'); + if (tab && headerEnd > 0) ctx.body = ctx.body.slice(0, headerEnd).replace(` { + if (ctx.method === 'GET' && /^recent$/i.test(String(ctx.query.filter || '')) && ['q', 'search'].some(k => String(ctx.query[k] || '').trim())) ctx.query = { ...ctx.query, filter: ctx.query.filter === 'RECENT' ? 'ALL' : 'all' }; await next(); }, - async (ctx, next) => { setLanguage(ctx.cookies.get("language") || getConfig().language || "en"); await next(); }, + async (ctx, next) => { await require('../models/typed_log').requestScope.run({ capped: false, limit: 0, path: ctx.path, query: ctx.querystring, method: ctx.method }, next); }, async (ctx, next) => { - const isBinary = ctx.path.startsWith('/qr') || ctx.path.startsWith('/image/') || ctx.path.startsWith('/blob/') || ctx.path.startsWith('/assets/'); + if (ctx.method !== 'POST') return next(); + const forget = () => { + censusCache.clear(); + lifetime.forget(); + try { activityModel.invalidateCache(); } catch (_) {} + clearnetDecisionCache = { at: 0, data: null }; + clearnetIndexCache = { ts: 0, data: null, building: null }; + clearnetBlobCache = { ts: 0, set: null, forcedAt: clearnetBlobCache.forcedAt }; + clearnetTribesCache = { at: 0, data: null }; + }; + forget(); + try { await next(); } finally { forget(); await settleLogWrites(); } + }, + publicModeGuard({ + isPublic: !!config.public, + onBlocked: (ctx) => sendErrorPage(ctx, "Sorry, many actions are unavailable when Oasis is running in public mode. Please run Oasis in the default mode and try again.", { status: 403 }) + }), + async (ctx, next) => { + if (isLoopbackOnlyPath(ctx.path) && !isLoopbackRequest(ctx)) { sendErrorPage(ctx, 'Forbidden', { status: 403 }); return; } + await next(); + }, + syncTribeModuleExposure, + async (ctx, next) => { applyFirstRunLanguage(ctx); setLanguage(isClearnetPath(ctx.request) ? clearnetLanguage(ctx) : (ctx.cookies.get("language") || getConfig().language || "en")); await next(); }, + async (ctx, next) => { + try { require('../views/comments_view').setCommentsOpen(ctx.method === 'GET' && String(ctx.query.comments || '') === 'open'); } catch (_) {} + await next(); + }, + async (ctx, next) => { + if (ctx.method === 'GET' && WALLET_CHIP_PATHS.some(p => ctx.path === p || ctx.path.startsWith(`${p}/`))) { + await settleWithin(refreshWalletReady(), WALLET_CHECK_WAIT_MS); + const kind = (ctx.path === '/transfers' || ctx.path.startsWith('/transfers/')) ? 'transfers' + : (ctx.path === '/banking' || ctx.path.startsWith('/banking/')) ? 'banking' : null; + if (kind) require('../models/typed_log').requestScope.run({ capped: false, limit: 0, path: '/', query: '' }, () => runWalletWork(kind)).catch(() => {}); + } + await next(); + }, + async (ctx, next) => { + await next(); + const flash = String((ctx.state && ctx.state.inlineError) || (ctx.method === 'GET' ? signedErrorOf(ctx) : '')).trim(); + if (!flash || typeof ctx.body !== 'string' || !/html/.test(String(ctx.type || ''))) return; + const { renderInlineError } = require('../views/main_views'); + const marker = ''; + const at = ctx.body.indexOf(marker); + if (at < 0) return; + const cleanUrl = ctx.method === 'GET' ? ctx.path + (() => { const q = new URLSearchParams(ctx.querystring); q.delete('error'); q.delete('errsig'); const s = q.toString(); return s ? `?${s}` : ''; })() : null; + ctx.body = ctx.body.slice(0, at + marker.length) + renderInlineError(flash, cleanUrl) + ctx.body.slice(at + marker.length); + }, + async (ctx, next) => { + const isBinary = ctx.path.startsWith('/qr') || ctx.path.startsWith('/wallet/qr/') || ctx.path.startsWith('/image/') || ctx.path.startsWith('/blob/') || ctx.path.startsWith('/assets/'); if (isBinary) { try { await next(); } catch (err) { ctx.status = err.status || 500; @@ -9331,9 +15132,17 @@ const middleware = [ } return; } - const ssb = await cooler.open(), status = await ssb.status(), values = Object.values(status.sync.plugins); - const totalCurrent = values.reduce((acc, cur) => acc + cur, 0), totalTarget = status.sync.since * values.length; - if (totalTarget - totalCurrent > 1024 * 1024) ctx.response.body = indexingView({ percent: Math.floor((totalCurrent / totalTarget) * 1000) / 10 }); + const migration = typeof SSBconfig.migrationStatus === 'function' ? SSBconfig.migrationStatus() : null; + if (migration && migration.running) { ctx.response.body = indexingView({ percent: migration.percent }); return; } + const until = Date.now() + STARTUP_WAIT_MS; + const ssb = await Promise.race([cooler.open(), new Promise((resolve) => setTimeout(() => resolve(null), STARTUP_WAIT_MS))]); + let status = ssb ? await ssb.status() : { ready: false }; + while (ssb && status && status.ready === false && Date.now() < until) { + await new Promise((resolve) => setTimeout(resolve, 150)); + status = await ssb.status(); + } + if (status && status.ready === false) ctx.response.body = indexingView({ percent: startupProgress(status), refresh: 5 }); + else if (indexingLag(status) > INDEXING_LAG_BYTES) ctx.response.body = indexingView({ percent: Math.floor(indexingProgress(status) * 1000) / 10 }); else { try { await next(); } catch (err) { const { i18n } = require('../views/main_views'); if (err.name === 'FileTooLargeError' || (err.message && err.message.includes('maxFileSize'))) { @@ -9347,97 +15156,11 @@ const middleware = [ }, async (ctx, next) => { if (!ctx.path.startsWith('/assets/') && !ctx.path.startsWith('/image/') && !ctx.path.startsWith('/blob/') && !ctx.path.startsWith('/qr') && !ctx.path.startsWith('/c/')) { - // filtro del hive (Personal/Community); lo leen la topbar y renderHiveNav + // filtro de la home (Personal/Community); lo leen la topbar y el lanzador global.__OASIS_HIVE_FILTER__ = (ctx.path === '/activity' && (ctx.query.hive === 'personal' || ctx.query.hive === 'community')) ? ctx.query.hive : ''; - // los hexagonos SOLO en la home; en el resto de paginas el top queda limpio + // el lanzador de modulos SOLO en la home; en el resto de paginas el top queda limpio global.__OASIS_SHOW_HIVE__ = (ctx.path === '/activity' || ctx.path === '/'); - const now = Date.now(); - if (now - sharedState.getLastRefresh() > 60000) { - sharedState.setLastRefresh(now); - try { - // avatar propio para el circulo de la topbar movil (lo lee renderMobileTopbar) - const meId = getViewerId(); - const selfImg = meId ? await about.image(meId) : null; - const NULL_IMG = '&' + '0'.repeat(43) + '=.sha256'; - if (typeof selfImg === 'string' && selfImg[0] === '&' && selfImg !== NULL_IMG) { - global.__OASIS_SELF_AVATAR__ = `/image/256/${encodeURIComponent(selfImg)}`; - } else if (typeof selfImg === 'string' && selfImg[0] === '/') { - global.__OASIS_SELF_AVATAR__ = selfImg; - } else { - global.__OASIS_SELF_AVATAR__ = null; - } - } catch (_) { } - try { - const stats = await statsModel.getStats('ALL'); - const totalMB = parseSizeMB(stats.statsBlobsSize) + parseSizeMB(stats.statsBlockchainSize); - const hcT = parseFloat((totalMB * 0.0002 * 475).toFixed(2)); - const inhabitants = stats.usersKPIs?.totalInhabitants || stats.inhabitants || 1; - const hcH = inhabitants > 0 ? parseFloat((hcT / inhabitants).toFixed(2)) : 0; - sharedState.setCarbonHcT(hcT); - sharedState.setCarbonHcH(hcH); - } catch (_) {} - try { - const dataRes = await dataModel.listMatches('ALL'); - const top = (dataRes.matches || [])[0] || null; - sharedState.setBestMatch(top ? { href: top.href, title: top.title || top.id, kind: top.kind, score: top.score } : null); - } catch (_) {} - try { await refreshInboxCount(); } catch (_) {} - try { await refreshMentionsCount(); } catch (_) {} - try { await calendarsModel.checkDueReminders(); } catch (_) {} - try { await tasksModel.checkDueReminders(); } catch (_) {} - try { await checkPoliticalChanges(); } catch (_) {} - try { await checkJobMatches(); } catch (_) {} - try { - const peers = await meta.connectedPeers(); - sharedState.setOnlinePeerCount(Array.isArray(peers) ? peers.length : 0); - } catch (_) {} - try { - sharedState.setInboxUnreadCount(sharedState.getInboxCount()); - sharedState.setLastSyncTs(now); - } catch (_) {} - try { - const ex = await bankingModel.calculateEcoinValue(); - if (ex && ex.isSynced) sharedState.setEcoValue(Number(ex.ecoValue).toFixed(4)); - } catch (_) {} - try { - const me = getViewerId(); - const actions = await activityModel.listFeed('all').catch(() => []); - const mine = (actions || []).filter(a => a && a.author === me && a.type !== 'tombstone' && a.type !== 'post'); - mine.sort((a, b) => (b.ts || 0) - (a.ts || 0)); - const prev = mine[1] || mine[0]; - if (prev) { - const hrefByType = { - vote: prev.content?.vote?.link ? `/thread/${encodeURIComponent(prev.content.vote.link)}#${encodeURIComponent(prev.content.vote.link)}` : null, - transfer: `/transfers/${encodeURIComponent(prev.id)}`, - tribe: `/tribe/${encodeURIComponent(prev.id)}`, - shop: `/shops/${encodeURIComponent(prev.id)}`, - job: `/jobs/${encodeURIComponent(prev.id)}`, - event: `/events/${encodeURIComponent(prev.id)}`, - project: `/projects/${encodeURIComponent(prev.id)}`, - image: `/images/${encodeURIComponent(prev.id)}`, - audio: `/audios/${encodeURIComponent(prev.id)}`, - video: `/videos/${encodeURIComponent(prev.id)}`, - document: `/documents/${encodeURIComponent(prev.id)}`, - torrent: `/torrents/${encodeURIComponent(prev.id)}`, - forum: `/forum/${encodeURIComponent(prev.content?.key || prev.id)}`, - bookmark: `/bookmarks/${encodeURIComponent(prev.id)}`, - task: `/tasks/${encodeURIComponent(prev.id)}`, - event_: `/events/${encodeURIComponent(prev.id)}`, - about: `/author/${encodeURIComponent(prev.author)}`, - map: `/maps/${encodeURIComponent(prev.id)}`, - chat: `/chats/${encodeURIComponent(prev.id)}`, - pad: `/pads/${encodeURIComponent(prev.id)}`, - pixelia: `/pixelia` - }; - sharedState.setLastActivity({ - id: prev.id, - type: prev.type, - ts: prev.ts, - href: hrefByType[prev.type] || `/activity?filter=mine` - }); - } - } catch (_) {} - } + if (Date.now() - sharedState.getLastRefresh() > 60000) kickRefresh(); } await next(); }, @@ -9446,28 +15169,146 @@ const middleware = [ getConfig, saveConfig, isLoopbackRequest, sendErrorPage }), routes, + async (ctx) => { + if (ctx.body != null || (ctx.method !== 'GET' && ctx.method !== 'HEAD')) return; + if (isClearnetPath(ctx.request) || ctx.path === '/clearnet' || ctx.path.startsWith('/clearnet/')) { + ctx.status = 404; + ctx.type = 'text/html'; + ctx.body = require('../views/clearnet_view').renderClearnetNotFound(); + return; + } + if (/^\/(blob|image|assets|qr|qr-invite)(\/|$)/.test(ctx.path)) return; + ctx.redirect('/'); + }, ]; const app = http({ host, port, middleware, allowHost: config.allowHost }); +if (ADMIN_TOKEN) console.log(`- Admin access (open it on this device): http://localhost:${port}/admin-session/${ADMIN_TOKEN}`); +const startDesktopNotices = async () => { + const { notify: desktopNotify, i18nNow, displayName } = require('./desktopNotify'); + const ssb = await cooler.open(); + if (!ssb || !ssb.db || typeof ssb.db.onMsgAdded !== 'function') return; + const me = ssb.id; + let lastEmergency = null; + try { const f = await emergenciesModel.featured(); lastEmergency = f && f.id; } catch (_) {} + const nameFor = async (id) => { try { return displayName(id, await about.name(id)); } catch (_) { return String(id); } }; + const isMutual = async (id) => { try { const rel = await friend.getRelationship(id); return !!(rel && rel.following && rel.followsMe); } catch (_) { return false; } }; + ssb.db.onMsgAdded((ev) => { + const kvt = ev && ev.kvt; + const v = kvt && kvt.value; + if (!v || v.author === me || Date.now() - (Number(v.timestamp) || 0) > 15 * 60 * 1000) return; + (async () => { + const i18n = i18nNow(); + const cfg = getConfig(); + if (typeof v.content === 'string') { + let c = null; + try { const dec = ssb.private.unbox({ key: kvt.key, value: v, timestamp: kvt.timestamp }); c = dec && dec.value && dec.value.content; } catch (_) {} + if (!c || c.private !== true || !Array.isArray(c.to) || !c.to.includes(me)) return; + if (cfg.pmVisibility === 'mutuals' && !(await isMutual(v.author))) return; + if (c.type === 'pam') { + if (cfg.modules.phoneMod !== 'off') desktopNotify(i18n.phoneTitle, `${i18n.notifyNewPamFrom} ${await nameFor(v.author)}`); + return; + } + if (c.type !== 'post') return; + const bot = pmModel.botOf({ subject: String(c.subject || '') }); + if (bot && pmModel.mutedBots(cfg).has(bot)) return; + desktopNotify(i18n.inbox, `${i18n.notifyNewPmFrom} ${bot || await nameFor(v.author)}`); + return; + } + const t = v.content && v.content.type; + if (typeof t === 'string' && t.startsWith('emergency') && cfg.modules.emergenciesMod === 'on') { + const f = await emergenciesModel.featured().catch(() => null); + if (!f || !f.id || f.id === lastEmergency) return; + lastEmergency = f.id; + if (sharedState.getDismissedEmergency && sharedState.getDismissedEmergency() === f.id) return; + desktopNotify(i18n.emergenciesTitle, f.title); + } + })().catch(() => {}); + }, false); +}; +if (!config.public) setTimeout(() => { phoneModel.start().catch(() => {}); startDesktopNotices().catch(() => {}); syncClearnetSince().catch(() => {}); }, 3000); let pubEngineTimer = null; +let pubAddressEnsured = false; +let pubEngineRunning = false; async function runPubEngineTick() { - if (!bankingModel.isPubNode()) return; - try { await bankingModel.executeEpoch({}); } catch (_) {} - try { await bankingModel.processPendingClaims(); } catch (_) {} - try { await bankingModel.publishPubAvailability(); } catch (_) {} + if (!bankingModel.isPubNode() || pubEngineRunning) return; + pubEngineRunning = true; + try { + if (!pubAddressEnsured) { try { const r = await bankingModel.ensureSelfAddressPublished(); pubAddressEnsured = r && r.status !== 'skipped'; } catch (_) {} } + try { await bankingModel.executeEpoch({}); } catch (_) {} + try { await bankingModel.processPendingClaims(); } catch (_) {} + try { await bankingModel.confirmIncomingTransfers(); } catch (_) {} + try { await bankingModel.rebalanceUbiPools(); } catch (_) {} + try { await bankingModel.publishPubAvailability(); } catch (_) {} + } finally { + pubEngineRunning = false; + } +} +const ubiNoticePath = process.env.OASIS_BANKING_DIR ? path.join(process.env.OASIS_BANKING_DIR, 'banking-ubi-notice.json') : stateFilePath('banking-ubi-notice.json'); +const confirmNoticePath = process.env.OASIS_BANKING_DIR ? path.join(process.env.OASIS_BANKING_DIR, 'banking-confirm-notice.json') : stateFilePath('banking-confirm-notice.json'); +async function notifyPendingConfirmations() { + if (getConfig().modules?.transfersMod === 'off') return; + try { + const me = getViewerId(); + const list = await transfersModel.listAll('all', me).catch(() => []); + const pending = (list || []).filter(t => { + const tags = Array.isArray(t.tags) ? t.tags.map(x => String(x).toUpperCase()) : []; + const settledUbi = tags.includes('UBI') && /^[0-9a-f]{64}$/i.test(String(t.txid || '')); + return String(t.status || '').toUpperCase() === 'UNCONFIRMED' + && String(t.to) === String(me) + && !(Array.isArray(t.confirmedBy) ? t.confirmedBy : []).includes(me) + && !tags.includes('PENDING') + && !settledUbi; + }); + if (!pending.length) return; + let notified = []; + try { notified = JSON.parse(fs.readFileSync(confirmNoticePath, 'utf8')) || []; } catch (_) {} + const fresh = pending.filter(t => !notified.includes(t.id)); + if (!fresh.length) return; + const { i18n: i18nB } = require('../views/main_views'); + const lines = fresh.slice(0, 5).map(t => `· [${t.concept || t.id}](/transfers/${encodeURIComponent(t.id)}) — ${Number(t.amount || 0).toFixed(6)} ECO`).join('\n'); + await notifyBot('BANKING_CONFIRM_PENDING', [me], `${i18nB.bankingBotConfirmText} (${fresh.length}):\n${lines}\n\n[${i18nB.transfersFilterPending}](/transfers?filter=pending)`); + fs.writeFileSync(confirmNoticePath, JSON.stringify([...notified, ...fresh.map(t => t.id)].slice(-500))); + } catch (_) {} +} + +const lastWalletWork = { transfers: 0, banking: 0 }; +async function runWalletWork(kind) { + if (bankingModel.isPubNode()) return; + if (Date.now() - (lastWalletWork[kind] || 0) < 60000) return; + lastWalletWork[kind] = Date.now(); + try { await bankingModel.confirmIncomingTransfers(); } catch (_) {} + if (kind === 'transfers') { try { await notifyPendingConfirmations(); } catch (_) {} return; } + try { + const avail = await bankingModel.claimAvailability(getViewerId()); + if (!avail.available) return; + let notified = {}; + try { notified = JSON.parse(fs.readFileSync(ubiNoticePath, 'utf8')) || {}; } catch (_) {} + if (notified.epochId === avail.epochId) return; + const { i18n: i18nB } = require('../views/main_views'); + await notifyBot('BANKING_UBI_AVAILABLE', [getViewerId()], `${i18nB.bankingBotUbiAvailableText}: [${i18nB.bankClaimUBI}](/banking?filter=exchange) · ${avail.epochId}`); + fs.writeFileSync(ubiNoticePath, JSON.stringify({ epochId: avail.epochId, at: new Date().toISOString() })); + } catch (_) {} +} +const PUB_INVITES_REFRESH_MS = 24 * 60 * 60 * 1000; +if (ssbConfig.pub === true) { + const announcePubInvites = async () => { try { await pubInvites.announce(await cooler.open()); } catch (_) {} }; + setTimeout(announcePubInvites, 60000); + setInterval(announcePubInvites, PUB_INVITES_REFRESH_MS); } if (bankingModel.isPubNode()) { + console.log(`[UBI] PUB engine on: paying UBI through ecoind at ${getConfig().wallet.url}`); setTimeout(() => { runPubEngineTick(); }, 15000); pubEngineTimer = setInterval(runPubEngineTick, 30 * 60 * 1000); } -setTimeout(() => { larpModel.init().catch(() => {}); }, 10000); +setTimeout(() => { if (getConfig().modules?.larpMod === 'on') larpModel.init().catch(() => {}); }, 10000); let welcomePmAttempted = false; async function sendWelcomePmIfFirstLaunch() { if (welcomePmAttempted) return; welcomePmAttempted = true; - const flagPath = path.join(ssbConfig.path, 'oasis-first-contact'); + const flagPath = stateFilePath('oasis-first-contact'); try { const ssbClient = await cooler.open(); if (!ssbClient || !ssbClient.id) { welcomePmAttempted = false; return; } @@ -9502,9 +15343,47 @@ async function sendWelcomePmIfFirstLaunch() { welcomePmAttempted = false; } } +let firstRunLanguageSettled = false; +const supportedLanguages = () => Object.keys(require('../client/assets/translations/i18n')); +function clearnetLanguage(ctx) { + const supported = supportedLanguages(); + const wanted = String(ctx.query.lang || '').trim().toLowerCase(); + if (supported.includes(wanted)) { + const store = require('../models/typed_log').requestScope.getStore(); + if (store) store.cnLang = wanted; + return wanted; + } + const detected = require('../models/onboarding_model').browserLanguage(ctx.get('accept-language'), supported); + return detected || getConfig().language || 'en'; +} +function applyFirstRunLanguage(ctx) { + if (firstRunLanguageSettled || isClearnetPath(ctx.request)) return; + const cfg = getConfig(); + const decision = require('../models/onboarding_model').firstRunLanguage({ + fresh: !onboardingModel.firstContactSeen(), + isPublic: !!config.public, + configured: cfg.language, + cookie: ctx.cookies.get('language'), + header: ctx.get('accept-language'), + supported: supportedLanguages() + }); + if (!decision.settled) return; + firstRunLanguageSettled = true; + if (decision.language && decision.language !== cfg.language) { + cfg.language = decision.language; + saveConfig(cfg); + } + setTimeout(welcomePmTick, 0); +} let welcomePmRetries = 0; const welcomePmTick = async () => { if (onboardingModel.firstContactSeen()) return; + if (!firstRunLanguageSettled) { + const cfg = getConfig(); + const decision = require('../models/onboarding_model').firstRunLanguage({ fresh: true, isPublic: !!config.public, configured: cfg.language, supported: supportedLanguages() }); + if (!decision.settled) return; + firstRunLanguageSettled = true; + } await sendWelcomePmIfFirstLaunch(); if (onboardingModel.firstContactSeen()) return; if (welcomePmRetries++ >= 30) return; @@ -9605,7 +15484,7 @@ async function checkJobMatches() { (t.jobsBotMatchHint || 'You receive this because your curriculum is AI managed. You can turn it off in your CV.') ].filter(Boolean).join('\n'); try { - await pmModel.sendMessage([], 'JOB_MATCH', body, false, ref); + await notifyBot('JOB_MATCH', [], body, false, ref); announced.add(`JOB_MATCH|${ref}`); } catch (e) { if (config.debug) console.error('[jobs-bot] send failed:', e && e.message); @@ -9639,7 +15518,7 @@ async function checkPoliticalChanges() { return; } try { - await pmModel.sendMessage([], subject, body, false, ref); + await notifyBot(subject, [], body, { ref }); announced.add(`${subject}|${ref}`); seen[subject] = String(ref); pmPolicy.writeAnnounceSeen(seen); @@ -9668,7 +15547,7 @@ async function checkPoliticalChanges() { const ref = [String(card.id || card.method || ''), String(card.since || '')].filter(Boolean).join(':'); if (ref && !alreadyAnnounced('PARLIAMENT_GOV', ref)) { let population = 0; - try { const inh = await inhabitantsModel.listInhabitants({ filter: 'all', includeInactive: true }); population = Array.isArray(inh) ? inh.length : 0; } catch (_) {} + try { population = Number(await inhabitantsModel.countInhabitants()) || 0; } catch (_) {} const leaderId = (card.powerType === 'inhabitant' && card.powerId) ? card.powerId : null; const { subject, body } = buildParliamentGovMsg({ method: card.method, since: card.since, end: card.end, population, leaderId }, t); await send(subject, body, ref); @@ -9708,19 +15587,8 @@ async function logClearnetStatus() { const ssbClient = await cooler.open(); if (!ssbClient || !ssbClient.id) return; const prefs = await about.visibilityPrefs(ssbClient.id).catch(() => null); - const modules = [ - ['Shops', 'clearnetShops'], - ['Jobs', 'clearnetJobs'], - ['Events', 'clearnetEvents'], - ['Projects', 'clearnetProjects'], - ['Blogs', 'clearnetPosts'], - ['Audios', 'clearnetAudios'], - ['Videos', 'clearnetVideos'], - ['Images', 'clearnetImages'], - ['Documents', 'clearnetDocuments'], - ['Torrents', 'clearnetTorrents'] - ]; - const active = prefs ? modules.filter(([_, k]) => prefs[k] === true).map(([label]) => label) : []; + const { CLEARNET_MODULES } = require('../views/main_views'); + const active = prefs ? CLEARNET_MODULES.filter(m => prefs[m.prefKey] === true).map(m => m.label) : []; try { const { setClearnetModules } = require('../server/ssb_metadata'); setClearnetModules(active); diff --git a/src/backend/blobHandler.js b/src/backend/blobHandler.js index dfa6e17..abee774 100644 --- a/src/backend/blobHandler.js +++ b/src/backend/blobHandler.js @@ -5,11 +5,13 @@ const ssb = require("../client/gui"); const config = require("../server/SSB_server").config; const cooler = ssb({ offline: config.offline }); -let sharp; -try { - sharp = require("sharp"); -} catch (e) { -} +let sharpLib; +const getSharp = () => { + if (sharpLib === undefined) { + try { sharpLib = require('../server/node_modules/sharp'); } catch (_) { sharpLib = null; } + } + return sharpLib; +}; /** * Quitar metadatos de una imagen SIN sharp. @@ -161,7 +163,8 @@ const stripMetadataPure = (buffer) => { const stripImageMetadata = async (buffer) => { // Con sharp se prefiere su camino: ademas de quitar metadatos, aplica la // orientacion girando los pixeles de verdad. - if (typeof sharp === "function") { + const sharp = getSharp(); + if (sharp) { try { return await sharp(buffer).rotate().toBuffer(); } catch { @@ -198,7 +201,7 @@ const stripPdfMetadata = (buffer) => { } }; -const MAX_BLOB_SIZE = 50 * 1024 * 1024; +const MAX_BLOB_SIZE = 75 * 1024 * 1024; class FileTooLargeError extends Error { constructor(fileName, fileSize) { @@ -346,6 +349,52 @@ function waitForBlob(ssbClient, blobId, timeoutMs = 8000) { }); } +const contentDisposition = (type, name) => { + const clean = String(name || 'file').replace(/["\r\n\\/]/g, '').trim() || 'file'; + const ascii = clean.replace(/[^\x20-\x7e]/g, '_'); + if (ascii === clean) return `${type}; filename="${clean}"`; + return `${type}; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(clean).replace(/['()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase())}`; +}; + +const sendBlobBuffer = (ctx, buffer) => { + const size = buffer.length; + const range = ctx.headers.range; + + if (range) { + const match = /^bytes=(\d*)-(\d*)$/.exec(range); + if (!match) { + ctx.status = 416; + ctx.set('Content-Range', `bytes */${size}`); + return; + } + + let start = match[1] ? parseInt(match[1], 10) : 0; + let end = match[2] ? parseInt(match[2], 10) : size - 1; + + if (Number.isNaN(start) || start < 0) start = 0; + if (Number.isNaN(end) || end >= size) end = size - 1; + + if (start > end || start >= size) { + ctx.status = 416; + ctx.set('Content-Range', `bytes */${size}`); + return; + } + + const chunk = buffer.slice(start, end + 1); + + ctx.status = 206; + ctx.set('Content-Range', `bytes ${start}-${end}/${size}`); + ctx.set('Accept-Ranges', 'bytes'); + ctx.set('Content-Length', String(chunk.length)); + ctx.body = chunk; + } else { + ctx.status = 200; + ctx.set('Accept-Ranges', 'bytes'); + ctx.set('Content-Length', String(size)); + ctx.body = buffer; + } +}; + const serveBlob = async function (ctx) { const encodedParam = (ctx.params.id || ctx.params.blobId || '').trim(); const raw = decodeURIComponent(encodedParam); @@ -385,8 +434,6 @@ const serveBlob = async function (ctx) { return; } - const size = buffer.length; - let mime = 'application/octet-stream'; try { const ft = await FileType.fromBuffer(buffer); @@ -394,7 +441,7 @@ const serveBlob = async function (ctx) { } catch {} if (mime === 'application/octet-stream' && buffer.length > 10 && buffer[0] === 0x64) { - const head = buffer.slice(0, 128).toString('ascii'); + const head = buffer.slice(0, 512).toString('ascii'); if (head.includes('announce') || head.includes('8:announce') || head.includes('4:info')) mime = 'application/x-bittorrent'; } @@ -412,48 +459,17 @@ const serveBlob = async function (ctx) { const isSvg = mime === 'image/svg+xml'; const qName = ctx.query.name ? String(ctx.query.name).replace(/["\r\n\\]/g, '').trim() : ''; const safeRaw = String(raw).replace(/["\r\n\\]/g, ''); - const filename = qName || (mime === 'application/x-bittorrent' ? 'download.torrent' : safeRaw); - const disposition = isSvg ? 'attachment' : 'inline'; + const wantsDownload = String(ctx.query.download || '') === '1'; + const extByMime = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp', 'image/svg+xml': 'svg', 'video/mp4': 'mp4', 'video/webm': 'webm', 'audio/mpeg': 'mp3', 'audio/ogg': 'ogg', 'application/pdf': 'pdf', 'application/x-bittorrent': 'torrent' }; + const ext = extByMime[mime] || String(mime.split('/')[1] || 'bin').replace(/[^a-z0-9]/gi, '').slice(0, 8) || 'bin'; + const filename = qName || (mime === 'application/x-bittorrent' ? 'download.torrent' : (wantsDownload ? `oasis-${safeRaw.replace(/^&/, '').slice(0, 8)}.${ext}` : safeRaw)); + const disposition = isSvg || wantsDownload ? 'attachment' : 'inline'; ctx.type = mime; - ctx.set('Content-Disposition', `${disposition}; filename="${filename}"`); + ctx.set('Content-Disposition', contentDisposition(disposition, filename)); ctx.set('Cache-Control', 'public, max-age=31536000, immutable'); - const range = ctx.headers.range; - - if (range) { - const match = /^bytes=(\d*)-(\d*)$/.exec(range); - if (!match) { - ctx.status = 416; - ctx.set('Content-Range', `bytes */${size}`); - return; - } - - let start = match[1] ? parseInt(match[1], 10) : 0; - let end = match[2] ? parseInt(match[2], 10) : size - 1; - - if (Number.isNaN(start) || start < 0) start = 0; - if (Number.isNaN(end) || end >= size) end = size - 1; - - if (start > end || start >= size) { - ctx.status = 416; - ctx.set('Content-Range', `bytes */${size}`); - return; - } - - const chunk = buffer.slice(start, end + 1); - - ctx.status = 206; - ctx.set('Content-Range', `bytes ${start}-${end}/${size}`); - ctx.set('Accept-Ranges', 'bytes'); - ctx.set('Content-Length', String(chunk.length)); - ctx.body = chunk; - } else { - ctx.status = 200; - ctx.set('Accept-Ranges', 'bytes'); - ctx.set('Content-Length', String(size)); - ctx.body = buffer; - } + sendBlobBuffer(ctx, buffer); }; -module.exports = { handleBlobUpload, handleBlobUploads, serveBlob, oggMime, FileTooLargeError }; +module.exports = { handleBlobUpload, handleBlobUploads, serveBlob, sendBlobBuffer, contentDisposition, oggMime, FileTooLargeError, stripImageMetadata }; diff --git a/src/backend/contentPdf.js b/src/backend/contentPdf.js deleted file mode 100644 index c84e05b..0000000 --- a/src/backend/contentPdf.js +++ /dev/null @@ -1,280 +0,0 @@ -const { buildDocumentPdf } = require('./pdfDocument'); - -const fmtDate = v => { - if (!v) return ''; - const d = new Date(v); - return isNaN(d.getTime()) ? String(v) : d.toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; -}; - -const fmtDay = v => { - if (!v) return ''; - const d = new Date(v); - return isNaN(d.getTime()) ? String(v) : d.toISOString().slice(0, 10); -}; - -const asList = v => (Array.isArray(v) ? v : []); -const txt = v => (v == null ? '' : String(v)); - -const humanLabel = (key) => String(key || '') - .replace(/([a-z0-9])([A-Z])/g, '$1 $2') - .replace(/^./, c => c.toUpperCase()); - -const pushMeta = (out, item) => { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'METADATA' }); - if (item.id) out.push({ kind: 'kv', label: 'Content ID', value: item.id }); - const author = item.author || item.organizer || item.createdBy || item.from || ''; - if (author) out.push({ kind: 'kv', label: 'Author', value: author }); - if (item.createdAt) out.push({ kind: 'kv', label: 'Created At', value: fmtDate(item.createdAt) }); - if (item.updatedAt) out.push({ kind: 'kv', label: 'Updated At', value: fmtDate(item.updatedAt) }); -}; - -const pushOpinions = (out, item) => { - const op = item.opinions && typeof item.opinions === 'object' ? item.opinions : {}; - const entries = Object.entries(op).filter(([, n]) => Number(n) > 0); - if (!entries.length) return; - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'OPINIONS' }); - for (const [cat, n] of entries) out.push({ kind: 'kv', label: humanLabel(cat), value: String(n) }); -}; - -const pushTags = (out, item) => { - const tags = asList(item.tags).filter(Boolean); - if (tags.length) out.push({ kind: 'kv', label: 'Tags', value: tags.join(', ') }); -}; - -const reportSections = (report) => { - const out = []; - out.push({ kind: 'title', text: `Report: ${txt(report.title) || '-'}` }); - out.push({ kind: 'blank' }); - - out.push({ kind: 'section', text: 'CLASSIFICATION' }); - out.push({ kind: 'kv', label: 'Category', value: txt(report.category).toUpperCase() }); - out.push({ kind: 'kv', label: 'Severity', value: txt(report.severity).toUpperCase() }); - out.push({ kind: 'kv', label: 'Status', value: txt(report.status).toUpperCase() }); - pushTags(out, report); - - if (txt(report.description).trim()) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'DESCRIPTION' }); - out.push({ kind: 'text', text: report.description }); - } - - const tpl = report.template && typeof report.template === 'object' ? report.template : {}; - const tplEntries = Object.entries(tpl).filter(([, v]) => txt(v).trim()); - if (tplEntries.length) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'DETAILS' }); - for (const [k, v] of tplEntries) out.push({ kind: 'kv', label: humanLabel(k), value: v }); - } - - const confirmations = asList(report.confirmations); - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'CONFIRMATIONS' }); - out.push({ kind: 'kv', label: 'Confirmed', value: String(confirmations.length) }); - for (const c of confirmations) out.push({ kind: 'kv', label: 'Confirmed by', value: c }); - - pushOpinions(out, report); - pushMeta(out, report); - return out; -}; - -const voteSections = (vote) => { - const out = []; - out.push({ kind: 'title', text: `Votation: ${txt(vote.question) || '-'}` }); - out.push({ kind: 'blank' }); - - out.push({ kind: 'section', text: 'TERMS' }); - out.push({ kind: 'kv', label: 'Status', value: txt(vote.status).toUpperCase() }); - if (vote.deadline) out.push({ kind: 'kv', label: 'Deadline', value: fmtDate(vote.deadline) }); - pushTags(out, vote); - - const votes = vote.votes && typeof vote.votes === 'object' ? vote.votes : {}; - const total = Number(vote.totalVotes) || asList(vote.voters).length; - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'RESULTS' }); - out.push({ kind: 'kv', label: 'Total votes', value: String(total) }); - for (const [choice, n] of Object.entries(votes)) { - const count = Number(n) || 0; - const pct = total > 0 ? Math.round((count / total) * 100) : 0; - out.push({ kind: 'kv', label: choice, value: `${count} (${pct}%)` }); - } - - pushOpinions(out, vote); - pushMeta(out, vote); - return out; -}; - -const eventSections = (event) => { - const out = []; - out.push({ kind: 'title', text: `Event: ${txt(event.title) || '-'}` }); - out.push({ kind: 'blank' }); - - out.push({ kind: 'section', text: 'SCHEDULE' }); - out.push({ kind: 'kv', label: 'Date', value: fmtDate(event.date) }); - out.push({ kind: 'kv', label: 'Status', value: txt(event.status).toUpperCase() }); - if (event.location) out.push({ kind: 'kv', label: 'Location', value: event.location }); - if (event.mapUrl) out.push({ kind: 'kv', label: 'Map', value: event.mapUrl }); - if (Number(event.price) > 0) out.push({ kind: 'kv', label: 'Price', value: `${Number(event.price)} ECO` }); - out.push({ kind: 'kv', label: 'Privacy', value: txt(event.isPublic).toUpperCase() }); - if (event.url) out.push({ kind: 'kv', label: 'Url', value: event.url }); - pushTags(out, event); - - if (txt(event.description).trim()) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'DESCRIPTION' }); - out.push({ kind: 'text', text: event.description }); - } - - const attendees = asList(event.attendees); - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'ATTENDEES' }); - out.push({ kind: 'kv', label: 'Total', value: String(attendees.length) }); - for (const a of attendees) out.push({ kind: 'kv', label: 'Attendee', value: a }); - - pushOpinions(out, event); - pushMeta(out, event); - return out; -}; - -const taskSections = (task) => { - const out = []; - out.push({ kind: 'title', text: `Task: ${txt(task.title) || '-'}` }); - out.push({ kind: 'blank' }); - - out.push({ kind: 'section', text: 'SCHEDULE' }); - out.push({ kind: 'kv', label: 'Status', value: txt(task.status).toUpperCase() }); - out.push({ kind: 'kv', label: 'Priority', value: txt(task.priority).toUpperCase() }); - if (task.startTime) out.push({ kind: 'kv', label: 'Starts', value: fmtDate(task.startTime) }); - if (task.endTime) out.push({ kind: 'kv', label: 'Ends', value: fmtDate(task.endTime) }); - if (task.location) out.push({ kind: 'kv', label: 'Location', value: task.location }); - out.push({ kind: 'kv', label: 'Privacy', value: txt(task.isPublic).toUpperCase() }); - pushTags(out, task); - - if (txt(task.description).trim()) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'DESCRIPTION' }); - out.push({ kind: 'text', text: task.description }); - } - - const assignees = asList(task.assignees); - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'ASSIGNEES' }); - out.push({ kind: 'kv', label: 'Total', value: String(assignees.length) }); - for (const a of assignees) out.push({ kind: 'kv', label: 'Assignee', value: a }); - - pushOpinions(out, task); - pushMeta(out, task); - return out; -}; - -const calendarSections = (calendar, extra = {}) => { - const dates = asList(extra.dates); - const notesByDate = extra.notesByDate && typeof extra.notesByDate === 'object' ? extra.notesByDate : {}; - - const out = []; - out.push({ kind: 'title', text: `Calendar: ${txt(calendar.title) || '-'}` }); - out.push({ kind: 'blank' }); - - out.push({ kind: 'section', text: 'SUMMARY' }); - out.push({ kind: 'kv', label: 'Status', value: calendar.isClosed ? 'CLOSED' : txt(calendar.status).toUpperCase() }); - if (calendar.deadline) out.push({ kind: 'kv', label: 'Deadline', value: fmtDate(calendar.deadline) }); - if (calendar.mapUrl) out.push({ kind: 'kv', label: 'Map', value: calendar.mapUrl }); - out.push({ kind: 'kv', label: 'Participants', value: String(asList(calendar.participants).length) }); - out.push({ kind: 'kv', label: 'Dates', value: String(dates.length) }); - const noteTotal = Object.values(notesByDate).reduce((n, arr) => n + asList(arr).length, 0); - out.push({ kind: 'kv', label: 'Notes', value: String(noteTotal) }); - pushTags(out, calendar); - - const participants = asList(calendar.participants); - if (participants.length) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'PARTICIPANTS' }); - for (const p of participants) out.push({ kind: 'kv', label: 'Participant', value: p }); - } - - if (dates.length) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'DATES' }); - for (const d of dates) { - const day = fmtDay(d && d.date) || txt(d && d.date); - out.push({ kind: 'kv', label: day, value: txt(d && d.label) }); - for (const note of asList(notesByDate[d && d.key])) { - const noteText = txt(note && note.text); - if (noteText) out.push({ kind: 'text', text: ` - ${noteText}` }); - } - } - } - - pushMeta(out, calendar); - return out; -}; - -const cvSections = (cv) => { - const out = []; - out.push({ kind: 'title', text: `Curriculum: ${txt(cv.name) || txt(cv.author) || '-'}` }); - out.push({ kind: 'blank' }); - - out.push({ kind: 'section', text: 'PROFILE' }); - if (cv.location) out.push({ kind: 'kv', label: 'Location', value: cv.location }); - if (cv.status) out.push({ kind: 'kv', label: 'Status', value: txt(cv.status).toUpperCase() }); - if (cv.preferences) out.push({ kind: 'kv', label: 'Preferences', value: txt(cv.preferences).toUpperCase() }); - if (cv.languages) out.push({ kind: 'kv', label: 'Languages', value: cv.languages }); - - if (txt(cv.description).trim()) { - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: 'DESCRIPTION' }); - out.push({ kind: 'text', text: cv.description }); - } - - const blocks = [ - ['PERSONAL', cv.personalExperiences, cv.personalSkills], - ['EDUCATION', cv.educationExperiences, cv.educationalSkills], - ['PROFESSIONAL', cv.professionalExperiences, cv.professionalSkills], - ['OASIS', cv.oasisExperiences, cv.oasisSkills] - ]; - - for (const [heading, experiences, skills] of blocks) { - const skillList = asList(skills).filter(Boolean); - const body = txt(experiences).trim(); - if (!body && !skillList.length) continue; - out.push({ kind: 'blank' }); - out.push({ kind: 'section', text: heading }); - if (body) out.push({ kind: 'text', text: body }); - if (skillList.length) out.push({ kind: 'kv', label: 'Skills', value: skillList.join(', ') }); - } - - pushMeta(out, cv); - return out; -}; - -const BUILDERS = { - reports: { title: 'OASIS - Report', sections: reportSections, name: item => item.title }, - votes: { title: 'OASIS - Votation', sections: voteSections, name: item => item.question }, - events: { title: 'OASIS - Event', sections: eventSections, name: item => item.title }, - tasks: { title: 'OASIS - Task', sections: taskSections, name: item => item.title }, - calendars: { title: 'OASIS - Calendar', sections: calendarSections, name: item => item.title }, - cv: { title: 'OASIS - Curriculum', sections: cvSections, name: item => item.name || item.author } -}; - -const isSupported = kind => Object.prototype.hasOwnProperty.call(BUILDERS, kind); - -const pdfFilename = (kind, item) => { - const b = BUILDERS[kind]; - const raw = b ? txt(b.name(item || {})) : ''; - const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40); - return `oasis-${kind}-${slug || 'document'}.pdf`; -}; - -const buildContentPdf = (kind, item, extra = {}, viewerId = null) => { - const b = BUILDERS[kind]; - if (!b) throw new Error('Unsupported pdf kind'); - return buildDocumentPdf({ - title: b.title, - issuedToLabel: 'Issued to', - issuedTo: viewerId || null, - sections: b.sections(item || {}, extra) - }); -}; - -module.exports = { buildContentPdf, pdfFilename, isSupported }; diff --git a/src/backend/content_favorites.js b/src/backend/content_favorites.js index a3d86b8..1d7540c 100644 --- a/src/backend/content_favorites.js +++ b/src/backend/content_favorites.js @@ -1,19 +1,15 @@ const fs = require("fs"); const path = require("path"); -const TEMPLATE = path.join(__dirname, "../configs/content_favorites.json"); - -const storePath = () => { - try { - return require("../server/ssb_config").statePath("content_favorites.json") || TEMPLATE; - } catch (_) { return TEMPLATE; } -}; +const storePath = () => require("../configs/state-manager").statePath("content_favorites.json"); const DEFAULT = { + emergencies: [], audios: [], blogs: [], bookmarks: [], calendars: [], + campaigns: [], chats: [], documents: [], events: [], @@ -21,20 +17,27 @@ const DEFAULT = { housing: [], images: [], jobs: [], + logistics: [], logs: [], + mailing: [], maps: [], pads: [], + rooms: [], + podcasts: [], polls: [], projects: [], reports: [], + school: [], shops: [], market: [], shopProducts: [], tasks: [], torrents: [], + files: [], transfers: [], videos: [], - votes: [] + votes: [], + wiki: [] }; const safeArr = (v) => (Array.isArray(v) ? v : []); diff --git a/src/backend/dedupe.js b/src/backend/dedupe.js index c5d9e97..c0d3d5b 100644 --- a/src/backend/dedupe.js +++ b/src/backend/dedupe.js @@ -49,4 +49,79 @@ const dedupeByPreferring = (items, keyFn, scoreFn) => { return order.map(o => (o.solo !== undefined ? o.solo : o.slot.v)); }; -module.exports = { dedupeBy, mergeDuplicatesBy, dedupeByPreferring, norm }; +const versionGroups = (entries) => { + const byKey = new Map(); + for (const e of (entries || [])) if (e && e.key) byKey.set(e.key, e); + const memo = new Map(); + const rootOf = (key) => { + if (memo.has(key)) return memo.get(key); + const path = []; + const seen = new Set([key]); + let cur = key; + let root = null; + while (root === null) { + if (memo.has(cur)) { root = memo.get(cur); break; } + const e = byKey.get(cur); + const p = e && e.replaces; + if (!p || !byKey.has(p) || seen.has(p)) { root = cur; break; } + path.push(cur); + seen.add(p); + cur = p; + } + memo.set(cur, root); + for (const k of path) memo.set(k, root); + return root; + }; + const isOrphan = (key) => { + const e = byKey.get(key); + return !!(e && e.replaces && !byKey.has(e.replaces)); + }; + const isForeignCopy = (key) => { + const e = byKey.get(key); + return !!(e && isOrphan(key) && typeof e.claimedAuthor === 'string' && e.claimedAuthor && e.claimedAuthor !== e.author); + }; + const groups = new Map(); + for (const k of byKey.keys()) { + const r = rootOf(k); + if (!groups.has(r)) groups.set(r, []); + groups.get(r).push(k); + } + const later = (a, b) => (a.seq || 0) > (b.seq || 0) || ((a.seq || 0) === (b.seq || 0) && (a.ts || 0) >= (b.ts || 0)); + for (const [r, keys] of Array.from(groups.entries())) { + if (!isForeignCopy(r)) continue; + const owner = byKey.get(r).claimedAuthor; + let first = null; + for (const k of keys) { const e = byKey.get(k); if (e.author === owner && (!first || later(byKey.get(first), e))) first = k; } + if (!first) continue; + groups.delete(r); + groups.set(first, keys); + for (const k of keys) memo.set(k, first); + } + const ownerTip = new Map(); + for (const [r, keys] of groups) { + const re = byKey.get(r); + for (const k of keys) { + const e = byKey.get(k); + if (!re || e.author !== re.author) continue; + const prev = ownerTip.has(r) ? byKey.get(ownerTip.get(r)) : null; + if (!prev || later(e, prev)) ownerTip.set(r, k); + } + } + const isForeign = (key) => { + const e = byKey.get(key); + const re = byKey.get(rootOf(key)); + return !!(e && re && e.author !== re.author); + }; + const roots = new Set(); + for (const r of groups.keys()) if (!isForeignCopy(r)) roots.add(r); + const tipOf = (key) => { const r = rootOf(key); return ownerTip.get(r) || r; }; + return { rootOf, tipOf, roots, groups, isOrphan, isForeign, isForeignCopy }; +}; + +const versionEntry = (m) => { + const v = (m && m.value) || {}; + const c = v.content || {}; + return { key: m && m.key, author: v.author, seq: v.sequence || 0, ts: v.timestamp || (m && m.timestamp) || 0, replaces: typeof c.replaces === 'string' ? c.replaces : null, claimedAuthor: typeof c.author === 'string' ? c.author : null }; +}; + +module.exports = { dedupeBy, mergeDuplicatesBy, dedupeByPreferring, norm, versionGroups, versionEntry }; diff --git a/src/backend/desktopNotify.js b/src/backend/desktopNotify.js new file mode 100644 index 0000000..c12a6ea --- /dev/null +++ b/src/backend/desktopNotify.js @@ -0,0 +1,80 @@ +const fs = require('fs'); +const path = require('path'); +const { spawn } = require('child_process'); +const { getConfig } = require('../configs/config-manager.js'); + +const REPEAT_MS = 60000; +const ICON = path.join(__dirname, '..', 'client', 'assets', 'images', 'snh-oasis.jpg'); +const recent = new Map(); + +const onPath = (bin) => (process.env.PATH || '').split(path.delimiter).some(d => d && fs.existsSync(path.join(d, bin))); + +const i18nNow = () => { + const all = require('../client/assets/translations/i18n'); + let lang = 'en'; + try { lang = getConfig().language || 'en'; } catch (_) {} + return { ...all.en, ...(all[lang] || {}) }; +}; + +const displayName = (id, name) => { + const n = String(name || '').trim(); + if (!n || n === 'Redacted' || n === String(id || '').replace(/^@/, '').slice(0, 8)) return String(id || ''); + return n.startsWith('@') ? n : `@${n}`; +}; + +const MOBILE_QUEUE = path.join(__dirname, '..', '..', '.notifications.json'); +const queueForAndroid = (keyword, body, line, key) => { + try { + let list = []; + try { list = JSON.parse(fs.readFileSync(MOBILE_QUEUE, 'utf8')); } catch (_) { list = []; } + if (!Array.isArray(list)) list = []; + const t = i18nNow(); + const k = String(keyword || ''); + const route = k === t.phoneTitle ? '/phone' : k === t.inbox ? '/inbox' : k === t.emergenciesTitle ? '/emergencies' : '/'; + const entry = { id: Date.now() + '-' + Math.random().toString(36).slice(2, 8), keyword: k || 'Oasis', body, line, route, key: key ? String(key) : null, ts: Date.now() }; + if (entry.key && entry.key.indexOf('call:') === 0) { + const ringing = String(t.phoneIsCalling || ''); + entry.caller = ringing && body.endsWith(ringing) ? body.slice(0, body.length - ringing.length).trim() : body; + entry.accept = String(t.phoneAnswer || ''); + entry.reject = String(t.phoneReject || ''); + } + list.push(entry); + while (list.length > 20) list.shift(); + fs.writeFileSync(MOBILE_QUEUE, JSON.stringify(list)); + return true; + } catch (_) { + return false; + } +}; + +const notify = (keyword, text, key = null, opts = {}) => { + const onMobile = process.env.OASIS_MOBILE === '1'; + if (!onMobile && !onPath('notify-send')) return false; + const body = String(text || '').replace(/\s+/g, ' ').trim().replace(/[.。!…]+$/, '').slice(0, 200); + const line = `${String(keyword || 'Oasis').toUpperCase()}: ${body}...`; + const now = Date.now(); + const dedupe = key || line; + for (const [k, t] of recent) if (now - t > REPEAT_MS) recent.delete(k); + if (recent.has(dedupe)) return false; + recent.set(dedupe, now); + if (onMobile) return queueForAndroid(keyword, body, line, key); + const actions = Array.isArray(opts.actions) ? opts.actions.filter(x => x && x.key && x.label) : []; + const extra = [ + ...(opts.timeoutMs > 0 ? ['-t', String(Math.round(opts.timeoutMs))] : []), + ...actions.flatMap(x => ['-A', `${x.key}=${x.label}`]) + ]; + try { + const p = spawn('notify-send', ['-a', 'Oasis', ...(fs.existsSync(ICON) ? ['-i', ICON] : []), ...extra, line], { stdio: ['ignore', actions.length ? 'pipe' : 'ignore', 'ignore'] }); + p.on('error', () => {}); + if (actions.length && typeof opts.onAction === 'function') { + let out = ''; + p.stdout.on('data', (d) => { out += d; }); + p.on('close', () => { const chosen = out.trim(); if (actions.some(x => x.key === chosen)) opts.onAction(chosen); }); + } + return true; + } catch (_) { + return false; + } +}; + +module.exports = { notify, i18nNow, displayName }; diff --git a/src/backend/logsPdf.js b/src/backend/logsPdf.js deleted file mode 100644 index 676ac90..0000000 --- a/src/backend/logsPdf.js +++ /dev/null @@ -1,206 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const LOGO_PATH = path.join(__dirname, '..', 'client', 'assets', 'images', 'snh-oasis.jpg'); - -const { escapePdf } = require('./pdfDocument'); - -const linkPattern = /(?:https?:\/\/[^\s]+|www\.[^\s]+|@[A-Za-z0-9+/=.\-]+\.ed25519|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/g; - -const splitSegments = (line) => { - const segs = []; - let last = 0; - const re = new RegExp(linkPattern.source, 'g'); - let m; - while ((m = re.exec(line)) !== null) { - if (m.index > last) segs.push({ t: line.slice(last, m.index), l: false }); - segs.push({ t: m[0], l: true }); - last = m.index + m[0].length; - } - if (last < line.length) segs.push({ t: line.slice(last), l: false }); - return segs; -}; - -const wrap = (txt, max = 82) => { - const out = []; - for (const raw of String(txt || '').split('\n')) { - let line = raw; - while (line.length > max) { - let cut = line.lastIndexOf(' ', max); - if (cut <= 0) cut = max; - out.push(line.slice(0, cut)); - line = line.slice(cut).replace(/^\s+/, ''); - } - out.push(line); - } - return out; -}; - -const readJpegDims = (buf) => { - let i = 2; - while (i < buf.length) { - if (buf[i] !== 0xFF) return null; - const marker = buf[i + 1]; - if (marker === 0xD8 || marker === 0xD9) { i += 2; continue; } - const len = buf.readUInt16BE(i + 2); - if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) { - const h = buf.readUInt16BE(i + 5); - const w = buf.readUInt16BE(i + 7); - const c = buf[i + 9]; - return { w, h, c }; - } - i += 2 + len; - } - return null; -}; - -function buildLogsPdf(entries, oasisId, opts = {}) { - const pageW = 612; - const pageH = 792; - const marginX = 50; - const headerH = 90; - const footerH = 40; - const bodyTop = pageH - headerH - 22; - const bodyBottom = footerH + 10; - const lineH = 12; - const maxBodyLines = Math.floor((bodyTop - bodyBottom) / lineH); - - let logoBuf = null; - let logoDims = null; - try { - logoBuf = fs.readFileSync(LOGO_PATH); - logoDims = readJpegDims(logoBuf); - } catch (_) {} - - const allLines = []; - for (const e of entries) { - const ts = new Date(e.ts); - const when = ts.toISOString().replace('T', ' ').slice(0, 19); - allLines.push({ kind: 'header', text: `[${when}]:` }); - allLines.push({ kind: 'blank', text: '' }); - for (const l of wrap(e.text, 82)) allLines.push({ kind: 'text', text: l }); - allLines.push({ kind: 'blank', text: '' }); - } - if (!allLines.length) allLines.push({ kind: 'text', text: '(no entries)' }); - - const pages = []; - for (let i = 0; i < allLines.length; i += maxBodyLines) { - pages.push(allLines.slice(i, i + maxBodyLines)); - } - if (!pages.length) pages.push([{ kind: 'text', text: '(no entries)' }]); - - const objects = []; - const addObj = body => { objects.push(body); return objects.length; }; - - const catalogId = addObj(null); - const pagesId = addObj(null); - const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); - const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); - - let logoXObjId = null; - if (logoBuf && logoDims) { - const colorSpace = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; - const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${colorSpace} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; - logoXObjId = addObj({ dict, stream: logoBuf }); - } - - const exportDate = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; - const footerLeft = `Generated: ${exportDate}`; - - const pageIds = []; - const contentIds = []; - - pages.forEach((pg, pgIdx) => { - const parts = []; - - if (logoXObjId) { - const logoH = 60; - const logoW = Math.round((logoDims.w / logoDims.h) * logoH); - const logoX = marginX; - const logoY = pageH - headerH + 15; - parts.push(`q\n${logoW} 0 0 ${logoH} ${logoX} ${logoY} cm\n/Logo Do\nQ`); - } - - const titleX = (logoXObjId ? marginX + 80 : marginX); - const titleY = pageH - 45; - parts.push(`BT\n/F2 16 Tf\n${titleX} ${titleY} Td\n(${escapePdf('OASIS - Experience logs')}) Tj\nET`); - const inhabitantPrefix = 'Inhabitant: '; - const inhabitantPrefixW = inhabitantPrefix.length * 5.4; - parts.push(`BT\n/F1 9 Tf\n${titleX} ${titleY - 16} Td\n(${escapePdf(inhabitantPrefix)}) Tj\nET`); - parts.push(`BT\n/F2 9 Tf\n${titleX + inhabitantPrefixW} ${titleY - 16} Td\n(${escapePdf(String(oasisId || ''))}) Tj\nET`); - - parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${pageH - headerH} m\n${pageW - marginX} ${pageH - headerH} l\nS\nQ`); - - let y = bodyTop; - const charW = 6; - for (const ln of pg) { - if (ln.kind === 'header') { - parts.push(`BT\n/F2 10 Tf\n1 0.647 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } else if (ln.text) { - const segs = splitSegments(ln.text); - let x = marginX; - for (const s of segs) { - if (!s.t) continue; - const color = s.l ? '0 0 1 rg' : '0 0 0 rg'; - parts.push(`BT\n/F1 10 Tf\n${color}\n${x} ${y} Td\n(${escapePdf(s.t)}) Tj\nET`); - x += s.t.length * charW; - } - } - y -= lineH; - } - - parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${footerH + 5} m\n${pageW - marginX} ${footerH + 5} l\nS\nQ`); - parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 10} Td\n(${escapePdf(footerLeft)}) Tj\nET`); - const pageLabel = `Page ${pgIdx + 1} of ${pages.length}`; - const pageLabelW = pageLabel.length * 4.8; - parts.push(`BT\n/F1 8 Tf\n${pageW - marginX - pageLabelW} ${footerH - 10} Td\n(${escapePdf(pageLabel)}) Tj\nET`); - - const content = parts.join('\n'); - const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; - const cid = addObj(stream); - contentIds.push(cid); - const pid = addObj(null); - pageIds.push(pid); - }); - - for (let i = 0; i < pageIds.length; i++) { - const resources = logoXObjId - ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << /Logo ${logoXObjId} 0 R >> >>` - : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; - objects[pageIds[i] - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentIds[i]} 0 R /Resources ${resources} >>`; - } - - objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; - objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; - - const chunks = []; - const offsets = [0]; - let byteLen = 0; - const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; - - push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); - - for (let i = 0; i < objects.length; i++) { - offsets.push(byteLen); - const obj = objects[i]; - if (obj && typeof obj === 'object' && obj.dict && obj.stream) { - push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); - push(obj.stream); - push(Buffer.from('\nendstream\nendobj\n', 'binary')); - } else { - push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); - } - } - - const xrefStart = byteLen; - let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; - for (let i = 1; i <= objects.length; i++) { - xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; - } - xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; - push(Buffer.from(xref, 'binary')); - - return Buffer.concat(chunks); -} - -module.exports = { buildLogsPdf }; diff --git a/src/backend/long_text.js b/src/backend/long_text.js new file mode 100644 index 0000000..c4095ad --- /dev/null +++ b/src/backend/long_text.js @@ -0,0 +1,195 @@ +const crypto = require('crypto'); + +const CHUNK_TYPE = 'textChunk'; +const TEXT_CAP = 60000; +const CONTENT_BYTES = { plain: 6500, boxed: 4500, sealed: 3300 }; +const MIN_HEAD_BYTES = 200; +const CACHE_MAX = 400; +const KEY_RE = /^%[A-Za-z0-9+/]{43}=?\.sha256$/; +const KEY_SAMPLE = '%' + 'A'.repeat(44) + '.sha256'; + +const byteLength = (s) => Buffer.byteLength(String(s == null ? '' : s), 'utf8'); +const contentBytes = (content) => { try { return byteLength(JSON.stringify(content)); } catch (_) { return Infinity; } }; +const isChunkKey = (k) => typeof k === 'string' && KEY_RE.test(k); +const tooLong = (text) => String(text == null ? '' : text).length > TEXT_CAP; + +const cutAt = (text, maxBytes) => { + let i = 0, bytes = 0; + while (i < text.length) { + const cp = text.codePointAt(i); + const len = cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4; + if (bytes + len > maxBytes) break; + bytes += len; + i += cp >= 0x10000 ? 2 : 1; + } + if (i >= text.length) return text.length; + const min = Math.floor(i / 2); + const para = text.lastIndexOf('\n\n', i); + if (para > min) return para + 2; + const line = text.lastIndexOf('\n', i); + if (line > min) return line + 1; + const space = text.lastIndexOf(' ', i); + if (space > min) return space + 1; + return Math.max(i, 1); +}; + +const splitText = (text, maxBytes, firstMax = maxBytes) => { + const s = String(text == null ? '' : text); + const out = []; + let rest = s; + let limit = Math.max(1, firstMax); + while (rest.length) { + const cut = cutAt(rest, limit); + out.push(rest.slice(0, cut)); + rest = rest.slice(cut); + limit = Math.max(1, maxBytes); + } + return out.length ? out : ['']; +}; + +const chunkTemplate = (extra) => ({ type: CHUNK_TYPE, text: '', index: 999, total: 999, group: 'g'.repeat(16), ...extra }); +const chunkBudget = (extra, maxBytes) => maxBytes - contentBytes(chunkTemplate(extra)); +const headBudget = (content, field, maxBytes, count) => maxBytes - contentBytes({ ...content, [field]: '', chunks: new Array(count).fill(KEY_SAMPLE) }); + +const needsChunking = (content, maxBytes = CONTENT_BYTES.plain) => contentBytes(content) > maxBytes; + +const planPieces = (content, field, { maxBytes = CONTENT_BYTES.plain, extra = {} } = {}) => { + const text = String(content[field] == null ? '' : content[field]); + if (tooLong(text)) throw new Error('Text too long'); + if (!needsChunking(content, maxBytes)) return null; + const perChunk = chunkBudget(extra, maxBytes); + if (perChunk < MIN_HEAD_BYTES) throw new Error('Text does not fit'); + let count = 0; + let pieces = null; + for (let guard = 0; guard < 12; guard++) { + const head = headBudget(content, field, maxBytes, count); + if (head < MIN_HEAD_BYTES) throw new Error('Text does not fit'); + pieces = splitText(text, perChunk, head); + if (pieces.length - 1 <= count) return pieces; + count = pieces.length - 1; + } + return pieces; +}; + +const publishChunks = async (pieces, publish, { extra = {}, group } = {}) => { + const g = group || crypto.randomBytes(8).toString('hex'); + const total = pieces.length; + const keys = []; + for (let i = 0; i < pieces.length; i++) { + const r = await publish({ type: CHUNK_TYPE, text: pieces[i], index: i + 1, total, group: g, ...extra }); + const key = r && typeof r === 'object' ? r.key : r; + if (!isChunkKey(key)) throw new Error('Chunk publish failed'); + keys.push(key); + } + return keys; +}; + +const chunkContent = async (content, field, { maxBytes = CONTENT_BYTES.plain, extra = {}, publish } = {}) => { + const base = { ...content }; + delete base.chunks; + const pieces = planPieces(base, field, { maxBytes, extra }); + if (!pieces) return base; + const keys = await publishChunks(pieces.slice(1), publish, { extra }); + return { ...base, [field]: pieces[0], chunks: keys }; +}; + +const chunkOf = (author, c) => { + if (!c || typeof c !== 'object' || typeof c.text !== 'string') return null; + if (c.type !== CHUNK_TYPE && c.k !== CHUNK_TYPE) return null; + return { author, text: c.text, index: Number(c.index) || 0, total: Number(c.total) || 0, group: String(c.group || '') }; +}; + +const joinText = (head, keys, lookup, author) => { + let out = String(head == null ? '' : head); + if (!Array.isArray(keys) || !keys.length) return out; + let group = null; + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + if (!isChunkKey(k)) break; + const ch = lookup(k); + if (!ch || (author && ch.author !== author)) break; + if (group === null) group = ch.group; else if (ch.group !== group) break; + if (ch.index !== i + 1 || ch.total !== keys.length) break; + out += ch.text; + } + return out; +}; + +const hasChunks = (content) => !!content && Array.isArray(content.chunks) && content.chunks.length > 0; + +const resolveField = (content, field, author, lookup) => { + if (!hasChunks(content)) return content; + return { ...content, [field]: joinText(content[field], content.chunks, lookup, author) }; +}; + +const indexChunks = (messages, decode) => { + const index = new Map(); + for (const m of messages || []) { + const v = m && m.value; + if (!v || !m.key) continue; + const raw = v.content; + const c = decode ? decode(raw, m) : raw; + const ch = chunkOf(v.author, c); + if (ch) index.set(m.key, ch); + } + return index; +}; + +const lookupIn = (index) => (k) => index.get(k) || null; + +const caches = new WeakMap(); +const cacheFor = (ssb) => { + let c = caches.get(ssb); + if (!c) { c = new Map(); caches.set(ssb, c); } + return c; +}; +const remember = (cache, key, value) => { + if (cache.has(key)) cache.delete(key); + cache.set(key, value); + while (cache.size > CACHE_MAX) cache.delete(cache.keys().next().value); + return value; +}; + +const fetchChunk = async (ssb, key, unbox) => { + const cache = cacheFor(ssb); + if (cache.has(key)) { const v = cache.get(key); cache.delete(key); cache.set(key, v); return v; } + const raw = await new Promise((resolve) => { try { ssb.get(key, (err, m) => resolve(err ? null : m)); } catch (_) { resolve(null); } }); + if (!raw) return null; + let content = raw.content; + if (typeof content === 'string' && unbox) { try { content = unbox(content); } catch (_) { content = null; } } + const ch = chunkOf(raw.author, content); + return ch ? remember(cache, key, ch) : null; +}; + +const joinChunks = async (ssb, content, author, { field = 'text', unbox = null } = {}) => { + if (!hasChunks(content)) return content; + const found = new Map(); + for (const k of content.chunks) { + if (!isChunkKey(k)) break; + const ch = await fetchChunk(ssb, k, unbox); + if (!ch) break; + found.set(k, ch); + } + return resolveField(content, field, author, lookupIn(found)); +}; + +module.exports = { + CHUNK_TYPE, + TEXT_CAP, + CONTENT_BYTES, + byteLength, + contentBytes, + tooLong, + splitText, + needsChunking, + planPieces, + publishChunks, + chunkContent, + chunkOf, + joinText, + hasChunks, + resolveField, + indexChunks, + lookupIn, + joinChunks +}; diff --git a/src/backend/opinion_categories.js b/src/backend/opinion_categories.js index a6e314d..f55ea30 100644 --- a/src/backend/opinion_categories.js +++ b/src/backend/opinion_categories.js @@ -46,5 +46,31 @@ const all = [...positive, ...constructive, ...moderation]; all.positive = positive; all.constructive = constructive; all.moderation = moderation; +all.messageTypes = { + bookmark: 'bookmarkOpinion', + votes: 'votesOpinion', + poll: 'pollOpinion', + transfer: 'transferOpinion', + feed: 'feedOpinion', + image: 'imageOpinion', + audio: 'audioOpinion', + video: 'videoOpinion', + document: 'documentOpinion', + torrent: 'torrentOpinion', + file: 'fileOpinion', + industry: 'industryOpinion', + project: 'projectOpinion', + report: 'reportOpinion', + task: 'taskOpinion', + event: 'eventOpinion', + shopProduct: 'shopOpinion', + housing: 'housingOpinion', + market: 'marketOpinion', + schoolCourse: 'schoolOpinion', + podcast: 'podcastOpinion', + podcastEpisode: 'podcastOpinion', + campaign: 'campaignOpinion', + logisticsRoute: 'logisticsOpinion' +}; module.exports = all; diff --git a/src/backend/pdf.js b/src/backend/pdf.js new file mode 100644 index 0000000..10da631 --- /dev/null +++ b/src/backend/pdf.js @@ -0,0 +1,1485 @@ +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); +const { plainText } = require('./renderStyledText'); +const { MAP_W, MAP_H, makeView, fitView, toScreen, tileCoverage } = require('../maps/map_renderer'); + +const LOGO_PATH = path.join(__dirname, '..', 'client', 'assets', 'images', 'snh-oasis.jpg'); +const TILES_DIR = path.join(__dirname, '..', 'maps', 'tiles'); + +const WIN_ANSI_HIGH = { + '\u20AC': '\x80', '\u201A': '\x82', '\u0192': '\x83', '\u201E': '\x84', + '\u2026': '\x85', '\u2020': '\x86', '\u2021': '\x87', '\u02C6': '\x88', + '\u2030': '\x89', '\u0160': '\x8A', '\u2039': '\x8B', '\u0152': '\x8C', + '\u017D': '\x8E', '\u2018': '\x91', '\u2019': '\x92', '\u201C': '\x93', + '\u201D': '\x94', '\u2022': '\x95', '\u2013': '\x96', '\u2014': '\x97', + '\u02DC': '\x98', '\u2122': '\x99', '\u0161': '\x9A', '\u203A': '\x9B', + '\u0153': '\x9C', '\u017E': '\x9E', '\u0178': '\x9F' +}; + +const escapePdf = s => String(s == null ? '' : s) + .replace(/[\r\n\t]+/g, ' ') + .replace(/[\u0152\u0153\u0160\u0161\u0178\u017D\u017E\u0192\u02C6\u02DC\u2013\u2014\u2018\u2019\u201A\u201C\u201D\u201E\u2020\u2021\u2022\u2026\u2030\u2039\u203A\u20AC\u2122]/g, + (c) => WIN_ANSI_HIGH[c] || '?') + .replace(/[^\x20-\xFF\x80-\x9F]/g, '?') + .replace(/\\/g, '\\\\') + .replace(/\(/g, '\\(') + .replace(/\)/g, '\\)'); + +const wrap = (txt, max = 82) => { + const out = []; + for (const raw of String(txt == null ? '' : txt).split('\n')) { + let line = raw; + while (line.length > max) { + let cut = line.lastIndexOf(' ', max); + if (cut <= 0) cut = max; + out.push(line.slice(0, cut)); + line = line.slice(cut).replace(/^\s+/, ''); + } + out.push(line); + } + return out; +}; + +const readJpegDims = (buf) => { + let i = 2; + while (i < buf.length) { + if (buf[i] !== 0xFF) return null; + const marker = buf[i + 1]; + if (marker === 0xD8 || marker === 0xD9) { i += 2; continue; } + const len = buf.readUInt16BE(i + 2); + if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) { + const h = buf.readUInt16BE(i + 5); + const w = buf.readUInt16BE(i + 7); + const c = buf[i + 9]; + return { w, h, c }; + } + i += 2 + len; + } + return null; +}; + +const decodePng = (buf) => { + if (!Buffer.isBuffer(buf) || buf.length < 33 || buf.readUInt32BE(0) !== 0x89504e47) return null; + let pos = 8; + let w = 0, h = 0, depth = 0, colorType = 0, interlace = 0; + let palette = null; + const idat = []; + while (pos + 8 <= buf.length) { + const len = buf.readUInt32BE(pos); + const type = buf.toString('ascii', pos + 4, pos + 8); + const data = buf.slice(pos + 8, pos + 8 + len); + if (type === 'IHDR') { w = data.readUInt32BE(0); h = data.readUInt32BE(4); depth = data[8]; colorType = data[9]; interlace = data[12]; } + else if (type === 'PLTE') palette = data; + else if (type === 'IDAT') idat.push(data); + else if (type === 'IEND') break; + pos += 12 + len; + } + if (!w || !h || depth !== 8 || interlace !== 0 || !idat.length) return null; + if (w > 8192 || h > 8192 || w * h > 20 * 1024 * 1024) return null; + const channels = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }[colorType]; + if (!channels) return null; + let raw; + try { raw = zlib.inflateSync(Buffer.concat(idat), { maxOutputLength: (w * channels + 1) * h }); } catch (_) { return null; } + const stride = w * channels; + const out = Buffer.alloc(w * h * 3); + let prev = Buffer.alloc(stride); + let inPos = 0; + for (let y = 0; y < h; y++) { + const filter = raw[inPos++]; + const cur = Buffer.from(raw.slice(inPos, inPos + stride)); + inPos += stride; + for (let i = 0; i < stride; i++) { + const a = i >= channels ? cur[i - channels] : 0; + const b = prev[i]; + const c = i >= channels ? prev[i - channels] : 0; + let v = cur[i]; + if (filter === 1) v += a; + else if (filter === 2) v += b; + else if (filter === 3) v += Math.floor((a + b) / 2); + else if (filter === 4) { const p = a + b - c; const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); v += (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c); } + cur[i] = v & 0xff; + } + for (let x = 0; x < w; x++) { + const o = (y * w + x) * 3; + const s = x * channels; + if (colorType === 2 || colorType === 6) { out[o] = cur[s]; out[o + 1] = cur[s + 1]; out[o + 2] = cur[s + 2]; } + else if (colorType === 0 || colorType === 4) { out[o] = out[o + 1] = out[o + 2] = cur[s]; } + else if (colorType === 3 && palette) { const pi = cur[s] * 3; out[o] = palette[pi]; out[o + 1] = palette[pi + 1]; out[o + 2] = palette[pi + 2]; } + if (colorType === 6 || colorType === 4) { + const alpha = cur[s + channels - 1] / 255; + out[o] = Math.round(out[o] * alpha + 255 * (1 - alpha)); + out[o + 1] = Math.round(out[o + 1] * alpha + 255 * (1 - alpha)); + out[o + 2] = Math.round(out[o + 2] * alpha + 255 * (1 - alpha)); + } + } + prev = cur; + } + return { w, h, rgb: out }; +}; + +const imageXObject = (buf) => { + if (!Buffer.isBuffer(buf) || buf.length < 4) return null; + if (buf[0] === 0xff && buf[1] === 0xd8) { + const dims = readJpegDims(buf); + if (!dims) return null; + const cs = dims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; + return { w: dims.w, h: dims.h, dict: `<< /Type /XObject /Subtype /Image /Width ${dims.w} /Height ${dims.h} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode /Length ${buf.length} >>`, stream: buf }; + } + const png = decodePng(buf); + if (!png) return null; + const compressed = zlib.deflateSync(png.rgb); + return { w: png.w, h: png.h, dict: `<< /Type /XObject /Subtype /Image /Width ${png.w} /Height ${png.h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length ${compressed.length} >>`, stream: compressed }; +}; + +const rasterXObject = (raster) => { + if (!raster || !raster.w || !raster.h || !Buffer.isBuffer(raster.rgb) || raster.rgb.length !== raster.w * raster.h * 3) return null; + const compressed = zlib.deflateSync(raster.rgb); + return { w: raster.w, h: raster.h, dict: `<< /Type /XObject /Subtype /Image /Width ${raster.w} /Height ${raster.h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Interpolate false /Filter /FlateDecode /Length ${compressed.length} >>`, stream: compressed }; +}; + +const jpegXObject = (buf) => { + if (!Buffer.isBuffer(buf) || buf[0] !== 0xff || buf[1] !== 0xd8) return null; + const dims = readJpegDims(buf); + if (!dims) return null; + return { w: dims.w, h: dims.h, dict: `<< /Type /XObject /Subtype /Image /Width ${dims.w} /Height ${dims.h} /ColorSpace ${dims.c === 1 ? '/DeviceGray' : '/DeviceRGB'} /BitsPerComponent 8 /Filter /DCTDecode /Length ${buf.length} >>`, stream: buf }; +}; + +const tileXObject = (t) => { + try { return jpegXObject(fs.readFileSync(path.join(TILES_DIR, String(t.z), `${t.x}_${t.y}.jpg`))); } catch (_) { return null; } +}; + +const MAP_PIN_R = 4.5; +const MAP_LABEL_MAX = 28; + +const mapBlock = (pins) => { + const pts = (Array.isArray(pins) ? pins : []).filter(p => p && isFinite(Number(p.lat)) && isFinite(Number(p.lng))); + if (!pts.length) return null; + const fit = fitView(pts, { singleZoom: 8 }); + const view = makeView(fit.lat, fit.lng, fit.zoom); + const tiles = tileCoverage(view).map(t => ({ ...t, xo: tileXObject(t) })).filter(t => t.xo); + if (!tiles.length) return null; + const marks = pts.map(p => { + const s = toScreen(view, Number(p.lat), Number(p.lng)); + const line = String(p.label || '').split(/\r?\n/)[0].trim(); + return { x: s.x, y: s.y, main: !!p.main, label: line.length > MAP_LABEL_MAX ? line.slice(0, MAP_LABEL_MAX - 1) + '…' : line }; + }); + return { kind: 'map', w: MAP_W, h: MAP_H, zoom: view.zoom, tiles, pins: marks }; +}; + +const circlePath = (cx, cy, r) => { + const k = 0.5523 * r; + return `${cx + r} ${cy} m ${cx + r} ${cy + k} ${cx + k} ${cy + r} ${cx} ${cy + r} c ${cx - k} ${cy + r} ${cx - r} ${cy + k} ${cx - r} ${cy} c ${cx - r} ${cy - k} ${cx - k} ${cy - r} ${cx} ${cy - r} c ${cx + k} ${cy - r} ${cx + r} ${cy - k} ${cx + r} ${cy} c`; +}; + +const mapContent = (ln, x0, y0, box, tileIds) => { + const s = box.w / ln.w; + const f = v => Math.round(v * 100) / 100; + const parts = [`q ${f(x0)} ${f(y0)} ${f(box.w)} ${f(box.h)} re W n`]; + for (const t of ln.tiles) { + parts.push(`q ${f(t.size * s + 0.5)} 0 0 ${f(t.size * s + 0.5)} ${f(x0 + t.sx * s)} ${f(y0 + (ln.h - t.sy - t.size) * s)} cm /Im${tileIds.get(t)} Do Q`); + } + const at = p => ({ x: x0 + p.x * s, y: y0 + (ln.h - p.y) * s }); + for (const p of ln.pins) { + const c = at(p); + parts.push(`q ${p.main ? '0.95 0.55 0' : '0.85 0.15 0.15'} rg 1 1 1 RG 1 w ${circlePath(f(c.x), f(c.y), MAP_PIN_R)} h B Q`); + } + for (const p of ln.pins) { + if (!p.label) continue; + const c = at(p); + const tw = p.label.length * 4.2; + parts.push(`q 1 1 1 rg ${f(c.x + MAP_PIN_R + 2)} ${f(c.y - 5)} ${f(tw + 4)} 10 re f Q`); + parts.push(`BT /F1 7 Tf 0 0 0 rg ${f(c.x + MAP_PIN_R + 4)} ${f(c.y - 2.5)} Td (${escapePdf(p.label)}) Tj ET`); + } + parts.push(`q 0.4 0.4 0.4 RG 0.5 w ${f(x0)} ${f(y0)} ${f(box.w)} ${f(box.h)} re S Q`); + parts.push('Q'); + return parts.join('\n'); +}; + +const flattenSections = (sections) => { + const lines = []; + for (const s of Array.isArray(sections) ? sections : []) { + if (!s) continue; + if (s.kind === 'image') { + const xo = s.raster ? rasterXObject(s.raster) : imageXObject(s.buffer); + if (xo) lines.push({ kind: 'image', xo, caption: s.caption || '', maxW: s.maxW, maxH: s.maxH }); + else if (s.caption) lines.push({ kind: 'kv', text: `[image: ${s.caption}]` }); + } else if (s.kind === 'map') { + const block = mapBlock(s.pins); + if (block) lines.push(block); + } else if (s.kind === 'kv') { + const txt = `${s.label}: ${s.value == null ? '' : s.value}`; + for (const w of wrap(txt, 82)) lines.push({ kind: 'kv', text: w }); + } else if (s.kind === 'text') { + for (const w of wrap(plainText(s.text), 82)) lines.push({ kind: 'kv', text: w }); + } else { + lines.push({ kind: s.kind, text: s.text }); + } + } + return lines; +}; + +const licenseFooter = (id) => { + const l = require('../views/clearnet_view').licenseInfo(id); + return l ? `License: ${l.name}` : ''; +}; + +function buildDocumentPdf({ title, issuedToLabel, issuedTo, sections, license = '' } = {}) { + const footerNote = licenseFooter(license); + const pageW = 612; + const pageH = 792; + const marginX = 50; + const headerH = 90; + const footerH = 40; + const bodyTop = pageH - headerH - 24; + const bodyBottom = footerH + 10; + const lineH = 14; + + let logoBuf = null; + let logoDims = null; + try { + logoBuf = fs.readFileSync(LOGO_PATH); + logoDims = readJpegDims(logoBuf); + } catch (_) {} + + const lines = flattenSections(sections); + const maxImgW = 240; + const maxImgH = 160; + const imageBox = (ln) => { + const scale = Math.min(1, (ln.maxW || maxImgW) / ln.xo.w, (ln.maxH || maxImgH) / ln.xo.h); + return { w: Math.round(ln.xo.w * scale), h: Math.round(ln.xo.h * scale) }; + }; + const mapBox = (ln) => { + const scale = Math.min(1, (pageW - 2 * marginX) / ln.w); + return { w: Math.round(ln.w * scale), h: Math.round(ln.h * scale) }; + }; + const heightOf = (ln) => ln.kind === 'image' ? imageBox(ln).h + lineH : ln.kind === 'map' ? mapBox(ln).h + lineH : lineH; + + const bodyHeight = bodyTop - bodyBottom; + const pages = []; + let current = []; + let used = 0; + for (const ln of lines) { + const hgt = Math.min(heightOf(ln), bodyHeight); + if (used + hgt > bodyHeight && current.length) { pages.push(current); current = []; used = 0; } + current.push(ln); + used += hgt; + } + pages.push(current); + + const objects = []; + const addObj = body => { objects.push(body); return objects.length; }; + + const catalogId = addObj(null); + const pagesId = addObj(null); + const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); + const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); + let logoXObjId = null; + if (logoBuf && logoDims) { + const cs = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; + const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; + logoXObjId = addObj({ dict, stream: logoBuf }); + } + + const imageIds = new Map(); + for (const ln of lines) { + if (ln.kind === 'map') { + for (const t of ln.tiles) imageIds.set(t, addObj({ dict: t.xo.dict, stream: t.xo.stream })); + continue; + } + if (ln.kind !== 'image') continue; + const id = addObj({ dict: ln.xo.dict, stream: ln.xo.stream }); + imageIds.set(ln, id); + } + + const exportDate = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; + const footerLeft = `Generated: ${exportDate}`; + + const pageIds = []; + const contentIds = []; + + pages.forEach((pg, pgIdx) => { + const parts = []; + if (logoXObjId) { + const logoH = 60; + const logoW = Math.round((logoDims.w / logoDims.h) * logoH); + parts.push(`q\n${logoW} 0 0 ${logoH} ${marginX} ${pageH - headerH + 15} cm\n/Logo Do\nQ`); + } + const titleX = (logoXObjId ? marginX + 80 : marginX); + const titleY = pageH - 45; + parts.push(`BT\n/F2 16 Tf\n${titleX} ${titleY} Td\n(${escapePdf(title || 'OASIS')}) Tj\nET`); + if (issuedTo) { + const prefix = `${issuedToLabel || 'Issued to'}: `; + const prefixW = prefix.length * 5.4; + parts.push(`BT\n/F1 9 Tf\n${titleX} ${titleY - 16} Td\n(${escapePdf(prefix)}) Tj\nET`); + parts.push(`BT\n/F2 9 Tf\n${titleX + prefixW} ${titleY - 16} Td\n(${escapePdf(String(issuedTo))}) Tj\nET`); + } + + parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${pageH - headerH} m\n${pageW - marginX} ${pageH - headerH} l\nS\nQ`); + + let y = bodyTop; + for (const ln of pg) { + if (ln.kind === 'title') { + parts.push(`BT\n/F2 14 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } else if (ln.kind === 'subtitle') { + parts.push(`BT\n/F1 11 Tf\n0.2 0.2 0.2 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } else if (ln.kind === 'section') { + parts.push(`BT\n/F2 11 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + parts.push(`q\n0 0 0 RG\n0.5 w\n${marginX} ${y - 3} m\n${pageW - marginX} ${y - 3} l\nS\nQ`); + } else if (ln.kind === 'kv') { + parts.push(`BT\n/F1 10 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } else if (ln.kind === 'image') { + const box = imageBox(ln); + parts.push(`q\n${box.w} 0 0 ${box.h} ${marginX} ${y - box.h + lineH - 4} cm\n/Im${imageIds.get(ln)} Do\nQ`); + y -= box.h; + } else if (ln.kind === 'map') { + const box = mapBox(ln); + parts.push(mapContent(ln, marginX, y - box.h + lineH - 4, box, imageIds)); + y -= box.h; + } + y -= lineH; + } + + parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${footerH + 5} m\n${pageW - marginX} ${footerH + 5} l\nS\nQ`); + parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 10} Td\n(${escapePdf(footerLeft)}) Tj\nET`); + if (footerNote) parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 21} Td\n(${escapePdf(footerNote)}) Tj\nET`); + const pageLabel = `Page ${pgIdx + 1} of ${pages.length}`; + const pageLabelW = pageLabel.length * 4.8; + parts.push(`BT\n/F1 8 Tf\n${pageW - marginX - pageLabelW} ${footerH - 10} Td\n(${escapePdf(pageLabel)}) Tj\nET`); + + const content = parts.join('\n'); + const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; + const cid = addObj(stream); + contentIds.push(cid); + const pid = addObj(null); + pageIds.push(pid); + }); + + const imageRefs = Array.from(imageIds.values()).map(id => `/Im${id} ${id} 0 R`).join(' '); + for (let i = 0; i < pageIds.length; i++) { + const xobjects = [logoXObjId ? `/Logo ${logoXObjId} 0 R` : '', imageRefs].filter(Boolean).join(' '); + const resources = xobjects + ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << ${xobjects} >> >>` + : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; + objects[pageIds[i] - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentIds[i]} 0 R /Resources ${resources} >>`; + } + objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; + objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; + + const chunks = []; + const offsets = [0]; + let byteLen = 0; + const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; + push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); + for (let i = 0; i < objects.length; i++) { + offsets.push(byteLen); + const obj = objects[i]; + if (obj && typeof obj === 'object' && obj.dict && obj.stream) { + push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); + push(obj.stream); + push(Buffer.from('\nendstream\nendobj\n', 'binary')); + } else { + push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); + } + } + const xrefStart = byteLen; + let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; + for (let i = 1; i <= objects.length; i++) xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; + xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; + push(Buffer.from(xref, 'binary')); + return Buffer.concat(chunks); +} + +const fmtDate = v => { + if (!v) return ''; + const d = new Date(v); + return isNaN(d.getTime()) ? String(v) : d.toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; +}; + +const fmtDay = v => { + if (!v) return ''; + const d = new Date(v); + return isNaN(d.getTime()) ? String(v) : d.toISOString().slice(0, 10); +}; + +const asList = v => (Array.isArray(v) ? v : []); +const txt = v => (v == null ? '' : String(v)); + +const humanLabel = (key) => String(key || '') + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') + .replace(/^./, c => c.toUpperCase()); + +const pushMeta = (out, item) => { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'METADATA' }); + if (item.id) out.push({ kind: 'kv', label: 'Content ID', value: item.id }); + const author = item.author || item.organizer || item.createdBy || item.from || ''; + if (author) out.push({ kind: 'kv', label: 'Author', value: author }); + if (item.createdAt) out.push({ kind: 'kv', label: 'Created At', value: fmtDate(item.createdAt) }); + if (item.updatedAt) out.push({ kind: 'kv', label: 'Updated At', value: fmtDate(item.updatedAt) }); +}; + +const pushOpinions = (out, item) => { + const op = item.opinions && typeof item.opinions === 'object' ? item.opinions : {}; + const entries = Object.entries(op).filter(([, n]) => Number(n) > 0); + if (!entries.length) return; + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'OPINIONS' }); + for (const [cat, n] of entries) out.push({ kind: 'kv', label: humanLabel(cat), value: String(n) }); +}; + +const pushTags = (out, item) => { + const tags = asList(item.tags).filter(Boolean); + if (tags.length) out.push({ kind: 'kv', label: 'Tags', value: tags.join(', ') }); +}; + +const reportSections = (report) => { + const out = []; + out.push({ kind: 'title', text: `Report: ${txt(report.title) || '-'}` }); + out.push({ kind: 'blank' }); + + out.push({ kind: 'section', text: 'CLASSIFICATION' }); + out.push({ kind: 'kv', label: 'Category', value: txt(report.category).toUpperCase() }); + out.push({ kind: 'kv', label: 'Severity', value: txt(report.severity).toUpperCase() }); + out.push({ kind: 'kv', label: 'Status', value: txt(report.status).toUpperCase() }); + pushTags(out, report); + + if (txt(report.description).trim()) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'text', text: report.description }); + } + + const tpl = report.template && typeof report.template === 'object' ? report.template : {}; + const tplEntries = Object.entries(tpl).filter(([, v]) => txt(v).trim()); + if (tplEntries.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DETAILS' }); + for (const [k, v] of tplEntries) out.push({ kind: 'kv', label: humanLabel(k), value: v }); + } + + const confirmations = asList(report.confirmations); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'CONFIRMATIONS' }); + out.push({ kind: 'kv', label: 'Confirmed', value: String(confirmations.length) }); + for (const c of confirmations) out.push({ kind: 'kv', label: 'Confirmed by', value: c }); + + pushOpinions(out, report); + pushMeta(out, report); + return out; +}; + +const voteSections = (vote) => { + const out = []; + out.push({ kind: 'title', text: `Votation: ${txt(vote.question) || '-'}` }); + out.push({ kind: 'blank' }); + + out.push({ kind: 'section', text: 'TERMS' }); + out.push({ kind: 'kv', label: 'Status', value: txt(vote.status).toUpperCase() }); + if (vote.deadline) out.push({ kind: 'kv', label: 'Deadline', value: fmtDate(vote.deadline) }); + pushTags(out, vote); + + const votes = vote.votes && typeof vote.votes === 'object' ? vote.votes : {}; + const total = Number(vote.totalVotes) || asList(vote.voters).length; + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'RESULTS' }); + out.push({ kind: 'kv', label: 'Total votes', value: String(total) }); + for (const [choice, n] of Object.entries(votes)) { + const count = Number(n) || 0; + const pct = total > 0 ? Math.round((count / total) * 100) : 0; + out.push({ kind: 'kv', label: choice, value: `${count} (${pct}%)` }); + } + + pushOpinions(out, vote); + pushMeta(out, vote); + return out; +}; + +const eventSections = (event) => { + const out = []; + out.push({ kind: 'title', text: `Event: ${txt(event.title) || '-'}` }); + out.push({ kind: 'blank' }); + + out.push({ kind: 'section', text: 'SCHEDULE' }); + out.push({ kind: 'kv', label: 'Date', value: fmtDate(event.date) }); + out.push({ kind: 'kv', label: 'Status', value: txt(event.status).toUpperCase() }); + if (event.location) out.push({ kind: 'kv', label: 'Location', value: event.location }); + if (event.mapUrl) out.push({ kind: 'kv', label: 'Map', value: event.mapUrl }); + if (Number(event.price) > 0) out.push({ kind: 'kv', label: 'Price', value: `${Number(event.price)} ECO` }); + out.push({ kind: 'kv', label: 'Privacy', value: txt(event.isPublic).toUpperCase() }); + if (event.url) out.push({ kind: 'kv', label: 'Url', value: event.url }); + pushTags(out, event); + + if (txt(event.description).trim()) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'text', text: event.description }); + } + + const attendees = asList(event.attendees); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'ATTENDEES' }); + out.push({ kind: 'kv', label: 'Total', value: String(attendees.length) }); + for (const a of attendees) out.push({ kind: 'kv', label: 'Attendee', value: a }); + + pushOpinions(out, event); + pushMeta(out, event); + return out; +}; + +const taskSections = (task) => { + const out = []; + out.push({ kind: 'title', text: `Task: ${txt(task.title) || '-'}` }); + out.push({ kind: 'blank' }); + + out.push({ kind: 'section', text: 'SCHEDULE' }); + out.push({ kind: 'kv', label: 'Status', value: txt(task.status).toUpperCase() }); + out.push({ kind: 'kv', label: 'Priority', value: txt(task.priority).toUpperCase() }); + if (task.startTime) out.push({ kind: 'kv', label: 'Starts', value: fmtDate(task.startTime) }); + if (task.endTime) out.push({ kind: 'kv', label: 'Ends', value: fmtDate(task.endTime) }); + if (task.location) out.push({ kind: 'kv', label: 'Location', value: task.location }); + out.push({ kind: 'kv', label: 'Privacy', value: txt(task.isPublic).toUpperCase() }); + pushTags(out, task); + + if (txt(task.description).trim()) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'text', text: task.description }); + } + + const assignees = asList(task.assignees); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'ASSIGNEES' }); + out.push({ kind: 'kv', label: 'Total', value: String(assignees.length) }); + for (const a of assignees) out.push({ kind: 'kv', label: 'Assignee', value: a }); + + pushOpinions(out, task); + pushMeta(out, task); + return out; +}; + +const calendarSections = (calendar, extra = {}) => { + const dates = asList(extra.dates); + const notesByDate = extra.notesByDate && typeof extra.notesByDate === 'object' ? extra.notesByDate : {}; + + const out = []; + out.push({ kind: 'title', text: `Calendar: ${txt(calendar.title) || '-'}` }); + out.push({ kind: 'blank' }); + + out.push({ kind: 'section', text: 'SUMMARY' }); + out.push({ kind: 'kv', label: 'Status', value: calendar.isClosed ? 'CLOSED' : txt(calendar.status).toUpperCase() }); + if (calendar.deadline) out.push({ kind: 'kv', label: 'Deadline', value: fmtDate(calendar.deadline) }); + if (calendar.mapUrl) out.push({ kind: 'kv', label: 'Map', value: calendar.mapUrl }); + out.push({ kind: 'kv', label: 'Participants', value: String(asList(calendar.participants).length) }); + out.push({ kind: 'kv', label: 'Dates', value: String(dates.length) }); + const noteTotal = Object.values(notesByDate).reduce((n, arr) => n + asList(arr).length, 0); + out.push({ kind: 'kv', label: 'Notes', value: String(noteTotal) }); + pushTags(out, calendar); + + const participants = asList(calendar.participants); + if (participants.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'PARTICIPANTS' }); + for (const p of participants) out.push({ kind: 'kv', label: 'Participant', value: p }); + } + + if (dates.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DATES' }); + for (const d of dates) { + const day = fmtDay(d && d.date) || txt(d && d.date); + out.push({ kind: 'kv', label: day, value: txt(d && d.label) }); + for (const note of asList(notesByDate[d && d.key])) { + const noteText = txt(note && note.text); + if (noteText) out.push({ kind: 'text', text: ` - ${noteText}` }); + } + } + } + + pushMeta(out, calendar); + return out; +}; + +const cvSections = (cv) => { + const out = []; + out.push({ kind: 'title', text: `Curriculum: ${txt(cv.name) || txt(cv.author) || '-'}` }); + out.push({ kind: 'blank' }); + + out.push({ kind: 'section', text: 'PROFILE' }); + if (cv.location) out.push({ kind: 'kv', label: 'Location', value: cv.location }); + if (cv.status) out.push({ kind: 'kv', label: 'Status', value: txt(cv.status).toUpperCase() }); + if (cv.preferences) out.push({ kind: 'kv', label: 'Preferences', value: txt(cv.preferences).toUpperCase() }); + if (cv.languages) out.push({ kind: 'kv', label: 'Languages', value: cv.languages }); + + if (txt(cv.description).trim()) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'text', text: cv.description }); + } + + const blocks = [ + ['PERSONAL', cv.personalExperiences, cv.personalSkills], + ['EDUCATION', cv.educationExperiences, cv.educationalSkills], + ['PROFESSIONAL', cv.professionalExperiences, cv.professionalSkills], + ['OASIS', cv.oasisExperiences, cv.oasisSkills] + ]; + + for (const [heading, experiences, skills] of blocks) { + const skillList = asList(skills).filter(Boolean); + const body = txt(experiences).trim(); + if (!body && !skillList.length) continue; + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: heading }); + if (body) out.push({ kind: 'text', text: body }); + if (skillList.length) out.push({ kind: 'kv', label: 'Skills', value: skillList.join(', ') }); + } + + pushMeta(out, cv); + return out; +}; + +const IMAGE_MD = /^\s*!\[image:([^\]]*)\]\((&[^)]+)\)\s*$/; +const MEDIA_MD = /^\s*\[(audio|video|pdf|torrent):([^\]]*)\]\((&[^)]+)\)\s*$/; +const inlineMarkdown = (line) => String(line || '') + .replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, '$2') + .replace(/\[\[([^\]]+)\]\]/g, (m, t) => t.replace(/^.*:/, '')) + .replace(/!\[image:([^\]]*)\]\((&[^)]+)\)/g, '[image: $1]') + .replace(/\[(audio|video|pdf|torrent):([^\]]*)\]\((&[^)]+)\)/g, '[$1: $2]') + .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)') + .replace(/(\*\*|__)(.*?)\1/g, '$2') + .replace(/(\*|_)(.*?)\1/g, '$2') + .replace(/`([^`]*)`/g, '$1') + .replace(/^\s{0,3}>\s?/, '| '); + +const pushWikiBody = (out, body, images = {}) => { + const lines = String(body || '').replace(/\r\n/g, '\n').split('\n'); + let paragraph = []; + const flush = () => { if (paragraph.length) { out.push({ kind: 'text', text: paragraph.join('\n') }); paragraph = []; } }; + for (const raw of lines) { + const img = raw.match(IMAGE_MD); + if (img) { + flush(); + const buf = images && images[img[2]]; + if (buf) out.push({ kind: 'image', buffer: buf, caption: txt(img[1]) }); + else out.push({ kind: 'text', text: `[image: ${txt(img[1])}]` }); + continue; + } + const media = raw.match(MEDIA_MD); + if (media) { flush(); out.push({ kind: 'text', text: `[${media[1]}: ${txt(media[2])}]` }); continue; } + const heading = raw.match(/^\s{0,3}#{1,6}\s+(.*)$/); + if (heading) { flush(); out.push({ kind: 'blank' }); out.push({ kind: 'section', text: inlineMarkdown(heading[1]).toUpperCase() }); continue; } + if (!raw.trim()) { flush(); out.push({ kind: 'blank' }); continue; } + paragraph.push(inlineMarkdown(raw)); + } + flush(); +}; + +const padSections = (pad, extra = {}) => { + const out = []; + out.push({ kind: 'title', text: txt(pad.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Author', value: txt(pad.author) }); + out.push({ kind: 'kv', label: 'Created', value: fmtDate(pad.createdAt) }); + out.push({ kind: 'kv', label: 'Status', value: txt(pad.status).toUpperCase() }); + out.push({ kind: 'kv', label: 'Members', value: String(Array.isArray(pad.members) ? pad.members.length : 0) }); + out.push({ kind: 'kv', label: 'Versions', value: String(Array.isArray(pad.versions) ? pad.versions.length : 0) }); + pushTags(out, pad); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'CONTENT' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'text', text: String(pad.text || '').replace(/\r\n/g, '\n') }); + const versions = Array.isArray(pad.versions) ? pad.versions : []; + if (versions.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'VERSION HISTORY' }); + out.push({ kind: 'blank' }); + for (const v of versions.slice().reverse().slice(0, 50)) out.push({ kind: 'kv', label: fmtDate(v.createdAt), value: txt(v.author) }); + } + return out; +}; + +const wikiSections = (page, extra = {}) => { + const out = []; + out.push({ kind: 'title', text: txt(page.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Author', value: txt(page.author) }); + out.push({ kind: 'kv', label: 'Updated', value: fmtDate(page.updatedAt) }); + out.push({ kind: 'kv', label: 'Versions', value: String(page.versionCount || 1) }); + pushTags(out, page); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'CONTENT' }); + out.push({ kind: 'blank' }); + pushWikiBody(out, page.body, (extra && extra.images) || {}); + return out; +}; + +const emergencySections = (item, extra = {}) => { + const out = []; + out.push({ kind: 'title', text: txt(item.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Status', value: txt(item.status).toUpperCase() }); + out.push({ kind: 'kv', label: 'Severity', value: `${txt(item.severity).toUpperCase()} (${Number(item.confirmationCount) || 0} confirmations)` }); + out.push({ kind: 'kv', label: 'Category', value: txt(item.category).toUpperCase() }); + if (item.expiresAt) out.push({ kind: 'kv', label: 'Expires', value: fmtDate(item.expiresAt) }); + out.push({ kind: 'kv', label: 'Author', value: txt(item.author) }); + out.push({ kind: 'kv', label: 'Created', value: fmtDate(item.createdAt) }); + if (item.mapUrl) out.push({ kind: 'kv', label: 'Map', value: txt(item.mapUrl) }); + pushTags(out, item); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'CONTENT' }); + out.push({ kind: 'blank' }); + pushWikiBody(out, item.text, (extra && extra.images) || {}); + const updates = asList(item.updates); + if (updates.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'LAST UPDATES' }); + for (const u of updates) { + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: fmtDate(u.createdAt), value: txt(u.author) }); + pushWikiBody(out, u.text, (extra && extra.images) || {}); + } + } + return out; +}; + +const campaignSections = (item, extra = {}) => { + const out = []; + out.push({ kind: 'title', text: txt(item.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Status', value: txt(item.status).toUpperCase() }); + out.push({ kind: 'kv', label: 'Category', value: txt(item.category).toUpperCase() }); + out.push({ kind: 'kv', label: 'Signatures', value: `${Number(item.signatureCount) || 0} / ${Number(item.goal) || 0} (${Number(item.progress) || 0}%)` }); + if (item.deadline) out.push({ kind: 'kv', label: 'Deadline', value: fmtDate(item.deadline) }); + out.push({ kind: 'kv', label: 'Promoter', value: txt(item.author) }); + out.push({ kind: 'kv', label: 'Created', value: fmtDate(item.createdAt) }); + if (item.mapUrl) out.push({ kind: 'kv', label: 'Map', value: txt(item.mapUrl) }); + pushTags(out, item); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'CONTENT' }); + out.push({ kind: 'blank' }); + pushWikiBody(out, item.text, (extra && extra.images) || {}); + const updates = asList(item.updates); + if (updates.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'LAST UPDATES' }); + for (const u of updates) { + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: fmtDate(u.createdAt), value: txt(u.author) }); + pushWikiBody(out, u.text, (extra && extra.images) || {}); + } + } + const signatures = asList(item.signatures); + if (signatures.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: `SUPPORTERS (${signatures.length})` }); + for (const s of signatures) out.push({ kind: 'kv', label: fmtDate(s.createdAt), value: `${txt(s.author)}${s.text ? ' - ' + txt(s.text) : ''}` }); + } + return out; +}; + +const logisticsSections = (item, extra = {}) => { + const out = []; + out.push({ kind: 'title', text: txt(item.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Type', value: `${txt(item.kind).toUpperCase()} - ${txt(item.mode).toUpperCase()}` }); + out.push({ kind: 'kv', label: 'Status', value: txt(item.status).toUpperCase() }); + out.push({ kind: 'kv', label: 'Route', value: `${txt(item.origin)} -> ${txt(item.destination)}` }); + if (item.date) out.push({ kind: 'kv', label: 'Date', value: `${fmtDate(item.date)}${item.recurrence && item.recurrence !== 'NONE' ? ' (' + txt(item.recurrence) + ')' : ''}` }); + if (item.kind === 'TRIP' && item.seats) out.push({ kind: 'kv', label: 'Seats', value: `${Number(item.seatsLeft) || 0} / ${Number(item.seats) || 0}` }); + if (item.size) out.push({ kind: 'kv', label: 'Size', value: txt(item.size) }); + if (item.weight) out.push({ kind: 'kv', label: 'Weight', value: txt(item.weight) }); + out.push({ kind: 'kv', label: 'Price', value: item.priceType === 'FREE' || !(Number(item.price) > 0) ? 'FREE' : `${item.price} ${txt(item.priceType)}` }); + if (item.orderRef) out.push({ kind: 'kv', label: 'Order', value: txt(item.orderRef) }); + if (item.mapUrl) out.push({ kind: 'kv', label: 'Map', value: txt(item.mapUrl) }); + out.push({ kind: 'kv', label: 'Author', value: txt(item.author) }); + out.push({ kind: 'kv', label: 'Created', value: fmtDate(item.createdAt) }); + pushTags(out, item); + if (txt(item.description).trim()) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'blank' }); + pushWikiBody(out, item.description, (extra && extra.images) || {}); + } + const bookings = asList(item.bookings); + if (bookings.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: `BOOKINGS (${bookings.length})` }); + for (const b of bookings) out.push({ kind: 'kv', label: `${fmtDate(b.createdAt)} ${txt(b.status)}`, value: `${txt(b.booker)}${b.seats ? ' x' + b.seats : ''}${b.notes ? ' - ' + txt(b.notes) : ''}${b.receipt ? ' [' + txt(b.receipt) + ']' : ''}` }); + } + const ratings = asList(item.ratings); + if (ratings.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: `RATINGS (${ratings.length}) - ${Number(item.ratingAvg) || 0}/5` }); + for (const r of ratings) out.push({ kind: 'kv', label: `${'*'.repeat(Number(r.score) || 0)} ${fmtDate(r.createdAt)}`, value: `${txt(r.author)}${r.text ? ' - ' + txt(r.text) : ''}` }); + } + return out; +}; + +const mailingSections = (item, extra = {}) => { + const out = []; + out.push({ kind: 'title', text: txt(item.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Type', value: txt(item.listType).toUpperCase() }); + out.push({ kind: 'kv', label: 'Status', value: txt(item.status).toUpperCase() }); + out.push({ kind: 'kv', label: 'Subscribers', value: String(Number(item.participantCount) || 0) }); + out.push({ kind: 'kv', label: 'Messages', value: String(Number(item.messageCount) || 0) }); + out.push({ kind: 'kv', label: 'Author', value: txt(item.author) }); + out.push({ kind: 'kv', label: 'Created', value: fmtDate(item.createdAt) }); + pushTags(out, item); + if (txt(item.description).trim()) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'blank' }); + pushWikiBody(out, item.description, (extra && extra.images) || {}); + } + const history = asList(item.history); + if (history.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: `HISTORY (${history.length})` }); + for (const m of history) { + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: fmtDate(m.sentAt), value: `${txt(m.author)} - ${txt(m.subject)}` }); + pushWikiBody(out, m.text, {}); + } + } + return out; +}; + +const PIXELIA_EMPTY = [0x1a, 0x1a, 0x1a]; +const PIXELIA_GRID = [0x33, 0x33, 0x33]; +const PIXELIA_PAGE_W = 512; +const PIXELIA_PAGE_H = 520; +const PIXELIA_MAX_CELL = 24; + +const pixeliaCells = (pixels, cols, rows) => { + const cells = new Map(); + for (const px of asList(pixels)) { + const key = `${px.x},${px.y}`; + const hex = /^#[0-9a-f]{6}$/i.test(String(px.color || '')) ? String(px.color).slice(1) : null; + if (!hex || cells.has(key) || px.x < 1 || px.x > cols || px.y < 1 || px.y > rows) continue; + cells.set(key, [0, 2, 4].map(i => parseInt(hex.slice(i, i + 2), 16))); + } + return cells; +}; + +const pixeliaRaster = (cells, x0, y0, cols, rows, block) => { + const w = cols * block; + const h = rows * block; + const rgb = Buffer.alloc(w * h * 3); + const segment = (c, edge) => { + const seg = Buffer.alloc(block * 3); + for (let i = 0; i < block; i++) { + const col = edge || i === block - 1 ? PIXELIA_GRID : c; + seg[i * 3] = col[0]; seg[i * 3 + 1] = col[1]; seg[i * 3 + 2] = col[2]; + } + return seg; + }; + const gridSeg = segment(PIXELIA_GRID, true); + const segs = new Map(); + for (let cy = 0; cy < rows; cy++) { + for (let cx = 0; cx < cols; cx++) { + const c = cells.get(`${x0 + cx},${y0 + cy}`) || PIXELIA_EMPTY; + const key = c.join(','); + if (!segs.has(key)) segs.set(key, segment(c, false)); + const seg = segs.get(key); + for (let py = 0; py < block; py++) { + rgb.set(py === block - 1 ? gridSeg : seg, (((cy * block + py) * w) + cx * block) * 3); + } + } + } + return { w, h, rgb }; +}; + +const pixeliaSections = (canvas) => { + const out = []; + const pixels = asList(canvas.pixels); + const cols = Number(canvas.width) || 1; + const rows = Number(canvas.height) || 1; + const contributors = [...new Set(pixels.flatMap(px => asList(px.contributors_inhabitants)))]; + const cells = pixeliaCells(pixels, cols, rows); + const cell = Math.min(PIXELIA_MAX_CELL, PIXELIA_PAGE_W / cols); + const block = Math.max(6, Math.ceil(cell * 2)); + const perPage = Math.max(1, Math.floor(PIXELIA_PAGE_H / cell)); + out.push({ kind: 'title', text: 'Pixelia' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Total pixels', value: String(pixels.length) }); + out.push({ kind: 'kv', label: 'Contributors', value: String(contributors.length) }); + out.push({ kind: 'blank' }); + for (let y = 1; y <= rows; y += perPage) { + const n = Math.min(perPage, rows - y + 1); + out.push({ kind: 'image', raster: pixeliaRaster(cells, 1, y, cols, n, block), maxW: cols * cell, maxH: n * cell }); + } + if (contributors.length) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'CONTRIBUTORS' }); + out.push({ kind: 'blank' }); + for (const id of contributors) out.push({ kind: 'text', text: txt(id) }); + } + return out; +}; + +const mapPins = (item) => [{ + lat: item.lat, lng: item.lng, main: true, + label: txt(item.markerLabel) || txt(item.description) || txt(item.title) || 'Marker', + image: txt(item.image), author: item.author, createdAt: item.createdAt +}].concat(asList(item.markers).map(mk => ({ lat: mk.lat, lng: mk.lng, main: false, label: txt(mk.label), image: txt(mk.image), author: mk.author, createdAt: mk.createdAt }))); + +const mapSections = (item, extra = {}) => { + const names = (extra && extra.names) || {}; + const images = (extra && extra.images) || {}; + const who = id => names[id] ? `${names[id]} (${id})` : txt(id); + const coords = (lat, lng) => `${(Number(lat) || 0).toFixed(4)}, ${(Number(lng) || 0).toFixed(4)}`; + const out = []; + out.push({ kind: 'title', text: txt(item.title) || '-' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: 'Type', value: txt(item.mapType).toUpperCase() }); + out.push({ kind: 'kv', label: 'Location', value: coords(item.lat, item.lng) }); + out.push({ kind: 'kv', label: 'Author', value: who(item.author) }); + out.push({ kind: 'kv', label: 'Created', value: fmtDate(item.createdAt) }); + if (item.updatedAt && item.updatedAt !== item.createdAt) out.push({ kind: 'kv', label: 'Updated', value: fmtDate(item.updatedAt) }); + pushTags(out, item); + if (txt(item.description)) { + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'DESCRIPTION' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'text', text: String(item.description).replace(/\r\n/g, '\n') }); + } + const pins = mapPins(item); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: 'MAP' }); + out.push({ kind: 'blank' }); + out.push({ kind: 'map', pins }); + out.push({ kind: 'blank' }); + out.push({ kind: 'section', text: `MARKERS (${pins.length})` }); + pins.forEach((p, i) => { + out.push({ kind: 'blank' }); + out.push({ kind: 'kv', label: i === 0 ? 'Main marker' : `Marker ${i}`, value: p.label }); + out.push({ kind: 'kv', label: 'Coordinates', value: coords(p.lat, p.lng) }); + out.push({ kind: 'kv', label: 'Author', value: who(p.author) }); + if (p.createdAt) out.push({ kind: 'kv', label: 'Date', value: fmtDate(p.createdAt) }); + if (p.image && images[p.image]) out.push({ kind: 'image', buffer: images[p.image], caption: p.label, maxW: 200, maxH: 140 }); + }); + return out; +}; + +const BUILDERS = { + maps: { title: 'OASIS - Map', sections: mapSections, name: item => item.title }, + wiki: { title: 'OASIS - Wiki', sections: wikiSections, name: item => item.title }, + pads: { title: 'OASIS - Pad', sections: padSections, name: item => item.title }, + campaigns: { title: 'OASIS - Campaign', sections: campaignSections, name: item => item.title }, + logistics: { title: 'OASIS - Route', sections: logisticsSections, name: item => item.title }, + mailing: { title: 'OASIS - Mailing List', sections: mailingSections, name: item => item.title }, + emergencies: { title: 'OASIS - Emergency', sections: emergencySections, name: item => item.title }, + reports: { title: 'OASIS - Report', sections: reportSections, name: item => item.title }, + votes: { title: 'OASIS - Votation', sections: voteSections, name: item => item.question }, + events: { title: 'OASIS - Event', sections: eventSections, name: item => item.title }, + tasks: { title: 'OASIS - Task', sections: taskSections, name: item => item.title }, + calendars: { title: 'OASIS - Calendar', sections: calendarSections, name: item => item.title }, + cv: { title: 'OASIS - Curriculum', sections: cvSections, name: item => item.name || item.author }, + pixelia: { title: 'OASIS - Pixelia', sections: pixeliaSections, name: () => 'canvas' } +}; + +const isSupported = kind => Object.prototype.hasOwnProperty.call(BUILDERS, kind); + +const pdfFilename = (kind, item) => { + const b = BUILDERS[kind]; + const raw = b ? txt(b.name(item || {})) : ''; + const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40); + return `oasis-${kind}-${slug || 'document'}.pdf`; +}; + +const buildContentPdf = (kind, item, extra = {}, viewerId = null) => { + const b = BUILDERS[kind]; + if (!b) throw new Error('Unsupported pdf kind'); + return buildDocumentPdf({ + title: b.title, + issuedToLabel: 'Issued to', + issuedTo: viewerId || null, + sections: b.sections(item || {}, extra), + license: (item && item.license) || '' + }); +}; + +function buildSmartContractPdf({ transfer, block, viewerId }) { + const pageW = 612; + const pageH = 792; + const marginX = 50; + const headerH = 90; + const footerH = 40; + const bodyTop = pageH - headerH - 24; + const bodyBottom = footerH + 10; + const lineH = 14; + + let logoBuf = null; + let logoDims = null; + try { + logoBuf = fs.readFileSync(LOGO_PATH); + logoDims = readJpegDims(logoBuf); + } catch (_) {} + + const t = transfer || {}; + const b = block || {}; + const fmt = v => (v === undefined || v === null) ? '' : String(v); + const fmtAmount = () => { + const cat = String(t.category || 'ECONOMIC').toUpperCase(); + const unit = cat === 'TIME' ? 'h' : cat === 'TRUST' ? 'trust' : 'ECO'; + return `${Number(t.amount || 0).toFixed(6)} ${unit}`; + }; + const fmtDate = v => v ? new Date(v).toISOString().replace('T', ' ').slice(0, 19) + ' UTC' : ''; + const confirmedBy = Array.isArray(t.confirmedBy) ? t.confirmedBy : []; + const settledUbi = (Array.isArray(t.tags) ? t.tags : []).some(x => String(x).toUpperCase() === 'UBI') && /^[0-9a-f]{64}$/i.test(String(t.txid || '')); + const required = (t.from === t.to || settledUbi) ? 1 : 2; + const confirmedCount = confirmedBy.length; + const tags = Array.isArray(t.tags) ? t.tags.join(', ') : ''; + + const sections = []; + sections.push({ kind: 'title', text: `Concept: ${t.concept || '-'}` }); + sections.push({ kind: 'blank' }); + + sections.push({ kind: 'section', text: 'OASIS IDs' }); + sections.push({ kind: 'kv', label: 'From', value: fmt(t.from) }); + sections.push({ kind: 'kv', label: 'To', value: fmt(t.to) }); + sections.push({ kind: 'blank' }); + + sections.push({ kind: 'section', text: 'TERMS' }); + sections.push({ kind: 'kv', label: 'Category', value: String(t.category || 'ECONOMIC').toUpperCase() }); + if (String(t.category || '').toUpperCase() !== 'TRUST') sections.push({ kind: 'kv', label: 'Amount', value: fmtAmount() }); + sections.push({ kind: 'kv', label: 'Status', value: fmt(t.status) }); + if (t.deadline) sections.push({ kind: 'kv', label: 'Deadline', value: fmtDate(t.deadline) }); + if (tags) sections.push({ kind: 'kv', label: 'Tags', value: tags }); + sections.push({ kind: 'blank' }); + + sections.push({ kind: 'section', text: 'CONFIRMATIONS' }); + sections.push({ kind: 'kv', label: 'Required', value: String(required) }); + sections.push({ kind: 'kv', label: 'Confirmed', value: String(confirmedCount) }); + if (confirmedBy.length) { + for (const f of confirmedBy) sections.push({ kind: 'kv', label: 'Signed by', value: fmt(f) }); + } + sections.push({ kind: 'blank' }); + + sections.push({ kind: 'section', text: 'BLOCK VERIFICATION' }); + if (b && b.id) { + sections.push({ kind: 'kv', label: 'Block ID', value: fmt(b.id) }); + if (b.ts) sections.push({ kind: 'kv', label: 'Block Timestamp', value: fmtDate(b.ts) }); + if (b.type) sections.push({ kind: 'kv', label: 'Block Type', value: String(b.type).toUpperCase() }); + if (b.author) sections.push({ kind: 'kv', label: 'Block Author', value: fmt(b.author) }); + if (b.size) sections.push({ kind: 'kv', label: 'Block Size', value: `${b.size} bytes` }); + } else { + sections.push({ kind: 'kv', label: 'Block ID', value: fmt(t.id) }); + } + sections.push({ kind: 'blank' }); + + sections.push({ kind: 'section', text: 'METADATA' }); + sections.push({ kind: 'kv', label: 'Transfer ID', value: fmt(t.id) }); + if (t.createdAt) sections.push({ kind: 'kv', label: 'Created At', value: fmtDate(t.createdAt) }); + if (t.updatedAt) sections.push({ kind: 'kv', label: 'Updated At', value: fmtDate(t.updatedAt) }); + + const lines = []; + for (const s of sections) { + if (s.kind === 'kv') { + const txt = `${s.label}: ${s.value}`; + for (const w of wrap(txt, 82)) lines.push({ kind: 'kv', text: w }); + } else { + lines.push(s); + } + } + + const maxBodyLines = Math.floor((bodyTop - bodyBottom) / lineH); + const pages = []; + for (let i = 0; i < lines.length; i += maxBodyLines) pages.push(lines.slice(i, i + maxBodyLines)); + if (!pages.length) pages.push([]); + + const objects = []; + const addObj = body => { objects.push(body); return objects.length; }; + + const catalogId = addObj(null); + const pagesId = addObj(null); + const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); + const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); + let logoXObjId = null; + if (logoBuf && logoDims) { + const cs = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; + const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; + logoXObjId = addObj({ dict, stream: logoBuf }); + } + + const exportDate = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; + const footerLeft = `Generated: ${exportDate}`; + + const pageIds = []; + const contentIds = []; + + pages.forEach((pg, pgIdx) => { + const parts = []; + if (logoXObjId) { + const logoH = 60; + const logoW = Math.round((logoDims.w / logoDims.h) * logoH); + parts.push(`q\n${logoW} 0 0 ${logoH} ${marginX} ${pageH - headerH + 15} cm\n/Logo Do\nQ`); + } + const titleX = (logoXObjId ? marginX + 80 : marginX); + const titleY = pageH - 45; + parts.push(`BT\n/F2 16 Tf\n${titleX} ${titleY} Td\n(${escapePdf('OASIS - Smart Contract')}) Tj\nET`); + const prefix = 'Issued to: '; + const prefixW = prefix.length * 5.4; + parts.push(`BT\n/F1 9 Tf\n${titleX} ${titleY - 16} Td\n(${escapePdf(prefix)}) Tj\nET`); + parts.push(`BT\n/F2 9 Tf\n${titleX + prefixW} ${titleY - 16} Td\n(${escapePdf(String(viewerId || ''))}) Tj\nET`); + + parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${pageH - headerH} m\n${pageW - marginX} ${pageH - headerH} l\nS\nQ`); + + let y = bodyTop; + for (const ln of pg) { + if (ln.kind === 'title') { + parts.push(`BT\n/F2 14 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } else if (ln.kind === 'subtitle') { + parts.push(`BT\n/F1 11 Tf\n0.2 0.2 0.2 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } else if (ln.kind === 'section') { + parts.push(`BT\n/F2 11 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + parts.push(`q\n0 0 0 RG\n0.5 w\n${marginX} ${y - 3} m\n${pageW - marginX} ${y - 3} l\nS\nQ`); + } else if (ln.kind === 'kv') { + parts.push(`BT\n/F1 10 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } + y -= lineH; + } + + parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${footerH + 5} m\n${pageW - marginX} ${footerH + 5} l\nS\nQ`); + parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 10} Td\n(${escapePdf(footerLeft)}) Tj\nET`); + parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 21} Td\n(${escapePdf(licenseFooter(''))}) Tj\nET`); + const pageLabel = `Page ${pgIdx + 1} of ${pages.length}`; + const pageLabelW = pageLabel.length * 4.8; + parts.push(`BT\n/F1 8 Tf\n${pageW - marginX - pageLabelW} ${footerH - 10} Td\n(${escapePdf(pageLabel)}) Tj\nET`); + + const content = parts.join('\n'); + const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; + const cid = addObj(stream); + contentIds.push(cid); + const pid = addObj(null); + pageIds.push(pid); + }); + + for (let i = 0; i < pageIds.length; i++) { + const resources = logoXObjId + ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << /Logo ${logoXObjId} 0 R >> >>` + : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; + objects[pageIds[i] - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentIds[i]} 0 R /Resources ${resources} >>`; + } + objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; + objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; + + const chunks = []; + const offsets = [0]; + let byteLen = 0; + const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; + push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); + for (let i = 0; i < objects.length; i++) { + offsets.push(byteLen); + const obj = objects[i]; + if (obj && typeof obj === 'object' && obj.dict && obj.stream) { + push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); + push(obj.stream); + push(Buffer.from('\nendstream\nendobj\n', 'binary')); + } else { + push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); + } + } + const xrefStart = byteLen; + let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; + for (let i = 1; i <= objects.length; i++) xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; + xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; + push(Buffer.from(xref, 'binary')); + return Buffer.concat(chunks); +} + +function buildCertificatePdf({ cert, course, studentName, teacherName }) { + const pageW = 792; + const pageH = 612; + const margin = 40; + + let logoBuf = null; + let logoDims = null; + try { + logoBuf = fs.readFileSync(LOGO_PATH); + logoDims = readJpegDims(logoBuf); + } catch (_) {} + + const c = cert || {}; + const courseTitle = String((course && course.title) || c.courseTitle || 'Oasis Course'); + const issued = c.createdAt ? new Date(c.createdAt) : new Date(); + const issuedStr = issued.toISOString().slice(0, 10); + + const charW = (size) => size * 0.6; + const centered = (text, size, font, y, rgb = '0 0 0') => { + const t = String(text || ''); + const x = Math.max(margin, (pageW - t.length * charW(size)) / 2); + return `BT\n/${font} ${size} Tf\n${rgb} rg\n${x} ${y} Td\n(${escapePdf(t)}) Tj\nET`; + }; + + const parts = []; + + parts.push(`q\n0.85 0.65 0.1 RG\n3 w\n${margin} ${margin} ${pageW - margin * 2} ${pageH - margin * 2} re\nS\nQ`); + parts.push(`q\n0.3 0.3 0.3 RG\n1 w\n${margin + 8} ${margin + 8} ${pageW - (margin + 8) * 2} ${pageH - (margin + 8) * 2} re\nS\nQ`); + + if (logoBuf && logoDims) { + const logoH = 64; + const logoW = Math.round((logoDims.w / logoDims.h) * logoH); + parts.push(`q\n${logoW} 0 0 ${logoH} ${(pageW - logoW) / 2} ${pageH - 140} cm\n/Logo Do\nQ`); + } + + parts.push(centered('OASIS', 26, 'F2', pageH - 175)); + parts.push(centered('CERTIFICATE OF COMPLETION', 16, 'F2', pageH - 205, '0.55 0.4 0.05')); + + parts.push(centered('This certifies that', 11, 'F1', pageH - 250, '0.25 0.25 0.25')); + parts.push(centered(studentName || c.student || '', 18, 'F2', pageH - 280)); + if (studentName && c.student && studentName !== c.student) { + parts.push(centered(c.student, 7, 'F1', pageH - 296, '0.45 0.45 0.45')); + } + + parts.push(centered('has successfully completed the course', 11, 'F1', pageH - 330, '0.25 0.25 0.25')); + parts.push(centered(courseTitle, 16, 'F2', pageH - 360)); + + parts.push(centered(`Issued on ${issuedStr}`, 10, 'F1', pageH - 400, '0.25 0.25 0.25')); + + const sigY = 135; + parts.push(`q\n0.3 0.3 0.3 RG\n0.7 w\n${margin + 60} ${sigY + 14} m\n${margin + 280} ${sigY + 14} l\nS\nQ`); + parts.push(`BT\n/F2 11 Tf\n0 0 0 rg\n${margin + 60} ${sigY} Td\n(${escapePdf(teacherName || c.author || '')}) Tj\nET`); + parts.push(`BT\n/F1 8 Tf\n0.35 0.35 0.35 rg\n${margin + 60} ${sigY - 13} Td\n(${escapePdf('Teacher')}) Tj\nET`); + if (teacherName && c.author && teacherName !== c.author) { + parts.push(`BT\n/F1 6 Tf\n0.45 0.45 0.45 rg\n${margin + 60} ${sigY - 24} Td\n(${escapePdf(c.author)}) Tj\nET`); + } + + parts.push(centered('Cryptographically signed on the Oasis P2P network — verifiable by anyone', 8, 'F1', margin + 30, '0.35 0.35 0.35')); + if (c.id) parts.push(centered(`Certificate ID: ${c.id}`, 6, 'F1', margin + 18, '0.45 0.45 0.45')); + parts.push(centered(licenseFooter(''), 6, 'F1', margin + 42, '0.45 0.45 0.45')); + + const content = parts.join('\n'); + + const objects = []; + const addObj = body => { objects.push(body); return objects.length; }; + + const catalogId = addObj(null); + const pagesId = addObj(null); + const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); + const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); + let logoXObjId = null; + if (logoBuf && logoDims) { + const cs = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; + const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; + logoXObjId = addObj({ dict, stream: logoBuf }); + } + + const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; + const contentId = addObj(stream); + const pageId = addObj(null); + + const resources = logoXObjId + ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << /Logo ${logoXObjId} 0 R >> >>` + : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; + objects[pageId - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentId} 0 R /Resources ${resources} >>`; + objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; + objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageId} 0 R] /Count 1 >>`; + + const chunks = []; + const offsets = [0]; + let byteLen = 0; + const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; + push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); + for (let i = 0; i < objects.length; i++) { + offsets.push(byteLen); + const obj = objects[i]; + if (obj && typeof obj === 'object' && obj.dict && obj.stream) { + push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); + push(obj.stream); + push(Buffer.from('\nendstream\nendobj\n', 'binary')); + } else { + push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); + } + } + const xrefStart = byteLen; + let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; + for (let i = 1; i <= objects.length; i++) xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; + xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; + push(Buffer.from(xref, 'binary')); + return Buffer.concat(chunks); +} + +const linkPattern = /(?:https?:\/\/[^\s]+|www\.[^\s]+|@[A-Za-z0-9+/=.\-]+\.ed25519|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/g; + +const splitSegments = (line) => { + const segs = []; + let last = 0; + const re = new RegExp(linkPattern.source, 'g'); + let m; + while ((m = re.exec(line)) !== null) { + if (m.index > last) segs.push({ t: line.slice(last, m.index), l: false }); + segs.push({ t: m[0], l: true }); + last = m.index + m[0].length; + } + if (last < line.length) segs.push({ t: line.slice(last), l: false }); + return segs; +}; + +function buildLogsPdf(entries, oasisId, opts = {}) { + const pageW = 612; + const pageH = 792; + const marginX = 50; + const headerH = 90; + const footerH = 40; + const bodyTop = pageH - headerH - 22; + const bodyBottom = footerH + 10; + const lineH = 12; + const maxBodyLines = Math.floor((bodyTop - bodyBottom) / lineH); + + let logoBuf = null; + let logoDims = null; + try { + logoBuf = fs.readFileSync(LOGO_PATH); + logoDims = readJpegDims(logoBuf); + } catch (_) {} + + const allLines = []; + for (const e of entries) { + const ts = new Date(e.ts); + const when = ts.toISOString().replace('T', ' ').slice(0, 19); + allLines.push({ kind: 'header', text: `[${when}]:` }); + allLines.push({ kind: 'blank', text: '' }); + for (const l of wrap(e.text, 82)) allLines.push({ kind: 'text', text: l }); + allLines.push({ kind: 'blank', text: '' }); + } + if (!allLines.length) allLines.push({ kind: 'text', text: '(no entries)' }); + + const pages = []; + for (let i = 0; i < allLines.length; i += maxBodyLines) { + pages.push(allLines.slice(i, i + maxBodyLines)); + } + if (!pages.length) pages.push([{ kind: 'text', text: '(no entries)' }]); + + const objects = []; + const addObj = body => { objects.push(body); return objects.length; }; + + const catalogId = addObj(null); + const pagesId = addObj(null); + const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); + const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); + + let logoXObjId = null; + if (logoBuf && logoDims) { + const colorSpace = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; + const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${colorSpace} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; + logoXObjId = addObj({ dict, stream: logoBuf }); + } + + const exportDate = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; + const footerLeft = `Generated: ${exportDate}`; + + const pageIds = []; + const contentIds = []; + + pages.forEach((pg, pgIdx) => { + const parts = []; + + if (logoXObjId) { + const logoH = 60; + const logoW = Math.round((logoDims.w / logoDims.h) * logoH); + const logoX = marginX; + const logoY = pageH - headerH + 15; + parts.push(`q\n${logoW} 0 0 ${logoH} ${logoX} ${logoY} cm\n/Logo Do\nQ`); + } + + const titleX = (logoXObjId ? marginX + 80 : marginX); + const titleY = pageH - 45; + parts.push(`BT\n/F2 16 Tf\n${titleX} ${titleY} Td\n(${escapePdf('OASIS - Experience logs')}) Tj\nET`); + const inhabitantPrefix = 'Inhabitant: '; + const inhabitantPrefixW = inhabitantPrefix.length * 5.4; + parts.push(`BT\n/F1 9 Tf\n${titleX} ${titleY - 16} Td\n(${escapePdf(inhabitantPrefix)}) Tj\nET`); + parts.push(`BT\n/F2 9 Tf\n${titleX + inhabitantPrefixW} ${titleY - 16} Td\n(${escapePdf(String(oasisId || ''))}) Tj\nET`); + + parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${pageH - headerH} m\n${pageW - marginX} ${pageH - headerH} l\nS\nQ`); + + let y = bodyTop; + const charW = 6; + for (const ln of pg) { + if (ln.kind === 'header') { + parts.push(`BT\n/F2 10 Tf\n1 0.647 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); + } else if (ln.text) { + const segs = splitSegments(ln.text); + let x = marginX; + for (const s of segs) { + if (!s.t) continue; + const color = s.l ? '0 0 1 rg' : '0 0 0 rg'; + parts.push(`BT\n/F1 10 Tf\n${color}\n${x} ${y} Td\n(${escapePdf(s.t)}) Tj\nET`); + x += s.t.length * charW; + } + } + y -= lineH; + } + + parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${footerH + 5} m\n${pageW - marginX} ${footerH + 5} l\nS\nQ`); + parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 10} Td\n(${escapePdf(footerLeft)}) Tj\nET`); + parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 21} Td\n(${escapePdf(licenseFooter(''))}) Tj\nET`); + const pageLabel = `Page ${pgIdx + 1} of ${pages.length}`; + const pageLabelW = pageLabel.length * 4.8; + parts.push(`BT\n/F1 8 Tf\n${pageW - marginX - pageLabelW} ${footerH - 10} Td\n(${escapePdf(pageLabel)}) Tj\nET`); + + const content = parts.join('\n'); + const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; + const cid = addObj(stream); + contentIds.push(cid); + const pid = addObj(null); + pageIds.push(pid); + }); + + for (let i = 0; i < pageIds.length; i++) { + const resources = logoXObjId + ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << /Logo ${logoXObjId} 0 R >> >>` + : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; + objects[pageIds[i] - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentIds[i]} 0 R /Resources ${resources} >>`; + } + + objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; + objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; + + const chunks = []; + const offsets = [0]; + let byteLen = 0; + const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; + + push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); + + for (let i = 0; i < objects.length; i++) { + offsets.push(byteLen); + const obj = objects[i]; + if (obj && typeof obj === 'object' && obj.dict && obj.stream) { + push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); + push(obj.stream); + push(Buffer.from('\nendstream\nendobj\n', 'binary')); + } else { + push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); + } + } + + const xrefStart = byteLen; + let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; + for (let i = 1; i <= objects.length; i++) { + xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; + } + xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; + push(Buffer.from(xref, 'binary')); + + return Buffer.concat(chunks); +} + +const buildRecoveryKitPdf = (kit, { qr = null, labels = {} } = {}) => { + const sections = []; + sections.push({ kind: 'title', text: labels.title || 'RECOVERY' }); + sections.push({ kind: 'blank' }); + sections.push({ kind: 'kv', label: labels.id || 'Oasis ID', value: kit.id || '' }); + sections.push({ kind: 'kv', label: labels.date || 'Generated', value: fmtDate(kit.createdAt) }); + if (qr) { sections.push({ kind: 'blank' }); sections.push({ kind: 'image', buffer: qr, caption: '' }); } + sections.push({ kind: 'blank' }); + sections.push({ kind: 'section', text: 'SECRET' }); + sections.push({ kind: 'blank' }); + for (const line of String(kit.secret || '').split('\n')) sections.push({ kind: 'text', text: line }); + return buildDocumentPdf({ title: 'OASIS - Recovery', issuedToLabel: labels.id || 'Oasis ID', issuedTo: kit.id || '', sections }); +}; + +module.exports = { buildDocumentPdf, escapePdf, wrap, buildContentPdf, pdfFilename, isSupported, buildSmartContractPdf, buildCertificatePdf, buildLogsPdf, buildRecoveryKitPdf }; diff --git a/src/backend/pdfDocument.js b/src/backend/pdfDocument.js deleted file mode 100644 index 4e3dc3e..0000000 --- a/src/backend/pdfDocument.js +++ /dev/null @@ -1,199 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const LOGO_PATH = path.join(__dirname, '..', 'client', 'assets', 'images', 'snh-oasis.jpg'); - -const WIN_ANSI_HIGH = { - '\u20AC': '\x80', '\u201A': '\x82', '\u0192': '\x83', '\u201E': '\x84', - '\u2026': '\x85', '\u2020': '\x86', '\u2021': '\x87', '\u02C6': '\x88', - '\u2030': '\x89', '\u0160': '\x8A', '\u2039': '\x8B', '\u0152': '\x8C', - '\u017D': '\x8E', '\u2018': '\x91', '\u2019': '\x92', '\u201C': '\x93', - '\u201D': '\x94', '\u2022': '\x95', '\u2013': '\x96', '\u2014': '\x97', - '\u02DC': '\x98', '\u2122': '\x99', '\u0161': '\x9A', '\u203A': '\x9B', - '\u0153': '\x9C', '\u017E': '\x9E', '\u0178': '\x9F' -}; - -const escapePdf = s => String(s == null ? '' : s) - .replace(/[\r\n\t]+/g, ' ') - .replace(/[\u0152\u0153\u0160\u0161\u0178\u017D\u017E\u0192\u02C6\u02DC\u2013\u2014\u2018\u2019\u201A\u201C\u201D\u201E\u2020\u2021\u2022\u2026\u2030\u2039\u203A\u20AC\u2122]/g, - (c) => WIN_ANSI_HIGH[c] || '?') - .replace(/[^\x20-\xFF\x80-\x9F]/g, '?') - .replace(/\\/g, '\\\\') - .replace(/\(/g, '\\(') - .replace(/\)/g, '\\)'); - -const wrap = (txt, max = 82) => { - const out = []; - for (const raw of String(txt == null ? '' : txt).split('\n')) { - let line = raw; - while (line.length > max) { - let cut = line.lastIndexOf(' ', max); - if (cut <= 0) cut = max; - out.push(line.slice(0, cut)); - line = line.slice(cut).replace(/^\s+/, ''); - } - out.push(line); - } - return out; -}; - -const readJpegDims = (buf) => { - let i = 2; - while (i < buf.length) { - if (buf[i] !== 0xFF) return null; - const marker = buf[i + 1]; - if (marker === 0xD8 || marker === 0xD9) { i += 2; continue; } - const len = buf.readUInt16BE(i + 2); - if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) { - const h = buf.readUInt16BE(i + 5); - const w = buf.readUInt16BE(i + 7); - const c = buf[i + 9]; - return { w, h, c }; - } - i += 2 + len; - } - return null; -}; - -const flattenSections = (sections) => { - const lines = []; - for (const s of Array.isArray(sections) ? sections : []) { - if (!s) continue; - if (s.kind === 'kv') { - const txt = `${s.label}: ${s.value == null ? '' : s.value}`; - for (const w of wrap(txt, 82)) lines.push({ kind: 'kv', text: w }); - } else if (s.kind === 'text') { - for (const w of wrap(s.text, 82)) lines.push({ kind: 'kv', text: w }); - } else { - lines.push({ kind: s.kind, text: s.text }); - } - } - return lines; -}; - -function buildDocumentPdf({ title, issuedToLabel, issuedTo, sections } = {}) { - const pageW = 612; - const pageH = 792; - const marginX = 50; - const headerH = 90; - const footerH = 40; - const bodyTop = pageH - headerH - 24; - const bodyBottom = footerH + 10; - const lineH = 14; - - let logoBuf = null; - let logoDims = null; - try { - logoBuf = fs.readFileSync(LOGO_PATH); - logoDims = readJpegDims(logoBuf); - } catch (_) {} - - const lines = flattenSections(sections); - - const maxBodyLines = Math.floor((bodyTop - bodyBottom) / lineH); - const pages = []; - for (let i = 0; i < lines.length; i += maxBodyLines) pages.push(lines.slice(i, i + maxBodyLines)); - if (!pages.length) pages.push([]); - - const objects = []; - const addObj = body => { objects.push(body); return objects.length; }; - - const catalogId = addObj(null); - const pagesId = addObj(null); - const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); - const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); - let logoXObjId = null; - if (logoBuf && logoDims) { - const cs = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; - const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; - logoXObjId = addObj({ dict, stream: logoBuf }); - } - - const exportDate = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; - const footerLeft = `Generated: ${exportDate}`; - - const pageIds = []; - const contentIds = []; - - pages.forEach((pg, pgIdx) => { - const parts = []; - if (logoXObjId) { - const logoH = 60; - const logoW = Math.round((logoDims.w / logoDims.h) * logoH); - parts.push(`q\n${logoW} 0 0 ${logoH} ${marginX} ${pageH - headerH + 15} cm\n/Logo Do\nQ`); - } - const titleX = (logoXObjId ? marginX + 80 : marginX); - const titleY = pageH - 45; - parts.push(`BT\n/F2 16 Tf\n${titleX} ${titleY} Td\n(${escapePdf(title || 'OASIS')}) Tj\nET`); - if (issuedTo) { - const prefix = `${issuedToLabel || 'Issued to'}: `; - const prefixW = prefix.length * 5.4; - parts.push(`BT\n/F1 9 Tf\n${titleX} ${titleY - 16} Td\n(${escapePdf(prefix)}) Tj\nET`); - parts.push(`BT\n/F2 9 Tf\n${titleX + prefixW} ${titleY - 16} Td\n(${escapePdf(String(issuedTo))}) Tj\nET`); - } - - parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${pageH - headerH} m\n${pageW - marginX} ${pageH - headerH} l\nS\nQ`); - - let y = bodyTop; - for (const ln of pg) { - if (ln.kind === 'title') { - parts.push(`BT\n/F2 14 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } else if (ln.kind === 'subtitle') { - parts.push(`BT\n/F1 11 Tf\n0.2 0.2 0.2 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } else if (ln.kind === 'section') { - parts.push(`BT\n/F2 11 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - parts.push(`q\n0 0 0 RG\n0.5 w\n${marginX} ${y - 3} m\n${pageW - marginX} ${y - 3} l\nS\nQ`); - } else if (ln.kind === 'kv') { - parts.push(`BT\n/F1 10 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } - y -= lineH; - } - - parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${footerH + 5} m\n${pageW - marginX} ${footerH + 5} l\nS\nQ`); - parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 10} Td\n(${escapePdf(footerLeft)}) Tj\nET`); - const pageLabel = `Page ${pgIdx + 1} of ${pages.length}`; - const pageLabelW = pageLabel.length * 4.8; - parts.push(`BT\n/F1 8 Tf\n${pageW - marginX - pageLabelW} ${footerH - 10} Td\n(${escapePdf(pageLabel)}) Tj\nET`); - - const content = parts.join('\n'); - const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; - const cid = addObj(stream); - contentIds.push(cid); - const pid = addObj(null); - pageIds.push(pid); - }); - - for (let i = 0; i < pageIds.length; i++) { - const resources = logoXObjId - ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << /Logo ${logoXObjId} 0 R >> >>` - : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; - objects[pageIds[i] - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentIds[i]} 0 R /Resources ${resources} >>`; - } - objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; - objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; - - const chunks = []; - const offsets = [0]; - let byteLen = 0; - const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; - push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); - for (let i = 0; i < objects.length; i++) { - offsets.push(byteLen); - const obj = objects[i]; - if (obj && typeof obj === 'object' && obj.dict && obj.stream) { - push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); - push(obj.stream); - push(Buffer.from('\nendstream\nendobj\n', 'binary')); - } else { - push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); - } - } - const xrefStart = byteLen; - let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; - for (let i = 1; i <= objects.length; i++) xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; - xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; - push(Buffer.from(xref, 'binary')); - return Buffer.concat(chunks); -} - -module.exports = { buildDocumentPdf, escapePdf, wrap }; diff --git a/src/backend/pm_policy.js b/src/backend/pm_policy.js index 6b69b8a..d6dd9e5 100644 --- a/src/backend/pm_policy.js +++ b/src/backend/pm_policy.js @@ -11,7 +11,7 @@ const isRecipientAllowed = ({ pmVisibility, viewerId, recipientId, relationship }; const announceSeenPath = () => { - try { return require('../server/ssb_config').statePath(ANNOUNCE_SEEN_FILE); } catch (_) { return null; } + try { return require('../configs/state-manager').statePath(ANNOUNCE_SEEN_FILE); } catch (_) { return null; } }; const readAnnounceSeen = () => { diff --git a/src/backend/renderStyledText.js b/src/backend/renderStyledText.js new file mode 100644 index 0000000..baf8879 --- /dev/null +++ b/src/backend/renderStyledText.js @@ -0,0 +1,325 @@ +const { a, img, video, audio, span, strong, em, u, s: strike, code, div, textarea } = require("../server/node_modules/hyperaxe"); +const i18nBase = require("../client/assets/translations/i18n"); +const { WIKILINK_RE, slugify, linkTarget } = require("../models/wiki_model"); + +let zoomSeq = 0; + +function getI18n() { + try { + const { i18n } = require("../views/main_views"); + return i18n; + } catch (_) { + return i18nBase['en'] || {}; + } +} + +const ESCAPE_RE = /\\([\\*_~`#>\-\[\]!])/g; +const CODE_BLOCK_RE = /```([\s\S]*?)```[ \t]*(?:\n(?:[ \t]*\n)*|$)/g; +const RULE_RE = /^[ \t]*---[ \t]*(?:\n(?:[ \t]*\n)*|$)/gm; +const HEADER_CLOSED_RE = /^[ \t]*(#{1,6})[ \t]*([^\n]{1,200}?)[ \t]*\1[ \t]*(?:\n(?:[ \t]*\n)*|$)/gm; +const HEADER_SPACED_RE = /^[ \t]*(#{1,6})[ \t]+([^\n]{1,200}?)[ \t]*#*[ \t]*(?:\n(?:[ \t]*\n)*|$)/gm; +const QUOTE_RE = /^[ \t]*>[ \t]?([^\n]*)(?:\n(?:[ \t]*\n)*|$)/gm; +const BULLET_RE = /^([ \t]*)-[ \t]+([^\n]+)(?:\n(?:[ \t]*\n)*|$)/gm; +const NUMBER_RE = /^([ \t]*)(\d{1,3})([.)])[ \t]+([^\n]+)(?:\n(?:[ \t]*\n)*|$)/gm; +const BOLD_RE = /\*\*(?!\s)((?:[^*\n]|\*(?!\*))+?)(? { mentionResolver = typeof fn === 'function' ? fn : null; }; +const bareMentionName = (token) => (/\.ed25519$/i.test(token) ? '' : token.replace(/[.\-]+$/, '')); +const MSG_REF_RE = /%[A-Za-z0-9+/=]{44}\.sha256/g; +const MD_LINK_RE = /!?\[([^\]\n]{1,160})\]\((https?:\/\/[^)\s]+|\/(?![\/\\])[^)\s]*)\)/g; +const URL_RE = /\b(?:https?:\/\/|www\.)[^\s<>"'`]+/gi; +const EMAIL_RE = /\b[A-Za-z0-9][A-Za-z0-9._%+-]{0,63}@(?:[A-Za-z0-9-]{1,63}\.)+[A-Za-z]{2,24}\b/g; +const HASHTAG_RE = /#[\w\u0080-\uFFFF]{1,32}(?![\w\u0080-\uFFFF])/g; +const URL_TAIL_RE = /[.,;:!?»"')\]}>]+$/; +const SELF_HOST_RE = /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:[/?#]|$)/i; +const INTERNAL_PATHS = [ + 'agenda','ai','audios','author','backup','banking','blockexplorer','blogs','bookmarks','calendars','campaigns', + 'chats','cipher','courts','cv','dev','docs','emergencies','events','favorites','feed','files','forum','games','graphos', + 'hashtag','housing','images','industry','inbox','inhabitants','invites','jobs','larp','logistics','logs','mailing', + 'maps','market','melody','mentions','modules','multiverse','opinions','pads','parliament','rooms','peers','pixelia','pm', + 'podcasts','polls','popular','profile','projects','publish','reports','school','search','settings','shops','spread', + 'stats','tags','tasks','thread','torrents','transfers','trending','tribes','tribe','videos','votes','votations','wallet','wiki' +]; +const INTERNAL_PATH_SET = new Set(INTERNAL_PATHS); +const SSB_ID_RE = /^(?:%[A-Za-z0-9+/=]{20,}\.sha256|@[A-Za-z0-9+/=]{20,}\.ed25519|&[A-Za-z0-9+/=]{20,}\.sha256)$/; + +const internalTargetOf = (url) => { + let parsed = null; + try { parsed = new URL(url); } catch (_) { return null; } + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null; + const segments = parsed.pathname.split('/').filter(Boolean).map(seg => { try { return decodeURIComponent(seg); } catch (_) { return seg; } }); + if (!segments.length || !INTERNAL_PATH_SET.has(segments[0].toLowerCase())) return null; + const sameHost = SELF_HOST_RE.test(url); + const hasSsbId = segments.slice(1).some(seg => SSB_ID_RE.test(seg)); + if (!sameHost && !hasSsbId) return null; + const id = segments.slice(1).find(seg => SSB_ID_RE.test(seg)) || segments[1] || ''; + return { href: `${parsed.pathname}${parsed.search}${parsed.hash}`, kind: segments[0].toLowerCase(), id }; +}; + +const shortInternalId = (id) => { + const value = String(id || ''); + if (!value) return ''; + const sigil = /^[%@&]/.test(value) ? value[0] : ''; + const body = sigil ? value.slice(1) : value; + return body.length > 8 ? `${sigil}${body.slice(0, 8)}…` : `${sigil}${body}`; +}; +const MAX_DEPTH = 8; + +const headerLevel = (hashes) => Math.min(3, String(hashes || '#').length); + +const indentLevel = (indent) => { + const width = String(indent || '').replace(/\t/g, ' ').length; + return Math.min(4, Math.floor(width / 2) + 1); +}; + +const codeBlockBody = (raw) => { + const s = String(raw); + const nl = s.indexOf('\n'); + if (nl === -1) return s; + return s.slice(nl + 1).replace(/\n[ \t]*$/, ''); +}; + +function renderStyledText(value, opts = {}) { + if (typeof value !== 'string') return value === null || value === undefined ? [] : [String(value)]; + const text = value.indexOf('\r') === -1 ? value : value.replace(/\r\n?/g, '\n'); + const depth = Number(opts.depth) || 0; + if (depth > MAX_DEPTH) return [text]; + const allMatches = []; + const push = (m, extra) => allMatches.push({ index: m.index, length: m[0].length, ...extra }); + for (const m of text.matchAll(ESCAPE_RE)) push(m, { type: 'escaped', body: m[1] }); + for (const m of text.matchAll(WIKILINK_RE)) push(m, { type: 'wikilink', target: linkTarget(m[1]), label: (m[2] || linkTarget(m[1])).trim() }); + for (const m of text.matchAll(CODE_BLOCK_RE)) push(m, { type: 'code-block', body: codeBlockBody(m[1]) }); + for (const m of text.matchAll(RULE_RE)) push(m, { type: 'rule' }); + for (const m of text.matchAll(HEADER_CLOSED_RE)) push(m, { type: 'header', level: headerLevel(m[1]), body: m[2] }); + for (const m of text.matchAll(HEADER_SPACED_RE)) push(m, { type: 'header', level: headerLevel(m[1]), body: m[2] }); + for (const m of text.matchAll(QUOTE_RE)) push(m, { type: 'quote', body: m[1] }); + for (const m of text.matchAll(BULLET_RE)) push(m, { type: 'bullet', level: indentLevel(m[1]), body: m[2] }); + for (const m of text.matchAll(NUMBER_RE)) push(m, { type: 'number', level: indentLevel(m[1]), marker: `${m[2]}${m[3]}`, body: m[4] }); + for (const m of text.matchAll(BLOB_IMAGE_RE)) push(m, { type: 'blob-image', name: m[1], blob: m[2] }); + for (const m of text.matchAll(BLOB_VIDEO_RE)) push(m, { type: 'blob-video', name: m[1], blob: m[2] }); + for (const m of text.matchAll(BLOB_AUDIO_RE)) push(m, { type: 'blob-audio', name: m[1], blob: m[2] }); + for (const m of text.matchAll(BLOB_PDF_RE)) push(m, { type: 'blob-pdf', name: m[1], blob: m[2] }); + for (const m of text.matchAll(BLOB_TORRENT_RE)) push(m, { type: 'blob-file', name: m[1], blob: m[2] }); + for (const m of text.matchAll(BLOB_FILE_RE)) push(m, { type: 'blob-file', name: m[1], blob: m[2] }); + for (const m of text.matchAll(MD_MENTION_RE)) push(m, { type: 'md-mention', name: m[1], feedId: m[2] }); + for (const m of text.matchAll(RAW_MENTION_RE)) push(m, { type: 'raw-mention', feedId: m[1] }); + for (const m of text.matchAll(BARE_MENTION_RE)) { + const name = bareMentionName(m[2]); + if (name) allMatches.push({ index: m.index + m[1].length, length: name.length + 1, type: 'bare-mention', name }); + } + for (const m of text.matchAll(MSG_REF_RE)) push(m, { type: 'msg-ref', id: m[0] }); + for (const m of text.matchAll(MD_LINK_RE)) push(m, { type: 'md-link', label: m[1], href: m[2] }); + for (const m of text.matchAll(BOLD_RE)) push(m, { type: 'bold', body: m[1] }); + for (const m of text.matchAll(UNDERLINE_RE)) push(m, { type: 'underline', body: m[1] }); + for (const m of text.matchAll(STRIKE_RE)) push(m, { type: 'strike', body: m[1] }); + for (const m of text.matchAll(ITALIC_RE)) push(m, { type: 'italic', body: m[1] }); + for (const m of text.matchAll(INLINE_CODE_RE)) push(m, { type: 'inline-code', body: m[1] }); + for (const m of text.matchAll(URL_RE)) { + const trimmed = m[0].replace(URL_TAIL_RE, ''); + if (trimmed) allMatches.push({ index: m.index, length: trimmed.length, type: 'url', text: trimmed }); + } + for (const m of text.matchAll(EMAIL_RE)) push(m, { type: 'email', text: m[0] }); + for (const m of text.matchAll(HASHTAG_RE)) push(m, { type: 'hashtag', tag: m[0].slice(1) }); + allMatches.sort((x, y) => x.index - y.index); + const filtered = []; + let lastEnd = 0; + for (const m of allMatches) { + if (m.index < lastEnd) continue; + filtered.push(m); + lastEnd = m.index + m.length; + } + const blobHref = (id) => `${opts.blobPrefix || '/blob/'}${encodeURIComponent(id)}`; + const linksOn = opts.links !== false; + const internalOn = opts.internalLinks !== false; + const plain = (value) => opts.plainUrlClass ? span({ class: opts.plainUrlClass }, value) : value; + const inner = (body) => renderStyledText(body, { ...opts, depth: depth + 1 }); + const result = []; + let cursor = 0; + for (const m of filtered) { + if (cursor < m.index) result.push(text.slice(cursor, m.index)); + if (m.type === 'escaped') { + result.push(m.body); + } else if (m.type === 'msg-ref') { + result.push(internalOn ? a({ href: `/thread/${encodeURIComponent(m.id)}`, class: 'styled-link' }, m.id.slice(0, 10) + '...') : m.id); + } else if (m.type === 'wikilink') { + result.push(typeof opts.wikiLink === 'function' + ? opts.wikiLink(m.target, slugify(m.target), m.label) + : internalOn + ? a({ href: `/wiki/${encodeURIComponent(slugify(m.target))}`, class: 'wiki-link' }, m.label) + : m.label); + } else if (m.type === 'code-block') { + result.push(span({ class: 'rt-code-block' }, m.body)); + } else if (m.type === 'rule') { + result.push(span({ class: 'rt-rule' })); + } else if (m.type === 'header') { + result.push(span({ class: `rt-header rt-header-${m.level}` }, ...inner(m.body))); + } else if (m.type === 'quote') { + result.push(span({ class: 'rt-quote' }, ...inner(m.body))); + } else if (m.type === 'bullet') { + result.push(span({ class: `rt-item rt-item-${m.level}` }, ...inner(m.body))); + } else if (m.type === 'number') { + result.push(span({ class: `rt-item rt-item-number rt-item-${m.level}` }, `${m.marker} `, ...inner(m.body))); + } else if (m.type === 'bold') { + result.push(strong(...inner(m.body))); + } else if (m.type === 'underline') { + result.push(u(...inner(m.body))); + } else if (m.type === 'strike') { + result.push(strike(...inner(m.body))); + } else if (m.type === 'italic') { + result.push(em(...inner(m.body))); + } else if (m.type === 'inline-code') { + result.push(code({ class: 'rt-code' }, m.body)); + } else if (m.type === 'blob-image') { + const imageSrc = blobHref(m.blob); + if (opts.zoomImages) { + zoomSeq = (zoomSeq + 1) % 1000000; + const zoomId = `rtzoom-${zoomSeq}-${String(m.blob).replace(/[^a-zA-Z0-9]/g, '').slice(-10)}`; + result.push(a({ href: `#${zoomId}`, id: `${zoomId}-src`, class: 'zoom-link' }, img({ src: imageSrc, alt: m.name || '', class: 'post-image' }))); + result.push(span({ id: zoomId, class: 'lightbox' }, + a({ href: `${imageSrc}${imageSrc.includes('?') ? '&' : '?'}download=1`, class: 'lightbox-download', title: getI18n().fileShareDownload || 'Download' }, '\u2913'), + a({ href: `#${zoomId}-src`, class: 'lightbox-close' }, '\u00d7'), + img({ src: imageSrc, alt: m.name || '', class: 'lightbox-image' }) + )); + } else { + result.push(img({ src: imageSrc, alt: m.name || '', class: 'post-image' })); + } + } else if (m.type === 'blob-video') { + result.push(video({ controls: true, class: 'post-video', src: blobHref(m.blob) })); + } else if (m.type === 'blob-audio') { + result.push(audio({ controls: true, class: 'post-audio', src: blobHref(m.blob) })); + } else if (m.type === 'blob-pdf') { + const i18n = getI18n(); + result.push(a({ href: blobHref(m.blob), class: 'post-pdf', target: '_blank', rel: 'noopener noreferrer' }, m.name || i18n.pdfFallbackLabel || 'PDF')); + } else if (m.type === 'blob-file') { + result.push(a({ href: blobHref(m.blob), class: 'post-pdf', target: '_blank', rel: 'noopener noreferrer' }, m.name || m.blob.slice(1, 9))); + } else if (m.type === 'md-mention') { + result.push(internalOn ? a({ href: `/author/${encodeURIComponent('@' + m.feedId)}`, class: 'mention' }, '@' + m.name) : '@' + m.name); + } else if (m.type === 'raw-mention') { + result.push(internalOn ? a({ href: `/author/${encodeURIComponent('@' + m.feedId)}`, class: 'mention' }, '@' + m.feedId.slice(0, 8) + '...') : '@' + m.feedId.slice(0, 8) + '...'); + } else if (m.type === 'bare-mention') { + const feedId = internalOn && mentionResolver ? mentionResolver(m.name) : null; + result.push(feedId ? a({ href: `/author/${encodeURIComponent(feedId)}`, class: 'mention' }, '@' + m.name) : '@' + m.name); + } else if (m.type === 'md-link') { + const external = /^https?:\/\//.test(m.href) && !SELF_HOST_RE.test(m.href); + result.push(external + ? (linksOn ? a({ href: m.href, class: 'styled-link', target: '_blank', rel: 'noopener noreferrer' }, ...inner(m.label)) : plain(m.label)) + : (internalOn ? a({ href: m.href, class: 'styled-link' }, ...inner(m.label)) : m.label)); + } else if (m.type === 'url') { + const schemeMatch = m.text.match(/^(https?):\/\//i); + const href = schemeMatch ? `${schemeMatch[1].toLowerCase()}://${m.text.slice(schemeMatch[0].length)}` : `https://${m.text}`; + const internalTarget = internalTargetOf(href); + if (!linksOn) result.push(plain(m.text)); + else if (internalTarget) { + result.push(internalOn + ? a({ href: internalTarget.href, class: 'internal-link' }, + span({ class: 'internal-link-kind' }, internalTarget.kind.toUpperCase()), + internalTarget.id ? span({ class: 'internal-link-id' }, shortInternalId(internalTarget.id)) : null + ) + : plain(internalTarget.href)); + } + else if (SELF_HOST_RE.test(href)) result.push(a({ href }, m.text)); + else result.push(a({ href, target: '_blank', rel: 'noopener noreferrer' }, m.text)); + } else if (m.type === 'email') { + result.push(linksOn ? a({ href: `mailto:${m.text}` }, m.text) : plain(m.text)); + } else if (m.type === 'hashtag') { + const tagHref = typeof opts.hashtagHref === 'function' ? opts.hashtagHref(m.tag) : (internalOn ? `/search?query=%23${encodeURIComponent(m.tag)}` : null); + result.push(tagHref ? a({ href: tagHref, class: 'tag-link tag-inline' }, `#${m.tag}`) : `#${m.tag}`); + } + cursor = m.index + m.length; + } + if (cursor < text.length) result.push(text.slice(cursor)); + return result; +} + +const safeExternalHref = (url) => { + const value = String(url || '').trim(); + const lower = value.toLowerCase(); + if (lower.startsWith('http://') || lower.startsWith('https://') || lower.startsWith('mailto:')) return value; + return ''; +}; + +const escapeHtml = (value) => String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + +function renderStyledHtml(text, opts = {}) { + if (text === null || text === undefined || text === '') return ''; + return renderStyledText(String(text), opts) + .map(node => (node && typeof node === 'object' && node.outerHTML) ? node.outerHTML : escapeHtml(node)) + .join(''); +} + +function plainText(text) { + if (text === null || text === undefined) return ''; + return String(text) + .replace(/```[\s\S]*?```/g, '') + .replace(/^[ \t]*>[ \t]?/gm, '') + .replace(/^[ \t]*---[ \t]*$/gm, '') + .replace(/^[ \t]*#{1,6}[ \t]*/gm, '') + .replace(/[ \t]*#{1,6}[ \t]*$/gm, '') + .replace(/^[ \t]*- /gm, '') + .replace(/^[ \t]*\d{1,3}[.)] /gm, '') + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/__(.*?)__/g, '$1') + .replace(/~~(.*?)~~/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/`([^`]+)`/g, '$1') + .replace(/!?\[[^\]\n]{0,160}\]\(\s*&[^)\s\n]{1,120}\.sha256\s*\)/g, '') + .replace(/\[(?:video|audio|pdf):[^\]\n]{0,160}\]\([^)\n]{0,200}\)/g, '') + .replace(/\[\[([^\]|\n]{1,120})(?:\|([^\]\n]{1,120}))?\]\]/g, (m, target, label) => label || target) + .replace(/!?\[([^\]\n]{1,160})\]\(([^)\s\n]{0,200})\)/g, (m, label, href) => href ? `${label} (${href})` : label) + .replace(/\\([\\*_~`#>\-\[\]!])/g, '$1') + .trim(); +} + +function renderTextPreview(text, maxLength = 220) { + const preview = plainText(text).replace(/\n+/g, ' ').trim(); + return preview.length > maxLength ? preview.slice(0, maxLength) + '...' : preview; +} + +const FORMAT_MARKS = [ + { mark: '**bold**', style: 'bold' }, + { mark: '*italic*', style: 'italic' }, + { mark: '__underline__', style: 'underline' }, + { mark: '~~strike~~', style: 'strike' }, + { mark: '`code`', style: 'code' }, + { mark: '### title ###', style: 'header' }, + { mark: '- list', style: 'item' }, + { mark: '> quote', style: 'quote' }, + { mark: '[[wiki]]', style: 'wiki' } +]; + +function renderFormatBar() { + const i18n = getI18n(); + const marks = []; + FORMAT_MARKS.forEach((m, i) => { + if (i) marks.push(span({ class: 'rt-sep' }, '|')); + marks.push(span({ class: `rt-mark rt-mark-${m.style}` }, m.mark)); + }); + return div({ class: 'rt-toolbar' }, + span({ class: 'rt-toolbar-label' }, `${i18n.formatBarLabel || 'Format'}:`), + ...marks + ); +} + +function richTextarea(attrs, ...children) { + return [renderFormatBar(), textarea(attrs, ...children)]; +} + +module.exports = { renderStyledText, renderStyledHtml, renderTextPreview, plainText, escapeHtml, safeExternalHref, renderFormatBar, richTextarea, setMentionResolver, INTERNAL_PATHS }; diff --git a/src/backend/renderTextWithStyles.js b/src/backend/renderTextWithStyles.js deleted file mode 100644 index c403a45..0000000 --- a/src/backend/renderTextWithStyles.js +++ /dev/null @@ -1,139 +0,0 @@ -const i18nBase = require("../client/assets/translations/i18n"); - -function getI18n() { - try { - const { i18n } = require("../views/main_views"); - return i18n; - } catch (_) { - return i18nBase['en'] || {}; - } -} - -function renderTextPreview(text, maxLength = 220) { - if (!text) return '' - - let preview = String(text) - - preview = preview - .replace(/```[\s\S]*?```/g, '') - .replace(/^>.*$/gm, '') - .replace(/^#{1,6}\s*/gm, '') - .replace(/^- /gm, '') - .replace(/^\d+\. /gm, '') - .replace(/\*\*(.*?)\*\*/g, '$1') - .replace(/\*(.*?)\*/g, '$1') - .replace(/`([^`]+)`/g, '$1') - .replace(/!\[.*?\]\(.*?\)/g, '') - .replace(/\[.*?\]\(.*?\)/g, '') - .replace(/\n+/g, ' ') - .trim() - - if (preview.length > maxLength) { - preview = preview.slice(0, maxLength) + '...' - } - - return preview -} - -function renderTextWithStyles(text) { - if (!text) return '' - const i18n = getI18n() - - let html = String(text) - - html = html - .replace(/&/g, '&') - .replace(//g, '>') - - html = html - .replace(/```([\s\S]*?)```/gim, '
$1
') - .replace(/^> (.*)$/gim, '
$1
') - .replace(/^---$/gim, '
') - .replace(/^### (.*)$/gim, '

$1

') - .replace(/^## (.*)$/gim, '

$1

') - .replace(/^# (.*)$/gim, '

$1

') - - html = html - .replace(/\*\*(.*?)\*\*/gim, '$1') - .replace(/\*(.*?)\*/gim, '$1') - .replace(/`([^`]+)`/gim, '$1') - - html = html - .replace(/!\[([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, (_, alt, blob) => - `${alt}` - ) - .replace(/\[video:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, (_, _name, blob) => - `` - ) - .replace(/\[audio:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, (_, _name, blob) => - `` - ) - .replace(/\[pdf:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, (_, name, blob) => - `
${name || i18n.pdfFallbackLabel || 'PDF'}` - ) - - html = html - .replace(/\[@([^\]]+)\]\(@?([A-Za-z0-9+/=.\-]+\.ed25519)\)/g, (_, name, id) => - `@${name}` - ) - .replace(/@([A-Za-z0-9+/=.\-]+\.ed25519)/g, (_, id) => - `@${id}` - ) - - const escAttr = (s) => String(s).replace(/"/g, '"').replace(/'/g, ''') - html = html - .replace(/#(\w+)/g, (_, tag) => - `#${escAttr(tag)}` - ) - .replace(/(https?:\/\/[^\s"'<>]+)/g, url => - `${escAttr(url)}` - ) - .replace(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, email => - `${escAttr(email)}` - ) - - const lines = html.split('\n') - let result = '' - let inUL = false - let inOL = false - - for (let line of lines) { - if (/^- /.test(line)) { - if (!inUL) { - result += '
    ' - inUL = true - } - result += `
  • ${line.replace(/^- /, '')}
  • ` - continue - } - - if (/^\d+\. /.test(line)) { - if (!inOL) { - result += '
      ' - inOL = true - } - result += `
    1. ${line.replace(/^\d+\. /, '')}
    2. ` - continue - } - - if (inUL) { - result += '
' - inUL = false - } - - if (inOL) { - result += '' - inOL = false - } - - result += line + '
' - } - - if (inUL) result += '' - if (inOL) result += '' - - return result -} - -module.exports = { renderTextWithStyles, renderTextPreview } diff --git a/src/backend/renderUrl.js b/src/backend/renderUrl.js deleted file mode 100644 index 9267768..0000000 --- a/src/backend/renderUrl.js +++ /dev/null @@ -1,92 +0,0 @@ -const { a, img, video, audio } = require("../server/node_modules/hyperaxe"); -const i18nBase = require("../client/assets/translations/i18n"); - -function getI18n() { - try { - const { i18n } = require("../views/main_views"); - return i18n; - } catch (_) { - return i18nBase['en'] || {}; - } -} - -function renderUrl(text) { - if (typeof text !== 'string') return [text]; - const blobImageRegex = /!\[([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g; - const blobVideoRegex = /\[video:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g; - const blobAudioRegex = /\[audio:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g; - const blobPdfRegex = /\[pdf:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g; - const mdMentionRegex = /\[@([^\]]+)\]\(@?([A-Za-z0-9+/=.\-]+\.ed25519)\)/g; - const rawMentionRegex = /@([A-Za-z0-9+/=.\-]+\.ed25519)/g; - const urlRegex = /\b(?:https?:\/\/|www\.)[^\s]+/g; - const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b/gi; - const allMatches = []; - for (const m of text.matchAll(blobImageRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'blob-image', name: m[1], blob: m[2] }); - } - for (const m of text.matchAll(blobVideoRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'blob-video', name: m[1], blob: m[2] }); - } - for (const m of text.matchAll(blobAudioRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'blob-audio', name: m[1], blob: m[2] }); - } - for (const m of text.matchAll(blobPdfRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'blob-pdf', name: m[1], blob: m[2] }); - } - for (const m of text.matchAll(mdMentionRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'md-mention', name: m[1], feedId: m[2] }); - } - for (const m of text.matchAll(rawMentionRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'raw-mention', feedId: m[1] }); - } - for (const m of text.matchAll(urlRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'url', text: m[0] }); - } - for (const m of text.matchAll(emailRegex)) { - allMatches.push({ index: m.index, length: m[0].length, type: 'email', text: m[0] }); - } - allMatches.sort((a, b) => a.index - b.index); - const filtered = []; - let lastEnd = 0; - for (const m of allMatches) { - if (m.index < lastEnd) continue; - filtered.push(m); - lastEnd = m.index + m.length; - } - const result = []; - let cursor = 0; - for (const m of filtered) { - if (cursor < m.index) { - result.push(text.slice(cursor, m.index)); - } - if (m.type === 'blob-image') { - result.push(img({ src: `/blob/${encodeURIComponent(m.blob)}`, alt: m.name || '', class: 'post-image' })); - } else if (m.type === 'blob-video') { - result.push(video({ controls: true, class: 'post-video', src: `/blob/${encodeURIComponent(m.blob)}` })); - } else if (m.type === 'blob-audio') { - result.push(audio({ controls: true, class: 'post-audio', src: `/blob/${encodeURIComponent(m.blob)}` })); - } else if (m.type === 'blob-pdf') { - const i18n = getI18n(); - const label = m.name || i18n.pdfFallbackLabel || 'PDF'; - result.push(a({ href: `/blob/${encodeURIComponent(m.blob)}`, class: 'post-pdf', target: '_blank' }, label)); - } else if (m.type === 'md-mention') { - const feedWithAt = '@' + m.feedId; - result.push(a({ href: `/author/${encodeURIComponent(feedWithAt)}`, class: 'mention' }, '@' + m.name)); - } else if (m.type === 'raw-mention') { - const feedWithAt = '@' + m.feedId; - result.push(a({ href: `/author/${encodeURIComponent(feedWithAt)}`, class: 'mention' }, '@' + m.feedId.slice(0, 8) + '...')); - } else if (m.type === 'url') { - const href = m.text.startsWith('http') ? m.text : `https://${m.text}`; - result.push(a({ href, target: '_blank', rel: 'noopener noreferrer' }, m.text)); - } else if (m.type === 'email') { - result.push(a({ href: `mailto:${m.text}` }, m.text)); - } - cursor = m.index + m.length; - } - if (cursor < text.length) { - result.push(text.slice(cursor)); - } - return result; -} - -module.exports = { renderUrl }; diff --git a/src/backend/request_guards.js b/src/backend/request_guards.js new file mode 100644 index 0000000..bc62e3e --- /dev/null +++ b/src/backend/request_guards.js @@ -0,0 +1,99 @@ +const MSG_KEY_RE = /^%[A-Za-z0-9+/]{43}=\.sha256$/; + +const isMsgKey = (value) => typeof value === 'string' && MSG_KEY_RE.test(value); + +const pickMsgKeys = (value) => { + const list = Array.isArray(value) ? value : (value == null ? [] : [value]); + return Array.from(new Set(list.filter(isMsgKey))); +}; + +const isLocalPath = (value) => { + if (typeof value !== 'string' || !value.startsWith('/')) return false; + if (value.startsWith('//') || value.startsWith('/\\')) return false; + if (/[\r\n\0]/.test(value)) return false; + return true; +}; + +const SHARED_RETURN_PAGES = ['/activity', '/agenda', '/search', '/trending', '/favorites', '/opinions', '/mentions', '/melody', '/author', '/inhabitant', '/tribe']; +const isSharedReturnPage = (rt) => SHARED_RETURN_PAGES.some(p => rt === p || (rt.startsWith(p) && /^[?#/]/.test(rt.slice(p.length)))); +const safeReturnTo = (ctx, fallback, allowedPrefixes) => { + const rt = ctx?.request?.body?.returnTo || ctx?.query?.returnTo; + if (!isLocalPath(rt)) return fallback; + if (isSharedReturnPage(rt)) return rt; + return Array.isArray(allowedPrefixes) && allowedPrefixes.some(p => rt.startsWith(p)) ? rt : fallback; +}; + +const refererPath = (referer, host) => { + if (!referer) return null; + try { + const u = new URL(referer); + if ((u.protocol !== 'http:' && u.protocol !== 'https:') || u.host !== host) return null; + const target = u.pathname + u.search + u.hash; + return isLocalPath(target) ? target : null; + } catch (_) { + return null; + } +}; + +const safeRefererRedirect = (ctx, fallback = '/') => { + const target = refererPath(ctx.request.header.referer, ctx.host); + ctx.redirect(target || fallback); +}; + +const isClearnetPath = (request) => { + const url = String((request && request.url) || ''); + return url === '/c' || url.startsWith('/c/') || url.startsWith('/c?'); +}; + +const isTrustedRequest = (request, validHosts) => { + if (isClearnetPath(request)) return request.method === 'GET'; + if (!Array.isArray(validHosts) || !validHosts.includes(request.hostname)) return false; + if (request.method === 'GET') return true; + const origin = request.header && request.header.origin; + const source = origin && origin !== 'null' ? origin : (request.header && request.header.referer); + if (source == null) return false; + try { + const u = new URL(source); + if ((u.protocol !== 'http:' && u.protocol !== 'https:') || !validHosts.includes(u.hostname)) return false; + if (u.host !== String(request.host || '')) return false; + if (u.pathname.startsWith('/blob/')) return false; + } catch (_) { + return false; + } + return true; +}; + +const publicModeGuard = ({ isPublic, onBlocked }) => async (ctx, next) => { + if (isPublic && ctx.method !== 'GET') { onBlocked(ctx); return; } + await next(); +}; + +const buildCsp = (isClearnet, { frames = false } = {}) => (isClearnet + ? [ + "default-src 'self'", + "script-src 'none'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data:", + "media-src 'self' blob:", + "connect-src 'self'", + "frame-src 'none'", + "form-action 'self'", + "object-src 'none'", + "base-uri 'none'", + "frame-ancestors 'none'" + ] + : [ + "default-src 'self'", + "script-src 'self' http://localhost:3000/js", + "style-src 'self'", + "img-src 'self'", + "media-src 'self' blob:", + "worker-src 'self' blob:", + frames ? "frame-src 'self'" : "frame-src 'none'", + "form-action 'self'", + "object-src 'none'", + "base-uri 'none'", + "frame-ancestors 'none'" + ]).join('; '); + +module.exports = { isMsgKey, pickMsgKeys, isLocalPath, safeReturnTo, refererPath, safeRefererRedirect, isClearnetPath, isTrustedRequest, publicModeGuard, buildCsp }; diff --git a/src/backend/sanitizeHtml.js b/src/backend/sanitizeHtml.js index 85bb4b4..bb8f6ed 100644 --- a/src/backend/sanitizeHtml.js +++ b/src/backend/sanitizeHtml.js @@ -1,14 +1,32 @@ "use strict"; -const { JSDOM } = require('../server/node_modules/jsdom'); -const DOMPurify = require('../server/node_modules/dompurify'); -const window = new JSDOM('').window; -const purify = DOMPurify(window); +const SAFE_SRC = /^\/(?:blob|image)\/[^\s"'<>]+$/i; +const SAFE_HREF = /^(?:https?:\/\/|\/(?!\/)|#|mailto:)/i; +const SAFE_CLASS = /^(?:rt-[a-z0-9-]+|styled-link|mention|tag-link|zoom-link|pm-quote|wiki-link(?:-missing)?|internal-link(?:-id|-kind)?|lightbox(?:-[a-z]+)?|post-(?:audio|image|pdf|video))$/; +let purifier = null; +const getPurify = () => { + if (purifier) return purifier; + const { JSDOM } = require('../server/node_modules/jsdom'); + const DOMPurify = require('../server/node_modules/dompurify'); + const purify = DOMPurify(new JSDOM('').window); + purify.addHook('uponSanitizeAttribute', (node, data) => { + const name = String(data.attrName || '').toLowerCase(); + const value = String(data.attrValue || '').trim(); + if (name === 'src' && !SAFE_SRC.test(value)) data.keepAttr = false; + if (name === 'href' && !SAFE_HREF.test(value)) data.keepAttr = false; + if (name === 'class') { + const kept = value.split(/\s+/).filter(c => SAFE_CLASS.test(c)); + if (kept.length) data.attrValue = kept.join(' '); else data.keepAttr = false; + } + }); + purifier = purify; + return purifier; +}; +const dropControlChars = (html) => String(html).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, ''); const stripDangerousTags = (input) => { if (typeof input !== 'string') return ''; - return purify.sanitize(input, { - USE_PROFILES: { html: true }, + return dropControlChars(getPurify().sanitize(input, { ALLOWED_TAGS: [ 'p', 'br', 'b', 'strong', 'i', 'em', 'u', @@ -19,13 +37,12 @@ const stripDangerousTags = (input) => { ALLOWED_ATTR: [], FORBID_TAGS: ['svg', 'math', 'iframe', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button', 'script'], FORBID_ATTR: ['style'] - }); + })); }; const sanitizeHtml = (input) => { if (typeof input !== 'string') return ''; - return purify.sanitize(input, { - USE_PROFILES: { html: true }, + return dropControlChars(getPurify().sanitize(input, { ALLOWED_TAGS: [ 'p', 'br', 'hr', 'b', 'strong', 'i', 'em', 'u', 's', 'del', @@ -39,7 +56,7 @@ const sanitizeHtml = (input) => { ALLOWED_ATTR: ['href', 'class', 'target', 'rel', 'src', 'alt', 'title', 'controls'], FORBID_TAGS: ['svg', 'math', 'iframe', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button', 'script', 'style', 'link', 'meta'], FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onsubmit', 'onchange', 'style'] - }); + })); }; module.exports = { stripDangerousTags, sanitizeHtml }; diff --git a/src/backend/smartContractPdf.js b/src/backend/smartContractPdf.js deleted file mode 100644 index e396747..0000000 --- a/src/backend/smartContractPdf.js +++ /dev/null @@ -1,228 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const LOGO_PATH = path.join(__dirname, '..', 'client', 'assets', 'images', 'snh-oasis.jpg'); - -const { escapePdf } = require('./pdfDocument'); - -const wrap = (txt, max = 82) => { - const out = []; - for (const raw of String(txt || '').split('\n')) { - let line = raw; - while (line.length > max) { - let cut = line.lastIndexOf(' ', max); - if (cut <= 0) cut = max; - out.push(line.slice(0, cut)); - line = line.slice(cut).replace(/^\s+/, ''); - } - out.push(line); - } - return out; -}; - -const readJpegDims = (buf) => { - let i = 2; - while (i < buf.length) { - if (buf[i] !== 0xFF) return null; - const marker = buf[i + 1]; - if (marker === 0xD8 || marker === 0xD9) { i += 2; continue; } - const len = buf.readUInt16BE(i + 2); - if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) { - const h = buf.readUInt16BE(i + 5); - const w = buf.readUInt16BE(i + 7); - const c = buf[i + 9]; - return { w, h, c }; - } - i += 2 + len; - } - return null; -}; - -function buildSmartContractPdf({ transfer, block, viewerId }) { - const pageW = 612; - const pageH = 792; - const marginX = 50; - const headerH = 90; - const footerH = 40; - const bodyTop = pageH - headerH - 24; - const bodyBottom = footerH + 10; - const lineH = 14; - - let logoBuf = null; - let logoDims = null; - try { - logoBuf = fs.readFileSync(LOGO_PATH); - logoDims = readJpegDims(logoBuf); - } catch (_) {} - - const t = transfer || {}; - const b = block || {}; - const fmt = v => (v === undefined || v === null) ? '' : String(v); - const fmtAmount = () => { - const cat = String(t.category || 'ECONOMIC').toUpperCase(); - const unit = cat === 'TIME' ? 'h' : cat === 'TRUST' ? 'trust' : 'ECO'; - return `${Number(t.amount || 0).toFixed(6)} ${unit}`; - }; - const fmtDate = v => v ? new Date(v).toISOString().replace('T', ' ').slice(0, 19) + ' UTC' : ''; - const confirmedBy = Array.isArray(t.confirmedBy) ? t.confirmedBy : []; - const required = t.from === t.to ? 1 : 2; - const confirmedCount = confirmedBy.length; - const tags = Array.isArray(t.tags) ? t.tags.join(', ') : ''; - - const sections = []; - sections.push({ kind: 'title', text: `Concept: ${t.concept || '-'}` }); - sections.push({ kind: 'blank' }); - - sections.push({ kind: 'section', text: 'OASIS IDs' }); - sections.push({ kind: 'kv', label: 'From', value: fmt(t.from) }); - sections.push({ kind: 'kv', label: 'To', value: fmt(t.to) }); - sections.push({ kind: 'blank' }); - - sections.push({ kind: 'section', text: 'TERMS' }); - sections.push({ kind: 'kv', label: 'Category', value: String(t.category || 'ECONOMIC').toUpperCase() }); - if (String(t.category || '').toUpperCase() !== 'TRUST') sections.push({ kind: 'kv', label: 'Amount', value: fmtAmount() }); - sections.push({ kind: 'kv', label: 'Status', value: fmt(t.status) }); - if (t.deadline) sections.push({ kind: 'kv', label: 'Deadline', value: fmtDate(t.deadline) }); - if (tags) sections.push({ kind: 'kv', label: 'Tags', value: tags }); - sections.push({ kind: 'blank' }); - - sections.push({ kind: 'section', text: 'CONFIRMATIONS' }); - sections.push({ kind: 'kv', label: 'Required', value: String(required) }); - sections.push({ kind: 'kv', label: 'Confirmed', value: String(confirmedCount) }); - if (confirmedBy.length) { - for (const f of confirmedBy) sections.push({ kind: 'kv', label: 'Signed by', value: fmt(f) }); - } - sections.push({ kind: 'blank' }); - - sections.push({ kind: 'section', text: 'BLOCK VERIFICATION' }); - if (b && b.id) { - sections.push({ kind: 'kv', label: 'Block ID', value: fmt(b.id) }); - if (b.ts) sections.push({ kind: 'kv', label: 'Block Timestamp', value: fmtDate(b.ts) }); - if (b.type) sections.push({ kind: 'kv', label: 'Block Type', value: String(b.type).toUpperCase() }); - if (b.author) sections.push({ kind: 'kv', label: 'Block Author', value: fmt(b.author) }); - if (b.size) sections.push({ kind: 'kv', label: 'Block Size', value: `${b.size} bytes` }); - } else { - sections.push({ kind: 'kv', label: 'Block ID', value: fmt(t.id) }); - } - sections.push({ kind: 'blank' }); - - sections.push({ kind: 'section', text: 'METADATA' }); - sections.push({ kind: 'kv', label: 'Transfer ID', value: fmt(t.id) }); - if (t.createdAt) sections.push({ kind: 'kv', label: 'Created At', value: fmtDate(t.createdAt) }); - if (t.updatedAt) sections.push({ kind: 'kv', label: 'Updated At', value: fmtDate(t.updatedAt) }); - - const lines = []; - for (const s of sections) { - if (s.kind === 'kv') { - const txt = `${s.label}: ${s.value}`; - for (const w of wrap(txt, 82)) lines.push({ kind: 'kv', text: w }); - } else { - lines.push(s); - } - } - - const maxBodyLines = Math.floor((bodyTop - bodyBottom) / lineH); - const pages = []; - for (let i = 0; i < lines.length; i += maxBodyLines) pages.push(lines.slice(i, i + maxBodyLines)); - if (!pages.length) pages.push([]); - - const objects = []; - const addObj = body => { objects.push(body); return objects.length; }; - - const catalogId = addObj(null); - const pagesId = addObj(null); - const fontId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>'); - const fontBoldId = addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>'); - let logoXObjId = null; - if (logoBuf && logoDims) { - const cs = logoDims.c === 1 ? '/DeviceGray' : '/DeviceRGB'; - const dict = `<< /Type /XObject /Subtype /Image /Width ${logoDims.w} /Height ${logoDims.h} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode /Length ${logoBuf.length} >>`; - logoXObjId = addObj({ dict, stream: logoBuf }); - } - - const exportDate = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; - const footerLeft = `Generated: ${exportDate}`; - - const pageIds = []; - const contentIds = []; - - pages.forEach((pg, pgIdx) => { - const parts = []; - if (logoXObjId) { - const logoH = 60; - const logoW = Math.round((logoDims.w / logoDims.h) * logoH); - parts.push(`q\n${logoW} 0 0 ${logoH} ${marginX} ${pageH - headerH + 15} cm\n/Logo Do\nQ`); - } - const titleX = (logoXObjId ? marginX + 80 : marginX); - const titleY = pageH - 45; - parts.push(`BT\n/F2 16 Tf\n${titleX} ${titleY} Td\n(${escapePdf('OASIS - Smart Contract')}) Tj\nET`); - const prefix = 'Issued to: '; - const prefixW = prefix.length * 5.4; - parts.push(`BT\n/F1 9 Tf\n${titleX} ${titleY - 16} Td\n(${escapePdf(prefix)}) Tj\nET`); - parts.push(`BT\n/F2 9 Tf\n${titleX + prefixW} ${titleY - 16} Td\n(${escapePdf(String(viewerId || ''))}) Tj\nET`); - - parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${pageH - headerH} m\n${pageW - marginX} ${pageH - headerH} l\nS\nQ`); - - let y = bodyTop; - for (const ln of pg) { - if (ln.kind === 'title') { - parts.push(`BT\n/F2 14 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } else if (ln.kind === 'subtitle') { - parts.push(`BT\n/F1 11 Tf\n0.2 0.2 0.2 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } else if (ln.kind === 'section') { - parts.push(`BT\n/F2 11 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - parts.push(`q\n0 0 0 RG\n0.5 w\n${marginX} ${y - 3} m\n${pageW - marginX} ${y - 3} l\nS\nQ`); - } else if (ln.kind === 'kv') { - parts.push(`BT\n/F1 10 Tf\n0 0 0 rg\n${marginX} ${y} Td\n(${escapePdf(ln.text)}) Tj\nET`); - } - y -= lineH; - } - - parts.push(`q\n0.6 0.6 0.6 RG\n0.5 w\n${marginX} ${footerH + 5} m\n${pageW - marginX} ${footerH + 5} l\nS\nQ`); - parts.push(`BT\n/F1 8 Tf\n${marginX} ${footerH - 10} Td\n(${escapePdf(footerLeft)}) Tj\nET`); - const pageLabel = `Page ${pgIdx + 1} of ${pages.length}`; - const pageLabelW = pageLabel.length * 4.8; - parts.push(`BT\n/F1 8 Tf\n${pageW - marginX - pageLabelW} ${footerH - 10} Td\n(${escapePdf(pageLabel)}) Tj\nET`); - - const content = parts.join('\n'); - const stream = `<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`; - const cid = addObj(stream); - contentIds.push(cid); - const pid = addObj(null); - pageIds.push(pid); - }); - - for (let i = 0; i < pageIds.length; i++) { - const resources = logoXObjId - ? `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> /XObject << /Logo ${logoXObjId} 0 R >> >>` - : `<< /Font << /F1 ${fontId} 0 R /F2 ${fontBoldId} 0 R >> >>`; - objects[pageIds[i] - 1] = `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${pageW} ${pageH}] /Contents ${contentIds[i]} 0 R /Resources ${resources} >>`; - } - objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; - objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; - - const chunks = []; - const offsets = [0]; - let byteLen = 0; - const push = (buf) => { chunks.push(buf); byteLen += buf.length; }; - push(Buffer.from('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n', 'binary')); - for (let i = 0; i < objects.length; i++) { - offsets.push(byteLen); - const obj = objects[i]; - if (obj && typeof obj === 'object' && obj.dict && obj.stream) { - push(Buffer.from(`${i + 1} 0 obj\n${obj.dict}\nstream\n`, 'binary')); - push(obj.stream); - push(Buffer.from('\nendstream\nendobj\n', 'binary')); - } else { - push(Buffer.from(`${i + 1} 0 obj\n${obj}\nendobj\n`, 'binary')); - } - } - const xrefStart = byteLen; - let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; - for (let i = 1; i <= objects.length; i++) xref += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`; - xref += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefStart}\n%%EOF`; - push(Buffer.from(xref, 'binary')); - return Buffer.concat(chunks); -} - -module.exports = { buildSmartContractPdf }; diff --git a/src/backend/updater.js b/src/backend/updater.js index bb2d660..2f6f3b3 100644 --- a/src/backend/updater.js +++ b/src/backend/updater.js @@ -1,12 +1,50 @@ -const fetch = require('../server/node_modules/node-fetch'); const { existsSync, readFileSync, writeFileSync, unlinkSync } = require('fs'); const { join } = require('path'); const localpackage = join(__dirname, '../server/package.json'); -const remoteUrl = 'https://code.03c8.net/KrakensLab/oasis/raw/master/src/server/package.json'; // Official SNH-Oasis -const remoteUrl2 = 'https://raw.githubusercontent.com/epsylon/oasis/refs/heads/main/src/server/package.json'; // Mirror SNH-Oasis +const remoteUrl = 'https://code.03c8.net/KrakensLab/oasis/raw/master/src/server/package.json'; +const remoteUrl2 = 'https://raw.githubusercontent.com/epsylon/oasis/refs/heads/main/src/server/package.json'; let printed = false; +const repoRoot = join(__dirname, '..', '..'); +const updateFlagFile = join(__dirname, '../server/.update_required'); + +const git = (args, cwd = repoRoot) => new Promise((resolve) => { + require('child_process').execFile('git', args, { cwd, timeout: 60000, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } }, (err, out) => resolve(err ? '' : String(out || '').trim())); +}); +const commitOf = (line) => { + const [hash, date, ...rest] = String(line || '').split('\t'); + return hash ? { hash, date: date || '', subject: rest.join('\t') } : null; +}; +const cleanOrigin = (url) => String(url || '').replace(/\/\/[^@\/]*@/, '//').replace(/\.git$/, ''); + +async function collectDetails(root = repoRoot) { + if (!existsSync(join(root, '.git'))) return null; + const installed = commitOf(await git(['log', '-1', '--format=%h%x09%cI%x09%s'], root)); + await git(['fetch', '--quiet'], root); + const upstream = (await git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], root)) || 'origin/master'; + const available = commitOf(await git(['log', '-1', '--format=%h%x09%cI%x09%s', upstream], root)); + const commits = (await git(['log', '--format=%h%x09%cI%x09%s', '-n', '10', `HEAD..${upstream}`], root)).split('\n').map(commitOf).filter(Boolean); + const count = Number(await git(['rev-list', '--count', `HEAD..${upstream}`], root)) || commits.length; + const stat = await git(['diff', '--shortstat', 'HEAD', upstream], root); + const num = (re) => { const m = stat.match(re); return m ? Number(m[1]) : 0; }; + return { + installed, available, commits, count, + files: num(/(\d+) files? changed/), insertions: num(/(\d+) insertions?/), deletions: num(/(\d+) deletions?/), + origin: cleanOrigin(await git(['remote', 'get-url', 'origin'], root)) + }; +} +exports.collectDetails = collectDetails; + +exports.readUpdateInfo = () => { + if (!existsSync(updateFlagFile)) return null; + try { + const info = JSON.parse(readFileSync(updateFlagFile, 'utf8')); + return info && info.required ? info : { required: true }; + } catch (_) { + return { required: true }; + } +}; async function extractVersionFromText(text) { try { @@ -33,11 +71,22 @@ async function diffVersion(body, callback) { const updateFlagPath = join(__dirname, "../server/.update_required"); if (remoteVersion !== localVersion) { - writeFileSync(updateFlagPath, JSON.stringify({ required: true })); + const alreadyKnown = existsSync(updateFlagPath); + writeFileSync(updateFlagPath, JSON.stringify({ required: true, version: remoteVersion, installedVersion: localVersion })); + collectDetails().then((details) => { + if (details && existsSync(updateFlagPath)) writeFileSync(updateFlagPath, JSON.stringify({ required: true, version: remoteVersion, installedVersion: localVersion, details })); + }).catch(() => {}); + if (!alreadyKnown) { + try { + const { notify, i18nNow } = require('./desktopNotify'); + const i18n = i18nNow(); + notify(i18n.notifyUpdateLabel, `${i18n.updateBannerText} (${remoteVersion})`); + } catch (_) {} + } callback("required"); } else { if (existsSync(updateFlagPath)) unlinkSync(updateFlagPath); - callback(""); // no updates required + callback(""); } } catch (error) { console.error("Error comparing versions:", error.message); @@ -49,15 +98,8 @@ async function checkMirror(callback) { try { const response = await fetch(remoteUrl2, { method: 'GET', - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'application/json, text/plain, */*', - 'Accept-Language': 'en-US,en;q=0.9', - 'Accept-Encoding': 'gzip, deflate, br', - 'Connection': 'keep-alive', - 'Referer': 'https://raw.githubusercontent.com', - 'Origin': 'https://raw.githubusercontent.com' - } + headers: { 'Accept': 'application/json, text/plain, */*' }, + signal: AbortSignal.timeout(20000) }); if (!response.ok) { @@ -72,20 +114,154 @@ async function checkMirror(callback) { } } +const networkAllowed = () => { + if (process.argv.includes('--offline') || process.env.OASIS_NETWORK_PAUSED === '1') return false; + try { if (require('../server/ssb_config').offline === true) return false; } catch (_) {} + try { if (require('../configs/config-manager.js').getConfig().networkPaused === true) return false; } catch (_) {} + return true; +}; + +const MOBILE_RELEASE_SOURCES = [ + { url: 'https://code.03c8.net/KrakensLab/oasis/releases', type: 'html' }, + { url: 'https://api.github.com/repos/epsylon/oasis/releases?per_page=20', type: 'json' } +]; +const MOBILE_APK_RE = /^oasis-v(\d+\.\d+\.\d+)\.apk$/i; +const MOBILE_APK_HTML_RE = /oasis-v(\d+\.\d+\.\d+)\.apk/i; +const ECOIN_RELEASES_API = 'https://api.github.com/repos/epsylon/ecoin/releases?per_page=20'; +const ECOIN_RELEASES_PAGE = 'https://github.com/epsylon/ecoin/releases'; +const ECOIN_APK_RE = /^ecoin-wallet-v(\d+(?:\.\d+)*)\.apk$/i; +const MOBILE_FETCH_HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + 'Accept-Language': 'en-US,en;q=0.9' +}; + +let ecoinWalletApk = ''; + +const versionParts = (v) => String(v || '').split('.').map(n => parseInt(n, 10) || 0); + +const isNewerVersion = (remote, local) => { + const a = versionParts(remote), b = versionParts(local); + const len = Math.max(a.length, b.length, 3); + for (let i = 0; i < len; i++) { + if ((a[i] || 0) !== (b[i] || 0)) return (a[i] || 0) > (b[i] || 0); + } + return false; +}; + +const localMobileVersion = () => { + const fromEnv = String(process.env.OASIS_ANDROID_VERSION || '').split('-')[0].trim(); + if (fromEnv) return fromEnv; + try { + return JSON.parse(readFileSync(localpackage, 'utf8')).version || ''; + } catch (error) { + return ''; + } +}; + +const pickReleaseFromJson = (releases) => { + for (const rel of Array.isArray(releases) ? releases : []) { + if (!rel || rel.draft) continue; + for (const asset of Array.isArray(rel.assets) ? rel.assets : []) { + const match = MOBILE_APK_RE.exec(String((asset && asset.name) || '')); + if (match) { + return { + version: match[1], + page: String(rel.html_url || ''), + apk: String(asset.browser_download_url || '') + }; + } + } + } + return null; +}; + +const pickReleaseFromHtml = (html, pageUrl) => { + const match = MOBILE_APK_HTML_RE.exec(String(html || '')); + return match ? { version: match[1], page: pageUrl, apk: '' } : null; +}; + +const fetchMobileRelease = async (source) => { + const isHtml = source.type === 'html'; + const response = await fetch(source.url, { + method: 'GET', + headers: { ...MOBILE_FETCH_HEADERS, 'Accept': isHtml ? 'text/html,application/xhtml+xml' : 'application/json, text/plain, */*' } + }); + if (!response.ok) throw new Error(`Request failed with status ${response.status}`); + return isHtml + ? pickReleaseFromHtml(await response.text(), source.url) + : pickReleaseFromJson(await response.json()); +}; + +const refreshEcoinWalletApk = async () => { + try { + const response = await fetch(ECOIN_RELEASES_API, { + method: 'GET', + headers: { ...MOBILE_FETCH_HEADERS, 'Accept': 'application/json, text/plain, */*' } + }); + if (!response.ok) return; + const releases = await response.json(); + let best = null; + for (const rel of Array.isArray(releases) ? releases : []) { + if (!rel || rel.draft) continue; + for (const asset of Array.isArray(rel.assets) ? rel.assets : []) { + const match = ECOIN_APK_RE.exec(String((asset && asset.name) || '')); + if (!match || !asset.browser_download_url) continue; + if (!best || isNewerVersion(match[1], best.version)) best = { version: match[1], apk: String(asset.browser_download_url) }; + } + } + if (best) ecoinWalletApk = best.apk; + } catch (error) {} +}; + +exports.ecoinWalletDownloadUrl = () => { + if (String(process.env.OASIS_ANDROID_INSTALLER || '') === 'com.android.vending') return ECOIN_RELEASES_PAGE; + if (!ecoinWalletApk) refreshEcoinWalletApk(); + return ecoinWalletApk || ECOIN_RELEASES_PAGE; +}; + +exports.getMobileRemoteVersion = async () => { + await refreshEcoinWalletApk(); + if (String(process.env.OASIS_ANDROID_INSTALLER || '') === 'com.android.vending') return; + const localVersion = localMobileVersion(); + if (!localVersion) return; + const updateFlagPath = join(__dirname, '../server/.update_required'); + const found = []; + for (const source of MOBILE_RELEASE_SOURCES) { + try { + const candidate = await fetchMobileRelease(source); + if (candidate) found.push(candidate); + } catch (error) {} + } + if (!found.length) return; + const top = found.reduce((best, r) => (isNewerVersion(r.version, best.version) ? r : best), found[0]); + const sameVersion = found.filter(r => r.version === top.version); + const withApk = sameVersion.find(r => r.apk); + const release = { version: top.version, page: sameVersion[0].page, apk: withApk ? withApk.apk : '' }; + if (isNewerVersion(release.version, localVersion)) { + try { + writeFileSync(updateFlagPath, JSON.stringify({ + required: true, + version: release.version, + installedVersion: localVersion, + page: release.page || release.apk, + apk: release.apk + })); + } catch (error) {} + console.log(`\noasis@version: Oasis ${release.version} is available at ${release.apk || release.page}\n`); + } else if (existsSync(updateFlagPath)) { + try { unlinkSync(updateFlagPath); } catch (error) {} + } +}; + exports.getRemoteVersion = async () => { - if (existsSync('../../.git')) { + if (process.env.OASIS_MOBILE === '1') return exports.getMobileRemoteVersion(); + if (!networkAllowed()) return; + if (existsSync(join(__dirname, '..', '..', '.git'))) { try { const response = await fetch(remoteUrl, { method: 'GET', - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'application/json, text/plain, */*', - 'Accept-Language': 'en-US,en;q=0.9', - 'Accept-Encoding': 'gzip, deflate, br', - 'Connection': 'keep-alive', - 'Referer': 'https://code.03c8.net', - 'Origin': 'https://code.03c8.net' - } + headers: { 'Accept': 'application/json, text/plain, */*' }, + signal: AbortSignal.timeout(20000) }); if (!response.ok) { diff --git a/src/backend/vote_tally.js b/src/backend/vote_tally.js index 54f02af..d8e4599 100644 --- a/src/backend/vote_tally.js +++ b/src/backend/vote_tally.js @@ -1,116 +1,119 @@ -const buildVoteTally = (messages) => { - const nodes = new Map(); - const ballots = []; +const INSTITUTIONAL_VOTE_TAG = /^(gov|govMethod|courtsCase|courtsMethod):/; - for (const m of messages) { +const msOf = (v) => { + const t = Date.parse(v); + return Number.isFinite(t) ? t : null; +}; + +const isLater = (a, b) => a.ts > b.ts || (a.ts === b.ts && a.seq > b.seq); + +const legacyChoice = (kid, parent, options) => { + const kVoters = Array.isArray(kid.content.voters) ? kid.content.voters : []; + const pVoters = Array.isArray(parent.content.voters) ? parent.content.voters : []; + if (!kVoters.includes(kid.author) || pVoters.includes(kid.author)) return null; + const kVotes = kid.content.votes || {}; + const pVotes = parent.content.votes || {}; + let choice = null; + for (const o of options) { + const d = (Number(kVotes[o]) || 0) - (Number(pVotes[o]) || 0); + if (d === 0) continue; + if (d !== 1 || choice !== null) return null; + choice = o; + } + return choice; +}; + +const buildVoteResults = (messages) => { + const nodes = new Map(); + const ballotsByTarget = new Map(); + + for (const m of messages || []) { const v = m && m.value; const c = v && v.content; if (!c) continue; - if (c.type === 'votesVote') { if (c.target) ballots.push({ target: c.target, author: v.author, choice: c.choice }); continue; } + const ts = Number(v.timestamp || m.timestamp || 0) || 0; + const seq = Number(v.sequence || 0) || 0; + if (c.type === 'votesVote') { + if (typeof c.target !== 'string' || !v.author) continue; + if (!ballotsByTarget.has(c.target)) ballotsByTarget.set(c.target, []); + ballotsByTarget.get(c.target).push({ author: v.author, choice: c.choice, ts, seq }); + continue; + } if (c.type !== 'votes') continue; - nodes.set(m.key, { key: m.key, author: v.author, content: c, ts: v.timestamp || m.timestamp || 0 }); + nodes.set(m.key, { key: m.key, author: v.author, content: c, ts, seq }); } const prev = new Map(); for (const [k, n] of nodes) { const t = n.content.replaces; - if (typeof t === 'string' && nodes.has(t)) prev.set(k, t); - } - const childrenOf = new Map(); - for (const [k, p] of prev) { - if (!childrenOf.has(p)) childrenOf.set(p, []); - childrenOf.get(p).push(k); + if (typeof t === 'string' && t !== k && nodes.has(t)) prev.set(k, t); } const rootOf = (k) => { let x = k, g = 0; while (prev.has(x) && g++ < 100000) x = prev.get(x); return x; }; const groups = new Map(); for (const k of nodes.keys()) { const r = rootOf(k); if (!groups.has(r)) groups.set(r, []); groups.get(r).push(k); } - const tallyByKey = new Map(); + const results = new Map(); for (const [root, keys] of groups.entries()) { const rootNode = nodes.get(root); if (!rootNode) continue; - const options = Array.isArray(rootNode.content.options) ? rootNode.content.options : []; - let votesMap = Object.assign({}, rootNode.content.votes || {}); - let voters = Array.isArray(rootNode.content.voters) ? rootNode.content.voters.slice() : []; - for (const o of options) if (!(o in votesMap)) votesMap[o] = 0; - - let cur = root; - let g = 0; - let creatorChoice = null; - let creatorCounted = false; - while (g++ < 100000) { - const kids = (childrenOf.get(cur) || []).map(k => nodes.get(k)).filter(Boolean).sort((a, b) => a.ts - b.ts); - if (!kids.length) break; - let advanced = false; - for (const kid of kids) { - const kVotes = Object.assign({}, kid.content.votes || {}); - const kVoters = Array.isArray(kid.content.voters) ? kid.content.voters.slice() : []; - for (const o of options) if (!(o in kVotes)) kVotes[o] = 0; - if (kid.author === rootNode.author) { - if (!creatorCounted && kVoters.includes(rootNode.author) && !voters.includes(rootNode.author)) { - creatorCounted = true; - let inc = null; - let incCount = 0; - for (const o of options) { - const d = (Number(kVotes[o]) || 0) - (Number(votesMap[o]) || 0); - if (d >= 1) { inc = o; incCount++; } - } - if (incCount === 1) creatorChoice = inc; - } - votesMap = kVotes; - voters = kVoters; - cur = kid.key; - advanced = true; - break; - } - if (voters.includes(kid.author)) continue; - if (kVoters.length !== voters.length + 1) continue; - const added = kVoters.filter(x => !voters.includes(x)); - if (added.length !== 1 || added[0] !== kid.author) continue; - let deltas = 0; - let deltaOk = true; - for (const o of options) { - const d = (Number(kVotes[o]) || 0) - (Number(votesMap[o]) || 0); - if (d === 0) continue; - if (d === 1) { deltas++; continue; } - deltaOk = false; - break; - } - if (!deltaOk || deltas !== 1) continue; - votesMap = kVotes; - voters = kVoters; - cur = kid.key; - advanced = true; - break; + const creator = rootNode.author; + const tags = Array.isArray(rootNode.content.tags) ? rootNode.content.tags : []; + const frozen = tags.some(t => INSTITUTIONAL_VOTE_TAG.test(String(t))); + let current = rootNode; + if (!frozen) { + for (const k of keys) { + const n = nodes.get(k); + if (n.author === creator && isLater(n, current)) current = n; } - if (!advanced) break; + } + const options = Array.isArray(current.content.options) ? current.content.options.filter(o => typeof o === 'string') : []; + const deadline = typeof current.content.deadline === 'string' ? current.content.deadline : null; + const opensAt = rootNode.ts; + const closesAt = deadline ? msOf(deadline) : null; + + const cast = []; + for (const k of keys) { + for (const b of ballotsByTarget.get(k) || []) cast.push(b); + const n = nodes.get(k); + if (n.author === creator || !prev.has(k)) continue; + const choice = legacyChoice(n, nodes.get(prev.get(k)), options); + if (choice !== null) cast.push({ author: n.author, choice, ts: n.ts, seq: n.seq }); } - if (voters.includes(rootNode.author)) { - voters = voters.filter(x => x !== rootNode.author); - if (creatorChoice && (Number(votesMap[creatorChoice]) || 0) > 0) votesMap[creatorChoice] = votesMap[creatorChoice] - 1; - } - - const voterSet = new Set(voters); - let totalVotes = voters.length; - const chainKeys = new Set(keys); - for (const b of ballots) { - if (!chainKeys.has(b.target)) continue; - if (b.author === rootNode.author) continue; - if (!b.author || voterSet.has(b.author)) continue; + const latest = new Map(); + for (const b of cast) { + if (!b.author || b.author === creator) continue; if (!options.includes(b.choice)) continue; - voterSet.add(b.author); - voters.push(b.author); - votesMap[b.choice] = (votesMap[b.choice] || 0) + 1; - totalVotes += 1; + if (b.ts < opensAt) continue; + if (closesAt !== null && b.ts > closesAt) continue; + const known = latest.get(b.author); + if (!known || isLater(b, known)) latest.set(b.author, b); } - const tally = { votes: votesMap, voters, totalVotes }; - for (const k of keys) tallyByKey.set(k, tally); + const votes = {}; + for (const o of options) votes[o] = 0; + const voters = []; + for (const b of [...latest.values()].sort((a, b) => a.ts - b.ts || a.seq - b.seq)) { + votes[b.choice] = (votes[b.choice] || 0) + 1; + voters.push(b.author); + } + + const result = { votes, voters, totalVotes: voters.length, creator, rootId: root, deadline, options }; + for (const k of keys) results.set(k, result); } + return results; +}; + +const buildVoteTally = (messages) => { + const shared = new Map(); + const tallyByKey = new Map(); + for (const [k, r] of buildVoteResults(messages)) { + if (!shared.has(r)) shared.set(r, { votes: r.votes, voters: r.voters, totalVotes: r.totalVotes }); + tallyByKey.set(k, shared.get(r)); + } return tallyByKey; }; -module.exports = { buildVoteTally }; +module.exports = { buildVoteTally, buildVoteResults }; diff --git a/src/backend/wallet_addresses.js b/src/backend/wallet_addresses.js deleted file mode 100644 index ada6f20..0000000 --- a/src/backend/wallet_addresses.js +++ /dev/null @@ -1,17 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -const STORAGE_DIR = path.join(__dirname, "..", "configs"); -const ADDR_PATH = path.join(STORAGE_DIR, "wallet-addresses.json"); - -function ensure() { - if (!fs.existsSync(STORAGE_DIR)) fs.mkdirSync(STORAGE_DIR, { recursive: true }); - if (!fs.existsSync(ADDR_PATH)) fs.writeFileSync(ADDR_PATH, "{}"); -} -function readAll() { ensure(); return JSON.parse(fs.readFileSync(ADDR_PATH, "utf8")); } -function writeAll(m) { fs.writeFileSync(ADDR_PATH, JSON.stringify(m, null, 2)); } - -async function getAddress(userId) { const m = readAll(); return m[userId] || null; } -async function setAddress(userId, address) { const m = readAll(); m[userId] = address; writeAll(m); return true; } - -module.exports = { getAddress, setAddress }; diff --git a/src/client/assets/images/theme-clear-snh.png b/src/client/assets/images/theme-clear-snh.png new file mode 100644 index 0000000000000000000000000000000000000000..b759e6b306e4e1239e75371b5dea300b0905c09d GIT binary patch literal 2036 zcmb7FX;4#F7<~x=0zKtUTe z2?)i2h=?U3vV~YwD3Y)*fs%kIQAofs>=EcA&>Ee#GyZt8d-#z#8em5tC zGEx};00efQ0}cQn!{B28hk`Ry+8hD^6l<{#wg;jIL_@ZxMjvj%c<8@@qDRvX-Jy4H zvV756d43v$DXjI-N>VSmHqFV7Lx1~pbVK=9+zcBglc^2R+KTmn&w>FM5|a-BV}gP- zQj%iKR+4mdRu*sA#Bt>PuB0Zqt1auZO%TRiV=kKf?gbH9-mh2btUo!oP&W(Ll1I)X zsY);xphIV@dV_zWj!`@FbZ{YYn=2*O?UB4EkTV0^3X#o7{08*)_A-r3LUQ7lO#%-+ zXH*>NAUOL1h6q!HBDS;xa5RR3sbZew3<|eUXyV2AilyUa{5q6Kx=8aVxx;MvA_%&7 z4;DTx#{NF3h}3wsGsP>2QebxE`8=~>T*cVunP@LLmYPANFkncEeYA!m1tSMyLlIzv zqu1hLwz#kSh!=#Vgapf$;UIoufxI#@a~7W~$_V@op0De_`R%(go#dt2#?6BZJAVoD zMpiI%4Z{H%bsa5C&fO)fE~ma`Xj*j>qlu=Kz;w+Go$ZW71&;cf##YFr(5npLcX6lk4`q~SXp9+%w{n{nwJxI*Jhg1)q}$OuErTC zE1cSnh$$6=i_;MnM*W^aSyh(pbZSE=I9I$hpY_(C9G^WTJe(U9 z{%3fm+tMFI^Igy~P86?{m}JY&qx}} zmuaC+kNOdGT~BBkoyb(YcO18rEhSBTh22YF!uxTW4O#2GaZdd;I z_&$eMRzL*uvEwRYGsPOJ8p8`k-%q~!sE%YWa!Ge9 z6%Lp9giLrB$ssF>b#Dx%s-FxL?YLL0nm5OtgUv_p_Gz$rH{P}osh&KvA8Df3eqG*T zPLq{%QKqfd;MORfPB(aVdTPUi3|vOwceSAADa?LD(^{)lqIs)C7xj?ZR;}eJoYIl*O~13sEK9!d|@ngV;zL z+^wmXx$_eBg*yLP7COKyzfV7*cWOh!{aAjQ%5rmJXBu8Vm<^@H62)KZ$_V)P0hY!! z#!HuT^s2wuc<)l8tI~AMor1}iH9s448Hd>)Yd(~lX<=7(f)(>9&#JMGonT0ReCMDu zs{2TQ@svS{F3G(eX#Gak8e}zG?;F|r*8GF@>YvI67Smr-5{1W_gXXq69>PnMg(VjR zakJKtJ=&?OJzfHgtLfe>{)F_*V?wHLuPD?`V~6c4sP*X^*xa}w&>_8M3t3M z_(dNsMSR{mA~7aMj^KP5UrNQ@YH0T3;c@n3dHIm6AMznm7?~U&-Donoq>CsW|8HOt zVJ!6AZtXI@PW+7E+%Dy(p#Ns8N!w9%*wOA|IB+bJ+lxPBfa;B5|I9o-!6+WDRT3+xhX$^ZZW literal 0 HcmV?d00001 diff --git a/src/client/assets/images/theme-dark-snh.png b/src/client/assets/images/theme-dark-snh.png new file mode 100644 index 0000000000000000000000000000000000000000..76c9fa016c8223e43819bd5b8c4ce5b5a908200a GIT binary patch literal 2076 zcmah~dstF;7(R$Yv9L|D3(lA;=3FhUuqlR-T4f6>%0T8QVsC`hO zr(;GE6lz7laLE)1(rF!RXh~qmy^xzx6{!iI#*EjNU2;g8d?wXNk$`TL0tZY{KmzQ| zFwhcskQv)U=2lPlsI+L2p+!q`SDEJ2!e zCsx`N``c!b0(}S_mP=^C*@k0JZy$Bqlr@#ck!J<)M-qxh*IK^Q{&VKeY4|w46_=35 zh>-mfW*!+A%=fC(O!ZyzU@q!2MfgK!GvOHsEzItmD%lp(D&66@&9X>`%8NpWE4R!d z6JgiOg<(h*=z&fD*vjO2T!xb_Vb6jw&NluAaZPb>@1H`^z{2vHOqT z*w~9xr|2Sm`DY76p;pu)y9GAAR%T*9_^g|C*qYlDA28^QF1LMmS42gdPCC)c)I~#O zgYOOR7Tn3nJ+?NYWRwJ!(FPC{3+kJ^WAfQiaWJi=*v>@(FRHQ z#P@>mhG%T7tO>WU#pSG9iu!Osc`v&yEo;4F#OeLA_|Zgs<(b$cW|Jpe?_BP5+N6kr z?s4^{Hh4XtXwT@U3cHG9M)UDR1 zl;A~=8fWryo?KSjp&q#F>s~^79^0s<+=2fMk@-+bfbo(E%HdRwJbv|glACsQI|LEf zmR6-ECSR|Zu>SoMo0jY-LE#@h^r$Lvo8<*=e=O(gOwf|=uJoQ^)kDD`4qzO|5;iw5>=0|g)yd3cB z(O!IX*|xu1pH8y*vWP@goeahC?0iwrGWFi~_H^AcVNf5%W()1b?N6~t}EP~{(Soc6pA@ZCj$;Kw1BZQBgJYFj6 zyim=>Ol#`{i>*fn+O$k5{XBl~dxaWB`oV7JM1^JDhUVUsl~>O{ZBSVX3hdfLgL};@ zD1ktu=vLow{hvkMz@G0uf*I4ZtvcEs@(*i14bUAf2zs+Fy0N~1y# zQts0g^*5>D#di{nB|Uf`nzh5#hKqkOW4i7|#((hEsLV^~5B!&M?H6Nm7v5 z+U}yQtP1c*;73Vj@JyUTGMun&l{+^o-W;k=}4*m%fZE~et zVLx0x;JE(%`%?l3e z@$r06fp(BL%J-5#{(KXyS(5RqeL@Y*)9|oCY)t$ADf40{#RnK98TXSc_IZFVCxW^T z#cb2<%s9F)!t-c@?}aQEk1NN-vA6T&bwUA@c{As57j;=)zTw?dkw<(G4va(?4?AzU z%@7mJ`89&h-GE(|V?ze-Qp63CLuHg`y=`k61`*qIvM_9Q_`w8xSj!jiF;`C#BpZm|Le0%_1(9PAIAd^yzhD6+#+$8MjMz)M{`J?*Hq)RyCi z-LJFPWFO7w9fi3%cEfOgn#K8W?!8d5I*2#G)dtN+g21q~0svF&AWGz9=qf&G1Z_^9 z?vGX2tV}^fcJ#RX?LiKLQ!5w+mV|3^uI)T(t0w+vGUbXP`E&+z=mo1A60zNU>Rmn^ zi%fHk!PATa)|rk6HCHL@I&jN1JO=~0caziKp*)N1WT>Hx z8=$25?O=Tfb}^l9k}ML4L~eSv3U7)Uxg)yf#k2vPuG?!uRPRNx&q9FgOR+%qxhenx z^vzx=TvpQLMvrsCU_--AFssh&k&NV?a(Z~|U;8)o-9Ww$L<7ZoX;8Q_Ny@@iTOXOS z+L#>{%p!eatt-WJtT`T+&q!>MZVxHXchi%cYK|>6r@$HwV&b8`6n?GuOq6-Ou3k&h zr3*7}lfN>3SvoAdS0^W<-b&#oScXSGR9|&E%VgpHJbiDEy%D^6-<$QBp2n+>``;?( zZIcb#70~M|58~G;I#V`04S}^k<4WB}YW!o?M4LUCsoJkemdl#@mX#2;Cq8f25{u(( zrv1%kLY1Z$-&7c1=1rY7W&+hsucn4+t&s z%rG<#U$+j4;-~i`X_J~eUrs!kB7hSm2?LD#K1~zowWdT-Yr8HvY7=Zw8`}sjPVDAf zOas&f2Wo^2#Qn-%*l8bmDK@R|NAc(5L8#W^+1Yo-T+%+BvgZ0HD%wFIGw#)J+{1U ze^g0A<)oVBBD6o4ag(_N+2aXsS}!2avsAE&8#QNz3efUd5}#3=bz*e`24NlG({Ox8 z4M6>cPb|M<@o|n+vk7)hvsCijy}xW4b>ud?f*Y}kAGWotQ5xKujezM^d}+GPM3}?Z z4|YPESFr1~js-z@3&)yu-*)MXt8*{iam~_^So|zbI3>!i-G~L@mwlctBJeh}t{2CM zjtsWg1y0J_86bUpo*N_I)$klp-m&}jWjv?7icn#@01%54wPjqY6su}9+Y^T?CHR%E z-j-A!=P{#_jbcHZflZ&6I5^8{7g+xxZ=k0_8LBu&-v`2;=E8vJps`C#Axa=jYKU3P z0+zP1?!)8|T`t2gGZ;)#xP_N3<{JOakm94xIQ!_>GbGKM+A3qPf9=o|WD{VzJyRz^ z@amlZWcPEchgy0}HaBNz)Kn})mK*wkA0omk6YnE@u9iiR&0fM{anK3xl~{N511R24H0m6^BH^LwGr~EzMX=z`sP7eijay?CPm1(S4Zw~v z4~PFW<6mQxhGdhUn;QA=&ZWh*Dq{Pcq}?_Bn{J5=osO#KJT7GQP& literal 0 HcmV?d00001 diff --git a/src/client/assets/images/theme-purple-snh.png b/src/client/assets/images/theme-purple-snh.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9fffca68c6c047744f7a2eeb4009363fa53ae1 GIT binary patch literal 2113 zcmbVOeK?bA7=OKHp|jM8EZ>o&BF)D+D$7SuIjqZ`QyFr=XtK@x$oz>f4}>8zo*vuu0d#` zGywoWczL?}1As~=^es{cp_Ox4QUm~~t9!Yx+IaX?f5*~DuIF6Ju7^0L`N@bw$8>Z% zS2uy=#F_eha9$SvO}b_ zu(1YWj!YD%X40lX!o_75m|mzIcIwW_vAJBTh4#x`lbvLbaxJW6^5}|`I}xJQ4AwMM zGt-IUuD#7cVR=r9k-$j8hrxOUrgfi)CCKPDu5-Zb2C|7Io`O_IQ!uI~Y@mA@NyzPC zHO(+{+Ucv5*VBu+J=uM zNdvM~LLA@56znr9EY!y>rowUhyoV}~-wFR9Vl2+K4NA#7$6}xpHnO0O20+B-H?wFfo(+NC>jh((faG(Q|am{B&5-NzcN3WG+= z)(=@Et;=c7xO-2NSLo8;Qt`FN9&%y7o8(r+5AH9ia5~LR76z@ieV6b&g^9E|@b}hs ziX?tTc>;0Gw4qX$M^`rysw#WojZtZw6e_Dpq$gLr$0ixe`{e;;v}}fCNfJod)0z_= zGZOXBfgK~>XHCI~17o8M@e;YKt<8gublB`ARC&mKQukyWaNAS2{ZzV> zDZcu(Ly8Q?qE$2K(@3MAbd zD|~9s7jZ*`0d!TAU7@K~eX>qOaKClwW!;Mv-emA~e@sxd%aIRHWidL9%sjL?HrIao zeo-JrPh+f*psJe4)R+}4CdLvM%a1WmC_l-%TZAENa_vb$OpdTG5wrI*iEyR8CG&cb z)mQ0*K)SxTZ$L3H^mz;CIpR%WNb>K@1YsJBw!Qjl#~0nu1M;)I4Tty#PqaTv>G}y7 zjt`ywZ6Mpi-dwDKqP!lz2?gAkh9mkV8)H$S9OmOjqk}Op(z56!!Y7G2MkiwP8-+_l zEif^e9WBcgV;2GC3MkikC!MhmBPBBHh4ckSo0w;^^$lz8aYxqO6C8 zAfwqrylIqA@NdZwe2l7|7uF=cZ3nroH=2^@LE1t-7yAKvt=6_sgno&4?4@;Yp3uZn zs{!I;;n_W&B^aq6IFj=h4R=lT6{!%5Dt*}()SRUJ9uLa0#aUgxyBghBAV#izc@*66>YUD74Bx%OyU2%zl#Uj%5FM9fXXKa`! zPa6$XYK#lhPlz+28&gvhc712&41C?WgyrTg{x{E;U#q#e^)PQ? zzsCLw?iuCqM}|u;cfG9`s_jM8)JQ9TBCM zGkN*S`Ss|=+j(ey$pfrG-e!kR7S4~ci}#GWQkcASZ_D<5dBbMMj)Zz4>dEnN6UQm} z5u729Fw@86SIk_9Ej>~IY1wg~-6J9rTh+j~&)7?N>%BXvE6P8ztQ@g@Ik1Mi2pMgj>axfM5H|QGFpI3)`5)nAepvi! zU+UW0#JS#XB5&4%Sz*Dd&C>@ts2)Q@LGAFIvdvJ{^x4?e5g>JBK~ff_JvEm<;LAki zP9_{D!ayDFSI^n>O1m?%V($NwIdK)u;`p659b0Earl8iCS$?~mVg{pNb#>$&gydGF_W=DwfL=YF2w4>{Qg^6%w` z!C-=Rwg+5bFfJzaY9V+ai@96^1Y{Fp!s*Un%8UD&ZQuz;p8? zLS0?+FiF_Z(2zmn#o=&z7}2>K+}hfBb#?XjG+sG5IVB|}Wo2buU0p3LEj2ZDO-(H< zRzp@+ZqFXHjEtPRI#y9pSx!ztQBg@&R#r<3r=g*tqN0Mqs9EZxTax%!=-kbT{QLCK z9u^X}BLqe+!iV$V6%hjN<`Rn~+$P$n&UC()03Qj5YD(lkY$Cxb<~~G{TrTBaDB?EO zLM@eWo9UpM6A{a$TuliGQ*Bgx8ec=4fSZ{F2BWT_p{=Q@Ghf6_#K}C4M^;Aimxl{9 zC-E;9b8Bee>SB;sthS!I^iVFmF#%ad5kRA5-OMFkoZ%yCN_PN!s;cT(EKW&D1&7l% zGBVQB(pRg9*LZdAT|x?*u|dzGyo~Yrbg&N=QXf z_uk3FP@lEk0m~!tLvtmYwBaw3S%PP|GShCqlWI8d5X)Sy6QNURNQua5mYd& zaNZm1X#bJWGr(w+THCK<5Po!(Bh*Vd==-+Llp}luM}|m|Rr5V6WR41REb?A6OUOc|zVdaS$@i zBKgOxM#a1+;~$(~KlIO^Ir~mIUI+R{cf{lMo1c0^PHM&z)%w5M!bWe^Zo2uY74W44 zv>hiS%YY?*01|toRccoA^xU%Mudx2pinL#+`q_)dG3t(8;#qBrvF(tG2;v0ZleyCjW`DgG z|LWQJP%Pd1`~qkB$g93rpGPIDy7^{|$(06x)d3-Zm@BP+;@U@nnb}n>GRi)7DSdGbGo`?NY!QVD!=ZrCP5~Z`V zCL>mT;Z2>2S0DC<3$;a5v1YPNB=18eo9}oZx}r4j@6*8zepj}c%He-4?|rjS)ry6V zY&A4kS@j#MQ(gbhDamJ!xgl@yL~x;X>hbcH@GQdo-gJ{E`{I!MDlg}I`GeThl{!iA zx6LW3oW4dqo%PYYC!0UI#;+l_3x*0UL3+w|#s*49s%?GgEE4ql!I@4Xz~x;zg=%-h zuZy#PpgF86CH?03SIb_iIh!B>JTHB%CJKA6eI2e2g`o zPFtpQ*JO@4c04}E*zT^#h|19zk6a9t+B%5LeN2m}BepP`0NV0gPe;`XyVTsQQpJ-n z38y=2H?P^)5m8|g&#Rnk_E8I%I-dnvF9OVGUGSRykSbp1T>plxoZ++5)D`=-Ttp_} zdh{oHQh@9kojV(IYU@2r`d0ts@zMP1D~5>4nDWmhH<=4ZyTbxB_a@r(m+@QSFEQ`Z zHn&>oF8+v0F}D18-sPbdH>`{=P-VVd(OpxLCCKo{#80Pl5>wms{@B`&7Y1Ej{pee< zy`_=8saKEtw=r|}hEQd^TCNF#Qv926zY3{<+#C@ZCC^RsJr;!uMhEbZCG+=D&yf{v zn3$^y(w$5;@}<|KXs|wwJh88qs^DoPq9ucn%DoksPH#2lLrf|drPhPa^kN|f zUpTU|u?k24_jc{FKuK)KxHqSMJz1pLKZ^)Xg-4vW^R5A*$;^$jT7eL4zd7n4>)`nS zY8>%62F=aKOf}9*1!jCwX#`3v8d~b0;6WRfut&+?S*#t2r^AKbu7+ z0Ns}0g~IV`9(wkVxiS>5*|J)1GE<6j0y=FvcS&TDmB6d%fKH@#Zi?pdpnd*`H_gls zKfkNJ>$CFL8HOd;AGup_^r`g&B|ZQh>{QR)wVejgC_c}|=e7QPrsovfaf1hh$fDo7 zJ3y74ba^Ws?zcAIJF>{0{ua*Y#cP5FL{daNu+WH7iEl0V(&8)!@3%ojxIwn5VS$w` zOyYb#ALzp#&?6yK2KnVXa=qg2~c-C9cJ~SPtBxp z60Y7$l$qZ*W{f_D~sgveN}Y9lmTA6xQgqo8}U(Zc30+C3jdq;=hj|QPS7R&l08b^KX<5gy?$%{58Ovh zCo%9|)ZS}I?Pk^Km&0WfnQLCUgEvW4X+=y%G!`|?N(?Y9>pj`E>#MQgEF3lR>R8NC zqYo|F=)sVTK&$h{f^0ad7`awU9r4O)?JdZN+65Qj12UjhO(+fQXIRi#A{5mLy0a&u zF`w0|CIk2WZGB2y2*5pCFLjni5H7REl`ekIBA*5`#kPI2!!Bi%AlD2hn#G0m!OBnN z-si0)V>NduFh!1V5kZiIEhI{sm9e6KD10wBMZYen%nkz0HdAcFyKj=wDU*tx zp`4N9hyUmiDMo^HmtlY`2zK`I(kmKy!My;g0HT#-t8f9p5Z;e^gVT^b;~e}}2X6m& zT>mF-QvsX`Tk)Dx?Ul0@@O&f`;w-WTSSYrg3O>MvaR**nN6wJr9Uu z*gU_dT7$^?oDt7#MNi{Fk)+dBH;|m$H(dPbfUCUgZp!z zJ8It*$h|`GB&ZrCz>=-+;&Z`8-{d@Ei=rEN|F?`&QnJp}r(J?~=gVxa@P8&5`PM6r zeaSQs-_{KKEkn`By|8_?l>SDdn75OWN=Sp^4fjZ`01UMT55M_Nena z0vt(8?FT139=X81)n9X*3XNZgyj}`@ce(KpLObwNs2ChA{(2rU@@2>HQr9R1I3`05PVvuBw@V%h*6Z=j)Wj#it;8_l6FOes}ZptB@~e*`{k2SHj6c%Z^wMxww~ z{;Ayvsb87cA*Xf+LVM{47}(zKL})MmDL50fLcD7ACt5Kwag5(*8{XwCECw60#Ae-J zxh)oy%M;X*xy1WvSD4?HET?S}2-mQVHY;A(L6Jyz9yKr`Vr&p=ZOZbn+Zper{QN_Mb8r+PWYkK59tqfH^V8kz$xYT|>+eT?dVjx&& zUMFWmN<0!{2Ps#{=Nt2J1N#^jDuWibtV`v~8f$ZtdALK}ubz^XA&3k;hu~mSK+F9S zFD2L@M!}eq2(`7&tix_?9iOuL8tb(O9AL>oWC%#Nx1w^a=LlA{mf(FJff;`2T;u+c zw`kBhPe^N4JlVmAWp~os3q?_xXsiD@2C!uJ@p|N)@F1Vsw6iqZdfFN02BXFLHV>;y zYvZe`G?7#x&{*&b-6GYx4VK2=o-0fU?`&p?e4*W9+alG(2LHix%)T?shruVdD%y^| z;{x#I2#vk-70Fj99ot7aFft{1AUw>H5;-j>1pBb(UU2j;i|SFvMlQE>sE z$5Ni{I{hzk<3(njhOnlCd#?P1=*~$SBQ$`0YB+1^tUQKph)#+4eB@*oXPW%bt@uH< zeqTycN8(yj_#*QnXe4Y^;lWokJOtTed)xqh(LErR+VRJ!M(sv(PN zw6$k+pnr4_Dd2Pv)WG!cdITKa2&boiMAv|%Pax^*YvS=FJf1C?vG;#DghvL31}FUA z4i{|ADj|nGJ2SXO21m#FpALe>#l>ldMubHL_)~+lBTt7E&71Cw3bV6zI`D8m8TcCk;?0000UP)t-s|D^y# zL`XV1Ix{mkD=RNLDi9|al85tWC6cpQN;kN()1ma0VK~#9!?cA|$+b|G-VJ=3K zF+^PR5Fl$2WbkO9Ui!9;BRi5UQluoyB=3I0iwVKjXHwGL z(USS6tQo+70RsjMI6<&6m#VSk{ znbY5EnB`4qPrl&#;FZG{(o=!=Gdi0&eK2_M!xz#67%*TdV0C9{eb-@ik(RnxT3xoJ zFCW%d@z7V(0t^^1V8DO@0|pEjaBr{?+b*qOV>E~>@Y*cIw!&Dzg1v9z%LaHokqllK zz@le#ZB7FQ3>YwAz<>b*20Si!n>0`KfQwz7Knu9|sEjNYw%!gc4{)`=lY!oJ#BJch zy1#^(2KXY_7P1Kxwv3>^Ri z1`IeMu>Z;s1w1=_`w{vh#{mBt0X%btQ3-&@@RmaykX6Qrv;YGh5jt>0U!vh z?mr#^8(_eI0RsjM7%*VKqk*}dRytVS9pd_)0ifE;yzUW`XAK^hga8K2G0a(->o8!zfB^&644#Q?b1QiEEVez(E#S?w;Pzwz-%18Q z&l_OT16&(oykF~t;&-0XuZAE2QJHVEwSyma1<4-X*kadB@n3|3AD#Eaw}SU2=k1*CoORv;PGCmw=QQ9ZIIm+Ea2U9L zmd@)sd((ozA=HkR%S&Nf@>4bC+m?ip!Wn|F%8ZP4{Fm`=AZ?=}&@A(-Yi z&3!%U@q)_@I+*)qOD@cjr~bCNL32ls_C5sD8Qd=)4gnwae7ybWY3{2F-0~B22KTIZ zIJ9R5H#ay_nYM}X_OxKad+KjbWbO_wJuPpWVC&v{H%JKVef0nb{siX$ch#1!uDrmX z05D*{fB^#rEE}vY($XGFOUsrvvh(5azaHjO@#qM4)p)Y0X@$*b|No`4sal2&aL_;E zv#3kAd*|Hqz->H$0RsjM7%*VKfKLXN{|Jvm@LK*uS`NV)p3(wX6ES;tu z{N~M9e9_kH0B^n1ubB>TYquTzKESyS@nCTOxaKaQ8NKhfgG+BYr(Xv|a#@0hgsyjh z0dovT0joPptGf=Xi?sB`((1A$Mfor#9_e6pRZD$!X>}_PET#bl44Cy{)`t_i4r`0F zNFJnv)#bxVD;}&53$I$TKFs%%OU0_GS_2+aDh^y@I|!{TFU)`#Wh!<2ZWgIOOI z)mR^9F%2+az^o6mKAhHdSX-o}HF?N6AMOSgQ1J+w(}HSReZW~)EeC`1s74P14_kT2 z9GuI717N@$!-~(+ny$ke*^!njc@P^*J69fFgy#yL3Y literal 0 HcmV?d00001 diff --git a/src/client/assets/images/world-map.svg b/src/client/assets/images/world-map.svg deleted file mode 100644 index cf308be..0000000 --- a/src/client/assets/images/world-map.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/src/client/assets/images/worldmap-z2.png b/src/client/assets/images/worldmap-z2.png deleted file mode 100644 index ce8e6ac3bd59229d7847764f6761398d59c70867..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135491 zcmce;byQVv)HO?Ll?k<5tcO#8Rcb9}ncX!+ezwy5J z|2xKa{R8$mxHr#!Vy(I6oQq&31u4vDq|e~s;4o#RB~;+xkiaDp92x@ncI4AM2?q}c zCnNDz&2@h7e$H7()BO;0#F@KYO#IEu*D9}=`y6FuWmvk_DKuB3b^m%fJpU0)zJ4b7 zz_QvDbr*DL>X0SAZmznA}aKji!0-{Xt;znA}aKm7me)&Ac( z{QvT5?d!fBcyMsG_w<6WHJpH`Q8TF8IPvXiksM+KS>*vbX7SS|`uAj8WEb`@#0Z_> zR=u?>t|6^Orla+hp^a$;E~qQ3N+pjAVmF#3+LZWt6_Yl41K4YDaQ|jlGr%oVgr1*2 z5Lav&?2X@FE{wWz=YrY!A zk?O<>u%$l|_GA4aI1IF4@1fL8JKIDwFM(3memMMwYHXTd8av;-*Qb<{qG$KB)({K2szqi?dCa z=gnT-@i$>BUeJIQA;eh9u=?k-*h|;%a`-3 zzGqu6NjE$+l4)4Boz{F>aP`yIwyh8cZ_a2AJzl&kaPZ-Lcb(^dKCP>aypMSpS03SR z&DO@-xetTe%li@|f~zu9!XuZ}bFs_y|D~h1vt^w2dc*W3z>WXsNVW_9QErYibwNxp zBVasrWMyf3x#F4w^=ZSW3Gh~S+VpCq+SW>sIHq8z#xH7}+?SB*cO`%X$YK>X#NocK z4URXb>+H`w+h_Ni(e9JP?*IDqVB4oJO9@@%_}u~HXSodgFME#s6nC>uNF>o}@rKwQ z>f$|xHcMQ7ndo)TI|i`WS81>Hh`0%>(FJMUiVRR)u1cr){w`G`iwu}@be&aaVtz{=bIOc!*PJBO}*Q{i4PEl3-)q1+|Y6qJZf`S_l* z@R~~_kF|D`UXW~-qYjtk>21o$+0ZcHhS4KeL*!B`LpX9@gJszu4;VdkrnP)gPM{Ty zAGsQy@f&?gimr7otQH$rLeZ(gLZwvFON0EToD{$2Wh(hJCPvQoXoRfMvM+l5GdK*L$Dpajz~ab#y&# z!jITN;3!;O>DBesfd&n!qnt7jK@@CDijXMmDH@qj@J*v zStTxiac!bV8<2olmy{E>qBbnL!naaFS=7qK1g~@WtqI;k$r*)_Qu=<*F9=8W`K4(V z)0sEShe&ez(v@hJ>S-FDHuq_Rapl@otMv= z*P`;n8#X3NTN|}A-c03kXEqXuTK{6|NR9k5WAw5>?{wQlk!53R(bpP#(!RYhSQ5=^ zLcSl%j$ujJq$6MTH(V;O*)M?@lVQjyOL%Q^uEXbNi00IH*kAxp@u@vJ^_ihg$v8d= zP0h;0{?|Ctch8Xj%4*7DeP95~`H*K%&*xR%{y3jlIMk2!2`i~It?*iH>zAo(G3#kq zO)L|&qFZ@yG0R)DMb;Cl#?`<4rtXUS1`%a0zYHR}<~@}oam3nM_eZX@RZ^D^ySXzT zP7+cD(9d1!7JatR0*_TPo`v^gU6&z4p7O0P6Z(Ck&podeG8`YXP>86`(ZSlBhsa{+ z)N&k!g6$ZMS7AM>{V)YpjWhaO=wwUh@pg=ON@apO-?JGpwhQ`K#511SPTT21#qMV< zW?!H!uTWyNB%S`2})*W$v9X5G0Re+Y?UrX6o;&5S!?qh6M8GZy^Mc$<*bo{_}_miZbrZ z4U&h7b_Wa(f=BDQ=x*3z8D!E^V474!!{d|1>2kOhX*FI2R6P-Q4~Kw3)w#E`3jbr8 z{km*>iQR97vqgmEt>BuhA8S$0*~Kg*V{>VK)_75>oMa!8hgS>d*M;>lx*^O+^+F+m z)gg18RfalgPceJt1d;9yob9u_a<{`AytY_V-NM?pRPlic*`>2G@2^{=_j^)sJttYp z8_%ZR$c8Hv*$wq|Ueka23F>&we-Ux$ zeAkswapSt1BHY~>L=r{_w?%hj`quu}-+Ke4tvhJ@I6^%Bf0XaUrMmmWbhD*t{FHA( z*VKmW{G^z^358e-$4J=?<5zsvov<>#w*)u4kRJthOA5zq@jhaGR-iPuZo< zquXRT8Dk)rDGugiH2i^bSPcBXvXXEI(Owa665Dj&#Ol~g87_F8tJ1qbgq6DMaP`n_ z+|JbXzCPu2xQ`~^(TU-f`rzsL*WS)uJ1*_ZwFoPr(db;RDyJD%@-Vnp|Odh>%N-F_eYNv_ZWWkHdg#~uR#Q|7mX0Nz(ANw6_=hF(F z@N2=x<}DwdLlJtx%$a7lOv!%v81Y)FHdbyiv)bNGp;eXN_^tr z%5g1?FL4Q&G+~!&3bq-f{!vX-czL=x<2|8phPJ={ zaQLgA%;`tuP!qfL`N#;!r)kORdPE0u^mU&Xu4X$*RA6&nVF61Hc2)$>;vIu8L-EFxF}3 zw|DxyJLnFR@V)LDl0Hd-SNlO$2yzk(=yg*q|*T5)@BezV(Cwz>}xJ?!ae#`N1{T_*Q6 zf$?}$Q37mzmreQh_$epb{^$GZGaRvEJ9=lqT9gn3qH2N|LGrfvUF*si0HF}v-QtyT z@|wd=EEE*ZCZ-fHPTw}m&=GPDjrKE} zKdw`Ea|?M3!3sg(h-`fbLrs_$vVIw^)gk;Z^@0sM6Ed=~$PjXS`OLE#nuu73R{`)y zTd@G)p@n?0?%_8E#y9ZL;&%@g)S98>G;u>;Uau!Rp%<}4cLNCO87*;97$HEwotdz7 z`p`gh86b&v9^gGMEt2lT8U3BINGlnt&TC!C7mc1_wid+>!KLpupE!jrzt7b!J|N4K zu$Xv}$6BL)?1Db*pt71nL69{LfO)~i)5HU;QH3eZQVwb#uPcKa0@)#37DF6Q5#VXu zsXq-OFkuJikZ`wY`A=?Z3K8$^5d%02>Kr54#9BNN;X_^p0*q%2x#2WG~}iXiDQg(|b5y{!9q> zc`5&~Vr6l-^3qe_aH2)pOKUA`<#0iaCIE94DR%JXqI9yL8_%W+*=(u%r0Lp%#;S^^ zn%im2k*M{U@aufe!-+?y0p{W9_BU+aWAV4aW$u$%4Ljp?qjkcKOlRprkiV}mHFdRD zVDV-PZsd%dQ5((?`&~uu4Ye~luIxR-rYnh0Y2>)GVOTeE)i>Xu+*P(zaVj^|O~Y>6 zI@8Xt+OkP|rCz9UPL-4$KOnGcTaMrtXer+#0$k4Xdx$Kg^rB?>O&+k-osp#NEjf z(DoZwIMXppN}vEgFRP7l&Ss+2 zIj3ScUAMW@-3=ESYgQBv0f89=iPSEaLvfoF7o%5JRkjXc__O$wU;#BZBB;IgY9T~ zE92%DE%4t^hUc`9+P28yM38Zh;Z>8y$-0eDlN7EkEoMKvm6z|A6q!cZXpXtMl}eFz zS$#8{$J|(grMS7Ys>okxl&PTwK|7a%%8bkA1DPTj4hMu+GKr2Q4Q6$${3>}Qg++2D zBlxlo$xWSL@qJTK;^5$UaTl&F{2MBHkQHvmjXV@dI=1x?BXW7@g5pfPA7T@x*HTy8m^aQd);g^l-wbL_kD1dHYLxkj!)&UC+^?kjGr+6v4MR znZLi6lpdrnav#pE+tSSxY%QjtvQkFGev}{hWnE17*I22<_K<9*^*g;_8!!he>3)9SLn{K+dYhg z!ANC8`87_rXAl0HoW|x0hRMu;xZ0L!s&~gl{_TSIovBg$_&dO)VSBLuhP9LIGmBmr zX`}H5Ke0E1Og%ybz;z3}^Ir(FmvY>TRSRd_e7WqCG#XzL6vXc~9 zTq0t^rcn-56M=`0Zk1}Rbx@>}=zMEyr1&7!XuwZV&-q=+pnbc>Gpih?5$N3)w~uy9 ztM%i-@RVHuj>sbD+iKA|*RI{o;@?Imbe{!y&&56$S=j26iZ?`|Mk0SaNw~dTY(T9> z^i>r-WN+16iOtjF3?q3vB1ju&!Z3m(wol9=_Kz?u6S<8x(fZ&)KvRQPQPU0RBGTw0 z_nOu5NvoEcJqgCRyp4&NPFiCg4-NsgHzeH-lyA}YBSOnv@P()Q3L9ZN0+yw)HF>VM zF15J_TBy|SxA8fPeF;8EGMd-NYMl*cY@fKc$ilAv2zabqJFgPX zx{1?>tt(O*HT&L58*Io`wc8QAuWHWDSe3YTuuacR6GO6qA!Ynxe)z1$R!1ZJ6B}&% z{)l)9b86S$J_6Mkm4<-v-U;$yuo8gU!uQ#%hF{(!kaq&saX#l`idHI@Y|9fPC0WuV z05>6Z(VDqwKJ}oSmpCf)`c%e$97l|$3(x4dmG>c@M(mAeU0_6-5?yz}V`7Si16aC=XsTXuEUE2jUk^CQpG!w-I zBegXseRR%l`yEc?*|?(0rG_MimL41}5S7PtRyf%LGXX(#Bqf+@!#mov#ZQKj3ghp> z!>=Zhl?D<;uGRdu)4jC-8Sepg_9QHkv8;sSZLaJ;em-iX8gVlI8NclT^B{ftp3{cz z=IvbH1> zHQgI$P`j+W!-*Wo6FIzy=`MTGk1)~uA%ho>F=77Bjx~eePb%I+JrX&m7)vwB4Z<-!W#M=w z1w?i5vee&idvZj7Eo!Ym>R2X7oYQYp2tk9CO^_~V*1ekR2v@dR`%k^YS?c|-x@y)9 zVllI&TwC-1v#T(ZFAJGrlBkTlBPoW1tEH_a0x%BseFcltDkxd>%{ucX^WZAT)+K|n zMW+X^YU6J|9)6Q7p$bbir>nho7ft5)P{-=jzJ2_H&GwD3nww-E>^+aicmxTH;2Oa~ z&q8p9-5WAEtyTZ~aNM&-J zRi&Q&ONz!Yrn5JsLEl@s=z`RA0cg9;rZo2Q^^KjP^_ffj`MpT4n{+E&OVqL(IjA}P zd>P*ass;+qblEpv9^dOwPi(#}pakY5Y+byG(0YUzzZ}xi&9X>)-sAppA~bIYRh8)A z#g%#P`|)Y4t7pD*GQ9QDB*sd8G{_1l<99BQgn6}(?zZYVLOqpYWWwn8a2S7udbX&? zc)M;M8&+up9;zvS#-lvZr-_LfqmP&QqY=%OElFW3QiPWSi@f&d07_1!zZxKB04l(z zqaH4FX=~ET4dVCxlw1b7PJMa;eK{eg2khrxxpWY>@$TmQc>9cmv^M~jy?lOPQ*f*D zc8j3ZZZ{?-y5xgP0J zISJz21+Dtpnq606kOh>Jwqj!^0O8?vrjq$|1wSVkwvvH7`@Q!K~ImU9kq@t{0>z7amGxt`?*Hkl9C*qpa0U8L- z+S5LeY1T#r4NAU#l?di5$Kvu=BW{4+)=yG8_m+92!df5J4%_$!pNfHiV^W|qv>MDPj=^A=?6neYySh4K*Xp>=R1X^k!nN#EsUJQ z1p@{ji~dVh`9aB&NXZisM*HaNIsYn7B@sc&1-)g3#dD0$k)yro zJ4rdCo4itlg(YPf?L#A9CR0%tEwNJNW|!;QE1M*bOznlX6n5b28;}^KL|c3 z8yC<~zk-PydZn{Un-5M%3Tb>v)NfvysH}`x;J>K~)lWcpDkDC!l;91>w)a@ruxx16 zeVf*BVA+^0f6=VA$*$7L=(d0ul@Sfi-k6YCG#HXThy7Sj7qh2UF$A%21W_OgSzc7L zD+eAboMkD}(kGUg|I=M+56GIltyh~(3DTwrIUsDozOm9NR!81@ zHb*`_&ob3?6S36ImQUw=_9gZuGcZjp60w}2`R*Hk&r@~lKiTtc0py6}WSKc1_spF(;KICdwb?e!Nb*o%)1S z&-`x*Ql67i`N%S_f8ar1!tZ9h2orj(0s?2`tFr-|E=!I=tm>j{l&M`-r#J8WJROb*OJ9Sn4<@gsGN+f6W~7P!@l8k}!b&K>eAegF+uR zUCHx!TQ?JSJ|P)~6^FN1OJsXuHar(Rc^cu$XVcyL-^c92$b0>Kb^t6Y_KKg0C z$(`9SgYnOI`w~7s$RGjkVu=(S0Hrlm&v`(uGlc{CgZ>K!dTh|MXAg>rU%3b`C+C}_ z`p13ALZyzS=*lp)`#FPF9_NEk$aMrMX=;{mJ)*S-D@}wxHG`^$7E0OGrhT-NbrU($ zxski>i*7xw&5^qEnt$zY$O55&#zumab-x;E&AaunW(~+RQMMPq3tQ87l3fSRTHz3D zog+u8?;8`p{Vtt21IIr9i)zPQ22+h8QIGimYle|pIBe4*%Ul% zx3C8~C?-XWg0dca3TT0wA9z+<=VCSdIxTF{qGyM-3S9$_XB*XV?)Y_SjlH+NRf#LO z8t>qVPD-%U&OJeOC;I!$@FJ<()%gTDb>W-pn7_(*?}W9PPiT9>sDoBPJ;^IA(Y;?_UL2@!rwj zhYQ)PwS-RQbd5XXGT}%%ULUa4hw?Lro8}O&6vJmlOs8@ci-t*@=CELWvR#Q3{}jx) zd^*&S+mW=dqm>-7J3g<*!qoLvkTdh>w3cfnNY!g|EtM45(zTL>poBzv)iUxLbp!Li zHGHq$X~vPyXlLIlpKUkm*xQn3M4|Md^Oy>b?Z51kq_WxE0(Jg*F4rrj$2Km7?0yS) z>$5e3h+6y>vf_Jby%pm*{F}R>-O^SAiTj%idGiT!e`cb8NHk758R$n<;x8QJN%4z# zPuol+NUO(U&z00a0#8Lef*@ajyawJ<4!w%WSZ?7>)8vM+qK#t^-Xc7dD&7?`T?y*d z5V=V8IMI&O$&J(vv!fA4C;4b{WQ7U(=vTW};pd~?TWAWS1~y3)ML`zDtPtZ?%d9H# zgBPwP@|@oIlV`*U^U}RY@;uS|LsJpwwRW*WA&Dne3_W~r>w0_Um!mm={o#%v{-Ylb zV9ccK!&n=G0#BA$Pc|u{t&u|Fw!i&(FwFN|-~sSZ0!=0y#_pN+C&gVu?_lY-8^L&z zl=0)pLo4q2ED;(e?#u?kl>m@r$kQ zJu-kJK*tK;+K}Su2ELc#F&kG7i-ZPmMYGX;eGUOinn{Ve?72sUYf-JEz(4?mBwLI7 zd|6|?1IfY}{Y$Q__tU7v(x(*i^}w$>a)H-i(92JWrJ2l54seKY)EC3Z0;B0 z?Ft%W4WEm6j1d;H8a?{7^)8<#OGS^Gv8tqc&EYqaSd!gdRrK0&gpG`Oz=vKZ_N(K}n`Dx}ZBSo8{@^vPVqt1Dr9=J52S>5N1o+`h_e|WCDH@hL8HK%Ri#cp7$5M8 z$e9n`P;E=PT?%p;PNfZn2xBK?o2~F*cClVlK4r56P6>+!ANVJHk^Mt&?QnBWa2js5 zD%=Wf*?D+M+^OaYuuzl=W^8=^vFP`FQM(j&XVxHdKKE}ad$BL^I7+B==2If-1MSJ+ zj*XPPYtpcZ<9h>&ih2i2abOP2lvY8z@ zv%^C_$SM+rw<%|D&Xu?grosrtHYZ(l?0c6J(pkMpV-urDX_T(pJZB$bD>V&a81zBp za`fTnW!COPENfD9OStpSNA-q_@42ATW;uhS`rqOU-6n0&zFanj@?R(idk~bn{DNiY zaGsu4mcIOmHT#D2$zHVkCn;Ia+}@C(hmqwElIV|h5zB7u{^1G9<|M;9f!qz%RIG69 zj1KRD5-Iq7`Z@K*tv*^P=SCDmVDEF=+N{Tu@<)_nV51WXS#LmO0$^#plE+Km3hhx{ zZ=K{7=iylS%{aB16NA`RP{bQFcr$B0uVq_;?$ibjkajck$& z{HBxVoF>k4&c+lgcUOU|sR$h^JC446S!0K`6uRU}K-q<+Z!5`M$#GkGAQQW3w1<== zkxHcdV@h*K%lcwnQc>)Rs$m?+#0|8XjNMGg8rbnJqjL%O`ME-+K^8eoRO;l`qcf#z z{wqj-+!opvLYyGe8jNJ`)%`q_>@RZ6a+s_a#|nLBi{$E1E!d@iqr#-5#O87qnEyoU zN}oeS6X&h4jWX<%Pz3~Ojh)5TRfO<3te}m(eHPJR>8UPOi{uQgVL+1*)~DWZe@z!8 zRMw`>6wu0gcRVfMg7PSA&0lsPo59k^gPDRz7F-czrs{ zfKAEA5aAGB_i+WB>ci#SBwV8&G=v0NP_9t;Jx~6_eBmzL<(VrP_8rwY2=~qrTcX9c z_=!p_?pUPNV?OC3XP(=c!ocdv9QHsCI(WCE4wUz`W~oF(Nrjl~(rd1jmt%Q!HR86g zu%06Wo93WFbQ7c*NW9tE3*nxgal$;xB26!K+uk0+p3gN@yD~gxQ-)%x=}PhBw6)iA zI%fXfdwtjl2kaFOBjXHo4Qo+WdwLcVgd66N)>zX@Nw9}gKEOk>IE2lqKl44Wy|9e|&Wqk@@P6dBDz;`)TN0^}vRFUOsC!Z^0WrR| zc3XGlEb2B#7<mgDc7N@_2~4Q{dbIGc`H#S+2Y2|kS;bBzL(6~G{p7) z*+6g1^8A2w&So9OVOGrQv)8AU+TEeG93uzVJ<(-?9y)9`eu7PY;P?6jbB3r7C^N-s zV^8@Ub_D<$2y9}v%$7{k%Mjp{Bky!m@P0N0o8*G~xHCYR0M~#w+i@PYf+sbXoH&hE zw&R$}yRcP5z6S2i`P+u?@xW zwM_?`?4jbXACI<2`ahC5s8LRu`0l&&7;-!FVf@SI`G$sW7?H42Yw56P-Y(+9g0Z?M zlb6`nJ>YP>vMB8pNLF zl{t>_pVx+$`$#T-pdy#K(HdkytiQDNBRU4OfTHsqKMMx1PLHMw-Iu3-!v!p@sqq1h zkTaLo?uvbBqtP&3sYItQ+e#7KslRXW*HM%H?)k)=g@dMFZK{U`3{QUME^-Q;R|9Au z(__<2ew%EXoR_xl?xui%=s=PoRNG8eRNCq{zV!T$vF7V5$6q&6Xf0TD0gq79t$FHz z4c(goorBIQe-IIXp1PK|r0KcgaVKlqIev8?T6UF9`Qbm`dT;QE*L-VjWXC?u5#J?_ z!YO=y5uGqx-y+vT9s6j8*LF>*xjQ=p;e%H6)F@DP-ux?SLhit>UR0==i$x0$e*TIy zeU+ZYSiY{&7gwkdhknI1x840`bM1eXOSpQIIjr6xJ6WXCvF|^+60|uHJm%nG^QDPz zzK2YgN>G(*N6*8C)_Fr>#EU4;>Fanbr*c4$xWtw^*vG*R7`1P=dOR?Q*bk7ES6B_k z-?%L68O)PT&KOwefdj zfpN$hd+oyr6y+63N+kzmMjSFdh8g=%;$Fjg#vIAw>gYjz8ulF#GxN!T25cY2Bb=+P z2LU^g9-xd4(l;Ws+Q}*kVw}!nJyY@4s`Pp-mP#c+i?FEvnQ;XB)k|2ShTzz)HLk77 zX(5M+)qSM+5n%b(g19py;;m5rF*5ttW%1Mj7k|gJbBqBS6XtkrPErY9ZYpnk5Xxui=h56)C;0 zfy|yp@R4R*(hoZTyX58muZU{tcw6C+(=@B|bq|r$+=0h)xEqeIe}Q20g!A1;OwqEG zw)27S^3<+NurNu2qMH$M1z1Hsu+gqZGa~Eju;zl*AS$s0LZ-0Pt=o4?xb8X7 zr~HCO>hl~I*hWffW84c80D-r(Jbc-(U(LFs51AI}Y&!G|A>h|~G#wbL8L`g%Aj+oF zJ)a(a6{w9kPpzQLInAB9#AF+w1epd_!QC>^!bY^LTyl>kl8L`dgq?v@w~x{!kp?iZ zNFsFBOCQAUYD;-sh(3QK6`d;`Isk_ek=A^vgSrW6JQ~-xYy?nD?xgnfu;Gn z$95`5s@BZ=+4?;#00w6;>%zO96e5PR&3nFSlkU~bFDdA11|JE<%uBwVR-1S#=Q;4G zt|^UQ}Iy)x}~?R__b^?37Jwwyo* zFyGBs2rdhfwSd%K5AANb2Z5mPQ~kA+Q2Ugq^=0LRMS{A`6+$)HMx zv8A_Fh6U^lGSaKWz4iTa9Mcgis60b!;S^{Uh6tf%EFEoXE3F)XgqBTtIGb5DqF=_=2socBmWC&qtzNVq1h~yIbGtu*G^4ri@cn z?Zc=8BKluzTMk;FBBueq8E}R}yKJ{f7i(E^slw=|NoC|3n`hYq@SrB?$_@LDoGEhn zO*%QYpN*bV5-URgZo3)vEy>FGoNfhD`#az~sXeCuG*}7hu|nK>1y_?u;-P4c<@$Gh zG-%201t7O!PiZq%;?XW2^r%PD1~9S+o8we&s4nDPIA=OmLk+D1*xQza?Za&S+3nJ#nIOLo+2pr3!N{&;*uO5qjhdts1e}=#JmrPx z1qHK;8rNcV0HDCF`DYg4edzB5iZQQck$2rnE&l!bfne9qTy{~%cf4a^D<&m7e=4Xf z={}o(ZB1`3#uSJqi^_2o9siPl*+yQG!KM-_8{rgifW{o&7ebct0@QXGe;(z0_sNn~ zglzZo5v2N?DGQJ@4+0t~i>%~sR3dU);L}!eh4#^e>rA~YsrOaSb4`$>WG}mp`l2~5 zKG{I$yEQ;VNU5;{{OA67p}&wBZJDlINd3kBZ`;?t;gE} zX|EvT=XpI-QsdIE;TiH=_Gnd)Bb>$J9K{T6OSscXz^1FS)Q5)_&9N`h)35fiAC1Lc zWo_;65B;>(C;D*$xAilz4`1q=XSR7Y`01uFlI62lZ(fZ7cryBGSH3_2`&+e-jZ3(O zwO#-kvbq>bD(PWNFi6FEw=Do>w~w<5F^|v|rFky|IrHl53LyhI^F!7`98`UV1{kPN z0ggEL2IKs?$_0q!6-h`qiFB;t?0gSv(`sVN5oR!O7FI1OgxEqu|_e89=5B;R+y?}ESDQ)~E`Ky?v zGTX}Kxo2s7v!R*iQhs5Sbi@!WbQ%BEN@w<3`6Ygqd>_p?b>WbI6$m0_bO&o@T^^@a z7afLj{`0{a04*_6?mq((8XG|HhYPDYc&z-eoftqQ&)+FInYY_zM6dhK6~UxQ*dADy zN~Z#Gt)#|sm(AyB=)CdUQk}DX>hb52^;8QQUTW0f?&h4vR2YeIlSm<9>EY(c!hMOTHx(Az%1~~)QENb3(=e*@#DRpOTBGXOwv%s zpuboItJXzJ# zLW)rxz^&`G?hHs~3kLI#nR;qDQx5Z=E-8IoDk5X|Lr1}0p$(3k+Zsq$YT4@t+DYZT zcH+riPZ`D+i&H(OF%d{*#;m#+1Ks|9q3G-CkH{8m31S&g@r^T&`G!R(!wB0)R(PhbBgE}F}!otk&i>D;^M_5q6) z`Yg6?C6)PYe23HXLD$bTXL;p=3O-b~y{m5#{`eydn81PX4s1Bi+4?Hnww1nY%(l_5 z*SDwNNcO`7d?~v?pHszslcP#m?eeF(<$hFQ%WiOval}B=A&>$bD39q6UfncljDMTd zW&+voXm%$(gDqa(%z^qnl>NEL?UbL(1%A}<53$a!j|cUpmpim)d;=+Rl`G^?9Rj-e1X8b; z5`$i1Ibh^3qR|Bfj1Ll{YNHcFhyy)!wHuQg|9BujY~B;o{9+rvKwiRu0p+ob^EbY` z=u;>xJ_*K=J@+x`o0&V=1%{JHFQznh!XwyD9)m=aCR>^GG%CH_{M_jHnhxD_%yIWI4 zj^U}>5Se-I5b4|ps?Y2_R;c^8ZWp)d!4v9xohmb{S^@0S@JI9t@dA-2GE?_?$)NSc zBKcxnMxMh8H9@C|d%&ccH;kZ5Yi9xlur>drrc zg>p*qW2`2a;DjDj4J|zr+n)`CSmX?+j9Nt;iOa1BCl&~a=9N|5%d-Rj$qLF3E%THa2Y9F_WSh6Kk9`~-9R z_r;$<*BS8NL4RH8IlimI+$Ao4mr4uWrKrSbES?ix?*I*kwR_8Udk2{*$Cv5BF}4_n zfybNN*d>nxt+58bjz$~8;;sQDUpzS0@i`>5TT-!mUfNL7-i2Y=B}O`{1}z(Rw>AMM z_SUY=nhtDbV3a=$fU+e4truOmwf)0QqjS#i!zd+YE=dSn)-yCXRJewpY+(afKXg_K z8Je}%V%6*-Y<$!cIl?Xt&x=I&KbAMHDX)jeKdvg((|y94oz)DB1CDY%-q>TL}JSNmsYXW3BfG$sr6>0IU?h>xQ(L*XPDCfb-{E$?FG^ zj%X31<7H*Vra?CBEGCrto%}|B}T0A)(0}z9H0)v1M@v}p%JO=U84dM5-sNcA;pa*5H zEo(ZT>y?^GZae4!)zCu+J%@JN74UhumQ0DQapD){9?=5xY7^hit2ZhH%GRQC$(z!1 z(pE-ZG^Z=Q?`MW9XX`o=v^ZdzRqUS z4qif^N|OrU>Hn^k?5N2=S(a8;b?d4^&H8e#cA>S|)?FP$OG4~M5HE?0`K6;%sI zjaPG%wtYvru6CEa*(O6AZ=oM`T}gw8vw@#Pxx+ygJTod zbc0NkYzo2G0fhsu9le|w7&yaLl(NcQaxzAF4DPcb2G*2A{Nos1w>cy~ABD4cJ?!~E zM|1d?ht43n3HDfs#Lts+XUJm746H+p=YuIzeawEjzocObcdN@R0sZLyaiHg2rf|q2 z+DAFkO09Kud^pKstkn7_?t>F*inQSqqvTp+mb(-)1V7eyl;V)ghGw&VCW zK@k;n4$&MUo!j!cx9?iD*EERCBwpuI7J0ua8GY}l!65yUfOX|0I<a+Ze$RP~Zig)$v zWZh>&>KZ8q0VT?s+gdqWy_A?!g||H>Fw$UW^w#yU@C?3rxTp|m1kFKsocWB^XbucR z$qG3;{=ZdAT07BI)yjs}jXw$ayypnbe@s60T2n8)@ch+QcI~+T`FmCE73#Qz?2PzS zVhZk=a>pF0H1XE+k1s->{bl$|fp2|bxEMAiZRV@*S>JwXK#z*>bVmHh*w)LTblfo_ zua5wkB7lPd)|BT-a{8##wCz}gn|Djmj_h1%q5xm;e7YZf{+qu4aJ7BkJhRARBpJxbYGg3 zSmDA?wMAZ@UyHuNhu>sYcY%nAsb1&BHY5srqR-ExxD3_p3VF;*kaUzTAujx?=l@1KLp=3~a*?6j1EVDmeV?2_QidG!o~M;4dP0u(}c` zQo((+M*}D;%uUT%N_$QGsLHqc4|BU-@}92&IX!_BfI}DFRoh9&EEjA z(R|TA$T-Mgk7C-n;=UhVG}$0>h*|zP0+cqQjCM(PPPJw)8=*B)b`}*p2K#unYm%TC z{2Kzgt}0bDfuq`GO#!qmgveFdh)>bgojjX<(V&GOV)bYZ6(Ac-i3WcUU`r;_z|02z zY077&FG7oUmG25TUjM8VkLaE+1Y(S#(sH^P&dc-ld|Z#~x?jtUR+bWEPpOw3 zqt~ngmIrpdMXi1-tk|MkVK0<5svPm%U%+PaS-KFr--r6P<-n!9Bfb`Mne%Ey@~2yJ zZD5+NnN;g&Me@@{?b0>FF(U&^Qs4QYw7f~0zuBES$xX~24_J!Kq0qRgNeWCjNkDKE znM=OZ$osvxUcvg_(G18VALSbBdjZ_xx9`n&c9-{gVN{+a&BI zssZqs_I_QS4rgCBc+%N0R(VLw(6ahW(gHt9>+L^MM}Tsu=4h#|ag)&Xvtn$;$oznH zc@U_F-MTwwj^=OYN(-Tf(N!46t4!PvKooic+o zk^Eb@t>g?=%Dsgy|8fKS&y`hz$~fhHe-6P;leKEDiDld)tDX-sE0J)1SwsFRk&!;87x9_LC+^`J>Em2d zh9BwM4Hfxg<>l4;So;pW)}D> z3Yh_bK!-8BFO7o=7{P@~Mmij*Aw$Ond#{6G!Z~;iH_i5Wk&O?H~`JC1A1cpi{5VBaWXbKc`J;NrANH{9&yPac_kU!7JgmY}=Kv1r23 z_d^3ZMwYS{=Hko8LLtVJ345d5=u~A1Cn)d3oX%14GVNDf^rlTI>ugiY)Bhb+w;Dq0zckF!L38N%1Qtv($C{psg%932%>4d&(-+jqq~Y(evQHX{9fXXq=V)yjMM zWCv2*GRerZVe0hJ(~>*6BC*qwsksla({vMI3d~k4trcWZ+-41EHB@g?)m|=xq6tlq#4s<+4dwJ5e`--;tH?{sGGtqZ3uk==nz(PeM7H^h-KV7CNU<-F7go!R`YkpXffO($KP&jN zGC*)b7?DPOEo&#I^dW{za!>qS9f$}%T1*%|PZj@8>9nCT%x12<_*@TV92p@BM}PW% z{D{s$B?7VELtnidezpf{1;Qm)Bu7)>=3q9gd zD(b4ADAefgl;5H&pNf_;_Y*QS4e!oYG(3@%Jo5hgfPO^d&0c6_ai}bL9X?ycaqaz^ zVD)z6(COvZtFcPIKZyS2Ey0^aJG^ihQXDCo=Z^(`H^Qr|kDrHJyZlf`AL)W<5&BGg ze8JZNbL*cScR2e1na%kfVg}`*6K=s)GTbSZBk3xlUHL8R>i&wpn{O?3-Bci+*v0n4 zTc2x3>rZ%ho}<@?k~P9#coaFeyk@htT4T1c}V17_;=eZ=A%V&ukkZCgeX-OQq7BAj_ zW=U(L>xW9o^d!>#fVmGWBT1oq5kAf(+*W(fq|lC%lJ5EY|0Yv4TztCcsp;JSE)*1V z^S5|C#rK)lQs`N|A}rBXwC0m?RWC#BTK>g-`N6ELh(#Ez+?)T{y197r?@4%{Qgm%_ z_u@A~>^9_$&l&rnU9GrJ2$YHN6)&aKEM)*NKD&r~) zW8;H!bRLff)@kC=PsJ8y?J!VEvSTM;ATqWQuz+}&{UAxC=Jxn^av?NbY_T}`v4FqU~UIY7+PtN+YnF30_1*< zmTsnKr{@ptSs(MkKH*;%z+3QA&iZ`&t<)*S*(WtN9y1QZIn|N#n#B`%L6eXA^hrJ; zJ4y_TFw<6rS|3Og9F)9@1~)SgjHUcJY|(&A<~eY&4`>yvPP72NEg*@nkcI(>-5DS$>7xfAS?p$Beva#7i#lM7Ah^Gg z?R1VRSGW+W?H@1L;8yc)W43!p!*hLm%(}KjVk4^e{uQ$0RMkd{s!cz!OL2~0h)MC09L$eOyk6T&;gp^I9H#sa6AGsi!IR)7B<m5Eujd&-%{QZ%a6`(5{qWxF*|FsV7T0H*ngNTV86`*gIi%5!`dYxSV1XtUOQau#I4gQ!wT@ zLmN%fT`A3jHirl+WsV|*9EW<~15U^$+dvzXn8~dJn zE4!0)X-X%a9 z7THX+vQ=fTNplU%!Xrh%;O-WS^IjCdS4LYbgg3@R4t~dG3P-IW^@E%4Xtj*{qiEZ9 z6$+DnXu^v|kgMSEO~Lc!HPXm(>GHW9xR=xfP%3Z1z@tan4{eaW+!uVKoHY{(w{fGK zFvo$~`^_@RB}La$BMK6>aEp;#5;;M{cVlJ7_h2(TpNjD`+AX6{2D#_ucmz|bjj@@d zDDl5o{dw#F^)6?5f3X(AcvO=H)4Uw%ezTS#Jx>#$>TbIr{Hu}qSU`=M5SyEs8(;Vf zplng^`{mwFa+v}Yn`^%L3iw$6z+QUAFO*dHROCJsRf)6!(_7RimkZn=oo@ERdAM`h zw>dq29s@R0Y}#3!6uX!f(Q{<++8QAKm~wrL zyrpb1+ysvzfy>*#G)BthK>V*6AY;y{QQ1Rd`KEG;h)#X;OCQMjNCsk)Ys4nb3n|X{ z{O@+XpEdCo7IG{b2&`YL4Jcy8oib9EB`Pz8zn_U3WNKdY$6PgWsXiscduQ*9+XD)M13pR6vB zi8uFsF)Jz|Gd7OsxI35jDURdbM&M>4BpI0U1t)%{`k>kuR@*^$60P%u`k{O1veOy- z9ssQXMJuAxoS_B$I2+n?W%eMN7Ef3y3s&K-92R%*8p4tUIIMLZVHpxQ(K@#YBiT#Q zN!(^D>$ERjTuB7^?U6G*7$BbY1874O=Pczq9a*Gr>aXwZTPePm$|=Wz+uhmmvczKv z9_!ZJL^lJ>{DX6{)G?ro{Jxce%tjftbQHb6h~of(dDGZ+wYloeFXu5@%qQ7gL;z4Y z4*D!E>&p5G5@y~;1%!#}pc=YGZC0O&sxFPg#6V)*BztNzBePa!d zZViSN#HH_Av+;BbZD6>4u0D5|^)u`FI=EmD4uP0K5Gh;qMp;IJ=HZDuaPuMXUflM1 z@{4ZCDRJGni(2(q^OH1m+v9wAYe3~_^2@f4-KOG0bMX(U>VxOC0m2%{$L}?nw&)>O zfK#`qZiV-~|434kBWN-_H);*dtcbKMUFjg8!;A8DZ~>X*e!tFR6vc}%oPzmCLNR)& zV;x4P=h%hSExv|(>0rh4`_?4MMX;>N^(fL^D_`z-U|3&}?njbkwpUs5?_A76{;aQb zU60GsB<}zqlM7jo3PxNFIm@E(DUl#c#^7_O5#{8BNo`fZw6;0Aa{zfZbK43?$7 zWdt!_kaPyYlDAO5<96+kfQTNmjiVmOEQ87RG({3wuyv+-W9Xl>*^u zdBJ*%PyN!9cS1Ff5SFi;iv%RsyDq|U07J7eC>REJn|ckHlhq6@FjDq=^-0S6-i89T zLuW8(tOk`I6OyxT>$&0&D;c>!DC7?{FDK~|A%BKV9{1LH5|51LgCQ{uIIV!}-Eg^2 z+aJf_SAr+DEKw95)=ME*%XlD>vo0@14yV>$+|??wtOanYUx-V)7C(cOOwCHEmiK;P z|IRciRl%VNRu)z}+~#MRD98TDUXnD9Xk*rSVh-SWU1%T*sr%A3n&)Mk^2IB0}f8A!_Ebk_M1zqGr7P zd#&DurHgt0@@>7FY(O6Q`87pe4fMoOXlgv5Z0kC>L`#r)p zCUe0|6O7(R8_Kjb*D4j636~UrnZFt-YrnJl`fTWqu+-j!tlE1b$u5_n54$UI?Ow}9 zm+pfy$RkM<$*g@O(8V>0?jY8m7j8ouktU5+7Gy_+Lfjq?Bzy?v>@EmuxxZ*6nY%Db$_d;< z*KNB6y011$%W!Ybk_Br5Vva`qC);LwcQ8%uzKAzCOs}i>$MSfcVm$()Oz5Fc_<5W&aWJiR}PP&3Cw#*<`&tY*WJ8l%S`j zdI2t15`mwfAVBa(@cLu{WK0FKxi9k z`86s@J(a5YCHO0q5NzL?eGF2{sMZ;(P5Q`SYH$vceB{1509Ol^FA&tcm!U?kJlzX| zVys)KZ05YJA%zE2Q4&Cc_4GiB;q`GyZ*2bAy7Qv!7+ApY7W`B>KMC_NnShnD&a^%9 z<`Yy;h9COc;b`ln`!cWTc`VyF_#C~bMSKjD%hd|I3~AZ zn%IdvK$IvS6YiT_R}2VPu7(%%|Kv>nF52lUD5ZMv&i=x-k|?F3!uJ!MVA|9kQ83Lu zfC6Hc>a~ibqv2jQ8&z*4NcKVn#W2jsWasnLgTJ^*BPklVf95KtD)sHfGI}vk0?$RM zdii5n{($+V#E<|&g_esYkP{hcLzhm9D?pmVF-Q&@5FK4?HMlojL%QqhZb?Z;)Bd0k zQJBPznwN`NO%D0^_|JEnwLuK9HoYz^mj|Z~b=DPQ^hMDCu_|^e^EYz6`v;zZR;4le z9&;Im7#o~Kgk^M!h|{Ph!p6^o?3tEzIXjownCsL6CYN88|JcU`Xt_Qtq@d@9k{W|2 zXAX0#;DoAV0u5D??Ijrz-#fI)UiB1~tx)F&w3o~ak%>G&U7eLCJ$o0}u~(L4#HZbt zuSFSbENSq!%FpX;W?M)$CK&4^vtWjmKZF$gF~fVCA&Ny}5EvbdF5|zP%iVjV$6d?| zAYMwAo7$1|smJy$+|?JEn7zo{B(?8rW{tKpk}ixs3v7}oP%gmiLN~1=cpKz|T^GDE zK|4jKt4$1cnry91pC#2(KEB3se11%?@l2Rr5IsMJ?T|W5d>B@O&j4U#tBt+f8BTGE zsn7X99*UwHXZz~*iJb-haRX4vigrL-pnx8t$y?UpA4N#P^VIdzErN|C#^l0gU#V^O zsocS-^HgNv_brH#ZIlNV%Hde^20U1aYGZ2*>gSf|f)(#umXSo`A z7kS1rC;ldYfM(gv)~>q=0Cn>z4MPDd`BQSD2c|DFqX-2!qo~z0^TeNsBebDSiF2tt ztE`^fIsVFPepZu40992iKU3&}z58NH#&srtpVD~S057VHnIq5|lx zcF!iY9H9Hi=zhg`7gc`qDa|{$L5`l!@R(xPJD3HP$SurnXrCyd>Q4rpJnl|)s|l;QA9IThy&^$* zrYE+)`nKQ)VG6~KvD2*mIRKhCMAj$IDX~mS*PK#dV3#yc=kf^^c4^(L4Qe78JdtKO z(fK!{GXpeJZ@S8dg&^Qazk0J+yRRZY=7fpbJC{#ldglGLa>Vib?fZxC@Nf9m+&N^y z@FkXz>WYseoWP6}Ir`?%$pwiA-0;x3#0c{%+9%Q72uyKJP*zWhIV@{M6L0Ud=YMFf z!7{3FhG!$>>~i)M3koEkMW=Av_UVu%a_0wVxLb>iguHJA^45IPvo?>TQ(cyMcpE8H z&lwo<0(Jq9*fdyTh%IPKFJ0#jRnY}AmWzF{@p8+Qilj-js);1uw$?dMLDy2ZWS+=; z1)`p!gR@-}C~!{hU)U)e)B?+K^Iu9ofsiK6iz?;;*ZIvP#r`g0!Ws%%<|au40nZi% z%-QPg5aO(;<2)dCwgKf}2Y{6@M6!g@nr3Amif9+s*EjMt>MB^}4av-gcNf|xUzvhK zaPpPlu`s!_x0(~17$UUMr4Ekk|6HIX@m&`~^mu!!!|AKzAZ+ zU7#*;8T?DbI1f^)-!rgRHIm$DUqcc5!#PlJ>M_v!7IS4s@&ma9b8C=M z07nGK*IRRn#aHikGHIE;5n3!u@_fXHCC-)lp*fl5*_D?RZ}TTo)wWvzZ12fS3m8;keuis3 z&z4!m|9;3DiL5MdPBfu#JQ0`Do3N=_`*LN}_=bd9{a^!Sn;(ztxt;AzZ%wKxs^XU3 zjYs2BtsJ`E_A)~9+A|nV5*Hm2)R;uH67$yEjxf*|(DXPtL~SOMEzWbLrWOBZmw2f1 zV(njnCpET;Pkf8pPUr*6Xjpy{62ZbNSIh4y(~=~gaAgT=$oRq~X)XB5zxsSZ`+oBt ztYaNMYuwV`{(x_q=KPUfH_eDB1saAsQVg*R7mPkDUC((Sh+{;n5GJPCdli`&o@hd1 z0cAlxRP;7O>MS&2P_n?$!LT#HQ4@wm%bDx6$szBkJb(OD`*GUW+G12bDpl_reYiMa zAkXRy-oM7FvPJwdvtbYI{zp^J3=*5*k9P z7#yMtM$sLhjIska4!JP@;go&eA@TKT%y)M!v@Pj&u3W!?z@sP(3! z<}hi~p2~^KWg~@nN{EFTyH<7T1G|-IhjknYW6Csvd3gvWo84|5SW0M5EdDu1*d<&$ zaq1XIakh1j%Q;*ebmJ&0-_5CisF)suW%Hv@mw9=EFI9L^$yrm|&X(3l5G<9377z-b zijB=Kqvz+!|uYDm4-bZYRw zM+mC+c5vU{7UjnJ&d%P+Kqz-}o`|Ob9c$59cD|prhHA6NSz9#S8jVSy-o>;#3T~#> zA_}q4(6wwf^jxXXXoAo+fXva1p1VP~jPzxNeIWq%t5iuPz*^5gs{%{NL7IhEK~jf< zm29Xn5Hm^t?3FZB|6+TKNk$$V`41t-tx6s^ACi2Ya_RF@4Fv!5XHa_e-fJE7y#NTY zqw|ZhxB!kGN$SoM`f&+PCL^HG`oy&#IFzO7AiWwEQ@JZ&}Ejk$(`PoJSHL-tq`3d1Vb z>IZ~~%(=dg@#5?}qph zx3vH~2N;ZA=eFLWY z`8^!o@l^KcHb)$UACKKvtc+z-4mp2Ti*V zkv(JkYk@j_(590;&h{$$2PX^i_qBu|t zD;f=9#t4Gg8A*=hV>k>qW2O#sQ)12~V%L zR05Nn-zNx4Dr~8zh`#a8|96Nk3`^=kUkU%9BgMr~Z^y`0AU2s=b&=Odzw3puAJ7J= zw2Wt8>!IakJUp%>r+;@KkwO8(nE{ftiLc?bxX~-<-h~fDDlTQx~&M)8o!hnNc zcvceN+$E28%dhzQ@LyUh2e{|%?*^O-NT7|h%xbv~k|5Rxa)ISEkj?S?0}gO>->8n! zw7my-Vxf?tRsU#dBK~7~duTJOTXx*CmKy={IXTXsxdJLd;;9QW0E4P&L(>e)9>?o@ z^-MsJs@Km7Af-3ID3w;;cc-*exB0n?g=6_~D`&RudZT@G`AWGjS*{smHT(Ev{;<2K z`cP-mm)a;*tzfwg$~;B~*PPJpdz$xRMD59{?fXhu&rA3a0<7QsmND(rIqI(j>+Ptu zQaSJ(1L(*0w=aEBGZnz%0n5O7F|2_Yy{5XZ;=)-G06y}@GQwhpU_7Nc$)mWP375V# z!P~Q(^DoA&D~j}e(ONU&>T8?laSJ3?-+CXIDl@|)h7U0doNBRV;1a{Esm%!vv?;hW`ReKuZBbHN=(>9TSY$k&4 zIZGxUTAwq8*>z$3E=rnI5JZ=UU8UjghqBnXzpw(T)~MykAa$Giq@Tw)NnV-}3tGZ4gZ=3Dt?AqGiVH!n9&tPo^g2*;)? z8)f#KrtxfJHgNAXDF3%Pr~5%q+)et&ZF==YLM*3(pPzBINANiPqTc29JwQ#E6N~CQ zL`L{nz62L@UB4eLpNc{P!6x)0IJdlhW#N+;9EP3lzBvWX_qj=ukYoGe=5T|dTDW^o zR$Tk6B2{8$8^rX$L(GgZ(!3m3e|X|NhNQ*DhtSX}i9MwH;6rph(2-r_(hYI;a>YVX ze;_N`+xn9>X<0mIb5=H;z4MCNi5{neSw8g<{ANW;-{u=be_Gl-nQQ`9GPDM1RvQz* zGF$5NE?G4&BopRK5fyDsDeu}^4GkC;9hqJV*l6pB2QZA4vOF)0>!T1>Ukta04u<~= zdn^xCPgy{&OtK`weO4ssh{V^MP#&;&cXQJ^?Euduax|gUfFJ0)hm8$LYPy3Ko!9j; zTtn%GExD5dPV_5S>e&fgLN9HASuNo$`LzUSLHd4h0)I|G0pE(&!l`sv`R<0>Uh@m- z9)WX`V9OwI(Pm2qSPF&!%KpeDhfMr+ zuImPZ6Mn`bZ=FKd(v|LAh<#hL^7rkI-8g~6Q#vhh8rjVL2Y`A^roM(+dHVD$Jyz1j zD4>^nNN7BqG>?)3D;cnb4OdScWLx*Vs3Yk_Y>G4F0T70*CqhX@M~V)Ktm; z*pk~6GA1Nojb>AULfOjF3w8z`;%1K-u8)hNzCXy5D8ZyqA+hMkAD}$_fjd$loQEWZ z54M0=VAU64^tSGQ!PE)~hYLU`WS1iC0<{!C-Hg&Z{~82*1WkMKLXc{n{$yN)5{uNE zP~e#gR+idPHjsHY_p8C?9pa3x@@Bz$Jl?rd+0n1(dorgv=R2}J52v8mXJPQzVa5Oo zM(Dp*7Paa zwScE%4XJX#!hq*kym$~DGx+&dPg4lUL5ce7_;8q>Tc|3VSmC41_QApVBENf8=KJHDR zkkPpQ0+vJC(9GByi(*$OJJ3O9!Ya5s2udZ(A1>`2`hI5b&e43j7z0bd0s;7hEb0xy z+J{C=rmp&>`;-$`+O-6E-kf;gB>a8rz<>Ss{J-{g<&IW3Zbld1^7;$%TKa?&VtVIcNFpMvWg*rb%c(^fYE&vb-^gNw5GfU(>H%$M^OZ--Nj+ zfH`&#_FwppnYnMtFrW@(BLW3OD8DCFB7ZkfR}uua8}asuDJN0{V8A1?HT4=Q`q`RS zEyZiYl2%#GZz}~?Y~a}>61Ffx|COATFZahQC*3wC|9aoPprHqmYc?V)^Bed6mr}_TC&lY}3zNEwtt}K!l@0j^XjHv8Los%q-w#@U zp}T#)b{7TAh<%>n+d3ag^`n)nEM@J3&+d7`%82PT%5m{z^(ObXZ001+s-36_$svsd z%p7j4)xbD$VOsAK9aR(rL_CTOE%Fj>fiN#QQ+WRek``C`4R;X-E792z&*Dt?Uxf`< z{b|gPMp#R6UKi)eZDa%RMhyUolULe#^rTeR_nz_L2V=VE>>a@8nMaLV&6#u|BE-}_ z0OB}_AgKpafUdjbBr4eCv!47S_ywLeC?;g*-F)?;jj)<&#Ut27-Dppa)O8*&BeC-L zV8M59r6`4A+x<&;aWzPisk~UQi~Ckk<5u_A8WsQvH`&V zratzZW;EXq15NW;rVD!DYcKdWYRYHi359KMArMkhFvPbPf;~yN`Il%RkW^Na+Sw?r zrQWaC04E}AOSj99EX&wG*?jj^+%2(LZmeB7mm5h)8TPhZCT9HzpFAHK#2)_|K4NM7 zAro*X!T7YLw>L@`DAb?@pvrC1`c2#b)Oo&R9f$F$luG}|su(P^z`=ft`6stw8PW6M zE97+NbS_qg4Vw>mU!y-PIIh=-zra3bz$07kWQO(w&`m#(IjvV(e#jt10d32!vdB+< zf+iC1novxaQ2zw1y}r2XFg$wclK-x)l8py%A!X})ZDCeX8l*Tc=oOWaW7h!&2Df}z zIqZ~T4J8F9Gx~S#p4_QH4D2o$F%bZ{E;jDU;fWOZjy0(k!p8mrN4{KQU}`?Zy!jv` zY5Nz}Ik=4E&}%{B6`O=dMyX4$jx(c_JLKg2D_=CnMK7WyJh0*-iw8?9$flV*J)6yt zcLRK}cYq`uIh7n~?n$2xuo4=Nsgrk${1;BWaSL^yubb9Ed7a^c$taBr8fP1^@6CHc zvoddBCEcXRxd}@o&A@6Lk!!RMMH~`c&GC5zn&wX2jO@t8c^6}WzMXIQ z#plZU1Ini}uSHd^0)nHn>!640U6!Eu47it2Y+t#h?;p{#@E3^&e1q2675;|~GDu_1 zpX`%)p6&O4qf>ZVO`VAbMC7*MfCJ@BC@j${Wf@xU5AO0sK1J@apj4xS`E=U9v}sS9 zGwozEce){C{;f{I>-~8pGRGy^sQc4?q9QzSC6CQA;@05r%D+l!Y+W0;yU2gG+N$)} z052#B0R<)Y#Uf0OEyTbD$*Dor%P-0IuK$KV<;!bm=GQ=uZOB)rw36#Os?v{B;0Gs^ zdeJMEa+OgsXVsyg`Jdc#NA{2^x$5z)(3s*Tt}-7}o4LwWK2*VmUP%j*F7=cGYM_AC z*;e80^W{&usn6{Uo?nbx*z{RfBw;4*(%q=^zDEa_h092x#J=2tqBpQ_S*^PVAWfh2 zQRb7o7z40IIwbU6(zEado3`qh5Sj{pgd4TAd}pq7=1Txa81NTgqsjr({%DePnpe)t zD+ai?`lUX35VE31Q6F>kfEn|uj)$ql~1Wl-?v@neFN%jiJ(iE-dzNVn`%{Hgh5%isG}Fgb_%{--vo9+w5H>EXCUmsURIqd0jL%8R z{Q2kVGe@!i_*B45iT5;Z6a32}zW^;;oc$=s%j9$E`(E$9INiQ)VwJa^;E1Y#kF_d2 z)i+S@;==j(_OZ2-AC?JVD_3L@~_)t%BBZp_8t`4QYXq z98Iy<4K15Nv|!vP7LQq<+FSOOlFEbDwTZS7v{Tzya1PVSq+=KS`v41#8nzC+A18w7 zk^o7ME-wdc#cbPW6w{evGZ?j6dfdHj9- zlKEi$-#x#5r=8Y8MX##|+MKA=^=|#LV%a*flteEUtFF(OBECL_gW#7D$1T4f3)sV} zE?xtN8I1_UfgL%OvhKh0;D@TL9hKg}l|EJT0EF+0Fzf$&i4j3x* zTEM-Fjtt64f9mi6!h}f-M-Zj+y3u5-lHFq^@Y;iCo{;i)Q8&V#B7G6bR}e$70f{%Z zVafG>`o<47P?HaH=Sl6`yIwdYBa%uK(JV}V`&Kc)J)efq5kQW(Ahh+>Vj%G6)dqPf zBv7(4oqq+GQx5wR>c^lb(fel;;VtGW#r~>5GH97Y8+rnL*?=36Qfm7&;NXw6u=<>M z2&&A}l+kW$bA~hrto?LQC-3;45j!qvIZY&><_S&Q*7MbJ3O(n44+tr?H)R%wS^4Cq zv$RJfbXVU0#Pp59rC$56Pq8+QeYsj^lap!)w(($_o(qg8>9$FDVguO={DR(Cq_+u+ z{F#=uGbt90SUl^z@v99Km&sBU4>k8RpC{#jypZU@I%nClaPd%y0{?_0cW^G~J?`PjUL?_Kt4HH&3|DD)<4g(bqSYp2&iLLvF{?(m|yhnG63eeaZ9j0lO$UqJF#<7gl zk0DaeqTLxT=V&VC62=9thf?YDa2s}$XZU7BeV`IjsAc%zA~1WrsKJrf z*0h2*L=xiEd2Ai$5ezCPGNoedF06Y@U3FH*Vmt8MUw_6y@+Q|lWi1G&= zStKXpou9v0Z1DcAWD~t$`MN7j&iMs!6nD@mqZyAskbqOj{(QB7u3U!qV!o8hPGZPj z$}e~Iuce^HUT(V6Hkf)t&9*BFBPfC_E+c`y-h#RkrJgt9BXZlG@ZlFRX`fe&so86@ z7>xz3691g|Eafed>eW;?wJp7qf<49GXMz~sVwXn$UhpCOH*0^pVQ@G9b5S1r?m**0 z%+ny$_YDuuL+NHxmtP5IY2LW`T|EWkMiVZl>GRJUkljk(7KfdBOIa7T`adAg<*hc0{UV!UyY5 zt-hWSKFJl@&70Y8AfB(>$_Fm(Xrj_Qb`^G>jXK4*y?JV}0#$~<0vAZ2i=lbEV?AMu zlX$LHC98qf6oTE^R6P7&`n{IhSR?1;=r5mzu)=J*mxJhUom;uP2`UNa&=79{le%UQ zz&lQqDr2bB}3pOYavn6i3;y-M4ORCy&tXtg4%u(gB((i&9Fs?OgB zbj00lM1XdiVkliUV$|B@DV+6yJxd&OgGFJvZa)J!N6$e>3Jd zQl$A4F)Xo!BQMH|RzL$NXjx0qbL=K&Hfcq(XDHnJhTihn3Xe{IF~P zR|Ob1dnxWW43=ciB$coeuZwp8`n6>R&mBR-BC~pb;y4J0%E#0=0Qcx#dhDrHE*fQb zsvmG(4GM(5`TT^3yj2qz8>}@4J1HJhvu-2q9G0u{p5w+_(+EN+s?}t9kY9z`397>Q zoA=#6ti%=8(?JK%=PZ>@HUnpA!@ZIAR* z*ze?fLE;gaQHjHBL7H--DJ^*2b0L51vXq3?F#pI{?JPC@%*giS>~GwQ2YifgzewFa zkGedah4uJW!)5KwMD!2j7VMw>O zg7AO6#+qi^L*AAEI)3u@x1;VGpi}c2`SEAcSYzO->IdBTVm|OTuPynQJ$@jeWc4;Ok#v`FL=fy`J+=Q5%J4{kj;zeap>vFk{X5*nQKR9*}qhV zsC`O2nOd09Y!fqou#w*RoPEKw$!Xs$D)2{a<=#)JBAEO3$FzU2soN@&qBABz6ex+l z;0IjQLluyv)~lIURUJOKbcWj-w~`BOe>n)OvHC~~;W}5gyG#;yT8PJWJej{Erw%a! zPdXB?C+UIUW{O`~O(AOBBsNgW)=1mq_7S_Z$hqQE9^09LbwwcXqSO9;0hE z9;1e!i9kS0PG&9F5CGQEYC*WXSK=VLVCN|4bx|5%83}4n4v8tvlrN>fN?4x03p6}; zV4m1gHw2*GQ5EmqMpd8LM*MEjNJ||-U{ zWGY}X-wWw7pKygLugz&}LpSmYuAg^NTMehQOoX3=ql(yWMuWPA`T0&yZ!0q|=jKm% zpd1cQ%su;Wb+_t5c*9}t*3|9$QMStFI=+9L3tam^RD-&U~m{b!$YEUuGN&yf4QY3AD9`HAXBG8+hX zBgWV*pgJSvXc4&#exe(k_BF~=5LUabHaM!%;+bjJji^={{BO^@xPS+_BzER^K6^y8NY z9aT5@G=ws>v$9~|E06?Qw_x1OtF^Re^Ai>%jH?>!o_k9akxn<`W%5YsT}dUZn}yK&POhA`N(M3;t2%I0%7gya#Z$3j5(Ovv6F{;N`Z9 zmMS&ryZX`$_-c0)S~3bi1z^(<0QNSm&x!m$5I>dX(#Jmz7X)7((O6!@Mz6+=u1b$~ zBo%^-aW%OQK@tT3)x1aNHlbuZ-EFm?27LGu{2gldd~h02VT!b<0fGFoFq@;(Ms!wF z6UNV24X^-Uq?4^`EIj|`9JNW3emY$;Ch_R?nc6NPb^5?ARB5pqfAk%tGpEMqPoLT| zEa$4b5Sy|=R)~P;5h(bjb*Hni5L5CF*p?87m6Q>R()I5{oxweW5!i8QA-rE{}f@dZ^tQ<#;(VUFN9Y%fzm-z23g1K zX!AW{p@pz%vkOFc!Dm5!omfK1aOED8!94do^>a|pT3Fq$G*VP}*(%Dtu94Sa)d%Q4 z+uVnd-YC!)jxRW?X;P0A`$0+&H~}rpTvsuvygsjNxxjiVLs9bF zlITuB0rhKO@i-rWZ8dC`@#qbG5(skC7B^I#vDWFZyNFUQg0qIAUQmFRAfLFsH{B6m zrwXBG4hEsDE$%4W>}a5SUF65#N|f@wc=K{#otqpqn-3+T1f4qhDWp$c%)Ua;S0hn1 zZ-5)T+G0`H<)yTv99_3F?00R3LA-mnROka%oNjmV68VzXw_YCc|NdUD zruHjVQ>ui4_9^FIBkY+G5>+U0(2SlBlJ0BZO+lzM(=Fu$2Ds|SeivYi@183stm*q!z3;QzA| z25&P(V`zTo>DqrqCsEiT($T!@7V_#2=Q9JL>Fb-Ggio#G{VB76iZSLn+yV%-mmC;L zIeJl>xMwQ63ia{lB{!3fQ8n*ObeTXIuz;mr`({=>hRE`hMYQk}f_(NLt+Zw3l+69w zz)e!{lQ^WCT7zvd8L%#hhtVJs2Vyn41YHMLbPHepOf{8LQWej6YJ?6b_u_%sQ+@*e z&#!RMW!AAJyzdYj{cq9pl)Ok7mLcqQT|Zq(&7I@klMyB}Pvci|s*1`B%u?Yx&cYF^ zEG)BOZo+E7U5*1f9rNFoy+;FjnFRT`0|MHb4gktLdFlIkI;iH`$L?fFS_C&qvU@Qa zoBx~tt|2&8c~k@4PZOilRS{7Bwg)?G!q2~%=E&`(IHw;Sok}w)=j15;WGsm2oQa9u z|DFUm@kjql2kcbH4veVQ+^TP=VQHS1seV@u1aE&20)jH?6Z;3WbGtPh%QWyFPx{+* za&KwfK4c0ilu`Xv%_limF0B3o^6kJCCSpL5MwMiGm7og>%U4c$E6Mtlz`0^E!nu4j2|cA&ICwFWm%^CUgi#T3EhK5}H!yJuBaU#xd6C5pkQyWqRHU zF$xmoL-?JrO?eDn2k^#`|FGMxYX>O*Cr|oLhWLZ0#q}&;c&W1C`5+Z@wDi1cPja8m zyJlb$2Cs`lPh5^5@ZyqAAIY|b-W*7ZcoAxHKtaF7U)(qKPqR!`A|)6NHT1|Foy*f# zu3ZitZ|Ye`rX=Bc|FUYwI}HzuNPG`hYt+J2vBhXKuR#MYGSN_Lw3sRu%Ak8U7rm+J^!W2 z=p$U+db6cA{-PyS}XcUgL2aDTf}8Oxg}?+``eMc?4&eH)fz}!;+6*#39cc#uw6LHwEU9!i zmn70n2nkuWLV=&H{vStY84y+5gkca#DGBKkkOpa%4ry7sq+@}l8)+m3q)T#XknT?D z?(RmqJHGRN|NM1acF!}C`@~(3}hi`)Z_SO7UF?FeFl3UEV)oLDB>J2H4N;Z;? z;~;pc@858oaD8>LqU5weA;q}C;ZSMAd$Nh=C0lPkZ>Hu*CeC#55|n{uftWrNN9Ij; zO9*$%ZPyzrY&fy#w=&U-il(OhIH5+TwR0XJd*0-jwe#^pW>M|ju=}(!%giK0SWZI7p@L}GKM`nClWKm2VD01inATA~g4W%F&wZfQb9My0I_q63O zu5EW6@81M7^SP;JVNMbymBsdK9BcxEdo{u*|Ay=zyk5{lfamBtep*_1nT~!8t!zFA z=yC~>mKF7gZbI?ZV2^)%le=18nhHD|)+37CBV)X-Z&A2!6m`U?==Q5;f0K)9C^4~r zq>mS>BY=9^m){)~Ao4Lg+36|#e6N)50v{tp)q3>Yxuk@ZXx_FEjzV=%ac@kKpycL4%BY59 zBLO@&v0u1du@Jpi6<3BaYxLbW*MA1Z=j$pOKMmsbiWLQ~m=Hr&2QPBwea03Kz=;$( z9c@Vj#pJ2?E)|oaY2s2!N#Y^VlcJf8$vX*3Z2r3EFgFwMK+4#|pmJJl>=0vCB=-l> z1ASCRFOjWqx(o1~R1>f`~K6pdM*jB=a@2VtI(3RsJuAXJK+E2e6!uu-WO-4h}ZJN-0ZRLU*Qlwt@ zB9&6JbhecUNkVs!ZQVPwUOiUobKG(U^cV8r>DG>DAxYoxePI)8r45&lAB1z-6#do9 zKOE9w+-KCh;p+gVVERieyk7ghP)Go!r^@;)@{&^4yXfbzF&`aQAt{~7To1UMjqBqy z%0MohOA{gJCn+irqq>Sr6BntdPZxFe%pVvD(Xjopx3O`lhp9QszZ-3+lf&MB(YN zp@4N7tDn_+i$F|}MQx6vJWu`l9sPtL?YaBOV;~eGOOzi?8acCk5?4)exXb&#g~iVN ze*BFg+}nQrTvc8_yuDw)GUpK5jwJ~fjdb9;G4r!ZhNeDe{*rxo{@E`NQTK}0r;NL=Zs zw3ZH$n47qevvKKzbzy*IbBRe5x>zR!+me~@YMD=uH6UApD5!P+fWVQj1K1-Ep1hS{A?-iK z(@NeZfy#bQC+4$^xB}D!f7mUHczh?FLgT?DmXr9#z!g7?3sPv`A7GCXkdmcwQ)Xad zOCzOJDAlw-9#n<4+J82}?7JN8Tk>Vw(3WcQH~V+QPwxn|kubPL$-U<2K#G&`DY%g8hQP>x^&kg(I0w8um zwS~$d=#diG;ZHd--S6Os>_VJXjZV8X(P?1}kfH zq^C)s{ONg+)KM2_QMhnWRx`o1YDqc;1qne1P3VG=9Zmy@yAVTe?&Tx8>1iTlkkfoL z9NShyKhPoHL&;jw^B(=PrRy&v$B0}4f}CmV>>wT&t;Wy0YDVz) zrN8oyqIob4`OYiKg#F+%nK2j08U@8O)$rKx?APBATH@CpK}Iw}?MgW*kYI zQFqxwdMsqG#gw`7lJ8HNwkOK|`g0yn65u@rm^9%WENKh#coE>er48(m>Y(5?mEU|T zNvU(S0aw{OXq0aicDcyXx7rR-(Ode|YET!zIg)gj!MjB=V(q z%!VBdYT~6*G$E?!Ssx5Kcn;X_EPI~oc_*~NJ9(i;Uhd1k#1`_>{-%uCngyyK)x3i4 zI7vE}>vP-qm8jX7gpd1Ih5Lc=HU;66bHp?s_DC&5;LH4qjj7qANH~~0PI6fh1t>oH zJwe%oQ5u7n^5KzTU5)>+%aB_>9Qv+>PCi@GC1eDrc|!*!Xqmy4iG0$-1jT*s9Y2PZK< zeDN!jJT*-%I(F#jP`7^|VUzxPkAz5B@t4$O;x5h#0g{saK6pO-vk(#yn74WvOpX4M z`>ZG+V%WYOUPnwywLd1#Y&hgV$5kw>jP#KOwnY(5@pRnjtZhMBnqcRvRj2Rh9R4539)J$~(EIG8 z-M3!92?C=FSe_`^E|3*#q*UHrPbIdb;-+f9c(WuU^X`gsP>BD8EA0KmCQ2;6G5$TT zZyjr{r4Iwtb((77rKSjW)od49K-in1^s?MF7v_40f-KC7&VVbk7KVp z?fQukFEGuPmP{t{>gtRfqr|>Uk!%8crQjzBn-7Vln9+#22=})7M??odcdz6X8MemZ ztiU&4h;c6?Ef>`9@{4_K2UhxNns{6Ji*NO??%KEzLl`)R3rEpXoc2udueNj)6#CZ2ZX*!3e$enf4CxGeW zt!`oHD|Vm=iu~IuBb_4irCA_e_k-nE%-d2yq@+qOd(NTKvcJQCcwq@r)Ul+SuI)aO z+I5$I$zg@n?%3uwqb*GyNqNw5RCZQApchCCK#*i9_oGiDy4JxOUxf?sabR7s5>bP) zf=K>*xep4Ci=NdXQ#?Q)~|L6s# zWo#yPvYFrD1Wc~4$DFcjT`wqNB=6KDCBjD7HjU4787{Lmr+;zr&;%13)a@K$`DrYH?Kv}BZ^R$v*%tC9sM(!vy#n_iew6lm7LsL6@#kCyyPfW^zK;<&10Ef3 z5ux_Y#U(V2eUue%bmMDqT8NJxu@%tV0sf~(;Q9UBUT3{;9bjv66BAU>|s-R>(? z-`s&Ac#*`NYW#P(o#{&Ed?P4Lxc|z;qb7#*y&Lc>X!3Cmq>Q7ZFS@Uy;9Kk%h!D;| zWPdg8k3(9}+0>c^5;KXI zm813kI0`>Aua)uDgW*$PdXkK#wSxehwwR63U3itw&2O$iRX%8#-H_n(Yc3ol$;R~i z0t8X)QBOukUC)sSfuo@6;RxPz#T*jVel#3DV2QaF=`U|*?ukQ>i4X#hxaq)1PYOx! z;Kw6Pd9OzN5raL@6fq*Qnc?=`=%fRM-Jc23sDgXIX<`t^XuS3-cJ|) z@tvp`$Wk)WM%)>eXhrKvnA>~#7lhky^WcJ`{Q|i7WUQ-o&W0jAmS*POtny3CrA9E? zn%%og>kz zz~kq%10|Wwm5qM|FS62~V1uLAez)2-1)gFO{Q=pWtsnMax0^nHfoMKAmj8zm<0E{K zDch_96m1moS6OSwxXfy>Ww7~kLU$Y2ihNZv^FHSmdqWZde8Ds5SDN3#!QaSPbTmvB zzEKZD*N=J;HHIDVHzwZkb|?1+xKUyTm{keC&23yp_HuaY6Qvw)M*Upg)Kx0KvhgDS zU9+^_-Nviit*(-9Uybkvn?uYRXEah5^#2_el0Hl7w4SthHkYM**^J`K+U=*HcW1`F zT3ZdoVFwDcVFHa`dl=9yhjqv&3|oM3=&I>}<{uk@?y2M)hMBIvb$xVaIa7_5&v945 z`dUMP#p5gG631$gDf*> z^lf=d;KiM{NW znoPFF{kGqq601oL2kb=A{#%KKKe76RU{XxEav9 zTmToo!(?eZl9X@6OBCAW%oOGD`rf)S6y8~*OYC+39RB3!b7(ilOR*dsd0CR+%)N_9 z8>maG;|Enz{IxPa=XK*S0dhh7fzVB_=#MfSQZddDKo|+ z8{f4j; z7DF$H40>XEpUzy~Yevtkb*W7|?ZlF8irA`YKgg;FyeS*EwZBQHgAtD*Uw~vo()_sn z18LKCRYB=gar5d(D3NWvw_`iU6nTxt*ErSncrB|j)OzO01`gciNXv zw-u8MxCvb1XP#+_+1N{jJCX$cV;`_%JLO9Y$?GF{o4p^T1}A7|qCbw_UJ}1N!fYV& zdo?31B~fo(6cCPe#04iGo&3d}8J35_rHu7!)I0OmG`5;SMy-{4vrMAP1EFFnFh1Si z^PcKfC){>EuJTG+ZOR_!w@G=2J;*z{x#vrkg3ghnMQ%;~7BptXrDe!p$5-pTnn>3* zA`LF@-(OSbJMj4?KIbp|;z9OgW%m3^>@}ew@nJ>6Le^_W9p0@azSWVD2FVvX7&|lx z?^k)}JW%;(0&HPcRVTvI7#Q6bkxT6vn~vr%Qy`pkdy#!AX7;>}Ox=NNv|$EU<^Pg@ z#EcR_h@$OCgqR7Z%`Rn1$z%a0m1Pp41`;1+$-cxt!2M-6(`{6-T_ux4YjWl5DB{jL z(ZgVECOMEN!n=kMmZ+ELQaD!V>HJyfXIkcE;TG6kC!v$#@FHc5IP{!Q(5`sm`!9s2 zjTD23T5;o7b>f!Y>YP<^qNlnPnSgr7k7|8U?led`2uWOB(Ag~BH)YW^c~IFL^iXa~ z8qZ4LeXP%VF&$V4=PFQ&sO7rT<91(EiIGCf#1@jeHCDyG!n4vLa;BG&a20PQujqs# zqiwrC3gW;;b<)X4_F?@CFMlBQoHU`Yc6kRq4JQefj-N zRSS=_X_K8sN%1T9ER3}gZ6d4UpjsUk)I?XgLr#80+#TNDepS8t5Hk0m*0gE4aXdxF zwGV0Kh9RdRkv=#X5Q(+xy0Xcc-7W(UmdhRJ+#j;dwKHwt+Wr!Le;Jz!+N|OvR0WI| z4Kj$(diBScCATa%;(_f|YMjX!GwL{`@`CQ-$kqOe?6_UUKD9&OO9!@a+@b;34I-(n z*2=uPI+>(; zSC{CF=NkX#3xC(YdAM6mN%IAhIj_2ze|g9;y`^=#cxaepWMkelKutT6OHN`~Z1nRp z`{g74tyHj1PGMRmSp+2Mji`LGCK<%V_b{}P{LBEqr)UgN)x@_o9w*z9h4v!sS-ojp z=&^AJ5sQUtL{*N_8}tEdv#|v%_*Q}xic=+-tT__61DFB$QR;w)@l;5kQ9sisKboMq zZ9x-^eOH#4JN?=y_1doQ7!$3vB^4}yhZ7)NKteRiFpqsXpD;TQ)o*U*xk~Dp`I`z` zV>K`UvJygO&m-WAymZ$DWEUo4L&8pd)$)P6fh?goPQcrrLl*KWk-Y|Npj7QP^0U|> zIo2V8Y06|^Pp~jwM*bHT9*xGkt`^Q#fx&ayuW;ZWZ`Qvkcz`=+z0K}sPw|OEwZYAF zs)hr3c$8`~{Zz-kn4<85!Z~mh+mbpdIdw_?5+ePVGPt**h8}E_+G6F%);$zF%@rp- zh#?=yi0$e5YmPV`&7(T2q?>UAT7%?&vPFw}B{uBn>z6nS1V%qw&*qIQ@485@vHe6{ zQD3T6{%IwA%D5cml3Teyu9&DR7TT9t{CT<%-PT3TH_(|GRCF}?LVq?0k0I0r&Gp<2 zmiTj!1i6(}1>5`C%3oHniZJl+4gdC-0&F6c?{J ztzYr9r&stI_l0Hh*WWn-W#Z?t^2A+^d6b4sQxTroc$<>rt?fCey3YO1 z@?Bv}byjt-YnLzMz5n01{2I@EppVz8IcpW;ngDV;6!7C8u#=5)%ae`m8;*g?_Im## zrB4YRe1JMkabWIvNb@ML=!n1inH2;Na1E|?%BSX z6yCeCr(YggIuH=H8}fsv%H6J_OHnm2C*M}Pr$u$1zXTyB8V9cK+^u!`pMJqay~hz^5EbS z4ckAM2r=-Nl)q`q*nlMQqiYD-CU;L++mII=c7AT`rwrw)Vexlwd`tFpsr2J^% znCYwE_K&C1Cob9*&;#k7O3YuYr}o?K9%m=NO*pvQnZ#=;ewEg15ztyNX=mhP4Ipy8 z5B@64iM-cCuX}i(zVSFf_)%=a2)%zR_!m|x0(p>D&dLBuXk)_8W!+C>I2-rE(Z3An z#G0}JF}<{anEQn>Ap<9KKr8scGv=I6A(f8qn-AQ44557ruJeewM4bg|z_s6hMuz!+ zQMMv>C{g$3Ja$XT5Dsl%OyR!hGE%9A02;B4r{YN-P&G8mxlB1=Ph2GF^2U|WOAR~x z(w)8|9ukZL`E%UN;>MK%!*z<{v@0LqGY$ zBW!X!%voCZtyouUQL)P`PJSo7DJaZTrA%Ge&=G}y8-yNG8aeuon0)*vLyJ5TUM#X? z$eVfdJA?8^Aaz)>Y+0F+XT1c2-)`bHg#mBnk8FucOqOQrnLWUE7VbnVI5X0Z7y1D8 znv53#`f+EUqi&TaKtV2x5V72W+uCiH+rgw@HaPEy*Ri3KqA@Q_*VAjlz?24bdTel~ z3AWp&^y|y_r7wJVy(fVMehL$hBtA@?*TXdLB*7bI!MswrBYB-t{%Gu7 z1(U8zCq*w1U98cZ;W?RML-y=fvb@grnOLPQj}9VHjd8z#ACB0kDf8<<+sH+jH)AG%rAItP=!_CoZ>(1xi_8(IBjAxka6}I)3M)6Y+Z8Wt^U zqOJfH9!5nH{ZaC8T6NVFpSn2X zU4eFdzPi5H9XqW&;-$I|c-GAI*ZVWiuRA#fzMej}Fk9G_UT$X+8KTO2@ozI4KsVjx ziwiI3?)U)eJ|i>#^hq{tlDhb@lZBiU=H%FiRH9Y$3LslEK=45NQzHI|vh;iakzNr1 z|9-8e-7|OH_uHRB&3C3fC4Xw^GmK7y*1CmPyI!Pp9OL1&F*pB{;H539l&w`Q2pO}8 zvtSAvVfbF)ovt3mokw(W7ReO}flt4}eHkjc^=4T(DMCDe^NJ?mW%ZDGWj>IQAoF{9 zm$|D{+m!u2AwLVx9+d1 z_3Yrf!Hu!b^xDZm0Sl!4WqlVIz3G5R%8ygpAN8CJ_*ybg&qZ$SlowkWaA-;q*P=`a zgXB7w$r7PdXHCT@RCNmq@Q(gKnHm%#iAI5!Ue(7%h1&%0N@L4R1=QdzXe89~N-`-3 zy1^wtvKv%?T()`|YA*p+Ds~`;I%XRkc1M5Nia^&4eo~UpsNXq^6jp5Vr=poQS!LZD z|6&q6m7NJ^pYT&uPtrAK)%z>Elb5dZL1>*?hZVfWb6q?}INFvNQc{Pb|1THmgw zVOMH;3EZZtr%3nIs{0zXk|2_&A3!MkLxqpy)reb5J-84NRfhH9B}6G)Vbmz`#t@Nr zkhMDaTmd@z1OM6RCHli=1eYnpwYC7NwITz% z#f}uXKDRV=Ds1eSd4D~ez+7xt-x12fbL0!K6uzupf4fZV+t-&zLgl^Z1WJE~Z@gWn z+$=aGA}7iIMcW394AtY2GTS>`xf7chz{HaoRefK3RD}=QdyB#S%_v((Ti@BD1MAPQ z3T1skn>I`6EXvWIS=2}`JpGk3&+*}vyM}jYs=+tQ=Hh1LuMccVt@&Imp>CHSc}%Sz z=OLfzLmrB=<8W&}q_#*PU?MtLMEa{1e(Gf;0baG6W&u2@Jq+gPXn){Cw2`Prj6^+l zglndRo35y1Ysyt_@z4*2$rR5Y0bPTTrtrK=}vfOOBpU(Imbai}#21 zk>jPyn2aWmVAk>>R{rt?yRwX|qKZ1E zqT8DB1sMse6rQJy8dJd^0G4WK>L(9*_ghX!IQ`>CY8sh!W= zhE4r|r|i^%LFq>!0B&H;G808}O3?ka!4e$+Bc8CV^b1GUkQ-X|fG*Au4n+7Tp|iwG zLN9&Chrhxa@7)U{ZJUQ5E6qAsE!RKMQPSO+B{EG_3+Q1H7T62?R}TFcgr=Bjhzor( zV&z1MvFzv7yRNrnCJaOyV%59$EQ8I;IDXc;CHfQijp{{7-Z-FL&$VxM z3(#WRFrgy4!RTv0b(88Djl@!}re52(Pa*r!Qd4G$Zt?C8fh8|m1OqZAnkoJgb~|_2 z6U8_O(%0;AF(cjEWR~i`nTf0)WDjL-&GKzn##2XUjK~R}^hrBZel(-mtX(onzK$(j zSPDq!8KwifB$D1BSM65E4rOpfJWIF*I?z%7bCg19ygp1-e7*5H9xZxC5KtL~VO%8k*UJm9bs)xQOBUmO>TBLSh^rbo^GdYihh5viDo zvod&*u7q_=QYdwcpmIuQo z3f46e%aKjcA_f7E{4n%>*u<@>3$+6g`=v7j=WE=D((u&ex*}LCb$YwJMy*tuH<|)tlj}QldT|);f4||@>U9>opGNKVHd{NkI1j}#;m9iL1}g#*%}uuw<-9tfJ>Q?> z0kgzrbn=-vT%lkIlD}_(@V@C^NM}ZGOa7n_OVVw|=p}xHnO^O^!>o<%z7f$By2;KH z#6<6Z_IH{*&A0Z~f+tG*BpeZP~=ISqFtkWkqsjh|{}UdvV^o z+S;?}|Cdi2l6daha54ggw7s*^@U|VhaB9%<85|~I zc)cA0uc>W~jZG;0QeJ3OaLa7R664sVrOCiIp_1Ya-;o3^g=2SSBu%B?8IZ z#n>T^uu9nxkB+E8SK|QJUf<^8K3Zi{YA1SjrJt{DQ>oiJmkXsO-&fRU<{Qu>Q#i>L zh93$%gH^?U*(9bpr5&(W*gCs8ZmMh6;&d@ETbDP){FXA#C(^AHfqDy0j{BBj8+UgRe6B5d+WF9>o#c!G9usIFYQTCw=_DkYL51#e?QVK_YM?@DBB`i zJb8;Ke#YimdEApufAU|2Y#H?dKBvI|`@a%5^i~3LR`?`F1+Ivp5)u|Y4U-L+fMZ_+ zoY4iT;5p+`^frs)2tO>J+K}rnR6an<-M4*hr8|IJNot)xsU)Rji)?fb0Ci}+?y?P? z0Hjqlu}eRy1OAli8Ssphmoc) zbpt87^_Qyw;u;0tf3D)!EfFTQVvd;JE1(N#QSYuRX6j@hTJzwOtNS;juqyfgqHQwA zwnAZir(EF-$Z;AZI+_(2?PbRo3rrFtxctJxcO|KVFSV#qCu;H99`l>PW+vWD{A;3d zFmTSo2a3jsqQ6=5Awtgf@^3)8amo&~iYWI>ykn*8z!W~tt3wJOhsOZ}AEN9BZ1G;H z*Hbi?(*En7{R^#|`F~)mp}}<0KUkYaLidzxNDQnoy%dK7@tfj2RRr-sP-vBTRXPj} zMMd*w_`Fn#kI)+qDyN%rh1%6$^vXBpLnWl74Z;sY$%uE*g;}cG;`%0aZZ2RGf4`yu2`E&fI^uuSk+a}}z za@l%ltxIl^>`VowJrLG_v`n7&mfgXmMyjopcMbG)&HPT3t@zMwQGM5V`KkWU}MPA+FpzYS?PcbP0q&i$n zs8Sjz5RLD+J(BCrN%b2drq1J=J~cN^bOG>M%gi$Q*sW#!-{jrXLi83 zxB;Yo1 z+#S7+-{Z)X3h^VoOhCZcghvP9kONGJ>w;M#FYO7P6fq4P00cA_dxHxIFwO|S12#_` z>^FYYf=_*lFZQ8BSRf6O%}`#O5b+O{0cMkcl1JrE& zl)K8Q$IKMFS8|uo9~ni_P;nf4wK$pfxnbP2!Bav$NCSr5!wh_QH`%h`(Fk7puDl#A zYf2+wtGLqea;%p`K)q+)u+yk-(Ha#E;%0^`An~bLT8#MVP@ioGDqFoBIngvObMx`p zNPqjSQ1hbf+G;WFxvc7m^$3M2ks1^a-+8}6S;m_C!APi52pMdY61%#8 zKUco9M6e;+-F?`_5WjLBpCz+Z2%39_&#?k$?JJ4jNd=484atb*R(?4A_Xj2I4c&O+ zoe}ux3qMog*bkcEuUDPPl7!M>n0hzAg{Dm+tb~_BYceY;_RNVjYy8HmkrZJ+JgZL)8+{N!l+xL2?7Y7W5lnAuM| zBzecMwYnmfCFIXCy=KdFrK0c9+I3vp%V}}W=bfBtb^$G_NiK9i00QvQy~6ZPVP{Zh zeACkx%1L;|5d1P=%j!k+NDrWjeaQGDcd zY#lFcSCRg)V!K+B1CdjyKlPX(TP2tGKw9Bb0`g<&Ul^P!oJ?bp(MesE*@zt)WK~5f zM;2T^b04Twz8<&3Cw-VHx}7Pi->p`ZAN|e0w!v#D@64b?shD!n{4E>Z107J41W*!1 zL3;iIt9>k_5#kA-I>4ogGkY&>)MGBOX3IM(8Iq_6c;nA=hx!`hswiMHL{q+P#F2L% zbX_7kDdl-c$Q826Fu~d*_NX87Mr@Na^UN0kW(8daH8Zw_OB_2-X-tWN-$JikH8G@K zg^|T&f+eKJ2dl|M_U=KayU&Mo-xsVTi3S>Q6}@T(3~n}0#N2%Vm7Y*q^qf%nATqh0 zdIBBI2N)}J`O!fKkk@BcSR?yKZnOF}K%})FlXyoGKn-%~zGl}#CuuWRDQd&7I z9;BkVi+Lx`nwNVhn_YWF=HohNzXxFhxFqcmOr1!Paf=aZ*O8S@F@ zF*(732V#dv7mG#iX>S#ql?%V>WQ$wNUdlQY-P08pojb|bVGhn~i}7-{tdGl>lsGgO zFXFhF=-FHBPzE1jX*&4jasKWvxi4XxG|$_gMewoq@}(O(rUTtvOJm)&WCA+QMq0=x)f*Rt1153-0?&KIXhh8 zQ-L=4##qprS*+3XPeTm1#DB)}9fBHHWe!92L8`;_d34t7AoyFXVx%Ra&87B6ft%Kk z&$dE#wKJh$da(_kRoPFD41et6!6uF3P*!@gN_zIGrbtN7;x}VqH)0O#b#?OGHZZT zA%Rzt!KEi7(QJ4aHjdRVj6~hU4Z|S5M-ftcpqhXi@70Ry@P;)=DQ?+$t$cl)ZK#an zHzAItm-!b_6e?4p?|TQ=|6(s&N)s{BCNyUHC~+Jg(u+^`@S#szOZy@=q^Htd3{S2L z&mMIAJhJ3Awhee6pTUGRzHc+G;;UjJ#17aemG2ys>1ir^K^S#S=m2L|c{JG_O^3{1 zz{}|c-v?K>XgP?^(nG39?3Zb)waGHK+>$p z6xS&6p>Hwc*DlFx)q!?(`?=zHr!51<%bH#GL`XaWo^Dg}hGr=3<55NWf>!%kD$z>= zm4Qn_ddkQp~@D(ver%t~e!m?#O-ml)i?XY|bN&yP*cE#su!rg2N|5U!) zJ;IR#MVz3;U&jp!)DtGu7ccLx3p2h(mx(+UT<4=GmNw1FcZIWC$gTr>Rg3n4B%dUKX_8HB4D( z#o5;E0_!SQlnVq3kJM_oan}Qv*?y+WP~iKgRr2Ll(q(r46A+5aQLAVB&|E33n0kq~ zmGc@yI5*jgLiH@u73}d}rIs9`^c`<_z{P$|c%1(U)>Xr=W{GP<;Uml< zTO9VfZamy@vXzOFc<}uulA+$G9TYZ*%eklLu9j!fJs7>xN-G`}>lfSngu&rM)J_6$PRieqzQ1sh%~0LvJKu z(5LpFyoj7>CxMHQirs30S3=g_F(GpAyMx8@S?Sj{iccAOf%@a;rBTAywCh5b-4lY_ zVF|D8psy5sylj+FV$xKgZ%3oSFW^Bp5%dso-WA@dw}#jX(4z)mak}~FpOx@cw}}P6 zTZ<}*AC3!oeRbOaX9M8tKJ5oAi%AEJ!>DHvK3O(YRmknc7FirP8eb!;7% zZ%N=3={NJQbvEMr>;MBUW$gOVW~DDU|Fa?nXfvm=;-?GaRiphGpS9Ut(HLQLIPu81 zTyt6kt2Im;hA!TuQPcQ48?T78V4g`YPkH)+C^ifxwl=(CiG>X{SB?K*}G7?s$Y) zBAGIAp3qoYm!i(JW^xw7UGAGu5^%wQW3nU`#`TRutK-iX0O$_DIVzCQ$<912A%CDy zCsLF7P(-DKdOfrLLI2M_><8`ZXG(wn%v^OFNLVtbO)8z5)7s1Zac}FPiG-`zKzLmw z_h>FLe$-at7h-_9QIA>}+4krl=f&hkODm3rlL^oZ5}3$(?-eEXviVnTiMW4Is9ZlNhLFJw3=mY_%evZu2JDoU`NQDDLhG)z@3vdP;jjE{? z5(MTSg8+z402t5>U@Gm5{J1!7kTY4Jz&e%Wa$wo={tPrD{sxmasTJe8s1Gj|RQp{J zi9O&R!X6{_)`I3!ml6GNX`n|y<RMY*3Z3RZNNkEbUPAC)r(j#sqpx2f94`BN$mDH@uCQ`|C4S!Gg9uNJq+DlAAdC%n~99Fqo*jE^h} zs)_&-+>)6f(~Jjb=WFdzcXK;k`+a~x5KIl!3o^$Gj^uV1p^kB8{ipyJoD)YI<>>df z;i223!_}nD)6v|333(#Q#TxEGf@LG#w4n-roHl7?B=j~dQp5ER$9KH0g7)IX6(dv| z67@Obm3T7|4?6TQ_!ENe_0B2vyDgup!{LKtm$t?^DU$eNFA85l7iB#bYJ!8$m-a* z&6ov?*!DjnZgAXr&>bYbw$nFrCYSKNKjQ1t_Fo6$$u?FJG4zp30S+mQ7xVcuosaDR zV6V2_9vG&bLl(#;AJTt8lJIps=!RI5=cPcczwsTq_9{C`;L8Ia6f}X}Nkm^v#{`0rglad#tKsP!4XLNJVMqIx3i?z zy2<{UO`Ao|yX~s!j{Y&iAby1##t=Xo)mlUw)Xjy%DX}|dais{{HF&z;B5IBYP~?mQ z$bvNfiA-vRyL19=R6CsP3uv;}J~6KJf_CXHx*N9OD|7Utp^VoRBGc{#y1yxX3%OSi z>Dj%Kmqv1Xn8X^lvD|0B*Y7Kb^nm1U7ujP%5EbVOiWSgRFlbXI4g-OesYVl==$;dD zWTai$J%K7n!*PNnaVhdBvb%#hKhzL%*7uwX+bTtV0)w9#C^-E*jJdLH==5)vEFZI< z=^QwR^pMJ%=ZOkr0q8PjjrdUoXD;yW#^FZ{`^#11&_a`%r& z^>$wy=)ExeVt)~JNQYRB<^0R%rm~g01f&xw9u3=2<}h9p@MyqAV!uds&|d9l>usz5Wr5(%&^7QG}&i*`EAe@A_( z?yJZ$g%<-8G8a=~ym&TOZ1~j6DgAP2JcpK}C;3d8IQOjQd)IjP84%V*l>CO+1W1{_ z{Jn$%8s-Qno(AYkdrTi=LMORs!i(=TGu}2z-pcx&L!9b_8Tr`SCPDH(Is)BJZp!Zb zc`|-Xr)Yni8+Kh_(A%~MWLz(H?0&$T%U%KgIWzy{!bVPf-FPHDY1q-aTbQk6gh4r4XGaRgACpJ!?Kukw>iVgH4gT8NYp z5_ws(DXO7cM{?)~Gvm0%^DN85T3F|v-uCJ@1VXR2D~xN0O`&l`JwXXzQlyNJ>dSh* zjs5I#P`k2=8msE_QQwEAE(%N~2{y&y`WE-b^zO0XGqxn`2nA%tfWo??e`P9KRsZ4F zvB3@4eiI?UL0S~k7Ki&RUx40A>}x3_WbPVKYGCa)Q_9T7cU7!@dYE45Yjpfa7G(ep zQ2jth(=n;y?gHnk=1mu;!Ks;;Jf+^iGaJ%+HVlfcCVMjul)YVw|RF7 zyU%0Ib^GH@hPZ;4V+=^NB+6VjUND8A#y4FD$9vZ5T?z8hEz>GMM=Og>N_D(b{d~<^ z?bT1|w42hIB@XxZhdr8|#17{wuRw7xUor8<_Af9|&0DM}u>nYelCQF>4cF(c1{?8) z9CW~bM~8eNM>tW@p?@yfeu46aaoM`5Ph`ondQQf(k|}?F`Uk5-N zp!JnCfK3jwKuK{potdq%gnGpAU!W>IpFQ{xz zWW_P$o}V2S!y$mXPg&PISoIbZq_nZVXadK}VNJb$A0wS}q#`pg!|*|bKX+Z_`xx1` z@;jE3*MGm_4}B=FuTQd0%zYKF2(3-;(sP=TpRtr&2{aM7=qT0t*-mmmc%LqPQK)V& zLrg^V6?IgI9o3@M_ykS(i2~SR9Cs!E^%hKDHs`=mYP3{Qb5j9-t-#>&D4Ll0 z?E501sq=7R(|`hS(Pm_PqX~eXShk<9FB&4YgKTo6_1y8j!;G`bCyYz{6wd{o?heyQ zHgc+{E`p+m08!v1{yb%rj*LPHU#SwnLk9|3 zY!8wbqtd?A?#xP5R(JiEGNxaXAa%s}T7`uz9r4`DkaFK{0=yvq6IF2ngc+=Xa-cZMF6Ft-tkk8Jfyq;6D#L6yw$qV zl27p~Q1D|)ftxoeiTS<9qVBjL+jZje`CEhn;IflCqI`(AP$2Axm9R6rpV_C?D`Bk| z%c4D>XNFv{0qVm=lUoYugQl0+AA_g^*@>*Bm8m;+c?ny{{TR!=5g52B=8@Qv;+rFk)k+tzz6~H6%k^b0?~cmjGVpIPKhm z##t@Ud1NmtH3V4wMLTQnRN;|As1~fX6rz2O$z`lbjmVc6!J~84+RKNX^jE4wBBEBy zjT`eESSP8Y4Mu_fkEF8AAPv$bARyf$-OWb2yBjy%(klTZ7$d81A_wrTskut{C3nV0aI6f5Em%k~ z5gxvZ823|7w%RjhnfjxnV!J|+zk~49NGl(M2+TR0T;P`)G|7ZEhB1IQu6%{vjj*ol zir92aJx&rFY{+k&xN6^WOSn(3@0P0{{?>~^=PPp6D~eQJT)#|Q)N5n7c4eC+34SZ- zmt)r^r1-38UqN0z!gv?t(rpiL$YeHD+dGMe5-a|3w}tZAC)dFjpJNX@I2-U1RxMuSslR*5Nrp)?c#$Lv#FbR|U9*2YIy zY=>1PW{swm3J8C2X4Z*#0cACO+%n;_fkHkxjd>?UHw7gaNuLOZR>ye#Zq`{(JymUY zL3(QC%RRkCfIWGj$U@4Qh!Ubv0E)*Z@gFN|8RdwphlJ?2NoXe`WhmFGaaV-%l_xfKbY^JFYVY~ z(RjZ-y%B03%glcYP%vA(zs;LGmCt)XQdd>v$9^ozXf-bpI_U)>E^iQ>4V2B?=x^0m zV)->GWpsI8CPA(mPwu&vZ11lM-YtiVELY3k|x^E56zhJ*_7VJdMN3pfJm zEIi0SY9(tIjnj;A)a%#&zKf*lqv$iytYn3|fa%9VV133pChpJ4!0&RjoFK1pUcG3S z14lP33t_3u?*^0|7p3V9SQp_%>NRDN)hAll>WSTLt+afXI3zRAWvxkg35pF{xUw+& zVHy}bV~&1~|Lk!VmiJ;M&+I@e`$?g&mMZajkpD6+$EfhzeTVfObeiS@a+$eKZ{U5M zO84J8*dpQ{Bo9iQHINE%`MMy$mNAU1Kk>(tMnbIP-+MI$xAt&~C}sF)-^9VUtA7Jd ztNHp`j+qogu$${116Nff9oLa^bqZ5nij!;15%NhnRXx{(++_LXSas(IJjg^0n^@jS zO+sE3(2dsA51|r%D;4V_-MRj=Pys#{<3_&pN1w4hsEGt&W}We%Nk#L%v#Rke?DU>( z#E8*pQO4z)j41ysa`d>V{V=yl)qf+Wf~uD;^3}&YQ)f{o7@Umhb-gQfv>Cav$Tmp@@~uo0*@0{0;Ai{$H3F!5gzTV#jh zF7a!rp1p*QY2dFIU5LC?Celn9DWr%kzEpU_8Hl7(_};{+j-4PKCik#EMwL!z{*m-h-En?!?2j= zTA}64c>O2Om_e;u?0=r6RpjNG^t|(!jg_|vUFsBL;2hGiek9(UD=WY%79vi_kUb^Y zu6>Q@<9!u~AX{Rzt4JPLOLg4uF3_m|umTdNUSTzOgjRtuBpOd#FB%sqQ22sYe&C%* z|0*R%fCJO=a=(<1a$<8}EAtxe2fzZ~T3O^ieVQ($ z-{=ExyT;YR^nafTMQ2N{Hi?^4whmw)z{edAt6M{k9})3H%J( zV?Ux;g5tE%_>APn@0K%jUn2S?i;@s0x14$IsoPDeeC3h}dpdhx)E6{~S#mFQkh;&> z1#d{amdo)xE>f_96?JR*6az8fRE zZ19oVpOI6GJiN~|g}W0kwxd3Zj&tzG?57K8uOfJZ)Qtq1zKDDx4Jh?R5<{|0#C{wi z-niOkyKUVZQ*Bx@igqX(Eun;pg?P7b&J*4I*;?C)#H_}qkA2sx(v{BGP&~bt->6+V zDJDMwNFw(NLNl>H-zC>`yjbzqJ|{g%hh^pEejoSisTonb6&Aa*W8aICiQKPddXJ{- zzul*V-3;OeT})Lwd9iXkoqL3-bpS%3Ux^J#YA;#AB~DH0?Rtq4kJ=LAtp{fffH8<7 z95gm`f8Kqc+MgAZEV-f1QWnXfA?%~oHAd$msZ4cXWl<>?zU;+8cMon8JRHT~JR_iVAB$E52 z>J;XI=05wBQX@8mXvKubiNF=Dy{ymqm}+>pOh!zwg;pm^gJOniN}Xr+#JWz!@&n86 z$1!~(!{a_a7IR9j8DBM-t~5ew?sKj&;9Z~dtvZhGEz?#hlL zn)*_6>l<)ln#kfYVqJf8(ch2ltS685hR15q!DLuH#8l>k7RJ1i0JNsndon-5D2abQ zqKKv-I&H9~HTTrfXbUy+(2wA5q@hPkgVnh_9d?P-Mg6OY)vV_D^W1gSR46#2ELcB+)VooV|y4PBuTEY%|CC!E`*voeNTi@oqJ!V*vOH zhEsRH{QETUeX@RXe@!vAR9z}T^SzHJsJj0>6^s=GxHZH(A1Fed$h~JLd0|bWq&^Q{ z50V_?Uit`fUtudc(^0h2jW!`mqabeT6DDI$MenjE<0382A^fKiV%<^_j;`V={Z27& zGCW0>)fG*UnTI-xOTj~2cC48W}ksmP+8*qBYT6lya96yEb<)jpuhIUB~cV@Y2Qu4 z1M`%YJ;b>eEG3fu<@{K&R6at6`rq8iC+M`Fqr59-)GUmZGd!5{j($GS`iuvth<^n+oc%8Xupr@S~zjdJ8OBOnrfU?vTADlPb0ez!+~ZcmoN+{xh&(YLCo&~rRf`Njl#r; zOTEoEDX+ZYTGCUD^Yzdi#MZ-N>aKIMP)%(_FyH{CQ~dqR08HwXrHhJ8>Yt|C&CGQH zIHdF9+-YKc*kNXgWgM*n4#`BXdZ9|UjQ6nQfvbKW|1QC6P<<2Zz#**Vg2g-*!Azv> zRO0qLC;Xg~`q3D|%r+I7_E^VS7PjUyeLFIlvkS zfu%qQu}5Jayx|GrjN--NSbr8lA8<#D?nL6Xn}WSR`8JsAJ{4=~jZ%gedgTjdId;`| zk-thJNsm&D5uS_(dH1DU2uj@LM1cfTUC`~UY!QZ&BrH%Z+#s6jdhr@;$Rd4p4rIw~v?OSih! zi7J^QLJ$Y_u^32$`7=l6so753BEi&J$TUdjsSLGw)p|JfgTR}GpK z!+z2Tz@6=4n13!qo-9J!Mi3YdqHnnro&@t`GR4jZ39&H`(ito9P)+Tr%EH(2MYZS)&Au|}*IZ$#g?wz!bS5@p?~e8K z0r4HHqB(eZ*jLog1}Ldp6-heoHO5HmB!jZ`Zj$;xyxug!JH;(P=vGec za-nmp-Q?x!mg>&Jx#)Yx5a9^sa^4u{PUbh~hI7cB%W0K@%0#fhe9Y}Jsd4TH2oULV z1fA*e2E5ay`sd=g6Oo3Qj88wd(FE%_CbBSi5pF-w^!V+}aFk^SWCmu~n->`caKo*{ zZ>xZiE6rET(>yxSpgMIMV^=-vqW`IK9j#@?6Y1LHicMJixi~u2{@%LwnRT0+w+e(< z^dNNkU1Tzwo4xyyTYk++LNIol+oXYdbXR5T(`#x1;R6z(eL~0-23(up3bCrTmCDA_YI_1pOsdC2y!Wwc;E@Y2L1x4ELDKHi-&C0r3)w zNu~D%PYu>G#xP=d@m*Cv_Cwsx3YT{)W*ZNue5WUo^8Oz4H0-{pNNDxmXrzCspEQ8> zX`N49!u**6rig{2GjVbCl#QeE=3ii7{!4TXx=pT(|3}nH()5N*nQ*(1oI&1NtbKQ^ zC+mguqyIp(R$+e|pzbmnz7FOx9Di2>+^}>1_WjQ^PraE@rZcr$w~jf~XK~aS*!n4d z<8oiRILPO2#RdsH@ZiwFhM^w>1am?7P|X&RKsZf=Y3@2We;L zHxnXk7%l4{P~%^Yr2TFoN=|&@@Nl3d`Zc&Z3i3PAz96 z=pw#9X8w(RQJO!dVbDiDh>8e zIIr4BLy2Hp^1ivVV#A<(FoDxoa$Yz}<8ivwgUZPeLX9gqUkXYT_+W`a&Nvm$F<#@M z=?x;9z-VdZo?WLx0jvrpj{X!mf8l9snX?FcJg3$|Ce)W7o6JEtn^K9`7&z^% zjS-LNN+D8t6GD8jyAm&SU$l96g24Q&xe!)6Y%U=Grs+!>7RF6}N+#OwrGp1IoOPLx z0AiCP>o^S?|CN<0Vm`$)4(FA0Q6>j9(TCj;RCgAZS6~ze(wYmnW#d?jXpVLBq2VINLA~cU;Tr0eFdZY>eTuLi-#|ns|u7{Tj)mtI2(geRz zt9^O@V)6NfI_jEcgQ$>jDtYx}#P6$!Kq5=wTbozPyxo#L7T*)oI_YdtW?hqS^v(-3 zi~ebnavDGBnDGIil-_e$#ai=Lft6De3xQhHWBbY9Rb7FZXZ;#~K3sOFJdJFVplFpO z$fu-N%PJpDUe}3Qh0Lu={O88ez!IL_szN9`dPfQYy+19r02sd#$o2*bp?OfG-f16vVp92 zRKo5-y7^@r96CmL}WJ+CcudH8VQC(MHq#jO`m8m@k%*#yXzeFEfPYnHiC1#SNO7| zdNQ`=2u|%L_xhQ&teoPHck~CoobMi$Rsz~^nW%CZmxUQ%UiFKAtl(=diBS%%1^+{? zP=d#TZ@{H+`DVbosP|uFmRvy#53?c=h23m;o82D^vTsND#6m00{Aa*6v&+3xM9#F` zLn|y}X!3$<3ezTB$Jks%e}Hd8N?BFpsp9vOkzT(hCLpT_+LVwTggFt_BE-b|rLTX? zMq4bJB>l8^-r@+naQ$z7Vp_@Cv#@M0t9|F75AcZbURDtcSs6r*5?0HGYB96Hs$;kc z{gO#Kf3)pG0Ao3jMP9r}<_wQOQg#)hwjN#q-G80vklW*P;OjcG7OzW6`Tul+*{zw7(N@LWi-+()Fz3ky9owKqL6<}FDSQPFydel7&r7ivk>bsF7^z+&7Hzwb9m#I1%XekbjRsM}5M8*~${I}49L-mr;|TK>+On|l4RK)Qz6GA? z28Sj0ry1-xhaz7C>QxI%6tfa^bS&p1$<95}rhnwZ1~aqwxRCFW+cY>BIV<~q-XjNbE$-uq>SSv{BMPKSTc#PPZTmDCFYb~wpxa+J|ktsa`G_b7Ch4-1$ z3Gs#jb>_$4>7=C=4BU`jQ+Q_NjXvGtSD?A@rBs+x!GdR3Y3^xTYh~H@M;tjA7;|ZC z`b*x>fCl*^I%VxoufnL5l7}Y8K1d7dB<#G5 z8cw|mA0HReE)pNh;=YPR#oc$fs=H5W8(_D+sHC6NMR(lJ*0{+w6}t!T@n|TzbX=Tp zS2FF2X8lV0gZcXby0v&7N8POwqmT}U>@myq@*mt zj~54H%-a^{UaW_C7%ueoZiGz_X+s@K`}&rc>eo{%I|sqiEHH}bQQMaL6(r+_&eN3H zMY^+H^?c;D)Fy-gNpYy!}OzQh64nb?lKY=His;Zj$G{orMjK7nV)iG|J z)yf*#nHSWabD6XQQxjQ9{!LO`= zRp!FnPJZ2MI@%p6fgxm^|wJlDiNI4CcsWG*ZIe#f5g_X#Y* z{$qWHT~DXj|0V@oO_rz;0kFaO+vP~zkJlkiP1yV2eZ>twBL1jh@cBb>3pt3-$-%3M z(6My0$DExXv048xV}=_h6hfTU;d%5m?U(M#l;rn>7av}c_ZRq&rVCFwhroNvGDmFI zYlOZ2pq|?rI&4FIhxGEEX7%%=P~j)PG?90oD8agwY&I?e`g}xF)e@U!d`T*IYe6ij z`78vvGh4Btply14>U~*(hHg#5z8qg*cyNc^gb5pxoss8t?`#CT7~YqEW6g7YL?jtl zwIndj;L*Y4_Ls4x{`9Og?tJ=7UgHRI=fco`4`(r{?}%5sySG4g6u3Z8sxm)V)|shR zKqMb;(syTwLr^!?9u*_N{sP&Z6~SKXiXsri8;lCo$_^}w#yJiXu=O#2g;`(VJ>#Fk zw*2j)@Iwm;aheq(!SG@UK?i3M2 zN}jd9n`#7m0)cr=pi2iwZupC;nSJF-C%>xMMgEj`22M%2BIAGRsLwndapSwptU&On z8jct-vD|7Q^E%V|RVzgm<)|P9VB8u-wX9AgYG0ZME92wT9iT$3UU?2|4H>Pn4MaE) z9xWeRBnit`DhrXK+6SF%%CnF5|P$38_J*UOwdDk5Jt!67)r>A(GU^hq=9?|=&GoG9G?}Zl#kPiUUF z1#F);H6D_a|2!Xn_+j9EQnx)haTZVR!hm#9S<57W-p(a53_Op=f!nOvH^FaG+V%^7 zZ#JG^Srm;KYjds@&l)b<@MDTtZvByu`9dxj@*8m5D3K{Dn(5~b^EQ4&yif^!D?t`3 z6*pulqoW~kIK00{1ZY|Dw76U0 zaGV`XnoA7#pg>W=r?|wP-{~9tr-C6i#b?h3HvdI+301guG0v}okc{Mk12eL3mT_23 z+wCQtWLNgwRBxj9iwU{NzD3%d{9-KNQ>y!2R|%I1kDxa9yIZPx2+m?$7bL_ zVe9&<`vvLvUOF1qlotF&ME(nw`di2(Mi|yt&mk5Dv@$J$C&WCpR^c2P(1x8y~f(c+cem3~C zHBaZ0U(SV6P}uY5G^WXRb2^dNg;XH6kI;{ZF|^vdj8s|mp{kzM>nxOBzAIa)C`sVDId=|zMai_+M2kcv z!imYWL@@K?-4KGu(}h<5T3~ct)ZDVzRd}Z5SSq&u$*EVEhi+On6dq2A$DaFa zqbG@oPsGJ;aOB8ziT5TbKA>$61tRkR&GwJZy|Ul0WKO%lHQpx5(@q0kq zZCo^#F*H1dC_Bf;Z0oT5!E$U>3a_Q7;qxgM%EHiRlQOF0ZN1`919FwP;Xjt34S)7=N6ZuziW5WF^HsCL>lCX?g)tuZiNpc{Ckl|G znJqS(9`eqZksQ&xS+7uStCk_Tk6fILH}!8G-m7kS)?nlFUyd>~f_lW%dxeP`5=yZI zR-2Y+z!Jfd4J%tvy>HM|Z&p2IN+Q#HE5aqzR5gHc7jG^J418Zz0!Bh#8zaFWmV_O^ z{z0ze#3F5#5DV!;ER^D1M~I7=41K5?3u70sMu7ENS)NWzQw(p6c^lT15`DQH;;p1X zu}4oyi;FY0VOp*aqhn%m2AQd%&n~ACIG6q4qrol@2x!y_C&8)8m=g@*^;9U*bDrxBh$!asw85sOZ3>;5oR4V-HvDPamMi>;W8s@(m~n zT_@kd#I>(d1Q4do>BPcILcqR{Z>>B!S2-3lMDjhjNJy? zSmcL7FG(-%f_O?45SbC)nd1b>RTGJ~BGJ*yl79zmOA-j^fyU_(qcJ{GeM0rd+6jkZ z)4Pk#!*ciddFg8)$gMjY9kDj^m5W6N_7GwRcvJz(CNN>v)O)U|R`Ai!;9HEmG2h_H zPa9}t@o`mZtp8ki;T6fjynXl9mbPU?Acsa2sPh4V8AB_Y1hq?1hcMVlaEwisMLoAN zG7vz9TT02{zsxDq#N&n?fe~*A{|CO3w@Zlcl8h_p;m$56JxzU2yA?0sOw-Sa1*xRQ zOOg)=3`D*MEkp+bb3xRKguIH-cj&WX0EOw#VHT)b8x9g zc+km75J;~%Cno7|<+D)zV?A=S85*c$DCbGqIX|i^$kWjNf(5afHyNRmy03!b5fq#5LzcVajv@7??5c(E;jI3Vq$tfZOz=Nzz^m~OR zG+X#*VvkJ;E0SU;aFpSv;Xh_4%6^ZxB~4pFO+LowPv<543EW~7Z1)`feQ^bc7M9cn zo7G=}k)=uJOeES+8J|@nNDwnwcvWKoH-cb_BomWeE>xal9eg!Xe8p=Qf`<2?QC#)j zo}4`hi(VR5Dq6o!%G>Yq?VkcTACeGbqESnz0HIPtHbNpZhmJjbPirS>-Pi<1Fl3Sy z08@drzdxA2pZSDN8vSIKfLGY+w00Fwj#4$((cOL$Jd#Gu1mV$@%jR1h)|W?yd=_9o zN0?c0c1iSF4+Jo@j$glxkJomIeLf#tv2LUc<5e2;FpHZCd)eIX%^LLV)5IGZvEvudzXu28 zD*$81|&zQNu&n6a*Q#hZ_hoYmE z@o7p);BV|v`SEZgL%^FblO%6F8T8Z?fn{E39@8kEiHl|W*-eSAS_(Fujkl(KU;9@k z-O?|=*Wbgq$RF3c@-wJB!R3o z?Ev?w6C8K{8h?+(au`?$Xv?L9H5dQG)xsT_o?! zL}Cj8Ourlz$@WO5|L>wUVvoCE97Njt2nNs{gc8ywYzuouRUaS~0HE?sa|!7jw$>il zEyN^aocm+4(PGb6zzG%8)Fm$uqqcBrL{6lRH~+&(rKf&*u(R{Lw5f@I?k9R0fxw-j zSMby8x7`&d_CPoaz0d>l|Eh09cRFaLzBCcNDgHHz%d%LEwco;P_i&-5^|#~H!Mi+d z6xWOY@$Cn5`xpi@7d=%q>%`vyC7(#W{-qtH6|1-H`5$86I+l7`P_kYeAT!P!9vl&8 z26s3fqU?O)o08SPMwMn;k8U?Gl>BFPm*Cr)>nH?|+j>I#zTXh3@3r(zjNFr<4b+on zn^H1!@4A12V9gT(;MxN!vrM*-zhMFivwqV)uaLS%-`R!Q7AcN(yAgl zJc`)i9L_VYzdsxp0+G;X1v&sLaG6G#E%|5ijoF^2l2kg5eS)F~8V{zhRH@yO=GiDq zf@xg>%MSCN!~6wG_JrifMIuJLCMHye^S;`TqL&=x&}N@+BN z2|oQ*nXx7j9pN^y{pXgRjpfPyVpQ^s5k7WP_j#SlX_ZC}m|w6&o~N6I{+N$=kJ8Aw zooqhszPfz<{1T)lWV*3P3(H7>nMjm{kmr@L+r`o1{|@k&W?8Nqw!$QAXmB1Z zsUzVZtrz6b_Lx7ZSPNsrAV%YTC3w8hwL~3ERH@#a_*>qnkZM0_=2HDAr3@?=6{-UE zt3Ahx_{DkGQfhd#AI@v3JG7Y@!*sbf3f<3@CINzx7Ms5HxodGQs-kx0iPuji5Ws>v zSAn`J3T}4cL5|6ns=Xcpp@i|Zi0WZFTk@Dbko6AICWa;pX~zo56e%glzAQH5AaWO! zW`WI)#1ReX%CLfK9jD1>ROTmivv`Tj| zU2_z{g>BS=)NlQp5cof^V`mJHSS8VThuo_UEA>Lt?t;q1`Vvz>6P7j#gp6E_lS?qb z7r3)^^FVP885GQ35~+?cBd>tbQbT6_aVq{vXMJY28#j|A0S34}tO~03+4;+wF=MWq z?qJehOG>J(9h8T2#7M5g>+F;RVk|Xb!{WY})(Opa=8*MZejsKNET)!5$TR(2X3N=+ z^7lv=22j*5pOqw?DRr(J0b=+J!RkGzUQ;wsGw8|=n)Pg(3gxpaz|=06sLDN+Q~NuL zDhzO>6?aPl0TB+=Gm?=hLFrf;!tm(8)R)6lsz@f&SzJ$k$W#GZGx?RiY#YW9PVX68c+&T z$N5lD$6|D;1(90dT`d!_B?OTmr-i}shz%{8ijC^HAvk!zY3zdAjHc_Pa#xuk*OeX9 zS1PGCpz#*+=q;r2$*BcgYNi&WQHAk+Kg2gKo;>mb&tpNNT55>gSR z)V4q9c3Ws#x;S+i$WR87QLR=xlCH`EP?G|`ofebf<;#z1kC~?@1EDIrQUYZ2=~t^g zF^(@ES8?V%1-XuqaRqIDmWv~v9#Dhf(%rBb@r~}NrW*__42;xg;jgg~#ewfApgo6T z?t>;|o1e9^>wMq8!ig=-O4(Za<70`ZdXcp2tjP1VoBzp*^R_t`c3?SegY7l0McTq` zyy%*WgwASsSs)*QWSprI0tEojPJzZZ-o14kEYZa&_3`D)*G+~4DEDFu4zwS?Nd@&7 zeZb###(f@NHV-~z0bT&Ey7^0nW1XP!ezBM95FJF`E^S1F9mTQJ93}y-Rwvqk3W=$i z-1-AWBZ(BUo4BW*`KU9l8UM%|QaT=v8TE)!rSVnyybFM)O`BJYh(9& zxqpH|X>VKkO{}O96#Nrma}e1rb4ByjzPKAj^H~6#H{b^43G#R4#=mJ7n4iK7=^BNf z+AJxz2j*qV_enXc6Uyp*BbakEk;=)lm03qj3Y1V2C1*(5T0gpKL8GH0B0+Q;e5`^g zIii)+<06}lDb(E%G z6H+C!H7mPs&YfTo?F3d$l8{jjV3UHWg3YOfxPd$M_xba3xEZ7XwvdPX*4=NHUi(_3 z^Ang(L5IqfFA)_`K5W(3u()7Nar!Ycl~FKlU8mwJ_>1!=CP!gL zJAPxsf)PEl``=!;ec|A$1r@zb&(@-^0_k|%4R5}nmS7yWuBmxiE-a#Zo001R3@B#yv%p^HP$^kuGy`KJhSopU` zNtrn6X08u=JU$)?Fi`-@?|f<|t`j&$MG@JzJji3I$uF&3oQy^vzmm(t*-1>ncrN5f zUW?v_DHP2+^ZA++M4#D$bDU5B{d6DFuKnS8ih6|#SnZ1{pG(W`Cxzf|m#ej$_n^L7 zUlgflDw-{X2QYxMxM3@Hm=7f%JOjvM)j|6j@xT>Ec^jC&sL3|koljo9R4hxHAuJ7@ zM@2{o>I|()!Gm(kstfusR#&_yj5dgg@#BknNiKn^B2U_ZlcXK`p0)t127qyj4U%jR zgTpP6nbtcfZg1+11bxe4O)PsO`)_Rar*|hTW4_7<%xu^HXtW+~Ypl1Wn`rXKeGvk? zDCGy4&^OVvM|`(?>3+tlok7}JH zceT|~C=t~5#R1zVWN2BDWy}fO*b&CiR~*Du^^$+G|za*qycCl>57FnTgFkcMc|3F zXuoFc!zE8D#F$00b7++1AZZ6WI&yq0U9MIiEP8gV-1r%IqVcQ}%(ZI~;>|th;=5vN zwrfJ5c?roZL)J`l7p0u)mekZ0oKWi+a2X z18jJHOg{CbT^i^eJ<7MwUOaJO7|oqH1H99IJV5{+3Fz!1ma)ns@N>J06u`^0kVY#? z07+AB34;YhE^5QhpEE`HZga7L(fhD_vhNd+!6yH@dm+=*p&p($-v8}R;{_B*gVv=u@2+YYiGTpWlPuBA10_;_ zbT~_7vkt3nL(|x&=1<@=g$TY99Ma-AK8A{f%OFAAj|%EJoOw0+Hs(*)P0VzO2mbJm z(Yejek!?f9a&t|x{+Z<;OF4dC{g$;ASoQKbP=qFx7{YWA*kbHbUubyKeN^2^&77Y) z_b>c(LNvr|CAI1M)L(O0ko{8I)!8uX%9{H!eZ)1nZr8b8r!gY>HU$=NpFv`L;oL`N z9atlZZyd8EeAM4PPsO7$xa0pr>DRlYN2j#Iby6)v_^|CJbZ-x5>B`J$4+y{rMn9?i|0lFW_lrAFU zj>{xGFN$Vn_yogv+v7z&6ACnhl5%hi0-1LCB8#e(FvkK+Oc9_C)$uf}66@cgVfXnk zJ-LuJBq)Q&*~3}ST_ASb;&R$4QXlg}j3On$`zMBg*?Gx;p%J?#`^8~_JPNi^xN^+< z^sPw3%vC4c=Q6_a3Y>ETU7UaIEN(O9wGRkq9i?{x+eC3S*8qH<0!fB=6FwD6+RaYJ zHj*)|)2Vzs`hK9Qb=2=ZX&S$MWoPOEcpFa^y+<}r&SFDQw0pG5(7#qzV3IYnQgMa=ItufuMw4L*p`Q*@ZN33aOO^MO zWMaQl6|r>BM;VHt)#zWQYKCeg_E+b;#GKhBXvcxgG0EyC0 zr%jL?VA|xZ^VCfJWC=;h6Wt957eu)oa?#B9tCY0UZsz8*KbfoC+1MI&D zl~Dj}fY@n|By0B<5e8Q3qy>{dD&}r%s-CAx0x1UpWERBs%$`fK_wfB_tGWK(LHYGtI-zq|c!l-kERC0bfq(Zs}>r7atupJ@XWJOG0yE3cU3m!<#m^07lLI6!!6 z7Bs!c7|YPng7iyuKXt}bt<9iO_OEnB1}agOQ?Z3n6h27<6rOmU`ppT4md^;n$bRK) zfze-6?k&g}fG5tJ?jI1INH|-_5#JWXg`jBv!-($K^|SjYB3)V)S1laVC;5rn3#`fj zza;?H%N0Ecfr}t?NFx~v!Z&Hp%62FX4(ncxg)x1>pJ6^){8dE+{t8`7Pw{+;cXIHi zIKE@H3@gwP-8`SRO&pRTSPuUmgnn|Zw>~QoHya3Y=Ml{O2*O!d`(O+MZ1{aKep|d+ z_)}qBfY(g?EJH&Lx+I1yEb)mc+1&}uh|9j@eg8h&Ifgu7n~LYiJ=Kl@?RhP#J}1=6 zU}NegV|eTzj{dK9`P^T(87^Vgg0{t)k`>r|@^Vt<> zATr{baX4xSWj*uZ8<-1y<>mgOpy&WN)<*b+em}bGfXO^gT;w$}?#kF1zlQbr$Bc_0@Gz*nhP5sHEUJJT+wA($_!JKIhZq-eT6gP5dU_>|kSwrR;S%?WD zs9_Hnj{q4#2mzfa(dLitH0Sv0^$Ng+HZ9-H>TB`nUFEBZRko?KRmp28Bq^8sLXbmY zmZrNhc&?K9$Ml@edIo6HRNF)2?V$hk(>4!w4HV|7r!m zuE_K_kC+~G-nj`EreQYH0*_*c?nOqOv6@k5=do%`-<}%yM}-xC=Q(UvIOVmr76)v< z)u%4PY4f?FNizc$cdmQvTaGzePE{;G+)q+DDf*k*kF)5kc1LV4f+_NEp?v?{+PIPP z>NE$ zGgW(2cP~**Z4KjLaNTEYdKn=~)FTHzw|`Z55;O&NbRhfolkX@3Uf~+Z_j8yiUl-7n zc{&Z|hKVP|h}(0zyh!E>Nc@5jc`2JrfT~ zy~-lz?-@-iIr;LwC0xC<63N|)@x$`w+uKqD+$E~?@n%nJrfiS^yTkCL`(o_Xe4bHP z5**rUO{}srL?T<{9PC-|Okb|wQw5gZTK@X*G(|jOj`RSY9Y9Cq=QUs;1b+vLum@mw z%s!0c#DiXr=bdBZODO_hj?FehrHc-t0#@ATy$;#N`sePBeBs)%7hUC@COfet7wSR$HJ zo9Q>yIFTIN2yIDbC*zCtdDEk47`I04M(a}l+5da)6k9umiqQHM52@2r9Qv9~AA~B|jGCh^G z!_-oM7WpQBh*{ugHf-UC?Edqj3`tRX;5d@~AWh_{i$O;b)E`YVpc5-E5G37@ghFD4)NtrnB+i}Ndi)412 z2KN){xx{+`h0ZxdXg?qDM!*TWr3Wy2R`#C^^Qx#vQ^(<++dp^5^d*rb;GHDd=IMx( zgama&i7>GxNG|i%(9bl9{YE_dEZXVpUAxO%6dlZlZ*k7`l?Lm9l#kYG;T;1lMIL2U z46Vq3d}An*qC`rZE}48bTd#{!sL%`+y8cC=zv#Hx%GEkQ4A95ED(Dh7_OSaR>pgpA z(O$Mc2&0(KUJ|?S9rZ+6m5qJ4x1lN^6Ce3Mj;=DQ%B~Au;7XU2bV#RkOI%Vyx(mOSU2Ju`duY=L5ePMXSkPlvUqVpJd)q$}^` zA3!GlONaszz0H}mpp*4Fwv=r3!`RJ@=ibSBgw{*(H0N+kOu%jg1=$M=s`%X<=M}VS zF{8_zmHWt5<-4@Ahn*+&t z>W~j9Z;hLau|$9UMB7lEO2<_BLcKj_d-s5NAKOS0Q(kJT?>Kp6Y`H41ehjw` z9<-ZCw@NS+GdW_Gi~8# zSZdpEIN1z6r>+*~hrWKmJcH>?K7juC0dMQX%T3ZOr1f9gFVM;b4B*z z^EZ!M3NE<(N#Lys_0Va9jDazN=_>seK|Ry4F^)Jmt%!F&62{pfrZFoqU!5aywDWz} z5$akxQxJKyt9L-TDOrGv$6Nm#Hk22-Z^y;GeKCX_Z(%d8(lfg#2eJWzXXzE^WF|r< zJ~Odj9ER8iXgZ>H9xwZ-N4Z*kkn`+dl zd^dU;%iPGe&)xeCvtmGZ^tAqit?TdifG4bVF->I@$nkh~6n;H#5NNM5Mc5&XbR+;Q z6pQ8|ac{5OG6+E*fa!>@qc*@mT!lagCi;G<~lx_0AaSIX~azK*T3;6aH#p(H_q~8wTO*#I=5hC{F!|I+y)bfPybzy8cv=1*-}Qp zIbpZycY_~KFA%}npO=@$zM}MFCz&g_3W#hXB6h|7_lIID3Z0CKjpbY2u?DIt)4!FBXW<_ zVfb}+DC6*+rEH2`$L1oiF*)|Sgp-JX1?>+$5R)6XY@K+hn3x4J^@5mbOwiB!jr6pi zd@Z{(l1DtT5;QghW@sikc=BQr;Eh3ZT|^IS7ZoyP96>UXKrIO-BCw<8wS70#KtbCz z%V|QI8;_f+WwyYnTmZ=2@2G+9;ZAbX{u*G-N+mHxyPre2E-3UziLn-n4#}hdf%!)*$$}_??WKUix2%zx?7y0Cq_K zG<`vua~8R4Ti?~Km^l-~3V1uu=p{PwG6GY_X8yEGLyjhnZ21AKPNy9wg5y&zo5`sX zD&+pr>ZB@!@HL-$7T!J!mTe`0?l_2`D;rHAqXFwQOKiMmJz+%%KW-Jxm3jdfHugb( zO_z|6oD_W&T{K8N1a_+;=u+&8fI~I+zAN~0@6GE`{6yqGFfAWGEDXY@jzA!0V%yW# zPtyHYmQ%#nHw2-?BjM+(|HSXo`E((?3}<%@!&u4sPXif!b5w2|)(8}ofLf|G%W-qw z`0o;zm`G6moBNgKDde+S^Z|RBM9IEhC@1+`n#F{`O?P+ZQm3&~0WhfZVmJ>hwD4cM znm*cdCEQ$do3x;MF-Doi*X?f3q6Z(jB?D>mhfn(f1OoR8d@%lPhcBj|>|~u8$2qdJ zPx7yc-w9=79eZbhG(Qv_*=iv*kM9TyaHZZAZAaZe0~mu}c$Fyu_k|Ec1mOI2vCvl( zjr<(U7ur8?HIevNf|uiBUtGhOk!^b!OBBNssr$47#e8HKew@^qp0l_ZjfCuv%g0Ni+erv*G<|!)6Lgc*+@A)1T zSb79(Om>CFN#(}4H>gm9mO_DNQdI2BY%XzMVI=X85JNB90&aA#C1VTwMP|S>u_>%- zqwzLI@9%N=(ZFkk-6fA3VY}1CqZCnLuI-(v>(LNtaoy`|2j)>LA%gH1f7A@P2dkkM zFP)GmdQJyC3i5|_(@EG{4J!nx8E-(PC6PHN6;Eeg|si2A-oX_1~T-k^uWGHH8VB0 z+$wC>k5gzj@;9YDHQ#yNuZDFV+yof)Oa+otZ0MP_i+HiYFh(2m8B^gBqU1C|D=oo`yPFTm-tpCDa~UoFsI5mP|OrwuxpZVA>Xvlg~t&mmN#M&E2yv` z-c0GY ztlrQ#&DDV(2X16Zsv*c+=r=YOLwGT}W93T49Dm~Z&l0gzHi~pGOsd*r{jPnOg2bG- z(w-MfcK@GI@*W0nrmnfp?IXco%`yyXh={a#Z=~xAM1}2NY=A1lH zgrE(1>?@{wkNP3hCLQw^)Vwd5_v7!*qMB~Qt6u87QO2x7He!i7xk`H9O{PU+wqd1b zXDpF&znF#QJnE`F2=&T!+tR5bH;-A~P6ZG7p+EK8U$70wwtltSDeAxeo>vs9I%(U; z@R;@~)+^Hxn0qFVr00TGw6Py3$X$S>YDI{$3)@Vhzl?MX>)uofu=(7J%`I; z&mnx;gRTl6&UQ9@sZcDzvFF<3UIIr>Lx(uOBx=$&tV|26Y?4SlN7A=KhZ*>D`SNiwwH#15T4Xnp8AV&B`@ zHmzd2M0d}v>cG8378--e`fj{KbND`{YU$^T&Z$3yTR_i5tKm0HkbA;Y7X}FxS;@^3(ugP>!t&? z^20HCh_PR1Wr=Q3%ERP)*bgMSh(GM0?Ka4QJVANI$!qo*xA>k}zZG*;ik(`A_3;Mr zgjI&#*LJXHxvfeiZ;~zV`a??ulF%)}UR9CPlZcS^qMEoKe0@(2+6P4cz+)INOCJ74 zsdh2OE3Z_VZE(EYY`XH_+B@HUOOfKQsEKz|_;~4vnlENBATks%g2;~&`knfGd0J|O z$E@AD)4zzgqiS!tL{6V(Dr(BShe-2hR%*GN&QUI>WkFT&vhn_+*1aFB_785X;R5Iz zBUzaMMBW>z&E`|3;z!?N!V6hkSW7Du6FX3c4yOB z@xI^|KIz89U`YPRj1^}$-xhy4UZvYY$CF9(8&{Xy!4u)6cb15UN}yovslsx(8ASaP z4P$k^y@E_=v7;aYw(tYv-i{R=V#t1Q%&#D*wW?1R*v5F2N;-W7!fGS}yW^B}FP9S< zB>R)f0E=^~zUAGpXL&OvOdJ-KJQYlnU3ivTpDF!*snA`TYR6%PQ~?e$1ucYWdYJXr z5I=FGC}~H3BCXLu%HM3qUS8hU3@duqTVvML^~u`q<`)78mLhPhvqSDx6S?F@*t#UC)#OgP3C`G0BtC7yY)uyDPc#&E{OjA2)Kn{oLmxN$>l!H`mCV z(2sU`!*K@mqJCX!BGzn$Y@ClH9A4aiVDDHGbq2_B*+_KbpQU#<#>Fr8q|1{gvMa3V;{m)&t6(G`gX zH~Dp<5}NT>t7ZhyPq-8?9YAyoD?Lk}Kj`@7fP8ZV5DMxn2h7WO!>`8EqY z#jLigW2kNY*!K8$j|@B>#q49lMIeZz6cBnO!^x-{~r^LWFUOZwNByyua1q)*Y&;Q4BjnHYX zQ0Blc`^5$CVrk_obO`MxHp(p-*;UocwM~l(q{c7>yL(%D+egL9R>kp0Hq^|fYvr?q znIW(JuD7Fc?dvR(W=!065i>X++&3dnPALCjDn=&E_J<0Y`2iu`DhDy=ho>9Awc3Y~ zby_Bq$w3!pR1R+an7C-D{Y)ndagM~V80=Pr`*jO;r#8=|394bA3I9(j)Kw!?(iunD zW!zy;uG~|cu1P?d;AV6oOpX_$Xa_N1IFO1jyz7~l=EBL31BrYr%0TlW8gRWcs@{x4 z4|M$|Crv57204-F+^xTP3(v$w2CYczvOls}_GOKP*ZAlniaU$)g@A`y*dJcJFbyV^tPx+%w2>O*Z*w4Fn^W)tH@D|6DxZz>cUVYBR|bte3+sv zAbhT{6QAmI05kB0JkPx%e|*W+Du@fWMfWnv?_nO@a&MRY&ggr%E?1DW*KVea4JrUP z4D(tKSWOG)M3vyE(L~Xol@K z>9GZt-I)QEs{9@W98_uyLSJs04N>*p!jT>}EoXFpk#Wo%k!cHWCqZJr*uE&F-fr8B z*sC9lW>4zynilxii>4=8Q~T68qC-1tLgg*| z)N0=f8<6|vMPOutpQ=lW;{M+;n+$!G8x0Ah5rjWleD;F}-97Hyu}cY)>HgH}pke0l zPmnxwsZ(-3y*{$6#J^JUm*f@)>Lj)9*xSU@^t^hKh#KcFHUeY<>|WJK#Vot1RMmsJ>Qf!()ZTHL~st{JeB6E{`oHb19@|t=}#_MyHT5 za|wR$Lz{{j5BvZN3P=J4NV6xjXOd2rO5S(iafm9ScDi)kwJbJ>ET+q49L0){V0CH6 zUC|AIuMe1&!sGIa?Yd^-Z*ZnbuYybxQ5tCpC#J zieNEB(Wa-L9xBMzazZlrJRj+2jxyPi#`5dd=ScWsj|BDeArfcFSn-dih42+BqVHHS zBQh^y4 zJl65afe$;J#p!rZlnE(XXC>!!!~n^pWb?vsmv?e%KY4_vF`OJ~3u{+nzVA z!Yf#ry*T|wQH)!UcVUVc1pH4r-_{WZtAB2IH``!Y@gk9&nmWYvbI`Xzbyan?z(BQ%cS|)?HChLblqdq zQpy}uLX`@pn-oiiqNGkk>P^uNk4c+7mA6f@QfnZ`@%Fxp@+UG}yOX zw{@?^@C~e}Z8n0mdpim$CF+j|##uGh=~=b4#zkGed(xk#i&KvX-%vMpj*o(8%+GSG z(Oxpz0XQTzm7m>Ms7(LOatgBH+mNx%7~#e0PJ6QG)B5Y6MFH=Eg%`>EZlrr+4f3&@ zH%Ux>TmJI3O$t@8m2+9(h!w}HlrEL>-qljIL^v^F(g^YQ9+kml(nlD@#VcqaND1~j zr;Ou-oq%F=kK8G9?TI1Oss{^ko*eU^Nqepp`|8T!nINheZ+UEgk*7uy4KQOC?qGjlqf&@Pkc&sTl z2s>i?HXFQys1ICk{&t*`OP9_i1A&5Oj{eRZAPy)V2bEbCU?NhC+$Fycrs9ZvD8`|o z{E{K@>npv+FgUYi)-<+~#6CCaI@zhmXl3{>t-E#(ro7}wM=1w_gb*@7;EmV)fwRH;CAPU?(h_L!=S%(9cK)c>mX=pf7;a+h;R$Nge zA{gASM61I^`4_-yy*aIOct#K};x;){J!Q8wLx`Qa1qJ3e7EOLOcGLV^MR7#pvD@bc ze17nj<0#tFjD8v^IrRW*dm2M-52z0AaH~{&+x&DrTHS-+ zH^jeNQV2Uhv}=bL!0jLs10tk}g=yo~#umHRv+W#VEHD}G+mbFTCm^hrAHjwj0hTDA z6?C?@!#NP*b{kY&j|LSr=0L5Cd5Hx@s!qr^1S$1)OKtnsv$gz}|JT9~T;hn#~ zj4^>fcD)nBHUDYLsAAcdkJ~Af$>VN4VfqXHGX7TKQ__jb7~{iOwn9pD55^at(R%=d z+4&Nq_W@O4uR8JdES2!vH{nya$@C*hFX1CcYU`9ckKz6bU2}`d{&0=VFK6EbK#hlR zT`ZxK904|{xkVGz44;P&Kg6oreG$9TqiNmB9sWCXKgfg)96kb~yF$krqti}TyeCXa z?WRAV0+CNqyI#@aPf-%hJh!kQU7Aj{k80)5`S?p^ihQ}9N^`SvS}s?1FAxq3dYtZc zkUFK_x=1DmMmS4tm^P6MavvrkqF@A3u_EWjP98gzdT3X6{go2;S~O ztSt%nU^3kcM^2S8xuqkV^=E(%@T|Y*gx|?i^%cw}N(m_`^O4j#}Qt2zW>9M}CDl5={k&?Ud%405{=6;mPXZDgEc| z!H)mCA8aKTpzdYp>E<||RfV#v@3jc+_;)KLCT!J_==6A@{+emrOYQb}t$zvh|U zYJYwdD^(_`(ZZrx$Y?~kMd1<_<5@EI#)@$Rnav|yr9Jmx@`4F5@qCpecD{^tsnJrz$I#CG ziLDRp9$4Dm8kzs|pT2N=-C3I692&OKfOTivvzVMWW{__OBqRF>nj6BwAW?xn1a7d4)jmb(dTH2l7VVW?N}eu1#)T91KW? zHktPArYu*(7}8U#hyP6zU4jCWCdi9u4n3eGIhoGW45*4}arD^95G7QPIq^i@#j%hg zSXLy$`0?FgDt7mI24;g5$$x*G9BFg6KkGfYxV8=p((J*jD!m={{%zl=nt4y4P=QVa znlXCe@mv2pY8Kj4+px{NcK%*G^`}a1DkR>u4!R4VP#MRlv_PTU)<;x1WTcierXxpJ zy%TR+*z&M~uc0-@>2y9UQ83Gccwt}JcKBk+It=(ANo)PC|Hv1qIL?oLR6d;J@&ZUR z-oymeD;pFL>YX0UgTQQy3Ltz+aKl|A0~dw2v z!l90S%4C}k4Yi3MKWE4W`^p|9x@^2d3IfBhj(y!YZ0egEw3*tI(Ihu5U`Qr0*Xyor z2%7d~Ibz;m-|>CkKG`dpot2Bl1f&Ax9RlFD*wftMM1L|S;eb| z4=DfcN_%={X_l6lLSYv)hegqs?D{4PX%Z)N)*i`(h>(ma4Q@aI>x0%;>a6O_+9;@yP=1$a+ArLU|GS6?7-`Im%0mgTSvFGr;{J6cbQQX&Y&y_ z>sV)YLbjs{n|72)5#PReI~L}uWis>vec|uB<1Y{)BxZfriArZ$XNds>J@!* z1x5P$o3Q(Ia{Y5oZy9+~p^;g?&zs^;!Ck{=K)d~u+YKnDTsZ!^PLDpX<3=k==&Jx9 z@2hR7S;fZ!GouR2r2;RUT_@s3GcCa%gwrKMX&TtW=S|~ z=4fuEpZx$7IQYKRS?2QU+BYcnZlhc3Wo0P5h!wV#%3%= zV@>~X+-6Bq%LPoD))ZtGlXE899C=a7nmn#KA|t=@S0O;G;#s$n20Ve9I_1V3c9l1k z5XLseW+;>@l-jFT_n3;V8x=1y3TQ2?hGj_fB)(}!mS=Mx%aTG*RjVYvJOsyeLGw=& zqlhFWKD^1{3SC;S#gzCfJk6L^Urxj7mwWwa|zC)E{l}3n6K!q zGO)vBxKT*V;BvC(-r-yv3mvp$a+j3G(})Ay9j}RfONZmXq-yt!O0b}nsu>BQHwtTi zZ$e#v%}>}n>RNAWFf$u0goBt#4&vS*5eb}}qKSZUk~WoPA3J&P>Z$NCTFEb^i7*u8J1lK zGMDiiqwaP=FNUJ+TJh0NH9OkB^KzBaD97KKsOAV9nCL*?DtQ=os*4ebjr~ zOdAgT|HMbvhDVR`X-}O!hnA>jJkOb!)ED(;4VN*+SGNnD1E?C{FX zM6!Lw+QHhR3GCb>A5ugDY74%gZ>bjjN1%X_FO(1iJ{$lYf9rkTLPnTK1uwrz_wavf#h^KjgX9*BJM9Uj)^^;@ zGa7f}Ujdt?^>Sq$Zb?C~yR}D?lgQnZmAP&pPHcbZN9mFEiM|jY74+oI+P@3BGH`)K zkxr!cX(-%bj@Z>BqhF!r6=}%0z2llZIrfg}f)1;a10LM=>=b8mkGje*7bDQt8JHV|z;roB2JIZ#aE@t9%NH$)B02N7> zJ8L{G6m?P;e#hhNM|Imck!)hhv#^J^%iF5z7>2HYqiu* zEeq^(3MtEwMj0%MOdvbW=#kcu>E3t!Vtv1%ju0@uNl4_(b7cp$RV$x9c+4wy6f^G} z=GT5B06NAQlZW)8SNhTWf_CJr_J-jirWx3r31Q#KzQbUruJABqrn1kf-Daau_$PGL z!`3y08o~cf>+11PGLh!nJs27H7QVP`ktq$8$yev4g9Tk|eCxV4a8$-49;1*1i^}k| z+sypMFwf5rSY0#=#RS!14I#Kr)$qNb}Tyi{gm z(-fN+R-quTLuacsS?JcD1Rp?vQ0aK^D1oG8KbM#OhnNrbkc%0bz#mhdpfm>O+q-3TnUxZ)}H6IcLw_hk`2TaTDhA}2_i8} z3f3b>n*G$mpFk4m{^xrJW1g`CqWl>l?DwHWBuEOF&w9g;9p)N?X>6!jV^nT_8v?xo z3GpU0hHHr|HQMwG+VJ?7-K#uI&IN+$fMoKg&1GySUsi=idw+M&-)m#<0jS9D`G96`hBFdjNcN6cAIb*MvV_!M+r zY+nKQrSvXCHD--4=8qx>2JNO5)9T2V<+%MjQ`R&u(y}t^lyN4FB~o172sNO( z^-IO0{v;*s-1s}WXDrKo3(+N!8zHDyUI*J%YU2MA`{|L_>f~h(`&eL-`j3n(UM~(S zr`1(QBxxk_tj@$S+aqs@$60A)R9 ziHrgQd71B$JWo!^1V6ThJmG)1eP}`iJ^)Fpgq^SEd&VXq2RWF`i`o{T&~h|72lPuu zMW$JGr^PD4fkxmZKv2~XIyCB=PjM_xR54GGu_W)~iy*L|H00D#MMLXH#(k-(&RFle zM`&x8>#5E8Fyl=B#zBUUID;X^J*+Ub_mcD`i3Cfg{qFR)Zb}i3Ulpp4Ucun`y}uT! zcgUNXwNotLb}E(wA5td7D5#je;HNYv0pGVplxFu&TeZG!%d9lge5pt$>Kh2X?aLlT z4jwIk4hFvnB&O|XG8FvAgf+a3@8*lWRDD`3G*-rgjd#7095|*e=q05@V3`l}KL>3z z*tG8>nM)T4uDPtAEErC_1Pj6+Vp28f>V zSE+m59;UFD{r*!q9N#}RGAMdB7h=h_1A#icH#iZM zd<-X=CM+G-gI|p(a@sR<;tKg69}I3a7(ck>9<(C~dY^QjB%UWGOUY^>tP@m@1Kjd!QH>czOVDRy+0B^~uf3SN^A>f=+C_ zbxetJGuvDGWyRBjKZ%zmihnXem!G808=Mb4a7rt2tN?DQ6T^puE9x$!wdU{4`k*%+ z7D#Xag*Jz8KNVo=(#uHfjPwhDMT>0!HJLwIPKIbOEzpMr4ZJ`p65iJY=djKiCxj4O zx5*iB6*IpJ@2P?u)uS2vzWELggrzstJZ;K{GZfINONa>ozy^F+5EFRWh~%}%-DC-9 z1MGFWx|Ktu`EY!PdDWBxU+3&o;aC?Y$g%&1|7xGqF8n3@^*JfBN7sTm=FMkn<44m_ zjyl_A%3}w;Y3XoTTTPwWfcV|7ec$HFC}l*C1y+%kAJokucFUUMl3O$HFV(}=jYWZb!`P%j+9-2i77A3$ z;?|W?t=j50`gdaYyp{npt|V8YM;8g**n@_}3r?il)%D6N85Rto1E%bTEmID@3G4v< z*7dn`ZvS?4peUHx`f-mzvnsj@e`m%N9(H*G&W0*Fn163mHtR-kX2qTUX0y`6ClT?vamg-w5#rS2zn@u3*HuI&9Sa$9jD>Qy>rXS{8W^V8*+gs zxSijl`lR9aHiIdITl-8VUs2n8{JqmBNjX#n!7Ux%OMvuZXU1Ztu`8Vve3YWt;SNI& zL~wnevy3h~H*e*RIT5~O05suoy87L}wn82`+_n40WuI=Mnu_fU0@+X}%G&y=0{-8P z%Mt0%jZqwn40I;dSW{tsp#`{qi%;|L_P6Owm{eC9uMceO6d!E5R7^YmN&!3or#bWV z+Tq}7*GTbg&MFXEL_keq#eiq~+j*7Pin#5iCbwTCLkF@E0H-nW?WaBym_+%D%uPvn zoX>E-m2^v>2nO`6393U3A`)W=AnXGSOGJg~^=gEv=y``@;#TP*@@o9RAyVu*DlBSKY;`ZvH8%bE=Qon1uin+~nxw>t(_+b+HHeEM1 zu`Mu3wu;l=EA|Npqc+9(!?*~_bF=L>wnErZ7kihMPLu!V3FP}7K1po0ZrF*mRxzFx z@PMu!$~0n6DrJ-lgX56?D&zhADo&Poh0*fY{(da0STzJsF>2DESBMmq;ya?Raa#&BwNwA?;-7d zxv&H(J&6PTNJ+Nv;0Urz+UtclY^yjx!T2xD7*Rj9 zlWta2dZl&vVu8Pq*=n_T=sn5OX9iIB-mH6mq&xx+tFD`5_oNcAHuU?4&>hP$8wD*B zAW_=(7O(QNix%6?TU>qkFSx29X`vlBo}V{`bqn!!FEiZ_08YK5e4Eqpu%YI&W;sQ%RKB(heKz2c7DA! zHp}Fv0804lK46I{wZN@N)#EUt|8}r*KAX60&*|5^zJ1^Isw6x16)N;49fkWfA3sT$ z^STVGiNN0smuFebYt+n3L)B(vzN=b?M_H`M3;ws&vPVncHZ;04=5hmW`GL0Vj>h-z8e9wq|DvmF2zqZB%sN%%QLgv z>B<5x|4zMP{DDQf9x|SVuAn80&`(?H19q>2*G*hiSJl%8ZpqwhH<>?q+sMwp((Lm* zklW_Re42w4av2!_+NXEa2d-rrfXqjLlQ=Wrfd}>5)9)I?oyNihD|hu|;nR!z`FP>) zL0LtSWBHn~@t!F5>SbGK;XzZN38sG$vhRpt(GVAJR@Xg_Pz4OD!w@R#Be=uV7(~S! zC8T&c<#AxyHoV}~r2LK>`a{^&G<~-Qa;c)<#-2#voOH*s3D?W(V@?`8iC(5!qIy=` zwF>i0k9*J(^QQJC2inMZsN#|p;dLd^Tz;{E`d&P~T_Yd{$*h=Df81Kee_{0*9A>rq zNHH}8Y!SPmGt}?Bbo;Nvp&Q zfzz`SonNm-FH0y(YR5iM*fPd>DwKMMBnMWijxV0_Z7ZX=d-mdtV{v_IfLm_d*^ChL z$f#b(D-+jnqEV2lTH{O)xRZQY^ zP6Ej%iBlfzFNkuZQ34zg>ZOOW?E#9x^2x=eH?vS>nC~#l2hka_U(nU2*^d64-qM)7 z2NS)2se*p8)w^x?(Lz14b@^o0RQ9<;&Wt3h<@e#%5^3{UvLYs!63o2@Ll5ZGLjg}U zH)$@2O_&?ijfcp^2#%DN1%~BE>-Ph6B0=OZb#ICOmh49w^w;4=Gwemrh2fagfG4qd z&UB#PVZn*#bZ`o;spN7{%dioWdAS;sGT6UPDd%wM7MeQbAbuhMyL-uwC(3Fo3hI*y zNfN7mtR(@-PwQ#rhFCt7AO;C=Wy{X$ew`2z>uUC8(HS_0yXqMu?11{>y?3O)CXF>% zA@;2e`47S$Abv?dUhP^ftDz4cxGV zt)*d1$QZS#0VGKbmSyor6X`S?W*w&Lrsz476f20_uJ69-r<4^{m zGOnVj|3YPv?; z`0awz7cf^+rF?g7d{sMJE0Z`?eNnikjN%3aeL&sow!6%o8u-MKB7~b5nPFAq$MU!a zgHNXNS3E$-q6oj4F9x) zmYW>bkGTD(Q3LiTg>g-iy>jL@!kXjni{(Z?j{dsR1Fot&;VG3x0x#WtK_N{Nhl}8Z z!|t@%A@f5WwKc)Jie398NW7ziGi^=7>pb4nS{ia3XQ@Z#QxB#tAqkxiK7PThu2*Wi z+{Kcn?A*kW^1+6i^=}CsF5$!m;_qHMg7shD1@4vcQ)0G&g^&YhmfkSGRtdf1h@Li0 z9&5`-5u=R|k;a|XX{whQIK6t?XWd;CeIJy;I8pj_#$PW1f1uyWm?0m( zW5G9htTEtDn7s?@yU>9aVA;;Z=$U($n{mN&lBwLL77BAoPTOp-_vBcpEk95p^@hppUCI>TaXK>N-a0(9qx z72h>38)Fj^@S%8Qqn!tR(XrSP+J^nRj0p!kV;cFu@X5W{y#uujV$fUiowsbnusoRM zG_!n;6s7;jl5x)<_RDPsJ_OY;S%xd$CICYXS;Bk3(BON|JpkPmQIjkba?>;{PoXCw zu_XfGEO+g|yvtsUO5v~duI&xX4Ws#We76PcFB!*VhtWz$FxtfT-Td>30RR$d&mzR# z&N(e7NNfOWl*^@$4j&cxX%|`+Y&3eJRC=8SQmz)7Rm%96&*k$%)fjrG^el2odOuaH zp|XZBzMq@!B*J{vEdiKBJ{|bu(I3TtW_&FdR#68oNSk2o>yh#>n#ZTFfUM4s3kMcm zGBSzChT{%YP%aVMN#1B>EPu4vG?&LnpgDEq1GZ>>{*uo8L30N_bg;3T=LtdBrdB+B z+j!oA0ncVbcPRC`ZNz24Mw(VDHz>W7_&bDL4!jssS^UfH`~Mx@$hbOckAk)oh%UEp z`#a8j1@zp5pQTTu@;3(T8FzV^{fGwJBH8h#JiBm^;b3m3@kLc-umgvSw(JZ%a&jL| zn1JF9G{LItH?`9mW?t}tkJJbO7_cOI|Iy#PDM6G{IhVa*$B;h6K+DQzaq~vVJdNGH zVNGx`@Zu{V>_okLS~0ddeNO&(3?2x`rL9@4R{hl3exCUIWk{UH5P#*reH+DZViC;3NO(7ko4kJP5GOX_)8#g+UC7LKphO z!nW;xHZj0@^oMgl^gNL+X;LN77L}|y(oqkai|D8_dUNiza>zd{LZ994ovsXn2;YLK zGfym`1-t$3&sZL75U5y@xWmS*%AaQ64tl;kvIZF`-l&)Z9<8t$|U5D5B`eP3OpZ_Kn=aU{%v3pl-+vb#3@r4w0 zpqv9vAubbZmP*(_gQP}|4<{41gOH-*&yCrC2N$*}qTgj!0Xnt<__0P1OXP|Mj<~~s z{^cV9raEGc`shN$XC96GA3(|yb*F0zYg}NTU$y`k3z0CH4;ZQwBXf~)rspweotnc_ zwhav~9zYDTa!Z36hU5)1D#D)r=H4@X<#eDhRICg69(_P#^km($gfs zgC$9i8G6B9zQ&pncRy8WgwM5e`QbeB^Jp`^l9ej6hEt(c&GPN11E&zx@`$*ls*F<( zQjcsC*n8cja^0A?+ZG?l9%8dy4p91o^I)aD4EE8soe*DHj9;GOYl%Q55kFU53GsaT zkD$iuOyMU{@KO|*R+O!ns6QSwH~d0`l6{}4OoO7vC@TDYDedT7pCxhpDQB%PvB7}x zt}3|q4O7a7-lP_oBMU-~W1lq$bwF@U5vzl|!)7-v5W9Q+(*{$ix|CkVVL;yZ(2gP2 zUIj7~(L@dvGjQH!#{PSjg#iNW@IWu!Zi9mY=n{_sGJS)NG4KS9P#;P5Jl3+SPwfG8 zxX@P`YCFZxYn8X)N*g0Z<2YPnfr3tq+CCPFn z28qmmK)Qxxe-Wl9pDY7P%3V}Th7}Lj>Msx%SIMq+>jbp~~=6t8dfY{M8 zN5=a~D8A4`dw=M6o*}EP_D^Wv*s&@ArkXKP>9#w}M+H&HT1z4jwpsTZPD<;|0S$n6 zqyBp|rr?(=4uuvwh;jI0Bq`zeeHrw@mJ%lFun)PbxAqBVb-$E zd1X^$D3MKvI0M1?7B3sF9WI&lYgq=uxCR0eIAfb0hUmIQPMPC7XxzzsQz`4~L7Q~x z_mAa|$bwF;YMFICgk83m??kDWdR|zOcZU1-+31vra_zg!;PV4s=s1y6 z0ysB<@u8FEWIP8{6+8qSo5xEgbqoaebT)Wz=Vr@+7gsnrFk-P`!&04Fpwpd|#iYBv zsxjGo!6!#y0{K7Bfwy&A7|uubNIQy*iL?5(-Al3Cc1Amb9$wsb=_Gc3=@F}$5zIiu z{@lMR{TE-XD#K27Qt2PCi)jA@l2TgbM`58_(>j$WZ>g(nfdir^oU0RFe+F&${xicG zCdxp_j)aHm!IBi?0)jYBLuCHXWpHIl^1hI_;ijZT)EO9l!VXNFQ_Fa{Y5!z5BJV6@ zZ285U>|7QIgRGdaTMv<|{@1PQhngdu7-XG$AQ>Nu;Ec~QSoqRdLfx}QvtAL?n)1h=o z^I$yxL&}Xd9j23lnSH($7B|IJ{R?c<`*{*gxmBb%DVBDPHkpjAT(QZ{o6p@Q_@X1< zsWkap8h?H%`yLp>RcCkV9z0NbR$ zpVmcWl-y1OD^$EfhVfIT(gFjIS(?+Z#%j+qW;?Idlu+QCd~ANLmXUDWc6{lDLG$A7 z!95>O%T&O01`gi1IkFM6L?*JkUe^8mRA0qqJZ2pebj>p&NPB8q(j+xI1d)aG(fmdd zP024~r|+^};MkQqyXS96k9wwS?U2`{XsjKL!Cf&)4GxU1K}`*OG#Q^`EtPWT#(??r z1?Jg5u9ps!hsB|JqnNS-A`#`<1cvvmsV8oNiyk2;_UrtaQb34%pkMQ%zsbaPNI@7m z10qnB$^FJ}Ry|speNLdBqU{KfmtVJohJIJyXm?)TblP-!+;A)}a)#TRe@(-wI*?4loz)ig`dP$k*D!d!b zLmv!sHVM(-ln4xclJ%1-fAknX>6p;$p~AayB6rp3=Zw+r?Yl}dHpL@zPwuSn0MltI zv8&DGIQyeb$J~$4Far-Ud|7lS!oNj_fTEdv`gD-LII+VX>A`VI=-iEgu<|d&xRj!4 znBP@EZpH#*yS*(jk`Qs!!zQorMmpkz9SNv{#k9PZZ86`x27FU z$C9-%Sm7(0;n{G-jLLPS1uBWGKwWiiUaR}h>K1bi-~HWjd2dQ*X>e^{g-7z)Ag)Ri z@}du;`u*=Vgu)qyOYi;{(H|4X3iF=pfA))v;?+npag!mcAYIIc`<7+@xhA+fc4SMf z5{DixxF7FM-K_pAendtixQAIij%TLS3@Jl-g2YI@fiB!9zMkCsoO48>kLFFr2)cD* z(o36+<$+o?Ocu$K@9&M3TIiws%r;PXQkxPzy03y-i@OKN9b0ElxGqNz1>TjC9Zq|; z(ZJ0hTvUrb-AlF`1N}_}&qt3VZ!o+3GE1UlV>Ptruv@c?3c-)7$9WR9f*4+hXZLfDQ}FJ3JK_sKV z-Yb`B%%+ys<+Vqcn!=jJ(x%ITmb~xf)^Z^4x)F*iSUTj7c@OF;b6aGV zoPP0zwn1duCvn9E6?iivFxy5OwY@G?+z@vX;K}ptaColizgw<8rT z^VI$_AaZF0e!S;lDD7YhNBgqs90x)Vf{1MJ^pG}%XknFduauDHh9+{_*ZgEJYV?2IqtIMrpDne~Ma(#CX-|5v~($DWaF zQ1yD+#7wQ2Xc7k(b++@k1O!42Lp9jgYM| z-=rA3SXs+`Wk`H;;yk1m_Fs2QrIyj>{<9m3S}B6}XwnHEWIR4L2p7rw=8|joiN*1% z1;?N=1ZTJl93D@iO7RHJzNB~LUEF-bDmb8qK3Uw_?0uB6|-OA0-AS4n3)S?q{N z+Bhkbu(Ru6%{eVjCAI)(mp3dN+`?Np8ef}x>Aog&y%e+biNpmH(&W&(hnQIE!8R+e z{wbHQ-^e!?8dl;^Lx~vNn|J!g9Eb8a6ti;Y0x*^7zfm-_b8VS@v?uSL(p4F zzD$H4eO$;dPZ)*l2ZJYJ6!w}@E985*MGYHF4B;g~R!x1uwS3j*kxhJVVQp;&XH?BjB*t1?A4_c`5;6#*dv+%MT4$3$FxeU_l4flB=hdH=~ zRnY~hC|j~mdSid~uyI6)KmS)f6Kr2>U%hN*PhxWk`?&m#Kx#|aRgG+^Gm7Y+?1}$f zXak7v_5KQ!ghzpP9H_eAXXcAWz=$?Y3Mby&35270A00Upzd;}!-xu%CHD!>Fb6jQr zdA7W04`yKnQ)cl#@qRqRmvtH$8YqRsaNGg@c(i&gv*y54nG9E*ObFz>J!Z-*4Ci~z zcgofrQi=qe>0;A~CMIOwC1f9JbIMrltO^NHBZ?{W8z-95=>Ejoqj?9{6ds7l{Vu04 z^UC8fSP&rr47>s!MBfxXkZ1ipuQ=`P`hCz((MtJy(+J{I z+`2Dtx7M!Blb|j8s-kx>uJPjlPthCK0$iUoX{XALj~==?FIT{Wde1+f&mPyC+=B&0 zfP8gEhAb3tAi|zkeDQRS3AI!)4I2M&YC`pM&W zpTAKCsYG0fTW{gFOF|k=Ns-+tL8^kS(e96yubTyy9}Ao5gL2XxiS!Vu5$KWE+BO1x z)br?}$><<``XvTi22Es%7}3W``SMdhx}YGs2q!K(Qn5s@1~orFGU!d%b&PmeFrh#o z55r~`a@BUzdrP9Z&e$%Ao6CGwB0V2RfogC=$FEkXqFGykWU%?`z6}et9Uup~IxElm zwB6kfw&K=}>DX5#4;+tVV$|~g%y{as$@&eVPqG(j1+pNFd1Nm*&V7ssZGGOcoGs_K zV&*sn!pae=tg4o6urtN?KY!T%mp*$NCVA-49E?nV+6?bfi-~1TBArk=ICR`TeN+3! zo&jg4s2-Qz_KbV%gX4S|dvdMa;cOMQ?;#_F;pcw56MBoSgJFAo7@PD+{-Yv7J^cL~ z*{-e>u17rE94mMoz^i(ENzWS<&`Jw(2H(?zlM~Z~s;a zhPzd-Q;S8RI(GIf2p3{UcC=5XeA`~>HonPmcq|sTA33%om%Xk#xodNO*toZw49=YB z&f{lnb21!iSwSr-T+HCP;NRxN_KIga>dN*Q5J~uGu=u}g-*VUc?vBxfvY`mpu5omI zwOe{1A=P8Gtx_0)g(LkgZJ=XSqgui9>Mph7LCfp;%^8@l*!jE9786<$+-{E%?Kqz6 z$31Vq*o3z|BPYs)(sj6Cc3M&i0LI(F@^^}235t+@Z;1Uw2X zcK*dwE}IonrT^v?3m4_%S+I4yRw}G64u@v*$j}#|0`J?rzg8Cy`{kxB0=d2K;PVVE zYnXwff!LThaI<_XimhD7&uYrK4MnThG2%76M}oxCyfVp*9lS~+XeHd?kRFtL@+(`{ zi-RN}Z_Y^TjDwNU6%HVei*J4AV-17FaQLlyr8qJG!ilGc zY$9_tMdaoUk{Rld>ramD^1Q(AQ+wmu6*3RP;qzA~t7l#paBAJ>NNIXjIC6=3MZ7H* zV!>?Oo~w>hIbZ~p*r_kC-0ko;>skx@y@@fiIHz?ERv9Y<+;TmHbX@k3lZqD=d1L&NoAK_LfymtEG2T9l3{K@W*a|w*g%wHuSE;7RU6qm8TBjz16iVK? z@vX&`?prm9g@u==#)~hPW>f8N6#d>!syYHHi$>Hlg?!4|cg%X`QnM_-H`n8Y%nk=1 zKr*Q_4TrqAP3<>Fibcw3jh2zJW#g`?KP*|y6P@e6QrU?dzfn?honkQ-m<6QABlNFEFhg?`l3kfB zNGwarPM~B|WMc78v5cQ>MiqmLEbh#S?V5$)Qtr9RH@e0-FW7F6p;j zMCj*_9!S3QqEF-Zx_&4mU3$2m2E%s*YlA}Laql3|+hr8A7kkaKQ8|Xd#A+pANHXpz z9XxhL&aEGvzC_lO#i4sXv1*ki;{ELBS6tpo@1dLbt!tNN2yhrmVv>ew5=JQOB zh~gZ6N%VNXdV}{L2VPK_7T@<_6WEG$d(3GxbbtQuy0Yp!$>wYfZTX=&~T$^e6%55gU z{ND@n>hXe57^c&DN^WIH{`6|^hP{@lgepApn$1Bd3io%;-DXkqQZsUX{oaAg7(Ekt-2c@r9UT>_tbBV!cMj(jRe7 zP*BCZ)@RAJy?gbdVK0e5uUcqRV2S105B67y$RqOHFR%Vjb9~8Nf;m2&W!%MF(Fi-n zc9Hmpp>>c=5bcT+dNknnt-6K0d)hQV9mtKA2RP6ab?xNW)r<9TzIE`*msoF>D~ zm`CFD?HgAkw2yRzI2;+GZh_;dKaZd7Sg;VBK$!$$wLY2Q$)8+Ap~fzEBrff|)Z0-X&ehz*Bfp$f35?yP|1hsO{|1J}u3X_ttHSVEFq zK*}NP6-|f}CWN}~Rn2I5MQijMUiHV?Pmf`{7^B9Bva6SCCuhhgaMO)cqM?h9WU6o*8<7>53TlpW}+-EQ!Wd30#@Z5TE`{D=mCf z_pc7RX?DK;ePtuy>bzq?f|U8*CvHyr$L8yvS0sJfV#3jUDpuoqIxrgC z*UZ|F5l2Z_Rmx&q&dKf^W8C6Hi75sG77oW;LSW!@Ps(C`GwNmJAzhemlCUEITk+;U z0kJXCE7O!Z&&~EVaIApW40v`!X2}W*h%C!hs@9hqHX8aUWieD4adegp?^z|kosjIN z@JvWhc%QaWMF9PitSyHL`7XFpA>oQB2!qHr-|4dcZn#PKOoA(_x0erXQ5Jy(V6G5Z zl7>TubB1#+5N!FdU^;_;$CuDn9D#7wvxk)T-d{TZug`?=Canj zBBZ6OtIAQcrb|=rjZvk-7(M&R-^(t)RcPVbbfBm!l`-gle$M6Gfh=tm8o^Gqe)-AfzbQVa~eRrIG8 zq1A`;lh6%d<2C2RUP4)gh4M%AKl z+#J*@0X0b-0n#l@c_oeQ|Nq?I=GgOkX>hwG)jQShpz)?~ispAqpfmtJqSBp0S7G+v zn_q=7@M6gy@0*}CUvDVpY{pvTcS8Ga8V?8(jocEr z9z362obX#%p_PoVX|c8O%?h_;8|yY?L`}Rcq6&Q428i{4+i&^8sAu-IZtG=V-x0h= z1$kZY#egjJ04l$HCUj_C+4`ehHxe&yGS%laF5#ud->nA6W>Z8bjlYnZ4u_(?Mn~4T z?>L8eCE@TB9dV(o_hpnEfreAUQ4m{uDod_dq6^2Ya)dvYL$MnSNdAElABX^3^feK` z2100``2CDaflhpSl%wGJH=p^=`}mK|A6eio`>ctzcD3_vwLIKe=|X9o!QcaASdJCO zmWc1h9qx|9@xJ>P7dRQ^@ZhJ{PR(OObG8l2g*L1kXO92HY?j^1t{#Fff!{XE4k`Y7 zBe%;|MdVtlY~P1*6T1}-&%*on>izirxXZJ#hkPYcUg{?_n5w;IMkZP4(uOXy1I)9b z?O_jci>A|w7j;#~xe8kO-+e2m6f%_oVEuDx8DSMLd8REdi^47l7_J&DX zy&YDDC%N~Qvd+H5QiX?b$`EZj!ZVAVrq>l&_m=CY$O~Vd&mEAf zz(>omz933zZM80zxAGka&*0$lDh7rY`KRUbe|n^#C`dZLOpMxWGucq^sMlX~E2}$Q zr({PHSASqc;x(^*0OX9qn4Nx{!y@3Ao~PoszdiUl2~6V14{wE2pMi(si7u-C=M@`+ zUfkAr^s&pD)eKkd96}rJ&^!Q9=@KH|R}VtEeDuZ-)A8C?Wm6HKjI-35~rIS5;JPc>xwoh^H8ha6R=M z5|ws7_pRE=_^7C5Ckgy;9@jaHQFu{j4T3qECSa7oXpL#5!gefrrSt*fv;Ao|tpxjs zNZzZ}zRB6)?BMNJ?{e2qOq4OgkKW$s5NcFVWt@Bslo*t+6<+qL55oTMk@PD@i3NwzU~nHxejS)>0=EGVHV9YQvUrZuT+B;@&EZ!Ik;Kvy5lm&S{~44h#< zYRJM|`_s%`B$prArugD~E_e>-P7E<&mXOXcZ;?`ccK4U+`olY+WmR(!`9DpMZM^4i zZRgBNtiI7u!P&WkL7tvG?q&N`UDMlAtuJXs2lsW7LT6uc)c>){AtpDzX*14MA@?Pt z0=y1-n~+$G|3HpsK>{UxHZdFxm-lHrf2*w5h@kxYrdkG$=2xiL#Q7bfHy|P??S;|q ztRu}r8&tM(3hb+b61V}_$WT!~FyDz5Q#9f*PsC9;dp}A%x0Pn_zbMuQYATB_p`2tm zPjTg?YHam!z$L;H<1lOg3cLp3!=RN8MG(&8eDsIiN_Euzz^|iJp3#zzjQ$F%^WL9# z1i!$gP^%e9om;q%G-BOz`FqEMj|C=wL}L1=hW&i{T`E(h6wFS3lkfkAq{xq8x1HIO z*^z^Uhdgm7nMCCeHgsQ-^e;RHPK&?#Gv4M^?(!zk-vZ02<*mh(w`}58;WG@b?PH5^ z?|+J&W$jQWG0T#uMbmx<2EEikf!45J>p#K%MNrk`sf) zcVIbq&DKi01g%1!_qFZrP_t~0uCD5;dO9LO| z3K)}y-d-7WjKqv9i~iYAF!q>8zg-=wcGJoTW+5*Tu73gB&ncJvfCvPhO1BsBEk0)R zC^N;ozV9lJRb5=mB)VFuu9IS<2vva+pF;L7uQJmcDZ-H_Xni<>4fd#6cT(qHYvR6k z%-DQ#<9&3ob+kYFT2F|6tmEbkX0|R$<-Q0_=1uNU2yO%9=z^QF#icou3a{pafD1nJzTC$HuA};F zL_0vw1YE#zj6ex+Qu{>+vH*vci6MpPT@DYT42DM_B>+eUj~15v>#EDEjCMC%X1X7ka0^>P11V z3>ZT8MyRlesz4SB;RVAVUn)BVSkU}dLg#}xjwcbSsF33-HvG=&&)lpyE`K*AITOw9yb(Jwy6~_c0WBvAVreW@2*VK&M8FM z+B-}c{>9O_O{K*zDZ1$1y17>2xpsSIFqEE5x?s@V1H02-8IEIDGiiD3w=f-6rS9( zj8%Q#kg*iY_3qQp4Eg0|*TJsl|Q-5Sz2%eH|`{L`u=#yru%L8F8tulK_x3p&d zMk*R;y92#$N?`S&xc})A6DroWb+?TZgloJ%3|^+c`W^*`sXe!h z9rTDwbkd1=^WRG&#ZkF%N!*iVKd_k@Ozxz2uT{yeUhi7iv3?YwLxLGWW8pte-TK7I z(Xw8IKK22Rgoi+xyH}q$;JNKYkvn$_#XV;fnA!=^(HmCb)QSCW>hX^LDWU(l&p@&= zANMGe_1`{ue--gPpn7Sbcg=k(5(I>G6}f8){$LiFL<(sMxp_95&QR^Om1nkPkEbB& zN87&fFpP41?b^IIv@A8qoP&w>TeXyo;FBbO70K&*(9YLS&A zw&dD3mzBMjIVcT#^h{$tZ|)_SAX z7?LPF8l#!RO*WV#j(X=YbjQCZ@a^A*#?gEgx9PJ1ICTx9iV4N*P4VaciW;%3=mrMH zthzh8Ju!WBZvr*HCLQuA9o$t#;ksVkEj@)gj_#L*6rKsAe2yxU0tpx`}WLvggm?qib#UWjx(Gvj$eJ}I-h1!#)VS- zjefR2=%o{b1JDb(R2L~++0Pz2!{@SN#ihdk)Y@B$11s~Gf9_1KbWS)uU zDBMRqE_;I_kiQEP0?~Ki(hRD0E1Xs=?*7S_<C*Z7a!8*GF?|z_J_=d4jl$Mftv?#GKas_uW$9tzE7;bHU{_X#n+%;`51a zJ3ctJ&Xm~xaf4`t!rs&jIHMjt_kdgUaWk2>JnR#j%6;Kz%9tsC!|rp&iyhg7!@?-_ zi={oia}(yCv)#?kH{<<|8$UU6?kxKLgsW5Px5(ieB@>W?P|S!q3-FtR0OsR##$_DA zW8$tC7wIf$;iqa_s|1$D#DZWdkAwe_`@m$kk4`7^6;~ZC(xY4++nQevzOPNx$jr*w za?E#nhAqKHq+bbtbozVW6Y|(VSb*Wsu}&<;ux$))ki|Cfkrgh|4*z!hL}YLSfQWGCI?JMJO%<{>-KFJWxvFx3?lf#FpF7wh!w13G4E3i=XdFITh zUSkQ;9r9DEtZ&BqzCc7#7#Mk&F3N;@um>PF*yYEJAr&&qOsUx23+dDS1yziZ zRw!BkYU*?^E|m9;6Iit6&+zjEL_BQ_w~s^d&-NZ;QtGfGMY4k9sp|j$WAJck@%C;> zGh7_&%w~ydTT;sjxGD#w&Xap;3!HdtB^IGic1Wg11Xgt4iq5G_kUTOWB!CHB$wb!7fX}n4#_~iobK!k|B)S0N*CbVpp7spE z^rak1v$u+ssyY359;&?YXx9aQ8QP#p64myV1Uaku1q&1KnItBRUB^#x?!Pw8 zkoPtHcU*sW!1bvpSjPS<>gB*$oLxA7y}-{rdvu|=KL3V->llvFQ#a+bZqj81JlXi8 zFJU$Pf91`HbCm(CGN&TvO+J-29gemd`tJ{u2R*%NF}ltRjhy)joKgKBI(%J49DgcB zvOF+kHNhwoxkf*1)=|}2w|5u(G~n|)ML>aESregeRWGFgT{SVPAEBR#39Aa>%PULj zH2Dp$ft(`tMQZLpZpt(XC}B;p@N$g|B%InQSz+QSHtL16&aNYh1Zk%cblQWNX5!4CkrEXy_z-+sj|N0HL z~2$_=1d0 ziU5)Qz@^ZwY@iE6m0l=?Gs$p;C5cYl@qbZwBL@?^4 z&EeJXRr6ipd*xB^=#SQ$AD6}Jrhb0tTvzP-SSR~1o>@FLhYCk(_tvgD6bAwf91%fe zKxDjH0~Zx9fd2u6AtU}F;`=lW*A`ee_Fe1@yki*r^%Cg{GS3}*`RgqO74MHh0put5$0y8y@9FTaW|HmZl{#53Z!&AuZQYcVXPj2uY-sa=W$aK0>q_>JBw}Bqdnx&UTkg7=O|iWHSlJ9N{lYZ~+M??$E}VQhJkt zL3Dt*YaP0Nyq$RySo_k$Z88C}&Hiyj>#$KHO$wCy`fzaje!L8`M7ciTy6O*EZi<@d zFB!W%{BTpk^iCyM`$6e{Ze4W~ltwC7Lp<1 zjfhGE4gl-VMLvDy)Nk0EMJ!kZ5#gskv$SoLrc59FS=#O!1A$jnJYB`cGzJO?4wS6Q z2{l5APVe=aSb}RS4ayz5#X;SL_s>-3s)5VE= z1hqW#I6KTyom;P63z~sUSHzj`dKY1oq$0X#wO;;WpfSlDbodnYJvzhzEIt`~*s*{pWvECM0Wvk^HDhO!mihQ%oqV4gO@S3?Z*W0@hkj9{h1U$;7As zcSzS`rM88^kGM=OJ|KpRzJG0!xq&)cT-hC2;sL1^(R)qDm0(C#8(`R`RfI4Y-4THK zY^yaig}RQ2=Gy0yMEmVgL^8*Zz}1$1%kgo}fgAxRx3u~xY5Hz8(q}NcTU{MAM0)~e zek$QWbR2eu*1_AIALk??T=yXnjp%oPC>=PrNY_J6NaM5L}n$o)4}goIb|g-=rt z0DU!OudH0D&9WI9*!;>y8TKiQ6Wy#BcFM1$*a?t#afmzV-?- zxcva%U+|_U0;bE^jPsy4rqI}1>-IQ64DsRA%PXWO1m=25+6aGs;p4XuecH_RuF9YX z3GT$9JMCPeAfh1QIE(g8;2AQ?TL#S#E^jPN-P0B=GUcyGELfaNamU{e6@KD=pW^Tvem++ z5?l|qqb(EmWb++4605olTa?`T)UP1vY6V{-7j3sV8IZ8O^2Z`;J+{W1c~QWspGWKB z``VZvfsYB%C@fY>3aE(kO8?YikhNnG+q0RTbH;aHATNU zs51VfigQmj;(nukThsBLZbNjrAg+K(Xa#^>z?IKeoVuukn1U>Rqs^gLCtEWeo=^jO ztLIiV@t__kvZo^XvOR%IvCX8iv?JU2g*fWXedb`moff-_p*^-QDaJgB1nNX%(9?aW z^;<0le10W8#`m2awdU)Wsyxw)^fv275lFCk8Thmj@JGK<92UI;?m~_PG-Y8DKG2|DG*~mbbd;1F7znIWjwS6ZV^yWB>k!iuC?1aRiam+MuXr! z5m*Qi1B94gq&{sa0OZB%8vMwTT&wo(!4iA|uqo69`0wJ4iNzl&)gaO)l}AeFG_MGgZ^s%^6&o*x840uOEr@Wq7YL3X~A z1tLWEqlyB1M<6wX8h6a7g5-#mXwN8W+odE4xZDvT(ivB!9!J(o6ItzaR17BFq>e@-k(bHgvez={7FKJump&xHe?9=W8*P70sqWHx(;8urAWbKp67rhi zXQgcEIF$#NCB$dW$^ za)BZwxUU9I8|{}4b`g*2{n^6`AVkvY|yj}VNJ+cxr0C;@&M;segsXu$-?@;}eX7uq>vBIzK7*G7j6 z(I;@Fo&Lai{r`=dWOA3ZRW6b(&y-=wv+Y8QX;nZ9Ux0drMw^lfuANJC&er-8am z(5XLr55gJXEvh{+9F)6;0FN7NM2D1M@YTf9gjtpIQ|wR6C`Tbr%ItE@zceq)Uuv7v z?TaViAK*eD;G@`1P_RreAv@v~}%j{2liptkLB~rVc^X-3uSZh^8h?>?P@B1Ge zS=6z7`cg)>?-0bC%QL}T)~{(8M<3Pcf{1I_^m}1FEQhDV zU|x^Z&KLtymhNZpU%TuTgfQV0ofC;)E!3zIX#5ymKl+Ce@z@pUjsLW(lVU)=Q{5lA zQecoOD~P2bM*NdbZQE6<+e+^Fu!aow0bz6k$6vP{5W9I=7Rmar1+3OVd_C;PGxA6E zCH0sT1_GN`;WAkv8Ysf@T3J>&6z8sPgOE5i7;XdX(|AO3k69h!*GB0i)(6vO70gx5 zXb1$KPBZF9%AC0;#HH&YbRzhBtEJIfI}N_Q-RHQ_KoO55s^Es9Q-Fir+H8)G27u0M zfh$=cwVV`YRI=zB9XUeCZH0;5)9K1ok_OXC{d#4c~dot zzR<*C&p>{UFD*Hlmc8;mTKj`G(@Y)JZM;aGP7aFm(j6+f+~-SozhpExUH)LK{U-KoRF`F*h?v6`2$re)RuuXlIGbEh_EP9shXl z&F^+6m2?L=4b5ND)8e5!lfYjo09(QzowMlh zpMzXkF2461ibxmUY`g4Zs27FD-TTAkPD6HTBQmWLjy8^=ZNe08ttxqQ$zyWtUMw4? z*G3C0ZUo(G34Q8j%|$4FD`2W2P3}j~meKE!ReDN?$^o;%te)n5KH#y}%FA3MBwF?e z?=xt&!1?Gols(|)aVh5L#~7D}Tk#t*l=0d)#%K~lENm93+!??g-!O-8Lo<`z-*GG_KX(1 zYEIv<|H<*kD1rrvn` z>RVFH8>S_wsDJBJ+YM63ETzkZ3cnh*oDfT}fM%6wm6_|{z|goqe#q#w)Dfq-nl;~T z?Qxv~Xs4Y4>^pd8iC%xcf}jc5Tr=-FVbu}9q-?RH^1yX;!Zh(3_LtZ+^VVn-UR+to zwKIc`ezDK}(TfW+|Kfeeh`>*By*Ac)l;EM)nEpt>t7udJlJnp-9Qv(b zNML;;p@HM_;VC^f=Y+jA@}!z#10Ier3m@;vl*_VCD%ffnDb^xJqGnysHT zdj^-=_|TuoZ=Kw}^09)wDn4jZ8}$+j(IQ&B=*zf#QPX)WV?fr-xm>_&prV2Jh{YXF%^LH+m+e!c_AwE@;@U=S0j}T( zRw@<^wmdb5)BIzqQ?PI4!#gb!muOPekD#B-5s5~$cQ5y7(Jkk6t|?vQoX(==Tk>z{3xc9Qm@{j4?fw<0kNRGd_6gAWo|kSZ0F$ zT=tJl;eaH6?8kAuxL)B+k9}v}Hr%C&S!!JTiO*(b4)OonsrKm)*v_bq4X_SuLng`= zhiA9Tp(JX5(V~}NMzNMS&(~I>koFDDSqw!7(vk> z>s@I?v%bPQCe|1LrrSj$54Dcuox;X8g51{00Q`c^kHkhgL-JtX%K%;Lvaqu#ca{7+ z`{Xw!@fV=?Ju1=DYONh%-`{hrR~N=v%S#qXL;3?MrG?GJ<^F7Ep{=44@VxAB6266j zTrS8JIiXIFu9gEwHjwWt-3pB;r3NpH6;|1$a?aw<;={?=;2_W_MZlo(ADsH508bl} zYMOU4RS_vt(({7i$KLkMF%u4(3~wF#i9c};ASkH!ywjzygyiVrU03&!;@BO}VbomT zE7a)FgY{qvHMBS>sBa)Ofse>rvn;Pfe{wSsJ>+Qv#M{)TdNADLQNfwJ@+{+j3sQTM z=UN7%;m55}&F3V*lS#JP45$8|)B9rQn-aZD##^W1{ir&(jLyRXYjq1rlyqqjuC-kV z{oCexU>GO0uPILk&MU@FWlDe%mV)?Yk1lC{RoJ{-uMmW!)hJZ7k{Te1hsB6KUMHhL z-qNqxQmy`8XWS_@M8C0W9()ofxB@$G3YpCOTdQ@k zxO2XN{>`!0B`-bnu6{#TR*5{QGvkhIowl0T(;7S1|2RI2d94Ij9-Zj8~cX==YB8Yt5V^ug25PxK;jbsqCUcPBAUOcX>(jGfv(`V}t|*)hmnd-N^CRg=lz1y}3z zNOIv#{YGgDOaw?s0wseEg(ub}pqQtQ2$Ty!Vk&Z;gGU_rJwkTVZkn>})Y7Jh$D`e7 z%d@~HJNbl>sCw1Uz@G~70XC@#90)}E`d07c-N8tKLJ2=ilrv#Vs*P+gexSOEw?FJ( zQ%+&$XV!i^M95VXfp-nFI^6`x1YQgHNb)c^>7K;pcUUe-eiXoaQmtRy^Wk)61!S+v zCw|HI^HJH+-82tPf$Tyb6aqlq|HC)}WyxA-$`$dq`9u55`qX-}^{7*kvln{6#LkuI z)%(`9CT5~qXuyVUSAP<1goWd+5wTl$`|2N|74W*hc|(_`oCY9t^>o4!)ZonWin)GU z86|bS!vy@4Svr~~hRBxooHtET|It~d5qr5f#pin4_#*oMhtWVdJLBe96u7kHY_VOXeu~{n<>Bf{r-vD%a(C3q#;!Se*Nq zI6zhQ;?UX&{aidT7_9e;_eBk$=-G84r;?%ui!}u$Qhybf5*5X+$as~4otZ>E+$Nd3 zTKn#(PY5xx!RI`n1wVD0ON^CjLlVPPai&h3#|&aK7&^(J+!OV3)R4CBGQw}b>Xd&PLJo4Y#xIiX?f3{;Kl=aM z%v-L}x?A+{x#-~}IU^Fl%dfc3u)@fCS{#1(DWkZMuQ~A|t zzE;|SH0L8?i>F260yf3s=oPwtTuyufqEMYAy(&Mu907YFR_faS8$K!BlmZy)_+H#HeD0weK*fYjm zZ$9`Kk>!k3BX{g+2{q7uK*gMLx~!U)n#vv_1cCUZJARqoJW*xea;<(P$Mt!21GSv( z&T;6g24>9n3C*fy%U(&T^#`Rj`{T3LWqW{-KPRz}T{taxSW3DFo6Vw6x@oc-t&X`& zFx7ITsPE33W)J3H%K@Yp&(o4H_rWxHZi}gUX=-Bc@9v{1Mk6v*JBsmS=eg;nc;DO- z;=AALV6_q0tT5GH5aZ@lEemuQAt5zRojfm}&!-4K$2Kd5j4O(7yShhQ>;lq_qVmI- z2|1*xpQoq&XoJF4ttBvj_nlXtNtz6UGK|wkc1*8S(R=TBo2nhuV=|;Li zI)?7<9)|9&p@;chzwi5(Yq@l0p8MQ;?m2s(vp0v(sgNW6%u{HY0Zgv4@G<*Lt<1Vs zG$+VXqrZPNpz?8}rMUK7DR{`?FXYc|B(zbU4Nl6OFkE{7N08@{-@sJ3 zx|0wFge*{d@{|GxyDf%EFO)JA5saq^nzuH|K#~nuXxwyh7O`BfT-m2vHqp1PWU1G4 zw`ro47C{@l88YOOMp-42A8)HIz8*|!2H*$14d33IXIm7dX`TnpEk=?hw=6N4wITXs zRb%MAqiVVRhvc3^tBT6H18mr>64-G@ZkHkev z>AMp_A;atN*G{-Pp%Fg)$-@MKj69h|l6EEIe=_`ib94`4@!YoTjfd@+iAoT9Qxwy9?{wgj73KB}votg3l%|cc;I*a8?)R zrRe;)i0Tzt5GKRI>#gt^J~Equ_Y$i7 zb=9$oodvyTs8$oIM$$ltk1`W9=JmiXLdJb)x@hbZYB9eIv@b_gc?b(^+8L|nvJ-ak zYZanGH`wMq^I6*wU7y&e<@y%hp!zm{Qs&LBsC%dKjLP_fZ6OCNp5Mu)4rz8 zoSBw8tHc@`3_ZDhM(2v&AI%rdo_v{-r?_UL+78x4Dtv*|g=BG(g${V6nYrBPU3_dXy0L3ji86Lb8wSd9oUAmHp3X?P~su)qe;kHv7t(dFs8v zKn7@d_hQ7L40^<8@Z9M6+LkhgZ^SFC*BJ1)leaxZ->Dky0D?NL8G|pP3_ZWxEaHyN zxRPOdywT&;85SV&YZXmf5|ndE^*MG{(~|vlj+lU!5E+&75xVcZAU?Nf^gq4 zlMn+>0NRU=(2lzKsc_RT3#&W7I_1w^h%bPIm+6_9AY*PF;u)8Jd-)xnzgHmoiA`r! zW^DN$#VT;+4c+3_!ga2^D^yX-Z%_$7I6;N?QpPY7Fu?b!i}Af8E?pJI$gVvXzz`QO z+;kxOk9g;3c;K&ovyDG|wQY6v?;UKp(uu3I`^WcEC*kyn#SLV97DcYW01z}gY{~`c ze^3#slvN?E?MgJN)5qIesrFO`7~F-3k34qJ43EnC$%YLGQ{F&nlKfs{%U5kL^0@6* zUcpS?H_JSqfUmuqjD(UMNd)m|qX&~lE&L%6mAMy($Hgq6NoE64*k?&Q@p3W1Jg<9i+y?d%K|pro;eu5v0f=^>V4`{dzPPg^gtFDiuf@PPnPYc45a=fc z4(j68hSHI=bd9tDA;{o(n9gHdd2FH`9pDhdQYqqj%Jl_+5}oEt4%-fzM4ib$SFE47 z6j*%hWqO6N$!7HxPaN3YD+BdR(3Nb5;$6wcRpQVLDHekp=XqJY0_q`|6v`hk+Hv3Y z;U1JJ)7qvOV7ZVw>AC;C><_sJ^u>TA+d&|V@!Y98ayfCEOsg~0Z4apU3@W`r!8PF*Y@(2%{6j))|wb3*eyZz<4e~1UZ^z zNKr4Kgf1EbA&HUOG6iW;{Fvun>z;B+h z5W5b;_qxyFiUV{v*hqIo@o~5w{dhcavOR=(2h^A&^90oY;71k6*{GWJ7&ngdtjO}M z2NFyi4FWF)zZSUzelHW=vlj-TCh;~GnOTo;XZ3FKxPC?$5U3=<8ZHRgSxK;RvzlWC^S zJ;^5VLkl)W294$Ad;kZ&+Y>;I>W$IO(I=A0@&yBed^CBrSz=0AXl9}%mxt#;-kT+$E;%6IhURKKCRg8jWYf=yUt2sMF)#1|JkPY&t2p|sx*s1WaeN<6eqi)tyjO6xb+~@S zHgfVVY6$JH1|DI0f}Nrnh`3yibmRFlm#nt0Ri0n5Gaeoz`Q6mh1AJ^%%^V!QNrA>% z;2(cH^uWyTZJ+oftSWxUeUAS7!iLL&Bu~I7wKP3{V|UnUd+80#@3eome82eSgL+sk9`tg z7bwH7A-7xAR}%@oc$*S*m$?!*_mH0MB=~Mqr;$xzNd0VO^}G2Ku90`BTTD>gGrdE7 zIG_@N{QBO!CfFnSW-eq>jP}JZLm*{lqndxBi^Wm#Xr4M#X~U}DBL8l-XFZ3#!{4eF zn-Q~sqK0bM4meUqyBrrh?UDy-Y7=j7oSeH`VQ=n1W-dqW{aGlKlnBLV%sL66ENm-% zJoDjCkdeh93fP$1GbwnMbjhDT)AG!J31UCRea@Ln-GIlqw!OlJ|LWfNt6$oY#kT$aiIbAFDf2kSbeH<7m zU~&6}nRF-Wna8a1-M1sDA{9`2+!^-D_;eN$Sk-%vw+Dk(ahJ>_|0tJ>c3!xKXAF6M4Z zw8j4vO)a1IaLG>KKQMF1Oc$E-MN_W;-(6KuD^IRZ{Q1K4P>h3xin9mh>Son@jwrc4 zARYLnZRX=sJIv;u8QXq3vp(L+TkBI5Ax5Ds^2xkUMA8n@UM=hYkp3d-V3sF#ncs!C zkbD6MN_fFo>&~U>vEst2zHt$Gz}q(uhpS)y0U(SCE+EKHl6d}rB<4$0k*F3XZHMQ* zdERq;o7wDWs(aPc+`P0@=uJwt*fcH>o&jGoN;9*0n)6dI#MH9#;RvvL3$hv6;-&cS z+Hb;f4G1%`?Y(MHTHQ2pyR+fJx7Pu_pQ?<*o~dO=Q-6LIZA|zss(jDFKZyBV9P9@o z?sFTBr;>y`Y>Ec|)Gkh}rZN9_B{8PmEvrL(+-DJ> zuS@c3ZhW#;4sDU1*w`Fjm(-O1a7_61UIFNO4SduLnhF{MoMc=4D>O|&!^Tg+th>(9 z(hi%@?R@7%8vrbSPD4AlTIj3b`5DZta5u86V`^`T1?s3J+?G8Bs&g1|Q-9p~X|#z1 z?nu(yPL{?GQ}lHeQR-ZCB^kSXcfMIZQ6{xfTtqm5@G(ye~X zK%clRv{W55gku_%gz-sr2n=f%`tZH-v5Q6E3_moAjfE43!JXx!)49&fl7tcny zCZBsLTJXGIMs@C?AyZRo)P}Tj816SC)Ah3KenoGgxJhwlN?3fZdjjs~H@T^>d6& zFCauc_f%3jM%=nSRz zA9VKot(0-Gkn6sCLt|dXmJkgf93=nM=%!98xf~1a(pY^;>>`nH?M{!eDhnW~rY#Cq z&t|)Y3qqHXd<;lsP_>+=KkxpB`g#(y*?4sUhU`D{>*Ja)`Ze(^*!ceQ1tWnykKY*# zeHbjA|73O(ZN_O{U28l``qpjCii+wDt~iG}Tp z-mEH2PN^Q^mruCp9%Su$^PuKB9ynA;b{>*jGbEaSeQKX?F#-?RhkJeB%_6b@?LaGx zC!AW#gNLs>OcFN65(Nk@4z7`Pd!~U}V(`|e=&Khv1#&FQ6Vut>3h_7D8YmfFVFhAR z!;0LjG^L=z1Y$|t?_XJtIsD@?0;^k?HNRurt7iQx?b@Q%1+m;k7Yt}?2tlH{peeR5 z7<({2CVYJJ{Eo$#(0J#_V&c+aXk5Q?LuTfwWN%$O>T3Qy=6Ea)1a#(^w8|sbz_gR< zE9`bl1(8z;TPb7GG(mBL<#LAWKSOc!Uo{$y>@X)?P1bd(>-J=FJQr4B_dd&HvRJ;N zN-_E0uL+EN!%xt;sU9byw36;T0cJ3fG(IkGL(1gH4q13CS9=p4c)s+LU3RO4GW{p< zKWWBMQJ)PNji)(mm73`;|79^Sm0(KCgcghAKn&i9PrUf>{KF8ibHVoo!eSTE{r&~Z zWv?o^_9|?hCIYRxGZ-1O5^1JxTKdxe;#n=k6!~TtdJ7!4R>2cp3X1INLH|_6r-8S; z{{t=}1ng&%f8OiC${WSttA0YqPbJ#&0dL5RlDd}di$d_Tyq`;@%3!o=geFnOeBP%X z`=bLx-*Vj~bmw%DbB?`vzZMiinEumWA)XQYz;H1jLm}G&?-LryX|PD#PN8wyq|NFV zx||#^jJa?E7Y*6omD4c8<+bShFwMDWs(heA@oqD7BsE8l@i>7qNASrX7nx9#%RN1% z^R+ngLy%+H-u^YE2P!ghjKu*%K>AM_0*Uzqfr!(A@rNTyzh5z*%y#v;|GqqO8bb5Z zHZTXsQ08%_MYSLVJ#_KIbtFduVtwu&j8iuHJtO*!&%c~`vrGsKIPI`NBe=$oregAtUw zVt5g{+D9x44}fpkW8e7lhykmINmeFXV>iCX!m1CE2f<|#j*xNpiv{pJ(>6DvBPh;-dV>_|#O zxpdp{V2wDBvTAZuH~e?BvCf>Fu=)S%d1*T4NlcHraGQVoDw9*TEjV84Fbt@7ow>n8 zarCJ@JcEfY&w1ErJ#M2qj2>8ghOUFI zI2{%T4g}NZQxD~n8>iTdy5Z~-{J!)EVlCX*_jE08=?xJE%;L`&2l?Nb`hFDRH(a%6 z!u2=5FQ)(3izD-%J=e))U7u+e-BU~sABfM}yb=UxW+W!%BMt=qfS^64?}J+!33j+} z>V*J=%c8}nKkFwU_ItopCtzd5?PNnBb|m``c_2p2U{G=6@kIW7aoPJgv~v1`!wh>Z zd6UO1Bdd1A_@D4Uyds9H7L?;~M7gDbKGVZf*l75x+I8ID5JCxBKfHVer>R!Aw8GNw z_(%Na_7ELLJb%VOs}(Pde=l7U*iD|(9uCaw&Ukw`R#-#04UKoAxO!tl-7;fLo{=9G z7HNL@H!iZnW(~TJ02xki)Pm7+4I7gq3k~!Qzenv~;OsplNkK53z@K*t5GEFa>)GUO zZVCn_s#eh`Z!rQ|h6g}l^m%+VV<2c4?sLHPAE*;H&mc+D#!Uszg_r=Hqxe>Wxd}8x z+umZoBY98ao7~_3Wrjahat-!~k=E7SW?^+7RR!9hp07!MzHAZy6mY4FFQ|4G-99CJ zn-MB>Z@W}*_X54Ru*g^QTZbUk+eB6ENh%&@22-$wa%@p*EVMY**cQG^qgceZV&kvB z>xDydoD`4jxDK0EcPO1dSWpX}P!5^E!YO;_2f_VQP+oo>9{K!-BBr!3>5s#g)I`$q zenyYprbM}&B_HcntJ@=`6gFI%0noHk-VwNLz*&9r2|MHFs6OD^OgajW~26-or6ck1^qkO3}AF}d`o!086npD7eJ^Hqe z-1_FE=X0@4AL~uAc41uSjbV5AMcc6|`?HH4#iTFWc{Iph?7MfPhgbLS1+&*qS?D#+ zP4`9a`=->lZs|)WxCHK`S0X~AqhVN!lAOWtFFN0f)>;*l)a6odH*LLFRIz{cW|*RE zn7Q}*xTh?%#<_jFJXn$xWLVb~Vr9Rp+%R6qd%l9^W5T*_Eua;VGyV3!3yg zHGp+evE0n9S++;2N*x9rJ3eG%XEA5X&mt9PGu>CG z@v@I7&bn1(O~692UpshD6BHaIVUd4f_J}F%42jaadso@Ww7Fg=^c+S{tQjqlB!*L7@Wf`z1_a;U1rE0#3hMK65Ti-usVB6A8u zhCbb=<#yu~z&g5Y{mi+GPuIq!Rc9xFuYk|&Ttf+~3M{sQtIEu3NmVS!=N%-qdco-? z>y^s$zE8}V?Itmp2CKd`y&QYEd*`h)L`Ea9w#tnU+4iPj3=KR`ZG;=EYo7GD3}sj{ z*tRJqe!(Yzhf#@s`!Gb@xp;8Tg&c9G@oG5I5~pfkO8mzmRjZ5w-to`D2s#wfno}V1 zwv{1N)ju5Ost>==7n@~K3qcuT*`_wKb8-%R5gKi5 zd9nKJw>_WiP;vAdOu=^sE3}kfuA&`KyzuKA$(BRP_V};lOIY9Q@4eso#jbuTlXN)= zjUM_hcA3NnD^1mronbx0lHJ70aHZR0*-i>~2u;H%MWhU;il%`WG`K6>7Eus6(@^{j zJ=B{isXs2<{f^!;M*Jlo(#8bNrZ%5kUeTxWwj=%QO^w!cPP}+AE1VODwOI4+y>I0r zxX8Kt$;^;_A)3X(KV?6q;D)Lm4R!pdhrmNy&bw0gpIvw9AFlpE$H;@MJyGRi83izlY|s zr!(dR{B`hT+pP|KusY|&w0iHFQFXP&hgn8CqIlJn({-_Lyo28Jvpmrr^mT#gzCw?M z0=vmx^Q60JlaGh*EP-G9^t*5yF#}F6^hwT5Z+OBKX9_kX{!_C1MqiNrLUZbED{#Cw zud;8$^e!A_SJ_~?zL39l4d3$a%A?S{JM=(|7P`IhL}#>gu%Qi|Qyfqp47v6)?y&|3k4lv<+XzADtart!WN)0UrBVQFPc z-EF}ZwhpXvNV{Kue$}`8Qv0Rn+nl8-{e6FTJ<4VD2<~`;bSS-m{dv=7$~ZQ~OHzJB za?0t$7R@maW^&zK`z}4!JQ}evyIU`M*UJj@m7j;psBYg}(EU{-tAw&_QTh0kGUF5e z5|LP!C}*<_4I?YJ!1XJfX)S)#i{Dv~&BvPAQcGKm8U&1GZ|K)$>QIeC%PVOv=`rQr zcIa!xhiRpVI_G6Y%uB|cbu^nypz)Bu+NBM9^GeD=jE4^AA8r-35j9-y7e6nfo}%9W zF~>7p!I`dCw+kTl@O6WS_!F0&BkiYbu)C_VBkf^Ad%mksk1ftV1y-I_raxk*hN*5+ zUr$T}q#-_dvpZXzpQ}j(_WDBm#Y8<9IPnEv-@m-wP3leXofj|BVAh$CTRP0Atqg0= zf!{l`D%qH*+=xSzx{KpJh7826dG6Z}9c<5(XIcJirT>O{b5iAZ!Ztg0oD>Gu$SCVL zw_T0-rbcNvQj-Bb;hXAO7baWCgn`Mpd91gUByDuHs}HTdTQ{v!u2zD3+%9cPu;9-T z$RYR?6PHVDT_p!&Z&VMb*!zp;qiN?d%AQ(2V9Ut`d#?^BPY&lD3z6BNnYI1GlbK_W8xQt(9ynNupH2B+ zoN)wfT_?=v%eqGje1&uF4By=eCr~8+qE3|-EIblX%TE7bTsac-9@$_0$&%Mvy|z8FROk3(d9e59Mjv^Hl-&tSe$ z0)~$|$4n=wn(LduqQ~4nu-w*c)$2*Jo7(4Ev&^Sp}Rb){53A4>?Vn z^!lion)EsIR<=Dn_b-)%c&71(B1#k6JH^$@olo|4ecB<=4kE}myOdn7&_xRQ-K1M_ z8&gXcMs6ngRrDpuW z)yOEE+!nb{A;NdBInwUTo5|y|_zs#Iwo|Vfy#y>v$tS$EdW$wpp&gem^tY2SAdJ@2 z|F#>&T>9Rjla;!F1@@sG_Z-y(->ax*lix!9$Cf99aS9c04pWeC+~4GuOEJg-&pybw zqsyp>PD*)lm2GTtSeO%a4d!VB4H@;c&|nDaSZ{{YllG($)$>qR%W|Fr=4LFikkp9r zySEBKFz`aLwhRj**E();IjHb$Zia6Q`|7Lni^Z1n5O<#MrVwG-v198Eo%rJ#^waA% z9vjc9rU@)ic}jG>0tTtF@xl!2?n*Q+A8S@-^`lc3RT>xBj79bNb%^g}8_J^u8B!5ruh6n*>1*ChK#DO2A{HYm*0=++1snDvA}6lfI86 z(HER2KAewfx+%0d>SKw&GF$Hc93c2A zff1h=T3zClNhPrvwdxxsygZncQ(LCORpxTw7c93B;+L?;qT$Fk=kv^^P+__2VltT} z+v6Qa@T%4{lPn`2BE!L|{G5-KalAPcGk18&e!Mi5fZO!w1+=bIdkHz)CYzk9fbV0eVng(9ZF573toOD-#!JdIm zh`hV^M-$#>B6WHDtJ&7GUYRL>6PzC@jw{+*q8QJ=h}AlO=>qrjCSLI$slQ2TSwXhA zUZi`xH&`ty;OQbZ`KWMZRxL+_Vrc%w`VmiF&RkO8;Rf0Mh_+4JGrmJ1JUTc|6 zOfpGM_5ue?Vd0so@9K42T7Y1%C%gX-N25wNl+kWiX-V#t_!Xnc725yiKKXLgsGC$$ zBx}XFhRt8!pFzn_KRFjG9^4GNzx_^86+Dw?Mkdb0 ztbXcTOodc@ZnRfHN;w!@Pm5rN6^~#WTHe$8R#erXHPi!k4IU(dY=1d>e#Mbf%4U}AQ{N^vk5PURWFxBJeay&snNcP8OT+>k2VvpNRCphNd2{mO%9Ds?R@jw z2>o|wmO}enGWyF=%6`+wUyi3toI(O>Mupr`GFMRf-%~;OKG;OegPr zSnWmAEiLnw{Q2`x-nnb^!gbyb--J?D=5Im_er3kPbuxQ!%MNvR&d16zUKtg`dWuzv@xZ|*znzdZ zHTlgq+g$ftr!EslQfKJ#sio{P6X%4c`P}_%-r#^Uvr#1Vhqeb{g2!b`yhb ztUiA=>G_t7&vnW@@6X7)hRJ<&m$PW$SL zY3a>L*9ljb*M!_zNq))JYhE8=zXcr5I7L&1kEEj;zkH>9C!@_q8Y67|_AtrSIal%k z$HdmLIRw!%;Wa*X9A&~Eu>vAwmbMU&w+H-YvAH&p6iscmkj7T(ep)7ZX_5BP?7ZP% zD=Yb8?O~x4y;$%xKKD0Se7W1j>Y>SK2Qi-Z!;g-$$hHqfYZHyZLRTMMsOrgC#T1WwR6CMpyg}yKgZG2XNIe#tmc6;QB9jjfBjEplb~Tf=LVV;YOjK2uX$Ud5zXLq%D zce)s7%a+US3^a3m6S!eX5GcDh5Ffga{VcDPggsdzqq#y2v64al7J5;7mo{Aeu^B`(J(%N3_k693NQ&6q zDQ^HVdFwA5%#O?^WC{5MSBkWh2TS6XHxV;)3`3-b14FJ&bTV}0aBvZCBqOpja{Nf% zqhPTM`V^fTGvFK9M&f+0OZ}#g&cL9HjU&#bhB6mbWYVZw1kGq1UXiKOw9Ve%^qRnR z_KHPw`c2b_gc!3kOxJ&YwvMr$^nOD7zDYE}c``#hD^9n}9onW71@g9wIBpOI>eTHv zO?%PUCF}w5CN9ruy3u*}Epd=Y<&BowMP%>!JHUmRv>4!xtjM_4OvOjhHXJ1NPU{(Z zrtS@{*;WYG|F>NzunwGo*M+4I(ar)6%{#|NN!t4(;2cfMF4#|Tu+%MSk;D4XDhI0b zG0BNGvOnu-Q8&ZT$%+HDhF1JE1yb6Nc;G#`O<%b_jYv9mzsct(-A$@}5_X(&wk|q~ z4mhdL+`W^t!TiN?OwCFJ7Q9jQzxRe7dSkC7BgIs1hm*4QHcfi308|4vjw7)5!@1Cvy4}vffFrg_ zs}_>N{v9Z`*Yl|=HUAM|U~#wHxQZ;b7%YJ*oejNGzMtQvmU%v|4Og8O)pxscWyN2> z!4PXM@nMv zuxVN|rL|pcW?a$Ys^jnEXiGQMT}krt6f{4ZyR%dhic8d5e`pju@VLp$(I9!q!L)3` zeYnW0q0h}PAT4{n`G%{3e8UT|1)XvWB;ssdYERNs!Da#l#=GvrhXk-QO{bpOFKOOh zpK*Why)Zh_D;z}~FQ3vU1x%5CBx2NnJzLJ!pDDRz#XGS?i%SEY_^461V)b=591L6sjuA)5^S)Z+FD z6MF!1F>+&(uIvhGIq*K_JW>F9AZ1C#x~|q(bv7H6lXev_KdH?MZb8wtxc;@q7nw}4 z72SjPO2X)_7d;j){!ab;amQJ0!WG`UNcV8yQ_J_yO0hcMig%*5JfKR>kyzc_j-A0W z9H#qKe%Bev!6GCy-EhQ5+;qlEA*1_{k1(X4Ahm^hDTi7q2RRY zgM31FYEqwfxYIDX4Vy2NCfSU|CYMSdMF2*1)LUjp!(@UsmfH=KD{rsY!t?Rxp-N)y znF4)xov7W=xdrfsQc)&^?^!sA(s|4lq@P}y+KW{AxSNagg zm@zKTiGIiX8;dmjQY)yz4qI3js(I{BEHRxJMD1fu|9t=KLzpb1lPJ|as9nwF_X%_-D5_k|VeS0K?PDovweBE;i)q^23m> zW3};H(fip=DW%>U)#31s46+}lgb@dEdC`1~{0Kz9Tch(*bVU$)eBm_FelOI-1t6J$ zUOcjQjoUTvr|y7{aff);tNWMT3S0-@Zk?o>54H*g7@pn)IB4|Ey(7I*YM7l`gU~CT z7a0i-21wx%M>sEF-(*)_2zF*>c+>gEIoaOsxaVt8H{Fcr8RXrz+Bqu=5d3+fQ1VbQ z^4^hL%WTCK(|M-ro6^)O@sh4Qn_7WR3J7G}xz^PrzAMG_6hPVF)G6G>cgEP83OlrN z+1BUpVQD+A4L;VLd_{GR>5#d_DM6qL2ID$EtRz-|ilW}>2#0cMpJQ^LJlPc( z_7#z3ktTl*`Rs%o@sBh0_J*Pk=6aeViue8p&uTT<32b9d;2V;3=PbH4uSqwyjP^$JVI2b4HH#-KC>{iIaeGpGSR_T#)5;l!`U##pD z$E@Nz4bkK6dfY(wXW>jEA|!n7?!8rH9Gp5go(vPCT{WBq>V54}XmMPU?vT_@0R!sI z8_91BrZ96Y8(^o{sxy!rw}0e3yDs>vL+jm}_v5gwl(zuTJJUKVYJL1it#N<7!R`av zE1|*VlKNZWXfaAfyZL7J_bG;g=@*FI@0YW0F+vO={HR z=e%3KXDkA=jpCL`VlM+ONkW5m_EX}-&?k3D?hok39~$^+M_%-Kxy-WjrdBv6BPFzh z*RwHR*3a(tX!%}xw3Qg`6QGm-<-4q6pWdT4X5lgUAJE#cT$US(vz&e(ch9-+`+D4O z2;v`TseieL%y6CRR!4Ba(3Oeuy-lDSrg6|@8wOwo02Vz`RTR&Nh)cvxdEW{roYw_=}f)tv`U5CgYSPc zZS=$>7H^mn6D+cQ;8-m`6{D=N0&w6(Cs$o#m_rC$6WlJ!tk<`4Ai!*MlRb<@*Vp(qY(=_^PM2=i>92<_5xUh z6$vCb%ySB#_Ap?7xjyO8Y9D&syuhMH0J4!&WK^+Qqli)B@!tb63+yCo`9tsI?$fCc z?Z-Ao8h70RijZLLv2l~usyl0#%aYWk4IeI}hjv1gG23Iv^X%)FKH%0(61q8C^myXZ zm^VexC$er3^$+w^x3vc4&H8F_l$iZ=xVw^P=9aMKBiUsg-Z>HkcD&6loxvLKh3O@8 z-Z{*-#KPQT`7R%__zQY5$Ja^SiP~w$r0~tw!`y9D6J$lgo*+{5y~j*PulWk zjBbSs9I7EfXpyLk$lyPS;;v=%wE>)596Rt8&(qN96lor2?=iw=TWlR`9pUhcoYM}4 zU}Ti@U6qZaJu9l-mKW~M$>qJULo{_eQ9sHjH0}w^wVsuKb)Fi%NBS|}OJw`vPf9U@ zWaDn(auNyh!6vtJ=Odmrz!FARdHklk?#2}x#_`sb zb7nuGUH)TQRU0fOlfbngHRkqazx<{?lYHLg!CMs5UB;f=@s3DR())K{edgQ*N^1^5 zE;Z2{#mdKVX!Ax-6sjJ%mNZkb#+vrM`DtTIN6DMmsE{g~4`<;a)|hYDntocQn8CxV z!xu|;C5{1E^D9z>#=B{0ji$e9-It`dU(-Y2=1ZJMJvY85$?$Lz*1we-ynsbaR#|`p z=kN^cX$(jqp@p#KX>(X=`Pzo)s94v3!G?5_X%Mg;?e)X!ojBq9T@gal^`n`R&;38e zPMFLnTnUE3zp(jVHa%iuJ`HR5>^#o3d+~AqTbPY0zk3*GvgapR%XcSZ19XNO^bLe(SA3#Unkhig=v z@O0v~__AcQKr6s*f`Vp2B(RgX-TWC&E@Ii%opmC>P;tOTdo{}$dsjNGOZVgMm1JlO zVH$+6ns7(5EA|B7U-l1P1vN1Vy&v+NC-@cC2{k*f!u!?3>?UA%kuej#(qOnBWa5eX z=16Vpbe3U>^thYwI^7Rj3|RrB@rjgwl7m}(C^qEjdeU6pvz<5wm$t>*6&d8A6IxyM zFp4jnmNHt|V9%2T#4I}zj3nQG7l`1zMMhs6q~b`rzV8_J_JRxPz`2*F zhc@nDLmL zRnJ6dJlYPahqBAfQC`I#&47=rdvySQdaOJ5a1A7>xA zfol3Gr8n!nA*_!(dN`P!<;Z^g{SNdHW&clJ0_cku_4O^^wUs(54H(rr_dRgTv}`^#<7lgB8-$jD2mfF(E@mZ=^s zxm)}tH>1tyJ@oZh>%Pun2Y?5h#MlcF`y)J_t5UR*_%F)#N@(}5N9}oq5(tgFO+3(t z*P~y%1nuvga;=GZ-;i)VeQs@zTEe_NP;2k7L+-3#H#vg*dw+9bdG*t#@CYF!{sqIj z#e!NasdzMXAw{AVpA3ABj3K++pI5y5*!`XEATvz}sA3t`#flaF!^pd#EeJPJvL9VI z&j^hb-4{s&he-EzweSdbqoyrL^=FNm+(&qd$k#I*EDid?0cxehpC-f^gJUnTuF-mMhoHkvMZsA z-225ICw!BX@`rb$qAtl=WEUaEH&?$*Mb}|Jr6;g{i|q{Yam;r;cWn06dbv^qfyBor zF>I>!9?R?440xhtYrdDxL_^g=-(&Cs2HxfSTsXFUqc19dXa$Zy! zGrTMaLZyR)SJ^XjB6*y-+6+*UJCL)PdvV`Nf~2Qx9Vj+OV8G)pqew@l4KwQ&b z5}DTY9W(&tM>Ob!C}_1E)QBix%Izm`4S4WD2ENNClqH9p4hFC|>`NS1n7HQdm29YX z#a_{U#RElOeuNxCrk8IVe>IfseO%7>adOB=oMRnD-L^BFDyZJpCa(AwsGJ@8uGjvW zf)%a&IDCS>c#y_L3W@Nm<#nIHmAr{>`l+v5+q<$qa>=OhIEhu$<8K(BVZV^E3-isW z9zF>b-o)-Vh?b?37x&WLhNb%)f zV;so~AI!2sAjy3-Lap3VdVMoBGDCKJrL>gkVXK_v2&{Dx5VcWx7sSdRrMIE;jfTa4 z?&UIB-OwWDLH4cEU^7|Sf9y#zEjSAJ+TEx}7!dMbVXKYL7YO9Id0-+H(N*NSg|vuk z#ycB6aP@-Dg~94)a*sD&T$78h(YT$o_%E#{34uE>xQ3VJClsl-2w-(R8y?i<1*XGV zml^9X7nB(vmo|+83KOEt&ioY+_{CwM4&n<|7%FSZzu6zbrE9XqmL{*#W-tX8$P*9pG`H|KHo!@L4AFW?IbZB?hV z_WIH+YPPLuyW2{Nsrpg-snoPBZ)Qt$xvQ0(ib2Qn5ESGCT6_0Qtd~la{zz${4zcphfh5E7}VdKBbQ3T6)U2b92kEXG9 z_Nv(ZN5L@(rm~}$7It=Y(;fp+0g37*Ex!K z)L1c35frS<8V}EzZNt}Hg3>QAE4)0BVqoi$(F><)L(@_T-J|udUWthrY|O0YiJH71U(5Vw*g7_{_W;1t8;ow}2dupQWW`RC^c+%eFu!pViG-!4T#eeqP+PAwl*d*$cg0%a0t;Aw zQIl-+B$Q`YEHSctPcz6N)z)Rmejyw{Ro9**he47_wOf6l;U|v_4lm{wn*;Bg${G|M z;<)mMw2+qDdBt$8(}J4~Ou4oz&(>q26r;LW>7PK6g6ncsxV5{9!6O~b2jGb&r>i6PM=CLY7fm&9?_{jv(oG#g z&&S(0{}qFk4=?vQ+M7WS>b^O*=NzGFrR$=r z0*D)X&RB1$jafI@tlx%&O%e%Q5kTTyxG7ODm0GJB3f!egcWxM=Q-u2aQhkEzX%E4+ z=nF21&sOK0SkNVNze`kSopmq!7YvFfN#L#cWzUI9%%9`bC z8hw%CAD$d$PJ5crX~S=?R32W|>@SokVnP6554s}S5zuQ-?!FBu;2p;&hz7%G0cv?U z3N?EzLzd#w?ez>TrWlxZU*|%;n9A5nZxno$(V6$^<-e~Yl5)!eC5C~8yHZis04r;5 zkWMWZIkuTY3h1E@yk^U<(@zfo9pzNQaZ>e{5YmA48V>K31O!&Y6*vKAF%n zAe;YOv;tP>?>_et0mNt4DRV3N7K5E8ygL684ps|K@!G^cA}m>5jTrRNp~ZjDQqqlz z0a~wcXI7wF(CrFs?9o9l3OL{#3Y0!4lWbpup*hJ#Av%CKJ>@cyhD3`qy41WcBPxsxr4LhZj4Wg2vaUvL{?fF zkC@B0WFwk*Y6hJf($IihSG>35n_sH=@f z=Pr0~|2oOKF}b32Etz#vQ4aH*%?%xSabyZupVNR5+S-Wd=n{55t?S7qy)&zSM1z@} ze#rZl7SgkfT8hNn!6QHy>064(LN)I6S@YIjM40wQg=6E<6{I?$BXhIXA~B=liaM}b zK5p^fdA1G;m6LaB(}*-}>Hqz|_P#5ssrGyG01605SM(DEg`hOy6BGj?iUQJ=9=cMc z_f7(cQU#?;2`VT`6(YUGf*7ixfOMou@4aO<{$~9z=HfqdH*3wBbD^vRPR@DHes_7E zXYYWgY5HN6Y(W``AIJ93<96h0ii@v|jcUuxK$@2?HgB`?e%|d~Q20I6X(BB=l+@_; zOr_ar$kntwYn+|e6>|p-w)1>*XauB+Cx3Asfp2(FP%9>I!+zsZZhR&5h`BMgHoSi4 zUt&#wtX^aKZwkYEs;e-G=m4eZaH)><`0J=nO(`z9wyq1GT$dA_!T+Z4+6&92Ly&9X zaF_0&E%0ezl>;`ahY-SigD-@+-u5L1wm~}-QXT?|Frzx-DIqrF`QH&8DA z{YT-OV|pjaA4`WApVPyyTzdjW3lGtd_L-LM$|x~l^g({5X9OD&ye_V0o^_83hAJXF zHSzI5;R);oBFfR73zX39jMbBFJiY9)PHBLnOs-8`ph<$SD@UAFDb0?c9z{Q5ezl9G zc7B^#vw$zemYAG!Tvrt0><)28P7HQJg!$#w=twR!V&j=}p-n=PuR46`GjWGv^5)hn z-i-YiYe{m_sp#Ukf)^R9kG1NMj+bKwa$%6tE4N~QgqwJk^Igw55NYvJ01zL7dd>VQ zdo&sZJoaY@Y!M*b5#yNM6b>#RBbqNyLC%gB0lH^MlJWvAIuh^;dKh^V#RLo<^%fbfa}O zb1d6bFwc3E0TFvW))#AN%yr_Km++Ze(osp>7v^Mo`}ddBvP-@Ct!t)iU*TXB%i{!r zk`_xEBsp4dM=E!0EqGeq<#S9Eeq4t>vADl_V>`4vQfQo4Ha9bTp8_eTjgLp8vX@^U zIE_ruK#<$sd3_FCp|4>3itO-C@1TFdcef#`kD^DjTb!jTX!N*~6Cd$vPyV`EiqwujfS+O#QGzU{KhZe^xZd>Rkyn-}=9wc}5A5v2S( zC{quvujRY8j8LwH2}Viej@gLkiK+wCi+i8FEf#HQ*_uRUVc%oAFDg|$RJIeh13PN? zz8Ne{J zza#G`*3>v=mj3a&HcP##>hIw&l^_zw^^m}axEReSlr_z79L04@ZseNev&q;!lDo6V zgbhDV_nDS*uX_=4Cbg!P=P2+jX-ZYcpx>m7^W{lImbQqp%@3Sil{gCYdvt z@tqq9l%Sr3VbJ!WR;n>=pm9h!E~A3E|2L1|s*S97Vo&e0gc8p{GRGscuFDsX>R!&7 z{3csqkt^*NdAiUD>`R!dVKj0d5^Vvy!%I(I5lpl_hGs+g!+`6kC zTp3^XIo2Fiay=k*=fgCOn7N6U#1RBG?sZz&v3XD4dVeBhF~7=eF}SgIun*kl1pc`ctbZ#OIzaEB@kU8pKRWt7ON-vZErPq#JD{lQ@u(TgM-F|J4Nt_aN`i zNq@$3?~1u$@2vyqErFA^z&FtewClahpGxGJ!=rpRqP5(|77{NWGz~P@UOwfJuFCwA zA0K+7?v#TFs9Qg(@t=cB`qwZgE=G)RAK{ zTnk)9SWXn(5h(~LP4@2!wsY_RQS~LK?tsicZ-i|!^Qk@)s1dOT;s(J&n8Q9u&V*Q z!`raGAUUJ74SmCv5+woz011Cxmo%Q2&KYzVMw7EXSqa3%y05jn^wZrxb4y%e1x?EU z&-Emrx2nq1!R3tLc|HLB0|z(^IN+m*j~a>~d_y+S%);eX4kis22N!(k;NOFn;b8sy zFCYBA+{sjv>wjNJK2;O;KYxUG;XUW2^8(LlsS${{aESG>Cm{%<`Drnzr|*MNMqdZF za&XoO0gB-PmcJjf^6>JD|D23I3ZeRYHJtb6xc}Qrm}q-tE#h0f1s}l`p@NuwlH<+6 zqe3R9^}l<-$H1j>fCzBzyE7MHaCDx(Dg5QWCMsV%1(_>72m_Fe_bGaEVR}0Tr=H!7 zt@r!85=IsPvsG=-3;3n@1|W#lDb#gD{i6PE=BR!H+}Xi}m6wNYQXJfy^S=V5d!}$3 z!_jRhvf2fwyioa8ECSCvQ`H7WmC2ZH1TU1(s?mZo#w>%4(T5PLpi2IvxLJ_SJ7miIfA z=Mt;#!bP3!%8j8O8BLT3krURneDh+dQDZMDBMwD=wKL_J0fN+54TiAJaN$;Jq7h|M z-04eu7ULRU6Xn8Y4*$V+8m{Jz75;s~MEg6MphhEc^MvS&z9o;^u7fD7=KKY;t~v(G zPA(q1;Cs2785WN9nDdc9AIOqJ6f!`ZZ9NxZEowbi=@i0zxWTuBVc|k$RTMQO1h9LX z>y}^tbV)0+6pcuT_YyMcBmlTo*}meLT$KOH9Dw474zg$|FiWt&W<2&wU3|$-HYL^z zt>vD@Jo2%0LI2g5zQ@pqo4pVJ+M?MT(>YDsm4dFiV=2;>k_-9;sJKb;UEXpX*WL4j zM^!Zl(-2T$Ju&M$P0#;0)XXT>oHtp`Gu4r4+y4HmLDFSF5@OHl?0DKhWOIQA>;Yo^ zEe@XHv%pMbJdy7mx}Giowr|GGQq!JEJ}_fA?@7WW22}HTv5|Po zk@90-06Gyy+b!$_0mzl9zBJJFcUGYcAVxPLadHlSg40= z=GZIi!eiR3^Rf>fLnozN819{i7Zq>FueFvRR^_9LTwFhPX}}~R#NpyT#OmNt0Iqa) z8gqdXUoLFCziK3-rF76{YTr5VBl=@iElcq6>woPkSzt&qIKQC4yOjCCuJ{ynXklB$;x(G6(io&T4Cgi38>75V;z`n6`HZYiu z^JpsOTlc$TVsQ^16?DF3mJ}cZ4}J$YC@6s3Aj(v z)|~`$Ol}W!`4FY&Vyy|ol(6M_J+o)mN6EqF`4gQ%hCkTE;`%%7a*~}H}I!}f?!kgLf1_&GmH6?O#C!FTJnBrgO1cgapBic zS6ubsSuNa|@R8p2|0p6jg}K;6ciV{cE*iVx8PHn@OOYX3uCtKIX^yu0lh!QhA-iW- zMWXgtet2GBqvVVe$UB^)ZC<>^U%tuJAgL0~UEhSkxWAP+eBlkW)6&Qhsgw)WxRVHE z%^BBxg=3}o%aXsC)JR*-Hr{vcl7P4u6?5`y&kd0AKa^H9UMT9L=s0&gVfVA^M4RD& z#Frl%xp?mSKJ3p<7mbES)?A-Xh8EGt)?mB2!xNyDBHsWhbcSC$t=%iJt6ObD`(Jbr zt8QiWx$V265#pu0d#)4ZjTxUcUCR)$s9bbOik`e;<>#8OGqpbNW$Jbiuf_svNI<1< zH?%tmbtcPSR|8=G15mMNlbm5Q_vHd3kPP~vmFERyO`Scd2p+pAW9R(Q;&?Xu(ltsuK!YO(CfUKK5>Do*&m?@{v02_mp^90F6l4spAk# zWLwk|nAq_$wjrnHV&-{-(3d{hf$$Z}zQcb_=cTr7Mh1XmCxW6xoRS7C-tR_p1DrKi z?1%QN)X5Of`++1!jYT=h$}dccD=u*rejwrAbblgXXtIu*!IE;;o)edAxpk40U} zw$7NEX-uaZp8@Z)*&}?9(&fTl=LPh>_04~=T>H*f*MaBRm5%|K4e zLUu9TbM3nJ6cMiMvMIc|912`w*Aq{Fwq$Q`q>LPd64@7{`brr0aWkwu)cQXilZ-7% zT(Sl&xzH^vgHyl8;XS4{f}4NDax^Pw^_>T=Wq4UJjNcX!AfTZ$3tsdL!4Sn45yOI#4I;DRP0*hn$|e0+8^I5t0q%8(OEyQgKb zIo6i;=<>@MUSBU6URH{rCXd6Ck{pi9Kob$^{iFClh+LLkJ5b@t|Nf2b29k77-g;z` z-w#W)YAuOUXac=|)+oj=d` zL?R0kb_J!SJVh`wZz}cA>ok$USjkIS%ocYN&k2)GL z?4!pY9E5anWyZJAL(VH^mu=bzpn4xy#)cw~&hJ@U6YvMJ61dLxfp2*nX%;#=x+F9% zDuMlDcL1A2@bkf=`YrE1#?_Jx&pM;+^kil{dz5gIksxdLHRS=|F|l75P1;s-GuzwO zMiYNn-CkwN5_$!9@+CucWgqO~WJ(r)*2$FTGsUiJqs5y_x_F+J?KYS@>R%UI(suc1 z3Oog^y>YVf5VSdTFgyYZCA5tqmhCgrVGhQ0ct6KFe?og8##MRW^Xi<#kk3 zLSL*5clhV0%J*v}$J>{_+)7FJySx{<6wY2YjREe{)VAj5X%Cf~k60njshD`V=bu6i z_+8$BzA4VGPl1@stZE!d<&X0E?*&`uhebm-tz;$qf~L3y)Tft$#No2LboXmw1O@+z z*12M7C-e78kQU_)Rf%KzPS`IXJbQw856fa1wuWb?RhM@(CVrkitw5i)YgA*pcDgIz z!r>!`bGy68RsWe8Kzq)QG*ZOXbSqbwXrmf56RU}LXTNEju(bEdoNT_;ht2v9zEku{ zQ1~NeLpL#&7+8CbXw-fYTi%35QA)CwZVYG>>{l~-gEO8r81iyn^(TzI{~YdCX^h=q zpO&}!cGoBl-698DdRC6NvA`pTc=j`klv(jLQ|E~+&v9FmCpmWCZPi)!%(bU$1<>ot zx&W{Ah5?1!;P*1x9A(*_pmhJeiL>Bfl}sfdDt10==HkEBMD2$dz?MqWOV53U2^Lo8 z*prESI*n}{6S@Acr!h0~9{Y^vXcPh{{@|?mmCCJ~vK%ij=#%O;LY?^BzQo38DA4O2 z_<`RF($!D9Lsle+43Q(7A7uW`!I!At5D~LF*0!D9Im>yxa7YlX+}N3xkY%M=DEYO+};4XNE(YkVR1#tvR-HC>TXX)D5YKVQ@?ZkX?2E; zyCBYp+{i*v5UU4YX%2DC+Nz@y-+Gr8kNvRnwy54;(|la(O|;1*@6!(M-+KAmiTtx5 zEQs-?-C++irmSfUND?9SWxFP0w}OA{4&EYCkMFfw z@QdP{38Ux5oU`gPb?1`^>tfc1;-!4sw^R7`NmqK6#Nn z%63-Iq5RcN^zRpow(7<$Y|nX~gwG7y)r|cj)Se?YG2BjX%^nHc7ZWvX%#&I(*q8MT z4jY$jE(UaAs%Z8i*6|Ywm-0TU1xAUuDdXoPDOLYvW0r#^Gzn)NF(HYcSI@Tz! z;gh(kmbQx{71a(Lp>}$8tgW$EcOa(qc>#=V$ei5dzozls=f!-%G=@~>OBhSr(n0Gb z64rywJ$QMk-&~~NAim|l2doSSnz8Dxdly}fY>2-Y4Mg0yg-E=|y7I%$>a!HdhBR-q z2J*|<3~Mf2QA`@(_L+C{xwMmYl7DAPx$$F6kNr$s)whhN*r=(f2VWC=!^BrdQ`%_5 zZMluizzaunx7^CO1Gh>SJ884iHgk&#^)3B}1XH2+`j+A5 zIqM^vBF(9gtewuei|)T|oB3L$0NCrc*u6W3N*LF1r=*J|j$?{T1~)nn)S7S!<9YcP ze5A2snBpNpzxUs7(?Z5L_}r?EE15TSb53)@w9Id0@7u4Bf^V>X86c~2w76<`dW$ym zQjjQxnJzr)k^*%yM*45s??j(`)exBWX&bj;hgTy#@aVtpb%I&9*v#BdyK#^N3gWg= ziw2UtHldODfZQ?80@WRh{9X%&^@%zy&p2m)LVkD?g{pkqjqivFAK%`F@E>oqH@dP zu9fW9a|YublZJGRFZk*T-R{Ab;1=)xOKgiY1gg-->x{TU+sX`R@F{zXajmNQ4gXBAv?hmI*%dd zy*8st@2c7L?oH^mTYib(MgrM$e7pY8M_V_Isa45}k12`gd3$yeYGFDOf8R?`$s~Um z+jRPx_>jVgY;1OMh{`z7sx_#7AA0BPC=tTQxB6N>wzoT0?Ii^kGO`h%Zi_>o1Kd$n z8}j_5?cJHux>3aeBcrvjY3Dl^{|TdqSt?@x)_~@l`BW#b-J(+J5o#(`%*8FBl%G3G^E9xs zoD0(0^4}a)PMiS|LhdxiOK32rg{Oy3pY-n82JOMfgxR?K{sxO><4_8O_!NEJLtrJz zppfgI?pR)&KV5Xt6e#XyUN@&t9rMwUykiq*nZCK`%1`_HGu z?`yDdg=&6FT*h|pI&+H4K-TdY&j!6SenAd8; zA^Jeigc(Re3v-2I@&i+y{;@64jBGwdc%B@L;wX56UKFL-uS_dxa8bqjn|OUN4~JO^ z^Qk%3D6%ZciAO0z%=o0?M7#&e4rBvXhC(W?Y6$WW%I+tLUA%MsYLtPIqtL4P^3ZkK zqV_J)jm*4QC{RFKN`D3$@W7QT$iEU%)A?J4z2BHI2$Hlwh>8GBJP_yH`mA{k)?E6YQ4uTcP6&b%2hNfU|MADqO~$ zb-Sme)u&Q2yF>HFUFW<$%Z&%(S9P+}i>2GscGY|KYFQS)q%N|J54*%Q}7+|dq)aKxI3R4te@vt@OzJhmkD2U>CJ4-d@;Stt#*Ct-bu{V%^9kHSCc?P1cT=NE2 zP`A2X0%k~OEicLZJQF_!Q9f?DCmA#Py3L55ROfPCfU@->P3_uI)k;LqpIyt|)4vesPdFB4(%YUXgltQcn!72iYeAy1F|Q%4nk-{sV> z=O_&PVL|dRJo4bMIVPKzf*4OV@8Vu2qd~2?)?X;-I3Rfd;%{q{0P29o zip+etN7?oVgnSvph#+6Sf4>J4rYn*N6CXXD7F}uwmd&V~ypb1me^#rN`?WH8r~O57;$OqY>YK2~P6Q1U4ZMN#shx6=DL?X^QA_U<6v4UZOJmoFGtug; z0Z^yO1)Qxx5awm2TQ{`re56U7upqJK>E8!G0O){n5~F98i`qFa7fgllLZ6EMK1|~r z%2X)tJgLWY#8F$^34jIqX)Dt6mUCWWqj=XOCLfno4v+^Pv;h#^3sO<%jReQQf2zQ&vjF9k`m^=}{v(3mUW1qj-sY@Abuc z!FQK7d3UD_93R&LNw{}!EAxRVgh$%}oA={3DTBJrnF?ZWPSUzXmcRn~0ImgWr_ifz zdi8wiL~KBRft4XS>jA4jgSexoZr+$?eER-&>;_3KkT>0S4)-Z+UorY-CluegI}<>1 zZalf{qKP(I%9C$9-Ozm>E`uvM)o?8@A?+X2`d?p0_Vc3$rzeZ;@;Nk@*zrq`8W`6z*wv*>-yftAB3*Drq)H1MPTF=)5I{ z#g9=#FuIZ=`6yN)u#6{$kU^>VLo z?#jF=j5Dx!;+A)W__@o>$cJBFcDuXlzB&L&?b}* zxsu*E+4@*dM(9D+b4~@HA)GQQK~9Kjtp#GvPMfH-`CbK_9Aud$jm+9lls*H!BTD4$W^y;%g=il$B+c9DG;Al0XGuGLLh^cG!r4%bT1EvTp~GSkxN;oj^;KEj62~t* zVzDTM_yH=>sncJ1zoxv&a>qWF8e~|D|3`j6omUL{oWCC5(;|yQYAPhLNrObG4@mZY z0lVE>bp#4hEAB&ih1c)xP#d<4r;DpIDe8%{XN;PD7`UuDNdfSz+9rkJ@ckUJyAynLT=f1zi(=DagthE3Zq`8T4P5d1)iGV7OmA+C zfk{!Ot8JTMW$viXdM1MWiNjNQ_qT~1*J(2#1>6?hDcbkFF(8b#>{i})b}jy5$yren z{Lk;j@~s=~?8tE1bZw8#RJ^}9do)v9>o)nGmsF zPM+svkZ!!P7Om7BEE@F86yQhl^tmH_Pd6tV#0RvSS;&h2#4fPZfh@g82So}%oSmCl zCiR1ySl$+3QS&191sU7P3q5gA0nO{l7%Ce!f?y6NC9GV>X;RCi!PyM*`bj^M97a z@qgX>HdibZQl2(mY=#xFo-;LFUcX7uy(1Skbiv2X;|_Qc(rH?VJ%P_?Ri_y9-;K8SA@_R63MLn`QC1qm}Yz;dE4Q%Hm?`_x$ey?et3EEth2kabJ@tTG#{Ua zu#xgEOU;YOaP{^>;%%fu1rr;IKq>+v9dTJjN^eQfQhj-6YZEu635NOkJNIxLwrb3{ z#dz`NMR&@>NK_-%#t}bpr)T}vdIoxK`^?Y?mWg3BH==4(kz-la`ELgMgwYHZg(Tgj zD}G{s)Z=P+pU?)nC|0X`86X$|xK2o1IfPeDTYKF>?-r%oNjSavPWq54B~RFXka%@E zNOVX>pXrCydn`L4pX5Jj_{O2nzhJ=!Cgh>)*!HNb-|OCSs%(#zUCD=~tr^d25oL$p z!z5H5GQ*k=L87eZQn5s3?di5oZzYItNd>uQUWX*LS+p0@c!8cwtN@J2R=sAvdAeEn z9~=epHGe)8Z^d}sHcxArKCKU?2w9OpfBZa^MIloIAs*6}v8ze0Om@RA;GhBIszA_6 zm5wV^4}F$ik8A%#i5#eMlMPp@=xRIL<-+`qC7q*i#scDSFX2_; z1(X!KB_VvqWJvtfG1?RqyzXTq2Ot(Z-osbays7F~Od~f!{4&efe!*#3$VS|s*NDW$ zQp?2l<1gZl+NM!}q6??rs$IQ$xwGPK);z4xT`0o3RZgPDk8q1`?X6&r2FLN^M*>h9 zt3GfJ+!%oTF8yq z$+klq>{B6cMv`8eGo=?CjKF!`Y?1WV>Jr86X&m&*-daB=u?7#lds%R5^O@0o#(he2 z4|HlGS80UxP@yK|k;%nbI$xiWr<4!M@(~Mq8|zxu30;x^x{E~bN17wU=7Hr8p(I)i zPP`dpc{{8RDcYt7aQ8g9(Y4~R^P75RUx9Qfg!z6u!Qe(7jo<@4SKD@&Vu2Sqs66y( z+v!?-Djl0`Dc^Ut?>*Z6UABhVq2V{bRLIC#_xyd7h_#&rNcHdUSds$3hwd(rddUVi z#<^6gO-uiB635!Q_)jyqGaah&7@|FMpj*_ZEgDs%i4BcRL^yjWX|F5u8M?tl-Ve1w z-~+cr;Nmi;G0Kfp?LJ=<_t^3u>ImLc&sDuED(**M&$UAgJSBjWr5C3@ zR~Su~C_8%?Y0d^%ocH1KGrerUs$q0svBsirk7QFYY#&Cl zlr?j;ob!TL?#>QGjsPKB34GM|XqlDV$nWvhTI-YTDM=xV@L7 zs3>dpnDcbwl2U0r22~|>8k-X+GLF-C?$YN$kSvA#Q=skYrf&tDWM_h?gdEIL97vh% zNcgAuSPtH=a0LJRnv4(ZYJ14B09mMXTEDsS=G0u;m49kRdLAD2hXj(VHk%ooFptc1 z?Na8Zm&$W0vdO{VDrm2eiauvrv3-wbI8gD4S!Vjz0mxD-V*x=5#Ncl3dN#;%cJ*mu zbzGRd(3=|>d-v8Uew{jmWcA~GI$?tBUROL=LtKn~jIm&qu_skrC7~1)Kq5JyhtmTJ z4kD08A9p_;Og#lcbn^<3*Vq7(UtgzE(AeTbOhpFb3@V2vN*-;4MdePWx!F{+`n;Pg}V*1}dt#?~6O| zCh}EK1f*eb@CS~T9{m5m=;Pp({}R^0&w!-C@!tvR;O+l^ii1T0`4AlcS-k&}8~)cT zprC`}|7bb?pF72c10(w1*wFup(f!{$h1q#Bw^Y=<@4b)sKwXI-O7iNr3gpa!{|6B^ BmhJ!m diff --git a/src/client/assets/styles/mobile.css b/src/client/assets/styles/mobile.css index 81cc969..d21d651 100644 --- a/src/client/assets/styles/mobile.css +++ b/src/client/assets/styles/mobile.css @@ -261,7 +261,7 @@ h3 { font-size: 1em !important; } display: flex !important; flex-direction: row !important; flex-wrap: wrap !important; - justify-content: center !important; + justify-content: flex-start !important; align-items: center !important; width: 100% !important; max-width: 100% !important; @@ -288,8 +288,8 @@ h3 { font-size: 1em !important; } } .inhabitant-action .filter-btn { min-height: 0 !important; - padding: 7px 12px !important; - font-size: 13px !important; + padding: 2px 9px !important; + font-size: .75em !important; white-space: nowrap !important; } .profile-sensors-box { @@ -315,8 +315,8 @@ h3 { font-size: 1em !important; } .activity-filter-col .filter-btn, .activity-sub-filter .filter-btn { min-height: 0 !important; - padding: 7px 12px !important; - font-size: 13px !important; + padding: 2px 9px !important; + font-size: .75em !important; white-space: nowrap !important; } @@ -450,10 +450,6 @@ h3 { font-size: 1em !important; } grid-template-columns: repeat(2, 1fr) !important; } -.tribe-media-grid { - grid-template-columns: repeat(2, 1fr) !important; -} - .tribe-overview-grid { grid-template-columns: 1fr !important; } @@ -466,24 +462,11 @@ h3 { font-size: 1em !important; } height: 180px !important; } -.tribe-votation-option { - flex-direction: column !important; - align-items: flex-start !important; -} - .tribe-content-header { flex-direction: column !important; gap: 8px !important; } -.tribe-content-filters { - flex-wrap: wrap !important; -} - -.tribe-media-filters { - flex-wrap: wrap !important; -} - .forum-card { flex-direction: column !important; } @@ -626,6 +609,7 @@ a.filter-btn, width: 100% !important; max-width: 100% !important; min-width: 0 !important; + flex: 0 0 auto !important; height: 42px !important; font-size: 16px !important; box-sizing: border-box !important; @@ -672,7 +656,7 @@ a.filter-btn, padding: 9px 16px !important; } .filters .ui-toolbar--filters { - justify-content: center !important; + justify-content: flex-start !important; align-items: center !important; gap: 6px !important; } @@ -728,6 +712,15 @@ a.filter-btn, max-width: 100% !important; overflow-wrap: anywhere !important; } +.card-field .card-value.user-link, +.card-field .card-value .user-link { + display: inline-block !important; + vertical-align: bottom !important; + max-width: 100% !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; +} .market-grid, .shop-products-grid, @@ -817,14 +810,14 @@ a.filter-btn, margin-left: 0 !important; } -.oasis-id-copy { +input.oasis-id-copy[readonly] { width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; font-family: monospace !important; - font-size: 12px !important; + font-size: clamp(8px, 2.5vw, 12px) !important; text-align: center !important; - padding: 8px !important; + padding: 8px 6px !important; border: 1px solid #FFA500 !important; border-radius: 8px !important; background: rgba(255, 165, 0, 0.08) !important; @@ -849,6 +842,7 @@ a.create-button { .peers-list .block-info-table, .peers-technical-block .block-info-table, +.parliament-rows-table, .gov-overview { display: block !important; width: 100% !important; @@ -859,17 +853,20 @@ a.create-button { } .peers-list .block-info-table tbody, .peers-technical-block .block-info-table tbody, +.parliament-rows-table tbody, .gov-overview tbody { display: block !important; width: 100% !important; } -.gov-overview thead { display: none !important; } +.gov-overview thead, +.parliament-rows-table thead { display: none !important; } .peers-list .block-info-table tr:first-child, .peers-technical-block .block-info-table tr:first-child { display: none !important; } .peers-list .block-info-table tr, .peers-technical-block .block-info-table tr, +.parliament-rows-table tbody tr, .gov-overview tbody tr { display: block !important; width: 100% !important; @@ -882,6 +879,7 @@ a.create-button { } .peers-list .block-info-table td, .peers-technical-block .block-info-table td, +.parliament-rows-table tbody td, .gov-overview tbody td { display: flex !important; justify-content: space-between !important; @@ -897,6 +895,7 @@ a.create-button { } .peers-list .block-info-table td::before, .peers-technical-block .block-info-table td::before, +.parliament-rows-table tbody td::before, .gov-overview tbody td::before { content: attr(data-label); flex: 0 0 auto !important; @@ -913,6 +912,121 @@ a.create-button { .peers-technical-block .block-info-table td[data-label=""]::before { content: none !important; } .peers-list .block-info-table td .peer-key, .peers-technical-block .block-info-table td .peer-key { text-align: right !important; } +.phone-history td.phone-dir, +.phone-contact .phone-dir, +.phone-dir { + display: none !important; +} +.phone-contact { + gap: 6px !important; + padding: 8px !important; +} +.phone-contact .user-link { + flex: 1 1 100% !important; + min-width: 0 !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; +} +.phone-contact .date-link { + flex: 0 1 auto !important; + font-size: 12px !important; +} +.phone-history, +.phone-history tbody { + display: block !important; + width: 100% !important; +} +.phone-history tr:first-child { + display: none !important; +} +.phone-history tr { + display: block !important; + width: 100% !important; + box-sizing: border-box !important; + border: 1px solid #444 !important; + border-radius: 10px !important; + margin: 0 0 8px 0 !important; + padding: 4px 10px !important; +} +.phone-history td { + display: flex !important; + justify-content: space-between !important; + align-items: baseline !important; + gap: 10px !important; + width: 100% !important; + border: 0 !important; + padding: 3px 0 !important; + font-size: 12px !important; + text-align: right !important; +} +.phone-history td::before { + content: attr(data-label); + flex: 0 0 auto !important; + font-weight: 700 !important; + text-transform: uppercase; + font-size: 10px !important; + letter-spacing: .03em; + opacity: .7 !important; + text-align: left !important; + white-space: nowrap !important; +} +.phone-history td.phone-row-actions { + justify-content: flex-end !important; + padding: 0 !important; + margin-top: -6px !important; +} +.phone-history .phone-row-delete .btn-singleview, +.phone-latest-row .phone-row-delete .btn-singleview { + width: 32px !important; + height: 32px !important; + min-width: 0 !important; + min-height: 0 !important; + line-height: 1 !important; + padding: 0 !important; + font-size: 14px !important; +} +.phone-history td.phone-row-actions::before { + content: none !important; +} +.phone-latest-row .phone-row-delete { + margin-left: auto !important; +} +.phone-history td .user-link { + min-width: 0 !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; +} +.peers-list .block-info-table td .ubi-tick-ok, +.peers-list .block-info-table td .peer-tor-no, +.peers-technical-block .block-info-table td .ubi-tick-ok, +.peers-technical-block .block-info-table td .peer-tor-no { + padding: 0 !important; + line-height: 1.2 !important; +} + +.conn-actions, +.peers-conn-actions { + display: flex !important; + flex-direction: row !important; + flex-wrap: wrap !important; + gap: 8px !important; + width: 100% !important; +} +.conn-actions form, +.peers-conn-actions form { + flex: 1 1 45% !important; + min-width: 130px !important; + max-width: 100% !important; + margin: 0 !important; + display: block !important; +} +.conn-actions form button, +.peers-conn-actions form button { + width: 100% !important; + min-width: 0 !important; +} .relationship-status .relationship-actions { flex-direction: column !important; align-items: stretch !important; gap: 8px !important; } .relationship-status .relationship-actions form, @@ -921,10 +1035,21 @@ a.create-button { .profile-sensors-box { align-items: center !important; text-align: center !important; } .profile-sensors-box > * { margin-left: auto !important; margin-right: auto !important; justify-content: center !important; } -.tribe-feed .refeed-column, -.tribe-feed-full .refeed-column { align-self: center !important; align-items: center !important; justify-content: center !important; } -.tribe-feed .card-footer, -.tribe-feed-full .card-footer { display: flex !important; flex-direction: column !important; align-items: center !important; text-align: center !important; gap: 2px !important; } +.profile-layout, +.profile-layout .tribe-side, +.profile-layout .tribe-main { + width: 100% !important; + max-width: 100% !important; + min-width: 0 !important; + box-sizing: border-box !important; +} +.profile-side-description, +.profile-side-description p, +.trending-card .card-section, +.feed-text { + word-break: normal !important; + overflow-wrap: break-word !important; +} .invite-qr-card { display: flex !important; justify-content: center !important; border: none !important; background: transparent !important; padding: 0 !important; box-shadow: none !important; margin: 0 auto !important; } .invite-qr-img { display: block; margin: 0 auto; } @@ -942,3 +1067,445 @@ a.create-button { .chat-messages-list { padding: 0 !important; } .chat-message { max-width: 92% !important; } .chat-message.chat-poll { max-width: 97% !important; } +.feed-workspace { + display: block !important; +} +.feed-side { + display: none !important; +} +.tribe-info-table, +.tribe-info-table tbody, +.tribe-info-table tr, +.tribe-info-table td { + display: block !important; + width: 100% !important; + max-width: 100% !important; + box-sizing: border-box !important; + text-align: left !important; + overflow: visible !important; +} +.tribe-info-table td .user-link { + display: inline-block !important; + max-width: 100% !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; +} +.school-lesson-summary { + flex-wrap: wrap !important; +} +.school-lesson-summary-title { + flex: 1 1 100% !important; + min-width: 0 !important; + white-space: normal !important; + overflow-wrap: anywhere !important; +} +.school-lesson-summary-meta { + flex: 1 1 100% !important; + white-space: normal !important; +} +.feed-votes-summary { + flex-wrap: wrap !important; + max-width: 100% !important; + min-width: 0 !important; +} +.feed-votes-summary .vote-category { + white-space: nowrap !important; + flex: 0 0 auto !important; +} +.tribe-open-invite-code { + font-size: .85em !important; +} + +.school-progress-row { + flex-direction: column !important; + align-items: flex-start !important; + gap: 6px !important; +} +.school-progress-issue { + width: 100% !important; +} +.school-progress-issue input[type="text"] { + max-width: 100% !important; + flex: 1 1 100% !important; +} + +.subscription-box-block { + display: flex !important; + flex-wrap: wrap !important; + align-items: center !important; + gap: 8px !important; +} +.subscription-box-block .subscription-form { margin: 0 !important; } + +.profile-module-section, +.profile-module-section .trending-card, +.profile-module-section .card-body, +.profile-module-section .card-section { + width: 100% !important; + max-width: 100% !important; + min-width: 0 !important; + box-sizing: border-box !important; +} +.card-section-action { + display: flex !important; + justify-content: center !important; + width: 100% !important; +} +.card-section-action .keep-reading-link { + text-align: center !important; +} +.card-field.image-container,.card-field.video-container,.card-field.audio-container { justify-content: center !important; } +.card-field.media-left { justify-content: flex-start !important; } + +.card-footer .date-link, +.card-footer .user-link { padding: 0 10px !important; font-size: 12px !important; } +.card-meta-row .opinions-summary, +.card-meta-row .comments-summary-link { padding: 0 10px !important; font-size: 12px !important; } + +.parliament-rows-table tbody td[data-label=""] { justify-content: center !important; } +.parliament-rows-table tbody td[data-label=""]::before { content: none !important; } +.parliament-rows-table tbody td .parliament-avatar-cell { + display: flex !important; + align-items: center !important; + gap: 8px !important; + justify-content: flex-end !important; +} +.parliament-rows-table tbody td .parliament-leader-avatar { width: 32px !important; height: 32px !important; } +.parliament-rows-table tbody td .parliament-method-img { max-height: 28px !important; width: auto !important; } +.parliament-rows-table tbody td form { margin: 0 !important; } + +.hemi-legend { + flex-direction: column !important; + align-items: flex-start !important; + gap: 6px !important; + justify-content: flex-start !important; +} +.hemi-legend-item { + width: 100% !important; + flex-wrap: nowrap !important; + justify-content: flex-start !important; +} +.hemi-legend-count { margin-left: auto !important; } +.hemi-modes { flex-wrap: wrap !important; } +.room-item .mailing-archive-head{flex-wrap:wrap} + +.oasis-nav-side-toggle { + position: absolute !important; + width: 1px !important; + height: 1px !important; + opacity: 0 !important; + pointer-events: none !important; +} +.oasis-nav-side-toggle ~ .header { + display: flex !important; + flex-direction: row !important; + flex-wrap: wrap !important; + align-items: center !important; + gap: 8px 6px !important; + padding: 8px 8px 4px !important; + min-height: 60px !important; + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + z-index: 900 !important; + box-sizing: border-box !important; +} +.oasis-nav-side-toggle ~ .header + * , +.oasis-nav-side-toggle ~ .header ~ .update-banner:first-of-type { + margin-top: 0 !important; +} +body:has(.oasis-nav-side-toggle) { + padding-top: 64px !important; +} +body:has(:is(input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]), textarea, select):focus) .oasis-nav-side-toggle ~ .header { + position: absolute !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left, +.oasis-nav-side-toggle ~ .header .top-bar-right, +.oasis-nav-side-toggle ~ .header .top-bar-mid { + display: contents !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left a.logo-icon { + order: 1 !important; + flex: 0 0 auto !important; + margin: 0 !important; + padding: 0 !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left a.logo-icon img.logo-icon { + width: 44px !important; + height: 44px !important; + margin: 0 !important; +} +.oasis-nav-side-toggle ~ .header .oasis-mobile-menus { + order: 2 !important; + display: flex !important; + flex: 1 1 auto !important; + flex-wrap: nowrap !important; + justify-content: flex-end !important; + align-items: center !important; + gap: 5px !important; + margin: 0 !important; + padding: 0 !important; +} +.oasis-nav-side-toggle ~ .header .oasis-menu-slot { + display: inline-flex !important; +} +.oasis-nav-side-toggle ~ .header .oasis-menu-btn { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + gap: 4px !important; + min-height: 44px !important; + padding: 6px 9px !important; + white-space: nowrap !important; + cursor: pointer !important; +} +.oasis-nav-side-toggle ~ .header .oasis-menu-slot .oasis-menu-close { + display: none !important; +} +#nav-left-toggle:checked ~ .header .oasis-menu-slot-left .oasis-menu-open, +#nav-right-toggle:checked ~ .header .oasis-menu-slot-right .oasis-menu-open { + display: none !important; +} +#nav-left-toggle:checked ~ .header .oasis-menu-slot-left .oasis-menu-close, +#nav-right-toggle:checked ~ .header .oasis-menu-slot-right .oasis-menu-close { + display: inline-flex !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav { + order: 3 !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-right nav { + order: 4 !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav, +.oasis-nav-side-toggle ~ .header .top-bar-right nav { + flex: 1 1 calc(50% - 4px) !important; + width: auto !important; + min-width: 0 !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav ul, +.oasis-nav-side-toggle ~ .header .top-bar-right nav ul { + display: grid !important; + grid-template-columns: repeat(3, minmax(0, 1fr)) !important; + gap: 4px !important; + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav ul li, +.oasis-nav-side-toggle ~ .header .top-bar-right nav ul li { + margin: 0 !important; + min-width: 0 !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav ul li a, +.oasis-nav-side-toggle ~ .header .top-bar-right nav ul li a { + position: relative !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + width: 100% !important; + min-height: 50px !important; + padding: 0 !important; + font-size: 0 !important; + text-decoration: none !important; + box-sizing: border-box !important; +} +.oasis-nav-side-toggle ~ .header nav ul li a .nav-text { + display: none !important; +} +.oasis-nav-side-toggle ~ .header nav ul li a .emoji { + font-size: 1.6rem !important; + line-height: 1 !important; + margin: 0 !important; +} +.oasis-nav-side-toggle ~ .header nav ul li a .inbox-badge { + position: absolute !important; + top: 3px !important; + right: 3px !important; + margin: 0 !important; + font-size: 0.66rem !important; +} +.oasis-nav-side-toggle ~ .main-content { + display: flex !important; + flex-direction: column !important; +} +.oasis-nav-side-toggle ~ .main-content .main-column { + order: 0 !important; +} +.oasis-nav-side-toggle ~ .main-content .sidebar-left, +.oasis-nav-side-toggle ~ .main-content .sidebar-right { + display: none !important; +} +#nav-left-toggle:checked ~ .main-content .sidebar-left, +#nav-right-toggle:checked ~ .main-content .sidebar-right { + display: block !important; + order: -1 !important; + width: 100% !important; + max-width: 100% !important; + padding: 0 4px !important; + margin: 0 0 8px 0 !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-header { + margin: 0 0 6px 0 !important; + padding: 12px !important; +} +.oasis-nav-side-toggle ~ .main-content .sidebar-left .oasis-nav-list, +.oasis-nav-side-toggle ~ .main-content .sidebar-right .oasis-nav-list { + display: grid !important; + grid-template-columns: repeat(3, minmax(0, 1fr)) !important; + gap: 8px !important; + margin: 0 !important; + padding: 0 !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-list li { + margin: 0 !important; + padding: 0 !important; + min-width: 0 !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-list li a { + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + gap: 6px !important; + min-height: 74px !important; + height: 100% !important; + padding: 8px 4px !important; + margin: 0 !important; + text-align: center !important; + font-size: 0.72rem !important; + opacity: 1 !important; + box-sizing: border-box !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-list li a .emoji { + width: auto !important; + font-size: 1.5rem !important; + line-height: 1 !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-list li a .nav-text { + line-height: 1.15 !important; + overflow-wrap: anywhere !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav, +.oasis-nav-side-toggle ~ .header .top-bar-right nav { + position: fixed !important; + bottom: 0 !important; + height: 66px !important; + width: 50% !important; + flex: none !important; + z-index: 900 !important; + margin: 0 !important; + box-sizing: border-box !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav { + left: 0 !important; + right: auto !important; + padding: 6px 2px 6px 6px !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-right nav { + right: 0 !important; + left: auto !important; + padding: 6px 6px 6px 2px !important; +} +.oasis-nav-side-toggle ~ .oasis-footer { + padding-bottom: 84px !important; +} +body:has(:is(input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]), textarea, select):focus) .oasis-nav-side-toggle ~ .header .top-bar-left nav, +body:has(:is(input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]), textarea, select):focus) .oasis-nav-side-toggle ~ .header .top-bar-right nav { + display: none !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-left nav { + width: calc(300% / 7) !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-right nav { + width: calc(400% / 7) !important; +} +.oasis-nav-side-toggle ~ .header .top-bar-right nav ul { + grid-template-columns: repeat(4, minmax(0, 1fr)) !important; +} +.oasis-nav-side-toggle ~ .main-content .sidebar-left nav > ul, +.oasis-nav-side-toggle ~ .main-content .sidebar-right nav > ul { + display: grid !important; + grid-template-columns: repeat(3, minmax(0, 1fr)) !important; + grid-auto-flow: row dense !important; + gap: 8px !important; + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-group { + display: contents !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-group > .oasis-nav-header { + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + gap: 6px !important; + min-height: 74px !important; + margin: 0 !important; + padding: 8px 4px !important; + text-align: center !important; + font-size: 0.68rem !important; + box-sizing: border-box !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-group > .oasis-nav-header .emoji { + width: auto !important; + font-size: 1.5rem !important; + line-height: 1 !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-group > .oasis-nav-header .nav-text { + flex: 0 0 auto !important; + line-height: 1.15 !important; + overflow-wrap: anywhere !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-group > .oasis-nav-header .oasis-nav-arrow { + display: none !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-group > .oasis-nav-list { + grid-column: 1 / -1 !important; + max-height: none !important; + overflow: visible !important; +} +.oasis-nav-side-toggle ~ .main-content .oasis-nav-toggle:not(:checked) + .oasis-nav-header + .oasis-nav-list { + display: none !important; +} +.oasis-nav-side-toggle ~ .header .oasis-phone-state { + order: 1 !important; + flex: 1 1 0 !important; + display: flex !important; + justify-content: center !important; + align-items: center !important; + align-self: center !important; + margin: 0 !important; + padding: 0 !important; + min-width: 0 !important; +} +.oasis-nav-side-toggle ~ .header:has(.oasis-phone-state) .oasis-mobile-menus { + flex: 0 1 auto !important; +} +.oasis-nav-side-toggle ~ .header .oasis-phone-state-btn { + appearance: none !important; + -webkit-appearance: none !important; + font: inherit !important; + display: inline-flex !important; + align-items: center !important; + gap: 3px !important; + min-height: 0 !important; + justify-content: center !important; + width: 38px !important; + height: 38px !important; + padding: 0 !important; + font-size: 8.5px !important; + line-height: 1.2 !important; + white-space: nowrap !important; + border-radius: 999px !important; + cursor: pointer !important; +} +.oasis-nav-side-toggle ~ .header .oasis-phone-state-btn .pm-exposition-icon { + font-size: 22px !important; + line-height: 1 !important; + margin: 0 !important; +} diff --git a/src/client/assets/styles/style.css b/src/client/assets/styles/style.css index 1382cff..3b5835a 100644 --- a/src/client/assets/styles/style.css +++ b/src/client/assets/styles/style.css @@ -394,6 +394,9 @@ nav ul li a:hover { min-width: 0; max-width: 100vw; } +.main-content > .sidebar-left { order: 1; } +.main-content > .main-column { order: 2; } +.main-content > .sidebar-right { order: 3; } .main-column pre, .main-column code { @@ -665,6 +668,49 @@ table th { background-color: #222; } +.main-column table th { + overflow-wrap: normal; + word-break: normal; + hyphens: auto; + white-space: normal; + line-height: 1.25; +} + +.main-column table td { + word-break: normal; + overflow-wrap: anywhere; +} + +.table-wrap { + max-width: 100%; + overflow-x: auto; +} + +.gov-overview { + table-layout: fixed; + width: 100%; +} + +.gov-overview th, +.gov-overview td { + padding: 8px 3px; + overflow-wrap: break-word; +} + +.gov-overview th { + font-size: 10.5px; + letter-spacing: 0; +} + +.gov-overview th:first-child, +.gov-overview td:first-child { + width: 15%; +} + +.gov-overview td:first-child { + font-size: 14px; +} + table tr:nth-child(even) { background-color: #444; } @@ -966,9 +1012,18 @@ article img { border-radius: 8px; margin: 8px 0; } +.trending-card .post-image { + width: auto; + max-height: 480px; + margin-left: auto; + margin-right: auto; +} .post-video { + width: 100%; max-width: 100%; + max-height: 480px; + height: auto; display: block; border-radius: 8px; margin: 8px 0; @@ -980,11 +1035,17 @@ article img { margin: 8px 0; } +.trending-card .post-image,.trending-card .post-video{margin-left:auto;margin-right:auto} +.trending-card .zoom-link:not(.zoom-inline){display:block;text-align:center} +.trending-card .zoom-link:not(.zoom-inline) img{display:inline-block} + .feed-container .post-image{max-width:100%;width:auto;max-height:480px;height:auto} .feed-container .post-video,.feed-container .video-container video{width:100%;max-width:720px;max-height:480px;height:auto} .feed-container .post-audio,.feed-container .audio-container audio{width:100%} .fediverse-network{border-radius:8px;padding:18px 22px;margin-top:10px;display:flex;flex-direction:column;gap:16px} +.fediverse-network > p,.fediverse-network form > p{margin:0} +.fediverse-network form{margin:0} .fediverse-network h3{margin:0} .fediverse-profile{display:flex;flex-direction:column;gap:24px} .fediverse-namerow{display:flex;align-items:center;gap:16px;flex-wrap:wrap} @@ -1013,6 +1074,13 @@ article img { .fediverse-profile,.fediverse-profile-id,.fediverse-namerow,.fediverse-stat,.fediverse-name,.fediverse-compact-actions,.fediverse-author,.fediverse-head,.fediverse-bio,.fediverse-stats,.fediverse-field,.fediverse-field-value,.fediverse-actions,.fediverse-compose,.fediverse-compose-actions,.fediverse-media,.fediverse-reach,.fediverse-preview-card,.fediverse-preview-head,.fediverse-disconnected{border:none !important;background:none !important;padding:0 !important;margin:0 !important;box-shadow:none !important;border-radius:0 !important} .fediverse-empty{text-align:center;padding:24px} .fediverse-error{color:#ff6b6b;margin:8px 0} +.telegram-unread-badge{background:#ffa300;color:#111;border-radius:10px;padding:1px 8px;font-size:.8em;font-weight:bold} +.telegram-preview{margin:0;opacity:.85} +.telegram-dialog .fediverse-avatar{width:44px;height:44px;border-radius:50%} +.telegram-message.telegram-out{border-left:3px solid #ffa300} +.telegram-text{white-space:pre-wrap} +.telegram-chat-title{display:flex;align-items:center;gap:10px;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;margin:0 0 10px 0} +.telegram-chat-title h3{margin:0} .fediverse-compose{display:flex;flex-direction:column;gap:8px;margin-bottom:18px} .fediverse-compose textarea{width:100%;box-sizing:border-box;resize:vertical} .fediverse-compose-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap} @@ -1127,6 +1195,7 @@ button.create-button:hover { .bookmark-url:hover { text-decoration: underline; } +.card-section.bookmark .bookmark-url{font-size:.95em;font-weight:600;margin:0;overflow-wrap:anywhere} .filter-box { display: flex; @@ -1150,18 +1219,12 @@ button.create-button:hover { vertical-align: middle; } -div:has(> .filter-box) { - border: none !important; - background: transparent !important; - padding: 0 !important; - box-shadow: none !important; -} - .filter-box div { border: none !important; background: transparent !important; box-shadow: none !important; padding: 0 !important; + margin: 0 !important; } .filter-box__controls .filter-box__input, @@ -1205,8 +1268,9 @@ div:has(> .filter-box) { } .filter-box__input { - width: 100%; - flex: 1 1 100%; + width: auto; + flex: 0 1 320px; + min-width: 160px; box-sizing: border-box; height: 42px; padding: 10px 12px; @@ -1226,7 +1290,7 @@ div:has(> .filter-box) { gap: 12px; align-items: center; margin-top: 0; - flex: 1 1 100%; + flex: 0 1 auto; flex-wrap: wrap; } @@ -1294,6 +1358,10 @@ div:has(> .filter-box) { .error-page-message{margin:8px 0 16px;white-space:pre-wrap;word-break:break-word} .error-page-actions{margin-top:16px} +.inline-error-box{border:1px solid #d9534f !important;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap} +.inline-error-box .error-page-message{margin:0;color:#d9534f;font-weight:bold} +.inline-error-box .inline-error-dismiss{display:inline-flex;align-items:center;padding:4px 14px;border:1px solid #d9534f;border-radius:6px;background:transparent;color:#d9534f;font-weight:700;font-size:.9em;text-decoration:none;cursor:pointer} +.inline-error-box .inline-error-dismiss:hover{background:#d9534f;color:#1a1a1a} .tags-container { display: flex; @@ -1309,6 +1377,12 @@ div:has(> .filter-box) { margin-right: 1em; margin-bottom: 0.5em; } +.tag-inline { + display: inline; + padding: 0; + margin: 0; + font-size: inherit; +} .tag-checkboxes-table { margin-top: 1rem; @@ -1343,14 +1417,36 @@ div:has(> .filter-box) { width: 300px; flex: 0 0 300px; } +.inhabitants-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px} +@media (max-width: 900px){.inhabitants-grid{grid-template-columns:1fr}} +.tribe-main .inhabitants-grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,260px),1fr))} +.inhabitants-grid > .inhabitants-card{margin:0;min-width:0} +.inhabitants-grid .inhabitants-card-body,.inhabitants-grid .inhabitant-card{padding:0 !important;margin:0 !important;border:0 !important;background:transparent !important;box-shadow:none !important} +.inhabitants-grid .inhabitant-card{flex-direction:column;align-items:stretch} +.inhabitants-grid .inhabitant-left{width:auto;flex:1 1 auto;min-width:0} +.inhabitants-grid .inhabitant-left > a:first-of-type{width:160px;height:160px} +.inhabitants-grid .inhabitant-photo-details{width:160px !important;height:160px !important} .inhabitant-left > a:first-of-type { margin-bottom: 0 !important; } .inhabitant-left > .status.you { margin: 0; } .inhabitant-left .profile-sensors-box { margin: 0; } .inhabitant-left .profile-sensors-box .profile-reach { margin: 0; } +.inhabitant-left .profile-reach { + margin: 0; + padding: 0 !important; + border: 0 !important; + background: transparent !important; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + width: auto; +} +.inhabitant-left .profile-reach .invite-qr-card { margin: 0; } .inhabitant-left > a:first-of-type { display: block; - width: 100%; + width: 256px; + height: 256px; } .inhabitant-left .profile-sensors-box a { display: inline; @@ -1371,6 +1467,7 @@ div:has(> .filter-box) { margin: 0 auto; } +.inhabitant-left-name{margin:6px 0 0 0;text-align:center;font-weight:bold} .inhabitant-photo-details { width: 100% !important; height: 100% !important; @@ -1424,6 +1521,10 @@ div:has(> .filter-box) { color: #e74c3c; } +.module-stats-line .stat-label-green{color:#2ecc71} +.module-stats-line .stat-label-orange{color:#f39c12} +.module-stats-line .stat-label-red{color:#e74c3c} + .profile-last-seen, .profile-device { display: block; @@ -1453,7 +1554,12 @@ div:has(> .filter-box) { .activity-search-input{min-width:240px;max-width:520px;width:100%} .post-text-pre{white-space:pre-wrap} .activity-image-thumb{width:100%;max-width:720px;height:auto;border-radius:14px;object-fit:cover} -.activity-avatar{width:256px;height:256px;} +.activity-avatar{width:256px;height:256px;object-fit:cover} +.about-card-cols{display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.about-card-cols > div{background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.about-card-col-img{flex:0 0 auto} +.about-card-col-desc{flex:1 1 240px;min-width:200px} +.about-card-header{margin:0 0 8px 0} .activity-pub{display:flex;flex-direction:column;gap:10px;align-items:flex-start} .activity-pub-title{margin:0} .activity-pub-title .user-link{display:inline} @@ -1500,6 +1606,11 @@ div:has(> .filter-box) { box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); } +.pdf-canvas-wrap { + height: 420px; + overflow: auto; +} + .pdf-viewer-container canvas { display: block; margin: 0 auto; @@ -1507,7 +1618,7 @@ div:has(> .filter-box) { background-color: #fff; } -.pdf-viewer-container div { +.pdf-viewer-container .pdf-controls { margin-top: 1rem; } @@ -1550,11 +1661,6 @@ div:has(> .filter-box) { padding: 20px; } -.no-cv { - padding: 20px; - text-align: center; -} - .checkbox-oasisContributor { display: table-cell; align-items: center; @@ -1587,6 +1693,21 @@ div:has(> .filter-box) { } .opinions-voting-collapse { margin: 8px 0; } +.card-meta-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:8px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;overflow:visible} +.card-meta-row .opinions-voting-collapse:not([open]){display:flex} +.card-meta-row .opinions-voting-collapse:not([open]) summary{display:inline-flex;align-items:center} +.card-meta-row .card-footer{display:flex;flex-wrap:wrap;align-items:center;gap:8px} +.card-footer .date-link, +.card-footer .user-link{display:inline-flex;align-items:center;box-sizing:border-box;height:34px;max-width:100%;min-width:0;margin:0;padding:0 14px;border:1px solid transparent;border-radius:5px;font-size:13px;line-height:32px;white-space:nowrap;flex:0 1 auto} +.card-footer .user-link{overflow:hidden;text-overflow:ellipsis} +.card-meta-row .opinions-voting-collapse{margin:0;width:auto;flex:0 1 auto;background:transparent;border:0;padding:0} +.card-meta-row .opinions-voting-collapse[open]{flex:1 1 100%;width:100%;margin-bottom:8px} +.card-meta-row .voting-buttons{margin:10px 0 0 0;padding:0} +.card-meta-row .voting-buttons form{margin:0;padding:0;background:transparent;border:0} +.card-meta-row .card-footer{margin:0 !important;padding:0 !important;border:0 !important;flex:1 1 auto;min-width:0} +.card-meta-row .opinions-summary{display:inline-flex !important;align-items:center;box-sizing:border-box;height:34px;padding:0 14px !important;border-radius:5px;font-size:13px;line-height:32px;flex:0 0 auto} +.card-meta-row .comments-summary-link{display:inline-flex;align-items:center;gap:4px;box-sizing:border-box;height:34px;padding:0 14px;border:1px solid rgba(128,128,128,.5);border-radius:5px;background:rgba(128,128,128,.12);font-size:13px;line-height:32px;text-decoration:none;flex:0 0 auto} +.card-meta-row .comments-summary-link:hover{background:rgba(128,128,128,.25);text-decoration:none} .opinions-summary { display: inline-block !important; width: auto !important; @@ -1621,6 +1742,8 @@ details[open] > .opinions-summary::after { content: " \25B4"; } list-style: none; } .comments-summary:hover { background: rgba(128, 128, 128, .25) !important; } +.keep-reading-link { text-decoration: none; color: inherit; } +.keep-reading-link::after { content: ""; } .comments-summary::-webkit-details-marker { display: none; } .comments-summary::after { content: " \25BE"; opacity: 0.85; } details[open] > .comments-summary::after { content: " \25B4"; } @@ -1687,39 +1810,12 @@ display:flex; gap:8px; margin-top:16px; margin-bottom: 8px; } -.tribe-feed { - background-color: #111; - padding: 16px; - border-radius: 8px; -} - -.tribe-feed-full { - width: 100%; - background-color: #222; - padding: 16px; - border-radius: 8px; - box-sizing: border-box; -} - -.feed-actions { - display: flex; - gap: 8px; - margin-bottom: 16px; -} - .vote-actions { display: flex; gap: 8px; margin-bottom: 16px; } -.feed-list { - display: flex; - flex-direction: column; - gap: 12px; - margin-bottom: 16px; -} - .feed-item { background-color: #1c1c1c; padding: 12px; @@ -1749,50 +1845,38 @@ display:flex; gap:8px; margin-top:16px; cursor: not-allowed; } -.tribe-feed .feed-row, -.tribe-feed-full .feed-row { - display: flex; - align-items: flex-start; - gap: 12px; -} - -.tribe-feed .refeed-column, -.tribe-feed-full .refeed-column { - flex: 0 0 68px; - min-width: 68px; - max-width: 68px; +.refeed-column { + flex: 0 0 80px; + min-width: 80px; + max-width: 80px; padding: 10px 8px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 88px; + box-sizing: border-box; + gap: 6px; } -.tribe-feed .refeed-column h1, -.tribe-feed-full .refeed-column h1 { - margin: 0 0 8px 0; +.refeed-column h1 { + margin: 0; font-size: 24px; } -.tribe-feed .refeed-btn, -.tribe-feed-full .refeed-btn { - padding: 6px 8px; +.refeed-btn { + padding: 6px 4px; font-size: 12px; width: 100%; -} - -.tribe-feed .feed-main, -.tribe-feed-full .feed-main { - flex: 1 1 auto; - min-width: 0; + white-space: normal; overflow-wrap: anywhere; - word-break: break-word; + line-height: 1.15; } @media (max-width: 720px) { - .tribe-feed .feed-row, - .tribe-feed-full .feed-row { - flex-direction: column; - } + - .tribe-feed .refeed-column, - .tribe-feed-full .refeed-column { + .refeed-column { width: 100%; min-width: 0; max-width: none; @@ -1800,60 +1884,10 @@ display:flex; gap:8px; margin-top:16px; } } -.tribe-feed .feed-actions, -.tribe-feed-full .feed-actions { - display: flex; - gap: 8px; - flex-wrap: wrap; - margin-bottom: 8px; -} - -.tribe-feed .feed-actions form, -.tribe-feed-full .feed-actions form, -.tribe-feed .refeed-column form, -.tribe-feed-full .refeed-column form { +.refeed-column form { margin: 0; } -.tribe-feed-compose { - display: grid; - grid-template-columns: 1fr auto; - gap: 12px; - margin-top: 14px; - align-items: end; -} - -.tribe-feed-compose textarea { - width: 100%; - min-width: 0; - padding: 12px; - border-radius: 10px; - border: 1px solid #444; - background: #222; - color: #eee; - resize: vertical; - min-height: 120px; - line-height: 1.45; - box-sizing: border-box; -} - -.tribe-feed-compose .tribe-feed-send { - height: 40px; - padding: 0 14px; - border-radius: 10px; - white-space: nowrap; -} - -@media (max-width: 720px) { - .tribe-feed-compose { - grid-template-columns: 1fr; - } - - .tribe-feed-compose .tribe-feed-send { - width: 100%; - } -} - .tribes-container{ display:grid; grid-template-columns:repeat(auto-fill,minmax(400px,1fr)); gap:20px; margin-top:16px; } @@ -2024,22 +2058,40 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; } .zoom-link{display:inline-block;border:none !important;background:transparent !important;padding:0 !important} +.internal-link{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border:1px solid rgba(128,128,128,.5);border-radius:10px;background:rgba(128,128,128,.12);text-decoration:none;vertical-align:baseline} +.internal-link:hover{background:rgba(128,128,128,.25);text-decoration:none} +.internal-link-kind{font-weight:700;letter-spacing:1px;font-size:.8em} +.internal-link-id{font-family:monospace;opacity:.75;font-size:.8em} .zoom-link img{cursor:zoom-in} +.link-lightbox .link-box{display:flex;flex-direction:column;gap:10px;width:min(92vw,640px);background:#1e1f23;border:1px solid #333;border-radius:10px;padding:18px;box-sizing:border-box} +.link-box-row{display:flex;flex-direction:column;gap:4px} +.link-box-label{font-weight:700;font-size:.8em;letter-spacing:.5px;text-transform:uppercase} +.link-box-input{width:100%;box-sizing:border-box;font-family:monospace;margin:0} -.lightbox-close { +.lightbox-close, +.lightbox-download { position: absolute; top: 15px; - right: 60px; width: 42px; height: 42px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; background: rgba(0, 0, 0, 0.6); border-radius: 50%; color: #fff; font-size: 1.5em; - text-align: center; - line-height: 32px; + line-height: 1; text-decoration: none; } +.lightbox-close { + right: 60px; +} +.lightbox-download { + right: 112px; + font-size: 1.15em; +} /* Opinions */ .voting-buttons { @@ -2098,9 +2150,13 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; .conn-actions { display: flex; + flex-wrap: wrap; + align-items: center; gap: 10px; margin-top: 10px; } +.conn-actions form { margin: 0; padding: 0; border: none; background: transparent; box-shadow: none; } +.peers-paused-note { margin: 0; color: #ffa500; } .inhabitant-action { display: flex; @@ -2216,6 +2272,8 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; background: transparent !important; } +.doc-export-actions.doc-export-left { justify-content: flex-start; } + .doc-export-actions form { border: none !important; background: transparent !important; @@ -2244,12 +2302,6 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; margin-top: 10px; } -.feed-actions { - display: flex; - gap: 10px; - margin-top: 10px; -} - .image-actions { display: flex; gap: 10px; @@ -2420,7 +2472,9 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; margin-top: 10px; background-color: #FFA500; border: none; - padding: 6px 12px; + padding: 4px 12px; + font-size: 20px; + line-height: 1.2; border-radius: 6px; font-weight: bold; color: #000; @@ -2535,7 +2589,6 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; } .market-tribe-card .market-card-kind{display:inline-block;font-size:10px;letter-spacing:2px;color:#888;text-transform:uppercase;font-weight:600;margin-bottom:4px} -.market-tribe-card .market-card-price{font-size:16px;color:#FFD700;font-weight:600;margin-top:6px} .market-tribe-card .market-card-seller{font-size:13px;color:#bbb;margin-top:4px;word-break:break-all;border:none;background:transparent;padding:0} .market-tribe-card .market-card-seller-label{color:#ff9800;font-weight:600} @@ -2668,14 +2721,14 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; display: flex; flex-direction: column; align-items: center; - gap: 10px; + gap: 8px; width: 100%; - margin-top: 10px; - padding: 10px !important; - border: 1px solid rgba(128, 128, 128, .25) !important; - border-radius: 8px; + margin-top: 0; + padding: 0 !important; + border: 0 !important; background: transparent !important; } +.inhabitant-user-actions .filter-btn{margin-right:0} .inhabitant-user-actions form { background: transparent !important; @@ -3032,7 +3085,7 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; display: flex; flex-direction: row; align-items: flex-start; - margin-bottom: 1.5em; + margin-bottom: 10px; } .mode-buttons-cols { flex: 1; @@ -3055,7 +3108,12 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; .comment-header { display: flex; align-items: center; - justify-content: space-between; + justify-content: flex-start; + gap: 10px; +} + +.comment-header .comment-votes { + margin-left: auto; } .comment-body-row { @@ -3203,10 +3261,6 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; margin-bottom: 16px; } -.tribe-forum-reply-form { - margin-top: 12px; - margin-bottom: 16px; -} .forum-comment.level-4 { margin-left: 80px; } /*blockexplorer*/ @@ -3407,7 +3461,11 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; background: #1e1f23; border: none; border-radius: 50%; - padding: 7px 12px; + min-width: 36px; + height: 36px; + box-sizing: border-box; + line-height: 1; + padding: 0 11px; cursor: pointer; font-size: 1.14em; color: #ffa300; @@ -3422,6 +3480,7 @@ width:100%; max-width:200px; max-height:300px; object-fit:cover; margin:16px 0; .btn-singleview:hover { background: #2d2e34; color: #ffd740; + text-decoration: none; } .btn-datagram { @@ -3738,68 +3797,9 @@ a.pm-btn:hover { text-decoration: none } } /*banking*/ -.addr-form-card, .addr-list-card { - border: 1px solid rgba(255,255,255,0.1); - border-radius: 12px; - background: rgba(255,255,255,0.03); - padding: 20px; -} - -.addr-form-card.wide { - padding: 22px; -} - -.addr-form .form-row { - display: grid; - grid-template-columns: 140px 1fr; - gap: 10px; - align-items: center; - margin-bottom: 12px; -} - -.addr-form .form-input { - padding: 0 14px; -} - -.addr-form .form-input.xl { - height: 50px; - font-size: 16px; -} - -.addr-form .form-actions { - display: flex; - margin-top: 8px; -} - -.addr-list-card .form-row { - display: grid; - grid-template-columns: 150px 1fr; - gap: 10px; - align-items: center; -} - -.addr-list-card .form-input { - padding: 0 14px; -} - -.addr-search input[type="text"]:focus, -.addr-form .form-input:focus { - outline: none; - border-color: #ffa300; -} - -.addr-form .form-actions .mini-btn { - background: #ffa300; - border: none; - color: #fff; - padding: 9px 14px; - border-radius: 8px; - cursor: pointer; -} - -.addr-form .form-actions .mini-btn:hover { - background: #ff7f00; -} +.bank-form{max-width:560px} +.bank-form input[type="text"]{width:100%;box-sizing:border-box} +.no-content-box{border:1px solid #333;border-radius:10px;padding:18px;margin:12px 0;opacity:.85} .bank-summary .bank-info-table tbody tr .synced { color: green; @@ -3812,11 +3812,21 @@ a.pm-btn:hover { text-decoration: none } } .bank-claim-ubi{margin:16px 0} -.bank-claim-card{border:1px solid #FFA500;border-radius:8px;padding:16px;margin:8px 0} +.bank-claim-card{background:transparent;border:0;box-shadow:none;padding:0;margin:0 0 14px} .bank-claim-card p{margin:4px 0} .bank-claim-btn{font-size:16px;padding:10px 24px;margin-top:8px} +.bank-claim-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.bank-claim-actions form{margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} .bank-eco-chart-block{margin:24px 0;border:1px solid #2a2a2a;border-radius:8px;padding:16px;background:#111} +.bank-eco-chart-zoom-link{display:block;cursor:zoom-in;text-decoration:none;border:0 !important;background:transparent !important;padding:0 !important} +.bank-range-tabs{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 12px;background:transparent !important;border:0 !important;padding:0 !important} +.bank-chart-empty-note{margin:6px 0 0;font-size:.9em;opacity:.75} +.bank-rules-list{margin:6px 0 14px 18px;padding:0} +.bank-rules-list li{margin:4px 0} +.bank-eco-chart-lightbox .bank-eco-chart-canvas-zoom{width:92vw;max-width:1400px;background:#111;border:1px solid #2a2a2a;border-radius:8px;padding:16px;box-sizing:border-box} +.bank-eco-chart-lightbox .bank-eco-chart-svg{width:100%;height:auto;display:block} +.addr-card-head .content-actions{margin:0;padding:0 !important} .bank-eco-chart-title{font-size:16px;margin:0 0 12px 0;color:#FFA500} .bank-eco-chart-canvas{width:100%;overflow:hidden} .bank-eco-chart-svg{width:100%;height:auto;display:block} @@ -3854,6 +3864,10 @@ a.pm-btn:hover { text-decoration: none } .eco-tax-chip-low .eco-tax-chip-value{color:#9aff9a} .eco-tax-chip-low:hover{background:#143820;border-color:#5aa066} .eco-tax-chip-mid{background:#2a2410;border-color:#a39134;color:#ffd95a} +.eco-value-chip{margin-left:8px;vertical-align:middle} +.log-window-note{display:flex;justify-content:center;margin:16px 0 8px 0} +.mobile-counters{display:none} +.log-window-chip .eco-tax-chip-value{font-family:inherit} .eco-tax-chip-mid .eco-tax-chip-label{color:#ffe9a0} .eco-tax-chip-mid .eco-tax-chip-value{color:#ffd95a} .eco-tax-chip-mid:hover{background:#3a3416;border-color:#c5b14e} @@ -3884,13 +3898,26 @@ a.pm-btn:hover { text-decoration: none } .melody-upload-submit{width:auto;display:inline-block;padding:6px 18px;align-self:flex-start} .melody-upload-label{color:#FFD700;font-size:13px;font-weight:600;display:inline-block;margin-top:6px} .melody-bcs-list{display:flex;flex-direction:column;gap:14px;margin-top:14px;background:transparent !important;border:0 !important;padding:0 !important} -.audio-container{display:flex;flex-direction:column;gap:8px;align-items:stretch;width:100%;background:transparent !important;border:0 !important;padding:0 !important} -.audio-container audio{width:100%} +.video-container{display:flex;flex-direction:column;align-items:center;width:100%;background:transparent !important;border:0 !important;padding:0 !important} +.video-container video{width:100%;max-width:720px;max-height:480px;height:auto} .audio-transcode-form{display:inline-block;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.audio-container{display:flex;flex-direction:row;align-items:center;gap:10px;width:100%;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;margin:0 0 8px 0} +.audio-container audio{flex:1 1 auto;min-width:0;width:100%} +.card-section .card-field.audio-container, +.trending-card .card-field.audio-container, +.opinions-card .card-field.audio-container{display:flex} +.card-section .card-field.video-container, +.trending-card .card-field.video-container, +.opinions-card .card-field.video-container{display:block} +.audio-transcode-form .filter-btn{background:transparent;color:inherit;font-size:.75em;padding:3px 10px;border:1px solid #555;border-radius:12px;white-space:nowrap;line-height:1.5} +.audio-transcode-form .filter-btn:hover{border-color:#ffa300;color:#ffa300;background:transparent} +.audio-transcode-form .create-button{margin:0;font-size:.75em;padding:3px 12px;border-radius:12px;border:1px solid #ffcc00;background:#ffcc00;color:#111;font-weight:bold;line-height:1.5;cursor:pointer} +.audio-transcode-run-form{margin:6px 0 16px 0 !important} .melody-bcs-card{background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:12px 16px} .melody-bcs-head{display:flex;flex-direction:column;gap:4px;background:transparent !important;border:0 !important;padding:0 !important} .melody-bcs-title{color:#FFA500;margin:0;font-size:17px} -.melody-bcs-meta{color:#bbb;font-size:13px;background:transparent !important;border:0 !important;padding:0 !important} +.melody-bcs-meta{color:#bbb;font-size:13px;background:transparent !important;border:0 !important;padding:0 !important;display:flex;align-items:center;gap:10px;margin:6px 0 0 0} +.melody-bcs-date{opacity:.75} .melody-bcs-player{margin:10px 0;background:transparent !important;border:0 !important;padding:0 !important} .melody-bcs-desc{color:#ccc;font-size:13px;margin:6px 0 0 0;line-height:1.5} .melody-bcs-actions{margin-top:10px;background:transparent !important;border:0 !important;padding:0 !important} @@ -3907,11 +3934,10 @@ a.pm-btn:hover { text-decoration: none } .pub-item{border:1;border-radius:10px;background:none} .snh-invite-box{border:1px solid currentColor;border-radius:8px;padding:16px;margin:12px 0;font-family:monospace} .snh-invite-name{margin:0 0 8px 0} +.snh-invite-code{word-break:break-all} .snh-invite-date{font-size:13px;opacity:0.7;margin:0 0 10px 0} .snh-invite-btn{display:block;word-break:break-all;font-size:14px;margin:0;cursor:pointer;background:none;border:1px solid #FFA500;color:#FFA500;padding:10px;border-radius:5px;width:auto;text-align:left;font-family:monospace} .error-box{background:#222326;border:none;color:#f5c242;padding:12px;border-radius:8px;margin-top:8px} -.error-title{margin:0 0 6px 0;font-weight:600} -.error-pre{margin:0;white-space:pre-wrap;font-family:monospace} .shop-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin:8px 0} .shop-card{border:1px solid #333;border-radius:8px;overflow:hidden;display:flex;flex-direction:column} @@ -3938,8 +3964,9 @@ a.pm-btn:hover { text-decoration: none } .shop-product-media{max-height:180px;overflow:hidden;display:flex;align-items:center;justify-content:center;background:#111} .shop-product-media img{width:100%;max-height:180px;object-fit:cover} .shop-product-body{padding:10px;display:flex;flex-direction:column;gap:6px} -.shop-product-price{font-size:18px;font-weight:bold;color:#FFA500} -.shop-product-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px} +.price-chip{display:inline-flex;align-items:center;width:auto;max-width:max-content;box-sizing:border-box;border-radius:10px;font-size:14px;font-weight:700;border:1px solid #a39134;background:#3a2300;color:#ffd700;padding:4px 12px;margin:6px 0 0 0;box-shadow:none;white-space:nowrap} +.time-chip{display:inline-flex;align-items:center;width:auto;max-width:100%;box-sizing:border-box;border-radius:10px;font-size:14px;font-weight:700;line-height:1.4;border:1px solid #5a5a5a;background:#3a3a3a;color:#ffd740;padding:4px 12px;margin:6px 0 0 0;box-shadow:none;white-space:normal;overflow-wrap:anywhere} +.shop-product-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px;padding:0;border:0;background:transparent;box-shadow:none} .shop-add-product{border:1px dashed #555;border-radius:8px;padding:16px;margin:12px 0} .shop-featured-products{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0} .shop-featured-item{display:flex;flex-direction:column;align-items:center;width:72px;text-decoration:none;border:1px solid #333;border-radius:6px;overflow:hidden;background:#111} @@ -3960,6 +3987,7 @@ a.pm-btn:hover { text-decoration: none } /* carbon footprint chart */ .carbon-section-title{font-size:18px;color:#555;margin:8px 0;font-weight:600} #carbon{scroll-margin-top:16px} +#wallet,#language,#ux,#theme,#wish,#workflows,#home-page,#ai,#logstream,#replication,#lan,#pm-visibility,#multiverse,#verification,#indexes,#export,#panic{scroll-margin-top:16px} .carbon-chart{margin:12px 0;background:transparent !important;border:none !important} .carbon-bar-label{display:flex;justify-content:space-between;font-size:14px;margin:4px 0 2px;background:transparent !important;border:none !important} .carbon-bar-track{background:#3a3a3a !important;border-radius:6px;height:22px;overflow:hidden;border:none !important} @@ -4202,12 +4230,22 @@ a.pm-btn:hover { text-decoration: none } .jobs-grid .tribe-card-title, .tribe-grid .tribe-card-title, -.trending-card .tribe-card-title { +.trending-card .tribe-card-title, +.trending-card .card-section > h2, +.data-card-title, +.job-section-title, +.melody-bcs-title { + overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } +.data-reasons-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;background:transparent !important;border:0 !important;padding:0 !important} +.data-section{margin-top:18px;background:transparent !important;border:0 !important;padding:0 !important} +.data-section-title{margin:0 0 8px 0;font-size:.95em;letter-spacing:.06em;color:#ffa500} +.data-top-best{margin-top:6px} +.data-reason-chip{opacity:1;border-color:rgba(255,163,0,.6)} .tribe-card-footer { display: flex; @@ -4270,6 +4308,8 @@ a.pm-btn:hover { text-decoration: none } gap: 6px; } .tribe-side-actions form > * { margin: 0 } +.tribe-side-center { flex-direction: column; justify-content: center; } +.tribe-enter-invite { border: none !important; background: transparent !important; box-shadow: none !important; padding: 0; } .tribe-side-actions select, .tribe-side-actions input[type="number"], .tribe-side-actions input[type="text"] { height: 36px; box-sizing: border-box } @@ -4329,7 +4369,8 @@ a.pm-btn:hover { text-decoration: none } .tribe-mode-buttons { display: flex; gap: 8px; - margin-top: 16px; + margin-top: 0; + margin-bottom: 10px; } .tribe-card-image-wrapper { @@ -4394,18 +4435,30 @@ a.pm-btn:hover { text-decoration: none } } .tribe-open-invite { - border: none; + display: block; + border: none !important; border-radius: 4px; - padding: 8px; + padding: 0 !important; margin: 6px 0; - background: #1e1f23; - text-align: center; + background: transparent !important; + box-shadow: none; + text-align: left; } .tribe-open-invite-code { - font-family: monospace; + display: block; + font-family: 'JetBrains Mono', monospace; + font-size: .95em; + color: #FFD700; + margin: 8px 0 10px; + user-select: all; + overflow-wrap: anywhere; word-break: break-all; - font-weight: bold; - margin-left: 6px; + background: #111 !important; + border: 1px dashed rgba(255, 163, 0, .5) !important; + border-radius: 6px; + padding: 8px 10px !important; + text-align: center; + box-shadow: none; } .tribe-open-invite-panel { margin: 6px 0; } .profile-side-version { margin: 4px 0 8px; font-size: 0.9em; text-align: center; } @@ -4454,15 +4507,24 @@ a.pm-btn:hover { text-decoration: none } .tribe-action-btn { padding: 6px 16px; border-radius: 6px; + font-family: inherit; font-size: 12px; font-weight: 700; letter-spacing: 0.5px; + line-height: 1.2; cursor: pointer; border: 1px solid #ffa300; background: transparent; color: #ffa300; + text-decoration: none; + display: inline-block; + appearance: none; + -webkit-appearance: none; transition: background 0.15s, color 0.15s; } +.tribe-action-btn:hover { + text-decoration: none; +} .tribe-action-btn.danger-btn, .tribe-card-actions .tribe-action-btn.danger-btn, .tribe-side-actions .tribe-action-btn.danger-btn, @@ -4492,6 +4554,8 @@ a.pm-btn:hover { text-decoration: none } color: #000; } .tribe-action-btn.danger-btn { border-color: #d9534f; color: #d9534f; } +.tribe-action-btn.success-btn { border-color: #2ecc71; color: #2ecc71; } +.tribe-action-btn.success-btn:hover { background: #2ecc71; color: #000; } .tribe-action-btn.danger-btn:hover { background: #d9534f; color: #000; } .calendar-interval-block { margin: 8px 0 16px; } @@ -4523,22 +4587,6 @@ a.pm-btn:hover { text-decoration: none } object-fit: cover; } -.tribe-feed-compose { - display: flex; - flex-direction: column; - gap: 8px; - margin-bottom: 16px; -} - -.tribe-feed-compose textarea { - width: 100%; - box-sizing: border-box; -} - -.tribe-feed-compose .tribe-feed-send { - align-self: flex-end; -} - .tribe-parent-box { margin-bottom: 12px; text-align: center; @@ -4591,9 +4639,12 @@ a.pm-btn:hover { text-decoration: none } .tribe-grid { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; } +@media (max-width: 900px) { + .tribe-grid { grid-template-columns: 1fr; } +} .tribe-section-nav { display: flex; @@ -4630,16 +4681,6 @@ a.pm-btn:hover { text-decoration: none } border-right: none; } -.tribe-section-nav .filter-btn { - padding: 8px 12px; - text-transform: uppercase; -} - -.tribe-section-nav .filter-btn.active { - background: #ffcc00; - color: #000; -} - .tribe-content-card { background: #1c1c1c; border: 1px solid #333; @@ -4654,7 +4695,7 @@ a.pm-btn:hover { text-decoration: none } .tribe-content-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; } @@ -4670,13 +4711,6 @@ a.pm-btn:hover { text-decoration: none } align-items: center; margin-bottom: 16px; } -.tribe-search-form{display:flex;gap:10px;align-items:center} - -.tribe-content-filters { - display: flex; - gap: 8px; - margin-bottom: 12px; -} .tribe-content-status { display: inline-block; @@ -4740,68 +4774,6 @@ a.pm-btn:hover { text-decoration: none } object-fit: cover; } -.tribe-votation-options { - display: flex; - flex-direction: column; - gap: 8px; - margin: 12px 0; -} - -.tribe-votation-option { - display: flex; - align-items: center; - gap: 12px; -} - -.tribe-votation-bar { - background: #333; - border-radius: 4px; - height: 24px; - position: relative; - overflow: hidden; - flex: 1; -} - -.tribe-votation-fill { - background: #ff6600; - height: 100%; - border-radius: 4px; - transition: width 0.3s ease; -} - -.tribe-fill-0 { width: 0%; } -.tribe-fill-5 { width: 5%; } -.tribe-fill-10 { width: 10%; } -.tribe-fill-15 { width: 15%; } -.tribe-fill-20 { width: 20%; } -.tribe-fill-25 { width: 25%; } -.tribe-fill-30 { width: 30%; } -.tribe-fill-35 { width: 35%; } -.tribe-fill-40 { width: 40%; } -.tribe-fill-45 { width: 45%; } -.tribe-fill-50 { width: 50%; } -.tribe-fill-55 { width: 55%; } -.tribe-fill-60 { width: 60%; } -.tribe-fill-65 { width: 65%; } -.tribe-fill-70 { width: 70%; } -.tribe-fill-75 { width: 75%; } -.tribe-fill-80 { width: 80%; } -.tribe-fill-85 { width: 85%; } -.tribe-fill-90 { width: 90%; } -.tribe-fill-95 { width: 95%; } -.tribe-fill-100 { width: 100%; } - -.tribe-votation-label { - min-width: 120px; - color: #ccc; -} - -.tribe-votation-count { - min-width: 40px; - text-align: right; - color: #9aa3b2; -} - .tribe-forum-thread { background: #1c1c1c; border: 1px solid #333; @@ -4818,33 +4790,6 @@ a.pm-btn:hover { text-decoration: none } border-radius: 0 8px 8px 0; } -.tribe-forum-reply-form { - margin-top: 16px; -} - -.tribe-media-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: 18px; -} - -.tribe-media-item { - background: #1c1c1c; - border-radius: 8px; - overflow: hidden; - border: 1px solid #333; - transition: border-color .2s; - padding-bottom: 8px; -} - -.tribe-media-item:hover { - border-color: #ffa500; -} - -.tribe-media-item a { - display: block; -} - .tribe-media-thumb { width: 100%; height: 160px; @@ -4859,50 +4804,6 @@ a.pm-btn:hover { text-decoration: none } box-sizing: border-box; } -.tribe-media-item img { - width: 100%; - height: 160px; - object-fit: cover; -} - -.tribe-media-item video { - width: 100%; - height: 160px; - object-fit: cover; -} - -.tribe-media-title-link { - color: #ffa500; - font-weight: bold; - text-decoration: none; - display: block; - margin-bottom: 4px; -} - -.tribe-media-title-link:hover { - text-decoration: underline; -} - -.tribe-media-item-info { - padding: 12px; -} - -.tribe-media-date { - margin: 6px 0 2px 0; - font-size: 0.85em; -} - -.tribe-media-author { - margin: 0 0 6px 0; - font-size: 0.85em; -} - -.tribe-media-filters { - display: flex; - gap: 8px; - margin-bottom: 12px; -} - .tribe-overview-grid { display: grid; grid-template-columns: 1fr 1fr; @@ -4940,22 +4841,6 @@ a.pm-btn:hover { text-decoration: none } color: #88cc88; } -.tribe-feed-compose { - display: flex; - flex-direction: column; - gap: 8px; - margin-bottom: 16px; -} - -.tribe-feed-compose textarea { - width: 100%; - box-sizing: border-box; -} - -.tribe-feed-compose .tribe-feed-send { - align-self: flex-end; -} - .comment-submit-btn { width: auto; max-width: 200px; @@ -5077,6 +4962,19 @@ a.pm-btn:hover { text-decoration: none } font-size: 1.15rem; font-weight: 600; } +.tag-cloud-item[data-weight="5"] { + font-size: 1.3rem; + font-weight: 700; +} +.tag-cloud-item[data-weight="1"] { color: hsl(200,70%,60%); } +.tag-cloud-item[data-weight="2"] { color: hsl(230,70%,60%); } +.tag-cloud-item[data-weight="3"] { color: hsl(260,70%,60%); } +.tag-cloud-item[data-weight="4"] { color: hsl(290,70%,60%); } +.tag-cloud-item[data-weight="5"] { color: hsl(320,70%,60%); } +.vote-outcome{font-weight:bold} +.vote-outcome-yes{color:#4caf50} +.vote-outcome-no{color:#e53935} +.vote-outcome-none{color:#ffcc00} .stats-karma-block { background-color: #222; @@ -5144,20 +5042,6 @@ a.pm-btn:hover { text-decoration: none } word-break: break-all; } -.map-wrap { - margin-right: 30px; - position: relative; - border: 0px; -} - -.map-img { - display: block; - width: 100% !important; - height: 100% !important; - max-width: none !important; - border: none !important; -} - .map-bar { display: flex; align-items: center; @@ -5481,80 +5365,552 @@ a.pm-btn:hover { text-decoration: none } .map-markers-list h3 { margin-bottom: 4px; } -.map-osm-embed { - width: 100%; - height: 450px; - border: 1px solid #444; - border-radius: 8px; - margin: 12px 0; +.map-viewer, +.map-stage, +.map-stage div, +.map-hero, +.map-toolbar, +.map-toolbar div, +.map-place-results, +.map-markers-list, +.map-markers-list div, +.map-detail-grid, +.map-picked, +.map-card-thumb .map-viewer { + margin: 0; + padding: 0; + background: transparent; + border: 0; + box-shadow: none; + border-radius: 0; + overflow: visible; +} +.map-marker-meta { + display: inline-flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; + font-size: 0.75rem; +} +.map-marker-meta a.user-link { + padding: 2px 8px; + font-size: 0.7rem; + border-width: 1px; } .map-viewer { width: 100%; - overflow-x: auto; - overflow-y: hidden; border: 0px; } -.map-popup { - display: none; - position: fixed; - top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0,0,0,0.65); - z-index: 1000; - align-items: center; - justify-content: center; +.map-toolbar label { + display: inline-block; + width: auto; + white-space: nowrap; } -.map-popup:target { - display: flex; +.map-toolbar .filter-btn, +.map-place-result, +.map-center-link { + white-space: nowrap; } -.map-popup-box { - background: #222; - border: 1px solid #555; - border-radius: 10px; - padding: 20px 24px; - max-width: 360px; - width: 90%; - color: #ddd; +.map-stage { position: relative; + width: 100%; + background: #16232e; + border: 1px solid #333; + border-radius: 8px; + overflow: hidden; + line-height: 0; } -.map-popup-close { +.map-stage-thumb { + border: 0; + border-radius: 0; +} +.map-svg { + display: block; + width: 100%; + height: auto; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; +} +.map-pins-layer { position: absolute; - top: 10px; right: 14px; - color: #FFA500; - text-decoration: none; - font-size: 1.2rem; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 4; + pointer-events: none; +} +.map-pins-layer a { + pointer-events: auto; +} +.map-ocean { fill: #a7c6de; } +.map-tile { image-rendering: auto; } +.map-grat { fill: none; stroke: rgba(255, 255, 255, 0.4); stroke-width: 0.6; } +.map-border { fill: none; stroke: rgba(72, 52, 32, 0.65); stroke-width: 0.8; stroke-linejoin: round; } +.map-lake { fill: none; stroke: rgba(36, 86, 128, 0.7); stroke-width: 0.7; stroke-linejoin: round; } +.map-state { fill: none; stroke: rgba(72, 52, 32, 0.5); stroke-width: 0.6; stroke-dasharray: 3 2; } +.map-coast { fill: none; stroke: rgba(36, 86, 128, 0.75); stroke-width: 0.8; stroke-linejoin: round; } +.map-label-country { fill: #262c31; font-size: 14px; font-weight: 600; letter-spacing: 0.6px; text-anchor: middle; paint-order: stroke; stroke: rgba(255, 255, 255, 0.85); stroke-width: 3px; stroke-linejoin: round; } +.map-place-dot { fill: #1f252a; stroke: #ffffff; stroke-width: 1; } +.map-place-capital { fill: #d35400; } +.map-place-label { fill: #1f252a; font-size: 12px; paint-order: stroke; stroke: rgba(255, 255, 255, 0.85); stroke-width: 3px; stroke-linejoin: round; } +.map-place-label-capital { font-weight: 700; } +.map-pin-body { fill: #3498db; stroke: #ffffff; stroke-width: 1.5; } +.map-pin-main .map-pin-body { fill: #e74c3c; } +.map-pin-focus .map-pin-body { fill: #FFA500; stroke: #ffffff; stroke-width: 2.2; } +.map-pin-eye { fill: #ffffff; } +.map-pin-label-bg { fill: rgba(8, 12, 16, 0.86); stroke: rgba(255, 255, 255, 0.35); stroke-width: 0.6; } +.map-pin-focus .map-pin-label-bg { stroke: #FFA500; stroke-width: 1.2; } +.map-pin-label { fill: #ffffff; font-size: 12px; font-weight: 700; text-anchor: middle; } +.map-pin-img-frame { fill: #ffffff; } +.map-pins-layer a:hover .map-pin-body, .map-pins a:hover .map-pin-body { fill: #FFA500; } +.map-cluster-ring { fill: rgba(255, 165, 0, 0.28); } +.map-cluster-dot { fill: #FFA500; stroke: #ffffff; stroke-width: 1.6; } +.map-cluster-count { fill: #000000; font-size: 13px; font-weight: 700; text-anchor: middle; } +a:hover .map-cluster-dot { fill: #ffffff; } +.map-pick-ring { fill: none; stroke: #FFA500; stroke-width: 2; } +.map-pick-cross { fill: none; stroke: #FFA500; stroke-width: 2; stroke-linecap: round; } +.map-pick-center { fill: #ffffff; } +.map-scale-line { fill: none; stroke: #1f252a; stroke-width: 1.6; } +.map-scale-text { fill: #1f252a; font-size: 11px; font-weight: 600; paint-order: stroke; stroke: rgba(255, 255, 255, 0.85); stroke-width: 3px; } +.map-attrib { fill: #3b434a; font-size: 9px; text-anchor: end; paint-order: stroke; stroke: rgba(255, 255, 255, 0.75); stroke-width: 2px; } +.map-pick-grid { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: grid; + grid-template-columns: repeat(24, 1fr); + grid-template-rows: repeat(15, 1fr); +} +.map-pick-cell { + display: block; + background: transparent; + border: 0; + margin: 0; + padding: 0; + min-width: 0; + min-height: 0; + width: 100%; + height: 100%; + cursor: crosshair; + border-radius: 0; + box-shadow: none; +} +.map-pick-cell:hover { + background: rgba(255, 165, 0, 0.22); + outline: 1px solid rgba(255, 165, 0, 0.7); + outline-offset: -1px; +} +.map-controls { + position: absolute; + z-index: 3; + display: flex; + flex-direction: column; + gap: 4px; line-height: 1; } -.map-popup-label { - font-weight: 600; - font-size: 1rem; - color: #eee; - margin-bottom: 6px; +.map-controls-zoom { + top: 10px; + left: 10px; } -.map-popup-coords { - font-family: monospace; - font-size: 0.85rem; - color: white; +.map-controls-pan { + top: 10px; + right: 10px; + display: grid; + grid-template-columns: repeat(3, 30px); + grid-template-rows: repeat(3, 30px); + gap: 2px; } -.map-popup-link { +.map-ctrl { + width: 30px; + height: 30px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(18, 18, 18, 0.88); color: #FFA500; - text-decoration: underline; - word-break: break-all; + border: 1px solid #555; + border-radius: 6px; + text-decoration: none; + font-size: 17px; + font-weight: 700; + font-family: inherit; + margin: 0; + padding: 0; + cursor: pointer; + box-sizing: border-box; +} +.map-ctrl:hover { + background: #FFA500; + color: #000; + border-color: #FFA500; +} +.map-ctrl-off { + opacity: 0.35; + cursor: default; +} +.map-ctrl-off:hover { + background: rgba(18, 18, 18, 0.88); + color: #FFA500; + border-color: #555; +} +.map-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + margin: 10px 0; +} +.map-toolbar form { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 0; +} +.map-toolbar label { + margin: 0; +} +.map-toolbar select, +.map-toolbar input[type="text"] { + height: 34px; + box-sizing: border-box; + margin: 0; +} +.map-toolbar .filter-btn { + height: 34px; + box-sizing: border-box; + display: inline-flex; + align-items: center; + margin: 0; +} +.map-place-search input[type="text"] { + min-width: 220px; +} +.map-place-results { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 6px 0; + align-items: center; +} +.map-place-result { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border: 1px solid #555; + border-radius: 14px; + color: #FFA500; + text-decoration: none; + font-size: 0.85rem; + background: transparent; + font-family: inherit; + cursor: pointer; + line-height: 1.3; +} +.map-place-result:hover { + background: #FFA500; + color: #000; + border-color: #FFA500; +} +.map-place-result-country { + opacity: 0.75; + font-size: 0.8em; +} +.map-place-none { + color: #888; + font-size: 0.85rem; +} +.map-hero { + margin: 0 0 12px 0; +} +.map-detail-grid { + display: flex; + gap: 24px; + align-items: flex-start; +} +.map-detail-side { + width: 320px; + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 16px; +} +.map-detail-main { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 16px; +} +@media (max-width: 900px) { + .map-detail-grid { + flex-direction: column; + } + .map-detail-side { + width: 100%; + } +} +.map-markers-list .map-marker-row { + display: grid; + grid-template-columns: 28px minmax(0, 1fr) auto; + align-items: center; + gap: 12px; + padding: 10px 12px; + border-bottom: 1px solid #333; + text-align: left; +} +.map-markers-list .map-marker-row:last-child { + border-bottom: 0; +} +.map-marker-body { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} +.map-marker-date { + color: #888; +} +.map-marker-meta a.user-link { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + box-sizing: border-box; +} +.map-form-actions { + margin-top: 14px; +} +.map-form-actions .create-button, +.map-form-actions .filter-btn { + height: 36px; + margin: 0; + padding: 0 18px; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-size: 0.9em; + line-height: 1; + white-space: nowrap; + text-decoration: none; +} +.map-popup { + pointer-events: auto; +} +.map-popup-pointer { + fill: #1e1f23; + stroke: #555; + stroke-width: 1; +} +.map-stage .map-popup-wrap { + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + line-height: 1.35; + font-family: inherit; +} +.map-stage .map-popup--below .map-popup-wrap { + justify-content: flex-start; +} +.map-stage .map-popup-card { + background: #1e1f23; + border: 1px solid #555; + border-radius: 8px; + padding: 10px 12px; + color: #fff; + font-size: 13px; + box-sizing: border-box; + max-height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + gap: 6px; + text-align: left; +} +.map-stage .map-popup-card > * { + flex-shrink: 0; +} +.map-stage .map-popup-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} +.map-popup-close { + color: #aaa; + text-decoration: none; + font-size: 20px; + line-height: 1; +} +.map-popup-close:hover { + color: #FFA500; + text-decoration: none; } .map-popup-img { - display: block; - max-width: 180px; + width: 100%; max-height: 120px; object-fit: cover; border-radius: 4px; - margin: 6px auto; -} -.map-marker-img { display: block; - max-width: 120px; - max-height: 80px; +} +.map-stage .map-popup-text { + color: #ddd; + word-break: break-word; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + overflow: hidden; +} +.map-popup-text img { + max-width: 100%; + max-height: 100px; +} +.map-popup-coords { + font-family: monospace; + font-size: 12px; + color: #fff; +} +.map-stage .map-popup-meta { + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; + font-size: 12px; + color: #aaa; +} +.map-popup-meta a.user-link { + padding: 2px 8px; + font-size: 0.7rem; + border-width: 1px; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + box-sizing: border-box; +} +.map-stage .map-popup-actions { + display: flex; + gap: 6px; + align-items: center; +} +.map-stage .map-popup-actions form { + margin: 0; +} +.map-popup-actions .btn-singleview { + min-width: 30px; + height: 30px; + font-size: 1em; +} +.map-markers-list .map-marker-row--focus { + background: rgba(255, 165, 0, 0.08); + box-shadow: inset 3px 0 0 #FFA500; +} +.map-marker-idx { + min-width: 24px; + height: 24px; + border-radius: 50%; + background: #3498db; + color: #fff; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + font-weight: 700; + flex-shrink: 0; +} +.map-marker-idx--main { + background: #e74c3c; +} +.map-marker-row--focus .map-marker-idx { + background: #FFA500; + color: #000; +} +.map-marker-thumb { + width: 44px; + height: 44px; object-fit: cover; border-radius: 4px; - margin: 4px 0; + flex-shrink: 0; +} +.map-marker-text { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 3px; +} +.map-marker-label { + font-weight: 600; + color: #ffa300; + word-break: break-word; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +.map-marker-actions { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 6px; + justify-self: end; +} +.map-marker-actions .btn-singleview { + align-self: center; +} +.map-marker-actions form { + margin: 0; +} +.map-marker-actions .btn-singleview { + min-width: 32px; + height: 32px; + font-size: 1em; +} +.map-center-link { + color: #FFA500; + text-decoration: none; + font-size: 0.85rem; + border: 1px solid #555; + border-radius: 6px; + padding: 0 10px; + height: 32px; + display: inline-flex; + align-items: center; + box-sizing: border-box; + white-space: nowrap; +} +.map-center-link:hover { + background: #FFA500; + color: #000; + border-color: #FFA500; +} +.map-picked { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin: 8px 0; + padding: 8px 12px; + background: #222; + border-radius: 6px; + color: #FFA500; + font-family: monospace; + font-size: 0.9rem; +} +.map-picked-near { + color: #ddd; + font-family: inherit; +} +.map-pick-hint { + color: #888; + font-size: 0.85rem; + margin: 6px 0; } .map-zoom-info { display: block; @@ -5568,10 +5924,6 @@ a.pm-btn:hover { text-decoration: none } .map-embed-url .map-location-link { font-size: 0.8rem; } -.map-zone { - display: block; -} -.map-popup-container { height: 0; overflow: hidden; border: 0px; } .project-maploc { margin: 6px 0; } @@ -5630,13 +5982,58 @@ a.pm-btn:hover { text-decoration: none } .chat-messages-list{display:flex;flex-direction:column;gap:6px} .chat-bubble-row{display:flex;justify-content:flex-start;border:none !important;background:transparent !important;padding:0 !important} .chat-bubble-row-self{justify-content:flex-end} -.chat-message{max-width:85%;padding:7px 12px 5px;border-radius:14px 14px 14px 4px;background:#23242a;word-break:break-word;border:none} +.chat-message{position:relative;max-width:85%;padding:7px 12px 5px;border-radius:14px 14px 14px 4px;background:#23242a;word-break:break-word;border:none} .chat-message-author{background:#1f1800} .chat-bubble-row-self .chat-message{border-radius:14px 14px 4px 14px;background:#2f2510} .chat-bubble-sender{display:block;font-size:0.78rem;font-weight:bold;color:#7fb3ff;text-decoration:none;margin-bottom:2px;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .chat-bubble-sender:hover{text-decoration:underline} .chat-bubble-sender-owner{color:#ffa300} .chat-bubble-time{display:block;font-size:0.68rem;color:#888;text-align:right;margin-top:2px;line-height:1} +.chat-reply-quote{display:block;border-left:3px solid #FFA500;background:rgba(255,163,0,.08);border-radius:4px;padding:2px 8px;margin-bottom:4px;font-size:.78rem;color:#ccc;text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px} +.chat-reply-quote:hover{background:rgba(255,163,0,.16)} +.chat-reply-author{color:#7fb3ff;font-weight:bold} +.chat-replying-banner{display:flex;align-items:center;gap:6px;border:0 !important;border-left:3px solid #FFA500 !important;background:rgba(255,163,0,.08) !important;border-radius:4px;padding:4px 10px !important;margin:0 0 6px;box-shadow:none;font-size:.8rem;color:#ccc;overflow:hidden} +.chat-replying-banner .chat-reply-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} +.chat-reply-cancel{color:#ff6b6b;text-decoration:none;font-weight:bold;margin-left:auto} +.chat-msg-actions{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin:4px 0 0;padding:0 !important;background:transparent !important;border:0 !important;border-radius:0;box-shadow:none} +.chat-react-form{display:inline;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.chat-react-btn{display:inline-flex;align-items:center;justify-content:center;font-size:.72rem;line-height:1.4;padding:1px 7px;border:1px solid rgba(128,128,128,.4);border-radius:10px;background:rgba(128,128,128,.12);color:#eee;cursor:pointer;text-decoration:none;font-family:"Noto Color Emoji","Apple Color Emoji","Segoe UI Emoji",sans-serif} +.chat-react-btn:hover{background:rgba(128,128,128,.3)} +.chat-reply-btn{margin-left:auto} +.chat-react-active{border-color:#FFA500;color:#FFA500} +.chat-message-pinned{border:1px solid #FFA500 !important;background:#332a14 !important} +.chat-pin-toggle{position:absolute;top:4px;right:6px} +.chat-has-pin-toggle{padding-right:38px} +.chat-pinned-link{margin-right:8px} +.btn-singleview.btn-delete{color:#ff6b6b !important;border-color:rgba(255,107,107,.55) !important} +.btn-singleview.btn-delete:hover{background:rgba(255,107,107,.15) !important} +.feed-opinions-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:transparent !important;border:0 !important;padding:0 !important;margin:6px 0;box-shadow:none} +.feed-opinions-count{font-weight:bold;color:#FFA500} +.feed-votes-summary{display:flex;gap:6px;flex-wrap:wrap;background:transparent !important;border:0 !important;padding:0 !important;margin:6px 0;box-shadow:none} +.feed-votes-summary .vote-category{font-size:.75em;padding:2px 9px;border:1px solid rgba(255,163,0,.45);border-radius:10px;background:rgba(255,163,0,.08);color:#FFD700;white-space:nowrap} +.school-invite-code-block{display:block} +.school-lesson-details{background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none} +.school-lesson-summary{cursor:pointer;display:flex;justify-content:space-between;align-items:center;gap:10px;list-style:none;font-weight:bold} +.school-lesson-summary-title{flex:1 1 auto;min-width:0} +.school-lesson-summary::-webkit-details-marker{display:none} +.school-lesson-summary-title{color:#ffa300} +.school-lesson-summary-title::after{content:" \25BE";opacity:.7} +details[open] > .school-lesson-summary .school-lesson-summary-title::after{content:" \25B4"} +.school-lesson-summary-meta{font-size:.78em;color:#888;white-space:nowrap;font-weight:normal} +.school-lesson-body{background:transparent !important;border:0 !important;padding:10px 0 0 !important;margin:0;box-shadow:none} +.school-lesson-actions{display:flex;gap:8px;align-items:center;margin-top:8px;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.feed-workspace{display:grid;grid-template-columns:230px minmax(0,1fr) 230px;gap:18px;align-items:start;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;margin:0;overflow:visible} +.feed-workspace-center{min-width:0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;margin:0;overflow:visible} +.feed-side{position:sticky;top:12px;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;margin:0;overflow:visible} +.feed-side-box{background:#1c1c1c;border:1px solid #333;border-radius:12px;padding:14px 16px;margin:0;box-shadow:none;overflow:hidden} +.feed-side-title{font-size:1em;margin:0 0 10px;color:#FFA500} +.feed-side-tags{display:flex;flex-wrap:wrap;gap:6px;background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none} +.feed-side-users{display:flex;flex-direction:column;gap:8px;background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none} +.feed-side-user{display:flex;align-items:center;gap:8px;background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none;overflow:hidden} +.feed-side-avatar{width:28px;height:28px;border-radius:50%;object-fit:cover;flex:0 0 auto} +.feed-side-user .user-link{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px} +.chat-time-inline{margin:0 0 0 6px;line-height:1.4} +.chat-time-push{margin-left:auto} .chat-day-separator{display:flex;justify-content:center;margin:10px 0 4px;border:none !important;background:transparent !important;padding:0 !important} .chat-day-chip{font-size:0.72rem;padding:2px 12px;border-radius:10px;background:#1a1a1a;color:#aaa;border:1px solid #333} .chat-message.chat-poll{max-width:92%} @@ -5688,6 +6085,73 @@ a.pm-btn:hover { text-decoration: none } .pad-readonly-colored{background:#fff;color:#111;padding:12px;border-radius:4px;min-height:120px;white-space:pre-wrap;word-break:break-word;font-family:inherit;font-size:.95rem;border:1px solid #aaa;margin-bottom:10px} .pad-author-span{padding:1px 2px;border-radius:2px} .pad-members-section{margin-top:12px;border:none} +.feed-error-msg{margin:8px 0 12px;padding:10px 14px;border-left:4px solid #e74c3c;background:rgba(231,76,60,.12);border-radius:6px} +.feed-error-msg p{margin:0} +.pad-workspace{display:flex;flex-direction:column;gap:14px;width:100%} +.pad-workspace-main{min-width:0;width:100%} +.pad-workspace-side{display:flex;flex-direction:column;gap:14px} +.pad-editor{width:100%;box-sizing:border-box;min-height:62vh;background:#fff;color:#111;border:1px solid #aaa;border-radius:4px;padding:12px;font-family:monospace;font-size:.95rem;line-height:1.45;resize:vertical} +.pad-editor-bar{display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-top:8px} +.pad-page .tribe-main .pad-document{width:100%;box-sizing:border-box} +.pad-editor-hint{color:#888;font-size:.8rem} +.pad-attribution{margin-top:10px} +.pad-attribution summary{cursor:pointer;color:#aaa;font-size:.8rem;text-transform:uppercase;letter-spacing:1px;padding:4px 0} +.pad-document{background:#fff;color:#111;padding:14px;border-radius:4px;min-height:40vh;white-space:pre-wrap;word-break:break-word;font-family:monospace;font-size:.95rem;line-height:1.45} +.pad-users h4,.pad-history h4{margin:0 0 6px 0;color:#aaa;font-size:.8rem;text-transform:uppercase;letter-spacing:1px} +.pad-user-item{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:.9rem;min-width:0} +.pad-user-item a{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.pad-history{max-height:40vh;overflow:auto} +.pad-history-item{width:100%;box-sizing:border-box} +.pad-line-placeholder{color:#888;font-style:italic} +.pad-page .tribe-side .pad-meta{flex-basis:100%} +.pad-history-item{display:flex;align-items:center;gap:6px;padding:4px 0;font-size:.85rem;border-bottom:1px solid rgba(128,128,128,.15);min-width:0} +.pad-history-item .pad-version-link{color:#aaa;text-decoration:none;font-family:monospace;white-space:nowrap} +.pad-history-item .pad-version-link.active{color:#FFA500} +.pad-history-item a.user-link{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.tribe-details.pad-page{flex-direction:column;gap:16px} +.pad-page .tribe-side{width:100%;flex-direction:row;flex-wrap:wrap;align-items:center;gap:10px 18px;padding:12px 16px;box-sizing:border-box} +.pad-page .tribe-side > *{margin:0} +.pad-page .tribe-side > div:not(.tribe-info-table),.pad-page .tribe-side > form{border:none;background:transparent;padding:2px} +.pad-page .tribe-side .tribe-side-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap} +.pad-page .tribe-side .card-header{border:none;background:transparent;padding:0} +.pad-page .tribe-side .shop-title-row{border:none;background:transparent;padding:0} +.pad-page .tribe-side .tribe-card-title{margin:0} +.pad-page .tribe-side .tribe-info-table{margin:0} +.pad-page .tribe-side form{display:inline-block} +.pad-page .tribe-main{width:100%;border:none;background:transparent;padding:0} +.pad-page .tribe-main,.pad-page .pad-workspace,.pad-page .pad-workspace div{padding:0;margin:0;border:none;border-radius:0;background:transparent;box-shadow:none;overflow:visible;box-sizing:border-box} +.pad-page .pad-workspace .pad-editor-wrap{display:flex;width:100%;background:#fff;border:1px solid #aaa;border-radius:4px;overflow:hidden} +.pad-page .pad-workspace .pad-gutter{padding:12px 8px 12px 10px;background:#f4f4f4;border-right:1px solid #ddd} +.pad-page .pad-workspace .pad-document{width:100%;padding:12px 14px 12px 4px;background:#fff;border-radius:4px;min-height:40vh} +.pad-page .pad-workspace .pad-readonly-colored{padding:12px 14px 12px 4px;background:#fff;border-radius:4px} +.pad-page .pad-workspace .pad-workspace-side > div{border:1px solid #333;background:#1A1A1A;border-radius:8px;padding:10px 12px} +.pad-page .pad-workspace .pad-history-item{padding:4px 0;border-bottom:1px solid rgba(128,128,128,.15)} +.pad-page .pad-workspace .pad-editor-bar{margin-top:8px} +.pad-page .pad-workspace .pad-editor-wrap .pad-editor{margin:0;min-height:0;padding:12px;line-height:1.45;font-size:.95rem} +.pad-page .pad-workspace .pad-attribution{margin-top:10px} +.pad-workspace .pad-document,.pad-workspace .pad-readonly-colored{background:#fff} +.pad-meta{display:flex;align-items:center;gap:6px 10px;flex-wrap:wrap;font-size:.9em} +.pad-meta .user-link{display:inline-block;width:auto} +.pad-page .tribe-side .pad-members-details{flex-basis:100%;border:none;background:transparent;padding:0} +.pad-members-details summary{cursor:pointer;display:inline-block} +.pad-members-chips{display:flex;flex-wrap:wrap;gap:6px 12px;padding:8px 0 2px;max-height:150px;overflow:auto;border:none;background:transparent} +.pad-page .tribe-side div form{border:none;background:transparent;padding:2px} +.pad-member-chip{display:inline-flex;align-items:center;gap:6px;font-size:.9em;min-width:0} +.supporters-list{display:flex;flex-wrap:wrap;gap:8px 12px;margin:0;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none} +.pad-member-chip .user-link{display:inline-block;width:auto;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.pad-workspace-main form{display:block;width:100%;margin:0} +.pad-editor-wrap{display:flex;align-items:stretch;width:100%;box-sizing:border-box;background:#fff;border:1px solid #aaa;border-radius:4px;overflow:hidden} +.pad-gutter{flex:0 0 auto;padding:12px 8px 12px 10px;font-family:monospace;font-size:.95rem;line-height:1.45;color:#999;text-align:right;white-space:pre;user-select:none;border-right:1px solid #ddd;background:#f4f4f4} +.pad-editor-wrap .pad-editor{flex:1 1 0;min-width:0;width:100%;border:none;border-radius:0;min-height:62vh;resize:vertical;overflow:auto} +.pad-lines,.pad-document table.pad-lines,.pad-readonly-colored table.pad-lines{border-collapse:collapse;width:100%;background:transparent;border:none;margin:0;box-shadow:none} +.pad-lines tr,.pad-lines tr:nth-child(even),.pad-lines tr:nth-child(odd),.pad-lines td,.pad-lines th{background:transparent !important;border:none !important;padding:0;vertical-align:top;color:#111} +.pad-lines .pad-line-num{text-align:right;padding:0 10px 0 2px;width:1%;min-width:40px;white-space:nowrap;user-select:none;border-right:1px solid #ddd;font-family:monospace;line-height:1.45} +.pad-lines .pad-line-num a{color:#999;text-decoration:none} +.pad-lines tr:target .pad-line-num a{color:#FFA500;font-weight:700} +.pad-lines .pad-line-text{padding-left:12px;white-space:pre-wrap;word-break:break-word;font-family:monospace;line-height:1.45;min-height:1.45em} +.pad-workspace .pad-document{padding:12px 14px 12px 4px} +.pad-workspace .pad-readonly-colored{padding:12px 14px 12px 4px;color:#111} +@media (max-width:720px){.pad-workspace{grid-template-columns:1fr}.pad-editor{min-height:50vh}.pad-history{max-height:30vh}} .pad-members-below-tags{margin-top:12px;border:none} .pad-member-card{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:0.85rem} .pad-members-grid{display:flex;flex-direction:column;gap:4px;margin-bottom:12px} @@ -5717,6 +6181,7 @@ a.pm-btn:hover { text-decoration: none } .game-iframe-ecoinflow{height:95vh} .game-iframe-neoninfiltrator{height:95vh} .game-iframe-audiopendulum{height:95vh} +.game-iframe-visualdj{height:95vh} .game-iframe-flipflop{height:720px} .game-iframe-rockpaperscissors{height:580px} .game-iframe-tiktaktoe{height:580px} @@ -5744,10 +6209,11 @@ a.pm-btn:hover { text-decoration: none } .torrent-table{width:100%;border-collapse:collapse;margin-top:12px} .torrent-table th,.torrent-table td{padding:8px 12px;text-align:left} .torrent-table td:has(.filter-btn){width:1%;white-space:nowrap} +.torrent-table td:nth-child(3){max-width:380px;overflow-wrap:anywhere} +.torrent-table td:nth-child(4),.torrent-table td:nth-child(6){white-space:nowrap;width:1%} .torrent-table td .filter-btn{margin:0} .torrent-table th{background:#1a1a1a;color:#FFA500;font-weight:bold} -.torrent-download{margin:12px 0} -.torrent-download .filter-btn{border:none} +.torrent-download{margin:12px 0;background:transparent;border:0;padding:0;box-shadow:none} .logs-table{width:100%;border-collapse:collapse;margin-top:12px} .logs-table th,.logs-table td{padding:8px 12px;text-align:left;vertical-align:middle;border-bottom:1px solid #222} @@ -5814,7 +6280,7 @@ a.pm-btn:hover { text-decoration: none } .stats-link-break{color:#ffa500;text-decoration:none;word-break:break-all} .stats-muted{color:#aaa} .stats-strong{color:#ffd700;font-weight:600} -.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:16px;margin-bottom:24px} +.stats-grid{display:flex;flex-wrap:wrap;gap:6px 28px;margin-bottom:6px;background:transparent !important;border:0 !important;box-shadow:none;padding:0 !important} .stats-mode-row{justify-content:flex-start;margin-bottom:24px} .stats-section-h{font-size:18px;color:#ffa500;margin:8px 0;font-weight:600} .stats-list-reset{list-style-type:none;padding:0;margin:0} @@ -5834,6 +6300,8 @@ a.pm-btn:hover { text-decoration: none } .invites-pubs-table td:nth-child(4){width:30%} .invites-pubs-table tr:first-child td{white-space:nowrap} .stats-block{background:none;padding:0;border:none;border-radius:0;margin-bottom:16px} +.stats-block.stats-block-boxed{background:#1c1c1c;border:1px solid #333;border-radius:10px;padding:14px 16px;margin-bottom:16px} +.stats-block.stats-block-boxed h2{margin-top:0} .stats-block h2{margin:0 0 10px;font-size:16px;color:#ffa500;font-weight:600} .stats-block ul{margin:6px 0 0;padding-left:18px} .stats-block ul li{margin:3px 0;color:#ddd;font-size:14px} @@ -5849,6 +6317,13 @@ a.pm-btn:hover { text-decoration: none } .stats-toplist .stats-toplist-name{flex:0 0 auto;color:#FFDD44;font-size:13px;min-width:120px} .stats-toplist .stats-toplist-num{flex:0 0 auto;color:#FFDD44;font-size:13px;min-width:36px;text-align:right} .peer-key{word-break:break-all;font-size:12px} +.peers-list .block-info-table{table-layout:fixed;width:100%} +.peers-list .block-info-table td{overflow-wrap:anywhere} +.peers-list .block-info-table td:nth-child(1){width:18%} +.peers-list .block-info-table td:nth-child(2){width:47%} +.peers-list .block-info-table td:nth-child(3){width:12%} +.peers-list .block-info-table td:nth-child(4){width:8%} +.peers-list .block-info-table td:nth-child(5){width:15%} .peers-technical-block{margin-top:16px} .peers-conn-actions{margin-bottom:12px} .invites-pubs-actions{margin:12px 0} @@ -5889,7 +6364,7 @@ a.pm-btn:hover { text-decoration: none } .larp-detail-description{color:#FFDD44;line-height:1.6;font-size:15px;margin:0} .larp-actions{margin-top:12px;background:transparent !important;border:0 !important;padding:0 !important;display:flex;flex-wrap:wrap;gap:8px;align-items:center} .larp-actions form{margin:0;display:inline-flex;align-items:center} -.larp-actions .filter-btn,.larp-actions button{height:36px;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap;text-decoration:none} +.larp-actions .filter-btn,.larp-actions button{height:36px;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap;text-decoration:none;margin:0;padding:6px 16px;font-family:inherit;font-size:inherit;line-height:1.2;vertical-align:middle} .larp-members-block{margin-top:24px} .larp-members-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:6px;background:transparent !important;border:0 !important;padding:0 !important} .larp-member-row{padding:4px 8px;background:#1A1A1A;border:1px solid #333;border-radius:4px} @@ -5921,6 +6396,50 @@ a.pm-btn:hover { text-decoration: none } .lan-checkbox-text{line-height:1} .larp-sign-block{display:block;line-height:0;margin:0 auto;background:transparent !important;border:0 !important;padding:0 !important;width:180px} .profile-sensors-box .larp-sign-block{margin-top:14px} +.wallet-line{display:flex;flex-direction:row;align-items:center;justify-content:center;flex-wrap:wrap;gap:6px;padding:0.2rem 0.6rem !important;border:1px solid #555 !important;border-radius:4px;background-color:#1A1A1A !important;font-size:0.9rem;color:#FFDD44;margin:0;box-shadow:none;overflow:visible} +.profile-sensors-box .wallet-line a,.inhabitant-left .wallet-line a{color:#FFDD44;text-decoration:none} +.profile-sensors-box .wallet-line a:hover,.inhabitant-left .wallet-line a:hover{color:#ffa500;text-decoration:underline} +.wallet-line-head{display:inline-flex;align-items:center;font-weight:700;font-size:1em} +.wallet-icon{color:#4fc3f7;font-size:1.1em} +.phone-number-icon{color:#ffa500;font-size:1.1em} +.wallet-address{display:inline;font-family:monospace;font-size:.85em;font-weight:700;color:#FFDD44;word-break:break-all;background:transparent;border-radius:0;padding:0;cursor:copy;-webkit-user-select:all;user-select:all} +.wallet-line-link{text-decoration:none;color:inherit} +.wallet-transfer-ctx{display:flex;flex-direction:column;gap:6px;background:transparent !important;border:0 !important;border-radius:0;padding:0 !important;margin:10px 0;box-shadow:none} +.wallet-transfer-ctx-line{display:flex;flex-wrap:wrap;align-items:center;gap:6px;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.wallet-transfer-ctx-label{font-weight:700;font-size:.85em;letter-spacing:.5px;text-transform:uppercase} +.ubi-status{display:flex;flex-direction:column;gap:8px;background:#1e1f23 !important;border:1px solid #333 !important;border-radius:10px;padding:12px 14px !important;margin:0 0 14px} +.ubi-status-line{display:flex;flex-wrap:wrap;align-items:center;gap:8px;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.ubi-status-note{font-size:.85em;opacity:.75} +.ubi-status a{text-decoration:none} +.wallet-chip-link,.wallet-chip-link:hover{text-decoration:none} +.module-header-line .wallet-chip-link{display:inline-flex} +.addr-cards.ubi-pub-cards{grid-template-columns:repeat(3,1fr)} +.ubi-pub-summary{margin:0;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none} +.ubi-pub-summary .bank-info-table{width:100%;margin:0} +.ubi-pub-summary .bank-info-table td{padding:6px 8px;font-size:.9em;letter-spacing:0;white-space:nowrap} +.ubi-pub-summary .bank-info-table td.card-value{width:1%} +.ubi-tick-ok{color:#7fff7f;font-weight:700} +.ubi-tick-bad{color:#ff8a8a;font-weight:700} +.bank-addr-search{margin-bottom:14px} +.bank-addresses{width:100%;border-collapse:collapse} +.bank-addresses th,.bank-addresses td{text-align:left;padding:8px 10px;border-bottom:1px solid #333;vertical-align:middle} +.bank-addresses th{font-size:.85em;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap} +.bank-addresses .delete-btn{white-space:nowrap} +.bank-addresses td:last-child,.bank-addresses td:nth-child(4){white-space:nowrap} +.bank-addresses td:nth-child(3){min-width:290px} +.bank-address-code{font-family:monospace;font-size:1em;word-break:break-all} +.bank-summary .module-stats-line{margin:0 0 10px;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none;display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:.72em;white-space:nowrap;text-transform:uppercase} +.bank-addresses .addr-del{margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} +.ubi-pub-card form{margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} +@media (max-width:900px){.addr-cards.ubi-pub-cards{grid-template-columns:1fr}} +.ubi-pub-card .ubi-status-line{gap:6px} +.wallet-line-link:hover .wallet-address{color:#ffa500;text-decoration:underline} +.addr-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px} +.addr-card{display:flex;flex-direction:column;gap:10px;background:#1e1f23 !important;border:1px solid #333 !important;border-radius:10px;padding:14px !important;margin:0;box-shadow:none;overflow:visible} +.addr-card-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.addr-card .wallet-line{margin:0} +.addr-card-owner{margin:0} +.addr-card .addr-del{margin:0;padding:0;background:transparent;border:0;box-shadow:none;display:flex;justify-content:flex-end} .larp-sign-large{width:180px;height:180px;object-fit:cover;border-radius:8px;border:2px solid #FFA500;display:block} .larp-sign-mini-block{display:inline-block;line-height:0;margin:6px 0;background:transparent !important;border:0 !important;padding:0 !important;width:64px} .larp-sign-mini{width:64px;height:64px;object-fit:cover;border-radius:50%;border:2px solid #FFA500;display:block} @@ -5985,10 +6504,17 @@ a.pm-btn:hover { text-decoration: none } .inbox-exposition{margin:12px 0 4px 0;display:flex;flex-wrap:wrap;align-items:center;gap:8px} .inbox-filters-label{color:#FFDD44;font-size:13px;font-weight:600;align-self:center} .inbox-vis-toggle{background:transparent !important;border:0 !important;padding:0 !important;display:inline-block} +.inbox-bot-filters{margin-top:-4px} +.pm-subject-cell{display:flex;align-items:center;gap:8px;flex-wrap:wrap} +.pm-unread-chip{background:#3a2a00;color:#FFA500;border-color:#FFA500} +.inbox-bot-label{display:flex;margin:6px 0} +.pm-title-link{color:inherit;text-decoration:none} +.mention-actions{margin-top:8px} .suggested-badge{display:inline-block;background:#FFA500;color:#000;padding:2px 8px;border-radius:4px;font-size:12px;font-weight:600;width:fit-content} .peers-import-form{margin-top:16px;display:flex;flex-direction:column;gap:8px;align-items:flex-start} -.peers-import-form textarea{width:100%;max-width:720px;font-family:monospace;font-size:12px} +.peers-import-form textarea{width:100%;max-width:720px;box-sizing:border-box;font-family:monospace;font-size:12px} .peers-import-label{color:#FFDD44;font-size:13px;font-weight:600} +.invites-used-table td:nth-child(1),.invites-used-table td:nth-child(3){width:1%;white-space:nowrap} .graphos-canvas{width:100%;max-width:1100px;margin:12px auto;padding:8px;background:transparent} .graphos-svg{width:100%;height:auto;min-height:480px;display:block} .graphos-edge{stroke:#666;stroke-width:1;opacity:.55} @@ -5996,7 +6522,7 @@ a.pm-btn:hover { text-decoration: none } .graphos-edge-discovered{stroke:#ffd700;stroke-width:1.5;opacity:.75} .graphos-edge-unknown{stroke:#888;stroke-width:1;stroke-dasharray:4 4;opacity:.55} .graphos-node-circle{stroke:#1a1a1a;stroke-width:2;transition:stroke-width .15s,filter .15s} -.graphos-node-circle-me{fill:#ffa500;stroke:#cc7700;stroke-width:3} +.graphos-node-circle-me{fill:#3498db;stroke:#1f6391;stroke-width:3} .graphos-node-circle-online{fill:#2ecc71;stroke:#1e8449} .graphos-node-circle-discovered{fill:#ffd700;stroke:#b8860b} .graphos-node-circle-unknown{fill:#888;stroke:#555} @@ -6004,7 +6530,7 @@ a.pm-btn:hover { text-decoration: none } .graphos-node-link:hover .graphos-node-circle{stroke-width:4;filter:brightness(1.15)} .graphos-node-link:hover .graphos-node-label{font-weight:600} .graphos-node-label{font-size:12px;fill:#ddd;text-anchor:middle;pointer-events:none} -.graphos-node-label-me{font-weight:600;fill:#ffa500} +.graphos-node-label-me{font-weight:600;fill:#5dade2} .graphos-legend{display:flex;gap:18px;flex-wrap:wrap;align-items:center;justify-content:center;font-size:14px;color:#ddd;margin:14px 0 8px} .graphos-stats .stats-grid{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:18px} .graphos-stats .stats-kpi{background:#1c1c1c;border:1px solid #444;border-radius:8px;padding:12px 14px;box-shadow:0 1px 3px rgba(0,0,0,0.35)} @@ -6012,13 +6538,43 @@ a.pm-btn:hover { text-decoration: none } .graphos-stats .stats-kpi-inline .stats-kpi-value{font-size:20px} .graphos-legend-item{display:flex;align-items:center;gap:6px} .graphos-legend-dot{display:inline-block;width:12px;height:12px;border-radius:50%;border:2px solid #1a1a1a} -.graphos-legend-dot.graphos-node-circle-me{background:#ffa500;border-color:#cc7700} +.graphos-legend-dot.graphos-node-circle-me{background:#3498db;border-color:#1f6391} .graphos-legend-dot.graphos-node-circle-online{background:#2ecc71;border-color:#1e8449} .graphos-legend-dot.graphos-node-circle-discovered{background:#ffd700;border-color:#b8860b} .graphos-legend-dot.graphos-node-circle-unknown{background:#888;border-color:#555} -.stats-kpi{padding:8px 4px;display:flex;flex-direction:column;gap:4px;background:transparent;border:none} -.stats-kpi-label{color:#ffa500;font-size:12px;font-weight:600;letter-spacing:.3px} -.stats-kpi-value{color:#ffd700;font-weight:600;font-size:22px;line-height:1.1;word-break:break-word} +.graphos-fed-connected{fill:#2ecc71;stroke:#1e8449} +.graphos-fed-available{fill:#ffd700;stroke:#b8860b} +.graphos-fed-unreachable{fill:#7f8c8d;stroke:#4d5656} +.graphos-fed-unknown{fill:#444;stroke:#666} +.graphos-fed-outdated-ring{fill:none;stroke:#e74c3c;stroke-width:3} +.graphos-fed-isolated-ring{fill:none;stroke:#e74c3c;stroke-width:1.5;stroke-dasharray:3 3} +.graphos-fed-link{stroke-width:1.5;opacity:.8} +.graphos-fed-mutual{stroke:#ffa500} +.graphos-fed-oneway{stroke:#ffa500;stroke-dasharray:5 5;opacity:.5} +.graphos-fed-me-live{stroke:#2ecc71;stroke-width:2.5;opacity:.9} +.graphos-fed-me-follow{stroke:#888;stroke-width:1;stroke-dasharray:2 4;opacity:.6} +.graphos-fed-tor{font-size:10px;fill:#c9a0ff;font-weight:700;pointer-events:none} +.graphos-legend-dot.graphos-fed-dot-connected{background:#2ecc71;border-color:#1e8449} +.graphos-legend-dot.graphos-fed-dot-available{background:#ffd700;border-color:#b8860b} +.graphos-legend-dot.graphos-fed-dot-unreachable{background:#7f8c8d;border-color:#4d5656} +.graphos-legend-dot.graphos-fed-dot-outdated{background:transparent;border:3px solid #e74c3c} +.graphos-legend-line{display:inline-block;width:22px;height:0;border-top:2px solid #ffa500} +.graphos-legend-line.graphos-fed-line-oneway{border-top-style:dashed;opacity:.6} +.graphos-fed-table{margin-top:16px} +.stats-kpi{padding:4px 0 !important;margin:0 !important;display:inline-flex !important;flex-direction:row !important;flex-wrap:nowrap;align-items:baseline !important;gap:6px;background:transparent !important;border:none !important;width:auto !important} +.stats-kpi-label{color:#ffa500;font-size:15px;font-weight:600;letter-spacing:.3px;padding:0 !important;margin:0 !important;background:transparent !important;border:0 !important;width:auto !important;flex:0 0 auto;white-space:nowrap} +.stats-kpi-label::after{content:':'} +.stats-kpi-value{color:#ffd700;font-weight:600;font-size:15px;line-height:1.1;word-break:break-word;padding:0 !important;margin:0 !important;background:transparent !important;border:0 !important;width:auto !important;flex:0 0 auto} +.ai-ask-row{display:flex;flex-wrap:nowrap;align-items:center;justify-content:center;gap:18px;width:100%;max-width:960px;min-width:0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.ai-ask-row .ai-ask-nav{flex:0 0 auto} +.ai-ask-row .ai-ask-form{flex:1 1 260px;min-width:180px;max-width:480px} +.ai-ask-row .ai-suggestion-banner{flex:0 1 auto;min-width:0;justify-content:flex-start;border:0 !important;background:transparent !important;padding:0 !important;margin:0;white-space:nowrap;overflow:hidden} +.ai-ask-row .ai-suggestion-banner .update-banner-link{max-width:28vw} +.ai-ask-nav ul{display:flex;margin:0;padding:0;list-style:none} +.ai-ask-nav ul li{margin:0} +.header .top-bar-center{align-self:center;margin:0 0 20px 0 !important;padding:0 !important;box-shadow:none;overflow:visible} +.header .ai-ask-row{margin:0 !important;overflow:visible} +.header .ai-ask-row .ai-suggestion-banner{margin:0 !important} .stats-kpi-bar{margin-top:6px} .stats-activity-totals{display:flex;gap:24px;flex-wrap:wrap;color:#ffa500;font-size:13px;margin-top:8px} .stats-activity-totals strong{color:#ffd700} @@ -6041,7 +6597,9 @@ a.pm-btn:hover { text-decoration: none } .prefs-card .tags-header{margin:0 0 10px 0} .prefs-card .tags-header h2{font-size:16px;color:#FFA500;margin:0 0 4px 0} .prefs-help{color:#FFD700;font-size:12px;margin:0} -.pref-pill-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px} +.pref-pill-row{display:grid;grid-template-columns:repeat(8,minmax(0,1fr));gap:8px;margin-top:8px} +@media (max-width:1100px){.pref-pill-row{grid-template-columns:repeat(4,minmax(0,1fr))}} +@media (max-width:640px){.pref-pill-row{grid-template-columns:repeat(2,minmax(0,1fr))}} .pref-pill{display:inline-flex;align-items:center;border:1px solid #333;padding:6px 12px;cursor:pointer;background:#161616;color:#888;font-size:13px;user-select:none} .pref-pill:hover{border-color:#555;color:#bbb} .pref-pill-input{position:absolute;opacity:0;width:0;height:0;pointer-events:none} @@ -6100,25 +6658,34 @@ a.pm-btn:hover { text-decoration: none } } .inhabitant-stats-grid{ display:grid; - grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); + grid-template-columns:repeat(2,minmax(0,1fr)); gap:6px; } .inhabitant-stat{ display:flex; justify-content:space-between; align-items:center; - gap:8px; - padding:5px 10px; + gap:4px; + min-width:0; + padding:4px 7px; border:1px solid rgba(128,128,128,.35); border-radius:6px; + white-space:nowrap; } .inhabitant-stat-label{ - font-size:12px; + font-size:11px; opacity:.85; + overflow:hidden; + text-overflow:ellipsis; +} +.inhabitant-stat-value{ + flex:0 0 auto; + font-size:13px; } .inhabitant-stat-value{ font-weight:700; } +.profile-side-actions{align-items:center} .profile-side-actions .btn{ background-color:#FFA500; color:#000; @@ -6174,7 +6741,7 @@ a.pm-btn:hover { text-decoration: none } .inhabitant-left .gpg-line a{color:#FFDD44;text-decoration:none} a.chip-link{text-decoration:none;display:inline-block} a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} -.gpg-edit-row{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:4px} +.gpg-edit-row{display:flex;flex-direction:column;align-items:flex-start;gap:10px;flex-wrap:wrap;margin-top:4px} .gpg-edit-row .gpg-remove-btn{background:transparent;border:1px solid #FF6B6B;color:#FF6B6B;cursor:pointer;padding:4px 10px;border-radius:4px;font-family:inherit;font-size:0.9rem} .gpg-edit-row .gpg-remove-btn:hover{background:#FF6B6B;color:#000} .profile-sensors-box .inhabitant-last-activity a:hover, @@ -6207,24 +6774,8 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .btn-spread-on{background:#ffa300;color:#000} .btn-spread-on:hover{background:#ffb733;color:#000} .spread-form .btn-singleview{border-radius:16px;padding:7px 12px} -.welcome-header{margin-bottom:18px} -.welcome-chat{max-width:760px;margin:0 auto;padding:8px} -.welcome-ai-header{display:flex;align-items:center;gap:10px;margin-bottom:10px;color:#ffa500;font-weight:600} -.welcome-ai-avatar{font-size:24px} -.welcome-ai-name{font-size:16px} -.welcome-ai-step{margin-left:auto;font-size:12px;color:#888} -.welcome-bubble{background:#1a1a1a;border:1px solid #333;border-radius:12px;padding:14px 18px;margin-bottom:14px;line-height:1.5} -.welcome-bubble-ai{border-left:3px solid #ffa500} -.welcome-bubble-title{margin:0 0 8px;color:#ffd700;font-size:18px} -.welcome-bubble p{margin:6px 0;color:#ddd} -.welcome-actions{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0;justify-content:flex-end} -.welcome-action-primary{background:#ff6600;color:#fff;border-color:#ff3300} -.welcome-progress{display:flex;justify-content:center;gap:10px;margin:18px 0 6px} -.welcome-dot{font-size:14px;color:#444;text-decoration:none} -.welcome-dot-active{color:#ffa500} -.welcome-skip{text-align:center;margin-top:6px} -.welcome-skip-link{color:#666;font-size:12px} .indexing-progress-block{max-width:640px;margin:24px auto;text-align:center} +.indexing-message{margin:0 0 14px;font-size:16px;line-height:1.4;color:#ffd700} .indexing-progress{width:100%;height:18px} .indexing-percent{margin:8px 0 4px;font-size:22px;color:#ffd700} .indexing-note{color:#888;font-size:12px;margin:0} @@ -6243,6 +6794,108 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .pm-exposition-whole{background:#3a2300;color:#ffd700;border-color:#a39134} .pm-exposition-mutuals{background:#1a3a1a;color:#7fff7f;border-color:#3a7f4c} .pm-exposition-encrypted{background:#0e3320;color:#7fff7f;border-color:#3a7f4c} +.pm-exposition-transport{background:#132433;color:#8fc7ff;border-color:#35648f} +.pm-exposition-license{background:#251f33;color:#c8b8ec;border-color:#6a5a96} +.pm-exposition-tribe{background:#2b1d0a;color:#ffb347;border-color:#a8692a} +.phone-action-form{display:inline-block;margin:0;padding:0} +.phone-call-panel{border:1px solid #ffa500;border-radius:8px;padding:14px;margin:12px 0} +.phone-call-panel div{background:transparent} +.phone-call-who{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:16px;font-weight:600;padding:0;margin:8px 0;border:none;box-shadow:none} +.phone-call-icon{font-size:22px} +.phone-call-clock{font-family:monospace;color:#ffa500} +.phone-call-actions{display:flex;gap:10px;flex-wrap:wrap;padding:0;margin:8px 0 0 0;border:none;box-shadow:none} +.phone-block{padding:14px 18px;margin:0 0 14px 0;background:transparent} +.phone-block > h2{margin:0 0 12px 0} +.phone-block > .phone-empty{margin:0} +.phone-contacts{display:flex;flex-direction:column;gap:8px;padding:0;margin:8px 0 0 0;background:transparent;border:none !important;box-shadow:none} +.phone-contact{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px 10px;margin:0;border:1px solid #333;border-radius:6px} +.phone-contact .phone-action-form{margin-left:auto} +.phone-record audio{width:100%;max-width:520px;display:block;margin:8px 0} +.phone-record .phone-record-pending{display:flex;align-items:center;min-height:40px;margin:8px 0} +.phone-number-chooser{margin:0 0 12px 0;padding:0;border:none;background:transparent} +.phone-number-chooser > p{margin:0 0 8px 0} +.phone-call-ringing{font-size:14px;color:#888;padding:0;margin:4px 0} +.phone-history{width:100%;border-collapse:collapse} +.phone-history th,.phone-history td{padding:6px 8px;border-bottom:1px solid #333;text-align:left} +.phone-dir{font-size:16px;width:24px} +.phone-empty{color:#888} +.phone-banner{display:flex;align-items:center;gap:10px;flex-wrap:wrap} +.phone-banner{gap:10px 12px;padding:8px 16px} +.phone-banner .update-banner-link{font-size:1.05em;text-decoration:none;border-bottom:1px dotted currentColor} +.phone-banner-dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:#2ecc71;box-shadow:0 0 6px #2ecc71;flex:0 0 auto} +.phone-banner-chip{white-space:nowrap} +.phone-banner-chip .live-clock{line-height:1.2em} +.phone-banner .phone-action-form{margin:0;display:inline-flex;align-items:center} +.phone-banner .tribe-action-btn{padding:4px 12px;font-size:11px} +.phone-banner button{margin:0} +.live-clock{display:inline-flex;align-items:flex-end;white-space:nowrap;line-height:1.2em} +.live-clock-win{display:inline-block;height:1.2em;line-height:1.2em;overflow:hidden;vertical-align:bottom;font-variant-numeric:tabular-nums;padding:0} +.live-clock-roll{display:block;animation:live-clock-roll 60s steps(60) infinite;padding:0} +.live-clock-roll>span{display:block;height:1.2em;line-height:1.2em;padding:0} +.live-clock-min{animation-duration:3600s;animation-iteration-count:1;animation-fill-mode:forwards} +.live-clock-once{animation-iteration-count:1;animation-fill-mode:forwards} +@keyframes live-clock-roll{to{transform:translateY(-100%)}} +.lc-d1{animation-delay:-1s} +.lc-d2{animation-delay:-2s} +.lc-d3{animation-delay:-3s} +.lc-d4{animation-delay:-4s} +.lc-d5{animation-delay:-5s} +.lc-d6{animation-delay:-6s} +.lc-d7{animation-delay:-7s} +.lc-d8{animation-delay:-8s} +.lc-d9{animation-delay:-9s} +.lc-d10{animation-delay:-10s} +.lc-d11{animation-delay:-11s} +.lc-d12{animation-delay:-12s} +.lc-d13{animation-delay:-13s} +.lc-d14{animation-delay:-14s} +.lc-d15{animation-delay:-15s} +.lc-d16{animation-delay:-16s} +.lc-d17{animation-delay:-17s} +.lc-d18{animation-delay:-18s} +.lc-d19{animation-delay:-19s} +.lc-d20{animation-delay:-20s} +.lc-d21{animation-delay:-21s} +.lc-d22{animation-delay:-22s} +.lc-d23{animation-delay:-23s} +.lc-d24{animation-delay:-24s} +.lc-d25{animation-delay:-25s} +.lc-d26{animation-delay:-26s} +.lc-d27{animation-delay:-27s} +.lc-d28{animation-delay:-28s} +.lc-d29{animation-delay:-29s} +.lc-d30{animation-delay:-30s} +.lc-d31{animation-delay:-31s} +.lc-d32{animation-delay:-32s} +.lc-d33{animation-delay:-33s} +.lc-d34{animation-delay:-34s} +.lc-d35{animation-delay:-35s} +.lc-d36{animation-delay:-36s} +.lc-d37{animation-delay:-37s} +.lc-d38{animation-delay:-38s} +.lc-d39{animation-delay:-39s} +.lc-d40{animation-delay:-40s} +.lc-d41{animation-delay:-41s} +.lc-d42{animation-delay:-42s} +.lc-d43{animation-delay:-43s} +.lc-d44{animation-delay:-44s} +.lc-d45{animation-delay:-45s} +.lc-d46{animation-delay:-46s} +.lc-d47{animation-delay:-47s} +.lc-d48{animation-delay:-48s} +.lc-d49{animation-delay:-49s} +.lc-d50{animation-delay:-50s} +.lc-d51{animation-delay:-51s} +.lc-d52{animation-delay:-52s} +.lc-d53{animation-delay:-53s} +.lc-d54{animation-delay:-54s} +.lc-d55{animation-delay:-55s} +.lc-d56{animation-delay:-56s} +.lc-d57{animation-delay:-57s} +.lc-d58{animation-delay:-58s} +.lc-d59{animation-delay:-59s} +.update-changes{margin:0;padding:0 0 0 18px} +.update-changes li{margin:0 0 4px 0} .pm-double-enc-chip{background:#2a1633;border-color:#7a4b9c} .pm-double-enc-chip,.pm-double-enc-chip .pm-exposition-icon,.pm-double-enc-chip .pm-exposition-text{color:#d59bff} .pm-exposition-closed{background:#3a0e0e;color:#ff8a8a;border-color:#b04040} @@ -6251,7 +6904,8 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .pm-exposition-lifespan-green{background:#0e2a16;color:#9aff9a;border-color:#3a7f4c} .pm-exposition-lifespan-orange{background:#2a1f10;color:#ffb84d;border-color:#a37a34} .pm-exposition-lifespan-red{background:#2a0e0e;color:#ff7f7f;border-color:#b04040} -.jobs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:20px} +.jobs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(max(300px,calc((100% - 40px)/3)),1fr));gap:20px} +.blogs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(max(300px,calc((100% - 40px)/3)),1fr));gap:20px} .jobs-grid .tribe-card,.tribe-grid .tribe-card{margin-bottom:0} .tribe-card .card-comments-summary{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:8px} .tribe-card .card-comments-summary .inline-form{margin:0} @@ -6260,7 +6914,6 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .jobs-info-table .tribe-info-value{white-space:normal;word-break:normal;overflow-wrap:anywhere} .job-section{margin-bottom:12px;padding:0} .job-section-title{margin:0 0 8px 0;color:#ffa500;font-size:1.05em} -.job-card .tribe-card-members{background:transparent;padding:4px 0;text-align:left} .job-card .card-field{border-bottom:none} .job-card .confirmations-block{margin:6px 0} .job-meta-line{margin:0;color:#9aa3b2;font-size:12px;letter-spacing:1px;text-transform:uppercase} @@ -6289,20 +6942,6 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .tribe-main .job-section{border:none;background:transparent} .profile-reach{display:flex;flex-direction:column;align-items:center;gap:8px;margin:12px 0;text-align:center;border:none !important;background:none !important} .profile-reach .shop-clearnet-url{margin:0;text-align:center;border:none !important;background:none !important} -.profile-reach-toggle{margin:0} -.profile-reach-toggle .btn{ - background-color:#FFA500; - color:#000; - padding:8px 16px; - border-radius:5px; - text-align:center; - text-decoration:none; - border:none; - cursor:pointer; - font-family:inherit; - font-size:inherit; -} -.profile-reach-toggle .btn:hover{background-color:#FFD700} /* Unified item card aesthetic — applied to per-module containers that previously had no card styling. Existing rules (.market-card, .feed-card, .tribe-card, etc.) keep their own look via cascade. */ .card, @@ -6330,8 +6969,6 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .post-card, .mention-card, .trending-card, -.bank-claim-card, -.addr-list-card, .shop-product-card, .cv-item, .transfer-item, @@ -6583,9 +7220,9 @@ a.chip-link:hover .pm-exposition-chip{filter:brightness(1.2)} .activity-card-header{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px;border:none;background:transparent;padding:0} .activity-card-header .card-chips-row{margin:0} .content-actions{display:flex;justify-content:right;align-items:center;gap:12px;flex-wrap:wrap;border:none;background:transparent;padding:0} -.tribe-side .content-actions{flex-wrap:nowrap;gap:6px} +.tribe-side .content-actions{flex-wrap:wrap;gap:6px} .tribe-side .content-actions .btn-singleview{padding:6px 9px;font-size:1em} -.trending-card.own-content{box-shadow:inset 3px 0 0 #ffa300} +.trending-card.own-content,.addr-card.ubi-pub-card.own-content{box-shadow:inset 3px 0 0 #ffa300} .trending-dominant-sep{margin:0 8px;opacity:0.5} .trending-dominant-text,.trending-total-count{font-weight:700} .comment-file-upload{display:flex;flex-direction:column;align-items:flex-start;gap:6px;background:transparent !important;border:0 !important;padding:0 !important;margin:8px 0} @@ -6601,11 +7238,11 @@ div .header-content{width:100%} .card-section{border:none;padding:10px 0 0 16px} .card-header{display:flex;justify-content:space-between;align-items:center;padding-bottom:0;margin-top:8px;padding-top:0;border:none} .card-label{font-weight:bold;letter-spacing:1.5px;line-height:1.2;margin-bottom:0} -.card-footer{margin-top:6px;font-weight:500;font-size:1.07em;display:flex;align-items:center;gap:8px;background:none;border:none;padding-top:0} +.card-footer{margin-top:6px;font-weight:500;font-size:1.07em;display:flex;flex-wrap:wrap;align-items:center;gap:8px;background:none;border:none;padding-top:0} .card-body{margin-top:0;padding:0} .card-field{display:flex;align-items:baseline;padding:0;margin-bottom:0;border:none;background:none} .card-tags{margin:5px 0 3px 0;display:flex;flex-wrap:wrap;gap:9px} -.card-tags a.tag-link{text-decoration:none;padding:5px 13px 4px 13px;border-radius:7px;font-size:.98em;border:none;font-weight:bold} +.tribe-side .card-tags{background-color:#222 !important;border:none !important;border-radius:10px;padding:20px !important;margin:0 0 20px 0;box-shadow:0 4px 6px rgba(0,0,0,.1);gap:8px} a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bold;text-decoration:none;display:inline-block;border:2px solid transparent;font-size:.8em} .date-link{padding:8px 16px;border-radius:5px;margin-left:8px} .activitySpreadInhabitant2,.activityVotePost{padding:8px 16px;border-radius:5px;font-weight:bold;text-decoration:none;display:inline-block;border:2px solid transparent} @@ -6644,7 +7281,7 @@ a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bol .industry-hero-image,.tribe-card-hero-image{max-width:100%;height:auto;border-radius:6px} .industry-search{margin:8px 0} .industry-card-noimage{display:flex;align-items:center;justify-content:center;min-height:120px;font-size:48px;opacity:.5} -.industry-bot-notification .pm-title,.housing-bot-notification .pm-title{margin-top:4px} +.industry-bot-notification .pm-title,.housing-bot-notification .pm-title,.banking-bot-notification .pm-title{margin-top:4px} .industry-table{width:100%;border-collapse:collapse;margin:8px 0} .industry-table th,.industry-table td{text-align:left;padding:6px 8px;border-bottom:1px solid rgba(128,128,128,.3)} .industry-contrib-form{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0} @@ -6670,9 +7307,8 @@ a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bol .industry-build-notes{margin:22px 0} .industry-dates-row{border:none;background:transparent;padding:0;box-shadow:none;border-radius:0;margin:8px 0} .industry-dates-row p{margin:0} -.dev-breadcrumb,.dev-stats,.dev-file-meta,.dev-file-head,.dev-listing,.dev-pager,.dev-actions,.dev-result-text,.dev-result-head,.dev-desktop{border:none;background:transparent;padding:0;box-shadow:none;border-radius:0} -.dev-stats{margin:12px 0} -.dev-stats p,.dev-file-meta p{margin:4px 0;line-height:1.9} +.dev-breadcrumb,.dev-file-meta,.dev-file-head,.dev-listing,.dev-pager,.dev-actions,.dev-result-text,.dev-result-head,.dev-desktop{border:none;background:transparent;padding:0;box-shadow:none;border-radius:0} +.dev-file-meta p{margin:4px 0;line-height:1.9} .dev-label{font-weight:bold} .dev-sep{opacity:.45} .dev-hint{opacity:.75;font-style:italic} @@ -6716,48 +7352,39 @@ a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bol .dev-code-row td{border:none;vertical-align:top} .dev-code-num{text-align:right;padding:0 14px 0 4px;width:1%;min-width:52px;white-space:nowrap;opacity:.5;user-select:none;border-right:1px solid rgba(128,128,128,.25) !important} .dev-code-line{padding-left:16px} -.ai-suggestion-banner .update-banner-link{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60vw} +.ai-suggestion-banner .update-banner-link{display:inline-block;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60vw} +.welcome-banner .welcome-banner-start{display:inline-flex;align-items:center;margin:0 10px;padding:6px 18px;font-size:1em;text-decoration:none;white-space:nowrap} .welcome-banner .welcome-banner-close{margin:0;padding:0;border:none;background:transparent;box-shadow:none;display:inline-flex;align-items:center} .welcome-banner-close-btn{background:transparent;border:none;color:inherit;cursor:pointer;font-size:1em;line-height:1;padding:0 4px;margin:0;opacity:.6} .welcome-banner-close-btn:hover{opacity:1} -.welcome-ux-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;border:none !important;background:transparent !important} +.welcome-ux-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;border:none !important;background:transparent !important;max-width:680px} .welcome-ux-option{display:flex;flex-direction:column;gap:6px;align-items:center;border:1px solid rgba(128,128,128,.35);border-radius:8px;padding:10px;cursor:pointer;background:transparent} .welcome-ux-option:has(input:checked){border-color:#ffa300;background:rgba(255,163,0,.07)} .welcome-ux-option input{margin:0} -.welcome-ux-shot{width:100%;border-radius:6px;border:1px solid rgba(128,128,128,.25)} +.welcome-ux-shot{width:100%;max-width:320px;border-radius:6px;border:1px solid rgba(128,128,128,.25)} .welcome-ux-label{font-weight:bold} @media (max-width: 700px){.welcome-ux-grid{grid-template-columns:1fr}} -.welcome-progress,.welcome-steps,.welcome-complete,.welcome-footer,.welcome-action,.welcome-step-head{border:none !important;background:transparent !important;padding:0 !important;box-shadow:none !important;border-radius:0 !important} -.welcome-progress{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;margin:12px 0} -.welcome-progress p{margin:0} -.welcome-progress form{margin:0;padding:0;border:none;background:transparent} -.welcome-progress-label{font-weight:bold} -.welcome-optional{opacity:.75;font-style:italic;margin:0 0 18px 0} -.welcome-steps{display:flex;flex-direction:column;gap:14px;margin:16px 0} +.welcome-complete,.welcome-action{border:none !important;background:transparent !important;padding:0 !important;box-shadow:none !important;border-radius:0 !important} .welcome-step{border:1px solid rgba(128,128,128,.3) !important;border-radius:8px;padding:14px 16px !important;margin:0} -.welcome-step-done{opacity:.72} -.welcome-step-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 6px 0} -.welcome-step-number{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;border:1px solid rgba(128,128,128,.5);font-size:.85em;flex:0 0 auto} -.welcome-step-title{margin:0;font-size:1.05em} -.welcome-step-text{margin:0 0 12px 0} -.welcome-chip{border:1px solid rgba(128,128,128,.4);border-radius:12px;padding:2px 10px;font-size:.78em;opacity:.8} -.welcome-chip-done{opacity:1;font-weight:bold} +.welcome-step summary{cursor:pointer} +.welcome-step summary h2{display:inline;margin:0 10px 0 0} +.welcome-step-text{font-size:.95em;opacity:.85} +.welcome-step[open] summary{margin-bottom:10px} .welcome-action{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0} -.welcome-action form,.welcome-language-form{margin:0;padding:0;border:none;background:transparent} -.welcome-language-form select{width:auto;min-width:170px;height:38px;box-sizing:border-box;margin:0} +.welcome-action form{margin:0;padding:0;border:none;background:transparent} .welcome-action .filter-btn{margin:0;height:38px;padding:0 20px;display:inline-flex;align-items:center;justify-content:center} .welcome-complete{margin:16px 0} -.welcome-footer{margin:20px 0 0 0} -.welcome-footer form{margin:0;padding:0;border:none;background:transparent} -.welcome-profile-form,.welcome-greeting-form{margin:0;padding:0;border:none;background:transparent;box-shadow:none} +.welcome-profile-form,.welcome-greeting-form,.welcome-ux-form,.welcome-select-form,.welcome-community-form{margin:0;padding:0;border:none;background:transparent;box-shadow:none} +.welcome-pick{display:flex;align-items:flex-start;gap:10px;cursor:pointer} +.welcome-pick input[type="checkbox"]{margin:18px 0 0 0;flex:0 0 auto;width:auto !important;min-width:0 !important} +.welcome-pick .snh-invite-box{flex:1 1 auto;min-width:0;margin:6px 0} +.welcome-select-form select{width:auto;min-width:220px;margin:0 0 14px 0} .welcome-profile-form label{display:block;margin:10px 0 4px 0} .welcome-profile-form input[type="text"],.welcome-profile-form textarea,.welcome-greeting-form textarea{width:100%;box-sizing:border-box;display:block;margin:0} .welcome-profile-form input[type="file"]{margin:0;width:auto} +.welcome-profile-form input[type="text"]{max-width:420px} .welcome-profile-form .welcome-action,.welcome-greeting-form .welcome-action{margin-top:14px} .welcome-profile-avatar{display:block;width:96px;height:96px;object-fit:cover;border-radius:6px;margin:0 0 10px 0} -.welcome-step-warning{margin:0 0 12px 0} -.welcome-oasisid{border:none !important;background:transparent !important;padding:0 !important;box-shadow:none !important;margin:0 0 14px 0;word-break:break-all} -.welcome-oasisid .user-link{user-select:all} .housing-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:20px} .housing-grid .tribe-card{margin-bottom:0} .housing-card .card-field{border-bottom:none} @@ -6776,7 +7403,6 @@ a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bol .tribe-main .housing-opinions{border:none;background:transparent} .housing-form input[type="file"]{width:auto} .vote-form-error{margin:0 0 14px 0} -.vote-form-error .error-title{margin:0 0 4px 0;font-weight:bold} .vote-form-error p{margin:0} .media-file-row{border:none !important;background:transparent !important;padding:0 !important;box-shadow:none !important;display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 6px 0} .media-file-row input[type="file"]{width:auto;margin:0} @@ -6806,6 +7432,8 @@ a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bol .stats-kpi-inline .stats-kpi-value{font-size:16px} .stats-kpi-inline .stats-kpi-label{white-space:nowrap} .content-actions .content-action-form{margin:0;display:inline-flex} +.content-actions .btn-donate{font-family:inherit;line-height:1;text-decoration:none} +.btn-singleview>*{line-height:1} .content-actions .btn-pin-on{background:#ffa300;color:#000} .content-actions .btn-pin-on:hover{background:#ffb733;color:#000} .content-actions .spread-form{margin:0;display:inline-flex} @@ -6990,6 +7618,10 @@ a.user-link{padding:8px 16px;border-radius:5px;text-align:center;font-weight:bol display: flex; flex-direction: column; align-items: flex-start; + background: transparent !important; + border: 0 !important; + padding: 0 !important; + margin: 0; } .chat-poll-form input[type="text"], @@ -7086,7 +7718,9 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } font-weight: bold; } -.forum-reply-body { +.forum-reply-body, +.trending-card .forum-reply-body { + display: block; margin-bottom: 12px; } @@ -7095,6 +7729,17 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } overflow-wrap: anywhere; } +.forum-reply-text img { + display: block; + max-width: 100%; + height: auto; +} + +.trending-card .card-field > .forum-reply-text { + grid-column: 1 / -1; + min-width: 0; +} + .chat-thread-details, .chat-thread-meta { border: none !important; @@ -7178,10 +7823,28 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } background: transparent !important; } +.poll-ballot-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 6px; +} + +.chat-poll-close-form:empty, +.chat-poll-close-form:not(:has(button)) { + margin: 0; + padding: 0; +} + .report-category-select { width: auto; max-width: 260px; } +.apply-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:8px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.apply-row select{margin:0} +.apply-row .create-button{margin:0} +.form-sep{border:0;border-top:1px solid #444;margin:14px 0} .data-search { margin-top: 10px; @@ -7274,7 +7937,6 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } .data-list { grid-template-columns: 1fr; } } - .data-card { display: flex; flex-direction: column; @@ -7360,7 +8022,6 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } font-size: 1.15em; } - .data-card-author { font-size: .78em; opacity: .7; @@ -7392,6 +8053,11 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } min-width: 0; } +.card-engage > .feed-votes-summary { + grid-column: 1 / -1; + margin: 0; +} + .card-engage > details[open] { grid-column: 1 / -1; } @@ -7532,10 +8198,42 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } .ai-prompt-form{margin-bottom:0} .ai-submit-row{display:flex;gap:1.5em;justify-content:flex-end;align-items:center;margin-top:.7em} -.ai-clear-row{display:flex;justify-content:flex-end;margin-bottom:2em} .ai-clear-form{display:inline} -.ai-clear-btn{background:#b80c09;color:#fff;border:none;padding:.4em 1.2em;border-radius:6px;cursor:pointer;font-size:1em;margin-left:1em} -.user-prompt{margin-bottom:2em;font-size:.95em;color:#888} +.ai-clear-btn{background:transparent;color:#d9534f;border:1px solid #d9534f;padding:.4em 1.2em;border-radius:6px;cursor:pointer;font-size:1em} +.ai-clear-btn:hover{background:#d9534f;color:#fff} +.user-prompt{margin-right:auto;font-size:.85em;color:#888;align-self:center} +.ai-workspace{display:flex;flex-direction:column;gap:12px;background:transparent;border:0;padding:0} +.ai-submit-row{background:transparent;border:0;padding:8px 0 0 0} +.ai-messages-list{padding:10px 14px;gap:14px} +.ai-row .chat-message{padding:10px 14px 8px} +.ai-row.chat-bubble-row-self .chat-message{max-width:85%;margin-left:auto;background:#23242a} +.ai-row:not(.chat-bubble-row-self) .chat-message{max-width:70%;margin-right:auto} +.ai-bubble-user{background:#2f2510} +.ai-chip{background:#1a2a3a;color:#9fd0ff;border-color:#3a6a8f;align-self:center} +.ai-submit-row .ai-chip-waiting{margin-right:auto} +.ai-messages-list .ai-chip-thinking{background:#2a2410;color:#ffd95a;border-color:#a39134;align-self:flex-end;margin-top:4px} +.ai-messages-list .ai-chip-thinking:not(:last-child){display:none} +.ai-bubble-user .chat-message-text,.ai-bubble-42 .chat-message-text{background:transparent;border:0;padding:0;margin:0} +.ai-bubble-42 .ai-answer-line{background:transparent;border:0;padding:0} +.ai-bubble-42{max-width:92%} +.ai-bubble-data{border-left:3px solid #7fb3ff} +.ai-bubble-network{border-left:3px solid #3a7f4c} +.ai-bubble-error{border-left:3px solid #d9534f} +.ai-answer-line{margin:0 0 4px 0} +.ai-rating{display:flex;align-items:center;gap:6px;margin-top:4px;background:transparent;border:0;padding:0} +.ai-stars{flex-direction:row-reverse;justify-content:flex-end} +.ai-star-btn{background:transparent;border:0;color:#555;font-size:18px;line-height:1;padding:0 1px;cursor:pointer} +.ai-star-btn:hover,.ai-star-btn:hover ~ .ai-star-btn{color:#ffa300} +.ai-stars-static{color:#ffa300;font-size:16px;letter-spacing:1px} +.ai-rating-label{font-size:11px;color:#888} +.ai-status{padding:8px 12px;border-radius:6px;font-size:13px;margin-bottom:8px} +.ai-status-loading{background:#2a2410;color:#ffd95a;border:1px solid #a39134} +.ai-status-missing{background:#3a1a1a;color:#ffb3b3;border:1px solid #d9534f} +.ai-export-btn{margin-left:8px} +.blob-cache-clean-btn{margin-left:8px} +.blob-cache-usage{margin:4px 0 10px 0} +.blob-cache-notice{margin:0 0 10px 0;font-weight:bold} +.peers-connect-error{margin:0 0 12px 0;padding:8px 12px;border:1px solid #d9534f;border-radius:6px;color:#d9534f;font-weight:bold} .user-prompt-text{font-style:italic} .chat-entry{margin-bottom:2em;position:relative;background:#191919;border-radius:10px;box-shadow:0 0 8px #0004;padding-top:1.8em} .chat-entry-timestamp{position:absolute;top:.5em;right:1.3em;font-size:.92em;color:#888} @@ -7547,13 +8245,13 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } .ai-train-approved{color:#5ad25a;font-weight:600} .ai-train-rejected{color:#ff6b6b;font-weight:600} -.modules-counts{margin-bottom:16px} -.modules-count-value{color:#888} +.modules-counts{display:flex;flex-wrap:wrap;gap:18px;align-items:baseline;padding:10px 0 !important;margin-bottom:10px;border:0 !important} +.modules-count-value{color:#ffa300} .modules-count-list{list-style-type:none;padding:0;margin:0} -.modules-count-item{font-size:18px;color:#555;margin:8px 0} +.modules-count-item{margin:0} .modules-preset-form{display:inline;margin:0} .module-status-cell{text-align:center} -.save-button-container{margin-top:20px;text-align:center} +.save-button-container{margin-top:20px;text-align:left} .votes-count-negative{margin-left:12px} .forum-thread-header{display:flex;align-items:flex-start} @@ -7566,7 +8264,8 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } .card-field-row{display:flex;gap:.6em;flex-wrap:wrap} .card-section-action{margin-top:6px} .card-section-text{margin:.4rem 0} -.post-text-clamped{max-height:180px;overflow:hidden} +.post-text-clamped{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:16;overflow:hidden;max-height:none} +.post-text-clamped:has(video,audio,iframe,embed,object){display:block;-webkit-line-clamp:unset;max-height:none;overflow:visible} .reply-context{border-left:3px solid #666;padding-left:10px;margin-bottom:8px;opacity:.85} .reply-context-label{font-size:.85em} .reply-context-text{font-size:.85em;max-height:80px;overflow:hidden;margin-top:4px} @@ -7581,8 +8280,12 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } .feed-vote-sep{margin:0 8px;opacity:.5} .feed-vote-dominant{font-weight:700} .image-container-row,.video-container-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap} -.video-container-row{flex-direction:column;align-items:stretch;width:100%} -.video-container-row video{width:100%;max-height:640px} +.image-card .image-container-row, +.card-section .card-field.image-container, +.trending-card .card-field.image-container, +.opinions-card .card-field.image-container{display:block;text-align:center} +.video-container-row{flex-direction:column;align-items:center;width:100%} +.video-container-row video{width:100%;max-height:640px;margin:0 auto} .vote-no-quorum{color:#ffcc00;font-weight:bold} .pixelia-swatch-000000{background-color:#000000} @@ -7601,3 +8304,573 @@ details[open] > .chat-poll-summary::after { content: " \25B4"; } .pixelia-swatch-00bfff{background-color:#00bfff} .pixelia-swatch-d3d3d3{background-color:#d3d3d3} .pixelia-swatch-ff6347{background-color:#ff6347} + +.school-lesson-header{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;flex-wrap:wrap} +.school-lesson-header h3{margin:0} +.school-lesson-header form{margin:0} +.school-complete-btn--done{border-color:#2ecc71;color:#2ecc71} + +.school-invited-list{border:none !important;background:transparent !important;box-shadow:none !important;padding:0;margin:0} +.stacked-action-row{flex-direction:column;align-items:flex-start;gap:6px} +.school-invite-students form{flex-direction:column;align-items:stretch;width:100%} +.school-invite-students form br{display:none} +.school-invite-students input[type="text"]{width:100%} +.school-invite-students form button{align-self:flex-start} +.stacked-action-row .subscription-form{margin:0} +.subscription-actions{display:flex;align-items:center;gap:8px;background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none !important} +.subscription-label-row{display:flex;align-items:center;gap:8px;background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none !important} +.pm-list-row{background:transparent !important;border:0 !important;padding:0 !important;margin:8px 0;box-shadow:none !important} +.school-progress-row{border:none !important;background:transparent !important;box-shadow:none !important;padding:0;margin:0 0 10px 0;display:flex;flex-wrap:wrap;align-items:center;gap:10px} +.school-progress-row p{margin:0} +.school-progress-issue{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0} +.school-progress-issue input[type="text"]{max-width:220px} +.school-progress-failed{font-size:0.85em;color:#e57373;cursor:help} +.school-lessons .school-lesson,.school-exams .school-lesson{background:transparent !important;border:0 !important;box-shadow:none;padding:0 !important;margin:0 0 16px 0} +.school-exam-question{background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;margin:0 0 14px 0} +.top-bar-center .ai-ask-form{flex:1 1 auto;min-width:220px} +.ai-suggestion-inline{display:inline-flex;align-items:center;gap:6px;background:transparent !important;border:0 !important;padding:0 !important;margin:0 0 0 12px !important;box-shadow:none;font-size:.85em;white-space:nowrap;flex:0 1 auto;min-width:0;overflow:hidden} +.ai-suggestion-inline .update-banner-link{display:inline-block;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle} +.ai-suggestion-inline .welcome-banner-close{margin:0;padding:0;background:transparent;border:0;box-shadow:none;display:inline} +.activity-update-title{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap} +.tags-header:not(.module-header-line):not(.activity-header-line) > h2{display:inline-block;margin:0 12px 8px 0;vertical-align:middle} +.tags-header:not(.module-header-line):not(.activity-header-line) > h2 + p{display:inline-block;margin:0 0 8px 0;font-size:.9em;opacity:.8;vertical-align:middle} +.activity-header-line,.module-header-line{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:12px 20px} +.activity-header-line h2,.module-header-line h2{margin:0;line-height:1.1} +.activity-header-line p,.module-header-line p{margin:0;font-size:.9em;opacity:.8;line-height:1.2;padding-top:2px} +.module-header-line .pm-exposition-chip,.module-header-line .pm-exposition-chip-link{align-self:center} +.tribe-mode-buttons,.ui-toolbar--filters,.mode-buttons,.mode-buttons-row,.mode-buttons-cols,.filters,.inhabitant-action{display:flex;flex-wrap:wrap;gap:5px !important;align-items:center} +.tribe-mode-buttons .filter-btn,.ui-toolbar--filters .filter-btn,.mode-buttons .filter-btn,.mode-buttons-row .filter-btn,.mode-buttons-cols .filter-btn,.filters .filter-btn,.inhabitant-action .filter-btn,.tribe-section-nav .filter-btn{background:transparent;color:inherit;font-size:.75em;padding:2px 9px;border:1px solid #555;border-radius:12px;white-space:nowrap;line-height:1.5} +.tribe-mode-buttons .filter-btn:hover,.ui-toolbar--filters .filter-btn:hover,.mode-buttons .filter-btn:hover,.mode-buttons-row .filter-btn:hover,.mode-buttons-cols .filter-btn:hover,.filters .filter-btn:hover,.inhabitant-action .filter-btn:hover,.tribe-section-nav .filter-btn:hover{border-color:#ffa300;color:#ffa300;background:transparent} +.tribe-mode-buttons .filter-btn.active,.ui-toolbar--filters .filter-btn.active,.mode-buttons .filter-btn.active,.mode-buttons-row .filter-btn.active,.mode-buttons-cols .filter-btn.active,.filters .filter-btn.active,.inhabitant-action .filter-btn.active,.tribe-section-nav .filter-btn.active{background:#ffa300;border-color:#ffa300;color:#111;font-weight:bold} +.tribe-mode-buttons .create-button,.ui-toolbar--filters .create-button,.mode-buttons .create-button,.mode-buttons-row .create-button,.filters .create-button{font-size:.75em;padding:2px 10px;border-radius:12px;border:1px solid #ffcc00;line-height:1.5;background:#ffcc00;color:#111;font-weight:bold} +.filter-box{padding:0;justify-content:flex-end;border:none;background:transparent} +.filter-box input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),.filter-box select,.filter-box button{height:32px !important} +.filter-box input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]),.filter-box select{height:32px;padding:0 12px} +.activity-filter-chips{display:flex;flex-wrap:wrap;align-items:center;gap:5px;padding:10px 14px !important;margin-bottom:10px} +.activity-chip{text-decoration:none;font-size:.75em;padding:2px 9px;border:1px solid #555;border-radius:12px;color:inherit;white-space:nowrap;line-height:1.5} +.activity-chip:hover{border-color:#ffa300;color:#ffa300} +.activity-chip.active{background:#ffa300;border-color:#ffa300;color:#111;font-weight:bold} +.activity-toolbar-row{margin-top:-4px;max-width:100%;box-sizing:border-box} +.activity-toolbar-row .filter-box{margin-left:auto;flex:0 1 auto;flex-wrap:nowrap;max-width:100%;min-width:0} +.activity-toolbar-row .filter-box select{flex:0 1 auto !important;width:auto !important;max-width:220px;height:32px !important} +.activity-toolbar-row .filter-box .transfer-range{flex:0 1 auto !important;width:auto !important;min-width:0 !important;display:flex;gap:8px} +.activity-toolbar-row .filter-box .transfer-range input{flex:0 1 130px;min-width:90px;width:auto !important} +.activity-toolbar-row .filter-box__controls{gap:8px !important;justify-content:flex-end !important} +.activity-toolbar-row .filter-box__controls > *{margin:0 !important;flex:0 1 auto !important;width:auto !important} +.activity-toolbar-row .filter-box__controls > .transfer-range{min-width:0 !important} +.activity-toolbar-row .inbox-exposition{margin:0 !important;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none;display:flex;align-items:center;gap:8px} +.activity-toolbar-row .module-stats-line{margin:0 !important;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none;display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:.72em;white-space:nowrap;text-transform:uppercase} +.module-stats-line strong{text-transform:none} +.activity-toolbar-row .inbox-exposition .btn{margin:0;padding:2px 10px;font-size:.75em;border-radius:12px;line-height:1.5} +.activity-toolbar-row .inbox-exposition form{margin:0;padding:0;background:transparent;border:0;box-shadow:none} +.activity-toolbar-row .filter-box > input[type="number"]{flex:0 0 150px;min-width:150px} +.activity-toolbar-row .filter-box > .housing-place-input{flex:0 1 220px;min-width:140px} +.activity-toolbar-row .filter-box__controls{flex-wrap:nowrap;flex:0 0 auto} +.activity-toolbar-row .filter-box .filter-box__input{min-width:0} +.activity-toolbar-row .filter-box__controls{flex:0 0 auto} +.filters{padding:10px 14px;margin-bottom:10px} +.filters,.mode-buttons,.mode-buttons-row,.tribe-mode-buttons,.inhabitant-action,.activity-filter-chips,.activity-sub-filter,.filter-group,.activity-filter-grid,.logs-toolbar-wrap{border:0 !important} +.mode-buttons-row,.mode-buttons,.tribe-mode-buttons,.inhabitant-action{padding:10px 14px !important;margin-bottom:10px !important} +.mode-buttons-cols{display:contents !important} +.filters > form:not(.filter-box),.inhabitant-action > form,.tribe-mode-buttons > form,.mode-buttons > form,.mode-buttons-row > form,.filter-group > form,.activity-filter-grid > form{display:contents} +.mode-buttons .column,.mode-buttons-row .column,.mode-buttons-row .filter-column,.mode-buttons-cols .filter-column,.mode-buttons-row .filter-group,.mode-buttons .filter-group,.forum-create-col,.activity-filter-col{display:contents !important} +.activity-filter-grid,.filter-group,.activity-sub-filter{display:flex !important;flex-wrap:wrap;gap:5px !important;align-items:center;padding:10px 14px !important;margin-bottom:10px !important;grid-template-columns:none !important} +.activity-filter-grid form,.filter-group form,.activity-sub-filter form,.sub-filter-form{margin:0 !important;padding:0 !important;background:transparent !important;border:none !important;box-shadow:none !important} +.activity-filter-grid .filter-btn,.filter-group .filter-btn,.activity-sub-filter .filter-btn{margin:0;background:transparent;color:inherit;font-size:.75em;padding:2px 9px;border:1px solid #555;border-radius:12px;white-space:nowrap;line-height:1.5} +.activity-filter-grid .filter-btn.active,.filter-group .filter-btn.active,.activity-sub-filter .filter-btn.active{background:#ffa300;border-color:#ffa300;color:#111;font-weight:bold} +.filter-group .create-button,.activity-sub-filter .create-button,.activity-filter-grid .create-button{margin:0;font-size:.75em;padding:2px 10px;border-radius:12px;border:1px solid #ffcc00;background:#ffcc00;color:#111;font-weight:bold;line-height:1.5} +.mode-buttons-row form,.mode-buttons form,.tribe-mode-buttons form{margin:0 !important;padding:0 !important;background:transparent !important;border:none !important;box-shadow:none !important} +.mode-buttons-row .filter-btn,.mode-buttons-row .create-button,.mode-buttons .filter-btn,.mode-buttons .create-button,.tribe-mode-buttons .filter-btn,.tribe-mode-buttons .create-button{margin:0} +.filters form{margin:0;padding:0;background:transparent;border:none;box-shadow:none} +.filters .filter-btn,.filters .create-button,.ui-toolbar--filters .filter-btn,.ui-toolbar--filters .create-button{margin:0} +.activity-subchip-label{color:#ffa300;font-weight:bold} +.transfers-subfilters{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:8px 0 0;background:transparent !important;border:0 !important;padding:0 !important} +.activity-update-counts{white-space:nowrap} +.activity-update-msg{margin:10px 15px 0 0} +.activity-update-msg .post-text{margin:0} +.school-exam-question p{margin:2px 0} +.school-exam-correct{color:#2ecc71} +.school-exam-option-row{color:#999} +.school-panel-box{border:1px solid #444;border-radius:8px;margin:0 0 14px 0} +.school-panel-box h3{margin:0 0 10px 0;color:#ffa300;font-size:.9em;text-transform:uppercase;letter-spacing:1px} +.school-students-table{width:100%;border-collapse:collapse} +.school-students-table th{text-align:left;color:#ffa300;font-size:.85em;text-transform:uppercase;letter-spacing:1px;padding:6px 10px;border-bottom:1px solid #444} +.school-students-table td{padding:6px 10px;border-bottom:1px solid #333;vertical-align:middle} +.school-students-table tr.school-row-passed td{background:rgba(46,204,113,.08)} +.school-students-table .school-progress-issue input[type="text"]{max-width:160px} +.school-progress-bar{flex:1 1 auto;min-width:100px;height:12px;accent-color:#ffa300} +.school-progress-count{font-size:.85em;white-space:nowrap} +.school-unit{background:transparent !important;border:0 !important;padding:0 !important;margin:0 0 14px 0;box-shadow:none} +.school-unit-summary{cursor:pointer;list-style:none;font-weight:bold;color:#ffa300;text-transform:uppercase;letter-spacing:1px;margin:0 0 8px 0} +.school-unit-summary::-webkit-details-marker{display:none} +.school-unit-summary::after{content:" \25BE";opacity:.7} +details[open] > .school-unit-summary::after{content:" \25B4"} +.school-lessons .school-lesson-next{border-left:3px solid #ffa300 !important;padding-left:10px !important} +.school-pending-block{border:none !important;background:transparent !important;box-shadow:none !important;padding:0;margin:0} +.school-pending-row{justify-content:center;border:none !important;background:transparent !important;box-shadow:none !important;padding:0;margin:0} +.school-pending-block .doc-export-actions{margin-top:8px;margin-bottom:0} + +.wiki-body{line-height:1.55} +.wiki-paragraph{margin:0 0 12px 0} +.wiki-link{color:#ffd740;text-decoration:underline dotted} +.wiki-link-missing{color:#e74c3c;border-bottom:1px dashed #e74c3c;text-decoration:none} +.wiki-page-title{margin:0 0 8px 0;overflow-wrap:anywhere} +.wiki-version-notice{font-size:.9em;opacity:.85;margin:6px 0 10px 0} +.wiki-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important} +.wiki-actions form{margin:0;padding:0;background:transparent !important;border:0 !important} +.wiki-backlinks ul{margin:6px 0 0 18px;padding:0} +.wiki-backlinks li{margin:2px 0} +.wiki-diff{font-family:monospace;font-size:.9em;white-space:pre-wrap;overflow-x:auto;background:transparent !important;border:0 !important;padding:0 !important} +.wiki-diff-line{display:flex;gap:8px;padding:1px 6px;background:transparent !important;border:0 !important;margin:0} +.wiki-diff-sign{width:1em;flex:0 0 1em;opacity:.7} +.wiki-diff-add{background:rgba(46,204,113,.16) !important} +.wiki-diff-del{background:rgba(231,76,60,.16) !important;text-decoration:line-through;opacity:.85} +.wiki-history .wiki-version,.wiki-changes .wiki-change{margin-bottom:10px} +.wiki-version-current{border-left:3px solid #ffa500} +.wiki-change-summary{margin:4px 0;opacity:.9} + +.emergency-severity{display:inline-block;font-size:.72em;font-weight:bold;letter-spacing:.06em;padding:2px 9px;border-radius:12px;border:1px solid transparent;white-space:nowrap} +.emergency-severity-unverified{color:#fff;border-color:#c0392b;background:#c0392b} +.emergency-severity-low{color:#111;background:#f1c40f;border-color:#f1c40f} +.emergency-severity-medium{color:#111;background:#e67e22;border-color:#e67e22} +.emergency-severity-high{color:#fff;background:#e74c3c;border-color:#e74c3c} +.emergency-card-low{border-left:3px solid #f1c40f} +.emergency-card-medium{border-left:3px solid #e67e22} +.emergency-card-high{border-left:3px solid #e74c3c} +.emergency-title{margin:0 0 8px 0;overflow-wrap:anywhere} +.emergency-meta{margin:6px 0;font-size:.9em;opacity:.85} +.emergency-body{line-height:1.5;margin:8px 0} +.emergency-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important} +.emergency-actions form{margin:0;padding:0;background:transparent !important;border:0 !important} +.emergency-updates{margin-top:14px} +.emergency-update{margin:8px 0;padding:8px 12px !important;border-left:2px solid #ffa500} +.emergency-update-form{margin-top:10px;background:transparent !important;border:0 !important;padding:0 !important} +.emergency-update-form textarea{width:100%;max-width:640px} +.emergency-banner-high{border-color:#e74c3c} +.emergency-banner-medium{border-color:#e67e22} +.emergency-banner .update-banner-icon{color:#e74c3c} + +.mailing-counts{margin:0;opacity:.85} +.mailing-archive{list-style:none;margin:0;padding:0;background:transparent !important;border:0 !important} +.mailing-archive-item{margin:0 0 12px 0;padding:12px 14px !important;border-left:2px solid #ffa300} +.mailing-archive-head{margin:0 0 8px 0} +.mailing-archive-item>div{margin:0;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none} +.mailing-archive-head>.content-actions,.mailing-archive-head>.card-header{margin:0;padding:0 !important} +.mailing-archive-meta{display:flex;flex-wrap:wrap;gap:6px 12px;align-items:center;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.mailing-archive-row{margin:0} +.mailing-archive-meta>.user-link{max-width:100%;overflow-wrap:anywhere} +.mailing-archive-meta>*{margin:0} +.mailing-description{margin:6px 0;overflow-wrap:anywhere} +.mailing-title{margin:0 0 8px 0;overflow-wrap:anywhere} +.mailing-body{line-height:1.5;margin:8px 0} +.mailing-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-actions form{margin:0;padding:0;background:transparent !important;border:0 !important} +.mailing-participants{margin:8px 0;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-participants-list{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:6px;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-participants-list div{background:transparent !important;border:0 !important;padding:0 !important} +.mailing-history{margin-top:14px} +.mailing-history .filter-box{margin:8px 0} +.mailing-message{margin:10px 0;padding:10px 12px !important;border-left:2px solid #8e44ad} +.mailing-message-head{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;margin-bottom:6px;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-message-subject{font-weight:bold;overflow-wrap:anywhere} +.mailing-message-text{line-height:1.5;overflow-wrap:anywhere;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-reply-form{margin:6px 0 0 0;padding:0;background:transparent !important;border:0 !important} +.mailing-thread{margin:8px 0;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-thread-details{margin:0 0 0 16px;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-thread-details summary{cursor:pointer;font-size:.9em;opacity:.85} +.mailing-author-group{margin:10px 0;background:transparent !important;border:0 !important;padding:0 !important} +.mailing-author-title{margin:0 0 4px 0;font-size:1em} +.mailing-author-count{opacity:.7} +.mailing-compose{margin:14px 0} +.mailing-compose textarea{width:100%;max-width:640px} +.mailing-inbox-list{font-weight:bold} +.mailing-inbox-reply{display:flex;flex-direction:column;gap:6px;align-items:flex-start;width:100%} +.mailing-inbox-reply-text{width:100%;max-width:640px} + +.logistics-route-line{margin:6px 0;font-weight:bold;overflow-wrap:anywhere} +.logistics-zone{overflow-wrap:anywhere} +.logistics-zones{align-items:center} +.logistics-price-unit{width:auto;margin-left:6px} +.logistics-short{width:120px} +.logistics-title{margin:0 0 8px 0;overflow-wrap:anywhere} +.logistics-body{line-height:1.5;margin:8px 0} +.logistics-rating-line{color:#f1c40f} +.logistics-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important} +.logistics-actions form{margin:0;padding:0;background:transparent !important;border:0 !important} +.logistics-book-form{background:transparent !important;border:0 !important;padding:0 !important} +.logistics-book-form textarea{width:100%;max-width:640px} +.logistics-bookings{margin-top:14px} +.logistics-booking{margin:8px 0;padding:8px 12px !important;border-left:2px solid #16a085} +.logistics-booking-head{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;background:transparent !important;border:0 !important;padding:0 !important} +.logistics-booking-notes{margin:6px 0;overflow-wrap:anywhere} +.logistics-receipt{margin:6px 0;font-size:.9em} +.logistics-booking-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;background:transparent !important;border:0 !important;padding:0 !important} +.logistics-booking-actions form,.logistics-receipt-form,.logistics-rate-form{margin:6px 0 0 0;padding:0;background:transparent !important;border:0 !important} +.logistics-receipt-form{display:flex;flex-wrap:wrap;gap:6px;align-items:center} +.logistics-ratings{margin-top:14px} +.logistics-rating{margin:8px 0;padding:8px 12px !important;border-left:2px solid #f1c40f} +.logistics-rating-head{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;background:transparent !important;border:0 !important;padding:0 !important} +.logistics-stars{color:#f1c40f;letter-spacing:.1em} +.logistics-rate-form textarea{width:100%;max-width:640px} + +.podcast-card-cover{width:100%;max-height:220px;object-fit:cover;border-radius:6px;display:block;margin:0 0 8px 0} +.podcast-cover{width:120px;height:120px;object-fit:cover;border-radius:6px;display:block} +.podcast-cover-large{width:220px;height:220px} +.podcast-page-body{display:flex;gap:18px;align-items:flex-start;flex-wrap:wrap} +.podcast-page-main{flex:1 1 320px;min-width:0;background:transparent !important;border:0 !important;padding:0 !important} +.podcast-description{margin:6px 0;overflow-wrap:anywhere} +.podcast-counts{margin:6px 0;opacity:.85} +.podcast-title{margin:0 0 8px 0;overflow-wrap:anywhere} +.podcast-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important} +.podcast-actions form{margin:0;padding:0;background:transparent !important;border:0 !important} +.podcast-episodes{margin-top:14px} +.podcast-episode-list{background:transparent !important;border:0 !important;padding:0 !important} +.podcast-episode-row{display:flex;gap:12px;align-items:flex-start;margin:8px 0;padding:8px 12px !important;border-left:2px solid #d35400} +.podcast-episode-number{font-weight:bold;opacity:.8;flex:0 0 auto} +.podcast-episode-main{flex:1 1 auto;min-width:0;background:transparent !important;border:0 !important;padding:0 !important} +.podcast-episode-title{font-weight:bold;overflow-wrap:anywhere;display:inline-block;margin:0} +.podcast-episode-main .tribe-side-description{margin:0 0 8px 0} +.podcast-episode-main .card-footer{margin:0} +.podcast-episode-head{margin:0 0 10px 0} +.podcast-featured{margin:0 0 16px 0;background:transparent !important;border:0 !important;padding:0 !important} +.podcast-cover-link{display:inline-block;border:0 !important;background:transparent !important;padding:0 !important} +.tribe-side .card-tags + .tribe-side-actions{margin-top:0} +.podcast-episode-head .tribe-card-title{margin:0} +.podcast-episode-head-left{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:transparent !important;border:0 !important;padding:0 !important;margin:0} +.podcast-player{margin:10px 0;background:transparent !important;border:0 !important;padding:0 !important} +.podcast-player audio{width:100%;max-width:100%} +.podcast-player.video-container .podcast-video{width:100%;max-width:100%;max-height:640px;display:block} +@media (max-width:680px){.podcast-cover-large{width:140px;height:140px}} + +.campaign-progress{margin:8px 0} +.campaign-title{margin:0 0 8px 0;overflow-wrap:anywhere} +.campaign-body{line-height:1.5;margin:8px 0} +.campaign-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:12px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important} +.campaign-actions form{margin:0;padding:0;background:transparent !important;border:0 !important} +.campaign-page-body{display:flex;gap:18px;align-items:flex-start;flex-wrap:wrap} +.campaign-page-main{flex:1 1 320px;min-width:0;background:transparent !important;border:0 !important;padding:0 !important} +.campaign-signed{text-align:center;margin:8px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.campaign-qr{flex:0 0 auto;text-align:center;background:transparent !important;border:0 !important;padding:0 !important} +.campaign-qr-img{width:160px;height:160px;display:block;margin:0 auto;background:#fff;border-radius:6px} +.campaign-qr-caption{font-size:.8em;opacity:.8;max-width:180px;margin:6px auto 0 auto} +.campaign-sign-form{background:transparent !important;border:0 !important;padding:0 !important} +.campaign-sign-form textarea,.campaign-update-form textarea{width:100%;max-width:640px} +.campaign-updates,.campaign-signatures{margin-top:14px} +.campaign-update{margin:8px 0;padding:8px 12px !important;border-left:2px solid #c0392b} +.campaign-update-form{margin-top:10px;background:transparent !important;border:0 !important;padding:0 !important} +.campaign-signature{margin:6px 0;padding:6px 12px !important;border-left:2px solid #27ae60} +.campaign-signature-head{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;background:transparent !important;border:0 !important;padding:0 !important} +.campaign-signature-text{margin:4px 0 0 0;overflow-wrap:anywhere} +.campaign-signature-list{background:transparent !important;border:0 !important;padding:0 !important;margin-bottom:8px} +.campaign-achieved{border-left:3px solid #27ae60} + +.backup-section{background:transparent !important;border:0 !important;padding:0 !important} +.backup-section-title{margin:0 0 10px 0} +.backup-estimate{margin:0 0 12px 0;background:transparent !important;border:0 !important;padding:0 !important} +.backup-hint{font-size:.85em;opacity:.8;margin:4px 0} +.backup-warning{color:#e74c3c;font-weight:bold} +.backup-verification ul{margin:6px 0;padding-left:18px} +.backup-restored{margin-bottom:12px;background:transparent !important;border:0 !important;padding:0 !important} +.backup-kit{max-width:none;margin:0;background:transparent !important;border:0 !important;padding:0 !important} +.backup-wrap{background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.backup-kit-line{margin:6px 0;overflow-wrap:anywhere} +.backup-kit-qr{display:flex;justify-content:center;margin:12px 0;background:transparent !important;border:0 !important;padding:0 !important} +.backup-kit-qr-img{width:260px;height:260px;background:#fff;border-radius:6px;padding:8px} +.backup-kit-secret{white-space:pre;overflow-x:auto;font-size:1em;line-height:1.6;padding:10px;border:1px dashed currentColor;border-radius:6px} +.backup-kit-steps{margin:10px 0 0 0;padding-left:18px} +.backup-kit .doc-export-actions{margin-top:6px} +.backup-verification{margin-top:10px} +.backup-check-ok{color:#27ae60} +.backup-check-bad{color:#e74c3c} +.backup-check-warn{color:#f1c40f} +.backup-fork{overflow-wrap:anywhere;font-size:.9em} +@media print{nav,.sidebar-left,.sidebar-right,.filters,.report-form,.module-header-line p{display:none !important}.backup-kit{max-width:none}.backup-kit-secret{color:#000;border-color:#000}body{background:#fff;color:#000}} +.wiki-card-cover{width:100%;max-height:220px;object-fit:cover;border-radius:6px;display:block;margin:0 0 8px 0} +.wiki-actions-top{justify-content:flex-end;margin:0 0 10px 0} +.wiki-actions-top .wiki-actions-left{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-right:auto} +.wiki-version-actions{justify-content:flex-start;margin:6px 0} +.wiki-preview,.blog-preview{margin:0 0 14px 0} +.stats-kpi{margin:0 !important;padding:0 !important;box-shadow:none !important;overflow:visible} + +.stats-kpi-label{white-space:nowrap} +.stats-block,.stats-container{box-shadow:none !important} +.emergency-card-cover{width:100%;max-height:220px;object-fit:cover;border-radius:6px;display:block;margin:0 0 8px 0} +.trending-card .card-field.media-left{text-align:left;align-items:flex-start} +.trending-card .media-left .zoom-link:not(.zoom-inline){display:inline-block;text-align:left} +.trending-card .media-left .post-image{margin-left:0;margin-right:0} +.campaign-card-cover,.logistics-card-cover{width:100%;max-height:220px;object-fit:cover;border-radius:6px;display:block;margin:0 0 8px 0} +.campaign-progress .confirmations-progress{width:100%;margin-top:6px} +.logistics-booking,.logistics-rating,.mailing-message{margin:8px 0} +.emergency-update-head{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 6px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.emergency-update-head .card-chips-row{margin:0} +.emergency-update-actions{margin:0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.emergency-update-head .card-chips-row{margin:0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.backup-kit-id{font-family:monospace;font-size:1.1em} + +.preview-rendered{white-space:pre-wrap} +.blog-card-text, +.blog-detail-text, +.feed-text, +.emergency-body, +.mailing-message-text{white-space:pre-wrap} +.rt-header{display:block;font-weight:bold;margin:10px 0 4px;line-height:1.3} +.rt-header-1{font-size:1.5em} +.rt-header-2{font-size:1.3em} +.rt-header-3{font-size:1.15em} +.rt-item{display:block;padding-left:1.4em;text-indent:-0.7em} +.rt-item-2{padding-left:3em} +.rt-item-3{padding-left:4.6em} +.rt-item-4{padding-left:6.2em} +.rt-item::before{content:"• ";opacity:.7} +.rt-item-number{text-indent:-1.2em} +.rt-item-number::before{content:none} +.rt-item .tag-link{text-indent:0} +.rt-quote{display:block;border-left:3px solid currentColor;padding-left:10px;margin:6px 0;opacity:.85} +.rt-rule{display:block;border-top:1px solid currentColor;opacity:.4;margin:10px 0} +.rt-code{font-family:monospace;background:rgba(128,128,128,.18);padding:1px 4px;border-radius:3px;word-break:break-word} +.rt-code-block{display:block;font-family:monospace;white-space:pre-wrap;background:rgba(128,128,128,.18);padding:10px;border-radius:5px;margin:8px 0;overflow-x:auto;word-break:break-word} +.rt-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;width:fit-content;max-width:100%;margin:14px 0 0 !important;padding:5px 10px !important;font-size:.95em;background:rgba(128,128,128,.13) !important;box-shadow:none !important;border:none !important;border-radius:5px} +.rt-toolbar-label{font-weight:bold} +.rt-sep{opacity:.4} +.rt-mark{font-family:monospace;white-space:nowrap} +.rt-mark-bold{font-weight:bold} +.rt-mark-italic{font-style:italic} +.rt-mark-underline{text-decoration:underline} +.rt-mark-strike{text-decoration:line-through} + +.qr-code{display:flex;justify-content:center;align-items:center;width:100%} +.wallet-qr-img{max-width:320px;width:100%;height:auto} +label.wallet-create-transfer{display:flex !important;flex-direction:row !important;align-items:center !important;justify-content:flex-start !important;gap:8px;text-align:left !important;width:100% !important;margin:8px 0} +label.wallet-create-transfer input[type="checkbox"]{width:auto !important;flex:0 0 auto !important;margin:0 !important} +.wallet-status{margin:0 0 12px 0} +.wallet-history .wallet-col-confirmations{width:10%} +.wallet-history .wallet-col-date{width:34%;white-space:nowrap} +.torrent-file-info{margin:0 0 10px 0} +.hemicycle{background:transparent;border:0;padding:0;margin:12px 0} +.hemicycle-canvas{background:transparent;border:0;padding:0;margin:0 auto;max-width:760px} +.hemicycle-svg{width:100%;height:auto;display:block} +.hemi-seat{fill:#444;stroke:#1a1a1a;stroke-width:1} +.hemi-seat.hemi-leader{stroke:#fff;stroke-width:2.5} +.hemi-seat.hemi-power{stroke:#ffd700;stroke-width:2} +.hemi-bloc-gov{fill:#ffa300} +.hemi-bloc-opp0{fill:#ab47bc}.hemi-bloc-opp1{fill:#ef5350}.hemi-bloc-opp2{fill:#26a69a}.hemi-bloc-opp3{fill:#ec407a}.hemi-bloc-opp4{fill:#8d6e63}.hemi-bloc-opp5{fill:#ffca28}.hemi-bloc-opp6{fill:#4fc3f7}.hemi-bloc-opp7{fill:#9e9e9e} +.hemi-bloc-none{fill:#2f6fb5} +.hemi-house-academia{fill:#5c6bc0}.hemi-house-solaris{fill:#ffb300}.hemi-house-arrakis{fill:#d2691e}.hemi-house-terraverde{fill:#43a047}.hemi-house-unsystem{fill:#9e9e9e}.hemi-house-dogma{fill:#8e24aa}.hemi-house-helix{fill:#00acc1}.hemi-house-quark{fill:#e53935}.hemi-house-hermandad{fill:#c0a000} +.hemi-threshold{stroke:#ddd;stroke-width:1.5;stroke-dasharray:4 3} +.hemi-threshold-quorum{stroke:#66bb6a} +.hemi-threshold-pass{stroke:#ffa300} +.hemi-threshold-label{font-size:11px;font-weight:400;fill:#ddd} +.hemi-threshold-label.hemi-threshold-quorum{fill:#66bb6a} +.hemi-threshold-label.hemi-threshold-pass{fill:#ffa300} +.hemi-modes{padding:0 !important;margin:0 0 8px 0;display:flex;gap:6px;justify-content:center;background:transparent !important;border:0 !important} +.hemi-legend{list-style:none;margin:10px auto 0;padding:0;display:flex;flex-wrap:wrap;gap:6px 18px;justify-content:center} +.hemi-legend-item{display:inline-flex;align-items:center;gap:6px;font-size:13px} +.hemi-swatch{display:inline-block;width:12px;height:12px;border-radius:50%;background:#444} +.hemi-swatch.hemi-bloc-gov{background:#ffa300} +.hemi-swatch.hemi-bloc-opp0{background:#ab47bc}.hemi-swatch.hemi-bloc-opp1{background:#ef5350}.hemi-swatch.hemi-bloc-opp2{background:#26a69a}.hemi-swatch.hemi-bloc-opp3{background:#ec407a}.hemi-swatch.hemi-bloc-opp4{background:#8d6e63}.hemi-swatch.hemi-bloc-opp5{background:#ffca28}.hemi-swatch.hemi-bloc-opp6{background:#4fc3f7}.hemi-swatch.hemi-bloc-opp7{background:#9e9e9e} +.hemi-swatch.hemi-bloc-none{background:#2f6fb5} +.hemi-swatch.hemi-house-academia{background:#5c6bc0}.hemi-swatch.hemi-house-solaris{background:#ffb300}.hemi-swatch.hemi-house-arrakis{background:#d2691e}.hemi-swatch.hemi-house-terraverde{background:#43a047}.hemi-swatch.hemi-house-unsystem{background:#9e9e9e}.hemi-swatch.hemi-house-dogma{background:#8e24aa}.hemi-swatch.hemi-house-helix{background:#00acc1}.hemi-swatch.hemi-house-quark{background:#e53935}.hemi-swatch.hemi-house-hermandad{background:#c0a000} +.hemi-legend-link{color:#ffa300;text-decoration:none} +.hemi-legend-count{color:#ffd700;font-size:12px} +.hemi-legend-item{color:#ffd700} +.hemi-note{text-align:center;color:#ffd700;font-size:12px;margin:6px 0 0} +.torrent-oasis{margin:8px 0 14px} +.torrent-progress{width:160px;max-width:100%;vertical-align:middle} +.torrent-progress-text{font-family:monospace;font-size:.85em} +.torrent-progress-cell{white-space:nowrap} +.torrent-from-notice p{margin:0 0 10px} +.btn-torrent{font-weight:700} +.peertube-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px} +.peertube-card{display:flex;flex-direction:column;gap:8px;padding:10px} +.peertube-thumb{position:relative;display:block;border-radius:6px;overflow:hidden;background:#000} +.peertube-thumb img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover} +.peertube-thumb-empty{width:100%;aspect-ratio:16/9;background:#222} +.peertube-duration{position:absolute;right:6px;bottom:6px;background:rgba(0,0,0,.8);color:#fff;font-size:.78em;padding:1px 6px;border-radius:4px;font-family:monospace} +.peertube-title{font-weight:700;display:block} +.peertube-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:.85em;opacity:.9} +.peertube-channel-avatar{width:22px;height:22px;border-radius:50%;object-fit:cover} +.peertube-player{width:100%;max-height:70vh;background:#000;border-radius:6px} +.peertube-watch{gap:10px} +.peertube-description{white-space:pre-wrap;word-break:break-word} +.peertube-nostream p{margin:8px 0 0} +.torrent-table td{vertical-align:middle} +.torrent-spread-cell{text-align:center;white-space:nowrap} +.torrent-spread-cell .spread-form{display:inline-block;margin:0;vertical-align:middle} +.torrent-spread-cell .btn-singleview{margin:0;vertical-align:middle} +.torrent-detail-section{margin:0 0 18px} +.torrent-section-title{margin:0 0 8px;font-size:1em;text-transform:uppercase;letter-spacing:1px;opacity:.85} +.torrent-detail-section .tribe-info-table{margin:0 0 10px} +.pad-page .tribe-side .pad-actions-top{flex-basis:100%;display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin:0} +.pad-page .tribe-side .pad-actions-top .card-header{margin:0;padding:0;border:none;background:transparent;box-shadow:none} +.pad-page .tribe-side .pad-actions-top form{margin:0;padding:0} +.pad-page .tribe-side .shop-title-row{flex-basis:auto} +.torrent-card-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:0;margin:0;border:none;background:transparent;box-shadow:none} +.feed-preview{margin:14px 0} +.feed-preview-title{font-size:1em;margin:0 0 8px;text-transform:uppercase;letter-spacing:1px;opacity:.85} +.feed-compose-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:10px} +.peer-tor-no{color:#888;font-weight:700} +.room-join-form{display:inline-flex;margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} +.room-join-btn,.room-join-form .room-join-btn{display:inline-flex;align-items:center;gap:4px;border-radius:10px;font-size:12px;font-weight:700;line-height:1.4;padding:2px 10px;margin:0;width:auto;height:auto;min-height:0;border:1px solid #ffa500;background:transparent;color:#ffa500;box-shadow:none;cursor:pointer;text-decoration:none} +.room-join-btn:hover,.room-join-form .room-join-btn:hover{background:#ffa500;color:#1a1a1a} +.room-join-form .room-join-btn:disabled{opacity:.5;cursor:default} +.room-item .mailing-archive-head{flex-wrap:nowrap;align-items:flex-start} +.room-item .mailing-archive-meta{flex:1 1 0;min-width:0} +.room-peer{flex-wrap:nowrap;min-width:0} +.room-peer .user-link{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.room-peer-you{flex:0 0 auto;color:#888;font-size:12px;background:transparent;border:0;padding:0} +.room-peer-muted{flex:0 0 auto;color:#ff6b6b;font-size:11px;font-weight:700;border:1px solid #ff6b6b;border-radius:10px;padding:0 8px;line-height:1.5} +.room-peer-hand{flex:0 0 auto;color:#FFA500;font-size:11px;font-weight:700;border:1px solid #FFA500;border-radius:10px;padding:0 5px;line-height:1.5;white-space:nowrap} +.room-peer-raised{border-left:3px solid #FFA500} +.tribe-action-btn.room-hand-btn-on{border-color:#FFA500;color:#FFA500} +.room-peer .phone-action-form{flex:0 0 auto;margin-left:auto} +.room-live-controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end;margin:0;padding:0;border:none;background:transparent;flex:1 0 100%;min-width:0} +.room-live-controls>form,.room-live-controls>a,.room-live-controls>span{display:inline-flex;align-items:center;margin:0;padding:0;flex:0 0 auto} +.room-live-controls .tribe-action-btn,.room-live-controls .room-refresh-btn{margin:0;display:inline-flex;align-items:center;justify-content:center;height:28px;line-height:1;padding:0 12px;font-family:inherit;font-size:12px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;white-space:nowrap;width:auto;min-width:0;max-width:none;overflow:visible;box-sizing:border-box;border-width:1px;border-style:solid;border-radius:6px;box-shadow:none;flex:0 0 auto} +.room-live-controls>*{min-width:0;flex:0 0 auto} +.room-live-head,.room-live-controls,.room-live-controls>form{overflow:visible} +.room-live-controls .room-rec-chip{min-width:max-content;padding:2px 8px} +.room-live-head{flex-wrap:wrap;align-items:center} +.room-live-head-main{flex:1 1 160px} +.room-rec-chip{flex:0 0 auto;color:#ff4d4d;font-size:11px;font-weight:700;border:1px solid #ff4d4d;border-radius:10px;padding:0 8px;line-height:1.5;white-space:nowrap} +.room-notify-off{opacity:.6} +.room-events{list-style:none;margin:10px 0 0 0;padding:0;display:flex;flex-direction:column;gap:4px;font-size:12px} +.room-event{display:flex;gap:8px;align-items:center;flex-wrap:nowrap;min-width:0} +.room-event .date-link{flex:0 0 auto;margin:0} +.room-event-text{display:flex;gap:6px;align-items:center;flex-wrap:nowrap;min-width:0;white-space:nowrap} +.room-event-text .user-link{flex:0 1 auto;min-width:0;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block} +.room-recordings{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:6px} +.room-recording{display:flex;gap:8px;align-items:center;flex-wrap:wrap} +.room-peer .phone-action-form .tribe-action-btn{padding:2px 8px;font-size:11px} +.phone-call-peers{margin:8px 0 0 0} +.block-info-table .pm-exposition-chip{white-space:nowrap} +.peers-technical-block .inline-form .filter-btn{white-space:nowrap} +.peers-technical-block .peer-port{white-space:nowrap} +.peers-technical-block .peer-host{overflow-wrap:normal;word-break:normal} +.phone-chip-form{display:inline-flex;margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} +.phone-chip-btn{display:inline-flex;align-items:center;background:transparent !important;border:0 !important;padding:0 !important;margin:0;width:auto;height:auto;min-height:0;box-shadow:none;cursor:pointer;font:inherit;line-height:inherit;color:inherit} +.phone-chip-btn:hover .pm-exposition-chip,.phone-chip-btn:hover .phone-number-value{filter:brightness(1.2)} +.phone-flash-anchor{display:none} +.clearnet-choice{display:block;margin:0 0 10px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.clearnet-choice-options{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin:6px 0 4px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none !important} +.clearnet-choice-option{display:inline-flex;align-items:center;gap:8px;cursor:pointer;white-space:nowrap;width:auto;margin:0} +.clearnet-choice-option input{width:auto;min-width:0;margin:0;flex:0 0 auto} +.clearnet-choice-option .pm-exposition-chip{margin:0} + +.clearnet-switch{display:inline-flex;margin:0;padding:0;border:1px solid #555;border-radius:6px;overflow:hidden;background:transparent !important;box-shadow:none} +.clearnet-switch-btn{margin:0;border:0;border-radius:0;padding:5px 14px;font-size:12px;font-weight:700;letter-spacing:.5px;background:transparent;color:#bbb;cursor:pointer;min-height:0;width:auto} +.clearnet-switch-btn+.clearnet-switch-btn{border-left:1px solid #555} +.clearnet-switch-btn.active{background:#ffa500;color:#000;cursor:default} +.clearnet-switch-btn:disabled{opacity:1} +.votations-comment-card,.larp-post{position:relative} +.larp-post.own-comment > .larp-post-head{padding-right:48px;box-sizing:border-box} +.comment-delete-form{position:absolute;top:12px;right:12px;margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} +.votations-comment-card.own-comment > .created-at{display:block;padding-right:48px;box-sizing:border-box} +.comment-form .comment-textarea{display:block;max-width:100%;margin:0} +.comments-latest-anchor{background:transparent !important;border:0 !important;padding:0 !important;margin:0;box-shadow:none} +.phone-dial-call .pm-btn{font-size:1.25em;padding:14px 48px;min-width:220px} +.card-section .card-field.activity-room-title{display:flex;align-items:center;gap:10px} +.card-section .card-field.activity-room-title>.card-value{flex:0 1 auto} +.room-live-head,.room-live-head-main{padding:0;margin:0;background:transparent;border:none !important;box-shadow:none;border-radius:0} +.room-live-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px} +.room-live-head-main{flex:1 1 auto;min-width:0} +.room-live-head-main>.card-chips-row{margin:0} +.room-live-head-main>.phone-empty{margin:4px 0 0 0} +.room-refresh-btn{flex:0 0 auto} +.phone-dialer{display:flex;flex-direction:column;align-items:center;gap:12px;margin:10px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.phone-screen{order:1;width:100%;max-width:320px;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.phone-screen input{width:100%;text-align:center;font-size:22px;letter-spacing:2px;margin:0} +.phone-dialpad{order:2;display:grid;grid-template-columns:repeat(3,64px);gap:12px;justify-content:center;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.phone-dialpad .phone-key{width:64px;height:64px;min-height:0;border-radius:50%;border:1px solid #ffa500;background:transparent;color:#ffa500;font-size:22px;font-weight:700;padding:0;margin:0;display:flex;align-items:center;justify-content:center;cursor:pointer} +.phone-dialpad .phone-key:hover{background:#ffa500;color:#1a1a1a} +.phone-dialpad .phone-key-fn{font-size:18px;color:#ccc;border-color:#555} +.phone-dialer .phone-dial-add{order:3} +.phone-recipients{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:center;margin:8px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.phone-recipients>.card-label{flex-basis:100%;text-align:center} +.phone-row-delete{display:inline-flex;margin:0 0 0 auto;padding:0 !important;background:transparent !important;border:0 !important;box-shadow:none} +.phone-contact .phone-action-form+.phone-row-delete{margin-left:6px} +.phone-latest-row{flex-wrap:nowrap;min-width:0} +.phone-latest-row .user-link{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.phone-latest-row .date-link,.phone-latest-row .phone-action-form,.phone-latest-row .phone-row-delete{flex:0 0 auto} +.phone-history td.phone-row-actions{text-align:right;width:1%;white-space:nowrap} +.phone-pam-hint{color:#ffa500;font-weight:600;margin:6px 0} +.phone-refused{color:#ffa500;margin:8px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none;display:flex;flex-wrap:wrap;align-items:center;gap:6px} +.phone-dnd-form{display:inline-flex;margin:0;padding:0;background:transparent !important;border:0 !important;box-shadow:none} +.phone-recipient-list{display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin:0;padding:10px 14px;border:1px solid #ffa500 !important;border-radius:10px;background:transparent !important;box-shadow:none} +.phone-recipient{display:inline-flex;align-items:center;gap:6px;border:none;padding:0;font-size:13px;background:transparent !important} +.phone-recipient form{display:inline-flex;margin:0;padding:0;background:transparent !important;border:0 !important} +.phone-recipient form .phone-recipient-remove{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;min-height:0;padding:0;margin:0;border:1px solid #ff8a8a;border-radius:50%;background:transparent;color:#ff8a8a;font-size:11px;line-height:1;cursor:pointer;box-shadow:none} +.phone-recipient form .phone-recipient-remove:hover{background:#ff8a8a;color:#1a1a1a} +.phone-dial-hint{text-align:center;margin:0 0 8px 0} +.phone-dial-call{display:flex;justify-content:center;margin:0;background:transparent !important;border:0 !important;padding:0 !important} +.peers-pause-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 12px 0;background:transparent !important;border:0 !important;padding:0 !important;box-shadow:none} +.peers-pause-row form{margin:0;padding:0;border:0;background:transparent;box-shadow:none} +.peers-pause-hint{margin:0;color:#ccc;font-size:.9em} +.tribe-main .tribe-content-list{padding:0;margin:0;background:transparent;border:0;box-shadow:none;gap:0;overflow:visible} +.tribe-main .jobs-grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr))} +.tribe-main .feed-row{flex-wrap:wrap} +.tribe-main .feed-main{flex:1 1 180px;min-width:0} +.tribe-main .clearnet-switch{flex-wrap:wrap} +.tribe-main .forum-back-form{margin:0 0 10px 0} +.tribe-main .feed-container,.tribe-main .jobs-grid,.tribe-main .images-list,.tribe-main .audios-list,.tribe-main .videos-list,.tribe-main .documents-list,.tribe-main .bookmark-list,.tribe-main .inhabitants-list,.tribe-main .forum-thread-container{padding:0;margin:0;background:transparent;border:0;box-shadow:none;overflow:visible} +.tribe-main .feed-text{margin-right:0} +.tribe-main .forum-card{flex-wrap:wrap} +.tribe-main .forum-main-col{flex:1 1 220px;min-width:0} +.tribe-main .comment-body-row{flex-wrap:wrap} +.tribe-main .comment-text-col{flex:1 1 200px;min-width:0} +.tribe-main .tribe-grid{padding:0;margin:0;background:transparent;border:0;box-shadow:none;grid-template-columns:repeat(auto-fill,minmax(min(100%,260px),1fr))} +.tribe-main .audios-list{overflow-x:auto} +.tribe-main .torrent-table .user-link,.tribe-main .file-table .user-link{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom} +.tribe-main .forum-footer,.tribe-main .forum-meta,.tribe-main .comment-header{flex-wrap:wrap} +.tribe-main .forum-footer .date-link,.tribe-main .comment-header .date-link{flex:0 0 auto;white-space:nowrap} +.tribe-main .forum-footer .user-link,.tribe-main .comment-header .user-link{flex:1 1 120px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.tribe-main .room-item .mailing-archive-head{flex-wrap:wrap} +.tribe-main .room-item .mailing-archive-meta{flex:1 1 200px} + +.agenda-card-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;padding:0;border:0;background:transparent;box-shadow:none} +.card-section>.card-footer.favorites-meta{border-top:0;padding-top:0;margin-top:6px} +.agenda-list.jobs-grid{margin-top:12px} +.oasis-pager{display:flex;justify-content:flex-end;align-items:center;flex-wrap:wrap;gap:8px;margin:24px 0 8px 0} +.oasis-pager-info{color:#9a8a2e;font-size:12px;margin-right:4px} +.oasis-pager-sizes{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-right:auto;padding:0} +.oasis-pager-btn{display:inline-block;padding:6px 14px;background:#1C1C1C;color:#E6C200;border:1px solid #333;border-radius:14px;font-size:13px;text-decoration:none;transition:border-color .15s ease,color .15s ease,background .15s ease} +.oasis-pager-btn:hover,.oasis-pager-btn.active{border-color:#FFD700;color:#FFD700;text-decoration:none} +.card-title-link{color:inherit;text-decoration:none} +.card-title-link:hover{text-decoration:underline} +.graphos-zoom-row{display:flex;justify-content:flex-end;margin:0 0 8px;padding:0;border:0;background:none;box-shadow:none} +.graphos-lightbox .graphos-canvas-zoom{width:96vw;height:92vh;overflow:auto;background:#111;border:1px solid #2a2a2a;border-radius:8px;box-sizing:border-box;padding:0;margin:0} +.graphos-lightbox .graphos-zoom-stage,.graphos-lightbox .graphos-zoom-svg{padding:0;margin:0;border:0;border-radius:0;background:none;box-shadow:none} +.graphos-lightbox .graphos-svg{width:100%;height:auto;max-height:calc(92vh - 4px);min-height:0;display:block} +.graphos-lightbox .lightbox-close{position:fixed;top:14px;right:22px;z-index:10002;font-size:28px;text-decoration:none} +.live-lightbox .lightbox-image-link{display:flex;align-items:center;justify-content:center;max-width:100%;max-height:100%;padding:0;margin:0;border:0;background:none} +.shop-buy-form,.market-item.actions,.market-bid-form,.market-buy-form{border:0;background:transparent;box-shadow:none;padding:0} +.project-progress-block .confirmations-progress{width:100%} +.tags-list .tag-table th:last-child,.tags-list .tag-table td:last-child{width:1%;white-space:nowrap;text-align:right} +.stats-block .tag-table,.stats-card .tag-table{width:100%;border-collapse:collapse;table-layout:fixed} +.stats-block .tag-table th:first-child,.stats-block .tag-table td:first-child,.stats-card .tag-table th:first-child,.stats-card .tag-table td:first-child{width:70%} +.stats-block .tag-table th:last-child,.stats-block .tag-table td:last-child,.stats-card .tag-table th:last-child,.stats-card .tag-table td:last-child{width:30%;text-align:right} +.video-grid{display:flex;flex-wrap:wrap;gap:8px;margin:18px 0 0;padding:0;border:0;background:transparent;box-shadow:none} +.video-tile{display:flex;flex-direction:column;gap:0;padding:0;margin:0;width:320px;max-width:100%;border:1px solid #333;border-radius:6px;overflow:hidden;background:#000} +.video-feed{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;background:#000} +.video-tile .user-link,.video-tile-name{display:block;margin:0;padding:4px 8px;font-size:12px;text-align:left;background:#1a1a1a} +.video-tile-me .video-feed{transform:scaleX(-1)} +.room-peer-cam{flex:0 0 auto;color:#ffa300;font-size:11px;font-weight:700;border:1px solid #ffa300;border-radius:4px;padding:1px 6px} +.room-cam-btn-on{background:#ffa500;color:#000} +.video-grid-one{flex-direction:column;align-items:flex-start} +.video-feed-grid{width:auto;max-width:100%;aspect-ratio:auto;border:1px solid #333;border-radius:6px} +.video-grid-names{display:flex;flex-wrap:wrap;gap:6px;padding:0;margin:0;border:0;background:transparent;box-shadow:none} +.video-grid-names .user-link,.video-grid-names .video-tile-name{padding:4px 8px;font-size:12px;background:#1a1a1a;border-radius:4px} +.file-table{width:100%;border-collapse:collapse;margin-top:12px} +.file-table th,.file-table td{padding:8px 12px;text-align:left;vertical-align:middle} +.file-table th{background:#1a1a1a;color:#FFA500;font-weight:bold} +.file-table td:nth-child(1),.file-table td:nth-child(4),.file-table td:nth-child(5),.file-table td:nth-child(6),.file-table td:nth-child(7){white-space:nowrap;width:1%} +.file-table td:nth-child(3){overflow-wrap:anywhere} +.file-table td .filter-btn{margin:0} +.file-table td.torrent-spread-cell{text-align:center} +.forum-inline-reply{padding:0;margin:6px 0 0;border:0;background:transparent;box-shadow:none} +.forum-inline-reply-toggle{cursor:pointer;display:inline-block;padding:3px 10px;border:1px solid #ffa500;border-radius:10px;background:#1a1a1a;color:#ffa500;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;list-style:none;width:max-content} +.forum-inline-reply-toggle::-webkit-details-marker{display:none} +.forum-inline-reply[open] .forum-inline-reply-toggle{margin-bottom:6px} +.video-tile .zoom-link,.video-grid-one .zoom-link{display:block;width:100%;border:0 !important;background:transparent !important;padding:0 !important} +.video-tile-me .lightbox-image{transform:scaleX(-1)} +.phone-call-panel .live-lightbox{background:rgba(0,0,0,.85)} +.live-lightbox .lightbox-image{width:min(80vw,880px);height:auto;max-width:none;max-height:80vh;object-fit:contain;cursor:zoom-out} diff --git a/src/client/assets/themes/Clear-SNH.css b/src/client/assets/themes/Clear-SNH.css index a04c3f3..28d53ba 100644 --- a/src/client/assets/themes/Clear-SNH.css +++ b/src/client/assets/themes/Clear-SNH.css @@ -224,32 +224,23 @@ footer a:hover { background: none; } -.card-tags a.tag-link { - color: #181818; - background: #D94F4F; -} -.card-tags a.tag-link:hover { - background: #D94F4F; - color: #111; - cursor: pointer; -} -a.user-link { +a.user-link, button.user-link { background-color: #FFD600; color: #FFFFFF; border-color: #FFD600; transition: background-color 0.3s, color 0.3s, border-color 0.3s; } -a.user-link:hover { +a.user-link:hover, button.user-link:hover { background-color: #FFD600; border-color: #FFD600; color: #FFFFFF; cursor: pointer; } -a.user-link:focus { +a.user-link:focus, button.user-link:focus { background-color: #9A2F2F; border-color: #9A2F2F; color: #FFFFFF; @@ -302,7 +293,7 @@ a.user-link:focus { .stats-kpi-value { color: #007BFF !important; } .carbon-bar-note, .carbon-bar-formula { color: #007BFF !important; } .graphos-node-label { fill: #2C2C2C !important; } -.graphos-node-label-me { fill: #cc7700 !important; } +.graphos-node-label-me { fill: #1f6391 !important; } .data-best-label { color: #cc7700 !important; } .data-best-card { background: #FFF3E0 !important; border: 1px solid rgba(204, 119, 0, .4) !important; } .graphos-legend { color: #2C2C2C !important; } @@ -327,6 +318,8 @@ a.user-link:focus { .btn-singleview:hover { background: #E0E0E0 !important; color: #FF8F00 !important; } .btn-spread-on, .content-actions .btn-pin-on { background: #FF6F00 !important; color: #FFFFFF !important; } .btn-spread-on:hover, .content-actions .btn-pin-on:hover { background: #FF8F00 !important; color: #FFFFFF !important; } +.content-actions .btn-singleview.btn-content { border: 1px solid #FF6F00 !important; } +.content-actions .btn-singleview.btn-content:hover { background: rgba(255, 111, 0, .18) !important; border-color: #FF6F00 !important; } .btn-back { background: #FF6F00 !important; color: #FFFFFF !important; } .btn-back:hover { background: #FF8F00 !important; color: #FFFFFF !important; } .block-info-table td, .pm-info-table td { border-color: #E0E0E0 !important; } @@ -383,3 +376,48 @@ a.user-link:focus { .karvan-msg-self { background-color: #FFEFC2 !important; border-color: #D9A400 !important; } .karvan-msg .km-from { color: #8A6D00 !important; } .karvan-msg .km-text { color: #2C2C2C !important; } +.map-viewer, .map-stage div, .map-hero, .map-toolbar, .map-toolbar div, .map-place-results, .map-markers-list, .map-markers-list div, .map-detail-grid, .map-card-thumb .map-viewer { background: transparent !important; border: 0 !important; } +.map-pick-cell, .map-pick-cell:hover { border: 0 !important; padding: 0 !important; border-radius: 0 !important; box-shadow: none !important; } +.map-pick-cell { background: transparent !important; } +.map-pick-cell:hover { background: rgba(255, 111, 0, 0.18) !important; } +.map-ctrl, .map-place-result { padding: 0 !important; border-radius: 6px !important; box-shadow: none !important; } +.map-place-result { padding: 4px 10px !important; border-radius: 14px !important; } +.map-stage { background: #cfe0ec !important; border: 1px solid #E0E0E0 !important; } +.map-marker-row { border-bottom: 1px solid #E0E0E0 !important; } +.map-label-country { fill: #4a4a4a; stroke: #ffffff; } +.map-place-dot { fill: #333333; stroke: #ffffff; } +.map-place-capital { fill: #E65100; } +.map-place-label { fill: #222222; stroke: #ffffff; } +.map-pin-label-bg { fill: rgba(255, 255, 255, 0.92); stroke: rgba(0, 0, 0, 0.3); } +.map-pin-label { fill: #222222; } +.map-pin-focus .map-pin-body, .map-cluster-dot { fill: #FF6F00; } +.map-pin-focus .map-pin-label-bg { stroke: #FF6F00; } +.map-cluster-ring { fill: rgba(255, 111, 0, 0.25); } +.map-pick-ring, .map-pick-cross { stroke: #FF6F00; } +.map-pins-layer a:hover .map-pin-body, .map-pins a:hover .map-pin-body { fill: #FF6F00; } +.map-scale-line { stroke: #333333; } +.map-scale-text { fill: #333333; stroke: #ffffff; } +.map-attrib { fill: #777777; } +.map-ctrl { background: rgba(255, 255, 255, 0.92) !important; color: #FF6F00 !important; border-color: #cccccc !important; } +.map-ctrl:hover { background: #FF6F00 !important; color: #ffffff !important; border-color: #FF6F00 !important; } +.map-ctrl-off:hover { background: rgba(255, 255, 255, 0.92) !important; color: #FF6F00 !important; border-color: #cccccc !important; } +.map-pick-cell:hover { background: rgba(255, 111, 0, 0.18); outline-color: rgba(255, 111, 0, 0.7); } +.map-place-result, .map-center-link { color: #FF6F00 !important; border-color: #cccccc !important; } +.map-place-result:hover, .map-center-link:hover { background: #FF6F00 !important; color: #ffffff !important; border-color: #FF6F00 !important; } +.map-picked { background: #F0F0F0 !important; color: #FF6F00 !important; } +.map-picked-near { color: #555555 !important; } +.map-markers-list .map-marker-row--focus { background: rgba(255, 111, 0, 0.08) !important; box-shadow: inset 3px 0 0 #FF6F00 !important; } +.map-marker-row--focus .map-marker-idx { background: #FF6F00 !important; color: #ffffff !important; } +.map-marker-label { color: #E65100 !important; } +.map-marker-date { color: #777777 !important; } +.map-marker-coords { color: #222222 !important; } +.map-popup-pointer { fill: #ffffff; stroke: #cccccc; } +.map-stage .map-popup-card { background: #ffffff !important; border: 1px solid #cccccc !important; color: #222222; } +.map-stage .map-popup-text { color: #333333; } +.map-popup-coords { color: #222222; } +.map-stage .map-popup-meta { color: #777777; } +.map-popup-close { color: #777777; } +.map-popup-close:hover { color: #FF6F00; } +.oasis-pager-info { color: #888888; } +.oasis-pager-btn { background: #FFFFFF; color: #555555 !important; border: 1px solid #E0E0E0; text-decoration: none !important; } +.oasis-pager-btn:hover, .oasis-pager-btn.active { border-color: #2C2C2C; color: #2C2C2C !important; text-decoration: none !important; } diff --git a/src/client/assets/themes/Dark-SNH.css b/src/client/assets/themes/Dark-SNH.css index 7167ffe..335c1d4 100644 --- a/src/client/assets/themes/Dark-SNH.css +++ b/src/client/assets/themes/Dark-SNH.css @@ -210,24 +210,9 @@ footer a:hover { gap: 9px; } -.card-tags a.tag-link { - text-decoration: none; - color: #181818; - background: #FFD600; - padding: 5px 13px 4px 13px; - border-radius: 7px; - font-size: .98em; - border: none; - font-weight: bold; -} -.card-tags a.tag-link:hover { - background: #ffe86a; - color: #111; - cursor: pointer; -} -a.user-link { +a.user-link, button.user-link { background-color: #FFA500; color: #000; padding: 8px 16px; @@ -241,14 +226,14 @@ a.user-link { font-size: 0.8em; } -a.user-link:hover { +a.user-link:hover, button.user-link:hover { background-color: #FFD700; border-color: #FFD700; color: #000; cursor: pointer; } -a.user-link:focus { +a.user-link:focus, button.user-link:focus { background-color: #007B9F; border-color: #007B9F; color: #fff; @@ -316,7 +301,7 @@ a.user-link:focus { .stats-kpi-value { color: #FFD700 !important; } .carbon-bar-note, .carbon-bar-formula { color: #FFD700 !important; } .graphos-node-label { fill: #ddd !important; } -.graphos-node-label-me { fill: #ffa500 !important; } +.graphos-node-label-me { fill: #5dade2 !important; } .graphos-legend { color: #ddd !important; } .blockchain-view { background-color: #191b20 !important; color: #FFD700 !important; } .block { background: #23242a !important; } @@ -333,6 +318,8 @@ a.user-link:focus { .btn-singleview:hover { background: #2d2e34 !important; color: #FFD700 !important; } .btn-spread-on, .content-actions .btn-pin-on { background: #ffa300 !important; color: #000000 !important; } .btn-spread-on:hover, .content-actions .btn-pin-on:hover { background: #ffb733 !important; color: #000000 !important; } +.content-actions .btn-singleview.btn-content { border: 1px solid #ffa300 !important; } +.content-actions .btn-singleview.btn-content:hover { background: rgba(255, 163, 0, .18) !important; border-color: #ffa300 !important; } .btn-back { background: #21232b !important; color: #ffa300 !important; } .btn-back:hover { background: #292b36 !important; color: #FFD700 !important; } .block-info-table td, .pm-info-table td { border-color: #444 !important; } diff --git a/src/client/assets/themes/Matrix-SNH.css b/src/client/assets/themes/Matrix-SNH.css index a0cb38d..ebf4f2d 100644 --- a/src/client/assets/themes/Matrix-SNH.css +++ b/src/client/assets/themes/Matrix-SNH.css @@ -241,32 +241,23 @@ footer a { color: #00FF00; } -.card-tags a.tag-link { - color: #00FF00; - background: #000000; -} -.card-tags a.tag-link:hover { - background: #00FF00; - color: #000000; - cursor: pointer; -} -a.user-link { +a.user-link, button.user-link { background-color: #00FF00; color: #000000; border-color: #00FF00; transition: background-color 0.3s, color 0.3s, border-color 0.3s; } -a.user-link:hover { +a.user-link:hover, button.user-link:hover { background-color: #000000; border-color: #00FF00; color: #00FF00; cursor: pointer; } -a.user-link:focus { +a.user-link:focus, button.user-link:focus { background-color: #00FF00; border-color: #00FF00; color: #000000; @@ -310,7 +301,7 @@ a.user-link:focus { .stats-kpi-value { color: #00FF00 !important; } .carbon-bar-note, .carbon-bar-formula { color: #00FF00 !important; } .graphos-node-label { fill: #00FF00 !important; } -.graphos-node-label-me { fill: #ffa500 !important; font-weight: bold !important; } +.graphos-node-label-me { fill: #5dade2 !important; font-weight: bold !important; } .graphos-legend { color: #00FF00 !important; } .graphos-edge { stroke: #003300 !important; } /* Blockexplorer */ @@ -329,6 +320,8 @@ a.user-link:focus { .btn-singleview:hover { background: #00FF00 !important; color: #000000 !important; } .btn-spread-on, .content-actions .btn-pin-on { background: #00FF00 !important; color: #000000 !important; } .btn-spread-on:hover, .content-actions .btn-pin-on:hover { background: #66FF66 !important; color: #000000 !important; } +.content-actions .btn-singleview.btn-content { border: 1px solid #00FF00 !important; } +.content-actions .btn-singleview.btn-content:hover { background: rgba(0, 255, 0, .18) !important; border-color: #00FF00 !important; } .btn-back { background: #000000 !important; color: #00FF00 !important; border: 1px solid #00FF00 !important; } .btn-back:hover { background: #00FF00 !important; color: #000000 !important; } .block-info-table td, .pm-info-table td { border-color: #00FF00 !important; } @@ -379,4 +372,47 @@ nav, .sidebar-left nav, .sidebar-right nav, .sidebar-left ul, .sidebar-right ul .ai-nav-result-card a { color: #00FF00 !important; } .ai-nav-result-card a:hover { background: #00FF00 !important; color: #000000 !important; } .ai-nav-query { color: #00FF00 !important; } -.trending-card.own-content { box-shadow: inset 3px 0 0 #00FF00; } +.trending-card.own-content, .addr-card.ubi-pub-card.own-content { box-shadow: inset 3px 0 0 #00FF00; } +.map-stage { background: #000000 !important; border-color: #00FF00 !important; } +.map-label-country { fill: #00FF00; stroke: #000000; } +.map-place-dot { fill: #00FF00; stroke: #000000; } +.map-place-capital { fill: #66FF66; } +.map-place-label { fill: #00FF00; stroke: #000000; } +.map-pin-body { fill: #00aa00; stroke: #00FF00; } +.map-pin-main .map-pin-body { fill: #00FF00; stroke: #000000; } +.map-pin-focus .map-pin-body { fill: #ffffff; stroke: #00FF00; } +.map-pin-eye { fill: #000000; } +.map-pin-label-bg { fill: rgba(0, 0, 0, 0.9); stroke: #00FF00; } +.map-pin-focus .map-pin-label-bg { stroke: #ffffff; } +.map-pin-label { fill: #00FF00; } +.map-pins-layer a:hover .map-pin-body, .map-pins a:hover .map-pin-body { fill: #ffffff; } +.map-cluster-ring { fill: rgba(0, 255, 0, 0.25); } +.map-cluster-dot { fill: #00FF00; stroke: #000000; } +.map-pick-ring, .map-pick-cross { stroke: #00FF00; } +.map-scale-line { stroke: #00FF00; } +.map-scale-text { fill: #00FF00; stroke: #000000; } +.map-attrib { fill: #008800; } +.map-ctrl { background: rgba(0, 0, 0, 0.9) !important; color: #00FF00 !important; border-color: #00FF00 !important; } +.map-ctrl:hover { background: #00FF00 !important; color: #000000 !important; } +.map-ctrl-off:hover { background: rgba(0, 0, 0, 0.9) !important; color: #00FF00 !important; } +.map-pick-cell:hover { background: rgba(0, 255, 0, 0.18); outline-color: rgba(0, 255, 0, 0.7); } +.map-place-result, .map-center-link { color: #00FF00 !important; border-color: #00FF00 !important; } +.map-place-result:hover, .map-center-link:hover { background: #00FF00 !important; color: #000000 !important; } +.map-picked { background: #050505 !important; color: #00FF00 !important; } +.map-picked-near { color: #00CC00 !important; } +.map-markers-list .map-marker-row { border-color: #003300 !important; } +.map-markers-list .map-marker-row--focus { background: rgba(0, 255, 0, 0.08) !important; box-shadow: inset 3px 0 0 #00FF00 !important; } +.map-marker-idx { background: #004400 !important; color: #00FF00 !important; } +.map-marker-idx--main, .map-marker-row--focus .map-marker-idx { background: #00FF00 !important; color: #000000 !important; } +.map-marker-label { color: #00FF00 !important; } +.map-marker-date { color: #00AA00 !important; } +.map-popup-pointer { fill: #000000; stroke: #00FF00; } +.map-stage .map-popup-card { background: #000000; border-color: #00FF00; color: #00FF00; } +.map-stage .map-popup-text { color: #00FF00; } +.map-popup-coords { color: #66FF66; } +.map-stage .map-popup-meta { color: #00AA00; } +.map-popup-close { color: #00AA00; } +.map-popup-close:hover { color: #ffffff; } +.oasis-pager-info { color: #008800; } +.oasis-pager-btn { background: #0a0a0a; color: #00CC00; border: 1px solid #00FF00; } +.oasis-pager-btn:hover, .oasis-pager-btn.active { border-color: #00FF00; color: #00FF00; } diff --git a/src/client/assets/themes/Purple-SNH.css b/src/client/assets/themes/Purple-SNH.css index 67cd225..8ebb4a1 100644 --- a/src/client/assets/themes/Purple-SNH.css +++ b/src/client/assets/themes/Purple-SNH.css @@ -277,32 +277,23 @@ header { color: #B86ADE; } -.card-tags a.tag-link { - color: #FFFFFF; - background: #D94F4F; -} -.card-tags a.tag-link:hover { - background: #B86ADE; - color: #E5E5E5; - cursor: pointer; -} -a.user-link { +a.user-link, button.user-link { background-color: #FFD600; color: #3C1360; border-color: #FFD600; transition: background-color 0.3s, color 0.3s, border-color 0.3s; } -a.user-link:hover { +a.user-link:hover, button.user-link:hover { background-color: #FFB600; border-color: #FFB600; color: #3C1360; cursor: pointer; } -a.user-link:focus { +a.user-link:focus, button.user-link:focus { background-color: #9A2F2F; border-color: #9A2F2F; color: #E5E5E5; @@ -346,7 +337,7 @@ a.user-link:focus { .stats-kpi-value { color: #FFEEDB !important; } .carbon-bar-note, .carbon-bar-formula { color: #FFEEDB !important; } .graphos-node-label { fill: #FFEEDB !important; } -.graphos-node-label-me { fill: #ffa500 !important; } +.graphos-node-label-me { fill: #5dade2 !important; } .graphos-legend { color: #FFEEDB !important; } /* Blockexplorer */ .blockchain-view { background-color: #2D0B47 !important; color: #FFEEDB !important; } @@ -364,6 +355,8 @@ a.user-link:focus { .btn-singleview:hover { background: #5A1A85 !important; color: #FFD600 !important; } .btn-spread-on, .content-actions .btn-pin-on { background: #B86ADE !important; color: #2A0E42 !important; } .btn-spread-on:hover, .content-actions .btn-pin-on:hover { background: #CE8CE9 !important; color: #2A0E42 !important; } +.content-actions .btn-singleview.btn-content { border: 1px solid #B86ADE !important; } +.content-actions .btn-singleview.btn-content:hover { background: rgba(184, 106, 222, .18) !important; border-color: #B86ADE !important; } .btn-back { background: #A34AD8 !important; color: #FFFFFF !important; } .btn-back:hover { background: #751E9F !important; color: #FFFFFF !important; } .block-info-table td, .pm-info-table td { border-color: #B86ADE !important; } @@ -395,4 +388,41 @@ a.user-link:focus { .tribe-subtribe-link:hover { background: #5A1A85 !important; } .tribe-parent-image { border-color: #B86ADE !important; } .tribe-parent-box { background: #3C1360 !important; } -.trending-card.own-content { box-shadow: inset 3px 0 0 #B86ADE; } +.trending-card.own-content, .addr-card.ubi-pub-card.own-content { box-shadow: inset 3px 0 0 #B86ADE; } +.map-stage { background: #2a0640 !important; border-color: #9B1C96 !important; } +.map-label-country { fill: #E5E5E5; stroke: #2a0640; } +.map-place-dot { fill: #ffffff; stroke: #2a0640; } +.map-place-capital { fill: #B86ADE; } +.map-place-label { fill: #E5E5E5; stroke: #2a0640; } +.map-pin-focus .map-pin-body, .map-cluster-dot { fill: #B86ADE; } +.map-pin-focus .map-pin-label-bg { stroke: #B86ADE; } +.map-pins-layer a:hover .map-pin-body, .map-pins a:hover .map-pin-body { fill: #B86ADE; } +.map-cluster-ring { fill: rgba(184, 106, 222, 0.28); } +.map-pick-ring, .map-pick-cross { stroke: #B86ADE; } +.map-scale-text { stroke: #2a0640; } +.map-attrib { fill: #9B7CAA; } +.map-ctrl { background: rgba(43, 10, 70, 0.9) !important; color: #E5E5E5 !important; border-color: #9B1C96 !important; } +.map-ctrl:hover { background: #B86ADE !important; color: #000000 !important; border-color: #B86ADE !important; } +.map-ctrl-off:hover { background: rgba(43, 10, 70, 0.9) !important; color: #E5E5E5 !important; border-color: #9B1C96 !important; } +.map-pick-cell, .map-pick-cell:hover { border: 0 !important; padding: 0 !important; border-radius: 0 !important; box-shadow: none !important; } +.map-pick-cell { background: transparent !important; } +.map-pick-cell:hover { background: rgba(184, 106, 222, 0.22) !important; outline-color: rgba(184, 106, 222, 0.7); } +.map-place-result, .map-center-link { color: #E5E5E5 !important; border-color: #9B1C96 !important; } +.map-place-result:hover, .map-center-link:hover { background: #B86ADE !important; color: #000000 !important; border-color: #B86ADE !important; } +.map-picked { background: #3C1360 !important; color: #E5E5E5 !important; } +.map-picked-near { color: #C8C8C8 !important; } +.map-markers-list .map-marker-row { border-color: #5A1A85 !important; } +.map-markers-list .map-marker-row--focus { background: rgba(184, 106, 222, 0.1) !important; box-shadow: inset 3px 0 0 #B86ADE !important; } +.map-marker-row--focus .map-marker-idx { background: #B86ADE !important; color: #000000 !important; } +.map-marker-label { color: #B86ADE !important; } +.map-marker-date { color: #C8C8C8 !important; } +.map-popup-pointer { fill: #3C1360; stroke: #9B1C96; } +.map-stage .map-popup-card { background: #3C1360; border-color: #9B1C96; color: #E5E5E5; } +.map-stage .map-popup-text { color: #E5E5E5; } +.map-popup-coords { color: #ffffff; } +.map-stage .map-popup-meta { color: #C8C8C8; } +.map-popup-close { color: #C8C8C8; } +.map-popup-close:hover { color: #B86ADE; } +.oasis-pager-info { color: #9B7CAA; } +.oasis-pager-btn { background: #39006D; color: #C8C8C8; border: 1px solid #9B1C96; } +.oasis-pager-btn:hover, .oasis-pager-btn.active { border-color: #E5E5E5; color: #E5E5E5; } diff --git a/src/client/assets/translations/i18n.js b/src/client/assets/translations/i18n.js index 2ed00fb..34ad3a0 100644 --- a/src/client/assets/translations/i18n.js +++ b/src/client/assets/translations/i18n.js @@ -1,15 +1,22 @@ const path = require('path'); -let i18n = {}; -const languages = ['en', 'es', 'fr', 'eu', 'de', 'it', 'pt', 'zh', 'ar', 'hi', 'ru']; +const i18n = {}; +const languages = ['en', 'es', 'ca', 'gl', 'fr', 'eu', 'de', 'it', 'pt', 'zh', 'ar', 'hi', 'ru']; languages.forEach(language => { - try { - const languagePath = path.join(__dirname, `oasis_${language}.js`); - const languageData = require(languagePath); - i18n[language] = languageData[language]; - } catch (error) { - console.error(`Failed to load language file for ${language}:`, error); - } + Object.defineProperty(i18n, language, { + enumerable: true, + configurable: true, + get() { + let languageData; + try { + languageData = require(path.join(__dirname, `oasis_${language}.js`))[language]; + } catch (error) { + console.error(`Failed to load language file for ${language}:`, error); + } + Object.defineProperty(i18n, language, { value: languageData, enumerable: true, configurable: true, writable: true }); + return languageData; + } + }); }); try { diff --git a/src/client/assets/translations/oasis_ar.js b/src/client/assets/translations/oasis_ar.js index 6cb95c1..029a94a 100644 --- a/src/client/assets/translations/oasis_ar.js +++ b/src/client/assets/translations/oasis_ar.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "المشتريات", shopMyOrdersDescription: "طلبات الشراء الخاصة بك.", shopMyOrdersEmpty: "ليس لديك مشتريات بعد.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM البائع", shopOrderSeller: "البائع", shopOrderInvoice: "فاتورة", shopOrderInvoiceConcept: "فاتورة", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "قبول", shopOrderReject: "رفض", shopOrderMarkShipped: "وضع علامة شُحن", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM المشتري", shopOrderContractConcept: "طلب متجر", shopOrdersPending: "طلبات معلّقة", all: "الكل", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "تحذير المحتوى", invalidPost: "محتوى غير صالح", invalidPostHint: "هذه الرسالة تحتوي على نص فارغ/غير صالح.", - spreadBy: "بواسطة", spreadChron: "نشر", spreaded: "تم النشر", spreadContentUnavailable: "المحتوى غير متاح بعد (في انتظار النسخ)", filteredByTag: "مُصفّى حسب الوسم", filteredByTagTitle: "مُصفّى حسب الوسم", feedPublishedSuccess: "تم نشر التغذية بنجاح!", + feedMastodonFailed: "نُشر في Oasis، لكن تعذّر إرساله إلى Mastodon.", tribeFeedSent: "تم إرسال الرسالة بنجاح!", tribeContentEncrypted: "محتوى مشفّر", tribeTorrentsEmpty: "لا توجد تورنتات بعد.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "آخر زيارة", profileClearnetBookmarksLabel: "الإشارات المرجعية", invitesPort: "المنفذ", - currentlyUnrecheable: "خطأ!", - peerHostValidation: "عنوان IPv4 صالح (مثل 192.168.1.100) أو اسم مضيف (مثل pub.example.com)", + peerHostValidation: "عنوان IPv4 صالح (مثل 192.168.1.100) أو اسم مضيف (مثل pub.solarnethub.com)", peerPortValidation: "المنفذ 1-65535", peerKeyValidation: "مفتاح SSB ed25519 العام (@<44 حرف base64>=.ed25519)", inboxJobUnsubscribedTitle: "إلغاء الاشتراk في عرض عملك", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "وصف المهمة", exportPasswordLabel: "كلمة مرور التصدير", importPasswordPlaceholder: "أدخل كلمة المرور", - searchCasesPlaceholder: "البحث عن القضايا…", + searchCasesPlaceholder: "ابحث في المحاكم...", shopBuyDeliveryAddressPlaceholder: "عنوان التسليم", shopBuyNotesPlaceholder: "ملاحظات", bankTaxesUserNoteBold: "تخفيض ضريبة ECO مباشرة", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "دعم القضية", courtsSettlementAcceptBtn: "قبول التسوية", courtsSupportCount: "المؤيدون", - courtsVerdictVoteTitle: "التصويت على الحكم", - courtsVerdictVoteLabel: "تصويت الحكم", - courtsVerdictVoteAccept: "قبول", - courtsVerdictVoteReject: "رفض", - courtsVerdictVoteSubmit: "إرسال التصويت", reportsSetStatus: "تعيين الحالة", reportsStatusClosed: "مغلق", statsAvgPerInhabitant: "المتوسط لكل ساكن", @@ -189,9 +183,29 @@ module.exports = { ], profile: "الصورة الرمزية", inhabitants: "السكان", - peersReplicatedFeeds: "الموجزات المنسوخة", + peersReplicatedFeeds: "المنسوخة", + peersTorColumn: "TOR", + peersTorYes: "يمكن الوصول إليه عبر Tor", + peersTorNo: "لا يمكن الوصول إليه عبر Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "الإصدار", graphos: "غرافوس", graphosDescription: "خريطة تفاعلية للشبكة من حولك.", + graphosFederation: "الاتحاد", + graphosFedPubs: "خوادم PUB", + graphosFedUnreachable: "غير قابلة للوصول", + graphosFedLinks: "روابط متبادلة", + graphosFedIsolated: "معزولة", + graphosFedNoData: "لا بيانات", + graphosFedMutual: "متبادل", + graphosFedOneWay: "باتجاه واحد", + graphosFedTransport: "الشبكة", + graphosFedWith: "متحد مع", + graphosZoom: "تكبير", + graphosFedInhabitants: "السكان", + graphosFedClearnet: "الإنترنت", + graphosFedBoth: "الإنترنت + TOR", + graphosFedEmpty: "لا تعرف عقدتك أي PUB بعد.", graphosViewingGraphOf: "عرض", graphosBackToMine: "← شبكتي", graphosShowing: "عرض", @@ -205,6 +219,22 @@ module.exports = { noMentions: "لم تتلقَّ إشارات @ بعد.", mentionsSearchPlaceholder: "ابحث في الإشارات...", privateReminders: "تذكيرات", + inboxFilterPms: "رسائل خاصة", + inboxFilterNotifications: "إشعارات", + inboxMarkRead: "وضع علامة كمقروء", + inboxMarkUnread: "وضع علامة كغير مقروء", + inboxUnreadChip: "غير مقروء", + inboxMarkAllRead: "وضع علامة مقروء على الكل", + inboxDeleteShown: "حذف الكل", + inboxArchive: "أرشفة", + inboxUnarchive: "إلغاء الأرشفة", + inboxFilterArchived: "مؤرشف", + inboxEarlierMessages: "رسائل سابقة", + inboxSortThread: "حسب المحادثة", + inboxSortRecent: "الأحدث أولاً", + inboxAllBots: "الروبوتات", + settingsInboxBotsTitle: "إشعارات صندوق الوارد", + settingsInboxBotsDesc: "اختر الروبوتات التي يمكنها إشعارك في صندوق الوارد. الروبوتات غير المحددة تُخفى ولا تُحتسب إشعاراتها.", inboxFiltersLabel: "المرشحات:", inboxToggleToMutuals: "التبديل إلى المتبادلين", inboxToggleToWhole: "التبديل إلى الجميع", @@ -212,6 +242,7 @@ module.exports = { pmReply: "رد", pmReplies: "ردود", inReplyTo: "ردًا على", + forumInThread: "في الموضوع", pmPreview: "معاينة", pmPreviewTitle: "معاينة الرسالة", peers: "الأقران", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "من", searchToLabel: "إلى", searchQueryLabel: "استعلام", - searchPerPageLabel: "نتائج لكل صفحة", + searchPerPageLabel: "النتائج", settings: "الإعدادات", + settingsDescription: "اضبط بيئة شبكتك.", continueReading: "متابعة القراءة", moreComments: "تعليق إضافي", readThread: "اقرأ بقية المحادثة", @@ -241,7 +273,6 @@ module.exports = { profileClearnetHelp: 'الوحدات التي يمكن الوصول إليها من خارج أوازيس.', profileHubAll: 'الكل', footerLicenseLabel: 'الترخيص', - profileSwitchToOasis: 'العودة إلى Oasis', profileVisibilityFediverse: "الكون المتعدد", contributorsTitle: "المساهمون", colorLabel: 'اختر لونًا', @@ -251,6 +282,7 @@ module.exports = { modulesViewTitle: "الوحدات", modulesViewDescription: "اضبط بيئتك بتفعيل أو تعطيل الوحدات.", inbox: "صندوق الوارد", + inboxSearchPlaceholder: "ابحث في صندوق الوارد...", multiverse: "الكون المتعدد", fediverse: "الكون المتعدد", fediverseDescription: "أدر حساباتك الأخرى في الكون المتعدد، بما في ذلك إرسال واستقبال المحتوى.", @@ -259,9 +291,17 @@ module.exports = { fediverseSettingsTitle: "الكون المتعدد", fediverseInstanceLabel: "العنوان", fediverseTokenLabel: "رمز الوصول", - fediverseTokenHelp: "اضبط رمز الوصول الخاص بك. سيُستخدم لإدارة حساب ماستودون من داخل أوازيس.", fediverseConnect: "ربطه", fediverseConnectedAs: "متصل باسم", + nextcloudServerLabel: "الخادم", + nextcloudUserLabel: "المستخدم", + nextcloudFolderLabel: "المجلد", + nextcloudUploadButton: "رفع", + nextcloudFileLabel: "اختر ملفًا", + fileExternalLink: "رابط خارجي", + nextcloudErrConnect: "تعذر الوصول إلى Nextcloud بهذه البيانات.", + nextcloudErrUpload: "فشل الرفع إلى Nextcloud.", + nextcloudErrShare: "لم يعطِ Nextcloud رابطًا عامًا للملف.", fediverseDisconnect: "قطع الاتصال", fediverseEmptyLink: "الإعدادات", fediverseComposePlaceholder: "بماذا تفكر؟", @@ -290,6 +330,76 @@ module.exports = { fediverseErrPost: "تعذّر النشر.", fediverseErrMedia: "تعذّر رفع الملف.", fediverseErrEmpty: "اكتب شيئاً أو أرفق ملفاً.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "رقم الهاتف", + telegramSendCode: "إرسال الرمز", + telegramCodeHelp: "أدخل رمز الدخول الذي أرسله لك تيليغرام للتو (التطبيق أو الرسائل).", + telegramCodeLabel: "الرمز", + telegramPasswordHelp: "هذا الحساب يستخدم التحقق بخطوتين. أدخل كلمة المرور.", + telegramPasswordLabel: "كلمة المرور", + telegramVerify: "تحقق", + telegramCancel: "إلغاء", + telegramChats: "المحادثات", + telegramUnread: "غير مقروءة", + telegramOpen: "فتح", + telegramSend: "إرسال", + telegramYou: "أنت", + telegramNoChats: "لا توجد محادثات بعد.", + telegramNoMessages: "لا توجد رسائل بعد.", + telegramComposePlaceholder: "اكتب رسالة…", + telegramChannel: "قناة", + telegramGroup: "مجموعة", + telegramPrivate: "خاص", + telegramErrConnect: "تعذر الاتصال بتيليغرام.", + telegramErrTimeout: "استغرق تيليغرام وقتاً طويلاً للرد.", + telegramErrAuth: "جلسة تيليغرام غير صالحة أو منتهية. أعد الاتصال.", + telegramErrFetch: "تعذر تحميل المحادثات.", + telegramErrSend: "تعذر إرسال الرسالة.", + telegramErrMissing: "مطلوب API ID و API hash ورقم الهاتف.", + telegramErrCode: "رمز غير صحيح. حاول مرة أخرى.", + telegramErrCodeExpired: "انتهت صلاحية الرمز. ابدأ من جديد.", + telegramErrPassword: "كلمة مرور خاطئة.", + telegramErrPhone: "رقم هاتف غير صالح.", + telegramErrApi: "API ID أو API hash غير صالح.", + telegramErrFlood: "محاولات كثيرة جداً. انتظر قليلاً ثم حاول مجدداً.", + telegramErrNoLogin: "لا يوجد تسجيل دخول جارٍ. ابدأ من جديد.", + peertubeUsernameLabel: "اسم المستخدمة", + peertubePasswordLabel: "كلمة المرور", + peertubeSubscriptions: "الاشتراكات", + peertubeMyVideos: "فيديوهاتي", + peertubeVideos: "فيديوهات", + peertubeFollowers: "المتابِعات", + peertubeViews: "مشاهدة", + peertubeLikes: "إعجاب", + peertubeQuota: "الحصة", + peertubeUpload: "رفع فيديو", + peertubeUploadHelp: "اختاري ملف فيديو. يُرفع إلى قناتك ثم يعالجه PeerTube.", + peertubeTitleLabel: "العنوان", + peertubeDescriptionLabel: "الوصف", + peertubePrivacyLabel: "الخصوصية", + peertubePrivacyPublic: "عام", + peertubePrivacyUnlisted: "غير مدرج", + peertubePrivacyPrivate: "خاص", + peertubeNoVideos: "لا توجد فيديوهات بعد.", + peertubeComments: "التعليقات", + peertubeComment: "تعليق", + peertubeCommentPlaceholder: "اكتبي تعليقًا…", + peertubeNoComments: "لا توجد تعليقات بعد.", + peertubeOpen: "فتح على PeerTube", + peertubeWatch: "مشاهدة", + peertubeChannel: "القناة", + peertubeNoStream: "لا يمكن تشغيل هذا الفيديو هنا بعد. افتحيه على PeerTube.", + peertubeUploaded: "تم رفع الفيديو. PeerTube يعالجه الآن.", + peertubeErrConnect: "تعذر الاتصال بـ PeerTube.", + peertubeErrAuth: "اسم المستخدمة أو كلمة المرور غير صحيحة، أو انتهت الجلسة. أعيدي الاتصال.", + peertubeErrInstance: "عنوان الخادم غير صالح.", + peertubeErrMissing: "يلزم الخادم واسم المستخدمة وكلمة المرور.", + peertubeErrFetch: "تعذر تحميل الفيديوهات.", + peertubeErrUpload: "تعذر رفع الفيديو.", + peertubeErrTitle: "العنوان مطلوب.", + peertubeErrComment: "تعذر نشر التعليق.", + peertubeErrNoFile: "اختاري ملف فيديو.", bookmarksLabel: "العلامات المرجعية", videosLabel: "الفيديوهات", torrentsLabel: "التورنت", @@ -312,7 +422,6 @@ module.exports = { comment: "تعليق", subtopic: "موضوع فرعي", json: "JSON", - createdBy: "بواسطة", unfollow: "إلغاء الدعم", follow: "دعم", block: "حظر", @@ -328,12 +437,15 @@ module.exports = { relationshipNone: "أنت لا تدعم", relationshipBlockingPost: "منشور محظور", viewLikes: "عرض الانتشارات", + viewSupporters: "عرض الداعمين", spreadedDescription: "قائمة المنشورات التي نشرها الساكن.", preview: "معاينة", publish: "كتابة", contentWarningPlaceholder: "أضف موضوعًا للمنشور (اختياري)", - publishWarningPlaceholder: "النص محدود بـ 7000 حرفًا.", + publishWarningPlaceholder: "اكتب نص مقالتك.", publishTooLong: "منشورك طويل جدًا. الرجاء تقصيره.", + keepTextRetry: "إرسال مرة أخرى", + dateInPastError: "لا يمكن أن يكون التاريخ في الماضي", publishCustomDescription: [ "تذكّر: بسبب تقنية البلوكتشين، بمجرد نشر المنشور لا يمكن تعديله أو حذفه.", ], @@ -363,9 +475,8 @@ module.exports = { mentionsMatching: "الإشارات المطابقة", mentionsName: "الاسم", mentionsRelationship: "العلاقة", - updateit: "احصل على التحديثات!", updateBannerText: "يتوفر إصدار جديد من Oasis.", - updateBannerAction: "حدّث الآن →", + updateBannerAction: "حدّث الآن!", info: "معلومات", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -373,16 +484,14 @@ module.exports = { timeAgo: "مضت", sendTime: "حوالي ", theme: "السمة", - legacy: "المفاتيح", - legacyTitle: "المفاتيح", - legacyDescription: "إدارة مفتاحك السري (المفتاح الخاص) بسرعة وأمان.", - legacyExportButton: "تصدير", - legacyImportButton: "استيراد", + backupKeysExportButton: "تصدير", + backupKeysImportButton: "استيراد", ssbLogStream: "البلوكتشين", ssbLogStreamDescription: "تكوين حد الرسائل لتدفقات البلوكتشين.", + logWindowChip: "آخر {n} تدفقات من البلوكتشين", saveSettings: "حفظ الإعدادات", exportDescription: "عيّن كلمة مرور (32 حرفًا على الأقل) لتشفير مفتاحك", - exportDataTitle: "نسخة احتياطية", + exportDataTitle: "نسخة فورية", exportDataDescription: "تنزيل بياناتك (بدون المفتاح السري!)", exportDataButton: "تنزيل قاعدة البيانات", importDescription: "استورد مفتاحك السري المشفر (المفتاح الخاص) لتفعيل صورتك الرمزية", @@ -391,38 +500,33 @@ module.exports = { fileInfo: "سيتم تنزيل مفتاحك السري المشفر إلى جهازك (الاسم: oasis.enc)", developer: "التطوير", devTitle: "التطوير", - welcomeBannerText: "أهلًا بك في أواسيس. اتبع هذه الخطوات السريعة لإعداد شخصيتك.", + welcomeBannerText: "لم يتم إعداد صورتك الرمزية بعد.", aiSuggestionBanner: "اقتراح:", aiSuggestionsEnable: "اقتراحات الذكاء الاصطناعي", - welcomeBannerAction: "ابدأ!", + welcomeBannerAction: "ابدأ", welcomeTitle: "أهلًا بك", + welcomeStepIdentity: "الهوية", + welcomeStepType: "النوع", + welcomeStepUse: "الاستخدام", + welcomeStepLook: "المظهر", + welcomeStepCommunity: "المجتمع", + welcomeStepIntro: "ترحيب", + welcomeHintIdentity: "اسمك وصورتك.", + welcomeHintType: "كيف يُفتح Oasis لك.", + welcomeHintUse: "الوحدات التي تبقى مفعّلة.", + welcomeHintLook: "السمة التي يرتديها Oasis.", + welcomeHintCommunity: "انضم إلى PUB للتعرّف على سكان آخرين.", + welcomeHintIntro: "أرسل تحية أولى إلى المجتمع.", + welcomeStepThemeAction: "استخدم هذه السمة", + welcomeJoinPubs: "انضم إلى PUBs", + welcomeNoFederation: "بدون اتحاد", welcomeStepGreetingAction: "إرسال", - welcomeJoinPubButton: "انضم إلى الـ pub", - welcomeStepLarpAction: "انضم إلى «الأكاديمية»", - welcomeStepLarpText: "انضم إلى لعبة تقمص الأدوار الحية الخاصة بنا.", - welcomeStepLarpTitle: "انضم إلى L.A.R.P.", - welcomeStepGreetingTitle: "أرسل «مرحبًا يا عالم!»", - welcomeStepGreetingText: "أرسل رسالة ليتمكن بقية السكان من اكتشافك.", - welcomeJoinPub: "انضم إلى", welcomeDescription: "بعض الأمور التي يُستحسن فعلها قبل البدء.", - welcomeProgress: "مكتمل", - welcomeStepDone: "تم", - welcomeStepPending: "معلّق", welcomeSkip: "ليس الآن", - welcomeStepLanguageTitle: "اختر لغتك", - welcomeStepLanguageText: "أواسيس يتحدث عدة لغات. يمكنك تغييرها متى شئت.", - welcomeStepProfileTitle: "حرّر ملفك الشخصي", - welcomeStepProfileText: "اختر اسمًا وأضف وصفًا وضع صورة ليتعرّف عليك بقية السكان.", welcomeStepProfileAction: "حفظ الملف", - welcomeStepFederationTitle: "انضم إلى الشبكة الرئيسية", - welcomeStepFederationText: "اتصل بالـ pub الخاص بنا للقاء سكان آخرين والبدء في المزامنة.", - welcomeStepFederationAction: "الذهاب إلى الدعوات", - welcomeStepBackupTitle: "انسخ معرّفك احتياطيًا", - welcomeStepBackupText: "هويتك ملف مفاتيح على هذا الجهاز.", - welcomeStepBackupWarning: "إذا ضاع، لا يستطيع أحد استعادته لك.", - welcomeStepBackupAction: "انسخ احتياطيًا!", + welcomeBootstrapRunning: "جارٍ تحميل لقطة الـpub. عند الانتهاء يتم الانضمام إلى الـpub تلقائيًا؛ يمكنك متابعة استخدام Oasis في هذه الأثناء.", welcomeCompleteTitle: "كل شيء جاهز.", - welcomeCompleteText: "عقدتك جاهزة. من هنا تنمو الشبكة مع من تتابعهم.", + welcomeCompleteText: "عقدتك جاهزة. من هنا تنمو الشبكة مع السكان الذين تدعمهم.", welcomeFindPeople: "ابحث عن السكان", devFilterFiles: "الملفات", devFilterSearch: "بحث", @@ -432,7 +536,7 @@ module.exports = { devFilterTests: "الاختبارات", devVersion: "الإصدار", devNodeVersion: "Node", - devStatsTests: "مجموعات الاختبار", + devStatsTests: "اختبارات", devParentFolder: "المجلد الأعلى", devOpenFolder: "فتح", devDescription: "استكشف الشيفرة المصدرية التي تعمل على هذه العقدة.", @@ -476,6 +580,27 @@ module.exports = { "إذا كنت ترغب في استخدام لغة أخرى، اخترها هنا.", setLanguage: "تعيين اللغة", peerConnectionsIntro: "إدارة جميع اتصالاتك مع الأقران الآخرين.", + peersConnectFailed: "تعذّر الاتصال: {reason}.", + peerStateLabel: "الحالة", + peerAddressLabel: "العنوان", + peerLastChangeLabel: "آخر تغيير", + peerStateConnected: "متصل", + peerStateConnecting: "جارٍ الاتصال", + peerStateStaged: "متاح", + peerErrSilent: "لا نشاط", + peerErrTimeout: "لا يستجيب", + peerErrRefused: "يرفض الاتصال", + peerErrNotFound: "عنوان غير معروف", + peerErrUnreachable: "الشبكة غير قابلة للوصول", + peerErrKeys: "يستخدم مفاتيح مختلفة", + peerErrIdentity: "لا يقبل اتصالك", + peerErrTor: "TOR فقط", + peerErrDropped: "انقطع الاتصال", + peerErrPaused: "الشبكة متوقفة مؤقتًا", + peerErrOther: "خطأ في الاتصال", + peerOutdated: "قديم", + peerVersionUnannounced: "لا يعلن عن إصداره: Oasis قديم", + peersTorMissing: "هذا الـPUB لا يسمح إلا بالاتصالات عبر TOR.", peerConnectionsTitle: "الاتصالات", online: "متصل", offline: "غير متصل", @@ -494,22 +619,25 @@ module.exports = { noConnections: "لا يوجد أقران متصلون.", noDiscovered: "لم يتم اكتشاف أقران.", noUnknownPeers: "لا توجد أقران غير معروفة في رسم بياني الأصدقاء.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "لا توجد عقد مسجلة بعد.", + peerConnect: "اتصال", + peerDisconnect: "قطع الاتصال", peerRefresh: "تحديث", peerPruneIdle: "إزالة الخاملة", + peersPause: "إيقاف مؤقت", + peersResume: "استئناف", + peersPausedNote: "متوقف مؤقتًا: لا يتصل Oasis بأحد ولا يمكن لأحد الاتصال به.", + peersPauseHint: "يوقف كل الاتصالات: لا يتصل Oasis بعُقد أخرى ولا يقبلها حتى تستأنف.", peerExport: "تصدير", peerImport: "استيراد", peerImportTitle: "استيراد قائمة الأقران", peerImportPlaceholder: "ألصق عنوان multiserver واحدًا لكل سطر، أو حمّل ملف .txt…", connectionActionIntro: "", - startNetworking: "بدء الشبكة", - stopNetworking: "إيقاف الشبكة", - restartNetworking: "إعادة تشغيل الشبكة", - sync: "مزامنة الشبكة", indexes: "الفهارس", + indexesRebuilt: "أُعيد بناء الفهارس", + indexesRebuildFailed: "تعذّر إعادة بناء الفهارس", + indexesFiles: "ملفات الفهرس", indexesDescription: "إعادة بناء الفهارس آمنة، وقد تصلح بعض أنواع الأخطاء.", homePageTitle: "الصفحة الرئيسية", @@ -519,44 +647,33 @@ module.exports = { invitesDescription: "إدارة وتطبيق رموز الدعوة في شبكتك.", invitesPubsHint: "ألصق رمز دعوة لخادم pub للاتحاد معه والبدء بنسخ سكانه.", invitesFederationsHint: "الخوادم المتحدة معك: حدّثها، أزل غير المتاحة أو صدّر القائمة.", - invitesHousesHint: "أدخل رمز بيت للانضمام إلى بيت في LARP والمشاركة في اقتصاده.", - invitesTribesHint: "أدخل رمز قبيلة لتصبح عضوًا وتصل إلى محتواها الخاص.", - invitesChatsHint: "أدخل رمز محادثة للانضمام إلى الحوار ومواضيعه.", - invitesPadsHint: "أدخل رمز لوح للكتابة مع آخرين في مستند مشترك.", - invitesCalendarsHint: "أدخل رمز تقويم لمشاركة المواعيد والتذكيرات.", - invitesEventsHint: "أدخل رمز فعالية للانضمام إلى فعالية خاصة.", - invitesForumsHint: "أدخل رمز منتدى للقراءة والرد في منتدى خاص.", - invitesMapsHint: "أدخل رمز خريطة لرؤية الأماكن وإضافتها على خريطة مشتركة.", - invitesShopsHint: "أدخل رمز متجر للانضمام إلى متجر وكتالوجه.", - invitesInhabitantsHint: "أدخل رمز ساكن لمتابعة بعضكما وتبادل المحتوى.", invitesTribesTitle: "القبائل", - invitesTribeInviteCodePlaceholder: "أدخل رمز دعوة القبيلة", - invitesTribeJoinButton: "انضم إلى القبيلة", invitesChatsTitle: "المحادثات", - invitesChatInviteCodePlaceholder: "أدخل رمز دعوة المحادثة", - invitesChatJoinButton: "انضم إلى المحادثة", - invitesPadsTitle: "الوسادات", - invitesPadInviteCodePlaceholder: "أدخل رمز دعوة الوسادة", - invitesPadJoinButton: "انضم إلى الوسادة", + invitesPadsTitle: "المستندات التعاونية", invitesCalendarsTitle: "التقاويم", - invitesCalendarInviteCodePlaceholder: "أدخل رمز دعوة التقويم", - invitesCalendarJoinButton: "انضم إلى التقويم", invitesEventsTitle: "الأحداث", - invitesEventInviteCodePlaceholder: "أدخل رمز دعوة الحدث", - invitesEventJoinButton: "انضم إلى الحدث", invitesForumsTitle: "المنتديات", - invitesForumInviteCodePlaceholder: "أدخل رمز دعوة المنتدى", - invitesForumJoinButton: "انضم إلى المنتدى", invitesMapsTitle: "خرائط", - invitesMapInviteCodePlaceholder: "أدخل رمز دعوة الخريطة", - invitesMapJoinButton: "انضم إلى الخريطة", invitesShopsTitle: "المتاجر", - invitesShopInviteCodePlaceholder: "أدخل رمز دعوة المتجر", - invitesShopJoinButton: "الانضمام إلى المتجر", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "PUBs مقترحة", + invitesFilterOthers: "أخرى", + invitesJoinTitle: "الدعوات", + invitesJoinHint: "ألصق أي رمز دعوة، أيًّا كان غرضه، أو معرّف Oasis لأحد السكان.", + invitesJoinPlaceholder: "رمز دعوة أو معرّف Oasis", + invitesJoinButton: "انضمام", + invitesUsedTitle: "أحدث الدعوات", + invitesUsedEmpty: "لم تستعمل أي دعوة بعد.", + invitesUsedType: "النوع", + invitesUsedName: "الاسم", + invitesUsedDate: "التاريخ", invitesPubInviteCodePlaceholder: "أدخل رمز دعوة PUB", invitesAcceptInvite: "انضم إلى PUB", + invitesPubUses: "الدعوات المستخدمة", + invitesPubExhausted: "لم يتبقَّ لدى هذا الـ PUB أي دعوات.", invitesAlreadyFederated: "أنت متحد بالفعل مع هذا الخادم.", + invitesJoinPubMode: "يعمل Oasis هذا كـ PUB في وضع القراءة فقط. لربطه بـ PUB آخر، استخدم في الطرفية: ./oasis.sh join <رمز الدعوة>", + invitesJoinLocalOnly: "لا تُقبل الدعوات إلا من الجهاز الذي يعمل عليه Oasis، أو باستخدام رابط الإدارة الذي يظهر عند التشغيل.", invitesFederationsTitle: "الاتحادات", invitesAcceptedInvites: "متحدة", invitesUnfollow: "إلغاء المتابعة", @@ -564,16 +681,12 @@ module.exports = { invitesUnfollowedInvites: "غير متحدة", invitesNoFederatedPubs: "لا توجد pubs متحدة.", invitesNoUnfollowed: "لا توجد pubs غير متحدة.", - invitesUnreachablePubs: "يتعذر الوصول", - invitesNoUnreachablePubs: "لا توجد pubs يتعذر الوصول إليها.", invitesPubsRefresh: "تحديث", - invitesPubsClearUnreachable: "إزالة المتعذر الوصول إليها", + invitesPubsClearUnreachable: "تنظيف", invitesPubsExport: "تصدير", invitesPubsImport: "استيراد", invitesPubsImportTitle: "استيراد pubs", invitesPubsImportPlaceholder: "ألصق عنوان multiserver أو رمز دعوة لكل سطر، أو حمّل ملف .txt…", - currentlyUnreachable: "خطأ!", - errorDetails: "تفاصيل الخطأ", genericError: "حدث خطأ.", panicMode: "وضع الطوارئ!", encryptData: "عيّن كلمة مرور (32 حرفًا على الأقل) لتشفير البلوكتشين الخاص بك", @@ -600,7 +713,6 @@ module.exports = { votesOptionNoLabel:"لا", votesOptionAbstentionLabel:"امتناع", votesQuestionLabel:"السؤال", - votesCreatedByLabel:"أنشأه", voteStatusOpen:"مفتوح", voteStatusClosed:"مغلق", commentDescription: ({ parentUrl }) => [ @@ -617,22 +729,22 @@ module.exports = { searchSubmit: "لنبحث...", votesLabel: "التصويتات", pdfFallbackLabel: "مستند PDF", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "المتاجر", + voteLabel: "الأصوات", submit: "إرسال", editProfile: "تعديل الصورة الرمزية", oasisIdLabel: "OasisID", spreadContent: "نسخ", welcomePmSubject: "Hello World!", - welcomePmBody: "مرحبًا بك في أوازيس — شبكة اجتماعية حرة، نِدّ لِنِد، اتحادية ومشفّرة، ذات بنية موزّعة بالدعوة فقط.\n\nبعض الأماكن المثيرة للاهتمام للبدء:\n\n- /profile — عدّل صورتك الرمزية واسمك ووصفك والوحدات التي تظهر في جانبك العام.\n- /invites — أضف pub للاتحاد مع بقية الشبكة.\n- /peers — اطّلع على من هو متصل، أعد مسح شبكتك المحلية، صدّر أو استورد قوائم الأقران.\n- /inhabitants — اكتشف الصور الرمزية للآخرين وزرها.\n- /tribes — أنشئ مجموعات خاصة بتشفير من طرف إلى طرف أو انضم إليها.\n- /inbox — اقرأ وأرسل الرسائل الخاصة.\n- /activity — اطّلع على النشاط الأخير، نشاطك ونشاط شبكتك.\n- /publish — اكتب منشورًا أو شارك صورة أو صوتًا أو فيديو أو مستندًا.\n- /search — ابحث في محتوى الشبكة حسب النوع.\n\nبعض الأماكن المثيرة للاهتمام للاتصال:\n\n- /multiverse — أدر حساباتك الأخرى في الكون المتعدد، بما في ذلك إرسال واستقبال المحتوى.\n\nأُرسلت هذه الرسالة بشكل خاص منك إلى نفسك، لذا لم يلزم أي اتصال لاستلامها.", + welcomePmBody: "مرحبًا بك في أوازيس — شبكة اجتماعية حرة، نِدّ لِنِد، اتحادية ومشفّرة، ذات بنية موزّعة بالدعوة فقط.\n\nبعض الأماكن المثيرة للاهتمام للبدء:\n\n- [/avatar](/profile) — عدّل صورتك الرمزية واسمك ووصفك والوحدات التي تظهر في جانبك العام.\n- /settings — اضبط نسختك المحلية: اللغة والسمة والوحدات والخصوصية والمزامنة.\n- /invites — أضف pub للاتحاد مع بقية الشبكة.\n- /peers — اطّلع على من هو متصل، أعد مسح شبكتك المحلية، صدّر أو استورد قوائم الأقران.\n- /inhabitants — اكتشف الصور الرمزية للسكان الآخرين وزرها.\n- /tribes — أنشئ مجموعات خاصة بتشفير من طرف إلى طرف أو انضم إليها.\n- /search — ابحث في محتوى الشبكة حسب النوع.\n\nبعض الأماكن المثيرة للاهتمام للاتصال:\n\n- /multiverse — أدر حساباتك الأخرى في الكون المتعدد، بما في ذلك إرسال واستقبال المحتوى.\n\nأُرسلت هذه الرسالة بشكل خاص منك إلى نفسك، لذا لم يلزم أي اتصال لاستلامها.", profileSensorsSectionTitle: "المستشعرات", profileVisibilityActivity: "مستوى النشاط", profileDeviceLockedHint: "هذا المستشعر مفعّل دائمًا: يتيح للآخرين معرفة الجهاز الذي تتصل منه ولا يمكن تعطيله.", profileVisibilityKarma: "نقاط KARMA", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "محفظة ECOIN", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "الدخل الأساسي", + profileVisibilityWallet: "محفظة ECOin", + profileVisibilityEcoTax: "ضريبة ECO", + profileVisibilityLarpSign: "ختم L.A.R.P.", profileVisibilityGpg: "مفتاح GPG", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "المتاجر", @@ -645,6 +757,10 @@ module.exports = { profileClearnetImagesLabel: "الصور", profileClearnetDocumentsLabel: "المستندات", profileClearnetTorrentsLabel: "التورنت", + profileClearnetPodcastsLabel: "البودكاست", + profileClearnetMarketLabel: "السوق", + profileClearnetFeedLabel: "التدفق", + profileClearnetWikiLabel: "ويكي", editProfileDescription: "", profileName: "الاسم", @@ -670,7 +786,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "تأكيدات", walletConfirm: "تأكيد", - walletDescription: "إدارة أصولك الرقمية، بما في ذلك إرسال واستقبال ECOin، وعرض رصيدك، والوصول إلى سجل معاملاتك.", + walletDescription: "أدر محفظة ECOin الخاصة بك.", walletDate: "التاريخ", walletFee: "الرسوم (كلما ارتفعت الرسوم، زادت سرعة معالجة معاملتك)", walletFeeLine: ({ fee }) => `الرسوم: ECO ${fee}`, @@ -678,6 +794,86 @@ module.exports = { walletReceive: "استقبال", walletReset: "إعادة تعيين", walletSend: "إرسال", + walletPayingTransfer: "دفع التحويل", + walletCreateTransferLabel: "إنشاء عقد ذكي (تحويل)", + walletTransferNotifiedNote: "تم إخطار الطرف الآخر بهذه الدفعة", + walletTransferCreatedNote: "تم إنشاء تحويل (إيصال) لهذه الدفعة", + walletTransferNoPayeeNote: "لم ينشر أي ساكن هذا العنوان، فلم يُنشأ تحويل", + walletTransferReceiptConcept: "دفعة من المحفظة", + walletPaymentPmText: "أرسل لك دفعة", + walletPayeeLabel: "المستلم", + walletConceptLabel: "البيان", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "لقد استلمت دخلك الأساسي.", + bankingBotPaymentTitle: "لقد استلمت دفعة.", + bankingBotUbiPaidText: "تم دفع دخلك الأساسي الشامل", + bankUbiPubThisNode: "هذه العقدة تدفع الدخل الأساسي", + bankUbiPubConnected: "PUB الدخل الأساسي", + bankUbiPubNone: "لا يوجد PUB يعلن عن الدخل الأساسي في شبكتك بعد", + bankUbiAddressPublished: "تم نشر عنوان ECOin", + bankUbiAddressMissing: "انشر عنوان ECOin الخاص بك لتلقي الدخل الأساسي", + bankUbiClaimedThisMonth: "تمت المطالبة هذا الشهر", + bankUbiPubBalance: "محفظة PUB المتاحة للدخل الأساسي", + bankNoPubAddress: "لم ينشر PUB الدخل الأساسي عنوان ECOin بعد.", + bankUbiTab: "الدخل الأساسي", + bankDonateEco: "تبرّع!", + donateTooltip: "تبرّع بـ ECOin", + bankNoUserAddress: "هذا الساكن لا يشارك عنوان ECOin.", + bankUbiPubDefault: "افتراضي", + bankUbiPool: "الرصيد", + bankUbiLastSeen: "آخر ظهور", + bankUbiLastPayout: "آخر إجراء", + bankUbiPaidOut: "تم توزيعه", + bankRangeToday: "اليوم", + bankRangeWeek: "أسبوع", + bankRangeMonth: "شهر", + bankRangeYear: "سنة", + bankRangeFiveYears: "5 سنوات", + bankRangeAll: "الكل", + bankChartValueTitle: "تطور قيمة ECOin", + bankChartValueHours: "المعادل الزمني (ساعة)", + bankChartWealthTitle: "إعادة توزيع ثروة ECOin", + bankChartDistributed: "الدخل الأساسي الموزّع (ECO)", + bankChartRebalanceTitle: "إعادة توزيع الثروة بين الـ PUBs", + bankChartRebalanced: "ECO المنقولة بين الـ PUBs", + bankChartTaxes: "الضرائب المحتجزة (ECO)", + bankChartNoDataRange: "لا توجد بيانات في هذا النطاق.", + bankChartNoDataYet: "لا توجد بيانات بعد", + bankChartPerHour: "في الساعة", + bankChartPerDay: "في اليوم", + bankChartPerMonth: "في الشهر", + bankTotalUbiDistributed: "إجمالي الدخل الأساسي المعاد توزيعه (الشبكة)", + bankTotalTaxesWithheld: "إجمالي الضرائب المحتجزة (الشبكة)", + bankInflationIssuance: "معدل الإصدار (سنوي، من مكافأة الكتلة)", + bankRulesEligibilityTitle: "من يمكنه تلقي الدخل الأساسي", + bankRulesEligibility1: "عنوان ECOin منشور في ملفك (الملف → المستشعرات → محفظة ECOin).", + bankRulesEligibility2: "تغذية عمرها أكثر من 30 يوماً ولها نشاط في الشبكة: كارما فوق الصفر.", + bankRulesEligibility3: "مطالبة واحدة في الشهر (الحقبة)، من Banking بزر المطالبة بالدخل الأساسي. يمكن رفض الشهر الحالي بزر رفض الدخل الأساسي.", + bankRulesEligibility4: "الحد الأدنى 1 ECO يُدفع دائماً؛ الضرائب تقلّل فقط الجزء الذي يتجاوزه ولا تُحصَّل أبداً بشكل منفصل.", + bankRulesEligibility5: "يدفع الـ PUB في دورته التالية وينشر تحويل دخل أساسي يمكنك تأكيده.", + bankRulesEligibility6: "إذا غاب عنوانك أو كانت التغذية حديثة جداً أو رفضت الشهر أو لم يكن لدى الـ PUB أموال، تُتخطى المطالبة وتُسجَّل على الـ PUB.", + copyLinkTitle: "نسخ الرابط", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "رفض الدخل الأساسي", + bankUbiRefusedThisMonth: "لقد رفضت دخل هذا الشهر الأساسي.", + bankingBotUbiAvailableTitle: "يمكنك المطالبة بدخلك الأساسي.", + bankingBotUbiAvailableText: "دخلك الأساسي لهذا الشهر متاح", + bankingBotConfirmTitle: "لديك عقد ذكي بانتظار التأكيد", + bankingBotConfirmText: "عقود ذكية بانتظار تأكيدك", + bankChartZoomHint: "انقر للتكبير", + bankChartPoolTitle: "رصيد الدخل الأساسي (عبر الزمن)", + bankChartNetworkUbiTitle: "إجمالي الدخل الأساسي المعاد توزيعه (الشبكة)", + bankChartSupplyTitle: "المعروض المتداول من ECOin (عبر الزمن)", + bankChartInflationTitle: "تضخم ECOin (عبر الزمن)", + bankChartInflationSeries: "المعدل السنوي (%)", + bankChartPool: "إجمالي الرصيد (ECO)", + bankChartKarmaTitle: "KARMA Scoring (عبر الزمن)", + bankChartFundsTitle: "أموالك ECOin (عبر الزمن)", + bankChartKarma: "كارما", + bankWalletReady: "ECOin متصل", + bankWalletNotConfigured: "ECOin غير متصل", + transfersPayWithWallet: "ادفع!", walletStatus: "الحالة", walletDisconnected: [ "محفظة ECOin ", @@ -687,8 +883,11 @@ module.exports = { " أو حالة الاتصال.", ], walletSentToLine: ({ destination, amount }) => `تم إرسال ECO ${amount} إلى ${destination}`, + walletNotConfigured: "محفظة ECOin الخاصة بك غير مُهيأة بعد.", walletSettingsDescription: "دمج Oasis مع محفظة ECOin الخاصة بك.", - walletSettingsDocLink: "دليل تثبيت ECOin", + walletSettingsMobileDescription: "اربط Oasis بتطبيق ECOin Wallet على هذا الجهاز.", + walletDownloadEcoin: "نزّل ECOin!", + walletRpcUrl: "عنوان RPC", walletStatusMessages: { invalid_amount: "مبلغ غير صالح", invalid_dest: "عنوان وجهة غير صالح", @@ -704,6 +903,7 @@ module.exports = { walletUser: "اسم المستخدم", walletPass: "كلمة المرور", walletConfiguration: "تعيين المحفظة", + walletDisconnectButton: "فصل المحفظة", cipher: "التشفير", cipherTitle: "التشفير", cipherDescription: "تشفير وفك تشفير نصك بشكل متماثل (باستخدام كلمة مرور مشتركة).", @@ -737,17 +937,16 @@ module.exports = { bookmarkFilterRecent: "الأخيرة", bookmarkCreateButton: "إنشاء علامة مرجعية", bookmarkUpdateButton: "تحديث", - bookmarkDeleteButton: "حذف", bookmarkAddFavoriteButton: "إضافة للمفضلة", bookmarkRemoveFavoriteButton: "إزالة من المفضلة", bookmarkUrlLabel: "الرابط", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "الوصف", bookmarkDescriptionPlaceholder: "اختياري", bookmarkTagsLabel: "الوسوم", bookmarkTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", bookmarkLastVisitLabel: "آخر زيارة", - bookmarkSearchPlaceholder: "ابحث عن رابط، وسوم، فئة، مؤلف...", + bookmarkSearchPlaceholder: "ابحث في العلامات المرجعية...", bookmarkSortRecent: "الأحدث", bookmarkSortOldest: "الأقدم", bookmarkSortTop: "الأكثر تصويتًا", @@ -786,7 +985,7 @@ module.exports = { videoDescriptionPlaceholder: "اختياري", videoNoFile: "لم يتم تقديم ملف فيديو", noVideos: "لا توجد فيديوهات متاحة.", - videoSearchPlaceholder: "ابحث عن عنوان، وسوم، مؤلف...", + videoSearchPlaceholder: "ابحث في الفيديوهات...", videoSortRecent: "الأحدث", videoSortOldest: "الأقدم", videoSortTop: "الأكثر تصويتًا", @@ -809,7 +1008,6 @@ module.exports = { documentFilterFavorites: "المفضلة", documentCreateButton: "رفع مستند", documentUpdateButton: "تحديث", - documentDeleteButton: "حذف", documentAddFavoriteButton: "إضافة للمفضلة", documentRemoveFavoriteButton: "إزالة من المفضلة", documentMessageAuthorButton: "رسالة خاصة", @@ -822,7 +1020,7 @@ module.exports = { documentDescriptionPlaceholder: "اختياري", documentNoFile: "لا يوجد ملف.", noDocuments: "لا توجد مستندات متاحة.", - documentSearchPlaceholder: "ابحث عن عنوان، وسوم، وصف، مؤلف...", + documentSearchPlaceholder: "ابحث في المستندات...", documentSortRecent: "الأحدث", documentSortOldest: "الأقدم", documentSortTop: "الأكثر تصويتًا", @@ -843,19 +1041,19 @@ module.exports = { audioFilterTop: "الأفضل", audioCreateButton: "رفع ملف صوتي", audioUpdateButton: "تحديث", - audioDeleteButton: "حذف", audioAddFavoriteButton: "إضافة للمفضلة", audioRemoveFavoriteButton: "إزالة من المفضلة", audioFileLabel: "اختر ملفًا صوتيًا (.mp3, .wav, .ogg)", audioTagsLabel: "الوسوم", audioTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + tagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", audioTitleLabel: "العنوان", audioTitlePlaceholder: "اختياري", audioDescriptionLabel: "الوصف", audioDescriptionPlaceholder: "اختياري", audioNoFile: "لم يتم تقديم ملف صوتي", noAudios: "لا توجد ملفات صوتية متاحة.", - audioSearchPlaceholder: "ابحث عن عنوان، وسوم، مؤلف...", + audioSearchPlaceholder: "ابحث في الملفات الصوتية...", audioSortRecent: "الأحدث", audioSortOldest: "الأقدم", audioSortTop: "الأكثر تصويتًا", @@ -879,13 +1077,12 @@ module.exports = { favoritesFilterChats: "الدردشات", favoritesFilterCalendars: "التقويمات", favoritesFilterVideos: "الفيديوهات", - favoritesRemoveButton: "إزالة من المفضلة", favoritesNoItems: "لا توجد مفضلات بعد.", yourContacts: "جهات اتصالك", allInhabitants: "السكان", allCVs: "السير الذاتية", discoverPeople: "اكتشف السكان في شبكتك.", - searchInhabitantsPlaceholder: "تصفية السكان حسب الاسم …", + searchInhabitantsPlaceholder: "ابحث في السكان...", filterLocation: "تصفية السكان حسب الموقع …", filterLanguage: "تصفية السكان حسب اللغة …", filterSkills: "تصفية السكان حسب المهارات …", @@ -909,7 +1106,6 @@ module.exports = { topSectionTitle: "الأعلى", blockedLabel: "مستخدم محظور", inhabitantviewDetails: "عرض التفاصيل", - cvVisitButton: "زيارة السيرة الذاتية", keepReading: "متابعة القراءة...", oasisId: "المعرّف", noInhabitantsFound: "لم يتم العثور على سكان بعد.", @@ -927,6 +1123,18 @@ module.exports = { parliamentFilterLeaders: "القادة", parliamentFilterRules: "القواعد", parliamentGovernmentCard: "الحكومة الحالية", + parliamentSeatsGovernment: "الحكومة", + parliamentSeatsOpposition: "المعارضة", + parliamentSeatsAbstention: "امتناع", + parliamentSeatsQuorum: "نصاب الانتخاب", + parliamentSeatsPass: "عتبة الموافقة", + parliamentSeatsByElection: "حسب الانتخاب", + parliamentSeatsByHouses: "حسب بيت LARP", + parliamentSeatsSeats: "مقاعد", + parliamentSeatsSeat: "مقعد", + parliamentSeatsLeader: "القائد", + parliamentSeatsVotesFor: "أصوات لـ", + parliamentSeatsNoHouse: "بلا بيت", parliamentActorInPowerInhabitant: 'الساكن الحاكم', parliamentActorInPowerTribe: 'القبيلة الحاكمة', parliamentHistoricalGovernmentsTitle: 'الحكومات', @@ -994,6 +1202,7 @@ module.exports = { parliamentTimeRemaining: "الوقت المتبقي", parliamentElectionsStatusTitle: "الحكومة القادمة", parliamentProposalDeadlineLabel: "الموعد النهائي", + parliamentViewReport: "عرض التقرير", parliamentProposalTimeLeft: "الوقت المتبقي", parliamentRulesTitle: "كيف يعمل البرلمان", parliamentRulesIntro: "تُحسم الانتخابات كل شهرين؛ الترشيحات مستمرة وتُعاد عند اختيار حكومة.", @@ -1008,6 +1217,7 @@ module.exports = { parliamentRulesLaws: "عندما يصل اقتراح إلى حده الأدنى، يصبح قانونًا ويظهر في تبويب القوانين مع تاريخ سنّه.", parliamentRulesHistorical: "في تبويب السجل التاريخي يمكنك رؤية كل دورة حكومية حدثت وبيانات عن إدارتها.", parliamentRulesLeaders: "في تبويب القادة يمكنك رؤية ترتيب السكان/القبائل التي حكمت (أو ترشحت)، مرتبة حسب الكفاءة.", + parliamentRulesHemicycle: "يمنح نصف الدائرة مقعداً لكل ساكن: كتلة الحكومة (ناخبو الترشيح الفائز والقائد)، وكتلة معارضة لكل ترشيح آخر في الدورة، والامتناع. تُظهر العلامات المتقطعة نصاب الانتخاب (25٪ من التعداد) وعتبة موافقة الطريقة الحالية. «حسب بيت LARP» يلوّن المقاعد نفسها حسب البيت.", parliamentProposalVoteStatusLabel: "حالة التصويت", parliamentProposalOnTrackYes: "تم الوصول للحد الأدنى", parliamentProposalOnTrackNo: "دون الحد الأدنى", @@ -1021,6 +1231,7 @@ module.exports = { courtsCaseFormTitle: "فتح قضية", courtsCaseRespondent: "المتهم / المدعى عليه", courtsCaseRespondentPh: "معرّف Oasis (@...) أو اسم القبيلة", + courtsCaseSubjectPlaceholder: "الموضوع أو وصف قصير", courtsCaseMethod: "طريقة الحل", courtsCaseSubmit: "رفع القضية", courtsNominateJudge: "ترشيح قاضٍ", @@ -1054,15 +1265,30 @@ module.exports = { courtsMethodDICTATOR: "ديكتاتور", courtsMethodPOPULAR: "شعبي", courtsMethodKARMATOCRACY: "كارماتوقراطية", + courtsCaseType: "نوع القضية", + courtsPresetMinorConflict: "نزاع بسيط", + courtsPresetModerateConflict: "نزاع متوسط", + courtsPresetSevereConflict: "نزاع خطير", + courtsPresetHarassment: "تحرش أو إساءة", + courtsPresetContentModeration: "إشراف على المحتوى", + courtsPresetBanRestriction: "حظر أو تقييد", + courtsPresetGovernanceDispute: "نزاع حوكمة", + courtsStatusOPEN: "مفتوحة", + courtsStatusIN_PROGRESS: "قيد النظر", + courtsStatusDECIDED: "تم البت فيها", + courtsStatusCLOSED: "مغلقة", + courtsStatusSOLVED: "محلولة", + courtsStatusUNSOLVED: "غير محلولة", + courtsStatusDISCARDED: "مرفوضة", courtsRulesTitle: "كيف تعمل المحاكم", courtsRulesIntro: "المحاكم هي عملية يديرها المجتمع لحل النزاعات وتعزيز العدالة التصالحية. يُعطى الأولوية للحوار والأدلة الواضحة والعلاجات المتناسبة.", courtsRulesLifecycle: "العملية: 1) فتح قضية 2) اختيار الطريقة 3) تقديم الأدلة 4) الاستماع والمداولة 5) الحكم والعلاج 6) الامتثال والإغلاق 7) الاستئناف (إن وُجد).", - courtsRulesRoles: "المدعي: يفتح القضية. الدفاع: الشخص أو القبيلة المتهمة. الطريقة: الآلية التي يختارها المجتمع لتسهيل وتقييم الأدلة وإصدار الحكم. الشاهد: يقدم شهادة أو دليلاً. الوسطاء: أشخاص محايدون يدعوهم المدعي و/أو الدفاع، لديهم حق الوصول لجميع التفاصيل، يساعدون في تهدئة النزاع والمشاركة في صنع الاتفاقيات.", + courtsRulesRoles: "المدعي: يفتح القضية. الدفاع: الساكن أو القبيلة المتهمة. الطريقة: الآلية التي يختارها المجتمع لتسهيل وتقييم الأدلة وإصدار الحكم. الشاهد: يقدم شهادة أو دليلاً. الوسطاء: سكان محايدون يدعوهم المدعي و/أو الدفاع، لديهم حق الوصول لجميع التفاصيل، يساعدون في تهدئة النزاع والمشاركة في صنع الاتفاقيات.", courtsRulesEvidence: "الوصف حتى 1000 حرف. أرفق صورًا وملفات صوتية وفيديوهات ومستندات PDF ذات صلة وقانونية. لا تشارك بيانات خاصة حساسة دون موافقة.", courtsRulesDeliberation: "قد تكون الجلسات علنية أو خاصة. يضمن القضاة الاحترام ويطلبون التوضيحات وقد يتجاهلون المواد غير ذات الصلة أو غير القانونية.", courtsRulesVerdict: "يُعطى الأولوية للتصالح: الاعتذارات، اتفاقيات الوساطة، إدارة المحتوى، القيود المؤقتة، أو تدابير متناسبة أخرى. يجب تسجيل الأسباب.", courtsRulesAppeals: "الاستئناف: مسموح عند وجود أدلة جديدة أو خطأ إجرائي واضح. يجب تقديمه خلال 7 أيام ما لم يُذكر خلاف ذلك.", - courtsRulesPrivacy: "احترم الخصوصية والأمان. يتم إزالة التشهير والكراهية والتهديدات. قد يعدل القضاة أو يختمون أجزاء من السجل لحماية الأشخاص المعرضين للخطر.", + courtsRulesPrivacy: "احترم الخصوصية والأمان. يتم إزالة التشهير والكراهية والتهديدات. قد يعدل القضاة أو يختمون أجزاء من السجل لحماية السكان المعرضين للخطر.", courtsRulesMisconduct: "قد يؤدي التحرش أو التلاعب أو تلفيق الأدلة إلى حكم سلبي فوري.", courtsRulesGlossary: "القضية: سجل نزاع. الدليل: مواد تدعم الادعاءات. الحكم: قرار مع علاج. الاستئناف: طلب مراجعة الحكم.", courtsFilterActions: "الإجراءات", @@ -1078,12 +1304,16 @@ module.exports = { exploreTrending: "استكشف المحتوى الأكثر شعبية في شبكتك.", votesButton: "التصويتات", shopProductButton: "متجر", + podcastButton: "بودكاست", + podcastEpisodeButton: "حلقة", + campaignButton: "حملة", + logisticsRouteButton: "مسار", trendingTotalOpinions: "إجمالي الآراء", trendingNoContentMessage: "لا توجد اتجاهات بعد.", trendingTotalCount: "العدد الإجمالي", tasksTitle: "المهام", tasksDescription: "اكتشف وأدِر المهام في شبكتك.", - taskSearchPlaceholder: "ابحث عن المهام...", + taskSearchPlaceholder: "ابحث في المهام...", taskTitleLabel: "العنوان", taskDescriptionLabel: "الوصف", taskStartTimeLabel: "وقت البدء", @@ -1102,7 +1332,7 @@ module.exports = { taskStatusOpen: "مفتوح", taskStatusInProgress: "قيد التنفيذ", taskStatusClosed: "مغلق", - taskAssignedTo: "مُسنَد إلى", + taskAssignedTo: "مُسنَد", taskAssignedChip: "مُسندة", taskUnassignedChip: "غير مُسندة", taskAssignees: "المُكلَّفون", @@ -1112,6 +1342,7 @@ module.exports = { taskUpdateButton: "تحديث", taskDeleteButton: "حذف", taskFilterAll: "الكل", + taskFilterRecent: "الأخيرة", taskFilterMine: "خاصتي", taskFilterOpen: "مفتوح", taskFilterInProgress: "قيد التنفيذ", @@ -1139,14 +1370,13 @@ module.exports = { eventUpcomingDates: "التواريخ القادمة", eventsTitle: "الأحداث", eventsDescription: "اكتشف وأدِر الأحداث في شبكتك.", - eventSearchPlaceholder: "ابحث عن الأحداث...", + eventSearchPlaceholder: "ابحث في الأحداث...", eventPrice: "السعر", eventStatus: "الحالة", eventAllSectionTitle: "الأحداث", eventMineSectionTitle: "أحداثك", eventCreateSectionTitle: "إنشاء حدث", eventUpdateSectionTitle: "تحديث الحدث", - eventDeleteButton: "حذف", eventCreateButton: "إنشاء حدث", eventAddToCalendar: "إضافة إلى التقويم", eventVisitCalendar: "عرض التقويم", @@ -1188,6 +1418,7 @@ module.exports = { eventDate: "التاريخ", eventAttendButton: "حضور الحدث", eventFilterAll: "الكل", + eventFilterRecent: "الأخيرة", eventFilterMine: "خاصتي", eventFilterToday: "اليوم", eventFilterWeek: "هذا الأسبوع", @@ -1214,7 +1445,7 @@ module.exports = { tagsFilterTop: "الأفضل", tagsFilterCloud: "السحابة", tagsNoItems: "لا توجد وسوم متاحة.", - tagsSearchPlaceholder: "ابحث عن الوسوم...", + tagsSearchPlaceholder: "ابحث في الوسوم...", tagsTableHeaderTag: "الوسم/الرابط:", tagsTableHeaderCount: "العداد:", transfersTitle: "التحويلات", @@ -1222,8 +1453,11 @@ module.exports = { transfersFrom: "من", transfersTo: "إلى", transfersFilterAll: "الكل", + transfersFilterRecent: "الأخيرة", transfersFilterMine: "خاصتي", - transfersFilterUBI: "UBI", + transfersFilterMinePending: "قيد الانتظار", + transfersFilterMineConfirmed: "موقَّعة", + transfersFilterUBI: "الدخل الأساسي", transfersFilterMarket: "السوق", transfersFilterTop: "الأفضل", transfersFilterPending: "معلّق", @@ -1237,7 +1471,6 @@ module.exports = { transfersCategoryEconomic: "اقتصادي", transfersCategoryTime: "وقت", transfersCategoryTrust: "ثقة", - transfersCategoryApply: "تطبيق", transfersUnitEco: "ECO", transfersUnitHours: "س", transfersUnitTrust: "ثقة", @@ -1267,7 +1500,7 @@ module.exports = { transfersDiscardedSectionTitle: "التحويلات الملغاة", transfersCreateSectionTitle: "إنشاء عملية نقل", transfersAllSectionTitle: "التحويلات", - transfersSearchPlaceholder: "ابحث عن مفهوم، وسوم، مستخدمين...", + transfersSearchPlaceholder: "ابحث في التحويلات...", transfersMinAmountLabel: "الحد الأدنى للمبلغ", transfersMaxAmountLabel: "الحد الأقصى للمبلغ", transfersSortRecent: "الأحدث", @@ -1289,6 +1522,7 @@ module.exports = { voteErrorTitle: "راجع النموذج", voteErrorDeadlineMin: "يجب أن يكون الموعد النهائي بعد {days} أيام على الأقل من الآن.", voteErrorGeneric: "تعذّر حفظ التصويت.", + voteLocked: "تلقّى هذا التصويت أصواتًا أو آراء بالفعل، أو يتبع البرلمان أو المحاكم: لم يعد بالإمكان تعديله أو حذفه.", voteUpdateButton: "تحديث", voteDeleteButton: "حذف", voteOptionYes: "نعم", @@ -1298,6 +1532,7 @@ module.exports = { voteFollowMajority: "اتباع الأغلبية", voteNotInterested: "غير مهتم", voteFilterAll: "الكل", + voteFilterRecent: "الأخيرة", voteFilterMine: "خاصتي", voteFilterOpen: "مفتوح", voteFilterClosed: "مغلق", @@ -1313,7 +1548,6 @@ module.exports = { voteTags: "الوسوم", voteOpinions: "الآراء", novotes: "لا توجد مقترحات تصويت متاحة.", - voteBy: "بواسطة", voteCreatedAt: "أُنشئ في", voteNoQuestion: "لم يتم تقديم سؤال", voteUnknownCreator: "مُنشئ غير معروف", @@ -1323,6 +1557,7 @@ module.exports = { voteCommentsForumButton: 'فتح النقاش', voteCommentsSectionTitle: 'فتح النقاش', voteNoCommentsYet: 'لا توجد تعليقات بعد. كن أول من يرد.', + commentsGoToLast: "اذهب إلى آخر تعليق", voteNewCommentPlaceholder: 'اكتب تعليقك هنا…', voteNewCommentButton: 'نشر التعليق', voteNewCommentLabel: 'إضافة تعليق', @@ -1333,6 +1568,8 @@ module.exports = { cvNameLabel: "الاسم الكامل", cvDescriptionLabel: "الملخص", cvPhotoLabel: "الصورة", + cvPdfLabel: "السيرة الذاتية (PDF)", + cvVisitButton: "زيارة السيرة الذاتية", cvPersonalExperiencesLabel: "الخبرات الشخصية", cvPersonalSkillsLabel: "المهارات الشخصية (مفصولة بفواصل)", cvOasisExperiencesLabel: "خبرات المساهمة في Oasis", @@ -1364,7 +1601,7 @@ module.exports = { blogMessage: "الرسالة", blogImage: "رفع وسائط (الحد الأقصى: 50 ميغابايت)", blogMedia: "المرفقات (حتى 8 صور ومقطع فيديو)", - blogPublish: "معاينة", + blogPublish: "نشر", pollsTitle: "الاستطلاعات", dataTitle: "التطابقات", dataDescription: "استكشف تطابقات شبكتك وشاهدها.", @@ -1383,12 +1620,23 @@ module.exports = { dataKindTasks: "المهام", dataKindReports: "التقارير", dataKindVotes: "التصويتات", - dataSearchPlaceholder: "ابحث في البيانات المتقاطعة...", + dataSearchPlaceholder: "ابحث في التطابقات...", dataCohesionTitle: "معامل التماسك (CC)", dataCcShort: "CC", dataCohesionHint: "متوسط التقارب بين كل ما نشرته الشبكة.", dataAffinity: "التوافق", dataCommonTerms: "كلمة مفتاحية", + dataReasonFollowing: "أنت تدعم هذا الساكن", + dataReasonTribe: "يشاركك قبيلة", + dataReasonPinned: "يطابق شيئًا ثبّتّه", + dataReasonCv: "يطابق مهارات سيرتك الذاتية", + dataReasonContent: "مرتبط بما تنشره", + dataReasonRelated: "مرتبط باهتماماتك", + dataReasonAlike: "نشره ساكن يشبهك", + dataReasonSupportsYou: "يدعمك", + dataReasonMutual: "دعم متبادل", + dataReasonRated: "حاصل على تقييم جيد من الشبكة", + dataReasonNear: "بالقرب منك", dataStatPeople: "السير الذاتية", dataStatConnected: "متصلون", dataStatSkills: "المهارات", @@ -1467,12 +1715,11 @@ module.exports = { forumCreateButton: "إنشاء منتدى", forumCreateSectionTitle: "إنشاء منتدى", forumDescription: "تحدث بصراحة مع السكان الآخرين في شبكتك.", - forumSearchPlaceholder: "ابحث في المنتدى...", + forumSearchPlaceholder: "ابحث في المنتديات...", forumFilterAll: "الكل", forumFilterMine: "خاصتي", forumFilterRecent: "الأخيرة", forumFilterTop: "الأفضل", - forumDeleteButton: "حذف", forumParticipants: "المشاركون", forumMessages: "الرسائل", forumMessageLabel: "الرسالة", @@ -1499,7 +1746,6 @@ module.exports = { imageFilterGallery: "المعرض", imageCreateButton: "رفع صورة", imageUpdateButton: "تحديث", - imageDeleteButton: "حذف", imageAddFavoriteButton: "إضافة للمفضلة", imageRemoveFavoriteButton: "إزالة من المفضلة", imageFileLabel: "اختر ملف صورة (.jpeg, .jpg, .png, .gif)", @@ -1511,13 +1757,14 @@ module.exports = { imageDescriptionPlaceholder: "اختياري", imageNoFile: "لم يتم تقديم ملف صورة", noImages: "لا توجد صور متاحة.", - imageSearchPlaceholder: "ابحث عن عنوان، وسوم، وصف، مؤلف...", + imageSearchPlaceholder: "ابحث في الصور...", imageSortRecent: "الأحدث", imageSortOldest: "الأقدم", imageSortTop: "الأكثر تصويتًا", imageSearchButton: "بحث", imageUpdatedAt: "تم التحديث", imageNoMatch: "لا توجد صور تطابق بحثك.", + feedSearchPlaceholder: "ابحث في التغذية...", feedTitle: "التغذية", createFeedTitle: "إنشاء تغذية", createFeedButton: "أرسل التغذية!", @@ -1532,7 +1779,7 @@ module.exports = { alreadyRefeeded: "لقد أعدت نشر هذا بالفعل.", activityTitle: "النشاط", yourActivity: "نشاطك", - globalActivity: "النشاط العام", + globalActivity: "النشاط", activityList: "النشاط", activityDesc: "شاهد آخر نشاط في شبكتك.", allButton: "الكل", @@ -1547,7 +1794,8 @@ module.exports = { meme: "ميم", typeRecent: "الأخيرة", typeTop: "الأعلى", - typePost: "مدونات", + typePost: "مدونة", + activityUpdateLabel: "تحديث", typeTribe: "القبائل", typeLarp: "L.A.R.P.", typeInhabitants: "السكان", @@ -1682,7 +1930,7 @@ module.exports = { courtsNoCases: 'لا توجد قضايا.', reportsTitle: "التقارير", reportsDescription: "أدر تقارير شبكتك وتابعها.", - reportsSearchPlaceholder: "ابحث عن البلاغات...", + reportsSearchPlaceholder: "ابحث في التقارير...", reportsFilterAll: "الكل", reportsFilterRecent: "حديثة", reportsFilterTop: "الأعلى", @@ -1730,7 +1978,6 @@ module.exports = { reportsUnderReviewSectionTitle: "التقارير قيد المراجعة", reportsResolvedSectionTitle: "التقارير المحلولة", reportsInvalidSectionTitle: "التقارير غير الصالحة", - reportsTemplateSectionTitle: 'قالب التقرير', reportsBugTemplateTitle: 'تفاصيل إعادة الإنتاج (الأخطاء)', reportsFeatureTemplateTitle: 'التفاصيل (الميزات)', reportsAbuseTemplateTitle: 'التفاصيل (الإساءة)', @@ -1790,8 +2037,6 @@ module.exports = { tribeGallerySectionTitle: "معرض القبائل", tribeRecentSectionTitle: "القبائل الأخيرة", tribeTopSectionTitle: "القبائل الشائعة", - tribeviewTribeButton: "زيارة القبيلة", - tribeviewSubTribeButton: "زيارة القبيلة الفرعية", tribeRootLabel: "الجذر", tribeDescription: "استكشف أو أنشئ قبائل في شبكتك.", tribeMainTribeLabel: "القبيلة الرئيسية", @@ -1800,7 +2045,7 @@ module.exports = { tribeDeleteButton: "حذف", tribeImageLabel: "رفع وسائط (الحد الأقصى: 50 ميغابايت)", tribeTitleLabel: "العنوان", - searchTribesPlaceholder: "تصفية القبائل حسب الاسم …", + searchTribesPlaceholder: "ابحث في القبائل...", tribeTitlePlaceholder: "اسم القبيلة", tribeDescriptionLabel: "الوصف", tribeDescriptionPlaceholder: "صف هذه القبيلة", @@ -1813,6 +2058,15 @@ module.exports = { tribeIsAnonymousLabel: "الحالة", tribeMembersCount: "الأعضاء", tribeEnterInvite: "إدخال الرمز", + subscriptionTitle: "الاشتراكات", + subscriptionOn: "مشترك", + subscriptionOff: "غير مشترك", + subscriptionSubscribe: "اشترك", + subscriptionUnsubscribe: "إلغاء الاشتراك", + subscriptionSubscribers: "المشتركون", + pmListLabel: "إرسال إلى", + pmListNone: "— بدون قائمة —", + pmListNoSubscribers: "لا مشتركين", tribeLeaveButton: "مغادرة", tribePublic: "عامة", tribePrivate: "خاصة", @@ -1845,16 +2099,16 @@ module.exports = { tribeSectionVideos: "الفيديوهات", tribeSectionDocuments: "المستندات", tribeSectionBookmarks: "العلامات المرجعية", - tribeSectionMaps: "MAPS", - tribeSectionPads: "PADS", - tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionMaps: "الخرائط", + tribeSectionPads: "المستندات التعاونية", + tribeSectionChats: "المحادثات", + tribeSectionCalendars: "التقاويم", + tribePadCreate: "إنشاء مستند تعاوني", + tribeChatCreate: "إنشاء محادثة", + tribeCalendarCreate: "إنشاء تقويم", + tribePadsEmpty: "لا توجد مستندات تعاونية بعد.", + tribeChatsEmpty: "لا توجد محادثات بعد.", + tribeCalendarsEmpty: "لا توجد تقاويم بعد.", tribeInhabitantsEmpty: "لا يوجد سكان في هذه القبيلة بعد.", tribeEventCreate: "إنشاء حدث", tribeEventsEmpty: "لا توجد أحداث بعد.", @@ -1909,9 +2163,14 @@ module.exports = { tribeMediaTypeDocument: "مستند", tribeMediaTypeBookmark: "علامة مرجعية", tribeContentDelete: "حذف", + tribeReachTribe: "القبيلة", + tribeReachNotice: "يعرضه OASIS لجميع السكان ويعرضه CLEARNET أيضًا على الويب المفتوح. فتحه يترك نسخة مقروءة خارج القبيلة: إعادته إلى القبيلة تخفيه، لكن لا يمكن مسح النسخ التي تمت مشاركتها.", + tribeReachPrivate: "محتوى القبيلة الخاصة لا يمكن أن يبقى إلا داخل القبيلة.", + tribeReachOpenedBy: "فتحه", + tribeReachChange: "مكان النشر", tribeInviteCodeText: "رمز الدعوة: ", oasisVersionLabel: "إصدار Oasis", - tribeInviteCodeHint: "شارك هذا الرمز مع من تريد دعوته. يمكنه الانضمام من /invites → Tribes.", + tribeInviteCodeHint: "شارك هذا الرمز مع من تريد دعوته. يمكنه الانضمام من /invites.", tribeStatusOpen: "مفتوح", tribeStatusClosed: "مغلق", tribeStatusInProgress: "قيد التنفيذ", @@ -1951,7 +2210,7 @@ module.exports = { tribeSearchMinChars: "أدخل حرفين على الأقل للبحث.", agendaTitle: "الأجندة", agendaDescription: "هنا يمكنك العثور على جميع العناصر المسندة إليك.", - agendaSearchPlaceholder: "ابحث في جدول الأعمال...", + agendaSearchPlaceholder: "ابحث في الأجندة...", agendaFilterAll: "الكل", agendaFilterToday: "اليوم", agendaFilterUpcoming: "القادمة", @@ -1972,6 +2231,7 @@ module.exports = { agendaNoItems: "لم يتم العثور على إسنادات.", agendaDiscardButton: "تجاهل", agendaRestoreButton: "استعادة", + agendaRemoveButton: "إزالة", agendaMembersCount: "الأعضاء", agendaLocationLabel: "الموقع", agendaYes: "نعم", @@ -2038,8 +2298,8 @@ module.exports = { pmComposeTitle: "إرسال رسالة", pmRecipients: "المستلمون", pmRecipientsHint: "أدخل معرّفات Oasis مفصولة بفواصل", - pmLimitsHint: "حتى 7 مستلمين لكل رسالة.", - pmTextPlaceholder: "النص محدود بـ 7000 حرفًا.", + pmLimitsHint: "حتى 7 من السكان لكل رسالة.", + pmTextPlaceholder: "اكتب رسالتك.", pmSubject: "الموضوع", pmSubjectHint: "أدخل موضوع الرسالة", pmText: "الرسالة", @@ -2061,8 +2321,8 @@ module.exports = { pmCrypterCharsLabel: "حرفًا", pmInvalidRecipients: "معرّف Oasis غير صالح (يجب أن يكون بالشكل @…=.ed25519).", pmEncryptionLabel: "التشفير", - private: "خاص", - privateDescription: "رسائلك المشفرة.", + private: "صندوق الوارد", + privateDescription: "أدر رسائلك الخاصة.", privateInbox: "الوارد", privateSent: "المُرسَل", privateDelete: "حذف", @@ -2118,24 +2378,24 @@ module.exports = { visitContent: "زيارة المحتوى", favoriteAdd: "تثبيت في المفضلة", pmContentTooltip: "إرسال رسالة خاصة", + phoneCallTooltip: "إجراء مكالمة هاتفية", favoriteRemove: "إزالة من المفضلة", reportContent: "الإبلاغ عن المحتوى", banking: 'المصرفية', bankingTitle: 'المصرفية', - bankingDescription: "اقتصاد ECOin: الرصيد، الدخل الأساسي، الضرائب وقيمة الصناعة.", + bankingDescription: "اقتصاد ECOin الجماعي.", bankOverview: 'نظرة عامة', bankEpochs: 'الحقب', bankRules: 'القواعد', pending: 'معلّق', closed: 'مغلق', - bankViewTx: 'عرض المعاملة', bankClaimNow: 'طالب الآن', - bankClaimUBI: 'طالب بـ UBI!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: 'طالب بـ UBI', + bankClaimAndPay: "المطالبة والدفع", + bankClaimedPending: "المطالبة قيد الانتظار...", + bankStatusClaimed: "تمت المطالبة", + bankStatusExpired: "منتهية الصلاحية", + bankPubOnly: "عملية خاصة بـ PUB فقط", bankEpoch: 'الحقبة', bankPool: 'المجمع (هذه الحقبة)', bankWeightsSum: 'مجموع الأوزان', @@ -2154,8 +2414,8 @@ module.exports = { bankViewEpoch: 'عرض الحقبة', bankYourIndustryBalance: "حصتك الصناعية", bankYourUbiMonth: "دخلك الأساسي (هذا الشهر)", - bankYourFundsMonth: "أموالك (هذا الشهر)", - bankIndustryBalance: "الإنتاج الصناعي (تقديري)", + bankYourFundsMonth: "إجمالي الأموال", + bankIndustryBalance: "الإنتاج الصناعي", bankUserBalance: 'رصيدك', bankAddresses: 'العناوين', bankNoAddresses: 'لم يتم العثور على عناوين.', @@ -2163,6 +2423,10 @@ module.exports = { bankAddress: 'العنوان', bankAddAddressTitle: 'إضافة عنوان ECOIN', bankAddAddressUser: 'معرّف Oasis', + bankAddAddressLabel: "تسمية (اختياري)", + bankAddAddressLabelPlaceholder: "محفظة بوب، متجر أليس…", + bankAddAddressUserOptional: "معرّف Oasis (اختياري)", + bankAddressUnnamed: "عنوان بدون اسم", bankAddAddressAddress: 'عنوان ECOIN', bankAddAddressSave: 'حفظ', bankAddressAdded: 'تمت إضافة العنوان', @@ -2180,11 +2444,11 @@ module.exports = { bankAddressDeleteConfirm: 'حذف هذا العنوان؟', search: 'ابحث!', bankLocal: 'محلي', - bankFromOasis: 'Oasis', + bankFromOasis: "الشبكة", pubIdLabel: "PUB ID", bankUbiAvailableNo: "لا توجد أموال!", bankUbiAvailableOk: "متوفر!", - bankUbiAvailability: "الدخل الأساسي (الشبكة)", + bankUbiAvailability: "الدخل الأساسي", bankAlreadyClaimedThisMonth: "تم المطالبة بالفعل هذا الشهر", bankUbiThisMonth: "الدخل الأساسي (هذا الشهر)", bankUbiLastClaimed: "الدخل الأساسي (آخر مطالبة)", @@ -2214,6 +2478,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "مشفّر أثناء النقل", shopAddFavorite: "إضافة مفضلة", shopRemoveFavorite: "إزالة مفضلة", shopOpen: "مفتوح", @@ -2221,6 +2486,13 @@ module.exports = { shopMakePrivate: "جعلها خاصة (E2E)", shopMakePublic: "جعلها عامة", shopProducts: "المنتجات", + shopFilterAll: "الكل", + shopFilterRecent: "الأحدث", + shopFilterMine: "خاصتي", + shopFilterTop: "الأفضل", + shopFilterProducts: "المنتجات", + shopFilterPrices: "الأسعار", + shopFilterFavorites: "المفضلة", shopProductAdd: "إضافة منتج", shopProductTitle: "منتج", shopProductUpdate: "تحديث منتج", @@ -2248,7 +2520,7 @@ module.exports = { marketVisitItem: "زيارة العنصر", shopStatus: "الحالة", shopCreatedAt: "تاريخ الإنشاء", - shopSearchPlaceholder: "البحث عن متاجر...", + shopSearchPlaceholder: "ابحث في متاجر...", shopUrl: "الرابط", shopLocation: "الموقع", shopTags: "الوسوم", @@ -2258,37 +2530,40 @@ module.exports = { shopTitlePlaceholder: "اسم متجرك", shopDescriptionPlaceholder: "وصف تفصيلي لمتجرك", shopLocationPlaceholder: "المدينة، البلد", - shopTagsPlaceholder: "وسم1، وسم2، وسم3", + shopTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", mapTitlePlaceholder: "عنوان الخريطة", shopProductFeatured: "منتج مميز", - shopSendToMarket: "Send to Market", + shopSendToMarket: "إرسال إلى السوق", typeShop: "متاجر", typeShopProduct: "منتج متجر", bankExchange: 'الصرف', bankExchangeCurrentValue: 'قيمة ECOin (1 ساعة)', - bankTotalSupply: 'إجمالي معروض ECOin', - bankEcoinHours: "معادلة ECOin بالوقت", + bankTotalSupply: "إجمالي معروض ECOin", + bankPubsSupply: "معروض ECOin لرصيد PUBs", + bankHoldingSupply: "معروض ECOin خارج UBI", + bankEcoinHours: "معادلة ECOin بالوقت (1 ECO)", bankHoursOfWork: 'ساعات', bankUnitMs: "مللي ثانية", bankUnitSeconds: "ثواني", bankUnitMinutes: "دقائق", bankUnitDays: "أيام", bankExchangeNoData: 'لا توجد بيانات متاحة', - bankInflation: 'تضخم ECOin (anual)', - bankInflationMonthly: 'تضخم ECOin (mensual)', + bankInflation: "تضخم ECOin (سنوي)", + bankInflationMonthly: "تضخم ECOin (شهري)", bankExchangeChartTitle: 'تطور قيمة ECOin عبر الزمن', bankExchangeChartValue: 'القيمة (ECO/ساعة)', bankExchangeChartSupply: 'العرض', bankExchangeChartInflation: 'التضخم %', bankExchangeChartEmpty: 'لا توجد عينات كافية بعد — ارجع لاحقًا', - bankCurrentSupply: 'المعروض الحالي من ECOin', + bankCurrentSupply: "معروض ECOin الحالي", bankingSyncStatus: 'حالة ECOin', bankingSyncStatusSynced: 'متزامن', - bankingSyncStatusOutdated: 'قديم', + bankingSyncStatusOutdated: "غير متزامن", statsTitle: 'الإحصائيات', statistics: "الإحصائيات", statsDescription: "اكتشف إحصائيات عن شبكتك.", ALLButton: "الكل", + RECENTButton: "الأخيرة", MINEButton: "خاصتي", TOMBSTONEButton: "شواهد القبور", statsCreatedAt: "أُنشئ في", @@ -2296,7 +2571,8 @@ module.exports = { statsYourOpinions: "الآراء", statsNetworkContent: "المحتوى", statsNetworkOpinions: "الآراء", - statsEcoWalletLabel: "محفظة ECOIN", + statsEcoWalletLabel: "محفظة ECOin", + walletAddressCopyHint: "انقر للتحديد ثم انسخ", statsEcoWalletNotConfigured: "غير مُعدّة!", statsBookmark: "العلامات المرجعية", statsEvent: "الأحداث", @@ -2318,6 +2594,8 @@ module.exports = { statsTransfer: "التحويلات", statsAiExchange: "الذكاء الاصطناعي", statsPUBs: 'PUBs', + statsSyncedPeers: "الأقران", + statsTotalLabel: "الإجمالي", statsPost: "المنشورات", statsSize: "الإجمالي (الحجم)", statsBlockchainSize: "البلوكتشين (الحجم)", @@ -2369,8 +2647,14 @@ module.exports = { aiPrompt: "قدّم ردًا إعلاميًا ودقيقًا.", aiConfiguration: "تعيين التوجيه", aiPromptUsed: "التوجيه", - aiClearHistory: "مسح سجل المحادثة", - aiServerError: "لم يتمكن الذكاء الاصطناعي من الرد. يرجى المحاولة مرة أخرى.", + aiClearHistory: "مسح السجل", + aiServerError: "لا أستطيع الإجابة بشكل صحيح. حاول مرة أخرى.", + aiStatusLoading: "يقوم 42 بتحميل النموذج. اسأل مرة أخرى بعد لحظة.", + aiThinking: "يفكر", + aiWaiting: "في الانتظار", + aiStatusMissingModel: "نموذج الذكاء الاصطناعي غير مثبت: الملف src/AI/oasis-42-1-chat.Q4_K_M.gguf مفقود.", + aiExportFineTuning: "تصدير Fine-Tuning", + aiFactsTitle: "بيانات من شبكتك", aiInputPlaceholder: "ما هو Oasis؟", typeAiExchange: "الذكاء الاصطناعي", aiApproveTrain: "إضافة للتدريب الجماعي", @@ -2399,7 +2683,7 @@ module.exports = { marketFilterDiscarded: "ملغى", marketFilterRecent: "الأخيرة", marketFilterMyBids: "العروض", - marketCreateButton: "إنشاء عنصر", + marketCreateButton: "بِع شيئًا", marketItemType: "النوع", marketItemTitle: "العنوان", marketItemAvailable: "الموعد النهائي", @@ -2427,7 +2711,7 @@ module.exports = { marketNoItems: "لا توجد عناصر متاحة بعد.", marketYourBid: "عرضك", marketCreateFormImageLabel: "رفع وسائط (الحد الأقصى: 50 ميغابايت)", - marketSearchPlaceholder: "ابحث عن عنوان أو وسوم", + marketSearchPlaceholder: "ابحث في السوق...", marketMinPriceLabel: "الحد الأدنى للسعر", marketMaxPriceLabel: "الحد الأقصى للسعر", marketSortRecent: "الأحدث", @@ -2493,8 +2777,7 @@ module.exports = { housingSetClosed: "اجعله غير متاح", housingCreateButton: "إنشاء مكان", housingUpdateButton: "تحديث", - housingDeleteButton: "حذف", - housingSearchPlaceholder: "ابحث عن أماكن...", + housingSearchPlaceholder: "ابحث في السكن...", housingSearchButton: "بحث", housingMinPrice: "أقل سعر", housingMaxPrice: "أعلى سعر", @@ -2572,12 +2855,11 @@ module.exports = { jobsExchangeSkill: "المهارة المطلوبة في المقابل", jobTimePartial: "دوام جزئي", jobTimeComplete: "دوام كامل", - jobsDeleteButton: "حذف", jobsUpdateButton: "تحديث", jobsFilterApplied: "مُتقدَّم", jobsAppliedTitle: "طلباتي", jobsAppliedBadge: "مُتقدَّم", - jobsSearchPlaceholder: "ابحث عن عنوان، وسوم، وصف...", + jobsSearchPlaceholder: "ابحث في الوظائف...", jobsMinSalaryLabel: "الحد الأدنى للراتب", jobsMaxSalaryLabel: "الحد الأقصى للراتب", jobsSortRecent: "الأحدث", @@ -2589,7 +2871,7 @@ module.exports = { jobSetOpen: "تعيين كمفتوح", jobsTagsLabel: "الوسوم", noJobsMatch: "لا توجد وظائف تطابق بحثك.", - industrySearchPlaceholder: "ابحث عن المصانع…", + industrySearchPlaceholder: "ابحث في الصناعة...", industryAllSectors: "جميع القطاعات", industryVisitButton: "زيارة الصناعة", industryAdvanceTo: "تعيين إلى", @@ -2663,7 +2945,6 @@ module.exports = { industryBuildStatus_FAILED: "فاشل", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "وظيفة تطابق سيرتك الذاتية", jobsBotMatchIntro: "وظيفة تطابق سيرتك الذاتية.", jobsBotMatchJob: "الوظيفة", @@ -2690,7 +2971,7 @@ module.exports = { industryRulesIntro: "تتيح Industry للسكان امتلاك وسائل الإنتاج جماعيًا: المصنع ورشة مملوكة للشبكة لا يملكها أحد بشكل خاص.", industryRulesSteward: "المؤسس هو القيّم — راعٍ لا مالك: لا يمكنه بيع المصنع أو خصخصته أو حله بمفرده.", industryRulesMembership: "تحدد سياسة العضوية كيفية الانضمام: مفتوح (فورًا)، بالتصويت (بقبول أصوات الأعضاء) أو بالدعوة فقط (يجب أن يدعوك عضو أولاً).", - industryRulesQuorum: "النصاب هو الحد الأدنى من الأعضاء الذين يجب أن يصوّتوا لتُحتسب القرار، حتى لا يقرر شخص واحد في مصنع صغير.", + industryRulesQuorum: "النصاب هو الحد الأدنى من الأعضاء الذين يجب أن يصوّتوا لتُحتسب القرار، حتى لا يقرر ساكن واحد في مصنع صغير.", industryRulesMajority: "الأغلبية هي نسبة الأصوات اللازمة (0.5 = نصف، 1 = إجماع)؛ يمر القرار عندما تبلغ أصوات نعم على الأقل max(النصاب، الأعضاء × الأغلبية).", industryRulesDecisions: "يصوّت الأعضاء لقبول الجدد والموافقة على عمليات البناء وحل المصنع؛ لا يمكن للقيّم تجاوز هذه القرارات الجماعية.", industryRulesBlueprints: "المخططات وصفات حرة (COPYLEFT) — المدخلات (مواد، ساعات عمل، مهارات) والمخرجات (سلعة مادية أو رقمية) — ويمكن لأي شخص نسخها.", @@ -2716,6 +2997,7 @@ module.exports = { modulesIndustryLabel: "الصناعة", modulesIndustryDescription: "وحدة لإدارة وسائل الإنتاج بشكل جماعي.", industryFilterAll: "الكل", + industryFilterRecent: "الأخيرة", industryFilterMember: "عضو", industryFilterBlueprints: "مخططات", industryFilterBuilds: "إنتاجات", @@ -2724,6 +3006,7 @@ module.exports = { industryFilterPaused: "موقوفة", industryFilterDissolved: "منحلة", industryAllTitle: "الصناعة", + industryRecentTitle: "المصانع الأخيرة", industryMemberTitle: "المصانع التي أنتمي إليها", industryMineTitle: "مصانعي", industryActiveTitle: "يعمل", @@ -2771,7 +3054,7 @@ module.exports = { industryPolicy_invite: "بالدعوة فقط", projectsTitle: "المشاريع", projectsDescription: "أنشئ ومُوّل وتابع مشاريع يقودها المجتمع في شبكتك.", - projectSearchPlaceholder: "ابحث عن المشاريع...", + projectSearchPlaceholder: "ابحث في المشاريع...", projectCreateProject: "إنشاء مشروع", projectCreateButton: "إنشاء مشروع", projectUpdateButton: "تحديث", @@ -2866,15 +3149,13 @@ module.exports = { modulesModuleName: "الاسم", modulesModuleDescription: "الوصف", modulesModuleStatus: "الحالة", - modulesTotalModulesLabel: "الوحدات المُحمَّلة", + modulesTotalModulesLabel: "الإجمالي", modulesEnabledModulesLabel: "مُفعَّلة", modulesDisabledModulesLabel: "مُعطَّلة", modulesInvitesLabel: "الدعوات", modulesInvitesDescription: "وحدة لإدارة وتطبيق رموز الدعوة.", modulesWalletLabel: "المحفظة", modulesWalletDescription: "وحدة لإدارة أصولك الرقمية (ECOin).", - modulesLegacyLabel: "القديم", - modulesLegacyDescription: "وحدة لإدارة مفتاحك السري (المفتاح الخاص) بسرعة وأمان.", modulesCipherLabel: "التشفير", modulesCipherDescription: "وحدة لتشفير وفك تشفير نصك بشكل متماثل (باستخدام كلمة مرور مشتركة).", modulesBookmarksLabel: "العلامات المرجعية", @@ -2936,20 +3217,23 @@ module.exports = { modulesProjectsLabel: "المشاريع", modulesProjectsDescription: "وحدة لاستكشاف وتمويل جماعي وإدارة المشاريع.", modulesBankingLabel: "المصرفية", - modulesBankingDescription: "وحدة لتحديد القيمة الحقيقية لـ ECOIN وتوزيع الدخل الأساسي الشامل باستخدام الخزينة المشتركة.", + modulesBankingDescription: "وحدة لإدارة الخزينة المشتركة: قيمة ECOIN والضرائب والدخل الأساسي الشامل.", modulesFavoritesLabel: "المفضلة", modulesFavoritesDescription: "وحدة لإدارة المحتوى المفضل لديك.", fileTooLargeTitle: "الملف كبير جدًا", fileTooLargeMessage: "يتجاوز الملف الحجم الأقصى المسموح به (50 ميغابايت). يرجى اختيار ملف أصغر.", goBack: "رجوع", + errorDismiss: "حسنًا", errorPageTitle: "حدث خطأ", + errorNoAllocation: "لم يتم العثور على مخصصات لهذه المطالبة.", + errorInvalidClaim: "لا يمكن المطالبة بهذه المخصصات.", visibilityLabel: "الرؤية", + clearnetPrivateConflict: "لا يمكن نشر محتوى خاص أو مخفي أو مغلق أو بدعوة فقط أو مدفوع على CLEARNET. اختر OASIS أو اجعله عامًا.", visibilityPublic: "عام", visibilityHidden: "مخفي", visibilityMakePublic: "جعله عامًا", visibilityMakeHidden: "إخفاؤه", invitesInhabitantsTitle: "السكان", - invitesInhabitantsFollow: "متابعة", aiNavPlaceholder: "صف ما تحتاجه...", aiNavDisabled: "تعطيل التنقل بالذكاء الاصطناعي.", aiNavResultsTitle: "اقتراحات AINav", @@ -2960,7 +3244,7 @@ module.exports = { modulesAINavDescription: "وحدة لإجراء استعلامات بلغة طبيعية حول محتوى الشبكة.", directConnectDescription: "اتصل مباشرة بقرين عن طريق إدخال عنوان IP والمنفذ والمفتاح العام. سيتم إضافة القرين كاتصال مُتابَع.", peerHost: "IP", - peerPort: "المنفذ (افتراضي: 8008)", + peerPort: "المنفذ", peerPublicKey: "المفتاح العام (@...ed25519)", connectAndFollow: "اتصال", modulesPresetTitle: "الإعدادات الشائعة", @@ -2969,11 +3253,11 @@ module.exports = { workflowsSet: "تطبيق مسار العمل", workflow_default: "الافتراضي", workflow_jobs: "العمل", - workflow_ruling: "الحُكم", - workflow_politics: "السياسة", workflow_social: "اجتماعي", workflow_business: "الأعمال", workflow_night: "ليل", + workflow_kids: "الأطفال", + workflow_activists: "الناشطون", workflow_mobile: "الهاتف", statsCarbonFootprintTitle: "البصمة الكربونية", statsCarbonTombstone: "بصمة الحذف", @@ -2985,7 +3269,7 @@ module.exports = { noComments: "لا توجد تعليقات بعد", mapsLabel: "خرائط", mapTitle: "خرائط", - mapDescription: "استكشف وأدر الخرائط غير المتصلة في شبكتك.", + mapDescription: "أدر الخرائط غير المتصلة وشاركها في شبكتك.", mapMineSectionTitle: "خرائطك", mapCreateSectionTitle: "إنشاء خريطة", mapUpdateSectionTitle: "تحديث الخريطة", @@ -3025,9 +3309,22 @@ module.exports = { markerImageLabel: "صورة العلامة", mapAddMarkerTitle: "إضافة علامة", mapAddMarkerButton: "إضافة علامة", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "مسح العلامة", mapApplyZoom: "تطبيق التكبير", - mapSearchPlaceholder: "البحث في الوصف، الوسوم، المؤلف...", + mapPlaceSearchPlaceholder: "ابحث عن مكان أو بلد...", + mapNoPlaces: "لم يتم العثور على أماكن.", + mapClusterTitle: "علامات هنا، انقر للتكبير", + mapDeleteMarker: "حذف العلامة", + mapZoomIn: "تكبير", + mapZoomOut: "تصغير", + mapFitMarkers: "ملاءمة كل العلامات", + mapPanLabel: "تحريك الخريطة", + mapCenterHere: "توسيط هنا", + mapNear: "بالقرب من", + mapPickedSpot: "النقطة المختارة", + mapPickHint: "انقر على الخريطة لاختيار نقطة.", + mapClearPick: "مسح", + mapSearchPlaceholder: "ابحث في خرائط...", mapSearchButton: "بحث", mapUpdatedAt: "محدّث", mapNoMatch: "لا توجد خرائط مطابقة لبحثك.", @@ -3038,54 +3335,625 @@ module.exports = { typeMapMarker: "علامة خريطة", modulesMapLabel: "خرائط", modulesMapDescription: "وحدة لإدارة ومشاركة الخرائط غير المتصلة.", - padsTitle: "الوسادات", - padTitle: "وسادة", - modulesPadsLabel: "الوسادات", + emergenciesTitle: "الطوارئ", + modulesEmergenciesLabel: "الطوارئ", + modulesEmergenciesDescription: "وحدة لإطلاق حالات الطوارئ وتأكيدها ومتابعتها في الشبكة.", + emergenciesDescription: "وحدة لإطلاق حالات الطوارئ وتأكيدها ومتابعتها في الشبكة.", + emergencyCreate: "إنشاء حالة طوارئ", + emergencyUpdate: "تحديث", + emergencyDelete: "حذف", + emergencyTitleLabel: "العنوان", + emergencyTitlePlaceholder: "ماذا يحدث؟", + emergencyTextLabel: "التفاصيل", + emergencyTextPlaceholder: "أين، منذ متى، ماذا نفعل...", + emergencyCategoryLabel: "الفئة", + emergencyCategoryWeather: "الطقس", + emergencyCategoryInfrastructure: "البنية التحتية", + emergencyCategoryHealth: "الصحة", + emergencyCategorySecurity: "الأمن", + emergencyCategoryLost: "المفقودات", + emergencyCategoryNeighborhood: "الحي", + emergencyExpiresLabel: "ينتهي خلال", + emergencyExpires1d: "يوم واحد", + emergencyExpires3d: "3 أيام", + emergencyExpires7d: "أسبوع واحد", + emergencyExpires30d: "شهر واحد", + emergencyExpiresNone: "غير محدد", + emergencyExpiresKeep: "الاحتفاظ بالتاريخ الحالي", + emergencyTagsLabel: "الوسوم", + emergencyTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + emergencyFilterAll: "الكل", + emergencyFilterMine: "تنبيهاتي", + emergencyFilterRecent: "الأحدث", + emergencySeverityUnverified: "غير مؤكد", + emergencySeverityLow: "منخفضة", + emergencySeverityMedium: "متوسطة", + emergencySeverityHigh: "عالية", + emergencyStatusActive: "نشط", + emergencyStatusResolved: "تم الحل", + emergencyStatusExpired: "منتهٍ", + emergencyConfirmations: "التأكيدات", + emergencyConfirmButton: "تأكيد", + emergencyConfirmed: "لقد أكدت حالة الطوارئ هذه", + emergencyResolveButton: "حل", + emergencyReopenButton: "إعادة فتح", + emergencyGenerateReport: "إنشاء التقرير", + emergencyUpdateEdited: "مُعدَّل", + emergencyLastUpdate: "آخر تحديث", + emergencyUpdatesTitle: "آخر التحديثات:", + emergencyNoUpdates: "لا تحديثات بعد.", + emergencyAddUpdate: "نشر تحديث", + emergencyUpdatePlaceholder: "ما الذي تغيّر؟", + emergencyNoItems: "لم يتم العثور على حالات طوارئ.", + emergencyExpiresAt: "ينتهي", + emergencyBannerLabel: "حالة طوارئ نشطة", + emergencyCreateSectionTitle: "إنشاء حالة طوارئ", + emergencyUpdateSectionTitle: "تحديث حالة الطوارئ", + emergencySearchPlaceholder: "البحث في الطوارئ...", + typeEmergency: "الطوارئ", + typeEmergencyConfirm: "تأكيد حالة طوارئ", + typeEmergencyUpdate: "تحديث حالة طوارئ", + statsEmergency: "الطوارئ", + statsEmergencyConfirm: "تأكيدات الطوارئ", + statsEmergencyUpdate: "تحديثات الطوارئ", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "حالة طوارئ تتابعها لديها تحديث.", + emergencyBotResolvedTitle: "تم حل حالة طوارئ تتابعها.", + dataKindEmergencies: "الطوارئ", + mailingTitle: "القوائم البريدية", + modulesMailingLabel: "القوائم البريدية", + modulesMailingDescription: "اكتشف وأدر القوائم البريدية في شبكتك.", + modulesPhoneLabel: "الهاتف", + modulesPhoneDescription: "وحدة للاتصال بالسكان الآخرين عبر مكالمات فيديو مشفّرة من طرف إلى طرف.", + mailingDescription: "اكتشف وأدر القوائم البريدية في شبكتك.", + mailingCreate: "إنشاء قائمة بريدية", + mailingUpdate: "تحديث", + mailingArchive: "أرشفة", + mailingActivate: "تفعيل", + mailingCreateSectionTitle: "إنشاء قائمة بريدية", + mailingUpdateSectionTitle: "تحديث القائمة البريدية", + mailingTitleLabel: "العنوان", + mailingTitlePlaceholder: "اسم القائمة البريدية", + mailingDescriptionLabel: "الوصف", + mailingDescriptionPlaceholder: "ما موضوع هذه القائمة؟", + mailingTypeLabel: "نوع القائمة البريدية", + mailingTypeOpen: "مفتوحة", + mailingTypeClosed: "مغلقة", + mailingStatusLabel: "الحالة", + mailingStatusActive: "نشطة", + mailingStatusArchived: "مؤرشفة", + mailingMembersLabel: "دعوة السكان", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "المشتركون", + mailingTagsLabel: "الوسوم", + mailingTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + mailingFilterAll: "الكل", + mailingFilterMine: "قوائمي", + mailingFilterSubscribed: "مشترك بها", + mailingFilterRecent: "الأحدث", + mailingHistoryDate: "التاريخ", + mailingHistoryThreads: "المواضيع", + mailingHistoryInhabitants: "السكان", + mailingHistorySearchPlaceholder: "البحث في السجل...", + mailingNoMessages: "لا توجد رسائل بعد.", + mailingNoItems: "لم يتم العثور على قوائم بريدية.", + mailingSearchPlaceholder: "البحث في القوائم البريدية...", + mailingWriteButton: "كتابة رسالة", + mailingWriteTitle: "الكتابة إلى القائمة", + mailingReplyTitle: "الرد على القائمة", + mailingSend: "إرسال", + mailingTextPlaceholder: "تصل رسالتك إلى جميع المشاركين في القائمة.", + mailingParticipants: "المشاركون", + inboxFilterMailing: "القوائم البريدية", + mailingReplyPlaceholder: "الرد على القائمة...", + mailingReplyToList: "الرد على القائمة", + typeMailingList: "قائمة بريدية", + statsMailingList: "القوائم البريدية", + mailingListLabel: "قائمة بريدية", + logisticsTitle: "اللوجستيات", + modulesLogisticsLabel: "اللوجستيات", + modulesLogisticsDescription: "اكتشف وأدر الخطط اللوجستية في شبكتك.", + logisticsDescription: "اكتشف وأدر الخطط اللوجستية في شبكتك.", + logisticsCreate: "إنشاء مسار", + logisticsUpdate: "تحديث", + logisticsClose: "إغلاق", + logisticsReopen: "إعادة فتح", + logisticsCreateSectionTitle: "إنشاء مسار", + logisticsUpdateSectionTitle: "تحديث المسار", + logisticsTitleLabel: "العنوان", + logisticsTitlePlaceholder: "رحلة إلى السوق، شاحنة متاحة...", + logisticsKindLabel: "نوع اللوجستيات", + logisticsKindTrip: "رحلة", + logisticsKindShipment: "شحنة", + logisticsKindTrips: "الرحلات", + logisticsKindShipments: "الشحنات", + logisticsModeLabel: "الوضع", + logisticsModeOffer: "أعرض", + logisticsModeRequest: "أبحث", + logisticsModeOffers: "العروض", + logisticsModeRequests: "الطلبات", + logisticsDescriptionLabel: "الوصف", + logisticsDescriptionPlaceholder: "نقطة اللقاء، ما تحمله، الشروط...", + logisticsOriginLabel: "نقطة الانطلاق", + logisticsOriginPlaceholder: "من أين يبدأ", + logisticsDestinationLabel: "الوجهة", + logisticsDestinationPlaceholder: "أين ينتهي", + logisticsDateLabel: "التاريخ والوقت", + logisticsRecurrenceLabel: "التكرار", + logisticsFrequencyLabel: "التكرار", + logisticsRecurrenceNone: "مرة واحدة", + logisticsRecurrenceDaily: "يومي", + logisticsRecurrenceWeekly: "أسبوعي", + logisticsRecurrenceMonthly: "شهري", + logisticsSeatsLabel: "المقاعد", + logisticsSizeLabel: "الحجم", + logisticsSizePlaceholder: "مثلاً 2m x 1m", + logisticsWeightLabel: "الوزن", + logisticsWeightPlaceholder: "مثلاً 40kg", + logisticsPriceFree: "مجاني", + logisticsPriceEco: "ECO", + logisticsPriceTime: "وقت", + logisticsPriceLabel: "السعر", + logisticsHours: "س", + logisticsOrderRefLabel: "رابط الطلب", + logisticsOrderRefPlaceholder: "/shops/... أو /market/... للطلب المراد تسليمه", + logisticsOrderLink: "الطلب المرتبط", + logisticsTagsLabel: "الوسوم", + logisticsTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + logisticsFilterAll: "الكل", + logisticsFilterMine: "مساراتي", + logisticsFilterBooked: "المحجوزة", + logisticsFilterRecent: "الأحدث", + logisticsFilterHistory: "السجل", + logisticsFilterTop: "الأفضل", + logisticsStatusOpen: "مفتوح", + logisticsStatusUpcoming: "القادمة", + logisticsStatusPast: "السابقة", + logisticsStatusClosed: "المغلقة", + logisticsZoneLabel: "المنطقة", + logisticsSearchPlaceholder: "البحث في اللوجستيات...", + logisticsNoItems: "لم يتم العثور على مسارات.", + logisticsBookingPending: "قيد الانتظار", + logisticsBookingConfirmed: "مؤكد", + logisticsBookingRejected: "مرفوض", + logisticsBookingCancelled: "ملغى", + logisticsBookingDelivered: "تم التسليم", + logisticsConfirm: "تأكيد", + logisticsReject: "رفض", + logisticsDeliver: "تسليم", + logisticsReceiptLabel: "إيصال", + logisticsReceiptPlaceholder: "ملاحظة التسليم (اختياري)", + logisticsCancelBooking: "إلغاء الحجز", + logisticsBookTitle: "احجز هذا المسار", + logisticsNotesLabel: "ملاحظات", + logisticsNotesPlaceholder: "ما يجب أن يعرفه المسؤول", + logisticsBook: "حجز", + logisticsBookingsTitle: "الحجوزات", + logisticsMyBookingTitle: "حجزك", + logisticsRatingsTitle: "التقييمات", + logisticsNoRatings: "لا توجد تقييمات بعد.", + logisticsRatingsWhenClosed: "تُفتح التقييمات بعد إغلاق المسار.", + logisticsScoreLabel: "الدرجة", + logisticsRatingPlaceholder: "كيف سارت الأمور؟", + logisticsRate: "تقييم", + typeLogisticsRoute: "مسار", + typeLogisticsRating: "تقييم مسار", + statsLogisticsRoute: "المسارات", + statsLogisticsRating: "تقييمات المسارات", + logisticsRouteLabel: "مسار", + podcastsTitle: "البودكاست", + modulesPodcastsLabel: "البودكاست", + modulesPodcastsDescription: "وحدة لنشر قنوات البودكاست.", + podcastsDescription: "وحدة لنشر قنوات البودكاست.", + podcastCreate: "إنشاء بودكاست", + podcastUpdate: "تحديث", + podcastDelete: "حذف", + podcastCreateSectionTitle: "إنشاء بودكاست", + podcastUpdateSectionTitle: "تحديث البودكاست", + podcastTitleLabel: "العنوان", + podcastTitlePlaceholder: "اسم القناة", + podcastDescriptionLabel: "الوصف", + podcastDescriptionPlaceholder: "ما موضوع هذا البودكاست؟", + podcastCategoryLabel: "الفئة", + podcastCategoryNews: "أخبار", + podcastCategoryMusic: "موسيقى", + podcastCategoryTalk: "حوار", + podcastCategoryEducation: "تعليم", + podcastCategoryTech: "تقنية", + podcastCategoryCulture: "ثقافة", + podcastCategoryCommunity: "مجتمع", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "أخرى", + podcastTagsLabel: "الوسوم", + podcastCoverLabel: "الغلاف (صورة أو فيديو)", + podcastEpisodeAudioLabel: "ملف صوتي أو فيديو (الحد الأقصى: 75MB)", + podcastTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + podcastFilterAll: "الكل", + podcastFilterMine: "قنواتي", + podcastFilterRecent: "الأحدث", + podcastFilterTop: "الأفضل", + podcastFilterViewers: "المستمعون", + podcastSearchPlaceholder: "البحث في البودكاست...", + podcastNoItems: "لم يتم العثور على بودكاست.", + podcastEpisodesLabel: "الحلقات", + podcastPlaysLabel: "المشاهدات", + podcastLastEpisode: "آخر حلقة", + podcastEpisodesTitle: "الحلقات", + podcastNoEpisodes: "لا توجد حلقات بعد.", + podcastEpisodeAdd: "إضافة حلقة", + podcastEpisodeCreateTitle: "نشر حلقة", + podcastEpisodeUpdateTitle: "تحديث الحلقة", + podcastEpisodeTitleLabel: "عنوان الحلقة", + podcastEpisodeTitlePlaceholder: "عنوان الحلقة", + podcastEpisodeDescriptionLabel: "ملاحظات", + podcastEpisodeDescriptionPlaceholder: "ما يحدث في هذه الحلقة، روابط، شكر...", + podcastEpisodePublish: "نشر", + podcastPlayed: "تم الاستماع", + podcastMarkPlayed: "وضع علامة كمستمَع", + typePodcast: "بودكاست", + typePodcastEpisode: "حلقة", + typePodcastOpinion: "رأي في الحلقة", + typePodcastPlay: "تشغيل الحلقة", + statsPodcast: "البودكاست", + statsPodcastEpisode: "الحلقات", + statsPodcastPlay: "مرات تشغيل الحلقات", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "مدونة تتابعها فيها مقال جديد.", + podcastBotEpisodeTitle: "بودكاست تتابعه لديه حلقة جديدة.", + podcastLabel: "بودكاست", + podcastEpisodeLabel: "حلقة", + campaignsTitle: "الحملات", + modulesCampaignsLabel: "الحملات", + modulesCampaignsDescription: "اكتشف وأدر الحملات في شبكتك.", + campaignsDescription: "اكتشف وأدر الحملات في شبكتك.", + campaignCreate: "إنشاء حملة", + campaignUpdate: "تحديث", + campaignDelete: "حذف", + campaignClose: "إغلاق", + campaignReopen: "إعادة فتح", + campaignCreateSectionTitle: "إنشاء حملة", + campaignUpdateSectionTitle: "تحديث الحملة", + campaignTitleLabel: "العنوان", + campaignTitlePlaceholder: "ماذا تريد أن تحقق؟", + campaignTextLabel: "النص", + campaignTextPlaceholder: "اشرح القضية، من يقرر وماذا تطلب...", + campaignCategoryLabel: "الفئة", + campaignCategoryEnvironment: "البيئة", + campaignCategoryRights: "الحقوق", + campaignCategoryHealth: "الصحة", + campaignCategoryEducation: "التعليم", + campaignCategoryInfrastructure: "البنية التحتية", + campaignCategoryCulture: "الثقافة", + campaignCategoryEconomy: "الاقتصاد", + campaignCategoryOther: "أخرى", + campaignGoalLabel: "هدف التوقيعات", + campaignDeadlineLabel: "الموعد النهائي", + campaignTagsLabel: "الوسوم", + campaignTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + campaignFilterAll: "الكل", + campaignFilterMine: "حملاتي", + campaignFilterSigned: "الموقّعة", + campaignFilterRecent: "الأحدث", + campaignFilterTop: "الأفضل", + campaignStatusOpen: "مفتوحة", + campaignStatusAchieved: "محققة", + campaignStatusUnachieved: "غير محقَّقة", + campaignStatusClosed: "مغلقة", + campaignSearchPlaceholder: "البحث في الحملات...", + campaignNoItems: "لم يتم العثور على حملات.", + campaignSignaturesLabel: "التوقيعات", + campaignSignaturesTitle: "الداعمون", + campaignNoSignatures: "لا توجد توقيعات بعد.", + campaignSignTitle: "وقّع هذه الحملة", + campaignSignPlaceholder: "لماذا تدعمها؟ (اختياري)", + campaignSign: "توقيع", + campaignSigned: "لقد وقّعت بالفعل!", + campaignNotSigned: "ما زال توقيعك ناقصًا", + campaignElevate: "رفعها إلى البرلمان", + campaignElevated: "رُفعت إلى البرلمان", + campaignUpdatesTitle: "التحديثات", + campaignNoUpdates: "لا توجد تحديثات بعد.", + campaignUpdatePlaceholder: "ماذا حدث؟", + campaignAddUpdate: "نشر تحديث", + campaignQrAlt: "رمز QR لتوقيع هذه الحملة", + campaignQrCaption: "امسح للتوقيع", + typeCampaign: "حملة", + typeCampaignSignature: "توقيع", + typeCampaignUpdate: "تحديث حملة", + statsCampaign: "الحملات", + statsCampaignSignature: "توقيعات الحملات", + statsCampaignUpdate: "تحديثات الحملات", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "حملة تتابعها لديها تحديث.", + campaignBotAchievedTitle: "حملة تتابعها حققت هدفها.", + campaignBotRaisedTitle: "حملة وقّعت عليها رُفعت إلى البرلمان.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "تغيّر مسار حجزته.", + logisticsBotClosedTitle: "أُغلق مسار حجزته.", + logisticsBotBookedTitle: "حجز شخص ما مسارك.", + logisticsBotCancelledTitle: "أُلغي حجز على مسارك.", + logisticsBotConfirmedTitle: "تأكد حجزك: الطرد في الطريق.", + logisticsBotRejectedTitle: "رُفض حجزك.", + logisticsBotDeliveredTitle: "وصل طردك.", + campaignLabel: "حملة", + campaignGenerateReport: "إنشاء التقرير", + logisticsGenerateReport: "إنشاء التقرير", + mailingGenerateReport: "إنشاء التقرير", + campaignUpdateEdited: "مُعدَّل", + mailingMessagesLabel: "الرسائل", + logisticsBookingsLabel: "الحجوزات", + backupTitle: "النسخ الاحتياطي", + modulesBackupLabel: "النسخ الاحتياطي", + modulesBackupDescription: "وحدة لتصدير مفاتيحك واستيرادها وإنشاء نسخ احتياطية مشفّرة.", + backupDescription: "وحدة لتصدير مفاتيحك واستيرادها وإنشاء نسخ احتياطية مشفّرة.", + backupTypeRecovery: "الاستعادة", + backupTypeKeys: "تصدير المفاتيح", + backupTypeFull: "نسخة كاملة", + backupTypeRestore: "استعادة النسخة", + backupTypeInstant: "فورية", + backupInstantDescription: "ملف .zip عادي يضم سجلك وملفاتك كاملة، بلا مفتاحك السري وبلا كلمة سر. سريع، لكن ", + backupInstantRisk: "من يحصل على الملف يقرأ كل ما فيه.", + backupKitPageDescription: "لا يمكن فتح حزمة الاستعادة إلا من هذا الجهاز.", + backupKitId: "معرّف Oasis", + backupKitDate: "تاريخ الإنشاء", + backupKitQrAlt: "رمز QR لمفتاحك السري", + backupKitStep1: "اطبعه أو انسخه يدوياً واحتفظ به في مكان آمن ودون اتصال.", + backupKitStep2: "للاستعادة، ثبّت Oasis على جهاز جديد واستورد هذا السر من النسخ الاحتياطي › المفاتيح.", + backupKitStep3: "أتلف هذه الصفحة إذا تعرّضت للكشف.", + backupKitPdfButton: "إنشاء PDF", + backupScopeLabel: "النطاق", + backupScopeAll: "كل شيء", + backupScopeMine: "محتواي فقط", + backupSinceLabel: "منذ", + backupEstimateTitle: "التقدير", + backupFileInfo: "سيتم تنزيل نسختك المشفّرة إلى جهازك (الاسم: oasis-backup-التاريخ.oasisbk)", + backupExportButton: "تنزيل النسخة", + backupRestorePassword: "اكتب كلمة المرور المستخدمة عند إنشاء النسخة", + backupRestoreButton: "استعادة النسخة", + backupRestoredLine: "تمت استعادة النسخة.", + backupRestoredMessages: "الرسائل المضافة", + backupRestoredSkipped: "موجودة مسبقاً", + backupRestoredBlobs: "الملفات المضافة", + backupRestoredState: "الإعدادات المستعادة", + backupRestoredFailed: "فشلت", + backupRestoredForked: "متباينة", + backupRestoreRunning: "جارٍ استعادة النسخة. تتحدث هذه الصفحة تلقائيًا؛ يمكنك متابعة استخدام Oasis في هذه الأثناء.", + backupRestoreElapsed: "المدة المنقضية", + backupRestoreFailedLine: "تعذّر استعادة النسخة.", + backupRestoreForkMine: "نشر هذا الجهاز بالفعل رسائل بهويتك غير موجودة في النسخة، لذا تعذّر تطبيقها فوقها. استعد النسخة على تثبيت نظيف مباشرة بعد استيراد مفاتيحك وقبل القيام بأي شيء آخر.", + verificationTitle: "التحقق", + verificationDescription: "افحص سلسلة خلاصتك، واكتشف التفرعات، وراجع الملفات اليتيمة أو المفقودة.", + verificationRun: "تشغيل التحقق", + verificationTotalMessages: "رسالة في السجل", + verificationFeed: "خلاصتك", + verificationMessages: "رسالة", + verificationLastSequence: "آخر تسلسل", + verificationGaps: "الفجوات", + verificationBrokenLinks: "روابط مكسورة", + verificationBadHashes: "تجزئات خاطئة", + verificationBadSignatures: "توقيعات خاطئة", + verificationDuplicates: "تسلسلات مكررة", + verificationForks: "التفرعات", + verificationBlobs: "الملفات", + verificationBlobsPresent: "موجودة", + verificationBlobsReferenced: "مُشار إليها", + verificationBlobsOrphan: "يتيمة", + verificationBlobsMissing: "مفقودة", + verificationBlobsMissingOwn: "ملفاتك المفقودة من هذا الجهاز", + verificationBlobsMissingOthers: "ملفات سكان آخرين لم تُنزَّل بعد", + verificationBlobsUnavailable: "تعذّر سرد الملفات على هذا الجهاز.", + wikiTitle: "ويكي", + modulesWikiLabel: "ويكي", + modulesWikiDescription: "وحدة لاكتشاف وإدارة الويكي.", + wikiDescription: "وحدة لاكتشاف وإدارة الويكي.", + wikiCreate: "إنشاء ويكي", + wikiUpdate: "تحديث", + wikiDelete: "حذف", + wikiEdit: "تعديل", + wikiTitleLabel: "العنوان", + wikiTitlePlaceholder: "عنوان الويكي...", + wikiBodyLabel: "المحتوى", + wikiBodyPlaceholder: "اربط ويكي أخرى عبر [[عنوان الويكي]] أو [[عنوان الويكي|نص مخصص]].\n\nاذكر ساكنًا عبر @id.\n\nضع وسمًا مثل #oasis.\n\nألصق روابط مثل https://solarnethub.com\n\nالسطر الفارغ يبدأ فقرة جديدة؛ وتُحفظ فواصل الأسطر المفردة.\n\nأرفق أي وسائط مدعومة وسيتم دمجها هنا.", + formatBarLabel: "التنسيق", + wikiStatusLabel: "نوع الويكي", + wikiStatusOpen: "مفتوحة", + wikiStatusClosed: "مغلقة", + wikiTagsLabel: "الوسوم", + wikiTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + wikiSummaryLabel: "ملخص التغيير", + wikiSummaryPlaceholder: "ما الذي تغيّر؟", + wikiFilterAll: "الكل", + wikiFilterMine: "صفحاتي", + wikiFilterRecent: "الأحدث", + wikiFilterLinked: "المرتبطة", + wikiChanges: "آخر التغييرات", + wikiNoPages: "لم يتم العثور على ويكي.", + wikiNoChanges: "لا تغييرات بعد.", + wikiHistory: "السجل", + wikiVersion: "الإصدار", + wikiVersions: "إصدارات", + wikiRestore: "استعادة", + wikiDiff: "التغييرات", + wikiViewVersion: "عرض", + wikiLinkedPages: "الصفحات المرتبطة", + wikiNoLinkedPages: "هذه الصفحة غير مرتبطة بأي صفحة أخرى بعد.", + wikiMissingLink: "الصفحة غير موجودة بعد", + wikiDuplicateTitle: "توجد صفحة بهذا العنوان بالفعل. غيّر العنوان لإنشاء صفحة مختلفة.", + wikiCreateSectionTitle: "إنشاء ويكي", + wikiUpdateSectionTitle: "تحديث الويكي", + wikiSearchPlaceholder: "البحث في الويكي...", + wikiCurrentVersion: "الإصدار الحالي", + wikiViewingVersion: "أنت تعرض إصدارًا قديمًا", + wikiBackToPage: "العودة إلى الصفحة", + wikiLinkedChip: "مرتبطة", + wikiRestoredSummary: "إصدار مستعاد", + wikiExportPdf: "إنشاء الصفحة", + wikiPreview: "معاينة", + tribeSectionWiki: "ويكي", + tribeWikiCreate: "إنشاء ويكي", + tribeWikiEmpty: "لا ويكي بعد.", + typeWiki: "ويكي", + padsTitle: "المستندات التعاونية", + padTitle: "مستند تعاوني", + modulesPadsLabel: "المستندات التعاونية", modulesPadsDescription: "وحدة لإدارة محررات النصوص التعاونية.", - padCreate: "إنشاء وسادة", + padCreate: "إنشاء مستند تعاوني", padUpdate: "تحديث", padDelete: "حذف", padTitleLabel: "العنوان", - padTitlePlaceholder: "أدخل عنوان الوسادة...", + padTitlePlaceholder: "أدخل عنوان المستند التعاوني...", padStatusLabel: "الحالة", + padTypeLabel: "نوع اللوح", padStatusOpen: "مفتوح", padStatusInviteOnly: "بدعوة فقط", padStatusClosed: "مغلق", padDeadlineLabel: "الموعد النهائي", padTagsLabel: "الوسوم", - padTagsPlaceholder: "وسم1، وسم2، ...", + padTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", padMembersLabel: "الأعضاء", padCreated: "تاريخ الإنشاء", - padStartEditing: "ابدأ التحرير!", + padStartEditing: "تحرير", padEditorPlaceholder: "ابدأ الكتابة...", padSubmitEntry: "إرسال", padNoEntries: "لا توجد مدخلات بعد.", - padAllSectionTitle: "جميع الوسادات", - padMineSectionTitle: "وساداتي", + padAllSectionTitle: "جميع المستندات التعاونية", + padMineSectionTitle: "مستنداتي التعاونية", padsDescription: "إدارة محرّرات النصوص التعاونية المشفّرة في شبكتك.", - padRecentSectionTitle: "الوسادات الأخيرة", - padOpenSectionTitle: "الوسادات المفتوحة", - padClosedSectionTitle: "الوسادات المغلقة", - padCreateSectionTitle: "إنشاء وسادة جديدة", - padUpdateSectionTitle: "تحديث الوسادة", - typePad: "وسادات", + roomsTitle: "الغرف", + roomsDescription: "اكتشف غرف مؤتمرات فيديو مشفّرة في شبكتك وانضم إليها.", + modulesRoomsLabel: "الغرف", + modulesRoomsDescription: "وحدة لاكتشاف غرف مؤتمرات الفيديو المشفّرة والانضمام إليها.", + roomFilterRecent: "الأخيرة", + roomFilterMine: "غرفي", + roomFilterAll: "الكل", + roomFilterLive: "مباشر", + roomFilterOpen: "مفتوحة", + roomFilterInvite: "بدعوة فقط", + roomFilterClosed: "مغلقة", + roomCreate: "إنشاء غرفة", + roomUpdate: "تحديث", + roomDelete: "حذف", + roomClose: "إغلاق", + roomCreateSectionTitle: "إنشاء غرفة", + roomUpdateSectionTitle: "تحديث الغرفة", + roomTitlePlaceholder: "عنوان الغرفة", + roomDescriptionLabel: "الوصف", + roomDescriptionPlaceholder: "عمّ ستتحدثون؟", + roomTypeLabel: "نوع الغرفة", + roomTagsLabel: "الوسوم", + roomStatusOpen: "مفتوحة", + roomStatusInviteOnly: "بدعوة فقط", + roomStatusClosed: "مغلقة", + roomParticipants: "المشاركون", + roomJoin: "انضمام", + roomLeave: "مغادرة", + roomFull: "الغرفة ممتلئة", + roomLiveChip: "مباشر", + roomMuted: "مكتوم", + roomRec: "تسجيل الصوت", + roomRecStop: "إيقاف التسجيل", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} دخل الغرفة", + roomEventLeft: "{name} غادر الغرفة", + roomEventRecStart: "{name} بدأ التسجيل", + roomEventRecStop: "{name} أوقف التسجيل", + roomEventMuted: "{name} كتم صوته", + roomEventUnmuted: "{name} ألغى كتم صوته", + roomEventHand: "{name} رفع يده", + roomRaiseHand: "ارفع يدك", + roomLowerHand: "أنزل يدك", + confirmActionTitle: "أكّد هذا الإجراء", + confirmActionButton: "تأكيد", + confirmJoinText: "أنت على وشك الانضمام باستخدام هذه الدعوة.", + confirmFollowText: "أنت على وشك دعم هذا الساكن.", + confirmSignText: "أنت على وشك توقيع هذه الحملة.", + confirmDownloadText: "أنت على وشك تنزيل هذا الملف إلى جهازك.", + roomRecordings: "التسجيلات", + roomClearNotices: "مسح", + roomLineSwitchboard: "مقسم", + roomLineDndError: "رقم هذه الغرفة لا يستقبل المكالمات.", + roomNumberHint: "اطلب هذا الرقم من الهاتف للانضمام إلى الغرفة.", + liveRefresh: "تحديث", + roomNobody: "لا أحد في الغرفة الآن.", + roomSomeone: "{n} في الغرفة الآن.", + roomYou: "أنت", + roomYouAreIn: "أنت في الداخل", + roomNobodyInside: "لا يوجد أحد في هذه الغرفة الآن.", + roomErrorFull: "الغرفة ممتلئة. يمكنك الانضمام عندما يغادر أحدهم.", + roomErrorUnreachable: "لا يمكن الوصول إلى الغرفة الآن. حاول لاحقًا.", + roomErrorBusy: "أنت بالفعل في مكالمة أو في غرفة أخرى.", + roomErrorClosed: "هذه الغرفة مغلقة.", + roomAccessDenied: "هذه الغرفة بدعوة فقط. تحتاج إلى دعوة للانضمام.", + roomsNoItems: "لم يُعثر على غرف.", + roomSearchPlaceholder: "ابحث في الغرف...", + roomInviteMode: "دعوة", + tribeSectionRooms: "الغرف", + tribeRoomCreate: "إنشاء غرفة", + tribeRoomsEmpty: "لا توجد غرف بعد.", + typeRoom: "غرف", + typeRoomMember: "مشارك في غرفة", + statsRoom: "الغرف", + statsRoomMember: "المشاركون في الغرف", + favoritesFilterRooms: "الغرف", + invitesRoomsTitle: "الغرف", + inviteCodeInvalid: "رمز الدعوة هذا غير صالح أو استُخدم من قبل.", + inviteAlreadyMember: "أنت منضم بالفعل. يمكنك فتحه من وحدته.", + actionFailed: "تعذّر تنفيذ ذلك. حاول مرة أخرى.", + publishTooShort: "منشورك قصير جدًا.", + walletSendFailed: "تعذّر إرسال الدفعة: {reason}", + pixeliaCoordsError: "هذه الإحداثيات خارج الشبكة.", + courtsErrTitle: "العنوان مطلوب.", + courtsErrRespondent: "حدّد ضد من تُرفع القضية.", + courtsErrRespondentId: "هذا ليس معرّف Oasis صالحًا.", + courtsErrMethod: "اختر طريقة حل القضية.", + courtsErrCaseNotFound: "تعذّر العثور على هذه القضية.", + courtsErrBrief: "اكتب ردّك.", + courtsErrStance: "اختر موقفك.", + courtsErrResult: "اكتب النتيجة.", + courtsErrTerms: "اكتب شروط الاتفاق.", + courtsErrMediator: "أضف وسيطًا واحدًا على الأقل.", + courtsErrSelfMediator: "لا يمكنك أن تكون وسيطًا في قضيتك.", + courtsErrJudge: "اختر قاضيًا.", + courtsErrSelfJudge: "لا يمكنك أن تكون قاضيًا في قضيتك.", + courtsErrVisibility: "اختر من يمكنه رؤية القضية.", + courtsErrNomination: "تعذّر العثور على هذا الترشيح.", + opinionAlreadyGiven: "لقد أبديت رأيك بالفعل.", + cipherPasswordShort: "يجب ألا تقل كلمة المرور عن 32 حرفًا.", + errorForbidden: "ليس لديك صلاحية الوصول إلى هذا.", + errorNotFound: "تعذّر العثور عليه.", + errorClosed: "هذا مغلق.", + errorInviteOnly: "هذا بدعوة فقط. تحتاج إلى دعوة.", + errorTooLong: "هذا النص طويل جدًا.", + errorRequired: "هناك معلومات مطلوبة ناقصة.", + errorInvalid: "بعض المعلومات غير صالحة.", + errorNotAuthor: "لا يمكن القيام بهذا إلا لصاحب المحتوى.", + panicRemoved: "تم حذف سلسلة الكتل الخاصة بك.", + padRecentSectionTitle: "المستندات التعاونية الأخيرة", + padOpenSectionTitle: "المستندات التعاونية المفتوحة", + padClosedSectionTitle: "المستندات التعاونية المغلقة", + padCreateSectionTitle: "إنشاء مستند تعاوني جديد", + padUpdateSectionTitle: "تحديث المستند التعاوني", + typePad: "مستندات تعاونية", padNew: "جديد", padAddFavorite: "إضافة إلى المفضلة", padRemoveFavorite: "إزالة من المفضلة", padClose: "إغلاق", padBackToEditor: "العودة إلى المحرر", - padSearchPlaceholder: "البحث عن الوسادات...", + padSearchPlaceholder: "ابحث في المستندات التعاونية...", modulesChatsLabel: "المحادثات", - modulesChatsDescription: "وحدة لاكتشاف وإدارة المحادثات المشفرة.", + modulesChatsDescription: "وحدة لاكتشاف وإدارة الدردشات.", typeChat: "محادثات", typeChatMessage: "رسالة محادثة", chatsTitle: "المحادثات", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "محادثاتك", + chatRecentTitle: "المحادثات الأخيرة", chatFavoritesTitle: "المفضلة", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "المحادثات المفتوحة", + chatClosedTitle: "المحادثات المغلقة", chatDescription: "الوصف", chatCategory: "الفئة", chatStatus: "الحالة", @@ -3100,34 +3968,172 @@ module.exports = { chatMessagesLabel: "الرسائل", chatThreadsLabel: "المواضيع", chatCreatedAt: "تم الإنشاء", - chatSearchPlaceholder: "بحث في المحادثات...", + chatSearchPlaceholder: "ابحث في المحادثات...", chatStatusOpen: "مفتوح", chatStatusInviteOnly: "بالدعوة فقط", chatStatusClosed: "مغلق", chatSendMessage: "إرسال", chatJumpLatest: "آخر رسالة", + chatReply: "رد", + chatReplyCancel: "إلغاء الرد", + chatPin: "تثبيت", + chatUnpin: "إلغاء التثبيت", + chatPinned: "المثبتة", chatPickChat: "اختر محادثة لفتحها", chatNoneYet: "لا توجد محادثة متاحة بعد.", activitySearchPlaceholder: "ابحث في النشاط...", trendingSearchPlaceholder: "ابحث في الرائج...", opinionsSearchPlaceholder: "ابحث في الآراء...", votesSearchPlaceholder: "ابحث في التصويتات...", - welcomeStepUxTitle: "اختر واجهتك", - welcomeStepUxText: "قرر شكل Oasis. يمكنك تغييره لاحقاً من الإعدادات.", - welcomeStepUxAction: "استخدم هذا العرض", + gamesSearchPlaceholder: "ابحث في الألعاب...", + schoolTitle: "المدرسة", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "تم تعديل صفحة ويكي تتابعها.", + wikiBotRestoredTitle: "تمت استعادة إصدار سابق من صفحة ويكي تتابعها.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "انضم طالب جديد إلى دورتك.", + schoolBotInvitedTitle: "تمت دعوتك إلى دورة.", + schoolBotAdmittedTitle: "تم قبولك في دورة.", + schoolBotCertificateTitle: "لقد حصلت على شهادة.", + schoolBotPassedTitle: "نجح طالب في دورتك.", + schoolBotLessonTitle: "تم نشر درس جديد.", + invitesSchoolTitle: "المدرسة", + schoolEnrollLabel: "الانضمام إلى الدورة", + schoolExamRetryLater: "يمكنك المحاولة مجددًا بعد 24 ساعة.", + schoolApproved: "ناجح", + schoolExamPassMark: "درجة النجاح (0-10)", + schoolExamPassed: "ناجح", + schoolExamPending: "قيد الانتظار", + schoolExamFailed: "راسب", + schoolExamAddQuestion: "إضافة سؤال", + schoolExamCreateQuestion: "إنشاء سؤال", + schoolExamOption: "خيار", + schoolExamCorrect: "الخيار الصحيح", + schoolExamPoints: "النقاط", + schoolMaterialTextPlaceholder: "ملاحظات لهذا الدرس...", + schoolMaterials: "المواد", + schoolAddMaterial: "إضافة مادة", + schoolNoMaterials: "لا توجد مواد بعد.", + bankYourSchoolBalance: "أرباحك من المدرسة", + schoolProgress: "التقدّم", + schoolProgressMark: "وضع علامة مكتمل", + schoolProgressDone: "مكتمل", + schoolExams: "الاختبارات", + schoolExamCreate: "إنشاء اختبار", + schoolExamTitlePlaceholder: "عنوان الاختبار", + schoolExamHelp: "سؤال واحد في كل سطر: سؤال؟ ; خيار ; الخيار الصحيح* ; خيار", + schoolExamQuestionsPlaceholder: "ما هو SSB؟ ; قاعدة بيانات ; بروتوكول P2P* ; متصفح", + schoolExamSubmit: "إرسال الإجابات", + schoolExamScore: "نتيجتك", + schoolNoScoresYet: "لا توجد نتائج بعد.", + schoolLessonUnit: "الوحدة / المرحلة", + schoolLessonUnitPlaceholder: "مثال: المرحلة 1", + schoolLessonOrder: "الترتيب", + profileClearnetSchoolLabel: "المدرسة", + schoolStartDate: "تاريخ البدء", + schoolSessionDate: "تاريخ الجلسة", + schoolCourseChat: "دردشة الدورة", + agendaFilterSchool: "المدرسة", + agendaFilterCampaigns: "الحملات", + agendaFilterLogistics: "اللوجستيات", + schoolCourseType: "نوع الدورة", + schoolTypeOpen: "مفتوح", + schoolTypePaid: "مدفوع", + schoolTypeInvite: "بالدعوة فقط", + schoolLessonLocked: "الدرس مشفّر — اطلب مفتاح الدورة من المعلم.", + schoolGrantAccess: "إرسال مفتاح الدورة", + schoolKeySent: "تم إرسال المفتاح", + schoolOngoing: "جارٍ", + schoolFilterApplied: "مسجّل", + schoolFilterOpen: "مفتوحة", + schoolFilterFavorites: "المفضلة", + schoolPendingPayment: "الدفع معلّق", + schoolPendingStudents: "تسجيلات معلّقة", + schoolViewBill: "عرض الفاتورة", + schoolPaymentNote: "التسجيل يُنشئ فاتورة ECO (تحويل). يتأكد تسجيلك بعد توقيع التحويل.", + schoolCloseCourse: "إغلاق الدورة", + schoolReopenCourse: "إعادة فتح الدورة", + schoolDescription: "علّم وتعلّم الدورات في شبكتك.", + schoolFilterAll: "الكل", + schoolFilterMine: "دوراتي", + schoolFilterTop: "الأفضل", + schoolFilterRecent: "الأحدث", + schoolCreateButton: "إنشاء دورة", + schoolCreateSectionTitle: "إنشاء دورة", + schoolUpdateSectionTitle: "تحديث الدورة", + schoolCourseTitlePlaceholder: "اسم دورتك", + schoolCourseDescriptionPlaceholder: "ماذا ستعلّم في هذه الدورة؟", + schoolCourseImage: "صورة الدورة (الحجم الأقصى: 75MB)", + schoolTags: "الوسوم", + schoolTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + schoolPrice: "السعر (ECO)", + schoolVisibility: "الظهور", + schoolVisibilityPublic: "عام", + schoolVisibilityInvite: "بدعوة", + schoolStatus: "الحالة", + schoolClosed: "مغلق", + schoolFree: "مجاني", + schoolCreate: "إنشاء الدورة", + schoolUpdate: "تحديث الدورة", + schoolSearchPlaceholder: "ابحث في المدرسة...", + schoolSortTitle: "العنوان", + schoolNoCourses: "لم يتم العثور على دورات.", + schoolStudents: "الطلاب", + schoolTeacher: "المعلم", + schoolCreatedAt: "تاريخ الإنشاء", + schoolLessons: "الدروس", + schoolNoLessons: "لا توجد دروس بعد.", + schoolEnrollToSee: "سجّل لرؤية دروس هذه الدورة.", + schoolEnrollButton: "التسجيل", + schoolUnenrollButton: "مغادرة الدورة", + schoolInviteRequired: "هذه الدورة بدعوة فقط.", + schoolTeacherPanel: "لوحة المعلم", + schoolStudentPanel: "لوحة الطالب", + schoolAddLesson: "إنشاء درس", + schoolLessonTitlePlaceholder: "عنوان الدرس", + schoolLessonTextPlaceholder: "صف المفاهيم التي يغطيها هذا الدرس...", + schoolInviteStudents: "دعوة الطلاب", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "دعوة", + schoolInvited: "المدعوون", + schoolIssueCertificate: "إنشاء شهادة", + schoolCertified: "معتمد", + schoolCertificateTextPlaceholder: "ملاحظة الشهادة (اختياري)", + schoolLessonOpen: "افتح الدرس", + schoolCertificatePdf: "PDF", + schoolCertificates: "الشهادات", + schoolNoCertificates: "لم يتم إصدار شهادات بعد.", + schoolEditButton: "تعديل", + schoolDeleteButton: "حذف", + typeSchool: "المدرسة", + schoolCourseLabel: "دورة", + emergencyLabel: "طوارئ", + wikiPageLabel: "ويكي", + statsSchoolCourse: "الدورات", + statsSchoolLesson: "الدروس", + statsSchoolEnroll: "التسجيلات", + statsSchoolCertificate: "شهادات المدرسة", + modulesSchoolLabel: "المدرسة", + modulesSchoolDescription: "وحدة لتبادل المعرفة.", + welcomeStepUxAction: "حفظ العرض", + welcomeStepWorkflowTitle: "ما الاستخدام الذي تفكر فيه لـ OASIS؟", + welcomeStepWorkflowText: "في Oasis وحدات كثيرة. اختر الإعداد الذي يناسبك ولن يبقى مفعّلًا إلا المفيد منها. يمكنك تعديله متى شئت من «الوحدات».", + welcomeStepWorkflowAction: "حفظ الإعداد", + welcomeWorkflow_full: "الافتراضي", chatRateLimitTitle: "رسائل كثيرة جدًا", chatRateLimitMessage: "لقد بلغت حد الرسائل الذي تقبله هذه الغرفة في الساعة. حاول لاحقًا.", chatMessagePlaceholder: "اكتب رسالتك...", chatNoMessages: "لا توجد رسائل بعد.", chatTitlePlaceholder: "عنوان المحادثة", chatDescriptionPlaceholder: "وصف المحادثة", - chatTagsPlaceholder: "وسم1، وسم2", + chatTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", chatImageLabel: "اختر ملف صورة (.jpeg, .jpg, .png, .gif)", chatAuthor: "المؤلف", chatAddFavorite: "إضافة للمفضلة", chatRemoveFavorite: "إزالة من المفضلة", chatPM: "رسالة", chatStatusLabel: "الحالة", + chatTypeLabel: "نوع الدردشة", chatCategoryLabel: "الفئة", gamesTitle: "الألعاب", gamesDescription: "اكتشف وألعب بعض الألعاب.", @@ -3158,22 +4164,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "اقلب عملة معدنية وراهن على الوجه أو الظهر. كم أنت محظوظ?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "بلياردو من منظور علوي. انقر للتصويب واضغط باستمرار لشحن القوة. أدخل جميع الكرات بأقل عدد من الضربات.", + gamesArtilleryTitle: "المدفعية", + gamesArtilleryDesc: "صوّب مدفعك، وخذ الرياح في الحسبان، وأصب الهدف. 5 جولات، ويفوز صاحب أقل عدد من الطلقات.", + gamesLabyrinthTitle: "المتاهة", + gamesLabyrinthDesc: "اهرب من المتاهة قبل أن تنفد حركاتك. كل مستوى أكبر وأصعب.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "كُل كل النقاط وتجنّب الأشباح. تتحرك الأشباح في الوقت الحقيقي — استمر في الحركة!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "البندول الصوتي", + gamesAudioPendulumDesc: "محاكي فيزياء فوضوية مع توليف صوتي في الوقت الحقيقي. تتحول السرعات الزاوية إلى ترددات، والقمم إلى ضربات طبول. لا توجد محاكاتان تبدوان متشابهتين.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "امزج المرئيات مباشرة على منصتين: اختر نمطاً لكل منهما، واضبط السرعة واللون والتكبير، وانتقل بينهما وأطلق الومضات على الإيقاع. انقر لضبط الإيقاع أو دع الميكروفون يقود النبض.", + gamesTetrisDesc: "المكعبات الساقطة الكلاسيكية. أكمل الصفوف لتسجل النقاط. إلى متى يمكنك الصمود؟", + gamesMinesweeperTitle: "كاسحة الألغام", + gamesMinesweeperDesc: "صيد الألغام الكلاسيكي. نظّف اللوحة لترتقي مستوى — شبكات أكبر وألغام أكثر ونقاط أعلى.", + gamesFilterScoring: "النقاط", + gamesHallOfFame: "قاعة المشاهير", + gamesHallPlayer: "اللاعب", + gamesHallScore: "النتيجة", + gamesNoScores: "لا توجد نتائج بعد.", calendarsTitle: "التقاويم", calendarTitle: "التقويم", modulesCalendarsLabel: "التقاويم", @@ -3181,15 +4191,15 @@ module.exports = { typeCalendar: "تقويمات", calendarCreate: "إنشاء تقويم", calendarUpdate: "تحديث", - calendarDelete: "حذف", calendarTitleLabel: "العنوان", calendarTitlePlaceholder: "عنوان التقويم...", calendarStatusLabel: "الحالة", + calendarTypeLabel: "نوع التقويم", calendarStatusOpen: "مفتوح", calendarStatusClosed: "مغلق", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "الموعد النهائي", calendarTagsLabel: "الوسوم", - calendarTagsPlaceholder: "وسم1، وسم2...", + calendarTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", calendarParticipantsLabel: "المشاركون", calendarCreated: "تم الإنشاء", calendarJoin: "انضمام", @@ -3200,11 +4210,11 @@ module.exports = { calendarNotePlaceholder: "إضافة ملاحظة...", calendarFirstDateLabel: "التاريخ", calendarFirstNoteLabel: "ملاحظات", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "الفاصل الزمني", calendarIntervalNone: "بدون تكرار", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "أسبوعيًا", + calendarIntervalMonthly: "شهريًا", + calendarIntervalYearly: "سنويًا", calendarFormDescription: "الوصف", calendarNoDates: "لا توجد تواريخ.", calendarNoNotes: "لا توجد ملاحظات.", @@ -3212,6 +4222,8 @@ module.exports = { calendarsDescription: "اكتشف وأدر التقويمات في شبكتك.", calendarMonthPrev: "← السابق", calendarMonthNext: "التالي →", + calendarMonthNames: "يناير,فبراير,مارس,أبريل,مايو,يونيو,يوليو,أغسطس,سبتمبر,أكتوبر,نوفمبر,ديسمبر", + calendarDayInitials: "ن,ث,ر,خ,ج,س,ح", calendarAllSectionTitle: "التقاويم", calendarRecentSectionTitle: "التقويمات الأحدث", calendarFavoritesSectionTitle: "المفضلة", @@ -3221,20 +4233,160 @@ module.exports = { calendarCreateSectionTitle: "إنشاء تقويم جديد", calendarUpdateSectionTitle: "تحديث التقويم", calendarAddFavorite: "إضافة إلى المفضلة", - calendarDeleteNote: "Delete", + calendarDeleteNote: "حذف", calendarRemoveFavorite: "إزالة من المفضلة", - calendarSearchPlaceholder: "البحث عن تقاويم...", + calendarSearchPlaceholder: "ابحث في التقويم...", calendarAddEntry: "إضافة إدخال", statsCalendar: "التقاويم", statsCalendarDate: "تواريخ التقويم", statsCalendarNote: "ملاحظات التقويم", chatAccessDenied: "ليس لديك حق الوصول إلى هذه المحادثة. اطلب دعوة للوصول إلى المحتوى.", - padAccessDenied: "ليس لديك حق الوصول إلى هذا الوسادة. اطلب دعوة للوصول إلى المحتوى.", + padAccessDenied: "ليس لديك حق الوصول إلى هذا المستند التعاوني. اطلب دعوة للوصول إلى المحتوى.", contentAccessDenied: "ليس لديك حق الوصول إلى هذا المحتوى.", blockAccessRestricted: "الوصول مقيد", tribeContentAccessDenied: "تم رفض الوصول", tribeContentAccessDeniedMsg: "هذا المحتوى ينتمي إلى قبيلة. يجب أن تكون عضواً للوصول إليه.", tribeViewTribes: "عرض القبائل", + tribeFilesEmpty: "لا توجد تورنتات بعد.", + filesLabel: "التورنت", + profileClearnetFilesLabel: "التورنت", + filesTitle: "التورنت", + filesDescription: "استكشف وأدِر التورنت في شبكتك.", + fileAllSectionTitle: "التورنت", + fileMineSectionTitle: "تورنتاتك", + fileRecentSectionTitle: "تورنتات حديثة", + fileTopSectionTitle: "أفضل التورنتات", + fileFavoritesSectionTitle: "المفضلة", + fileFilterAll: "الكل", + fileFilterMine: "خاصتي", + fileFilterRecent: "الحديثة", + fileFilterTop: "الأفضل", + fileFilterFavorites: "المفضلة", + fileCreateSectionTitle: "رفع تورنت", + fileUpdateSectionTitle: "تحديث التورنت", + fileCreateButton: "رفع تورنت", + fileUpdateButton: "تحديث", + fileAddFavoriteButton: "إضافة إلى المفضلة", + fileRemoveFavoriteButton: "إزالة من المفضلة", + fileFileLabel: "اختاري ملفًا (الحد الأقصى: 50 ميغابايت)", + calendarFilterRecent: "الأخيرة", + calendarFilterAll: "الكل", + calendarFilterMine: "خاصتي", + calendarFilterFavorites: "المفضلة", + calendarFilterOpen: "مفتوح", + calendarFilterClosed: "مغلق", + padFilterRecent: "الأخيرة", + padFilterAll: "الكل", + padFilterMine: "خاصتي", + padFilterOpen: "مفتوح", + padFilterClosed: "مغلق", + chatFilterRecent: "الأخيرة", + chatFilterAll: "الكل", + chatFilterMine: "خاصتي", + chatFilterFavorites: "المفضلة", + chatFilterOpen: "مفتوح", + chatFilterClosed: "مغلق", + licenseLabel: "الترخيص", + phoneTitle: "الهاتف", + phoneDescription: "أدر مكالمات الفيديو المشفّرة من طرف إلى طرف.", + phoneUnavailable: "المكالمات غير متاحة على هذا الجهاز.", + phoneCallButton: "اتصال", + profileVisibilityPhone: "رقم الهاتف", + phoneNumberUnknown: "لا أحد في شبكتك لديه الرقم {number}.", + phoneNumberChoose: "تحقق ممن تريد الاتصال به على الرقم {number}:", + phoneAnswer: "رد", + phoneSpeaker: "مكبر الصوت", + phoneEarpiece: "سماعة الأذن", + phoneReject: "رفض", + phoneHangup: "إنهاء المكالمة", + phoneMute: "كتم الصوت", + phoneUnmute: "إلغاء الكتم", + phoneCameraOn: "الكاميرا: تشغيل", + phoneCameraOff: "الكاميرا: إيقاف", + settingsPhoneVideo: "الفيديو", + settingsPhoneVideoOn: "مسموح في المكالمات والغرف", + settingsPhoneVideoOff: "صوت فقط", + phoneSilence: "عدم السماع", + phoneUnsilence: "السماع مجدداً", + phoneCalling: "جارٍ الاتصال بـ", + phoneIsCalling: "يتصل بك", + notifyNewPmFrom: "رسالة خاصة جديدة من", + notifyNewPamFrom: "رسالة PaM جديدة من", + notifyUpdateLabel: "تحديث", + updateInstalledLabel: "الإصدار المثبت", + updateAvailableLabel: "الإصدار المتاح", + updateChangesLabel: "التغييرات", + updateFilesLabel: "الملفات", + updateOriginLabel: "المصدر", + phoneRecording: "جارٍ تسجيل PaM لـ", + phoneLeaveMessage: "لم يرد أحد: اترك رسالتك بعد الصفارة.", + phoneLeaveMessageBusy: "في مكالمة أخرى: اترك رسالتك بعد الإشارة.", + phoneLeaveMessageUnreachable: "تعذّر الوصول: اترك رسالتك بعد الإشارة.", + phoneConnecting: "جارٍ الاتصال بـ", + phoneSendPam: "إرسال PaM", + phoneCancel: "إلغاء", + phoneSearchPlaceholder: "ابحث في الهاتف...", + phonePamTitle: "تسجيلات صوتية", + phoneNew: "جديد", + phoneDelete: "حذف", + phoneNoPams: "ليست لديك تسجيلات صوتية.", + phoneHistoryTitle: "السجل", + phoneLatestTitle: "آخر المكالمات", + phoneCreateButton: "مكالمة جديدة", + phoneLimitsHint: "حتى 7 من السكان لكل مكالمة؛ أكثر من ساكن واحد يجعلها مكالمة مشتركة.", + phoneErrorPub: "لا يمكن الاتصال بخادم pub.", + phoneJointCall: "مكالمة مشتركة", + phoneWith: "مع", + phoneResult: "النتيجة", + phoneWhen: "متى", + phoneDuration: "المدة", + phoneOutgoing: "صادرة", + phoneIncoming: "واردة", + phoneNoHistory: "لا توجد سجلات بعد.", + phoneOutcomeEnded: "تم الرد", + phoneOutcomeMissed: "فائتة", + phoneOutcomeRejected: "مرفوضة", + phoneOutcomeNoanswer: "لا رد", + phoneOutcomeCancelled: "ملغاة", + phoneOutcomePam: "تم إرسال PaM", + phoneOutcomeFailed: "فشلت", + phoneDialAdd: "إضافة", + phoneDialBack: "حذف", + phoneDialClear: "مسح", + phoneDialPlaceholder: "رقم أو معرّف Oasis", + phoneDialInhabitants: "السكان", + phoneDialRemove: "إزالة", + phoneOwnNumber: "هذا رقمك أنت.", + phoneSettingsDesc: "اختر من يمكنه الاتصال بك.", + phoneDnd: "عدم الإزعاج", + phoneOpen: "مفتوح", + phoneRecipientRefuses: "الساكن الذي تحاول الاتصال به لا يقبل المكالمات.", + phoneRefusedNotice: "هؤلاء السكان لا يقبلون المكالمات ولم يتم الاتصال بهم:", + fileEmptyError: "الملف فارغ. اختاري ملفًا آخر.", + fileTitleLabel: "العنوان", + fileTitlePlaceholder: "العنوان", + fileDescriptionLabel: "الوصف", + fileDescriptionPlaceholder: "الوصف", + fileTagsLabel: "الوسوم", + fileTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", + fileSizeLabel: "الحجم", + fileDownloadButton: "تحميل", + fileDetailsButton: "التفاصيل", + fileNoFile: "لا يوجد ملف تورنت", + fileSearchPlaceholder: "ابحث في التورنت...", + fileSearchButton: "بحث", + fileSortRecent: "الأحدث", + fileSortOldest: "الأقدم", + fileSortTop: "الأكثر تصويتاً", + fileNoMatch: "لا توجد تورنتات مطابقة.", + fileUpdatedAt: "تم التحديث", + typeFile: "التورنت", + modulesFilesLabel: "الملفات", + modulesFilesDescription: "وحدة لاكتشاف وإدارة الملفات.", + tribeSectionFiles: "التورنت", + fileDescription: "الوصف", + fileDownload: "تنزيل", + fileTypeLabel: "النوع", torrentsTitle: "التورنت", torrentsDescription: "استكشف وأدِر التورنت في شبكتك.", torrentAllSectionTitle: "التورنت", @@ -3262,10 +4414,33 @@ module.exports = { torrentTagsLabel: "الوسوم", torrentTagsPlaceholder: "أدخل الوسوم مفصولة بفواصل", torrentSizeLabel: "الحجم", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "إنشاء تورنت", + torrentFromContentMissing: "الملف غير متاح بعد على هذه العقدة. حاولي لاحقًا.", + torrentDownloads: "التنزيلات", + torrentFetchOasis: "تنزيل داخل Oasis", + torrentSaveFile: "حفظ الملف", + torrentSeeds: "بذور", + seedsLabel: "البذور", + seedAction: "دعم", + torrentSourceFile: "الملف", + torrentStateQueued: "في الانتظار", + torrentStateProbing: "البحث عن بذور", + torrentStateDownloading: "جارٍ التنزيل", + torrentStateDone: "اكتمل", + torrentStateFailed: "فشل", + torrentStateNoSource: "لا توجد بذور متصلة بعد، في الانتظار", + torrentStateCancelled: "أُلغي", + torrentNoDownloads: "لا توجد تنزيلات بعد.", + torrentOriginalSection: "الأصل", + torrentCancel: "إلغاء", + torrentDownloadButton: "تحميل", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "التفاصيل", torrentNoFile: "لا يوجد ملف تورنت", noTorrents: "لم يتم العثور على تورنتات.", - torrentSearchPlaceholder: "بحث بالعنوان، الوسوم، المؤلف...", + noFiles: "لم يتم العثور على تورنتات.", + torrentSearchPlaceholder: "ابحث في التورنت...", torrentSearchButton: "بحث", torrentSortRecent: "الأحدث", torrentSortOldest: "الأقدم", @@ -3273,10 +4448,12 @@ module.exports = { torrentNoMatch: "لا توجد تورنتات مطابقة.", torrentUpdatedAt: "تم التحديث", statsTorrent: "التورنت", + statsFile: "الملفات", typeTorrent: "التورنت", modulesTorrentsLabel: "التورنت", modulesTorrentsDescription: "وحدة لاكتشاف وإدارة التورنت.", favoritesFilterTorrents: "التورنت", + favoritesFilterFiles: "الملفات", favoritesFilterMarket: "السوق", favoritesFilterEvents: "الفعاليات", favoritesFilterForum: "المنتدى", @@ -3290,15 +4467,17 @@ module.exports = { favoritesFilterShopProducts: "منتجات المتجر", tribeSectionTorrents: "التورنت", tribeCreateTorrent: "رفع تورنت", + tribeCreateFile: "رفع ملف", settingsWishTitle: "الرغبة", - settingsWishDesc: "قم بتكوين رغبة الأفاتار الخاص بك.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "يحدد كيفية وصولك إلى كل المحتوى وتجربتك في شبكة Oasis.", + settingsWishWhole: "الكون المتعدد", + settingsWishMutuals: "الدعم المتبادل فقط", + settingsWishOnlyLan: "الشبكة المحلية فقط", + settingsWishLocal: "محلي", settingsPmVisibilityTitle: "الرسائل الخاصة", settingsPmVisibilityDesc: "قم بتكوين مستوى التعرض للرسائل الخاصة في الشبكة.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "الكون المتعدد", + settingsPmVisibilityMutuals: "الدعم المتبادل فقط", pmMutualNotice: "الإرسال حاجز UX. الاستقبال فلتر انتباه.", inhabitantsPendingFollowsTitle: "طلبات الدعم المعلقة", inhabitantsPendingAccept: "قبول", @@ -3342,7 +4521,7 @@ module.exports = { tribeGovNoRevocations: "لا يوجد أي إلغاء بعد", tribeGovNoHistorical: "لا يوجد أي سجل بعد", logsTitle: "السجل", - logsDescription: "سجل تجربتك في الشبكة.", + logsDescription: "احتفظ بمذكرات خاصة لتجاربك.", logsViewTitle: "السجل", logsColumnType: "النوع", logsView: "عرض", @@ -3351,7 +4530,7 @@ module.exports = { logsCreateTitle: "إنشاء إدخال", logsWriteButton: "اكتب", logsTextPlaceholder: "صف تجاربك...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "مساعد الذكاء الاصطناعي", logsModeManual: "يدوي", logsModeAI: "ذكاء", logsColumnLog: "السجل", @@ -3370,23 +4549,24 @@ module.exports = { logsExportOne: "تصدير السجل", logsViewDetails: "عرض التفاصيل", logsGenerateButton: "توليد نص", - logsSearchText: "ابحث في السجلات...", + logsSearchText: "ابحث في السجل...", logsSearchAnyType: "أي نوع", logsSearchButton: "بحث", logsEmpty: "لا توجد إدخالات بعد.", + logsNothingNew: "لا توجد إجراءات جديدة لتسجيلها.", modulesLogsLabel: "السجل", - modulesLogsDescription: "وحدة لتسجيل تجاربك (عبر مساعد ذكاء اصطناعي).", + modulesLogsDescription: "وحدة للاحتفاظ بمذكرات خاصة لتجاربك.", statsLogsTitle: "السجل", typeLog: "سجل", larpTitle: "L.A.R.P.", larpDescription: "طبقة لعب أدوار حية للتجربة التعاونية.", larpNoHousesMatch: "لا توجد بيوت تطابق بحثك.", - larpSearchPlaceholder: "ابحث في البيوت...", + larpSearchPlaceholder: "ابحث في L.A.R.P....", larpAllHouses: "البيوت:", larpFilterRuling: "يحكم", larpFilterHouses: "البيوت", - larpFilterRules: "FAQ", + larpFilterRules: "الأسئلة الشائعة", larpRulesTitle: "أسئلة شائعة عن L.A.R.P.", larpRulesEntryText: "يبدأ كل ساكن في ACADEMIA افتراضياً. من ACADEMIA، اختر بيتاً من لوحة «الانضمام إلى بيت»: ستفتح اختبار الدخول الخاص به. أجب عن الأسئلة العشرة وأرسلها. إذا تجاوزت الحد (7/10) فستُقبل آلياً في ذلك البيت؛ وإلا تُرفض وتبقى في ACADEMIA. في كل الأحوال يسجّل النظام OasisID الخاص بك ودورة المحاولة، ويمنع محاولة جديدة حتى انتهاء فترة الانتظار وهي 30 دورة.", larpRulesGovernanceText: "خلال دورته، يحمل البيت الحاكم شارة «يحكم» وهو الوحيد الذي يُعرض حائطه علنياً ضمن تبويب «يحكم».", @@ -3397,118 +4577,117 @@ module.exports = { larpPostPlaceholder: "ماذا يريد هذا البيت أن يقول؟", larpPostSubmit: "انشر", larpCycleLabel: "الدورة:", - audioBackToBcs: "Back to BCS", + audioBackToBcs: "العودة إلى BCS", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeCompositionEmpty: "This audio does not include a stored blockchain composition.", - audioTranscodeDetailDescription: "Decode the embedded payload and the original blockchain composition map.", - audioTranscodeDetailTitle: "Transcode", - audioTranscodeStegoEmpty: "(none)", - audioTranscodeStegoMessage: "TEXT", - audioTranscodeStegoNotFound: "No steganographic payload could be decoded from this audio.", - audioTranscodeStegoOasisId: "By", - audioTranscodeStegoTimestamp: "Generated at", + audioTranscodeButton: "تحويل الترميز", + audioTranscodeCompositionEmpty: "لا يتضمن هذا الملف الصوتي تأليفًا محفوظًا لسلسلة الكتل.", + audioTranscodeDetailDescription: "فك ترميز الحمولة المضمنة وخريطة التأليف الأصلية لسلسلة الكتل.", + audioTranscodeDetailTitle: "تحويل الترميز", + audioTranscodeStegoEmpty: "(لا شيء)", + audioTranscodeStegoMessage: "النص", + audioTranscodeStegoNotFound: "تعذّر فك ترميز أي حمولة مخفية من هذا الملف الصوتي.", + audioTranscodeStegoTimestamp: "تاريخ الإنشاء", audioTranscodeStegoUnknown: "—", - bankExchangeEcoTaxAnnual: "ECOin Taxes (annual)", - bankExchangeEcoTaxMonthly: "ECOin Taxes (monthly)", - bankOverviewYourTaxes: "Your taxes", - bankRulesAlpha: "Alpha (max share of pub balance per epoch)", + bankExchangeEcoTaxAnnual: "ضرائب ECOin (سنويًا)", + bankExchangeEcoTaxMonthly: "ضرائب ECOin (شهريًا)", + bankOverviewYourTaxes: "إجمالي الضرائب", + bankRulesAlpha: "Alpha (الحصة القصوى من رصيد الـ pub لكل حقبة)", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", - bankRulesArchTaxRate: "Rate", - bankRulesArchTaxTitle: "ARCH Tax", - bankRulesCapPerEpoch: "Cap per epoch (absolute)", - bankRulesCapUser: "Cap per user per epoch", - bankRulesCarbonFactor: "Carbon factor", + bankRulesArchTaxNote: "تعكس ضريبة ARCH التكلفة طويلة الأمد لتوسيع أرشيف الشبكة وصيانته: كلما كانت بياناتك أقدم في الشبكة، زادت حصتك من تكلفة الصيانة.", + bankRulesArchTaxRate: "المعدل", + bankRulesArchTaxTitle: "ضريبة ARCH", + bankRulesCapPerEpoch: "الحد الأقصى لكل حقبة (مطلق)", + bankRulesCapUser: "الحد الأقصى لكل مستخدم في كل حقبة", + bankRulesCarbonFactor: "معامل الكربون", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", - bankRulesChipTitle: "ECO Tax chip color band", + bankRulesChipNote: "تُلوَّن شارة كل عنصر بالنسبة إلى أكبر رسالة رُصدت في الشبكة، وتُلطَّف بعدد السكان النشطين — فالشبكة الأكبر توزّع الحمل وتدفع مزيدًا من العناصر إلى النطاق الأخضر.", + bankRulesChipTitle: "نطاق لون شارة ضريبة ECO", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", - bankRulesEcoTaxRate: "Rate", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesEpochKind: "Epoch granularity", - bankRulesFloor: "Floor per user (gross)", - bankRulesGraceDays: "Claim grace period", + bankRulesEcoTaxNote: "تُخصم ضريبة ECO من الدخل الأساسي الإجمالي عند حساب تخصيص الحقبة. ولا يُعاد توزيع وحدات ECO المخصومة في هذه الحقبة — بل تبقى في رصيد الـ pub وتغذّي صندوق الحقبة التالية عبر حدود reserveMin / alpha.", + bankRulesEcoTaxRate: "المعدل", + bankRulesEcoTaxTitle: "ضريبة ECO", + bankRulesEpochKind: "دقة الحقبة", + bankRulesFloor: "الحد الأدنى لكل مستخدم (إجمالي)", + bankRulesGraceDays: "فترة السماح للمطالبة", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", - bankRulesKarmaTitle: "Karma (user engagement score)", + bankRulesKarmaNote: "تُشتق الكارما من أفعالك (المنشورات، والأصوات، والمشاركة في البرلمان/المحاكم، وغير ذلك) مع معامل تناقص بمرور الوقت، مطروحًا منها غرامات الكربون التي تولّدها تغذيتك.", + bankRulesKarmaTitle: "الكارما (درجة تفاعل المستخدم)", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", - bankRulesPoolTitle: "Monthly pool", - bankRulesReserveMin: "Reserve minimum (kept in pub balance)", + bankRulesPoolTitle: "الصندوق الشهري", + bankRulesReserveMin: "الحد الأدنى للاحتياطي (يبقى في رصيد الـ pub)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", - bankRulesShareTitle: "Per-user share", + bankRulesShareTitle: "حصة كل مستخدم", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankRulesWMax: "Maximum weight (w_max)", - bankRulesWMin: "Minimum weight (w_min)", - bankTaxes: "Taxes", - bankTaxesAnnualEcoin: "ECO Tax (annual)", - bankTaxesArchTaxAnnual: "ARCH Tax (annual)", - bankTaxesArchTaxFirstBlock: "Your first block age", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxLifetime: "ARCH Tax (lifetime)", - bankTaxesArchTaxMonthly: "ARCH Tax (monthly)", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesArchTaxRate: "Rate", - bankTaxesArchTaxTitle: "ARCH Tax", - bankTaxesArchTaxUserAmount: "Your ARCH tax (price to return)", - bankTaxesEcoTaxLifetime: "ECO Tax (lifetime)", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesLookupAuthor: "Author", - bankTaxesLookupButton: "Inspect", - bankTaxesLookupCarbon: "Carbon footprint", - bankTaxesLookupEcoin: "ECO Tax", - bankTaxesLookupNotFound: "No block found in your local feed with that ID.", - bankTaxesLookupPlaceholder: "Block ID (e.g. %abc...=.sha256)", - bankTaxesLookupSize: "Size", - bankTaxesLookupTitle: "Inspect a block", - bankTaxesLookupType: "Type", - bankTaxesMonthlyEcoin: "ECO Tax (monthly)", - bankTaxesRate: "Rate", - bankTaxesSpan: "Sampling span", - bankTaxesTotalAnnual: "Total (annual)", - bankTaxesTotalBlocks: "Total blocks", - bankTaxesTotalBytes: "Total bytes", - bankTaxesTotalCarbon: "Total carbon", - bankTaxesTotalEcoin: "Total ECO tax (lifetime)", - bankTaxesTotalLifetime: "Total (lifetime)", - bankTaxesTotalMonthly: "Total (monthly)", - bankTaxesTypesApply: "Set my taxes", - bankTaxesTypesLabel: "Select which taxes you want to pay", - bankTaxesUserAmount: "Your ECO tax (price to return)", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - blockchainBackToBlockexplorer: "Back to blockexplorer", - blockchainBlockNotAvailable: "This block is not available in your local feed. It may belong to a peer you are not yet replicating from.", - ecoTaxLabel: "ECO Tax", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - melodyAllEmpty: "No BCS compositions from other inhabitants yet.", - melodyByLabel: "By", - melodyNoteUnavailable: "block not in your local feed", - melodyUploadBcsButton: "Publish", - melodyUploadBcsNameNote: "The audio will be published with the auto-generated name", - melodyUploadBcsTitle: "Publish", - melodyUploadStegoLabel: "Hidden message (steganography)", - melodyUploadStegoMaxLabel: "max 280 characters", - melodyUploadStegoPlaceholder: "Optional.", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", - statsPad: "Pads", - statsPadEntry: "Pad entries", - statsTombstoneEmpty: "No tombstones in the network yet.", - typeGameScore: "GAME SCORES", + bankRulesWMax: "الوزن الأقصى (w_max)", + bankRulesWMin: "الوزن الأدنى (w_min)", + bankTaxes: "الضرائب", + bankTaxesAnnualEcoin: "ضريبة ECO (سنويًا)", + bankTaxesArchTaxAnnual: "ضريبة ARCH (سنويًا)", + bankTaxesArchTaxFirstBlock: "عمر أول كتلة لك", + bankTaxesArchTaxFootprintNote: "تعكس ضريبة ARCH تكلفة توسيع أرشيف الشبكة وصيانته. وتُحسب من الفارق الزمني بين أول كتلة نشرتها وأحدث كتلة في الشبكة — فكلما طال بقاء تاريخك في الأرشيف، زادت حصتك من تكلفة الصيانة.", + bankTaxesArchTaxLifetime: "ضريبة ARCH (مدى الحياة)", + bankTaxesArchTaxMonthly: "ضريبة ARCH (شهريًا)", + bankTaxesArchTaxNoteBold: "خفض ضريبة ARCH مباشرةً", + bankTaxesArchTaxNoteIntro: "تُخصم ضريبة ARCH الخاصة بك من الفائض الذي تولّده الشبكة فوق دخلك الأساسي؛ وتُحدَّد القيمة الأساسية للدخل الأساسي كحد أدنى ثابت. ولا تُخصم الضريبة أبدًا من المبلغ الثابت الذي يستحقه كل ساكن بوصفه دخلًا أساسيًا. تُغذّي الأموال المخصومة خوارزمية إعادة توزيع الثروة وتُعاد إلى سكان آخرين عبر مطالباتهم بالدخل الأساسي. ومن المرجح أن يكون لدى السكان الأحدث، ولدى المتفرغين لمهام الأرشفة والنسخ والصيانة، مهمة مخصصة للحفاظ على سلامة أرشيف الشبكة نيابةً عنك. أو قد يكرّسون أنفسهم لـ", + bankTaxesArchTaxNoteOutro: " عبر اتفاقيات التقليم، وإزالة تكرار البيانات الزائدة، وبنية صيانة مشتركة، وقد يطلب إجماع الشبكة مساهمتهم المستمرة.", + bankTaxesArchTaxRate: "المعدل", + bankTaxesArchTaxTitle: "ضريبة ARCH", + bankTaxesArchTaxUserAmount: "ضريبة ARCH الخاصة بك (المبلغ الواجب إرجاعه)", + bankTaxesEcoTaxLifetime: "ضريبة ECO (مدى الحياة)", + bankTaxesEcoTaxTitle: "ضريبة ECO", + bankTaxesLookupAuthor: "المؤلف", + bankTaxesLookupButton: "فحص", + bankTaxesLookupCarbon: "البصمة الكربونية", + bankTaxesLookupEcoin: "ضريبة ECO", + bankTaxesLookupNotFound: "لم يُعثر في تغذيتك المحلية على كتلة بهذا المعرّف.", + bankTaxesLookupPlaceholder: "معرّف الكتلة (مثل %abc...=.sha256)", + bankTaxesLookupSize: "الحجم", + bankTaxesLookupTitle: "فحص كتلة", + bankTaxesLookupType: "النوع", + bankTaxesMonthlyEcoin: "ضريبة ECO (شهريًا)", + bankTaxesRate: "المعدل", + bankTaxesSpan: "فترة أخذ العينات", + bankTaxesTotalAnnual: "الإجمالي (سنويًا)", + bankTaxesTotalBlocks: "إجمالي الكتل", + bankTaxesTotalBytes: "إجمالي البايتات", + bankTaxesTotalCarbon: "إجمالي الكربون", + bankTaxesTotalEcoin: "إجمالي ضريبة ECO (مدى الحياة)", + bankTaxesTotalLifetime: "الإجمالي (مدى الحياة)", + bankTaxesTotalMonthly: "الإجمالي (شهريًا)", + bankTaxesTypesApply: "تعيين ضرائبي", + bankTaxesTypesLabel: "اختر الضرائب التي تريد دفعها", + bankTaxesUserAmount: "ضريبة ECO الخاصة بك (المبلغ الواجب إرجاعه)", + bankTaxesUserNoteChips: "تتغير شارة ضريبة ECO المعروضة بجانب كل عنصر لونها (أخضر / أصفر / أحمر) بحسب حجم العنصر مقارنةً بأكبر كتلة شوهدت في الشبكة، مع تلطيفها بعدد السكان النشطين — فالشبكة الشمسية الأكبر توزّع الحمل وتدفع مزيدًا من العناصر إلى النطاق الأخضر.", + bankTaxesUserNoteOtherParams: "تُشتق ضريبة ECO حاليًا من البصمة الكربونية لكل كتلة، لكنها قد تتضمن مع الوقت معايير أخرى — الطاقة المستهلكة في النسخ، والتخزين المكرر بين العقد، وعرض نطاق الملفات، والتكلفة الحسابية لفك التشفير، وبصمة تعدين المعاملات المرتبطة، أو أي حمل آخر قابل للقياس تتفق الشبكة على تقييمه.", + blockchainBackToBlockexplorer: "العودة إلى مستكشف الكتل", + blockchainBlockNotAvailable: "هذه الكتلة غير متوفرة في تغذيتك المحلية. قد تنتمي إلى عقدة لا تنسخ منها بعد.", + ecoTaxLabel: "ضريبة ECO", + gamesHallDate: "التاريخ", + gamesNewRecord: "رقم قياسي جديد!", + indexingRefreshNote: "تُحدَّث هذه الصفحة كل 10 ثوانٍ.", + indexingMessage: "يقوم Oasis بمزامنة شبكة العقد الموزعة. يرجى الانتظار.", + indexingTitle: "جارٍ المزامنة", + melodyAllEmpty: "لا توجد بعد مؤلفات BCS من سكان آخرين.", + melodyNoteUnavailable: "الكتلة ليست في تغذيتك المحلية", + melodyUploadBcsButton: "نشر", + melodyUploadBcsNameNote: "سيُنشر الملف الصوتي بالاسم المُنشأ تلقائيًا", + melodyUploadBcsTitle: "نشر", + melodyUploadStegoLabel: "رسالة مخفية (إخفاء المعلومات)", + melodyUploadStegoMaxLabel: "280 حرفًا كحد أقصى", + melodyUploadStegoPlaceholder: "اختياري.", + padsNoItems: "لم يتم العثور على مستندات تعاونية.", + padVersionHistory: "سجل الإصدارات", + padAttributionView: "حسب المؤلفة", + padVersionView: "عرض", + statsChat: "المحادثات", + statsChatMessage: "رسائل المحادثة", + statsGameScore: "الألعاب", + statsWiki: "صفحات الويكي", + statsPad: "المستندات التعاونية", + statsPadEntry: "مدخلات المستندات التعاونية", + statsTombstoneEmpty: "لا توجد عناصر محذوفة (tombstones) في الشبكة بعد.", + typeGameScore: "نتائج الألعاب", bankTaxesLookupNote: "أدخل معرف كتلة للبحث عنه في خلاصتك المحلية.", bankTaxesUserNoteChipsClick: "انقر على أي شريحة لفحص كتلتها في blockexplorer.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3523,6 +4702,14 @@ module.exports = { settingsReplicationTitle: "النسخ", settingsReplicationDesc: "اضبط عدد القفزات التي يتبعها نظيرك انطلاقًا من خلاصتك عند نسخ المحتوى.", settingsReplicationHopsLabel: "قفزات", + settingsBlobCacheTitle: "ذاكرة الوسائط المؤقتة", + settingsBlobCacheDesc: "يُحفظ محتوى السكان الآخرين على هذا الجهاز. اختر المساحة التي يمكنه شغلها.", + settingsBlobCacheUsage: "الاستخدام الحالي", + settingsBlobCacheFiles: "ملفات", + settingsBlobCacheLimit: "الحد", + settingsBlobCacheUnlimited: "بلا حد", + settingsBlobCacheCleanNow: "تنظيف الآن", + settingsBlobCacheCleaned: "تمت إزالة {n} ملفات وتحرير {size}.", aiApproveTagsLabel: "وسوم (مفصولة بفواصل)", aiApproveTagsPlaceholder: "مثلاً oasis، حوكمة، بيئة", aiApproveRatingLabel: "التقييم", @@ -3550,8 +4737,6 @@ module.exports = { larpInviteBannerHint: "شارك هذا الرمز مع شخص في ACADEMIA. ينتهي خلال 30 دورة أو بعد استخدام واحد.", larpTestRankingTitle: "النقاط لكل بيت", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "رمز دعوة بيت", - invitesHouseJoinButton: "الانضمام إلى البيت", larpLastAttemptTitle: "آخر محاولاتك", larpLastAttemptHouse: "البيت", larpLastAttemptWhen: "متى", @@ -3569,7 +4754,7 @@ module.exports = { larpBadgeYou: "أنت", larpBadgeRuling: "يحكم", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "وحدة طبقة لعب الأدوار الحية في Oasis (البيوت التسعة).", + modulesLarpDescription: "وحدة لطبقة لعب الأدوار الحيّ.", larpProfileQ1: "عند مواجهة مشكلة معقدة، ما الذي تفعله أولاً؟", larpProfileQ1O1: "التحدث مع الآخرين للوصول إلى توافق", larpProfileQ1O2: "بناء نموذج أولي لاختباره", @@ -3643,7 +4828,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "اختر وضع التنقل UX الذي تريده لواجهتك.", uxModeMenus: "الكتل", - uxModeAINav: "AI", + uxModeChats: "المحادثات", + uxModeFeed: "التدوين المصغر", + feedTrendingTitle: "الوسوم الرائجة", + feedActiveTitle: "السكان النشطون", + cnBrandSub: "حر · P2P · اتحادي", + cnSyncedPeers: "العقد المتزامنة", + cnLicense: "الرخصة", + cnNotAccessible: "المحتوى غير متاح في هذه اللحظة.", + cnUntitled: "بلا عنوان", + cnSearchPlaceholder: "بحث…", + cnHubTitle: "مركز Clearnet", + cnHubDescription: "محتوى عام يشاركه سكان شبكة Oasis هذه.", + cnHubEmpty: "لم تتم مشاركة أي محتوى عام على Clearnet بعد.", + cnPublicContent: "المحتوى", + cnTribesContent: "القبائل", + cnNetworkContent: "الشبكة", + cnCategoryEmpty: "لا يوجد محتوى في هذه الفئة.", + cnInhabitantEmpty: "لم ينشر هذا الساكن أي محتوى على Clearnet بعد.", + cnPrevPage: "← السابق", + cnNextPage: "التالي →", + cnPageOf: "صفحة {page} من {pages}", + cnMoreInfo: "مزيد من المعلومات ←", + cnOpenCourse: "دورة مفتوحة", + cnAuction: "مزاد", + cnExchange: "تبادل", + cnRssDescription: "نشرها سكان هذا المركز", + cnKindAudio: "صوت", + cnKindBlog: "مدونة", + cnKindBookmark: "إشارة مرجعية", + cnKindDocument: "مستند", + cnKindEvent: "حدث", + cnKindFeed: "تغذية", + cnKindImage: "صورة", + cnKindJob: "وظيفة", + cnKindMarket: "سوق", + cnKindPodcast: "بودكاست", + cnKindProject: "مشروع", + cnKindCourse: "دورة", + cnKindShop: "متجر", + cnKindTorrent: "تورنت", + cnKindFile: "ملف", + cnKindVideo: "فيديو", + cnKindWiki: "ويكي", + cnKindRoom: "غرفة", + cnKindMap: "خريطة", + uxModeAINav: "الذكاء الاصطناعي", } }; diff --git a/src/client/assets/translations/oasis_ca.js b/src/client/assets/translations/oasis_ca.js new file mode 100644 index 0000000..0129a4e --- /dev/null +++ b/src/client/assets/translations/oasis_ca.js @@ -0,0 +1,4880 @@ +const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); +module.exports = { + ca: { + shopOrderStatusPaid: "Pagament rebut", + shopOrderStatusReceived: "Rebut", + shopOrderMarkPaid: "Marcar pagament rebut", + shopOrderConfirmReceived: "Confirmar rebut", + shopMyOrdersTitle: "Les meves comandes", + shopPurchasesButton: "Compres", + shopMyOrdersDescription: "Les teves comandes de compra.", + shopMyOrdersEmpty: "Encara no tens compres.", + shopOrderPmSeller: "PM Venedor", + shopOrderSeller: "Venedor", + shopOrderInvoice: "Factura", + shopOrderInvoiceConcept: "Factura", + shopOrderStatusPending: "Pendent", + shopOrderStatusAccepted: "Acceptat", + shopOrderStatusRejected: "Rebutjat", + shopOrderStatusShipped: "Enviat", + shopOrderAccept: "Acceptar", + shopOrderReject: "Rebutjar", + shopOrderMarkShipped: "Marcar enviat", + shopOrderPmBuyer: "PM Comprador", + shopOrderContractConcept: "Comanda de botiga", + shopOrdersPending: "Comandes pendents", + all: "Tots", + mine: "Meus", + recent: "Recents", + anonymous: "Anònim", + unnamed: "Anònim", + authorLabel: "Autoria", + delete: "Eliminar", + save: "Desar", + vote: "Votar", + url: "URL", + price: "Preu", + priority: "Prioritat", + severity: "Gravetat", + organizer: "Organitzador", + edited: "editat", + isPublic: "Públic", + searchIsPublicLabel: "Públic", + privacyPrivate: "PRIVAT", + privacyPublic: "PÚBLIC", + notFound: "No trobat", + no_results: "Sense dades", + searchButton: "Cercar", + mapZoomLabel: "Zoom", + modulesTitle: "Mòduls", + viewJson: "Veure JSON", + matchScore: "Puntuació de coincidència", + preferencesLabel: "Preferències", + inhabitantProfileTitle: "Habitant", + contentWarningLabel: "Avís de contingut", + invalidPost: "Contingut no vàlid", + invalidPostHint: "Aquest missatge té el text buit o no vàlid.", + spreadChron: "Rèpliques", + spreaded: "Replicat", + spreadContentUnavailable: "Contingut encara no disponible (replicació pendent)", + filteredByTag: "Filtrat per etiqueta", + filteredByTagTitle: "Filtrat per etiqueta", + feedPublishedSuccess: "Publicació creada correctament!", + feedMastodonFailed: "S'ha publicat a Oasis, però no s'ha pogut enviar a Mastodon.", + tribeFeedSent: "Missatge enviat correctament!", + tribeContentEncrypted: "Contingut xifrat", + tribeTorrentsEmpty: "Encara no hi ha torrents.", + torrentDescription: "Descripció", + torrentDownload: "Baixar", + chatInviteMode: "Convidar", + padInviteMode: "Convidar", + noInviteMode: "Sense mode d'invitació", + noDeadline: "Sense data límit", + noStatus: "Sense estat", + noSeverity: "Sense gravetat", + calendarDeleteDate: "Eliminar data", + calendarIntervalUntil: "Fins a", + bookmarkLastVisit: "Darrera visita", + profileClearnetBookmarksLabel: "Marcadors", + invitesPort: "Port", + peerHostValidation: "IPv4 vàlida (p. ex. 192.168.1.100) o nom d'amfitrió (p. ex. pub.solarnethub.com)", + peerPortValidation: "Port 1-65535", + peerKeyValidation: "Clau pública SSB ed25519 (@<44 caràcters base64>=.ed25519)", + inboxJobUnsubscribedTitle: "Baixa de la teva oferta de feina", + inboxProjectFollowedTitle: "Nou seguidor del teu projecte", + inboxProjectUnfollowedTitle: "Ha deixat de seguir el teu projecte", + pmHasFollowedYourProject: "ha seguit el teu projecte", + pmHasUnfollowedYourProject: "ha deixat de seguir el teu projecte", + pmHasUnsubscribedFromYourJobOffer: "s'ha donat de baixa de la teva oferta de feina", + marketDeleteButton: "Eliminar", + marketNoBids: "Encara no s'han fet licitacions.", + marketAuctionBidAmount: "Import de la licitació", + marketAuctionBidTime: "Hora de la licitació", + marketAuctionUser: "Licitador", + parliamentActorInPower: "TRIBU GOVERNANT", + parliamentWinningCandidature: "Candidatura guanyadora", + projectBounty: "Recompensa", + projectFollowing: "SEGUINT", + projectBackerAuthor: "Mecenes", + projectBackerDate: "Data", + projectConfirmTransferButton: "Confirmar transferència", + projectPendingTransfersTitle: "Transferències pendents", + transfersUpdateSectionTitle: "Actualitzar transferència", + taskUnassignedFrom: "Desassignat de", + taskDescriptionPlaceholder: "Descripció de la tasca", + exportPasswordLabel: "Contrasenya d'exportació", + importPasswordPlaceholder: "Introdueix la contrasenya", + searchCasesPlaceholder: "Cercar a Tribunals...", + shopBuyDeliveryAddressPlaceholder: "Adreça de lliurament", + shopBuyNotesPlaceholder: "Notes", + bankTaxesUserNoteBold: "reduir directament l'impost ECO", + bankTaxesUserNoteIntro: "El teu impost ECO es dedueix de l'excedent que la xarxa genera per sobre de la teva RBU; el valor base de la RBU es fixa com a mínim inamovible. L'impost mai no es dedueix de la quantitat fixa que correspon a cada habitant com a RBU. Els fons deduïts alimenten l'algorisme de redistribució de la riquesa i es canalitzen de tornada cap a altres habitants mitjançant les seves reclamacions de RBU. Aquests altres habitants que reben més RBU per als seus projectes probablement tindran alguna tasca dedicada a ajudar-te a reduir el teu impost ECO. O poden dedicar-se a ", + bankTaxesUserNoteOutro: ", i el consens de la xarxa pot requerir-ne la contribució contínua.", + courtsViewDetails: "Veure", + courtsViewDetailsShort: "Detalls", + courtsThDetails: "Detalls", + courtsDetailsAnswersTitle: "Respostes", + courtsDetailsEvidenceTitle: "Proves", + courtsDetailsSettlementsTitle: "Acords", + courtsDetailsVerdictTitle: "Veredicte", + courtsDetailsNotPublic: "No públic", + courtsEvidenceSideAccuser: "Proves de l'acusador", + courtsEvidenceSideRespondent: "Proves de la defensa", + courtsEvidenceSideUnknown: "Desconegut", + courtsJudgeNotAssigned: "-", + courtsSupportCase: "Donar suport al cas", + courtsSettlementAcceptBtn: "Acceptar l'acord", + courtsSupportCount: "Suports", + reportsSetStatus: "Canviar l'estat", + reportsStatusClosed: "TANCAT", + statsAvgPerInhabitant: "Mitjana per habitant", + statsCarbonMaxAnnual: "Estimació màx. anual", + statsCarbonNetwork: "Total de la xarxa", + statsCarbonOfEstMax: "de la capacitat màx. estimada", + statsCarbonYearProgress: "de l'any transcorregut", + statsNetworkTitle: "Xarxa", + statsStorageTitle: "Emmagatzematge", + statsEcoTaxTitle: "Taxa ECO", + statsAccountTitle: "Compte", + statsAveragesTitle: "Mitjanes", + statsCarbonOfNetwork: "del total de la xarxa", + statsCarbonUser: "La teva petjada", + statsContentCountColumn: "Quantitat", + statsContentTypeColumn: "Tipus", + statsMsgsPerDay: "Missatges/dia (històric)", + statsMyShare: "La teva part de la xarxa", + statsNetworkSpan: "Antiguitat de la xarxa", + statsTombstoneRatioLabel: "Proporció d'esborrats", + statsTopTagsTitle: "Etiquetes principals", + statsTopTypesTitle: "Tipus de contingut principals", + statsTotalMsgs: "Missatges totals", + extended: "Multivers", + extendedDescription: [ + "Quan dones suport a algú, pots baixar les publicacions dels habitants a qui dona suport, i aquestes publicacions apareixeran aquí, ordenades per la més recent.", + ], + popular: "Destacades", + popularDescription: [ + "Publicacions d'habitants de la teva xarxa, ", + strong("ordenades per suports"), + ". Selecciona el període de temps per obtenir una llista.", + ], + week: "Setmana", + month: "Mes", + year: "Any", + latest: "Darrers", + latestDescription: [ + strong("Publicacions"), + " teves i d'habitants a qui dones suport, ordenades per les més recents.", + ], + topics: "Temes", + topicsDescription: [ + strong("Temes"), + " teus i d'habitants a qui dones suport, ordenats pels més recents.", + ], + summaries: "Resums", + summariesDescription: [ + strong("Temes i alguns comentaris"), + " teus i d'habitants a qui dones suport, ordenats pels més recents.", + ], + threads: "Fils", + threadsDescription: [ + strong("Publicacions que tenen comentaris"), + " d'habitants a qui dones suport i del teu multivers, ordenades per les més recents.", + ], + profile: "Avatar", + inhabitants: "Habitants", + peersReplicatedFeeds: "Replicats", + peersTorColumn: "TOR", + peersTorYes: "Accessible per Tor", + peersTorNo: "No accessible per Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Versió", + graphos: "Graphos", + graphosDescription: "Mapa interactiu de la xarxa al teu voltant.", + graphosFederation: "Federació", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Inabastables", + graphosFedLinks: "Enllaços mutus", + graphosFedIsolated: "Aïllats", + graphosFedNoData: "Sense dades", + graphosFedMutual: "Mutu", + graphosFedOneWay: "En un sentit", + graphosFedTransport: "Xarxa", + graphosFedWith: "Federat amb", + graphosZoom: "Amplia", + graphosFedInhabitants: "Habitants", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "El teu node encara no coneix cap PUB.", + graphosViewingGraphOf: "Veient", + graphosBackToMine: "← La meva xarxa", + graphosShowing: "Mostrant", + graphosYou: "Tu", + graphosTotalNodes: "Nodes totals", + mentions: "Mencions", + mentionsDescription: [ + strong("Tots els missatges que et @mencionen"), + ".", + ], + noMentions: "Encara no has rebut @mencions.", + mentionsSearchPlaceholder: "Cercar a Mencions...", + privateReminders: "RECORDATORIS", + inboxFilterPms: "PMs", + inboxFilterNotifications: "Notificacions", + inboxMarkRead: "Marcar com a llegit", + inboxMarkUnread: "Marcar com a no llegit", + inboxUnreadChip: "No llegit", + inboxMarkAllRead: "Marcar-ho tot com a llegit", + inboxDeleteShown: "Esborrar-ho tot", + inboxArchive: "Arxivar", + inboxUnarchive: "Desarxivar", + inboxFilterArchived: "Arxivats", + inboxEarlierMessages: "missatges anteriors", + inboxSortThread: "Per fil", + inboxSortRecent: "Més recents primer", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Notificacions de l'Inbox", + settingsInboxBotsDesc: "Tria quins bots et poden avisar a l'Inbox. Els bots desmarcats s'amaguen i els seus avisos no es compten.", + inboxFiltersLabel: "Filtres:", + inboxToggleToMutuals: "Canviar a mutus", + inboxToggleToWhole: "Canviar a tots", + privateDate: "Data", + peers: "Nodes", + searchDescription: "Descripció", + searchFromLabel: "Des de", + searchToLabel: "Fins a", + searchQueryLabel: "Cerca", + searchPerPageLabel: "Resultats", + settings: "Configuració", + settingsDescription: "Configura l'entorn de la teva xarxa.", + continueReading: "Continuar llegint", + moreComments: "més comentaris", + readThread: "llegir la resta del fil", + pixeliaTitle: "Pixelia", + pixeliaDescription: "Dibuixa píxels en una graella i colabor-ART amb habitants de la teva xarxa.", + melodyTitle: "Melody", + melodyDescription: "Reprodueix la melodia de la teva blockchain — cada bloc es converteix en una nota.", + melodyEmpty: "Encara no hi ha notes — la teva blockchain encara no ha generat cap bloc.", + melodyTotalBlocks: "Notes", + melodyFilterAll: "TOTES", + melodyRegenerate: "Regenerar", + melodyDownload: "Baixar BCS", + profileContentSectionTitle: "Contingut de l'Avatar", + profileContentHelp: "Tria quins mòduls es mostraran al teu avatar.", + profileSensorsHelp: "Mètriques opcionals que es mostren al teu avatar.", + profileClearnetHelp: "Mòduls que es poden consultar des de fora d'Oasis.", + profileHubAll: "Tot", + footerLicenseLabel: "Llicència", + profileVisibilityFediverse: "Multivers", + contributorsTitle: "Contribucions", + colorLabel: "Tria un color", + paintButton: "Dibuixar!", + totalPixels: "Total de píxels", + modules: "Mòduls", + modulesViewTitle: "Mòduls", + modulesViewDescription: "Defineix el teu entorn ideal activant i desactivant mòduls.", + inbox: "Bústia", + inboxSearchPlaceholder: "Cercar a la Bústia...", + multiverse: "Multivers", + fediverse: "Multivers", + fediverseDescription: "Gestiona els teus altres comptes del multivers, incloent-hi enviar i rebre contingut.", + fediverseStatus: "Estat", + fediverseDisconnected: "Multivers desconnectat. Comprova %LINK% o l'estat de la connexió.", + fediverseSettingsTitle: "Multivers", + fediverseInstanceLabel: "Adreça", + fediverseTokenLabel: "Testimoni d'accés", + fediverseConnect: "Connectar", + fediverseConnectedAs: "Connectat com a", + nextcloudServerLabel: "Servidor", + nextcloudUserLabel: "Usuari", + nextcloudFolderLabel: "Carpeta", + nextcloudUploadButton: "Pujar", + nextcloudFileLabel: "Tria un fitxer", + fileExternalLink: "Enllaç extern", + nextcloudErrConnect: "No s'ha pogut arribar a Nextcloud amb aquestes dades.", + nextcloudErrUpload: "La pujada a Nextcloud ha fallat.", + nextcloudErrShare: "Nextcloud no ha donat cap enllaç públic per al fitxer.", + fediverseDisconnect: "Desconnectar", + fediverseEmptyLink: "la teva configuració", + fediverseComposePlaceholder: "Què estàs pensant?", + fediverseReplyPlaceholder: "Escriu la teva resposta…", + fediverseAttach: "Adjuntar multimèdia", + fediversePublish: "Publicar", + fediverseReply: "Respondre", + fediverseBoosted: "ha impulsat", + fediverseNoPosts: "La teva cronologia és buida.", + fediverseTimeline: "Cronologies", + fediverseFollowers: "Seguidors", + fediverseFollowing: "Seguint", + fediversePosts: "Publicacions", + fediverseJoined: "S'hi va unir", + fediverseRefresh: "Actualitzar", + fediverseWrite: "Escriure", + fediversePreview: "Previsualització", + fediverseManage: "Gestionar", + fediverseError: "No s'ha pogut contactar amb Mastodon.", + fediverseErrInstance: "URL d'instància no vàlid.", + fediverseErrAuth: "Testimoni no vàlid o caducat.", + fediverseErrConnect: "No s'ha pogut connectar amb la instància.", + fediverseErrToken: "Cal un testimoni.", + fediverseErrPublic: "No disponible en mode públic.", + fediverseErrFetch: "No s'ha pogut carregar la cronologia.", + fediverseErrPost: "No s'ha pogut publicar.", + fediverseErrMedia: "No s'ha pogut pujar el fitxer.", + fediverseErrEmpty: "Escriu alguna cosa o adjunta un fitxer.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Número de telèfon", + telegramSendCode: "Enviar codi", + telegramCodeHelp: "Introdueix el codi que Telegram t'acaba d'enviar (app o SMS).", + telegramCodeLabel: "Codi", + telegramPasswordHelp: "Aquest compte té verificació en dos passos. Introdueix la teva contrasenya.", + telegramPasswordLabel: "Contrasenya", + telegramVerify: "Verificar", + telegramCancel: "Cancel·lar", + telegramChats: "Xats", + telegramUnread: "Sense llegir", + telegramOpen: "Obrir", + telegramSend: "Enviar", + telegramYou: "Tu", + telegramNoChats: "Encara no hi ha xats.", + telegramNoMessages: "Encara no hi ha missatges.", + telegramComposePlaceholder: "Escriu un missatge…", + telegramChannel: "Canal", + telegramGroup: "Grup", + telegramPrivate: "Privat", + telegramErrConnect: "No s'ha pogut contactar amb Telegram.", + telegramErrTimeout: "Telegram ha trigat massa a respondre.", + telegramErrAuth: "Sessió de Telegram no vàlida o caducada. Torna-la a connectar.", + telegramErrFetch: "No s'han pogut carregar els xats.", + telegramErrSend: "No s'ha pogut enviar el missatge.", + telegramErrMissing: "Calen l'API ID, l'API hash i el número de telèfon.", + telegramErrCode: "Codi incorrecte. Torna-ho a provar.", + telegramErrCodeExpired: "El codi ha caducat. Torna a començar.", + telegramErrPassword: "Contrasenya incorrecta.", + telegramErrPhone: "Número de telèfon no vàlid.", + telegramErrApi: "API ID o API hash no vàlids.", + telegramErrFlood: "Massa intents. Espera una estona i torna-ho a provar.", + telegramErrNoLogin: "No hi ha cap inici de sessió en curs. Torna a començar.", + peertubeUsernameLabel: "Usuària", + peertubePasswordLabel: "Contrasenya", + peertubeSubscriptions: "Subscripcions", + peertubeMyVideos: "Els meus vídeos", + peertubeVideos: "Vídeos", + peertubeFollowers: "Seguidores", + peertubeViews: "visualitzacions", + peertubeLikes: "m'agrada", + peertubeQuota: "Quota", + peertubeUpload: "Pujar vídeo", + peertubeUploadHelp: "Tria un fitxer de vídeo. Es puja al teu canal i PeerTube el processa després.", + peertubeTitleLabel: "Títol", + peertubeDescriptionLabel: "Descripció", + peertubePrivacyLabel: "Privacitat", + peertubePrivacyPublic: "Públic", + peertubePrivacyUnlisted: "No llistat", + peertubePrivacyPrivate: "Privat", + peertubeNoVideos: "Encara no hi ha vídeos.", + peertubeComments: "Comentaris", + peertubeComment: "Comentar", + peertubeCommentPlaceholder: "Escriu un comentari…", + peertubeNoComments: "Encara no hi ha comentaris.", + peertubeOpen: "Obrir a PeerTube", + peertubeWatch: "Veure", + peertubeChannel: "Canal", + peertubeNoStream: "Aquest vídeo encara no es pot reproduir aquí. Obre'l a PeerTube.", + peertubeUploaded: "Vídeo pujat. PeerTube l'està processant.", + peertubeErrConnect: "No s'ha pogut contactar amb PeerTube.", + peertubeErrAuth: "Usuària o contrasenya incorrectes, o la sessió ha caducat. Connecta-la de nou.", + peertubeErrInstance: "URL d'instància no vàlida.", + peertubeErrMissing: "Calen instància, usuària i contrasenya.", + peertubeErrFetch: "No s'han pogut carregar els vídeos.", + peertubeErrUpload: "No s'ha pogut pujar el vídeo.", + peertubeErrTitle: "Cal un títol.", + peertubeErrComment: "No s'ha pogut publicar el comentari.", + peertubeErrNoFile: "Tria un fitxer de vídeo.", + bookmarksLabel: "Marcadors", + videosLabel: "Vídeos", + torrentsLabel: "Torrents", + docsLabel: "Documents", + audiosLabel: "Àudios", + tagsLabel: "Etiquetes", + imagesLabel: "Imatges", + inhabitantsLabel: "Habitants", + trendingLabel: "Tendències", + eventsLabel: "Esdeveniments", + apply: "Aplicar", + menuPersonal: "Personal", + menuGovernance: "Governança", + menuOffice: "Oficina", + menuNetwork: "Comunitat", + menuCreative: "Creatiu", + menuEconomy: "Economia", + menuMedia: "Biblioteca", + menuTools: "Eines", + comment: "Comentar", + subtopic: "Subtema", + json: "JSON", + unfollow: "Deixar de donar suport", + follow: "Donar suport", + block: "Bloquejar", + unblock: "Desbloquejar", + relationshipNotFollowing: "No et dona suport", + relationshipTheyFollow: "Et dona suport", + relationshipMutuals: "Suport mutu", + relationshipFollowing: "Li dones suport", + relationshipYou: "Tu", + relationshipBlocking: "L'estàs bloquejant", + relationshipBlockedBy: "Et bloqueja", + relationshipMutualBlock: "Bloqueig mutu", + relationshipNone: "No li dones suport", + relationshipBlockingPost: "Post bloquejat", + viewLikes: "Veure rèpliques", + viewSupporters: "Veure suports", + spreadedDescription: "Llista de continguts replicats per habitant.", + preview: "Previsualitzar", + publish: "Publicar", + contentWarningPlaceholder: "Afegeix un títol al teu post (opcional)", + publishWarningPlaceholder: "Escriu el cos de la teva entrada.", + publishTooLong: "La teva publicació és massa llarga. Escurça-la, si us plau.", + keepTextRetry: "Envia de nou", + dateInPastError: "La data no pot ser anterior a avui", + publishCustomDescription: [ + "RECORDA: A causa de la tecnologia blockchain, un cop has publicat alguna cosa, no es pot editar ni esborrar. Pensa-t'ho bé!", + ], + commentWarning: [ + "RECORDA: A causa de la tecnologia blockchain, un cop has publicat alguna cosa, no es pot editar ni esborrar. Pensa-t'ho bé!", + ], + commentLabel: ({ publicOrPrivate, markdownUrl }) => [ + ], + publishLabel: ({ markdownUrl, linkTarget }) => [ + "RECORDA: A causa de la tecnologia blockchain, un cop has publicat alguna cosa, no es pot editar ni esborrar. Pensa-t'ho bé!", + ], + replyLabel: ({ markdownUrl }) => [ + "RECORDA: A causa de la tecnologia blockchain, un cop has publicat alguna cosa, no es pot editar ni esborrar. Pensa-t'ho bé!", + ], + publishCustomInfo: ({ href }) => [ + "Si tens experiència, també pots ", + a({ href }, "escriure un post avançat"), + ".", + ], + publishBasicInfo: ({ href }) => [ + "Si no tens experiència, hauries d'", + a({ href }, "escriure un post normal"), + ".", + ], + publishCustom: "Escriure un post avançat", + messagePreview: "Previsualització", + mentionsMatching: "Mencions", + mentionsName: "Nom", + mentionsRelationship: "Relació", + updateBannerText: "Hi ha una nova versió d'Oasis disponible.", + updateBannerAction: "Actualitzar ara!", + info: "Info", + settingsIntro: ({ version }) => [ + `[SNH] ꖒ OASIS [ v.${version} ]`, + ], + timeAgo: "fa", + sendTime: "escrit ", + theme: "Tema", + backupKeysExportButton: "Exportar", + backupKeysImportButton: "Importar", + ssbLogStream: "Blockchain", + ssbLogStreamDescription: "Configura el límit de missatges per als fluxos de la blockchain.", + logWindowChip: "Darrers {n} streams de la blockchain", + saveSettings: "Desar la configuració", + exportDescription: "Estableix una contrasenya (mínim 32 caràcters de llargada) per xifrar el teu secret", + exportDataTitle: "Còpia instantània", + exportDataDescription: "Baixar la teva blockchain (clau privada exclosa!)", + exportDataButton: "Baixar la blockchain", + importDescription: "Importa el teu secret xifrat (clau privada) per activar el teu avatar", + passwordImport: "Escriu la teva contrasenya per desxifrar les dades que es desaran al teu sistema (nom: secret)", + exportPasswordPlaceholder: "Utilitza minúscules, majúscules, números i símbols", + fileInfo: "El teu secret xifrat es baixarà al teu dispositiu (nom: oasis.enc)", + developer: "Desenvolupament", + devTitle: "Desenvolupament", + welcomeBannerText: "El teu avatar encara no està configurat.", + aiSuggestionBanner: "Suggeriment:", + aiSuggestionsEnable: "Suggeriments de la IA", + welcomeBannerAction: "Començar", + welcomeTitle: "Benvinguda", + welcomeStepIdentity: "Identitat", + welcomeStepType: "Tipus", + welcomeStepUse: "Ús", + welcomeStepLook: "Aparença", + welcomeStepCommunity: "Comunitat", + welcomeStepIntro: "Benvinguda", + welcomeHintIdentity: "El teu nom i la teva imatge.", + welcomeHintType: "Com s'obre Oasis per a tu.", + welcomeHintUse: "Quins mòduls queden actius.", + welcomeHintLook: "El tema que vesteix Oasis.", + welcomeHintCommunity: "Entra en un PUB per conèixer altres habitants.", + welcomeHintIntro: "Envia una primera salutació a la comunitat.", + welcomeStepThemeAction: "Fes servir aquest tema", + welcomeJoinPubs: "Entra als PUBs", + welcomeNoFederation: "Sense federar-se", + welcomeStepGreetingAction: "Enviar feed", + welcomeDescription: "Unes quantes coses que convé fer abans de començar.", + welcomeSkip: "Ara no", + welcomeStepProfileAction: "Desar el perfil", + welcomeBootstrapRunning: "Carregant la instantània del pub. En acabar s'uneix al pub automàticament; mentrestant pots continuar fent servir Oasis.", + welcomeCompleteTitle: "Tot a punt.", + welcomeCompleteText: "El teu node està preparat. A partir d'aquí la xarxa creix amb els habitants a qui dones suport.", + welcomeFindPeople: "Cercar habitants", + devFilterFiles: "FITXERS", + devFilterSearch: "CERCAR", + devFilterModules: "MÒDULS", + devFilterTranslations: "TRADUCCIONS", + devFilterStyles: "ESTILS", + devFilterTests: "TESTS", + devVersion: "Versió", + devNodeVersion: "Node", + devStatsTests: "Tests", + devParentFolder: "Carpeta superior", + devOpenFolder: "obrir", + devDescription: "Explora el codi que s'està executant en aquest node.", + devRoot: "arrel", + devSearchPlaceholder: "Text a cercar al codi", + devAnyExtension: "Qualsevol fitxer", + devStatsFiles: "Fitxers", + devStatsLines: "Línies", + devStatsModules: "Mòduls", + devStatsLanguages: "Idiomes", + devNotViewable: "no visualitzable", + devEmptyFolder: "Aquesta carpeta és buida.", + devSize: "Mida", + devShowing: "Mostrant", + devLine: "línia", + devReportBug: "Informar d'un error", + devCreateTask: "Crear tasca", + devRaw: "RAW", + devReportContext: "Trobat llegint el codi d'Oasis", + devTaskPrefix: "Revisar", + devPrevPage: "Línies anteriors", + devNextPage: "Línies següents", + devMatches: "coincidències", + devFilesScanned: "fitxers revisats", + devNoResults: "Encara no hi ha coincidències.", + devColModule: "Mòdul", + devColState: "Estat", + devColModel: "Model", + devColView: "Vista", + devColRoutes: "Rutes", + devColTests: "Tests", + devOn: "actiu", + devOff: "inactiu", + modulesDevLabel: "Desenvolupament", + modulesDevDescription: "Mòdul per gestionar el codi d'Oasis.", + themeIntro: + "Tria un tema.", + setTheme: "Establir el tema", + language: "Idioma", + languageDescription: + "Si vols utilitzar un altre idioma, selecciona'l aquí.", + setLanguage: "Establir l'idioma", + peerConnectionsIntro: "Gestiona totes les teves connexions amb altres nodes.", + peersConnectFailed: "No s'ha pogut connectar: {reason}.", + peerStateLabel: "Estat", + peerAddressLabel: "Adreça", + peerLastChangeLabel: "Darrer canvi", + peerStateConnected: "Connectat", + peerStateConnecting: "Connectant", + peerStateStaged: "Disponible", + peerErrSilent: "Sense activitat", + peerErrTimeout: "No respon", + peerErrRefused: "Rebutja la connexió", + peerErrNotFound: "Adreça desconeguda", + peerErrUnreachable: "Xarxa inaccessible", + peerErrKeys: "Fent servir claus diferents", + peerErrIdentity: "No accepta la teva connexió", + peerErrTor: "Només TOR", + peerErrDropped: "Connexió tallada", + peerErrPaused: "Xarxa en pausa", + peerErrOther: "Error de connexió", + peerOutdated: "Desactualitzat", + peerVersionUnannounced: "No anuncia la seva versió: un Oasis antic", + peersTorMissing: "Aquest PUB només permet connexions des de TOR.", + peerConnectionsTitle: "Connexions", + online: "Connectat", + offline: "Desconnectat", + discovered: "Descoberts", + unknown: "Desconeguts", + lanBroadcastActive: "Activa (multicast UDP a la LAN)", + peerSourceRpc: "RPC", + peerSourceGossip: "Gossip", + peerSourceEbt: "EBT", + peerSourceRecent: "Recent", + peerSourceLan: "LAN", + pub: "PUB", + supported: "Amb suport", + recommended: "Recomanat", + blocked: "Bloquejat", + noConnections: "No hi ha nodes connectats.", + noDiscovered: "No has descobert cap node.", + noUnknownPeers: "No hi ha nodes desconeguts al graf d'amistats.", + peersTechnicalEmpty: "Encara no hi ha nodes registrats.", + peerConnect: "Connectar", + peerDisconnect: "Desconnectar", + peerRefresh: "Refrescar", + peerPruneIdle: "Treure els inactius", + peersPause: "Pausa", + peersResume: "Reprèn", + peersPausedNote: "En pausa: Oasis no es connecta amb ningú i ningú s'hi pot connectar.", + peersPauseHint: "Atura totes les connexions: Oasis no es connecta a altres nodes ni els accepta fins que reprenguis.", + peerExport: "Exportar", + peerImport: "Importar", + peerImportTitle: "Importar una llista de nodes", + peerImportPlaceholder: "Enganxa una adreça multiserver per línia, o puja un fitxer .txt…", + connectionActionIntro: + "", + indexes: "Índexs", + indexesRebuilt: "Índexs reconstruïts", + indexesRebuildFailed: "No s'han pogut reconstruir els índexs", + indexesFiles: "Fitxers d'índex", + indexesDescription: + "Reconstruir els índexs és segur i pot arreglar alguns errors de la teva blockchain.", + homePageTitle: "Pàgina d'inici", + homePageDescription: "Selecciona quin mòdul vols com a pàgina d'inici.", + saveHomePage: "Establir la pàgina d'inici", + invites: "Invitacions", + invitesDescription: "Gestiona i aplica codis d'invitació a la teva xarxa.", + invitesPubsHint: "Enganxa un codi d'invitació d'un pub per federar-t'hi i començar a replicar els seus habitants.", + invitesFederationsHint: "Pubs amb què estàs federat: actualitza'ls, treu els inabastables o exporta la llista.", + invitesTribesTitle: "Tribus", + invitesChatsTitle: "Xats", + invitesPadsTitle: "Pads", + invitesCalendarsTitle: "Calendaris", + invitesEventsTitle: "Esdeveniments", + invitesForumsTitle: "Fòrums", + invitesMapsTitle: "Mapes", + invitesShopsTitle: "Botigues", + invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "PUBs Suggerits", + invitesFilterOthers: "Altres", + invitesJoinTitle: "Invitacions", + invitesJoinHint: "Enganxa qualsevol codi d'invitació, sigui del que sigui, o l'Oasis ID d'un habitant.", + invitesJoinPlaceholder: "Codi d'invitació o Oasis ID", + invitesJoinButton: "Entra", + invitesUsedTitle: "Últimes invitacions", + invitesUsedEmpty: "Encara no has utilitzat cap invitació.", + invitesUsedType: "Tipus", + invitesUsedName: "Nom", + invitesUsedDate: "Data", + invitesPubInviteCodePlaceholder: "Introdueix el codi d'invitació del PUB", + invitesAcceptInvite: "Entrar al PUB", + invitesPubUses: "Invitacions usades", + invitesPubExhausted: "Aquest PUB no té invitacions disponibles.", + invitesAlreadyFederated: "Ja estàs federat amb aquest pub.", + invitesJoinPubMode: "Aquest Oasis funciona com a PUB, en mode només lectura. Per unir-lo a un altre PUB, fes servir al terminal: ./oasis.sh join ", + invitesJoinLocalOnly: "Les invitacions només s'accepten des de l'equip on funciona Oasis, o amb l'enllaç d'administració que mostra en arrencar.", + invitesFederationsTitle: "Federacions", + invitesAcceptedInvites: "Federades", + invitesNoFederatedPubs: "No hi ha pubs federats.", + invitesPubsRefresh: "Refrescar", + invitesPubsClearUnreachable: "Netejar", + invitesPubsExport: "Exportar", + invitesPubsImport: "Importar", + invitesPubsImportTitle: "Importar pubs", + invitesPubsImportPlaceholder: "Enganxa una adreça multiserver o un codi d'invitació per línia, o puja un fitxer .txt…", + genericError: "S'ha produït un error.", + panicMode: "Mode pànic!", + encryptData: "Estableix una contrasenya (mínim 32 caràcters de llargada) per xifrar la teva blockchain", + decryptData: "Introdueix la contrasenya per desxifrar la teva blockchain", + removeDataDescription: "COMPTE: Aquest procés no es pot desfer.", + removePanicButton: "ESBORRAR TOTES LES TEVES DADES!", + searchTitle: "Cercar", + searchDescriptionLabel: "Cerca contingut a la teva xarxa.", + searchPlaceholder:"Cercar contingut...", + searchLocationLabel:"Localització", + searchPriceLabel:"Preu", + searchCategoryLabel:"Categoria", + searchPriorityLabel:"Prioritat", + searchStatusLabel:"Estat", + statusLabel:"Estat", + noResultsFound:"No s'han trobat resultats.", + votesOption:"Opinions votades", + voteYesLabel:"Sí", + voteNoLabel:"No", + allTypesLabel: "SENSE LÍMITS", + YESLabel:"SÍ", + NOLabel:"NO", + votesOptionYesLabel:"Sí", + votesOptionNoLabel:"No", + votesOptionAbstentionLabel:"Abstenció", + votesQuestionLabel:"Pregunta", + voteStatusOpen:"OBERT", + voteStatusClosed:"TANCAT", + commentDescription: ({ parentUrl }) => [ + " ha comentat en ", + a({ href: parentUrl }, " fil"), + ], + commentTitle: ({ authorName }) => [`Ha comentat al post de @${authorName}`], + subtopicDescription: ({ parentUrl }) => [ + " ha creat un subtema des de ", + a({ href: parentUrl }, " un post"), + ], + subtopicTitle: ({ authorName }) => [`Ha creat un subtema al post de @${authorName}`], + oasisDescription: "OASIS Xarxa de Projectes", + searchSubmit: "Cercar...", + votesLabel: "GOVERN", + pdfFallbackLabel: "Document PDF", + shopLabel: "BOTIGUES", + voteLabel: "VOTS", + submit: "Aplicar", + editProfile: "Editar l'Avatar", + oasisIdLabel: "OasisID", + spreadContent: "Replicar", + welcomePmSubject: "Hello World!", + welcomePmBody: "Et donem la benvinguda a Oasis — una xarxa social lliure, peer-to-peer, federada i xifrada, amb una arquitectura distribuïda i només per invitació.\n\nAlguns llocs interessants per començar:\n\n- [/avatar](/profile) — Edita el teu avatar, el nom, la descripció i quins mòduls es mostren al teu avatar públic.\n- /settings — Ajusta la teva instància local: idioma, tema, mòduls, privadesa i sincronització.\n- /invites — Afegeix un pub per federar-te amb la resta de la xarxa.\n- /peers — Mira qui està connectat, torna a escanejar la teva LAN, exporta o importa llistes de nodes.\n- /inhabitants — Descobreix i visita els avatars d'altres habitants.\n- /tribes — Crea grups privats amb xifratge d'extrem a extrem o uneix-t'hi.\n- /search — Cerca el contingut de la xarxa per tipus.\n\nAlguns llocs interessants per connectar:\n\n- /multiverse — Gestiona els teus altres comptes del multivers, incloent-hi enviar i rebre contingut.\n\nAquest missatge s'ha enviat de manera privada de tu cap a tu, per això no ha calgut connexió per rebre'l.", + profileSensorsSectionTitle: "Sensors", + profileVisibilityActivity: "Nivell d'activitat", + profileDeviceLockedHint: "Aquest sensor està sempre actiu: permet que altres vegin des de quin dispositiu et connectes i no es pot desactivar.", + profileVisibilityKarma: "Puntuació KARMA", + profileVisibilityUbi: "RBU", + profileVisibilityWallet: "Moneder ECOin", + profileVisibilityEcoTax: "Taxa ECO", + profileVisibilityLarpSign: "Segell L.A.R.P.", + profileVisibilityGpg: "Clau GPG", + clearnetSectionTitle: "Clearnet", + profileClearnetShopsLabel: "Botigues", + profileClearnetJobsLabel: "Feina", + profileClearnetEventsLabel: "Esdeveniments", + profileClearnetProjectsLabel: "Projectes", + profileClearnetPostsLabel: "Blogs", + profileClearnetAudiosLabel: "Àudios", + profileClearnetVideosLabel: "Vídeos", + profileClearnetImagesLabel: "Imatges", + profileClearnetDocumentsLabel: "Documents", + profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Mercat", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", + editProfileDescription: + "", + profileName: "Nom", + profileImage: "Imatge de l'Avatar", + profileGpgKey: "Clau pública GPG (.asc)", + profileGpgChip: "GPG", + profileGpgDownload: "Baixar", + profileGpgRemove: "Eliminar", + profileDescription: "Descripció", + hashtagDescription: + "Posts d'habitants de la teva xarxa que fan referència a aquest #hashtag, ordenats pel més recent.", + rebuildName: "Reconstruir la blockchain", + wallet: "Moneder", + walletAddress: "Adreça", + walletAmount: "Quantitat", + walletAddressLine: ({ address }) => `Adreça: ${address}`, + walletAmountLine: ({ amount }) => `Quantitat: ${amount} ECO`, + walletBack: "Tornar", + walletBalanceTitle: "Saldo", + walletWalletSendTitle: "Enviar", + walletReceiveTitle: "Rebre", + walletHistoryTitle: "Historial", + walletBalanceLine: ({ balance }) => `${balance} ECO`, + walletCnfrs: "Conf.", + walletConfirm: "Confirmar", + walletDescription: "Gestiona el teu moneder ECOin.", + walletDate: "Data", + walletFee: "Comissió (com més alta sigui la comissió, més ràpid es processarà la teva transacció)", + walletFeeLine: ({ fee }) => `Comissió: ECO ${fee}`, + walletHistory: "Historial", + walletReceive: "Rebre", + walletReset: "Restablir", + walletSend: "Enviar", + walletPayingTransfer: "Pagant el transfer", + walletCreateTransferLabel: "Generar contracte (transfer)", + walletTransferNotifiedNote: "S'ha avisat del pagament a l'altra part", + walletTransferCreatedNote: "S'ha creat un transfer (rebut) d'aquest pagament", + walletTransferNoPayeeNote: "Cap habitant no ha publicat aquesta adreça, així que no s'ha creat cap transfer", + walletTransferReceiptConcept: "Pagament des del moneder", + walletPaymentPmText: "t'ha enviat un pagament", + walletPayeeLabel: "Destinatari", + walletConceptLabel: "Concepte", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Has rebut la teva RBU.", + bankingBotPaymentTitle: "Has rebut un pagament.", + bankingBotUbiPaidText: "La teva Renda Bàsica Universal s'ha pagat", + bankUbiPubThisNode: "Aquest node paga la RBU", + bankUbiPubConnected: "PUB de la RBU", + bankUbiPubNone: "De moment no hi ha PUBs que anunciïn una RBU.", + bankUbiAddressPublished: "Adreça ECOin publicada", + bankUbiAddressMissing: "Publica la teva adreça ECOin per rebre la RBU", + bankUbiClaimedThisMonth: "Reclamada aquest mes", + bankUbiPubBalance: "Moneder del PUB disponible per a la RBU", + bankNoPubAddress: "El PUB de la RBU encara no ha publicat cap adreça ECOin.", + bankUbiTab: "RBU", + bankDonateEco: "DONAR!", + donateTooltip: "Donar ECOin", + bankNoUserAddress: "Aquest habitant no comparteix cap adreça ECOin.", + bankUbiPubDefault: "Per defecte", + bankUbiPool: "Assignació", + bankUbiLastSeen: "Vist per última vegada", + bankUbiLastPayout: "Darrera acció", + bankUbiPaidOut: "Repartit", + bankRangeToday: "Avui", + bankRangeWeek: "Setmana", + bankRangeMonth: "Mes", + bankRangeYear: "Any", + bankRangeFiveYears: "5 anys", + bankRangeAll: "Tot", + bankChartValueTitle: "Evolució del valor d'ECOin", + bankChartValueHours: "Equivalència en temps (h)", + bankChartWealthTitle: "Redistribució de la riquesa ECOin", + bankChartDistributed: "RBU repartida (ECO)", + bankChartRebalanceTitle: "Redistribució de la riquesa entre PUBs", + bankChartRebalanced: "ECO moguts entre PUBs", + bankChartTaxes: "Impostos retinguts (ECO)", + bankChartNoDataRange: "No hi ha dades en aquest interval.", + bankChartNoDataYet: "Encara no hi ha dades", + bankChartPerHour: "per hora", + bankChartPerDay: "per dia", + bankChartPerMonth: "per mes", + bankTotalUbiDistributed: "RBU total redistribuïda (xarxa)", + bankTotalTaxesWithheld: "Impostos totals retinguts (xarxa)", + bankInflationIssuance: "Taxa d'emissió (anual, per recompensa de bloc)", + bankRulesEligibilityTitle: "Qui pot rebre la RBU", + bankRulesEligibility1: "Una adreça ECOin publicada al teu perfil (Perfil → Sensors → Moneder ECOin).", + bankRulesEligibility2: "Un feed de més de 30 dies i activitat a la xarxa: karma per sobre de zero.", + bankRulesEligibility3: "Una reclamació per mes (epoch), des de Banca amb el botó Reclamar RBU. Es pot rebutjar la del mes en curs amb Rebutjar RBU.", + bankRulesEligibility4: "El mínim d'1 ECO es paga sempre; els impostos només redueixen la part que el supera i mai no es cobren a part.", + bankRulesEligibility5: "El PUB paga en el seu següent cicle i publica un transfer RBU que pots confirmar.", + bankRulesEligibility6: "Si falta la teva adreça, el feed és massa jove, has rebutjat el mes o el PUB no té fons, la reclamació se salta i queda registrada al PUB.", + copyLinkTitle: "Copiar l'enllaç", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Rebutjar RBU", + bankUbiRefusedThisMonth: "Has rebutjat la RBU d'aquest mes.", + bankingBotUbiAvailableTitle: "Pots reclamar la teva RBU.", + bankingBotUbiAvailableText: "La teva RBU d'aquest mes està disponible", + bankingBotConfirmTitle: "Tens un contracte pendent de confirmació", + bankingBotConfirmText: "Contractes pendents de confirmació", + bankChartZoomHint: "Fes clic per ampliar", + bankChartPoolTitle: "Assignació de la RBU (en el temps)", + bankChartNetworkUbiTitle: "RBU total redistribuïda (xarxa)", + bankChartSupplyTitle: "Oferta circulant d'ECOin (en el temps)", + bankChartInflationTitle: "Inflació d'ECOin (en el temps)", + bankChartInflationSeries: "Taxa anualitzada (%)", + bankChartPool: "Assignació total (ECO)", + bankChartKarmaTitle: "Puntuació KARMA (en el temps)", + bankChartFundsTitle: "Els teus fons ECOin (en el temps)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin connectat", + bankWalletNotConfigured: "ECOin desconnectat", + transfersPayWithWallet: "PAGAR!", + walletStatus: "Estat", + walletDisconnected: [ + "Tens el ", + strong("moneder d'ECOin desconnectat"), + ". Revisa ", + a({ href: '/settings#wallet' }, "la teva configuració"), + " o l'estat de la teva connexió.", + ], + walletSentToLine: ({ destination, amount }) => `Enviar ECO ${amount} a ${destination}`, + walletNotConfigured: "La teva cartera d'ECOin encara no està configurada.", + walletSettingsDescription: "Integrar Oasis amb el teu moneder d'ECOin.", + walletSettingsMobileDescription: "Vincula Oasis amb l'app ECOin Wallet d'aquest dispositiu.", + walletDownloadEcoin: "Baixar ECOin!", + walletRpcUrl: "URL del RPC", + walletStatusMessages: { + invalid_amount: "Quantitat no vàlida", + invalid_dest: "Adreça de destinació no vàlida", + invalid_fee: "Comissió no vàlida", + validation_errors: "Errors en la validació", + send_tx_success: "Transacció feta", + }, + walletTitle: "Moneder", + walletTotalCostLine: ({ totalCost }) => `Cost total: ECO ${totalCost}`, + walletTransactionId: "ID de la transacció", + walletTxId: "ID Tx", + walletType: "Tipus", + walletUser: "Usuari", + walletPass: "Contrasenya", + walletConfiguration: "Configurar el moneder", + walletDisconnectButton: "Desconnectar el moneder", + cipher: "Xifrador", + cipherTitle: "Xifrador", + cipherDescription: "Xifra i desxifra el teu text de manera simètrica (amb una contrasenya compartida).", + randomPassword: "Contrasenya aleatòria", + cipherTextPlaceholder: "Introdueix el text que vols xifrar...", + cipherPasswordLabel: "Estableix una contrasenya (mínim 32 caràcters) per xifrar el teu text", + cipherPasswordDecryptLabel: "Estableix una contrasenya (mínim 32 caràcters) per desxifrar el teu text", + cipherPasswordPlaceholder: "Introdueix una contrasenya...", + cipherEncryptButton: "Xifrar", + cipherEncryptedMessageLabel: "Text xifrat", + cipherDecryptedMessageLabel: "Text desxifrat", + cipherEncryptedTextPlaceholder: "Introdueix el text xifrat...", + cipherDecryptButton: "Desxifrar", + password: "Contrasenya", + text: "Text", + encryptedText: "Text xifrat", + iv: "Vector d'inicialització (IV)", + bookmarkTitle: "Marcadors", + bookmarkDescription: "Descobreix i gestiona marcadors a la teva xarxa.", + bookmarkAllSectionTitle: "Marcadors", + bookmarkMineSectionTitle: "Els teus marcadors", + bookmarkRecentSectionTitle: "Marcadors recents", + bookmarkTopSectionTitle: "Marcadors top", + bookmarkFavoritesSectionTitle: "Preferits", + bookmarkCreateSectionTitle: "Crear un marcador", + bookmarkUpdateSectionTitle: "Actualitzar el marcador", + bookmarkFilterAll: "TOTS", + bookmarkFilterMine: "MEUS", + bookmarkFilterTop: "TOP", + bookmarkFilterFavorites: "PREFERITS", + bookmarkFilterRecent: "RECENTS", + bookmarkCreateButton: "Crear un marcador", + bookmarkUpdateButton: "Actualitzar", + bookmarkAddFavoriteButton: "Afegir a preferits", + bookmarkRemoveFavoriteButton: "Treure de preferits", + bookmarkUrlLabel: "Enllaç", + bookmarkUrlPlaceholder: "https://solarnethub.com", + bookmarkDescriptionLabel: "Descripció", + bookmarkDescriptionPlaceholder: "Opcional", + bookmarkTagsLabel: "Etiquetes", + bookmarkTagsPlaceholder: "Introdueix etiquetes separades per comes", + bookmarkLastVisitLabel: "Darrera visita", + bookmarkSearchPlaceholder: "Cercar a Marcadors...", + bookmarkSortRecent: "Més recents", + bookmarkSortOldest: "Més antics", + bookmarkSortTop: "Més votats", + bookmarkSearchButton: "Cercar", + bookmarkUpdatedAt: "Actualitzat", + bookmarkNoMatch: "No hi ha marcadors que coincideixin amb la teva cerca.", + noBookmarks: "No hi ha marcadors disponibles.", + noUrl: "Sense enllaç", + noCategory: "Sense categoria", + noLastVisit: "Sense darrera visita", + videoTitle: "Vídeos", + videoDescription: "Explora i gestiona contingut de vídeo a la teva xarxa.", + videoMineSectionTitle: "Els teus vídeos", + videoCreateSectionTitle: "Pujar un vídeo", + videoUpdateSectionTitle: "Actualitzar el vídeo", + videoAllSectionTitle: "Vídeos", + videoRecentSectionTitle: "Vídeos recents", + videoTopSectionTitle: "Vídeos més votats", + videoFavoritesSectionTitle: "Preferits", + videoFilterAll: "TOTS", + videoFilterMine: "MEUS", + videoFilterRecent: "RECENTS", + videoFilterTop: "TOP", + videoFilterFavorites: "PREFERITS", + videoCreateButton: "Pujar un vídeo", + videoUpdateButton: "Actualitzar", + videoDeleteButton: "Eliminar", + videoAddFavoriteButton: "Afegir a preferits", + videoRemoveFavoriteButton: "Treure de preferits", + videoFileLabel: "Selecciona un fitxer de vídeo (.mp4, .webm, .ogv, .mov)", + videoTagsLabel: "Etiquetes", + videoTagsPlaceholder: "Introdueix etiquetes separades per comes", + videoTitleLabel: "Títol", + videoTitlePlaceholder: "Opcional", + videoDescriptionLabel: "Descripció", + videoDescriptionPlaceholder: "Opcional", + videoNoFile: "No s'ha proporcionat cap fitxer de vídeo", + noVideos: "No hi ha vídeos disponibles.", + videoSearchPlaceholder: "Cercar a Vídeos...", + videoSortRecent: "Més recents", + videoSortOldest: "Més antics", + videoSortTop: "Més votats", + videoSearchButton: "Cercar", + videoUpdatedAt: "Actualitzat", + videoNoMatch: "Cap vídeo no coincideix amb la teva cerca.", + documentTitle: "Documents", + documentDescription: "Descobreix i gestiona documents a la teva xarxa.", + documentAllSectionTitle: "Documents", + documentMineSectionTitle: "Els teus documents", + documentRecentSectionTitle: "Documents recents", + documentTopSectionTitle: "Documents top", + documentFavoritesSectionTitle: "Preferits", + documentCreateSectionTitle: "Pujar un document", + documentUpdateSectionTitle: "Editar el document", + documentFilterAll: "TOT", + documentFilterMine: "MEUS", + documentFilterRecent: "RECENTS", + documentFilterTop: "TOP", + documentFilterFavorites: "PREFERITS", + documentCreateButton: "Pujar un document", + documentUpdateButton: "Actualitzar", + documentAddFavoriteButton: "Afegir a preferits", + documentRemoveFavoriteButton: "Treure de preferits", + documentMessageAuthorButton: "MP", + documentFileLabel: "Pujar un document (.pdf)", + documentTagsLabel: "Etiquetes", + documentTagsPlaceholder: "Introdueix etiquetes separades per comes", + documentTitleLabel: "Títol", + documentTitlePlaceholder: "Opcional", + documentDescriptionLabel: "Descripció", + documentDescriptionPlaceholder: "Opcional", + documentNoFile: "Sense fitxer.", + noDocuments: "No hi ha documents disponibles.", + documentSearchPlaceholder: "Cercar a Documents...", + documentSortRecent: "Més recents", + documentSortOldest: "Més antics", + documentSortTop: "Més votats", + documentSearchButton: "Cercar", + documentNoMatch: "No hi ha documents que coincideixin amb la teva cerca.", + documentUpdatedAt: "Actualitzat", + audioTitle: "Àudios", + audioDescription: "Explora i gestiona contingut d'àudio a la teva xarxa.", + audioMineSectionTitle: "Els teus àudios", + audioCreateSectionTitle: "Pujar un àudio", + audioUpdateSectionTitle: "Actualitzar l'àudio", + audioAllSectionTitle: "Àudios", + audioRecentSectionTitle: "Àudios recents", + audioTopSectionTitle: "Àudios més votats", + audioFilterAll: "TOTS", + audioFilterMine: "MEUS", + audioFilterRecent: "RECENTS", + audioFilterTop: "TOP", + audioCreateButton: "Pujar un àudio", + audioUpdateButton: "Actualitzar", + audioAddFavoriteButton: "Afegir a preferits", + audioRemoveFavoriteButton: "Treure de preferits", + audioFileLabel: "Selecciona un fitxer d'àudio (.mp3, .wav, .ogg)", + audioTagsLabel: "Etiquetes", + audioTagsPlaceholder: "Introdueix etiquetes separades per comes", + tagsPlaceholder: "Introdueix etiquetes separades per comes", + audioTitleLabel: "Títol", + audioTitlePlaceholder: "Opcional", + audioDescriptionLabel: "Descripció", + audioDescriptionPlaceholder: "Opcional", + audioNoFile: "No s'ha proporcionat cap fitxer d'àudio", + noAudios: "No hi ha àudios disponibles.", + audioSearchPlaceholder: "Cercar a Àudios...", + audioSortRecent: "Més recents", + audioSortOldest: "Més antics", + audioSortTop: "Més votats", + audioSearchButton: "Cercar", + audioMessageAuthorButton: "MP", + audioUpdatedAt: "Actualitzat", + audioNoMatch: "Cap àudio no coincideix amb la teva cerca.", + audioFavoritesSectionTitle: "Preferits", + audioFilterFavorites: "PREFERITS", + favoritesTitle: "Preferits", + favoritesDescription: "Tots els teus elements marcats com a preferits en un sol lloc.", + favoritesSearchPlaceholder: "Cercar a Preferits...", + favoritesFilterAll: "TOTS", + favoritesFilterRecent: "RECENTS", + favoritesFilterAudios: "ÀUDIOS", + favoritesFilterBookmarks: "MARCADORS", + favoritesFilterDocuments: "DOCUMENTS", + favoritesFilterImages: "IMATGES", + favoritesFilterMaps: "MAPES", + favoritesFilterPads: "PADS", + favoritesFilterChats: "XATS", + favoritesFilterCalendars: "CALENDARIS", + favoritesFilterVideos: "VÍDEOS", + favoritesNoItems: "Encara no hi ha preferits.", + yourContacts: "Els teus contactes", + allInhabitants: "Habitants", + allCVs: "CV", + discoverPeople: "Descobreix habitants a la teva xarxa.", + searchInhabitantsPlaceholder: "Cercar a Habitants...", + filterLocation: "FILTRAR habitants PER UBICACIÓ …", + filterLanguage: "FILTRAR habitants PER IDIOMA …", + filterSkills: "FILTRAR habitants PER HABILITATS …", + cvSkillsCount: "Habilitats", + applyFilters: "Aplicar els filtres", + locationLabel: "Ubicació", + languagesLabel: "Idiomes", + skillsLabel: "Habilitats", + commonSkills: "Habilitats comunes", + mutualFollowers: "Seguidors mutus", + suggestedFollowsYou: "Et segueix", + latestInteractions: "Darreres interaccions", + suggestedSectionTitle: "Suggerits", + topkarmaSectionTitle: "Millor karma", + topecoSectionTitle: "Millor Eco", + topactivitySectionTitle: "Més activitat", + "TOP INACTIVITYButton": "MÉS INACTIUS", + topinactivitySectionTitle: "Habitants menys actius", + blockedSectionTitle: "Bloquejats", + gallerySectionTitle: "GALERIA", + topSectionTitle: "TOP", + blockedLabel: "Usuari bloquejat", + inhabitantviewDetails: "Veure els detalls", + keepReading: "Continuar llegint...", + oasisId: "ID", + noInhabitantsFound: "Encara no s'han trobat habitants.", + inhabitantActivityLevel: "Nivell d'activitat", + lifespanLabel: "Vigència", + deviceLabel: "Dispositiu", + parliamentTitle: "Parlament", + parliamentDescription: "Explora formes de govern i polítiques de gestió col·lectiva.", + parliamentFilterGovernment: "GOVERN", + parliamentFilterCandidatures: "CANDIDATURES", + parliamentFilterProposals: "PROPOSTES", + parliamentFilterLaws: "LLEIS", + parliamentFilterRevocations: "REVOCACIONS", + parliamentFilterHistorical: "HISTÒRIC", + parliamentFilterLeaders: "LÍDERS", + parliamentFilterRules: "REGLES", + parliamentGovernmentCard: "Govern actual", + parliamentSeatsGovernment: "Govern", + parliamentSeatsOpposition: "Oposició", + parliamentSeatsAbstention: "Abstenció", + parliamentSeatsQuorum: "Quòrum d'elecció", + parliamentSeatsPass: "Llindar d'aprovació", + parliamentSeatsByElection: "Per elecció", + parliamentSeatsByHouses: "Per casa del LARP", + parliamentSeatsSeats: "escons", + parliamentSeatsSeat: "escó", + parliamentSeatsLeader: "Líder", + parliamentSeatsVotesFor: "vots a", + parliamentSeatsNoHouse: "Sense casa", + parliamentActorInPowerInhabitant: "HABITANT AL PODER", + parliamentActorInPowerTribe: "TRIBU AL PODER", + parliamentHistoricalGovernmentsTitle: "GOVERNS", + parliamentHistoricalElectionsTitle: "CICLES ELECTORALS", + parliamentHistoricalLawsTitle: "HISTÒRIC", + parliamentHistoricalLeadersTitle: "LÍDERS", + parliamentThCycles: "CICLES", + parliamentThTimesInPower: "MANDATS", + parliamentThTotalCandidatures: "CANDIDATURES", + parliamentThProposed: "LLEIS PROPOSADES", + parliamentThApproved: "LLEIS APROVADES", + parliamentThDeclined: "LLEIS REBUTJADES", + parliamentThDiscarded: "LLEIS DESCARTADES", + parliamentMembers: "MEMBRES", + parliamentLegSince: "INICI DEL CICLE", + parliamentLegEnd: "FI DEL CICLE", + parliamentPoliciesProposal: "PROPOSTES DE LLEI", + parliamentPoliciesApproved: "LLEIS APROVADES", + parliamentPoliciesDeclined: "LLEIS REBUTJADES", + parliamentPoliciesDiscarded: "LLEIS DESCARTADES", + parliamentEfficiency: "% EFICIÈNCIA", + parliamentRevocationFormTitle: "Revocar una llei", + parliamentRevocationLaw: "Llei", + parliamentRevocationReasons: "Motius", + parliamentRevocationPublish: "Publicar la revocació", + parliamentCurrentRevocationsTitle: "Revocacions actuals", + parliamentFutureRevocationsTitle: "Revocacions futures", + parliamentPoliciesRevocated: "LLEIS REVOCADES", + parliamentPoliciesTitle: "HISTÒRIC", + parliamentLawsTitle: "LLEIS APROVADES", + parliamentRulesRevocations: "Qualsevol llei aprovada es pot revocar amb el mètode de govern vigent.", + parliamentNoStableGov: "Encara no hi ha cap govern elegit.", + parliamentNoGovernments: "Encara no hi ha governs a l'històric.", + parliamentCandidatureFormTitle: "Proposar una candidatura", + parliamentCandidatureIdPh: "Oasis ID (@...) o nom de la tribu", + parliamentCandidatureMethod: "Mètode", + parliamentCandidatureProposeBtn: "Publicar la candidatura", + parliamentVoteBtn: "Votar", + parliamentProposalFormTitle: "Proposar una llei", + parliamentProposalDescription: "Descripció (≤1000)", + parliamentProposalPublish: "Publicar la proposta", + parliamentNoProposals: "Encara no hi ha propostes de llei.", + parliamentNoLaws: "Encara no hi ha lleis aprovades.", + parliamentMethodDEMOCRACY: "Democràcia", + parliamentMethodMAJORITY: "Majoria (80%)", + parliamentMethodMINORITY: "Minoria (20%)", + parliamentMethodDICTATORSHIP: "Dictadura", + parliamentMethodKARMATOCRACY: "Karmatocràcia", + parliamentThId: "ID", + parliamentThProposalDate: "Data de la proposta", + parliamentThMethod: "Mètode", + parliamentThKarma: "Karma", + parliamentThSupports: "Suports", + parliamentThVote: "Votar", + parliamentCurrentProposalsTitle: "Propostes actuals", + parliamentVotesSlashTotal: "Vots/Total", + parliamentVotesNeeded: "Vots necessaris", + parliamentFutureLawsTitle: "Futures lleis", + parliamentVoteAction: "Votar", + parliamentThLeader: "AVATAR", + parliamentPopulation: "Població", + parliamentThPresented: "CANDIDATURES", + parliamentNoLeaders: "Encara no hi ha líders.", + parliamentCandidaturesListTitle: "Llista de candidatures", + parliamentTimeRemaining: "Temps restant", + parliamentElectionsStatusTitle: "Pròxim govern", + parliamentProposalDeadlineLabel: "Data límit", + parliamentViewReport: "Veure l'informe", + parliamentProposalTimeLeft: "Temps restant", + parliamentRulesTitle: "Com funciona el Parlament", + parliamentRulesIntro: "Les eleccions es resolen cada 2 mesos; les candidatures són contínues i es reinicien quan s'elegeix un govern.", + parliamentRulesCandidates: "Qualsevol habitant es pot proposar a si mateix, a un altre habitant o a qualsevol tribu. Cada habitant pot proposar fins a 3 candidatures per cicle; es rebutgen els duplicats dins el mateix cicle.", + parliamentRulesElection: "Guanya la candidatura amb més vots en el moment de la resolució.", + parliamentRulesTies: "Desempat: més karma de l'habitant; si persisteix, perfil més antic; si persisteix, proposta més primerenca; després, ordre lexicogràfic per ID.", + parliamentRulesTerm: "La pestanya Govern mostra el govern actual i les seves estadístiques.", + parliamentRulesMethods: "Formes: Anarquia (majoria simple), Democràcia (50%+1), Majoria (80%), Minoria (20%), Karmatocràcia (propostes amb més karma), Dictadura (aprovació instantània).", + parliamentRulesAnarchy: "L'Anarquia és el mode per defecte: si en resoldre no s'elegeix cap candidatura, es proclama l'Anarquia. En Anarquia, qualsevol habitant pot proposar lleis.", + parliamentRulesProposals: "Si ets l'habitant governant o membre de la tribu governant, pots publicar propostes de llei. Els mètodes no dictatorials creen una votació pública.", + parliamentRulesLimit: "Cada habitant pot publicar com a màxim 3 propostes de llei per cicle.", + parliamentRulesLaws: "Quan una proposta arriba al seu llindar, es converteix en Llei i apareix a la pestanya Lleis amb la seva data d'entrada en vigor.", + parliamentRulesHistorical: "A Històric es pot veure cada cicle de govern que hi ha hagut i dades sobre la seva gestió.", + parliamentRulesLeaders: "A Líders es pot veure un rànquing d'habitants/tribus que han governat (o s'han presentat), ordenats per eficàcia.", + parliamentRulesHemicycle: "L'hemicicle dona un escó per habitant: el bloc de govern (votants de la candidatura guanyadora i el líder), un bloc d'oposició per cada altra candidatura del cicle, i l'abstenció. Les marques discontínues assenyalen el quòrum d'elecció (25 % del cens) i el llindar d'aprovació del mètode vigent. \"Per casa del LARP\" acoloreix els mateixos escons per casa.", + parliamentProposalVoteStatusLabel: "Estat de la votació", + parliamentProposalOnTrackYes: "Llindar assolit", + parliamentProposalOnTrackNo: "Per sota del llindar", + courtsTitle: "Tribunals", + courtsDescription: "Explora formes de resolució de conflictes i de gestió col·lectiva de la justícia.", + courtsFilterCases: "CASOS", + courtsFilterMyCases: "MEUS", + courtsFilterJudges: "JUTGES", + courtsFilterHistory: "HISTORIAL", + courtsFilterRules: "REGLES", + courtsCaseFormTitle: "Obrir un cas", + courtsCaseRespondent: "Acusat / Part demandada", + courtsCaseRespondentPh: "ID d'Oasis (@...) o nom de la tribu", + courtsCaseSubjectPlaceholder: "Assumpte o descripció breu", + courtsCaseMethod: "Mètode de resolució", + courtsCaseSubmit: "Presentar el cas", + courtsNominateJudge: "Nominar un jutge", + courtsJudgeId: "Jutge", + courtsJudgeIdPh: "ID d'Oasis (@...) o nom de l'habitant", + courtsNominateBtn: "Nominar", + courtsAddEvidence: "Afegir proves", + courtsEvidenceText: "Text", + courtsEvidenceLink: "Enllaç", + courtsEvidenceLinkPh: "https://…", + courtsEvidenceSubmit: "Adjuntar", + courtsAnswerText: "Resum de la resposta", + courtsAnswerSubmit: "Enviar la resposta", + courtsVerdictTitle: "Emetre veredicte", + courtsVerdictOrdersPh: "Accions, terminis, passos restauratius...", + courtsIssueVerdict: "Emetre veredicte", + courtsMediationPropose: "Proposar un acord", + courtsSettlementProposeBtn: "Proposar", + courtsNominationsTitle: "Nominacions a la judicatura", + courtsThJudge: "Jutge", + courtsThDate: "Data", + courtsThVote: "Votar", + courtsNoNominations: "Encara no hi ha nominacions.", + courtsRespondentInvalid: "Ha de ser un ID SSB vàlid (@...ed25519)", + courtsCaseMediatorsRespondentTitle: "Afegir mediadors de la defensa", + courtsCaseMediatorsRespondent: "Mediadors (defensa)", + courtsMediatorsAccuserLabel: "Mediadors (acusació)", + courtsMediatorsRespondentLabel: "Mediadors (defensa)", + courtsMediatorsSubmit: "Desar els mediadors", + courtsNoMyCases: "Encara no tens conflictes.", + courtsNoHistory: "Encara no hi ha judicis registrats.", + courtsMethodDICTATOR: "Dictador", + courtsMethodPOPULAR: "Popular", + courtsMethodKARMATOCRACY: "Karmatocràcia", + courtsCaseType: "Tipus de cas", + courtsPresetMinorConflict: "Conflicte lleu", + courtsPresetModerateConflict: "Conflicte moderat", + courtsPresetSevereConflict: "Conflicte greu", + courtsPresetHarassment: "Assetjament o abús", + courtsPresetContentModeration: "Moderació de contingut", + courtsPresetBanRestriction: "Expulsió o restricció", + courtsPresetGovernanceDispute: "Disputa de governança", + courtsStatusOPEN: "Obert", + courtsStatusIN_PROGRESS: "En curs", + courtsStatusDECIDED: "Decidit", + courtsStatusCLOSED: "Tancat", + courtsStatusSOLVED: "Resolt", + courtsStatusUNSOLVED: "Sense resoldre", + courtsStatusDISCARDED: "Descartat", + courtsRulesTitle: "Com funcionen els Tribunals", + courtsRulesIntro: "Els Tribunals són un procés gestionat per la comunitat per resoldre conflictes i promoure la justícia restaurativa. Es prioritzen el diàleg, les proves clares i els remeis proporcionals.", + courtsRulesLifecycle: "Procés: 1) Obrir el cas 2) Seleccionar el mètode 3) Presentar proves 4) Audiència i deliberació 5) Veredicte i remei 6) Compliment i tancament 7) Apel·lació (si escau).", + courtsRulesRoles: "Acusació: obre el cas. Defensa: habitant o tribu acusada. Mètode: mecanisme triat per la comunitat per facilitar, avaluar les proves i emetre veredicte. Testimoni: aporta testimoniatge o proves. Mediadors: habitants neutrals convidats per l'acusació i/o la defensa, amb accés a tots els detalls, que ajuden a desescalar el conflicte i a cocrear acords.", + courtsRulesEvidence: "Descripció de fins a 1000 caràcters. Adjunta imatges, àudio, vídeo i documents PDF rellevants i legals. No comparteixis dades privades sensibles sense consentiment.", + courtsRulesDeliberation: "Les audiències poden ser públiques o privades. Els jutges garanteixen el respecte, demanen aclariments i poden descartar material irrellevant o il·legal.", + courtsRulesVerdict: "Es prioritza la restauració: disculpes, acords de mediació, moderació de contingut, restriccions temporals o altres mesures proporcionals. El raonament ha de quedar registrat.", + courtsRulesAppeals: "Apel·lació: permesa quan hi ha proves noves o un error processal clar. S'ha de presentar en un termini de 7 dies llevat que s'indiqui el contrari.", + courtsRulesPrivacy: "Respecta la privadesa i la seguretat. El doxing, l'odi o les amenaces s'eliminen. Els jutges poden editar o segellar parts de l'expedient per protegir habitants en risc.", + courtsRulesMisconduct: "L'assetjament, la manipulació o les proves fabricades poden comportar una resolució negativa immediata.", + courtsRulesGlossary: "Cas: registre d'un conflicte. Proves: materials que avalen les afirmacions. Veredicte: decisió amb remei. Apel·lació: sol·licitud per revisar el veredicte.", + courtsFilterActions: "ACCIONS", + courtsThRole: "Rol", + courtsRoleAccuser: "Acusació", + courtsRoleDefence: "Defensa", + courtsRoleMediator: "Mediador", + courtsRoleJudge: "Jutge", + courtsRoleDictator: "Dictador", + courtsAssignJudgeTitle: "Triar un jutge", + courtsAssignJudgeBtn: "Triar un jutge", + trendingTitle: "Tendències", + exploreTrending: "Explora el contingut més popular de la teva xarxa.", + votesButton: "GOVERN", + shopProductButton: "BOTIGA", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISODI", + campaignButton: "CAMPANYA", + logisticsRouteButton: "RUTA", + trendingTotalOpinions: "Total d'opinions", + trendingNoContentMessage: "Encara no hi ha tendències.", + trendingTotalCount: "Total de comptadors", + tasksTitle: "Tasques", + tasksDescription: "Descobreix i gestiona tasques a la teva xarxa.", + taskSearchPlaceholder: "Cercar a Tasques...", + taskTitleLabel: "Títol", + taskDescriptionLabel: "Descripció", + taskStartTimeLabel: "Data d'inici", + taskEndTimeLabel: "Data de fi", + taskPriorityLabel: "Prioritat", + taskPriorityUrgent: "Urgent", + taskPriorityHigh: "Alta", + taskPriorityMedium: "Mitjana", + taskPriorityLow: "Baixa", + taskLocationLabel: "Ubicació", + taskTagsLabel: "Etiquetes", + taskVisibilityLabel: "Visibilitat", + taskPublic: "públic", + taskPrivate: "privat", + taskStatus: "Estat", + taskStatusOpen: "Oberta", + taskStatusInProgress: "En curs", + taskStatusClosed: "Tancada", + taskAssignedTo: "Assignada", + taskAssignedChip: "Assignada", + taskUnassignedChip: "Sense assignar", + taskAssignees: "Assignats", + taskAssignButton: "Assignar-me-la", + taskUnassignButton: "Desassignar", + taskCreateButton: "Crear una tasca", + taskUpdateButton: "Actualitzar", + taskDeleteButton: "Eliminar", + taskFilterAll: "TOTES", + taskFilterRecent: "RECENTS", + taskFilterMine: "MEVES", + taskFilterOpen: "OBERTES", + taskFilterInProgress: "EN CURS", + taskFilterClosed: "TANCADES", + taskFilterAssigned: "ASSIGNADES", + taskFilterUrgent: "URGENTS", + taskFilterHigh: "ALTES", + taskFilterMedium: "MITJANES", + taskFilterLow: "BAIXES", + taskAllSectionTitle: "Tasques", + taskMineSectionTitle: "Les teves tasques", + taskCreateSectionTitle: "Crear una tasca", + taskUpdateSectionTitle: "Actualitzar", + taskOpenTitle: "Tasques obertes", + taskInProgressTitle: "Tasques en curs", + taskClosedTitle: "Tasques tancades", + taskAssignedTitle: "Tasques assignades", + notasks: "No hi ha tasques disponibles.", + noLocation: "No s'ha especificat cap ubicació", + taskSetStatus: "Establir l'estat", + eventDateLabel: "Data", + eventRecurrenceLabel: "Recurrència", + eventRecurrenceUntil: "Repetir fins a", + eventRecurrenceHint: "Deixa buida la data de repetició per a un esdeveniment únic.", + eventUpcomingDates: "Pròximes dates", + eventsTitle: "Esdeveniments", + eventsDescription: "Descobreix i gestiona esdeveniments a la teva xarxa.", + eventSearchPlaceholder: "Cercar a Esdeveniments...", + eventPrice: "Preu", + eventStatus: "Estat", + eventAllSectionTitle: "Esdeveniments", + eventMineSectionTitle: "Els teus esdeveniments", + eventCreateSectionTitle: "Crear un esdeveniment", + eventUpdateSectionTitle: "Actualitzar l'esdeveniment", + eventCreateButton: "Crear un esdeveniment", + eventAddToCalendar: "Afegir al calendari", + eventVisitCalendar: "Visitar el calendari", + viewTask: "Veure la tasca", + viewEvent: "Veure l'esdeveniment", + viewPad: "Veure el pad", + viewMap: "Veure el mapa", + viewCalendar: "Veure el calendari", + viewReport: "Veure l'informe", + viewTransfer: "Veure la transferència", + viewItem: "Veure l'article", + viewShop: "Veure la botiga", + viewProject: "Veure el projecte", + viewJob: "Veure la feina", + viewPlace: "Veure el lloc", + generatePdf: "Generar PDF", + sharePm: "Compartir per PM", + galleryImages: "Imatges (mida màx.: 75MB cadascuna)", + galleryPhotos: "Imatges", + galleryAddPhoto: "Afegir una imatge", + galleryRemovePhoto: "Treure", + galleryVideo: "Vídeo (mida màx.: 75MB)", + galleryAddVideo: "Afegir un vídeo", + galleryRemoveVideo: "Treure el vídeo", + viewVotation: "Veure la votació", + voteCastTitle: "Emetre el vot", + voteResults: "Resultats", + eventTitleLabel: "Títol", + eventDescriptionLabel: "Descripció", + eventDescriptionPlaceholder: "Introdueix la descripció de l'esdeveniment...", + eventUpdateButton: "Actualitzar", + eventTagsLabel: "Etiquetes", + eventPriceLabel: "Preu", + eventUrlLabel: "URL", + eventAttendees: "Assistents", + eventLocation: "Ubicació", + eventLocationLabel: "Ubicació", + noevents: "No hi ha esdeveniments disponibles.", + eventDate: "Data", + eventAttendButton: "Assistir a l'esdeveniment", + eventFilterAll: "TOTS", + eventFilterRecent: "RECENTS", + eventFilterMine: "MEUS", + eventFilterToday: "AVUI", + eventFilterWeek: "AQUESTA SETMANA", + eventFilterMonth: "AQUEST MES", + eventFilterYear: "AQUEST ANY", + eventFilterArchived: "ARXIVATS", + eventPrivacyLabel: "Visibilitat", + eventPublic: "Públic", + eventPrivate: "Privat", + eventAttended: "Hi assisteixo", + eventUnattended: "No hi assisteixo", + eventStatusOpen: "Obert", + eventStatusClosed: "Tancat", + tagsTitle: "Etiquetes", + tagsDescription: "Descobreix i explora patrons de taxonomia a la teva xarxa.", + tagsAllSectionTitle: "Etiquetes", + tagsTopSectionTitle: "Etiquetes principals", + tagsCloudSectionTitle: "Núvol d'etiquetes", + tagsFilterAll: "TOTES", + tagsFilterMine: "MEVES", + tagsFilterRecent: "RECENTS", + tagsMineSectionTitle: "Les meves etiquetes", + tagsRecentSectionTitle: "Etiquetes recents", + tagsFilterTop: "TOP", + tagsFilterCloud: "NÚVOL", + tagsNoItems: "No hi ha etiquetes disponibles.", + tagsSearchPlaceholder: "Cercar a Etiquetes...", + tagsTableHeaderTag: "ETIQUETA/ENLLAÇ:", + tagsTableHeaderCount: "COMPTADOR:", + transfersTitle: "Transferències", + transfersDescription: "Descobreix i gestiona transferències a la teva xarxa.", + transfersFrom: "De", + transfersTo: "A", + transfersFilterAll: "TOTES", + transfersFilterRecent: "RECENTS", + transfersFilterMine: "MEVES", + transfersFilterMinePending: "PENDENTS", + transfersFilterMineConfirmed: "SIGNADES", + transfersFilterUBI: "RBU", + transfersFilterMarket: "MERCAT", + transfersFilterTop: "TOP", + transfersFilterPending: "PENDENTS", + transfersFilterUnconfirmed: "NO CONFIRMADES", + transfersFilterClosed: "TANCADES", + transfersFilterDiscarded: "DESCARTADES", + transfersFilterEconomic: "ECONÒMICA", + transfersFilterTime: "TEMPS", + transfersFilterTrust: "CONFIANÇA", + transfersCategory: "Categoria", + transfersCategoryEconomic: "Econòmica", + transfersCategoryTime: "Temps", + transfersCategoryTrust: "Confiança", + transfersUnitEco: "ECO", + transfersUnitHours: "h", + transfersUnitTrust: "confiança", + transfersEconomicSectionTitle: "Transferències econòmiques", + transfersTimeSectionTitle: "Transferències de temps", + transfersTrustSectionTitle: "Transferències de confiança", + transfersCreateButton: "Crear una transferència", + transfersUpdateButton: "Actualitzar", + transfersDeleteButton: "Eliminar", + transfersToUser: "ID d'Oasis", + transfersToUserValidation: "ID d'Oasis vàlid, p. ex. @…=.ed25519", + transfersConcept: "Concepte", + transfersAmount: "Quantitat", + transfersDeadline: "Termini", + transfersTags: "Etiquetes", + transfersStatus: "Estat", + transfersConfirmations: "CONFIRMACIONS", + transfersExportContract: "Generar la factura", + transfersConfirmButton: "Confirmar la transferència", + transfersNoItems: "No s'han trobat transferències.", + transfersMineSectionTitle: "Les teves transferències", + transfersUBISectionTitle: "Transferències RBU", + transfersTopSectionTitle: "Transferències principals", + transfersPendingSectionTitle: "Transferències pendents", + transfersUnconfirmedSectionTitle: "Transferències no confirmades", + transfersClosedSectionTitle: "Transferències tancades", + transfersDiscardedSectionTitle: "Transferències descartades", + transfersCreateSectionTitle: "Crear una transferència", + transfersAllSectionTitle: "Transferències", + transfersSearchPlaceholder: "Cercar a Transferències...", + transfersMinAmountLabel: "Import mín.", + transfersMaxAmountLabel: "Import màx.", + transfersSortRecent: "Més recents", + transfersSortAmount: "Import més alt", + transfersSortDeadline: "Termini més proper", + transfersSearchButton: "Cercar", + transfersExpiredBadge: "CADUCADA", + transfersUpdatedAt: "Actualitzat", + transfersNoMatch: "No hi ha transferències que coincideixin amb la teva cerca.", + votationsTitle: "Votacions", + votationsDescription: "Descobreix i gestiona votacions a la teva xarxa.", + voteMineSectionTitle: "Les teves votacions", + voteCreateSectionTitle: "Crear una votació", + voteUpdateSectionTitle: "Actualitzar", + voteOpenTitle: "Votacions obertes", + voteClosedTitle: "Votacions tancades", + voteAllSectionTitle: "Govern", + voteCreateButton: "Crear una votació", + voteErrorTitle: "Revisa el formulari", + voteErrorDeadlineMin: "La data límit ha de ser d'almenys {days} dies a partir d'ara.", + voteErrorGeneric: "No s'ha pogut desar la votació.", + voteLocked: "Aquesta votació ja ha rebut vots o opinions, o pertany al Parlament o als Tribunals: ja no es pot editar ni esborrar.", + voteUpdateButton: "Actualitzar", + voteDeleteButton: "Eliminar", + voteOptionYes: "SÍ", + voteOptionNo: "NO", + voteOptionAbstention: "ABSTENCIÓ", + voteConfused: "CONFÓS", + voteFollowMajority: "SEGUIR LA MAJORIA", + voteNotInterested: "SENSE INTERÈS", + voteFilterAll: "TOTES", + voteFilterRecent: "RECENTS", + voteFilterMine: "MEVES", + voteFilterOpen: "OBERTES", + voteFilterClosed: "TANCADES", + voteQuestionLabel: "Pregunta", + voteDeadlineLabel: "Termini", + voteOptionsLabel: "El teu vot", + voteBreakdown: "Desglossament", + voteFinalResult: "RESULTAT", + voteNoQuorum: "SENSE QUÒRUM", + voteTagsLabel: "Etiquetes", + voteDeadline: "Termini", + voteStatus: "Estat", + voteTags: "Etiquetes", + voteOpinions: "Opinions", + novotes: "No hi ha propostes de votació disponibles.", + voteCreatedAt: "Creat el", + voteNoQuestion: "No s'ha proporcionat cap pregunta", + voteUnknownCreator: "Creador desconegut", + voteUnknownDate: "Data desconeguda", + voteDetailSectionTitle: "Detalls de la votació", + voteCommentsLabel: "Comentaris", + voteCommentsForumButton: "Obrir la conversa", + voteCommentsSectionTitle: "Conversa oberta", + voteNoCommentsYet: "Encara no hi ha comentaris. Sigues la primera persona a opinar.", + commentsGoToLast: "Anar al darrer comentari", + voteNewCommentPlaceholder: "Escriu aquí el teu comentari…", + voteNewCommentButton: "Publicar el comentari", + voteNewCommentLabel: "Afegeix un comentari", + cvTitle: "CV", + cvEditSectionTitle: "Editar el CV", + cvCreateSectionTitle: "Crear un CV", + cvDescription: "Gestiona i comparteix les teves habilitats professionals i la teva informació.", + cvNameLabel: "Nom complet", + cvDescriptionLabel: "Resum", + cvPhotoLabel: "Foto", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Visitar el CV", + cvPersonalExperiencesLabel: "Experiències personals", + cvPersonalSkillsLabel: "Habilitats personals (separades per comes)", + cvOasisExperiencesLabel: "Experiències de contribució a Oasis", + cvOasisSkillsLabel: "Habilitats de contribució a Oasis (separades per comes)", + cvEducationExperiencesLabel: "Experiències educatives", + cvEducationalSkillsLabel: "Habilitats educatives (separades per comes)", + cvProfessionalExperiencesLabel: "Experiències professionals", + cvProfessionalSkillsLabel: "Habilitats professionals (separades per comes)", + cvLanguagesLabel: "Idiomes", + cvLocationLabel: "Ubicació", + cvStatusLabel: "Estat", + cvPreferencesLabel: "Preferències", + cvPersonal: "Personal", + cvOasis: "Contribuïdor d'Oasis (opcional)", + cvOasisContributorView: "Contribució", + cvEducational: "Educatiu (opcional)", + cvEducationalView: "Educatiu", + cvProfessional: "Professional (opcional)", + cvProfessionalView: "Professional", + cvAvailability: "Disponibilitat (opcional)", + cvUpdateButton: "Actualitzar", + cvCreateButton: "Crear un CV", + cvCreatedAt: "Creat el", + cvUpdatedAt: "Actualitzat el", + cvEditButton: "Actualitzar", + cvDeleteButton: "Eliminar", + cvNoCV: "No s'ha trobat cap CV.", + blogSubject: "Assumpte", + blogMessage: "Missatge", + blogImage: "Pujar contingut multimèdia (màx.: 75MB)", + blogMedia: "Adjunts (fins a 8 imatges i un vídeo)", + blogPublish: "Publicar", + pollsTitle: "Enquestes", + dataTitle: "Coincidències", + dataDescription: "Explora i visualitza les coincidències de la teva xarxa.", + dataFilterAll: "TOT", + dataFilterMine: "MEUS", + dataFilterRecent: "RECENTS", + dataFilterTop: "TOP", + dataKindInhabitants: "HABITANTS", + dataKindJobs: "FEINES", + dataKindProjects: "PROJECTES", + dataKindEvents: "ESDEVENIMENTS", + dataKindTribes: "TRIBUS", + dataKindMarket: "MERCAT", + dataKindHousing: "HABITATGE", + dataKindIndustry: "INDÚSTRIA", + dataKindTasks: "TASQUES", + dataKindReports: "INFORMES", + dataKindVotes: "VOTACIONS", + dataSearchPlaceholder: "Cercar a Coincidències...", + dataCohesionTitle: "Coeficient de cohesió (CC)", + dataCcShort: "CC", + dataCohesionHint: "Afinitat mitjana entre tot el que ha publicat la xarxa.", + dataAffinity: "Afinitat", + dataCommonTerms: "Paraula clau", + dataReasonFollowing: "Dones suport a aquest habitant", + dataReasonTribe: "Comparteix tribu amb tu", + dataReasonPinned: "Coincideix amb alguna cosa que has fixat", + dataReasonCv: "Coincideix amb les habilitats del teu CV", + dataReasonContent: "Relacionat amb el que publiques", + dataReasonRelated: "Relacionat amb els teus interessos", + dataReasonAlike: "Publicat per un habitant afí a tu", + dataReasonSupportsYou: "Et dona suport", + dataReasonMutual: "Suport mutu", + dataReasonRated: "Ben valorat per la xarxa", + dataReasonNear: "A prop teu", + dataStatPeople: "CV", + dataStatConnected: "Connectats", + dataStatSkills: "Habilitats", + dataBestMatch: "Millor coincidència", + dataStatIsolated: "Aïllats", + dataStatEntities: "Entitats comparades", + dataStatTerms: "Termes diferents", + dataStatPairs: "Parells connectats", + dataMatchesTitle: "Coincidències", + dataMatchesHint: "Suggeriments personalitzats de l'algorisme.", + dataTermsTitle: "Paraules clau", + dataTermsHint: "Els termes que apareixen en més continguts.", + dataConnections: "Coincidències enllaçades", + dataTopicTitle: "Tot el relacionat amb", + dataTopicHint: "El que l'algorisme relaciona amb aquest tema", + dataNoMatches: "No hi ha dades creuades per mostrar.", + dataNoProfile: "Publica contingut o escriu el teu CV perquè l'algorisme aprengui què t'interessa.", + pollsDescription: "Mòdul per preguntar a la xarxa i comptar les respostes.", + pollFilterAll: "TOTES", + pollFilterMine: "MEVES", + pollFilterRecent: "RECENTS", + pollFilterTop: "TOP", + pollFilterVoted: "VOTADES", + pollFilterOpen: "OBERTES", + pollFilterClosed: "TANCADES", + pollCreateButton: "Crear una enquesta", + pollCreateTitle: "Crear una enquesta", + pollEditTitle: "Editar l'enquesta", + pollQuestion: "Pregunta", + pollQuestionPlaceholder: "Què vols preguntar?", + pollOptions: "Opcions (una per línia)", + pollOutcome: "Resultat", + pollNoVotesYet: "Encara sense vots", + pollTie: "Empat", + pollOptionsPlaceholder: "Plaça\nParc\nBar", + pollAnonymous: "Anònima", + pollAnonymousLabel: "Enquesta anònima", + pollMultiple: "Resposta múltiple", + pollMultipleLabel: "Permetre diverses respostes", + pollDeadline: "Data límit", + pollTags: "Etiquetes", + pollPublishButton: "Publicar l'enquesta", + pollUpdateButton: "Actualitzar", + pollCloseButton: "Tancar", + pollDeleteButton: "Eliminar", + pollVoteButton: "Votar", + pollChangeVote: "Canviar el vot", + pollVoters: "Votants", + pollComments: "Comentaris", + pollStatusLabel: "Estat", + pollStatusOpen: "OBERTA", + pollStatusClosed: "TANCADA", + pollSearchPlaceholder: "Cercar a Enquestes...", + pollsNoItems: "No s'han trobat enquestes.", + viewPoll: "Veure l'enquesta", + pollChatCreate: "Crear una enquesta", + pollInChat: "Enquesta", + blogTitle: "Blogs", + blogDescription: "Mòdul per descobrir i gestionar blogs.", + blogFilterAll: "TOTS", + blogFilterMine: "MEUS", + blogFilterRecent: "RECENTS", + blogFilterFavorites: "PREFERITS", + blogFilterTop: "TOP", + blogCreateButton: "Crear un blog", + blogSearchPlaceholder: "Cercar a Blogs...", + blogComments: "Comentaris", + blogAllowComments: "Permetre comentaris", + blogCommentsClosed: "L'autor ha tancat els comentaris d'aquest blog.", + blogNoItems: "No s'han trobat blogs.", + viewBlog: "Veure el blog", + forumTitle: "Fòrums", + forumCategoryLabel: "Categoria", + forumTitleLabel: "Títol", + forumTitlePlaceholder: "Títol del fòrum...", + forumCreateButton: "Crear un fòrum", + forumCreateSectionTitle: "Crear un fòrum", + forumDescription: "Parla obertament amb habitants de la teva xarxa.", + forumSearchPlaceholder: "Cercar a Fòrums...", + forumFilterAll: "TOTS", + forumFilterMine: "MEUS", + forumFilterRecent: "RECENTS", + forumFilterTop: "TOP", + forumParticipants: "participants", + forumMessages: "missatges", + forumMessageLabel: "Missatge", + forumMessagePlaceholder: "Escriu el teu missatge...", + forumSendButton: "Enviar", + noForums: "Encara no hi ha fòrums disponibles.", + forumVisitButton: "Visitar el fòrum", + forumTopicLabel: "Tema", + imageTitle: "Imatges", + imageDescription: "Explora i gestiona contingut d'imatges a la teva xarxa.", + imageMineSectionTitle: "Les teves imatges", + imageCreateSectionTitle: "Pujar una imatge", + imageUpdateSectionTitle: "Actualitzar la imatge", + imageAllSectionTitle: "Imatges", + imageRecentSectionTitle: "Imatges recents", + imageTopSectionTitle: "Imatges top", + imageFavoritesSectionTitle: "Preferits", + imageGallerySectionTitle: "Galeria", + imageFilterAll: "TOTES", + imageFilterMine: "MEVES", + imageFilterRecent: "RECENTS", + imageFilterTop: "TOP", + imageFilterFavorites: "PREFERITS", + imageFilterGallery: "GALERIA", + imageCreateButton: "Pujar una imatge", + imageUpdateButton: "Actualitzar", + imageAddFavoriteButton: "Afegir a preferits", + imageRemoveFavoriteButton: "Treure de preferits", + imageFileLabel: "Selecciona un fitxer d'imatge (.jpeg, .jpg, .png, .gif)", + imageTagsLabel: "Etiquetes", + imageTagsPlaceholder: "Introdueix etiquetes separades per comes", + imageTitleLabel: "Títol", + imageTitlePlaceholder: "Opcional", + imageDescriptionLabel: "Descripció", + imageDescriptionPlaceholder: "Opcional", + imageNoFile: "No s'ha proporcionat cap fitxer d'imatge", + noImages: "No hi ha imatges disponibles.", + imageSearchPlaceholder: "Cercar a Imatges...", + imageSortRecent: "Més recents", + imageSortOldest: "Més antigues", + imageSortTop: "Més votades", + imageSearchButton: "Cercar", + imageUpdatedAt: "Actualitzat", + imageNoMatch: "Cap imatge no coincideix amb la teva cerca.", + feedSearchPlaceholder: "Cercar a Feed...", + feedTitle: "Feed", + feedDetailTitle: "Feed", + feedOpenDiscussion: "Obrir el debat", + feedPostComment: "Publicar el comentari", + noComments: "Encara no hi ha comentaris", + createFeedTitle: "Crear un feed", + createFeedButton: "Enviar el feed!", + feedPlaceholder: "Què està passant? (màxim 280 caràcters)", + TODAYButton: "AVUI", + totalOpinions: "Total d'opinions", + moreVoted: "Més votat", + noFeedsFound: "No s'han trobat feeds.", + createdAtLabel: "Creat el", + FeedshareYourOpinions: "Descobreix i comparteix textos breus a la teva xarxa.", + refeedButton: "Reenviar", + alreadyRefeeded: "Ja has realimentat això.", + activityTitle: "Activitat", + yourActivity: "La teva activitat", + globalActivity: "Activitat", + activityList: "Activitat", + activityDesc: "Consulta l'activitat més recent de la teva xarxa.", + allButton: "TOT", + mineButton: "MEVES", + noActions: "No hi ha activitat disponible.", + from: "De", + to: "A", + amount: "Quantitat", + concept: "Concepte", + description: "Descripció", + meme: "Mem", + typeRecent: "RECENTS", + typeTop: "TOP", + typePost: "BLOG", + activityUpdateLabel: "ACTUALITZACIÓ", + typeTribe: "TRIBUS", + typeLarp: "L.A.R.P.", + typeInhabitants: "HABITANTS", + typeAbout: "HABITANTS", + typeCurriculum: "CV", + typeImage: "IMATGES", + typeBookmark: "MARCADORS", + typeDocument: "DOCUMENTS", + typeVotes: "VOTACIONS", + typeAudio: "ÀUDIOS", + typeMarket: "MERCAT", + typeJob: "FEINES", + typeProject: "PROJECTES", + typeVideo: "VÍDEOS", + typeVote: "DIFUSIÓ", + typeEvent: "ESDEVENIMENTS", + typeTransfer: "TRANSFERÈNCIES", + typeTask: "TASQUES", + typePixelia: "PIXELIA", + typeForum: "FÒRUMS", + typeReport: "INFORMES", + typeFeed: "FEEDS", + typeContact: "CONTACTE", + typePub: "PUB", + typeTombstone: "ELIMINAT", + typeBanking: "BANCA", + typeBankWallet: "BANCA/MONEDER", + typeBankClaim: "BANCA/RBU", + typeKarmaScore: "KARMA", + typeParliament: "PARLAMENT", + typeSpread: "RÈPLIQUES", + typeParliamentCandidature: "Parlament · Candidatura", + typeParliamentTerm: "Parlament · Mandat", + typeParliamentProposal:"Parlament · Proposta", + typeParliamentRevocation:"Parlament · Revocació", + typeParliamentLaw: "Parlament · Nova llei", + typeCourts: "TRIBUNALS", + typeCourtsCase: "Tribunals · Cas", + typeCourtsEvidence: "Tribunals · Prova", + typeCourtsAnswer: "Tribunals · Resposta", + typeCourtsVerdict: "Tribunals · Veredicte", + typeCourtsSettlement: "Tribunals · Acord", + typeCourtsSettlementProposal: "Tribunals · Proposta d'acord", + typeCourtsSettlementAccepted: "Tribunals · Acord acceptat", + typeCourtsNomination: "Tribunals · Nominació", + typeCourtsNominationVote: "Tribunals · Votació de nominació", + question: "Pregunta", + deadline: "Data límit", + status: "Estat", + votes: "Vots", + totalVotes: "Vots totals", + voteTotalVotes: "Vots totals", + name: "Nom", + skills: "Habilitats", + tags: "Etiquetes", + title: "Títol", + date: "Data", + category: "Categoria", + attendees: "Assistents", + location: "Ubicació", + contentWarning: "Assumpte", + bankWalletConnected: "Moneder ECOin", + bankUbiReceived: "RBU rebuda", + bankTx: "Tx", + bankEpochShort: "Època", + viewDetails: "Veure els detalls", + link: "Enllaç", + activityProjectFollow: "%OASIS% ara està %ACTION% aquest projecte %PROJECT%", + activityProjectUnfollow: "%OASIS% ara està %ACTION% aquest projecte %PROJECT%", + following: "SEGUINT", + unfollowing: "DEIXANT DE SEGUIR", + pledged: "APORTAT", + parliamentCandidatureId: "Candidatura", + parliamentGovMethod: "Mètode", + parliamentVotesReceived: "Vots rebuts", + parliamentMethodANARCHY: "Anarquia", + parliamentMethodVOTE: "Votació comunitària", + parliamentMethodRANKED: "Vot preferencial", + parliamentMethodPLURALITY: "Pluralitat", + parliamentMethodCOUNCIL: "Consell", + parliamentMethodJURY: "Jurat", + parliamentAnarchy: "ANARQUIA", + parliamentElectionsStart: "Inici de les eleccions", + parliamentElectionsEnd: "Fi de les eleccions", + parliamentCurrentLeader: "Candidatura guanyadora", + parliamentProposalTitle: "Títol", + parliamentOpenVote: "Votació oberta", + parliamentStatus: "Estat", + parliamentLawQuestion: "Pregunta", + parliamentLawMethod: "Mètode", + parliamentLawProposer: "Proponent", + parliamentLawEnacted: "Promulgada", + parliamentLawVotes: "Vots", + createdAt: "Creat el", + courtsCaseTitle: "Cas", + courtsMethod: "Mètode", + courtsMethodJUDGE: "Jutge", + courtsMethodJUDGES: "Tribunal de jutges", + courtsMethodSINGLE_JUDGE: "Jutge únic", + courtsMethodJURY: "Jurat", + courtsMethodCOUNCIL: "Consell", + courtsMethodCOMMUNITY: "Comunitat", + courtsMethodARBITRATION: "Arbitratge", + courtsMethodVOTE: "Votació comunitària", + courtsAccuser: "Acusació", + courtsRespondent: "Defensa", + courtsThStatus: "Estat", + courtsThAnswerBy: "Respondre abans de", + courtsThEvidenceBy: "Proves fins a", + courtsThDecisionBy: "Decisió abans de", + courtsVotesNeeded: "Vots necessaris", + courtsVotesSlashTotal: "SÍ/TOTAL", + courtsOpenVote: "Votació oberta", + courtsAnswerTitle: "Resposta", + courtsVerdictResult: "Resultat", + courtsVerdictOrders: "Ordres", + courtsSettlementText: "Acord", + courtsSettlementAccepted: "Acceptat", + courtsJudge: "Jutge", + courtsThSupports: "Suports", + courtsFilterOpenCase: "Obrir un cas", + courtsCaseMediatorsPh: "IDs d'Oasis dels mediadors, separats per comes", + courtsMediatorsLabel: "Mediadors", + courtsThCase: "Cas", + courtsThCreatedAt: "Data d'inici", + courtsPublicPrefLabel: "Visibilitat després de la resolució", + courtsPublicPrefYes: "Accepto que aquest cas sigui totalment públic", + courtsPublicPrefNo: "Prefereixo mantenir privats els detalls", + courtsPublicPrefSubmit: "Desar la preferència de visibilitat", + courtsMethodMEDIATION: "Mediació", + courtsNoCases: "No hi ha casos.", + reportsTitle: "Informes", + reportsDescription: "Gestiona i segueix els informes de la teva xarxa.", + reportsSearchPlaceholder: "Cercar a Informes...", + reportsFilterAll: "TOTS", + reportsFilterRecent: "RECENTS", + reportsFilterTop: "TOP", + reportsFilterMine: "MEUS", + reportsFilterFeatures: "FUNCIONS", + reportsFilterBugs: "ERRORS", + reportsFilterAbuse: "ABUSOS", + reportsFilterContent: "CONTINGUT", + reportsFilterConfirmed: "CONFIRMATS", + reportsFilterResolved: "RESOLTS", + reportsFilterOpen: "OBERTS", + reportsFilterUnderReview: "EN REVISIÓ", + reportsFilterInvalid: "NO VÀLIDS", + reportsCreateButton: "Crear un informe", + reportsTitleLabel: "Títol", + reportsCategory: "Categoria", + reportsCategoryFeatures: "Funcions", + reportsCategoryBugs: "Errors", + reportsCategoryAbuse: "Abús", + reportsCategoryContent: "Contingut", + reportsUpdateButton: "Actualitzar", + reportsDeleteButton: "Eliminar", + reportsMineSectionTitle: "Els teus informes", + reportsFeaturesSectionTitle: "Sol·licituds de funcions", + reportsBugsSectionTitle: "Errors", + reportsAbuseSectionTitle: "Informes d'abús", + reportsContentSectionTitle: "Problemes de contingut", + reportsAllSectionTitle: "Informes", + reportsNoItems: "No hi ha informes disponibles.", + reportsSeverity: "Gravetat", + reportsSeverityLow: "Baixa", + reportsSeverityMedium: "Mitjana", + reportsSeverityHigh: "Alta", + reportsSeverityCritical: "Crítica", + reportsStatus: "Estat", + reportsStatusOpen: "Obert", + reportsStatusUnderReview: "En revisió", + reportsStatusResolved: "Resolt", + reportsStatusInvalid: "No vàlid", + reportsConfirmButton: "CONFIRMAR L'INFORME!", + reportsConfirmations: "Confirmacions", + reportsConfirmedSectionTitle: "Informes confirmats", + reportsCreateTaskButton: "Crear una tasca", + reportsOpenSectionTitle: "Informes oberts", + reportsUnderReviewSectionTitle: "Informes en revisió", + reportsResolvedSectionTitle: "Informes resolts", + reportsInvalidSectionTitle: "Informes no vàlids", + reportsBugTemplateTitle: "Dades per reproduir (errors)", + reportsFeatureTemplateTitle: "Detalls (funcions)", + reportsAbuseTemplateTitle: "Detalls (abús)", + reportsContentTemplateTitle: "Detalls (problemes de contingut)", + reportsStepsToReproduceLabel: "Passos per reproduir-ho", + reportsStepsToReproducePlaceholder: "1) ...\n2) ...\n3) ...", + reportsExpectedBehaviorLabel: "Resultat esperat", + reportsExpectedBehaviorPlaceholder: "Què hauria de passar?", + reportsActualBehaviorLabel: "Resultat real", + reportsActualBehaviorPlaceholder: "Què passa realment?", + reportsEnvironmentLabel: "Entorn", + reportsEnvironmentPlaceholder: "Versió, dispositiu, SO, navegador, configuració, registres, etc.", + reportsReproduceRateLabel: "Freqüència", + reportsReproduceRateAlways: "Sempre", + reportsReproduceRateOften: "Sovint", + reportsReproduceRateSometimes: "De vegades", + reportsReproduceRateRarely: "Rarament", + reportsReproduceRateUnable: "No es pot reproduir", + reportsReproduceRateUnknown: "No especificat", + reportsProblemStatementLabel: "Problema / necessitat", + reportsProblemStatementPlaceholder: "Quin problema resol aquesta funció?", + reportsUserStoryLabel: "Història d'habitant", + reportsUserStoryPlaceholder: "Com a , vull , per .", + reportsAcceptanceCriteriaLabel: "Criteris d'acceptació", + reportsAcceptanceCriteriaPlaceholder: "- Donat...\n- Quan...\n- Aleshores...", + reportsWhatHappenedLabel: "Què ha passat?", + reportsWhatHappenedPlaceholder: "Descriu l'incident amb context i dates aproximades.", + reportsReportedUserLabel: "Habitant / entitat denunciada", + reportsReportedUserPlaceholder: "@habitant, feed, ID, enllaç, etc.", + reportsEvidenceLinksLabel: "Proves / enllaços", + reportsEvidenceLinksPlaceholder: "Enganxa enllaços, IDs de missatges, captures (si escau), etc.", + reportsContentLocationLabel: "On és el contingut", + reportsContentLocationPlaceholder: "Enllaç, ID, canal, fil, autor, etc.", + reportsWhyInappropriatePlaceholder: "Explica el motiu i l'impacte.", + reportsRequestedActionLabel: "Acció sol·licitada", + reportsRequestedActionPlaceholder: "Eliminar, amagar, marcar, advertir, etc.", + tribesTitle: "Tribus", + tribeFilterAll: "TOTES", + tribeFilterRecent: "RECENTS", + tribeFilterMine: "MEVES", + tribeFilterMembership: "PERTINENÇA", + tribeFilterSubtribes: "SUBTRIBUS", + tribeFilterTop: "TOP", + tribeFilterGallery: "GALERIA", + tribeFeedFilterTOP: "TOP", + tribeFeedFilterMINE: "MEUS", + tribeFeedFilterALL: "TOTS", + tribeFeedFilterRECENT: "RECENTS", + courtsStanceDENY: "Negar", + courtsStanceADMIT: "Admetre", + courtsStancePARTIAL: "Admetre en part", + tribeAllSectionTitle: "Tribus", + tribeMineSectionTitle: "Les teves tribus", + tribeCreateSectionTitle: "Crear una tribu", + tribeUpdateSectionTitle: "Actualitzar la tribu", + tribeGallerySectionTitle: "Galeria de tribus", + tribeRecentSectionTitle: "Tribus recents", + tribeTopSectionTitle: "Tribus populars", + tribeRootLabel: "ARREL", + tribeDescription: "Explora o crea tribus a la teva xarxa.", + tribeMainTribeLabel: "TRIBU PRINCIPAL", + tribeCreateButton: "Crear una tribu", + tribeUpdateButton: "Actualitzar", + tribeDeleteButton: "Eliminar", + tribeImageLabel: "Pujar contingut multimèdia (màx.: 75MB)", + tribeTitleLabel: "Títol", + searchTribesPlaceholder: "Cercar a Tribus...", + tribeTitlePlaceholder: "Nom de la tribu", + tribeDescriptionLabel: "Descripció", + tribeDescriptionPlaceholder: "Descriu aquesta tribu", + tribeLocationLabel: "Ubicació", + tribeLocationPlaceholder: "On és ubicada aquesta tribu?", + tribeTagsLabel: "Etiquetes", + tribeTagsPlaceholder: "Introdueix etiquetes separades per comes", + tribeInviteMode: "Mode d'invitació", + tribeModeLabel: "MODE", + tribeIsAnonymousLabel: "ESTAT", + tribeMembersCount: "Membres", + tribeEnterInvite: "INTRODUIR LA INVITACIÓ", + subscriptionTitle: "Subscripcions", + subscriptionOn: "Subscrit", + subscriptionOff: "No subscrit", + subscriptionSubscribe: "Subscriure-s'hi", + subscriptionUnsubscribe: "Cancel·lar la subscripció", + subscriptionSubscribers: "Subscriptors", + pmListLabel: "Enviar a", + pmListNone: "— Sense llista —", + pmListNoSubscribers: "Sense subscriptors", + tribeLeaveButton: "SORTIR", + tribePublic: "PÚBLICA", + tribePrivate: "PRIVADA", + tribeGenerateInvite: "INVITACIÓ ÚNICA", + tribeOpenInvitation: "INVITACIÓ OBERTA", + tribeJoinOpen: "UNIR-SE A LA TRIBU", + tribeRemoveInvitation: "ELIMINAR LA INVITACIÓ", + tribeCreatedAt: "Creat el", + tribeAuthor: "Per", + tribeStrict: "Estricte", + tribeOpen: "Oberta", + tribeFeedRefeeds: "Redifusions", + tribeFeedRefeed: "Redifondre", + tribeFeedMessagePlaceholder: "Escriu un feed…", + tribeFeedSend: "Enviar", + tribeFeedEmpty: "Encara no hi ha missatges de feed disponibles.", + noTribes: "Encara no s'han trobat tribus.", + tribeNotFound: "No s'ha trobat la tribu!", + createTribeTitle: "Crear una tribu", + updateTribeTitle: "Actualitzar la tribu", + tribeSectionInhabitants: "Habitants", + tribeSectionVotations: "VOTACIONS", + tribeSectionEvents: "ESDEVENIMENTS", + tribeSectionTasks: "TASQUES", + tribeSectionFeed: "FEED", + tribeSectionForum: "FÒRUM", + tribeSectionMedia: "Multimèdia", + tribeSectionImages: "IMATGES", + tribeSectionAudios: "ÀUDIOS", + tribeSectionVideos: "VÍDEOS", + tribeSectionDocuments: "DOCUMENTS", + tribeSectionBookmarks: "MARCADORS", + tribeSectionMaps: "MAPES", + tribeSectionPads: "PADS", + tribeSectionChats: "XATS", + tribeSectionCalendars: "CALENDARIS", + tribePadCreate: "Crear un pad", + tribeChatCreate: "Crear un xat", + tribeCalendarCreate: "Crear un calendari", + tribePadsEmpty: "Encara no hi ha pads.", + tribeChatsEmpty: "Encara no hi ha xats.", + tribeCalendarsEmpty: "Encara no hi ha calendaris.", + tribeInhabitantsEmpty: "Encara no hi ha habitants en aquesta tribu.", + tribeEventCreate: "Crear un esdeveniment", + tribeEventsEmpty: "Encara no hi ha esdeveniments.", + tribeEventTitle: "Títol", + tribeEventDescription: "Descripció", + tribeEventDate: "Data", + tribeEventLocation: "Ubicació", + tribeEventAttend: "ASSISTIR-HI", + tribeEventUnattend: "NO ASSISTIR-HI", + tribeEventAttendees: "Assistents", + tribeTaskCreate: "Crear una tasca", + tribeTasksEmpty: "Encara no hi ha tasques.", + tribeTaskTitle: "Títol", + tribeTaskDescription: "Descripció", + tribeTaskPriority: "Prioritat", + tribeTaskDeadline: "Data límit", + tribeTaskAssignees: "Assignats", + tribeTaskStatusInProgress: "EN CURS", + tribeTaskStatusClosed: "TANCAR", + tribeTaskAssign: "ASSIGNAR", + tribeTaskUnassign: "DESASSIGNAR", + tribeVotationCreate: "Crear una votació", + tribeVotationsEmpty: "Encara no hi ha votacions.", + tribeVotationTitle: "Títol", + tribeVotationDescription: "Descripció", + tribeVotationOptions: "Opcions", + tribeVotationDeadline: "Data límit", + tribeVotationVote: "VOTAR", + tribeVotationResults: "Vots", + tribeVotationClose: "TANCAR LA VOTACIÓ", + tribeVotationOptionPlaceholder: "Opció", + tribeForumCreate: "Crear un fòrum", + tribeForumEmpty: "Encara no hi ha fils.", + tribeForumTitle: "Títol", + tribeForumText: "Missatge", + tribeForumCategory: "Categoria", + tribeForumReply: "Respondre", + tribeForumReplies: "Respostes", + tribeMediaUpload: "Pujar multimèdia", + readDocument: "Llegir el document", + tribeCreateImage: "Crear una imatge", + tribeCreateAudio: "Crear un àudio", + tribeCreateVideo: "Crear un vídeo", + tribeCreateDocument: "Crear un document", + tribeCreateBookmark: "Crear un marcador", + tribeMediaEmpty: "Encara no hi ha multimèdia.", + tribeMediaTitle: "Títol", + tribeMediaDescription: "Descripció", + tribeMediaTypeImage: "Imatge", + tribeMediaTypeVideo: "Vídeo", + tribeMediaTypeAudio: "Àudio", + tribeMediaTypeDocument: "Document", + tribeMediaTypeBookmark: "Marcador", + tribeContentDelete: "ELIMINAR", + tribeReachTribe: "TRIBU", + tribeReachNotice: "OASIS ho mostra a tots els habitants i CLEARNET també a la web oberta. Obrir-ho deixa una còpia llegible fora de la tribu: tornar a TRIBU ho amaga, però les còpies ja compartides no es poden esborrar.", + tribeReachPrivate: "El contingut d'una tribu privada només es pot quedar dins de la tribu.", + tribeReachOpenedBy: "Obert per", + tribeReachChange: "On es publica", + tribeInviteCodeText: "Codi d'invitació: ", + oasisVersionLabel: "Versió d'Oasis", + tribeInviteCodeHint: "Comparteix aquest codi amb qui vulguis convidar. S'hi pot unir des de /invites.", + tribeStatusOpen: "OBERT", + tribeStatusClosed: "TANCAT", + tribeStatusInProgress: "EN CURS", + tribePriorityLow: "BAIXA", + tribePriorityMedium: "MITJANA", + tribePriorityHigh: "ALTA", + tribePriorityCritical: "CRÍTICA", + tribeTaskFilterAll: "TOTES", + tribeMediaFilterAll: "TOTS", + tribeForumCatGeneral: "GENERAL", + tribeForumCatProposal: "PROPOSTA", + tribeForumCatQuestion: "PREGUNTA", + tribeForumCatAnnouncement: "ANUNCI", + tribeStatusLabel: "Estat", + tribeSubTribes: "SUBTRIBUS", + tribeSubTribesCreate: "Crear una subtribu", + tribeSubTribesStrictDenied: "El mode estricte de la tribu no et permet crear noves subtribus. Posa't en contacte amb l'administrador.", + tribeActivityRefeed: "REFEED", + tribeSectionActivity: "ACTIVITAT", + tribeSectionTrending: "TENDÈNCIES", + tribeSectionTags: "ETIQUETES", + tribeSectionSearch: "CERCAR", + tribeActivityEmpty: "Encara no hi ha activitat.", + tribeActivityCreated: "ha creat", + tribeActivityPosted: "ha publicat", + tribeActivityReplied: "ha respost", + tribeTrendingEmpty: "Encara no hi ha contingut en tendència.", + tribeTrendingPeriodDay: "Avui", + tribeTrendingPeriodWeek: "Aquesta setmana", + tribeTrendingPeriodAll: "Tot", + tribeTrendingEngagement: "interacció", + tribeTopCategory: "Més votat", + tribeTagsEmpty: "No s'han trobat etiquetes.", + tribeSearchPlaceholder: "Cercar contingut de la tribu...", + tribeSearchEmpty: "Sense resultats.", + tribeSearchResults: "Resultats", + tribeSearchMinChars: "Introdueix almenys 2 caràcters per cercar.", + agendaTitle: "Agenda", + agendaDescription: "Aquí pots trobar tots els teus elements assignats.", + agendaSearchPlaceholder: "Cercar a l'Agenda...", + agendaFilterAll: "TOTS", + agendaFilterToday: "AVUI", + agendaFilterUpcoming: "PROPERS", + agendaFilterOverdue: "VENÇUTS", + agendaFilterOpen: "OBERTS", + agendaFilterClosed: "TANCATS", + agendaFilterTasks: "TASQUES", + agendaFilterMarket: "MERCAT", + agendaFilterTribes: "TRIBUS", + agendaFilterEvents: "ESDEVENIMENTS", + agendaFilterReports: "INFORMES", + agendaFilterTransfers: "TRANSFERÈNCIES", + agendaFilterJobs: "FEINES", + agendaFilterIndustry: "INDÚSTRIA", + agendaFilterHousing: "HABITATGE", + agendaFilterProjects: "PROJECTES", + agendaFilterCalendars: "CALENDARIS", + agendaNoItems: "No s'han trobat assignacions.", + agendaDiscardButton: "Descartar", + agendaRestoreButton: "Restaurar", + agendaRemoveButton: "Treure", + agendaMembersCount: "Membres", + agendaLocationLabel: "Ubicació", + agendaYes: "SÍ", + agendaNo: "NO", + agendaInviteModeLabel: "Mode d'invitació", + agendaAnonymousLabel: "Mode anònim", + agendaMembersLabel: "Membres", + agendareportCategory: "Categoria", + agendareportSeverity: "Gravetat", + agendareportStatus: "Estat", + agendaTransferConcept: "Concepte", + agendaTransferAmount: "Quantitat", + agendaTransferDeadline: "Termini", + opinionsTitle: "Opinions", + shareYourOpinions: "Descobreix i vota opinions a la teva xarxa.", + author: "Per", + voteNow: "Vota ara", + alreadyVoted: "Ja has opinat.", + noOpinionsFound: "No s'han trobat opinions.", + TOPButton: "TOP", + opinionsTotalCount: "Total d'opinions", + voteInteresting: "Interessant", + voteNecessary: "Necessari", + voteUseful: "Útil", + voteInformative: "Informatiu", + voteWellResearched: "Ben documentat", + voteNeedsSources: "Necessita fonts", + voteWrong: "Incorrecte", + voteLowQuality: "Baixa qualitat", + voteLove: "Amor", + voteClear: "Clar", + voteMisleading: "Enganyós", + voteOffTopic: "Fora de tema", + voteDuplicate: "Duplicat", + voteClickbait: "Esquer de clics", + votePropaganda: "Propaganda", + voteFunny: "Divertit", + voteInspiring: "Inspirador", + voteUplifting: "Encoratjador", + voteUnnecessary: "Innecessari", + voteRejected: "Rebutjat", + voteConfusing: "Confús", + voteTroll: "Troll", + voteNsfw: "NSFW", + voteViolent: "Violent", + voteToxic: "Tòxic", + voteHarassment: "Assetjament", + voteHate: "Odi", + voteScam: "Estafa", + voteTriggering: "Provocador", + voteInsightful: "Perspicaç", + voteAccurate: "Precís", + voteActionable: "Aplicable", + voteCreative: "Creatiu", + voteSpam: "Spam", + voteAdultOnly: "Només adults", + privateMessage: "MP", + pmSendTitle: "Missatges privats", + pmSend: "Enviar!", + pmCancel: "Cancel·lar", + pmReset: "Restablir", + pmSharedKeyHint: "Comparteix aquesta clau amb el destinatari per un altre canal. Cal per desxifrar.", + pmDescription: "Fes servir aquest formulari per enviar un missatge/fitxer xifrat a altres habitants.", + pmComposeTitle: "Enviar un missatge", + pmRecipients: "Destinataris", + pmRecipientsHint: "Introdueix els IDs d'Oasis separats per comes", + pmLimitsHint: "Fins a 7 habitants per missatge.", + pmTextPlaceholder: "Escriu el teu missatge.", + pmSubject: "Assumpte", + pmSubjectHint: "Introdueix l'assumpte del missatge", + pmText: "Missatge", + pmCrypterChip: "2xE2E", + pmCrypterDecryptButton: "Desxifrar", + fileShareTitle: "Compartir un fitxer", + fileShareRecipientPlaceholder: "@...=.ed25519", + fileShareFileLabel: "Fitxer", + fileShareDownload: "Baixar", + fileShareMutualError: "Només pots compartir fitxers amb habitants amb qui us doneu suport mútuament.", + fileShareNoFile: "No s'ha seleccionat cap fitxer.", + fileShareTooLarge: "El fitxer supera la mida permesa.", + fileShareFailed: "No s'ha pogut preparar el fitxer.", + fileShareSendError: "No s'ha pogut enviar el fitxer.", + fileShareUnavailable: "Aquest fitxer no està disponible ara mateix. Torna-ho a provar més tard.", + pmCrypterBadKey: "La clau compartida és incorrecta!", + pmCrypterTooLong: "El missatge és massa llarg per xifrar-lo amb el Crypter. Escurça'l i torna-ho a provar.", + pmCrypterCipherLabel: "Missatge rexifrat", + pmCrypterCharsLabel: "caràcters", + pmInvalidRecipients: "Oasis ID no vàlid (hauria de ser com @…=.ed25519).", + pmEncryptionLabel: "Xifratge", + private: "Bústia", + privateDescription: "Gestiona els teus missatges privats.", + privateInbox: "BÚSTIA", + privateSent: "ENVIATS", + privateDelete: "Esborrar", + pmCreateButton: "Escriure un MP", + noPrivateMessages: "No hi ha missatges privats.", + pmReply: "Respondre", + pmReplies: "respostes", + inReplyTo: "EN RESPOSTA A", + forumInThread: "AL FIL", + pmPreview: "Previsualitzar", + pmPreviewTitle: "Previsualització", + performed: "→", + pmFromLabel: "De:", + pmToLabel: "Per a:", + pmNoSubject: "(sense assumpte)", + pmSubjectLabel: "Assumpte:", + pmBotJobs: "JobsBot", + pmBotProjects: "ProjectsBot", + pmBotMarket: "MarketBot", + pmBotShops: "ShopsBot", + pmBotPolitical: "PoliticalBot", + politicalBotLarpTitle: "La casa governant del L.A.R.P ha canviat.", + politicalBotParliamentTitle: "Ha començat un nou cicle de govern al Parlament.", + politicalBotTribeTitle: "Ha començat un nou cicle de govern en una de les teves tribus.", + politicalBotLarpIntro: "Ara governa {house} durant aquest cicle: {cycle}", + politicalBotParliamentIntro: "El govern actual és {gov}", + politicalBotParliamentIntroLeader: "El govern actual és {gov}, exercit per {leader}", + politicalBotTribeIntro: "El govern actual a la tribu {tribe} és {gov}", + politicalBotTribeIntroLeader: "El govern actual a la tribu {tribe} és {gov}, exercit per {leader}", + politicalBotLeaderLabel: "Líder", + inboxJobSubscribedTitle: "Nova subscripció a la teva oferta de feina", + pmInhabitantWithId: "Habitant amb ID OASIS:", + pmHasSubscribedToYourJobOffer: "s'ha subscrit a la teva oferta de feina", + inboxMarketItemSoldTitle: "Article venut", + inboxShopSoldTitle: "Producte venut", + pmYourItem: "El teu article", + pmHasBeenSoldTo: "s'ha venut a", + pmFor: "per", + inboxProjectPledgedTitle: "Nova aportació al teu projecte", + pmHasPledged: "ha aportat", + pmToYourProject: "al teu projecte", + blockchain: "Explorador", + blockchainDescription: "Explora i visualitza els blocs de la cadena.", + blockchainNoBlocks: "No s'han trobat blocs a la cadena.", + blockchainStatsTitle: "Estadístiques", + blockchainStatsTypeBreakdown: "Desglossament per tipus", + blockchainStatsTopAuthors: "Autors principals", + blockchainStatsCount: "Blocs", + blockchainBlockID: "ID del bloc", + blockchainBlockAuthor: "Autoria", + blockchainBlockCarbon: "Petjada de carboni", + blockchainBlockType: "Tipus", + blockchainBlockTimestamp: "Marca de temps", + blockchainLatestDatagram: "Darrer datagrama", + blockchainDatagram: "Datagrama", + blockchainDetails: "Veure els detalls del bloc", + blockchainViewBlockexplorer: "Veure a Blockexplorer", + blockchainBack: "Tornar a Blockexplorer", + banking: "Banca", + bankingTitle: "Banca", + bankingDescription: "L'economia col·lectiva d'ECOin.", + bankOverview: "Resum", + bankEpochs: "Èpoques", + bankRules: "Regles", + pending: "Pendents", + closed: "Tancades", + bankClaimNow: "Reclamar", + bankClaimUBI: "Reclamar RBU", + bankClaimAndPay: "Reclamar i pagar", + bankClaimedPending: "Reclamació pendent...", + bankStatusClaimed: "Reclamat", + bankStatusExpired: "Caducat", + bankPubOnly: "Operació només per a PUBs", + bankEpoch: "Època", + bankPool: "Fons (aquesta època)", + bankWeightsSum: "Suma de pesos", + bankNoAllocations: "No s'han trobat assignacions.", + bankNoEpochs: "No s'han trobat èpoques.", + bankEpochAllocations: "Assignacions de l'època", + bankAllocId: "ID d'assignació", + bankAllocDate: "Data", + bankAllocConcept: "Concepte", + bankAllocFrom: "De", + bankAllocTo: "Per a", + bankAllocAmount: "Quantitat", + bankAllocStatus: "Estat", + bankEpochId: "ID d'època", + bankRuleHash: "Hash de la instantània de regles", + bankViewEpoch: "Veure l'època", + bankYourIndustryBalance: "La teva part de la indústria", + bankYourUbiMonth: "La teva RBU (aquest mes)", + bankYourFundsMonth: "Fons totals", + bankIndustryBalance: "Producció industrial", + bankUserBalance: "El teu saldo", + bankAddresses: "Adreces", + bankNoAddresses: "No s'han trobat adreces.", + bankUser: "ID d'Oasis", + bankAddress: "Adreça", + bankAddAddressTitle: "Afegir una adreça d'ECOin", + bankAddAddressUser: "ID d'Oasis", + bankAddAddressLabel: "Etiqueta (opcional)", + bankAddAddressLabelPlaceholder: "Moneder de Bob, botiga d'Alice…", + bankAddAddressUserOptional: "Oasis ID (opcional)", + bankAddressUnnamed: "Adreça sense nom", + bankAddAddressAddress: "Adreça d'ECOin", + bankAddAddressSave: "Desar", + bankAddressAdded: "Adreça afegida", + bankAddressUpdated: "Adreça actualitzada", + bankAddressExists: "L'adreça ja existeix", + bankAddressInvalid: "Adreça no vàlida", + bankAddressDeleted: "Adreça eliminada", + bankAddressNotFound: "No s'ha trobat l'adreça", + bankAddressForbidden: "Només pots configurar la teva pròpia adreça de cobrament", + bankAddressTotal: "Total d'adreces", + bankAddressSearch: "Cercar @habitant o adreça", + bankAddressActions: "Accions", + bankAddressDelete: "Eliminar", + bankAddressSource: "Font", + bankAddressDeleteConfirm: "Vols eliminar aquesta adreça?", + search: "Cercar!", + bankLocal: "Local", + bankFromOasis: "Xarxa", + pubIdLabel: "PUB ID", + bankUbiAvailableNo: "SENSE FONS!", + bankUbiAvailableOk: "DISPONIBLE!", + bankUbiAvailability: "RBU", + bankAlreadyClaimedThisMonth: "Ja reclamada aquest mes", + bankUbiThisMonth: "RBU (aquest mes)", + bankUbiLastClaimed: "RBU (darrera reclamada)", + bankUbiNeverClaimed: "Mai reclamada", + bankUbiTotalClaimed: "RBU (total reclamat)", + bankUbiPub: "PUB", + bankUbiInhabitant: "HABITANT", + bankUbiClaimedAmount: "RECLAMAT (ECO)", + typeBankUbiResult: "BANCA - RBU", + bankNoPubConfigured: "No hi ha cap PUB configurat. Estableix l'ID del teu PUB a Configuració.", + shopsTitle: "Botigues", + shopDescription: "Descobreix i gestiona botigues a la xarxa.", + shopTitle: "Botiga", + shopUpload: "Crear una botiga", + shopAllSectionTitle: "Botigues", + shopMineSectionTitle: "Les meves botigues", + shopRecentSectionTitle: "Botigues recents", + shopTopSectionTitle: "Botigues destacades", + shopProductsSectionTitle: "Productes destacats", + shopPricesSectionTitle: "Productes per preu", + shopFavoritesSectionTitle: "Preferides", + shopCreateSectionTitle: "Crear una botiga", + shopUpdateSectionTitle: "Actualitzar la botiga", + shopCreate: "Crear", + shopUpdate: "Actualitzar", + shopDelete: "Eliminar", + shopReachClearnet: "Clearnet", + shopReachOasis: "Oasis", + encryptedChipLabel: "E2E", + transportChipLabel: "Xifrat en trànsit", + shopAddFavorite: "Afegir a preferides", + shopRemoveFavorite: "Treure de preferides", + shopOpen: "OBERTA", + shopClosed: "TANCADA", + shopMakePrivate: "FER-LA PRIVADA (E2E)", + shopMakePublic: "FER-LA PÚBLICA", + shopProducts: "Productes", + shopFilterAll: "TOTES", + shopFilterRecent: "RECENTS", + shopFilterMine: "MEVES", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUCTES", + shopFilterPrices: "PREUS", + shopFilterFavorites: "PREFERITS", + shopProductAdd: "Afegir un producte", + shopProductTitle: "Producte", + shopProductUpdate: "Actualitzar el producte", + shopProductPrice: "Preu (ECO)", + shopProductStock: "Estoc", + shopProductUntitled: "Producte sense títol", + shopUntitled: "Botiga sense títol", + shopNoItems: "No s'han trobat botigues.", + shopNoProducts: "Encara no hi ha productes.", + shopOutOfStock: "Exhaurit", + shopBuy: "Comprar", + shopBuyEncryptedNote: "Les teves dades de lliurament s'envien xifrades; només el propietari de la botiga les pot veure.", + shopBuyDeliveryAddress: "Adreça de lliurament", + shopBuyContact: "Contacte", + shopBuyContactPlaceholder: "correu, telèfon, etc.", + shopBuyNotes: "Notes", + shopBuyDeliveryRequired: "L'adreça de lliurament és obligatòria.", + shopOrdersTitle: "Comandes", + shopOrdersDescription: "Comandes rebudes per aquesta botiga.", + shopOrdersEmpty: "Encara no hi ha comandes.", + shopOrderProduct: "Producte", + shopOrderPrice: "Preu", + shopOrderBuyer: "Comprador", + shopBackToShop: "Tornar a la botiga", + marketVisitItem: "VEURE L'ARTICLE", + shopStatus: "ESTAT", + shopCreatedAt: "CREADA", + shopSearchPlaceholder: "Cercar a Botigues...", + shopUrl: "URL", + shopLocation: "Ubicació", + shopTags: "Etiquetes", + shopVisibility: "Visibilitat", + shopShortDescription: "Descripció curta", + shopShortDescriptionPlaceholder: "Descripció breu per a les targetes (màx. 160 caràcters)", + shopTitlePlaceholder: "Nom de la teva botiga", + shopDescriptionPlaceholder: "Descripció detallada de la teva botiga", + shopLocationPlaceholder: "Ciutat, País", + shopTagsPlaceholder: "Introdueix etiquetes separades per comes", + mapTitlePlaceholder: "Títol del mapa", + shopProductFeatured: "Producte destacat", + shopSendToMarket: "Enviar al mercat", + typeShop: "BOTIGUES", + typeShopProduct: "PRODUCTE DE BOTIGA", + bankExchange: "Intercanvi", + bankExchangeCurrentValue: "Valor d'ECOin (1h)", + bankTotalSupply: "Oferta total d'ECOin", + bankPubsSupply: "Oferta d'ECOin del fons dels PUBs", + bankHoldingSupply: "Oferta d'ECOin fora de la RBU", + bankHoursOfWork: "hores", + bankUnitMs: "ms", + bankUnitSeconds: "segons", + bankUnitMinutes: "minuts", + bankUnitDays: "dies", + bankExchangeNoData: "No hi ha dades disponibles", + bankInflation: "Inflació d'ECOin (anual)", + bankInflationMonthly: "Inflació d'ECOin (mensual)", + bankExchangeChartTitle: "Evolució del valor d'ECOin al llarg del temps", + bankExchangeChartValue: "Valor (ECO/h)", + bankExchangeChartSupply: "Oferta", + bankExchangeChartInflation: "Inflació %", + bankExchangeChartEmpty: "Encara no hi ha prou mostres — torna més tard", + bankCurrentSupply: "Oferta actual d'ECOin", + bankingSyncStatus: "Estat d'ECOin", + bankingSyncStatusSynced: "Sincronitzat", + bankingSyncStatusOutdated: "Sense sincronitzar", + statsTitle: "Estadístiques", + statistics: "Estadístiques", + statsDescription: "Descobreix estadístiques sobre la teva xarxa.", + ALLButton: "TOT", + RECENTButton: "RECENTS", + MINEButton: "MEVES", + TOMBSTONEButton: "ELIMINATS", + statsCreatedAt: "Creat el", + statsYourContent: "Contingut", + statsYourOpinions: "Opinions", + statsNetworkContent: "Contingut", + statsNetworkOpinions: "Opinions", + statsEcoWalletLabel: "Moneder ECOin", + walletAddressCopyHint: "Prem per seleccionar i copiar", + statsEcoWalletNotConfigured: "No configurat!", + statsBookmark: "Marcadors", + statsEvent: "Esdeveniments", + statsTask: "Tasques", + statsVotes: "Vots", + statsMarket: "Mercat", + statsForum: "Fòrums", + statsJob: "Feines", + statsReport: "Informes", + statsFeed: "Feed", + statsTribe: "Tribus", + statsImage: "Imatges", + statsAudio: "Àudios", + statsVideo: "Vídeos", + statsDocument: "Documents", + statsMap: "Mapes", + statsShop: "Botigues", + statsShopProduct: "Productes de botiga", + statsTransfer: "Transferències", + statsAiExchange: "IA", + statsPUBs: "PUBs", + statsSyncedPeers: "Nodes", + statsTotalLabel: "Total", + statsPost: "Publicacions", + statsSize: "Total (mida)", + statsBlockchainSize: "Blockchain (mida)", + statsBlobsSize: "Blobs (mida)", + statsActivity7d: "Activitat (darrers 7 dies)", + statsActivity7dTotal: "Total 7 dies", + statsActivity30dTotal: "Total 30 dies", + day: "Dia", + messages: "Missatges", + statsProject: "Projectes", + statsProjectsTitle: "Projectes", + statsProjectsTotal: "Total de projectes", + statsProjectsActive: "Actius", + statsProjectsCompleted: "Completats", + statsProjectsPaused: "En pausa", + statsProjectsCancelled: "Cancel·lats", + statsProjectsGoalTotal: "Objectiu total", + statsProjectsPledgedTotal: "Aportació total", + statsMarketTitle: "Mercat", + statsMarketTotal: "Articles totals", + statsMarketForSale: "En venda", + statsMarketReserved: "Reservats", + statsMarketClosed: "Tancats", + statsMarketSold: "Venuts", + statsUsersTitle: "Habitants", + user: "Habitant", + statsTombstoneRatio: "Ràtio d'eliminats (%)", + bankingUserEngagementScore: "Puntuació de KARMA", + statsParliamentCandidature: "Candidatures del parlament", + statsParliamentTerm: "Mandats del parlament", + statsParliamentProposal: "Propostes del parlament", + statsParliamentRevocation: "Revocacions del parlament", + statsParliamentLaw: "Lleis del parlament", + statsCourtsCase: "Casos judicials", + statsCourtsEvidence: "Proves judicials", + statsCourtsAnswer: "Respostes judicials", + statsCourtsVerdict: "Veredictes judicials", + statsCourtsSettlement: "Acords judicials", + statsCourtsSettlementProposal: "Propostes d'acord", + statsCourtsSettlementAccepted: "Acords acceptats", + statsCourtsNomination: "Nominacions de jutges", + statsCourtsNominationVote: "Vots de nominació", + ai: "IA", + aiTitle: "IA", + aiDescription: "Una Intel·ligència Artificial Col·lectiva (IAC) anomenada '42' que aprèn de la teva xarxa.", + aiUserQuestion: "Pregunta", + aiResponseTitle: "Resposta", + aiSubmitButton: "Enviar!", + aiSettingsDescription: "Configura el teu prompt (màx. 128 caràcters) per al model d'IA.", + aiPrompt: "Proporciona una resposta informativa i precisa.", + aiConfiguration: "Configurar el prompt", + aiPromptUsed: "Indicació", + aiClearHistory: "Esborrar l'historial", + aiServerError: "No puc respondre bé. Torna-ho a provar.", + aiStatusLoading: "42 està carregant el model. Torna a preguntar d'aquí a un moment.", + aiThinking: "Pensant", + aiWaiting: "Esperant", + aiStatusMissingModel: "El model d'IA no està instal·lat: falta src/AI/oasis-42-1-chat.Q4_K_M.gguf.", + aiExportFineTuning: "Exportar Fine-Tuning", + aiFactsTitle: "Dades de la teva xarxa", + aiInputPlaceholder: "Què és Oasis?", + typeAiExchange: "IA", + aiApproveTrain: "Afegir a l'entrenament col·lectiu", + aiRejectTrain: "No entrenar", + aiTrainApproved: "Aprovat per a l'entrenament", + aiTrainRejected: "Rebutjat per a l'entrenament", + aiSnippetsUsed: "Fragments utilitzats", + aiSnippetsLearned: "Fragments apresos", + statsAITraining: "Entrenament d'IA", + aiCustomAnswerPlaceholder: "Escriu la teva resposta personalitzada…", + marketMineSectionTitle: "Els teus articles", + marketCreateSectionTitle: "Crear un article", + marketUpdateSectionTitle: "Actualitzar", + marketAllSectionTitle: "Mercat", + marketTitle: "Mercat", + marketDescription: "Un mercat per intercanviar béns o serveis a la teva xarxa.", + marketFilterAll: "TOTS", + marketFilterMine: "MEUS", + marketFilterAuctions: "SUBHASTES", + marketFilterItems: "INTERCANVI", + marketFilterNew: "NOU", + marketFilterUsed: "USAT", + marketFilterBroken: "TRENCAT", + marketFilterForSale: "EN VENDA", + marketFilterSold: "VENUT", + marketFilterDiscarded: "DESCARTAT", + marketFilterRecent: "RECENTS", + marketFilterMyBids: "LICITACIONS", + marketCreateButton: "Vendre alguna cosa", + marketItemType: "Tipus", + marketItemTitle: "Títol", + marketItemAvailable: "Data límit", + marketItemDescription: "Descripció", + marketItemDescriptionPlaceholder: "Descriu l'article que vens", + marketItemStatus: "Estat", + marketShopLabel: "Botiga", + marketItemCondition: "Condició", + marketItemPrice: "Preu", + marketItemTags: "Etiquetes", + marketItemTagsPlaceholder: "Introdueix etiquetes separades per comes", + marketItemDeadline: "Data límit", + marketItemIncludesShipping: "Inclou l'enviament?", + marketItemHighestBid: "Licitació més alta", + marketItemStock: "Existències", + marketOutOfStock: "Sense estoc", + marketActionsUpdate: "Actualitzar", + marketUpdateButton: "Actualitzar", + marketActionsDelete: "Eliminar", + marketActionsChangeStatus: "CANVIAR L'ESTAT", + marketActionsBuy: "COMPRAR!", + marketAuctionBids: "Licitacions actuals", + marketPlaceBidButton: "Licitar", + marketItemSeller: "Venedor", + marketNoItems: "Encara no hi ha articles disponibles.", + marketYourBid: "La teva licitació", + marketCreateFormImageLabel: "Pujar contingut multimèdia (màx.: 75MB)", + marketSearchPlaceholder: "Cercar al Mercat...", + marketMinPriceLabel: "Preu mínim", + marketMaxPriceLabel: "Preu màxim", + marketSortRecent: "Més recents", + marketSortPrice: "Preu", + marketSortDeadline: "Data límit", + marketSearchButton: "Cercar", + marketAuctionEndsIn: "Acaba", + marketAuctionEnded: "Ha acabat", + marketNoItemsMatch: "No hi ha articles que coincideixin amb la teva cerca.", + housingTitle: "Habitatge", + housingDescriptionText: "Descobreix i gestiona llocs a la teva xarxa.", + modulesHousingLabel: "Habitatge", + modulesHousingDescription: "Mòdul per descobrir i gestionar llocs.", + typeHousing: "HABITATGE", + housingFilterAll: "TOT", + housingFilterMine: "MEUS", + housingFilterRequested: "SOL·LICITATS", + housingFilterSale: "VENDA", + housingFilterRent: "LLOGUER", + housingFilterCouchsurfing: "COUCHSURFING", + housingFilterOpen: "DISPONIBLES", + housingFilterClosed: "NO DISPONIBLES", + housingFilterRecent: "RECENTS", + housingFilterTop: "TOP", + housingType: "Oferta", + housingTypeSALE: "En venda", + housingTypeRENT: "En lloguer", + housingTypeCOUCHSURFING: "Couchsurfing", + housingProperty: "Immoble", + housingPropertyAPARTMENT: "Pis", + housingPropertyHOUSE: "Casa", + housingPropertyROOM: "Habitació", + housingPropertyLAND: "Terreny", + housingPropertyOTHER: "Altres", + housingTitleLabel: "Títol", + housingTitlePlaceholder: "Nom del lloc", + housingDescription: "Descripció", + housingDescriptionPlaceholder: "Descriu el lloc, el barri, què inclou: banys, jardí, llits…", + housingRules: "Normes", + housingRulesPlaceholder: "Mascotes, fumar, hores de silenci, estada mínima…", + housingPlace: "Lloc", + housingPlacePlaceholder: "País, ciutat, adreça", + housingPrice: "Preu", + housingPriceLabel: "Preu (ECO)", + housingSizeLabel: "Superfície (m²)", + housingFree: "GRATIS", + housingPerMonth: "/mes", + housingRooms: "Habitacions", + housingSize: "Superfície", + housingCapacity: "Hostes", + housingAvailableFrom: "Disponible des de", + housingAvailableTo: "Disponible fins a", + housingTags: "Etiquetes", + spreadEditWarning: "Aquest contingut perdrà les seves rèpliques si l'edites", + housingRequests: "Sol·licituds", + housingRequestButton: "Sol·licitar", + housingCancelButton: "Cancel·lar la sol·licitud", + housingRequestedBadge: "SOL·LICITAT", + housingStatus: "Estat", + housingStatusOPEN: "DISPONIBLE", + housingStatusCLOSED: "NO DISPONIBLE", + housingSetOpen: "Marcar com a disponible", + housingSetClosed: "Marcar com a no disponible", + housingCreateButton: "Crear un lloc", + housingUpdateButton: "Actualitzar", + housingSearchPlaceholder: "Cercar a Habitatge...", + housingSearchButton: "Cercar", + housingMinPrice: "Preu mín.", + housingMaxPrice: "Preu màx.", + housingSortRecent: "Més recents", + housingSortPrice: "Preu més baix", + housingSortRequests: "Més sol·licitats", + housingSortRating: "Més ben valorats", + housingNoItems: "No s'han trobat llocs.", + jobsTitle: "Feina", + jobsCandidatesTitle: "Candidats suggerits", + jobsCandidatesDescription: "Habitants amb competències que encaixen amb aquesta feina. Envia'ls un missatge privat per convidar-los.", + jobsCandidatesPmBody: "Hola, crec que el teu perfil encaixa amb la meva oferta de feina", + jobsDescription: "Descobreix i gestiona ofertes de feina a la teva xarxa.", + jobsFilterRecent: "RECENTS", + jobsFilterMine: "MEVES", + jobsFilterAll: "TOTES", + jobsFilterRemote: "REMOTES", + jobsFilterOpen: "OBERTES", + jobsFilterClosed: "TANCADES", + jobsCV: "CV", + jobsCreateJob: "Publicar una feina", + jobsRecentTitle: "Feines recents", + jobsMineTitle: "Les teves feines", + jobsAllTitle: "Ofertes de feina", + jobsRemoteTitle: "Feines remotes", + jobsOpenTitle: "Feines obertes", + jobsClosedTitle: "Feines tancades", + jobsCVTitle: "CV", + jobsFilterPresencial: "PRESENCIAL", + jobsFilterFreelancer: "FREELANCE", + jobsFilterEmployee: "ASSALARIAT", + jobsPresencialTitle: "Feines presencials", + jobsFreelancerTitle: "Feines freelance", + jobsEmployeeTitle: "Feines assalariades", + jobTitle: "Títol", + jobLocation: "Ubicació", + jobSalary: "Salari (ECO/1h)", + jobVacants: "Vacants", + jobDescription: "Descripció", + jobRequirements: "Requisits", + jobLanguages: "Idiomes", + jobStatus: "Estat", + jobStatusOPEN: "OBERTA", + jobStatusCLOSED: "TANCADA", + jobSetClosed: "Marcar com a TANCADA", + jobSubscribeButton: "Uneix-te a aquesta oferta!", + jobUnsubscribeButton: "Surt d'aquesta oferta!", + jobTitlePlaceholder: "Introdueix el títol de la feina", + jobDescriptionPlaceholder: "Descriu el lloc de treball", + jobRequirementsPlaceholder: "Introdueix els requisits", + jobLanguagesPlaceholder: "Anglès, francès, castellà, català...", + jobTasksPlaceholder: "Llista de tasques", + jobLocationPresencial: "Presencial", + jobLocationRemote: "Remot", + jobVacantsPlaceholder: "Nombre de vacants", + jobImage: "Pujar contingut multimèdia (màx.: 75MB)", + jobTasks: "Tasques", + jobType: "Tipus de feina", + jobTime: "Temps de feina", + jobSubscribers: "Subscriptors", + noSubscribers: "Sense subscriptors", + jobsFilterTop: "TOP", + jobsTopTitle: "Feines més ben pagades", + createJobButton: "Publicar una feina", + noJobsFound: "No s'han trobat ofertes de feina.", + jobAuthor: "Per", + jobTypeFreelance: "Autònom", + jobTypeSalary: "Assalariat", + jobTypeExchange: "Intercanvi d'hores", + jobTypeEXCHANGE: "INTERCANVI D'HORES", + jobsFilterExchange: "INTERCANVI", + jobsExchangeTitle: "Feines per intercanvi d'hores", + jobsHoursOffered: "Hores oferides", + jobsHoursRequested: "Hores demanades a canvi", + jobsExchangeSkill: "Habilitat demanada a canvi", + jobTimePartial: "Parcial", + jobTimeComplete: "Completa", + jobsUpdateButton: "ACTUALITZAR", + jobsFilterApplied: "CANDIDATURA", + jobsAppliedTitle: "Les meves candidatures", + jobsAppliedBadge: "Inscrit", + jobsSearchPlaceholder: "Cercar a Feina...", + jobsMinSalaryLabel: "Salari mín.", + jobsMaxSalaryLabel: "Salari màx.", + jobsSortRecent: "Més recents", + jobsSortSalary: "Salari més alt", + jobsSortSubscribers: "Més candidats", + jobsSearchButton: "Cercar", + jobsApplicants: "Candidats", + jobsUpdatedAt: "Actualitzat", + jobSetOpen: "Marcar com a oberta", + jobsTagsLabel: "Etiquetes", + noJobsMatch: "No hi ha ofertes que coincideixin amb la teva cerca.", + industrySearchPlaceholder: "Cercar a Indústria...", + industryAllSectors: "Tots els sectors", + industryVisitButton: "Visitar Indústria", + industryAdvanceTo: "Canviar a", + industryAmount: "Quantitat", + industryApproveBuild: "Aprovar", + industryBlueprint: "Plànol", + industryBlueprints: "Plànols", + industryBuild: "Producció", + industryBuildNotes: "Notes", + industryBuildStart: "Data d'inici", + industryVoteUpdateButton: "Votar l'actualització", + industryVoteDeleteButton: "Votar l'esborrament", + industryRevokeVote: "Revocar", + industryTimeLeft: "Temps restant", + industryBuildEnd: "Data de fi", + industryBuilds: "Produccions", + industryBuildTitle: "Títol", + industryContribute: "Contribuir", + industryContributeButton: "Contribuir", + industryContributions: "Contribucions", + industryCreateBlueprintButton: "Crear un plànol", + industryDistribute: "Distribuir", + industryDistributeButton: "Distribuir per quotes", + industryDistribution: "Distribució", + industryEcoAmount: "Quantitat (ECO)", + industryEditBlueprint: "Editar el plànol", + industryFacility: "Fàbrica", + industryKindLabel: "Tipus", + industryKind_labor: "Mà d'obra", + industryKind_material: "Material", + industryKind_eco: "Fons", + industryLaborHours: "Mà d'obra (hores)", + industryHours: "Hores", + industryMaterialItem: "Material", + industryMaterials: "Materials", + industryEstMaterials: "Total de materials", + industryEstLabor: "Total de mà d'obra", + industryEstTotal: "Preu estimat", + industryBuildingPrice: "Preu de construcció", + industryFinalPrice: "Preu final", + industryBlueprintDescPlaceholder: "Especificacions, dimensions, pes, documentació…", + industryMaterialValue: "Valor (ECO)", + industryMember: "Membre", + industryNewBlueprint: "Nou plànol", + industryNewBuild: "Proposar una producció", + industryEditBuild: "Editar la producció", + industryNeedBlueprint: "Crea primer un plànol: cada producció en fabrica un.", + industryNoBlueprints: "Encara no hi ha plànols.", + industryNoBuilds: "Encara no hi ha produccions.", + industryNote: "Nota", + industryOutput: "Sortida", + industryOutputKind: "Tipus de producte", + industryOutputValue: "Valor de sortida (ECO, p. ex. de la venda)", + industryPostJob: "Crear una feina", + industryPot: "Pot", + industryProposeBuildButton: "Proposar la producció", + industryProposer: "Proponent", + industryAuthor: "Autor", + industrySellOnMarket: "Enviar al mercat", + industrySendToProjects: "Crear un projecte", + industrySkills: "Habilitats", + industryTreasury: "Tresoreria", + industryKind_physical: "Físic", + industryKind_digital: "Digital", + industryBuildStatus_PROPOSED: "PROPOSADA", + industryBuildStatus_REJECTED: "REBUTJADA", + industryBuildStatus_APPROVED: "APROVADA", + industryBuildStatus_STOCKING: "PROVEINT-SE", + industryBuildStatus_IN_PRODUCTION: "EN PRODUCCIÓ", + industryBuildStatus_COMPLETED: "COMPLETADA", + industryBuildStatus_FAILED: "FALLIDA", + pmBotIndustry: "IndustryBot", + pmBotHousing: "HousingBot", + jobsBotMatchTitle: "Una feina encaixa amb el teu currículum", + jobsBotMatchIntro: "Una feina encaixa amb el teu currículum.", + jobsBotMatchJob: "Feina", + jobsBotMatchHint: "Reps això perquè el teu currículum està gestionat per IA. Ho pots desactivar al teu CV.", + cvAiManaged: "Gestionat per IA", + switchOn: "ACTIVAT", + switchOff: "DESACTIVAT", + cvAiManagedHint: "Si està actiu, JobsBot t'avisa per missatge privat quan una feina encaixa amb el teu currículum.", + cvMatchThreshold: "Avisa'm a partir d'aquest nivell de coincidència", + housingBotRequestedTitle: "Algú ha sol·licitat un dels teus llocs.", + housingBotCancelledTitle: "S'ha cancel·lat una sol·licitud sobre un dels teus llocs.", + housingBotYouRequestedTitle: "Has sol·licitat un lloc.", + housingBotYouCancelledTitle: "Has cancel·lat una sol·licitud.", + housingBotUnavailableTitle: "Un lloc que vas sol·licitar ja no està disponible.", + housingBotUnavailableSentTitle: "Has avisat qui va sol·licitar aquest lloc.", + industryBotAdmittedTitle: "T'han admès en una fàbrica.", + industryBotApplicationTitle: "Nova sol·licitud d'ingrés a la teva fàbrica.", + industryBotInvitedTitle: "T'han convidat a una fàbrica.", + industryBotDissolvedTitle: "Una fàbrica de la qual formes part s'ha dissolt.", + industryBotBuildApprovedTitle: "S'ha aprovat una producció.", + industryBotDistributedTitle: "S'ha distribuït la sortida d'una producció.", + industryFilterRules: "REGLES", + industryRulesTitle: "Regles", + industryRulesIntro: "Indústria permet als habitants posseir col·lectivament els mitjans de producció: una Fàbrica és un taller de la xarxa que ningú no posseeix de manera privada.", + industryRulesSteward: "El fundador és el majordom — un custodi, no un amo: no pot vendre, privatitzar ni dissoldre la Fàbrica tot sol.", + industryRulesMembership: "La política de pertinença defineix com s'hi uneixen els habitants: Oberta (hi entres a l'instant), Per vot (t'admet el vot dels membres) o Només per invitació (un membre t'ha de convidar abans).", + industryRulesQuorum: "El quòrum és el nombre mínim de membres que han de votar perquè una decisió compti, perquè una Fàbrica petita no la decideixi un sol habitant.", + industryRulesMajority: "La majoria és la fracció de vots necessària per aprovar (0,5 = meitat, 1 = unanimitat); una decisió s'aprova quan els SÍ arriben almenys a max(quòrum, membres × majoria).", + industryRulesDecisions: "Els membres voten per admetre'n de nous, aprovar produccions i dissoldre la Fàbrica; el majordom no es pot saltar aquestes decisions col·lectives.", + industryRulesBlueprints: "Els plànols són receptes lliures (COPYLEFT) — les entrades (materials, hores de feina, habilitats) i la sortida (un bé físic o digital) — i qualsevol en pot fer un fork.", + industryRulesBuilds: "Una producció és una ordre de fabricació: PROPOSADA → APROVADA (per vot) → PROVEINT-SE → EN PRODUCCIÓ → COMPLETADA, i només accepta contribucions un cop aprovada.", + industryRulesContributions: "Els membres aporten feina (hores), materials (valor declarat en ECO) o ECO a una producció; cada aportació guanya karma.", + industryRulesLaborRate: "La tarifa de feina és el valor en ECO d'una hora de feina en aquesta Fàbrica; converteix hores en karma per comparar esforç i capital de manera justa: karma = hores × tarifa + valor material + ECO.", + industryRulesShares: "La teva quota en una producció és el teu karma ÷ karma total, i determina quina part del valor de sortida reps.", + industryRulesDistribution: "Quan una producció es completa, el majordom reparteix el pot (els fons de la producció més el valor de venda) entre qui hi va contribuir, en proporció a la seva participació.", + industryRulesTreasury: "El majordom custodia les aportacions en ECO com a tresoreria de la producció fins al repartiment; tots els moviments queden registrats a la xarxa i les disputes poden anar als Tribunals.", + industryJobs: "Feines", + industryReportButton: "Informar", + industryCreateTask: "Crear una tasca", + industryOpenDispute: "Obrir una disputa", + industryMaterialsPlaceholder: "placa-solar:2:120\nbateria:1:80\nkit:1:15", + industryCreateInvite: "Generar una invitació", + industryPauseVotes: "Vots d'estat", + industryTitle: "Indústria", + industryDescription: "Mòdul per gestionar col·lectivament els mitjans de producció.", + typeIndustryBuild: "PRODUCCIÓ", + typeIndustryBlueprint: "PLÀNOL", + typeIndustryAllocation: "DISTRIBUCIÓ", + typeIndustry: "INDÚSTRIA", + modulesIndustryLabel: "Indústria", + modulesIndustryDescription: "Mòdul per gestionar col·lectivament els mitjans de producció.", + industryFilterAll: "TOTES", + industryFilterRecent: "RECENTS", + industryFilterMember: "MEMBRE", + industryFilterBlueprints: "PLÀNOLS", + industryFilterBuilds: "PRODUCCIONS", + industryFilterMine: "MEVES", + industryFilterActive: "EN MARXA", + industryFilterPaused: "EN PAUSA", + industryFilterDissolved: "DISSOLTES", + industryAllTitle: "Indústria", + industryRecentTitle: "Fàbriques recents", + industryMemberTitle: "Fàbriques on soc", + industryMineTitle: "Les meves fàbriques", + industryActiveTitle: "En marxa", + industryPausedTitle: "Fàbriques en pausa", + industryDissolvedTitle: "Fàbriques dissoltes", + industryNoFacilitiesFound: "No s'han trobat fàbriques.", + industryCreateFacility: "Crear una fàbrica", + industryMembers: "Membres", + industryMemberBadge: "MEMBRE", + industryName: "Nom", + industryNamePlaceholder: "Nom de la fàbrica", + industryDescriptionLabel: "Descripció", + industryDescriptionPlaceholder: "Què produeix aquesta fàbrica?", + industrySector: "Sector", + industryMembershipPolicy: "Política de pertinença", + industryQuorum: "Quòrum", + industryMajority: "Majoria", + industryLaborRate: "Tarifa de feina", + industryCreateButton: "Crear la fàbrica", + industryUpdateButton: "Actualitzar", + industrySteward: "Majordom", + industryStatusActive: "EN MARXA", + industryStatusPaused: "EN PAUSA", + industryStatusDissolved: "DISSOLTA", + industryStatusLabel: "Estat", + industryJoinButton: "Unir-s'hi", + industryApplyButton: "Sol·licitar l'ingrés", + industryLeaveButton: "Sortir", + industryInviteButton: "Convidar", + industryInvitePlaceholder: "@...=.ed25519", + industryInviteNeeded: "Necessites una invitació per unir-t'hi.", + industryPauseButton: "Votar la pausa", + industryResumeButton: "Votar la represa", + industryDeleteButton: "Eliminar", + industryPendingApplicants: "Sol·licituds pendents", + industryVotesYes: "sí", + industryVotesNo: "no", + industryAlreadyVoted: "votat", + industryAdmit: "Admetre", + industryReject: "Rebutjar", + industryDissolveTitle: "Dissoldre la fàbrica", + industryDissolveVote: "Votar la dissolució", + industryPolicy_open: "Oberta", + industryPolicy_vote: "Per vot", + industryPolicy_invite: "Només per invitació", + projectsTitle: "Projectes", + projectsDescription: "Crea, finança i segueix projectes impulsats per la comunitat a la teva xarxa.", + projectSearchPlaceholder: "Cercar a Projectes...", + projectCreateProject: "Crear un projecte", + projectCreateButton: "Crear un projecte", + projectUpdateButton: "ACTUALITZAR", + projectDeleteButton: "ELIMINAR", + projectNoProjectsFound: "No s'han trobat projectes.", + projectFilterAll: "TOTS", + projectFilterMine: "ELS MEUS PROJECTES", + projectFilterActive: "ACTIUS", + projectFilterPaused: "EN PAUSA", + projectFilterCompleted: "COMPLETATS", + projectFilterFollowing: "SEGUITS", + projectFilterRecent: "RECENTS", + projectFilterTop: "TOP", + projectAllTitle: "Projectes", + projectMineTitle: "Els teus projectes", + projectActiveTitle: "Projectes actius", + projectPausedTitle: "Projectes en pausa", + projectCompletedTitle: "Projectes completats", + projectFollowingTitle: "Projectes seguits", + projectRecentTitle: "Projectes recents", + projectTopTitle: "Més ben finançats", + projectTitlePlaceholder: "Nom del projecte", + projectImage: "Pujar contingut multimèdia (màx.: 75MB)", + projectDescription: "Descripció", + projectDescriptionPlaceholder: "Explica la història i els objectius…", + projectGoal: "Objectiu (ECO)", + projectGoalPlaceholder: "50000", + projectDeadline: "Data límit", + projectProgress: "Progrés inicial (%)", + projectStatus: "Estat", + projectFunding: "Finançament", + projectPledged: "Compromès", + projectSetStatus: "Establir l'estat", + projectSetProgress: "Actualitzar el progrés", + projectFollowButton: "SEGUIR", + projectUnfollowButton: "DEIXAR DE SEGUIR", + projectStatusACTIVE: "ACTIU", + projectStatusPAUSED: "EN PAUSA", + projectStatusCOMPLETED: "COMPLETAT", + projectStatusCANCELLED: "CANCEL·LAT", + projectPledgeTitle: "Dona suport a aquest projecte", + projectPledgePlaceholder: "Quantitat en ECO", + projectBounties: "Recompenses", + projectNoBounties: "No s'han trobat recompenses.", + projectTitle: "Títol", + projectMilestoneSelect: "Seleccionar una fita", + projectBountyCreateButton: "Crear una recompensa", + projectBountyStatus: "Estat de la recompensa", + projectBountyOpen: "oberta", + projectBountyClaimed: "reclamada", + projectBountyDone: "completada", + projectBountyClaimedBy: "reclamada per", + projectBountyClaimButton: "reclamar", + projectBountyCompleteButton: "Marcar com a completada", + projectMilestones: "Fites", + projectAddMilestoneTitle: "Nova fita", + projectMilestoneTitle: "Títol de la fita", + projectMilestoneTargetPercent: "Percentatge (%)", + projectMilestoneDueDate: "Data", + projectMilestoneStatus: "Estat de la fita", + projectMilestoneOpen: "oberta", + projectMilestoneDone: "completada", + projectMilestoneDue: "venç", + projectMilestoneMarkDone: "Marcar com a feta", + projectMilestoneTitlePlaceholder: "Introdueix el títol de la fita", + projectMilestoneDescriptionPlaceholder: "Introdueix la descripció d'aquesta fita", + projectMilestoneDescription: "Descripció de la fita", + projectBudgetAssigned: "Assignat a recompenses", + projectBudgetOver: "⚠ Pressupost excedit: l'assignat supera l'objectiu", + projectFollowers: "Seguidors", + projectFollowersTitle: "Seguidors", + projectFollowersNone: "Encara no hi ha seguidors.", + projectMore: "més", + projectYouFollowHint: "Segueixes aquest projecte", + projectBackers: "Mecenes", + projectBackersTitle: "Mecenes", + projectBackersTotal: "Total de mecenes", + projectBackersTotalPledged: "Total aportat", + projectBackersYourPledge: "La teva aportació", + projectBackersNone: "Encara no hi ha aportacions.", + projectFilterBackers: "MECENES", + projectFilterApplied: "APORTANT", + projectAppliedTitle: "APORTANT", + projectBackersLeaderboardTitle: "Mecenes destacats", + projectNoBackersFound: "No hi ha mecenes.", + projectBackerAmount: "Total aportat", + projectBackerPledges: "Aportacions", + projectBackerProjects: "Projectes", + projectSelectMilestoneOrBounty: "Seleccionar una fita o una recompensa", + projectPledgeButton: "Aportar", + footerLicense: "GPLv3", + modulesModuleName: "Nom", + modulesModuleDescription: "Descripció", + modulesModuleStatus: "Estat", + modulesTotalModulesLabel: "Total", + modulesEnabledModulesLabel: "Habilitats", + modulesDisabledModulesLabel: "Deshabilitats", + modulesInvitesLabel: "Invitacions", + modulesInvitesDescription: "Mòdul per gestionar i aplicar codis d'invitació.", + modulesWalletLabel: "Moneder", + modulesWalletDescription: "Mòdul per gestionar els teus actius digitals (ECOin).", + modulesCipherLabel: "Xifratge", + modulesCipherDescription: "Mòdul per xifrar i desxifrar el teu text de manera simètrica (amb una contrasenya compartida).", + modulesBookmarksLabel: "Marcadors", + modulesBookmarksDescription: "Mòdul per descobrir i gestionar marcadors.", + modulesVideosLabel: "Vídeos", + modulesVideosDescription: "Mòdul per descobrir i gestionar vídeos.", + modulesDocsLabel: "Documents", + modulesDocsDescription: "Mòdul per descobrir i gestionar documents.", + modulesAudiosLabel: "Àudios", + modulesAudiosDescription: "Mòdul per descobrir i gestionar àudios.", + modulesTagsLabel: "Etiquetes", + modulesTagsDescription: "Mòdul per descobrir i explorar patrons de taxonomia (etiquetes).", + modulesImagesLabel: "Imatges", + modulesImagesDescription: "Mòdul per descobrir i gestionar imatges.", + modulesTrendingLabel: "Tendències", + modulesTrendingDescription: "Mòdul per explorar el contingut més popular.", + modulesEventsLabel: "Esdeveniments", + modulesEventsDescription: "Mòdul per descobrir i gestionar esdeveniments.", + modulesTasksLabel: "Tasques", + modulesTasksDescription: "Mòdul per descobrir i gestionar tasques.", + modulesMarketLabel: "Mercat", + modulesMarketDescription: "Mòdul per intercanviar béns o serveis.", + modulesShopsLabel: "Botigues", + modulesShopsDescription: "Mòdul per gestionar i descobrir botigues.", + modulesTribesLabel: "Tribus", + modulesTribesDescription: "Mòdul per explorar o crear tribus (grups).", + modulesVotationsLabel: "Votacions", + modulesVotationsDescription: "Mòdul per descobrir i gestionar votacions.", + modulesParliamentLabel: "Parlament", + modulesParliamentDescription: "Mòdul per elegir governs i votar lleis.", + modulesCourtsLabel: "Tribunals", + modulesCourtsDescription: "Mòdul per resoldre conflictes i emetre veredictes.", + modulesReportsLabel: "Informes", + modulesReportsDescription: "Mòdul per gestionar i fer el seguiment d'informes sobre problemes, errors, abusos i avisos de contingut.", + modulesOpinionsLabel: "Opinions", + modulesOpinionsDescription: "Mòdul per descobrir i votar opinions.", + modulesTransfersLabel: "Transferències", + modulesTransfersDescription: "Mòdul per descobrir i gestionar contractes intel·ligents (transferències).", + modulesFediverseLabel: "Multivers", + modulesBlogsLabel: "Blogs", + modulesBlogsDescription: "Mòdul per descobrir i gestionar blogs.", + modulesPollsLabel: "Enquestes", + modulesPollsDescription: "Mòdul per preguntar a la xarxa i comptar les respostes.", + modulesFediverseDescription: "Gestiona els teus altres comptes del multivers, incloent-hi enviar i rebre contingut.", + modulesFeedLabel: "Feed", + modulesFeedDescription: "Mòdul per descobrir i compartir textos breus (feeds).", + modulesPixeliaLabel: "Pixelia", + modulesPixeliaDescription: "Mòdul per dibuixar en una graella col·laborativa.", + modulesMelodyLabel: "Melody", + modulesMelodyDescription: "Mòdul per generar i compartir el \"so\" de la teva blockchain.", + modulesAgendaLabel: "Agenda", + modulesAgendaDescription: "Mòdul per gestionar tots els teus elements assignats.", + modulesAILabel: "IA", + modulesAIDescription: "Mòdul per parlar amb un LLM anomenat '42'.", + modulesForumLabel: "Fòrums", + modulesForumDescription: "Mòdul per descobrir i gestionar fòrums.", + modulesJobsLabel: "Feina", + modulesJobsDescription: "Mòdul per descobrir i gestionar ofertes de feina.", + modulesProjectsLabel: "Projectes", + modulesProjectsDescription: "Mòdul per explorar, finançar i gestionar projectes.", + modulesBankingLabel: "Banca", + modulesBankingDescription: "Mòdul per gestionar el tresor comú: valor d'ECOin, impostos i RBU.", + modulesFavoritesLabel: "Preferits", + modulesFavoritesDescription: "Mòdul per gestionar el teu contingut preferit.", + fileTooLargeTitle: "Fitxer massa gran", + fileTooLargeMessage: "El fitxer supera la mida màxima permesa (75 MB). Si us plau, selecciona un fitxer més petit.", + goBack: "Tornar", + errorDismiss: "D'acord", + errorPageTitle: "S'ha produït un error", + errorNoAllocation: "No s'ha trobat cap assignació per a aquesta reclamació.", + errorInvalidClaim: "Aquesta assignació no es pot reclamar.", + visibilityLabel: "Visibilitat", + clearnetPrivateConflict: "Un contingut privat, ocult, tancat, només per invitació o de pagament no es pot publicar a la CLEARNET. Tria OASIS o fes-lo públic.", + visibilityPublic: "Pública", + visibilityHidden: "Oculta", + visibilityMakePublic: "Fer-la pública", + visibilityMakeHidden: "Fer-la oculta", + invitesInhabitantsTitle: "Habitants", + aiNavPlaceholder: "Descriu què necessites...", + aiNavDisabled: "La navegació amb IA està desactivada.", + aiNavResultsTitle: "Suggeriments d'AINav", + aiNavQueryLabel: "Consulta", + aiNavResultMatch: "Coincidència", + aiNavResultsEmpty: "No hi ha rutes coincidents. Prova amb /search.", + modulesAINavLabel: "AINav", + modulesAINavDescription: "Mòdul per fer peticions en llenguatge natural sobre el contingut de la xarxa.", + directConnectDescription: "Connecta't directament a un node introduint-ne l'adreça IP, el port i la clau pública. El node s'afegirà com a connexió seguida.", + peerHost: "IP", + peerPort: "Port", + peerPublicKey: "Clau pública (@...ed25519)", + connectAndFollow: "Connectar", + modulesPresetTitle: "Configuracions comunes", + workflowsTitle: "Fluxos de treball", + workflowsDescription: "Un flux de treball fixa el tema i quins mòduls estan actius.", + workflowsSet: "Aplicar el flux de treball", + workflow_default: "Per defecte", + workflow_jobs: "Feina", + workflow_social: "Social", + workflow_business: "Negocis", + workflow_night: "Nit", + workflow_kids: "Infants", + workflow_activists: "Activisme", + workflow_mobile: "Mòbil", + statsCarbonFootprintTitle: "Petjada de carboni", + statsCarbonTombstone: "Petjada dels esborrats (tombstoning)", + uploadMedia: "Pujar contingut multimèdia (màx.: 75MB)", + invitesUnfollow: "Deixar de seguir", + invitesFollow: "Seguir", + invitesUnfollowedInvites: "No federades", + invitesNoUnfollowed: "No hi ha pubs no federats.", + reportsWhyInappropriateLabel: "Per què és inadequat?", + blockchainContentDeleted: "Aquest contingut s'ha eliminat", + visitContent: "Visitar el contingut", + favoriteAdd: "Fixar a preferits", + pmContentTooltip: "Enviar un missatge privat", + phoneCallTooltip: "Fer una trucada", + favoriteRemove: "Treure de preferits", + reportContent: "Denunciar el contingut", + bankEcoinHours: "Equivalència d'ECOin en temps (1 ECO)", + mapsLabel: "Mapes", + mapTitle: "Mapes", + mapDescription: "Gestiona i comparteix mapes fora de línia a la teva xarxa.", + mapMineSectionTitle: "Els teus mapes", + mapCreateSectionTitle: "Crear un mapa", + mapUpdateSectionTitle: "Actualitzar el mapa", + mapAllSectionTitle: "Mapes", + mapRecentSectionTitle: "Mapes recents", + mapFavoritesSectionTitle: "Preferits", + mapFilterAll: "TOTS", + mapFilterMine: "MEUS", + mapFilterRecent: "RECENTS", + mapFilterFavorites: "PREFERITS", + mapUploadButton: "Crear un mapa", + mapCreateButton: "Crear un mapa", + mapUpdateButton: "Actualitzar", + mapDeleteButton: "Eliminar", + mapAddFavoriteButton: "Afegir a preferits", + mapRemoveFavoriteButton: "Treure de preferits", + mapLatLabel: "Latitud", + mapLatPlaceholder: "p. ex. 41.3874", + mapLngLabel: "Longitud", + mapLngPlaceholder: "p. ex. 2.1686", + mapDescriptionLabel: "Descripció", + mapDescriptionPlaceholder: "Descriu el mapa o la ubicació...", + mapTypeLabel: "Tipus de mapa", + mapInviteMode: "Invitació", + mapTypeSingle: "ÚNIC", + mapTypeOpen: "OBERT", + mapTypeClosed: "TANCAT", + mapTagsLabel: "Etiquetes", + mapTagsPlaceholder: "Introdueix etiquetes separades per comes", + mapUrlLabel: "URL del mapa", + mapMarkersTitle: "Marcadors", + mapMarkerDefault: "Marcador", + mapMarkerLatLabel: "Latitud del marcador", + mapMarkerLngLabel: "Longitud del marcador", + mapMarkerLabelField: "Etiqueta del marcador", + mapMarkerLabelPlaceholder: "Descriu aquest marcador...", + markerImageLabel: "Imatge del marcador", + mapAddMarkerTitle: "Afegir un marcador", + mapAddMarkerButton: "Afegir un marcador", + mapCleanMarkerButton: "Netejar el marcador", + mapApplyZoom: "Aplicar el zoom", + mapPlaceSearchPlaceholder: "Cerca un lloc o país...", + mapNoPlaces: "No s'han trobat llocs.", + mapClusterTitle: "marcadors aquí, fes clic per apropar", + mapDeleteMarker: "Elimina el marcador", + mapZoomIn: "Apropa", + mapZoomOut: "Allunya", + mapFitMarkers: "Ajusta a tots els marcadors", + mapPanLabel: "Mou el mapa", + mapCenterHere: "Centra aquí", + mapNear: "prop de", + mapPickedSpot: "Punt triat", + mapPickHint: "Fes clic al mapa per triar un punt.", + mapClearPick: "Neteja", + mapSearchPlaceholder: "Cercar a Mapes...", + mapSearchButton: "Cercar", + mapUpdatedAt: "Actualitzat", + mapNoMatch: "Cap mapa no coincideix amb la teva cerca.", + noMaps: "No hi ha mapes disponibles.", + mapLocationTitle: "Ubicació", + mapVisitLabel: "Visitar el mapa", + typeMap: "MAPES", + typeMapMarker: "MARCADOR DE MAPA", + modulesMapLabel: "Mapes", + modulesMapDescription: "Mòdul per gestionar i compartir mapes fora de línia.", + emergenciesTitle: "Emergències", + modulesEmergenciesLabel: "Emergències", + modulesEmergenciesDescription: "Mòdul per llançar, confirmar i seguir emergències a la xarxa.", + emergenciesDescription: "Mòdul per llançar, confirmar i seguir emergències a la xarxa.", + emergencyCreate: "Crear una emergència", + emergencyUpdate: "Actualitzar", + emergencyDelete: "Esborrar", + emergencyTitleLabel: "Títol", + emergencyTitlePlaceholder: "Què està passant?", + emergencyTextLabel: "Detalls", + emergencyTextPlaceholder: "On, des de quan, què cal fer...", + emergencyCategoryLabel: "Categoria", + emergencyCategoryWeather: "METEOROLOGIA", + emergencyCategoryInfrastructure: "INFRAESTRUCTURA", + emergencyCategoryHealth: "SALUT", + emergencyCategorySecurity: "SEGURETAT", + emergencyCategoryLost: "PERDUTS", + emergencyCategoryNeighborhood: "VEÏNAT", + emergencyExpiresLabel: "Caduca d'aquí a", + emergencyExpires1d: "1 dia", + emergencyExpires3d: "3 dies", + emergencyExpires7d: "1 setmana", + emergencyExpires30d: "1 mes", + emergencyExpiresNone: "Indefinit", + emergencyExpiresKeep: "Mantenir la data actual", + emergencyTagsLabel: "Etiquetes", + emergencyTagsPlaceholder: "Introdueix etiquetes separades per comes", + emergencyFilterAll: "TOTES", + emergencyFilterMine: "MEVES", + emergencyFilterRecent: "RECENTS", + emergencySeverityUnverified: "SENSE VERIFICAR", + emergencySeverityLow: "BAIXA", + emergencySeverityMedium: "MITJANA", + emergencySeverityHigh: "ALTA", + emergencyStatusActive: "ACTIVA", + emergencyStatusResolved: "RESOLTA", + emergencyStatusExpired: "CADUCADA", + emergencyConfirmations: "Confirmacions", + emergencyConfirmButton: "Confirmar", + emergencyConfirmed: "Has confirmat aquesta emergència", + emergencyResolveButton: "Resoldre", + emergencyReopenButton: "Reobrir", + emergencyGenerateReport: "Generar un informe", + emergencyUpdateEdited: "Editada", + emergencyLastUpdate: "DARRERA ACTUALITZACIÓ", + emergencyUpdatesTitle: "Darreres actualitzacions:", + emergencyNoUpdates: "Encara no hi ha actualitzacions.", + emergencyAddUpdate: "Publicar una actualització", + emergencyUpdatePlaceholder: "Què ha canviat?", + emergencyNoItems: "No s'han trobat emergències.", + emergencyExpiresAt: "Caduca", + emergencyBannerLabel: "Emergència activa", + emergencyCreateSectionTitle: "Crear una emergència", + emergencyUpdateSectionTitle: "Actualitzar l'emergència", + emergencySearchPlaceholder: "Cercar a emergències...", + typeEmergency: "EMERGÈNCIES", + typeEmergencyConfirm: "CONFIRMACIÓ D'EMERGÈNCIA", + typeEmergencyUpdate: "ACTUALITZACIÓ D'EMERGÈNCIA", + statsEmergency: "Emergències", + statsEmergencyConfirm: "Confirmacions d'emergències", + statsEmergencyUpdate: "Actualitzacions d'emergències", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Una emergència que segueixes té una actualització.", + emergencyBotResolvedTitle: "Una emergència que segueixes s'ha resolt.", + dataKindEmergencies: "EMERGÈNCIES", + mailingTitle: "Llistes de correu", + modulesMailingLabel: "Llistes de correu", + modulesMailingDescription: "Descobreix i gestiona llistes de correu a la teva xarxa.", + modulesPhoneLabel: "Telèfon", + modulesPhoneDescription: "Mòdul per trucar a altres habitants amb videotrucades xifrades d'extrem a extrem.", + mailingDescription: "Descobreix i gestiona llistes de correu a la teva xarxa.", + mailingCreate: "Crear una llista de correu", + mailingUpdate: "Actualitzar", + mailingArchive: "Arxivar", + mailingActivate: "Activar", + mailingCreateSectionTitle: "Crear una llista de correu", + mailingUpdateSectionTitle: "Actualitzar la llista de correu", + mailingTitleLabel: "Títol", + mailingTitlePlaceholder: "Nom de la llista de correu", + mailingDescriptionLabel: "Descripció", + mailingDescriptionPlaceholder: "De què tracta aquesta llista?", + mailingTypeLabel: "Tipus de llista de correu", + mailingTypeOpen: "OBERTA", + mailingTypeClosed: "TANCADA", + mailingStatusLabel: "Estat", + mailingStatusActive: "ACTIVA", + mailingStatusArchived: "ARXIVADA", + mailingMembersLabel: "Convidar habitants", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Subscriptors", + mailingTagsLabel: "Etiquetes", + mailingTagsPlaceholder: "Introdueix etiquetes separades per comes", + mailingFilterAll: "TOTES", + mailingFilterMine: "MEVES", + mailingFilterSubscribed: "SUBSCRITES", + mailingFilterRecent: "RECENTS", + mailingHistoryDate: "DATA", + mailingHistoryThreads: "FILS", + mailingHistoryInhabitants: "HABITANTS", + mailingHistorySearchPlaceholder: "Cercar a l'historial...", + mailingNoMessages: "Encara no hi ha missatges.", + mailingNoItems: "No s'han trobat llistes de correu.", + mailingSearchPlaceholder: "Cercar a llistes de correu...", + mailingWriteButton: "Escriure un missatge", + mailingWriteTitle: "Escriure a la llista", + mailingReplyTitle: "Respondre a la llista", + mailingSend: "Enviar", + mailingTextPlaceholder: "El teu missatge arriba a tots els participants de la llista.", + mailingParticipants: "Participants", + inboxFilterMailing: "Llistes de correu", + mailingReplyPlaceholder: "Respondre a la llista...", + mailingReplyToList: "Respondre a la llista", + typeMailingList: "LLISTA DE CORREU", + statsMailingList: "Llistes de correu", + mailingListLabel: "LLISTA DE CORREU", + logisticsTitle: "Logística", + modulesLogisticsLabel: "Logística", + modulesLogisticsDescription: "Descobreix i gestiona plans logístics a la teva xarxa.", + logisticsDescription: "Descobreix i gestiona plans logístics a la teva xarxa.", + logisticsCreate: "Crear una ruta", + logisticsUpdate: "Actualitzar", + logisticsClose: "Tancar", + logisticsReopen: "Reobrir", + logisticsCreateSectionTitle: "Crear una ruta", + logisticsUpdateSectionTitle: "Actualitzar la ruta", + logisticsTitleLabel: "Títol", + logisticsTitlePlaceholder: "Viatge al mercat, furgoneta disponible...", + logisticsKindLabel: "Tipus de logística", + logisticsKindTrip: "VIATGE", + logisticsKindShipment: "ENVIAMENT", + logisticsKindTrips: "VIATGES", + logisticsKindShipments: "ENVIAMENTS", + logisticsModeLabel: "Mode", + logisticsModeOffer: "OFEREIXO", + logisticsModeRequest: "BUSCO", + logisticsModeOffers: "OFERTES", + logisticsModeRequests: "PETICIONS", + logisticsDescriptionLabel: "Descripció", + logisticsDescriptionPlaceholder: "Punt de trobada, què portes, condicions...", + logisticsOriginLabel: "Origen", + logisticsOriginPlaceholder: "On comença", + logisticsDestinationLabel: "Destinació", + logisticsDestinationPlaceholder: "On acaba", + logisticsDateLabel: "Data i hora", + logisticsRecurrenceLabel: "Recurrència", + logisticsFrequencyLabel: "Freqüència", + logisticsRecurrenceNone: "ÚNIC", + logisticsRecurrenceDaily: "DIARI", + logisticsRecurrenceWeekly: "SETMANAL", + logisticsRecurrenceMonthly: "MENSUAL", + logisticsSeatsLabel: "Places", + logisticsSizeLabel: "Mida", + logisticsSizePlaceholder: "p. ex. 2m x 1m", + logisticsWeightLabel: "Pes", + logisticsWeightPlaceholder: "p. ex. 40kg", + logisticsPriceFree: "GRATIS", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TEMPS", + logisticsPriceLabel: "Preu", + logisticsHours: "h", + logisticsOrderRefLabel: "Enllaç a la comanda", + logisticsOrderRefPlaceholder: "/shops/... o /market/... de la comanda que cal lliurar", + logisticsOrderLink: "Comanda enllaçada", + logisticsTagsLabel: "Etiquetes", + logisticsTagsPlaceholder: "Introdueix etiquetes separades per comes", + logisticsFilterAll: "TOTES", + logisticsFilterMine: "MEVES", + logisticsFilterBooked: "RESERVADES", + logisticsFilterRecent: "RECENTS", + logisticsFilterHistory: "HISTORIAL", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "OBERTA", + logisticsStatusUpcoming: "PROPERES", + logisticsStatusPast: "PASSADES", + logisticsStatusClosed: "TANCADES", + logisticsZoneLabel: "Zona", + logisticsSearchPlaceholder: "Cercar a logística...", + logisticsNoItems: "No s'han trobat rutes.", + logisticsBookingPending: "PENDENT", + logisticsBookingConfirmed: "CONFIRMADA", + logisticsBookingRejected: "REBUTJADA", + logisticsBookingCancelled: "CANCEL·LADA", + logisticsBookingDelivered: "LLIURADA", + logisticsConfirm: "Confirmar", + logisticsReject: "Rebutjar", + logisticsDeliver: "Lliurar", + logisticsReceiptLabel: "Rebut", + logisticsReceiptPlaceholder: "Nota de lliurament (opcional)", + logisticsCancelBooking: "Cancel·lar la reserva", + logisticsBookTitle: "Reservar aquesta ruta", + logisticsNotesLabel: "Notes", + logisticsNotesPlaceholder: "El que hagi de saber la persona responsable", + logisticsBook: "Reservar", + logisticsBookingsTitle: "Reserves", + logisticsMyBookingTitle: "La teva reserva", + logisticsRatingsTitle: "Valoracions", + logisticsNoRatings: "Encara no hi ha valoracions.", + logisticsRatingsWhenClosed: "Les valoracions s'obren en tancar la ruta.", + logisticsScoreLabel: "Puntuació", + logisticsRatingPlaceholder: "Com ha anat?", + logisticsRate: "Valorar", + typeLogisticsRoute: "RUTA", + typeLogisticsRating: "VALORACIÓ DE RUTA", + statsLogisticsRoute: "Rutes", + statsLogisticsRating: "Valoracions de rutes", + logisticsRouteLabel: "RUTA", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Mòdul per publicar canals de podcast.", + podcastsDescription: "Mòdul per publicar canals de podcast.", + podcastCreate: "Crear un podcast", + podcastUpdate: "Actualitzar", + podcastDelete: "Esborrar", + podcastCreateSectionTitle: "Crear un podcast", + podcastUpdateSectionTitle: "Actualitzar el podcast", + podcastTitleLabel: "Títol", + podcastTitlePlaceholder: "Nom del canal", + podcastDescriptionLabel: "Descripció", + podcastDescriptionPlaceholder: "De què tracta aquest podcast?", + podcastCategoryLabel: "Categoria", + podcastCategoryNews: "NOTÍCIES", + podcastCategoryMusic: "MÚSICA", + podcastCategoryTalk: "XERRADA", + podcastCategoryEducation: "EDUCACIÓ", + podcastCategoryTech: "TECNOLOGIA", + podcastCategoryCulture: "CULTURA", + podcastCategoryCommunity: "COMUNITAT", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "ALTRES", + podcastTagsLabel: "Etiquetes", + podcastCoverLabel: "Portada (imatge o vídeo)", + podcastEpisodeAudioLabel: "Fitxer d'àudio o vídeo (màx.: 75MB)", + podcastTagsPlaceholder: "Introdueix etiquetes separades per comes", + podcastFilterAll: "TOTS", + podcastFilterMine: "MEUS", + podcastFilterRecent: "RECENTS", + podcastFilterTop: "TOP", + podcastFilterViewers: "OIENTS", + podcastSearchPlaceholder: "Cercar a podcasts...", + podcastNoItems: "No s'han trobat podcasts.", + podcastEpisodesLabel: "Episodis", + podcastPlaysLabel: "Reproduccions", + podcastLastEpisode: "Darrer episodi", + podcastEpisodesTitle: "Episodis", + podcastNoEpisodes: "Encara no hi ha episodis.", + podcastEpisodeAdd: "Afegir un episodi", + podcastEpisodeCreateTitle: "Publicar un episodi", + podcastEpisodeUpdateTitle: "Actualitzar l'episodi", + podcastEpisodeTitleLabel: "Títol de l'episodi", + podcastEpisodeTitlePlaceholder: "Títol de l'episodi", + podcastEpisodeDescriptionLabel: "Notes", + podcastEpisodeDescriptionPlaceholder: "Què passa en aquest episodi, enllaços, crèdits...", + podcastEpisodePublish: "Publicar", + podcastPlayed: "Reproduït", + podcastMarkPlayed: "Marcar com a reproduït", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISODI", + typePodcastOpinion: "OPINIÓ D'EPISODI", + typePodcastPlay: "REPRODUCCIÓ", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Episodis", + statsPodcastPlay: "Reproduccions", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Un blog que segueixes té una entrada nova.", + podcastBotEpisodeTitle: "Un podcast que segueixes té un episodi nou.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISODI", + campaignsTitle: "Campanyes", + modulesCampaignsLabel: "Campanyes", + modulesCampaignsDescription: "Descobreix i gestiona campanyes a la teva xarxa.", + campaignsDescription: "Descobreix i gestiona campanyes a la teva xarxa.", + campaignCreate: "Crear una campanya", + campaignUpdate: "Actualitzar", + campaignDelete: "Esborrar", + campaignClose: "Tancar", + campaignReopen: "Reobrir", + campaignCreateSectionTitle: "Crear una campanya", + campaignUpdateSectionTitle: "Actualitzar la campanya", + campaignTitleLabel: "Títol", + campaignTitlePlaceholder: "Què vols aconseguir?", + campaignTextLabel: "Text", + campaignTextPlaceholder: "Explica la causa, qui decideix i què demanes...", + campaignCategoryLabel: "Categoria", + campaignCategoryEnvironment: "MEDI AMBIENT", + campaignCategoryRights: "DRETS", + campaignCategoryHealth: "SALUT", + campaignCategoryEducation: "EDUCACIÓ", + campaignCategoryInfrastructure: "INFRAESTRUCTURA", + campaignCategoryCulture: "CULTURA", + campaignCategoryEconomy: "ECONOMIA", + campaignCategoryOther: "ALTRES", + campaignGoalLabel: "Objectiu de signatures", + campaignDeadlineLabel: "Termini", + campaignTagsLabel: "Etiquetes", + campaignTagsPlaceholder: "Introdueix etiquetes separades per comes", + campaignFilterAll: "TOTES", + campaignFilterMine: "MEVES", + campaignFilterSigned: "SIGNADES", + campaignFilterRecent: "RECENTS", + campaignFilterTop: "TOP", + campaignStatusOpen: "OBERTA", + campaignStatusAchieved: "ACONSEGUIDA", + campaignStatusUnachieved: "NO ACONSEGUIDA", + campaignStatusClosed: "TANCADA", + campaignSearchPlaceholder: "Cercar a campanyes...", + campaignNoItems: "No s'han trobat campanyes.", + campaignSignaturesLabel: "Signatures", + campaignSignaturesTitle: "Suports", + campaignNoSignatures: "Encara no hi ha signatures.", + campaignSignTitle: "Signar aquesta campanya", + campaignSignPlaceholder: "Per què hi dones suport? (opcional)", + campaignSign: "Signar", + campaignSigned: "Ja l'has signada!", + campaignNotSigned: "Encara hi falta la teva signatura", + campaignElevate: "Elevar al Parlament", + campaignElevated: "Elevada al Parlament", + campaignUpdatesTitle: "Actualitzacions", + campaignNoUpdates: "Encara no hi ha actualitzacions.", + campaignUpdatePlaceholder: "Què ha passat?", + campaignAddUpdate: "Publicar una actualització", + campaignQrAlt: "QR per signar aquesta campanya", + campaignQrCaption: "Escaneja per signar", + typeCampaign: "CAMPANYA", + typeCampaignSignature: "SIGNATURA", + typeCampaignUpdate: "ACTUALITZACIÓ DE CAMPANYA", + statsCampaign: "Campanyes", + statsCampaignSignature: "Signatures de campanyes", + statsCampaignUpdate: "Actualitzacions de campanyes", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Una campanya que segueixes té una actualització.", + campaignBotAchievedTitle: "Una campanya que segueixes ha assolit el seu objectiu.", + campaignBotRaisedTitle: "Una campanya que has signat s'ha elevat al Parlament.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Una ruta que vas reservar ha canviat.", + logisticsBotClosedTitle: "Una ruta que vas reservar s'ha tancat.", + logisticsBotBookedTitle: "Algú ha reservat la teva ruta.", + logisticsBotCancelledTitle: "S'ha cancel·lat una reserva de la teva ruta.", + logisticsBotConfirmedTitle: "La teva reserva s'ha confirmat: el paquet és en camí.", + logisticsBotRejectedTitle: "La teva reserva s'ha rebutjat.", + logisticsBotDeliveredTitle: "El teu paquet ha arribat.", + campaignLabel: "CAMPANYA", + campaignGenerateReport: "Generar un informe", + logisticsGenerateReport: "Generar un informe", + mailingGenerateReport: "Generar un informe", + campaignUpdateEdited: "Editada", + mailingMessagesLabel: "Missatges", + logisticsBookingsLabel: "Reserves", + backupTitle: "Còpia de seguretat", + modulesBackupLabel: "Còpia de seguretat", + modulesBackupDescription: "Mòdul per exportar i importar les teves claus i fer còpies de seguretat xifrades.", + backupDescription: "Mòdul per exportar i importar les teves claus i fer còpies de seguretat xifrades.", + backupTypeRecovery: "RECUPERACIÓ", + backupTypeKeys: "EXPORTAR CLAUS", + backupTypeFull: "CÒPIA COMPLETA", + backupTypeRestore: "RESTAURAR UNA CÒPIA", + backupTypeInstant: "INSTANTÀNIA", + backupInstantDescription: "Un .zip pla amb tot el teu registre i els teus fitxers, sense la teva clau secreta i sense contrasenya. Ràpida, però ", + backupInstantRisk: "qui tingui el fitxer ho pot llegir tot.", + backupKitPageDescription: "El kit de recuperació només es pot obrir des d'aquest dispositiu.", + backupKitId: "ID d'Oasis", + backupKitDate: "Generat", + backupKitQrAlt: "Codi QR de la teva clau secreta", + backupKitStep1: "Imprimeix-lo o copia'l a mà i desa'l en un lloc segur i sense connexió.", + backupKitStep2: "Per recuperar-lo, instal·la Oasis en un dispositiu nou i importa aquest secret des de Còpia de seguretat › Claus.", + backupKitStep3: "Destrueix aquesta pàgina si mai queda exposada.", + backupKitPdfButton: "Generar PDF", + backupScopeLabel: "Abast", + backupScopeAll: "TOT", + backupScopeMine: "NOMÉS EL MEU CONTINGUT", + backupSinceLabel: "Des de", + backupEstimateTitle: "Estimació", + backupFileInfo: "La teva còpia xifrada es baixarà al teu dispositiu (nom: oasis-backup-DATA.oasisbk)", + backupExportButton: "Baixar la còpia", + backupRestorePassword: "Escriu la contrasenya que vas fer servir en fer la còpia", + backupRestoreButton: "Restaurar la còpia", + backupRestoredLine: "Còpia restaurada.", + backupRestoredMessages: "Missatges afegits", + backupRestoredSkipped: "Ja existents", + backupRestoredBlobs: "Fitxers afegits", + backupRestoredState: "Configuració restaurada", + backupRestoredFailed: "Fallits", + backupRestoredForked: "Divergents", + backupRestoreRunning: "S'està restaurant la còpia. Aquesta pàgina s'actualitza sola; mentrestant pots continuar fent servir Oasis.", + backupRestoreElapsed: "Transcorregut", + backupRestoreFailedLine: "No s'ha pogut restaurar la còpia.", + backupRestoreForkMine: "Aquest dispositiu ja ha publicat missatges amb la teva identitat que no són a la còpia, així que no s'hi ha pogut aplicar a sobre. Restaura-la en una instal·lació neta just després d'importar les teves claus, abans de fer res més.", + verificationTitle: "Verificació", + verificationDescription: "Comprova la cadena del teu feed, detecta bifurcacions i revisa fitxers orfes o absents.", + verificationRun: "Executar la verificació", + verificationTotalMessages: "missatges al registre", + verificationFeed: "El teu feed", + verificationMessages: "missatges", + verificationLastSequence: "darrera seqüència", + verificationGaps: "Buits", + verificationBrokenLinks: "Enllaços trencats", + verificationBadHashes: "Hashes incorrectes", + verificationBadSignatures: "Signatures incorrectes", + verificationDuplicates: "Seqüències duplicades", + verificationForks: "Bifurcacions", + verificationBlobs: "Fitxers", + verificationBlobsPresent: "presents", + verificationBlobsReferenced: "referenciats", + verificationBlobsOrphan: "orfes", + verificationBlobsMissing: "absents", + verificationBlobsMissingOwn: "Fitxers teus que falten en aquest dispositiu", + verificationBlobsMissingOthers: "Fitxers d'altres habitants encara sense baixar", + verificationBlobsUnavailable: "No s'han pogut llistar els fitxers d'aquest dispositiu.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Mòdul per descobrir i gestionar wikis.", + wikiDescription: "Mòdul per descobrir i gestionar wikis.", + wikiCreate: "Crear una wiki", + wikiUpdate: "Actualitzar", + wikiDelete: "Esborrar", + wikiEdit: "Editar", + wikiTitleLabel: "Títol", + wikiTitlePlaceholder: "Títol de la wiki...", + wikiBodyLabel: "Contingut", + wikiBodyPlaceholder: "Enllaça altres wikis amb [[Títol de la wiki]] o [[Títol de la wiki|un text propi]].\n\nMenciona un habitant amb @id.\n\nEtiqueta amb #oasis.\n\nEnganxa enllaços com https://solarnethub.com\n\nUna línia en blanc obre un paràgraf nou; els salts de línia simples es conserven.\n\nAdjunta qualsevol mitjà compatible i s'incrustarà aquí.", + formatBarLabel: "Format", + wikiStatusLabel: "Tipus de wiki", + wikiStatusOpen: "OBERTA", + wikiStatusClosed: "TANCADA", + wikiTagsLabel: "Etiquetes", + wikiTagsPlaceholder: "Introdueix etiquetes separades per comes", + wikiSummaryLabel: "Resum del canvi", + wikiSummaryPlaceholder: "Què ha canviat?", + wikiFilterAll: "TOTES", + wikiFilterMine: "MEVES", + wikiFilterRecent: "RECENTS", + wikiFilterLinked: "ENLLAÇADES", + wikiChanges: "Canvis recents", + wikiNoPages: "No s'han trobat wikis.", + wikiNoChanges: "Encara no hi ha canvis.", + wikiHistory: "Historial", + wikiVersion: "Versió", + wikiVersions: "versions", + wikiRestore: "Restaurar", + wikiDiff: "Canvis", + wikiViewVersion: "Veure", + wikiLinkedPages: "Pàgines enllaçades", + wikiNoLinkedPages: "Aquesta pàgina encara no enllaça cap altra.", + wikiMissingLink: "La pàgina encara no existeix", + wikiDuplicateTitle: "Ja existeix una pàgina amb aquest títol. Canvia el títol per crear una pàgina diferent.", + wikiCreateSectionTitle: "Crear una wiki", + wikiUpdateSectionTitle: "Actualitzar la wiki", + wikiSearchPlaceholder: "Cercar a la Wiki...", + wikiCurrentVersion: "Versió actual", + wikiViewingVersion: "Estàs veient una versió antiga", + wikiBackToPage: "Tornar a la pàgina", + wikiLinkedChip: "Enllaçada", + wikiRestoredSummary: "Versió restaurada", + wikiExportPdf: "Generar la pàgina", + wikiPreview: "Previsualització", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Crear una wiki", + tribeWikiEmpty: "Encara no hi ha wikis.", + typeWiki: "WIKI", + padsTitle: "Pads", + padTitle: "Pad", + modulesPadsLabel: "Pads", + modulesPadsDescription: "Mòdul per gestionar editors de text col·laboratius.", + padCreate: "Crear un pad", + padUpdate: "Actualitzar", + padDelete: "Eliminar", + padTitleLabel: "Títol", + padTitlePlaceholder: "Escriu el títol del pad...", + padStatusLabel: "Estat", + padTypeLabel: "Tipus de pad", + padStatusOpen: "OBERT", + padStatusInviteOnly: "NOMÉS CONVIDATS", + padStatusClosed: "TANCAT", + padDeadlineLabel: "Data límit", + padTagsLabel: "Etiquetes", + padTagsPlaceholder: "Introdueix etiquetes separades per comes", + padMembersLabel: "Membres", + padCreated: "Creat", + padStartEditing: "Editar", + padEditorPlaceholder: "Comença a escriure...", + padSubmitEntry: "Enviar", + padNoEntries: "Encara no hi ha entrades.", + padAllSectionTitle: "Tots els pads", + padMineSectionTitle: "Els meus pads", + padsDescription: "Gestiona editors de text xifrats i col·laboratius a la teva xarxa.", + roomsTitle: "Sales", + roomsDescription: "Descobreix i uneix-te a sales de videoconferència xifrades a la teva xarxa.", + modulesRoomsLabel: "Sales", + modulesRoomsDescription: "Mòdul per descobrir i unir-se a sales de videoconferència xifrades.", + roomFilterRecent: "RECENTS", + roomFilterMine: "MEVES", + roomFilterAll: "TOTES", + roomFilterLive: "EN DIRECTE", + roomFilterOpen: "OBERTES", + roomFilterInvite: "NOMÉS CONVIDATS", + roomFilterClosed: "TANCADES", + roomCreate: "Crear sala", + roomUpdate: "Actualitzar", + roomDelete: "Eliminar", + roomClose: "Tancar", + roomCreateSectionTitle: "Crear sala", + roomUpdateSectionTitle: "Actualitzar sala", + roomTitlePlaceholder: "Títol de la sala", + roomDescriptionLabel: "Descripció", + roomDescriptionPlaceholder: "De què parlareu?", + roomTypeLabel: "Tipus de sala", + roomTagsLabel: "Etiquetes", + roomStatusOpen: "OBERTA", + roomStatusInviteOnly: "NOMÉS CONVIDATS", + roomStatusClosed: "TANCADA", + roomParticipants: "Participants", + roomJoin: "Entrar", + roomLeave: "Sortir", + roomFull: "Sala plena", + roomLiveChip: "EN DIRECTE", + roomMuted: "silenciat", + roomRec: "Gravar àudio", + roomRecStop: "Atura la gravació", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} ha entrat a la sala", + roomEventLeft: "{name} ha sortit de la sala", + roomEventRecStart: "{name} ha començat a gravar", + roomEventRecStop: "{name} ha deixat de gravar", + roomEventMuted: "{name} s'ha silenciat", + roomEventUnmuted: "{name} ha tret el silenci", + roomEventHand: "{name} ha aixecat la mà", + roomRaiseHand: "Aixecar la mà", + roomLowerHand: "Abaixar la mà", + confirmActionTitle: "Confirma aquesta acció", + confirmActionButton: "Confirmar", + confirmJoinText: "Estàs a punt d'unir-te amb aquesta invitació.", + confirmFollowText: "Estàs a punt de donar suport a aquest habitant.", + confirmSignText: "Estàs a punt de signar aquesta campanya.", + confirmDownloadText: "Estàs a punt de descarregar aquest fitxer al teu dispositiu.", + roomRecordings: "Gravacions", + roomClearNotices: "Netejar", + roomLineSwitchboard: "Centraleta", + roomLineDndError: "El número d'aquesta sala no accepta trucades.", + roomNumberHint: "Marca aquest número des de Telèfon per entrar a la sala.", + liveRefresh: "Refrescar", + roomNobody: "Ara mateix no hi ha ningú a la sala.", + roomSomeone: "{n} a la sala ara mateix.", + roomYou: "tu", + roomYouAreIn: "Hi ets dins", + roomNobodyInside: "Ara mateix no hi ha ningú en aquesta sala.", + roomErrorFull: "La sala és plena. Podràs entrar quan algú en surti.", + roomErrorUnreachable: "Ara mateix no es pot arribar a la sala. Torna-ho a provar més tard.", + roomErrorBusy: "Ja ets en una trucada o en una altra sala.", + roomErrorClosed: "Aquesta sala està tancada.", + roomAccessDenied: "Aquesta sala és només amb invitació. Necessites una invitació per entrar.", + roomsNoItems: "No s'han trobat sales.", + roomSearchPlaceholder: "Cercar sales...", + roomInviteMode: "Invitació", + tribeSectionRooms: "SALES", + tribeRoomCreate: "Crear sala", + tribeRoomsEmpty: "Encara no hi ha sales.", + typeRoom: "SALES", + typeRoomMember: "PARTICIPANT DE SALA", + statsRoom: "Sales", + statsRoomMember: "Participants de sales", + favoritesFilterRooms: "SALES", + invitesRoomsTitle: "Sales", + inviteCodeInvalid: "Aquest codi d'invitació no és vàlid o ja s'ha fet servir.", + inviteAlreadyMember: "Ja en formes part. Pots obrir-ho des del seu mòdul.", + actionFailed: "No s'ha pogut fer. Torna-ho a provar.", + publishTooShort: "La teva publicació és massa curta.", + walletSendFailed: "No s'ha pogut enviar el pagament: {reason}", + pixeliaCoordsError: "Aquestes coordenades són fora de la quadrícula.", + courtsErrTitle: "Cal un títol.", + courtsErrRespondent: "Indica contra qui és el cas.", + courtsErrRespondentId: "Aquest no és un Oasis ID vàlid.", + courtsErrMethod: "Tria com es resoldrà el cas.", + courtsErrCaseNotFound: "No s'ha trobat aquest cas.", + courtsErrBrief: "Escriu la teva resposta.", + courtsErrStance: "Tria la teva postura.", + courtsErrResult: "Escriu el resultat.", + courtsErrTerms: "Escriu els termes de l'acord.", + courtsErrMediator: "Afegeix almenys un mediador.", + courtsErrSelfMediator: "No pots ser mediador en el teu propi cas.", + courtsErrJudge: "Tria un jutge.", + courtsErrSelfJudge: "No pots ser jutge del teu propi cas.", + courtsErrVisibility: "Tria qui pot veure el cas.", + courtsErrNomination: "No s'ha trobat aquesta nominació.", + opinionAlreadyGiven: "Ja has donat la teva opinió.", + cipherPasswordShort: "La contrasenya ha de tenir almenys 32 caràcters.", + errorForbidden: "No hi tens accés.", + errorNotFound: "No s'ha trobat.", + errorClosed: "Està tancat.", + errorInviteOnly: "Això és només amb invitació. Necessites una invitació.", + errorTooLong: "Aquest text és massa llarg.", + errorRequired: "Falta informació obligatòria.", + errorInvalid: "Part de la informació no és vàlida.", + errorNotAuthor: "Només qui ho va crear pot fer això.", + panicRemoved: "La teva blockchain s'ha eliminat.", + padRecentSectionTitle: "Pads recents", + padOpenSectionTitle: "Pads oberts", + padClosedSectionTitle: "Pads tancats", + padCreateSectionTitle: "Crear un pad nou", + padUpdateSectionTitle: "Actualitzar el pad", + typePad: "PADS", + padNew: "NOU", + padAddFavorite: "Afegir a preferits", + padRemoveFavorite: "Treure de preferits", + padClose: "Tancar", + padBackToEditor: "Tornar a l'editor", + padSearchPlaceholder: "Cercar a Pads...", + + calendarsTitle: "Calendaris", + calendarTitle: "Calendari", + modulesCalendarsLabel: "Calendaris", + modulesCalendarsDescription: "Mòdul per descobrir i gestionar calendaris.", + typeCalendar: "CALENDARIS", + calendarCreate: "Crear un calendari", + calendarUpdate: "Actualitzar", + calendarTitleLabel: "Títol", + calendarTitlePlaceholder: "Títol del calendari...", + calendarStatusLabel: "Estat", + calendarTypeLabel: "Tipus de calendari", + calendarStatusOpen: "OBERT", + calendarStatusClosed: "TANCAT", + calendarDeadlineLabel: "Data límit", + calendarTagsLabel: "Etiquetes", + calendarTagsPlaceholder: "Introdueix etiquetes separades per comes", + calendarParticipantsLabel: "Participants", + calendarCreated: "Creat", + calendarJoin: "Unir-se al calendari", + calendarGenerateInvite: "Generar una invitació", + calendarDateLabel: "Data", + calendarDatePlaceholder: "Descriu aquesta data...", + calendarNoteLabel: "Notes", + calendarNotePlaceholder: "Afegir una nota...", + calendarFirstDateLabel: "Data", + calendarFirstNoteLabel: "Notes", + calendarIntervalLabel: "Interval", + calendarIntervalNone: "Sense repetició", + calendarIntervalWeekly: "Setmanal", + calendarIntervalMonthly: "Mensual", + calendarIntervalYearly: "Anual", + calendarFormDescription: "Descripció", + calendarNoDates: "No hi ha dates afegides.", + calendarNoNotes: "Sense notes.", + calendarsNoItems: "No s'han trobat calendaris.", + calendarsDescription: "Descobreix i gestiona calendaris a la teva xarxa.", + calendarMonthPrev: "← Anterior", + calendarMonthNext: "Següent →", + calendarMonthNames: "Gener,Febrer,Març,Abril,Maig,Juny,Juliol,Agost,Setembre,Octubre,Novembre,Desembre", + calendarDayInitials: "Dl,Dt,Dc,Dj,Dv,Ds,Dg", + calendarAllSectionTitle: "Calendaris", + calendarRecentSectionTitle: "Calendaris recents", + calendarFavoritesSectionTitle: "Preferits", + calendarMineSectionTitle: "Els teus calendaris", + calendarOpenSectionTitle: "Calendaris oberts", + calendarClosedSectionTitle: "Calendaris tancats", + calendarCreateSectionTitle: "Crear un calendari nou", + calendarUpdateSectionTitle: "Actualitzar el calendari", + calendarAddFavorite: "Afegir a preferits", + calendarDeleteNote: "Eliminar", + calendarRemoveFavorite: "Treure de preferits", + calendarSearchPlaceholder: "Cercar al calendari...", + calendarAddEntry: "Afegir una entrada", + statsCalendar: "Calendaris", + statsCalendarDate: "Dates de calendari", + statsCalendarNote: "Notes de calendari", + + modulesChatsLabel: "Xats", + modulesChatsDescription: "Mòdul per descobrir i gestionar xats.", + typeChat: "XATS", + typeChatMessage: "MISSATGE DE XAT", + chatsTitle: "Xats", + chatMineSectionTitle: "Els teus xats", + chatRecentTitle: "Xats recents", + chatFavoritesTitle: "Preferits", + chatOpenTitle: "Xats oberts", + chatClosedTitle: "Xats tancats", + chatDescription: "Descripció", + chatCategory: "Categoria", + chatStatus: "ESTAT", + chatCreate: "Crear un xat", + chatUpdate: "Actualitzar", + chatDelete: "Esborrar", + chatClose: "Tancar el xat", + chatVisitChat: "VEURE EL XAT", + chatUntitled: "Xat sense títol", + chatNoItems: "No s'han trobat xats.", + chatParticipants: "Participants", + chatMessagesLabel: "Missatges", + chatThreadsLabel: "Fils", + chatCreatedAt: "CREAT", + chatSearchPlaceholder: "Cercar a Xats...", + chatStatusOpen: "OBERT", + chatStatusInviteOnly: "NOMÉS CONVIDATS", + chatStatusClosed: "TANCAT", + chatSendMessage: "Enviar", + chatJumpLatest: "Darrer missatge", + chatReply: "Respondre", + chatReplyCancel: "Cancel·lar la resposta", + chatPin: "Fixar", + chatUnpin: "Desfixar", + chatPinned: "Fixats", + chatPickChat: "Tria un xat per obrir-lo", + chatNoneYet: "Encara no hi ha cap xat disponible.", + activitySearchPlaceholder: "Cercar a Activitat...", + trendingSearchPlaceholder: "Cercar a Tendències...", + opinionsSearchPlaceholder: "Cercar a Opinions...", + votesSearchPlaceholder: "Cercar a Votacions...", + gamesSearchPlaceholder: "Cercar a Jocs...", + schoolTitle: "Escola", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "S'ha editat una pàgina wiki que segueixes.", + wikiBotRestoredTitle: "S'ha restaurat una versió anterior d'una pàgina wiki que segueixes.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Un estudiant nou s'ha apuntat al teu curs.", + schoolBotInvitedTitle: "T'han convidat a un curs.", + schoolBotAdmittedTitle: "T'han admès en un curs.", + schoolBotCertificateTitle: "Has rebut un certificat.", + schoolBotPassedTitle: "Un estudiant ha aprovat el teu curs.", + schoolBotLessonTitle: "S'ha publicat una lliçó nova.", + invitesSchoolTitle: "Escola", + schoolEnrollLabel: "Unir-se al curs", + schoolExamRetryLater: "Ho podràs tornar a provar passades 24 hores.", + schoolApproved: "APROVAT", + schoolExamPassMark: "Nota d'aprovat (0-10)", + schoolExamPassed: "APROVAT", + schoolExamPending: "Pendent", + schoolExamFailed: "SUSPÈS", + schoolExamAddQuestion: "Afegir una pregunta", + schoolExamCreateQuestion: "Crear una pregunta", + schoolExamOption: "Opció", + schoolExamCorrect: "Opció correcta", + schoolExamPoints: "Punts", + schoolMaterialTextPlaceholder: "Notes per a aquesta lliçó...", + schoolMaterials: "Materials", + schoolAddMaterial: "Afegir material", + schoolNoMaterials: "Encara no hi ha materials.", + bankYourSchoolBalance: "Els teus ingressos de l'escola", + schoolProgress: "Progrés", + schoolProgressMark: "Marcar com a completada", + schoolProgressDone: "Completada", + schoolExams: "Exàmens", + schoolExamCreate: "Crear un examen", + schoolExamTitlePlaceholder: "Títol de l'examen", + schoolExamHelp: "Una pregunta per línia: Pregunta? ; opció ; opció correcta* ; opció", + schoolExamQuestionsPlaceholder: "Què és SSB? ; Una base de dades ; Un protocol P2P* ; Un navegador", + schoolExamSubmit: "Enviar les respostes", + schoolExamScore: "La teva nota", + schoolNoScoresYet: "Encara no hi ha resultats.", + schoolLessonUnit: "Unitat / fase", + schoolLessonUnitPlaceholder: "p. ex. Fase 1", + schoolLessonOrder: "Ordre", + profileClearnetSchoolLabel: "Escola", + schoolStartDate: "Data d'inici", + schoolSessionDate: "Data de la sessió", + schoolCourseChat: "Xat del curs", + agendaFilterSchool: "ESCOLA", + agendaFilterCampaigns: "CAMPANYES", + agendaFilterLogistics: "LOGÍSTICA", + schoolCourseType: "Tipus de curs", + schoolTypeOpen: "OBERT", + schoolTypePaid: "DE PAGAMENT", + schoolTypeInvite: "NOMÉS PER INVITACIÓ", + schoolLessonLocked: "Lliçó xifrada — demana la clau del curs al professor.", + schoolGrantAccess: "Enviar la clau del curs", + schoolKeySent: "Clau enviada", + schoolOngoing: "En curs", + schoolFilterApplied: "APUNTAT", + schoolFilterOpen: "OBERTS", + schoolFilterFavorites: "PREFERITS", + schoolPendingPayment: "Pagament pendent", + schoolPendingStudents: "Inscripcions pendents", + schoolViewBill: "Veure la factura", + schoolPaymentNote: "En inscriure't es genera una factura en ECO (una transferència). La teva inscripció es confirma quan la transferència queda signada.", + schoolCloseCourse: "Tancar el curs", + schoolReopenCourse: "Reobrir el curs", + schoolDescription: "Ensenya i aprèn cursos a la teva xarxa.", + schoolFilterAll: "TOTS", + schoolFilterMine: "MEUS", + schoolFilterTop: "TOP", + schoolFilterRecent: "RECENTS", + schoolCreateButton: "Crear un curs", + schoolCreateSectionTitle: "Crear un curs", + schoolUpdateSectionTitle: "Actualitzar el curs", + schoolCourseTitlePlaceholder: "Nom del teu curs", + schoolCourseDescriptionPlaceholder: "Què ensenyaràs en aquest curs?", + schoolCourseImage: "Imatge del curs (mida màx.: 75MB)", + schoolTags: "Etiquetes", + schoolTagsPlaceholder: "Introdueix etiquetes separades per comes", + schoolPrice: "Preu (ECO)", + schoolVisibility: "Visibilitat", + schoolVisibilityPublic: "Públic", + schoolVisibilityInvite: "Per invitació", + schoolStatus: "Estat", + schoolClosed: "Tancat", + schoolFree: "Gratis", + schoolCreate: "Crear el curs", + schoolUpdate: "Actualitzar el curs", + schoolSearchPlaceholder: "Cercar a l'Escola...", + schoolSortTitle: "Títol", + schoolNoCourses: "No s'han trobat cursos.", + schoolStudents: "Estudiants", + schoolTeacher: "Professor", + schoolCreatedAt: "CREAT", + schoolLessons: "Lliçons", + schoolNoLessons: "Encara no hi ha lliçons.", + schoolEnrollToSee: "Inscriu-te per veure les lliçons d'aquest curs.", + schoolEnrollButton: "INSCRIURE'S", + schoolUnenrollButton: "Abandonar el curs", + schoolInviteRequired: "Aquest curs és per invitació.", + schoolTeacherPanel: "Tauler del professor", + schoolStudentPanel: "Tauler de l'estudiant", + schoolAddLesson: "Crear una lliçó", + schoolLessonTitlePlaceholder: "Títol de la lliçó", + schoolLessonTextPlaceholder: "Descriu les nocions que cobreix aquesta lliçó...", + schoolInviteStudents: "Convidar estudiants", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "CONVIDAR", + schoolInvited: "Convidats", + schoolIssueCertificate: "Crear un certificat", + schoolCertified: "Certificat", + schoolCertificateTextPlaceholder: "Nota del certificat (opcional)", + schoolLessonOpen: "Obrir la lliçó", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificats", + schoolNoCertificates: "Encara no s'han emès certificats.", + schoolEditButton: "Editar", + schoolDeleteButton: "Esborrar", + typeSchool: "ESCOLA", + schoolCourseLabel: "CURS", + emergencyLabel: "EMERGÈNCIA", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Cursos", + statsSchoolLesson: "Lliçons", + statsSchoolEnroll: "Inscripcions", + statsSchoolCertificate: "Certificats de l'escola", + modulesSchoolLabel: "Escola", + modulesSchoolDescription: "Mòdul per intercanviar coneixement.", + welcomeStepUxAction: "Desar vista", + welcomeStepWorkflowTitle: "Quin ús d'OASIS tens al cap?", + welcomeStepWorkflowText: "Oasis té molts mòduls. Tria la configuració que s'adapti a tu i només quedaran activats els útils. Pots ajustar-ho quan vulguis a Mòduls.", + welcomeStepWorkflowAction: "Desar configuració", + welcomeWorkflow_full: "Per defecte", + chatRateLimitTitle: "Massa missatges", + chatRateLimitMessage: "Has arribat al límit de missatges que aquesta sala accepta en una hora. Torna-ho a provar més tard.", + chatMessagePlaceholder: "Escriu el teu missatge...", + chatNoMessages: "Encara no hi ha missatges.", + chatTitlePlaceholder: "Títol del xat", + chatDescriptionPlaceholder: "Descripció del xat", + chatTagsPlaceholder: "Introdueix etiquetes separades per comes", + chatImageLabel: "Selecciona un fitxer d'imatge (.jpeg, .jpg, .png, .gif)", + chatAuthor: "Autor", + chatAddFavorite: "Afegir a preferits", + chatRemoveFavorite: "Treure de preferits", + chatPM: "MP", + chatStatusLabel: "Estat", + chatTypeLabel: "Tipus de xat", + chatCategoryLabel: "Categoria", + gamesTitle: "Jocs", + gamesDescription: "Descobreix alguns jocs i juga-hi.", + gamesFilterAll: "TOTS", + gamesPlayButton: "JUGAR!", + modulesGamesLabel: "Jocs", + modulesGamesDescription: "Mòdul per descobrir alguns jocs i jugar-hi.", + modulesGraphosLabel: "Graphos", + modulesGraphosDescription: "Mòdul per explorar la xarxa com un mapa interactiu de nodes.", + gamesCocolandTitle: "Cocoland", + gamesCocolandDesc: "Un coco amb ulls que salta palmeres i col·lecciona ECOins. Fins on pots arribar?", + gamesTheFlowTitle: "ECOinflow", + gamesTheFlowDesc: "Connecta PUBs amb habitants mitjançant validadors, botigues i acumuladors. Sobreviu a l'amenaça CBDC!", + gamesNeonInfiltratorTitle: "Neon Infiltrator", + gamesNeonInfiltratorDesc: "Infiltra't a la graella, recull dades confidencials, esquiva els drons de seguretat i escapa. Quants nivells pots superar?", + gamesSpaceInvadersTitle: "Space Invaders", + gamesSpaceInvadersDesc: "Atura la invasió alienígena! Destrueix onades d'invasors abans que arribin a terra.", + gamesArkanoidTitle: "Arkanoid", + gamesArkanoidDesc: "Trenca tots els maons amb la pala i la pilota. Un clàssic desafiament arcade.", + gamesPingPongTitle: "PingPong", + gamesPingPongDesc: "Ping-pong clàssic contra una IA. Guanya qui arriba primer a 5 punts.", + gamesAsteroidsTitle: "Asteroids", + gamesAsteroidsDesc: "Pilota la teva nau per un camp d'asteroides mortal. Dispara'ls abans que t'atrapin.", + gamesRockPaperScissorsTitle: "Pedra, paper, tisores", + gamesRockPaperScissorsDesc: "Pedra, paper o tisores contra una IA. Guanya qui s'emporta dues de tres rondes.", + gamesTikTakToeTitle: "TikTakToe", + gamesTikTakToeDesc: "Tres en ratlla clàssic contra la IA. Fes tres en línia per guanyar.", + gamesFlipFlopTitle: "FlipFlop", + gamesFlipFlopDesc: "Llança una moneda i aposta cara o creu. Quanta sort tens?", + games8BallTitle: "8Ball Pool", + games8BallDesc: "Billar vist des de dalt. Fes clic per apuntar i mantén premut per carregar la potència. Entra totes les boles amb el mínim de tirs.", + gamesArtilleryTitle: "Artilleria", + gamesArtilleryDesc: "Apunta el canó, tingues en compte el vent i encerta el blanc. 5 rondes: guanya qui fa menys tirs.", + gamesLabyrinthTitle: "Laberint", + gamesLabyrinthDesc: "Surt del laberint abans que s'acabin els moviments. Cada nivell és més gran i més difícil.", + gamesCocomanTitle: "Cocoman", + gamesCocomanDesc: "Menja't tots els punts i esquiva els fantasmes. Els fantasmes es mouen en temps real: no t'aturis!", + gamesTetrisTitle: "Tetris", + gamesAudioPendulumTitle: "Pèndol sonor", + gamesAudioPendulumDesc: "Simulador de física caòtica amb síntesi d'àudio en temps real. Les velocitats angulars es converteixen en freqüències i els pics, en cops de percussió. No hi ha dues simulacions que sonin igual.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Punxa visuals en directe amb dos plats: tria un patró per a cadascun, ajusta velocitat, color i zoom, mescla entre tots dos i llança flaixos al ritme. Marca el tempo o deixa que el micròfon porti el pols.", + gamesTetrisDesc: "Els clàssics blocs que cauen. Completa línies per puntuar. Quant de temps aguantaràs?", + gamesMinesweeperTitle: "Pescamines", + gamesMinesweeperDesc: "La clàssica caça de mines. Neteja el tauler per pujar de nivell — graelles més grans, més mines, més puntuació.", + gamesFilterScoring: "PUNTUACIONS", + gamesHallOfFame: "Saló de la fama", + gamesHallPlayer: "Jugador", + gamesHallScore: "Puntuació", + gamesNoScores: "Encara no hi ha puntuacions.", + chatAccessDenied: "No tens accés a aquest xat. Demana una invitació per accedir al contingut.", + padAccessDenied: "No tens accés a aquest pad. Demana una invitació per accedir al contingut.", + contentAccessDenied: "No tens accés a aquest contingut.", + blockAccessRestricted: "Accés restringit", + tribeContentAccessDenied: "Accés denegat", + tribeContentAccessDeniedMsg: "Aquest contingut pertany a una tribu. N'has de ser membre per accedir-hi.", + tribeViewTribes: "Veure les tribus", + tribeFilesEmpty: "Encara no hi ha fitxers.", + filesLabel: "Fitxers", + profileClearnetFilesLabel: "Fitxers", + filesTitle: "Fitxers", + filesDescription: "Explora i gestiona fitxers a la teva xarxa.", + fileAllSectionTitle: "Fitxers", + fileMineSectionTitle: "Els teus fitxers", + fileRecentSectionTitle: "Fitxers recents", + fileTopSectionTitle: "Fitxers destacats", + fileFavoritesSectionTitle: "Preferits", + fileFilterAll: "TOTS", + fileFilterMine: "MEUS", + fileFilterRecent: "RECENTS", + fileFilterTop: "DESTACATS", + fileFilterFavorites: "PREFERITS", + fileCreateSectionTitle: "Pujar un fitxer", + fileUpdateSectionTitle: "Actualitzar el fitxer", + fileCreateButton: "Pujar un fitxer", + fileUpdateButton: "Actualitzar", + fileAddFavoriteButton: "Afegir a preferits", + fileRemoveFavoriteButton: "Treure de preferits", + fileFileLabel: "Selecciona un fitxer (màx.: 75MB)", + calendarFilterRecent: "RECENTS", + calendarFilterAll: "TOTS", + calendarFilterMine: "MEUS", + calendarFilterFavorites: "PREFERITS", + calendarFilterOpen: "OBERTS", + calendarFilterClosed: "TANCATS", + padFilterRecent: "RECENTS", + padFilterAll: "TOTS", + padFilterMine: "MEUS", + padFilterOpen: "OBERTS", + padFilterClosed: "TANCATS", + chatFilterRecent: "RECENTS", + chatFilterAll: "TOTS", + chatFilterMine: "MEUS", + chatFilterFavorites: "PREFERITS", + chatFilterOpen: "OBERTS", + chatFilterClosed: "TANCATS", + licenseLabel: "Llicència", + phoneTitle: "Telèfon", + phoneDescription: "Gestiona les teves videotrucades xifrades d'extrem a extrem.", + phoneUnavailable: "Les trucades no estan disponibles en aquest dispositiu.", + phoneCallButton: "Trucar", + profileVisibilityPhone: "Número de telèfon", + phoneNumberUnknown: "Ningú de la teva xarxa té el número {number}.", + phoneNumberChoose: "Comprova a qui vols trucar al {number}:", + phoneAnswer: "Respondre", + phoneSpeaker: "Altaveu", + phoneEarpiece: "Auricular", + phoneReject: "Rebutjar", + phoneHangup: "Penjar", + phoneMute: "Silenciar", + phoneUnmute: "Activar micròfon", + phoneCameraOn: "Càmera on", + phoneCameraOff: "Càmera off", + settingsPhoneVideo: "Vídeo", + settingsPhoneVideoOn: "Permès en trucades i sales", + settingsPhoneVideoOff: "Només àudio", + phoneSilence: "No sentir", + phoneUnsilence: "Tornar a sentir", + phoneCalling: "Trucant a", + phoneIsCalling: "t'està trucant", + notifyNewPmFrom: "Nou MP de", + notifyNewPamFrom: "Nou PaM de", + notifyUpdateLabel: "Actualització", + updateInstalledLabel: "Versió instal·lada", + updateAvailableLabel: "Versió disponible", + updateChangesLabel: "Canvis", + updateFilesLabel: "Fitxers", + updateOriginLabel: "Origen", + phoneRecording: "Gravant PaM per a", + phoneLeaveMessage: "Ningú respon: deixa el teu missatge després del senyal.", + phoneLeaveMessageBusy: "És en una altra trucada: deixa el teu missatge després del senyal.", + phoneLeaveMessageUnreachable: "No s'ha pogut contactar: deixa el teu missatge després del senyal.", + phoneConnecting: "Connectant amb", + phoneSendPam: "Enviar PaM", + phoneCancel: "Cancel·lar", + phoneSearchPlaceholder: "Cercar a Telèfon...", + phonePamTitle: "Enregistraments d'àudio", + phoneNew: "Nou", + phoneDelete: "Esborrar", + phoneNoPams: "No tens enregistraments d'àudio.", + phoneHistoryTitle: "Historial", + phoneLatestTitle: "Últimes trucades", + phoneCreateButton: "Nova trucada", + phoneLimitsHint: "Fins a 7 habitants per trucada; amb més d'un és una trucada conjunta.", + phoneErrorPub: "No es pot trucar a un pub.", + phoneJointCall: "Trucada conjunta", + phoneWith: "Amb", + phoneResult: "Resultat", + phoneWhen: "Quan", + phoneDuration: "Durada", + phoneOutgoing: "Sortint", + phoneIncoming: "Entrant", + phoneNoHistory: "Encara no hi ha registres.", + phoneOutcomeEnded: "Atesa", + phoneOutcomeMissed: "Perduda", + phoneOutcomeRejected: "Rebutjada", + phoneOutcomeNoanswer: "Sense resposta", + phoneOutcomeCancelled: "Cancel·lada", + phoneOutcomePam: "PaM enviat", + phoneOutcomeFailed: "Fallida", + phoneDialAdd: "Afegir", + phoneDialBack: "Esborrar", + phoneDialClear: "Netejar", + phoneDialPlaceholder: "Número o Oasis ID", + phoneDialInhabitants: "Habitants", + phoneDialRemove: "Treure", + phoneOwnNumber: "Aquest és el teu propi número.", + phoneSettingsDesc: "Tria qui et pot trucar.", + phoneDnd: "No molestar", + phoneOpen: "Obert", + phoneRecipientRefuses: "L'habitant a qui intentes trucar no permet rebre trucades.", + phoneRefusedNotice: "Aquests habitants no permeten rebre trucades i no se'ls ha trucat:", + fileEmptyError: "L'arxiu és buit. Tria un altre arxiu.", + fileTitleLabel: "Títol", + fileTitlePlaceholder: "Títol", + fileDescriptionLabel: "Descripció", + fileDescriptionPlaceholder: "Descripció", + fileTagsLabel: "Etiquetes", + fileTagsPlaceholder: "Introdueix etiquetes separades per comes", + fileSizeLabel: "Mida", + fileDownloadButton: "BAIXA", + fileDetailsButton: "DETALLS", + fileNoFile: "Sense fitxer fitxer", + fileSearchPlaceholder: "Cercar a Fitxers...", + fileSearchButton: "Cercar", + fileSortRecent: "Més recents", + fileSortOldest: "Més antics", + fileSortTop: "Més votats", + fileNoMatch: "No s'han trobat fitxers coincidents.", + fileUpdatedAt: "Actualitzat", + typeFile: "FITXERS", + modulesFilesLabel: "Fitxers", + modulesFilesDescription: "Mòdul per descobrir i gestionar àudios.", + tribeSectionFiles: "FITXERS", + fileDescription: "Descripció", + fileDownload: "Baixar", + fileTypeLabel: "Tipus", + torrentsTitle: "Torrents", + torrentsDescription: "Explora i gestiona torrents a la teva xarxa.", + torrentAllSectionTitle: "Torrents", + torrentMineSectionTitle: "Els teus torrents", + torrentRecentSectionTitle: "Torrents recents", + torrentTopSectionTitle: "Torrents destacats", + torrentFavoritesSectionTitle: "Preferits", + torrentFilterAll: "TOTS", + torrentFilterMine: "MEUS", + torrentFilterRecent: "RECENTS", + torrentFilterTop: "DESTACATS", + torrentFilterFavorites: "PREFERITS", + torrentCreateSectionTitle: "Pujar un torrent", + torrentUpdateSectionTitle: "Actualitzar el torrent", + torrentCreateButton: "Pujar un torrent", + torrentUpdateButton: "Actualitzar", + torrentDeleteButton: "Eliminar", + torrentAddFavoriteButton: "Afegir a preferits", + torrentRemoveFavoriteButton: "Treure de preferits", + torrentFileLabel: "Seleccionar un fitxer torrent (.torrent)", + torrentTitleLabel: "Títol", + torrentTitlePlaceholder: "Títol", + torrentDescriptionLabel: "Descripció", + torrentDescriptionPlaceholder: "Descripció", + torrentTagsLabel: "Etiquetes", + torrentTagsPlaceholder: "Introdueix etiquetes separades per comes", + torrentSizeLabel: "Mida", + torrentFromContent: "Generar torrent", + torrentFromContentMissing: "El fitxer encara no està disponible en aquest node. Torna-ho a provar d'aquí a una estona.", + torrentDownloads: "Baixades", + torrentFetchOasis: "Baixar a Oasis", + torrentSaveFile: "Desar fitxer", + torrentSeeds: "llavors", + seedsLabel: "Llavors", + seedAction: "Donar suport", + torrentSourceFile: "Fitxer", + torrentStateQueued: "En cua", + torrentStateProbing: "Cercant llavors", + torrentStateDownloading: "Baixant", + torrentStateDone: "Completada", + torrentStateFailed: "Fallida", + torrentStateNoSource: "Sense llavors connectades, esperant", + torrentStateCancelled: "Cancel·lada", + torrentNoDownloads: "Encara no hi ha baixades.", + torrentOriginalSection: "Original", + torrentCancel: "Cancel·lar", + torrentDownloadButton: "BAIXA", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETALLS", + torrentNoFile: "Sense fitxer torrent", + noTorrents: "No s'han trobat torrents.", + noFiles: "No s'han trobat fitxers.", + torrentSearchPlaceholder: "Cercar a Torrents...", + torrentSearchButton: "Cercar", + torrentSortRecent: "Més recents", + torrentSortOldest: "Més antics", + torrentSortTop: "Més votats", + torrentNoMatch: "No s'han trobat torrents coincidents.", + torrentUpdatedAt: "Actualitzat", + statsTorrent: "Torrents", + statsFile: "Fitxers", + typeTorrent: "TORRENTS", + modulesTorrentsLabel: "Torrents", + modulesTorrentsDescription: "Mòdul per descobrir i gestionar torrents.", + favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "FITXERS", + favoritesFilterMarket: "MERCAT", + favoritesFilterEvents: "ESDEVENIMENTS", + favoritesFilterForum: "FÒRUM", + favoritesFilterHousing: "HABITATGE", + favoritesFilterJobs: "FEINES", + favoritesFilterProjects: "PROJECTES", + favoritesFilterReports: "INFORMES", + favoritesFilterTasks: "TASQUES", + favoritesFilterTransfers: "TRANSFERÈNCIES", + favoritesFilterVotes: "VOTACIONS", + favoritesFilterShopProducts: "ARTICLES DE BOTIGA", + tribeSectionTorrents: "TORRENTS", + tribeCreateTorrent: "Pujar un torrent", + tribeCreateFile: "Pujar fitxer", + settingsWishTitle: "Desig", + settingsWishDesc: "Determina com accedeixes a tot el contingut i la teva experiència a la xarxa Oasis.", + settingsWishWhole: "Multivers", + settingsWishMutuals: "Només suport mutu", + settingsWishOnlyLan: "Només LAN", + settingsWishLocal: "Local", + settingsPmVisibilityTitle: "Missatges privats", + settingsPmVisibilityDesc: "Configura el nivell d'exposició a missatges privats que vols tenir a la xarxa.", + settingsPmVisibilityWhole: "Multivers", + settingsPmVisibilityMutuals: "Només suport mutu", + pmMutualNotice: "L'enviament és una barrera d'UX. La recepció és un filtre d'atenció (el xifratge ja està replicat).", + inhabitantsPendingFollowsTitle: "Sol·licituds de suport pendents", + inhabitantsPendingAccept: "Acceptar", + inhabitantsPendingReject: "Rebutjar", + tribeSectionGovernance: "GOVERN", + tribePadInviteRequired: "No tens accés al pad. Demana una invitació per accedir al contingut.", + tribeChatInviteRequired: "No tens accés al xat. Demana una invitació per accedir al contingut.", + tribeGovernanceDesc: "Govern intern d'aquesta tribu.", + tribeGovernanceNoGov: "Sense govern actiu", + tribeGovernanceNoGovDesc: "Aquesta tribu encara no té govern. Proposa candidatures per iniciar el procés.", + tribeGovCardTitle: "Govern actual", + tribeGovCycleSince: "INICI DEL CICLE", + tribeGovCycleEnd: "FI DEL CICLE", + tribeGovTimeRemaining: "TEMPS RESTANT", + tribeGovPopulation: "POBLACIÓ", + tribeGovMethod: "MÈTODE", + tribeGovVotesReceived: "VOTS REBUTS", + tribeGovLeader: "LÍDER", + tribeGovFilterGovernment: "GOVERN", + tribeGovFilterCandidatures: "CANDIDATURES", + tribeGovFilterLaws: "LLEIS", + tribeGovCandidatureId: "Candidatura", + tribeGovCandidatureMethod: "Mètode", + tribeGovCandidatureProposeBtn: "Publicar la candidatura", + tribeGovRuleTitle: "Títol de la regla", + tribeGovRuleBody: "Cos de la regla", + tribeGovProposals: "PROPOSTES", + tribeGovRevocations: "REVOCACIONS", + tribeGovHistorical: "HISTÒRIC", + tribeGovRules: "REGLES", + tribeGovernanceAlreadyPublished: "Aquesta tribu ja té una candidatura oberta en el cicle actual del parlament global.", + tribeGovernanceProposeInternal: "Proposar una candidatura interna", + tribeGovernanceInternalCandidatures: "Candidatures internes", + tribeGovernanceNoCandidatures: "No hi ha candidatures obertes.", + tribeGovernanceAddRule: "Afegir una regla", + tribeGovernanceNoRules: "Encara no hi ha regles.", + tribeGovernanceNoLeaders: "Encara no hi ha líders elegits.", + tribeGovernanceComingSoon: "Aviat al mòdul de govern d'aquesta tribu.", + tribeGovNoProposals: "Encara no hi ha cap proposta", + tribeGovNoLaws: "Encara no hi ha cap llei", + tribeGovNoRevocations: "Encara no hi ha cap revocació", + tribeGovNoHistorical: "Encara no hi ha cap registre", + logsTitle: "Bitàcola", + logsDescription: "Porta un diari privat de les teves experiències.", + logsViewTitle: "Bitàcola", + logsColumnType: "Tipus", + logsView: "Veure", + logsUpdateButton: "Actualitzar", + logsEditTitle: "Actualitzar el registre", + logsCreateTitle: "Crear una entrada", + logsWriteButton: "Escriure", + logsTextPlaceholder: "Descriu les teves experiències...", + logsModeAIWritten: "Assistent d'IA", + logsModeManual: "Manual", + logsModeAI: "IA", + logsColumnLog: "Bitàcola", + logsColumnDate: "Data", + logsDelete: "Eliminar", + logsEdit: "Actualitzar", + logsFilterAlways: "SEMPRE", + logsFilterYear: "DARRER ANY", + logsFilterMonth: "DARRER MES", + logsFilterWeek: "DARRERA SETMANA", + logsFilterToday: "AVUI", + logsComments: "Comentaris", + logsAuthorEntries: "Entrades", + logsCreate: "Crear una entrada", + logsExport: "Exportar la bitàcola", + logsExportOne: "Exportar el registre", + logsViewDetails: "Veure els detalls", + logsGenerateButton: "Generar el text", + logsSearchText: "Cercar a la Bitàcola...", + logsSearchAnyType: "Qualsevol tipus", + logsSearchButton: "Cercar", + logsEmpty: "Encara no hi ha entrades.", + logsNothingNew: "No hi ha accions noves per registrar.", + modulesLogsLabel: "Bitàcola", + modulesLogsDescription: "Mòdul per portar un diari privat de les teves experiències.", + statsLogsTitle: "Bitàcola", + typeLog: "BITÀCOLA", + + larpTitle: "L.A.R.P.", + larpDescription: "Una capa de joc de rol en viu per a l'experimentació col·laborativa.", + larpNoHousesMatch: "Cap casa no coincideix amb la teva cerca.", + larpSearchPlaceholder: "Cercar a L.A.R.P....", + larpAllHouses: "Cases:", + larpFilterRuling: "GOVERNA", + larpFilterHouses: "CASES", + larpFilterRules: "PMF", + larpRulesTitle: "PMF del L.A.R.P.", + larpRulesEntryText: "Tot habitant comença per defecte a ACADÈMIA. Des d'ACADÈMIA, tria una casa al tauler \"Unir-se a una casa\": això n'obre el test d'entrada. Respon les 10 preguntes i envia. Si superes el llindar (7/10), t'admetran automàticament en aquella casa; si no, seràs rebutjat i romandràs a ACADÈMIA. En tots dos casos el sistema registra el teu OasisID i el cicle de l'intent, i impedeix un altre intent fins que passi el període d'espera de 30 cicles.", + larpRulesGovernanceText: "Durant el seu cicle, la casa governant llueix la insígnia \"Governa\" i és l'única el Mur de la qual es mostra públicament a la pestanya GOVERNA.", + larpRulesWallText: "Cada casa té un Mur on escriuen els seus membres. El Mur d'una casa és privat per a la casa, excepte el de la casa governant i el d'ACADÈMIA, que pot llegir qualsevol.", + larpRulesSignText: "Els habitants el poden activar (a /profile/edit > Sensors) per mostrar la imatge de la seva casa actual com un segell al seu perfil, a la seva targeta d'autor i d'habitant. El segell enllaça a la pàgina de la casa.", + larpPostsTitle: "Mur", + larpPostsEmpty: "Encara no hi ha publicacions.", + larpPostPlaceholder: "Què té a dir aquesta casa?", + larpPostSubmit: "Publicar", + larpCycleLabel: "Cicle:", + bankRulesArchTaxFormula: "Taxa ARCH(usuari) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", + bankRulesArchTaxNote: "L'impost ARCH reflecteix el cost a llarg termini de fer créixer i mantenir l'arxiu de la xarxa: com més antigues siguin les teves dades a la xarxa, més gran serà la teva part del cost de manteniment.", + bankRulesChipFormula: "band = ratio / reducer, on ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). alt ≥ 0.66, mitjà ≥ 0.33, altrament baix.", + bankRulesChipNote: "El xip de cada element s'acoloreix respecte al missatge més gran vist a la xarxa i se suavitza segons el nombre d'habitants actius — una xarxa més gran reparteix la càrrega i empeny més elements cap a la banda verda.", + bankRulesEcoTaxFormula: "Taxa ECO(usuari) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", + bankRulesEcoTaxNote: "L'impost ECO es dedueix de la RBU bruta quan es calcula l'assignació de l'època. Els ECO deduïts no es redistribueixen en aquesta època — es queden al saldo del pub i alimenten el fons de la pròxima època a través dels límits reserveMin / alpha.", + bankRulesKarmaFormula: "karma = log10(1 + actions) + reputation", + bankRulesKarmaNote: "El karma es deriva de les teves accions (publicacions, vots, participació al parlament/tribunals, etc.) amb un factor de decaïment temporal, menys els grams de carboni que genera el teu feed.", + bankRulesNetFormula: "net_UBI = floor + max(0, gross_UBI − floor) − ECO Tax − ARCH Tax", + bankRulesPoolFormula: "pool = min(pubBal − reserveMin, capPerEpoch, alpha · pubBal)", + bankRulesShareFormula: "share = clamp(1 + karma/100, w_min, w_max) / sum(weights)", + bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", + bankTaxesArchTaxFootprintNote: "L'impost ARCH reflecteix el cost de fer créixer i mantenir l'arxiu de la xarxa. Es calcula a partir del temps transcorregut entre el primer bloc que vas publicar i el bloc més nou de la xarxa — com més temps visqui la teva història a l'arxiu, més gran serà la teva part del cost de manteniment.", + bankTaxesArchTaxNoteBold: "reduir directament l'impost ARCH", + bankTaxesArchTaxNoteIntro: "El teu impost ARCH es dedueix de l'excedent que la xarxa genera per sobre de la teva RBU; el valor base de la RBU és un mínim inamovible. L'impost mai no es dedueix de l'import fix que correspon a cada habitant com a RBU. Els fons deduïts alimenten l'algorisme de redistribució de la riquesa i es canalitzen cap a altres habitants a través de les seves reclamacions de RBU. Els habitants més nous i els que es dediquin específicament a tasques d'arxiu, replicació i manteniment probablement tindran alguna tasca dedicada a mantenir sa l'arxiu de la xarxa en nom teu. O poden dedicar-se a ", + bankTaxesArchTaxNoteOutro: " mitjançant acords de poda, deduplicació de dades redundants i infraestructura de manteniment compartida, i el consens de la xarxa pot requerir-ne la contribució contínua.", + bankTaxesUserNoteChips: "El xip d'impost ECO que es mostra al costat de cada element canvia de color (verd / groc / vermell) segons la mida de l'element en relació amb el bloc més gran vist a la xarxa, suavitzat pel nombre d'habitants actius — una xarxa solar-punk més gran reparteix la càrrega i empeny més elements cap a la banda verda.", + bankTaxesUserNoteOtherParams: "Ara com ara l'impost ECO es deriva de la petjada de carboni de cada bloc, però amb el temps pot incorporar altres paràmetres — energia gastada en la replicació, emmagatzematge redundant entre nodes, amplada de banda dels blobs, cost computacional del desxifratge, petjada de mineria de les transaccions associades o qualsevol altra càrrega mesurable que la xarxa acordi valorar.", + gamesHallDate: "Data", + gamesNewRecord: "Nou rècord!", + indexingRefreshNote: "Aquesta pàgina es recarrega cada 10 segons.", + indexingMessage: "Oasis està sincronitzant la xarxa de nodes distribuïda. Si us plau, espera.", + indexingTitle: "Sincronitzant", + padsNoItems: "Encara no hi ha pads.", + padVersionHistory: "Historial", + padAttributionView: "Per autora", + padVersionView: "Veure la versió", + statsChat: "Xats", + statsChatMessage: "Missatges de xat", + statsGameScore: "Jocs", + statsWiki: "Pàgines wiki", + statsPad: "Pads", + statsPadEntry: "Entrades de pad", + typeGameScore: "PUNTUACIONS", + bankTaxesLookupNote: "Introdueix un ID de bloc per cercar-lo al teu feed local.", + bankTaxesUserNoteChipsClick: "Fes clic a qualsevol xip per inspeccionar-ne el bloc al blockexplorer.", + mapUrlPlaceholder: "/maps/MAP_ID", + larpRulesInviteEntryText: "Els membres de les cases poden generar codis d'invitació que donen accés directe a la casa.", + larpRulesOneHouseText: "Només pots pertànyer a una casa alhora. Si no pertanys a cap, ets a ACADÈMIA. Cada pàgina de casa (excepte ACADÈMIA) mostra als seus membres un botó \"Sortir de la casa\" que torna la pertinença a ACADÈMIA. Sortir NO anul·la el període d'espera del test.", + larpRulesTestEntryText: "Al test WILL cada opció pondera una o més cases; en enviar-lo, el sistema suma les puntuacions i t'admet automàticament a la casa amb més punts.", + larpWillTestHint: "Un cop completat, se t'assignarà la casa que més s'ajusti a les teves respostes. Les teves respostes individuals es processen localment i no s'emmagatzemen mai — només es publica al teu feed l'assignació de casa resultant.", + larpWillTestTitle: "Test WILL", + settingsLanDesc: "Anuncia periòdicament aquest node a altres instàncies d'Oasis de la mateixa xarxa local. Desactiva-ho per aturar les difusions UDP.", + settingsLanEnable: "Activar la difusió LAN", + settingsLanTitle: "Difusió LAN", + settingsReplicationTitle: "Replicació", + settingsReplicationDesc: "Configura el nombre de salts que el teu node segueix des del teu propi feed en replicar contingut.", + settingsReplicationHopsLabel: "Salts", + settingsBlobCacheTitle: "Memòria cau de mitjans", + settingsBlobCacheDesc: "El contingut d'altres habitants es desa en aquest dispositiu. Tria quant d'espai pot ocupar.", + settingsBlobCacheUsage: "Ús actual", + settingsBlobCacheFiles: "fitxers", + settingsBlobCacheLimit: "Límit", + settingsBlobCacheUnlimited: "Sense límit", + settingsBlobCacheCleanNow: "Netejar ara", + settingsBlobCacheCleaned: "{n} fitxers eliminats, {size} alliberats.", + aiApproveTagsLabel: "Etiquetes (separades per comes)", + aiApproveTagsPlaceholder: "p. ex. oasis, governança, ecologia", + aiApproveRatingLabel: "Valoració", + aiExchangeLang: "Idioma", + aiExchangeTags: "Etiquetes", + aiExchangeRating: "Valoració", + aiExchangeHelpful: "Útil", + aiExchangeMarkHelpful: "+1 útil", + larpCyclesUntilRuling: "Pròxim cicle de govern", + larpVisitTribe: "Visitar la tribu", + larpStartJourney: "Començar el viatge", + larpGoverning: "Governa:", + larpStartHere: "Comença aquí:", + larpMyHouse: "La meva casa:", + larpMembersCount: "Membres", + larpRolesLabel: "Rols", + larpFunctionLabel: "Funció", + larpMonthLabel: "Cicle de govern", + larpLeaveToAcademia: "Tornar a ACADÈMIA", + larpAcademiaJoinTitle: "Cases L.A.R.P.", + larpInviteRedeem: "Unir-se a la casa", + larpInviteCreate: "Generar un codi d'invitació", + larpInvitePlaceholder: "Codi d'invitació", + larpInviteBannerTitle: "Nou codi d'invitació", + larpInviteBannerHint: "Comparteix aquest codi amb algú que sigui a ACADÈMIA. Caduca d'aquí a 30 cicles o després d'un ús.", + larpTestRankingTitle: "Puntuació per casa", + invitesHousesTitle: "L.A.R.P.", + ecoTaxLabel: "Taxa ECO", + bankTaxes: "Impostos", + bankOverviewYourTaxes: "Impostos totals", + bankTaxesEcoTaxTitle: "Taxa ECO", + bankTaxesArchTaxTitle: "Taxa ARCH", + bankTaxesUserAmount: "La teva taxa ECO (import a retornar)", + bankTaxesArchTaxUserAmount: "La teva taxa ARCH (import a retornar)", + bankTaxesArchTaxFirstBlock: "Edat del teu primer bloc", + bankTaxesArchTaxRate: "Tipus", + bankTaxesTotalBlocks: "Blocs totals", + bankTaxesTotalBytes: "Bytes totals", + bankTaxesTotalCarbon: "Carboni total", + bankTaxesTotalEcoin: "Taxa ECO total (tota la vida)", + bankTaxesEcoTaxLifetime: "Taxa ECO (tota la vida)", + bankTaxesAnnualEcoin: "Taxa ECO (anual)", + bankTaxesMonthlyEcoin: "Taxa ECO (mensual)", + bankTaxesArchTaxLifetime: "Taxa ARCH (tota la vida)", + bankTaxesArchTaxAnnual: "Taxa ARCH (anual)", + bankTaxesArchTaxMonthly: "Taxa ARCH (mensual)", + bankTaxesTotalLifetime: "Total (tota la vida)", + bankTaxesTotalAnnual: "Total (anual)", + bankTaxesTotalMonthly: "Total (mensual)", + bankTaxesRate: "Tipus", + bankTaxesSpan: "Període de mostreig", + bankTaxesLookupTitle: "Inspeccionar un bloc", + bankTaxesLookupPlaceholder: "ID del bloc (p. ex. %abc...=.sha256)", + bankTaxesLookupButton: "Inspeccionar", + bankTaxesLookupNotFound: "No s'ha trobat cap bloc amb aquest ID al teu feed local.", + bankTaxesLookupAuthor: "Autor", + bankTaxesLookupType: "Tipus", + bankTaxesLookupSize: "Mida", + bankTaxesLookupCarbon: "Petjada de carboni", + bankTaxesLookupEcoin: "Taxa ECO", + bankExchangeEcoTaxAnnual: "Impostos d'ECOin (anual)", + bankExchangeEcoTaxMonthly: "Impostos d'ECOin (mensual)", + bankTaxesTypesLabel: "Selecciona quins impostos vols pagar", + bankTaxesTypesApply: "Establir els meus impostos", + bankRulesPoolTitle: "Fons mensual", + bankRulesShareTitle: "Repartiment per usuari", + bankRulesKarmaTitle: "Karma (puntuació d'implicació)", + bankRulesEcoTaxTitle: "Taxa ECO", + bankRulesArchTaxTitle: "Taxa ARCH", + bankRulesChipTitle: "Banda de color del xip de la taxa ECO", + bankRulesEpochKind: "Granularitat de l'època", + bankRulesAlpha: "Alpha (quota màx. del saldo del pub per època)", + bankRulesReserveMin: "Reserva mínima (es manté al saldo del pub)", + bankRulesCapPerEpoch: "Límit per època (absolut)", + bankRulesGraceDays: "Període de gràcia de la reclamació", + bankRulesWMin: "Pes mínim (w_min)", + bankRulesWMax: "Pes màxim (w_max)", + bankRulesFloor: "Mínim per usuari (brut)", + bankRulesCapUser: "Límit per usuari i època", + bankRulesEcoTaxRate: "Tipus", + bankRulesArchTaxRate: "Tipus", + bankRulesCarbonFactor: "Factor de carboni", + statsTombstoneEmpty: "Encara no hi ha esborrats (tombstones) a la xarxa.", + blockchainBlockNotAvailable: "Aquest bloc no és al teu feed local. Pot pertànyer a un node que encara no repliques.", + blockchainBackToBlockexplorer: "Tornar a Blockexplorer", + audioFilterBcs: "BCS", + audioTranscodeButton: "TRANSCODIFICAR", + audioTranscodeDetailTitle: "Transcodificar", + audioTranscodeDetailDescription: "Descodifica la càrrega útil incrustada i el mapa de composició original de la blockchain.", + audioTranscodeStegoTimestamp: "Generat el", + audioTranscodeStegoMessage: "TEXT", + audioTranscodeStegoEmpty: "(cap)", + audioTranscodeStegoUnknown: "—", + audioTranscodeStegoNotFound: "No s'ha pogut descodificar cap càrrega esteganogràfica en aquest àudio.", + audioTranscodeCompositionEmpty: "Aquest àudio no inclou cap composició de blockchain desada.", + audioBackToBcs: "Tornar a BCS", + melodyCompositionTitle: "Mapa de composició de la blockchain", + melodyFilterMine: "MEVES", + melodyAllEmpty: "Encara no hi ha composicions BCS d'altres habitants.", + melodyNoteUnavailable: "el bloc no és al teu feed local", + melodyUploadBcsTitle: "Publicar", + melodyUploadBcsButton: "Publicar", + melodyUploadStegoLabel: "Missatge ocult (esteganografia)", + melodyUploadStegoMaxLabel: "màx. 280 caràcters", + melodyUploadStegoPlaceholder: "Opcional.", + melodyUploadBcsNameNote: "L'àudio es publicarà amb el nom generat automàticament", + larpLastAttemptTitle: "El teu darrer intent", + larpLastAttemptHouse: "Casa", + larpLastAttemptWhen: "Quan", + larpLastAttemptCooldown: "Pròxim intent d'aquí a", + larpTestTitle: "Test d'entrada", + larpTestIntro: "Respon les 10 preguntes. Necessites almenys 7 respostes correctes per ser admès. Pots intentar un test cada 30 cicles sigui quin sigui el resultat.", + larpTestSubmit: "Unir-se a la casa", + larpTestCooldownActive: "Pròxim test disponible d'aquí a", + larpTestCooldownDays: "cicles", + larpTestResultTitle: "Resultat del test", + larpTestScore: "Puntuació", + larpTestNextAttempt: "Pots intentar un altre test d'aquí a 30 cicles.", + larpGoToHouse: "Anar a la teva casa", + larpBackToAcademia: "Tornar a ACADÈMIA", + larpBadgeYou: "Tu", + larpBadgeRuling: "Governa", + modulesLarpLabel: "L.A.R.P.", + modulesLarpDescription: "Mòdul per a la capa de joc de rol en viu.", + larpProfileQ1: "Quan t'enfrontes a un problema complex, què fas primer?", + larpProfileQ1O1: "Parlar amb altres persones per buscar consens", + larpProfileQ1O2: "Construir un prototip per provar-lo", + larpProfileQ1O3: "Investigar la bibliografia", + larpProfileQ1O4: "Sabotejar el sistema que el provoca", + larpProfileQ2: "Què dona sentit a la teva feina diària?", + larpProfileQ2O1: "Defensar els habitants que estimo", + larpProfileQ2O2: "Crear alguna cosa tangible", + larpProfileQ2O3: "Guarir o tenir cura de la vida", + larpProfileQ2O4: "Treballar amb paraules i idees", + larpProfileQ2O5: "Fer riure els altres", + larpProfileQ3: "Quan sorgeix un conflicte al teu grup, tu…", + larpProfileQ3O1: "Lideres el diàleg", + larpProfileQ3O2: "Prens partit i et mantens ferm", + larpProfileQ3O3: "Deixes anar una broma per rebaixar la tensió", + larpProfileQ3O4: "Qüestiones si el conflicte és real", + larpProfileQ3O5: "Busques les causes ecològiques d'arrel", + larpProfileQ4: "El teu projecte preferit a llarg termini seria…", + larpProfileQ4O1: "Construir una ciutat", + larpProfileQ4O2: "Restaurar un bosc", + larpProfileQ4O3: "Redactar una constitució", + larpProfileQ4O4: "Organitzar un festival", + larpProfileQ4O5: "Muntar una xarxa de defensa", + larpProfileQ4O6: "Dissenyar una màquina nova", + larpProfileQ4O7: "Curar un arxiu", + larpProfileQ4O8: "Desmuntar un ordre injust", + larpProfileQ5: "Què fa una bona líder?", + larpProfileQ5O1: "Algú que escolta i fa de mediadora", + larpProfileQ5O2: "Algú capaç de lluitar i protegir", + larpProfileQ5O3: "Algú que coneix la història", + larpProfileQ5O4: "Algú que et fa somriure", + larpProfileQ6: "La teva relació amb les regles:", + larpProfileQ6O1: "Les regles sorgeixen del diàleg i la llei", + larpProfileQ6O2: "Les regles s'han de complir estrictament", + larpProfileQ6O3: "Les regles s'han de trencar sovint", + larpProfileQ6O4: "Les regles han de servir la vida", + larpProfileQ6O5: "Les regles aixequen infraestructures sòlides", + larpProfileQ7: "Com gestiones la informació?", + larpProfileQ7O1: "La curo i la preservo", + larpProfileQ7O2: "La comparteixo a través d'històries", + larpProfileQ7O3: "En qüestiono els orígens", + larpProfileQ7O4: "N'extrec el que és útil", + larpProfileQ7O5: "La faig servir per guarir", + larpProfileQ8: "La teva idea d'èxit és…", + larpProfileQ8O1: "Una màquina que funciona", + larpProfileQ8O2: "Una comunitat en pau", + larpProfileQ8O3: "Un festival animat", + larpProfileQ8O4: "Una família segura", + larpProfileQ8O5: "Un arxiu intacte", + larpProfileQ8O6: "Un dogma esquerdat", + larpProfileQ8O7: "Una collita pròspera", + larpProfileQ8O8: "Un edifici acabat", + larpProfileQ9: "En llevar-te, el que vols és…", + larpProfileQ9O1: "Entrenar el cos", + larpProfileQ9O2: "Llegir o escriure", + larpProfileQ9O3: "Conrear o cuinar", + larpProfileQ9O4: "Remenar alguna cosa", + larpProfileQ9O5: "Qüestionar l'autoritat", + larpProfileQ9O6: "Planificar un projecte", + larpProfileQ9O7: "Parlar amb les teves amigues", + larpProfileQ9O8: "Fer art", + larpProfileQ10: "La teva debilitat podria ser:", + larpProfileQ10O1: "Parlar massa", + larpProfileQ10O2: "Ser massa pragmàtica", + larpProfileQ10O3: "Ser massa idealista", + larpProfileQ10O4: "Ser massa disruptiva", + larpProfileQ10O5: "Ser massa rígida", + larpProfileQ10O6: "Ser massa despreocupada", + larpProfileQ10O7: "Ser massa prudent", + larpProfileQ10O8: "Ser massa ambiciosa", + uxModeTitle: "UX", + uxModeDescription: "Selecciona quin mode de navegació UX vols per a la teva interfície.", + uxModeMenus: "Blocs", + uxModeChats: "Converses", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Etiquetes en tendència", + feedActiveTitle: "Habitants actius", + cnBrandSub: "Lliure · P2P · Federada", + cnSyncedPeers: "Nodes sincronitzats", + cnLicense: "Llicència", + cnNotAccessible: "El contingut no és accessible en aquest moment.", + cnUntitled: "Sense títol", + cnSearchPlaceholder: "Cercar…", + cnHubTitle: "HUB Clearnet", + cnHubDescription: "Contingut públic compartit pels habitants d'aquesta xarxa Oasis.", + cnHubEmpty: "Encara no s'ha compartit contingut públic a la Clearnet.", + cnPublicContent: "Contingut", + cnTribesContent: "Tribus", + cnNetworkContent: "Xarxa", + cnCategoryEmpty: "No hi ha contingut en aquesta categoria.", + cnInhabitantEmpty: "Aquest habitant encara no ha publicat contingut a la Clearnet.", + cnPrevPage: "← Anterior", + cnNextPage: "Següent →", + cnPageOf: "Pàgina {page} de {pages}", + cnMoreInfo: "Més informació →", + cnOpenCourse: "Curs obert", + cnAuction: "SUBHASTA", + cnExchange: "INTERCANVI", + cnRssDescription: "publicats pels habitants d'aquest HUB", + cnKindAudio: "Àudio", + cnKindBlog: "Blog", + cnKindBookmark: "Marcador", + cnKindDocument: "Document", + cnKindEvent: "Esdeveniment", + cnKindFeed: "Feed", + cnKindImage: "Imatge", + cnKindJob: "Feina", + cnKindMarket: "Mercat", + cnKindPodcast: "Podcast", + cnKindProject: "Projecte", + cnKindCourse: "Curs", + cnKindShop: "Botiga", + cnKindTorrent: "Torrent", + cnKindFile: "Fitxer", + cnKindVideo: "Vídeo", + cnKindWiki: "Wiki", + cnKindRoom: "Sala", + cnKindMap: "Mapa", + uxModeAINav: "IA", + + } +}; diff --git a/src/client/assets/translations/oasis_de.js b/src/client/assets/translations/oasis_de.js index dfa3426..075db92 100644 --- a/src/client/assets/translations/oasis_de.js +++ b/src/client/assets/translations/oasis_de.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "Einkäufe", shopMyOrdersDescription: "Deine Bestellungen.", shopMyOrdersEmpty: "Du hast noch keine Käufe.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PN Verkäufer", shopOrderSeller: "Verkäufer", shopOrderInvoice: "Rechnung", shopOrderInvoiceConcept: "Rechnung", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Annehmen", shopOrderReject: "Ablehnen", shopOrderMarkShipped: "Als versandt markieren", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PN Käufer", shopOrderContractConcept: "Shop-Bestellung", shopOrdersPending: "Ausstehende Bestellungen", all: "Alle", @@ -28,7 +28,7 @@ module.exports = { recent: "Aktuell", anonymous: "Anonym", unnamed: "Anonym", - authorLabel: "AUTOR", + authorLabel: "Autor", delete: "Löschen", save: "Speichern", vote: "Abstimmen", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Inhaltswarnung", invalidPost: "Ungültiger Inhalt", invalidPostHint: "Diese Nachricht hat ungültigen/leeren Text.", - spreadBy: "Von", spreadChron: "Verbreitung", spreaded: "Verbreitet", spreadContentUnavailable: "Inhalt noch nicht verfügbar (Replikation ausstehend)", filteredByTag: "Nach Tag gefiltert", filteredByTagTitle: "Nach Tag gefiltert", feedPublishedSuccess: "Beitrag erfolgreich veröffentlicht!", + feedMastodonFailed: "In Oasis veröffentlicht, konnte aber nicht an Mastodon gesendet werden.", tribeFeedSent: "Nachricht erfolgreich gesendet!", tribeContentEncrypted: "Verschlüsselter Inhalt", tribeTorrentsEmpty: "Noch keine Torrents.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Letzter Besuch", profileClearnetBookmarksLabel: "Lesezeichen", invitesPort: "Port", - currentlyUnrecheable: "FEHLER!", - peerHostValidation: "Gültige IPv4 (z. B. 192.168.1.100) oder Hostname (z. B. pub.example.com)", + peerHostValidation: "Gültige IPv4 (z. B. 192.168.1.100) oder Hostname (z. B. pub.solarnethub.com)", peerPortValidation: "Port 1-65535", peerKeyValidation: "SSB ed25519 öffentlicher Schlüssel (@<44 Zeichen base64>=.ed25519)", inboxJobUnsubscribedTitle: "Abmeldung von deinem Stellenangebot", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Aufgabenbeschreibung", exportPasswordLabel: "Export-Passwort", importPasswordPlaceholder: "Passwort eingeben", - searchCasesPlaceholder: "Fälle suchen…", + searchCasesPlaceholder: "In Gerichte suchen...", shopBuyDeliveryAddressPlaceholder: "Lieferadresse", shopBuyNotesPlaceholder: "Notizen", bankTaxesUserNoteBold: "die ECO-Steuer direkt senken", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Fall unterstützen", courtsSettlementAcceptBtn: "Vergleich annehmen", courtsSupportCount: "Unterstützer", - courtsVerdictVoteTitle: "Über das Urteil abstimmen", - courtsVerdictVoteLabel: "Urteilsabstimmung", - courtsVerdictVoteAccept: "Annehmen", - courtsVerdictVoteReject: "Ablehnen", - courtsVerdictVoteSubmit: "Stimme abgeben", reportsSetStatus: "Status setzen", reportsStatusClosed: "GESCHLOSSEN", statsAvgPerInhabitant: "Durchschnitt pro Bewohner", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Avatar", inhabitants: "Bewohner", - peersReplicatedFeeds: "Replizierte Feeds", + peersReplicatedFeeds: "Repliziert", + peersTorColumn: "TOR", + peersTorYes: "Über Tor erreichbar", + peersTorNo: "Nicht über Tor erreichbar", + peersOasisId: "Oasis ID", + peersOasisVersion: "Version", graphos: "Graphos", graphosDescription: "Interaktive Karte des Netzwerks um dich herum.", + graphosFederation: "Föderation", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Nicht erreichbar", + graphosFedLinks: "Gegenseitige Verbindungen", + graphosFedIsolated: "Isoliert", + graphosFedNoData: "Keine Daten", + graphosFedMutual: "Gegenseitig", + graphosFedOneWay: "Einseitig", + graphosFedTransport: "Netz", + graphosFedWith: "Föderiert mit", + graphosZoom: "Vergrößern", + graphosFedInhabitants: "Bewohner", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "Dein Knoten kennt noch keinen PUB.", graphosViewingGraphOf: "Ansicht", graphosBackToMine: "← Mein Netzwerk", graphosShowing: "Zeige", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "Du hast noch keine @Erwähnungen erhalten.", - mentionsSearchPlaceholder: "Erwähnungen suchen...", + mentionsSearchPlaceholder: "In Erwähnungen suchen...", privateReminders: "ERINNERUNGEN", + inboxFilterPms: "PNs", + inboxFilterNotifications: "Benachrichtigungen", + inboxMarkRead: "Als gelesen markieren", + inboxMarkUnread: "Als ungelesen markieren", + inboxUnreadChip: "Ungelesen", + inboxMarkAllRead: "Alle als gelesen markieren", + inboxDeleteShown: "Alle löschen", + inboxArchive: "Archivieren", + inboxUnarchive: "Dearchivieren", + inboxFilterArchived: "Archiviert", + inboxEarlierMessages: "frühere Nachrichten", + inboxSortThread: "Nach Thread", + inboxSortRecent: "Neueste zuerst", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Inbox-Benachrichtigungen", + settingsInboxBotsDesc: "Wähle, welche Bots dich in deiner Inbox benachrichtigen dürfen. Abgewählte Bots werden ausgeblendet und ihre Hinweise nicht gezählt.", inboxFiltersLabel: "Filter:", inboxToggleToMutuals: "Auf Gegenseitige umschalten", inboxToggleToWhole: "Auf Alle umschalten", @@ -212,6 +242,7 @@ module.exports = { pmReply: "Antworten", pmReplies: "Antworten", inReplyTo: "ALS ANTWORT AUF", + forumInThread: "IM THREAD", pmPreview: "Vorschau", pmPreviewTitle: "Nachrichtenvorschau", peers: "Verbindungen", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "Von", searchToLabel: "Bis", searchQueryLabel: "Suche", - searchPerPageLabel: "Ergebnisse pro Seite", + searchPerPageLabel: "Ergebnisse", settings: "Einstellungen", + settingsDescription: "Richte deine Netzwerkumgebung ein.", continueReading: "Weiterlesen", moreComments: "weitere Kommentare", readThread: "den Rest des Threads lesen", @@ -239,7 +271,6 @@ module.exports = { profileClearnetHelp: 'Module, die von außerhalb von Oasis aufgerufen werden können.', profileHubAll: 'Alle', footerLicenseLabel: 'Lizenz', - profileSwitchToOasis: 'Zurück zu Oasis', profileVisibilityFediverse: "Multiversum", contributorsTitle: "Mitwirkende", colorLabel: 'Farbe wählen', @@ -249,6 +280,7 @@ module.exports = { modulesViewTitle: "Module", modulesViewDescription: "Konfiguriere deine Umgebung durch Aktivieren oder Deaktivieren von Modulen.", inbox: "Posteingang", + inboxSearchPlaceholder: "In Posteingang suchen...", multiverse: "Multiversum", fediverse: "Multiversum", fediverseDescription: "Verwalte deine anderen Multiversum-Konten, einschließlich Senden und Empfangen von Inhalten.", @@ -257,9 +289,17 @@ module.exports = { fediverseSettingsTitle: "Multiversum", fediverseInstanceLabel: "Adresse", fediverseTokenLabel: "Zugriffstoken", - fediverseTokenHelp: "Lege dein Zugriffstoken fest. Es wird verwendet, um dein Mastodon-Konto aus Oasis heraus zu verwalten.", fediverseConnect: "Verbinden", fediverseConnectedAs: "Verbunden als", + nextcloudServerLabel: "Server", + nextcloudUserLabel: "Benutzer", + nextcloudFolderLabel: "Ordner", + nextcloudUploadButton: "Hochladen", + nextcloudFileLabel: "Datei auswählen", + fileExternalLink: "Externer Link", + nextcloudErrConnect: "Nextcloud war mit diesen Angaben nicht erreichbar.", + nextcloudErrUpload: "Der Upload zu Nextcloud ist fehlgeschlagen.", + nextcloudErrShare: "Nextcloud hat keinen öffentlichen Link für die Datei geliefert.", fediverseDisconnect: "Trennen", fediverseEmptyLink: "deinen Einstellungen", fediverseComposePlaceholder: "Was denkst du gerade?", @@ -288,6 +328,76 @@ module.exports = { fediverseErrPost: "Konnte nicht veröffentlichen.", fediverseErrMedia: "Datei konnte nicht hochgeladen werden.", fediverseErrEmpty: "Schreibe etwas oder hänge eine Datei an.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API-Hash", + telegramPhoneLabel: "Telefonnummer", + telegramSendCode: "Code senden", + telegramCodeHelp: "Gib den Code ein, den Telegram dir gerade geschickt hat (App oder SMS).", + telegramCodeLabel: "Code", + telegramPasswordHelp: "Dieses Konto nutzt die Zwei-Schritt-Bestätigung. Gib dein Passwort ein.", + telegramPasswordLabel: "Passwort", + telegramVerify: "Bestätigen", + telegramCancel: "Abbrechen", + telegramChats: "Chats", + telegramUnread: "Ungelesen", + telegramOpen: "Öffnen", + telegramSend: "Senden", + telegramYou: "Du", + telegramNoChats: "Noch keine Chats.", + telegramNoMessages: "Noch keine Nachrichten.", + telegramComposePlaceholder: "Nachricht schreiben…", + telegramChannel: "Kanal", + telegramGroup: "Gruppe", + telegramPrivate: "Privat", + telegramErrConnect: "Telegram konnte nicht erreicht werden.", + telegramErrTimeout: "Telegram hat zu lange nicht geantwortet.", + telegramErrAuth: "Telegram-Sitzung ungültig oder abgelaufen. Erneut verbinden.", + telegramErrFetch: "Chats konnten nicht geladen werden.", + telegramErrSend: "Nachricht konnte nicht gesendet werden.", + telegramErrMissing: "API-ID, API-Hash und Telefonnummer sind erforderlich.", + telegramErrCode: "Ungültiger Code. Erneut versuchen.", + telegramErrCodeExpired: "Der Code ist abgelaufen. Neu beginnen.", + telegramErrPassword: "Falsches Passwort.", + telegramErrPhone: "Ungültige Telefonnummer.", + telegramErrApi: "Ungültige API-ID oder API-Hash.", + telegramErrFlood: "Zu viele Versuche. Warte kurz und versuche es erneut.", + telegramErrNoLogin: "Keine Anmeldung aktiv. Neu beginnen.", + peertubeUsernameLabel: "Benutzername", + peertubePasswordLabel: "Passwort", + peertubeSubscriptions: "Abonnements", + peertubeMyVideos: "Meine Videos", + peertubeVideos: "Videos", + peertubeFollowers: "Follower", + peertubeViews: "Aufrufe", + peertubeLikes: "Likes", + peertubeQuota: "Kontingent", + peertubeUpload: "Video hochladen", + peertubeUploadHelp: "Wähle eine Videodatei. Sie wird in deinen Kanal hochgeladen und danach von PeerTube verarbeitet.", + peertubeTitleLabel: "Titel", + peertubeDescriptionLabel: "Beschreibung", + peertubePrivacyLabel: "Sichtbarkeit", + peertubePrivacyPublic: "Öffentlich", + peertubePrivacyUnlisted: "Nicht gelistet", + peertubePrivacyPrivate: "Privat", + peertubeNoVideos: "Noch keine Videos.", + peertubeComments: "Kommentare", + peertubeComment: "Kommentieren", + peertubeCommentPlaceholder: "Kommentar schreiben…", + peertubeNoComments: "Noch keine Kommentare.", + peertubeOpen: "Auf PeerTube öffnen", + peertubeWatch: "Ansehen", + peertubeChannel: "Kanal", + peertubeNoStream: "Dieses Video kann hier noch nicht abgespielt werden. Öffne es auf PeerTube.", + peertubeUploaded: "Video hochgeladen. PeerTube verarbeitet es.", + peertubeErrConnect: "PeerTube ist nicht erreichbar.", + peertubeErrAuth: "Falscher Benutzername oder falsches Passwort, oder die Sitzung ist abgelaufen. Verbinde dich erneut.", + peertubeErrInstance: "Ungültige Instanz-URL.", + peertubeErrMissing: "Instanz, Benutzername und Passwort sind erforderlich.", + peertubeErrFetch: "Videos konnten nicht geladen werden.", + peertubeErrUpload: "Das Video konnte nicht hochgeladen werden.", + peertubeErrTitle: "Titel erforderlich.", + peertubeErrComment: "Der Kommentar konnte nicht veröffentlicht werden.", + peertubeErrNoFile: "Wähle eine Videodatei.", bookmarksLabel: "Lesezeichen", videosLabel: "Videos", torrentsLabel: "Torrents", @@ -310,7 +420,6 @@ module.exports = { comment: "Kommentar", subtopic: "Unterthema", json: "JSON", - createdBy: "von", unfollow: "Nicht mehr unterstützen", follow: "Unterstützen", block: "Blockieren", @@ -326,12 +435,15 @@ module.exports = { relationshipNone: "Du unterstützt nicht", relationshipBlockingPost: "Blockierter Beitrag", viewLikes: "Verbreitungen ansehen", + viewSupporters: "Unterstützer ansehen", spreadedDescription: "Liste der vom Bewohner verbreiteten Beiträge.", preview: "Vorschau", publish: "Schreiben", contentWarningPlaceholder: "Betreff zum Beitrag hinzufügen (optional)", - publishWarningPlaceholder: "Text auf 7000 Zeichen begrenzt.", + publishWarningPlaceholder: "Schreibe den Text deines Beitrags.", publishTooLong: "Dein Beitrag ist zu lang. Bitte kürze ihn.", + keepTextRetry: "Erneut senden", + dateInPastError: "Das Datum darf nicht in der Vergangenheit liegen", publishCustomDescription: [ "ACHTUNG: Aufgrund der Blockchain-Technologie kann ein veröffentlichter Beitrag nicht bearbeitet oder gelöscht werden.", ], @@ -361,9 +473,8 @@ module.exports = { mentionsMatching: "Passende Erwähnungen", mentionsName: "Name", mentionsRelationship: "Beziehung", - updateit: "UPDATES HOLEN!", updateBannerText: "Eine neue Version von Oasis ist verfügbar.", - updateBannerAction: "Jetzt aktualisieren →", + updateBannerAction: "Jetzt aktualisieren!", info: "Info", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -371,16 +482,14 @@ module.exports = { timeAgo: "her", sendTime: "etwa ", theme: "Design", - legacy: "Schlüssel", - legacyTitle: "Schlüssel", - legacyDescription: "Verwalte deinen geheimen Schlüssel (privater Schlüssel) schnell und sicher.", - legacyExportButton: "Exportieren", - legacyImportButton: "Importieren", + backupKeysExportButton: "Exportieren", + backupKeysImportButton: "Importieren", ssbLogStream: "Blockchain", ssbLogStreamDescription: "Konfiguriere das Nachrichtenlimit für Blockchain-Streams.", + logWindowChip: "Letzte {n} Blockchain-Streams", saveSettings: "Einstellungen speichern", exportDescription: "Passwort festlegen (mind. 32 Zeichen), um deinen Schlüssel zu verschlüsseln", - exportDataTitle: "Sicherung", + exportDataTitle: "Sofort-Sicherung", exportDataDescription: "Lade deine Daten herunter (geheimer Schlüssel ausgeschlossen!)", exportDataButton: "Datenbank herunterladen", importDescription: "Importiere deinen verschlüsselten geheimen Schlüssel, um deinen Avatar zu aktivieren", @@ -389,38 +498,33 @@ module.exports = { fileInfo: "Dein verschlüsselter Schlüssel wird auf dein Gerät heruntergeladen (Name: oasis.enc)", developer: "Entwicklung", devTitle: "Entwicklung", - welcomeBannerText: "Willkommen bei OASIS. Folge diesen kurzen Schritten, um deinen Avatar einzurichten.", + welcomeBannerText: "Dein Avatar ist noch nicht eingerichtet.", aiSuggestionBanner: "Vorschlag:", aiSuggestionsEnable: "KI-Vorschläge", - welcomeBannerAction: "Los geht es!", + welcomeBannerAction: "Loslegen", welcomeTitle: "Willkommen", + welcomeStepIdentity: "Identität", + welcomeStepType: "Typ", + welcomeStepUse: "Nutzung", + welcomeStepLook: "Aussehen", + welcomeStepCommunity: "Gemeinschaft", + welcomeStepIntro: "Willkommen", + welcomeHintIdentity: "Dein Name und dein Bild.", + welcomeHintType: "Wie sich Oasis für dich öffnet.", + welcomeHintUse: "Welche Module aktiv bleiben.", + welcomeHintLook: "Das Design, das Oasis trägt.", + welcomeHintCommunity: "Tritt einem PUB bei, um andere Bewohner kennenzulernen.", + welcomeHintIntro: "Sende einen ersten Gruß an die Gemeinschaft.", + welcomeStepThemeAction: "Dieses Design verwenden", + welcomeJoinPubs: "PUBs beitreten", + welcomeNoFederation: "Ohne Föderation", welcomeStepGreetingAction: "Senden", - welcomeJoinPubButton: "Pub beitreten", - welcomeStepLarpAction: "\"Der Akademie\" beitreten", - welcomeStepLarpText: "Mach mit bei unserem Live-Rollenspiel.", - welcomeStepLarpTitle: "L.A.R.P. beitreten", - welcomeStepGreetingTitle: "Sende ein \"Hallo Welt!\"", - welcomeStepGreetingText: "Sende eine Nachricht, damit andere Bewohner dich entdecken können.", - welcomeJoinPub: "Beitreten:", welcomeDescription: "Ein paar Dinge, die sich vor dem Start lohnen.", - welcomeProgress: "Erledigt", - welcomeStepDone: "fertig", - welcomeStepPending: "offen", welcomeSkip: "Jetzt nicht", - welcomeStepLanguageTitle: "Wähle deine Sprache", - welcomeStepLanguageText: "Oasis spricht mehrere Sprachen. Du kannst sie jederzeit ändern.", - welcomeStepProfileTitle: "Bearbeite dein Profil", - welcomeStepProfileText: "Wähle einen Namen, füge eine Beschreibung hinzu und setze ein Bild, damit andere Bewohner dich erkennen.", welcomeStepProfileAction: "Profil speichern", - welcomeStepFederationTitle: "Tritt dem Hauptnetzwerk bei", - welcomeStepFederationText: "Verbinde dich mit unserem Pub, um andere Bewohner zu treffen und zu replizieren.", - welcomeStepFederationAction: "Zu den Einladungen", - welcomeStepBackupTitle: "Sichere deine ID", - welcomeStepBackupText: "Deine Identität ist eine Schlüsseldatei auf diesem Gerät.", - welcomeStepBackupWarning: "WENN SIE VERLOREN GEHT, KANN SIE NIEMAND WIEDERHERSTELLEN.", - welcomeStepBackupAction: "Sicherung!", + welcomeBootstrapRunning: "Snapshot des Pubs wird geladen. Danach wird dem Pub automatisch beigetreten; du kannst Oasis währenddessen weiter nutzen.", welcomeCompleteTitle: "Alles bereit.", - welcomeCompleteText: "Dein Knoten ist bereit. Von hier an wächst das Netzwerk mit den Menschen, denen du folgst.", + welcomeCompleteText: "Dein Knoten ist bereit. Von hier an wächst das Netzwerk mit den Bewohnern, die du unterstützt.", welcomeFindPeople: "Bewohner finden", devFilterFiles: "DATEIEN", devFilterSearch: "SUCHEN", @@ -430,7 +534,7 @@ module.exports = { devFilterTests: "TESTS", devVersion: "Version", devNodeVersion: "Node", - devStatsTests: "Test-Suites", + devStatsTests: "Tests", devParentFolder: "Übergeordneter Ordner", devOpenFolder: "öffnen", devDescription: "Erkunde den Quellcode, der auf diesem Knoten läuft.", @@ -472,6 +576,27 @@ module.exports = { languageDescription: "Wenn du eine andere Sprache verwenden möchtest, wähle sie hier aus.", setLanguage: "Sprache festlegen", peerConnectionsIntro: "Verwalte alle Verbindungen mit anderen Peers.", + peersConnectFailed: "Verbindung fehlgeschlagen: {reason}.", + peerStateLabel: "Status", + peerAddressLabel: "Adresse", + peerLastChangeLabel: "Letzte Änderung", + peerStateConnected: "Verbunden", + peerStateConnecting: "Verbindet", + peerStateStaged: "Verfügbar", + peerErrSilent: "Keine Aktivität", + peerErrTimeout: "Antwortet nicht", + peerErrRefused: "Lehnt die Verbindung ab", + peerErrNotFound: "Unbekannte Adresse", + peerErrUnreachable: "Netz nicht erreichbar", + peerErrKeys: "Verwendet andere Schlüssel", + peerErrIdentity: "Akzeptiert deine Verbindung nicht", + peerErrTor: "Nur TOR", + peerErrDropped: "Verbindung abgebrochen", + peerErrPaused: "Netzwerk pausiert", + peerErrOther: "Verbindungsfehler", + peerOutdated: "Veraltet", + peerVersionUnannounced: "Gibt seine Version nicht bekannt: ein altes Oasis", + peersTorMissing: "Dieser PUB erlaubt nur Verbindungen über TOR.", peerConnectionsTitle: "Verbindungen", online: "Online", offline: "Offline", @@ -490,22 +615,25 @@ module.exports = { noConnections: "Keine Peers verbunden.", noDiscovered: "Keine Peers entdeckt.", noUnknownPeers: "Keine unbekannten Peers im Freundes-Graphen.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Noch keine Peers registriert.", + peerConnect: "Verbinden", + peerDisconnect: "Trennen", peerRefresh: "Aktualisieren", peerPruneIdle: "Inaktive entfernen", + peersPause: "Pausieren", + peersResume: "Fortsetzen", + peersPausedNote: "Pausiert: Oasis verbindet sich mit niemandem und niemand kann sich damit verbinden.", + peersPauseHint: "Stoppt alle Verbindungen: Oasis verbindet sich mit keinem anderen Knoten und nimmt keine an, bis du fortsetzt.", peerExport: "Exportieren", peerImport: "Importieren", peerImportTitle: "Peer-Liste importieren", peerImportPlaceholder: "Eine Multiserver-Adresse pro Zeile einfügen oder eine .txt-Datei hochladen…", connectionActionIntro: "", - startNetworking: "Netzwerk starten", - stopNetworking: "Netzwerk stoppen", - restartNetworking: "Netzwerk neu starten", - sync: "Netzwerk synchronisieren", indexes: "Indizes", + indexesRebuilt: "Indizes neu aufgebaut", + indexesRebuildFailed: "Indizes konnten nicht neu aufgebaut werden", + indexesFiles: "Indexdateien", indexesDescription: "Das Neuerstellen deiner Indizes ist sicher und kann einige Fehlerarten beheben.", homePageTitle: "Startseite", homePageDescription: "Wähle, welches Modul deine Startseite sein soll.", @@ -514,44 +642,33 @@ module.exports = { invitesDescription: "Verwalte und verwende Einladungscodes in deinem Netzwerk.", invitesPubsHint: "Füge einen Pub-Einladungscode ein, um dich mit ihm zu föderieren und seine Bewohner zu replizieren.", invitesFederationsHint: "Pubs, mit denen du föderiert bist: aktualisieren, unerreichbare entfernen oder die Liste exportieren.", - invitesHousesHint: "Gib einen Haus-Code ein, um einem LARP-Haus beizutreten und an seiner Wirtschaft teilzunehmen.", - invitesTribesHint: "Gib einen Stammes-Code ein, um Mitglied zu werden und die privaten Inhalte zu sehen.", - invitesChatsHint: "Gib einen Chat-Code ein, um dem Gespräch und seinen Threads beizutreten.", - invitesPadsHint: "Gib einen Pad-Code ein, um gemeinsam an einem Dokument zu schreiben.", - invitesCalendarsHint: "Gib einen Kalender-Code ein, um Termine und Erinnerungen zu teilen.", - invitesEventsHint: "Gib einen Event-Code ein, um an einer privaten Veranstaltung teilzunehmen.", - invitesForumsHint: "Gib einen Forum-Code ein, um in einem privaten Forum zu lesen und zu antworten.", - invitesMapsHint: "Gib einen Karten-Code ein, um Orte auf einer gemeinsamen Karte zu sehen und hinzuzufügen.", - invitesShopsHint: "Gib einen Shop-Code ein, um einem Shop und seinem Katalog beizutreten.", - invitesInhabitantsHint: "Gib einen Bewohner-Code ein, um euch gegenseitig zu folgen und Inhalte auszutauschen.", invitesTribesTitle: "Stämme", - invitesTribeInviteCodePlaceholder: "Stamm-Einladungscode eingeben", - invitesTribeJoinButton: "Stamm beitreten", invitesChatsTitle: "Chats", - invitesChatInviteCodePlaceholder: "Chat-Einladungscode eingeben", - invitesChatJoinButton: "Chat beitreten", invitesPadsTitle: "Pads", - invitesPadInviteCodePlaceholder: "Pad-Einladungscode eingeben", - invitesPadJoinButton: "Pad beitreten", invitesCalendarsTitle: "Kalender", - invitesCalendarInviteCodePlaceholder: "Kalender-Einladungscode eingeben", - invitesCalendarJoinButton: "Kalender beitreten", invitesEventsTitle: "Termine", - invitesEventInviteCodePlaceholder: "Termin-Einladungscode eingeben", - invitesEventJoinButton: "Termin beitreten", invitesForumsTitle: "Foren", - invitesForumInviteCodePlaceholder: "Forum-Einladungscode eingeben", - invitesForumJoinButton: "Forum beitreten", invitesMapsTitle: "Karten", - invitesMapInviteCodePlaceholder: "Karten-Einladungscode eingeben", - invitesMapJoinButton: "Karte beitreten", invitesShopsTitle: "Shops", - invitesShopInviteCodePlaceholder: "Shop-Einladungscode eingeben", - invitesShopJoinButton: "Shop beitreten", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "Empfohlene PUBs", + invitesFilterOthers: "Andere", + invitesJoinTitle: "Einladungen", + invitesJoinHint: "Füge einen beliebigen Einladungscode ein, wofür er auch ist, oder die Oasis-ID eines Bewohners.", + invitesJoinPlaceholder: "Einladungscode oder Oasis-ID", + invitesJoinButton: "Beitreten", + invitesUsedTitle: "Letzte Einladungen", + invitesUsedEmpty: "Du hast noch keine Einladung eingelöst.", + invitesUsedType: "Typ", + invitesUsedName: "Name", + invitesUsedDate: "Datum", invitesPubInviteCodePlaceholder: "PUB-Einladungscode eingeben", invitesAcceptInvite: "PUB beitreten", + invitesPubUses: "Verwendete Einladungen", + invitesPubExhausted: "Dieser PUB hat keine Einladungen mehr.", invitesAlreadyFederated: "Du bist bereits mit diesem Pub föderiert.", + invitesJoinPubMode: "Dieses Oasis läuft als PUB im Nur-Lese-Modus. Um es mit einem anderen PUB zu verbinden, im Terminal: ./oasis.sh join ", + invitesJoinLocalOnly: "Einladungen können nur auf dem Gerät angenommen werden, auf dem Oasis läuft, oder über den Admin-Link, der beim Start angezeigt wird.", invitesFederationsTitle: "Föderationen", invitesAcceptedInvites: "Föderiert", invitesUnfollow: "Entfolgen", @@ -559,16 +676,12 @@ module.exports = { invitesUnfollowedInvites: "Nicht föderiert", invitesNoFederatedPubs: "Keine föderierten Pubs.", invitesNoUnfollowed: "Keine nicht föderierten Pubs.", - invitesUnreachablePubs: "Nicht erreichbar", - invitesNoUnreachablePubs: "Keine nicht erreichbaren Pubs.", invitesPubsRefresh: "Aktualisieren", - invitesPubsClearUnreachable: "Nicht erreichbare entfernen", + invitesPubsClearUnreachable: "Bereinigen", invitesPubsExport: "Exportieren", invitesPubsImport: "Importieren", invitesPubsImportTitle: "Pubs importieren", invitesPubsImportPlaceholder: "Eine Multiserver-Adresse oder einen Einladungscode pro Zeile einfügen oder eine .txt-Datei hochladen…", - currentlyUnreachable: "FEHLER!", - errorDetails: "Fehlerdetails", genericError: "Ein Fehler ist aufgetreten.", panicMode: "Panikmodus!", encryptData: "Passwort festlegen (mind. 32 Zeichen), um deine Blockchain zu verschlüsseln", @@ -595,7 +708,6 @@ module.exports = { votesOptionNoLabel:"Nein", votesOptionAbstentionLabel: "Enthaltung", votesQuestionLabel: "Frage", - votesCreatedByLabel: "Erstellt von", voteStatusOpen: "OFFEN", voteStatusClosed: "GESCHLOSSEN", commentDescription: ({ parentUrl }) => [ @@ -613,21 +725,21 @@ module.exports = { votesLabel: "ABSTIMMUNGEN", pdfFallbackLabel: "PDF-Dokument", shopLabel: "SHOPS", - voteLabel: "VOTES", + voteLabel: "STIMMEN", submit: "Absenden", editProfile: "Avatar bearbeiten", oasisIdLabel: "OasisID", spreadContent: "Replizieren", welcomePmSubject: "Hello World!", - welcomePmBody: "Willkommen bei Oasis — ein freies, peer-to-peer, föderiertes und verschlüsseltes soziales Netzwerk mit einer verteilten Architektur nur mit Einladung.\n\nEinige interessante Orte zum Anfangen:\n\n- /profile — Bearbeite deinen Avatar, Namen, deine Beschreibung und welche Module auf deiner öffentlichen Seite erscheinen.\n- /invites — Füge einen Pub hinzu, um dich mit dem weiteren Netzwerk zu föderieren.\n- /peers — Sieh, wer verbunden ist, scanne dein LAN erneut, exportiere oder importiere Peer-Listen.\n- /inhabitants — Entdecke und besuche die Avatare anderer Personen.\n- /tribes — Erstelle private Gruppen mit Ende-zu-Ende-Verschlüsselung oder tritt ihnen bei.\n- /inbox — Lies und sende private Nachrichten.\n- /activity — Sieh die jüngste Aktivität, deine und die deines Netzwerks.\n- /publish — Schreibe einen Beitrag oder teile ein Bild, Audio, Video oder Dokument.\n- /search — Durchsuche die Inhalte des Netzwerks nach Typ.\n\nEinige interessante Orte zum Verbinden:\n\n- /Multiversum — Verwalte deine anderen Multiversum-Konten, einschließlich Senden und Empfangen von Inhalten.\n\nDiese Nachricht wurde privat von dir an dich selbst gesendet, daher war keine Verbindung nötig, um sie zu empfangen.", + welcomePmBody: "Willkommen bei Oasis — ein freies, peer-to-peer, föderiertes und verschlüsseltes soziales Netzwerk mit einer verteilten Architektur nur mit Einladung.\n\nEinige interessante Orte zum Anfangen:\n\n- [/avatar](/profile) — Bearbeite deinen Avatar, Namen, deine Beschreibung und welche Module auf deiner öffentlichen Seite erscheinen.\n- /settings — Passe deine lokale Instanz an: Sprache, Design, Module, Privatsphäre und Synchronisierung.\n- /invites — Füge einen Pub hinzu, um dich mit dem weiteren Netzwerk zu föderieren.\n- /peers — Sieh, wer verbunden ist, scanne dein LAN erneut, exportiere oder importiere Peer-Listen.\n- /inhabitants — Entdecke und besuche die Avatare anderer Bewohner.\n- /tribes — Erstelle private Gruppen mit Ende-zu-Ende-Verschlüsselung oder tritt ihnen bei.\n- /search — Durchsuche die Inhalte des Netzwerks nach Typ.\n\nEinige interessante Orte zum Verbinden:\n\n- /Multiversum — Verwalte deine anderen Multiversum-Konten, einschließlich Senden und Empfangen von Inhalten.\n\nDiese Nachricht wurde privat von dir an dich selbst gesendet, daher war keine Verbindung nötig, um sie zu empfangen.", profileSensorsSectionTitle: "Sensoren", profileVisibilityActivity: "Aktivitätsstufe", profileDeviceLockedHint: "Dieser Sensor ist immer aktiv: Er zeigt anderen, von welchem Gerät du dich verbindest, und kann nicht deaktiviert werden.", profileVisibilityKarma: "KARMA-Punkte", profileVisibilityUbi: "UBI", - profileVisibilityWallet: "ECOIN-Wallet", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityWallet: "ECOin-Wallet", + profileVisibilityEcoTax: "ECO-Steuer", + profileVisibilityLarpSign: "L.A.R.P.-Siegel", profileVisibilityGpg: "GPG-Schlüssel", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Shops", @@ -640,6 +752,10 @@ module.exports = { profileClearnetImagesLabel: "Bilder", profileClearnetDocumentsLabel: "Dokumente", profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Markt", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", editProfileDescription: "", profileName: "Name", @@ -665,7 +781,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Best.", walletConfirm: "Bestätigen", - walletDescription: "Verwalte deine digitalen Vermögenswerte.", + walletDescription: "Verwalte dein ECOin-Wallet.", walletDate: "Datum", walletFee: "Gebühr (Je höher, desto schneller)", walletFeeLine: ({ fee }) => `Gebühr: ECO ${fee}`, @@ -673,6 +789,86 @@ module.exports = { walletReceive: "Empfangen", walletReset: "Zurücksetzen", walletSend: "Senden", + walletPayingTransfer: "Überweisung wird bezahlt", + walletCreateTransferLabel: "Smart Contract erzeugen (Transfer)", + walletTransferNotifiedNote: "Die Gegenseite wurde über diese Zahlung informiert", + walletTransferCreatedNote: "Für diese Zahlung wurde ein Transfer (Beleg) erstellt", + walletTransferNoPayeeNote: "Niemand hat diese Adresse veröffentlicht, daher wurde kein Transfer erstellt", + walletTransferReceiptConcept: "Zahlung aus dem Wallet", + walletPaymentPmText: "hat dir eine Zahlung gesendet", + walletPayeeLabel: "Empfänger", + walletConceptLabel: "Verwendungszweck", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Du hast dein UBI erhalten.", + bankingBotPaymentTitle: "Du hast eine Zahlung erhalten.", + bankingBotUbiPaidText: "Dein bedingungsloses Grundeinkommen wurde ausgezahlt", + bankUbiPubThisNode: "Dieser Knoten zahlt das UBI", + bankUbiPubConnected: "UBI-PUB", + bankUbiPubNone: "Noch kein PUB kündigt UBI in deinem Netz an", + bankUbiAddressPublished: "ECOin-Adresse veröffentlicht", + bankUbiAddressMissing: "Veröffentliche deine ECOin-Adresse, um UBI zu erhalten", + bankUbiClaimedThisMonth: "Diesen Monat beansprucht", + bankUbiPubBalance: "PUB-Wallet verfügbar für UBI", + bankNoPubAddress: "Das UBI-PUB hat noch keine ECOin-Adresse veröffentlicht.", + bankUbiTab: "UBI", + bankDonateEco: "SPENDEN!", + donateTooltip: "ECOin spenden", + bankNoUserAddress: "Dieser Bewohner teilt keine ECOin-Adresse.", + bankUbiPubDefault: "Standard", + bankUbiPool: "Fonds", + bankUbiLastSeen: "Zuletzt gesehen", + bankUbiLastPayout: "Letzte Aktion", + bankUbiPaidOut: "Ausgezahlt", + bankRangeToday: "Heute", + bankRangeWeek: "Woche", + bankRangeMonth: "Monat", + bankRangeYear: "Jahr", + bankRangeFiveYears: "5 Jahre", + bankRangeAll: "Alles", + bankChartValueTitle: "Entwicklung des ECOin-Werts", + bankChartValueHours: "Zeitäquivalent (Std.)", + bankChartWealthTitle: "Umverteilung des ECOin-Vermögens", + bankChartDistributed: "Verteiltes UBI (ECO)", + bankChartRebalanceTitle: "Umverteilung des Reichtums zwischen PUBs", + bankChartRebalanced: "Zwischen PUBs verschobene ECO", + bankChartTaxes: "Einbehaltene Steuern (ECO)", + bankChartNoDataRange: "Keine Daten in diesem Zeitraum.", + bankChartNoDataYet: "Noch keine Daten", + bankChartPerHour: "pro Stunde", + bankChartPerDay: "pro Tag", + bankChartPerMonth: "pro Monat", + bankTotalUbiDistributed: "Insgesamt umverteiltes UBI (Netz)", + bankTotalTaxesWithheld: "Insgesamt einbehaltene Steuern (Netz)", + bankInflationIssuance: "Emissionsrate (jährlich, aus Blockbelohnung)", + bankRulesEligibilityTitle: "Wer das UBI erhalten kann", + bankRulesEligibility1: "Eine im Profil veröffentlichte ECOin-Adresse (Profil → Sensoren → ECOin-Wallet).", + bankRulesEligibility2: "Ein Feed, der älter als 30 Tage ist und Aktivität im Netz hat: Karma über null.", + bankRulesEligibility3: "Ein Anspruch pro Monat (Epoche), in Banking mit der Schaltfläche UBI beanspruchen. Der laufende Monat kann mit UBI ablehnen abgelehnt werden.", + bankRulesEligibility4: "Der Sockel von 1 ECO wird immer gezahlt; Steuern mindern nur den Teil darüber und werden nie getrennt erhoben.", + bankRulesEligibility5: "Das PUB zahlt im nächsten Zyklus und veröffentlicht einen UBI-Transfer, den du bestätigen kannst.", + bankRulesEligibility6: "Fehlt die Adresse, ist der Feed zu jung, wurde der Monat abgelehnt oder hat das PUB keine Mittel, wird der Anspruch übersprungen und auf dem PUB protokolliert.", + copyLinkTitle: "Link kopieren", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "UBI ablehnen", + bankUbiRefusedThisMonth: "Du hast das UBI dieses Monats abgelehnt.", + bankingBotUbiAvailableTitle: "Du kannst dein UBI beanspruchen.", + bankingBotUbiAvailableText: "Dein UBI für diesen Monat ist verfügbar", + bankingBotConfirmTitle: "Du hast einen Smart Contract, der auf Bestätigung wartet", + bankingBotConfirmText: "Smart Contracts, die auf deine Bestätigung warten", + bankChartZoomHint: "Zum Vergrößern klicken", + bankChartPoolTitle: "UBI-Fonds (im Zeitverlauf)", + bankChartNetworkUbiTitle: "Insgesamt umverteiltes UBI (Netz)", + bankChartSupplyTitle: "Umlaufender ECOin-Bestand (im Zeitverlauf)", + bankChartInflationTitle: "ECOin-Inflation (im Zeitverlauf)", + bankChartInflationSeries: "Annualisierte Rate (%)", + bankChartPool: "Gesamtfonds (ECO)", + bankChartKarmaTitle: "KARMA Scoring (im Zeitverlauf)", + bankChartFundsTitle: "Dein ECOin-Guthaben (im Zeitverlauf)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin verbunden", + bankWalletNotConfigured: "ECOin getrennt", + transfersPayWithWallet: "BEZAHLEN!", walletStatus: "Status", walletDisconnected: [ "ECOin ", @@ -682,8 +878,11 @@ module.exports = { " oder den Verbindungsstatus.", ], walletSentToLine: ({ destination, amount }) => `ECO ${amount} gesendet an ${destination}`, + walletNotConfigured: "Deine ECOin-Wallet ist noch nicht eingerichtet.", walletSettingsDescription: "Integriere Oasis mit deiner ECOin-Wallet.", - walletSettingsDocLink: "ECOin-Installationsanleitung", + walletSettingsMobileDescription: "Verbinde Oasis mit der ECOin-Wallet-App auf diesem Gerät.", + walletDownloadEcoin: "ECOin herunterladen!", + walletRpcUrl: "RPC-URL", walletStatusMessages: { invalid_amount: "Ungültiger Betrag", invalid_dest: "Ungültige Zieladresse", @@ -699,6 +898,7 @@ module.exports = { walletUser: "Benutzername", walletPass: "Passwort", walletConfiguration: "Wallet konfigurieren", + walletDisconnectButton: "Wallet trennen", cipher: "Verschlüsselung", cipherTitle: "Verschlüsselung", cipherDescription: "Verschlüssele und entschlüssele Text symmetrisch.", @@ -731,17 +931,16 @@ module.exports = { bookmarkFilterRecent: "NEUESTE", bookmarkCreateButton: "Lesezeichen erstellen", bookmarkUpdateButton: "Aktualisieren", - bookmarkDeleteButton: "Löschen", bookmarkAddFavoriteButton: "Favorit hinzufügen", bookmarkRemoveFavoriteButton: "Favorit entfernen", bookmarkUrlLabel: "Link", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Beschreibung", bookmarkDescriptionPlaceholder: "Optional", bookmarkTagsLabel: "Tags", bookmarkTagsPlaceholder: "Tags durch Kommas getrennt eingeben", bookmarkLastVisitLabel: "Letzter Besuch", - bookmarkSearchPlaceholder: "URL, Tags, Kategorie, Autor suchen...", + bookmarkSearchPlaceholder: "In Lesezeichen suchen...", bookmarkSortRecent: "Neueste zuerst", bookmarkSortOldest: "Älteste zuerst", bookmarkSortTop: "Meistgewählt", @@ -780,7 +979,7 @@ module.exports = { videoDescriptionPlaceholder: "Optional", videoNoFile: "Keine Videodatei angegeben", noVideos: "Keine Videos vorhanden.", - videoSearchPlaceholder: "Titel, Tags, Autor suchen...", + videoSearchPlaceholder: "In Videos suchen...", videoSortRecent: "Neueste zuerst", videoSortOldest: "Älteste zuerst", videoSortTop: "Meistgewählt", @@ -803,7 +1002,6 @@ module.exports = { documentFilterFavorites: "FAVORITEN", documentCreateButton: "Dokument hochladen", documentUpdateButton: "Aktualisieren", - documentDeleteButton: "Löschen", documentAddFavoriteButton: "Favorit hinzufügen", documentRemoveFavoriteButton: "Aus Favoriten entfernen", documentMessageAuthorButton: "PN", @@ -816,7 +1014,7 @@ module.exports = { documentDescriptionPlaceholder: "Optional", documentNoFile: "Keine Datei.", noDocuments: "Keine Dokumente vorhanden.", - documentSearchPlaceholder: "Titel, Tags, Beschreibung, Autor suchen...", + documentSearchPlaceholder: "In Dokumente suchen...", documentSortRecent: "Neueste zuerst", documentSortOldest: "Älteste zuerst", documentSortTop: "Meistgewählt", @@ -837,19 +1035,19 @@ module.exports = { audioFilterTop: "TOP", audioCreateButton: "Audio hochladen", audioUpdateButton: "Aktualisieren", - audioDeleteButton: "Löschen", audioAddFavoriteButton: "Favorit hinzufügen", audioRemoveFavoriteButton: "Favorit entfernen", audioFileLabel: "Audiodatei auswählen (.mp3, .wav, .ogg)", audioTagsLabel: "Tags", audioTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + tagsPlaceholder: "Tags durch Kommas getrennt eingeben", audioTitleLabel: "Titel", audioTitlePlaceholder: "Optional", audioDescriptionLabel: "Beschreibung", audioDescriptionPlaceholder: "Optional", audioNoFile: "Keine Audiodatei angegeben", noAudios: "Keine Audios vorhanden.", - audioSearchPlaceholder: "Titel, Tags, Autor suchen...", + audioSearchPlaceholder: "In Audios suchen...", audioSortRecent: "Neueste zuerst", audioSortOldest: "Älteste zuerst", audioSortTop: "Meistgewählt", @@ -861,7 +1059,7 @@ module.exports = { audioFilterFavorites: "FAVORITEN", favoritesTitle: "Favoriten", favoritesDescription: "Alle deine favorisierten Inhalte an einem Ort.", - favoritesSearchPlaceholder: "Favoriten durchsuchen...", + favoritesSearchPlaceholder: "In Favoriten suchen...", favoritesFilterAll: "ALLE", favoritesFilterRecent: "NEUESTE", favoritesFilterAudios: "AUDIOS", @@ -873,13 +1071,12 @@ module.exports = { favoritesFilterChats: "CHATS", favoritesFilterCalendars: "KALENDER", favoritesFilterVideos: "VIDEOS", - favoritesRemoveButton: "Aus Favoriten entfernen", favoritesNoItems: "Noch keine Favoriten.", yourContacts: "Deine Kontakte", allInhabitants: "Bewohner", allCVs: "Lebensläufe", discoverPeople: "Entdecke Bewohner in deinem Netzwerk.", - searchInhabitantsPlaceholder: "BEWOHNER NACH NAME FILTERN …", + searchInhabitantsPlaceholder: "In Bewohner suchen...", filterLocation: "BEWOHNER NACH ORT FILTERN …", filterLanguage: "BEWOHNER NACH SPRACHE FILTERN …", filterSkills: "BEWOHNER NACH FÄHIGKEITEN FILTERN …", @@ -903,7 +1100,6 @@ module.exports = { topSectionTitle: "TOP", blockedLabel: "Blockierter Bewohner", inhabitantviewDetails: "Details anzeigen", - cvVisitButton: "Lebenslauf ansehen", keepReading: "Weiterlesen...", oasisId: "ID", noInhabitantsFound: "Noch keine Bewohner gefunden.", @@ -921,6 +1117,18 @@ module.exports = { parliamentFilterLeaders: "FÜHRUNG", parliamentFilterRules: "REGELN", parliamentGovernmentCard: "Aktuelle Regierung", + parliamentSeatsGovernment: "Regierung", + parliamentSeatsOpposition: "Opposition", + parliamentSeatsAbstention: "Enthaltung", + parliamentSeatsQuorum: "Wahlquorum", + parliamentSeatsPass: "Zustimmungsschwelle", + parliamentSeatsByElection: "Nach Wahl", + parliamentSeatsByHouses: "Nach LARP-Haus", + parliamentSeatsSeats: "Sitze", + parliamentSeatsSeat: "Sitz", + parliamentSeatsLeader: "Anführer", + parliamentSeatsVotesFor: "Stimmen für", + parliamentSeatsNoHouse: "Ohne Haus", parliamentActorInPowerInhabitant: 'REGIERENDER BEWOHNER', parliamentActorInPowerTribe: 'REGIERENDER STAMM', parliamentHistoricalGovernmentsTitle: 'REGIERUNGEN', @@ -988,6 +1196,7 @@ module.exports = { parliamentTimeRemaining: "Verbleibende Zeit", parliamentElectionsStatusTitle: "Nächste Regierung", parliamentProposalDeadlineLabel: "Frist", + parliamentViewReport: "Bericht ansehen", parliamentProposalTimeLeft: "Verbleibende Zeit", parliamentRulesTitle: "Wie das Parlament funktioniert", parliamentRulesIntro: "Wahlen werden alle 2 Monate aufgelöst; Kandidaturen laufen durchgehend und werden zurückgesetzt, wenn eine Regierung gewählt wird.", @@ -1002,6 +1211,7 @@ module.exports = { parliamentRulesLaws: "Wenn ein Vorschlag seine Schwelle erreicht, wird er zum Gesetz und erscheint im Gesetze-Tab mit seinem Inkrafttretungsdatum.", parliamentRulesHistorical: "Im Historisch-Tab kannst du jeden Regierungszyklus sehen, der stattgefunden hat, und Daten über dessen Verwaltung.", parliamentRulesLeaders: "Im Anführer-Tab kannst du ein Ranking der Bewohner/Stämme sehen, die regiert (oder kandidiert) haben, sortiert nach Effizienz.", + parliamentRulesHemicycle: "Das Halbrund vergibt einen Sitz pro Bewohner: den Regierungsblock (Wähler der siegreichen Kandidatur und der Anführer), je einen Oppositionsblock pro weiterer Kandidatur des Zyklus und die Enthaltung. Die gestrichelten Marken zeigen das Wahlquorum (25 % des Zensus) und die Zustimmungsschwelle der aktuellen Methode. „Nach LARP-Haus“ färbt dieselben Sitze nach Haus.", parliamentProposalVoteStatusLabel: "Abstimmungsstatus", parliamentProposalOnTrackYes: "Schwelle erreicht", parliamentProposalOnTrackNo: "Unter der Schwelle", @@ -1015,6 +1225,7 @@ module.exports = { courtsCaseFormTitle: "Fall eröffnen", courtsCaseRespondent: "Angeklagter / Beklagter", courtsCaseRespondentPh: "Oasis-ID (@...) oder Stammname", + courtsCaseSubjectPlaceholder: "Betreff oder kurze Beschreibung", courtsCaseMethod: "Lösungsmethode", courtsCaseSubmit: "Fall einreichen", courtsNominateJudge: "Richter nominieren", @@ -1048,15 +1259,30 @@ module.exports = { courtsMethodDICTATOR: "Diktator", courtsMethodPOPULAR: "Volksabstimmung", courtsMethodKARMATOCRACY: "Karmatokratie", + courtsCaseType: "Art des Falls", + courtsPresetMinorConflict: "Leichter Konflikt", + courtsPresetModerateConflict: "Mittlerer Konflikt", + courtsPresetSevereConflict: "Schwerer Konflikt", + courtsPresetHarassment: "Belästigung oder Missbrauch", + courtsPresetContentModeration: "Inhaltsmoderation", + courtsPresetBanRestriction: "Sperre oder Einschränkung", + courtsPresetGovernanceDispute: "Governance-Streit", + courtsStatusOPEN: "Offen", + courtsStatusIN_PROGRESS: "In Bearbeitung", + courtsStatusDECIDED: "Entschieden", + courtsStatusCLOSED: "Geschlossen", + courtsStatusSOLVED: "Gelöst", + courtsStatusUNSOLVED: "Ungelöst", + courtsStatusDISCARDED: "Verworfen", courtsRulesTitle: "Wie Gerichte funktionieren", courtsRulesIntro: "Gerichte sind ein gemeinschaftlich geführter Prozess zur Konfliktlösung und Förderung restaurativer Gerechtigkeit. Dialog, klare Beweise und verhältnismäßige Maßnahmen werden priorisiert.", courtsRulesLifecycle: "Ablauf: 1) Fall eröffnen 2) Methode wählen 3) Beweise einreichen 4) Anhörung und Beratung 5) Urteil und Maßnahme 6) Einhaltung und Abschluss 7) Berufung (falls zutreffend).", - courtsRulesRoles: "Ankläger: eröffnet den Fall. Verteidigung: angeklagte Person oder Stamm. Methode: von der Gemeinschaft gewählter Mechanismus zur Erleichterung, Beweiswürdigung und Urteilsfindung. Zeuge: liefert Aussagen oder Beweise. Mediatoren: neutrale Personen, eingeladen vom Ankläger und/oder der Verteidigung, mit Zugang zu allen Details, die helfen, den Konflikt zu deeskalieren und Vereinbarungen zu schaffen.", + courtsRulesRoles: "Ankläger: eröffnet den Fall. Verteidigung: angeklagter Bewohner oder Stamm. Methode: von der Gemeinschaft gewählter Mechanismus zur Erleichterung, Beweiswürdigung und Urteilsfindung. Zeuge: liefert Aussagen oder Beweise. Mediatoren: neutrale Bewohner, eingeladen vom Ankläger und/oder der Verteidigung, mit Zugang zu allen Details, die helfen, den Konflikt zu deeskalieren und Vereinbarungen zu schaffen.", courtsRulesEvidence: "Beschreibung bis zu 1000 Zeichen. Relevante und rechtmäßige Bilder, Audios, Videos und PDF-Dokumente anhängen. Keine sensiblen privaten Daten ohne Einwilligung teilen.", courtsRulesDeliberation: "Anhörungen können öffentlich oder privat sein. Richter sorgen für Respekt, fordern Klarstellungen an und können irrelevantes oder rechtswidriges Material verwerfen.", courtsRulesVerdict: "Wiederherstellung wird priorisiert: Entschuldigungen, Mediationsvereinbarungen, Inhaltsmoderation, vorübergehende Einschränkungen oder andere verhältnismäßige Maßnahmen. Die Begründung muss protokolliert werden.", courtsRulesAppeals: "Berufung: zulässig bei neuen Beweisen oder einem klaren Verfahrensfehler. Muss innerhalb von 7 Tagen eingereicht werden, sofern nicht anders angegeben.", - courtsRulesPrivacy: "Privatsphäre und Sicherheit respektieren. Doxxing, Hass oder Drohungen werden entfernt. Richter können Teile der Akte bearbeiten oder versiegeln, um gefährdete Personen zu schützen.", + courtsRulesPrivacy: "Privatsphäre und Sicherheit respektieren. Doxxing, Hass oder Drohungen werden entfernt. Richter können Teile der Akte bearbeiten oder versiegeln, um gefährdete Bewohner zu schützen.", courtsRulesMisconduct: "Belästigung, Manipulation oder gefälschte Beweise können zu einer sofortigen negativen Lösung führen.", courtsRulesGlossary: "Fall: Aufzeichnung eines Konflikts. Beweis: Materialien, die Ansprüche unterstützen. Urteil: Entscheidung mit Maßnahme. Berufung: Antrag auf Überprüfung des Urteils.", courtsFilterActions: "AKTIONEN", @@ -1072,12 +1298,16 @@ module.exports = { exploreTrending: "Entdecke die beliebtesten Inhalte in deinem Netzwerk.", votesButton: "ABSTIMMUNGEN", shopProductButton: "SHOP", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISODE", + campaignButton: "KAMPAGNE", + logisticsRouteButton: "ROUTE", trendingTotalOpinions: "Meinungen gesamt", trendingNoContentMessage: "Noch keine Trends vorhanden.", trendingTotalCount: "Gesamtzahl", tasksTitle: "Aufgaben", tasksDescription: "Aufgaben in deinem Netzwerk entdecken und verwalten.", - taskSearchPlaceholder: "Aufgaben suchen...", + taskSearchPlaceholder: "In Aufgaben suchen...", taskTitleLabel: "Titel", taskDescriptionLabel: "Beschreibung", taskStartTimeLabel: "Startzeit", @@ -1096,7 +1326,7 @@ module.exports = { taskStatusOpen: "Öffnen", taskStatusInProgress: "In Bearbeitung", taskStatusClosed: "Geschlossen", - taskAssignedTo: "Zugewiesen an", + taskAssignedTo: "Zugewiesen", taskAssignedChip: "Zugewiesen", taskUnassignedChip: "Nicht zugewiesen", taskAssignees: "Zugewiesene", @@ -1106,6 +1336,7 @@ module.exports = { taskUpdateButton: "Aktualisieren", taskDeleteButton: "Löschen", taskFilterAll: "ALLE", + taskFilterRecent: "NEUESTE", taskFilterMine: "MEINE", taskFilterOpen: "OFFEN", taskFilterInProgress: "IN BEARBEITUNG", @@ -1133,14 +1364,13 @@ module.exports = { eventUpcomingDates: "Nächste Termine", eventsTitle: "Termine", eventsDescription: "Termine in deinem Netzwerk entdecken und verwalten.", - eventSearchPlaceholder: "Veranstaltungen suchen...", + eventSearchPlaceholder: "In Termine suchen...", eventPrice: "Preis", eventStatus: "Status", eventAllSectionTitle: "Termine", eventMineSectionTitle: "Deine Termine", eventCreateSectionTitle: "Termin erstellen", eventUpdateSectionTitle: "Termin aktualisieren", - eventDeleteButton: "Löschen", eventCreateButton: "Termin erstellen", eventAddToCalendar: "Zum Kalender hinzufügen", eventVisitCalendar: "Kalender ansehen", @@ -1158,11 +1388,11 @@ module.exports = { viewPlace: "Ort Ansehen", generatePdf: "PDF Erzeugen", sharePm: "Per PN Teilen", - galleryImages: "Bilder (max. 50 MB pro Datei)", + galleryImages: "Bilder (max. 75 MB pro Datei)", galleryPhotos: "Bilder", galleryAddPhoto: "Bild Hinzufügen", galleryRemovePhoto: "Entfernen", - galleryVideo: "Video (max. 50 MB)", + galleryVideo: "Video (max. 75 MB)", galleryAddVideo: "Video Hinzufügen", galleryRemoveVideo: "Video entfernen", viewVotation: "Abstimmung ansehen", @@ -1182,6 +1412,7 @@ module.exports = { eventDate: "Datum", eventAttendButton: "Teilnehmen", eventFilterAll: "ALLE", + eventFilterRecent: "NEUESTE", eventFilterMine: "MEINE", eventFilterToday: "HEUTE", eventFilterWeek: "DIESE WOCHE", @@ -1208,7 +1439,7 @@ module.exports = { tagsFilterTop: "TOP", tagsFilterCloud: "WOLKE", tagsNoItems: "Keine Tags vorhanden.", - tagsSearchPlaceholder: "Tags suchen...", + tagsSearchPlaceholder: "In Tags suchen...", tagsTableHeaderTag: "TAG/LINK:", tagsTableHeaderCount: "ANZAHL:", transfersTitle: "Überweisungen", @@ -1216,7 +1447,10 @@ module.exports = { transfersFrom: "Von", transfersTo: "An", transfersFilterAll: "ALLE", + transfersFilterRecent: "NEUESTE", transfersFilterMine: "MEINE", + transfersFilterMinePending: "AUSSTEHEND", + transfersFilterMineConfirmed: "SIGNIERT", transfersFilterUBI: "UBI", transfersFilterMarket: "MARKT", transfersFilterTop: "TOP", @@ -1231,7 +1465,6 @@ module.exports = { transfersCategoryEconomic: "Wirtschaftlich", transfersCategoryTime: "Zeit", transfersCategoryTrust: "Vertrauen", - transfersCategoryApply: "Anwenden", transfersUnitEco: "ECO", transfersUnitHours: "Std", transfersUnitTrust: "Vertrauen", @@ -1261,7 +1494,7 @@ module.exports = { transfersDiscardedSectionTitle: "Verworfene Überweisungen", transfersCreateSectionTitle: "Transfer erstellen", transfersAllSectionTitle: "Überweisungen", - transfersSearchPlaceholder: "Konzept, Tags, Bewohner suchen...", + transfersSearchPlaceholder: "In Überweisungen suchen...", transfersMinAmountLabel: "Mindestbetrag", transfersMaxAmountLabel: "Höchstbetrag", transfersSortRecent: "Neueste zuerst", @@ -1283,6 +1516,7 @@ module.exports = { voteErrorTitle: "Formular prüfen", voteErrorDeadlineMin: "Die Frist muss mindestens {days} Tage in der Zukunft liegen.", voteErrorGeneric: "Die Abstimmung konnte nicht gespeichert werden.", + voteLocked: "Diese Abstimmung hat bereits Stimmen oder Meinungen erhalten oder gehört zum Parlament oder zu den Gerichten: Sie kann nicht mehr bearbeitet oder gelöscht werden.", voteUpdateButton: "Aktualisieren", voteDeleteButton: "Löschen", voteOptionYes: "JA", @@ -1292,6 +1526,7 @@ module.exports = { voteFollowMajority: "MEHRHEIT FOLGEN", voteNotInterested: "NICHT INTERESSIERT", voteFilterAll: "ALLE", + voteFilterRecent: "NEUESTE", voteFilterMine: "MEINE", voteFilterOpen: "OFFEN", voteFilterClosed: "GESCHLOSSEN", @@ -1307,7 +1542,6 @@ module.exports = { voteTags: "Tags", voteOpinions: "Meinungen", novotes: "Keine Abstimmungsvorschläge vorhanden.", - voteBy: "Von", voteCreatedAt: "Erstellt am", voteNoQuestion: "Keine Frage angegeben", voteUnknownCreator: "Unbekannter Ersteller", @@ -1317,6 +1551,7 @@ module.exports = { voteCommentsForumButton: 'Diskussion eröffnen', voteCommentsSectionTitle: 'Offene Diskussion', voteNoCommentsYet: 'Noch keine Kommentare. Sei der Erste, der antwortet.', + commentsGoToLast: "Zum letzten Kommentar", voteNewCommentPlaceholder: 'Schreibe deinen Kommentar hier…', voteNewCommentButton: 'Kommentar veröffentlichen', voteNewCommentLabel: 'Einen Kommentar hinzufügen', @@ -1327,6 +1562,8 @@ module.exports = { cvNameLabel: "Vollständiger Name", cvDescriptionLabel: "Zusammenfassung", cvPhotoLabel: "Foto", + cvPdfLabel: "Lebenslauf (PDF)", + cvVisitButton: "Lebenslauf ansehen", cvPersonalExperiencesLabel: "Persönliche Erfahrungen", cvPersonalSkillsLabel: "Persönliche Fähigkeiten (durch Komma getrennt)", cvOasisExperiencesLabel: "Oasis-Beitragserfahrungen", @@ -1356,9 +1593,9 @@ module.exports = { cvNoCV: "Kein Lebenslauf gefunden.", blogSubject: "Betreff", blogMessage: "Nachricht", - blogImage: "Medien hochladen (max: 50MB)", + blogImage: "Medien hochladen (max: 75MB)", blogMedia: "Anhänge (bis zu 8 Bilder und ein Video)", - blogPublish: "Vorschau", + blogPublish: "Veröffentlichen", pollsTitle: "Umfragen", dataTitle: "Übereinstimmungen", dataDescription: "Erkunde und visualisiere die Übereinstimmungen in deinem Netzwerk.", @@ -1377,12 +1614,23 @@ module.exports = { dataKindTasks: "AUFGABEN", dataKindReports: "BERICHTE", dataKindVotes: "ABSTIMMUNGEN", - dataSearchPlaceholder: "Querdaten durchsuchen...", + dataSearchPlaceholder: "In Übereinstimmungen suchen...", dataCohesionTitle: "Kohäsionskoeffizient (CC)", dataCcShort: "CC", dataCohesionHint: "Durchschnittliche Affinität zwischen allem, was das Netzwerk veröffentlicht hat.", dataAffinity: "Affinität", dataCommonTerms: "Schlüsselwort", + dataReasonFollowing: "Du unterstützt diesen Bewohner", + dataReasonTribe: "Teilt einen Stamm mit dir", + dataReasonPinned: "Passt zu etwas, das du angeheftet hast", + dataReasonCv: "Passt zu den Fähigkeiten in deinem Lebenslauf", + dataReasonContent: "Verwandt mit dem, was du veröffentlichst", + dataReasonRelated: "Verwandt mit deinen Interessen", + dataReasonAlike: "Veröffentlicht von einem Bewohner wie dir", + dataReasonSupportsYou: "Unterstützt dich", + dataReasonMutual: "Gegenseitige Unterstützung", + dataReasonRated: "Vom Netzwerk gut bewertet", + dataReasonNear: "In deiner Nähe", dataStatPeople: "Lebensläufe", dataStatConnected: "Verbunden", dataStatSkills: "Fähigkeiten", @@ -1435,7 +1683,7 @@ module.exports = { pollStatusLabel: "Status", pollStatusOpen: "OFFEN", pollStatusClosed: "GESCHLOSSEN", - pollSearchPlaceholder: "Umfragen suchen...", + pollSearchPlaceholder: "In Umfragen suchen...", pollsNoItems: "Keine Umfragen gefunden.", viewPoll: "Umfrage Ansehen", pollChatCreate: "Umfrage Erstellen", @@ -1448,7 +1696,7 @@ module.exports = { blogFilterFavorites: "FAVORITEN", blogFilterTop: "TOP", blogCreateButton: "Blog Erstellen", - blogSearchPlaceholder: "Blogs suchen...", + blogSearchPlaceholder: "In Blogs suchen...", blogComments: "Kommentare", blogAllowComments: "Kommentare erlauben", blogCommentsClosed: "Der Autor hat die Kommentare für diesen Blog geschlossen.", @@ -1461,12 +1709,11 @@ module.exports = { forumCreateButton: "Forum erstellen", forumCreateSectionTitle: "Forum erstellen", forumDescription: "Diskutiere offen mit anderen Bewohnern in deinem Netzwerk.", - forumSearchPlaceholder: "Forum durchsuchen...", + forumSearchPlaceholder: "In Foren suchen...", forumFilterAll: "ALLE", forumFilterMine: "MEINE", forumFilterRecent: "NEUESTE", forumFilterTop: "TOP", - forumDeleteButton: "Löschen", forumParticipants: "Teilnehmer", forumMessages: "Nachrichten", forumMessageLabel: "Nachricht", @@ -1493,7 +1740,6 @@ module.exports = { imageFilterGallery: "GALERIE", imageCreateButton: "Bild hochladen", imageUpdateButton: "Aktualisieren", - imageDeleteButton: "Löschen", imageAddFavoriteButton: "Favorit hinzufügen", imageRemoveFavoriteButton: "Favorit entfernen", imageFileLabel: "Bilddatei auswählen (.jpeg, .jpg, .png, .gif)", @@ -1505,13 +1751,14 @@ module.exports = { imageDescriptionPlaceholder: "Optional", imageNoFile: "Keine Bilddatei angegeben", noImages: "Keine Bilder vorhanden.", - imageSearchPlaceholder: "Titel, Tags, Beschreibung, Autor suchen...", + imageSearchPlaceholder: "In Bilder suchen...", imageSortRecent: "Neueste zuerst", imageSortOldest: "Älteste zuerst", imageSortTop: "Meistgewählt", imageSearchButton: "Suche", imageUpdatedAt: "Aktualisiert", imageNoMatch: "Keine Bilder gefunden.", + feedSearchPlaceholder: "In Feed suchen...", feedTitle: "Feed", createFeedTitle: "Feed erstellen", createFeedButton: "Feed senden!", @@ -1522,11 +1769,11 @@ module.exports = { noFeedsFound: "Keine Feeds gefunden.", createdAtLabel: "Erstellt am", FeedshareYourOpinions: "Kurztexte in deinem Netzwerk entdecken und teilen.", - refeedButton: "Refeed", + refeedButton: "Weiterleiten", alreadyRefeeded: "Du hast dies bereits refeeded.", activityTitle: "Aktivität", yourActivity: "Deine Aktivität", - globalActivity: "Globale Aktivität", + globalActivity: "Aktivität", activityList: "Aktivität", activityDesc: "Sieh dir die neueste Aktivität in deinem Netzwerk an.", allButton: "ALLE", @@ -1541,7 +1788,8 @@ module.exports = { meme: "Meme", typeRecent: "NEUESTE", typeTop: "TOP", - typePost: "BLOGS", + typePost: "BLOG", + activityUpdateLabel: "AKTUALISIERUNG", typeTribe: "STÄMME", typeLarp: "L.A.R.P.", typeInhabitants: "BEWOHNER", @@ -1566,7 +1814,7 @@ module.exports = { typeFeed: "FEEDS", typeContact: "KONTAKT", typePub: "PUB", - typeTombstone: "TOMBSTONE", + typeTombstone: "GELÖSCHT", typeBanking: "BANKWESEN", typeBankWallet: "BANKWESEN/WALLET", typeBankClaim: "BANKWESEN/UBI", @@ -1613,8 +1861,8 @@ module.exports = { bankingUserEngagementScore: "KARMA-Bewertung", viewDetails: "Details anzeigen", link: "Link", - activityProjectFollow: "%OASIS% unterstützt jetzt dieses Projekt %PROJECT%", - activityProjectUnfollow: "%OASIS% unterstützt dieses Projekt nicht mehr %PROJECT%", + activityProjectFollow: "%OASIS% %ACTION% jetzt dieses Projekt %PROJECT%", + activityProjectUnfollow: "%OASIS% %ACTION% dieses Projekt %PROJECT%", following: "UNTERSTÜTZT", unfollowing: "NICHT MEHR UNTERSTÜTZT", pledged: "ZUGESAGT", @@ -1679,7 +1927,7 @@ module.exports = { courtsNoCases: 'Keine Fälle.', reportsTitle: "Berichte", reportsDescription: "Verwalte und verfolge die Meldungen in deinem Netzwerk.", - reportsSearchPlaceholder: "Meldungen suchen...", + reportsSearchPlaceholder: "In Berichte suchen...", reportsFilterAll: "ALLE", reportsFilterRecent: "NEU", reportsFilterTop: "TOP", @@ -1727,7 +1975,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Berichte in Prüfung", reportsResolvedSectionTitle: "Gelöste Berichte", reportsInvalidSectionTitle: "Ungültige Berichte", - reportsTemplateSectionTitle: 'Berichtsvorlage', reportsBugTemplateTitle: 'Reproduktionsdetails (Fehler)', reportsFeatureTemplateTitle: 'Details (Funktionen)', reportsAbuseTemplateTitle: 'Details (Missbrauch)', @@ -1787,17 +2034,15 @@ module.exports = { tribeGallerySectionTitle: "Stämme-Galerie", tribeRecentSectionTitle: "Neueste Stämme", tribeTopSectionTitle: "Beliebte Stämme", - tribeviewTribeButton: "Stamm besuchen", - tribeviewSubTribeButton: "Unter-Stamm besuchen", tribeRootLabel: "WURZEL", tribeDescription: "Stämme in deinem Netzwerk erkunden oder erstellen.", tribeMainTribeLabel: "HAUPTSTAMM", tribeCreateButton: "Stamm erstellen", tribeUpdateButton: "Aktualisieren", tribeDeleteButton: "Löschen", - tribeImageLabel: "Medien hochladen (max: 50MB)", + tribeImageLabel: "Medien hochladen (max: 75MB)", tribeTitleLabel: "Titel", - searchTribesPlaceholder: "STÄMME NACH NAME FILTERN …", + searchTribesPlaceholder: "In Stämme suchen...", tribeTitlePlaceholder: "Name des Stammes", tribeDescriptionLabel: "Beschreibung", tribeDescriptionPlaceholder: "Diesen Stamm beschreiben", @@ -1810,6 +2055,15 @@ module.exports = { tribeIsAnonymousLabel: "STATUS", tribeMembersCount: "Mitgliederzahl", tribeEnterInvite: "CODE EINGEBEN", + subscriptionTitle: "Abonnements", + subscriptionOn: "Abonniert", + subscriptionOff: "Nicht abonniert", + subscriptionSubscribe: "Abonnieren", + subscriptionUnsubscribe: "Abbestellen", + subscriptionSubscribers: "Abonnenten", + pmListLabel: "Senden an", + pmListNone: "— Keine Liste —", + pmListNoSubscribers: "Keine Abonnenten", tribeLeaveButton: "VERLASSEN", tribePublic: "ÖFFENTLICH", tribePrivate: "PRIVAT", @@ -1842,16 +2096,16 @@ module.exports = { tribeSectionVideos: "VIDEOS", tribeSectionDocuments: "DOKUMENTE", tribeSectionBookmarks: "LESEZEICHEN", - tribeSectionMaps: "MAPS", + tribeSectionMaps: "KARTEN", tribeSectionPads: "PADS", tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionCalendars: "KALENDER", + tribePadCreate: "Pad erstellen", + tribeChatCreate: "Chat erstellen", + tribeCalendarCreate: "Kalender erstellen", + tribePadsEmpty: "Noch keine Pads.", + tribeChatsEmpty: "Noch keine Chats.", + tribeCalendarsEmpty: "Noch keine Kalender.", tribeInhabitantsEmpty: "Noch keine Bewohner in diesem Stamm.", tribeEventCreate: "Termin erstellen", tribeEventsEmpty: "Noch keine Termine.", @@ -1906,9 +2160,14 @@ module.exports = { tribeMediaTypeDocument: "Dokument", tribeMediaTypeBookmark: "Lesezeichen", tribeContentDelete: "LÖSCHEN", + tribeReachTribe: "STAMM", + tribeReachNotice: "OASIS zeigt es allen Bewohnern und CLEARNET zusätzlich im offenen Web. Das Öffnen hinterlässt eine lesbare Kopie außerhalb des Stammes: Zurück zu STAMM blendet es aus, aber bereits geteilte Kopien lassen sich nicht löschen.", + tribeReachPrivate: "Inhalte eines privaten Stammes können nur im Stamm bleiben.", + tribeReachOpenedBy: "Geöffnet von", + tribeReachChange: "Wo es veröffentlicht wird", tribeInviteCodeText: "Einladungscode: ", oasisVersionLabel: "Oasis-Version", - tribeInviteCodeHint: "Teile diesen Code mit jemandem, den du einladen möchtest. Er kann über /invites → Tribes beitreten.", + tribeInviteCodeHint: "Teile diesen Code mit jemandem, den du einladen möchtest. Er kann über /invites beitreten.", tribeStatusOpen: "OFFEN", tribeStatusClosed: "GESCHLOSSEN", tribeStatusInProgress: "IN BEARBEITUNG", @@ -1948,7 +2207,7 @@ module.exports = { tribeSearchMinChars: "Mindestens 2 Zeichen eingeben, um zu suchen.", agendaTitle: "Agenda", agendaDescription: "Hier findest du alle dir zugewiesenen Einträge.", - agendaSearchPlaceholder: "Agenda durchsuchen...", + agendaSearchPlaceholder: "In Agenda suchen...", agendaFilterAll: "ALLE", agendaFilterToday: "HEUTE", agendaFilterUpcoming: "BEVORSTEHEND", @@ -1969,6 +2228,7 @@ module.exports = { agendaNoItems: "Keine Zuweisungen gefunden.", agendaDiscardButton: "Verwerfen", agendaRestoreButton: "Wiederherstellen", + agendaRemoveButton: "Entfernen", agendaMembersCount: "Mitglieder", agendaLocationLabel: "Ort", agendaYes: "JA", @@ -2035,8 +2295,8 @@ module.exports = { pmComposeTitle: "Nachricht senden", pmRecipients: "Empfänger", pmRecipientsHint: "Oasis-IDs durch Kommas getrennt eingeben", - pmLimitsHint: "Bis zu 7 Empfänger pro Nachricht.", - pmTextPlaceholder: "Text auf 7000 Zeichen begrenzt.", + pmLimitsHint: "Bis zu 7 Bewohner pro Nachricht.", + pmTextPlaceholder: "Schreibe deine Nachricht.", pmSubject: "Betreff", pmSubjectHint: "Nachrichtenbetreff eingeben", pmText: "Nachricht", @@ -2058,8 +2318,8 @@ module.exports = { pmCrypterCharsLabel: "Zeichen", pmInvalidRecipients: "Ungültige Oasis-ID (sollte wie @…=.ed25519 aussehen).", pmEncryptionLabel: "Verschlüsselung", - private: "Privat", - privateDescription: "Deine verschlüsselten Nachrichten.", + private: "Posteingang", + privateDescription: "Verwalte deine privaten Nachrichten.", privateInbox: "POSTEINGANG", privateSent: "GESENDET", privateDelete: "Löschen", @@ -2094,7 +2354,7 @@ module.exports = { inboxProjectPledgedTitle: "Neue Zusage für dein Projekt", pmHasPledged: "hat zugesagt", pmToYourProject: "für dein Projekt", - blockchain: 'BlockExplorer', + blockchain: "Block-Explorer", blockchainDescription: 'Blöcke in der Blockchain erkunden und visualisieren.', blockchainNoBlocks: 'Keine Blöcke in der Blockchain gefunden.', blockchainStatsTitle: 'Statistiken', @@ -2115,24 +2375,24 @@ module.exports = { visitContent: "Inhalt besuchen", favoriteAdd: "Zu Favoriten anheften", pmContentTooltip: "Private Nachricht senden", + phoneCallTooltip: "Anruf tätigen", favoriteRemove: "Von Favoriten lösen", reportContent: "Inhalt melden", banking: 'Bankwesen', bankingTitle: 'Bankwesen', - bankingDescription: "Die ECOin-Wirtschaft: Guthaben, Grundeinkommen, Steuern und Industriewert.", + bankingDescription: "Die kollektive ECOin-Wirtschaft.", bankOverview: 'Übersicht', bankEpochs: 'Epochen', bankRules: 'Regeln', pending: 'Ausstehend', closed: 'Geschlossen', - bankViewTx: 'Tx anzeigen', bankClaimNow: 'Jetzt beanspruchen', - bankClaimUBI: 'UBI beanspruchen!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: 'UBI beanspruchen', + bankClaimAndPay: "Beanspruchen & zahlen", + bankClaimedPending: "Anspruch ausstehend...", + bankStatusClaimed: "Beansprucht", + bankStatusExpired: "Abgelaufen", + bankPubOnly: "Nur für PUBs", bankEpoch: 'Epoche', bankPool: 'Pool (diese Epoche)', bankWeightsSum: 'Gewichtssumme', @@ -2151,8 +2411,8 @@ module.exports = { bankViewEpoch: 'Epoche anzeigen', bankYourIndustryBalance: "Dein Industrieanteil", bankYourUbiMonth: "Dein BGE (diesen Monat)", - bankYourFundsMonth: "Deine Mittel (diesen Monat)", - bankIndustryBalance: "Industrieproduktion (geschätzt)", + bankYourFundsMonth: "Mittel gesamt", + bankIndustryBalance: "Industrieproduktion", bankUserBalance: 'Dein Kontostand', bankAddresses: 'Adressen', bankNoAddresses: 'Keine Adressen gefunden.', @@ -2160,6 +2420,10 @@ module.exports = { bankAddress: 'Adresse', bankAddAddressTitle: 'ECOIN-Adresse hinzufügen', bankAddAddressUser: 'Oasis-ID', + bankAddAddressLabel: "Bezeichnung (optional)", + bankAddAddressLabelPlaceholder: "Bobs Wallet, Alices Laden…", + bankAddAddressUserOptional: "Oasis-ID (optional)", + bankAddressUnnamed: "Adresse ohne Namen", bankAddAddressAddress: 'ECOIN-Adresse', bankAddAddressSave: 'Speichern', bankAddressAdded: 'Adresse hinzugefügt', @@ -2177,11 +2441,11 @@ module.exports = { bankAddressDeleteConfirm: 'Diese Adresse löschen?', search: 'Suchen!', bankLocal: 'Lokal', - bankFromOasis: 'Oasis', + bankFromOasis: "Netz", pubIdLabel: "PUB ID", bankUbiAvailableNo: "KEIN GUTHABEN!", bankUbiAvailableOk: "VERFÜGBAR!", - bankUbiAvailability: "BGE (Netzwerk)", + bankUbiAvailability: "UBI", bankAlreadyClaimedThisMonth: "Diesen Monat bereits beansprucht", bankUbiThisMonth: "UBI (diesen Monat)", bankUbiLastClaimed: "UBI (zuletzt beansprucht)", @@ -2190,7 +2454,7 @@ module.exports = { bankUbiPub: "PUB", bankUbiInhabitant: "BEWOHNER", bankUbiClaimedAmount: "BEANSPRUCHT (ECO)", - typeBankUbiResult: "BANKING - UBI", + typeBankUbiResult: "BANKWESEN - UBI", bankNoPubConfigured: "Kein PUB konfiguriert. Lege deine PUB-ID in den Einstellungen fest.", shopsTitle: "Shops", shopDescription: "Shops im Netzwerk entdecken und verwalten.", @@ -2211,6 +2475,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Verschlüsselt übertragen", shopAddFavorite: "Favorit hinzufügen", shopRemoveFavorite: "Favorit entfernen", shopOpen: "OFFEN", @@ -2218,6 +2483,13 @@ module.exports = { shopMakePrivate: "PRIVAT MACHEN (E2E)", shopMakePublic: "ÖFFENTLICH MACHEN", shopProducts: "Produkte", + shopFilterAll: "ALLE", + shopFilterRecent: "NEU", + shopFilterMine: "MEINE", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUKTE", + shopFilterPrices: "PREISE", + shopFilterFavorites: "FAVORITEN", shopProductAdd: "Produkt hinzufügen", shopProductTitle: "Produkt", shopProductUpdate: "Produkt aktualisieren", @@ -2245,7 +2517,7 @@ module.exports = { marketVisitItem: "ARTIKEL ANZEIGEN", shopStatus: "STATUS", shopCreatedAt: "ERSTELLT", - shopSearchPlaceholder: "Shops suchen...", + shopSearchPlaceholder: "In Shops suchen...", shopUrl: "URL", shopLocation: "Standort", shopTags: "Tags", @@ -2255,45 +2527,49 @@ module.exports = { shopTitlePlaceholder: "Name Ihres Shops", shopDescriptionPlaceholder: "Detaillierte Beschreibung Ihres Shops", shopLocationPlaceholder: "Stadt, Land", - shopTagsPlaceholder: "tag1, tag2, tag3", + shopTagsPlaceholder: "Tags durch Kommas getrennt eingeben", mapTitlePlaceholder: "Kartentitel", shopProductFeatured: "Hervorgehobenes Produkt", - shopSendToMarket: "Send to Market", + shopSendToMarket: "An den Markt senden", typeShop: "SHOPS", typeShopProduct: "SHOP-PRODUKT", bankExchange: 'Umtausch', bankExchangeCurrentValue: 'ECOin-Wert (1h)', - bankTotalSupply: 'ECOin Gesamtmenge', - bankEcoinHours: "ECOin-Äquivalent in Zeit", + bankTotalSupply: "ECOin-Gesamtbestand", + bankPubsSupply: "ECOin-Bestand im PUB-Pool", + bankHoldingSupply: "ECOin-Bestand außerhalb des BGE", + bankEcoinHours: "ECOin-Äquivalent in Zeit (1 ECO)", bankHoursOfWork: 'Stunden', bankUnitMs: "ms", bankUnitSeconds: "Sekunden", bankUnitMinutes: "Minuten", bankUnitDays: "Tage", bankExchangeNoData: 'Keine Daten verfügbar', - bankInflation: 'ECOin-Inflation (anual)', - bankInflationMonthly: 'ECOin-Inflation (mensual)', + bankInflation: "ECOin-Inflation (jährlich)", + bankInflationMonthly: "ECOin-Inflation (monatlich)", bankExchangeChartTitle: 'Progression des ECOin-Werts im Zeitverlauf', bankExchangeChartValue: 'Wert (ECO/h)', bankExchangeChartSupply: 'Umlauf', bankExchangeChartInflation: 'Inflation %', bankExchangeChartEmpty: 'Noch nicht genug Datenpunkte — komm später zurück', - bankCurrentSupply: 'ECOin aktuelle Menge', + bankCurrentSupply: "Aktueller ECOin-Bestand", bankingSyncStatus: 'ECOin-Status', bankingSyncStatusSynced: 'Synchronisiert', - bankingSyncStatusOutdated: 'Veraltet', + bankingSyncStatusOutdated: "Nicht synchron", statsTitle: 'Statistiken', statistics: "Statistiken", statsDescription: "Entdecke Statistiken über dein Netzwerk.", ALLButton: "ALLE", + RECENTButton: "NEUESTE", MINEButton: "MEINE", - TOMBSTONEButton: "TOMBSTONES", + TOMBSTONEButton: "GELÖSCHT", statsCreatedAt: "Erstellt am", statsYourContent: "Inhalt", statsYourOpinions: "Meinungen", statsNetworkContent: "Inhalt", statsNetworkOpinions: "Meinungen", - statsEcoWalletLabel: "ECOIN Wallet", + statsEcoWalletLabel: "ECOin Wallet", + walletAddressCopyHint: "Klicken zum Markieren, dann kopieren", statsEcoWalletNotConfigured: "Nicht konfiguriert!", statsBookmark: "Lesezeichen", statsEvent: "Termine", @@ -2315,6 +2591,8 @@ module.exports = { statsTransfer: "Überweisungen", statsAiExchange: "KI", statsPUBs: 'PUBs', + statsSyncedPeers: "Verbindungen", + statsTotalLabel: "Gesamt", statsPost: "Beiträge", statsSize: "Gesamt (Größe)", statsBlockchainSize: "Blockchain (Größe)", @@ -2366,8 +2644,14 @@ module.exports = { aiPrompt: "Gib eine informative und präzise Antwort.", aiConfiguration: "Prompt setzen", aiPromptUsed: "Prompt", - aiClearHistory: "Chatverlauf löschen", - aiServerError: "Die KI konnte nicht antworten. Bitte versuche es erneut.", + aiClearHistory: "Verlauf löschen", + aiServerError: "Ich kann nicht richtig antworten. Bitte versuch es noch einmal.", + aiStatusLoading: "42 lädt das Modell. Frag gleich noch einmal.", + aiThinking: "Denkt nach", + aiWaiting: "Wartet", + aiStatusMissingModel: "Das KI-Modell ist nicht installiert: src/AI/oasis-42-1-chat.Q4_K_M.gguf fehlt.", + aiExportFineTuning: "Fine-Tuning exportieren", + aiFactsTitle: "Daten aus deinem Netzwerk", aiInputPlaceholder: "Was gibt es Neues?", typeAiExchange: "KI", aiApproveTrain: "Zum kollektiven Training hinzufügen", @@ -2396,7 +2680,7 @@ module.exports = { marketFilterDiscarded: "VERWORFEN", marketFilterRecent: "NEUESTE", marketFilterMyBids: "GEBOTE", - marketCreateButton: "Artikel erstellen", + marketCreateButton: "Etwas verkaufen", marketItemType: "Typ", marketItemTitle: "Titel", marketItemAvailable: "Frist", @@ -2423,8 +2707,8 @@ module.exports = { marketItemSeller: "Verkäufer", marketNoItems: "Noch keine Artikel vorhanden.", marketYourBid: "Dein Gebot", - marketCreateFormImageLabel: "Medien hochladen (max: 50MB)", - marketSearchPlaceholder: "Titel oder Tags suchen", + marketCreateFormImageLabel: "Medien hochladen (max: 75MB)", + marketSearchPlaceholder: "In Markt suchen...", marketMinPriceLabel: "Mindestpreis", marketMaxPriceLabel: "Höchstpreis", marketSortRecent: "Neueste zuerst", @@ -2490,8 +2774,7 @@ module.exports = { housingSetClosed: "Nicht verfügbar machen", housingCreateButton: "Ort erstellen", housingUpdateButton: "Aktualisieren", - housingDeleteButton: "Löschen", - housingSearchPlaceholder: "Orte suchen...", + housingSearchPlaceholder: "In Wohnen suchen...", housingSearchButton: "Suchen", housingMinPrice: "Min. Preis", housingMaxPrice: "Max. Preis", @@ -2547,7 +2830,7 @@ module.exports = { jobLocationPresencial: "Vor Ort", jobLocationRemote: "Remote", jobVacantsPlaceholder: "Anzahl der Positionen", - jobImage: "Medien hochladen (max: 50MB)", + jobImage: "Medien hochladen (max: 75MB)", jobTasks: "Aufgaben", jobType: "Stellenart", jobTime: "Arbeitszeit", @@ -2569,12 +2852,11 @@ module.exports = { jobsExchangeSkill: "Im Tausch gefragte Fähigkeit", jobTimePartial: "Teilzeit", jobTimeComplete: "Vollzeit", - jobsDeleteButton: "LÖSCHEN", jobsUpdateButton: "AKTUALISIEREN", jobsFilterApplied: "BEWORBEN", jobsAppliedTitle: "Meine Bewerbungen", jobsAppliedBadge: "Beworben", - jobsSearchPlaceholder: "Titel, Tags, Beschreibung suchen...", + jobsSearchPlaceholder: "In Stellen suchen...", jobsMinSalaryLabel: "Mindestgehalt", jobsMaxSalaryLabel: "Höchstgehalt", jobsSortRecent: "Neueste zuerst", @@ -2586,23 +2868,23 @@ module.exports = { jobSetOpen: "Als OFFEN setzen", jobsTagsLabel: "Tags", noJobsMatch: "Keine Stellen gefunden.", - industrySearchPlaceholder: "Fabriken suchen…", + industrySearchPlaceholder: "In Industrie suchen...", industryAllSectors: "Alle Sektoren", industryVisitButton: "Industrie besuchen", industryAdvanceTo: "Setzen auf", industryAmount: "Betrag", industryApproveBuild: "Genehmigen", - industryBlueprint: "Blueprint", - industryBlueprints: "Blueprints", - industryBuild: "Build", + industryBlueprint: "Bauplan", + industryBlueprints: "Baupläne", + industryBuild: "Produktion", industryBuildNotes: "Notizen", industryBuildStart: "Startdatum", - industryVoteUpdateButton: "Update abstimmen", + industryVoteUpdateButton: "Über Aktualisierung abstimmen", industryVoteDeleteButton: "Löschung abstimmen", industryRevokeVote: "Widerrufen", industryTimeLeft: "Verbleibende Zeit", industryBuildEnd: "Enddatum", - industryBuilds: "Builds", + industryBuilds: "Produktionen", industryBuildTitle: "Titel", industryContribute: "Beitragen", industryContributeButton: "Beitragen", @@ -2660,7 +2942,6 @@ module.exports = { industryBuildStatus_FAILED: "FEHLGESCHLAGEN", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Ein Job passt zu deinem Lebenslauf", jobsBotMatchIntro: "Ein Job passt zu deinem Lebenslauf.", jobsBotMatchJob: "Job", @@ -2687,7 +2968,7 @@ module.exports = { industryRulesIntro: "Industry lässt die Bewohner die Produktionsmittel gemeinschaftlich besitzen: Eine Fabrik ist eine netzwerkeigene Werkstatt, die niemand privat besitzt.", industryRulesSteward: "Der Gründer ist der Verwalter — ein Treuhänder, kein Eigentümer: Er kann die Fabrik nicht allein verkaufen, privatisieren oder auflösen.", industryRulesMembership: "Die Mitgliedschaftsrichtlinie legt fest, wie man beitritt: Offen (sofort), Per Abstimmung (durch die Stimme der Mitglieder) oder Nur mit Einladung (ein Mitglied muss zuerst einladen).", - industryRulesQuorum: "Das Quorum ist die Mindestzahl an Mitgliedern, die abstimmen müssen, damit eine Entscheidung zählt, damit eine kleine Fabrik nicht von einer Person entschieden wird.", + industryRulesQuorum: "Das Quorum ist die Mindestzahl an Mitgliedern, die abstimmen müssen, damit eine Entscheidung zählt, damit eine kleine Fabrik nicht von einem einzigen Bewohner entschieden wird.", industryRulesMajority: "Die Mehrheit ist der Stimmenanteil zum Bestehen (0,5 = Hälfte, 1 = Einstimmigkeit); eine Entscheidung besteht, wenn die JA-Stimmen mindestens max(Quorum, Mitglieder × Mehrheit) erreichen.", industryRulesDecisions: "Mitglieder stimmen über Aufnahme, Genehmigung von Builds und Auflösung der Fabrik ab; der Verwalter kann diese kollektiven Entscheidungen nicht übergehen.", industryRulesBlueprints: "Blueprints sind freie (Copyleft-)Rezepte — die Eingaben (Materialien, Arbeitsstunden, Fähigkeiten) und die Ausgabe (ein physisches oder digitales Gut) — und jeder kann sie forken.", @@ -2706,13 +2987,14 @@ module.exports = { industryPauseVotes: "Status-Stimmen", industryTitle: "Industrie", industryDescription: "Modul zur gemeinschaftlichen Verwaltung der Produktionsmittel.", - typeIndustryBuild: "BUILD", - typeIndustryBlueprint: "BLUEPRINT", + typeIndustryBuild: "PRODUKTION", + typeIndustryBlueprint: "BAUPLAN", typeIndustryAllocation: "VERTEILUNG", typeIndustry: "INDUSTRIE", modulesIndustryLabel: "Industrie", modulesIndustryDescription: "Modul zur gemeinschaftlichen Verwaltung der Produktionsmittel.", industryFilterAll: "ALLE", + industryFilterRecent: "NEUESTE", industryFilterMember: "MITGLIED", industryFilterBlueprints: "BAUPLÄNE", industryFilterBuilds: "PRODUKTIONEN", @@ -2721,6 +3003,7 @@ module.exports = { industryFilterPaused: "PAUSIERT", industryFilterDissolved: "AUFGELÖST", industryAllTitle: "Industrie", + industryRecentTitle: "Neueste Fabriken", industryMemberTitle: "Fabriken, in denen ich bin", industryMineTitle: "Meine Fabriken", industryActiveTitle: "In Betrieb", @@ -2768,7 +3051,7 @@ module.exports = { industryPolicy_invite: "Nur mit Einladung", projectsTitle: "Projekte", projectsDescription: "Gemeinschaftsprojekte in deinem Netzwerk erstellen, finanzieren und verfolgen.", - projectSearchPlaceholder: "Projekte suchen...", + projectSearchPlaceholder: "In Projekte suchen...", projectCreateProject: "Projekt erstellen", projectCreateButton: "Projekt erstellen", projectUpdateButton: "AKTUALISIEREN", @@ -2791,7 +3074,7 @@ module.exports = { projectRecentTitle: "Neueste Projekte", projectTopTitle: "Meistfinanziert", projectTitlePlaceholder: "Projektname", - projectImage: "Medien hochladen (max: 50MB)", + projectImage: "Medien hochladen (max: 75MB)", projectDescription: "Beschreibung", projectDescriptionPlaceholder: "Erzähle die Geschichte und Ziele…", projectGoal: "Ziel (ECO)", @@ -2863,15 +3146,13 @@ module.exports = { modulesModuleName: "Name", modulesModuleDescription: "Beschreibung", modulesModuleStatus: "Status", - modulesTotalModulesLabel: "Geladene Module", + modulesTotalModulesLabel: "Gesamt", modulesEnabledModulesLabel: "Aktiviert", modulesDisabledModulesLabel: "Deaktiviert", modulesInvitesLabel: "Einladungen", modulesInvitesDescription: "Modul zum Verwalten und Einlösen von Einladungscodes.", modulesWalletLabel: "Geldbörse", modulesWalletDescription: "Modul zum Verwalten deiner digitalen Vermögenswerte (ECOin).", - modulesLegacyLabel: "Schlüssel", - modulesLegacyDescription: "Modul zum schnellen und sicheren Verwalten deines geheimen Schlüssels (privater Schlüssel).", modulesCipherLabel: "Verschlüsselung", modulesCipherDescription: "Modul zum symmetrischen Ver- und Entschlüsseln von Text (mit einem gemeinsamen Passwort).", modulesBookmarksLabel: "Lesezeichen", @@ -2933,20 +3214,23 @@ module.exports = { modulesProjectsLabel: "Projekte", modulesProjectsDescription: "Modul zum Erkunden, Crowdfunding und Verwalten von Projekten.", modulesBankingLabel: "Bankwesen", - modulesBankingDescription: "Modul zur Bestimmung des realen Werts von ECOIN und Verteilung eines UBI über die gemeinsame Kasse.", + modulesBankingDescription: "Modul zur Verwaltung der gemeinsamen Kasse: ECOIN-Wert, Steuern und BGE.", modulesFavoritesLabel: "Favoriten", modulesFavoritesDescription: "Modul zum Verwalten deiner favorisierten Inhalte.", fileTooLargeTitle: "Datei zu groß", - fileTooLargeMessage: "Die Datei überschreitet die maximal erlaubte Größe (50 MB). Bitte wähle eine kleinere Datei.", + fileTooLargeMessage: "Die Datei überschreitet die maximal erlaubte Größe (75 MB). Bitte wähle eine kleinere Datei.", goBack: "Zurück", + errorDismiss: "OK", errorPageTitle: "Etwas ist schiefgelaufen", + errorNoAllocation: "Keine Zuteilung für diesen Anspruch gefunden.", + errorInvalidClaim: "Diese Zuteilung kann nicht beansprucht werden.", visibilityLabel: "Sichtbarkeit", + clearnetPrivateConflict: "Private, versteckte, geschlossene, nur auf Einladung zugängliche oder kostenpflichtige Inhalte können nicht im CLEARNET veröffentlicht werden. Wähle OASIS oder mache sie öffentlich.", visibilityPublic: "Öffentlich", visibilityHidden: "Versteckt", visibilityMakePublic: "Öffentlich machen", visibilityMakeHidden: "Verstecken", invitesInhabitantsTitle: "Bewohner", - invitesInhabitantsFollow: "Folgen", aiNavPlaceholder: "Beschreib, was du brauchst...", aiNavDisabled: "KI-Navigation ist deaktiviert.", aiNavResultsTitle: "AINav-Vorschläge", @@ -2957,7 +3241,7 @@ module.exports = { modulesAINavDescription: "Modul für natürlichsprachliche Anfragen zum Inhalt des Netzwerks.", directConnectDescription: "Verbinde dich direkt mit einem Peer, indem du IP-Adresse, Port und öffentlichen Schlüssel eingibst.", peerHost: "IP", - peerPort: "Port (Standard: 8008)", + peerPort: "Port", peerPublicKey: "Öffentlicher Schlüssel (@...ed25519)", connectAndFollow: "Verbinden", modulesPresetTitle: "Gängige Konfigurationen", @@ -2966,15 +3250,15 @@ module.exports = { workflowsSet: "Workflow anwenden", workflow_default: "Standard", workflow_jobs: "Arbeit", - workflow_ruling: "Regierung", - workflow_politics: "Politik", workflow_social: "Sozial", workflow_business: "Geschäft", workflow_night: "Nacht", + workflow_kids: "Kinder", + workflow_activists: "Aktivismus", workflow_mobile: "Mobil", statsCarbonFootprintTitle: "CO2-Fußabdruck", statsCarbonTombstone: "Tombstoning-Fußabdruck", - uploadMedia: "Medien hochladen (max: 50MB)", + uploadMedia: "Medien hochladen (max: 75MB)", cipherPasswordDecryptLabel: "Passwort (zum Entschlüsseln)", courtsRespondentInvalid: "Ungültiger Beklagter", feedDetailTitle: "Feed-Details", @@ -2983,7 +3267,7 @@ module.exports = { noComments: "Noch keine Kommentare.", mapsLabel: "Karten", mapTitle: "Karten", - mapDescription: "Erkunden und verwalten Sie Offline-Karten in Ihrem Netzwerk.", + mapDescription: "Verwalten und teilen Sie Offline-Karten in Ihrem Netzwerk.", mapMineSectionTitle: "Ihre Karten", mapCreateSectionTitle: "Karte erstellen", mapUpdateSectionTitle: "Karte aktualisieren", @@ -3023,9 +3307,22 @@ module.exports = { markerImageLabel: "Marker-Bild", mapAddMarkerTitle: "Markierung hinzufügen", mapAddMarkerButton: "Markierung hinzufügen", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Markierung entfernen", mapApplyZoom: "Zoom anwenden", - mapSearchPlaceholder: "Beschreibung, Tags, Autor suchen...", + mapPlaceSearchPlaceholder: "Ort oder Land suchen...", + mapNoPlaces: "Keine Orte gefunden.", + mapClusterTitle: "Markierungen hier, klicken zum Vergrößern", + mapDeleteMarker: "Markierung löschen", + mapZoomIn: "Vergrößern", + mapZoomOut: "Verkleinern", + mapFitMarkers: "Alle Markierungen einpassen", + mapPanLabel: "Karte verschieben", + mapCenterHere: "Hier zentrieren", + mapNear: "nahe", + mapPickedSpot: "Gewählter Punkt", + mapPickHint: "Klicke auf die Karte, um einen Punkt zu wählen.", + mapClearPick: "Leeren", + mapSearchPlaceholder: "In Karten suchen...", mapSearchButton: "Suchen", mapUpdatedAt: "Aktualisiert", mapNoMatch: "Keine Karten entsprechen Ihrer Suche.", @@ -3036,6 +3333,465 @@ module.exports = { typeMapMarker: "KARTENMARKIERUNG", modulesMapLabel: "Karten", modulesMapDescription: "Modul zum Verwalten und Teilen von Offline-Karten.", + emergenciesTitle: "Notfälle", + modulesEmergenciesLabel: "Notfälle", + modulesEmergenciesDescription: "Modul zum Auslösen, Bestätigen und Verfolgen von Notfällen im Netzwerk.", + emergenciesDescription: "Modul zum Auslösen, Bestätigen und Verfolgen von Notfällen im Netzwerk.", + emergencyCreate: "Notfall erstellen", + emergencyUpdate: "Aktualisieren", + emergencyDelete: "Löschen", + emergencyTitleLabel: "Titel", + emergencyTitlePlaceholder: "Was passiert?", + emergencyTextLabel: "Details", + emergencyTextPlaceholder: "Wo, seit wann, was tun...", + emergencyCategoryLabel: "Kategorie", + emergencyCategoryWeather: "WETTER", + emergencyCategoryInfrastructure: "INFRASTRUKTUR", + emergencyCategoryHealth: "GESUNDHEIT", + emergencyCategorySecurity: "SICHERHEIT", + emergencyCategoryLost: "FUNDSACHEN", + emergencyCategoryNeighborhood: "NACHBARSCHAFT", + emergencyExpiresLabel: "Läuft ab in", + emergencyExpires1d: "1 Tag", + emergencyExpires3d: "3 Tage", + emergencyExpires7d: "1 Woche", + emergencyExpires30d: "1 Monat", + emergencyExpiresNone: "Unbestimmt", + emergencyExpiresKeep: "Aktuelles Datum behalten", + emergencyTagsLabel: "Tags", + emergencyTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + emergencyFilterAll: "ALLE", + emergencyFilterMine: "MEINE", + emergencyFilterRecent: "NEUESTE", + emergencySeverityUnverified: "UNBESTÄTIGT", + emergencySeverityLow: "NIEDRIG", + emergencySeverityMedium: "MITTEL", + emergencySeverityHigh: "HOCH", + emergencyStatusActive: "AKTIV", + emergencyStatusResolved: "GELÖST", + emergencyStatusExpired: "ABGELAUFEN", + emergencyConfirmations: "Bestätigungen", + emergencyConfirmButton: "Bestätigen", + emergencyConfirmed: "Du hast diesen Notfall bestätigt", + emergencyResolveButton: "Lösen", + emergencyReopenButton: "Wieder öffnen", + emergencyGenerateReport: "Bericht erzeugen", + emergencyUpdateEdited: "Bearbeitet", + emergencyLastUpdate: "LETZTE AKTUALISIERUNG", + emergencyUpdatesTitle: "Letzte Aktualisierungen:", + emergencyNoUpdates: "Noch keine Aktualisierungen.", + emergencyAddUpdate: "Aktualisierung veröffentlichen", + emergencyUpdatePlaceholder: "Was hat sich geändert?", + emergencyNoItems: "Keine Notfälle gefunden.", + emergencyExpiresAt: "Läuft ab", + emergencyBannerLabel: "Aktiver Notfall", + emergencyCreateSectionTitle: "Notfall erstellen", + emergencyUpdateSectionTitle: "Notfall aktualisieren", + emergencySearchPlaceholder: "In Notfällen suchen...", + typeEmergency: "NOTFÄLLE", + typeEmergencyConfirm: "NOTFALLBESTÄTIGUNG", + typeEmergencyUpdate: "NOTFALLAKTUALISIERUNG", + statsEmergency: "Notfälle", + statsEmergencyConfirm: "Notfallbestätigungen", + statsEmergencyUpdate: "Notfallaktualisierungen", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Ein Notfall, dem du folgst, hat eine Aktualisierung.", + emergencyBotResolvedTitle: "Ein Notfall, dem du folgst, wurde gelöst.", + dataKindEmergencies: "NOTFÄLLE", + mailingTitle: "Mailinglisten", + modulesMailingLabel: "Mailinglisten", + modulesMailingDescription: "Entdecke und verwalte Mailinglisten in deinem Netzwerk.", + modulesPhoneLabel: "Telefon", + modulesPhoneDescription: "Modul für Ende-zu-Ende-verschlüsselte Videoanrufe mit anderen Bewohnern.", + mailingDescription: "Entdecke und verwalte Mailinglisten in deinem Netzwerk.", + mailingCreate: "Mailingliste erstellen", + mailingUpdate: "Aktualisieren", + mailingArchive: "Archivieren", + mailingActivate: "Aktivieren", + mailingCreateSectionTitle: "Mailingliste erstellen", + mailingUpdateSectionTitle: "Mailingliste aktualisieren", + mailingTitleLabel: "Titel", + mailingTitlePlaceholder: "Name der Mailingliste", + mailingDescriptionLabel: "Beschreibung", + mailingDescriptionPlaceholder: "Worum geht es in dieser Liste?", + mailingTypeLabel: "Typ der Mailingliste", + mailingTypeOpen: "OFFEN", + mailingTypeClosed: "GESCHLOSSEN", + mailingStatusLabel: "Status", + mailingStatusActive: "AKTIV", + mailingStatusArchived: "ARCHIVIERT", + mailingMembersLabel: "Bewohner einladen", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Abonnenten", + mailingTagsLabel: "Tags", + mailingTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + mailingFilterAll: "ALLE", + mailingFilterMine: "MEINE", + mailingFilterSubscribed: "ABONNIERT", + mailingFilterRecent: "NEU", + mailingHistoryDate: "DATUM", + mailingHistoryThreads: "THREADS", + mailingHistoryInhabitants: "BEWOHNER", + mailingHistorySearchPlaceholder: "Im Verlauf suchen...", + mailingNoMessages: "Noch keine Nachrichten.", + mailingNoItems: "Keine Mailinglisten gefunden.", + mailingSearchPlaceholder: "In Mailinglisten suchen...", + mailingWriteButton: "Nachricht schreiben", + mailingWriteTitle: "An die Liste schreiben", + mailingReplyTitle: "Der Liste antworten", + mailingSend: "Senden", + mailingTextPlaceholder: "Deine Nachricht erreicht alle Teilnehmer der Liste.", + mailingParticipants: "Teilnehmer", + inboxFilterMailing: "Mailinglisten", + mailingReplyPlaceholder: "Der Liste antworten...", + mailingReplyToList: "Der Liste antworten", + typeMailingList: "MAILINGLISTE", + statsMailingList: "Mailinglisten", + mailingListLabel: "MAILINGLISTE", + logisticsTitle: "Logistik", + modulesLogisticsLabel: "Logistik", + modulesLogisticsDescription: "Entdecke und verwalte Logistikpläne in deinem Netzwerk.", + logisticsDescription: "Entdecke und verwalte Logistikpläne in deinem Netzwerk.", + logisticsCreate: "Route erstellen", + logisticsUpdate: "Aktualisieren", + logisticsClose: "Schließen", + logisticsReopen: "Wieder öffnen", + logisticsCreateSectionTitle: "Route erstellen", + logisticsUpdateSectionTitle: "Route aktualisieren", + logisticsTitleLabel: "Titel", + logisticsTitlePlaceholder: "Fahrt zum Markt, Transporter verfügbar...", + logisticsKindLabel: "Logistiktyp", + logisticsKindTrip: "FAHRT", + logisticsKindShipment: "SENDUNG", + logisticsKindTrips: "FAHRTEN", + logisticsKindShipments: "SENDUNGEN", + logisticsModeLabel: "Modus", + logisticsModeOffer: "BIETE", + logisticsModeRequest: "SUCHE", + logisticsModeOffers: "ANGEBOTE", + logisticsModeRequests: "GESUCHE", + logisticsDescriptionLabel: "Beschreibung", + logisticsDescriptionPlaceholder: "Treffpunkt, was du transportierst, Bedingungen...", + logisticsOriginLabel: "Start", + logisticsOriginPlaceholder: "Wo es losgeht", + logisticsDestinationLabel: "Ziel", + logisticsDestinationPlaceholder: "Wo es endet", + logisticsDateLabel: "Datum und Uhrzeit", + logisticsRecurrenceLabel: "Wiederholung", + logisticsFrequencyLabel: "Häufigkeit", + logisticsRecurrenceNone: "EINMALIG", + logisticsRecurrenceDaily: "TÄGLICH", + logisticsRecurrenceWeekly: "WÖCHENTLICH", + logisticsRecurrenceMonthly: "MONATLICH", + logisticsSeatsLabel: "Plätze", + logisticsSizeLabel: "Größe", + logisticsSizePlaceholder: "z. B. 2m x 1m", + logisticsWeightLabel: "Gewicht", + logisticsWeightPlaceholder: "z. B. 40kg", + logisticsPriceFree: "GRATIS", + logisticsPriceEco: "ECO", + logisticsPriceTime: "ZEIT", + logisticsPriceLabel: "Preis", + logisticsHours: "h", + logisticsOrderRefLabel: "Bestell-Link", + logisticsOrderRefPlaceholder: "/shops/... oder /market/... der zu liefernden Bestellung", + logisticsOrderLink: "Verknüpfte Bestellung", + logisticsTagsLabel: "Tags", + logisticsTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + logisticsFilterAll: "ALLE", + logisticsFilterMine: "MEINE", + logisticsFilterBooked: "GEBUCHT", + logisticsFilterRecent: "NEU", + logisticsFilterHistory: "VERLAUF", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "OFFEN", + logisticsStatusUpcoming: "BEVORSTEHEND", + logisticsStatusPast: "VERGANGEN", + logisticsStatusClosed: "GESCHLOSSEN", + logisticsZoneLabel: "Zone", + logisticsSearchPlaceholder: "In Logistik suchen...", + logisticsNoItems: "Keine Routen gefunden.", + logisticsBookingPending: "AUSSTEHEND", + logisticsBookingConfirmed: "BESTÄTIGT", + logisticsBookingRejected: "ABGELEHNT", + logisticsBookingCancelled: "STORNIERT", + logisticsBookingDelivered: "GELIEFERT", + logisticsConfirm: "Bestätigen", + logisticsReject: "Ablehnen", + logisticsDeliver: "Liefern", + logisticsReceiptLabel: "Beleg", + logisticsReceiptPlaceholder: "Lieferhinweis (optional)", + logisticsCancelBooking: "Buchung stornieren", + logisticsBookTitle: "Diese Route buchen", + logisticsNotesLabel: "Notizen", + logisticsNotesPlaceholder: "Was der Anbieter wissen sollte", + logisticsBook: "Buchen", + logisticsBookingsTitle: "Buchungen", + logisticsMyBookingTitle: "Deine Buchung", + logisticsRatingsTitle: "Bewertungen", + logisticsNoRatings: "Noch keine Bewertungen.", + logisticsRatingsWhenClosed: "Bewertungen sind möglich, sobald die Route geschlossen ist.", + logisticsScoreLabel: "Punkte", + logisticsRatingPlaceholder: "Wie lief es?", + logisticsRate: "Bewerten", + typeLogisticsRoute: "ROUTE", + typeLogisticsRating: "ROUTENBEWERTUNG", + statsLogisticsRoute: "Routen", + statsLogisticsRating: "Routenbewertungen", + logisticsRouteLabel: "ROUTE", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Modul zum Veröffentlichen von Podcast-Kanälen.", + podcastsDescription: "Modul zum Veröffentlichen von Podcast-Kanälen.", + podcastCreate: "Podcast erstellen", + podcastUpdate: "Aktualisieren", + podcastDelete: "Löschen", + podcastCreateSectionTitle: "Podcast erstellen", + podcastUpdateSectionTitle: "Podcast aktualisieren", + podcastTitleLabel: "Titel", + podcastTitlePlaceholder: "Name des Kanals", + podcastDescriptionLabel: "Beschreibung", + podcastDescriptionPlaceholder: "Worum geht es in diesem Podcast?", + podcastCategoryLabel: "Kategorie", + podcastCategoryNews: "NACHRICHTEN", + podcastCategoryMusic: "MUSIK", + podcastCategoryTalk: "GESPRÄCH", + podcastCategoryEducation: "BILDUNG", + podcastCategoryTech: "TECHNIK", + podcastCategoryCulture: "KULTUR", + podcastCategoryCommunity: "GEMEINSCHAFT", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "SONSTIGES", + podcastTagsLabel: "Tags", + podcastCoverLabel: "Cover (Bild oder Video)", + podcastEpisodeAudioLabel: "Audio- oder Videodatei (max: 75MB)", + podcastTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + podcastFilterAll: "ALLE", + podcastFilterMine: "MEINE", + podcastFilterRecent: "NEU", + podcastFilterTop: "TOP", + podcastFilterViewers: "HÖRER", + podcastSearchPlaceholder: "In Podcasts suchen...", + podcastNoItems: "Keine Podcasts gefunden.", + podcastEpisodesLabel: "Episoden", + podcastPlaysLabel: "Aufrufe", + podcastLastEpisode: "Letzte Episode", + podcastEpisodesTitle: "Episoden", + podcastNoEpisodes: "Noch keine Episoden.", + podcastEpisodeAdd: "Episode hinzufügen", + podcastEpisodeCreateTitle: "Episode veröffentlichen", + podcastEpisodeUpdateTitle: "Episode aktualisieren", + podcastEpisodeTitleLabel: "Episodentitel", + podcastEpisodeTitlePlaceholder: "Titel der Episode", + podcastEpisodeDescriptionLabel: "Shownotes", + podcastEpisodeDescriptionPlaceholder: "Was in dieser Episode passiert, Links, Credits...", + podcastEpisodePublish: "Veröffentlichen", + podcastPlayed: "Gehört", + podcastMarkPlayed: "Als gehört markieren", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISODE", + typePodcastOpinion: "EPISODENMEINUNG", + typePodcastPlay: "WIEDERGABE", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Episoden", + statsPodcastPlay: "Wiedergaben", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Ein Blog, dem du folgst, hat einen neuen Eintrag.", + podcastBotEpisodeTitle: "Ein Podcast, dem du folgst, hat eine neue Episode.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISODE", + campaignsTitle: "Kampagnen", + modulesCampaignsLabel: "Kampagnen", + modulesCampaignsDescription: "Entdecke und verwalte Kampagnen in deinem Netzwerk.", + campaignsDescription: "Entdecke und verwalte Kampagnen in deinem Netzwerk.", + campaignCreate: "Kampagne erstellen", + campaignUpdate: "Aktualisieren", + campaignDelete: "Löschen", + campaignClose: "Schließen", + campaignReopen: "Wieder öffnen", + campaignCreateSectionTitle: "Kampagne erstellen", + campaignUpdateSectionTitle: "Kampagne aktualisieren", + campaignTitleLabel: "Titel", + campaignTitlePlaceholder: "Was willst du erreichen?", + campaignTextLabel: "Text", + campaignTextPlaceholder: "Erkläre das Anliegen, wer entscheidet und was du forderst...", + campaignCategoryLabel: "Kategorie", + campaignCategoryEnvironment: "UMWELT", + campaignCategoryRights: "RECHTE", + campaignCategoryHealth: "GESUNDHEIT", + campaignCategoryEducation: "BILDUNG", + campaignCategoryInfrastructure: "INFRASTRUKTUR", + campaignCategoryCulture: "KULTUR", + campaignCategoryEconomy: "WIRTSCHAFT", + campaignCategoryOther: "SONSTIGES", + campaignGoalLabel: "Unterschriftenziel", + campaignDeadlineLabel: "Frist", + campaignTagsLabel: "Tags", + campaignTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + campaignFilterAll: "ALLE", + campaignFilterMine: "MEINE", + campaignFilterSigned: "UNTERSCHRIEBEN", + campaignFilterRecent: "NEU", + campaignFilterTop: "TOP", + campaignStatusOpen: "OFFEN", + campaignStatusAchieved: "ERREICHT", + campaignStatusUnachieved: "NICHT ERREICHT", + campaignStatusClosed: "GESCHLOSSEN", + campaignSearchPlaceholder: "In Kampagnen suchen...", + campaignNoItems: "Keine Kampagnen gefunden.", + campaignSignaturesLabel: "Unterschriften", + campaignSignaturesTitle: "Unterstützer", + campaignNoSignatures: "Noch keine Unterschriften.", + campaignSignTitle: "Diese Kampagne unterschreiben", + campaignSignPlaceholder: "Warum unterstützt du sie? (optional)", + campaignSign: "Unterschreiben", + campaignSigned: "Du hast bereits unterschrieben!", + campaignNotSigned: "Deine Unterschrift fehlt noch", + campaignElevate: "Ins Parlament bringen", + campaignElevated: "Ins Parlament gebracht", + campaignUpdatesTitle: "Aktualisierungen", + campaignNoUpdates: "Noch keine Aktualisierungen.", + campaignUpdatePlaceholder: "Was ist passiert?", + campaignAddUpdate: "Aktualisierung veröffentlichen", + campaignQrAlt: "QR zum Unterschreiben dieser Kampagne", + campaignQrCaption: "Scannen zum Unterschreiben", + typeCampaign: "KAMPAGNE", + typeCampaignSignature: "UNTERSCHRIFT", + typeCampaignUpdate: "KAMPAGNEN-AKTUALISIERUNG", + statsCampaign: "Kampagnen", + statsCampaignSignature: "Kampagnenunterschriften", + statsCampaignUpdate: "Kampagnenaktualisierungen", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Eine Kampagne, der du folgst, hat eine Aktualisierung.", + campaignBotAchievedTitle: "Eine Kampagne, der du folgst, hat ihr Ziel erreicht.", + campaignBotRaisedTitle: "Eine von dir unterschriebene Kampagne wurde ins Parlament gebracht.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Eine von dir gebuchte Route hat sich geändert.", + logisticsBotClosedTitle: "Eine von dir gebuchte Route wurde geschlossen.", + logisticsBotBookedTitle: "Jemand hat deine Route gebucht.", + logisticsBotCancelledTitle: "Eine Buchung deiner Route wurde storniert.", + logisticsBotConfirmedTitle: "Deine Buchung wurde bestätigt: das Paket ist unterwegs.", + logisticsBotRejectedTitle: "Deine Buchung wurde abgelehnt.", + logisticsBotDeliveredTitle: "Dein Paket ist angekommen.", + campaignLabel: "KAMPAGNE", + campaignGenerateReport: "Bericht erzeugen", + logisticsGenerateReport: "Bericht erzeugen", + mailingGenerateReport: "Bericht erzeugen", + campaignUpdateEdited: "Bearbeitet", + mailingMessagesLabel: "Nachrichten", + logisticsBookingsLabel: "Buchungen", + backupTitle: "Sicherung", + modulesBackupLabel: "Sicherung", + modulesBackupDescription: "Modul zum Exportieren und Importieren deiner Schlüssel und für verschlüsselte Sicherungen.", + backupDescription: "Modul zum Exportieren und Importieren deiner Schlüssel und für verschlüsselte Sicherungen.", + backupTypeRecovery: "WIEDERHERSTELLUNG", + backupTypeKeys: "SCHLÜSSEL EXPORTIEREN", + backupTypeFull: "VOLLSICHERUNG", + backupTypeRestore: "SICHERUNG WIEDERHERSTELLEN", + backupTypeInstant: "SOFORT", + backupInstantDescription: "Ein einfaches .zip deines gesamten Logs und deiner Dateien, ohne geheimen Schlüssel und ohne Passwort. Schnell, aber ", + backupInstantRisk: "wer die Datei hat, liest alles.", + backupKitPageDescription: "Das Wiederherstellungskit kann nur von diesem Gerät aus geöffnet werden.", + backupKitId: "Oasis-ID", + backupKitDate: "Erstellt", + backupKitQrAlt: "QR-Code deines geheimen Schlüssels", + backupKitStep1: "Drucke oder schreibe ihn ab und bewahre ihn sicher und offline auf.", + backupKitStep2: "Zur Wiederherstellung installiere Oasis auf einem neuen Gerät und importiere dieses Geheimnis unter Sicherung › Schlüssel.", + backupKitStep3: "Vernichte diese Seite, falls sie jemals offengelegt wird.", + backupKitPdfButton: "PDF erzeugen", + backupScopeLabel: "Umfang", + backupScopeAll: "ALLES", + backupScopeMine: "NUR MEINE INHALTE", + backupSinceLabel: "Seit", + backupEstimateTitle: "Schätzung", + backupFileInfo: "Deine verschlüsselte Kopie wird auf dein Gerät heruntergeladen (Name: oasis-backup-DATUM.oasisbk)", + backupExportButton: "Sicherung herunterladen", + backupRestorePassword: "Gib das Passwort ein, das bei der Sicherung verwendet wurde", + backupRestoreButton: "Sicherung wiederherstellen", + backupRestoredLine: "Sicherung wiederhergestellt.", + backupRestoredMessages: "Nachrichten hinzugefügt", + backupRestoredSkipped: "Bereits vorhanden", + backupRestoredBlobs: "Dateien hinzugefügt", + backupRestoredState: "Einstellungen wiederhergestellt", + backupRestoredFailed: "Fehlgeschlagen", + backupRestoredForked: "Abweichend", + backupRestoreRunning: "Die Sicherung wird wiederhergestellt. Diese Seite aktualisiert sich automatisch; du kannst Oasis währenddessen weiter benutzen.", + backupRestoreElapsed: "Vergangen", + backupRestoreFailedLine: "Die Sicherung konnte nicht wiederhergestellt werden.", + backupRestoreForkMine: "Dieses Gerät hat bereits Nachrichten mit deiner Identität veröffentlicht, die nicht in der Sicherung sind, daher konnte die Kopie nicht darüber angewendet werden. Stelle auf einer sauberen Installation direkt nach dem Import deiner Schlüssel wieder her, bevor du etwas anderes tust.", + verificationTitle: "Prüfung", + verificationDescription: "Prüfe die Kette deines Feeds, erkenne Abzweigungen und sieh dir verwaiste oder fehlende Dateien an.", + verificationRun: "Prüfung starten", + verificationTotalMessages: "Nachrichten im Log", + verificationFeed: "Dein Feed", + verificationMessages: "Nachrichten", + verificationLastSequence: "letzte Sequenz", + verificationGaps: "Lücken", + verificationBrokenLinks: "Gebrochene Verweise", + verificationBadHashes: "Falsche Hashes", + verificationBadSignatures: "Falsche Signaturen", + verificationDuplicates: "Doppelte Sequenzen", + verificationForks: "Abzweigungen", + verificationBlobs: "Dateien", + verificationBlobsPresent: "vorhanden", + verificationBlobsReferenced: "referenziert", + verificationBlobsOrphan: "verwaist", + verificationBlobsMissing: "fehlend", + verificationBlobsMissingOwn: "Eigene Dateien, die auf diesem Gerät fehlen", + verificationBlobsMissingOthers: "Dateien anderer Bewohner noch nicht heruntergeladen", + verificationBlobsUnavailable: "Dateien konnten auf diesem Gerät nicht aufgelistet werden.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Modul zum Entdecken und Verwalten von Wikis.", + wikiDescription: "Modul zum Entdecken und Verwalten von Wikis.", + wikiCreate: "Wiki erstellen", + wikiUpdate: "Aktualisieren", + wikiDelete: "Löschen", + wikiEdit: "Bearbeiten", + wikiTitleLabel: "Titel", + wikiTitlePlaceholder: "Wiki-Titel...", + wikiBodyLabel: "Inhalt", + wikiBodyPlaceholder: "Verlinke andere Wikis mit [[Wiki-Titel]] oder [[Wiki-Titel|eigener Text]].\n\nErwähne Bewohner mit @id.\n\nTagge mit #oasis.\n\nFüge Links wie https://solarnethub.com ein\n\nEine Leerzeile beginnt einen neuen Absatz; einfache Zeilenumbrüche bleiben erhalten.\n\nHänge beliebige unterstützte Medien an – sie werden hier eingebettet.", + formatBarLabel: "Format", + wikiStatusLabel: "Wiki-Typ", + wikiStatusOpen: "OFFEN", + wikiStatusClosed: "GESCHLOSSEN", + wikiTagsLabel: "Tags", + wikiTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + wikiSummaryLabel: "Zusammenfassung der Änderung", + wikiSummaryPlaceholder: "Was hat sich geändert?", + wikiFilterAll: "ALLE", + wikiFilterMine: "MEINE", + wikiFilterRecent: "NEUESTE", + wikiFilterLinked: "VERKNÜPFT", + wikiChanges: "Letzte Änderungen", + wikiNoPages: "Keine Wikis gefunden.", + wikiNoChanges: "Noch keine Änderungen.", + wikiHistory: "Verlauf", + wikiVersion: "Version", + wikiVersions: "Versionen", + wikiRestore: "Wiederherstellen", + wikiDiff: "Änderungen", + wikiViewVersion: "Ansehen", + wikiLinkedPages: "Verknüpfte Seiten", + wikiNoLinkedPages: "Diese Seite ist noch mit keiner anderen verknüpft.", + wikiMissingLink: "Seite existiert noch nicht", + wikiDuplicateTitle: "Es gibt bereits eine Seite mit diesem Titel. Ändere den Titel, um eine andere Seite anzulegen.", + wikiCreateSectionTitle: "Wiki erstellen", + wikiUpdateSectionTitle: "Wiki aktualisieren", + wikiSearchPlaceholder: "Im Wiki suchen...", + wikiCurrentVersion: "Aktuelle Version", + wikiViewingVersion: "Du siehst eine alte Version", + wikiBackToPage: "Zurück zur Seite", + wikiLinkedChip: "Verknüpft", + wikiRestoredSummary: "Wiederhergestellte Version", + wikiExportPdf: "Seite erzeugen", + wikiPreview: "Vorschau", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Wiki erstellen", + tribeWikiEmpty: "Noch keine Wikis.", + typeWiki: "WIKI", padsTitle: "Pads", padTitle: "Pad", modulesPadsLabel: "Pads", @@ -3046,21 +3802,133 @@ module.exports = { padTitleLabel: "Titel", padTitlePlaceholder: "Pad-Titel eingeben...", padStatusLabel: "Status", + padTypeLabel: "Pad-Typ", padStatusOpen: "OFFEN", padStatusInviteOnly: "NUR AUF EINLADUNG", padStatusClosed: "GESCHLOSSEN", padDeadlineLabel: "Frist", padTagsLabel: "Tags", - padTagsPlaceholder: "tag1, tag2, ...", + padTagsPlaceholder: "Tags durch Kommas getrennt eingeben", padMembersLabel: "Mitglieder", padCreated: "Erstellt", - padStartEditing: "BEARBEITUNG STARTEN!", + padStartEditing: "Bearbeiten", padEditorPlaceholder: "Schreiben beginnen...", padSubmitEntry: "Senden", padNoEntries: "Noch keine Einträge.", padAllSectionTitle: "Alle Pads", padMineSectionTitle: "Meine Pads", padsDescription: "Verwalte kollaborative verschlüsselte Texteditoren in deinem Netzwerk.", + roomsTitle: "Räume", + roomsDescription: "Entdecke verschlüsselte Videokonferenzräume in deinem Netzwerk und tritt ihnen bei.", + modulesRoomsLabel: "Räume", + modulesRoomsDescription: "Modul zum Entdecken und Betreten verschlüsselter Videokonferenzräume.", + roomFilterRecent: "NEUESTE", + roomFilterMine: "MEINE", + roomFilterAll: "ALLE", + roomFilterLive: "LIVE", + roomFilterOpen: "OFFEN", + roomFilterInvite: "NUR AUF EINLADUNG", + roomFilterClosed: "GESCHLOSSEN", + roomCreate: "Raum erstellen", + roomUpdate: "Aktualisieren", + roomDelete: "Löschen", + roomClose: "Schließen", + roomCreateSectionTitle: "Raum erstellen", + roomUpdateSectionTitle: "Raum aktualisieren", + roomTitlePlaceholder: "Titel des Raums", + roomDescriptionLabel: "Beschreibung", + roomDescriptionPlaceholder: "Worüber wollt ihr sprechen?", + roomTypeLabel: "Raumtyp", + roomTagsLabel: "Tags", + roomStatusOpen: "OFFEN", + roomStatusInviteOnly: "NUR AUF EINLADUNG", + roomStatusClosed: "GESCHLOSSEN", + roomParticipants: "Teilnehmer", + roomJoin: "Beitreten", + roomLeave: "Verlassen", + roomFull: "Raum voll", + roomLiveChip: "LIVE", + roomMuted: "stumm", + roomRec: "Audio aufnehmen", + roomRecStop: "Aufnahme beenden", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} hat den Raum betreten", + roomEventLeft: "{name} hat den Raum verlassen", + roomEventRecStart: "{name} hat die Aufnahme gestartet", + roomEventRecStop: "{name} hat die Aufnahme beendet", + roomEventMuted: "{name} hat sich stummgeschaltet", + roomEventUnmuted: "{name} hat die Stummschaltung aufgehoben", + roomEventHand: "{name} hat die Hand gehoben", + roomRaiseHand: "Hand heben", + roomLowerHand: "Hand senken", + confirmActionTitle: "Diese Aktion bestätigen", + confirmActionButton: "Bestätigen", + confirmJoinText: "Du bist dabei, mit dieser Einladung beizutreten.", + confirmFollowText: "Du bist dabei, diese Bewohnerin bzw. diesen Bewohner zu unterstützen.", + confirmSignText: "Du bist dabei, diese Kampagne zu unterzeichnen.", + confirmDownloadText: "Du bist dabei, diese Datei auf dein Gerät herunterzuladen.", + roomRecordings: "Aufnahmen", + roomClearNotices: "Leeren", + roomLineSwitchboard: "Zentrale", + roomLineDndError: "Die Nummer dieses Raums nimmt keine Anrufe an.", + roomNumberHint: "Wähle diese Nummer im Telefon, um den Raum zu betreten.", + liveRefresh: "Aktualisieren", + roomNobody: "Gerade ist niemand im Raum.", + roomSomeone: "{n} gerade im Raum.", + roomYou: "du", + roomYouAreIn: "Du bist drin", + roomNobodyInside: "Gerade ist niemand in diesem Raum.", + roomErrorFull: "Der Raum ist voll. Du kannst beitreten, sobald jemand geht.", + roomErrorUnreachable: "Der Raum ist gerade nicht erreichbar. Versuche es später erneut.", + roomErrorBusy: "Du bist bereits in einem Anruf oder in einem anderen Raum.", + roomErrorClosed: "Dieser Raum ist geschlossen.", + roomAccessDenied: "Dieser Raum ist nur auf Einladung. Du brauchst eine Einladung, um beizutreten.", + roomsNoItems: "Keine Räume gefunden.", + roomSearchPlaceholder: "Räume suchen...", + roomInviteMode: "Einladung", + tribeSectionRooms: "RÄUME", + tribeRoomCreate: "Raum erstellen", + tribeRoomsEmpty: "Noch keine Räume.", + typeRoom: "RÄUME", + typeRoomMember: "RAUMTEILNEHMER", + statsRoom: "Räume", + statsRoomMember: "Raumteilnehmer", + favoritesFilterRooms: "RÄUME", + invitesRoomsTitle: "Räume", + inviteCodeInvalid: "Dieser Einladungscode ist ungültig oder wurde schon benutzt.", + inviteAlreadyMember: "Du bist schon dabei. Du kannst es über sein Modul öffnen.", + actionFailed: "Das hat nicht geklappt. Bitte versuche es erneut.", + publishTooShort: "Dein Beitrag ist zu kurz.", + walletSendFailed: "Die Zahlung konnte nicht gesendet werden: {reason}", + pixeliaCoordsError: "Diese Koordinaten liegen außerhalb des Rasters.", + courtsErrTitle: "Ein Titel ist erforderlich.", + courtsErrRespondent: "Gib an, gegen wen sich der Fall richtet.", + courtsErrRespondentId: "Das ist keine gültige Oasis-ID.", + courtsErrMethod: "Wähle, wie der Fall gelöst wird.", + courtsErrCaseNotFound: "Dieser Fall wurde nicht gefunden.", + courtsErrBrief: "Schreibe deine Antwort.", + courtsErrStance: "Wähle deine Haltung.", + courtsErrResult: "Schreibe das Ergebnis.", + courtsErrTerms: "Schreibe die Bedingungen der Einigung.", + courtsErrMediator: "Füge mindestens eine vermittelnde Person hinzu.", + courtsErrSelfMediator: "Du kannst in deinem eigenen Fall nicht vermitteln.", + courtsErrJudge: "Wähle eine richtende Person.", + courtsErrSelfJudge: "Du kannst nicht über deinen eigenen Fall richten.", + courtsErrVisibility: "Wähle, wer den Fall sehen kann.", + courtsErrNomination: "Diese Nominierung wurde nicht gefunden.", + opinionAlreadyGiven: "Du hast deine Meinung schon abgegeben.", + cipherPasswordShort: "Das Passwort muss mindestens 32 Zeichen lang sein.", + errorForbidden: "Darauf hast du keinen Zugriff.", + errorNotFound: "Das wurde nicht gefunden.", + errorClosed: "Das ist geschlossen.", + errorInviteOnly: "Das ist nur auf Einladung. Du brauchst eine Einladung.", + errorTooLong: "Dieser Text ist zu lang.", + errorRequired: "Es fehlen Pflichtangaben.", + errorInvalid: "Einige Angaben sind ungültig.", + errorNotAuthor: "Nur wer es erstellt hat, kann das tun.", + panicRemoved: "Deine Blockchain wurde gelöscht.", padRecentSectionTitle: "Aktuelle Pads", padOpenSectionTitle: "Offene Pads", padClosedSectionTitle: "Geschlossene Pads", @@ -3072,7 +3940,7 @@ module.exports = { padRemoveFavorite: "Aus Favoriten Entfernen", padClose: "Schließen", padBackToEditor: "Zurück zum Editor", - padSearchPlaceholder: "Pads suchen...", + padSearchPlaceholder: "In Pads suchen...", gamesTitle: "Spiele", gamesDescription: "Entdecke und spiele einige Spiele.", gamesFilterAll: "ALLE", @@ -3102,24 +3970,28 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Werfe eine Münze und wette auf Kopf oder Zahl. Wie viel Glück hast du?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", + games8BallDesc: "Billard von oben. Klicken zum Zielen, gedrückt halten zum Aufladen. Versenke alle Kugeln mit möglichst wenigen Stößen.", + gamesArtilleryTitle: "Artillerie", + gamesArtilleryDesc: "Richte deine Kanone aus, berücksichtige den Wind und triff das Ziel. 5 Runden, die wenigsten Schüsse gewinnen.", gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + gamesLabyrinthDesc: "Entkomme dem Labyrinth, bevor dir die Züge ausgehen. Jedes Level wird größer und schwerer.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Friss alle Punkte, weiche den Geistern aus. Die Geister bewegen sich in Echtzeit — bleib in Bewegung!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", + gamesAudioPendulumTitle: "Klangpendel", + gamesAudioPendulumDesc: "Chaotischer Physiksimulator mit Echtzeit-Audiosynthese. Winkelgeschwindigkeiten werden zu Frequenzen, Spitzen zu Trommelschlägen. Keine zwei Simulationen klingen gleich.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Mische Visuals live auf zwei Decks: wähle je ein Muster, stelle Tempo, Farbe und Zoom ein, blende zwischen beiden über und lass es im Takt blitzen. Tippe das Tempo oder lass das Mikrofon den Puls vorgeben.", + gamesTetrisDesc: "Klassische fallende Blöcke. Räume Reihen ab, um zu punkten. Wie lange hältst du durch?", + gamesMinesweeperTitle: "Minesweeper", + gamesMinesweeperDesc: "Klassische Minensuche. Räume das Feld, um aufzusteigen — größere Felder, mehr Minen, höhere Punktzahl.", + gamesFilterScoring: "PUNKTESTAND", + gamesHallOfFame: "Ruhmeshalle", + gamesHallPlayer: "Spieler", + gamesHallScore: "Punktzahl", gamesNoScores: "Noch keine Ergebnisse.", modulesChatsLabel: "Chats", - modulesChatsDescription: "Modul zum Entdecken und Verwalten verschlüsselter Chats.", + modulesChatsDescription: "Modul zum Entdecken und Verwalten von Chats.", chatsTitle: "Chats", chatMineSectionTitle: "Meine Chats", chatRecentTitle: "Aktuelle Chats", @@ -3140,34 +4012,172 @@ module.exports = { chatMessagesLabel: "Nachrichten", chatThreadsLabel: "Threads", chatCreatedAt: "ERSTELLT", - chatSearchPlaceholder: "Chats suchen...", + chatSearchPlaceholder: "In Chats suchen...", chatStatusOpen: "OFFEN", chatStatusInviteOnly: "NUR AUF EINLADUNG", chatStatusClosed: "GESCHLOSSEN", chatSendMessage: "Senden", chatJumpLatest: "Neueste Nachricht", + chatReply: "Antworten", + chatReplyCancel: "Antwort abbrechen", + chatPin: "Anheften", + chatUnpin: "Lösen", + chatPinned: "Angeheftet", chatPickChat: "Wähle einen Chat, um ihn zu öffnen", chatNoneYet: "Noch kein Chat verfügbar.", - activitySearchPlaceholder: "In der Aktivität suchen...", - trendingSearchPlaceholder: "In den Trends suchen...", - opinionsSearchPlaceholder: "In den Meinungen suchen...", - votesSearchPlaceholder: "In den Abstimmungen suchen...", - welcomeStepUxTitle: "Wähle deine Oberfläche", - welcomeStepUxText: "Entscheide, wie Oasis aussieht. Später in den Einstellungen änderbar.", - welcomeStepUxAction: "Diese Ansicht verwenden", + activitySearchPlaceholder: "In Aktivität suchen...", + trendingSearchPlaceholder: "In Trends suchen...", + opinionsSearchPlaceholder: "In Meinungen suchen...", + votesSearchPlaceholder: "In Abstimmungen suchen...", + gamesSearchPlaceholder: "In Spiele suchen...", + schoolTitle: "Schule", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Eine Wiki-Seite, der du folgst, wurde bearbeitet.", + wikiBotRestoredTitle: "Eine frühere Version einer Wiki-Seite, der du folgst, wurde wiederhergestellt.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Ein neuer Student hat sich in deinen Kurs eingeschrieben.", + schoolBotInvitedTitle: "Du wurdest zu einem Kurs eingeladen.", + schoolBotAdmittedTitle: "Du wurdest in einen Kurs aufgenommen.", + schoolBotCertificateTitle: "Du hast ein Zertifikat erhalten.", + schoolBotPassedTitle: "Ein Student hat deinen Kurs bestanden.", + schoolBotLessonTitle: "Eine neue Lektion wurde veröffentlicht.", + invitesSchoolTitle: "Schule", + schoolEnrollLabel: "Kurs beitreten", + schoolExamRetryLater: "Du kannst es nach 24 Stunden erneut versuchen.", + schoolApproved: "BESTANDEN", + schoolExamPassMark: "Bestehensgrenze (0-10)", + schoolExamPassed: "BESTANDEN", + schoolExamPending: "Ausstehend", + schoolExamFailed: "NICHT BESTANDEN", + schoolExamAddQuestion: "Frage hinzufügen", + schoolExamCreateQuestion: "Frage erstellen", + schoolExamOption: "Option", + schoolExamCorrect: "Richtige Option", + schoolExamPoints: "Punkte", + schoolMaterialTextPlaceholder: "Notizen zu dieser Lektion...", + schoolMaterials: "Materialien", + schoolAddMaterial: "Material hinzufügen", + schoolNoMaterials: "Noch keine Materialien.", + bankYourSchoolBalance: "Deine Schul-Einnahmen", + schoolProgress: "Fortschritt", + schoolProgressMark: "Als abgeschlossen markieren", + schoolProgressDone: "Abgeschlossen", + schoolExams: "Prüfungen", + schoolExamCreate: "Prüfung erstellen", + schoolExamTitlePlaceholder: "Titel der Prüfung", + schoolExamHelp: "Eine Frage pro Zeile: Frage? ; Option ; richtige Option* ; Option", + schoolExamQuestionsPlaceholder: "Was ist SSB? ; Eine Datenbank ; Ein P2P-Protokoll* ; Ein Browser", + schoolExamSubmit: "Antworten absenden", + schoolExamScore: "Deine Punktzahl", + schoolNoScoresYet: "Noch keine Ergebnisse.", + schoolLessonUnit: "Einheit / Phase", + schoolLessonUnitPlaceholder: "z. B. Phase 1", + schoolLessonOrder: "Reihenfolge", + profileClearnetSchoolLabel: "Schule", + schoolStartDate: "Startdatum", + schoolSessionDate: "Sitzungsdatum", + schoolCourseChat: "Kurs-Chat", + agendaFilterSchool: "SCHULE", + agendaFilterCampaigns: "KAMPAGNEN", + agendaFilterLogistics: "LOGISTIK", + schoolCourseType: "Kursart", + schoolTypeOpen: "OFFEN", + schoolTypePaid: "KOSTENPFLICHTIG", + schoolTypeInvite: "NUR MIT EINLADUNG", + schoolLessonLocked: "Verschlüsselte Lektion — bitte den Lehrer um den Kursschlüssel.", + schoolGrantAccess: "Kursschlüssel senden", + schoolKeySent: "Schlüssel gesendet", + schoolOngoing: "Laufend", + schoolFilterApplied: "ANGEMELDET", + schoolFilterOpen: "OFFEN", + schoolFilterFavorites: "FAVORITEN", + schoolPendingPayment: "Zahlung ausstehend", + schoolPendingStudents: "Ausstehende Einschreibungen", + schoolViewBill: "Rechnung ansehen", + schoolPaymentNote: "Die Einschreibung erzeugt eine ECO-Rechnung (einen Transfer). Deine Einschreibung ist bestätigt, sobald der Transfer signiert ist.", + schoolCloseCourse: "Kurs schließen", + schoolReopenCourse: "Kurs wieder öffnen", + schoolDescription: "Lehre und lerne Kurse in deinem Netzwerk.", + schoolFilterAll: "ALLE", + schoolFilterMine: "MEINE", + schoolFilterTop: "TOP", + schoolFilterRecent: "NEUESTE", + schoolCreateButton: "Kurs Erstellen", + schoolCreateSectionTitle: "Kurs erstellen", + schoolUpdateSectionTitle: "Kurs aktualisieren", + schoolCourseTitlePlaceholder: "Name deines Kurses", + schoolCourseDescriptionPlaceholder: "Was wirst du in diesem Kurs unterrichten?", + schoolCourseImage: "Kursbild (max. Größe: 75MB)", + schoolTags: "Tags", + schoolTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + schoolPrice: "Preis (ECO)", + schoolVisibility: "Sichtbarkeit", + schoolVisibilityPublic: "Öffentlich", + schoolVisibilityInvite: "Auf Einladung", + schoolStatus: "Status", + schoolClosed: "Geschlossen", + schoolFree: "Kostenlos", + schoolCreate: "Kurs erstellen", + schoolUpdate: "Kurs aktualisieren", + schoolSearchPlaceholder: "In Schule suchen...", + schoolSortTitle: "Titel", + schoolNoCourses: "Keine Kurse gefunden.", + schoolStudents: "Studenten", + schoolTeacher: "Lehrer", + schoolCreatedAt: "ERSTELLT", + schoolLessons: "Lektionen", + schoolNoLessons: "Noch keine Lektionen.", + schoolEnrollToSee: "Schreibe dich ein, um die Lektionen dieses Kurses zu sehen.", + schoolEnrollButton: "EINSCHREIBEN", + schoolUnenrollButton: "Kurs verlassen", + schoolInviteRequired: "Dieser Kurs ist nur auf Einladung.", + schoolTeacherPanel: "Lehrerbereich", + schoolStudentPanel: "Schülerbereich", + schoolAddLesson: "Lektion erstellen", + schoolLessonTitlePlaceholder: "Titel der Lektion", + schoolLessonTextPlaceholder: "Beschreibe die Inhalte dieser Lektion...", + schoolInviteStudents: "Studenten einladen", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "EINLADEN", + schoolInvited: "Eingeladen", + schoolIssueCertificate: "Zertifikat erstellen", + schoolCertified: "Zertifiziert", + schoolCertificateTextPlaceholder: "Zertifikatsnotiz (optional)", + schoolLessonOpen: "Lektion öffnen", + schoolCertificatePdf: "PDF", + schoolCertificates: "Zertifikate", + schoolNoCertificates: "Noch keine Zertifikate ausgestellt.", + schoolEditButton: "Bearbeiten", + schoolDeleteButton: "Löschen", + typeSchool: "SCHULE", + schoolCourseLabel: "KURS", + emergencyLabel: "NOTFALL", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Kurse", + statsSchoolLesson: "Lektionen", + statsSchoolEnroll: "Einschreibungen", + statsSchoolCertificate: "Schulzertifikate", + modulesSchoolLabel: "Schule", + modulesSchoolDescription: "Modul zum Wissensaustausch.", + welcomeStepUxAction: "Ansicht speichern", + welcomeStepWorkflowTitle: "Welche Nutzung von OASIS hast du im Sinn?", + welcomeStepWorkflowText: "Oasis hat viele Module. Wähle die Konfiguration, die zu dir passt, und nur die nützlichen bleiben aktiv. Du kannst sie jederzeit unter Module anpassen.", + welcomeStepWorkflowAction: "Konfiguration speichern", + welcomeWorkflow_full: "Standard", chatRateLimitTitle: "Zu viele Nachrichten", chatRateLimitMessage: "Du hast das Nachrichtenlimit dieses Raums pro Stunde erreicht. Versuche es später erneut.", chatMessagePlaceholder: "Nachricht eingeben...", chatNoMessages: "Noch keine Nachrichten.", chatTitlePlaceholder: "Chat-Titel", chatDescriptionPlaceholder: "Chat-Beschreibung", - chatTagsPlaceholder: "tag1, tag2, tag3", + chatTagsPlaceholder: "Tags durch Kommas getrennt eingeben", chatImageLabel: "Bilddatei auswählen (.jpeg, .jpg, .png, .gif)", chatAuthor: "Autor", chatAddFavorite: "Zu Favoriten Hinzufügen", chatRemoveFavorite: "Aus Favoriten Entfernen", chatPM: "PM", chatStatusLabel: "Status", + chatTypeLabel: "Chat-Typ", chatCategoryLabel: "Kategorie", calendarsTitle: "Kalender", calendarTitle: "Kalender", @@ -3176,15 +4186,15 @@ module.exports = { typeCalendar: "KALENDER", calendarCreate: "Kalender erstellen", calendarUpdate: "Aktualisieren", - calendarDelete: "Löschen", calendarTitleLabel: "Titel", calendarTitlePlaceholder: "Kalendertitel...", calendarStatusLabel: "Status", + calendarTypeLabel: "Kalender-Typ", calendarStatusOpen: "OFFEN", calendarStatusClosed: "GESCHLOSSEN", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Frist", calendarTagsLabel: "Tags", - calendarTagsPlaceholder: "tag1, tag2...", + calendarTagsPlaceholder: "Tags durch Kommas getrennt eingeben", calendarParticipantsLabel: "Teilnehmer", calendarCreated: "Erstellt", calendarJoin: "Beitreten", @@ -3195,11 +4205,11 @@ module.exports = { calendarNotePlaceholder: "Notiz hinzufügen...", calendarFirstDateLabel: "Datum", calendarFirstNoteLabel: "Notizen", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Intervall", calendarIntervalNone: "Keine Wiederholung", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Wöchentlich", + calendarIntervalMonthly: "Monatlich", + calendarIntervalYearly: "Jährlich", calendarFormDescription: "Beschreibung", calendarNoDates: "Noch keine Daten hinzugefügt.", calendarNoNotes: "Keine Notizen.", @@ -3207,6 +4217,8 @@ module.exports = { calendarsDescription: "Entdecke und verwalte Kalender in deinem Netzwerk.", calendarMonthPrev: "← Zurück", calendarMonthNext: "Weiter →", + calendarMonthNames: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember", + calendarDayInitials: "Mo,Di,Mi,Do,Fr,Sa,So", calendarAllSectionTitle: "Kalender", calendarRecentSectionTitle: "Kürzliche Kalender", calendarFavoritesSectionTitle: "Favoriten", @@ -3216,9 +4228,9 @@ module.exports = { calendarCreateSectionTitle: "Neuen Kalender erstellen", calendarUpdateSectionTitle: "Kalender aktualisieren", calendarAddFavorite: "Zu Favoriten hinzufügen", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Löschen", calendarRemoveFavorite: "Aus Favoriten entfernen", - calendarSearchPlaceholder: "Kalender suchen...", + calendarSearchPlaceholder: "In Kalender suchen...", calendarAddEntry: "Eintrag hinzufügen", statsCalendar: "Kalender", statsCalendarDate: "Kalenderdaten", @@ -3230,6 +4242,146 @@ module.exports = { tribeContentAccessDenied: "Zugang Verweigert", tribeContentAccessDeniedMsg: "Dieser Inhalt gehört zu einem Stamm. Sie müssen Mitglied sein, um darauf zuzugreifen.", tribeViewTribes: "Stämme Anzeigen", + tribeFilesEmpty: "Noch keine Dateien.", + filesLabel: "Dateien", + profileClearnetFilesLabel: "Dateien", + filesTitle: "Dateien", + filesDescription: "Dateien in Ihrem Netzwerk erkunden und verwalten.", + fileAllSectionTitle: "Dateien", + fileMineSectionTitle: "Ihre Dateien", + fileRecentSectionTitle: "Neueste Dateien", + fileTopSectionTitle: "Top Dateien", + fileFavoritesSectionTitle: "Favoriten", + fileFilterAll: "ALLE", + fileFilterMine: "MEINE", + fileFilterRecent: "NEUESTE", + fileFilterTop: "TOP", + fileFilterFavorites: "FAVORITEN", + fileCreateSectionTitle: "Datei Hochladen", + fileUpdateSectionTitle: "Datei Aktualisieren", + fileCreateButton: "Datei Hochladen", + fileUpdateButton: "Aktualisieren", + fileAddFavoriteButton: "Zu Favoriten Hinzufügen", + fileRemoveFavoriteButton: "Aus Favoriten Entfernen", + fileFileLabel: "Datei auswählen (max: 75MB)", + calendarFilterRecent: "NEUESTE", + calendarFilterAll: "ALLE", + calendarFilterMine: "MEINE", + calendarFilterFavorites: "FAVORITEN", + calendarFilterOpen: "OFFEN", + calendarFilterClosed: "GESCHLOSSEN", + padFilterRecent: "NEUESTE", + padFilterAll: "ALLE", + padFilterMine: "MEINE", + padFilterOpen: "OFFEN", + padFilterClosed: "GESCHLOSSEN", + chatFilterRecent: "NEUESTE", + chatFilterAll: "ALLE", + chatFilterMine: "MEINE", + chatFilterFavorites: "FAVORITEN", + chatFilterOpen: "OFFEN", + chatFilterClosed: "GESCHLOSSEN", + licenseLabel: "Lizenz", + phoneTitle: "Telefon", + phoneDescription: "Verwalte deine Ende-zu-Ende-verschlüsselten Videoanrufe.", + phoneUnavailable: "Anrufe sind auf diesem Gerät nicht verfügbar.", + phoneCallButton: "Anrufen", + profileVisibilityPhone: "Telefonnummer", + phoneNumberUnknown: "Niemand in deinem Netzwerk hat die Nummer {number}.", + phoneNumberChoose: "Prüfe, wen du unter {number} anrufen möchtest:", + phoneAnswer: "Annehmen", + phoneSpeaker: "Lautsprecher", + phoneEarpiece: "Hörer", + phoneReject: "Ablehnen", + phoneHangup: "Auflegen", + phoneMute: "Stummschalten", + phoneUnmute: "Mikrofon an", + phoneCameraOn: "Kamera an", + phoneCameraOff: "Kamera aus", + settingsPhoneVideo: "Video", + settingsPhoneVideoOn: "In Anrufen und Räumen erlaubt", + settingsPhoneVideoOff: "Nur Audio", + phoneSilence: "Nicht hören", + phoneUnsilence: "Wieder hören", + phoneCalling: "Anruf an", + phoneIsCalling: "ruft dich an", + notifyNewPmFrom: "Neue PN von", + notifyNewPamFrom: "Neue PaM von", + notifyUpdateLabel: "Update", + updateInstalledLabel: "Installierte Version", + updateAvailableLabel: "Verfügbare Version", + updateChangesLabel: "Änderungen", + updateFilesLabel: "Dateien", + updateOriginLabel: "Quelle", + phoneRecording: "PaM wird aufgenommen für", + phoneLeaveMessage: "Niemand nimmt ab: Hinterlasse deine Nachricht nach dem Signalton.", + phoneLeaveMessageBusy: "Gerade in einem anderen Gespräch: Hinterlasse deine Nachricht nach dem Signalton.", + phoneLeaveMessageUnreachable: "Nicht erreichbar: Hinterlasse deine Nachricht nach dem Signalton.", + phoneConnecting: "Verbindung zu", + phoneSendPam: "PaM senden", + phoneCancel: "Abbrechen", + phoneSearchPlaceholder: "In Telefon suchen...", + phonePamTitle: "Audioaufnahmen", + phoneNew: "Neu", + phoneDelete: "Löschen", + phoneNoPams: "Du hast keine Audioaufnahmen.", + phoneHistoryTitle: "Verlauf", + phoneLatestTitle: "Letzte Anrufe", + phoneCreateButton: "Neuer Anruf", + phoneLimitsHint: "Bis zu 7 Bewohner pro Anruf; mit mehr als einem ist es ein gemeinsamer Anruf.", + phoneErrorPub: "Pubs können nicht angerufen werden.", + phoneJointCall: "Gemeinsamer Anruf", + phoneWith: "Mit", + phoneResult: "Ergebnis", + phoneWhen: "Wann", + phoneDuration: "Dauer", + phoneOutgoing: "Ausgehend", + phoneIncoming: "Eingehend", + phoneNoHistory: "Noch keine Protokolle.", + phoneOutcomeEnded: "Angenommen", + phoneOutcomeMissed: "Verpasst", + phoneOutcomeRejected: "Abgelehnt", + phoneOutcomeNoanswer: "Keine Antwort", + phoneOutcomeCancelled: "Abgebrochen", + phoneOutcomePam: "PaM gesendet", + phoneOutcomeFailed: "Fehlgeschlagen", + phoneDialAdd: "Hinzufügen", + phoneDialBack: "Löschen", + phoneDialClear: "Leeren", + phoneDialPlaceholder: "Nummer oder Oasis-ID", + phoneDialInhabitants: "Bewohner", + phoneDialRemove: "Entfernen", + phoneOwnNumber: "Das ist deine eigene Nummer.", + phoneSettingsDesc: "Wähle, wer dich anrufen kann.", + phoneDnd: "Nicht stören", + phoneOpen: "Offen", + phoneRecipientRefuses: "Der Bewohner, den du anrufen möchtest, nimmt keine Anrufe an.", + phoneRefusedNotice: "Diese Bewohner nehmen keine Anrufe an und wurden nicht angerufen:", + fileEmptyError: "Die Datei ist leer. Wähle eine andere Datei.", + fileTitleLabel: "Titel", + fileTitlePlaceholder: "Titel", + fileDescriptionLabel: "Beschreibung", + fileDescriptionPlaceholder: "Beschreibung", + fileTagsLabel: "Tags", + fileTagsPlaceholder: "Tags durch Kommas getrennt eingeben", + fileSizeLabel: "Größe", + fileDownloadButton: "HERUNTERLADEN", + fileDetailsButton: "DETAILS", + fileNoFile: "Keine Datei-Datei", + fileSearchPlaceholder: "In Dateien suchen...", + fileSearchButton: "Suchen", + fileSortRecent: "Neueste", + fileSortOldest: "Älteste", + fileSortTop: "Meistgewählt", + fileNoMatch: "Keine passenden Dateien.", + fileUpdatedAt: "Aktualisiert", + typeFile: "DATEIEN", + modulesFilesLabel: "Dateien", + modulesFilesDescription: "Modul zum Entdecken und Verwalten von Dateien.", + tribeSectionFiles: "DATEIEN", + fileDescription: "Beschreibung", + fileDownload: "Herunterladen", + fileTypeLabel: "Typ", torrentsTitle: "Torrents", torrentsDescription: "Torrents in Ihrem Netzwerk erkunden und verwalten.", torrentAllSectionTitle: "Torrents", @@ -3257,10 +4409,33 @@ module.exports = { torrentTagsLabel: "Tags", torrentTagsPlaceholder: "Tags durch Kommas getrennt eingeben", torrentSizeLabel: "Größe", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Torrent erzeugen", + torrentFromContentMissing: "Die Datei ist auf diesem Knoten noch nicht verfügbar. Versuche es später erneut.", + torrentDownloads: "Downloads", + torrentFetchOasis: "In Oasis herunterladen", + torrentSaveFile: "Datei speichern", + torrentSeeds: "Seeds", + seedsLabel: "Seeds", + seedAction: "Unterstützen", + torrentSourceFile: "Datei", + torrentStateQueued: "In Warteschlange", + torrentStateProbing: "Suche Seeds", + torrentStateDownloading: "Lädt herunter", + torrentStateDone: "Abgeschlossen", + torrentStateFailed: "Fehlgeschlagen", + torrentStateNoSource: "Keine Seeds verbunden, warte", + torrentStateCancelled: "Abgebrochen", + torrentNoDownloads: "Noch keine Downloads.", + torrentOriginalSection: "Original", + torrentCancel: "Abbrechen", + torrentDownloadButton: "HERUNTERLADEN", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETAILS", torrentNoFile: "Keine Torrent-Datei", noTorrents: "Keine Torrents gefunden.", - torrentSearchPlaceholder: "Titel, Tags, Autor suchen...", + noFiles: "Keine Dateien gefunden.", + torrentSearchPlaceholder: "In Torrents suchen...", torrentSearchButton: "Suchen", torrentSortRecent: "Neueste", torrentSortOldest: "Älteste", @@ -3268,10 +4443,12 @@ module.exports = { torrentNoMatch: "Keine passenden Torrents.", torrentUpdatedAt: "Aktualisiert", statsTorrent: "Torrents", + statsFile: "Dateien", typeTorrent: "TORRENTS", modulesTorrentsLabel: "Torrents", modulesTorrentsDescription: "Modul zum Entdecken und Verwalten von Torrents.", favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "DATEIEN", favoritesFilterMarket: "MARKT", favoritesFilterEvents: "EREIGNISSE", favoritesFilterForum: "FORUM", @@ -3285,15 +4462,17 @@ module.exports = { favoritesFilterShopProducts: "SHOP-ARTIKEL", tribeSectionTorrents: "TORRENTS", tribeCreateTorrent: "Torrent Hochladen", + tribeCreateFile: "Datei hochladen", settingsWishTitle: "Wunsch", - settingsWishDesc: "Konfiguriere den Wunsch deines Avatars.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Legt fest, wie du auf alle Inhalte zugreifst und wie du das Oasis-Netzwerk erlebst.", + settingsWishWhole: "Multiversum", + settingsWishMutuals: "Nur gegenseitige Unterstützung", + settingsWishOnlyLan: "Nur LAN", + settingsWishLocal: "Lokal", settingsPmVisibilityTitle: "Private Nachrichten", settingsPmVisibilityDesc: "Konfiguriere dein Niveau der Offenlegung für private Nachrichten.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Multiversum", + settingsPmVisibilityMutuals: "Nur gegenseitige Unterstützung", pmMutualNotice: "Ausgang ist ein UX-Schutz. Eingang ist ein Aufmerksamkeitsfilter.", inhabitantsPendingFollowsTitle: "Ausstehende Unterstützungsanfragen", inhabitantsPendingAccept: "Akzeptieren", @@ -3337,7 +4516,7 @@ module.exports = { tribeGovNoRevocations: "Noch kein Widerruf", tribeGovNoHistorical: "Noch kein Eintrag", logsTitle: "Logbuch", - logsDescription: "Erfasse deine Erfahrung im Netzwerk.", + logsDescription: "Führe ein privates Tagebuch deiner Erfahrungen.", logsViewTitle: "Logbuch", logsColumnType: "Typ", logsView: "Ansehen", @@ -3346,7 +4525,7 @@ module.exports = { logsCreateTitle: "Eintrag erstellen", logsWriteButton: "Schreiben", logsTextPlaceholder: "Beschreibe deine Erfahrungen...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "KI-Assistent", logsModeManual: "Manuell", logsModeAI: "KI", logsColumnLog: "Logbuch", @@ -3365,19 +4544,20 @@ module.exports = { logsExportOne: "Log exportieren", logsViewDetails: "Details ansehen", logsGenerateButton: "Text generieren", - logsSearchText: "In Logbüchern suchen...", + logsSearchText: "In Logbuch suchen...", logsSearchAnyType: "Beliebiger Typ", logsSearchButton: "Suchen", logsEmpty: "Noch keine Einträge.", + logsNothingNew: "Es gibt keine neuen Aktionen zum Aufzeichnen.", modulesLogsLabel: "Logbuch", - modulesLogsDescription: "Modul, um (über einen KI-Assistenten) deine Erfahrungen aufzuzeichnen.", + modulesLogsDescription: "Modul für ein privates Tagebuch deiner Erfahrungen.", statsLogsTitle: "Logbuch", typeLog: "LOGBUCH", larpTitle: "L.A.R.P.", larpDescription: "Eine Live-Rollenspiel-Schicht für kollaboratives Experimentieren.", larpNoHousesMatch: "Keine Häuser entsprechen deiner Suche.", - larpSearchPlaceholder: "Häuser durchsuchen...", + larpSearchPlaceholder: "In L.A.R.P. suchen...", larpAllHouses: "Häuser:", larpFilterRuling: "REGIERT", larpFilterHouses: "HÄUSER", @@ -3393,37 +4573,38 @@ module.exports = { larpPostSubmit: "Veröffentlichen", larpCycleLabel: "Zyklus:", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", + bankRulesArchTaxNote: "Die ARCH-Steuer spiegelt die langfristigen Kosten für Wachstum und Pflege des Netzwerkarchivs wider: Je älter deine Daten im Netzwerk sind, desto größer dein Anteil an den Wartungskosten.", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", + bankRulesChipNote: "Der Chip jedes Elements wird im Verhältnis zur größten jemals im Netzwerk beobachteten Nachricht eingefärbt und durch die Zahl der aktiven Bewohner abgeschwächt — ein größeres Netzwerk verteilt die Last und schiebt mehr Elemente in den grünen Bereich.", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", + bankRulesEcoTaxNote: "Die ECO-Steuer wird vom Brutto-UBI abgezogen, wenn die Zuteilung der Epoche berechnet wird. Die abgezogenen ECO werden in dieser Epoche nicht umverteilt — sie bleiben im Pub-Guthaben und speisen den Pool der nächsten Epoche über die Obergrenzen reserveMin / alpha.", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", + bankRulesKarmaNote: "Karma ergibt sich aus deinen Aktionen (Beiträge, Stimmen, Beteiligung an Parlament/Gerichten usw.) mit einem zeitlichen Abklingfaktor, abzüglich der von deinem Feed erzeugten Gramm CO2.", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", + bankTaxesArchTaxFootprintNote: "Die ARCH-Steuer spiegelt die Kosten für Wachstum und Pflege des Netzwerkarchivs wider. Sie wird aus der Zeitspanne zwischen deinem ersten veröffentlichten Block und dem neuesten Block im Netzwerk berechnet — je länger deine Geschichte im Archiv lebt, desto größer dein Anteil an den Wartungskosten.", + bankTaxesArchTaxNoteBold: "der direkten Senkung der ARCH-Steuer", + bankTaxesArchTaxNoteIntro: "Deine ARCH-Steuer wird von dem Überschuss abgezogen, den das Netzwerk über dein UBI hinaus erzeugt; der Basiswert des UBI gilt als unverrückbares Minimum. Die Steuer wird nie von dem festen Betrag abgezogen, der jedem Bewohner als UBI zusteht. Die abgezogenen Mittel speisen den Algorithmus zur Umverteilung des Wohlstands und fließen über deren UBI-Ansprüche an andere Bewohner zurück. Neuere Bewohner und jene, die sich speziell Archivierungs-, Replikations- und Wartungsaufgaben widmen, haben wahrscheinlich die Aufgabe, das Netzwerkarchiv in deinem Namen gesund zu halten. Oder sie widmen sich ", + bankTaxesArchTaxNoteOutro: " durch Pruning-Vereinbarungen, Deduplizierung redundanter Daten und gemeinsame Wartungsinfrastruktur, und ihr fortlaufender Beitrag kann durch Netzwerkkonsens verlangt werden.", + bankTaxesUserNoteChips: "Der ECO-Steuer-Chip neben jedem Element wechselt die Farbe (grün / gelb / rot) je nach Größe des Elements im Verhältnis zum größten jemals im Netzwerk gesehenen Block, abgeschwächt durch die Zahl der aktiven Bewohner — ein größeres Solarpunk-Netzwerk verteilt die Last und schiebt mehr Elemente in den grünen Bereich.", + bankTaxesUserNoteOtherParams: "Die ECO-Steuer wird derzeit aus dem CO2-Fußabdruck jedes Blocks abgeleitet, kann aber mit der Zeit weitere Parameter einbeziehen — Energie für die Replikation, redundanter Speicher zwischen Peers, Blob-Bandbreite, Rechenaufwand für die Entschlüsselung, Mining-Fußabdruck zugehöriger Transaktionen oder jede andere messbare Last, deren Bewertung das Netzwerk vereinbart.", + gamesHallDate: "Datum", + gamesNewRecord: "Neuer Rekord!", + indexingRefreshNote: "Diese Seite wird alle 10 Sekunden neu geladen.", + indexingMessage: "Oasis synchronisiert das verteilte Netzwerk aus Knoten. Bitte warten.", + indexingTitle: "Synchronisierung", + padsNoItems: "Keine Pads gefunden.", + padVersionHistory: "Versionsverlauf", + padAttributionView: "Nach Autorin", + padVersionView: "Ansehen", statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", + statsChatMessage: "Chat-Nachrichten", + statsGameScore: "Spiele", + statsWiki: "Wiki-Seiten", statsPad: "Pads", - statsPadEntry: "Pad entries", + statsPadEntry: "Pad-Einträge", bankTaxesLookupNote: "Gib eine Block-ID ein, um sie in deinem lokalen Feed nachzuschlagen.", bankTaxesUserNoteChipsClick: "Klicke auf einen Chip, um seinen Block im Blockexplorer zu inspizieren.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3438,6 +4619,14 @@ module.exports = { settingsReplicationTitle: "Replikation", settingsReplicationDesc: "Konfiguriere die Anzahl der Hops, die dein Peer von deinem eigenen Feed aus beim Replizieren von Inhalten folgt.", settingsReplicationHopsLabel: "Hops", + settingsBlobCacheTitle: "Medien-Cache", + settingsBlobCacheDesc: "Inhalte anderer Bewohner werden auf diesem Gerät gespeichert. Lege fest, wie viel Platz sie belegen dürfen.", + settingsBlobCacheUsage: "Aktuelle Belegung", + settingsBlobCacheFiles: "Dateien", + settingsBlobCacheLimit: "Limit", + settingsBlobCacheUnlimited: "Unbegrenzt", + settingsBlobCacheCleanNow: "Jetzt aufräumen", + settingsBlobCacheCleaned: "{n} Dateien entfernt, {size} freigegeben.", aiApproveTagsLabel: "Tags (kommagetrennt)", aiApproveTagsPlaceholder: "z. B. oasis, governance, Ökologie", aiApproveRatingLabel: "Bewertung", @@ -3465,17 +4654,15 @@ module.exports = { larpInviteBannerHint: "Teile diesen Code mit jemandem in ACADEMIA. Er läuft in 30 Zyklen oder nach einmaliger Nutzung ab.", larpTestRankingTitle: "Punktzahl nach Haus", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Haus-Einladungscode", - invitesHouseJoinButton: "Haus beitreten", - ecoTaxLabel: "ECO Tax", + ecoTaxLabel: "ECO-Steuer", bankTaxes: "Steuern", - bankOverviewYourTaxes: "Deine Steuern", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesArchTaxTitle: "ARCH Tax", + bankOverviewYourTaxes: "Steuern gesamt", + bankTaxesEcoTaxTitle: "ECO-Steuer", + bankTaxesArchTaxTitle: "ARCH-Steuer", bankTaxesUserAmount: "Deine ECO Tax (zu zahlender Beitrag)", bankTaxesArchTaxUserAmount: "Deine ARCH Tax (zu zahlender Beitrag)", bankTaxesArchTaxFirstBlock: "Alter deines ersten Blocks", - bankTaxesArchTaxRate: "Rate", + bankTaxesArchTaxRate: "Satz", bankTaxesTotalBlocks: "Blöcke gesamt", bankTaxesTotalBytes: "Bytes gesamt", bankTaxesTotalCarbon: "Kohlenstoff gesamt", @@ -3489,7 +4676,7 @@ module.exports = { bankTaxesTotalLifetime: "Total (Lebenszeit)", bankTaxesTotalAnnual: "Total (jährlich)", bankTaxesTotalMonthly: "Total (monatlich)", - bankTaxesRate: "Rate", + bankTaxesRate: "Satz", bankTaxesSpan: "Beobachtungszeitraum", bankTaxesLookupTitle: "Block inspizieren", bankTaxesLookupPlaceholder: "Block-ID (z. B. %abc...=.sha256)", @@ -3499,7 +4686,7 @@ module.exports = { bankTaxesLookupType: "Typ", bankTaxesLookupSize: "Größe", bankTaxesLookupCarbon: "CO₂-Fußabdruck", - bankTaxesLookupEcoin: "ECO Tax", + bankTaxesLookupEcoin: "ECO-Steuer", bankExchangeEcoTaxAnnual: "ECOin-Steuern (jährlich)", bankExchangeEcoTaxMonthly: "ECOin-Steuern (monatlich)", bankTaxesTypesLabel: "Wähle, welche Steuern du zahlen willst", @@ -3507,8 +4694,8 @@ module.exports = { bankRulesPoolTitle: "Monatlicher Pool", bankRulesShareTitle: "Anteil pro Nutzer", bankRulesKarmaTitle: "Karma (Engagement-Score)", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesArchTaxTitle: "ARCH Tax", + bankRulesEcoTaxTitle: "ECO-Steuer", + bankRulesArchTaxTitle: "ARCH-Steuer", bankRulesChipTitle: "ECO-Tax-Chip-Farbband", bankRulesEpochKind: "Epoch-Granularität", bankRulesAlpha: "Alpha (max. Anteil am Pub-Balance pro Epoch)", @@ -3519,17 +4706,16 @@ module.exports = { bankRulesWMax: "Höchstgewicht (w_max)", bankRulesFloor: "Sockel pro Nutzer (brutto)", bankRulesCapUser: "Obergrenze pro Nutzer pro Epoch", - bankRulesEcoTaxRate: "Rate", - bankRulesArchTaxRate: "Rate", + bankRulesEcoTaxRate: "Satz", + bankRulesArchTaxRate: "Satz", bankRulesCarbonFactor: "Kohlenstofffaktor", statsTombstoneEmpty: "Noch keine Tombstones im Netzwerk.", blockchainBlockNotAvailable: "Dieser Block ist in deinem lokalen Feed nicht verfügbar. Er gehört möglicherweise einem Peer, von dem du noch nicht replizierst.", blockchainBackToBlockexplorer: "Zurück zum Blockexplorer", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeDetailTitle: "Transcode", + audioTranscodeButton: "TRANSKODIEREN", + audioTranscodeDetailTitle: "Transkodieren", audioTranscodeDetailDescription: "Entschlüssle die eingebettete Nutzlast und die ursprüngliche Blockchain-Kompositionskarte.", - audioTranscodeStegoOasisId: "Von", audioTranscodeStegoTimestamp: "Erzeugt am", audioTranscodeStegoMessage: "TEXT", audioTranscodeStegoEmpty: "(keine)", @@ -3539,7 +4725,6 @@ module.exports = { audioBackToBcs: "Zurück zu BCS", melodyCompositionTitle: "Blockchain-Kompositionskarte", melodyFilterMine: "MEINS", - melodyByLabel: "Von", melodyAllEmpty: "Noch keine BCS-Kompositionen anderer Bewohner.", melodyNoteUnavailable: "Block ist nicht in deinem lokalen Feed", melodyUploadBcsTitle: "Veröffentlichen", @@ -3565,14 +4750,14 @@ module.exports = { larpBadgeYou: "Du", larpBadgeRuling: "Regiert", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Modul für die Live-Rollenspielschicht von Oasis (die 9 Häuser).", + modulesLarpDescription: "Modul für die Live-Rollenspiel-Ebene.", larpProfileQ1: "Wenn du vor einem komplexen Problem stehst, was tust du zuerst?", larpProfileQ1O1: "Mit anderen reden, um Konsens zu finden", larpProfileQ1O2: "Einen Prototyp zum Testen bauen", larpProfileQ1O3: "Die Literatur erforschen", larpProfileQ1O4: "Das System sabotieren, das es verursacht", larpProfileQ2: "Was gibt deiner täglichen Arbeit Sinn?", - larpProfileQ2O1: "Die Menschen verteidigen, die ich liebe", + larpProfileQ2O1: "Die Bewohner verteidigen, die ich liebe", larpProfileQ2O2: "Etwas Greifbares schaffen", larpProfileQ2O3: "Leben heilen oder nähren", larpProfileQ2O4: "Worte und Ideen formen", @@ -3639,7 +4824,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Wähle den UX-Navigationsmodus für deine Oberfläche.", uxModeMenus: "Blöcke", - uxModeAINav: "AI", + uxModeChats: "Unterhaltungen", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Trend-Tags", + feedActiveTitle: "Aktive Bewohner", + cnBrandSub: "Frei · P2P · Föderiert", + cnSyncedPeers: "Synchronisierte Peers", + cnLicense: "Lizenz", + cnNotAccessible: "Der Inhalt ist im Moment nicht erreichbar.", + cnUntitled: "Ohne Titel", + cnSearchPlaceholder: "Suchen…", + cnHubTitle: "Clearnet-HUB", + cnHubDescription: "Öffentliche Inhalte, geteilt von den Bewohnern dieses Oasis-Netzwerks.", + cnHubEmpty: "Es wurden noch keine öffentlichen Inhalte im Clearnet geteilt.", + cnPublicContent: "Inhalte", + cnTribesContent: "Stämme", + cnNetworkContent: "Netzwerk", + cnCategoryEmpty: "Keine Inhalte in dieser Kategorie.", + cnInhabitantEmpty: "Dieser Bewohner hat noch keine Inhalte im Clearnet veröffentlicht.", + cnPrevPage: "← Zurück", + cnNextPage: "Weiter →", + cnPageOf: "Seite {page} von {pages}", + cnMoreInfo: "Mehr Infos →", + cnOpenCourse: "Offener Kurs", + cnAuction: "AUKTION", + cnExchange: "TAUSCH", + cnRssDescription: "veröffentlicht von den Bewohnern dieses HUBs", + cnKindAudio: "Audio", + cnKindBlog: "Blog", + cnKindBookmark: "Lesezeichen", + cnKindDocument: "Dokument", + cnKindEvent: "Termin", + cnKindFeed: "Feed", + cnKindImage: "Bild", + cnKindJob: "Stelle", + cnKindMarket: "Markt", + cnKindPodcast: "Podcast", + cnKindProject: "Projekt", + cnKindCourse: "Kurs", + cnKindShop: "Shop", + cnKindTorrent: "Torrent", + cnKindFile: "Datei", + cnKindVideo: "Video", + cnKindWiki: "Wiki", + cnKindRoom: "Raum", + cnKindMap: "Karte", + uxModeAINav: "KI", } } \ No newline at end of file diff --git a/src/client/assets/translations/oasis_en.js b/src/client/assets/translations/oasis_en.js index 3ad88f5..32a6205 100644 --- a/src/client/assets/translations/oasis_en.js +++ b/src/client/assets/translations/oasis_en.js @@ -28,7 +28,7 @@ module.exports = { recent: "Recent", anonymous: "Anonymous", unnamed: "Anonymous", - authorLabel: "AUTHOR", + authorLabel: "Author", delete: "Delete", save: "Save", vote: "Vote", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Content warning", invalidPost: "Invalid content", invalidPostHint: "This message has invalid/empty text.", - spreadBy: "By", spreadChron: "Spread", spreaded: "Spread", spreadContentUnavailable: "Content not yet available (pending replication)", filteredByTag: "Filtered by tag", filteredByTagTitle: "Filtered by tag", feedPublishedSuccess: "Feed published successfully!", + feedMastodonFailed: "Published in Oasis, but it could not be sent to Mastodon.", tribeFeedSent: "Message sent successfully!", tribeContentEncrypted: "Encrypted content", tribeTorrentsEmpty: "No torrents, yet.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Last visit", profileClearnetBookmarksLabel: "Bookmarks", invitesPort: "Port", - currentlyUnrecheable: "ERROR!", - peerHostValidation: "Valid IPv4 (e.g. 192.168.1.100) or hostname (e.g. pub.example.com)", + peerHostValidation: "Valid IPv4 (e.g. 192.168.1.100) or hostname (e.g. pub.solarnethub.com)", peerPortValidation: "Port 1-65535", peerKeyValidation: "SSB ed25519 public key (@<44 chars base64>=.ed25519)", inboxJobUnsubscribedTitle: "Unsubscription from your job offer", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Task description", exportPasswordLabel: "Export password", importPasswordPlaceholder: "Enter password", - searchCasesPlaceholder: "Search cases…", + searchCasesPlaceholder: "Search in Courts...", shopBuyDeliveryAddressPlaceholder: "Delivery address", shopBuyNotesPlaceholder: "Notes", bankTaxesUserNoteBold: "reducing the ECO tax directly", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Support case", courtsSettlementAcceptBtn: "Accept settlement", courtsSupportCount: "Supporters", - courtsVerdictVoteTitle: "Vote on verdict", - courtsVerdictVoteLabel: "Verdict vote", - courtsVerdictVoteAccept: "Accept", - courtsVerdictVoteReject: "Reject", - courtsVerdictVoteSubmit: "Submit vote", reportsSetStatus: "Set status", reportsStatusClosed: "CLOSED", statsAvgPerInhabitant: "Avg per inhabitant", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Avatar", inhabitants: "Inhabitants", - peersReplicatedFeeds: "Replicated feeds", + peersReplicatedFeeds: "Replicated", + peersTorColumn: "TOR", + peersTorYes: "Reachable over Tor", + peersTorNo: "Not reachable over Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Version", graphos: "Graphos", graphosDescription: "Interactive map of the network around you.", + graphosFederation: "Federation", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Unreachable", + graphosFedLinks: "Mutual links", + graphosFedIsolated: "Isolated", + graphosFedNoData: "No data", + graphosFedMutual: "Mutual", + graphosFedOneWay: "One way", + graphosFedTransport: "Network", + graphosFedWith: "Federated with", + graphosZoom: "Enlarge", + graphosFedInhabitants: "Inhabitants", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "Your node does not know any PUB yet.", graphosViewingGraphOf: "Viewing", graphosBackToMine: "← My network", graphosShowing: "Showing", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "You haven't received @mentions, yet.", - mentionsSearchPlaceholder: "Search mentions...", + mentionsSearchPlaceholder: "Search in Mentions...", privateReminders: "REMINDERS", + inboxFilterPms: "PMs", + inboxFilterNotifications: "Notifications", + inboxMarkRead: "Mark as Read", + inboxMarkUnread: "Mark as Unread", + inboxUnreadChip: "Unread", + inboxMarkAllRead: "Mark all as Read", + inboxDeleteShown: "Delete all", + inboxArchive: "Archive", + inboxUnarchive: "Unarchive", + inboxFilterArchived: "Archived", + inboxEarlierMessages: "earlier messages", + inboxSortThread: "By thread", + inboxSortRecent: "Newest first", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Inbox Notifications", + settingsInboxBotsDesc: "Choose which bots can notify you in your Inbox. Unchecked bots are hidden and their notices are not counted.", inboxFiltersLabel: "Filters:", inboxToggleToMutuals: "Switch to mutuals", inboxToggleToWhole: "Switch to whole", @@ -212,6 +242,7 @@ module.exports = { pmReply: "Reply", pmReplies: "replies", inReplyTo: "IN REPLY TO", + forumInThread: "IN THREAD", pmPreview: "Preview", pmPreviewTitle: "Message preview", peers: "Peers", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "From", searchToLabel: "To", searchQueryLabel: "Query", - searchPerPageLabel: "Results per page", + searchPerPageLabel: "Results", settings: "Settings", + settingsDescription: "Set your network environment.", continueReading: "Continue reading", moreComments: "more comment", readThread: "read the rest of the thread", @@ -244,7 +276,6 @@ module.exports = { blockchainBackToBlockexplorer: "Back to blockexplorer", melodyUploadBcsNameNote: "The audio will be published with the auto-generated name", melodyFilterMine: "MINE", - melodyByLabel: "By", melodyAllEmpty: "No BCS compositions from other inhabitants yet.", profileContentSectionTitle: 'Avatar Content', profileContentHelp: 'Choose which of your modules will be displayed on your avatar.', @@ -252,7 +283,6 @@ module.exports = { profileClearnetHelp: 'Modules that can be accessed from outside Oasis.', profileHubAll: 'All', footerLicenseLabel: 'License', - profileSwitchToOasis: 'Return to Oasis', profileVisibilityFediverse: "Multiverse", contributorsTitle: "Contributors", colorLabel: 'Pick a color', @@ -262,6 +292,7 @@ module.exports = { modulesViewTitle: "Modules", modulesViewDescription: "Set your environment by enabling or disabling modules.", inbox: "Inbox", + inboxSearchPlaceholder: "Search in Inbox...", multiverse: "Multiverse", fediverse: "Multiverse", fediverseDescription: "Manage your other multiverse accounts, including sending and receiving content.", @@ -270,9 +301,17 @@ module.exports = { fediverseSettingsTitle: "Multiverse", fediverseInstanceLabel: "Address", fediverseTokenLabel: "Access token", - fediverseTokenHelp: "Set your access token. This will be used to manage your Mastodon account from inside Oasis.", fediverseConnect: "Connect it", fediverseConnectedAs: "Connected as", + nextcloudServerLabel: "Server", + nextcloudUserLabel: "User", + nextcloudFolderLabel: "Folder", + nextcloudUploadButton: "Upload", + nextcloudFileLabel: "Select a file", + fileExternalLink: "External link", + nextcloudErrConnect: "Nextcloud could not be reached with these details.", + nextcloudErrUpload: "The upload to Nextcloud failed.", + nextcloudErrShare: "Nextcloud did not give a public link for the file.", fediverseDisconnect: "Disconnect", fediverseEmptyLink: "your settings", fediverseComposePlaceholder: "What's on your mind?", @@ -301,6 +340,76 @@ module.exports = { fediverseErrPost: "Could not publish.", fediverseErrMedia: "Could not upload the file.", fediverseErrEmpty: "Write something or attach a file.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Phone number", + telegramSendCode: "Send code", + telegramCodeHelp: "Enter the login code Telegram just sent you (app or SMS).", + telegramCodeLabel: "Login code", + telegramPasswordHelp: "This account has two-step verification. Enter your cloud password.", + telegramPasswordLabel: "Password", + telegramVerify: "Verify", + telegramCancel: "Cancel", + telegramChats: "Chats", + telegramUnread: "Unread", + telegramOpen: "Open", + telegramSend: "Send", + telegramYou: "You", + telegramNoChats: "No chats yet.", + telegramNoMessages: "No messages yet.", + telegramComposePlaceholder: "Write a message…", + telegramChannel: "Channel", + telegramGroup: "Group", + telegramPrivate: "Private", + telegramErrConnect: "Could not contact Telegram.", + telegramErrTimeout: "Telegram took too long to respond.", + telegramErrAuth: "Telegram session invalid or expired. Connect again.", + telegramErrFetch: "Could not load chats.", + telegramErrSend: "Could not send the message.", + telegramErrMissing: "API ID, API hash and phone number are required.", + telegramErrCode: "Invalid code. Try again.", + telegramErrCodeExpired: "The code expired. Start again.", + telegramErrPassword: "Wrong password.", + telegramErrPhone: "Invalid phone number.", + telegramErrApi: "Invalid API ID or API hash.", + telegramErrFlood: "Too many attempts. Wait a while and try again.", + telegramErrNoLogin: "No login in progress. Start again.", + peertubeUsernameLabel: "Username", + peertubePasswordLabel: "Password", + peertubeSubscriptions: "Subscriptions", + peertubeMyVideos: "My videos", + peertubeVideos: "Videos", + peertubeFollowers: "Followers", + peertubeViews: "views", + peertubeLikes: "likes", + peertubeQuota: "Quota", + peertubeUpload: "Upload video", + peertubeUploadHelp: "Pick a video file. It is uploaded to your channel and PeerTube processes it afterwards.", + peertubeTitleLabel: "Title", + peertubeDescriptionLabel: "Description", + peertubePrivacyLabel: "Privacy", + peertubePrivacyPublic: "Public", + peertubePrivacyUnlisted: "Unlisted", + peertubePrivacyPrivate: "Private", + peertubeNoVideos: "No videos yet.", + peertubeComments: "Comments", + peertubeComment: "Comment", + peertubeCommentPlaceholder: "Write a comment…", + peertubeNoComments: "No comments yet.", + peertubeOpen: "Open on PeerTube", + peertubeWatch: "Watch", + peertubeChannel: "Channel", + peertubeNoStream: "This video cannot be played here yet. Open it on PeerTube.", + peertubeUploaded: "Video uploaded. PeerTube is processing it.", + peertubeErrConnect: "Could not contact PeerTube.", + peertubeErrAuth: "Wrong username or password, or the session expired. Connect again.", + peertubeErrInstance: "Invalid instance URL.", + peertubeErrMissing: "Instance, username and password are required.", + peertubeErrFetch: "Could not load videos.", + peertubeErrUpload: "Could not upload the video.", + peertubeErrTitle: "Title required.", + peertubeErrComment: "Could not publish the comment.", + peertubeErrNoFile: "Choose a video file.", bookmarksLabel: "Bookmarks", videosLabel: "Videos", torrentsLabel: "Torrents", @@ -323,7 +432,6 @@ module.exports = { comment: "Comment", subtopic: "Subtopic", json: "JSON", - createdBy: "by", unfollow: "Unsupport", follow: "Support", block: "Block", @@ -339,12 +447,15 @@ module.exports = { relationshipNone: "You are not supporting", relationshipBlockingPost: "Blocked post", viewLikes: "View spreads", + viewSupporters: "View supporters", spreadedDescription: "List of posts spread by the inhabitant.", preview: "Preview", publish: "Write", contentWarningPlaceholder: "Add a subject to the post (optional)", - publishWarningPlaceholder: "Body capped at 7000 characters.", + publishWarningPlaceholder: "Write the body of your entry.", publishTooLong: "Your post is too long. Please shorten it.", + keepTextRetry: "Send again", + dateInPastError: "The date cannot be in the past", publishCustomDescription: [ "REMEMBER: Due to blockchain technology, once a post is published it cannot be edited or deleted.", ], @@ -374,9 +485,8 @@ module.exports = { mentionsMatching: "Matching Mentions", mentionsName: "Name", mentionsRelationship: "Relationship", - updateit: "GET UPDATES!", updateBannerText: "A new version of Oasis is available.", - updateBannerAction: "Update now →", + updateBannerAction: "Update now!", info: "Info", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -384,22 +494,28 @@ module.exports = { timeAgo: "ago", sendTime: "about ", theme: "Theme", - legacy: "Keys", - legacyTitle: "Keys", - legacyDescription: "Manage your secret (private key) quickly and safely.", - legacyExportButton: "Export", - legacyImportButton: "Import", - ssbLogStream: "Blokchain", + backupKeysExportButton: "Export", + backupKeysImportButton: "Import", + ssbLogStream: "Blockchain", ssbLogStreamDescription: "Configure the message limit for Blockchain streams.", + logWindowChip: "Last {n} blockchain streams", settingsLanTitle: "LAN Broadcasting", settingsLanDesc: "Periodically announce this peer to other Oasis instances on the same local network. Disable to stop UDP broadcasts.", settingsReplicationTitle: "Replication", settingsReplicationDesc: "Configure the number of hops your peer follows out from your own feed when replicating content.", settingsReplicationHopsLabel: "Hops", + settingsBlobCacheTitle: "Media cache", + settingsBlobCacheDesc: "Content from other inhabitants is kept on this device. Choose how much space it may take.", + settingsBlobCacheUsage: "Current usage", + settingsBlobCacheFiles: "files", + settingsBlobCacheLimit: "Limit", + settingsBlobCacheUnlimited: "Unlimited", + settingsBlobCacheCleanNow: "Clean now", + settingsBlobCacheCleaned: "{n} files removed, {size} freed.", settingsLanEnable: "Enable LAN broadcasting", saveSettings: "Save settings", exportDescription: "Set password (min 32 characters long) to encrypt your key", - exportDataTitle: "Backup", + exportDataTitle: "Instant Backup", exportDataDescription: "Download your data (secret key excluded!)", exportDataButton: "Download database", importDescription: "Import your encrypted secret (private key) to enable your avatar", @@ -408,38 +524,33 @@ module.exports = { fileInfo: "Your encrypted secret key will be downloaded to your device (name: oasis.enc)", developer: "Developer", devTitle: "Developer", - welcomeBannerText: "Welcome to OASIS. Follow these quick steps to set up your avatar.", + welcomeBannerText: "Your avatar is not set up yet.", aiSuggestionBanner: "Suggestion:", aiSuggestionsEnable: "AI Suggestions", - welcomeBannerAction: "Start wizard!", + welcomeBannerAction: "Start", welcomeTitle: "Welcome", + welcomeStepIdentity: "Identity", + welcomeStepType: "Type", + welcomeStepUse: "Use", + welcomeStepLook: "Appearance", + welcomeStepCommunity: "Community", + welcomeStepIntro: "Welcome", + welcomeHintIdentity: "Your name and picture.", + welcomeHintType: "How Oasis opens for you.", + welcomeHintUse: "Which modules stay on.", + welcomeHintLook: "The theme Oasis wears.", + welcomeHintCommunity: "Join a PUB to meet other inhabitants.", + welcomeHintIntro: "Send a first greeting to the community.", + welcomeStepThemeAction: "Use this theme", + welcomeJoinPubs: "Join PUBs", + welcomeNoFederation: "No federation", welcomeStepGreetingAction: "Send Feed", - welcomeJoinPubButton: "Join Pub", - welcomeStepLarpAction: "Join \"The Academy\"", - welcomeStepLarpText: "Join our live action role playing game.", - welcomeStepLarpTitle: "Join L.A.R.P.", - welcomeStepGreetingTitle: "Send a \"Hello world!\"", - welcomeStepGreetingText: "Send a message so other inhabitants can discover you.", - welcomeJoinPub: "Join", welcomeDescription: "A few things worth doing before you start.", - welcomeProgress: "Completed", - welcomeStepDone: "done", - welcomeStepPending: "pending", welcomeSkip: "Skip for now", - welcomeStepLanguageTitle: "Choose your language", - welcomeStepLanguageText: "Oasis speaks different languages. You can change it whenever you want.", - welcomeStepProfileTitle: "Edit your profile", - welcomeStepProfileText: "Pick a name, add a description and set a picture so other inhabitants can recognise you.", welcomeStepProfileAction: "Save Profile", - welcomeStepFederationTitle: "Join Main Network", - welcomeStepFederationText: "Connect to our pub to meet other inhabitants and start replicating.", - welcomeStepFederationAction: "Go to invites", - welcomeStepBackupTitle: "Backup your ID", - welcomeStepBackupText: "Your identity is a key file on this device.", - welcomeStepBackupWarning: "IF YOU LOSE IT, NOBODY CAN RECOVER IT FOR YOU.", - welcomeStepBackupAction: "Backup!", + welcomeBootstrapRunning: "Loading the pub's snapshot. The pub is joined as soon as it finishes; you can keep using Oasis meanwhile.", welcomeCompleteTitle: "All set.", - welcomeCompleteText: "Your node is ready. From here the network grows with the people you follow.", + welcomeCompleteText: "Your node is ready. From here the network grows with the inhabitants you support.", welcomeFindPeople: "Find inhabitants", devFilterFiles: "FILES", devFilterSearch: "SEARCH", @@ -449,7 +560,7 @@ module.exports = { devFilterTests: "TESTS", devVersion: "Version", devNodeVersion: "Node", - devStatsTests: "Test suites", + devStatsTests: "Tests", devParentFolder: "Parent folder", devOpenFolder: "open", devDescription: "Explore the source code running on this node.", @@ -493,6 +604,27 @@ module.exports = { "If you'd like to use another language, select it here.", setLanguage: "Set Language", peerConnectionsIntro: "Manage all your connections with other peers.", + peersConnectFailed: "Could not connect: {reason}.", + peerStateLabel: "State", + peerAddressLabel: "Address", + peerLastChangeLabel: "Last change", + peerStateConnected: "Connected", + peerStateConnecting: "Connecting", + peerStateStaged: "Available", + peerErrSilent: "No activity", + peerErrTimeout: "Not responding", + peerErrRefused: "Refuses the connection", + peerErrNotFound: "Unknown address", + peerErrUnreachable: "Network unreachable", + peerErrKeys: "Using different keys", + peerErrIdentity: "Does not accept your connection", + peerErrTor: "TOR only", + peerErrDropped: "Connection dropped", + peerErrPaused: "Network paused", + peerErrOther: "Connection error", + peerOutdated: "Outdated", + peerVersionUnannounced: "It does not announce its version: an old Oasis", + peersTorMissing: "This PUB only allows connections through TOR.", peerConnectionsTitle: "Connections", online: "Online", offline: "Offline", @@ -516,17 +648,20 @@ module.exports = { peerDisconnect: "Disconnect", peerRefresh: "Refresh", peerPruneIdle: "Remove idle", + peersPause: "Pause", + peersResume: "Resume", + peersPausedNote: "Paused: Oasis does not connect to anyone and nobody can connect to it.", + peersPauseHint: "Stops every connection: Oasis neither connects to other nodes nor accepts them until you resume.", peerExport: "Export", peerImport: "Import", peerImportTitle: "Import peer list", peerImportPlaceholder: "Paste one multiserver address per line, or upload a .txt file…", connectionActionIntro: "", - startNetworking: "Start networking", - stopNetworking: "Stop networking", - restartNetworking: "Restart networking", - sync: "Sync network", indexes: "Indexes", + indexesRebuilt: "Indexes rebuilt", + indexesRebuildFailed: "Could not rebuild the indexes", + indexesFiles: "Index files", indexesDescription: "Rebuilding your indexes is safe, and may fix some types of bugs.", homePageTitle: "Home", @@ -536,44 +671,33 @@ module.exports = { invitesDescription: "Manage and apply invite codes in your network.", invitesPubsHint: "Paste a pub invite code to federate with it and start replicating its inhabitants.", invitesFederationsHint: "Pubs you are federated with: refresh them, drop the unreachable ones or export the list.", - invitesHousesHint: "Enter a house code to join a LARP house and take part in its economy.", - invitesTribesHint: "Enter a tribe code to become a member and reach its private content.", - invitesChatsHint: "Enter a chat code to join the conversation and its threads.", - invitesPadsHint: "Enter a pad code to write together on a shared document.", - invitesCalendarsHint: "Enter a calendar code to share dates and reminders with others.", - invitesEventsHint: "Enter an event code to join a private event.", - invitesForumsHint: "Enter a forum code to read and reply in a private forum.", - invitesMapsHint: "Enter a map code to add and see places on a shared map.", - invitesShopsHint: "Enter a shop code to join a shop and its catalogue.", - invitesInhabitantsHint: "Enter an inhabitant code to follow each other and exchange content.", invitesTribesTitle: "Tribes", - invitesTribeInviteCodePlaceholder: "Enter tribe invite code", - invitesTribeJoinButton: "Join Tribe", invitesChatsTitle: "Chats", - invitesChatInviteCodePlaceholder: "Enter chat invite code", - invitesChatJoinButton: "Join Chat", invitesPadsTitle: "Pads", - invitesPadInviteCodePlaceholder: "Enter pad invite code", - invitesPadJoinButton: "Join Pad", invitesCalendarsTitle: "Calendars", - invitesCalendarInviteCodePlaceholder: "Enter calendar invite code", - invitesCalendarJoinButton: "Join Calendar", invitesEventsTitle: "Events", - invitesEventInviteCodePlaceholder: "Enter event invite code", - invitesEventJoinButton: "Join Event", invitesForumsTitle: "Forums", - invitesForumInviteCodePlaceholder: "Enter forum invite code", - invitesForumJoinButton: "Join Forum", invitesMapsTitle: "Maps", - invitesMapInviteCodePlaceholder: "Enter map invite code", - invitesMapJoinButton: "Join Map", invitesShopsTitle: "Shops", - invitesShopInviteCodePlaceholder: "Enter shop invite code", - invitesShopJoinButton: "Join Shop", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "Suggested PUBs", + invitesFilterOthers: "Others", + invitesJoinTitle: "Invitations", + invitesJoinHint: "Paste any invitation code, whatever it is for, or an inhabitant's Oasis ID.", + invitesJoinPlaceholder: "Invitation code or Oasis ID", + invitesJoinButton: "Join", + invitesUsedTitle: "Latest invitations", + invitesUsedEmpty: "You have not used any invitation yet.", + invitesUsedType: "Type", + invitesUsedName: "Name", + invitesUsedDate: "Date", invitesPubInviteCodePlaceholder: "Enter PUB invite code", invitesAcceptInvite: "Join PUB", + invitesPubUses: "Invitations used", + invitesPubExhausted: "This PUB has no invitations left.", invitesAlreadyFederated: "You are already federated with this pub.", + invitesJoinPubMode: "This Oasis runs as a PUB, in read-only mode. To join it to another PUB, use the terminal: ./oasis.sh join ", + invitesJoinLocalOnly: "Invites can only be accepted from the device where Oasis runs, or through the admin link it shows when it starts.", invitesFederationsTitle: "Federations", invitesAcceptedInvites: "Federated", invitesUnfollow: "Unfollow", @@ -581,16 +705,12 @@ module.exports = { invitesUnfollowedInvites: "Unfederated", invitesNoFederatedPubs: "No federated pubs.", invitesNoUnfollowed: "No unfederated pubs.", - invitesUnreachablePubs: "Unreachable", - invitesNoUnreachablePubs: "No unreachable networks.", invitesPubsRefresh: "Refresh", - invitesPubsClearUnreachable: "Remove unreachable", + invitesPubsClearUnreachable: "Clean", invitesPubsExport: "Export", invitesPubsImport: "Import", invitesPubsImportTitle: "Import pubs", invitesPubsImportPlaceholder: "Paste one multiserver address or invite code per line, or upload a .txt file…", - currentlyUnreachable: "ERROR!", - errorDetails: "Error Details", genericError: "An error occurred.", panicMode: "Panic Mode!", encryptData: "Set password (min 32 characters long) to encrypt your blockchain", @@ -617,7 +737,6 @@ module.exports = { votesOptionNoLabel:"No", votesOptionAbstentionLabel:"Abstention", votesQuestionLabel:"Question", - votesCreatedByLabel:"Created by", voteStatusOpen:"OPEN", voteStatusClosed:"CLOSED", commentDescription: ({ parentUrl }) => [ @@ -641,17 +760,16 @@ module.exports = { oasisIdLabel: "OasisID", spreadContent: "Replicate", welcomePmSubject: "Hello World!", - welcomePmBody: "Welcome to Oasis — a libre, peer-to-peer, federated and encrypted social network with a distributed invite-only architecture.\n\nSome interesting places to start:\n\n- /profile — Edit your avatar, name, description and which modules appear on your public side.\n- /settings — Tune your local instance: language, theme, modules, privacy and sync.\n- /invites — Add a pub to federate with the wider network.\n- /peers — See who is connected, rescan your LAN, export or import peer lists.\n- /inhabitants — Discover and visit other people's avatars.\n- /tribes — Create or join private groups with end-to-end encryption.\n- /inbox — Read and send private messages.\n- /banking — See your ECO balance, UBI, ECO tax and the wealth-redistribution rules.\n- /activity — See recent activity, yours and your network's.\n- /publish — Write a post or share an image, audio, video or document.\n- /search — Search the network's content by type.\n\nSome interesting places to connect:\n\n- /multiverse — Manage your other multiverse accounts, including sending and receiving content.\n\nThis message was sent privately from you to yourself, so no connection was required to receive it.", + welcomePmBody: "Welcome to Oasis — a libre, peer-to-peer, federated and encrypted social network with a distributed invite-only architecture.\n\nSome interesting places to start:\n\n- [/avatar](/profile) — Edit your avatar, name, description and which modules appear on your public side.\n- /settings — Tune your local instance: language, theme, modules, privacy and sync.\n- /invites — Add a pub to federate with the wider network.\n- /peers — See who is connected, rescan your LAN, export or import peer lists.\n- /inhabitants — Discover and visit other inhabitants' avatars.\n- /tribes — Create or join private groups with end-to-end encryption.\n- /search — Search the network's content by type.\n\nSome interesting places to connect:\n\n- /multiverse — Manage your other multiverse accounts, including sending and receiving content.\n\nThis message was sent privately from you to yourself, so no connection was required to receive it.", indexingTitle: "Synchronizing", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingEditProfileLink: "Set up my avatar", + indexingMessage: "Oasis is synchronizing the distributed network of nodes. Please wait.", profileSensorsSectionTitle: "Sensors", profileVisibilityActivity: "Activity Level", profileDeviceLockedHint: "This sensor is always on: it lets other users see which device you connect from and cannot be disabled.", profileVisibilityKarma: "KARMA Scoring", profileVisibilityUbi: "UBI", - profileVisibilityWallet: "ECOIN Wallet", + profileVisibilityWallet: "ECOin Wallet", profileVisibilityEcoTax: "ECO Tax", profileVisibilityLarpSign: "L.A.R.P. Sign", profileVisibilityGpg: "GPG Key", @@ -666,6 +784,10 @@ module.exports = { profileClearnetImagesLabel: "Images", profileClearnetDocumentsLabel: "Documents", profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Market", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", editProfileDescription: "", profileName: "Name", @@ -691,7 +813,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Cnfrs", walletConfirm: "Confirm", - walletDescription: "Manage your digital assets, including sending and receiving ECOin, viewing your balance, and accessing your transaction history.", + walletDescription: "Manage your ECOin wallet.", walletDate: "Date", walletFee: "Fee (The higher the fee, the faster your transaction will be processed)", walletFeeLine: ({ fee }) => `Fee: ECO ${fee}`, @@ -699,6 +821,86 @@ module.exports = { walletReceive: "Receive", walletReset: "Reset", walletSend: "Send", + walletPayingTransfer: "Paying transfer", + walletCreateTransferLabel: "Generate smart-contract (transfer)", + walletTransferNotifiedNote: "The other party has been notified of this payment", + walletTransferCreatedNote: "A transfer (receipt) has been created for this payment", + walletTransferNoPayeeNote: "No inhabitant has published this address, so no transfer was created", + walletTransferReceiptConcept: "Wallet payment", + walletPaymentPmText: "sent you a payment", + walletPayeeLabel: "Recipient", + walletConceptLabel: "Concept", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "You have received your UBI.", + bankingBotPaymentTitle: "You have received a payment.", + bankingBotUbiPaidText: "Your Universal Basic Income has been paid", + bankUbiPubThisNode: "This node pays the UBI", + bankUbiPubConnected: "UBI PUB", + bankUbiPubNone: "No PUB is announcing UBI in your network yet", + bankUbiAddressPublished: "ECOin address published", + bankUbiAddressMissing: "Publish your ECOin address to receive UBI", + bankUbiClaimedThisMonth: "Claimed this month", + bankUbiPubBalance: "PUB wallet available for UBI", + bankNoPubAddress: "The UBI PUB has not published an ECOin address yet.", + bankUbiTab: "UBI", + bankDonateEco: "DONATE!", + donateTooltip: "Donate ECOin", + bankNoUserAddress: "This inhabitant has not shared an ECOin address.", + bankUbiPubDefault: "Default", + bankUbiPool: "Pool", + bankUbiLastSeen: "Last seen", + bankUbiLastPayout: "Last action", + bankUbiPaidOut: "Paid out", + bankRangeToday: "Today", + bankRangeWeek: "Week", + bankRangeMonth: "Month", + bankRangeYear: "Year", + bankRangeFiveYears: "5 years", + bankRangeAll: "All", + bankChartValueTitle: "ECOin Value Evolution", + bankChartValueHours: "Equivalence in time (h)", + bankChartWealthTitle: "ECOin Wealth Redistribution", + bankChartDistributed: "UBI distributed (ECO)", + bankChartRebalanceTitle: "Redistribution of wealth between PUBs", + bankChartRebalanced: "ECO moved between PUBs", + bankChartTaxes: "Taxes withheld (ECO)", + bankChartNoDataRange: "No data in this range.", + bankChartNoDataYet: "No data available, yet", + bankChartPerHour: "per hour", + bankChartPerDay: "per day", + bankChartPerMonth: "per month", + bankTotalUbiDistributed: "Total UBI redistributed (network)", + bankTotalTaxesWithheld: "Total taxes withheld (network)", + bankInflationIssuance: "Issuance rate (annual, from block reward)", + bankRulesEligibilityTitle: "Who can receive the UBI", + bankRulesEligibility1: "An ECOin address published in your profile (Profile → Sensors → ECOin Wallet).", + bankRulesEligibility2: "A feed older than 30 days with activity in the network: karma above zero.", + bankRulesEligibility3: "One claim per month (epoch), made from Banking with the Claim UBI button. It can be refused for the current month with Refuse UBI.", + bankRulesEligibility4: "The floor of 1 ECO is always paid; taxes only reduce the part above it and are never charged apart.", + bankRulesEligibility5: "The PUB pays on its next cycle and publishes a UBI transfer that you can confirm.", + bankRulesEligibility6: "If your address is missing, the feed is too young, you refused the month or the PUB has no funds, the claim is skipped and logged on the PUB.", + copyLinkTitle: "Copy link", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Refuse UBI", + bankUbiRefusedThisMonth: "You refused this month's UBI.", + bankingBotUbiAvailableTitle: "You can claim your UBI.", + bankingBotUbiAvailableText: "Your UBI for this month is available", + bankingBotConfirmTitle: "You have a smart-contract awaiting confirmation", + bankingBotConfirmText: "Smart-contracts awaiting your confirmation", + bankChartZoomHint: "Click to enlarge", + bankChartPoolTitle: "UBI Pool (over time)", + bankChartNetworkUbiTitle: "Total UBI redistributed (network)", + bankChartSupplyTitle: "ECOin Current Supply (over time)", + bankChartInflationTitle: "ECOin Inflation (over time)", + bankChartInflationSeries: "Annualised rate (%)", + bankChartPool: "Total Pool (ECO)", + bankChartKarmaTitle: "KARMA Scoring (over time)", + bankChartFundsTitle: "Your ECOin funds (over time)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin connected", + bankWalletNotConfigured: "ECOin disconnected", + transfersPayWithWallet: "PAY!", walletStatus: "Status", walletDisconnected: [ "ECOin ", @@ -708,8 +910,11 @@ module.exports = { " or connection status.", ], walletSentToLine: ({ destination, amount }) => `Sent ECO ${amount} to ${destination}`, + walletNotConfigured: "Your ECOin wallet is not configured yet.", walletSettingsDescription: "Integrate Oasis with your ECOin wallet.", - walletSettingsDocLink: "ECOin installation guide", + walletSettingsMobileDescription: "Link Oasis with the ECOin Wallet app on this device.", + walletDownloadEcoin: "Download ECOin!", + walletRpcUrl: "RPC URL", walletStatusMessages: { invalid_amount: "Invalid amount", invalid_dest: "Invalid destination address", @@ -725,6 +930,7 @@ module.exports = { walletUser: "Username", walletPass: "Password", walletConfiguration: "Set wallet", + walletDisconnectButton: "Disconnect wallet", cipher: "Crypter", cipherTitle: "Crypter", cipherDescription: "Encrypt and decrypt your text symmetrically (using a shared password).", @@ -758,17 +964,16 @@ module.exports = { bookmarkFilterRecent: "RECENT", bookmarkCreateButton: "Create Bookmark", bookmarkUpdateButton: "Update", - bookmarkDeleteButton: "Delete", bookmarkAddFavoriteButton: "Add favorite", bookmarkRemoveFavoriteButton: "Remove favorite", bookmarkUrlLabel: "Link", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Description", bookmarkDescriptionPlaceholder: "Optional", bookmarkTagsLabel: "Tags", bookmarkTagsPlaceholder: "Enter tags separated by commas", bookmarkLastVisitLabel: "Last Visit", - bookmarkSearchPlaceholder: "Search URL, tags, category, author...", + bookmarkSearchPlaceholder: "Search in Bookmarks...", bookmarkSortRecent: "Most recent", bookmarkSortOldest: "Oldest", bookmarkSortTop: "Most voted", @@ -807,7 +1012,7 @@ module.exports = { videoDescriptionPlaceholder: "Optional", videoNoFile: "No video file provided", noVideos: "No videos available.", - videoSearchPlaceholder: "Search title, tags, author...", + videoSearchPlaceholder: "Search in Videos...", videoSortRecent: "Most recent", videoSortOldest: "Oldest", videoSortTop: "Most voted", @@ -830,7 +1035,6 @@ module.exports = { documentFilterFavorites: "FAVORITES", documentCreateButton: "Upload Document", documentUpdateButton: "Update", - documentDeleteButton: "Delete", documentAddFavoriteButton: "Add favorite", documentRemoveFavoriteButton: "Remove from favorites", documentMessageAuthorButton: "PM", @@ -843,7 +1047,7 @@ module.exports = { documentDescriptionPlaceholder: "Optional", documentNoFile: "No file.", noDocuments: "No documents available.", - documentSearchPlaceholder: "Search title, tags, description, author...", + documentSearchPlaceholder: "Search in Documents...", documentSortRecent: "Most recent", documentSortOldest: "Oldest", documentSortTop: "Most voted", @@ -890,7 +1094,7 @@ module.exports = { bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", bankTaxesUserAmount: "Your ECO tax (price to return)", - bankOverviewYourTaxes: "Your Taxes", + bankOverviewYourTaxes: "Total Taxes", bankTaxesTotalBlocks: "Total blocks", bankTaxesTotalBytes: "Total bytes", bankTaxesTotalCarbon: "Total carbon", @@ -942,7 +1146,6 @@ module.exports = { bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", statsTombstoneEmpty: "No tombstones in the network yet.", - audioTranscodeStegoOasisId: "By", audioTranscodeStegoTimestamp: "Generated at", audioTranscodeStegoMessage: "TEXT", audioTranscodeStegoEmpty: "(none)", @@ -952,19 +1155,19 @@ module.exports = { audioBackToBcs: "Back to BCS", audioCreateButton: "Upload Audio", audioUpdateButton: "Update", - audioDeleteButton: "Delete", audioAddFavoriteButton: "Add favorite", audioRemoveFavoriteButton: "Remove favorite", audioFileLabel: "Select an audio file (.mp3, .wav, .ogg)", audioTagsLabel: "Tags", audioTagsPlaceholder: "Enter tags separated by commas", + tagsPlaceholder: "Enter tags separated by commas", audioTitleLabel: "Title", audioTitlePlaceholder: "Optional", audioDescriptionLabel: "Description", audioDescriptionPlaceholder: "Optional", audioNoFile: "No audio file provided", noAudios: "No audios available.", - audioSearchPlaceholder: "Search title, tags, author...", + audioSearchPlaceholder: "Search in Audios...", audioSortRecent: "Most recent", audioSortOldest: "Oldest", audioSortTop: "Most voted", @@ -976,7 +1179,7 @@ module.exports = { audioFilterFavorites: "FAVORITES", favoritesTitle: "Favorites", favoritesDescription: "All your favorited content in one place.", - favoritesSearchPlaceholder: "Search favorites...", + favoritesSearchPlaceholder: "Search in Favorites...", favoritesFilterAll: "ALL", favoritesFilterRecent: "RECENT", favoritesFilterAudios: "AUDIOS", @@ -988,13 +1191,12 @@ module.exports = { favoritesFilterChats: "CHATS", favoritesFilterCalendars: "CALENDARS", favoritesFilterVideos: "VIDEOS", - favoritesRemoveButton: "Remove from favorites", favoritesNoItems: "No favorites yet.", yourContacts: "Your Contacts", allInhabitants: "Inhabitants", allCVs: "CVs", discoverPeople: "Discover inhabitants in your network.", - searchInhabitantsPlaceholder: "FILTER inhabitants BY NAME …", + searchInhabitantsPlaceholder: "Search in Inhabitants...", filterLocation: "FILTER inhabitants BY LOCATION …", filterLanguage: "FILTER inhabitants BY LANGUAGE …", filterSkills: "FILTER inhabitants BY SKILLS …", @@ -1018,7 +1220,6 @@ module.exports = { topSectionTitle: "TOP", blockedLabel: "Blocked User", inhabitantviewDetails: "View Details", - cvVisitButton: "Visit CV", keepReading: "Keep reading...", oasisId: "ID", noInhabitantsFound: "No inhabitants found, yet.", @@ -1036,6 +1237,18 @@ module.exports = { parliamentFilterLeaders: "LEADERS", parliamentFilterRules: "RULES", parliamentGovernmentCard: "Current Government", + parliamentSeatsGovernment: "Government", + parliamentSeatsOpposition: "Opposition", + parliamentSeatsAbstention: "Abstention", + parliamentSeatsQuorum: "Election quorum", + parliamentSeatsPass: "Approval threshold", + parliamentSeatsByElection: "By election", + parliamentSeatsByHouses: "By LARP house", + parliamentSeatsSeats: "seats", + parliamentSeatsSeat: "seat", + parliamentSeatsLeader: "Leader", + parliamentSeatsVotesFor: "votes for", + parliamentSeatsNoHouse: "No house", parliamentActorInPowerInhabitant: 'INHABITANT RULING', parliamentActorInPowerTribe: 'TRIBE RULING', parliamentHistoricalGovernmentsTitle: 'GOVERMENTS', @@ -1103,6 +1316,7 @@ module.exports = { parliamentTimeRemaining: "Time remaining", parliamentElectionsStatusTitle: "Next Government", parliamentProposalDeadlineLabel: "Deadline", + parliamentViewReport: "View Report", parliamentProposalTimeLeft: "Time left", parliamentRulesTitle: "How Parliament works", parliamentRulesIntro: "Election resolve every 2 months; candidatures are continuous and reset when a government is chosen.", @@ -1117,6 +1331,7 @@ module.exports = { parliamentRulesLaws: "When a proposal meets its threshold, it becomes a Law and appears in the Laws tab with its enactment date.", parliamentRulesHistorical: "In the Historical tab you can see every government cycle that has occurred and data about its management.", parliamentRulesLeaders: "In the Leaders tab you can see a ranking of inhabitants/tribes that have governed (or stood as candidates), ordered by efficiency.", + parliamentRulesHemicycle: "The hemicycle gives one seat per inhabitant: the government bloc (the winning candidature's voters and the leader), one opposition bloc per other candidature of the cycle, and abstention. The dashed marks show the election quorum (25% of the census) and the approval threshold of the current method. \"By LARP house\" colours the same seats by house.", parliamentProposalVoteStatusLabel: "Vote status", parliamentProposalOnTrackYes: "Threshold reached", parliamentProposalOnTrackNo: "Below threshold", @@ -1130,6 +1345,7 @@ module.exports = { courtsCaseFormTitle: "Open Case", courtsCaseRespondent: "Accused / Respondent", courtsCaseRespondentPh: "Oasis ID (@...) or Tribe name", + courtsCaseSubjectPlaceholder: "Subject or short description", courtsCaseMethod: "Resolution method", courtsCaseSubmit: "File Case", courtsNominateJudge: "Nominate Judge", @@ -1164,15 +1380,30 @@ module.exports = { courtsMethodDICTATOR: "Dictator", courtsMethodPOPULAR: "Popular", courtsMethodKARMATOCRACY: "Karmatocracy", + courtsCaseType: "Case type", + courtsPresetMinorConflict: "Minor conflict", + courtsPresetModerateConflict: "Moderate conflict", + courtsPresetSevereConflict: "Severe conflict", + courtsPresetHarassment: "Harassment or abuse", + courtsPresetContentModeration: "Content moderation", + courtsPresetBanRestriction: "Ban or restriction", + courtsPresetGovernanceDispute: "Governance dispute", + courtsStatusOPEN: "Open", + courtsStatusIN_PROGRESS: "In progress", + courtsStatusDECIDED: "Decided", + courtsStatusCLOSED: "Closed", + courtsStatusSOLVED: "Solved", + courtsStatusUNSOLVED: "Unsolved", + courtsStatusDISCARDED: "Discarded", courtsRulesTitle: "How Courts work", courtsRulesIntro: "Courts are a community-run process to resolve conflicts and promote restorative justice. Dialogue, clear evidence, and proportional remedies are prioritized.", courtsRulesLifecycle: "Process: 1) Open case 2) Select method 3) Submit evidence 4) Hearing and deliberation 5) Verdict and remedy 6) Compliance and closure 7) Appeal (if applicable).", - courtsRulesRoles: "Accuser: opens the case. Defence: accused person or tribe. Method: mechanism chosen by the community to facilitate, assess evidence, and issue a verdict. Witness: provides testimony or evidence. Mediators: neutral people invited by the accuser and/or the defence, with access to all details, who help de-escalate the conflict and co-create agreements.", + courtsRulesRoles: "Accuser: opens the case. Defence: accused inhabitant or tribe. Method: mechanism chosen by the community to facilitate, assess evidence, and issue a verdict. Witness: provides testimony or evidence. Mediators: neutral inhabitants invited by the accuser and/or the defence, with access to all details, who help de-escalate the conflict and co-create agreements.", courtsRulesEvidence: "Description up to 1000 characters. Attach relevant and lawful images, audio, video, and PDF documents. Do not share sensitive private data without consent.", courtsRulesDeliberation: "Hearings may be public or private. Judges ensure respect, request clarifications, and may discard irrelevant or unlawful material.", courtsRulesVerdict: "Restoration is prioritized: apologies, mediation agreements, content moderation, temporary restrictions, or other proportional measures. The reasoning must be recorded.", courtsRulesAppeals: "Appeal: allowed when there is new evidence or a clear procedural error. Must be filed within 7 days unless otherwise stated.", - courtsRulesPrivacy: "Respect privacy and safety. Doxing, hate, or threats are removed. Judges may edit or seal parts of the record to protect people at risk.", + courtsRulesPrivacy: "Respect privacy and safety. Doxing, hate, or threats are removed. Judges may edit or seal parts of the record to protect inhabitants at risk.", courtsRulesMisconduct: "Harassment, manipulation, or fabricated evidence may lead to immediate negative resolution.", courtsRulesGlossary: "Case: record of a conflict. Evidence: materials that support claims. Verdict: decision with remedy. Appeal: request to review the verdict.", courtsFilterActions: "ACTIONS", @@ -1188,12 +1419,16 @@ module.exports = { exploreTrending: "Explore the most popular content in your network.", votesButton: "VOTATIONS", shopProductButton: "SHOP", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISODE", + campaignButton: "CAMPAIGN", + logisticsRouteButton: "ROUTE", trendingTotalOpinions: "Total Opinions", trendingNoContentMessage: "No trending available, yet.", trendingTotalCount: "Total Count", tasksTitle: "Tasks", tasksDescription: "Discover and manage tasks in your network.", - taskSearchPlaceholder: "Search tasks...", + taskSearchPlaceholder: "Search in Tasks...", taskTitleLabel: "Title", taskDescriptionLabel: "Description", taskStartTimeLabel: "Start Time", @@ -1212,7 +1447,7 @@ module.exports = { taskStatusOpen: "Open", taskStatusInProgress: "In Progress", taskStatusClosed: "Closed", - taskAssignedTo: "Assigned to", + taskAssignedTo: "Assigned", taskAssignedChip: "Assigned", taskUnassignedChip: "Unassigned", taskAssignees: "Assignees", @@ -1222,6 +1457,7 @@ module.exports = { taskUpdateButton: "Update", taskDeleteButton: "Delete", taskFilterAll: "ALL", + taskFilterRecent: "RECENT", taskFilterMine: "MINE", taskFilterOpen: "OPEN", taskFilterInProgress: "IN-PROGRESS", @@ -1249,14 +1485,13 @@ module.exports = { eventUpcomingDates: "Upcoming dates", eventsTitle: "Events", eventsDescription: "Discover and manage events in your network.", - eventSearchPlaceholder: "Search events...", + eventSearchPlaceholder: "Search in Events...", eventPrice: "Price", eventStatus: "Status", eventAllSectionTitle: "Events", eventMineSectionTitle: "Your Events", eventCreateSectionTitle: "Create Event", eventUpdateSectionTitle: "Update Event", - eventDeleteButton: "Delete", eventCreateButton: "Create Event", eventAddToCalendar: "Add to Calendar", eventVisitCalendar: "Visit Calendar", @@ -1274,11 +1509,11 @@ module.exports = { viewPlace: "View Place", generatePdf: "Generate PDF", sharePm: "Share via PM", - galleryImages: "Images (max-size: 50MB each)", + galleryImages: "Images (max-size: 75MB each)", galleryPhotos: "Images", galleryAddPhoto: "Add Image", galleryRemovePhoto: "Remove", - galleryVideo: "Video (max-size: 50MB)", + galleryVideo: "Video (max-size: 75MB)", galleryAddVideo: "Add Video", galleryRemoveVideo: "Remove video", viewVotation: "View Votation", @@ -1298,6 +1533,7 @@ module.exports = { eventDate: "Date", eventAttendButton: "Attend Event", eventFilterAll: "ALL", + eventFilterRecent: "RECENT", eventFilterMine: "MINE", eventFilterToday: "TODAY", eventFilterWeek: "THIS WEEK", @@ -1324,7 +1560,7 @@ module.exports = { tagsFilterTop: "TOP", tagsFilterCloud: "CLOUD", tagsNoItems: "No tags available.", - tagsSearchPlaceholder: "Search tags...", + tagsSearchPlaceholder: "Search in Tags...", tagsTableHeaderTag: "TAG/LINK:", tagsTableHeaderCount: "COUNTER:", transfersTitle: "Transfers", @@ -1332,7 +1568,10 @@ module.exports = { transfersFrom: "From", transfersTo: "To", transfersFilterAll: "ALL", + transfersFilterRecent: "RECENT", transfersFilterMine: "MINE", + transfersFilterMinePending: "PENDING", + transfersFilterMineConfirmed: "SIGNED", transfersFilterUBI: "UBI", transfersFilterMarket: "MARKET", transfersFilterTop: "TOP", @@ -1347,7 +1586,6 @@ module.exports = { transfersCategoryEconomic: "Economic", transfersCategoryTime: "Time", transfersCategoryTrust: "Trust", - transfersCategoryApply: "Apply", transfersUnitEco: "ECO", transfersUnitHours: "h", transfersUnitTrust: "trust", @@ -1377,7 +1615,7 @@ module.exports = { transfersDiscardedSectionTitle: "Discarded Transfers", transfersCreateSectionTitle: "Create transfer", transfersAllSectionTitle: "Transfers", - transfersSearchPlaceholder: "Search concept, tags, users...", + transfersSearchPlaceholder: "Search in Transfers...", transfersMinAmountLabel: "Min amount", transfersMaxAmountLabel: "Max amount", transfersSortRecent: "Most recent", @@ -1399,6 +1637,7 @@ module.exports = { voteErrorTitle: "Check the form", voteErrorDeadlineMin: "The deadline must be at least {days} days from now.", voteErrorGeneric: "The vote could not be saved.", + voteLocked: "This vote has already received votes or opinions, or belongs to Parliament or Courts: it can no longer be edited or deleted.", voteUpdateButton: "Update", voteDeleteButton: "Delete", voteOptionYes: "YES", @@ -1408,6 +1647,7 @@ module.exports = { voteFollowMajority: "FOLLOW MAJORITY", voteNotInterested: "NOT INTERESTED", voteFilterAll: "ALL", + voteFilterRecent: "RECENT", voteFilterMine: "MINE", voteFilterOpen: "OPEN", voteFilterClosed: "CLOSED", @@ -1423,7 +1663,6 @@ module.exports = { voteTags: "Tags", voteOpinions: "Opinions", novotes: "No voting proposals available.", - voteBy: "By", voteCreatedAt: "Created At", voteNoQuestion: "No question provided", voteUnknownCreator: "Unknown Creator", @@ -1433,6 +1672,7 @@ module.exports = { voteCommentsForumButton: 'Open discussion', voteCommentsSectionTitle: 'Open discussion', voteNoCommentsYet: 'There are no comments yet. Be the first to reply.', + commentsGoToLast: "Go to the last comment", voteNewCommentPlaceholder: 'Write your comment here…', voteNewCommentButton: 'Post comment', voteNewCommentLabel: 'Add a comment', @@ -1443,6 +1683,8 @@ module.exports = { cvNameLabel: "Full Name", cvDescriptionLabel: "Summary", cvPhotoLabel: "Photo", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Visit CV", cvPersonalExperiencesLabel: "Personal Experiences", cvPersonalSkillsLabel: "Personal Skills (comma-separated)", cvOasisExperiencesLabel: "Oasis Contribution Experiences", @@ -1472,9 +1714,9 @@ module.exports = { cvNoCV: "No CV found.", blogSubject: "Subject", blogMessage: "Message", - blogImage: "Upload media (max-size: 50MB)", + blogImage: "Upload media (max-size: 75MB)", blogMedia: "Attachments (up to 8 images and a video)", - blogPublish: "Preview", + blogPublish: "Publish", pollsTitle: "Polls", dataTitle: "Matches", dataDescription: "Explore and visualize the matches in your network.", @@ -1493,12 +1735,23 @@ module.exports = { dataKindTasks: "TASKS", dataKindReports: "REPORTS", dataKindVotes: "VOTATIONS", - dataSearchPlaceholder: "Search the cross-data...", + dataSearchPlaceholder: "Search in Matches...", dataCohesionTitle: "Cohesion Coefficient (CC)", dataCcShort: "CC", dataCohesionHint: "Average affinity between everything the network has published.", dataAffinity: "Affinity", dataCommonTerms: "Keyword", + dataReasonFollowing: "You support this inhabitant", + dataReasonTribe: "Shares a tribe with you", + dataReasonPinned: "Matches something you pinned", + dataReasonCv: "Matches your CV skills", + dataReasonContent: "Related to what you publish", + dataReasonRelated: "Related to your interests", + dataReasonAlike: "Published by an inhabitant like you", + dataReasonSupportsYou: "Supports you", + dataReasonMutual: "Mutual support", + dataReasonRated: "Well rated by the network", + dataReasonNear: "Near you", dataStatPeople: "CVs", dataStatConnected: "Connected", dataStatSkills: "Skills", @@ -1551,7 +1804,7 @@ module.exports = { pollStatusLabel: "Status", pollStatusOpen: "OPEN", pollStatusClosed: "CLOSED", - pollSearchPlaceholder: "Search polls...", + pollSearchPlaceholder: "Search in Polls...", pollsNoItems: "No polls found.", viewPoll: "View Poll", pollChatCreate: "Create Poll", @@ -1564,7 +1817,7 @@ module.exports = { blogFilterFavorites: "FAVORITES", blogFilterTop: "TOP", blogCreateButton: "Create Blog", - blogSearchPlaceholder: "Search blogs...", + blogSearchPlaceholder: "Search in Blogs...", blogComments: "Comments", blogAllowComments: "Allow comments", blogCommentsClosed: "The author has closed comments on this blog.", @@ -1577,12 +1830,11 @@ module.exports = { forumCreateButton: "Create forum", forumCreateSectionTitle: "Create forum", forumDescription: "Talk openly with other inhabitants in your network.", - forumSearchPlaceholder: "Search forums...", + forumSearchPlaceholder: "Search in Forums...", forumFilterAll: "ALL", forumFilterMine: "MINE", forumFilterRecent: "RECENT", forumFilterTop: "TOP", - forumDeleteButton: "Delete", forumParticipants: "participants", forumMessages: "messages", forumMessageLabel: "Message", @@ -1594,7 +1846,7 @@ module.exports = { larpTitle: "L.A.R.P.", larpDescription: "A live action role-playing layer for collaborative experimentation.", larpNoHousesMatch: "No houses match your search.", - larpSearchPlaceholder: "Search houses...", + larpSearchPlaceholder: "Search in L.A.R.P....", larpAllHouses: "Houses:", larpFilterRuling: "RULING", larpFilterHouses: "HOUSES", @@ -1630,8 +1882,6 @@ module.exports = { larpInviteCreate: "Generate invitation code", larpInvitePlaceholder: "Invitation code", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "House invitation code", - invitesHouseJoinButton: "Join House", larpInviteBannerTitle: "New invitation code", larpInviteBannerHint: "Share this code with someone in ACADEMIA. It expires in 30 cycles or after one use.", larpTestRankingTitle: "Score by house", @@ -1647,7 +1897,7 @@ module.exports = { larpProfileQ1O3: "Research the literature", larpProfileQ1O4: "Disrupt the system that creates it", larpProfileQ2: "What gives meaning to your daily work?", - larpProfileQ2O1: "Defending the people I love", + larpProfileQ2O1: "Defending the inhabitants I love", larpProfileQ2O2: "Creating something tangible", larpProfileQ2O3: "Healing or nurturing life", larpProfileQ2O4: "Crafting words and ideas", @@ -1722,7 +1972,7 @@ module.exports = { larpBadgeYou: "You", larpBadgeRuling: "Ruling", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Module for the live-action role-playing layer of Oasis (the 9 Houses).", + modulesLarpDescription: "Module for the live-action role-playing layer.", imageTitle: "Images", imageDescription: "Explore and manage image content in your network.", imageMineSectionTitle: "Your Images", @@ -1741,7 +1991,6 @@ module.exports = { imageFilterGallery: "GALLERY", imageCreateButton: "Upload Image", imageUpdateButton: "Update", - imageDeleteButton: "Delete", imageAddFavoriteButton: "Add favorite", imageRemoveFavoriteButton: "Remove favorite", imageFileLabel: "Select an image file (.jpeg, .jpg, .png, .gif)", @@ -1753,13 +2002,14 @@ module.exports = { imageDescriptionPlaceholder: "Optional", imageNoFile: "No image file provided", noImages: "No images available.", - imageSearchPlaceholder: "Search title, tags, description, author...", + imageSearchPlaceholder: "Search in Images...", imageSortRecent: "Most recent", imageSortOldest: "Oldest", imageSortTop: "Most voted", imageSearchButton: "Search", imageUpdatedAt: "Updated", imageNoMatch: "No images match your search.", + feedSearchPlaceholder: "Search in Feed...", feedTitle: "Feed", feedDetailTitle: "Feed", feedOpenDiscussion: "Open discussion", @@ -1778,7 +2028,7 @@ module.exports = { alreadyRefeeded: "You already refeeded this.", activityTitle: "Activity", yourActivity: "Your Activity", - globalActivity: "Global Activity", + globalActivity: "Activity", activityList: "Activity", activityDesc: "See the latest activity in your network.", allButton: "ALL", @@ -1793,7 +2043,8 @@ module.exports = { meme: "Meme", typeRecent: "RECENT", typeTop: "TOP", - typePost: "BLOGS", + typePost: "BLOG", + activityUpdateLabel: "UPDATE", typeTribe: "TRIBES", typeLarp: "L.A.R.P.", typeInhabitants: "INHABITANTS", @@ -1936,7 +2187,7 @@ module.exports = { courtsNoCases: 'No cases.', reportsTitle: "Reports", reportsDescription: "Manage and track reports in your network.", - reportsSearchPlaceholder: "Search reports...", + reportsSearchPlaceholder: "Search in Reports...", reportsFilterAll: "ALL", reportsFilterRecent: "RECENT", reportsFilterTop: "TOP", @@ -1984,7 +2235,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Under Review Reports", reportsResolvedSectionTitle: "Resolved Reports", reportsInvalidSectionTitle: "Invalid Reports", - reportsTemplateSectionTitle: 'Report template', reportsBugTemplateTitle: 'Reproduction details (Bugs)', reportsFeatureTemplateTitle: 'Details (Features)', reportsAbuseTemplateTitle: 'Details (Abuse)', @@ -2044,17 +2294,15 @@ module.exports = { tribeGallerySectionTitle: "Tribes Gallery", tribeRecentSectionTitle: "Recent Tribes", tribeTopSectionTitle: "Popular Tribes", - tribeviewTribeButton: "Visit Tribe", - tribeviewSubTribeButton: "Visit Sub-Tribe", tribeRootLabel: "ROOT", tribeDescription: "Explore or create tribes on your network.", tribeMainTribeLabel: "MAIN TRIBE", tribeCreateButton: "Create Tribe", tribeUpdateButton: "Update", tribeDeleteButton: "Delete", - tribeImageLabel: "Upload media (max-size: 50MB)", + tribeImageLabel: "Upload media (max-size: 75MB)", tribeTitleLabel: "Title", - searchTribesPlaceholder: "FILTER tribes BY NAME …", + searchTribesPlaceholder: "Search in Tribes...", tribeTitlePlaceholder: "Name of the tribe", tribeDescriptionLabel: "Description", tribeDescriptionPlaceholder: "Describe this tribe", @@ -2067,6 +2315,15 @@ module.exports = { tribeIsAnonymousLabel: "STATUS", tribeMembersCount: "Members", tribeEnterInvite: "ENTER INVITE CODE", + subscriptionTitle: "Subscriptions", + subscriptionOn: "Subscribed", + subscriptionOff: "Not subscribed", + subscriptionSubscribe: "Subscribe", + subscriptionUnsubscribe: "Unsubscribe", + subscriptionSubscribers: "Subscribers", + pmListLabel: "Send to", + pmListNone: "— No list —", + pmListNoSubscribers: "No subscribers", tribeLeaveButton: "LEAVE", tribePublic: "PUBLIC", tribePrivate: "PRIVATE", @@ -2163,9 +2420,14 @@ module.exports = { tribeMediaTypeDocument: "Document", tribeMediaTypeBookmark: "Bookmark", tribeContentDelete: "DELETE", + tribeReachTribe: "TRIBE", + tribeReachNotice: "OASIS shows it to every inhabitant and CLEARNET also on the open web. Opening it leaves a readable copy outside the tribe: moving it back to TRIBE hides it, but copies already shared cannot be erased.", + tribeReachPrivate: "Content of a private tribe can only stay inside the tribe.", + tribeReachOpenedBy: "Opened by", + tribeReachChange: "Where it is published", tribeInviteCodeText: "Invite code: ", oasisVersionLabel: "Oasis Version", - tribeInviteCodeHint: "Share this code with someone you want to invite. They can join via /invites → Tribes.", + tribeInviteCodeHint: "Share this code with someone you want to invite. They can join via /invites.", tribeStatusOpen: "OPEN", tribeStatusClosed: "CLOSED", tribeStatusInProgress: "IN PROGRESS", @@ -2205,7 +2467,7 @@ module.exports = { tribeSearchMinChars: "Enter at least 2 characters to search.", agendaTitle: "Agenda", agendaDescription: "Here you can find all your assigned items.", - agendaSearchPlaceholder: "Search agenda...", + agendaSearchPlaceholder: "Search in Agenda...", agendaFilterAll: "ALL", agendaFilterToday: "TODAY", agendaFilterUpcoming: "UPCOMING", @@ -2226,6 +2488,7 @@ module.exports = { agendaNoItems: "No assignments found.", agendaDiscardButton: "Discard", agendaRestoreButton: "Restore", + agendaRemoveButton: "Remove", agendaMembersCount: "Members", agendaLocationLabel: "Location", agendaYes: "YES", @@ -2292,8 +2555,8 @@ module.exports = { pmComposeTitle: "Send a message", pmRecipients: "Recipients", pmRecipientsHint: "Enter Oasis IDs separated by commas", - pmLimitsHint: "Up to 7 recipients per message.", - pmTextPlaceholder: "Body capped at 7000 characters.", + pmLimitsHint: "Up to 7 inhabitants per message.", + pmTextPlaceholder: "Write your message.", pmSubject: "Subject", pmSubjectHint: "Enter the message subject", pmText: "Message", @@ -2315,8 +2578,8 @@ module.exports = { pmCrypterCharsLabel: "characters", pmInvalidRecipients: "Invalid Oasis ID (should be like @…=.ed25519).", pmEncryptionLabel: "Encryption", - private: "Private", - privateDescription: "Your encrypted messages.", + private: "Inbox", + privateDescription: "Manage your private messages.", privateInbox: "INBOX", privateSent: "SENT", privateDelete: "Delete", @@ -2372,19 +2635,19 @@ module.exports = { visitContent: "Visit Content", favoriteAdd: "Pin to Favorites", pmContentTooltip: "Send a Private Message", + phoneCallTooltip: "Make a Phone Call", favoriteRemove: "Unpin from Favorites", reportContent: "Report Content", banking: 'Banking', bankingTitle: 'Banking', - bankingDescription: "The ECOin economy: balance, UBI, taxes and industry value.", + bankingDescription: "The ECOin collective economy.", bankOverview: 'Overview', bankEpochs: 'Epochs', bankRules: 'Rules', pending: 'Pending', closed: 'Closed', - bankViewTx: 'View Tx', bankClaimNow: 'Claim now', - bankClaimUBI: 'Claim UBI!', + bankClaimUBI: 'Claim UBI', bankClaimAndPay: 'Claim & Pay', bankClaimedPending: 'Claim pending...', bankStatusClaimed: 'Claimed', @@ -2408,8 +2671,8 @@ module.exports = { bankViewEpoch: 'View Epoch', bankYourIndustryBalance: "Your Industry Share", bankYourUbiMonth: "Your UBI (this month)", - bankYourFundsMonth: "Your Funds (this month)", - bankIndustryBalance: "Industry Production (estimated)", + bankYourFundsMonth: "Total Funds", + bankIndustryBalance: "Industry Production", bankUserBalance: 'Your Balance', bankAddresses: 'Addresses', bankNoAddresses: 'No addresses found.', @@ -2417,6 +2680,10 @@ module.exports = { bankAddress: 'Address', bankAddAddressTitle: 'Add ECOIN address', bankAddAddressUser: 'Oasis ID', + bankAddAddressLabel: "Label (optional)", + bankAddAddressLabelPlaceholder: "Bob's wallet, Alice's shop…", + bankAddAddressUserOptional: "Oasis ID (optional)", + bankAddressUnnamed: "Unnamed address", bankAddAddressAddress: 'ECOIN Address', bankAddAddressSave: 'Save', bankAddressAdded: 'Address added', @@ -2434,11 +2701,11 @@ module.exports = { bankAddressDeleteConfirm: 'Delete this address?', search: 'Search!', bankLocal: 'Local', - bankFromOasis: 'Oasis', + bankFromOasis: "Network", pubIdLabel: "PUB ID", bankUbiAvailableNo: "NO FUNDS!", bankUbiAvailableOk: "AVAILABLE!", - bankUbiAvailability: "UBI (network)", + bankUbiAvailability: "UBI", bankAlreadyClaimedThisMonth: "Already claimed this month", bankUbiThisMonth: "UBI (this month)", bankUbiLastClaimed: "UBI (last claimed)", @@ -2468,6 +2735,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Encrypted in transit", shopAddFavorite: "Add Favorite", shopRemoveFavorite: "Remove Favorite", shopOpen: "OPEN", @@ -2475,6 +2743,13 @@ module.exports = { shopMakePrivate: "MAKE PRIVATE (E2E)", shopMakePublic: "MAKE PUBLIC", shopProducts: "Products", + shopFilterAll: "ALL", + shopFilterRecent: "RECENT", + shopFilterMine: "MINE", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUCTS", + shopFilterPrices: "PRICES", + shopFilterFavorites: "FAVORITES", shopProductAdd: "Add Product", shopProductTitle: "Product", shopProductUpdate: "Update Product", @@ -2502,7 +2777,7 @@ module.exports = { marketVisitItem: "VISIT ITEM", shopStatus: "STATUS", shopCreatedAt: "CREATED", - shopSearchPlaceholder: "Search shops...", + shopSearchPlaceholder: "Search in Shops...", shopUrl: "URL", shopLocation: "Location", shopTags: "Tags", @@ -2512,7 +2787,7 @@ module.exports = { shopTitlePlaceholder: "Name of your shop", shopDescriptionPlaceholder: "Detailed description of your shop", shopLocationPlaceholder: "City, Country", - shopTagsPlaceholder: "tag1, tag2, tag3", + shopTagsPlaceholder: "Enter tags separated by commas", mapTitlePlaceholder: "Map title", shopProductFeatured: "Featured Product", shopSendToMarket: "Send to Market", @@ -2520,29 +2795,32 @@ module.exports = { typeShopProduct: "SHOP PRODUCT", bankExchange: 'Exchange', bankExchangeCurrentValue: 'ECOin Value (1h)', - bankTotalSupply: 'ECOin Total Supply', - bankEcoinHours: "ECOin Equivalence in Time", + bankTotalSupply: "ECOin Total Supply", + bankPubsSupply: "ECOin PUBs Pool Supply", + bankHoldingSupply: "ECOin Holding Supply", + bankEcoinHours: "ECOin Equivalence in Time (1 ECO)", bankHoursOfWork: 'hours', bankUnitMs: "ms", bankUnitSeconds: "seconds", bankUnitMinutes: "minutes", bankUnitDays: "days", bankExchangeNoData: 'No data available', - bankInflation: 'ECOin Inflation (anual)', - bankInflationMonthly: 'ECOin Inflation (mensual)', + bankInflation: "ECOin Inflation (annual)", + bankInflationMonthly: "ECOin Inflation (monthly)", bankExchangeChartTitle: 'ECOin value progression over time', bankExchangeChartValue: 'Value (ECO/h)', bankExchangeChartSupply: 'Supply', bankExchangeChartInflation: 'Inflation %', bankExchangeChartEmpty: 'Not enough samples yet — revisit later', - bankCurrentSupply: 'ECOin Current Supply', + bankCurrentSupply: "ECOin Current Supply", bankingSyncStatus: 'ECOin Status', bankingSyncStatusSynced: 'Synced', - bankingSyncStatusOutdated: 'Outdated', + bankingSyncStatusOutdated: "Unsync", statsTitle: 'Statistics', statistics: "Statistics", statsDescription: "Discover statistics about your network.", ALLButton: "ALL", + RECENTButton: "RECENT", MINEButton: "MINE", TOMBSTONEButton: "TOMBSTONES", statsCreatedAt: "Created At", @@ -2550,7 +2828,8 @@ module.exports = { statsYourOpinions: "Opinions", statsNetworkContent: "Content", statsNetworkOpinions: "Opinions", - statsEcoWalletLabel: "ECOIN Wallet", + statsEcoWalletLabel: "ECOin Wallet", + walletAddressCopyHint: "Click to select, then copy", statsEcoWalletNotConfigured: "Not configured!", statsBookmark: "Bookmarks", statsEvent: "Events", @@ -2572,6 +2851,8 @@ module.exports = { statsTransfer: "Transfers", statsAiExchange: "AI", statsPUBs: 'PUBs', + statsSyncedPeers: "Peers", + statsTotalLabel: "Total", statsPost: "Posts", statsSize: "Total (size)", statsBlockchainSize: "Blockchain (size)", @@ -2623,8 +2904,14 @@ module.exports = { aiPrompt: "Provide an informative and precise response.", aiConfiguration: "Set prompt", aiPromptUsed: "Prompt", - aiClearHistory: "Clear chat history", - aiServerError: "The AI could not answer. Please try again.", + aiClearHistory: "Clear history", + aiServerError: "I cannot answer properly. Please try again.", + aiStatusLoading: "42 is loading the model. Ask again in a moment.", + aiThinking: "Thinking", + aiWaiting: "Waiting", + aiStatusMissingModel: "The AI model is not installed: src/AI/oasis-42-1-chat.Q4_K_M.gguf is missing.", + aiExportFineTuning: "Export Fine-Tuning", + aiFactsTitle: "Data from your network", aiInputPlaceholder: "What is Oasis?", typeAiExchange: "AI", aiApproveTrain: "Add to collective training", @@ -2653,7 +2940,7 @@ module.exports = { marketFilterDiscarded: "DISCARDED", marketFilterRecent: "RECENT", marketFilterMyBids: "BIDS", - marketCreateButton: "Create Item", + marketCreateButton: "Sell Something", marketItemType: "Type", marketItemTitle: "Title", marketItemAvailable: "Deadline", @@ -2680,8 +2967,8 @@ module.exports = { marketItemSeller: "Seller", marketNoItems: "No items available, yet.", marketYourBid: "Your Bid", - marketCreateFormImageLabel: "Upload media (max-size: 50MB)", - marketSearchPlaceholder: "Search title or tags", + marketCreateFormImageLabel: "Upload media (max-size: 75MB)", + marketSearchPlaceholder: "Search in Market...", marketMinPriceLabel: "Min price", marketMaxPriceLabel: "Max price", marketSortRecent: "Most recent", @@ -2747,8 +3034,7 @@ module.exports = { housingSetClosed: "Make unavailable", housingCreateButton: "Create Place", housingUpdateButton: "Update", - housingDeleteButton: "Delete", - housingSearchPlaceholder: "Search places...", + housingSearchPlaceholder: "Search in Housing...", housingSearchButton: "Search", housingMinPrice: "Min price", housingMaxPrice: "Max price", @@ -2804,7 +3090,7 @@ module.exports = { jobLocationPresencial: "On-place", jobLocationRemote: "Remote", jobVacantsPlaceholder: "Number of positions", - jobImage: "Upload media (max-size: 50MB)", + jobImage: "Upload media (max-size: 75MB)", jobTasks: "Tasks", jobType: "Job Type", jobTime: "Job Time", @@ -2826,12 +3112,11 @@ module.exports = { jobsExchangeSkill: "Skill wanted in exchange", jobTimePartial: "Part-time", jobTimeComplete: "Full-time", - jobsDeleteButton: "DELETE", jobsUpdateButton: "UPDATE", jobsFilterApplied: "APPLIED", jobsAppliedTitle: "My applications", jobsAppliedBadge: "Applied", - jobsSearchPlaceholder: "Search title, tags, description...", + jobsSearchPlaceholder: "Search in Jobs...", jobsMinSalaryLabel: "Min salary", jobsMaxSalaryLabel: "Max salary", jobsSortRecent: "Most recent", @@ -2843,7 +3128,7 @@ module.exports = { jobSetOpen: "Set open", jobsTagsLabel: "Tags", noJobsMatch: "No jobs match your search.", - industrySearchPlaceholder: "Search facilities…", + industrySearchPlaceholder: "Search in Industry...", industryAllSectors: "All sectors", industryVisitButton: "Visit Industry", industryAdvanceTo: "Set to", @@ -2917,7 +3202,6 @@ module.exports = { industryBuildStatus_FAILED: "FAILED", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "A job matches your curriculum", jobsBotMatchIntro: "A job matches your curriculum.", jobsBotMatchJob: "Job", @@ -2932,7 +3216,7 @@ module.exports = { housingBotYouRequestedTitle: "You have requested a place.", housingBotYouCancelledTitle: "You have cancelled a request.", housingBotUnavailableTitle: "A place you requested is no longer available.", - housingBotUnavailableSentTitle: "You have told the people who requested this place.", + housingBotUnavailableSentTitle: "You have told the inhabitants who requested this place.", industryBotAdmittedTitle: "You have been admitted to a facility.", industryBotApplicationTitle: "New membership application in your facility.", industryBotInvitedTitle: "You have been invited to a facility.", @@ -2943,8 +3227,8 @@ module.exports = { industryRulesTitle: "Rules", industryRulesIntro: "Industry lets inhabitants collectively own the means of production: a Facility is a network-owned workshop that nobody owns privately.", industryRulesSteward: "The founder is the steward — a caretaker, not an owner: they cannot sell, privatize or dissolve the Facility on their own.", - industryRulesMembership: "Membership policy sets how people join: Open (join instantly), By vote (admitted by the members' vote) or Invite only (a member must invite them first).", - industryRulesQuorum: "Quorum is the minimum number of members that must vote for a decision to count, so a small Facility isn't decided by a single person.", + industryRulesMembership: "Membership policy sets how inhabitants join: Open (join instantly), By vote (admitted by the members' vote) or Invite only (a member must invite them first).", + industryRulesQuorum: "Quorum is the minimum number of members that must vote for a decision to count, so a small Facility isn't decided by a single inhabitant.", industryRulesMajority: "Majority is the fraction of votes needed to pass (0.5 = half, 1 = unanimity); a decision passes when YES votes reach at least max(quorum, members × majority).", industryRulesDecisions: "Members vote to admit newcomers, approve builds and dissolve the Facility; the steward cannot override these collective decisions.", industryRulesBlueprints: "Blueprints are libre (COPYLEFT) recipes — the inputs (materials, labor hours, skills) and the output (a physical or digital good) — and anyone can fork them.", @@ -2970,6 +3254,7 @@ module.exports = { modulesIndustryLabel: "Industry", modulesIndustryDescription: "Module to manage the means of production collectively.", industryFilterAll: "ALL", + industryFilterRecent: "RECENT", industryFilterMember: "MEMBER", industryFilterBlueprints: "BLUEPRINTS", industryFilterBuilds: "BUILDS", @@ -2978,6 +3263,7 @@ module.exports = { industryFilterPaused: "PAUSED", industryFilterDissolved: "DISSOLVED", industryAllTitle: "Industry", + industryRecentTitle: "Recent facilities", industryMemberTitle: "Facilities I'm in", industryMineTitle: "My facilities", industryActiveTitle: "Working", @@ -3025,7 +3311,7 @@ module.exports = { industryPolicy_invite: "Invite only", projectsTitle: "Projects", projectsDescription: "Create, fund, and follow community-driven projects in your network.", - projectSearchPlaceholder: "Search projects...", + projectSearchPlaceholder: "Search in Projects...", projectCreateProject: "Create Project", projectCreateButton: "Create Project", projectUpdateButton: "UPDATE", @@ -3048,7 +3334,7 @@ module.exports = { projectRecentTitle: "Recent Projects", projectTopTitle: "Top Funded", projectTitlePlaceholder: "Project name", - projectImage: "Upload media (max-size: 50MB)", + projectImage: "Upload media (max-size: 75MB)", projectDescription: "Description", projectDescriptionPlaceholder: "Tell the story and goals…", projectGoal: "Goal (ECO)", @@ -3120,15 +3406,13 @@ module.exports = { modulesModuleName: "Name", modulesModuleDescription: "Description", modulesModuleStatus: "Status", - modulesTotalModulesLabel: "Loaded Modules", + modulesTotalModulesLabel: "Total", modulesEnabledModulesLabel: "Enabled", modulesDisabledModulesLabel: "Disabled", modulesInvitesLabel: "Invites", modulesInvitesDescription: "Module to manage and apply invite codes.", modulesWalletLabel: "Wallet", modulesWalletDescription: "Module to manage your digital assets (ECOin).", - modulesLegacyLabel: "Legacy", - modulesLegacyDescription: "Module to manage your secret (private key) quickly and securely.", modulesCipherLabel: "Cipher", modulesCipherDescription: "Module to encrypt and decrypt your text symmetrically (using a shared password).", modulesBookmarksLabel: "Bookmarks", @@ -3190,20 +3474,23 @@ module.exports = { modulesProjectsLabel: "Projects", modulesProjectsDescription: "Module to explore, crowd-funding and manage projects.", modulesBankingLabel: "Banking", - modulesBankingDescription: "Module to determine the real value of ECOIN and distribute a UBI using the common treasury.", + modulesBankingDescription: "Module to manage the common treasury: ECOIN value, taxes and UBI.", modulesFavoritesLabel: "Favorites", modulesFavoritesDescription: "Module to manage your favorite content.", fileTooLargeTitle: "File too large", - fileTooLargeMessage: "The file exceeds the maximum allowed size (50 MB). Please select a smaller file.", + fileTooLargeMessage: "The file exceeds the maximum allowed size (75 MB). Please select a smaller file.", goBack: "Go back", + errorDismiss: "OK", errorPageTitle: "Something went wrong", + errorNoAllocation: "No allocation found for this claim.", + errorInvalidClaim: "This allocation cannot be claimed.", visibilityLabel: "Visibility", + clearnetPrivateConflict: "Private, hidden, closed, invite-only or paid content cannot be published on the CLEARNET. Choose OASIS or make it public.", visibilityPublic: "Public", visibilityHidden: "Hidden", visibilityMakePublic: "Make public", visibilityMakeHidden: "Make hidden", invitesInhabitantsTitle: "Inhabitants", - invitesInhabitantsFollow: "Give Support", aiNavPlaceholder: "Describe what you need...", aiNavDisabled: "AI navigation is disabled.", aiNavResultsTitle: "AINav Suggestions", @@ -3214,7 +3501,7 @@ module.exports = { modulesAINavDescription: "Module for natural-language queries about the network's content.", directConnectDescription: "Connect directly to a peer by entering their IP address, port and public key. The peer will be added as a followed connection.", peerHost: "IP", - peerPort: "Port (default: 8008)", + peerPort: "Port", peerPublicKey: "Public Key (@...ed25519)", connectAndFollow: "Connect", modulesPresetTitle: "Common Configurations", @@ -3223,18 +3510,18 @@ module.exports = { workflowsSet: "Set workflow", workflow_default: "Default", workflow_jobs: "Jobs", - workflow_ruling: "Ruling", - workflow_politics: "Politics", workflow_social: "Social", workflow_business: "Business", workflow_night: "Night", + workflow_kids: "Kids", + workflow_activists: "Activists", workflow_mobile: "Mobile", statsCarbonFootprintTitle: "Carbon Footprint", statsCarbonTombstone: "Tombstoning footprint", - uploadMedia: "Upload media (max-size: 50MB)", + uploadMedia: "Upload media (max-size: 75MB)", mapsLabel: "Maps", mapTitle: "Maps", - mapDescription: "Explore and manage offline maps in your network.", + mapDescription: "Manage and share offline maps in your network.", mapMineSectionTitle: "Your Maps", mapCreateSectionTitle: "Create Map", mapUpdateSectionTitle: "Update Map", @@ -3276,7 +3563,20 @@ module.exports = { mapAddMarkerButton: "Add Marker", mapCleanMarkerButton: "Clean Marker", mapApplyZoom: "Apply Zoom", - mapSearchPlaceholder: "Search description, tags, author...", + mapPlaceSearchPlaceholder: "Find a place or country...", + mapNoPlaces: "No places found.", + mapClusterTitle: "markers here, click to zoom in", + mapDeleteMarker: "Delete marker", + mapZoomIn: "Zoom in", + mapZoomOut: "Zoom out", + mapFitMarkers: "Fit all markers", + mapPanLabel: "Move the map", + mapCenterHere: "Center here", + mapNear: "near", + mapPickedSpot: "Picked spot", + mapPickHint: "Click on the map to pick a spot.", + mapClearPick: "Clear", + mapSearchPlaceholder: "Search in Maps...", mapSearchButton: "Search", mapUpdatedAt: "Updated", mapNoMatch: "No maps match your search.", @@ -3288,6 +3588,465 @@ module.exports = { typeMapMarker: "MAP MARKER", modulesMapLabel: "Maps", modulesMapDescription: "Module to manage and share offline maps.", + emergenciesTitle: "Emergencies", + modulesEmergenciesLabel: "Emergencies", + modulesEmergenciesDescription: "Module to raise, confirm and follow emergencies in the network.", + emergenciesDescription: "Module to raise, confirm and follow emergencies in the network.", + emergencyCreate: "Create Emergency", + emergencyUpdate: "Update", + emergencyDelete: "Delete", + emergencyTitleLabel: "Title", + emergencyTitlePlaceholder: "What is happening?", + emergencyTextLabel: "Details", + emergencyTextPlaceholder: "Where, since when, what to do...", + emergencyCategoryLabel: "Category", + emergencyCategoryWeather: "WEATHER", + emergencyCategoryInfrastructure: "INFRASTRUCTURE", + emergencyCategoryHealth: "HEALTH", + emergencyCategorySecurity: "SECURITY", + emergencyCategoryLost: "LOST & FOUND", + emergencyCategoryNeighborhood: "NEIGHBORHOOD", + emergencyExpiresLabel: "Expires in", + emergencyExpires1d: "1 day", + emergencyExpires3d: "3 days", + emergencyExpires7d: "1 week", + emergencyExpires30d: "1 month", + emergencyExpiresNone: "Undefined", + emergencyExpiresKeep: "Keep current date", + emergencyTagsLabel: "Tags", + emergencyTagsPlaceholder: "Enter tags separated by commas", + emergencyFilterAll: "ALL", + emergencyFilterMine: "MINE", + emergencyFilterRecent: "RECENT", + emergencySeverityUnverified: "UNVERIFIED", + emergencySeverityLow: "LOW", + emergencySeverityMedium: "MEDIUM", + emergencySeverityHigh: "HIGH", + emergencyStatusActive: "ACTIVE", + emergencyStatusResolved: "RESOLVED", + emergencyStatusExpired: "EXPIRED", + emergencyConfirmations: "Confirmations", + emergencyConfirmButton: "Confirm", + emergencyConfirmed: "You confirmed this emergency", + emergencyResolveButton: "Resolve", + emergencyReopenButton: "Reopen", + emergencyGenerateReport: "Generate Report", + emergencyUpdateEdited: "Edited", + emergencyLastUpdate: "LAST UPDATE", + emergencyUpdatesTitle: "Last Updates:", + emergencyNoUpdates: "No updates yet.", + emergencyAddUpdate: "Post update", + emergencyUpdatePlaceholder: "What changed?", + emergencyNoItems: "No emergencies found.", + emergencyExpiresAt: "Expires", + emergencyBannerLabel: "Active emergency", + emergencyCreateSectionTitle: "Create Emergency", + emergencyUpdateSectionTitle: "Update Emergency", + emergencySearchPlaceholder: "Search in Emergencies...", + typeEmergency: "EMERGENCIES", + typeEmergencyConfirm: "EMERGENCY CONFIRM", + typeEmergencyUpdate: "EMERGENCY UPDATE", + statsEmergency: "Emergencies", + statsEmergencyConfirm: "Emergency confirmations", + statsEmergencyUpdate: "Emergency updates", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "An emergency you follow has an update.", + emergencyBotResolvedTitle: "An emergency you follow has been resolved.", + dataKindEmergencies: "EMERGENCIES", + mailingTitle: "Mailing Lists", + modulesMailingLabel: "Mailing Lists", + modulesMailingDescription: "Discover and manage mailing lists in your network.", + modulesPhoneLabel: "Phone", + modulesPhoneDescription: "Module to call other inhabitants with end-to-end encrypted videocalls.", + mailingDescription: "Discover and manage mailing lists in your network.", + mailingCreate: "Create Mailing List", + mailingUpdate: "Update", + mailingArchive: "Archive", + mailingActivate: "Activate", + mailingCreateSectionTitle: "Create Mailing List", + mailingUpdateSectionTitle: "Update Mailing List", + mailingTitleLabel: "Title", + mailingTitlePlaceholder: "Name of the mailing list", + mailingDescriptionLabel: "Description", + mailingDescriptionPlaceholder: "What is this list about?", + mailingTypeLabel: "Mailing list type", + mailingTypeOpen: "OPEN", + mailingTypeClosed: "CLOSED", + mailingStatusLabel: "Status", + mailingStatusActive: "ACTIVE", + mailingStatusArchived: "ARCHIVED", + mailingMembersLabel: "Invite inhabitants", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Subscribers", + mailingTagsLabel: "Tags", + mailingTagsPlaceholder: "Enter tags separated by commas", + mailingFilterAll: "ALL", + mailingFilterMine: "MINE", + mailingFilterSubscribed: "SUBSCRIBED", + mailingFilterRecent: "RECENT", + mailingHistoryDate: "DATE", + mailingHistoryThreads: "THREADS", + mailingHistoryInhabitants: "INHABITANTS", + mailingHistorySearchPlaceholder: "Search in the history...", + mailingNoMessages: "No messages yet.", + mailingNoItems: "No mailing lists found.", + mailingSearchPlaceholder: "Search in Mailing Lists...", + mailingWriteButton: "Write message", + mailingWriteTitle: "Write to the list", + mailingReplyTitle: "Reply to the list", + mailingSend: "Send", + mailingTextPlaceholder: "Your message reaches every participant of the list.", + mailingParticipants: "Participants", + inboxFilterMailing: "Mailing lists", + mailingReplyPlaceholder: "Reply to the list...", + mailingReplyToList: "Reply to list", + typeMailingList: "MAILING LIST", + statsMailingList: "Mailing lists", + mailingListLabel: "MAILING LIST", + logisticsTitle: "Logistics", + modulesLogisticsLabel: "Logistics", + modulesLogisticsDescription: "Discover and manage logistic plans in your network.", + logisticsDescription: "Discover and manage logistic plans in your network.", + logisticsCreate: "Create Route", + logisticsUpdate: "Update", + logisticsClose: "Close", + logisticsReopen: "Reopen", + logisticsCreateSectionTitle: "Create Route", + logisticsUpdateSectionTitle: "Update Route", + logisticsTitleLabel: "Title", + logisticsTitlePlaceholder: "Ride to the market, van available...", + logisticsKindLabel: "Logistics type", + logisticsKindTrip: "TRIP", + logisticsKindShipment: "SHIPMENT", + logisticsKindTrips: "TRIPS", + logisticsKindShipments: "SHIPMENTS", + logisticsModeLabel: "Mode", + logisticsModeOffer: "OFFER", + logisticsModeRequest: "REQUEST", + logisticsModeOffers: "OFFERS", + logisticsModeRequests: "REQUESTS", + logisticsDescriptionLabel: "Description", + logisticsDescriptionPlaceholder: "Meeting point, what you carry, conditions...", + logisticsOriginLabel: "Origin", + logisticsOriginPlaceholder: "Where it starts", + logisticsDestinationLabel: "Destination", + logisticsDestinationPlaceholder: "Where it ends", + logisticsDateLabel: "Date and time", + logisticsRecurrenceLabel: "Recurrence", + logisticsFrequencyLabel: "Frequency", + logisticsRecurrenceNone: "ONE-OFF", + logisticsRecurrenceDaily: "DAILY", + logisticsRecurrenceWeekly: "WEEKLY", + logisticsRecurrenceMonthly: "MONTHLY", + logisticsSeatsLabel: "Seats", + logisticsSizeLabel: "Size", + logisticsSizePlaceholder: "e.g. 2m x 1m", + logisticsWeightLabel: "Weight", + logisticsWeightPlaceholder: "e.g. 40kg", + logisticsPriceFree: "FREE", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TIME", + logisticsPriceLabel: "Price", + logisticsHours: "h", + logisticsOrderRefLabel: "Order link", + logisticsOrderRefPlaceholder: "/shops/... or /market/... of an order to deliver", + logisticsOrderLink: "Linked order", + logisticsTagsLabel: "Tags", + logisticsTagsPlaceholder: "Enter tags separated by commas", + logisticsFilterAll: "ALL", + logisticsFilterMine: "MINE", + logisticsFilterBooked: "BOOKED", + logisticsFilterRecent: "RECENT", + logisticsFilterHistory: "HISTORY", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "OPEN", + logisticsStatusUpcoming: "UPCOMING", + logisticsStatusPast: "PAST", + logisticsStatusClosed: "CLOSED", + logisticsZoneLabel: "Zone", + logisticsSearchPlaceholder: "Search in Logistics...", + logisticsNoItems: "No routes found.", + logisticsBookingPending: "PENDING", + logisticsBookingConfirmed: "CONFIRMED", + logisticsBookingRejected: "REJECTED", + logisticsBookingCancelled: "CANCELLED", + logisticsBookingDelivered: "DELIVERED", + logisticsConfirm: "Confirm", + logisticsReject: "Reject", + logisticsDeliver: "Deliver", + logisticsReceiptLabel: "Receipt", + logisticsReceiptPlaceholder: "Delivery note (optional)", + logisticsCancelBooking: "Cancel booking", + logisticsBookTitle: "Book this route", + logisticsNotesLabel: "Notes", + logisticsNotesPlaceholder: "Anything the owner should know", + logisticsBook: "Book", + logisticsBookingsTitle: "Bookings", + logisticsMyBookingTitle: "Your booking", + logisticsRatingsTitle: "Ratings", + logisticsNoRatings: "No ratings yet.", + logisticsRatingsWhenClosed: "Ratings open once the route is closed.", + logisticsScoreLabel: "Score", + logisticsRatingPlaceholder: "How did it go?", + logisticsRate: "Rate", + typeLogisticsRoute: "ROUTE", + typeLogisticsRating: "ROUTE RATING", + statsLogisticsRoute: "Routes", + statsLogisticsRating: "Route ratings", + logisticsRouteLabel: "ROUTE", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Module to publish podcast channels.", + podcastsDescription: "Module to publish podcast channels.", + podcastCreate: "Create Podcast", + podcastUpdate: "Update", + podcastDelete: "Delete", + podcastCreateSectionTitle: "Create Podcast", + podcastUpdateSectionTitle: "Update Podcast", + podcastTitleLabel: "Title", + podcastTitlePlaceholder: "Name of the channel", + podcastDescriptionLabel: "Description", + podcastDescriptionPlaceholder: "What is this podcast about?", + podcastCategoryLabel: "Category", + podcastCategoryNews: "NEWS", + podcastCategoryMusic: "MUSIC", + podcastCategoryTalk: "TALK", + podcastCategoryEducation: "EDUCATION", + podcastCategoryTech: "TECH", + podcastCategoryCulture: "CULTURE", + podcastCategoryCommunity: "COMMUNITY", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "OTHER", + podcastTagsLabel: "Tags", + podcastCoverLabel: "Cover (image or video)", + podcastEpisodeAudioLabel: "Audio or video file (max-size: 75MB)", + podcastTagsPlaceholder: "Enter tags separated by commas", + podcastFilterAll: "ALL", + podcastFilterMine: "MINE", + podcastFilterRecent: "RECENT", + podcastFilterTop: "TOP", + podcastFilterViewers: "VIEWERS", + podcastSearchPlaceholder: "Search in Podcasts...", + podcastNoItems: "No podcasts found.", + podcastEpisodesLabel: "Episodes", + podcastPlaysLabel: "Views", + podcastLastEpisode: "Last episode", + podcastEpisodesTitle: "Episodes", + podcastNoEpisodes: "No episodes yet.", + podcastEpisodeAdd: "Add episode", + podcastEpisodeCreateTitle: "Publish episode", + podcastEpisodeUpdateTitle: "Update episode", + podcastEpisodeTitleLabel: "Episode title", + podcastEpisodeTitlePlaceholder: "Title of the episode", + podcastEpisodeDescriptionLabel: "Show notes", + podcastEpisodeDescriptionPlaceholder: "What happens in this episode, links, credits...", + podcastEpisodePublish: "Publish", + podcastPlayed: "Played", + podcastMarkPlayed: "Mark as played", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISODE", + typePodcastOpinion: "EPISODE OPINION", + typePodcastPlay: "EPISODE PLAY", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Episodes", + statsPodcastPlay: "Episode plays", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "A blog you follow has a new entry.", + podcastBotEpisodeTitle: "A podcast you follow has a new episode.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISODE", + campaignsTitle: "Campaigns", + modulesCampaignsLabel: "Campaigns", + modulesCampaignsDescription: "Discover and manage campaigns in your network.", + campaignsDescription: "Discover and manage campaigns in your network.", + campaignCreate: "Create Campaign", + campaignUpdate: "Update", + campaignDelete: "Delete", + campaignClose: "Close", + campaignReopen: "Reopen", + campaignCreateSectionTitle: "Create Campaign", + campaignUpdateSectionTitle: "Update Campaign", + campaignTitleLabel: "Title", + campaignTitlePlaceholder: "What do you want to achieve?", + campaignTextLabel: "Text", + campaignTextPlaceholder: "Explain the cause, who decides and what you ask for...", + campaignCategoryLabel: "Category", + campaignCategoryEnvironment: "ENVIRONMENT", + campaignCategoryRights: "RIGHTS", + campaignCategoryHealth: "HEALTH", + campaignCategoryEducation: "EDUCATION", + campaignCategoryInfrastructure: "INFRASTRUCTURE", + campaignCategoryCulture: "CULTURE", + campaignCategoryEconomy: "ECONOMY", + campaignCategoryOther: "OTHER", + campaignGoalLabel: "Signature goal", + campaignDeadlineLabel: "Deadline", + campaignTagsLabel: "Tags", + campaignTagsPlaceholder: "Enter tags separated by commas", + campaignFilterAll: "ALL", + campaignFilterMine: "MINE", + campaignFilterSigned: "SIGNED", + campaignFilterRecent: "RECENT", + campaignFilterTop: "TOP", + campaignStatusOpen: "OPEN", + campaignStatusAchieved: "ACHIEVED", + campaignStatusUnachieved: "UNACHIEVED", + campaignStatusClosed: "CLOSED", + campaignSearchPlaceholder: "Search in Campaigns...", + campaignNoItems: "No campaigns found.", + campaignSignaturesLabel: "Signatures", + campaignSignaturesTitle: "Supporters", + campaignNoSignatures: "No signatures yet.", + campaignSignTitle: "Sign this campaign", + campaignSignPlaceholder: "Why do you support it? (optional)", + campaignSign: "Sign", + campaignSigned: "You already signed!", + campaignNotSigned: "Your signature is still missing", + campaignElevate: "Raise to Parliament", + campaignElevated: "Raised to Parliament", + campaignUpdatesTitle: "Updates", + campaignNoUpdates: "No updates yet.", + campaignUpdatePlaceholder: "What happened?", + campaignAddUpdate: "Post update", + campaignQrAlt: "QR to sign this campaign", + campaignQrCaption: "Scan to sign", + typeCampaign: "CAMPAIGN", + typeCampaignSignature: "SIGNATURE", + typeCampaignUpdate: "CAMPAIGN UPDATE", + statsCampaign: "Campaigns", + statsCampaignSignature: "Campaign signatures", + statsCampaignUpdate: "Campaign updates", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "A campaign you follow has an update.", + campaignBotAchievedTitle: "A campaign you follow has reached its goal.", + campaignBotRaisedTitle: "A campaign you signed has been raised to Parliament.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "A route you booked has changed.", + logisticsBotClosedTitle: "A route you booked has been closed.", + logisticsBotBookedTitle: "Someone booked your route.", + logisticsBotCancelledTitle: "A booking on your route was cancelled.", + logisticsBotConfirmedTitle: "Your booking was confirmed: the package is on its way.", + logisticsBotRejectedTitle: "Your booking was rejected.", + logisticsBotDeliveredTitle: "Your package has arrived.", + campaignLabel: "CAMPAIGN", + campaignGenerateReport: "Generate Report", + logisticsGenerateReport: "Generate Report", + mailingGenerateReport: "Generate Report", + campaignUpdateEdited: "Edited", + mailingMessagesLabel: "Messages", + logisticsBookingsLabel: "Bookings", + backupTitle: "Backup", + modulesBackupLabel: "Backup", + modulesBackupDescription: "Module to export and import your keys and make encrypted backups.", + backupDescription: "Module to export and import your keys and make encrypted backups.", + backupTypeRecovery: "RECOVERY", + backupTypeKeys: "EXPORT KEYS", + backupTypeFull: "FULL BACKUP", + backupTypeRestore: "RESTORE BACKUP", + backupTypeInstant: "INSTANT", + backupInstantDescription: "A plain .zip of your whole log and blobs, without your secret key and without a password. Quick, but ", + backupInstantRisk: "anyone who gets the file can read everything in it.", + backupKitPageDescription: "The recovery kit can only be opened from this device.", + backupKitId: "Oasis ID", + backupKitDate: "Generated", + backupKitQrAlt: "QR code of your secret key", + backupKitStep1: "Print it or write it down and store it somewhere safe and offline.", + backupKitStep2: "To recover, install Oasis on a new device and import this secret from Backup › Keys.", + backupKitStep3: "Destroy this page if it is ever exposed.", + backupKitPdfButton: "Generate PDF", + backupScopeLabel: "Scope", + backupScopeAll: "EVERYTHING", + backupScopeMine: "ONLY MY CONTENT", + backupSinceLabel: "Since", + backupEstimateTitle: "Estimate", + backupFileInfo: "Your encrypted copy will be downloaded to your device (name: oasis-backup-DATE.oasisbk)", + backupExportButton: "Download backup", + backupRestorePassword: "Write the password used when the backup was made", + backupRestoreButton: "Restore backup", + backupRestoredLine: "Backup restored.", + backupRestoredMessages: "Messages added", + backupRestoredSkipped: "Already present", + backupRestoredBlobs: "Files added", + backupRestoredState: "Settings restored", + backupRestoredFailed: "Failed", + backupRestoredForked: "Diverged", + backupRestoreRunning: "Restoring the backup. This page refreshes automatically; you can keep using Oasis meanwhile.", + backupRestoreElapsed: "Elapsed", + backupRestoreFailedLine: "The backup could not be restored.", + backupRestoreForkMine: "This device already published messages with your identity that are not in the backup, so the copy could not be applied on top of them. Restore on a clean installation right after importing your keys, before doing anything else.", + verificationTitle: "Verification", + verificationDescription: "Check the chain of your feed, detect forks and review orphan or missing files.", + verificationRun: "Run verification", + verificationTotalMessages: "messages in the log", + verificationFeed: "Your feed", + verificationMessages: "messages", + verificationLastSequence: "last sequence", + verificationGaps: "Gaps", + verificationBrokenLinks: "Broken links", + verificationBadHashes: "Bad hashes", + verificationBadSignatures: "Bad signatures", + verificationDuplicates: "Duplicated sequences", + verificationForks: "Forks", + verificationBlobs: "Files", + verificationBlobsPresent: "present", + verificationBlobsReferenced: "referenced", + verificationBlobsOrphan: "orphan", + verificationBlobsMissing: "missing", + verificationBlobsMissingOwn: "Your own files missing from this device", + verificationBlobsMissingOthers: "Files from other inhabitants not downloaded yet", + verificationBlobsUnavailable: "Files could not be listed on this device.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Module to discover and manage wikis.", + wikiDescription: "Module to discover and manage wikis.", + wikiCreate: "Create Wiki", + wikiUpdate: "Update", + wikiDelete: "Delete", + wikiEdit: "Edit", + wikiTitleLabel: "Title", + wikiTitlePlaceholder: "Wiki title...", + wikiBodyLabel: "Content", + wikiBodyPlaceholder: "Link other wikis with [[Wiki title]] or [[Wiki title|a custom label]].\n\nMention an inhabitant with @id.\n\nTag with #oasis.\n\nPaste links like https://solarnethub.com\n\nA blank line starts a new paragraph; single line breaks are kept.\n\nAttach any supported media and it will be embedded here.", + formatBarLabel: "Format", + wikiStatusLabel: "Wiki type", + wikiStatusOpen: "OPEN", + wikiStatusClosed: "CLOSED", + wikiTagsLabel: "Tags", + wikiTagsPlaceholder: "Enter tags separated by commas", + wikiSummaryLabel: "Change summary", + wikiSummaryPlaceholder: "What changed?", + wikiFilterAll: "ALL", + wikiFilterMine: "MINE", + wikiFilterRecent: "RECENT", + wikiFilterLinked: "LINKED", + wikiChanges: "Recent changes", + wikiNoPages: "No wikis found.", + wikiNoChanges: "No changes yet.", + wikiHistory: "History", + wikiVersion: "Version", + wikiVersions: "versions", + wikiRestore: "Restore", + wikiDiff: "Changes", + wikiViewVersion: "View", + wikiLinkedPages: "Linked pages", + wikiNoLinkedPages: "This page is not linked to any other yet.", + wikiMissingLink: "Page does not exist yet", + wikiDuplicateTitle: "A page with this title already exists. Change the title to create a different page.", + wikiCreateSectionTitle: "Create Wiki", + wikiUpdateSectionTitle: "Update Wiki", + wikiSearchPlaceholder: "Search in Wiki...", + wikiCurrentVersion: "Current version", + wikiViewingVersion: "You are viewing an old version", + wikiBackToPage: "Back to page", + wikiLinkedChip: "Linked", + wikiRestoredSummary: "Restored version", + wikiExportPdf: "Generate Page", + wikiPreview: "Preview", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Create Wiki", + tribeWikiEmpty: "No wikis, yet.", + typeWiki: "WIKI", padsTitle: "Pads", padTitle: "Pad", modulesPadsLabel: "Pads", @@ -3298,15 +4057,16 @@ module.exports = { padTitleLabel: "Title", padTitlePlaceholder: "Enter pad title...", padStatusLabel: "Status", + padTypeLabel: "Pad type", padStatusOpen: "OPEN", padStatusInviteOnly: "INVITE-ONLY", padStatusClosed: "CLOSED", padDeadlineLabel: "Deadline", padTagsLabel: "Tags", - padTagsPlaceholder: "tag1, tag2, ...", + padTagsPlaceholder: "Enter tags separated by commas", padMembersLabel: "Members", padCreated: "Created", - padStartEditing: "START EDITING!", + padStartEditing: "Edit", padEditorPlaceholder: "Start writing...", padSubmitEntry: "Submit", padNoEntries: "No entries yet.", @@ -3314,7 +4074,119 @@ module.exports = { padMineSectionTitle: "Your Pads", padsNoItems: "No pads found.", padsDescription: "Manage collaborative encrypted text editors in your network.", + roomsTitle: "Rooms", + roomsDescription: "Discover and join encrypted videoconference rooms in your network.", + modulesRoomsLabel: "Rooms", + modulesRoomsDescription: "Module to discover and join encrypted videoconference rooms.", + roomFilterRecent: "RECENT", + roomFilterMine: "MINE", + roomFilterAll: "ALL", + roomFilterLive: "LIVE", + roomFilterOpen: "OPEN", + roomFilterInvite: "INVITE-ONLY", + roomFilterClosed: "CLOSED", + roomCreate: "Create Room", + roomUpdate: "Update", + roomDelete: "Delete", + roomClose: "Close", + roomCreateSectionTitle: "Create Room", + roomUpdateSectionTitle: "Update Room", + roomTitlePlaceholder: "Room title", + roomDescriptionLabel: "Description", + roomDescriptionPlaceholder: "What will you talk about?", + roomTypeLabel: "Room type", + roomTagsLabel: "Tags", + roomStatusOpen: "OPEN", + roomStatusInviteOnly: "INVITE-ONLY", + roomStatusClosed: "CLOSED", + roomParticipants: "Participants", + roomJoin: "Join", + roomLeave: "Leave", + roomFull: "Room full", + roomLiveChip: "LIVE", + roomMuted: "muted", + roomRec: "Record audio", + roomRecStop: "Stop recording", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} entered the room", + roomEventLeft: "{name} left the room", + roomEventRecStart: "{name} started recording", + roomEventRecStop: "{name} stopped recording", + roomEventMuted: "{name} muted", + roomEventUnmuted: "{name} unmuted", + roomEventHand: "{name} raised a hand", + roomRaiseHand: "Raise hand", + roomLowerHand: "Lower hand", + confirmActionTitle: "Confirm this action", + confirmActionButton: "Confirm", + confirmJoinText: "You are about to join using this invitation.", + confirmFollowText: "You are about to support this inhabitant.", + confirmSignText: "You are about to sign this campaign.", + confirmDownloadText: "You are about to download this file to your device.", + roomRecordings: "Recordings", + roomClearNotices: "Clear", + roomLineSwitchboard: "Switchboard", + roomLineDndError: "This room's number is not taking calls.", + roomNumberHint: "Dial this number from Phone to join the room.", + liveRefresh: "Refresh", + roomNobody: "Nobody is in the room right now.", + roomSomeone: "{n} in the room right now.", + roomYou: "you", + roomYouAreIn: "You are in", + roomNobodyInside: "Nobody is in this room right now.", + roomErrorFull: "The room is full. You can join when someone leaves.", + roomErrorUnreachable: "The room can't be reached right now. Try again later.", + roomErrorBusy: "You are already in a call or in another room.", + roomErrorClosed: "This room is closed.", + roomAccessDenied: "This room is invite-only. You need an invitation to join.", + roomsNoItems: "No rooms found.", + roomSearchPlaceholder: "Search rooms...", + roomInviteMode: "Invite", + tribeSectionRooms: "ROOMS", + tribeRoomCreate: "Create Room", + tribeRoomsEmpty: "No rooms, yet.", + typeRoom: "ROOMS", + typeRoomMember: "ROOM PARTICIPANT", + statsRoom: "Rooms", + statsRoomMember: "Room participants", + favoritesFilterRooms: "ROOMS", + invitesRoomsTitle: "Rooms", + inviteCodeInvalid: "This invitation code is not valid or has already been used.", + inviteAlreadyMember: "You are already in. You can open it from its module.", + actionFailed: "That could not be done. Please try again.", + publishTooShort: "Your post is too short.", + walletSendFailed: "The payment could not be sent: {reason}", + pixeliaCoordsError: "Those coordinates are outside the grid.", + courtsErrTitle: "A title is required.", + courtsErrRespondent: "Say who the case is against.", + courtsErrRespondentId: "That is not a valid Oasis ID.", + courtsErrMethod: "Choose how the case will be resolved.", + courtsErrCaseNotFound: "That case could not be found.", + courtsErrBrief: "Write your response.", + courtsErrStance: "Choose your stance.", + courtsErrResult: "Write the result.", + courtsErrTerms: "Write the terms of the agreement.", + courtsErrMediator: "Add at least one mediator.", + courtsErrSelfMediator: "You cannot be a mediator in your own case.", + courtsErrJudge: "Choose a judge.", + courtsErrSelfJudge: "You cannot be the judge of your own case.", + courtsErrVisibility: "Choose who can see the case.", + courtsErrNomination: "That nomination could not be found.", + opinionAlreadyGiven: "You have already given your opinion.", + cipherPasswordShort: "The password must be at least 32 characters long.", + errorForbidden: "You don't have access to this.", + errorNotFound: "That could not be found.", + errorClosed: "This is closed.", + errorInviteOnly: "This is invite-only. You need an invitation.", + errorTooLong: "That text is too long.", + errorRequired: "Some required information is missing.", + errorInvalid: "Some of the information is not valid.", + errorNotAuthor: "Only the author can do this.", + panicRemoved: "Your blockchain has been deleted.", padVersionHistory: "Version History", + padAttributionView: "By author", padVersionView: "View", padRecentSectionTitle: "Recent Pads", padOpenSectionTitle: "Open Pads", @@ -3327,7 +4199,7 @@ module.exports = { padRemoveFavorite: "Remove from Favorites", padClose: "Close", padBackToEditor: "Back to editor", - padSearchPlaceholder: "Search pads...", + padSearchPlaceholder: "Search in Pads...", calendarsTitle: "Calendars", calendarTitle: "Calendar", @@ -3336,15 +4208,15 @@ module.exports = { typeCalendar: "CALENDARS", calendarCreate: "Create Calendar", calendarUpdate: "Update", - calendarDelete: "Delete", calendarTitleLabel: "Title", calendarTitlePlaceholder: "Calendar title...", calendarStatusLabel: "Status", + calendarTypeLabel: "Calendar type", calendarStatusOpen: "OPEN", calendarStatusClosed: "CLOSED", calendarDeadlineLabel: "Deadline", calendarTagsLabel: "Tags", - calendarTagsPlaceholder: "tag1, tag2...", + calendarTagsPlaceholder: "Enter tags separated by commas", calendarParticipantsLabel: "Participants", calendarCreated: "Created", calendarJoin: "Join Calendar", @@ -3367,6 +4239,8 @@ module.exports = { calendarsDescription: "Discover and manage calendars in your network.", calendarMonthPrev: "\u2190 Prev", calendarMonthNext: "Next \u2192", + calendarMonthNames: "January,February,March,April,May,June,July,August,September,October,November,December", + calendarDayInitials: "M,T,W,T,F,S,S", calendarAllSectionTitle: "Calendars", calendarRecentSectionTitle: "Recent Calendars", calendarFavoritesSectionTitle: "Favorites", @@ -3378,14 +4252,14 @@ module.exports = { calendarAddFavorite: "Add to Favorites", calendarDeleteNote: "Delete", calendarRemoveFavorite: "Remove from Favorites", - calendarSearchPlaceholder: "Search calendars...", + calendarSearchPlaceholder: "Search in Calendar...", calendarAddEntry: "Add Entry", statsCalendar: "Calendars", statsCalendarDate: "Calendar Dates", statsCalendarNote: "Calendar Notes", modulesChatsLabel: "Chats", - modulesChatsDescription: "Module to discover and manage encrypted chats.", + modulesChatsDescription: "Module to discover and manage chats.", typeChat: "CHATS", typeChatMessage: "CHAT MESSAGE", chatsTitle: "Chats", @@ -3408,34 +4282,172 @@ module.exports = { chatMessagesLabel: "Messages", chatThreadsLabel: "Threads", chatCreatedAt: "CREATED", - chatSearchPlaceholder: "Search chats...", + chatSearchPlaceholder: "Search in Chats...", chatStatusOpen: "OPEN", chatStatusInviteOnly: "INVITE-ONLY", chatStatusClosed: "CLOSED", chatSendMessage: "Send", chatJumpLatest: "Latest Message", + chatReply: "Reply", + chatReplyCancel: "Cancel reply", + chatPin: "Pin", + chatUnpin: "Unpin", + chatPinned: "Pinned", chatPickChat: "Pick a chat to open it", chatNoneYet: "No chat available yet.", - activitySearchPlaceholder: "Search in activity...", - trendingSearchPlaceholder: "Search in trending...", - opinionsSearchPlaceholder: "Search in opinions...", - votesSearchPlaceholder: "Search in votations...", - welcomeStepUxTitle: "Choose your interface", - welcomeStepUxText: "Pick how Oasis should look. You can change it later in Settings.", - welcomeStepUxAction: "Use this view", + activitySearchPlaceholder: "Search in Activity...", + trendingSearchPlaceholder: "Search in Trending...", + opinionsSearchPlaceholder: "Search in Opinions...", + votesSearchPlaceholder: "Search in Votations...", + gamesSearchPlaceholder: "Search in Games...", + schoolTitle: "School", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "A wiki page you follow has been edited.", + wikiBotRestoredTitle: "An earlier version of a wiki page you follow has been restored.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "A new student has enrolled in your course.", + schoolBotInvitedTitle: "You have been invited to a course.", + schoolBotAdmittedTitle: "You have been admitted to a course.", + schoolBotCertificateTitle: "You have received a certificate.", + schoolBotPassedTitle: "A student has passed your course.", + schoolBotLessonTitle: "A new lesson has been published.", + invitesSchoolTitle: "School", + schoolEnrollLabel: "Join Course", + schoolExamRetryLater: "You can retry after 24 hours.", + schoolApproved: "APPROVED", + schoolExamPassMark: "Pass mark (0-10)", + schoolExamPassed: "PASSED", + schoolExamPending: "Pending", + schoolExamFailed: "FAILED", + schoolExamAddQuestion: "Add question", + schoolExamCreateQuestion: "Create question", + schoolExamOption: "Option", + schoolExamCorrect: "Correct option", + schoolExamPoints: "Points", + schoolMaterialTextPlaceholder: "Notes for this lesson...", + schoolMaterials: "Materials", + schoolAddMaterial: "Add material", + schoolNoMaterials: "No materials yet.", + bankYourSchoolBalance: "Your School Earnings", + schoolProgress: "Progress", + schoolProgressMark: "Mark as completed", + schoolProgressDone: "Completed", + schoolExams: "Exams", + schoolExamCreate: "Create exam", + schoolExamTitlePlaceholder: "Exam title", + schoolExamHelp: "One question per line: Question? ; option ; correct option* ; option", + schoolExamQuestionsPlaceholder: "What is SSB? ; A database ; A P2P protocol* ; A browser", + schoolExamSubmit: "Submit answers", + schoolExamScore: "Your score", + schoolNoScoresYet: "No results yet.", + schoolLessonUnit: "Unit / phase", + schoolLessonUnitPlaceholder: "e.g. Phase 1", + schoolLessonOrder: "Order", + profileClearnetSchoolLabel: "School", + schoolStartDate: "Start date", + schoolSessionDate: "Session date", + schoolCourseChat: "Course Chat", + agendaFilterSchool: "SCHOOL", + agendaFilterCampaigns: "CAMPAIGNS", + agendaFilterLogistics: "LOGISTICS", + schoolCourseType: "Course type", + schoolTypeOpen: "OPEN", + schoolTypePaid: "PAID", + schoolTypeInvite: "INVITE-ONLY", + schoolLessonLocked: "Encrypted lesson — ask the teacher for the course key.", + schoolGrantAccess: "Send course key", + schoolKeySent: "Key sent", + schoolOngoing: "Ongoing", + schoolFilterApplied: "APPLIED", + schoolFilterOpen: "OPEN", + schoolFilterFavorites: "FAVORITES", + schoolPendingPayment: "Payment pending", + schoolPendingStudents: "Pending enrollments", + schoolViewBill: "View bill", + schoolPaymentNote: "Enrolling generates an ECO bill (a transfer). Your enrollment is confirmed once the transfer is signed.", + schoolCloseCourse: "Close course", + schoolReopenCourse: "Reopen course", + schoolDescription: "Teach and learn courses in your network.", + schoolFilterAll: "ALL", + schoolFilterMine: "MINE", + schoolFilterTop: "TOP", + schoolFilterRecent: "RECENT", + schoolCreateButton: "Create Course", + schoolCreateSectionTitle: "Create a course", + schoolUpdateSectionTitle: "Update course", + schoolCourseTitlePlaceholder: "Name of your course", + schoolCourseDescriptionPlaceholder: "What will you teach in this course?", + schoolCourseImage: "Course image (max-size: 75MB)", + schoolTags: "Tags", + schoolTagsPlaceholder: "Enter tags separated by commas", + schoolPrice: "Price (ECO)", + schoolVisibility: "Visibility", + schoolVisibilityPublic: "Public", + schoolVisibilityInvite: "By invitation", + schoolStatus: "Status", + schoolClosed: "Closed", + schoolFree: "Free", + schoolCreate: "Create course", + schoolUpdate: "Update course", + schoolSearchPlaceholder: "Search in School...", + schoolSortTitle: "Title", + schoolNoCourses: "No courses found.", + schoolStudents: "Students", + schoolTeacher: "Teacher", + schoolCreatedAt: "CREATED", + schoolLessons: "Lessons", + schoolNoLessons: "No lessons yet.", + schoolEnrollToSee: "Enroll to see the lessons of this course.", + schoolEnrollButton: "ENROLL", + schoolUnenrollButton: "Leave course", + schoolInviteRequired: "This course is by invitation.", + schoolTeacherPanel: "Teacher Panel", + schoolStudentPanel: "Student Panel", + schoolAddLesson: "Create lesson", + schoolLessonTitlePlaceholder: "Lesson title", + schoolLessonTextPlaceholder: "Describe the notions this lesson covers...", + schoolInviteStudents: "Invite students", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "INVITE", + schoolInvited: "Invited", + schoolIssueCertificate: "Create certificate", + schoolCertified: "Certified", + schoolCertificateTextPlaceholder: "Certificate note (optional)", + schoolLessonOpen: "Open lesson", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificates", + schoolNoCertificates: "No certificates issued yet.", + schoolEditButton: "Edit", + schoolDeleteButton: "Delete", + typeSchool: "SCHOOL", + schoolCourseLabel: "COURSE", + emergencyLabel: "EMERGENCY", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Courses", + statsSchoolLesson: "Lessons", + statsSchoolEnroll: "Enrollments", + statsSchoolCertificate: "School certificates", + modulesSchoolLabel: "School", + modulesSchoolDescription: "Module to exchange knowledge.", + welcomeStepUxAction: "Save View", + welcomeStepWorkflowTitle: "What's the use for OASIS you have in mind?", + welcomeStepWorkflowText: "Oasis has many modules. Pick the configuration that fits you and only the useful ones stay on. You can fine-tune it anytime in Modules.", + welcomeStepWorkflowAction: "Save Configuration", + welcomeWorkflow_full: "Default", chatRateLimitTitle: "Too many messages", chatRateLimitMessage: "You have reached the limit of messages this room accepts in an hour. Try again later.", chatMessagePlaceholder: "Type your message...", chatNoMessages: "No messages yet.", chatTitlePlaceholder: "Chat title", chatDescriptionPlaceholder: "Chat description", - chatTagsPlaceholder: "tag1, tag2, tag3", + chatTagsPlaceholder: "Enter tags separated by commas", chatImageLabel: "Select an image file (.jpeg, .jpg, .png, .gif)", chatAuthor: "Author", chatAddFavorite: "Add to Favorites", chatRemoveFavorite: "Remove from Favorites", chatPM: "PM", chatStatusLabel: "Status", + chatTypeLabel: "Chat type", chatCategoryLabel: "Category", gamesTitle: "Games", gamesDescription: "Discover and play some mini-games in your network.", @@ -3476,7 +4488,11 @@ module.exports = { gamesTetrisTitle: "Tetris", gamesAudioPendulumTitle: "Audio Pendulum", gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Mix live visuals on two decks: pick a pattern for each, tune speed, colour and zoom, crossfade between them and flash to the beat. Tap the tempo or let the microphone drive the pulse.", gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", + gamesMinesweeperTitle: "Minesweeper", + gamesMinesweeperDesc: "Classic mine hunting. Clear the board to level up — bigger grids, more mines, higher score.", gamesFilterScoring: "SCORING", gamesHallOfFame: "Hall of Fame", gamesHallPlayer: "Player", @@ -3488,9 +4504,55 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Select which UX navigation mode you want for your GUI.", uxModeMenus: "Blocks", + uxModeChats: "Conversations", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Trending Tags", + feedActiveTitle: "Active Inhabitants", + cnBrandSub: "Libre · P2P · Federated", + cnSyncedPeers: "Synced-peers", + cnLicense: "License", + cnNotAccessible: "The content is not accessible at this moment.", + cnUntitled: "Untitled", + cnSearchPlaceholder: "Search…", + cnHubTitle: "Clearnet HUB", + cnHubDescription: "Public content shared by the inhabitants of this Oasis network.", + cnHubEmpty: "No public content has been shared to Clearnet yet.", + cnPublicContent: "Content", + cnTribesContent: "Tribes", + cnNetworkContent: "Network", + cnCategoryEmpty: "No content in this category.", + cnInhabitantEmpty: "This inhabitant has not published content to Clearnet yet.", + cnPrevPage: "← Previous", + cnNextPage: "Next →", + cnPageOf: "Page {page} of {pages}", + cnMoreInfo: "More info →", + cnOpenCourse: "Open course", + cnAuction: "AUCTION", + cnExchange: "EXCHANGE", + cnRssDescription: "published by the inhabitants of this HUB", + cnKindAudio: "Audio", + cnKindBlog: "Blog", + cnKindBookmark: "Bookmark", + cnKindDocument: "Document", + cnKindEvent: "Event", + cnKindFeed: "Feed", + cnKindImage: "Image", + cnKindJob: "Job", + cnKindMarket: "Market", + cnKindPodcast: "Podcast", + cnKindProject: "Project", + cnKindCourse: "Course", + cnKindShop: "Shop", + cnKindTorrent: "Torrent", + cnKindFile: "File", + cnKindVideo: "Video", + cnKindWiki: "Wiki", + cnKindRoom: "Room", + cnKindMap: "Map", uxModeAINav: "AI", statsChat: "Chats", statsChatMessage: "Chat messages", + statsWiki: "Wiki pages", statsPad: "Pads", statsPadEntry: "Pad entries", statsGameScore: "Gaming", @@ -3501,6 +4563,146 @@ module.exports = { tribeContentAccessDenied: "Access Denied", tribeContentAccessDeniedMsg: "This content belongs to a tribe. You must be a member to access it.", tribeViewTribes: "View Tribes", + tribeFilesEmpty: "No files, yet.", + filesLabel: "Files", + profileClearnetFilesLabel: "Files", + filesTitle: "Files", + filesDescription: "Explore and manage files in your network.", + fileAllSectionTitle: "Files", + fileMineSectionTitle: "Your Files", + fileRecentSectionTitle: "Recent Files", + fileTopSectionTitle: "Top Files", + fileFavoritesSectionTitle: "Favorites", + fileFilterAll: "ALL", + fileFilterMine: "MINE", + fileFilterRecent: "RECENT", + fileFilterTop: "TOP", + fileFilterFavorites: "FAVORITES", + fileCreateSectionTitle: "Upload File", + fileUpdateSectionTitle: "Update File", + fileCreateButton: "Upload File", + fileUpdateButton: "Update", + fileAddFavoriteButton: "Add Favorite", + fileRemoveFavoriteButton: "Remove Favorite", + fileFileLabel: "Select a file (max-size: 75MB)", + calendarFilterRecent: "RECENT", + calendarFilterAll: "ALL", + calendarFilterMine: "MINE", + calendarFilterFavorites: "FAVORITES", + calendarFilterOpen: "OPEN", + calendarFilterClosed: "CLOSED", + padFilterRecent: "RECENT", + padFilterAll: "ALL", + padFilterMine: "MINE", + padFilterOpen: "OPEN", + padFilterClosed: "CLOSED", + chatFilterRecent: "RECENT", + chatFilterAll: "ALL", + chatFilterMine: "MINE", + chatFilterFavorites: "FAVORITES", + chatFilterOpen: "OPEN", + chatFilterClosed: "CLOSED", + licenseLabel: "License", + phoneTitle: "Phone", + phoneDescription: "Manage your end-to-end encrypted videocalls.", + phoneUnavailable: "Calls are not available on this device.", + phoneCallButton: "Call", + profileVisibilityPhone: "Phone number", + phoneNumberUnknown: "No one in your network has the number {number}.", + phoneNumberChoose: "Check who you want to call at {number}:", + phoneAnswer: "Answer", + phoneSpeaker: "Speaker", + phoneEarpiece: "Earpiece", + phoneReject: "Reject", + phoneHangup: "Hang up", + phoneMute: "Mute", + phoneUnmute: "Unmute", + phoneCameraOn: "Camera on", + phoneCameraOff: "Camera off", + settingsPhoneVideo: "Video", + settingsPhoneVideoOn: "Allowed in calls and rooms", + settingsPhoneVideoOff: "Audio only", + phoneSilence: "Silence", + phoneUnsilence: "Hear again", + phoneCalling: "Calling", + phoneIsCalling: "is calling you", + notifyNewPmFrom: "New PM from", + notifyNewPamFrom: "New PaM from", + notifyUpdateLabel: "Update", + updateInstalledLabel: "Installed version", + updateAvailableLabel: "Available version", + updateChangesLabel: "Changes", + updateFilesLabel: "Files", + updateOriginLabel: "Source", + phoneRecording: "Recording PaM for", + phoneLeaveMessage: "Nobody answered: leave your message after the beep.", + phoneLeaveMessageBusy: "They are on another call: leave your message after the beep.", + phoneLeaveMessageUnreachable: "They could not be reached: leave your message after the beep.", + phoneConnecting: "Connecting to", + phoneSendPam: "Send PaM", + phoneCancel: "Cancel", + phoneSearchPlaceholder: "Search in Phone...", + phonePamTitle: "Audio Records", + phoneNew: "New", + phoneDelete: "Delete", + phoneNoPams: "You have no audio records.", + phoneHistoryTitle: "History", + phoneLatestTitle: "Latest Calls", + phoneCreateButton: "New call", + phoneLimitsHint: "Up to 7 inhabitants per call; more than one makes a joint call.", + phoneErrorPub: "Pubs cannot be called.", + phoneJointCall: "Joint call", + phoneWith: "With", + phoneResult: "Result", + phoneWhen: "When", + phoneDuration: "Duration", + phoneOutgoing: "Outgoing", + phoneIncoming: "Incoming", + phoneNoHistory: "No logs yet.", + phoneOutcomeEnded: "Answered", + phoneOutcomeMissed: "Missed", + phoneOutcomeRejected: "Rejected", + phoneOutcomeNoanswer: "No answer", + phoneOutcomeCancelled: "Cancelled", + phoneOutcomePam: "PaM sent", + phoneOutcomeFailed: "Failed", + phoneDialAdd: "Add", + phoneDialBack: "Delete", + phoneDialClear: "Clear", + phoneDialPlaceholder: "Number or Oasis ID", + phoneDialInhabitants: "Inhabitants", + phoneDialRemove: "Remove", + phoneOwnNumber: "That's your own number.", + phoneSettingsDesc: "Choose who can call you.", + phoneDnd: "Do not disturb", + phoneOpen: "Open", + phoneRecipientRefuses: "The recipient you are trying to call doesn't allow receiving phone calls.", + phoneRefusedNotice: "These inhabitants don't allow receiving phone calls and were not called:", + fileEmptyError: "The file is empty. Choose another file.", + fileTitleLabel: "Title", + fileTitlePlaceholder: "Title", + fileDescriptionLabel: "Description", + fileDescriptionPlaceholder: "Description", + fileTagsLabel: "Tags", + fileTagsPlaceholder: "Enter tags separated by commas", + fileSizeLabel: "Size", + fileDownloadButton: "DOWNLOAD", + fileDetailsButton: "DETAILS", + fileNoFile: "No file file", + fileSearchPlaceholder: "Search in Files...", + fileSearchButton: "Search", + fileSortRecent: "Most recent", + fileSortOldest: "Oldest", + fileSortTop: "Most voted", + fileNoMatch: "No matching files.", + fileUpdatedAt: "Updated", + typeFile: "FILES", + modulesFilesLabel: "Files", + modulesFilesDescription: "Module to discover and manage files.", + tribeSectionFiles: "FILES", + fileDescription: "Description", + fileDownload: "Download", + fileTypeLabel: "Type", torrentsTitle: "Torrents", torrentsDescription: "Explore and manage torrents in your network.", torrentAllSectionTitle: "Torrents", @@ -3528,10 +4730,33 @@ module.exports = { torrentTagsLabel: "Tags", torrentTagsPlaceholder: "Enter tags separated by commas", torrentSizeLabel: "Size", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Generate torrent", + torrentFromContentMissing: "The file is not available on this node yet. Try again in a while.", + torrentDownloads: "Downloads", + torrentFetchOasis: "Download in Oasis", + torrentSaveFile: "Save file", + torrentSeeds: "seeds", + seedsLabel: "Seeds", + seedAction: "Support", + torrentSourceFile: "File", + torrentStateQueued: "Queued", + torrentStateProbing: "Looking for seeds", + torrentStateDownloading: "Downloading", + torrentStateDone: "Completed", + torrentStateFailed: "Failed", + torrentStateNoSource: "No seeds online yet, waiting", + torrentStateCancelled: "Cancelled", + torrentNoDownloads: "No downloads yet.", + torrentOriginalSection: "Original", + torrentCancel: "Cancel", + torrentDownloadButton: "DOWNLOAD", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETAILS", torrentNoFile: "No torrent file", noTorrents: "No torrents found.", - torrentSearchPlaceholder: "Search title, tags, author...", + noFiles: "No files found.", + torrentSearchPlaceholder: "Search in Torrents...", torrentSearchButton: "Search", torrentSortRecent: "Most recent", torrentSortOldest: "Oldest", @@ -3539,10 +4764,12 @@ module.exports = { torrentNoMatch: "No matching torrents.", torrentUpdatedAt: "Updated", statsTorrent: "Torrents", + statsFile: "Files", typeTorrent: "TORRENTS", modulesTorrentsLabel: "Torrents", modulesTorrentsDescription: "Module to discover and manage torrents.", favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "FILES", favoritesFilterMarket: "MARKET", favoritesFilterEvents: "EVENTS", favoritesFilterForum: "FORUM", @@ -3556,11 +4783,13 @@ module.exports = { favoritesFilterShopProducts: "SHOP ITEMS", tribeSectionTorrents: "TORRENTS", tribeCreateTorrent: "Upload Torrent", + tribeCreateFile: "Upload File", settingsWishTitle: "Wish", - settingsWishDesc: "Configure the wish of your Avatar. This determines how to access the whole content and your level of experiences into the network.", + settingsWishDesc: "Determines how you access all the content and your experience in the Oasis network.", settingsWishWhole: "Multiverse", settingsWishMutuals: "Only mutual-support", settingsWishOnlyLan: "Only LAN", + settingsWishLocal: "Local", settingsPmVisibilityTitle: "Private Messages", settingsPmVisibilityDesc: "Configure the level of exposition to private messages that you want to have in the network.", settingsPmVisibilityWhole: "Multiverse", @@ -3608,7 +4837,7 @@ module.exports = { tribeGovNoRevocations: "Not any revocation, yet", tribeGovNoHistorical: "Not any record, yet", logsTitle: "Logs", - logsDescription: "Record your experience in the network.", + logsDescription: "Keep a private diary of your experiences.", logsViewTitle: "Log", logsColumnType: "Type", logsView: "View", @@ -3636,12 +4865,13 @@ module.exports = { logsExportOne: "Export Log", logsViewDetails: "View Details", logsGenerateButton: "Generate Text", - logsSearchText: "Search in logs...", + logsSearchText: "Search in Logs...", logsSearchAnyType: "Any type", logsSearchButton: "Search", logsEmpty: "No logs yet.", + logsNothingNew: "There are no new actions to record.", modulesLogsLabel: "Logs", - modulesLogsDescription: "Module to record (via AI assistant) your experiences.", + modulesLogsDescription: "Module to keep a private diary of your experiences.", statsLogsTitle: "Logs", typeLog: "LOGS", diff --git a/src/client/assets/translations/oasis_es.js b/src/client/assets/translations/oasis_es.js index b135331..1db3b16 100644 --- a/src/client/assets/translations/oasis_es.js +++ b/src/client/assets/translations/oasis_es.js @@ -9,8 +9,8 @@ module.exports = { shopPurchasesButton: "Compras", shopMyOrdersDescription: "Tus pedidos de compra.", shopMyOrdersEmpty: "Aún no tienes compras.", - shopOrderPmSeller: "PM Seller", - shopOrderSeller: "Vendedor", + shopOrderPmSeller: "PM Vendedora", + shopOrderSeller: "Vendedora", shopOrderInvoice: "Factura", shopOrderInvoiceConcept: "Factura", shopOrderStatusPending: "Pendiente", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Aceptar", shopOrderReject: "Rechazar", shopOrderMarkShipped: "Marcar enviado", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM Compradora", shopOrderContractConcept: "Pedido de tienda", shopOrdersPending: "Pedidos pendientes", all: "Todos", @@ -28,7 +28,7 @@ module.exports = { recent: "Recientes", anonymous: "Anónimo", unnamed: "Anónimo", - authorLabel: "AUTOR", + authorLabel: "Autoría", delete: "Eliminar", save: "Guardar", vote: "Votar", @@ -36,7 +36,7 @@ module.exports = { price: "Precio", priority: "Prioridad", severity: "Gravedad", - organizer: "Organizador", + organizer: "Organizadora", edited: "editado", isPublic: "Público", searchIsPublicLabel: "Público", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Aviso de contenido", invalidPost: "Contenido no válido", invalidPostHint: "Este mensaje tiene texto vacío o no válido.", - spreadBy: "Por", spreadChron: "Réplicas", spreaded: "Replicado", spreadContentUnavailable: "Contenido aún no disponible (replicación pendiente)", filteredByTag: "Filtrado por etiqueta", filteredByTagTitle: "Filtrado por etiqueta", feedPublishedSuccess: "¡Publicación creada con éxito!", + feedMastodonFailed: "Publicado en Oasis, pero no se ha podido enviar a Mastodon.", tribeFeedSent: "¡Mensaje enviado con éxito!", tribeContentEncrypted: "Contenido cifrado", tribeTorrentsEmpty: "Aún no hay torrents.", @@ -77,18 +77,17 @@ module.exports = { bookmarkLastVisit: "Última visita", profileClearnetBookmarksLabel: "Marcadores", invitesPort: "Puerto", - currentlyUnrecheable: "¡ERROR!", - peerHostValidation: "IPv4 válida (p. ej. 192.168.1.100) o nombre de host (p. ej. pub.example.com)", + peerHostValidation: "IPv4 válida (p. ej. 192.168.1.100) o nombre de host (p. ej. pub.solarnethub.com)", peerPortValidation: "Puerto 1-65535", peerKeyValidation: "Clave pública SSB ed25519 (@<44 caracteres base64>=.ed25519)", inboxJobUnsubscribedTitle: "Baja de tu oferta de empleo", - inboxProjectFollowedTitle: "Nuevo seguidor de tu proyecto", + inboxProjectFollowedTitle: "Nueva seguidora de tu proyecto", inboxProjectUnfollowedTitle: "Dejó de seguir tu proyecto", pmHasFollowedYourProject: "ha seguido tu proyecto", pmHasUnfollowedYourProject: "ha dejado de seguir tu proyecto", pmHasUnsubscribedFromYourJobOffer: "se ha dado de baja de tu oferta de empleo", marketDeleteButton: "Eliminar", - marketNoBids: "No se han realizado pujas, aún.", + marketNoBids: "Aún no se han realizado pujas.", marketAuctionBidAmount: "Importe de la puja", marketAuctionBidTime: "Hora de la puja", marketAuctionUser: "Pujador", @@ -105,11 +104,11 @@ module.exports = { taskDescriptionPlaceholder: "Descripción de la tarea", exportPasswordLabel: "Contraseña de exportación", importPasswordPlaceholder: "Introduce la contraseña", - searchCasesPlaceholder: "Buscar casos…", + searchCasesPlaceholder: "Buscar en Tribunales...", shopBuyDeliveryAddressPlaceholder: "Dirección de entrega", shopBuyNotesPlaceholder: "Notas", bankTaxesUserNoteBold: "reducir directamente el impuesto ECO", - bankTaxesUserNoteIntro: "Tu impuesto ECO se deduce del excedente que la red genera por encima de tu RBU; el valor base de la RBU se fija como mínimo inamovible. El impuesto nunca se deduce de la cantidad fija que corresponde a cada habitante como RBU. Los fondos deducidos alimentan el algoritmo de redistribución de riqueza y se canalizan de vuelta a otros habitantes mediante sus reclamos de RBU. Esos otros habitantes que reciben más RBU para sus proyectos probablemente tendrán alguna tarea dedicada a ayudarte a reducir tu impuesto ECO. O pueden dedicarse a ", + bankTaxesUserNoteIntro: "Tu impuesto ECO se deduce del excedente que la red genera por encima de tu RBU; el valor base de la RBU se fija como mínimo inamovible. El impuesto nunca se deduce de la cantidad fija que corresponde a cada habitante como RBU. Los fondos deducidos alimentan el algoritmo de redistribución de riqueza y se canalizan de vuelta a otras habitantes mediante sus reclamos de RBU. Esas otras habitantes que reciben más RBU para sus proyectos probablemente tendrán alguna tarea dedicada a ayudarte a reducir tu impuesto ECO. O pueden dedicarse a ", bankTaxesUserNoteOutro: ", y el consenso de la red puede requerir su contribución continua.", courtsViewDetails: "Ver", courtsViewDetailsShort: "Detalles", @@ -121,16 +120,11 @@ module.exports = { courtsDetailsNotPublic: "No público", courtsEvidenceSideAccuser: "Pruebas del acusador", courtsEvidenceSideRespondent: "Pruebas de la defensa", - courtsEvidenceSideUnknown: "Desconocido", + courtsEvidenceSideUnknown: "Desconocida", courtsJudgeNotAssigned: "-", courtsSupportCase: "Apoyar el caso", courtsSettlementAcceptBtn: "Aceptar acuerdo", courtsSupportCount: "Apoyos", - courtsVerdictVoteTitle: "Votar el veredicto", - courtsVerdictVoteLabel: "Voto del veredicto", - courtsVerdictVoteAccept: "Aceptar", - courtsVerdictVoteReject: "Rechazar", - courtsVerdictVoteSubmit: "Enviar voto", reportsSetStatus: "Cambiar estado", reportsStatusClosed: "CERRADO", statsAvgPerInhabitant: "Media por habitante", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Avatar", inhabitants: "Habitantes", - peersReplicatedFeeds: "Feeds replicados", + peersReplicatedFeeds: "Replicados", + peersTorColumn: "TOR", + peersTorYes: "Accesible por Tor", + peersTorNo: "No accesible por Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Versión", graphos: "Graphos", graphosDescription: "Mapa interactivo de la red alrededor de ti.", + graphosFederation: "Federación", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Inalcanzables", + graphosFedLinks: "Enlaces mutuos", + graphosFedIsolated: "Aislados", + graphosFedNoData: "Sin datos", + graphosFedMutual: "Mutuo", + graphosFedOneWay: "En un sentido", + graphosFedTransport: "Red", + graphosFedWith: "Federado con", + graphosZoom: "Ampliar", + graphosFedInhabitants: "Habitantes", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "Tu nodo aún no conoce ningún PUB.", graphosViewingGraphOf: "Viendo", graphosBackToMine: "← Mi red", graphosShowing: "Mostrando", @@ -202,20 +216,37 @@ module.exports = { strong("Todos los mensajes que te @mencionan"), ".", ], - noMentions: "No has recibido @menciones, aún.", - mentionsSearchPlaceholder: "Buscar menciones...", + noMentions: "Aún no has recibido @menciones.", + mentionsSearchPlaceholder: "Buscar en Menciones...", privateReminders: "RECORDATORIOS", + inboxFilterPms: "MPs", + inboxFilterNotifications: "Notificaciones", + inboxMarkRead: "Marcar como leído", + inboxMarkUnread: "Marcar como no leído", + inboxUnreadChip: "No leído", + inboxMarkAllRead: "Marcar todo como leído", + inboxDeleteShown: "Borrar todo", + inboxArchive: "Archivar", + inboxUnarchive: "Desarchivar", + inboxFilterArchived: "Archivados", + inboxEarlierMessages: "mensajes anteriores", + inboxSortThread: "Por hilo", + inboxSortRecent: "Más recientes primero", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Notificaciones del Inbox", + settingsInboxBotsDesc: "Elige qué bots pueden avisarte en tu Inbox. Los bots desmarcados se ocultan y sus avisos no se cuentan.", inboxFiltersLabel: "Filtros:", - inboxToggleToMutuals: "Cambiar a mutuos", + inboxToggleToMutuals: "Cambiar a mutuas", inboxToggleToWhole: "Cambiar a todos", privateDate: "Fecha", peers: "Nodos", - searchDescription: "Descripición", + searchDescription: "Descripción", searchFromLabel: "Desde", searchToLabel: "Hasta", searchQueryLabel: "Búsqueda", - searchPerPageLabel: "Resultados por página", + searchPerPageLabel: "Resultados", settings: "Configuración", + settingsDescription: "Configura el entorno de tu red.", continueReading: "Continuar leyendo", moreComments: "más comentarios", readThread: "leer el resto del hilo", @@ -234,16 +265,16 @@ module.exports = { profileClearnetHelp: 'Módulos que pueden ser consultados desde fuera de Oasis.', profileHubAll: 'Todo', footerLicenseLabel: 'Licencia', - profileSwitchToOasis: 'Volver a Oasis', profileVisibilityFediverse: "Multiverso", contributorsTitle: "Contribuciones", colorLabel: 'Elige un color', - paintButton: 'Dibujar!', + paintButton: "¡Dibujar!", totalPixels: 'Total Pixeles', modules: "Módulos", modulesViewTitle: "Módulos", modulesViewDescription: "Define tu entorno ideal activando y desactivando módulos.", inbox: "Buzón", + inboxSearchPlaceholder: "Buscar en Buzón...", multiverse: "Multiverso", fediverse: "Multiverso", fediverseDescription: "Gestiona tus otras cuentas del multiverso, incluyendo enviar y recibir contenido.", @@ -252,9 +283,17 @@ module.exports = { fediverseSettingsTitle: "Multiverso", fediverseInstanceLabel: "Dirección", fediverseTokenLabel: "Token de acceso", - fediverseTokenHelp: "Configura tu token de acceso. Se usará para gestionar tu cuenta de Mastodon desde Oasis.", fediverseConnect: "Conectar", fediverseConnectedAs: "Conectado como", + nextcloudServerLabel: "Servidor", + nextcloudUserLabel: "Usuario", + nextcloudFolderLabel: "Carpeta", + nextcloudUploadButton: "Subir", + nextcloudFileLabel: "Elige un fichero", + fileExternalLink: "Enlace externo", + nextcloudErrConnect: "No se ha podido llegar a Nextcloud con estos datos.", + nextcloudErrUpload: "La subida a Nextcloud ha fallado.", + nextcloudErrShare: "Nextcloud no ha dado un enlace público para el fichero.", fediverseDisconnect: "Desconectar", fediverseEmptyLink: "tus ajustes", fediverseComposePlaceholder: "¿Qué estás pensando?", @@ -265,7 +304,7 @@ module.exports = { fediverseBoosted: "ha impulsado", fediverseNoPosts: "Tu timeline está vacío.", fediverseTimeline: "Cronologías", - fediverseFollowers: "Seguidores", + fediverseFollowers: "Seguidoras", fediverseFollowing: "Siguiendo", fediversePosts: "Publicaciones", fediverseJoined: "Se unió", @@ -283,6 +322,76 @@ module.exports = { fediverseErrPost: "No se pudo publicar.", fediverseErrMedia: "No se pudo subir el archivo.", fediverseErrEmpty: "Escribe algo o adjunta un archivo.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Número de teléfono", + telegramSendCode: "Enviar código", + telegramCodeHelp: "Introduce el código que Telegram te acaba de enviar (app o SMS).", + telegramCodeLabel: "Código", + telegramPasswordHelp: "Esta cuenta tiene verificación en dos pasos. Introduce tu contraseña.", + telegramPasswordLabel: "Contraseña", + telegramVerify: "Verificar", + telegramCancel: "Cancelar", + telegramChats: "Chats", + telegramUnread: "Sin leer", + telegramOpen: "Abrir", + telegramSend: "Enviar", + telegramYou: "Tú", + telegramNoChats: "Todavía no hay chats.", + telegramNoMessages: "Todavía no hay mensajes.", + telegramComposePlaceholder: "Escribe un mensaje…", + telegramChannel: "Canal", + telegramGroup: "Grupo", + telegramPrivate: "Privado", + telegramErrConnect: "No se pudo contactar con Telegram.", + telegramErrTimeout: "Telegram tardó demasiado en responder.", + telegramErrAuth: "Sesión de Telegram inválida o caducada. Conéctala de nuevo.", + telegramErrFetch: "No se pudieron cargar los chats.", + telegramErrSend: "No se pudo enviar el mensaje.", + telegramErrMissing: "Hacen falta API ID, API hash y número de teléfono.", + telegramErrCode: "Código incorrecto. Inténtalo de nuevo.", + telegramErrCodeExpired: "El código ha caducado. Empieza de nuevo.", + telegramErrPassword: "Contraseña incorrecta.", + telegramErrPhone: "Número de teléfono inválido.", + telegramErrApi: "API ID o API hash inválidos.", + telegramErrFlood: "Demasiados intentos. Espera un rato e inténtalo de nuevo.", + telegramErrNoLogin: "No hay ningún inicio de sesión en curso. Empieza de nuevo.", + peertubeUsernameLabel: "Usuaria", + peertubePasswordLabel: "Contraseña", + peertubeSubscriptions: "Suscripciones", + peertubeMyVideos: "Mis vídeos", + peertubeVideos: "Vídeos", + peertubeFollowers: "Seguidoras", + peertubeViews: "visualizaciones", + peertubeLikes: "me gusta", + peertubeQuota: "Cuota", + peertubeUpload: "Subir vídeo", + peertubeUploadHelp: "Elige un fichero de vídeo. Se sube a tu canal y PeerTube lo procesa después.", + peertubeTitleLabel: "Título", + peertubeDescriptionLabel: "Descripción", + peertubePrivacyLabel: "Privacidad", + peertubePrivacyPublic: "Público", + peertubePrivacyUnlisted: "No listado", + peertubePrivacyPrivate: "Privado", + peertubeNoVideos: "Todavía no hay vídeos.", + peertubeComments: "Comentarios", + peertubeComment: "Comentar", + peertubeCommentPlaceholder: "Escribe un comentario…", + peertubeNoComments: "Todavía no hay comentarios.", + peertubeOpen: "Abrir en PeerTube", + peertubeWatch: "Ver", + peertubeChannel: "Canal", + peertubeNoStream: "Este vídeo aún no se puede reproducir aquí. Ábrelo en PeerTube.", + peertubeUploaded: "Vídeo subido. PeerTube lo está procesando.", + peertubeErrConnect: "No se pudo contactar con PeerTube.", + peertubeErrAuth: "Usuaria o contraseña incorrectas, o la sesión caducó. Conéctala de nuevo.", + peertubeErrInstance: "URL de instancia no válida.", + peertubeErrMissing: "Hacen falta instancia, usuaria y contraseña.", + peertubeErrFetch: "No se pudieron cargar los vídeos.", + peertubeErrUpload: "No se pudo subir el vídeo.", + peertubeErrTitle: "Hace falta un título.", + peertubeErrComment: "No se pudo publicar el comentario.", + peertubeErrNoFile: "Elige un fichero de vídeo.", bookmarksLabel: "Marcadores", videosLabel: "Vídeos", torrentsLabel: "Torrents", @@ -305,14 +414,13 @@ module.exports = { comment: "Comentar", subtopic: "Subtema", json: "JSON", - createdBy: "por", unfollow: "Dejar de soportar", follow: "Soportar", block: "Bloquear", unblock: "Desbloquear", relationshipNotFollowing: "No te da soporte", relationshipTheyFollow: "Te da soporte", - relationshipMutuals: "Soporte mútuo", + relationshipMutuals: "Soporte mutuo", relationshipFollowing: "Estás dando soporte", relationshipYou: "Tú", relationshipBlocking: "Estás bloqueando", @@ -320,26 +428,29 @@ module.exports = { relationshipMutualBlock: "Bloqueo mutuo", relationshipNone: "No estás dando soporte", relationshipBlockingPost: "Post bloqueado", - viewLikes: "Ver replicas", + viewLikes: "Ver réplicas", + viewSupporters: "Ver soportes", spreadedDescription: "Lista de contenidos replicados por habitante.", preview: "Previsualizar", publish: "Publicar", contentWarningPlaceholder: "Añade un título a tu post (opcional)", - publishWarningPlaceholder: "Cuerpo limitado a 7000 caracteres.", + publishWarningPlaceholder: "Escribe el cuerpo de tu entrada.", publishTooLong: "Tu publicación es demasiado larga. Acórtala, por favor.", + keepTextRetry: "Enviar de nuevo", + dateInPastError: "La fecha no puede ser anterior a hoy", publishCustomDescription: [ - "RECUERDA: Debido a la tecnología blockchain, una vez has publicado algo, no puede ser editado ni borrado. Piénsalo bien!.", + "RECUERDA: debido a la tecnología blockchain, una vez publicas algo, no se puede editar ni borrar. ¡Piénsalo bien!", ], commentWarning: [ - "RECUERDA: Debido a la tecnología blockchain, una vez has publicado algo, no puede ser editado ni borrado. Piénsalo bien!.", + "RECUERDA: debido a la tecnología blockchain, una vez publicas algo, no se puede editar ni borrar. ¡Piénsalo bien!", ], commentLabel: ({ publicOrPrivate, markdownUrl }) => [ ], publishLabel: ({ markdownUrl, linkTarget }) => [ - "RECUERDA: Debido a la tecnología blockchain, una vez has publicado algo, no puede ser editado ni borrado. Piénsalo bien!.", + "RECUERDA: debido a la tecnología blockchain, una vez publicas algo, no se puede editar ni borrar. ¡Piénsalo bien!", ], replyLabel: ({ markdownUrl }) => [ - "RECUERDA: Debido a la tecnología blockchain, una vez has publicado algo, no puede ser editado ni borrado. Piénsalo bien!.", + "RECUERDA: debido a la tecnología blockchain, una vez publicas algo, no se puede editar ni borrar. ¡Piénsalo bien!", ], publishCustomInfo: ({ href }) => [ "Si tienes experiencia, también puedes ", @@ -356,9 +467,8 @@ module.exports = { mentionsMatching: "Menciones", mentionsName: "Nombre", mentionsRelationship: "Relación", - updateit: "OBTENER ACTUALIZACIONES!", updateBannerText: "Hay una nueva versión de Oasis disponible.", - updateBannerAction: "Actualizar ahora →", + updateBannerAction: "¡Actualizar ahora!", info: "Info", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -366,17 +476,15 @@ module.exports = { timeAgo: "hace", sendTime: "escrito ", theme: "Tema", - legacy: "Llaves", - legacyTitle: "Llaves", - legacyDescription: "Maneja tu secreto (llave privada) de forma rápida y segura.", - legacyExportButton: "Exportar", - legacyImportButton: "Importar", + backupKeysExportButton: "Exportar", + backupKeysImportButton: "Importar", ssbLogStream: "Blockchain", ssbLogStreamDescription: "Configura el límite de mensajes para los flujos de la blockchain.", + logWindowChip: "Últimos {n} streams de la blockchain", saveSettings: "Guardar configuración", - exportDescription: "Establece una contraseña (min 32 caracteres de longitud) para cifrar tu secreto", - exportDataTitle: "Copia de Seguridad", - exportDataDescription: "Descargar tu blockchain (llave privada excluida!)", + exportDescription: "Establece una contraseña (mín. 32 caracteres) para cifrar tu secreto", + exportDataTitle: "Copia instantánea", + exportDataDescription: "Descargar tu blockchain (¡llave privada excluida!)", exportDataButton: "Descargar blockchain", importDescription: "Importa tu secreto cifrado (llave privada) para activar tu avatar", passwordImport: "Escribe tu contraseña para descifrar los datos que serán guardados en tu sistema (nombre: secret)", @@ -384,38 +492,33 @@ module.exports = { fileInfo: "Tu secreto cifrado se descargará en tu dispositivo (nombre: oasis.enc)", developer: "Desarrollo", devTitle: "Desarrollo", - welcomeBannerText: "Te damos la bienvenida a OASIS. Sigue estos pasos rápidos para configurar tu avatar.", + welcomeBannerText: "Tu avatar aún no está configurado.", aiSuggestionBanner: "Sugerencia:", aiSuggestionsEnable: "Sugerencias de la IA", - welcomeBannerAction: "¡Empezar!", + welcomeBannerAction: "Empezar", welcomeTitle: "Bienvenida", + welcomeStepIdentity: "Identidad", + welcomeStepType: "Tipo", + welcomeStepUse: "Uso", + welcomeStepLook: "Apariencia", + welcomeStepCommunity: "Comunidad", + welcomeStepIntro: "Bienvenida", + welcomeHintIdentity: "Tu nombre y tu imagen.", + welcomeHintType: "Cómo se abre Oasis para ti.", + welcomeHintUse: "Qué módulos quedan activos.", + welcomeHintLook: "El tema que viste Oasis.", + welcomeHintCommunity: "Entra en un PUB para conocer a otros habitantes.", + welcomeHintIntro: "Envía un primer saludo a la comunidad.", + welcomeStepThemeAction: "Usar este tema", + welcomeJoinPubs: "Entrar en los PUBs", + welcomeNoFederation: "Sin federarse", welcomeStepGreetingAction: "Enviar feed", - welcomeJoinPubButton: "Unirse al pub", - welcomeStepLarpAction: "Unirse a \"La Academia\"", - welcomeStepLarpText: "Únete a nuestro juego de rol de acción real.", - welcomeStepLarpTitle: "Únete al L.A.R.P.", - welcomeStepGreetingTitle: "Envía un \"Hola mundo!\"", - welcomeStepGreetingText: "Envía un mensaje para que otros habitantes puedan descubrirte.", - welcomeJoinPub: "Unirse a", welcomeDescription: "Unas pocas cosas que conviene hacer antes de empezar.", - welcomeProgress: "Completado", - welcomeStepDone: "hecho", - welcomeStepPending: "pendiente", welcomeSkip: "Ahora no", - welcomeStepLanguageTitle: "Elige tu idioma", - welcomeStepLanguageText: "Oasis habla varios idiomas. Puedes cambiarlo cuando quieras.", - welcomeStepProfileTitle: "Edita tu perfil", - welcomeStepProfileText: "Elige un nombre, añade una descripción y pon una imagen para que el resto de habitantes te reconozca.", welcomeStepProfileAction: "Guardar perfil", - welcomeStepFederationTitle: "Únete a la red principal", - welcomeStepFederationText: "Conéctate a nuestro pub para encontrar a otros habitantes y empezar a replicar.", - welcomeStepFederationAction: "Ir a invitaciones", - welcomeStepBackupTitle: "Copia de seguridad de tu ID", - welcomeStepBackupText: "Tu identidad es un fichero de claves en este dispositivo.", - welcomeStepBackupWarning: "SI LO PIERDES, NADIE PUEDE RECUPERARLO POR TI.", - welcomeStepBackupAction: "¡Copia de seguridad!", + welcomeBootstrapRunning: "Cargando la instantánea del pub. Al terminar se une al pub automáticamente; mientras tanto puedes seguir usando Oasis.", welcomeCompleteTitle: "Todo listo.", - welcomeCompleteText: "Tu nodo está preparado. A partir de aquí la red crece con la gente a la que sigues.", + welcomeCompleteText: "Tu nodo está preparado. A partir de aquí la red crece con los habitantes a los que apoyas.", welcomeFindPeople: "Buscar habitantes", devFilterFiles: "FICHEROS", devFilterSearch: "BUSCAR", @@ -425,7 +528,7 @@ module.exports = { devFilterTests: "TESTS", devVersion: "Versión", devNodeVersion: "Node", - devStatsTests: "Suites de tests", + devStatsTests: "Tests", devParentFolder: "Carpeta superior", devOpenFolder: "abrir", devDescription: "Explora el código que se está ejecutando en este nodo.", @@ -441,7 +544,7 @@ module.exports = { devSize: "Tamaño", devShowing: "Mostrando", devLine: "línea", - devReportBug: "Reportar Bug", + devReportBug: "Informar de un error", devCreateTask: "Crear Tarea", devRaw: "RAW", devReportContext: "Encontrado leyendo el código de Oasis", @@ -464,11 +567,32 @@ module.exports = { themeIntro: "Elige un tema.", setTheme: "Establecer tema", - language: "Lenguaje", + language: "Idioma", languageDescription: - "Si quieres utilizar otro idioma, seleccionalo aquí.", - setLanguage: "Establecer lenguaje", - peerConnectionsIntro: "Maneja todas tus conexiones con otros nodos.", + "Si quieres utilizar otro idioma, selecciónalo aquí.", + setLanguage: "Establecer idioma", + peerConnectionsIntro: "Gestiona todas tus conexiones con otros nodos.", + peersConnectFailed: "No se pudo conectar: {reason}.", + peerStateLabel: "Estado", + peerAddressLabel: "Dirección", + peerLastChangeLabel: "Último cambio", + peerStateConnected: "Conectado", + peerStateConnecting: "Conectando", + peerStateStaged: "Disponible", + peerErrSilent: "Sin actividad", + peerErrTimeout: "No responde", + peerErrRefused: "Rechaza la conexión", + peerErrNotFound: "Dirección desconocida", + peerErrUnreachable: "Red inaccesible", + peerErrKeys: "Usando llaves diferentes", + peerErrIdentity: "No acepta tu conexión", + peerErrTor: "Solo TOR", + peerErrDropped: "Conexión cortada", + peerErrPaused: "Red en pausa", + peerErrOther: "Error de conexión", + peerOutdated: "Desactualizado", + peerVersionUnannounced: "No anuncia su versión: un Oasis antiguo", + peersTorMissing: "Este PUB solamente permite conexiones desde TOR.", peerConnectionsTitle: "Conexiones", online: "Conectado", offline: "Desconectado", @@ -487,88 +611,76 @@ module.exports = { noConnections: "No hay nodos conectados.", noDiscovered: "No has descubierto nodos.", noUnknownPeers: "Sin pares desconocidos en el grafo de amistades.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Aún no hay nodos registrados.", + peerConnect: "Conectar", + peerDisconnect: "Desconectar", peerRefresh: "Refrescar", peerPruneIdle: "Quitar inactivos", + peersPause: "Pausar", + peersResume: "Reanudar", + peersPausedNote: "En pausa: Oasis no se conecta con nadie y nadie puede conectarse a él.", + peersPauseHint: "Detiene todas las conexiones: Oasis no se conecta a otros nodos ni los acepta hasta que reanudes.", peerExport: "Exportar", peerImport: "Importar", peerImportTitle: "Importar lista de peers", peerImportPlaceholder: "Pega una dirección multiserver por línea, o sube un archivo .txt…", connectionActionIntro: "", - startNetworking: "Iniciar red", - stopNetworking: "Parar red", - restartNetworking: "Reiniciar red", - sync: "Sincronizar red", indexes: "Índices", + indexesRebuilt: "Índices reconstruidos", + indexesRebuildFailed: "No se han podido reconstruir los índices", + indexesFiles: "Ficheros de índice", indexesDescription: "Reconstruir índices es seguro, y puede arreglar algunos errores en tu blockchain.", homePageTitle: "Página de inicio", homePageDescription: "Selecciona qué módulo quieres como tu página de inicio.", saveHomePage: "Establecer página de inicio", invites: "Invitaciones", - invitesDescription: "Maneja y aplica códigos de invitación en tu red.", + invitesDescription: "Gestiona y aplica códigos de invitación en tu red.", invitesPubsHint: "Pega un código de invitación de un pub para federarte con él y empezar a replicar a sus habitantes.", invitesFederationsHint: "Pubs con los que estás federado: actualízalos, quita los inalcanzables o exporta la lista.", - invitesHousesHint: "Introduce un código de casa para unirte a una casa del LARP y participar en su economía.", - invitesTribesHint: "Introduce un código de tribu para hacerte miembro y acceder a su contenido privado.", - invitesChatsHint: "Introduce un código de chat para unirte a la conversación y a sus hilos.", - invitesPadsHint: "Introduce un código de pad para escribir con otros en un documento compartido.", - invitesCalendarsHint: "Introduce un código de calendario para compartir fechas y recordatorios.", - invitesEventsHint: "Introduce un código de evento para unirte a un evento privado.", - invitesForumsHint: "Introduce un código de foro para leer y responder en un foro privado.", - invitesMapsHint: "Introduce un código de mapa para añadir y ver lugares en un mapa compartido.", - invitesShopsHint: "Introduce un código de tienda para unirte a una tienda y a su catálogo.", - invitesInhabitantsHint: "Introduce un código de habitante para seguiros mutuamente e intercambiar contenido.", invitesTribesTitle: "Tribus", - invitesTribeInviteCodePlaceholder: "Introduce el código de la tribu", - invitesTribeJoinButton: "Entrar en la tribu", invitesChatsTitle: "Chats", - invitesChatInviteCodePlaceholder: "Introduce el código del chat", - invitesChatJoinButton: "Entrar en el chat", invitesPadsTitle: "Pads", - invitesPadInviteCodePlaceholder: "Introduce el código del pad", - invitesPadJoinButton: "Entrar en el pad", invitesCalendarsTitle: "Calendarios", - invitesCalendarInviteCodePlaceholder: "Introduce el código del calendario", - invitesCalendarJoinButton: "Entrar en el calendario", invitesEventsTitle: "Eventos", - invitesEventInviteCodePlaceholder: "Introduce el código del evento", - invitesEventJoinButton: "Entrar en el evento", invitesForumsTitle: "Foros", - invitesForumInviteCodePlaceholder: "Introduce el código del foro", - invitesForumJoinButton: "Entrar en el foro", invitesMapsTitle: "Mapas", - invitesMapInviteCodePlaceholder: "Introduce el código del mapa", - invitesMapJoinButton: "Entrar en el mapa", invitesShopsTitle: "Tiendas", - invitesShopInviteCodePlaceholder: "Introduce el código de invitación de la tienda", - invitesShopJoinButton: "Unirse a la tienda", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "PUBs Sugeridos", + invitesFilterOthers: "Otras", + invitesJoinTitle: "Invitaciones", + invitesJoinHint: "Pega cualquier código de invitación, sea de lo que sea, o el Oasis ID de un habitante.", + invitesJoinPlaceholder: "Código de invitación u Oasis ID", + invitesJoinButton: "Entrar", + invitesUsedTitle: "Últimas invitaciones", + invitesUsedEmpty: "Aún no has utilizado ninguna invitación.", + invitesUsedType: "Tipo", + invitesUsedName: "Nombre", + invitesUsedDate: "Fecha", invitesPubInviteCodePlaceholder: "Introduce el código de invitación del PUB", invitesAcceptInvite: "Entrar en el PUB", + invitesPubUses: "Invitaciones usadas", + invitesPubExhausted: "Este PUB no tiene invitaciones disponibles.", invitesAlreadyFederated: "Ya estás federado con este pub.", + invitesJoinPubMode: "Este Oasis funciona como PUB, en modo de solo lectura. Para unirlo a otro PUB, usa en la terminal: ./oasis.sh join ", + invitesJoinLocalOnly: "Las invitaciones solo se aceptan desde el equipo donde funciona Oasis, o con el enlace de administración que muestra al arrancar.", invitesFederationsTitle: "Federaciones", invitesAcceptedInvites: "Federadas", invitesNoFederatedPubs: "No hay pubs federados.", - invitesUnreachablePubs: "Inalcanzables", - invitesNoUnreachablePubs: "No hay pubs inalcanzables.", invitesPubsRefresh: "Refrescar", - invitesPubsClearUnreachable: "Quitar inalcanzables", + invitesPubsClearUnreachable: "Limpiar", invitesPubsExport: "Exportar", invitesPubsImport: "Importar", invitesPubsImportTitle: "Importar pubs", invitesPubsImportPlaceholder: "Pega una dirección multiserver o código de invitación por línea, o sube un archivo .txt…", - currentlyUnreachable: "¡ERROR!", - errorDetails: "Detalles del error", - genericError: "A ocurrido un error.", - panicMode: "Modo Pánico!", - encryptData: "Establece una contraseña (min 32 caracteres de longitud) para cifrar tu blockchain", + genericError: "Ha ocurrido un error.", + panicMode: "¡Modo pánico!", + encryptData: "Establece una contraseña (mín. 32 caracteres) para cifrar tu blockchain", decryptData: "Introduce contraseña para descifrar tu blockchain", - removeDataDescription: "CUIDADO: Éste proceso no puede ser revocado.", - removePanicButton: "BORRAR TODOS TUS DATOS!", + removeDataDescription: "CUIDADO: Este proceso no se puede deshacer.", + removePanicButton: "¡BORRAR TODOS TUS DATOS!", searchTitle: "Buscar", searchDescriptionLabel: "Buscar contenido en tu red.", searchPlaceholder:"Buscar contenido...", @@ -580,16 +692,15 @@ module.exports = { statusLabel:"Estado", noResultsFound:"No se han encontrado resultados.", votesOption:"Opiniones Votadas", - voteYesLabel:"Si", + voteYesLabel:"Sí", voteNoLabel:"No", allTypesLabel: "SIN LÍMITES", - YESLabel:"SI", + YESLabel:"SÍ", NOLabel:"NO", - votesOptionYesLabel:"Si", + votesOptionYesLabel:"Sí", votesOptionNoLabel:"No", votesOptionAbstentionLabel:"Abstención", votesQuestionLabel:"Pregunta", - votesCreatedByLabel:"Creado el", voteStatusOpen:"ABIERTO", voteStatusClosed:"CERRADO", commentDescription: ({ parentUrl }) => [ @@ -598,30 +709,30 @@ module.exports = { ], commentTitle: ({ authorName }) => [`Ha comentado en el post de @${authorName}`], subtopicDescription: ({ parentUrl }) => [ - " creado un subtopic desde ", + " ha creado un subtema desde ", a({ href: parentUrl }, " un post"), ], - subtopicTitle: ({ authorName }) => [`Ha creado un subtopic en el post de @${authorName}`], + subtopicTitle: ({ authorName }) => [`Ha creado un subtema en el post de @${authorName}`], oasisDescription: "OASIS Red de Proyectos", searchSubmit: "Buscar...", votesLabel: "GOBIERNO", pdfFallbackLabel: "Documento PDF", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "TIENDAS", + voteLabel: "VOTOS", submit: "Aplicar", editProfile: "Editar Avatar", oasisIdLabel: "OasisID", spreadContent: "Replicar", welcomePmSubject: "Hello World!", - welcomePmBody: "Te damos la bienvenida a Oasis — una red social libre, peer-to-peer, federada y cifrada, con una arquitectura distribuida y solo por invitación.\n\nAlgunos lugares interesantes para empezar:\n\n- /profile — Edita tu avatar, nombre, descripción y qué módulos se muestran en tu cara pública.\n- /invites — Añade un pub para federarte con el resto de la red.\n- /peers — Mira quién está conectado, reescanea tu LAN, exporta o importa listas de peers.\n- /inhabitants — Descubre y visita los avatares de otras personas.\n- /tribes — Crea o únete a grupos privados con cifrado de extremo a extremo.\n- /inbox — Lee y envía mensajes privados.\n- /activity — Mira la actividad reciente, tuya y de tu red.\n- /publish — Escribe un post o comparte una imagen, audio, vídeo o documento.\n- /search — Busca el contenido de la red por tipo.\n\nAlgunos lugares interesantes para conectar:\n\n- /multiverse — Gestiona tus otras cuentas del multiverso, incluyendo enviar y recibir contenido.\n\nEste mensaje se envió de forma privada desde ti hacia ti, por eso no hizo falta conexión para recibirlo.", + welcomePmBody: "Te damos la bienvenida a Oasis — una red social libre, peer-to-peer, federada y cifrada, con una arquitectura distribuida y solo por invitación.\n\nAlgunos lugares interesantes para empezar:\n\n- [/avatar](/profile) — Edita tu avatar, nombre, descripción y qué módulos se muestran en tu avatar público.\n- /settings — Ajusta tu instancia local: idioma, tema, módulos, privacidad y sincronización.\n- /invites — Añade un pub para federarte con el resto de la red.\n- /peers — Mira quién está conectado, reescanea tu LAN, exporta o importa listas de peers.\n- /inhabitants — Descubre y visita los avatares de otros habitantes.\n- /tribes — Crea o únete a grupos privados con cifrado de extremo a extremo.\n- /search — Busca el contenido de la red por tipo.\n\nAlgunos lugares interesantes para conectar:\n\n- /multiverse — Gestiona tus otras cuentas del multiverso, incluyendo enviar y recibir contenido.\n\nEste mensaje se envió de forma privada desde ti hacia ti, por eso no hizo falta conexión para recibirlo.", profileSensorsSectionTitle: "Sensores", profileVisibilityActivity: "Nivel de actividad", - profileDeviceLockedHint: "Este sensor está siempre activo: permite a otros ver desde qué dispositivo te conectas y no se puede desactivar.", + profileDeviceLockedHint: "Este sensor está siempre activo: permite a otros habitantes ver desde qué dispositivo te conectas y no se puede desactivar.", profileVisibilityKarma: "Puntuación KARMA", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "Cartera ECOIN", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "RBU", + profileVisibilityWallet: "Cartera ECOin", + profileVisibilityEcoTax: "Tasa ECO", + profileVisibilityLarpSign: "Sello L.A.R.P.", profileVisibilityGpg: "Llave GPG", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Tiendas", @@ -634,17 +745,21 @@ module.exports = { profileClearnetImagesLabel: "Imágenes", profileClearnetDocumentsLabel: "Documentos", profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Mercado", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", editProfileDescription: "", profileName: "Nombre", - profileImage: "Imágen Avatar", + profileImage: "Imagen del avatar", profileGpgKey: "Clave Pública GPG (.asc)", profileGpgChip: "GPG", profileGpgDownload: "Descargar", profileGpgRemove: "Eliminar", profileDescription: "Descripción", hashtagDescription: - "Posts de habitantes de tu red que referencian éste #hashtag, ordenados por el más reciente.", + "Posts de habitantes de tu red que mencionan este #hashtag, ordenados por el más reciente.", rebuildName: "Reconstruir blockchain", wallet: "Cartera", walletAddress: "Dirección", @@ -659,7 +774,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Conf.", walletConfirm: "Confirmar", - walletDescription: "Administra tus activos digitales, incluido el envío y la recepción de ECOin, la visualización del saldo y el acceso al historial de transacciones.", + walletDescription: "Gestiona tu cartera ECOin.", walletDate: "Fecha", walletFee: "Tasa (Cuanto mayor sea la tasa, más rápido se procesará tu transacción)", walletFeeLine: ({ fee }) => `Tasa: ECO ${fee}`, @@ -667,6 +782,86 @@ module.exports = { walletReceive: "Recibir", walletReset: "Resetear", walletSend: "Enviar", + walletPayingTransfer: "Pagando transfer", + walletCreateTransferLabel: "Generar contrato (transfer)", + walletTransferNotifiedNote: "Se ha avisado del pago a la otra parte", + walletTransferCreatedNote: "Se ha creado un transfer (recibo) de este pago", + walletTransferNoPayeeNote: "Ningún habitante ha publicado esta dirección, así que no se creó transfer", + walletTransferReceiptConcept: "Pago desde la cartera", + walletPaymentPmText: "te ha enviado un pago", + walletPayeeLabel: "Destinataria", + walletConceptLabel: "Concepto", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Has recibido tu RBU.", + bankingBotPaymentTitle: "Has recibido un pago.", + bankingBotUbiPaidText: "Tu Renta Básica Universal ha sido pagada", + bankUbiPubThisNode: "Este nodo paga la RBU", + bankUbiPubConnected: "PUB de la RBU", + bankUbiPubNone: "No hay PUBs anunciando una RBU, de momento.", + bankUbiAddressPublished: "Dirección ECOin publicada", + bankUbiAddressMissing: "Publica tu dirección ECOin para recibir la RBU", + bankUbiClaimedThisMonth: "Reclamada este mes", + bankUbiPubBalance: "Cartera del PUB disponible para la RBU", + bankNoPubAddress: "El PUB de la RBU aún no ha publicado una dirección ECOin.", + bankUbiTab: "RBU", + bankDonateEco: "¡DONAR!", + donateTooltip: "Donar ECOin", + bankNoUserAddress: "Este habitante no comparte una dirección ECOin.", + bankUbiPubDefault: "Por defecto", + bankUbiPool: "Asignación", + bankUbiLastSeen: "Visto por última vez", + bankUbiLastPayout: "Última acción", + bankUbiPaidOut: "Repartido", + bankRangeToday: "Hoy", + bankRangeWeek: "Semana", + bankRangeMonth: "Mes", + bankRangeYear: "Año", + bankRangeFiveYears: "5 años", + bankRangeAll: "Todo", + bankChartValueTitle: "Evolución del valor de ECOin", + bankChartValueHours: "Equivalencia en tiempo (h)", + bankChartWealthTitle: "Redistribución de riqueza ECOin", + bankChartDistributed: "RBU repartida (ECO)", + bankChartRebalanceTitle: "Redistribución de riqueza entre PUBs", + bankChartRebalanced: "ECO movidos entre PUBs", + bankChartTaxes: "Impuestos retenidos (ECO)", + bankChartNoDataRange: "Sin datos en este rango.", + bankChartNoDataYet: "Todavía no hay datos", + bankChartPerHour: "por hora", + bankChartPerDay: "por día", + bankChartPerMonth: "por mes", + bankTotalUbiDistributed: "RBU total redistribuida (red)", + bankTotalTaxesWithheld: "Impuestos totales retenidos (red)", + bankInflationIssuance: "Tasa de emisión (anual, por recompensa de bloque)", + bankRulesEligibilityTitle: "Quién puede recibir la RBU", + bankRulesEligibility1: "Una dirección ECOin publicada en tu perfil (Perfil → Sensores → Cartera ECOin).", + bankRulesEligibility2: "Un feed con más de 30 días y actividad en la red: karma por encima de cero.", + bankRulesEligibility3: "Un reclamo por mes (epoch), desde Banking con el botón Reclamar RBU. Se puede rechazar la del mes en curso con Rechazar RBU.", + bankRulesEligibility4: "El suelo de 1 ECO se paga siempre; los impuestos solo reducen la parte que lo supera y nunca se cobran aparte.", + bankRulesEligibility5: "El PUB paga en su siguiente ciclo y publica un transfer RBU que puedes confirmar.", + bankRulesEligibility6: "Si falta tu dirección, el feed es demasiado joven, rechazaste el mes o el PUB no tiene fondos, el reclamo se salta y queda registrado en el PUB.", + copyLinkTitle: "Copiar enlace", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Rechazar RBU", + bankUbiRefusedThisMonth: "Has rechazado la RBU de este mes.", + bankingBotUbiAvailableTitle: "Puedes reclamar tu RBU.", + bankingBotUbiAvailableText: "Tu RBU de este mes está disponible", + bankingBotConfirmTitle: "Tienes un contrato pendiente de confirmación", + bankingBotConfirmText: "Contratos pendientes de confirmación", + bankChartZoomHint: "Clic para ampliar", + bankChartPoolTitle: "Asignación de la RBU (en el tiempo)", + bankChartNetworkUbiTitle: "RBU total redistribuida (red)", + bankChartSupplyTitle: "Oferta circulante de ECOin (en el tiempo)", + bankChartInflationTitle: "Inflación de ECOin (en el tiempo)", + bankChartInflationSeries: "Tasa anualizada (%)", + bankChartPool: "Asignación total (ECO)", + bankChartKarmaTitle: "KARMA Scoring (en el tiempo)", + bankChartFundsTitle: "Tus fondos ECOin (en el tiempo)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin conectada", + bankWalletNotConfigured: "ECOin desconectada", + transfersPayWithWallet: "¡PAGAR!", walletStatus: "Estado", walletDisconnected: [ "Tienes la ", @@ -676,8 +871,11 @@ module.exports = { " o el estado de tu conexión.", ], walletSentToLine: ({ destination, amount }) => `Enviar ECO ${amount} a ${destination}`, + walletNotConfigured: "Tu cartera de ECOin aún no está configurada.", walletSettingsDescription: "Integrar Oasis con tu cartera de ECOin.", - walletSettingsDocLink: "Guía de instalación de ECOin", + walletSettingsMobileDescription: "Vincula Oasis con la app ECOin Wallet de este dispositivo.", + walletDownloadEcoin: "¡Descargar ECOin!", + walletRpcUrl: "URL del RPC", walletStatusMessages: { invalid_amount: "Cantidad inválida", invalid_dest: "Dirección de destino inválida", @@ -693,22 +891,23 @@ module.exports = { walletUser: "Usuario", walletPass: "Contraseña", walletConfiguration: "Configurar cartera", + walletDisconnectButton: "Desconectar cartera", cipher: "Cifrador", cipherTitle: "Cifrador", - cipherDescription: "Encripta y desencripta tu texto de forma simétrica (usando una contraseña compartida).", + cipherDescription: "Cifra y descifra tu texto de forma simétrica (usando una contraseña compartida).", randomPassword: "Contraseña Aleatoria", - cipherTextPlaceholder: "Introduce el texto para encriptar...", - cipherPasswordLabel: "Establece una contraseña (mínimo 32 caracteres) para encriptar tu texto", - cipherPasswordDecryptLabel: "Establece una contraseña (mínimo 32 caracteres) para desencriptar tu texto", + cipherTextPlaceholder: "Introduce el texto para cifrar...", + cipherPasswordLabel: "Establece una contraseña (mínimo 32 caracteres) para cifrar tu texto", + cipherPasswordDecryptLabel: "Establece una contraseña (mínimo 32 caracteres) para descifrar tu texto", cipherPasswordPlaceholder: "Introduce una contraseña...", - cipherEncryptButton: "Encriptar", - cipherEncryptedMessageLabel: "Texto Encriptado", - cipherDecryptedMessageLabel: "Texto Desencriptado", - cipherEncryptedTextPlaceholder: "Introduce el texto encriptado...", - cipherDecryptButton: "Desencriptar", + cipherEncryptButton: "Cifrar", + cipherEncryptedMessageLabel: "Texto cifrado", + cipherDecryptedMessageLabel: "Texto descifrado", + cipherEncryptedTextPlaceholder: "Introduce el texto cifrado...", + cipherDecryptButton: "Descifrar", password: "Contraseña", text: "Texto", - encryptedText: "Texto Encriptado", + encryptedText: "Texto cifrado", iv: "Vector de Inicialización (IV)", bookmarkTitle: "Marcadores", bookmarkDescription: "Descubre y gestiona marcadores en tu red.", @@ -726,17 +925,16 @@ module.exports = { bookmarkFilterRecent: "RECIENTES", bookmarkCreateButton: "Crear marcador", bookmarkUpdateButton: "Actualizar", - bookmarkDeleteButton: "Eliminar", bookmarkAddFavoriteButton: "Añadir favorito", bookmarkRemoveFavoriteButton: "Quitar favorito", bookmarkUrlLabel: "Enlace", - bookmarkUrlPlaceholder: "https://ejemplo.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Descripción", bookmarkDescriptionPlaceholder: "Opcional", bookmarkTagsLabel: "Etiquetas", - bookmarkTagsPlaceholder: "Introduce tags separadas por comas", + bookmarkTagsPlaceholder: "Introduce etiquetas separadas por comas", bookmarkLastVisitLabel: "Última visita", - bookmarkSearchPlaceholder: "Buscar URL, tags, categoría, autor...", + bookmarkSearchPlaceholder: "Buscar en Marcadores...", bookmarkSortRecent: "Más recientes", bookmarkSortOldest: "Más antiguos", bookmarkSortTop: "Más votados", @@ -775,7 +973,7 @@ module.exports = { videoDescriptionPlaceholder: "Opcional", videoNoFile: "No se ha proporcionado ningún archivo de vídeo", noVideos: "No hay vídeos disponibles.", - videoSearchPlaceholder: "Buscar por título, etiquetas, autor...", + videoSearchPlaceholder: "Buscar en Vídeos...", videoSortRecent: "Más recientes", videoSortOldest: "Más antiguos", videoSortTop: "Más votados", @@ -798,7 +996,6 @@ module.exports = { documentFilterFavorites: "FAVORITOS", documentCreateButton: "Subir documento", documentUpdateButton: "Actualizar", - documentDeleteButton: "Eliminar", documentAddFavoriteButton: "Añadir a favoritos", documentRemoveFavoriteButton: "Quitar de favoritos", documentMessageAuthorButton: "MP", @@ -811,7 +1008,7 @@ module.exports = { documentDescriptionPlaceholder: "Opcional", documentNoFile: "Sin archivo.", noDocuments: "No hay documentos disponibles.", - documentSearchPlaceholder: "Buscar título, etiquetas, descripción, autor...", + documentSearchPlaceholder: "Buscar en Documentos...", documentSortRecent: "Más recientes", documentSortOldest: "Más antiguos", documentSortTop: "Más votados", @@ -832,19 +1029,19 @@ module.exports = { audioFilterTop: "TOP", audioCreateButton: "Subir audio", audioUpdateButton: "Actualizar", - audioDeleteButton: "Eliminar", audioAddFavoriteButton: "Añadir a favoritos", audioRemoveFavoriteButton: "Quitar de favoritos", audioFileLabel: "Selecciona un archivo de audio (.mp3, .wav, .ogg)", audioTagsLabel: "Etiquetas", audioTagsPlaceholder: "Introduce etiquetas separadas por comas", + tagsPlaceholder: "Introduce etiquetas separadas por comas", audioTitleLabel: "Título", audioTitlePlaceholder: "Opcional", audioDescriptionLabel: "Descripción", audioDescriptionPlaceholder: "Opcional", audioNoFile: "No se ha proporcionado ningún archivo de audio", noAudios: "No hay audios disponibles.", - audioSearchPlaceholder: "Buscar por título, etiquetas, autor...", + audioSearchPlaceholder: "Buscar en Audios...", audioSortRecent: "Más recientes", audioSortOldest: "Más antiguos", audioSortTop: "Más votados", @@ -856,7 +1053,7 @@ module.exports = { audioFilterFavorites: "FAVORITOS", favoritesTitle: "Favoritos", favoritesDescription: "Todos tus elementos marcados como favoritos en un solo lugar.", - favoritesSearchPlaceholder: "Buscar en favoritos...", + favoritesSearchPlaceholder: "Buscar en Favoritos...", favoritesFilterAll: "TODOS", favoritesFilterRecent: "RECIENTES", favoritesFilterAudios: "AUDIOS", @@ -868,13 +1065,12 @@ module.exports = { favoritesFilterChats: "CHATS", favoritesFilterCalendars: "CALENDARIOS", favoritesFilterVideos: "VÍDEOS", - favoritesRemoveButton: "Quitar de favoritos", favoritesNoItems: "Todavía no hay favoritos.", yourContacts: "Tus Contactos", allInhabitants: "Habitantes", allCVs: "CVs", discoverPeople: "Descubre habitantes en tu red.", - searchInhabitantsPlaceholder: "FILTRAR habitantes POR NOMBRE …", + searchInhabitantsPlaceholder: "Buscar en Habitantes...", filterLocation: "FILTRAR habitantes POR UBICACIÓN …", filterLanguage: "FILTRAR habitantes POR IDIOMA …", filterSkills: "FILTRAR habitantes POR HABILIDADES …", @@ -884,7 +1080,7 @@ module.exports = { languagesLabel: "Idiomas", skillsLabel: "Habilidades", commonSkills: "Habilidades Comunes", - mutualFollowers: "Seguidores Mutuos", + mutualFollowers: "Seguidoras Mutuas", suggestedFollowsYou: "Te sigue", latestInteractions: "Últimas Interacciones", suggestedSectionTitle: "Sugeridos", @@ -893,15 +1089,14 @@ module.exports = { topactivitySectionTitle: "Top Actividad", "TOP INACTIVITYButton": "MÁS INACTIVOS", topinactivitySectionTitle: "Habitantes menos activos", - blockedSectionTitle: "Bloqueados", + blockedSectionTitle: "Bloqueadas", gallerySectionTitle: "GALERÍA", topSectionTitle: "TOP", - blockedLabel: "Usuario Bloqueado", + blockedLabel: "Habitante bloqueada", inhabitantviewDetails: "Ver Detalles", - cvVisitButton: "Visitar CV", keepReading: "Seguir leyendo...", oasisId: "ID", - noInhabitantsFound: "No se encontraron habitantes, aún.", + noInhabitantsFound: "Aún no se han encontrado habitantes.", inhabitantActivityLevel: "Nivel Actividad", lifespanLabel: "Vigencia", deviceLabel: "Dispositivo", @@ -916,6 +1111,18 @@ module.exports = { parliamentFilterLeaders: "LÍDERES", parliamentFilterRules: "REGLAS", parliamentGovernmentCard: "Gobierno actual", + parliamentSeatsGovernment: "Gobierno", + parliamentSeatsOpposition: "Oposición", + parliamentSeatsAbstention: "Abstención", + parliamentSeatsQuorum: "Cuórum de elección", + parliamentSeatsPass: "Umbral de aprobación", + parliamentSeatsByElection: "Por elección", + parliamentSeatsByHouses: "Por casa del LARP", + parliamentSeatsSeats: "escaños", + parliamentSeatsSeat: "escaño", + parliamentSeatsLeader: "Líder", + parliamentSeatsVotesFor: "votos a", + parliamentSeatsNoHouse: "Sin casa", parliamentActorInPowerInhabitant: 'HABITANTE EN EL PODER', parliamentActorInPowerTribe: 'TRIBU EN EL PODER', parliamentHistoricalGovernmentsTitle: 'GOBIERNOS', @@ -983,20 +1190,22 @@ module.exports = { parliamentTimeRemaining: "Tiempo restante", parliamentElectionsStatusTitle: "Próximo Gobierno", parliamentProposalDeadlineLabel: "Fecha límite", + parliamentViewReport: "Ver informe", parliamentProposalTimeLeft: "Tiempo restante", parliamentRulesTitle: "Cómo funciona el Parlamento", parliamentRulesIntro: "Las elecciones se resuelven cada 2 meses; las candidaturas son continuas y se reinician cuando se elige un gobierno.", - parliamentRulesCandidates: "Cualquier habitante puede proponerse a sí mismo, a otro habitante o a cualquier tribu. Cada habitante puede proponer hasta 3 candidaturas por ciclo; se rechazan duplicados en el mismo ciclo.", + parliamentRulesCandidates: "Cualquier habitante puede proponerse a sí misma, a otra habitante o a cualquier tribu. Cada habitante puede proponer hasta 3 candidaturas por ciclo; se rechazan duplicados en el mismo ciclo.", parliamentRulesElection: "Gana la candidatura con más votos en el momento de la resolución.", - parliamentRulesTies: "Desempate: mayor karma del habitante; si persiste, perfil más antiguo; si persiste, propuesta más temprana; después, orden lexicográfico por ID.", + parliamentRulesTies: "Desempate: mayor karma de la habitante; si persiste, perfil más antiguo; si persiste, propuesta más temprana; después, orden lexicográfico por ID.", parliamentRulesTerm: "La pestaña Gobierno muestra el gobierno actual y sus estadísticas.", parliamentRulesMethods: "Formas: Anarquía (mayoría simple), Democracia (50%+1), Mayoría (80%), Minoría (20%), Karmatocracia (propuestas con mayor karma), Dictadura (aprobación instantánea).", parliamentRulesAnarchy: "Anarquía es el modo por defecto: si al resolver no se elige ninguna candidatura, se proclama la Anarquía. En Anarquía, cualquier habitante puede proponer leyes.", - parliamentRulesProposals: "Si eres el habitante gobernante o miembro de la tribu gobernante, puedes publicar propuestas de ley. Los métodos no dictatoriales crean una votación pública.", + parliamentRulesProposals: "Si eres la habitante gobernante o miembro de la tribu gobernante, puedes publicar propuestas de ley. Los métodos no dictatoriales crean una votación pública.", parliamentRulesLimit: "Cada habitante puede publicar como máximo 3 propuestas de ley por ciclo.", parliamentRulesLaws: "Cuando una propuesta alcanza su umbral, se convierte en Ley y aparece en la pestaña Leyes con su fecha de entrada en vigor.", parliamentRulesHistorical: "En Histórico se puede ver cada ciclo de gobierno que ha habido y datos sobre su gestión.", parliamentRulesLeaders: "En Líderes se puede ver un ranking de habitantes/tribus que han gobernado (o se han presentado), ordenados por eficacia.", + parliamentRulesHemicycle: "El hemiciclo da un escaño por habitante: el bloque de gobierno (votantes de la candidatura ganadora y quien lidera), un bloque de oposición por cada otra candidatura del ciclo, y la abstención. Las marcas discontinuas señalan el cuórum de elección (25 % del censo) y el umbral de aprobación del método vigente. \"Por casa del LARP\" colorea los mismos escaños por casa.", parliamentProposalVoteStatusLabel: "Estado de la votación", parliamentProposalOnTrackYes: "Umbral alcanzado", parliamentProposalOnTrackNo: "Por debajo del umbral", @@ -1004,12 +1213,13 @@ module.exports = { courtsDescription: "Explora formas de resolución de conflictos y de gestión colectiva de la justicia.", courtsFilterCases: "CASOS", courtsFilterMyCases: "MÍOS", - courtsFilterJudges: "JUECES", + courtsFilterJudges: "JUEZAS", courtsFilterHistory: "HISTORIAL", courtsFilterRules: "REGLAS", courtsCaseFormTitle: "Abrir caso", - courtsCaseRespondent: "Acusado / Parte demandada", + courtsCaseRespondent: "Acusada / Parte demandada", courtsCaseRespondentPh: "ID de Oasis (@...) o nombre de Tribu", + courtsCaseSubjectPlaceholder: "Asunto o descripción breve", courtsCaseMethod: "Método de resolución", courtsCaseSubmit: "Presentar caso", courtsNominateJudge: "Nominar juez", @@ -1041,18 +1251,33 @@ module.exports = { courtsMediatorsSubmit: "Guardar mediadores", courtsNoMyCases: "Aún no tienes conflictos.", courtsNoHistory: "Todavía no hay juicios registrados.", - courtsMethodDICTATOR: "Dictador", + courtsMethodDICTATOR: "Dictadora", courtsMethodPOPULAR: "Popular", courtsMethodKARMATOCRACY: "Karmatocracia", + courtsCaseType: "Tipo de caso", + courtsPresetMinorConflict: "Conflicto leve", + courtsPresetModerateConflict: "Conflicto moderado", + courtsPresetSevereConflict: "Conflicto grave", + courtsPresetHarassment: "Acoso o abuso", + courtsPresetContentModeration: "Moderación de contenido", + courtsPresetBanRestriction: "Expulsión o restricción", + courtsPresetGovernanceDispute: "Disputa de gobernanza", + courtsStatusOPEN: "Abierto", + courtsStatusIN_PROGRESS: "En curso", + courtsStatusDECIDED: "Decidido", + courtsStatusCLOSED: "Cerrado", + courtsStatusSOLVED: "Resuelto", + courtsStatusUNSOLVED: "Sin resolver", + courtsStatusDISCARDED: "Descartado", courtsRulesTitle: "Cómo funcionan los Tribunales", courtsRulesIntro: "Los Tribunales son un proceso gestionado por la comunidad para resolver conflictos y promover la justicia restaurativa. Se priorizan el diálogo, las pruebas claras y los remedios proporcionales.", courtsRulesLifecycle: "Proceso: 1) Abrir caso 2) Seleccionar método 3) Presentar pruebas 4) Audiencia y deliberación 5) Veredicto y remedio 6) Cumplimiento y cierre 7) Apelación (si procede).", - courtsRulesRoles: "Acusación: abre el caso. Defensa: persona o tribu acusada. Método: mecanismo elegido por la comunidad para facilitar, evaluar las pruebas y emitir veredicto. Testigo: aporta testimonio o pruebas. Mediadores: personas neutrales invitadas por la acusación y/o la defensa, con acceso a todos los detalles, que ayudan a desescalar el conflicto y co-crear acuerdos.", + courtsRulesRoles: "Acusación: abre el caso. Defensa: habitante o tribu acusada. Método: mecanismo elegido por la comunidad para facilitar, evaluar las pruebas y emitir veredicto. Testigo: aporta testimonio o pruebas. Mediadores: habitantes neutrales invitadas por la acusación y/o la defensa, con acceso a todos los detalles, que ayudan a desescalar el conflicto y co-crear acuerdos.", courtsRulesEvidence: "Descripción de hasta 1000 caracteres. Adjunta imágenes, audio, vídeo y documentos PDF relevantes y legales. No compartas datos privados sensibles sin consentimiento.", - courtsRulesDeliberation: "Las audiencias pueden ser públicas o privadas. Los jueces garantizan el respeto, piden aclaraciones y pueden descartar material irrelevante o ilegal.", + courtsRulesDeliberation: "Las audiencias pueden ser públicas o privadas. Las juezas garantizan el respeto, piden aclaraciones y pueden descartar material irrelevante o ilegal.", courtsRulesVerdict: "Se prioriza la restauración: disculpas, acuerdos de mediación, moderación de contenido, restricciones temporales u otras medidas proporcionales. El razonamiento debe quedar registrado.", courtsRulesAppeals: "Apelación: permitida cuando hay nuevas pruebas o un error procesal claro. Debe presentarse en un plazo de 7 días salvo que se indique lo contrario.", - courtsRulesPrivacy: "Respeta la privacidad y la seguridad. El doxing, el odio o las amenazas se eliminan. Los jueces pueden editar o sellar partes del expediente para proteger a personas en riesgo.", + courtsRulesPrivacy: "Respeta la privacidad y la seguridad. El doxing, el odio o las amenazas se eliminan. Las juezas pueden editar o sellar partes del expediente para proteger a habitantes en riesgo.", courtsRulesMisconduct: "El acoso, la manipulación o las pruebas fabricadas pueden llevar a una resolución negativa inmediata.", courtsRulesGlossary: "Caso: registro de un conflicto. Pruebas: materiales que respaldan las afirmaciones. Veredicto: decisión con remedio. Apelación: solicitud para revisar el veredicto.", courtsFilterActions: "ACCIONES", @@ -1061,19 +1286,23 @@ module.exports = { courtsRoleDefence: "Defensa", courtsRoleMediator: "Mediador", courtsRoleJudge: "Juez", - courtsRoleDictator: "Dictador", + courtsRoleDictator: "Dictadora", courtsAssignJudgeTitle: "Elegir juez", courtsAssignJudgeBtn: "Elegir juez", trendingTitle: "Tendencias", exploreTrending: "Explora el contenido más popular en tu red.", votesButton: "GOBIERNO", shopProductButton: "TIENDA", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISODIO", + campaignButton: "CAMPAÑA", + logisticsRouteButton: "RUTA", trendingTotalOpinions: "Total de Opiniones", trendingNoContentMessage: "Todavía no hay tendencias.", trendingTotalCount: "Total de Contadores", tasksTitle: "Tareas", tasksDescription: "Descubre y gestiona tareas en tu red.", - taskSearchPlaceholder: "Buscar tareas...", + taskSearchPlaceholder: "Buscar en Tareas...", taskTitleLabel: "Título", taskDescriptionLabel: "Descripción", taskStartTimeLabel: "Fecha de Inicio", @@ -1092,7 +1321,7 @@ module.exports = { taskStatusOpen: "Abierta", taskStatusInProgress: "En Progreso", taskStatusClosed: "Cerrada", - taskAssignedTo: "Asignada a", + taskAssignedTo: "Asignada", taskAssignedChip: "Asignada", taskUnassignedChip: "Sin asignar", taskAssignees: "Asignados", @@ -1102,6 +1331,7 @@ module.exports = { taskUpdateButton: "Actualizar", taskDeleteButton: "Eliminar", taskFilterAll: "TODOS", + taskFilterRecent: "RECIENTES", taskFilterMine: "MÍAS", taskFilterOpen: "ABIERTAS", taskFilterInProgress: "EN-PROCESO", @@ -1129,14 +1359,13 @@ module.exports = { eventUpcomingDates: "Próximas fechas", eventsTitle: "Eventos", eventsDescription: "Descubre y gestiona eventos en tu red.", - eventSearchPlaceholder: "Buscar eventos...", + eventSearchPlaceholder: "Buscar en Eventos...", eventPrice: "Precio", eventStatus: "Estado", eventAllSectionTitle: "Eventos", eventMineSectionTitle: "Tus Eventos", eventCreateSectionTitle: "Crear Evento", eventUpdateSectionTitle: "Actualizar Evento", - eventDeleteButton: "Eliminar", eventCreateButton: "Crear Evento", eventAddToCalendar: "Añadir al Calendario", eventVisitCalendar: "Visitar Calendario", @@ -1145,7 +1374,7 @@ module.exports = { viewPad: "Ver pad", viewMap: "Ver mapa", viewCalendar: "Ver calendario", - viewReport: "Ver Reporte", + viewReport: "Ver informe", viewTransfer: "Ver Transferencia", viewItem: "Ver Item", viewShop: "Ver Tienda", @@ -1154,11 +1383,11 @@ module.exports = { viewPlace: "Ver Lugar", generatePdf: "Generar PDF", sharePm: "Compartir por PM", - galleryImages: "Imágenes (tamaño máx: 50MB cada una)", + galleryImages: "Imágenes (tamaño máx: 75MB cada una)", galleryPhotos: "Imágenes", galleryAddPhoto: "Añadir Imagen", galleryRemovePhoto: "Quitar", - galleryVideo: "Vídeo (tamaño máx: 50MB)", + galleryVideo: "Vídeo (tamaño máx: 75MB)", galleryAddVideo: "Añadir Vídeo", galleryRemoveVideo: "Quitar vídeo", viewVotation: "Ver Votación", @@ -1178,7 +1407,8 @@ module.exports = { eventDate: "Fecha", eventAttendButton: "Asistir al Evento", eventFilterAll: "TODOS", - eventFilterMine: "MIOS", + eventFilterRecent: "RECIENTES", + eventFilterMine: "MÍOS", eventFilterToday: "HOY", eventFilterWeek: "ESTA SEMANA", eventFilterMonth: "ESTE MES", @@ -1204,7 +1434,7 @@ module.exports = { tagsFilterTop: "TOP", tagsFilterCloud: "NUBE", tagsNoItems: "No hay etiquetas disponibles.", - tagsSearchPlaceholder: "Buscar etiquetas...", + tagsSearchPlaceholder: "Buscar en Etiquetas...", tagsTableHeaderTag: "ETIQUETA/ENLACE:", tagsTableHeaderCount: "CONTADOR:", transfersTitle: "Transferencias", @@ -1212,7 +1442,10 @@ module.exports = { transfersFrom: "De", transfersTo: "A", transfersFilterAll: "TODOS", + transfersFilterRecent: "RECIENTES", transfersFilterMine: "MÍAS", + transfersFilterMinePending: "PENDIENTES", + transfersFilterMineConfirmed: "FIRMADOS", transfersFilterUBI: "RBU", transfersFilterMarket: "MERCADO", transfersFilterTop: "TOP", @@ -1227,7 +1460,6 @@ module.exports = { transfersCategoryEconomic: "Económica", transfersCategoryTime: "Tiempo", transfersCategoryTrust: "Confianza", - transfersCategoryApply: "Aplicar", transfersUnitEco: "ECO", transfersUnitHours: "h", transfersUnitTrust: "confianza", @@ -1257,12 +1489,12 @@ module.exports = { transfersDiscardedSectionTitle: "Transferencias Descartadas", transfersCreateSectionTitle: "Crear transferencia", transfersAllSectionTitle: "Transferencias", - transfersSearchPlaceholder: "Buscar por concepto, tags, usuarios...", + transfersSearchPlaceholder: "Buscar en Transferencias...", transfersMinAmountLabel: "Importe mín.", transfersMaxAmountLabel: "Importe máx.", transfersSortRecent: "Más recientes", transfersSortAmount: "Mayor importe", - transfersSortDeadline: "Deadline más próximo", + transfersSortDeadline: "Plazo más próximo", transfersSearchButton: "Buscar", transfersExpiredBadge: "CADUCADA", transfersUpdatedAt: "Actualizado", @@ -1279,6 +1511,7 @@ module.exports = { voteErrorTitle: "Revisa el formulario", voteErrorDeadlineMin: "La fecha límite debe ser de al menos {days} días desde ahora.", voteErrorGeneric: "No se ha podido guardar la votación.", + voteLocked: "Esta votación ya ha recibido votos u opiniones, o pertenece al Parlamento o a los Tribunales: ya no se puede editar ni borrar.", voteUpdateButton: "Actualizar", voteDeleteButton: "Eliminar", voteOptionYes: "SÍ", @@ -1288,6 +1521,7 @@ module.exports = { voteFollowMajority: "SEGUIR MAYORÍA", voteNotInterested: "SIN INTERÉS", voteFilterAll: "TODOS", + voteFilterRecent: "RECIENTES", voteFilterMine: "MÍAS", voteFilterOpen: "ABIERTAS", voteFilterClosed: "CERRADAS", @@ -1303,16 +1537,16 @@ module.exports = { voteTags: "Etiquetas", voteOpinions: "Opiniones", novotes: "No hay propuestas de votación disponibles.", - voteBy: "Por", voteCreatedAt: "Creado el", voteNoQuestion: "No se proporcionó pregunta", - voteUnknownCreator: "Creador Desconocido", + voteUnknownCreator: "Creadora desconocida", voteUnknownDate: "Fecha Desconocida", voteDetailSectionTitle: 'Detalles de la votación', voteCommentsLabel: 'Comentarios', voteCommentsForumButton: 'Abrir conversación', voteCommentsSectionTitle: 'Conversación abierta', - voteNoCommentsYet: 'Todavía no hay comentarios. Sé la primera persona en opinar.', + voteNoCommentsYet: 'Todavía no hay comentarios. Sé el primer habitante en opinar.', + commentsGoToLast: "Ir al último comentario", voteNewCommentPlaceholder: 'Escribe aquí tu comentario…', voteNewCommentButton: 'Publicar comentario', voteNewCommentLabel: 'Añade un comentario', @@ -1323,6 +1557,8 @@ module.exports = { cvNameLabel: "Nombre Completo", cvDescriptionLabel: "Resumen", cvPhotoLabel: "Foto", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Visitar CV", cvPersonalExperiencesLabel: "Experiencias Personales", cvPersonalSkillsLabel: "Habilidades Personales (separadas por comas)", cvOasisExperiencesLabel: "Experiencias de Contribución en Oasis", @@ -1352,9 +1588,9 @@ module.exports = { cvNoCV: "No se encontró ningún CV.", blogSubject: "Asunto", blogMessage: "Mensaje", - blogImage: "Subir contenido multimedia (max: 50MB)", + blogImage: "Subir contenido multimedia (máx.: 75MB)", blogMedia: "Adjuntos (hasta 8 imágenes y un vídeo)", - blogPublish: "Vista previa", + blogPublish: "Publicar", pollsTitle: "Encuestas", dataTitle: "Coincidencias", dataDescription: "Explora y visualiza las coincidencias de tu red.", @@ -1373,12 +1609,23 @@ module.exports = { dataKindTasks: "TAREAS", dataKindReports: "INFORMES", dataKindVotes: "VOTACIONES", - dataSearchPlaceholder: "Buscar en los datos cruzados...", + dataSearchPlaceholder: "Buscar en Coincidencias...", dataCohesionTitle: "Coeficiente de Cohesión (CC)", dataCcShort: "CC", dataCohesionHint: "Afinidad media entre todo lo que la red ha publicado.", dataAffinity: "Afinidad", dataCommonTerms: "Palabra clave", + dataReasonFollowing: "Das soporte a este habitante", + dataReasonTribe: "Comparte tribu contigo", + dataReasonPinned: "Coincide con algo que has fijado", + dataReasonCv: "Coincide con las habilidades de tu CV", + dataReasonContent: "Relacionado con lo que publicas", + dataReasonRelated: "Relacionado con tus intereses", + dataReasonAlike: "Publicado por una habitante afín a ti", + dataReasonSupportsYou: "Te da soporte", + dataReasonMutual: "Soporte mutuo", + dataReasonRated: "Bien valorado por la red", + dataReasonNear: "Cerca de ti", dataStatPeople: "CVs", dataStatConnected: "Conectados", dataStatSkills: "Habilidades", @@ -1431,7 +1678,7 @@ module.exports = { pollStatusLabel: "Estado", pollStatusOpen: "ABIERTA", pollStatusClosed: "CERRADA", - pollSearchPlaceholder: "Buscar encuestas...", + pollSearchPlaceholder: "Buscar en Encuestas...", pollsNoItems: "No se han encontrado encuestas.", viewPoll: "Ver Encuesta", pollChatCreate: "Crear Encuesta", @@ -1444,10 +1691,10 @@ module.exports = { blogFilterFavorites: "FAVORITOS", blogFilterTop: "TOP", blogCreateButton: "Crear Blog", - blogSearchPlaceholder: "Buscar blogs...", + blogSearchPlaceholder: "Buscar en Blogs...", blogComments: "Comentarios", blogAllowComments: "Permitir comentarios", - blogCommentsClosed: "El autor ha cerrado los comentarios de este blog.", + blogCommentsClosed: "La autora ha cerrado los comentarios de este blog.", blogNoItems: "No se han encontrado blogs.", viewBlog: "Ver Blog", forumTitle: "Foros", @@ -1457,18 +1704,17 @@ module.exports = { forumCreateButton: "Crear foro", forumCreateSectionTitle: "Crear foro", forumDescription: "Habla abiertamente con habitantes de tu red.", - forumSearchPlaceholder: "Buscar foros...", + forumSearchPlaceholder: "Buscar en Foros...", forumFilterAll: "TODOS", forumFilterMine: "MÍAS", forumFilterRecent: "RECIENTES", forumFilterTop: "TOP", - forumDeleteButton: "Borrar", forumParticipants: "participantes", forumMessages: "mensajes", forumMessageLabel: "Mensaje", forumMessagePlaceholder: "Escribe tu mensaje...", forumSendButton: "Enviar", - noForums: "No hay foros disponibles, aún.", + noForums: "Aún no hay foros disponibles.", forumVisitButton: "Visitar Foro", forumTopicLabel: "Tema", imageTitle: "Imágenes", @@ -1489,7 +1735,6 @@ module.exports = { imageFilterGallery: "GALERÍA", imageCreateButton: "Subir imagen", imageUpdateButton: "Actualizar", - imageDeleteButton: "Eliminar", imageAddFavoriteButton: "Añadir favorito", imageRemoveFavoriteButton: "Quitar favorito", imageFileLabel: "Selecciona un archivo de imagen (.jpeg, .jpg, .png, .gif)", @@ -1501,20 +1746,21 @@ module.exports = { imageDescriptionPlaceholder: "Opcional", imageNoFile: "No se ha proporcionado ningún archivo de imagen", noImages: "No hay imágenes disponibles.", - imageSearchPlaceholder: "Buscar por título, etiquetas, descripción, autor...", + imageSearchPlaceholder: "Buscar en Imágenes...", imageSortRecent: "Más recientes", imageSortOldest: "Más antiguas", imageSortTop: "Más votadas", imageSearchButton: "Buscar", imageUpdatedAt: "Actualizado", imageNoMatch: "Ninguna imagen coincide con tu búsqueda.", + feedSearchPlaceholder: "Buscar en Feed...", feedTitle: "Feed", feedDetailTitle: "Feed", feedOpenDiscussion: "Abrir discusión", feedPostComment: "Publicar comentario", noComments: "Aún no hay comentarios", createFeedTitle: "Crear Feed", - createFeedButton: "Enviar Feed!", + createFeedButton: "¡Enviar feed!", feedPlaceholder: "¿Qué está pasando? (máximo 280 caracteres)", TODAYButton: "HOY", totalOpinions: "Total de Opiniones", @@ -1522,11 +1768,11 @@ module.exports = { noFeedsFound: "No se encontraron feeds.", createdAtLabel: "Creado el", FeedshareYourOpinions: "Descubre y comparte textos breves en tu red.", - refeedButton: "Realimentar", + refeedButton: "Reenviar", alreadyRefeeded: "Ya has realimentado esto.", activityTitle: "Actividad", yourActivity: "Tu actividad", - globalActivity: "Actividad global", + globalActivity: "Actividad", activityList: "Actividad", activityDesc: "Consulta la actividad más reciente de tu red.", allButton: "TODO", @@ -1538,9 +1784,10 @@ module.exports = { concept: "Concepto", description: "Descripción", meme: "Meme", - typeRecent: "RECIENTE", + typeRecent: "RECIENTES", typeTop: "TOP", - typePost: "BLOGS", + typePost: "BLOG", + activityUpdateLabel: "ACTUALIZACIÓN", typeTribe: "TRIBUS", typeLarp: "L.A.R.P.", typeInhabitants: "HABITANTES", @@ -1561,14 +1808,14 @@ module.exports = { typeTask: "TAREAS", typePixelia: "PIXELIA", typeForum: "FOROS", - typeReport: "REPORTES", + typeReport: "INFORMES", typeFeed: "FEEDS", typeContact: "CONTACTO", typePub: "PUB", typeTombstone: "ELIMINADO", typeBanking: "BANCA", - typeBankWallet: "BANCA/MONEDERO", - typeBankClaim: "BANCA/UBI", + typeBankWallet: "BANCA/CARTERA", + typeBankClaim: "BANCA/RBU", typeKarmaScore: "KARMA", typeParliament: "PARLAMENTO", typeSpread: "RÉPLICAS", @@ -1602,8 +1849,8 @@ module.exports = { attendees: "Asistentes", location: "Ubicación", contentWarning: "Asunto", - bankWalletConnected: "Monedero ECOin", - bankUbiReceived: "UBI recibido", + bankWalletConnected: "Cartera ECOin", + bankUbiReceived: "RBU recibida", bankTx: "Tx", bankEpochShort: "Época", viewDetails: "Ver detalles", @@ -1638,7 +1885,7 @@ module.exports = { courtsCaseTitle: "Caso", courtsMethod: "Método", courtsMethodJUDGE: "Juez", - courtsMethodJUDGES: "Panel de jueces", + courtsMethodJUDGES: "Panel de juezas", courtsMethodSINGLE_JUDGE: "Juez único", courtsMethodJURY: "Jurado", courtsMethodCOUNCIL: "Consejo", @@ -1674,11 +1921,11 @@ module.exports = { courtsNoCases: 'No hay casos.', reportsTitle: "Informes", reportsDescription: "Gestiona y sigue los reportes de tu red.", - reportsSearchPlaceholder: "Buscar reportes...", + reportsSearchPlaceholder: "Buscar en Informes...", reportsFilterAll: "TODOS", reportsFilterRecent: "RECIENTES", reportsFilterTop: "TOP", - reportsFilterMine: "MIOS", + reportsFilterMine: "MÍOS", reportsFilterFeatures: "FUNCIONES", reportsFilterBugs: "ERRORES", reportsFilterAbuse: "ABUSOS", @@ -1687,7 +1934,7 @@ module.exports = { reportsFilterResolved: "RESUELTOS", reportsFilterOpen: "ABIERTOS", reportsFilterUnderReview: "EN REVISIÓN", - reportsFilterInvalid: "INVALIDOS", + reportsFilterInvalid: "INVÁLIDOS", reportsCreateButton: "Crear Informe", reportsTitleLabel: "Título", reportsCategory: "Categoría", @@ -1714,7 +1961,7 @@ module.exports = { reportsStatusUnderReview: "En Revisión", reportsStatusResolved: "Resuelto", reportsStatusInvalid: "Inválido", - reportsConfirmButton: "CONFIRMAR INFORME!", + reportsConfirmButton: "¡CONFIRMAR INFORME!", reportsConfirmations: "Confirmaciones", reportsConfirmedSectionTitle: "Informes Confirmados", reportsCreateTaskButton: "Crear Tarea", @@ -1722,7 +1969,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Informes en Revisión", reportsResolvedSectionTitle: "Informes Resueltos", reportsInvalidSectionTitle: "Informes Inválidos", - reportsTemplateSectionTitle: 'Plantilla de reporte', reportsBugTemplateTitle: 'Datos para reproducir (Bugs)', reportsFeatureTemplateTitle: 'Detalles (Features)', reportsAbuseTemplateTitle: 'Detalles (Abuso)', @@ -1755,7 +2001,7 @@ module.exports = { reportsEvidenceLinksLabel: 'Evidencia / enlaces', reportsEvidenceLinksPlaceholder: 'Pega enlaces, IDs de mensajes, capturas (si aplica), etc.', reportsContentLocationLabel: 'Dónde está el contenido', - reportsContentLocationPlaceholder: 'Enlace, ID, canal, hilo, autor, etc.', + reportsContentLocationPlaceholder: 'Enlace, ID, canal, hilo, autoría, etc.', reportsWhyInappropriatePlaceholder: 'Explica el motivo y el impacto.', reportsRequestedActionLabel: 'Acción solicitada', reportsRequestedActionPlaceholder: 'Eliminar, ocultar, marcar, advertir, etc.', @@ -1781,17 +2027,15 @@ module.exports = { tribeGallerySectionTitle: "Galería de Tribus", tribeRecentSectionTitle: "Tribus Recientes", tribeTopSectionTitle: "Tribus Populares", - tribeviewTribeButton: "Visitar Tribu", - tribeviewSubTribeButton: "Visitar Sub-Tribu", tribeRootLabel: "RAÍZ", tribeDescription: "Explora o crea tribus en tu red.", tribeMainTribeLabel: "TRIBU PRINCIPAL", tribeCreateButton: "Crear Tribu", tribeUpdateButton: "Actualizar", tribeDeleteButton: "Eliminar", - tribeImageLabel: "Subir contenido multimedia (max: 50MB)", + tribeImageLabel: "Subir contenido multimedia (máx.: 75MB)", tribeTitleLabel: "Título", - searchTribesPlaceholder: "FILTRAR tribus POR NOMBRE …", + searchTribesPlaceholder: "Buscar en Tribus...", tribeTitlePlaceholder: "Nombre de la tribu", tribeDescriptionLabel: "Descripción", tribeDescriptionPlaceholder: "Describe esta tribu", @@ -1804,6 +2048,15 @@ module.exports = { tribeIsAnonymousLabel: "ESTADO", tribeMembersCount: "Miembros", tribeEnterInvite: "INTRODUCIR INVITACIÓN", + subscriptionTitle: "Suscripciones", + subscriptionOn: "Suscrita", + subscriptionOff: "No suscrita", + subscriptionSubscribe: "Suscribirse", + subscriptionUnsubscribe: "Cancelar suscripción", + subscriptionSubscribers: "Suscriptoras", + pmListLabel: "Enviar a", + pmListNone: "— Sin lista —", + pmListNoSubscribers: "Sin suscriptoras", tribeLeaveButton: "DEJAR", tribePublic: "PÚBLICA", tribePrivate: "PRIVADA", @@ -1819,9 +2072,9 @@ module.exports = { tribeFeedRefeed: "Redifundir", tribeFeedMessagePlaceholder: "Escribe un feed…", tribeFeedSend: "Enviar", - tribeFeedEmpty: "No hay mensajes de feed disponibles, aún.", - noTribes: "No se encontraron tribus, aún.", - tribeNotFound: "Tribu no encontrada!", + tribeFeedEmpty: "Aún no hay mensajes de feed disponibles.", + noTribes: "Aún no se han encontrado tribus.", + tribeNotFound: "¡Tribu no encontrada!", createTribeTitle: "Crear Tribu", updateTribeTitle: "Actualizar Tribu", tribeSectionInhabitants: "Habitantes", @@ -1836,19 +2089,19 @@ module.exports = { tribeSectionVideos: "VÍDEOS", tribeSectionDocuments: "DOCUMENTOS", tribeSectionBookmarks: "MARCADORES", - tribeSectionMaps: "MAPS", + tribeSectionMaps: "MAPAS", tribeSectionPads: "PADS", tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", - tribeInhabitantsEmpty: "No hay habitantes en esta tribu, aún.", + tribeSectionCalendars: "CALENDARIOS", + tribePadCreate: "Crear pad", + tribeChatCreate: "Crear chat", + tribeCalendarCreate: "Crear calendario", + tribePadsEmpty: "Aún no hay pads.", + tribeChatsEmpty: "Aún no hay chats.", + tribeCalendarsEmpty: "Aún no hay calendarios.", + tribeInhabitantsEmpty: "Aún no hay habitantes en esta tribu.", tribeEventCreate: "Crear Evento", - tribeEventsEmpty: "No hay eventos, aún.", + tribeEventsEmpty: "Aún no hay eventos.", tribeEventTitle: "Título", tribeEventDescription: "Descripción", tribeEventDate: "Fecha", @@ -1857,7 +2110,7 @@ module.exports = { tribeEventUnattend: "NO ASISTIR", tribeEventAttendees: "Asistentes", tribeTaskCreate: "Crear Tarea", - tribeTasksEmpty: "No hay tareas, aún.", + tribeTasksEmpty: "Aún no hay tareas.", tribeTaskTitle: "Título", tribeTaskDescription: "Descripción", tribeTaskPriority: "Prioridad", @@ -1868,7 +2121,7 @@ module.exports = { tribeTaskAssign: "ASIGNAR", tribeTaskUnassign: "DESASIGNAR", tribeVotationCreate: "Crear Votación", - tribeVotationsEmpty: "No hay votaciones, aún.", + tribeVotationsEmpty: "Aún no hay votaciones.", tribeVotationTitle: "Título", tribeVotationDescription: "Descripción", tribeVotationOptions: "Opciones", @@ -1877,21 +2130,21 @@ module.exports = { tribeVotationResults: "Votos", tribeVotationClose: "CERRAR VOTACIÓN", tribeVotationOptionPlaceholder: "Opción", - tribeForumCreate: "Crear Forum", - tribeForumEmpty: "No hay hilos, aún.", + tribeForumCreate: "Crear foro", + tribeForumEmpty: "Aún no hay hilos.", tribeForumTitle: "Título", tribeForumText: "Mensaje", tribeForumCategory: "Categoría", tribeForumReply: "Responder", tribeForumReplies: "Respuestas", - tribeMediaUpload: "Subir Media", + tribeMediaUpload: "Subir multimedia", readDocument: "Leer Documento", tribeCreateImage: "Crear Imagen", tribeCreateAudio: "Crear Audio", - tribeCreateVideo: "Crear Video", + tribeCreateVideo: "Crear vídeo", tribeCreateDocument: "Crear Documento", tribeCreateBookmark: "Crear Marcador", - tribeMediaEmpty: "No hay media, aún.", + tribeMediaEmpty: "Aún no hay contenido multimedia.", tribeMediaTitle: "Título", tribeMediaDescription: "Descripción", tribeMediaTypeImage: "Imagen", @@ -1900,9 +2153,14 @@ module.exports = { tribeMediaTypeDocument: "Documento", tribeMediaTypeBookmark: "Marcador", tribeContentDelete: "ELIMINAR", + tribeReachTribe: "TRIBU", + tribeReachNotice: "OASIS lo muestra a todos los habitantes y CLEARNET también en la web abierta. Abrirlo deja una copia legible fuera de la tribu: volver a TRIBU lo oculta, pero las copias ya compartidas no se pueden borrar.", + tribeReachPrivate: "El contenido de una tribu privada solo puede quedarse dentro de la tribu.", + tribeReachOpenedBy: "Abierto por", + tribeReachChange: "Dónde se publica", tribeInviteCodeText: "Código de invitación: ", oasisVersionLabel: "Versión de Oasis", - tribeInviteCodeHint: "Comparte este código con quien quieras invitar. Puede unirse desde /invites → Tribes.", + tribeInviteCodeHint: "Comparte este código con quien quieras invitar. Puede unirse desde /invites.", tribeStatusOpen: "ABIERTO", tribeStatusClosed: "CERRADO", tribeStatusInProgress: "EN PROGRESO", @@ -1919,8 +2177,8 @@ module.exports = { tribeStatusLabel: "Estado", tribeSubTribes: "SUB-TRIBUS", tribeSubTribesCreate: "Crear Sub-Tribu", - tribeSubTribesStrictDenied: "El modo estricto de la tribu no te permite generar nuevas sub-tribus. Ponte en contacto con el administrador.", - tribeActivityRefeed: "REFEED", + tribeSubTribesStrictDenied: "El modo estricto de la tribu no te permite generar nuevas sub-tribus. Ponte en contacto con la administradora.", + tribeActivityRefeed: "REDIFUSIÓN", tribeSectionActivity: "ACTIVIDAD", tribeSectionTrending: "TENDENCIAS", tribeSectionTags: "ETIQUETAS", @@ -1942,7 +2200,7 @@ module.exports = { tribeSearchMinChars: "Introduce al menos 2 caracteres para buscar.", agendaTitle: "Agenda", agendaDescription: "Aquí puedes encontrar todos tus elementos asignados.", - agendaSearchPlaceholder: "Buscar en la agenda...", + agendaSearchPlaceholder: "Buscar en Agenda...", agendaFilterAll: "TODOS", agendaFilterToday: "HOY", agendaFilterUpcoming: "PRÓXIMO", @@ -1963,6 +2221,7 @@ module.exports = { agendaNoItems: "No se encontraron asignaciones.", agendaDiscardButton: "Descartar", agendaRestoreButton: "Restaurar", + agendaRemoveButton: "Quitar", agendaMembersCount: "Miembros", agendaLocationLabel: "Ubicación", agendaYes: "SÍ", @@ -2014,23 +2273,23 @@ module.exports = { voteScam: "Estafa", voteTriggering: "Provocador", voteInsightful: "Perspicaz", - voteAccurate: "Conciso", + voteAccurate: "Preciso", voteActionable: "Accionable", voteCreative: "Creativo", voteSpam: "Spam", voteAdultOnly: "Solo adultos", privateMessage: "MP", pmSendTitle: "Mensajes Privados", - pmSend: "Enviar!", + pmSend: "¡Enviar!", pmCancel: "Cancelar", pmReset: "Restablecer", - pmSharedKeyHint: "Comparte esta clave con el destinatario por otro canal. Es necesaria para descifrar.", - pmDescription: "Usa este formulario para enviar un mensaje/fichero cifrado a otros habitantes.", + pmSharedKeyHint: "Comparte esta clave con la destinataria por otro canal. Es necesaria para descifrar.", + pmDescription: "Usa este formulario para enviar un mensaje/fichero cifrado a otras habitantes.", pmComposeTitle: "Enviar un mensaje", - pmRecipients: "Destinatarios", + pmRecipients: "Destinatarias", pmRecipientsHint: "Introduce los IDs de Oasis separados por comas", - pmLimitsHint: "Hasta 7 destinatarios por mensaje.", - pmTextPlaceholder: "Cuerpo limitado a 7000 caracteres.", + pmLimitsHint: "Hasta 7 habitantes por mensaje.", + pmTextPlaceholder: "Escribe tu mensaje.", pmSubject: "Asunto", pmSubjectHint: "Introduce el asunto del mensaje", pmText: "Mensaje", @@ -2052,8 +2311,8 @@ module.exports = { pmCrypterCharsLabel: "caracteres", pmInvalidRecipients: "Oasis ID no válido (debería ser como @…=.ed25519).", pmEncryptionLabel: "Cifrado", - private: "Privados", - privateDescription: "Tus mensajes cifrados.", + private: "Buzón", + privateDescription: "Gestiona tus mensajes privados.", privateInbox: "BUZÓN", privateSent: "ENVIADOS", privateDelete: "Borrar", @@ -2062,6 +2321,7 @@ module.exports = { pmReply: "Responder", pmReplies: "respuestas", inReplyTo: "EN RESPUESTA A", + forumInThread: "EN EL HILO", pmPreview: "Previsualizar", pmPreviewTitle: "Vista previa", performed: "→", @@ -2099,7 +2359,7 @@ module.exports = { blockchainNoBlocks: 'No se encontraron bloques en la cadena.', blockchainStatsTitle: 'Estadísticas', blockchainStatsTypeBreakdown: 'Desglose por tipo', - blockchainStatsTopAuthors: 'Autores principales', + blockchainStatsTopAuthors: 'Autoras principales', blockchainStatsCount: 'Bloques', blockchainBlockID: 'ID del Bloque', blockchainBlockAuthor: 'Autoría', @@ -2111,22 +2371,21 @@ module.exports = { blockchainDetails: "Ver Detalles del Bloque", blockchainViewBlockexplorer: "Vista en Blockexplorer", blockchainBack: 'Volver al Blockexplorer', - banking: 'Banking', - bankingTitle: 'Banking', - bankingDescription: "La economía de ECOin: balance, RBU, tasas y valor de industria.", + banking: "Banca", + bankingTitle: "Banca", + bankingDescription: "La economía colectiva de ECOin.", bankOverview: 'Resumen', bankEpochs: 'Épocas', bankRules: 'Reglas', pending: 'Pendientes', closed: 'Cerradas', - bankViewTx: 'Ver Tx', bankClaimNow: 'Reclamar', - bankClaimUBI: '¡Reclamar RBU!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: 'Reclamar RBU', + bankClaimAndPay: "Reclamar y pagar", + bankClaimedPending: "Reclamación pendiente...", + bankStatusClaimed: "Reclamado", + bankStatusExpired: "Caducado", + bankPubOnly: "Operación solo para PUBs", bankEpoch: 'Época', bankPool: 'Fondo (esta época)', bankWeightsSum: 'Suma de pesos', @@ -2145,8 +2404,8 @@ module.exports = { bankViewEpoch: 'Ver época', bankYourIndustryBalance: "Tu parte de la industria", bankYourUbiMonth: "Tu RBU (este mes)", - bankYourFundsMonth: "Tus fondos (este mes)", - bankIndustryBalance: "Producción industrial (estimada)", + bankYourFundsMonth: "Fondos totales", + bankIndustryBalance: "Producción industrial", bankUserBalance: 'Tu saldo', bankAddresses: 'Direcciones', bankNoAddresses: 'No se encontraron direcciones.', @@ -2154,6 +2413,10 @@ module.exports = { bankAddress: 'Dirección', bankAddAddressTitle: 'Añadir dirección de ECOIN', bankAddAddressUser: 'ID de Oasis', + bankAddAddressLabel: "Etiqueta (opcional)", + bankAddAddressLabelPlaceholder: "Cartera de Bob, tienda de Alice…", + bankAddAddressUserOptional: "Oasis ID (opcional)", + bankAddressUnnamed: "Dirección sin nombre", bankAddAddressAddress: 'Dirección de ECOIN', bankAddAddressSave: 'Guardar', bankAddressAdded: 'Dirección añadida', @@ -2169,22 +2432,22 @@ module.exports = { bankAddressDelete: 'Eliminar', bankAddressSource: 'Fuente', bankAddressDeleteConfirm: 'Eliminar esta dirección?', - search: 'Buscar!', + search: "¡Buscar!", bankLocal: 'Local', - bankFromOasis: 'Oasis', + bankFromOasis: "Red", pubIdLabel: "PUB ID", - bankUbiAvailableNo: "SIN FONDOS!", + bankUbiAvailableNo: "¡SIN FONDOS!", bankUbiAvailableOk: "¡DISPONIBLE!", - bankUbiAvailability: "RBU (red)", + bankUbiAvailability: "RBU", bankAlreadyClaimedThisMonth: "Ya reclamado este mes", - bankUbiThisMonth: "UBI (este mes)", - bankUbiLastClaimed: "UBI (última reclamada)", + bankUbiThisMonth: "RBU (este mes)", + bankUbiLastClaimed: "RBU (última reclamada)", bankUbiNeverClaimed: "Nunca reclamado", - bankUbiTotalClaimed: "UBI (total reclamado)", + bankUbiTotalClaimed: "RBU (total reclamado)", bankUbiPub: "PUB", bankUbiInhabitant: "HABITANTE", bankUbiClaimedAmount: "RECLAMADO (ECO)", - typeBankUbiResult: "BANCARIO - UBI", + typeBankUbiResult: "BANCA - RBU", bankNoPubConfigured: "Sin PUB configurado. Establece tu ID de PUB en Ajustes.", shopsTitle: "Tiendas", shopDescription: "Descubre y gestiona tiendas en la red.", @@ -2205,6 +2468,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Cifrado en tránsito", shopAddFavorite: "Añadir Favorito", shopRemoveFavorite: "Quitar Favorito", shopOpen: "ABIERTA", @@ -2212,6 +2476,13 @@ module.exports = { shopMakePrivate: "HACER PRIVADA (E2E)", shopMakePublic: "HACER PÚBLICA", shopProducts: "Productos", + shopFilterAll: "TODAS", + shopFilterRecent: "RECIENTES", + shopFilterMine: "MÍAS", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUCTOS", + shopFilterPrices: "PRECIOS", + shopFilterFavorites: "FAVORITOS", shopProductAdd: "Añadir Producto", shopProductTitle: "Producto", shopProductUpdate: "Actualizar Producto", @@ -2223,7 +2494,7 @@ module.exports = { shopNoProducts: "Aún no hay productos.", shopOutOfStock: "Agotado", shopBuy: "Comprar", - shopBuyEncryptedNote: "Tus datos de entrega se envían cifrados; solo el dueño de la tienda puede verlos.", + shopBuyEncryptedNote: "Tus datos de entrega se envían cifrados; solo la dueña de la tienda puede verlos.", shopBuyDeliveryAddress: "Dirección de entrega", shopBuyContact: "Contacto", shopBuyContactPlaceholder: "correo, teléfono, etc.", @@ -2234,30 +2505,32 @@ module.exports = { shopOrdersEmpty: "Aún no hay pedidos.", shopOrderProduct: "Producto", shopOrderPrice: "Precio", - shopOrderBuyer: "Comprador", + shopOrderBuyer: "Compradora", shopBackToShop: "Volver a la Tienda", marketVisitItem: "VER ARTÍCULO", shopStatus: "ESTADO", shopCreatedAt: "CREADO", - shopSearchPlaceholder: "Buscar tiendas...", + shopSearchPlaceholder: "Buscar en Tiendas...", shopUrl: "URL", shopLocation: "Ubicación", shopTags: "Etiquetas", shopVisibility: "Visibilidad", - shopShortDescription: "Descripcion Corta", - shopShortDescriptionPlaceholder: "Descripcion breve para las tarjetas (max 160 caracteres)", + shopShortDescription: "Descripción corta", + shopShortDescriptionPlaceholder: "Descripción breve para las tarjetas (máx. 160 caracteres)", shopTitlePlaceholder: "Nombre de tu tienda", - shopDescriptionPlaceholder: "Descripcion detallada de tu tienda", - shopLocationPlaceholder: "Ciudad, Pais", - shopTagsPlaceholder: "etiqueta1, etiqueta2, etiqueta3", - mapTitlePlaceholder: "Titulo del mapa", + shopDescriptionPlaceholder: "Descripción detallada de tu tienda", + shopLocationPlaceholder: "Ciudad, País", + shopTagsPlaceholder: "Introduce etiquetas separadas por comas", + mapTitlePlaceholder: "Título del mapa", shopProductFeatured: "Producto Destacado", - shopSendToMarket: "Send to Market", + shopSendToMarket: "Enviar al mercado", typeShop: "TIENDAS", typeShopProduct: "PRODUCTO DE TIENDA", bankExchange: 'Intercambio', bankExchangeCurrentValue: 'Valor de ECOin (1h)', - bankTotalSupply: 'Suministro Total de ECOin', + bankTotalSupply: "Suministro ECOin total", + bankPubsSupply: "Suministro ECOin del pozo de los PUBs", + bankHoldingSupply: "Suministro ECOin fuera de la RBU", bankHoursOfWork: 'horas', bankUnitMs: "ms", bankUnitSeconds: "segundos", @@ -2271,14 +2544,15 @@ module.exports = { bankExchangeChartSupply: 'Suministro', bankExchangeChartInflation: 'Inflación %', bankExchangeChartEmpty: 'Aún no hay muestras suficientes — vuelve más tarde', - bankCurrentSupply: 'Suministro Actual de ECOin', + bankCurrentSupply: "Suministro ECOin actual", bankingSyncStatus: 'Estado de ECOin', bankingSyncStatusSynced: 'Sincronizado', - bankingSyncStatusOutdated: 'Desactualizado', + bankingSyncStatusOutdated: "Sin sincronizar", statsTitle: 'Estadísticas', statistics: "Estadísticas", statsDescription: "Descubre estadísticas sobre tu red.", ALLButton: "TODO", + RECENTButton: "RECIENTES", MINEButton: "MÍAS", TOMBSTONEButton: "ELIMINADOS", statsCreatedAt: "Creado el", @@ -2286,7 +2560,8 @@ module.exports = { statsYourOpinions: "Opiniones", statsNetworkContent: "Contenido", statsNetworkOpinions: "Opiniones", - statsEcoWalletLabel: "Billetera ECOIN", + statsEcoWalletLabel: "Cartera ECOin", + walletAddressCopyHint: "Pulsa para seleccionar y copiar", statsEcoWalletNotConfigured: "¡No configurada!", statsBookmark: "Marcadores", statsEvent: "Eventos", @@ -2295,8 +2570,8 @@ module.exports = { statsMarket: "Mercado", statsForum: "Foros", statsJob: "Empleos", - statsReport: "Reportes", - statsFeed: "Publicaciones", + statsReport: "Informes", + statsFeed: "Feed", statsTribe: "Tribus", statsImage: "Imágenes", statsAudio: "Audios", @@ -2308,6 +2583,8 @@ module.exports = { statsTransfer: "Transferencias", statsAiExchange: "IA", statsPUBs: 'PUBs', + statsSyncedPeers: "Nodos", + statsTotalLabel: "Total", statsPost: "Publicaciones", statsSize: "Total (tamaño)", statsBlockchainSize: "Blockchain (tamaño)", @@ -2348,20 +2625,26 @@ module.exports = { statsCourtsSettlement: "Acuerdos judiciales", statsCourtsSettlementProposal: "Propuestas de acuerdo", statsCourtsSettlementAccepted: "Acuerdos aceptados", - statsCourtsNomination: "Nominaciones de jueces", + statsCourtsNomination: "Nominaciones de juezas", statsCourtsNominationVote: "Votos de nominación", ai: "IA", aiTitle: "IA", aiDescription: "Una Inteligencia Artificial Colectiva (IAC) llamada '42' que aprende de tu red.", aiUserQuestion: "Pregunta", aiResponseTitle: "Respuesta", - aiSubmitButton: "Enviar!", + aiSubmitButton: "¡Enviar!", aiSettingsDescription: "Configura tu prompt (máx 128 caracteres) para el modelo de IA.", aiPrompt: "Proporciona una respuesta informativa y precisa.", aiConfiguration: "Configurar prompt", aiPromptUsed: "Indicación", - aiClearHistory: "Borrar historial de chat", - aiServerError: "La IA no ha podido responder. Inténtalo de nuevo.", + aiClearHistory: "Borrar historial", + aiServerError: "No puedo responder bien. Inténtalo de nuevo.", + aiStatusLoading: "42 está cargando el modelo. Pregunta de nuevo en un momento.", + aiThinking: "Pensando", + aiWaiting: "Esperando", + aiStatusMissingModel: "El modelo de IA no está instalado: falta src/AI/oasis-42-1-chat.Q4_K_M.gguf.", + aiExportFineTuning: "Exportar Fine-Tuning", + aiFactsTitle: "Datos de tu red", aiInputPlaceholder: "¿Qué es Oasis?", typeAiExchange: "IA", aiApproveTrain: "Añadir al entrenamiento colectivo", @@ -2388,9 +2671,9 @@ module.exports = { marketFilterForSale: "EN VENTA", marketFilterSold: "VENDIDO", marketFilterDiscarded: "DESCARTADO", - marketFilterRecent: "RECIENTE", + marketFilterRecent: "RECIENTES", marketFilterMyBids: "PUJAS", - marketCreateButton: "Crear artículo", + marketCreateButton: "Vender algo", marketItemType: "Tipo", marketItemTitle: "Título", marketItemAvailable: "Fecha límite", @@ -2414,11 +2697,11 @@ module.exports = { marketActionsBuy: "¡COMPRAR!", marketAuctionBids: "Pujas actuales", marketPlaceBidButton: "Pujar", - marketItemSeller: "Vendedor", + marketItemSeller: "Vendedora", marketNoItems: "Aún no hay artículos disponibles.", marketYourBid: "Tu puja", - marketCreateFormImageLabel: "Subir contenido multimedia (max: 50MB)", - marketSearchPlaceholder: "Buscar por título o etiquetas", + marketCreateFormImageLabel: "Subir contenido multimedia (máx.: 75MB)", + marketSearchPlaceholder: "Buscar en Mercado...", marketMinPriceLabel: "Precio mínimo", marketMaxPriceLabel: "Precio máximo", marketSortRecent: "Más recientes", @@ -2484,8 +2767,7 @@ module.exports = { housingSetClosed: "Marcar no disponible", housingCreateButton: "Crear lugar", housingUpdateButton: "Actualizar", - housingDeleteButton: "Borrar", - housingSearchPlaceholder: "Buscar lugares...", + housingSearchPlaceholder: "Buscar en Vivienda...", housingSearchButton: "Buscar", housingMinPrice: "Precio mín.", housingMaxPrice: "Precio máx.", @@ -2495,12 +2777,12 @@ module.exports = { housingSortRating: "Mejor valorados", housingNoItems: "No se han encontrado lugares.", jobsTitle: "Laboral", - jobsCandidatesTitle: "Candidatos sugeridos", + jobsCandidatesTitle: "Candidatas sugeridas", jobsCandidatesDescription: "Habitantes cuyas competencias encajan con este empleo. Envíales un mensaje privado para invitarles.", jobsCandidatesPmBody: "Hola, creo que tu perfil encaja con mi oferta laboral", jobsDescription: "Descubre y gestiona ofertas de trabajo en tu red.", jobsFilterRecent: "RECIENTES", - jobsFilterMine: "MIOS", + jobsFilterMine: "MÍOS", jobsFilterAll: "TODOS", jobsFilterRemote: "REMOTOS", jobsFilterOpen: "ABIERTOS", @@ -2516,10 +2798,10 @@ module.exports = { jobsCVTitle: "CVs", jobsFilterPresencial: "PRESENCIAL", jobsFilterFreelancer: "FREELANCE", - jobsFilterEmployee: "EMPLEADO", + jobsFilterEmployee: "EMPLEADA", jobsPresencialTitle: "Trabajos Presenciales", jobsFreelancerTitle: "Trabajos Freelance", - jobsEmployeeTitle: "Trabajos de Empleado", + jobsEmployeeTitle: "Trabajos por cuenta ajena", jobTitle: "Título", jobLocation: "Ubicación", jobSalary: "Salario (ECO/1h)", @@ -2531,8 +2813,8 @@ module.exports = { jobStatusOPEN: "ABIERTA", jobStatusCLOSED: "CERRADA", jobSetClosed: "Marcar como CERRADA", - jobSubscribeButton: "Unirse a esta oferta!", - jobUnsubscribeButton: "Salir de esta oferta!", + jobSubscribeButton: "¡Unirse a esta oferta!", + jobUnsubscribeButton: "¡Salir de esta oferta!", jobTitlePlaceholder: "Introduce el título del trabajo", jobDescriptionPlaceholder: "Describe el puesto", jobRequirementsPlaceholder: "Introduce los requisitos", @@ -2541,19 +2823,19 @@ module.exports = { jobLocationPresencial: "Presencial", jobLocationRemote: "Remoto", jobVacantsPlaceholder: "Número de vacantes", - jobImage: "Subir contenido multimedia (max: 50MB)", + jobImage: "Subir contenido multimedia (máx.: 75MB)", jobTasks: "Tareas", jobType: "Tipo de Trabajo", jobTime: "Tiempo de Trabajo", - jobSubscribers: "Suscriptores", - noSubscribers: "Sin suscriptores", + jobSubscribers: "Suscriptoras", + noSubscribers: "Sin suscriptoras", jobsFilterTop: "TOP", jobsTopTitle: "Trabajos Mejor Pagados", createJobButton: "Publicar Trabajo", noJobsFound: "No se encontraron ofertas de trabajo.", jobAuthor: "Por", jobTypeFreelance: "Autónomo", - jobTypeSalary: "Empleado", + jobTypeSalary: "Empleada", jobTypeExchange: "Intercambio de horas", jobTypeEXCHANGE: "INTERCAMBIO DE HORAS", jobsFilterExchange: "INTERCAMBIO", @@ -2563,32 +2845,31 @@ module.exports = { jobsExchangeSkill: "Habilidad solicitada a cambio", jobTimePartial: "Parcial", jobTimeComplete: "Completo", - jobsDeleteButton: "ELIMINAR", jobsUpdateButton: "ACTUALIZAR", jobsFilterApplied: "CANDIDATURA", jobsAppliedTitle: "Mis candidaturas", - jobsAppliedBadge: "Inscrito", - jobsSearchPlaceholder: "Buscar por título, tags, descripción...", + jobsAppliedBadge: "Inscrita", + jobsSearchPlaceholder: "Buscar en Laboral...", jobsMinSalaryLabel: "Salario mín.", jobsMaxSalaryLabel: "Salario máx.", jobsSortRecent: "Más recientes", jobsSortSalary: "Mayor salario", - jobsSortSubscribers: "Más candidatos", + jobsSortSubscribers: "Más candidatas", jobsSearchButton: "Buscar", - jobsApplicants: "Candidatos", + jobsApplicants: "Candidatas", jobsUpdatedAt: "Actualizado", jobSetOpen: "Marcar abierto", jobsTagsLabel: "Etiquetas", noJobsMatch: "No hay ofertas que coincidan con tu búsqueda.", - industrySearchPlaceholder: "Buscar fábricas…", + industrySearchPlaceholder: "Buscar en Industria...", industryAllSectors: "Todos los sectores", industryVisitButton: "Visitar Industria", industryAdvanceTo: "Cambiar a", industryAmount: "Cantidad", industryApproveBuild: "Aprobar", - industryBlueprint: "Blueprint", - industryBlueprints: "Blueprints", - industryBuild: "Build", + industryBlueprint: "Plano", + industryBlueprints: "Planos", + industryBuild: "Producción", industryBuildNotes: "Notas", industryBuildStart: "Fecha de inicio", industryVoteUpdateButton: "Votar actualización", @@ -2596,7 +2877,7 @@ module.exports = { industryRevokeVote: "Revocar", industryTimeLeft: "Tiempo restante", industryBuildEnd: "Fecha de fin", - industryBuilds: "Builds", + industryBuilds: "Producciones", industryBuildTitle: "Título", industryContribute: "Contribuir", industryContributeButton: "Contribuir", @@ -2638,7 +2919,7 @@ module.exports = { industryPot: "Bote", industryProposeBuildButton: "Proponer build", industryProposer: "Proponente", - industryAuthor: "Autor", + industryAuthor: "Autora", industrySellOnMarket: "Enviar al Mercado", industrySendToProjects: "Crear Proyecto", industrySkills: "Habilidades", @@ -2654,7 +2935,6 @@ module.exports = { industryBuildStatus_FAILED: "FALLIDO", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Un empleo encaja con tu currículum", jobsBotMatchIntro: "Un empleo encaja con tu currículum.", jobsBotMatchJob: "Empleo", @@ -2678,19 +2958,19 @@ module.exports = { industryBotDistributedTitle: "Se ha distribuido la salida de un build.", industryFilterRules: "REGLAS", industryRulesTitle: "Reglas", - industryRulesIntro: "Industry permite a los habitantes poseer colectivamente los medios de producción: una Fábrica es un taller de la red que nadie posee de forma privada.", - industryRulesSteward: "El fundador es el mayordomo — un custodio, no un dueño: no puede vender, privatizar ni disolver la Fábrica en solitario.", - industryRulesMembership: "La política de membresía define cómo se une la gente: Abierta (entras al instante), Por voto (te admite el voto de los miembros) o Solo invitación (un miembro debe invitarte antes).", - industryRulesQuorum: "El quórum es el número mínimo de miembros que deben votar para que una decisión cuente, para que una Fábrica pequeña no la decida una sola persona.", + industryRulesIntro: "Industry permite a las habitantes poseer colectivamente los medios de producción: una Fábrica es un taller de la red que nadie posee de forma privada.", + industryRulesSteward: "La fundadora es la mayordoma — una custodia, no una dueña: no puede vender, privatizar ni disolver la Fábrica en solitario.", + industryRulesMembership: "La política de membresía define cómo se unen los habitantes: Abierta (entras al instante), Por voto (te admite el voto de los miembros) o Solo invitación (un miembro debe invitarte antes).", + industryRulesQuorum: "El quórum es el número mínimo de miembros que deben votar para que una decisión cuente, para que una Fábrica pequeña no la decida un solo habitante.", industryRulesMajority: "La mayoría es la fracción de votos necesaria para aprobar (0,5 = mitad, 1 = unanimidad); una decisión pasa cuando los SÍ alcanzan al menos max(quórum, miembros × mayoría).", - industryRulesDecisions: "Los miembros votan para admitir nuevos, aprobar builds y disolver la Fábrica; el mayordomo no puede saltarse estas decisiones colectivas.", + industryRulesDecisions: "Los miembros votan para admitir nuevas integrantes, aprobar builds y disolver la Fábrica; la mayordoma no puede saltarse estas decisiones colectivas.", industryRulesBlueprints: "Los Blueprints son recetas libres (COPYLEFT) — las entradas (materiales, horas de trabajo, skills) y la salida (un bien físico o digital) — y cualquiera puede forkearlas.", industryRulesBuilds: "Un Build es una orden de producción: PROPUESTO → APROBADO (por voto) → ABASTECIENDO → EN PRODUCCIÓN → COMPLETADO, y solo acepta contribuciones una vez aprobado.", industryRulesContributions: "Los miembros aportan trabajo (horas), materiales (valor declarado en ECO) o ECO a un build; cada aporte gana karma.", industryRulesLaborRate: "La tarifa de trabajo es el valor en ECO de una hora de trabajo en esta Fábrica; convierte horas en karma para comparar esfuerzo y capital de forma justa: karma = horas × tarifa + valor material + ECO.", industryRulesShares: "Tu cuota en un build es tu karma ÷ karma total, y determina cuánto del valor de salida recibes.", industryRulesDistribution: "Cuando una producción se completa, el steward reparte el fondo (los fondos de la producción más el valor de venta) entre quienes contribuyeron, en proporción a su participación.", - industryRulesTreasury: "Las aportaciones en ECO las custodia el mayordomo como tesorería del build hasta el reparto; todos los movimientos quedan registrados en la red y las disputas pueden ir a Courts.", + industryRulesTreasury: "Las aportaciones en ECO las custodia la mayordoma como tesorería del build hasta el reparto; todos los movimientos quedan registrados en la red y las disputas pueden ir a Courts.", industryJobs: "Empleos", industryReportButton: "Reportar", industryCreateTask: "Crear Tarea", @@ -2700,13 +2980,14 @@ module.exports = { industryPauseVotes: "Votos de estado", industryTitle: "Industria", industryDescription: "Módulo para gestionar los medios de producción de forma colectiva.", - typeIndustryBuild: "BUILD", - typeIndustryBlueprint: "BLUEPRINT", + typeIndustryBuild: "PRODUCCIÓN", + typeIndustryBlueprint: "PLANO", typeIndustryAllocation: "DISTRIBUCIÓN", typeIndustry: "INDUSTRIA", modulesIndustryLabel: "Industria", modulesIndustryDescription: "Módulo para gestionar los medios de producción de forma colectiva.", industryFilterAll: "TODAS", + industryFilterRecent: "RECIENTES", industryFilterMember: "MIEMBRO", industryFilterBlueprints: "PLANOS", industryFilterBuilds: "PRODUCCIONES", @@ -2715,6 +2996,7 @@ module.exports = { industryFilterPaused: "PAUSADAS", industryFilterDissolved: "DISUELTAS", industryAllTitle: "Industria", + industryRecentTitle: "Fábricas recientes", industryMemberTitle: "Fábricas en las que estoy", industryMineTitle: "Mis fábricas", industryActiveTitle: "En marcha", @@ -2735,7 +3017,7 @@ module.exports = { industryLaborRate: "Tarifa de trabajo", industryCreateButton: "Crear fábrica", industryUpdateButton: "Actualizar", - industrySteward: "Mayordomo", + industrySteward: "Mayordoma", industryStatusActive: "EN MARCHA", industryStatusPaused: "PAUSADA", industryStatusDissolved: "DISUELTA", @@ -2762,7 +3044,7 @@ module.exports = { industryPolicy_invite: "Solo invitación", projectsTitle: "Proyectos", projectsDescription: "Crea, financia y sigue proyectos impulsados por la comunidad en tu red.", - projectSearchPlaceholder: "Buscar proyectos...", + projectSearchPlaceholder: "Buscar en Proyectos...", projectCreateProject: "Crear Proyecto", projectCreateButton: "Crear Proyecto", projectUpdateButton: "ACTUALIZAR", @@ -2771,7 +3053,7 @@ module.exports = { projectFilterAll: "TODOS", projectFilterMine: "MIS PROYECTOS", projectFilterActive: "ACTIVOS", - projectFilterPaused: "PONDERADOS", + projectFilterPaused: "PAUSADOS", projectFilterCompleted: "COMPLETADOS", projectFilterFollowing: "SEGUIDOS", projectFilterRecent: "RECIENTES", @@ -2785,7 +3067,7 @@ module.exports = { projectRecentTitle: "Proyectos Recientes", projectTopTitle: "Mejor Financiados", projectTitlePlaceholder: "Nombre del proyecto", - projectImage: "Subir contenido multimedia (max: 50MB)", + projectImage: "Subir contenido multimedia (máx.: 75MB)", projectDescription: "Descripción", projectDescriptionPlaceholder: "Cuenta la historia y los objetivos…", projectGoal: "Meta (ECO)", @@ -2832,9 +3114,9 @@ module.exports = { projectMilestoneDescription: "Descripción del hito", projectBudgetAssigned: "Asignado a recompensas", projectBudgetOver: "⚠ Presupuesto excedido: lo asignado supera el objetivo", - projectFollowers: "Seguidores", - projectFollowersTitle: "Seguidores", - projectFollowersNone: "Aún no hay seguidores.", + projectFollowers: "Seguidoras", + projectFollowersTitle: "Seguidoras", + projectFollowersNone: "Aún no hay seguidoras.", projectMore: "más", projectYouFollowHint: "Sigues este proyecto", projectBackers: "Patrocinadores", @@ -2857,20 +3139,18 @@ module.exports = { modulesModuleName: "Nombre", modulesModuleDescription: "Descripción", modulesModuleStatus: "Estado", - modulesTotalModulesLabel: "Módulos Cargados", + modulesTotalModulesLabel: "Total", modulesEnabledModulesLabel: "Habilitados", modulesDisabledModulesLabel: "Deshabilitados", modulesInvitesLabel: "Invitaciones", modulesInvitesDescription: "Módulo para gestionar y aplicar códigos de invitación.", modulesWalletLabel: "Cartera", modulesWalletDescription: "Módulo para gestionar tus activos digitales (ECOin).", - modulesLegacyLabel: "Legado", - modulesLegacyDescription: "Módulo para gestionar tu secreto (clave privada) de forma rápida y segura.", modulesCipherLabel: "Cifrado", - modulesCipherDescription: "Módulo para encriptar y desencriptar tu texto de forma simétrica (usando una contraseña compartida).", + modulesCipherDescription: "Módulo para cifrar y descifrar tu texto de forma simétrica (usando una contraseña compartida).", modulesBookmarksLabel: "Marcadores", modulesBookmarksDescription: "Módulo para descubrir y gestionar marcadores.", - modulesVideosLabel: "Videos", + modulesVideosLabel: "Vídeos", modulesVideosDescription: "Módulo para descubrir y gestionar videos.", modulesDocsLabel: "Documentos", modulesDocsDescription: "Módulo para descubrir y gestionar documentos.", @@ -2896,7 +3176,7 @@ module.exports = { modulesVotationsDescription: "Módulo para descubrir y gestionar votaciones.", modulesParliamentLabel: "Parlamento", modulesParliamentDescription: "Módulo para elegir gobiernos y votar leyes.", - modulesCourtsLabel: "Juzgados", + modulesCourtsLabel: "Tribunales", modulesCourtsDescription: "Módulo para resolver conflictos y emitir veredictos.", modulesReportsLabel: "Informes", modulesReportsDescription: "Módulo para gestionar y hacer un seguimiento de informes relacionados con problemas, errores, abusos y advertencias de contenido.", @@ -2918,29 +3198,32 @@ module.exports = { modulesMelodyDescription: "Módulo para generar y compartir el \"sonido\" de tu blockchain.", modulesAgendaLabel: "Agenda", modulesAgendaDescription: "Módulo para gestionar todos tus elementos asignados.", - modulesAILabel: "AI", + modulesAILabel: "IA", modulesAIDescription: "Módulo para hablar con un LLM llamado '42'.", modulesForumLabel: "Foros", modulesForumDescription: "Módulo para descubrir y gestionar foros.", modulesJobsLabel: "Trabajos", - modulesJobsDescription: "Modulo para descubrir y gestionar ofertas de trabajo.", + modulesJobsDescription: "Módulo para descubrir y gestionar ofertas de trabajo.", modulesProjectsLabel: "Proyectos", modulesProjectsDescription: "Módulo para explorar, financiar y gestionar proyectos.", modulesBankingLabel: "Banca", - modulesBankingDescription: "Módulo para conocer el valor real de ECOIN y distribuir una RBU utilizando la tesorería común.", + modulesBankingDescription: "Módulo para gestionar el tesoro común: valor de ECOIN, impuestos y RBU.", modulesFavoritesLabel: "Favoritos", modulesFavoritesDescription: "Módulo para gestionar tu contenido favorito.", fileTooLargeTitle: "Archivo demasiado grande", - fileTooLargeMessage: "El archivo supera el tamaño máximo permitido (50 MB). Por favor, selecciona un archivo más pequeño.", + fileTooLargeMessage: "El archivo supera el tamaño máximo permitido (75 MB). Por favor, selecciona un archivo más pequeño.", goBack: "Volver", + errorDismiss: "OK", errorPageTitle: "Ha ocurrido un error", + errorNoAllocation: "No se ha encontrado ninguna asignación para esta reclamación.", + errorInvalidClaim: "Esta asignación no se puede reclamar.", visibilityLabel: "Visibilidad", + clearnetPrivateConflict: "Un contenido privado, oculto, cerrado, solo por invitación o de pago no se puede publicar en la CLEARNET. Elige OASIS o hazlo público.", visibilityPublic: "Pública", visibilityHidden: "Oculta", visibilityMakePublic: "Hacer pública", visibilityMakeHidden: "Hacer oculta", invitesInhabitantsTitle: "Habitantes", - invitesInhabitantsFollow: "Seguir", aiNavPlaceholder: "Describe qué necesitas...", aiNavDisabled: "La navegación con IA está desactivada.", aiNavResultsTitle: "Sugerencias AINav", @@ -2951,24 +3234,24 @@ module.exports = { modulesAINavDescription: "Módulo para realizar peticiones en lenguaje natural sobre el contenido de la red.", directConnectDescription: "Conéctate directamente a un nodo introduciendo su dirección IP, puerto y clave pública. El nodo se añadirá como conexión seguida.", peerHost: "IP", - peerPort: "Puerto (por defecto: 8008)", + peerPort: "Puerto", peerPublicKey: "Clave Pública (@...ed25519)", connectAndFollow: "Conectar", modulesPresetTitle: "Configuraciones Comunes", - workflowsTitle: "Workflows", + workflowsTitle: "Flujos de trabajo", workflowsDescription: "Un workflow fija el tema y qué módulos están activos.", workflowsSet: "Aplicar workflow", workflow_default: "Por defecto", workflow_jobs: "Empleo", - workflow_ruling: "Gobierno", - workflow_politics: "Política", workflow_social: "Social", workflow_business: "Negocios", workflow_night: "Noche", + workflow_kids: "Niños", + workflow_activists: "Activismo", workflow_mobile: "Móvil", statsCarbonFootprintTitle: "Huella de Carbono", - statsCarbonTombstone: "Huella del tombstoning", - uploadMedia: "Subir contenido multimedia (max: 50MB)", + statsCarbonTombstone: "Huella de los elementos eliminados (tombstoning)", + uploadMedia: "Subir contenido multimedia (máx.: 75MB)", invitesUnfollow: "Dejar de seguir", invitesFollow: "Seguir", invitesUnfollowedInvites: "No federadas", @@ -2978,12 +3261,13 @@ module.exports = { visitContent: "Visitar Contenido", favoriteAdd: "Fijar en Favoritos", pmContentTooltip: "Enviar un Mensaje Privado", + phoneCallTooltip: "Hacer una Llamada", favoriteRemove: "Quitar de Favoritos", reportContent: "Reportar Contenido", - bankEcoinHours: "Equivalencia ECOin en tiempo", + bankEcoinHours: "Equivalencia ECOin en tiempo (1 ECO)", mapsLabel: "Mapas", mapTitle: "Mapas", - mapDescription: "Explora y gestiona mapas offline en tu red.", + mapDescription: "Gestiona y comparte mapas offline en tu red.", mapMineSectionTitle: "Tus Mapas", mapCreateSectionTitle: "Crear Mapa", mapUpdateSectionTitle: "Actualizar Mapa", @@ -3020,12 +3304,25 @@ module.exports = { mapMarkerLngLabel: "Longitud del marcador", mapMarkerLabelField: "Etiqueta del marcador", mapMarkerLabelPlaceholder: "Describe este marcador...", - markerImageLabel: "Imagen del Marker", + markerImageLabel: "Imagen del marcador", mapAddMarkerTitle: "Añadir Marcador", mapAddMarkerButton: "Añadir Marcador", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Limpiar marcador", mapApplyZoom: "Aplicar Zoom", - mapSearchPlaceholder: "Buscar descripción, etiquetas, autor...", + mapPlaceSearchPlaceholder: "Buscar un lugar o país...", + mapNoPlaces: "No se encontraron lugares.", + mapClusterTitle: "marcadores aquí, pulsa para acercar", + mapDeleteMarker: "Eliminar marcador", + mapZoomIn: "Acercar", + mapZoomOut: "Alejar", + mapFitMarkers: "Ajustar a todos los marcadores", + mapPanLabel: "Mover el mapa", + mapCenterHere: "Centrar aquí", + mapNear: "cerca de", + mapPickedSpot: "Punto elegido", + mapPickHint: "Pulsa en el mapa para elegir un punto.", + mapClearPick: "Limpiar", + mapSearchPlaceholder: "Buscar en Mapas...", mapSearchButton: "Buscar", mapUpdatedAt: "Actualizado", mapNoMatch: "Ningún mapa coincide con tu búsqueda.", @@ -3036,6 +3333,465 @@ module.exports = { typeMapMarker: "MARCADOR DE MAPA", modulesMapLabel: "Mapas", modulesMapDescription: "Módulo para gestionar y compartir mapas offline.", + emergenciesTitle: "Emergencias", + modulesEmergenciesLabel: "Emergencias", + modulesEmergenciesDescription: "Módulo para lanzar, confirmar y seguir emergencias en la red.", + emergenciesDescription: "Módulo para lanzar, confirmar y seguir emergencias en la red.", + emergencyCreate: "Crear emergencia", + emergencyUpdate: "Actualizar", + emergencyDelete: "Borrar", + emergencyTitleLabel: "Título", + emergencyTitlePlaceholder: "¿Qué está pasando?", + emergencyTextLabel: "Detalles", + emergencyTextPlaceholder: "Dónde, desde cuándo, qué hacer...", + emergencyCategoryLabel: "Categoría", + emergencyCategoryWeather: "METEOROLOGÍA", + emergencyCategoryInfrastructure: "INFRAESTRUCTURA", + emergencyCategoryHealth: "SALUD", + emergencyCategorySecurity: "SEGURIDAD", + emergencyCategoryLost: "PERDIDOS", + emergencyCategoryNeighborhood: "VECINDARIO", + emergencyExpiresLabel: "Caduca en", + emergencyExpires1d: "1 día", + emergencyExpires3d: "3 días", + emergencyExpires7d: "1 semana", + emergencyExpires30d: "1 mes", + emergencyExpiresNone: "Indefinido", + emergencyExpiresKeep: "Mantener la fecha actual", + emergencyTagsLabel: "Etiquetas", + emergencyTagsPlaceholder: "Introduce etiquetas separadas por comas", + emergencyFilterAll: "TODAS", + emergencyFilterMine: "MÍAS", + emergencyFilterRecent: "RECIENTES", + emergencySeverityUnverified: "SIN VERIFICAR", + emergencySeverityLow: "BAJA", + emergencySeverityMedium: "MEDIA", + emergencySeverityHigh: "ALTA", + emergencyStatusActive: "ACTIVA", + emergencyStatusResolved: "RESUELTA", + emergencyStatusExpired: "CADUCADA", + emergencyConfirmations: "Confirmaciones", + emergencyConfirmButton: "Confirmar", + emergencyConfirmed: "Has confirmado esta emergencia", + emergencyResolveButton: "Resolver", + emergencyReopenButton: "Reabrir", + emergencyGenerateReport: "Generar informe", + emergencyUpdateEdited: "Editada", + emergencyLastUpdate: "ÚLTIMA ACTUALIZACIÓN", + emergencyUpdatesTitle: "Últimas actualizaciones:", + emergencyNoUpdates: "Aún no hay actualizaciones.", + emergencyAddUpdate: "Publicar actualización", + emergencyUpdatePlaceholder: "¿Qué ha cambiado?", + emergencyNoItems: "No se han encontrado emergencias.", + emergencyExpiresAt: "Caduca", + emergencyBannerLabel: "Emergencia activa", + emergencyCreateSectionTitle: "Crear emergencia", + emergencyUpdateSectionTitle: "Actualizar emergencia", + emergencySearchPlaceholder: "Buscar en emergencias...", + typeEmergency: "EMERGENCIAS", + typeEmergencyConfirm: "CONFIRMACIÓN DE EMERGENCIA", + typeEmergencyUpdate: "ACTUALIZACIÓN DE EMERGENCIA", + statsEmergency: "Emergencias", + statsEmergencyConfirm: "Confirmaciones de emergencias", + statsEmergencyUpdate: "Actualizaciones de emergencias", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Una emergencia que sigues tiene una actualización.", + emergencyBotResolvedTitle: "Una emergencia que sigues se ha resuelto.", + dataKindEmergencies: "EMERGENCIAS", + mailingTitle: "Listas de correo", + modulesMailingLabel: "Listas de correo", + modulesMailingDescription: "Descubre y gestiona listas de correo en tu red.", + modulesPhoneLabel: "Teléfono", + modulesPhoneDescription: "Módulo para llamar a otros habitantes con videollamadas cifradas de extremo a extremo.", + mailingDescription: "Descubre y gestiona listas de correo en tu red.", + mailingCreate: "Crear lista de correo", + mailingUpdate: "Actualizar", + mailingArchive: "Archivar", + mailingActivate: "Activar", + mailingCreateSectionTitle: "Crear lista de correo", + mailingUpdateSectionTitle: "Actualizar lista de correo", + mailingTitleLabel: "Título", + mailingTitlePlaceholder: "Nombre de la lista de correo", + mailingDescriptionLabel: "Descripción", + mailingDescriptionPlaceholder: "¿De qué trata esta lista?", + mailingTypeLabel: "Tipo de lista de correo", + mailingTypeOpen: "ABIERTA", + mailingTypeClosed: "CERRADA", + mailingStatusLabel: "Estado", + mailingStatusActive: "ACTIVA", + mailingStatusArchived: "ARCHIVADA", + mailingMembersLabel: "Invitar habitantes", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Suscriptoras", + mailingTagsLabel: "Etiquetas", + mailingTagsPlaceholder: "Introduce etiquetas separadas por comas", + mailingFilterAll: "TODAS", + mailingFilterMine: "MÍAS", + mailingFilterSubscribed: "SUSCRITAS", + mailingFilterRecent: "RECIENTES", + mailingHistoryDate: "FECHA", + mailingHistoryThreads: "HILOS", + mailingHistoryInhabitants: "HABITANTES", + mailingHistorySearchPlaceholder: "Buscar en el historial...", + mailingNoMessages: "Aún no hay mensajes.", + mailingNoItems: "No se han encontrado listas de correo.", + mailingSearchPlaceholder: "Buscar en listas de correo...", + mailingWriteButton: "Escribir mensaje", + mailingWriteTitle: "Escribir a la lista", + mailingReplyTitle: "Responder a la lista", + mailingSend: "Enviar", + mailingTextPlaceholder: "Tu mensaje llega a todas las participantes de la lista.", + mailingParticipants: "Participantes", + inboxFilterMailing: "Listas de correo", + mailingReplyPlaceholder: "Responder a la lista...", + mailingReplyToList: "Responder a la lista", + typeMailingList: "LISTA DE CORREO", + statsMailingList: "Listas de correo", + mailingListLabel: "LISTA DE CORREO", + logisticsTitle: "Logística", + modulesLogisticsLabel: "Logística", + modulesLogisticsDescription: "Descubre y gestiona planes logísticos en tu red.", + logisticsDescription: "Descubre y gestiona planes logísticos en tu red.", + logisticsCreate: "Crear ruta", + logisticsUpdate: "Actualizar", + logisticsClose: "Cerrar", + logisticsReopen: "Reabrir", + logisticsCreateSectionTitle: "Crear ruta", + logisticsUpdateSectionTitle: "Actualizar ruta", + logisticsTitleLabel: "Título", + logisticsTitlePlaceholder: "Viaje al mercado, furgoneta disponible...", + logisticsKindLabel: "Tipo de logística", + logisticsKindTrip: "VIAJE", + logisticsKindShipment: "ENVÍO", + logisticsKindTrips: "VIAJES", + logisticsKindShipments: "ENVÍOS", + logisticsModeLabel: "Modo", + logisticsModeOffer: "OFREZCO", + logisticsModeRequest: "BUSCO", + logisticsModeOffers: "OFERTAS", + logisticsModeRequests: "PETICIONES", + logisticsDescriptionLabel: "Descripción", + logisticsDescriptionPlaceholder: "Punto de encuentro, qué llevas, condiciones...", + logisticsOriginLabel: "Origen", + logisticsOriginPlaceholder: "Dónde empieza", + logisticsDestinationLabel: "Destino", + logisticsDestinationPlaceholder: "Dónde termina", + logisticsDateLabel: "Fecha y hora", + logisticsRecurrenceLabel: "Recurrencia", + logisticsFrequencyLabel: "Frecuencia", + logisticsRecurrenceNone: "ÚNICO", + logisticsRecurrenceDaily: "DIARIO", + logisticsRecurrenceWeekly: "SEMANAL", + logisticsRecurrenceMonthly: "MENSUAL", + logisticsSeatsLabel: "Plazas", + logisticsSizeLabel: "Tamaño", + logisticsSizePlaceholder: "p. ej. 2m x 1m", + logisticsWeightLabel: "Peso", + logisticsWeightPlaceholder: "p. ej. 40kg", + logisticsPriceFree: "GRATIS", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TIEMPO", + logisticsPriceLabel: "Precio", + logisticsHours: "h", + logisticsOrderRefLabel: "Enlace al pedido", + logisticsOrderRefPlaceholder: "/shops/... o /market/... del pedido a entregar", + logisticsOrderLink: "Pedido enlazado", + logisticsTagsLabel: "Etiquetas", + logisticsTagsPlaceholder: "Introduce etiquetas separadas por comas", + logisticsFilterAll: "TODAS", + logisticsFilterMine: "MÍAS", + logisticsFilterBooked: "RESERVADAS", + logisticsFilterRecent: "RECIENTES", + logisticsFilterHistory: "HISTORIAL", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "ABIERTA", + logisticsStatusUpcoming: "PRÓXIMAS", + logisticsStatusPast: "PASADAS", + logisticsStatusClosed: "CERRADAS", + logisticsZoneLabel: "Zona", + logisticsSearchPlaceholder: "Buscar en logística...", + logisticsNoItems: "No se han encontrado rutas.", + logisticsBookingPending: "PENDIENTE", + logisticsBookingConfirmed: "CONFIRMADA", + logisticsBookingRejected: "RECHAZADA", + logisticsBookingCancelled: "CANCELADA", + logisticsBookingDelivered: "ENTREGADA", + logisticsConfirm: "Confirmar", + logisticsReject: "Rechazar", + logisticsDeliver: "Entregar", + logisticsReceiptLabel: "Recibo", + logisticsReceiptPlaceholder: "Nota de entrega (opcional)", + logisticsCancelBooking: "Cancelar reserva", + logisticsBookTitle: "Reservar esta ruta", + logisticsNotesLabel: "Notas", + logisticsNotesPlaceholder: "Lo que el responsable deba saber", + logisticsBook: "Reservar", + logisticsBookingsTitle: "Reservas", + logisticsMyBookingTitle: "Tu reserva", + logisticsRatingsTitle: "Valoraciones", + logisticsNoRatings: "Aún no hay valoraciones.", + logisticsRatingsWhenClosed: "Las valoraciones se abren al cerrar la ruta.", + logisticsScoreLabel: "Puntuación", + logisticsRatingPlaceholder: "¿Qué tal fue?", + logisticsRate: "Valorar", + typeLogisticsRoute: "RUTA", + typeLogisticsRating: "VALORACIÓN DE RUTA", + statsLogisticsRoute: "Rutas", + statsLogisticsRating: "Valoraciones de rutas", + logisticsRouteLabel: "RUTA", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Módulo para publicar canales de podcast.", + podcastsDescription: "Módulo para publicar canales de podcast.", + podcastCreate: "Crear podcast", + podcastUpdate: "Actualizar", + podcastDelete: "Borrar", + podcastCreateSectionTitle: "Crear podcast", + podcastUpdateSectionTitle: "Actualizar podcast", + podcastTitleLabel: "Título", + podcastTitlePlaceholder: "Nombre del canal", + podcastDescriptionLabel: "Descripción", + podcastDescriptionPlaceholder: "¿De qué trata este podcast?", + podcastCategoryLabel: "Categoría", + podcastCategoryNews: "NOTICIAS", + podcastCategoryMusic: "MÚSICA", + podcastCategoryTalk: "CHARLA", + podcastCategoryEducation: "EDUCACIÓN", + podcastCategoryTech: "TECNOLOGÍA", + podcastCategoryCulture: "CULTURA", + podcastCategoryCommunity: "COMUNIDAD", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "OTRAS", + podcastTagsLabel: "Etiquetas", + podcastCoverLabel: "Portada (imagen o vídeo)", + podcastEpisodeAudioLabel: "Archivo de audio o vídeo (máx: 75MB)", + podcastTagsPlaceholder: "Introduce etiquetas separadas por comas", + podcastFilterAll: "TODOS", + podcastFilterMine: "MÍOS", + podcastFilterRecent: "RECIENTES", + podcastFilterTop: "TOP", + podcastFilterViewers: "OYENTES", + podcastSearchPlaceholder: "Buscar en podcasts...", + podcastNoItems: "No se han encontrado podcasts.", + podcastEpisodesLabel: "Episodios", + podcastPlaysLabel: "Visualizaciones", + podcastLastEpisode: "Último episodio", + podcastEpisodesTitle: "Episodios", + podcastNoEpisodes: "Aún no hay episodios.", + podcastEpisodeAdd: "Añadir episodio", + podcastEpisodeCreateTitle: "Publicar episodio", + podcastEpisodeUpdateTitle: "Actualizar episodio", + podcastEpisodeTitleLabel: "Título del episodio", + podcastEpisodeTitlePlaceholder: "Título del episodio", + podcastEpisodeDescriptionLabel: "Notas", + podcastEpisodeDescriptionPlaceholder: "Qué pasa en este episodio, enlaces, créditos...", + podcastEpisodePublish: "Publicar", + podcastPlayed: "Reproducido", + podcastMarkPlayed: "Marcar como reproducido", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISODIO", + typePodcastOpinion: "OPINIÓN DE EPISODIO", + typePodcastPlay: "REPRODUCCIÓN", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Episodios", + statsPodcastPlay: "Reproducciones", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Un blog que sigues tiene una nueva entrada.", + podcastBotEpisodeTitle: "Un podcast que sigues tiene un episodio nuevo.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISODIO", + campaignsTitle: "Campañas", + modulesCampaignsLabel: "Campañas", + modulesCampaignsDescription: "Descubre y gestiona campañas en tu red.", + campaignsDescription: "Descubre y gestiona campañas en tu red.", + campaignCreate: "Crear campaña", + campaignUpdate: "Actualizar", + campaignDelete: "Borrar", + campaignClose: "Cerrar", + campaignReopen: "Reabrir", + campaignCreateSectionTitle: "Crear campaña", + campaignUpdateSectionTitle: "Actualizar campaña", + campaignTitleLabel: "Título", + campaignTitlePlaceholder: "¿Qué quieres conseguir?", + campaignTextLabel: "Texto", + campaignTextPlaceholder: "Explica la causa, quién decide y qué pides...", + campaignCategoryLabel: "Categoría", + campaignCategoryEnvironment: "MEDIO AMBIENTE", + campaignCategoryRights: "DERECHOS", + campaignCategoryHealth: "SALUD", + campaignCategoryEducation: "EDUCACIÓN", + campaignCategoryInfrastructure: "INFRAESTRUCTURA", + campaignCategoryCulture: "CULTURA", + campaignCategoryEconomy: "ECONOMÍA", + campaignCategoryOther: "OTRAS", + campaignGoalLabel: "Objetivo de firmas", + campaignDeadlineLabel: "Plazo", + campaignTagsLabel: "Etiquetas", + campaignTagsPlaceholder: "Introduce etiquetas separadas por comas", + campaignFilterAll: "TODAS", + campaignFilterMine: "MÍAS", + campaignFilterSigned: "FIRMADAS", + campaignFilterRecent: "RECIENTES", + campaignFilterTop: "TOP", + campaignStatusOpen: "ABIERTA", + campaignStatusAchieved: "LOGRADA", + campaignStatusUnachieved: "NO LOGRADA", + campaignStatusClosed: "CERRADA", + campaignSearchPlaceholder: "Buscar en campañas...", + campaignNoItems: "No se han encontrado campañas.", + campaignSignaturesLabel: "Firmas", + campaignSignaturesTitle: "Apoyos", + campaignNoSignatures: "Aún no hay firmas.", + campaignSignTitle: "Firmar esta campaña", + campaignSignPlaceholder: "¿Por qué la apoyas? (opcional)", + campaignSign: "Firmar", + campaignSigned: "¡Ya has firmado!", + campaignNotSigned: "Aún falta tu firma", + campaignElevate: "Elevar al Parlamento", + campaignElevated: "Elevada al Parlamento", + campaignUpdatesTitle: "Actualizaciones", + campaignNoUpdates: "Aún no hay actualizaciones.", + campaignUpdatePlaceholder: "¿Qué ha pasado?", + campaignAddUpdate: "Publicar actualización", + campaignQrAlt: "QR para firmar esta campaña", + campaignQrCaption: "Escanea para firmar", + typeCampaign: "CAMPAÑA", + typeCampaignSignature: "FIRMA", + typeCampaignUpdate: "ACTUALIZACIÓN DE CAMPAÑA", + statsCampaign: "Campañas", + statsCampaignSignature: "Firmas de campañas", + statsCampaignUpdate: "Actualizaciones de campañas", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Una campaña que sigues tiene una actualización.", + campaignBotAchievedTitle: "Una campaña que sigues ha alcanzado su objetivo.", + campaignBotRaisedTitle: "Una campaña que has firmado se ha elevado al Parlamento.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Una ruta que reservaste ha cambiado.", + logisticsBotClosedTitle: "Una ruta que reservaste se ha cerrado.", + logisticsBotBookedTitle: "Alguien ha reservado tu ruta.", + logisticsBotCancelledTitle: "Se ha cancelado una reserva de tu ruta.", + logisticsBotConfirmedTitle: "Tu reserva se ha confirmado: el paquete está en camino.", + logisticsBotRejectedTitle: "Tu reserva se ha rechazado.", + logisticsBotDeliveredTitle: "Tu paquete ha llegado.", + campaignLabel: "CAMPAÑA", + campaignGenerateReport: "Generar informe", + logisticsGenerateReport: "Generar informe", + mailingGenerateReport: "Generar informe", + campaignUpdateEdited: "Editada", + mailingMessagesLabel: "Mensajes", + logisticsBookingsLabel: "Reservas", + backupTitle: "Copia de seguridad", + modulesBackupLabel: "Copia de seguridad", + modulesBackupDescription: "Módulo para exportar e importar tus llaves y hacer copias de seguridad cifradas.", + backupDescription: "Módulo para exportar e importar tus llaves y hacer copias de seguridad cifradas.", + backupTypeRecovery: "RECUPERACIÓN", + backupTypeKeys: "EXPORTAR CLAVES", + backupTypeFull: "COPIA COMPLETA", + backupTypeRestore: "RESTAURAR COPIA", + backupTypeInstant: "INSTANTÁNEA", + backupInstantDescription: "Un .zip llano con todo tu registro y tus ficheros, sin tu clave secreta y sin contraseña. Rápido, pero ", + backupInstantRisk: "quien tenga el fichero lo lee todo.", + backupKitPageDescription: "El kit de recuperación solo puede abrirse desde este dispositivo.", + backupKitId: "ID de Oasis", + backupKitDate: "Generado", + backupKitQrAlt: "Código QR de tu llave secreta", + backupKitStep1: "Imprímelo o cópialo a mano y guárdalo en un lugar seguro y sin conexión.", + backupKitStep2: "Para recuperar, instala Oasis en un dispositivo nuevo e importa este secreto desde Copia de seguridad › Llaves.", + backupKitStep3: "Destruye esta página si alguna vez queda expuesta.", + backupKitPdfButton: "Generar PDF", + backupScopeLabel: "Alcance", + backupScopeAll: "TODO", + backupScopeMine: "SOLO MI CONTENIDO", + backupSinceLabel: "Desde", + backupEstimateTitle: "Estimación", + backupFileInfo: "Tu copia cifrada se descargará en tu dispositivo (nombre: oasis-backup-FECHA.oasisbk)", + backupExportButton: "Descargar copia", + backupRestorePassword: "Escribe la contraseña usada al hacer la copia", + backupRestoreButton: "Restaurar copia", + backupRestoredLine: "Copia restaurada.", + backupRestoredMessages: "Mensajes añadidos", + backupRestoredSkipped: "Ya existentes", + backupRestoredBlobs: "Archivos añadidos", + backupRestoredState: "Configuración restaurada", + backupRestoredFailed: "Fallidos", + backupRestoredForked: "Divergentes", + backupRestoreRunning: "Restaurando la copia. Esta página se actualiza sola; mientras tanto puedes seguir usando Oasis.", + backupRestoreElapsed: "Transcurrido", + backupRestoreFailedLine: "No se pudo restaurar la copia.", + backupRestoreForkMine: "Este dispositivo ya publicó mensajes con tu identidad que no están en la copia, así que no se pudo aplicar encima de ellos. Restaura en una instalación limpia justo después de importar tus llaves, antes de hacer nada más.", + verificationTitle: "Verificación", + verificationDescription: "Comprueba la cadena de tu feed, detecta bifurcaciones y revisa archivos huérfanos o ausentes.", + verificationRun: "Ejecutar verificación", + verificationTotalMessages: "mensajes en el registro", + verificationFeed: "Tu feed", + verificationMessages: "mensajes", + verificationLastSequence: "última secuencia", + verificationGaps: "Huecos", + verificationBrokenLinks: "Enlaces rotos", + verificationBadHashes: "Hashes incorrectos", + verificationBadSignatures: "Firmas incorrectas", + verificationDuplicates: "Secuencias duplicadas", + verificationForks: "Bifurcaciones", + verificationBlobs: "Archivos", + verificationBlobsPresent: "presentes", + verificationBlobsReferenced: "referenciados", + verificationBlobsOrphan: "huérfanos", + verificationBlobsMissing: "ausentes", + verificationBlobsMissingOwn: "Ficheros tuyos que faltan en este dispositivo", + verificationBlobsMissingOthers: "Ficheros de otras habitantes aún sin descargar", + verificationBlobsUnavailable: "No se han podido listar los archivos en este dispositivo.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Módulo para descubrir y gestionar wikis.", + wikiDescription: "Módulo para descubrir y gestionar wikis.", + wikiCreate: "Crear wiki", + wikiUpdate: "Actualizar", + wikiDelete: "Borrar", + wikiEdit: "Editar", + wikiTitleLabel: "Título", + wikiTitlePlaceholder: "Título de la wiki...", + wikiBodyLabel: "Contenido", + wikiBodyPlaceholder: "Enlaza otras wikis con [[Título de la wiki]] o [[Título de la wiki|un texto propio]].\n\nMenciona a una habitante con @id.\n\nEtiqueta con #oasis.\n\nPega enlaces como https://solarnethub.com\n\nUna línea en blanco abre un párrafo nuevo; los saltos de línea simples se conservan.\n\nAdjunta cualquier media soportado y se incrustará aquí.", + formatBarLabel: "Formato", + wikiStatusLabel: "Tipo de wiki", + wikiStatusOpen: "ABIERTA", + wikiStatusClosed: "CERRADA", + wikiTagsLabel: "Etiquetas", + wikiTagsPlaceholder: "Introduce etiquetas separadas por comas", + wikiSummaryLabel: "Resumen del cambio", + wikiSummaryPlaceholder: "¿Qué ha cambiado?", + wikiFilterAll: "TODAS", + wikiFilterMine: "MÍAS", + wikiFilterRecent: "RECIENTES", + wikiFilterLinked: "ENLAZADAS", + wikiChanges: "Cambios recientes", + wikiNoPages: "No se han encontrado wikis.", + wikiNoChanges: "Aún no hay cambios.", + wikiHistory: "Historial", + wikiVersion: "Versión", + wikiVersions: "versiones", + wikiRestore: "Restaurar", + wikiDiff: "Cambios", + wikiViewVersion: "Ver", + wikiLinkedPages: "Páginas enlazadas", + wikiNoLinkedPages: "Esta página aún no se enlaza con ninguna otra.", + wikiMissingLink: "La página aún no existe", + wikiDuplicateTitle: "Ya existe una página con este título. Cambia el título para crear una página distinta.", + wikiCreateSectionTitle: "Crear wiki", + wikiUpdateSectionTitle: "Actualizar wiki", + wikiSearchPlaceholder: "Buscar en la Wiki...", + wikiCurrentVersion: "Versión actual", + wikiViewingVersion: "Estás viendo una versión antigua", + wikiBackToPage: "Volver a la página", + wikiLinkedChip: "Enlazada", + wikiRestoredSummary: "Versión restaurada", + wikiExportPdf: "Generar página", + wikiPreview: "Vista previa", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Crear wiki", + tribeWikiEmpty: "Aún no hay wikis.", + typeWiki: "WIKI", padsTitle: "Pads", padTitle: "Pad", modulesPadsLabel: "Pads", @@ -3046,21 +3802,133 @@ module.exports = { padTitleLabel: "Título", padTitlePlaceholder: "Escribe el título del pad...", padStatusLabel: "Estado", + padTypeLabel: "Tipo de pad", padStatusOpen: "ABIERTO", - padStatusInviteOnly: "SOLO INVITADOS", + padStatusInviteOnly: "SOLO CON INVITACIÓN", padStatusClosed: "CERRADO", padDeadlineLabel: "Fecha límite", padTagsLabel: "Etiquetas", - padTagsPlaceholder: "etiqueta1, etiqueta2, ...", + padTagsPlaceholder: "Introduce etiquetas separadas por comas", padMembersLabel: "Miembros", padCreated: "Creado", - padStartEditing: "¡EMPEZAR A EDITAR!", + padStartEditing: "Editar", padEditorPlaceholder: "Empieza a escribir...", padSubmitEntry: "Enviar", padNoEntries: "Sin entradas aún.", padAllSectionTitle: "Todos los Pads", padMineSectionTitle: "Mis Pads", padsDescription: "Gestiona editores de texto cifrados colaborativos en tu red.", + roomsTitle: "Salas", + roomsDescription: "Descubre y únete a salas de videoconferencia cifradas en tu red.", + modulesRoomsLabel: "Salas", + modulesRoomsDescription: "Módulo para descubrir y unirse a salas de videoconferencia cifradas.", + roomFilterRecent: "RECIENTES", + roomFilterMine: "MÍAS", + roomFilterAll: "TODAS", + roomFilterLive: "EN DIRECTO", + roomFilterOpen: "ABIERTAS", + roomFilterInvite: "SOLO CON INVITACIÓN", + roomFilterClosed: "CERRADAS", + roomCreate: "Crear sala", + roomUpdate: "Actualizar", + roomDelete: "Eliminar", + roomClose: "Cerrar", + roomCreateSectionTitle: "Crear sala", + roomUpdateSectionTitle: "Actualizar sala", + roomTitlePlaceholder: "Título de la sala", + roomDescriptionLabel: "Descripción", + roomDescriptionPlaceholder: "¿De qué vais a hablar?", + roomTypeLabel: "Tipo de sala", + roomTagsLabel: "Etiquetas", + roomStatusOpen: "ABIERTA", + roomStatusInviteOnly: "SOLO CON INVITACIÓN", + roomStatusClosed: "CERRADA", + roomParticipants: "Participantes", + roomJoin: "Entrar", + roomLeave: "Salir", + roomFull: "Sala llena", + roomLiveChip: "EN DIRECTO", + roomMuted: "silenciado", + roomRec: "Grabar audio", + roomRecStop: "Parar grabación", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} ha entrado en la sala", + roomEventLeft: "{name} ha salido de la sala", + roomEventRecStart: "{name} ha empezado a grabar", + roomEventRecStop: "{name} ha dejado de grabar", + roomEventMuted: "{name} se ha silenciado", + roomEventUnmuted: "{name} ha quitado el silencio", + roomEventHand: "{name} ha levantado la mano", + roomRaiseHand: "Levantar mano", + roomLowerHand: "Bajar mano", + confirmActionTitle: "Confirma esta acción", + confirmActionButton: "Confirmar", + confirmJoinText: "Vas a unirte usando esta invitación.", + confirmFollowText: "Vas a apoyar a este habitante.", + confirmSignText: "Vas a firmar esta campaña.", + confirmDownloadText: "Vas a descargar este archivo a tu dispositivo.", + roomRecordings: "Grabaciones", + roomClearNotices: "Limpiar", + roomLineSwitchboard: "Centralita", + roomLineDndError: "El número de esta sala no acepta llamadas.", + roomNumberHint: "Marca este número desde Teléfono para entrar en la sala.", + liveRefresh: "Refrescar", + roomNobody: "Ahora mismo no hay nadie en la sala.", + roomSomeone: "{n} en la sala ahora mismo.", + roomYou: "tú", + roomYouAreIn: "Estás dentro", + roomNobodyInside: "Ahora mismo no hay nadie en esta sala.", + roomErrorFull: "La sala está llena. Podrás entrar cuando alguien salga.", + roomErrorUnreachable: "Ahora mismo no se puede llegar a la sala. Inténtalo más tarde.", + roomErrorBusy: "Ya estás en una llamada o en otra sala.", + roomErrorClosed: "Esta sala está cerrada.", + roomAccessDenied: "Esta sala es solo con invitación. Necesitas una invitación para entrar.", + roomsNoItems: "No se han encontrado salas.", + roomSearchPlaceholder: "Buscar salas...", + roomInviteMode: "Invitación", + tribeSectionRooms: "SALAS", + tribeRoomCreate: "Crear sala", + tribeRoomsEmpty: "Aún no hay salas.", + typeRoom: "SALAS", + typeRoomMember: "PARTICIPANTE DE SALA", + statsRoom: "Salas", + statsRoomMember: "Participantes de salas", + favoritesFilterRooms: "SALAS", + invitesRoomsTitle: "Salas", + inviteCodeInvalid: "Este código de invitación no es válido o ya se ha usado.", + inviteAlreadyMember: "Ya formas parte. Puedes abrirlo desde su módulo.", + actionFailed: "No se ha podido hacer. Inténtalo de nuevo.", + publishTooShort: "Tu publicación es demasiado corta.", + walletSendFailed: "No se ha podido enviar el pago: {reason}", + pixeliaCoordsError: "Esas coordenadas están fuera de la cuadrícula.", + courtsErrTitle: "Hace falta un título.", + courtsErrRespondent: "Indica contra quién es el caso.", + courtsErrRespondentId: "Ese no es un Oasis ID válido.", + courtsErrMethod: "Elige cómo se resolverá el caso.", + courtsErrCaseNotFound: "No se ha encontrado ese caso.", + courtsErrBrief: "Escribe tu respuesta.", + courtsErrStance: "Elige tu postura.", + courtsErrResult: "Escribe el resultado.", + courtsErrTerms: "Escribe los términos del acuerdo.", + courtsErrMediator: "Añade al menos un mediador.", + courtsErrSelfMediator: "No puedes ser mediador en tu propio caso.", + courtsErrJudge: "Elige un juez.", + courtsErrSelfJudge: "No puedes ser juez de tu propio caso.", + courtsErrVisibility: "Elige quién puede ver el caso.", + courtsErrNomination: "No se ha encontrado esa nominación.", + opinionAlreadyGiven: "Ya has dado tu opinión.", + cipherPasswordShort: "La contraseña debe tener al menos 32 caracteres.", + errorForbidden: "No tienes acceso a esto.", + errorNotFound: "No se ha encontrado.", + errorClosed: "Está cerrado.", + errorInviteOnly: "Esto es solo con invitación. Necesitas una invitación.", + errorTooLong: "Ese texto es demasiado largo.", + errorRequired: "Falta información obligatoria.", + errorInvalid: "Parte de la información no es válida.", + errorNotAuthor: "Solo quien lo creó puede hacer esto.", + panicRemoved: "Tu blockchain se ha eliminado.", padRecentSectionTitle: "Pads Recientes", padOpenSectionTitle: "Pads Abiertos", padClosedSectionTitle: "Pads Cerrados", @@ -3072,7 +3940,7 @@ module.exports = { padRemoveFavorite: "Quitar de Favoritos", padClose: "Cerrar", padBackToEditor: "Volver al editor", - padSearchPlaceholder: "Buscar pads...", + padSearchPlaceholder: "Buscar en Pads...", calendarsTitle: "Calendarios", calendarTitle: "Calendario", @@ -3081,15 +3949,15 @@ module.exports = { typeCalendar: "CALENDARIOS", calendarCreate: "Crear Calendario", calendarUpdate: "Actualizar", - calendarDelete: "Eliminar", calendarTitleLabel: "Título", calendarTitlePlaceholder: "Título del calendario...", calendarStatusLabel: "Estado", + calendarTypeLabel: "Tipo de calendario", calendarStatusOpen: "ABIERTO", calendarStatusClosed: "CERRADO", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Fecha límite", calendarTagsLabel: "Etiquetas", - calendarTagsPlaceholder: "etiqueta1, etiqueta2...", + calendarTagsPlaceholder: "Introduce etiquetas separadas por comas", calendarParticipantsLabel: "Participantes", calendarCreated: "Creado", calendarJoin: "Unirse al Calendario", @@ -3100,11 +3968,11 @@ module.exports = { calendarNotePlaceholder: "Añadir una nota...", calendarFirstDateLabel: "Fecha", calendarFirstNoteLabel: "Notas", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Intervalo", calendarIntervalNone: "Sin repetición", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Semanal", + calendarIntervalMonthly: "Mensual", + calendarIntervalYearly: "Anual", calendarFormDescription: "Descripción", calendarNoDates: "No hay fechas añadidas.", calendarNoNotes: "Sin notas.", @@ -3112,6 +3980,8 @@ module.exports = { calendarsDescription: "Descubre y gestiona calendarios en tu red.", calendarMonthPrev: "\u2190 Anterior", calendarMonthNext: "Siguiente \u2192", + calendarMonthNames: "Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,Octubre,Noviembre,Diciembre", + calendarDayInitials: "L,M,X,J,V,S,D", calendarAllSectionTitle: "Calendarios", calendarRecentSectionTitle: "Calendarios Recientes", calendarFavoritesSectionTitle: "Favoritos", @@ -3121,24 +3991,24 @@ module.exports = { calendarCreateSectionTitle: "Crear Nuevo Calendario", calendarUpdateSectionTitle: "Actualizar Calendario", calendarAddFavorite: "Añadir a Favoritos", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Eliminar", calendarRemoveFavorite: "Quitar de Favoritos", - calendarSearchPlaceholder: "Buscar calendarios...", + calendarSearchPlaceholder: "Buscar en Calendario...", calendarAddEntry: "Añadir Entrada", statsCalendar: "Calendarios", statsCalendarDate: "Fechas de Calendario", statsCalendarNote: "Notas de Calendario", modulesChatsLabel: "Chats", - modulesChatsDescription: "Módulo para descubrir y gestionar chats cifrados.", + modulesChatsDescription: "Módulo para descubrir y gestionar chats.", typeChat: "CHATS", typeChatMessage: "MENSAJE DE CHAT", chatsTitle: "Chats", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "Tus chats", + chatRecentTitle: "Chats recientes", chatFavoritesTitle: "Favoritos", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "Chats abiertos", + chatClosedTitle: "Chats cerrados", chatDescription: "Descripción", chatCategory: "Categoría", chatStatus: "ESTADO", @@ -3153,34 +4023,172 @@ module.exports = { chatMessagesLabel: "Mensajes", chatThreadsLabel: "Hilos", chatCreatedAt: "CREADO", - chatSearchPlaceholder: "Buscar chats...", + chatSearchPlaceholder: "Buscar en Chats...", chatStatusOpen: "ABIERTO", - chatStatusInviteOnly: "SOLO INVITADOS", + chatStatusInviteOnly: "SOLO CON INVITACIÓN", chatStatusClosed: "CERRADO", chatSendMessage: "Enviar", chatJumpLatest: "Último Mensaje", + chatReply: "Responder", + chatReplyCancel: "Cancelar respuesta", + chatPin: "Fijar", + chatUnpin: "Desfijar", + chatPinned: "Fijados", chatPickChat: "Elige un chat para abrirlo", chatNoneYet: "No existe ningún chat disponible, todavía.", - activitySearchPlaceholder: "Buscar en la actividad...", - trendingSearchPlaceholder: "Buscar en tendencias...", - opinionsSearchPlaceholder: "Buscar en opiniones...", - votesSearchPlaceholder: "Buscar en votaciones...", - welcomeStepUxTitle: "Elige tu interfaz", - welcomeStepUxText: "Decide cómo se ve Oasis. Podrás cambiarlo después en Ajustes.", - welcomeStepUxAction: "Usar esta vista", + activitySearchPlaceholder: "Buscar en Actividad...", + trendingSearchPlaceholder: "Buscar en Tendencias...", + opinionsSearchPlaceholder: "Buscar en Opiniones...", + votesSearchPlaceholder: "Buscar en Votaciones...", + gamesSearchPlaceholder: "Buscar en Juegos...", + schoolTitle: "Escuela", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Se ha editado una página wiki que sigues.", + wikiBotRestoredTitle: "Se ha restaurado una versión anterior de una página wiki que sigues.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Un nuevo estudiante se ha apuntado a tu curso.", + schoolBotInvitedTitle: "Te han invitado a un curso.", + schoolBotAdmittedTitle: "Te han admitido en un curso.", + schoolBotCertificateTitle: "Has recibido un certificado.", + schoolBotPassedTitle: "Un estudiante ha aprobado tu curso.", + schoolBotLessonTitle: "Se ha publicado una nueva lección.", + invitesSchoolTitle: "Escuela", + schoolEnrollLabel: "Unirse al curso", + schoolExamRetryLater: "Podrás reintentarlo pasadas 24 horas.", + schoolApproved: "APROBADO", + schoolExamPassMark: "Nota de aprobado (0-10)", + schoolExamPassed: "APROBADO", + schoolExamPending: "Pendiente", + schoolExamFailed: "SUSPENDIDO", + schoolExamAddQuestion: "Añadir pregunta", + schoolExamCreateQuestion: "Crear pregunta", + schoolExamOption: "Opción", + schoolExamCorrect: "Opción correcta", + schoolExamPoints: "Puntos", + schoolMaterialTextPlaceholder: "Notas para esta lección...", + schoolMaterials: "Materiales", + schoolAddMaterial: "Añadir material", + schoolNoMaterials: "Aún no hay materiales.", + bankYourSchoolBalance: "Tus ingresos de la escuela", + schoolProgress: "Progreso", + schoolProgressMark: "Marcar como completada", + schoolProgressDone: "Completada", + schoolExams: "Exámenes", + schoolExamCreate: "Crear examen", + schoolExamTitlePlaceholder: "Título del examen", + schoolExamHelp: "Una pregunta por línea: ¿Pregunta? ; opción ; opción correcta* ; opción", + schoolExamQuestionsPlaceholder: "¿Qué es SSB? ; Una base de datos ; Un protocolo P2P* ; Un navegador", + schoolExamSubmit: "Enviar respuestas", + schoolExamScore: "Tu nota", + schoolNoScoresYet: "Aún no hay resultados.", + schoolLessonUnit: "Unidad / fase", + schoolLessonUnitPlaceholder: "p. ej. Fase 1", + schoolLessonOrder: "Orden", + profileClearnetSchoolLabel: "Escuela", + schoolStartDate: "Fecha de inicio", + schoolSessionDate: "Fecha de la sesión", + schoolCourseChat: "Chat del curso", + agendaFilterSchool: "ESCUELA", + agendaFilterCampaigns: "CAMPAÑAS", + agendaFilterLogistics: "LOGÍSTICA", + schoolCourseType: "Tipo de curso", + schoolTypeOpen: "ABIERTO", + schoolTypePaid: "DE PAGO", + schoolTypeInvite: "SOLO INVITACIÓN", + schoolLessonLocked: "Lección cifrada — pide la clave a quien imparte del curso.", + schoolGrantAccess: "Enviar clave del curso", + schoolKeySent: "Clave enviada", + schoolOngoing: "En curso", + schoolFilterApplied: "APUNTADO", + schoolFilterOpen: "ABIERTOS", + schoolFilterFavorites: "FAVORITOS", + schoolPendingPayment: "Pago pendiente", + schoolPendingStudents: "Inscripciones pendientes", + schoolViewBill: "Ver factura", + schoolPaymentNote: "Al inscribirte se genera una factura en ECO (una transferencia). Tu inscripción se confirma cuando la transferencia queda firmada.", + schoolCloseCourse: "Cerrar curso", + schoolReopenCourse: "Reabrir curso", + schoolDescription: "Enseña y aprende cursos en tu red.", + schoolFilterAll: "TODOS", + schoolFilterMine: "MÍOS", + schoolFilterTop: "TOP", + schoolFilterRecent: "RECIENTES", + schoolCreateButton: "Crear Curso", + schoolCreateSectionTitle: "Crear un curso", + schoolUpdateSectionTitle: "Actualizar curso", + schoolCourseTitlePlaceholder: "Nombre de tu curso", + schoolCourseDescriptionPlaceholder: "¿Qué vas a enseñar en este curso?", + schoolCourseImage: "Imagen del curso (tamaño máx: 75MB)", + schoolTags: "Etiquetas", + schoolTagsPlaceholder: "Introduce etiquetas separadas por comas", + schoolPrice: "Precio (ECO)", + schoolVisibility: "Visibilidad", + schoolVisibilityPublic: "Público", + schoolVisibilityInvite: "Por invitación", + schoolStatus: "Estado", + schoolClosed: "Cerrado", + schoolFree: "Gratis", + schoolCreate: "Crear curso", + schoolUpdate: "Actualizar curso", + schoolSearchPlaceholder: "Buscar en Escuela...", + schoolSortTitle: "Título", + schoolNoCourses: "No se han encontrado cursos.", + schoolStudents: "Estudiantes", + schoolTeacher: "Docente", + schoolCreatedAt: "CREADO", + schoolLessons: "Lecciones", + schoolNoLessons: "Aún no hay lecciones.", + schoolEnrollToSee: "Inscríbete para ver las lecciones de este curso.", + schoolEnrollButton: "INSCRIBIRSE", + schoolUnenrollButton: "Abandonar curso", + schoolInviteRequired: "Este curso es por invitación.", + schoolTeacherPanel: "Panel docente", + schoolStudentPanel: "Panel del estudiante", + schoolAddLesson: "Crear lección", + schoolLessonTitlePlaceholder: "Título de la lección", + schoolLessonTextPlaceholder: "Describe las nociones que cubre esta lección...", + schoolInviteStudents: "Invitar estudiantes", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "INVITAR", + schoolInvited: "Invitadas", + schoolIssueCertificate: "Crear certificado", + schoolCertified: "Certificado", + schoolCertificateTextPlaceholder: "Nota del certificado (opcional)", + schoolLessonOpen: "Abrir lección", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificados", + schoolNoCertificates: "Aún no se han emitido certificados.", + schoolEditButton: "Editar", + schoolDeleteButton: "Borrar", + typeSchool: "ESCUELA", + schoolCourseLabel: "CURSO", + emergencyLabel: "EMERGENCIA", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Cursos", + statsSchoolLesson: "Lecciones", + statsSchoolEnroll: "Inscripciones", + statsSchoolCertificate: "Certificados de la escuela", + modulesSchoolLabel: "Escuela", + modulesSchoolDescription: "Módulo para intercambiar conocimiento.", + welcomeStepUxAction: "Guardar vista", + welcomeStepWorkflowTitle: "¿Qué uso de OASIS tienes en mente?", + welcomeStepWorkflowText: "Oasis tiene muchos módulos. Elige la configuración que encaje contigo y solo quedarán activados los útiles. Puedes ajustarlo cuando quieras en Módulos.", + welcomeStepWorkflowAction: "Guardar configuración", + welcomeWorkflow_full: "Por defecto", chatRateLimitTitle: "Demasiados mensajes", chatRateLimitMessage: "Has alcanzado el límite de mensajes que esta sala acepta en una hora. Inténtalo más tarde.", chatMessagePlaceholder: "Escribe tu mensaje...", chatNoMessages: "Sin mensajes aún.", chatTitlePlaceholder: "Título del chat", chatDescriptionPlaceholder: "Descripción del chat", - chatTagsPlaceholder: "etiqueta1, etiqueta2", + chatTagsPlaceholder: "Introduce etiquetas separadas por comas", chatImageLabel: "Selecciona un archivo de imagen (.jpeg, .jpg, .png, .gif)", - chatAuthor: "Autor", + chatAuthor: "Autora", chatAddFavorite: "Añadir a Favoritos", chatRemoveFavorite: "Quitar de Favoritos", chatPM: "MP", chatStatusLabel: "Estado", + chatTypeLabel: "Tipo de chat", chatCategoryLabel: "Categoría", gamesTitle: "Juegos", gamesDescription: "Descubre y juega algunos juegos.", @@ -3195,7 +4203,7 @@ module.exports = { gamesTheFlowTitle: "ECOinflow", gamesTheFlowDesc: "Conecta PUBs a habitantes mediante validadores, tiendas y acumuladores. ¡Sobrevive a la amenaza CBDC!", gamesNeonInfiltratorTitle: "Neon Infiltrator", - gamesNeonInfiltratorDesc: "Infiltra la cuadricula, recolecta datos confidenciales, evade los drones de seguridad y escapa. ¿Cuantos niveles puedes superar?", + gamesNeonInfiltratorDesc: "Infíltrate en la cuadrícula, recolecta datos confidenciales, evade los drones de seguridad y escapa. ¿Cuántos niveles puedes superar?", gamesSpaceInvadersTitle: "Space Invaders", gamesSpaceInvadersDesc: "¡Detén la invasión alienígena! Destruye oleadas de invasores antes de que lleguen al suelo.", gamesArkanoidTitle: "Arkanoid", @@ -3211,22 +4219,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Lanza una moneda y apuesta cara o cruz. ¿Cuánta suerte tienes?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "Billar visto desde arriba. Haz clic para apuntar y mantén pulsado para cargar la potencia. Mete todas las bolas con el menor número de tiros.", + gamesArtilleryTitle: "Artillería", + gamesArtilleryDesc: "Apunta tu cañón, ten en cuenta el viento y da en el blanco. 5 rondas: gana quien use menos disparos.", + gamesLabyrinthTitle: "Laberinto", + gamesLabyrinthDesc: "Escapa del laberinto antes de que se te acaben los movimientos. Cada nivel es más grande y más difícil.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Cómete todos los puntos y esquiva a los fantasmas. Los fantasmas se mueven en tiempo real: ¡no te detengas!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "Péndulo sonoro", + gamesAudioPendulumDesc: "Simulador de física caótica con síntesis de audio en tiempo real. Las velocidades angulares se convierten en frecuencias y los picos en golpes de percusión. No hay dos simulaciones que suenen igual.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Pincha visuales en directo con dos platos: elige un patrón para cada uno, ajusta velocidad, color y zoom, mezcla entre ambos y lanza destellos al ritmo. Marca el tempo o deja que el micrófono lleve el pulso.", + gamesTetrisDesc: "Los clásicos bloques que caen. Completa líneas para puntuar. ¿Cuánto aguantarás?", + gamesMinesweeperTitle: "Buscaminas", + gamesMinesweeperDesc: "La clásica caza de minas. Despeja el tablero para subir de nivel: cuadrículas más grandes, más minas, más puntuación.", + gamesFilterScoring: "PUNTUACIONES", + gamesHallOfFame: "Salón de la fama", + gamesHallPlayer: "Jugadora", + gamesHallScore: "Puntuación", + gamesNoScores: "Aún no hay puntuaciones.", chatAccessDenied: "No tienes acceso a este chat. Solicita una invitación para acceder al contenido.", padAccessDenied: "No tienes acceso a este pad. Solicita una invitación para acceder al contenido.", contentAccessDenied: "No tienes acceso a este contenido.", @@ -3234,6 +4246,146 @@ module.exports = { tribeContentAccessDenied: "Acceso Denegado", tribeContentAccessDeniedMsg: "Este contenido pertenece a una tribu. Debes ser miembro para acceder.", tribeViewTribes: "Ver Tribus", + tribeFilesEmpty: "Aún no hay ficheros.", + filesLabel: "Ficheros", + profileClearnetFilesLabel: "Ficheros", + filesTitle: "Ficheros", + filesDescription: "Explora y gestiona ficheros en tu red.", + fileAllSectionTitle: "Ficheros", + fileMineSectionTitle: "Tus Ficheros", + fileRecentSectionTitle: "Ficheros Recientes", + fileTopSectionTitle: "Ficheros Destacados", + fileFavoritesSectionTitle: "Favoritos", + fileFilterAll: "TODOS", + fileFilterMine: "MÍOS", + fileFilterRecent: "RECIENTES", + fileFilterTop: "DESTACADOS", + fileFilterFavorites: "FAVORITOS", + fileCreateSectionTitle: "Subir Fichero", + fileUpdateSectionTitle: "Actualizar Fichero", + fileCreateButton: "Subir Fichero", + fileUpdateButton: "Actualizar", + fileAddFavoriteButton: "Añadir Favorito", + fileRemoveFavoriteButton: "Quitar Favorito", + fileFileLabel: "Selecciona un archivo (máx.: 75MB)", + calendarFilterRecent: "RECIENTES", + calendarFilterAll: "TODOS", + calendarFilterMine: "MÍOS", + calendarFilterFavorites: "FAVORITOS", + calendarFilterOpen: "ABIERTOS", + calendarFilterClosed: "CERRADOS", + padFilterRecent: "RECIENTES", + padFilterAll: "TODOS", + padFilterMine: "MÍOS", + padFilterOpen: "ABIERTOS", + padFilterClosed: "CERRADOS", + chatFilterRecent: "RECIENTES", + chatFilterAll: "TODOS", + chatFilterMine: "MÍOS", + chatFilterFavorites: "FAVORITOS", + chatFilterOpen: "ABIERTOS", + chatFilterClosed: "CERRADOS", + licenseLabel: "Licencia", + phoneTitle: "Teléfono", + phoneDescription: "Gestiona tus videollamadas cifradas de extremo a extremo.", + phoneUnavailable: "Las llamadas no están disponibles en este dispositivo.", + phoneCallButton: "Llamar", + profileVisibilityPhone: "Número de teléfono", + phoneNumberUnknown: "Nadie en tu red tiene el número {number}.", + phoneNumberChoose: "Comprueba a quién quieres llamar al {number}:", + phoneAnswer: "Contestar", + phoneSpeaker: "Altavoz", + phoneEarpiece: "Auricular", + phoneReject: "Rechazar", + phoneHangup: "Colgar", + phoneMute: "Silenciar", + phoneUnmute: "Activar micrófono", + phoneCameraOn: "Cámara on", + phoneCameraOff: "Cámara off", + settingsPhoneVideo: "Vídeo", + settingsPhoneVideoOn: "Permitido en llamadas y salas", + settingsPhoneVideoOff: "Solo audio", + phoneSilence: "No oír", + phoneUnsilence: "Volver a oír", + phoneCalling: "Llamando a", + phoneIsCalling: "te está llamando", + notifyNewPmFrom: "Nuevo MP de", + notifyNewPamFrom: "Nuevo PaM de", + notifyUpdateLabel: "Actualización", + updateInstalledLabel: "Versión instalada", + updateAvailableLabel: "Versión disponible", + updateChangesLabel: "Cambios", + updateFilesLabel: "Archivos", + updateOriginLabel: "Origen", + phoneRecording: "Grabando PaM para", + phoneLeaveMessage: "Nadie contesta: deja tu mensaje después de la señal.", + phoneLeaveMessageBusy: "Está en otra llamada: deja tu mensaje después de la señal.", + phoneLeaveMessageUnreachable: "No se ha podido contactar: deja tu mensaje después de la señal.", + phoneConnecting: "Conectando con", + phoneSendPam: "Enviar PaM", + phoneCancel: "Cancelar", + phoneSearchPlaceholder: "Buscar en Teléfono...", + phonePamTitle: "Grabaciones de audio", + phoneNew: "Nuevo", + phoneDelete: "Borrar", + phoneNoPams: "No tienes grabaciones de audio.", + phoneHistoryTitle: "Historial", + phoneLatestTitle: "Últimas llamadas", + phoneCreateButton: "Nueva llamada", + phoneLimitsHint: "Hasta 7 habitantes por llamada; con más de uno es una llamada conjunta.", + phoneErrorPub: "No se puede llamar a un pub.", + phoneJointCall: "Llamada conjunta", + phoneWith: "Con", + phoneResult: "Resultado", + phoneWhen: "Cuándo", + phoneDuration: "Duración", + phoneOutgoing: "Saliente", + phoneIncoming: "Entrante", + phoneNoHistory: "Aún no hay registros.", + phoneOutcomeEnded: "Atendida", + phoneOutcomeMissed: "Perdida", + phoneOutcomeRejected: "Rechazada", + phoneOutcomeNoanswer: "Sin respuesta", + phoneOutcomeCancelled: "Cancelada", + phoneOutcomePam: "PaM enviado", + phoneOutcomeFailed: "Fallida", + phoneDialAdd: "Añadir", + phoneDialBack: "Borrar", + phoneDialClear: "Limpiar", + phoneDialPlaceholder: "Número u Oasis ID", + phoneDialInhabitants: "Habitantes", + phoneDialRemove: "Quitar", + phoneOwnNumber: "Ese es tu propio número.", + phoneSettingsDesc: "Elige quién puede llamarte.", + phoneDnd: "No molestar", + phoneOpen: "Abierto", + phoneRecipientRefuses: "El habitante al que intentas llamar no permite recibir llamadas.", + phoneRefusedNotice: "Estos habitantes no permiten recibir llamadas y no se les ha llamado:", + fileEmptyError: "El archivo está vacío. Elige otro archivo.", + fileTitleLabel: "Título", + fileTitlePlaceholder: "Título", + fileDescriptionLabel: "Descripción", + fileDescriptionPlaceholder: "Descripción", + fileTagsLabel: "Etiquetas", + fileTagsPlaceholder: "Introduce etiquetas separadas por comas", + fileSizeLabel: "Tamaño", + fileDownloadButton: "DESCARGA", + fileDetailsButton: "DETALLES", + fileNoFile: "Sin archivo fichero", + fileSearchPlaceholder: "Buscar en Ficheros...", + fileSearchButton: "Buscar", + fileSortRecent: "Más recientes", + fileSortOldest: "Más antiguos", + fileSortTop: "Más votados", + fileNoMatch: "No se encontraron ficheros coincidentes.", + fileUpdatedAt: "Actualizado", + typeFile: "FICHEROS", + modulesFilesLabel: "Ficheros", + modulesFilesDescription: "Módulo para descubrir y gestionar ficheros.", + tribeSectionFiles: "FICHEROS", + fileDescription: "Descripción", + fileDownload: "Descargar", + fileTypeLabel: "Tipo", torrentsTitle: "Torrents", torrentsDescription: "Explora y gestiona torrents en tu red.", torrentAllSectionTitle: "Torrents", @@ -3261,10 +4413,33 @@ module.exports = { torrentTagsLabel: "Etiquetas", torrentTagsPlaceholder: "Introduce etiquetas separadas por comas", torrentSizeLabel: "Tamaño", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Generar torrent", + torrentFromContentMissing: "El fichero aún no está disponible en este nodo. Inténtalo en un rato.", + torrentDownloads: "Descargas", + torrentFetchOasis: "Descargar en Oasis", + torrentSaveFile: "Guardar fichero", + torrentSeeds: "semillas", + seedsLabel: "Semillas", + seedAction: "Soportar", + torrentSourceFile: "Fichero", + torrentStateQueued: "En cola", + torrentStateProbing: "Buscando semillas", + torrentStateDownloading: "Descargando", + torrentStateDone: "Completada", + torrentStateFailed: "Fallida", + torrentStateNoSource: "Sin semillas conectadas, esperando", + torrentStateCancelled: "Cancelada", + torrentNoDownloads: "Todavía no hay descargas.", + torrentOriginalSection: "Original", + torrentCancel: "Cancelar", + torrentDownloadButton: "DESCARGA", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETALLES", torrentNoFile: "Sin archivo torrent", noTorrents: "No se encontraron torrents.", - torrentSearchPlaceholder: "Buscar título, etiquetas, autor...", + noFiles: "No se encontraron ficheros.", + torrentSearchPlaceholder: "Buscar en Torrents...", torrentSearchButton: "Buscar", torrentSortRecent: "Más recientes", torrentSortOldest: "Más antiguos", @@ -3272,10 +4447,12 @@ module.exports = { torrentNoMatch: "No se encontraron torrents coincidentes.", torrentUpdatedAt: "Actualizado", statsTorrent: "Torrents", + statsFile: "Ficheros", typeTorrent: "TORRENTS", modulesTorrentsLabel: "Torrents", modulesTorrentsDescription: "Módulo para descubrir y gestionar torrents.", favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "FICHEROS", favoritesFilterMarket: "MERCADO", favoritesFilterEvents: "EVENTOS", favoritesFilterForum: "FORO", @@ -3289,15 +4466,17 @@ module.exports = { favoritesFilterShopProducts: "ARTÍCULOS DE TIENDA", tribeSectionTorrents: "TORRENTS", tribeCreateTorrent: "Subir Torrent", + tribeCreateFile: "Subir fichero", settingsWishTitle: "Deseo", - settingsWishDesc: "Configura el deseo de tu Avatar. Determina cómo accedes a todo el contenido y tu experiencia en la red Oasis.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Determina cómo accedes a todo el contenido y tu experiencia en la red Oasis.", + settingsWishWhole: "Multiverso", + settingsWishMutuals: "Solo soporte mutuo", + settingsWishOnlyLan: "Solo LAN", + settingsWishLocal: "Local", settingsPmVisibilityTitle: "Mensajes Privados", settingsPmVisibilityDesc: "Configura el nivel de exposición a mensajes privados que deseas tener en la red.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Multiverso", + settingsPmVisibilityMutuals: "Solo soporte mutuo", pmMutualNotice: "El envío es una barrera de UX. La recepción es un filtro de atención (el cifrado ya está replicado).", inhabitantsPendingFollowsTitle: "Solicitudes de apoyo pendientes", inhabitantsPendingAccept: "Aceptar", @@ -3334,14 +4513,14 @@ module.exports = { tribeGovernanceNoCandidatures: "Sin candidaturas abiertas.", tribeGovernanceAddRule: "Añadir regla", tribeGovernanceNoRules: "Sin reglas todavía.", - tribeGovernanceNoLeaders: "Sin líderes elegidos todavía.", + tribeGovernanceNoLeaders: "Sin liderazgo elegido todavía.", tribeGovernanceComingSoon: "Próximamente en el módulo de gobierno de esta tribu.", tribeGovNoProposals: "Aún no hay ninguna propuesta", tribeGovNoLaws: "Aún no hay ninguna ley", tribeGovNoRevocations: "Aún no hay ninguna revocación", tribeGovNoHistorical: "Aún no hay ningún registro", logsTitle: "Bitácora", - logsDescription: "Registra tu experiencia en la red.", + logsDescription: "Lleva un diario privado de tus experiencias.", logsViewTitle: "Bitácora", logsColumnType: "Tipo", logsView: "Ver", @@ -3350,7 +4529,7 @@ module.exports = { logsCreateTitle: "Crear entrada", logsWriteButton: "Escribir", logsTextPlaceholder: "Describe tus experiencias...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "Asistente IA", logsModeManual: "Manual", logsModeAI: "IA", logsColumnLog: "Bitácora", @@ -3369,19 +4548,20 @@ module.exports = { logsExportOne: "Exportar registro", logsViewDetails: "Ver detalles", logsGenerateButton: "Generar texto", - logsSearchText: "Buscar en bitácoras...", + logsSearchText: "Buscar en Bitácora...", logsSearchAnyType: "Cualquier tipo", logsSearchButton: "Buscar", logsEmpty: "Aún no hay entradas.", + logsNothingNew: "No hay acciones nuevas que registrar.", modulesLogsLabel: "Bitácora", - modulesLogsDescription: "Módulo para registrar (vía asistente de IA) tus experiencias.", + modulesLogsDescription: "Módulo para llevar un diario privado de tus experiencias.", statsLogsTitle: "Bitácora", typeLog: "BITÁCORA", larpTitle: "L.A.R.P.", larpDescription: "Una capa de juego de rol en vivo para la experimentación colaborativa.", larpNoHousesMatch: "Ninguna casa coincide con tu búsqueda.", - larpSearchPlaceholder: "Buscar casas...", + larpSearchPlaceholder: "Buscar en L.A.R.P....", larpAllHouses: "Casas:", larpFilterRuling: "RIGE", larpFilterHouses: "CASAS", @@ -3390,42 +4570,43 @@ module.exports = { larpRulesEntryText: "Todo habitante comienza en ACADEMIA por defecto. Desde ACADEMIA, elige una casa en el panel \"Unirse a una casa\": esto abre su test de entrada. Responde las 10 preguntas y envía. Si superas el umbral (7/10), serás admitido automáticamente en esa casa; si no, serás rechazado y permanecerás en ACADEMIA. En cualquier caso el sistema registra tu OasisID y el ciclo del intento e impide otro intento hasta que pase el enfriamiento de 30 ciclos.", larpRulesGovernanceText: "Durante su ciclo, la casa gobernante luce la insignia \"Rige\" y es la única cuyo Muro se muestra públicamente bajo la pestaña RIGE.", larpRulesWallText: "Cada casa tiene un Muro donde escriben sus miembros. El Muro de una casa es privado para la casa, salvo el de la casa gobernante y el de la ACADEMIA, que puede leer cualquiera.", - larpRulesSignText: "Los habitantes pueden activarlo (en /profile/edit > Sensores) para mostrar la imagen de su casa actual como un sello en su perfil, en su tarjeta de autor y de habitante. El sello enlaza a la página de la casa.", + larpRulesSignText: "Las habitantes pueden activarlo (en /profile/edit > Sensores) para mostrar la imagen de su casa actual como un sello en su perfil, en su tarjeta de autora y de habitante. El sello enlaza a la página de la casa.", larpPostsTitle: "Muro", larpPostsEmpty: "Aún no hay publicaciones.", larpPostPlaceholder: "¿Qué tiene que decir esta casa?", larpPostSubmit: "Publicar", larpCycleLabel: "Ciclo:", - bankRulesArchTaxFormula: "ARCH Tax(usuario) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", + bankRulesArchTaxFormula: "ARCH Tax(habitante) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", bankRulesArchTaxNote: "El impuesto ARCH refleja el coste a largo plazo de hacer crecer y mantener el archivo de la red: cuanto más antiguos sean tus datos en la red, mayor será tu parte del coste de mantenimiento.", bankRulesChipFormula: "band = ratio / reducer, donde ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). alto ≥ 0.66, medio ≥ 0.33, en otro caso bajo.", bankRulesChipNote: "El chip de cada elemento se colorea respecto al mensaje más grande visto en la red y se suaviza por el número de habitantes activos — una red más grande distribuye la carga y empuja más elementos a la banda verde.", - bankRulesEcoTaxFormula: "ECO Tax(usuario) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "El impuesto ECO se deduce del UBI bruto cuando se calcula la asignación del epoch. Los ECO deducidos no se redistribuyen en este epoch — permanecen en el saldo del pub y alimentan el pool del próximo epoch a través de los topes reserveMin / alpha.", + bankRulesEcoTaxFormula: "ECO Tax(habitante) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", + bankRulesEcoTaxNote: "El impuesto ECO se deduce de la RBU bruta cuando se calcula la asignación de la época. Los ECO deducidos no se redistribuyen en esta época — permanecen en el saldo del pub y alimentan el fondo de la próxima época a través de los topes reserveMin / alpha.", bankRulesKarmaFormula: "karma = log10(1 + actions) + reputation", - bankRulesKarmaNote: "El karma pondera tu participación en la red de cara a la asignación de UBI.", + bankRulesKarmaNote: "El karma se deriva de tus acciones (publicaciones, votos, participación en el parlamento/tribunales, etc.) con un factor de decaimiento temporal, menos los gramos de carbono que genera tu feed.", bankRulesNetFormula: "net_UBI = floor + max(0, gross_UBI − floor) − ECO Tax − ARCH Tax", bankRulesPoolFormula: "pool = min(pubBal − reserveMin, capPerEpoch, alpha · pubBal)", bankRulesShareFormula: "share = clamp(1 + karma/100, w_min, w_max) / sum(weights)", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", bankTaxesArchTaxFootprintNote: "El impuesto ARCH refleja el coste de hacer crecer y mantener el archivo de la red. Se calcula a partir del tiempo transcurrido entre tu primer bloque publicado y el bloque más nuevo de la red — cuanto más viva tu historia en el archivo, mayor será tu parte del coste de mantenimiento.", bankTaxesArchTaxNoteBold: "reducir el impuesto ARCH directamente", - bankTaxesArchTaxNoteIntro: "Tu impuesto ARCH se deduce del excedente que la red genera sobre tu UBI; el valor base del UBI es un mínimo inamovible. El impuesto nunca se deduce del importe fijo que corresponde a cada habitante como UBI. Los fondos deducidos alimentan el algoritmo de redistribución de la riqueza y se canalizan a otros habitantes a través de sus reclamaciones de UBI. Los habitantes más nuevos y los que se dediquen específicamente a tareas de archivo, replicación y mantenimiento probablemente tengan alguna tarea dedicada a mantener sano el archivo de la red en tu nombre. O pueden dedicarse a ", + bankTaxesArchTaxNoteIntro: "Tu impuesto ARCH se deduce del excedente que la red genera sobre tu RBU; el valor base de la RBU es un mínimo inamovible. El impuesto nunca se deduce del importe fijo que corresponde a cada habitante como RBU. Los fondos deducidos alimentan el algoritmo de redistribución de la riqueza y se canalizan a otras habitantes a través de sus reclamaciones de RBU. Los habitantes más nuevos y los que se dediquen específicamente a tareas de archivo, replicación y mantenimiento probablemente tengan alguna tarea dedicada a mantener sano el archivo de la red en tu nombre. O pueden dedicarse a ", bankTaxesArchTaxNoteOutro: " mediante acuerdos de poda, deduplicación de datos redundantes e infraestructura de mantenimiento compartida, y su contribución continua puede ser requerida por consenso de la red.", bankTaxesUserNoteChips: "El chip de impuesto ECO mostrado junto a cada elemento cambia de color (verde / amarillo / rojo) según el tamaño del elemento en relación con el bloque más grande visto en la red, suavizado por el número de habitantes activos — una red solar-punk más grande distribuye la carga y empuja más elementos a la banda verde.", bankTaxesUserNoteOtherParams: "El impuesto ECO se deriva actualmente de la huella de carbono de cada bloque, pero puede incorporar otros parámetros con el tiempo — energía gastada en replicación, almacenamiento redundante entre peers, ancho de banda de blobs, coste computacional de descifrado, huella de minería de transacciones asociadas, o cualquier otra carga medible que la red acuerde valorar.", gamesHallDate: "Fecha", gamesNewRecord: "¡Nuevo récord!", - indexingEditProfileLink: "Editar perfil", - indexingMessage: "El servidor SSB está indexando. Vuelve en un momento.", - indexingRefreshNote: "Esta página se recargará automáticamente.", - indexingTitle: "Indexando", + indexingRefreshNote: "Esta página se recarga cada 10 segundos.", + indexingMessage: "Oasis está sincronizando la red de nodos distribuida. Por favor, espera.", + indexingTitle: "Sincronizando", padsNoItems: "Aún no hay pads.", padVersionHistory: "Historial", + padAttributionView: "Por autora", padVersionView: "Ver versión", statsChat: "Chats", statsChatMessage: "Mensajes de chat", - statsGameScore: "Gaming", + statsGameScore: "Juegos", + statsWiki: "Páginas wiki", statsPad: "Pads", statsPadEntry: "Entradas de pad", typeGameScore: "PUNTUACIONES", @@ -3443,6 +4624,14 @@ module.exports = { settingsReplicationTitle: "Replicación", settingsReplicationDesc: "Configura el número de saltos que tu nodo sigue desde tu propio feed al replicar contenido.", settingsReplicationHopsLabel: "Saltos", + settingsBlobCacheTitle: "Caché de medios", + settingsBlobCacheDesc: "El contenido de otros habitantes se guarda en este dispositivo. Elige cuánto espacio puede ocupar.", + settingsBlobCacheUsage: "Uso actual", + settingsBlobCacheFiles: "ficheros", + settingsBlobCacheLimit: "Límite", + settingsBlobCacheUnlimited: "Sin límite", + settingsBlobCacheCleanNow: "Limpiar ahora", + settingsBlobCacheCleaned: "{n} ficheros eliminados, {size} liberados.", aiApproveTagsLabel: "Etiquetas (separadas por comas)", aiApproveTagsPlaceholder: "p.ej. oasis, gobernanza, ecología", aiApproveRatingLabel: "Valoración", @@ -3470,13 +4659,11 @@ module.exports = { larpInviteBannerHint: "Comparte este código con alguien que esté en ACADEMIA. Expira en 30 ciclos o tras un uso.", larpTestRankingTitle: "Puntuación por casa", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Código de invitación de casa", - invitesHouseJoinButton: "Unirse a la Casa", - ecoTaxLabel: "ECO Tax", + ecoTaxLabel: "Tasa ECO", bankTaxes: "Impuestos", - bankOverviewYourTaxes: "Tus impuestos", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesArchTaxTitle: "ARCH Tax", + bankOverviewYourTaxes: "Impuestos totales", + bankTaxesEcoTaxTitle: "Tasa ECO", + bankTaxesArchTaxTitle: "Tasa ARCH", bankTaxesUserAmount: "Tu ECO tax (precio a devolver)", bankTaxesArchTaxUserAmount: "Tu ARCH tax (precio a devolver)", bankTaxesArchTaxFirstBlock: "Edad de tu primer bloque", @@ -3500,30 +4687,30 @@ module.exports = { bankTaxesLookupPlaceholder: "ID del bloque (p. ej. %abc...=.sha256)", bankTaxesLookupButton: "Inspeccionar", bankTaxesLookupNotFound: "No se ha encontrado en tu feed local ningún bloque con ese ID.", - bankTaxesLookupAuthor: "Autor", + bankTaxesLookupAuthor: "Autora", bankTaxesLookupType: "Tipo", bankTaxesLookupSize: "Tamaño", bankTaxesLookupCarbon: "Huella de carbono", - bankTaxesLookupEcoin: "ECO Tax", + bankTaxesLookupEcoin: "Tasa ECO", bankExchangeEcoTaxAnnual: "ECOin Taxes (anual)", bankExchangeEcoTaxMonthly: "ECOin Taxes (mensual)", bankTaxesTypesLabel: "Selecciona qué impuestos quieres pagar", bankTaxesTypesApply: "Establecer mis impuestos", bankRulesPoolTitle: "Bolsa mensual", - bankRulesShareTitle: "Reparto por usuario", + bankRulesShareTitle: "Reparto por habitante", bankRulesKarmaTitle: "Karma (puntuación de implicación)", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesArchTaxTitle: "ARCH Tax", + bankRulesEcoTaxTitle: "Tasa ECO", + bankRulesArchTaxTitle: "Tasa ARCH", bankRulesChipTitle: "Banda de color del chip ECO Tax", bankRulesEpochKind: "Granularidad de epoch", bankRulesAlpha: "Alpha (cuota máx. del balance del pub por epoch)", bankRulesReserveMin: "Reserva mínima (se mantiene en el balance del pub)", bankRulesCapPerEpoch: "Tope por epoch (absoluto)", - bankRulesGraceDays: "Periodo de gracia del claim", + bankRulesGraceDays: "Periodo de gracia de la reclamación", bankRulesWMin: "Peso mínimo (w_min)", bankRulesWMax: "Peso máximo (w_max)", - bankRulesFloor: "Suelo por usuario (bruto)", - bankRulesCapUser: "Tope por usuario por epoch", + bankRulesFloor: "Suelo por habitante (bruto)", + bankRulesCapUser: "Tope por habitante por epoch", bankRulesEcoTaxRate: "Tasa", bankRulesArchTaxRate: "Tasa", bankRulesCarbonFactor: "Factor de carbono", @@ -3531,21 +4718,19 @@ module.exports = { blockchainBlockNotAvailable: "Este bloque no está disponible en tu feed local. Puede pertenecer a un peer del que aún no replicas.", blockchainBackToBlockexplorer: "Volver al blockexplorer", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", + audioTranscodeButton: "TRANSCODIFICAR", audioTranscodeDetailTitle: "Transcodificar", audioTranscodeDetailDescription: "Decodifica la carga útil incrustada y el mapa de composición original de la blockchain.", - audioTranscodeStegoOasisId: "By", audioTranscodeStegoTimestamp: "Generado el", - audioTranscodeStegoMessage: "TEXT", + audioTranscodeStegoMessage: "TEXTO", audioTranscodeStegoEmpty: "(ninguno)", audioTranscodeStegoUnknown: "—", audioTranscodeStegoNotFound: "No se ha podido decodificar ninguna carga esteganográfica en este audio.", audioTranscodeCompositionEmpty: "Este audio no incluye una composición de blockchain guardada.", audioBackToBcs: "Volver a BCS", melodyCompositionTitle: "Mapa de composición de la blockchain", - melodyFilterMine: "MINE", - melodyByLabel: "Por", - melodyAllEmpty: "Todavía no hay composiciones BCS de otros habitantes.", + melodyFilterMine: "MÍAS", + melodyAllEmpty: "Todavía no hay composiciones BCS de otras habitantes.", melodyNoteUnavailable: "el bloque no está en tu feed local", melodyUploadBcsTitle: "Publicar", melodyUploadBcsButton: "Publicar", @@ -3570,18 +4755,18 @@ module.exports = { larpBadgeYou: "Tú", larpBadgeRuling: "Rige", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Módulo para la capa de juego de rol en vivo de Oasis (las 9 Casas).", + modulesLarpDescription: "Módulo para la capa de juego de rol en vivo.", larpProfileQ1: "Cuando te enfrentas a un problema complejo, ¿qué haces primero?", - larpProfileQ1O1: "Hablar con otras personas para buscar consenso", + larpProfileQ1O1: "Hablar con otros habitantes para buscar consenso", larpProfileQ1O2: "Construir un prototipo para probarlo", larpProfileQ1O3: "Investigar la literatura", larpProfileQ1O4: "Sabotear el sistema que lo provoca", larpProfileQ2: "¿Qué da sentido a tu trabajo diario?", - larpProfileQ2O1: "Defender a las personas que quiero", + larpProfileQ2O1: "Defender a los habitantes que quiero", larpProfileQ2O2: "Crear algo tangible", larpProfileQ2O3: "Sanar o cuidar la vida", larpProfileQ2O4: "Trabajar con palabras e ideas", - larpProfileQ2O5: "Hacer reír a los demás", + larpProfileQ2O5: "Hacer reír a las demás", larpProfileQ3: "Cuando surge un conflicto en tu grupo, tú…", larpProfileQ3O1: "Lideras el diálogo", larpProfileQ3O2: "Tomas partido y te mantienes firme", @@ -3644,7 +4829,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Selecciona qué modo de navegación UX quieres para tu interfaz.", uxModeMenus: "Bloques", - uxModeAINav: "AI", + uxModeChats: "Conversaciones", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Etiquetas en tendencia", + feedActiveTitle: "Habitantes Activos", + cnBrandSub: "Libre · P2P · Federada", + cnSyncedPeers: "Nodos sincronizados", + cnLicense: "Licencia", + cnNotAccessible: "El contenido no está accesible en este momento.", + cnUntitled: "Sin título", + cnSearchPlaceholder: "Buscar…", + cnHubTitle: "HUB Clearnet", + cnHubDescription: "Contenido público compartido por las habitantes de esta red Oasis.", + cnHubEmpty: "Aún no se ha compartido contenido público en la Clearnet.", + cnPublicContent: "Contenido", + cnTribesContent: "Tribus", + cnNetworkContent: "Red", + cnCategoryEmpty: "No hay contenido en esta categoría.", + cnInhabitantEmpty: "Este habitante aún no ha publicado contenido en la Clearnet.", + cnPrevPage: "← Anterior", + cnNextPage: "Siguiente →", + cnPageOf: "Página {page} de {pages}", + cnMoreInfo: "Más información →", + cnOpenCourse: "Curso abierto", + cnAuction: "SUBASTA", + cnExchange: "INTERCAMBIO", + cnRssDescription: "publicados por las habitantes de este HUB", + cnKindAudio: "Audio", + cnKindBlog: "Blog", + cnKindBookmark: "Marcador", + cnKindDocument: "Documento", + cnKindEvent: "Evento", + cnKindFeed: "Feed", + cnKindImage: "Imagen", + cnKindJob: "Empleo", + cnKindMarket: "Mercado", + cnKindPodcast: "Podcast", + cnKindProject: "Proyecto", + cnKindCourse: "Curso", + cnKindShop: "Tienda", + cnKindTorrent: "Torrent", + cnKindFile: "Fichero", + cnKindVideo: "Vídeo", + cnKindWiki: "Wiki", + cnKindRoom: "Sala", + cnKindMap: "Mapa", + uxModeAINav: "IA", } }; diff --git a/src/client/assets/translations/oasis_eu.js b/src/client/assets/translations/oasis_eu.js index 30b7521..31e64c4 100644 --- a/src/client/assets/translations/oasis_eu.js +++ b/src/client/assets/translations/oasis_eu.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "Erosketak", shopMyOrdersDescription: "Zure erosketa-eskaerak.", shopMyOrdersEmpty: "Oraindik ez duzu erosketarik.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM Saltzailea", shopOrderSeller: "Saltzailea", shopOrderInvoice: "Faktura", shopOrderInvoiceConcept: "Faktura", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Onartu", shopOrderReject: "Baztertu", shopOrderMarkShipped: "Markatu bidalia", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM Eroslea", shopOrderContractConcept: "Dendako eskaera", shopOrdersPending: "Eskaera zain", all: "Guztiak", @@ -28,7 +28,7 @@ module.exports = { recent: "Azkenak", anonymous: "Anonimoa", unnamed: "Anonimoa", - authorLabel: "EGILEA", + authorLabel: "Egilea", delete: "Ezabatu", save: "Gorde", vote: "Bozkatu", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Eduki abisua", invalidPost: "Eduki baliogabea", invalidPostHint: "Mezu honek testu baliogabea/hutsa du.", - spreadBy: "Honek", spreadChron: "Hedapena", spreaded: "Hedatua", spreadContentUnavailable: "Edukia oraindik ez dago eskuragarri (erreplikazioa zain)", filteredByTag: "Etiketaz iragazia", filteredByTagTitle: "Etiketaz iragazia", feedPublishedSuccess: "Jarioa ondo argitaratu da!", + feedMastodonFailed: "Oasisen argitaratu da, baina ezin izan da Mastodonera bidali.", tribeFeedSent: "Mezua ondo bidali da!", tribeContentEncrypted: "Eduki zifratua", tribeTorrentsEmpty: "Oraindik ez dago torrentik.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Azken bisita", profileClearnetBookmarksLabel: "Laster-markak", invitesPort: "Ataka", - currentlyUnrecheable: "ERROREA!", - peerHostValidation: "IPv4 baliozkoa (adib. 192.168.1.100) edo ostalari-izena (adib. pub.example.com)", + peerHostValidation: "IPv4 baliozkoa (adib. 192.168.1.100) edo ostalari-izena (adib. pub.solarnethub.com)", peerPortValidation: "Ataka 1-65535", peerKeyValidation: "SSB ed25519 gako publikoa (@<44 karaktere base64>=.ed25519)", inboxJobUnsubscribedTitle: "Zure lan-eskaintzatik baja", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Zereginaren deskribapena", exportPasswordLabel: "Esportazio pasahitza", importPasswordPlaceholder: "Sartu pasahitza", - searchCasesPlaceholder: "Bilatu kasuak…", + searchCasesPlaceholder: "Bilatu hemen: Auzitegiak...", shopBuyDeliveryAddressPlaceholder: "Bidalketa-helbidea", shopBuyNotesPlaceholder: "Oharrak", bankTaxesUserNoteBold: "ECO zerga zuzenean murriztea", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Lagundu kasua", courtsSettlementAcceptBtn: "Onartu akordioa", courtsSupportCount: "Babesak", - courtsVerdictVoteTitle: "Bozkatu epaia", - courtsVerdictVoteLabel: "Epaiaren botoa", - courtsVerdictVoteAccept: "Onartu", - courtsVerdictVoteReject: "Baztertu", - courtsVerdictVoteSubmit: "Bidali botoa", reportsSetStatus: "Ezarri egoera", reportsStatusClosed: "ITXITA", statsAvgPerInhabitant: "Batez bestekoa biztanleko", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Abatarra", inhabitants: "Bizilagunak", - peersReplicatedFeeds: "Errepikatutako jarioak", + peersReplicatedFeeds: "Erreplikatuak", + peersTorColumn: "TOR", + peersTorYes: "Tor bidez irisgarria", + peersTorNo: "Ez da Tor bidez irisgarria", + peersOasisId: "Oasis ID", + peersOasisVersion: "Bertsioa", graphos: "Graphos", graphosDescription: "Sarearen mapa interaktiboa zure inguruan.", + graphosFederation: "Federazioa", + graphosFedPubs: "PUBak", + graphosFedUnreachable: "Iritsiezinak", + graphosFedLinks: "Elkarrekiko loturak", + graphosFedIsolated: "Isolatuak", + graphosFedNoData: "Daturik gabe", + graphosFedMutual: "Elkarrekikoa", + graphosFedOneWay: "Norabide bakarrean", + graphosFedTransport: "Sarea", + graphosFedWith: "Honekin federatua", + graphosZoom: "Handitu", + graphosFedInhabitants: "Biztanleak", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "Zure nodoak oraindik ez du PUBik ezagutzen.", graphosViewingGraphOf: "Ikusten", graphosBackToMine: "← Nire sarea", graphosShowing: "Erakusten", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "Ez duzu aipamenik jaso, oraindik.", - mentionsSearchPlaceholder: "Bilatu aipamenak...", + mentionsSearchPlaceholder: "Bilatu hemen: Aipamenak...", privateReminders: "OROIGARRIAK", + inboxFilterPms: "MPak", + inboxFilterNotifications: "Jakinarazpenak", + inboxMarkRead: "Irakurritako gisa markatu", + inboxMarkUnread: "Irakurri gabeko gisa markatu", + inboxUnreadChip: "Irakurri gabe", + inboxMarkAllRead: "Dena irakurritzat markatu", + inboxDeleteShown: "Dena ezabatu", + inboxArchive: "Artxibatu", + inboxUnarchive: "Desartxibatu", + inboxFilterArchived: "Artxibatuak", + inboxEarlierMessages: "aurreko mezu", + inboxSortThread: "Harien arabera", + inboxSortRecent: "Berrienak lehenik", + inboxAllBots: "Botak", + settingsInboxBotsTitle: "Inbox jakinarazpenak", + settingsInboxBotsDesc: "Aukeratu zein botek jakinaraz diezazuketen Inboxean. Markatu gabeko botak ezkutatu egiten dira eta haien abisuak ez dira zenbatzen.", inboxFiltersLabel: "Iragazkiak:", inboxToggleToMutuals: "Mutuora aldatu", inboxToggleToWhole: "Guztiora aldatu", @@ -214,8 +244,9 @@ module.exports = { searchFromLabel: "Hasiera", searchToLabel: "Bukaera", searchQueryLabel: "Bilaketa", - searchPerPageLabel: "Emaitzak orriko", + searchPerPageLabel: "Emaitzak", settings: "Ezarpenak", + settingsDescription: "Konfiguratu zure sarearen ingurunea.", continueReading: "Irakurtzen jarraitu", moreComments: "iruzkin gehiago", readThread: "irakurri hari osoa", @@ -236,7 +267,6 @@ module.exports = { profileClearnetHelp: 'Oasis kanpotik atzitu daitezkeen moduluak.', profileHubAll: 'Guztiak', footerLicenseLabel: 'Lizentzia', - profileSwitchToOasis: 'Itzuli Oasisera', profileVisibilityFediverse: "Multibertsoa", contributorsTitle: "Ekarpenak", colorLabel: 'Hautatu kolorea', @@ -246,6 +276,7 @@ module.exports = { modulesViewTitle: "Moduluak", modulesViewDescription: "Konfiguratu zure ingurunea moduluak gaitu edo desgaituz.", inbox: "Sarrera-ontzia", + inboxSearchPlaceholder: "Bilatu hemen: Sarrera-ontzia...", multiverse: "Multibertsoa", fediverse: "Multibertsoa", fediverseDescription: "Kudeatu zure beste multibertso kontuak, edukia bidaltzea eta jasotzea barne.", @@ -254,9 +285,17 @@ module.exports = { fediverseSettingsTitle: "Multibertsoa", fediverseInstanceLabel: "Helbidea", fediverseTokenLabel: "Sarbide token-a", - fediverseTokenHelp: "Ezarri zure sarbide token-a. Zure Mastodon kontua Oasis barrutik kudeatzeko erabiliko da.", fediverseConnect: "Konektatu", fediverseConnectedAs: "Honela konektatuta", + nextcloudServerLabel: "Zerbitzaria", + nextcloudUserLabel: "Erabiltzailea", + nextcloudFolderLabel: "Karpeta", + nextcloudUploadButton: "Igo", + nextcloudFileLabel: "Aukeratu fitxategi bat", + fileExternalLink: "Kanpoko esteka", + nextcloudErrConnect: "Ezin izan da Nextcloud-era iritsi datu hauekin.", + nextcloudErrUpload: "Nextcloud-era igotzeak huts egin du.", + nextcloudErrShare: "Nextcloud-ek ez du esteka publikorik eman fitxategiarentzat.", fediverseDisconnect: "Deskonektatu", fediverseEmptyLink: "ezarpenetatik", fediverseComposePlaceholder: "Zer duzu buruan?", @@ -285,6 +324,76 @@ module.exports = { fediverseErrPost: "Ezin izan da argitaratu.", fediverseErrMedia: "Ezin izan da fitxategia igo.", fediverseErrEmpty: "Idatzi zerbait edo erantsi fitxategi bat.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Telefono zenbakia", + telegramSendCode: "Bidali kodea", + telegramCodeHelp: "Sartu Telegramek bidali dizun kodea (aplikazioa edo SMS).", + telegramCodeLabel: "Kodea", + telegramPasswordHelp: "Kontu honek bi urratseko egiaztapena du. Sartu pasahitza.", + telegramPasswordLabel: "Pasahitza", + telegramVerify: "Egiaztatu", + telegramCancel: "Utzi", + telegramChats: "Txatak", + telegramUnread: "Irakurri gabe", + telegramOpen: "Ireki", + telegramSend: "Bidali", + telegramYou: "Zu", + telegramNoChats: "Oraindik ez dago txatik.", + telegramNoMessages: "Oraindik ez dago mezurik.", + telegramComposePlaceholder: "Idatzi mezu bat…", + telegramChannel: "Kanala", + telegramGroup: "Taldea", + telegramPrivate: "Pribatua", + telegramErrConnect: "Ezin izan da Telegramekin konektatu.", + telegramErrTimeout: "Telegramek gehiegi tardatu du erantzuten.", + telegramErrAuth: "Telegram saioa baliogabea edo iraungita. Konektatu berriro.", + telegramErrFetch: "Ezin izan dira txatak kargatu.", + telegramErrSend: "Ezin izan da mezua bidali.", + telegramErrMissing: "API ID, API hash eta telefono zenbakia behar dira.", + telegramErrCode: "Kode okerra. Saiatu berriro.", + telegramErrCodeExpired: "Kodea iraungi da. Hasi berriro.", + telegramErrPassword: "Pasahitz okerra.", + telegramErrPhone: "Telefono zenbaki baliogabea.", + telegramErrApi: "API ID edo API hash baliogabea.", + telegramErrFlood: "Saiakera gehiegi. Itxaron pixka bat eta saiatu berriro.", + telegramErrNoLogin: "Ez dago saio-hasierarik abian. Hasi berriro.", + peertubeUsernameLabel: "Erabiltzailea", + peertubePasswordLabel: "Pasahitza", + peertubeSubscriptions: "Harpidetzak", + peertubeMyVideos: "Nire bideoak", + peertubeVideos: "Bideoak", + peertubeFollowers: "Jarraitzaileak", + peertubeViews: "ikustaldi", + peertubeLikes: "atsegin", + peertubeQuota: "Kuota", + peertubeUpload: "Bideoa igo", + peertubeUploadHelp: "Aukeratu bideo fitxategi bat. Zure kanalera igotzen da eta PeerTubek gero prozesatzen du.", + peertubeTitleLabel: "Izenburua", + peertubeDescriptionLabel: "Deskribapena", + peertubePrivacyLabel: "Pribatutasuna", + peertubePrivacyPublic: "Publikoa", + peertubePrivacyUnlisted: "Zerrendatu gabea", + peertubePrivacyPrivate: "Pribatua", + peertubeNoVideos: "Oraindik ez dago bideorik.", + peertubeComments: "Iruzkinak", + peertubeComment: "Iruzkindu", + peertubeCommentPlaceholder: "Idatzi iruzkin bat…", + peertubeNoComments: "Oraindik ez dago iruzkinik.", + peertubeOpen: "PeerTuben ireki", + peertubeWatch: "Ikusi", + peertubeChannel: "Kanala", + peertubeNoStream: "Bideo hau oraindik ezin da hemen erreproduzitu. Ireki PeerTuben.", + peertubeUploaded: "Bideoa igo da. PeerTube prozesatzen ari da.", + peertubeErrConnect: "Ezin izan da PeerTuberekin kontaktatu.", + peertubeErrAuth: "Erabiltzaile edo pasahitz okerra, edo saioa iraungi da. Konektatu berriro.", + peertubeErrInstance: "Instantziaren URL baliogabea.", + peertubeErrMissing: "Instantzia, erabiltzailea eta pasahitza behar dira.", + peertubeErrFetch: "Ezin izan dira bideoak kargatu.", + peertubeErrUpload: "Ezin izan da bideoa igo.", + peertubeErrTitle: "Izenburua behar da.", + peertubeErrComment: "Ezin izan da iruzkina argitaratu.", + peertubeErrNoFile: "Aukeratu bideo fitxategi bat.", bookmarksLabel: "Markagailuak", videosLabel: "Bideoak", torrentsLabel: "Torrentak", @@ -307,7 +416,6 @@ module.exports = { comment: "Iruzkindu", subtopic: "Azpi-gaia", json: "JSON", - createdBy: "Sortzailea", unfollow: "Laguntza eten", follow: "Lagundu", block: "Blokeatu", @@ -323,12 +431,15 @@ module.exports = { relationshipNone: "Ez duzu laguntzen", relationshipBlockingPost: "Blokeatutako bidalketa", viewLikes: "Ikusi zabalpenak", + viewSupporters: "Ikusi laguntzaileak", spreadedDescription: "Bizilagunak zabaldutako bidalketen zerrenda.", preview: "Aurrebista", publish: "Idatzi", contentWarningPlaceholder: "Gehitu gaia bidalketari (hautazkoa)", - publishWarningPlaceholder: "Gorputza 7000 karakteretara mugatua.", + publishWarningPlaceholder: "Idatzi zure sarreraren gorputza.", publishTooLong: "Zure argitalpena luzeegia da. Laburtu ezazu, mesedez.", + keepTextRetry: "Berriro bidali", + dateInPastError: "Data ezin da gaur baino lehenagokoa izan", publishCustomDescription: [ "GOGORATU: Blockchain teknologia dela eta, bidalketa argitaratu ezkero, ezin izango da aldatu ezta ezabatu.", ], @@ -358,9 +469,8 @@ module.exports = { mentionsMatching: "Bat datozen aipamenak", mentionsName: "Izena", mentionsRelationship: "Erlazioa", - updateit: "LORTU EGUNERAKETAK!", updateBannerText: "Oasis-en bertsio berri bat eskuragarri dago.", - updateBannerAction: "Eguneratu orain →", + updateBannerAction: "Eguneratu orain!", info: "Infoa", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -368,16 +478,14 @@ module.exports = { timeAgo: "dela", sendTime: "honi buruz ", theme: "Gaia", - legacy: "Gakoak", - legacyTitle: "Gakoak", - legacyDescription: "Kudeatu zure sekretua (gako pribatua) bizkor eta segurtasunez.", - legacyExportButton: "Esportatu", - legacyImportButton: "Inportatu", + backupKeysExportButton: "Esportatu", + backupKeysImportButton: "Inportatu", ssbLogStream: "Blockchain", ssbLogStreamDescription: "Konfiguratu blockchain fluxuetarako mezu-muga.", + logWindowChip: "Azken {n} blockchain stream", saveSettings: "Ezarpenak gorde", exportDescription: "Ezarri pasahitza (gutxienez 32 karaktere) zure gakoa zifratzeko", - exportDataTitle: "Babeskopia", + exportDataTitle: "Bat-bateko babeskopia", exportDataDescription: "Deskargatu zure datuak (gako sekretua izan ezik!)", exportDataButton: "Deskargatu datu-basea", importDescription: "Inportatu zifratutako sekretua (gako pribatua) zure abatarra gaitzeko", @@ -386,38 +494,33 @@ module.exports = { fileInfo: "Zure gako sekretu zifratua zure gailura deskargatuko da (izena: oasis.enc)", developer: "Garapena", devTitle: "Garapena", - welcomeBannerText: "Ongi etorri OASISera. Jarraitu urrats azkar hauek zure avatarra konfiguratzeko.", + welcomeBannerText: "Zure avatarra oraindik ez dago konfiguratuta.", aiSuggestionBanner: "Iradokizuna:", aiSuggestionsEnable: "AAren iradokizunak", - welcomeBannerAction: "Hasi!", + welcomeBannerAction: "Hasi", welcomeTitle: "Ongi etorri", + welcomeStepIdentity: "Nortasuna", + welcomeStepType: "Mota", + welcomeStepUse: "Erabilera", + welcomeStepLook: "Itxura", + welcomeStepCommunity: "Komunitatea", + welcomeStepIntro: "Ongi etorria", + welcomeHintIdentity: "Zure izena eta irudia.", + welcomeHintType: "Nola irekitzen den Oasis zuretzat.", + welcomeHintUse: "Zein modulu geratzen diren aktibo.", + welcomeHintLook: "Oasisek janzten duen gaia.", + welcomeHintCommunity: "Sartu PUB batean beste biztanle batzuk ezagutzeko.", + welcomeHintIntro: "Bidali lehen agur bat komunitateari.", + welcomeStepThemeAction: "Erabili gai hau", + welcomeJoinPubs: "Sartu PUBetan", + welcomeNoFederation: "Federatu gabe", welcomeStepGreetingAction: "Bidali", - welcomeJoinPubButton: "Batu pubera", - welcomeStepLarpAction: "Batu \"Akademiara\"", - welcomeStepLarpText: "Batu gure zuzeneko rol jokora.", - welcomeStepLarpTitle: "Batu L.A.R.P.era", - welcomeStepGreetingTitle: "Bidali \"Kaixo mundua!\" bat", - welcomeStepGreetingText: "Bidali mezu bat beste biztanleek aurki zaitzaten.", - welcomeJoinPub: "Batu:", welcomeDescription: "Hasi aurretik komeni diren gauza batzuk.", - welcomeProgress: "Osatuta", - welcomeStepDone: "eginda", - welcomeStepPending: "egiteke", welcomeSkip: "Orain ez", - welcomeStepLanguageTitle: "Aukeratu zure hizkuntza", - welcomeStepLanguageText: "Oasisek hainbat hizkuntza hitz egiten ditu. Nahi duzunean alda dezakezu.", - welcomeStepProfileTitle: "Editatu zure profila", - welcomeStepProfileText: "Aukeratu izena, gehitu deskribapena eta jarri irudi bat beste biztanleek ezagut zaitzaten.", welcomeStepProfileAction: "Gorde profila", - welcomeStepFederationTitle: "Batu sare nagusira", - welcomeStepFederationText: "Konektatu gure pubera beste biztanleak ezagutu eta erreplikatzen hasteko.", - welcomeStepFederationAction: "Joan gonbidapenetara", - welcomeStepBackupTitle: "Egin zure IDaren babeskopia", - welcomeStepBackupText: "Zure identitatea gailu honetako gako-fitxategi bat da.", - welcomeStepBackupWarning: "GALTZEN BADUZU, INORK EZIN DIZU BERRESKURATU.", - welcomeStepBackupAction: "Babeskopia!", + welcomeBootstrapRunning: "Pub-aren argazkia kargatzen. Amaitzean pub-era batuko da automatikoki; bitartean Oasis erabiltzen jarrai dezakezu.", welcomeCompleteTitle: "Dena prest.", - welcomeCompleteText: "Zure nodoa prest dago. Hemendik aurrera sarea jarraitzen duzun jendearekin hazten da.", + welcomeCompleteText: "Zure nodoa prest dago. Hemendik aurrera sarea babesten dituzun bizilagunekin hazten da.", welcomeFindPeople: "Bilatu biztanleak", devFilterFiles: "FITXATEGIAK", devFilterSearch: "BILATU", @@ -427,7 +530,7 @@ module.exports = { devFilterTests: "TESTAK", devVersion: "Bertsioa", devNodeVersion: "Node", - devStatsTests: "Test multzoak", + devStatsTests: "Testak", devParentFolder: "Goiko karpeta", devOpenFolder: "ireki", devDescription: "Arakatu nodo honetan exekutatzen ari den kodea.", @@ -471,6 +574,27 @@ module.exports = { "Beste hizkuntza bat erabili nahi baduzu, hautatu hemen.", setLanguage: "Ezarri hizkuntza", peerConnectionsIntro: "Kudeatu beste parekoekin dituzun konexio guztiak.", + peersConnectFailed: "Ezin izan da konektatu: {reason}.", + peerStateLabel: "Egoera", + peerAddressLabel: "Helbidea", + peerLastChangeLabel: "Azken aldaketa", + peerStateConnected: "Konektatuta", + peerStateConnecting: "Konektatzen", + peerStateStaged: "Erabilgarri", + peerErrSilent: "Jarduerarik gabe", + peerErrTimeout: "Ez du erantzuten", + peerErrRefused: "Konexioa baztertzen du", + peerErrNotFound: "Helbide ezezaguna", + peerErrUnreachable: "Sarea eskuraezin", + peerErrKeys: "Gako desberdinak erabiltzen", + peerErrIdentity: "Ez du zure konexioa onartzen", + peerErrTor: "TOR soilik", + peerErrDropped: "Konexioa etenda", + peerErrPaused: "Sarea pausan", + peerErrOther: "Konexio-errorea", + peerOutdated: "Zaharkituta", + peerVersionUnannounced: "Ez du bere bertsioa iragartzen: Oasis zahar bat", + peersTorMissing: "PUB honek TOR bidezko konexioak soilik onartzen ditu.", peerConnectionsTitle: "Konexioak", online: "Linean", offline: "Lineaz kanpo", @@ -489,22 +613,25 @@ module.exports = { noConnections: "Konektatutako parekorik ez.", noDiscovered: "Ez duzu nodorik aurkitu.", noUnknownPeers: "Ez dago kide ezezagunik adiskidetza-grafoan.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Oraindik ez dago nodorik erregistratuta.", + peerConnect: "Konektatu", + peerDisconnect: "Deskonektatu", peerRefresh: "Berritu", peerPruneIdle: "Inaktiboak kendu", + peersPause: "Pausatu", + peersResume: "Jarraitu", + peersPausedNote: "Pausan: Oasis ez da inorekin konektatzen eta inor ezin da harekin konektatu.", + peersPauseHint: "Konexio guztiak gelditzen ditu: Oasis ez da beste nodoetara konektatzen eta ez ditu onartzen berriro hasi arte.", peerExport: "Esportatu", peerImport: "Inportatu", peerImportTitle: "Peer-zerrenda inportatu", peerImportPlaceholder: "Itsatsi multiserver-helbide bat lerro bakoitzeko, edo igo .txt fitxategi bat…", connectionActionIntro: "", - startNetworking: "Abiarazi sarea", - stopNetworking: "Gelditu sarea", - restartNetworking: "Berrabiarazi sarea", - sync: "Sinkronizatu sarea", indexes: "Indizeak", + indexesRebuilt: "Indizeak berreraiki dira", + indexesRebuildFailed: "Ezin izan dira indizeak berreraiki", + indexesFiles: "Indize fitxategiak", indexesDescription: "Indizeak birsortzea segurua da eta hutsegite mota batzuk konpon ditzake.", homePageTitle: "Hasierako orria", @@ -514,44 +641,33 @@ module.exports = { invitesDescription: "Kudeatu eta erabili gonbidapen kodeak zure sarean.", invitesPubsHint: "Itsatsi pub baten gonbidapen-kodea harekin federatzeko eta bere biztanleak errepikatzen hasteko.", invitesFederationsHint: "Federatuta zauden pubak: eguneratu, kendu iritsezinak edo esportatu zerrenda.", - invitesHousesHint: "Sartu etxe-kode bat LARPeko etxe batean sartzeko eta bere ekonomian parte hartzeko.", - invitesTribesHint: "Sartu tribu-kode bat kide egiteko eta bere eduki pribatura iristeko.", - invitesChatsHint: "Sartu txat-kode bat elkarrizketara eta bere harietara batzeko.", - invitesPadsHint: "Sartu pad-kode bat besteekin batera dokumentu partekatu batean idazteko.", - invitesCalendarsHint: "Sartu egutegi-kode bat datak eta oroigarriak partekatzeko.", - invitesEventsHint: "Sartu ekitaldi-kode bat ekitaldi pribatu batera batzeko.", - invitesForumsHint: "Sartu foro-kode bat foro pribatu batean irakurri eta erantzuteko.", - invitesMapsHint: "Sartu mapa-kode bat mapa partekatu batean lekuak ikusi eta gehitzeko.", - invitesShopsHint: "Sartu denda-kode bat denda batera eta bere katalogora batzeko.", - invitesInhabitantsHint: "Sartu biztanle-kode bat elkar jarraitzeko eta edukia trukatzeko.", invitesTribesTitle: "Tribuak", - invitesTribeInviteCodePlaceholder: "Sartu tribuaren gonbidapen kodea", - invitesTribeJoinButton: "Sartu Tribuan", invitesChatsTitle: "Txatak", - invitesChatInviteCodePlaceholder: "Sartu txataren gonbidapen kodea", - invitesChatJoinButton: "Sartu txatean", invitesPadsTitle: "Padak", - invitesPadInviteCodePlaceholder: "Sartu padaren gonbidapen kodea", - invitesPadJoinButton: "Sartu padean", invitesCalendarsTitle: "Egutegia", - invitesCalendarInviteCodePlaceholder: "Sartu egutegiaren gonbidapen kodea", - invitesCalendarJoinButton: "Sartu egutegian", invitesEventsTitle: "Ekitaldiak", - invitesEventInviteCodePlaceholder: "Sartu ekitaldiaren gonbidapen kodea", - invitesEventJoinButton: "Sartu ekitaldian", invitesForumsTitle: "Foroak", - invitesForumInviteCodePlaceholder: "Sartu foroaren gonbidapen kodea", - invitesForumJoinButton: "Sartu foroan", invitesMapsTitle: "Mapak", - invitesMapInviteCodePlaceholder: "Sartu maparen gonbidapen kodea", - invitesMapJoinButton: "Sartu mapan", invitesShopsTitle: "Dendak", - invitesShopInviteCodePlaceholder: "Sartu dendaren gonbidapen-kodea", - invitesShopJoinButton: "Bat egin dendarekin", invitesPubsTitle: "PUBak", + invitesSuggestedPubs: "Iradokitako PUBak", + invitesFilterOthers: "Besteak", + invitesJoinTitle: "Gonbidapenak", + invitesJoinHint: "Itsatsi edozein gonbidapen-kode, edozertarako dela ere, edo biztanle baten Oasis IDa.", + invitesJoinPlaceholder: "Gonbidapen-kodea edo Oasis IDa", + invitesJoinButton: "Sartu", + invitesUsedTitle: "Azken gonbidapenak", + invitesUsedEmpty: "Oraindik ez duzu gonbidapenik erabili.", + invitesUsedType: "Mota", + invitesUsedName: "Izena", + invitesUsedDate: "Data", invitesPubInviteCodePlaceholder: "Sartu PUB-aren gonbidapen kodea", invitesAcceptInvite: "Sartu PUBean", + invitesPubUses: "Erabilitako gonbidapenak", + invitesPubExhausted: "PUB honek ez du gonbidapen erabilgarririk.", invitesAlreadyFederated: "Dagoeneko pub honekin federatuta zaude.", + invitesJoinPubMode: "Oasis hau PUB gisa dabil, irakurtzeko moduan soilik. Beste PUB batekin batzeko, erabili terminalean: ./oasis.sh join ", + invitesJoinLocalOnly: "Gonbidapenak Oasis dabilen gailutik bakarrik onar daitezke, edo abiaraztean erakusten duen administrazio-estekarekin.", invitesFederationsTitle: "Federazioak", invitesAcceptedInvites: "Federatuak", invitesUnfollow: "Utzi jarraitzea", @@ -559,16 +675,12 @@ module.exports = { invitesUnfollowedInvites: "Ez federatuak", invitesNoUnfollowed: "Ez dago pub ez-federaturik.", invitesNoFederatedPubs: "Ez dago pub federaturik.", - invitesUnreachablePubs: "Iristezinak", - invitesNoUnreachablePubs: "Ez dago pub iristezinik.", invitesPubsRefresh: "Berritu", - invitesPubsClearUnreachable: "Kendu iristezinak", + invitesPubsClearUnreachable: "Garbitu", invitesPubsExport: "Esportatu", invitesPubsImport: "Inportatu", invitesPubsImportTitle: "Inportatu pubak", invitesPubsImportPlaceholder: "Itsatsi multiserver-helbide bat edo gonbidapen-kode bat lerro bakoitzeko, edo igo .txt fitxategi bat…", - currentlyUnreachable: "ERROR!", - errorDetails: "Errore Xehetasunak", genericError: "Errore bat gertatu da.", panicMode: "Izu Modua!", encryptData: "Idatzi pasahitza (gutxienez 32 karaketere) zure blockchain-a zifratzeko.", @@ -595,7 +707,6 @@ module.exports = { votesOptionNoLabel:"Ez", votesOptionAbstentionLabel:"Abstentzioa", votesQuestionLabel:"Galdera", - votesCreatedByLabel:"Noiz", voteStatusOpen:"IREKITA", voteStatusClosed:"ITXITA", commentDescription: ({ parentUrl }) => [ @@ -612,22 +723,22 @@ module.exports = { searchSubmit: "Bilatu...", votesLabel: "GOBERNUA", pdfFallbackLabel: "PDF Dokumentua", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "DENDAK", + voteLabel: "BOTOAK", submit: "Bidali", editProfile: "Editatu Abatarra", oasisIdLabel: "OasisID", spreadContent: "Errepikatu", welcomePmSubject: "Hello World!", - welcomePmBody: "Ongi etorri Oasis-era — sare sozial libre, parekideen arteko, federatu eta zifratua, gonbidapen bidezko arkitektura banatu batekin.\n\nHasteko leku interesgarri batzuk:\n\n- /profile — Editatu zure avatarra, izena, deskribapena eta zein modulu agertuko diren zure alde publikoan.\n- /invites — Gehitu pub bat sarearen gainerakoarekin federatzeko.\n- /peers — Ikusi nor dagoen konektatuta, eskaneatu LAN-a berriz, esportatu edo inportatu peer-zerrendak.\n- /inhabitants — Aurkitu eta bisitatu beste pertsonen avatarrak.\n- /tribes — Sortu edo elkartu muturretik muturrera zifratutako talde pribatuetan.\n- /inbox — Irakurri eta bidali mezu pribatuak.\n- /activity — Ikusi azken jarduera, zurea eta zure sarearena.\n- /publish — Idatzi post bat edo partekatu irudia, audioa, bideoa edo dokumentua.\n- /search — Bilatu sarearen edukia motaren arabera.\n\nKonektatzeko leku interesgarri batzuk:\n\n- /multiverse — Kudeatu zure beste multibertso kontuak, edukia bidaltzea eta jasotzea barne.\n\nMezu hau zugandik zuregana modu pribatuan bidali da; horregatik ez zen konexiorik behar jasotzeko.", + welcomePmBody: "Ongi etorri Oasis-era — sare sozial libre, parekideen arteko, federatu eta zifratua, gonbidapen bidezko arkitektura banatu batekin.\n\nHasteko leku interesgarri batzuk:\n\n- [/avatar](/profile) — Editatu zure avatarra, izena, deskribapena eta zein modulu agertuko diren zure alde publikoan.\n- /settings — Doitu zure instantzia lokala: hizkuntza, gaia, moduluak, pribatutasuna eta sinkronizazioa.\n- /invites — Gehitu pub bat sarearen gainerakoarekin federatzeko.\n- /peers — Ikusi nor dagoen konektatuta, eskaneatu LAN-a berriz, esportatu edo inportatu peer-zerrendak.\n- /inhabitants — Aurkitu eta bisitatu beste bizilagunen avatarrak.\n- /tribes — Sortu edo elkartu muturretik muturrera zifratutako talde pribatuetan.\n- /search — Bilatu sarearen edukia motaren arabera.\n\nKonektatzeko leku interesgarri batzuk:\n\n- /multiverse — Kudeatu zure beste multibertso kontuak, edukia bidaltzea eta jasotzea barne.\n\nMezu hau zugandik zuregana modu pribatuan bidali da; horregatik ez zen konexiorik behar jasotzeko.", profileSensorsSectionTitle: "Sentsoreak", profileVisibilityActivity: "Jarduera-maila", profileDeviceLockedHint: "Sentsore hau beti dago aktibo: besteei zein gailutatik konektatzen zaren erakusten die eta ezin da desgaitu.", profileVisibilityKarma: "KARMA puntuazioa", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "ECOIN zorroa", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "OBU", + profileVisibilityWallet: "ECOin zorroa", + profileVisibilityEcoTax: "ECO tasa", + profileVisibilityLarpSign: "L.A.R.P. zigilua", profileVisibilityGpg: "GPG Gakoa", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Dendak", @@ -640,6 +751,10 @@ module.exports = { profileClearnetImagesLabel: "Irudiak", profileClearnetDocumentsLabel: "Dokumentuak", profileClearnetTorrentsLabel: "Torrentak", + profileClearnetPodcastsLabel: "Podcastak", + profileClearnetMarketLabel: "Merkatua", + profileClearnetFeedLabel: "Jarioa", + profileClearnetWikiLabel: "Wikiak", editProfileDescription: "", profileName: "Izena", @@ -665,7 +780,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Berr.", walletConfirm: "Berretsi", - walletDescription: "Kudeatu zure ondasun digitalak. Hala nola, ECOin-ak bidali eta jaso, zure balantzea ikusi eta zure transakzio historia kontsultatu.", + walletDescription: "Kudeatu zure ECOin zorroa.", walletDate: "Data", walletFee: "Tasa (Zenbat eta tasa altuagoa, orduan eta bizkorrago prozesatuko da trantsakzioa.)", walletFeeLine: ({ fee }) => `Tasa: ECO ${fee}`, @@ -673,6 +788,86 @@ module.exports = { walletReceive: "Jaso", walletReset: "Berrezarri", walletSend: "Bidali", + walletPayingTransfer: "Transferra ordaintzen", + walletCreateTransferLabel: "Kontratua sortu (transferentzia)", + walletTransferNotifiedNote: "Beste aldeari ordainketa honen berri eman zaio", + walletTransferCreatedNote: "Ordainketa honen transfer bat (ordainagiria) sortu da", + walletTransferNoPayeeNote: "Biztanle batek ere ez du helbide hau argitaratu, beraz ez da transferrik sortu", + walletTransferReceiptConcept: "Zorrotik egindako ordainketa", + walletPaymentPmText: "ordainketa bat bidali dizu", + walletPayeeLabel: "Hartzailea", + walletConceptLabel: "Kontzeptua", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Zure OBUa jaso duzu.", + bankingBotPaymentTitle: "Ordainketa bat jaso duzu.", + bankingBotUbiPaidText: "Zure Oinarrizko Errenta Unibertsala ordaindu da", + bankUbiPubThisNode: "Nodo honek OBUa ordaintzen du", + bankUbiPubConnected: "OBUko PUBa", + bankUbiPubNone: "Oraindik ez dago OBUa iragartzen duen PUBik zure sarean", + bankUbiAddressPublished: "ECOin helbidea argitaratuta", + bankUbiAddressMissing: "Argitaratu zure ECOin helbidea OBUa jasotzeko", + bankUbiClaimedThisMonth: "Hilabete honetan eskatuta", + bankUbiPubBalance: "PUBaren zorroa OBUrako erabilgarri", + bankNoPubAddress: "OBUko PUBak ez du oraindik ECOin helbiderik argitaratu.", + bankUbiTab: "OBU", + bankDonateEco: "EMAN!", + donateTooltip: "Eman ECOin", + bankNoUserAddress: "Biztanle honek ez du ECOin helbiderik partekatzen.", + bankUbiPubDefault: "Lehenetsia", + bankUbiPool: "Funtsa", + bankUbiLastSeen: "Azkenekoz ikusia", + bankUbiLastPayout: "Azken ekintza", + bankUbiPaidOut: "Banatua", + bankRangeToday: "Gaur", + bankRangeWeek: "Astea", + bankRangeMonth: "Hilabetea", + bankRangeYear: "Urtea", + bankRangeFiveYears: "5 urte", + bankRangeAll: "Guztia", + bankChartValueTitle: "ECOin balioaren bilakaera", + bankChartValueHours: "Denbora baliokidetza (h)", + bankChartWealthTitle: "ECOin aberastasunaren birbanaketa", + bankChartDistributed: "Banatutako OBUa (ECO)", + bankChartRebalanceTitle: "Aberastasunaren birbanaketa PUBen artean", + bankChartRebalanced: "PUBen artean mugitutako ECO", + bankChartTaxes: "Atxikitako zergak (ECO)", + bankChartNoDataRange: "Ez dago daturik tarte honetan.", + bankChartNoDataYet: "Oraindik ez dago daturik", + bankChartPerHour: "orduko", + bankChartPerDay: "eguneko", + bankChartPerMonth: "hileko", + bankTotalUbiDistributed: "Birbanatutako OBU guztira (sarea)", + bankTotalTaxesWithheld: "Atxikitako zerga guztira (sarea)", + bankInflationIssuance: "Jaulkipen-tasa (urtekoa, bloke-sariaren arabera)", + bankRulesEligibilityTitle: "Nork jaso dezake OBUa", + bankRulesEligibility1: "Profilean argitaratutako ECOin helbide bat (Profila → Sentsoreak → ECOin zorroa).", + bankRulesEligibility2: "30 egun baino gehiagoko feed bat sarean jarduerarekin: karma zerotik gora.", + bankRulesEligibility3: "Eskaera bat hilean (epoch), Banking-etik OBUa eskatu botoiarekin. Uneko hilabetea uka daiteke OBUari uko egin botoiarekin.", + bankRulesEligibility4: "1 ECO-ko oinarria beti ordaintzen da; zergek gainetik dagoen zatia bakarrik murrizten dute eta ez dira inoiz aparte kobratzen.", + bankRulesEligibility5: "PUBak hurrengo zikloan ordaintzen du eta berretsi dezakezun OBU transferentzia bat argitaratzen du.", + bankRulesEligibility6: "Helbidea falta bada, feeda gazteegia bada, hilabeteari uko egin badiozu edo PUBak fondorik ez badu, eskaera saltatu eta PUBean erregistratzen da.", + copyLinkTitle: "Kopiatu esteka", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "OBUari uko egin", + bankUbiRefusedThisMonth: "Hilabete honetako OBUari uko egin diozu.", + bankingBotUbiAvailableTitle: "Zure OBUa eska dezakezu.", + bankingBotUbiAvailableText: "Hilabete honetako zure OBUa eskuragarri dago", + bankingBotConfirmTitle: "Baieztatzeko zain dagoen smart-contract bat duzu", + bankingBotConfirmText: "Zure berrespenaren zain dauden smart-contract-ak", + bankChartZoomHint: "Klikatu handitzeko", + bankChartPoolTitle: "OBUaren funtsa (denboran)", + bankChartNetworkUbiTitle: "Birbanatutako OBU guztira (sarea)", + bankChartSupplyTitle: "ECOin zirkulazioko eskaintza (denboran)", + bankChartInflationTitle: "ECOin inflazioa (denboran)", + bankChartInflationSeries: "Urteko tasa (%)", + bankChartPool: "Funts osoa (ECO)", + bankChartKarmaTitle: "KARMA Scoring (denboran)", + bankChartFundsTitle: "Zure ECOin funtsak (denboran)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin konektatuta", + bankWalletNotConfigured: "ECOin deskonektatuta", + transfersPayWithWallet: "ORDAINDU!", walletStatus: "Egoera", walletDisconnected: [ "ECOin ", @@ -682,8 +877,11 @@ module.exports = { " edo konexioaren egoera.", ], walletSentToLine: ({ destination, amount }) => `${amount} ECO bidali dira ${destination} helbidera`, + walletNotConfigured: "Zure ECOin zorroa ez dago oraindik konfiguratuta.", walletSettingsDescription: "Integratu Oasis zure ECOin zorroarekin.", - walletSettingsDocLink: "ECOin instalazio gida", + walletSettingsMobileDescription: "Lotu Oasis gailu honetako ECOin Wallet aplikazioarekin.", + walletDownloadEcoin: "Deskargatu ECOin!", + walletRpcUrl: "RPC URLa", walletStatusMessages: { invalid_amount: "Zenbatekoa oker", invalid_dest: "Hartzailearen helbidea oker", @@ -699,6 +897,7 @@ module.exports = { walletUser: "Erabiltzaile izena", walletPass: "Pasahitza", walletConfiguration: "Ezarri zorroa", + walletDisconnectButton: "Zorroa deskonektatu", cipher: "Zifratzailea", cipherTitle: "Zifratzailea", cipherDescription: "Zifratu eta deszifratu testua simetrikoki (Pasahitza erabiliz).", @@ -732,17 +931,16 @@ module.exports = { bookmarkFilterRecent: "AZKENAK", bookmarkCreateButton: "Laster-marka sortu", bookmarkUpdateButton: "Eguneratu", - bookmarkDeleteButton: "Ezabatu", bookmarkAddFavoriteButton: "Gogokoetara gehitu", bookmarkRemoveFavoriteButton: "Gogokoetatik kendu", bookmarkUrlLabel: "Esteka", - bookmarkUrlPlaceholder: "https://adibidea.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Deskribapena", bookmarkDescriptionPlaceholder: "Aukerakoa", bookmarkTagsLabel: "Tagak", - bookmarkTagsPlaceholder: "Sartu tagak komaz banatuta", + bookmarkTagsPlaceholder: "Idatzi etiketak komaz bereizita", bookmarkLastVisitLabel: "Azken bisita", - bookmarkSearchPlaceholder: "Bilatu URLa, tagak, kategoria, egilea...", + bookmarkSearchPlaceholder: "Bilatu hemen: Laster-markak...", bookmarkSortRecent: "Berrienak", bookmarkSortOldest: "Zaharrenak", bookmarkSortTop: "Bozkatuenak", @@ -781,7 +979,7 @@ module.exports = { videoDescriptionPlaceholder: "Aukerakoa", videoNoFile: "Ez da bideo-fitxategirik eman", noVideos: "Ez dago bideorik erabilgarri.", - videoSearchPlaceholder: "Bilatu izenburua, etiketak, egilea...", + videoSearchPlaceholder: "Bilatu hemen: Bideoak...", videoSortRecent: "Berrienak", videoSortOldest: "Zaharrenak", videoSortTop: "Bozkatuenak", @@ -804,20 +1002,19 @@ module.exports = { documentFilterFavorites: "GOGOKOAK", documentCreateButton: "Dokumentua igo", documentUpdateButton: "Eguneratu", - documentDeleteButton: "Ezabatu", documentAddFavoriteButton: "Gehitu gogokoetara", documentRemoveFavoriteButton: "Gogokoetatik kendu", documentMessageAuthorButton: "MP", documentFileLabel: "Dokumentua igo (.pdf)", documentTagsLabel: "Etiketak", - documentTagsPlaceholder: "Sartu komaz bereizitako etiketak", + documentTagsPlaceholder: "Idatzi etiketak komaz bereizita", documentTitleLabel: "Izenburua", documentTitlePlaceholder: "Aukerakoa", documentDescriptionLabel: "Deskribapena", documentDescriptionPlaceholder: "Aukerakoa", documentNoFile: "Fitxategirik ez.", noDocuments: "Ez dago dokumenturik.", - documentSearchPlaceholder: "Bilatu izenburua, etiketak, deskribapena, egilea...", + documentSearchPlaceholder: "Bilatu hemen: Dokumentuak...", documentSortRecent: "Berrienak", documentSortOldest: "Zaharrenak", documentSortTop: "Bozkatuenak", @@ -838,19 +1035,19 @@ module.exports = { audioFilterTop: "TOP", audioCreateButton: "Audioa igo", audioUpdateButton: "Eguneratu", - audioDeleteButton: "Ezabatu", audioAddFavoriteButton: "Gehitu gogokoetara", audioRemoveFavoriteButton: "Kendu gogokoetatik", audioFileLabel: "Aukeratu audio-fitxategi bat (.mp3, .wav, .ogg)", audioTagsLabel: "Etiketak", audioTagsPlaceholder: "Idatzi etiketak komaz bereizita", + tagsPlaceholder: "Idatzi etiketak komaz bereizita", audioTitleLabel: "Izenburua", audioTitlePlaceholder: "Aukerakoa", audioDescriptionLabel: "Deskribapena", audioDescriptionPlaceholder: "Aukerakoa", audioNoFile: "Ez da audio-fitxategirik eman", noAudios: "Ez dago audiorik erabilgarri.", - audioSearchPlaceholder: "Bilatu izenburua, etiketak, egilea...", + audioSearchPlaceholder: "Bilatu hemen: Audioak...", audioSortRecent: "Berrienak", audioSortOldest: "Zaharrenak", audioSortTop: "Bozkatuenak", @@ -862,7 +1059,7 @@ module.exports = { audioFilterFavorites: "GOGOKOAK", favoritesTitle: "Gogokoak", favoritesDescription: "Zure gogoko guztiak leku berean.", - favoritesSearchPlaceholder: "Bilatu gogokoetan...", + favoritesSearchPlaceholder: "Bilatu hemen: Gogokoak...", favoritesFilterAll: "GUZTIAK", favoritesFilterRecent: "AZKENAK", favoritesFilterAudios: "AUDIOAK", @@ -874,13 +1071,12 @@ module.exports = { favoritesFilterChats: "TXATAK", favoritesFilterCalendars: "EGUTEGIAK", favoritesFilterVideos: "BIDEOAK", - favoritesRemoveButton: "Gogokoetatik kendu", favoritesNoItems: "Oraindik ez dago gogokorik.", yourContacts: "Zure Kontaktuak", allInhabitants: "Bizilagunak", allCVs: "CVak", discoverPeople: "Aurkitu bizilagunak zure sarean.", - searchInhabitantsPlaceholder: "IRAGAZI bizilagunak IZENAREN ARABERA …", + searchInhabitantsPlaceholder: "Bilatu hemen: Bizilagunak...", filterLocation: "IRAGAZI bizilagunak KOKAPENAREN ARABERA …", filterLanguage: "IRAGAZI bizilagunak BY HIZKUNTZAREN ARABERA …", filterSkills: "IRAGAZI bizilagunak BY TREBETASUNEN ARABERA …", @@ -904,7 +1100,6 @@ module.exports = { topSectionTitle: "TOP", blockedLabel: "Blokeatutako Erabiltzailea", inhabitantviewDetails: "Ikusi Xehetasunak", - cvVisitButton: "Bisitatu CVa", keepReading: "Irakurtzen jarraitu...", oasisId: "ID-a", noInhabitantsFound: "Bizilagunik ez, oraindik.", @@ -922,6 +1117,18 @@ module.exports = { parliamentFilterLeaders: "BURUAK", parliamentFilterRules: "ARAUAK", parliamentGovernmentCard: "Uneko gobernua", + parliamentSeatsGovernment: "Gobernua", + parliamentSeatsOpposition: "Oposizioa", + parliamentSeatsAbstention: "Abstentzioa", + parliamentSeatsQuorum: "Hauteskunde-quoruma", + parliamentSeatsPass: "Onarpen-atalasea", + parliamentSeatsByElection: "Hauteskundeen arabera", + parliamentSeatsByHouses: "LARP etxearen arabera", + parliamentSeatsSeats: "eserleku", + parliamentSeatsSeat: "eserleku", + parliamentSeatsLeader: "Buruzagia", + parliamentSeatsVotesFor: "botoak honi:", + parliamentSeatsNoHouse: "Etxerik gabe", parliamentActorInPowerInhabitant: 'BIZTANLEA AGINTEAN', parliamentActorInPowerTribe: 'TRIBUA AGINTEAN', parliamentHistoricalGovernmentsTitle: 'GOBERNUAK', @@ -949,7 +1156,7 @@ module.exports = { parliamentCurrentRevocationsTitle: 'Uneko Indargabetzeak', parliamentFutureRevocationsTitle: 'Etorkizuneko Indargabetzeak', parliamentPoliciesRevocated: 'HISTORIKOA', - parliamentPoliciesTitle: 'HISTORICAL', + parliamentPoliciesTitle: "HISTORIKOA", parliamentLawsTitle: 'ONARTUTAKO LEGEAK', parliamentRulesRevocations: 'Onartutako edozein lege indargabetu daiteke indarrean dagoen gobernu metodoa erabiliz.', parliamentNoStableGov: "Oraindik ez da gobernurik hautatu.", @@ -988,6 +1195,7 @@ module.exports = { parliamentTimeRemaining: "Geratzen den denbora", parliamentElectionsStatusTitle: "Hurrengo Gobernua", parliamentProposalDeadlineLabel: "Epemuga", + parliamentViewReport: "Txostena ikusi", parliamentProposalTimeLeft: "Geratzen den denbora", parliamentRulesTitle: "Legebiltzarrak nola funtzionatzen duen", parliamentRulesIntro: "Hauteskundeak 2 hilean behin ebazten dira; hautagaitzak etengabeak dira eta gobernua aukeratzean berrabiarazten dira.", @@ -1002,6 +1210,7 @@ module.exports = { parliamentRulesLaws: "Proposamenak bere atalasea gainditzen duenean, Lege bihurtzen da eta Legeak fitxan agertzen da indarrean sartzeko datarekin.", parliamentRulesHistorical: "Historia fitxan egondako gobernu ziklo bakoitza eta haren kudeaketari buruzko datuak ikus daitezke.", parliamentRulesLeaders: "Liderak fitxan, gobernatu duten (edo aurkeztu diren) biztanle/tribuen sailkapena ikus daiteke, eraginkortasunaren arabera ordenatuta.", + parliamentRulesHemicycle: "Hemizikloak eserleku bat ematen dio biztanle bakoitzari: gobernu-blokea (hautagaitza irabazlearen hautesleak eta buruzagia), oposizio-bloke bat zikloko beste hautagaitza bakoitzeko, eta abstentzioa. Marka etenek hauteskunde-quoruma (erroldaren % 25) eta indarrean dagoen metodoaren onarpen-atalasea erakusten dituzte. \"LARP etxearen arabera\" eserleku berak etxeka koloreztatzen ditu.", parliamentProposalVoteStatusLabel: "Bozketa egoera", parliamentProposalOnTrackYes: "Atalasea lortuta", parliamentProposalOnTrackNo: "Atalasearen azpitik", @@ -1017,6 +1226,7 @@ module.exports = { courtsCaseTitle: "Izenburua", courtsCaseRespondent: "Akusatua / Erantzulea", courtsCaseRespondentPh: "Oasis ID (@...) edo Tribu izena", + courtsCaseSubjectPlaceholder: "Gaia edo deskribapen laburra", courtsCaseMediatorsPh: "Oasis IDak, komaz bereizita", courtsCaseMethod: "Ebazpen metodoa", courtsCaseSubmit: "Kasu bidali", @@ -1077,6 +1287,21 @@ module.exports = { courtsMethodPOPULAR: "Herritarren bozketa", courtsMethodMEDIATION: "Bitartekaritza", courtsMethodKARMATOCRACY: "Karmatokrazia", + courtsCaseType: "Kasu mota", + courtsPresetMinorConflict: "Gatazka arina", + courtsPresetModerateConflict: "Gatazka ertaina", + courtsPresetSevereConflict: "Gatazka larria", + courtsPresetHarassment: "Jazarpena edo abusua", + courtsPresetContentModeration: "Edukien moderazioa", + courtsPresetBanRestriction: "Kanporaketa edo murrizketa", + courtsPresetGovernanceDispute: "Gobernantza-gatazka", + courtsStatusOPEN: "Irekita", + courtsStatusIN_PROGRESS: "Abian", + courtsStatusDECIDED: "Erabakita", + courtsStatusCLOSED: "Itxita", + courtsStatusSOLVED: "Konponduta", + courtsStatusUNSOLVED: "Konpondu gabe", + courtsStatusDISCARDED: "Baztertuta", courtsMethodJUDGES: "Epaile-mahaia", courtsMethodSINGLE_JUDGE: "Epaile bakarra", courtsMethodJURY: "Epaimahaia", @@ -1088,12 +1313,12 @@ module.exports = { courtsRulesTitle: "Nola funtzionatzen duten Auzitegiek", courtsRulesIntro: "Auzitegiak komunitateak kudeatutako prozesuak dira, gatazkak konpontzeko eta justizia zuzentzailea sustatzeko. Elkarrizketa, froga argiak eta neurri proportzionalak lehenesten dira.", courtsRulesLifecycle: "Prozesua: 1) Kasua ireki 2) Metodoa aukeratu 3) Frogak aurkeztu 4) Entzunaldia eta eztabaida 5) Epai eta neurriak 6) Betetze eta itxiera 7) Helegitea (badagokio).", - courtsRulesRoles: "Akusatzailea: kasua irekitzen du. Defentsa: akusatutako pertsona edo tribua. Metodoa: komunitateak aukeratutako mekanismoa, froga baloratu eta ebazpena emateko. Lekukoa: testigantza edo frogak ematen dituen pertsona. Bitartekariak: akusatzaileak eta/edo defentsak gonbidatutako pertsona neutralak, xehetasun guztietara sarbidea dutenak, gatazka deseskalatzen eta akordioak lankidetzan eraikitzen laguntzen dutenak.", + courtsRulesRoles: "Akusatzailea: kasua irekitzen du. Defentsa: akusatutako biztanlea edo tribua. Metodoa: komunitateak aukeratutako mekanismoa, froga baloratu eta ebazpena emateko. Lekukoa: testigantza edo frogak ematen dituen biztanlea. Bitartekariak: akusatzaileak eta/edo defentsak gonbidatutako biztanle neutralak, xehetasun guztietara sarbidea dutenak, gatazka deseskalatzen eta akordioak lankidetzan eraikitzen laguntzen dutenak.", courtsRulesEvidence: "Gehienez 1000 karaktereko azalpena. Erantsi irudi, audio, bideo eta PDF dokumentu garrantzitsu eta legezkoak. Ez partekatu datu pribatu sentikorrak baimenik gabe.", courtsRulesDeliberation: "Entzunaldiak publikoak edo pribatuak izan daitezke. Epaileek errespetua bermatzen dute, argipenak eskatzen dituzte eta material ez-legala edo esanguragabea baztertu dezakete.", courtsRulesVerdict: "Lehentasuna da konponketa: barkamenak, bitartekaritza-akordioak, edukiaren moderazioa, behin-behineko murrizketak edo beste neurri proportzional batzuk. Arrazoibidea erregistratuta geratu behar da.", courtsRulesAppeals: "Helegitea: onartzen da frogak berriak direnean edo akats prozesal argia dagoenean. Gehienez 7 eguneko epean aurkeztu behar da, bestela adierazi ezean.", - courtsRulesPrivacy: "Errespetatu pribatutasuna eta segurtasuna. Doxing-a, gorrotoa edo mehatxuak ezabatzen dira. Epaileek erregistroaren zatiak editatu edo ezkutatu ditzakete arriskuan dauden pertsonak babesteko.", + courtsRulesPrivacy: "Errespetatu pribatutasuna eta segurtasuna. Doxing-a, gorrotoa edo mehatxuak ezabatzen dira. Epaileek erregistroaren zatiak editatu edo ezkutatu ditzakete arriskuan dauden bizilagunak babesteko.", courtsRulesMisconduct: "Jazarpenak, manipulazioak edo faltsututako frogek berehalako ebazpen negatiboa ekar dezakete.", courtsRulesGlossary: "Kasua: gatazkaren erregistroa. Froga: aldarrikapenak babesten dituzten materialak. Epai/ebazpena: neurri edo konponketarekin datorren erabakia. Helegitea: ebazpena berrikusteko eskaera.", courtsFilterActions: "EKINTZAK", @@ -1109,12 +1334,16 @@ module.exports = { exploreTrending: "Aurkitu pil-pileko edukia zure sarean.", votesButton: "GOBERNUA", shopProductButton: "DENDA", + podcastButton: "PODCASTA", + podcastEpisodeButton: "ATALA", + campaignButton: "KANPAINA", + logisticsRouteButton: "IBILBIDEA", trendingTotalOpinions: "Iritzial Guztira", trendingNoContentMessage: "Oraindik ez dago joerarik.", trendingTotalCount: "Kopurua Guztira", tasksTitle: "Atazak", tasksDescription: "Aurkitu eta kudeatu atazak", - taskSearchPlaceholder: "Bilatu zereginak...", + taskSearchPlaceholder: "Bilatu hemen: Atazak...", taskTitleLabel: "Izenburua", taskDescriptionLabel: "Deskribapena", taskStartTimeLabel: "Hasiera Data", @@ -1133,7 +1362,7 @@ module.exports = { taskStatusOpen: "Irekita", taskStatusInProgress: "Ekinean", taskStatusClosed: "Itxita", - taskAssignedTo: "Honi esleituta", + taskAssignedTo: "Esleituta", taskAssignedChip: "Esleituta", taskUnassignedChip: "Esleitu gabe", taskAssignees: "Hauei esleituta", @@ -1143,6 +1372,7 @@ module.exports = { taskUpdateButton: "Eguneratu", taskDeleteButton: "Ezabatu", taskFilterAll: "GUZTIAK", + taskFilterRecent: "AZKENAK", taskFilterMine: "NEUREAK", taskFilterOpen: "IREKITA", taskFilterInProgress: "EKINEAN", @@ -1170,14 +1400,13 @@ module.exports = { eventUpcomingDates: "Hurrengo datak", eventsTitle: "Ekitaldiak", eventsDescription: "Aurkitu eta kudeatu ekitaldiak zure sarean.", - eventSearchPlaceholder: "Bilatu ekitaldiak...", + eventSearchPlaceholder: "Bilatu hemen: Ekitaldiak...", eventPrice: "Prezioa", eventStatus: "Egoera", eventAllSectionTitle: "Ekitaldiak", eventMineSectionTitle: "Zeure Ekitaldiak", eventCreateSectionTitle: "Sortu Ekitaldia", eventUpdateSectionTitle: "Eguneratu Ekitaldia", - eventDeleteButton: "Ezabatu", eventCreateButton: "Sortu Ekitaldia", eventAddToCalendar: "Egutegira gehitu", eventVisitCalendar: "Egutegia ikusi", @@ -1195,11 +1424,11 @@ module.exports = { viewPlace: "Ikusi Lekua", generatePdf: "Sortu PDFa", sharePm: "Partekatu PMz", - galleryImages: "Irudiak (geh. 50MB bakoitza)", + galleryImages: "Irudiak (geh. 75MB bakoitza)", galleryPhotos: "Irudiak", galleryAddPhoto: "Gehitu Irudia", galleryRemovePhoto: "Kendu", - galleryVideo: "Bideoa (geh. 50MB)", + galleryVideo: "Bideoa (geh. 75MB)", galleryAddVideo: "Gehitu Bideoa", galleryRemoveVideo: "Kendu bideoa", viewVotation: "Ikusi bozketa", @@ -1219,6 +1448,7 @@ module.exports = { eventDate: "Data", eventAttendButton: "Joan Ekitaldira", eventFilterAll: "GUZTIAK", + eventFilterRecent: "AZKENAK", eventFilterMine: "NEUREAK", eventFilterToday: "GAUR", eventFilterWeek: "ASTEON", @@ -1245,7 +1475,7 @@ module.exports = { tagsFilterTop: "GORENAK", tagsFilterCloud: "HODEIA", tagsNoItems: "Etiketarik ez.", - tagsSearchPlaceholder: "Bilatu etiketak...", + tagsSearchPlaceholder: "Bilatu hemen: Etiketak...", tagsTableHeaderTag: "ETIKETA/LOTURA:", tagsTableHeaderCount: "KONTAGAILUA:", transfersTitle: "Transferentziak", @@ -1253,8 +1483,11 @@ module.exports = { transfersFrom: "Nork", transfersTo: "Nori", transfersFilterAll: "GUZTIAK", + transfersFilterRecent: "AZKENAK", transfersFilterMine: "NEUREAK", - transfersFilterUBI: "RBU", + transfersFilterMinePending: "ZAIN", + transfersFilterMineConfirmed: "SINATUAK", + transfersFilterUBI: "OBU", transfersFilterMarket: "MERKATUA", transfersFilterTop: "GORENAK", transfersFilterPending: "ZAIN", @@ -1268,7 +1501,6 @@ module.exports = { transfersCategoryEconomic: "Ekonomikoa", transfersCategoryTime: "Denbora", transfersCategoryTrust: "Konfiantza", - transfersCategoryApply: "Aplikatu", transfersUnitEco: "ECO", transfersUnitHours: "h", transfersUnitTrust: "konfiantza", @@ -1290,7 +1522,7 @@ module.exports = { transfersConfirmButton: "Berretsi Transferentzia", transfersNoItems: "Transferentziarik ez.", transfersMineSectionTitle: "Zeure Transferentziak", - transfersUBISectionTitle: "RBU Transferentziak", + transfersUBISectionTitle: "OBU transferentziak", transfersTopSectionTitle: "Transferentzia Gorenak", transfersPendingSectionTitle: "Transferentziak Zain", transfersUnconfirmedSectionTitle: "Transferentziak Berretsi Gabe", @@ -1298,7 +1530,7 @@ module.exports = { transfersDiscardedSectionTitle: "Transferentziak Baztertuta", transfersCreateSectionTitle: "Sortu transferentzia", transfersAllSectionTitle: "Transferentziak", - transfersSearchPlaceholder: "Bilatu kontzeptua, etiketak, erabiltzaileak...", + transfersSearchPlaceholder: "Bilatu hemen: Transferentziak...", transfersMinAmountLabel: "Gutx. zenbatekoa", transfersMaxAmountLabel: "Geh. zenbatekoa", transfersSortRecent: "Berrienak", @@ -1320,6 +1552,7 @@ module.exports = { voteErrorTitle: "Egiaztatu inprimakia", voteErrorDeadlineMin: "Epemuga gutxienez {days} egunetara egon behar da.", voteErrorGeneric: "Ezin izan da bozketa gorde.", + voteLocked: "Bozketa honek botoak edo iritziak jaso ditu dagoeneko, edo Parlamentuarena edo Auzitegiena da: ezin da editatu ez ezabatu.", voteUpdateButton: "Eguneratu", voteDeleteButton: "Ezabatu", voteOptionYes: "BAI", @@ -1329,6 +1562,7 @@ module.exports = { voteFollowMajority: "GEHIENGOAREN HAUTUA", voteNotInterested: "INTERESIK EZ", voteFilterAll: "GUZTIAK", + voteFilterRecent: "AZKENAK", voteFilterMine: "NEUREAK", voteFilterOpen: "IREKITA", voteFilterClosed: "ITXITA", @@ -1344,7 +1578,6 @@ module.exports = { voteTags: "Etiketak", voteOpinions: "Iritziak", novotes: "Bozkatzeko proposamenik ez.", - voteBy: "Nork", voteCreatedAt: "Noiz", voteNoQuestion: "Galderarik ez", voteUnknownCreator: "Sortzaile Ezezaguna", @@ -1354,6 +1587,7 @@ module.exports = { voteCommentsForumButton: 'Eztabaida ireki', voteCommentsSectionTitle: 'Eztabaida irekia', voteNoCommentsYet: 'Oraindik ez dago iruzkinik. Izan zaitez lehen erantzuten.', + commentsGoToLast: "Joan azken iruzkinera", voteNewCommentPlaceholder: 'Idatzi hemen zure iruzkina…', voteNewCommentButton: 'Bidali iruzkina', voteNewCommentLabel: 'Gehitu iruzkina', @@ -1364,6 +1598,8 @@ module.exports = { cvNameLabel: "Izen Osoa", cvDescriptionLabel: "Laburpena", cvPhotoLabel: "Argazkia", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Bisitatu CVa", cvPersonalExperiencesLabel: "Esperientzia Pertsonalak", cvPersonalSkillsLabel: "Trebetasun Pertsonalak (komaz bereizita)", cvOasisExperiencesLabel: "Esperientzia Oasis-i Ekarpenak egiten", @@ -1393,9 +1629,9 @@ module.exports = { cvNoCV: "CV-rik ez.", blogSubject: "Gaia", blogMessage: "Mezua", - blogImage: "Multimedia igo (max: 50MB)", + blogImage: "Multimedia igo (max: 75MB)", blogMedia: "Eranskinak (8 irudi eta bideo bat gehienez)", - blogPublish: "Aurrebista", + blogPublish: "Argitaratu", pollsTitle: "Inkestak", dataTitle: "Bat etortzeak", dataDescription: "Arakatu eta ikusi zure sareko bat etortzeak.", @@ -1414,12 +1650,23 @@ module.exports = { dataKindTasks: "ZEREGINAK", dataKindReports: "TXOSTENAK", dataKindVotes: "BOZKETAK", - dataSearchPlaceholder: "Bilatu datu gurutzatuetan...", + dataSearchPlaceholder: "Bilatu hemen: Bat etortzeak...", dataCohesionTitle: "Kohesio Koefizientea (CC)", dataCcShort: "CC", dataCohesionHint: "Sareak argitaratutako guztiaren arteko batez besteko afinitatea.", dataAffinity: "Afinitatea", dataCommonTerms: "Gako-hitza", + dataReasonFollowing: "Bizilagun honi laguntzen diozu", + dataReasonTribe: "Tribu bat partekatzen du zurekin", + dataReasonPinned: "Finkatu duzun zerbaitekin bat dator", + dataReasonCv: "Zure CVko gaitasunekin bat dator", + dataReasonContent: "Argitaratzen duzunarekin lotuta", + dataReasonRelated: "Zure interesekin lotuta", + dataReasonAlike: "Zu bezalako bizilagun batek argitaratua", + dataReasonSupportsYou: "Laguntzen dizu", + dataReasonMutual: "Elkarren laguntza", + dataReasonRated: "Sareak ondo baloratua", + dataReasonNear: "Zure ondoan", dataStatPeople: "CVak", dataStatConnected: "Konektatuta", dataStatSkills: "Trebetasunak", @@ -1472,7 +1719,7 @@ module.exports = { pollStatusLabel: "Egoera", pollStatusOpen: "IREKIA", pollStatusClosed: "ITXIA", - pollSearchPlaceholder: "Bilatu inkestak...", + pollSearchPlaceholder: "Bilatu hemen: Inkestak...", pollsNoItems: "Ez da inkestarik aurkitu.", viewPoll: "Ikusi Inkesta", pollChatCreate: "Sortu Inkesta", @@ -1485,7 +1732,7 @@ module.exports = { blogFilterFavorites: "GOGOKOAK", blogFilterTop: "TOP", blogCreateButton: "Sortu Bloga", - blogSearchPlaceholder: "Bilatu blogak...", + blogSearchPlaceholder: "Bilatu hemen: Blogak...", blogComments: "Iruzkinak", blogAllowComments: "Baimendu iruzkinak", blogCommentsClosed: "Egileak blog honen iruzkinak itxi ditu.", @@ -1498,12 +1745,11 @@ module.exports = { forumCreateButton: "Sortu foroa", forumCreateSectionTitle: "Sortu foroa", forumDescription: "Hitz egin sareko beste erabiltzaileekin libreki.", - forumSearchPlaceholder: "Bilatu foroak...", + forumSearchPlaceholder: "Bilatu hemen: Foroak...", forumFilterAll: "GUZTIAK", forumFilterMine: "NIREAK", forumFilterRecent: "BERRIENAK", forumFilterTop: "GORENAK", - forumDeleteButton: "Ezabatu", forumParticipants: "parte-hartzaileak", forumMessages: "mezuak", forumMessageLabel: "Mezua", @@ -1530,7 +1776,6 @@ module.exports = { imageFilterGallery: "GALERIA", imageCreateButton: "Irudia igo", imageUpdateButton: "Eguneratu", - imageDeleteButton: "Ezabatu", imageAddFavoriteButton: "Gehitu gogokoetara", imageRemoveFavoriteButton: "Kendu gogokoetatik", imageFileLabel: "Aukeratu irudi-fitxategi bat (.jpeg, .jpg, .png, .gif)", @@ -1542,13 +1787,14 @@ module.exports = { imageDescriptionPlaceholder: "Aukerakoa", imageNoFile: "Ez da irudi-fitxategirik eman", noImages: "Ez dago irudirik erabilgarri.", - imageSearchPlaceholder: "Bilatu izenburua, etiketak, deskribapena, egilea...", + imageSearchPlaceholder: "Bilatu hemen: Irudiak...", imageSortRecent: "Berrienak", imageSortOldest: "Zaharrenak", imageSortTop: "Bozkatuenak", imageSearchButton: "Bilatu", imageUpdatedAt: "Eguneratua", imageNoMatch: "Ez dago zure bilaketarekin bat datorren irudirik.", + feedSearchPlaceholder: "Bilatu hemen: Jarioa...", feedTitle: "Jarioa", feedDetailTitle: "Feed", feedOpenDiscussion: "Eztabaida ireki", @@ -1563,11 +1809,11 @@ module.exports = { noFeedsFound: "Ez da jariorik aurkitu.", createdAtLabel: "Noiz", FeedshareYourOpinions: "Aurkitu eta partekatu testu laburrak zure sarean.", - refeedButton: "Realimentatu", + refeedButton: "Birbidali", alreadyRefeeded: "Berjaiotu duzu jada.", activityTitle: "Jarduera", yourActivity: "Zure jarduera", - globalActivity: "Jarduera globala", + globalActivity: "Jarduera", activityList: "Jarduera", activityDesc: "Ikusi zure sareko azken jarduera.", allButton: "GUZTIA", @@ -1579,9 +1825,10 @@ module.exports = { concept: "Kontzeptua", description: "Deskribapena", meme: "Meme", - typeRecent: "AZKENA", + typeRecent: "AZKENAK", typeTop: "TOP", - typePost: "BLOGAK", + typePost: "BLOGA", + activityUpdateLabel: "EGUNERAKETA", typeTribe: "TRIBUAK", typeLarp: "L.A.R.P.", typeInhabitants: "BIZTANLEAK", @@ -1606,10 +1853,10 @@ module.exports = { typeFeed: "FEEDAK", typeContact: "KONTAKTUA", typePub: "PUB", - typeTombstone: "TOMBSTONE", + typeTombstone: "EZABATUA", typeBanking: "BANKUA", typeBankWallet: "BANKUA/ZORROA", - typeBankClaim: "BANKUA/UBI", + typeBankClaim: "BANKUA/OBU", typeKarmaScore: "KARMA", typeParliament: "PARLAMENTUA", typeSpread: "ERREPLIKAK", @@ -1644,7 +1891,7 @@ module.exports = { location: "Kokalekua", contentWarning: "Gaia", bankWalletConnected: "ECOin zorroa", - bankUbiReceived: "Jasotako UBI", + bankUbiReceived: "Jasotako OBU", bankTx: "Tx", bankEpochShort: "Epoka", viewDetails: "Xehetasunak ikusi", @@ -1678,7 +1925,7 @@ module.exports = { createdAt: "Sortze-data", reportsTitle: "Txostenak", reportsDescription: "Kudeatu eta jarraitu zure sareko txostenak.", - reportsSearchPlaceholder: "Bilatu txostenak...", + reportsSearchPlaceholder: "Bilatu hemen: Txostenak...", reportsFilterAll: "GUZTIAK", reportsFilterRecent: "BERRIAK", reportsFilterTop: "TOP", @@ -1726,7 +1973,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Txostenak Berrikuspenean", reportsResolvedSectionTitle: "Zuzendutako Txostenak", reportsInvalidSectionTitle: "Txosten Okerrak", - reportsTemplateSectionTitle: 'Txosten-eredua', reportsBugTemplateTitle: 'Erreprodukzio xehetasunak (Bug-ak)', reportsFeatureTemplateTitle: 'Xehetasunak (Ezaugarriak)', reportsAbuseTemplateTitle: 'Xehetasunak (Abusua)', @@ -1785,29 +2031,36 @@ module.exports = { tribeGallerySectionTitle: "Tribuen Galeria", tribeRecentSectionTitle: "Tribu Berriak", tribeTopSectionTitle: "Tribu Gorenak", - tribeviewTribeButton: "Tribua Bisitatu", - tribeviewSubTribeButton: "Azpi-Tribua Bisitatu", tribeRootLabel: "ERROA", tribeDescription: "Aurkitu edo sortu tribuak zure sarean.", tribeMainTribeLabel: "TRIBU NAGUSIA", tribeCreateButton: "Sortu Tribua", tribeUpdateButton: "Egutneratu", tribeDeleteButton: "Ezabatu", - tribeImageLabel: "Multimedia igo (max: 50MB)", + tribeImageLabel: "Multimedia igo (max: 75MB)", tribeTitleLabel: "Izenburua", - searchTribesPlaceholder: "IRAGAZI tribuak IZENAREN ARABERA …", + searchTribesPlaceholder: "Bilatu hemen: Tribuak...", tribeTitlePlaceholder: "Tribuaren izena", tribeDescriptionLabel: "Deskribapena", tribeDescriptionPlaceholder: "Deskribatu tribua", tribeLocationLabel: "Kokapena", tribeLocationPlaceholder: "Non kokatzen da tribua?", tribeTagsLabel: "Etiketak", - tribeTagsPlaceholder: "Sartu etiketak, erabili komak bereizteko.", + tribeTagsPlaceholder: "Idatzi etiketak komaz bereizita", tribeInviteMode: "Gonbidapen modua", tribeModeLabel: "Gonbidapen MODUA", tribeIsAnonymousLabel: "Publikoa?", tribeMembersCount: "Partaideak", tribeEnterInvite: "SARTU GONBIDAPENA", + subscriptionTitle: "Harpidetzak", + subscriptionOn: "Harpidetuta", + subscriptionOff: "Harpidetu gabe", + subscriptionSubscribe: "Harpidetu", + subscriptionUnsubscribe: "Harpidetza utzi", + subscriptionSubscribers: "Harpidedunak", + pmListLabel: "Nori bidali", + pmListNone: "— Zerrendarik ez —", + pmListNoSubscribers: "Harpidedunik ez", tribeLeaveButton: "ATERA", tribePublic: "PULIKOA", tribePrivate: "PRIBATUA", @@ -1834,22 +2087,22 @@ module.exports = { tribeSectionTasks: "ATAZAK", tribeSectionFeed: "JARIOA", tribeSectionForum: "FOROA", - tribeSectionMedia: "Media", + tribeSectionMedia: "Multimedia", tribeSectionImages: "IRUDIAK", tribeSectionAudios: "AUDIOAK", tribeSectionVideos: "BIDEOAK", tribeSectionDocuments: "DOKUMENTUAK", tribeSectionBookmarks: "LASTER-MARKAK", - tribeSectionMaps: "MAPS", - tribeSectionPads: "PADS", - tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionMaps: "MAPAK", + tribeSectionPads: "PADAK", + tribeSectionChats: "TXATAK", + tribeSectionCalendars: "EGUTEGIAK", + tribePadCreate: "Sortu pad bat", + tribeChatCreate: "Sortu txat bat", + tribeCalendarCreate: "Sortu egutegi bat", + tribePadsEmpty: "Oraindik ez dago padik.", + tribeChatsEmpty: "Oraindik ez dago txatik.", + tribeCalendarsEmpty: "Oraindik ez dago egutegirik.", tribeInhabitantsEmpty: "Tribu honetan biztanlerik ez, oraindik.", tribeEventCreate: "Sortu Ekitaldia", tribeEventsEmpty: "Ekitaldirik ez, oraindik.", @@ -1904,9 +2157,14 @@ module.exports = { tribeMediaTypeDocument: "Dokumentua", tribeMediaTypeBookmark: "Laster-marka", tribeContentDelete: "EZABATU", + tribeReachTribe: "TRIBUA", + tribeReachNotice: "OASISek biztanle guztiei erakusten die eta CLEARNETek web irekian ere bai. Irekitzeak kopia irakurgarri bat uzten du tributik kanpo: TRIBUAra itzultzeak ezkutatu egiten du, baina dagoeneko partekatutako kopiak ezin dira ezabatu.", + tribeReachPrivate: "Tribu pribatu baten edukia tribuaren barruan bakarrik gera daiteke.", + tribeReachOpenedBy: "Ireki duena", + tribeReachChange: "Non argitaratzen den", tribeInviteCodeText: "Gonbidapen kodea: ", oasisVersionLabel: "Oasis Bertsioa", - tribeInviteCodeHint: "Partekatu kode hau gonbidatu nahi duzun pertsonarekin. /invites → Tribes bidez sar daiteke.", + tribeInviteCodeHint: "Partekatu kode hau gonbidatu nahi duzun pertsonarekin. /invites bidez sar daiteke.", tribeStatusOpen: "IREKITA", tribeStatusClosed: "ITXITA", tribeStatusInProgress: "ABIAN", @@ -1924,7 +2182,7 @@ module.exports = { tribeSubTribes: "AZPI-TRIBUAK", tribeSubTribesCreate: "Azpi-Tribua Sortu", tribeSubTribesStrictDenied: "Tribuaren modu zorrotzak ez dizu azpi-tribu berriak sortzen uzten. Jarri harremanetan administratzailearekin.", - tribeActivityRefeed: "REFEED", + tribeActivityRefeed: "BERJARIOA", tribeSectionActivity: "JARDUERA", tribeSectionTrending: "JOERAK", tribeSectionTags: "ETIKETAK", @@ -1946,7 +2204,7 @@ module.exports = { tribeSearchMinChars: "Idatzi gutxienez 2 karaktere bilatzeko.", agendaTitle: "Agenda", agendaDescription: "Eskeituta dauzkazun elementu guztiak aurki ditzakezu hemen.", - agendaSearchPlaceholder: "Bilatu agendan...", + agendaSearchPlaceholder: "Bilatu hemen: Agenda...", agendaFilterAll: "GUZTIAK", agendaFilterToday: "GAUR", agendaFilterUpcoming: "DATOZENAK", @@ -1968,6 +2226,7 @@ module.exports = { agendaNoItems: "Esleipenik ez.", agendaDiscardButton: "Baztertu", agendaRestoreButton: "Berrezarri", + agendaRemoveButton: "Kendu", agendaMembersCount: "Partaideak", agendaLocationLabel: "Kokapena", agendaYes: "BAI", @@ -2033,8 +2292,8 @@ module.exports = { pmComposeTitle: "Bidali mezu bat", pmRecipients: "Hartzaileak", pmRecipientsHint: "Idatzi Oasis IDak, komaz bereizita", - pmLimitsHint: "Mezu bakoitzeko 7 hartzaile gehienez.", - pmTextPlaceholder: "Gorputza 7000 karakteretara mugatua.", + pmLimitsHint: "Mezu bakoitzeko 7 bizilagun gehienez.", + pmTextPlaceholder: "Idatzi zure mezua.", pmSubject: "Gaia", pmSubjectHint: "Idatzi mezuaren gaia", pmText: "Mezua", @@ -2056,8 +2315,8 @@ module.exports = { pmCrypterCharsLabel: "karaktere", pmInvalidRecipients: "Oasis ID baliogabea (@…=.ed25519 itxurakoa izan behar du).", pmEncryptionLabel: "Zifratzea", - private: "Pribatuak", - privateDescription: "Zure mezu zifratuak.", + private: "Sarrera-ontzia", + privateDescription: "Kudeatu zure mezu pribatuak.", privateInbox: "SARRERA-ONTZIA", privateSent: "BIDALITAKOAK", privateDelete: "Ezabatu", @@ -2066,6 +2325,7 @@ module.exports = { pmReply: "Erantzun", pmReplies: "erantzunak", inReplyTo: "HONI ERANTZUNEZ", + forumInThread: "HARIAN", pmPreview: "Aurrebista", pmPreviewTitle: "Mezuaren aurrebista", performed: "→", @@ -2115,22 +2375,21 @@ module.exports = { blockchainViewBlockexplorer: "Ikusi Blockexplorer-en", blockchainBack: 'Itzuli blokearen azterkira', blockchainContentDeleted: "Edukia ezabatu egin da", - banking: 'Banking', - bankingTitle: 'Banking', - bankingDescription: "ECOin ekonomia: saldoa, oinarrizko errenta, zergak eta industria balioa.", + banking: "Bankua", + bankingTitle: "Bankua", + bankingDescription: "ECOin ekonomia kolektiboa.", bankOverview: 'Laburpena', bankEpochs: 'Epeak', bankRules: 'Arauak', pending: 'Zain', closed: 'Itxita', - bankViewTx: 'Tx ikusi', bankClaimNow: 'Esleitu', - bankClaimUBI: 'RBU eskatu!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: "OBU eskatu", + bankClaimAndPay: "Eskatu eta ordaindu", + bankClaimedPending: "Eskaera zain...", + bankStatusClaimed: "Eskatuta", + bankStatusExpired: "Iraungita", + bankPubOnly: "PUBentzako soilik den eragiketa", bankEpoch: 'Epea', bankPool: 'Funtsa (epe honetan)', bankWeightsSum: 'Pisuen batura', @@ -2149,8 +2408,8 @@ module.exports = { bankViewEpoch: 'Epea ikusi', bankYourIndustryBalance: "Zure industria zatia", bankYourUbiMonth: "Zure OBU (hilabete honetan)", - bankYourFundsMonth: "Zure funtsak (hilabete honetan)", - bankIndustryBalance: "Industria ekoizpena (estimatua)", + bankYourFundsMonth: "Funtsak guztira", + bankIndustryBalance: "Industria ekoizpena", bankUserBalance: 'Zure saldoa', bankAddresses: 'Helbideak', bankNoAddresses: 'Ez da helbiderik aurkitu.', @@ -2158,6 +2417,10 @@ module.exports = { bankAddress: 'Helbidea', bankAddAddressTitle: 'ECOIN helbidea gehitu', bankAddAddressUser: 'Oasis ID', + bankAddAddressLabel: "Etiketa (aukerakoa)", + bankAddAddressLabelPlaceholder: "Boben zorroa, Aliceren denda…", + bankAddAddressUserOptional: "Oasis ID (aukerakoa)", + bankAddressUnnamed: "Izenik gabeko helbidea", bankAddAddressAddress: 'ECOIN helbidea', bankAddAddressSave: 'Gorde', bankAddressAdded: 'Helbidea gehituta', @@ -2175,21 +2438,21 @@ module.exports = { bankAddressDeleteConfirm: 'Helbide hau ezabatu?', search: 'Bilatu!', bankLocal: 'Lokala', - bankFromOasis: 'Oasis', + bankFromOasis: "Sarea", bankingUserEngagementScore: "KARMA puntuazioa", pubIdLabel: "PUB ID", bankUbiAvailableNo: "FUNTSIK EZ!", bankUbiAvailableOk: "ESKURAGARRI!", - bankUbiAvailability: "OBU (sarea)", + bankUbiAvailability: "OBU", bankAlreadyClaimedThisMonth: "Hilabete honetan jada aldarrikatu da", - bankUbiThisMonth: "UBI (hilabete honetan)", - bankUbiLastClaimed: "UBI (azken aldarrikapena)", + bankUbiThisMonth: "OBU (hilabete honetan)", + bankUbiLastClaimed: "OBU (azken aldarrikapena)", bankUbiNeverClaimed: "Inoiz aldarrikatu ez", - bankUbiTotalClaimed: "UBI (guztira aldarrikatua)", + bankUbiTotalClaimed: "OBU (guztira aldarrikatua)", bankUbiPub: "PUB", bankUbiInhabitant: "BIZTANLEA", bankUbiClaimedAmount: "ALDARRIKATUA (ECO)", - typeBankUbiResult: "BANKUA - UBI", + typeBankUbiResult: "BANKUA - OBU", bankNoPubConfigured: "PUBik ez dago konfiguratuta. Ezarri zure PUB IDa Ezarpenetan.", shopsTitle: "Dendak", shopDescription: "Sareko dendak aurkitu eta kudeatu.", @@ -2210,6 +2473,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Garraioan zifratua", shopAddFavorite: "Gogoko gehitu", shopRemoveFavorite: "Gogoko kendu", shopOpen: "IREKIA", @@ -2217,11 +2481,18 @@ module.exports = { shopMakePrivate: "PRIBATU BIHURTU (E2E)", shopMakePublic: "PUBLIKO BIHURTU", shopProducts: "Produktuak", + shopFilterAll: "GUZTIAK", + shopFilterRecent: "AZKENAK", + shopFilterMine: "NIREAK", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUKTUAK", + shopFilterPrices: "PREZIOAK", + shopFilterFavorites: "GOGOKOAK", shopProductAdd: "Produktua gehitu", shopProductTitle: "Produktua", shopProductUpdate: "Produktua eguneratu", shopProductPrice: "Prezioa (ECO)", - shopProductStock: "Stock", + shopProductStock: "Izakinak", shopProductUntitled: "Izengabeko produktua", shopUntitled: "Izengabeko denda", shopNoItems: "Ez da dendarik aurkitu.", @@ -2244,7 +2515,7 @@ module.exports = { marketVisitItem: "ITEMA BISITATU", shopStatus: "EGOERA", shopCreatedAt: "SORTUA", - shopSearchPlaceholder: "Dendak bilatu...", + shopSearchPlaceholder: "Bilatu hemen: Dendak...", shopUrl: "URL", shopLocation: "Kokapena", shopTags: "Etiketak", @@ -2254,36 +2525,39 @@ module.exports = { shopTitlePlaceholder: "Zure dendaren izena", shopDescriptionPlaceholder: "Zure dendaren deskribapen zehatza", shopLocationPlaceholder: "Hiria, Herrialdea", - shopTagsPlaceholder: "tag1, tag2, tag3", + shopTagsPlaceholder: "Idatzi etiketak komaz bereizita", mapTitlePlaceholder: "Maparen titulua", shopProductFeatured: "Produktu Nabarmendua", - shopSendToMarket: "Send to Market", + shopSendToMarket: "Bidali merkatura", typeShop: "DENDAK", typeShopProduct: "DENDA PRODUKTUA", bankExchange: 'Trukea', bankExchangeCurrentValue: 'ECOin Balioa (1h)', - bankTotalSupply: 'ECOin Hornidura Guztizkoa', + bankTotalSupply: "ECOin hornidura guztizkoa", + bankPubsSupply: "PUBen poltsako ECOin hornidura", + bankHoldingSupply: "UBItik kanpoko ECOin hornidura", bankHoursOfWork: 'lan orduak', bankUnitMs: "ms", bankUnitSeconds: "segundo", bankUnitMinutes: "minutu", bankUnitDays: "egun", bankExchangeNoData: 'Ez dago daturik eskuragarri', - bankInflation: 'ECOin Inflazioa (anual)', - bankInflationMonthly: 'ECOin Inflazioa (mensual)', + bankInflation: "ECOin Inflazioa (urtekoa)", + bankInflationMonthly: "ECOin Inflazioa (hilekoa)", bankExchangeChartTitle: 'ECOin balioaren bilakaera denboran zehar', bankExchangeChartValue: 'Balioa (ECO/h)', bankExchangeChartSupply: 'Eskaintza', bankExchangeChartInflation: 'Inflazioa %', bankExchangeChartEmpty: 'Ez dago lagin nahikorik oraindik — itzuli geroago', - bankCurrentSupply: 'ECOin Oraingo Hornidura', + bankCurrentSupply: "Uneko ECOin hornidura", bankingSyncStatus: 'ECOin Egoera', bankingSyncStatusSynced: 'Sinkronizatuta', - bankingSyncStatusOutdated: 'Desegonetik', + bankingSyncStatusOutdated: "Sinkronizatu gabe", statsTitle: 'Estatistikak', statistics: "Estatistikak", statsDescription: "Ezagutu zure sareari buruzko estatistikak.", ALLButton: "GUZTIAK", + RECENTButton: "AZKENAK", MINEButton: "NIREAK", TOMBSTONEButton: "EZABAKETAK", statsCreatedAt: "Sortze data", @@ -2291,7 +2565,8 @@ module.exports = { statsYourOpinions: "Iritziak", statsNetworkContent: "Edukia", statsNetworkOpinions: "Iritziak", - statsEcoWalletLabel: "ECOIN zorroa", + statsEcoWalletLabel: "ECOin zorroa", + walletAddressCopyHint: "Sakatu hautatzeko, gero kopiatu", statsEcoWalletNotConfigured: "Konfiguratu gabe!", statsBookmark: "Laster-markak", statsEvent: "Ekitaldiak", @@ -2313,6 +2588,8 @@ module.exports = { statsTransfer: "Transferentziak", statsAiExchange: "IA", statsPUBs: 'PUBack', + statsSyncedPeers: "Parekoak", + statsTotalLabel: "Guztira", statsPost: "Mezuak", statsSize: "Guztira (tamaina)", statsBlockchainSize: "Blockchain (tamaina)", @@ -2364,9 +2641,15 @@ module.exports = { aiPrompt: "Eman erantzun informatibo eta zehatza.", aiConfiguration: "Konfiguratu prompt-a", aiPromptUsed: "Prompt-a", - aiClearHistory: "Txataren historia garbitu", - aiServerError: "Ezin izan da IAren erantzuna lortu. Saiatu berriro.", - aiInputPlaceholder: "What is Oasis?", + aiClearHistory: "Garbitu historia", + aiServerError: "Ezin dut ondo erantzun. Saiatu berriro.", + aiStatusLoading: "42 eredua kargatzen ari da. Galdetu berriro une batean.", + aiThinking: "Pentsatzen", + aiWaiting: "Zain", + aiStatusMissingModel: "IA eredua ez dago instalatuta: src/AI/oasis-42-1-chat.Q4_K_M.gguf falta da.", + aiExportFineTuning: "Fine-Tuning esportatu", + aiFactsTitle: "Zure sareko datuak", + aiInputPlaceholder: "Zer da Oasis?", typeAiExchange: "IA", aiApproveTrain: "Prestakuntza kolektibora gehitu", aiRejectTrain: "Ez entrenatu", @@ -2392,9 +2675,9 @@ module.exports = { marketFilterForSale: "SALGAI", marketFilterSold: "SALDUTA", marketFilterDiscarded: "BAZTERTUTA", - marketFilterRecent: "AZKENA", + marketFilterRecent: "AZKENAK", marketFilterMyBids: "PUJAK", - marketCreateButton: "Artikulua sortu", + marketCreateButton: "Saldu zerbait", marketItemType: "Mota", marketItemTitle: "Izenburua", marketItemAvailable: "Epemuga", @@ -2405,7 +2688,7 @@ module.exports = { marketItemCondition: "Egoera (kalitatea)", marketItemPrice: "Prezioa", marketItemTags: "Etiketak", - marketItemTagsPlaceholder: "Sartu etiketak komaz bereizita", + marketItemTagsPlaceholder: "Idatzi etiketak komaz bereizita", marketItemDeadline: "Epemuga", marketItemIncludesShipping: "Bidalketa barne?", marketItemHighestBid: "Puja altuena", @@ -2421,8 +2704,8 @@ module.exports = { marketItemSeller: "Saltzailea", marketNoItems: "Oraindik ez dago artikulurik eskuragarri.", marketYourBid: "Zure puja", - marketCreateFormImageLabel: "Multimedia igo (max: 50MB)", - marketSearchPlaceholder: "Izenburuan edo etiketetan bilatu", + marketCreateFormImageLabel: "Multimedia igo (max: 75MB)", + marketSearchPlaceholder: "Bilatu hemen: Merkatua...", marketMinPriceLabel: "Gutxieneko prezioa", marketMaxPriceLabel: "Gehieneko prezioa", marketSortRecent: "Berrienak", @@ -2488,8 +2771,7 @@ module.exports = { housingSetClosed: "Ez eskuragarri jarri", housingCreateButton: "Sortu lekua", housingUpdateButton: "Eguneratu", - housingDeleteButton: "Ezabatu", - housingSearchPlaceholder: "Bilatu lekuak...", + housingSearchPlaceholder: "Bilatu hemen: Etxebizitza...", housingSearchButton: "Bilatu", housingMinPrice: "Gutxieneko prezioa", housingMaxPrice: "Gehieneko prezioa", @@ -2556,7 +2838,7 @@ module.exports = { jobLocationPresencial: "Aurrez aurrekoa", jobLocationRemote: "Urrunekoa", jobVacantsPlaceholder: "Postu kopurua", - jobImage: "Multimedia igo (max: 50MB)", + jobImage: "Multimedia igo (max: 75MB)", jobTasks: "Eginkizunak", jobType: "Lanen Motak", jobTime: "Lanen Denbora", @@ -2567,12 +2849,11 @@ module.exports = { jobAuthor: "Egilea", jobTimePartial: "Partziala", jobTimeComplete: "Osotua", - jobsDeleteButton: "EZABATU", jobsUpdateButton: "EGUNERATU", jobsFilterApplied: "ESKATUTA", jobsAppliedTitle: "Nire eskaerak", jobsAppliedBadge: "Izena emanda", - jobsSearchPlaceholder: "Bilatu izenburua, etiketak, deskribapena...", + jobsSearchPlaceholder: "Bilatu hemen: Lanak...", jobsMinSalaryLabel: "Gutx. soldata", jobsMaxSalaryLabel: "Geh. soldata", jobsSortRecent: "Berrienak", @@ -2584,15 +2865,15 @@ module.exports = { jobSetOpen: "Ireki gisa markatu", jobsTagsLabel: "Etiketak", noJobsMatch: "Ez dago bilaketarekin bat datorren lan-eskaintzarik.", - industrySearchPlaceholder: "Bilatu fabrikak…", + industrySearchPlaceholder: "Bilatu hemen: Industria...", industryAllSectors: "Sektore guztiak", industryVisitButton: "Bisitatu industria", industryAdvanceTo: "Ezarri", industryAmount: "Kopurua", industryApproveBuild: "Onartu", - industryBlueprint: "Blueprint", + industryBlueprint: "Planoa", industryBlueprints: "Blueprint-ak", - industryBuild: "Build", + industryBuild: "Ekoizpena", industryBuildNotes: "Oharrak", industryBuildStart: "Hasiera data", industryVoteUpdateButton: "Bozkatu eguneraketa", @@ -2658,7 +2939,6 @@ module.exports = { industryBuildStatus_FAILED: "HUTS EGINDA", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Lanpostu bat zure curriculumarekin bat dator", jobsBotMatchIntro: "Lanpostu bat zure curriculumarekin bat dator.", jobsBotMatchJob: "Lanpostua", @@ -2685,7 +2965,7 @@ module.exports = { industryRulesIntro: "Industry-k biztanleei ekoizpen bideak modu kolektiboan izatea ahalbidetzen die: Fabrika bat sareko lantegia da, inork pribatuki ez duena.", industryRulesSteward: "Sortzailea arduraduna da — zaindaria, ez jabea: ezin du Fabrika bakarrik saldu, pribatizatu edo desegin.", industryRulesMembership: "Kidetza politikak nola sartu erabakitzen du: Irekia (berehala), Botoz (kideen botoak onartuta) edo Gonbidapenez soilik (kide batek lehenik gonbidatu behar zaitu).", - industryRulesQuorum: "Quoruma erabaki batek balio izateko bozkatu behar duten gutxieneko kide kopurua da, Fabrika txiki bat pertsona bakar batek erabaki ez dezan.", + industryRulesQuorum: "Quoruma erabaki batek balio izateko bozkatu behar duten gutxieneko kide kopurua da, Fabrika txiki bat biztanle bakar batek erabaki ez dezan.", industryRulesMajority: "Gehiengoa onartzeko behar den boto frakzioa da (0,5 = erdia, 1 = aho batez); erabaki bat onartzen da BAI botoek gutxienez max(quoruma, kideak × gehiengoa) lortzen dutenean.", industryRulesDecisions: "Kideek bozkatzen dute berriak onartzeko, build-ak onartzeko eta Fabrika desegiteko; arduradunak ezin ditu erabaki kolektibo hauek baztertu.", industryRulesBlueprints: "Blueprint-ak errezeta libreak dira (COPYLEFT) — sarrerak (materialak, lan orduak, trebetasunak) eta irteera (ondasun fisiko edo digital bat) — eta edonork forka ditzake.", @@ -2704,13 +2984,14 @@ module.exports = { industryPauseVotes: "Egoera botoak", industryTitle: "Industria", industryDescription: "Ekoizpen bideak modu kolektiboan kudeatzeko modulua.", - typeIndustryBuild: "BUILD", - typeIndustryBlueprint: "BLUEPRINT", + typeIndustryBuild: "EKOIZPENA", + typeIndustryBlueprint: "PLANOA", typeIndustryAllocation: "BANAKETA", typeIndustry: "INDUSTRIA", modulesIndustryLabel: "Industria", modulesIndustryDescription: "Ekoizpen bideak modu kolektiboan kudeatzeko modulua.", industryFilterAll: "GUZTIAK", + industryFilterRecent: "AZKENAK", industryFilterMember: "KIDEA", industryFilterBlueprints: "PLANOAK", industryFilterBuilds: "EKOIZPENAK", @@ -2719,6 +3000,7 @@ module.exports = { industryFilterPaused: "ETENDA", industryFilterDissolved: "DESEGINDA", industryAllTitle: "Industria", + industryRecentTitle: "Azken fabrikak", industryMemberTitle: "Kide naizen fabrikak", industryMineTitle: "Nire fabrikak", industryActiveTitle: "Martxan", @@ -2766,7 +3048,7 @@ module.exports = { industryPolicy_invite: "Gonbidapenez soilik", projectsTitle: "Proiektuak", projectsDescription: "Sortu, finantzatu eta jarraitu komunitateak gidatutako proiektuak zure sarean.", - projectSearchPlaceholder: "Bilatu proiektuak...", + projectSearchPlaceholder: "Bilatu hemen: Proiektuak...", projectCreateProject: "Proiektu Bat Sortu", projectCreateButton: "Proiektu Bat Sortu", projectUpdateButton: "EGUNERATU", @@ -2789,7 +3071,7 @@ module.exports = { projectRecentTitle: "Proiektu Azkenak", projectTopTitle: "Finantzatuenak", projectTitlePlaceholder: "Proiektuaren izena", - projectImage: "Multimedia igo (max: 50MB)", + projectImage: "Multimedia igo (max: 75MB)", projectDescription: "Deskribapena", projectDescriptionPlaceholder: "Kontatu istorioa eta helburuak...", projectGoal: "Helburu (ECO)", @@ -2861,15 +3143,13 @@ module.exports = { modulesModuleName: "Izena", modulesModuleDescription: "Deskribapena", modulesModuleStatus: "Egoera", - modulesTotalModulesLabel: "Kargatutako Moduluak", + modulesTotalModulesLabel: "Guztira", modulesEnabledModulesLabel: "Gaituta", modulesDisabledModulesLabel: "Desgaituta", modulesInvitesLabel: "Gonbidapenak", modulesInvitesDescription: "Gonbidapen-kodeak kudeatu eta aplikatzeko modulua.", modulesWalletLabel: "Zorroa", modulesWalletDescription: "Zure aktibo digitalak (ECOin) kudeatzeko modulua.", - modulesLegacyLabel: "Gakoak", - modulesLegacyDescription: "Zure sekretua (gako pribatua) modu azkar eta seguruan kudeatzeko modulua.", modulesCipherLabel: "Zifratzailea", modulesCipherDescription: "Zure testua simetrikoki zifratu eta deszifratzeko modulua (pasahitz partekatu baten bidez).", modulesBookmarksLabel: "Markagailuak", @@ -2900,7 +3180,7 @@ module.exports = { modulesVotationsDescription: "Bozketak aurkitu eta kudeatzeko modulua.", modulesParliamentLabel: "Legebiltzarra", modulesParliamentDescription: "Gobernuak hautatu eta legeak bozkatzeko modulua.", - modulesCourtsLabel: "Epaitegiak", + modulesCourtsLabel: "Auzitegiak", modulesCourtsDescription: "Gatazkak konpontzeko eta epaia emateko modulua.", modulesReportsLabel: "Txostenak", modulesReportsDescription: "Arazo, akats, abusu eta eduki-abisuetan erlazionatutako txostenak kudeatzeko modulua.", @@ -2922,7 +3202,7 @@ module.exports = { modulesMelodyDescription: "Zure blockchain-aren \"soinua\" sortzeko eta partekatzeko modulua.", modulesAgendaLabel: "Agenda", modulesAgendaDescription: "Esleitu zaizkizun elementu guztiak kudeatzeko modulua.", - modulesAILabel: "AI", + modulesAILabel: "IA", modulesAIDescription: "'42' izeneko LLM batekin hitz egiteko modulua.", modulesForumLabel: "Foroak", modulesForumDescription: "Foroak deskubritu eta kudeatzeko modulua.", @@ -2931,20 +3211,23 @@ module.exports = { modulesProjectsLabel: "Proiektuak", modulesProjectsDescription: "Proiektuak esploratzeko, finantzatzeko eta kudeatzeko modulu.", modulesBankingLabel: "Bankua", - modulesBankingDescription: "ECOINen benetako balioa zehazteko eta UBI bat altxortegi komuna erabiliz banatzeko modulua.", + modulesBankingDescription: "Altxor komuna kudeatzeko modulua: ECOINen balioa, zergak eta oinarrizko errenta.", modulesFavoritesLabel: "Gogokoak", modulesFavoritesDescription: "Zure gogoko edukia kudeatzeko modulua.", fileTooLargeTitle: "Fitxategia handiegia", - fileTooLargeMessage: "Fitxategiak onartutako gehienezko tamaina gainditzen du (50 MB). Mesedez, hautatu fitxategi txikiago bat.", + fileTooLargeMessage: "Fitxategiak onartutako gehienezko tamaina gainditzen du (75 MB). Mesedez, hautatu fitxategi txikiago bat.", goBack: "Itzuli", + errorDismiss: "Ados", errorPageTitle: "Zerbait gaizki joan da", + errorNoAllocation: "Ez da esleipenik aurkitu erreklamazio honetarako.", + errorInvalidClaim: "Esleipen hau ezin da erreklamatu.", visibilityLabel: "Ikusgaitasuna", + clearnetPrivateConflict: "Eduki pribatua, ezkutua, itxia, gonbidapen bidezkoa edo ordainpekoa ezin da CLEARNETen argitaratu. Aukeratu OASIS edo egin ezazu publiko.", visibilityPublic: "Publikoa", visibilityHidden: "Ezkutua", visibilityMakePublic: "Publiko egin", visibilityMakeHidden: "Ezkutatu", invitesInhabitantsTitle: "Biztanleak", - invitesInhabitantsFollow: "Jarraitu", aiNavPlaceholder: "Deskribatu zer behar duzun...", aiNavDisabled: "AI nabigazioa desgaituta dago.", aiNavResultsTitle: "AINav iradokizunak", @@ -2955,7 +3238,7 @@ module.exports = { modulesAINavDescription: "Sareko edukiari buruzko hizkuntza naturalezko galderak egiteko modulua.", directConnectDescription: "Konektatu zuzenean parekide batera bere IP helbidea, portua eta gako publikoa sartuz. Parekidea jarraipen-konexio gisa gehituko da.", peerHost: "IP", - peerPort: "Portua (lehenetsia: 8008)", + peerPort: "Ataka", peerPublicKey: "Gako Publikoa (@...ed25519)", connectAndFollow: "Konektatu", modulesPresetTitle: "Konfigurazio Arruntak", @@ -2964,26 +3247,27 @@ module.exports = { workflowsSet: "Workflow-a ezarri", workflow_default: "Lehenetsia", workflow_jobs: "Lana", - workflow_ruling: "Gobernua", - workflow_politics: "Politika", workflow_social: "Soziala", workflow_business: "Negozioak", workflow_night: "Gaua", + workflow_kids: "Haurrak", + workflow_activists: "Aktibismoa", workflow_mobile: "Mugikorra", statsCarbonFootprintTitle: "Karbono Aztarna", statsCarbonTombstone: "Tombstoning-aren aztarna", - uploadMedia: "Multimedia igo (max: 50MB)", + uploadMedia: "Multimedia igo (max: 75MB)", reportsWhyInappropriateLabel: "Zergatik da desegokia?", visitContent: "Bisitatu Edukia", favoriteAdd: "Ainguratu Gogokoetan", pmContentTooltip: "Bidali Mezu Pribatua", + phoneCallTooltip: "Egin Dei bat", favoriteRemove: "Kendu Gogokoetatik", reportContent: "Salatu Edukia", - bankEcoinHours: "ECOin Orduak", + bankEcoinHours: "ECOin Orduak (1 ECO)", mapsLabel: "Mapak", mapTitle: "Mapak", - mapDescription: "Esploratu eta kudeatu lineaz kanpoko mapak zure sarean.", + mapDescription: "Kudeatu eta partekatu lineaz kanpoko mapak zure sarean.", mapMineSectionTitle: "Zure Mapak", mapCreateSectionTitle: "Mapa Sortu", mapUpdateSectionTitle: "Mapa Eguneratu", @@ -3012,7 +3296,7 @@ module.exports = { mapTypeOpen: "IREKIA", mapTypeClosed: "ITXIA", mapTagsLabel: "Etiketak", - mapTagsPlaceholder: "Sartu etiketak komaz bereizita", + mapTagsPlaceholder: "Idatzi etiketak komaz bereizita", mapUrlLabel: "Maparen URLa", mapMarkersTitle: "Markatzaileak", mapMarkerDefault: "Markatzailea", @@ -3023,9 +3307,22 @@ module.exports = { markerImageLabel: "Markatzailearen Irudia", mapAddMarkerTitle: "Markatzailea Gehitu", mapAddMarkerButton: "Markatzailea Gehitu", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Garbitu markatzailea", mapApplyZoom: "Zoom aplikatu", - mapSearchPlaceholder: "Bilatu deskribapena, etiketak, egilea...", + mapPlaceSearchPlaceholder: "Bilatu leku edo herrialde bat...", + mapNoPlaces: "Ez da lekurik aurkitu.", + mapClusterTitle: "markatzaile hemen, sakatu hurbiltzeko", + mapDeleteMarker: "Ezabatu markatzailea", + mapZoomIn: "Hurbildu", + mapZoomOut: "Urrundu", + mapFitMarkers: "Doitu markatzaile guztietara", + mapPanLabel: "Mugitu mapa", + mapCenterHere: "Zentratu hemen", + mapNear: "hemendik gertu:", + mapPickedSpot: "Aukeratutako puntua", + mapPickHint: "Sakatu mapan puntu bat aukeratzeko.", + mapClearPick: "Garbitu", + mapSearchPlaceholder: "Bilatu hemen: Mapak...", mapSearchButton: "Bilatu", mapUpdatedAt: "Eguneratua", mapNoMatch: "Ez da maparik aurkitu zure bilaketarekin.", @@ -3036,31 +3333,602 @@ module.exports = { typeMapMarker: "MAPA MARKATZAILEA", modulesMapLabel: "Mapak", modulesMapDescription: "Lineaz kanpoko mapak kudeatzeko eta partekatzeko modulua.", + emergenciesTitle: "Larrialdiak", + modulesEmergenciesLabel: "Larrialdiak", + modulesEmergenciesDescription: "Sarean larrialdiak jaurti, berretsi eta jarraitzeko modulua.", + emergenciesDescription: "Sarean larrialdiak jaurti, berretsi eta jarraitzeko modulua.", + emergencyCreate: "Larrialdia sortu", + emergencyUpdate: "Eguneratu", + emergencyDelete: "Ezabatu", + emergencyTitleLabel: "Izenburua", + emergencyTitlePlaceholder: "Zer gertatzen ari da?", + emergencyTextLabel: "Xehetasunak", + emergencyTextPlaceholder: "Non, noiztik, zer egin...", + emergencyCategoryLabel: "Kategoria", + emergencyCategoryWeather: "EGURALDIA", + emergencyCategoryInfrastructure: "AZPIEGITURA", + emergencyCategoryHealth: "OSASUNA", + emergencyCategorySecurity: "SEGURTASUNA", + emergencyCategoryLost: "GALDUTAKOAK", + emergencyCategoryNeighborhood: "AUZOA", + emergencyExpiresLabel: "Iraungitzen da", + emergencyExpires1d: "Egun 1", + emergencyExpires3d: "3 egun", + emergencyExpires7d: "Astebete", + emergencyExpires30d: "Hilabete 1", + emergencyExpiresNone: "Zehaztugabea", + emergencyExpiresKeep: "Mantendu uneko data", + emergencyTagsLabel: "Etiketak", + emergencyTagsPlaceholder: "Idatzi etiketak komaz bereizita", + emergencyFilterAll: "GUZTIAK", + emergencyFilterMine: "NIREAK", + emergencyFilterRecent: "AZKENAK", + emergencySeverityUnverified: "EGIAZTATU GABE", + emergencySeverityLow: "BAXUA", + emergencySeverityMedium: "ERTAINA", + emergencySeverityHigh: "ALTUA", + emergencyStatusActive: "AKTIBOA", + emergencyStatusResolved: "KONPONDUA", + emergencyStatusExpired: "IRAUNGITUA", + emergencyConfirmations: "Berrespenak", + emergencyConfirmButton: "Berretsi", + emergencyConfirmed: "Larrialdi hau berretsi duzu", + emergencyResolveButton: "Konpondu", + emergencyReopenButton: "Berriro ireki", + emergencyGenerateReport: "Txostena sortu", + emergencyUpdateEdited: "Editatua", + emergencyLastUpdate: "AZKEN EGUNERAKETA", + emergencyUpdatesTitle: "Azken eguneraketak:", + emergencyNoUpdates: "Oraindik ez dago eguneraketarik.", + emergencyAddUpdate: "Eguneraketa argitaratu", + emergencyUpdatePlaceholder: "Zer aldatu da?", + emergencyNoItems: "Ez da larrialdirik aurkitu.", + emergencyExpiresAt: "Iraungitzen da", + emergencyBannerLabel: "Larrialdi aktiboa", + emergencyCreateSectionTitle: "Larrialdia sortu", + emergencyUpdateSectionTitle: "Larrialdia eguneratu", + emergencySearchPlaceholder: "Bilatu larrialdietan...", + typeEmergency: "LARRIALDIAK", + typeEmergencyConfirm: "LARRIALDI BERRESPENA", + typeEmergencyUpdate: "LARRIALDI EGUNERAKETA", + statsEmergency: "Larrialdiak", + statsEmergencyConfirm: "Larrialdi berrespenak", + statsEmergencyUpdate: "Larrialdi eguneraketak", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Jarraitzen duzun larrialdi batek eguneraketa bat du.", + emergencyBotResolvedTitle: "Jarraitzen duzun larrialdi bat konpondu da.", + dataKindEmergencies: "LARRIALDIAK", + mailingTitle: "Posta-zerrendak", + modulesMailingLabel: "Posta-zerrendak", + modulesMailingDescription: "Aurkitu eta kudeatu posta-zerrendak zure sarean.", + modulesPhoneLabel: "Telefonoa", + modulesPhoneDescription: "Beste biztanleei muturretik muturrera zifratutako bideodeiak egiteko modulua.", + mailingDescription: "Aurkitu eta kudeatu posta-zerrendak zure sarean.", + mailingCreate: "Posta-zerrenda sortu", + mailingUpdate: "Eguneratu", + mailingArchive: "Artxibatu", + mailingActivate: "Aktibatu", + mailingCreateSectionTitle: "Posta-zerrenda sortu", + mailingUpdateSectionTitle: "Posta-zerrenda eguneratu", + mailingTitleLabel: "Izenburua", + mailingTitlePlaceholder: "Posta-zerrendaren izena", + mailingDescriptionLabel: "Deskribapena", + mailingDescriptionPlaceholder: "Zeri buruz da zerrenda hau?", + mailingTypeLabel: "Posta-zerrenda mota", + mailingTypeOpen: "IREKIA", + mailingTypeClosed: "ITXIA", + mailingStatusLabel: "Egoera", + mailingStatusActive: "AKTIBOA", + mailingStatusArchived: "ARTXIBATUA", + mailingMembersLabel: "Biztanleak gonbidatu", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Harpidedunak", + mailingTagsLabel: "Etiketak", + mailingTagsPlaceholder: "Idatzi etiketak komaz bereizita", + mailingFilterAll: "GUZTIAK", + mailingFilterMine: "NIREAK", + mailingFilterSubscribed: "HARPIDETUAK", + mailingFilterRecent: "AZKENAK", + mailingHistoryDate: "DATA", + mailingHistoryThreads: "HARIAK", + mailingHistoryInhabitants: "BIZTANLEAK", + mailingHistorySearchPlaceholder: "Bilatu historian...", + mailingNoMessages: "Oraindik ez dago mezurik.", + mailingNoItems: "Ez da posta-zerrendarik aurkitu.", + mailingSearchPlaceholder: "Bilatu posta-zerrendetan...", + mailingWriteButton: "Mezua idatzi", + mailingWriteTitle: "Zerrendari idatzi", + mailingReplyTitle: "Zerrendari erantzun", + mailingSend: "Bidali", + mailingTextPlaceholder: "Zure mezua zerrendako partaide guztiei iristen zaie.", + mailingParticipants: "Partaideak", + inboxFilterMailing: "Posta-zerrendak", + mailingReplyPlaceholder: "Zerrendari erantzun...", + mailingReplyToList: "Zerrendari erantzun", + typeMailingList: "POSTA-ZERRENDA", + statsMailingList: "Posta-zerrendak", + mailingListLabel: "POSTA-ZERRENDA", + logisticsTitle: "Logistika", + modulesLogisticsLabel: "Logistika", + modulesLogisticsDescription: "Aurkitu eta kudeatu plan logistikoak zure sarean.", + logisticsDescription: "Aurkitu eta kudeatu plan logistikoak zure sarean.", + logisticsCreate: "Ibilbidea sortu", + logisticsUpdate: "Eguneratu", + logisticsClose: "Itxi", + logisticsReopen: "Berriro ireki", + logisticsCreateSectionTitle: "Ibilbidea sortu", + logisticsUpdateSectionTitle: "Ibilbidea eguneratu", + logisticsTitleLabel: "Izenburua", + logisticsTitlePlaceholder: "Bidaia merkatura, furgoneta eskuragarri...", + logisticsKindLabel: "Logistika mota", + logisticsKindTrip: "BIDAIA", + logisticsKindShipment: "BIDALKETA", + logisticsKindTrips: "BIDAIAK", + logisticsKindShipments: "BIDALKETAK", + logisticsModeLabel: "Modua", + logisticsModeOffer: "ESKAINTZEN DUT", + logisticsModeRequest: "BILA NABIL", + logisticsModeOffers: "ESKAINTZAK", + logisticsModeRequests: "ESKAERAK", + logisticsDescriptionLabel: "Deskribapena", + logisticsDescriptionPlaceholder: "Elkargunea, zer daramazun, baldintzak...", + logisticsOriginLabel: "Jatorria", + logisticsOriginPlaceholder: "Non hasten den", + logisticsDestinationLabel: "Helmuga", + logisticsDestinationPlaceholder: "Non amaitzen den", + logisticsDateLabel: "Data eta ordua", + logisticsRecurrenceLabel: "Errepikapena", + logisticsFrequencyLabel: "Maiztasuna", + logisticsRecurrenceNone: "BAKARRA", + logisticsRecurrenceDaily: "EGUNEROKOA", + logisticsRecurrenceWeekly: "ASTEKOA", + logisticsRecurrenceMonthly: "HILEKOA", + logisticsSeatsLabel: "Plazak", + logisticsSizeLabel: "Tamaina", + logisticsSizePlaceholder: "adib. 2m x 1m", + logisticsWeightLabel: "Pisua", + logisticsWeightPlaceholder: "adib. 40kg", + logisticsPriceFree: "DOAN", + logisticsPriceEco: "ECO", + logisticsPriceTime: "DENBORA", + logisticsPriceLabel: "Prezioa", + logisticsHours: "h", + logisticsOrderRefLabel: "Eskaeraren esteka", + logisticsOrderRefPlaceholder: "entregatu beharreko eskaeraren /shops/... edo /market/...", + logisticsOrderLink: "Lotutako eskaera", + logisticsTagsLabel: "Etiketak", + logisticsTagsPlaceholder: "Idatzi etiketak komaz bereizita", + logisticsFilterAll: "GUZTIAK", + logisticsFilterMine: "NIREAK", + logisticsFilterBooked: "ERRESERBATUAK", + logisticsFilterRecent: "AZKENAK", + logisticsFilterHistory: "HISTORIA", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "IREKIA", + logisticsStatusUpcoming: "HURRENGOAK", + logisticsStatusPast: "IGAROTAKOAK", + logisticsStatusClosed: "ITXIAK", + logisticsZoneLabel: "Eremua", + logisticsSearchPlaceholder: "Bilatu logistikan...", + logisticsNoItems: "Ez da ibilbiderik aurkitu.", + logisticsBookingPending: "ZAIN", + logisticsBookingConfirmed: "BAIEZTATUA", + logisticsBookingRejected: "BAZTERTUA", + logisticsBookingCancelled: "BERTAN BEHERA", + logisticsBookingDelivered: "ENTREGATUA", + logisticsConfirm: "Baieztatu", + logisticsReject: "Baztertu", + logisticsDeliver: "Entregatu", + logisticsReceiptLabel: "Ordainagiria", + logisticsReceiptPlaceholder: "Entrega-oharra (aukerakoa)", + logisticsCancelBooking: "Erreserba bertan behera utzi", + logisticsBookTitle: "Ibilbide hau erreserbatu", + logisticsNotesLabel: "Oharrak", + logisticsNotesPlaceholder: "Arduradunak jakin beharrekoa", + logisticsBook: "Erreserbatu", + logisticsBookingsTitle: "Erreserbak", + logisticsMyBookingTitle: "Zure erreserba", + logisticsRatingsTitle: "Balorazioak", + logisticsNoRatings: "Oraindik ez dago baloraziorik.", + logisticsRatingsWhenClosed: "Balorazioak ibilbidea ixtean irekitzen dira.", + logisticsScoreLabel: "Puntuazioa", + logisticsRatingPlaceholder: "Nola joan da?", + logisticsRate: "Baloratu", + typeLogisticsRoute: "IBILBIDEA", + typeLogisticsRating: "IBILBIDEAREN BALORAZIOA", + statsLogisticsRoute: "Ibilbideak", + statsLogisticsRating: "Ibilbideen balorazioak", + logisticsRouteLabel: "IBILBIDEA", + podcastsTitle: "Podcastak", + modulesPodcastsLabel: "Podcastak", + modulesPodcastsDescription: "Podcast kanalak argitaratzeko modulua.", + podcastsDescription: "Podcast kanalak argitaratzeko modulua.", + podcastCreate: "Podcasta sortu", + podcastUpdate: "Eguneratu", + podcastDelete: "Ezabatu", + podcastCreateSectionTitle: "Podcasta sortu", + podcastUpdateSectionTitle: "Podcasta eguneratu", + podcastTitleLabel: "Izenburua", + podcastTitlePlaceholder: "Kanalaren izena", + podcastDescriptionLabel: "Deskribapena", + podcastDescriptionPlaceholder: "Zeri buruz da podcast hau?", + podcastCategoryLabel: "Kategoria", + podcastCategoryNews: "ALBISTEAK", + podcastCategoryMusic: "MUSIKA", + podcastCategoryTalk: "SOLASALDIA", + podcastCategoryEducation: "HEZKUNTZA", + podcastCategoryTech: "TEKNOLOGIA", + podcastCategoryCulture: "KULTURA", + podcastCategoryCommunity: "KOMUNITATEA", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "BESTELAKOAK", + podcastTagsLabel: "Etiketak", + podcastCoverLabel: "Azala (irudia edo bideoa)", + podcastEpisodeAudioLabel: "Audio edo bideo fitxategia (gehienez: 75MB)", + podcastTagsPlaceholder: "Idatzi etiketak komaz bereizita", + podcastFilterAll: "GUZTIAK", + podcastFilterMine: "NIREAK", + podcastFilterRecent: "AZKENAK", + podcastFilterTop: "TOP", + podcastFilterViewers: "ENTZULEAK", + podcastSearchPlaceholder: "Bilatu podcastetan...", + podcastNoItems: "Ez da podcastik aurkitu.", + podcastEpisodesLabel: "Atalak", + podcastPlaysLabel: "Ikustaldiak", + podcastLastEpisode: "Azken atala", + podcastEpisodesTitle: "Atalak", + podcastNoEpisodes: "Oraindik ez dago atalik.", + podcastEpisodeAdd: "Atala gehitu", + podcastEpisodeCreateTitle: "Atala argitaratu", + podcastEpisodeUpdateTitle: "Atala eguneratu", + podcastEpisodeTitleLabel: "Atalaren izenburua", + podcastEpisodeTitlePlaceholder: "Atalaren izenburua", + podcastEpisodeDescriptionLabel: "Oharrak", + podcastEpisodeDescriptionPlaceholder: "Zer gertatzen den atal honetan, estekak, kredituak...", + podcastEpisodePublish: "Argitaratu", + podcastPlayed: "Entzunda", + podcastMarkPlayed: "Entzundako gisa markatu", + typePodcast: "PODCASTA", + typePodcastEpisode: "ATALA", + typePodcastOpinion: "ATALAREN IRITZIA", + typePodcastPlay: "ERREPRODUKZIOA", + statsPodcast: "Podcastak", + statsPodcastEpisode: "Atalak", + statsPodcastPlay: "Erreprodukzioak", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Jarraitzen duzun blog batek sarrera berria du.", + podcastBotEpisodeTitle: "Jarraitzen duzun podcast batek atal berria du.", + podcastLabel: "PODCASTA", + podcastEpisodeLabel: "ATALA", + campaignsTitle: "Kanpainak", + modulesCampaignsLabel: "Kanpainak", + modulesCampaignsDescription: "Aurkitu eta kudeatu kanpainak zure sarean.", + campaignsDescription: "Aurkitu eta kudeatu kanpainak zure sarean.", + campaignCreate: "Kanpaina sortu", + campaignUpdate: "Eguneratu", + campaignDelete: "Ezabatu", + campaignClose: "Itxi", + campaignReopen: "Berriro ireki", + campaignCreateSectionTitle: "Kanpaina sortu", + campaignUpdateSectionTitle: "Kanpaina eguneratu", + campaignTitleLabel: "Izenburua", + campaignTitlePlaceholder: "Zer lortu nahi duzu?", + campaignTextLabel: "Testua", + campaignTextPlaceholder: "Azaldu kausa, nork erabakitzen duen eta zer eskatzen duzun...", + campaignCategoryLabel: "Kategoria", + campaignCategoryEnvironment: "INGURUMENA", + campaignCategoryRights: "ESKUBIDEAK", + campaignCategoryHealth: "OSASUNA", + campaignCategoryEducation: "HEZKUNTZA", + campaignCategoryInfrastructure: "AZPIEGITURA", + campaignCategoryCulture: "KULTURA", + campaignCategoryEconomy: "EKONOMIA", + campaignCategoryOther: "BESTELAKOAK", + campaignGoalLabel: "Sinadura helburua", + campaignDeadlineLabel: "Epea", + campaignTagsLabel: "Etiketak", + campaignTagsPlaceholder: "Idatzi etiketak komaz bereizita", + campaignFilterAll: "GUZTIAK", + campaignFilterMine: "NIREAK", + campaignFilterSigned: "SINATUAK", + campaignFilterRecent: "AZKENAK", + campaignFilterTop: "TOP", + campaignStatusOpen: "IREKIA", + campaignStatusAchieved: "LORTUA", + campaignStatusUnachieved: "LORTU GABE", + campaignStatusClosed: "ITXIA", + campaignSearchPlaceholder: "Bilatu kanpainetan...", + campaignNoItems: "Ez da kanpainarik aurkitu.", + campaignSignaturesLabel: "Sinadurak", + campaignSignaturesTitle: "Babesleak", + campaignNoSignatures: "Oraindik ez dago sinadurarik.", + campaignSignTitle: "Kanpaina hau sinatu", + campaignSignPlaceholder: "Zergatik babesten duzu? (aukerakoa)", + campaignSign: "Sinatu", + campaignSigned: "Dagoeneko sinatu duzu!", + campaignNotSigned: "Zure sinadura falta da oraindik", + campaignElevate: "Parlamentura eraman", + campaignElevated: "Parlamentura eramana", + campaignUpdatesTitle: "Eguneraketak", + campaignNoUpdates: "Oraindik ez dago eguneraketarik.", + campaignUpdatePlaceholder: "Zer gertatu da?", + campaignAddUpdate: "Eguneraketa argitaratu", + campaignQrAlt: "Kanpaina hau sinatzeko QRa", + campaignQrCaption: "Eskaneatu sinatzeko", + typeCampaign: "KANPAINA", + typeCampaignSignature: "SINADURA", + typeCampaignUpdate: "KANPAINAREN EGUNERAKETA", + statsCampaign: "Kanpainak", + statsCampaignSignature: "Kanpainen sinadurak", + statsCampaignUpdate: "Kanpainen eguneraketak", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Jarraitzen duzun kanpaina batek eguneraketa bat du.", + campaignBotAchievedTitle: "Jarraitzen duzun kanpaina batek helburua lortu du.", + campaignBotRaisedTitle: "Sinatu duzun kanpaina bat Parlamentura eraman da.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Erreserbatu duzun ibilbide bat aldatu da.", + logisticsBotClosedTitle: "Erreserbatu duzun ibilbide bat itxi da.", + logisticsBotBookedTitle: "Norbaitek zure ibilbidea erreserbatu du.", + logisticsBotCancelledTitle: "Zure ibilbideko erreserba bat bertan behera utzi da.", + logisticsBotConfirmedTitle: "Zure erreserba berretsi da: paketea bidean da.", + logisticsBotRejectedTitle: "Zure erreserba baztertu da.", + logisticsBotDeliveredTitle: "Zure paketea iritsi da.", + campaignLabel: "KANPAINA", + campaignGenerateReport: "Txostena sortu", + logisticsGenerateReport: "Txostena sortu", + mailingGenerateReport: "Txostena sortu", + campaignUpdateEdited: "Editatua", + mailingMessagesLabel: "Mezuak", + logisticsBookingsLabel: "Erreserbak", + backupTitle: "Segurtasun-kopia", + modulesBackupLabel: "Segurtasun-kopia", + modulesBackupDescription: "Zure gakoak esportatu eta inportatzeko eta segurtasun-kopia zifratuak egiteko modulua.", + backupDescription: "Zure gakoak esportatu eta inportatzeko eta segurtasun-kopia zifratuak egiteko modulua.", + backupTypeRecovery: "BERRESKURATZEA", + backupTypeKeys: "GAKOAK ESPORTATU", + backupTypeFull: "KOPIA OSOA", + backupTypeRestore: "KOPIA LEHENERATU", + backupTypeInstant: "BAT-BATEAN", + backupInstantDescription: "Zure erregistro eta fitxategi guztien .zip soil bat, gako sekreturik gabe eta pasahitzik gabe. Azkarra, baina ", + backupInstantRisk: "fitxategia duenak dena irakurtzen du.", + backupKitPageDescription: "Berreskuratze-kita gailu honetatik bakarrik ireki daiteke.", + backupKitId: "Oasis IDa", + backupKitDate: "Sortua", + backupKitQrAlt: "Zure gako sekretuaren QR kodea", + backupKitStep1: "Inprimatu edo eskuz kopiatu eta gorde leku seguru batean, konexiorik gabe.", + backupKitStep2: "Berreskuratzeko, instalatu Oasis gailu berri batean eta inportatu sekretu hau Segurtasun-kopia › Gakoak ataletik.", + backupKitStep3: "Suntsitu orri hau inoiz agerian geratzen bada.", + backupKitPdfButton: "PDFa sortu", + backupScopeLabel: "Irismena", + backupScopeAll: "DENA", + backupScopeMine: "NIRE EDUKIA BAKARRIK", + backupSinceLabel: "Noiztik", + backupEstimateTitle: "Estimazioa", + backupFileInfo: "Zure kopia zifratua gailuan deskargatuko da (izena: oasis-backup-DATA.oasisbk)", + backupExportButton: "Kopia deskargatu", + backupRestorePassword: "Idatzi kopia egitean erabilitako pasahitza", + backupRestoreButton: "Kopia berreskuratu", + backupRestoredLine: "Kopia berreskuratuta.", + backupRestoredMessages: "Gehitutako mezuak", + backupRestoredSkipped: "Lehendik zeudenak", + backupRestoredBlobs: "Gehitutako fitxategiak", + backupRestoredState: "Ezarpenak berrezarrita", + backupRestoredFailed: "Huts egindakoak", + backupRestoredForked: "Dibergenteak", + backupRestoreRunning: "Kopia leheneratzen. Orri hau bere kabuz eguneratzen da; bitartean Oasis erabiltzen jarrai dezakezu.", + backupRestoreElapsed: "Igarotako denbora", + backupRestoreFailedLine: "Ezin izan da kopia leheneratu.", + backupRestoreForkMine: "Gailu honek dagoeneko zure identitatearekin kopian ez dauden mezuak argitaratu ditu, beraz ezin izan da haien gainean aplikatu. Leheneratu instalazio garbi batean, giltzak inportatu eta berehala, beste ezer egin aurretik.", + verificationTitle: "Egiaztapena", + verificationDescription: "Egiaztatu zure jarioaren katea, hauteman adarkatzeak eta berrikusi fitxategi umezurtzak edo falta direnak.", + verificationRun: "Egiaztapena exekutatu", + verificationTotalMessages: "mezu erregistroan", + verificationFeed: "Zure jarioa", + verificationMessages: "mezu", + verificationLastSequence: "azken sekuentzia", + verificationGaps: "Hutsuneak", + verificationBrokenLinks: "Lotura hautsiak", + verificationBadHashes: "Hash okerrak", + verificationBadSignatures: "Sinadura okerrak", + verificationDuplicates: "Sekuentzia bikoiztuak", + verificationForks: "Adarkatzeak", + verificationBlobs: "Fitxategiak", + verificationBlobsPresent: "daude", + verificationBlobsReferenced: "erreferentziatuak", + verificationBlobsOrphan: "umezurtzak", + verificationBlobsMissing: "falta dira", + verificationBlobsMissingOwn: "Gailu honetan falta diren zure fitxategiak", + verificationBlobsMissingOthers: "Beste biztanleen fitxategiak oraindik deskargatu gabe", + verificationBlobsUnavailable: "Ezin izan dira fitxategiak zerrendatu gailu honetan.", + wikiTitle: "Wikiak", + modulesWikiLabel: "Wikiak", + modulesWikiDescription: "Wikiak aurkitu eta kudeatzeko modulua.", + wikiDescription: "Wikiak aurkitu eta kudeatzeko modulua.", + wikiCreate: "Wikia sortu", + wikiUpdate: "Eguneratu", + wikiDelete: "Ezabatu", + wikiEdit: "Editatu", + wikiTitleLabel: "Izenburua", + wikiTitlePlaceholder: "Wikiaren izenburua...", + wikiBodyLabel: "Edukia", + wikiBodyPlaceholder: "Lotu beste wiki batzuk [[Wikiaren izenburua]] edo [[Wikiaren izenburua|testu propioa]] erabiliz.\n\nAipatu bizilagun bat @id bidez.\n\nEtiketatu #oasis erabiliz.\n\nItsatsi estekak, adibidez https://solarnethub.com\n\nLerro huts batek paragrafo berria hasten du; lerro-jauzi bakunak mantentzen dira.\n\nErantsi onartutako edozein media eta hemen txertatuko da.", + formatBarLabel: "Formatua", + wikiStatusLabel: "Wiki mota", + wikiStatusOpen: "IREKIA", + wikiStatusClosed: "ITXIA", + wikiTagsLabel: "Etiketak", + wikiTagsPlaceholder: "Idatzi etiketak komaz bereizita", + wikiSummaryLabel: "Aldaketaren laburpena", + wikiSummaryPlaceholder: "Zer aldatu da?", + wikiFilterAll: "GUZTIAK", + wikiFilterMine: "NIREAK", + wikiFilterRecent: "AZKENAK", + wikiFilterLinked: "LOTUTAK", + wikiChanges: "Azken aldaketak", + wikiNoPages: "Ez da wikirik aurkitu.", + wikiNoChanges: "Oraindik ez dago aldaketarik.", + wikiHistory: "Historia", + wikiVersion: "Bertsioa", + wikiVersions: "bertsio", + wikiRestore: "Berreskuratu", + wikiDiff: "Aldaketak", + wikiViewVersion: "Ikusi", + wikiLinkedPages: "Lotutako orriak", + wikiNoLinkedPages: "Orri hau ez dago oraindik beste inorekin lotuta.", + wikiMissingLink: "Orria ez da existitzen oraindik", + wikiDuplicateTitle: "Izenburu hori duen orri bat badago dagoeneko. Aldatu izenburua beste orri bat sortzeko.", + wikiCreateSectionTitle: "Wikia sortu", + wikiUpdateSectionTitle: "Wikia eguneratu", + wikiSearchPlaceholder: "Bilatu Wikian...", + wikiCurrentVersion: "Uneko bertsioa", + wikiViewingVersion: "Bertsio zahar bat ikusten ari zara", + wikiBackToPage: "Itzuli orrira", + wikiLinkedChip: "Lotuta", + wikiRestoredSummary: "Berreskuratutako bertsioa", + wikiExportPdf: "Orria sortu", + wikiPreview: "Aurrebista", + tribeSectionWiki: "WIKIA", + tribeWikiCreate: "Wikia sortu", + tribeWikiEmpty: "Oraindik ez dago wikirik.", + typeWiki: "WIKIA", padsTitle: "Padak", padTitle: "Pad", modulesPadsLabel: "Padak", - modulesPadsDescription: "Testu editoreak lankidetzan kudeatzeko modulua.", + modulesPadsDescription: "Testu-editore kolaboratiboak kudeatzeko modulua.", padCreate: "Pad Sortu", padUpdate: "Eguneratu", padDelete: "Ezabatu", padTitleLabel: "Izenburua", padTitlePlaceholder: "Idatzi pad-aren izenburua...", padStatusLabel: "Egoera", + padTypeLabel: "Pad mota", padStatusOpen: "IREKIA", padStatusInviteOnly: "GONBIDATUAK SOILIK", padStatusClosed: "ITXIA", padDeadlineLabel: "Epemuga", padTagsLabel: "Etiketak", - padTagsPlaceholder: "etiketa1, etiketa2, ...", + padTagsPlaceholder: "Idatzi etiketak komaz bereizita", padMembersLabel: "Kideak", padCreated: "Sortua", - padStartEditing: "EDITZEN HASI!", + padStartEditing: "Editatu", padEditorPlaceholder: "Idazten hasi...", padSubmitEntry: "Bidali", padNoEntries: "Oraindik sarrerarik ez.", padAllSectionTitle: "Pad Guztiak", padMineSectionTitle: "Nire Padak", padsDescription: "Kudeatu zure sareko enkriptatutako testu-editore kolaboratiboak.", + roomsTitle: "Gelak", + roomsDescription: "Aurkitu zure sareko bideokonferentzia-gela zifratuak eta sartu haietan.", + modulesRoomsLabel: "Gelak", + modulesRoomsDescription: "Bideokonferentzia-gela zifratuak aurkitu eta haietan sartzeko modulua.", + roomFilterRecent: "AZKENAK", + roomFilterMine: "NIREAK", + roomFilterAll: "GUZTIAK", + roomFilterLive: "ZUZENEAN", + roomFilterOpen: "IREKIAK", + roomFilterInvite: "GONBIDATUAK SOILIK", + roomFilterClosed: "ITXIAK", + roomCreate: "Sortu gela", + roomUpdate: "Eguneratu", + roomDelete: "Ezabatu", + roomClose: "Itxi", + roomCreateSectionTitle: "Sortu gela", + roomUpdateSectionTitle: "Eguneratu gela", + roomTitlePlaceholder: "Gelaren izenburua", + roomDescriptionLabel: "Deskribapena", + roomDescriptionPlaceholder: "Zertaz hitz egingo duzue?", + roomTypeLabel: "Gela mota", + roomTagsLabel: "Etiketak", + roomStatusOpen: "IREKIA", + roomStatusInviteOnly: "GONBIDATUAK SOILIK", + roomStatusClosed: "ITXIA", + roomParticipants: "Parte-hartzaileak", + roomJoin: "Sartu", + roomLeave: "Irten", + roomFull: "Gela beteta", + roomLiveChip: "ZUZENEAN", + roomMuted: "isilduta", + roomRec: "Audioa grabatu", + roomRecStop: "Grabazioa gelditu", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} gelan sartu da", + roomEventLeft: "{name} gelatik irten da", + roomEventRecStart: "{name} grabatzen hasi da", + roomEventRecStop: "{name} grabatzeari utzi dio", + roomEventMuted: "{name} isilarazi da", + roomEventUnmuted: "{name}(e)k isilunea kendu du", + roomEventHand: "{name}(e)k eskua altxatu du", + roomRaiseHand: "Eskua altxatu", + roomLowerHand: "Eskua jaitsi", + confirmActionTitle: "Berretsi ekintza hau", + confirmActionButton: "Berretsi", + confirmJoinText: "Gonbidapen honekin batzera zoaz.", + confirmFollowText: "Bizilagun hau babestera zoaz.", + confirmSignText: "Kanpaina hau sinatzera zoaz.", + confirmDownloadText: "Fitxategi hau zure gailura deskargatzera zoaz.", + roomRecordings: "Grabazioak", + roomClearNotices: "Garbitu", + roomLineSwitchboard: "Zentralita", + roomLineDndError: "Gela honen zenbakiak ez du deirik onartzen.", + roomNumberHint: "Markatu zenbaki hau Telefonotik gelan sartzeko.", + liveRefresh: "Berritu", + roomNobody: "Une honetan ez dago inor gelan.", + roomSomeone: "{n} gelan une honetan.", + roomYou: "zu", + roomYouAreIn: "Barruan zaude", + roomNobodyInside: "Une honetan ez dago inor gela honetan.", + roomErrorFull: "Gela beteta dago. Norbait irteten denean sartu ahal izango zara.", + roomErrorUnreachable: "Une honetan ezin da gelara iritsi. Saiatu berriro geroago.", + roomErrorBusy: "Dagoeneko dei batean edo beste gela batean zaude.", + roomErrorClosed: "Gela hau itxita dago.", + roomAccessDenied: "Gela hau gonbidatuentzat soilik da. Gonbidapen bat behar duzu sartzeko.", + roomsNoItems: "Ez da gelarik aurkitu.", + roomSearchPlaceholder: "Bilatu gelak...", + roomInviteMode: "Gonbidapena", + tribeSectionRooms: "GELAK", + tribeRoomCreate: "Sortu gela", + tribeRoomsEmpty: "Oraindik ez dago gelarik.", + typeRoom: "GELAK", + typeRoomMember: "GELAKO PARTE-HARTZAILEA", + statsRoom: "Gelak", + statsRoomMember: "Geletako parte-hartzaileak", + favoritesFilterRooms: "GELAK", + invitesRoomsTitle: "Gelak", + inviteCodeInvalid: "Gonbidapen-kode hau ez da baliozkoa edo dagoeneko erabili da.", + inviteAlreadyMember: "Dagoeneko bertan zaude. Bere modulutik ireki dezakezu.", + actionFailed: "Ezin izan da egin. Saiatu berriro.", + publishTooShort: "Zure argitalpena laburregia da.", + walletSendFailed: "Ezin izan da ordainketa bidali: {reason}", + pixeliaCoordsError: "Koordenatu horiek saretik kanpo daude.", + courtsErrTitle: "Izenburu bat behar da.", + courtsErrRespondent: "Adierazi kasua noren aurka den.", + courtsErrRespondentId: "Hori ez da baliozko Oasis ID bat.", + courtsErrMethod: "Aukeratu kasua nola ebatziko den.", + courtsErrCaseNotFound: "Ez da kasu hori aurkitu.", + courtsErrBrief: "Idatzi zure erantzuna.", + courtsErrStance: "Aukeratu zure jarrera.", + courtsErrResult: "Idatzi emaitza.", + courtsErrTerms: "Idatzi akordioaren baldintzak.", + courtsErrMediator: "Gehitu bitartekari bat gutxienez.", + courtsErrSelfMediator: "Ezin zara zure kasuko bitartekari izan.", + courtsErrJudge: "Aukeratu epaile bat.", + courtsErrSelfJudge: "Ezin zara zure kasuko epaile izan.", + courtsErrVisibility: "Aukeratu nork ikus dezakeen kasua.", + courtsErrNomination: "Ez da izendapen hori aurkitu.", + opinionAlreadyGiven: "Dagoeneko eman duzu zure iritzia.", + cipherPasswordShort: "Pasahitzak gutxienez 32 karaktere izan behar ditu.", + errorForbidden: "Ez duzu honetarako sarbiderik.", + errorNotFound: "Ez da aurkitu.", + errorClosed: "Itxita dago.", + errorInviteOnly: "Hau gonbidatuentzat soilik da. Gonbidapen bat behar duzu.", + errorTooLong: "Testu hori luzeegia da.", + errorRequired: "Derrigorrezko informazioa falta da.", + errorInvalid: "Informazioaren zati bat ez da baliozkoa.", + errorNotAuthor: "Sortzaileak bakarrik egin dezake hau.", + panicRemoved: "Zure blockchaina ezabatu da.", padRecentSectionTitle: "Azken Padak", padOpenSectionTitle: "Pad Irekiak", padClosedSectionTitle: "Pad Itxiak", @@ -3072,18 +3940,18 @@ module.exports = { padRemoveFavorite: "Gogokoetatik Kendu", padClose: "Itxi", padBackToEditor: "Editorera itzuli", - padSearchPlaceholder: "Bilatu padak...", + padSearchPlaceholder: "Bilatu hemen: Padak...", modulesChatsLabel: "Txatak", - modulesChatsDescription: "Txat zifratu publikoak aurkitu eta kudeatzeko modulua.", + modulesChatsDescription: "Txatak aurkitu eta kudeatzeko modulua.", typeChat: "TXATAK", typeChatMessage: "TXAT MEZUA", chatsTitle: "Txatak", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "Zure txatak", + chatRecentTitle: "Azken txatak", chatFavoritesTitle: "Gogokoak", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "Txat irekiak", + chatClosedTitle: "Txat itxiak", chatDescription: "Deskribapena", chatCategory: "Kategoria", chatStatus: "EGOERA", @@ -3098,34 +3966,172 @@ module.exports = { chatMessagesLabel: "Mezuak", chatThreadsLabel: "Hariak", chatCreatedAt: "SORTUA", - chatSearchPlaceholder: "Txatak bilatu...", + chatSearchPlaceholder: "Bilatu hemen: Txatak...", chatStatusOpen: "IREKIA", chatStatusInviteOnly: "GONBIDAPENEZ BAKARRIK", chatStatusClosed: "ITXIA", chatSendMessage: "Bidali", chatJumpLatest: "Azken Mezua", + chatReply: "Erantzun", + chatReplyCancel: "Erantzuna utzi", + chatPin: "Finkatu", + chatUnpin: "Askatu", + chatPinned: "Finkatuak", chatPickChat: "Aukeratu txat bat irekitzeko", chatNoneYet: "Oraindik ez dago txatik eskuragarri.", - activitySearchPlaceholder: "Bilatu jardueran...", - trendingSearchPlaceholder: "Bilatu joeretan...", - opinionsSearchPlaceholder: "Bilatu iritzietan...", - votesSearchPlaceholder: "Bilatu bozketetan...", - welcomeStepUxTitle: "Aukeratu zure interfazea", - welcomeStepUxText: "Erabaki Oasis nolakoa den. Gero ezarpenetan alda dezakezu.", - welcomeStepUxAction: "Erabili ikuspegi hau", + activitySearchPlaceholder: "Bilatu hemen: Jarduera...", + trendingSearchPlaceholder: "Bilatu hemen: Pil-pilean...", + opinionsSearchPlaceholder: "Bilatu hemen: Iritziak...", + votesSearchPlaceholder: "Bilatu hemen: Bozkatzeak...", + gamesSearchPlaceholder: "Bilatu hemen: Jokoak...", + schoolTitle: "Eskola", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Jarraitzen duzun wiki orri bat editatu da.", + wikiBotRestoredTitle: "Jarraitzen duzun wiki orri baten aurreko bertsio bat berreskuratu da.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Ikasle berri batek zure ikastaroan izena eman du.", + schoolBotInvitedTitle: "Ikastaro batera gonbidatu zaituzte.", + schoolBotAdmittedTitle: "Ikastaro batean onartu zaituzte.", + schoolBotCertificateTitle: "Ziurtagiri bat jaso duzu.", + schoolBotPassedTitle: "Ikasle batek zure ikastaroa gainditu du.", + schoolBotLessonTitle: "Ikasgai berri bat argitaratu da.", + invitesSchoolTitle: "Eskola", + schoolEnrollLabel: "Batu ikastarora", + schoolExamRetryLater: "24 ordu igarota berriro saia zaitezke.", + schoolApproved: "GAINDITUTA", + schoolExamPassMark: "Gainditzeko nota (0-10)", + schoolExamPassed: "GAINDITUTA", + schoolExamPending: "Zain", + schoolExamFailed: "EZ GAINDITUA", + schoolExamAddQuestion: "Gehitu galdera", + schoolExamCreateQuestion: "Galdera sortu", + schoolExamOption: "Aukera", + schoolExamCorrect: "Aukera zuzena", + schoolExamPoints: "Puntuak", + schoolMaterialTextPlaceholder: "Ikasgai honetarako oharrak...", + schoolMaterials: "Materialak", + schoolAddMaterial: "Gehitu materiala", + schoolNoMaterials: "Oraindik ez dago materialik.", + bankYourSchoolBalance: "Zure eskolako irabaziak", + schoolProgress: "Aurrerapena", + schoolProgressMark: "Markatu osatutzat", + schoolProgressDone: "Osatuta", + schoolExams: "Azterketak", + schoolExamCreate: "Sortu azterketa", + schoolExamTitlePlaceholder: "Azterketaren izenburua", + schoolExamHelp: "Galdera bat lerroko: Galdera? ; aukera ; aukera zuzena* ; aukera", + schoolExamQuestionsPlaceholder: "Zer da SSB? ; Datu-base bat ; P2P protokolo bat* ; Nabigatzaile bat", + schoolExamSubmit: "Bidali erantzunak", + schoolExamScore: "Zure nota", + schoolNoScoresYet: "Oraindik ez dago emaitzarik.", + schoolLessonUnit: "Unitatea / fasea", + schoolLessonUnitPlaceholder: "adib. 1. fasea", + schoolLessonOrder: "Ordena", + profileClearnetSchoolLabel: "Eskola", + schoolStartDate: "Hasiera data", + schoolSessionDate: "Saioaren data", + schoolCourseChat: "Ikastaroaren txata", + agendaFilterSchool: "ESKOLA", + agendaFilterCampaigns: "KANPAINAK", + agendaFilterLogistics: "LOGISTIKA", + schoolCourseType: "Ikastaro mota", + schoolTypeOpen: "IREKIA", + schoolTypePaid: "ORDAINPEKOA", + schoolTypeInvite: "GONBIDAPENEZ SOILIK", + schoolLessonLocked: "Ikasgai zifratua — eskatu irakasleari ikastaroaren gakoa.", + schoolGrantAccess: "Bidali ikastaroaren gakoa", + schoolKeySent: "Gakoa bidalita", + schoolOngoing: "Abian", + schoolFilterApplied: "IZENA EMANDA", + schoolFilterOpen: "IREKIAK", + schoolFilterFavorites: "GOGOKOAK", + schoolPendingPayment: "Ordainketa zain", + schoolPendingStudents: "Izen-emateak zain", + schoolViewBill: "Ikusi faktura", + schoolPaymentNote: "Izena emateak ECO faktura bat (transferentzia bat) sortzen du. Zure izen-ematea transferentzia sinatzean berresten da.", + schoolCloseCourse: "Itxi ikastaroa", + schoolReopenCourse: "Ireki berriro ikastaroa", + schoolDescription: "Irakatsi eta ikasi ikastaroak zure sarean.", + schoolFilterAll: "GUZTIAK", + schoolFilterMine: "NIREAK", + schoolFilterTop: "TOP", + schoolFilterRecent: "AZKENAK", + schoolCreateButton: "Sortu Ikastaroa", + schoolCreateSectionTitle: "Sortu ikastaro bat", + schoolUpdateSectionTitle: "Eguneratu ikastaroa", + schoolCourseTitlePlaceholder: "Zure ikastaroaren izena", + schoolCourseDescriptionPlaceholder: "Zer irakatsiko duzu ikastaro honetan?", + schoolCourseImage: "Ikastaroaren irudia (gehienezko tamaina: 75MB)", + schoolTags: "Etiketak", + schoolTagsPlaceholder: "Idatzi etiketak komaz bereizita", + schoolPrice: "Prezioa (ECO)", + schoolVisibility: "Ikusgaitasuna", + schoolVisibilityPublic: "Publikoa", + schoolVisibilityInvite: "Gonbidapenez", + schoolStatus: "Egoera", + schoolClosed: "Itxita", + schoolFree: "Doan", + schoolCreate: "Sortu ikastaroa", + schoolUpdate: "Eguneratu ikastaroa", + schoolSearchPlaceholder: "Bilatu hemen: Eskola...", + schoolSortTitle: "Izenburua", + schoolNoCourses: "Ez da ikastarorik aurkitu.", + schoolStudents: "Ikasleak", + schoolTeacher: "Irakaslea", + schoolCreatedAt: "SORTUA", + schoolLessons: "Ikasgaiak", + schoolNoLessons: "Oraindik ez dago ikasgairik.", + schoolEnrollToSee: "Eman izena ikastaro honetako ikasgaiak ikusteko.", + schoolEnrollButton: "IZENA EMAN", + schoolUnenrollButton: "Utzi ikastaroa", + schoolInviteRequired: "Ikastaro hau gonbidapenez da.", + schoolTeacherPanel: "Irakaslearen panela", + schoolStudentPanel: "Ikaslearen panela", + schoolAddLesson: "Ikasgaia sortu", + schoolLessonTitlePlaceholder: "Ikasgaiaren izenburua", + schoolLessonTextPlaceholder: "Deskribatu ikasgai honek lantzen dituen nozioak...", + schoolInviteStudents: "Gonbidatu ikasleak", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "GONBIDATU", + schoolInvited: "Gonbidatuak", + schoolIssueCertificate: "Ziurtagiria sortu", + schoolCertified: "Ziurtatua", + schoolCertificateTextPlaceholder: "Ziurtagiriaren oharra (aukerakoa)", + schoolLessonOpen: "Ikasgaia ireki", + schoolCertificatePdf: "PDF", + schoolCertificates: "Ziurtagiriak", + schoolNoCertificates: "Oraindik ez da ziurtagiririk eman.", + schoolEditButton: "Editatu", + schoolDeleteButton: "Ezabatu", + typeSchool: "ESKOLA", + schoolCourseLabel: "IKASTAROA", + emergencyLabel: "LARRIALDIA", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Ikastaroak", + statsSchoolLesson: "Ikasgaiak", + statsSchoolEnroll: "Izen-emateak", + statsSchoolCertificate: "Eskolako ziurtagiriak", + modulesSchoolLabel: "Eskola", + modulesSchoolDescription: "Ezagutza trukatzeko modulua.", + welcomeStepUxAction: "Gorde ikuspegia", + welcomeStepWorkflowTitle: "Zer erabilera duzu buruan OASISerako?", + welcomeStepWorkflowText: "Oasisek modulu asko ditu. Aukeratu zuri egokitzen zaizun konfigurazioa eta erabilgarriak bakarrik geratuko dira aktibatuta. Nahi duzunean doi dezakezu Moduluak atalean.", + welcomeStepWorkflowAction: "Gorde konfigurazioa", + welcomeWorkflow_full: "Lehenetsia", chatRateLimitTitle: "Mezu gehiegi", chatRateLimitMessage: "Gela honek ordu batean onartzen duen mezu mugara iritsi zara. Saiatu geroago.", chatMessagePlaceholder: "Idatzi zure mezua...", chatNoMessages: "Oraindik mezurik ez.", chatTitlePlaceholder: "Txataren izenburua", chatDescriptionPlaceholder: "Txataren deskribapena", - chatTagsPlaceholder: "etiketa1, etiketa2", + chatTagsPlaceholder: "Idatzi etiketak komaz bereizita", chatImageLabel: "Aukeratu irudi-fitxategi bat (.jpeg, .jpg, .png, .gif)", chatAuthor: "Egilea", chatAddFavorite: "Gogokoen artean gehitu", chatRemoveFavorite: "Gogokoenetatik kendu", chatPM: "MP", chatStatusLabel: "Egoera", + chatTypeLabel: "Txat mota", chatCategoryLabel: "Kategoria", gamesTitle: "Jokoak", gamesDescription: "Aurkitu eta jokatu joko batzuk.", @@ -3156,22 +4162,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Bota txanpon bat eta apostatu aurpegian edo buztanean. Zenbat zorte duzu?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "Goitik ikusitako billarra. Egin klik zuzentzeko eta eutsi sakatuta indarra kargatzeko. Sartu bola guztiak ahalik eta jaurtiketa gutxienekin.", + gamesArtilleryTitle: "Artilleria", + gamesArtilleryDesc: "Zuzendu zure kanoia, kontuan hartu haizea eta jo jomuga. 5 txanda: jaurtiketa gutxien egiten duenak irabazten du.", + gamesLabyrinthTitle: "Labirintoa", + gamesLabyrinthDesc: "Ihes egin labirintotik mugimenduak agortu baino lehen. Maila bakoitza handiagoa eta zailagoa da.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Jan puntu guztiak eta saihestu mamuak. Mamuak denbora errealean mugitzen dira — ez gelditu!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "Pendulu soinuduna", + gamesAudioPendulumDesc: "Fisika kaotikoaren simulagailua, denbora errealeko audio-sintesiarekin. Abiadura angeluarrak maiztasun bihurtzen dira, eta gailurrak perkusio-kolpe. Ez dago berdin soinatzen duten bi simulaziorik.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Nahastu bisualak zuzenean bi platerekin: aukeratu eredu bat bakoitzarentzat, doitu abiadura, kolorea eta zooma, nahastu biak eta jaurti distirak erritmora. Markatu tempoa edo utzi mikrofonoak pultsua eramaten.", + gamesTetrisDesc: "Erortzen diren bloke klasikoak. Osatu lerroak puntuak lortzeko. Zenbat iraungo duzu?", + gamesMinesweeperTitle: "Minak", + gamesMinesweeperDesc: "Minen ehiza klasikoa. Garbitu taula mailaz igotzeko — sareta handiagoak, mina gehiago, puntuazio handiagoa.", + gamesFilterScoring: "PUNTUAZIOAK", + gamesHallOfFame: "Ospearen aretoa", + gamesHallPlayer: "Jokalaria", + gamesHallScore: "Puntuazioa", + gamesNoScores: "Oraindik ez dago puntuaziorik.", calendarsTitle: "Egutegia", calendarTitle: "Egutegia", modulesCalendarsLabel: "Egutegiak", @@ -3179,15 +4189,15 @@ module.exports = { typeCalendar: "EGUTEGIAK", calendarCreate: "Egutegia sortu", calendarUpdate: "Eguneratu", - calendarDelete: "Ezabatu", calendarTitleLabel: "Izenburua", calendarTitlePlaceholder: "Egutegiko izenburua...", calendarStatusLabel: "Egoera", + calendarTypeLabel: "Egutegi mota", calendarStatusOpen: "IREKIA", calendarStatusClosed: "ITXIA", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Epemuga", calendarTagsLabel: "Etiketak", - calendarTagsPlaceholder: "etiketa1, etiketa2...", + calendarTagsPlaceholder: "Idatzi etiketak komaz bereizita", calendarParticipantsLabel: "Parte-hartzaileak", calendarCreated: "Sortua", calendarJoin: "Batu", @@ -3198,11 +4208,11 @@ module.exports = { calendarNotePlaceholder: "Oharra gehitu...", calendarFirstDateLabel: "Data", calendarFirstNoteLabel: "Oharrak", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Tartea", calendarIntervalNone: "Errepikapenik gabe", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Astero", + calendarIntervalMonthly: "Hilero", + calendarIntervalYearly: "Urtero", calendarFormDescription: "Deskribapena", calendarNoDates: "Ez dago datarik.", calendarNoNotes: "Ez dago oharrik.", @@ -3210,6 +4220,8 @@ module.exports = { calendarsDescription: "Aurkitu eta kudeatu zure sareko egutegiak.", calendarMonthPrev: "← Aurrekoa", calendarMonthNext: "Hurrengoa →", + calendarMonthNames: "Urtarrila,Otsaila,Martxoa,Apirila,Maiatza,Ekaina,Uztaila,Abuztua,Iraila,Urria,Azaroa,Abendua", + calendarDayInitials: "Al,Ar,Az,Og,Or,La,Ig", calendarAllSectionTitle: "Egutegiak", calendarRecentSectionTitle: "Egutegi Berriak", calendarFavoritesSectionTitle: "Gogokoak", @@ -3219,9 +4231,9 @@ module.exports = { calendarCreateSectionTitle: "Egutegi berria sortu", calendarUpdateSectionTitle: "Egutegia eguneratu", calendarAddFavorite: "Gogokoen gehitu", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Ezabatu", calendarRemoveFavorite: "Gogokoetatik kendu", - calendarSearchPlaceholder: "Egutegiak bilatu...", + calendarSearchPlaceholder: "Bilatu hemen: Egutegia...", calendarAddEntry: "Gehitu Sarrera", statsCalendar: "Egutegiak", statsCalendarDate: "Egutegi datak", @@ -3233,6 +4245,146 @@ module.exports = { tribeContentAccessDenied: "Sarbidea Ukatua", tribeContentAccessDeniedMsg: "Eduki hau tribu batena da. Kide izan behar zara sartzeko.", tribeViewTribes: "Tribuak Ikusi", + tribeFilesEmpty: "Oraindik ez dago fitxategiaik.", + filesLabel: "Fitxategiaak", + profileClearnetFilesLabel: "Fitxategiaak", + filesTitle: "Fitxategiaak", + filesDescription: "Esploratu eta kudeatu fitxategiaak zure sarean.", + fileAllSectionTitle: "Fitxategiaak", + fileMineSectionTitle: "Zure Fitxategiaak", + fileRecentSectionTitle: "Azken Fitxategiaak", + fileTopSectionTitle: "Fitxategia Onenak", + fileFavoritesSectionTitle: "Gogokoak", + fileFilterAll: "DENAK", + fileFilterMine: "NIREAK", + fileFilterRecent: "AZKENAK", + fileFilterTop: "ONENAK", + fileFilterFavorites: "GOGOKOAK", + fileCreateSectionTitle: "Fitxategiaa Igo", + fileUpdateSectionTitle: "Fitxategiaa Eguneratu", + fileCreateButton: "Fitxategiaa Igo", + fileUpdateButton: "Eguneratu", + fileAddFavoriteButton: "Gogokoetara Gehitu", + fileRemoveFavoriteButton: "Gogokoetatik Kendu", + fileFileLabel: "Aukeratu fitxategi bat (max: 75MB)", + calendarFilterRecent: "AZKENAK", + calendarFilterAll: "GUZTIAK", + calendarFilterMine: "NIREAK", + calendarFilterFavorites: "GOGOKOAK", + calendarFilterOpen: "IREKITA", + calendarFilterClosed: "ITXITA", + padFilterRecent: "AZKENAK", + padFilterAll: "GUZTIAK", + padFilterMine: "NIREAK", + padFilterOpen: "IREKITA", + padFilterClosed: "ITXITA", + chatFilterRecent: "AZKENAK", + chatFilterAll: "GUZTIAK", + chatFilterMine: "NIREAK", + chatFilterFavorites: "GOGOKOAK", + chatFilterOpen: "IREKITA", + chatFilterClosed: "ITXITA", + licenseLabel: "Lizentzia", + phoneTitle: "Telefonoa", + phoneDescription: "Kudeatu muturretik muturrera zifratutako zure bideodeiak.", + phoneUnavailable: "Deiak ez daude erabilgarri gailu honetan.", + phoneCallButton: "Deitu", + profileVisibilityPhone: "Telefono-zenbakia", + phoneNumberUnknown: "Zure sareko inork ez du {number} zenbakia.", + phoneNumberChoose: "Egiaztatu nori deitu nahi diozun {number} zenbakira:", + phoneAnswer: "Erantzun", + phoneSpeaker: "Bozgorailua", + phoneEarpiece: "Entzungailua", + phoneReject: "Baztertu", + phoneHangup: "Eseki", + phoneMute: "Isildu", + phoneUnmute: "Mikrofonoa aktibatu", + phoneCameraOn: "Kamera on", + phoneCameraOff: "Kamera off", + settingsPhoneVideo: "Bideoa", + settingsPhoneVideoOn: "Deietan eta geletan baimenduta", + settingsPhoneVideoOff: "Audioa bakarrik", + phoneSilence: "Ez entzun", + phoneUnsilence: "Berriro entzun", + phoneCalling: "Deitzen:", + phoneIsCalling: "deitzen ari zaizu", + notifyNewPmFrom: "MP berria:", + notifyNewPamFrom: "PaM berria:", + notifyUpdateLabel: "Eguneraketa", + updateInstalledLabel: "Instalatutako bertsioa", + updateAvailableLabel: "Bertsio erabilgarria", + updateChangesLabel: "Aldaketak", + updateFilesLabel: "Fitxategiak", + updateOriginLabel: "Jatorria", + phoneRecording: "PaM grabatzen honentzat:", + phoneLeaveMessage: "Inork ez du erantzun: utzi zure mezua seinalearen ondoren.", + phoneLeaveMessageBusy: "Beste dei batean dago: utzi zure mezua seinalearen ondoren.", + phoneLeaveMessageUnreachable: "Ezin izan da harremanetan jarri: utzi zure mezua seinalearen ondoren.", + phoneConnecting: "Konektatzen:", + phoneSendPam: "PaM bidali", + phoneCancel: "Utzi", + phoneSearchPlaceholder: "Bilatu Telefonoan...", + phonePamTitle: "Audio-grabazioak", + phoneNew: "Berria", + phoneDelete: "Ezabatu", + phoneNoPams: "Ez duzu audio-grabaziorik.", + phoneHistoryTitle: "Historia", + phoneLatestTitle: "Azken deiak", + phoneCreateButton: "Dei berria", + phoneLimitsHint: "Dei bakoitzeko 7 bizilagun gehienez; bat baino gehiagorekin dei bateratua da.", + phoneErrorPub: "Ezin zaio pub bati deitu.", + phoneJointCall: "Dei bateratua", + phoneWith: "Norekin", + phoneResult: "Emaitza", + phoneWhen: "Noiz", + phoneDuration: "Iraupena", + phoneOutgoing: "Irteerakoa", + phoneIncoming: "Sarrerakoa", + phoneNoHistory: "Oraindik ez dago erregistrorik.", + phoneOutcomeEnded: "Erantzunda", + phoneOutcomeMissed: "Galdua", + phoneOutcomeRejected: "Baztertua", + phoneOutcomeNoanswer: "Erantzunik gabe", + phoneOutcomeCancelled: "Bertan behera utzia", + phoneOutcomePam: "PaM bidalia", + phoneOutcomeFailed: "Huts egin du", + phoneDialAdd: "Gehitu", + phoneDialBack: "Ezabatu", + phoneDialClear: "Garbitu", + phoneDialPlaceholder: "Zenbakia edo Oasis IDa", + phoneDialInhabitants: "Biztanleak", + phoneDialRemove: "Kendu", + phoneOwnNumber: "Hori zure zenbakia da.", + phoneSettingsDesc: "Aukeratu nork deitu diezazukeen.", + phoneDnd: "Ez molestatu", + phoneOpen: "Irekia", + phoneRecipientRefuses: "Deitu nahi diozun biztanleak ez du deirik onartzen.", + phoneRefusedNotice: "Biztanle hauek ez dute deirik onartzen eta ez zaie deitu:", + fileEmptyError: "Fitxategia hutsik dago. Aukeratu beste fitxategi bat.", + fileTitleLabel: "Izenburua", + fileTitlePlaceholder: "Izenburua", + fileDescriptionLabel: "Deskribapena", + fileDescriptionPlaceholder: "Deskribapena", + fileTagsLabel: "Etiketak", + fileTagsPlaceholder: "Idatzi etiketak komaz bereizita", + fileSizeLabel: "Tamaina", + fileDownloadButton: "DESKARGATU", + fileDetailsButton: "XEHETASUNAK", + fileNoFile: "Fitxategia fitxategirik ez", + fileSearchPlaceholder: "Bilatu hemen: Fitxategiaak...", + fileSearchButton: "Bilatu", + fileSortRecent: "Berrienak", + fileSortOldest: "Zaharrenak", + fileSortTop: "Bozkatu enak", + fileNoMatch: "Ez dago fitxategia bat datorrenik.", + fileUpdatedAt: "Eguneratua", + typeFile: "FITXATEGIAAK", + modulesFilesLabel: "Fitxategiak", + modulesFilesDescription: "Fitxategiak aurkitu eta kudeatzeko modulua.", + tribeSectionFiles: "FITXATEGIAAK", + fileDescription: "Deskribapena", + fileDownload: "Deskargatu", + fileTypeLabel: "Mota", torrentsTitle: "Torrentak", torrentsDescription: "Esploratu eta kudeatu torrentak zure sarean.", torrentAllSectionTitle: "Torrentak", @@ -3258,12 +4410,35 @@ module.exports = { torrentDescriptionLabel: "Deskribapena", torrentDescriptionPlaceholder: "Deskribapena", torrentTagsLabel: "Etiketak", - torrentTagsPlaceholder: "Sartu etiketak komaz bereizita", + torrentTagsPlaceholder: "Idatzi etiketak komaz bereizita", torrentSizeLabel: "Tamaina", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Torrenta sortu", + torrentFromContentMissing: "Fitxategia oraindik ez dago nodo honetan eskuragarri. Saiatu geroago.", + torrentDownloads: "Deskargak", + torrentFetchOasis: "Oasisen deskargatu", + torrentSaveFile: "Fitxategia gorde", + torrentSeeds: "hazi", + seedsLabel: "Haziak", + seedAction: "Lagundu", + torrentSourceFile: "Fitxategia", + torrentStateQueued: "Ilaran", + torrentStateProbing: "Haziak bilatzen", + torrentStateDownloading: "Deskargatzen", + torrentStateDone: "Osatuta", + torrentStateFailed: "Huts egin du", + torrentStateNoSource: "Ez dago hazirik konektatuta, zain", + torrentStateCancelled: "Bertan behera utzita", + torrentNoDownloads: "Oraindik ez dago deskargarik.", + torrentOriginalSection: "Jatorrizkoa", + torrentCancel: "Utzi", + torrentDownloadButton: "DESKARGATU", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "XEHETASUNAK", torrentNoFile: "Torrent fitxategirik ez", noTorrents: "Ez da torrentik aurkitu.", - torrentSearchPlaceholder: "Bilatu izenburua, etiketak, egilea...", + noFiles: "Ez da fitxategiaik aurkitu.", + torrentSearchPlaceholder: "Bilatu hemen: Torrentak...", torrentSearchButton: "Bilatu", torrentSortRecent: "Berrienak", torrentSortOldest: "Zaharrenak", @@ -3271,10 +4446,12 @@ module.exports = { torrentNoMatch: "Ez dago torrent bat datorrenik.", torrentUpdatedAt: "Eguneratua", statsTorrent: "Torrentak", + statsFile: "Fitxategiak", typeTorrent: "TORRENTAK", modulesTorrentsLabel: "Torrentak", modulesTorrentsDescription: "Torrentak aurkitu eta kudeatzeko modulua.", favoritesFilterTorrents: "TORRENTAK", + favoritesFilterFiles: "FITXATEGIAK", favoritesFilterMarket: "MERKATUA", favoritesFilterEvents: "EKITALDIAK", favoritesFilterForum: "FOROA", @@ -3288,15 +4465,17 @@ module.exports = { favoritesFilterShopProducts: "DENDAKO ARTIKULUAK", tribeSectionTorrents: "TORRENTAK", tribeCreateTorrent: "Torrenta Igo", + tribeCreateFile: "Fitxategia igo", settingsWishTitle: "Nahia", - settingsWishDesc: "Konfiguratu zure Avatarraren nahia.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Eduki guztietara nola sartzen zaren eta Oasis sarean duzun esperientzia zehazten du.", + settingsWishWhole: "Multibertsoa", + settingsWishMutuals: "Elkar laguntza soilik", + settingsWishOnlyLan: "LAN soilik", + settingsWishLocal: "Lokala", settingsPmVisibilityTitle: "Mezu Pribatuak", settingsPmVisibilityDesc: "Konfiguratu sarean izan nahi duzun mezu pribatuen esposizio maila.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Multibertsoa", + settingsPmVisibilityMutuals: "Elkar laguntza soilik", pmMutualNotice: "Bidalketa UX babes bat da. Jasotzea arreta-iragazkia.", inhabitantsPendingFollowsTitle: "Onespen zain dauden eskaerak", inhabitantsPendingAccept: "Onartu", @@ -3340,7 +4519,7 @@ module.exports = { tribeGovNoRevocations: "Oraindik ez dago errebokatzerik", tribeGovNoHistorical: "Oraindik ez dago erregistrorik", logsTitle: "Egunkaria", - logsDescription: "Erregistratu zure esperientzia sarean.", + logsDescription: "Eraman zure esperientzien egunkari pribatua.", logsViewTitle: "Egunkaria", logsColumnType: "Mota", logsView: "Ikusi", @@ -3349,7 +4528,7 @@ module.exports = { logsCreateTitle: "Sarrera sortu", logsWriteButton: "Idatzi", logsTextPlaceholder: "Deskribatu zure esperientziak...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "IA laguntzailea", logsModeManual: "Eskuz", logsModeAI: "AA", logsColumnLog: "Egunkaria", @@ -3368,19 +4547,20 @@ module.exports = { logsExportOne: "Esportatu erregistroa", logsViewDetails: "Ikusi xehetasunak", logsGenerateButton: "Sortu testua", - logsSearchText: "Bilatu egunkarietan...", + logsSearchText: "Bilatu hemen: Egunkaria...", logsSearchAnyType: "Edozein mota", logsSearchButton: "Bilatu", logsEmpty: "Oraindik sarrerarik ez.", + logsNothingNew: "Ez dago erregistratzeko ekintza berririk.", modulesLogsLabel: "Egunkaria", - modulesLogsDescription: "Modulua zure esperientziak (AA laguntzailearen bidez) erregistratzeko.", + modulesLogsDescription: "Zure esperientzien egunkari pribatua eramateko modulua.", statsLogsTitle: "Egunkaria", typeLog: "EGUNKARIA", larpTitle: "L.A.R.P.", larpDescription: "Esperimentazio kolaboratiborako zuzeneko rol-joko geruza bat.", larpNoHousesMatch: "Ez dago bilaketarekin bat datorren etxerik.", - larpSearchPlaceholder: "Bilatu etxeak...", + larpSearchPlaceholder: "Bilatu hemen: L.A.R.P....", larpAllHouses: "Etxeak:", larpFilterRuling: "AGINTZEN", larpFilterHouses: "ETXEAK", @@ -3395,121 +4575,120 @@ module.exports = { larpPostPlaceholder: "Zer du etxe honek esateko?", larpPostSubmit: "Argitaratu", larpCycleLabel: "Zikloa:", - audioBackToBcs: "Back to BCS", + audioBackToBcs: "Itzuli BCSra", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeCompositionEmpty: "This audio does not include a stored blockchain composition.", - audioTranscodeDetailDescription: "Decode the embedded payload and the original blockchain composition map.", - audioTranscodeDetailTitle: "Transcode", - audioTranscodeStegoEmpty: "(none)", - audioTranscodeStegoMessage: "TEXT", - audioTranscodeStegoNotFound: "No steganographic payload could be decoded from this audio.", - audioTranscodeStegoOasisId: "By", - audioTranscodeStegoTimestamp: "Generated at", + audioTranscodeButton: "TRANSKODETU", + audioTranscodeCompositionEmpty: "Audio honek ez du blockchain-konposizio gordetakorik.", + audioTranscodeDetailDescription: "Deskodetu txertatutako karga eta blockchain-aren jatorrizko konposizio-mapa.", + audioTranscodeDetailTitle: "Transkodetu", + audioTranscodeStegoEmpty: "(bat ere ez)", + audioTranscodeStegoMessage: "TESTUA", + audioTranscodeStegoNotFound: "Ezin izan da audio honetatik karga esteganografikorik deskodetu.", + audioTranscodeStegoTimestamp: "Noiz sortua", audioTranscodeStegoUnknown: "—", - bankExchangeEcoTaxAnnual: "ECOin Taxes (annual)", - bankExchangeEcoTaxMonthly: "ECOin Taxes (monthly)", - bankOverviewYourTaxes: "Your taxes", - bankRulesAlpha: "Alpha (max share of pub balance per epoch)", + bankExchangeEcoTaxAnnual: "ECOin zergak (urtekoak)", + bankExchangeEcoTaxMonthly: "ECOin zergak (hilekoak)", + bankOverviewYourTaxes: "Zergak guztira", + bankRulesAlpha: "Alpha (pubaren saldoaren gehieneko zatia aro bakoitzeko)", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", - bankRulesArchTaxRate: "Rate", - bankRulesArchTaxTitle: "ARCH Tax", - bankRulesCapPerEpoch: "Cap per epoch (absolute)", - bankRulesCapUser: "Cap per user per epoch", - bankRulesCarbonFactor: "Carbon factor", + bankRulesArchTaxNote: "ARCH tasak sarearen artxiboa hazteko eta mantentzeko epe luzeko kostua islatzen du: zenbat eta zaharragoak izan zure datuak sarean, orduan eta handiagoa izango da mantentze-kostuan duzun zatia.", + bankRulesArchTaxRate: "Tarifa", + bankRulesArchTaxTitle: "ARCH tasa", + bankRulesCapPerEpoch: "Aro bakoitzeko muga (absolutua)", + bankRulesCapUser: "Erabiltzaile bakoitzeko muga aro bakoitzeko", + bankRulesCarbonFactor: "Karbono-faktorea", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", - bankRulesChipTitle: "ECO Tax chip color band", + bankRulesChipNote: "Elementu bakoitzaren txipa sarean inoiz ikusitako mezurik handienarekiko koloreztatzen da eta bizilagun aktiboen kopuruak leuntzen du — sare handiago batek karga banatzen du eta elementu gehiago eramaten ditu banda berdera.", + bankRulesChipTitle: "ECO tasaren txiparen kolore-banda", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", - bankRulesEcoTaxRate: "Rate", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesEpochKind: "Epoch granularity", - bankRulesFloor: "Floor per user (gross)", - bankRulesGraceDays: "Claim grace period", + bankRulesEcoTaxNote: "ECO tasa OBU gordinetik kentzen da aroaren esleipena kalkulatzen denean. Kendutako ECOak ez dira aro honetan birbanatzen — pubaren saldoan geratzen dira eta hurrengo aroaren funtsa elikatzen dute reserveMin / alpha mugen bidez.", + bankRulesEcoTaxRate: "Tarifa", + bankRulesEcoTaxTitle: "ECO tasa", + bankRulesEpochKind: "Aroaren granulartasuna", + bankRulesFloor: "Erabiltzaile bakoitzeko gutxienekoa (gordina)", + bankRulesGraceDays: "Eskaeraren grazia-epea", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", - bankRulesKarmaTitle: "Karma (user engagement score)", + bankRulesKarmaNote: "Karma zure ekintzetatik ateratzen da (argitalpenak, botoak, parlamentuan/epaitegietan parte hartzea, etab.), denborarekin ahultzen den faktore batekin, zure jarioak sortutako karbono-gramoak kenduta.", + bankRulesKarmaTitle: "Karma (erabiltzailearen konpromiso-puntuazioa)", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", - bankRulesPoolTitle: "Monthly pool", - bankRulesReserveMin: "Reserve minimum (kept in pub balance)", + bankRulesPoolTitle: "Hileko funtsa", + bankRulesReserveMin: "Gutxieneko erreserba (pubaren saldoan gordea)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", - bankRulesShareTitle: "Per-user share", + bankRulesShareTitle: "Erabiltzaile bakoitzeko zatia", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankRulesWMax: "Maximum weight (w_max)", - bankRulesWMin: "Minimum weight (w_min)", - bankTaxes: "Taxes", - bankTaxesAnnualEcoin: "ECO Tax (annual)", - bankTaxesArchTaxAnnual: "ARCH Tax (annual)", - bankTaxesArchTaxFirstBlock: "Your first block age", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxLifetime: "ARCH Tax (lifetime)", - bankTaxesArchTaxMonthly: "ARCH Tax (monthly)", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesArchTaxRate: "Rate", - bankTaxesArchTaxTitle: "ARCH Tax", - bankTaxesArchTaxUserAmount: "Your ARCH tax (price to return)", - bankTaxesEcoTaxLifetime: "ECO Tax (lifetime)", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesLookupAuthor: "Author", - bankTaxesLookupButton: "Inspect", - bankTaxesLookupCarbon: "Carbon footprint", - bankTaxesLookupEcoin: "ECO Tax", - bankTaxesLookupNotFound: "No block found in your local feed with that ID.", - bankTaxesLookupPlaceholder: "Block ID (e.g. %abc...=.sha256)", - bankTaxesLookupSize: "Size", - bankTaxesLookupTitle: "Inspect a block", - bankTaxesLookupType: "Type", - bankTaxesMonthlyEcoin: "ECO Tax (monthly)", - bankTaxesRate: "Rate", - bankTaxesSpan: "Sampling span", - bankTaxesTotalAnnual: "Total (annual)", - bankTaxesTotalBlocks: "Total blocks", - bankTaxesTotalBytes: "Total bytes", - bankTaxesTotalCarbon: "Total carbon", - bankTaxesTotalEcoin: "Total ECO tax (lifetime)", - bankTaxesTotalLifetime: "Total (lifetime)", - bankTaxesTotalMonthly: "Total (monthly)", - bankTaxesTypesApply: "Set my taxes", - bankTaxesTypesLabel: "Select which taxes you want to pay", - bankTaxesUserAmount: "Your ECO tax (price to return)", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - blockchain: "BlockExplorer", - blockchainBackToBlockexplorer: "Back to blockexplorer", - blockchainBlockNotAvailable: "This block is not available in your local feed. It may belong to a peer you are not yet replicating from.", - courtsMediatorsLabel: "Mediators", - ecoTaxLabel: "ECO Tax", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - melodyAllEmpty: "No BCS compositions from other inhabitants yet.", - melodyByLabel: "By", - melodyNoteUnavailable: "block not in your local feed", - melodyUploadBcsButton: "Publish", - melodyUploadBcsNameNote: "The audio will be published with the auto-generated name", - melodyUploadBcsTitle: "Publish", - melodyUploadStegoLabel: "Hidden message (steganography)", - melodyUploadStegoMaxLabel: "max 280 characters", - melodyUploadStegoPlaceholder: "Optional.", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - parliamentRevocationPublish: "Publish Revocation", - statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", - statsPad: "Pads", - statsPadEntry: "Pad entries", - statsTombstoneEmpty: "No tombstones in the network yet.", - typeGameScore: "GAME SCORES", + bankRulesWMax: "Gehieneko pisua (w_max)", + bankRulesWMin: "Gutxieneko pisua (w_min)", + bankTaxes: "Zergak", + bankTaxesAnnualEcoin: "ECO tasa (urtekoa)", + bankTaxesArchTaxAnnual: "ARCH tasa (urtekoa)", + bankTaxesArchTaxFirstBlock: "Zure lehen blokearen adina", + bankTaxesArchTaxFootprintNote: "ARCH tasak sarearen artxiboa hazteko eta mantentzeko kostua islatzen du. Argitaratu zenuen lehen blokearen eta sareko bloke berrienaren arteko denbora-tartetik kalkulatzen da — zenbat eta denbora gehiago egon zure historia artxiboan, orduan eta handiagoa izango da mantentze-kostuan duzun zatia.", + bankTaxesArchTaxLifetime: "ARCH tasa (bizitza osoa)", + bankTaxesArchTaxMonthly: "ARCH tasa (hilekoa)", + bankTaxesArchTaxNoteBold: "ARCH tasa zuzenean murrizteari", + bankTaxesArchTaxNoteIntro: "Zure ARCH tasa sareak zure OBUaren gainetik sortzen duen soberakinetik kentzen da; OBUaren oinarrizko balioa minimo aldaezin gisa finkatzen da. Tasa ez da inoiz kentzen bizilagun bakoitzari OBU gisa dagokion zenbateko finkotik. Kendutako funtsek aberastasuna birbanatzeko algoritmoa elikatzen dute, eta beste bizilagun batzuengana itzultzen dira haien OBU eskaeren bidez. Bizilagun berrienek eta artxibatze-, erreplikazio- eta mantentze-lanetan berariaz aritzen direnek ziurrenik izango dute zure izenean sarearen artxiboa osasuntsu mantentzeko zereginen bat. Edo ", + bankTaxesArchTaxNoteOutro: " ekin diezaiokete, inausketa-akordioen, datu erredundanteen desbikoizketaren eta mantentze-azpiegitura partekatuaren bidez, eta sarearen adostasunak haien ekarpen jarraitua eska dezake.", + bankTaxesArchTaxRate: "Tarifa", + bankTaxesArchTaxTitle: "ARCH tasa", + bankTaxesArchTaxUserAmount: "Zure ARCH tasa (itzuli beharreko prezioa)", + bankTaxesEcoTaxLifetime: "ECO tasa (bizitza osoa)", + bankTaxesEcoTaxTitle: "ECO tasa", + bankTaxesLookupAuthor: "Egilea", + bankTaxesLookupButton: "Ikuskatu", + bankTaxesLookupCarbon: "Karbono-aztarna", + bankTaxesLookupEcoin: "ECO tasa", + bankTaxesLookupNotFound: "Ez da ID hori duen blokerik aurkitu zure jario lokalean.", + bankTaxesLookupPlaceholder: "Blokearen IDa (adib. %abc...=.sha256)", + bankTaxesLookupSize: "Tamaina", + bankTaxesLookupTitle: "Ikuskatu bloke bat", + bankTaxesLookupType: "Mota", + bankTaxesMonthlyEcoin: "ECO tasa (hilekoa)", + bankTaxesRate: "Tarifa", + bankTaxesSpan: "Lagintze-tartea", + bankTaxesTotalAnnual: "Guztira (urtekoa)", + bankTaxesTotalBlocks: "Blokeak guztira", + bankTaxesTotalBytes: "Byteak guztira", + bankTaxesTotalCarbon: "Karbonoa guztira", + bankTaxesTotalEcoin: "ECO tasa guztira (bizitza osoa)", + bankTaxesTotalLifetime: "Guztira (bizitza osoa)", + bankTaxesTotalMonthly: "Guztira (hilekoa)", + bankTaxesTypesApply: "Ezarri nire zergak", + bankTaxesTypesLabel: "Hautatu zein zerga ordaindu nahi dituzun", + bankTaxesUserAmount: "Zure ECO tasa (itzuli beharreko prezioa)", + bankTaxesUserNoteChips: "Elementu bakoitzaren ondoan agertzen den ECO tasaren txipak kolorez aldatzen du (berdea / horia / gorria), elementuaren tamainaren arabera sarean inoiz ikusitako blokerik handienarekin alderatuta, bizilagun aktiboen kopuruak leunduta — sare solar-punk handiago batek karga banatzen du eta elementu gehiago eramaten ditu banda berdera.", + bankTaxesUserNoteOtherParams: "ECO tasa gaur egun bloke bakoitzaren karbono-aztarnatik ateratzen da, baina denborarekin beste parametro batzuk gehi ditzake — erreplikazioan gastatutako energia, nodoen arteko biltegiratze erredundantea, blob-en banda-zabalera, deszifratzearen kostu konputazionala, lotutako transakzioen meatzaritza-aztarna edo sareak baloratzea adosten duen beste edozein karga neurgarri.", + blockchain: "Bloke-esploratzailea", + blockchainBackToBlockexplorer: "Itzuli bloke-esploratzailera", + blockchainBlockNotAvailable: "Bloke hau ez dago eskuragarri zure jario lokalean. Oraindik erreplikatzen ez duzun nodo batena izan daiteke.", + courtsMediatorsLabel: "Bitartekariak", + ecoTaxLabel: "ECO tasa", + gamesHallDate: "Data", + gamesNewRecord: "Errekor berria!", + indexingRefreshNote: "Orri hau 10 segundoro birkargatzen da.", + indexingMessage: "Oasis nodoen sare banatua sinkronizatzen ari da. Itxaron, mesedez.", + indexingTitle: "Sinkronizatzen", + melodyAllEmpty: "Oraindik ez dago beste bizilagunen BCS konposiziorik.", + melodyNoteUnavailable: "blokea ez dago zure jario lokalean", + melodyUploadBcsButton: "Argitaratu", + melodyUploadBcsNameNote: "Audioa automatikoki sortutako izenarekin argitaratuko da", + melodyUploadBcsTitle: "Argitaratu", + melodyUploadStegoLabel: "Mezu ezkutua (esteganografia)", + melodyUploadStegoMaxLabel: "gehienez 280 karaktere", + melodyUploadStegoPlaceholder: "Aukerakoa.", + padsNoItems: "Ez da padik aurkitu.", + padVersionHistory: "Bertsioen historia", + padAttributionView: "Egilearen arabera", + padVersionView: "Ikusi", + parliamentRevocationPublish: "Argitaratu ezeztapena", + statsChat: "Txatak", + statsChatMessage: "Txat-mezuak", + statsGameScore: "Jokoak", + statsWiki: "Wiki orriak", + statsPad: "Padak", + statsPadEntry: "Pad-sarrerak", + statsTombstoneEmpty: "Oraindik ez dago ezabaketarik (tombstone) sarean.", + typeGameScore: "JOKO-PUNTUAZIOAK", bankTaxesLookupNote: "Sartu bloke-ID bat zure jario lokalean bilatzeko.", bankTaxesUserNoteChipsClick: "Egin klik edozein chiptan bere blokea blockexplorer-en ikuskatzeko.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3524,6 +4703,14 @@ module.exports = { settingsReplicationTitle: "Erreplikazioa", settingsReplicationDesc: "Konfiguratu zenbat jauzi jarraitu behar dituen zure kideak zure jariotik abiatuz edukia erreplikatzean.", settingsReplicationHopsLabel: "Jauziak", + settingsBlobCacheTitle: "Multimedia cachea", + settingsBlobCacheDesc: "Beste biztanleen edukia gailu honetan gordetzen da. Aukeratu zenbat leku har dezakeen.", + settingsBlobCacheUsage: "Uneko erabilera", + settingsBlobCacheFiles: "fitxategi", + settingsBlobCacheLimit: "Muga", + settingsBlobCacheUnlimited: "Mugarik gabe", + settingsBlobCacheCleanNow: "Garbitu orain", + settingsBlobCacheCleaned: "{n} fitxategi ezabatuta, {size} askatuta.", aiApproveTagsLabel: "Etiketak (komaz bereizita)", aiApproveTagsPlaceholder: "adib. oasis, gobernantza, ekologia", aiApproveRatingLabel: "Balorazioa", @@ -3551,8 +4738,6 @@ module.exports = { larpInviteBannerHint: "Partekatu kode hau ACADEMIA-n dagoen norbaitekin. 30 zikloko edo erabilera bakar baten ostean iraungitzen da.", larpTestRankingTitle: "Etxe bakoitzeko puntuazioa", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Etxearen gonbidapen-kodea", - invitesHouseJoinButton: "Etxera sartu", larpLastAttemptTitle: "Zure azken saiakera", larpLastAttemptHouse: "Etxea", larpLastAttemptWhen: "Noiz", @@ -3570,14 +4755,14 @@ module.exports = { larpBadgeYou: "Zu", larpBadgeRuling: "Agintzen", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Oasis-en zuzeneko rol-joko geruzaren modulua (9 Etxeak).", + modulesLarpDescription: "Zuzeneko rol-jokoaren geruzarako modulua.", larpProfileQ1: "Arazo konplexu baten aurrean, lehenik zer egiten duzu?", larpProfileQ1O1: "Besteekin hitz egin adostasuna lortzeko", larpProfileQ1O2: "Prototipo bat egin probatzeko", larpProfileQ1O3: "Literatura ikertu", larpProfileQ1O4: "Sortzen duen sistema sabotatu", larpProfileQ2: "Zer ematen dio zentzua zure eguneroko lanari?", - larpProfileQ2O1: "Maite ditudan pertsonak defendatu", + larpProfileQ2O1: "Maite ditudan biztanleak defendatu", larpProfileQ2O2: "Zerbait ukigarria sortu", larpProfileQ2O3: "Bizitza sendatu edo zaindu", larpProfileQ2O4: "Hitzak eta ideiak landu", @@ -3644,7 +4829,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Aukeratu nahi duzun UX nabigazio modua zure interfazerako.", uxModeMenus: "Blokeak", - uxModeAINav: "AI", + uxModeChats: "Elkarrizketak", + uxModeFeed: "Mikroblogintza", + feedTrendingTitle: "Joerako Etiketak", + feedActiveTitle: "Biztanle Aktiboak", + cnBrandSub: "Librea · P2P · Federatua", + cnSyncedPeers: "Sinkronizatutako nodoak", + cnLicense: "Lizentzia", + cnNotAccessible: "Edukia ez dago eskuragarri une honetan.", + cnUntitled: "Izenbururik gabe", + cnSearchPlaceholder: "Bilatu…", + cnHubTitle: "Clearnet HUBa", + cnHubDescription: "Oasis sare honetako bizilagunek partekatutako eduki publikoa.", + cnHubEmpty: "Oraindik ez da eduki publikorik partekatu Clearnet-en.", + cnPublicContent: "Edukia", + cnTribesContent: "Tribuak", + cnNetworkContent: "Sarea", + cnCategoryEmpty: "Ez dago edukirik kategoria honetan.", + cnInhabitantEmpty: "Bizilagun honek oraindik ez du edukirik argitaratu Clearnet-en.", + cnPrevPage: "← Aurrekoa", + cnNextPage: "Hurrengoa →", + cnPageOf: "{page}. orria {pages}-tik", + cnMoreInfo: "Informazio gehiago →", + cnOpenCourse: "Ikastaro irekia", + cnAuction: "ENKANTEA", + cnExchange: "TRUKEA", + cnRssDescription: "HUB honetako bizilagunek argitaratuak", + cnKindAudio: "Audioa", + cnKindBlog: "Bloga", + cnKindBookmark: "Laster-marka", + cnKindDocument: "Dokumentua", + cnKindEvent: "Ekitaldia", + cnKindFeed: "Jarioa", + cnKindImage: "Irudia", + cnKindJob: "Lana", + cnKindMarket: "Merkatua", + cnKindPodcast: "Podcasta", + cnKindProject: "Proiektua", + cnKindCourse: "Ikastaroa", + cnKindShop: "Denda", + cnKindTorrent: "Torrenta", + cnKindFile: "Fitxategia", + cnKindVideo: "Bideoa", + cnKindWiki: "Wikia", + cnKindRoom: "Gela", + cnKindMap: "Mapa", + uxModeAINav: "IA", } }; diff --git a/src/client/assets/translations/oasis_fr.js b/src/client/assets/translations/oasis_fr.js index 50ccf57..dfac9d8 100644 --- a/src/client/assets/translations/oasis_fr.js +++ b/src/client/assets/translations/oasis_fr.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "Achats", shopMyOrdersDescription: "Vos commandes d’achat.", shopMyOrdersEmpty: "Vous n’avez pas encore d’achats.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM Vendeur", shopOrderSeller: "Vendeur", shopOrderInvoice: "Facture", shopOrderInvoiceConcept: "Facture", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Accepter", shopOrderReject: "Rejeter", shopOrderMarkShipped: "Marquer expédié", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM Acheteur", shopOrderContractConcept: "Commande boutique", shopOrdersPending: "Commandes en attente", all: "Tous", @@ -28,7 +28,7 @@ module.exports = { recent: "Récents", anonymous: "Anonyme", unnamed: "Anonyme", - authorLabel: "AUTEUR", + authorLabel: "Auteur", delete: "Supprimer", save: "Enregistrer", vote: "Voter", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Avertissement de contenu", invalidPost: "Contenu invalide", invalidPostHint: "Ce message a un texte vide/invalide.", - spreadBy: "Par", spreadChron: "Diffusion", spreaded: "Diffusé", spreadContentUnavailable: "Contenu pas encore disponible (réplication en attente)", filteredByTag: "Filtré par étiquette", filteredByTagTitle: "Filtré par étiquette", feedPublishedSuccess: "Flux publié avec succès !", + feedMastodonFailed: "Publié dans Oasis, mais l'envoi vers Mastodon a échoué.", tribeFeedSent: "Message envoyé avec succès !", tribeContentEncrypted: "Contenu chiffré", tribeTorrentsEmpty: "Pas encore de torrents.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Dernière visite", profileClearnetBookmarksLabel: "Favoris", invitesPort: "Port", - currentlyUnrecheable: "ERREUR !", - peerHostValidation: "IPv4 valide (ex. 192.168.1.100) ou nom d’hôte (ex. pub.example.com)", + peerHostValidation: "IPv4 valide (ex. 192.168.1.100) ou nom d’hôte (ex. pub.solarnethub.com)", peerPortValidation: "Port 1-65535", peerKeyValidation: "Clé publique SSB ed25519 (@<44 caractères base64>=.ed25519)", inboxJobUnsubscribedTitle: "Désinscription de votre offre d’emploi", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Description de la tâche", exportPasswordLabel: "Mot de passe d’export", importPasswordPlaceholder: "Saisissez le mot de passe", - searchCasesPlaceholder: "Rechercher des affaires…", + searchCasesPlaceholder: "Rechercher dans Tribunaux...", shopBuyDeliveryAddressPlaceholder: "Adresse de livraison", shopBuyNotesPlaceholder: "Notes", bankTaxesUserNoteBold: "réduire directement la taxe ECO", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Soutenir l’affaire", courtsSettlementAcceptBtn: "Accepter l’accord", courtsSupportCount: "Soutiens", - courtsVerdictVoteTitle: "Voter sur le verdict", - courtsVerdictVoteLabel: "Vote du verdict", - courtsVerdictVoteAccept: "Accepter", - courtsVerdictVoteReject: "Rejeter", - courtsVerdictVoteSubmit: "Soumettre le vote", reportsSetStatus: "Définir le statut", reportsStatusClosed: "CLÔTURÉ", statsAvgPerInhabitant: "Moy. par habitant", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Avatar", inhabitants: "Habitants", - peersReplicatedFeeds: "Flux répliqués", + peersReplicatedFeeds: "Répliqués", + peersTorColumn: "TOR", + peersTorYes: "Joignable via Tor", + peersTorNo: "Non joignable via Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Version", graphos: "Graphos", graphosDescription: "Carte interactive du réseau autour de toi.", + graphosFederation: "Fédération", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Injoignables", + graphosFedLinks: "Liens mutuels", + graphosFedIsolated: "Isolés", + graphosFedNoData: "Pas de données", + graphosFedMutual: "Mutuel", + graphosFedOneWay: "Sens unique", + graphosFedTransport: "Réseau", + graphosFedWith: "Fédéré avec", + graphosZoom: "Agrandir", + graphosFedInhabitants: "Habitants", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "Votre nœud ne connaît encore aucun PUB.", graphosViewingGraphOf: "Affichage", graphosBackToMine: "← Mon réseau", graphosShowing: "Affichage", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "Vous n’avez pas encore reçu de @mentions.", - mentionsSearchPlaceholder: "Rechercher des mentions...", + mentionsSearchPlaceholder: "Rechercher dans Mentions...", privateReminders: "RAPPELS", + inboxFilterPms: "MPs", + inboxFilterNotifications: "Notifications", + inboxMarkRead: "Marquer comme lu", + inboxMarkUnread: "Marquer comme non lu", + inboxUnreadChip: "Non lu", + inboxMarkAllRead: "Tout marquer comme lu", + inboxDeleteShown: "Tout supprimer", + inboxArchive: "Archiver", + inboxUnarchive: "Désarchiver", + inboxFilterArchived: "Archivés", + inboxEarlierMessages: "messages précédents", + inboxSortThread: "Par fil", + inboxSortRecent: "Plus récents d'abord", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Notifications de l'Inbox", + settingsInboxBotsDesc: "Choisissez quels bots peuvent vous notifier dans votre Inbox. Les bots décochés sont masqués et leurs avis ne sont pas comptés.", inboxFiltersLabel: "Filtres :", inboxToggleToMutuals: "Passer à mutuels", inboxToggleToWhole: "Passer à tous", @@ -214,8 +244,9 @@ module.exports = { searchFromLabel: "De", searchToLabel: "À", searchQueryLabel: "Recherche", - searchPerPageLabel: "Résultats par page", + searchPerPageLabel: "Résultats", settings: "Configuration", + settingsDescription: "Configurez l'environnement de votre réseau.", continueReading: "Continuer la lecture", moreComments: "plus de commentaires", readThread: "lire le reste du fil", @@ -234,7 +265,6 @@ module.exports = { profileClearnetHelp: 'Modules accessibles depuis l’extérieur d’Oasis.', profileHubAll: 'Tout', footerLicenseLabel: 'Licence', - profileSwitchToOasis: 'Retour à Oasis', profileVisibilityFediverse: "Multivers", contributorsTitle: "Contributions", colorLabel: 'Choisir une couleur', @@ -244,6 +274,7 @@ module.exports = { modulesViewTitle: "Modules", modulesViewDescription: "Définissez votre environnement idéal en activant et désactivant des modules.", inbox: "Boîte", + inboxSearchPlaceholder: "Rechercher dans Boîte...", multiverse: "Multivers", fediverse: "Multivers", fediverseDescription: "Gérez vos autres comptes du multiverse, y compris l'envoi et la réception de contenu.", @@ -252,9 +283,17 @@ module.exports = { fediverseSettingsTitle: "Multivers", fediverseInstanceLabel: "Adresse", fediverseTokenLabel: "Jeton d'accès", - fediverseTokenHelp: "Définissez votre jeton d'accès. Il servira à gérer votre compte Mastodon depuis Oasis.", fediverseConnect: "Connecter", fediverseConnectedAs: "Connecté en tant que", + nextcloudServerLabel: "Serveur", + nextcloudUserLabel: "Utilisateur", + nextcloudFolderLabel: "Dossier", + nextcloudUploadButton: "Envoyer", + nextcloudFileLabel: "Choisissez un fichier", + fileExternalLink: "Lien externe", + nextcloudErrConnect: "Impossible de joindre Nextcloud avec ces informations.", + nextcloudErrUpload: "L'envoi vers Nextcloud a échoué.", + nextcloudErrShare: "Nextcloud n'a pas donné de lien public pour le fichier.", fediverseDisconnect: "Déconnecter", fediverseEmptyLink: "vos réglages", fediverseComposePlaceholder: "À quoi pensez-vous ?", @@ -283,6 +322,76 @@ module.exports = { fediverseErrPost: "Impossible de publier.", fediverseErrMedia: "Impossible d'envoyer le fichier.", fediverseErrEmpty: "Écrivez quelque chose ou joignez un fichier.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Numéro de téléphone", + telegramSendCode: "Envoyer le code", + telegramCodeHelp: "Saisissez le code que Telegram vient de vous envoyer (application ou SMS).", + telegramCodeLabel: "Code", + telegramPasswordHelp: "Ce compte utilise la validation en deux étapes. Saisissez votre mot de passe.", + telegramPasswordLabel: "Mot de passe", + telegramVerify: "Vérifier", + telegramCancel: "Annuler", + telegramChats: "Discussions", + telegramUnread: "Non lus", + telegramOpen: "Ouvrir", + telegramSend: "Envoyer", + telegramYou: "Vous", + telegramNoChats: "Aucune discussion pour l'instant.", + telegramNoMessages: "Aucun message pour l'instant.", + telegramComposePlaceholder: "Écrivez un message…", + telegramChannel: "Chaîne", + telegramGroup: "Groupe", + telegramPrivate: "Privé", + telegramErrConnect: "Impossible de contacter Telegram.", + telegramErrTimeout: "Telegram a mis trop de temps à répondre.", + telegramErrAuth: "Session Telegram invalide ou expirée. Reconnectez-vous.", + telegramErrFetch: "Impossible de charger les discussions.", + telegramErrSend: "Impossible d'envoyer le message.", + telegramErrMissing: "API ID, API hash et numéro de téléphone sont requis.", + telegramErrCode: "Code invalide. Réessayez.", + telegramErrCodeExpired: "Le code a expiré. Recommencez.", + telegramErrPassword: "Mot de passe incorrect.", + telegramErrPhone: "Numéro de téléphone invalide.", + telegramErrApi: "API ID ou API hash invalide.", + telegramErrFlood: "Trop de tentatives. Patientez puis réessayez.", + telegramErrNoLogin: "Aucune connexion en cours. Recommencez.", + peertubeUsernameLabel: "Nom d'utilisatrice", + peertubePasswordLabel: "Mot de passe", + peertubeSubscriptions: "Abonnements", + peertubeMyVideos: "Mes vidéos", + peertubeVideos: "Vidéos", + peertubeFollowers: "Abonné·es", + peertubeViews: "vues", + peertubeLikes: "j'aime", + peertubeQuota: "Quota", + peertubeUpload: "Téléverser une vidéo", + peertubeUploadHelp: "Choisissez un fichier vidéo. Il est envoyé sur votre chaîne et PeerTube le traite ensuite.", + peertubeTitleLabel: "Titre", + peertubeDescriptionLabel: "Description", + peertubePrivacyLabel: "Confidentialité", + peertubePrivacyPublic: "Publique", + peertubePrivacyUnlisted: "Non listée", + peertubePrivacyPrivate: "Privée", + peertubeNoVideos: "Aucune vidéo pour l'instant.", + peertubeComments: "Commentaires", + peertubeComment: "Commenter", + peertubeCommentPlaceholder: "Écrire un commentaire…", + peertubeNoComments: "Aucun commentaire pour l'instant.", + peertubeOpen: "Ouvrir sur PeerTube", + peertubeWatch: "Regarder", + peertubeChannel: "Chaîne", + peertubeNoStream: "Cette vidéo ne peut pas encore être lue ici. Ouvrez-la sur PeerTube.", + peertubeUploaded: "Vidéo envoyée. PeerTube la traite.", + peertubeErrConnect: "Impossible de contacter PeerTube.", + peertubeErrAuth: "Identifiant ou mot de passe incorrect, ou session expirée. Reconnectez-vous.", + peertubeErrInstance: "URL d'instance invalide.", + peertubeErrMissing: "Instance, identifiant et mot de passe sont requis.", + peertubeErrFetch: "Impossible de charger les vidéos.", + peertubeErrUpload: "Impossible d'envoyer la vidéo.", + peertubeErrTitle: "Titre requis.", + peertubeErrComment: "Impossible de publier le commentaire.", + peertubeErrNoFile: "Choisissez un fichier vidéo.", bookmarksLabel: "Marque-pages", videosLabel: "Vidéos", torrentsLabel: "Torrents", @@ -305,7 +414,6 @@ module.exports = { comment: "Commenter", subtopic: "Sous-sujet", json: "JSON", - createdBy: "par", unfollow: "Ne plus soutenir", follow: "Soutenir", block: "Bloquer", @@ -320,13 +428,16 @@ module.exports = { relationshipMutualBlock: "Blocage mutuel", relationshipNone: "Vous ne soutenez pas", relationshipBlockingPost: "Post bloqué", - viewLikes: "Voir les soutiens", + viewLikes: "Voir les diffusions", + viewSupporters: "Voir les soutiens", spreadedDescription: "Liste des posts soutenus par habitant.", preview: "Prévisualiser", publish: "Publier", contentWarningPlaceholder: "Ajoutez un titre à votre post (optionnel)", - publishWarningPlaceholder: "Corps limité à 7000 caractères.", + publishWarningPlaceholder: "Écris le corps de ton billet.", publishTooLong: "Votre publication est trop longue. Veuillez la raccourcir.", + keepTextRetry: "Envoyer à nouveau", + dateInPastError: "La date ne peut pas être dans le passé", publishCustomDescription: [ "RAPPELEZ-VOUS : En raison de la technologie blockchain, une fois que vous avez publié quelque chose, cela ne peut pas être modifié ni supprimé. Réfléchissez bien !", ], @@ -356,9 +467,8 @@ module.exports = { mentionsMatching: "Mentions", mentionsName: "Nom", mentionsRelationship: "Relation", - updateit: "OBTENIR DES MISES À JOUR !", updateBannerText: "Une nouvelle version d'Oasis est disponible.", - updateBannerAction: "Mettre à jour →", + updateBannerAction: "Mettre à jour !", info: "Info", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -366,16 +476,14 @@ module.exports = { timeAgo: "il y a", sendTime: "écrit ", theme: "Thème", - legacy: "Clés", - legacyTitle: "Clés", - legacyDescription: "Gérez votre secret (clé privée) rapidement et en toute sécurité.", - legacyExportButton: "Exporter", - legacyImportButton: "Importer", + backupKeysExportButton: "Exporter", + backupKeysImportButton: "Importer", ssbLogStream: "Blockchain", ssbLogStreamDescription: "Configurez la limite de messages pour les flux de la blockchain.", + logWindowChip: "Derniers {n} flux de la blockchain", saveSettings: "Enregistrer la configuration", exportDescription: "Définissez un mot de passe (min 32 caractères) pour chiffrer votre secret", - exportDataTitle: "Sauvegarde", + exportDataTitle: "Sauvegarde instantanée", exportDataDescription: "Télécharger votre blockchain (clé privée exclue !)", exportDataButton: "Télécharger la blockchain", importDescription: "Importez votre secret chiffré (clé privée) pour activer votre avatar", @@ -384,38 +492,33 @@ module.exports = { fileInfo: "Votre secret chiffré sera téléchargé sur votre appareil (nom : oasis.enc)", developer: "Développement", devTitle: "Développement", - welcomeBannerText: "Bienvenue sur OASIS. Suivez ces quelques étapes pour configurer votre avatar.", + welcomeBannerText: "Ton avatar n'est pas encore configuré.", aiSuggestionBanner: "Suggestion :", aiSuggestionsEnable: "Suggestions de l'IA", - welcomeBannerAction: "Commencer !", + welcomeBannerAction: "Commencer", welcomeTitle: "Bienvenue", + welcomeStepIdentity: "Identité", + welcomeStepType: "Type", + welcomeStepUse: "Usage", + welcomeStepLook: "Apparence", + welcomeStepCommunity: "Communauté", + welcomeStepIntro: "Bienvenue", + welcomeHintIdentity: "Votre nom et votre image.", + welcomeHintType: "Comment Oasis s'ouvre pour vous.", + welcomeHintUse: "Quels modules restent actifs.", + welcomeHintLook: "Le thème que porte Oasis.", + welcomeHintCommunity: "Rejoignez un PUB pour rencontrer d'autres habitants.", + welcomeHintIntro: "Envoyez un premier salut à la communauté.", + welcomeStepThemeAction: "Utiliser ce thème", + welcomeJoinPubs: "Rejoindre les PUBs", + welcomeNoFederation: "Sans fédération", welcomeStepGreetingAction: "Envoyer", - welcomeJoinPubButton: "Rejoindre le pub", - welcomeStepLarpAction: "Rejoindre « L Académie »", - welcomeStepLarpText: "Rejoignez notre jeu de rôle grandeur nature.", - welcomeStepLarpTitle: "Rejoindre le L.A.R.P.", - welcomeStepGreetingTitle: "Envoyez un « Hello world ! »", - welcomeStepGreetingText: "Envoyez un message pour que les autres habitants puissent vous découvrir.", - welcomeJoinPub: "Rejoindre", welcomeDescription: "Quelques choses à faire avant de commencer.", - welcomeProgress: "Terminé", - welcomeStepDone: "fait", - welcomeStepPending: "en attente", welcomeSkip: "Pas maintenant", - welcomeStepLanguageTitle: "Choisissez votre langue", - welcomeStepLanguageText: "Oasis parle plusieurs langues. Vous pouvez en changer quand vous voulez.", - welcomeStepProfileTitle: "Modifiez votre profil", - welcomeStepProfileText: "Choisissez un nom, ajoutez une description et mettez une image pour que les autres habitants vous reconnaissent.", welcomeStepProfileAction: "Enregistrer le profil", - welcomeStepFederationTitle: "Rejoignez le réseau principal", - welcomeStepFederationText: "Connectez-vous à notre pub pour rencontrer d autres habitants et commencer à répliquer.", - welcomeStepFederationAction: "Voir les invitations", - welcomeStepBackupTitle: "Sauvegardez votre ID", - welcomeStepBackupText: "Votre identité est un fichier de clés sur cet appareil.", - welcomeStepBackupWarning: "SI VOUS LE PERDEZ, PERSONNE NE POURRA LE RÉCUPÉRER.", - welcomeStepBackupAction: "Sauvegarder !", + welcomeBootstrapRunning: "Chargement de l'instantané du pub. Le pub est rejoint dès la fin ; vous pouvez continuer à utiliser Oasis entre-temps.", welcomeCompleteTitle: "Tout est prêt.", - welcomeCompleteText: "Votre nœud est prêt. À partir d'ici le réseau grandit avec les gens que vous suivez.", + welcomeCompleteText: "Votre nœud est prêt. À partir d'ici le réseau grandit avec les habitants que vous soutenez.", welcomeFindPeople: "Trouver des habitants", devFilterFiles: "FICHIERS", devFilterSearch: "CHERCHER", @@ -425,7 +528,7 @@ module.exports = { devFilterTests: "TESTS", devVersion: "Version", devNodeVersion: "Node", - devStatsTests: "Suites de tests", + devStatsTests: "Tests", devParentFolder: "Dossier parent", devOpenFolder: "ouvrir", devDescription: "Explorez le code source exécuté sur ce nœud.", @@ -469,6 +572,27 @@ module.exports = { "Si vous souhaitez utiliser une autre langue, sélectionnez-la ici.", setLanguage: "Définir la langue", peerConnectionsIntro: "Gérez toutes vos connexions avec d’autres nœuds.", + peersConnectFailed: "Connexion impossible : {reason}.", + peerStateLabel: "État", + peerAddressLabel: "Adresse", + peerLastChangeLabel: "Dernier changement", + peerStateConnected: "Connecté", + peerStateConnecting: "Connexion en cours", + peerStateStaged: "Disponible", + peerErrSilent: "Aucune activité", + peerErrTimeout: "Ne répond pas", + peerErrRefused: "Refuse la connexion", + peerErrNotFound: "Adresse inconnue", + peerErrUnreachable: "Réseau injoignable", + peerErrKeys: "Utilise des clés différentes", + peerErrIdentity: "N'accepte pas votre connexion", + peerErrTor: "TOR uniquement", + peerErrDropped: "Connexion coupée", + peerErrPaused: "Réseau en pause", + peerErrOther: "Erreur de connexion", + peerOutdated: "Obsolète", + peerVersionUnannounced: "N'annonce pas sa version : un ancien Oasis", + peersTorMissing: "Ce PUB n'accepte que des connexions via TOR.", peerConnectionsTitle: "Connexions", online: "En ligne", offline: "Hors ligne", @@ -487,22 +611,25 @@ module.exports = { noConnections: "Aucun nœud connecté.", noDiscovered: "Vous n’avez découvert aucun nœud.", noUnknownPeers: "Aucun pair inconnu dans le graphe d’amis.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Aucun pair enregistré pour l'instant.", + peerConnect: "Connecter", + peerDisconnect: "Déconnecter", peerRefresh: "Rafraîchir", peerPruneIdle: "Retirer les inactifs", + peersPause: "Pause", + peersResume: "Reprendre", + peersPausedNote: "En pause : Oasis ne se connecte à personne et personne ne peut s'y connecter.", + peersPauseHint: "Arrête toutes les connexions : Oasis ne se connecte à aucun autre nœud et n'en accepte aucun jusqu'à la reprise.", peerExport: "Exporter", peerImport: "Importer", peerImportTitle: "Importer la liste de pairs", peerImportPlaceholder: "Colle une adresse multiserver par ligne, ou téléverse un fichier .txt…", connectionActionIntro: "", - startNetworking: "Démarrer le réseau", - stopNetworking: "Arrêter le réseau", - restartNetworking: "Redémarrer le réseau", - sync: "Synchroniser le réseau", indexes: "Index", + indexesRebuilt: "Index reconstruits", + indexesRebuildFailed: "Impossible de reconstruire les index", + indexesFiles: "Fichiers d'index", indexesDescription: "Reconstruire les index est sans risque et peut corriger certains problèmes dans votre blockchain.", homePageTitle: "Page d'accueil", @@ -512,57 +639,42 @@ module.exports = { invitesDescription: "Gérer et appliquer des codes d’invitation dans votre réseau.", invitesPubsHint: "Collez un code d'invitation de pub pour vous fédérer avec lui et répliquer ses habitants.", invitesFederationsHint: "Pubs avec lesquels vous êtes fédéré : actualisez-les, retirez les injoignables ou exportez la liste.", - invitesHousesHint: "Saisissez un code de maison pour rejoindre une maison du LARP et participer à son économie.", - invitesTribesHint: "Saisissez un code de tribu pour en devenir membre et accéder à son contenu privé.", - invitesChatsHint: "Saisissez un code de chat pour rejoindre la conversation et ses fils.", - invitesPadsHint: "Saisissez un code de pad pour écrire à plusieurs sur un document partagé.", - invitesCalendarsHint: "Saisissez un code de calendrier pour partager dates et rappels.", - invitesEventsHint: "Saisissez un code d'événement pour rejoindre un événement privé.", - invitesForumsHint: "Saisissez un code de forum pour lire et répondre dans un forum privé.", - invitesMapsHint: "Saisissez un code de carte pour ajouter et voir des lieux sur une carte partagée.", - invitesShopsHint: "Saisissez un code de boutique pour rejoindre une boutique et son catalogue.", - invitesInhabitantsHint: "Saisissez un code d'habitant pour vous suivre mutuellement et échanger du contenu.", invitesTribesTitle: "Tribus", - invitesTribeInviteCodePlaceholder: "Saisissez le code de la tribu", - invitesTribeJoinButton: "Entrer dans la tribu", invitesChatsTitle: "Chats", - invitesChatInviteCodePlaceholder: "Saisissez le code du chat", - invitesChatJoinButton: "Rejoindre le chat", invitesPadsTitle: "Pads", - invitesPadInviteCodePlaceholder: "Saisissez le code du pad", - invitesPadJoinButton: "Rejoindre le pad", invitesCalendarsTitle: "Calendriers", - invitesCalendarInviteCodePlaceholder: "Saisissez le code du calendrier", - invitesCalendarJoinButton: "Rejoindre le calendrier", invitesEventsTitle: "Événements", - invitesEventInviteCodePlaceholder: "Saisissez le code de l'événement", - invitesEventJoinButton: "Rejoindre l'événement", invitesForumsTitle: "Forums", - invitesForumInviteCodePlaceholder: "Saisissez le code du forum", - invitesForumJoinButton: "Rejoindre le forum", invitesMapsTitle: "Cartes", - invitesMapInviteCodePlaceholder: "Saisissez le code de la carte", - invitesMapJoinButton: "Rejoindre la carte", invitesShopsTitle: "Boutiques", - invitesShopInviteCodePlaceholder: "Saisir le code d'invitation de la boutique", - invitesShopJoinButton: "Rejoindre la boutique", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "PUBs Suggérés", + invitesFilterOthers: "Autres", + invitesJoinTitle: "Invitations", + invitesJoinHint: "Collez n'importe quel code d'invitation, quel que soit son objet, ou l'Oasis ID d'un habitant.", + invitesJoinPlaceholder: "Code d'invitation ou Oasis ID", + invitesJoinButton: "Rejoindre", + invitesUsedTitle: "Dernières invitations", + invitesUsedEmpty: "Vous n'avez encore employé aucune invitation.", + invitesUsedType: "Type", + invitesUsedName: "Nom", + invitesUsedDate: "Date", invitesPubInviteCodePlaceholder: "Saisissez le code d’invitation du PUB", invitesAcceptInvite: "Entrer dans le PUB", + invitesPubUses: "Invitations utilisées", + invitesPubExhausted: "Ce PUB n'a plus d'invitations disponibles.", invitesAlreadyFederated: "Vous êtes déjà fédéré avec ce pub.", + invitesJoinPubMode: "Cet Oasis fonctionne comme PUB, en lecture seule. Pour le relier à un autre PUB, utilisez le terminal : ./oasis.sh join ", + invitesJoinLocalOnly: "Les invitations ne peuvent être acceptées que depuis l'appareil où tourne Oasis, ou via le lien d'administration affiché au démarrage.", invitesFederationsTitle: "Fédérations", invitesAcceptedInvites: "Fédérés", invitesNoFederatedPubs: "Aucun pub fédéré.", - invitesUnreachablePubs: "Inaccessibles", - invitesNoUnreachablePubs: "Aucun pub inaccessible.", invitesPubsRefresh: "Rafraîchir", - invitesPubsClearUnreachable: "Retirer les injoignables", + invitesPubsClearUnreachable: "Nettoyer", invitesPubsExport: "Exporter", invitesPubsImport: "Importer", invitesPubsImportTitle: "Importer des pubs", invitesPubsImportPlaceholder: "Colle une adresse multiserver ou un code d'invitation par ligne, ou téléverse un fichier .txt…", - currentlyUnreachable: "ERREUR !", - errorDetails: "Détails de l’erreur", genericError: "Une erreur s’est produite.", panicMode: "Mode Panique !", encryptData: "Définissez un mot de passe (min 32 caractères) pour chiffrer votre blockchain", @@ -589,7 +701,6 @@ module.exports = { votesOptionNoLabel:"Non", votesOptionAbstentionLabel:"Abstention", votesQuestionLabel:"Question", - votesCreatedByLabel:"Créé le", voteStatusOpen:"OUVERT", voteStatusClosed:"FERMÉ", commentDescription: ({ parentUrl }) => [ @@ -606,22 +717,22 @@ module.exports = { searchSubmit: "Rechercher...", votesLabel: "GOUVERNANCE", pdfFallbackLabel: "Document PDF", - shopLabel: "SHOPS", + shopLabel: "BOUTIQUES", voteLabel: "VOTES", submit: "Appliquer", editProfile: "Éditer l’Avatar", oasisIdLabel: "OasisID", spreadContent: "Répliquer", welcomePmSubject: "Hello World!", - welcomePmBody: "Bienvenue sur Oasis — un réseau social libre, pair-à-pair, fédéré et chiffré, avec une architecture distribuée sur invitation uniquement.\n\nQuelques endroits intéressants par où commencer :\n\n- /profile — Édite ton avatar, ton nom, ta description et les modules visibles sur ta partie publique.\n- /invites — Ajoute un pub pour fédérer avec le reste du réseau.\n- /peers — Vois qui est connecté, rescanne ton LAN, exporte ou importe des listes de pairs.\n- /inhabitants — Découvre et visite les avatars d'autres personnes.\n- /tribes — Crée ou rejoins des groupes privés chiffrés de bout en bout.\n- /inbox — Lis et envoie des messages privés.\n- /activity — Vois l'activité récente, la tienne et celle de ton réseau.\n- /publish — Écris un post ou partage une image, un audio, une vidéo ou un document.\n- /search — Recherchez le contenu du réseau par type.\n\nQuelques endroits intéressants pour se connecter :\n\n- /multiverse — Gérez vos autres comptes du multiverse, y compris l'envoi et la réception de contenu.\n\nCe message a été envoyé en privé de toi à toi-même : aucune connexion n'était nécessaire pour le recevoir.", + welcomePmBody: "Bienvenue sur Oasis — un réseau social libre, pair-à-pair, fédéré et chiffré, avec une architecture distribuée sur invitation uniquement.\n\nQuelques endroits intéressants par où commencer :\n\n- [/avatar](/profile) — Édite ton avatar, ton nom, ta description et les modules visibles sur ta partie publique.\n- /settings — Réglez votre instance locale : langue, thème, modules, confidentialité et synchronisation.\n- /invites — Ajoute un pub pour fédérer avec le reste du réseau.\n- /peers — Vois qui est connecté, rescanne ton LAN, exporte ou importe des listes de pairs.\n- /inhabitants — Découvre et visite les avatars d'autres habitants.\n- /tribes — Crée ou rejoins des groupes privés chiffrés de bout en bout.\n- /search — Recherchez le contenu du réseau par type.\n\nQuelques endroits intéressants pour se connecter :\n\n- /multiverse — Gérez vos autres comptes du multiverse, y compris l'envoi et la réception de contenu.\n\nCe message a été envoyé en privé de toi à toi-même : aucune connexion n'était nécessaire pour le recevoir.", profileSensorsSectionTitle: "Capteurs", profileVisibilityActivity: "Niveau d’activité", profileDeviceLockedHint: "Ce capteur est toujours actif : il permet aux autres de voir depuis quel appareil vous vous connectez et ne peut pas être désactivé.", profileVisibilityKarma: "Score KARMA", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "Portefeuille ECOIN", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "RBU", + profileVisibilityWallet: "Portefeuille ECOin", + profileVisibilityEcoTax: "Taxe ECO", + profileVisibilityLarpSign: "Sceau L.A.R.P.", profileVisibilityGpg: "Clé GPG", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Boutiques", @@ -634,6 +745,10 @@ module.exports = { profileClearnetImagesLabel: "Images", profileClearnetDocumentsLabel: "Documents", profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Marché", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", editProfileDescription: "", profileName: "Nom", @@ -657,9 +772,9 @@ module.exports = { walletReceiveTitle: "Recevoir", walletHistoryTitle: "Historique", walletBalanceLine: ({ balance }) => `${balance} ECO`, - walletCnfrs: "Cnfrs", + walletCnfrs: "Conf.", walletConfirm: "Confirmer", - walletDescription: "Gérez vos actifs numériques, y compris l’envoi et la réception d’ECOin, l’affichage du solde et l’accès à l’historique des transactions.", + walletDescription: "Gérez votre portefeuille ECOin.", walletDate: "Date", walletFee: "Frais (Plus les frais sont élevés, plus votre transaction sera traitée rapidement)", walletFeeLine: ({ fee }) => `Frais : ECO ${fee}`, @@ -667,6 +782,86 @@ module.exports = { walletReceive: "Recevoir", walletReset: "Réinitialiser", walletSend: "Envoyer", + walletPayingTransfer: "Paiement du transfert", + walletCreateTransferLabel: "Générer un contrat (transfert)", + walletTransferNotifiedNote: "L'autre partie a été informée de ce paiement", + walletTransferCreatedNote: "Un transfert (reçu) a été créé pour ce paiement", + walletTransferNoPayeeNote: "Aucun habitant n'a publié cette adresse, aucun transfert créé", + walletTransferReceiptConcept: "Paiement depuis le portefeuille", + walletPaymentPmText: "vous a envoyé un paiement", + walletPayeeLabel: "Destinataire", + walletConceptLabel: "Concept", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Vous avez reçu votre RBU.", + bankingBotPaymentTitle: "Vous avez reçu un paiement.", + bankingBotUbiPaidText: "Votre Revenu de Base Universel a été versé", + bankUbiPubThisNode: "Ce nœud paie le RBU", + bankUbiPubConnected: "PUB du RBU", + bankUbiPubNone: "Aucun PUB n'annonce encore le RBU dans votre réseau", + bankUbiAddressPublished: "Adresse ECOin publiée", + bankUbiAddressMissing: "Publiez votre adresse ECOin pour recevoir le RBU", + bankUbiClaimedThisMonth: "Réclamé ce mois-ci", + bankUbiPubBalance: "Portefeuille du PUB disponible pour le RBU", + bankNoPubAddress: "Le PUB du RBU n'a pas encore publié d'adresse ECOin.", + bankUbiTab: "RBU", + bankDonateEco: "DONNER !", + donateTooltip: "Faire un don d'ECOin", + bankNoUserAddress: "Cet habitant ne partage pas d'adresse ECOin.", + bankUbiPubDefault: "Par défaut", + bankUbiPool: "Dotation", + bankUbiLastSeen: "Vu pour la dernière fois", + bankUbiLastPayout: "Dernière action", + bankUbiPaidOut: "Distribué", + bankRangeToday: "Aujourd'hui", + bankRangeWeek: "Semaine", + bankRangeMonth: "Mois", + bankRangeYear: "Année", + bankRangeFiveYears: "5 ans", + bankRangeAll: "Tout", + bankChartValueTitle: "Évolution de la valeur d'ECOin", + bankChartValueHours: "Équivalence en temps (h)", + bankChartWealthTitle: "Redistribution de la richesse ECOin", + bankChartDistributed: "RBU distribué (ECO)", + bankChartRebalanceTitle: "Redistribution des richesses entre PUBs", + bankChartRebalanced: "ECO déplacés entre PUBs", + bankChartTaxes: "Impôts retenus (ECO)", + bankChartNoDataRange: "Aucune donnée sur cette période.", + bankChartNoDataYet: "Pas encore de données", + bankChartPerHour: "par heure", + bankChartPerDay: "par jour", + bankChartPerMonth: "par mois", + bankTotalUbiDistributed: "RBU total redistribué (réseau)", + bankTotalTaxesWithheld: "Impôts totaux retenus (réseau)", + bankInflationIssuance: "Taux d'émission (annuel, récompense de bloc)", + bankRulesEligibilityTitle: "Qui peut recevoir le RBU", + bankRulesEligibility1: "Une adresse ECOin publiée dans votre profil (Profil → Capteurs → Portefeuille ECOin).", + bankRulesEligibility2: "Un feed de plus de 30 jours avec de l'activité : karma supérieur à zéro.", + bankRulesEligibility3: "Une réclamation par mois (epoch), depuis Banking avec le bouton Réclamer. Le mois en cours peut être refusé avec Refuser le RBU.", + bankRulesEligibility4: "Le plancher de 1 ECO est toujours payé ; les impôts ne réduisent que la part au-dessus et ne sont jamais facturés à part.", + bankRulesEligibility5: "Le PUB paie à son prochain cycle et publie un transfert RBU que vous pouvez confirmer.", + bankRulesEligibility6: "Sans adresse, avec un feed trop jeune, un mois refusé ou un PUB sans fonds, la réclamation est ignorée et journalisée sur le PUB.", + copyLinkTitle: "Copier le lien", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Refuser le RBU", + bankUbiRefusedThisMonth: "Vous avez refusé le RBU de ce mois.", + bankingBotUbiAvailableTitle: "Vous pouvez réclamer votre RBU.", + bankingBotUbiAvailableText: "Votre RBU de ce mois est disponible", + bankingBotConfirmTitle: "Vous avez un smart-contract en attente de confirmation", + bankingBotConfirmText: "Smart-contracts en attente de votre confirmation", + bankChartZoomHint: "Cliquer pour agrandir", + bankChartPoolTitle: "Dotation du RBU (dans le temps)", + bankChartNetworkUbiTitle: "RBU total redistribué (réseau)", + bankChartSupplyTitle: "Offre en circulation d'ECOin (dans le temps)", + bankChartInflationTitle: "Inflation d'ECOin (dans le temps)", + bankChartInflationSeries: "Taux annualisé (%)", + bankChartPool: "Dotation totale (ECO)", + bankChartKarmaTitle: "KARMA Scoring (dans le temps)", + bankChartFundsTitle: "Vos fonds ECOin (dans le temps)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin connecté", + bankWalletNotConfigured: "ECOin déconnecté", + transfersPayWithWallet: "PAYER !", walletStatus: "État", walletDisconnected: [ "Vous avez le ", @@ -676,8 +871,11 @@ module.exports = { " ou l’état de votre connexion.", ], walletSentToLine: ({ destination, amount }) => `Envoyer ECO ${amount} à ${destination}`, + walletNotConfigured: "Votre portefeuille ECOin n'est pas encore configuré.", walletSettingsDescription: "Intégrer Oasis avec votre portefeuille ECOin.", - walletSettingsDocLink: "Guide d’installation d’ECOin", + walletSettingsMobileDescription: "Associez Oasis à l’application ECOin Wallet de cet appareil.", + walletDownloadEcoin: "Télécharger ECOin !", + walletRpcUrl: "URL du RPC", walletStatusMessages: { invalid_amount: "Montant invalide", invalid_dest: "Adresse de destination invalide", @@ -693,6 +891,7 @@ module.exports = { walletUser: "Utilisateur", walletPass: "Mot de passe", walletConfiguration: "Configurer le portefeuille", + walletDisconnectButton: "Déconnecter le portefeuille", cipher: "Chiffreur", cipherTitle: "Chiffreur", cipherDescription: "Chiffrez et déchiffrez votre texte de manière symétrique (à l’aide d’un mot de passe partagé).", @@ -726,17 +925,16 @@ module.exports = { bookmarkFilterRecent: "RÉCENTS", bookmarkCreateButton: "Créer un marque-page", bookmarkUpdateButton: "Mettre à jour", - bookmarkDeleteButton: "Supprimer", bookmarkAddFavoriteButton: "Ajouter aux favoris", bookmarkRemoveFavoriteButton: "Retirer des favoris", bookmarkUrlLabel: "Lien", - bookmarkUrlPlaceholder: "https://exemple.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Description", bookmarkDescriptionPlaceholder: "Optionnel", bookmarkTagsLabel: "Étiquettes", bookmarkTagsPlaceholder: "Saisissez des tags séparés par des virgules", bookmarkLastVisitLabel: "Dernière visite", - bookmarkSearchPlaceholder: "Rechercher URL, tags, catégorie, auteur...", + bookmarkSearchPlaceholder: "Rechercher dans Marque-pages...", bookmarkSortRecent: "Plus récents", bookmarkSortOldest: "Plus anciens", bookmarkSortTop: "Les plus votés", @@ -775,7 +973,7 @@ module.exports = { videoDescriptionPlaceholder: "Facultatif", videoNoFile: "Aucun fichier vidéo fourni", noVideos: "Aucune vidéo disponible.", - videoSearchPlaceholder: "Rechercher par titre, tags, auteur…", + videoSearchPlaceholder: "Rechercher dans Vidéos...", videoSortRecent: "Les plus récentes", videoSortOldest: "Les plus anciennes", videoSortTop: "Les plus votées", @@ -798,10 +996,9 @@ module.exports = { documentFilterFavorites: "FAVORIS", documentCreateButton: "Téléverser", documentUpdateButton: "Mettre à jour", - documentDeleteButton: "Supprimer", documentAddFavoriteButton: "Ajouter aux favoris", documentRemoveFavoriteButton: "Retirer des favoris", - documentMessageAuthorButton: "PM", + documentMessageAuthorButton: "MP", documentFileLabel: "Téléverser un document (.pdf)", documentTagsLabel: "Étiquettes", documentTagsPlaceholder: "Saisissez des tags séparés par des virgules", @@ -811,7 +1008,7 @@ module.exports = { documentDescriptionPlaceholder: "Optionnel", documentNoFile: "Aucun fichier.", noDocuments: "Aucun document disponible.", - documentSearchPlaceholder: "Rechercher titre, tags, description, auteur...", + documentSearchPlaceholder: "Rechercher dans Documents...", documentSortRecent: "Les plus récents", documentSortOldest: "Les plus anciens", documentSortTop: "Les plus votés", @@ -832,31 +1029,31 @@ module.exports = { audioFilterTop: "TOP", audioCreateButton: "Téléverser un audio", audioUpdateButton: "Mettre à jour", - audioDeleteButton: "Supprimer", audioAddFavoriteButton: "Ajouter aux favoris", audioRemoveFavoriteButton: "Retirer des favoris", audioFileLabel: "Sélectionnez un fichier audio (.mp3, .wav, .ogg)", audioTagsLabel: "Étiquettes", audioTagsPlaceholder: "Saisissez des tags séparés par des virgules", + tagsPlaceholder: "Saisissez des tags séparés par des virgules", audioTitleLabel: "Titre", audioTitlePlaceholder: "Facultatif", audioDescriptionLabel: "Description", audioDescriptionPlaceholder: "Facultatif", audioNoFile: "Aucun fichier audio fourni", noAudios: "Aucun audio disponible.", - audioSearchPlaceholder: "Rechercher par titre, tags, auteur…", + audioSearchPlaceholder: "Rechercher dans Audios...", audioSortRecent: "Les plus récents", audioSortOldest: "Les plus anciens", audioSortTop: "Les plus votés", audioSearchButton: "Rechercher", - audioMessageAuthorButton: "PM", + audioMessageAuthorButton: "MP", audioUpdatedAt: "Mis à jour", audioNoMatch: "Aucun audio ne correspond à votre recherche.", audioFavoritesSectionTitle: "Favoris", audioFilterFavorites: "FAVORIS", favoritesTitle: "Favoris", favoritesDescription: "Tous vos favoris au même endroit.", - favoritesSearchPlaceholder: "Rechercher dans les favoris...", + favoritesSearchPlaceholder: "Rechercher dans Favoris...", favoritesFilterAll: "TOUS", favoritesFilterRecent: "RÉCENTS", favoritesFilterAudios: "AUDIOS", @@ -868,13 +1065,12 @@ module.exports = { favoritesFilterChats: "CHATS", favoritesFilterCalendars: "CALENDRIERS", favoritesFilterVideos: "VIDÉOS", - favoritesRemoveButton: "Retirer des favoris", favoritesNoItems: "Aucun favori pour le moment.", yourContacts: "Vos contacts", allInhabitants: "Habitants", allCVs: "CV", discoverPeople: "Découvrez des habitants dans votre réseau.", - searchInhabitantsPlaceholder: "FILTRER les habitants PAR NOM …", + searchInhabitantsPlaceholder: "Rechercher dans Habitants...", filterLocation: "FILTRER les habitants PAR LOCALISATION …", filterLanguage: "FILTRER les habitants PAR LANGUE …", filterSkills: "FILTRER les habitants PAR COMPÉTENCES …", @@ -898,7 +1094,6 @@ module.exports = { topSectionTitle: "TOP", blockedLabel: "Utilisateur bloqué", inhabitantviewDetails: "Voir les détails", - cvVisitButton: "Voir le CV", keepReading: "Lire la suite...", oasisId: "ID", noInhabitantsFound: "Aucun habitant trouvé pour l’instant.", @@ -916,6 +1111,18 @@ module.exports = { parliamentFilterLeaders: "DIRIGEANTS", parliamentFilterRules: "RÈGLES", parliamentGovernmentCard: "Gouvernement actuel", + parliamentSeatsGovernment: "Gouvernement", + parliamentSeatsOpposition: "Opposition", + parliamentSeatsAbstention: "Abstention", + parliamentSeatsQuorum: "Quorum d'élection", + parliamentSeatsPass: "Seuil d'approbation", + parliamentSeatsByElection: "Par élection", + parliamentSeatsByHouses: "Par maison du LARP", + parliamentSeatsSeats: "sièges", + parliamentSeatsSeat: "siège", + parliamentSeatsLeader: "Chef", + parliamentSeatsVotesFor: "votes pour", + parliamentSeatsNoHouse: "Sans maison", parliamentActorInPowerInhabitant: 'HABITANT AU POUVOIR', parliamentActorInPowerTribe: 'TRIBU AU POUVOIR', parliamentHistoricalGovernmentsTitle: 'GOUVERNEMENTS', @@ -982,6 +1189,7 @@ module.exports = { parliamentTimeRemaining: "Temps restant", parliamentElectionsStatusTitle: "Prochain Gouvernement", parliamentProposalDeadlineLabel: "Date limite", + parliamentViewReport: "Voir le rapport", parliamentProposalTimeLeft: "Temps restant", parliamentRulesTitle: "Fonctionnement du Parlement", parliamentRulesIntro: "Les élections se résolvent tous les 2 mois ; les candidatures sont continues et sont réinitialisées lorsqu’un gouvernement est choisi.", @@ -996,6 +1204,7 @@ module.exports = { parliamentRulesLaws: "Lorsqu’une proposition atteint son seuil, elle devient une Loi et apparaît dans l’onglet Lois avec sa date d’entrée en vigueur.", parliamentRulesHistorical: "Dans l’onglet Historique, vous pouvez voir chaque cycle de gouvernement qui a eu lieu ainsi que des données sur sa gestion.", parliamentRulesLeaders: "Dans l’onglet Dirigeants, vous pouvez voir un classement des habitants/tribus qui ont gouverné (ou se sont présentés), classés par efficacité.", + parliamentRulesHemicycle: "L'hémicycle attribue un siège par habitant : le bloc du gouvernement (les électeurs de la candidature gagnante et le chef), un bloc d'opposition par autre candidature du cycle, et l'abstention. Les marques en pointillé indiquent le quorum d'élection (25 % du recensement) et le seuil d'approbation de la méthode en vigueur. « Par maison du LARP » colore les mêmes sièges par maison.", parliamentProposalVoteStatusLabel: "Statut du vote", parliamentProposalOnTrackYes: "Seuil atteint", parliamentProposalOnTrackNo: "En dessous du seuil", @@ -1009,6 +1218,7 @@ module.exports = { courtsCaseFormTitle: "Ouvrir une affaire", courtsCaseRespondent: "Accusé / Défendeur", courtsCaseRespondentPh: "ID Oasis (@...) ou nom de Tribu", + courtsCaseSubjectPlaceholder: "Objet ou courte description", courtsCaseMethod: "Méthode de résolution", courtsCaseSubmit: "Déposer l'affaire", courtsNominateJudge: "Nommer un juge", @@ -1043,15 +1253,30 @@ module.exports = { courtsMethodDICTATOR: "Dictateur", courtsMethodPOPULAR: "Populaire", courtsMethodKARMATOCRACY: "Karmatocratie", + courtsCaseType: "Type de dossier", + courtsPresetMinorConflict: "Conflit mineur", + courtsPresetModerateConflict: "Conflit modéré", + courtsPresetSevereConflict: "Conflit grave", + courtsPresetHarassment: "Harcèlement ou abus", + courtsPresetContentModeration: "Modération de contenu", + courtsPresetBanRestriction: "Bannissement ou restriction", + courtsPresetGovernanceDispute: "Litige de gouvernance", + courtsStatusOPEN: "Ouvert", + courtsStatusIN_PROGRESS: "En cours", + courtsStatusDECIDED: "Décidé", + courtsStatusCLOSED: "Clos", + courtsStatusSOLVED: "Résolu", + courtsStatusUNSOLVED: "Non résolu", + courtsStatusDISCARDED: "Rejeté", courtsRulesTitle: "Fonctionnement des Tribunaux", courtsRulesIntro: "Les Tribunaux sont un processus géré par la communauté pour résoudre les conflits et promouvoir la justice réparatrice. Le dialogue, des preuves claires et des remèdes proportionnels sont privilégiés.", courtsRulesLifecycle: "Processus : 1) Ouvrir une affaire 2) Choisir une méthode 3) Soumettre des preuves 4) Audience et délibération 5) Verdict et remède 6) Exécution et clôture 7) Appel (le cas échéant).", - courtsRulesRoles: "Accusation : ouvre l’affaire. Défense : personne ou tribu mise en cause. Méthode : mécanisme choisi par la communauté pour faciliter, évaluer les preuves et rendre un verdict. Témoin : fournit des témoignages ou des preuves. Médiateurs : personnes neutres invitées par l’accusation et/ou la défense, avec accès à tous les détails, qui aident à désamorcer le conflit et à co-créer des accords.", + courtsRulesRoles: "Accusation : ouvre l’affaire. Défense : habitant ou tribu mis en cause. Méthode : mécanisme choisi par la communauté pour faciliter, évaluer les preuves et rendre un verdict. Témoin : fournit des témoignages ou des preuves. Médiateurs : habitants neutres invités par l’accusation et/ou la défense, avec accès à tous les détails, qui aident à désamorcer le conflit et à co-créer des accords.", courtsRulesEvidence: "Description jusqu’à 1000 caractères. Joignez des images, audios, vidéos et documents PDF pertinents et légaux. Ne partagez pas de données privées sensibles sans consentement.", courtsRulesDeliberation: "Les audiences peuvent être publiques ou privées. Les juges garantissent le respect, demandent des clarifications et peuvent écarter le matériel non pertinent ou illégal.", courtsRulesVerdict: "La restauration est prioritaire : excuses, accords de médiation, modération de contenu, restrictions temporaires ou autres mesures proportionnées. Le raisonnement doit être consigné.", courtsRulesAppeals: "Appel : autorisé en présence de nouvelles preuves ou d’une erreur procédurale manifeste. Doit être déposé dans les 7 jours sauf indication contraire.", - courtsRulesPrivacy: "Respectez la vie privée et la sécurité. Le doxing, la haine ou les menaces sont supprimés. Les juges peuvent modifier ou sceller certaines parties du dossier pour protéger les personnes à risque.", + courtsRulesPrivacy: "Respectez la vie privée et la sécurité. Le doxing, la haine ou les menaces sont supprimés. Les juges peuvent modifier ou sceller certaines parties du dossier pour protéger les habitants à risque.", courtsRulesMisconduct: "Le harcèlement, la manipulation ou les preuves fabriquées peuvent entraîner une résolution négative immédiate.", courtsRulesGlossary: "Affaire : enregistrement d’un conflit. Preuves : éléments à l’appui des affirmations. Verdict : décision avec remède. Appel : demande de réexamen du verdict.", courtsFilterActions: "ACTIONS", @@ -1067,12 +1292,16 @@ module.exports = { exploreTrending: "Explorez le contenu le plus populaire dans votre réseau.", votesButton: "GOUVERNANCE", shopProductButton: "BOUTIQUE", + podcastButton: "PODCAST", + podcastEpisodeButton: "ÉPISODE", + campaignButton: "CAMPAGNE", + logisticsRouteButton: "TRAJET", trendingTotalOpinions: "Total des avis", trendingNoContentMessage: "Pas encore de tendances.", trendingTotalCount: "Total des compteurs", tasksTitle: "Tâches", tasksDescription: "Découvrez et gérez des tâches dans votre réseau.", - taskSearchPlaceholder: "Rechercher des tâches...", + taskSearchPlaceholder: "Rechercher dans Tâches...", taskTitleLabel: "Titre", taskDescriptionLabel: "Description", taskStartTimeLabel: "Date de début", @@ -1091,7 +1320,7 @@ module.exports = { taskStatusOpen: "Ouverte", taskStatusInProgress: "En cours", taskStatusClosed: "Fermée", - taskAssignedTo: "Assignée à", + taskAssignedTo: "Assignée", taskAssignedChip: "Assignée", taskUnassignedChip: "Non assignée", taskAssignees: "Assignés", @@ -1101,6 +1330,7 @@ module.exports = { taskUpdateButton: "Mettre à jour", taskDeleteButton: "Supprimer", taskFilterAll: "TOUS", + taskFilterRecent: "RÉCENTS", taskFilterMine: "MIENS", taskFilterOpen: "OUVERTES", taskFilterInProgress: "EN COURS", @@ -1128,14 +1358,13 @@ module.exports = { eventUpcomingDates: "Prochaines dates", eventsTitle: "Événements", eventsDescription: "Découvrez et gérez des événements dans votre réseau.", - eventSearchPlaceholder: "Rechercher des événements...", + eventSearchPlaceholder: "Rechercher dans Événements...", eventPrice: "Prix", eventStatus: "État", eventAllSectionTitle: "Événements", eventMineSectionTitle: "Vos événements", eventCreateSectionTitle: "Créer un événement", eventUpdateSectionTitle: "Mettre à jour l'événement", - eventDeleteButton: "Supprimer", eventCreateButton: "Créer un événement", eventAddToCalendar: "Ajouter au calendrier", eventVisitCalendar: "Voir le calendrier", @@ -1177,6 +1406,7 @@ module.exports = { eventDate: "Date", eventAttendButton: "Assister à l'événement", eventFilterAll: "TOUS", + eventFilterRecent: "RÉCENTS", eventFilterMine: "MIENS", eventFilterToday: "AUJOURD'HUI", eventFilterWeek: "CETTE SEMAINE", @@ -1203,7 +1433,7 @@ module.exports = { tagsFilterTop: "TOP", tagsFilterCloud: "NUAGE", tagsNoItems: "Aucune étiquette disponible.", - tagsSearchPlaceholder: "Rechercher des étiquettes...", + tagsSearchPlaceholder: "Rechercher dans Étiquettes...", tagsTableHeaderTag: "ÉTIQUETTE/LINK :", tagsTableHeaderCount: "COMPTEUR :", transfersTitle: "Transferts", @@ -1211,7 +1441,10 @@ module.exports = { transfersFrom: "De", transfersTo: "À", transfersFilterAll: "TOUS", + transfersFilterRecent: "RÉCENTS", transfersFilterMine: "MIENS", + transfersFilterMinePending: "EN ATTENTE", + transfersFilterMineConfirmed: "SIGNÉS", transfersFilterUBI: "RBU", transfersFilterMarket: "MARCHÉ", transfersFilterTop: "TOP", @@ -1226,7 +1459,6 @@ module.exports = { transfersCategoryEconomic: "Économique", transfersCategoryTime: "Temps", transfersCategoryTrust: "Confiance", - transfersCategoryApply: "Appliquer", transfersUnitEco: "ECO", transfersUnitHours: "h", transfersUnitTrust: "confiance", @@ -1256,7 +1488,7 @@ module.exports = { transfersDiscardedSectionTitle: "Transferts rejetés", transfersCreateSectionTitle: "Créer un transfert", transfersAllSectionTitle: "Transferts", - transfersSearchPlaceholder: "Rechercher concept, tags, utilisateurs...", + transfersSearchPlaceholder: "Rechercher dans Transferts...", transfersMinAmountLabel: "Montant min.", transfersMaxAmountLabel: "Montant max.", transfersSortRecent: "Plus récents", @@ -1278,6 +1510,7 @@ module.exports = { voteErrorTitle: "Vérifiez le formulaire", voteErrorDeadlineMin: "La date limite doit être d’au moins {days} jours à partir de maintenant.", voteErrorGeneric: "Le vote n’a pas pu être enregistré.", + voteLocked: "Ce vote a déjà reçu des votes ou des avis, ou appartient au Parlement ou aux Tribunaux : il ne peut plus être modifié ni supprimé.", voteUpdateButton: "Mettre à jour", voteDeleteButton: "Supprimer", voteOptionYes: "OUI", @@ -1287,6 +1520,7 @@ module.exports = { voteFollowMajority: "SUIVRE LA MAJORITÉ", voteNotInterested: "SANS INTÉRÊT", voteFilterAll: "TOUS", + voteFilterRecent: "RÉCENTS", voteFilterMine: "MIENS", voteFilterOpen: "OUVERTS", voteFilterClosed: "FERMÉS", @@ -1302,7 +1536,6 @@ module.exports = { voteTags: "Étiquettes", voteOpinions: "Opinions", novotes: "Aucune proposition de vote disponible.", - voteBy: "Par", voteCreatedAt: "Créé le", voteNoQuestion: "Aucune question fournie", voteUnknownCreator: "Créateur inconnu", @@ -1312,6 +1545,7 @@ module.exports = { voteCommentsForumButton: 'Ouvrir la discussion', voteCommentsSectionTitle: 'Discussion ouverte', voteNoCommentsYet: 'Il n’y a pas encore de commentaires. Soyez le premier à répondre.', + commentsGoToLast: "Aller au dernier commentaire", voteNewCommentPlaceholder: 'Écrivez votre commentaire ici…', voteNewCommentButton: 'Publier le commentaire', voteNewCommentLabel: 'Ajouter un commentaire', @@ -1322,6 +1556,8 @@ module.exports = { cvNameLabel: "Nom complet", cvDescriptionLabel: "Résumé", cvPhotoLabel: "Photo", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Voir le CV", cvPersonalExperiencesLabel: "Expériences personnelles", cvPersonalSkillsLabel: "Compétences personnelles (séparées par des virgules)", cvOasisExperiencesLabel: "Expériences de contribution dans Oasis", @@ -1353,7 +1589,7 @@ module.exports = { blogMessage: "Message", blogImage: "Télécharger un média (max: 50 Mo)", blogMedia: "Pièces jointes (jusqu'à 8 images et une vidéo)", - blogPublish: "Aperçu", + blogPublish: "Publier", pollsTitle: "Sondages", dataTitle: "Correspondances", dataDescription: "Explorez et visualisez les correspondances de votre réseau.", @@ -1372,12 +1608,23 @@ module.exports = { dataKindTasks: "TÂCHES", dataKindReports: "RAPPORTS", dataKindVotes: "VOTES", - dataSearchPlaceholder: "Rechercher dans les données croisées...", + dataSearchPlaceholder: "Rechercher dans Correspondances...", dataCohesionTitle: "Coefficient de cohésion (CC)", dataCcShort: "CC", dataCohesionHint: "Affinité moyenne entre tout ce que le réseau a publié.", dataAffinity: "Affinité", dataCommonTerms: "Mot-clé", + dataReasonFollowing: "Vous soutenez cet habitant", + dataReasonTribe: "Partage une tribu avec vous", + dataReasonPinned: "Correspond à un contenu que vous avez épinglé", + dataReasonCv: "Correspond aux compétences de votre CV", + dataReasonContent: "Lié à ce que vous publiez", + dataReasonRelated: "Lié à vos centres d’intérêt", + dataReasonAlike: "Publié par un habitant qui vous ressemble", + dataReasonSupportsYou: "Vous soutient", + dataReasonMutual: "Soutien mutuel", + dataReasonRated: "Bien noté par le réseau", + dataReasonNear: "Près de chez vous", dataStatPeople: "CV", dataStatConnected: "Connectés", dataStatSkills: "Compétences", @@ -1430,7 +1677,7 @@ module.exports = { pollStatusLabel: "Statut", pollStatusOpen: "OUVERT", pollStatusClosed: "FERMÉ", - pollSearchPlaceholder: "Rechercher des sondages...", + pollSearchPlaceholder: "Rechercher dans Sondages...", pollsNoItems: "Aucun sondage trouvé.", viewPoll: "Voir le Sondage", pollChatCreate: "Créer un Sondage", @@ -1443,7 +1690,7 @@ module.exports = { blogFilterFavorites: "FAVORIS", blogFilterTop: "TOP", blogCreateButton: "Créer un Blog", - blogSearchPlaceholder: "Rechercher des blogs...", + blogSearchPlaceholder: "Rechercher dans Blogs...", blogComments: "Commentaires", blogAllowComments: "Autoriser les commentaires", blogCommentsClosed: "L'auteur a fermé les commentaires de ce blog.", @@ -1456,12 +1703,11 @@ module.exports = { forumCreateButton: "Créer un forum", forumCreateSectionTitle: "Créer un forum", forumDescription: "Discutez ouvertement avec d’autres habitants de votre réseau.", - forumSearchPlaceholder: "Rechercher des forums...", + forumSearchPlaceholder: "Rechercher dans Forums...", forumFilterAll: "TOUS", forumFilterMine: "MIENS", forumFilterRecent: "RÉCENTS", forumFilterTop: "TOP", - forumDeleteButton: "Supprimer", forumParticipants: "participants", forumMessages: "messages", forumMessageLabel: "Message", @@ -1488,7 +1734,6 @@ module.exports = { imageFilterGallery: "GALERIE", imageCreateButton: "Téléverser une image", imageUpdateButton: "Mettre à jour", - imageDeleteButton: "Supprimer", imageAddFavoriteButton: "Ajouter aux favoris", imageRemoveFavoriteButton: "Retirer des favoris", imageFileLabel: "Sélectionnez un fichier image (.jpeg, .jpg, .png, .gif)", @@ -1500,13 +1745,14 @@ module.exports = { imageDescriptionPlaceholder: "Facultatif", imageNoFile: "Aucun fichier image fourni", noImages: "Aucune image disponible.", - imageSearchPlaceholder: "Rechercher par titre, tags, description, auteur…", + imageSearchPlaceholder: "Rechercher dans Images...", imageSortRecent: "Les plus récentes", imageSortOldest: "Les plus anciennes", imageSortTop: "Les plus votées", imageSearchButton: "Rechercher", imageUpdatedAt: "Mis à jour", imageNoMatch: "Aucune image ne correspond à votre recherche.", + feedSearchPlaceholder: "Rechercher dans Fil...", feedTitle: "Fil", feedDetailTitle: "Fil", feedOpenDiscussion: "Ouvrir la discussion", @@ -1525,7 +1771,7 @@ module.exports = { alreadyRefeeded: "Vous avez déjà repartagé ceci.", activityTitle: "Activité", yourActivity: "Votre activité", - globalActivity: "Activité globale", + globalActivity: "Activité", activityList: "Activité", activityDesc: "Découvrez l’activité récente de votre réseau.", allButton: "TOUT", @@ -1539,7 +1785,8 @@ module.exports = { meme: "Mème", typeRecent: "RÉCENT", typeTop: "TOP", - typePost: "BLOGS", + typePost: "BLOG", + activityUpdateLabel: "MISE À JOUR", typeTribe: "TRIBUS", typeLarp: "L.A.R.P.", typeInhabitants: "HABITANTS", @@ -1567,7 +1814,7 @@ module.exports = { typeTombstone: "SUPPRIMÉ", typeBanking: "BANQUE", typeBankWallet: "BANQUE/PORTEFEUILLE", - typeBankClaim: "BANQUE/UBI", + typeBankClaim: "BANQUE/RBU", typeKarmaScore: "KARMA", typeParliament: "PARLEMENT", typeSpread: "SOUTIENS", @@ -1602,7 +1849,7 @@ module.exports = { location: "Lieu", contentWarning: "Sujet", bankWalletConnected: "Portefeuille ECOin", - bankUbiReceived: "UBI reçu", + bankUbiReceived: "RBU reçue", bankTx: "Tx", bankEpochShort: "Époque", viewDetails: "Voir les détails", @@ -1673,7 +1920,7 @@ module.exports = { courtsNoCases: 'Aucun dossier.', reportsTitle: "Rapports", reportsDescription: "Gérez et suivez les signalements de votre réseau.", - reportsSearchPlaceholder: "Rechercher des rapports...", + reportsSearchPlaceholder: "Rechercher dans Rapports...", reportsFilterAll: "TOUS", reportsFilterRecent: "RÉCENTS", reportsFilterTop: "TOP", @@ -1721,7 +1968,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Rapports en révision", reportsResolvedSectionTitle: "Rapports résolus", reportsInvalidSectionTitle: "Rapports invalides", - reportsTemplateSectionTitle: 'Modèle de signalement', reportsBugTemplateTitle: 'Détails de reproduction (Bugs)', reportsFeatureTemplateTitle: 'Détails (Fonctionnalités)', reportsAbuseTemplateTitle: 'Détails (Abus)', @@ -1774,8 +2020,6 @@ module.exports = { tribeGallerySectionTitle: "Galerie de tribus", tribeRecentSectionTitle: "Tribus récentes", tribeTopSectionTitle: "Tribus populaires", - tribeviewTribeButton: "Visiter la tribu", - tribeviewSubTribeButton: "Visiter la sous-tribu", tribeRootLabel: "RACINE", tribeDescription: "Explorez ou créez des tribus dans votre réseau.", tribeMainTribeLabel: "TRIBU PRINCIPALE", @@ -1784,19 +2028,28 @@ module.exports = { tribeDeleteButton: "Supprimer", tribeImageLabel: "Télécharger un média (max: 50 Mo)", tribeTitleLabel: "Titre", - searchTribesPlaceholder: "FILTRER les tribus PAR NOM …", + searchTribesPlaceholder: "Rechercher dans Tribus...", tribeTitlePlaceholder: "Nom de la tribu", tribeDescriptionLabel: "Description", tribeDescriptionPlaceholder: "Décrivez cette tribu", tribeLocationLabel: "Localisation", tribeLocationPlaceholder: "Où se situe cette tribu ?", tribeTagsLabel: "Étiquettes", - tribeTagsPlaceholder: "Saisissez des étiquettes séparées par des virgules", + tribeTagsPlaceholder: "Saisissez des tags séparés par des virgules", tribeInviteMode: "Mode d’invitation", tribeModeLabel: "MODE", tribeIsAnonymousLabel: "STATUT", tribeMembersCount: "Membres", tribeEnterInvite: "SAISIR LE CODE", + subscriptionTitle: "Abonnements", + subscriptionOn: "Abonné", + subscriptionOff: "Non abonné", + subscriptionSubscribe: "S'abonner", + subscriptionUnsubscribe: "Se désabonner", + subscriptionSubscribers: "Abonnés", + pmListLabel: "Envoyer à", + pmListNone: "— Aucune liste —", + pmListNoSubscribers: "Aucun abonné", tribeLeaveButton: "QUITTER", tribePublic: "PUBLIQUE", tribePrivate: "PRIVÉE", @@ -1829,16 +2082,16 @@ module.exports = { tribeSectionVideos: "VIDÉOS", tribeSectionDocuments: "DOCUMENTS", tribeSectionBookmarks: "SIGNETS", - tribeSectionMaps: "MAPS", + tribeSectionMaps: "CARTES", tribeSectionPads: "PADS", tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionCalendars: "CALENDRIERS", + tribePadCreate: "Créer un pad", + tribeChatCreate: "Créer un chat", + tribeCalendarCreate: "Créer un calendrier", + tribePadsEmpty: "Aucun pad pour l'instant.", + tribeChatsEmpty: "Aucun chat pour l'instant.", + tribeCalendarsEmpty: "Aucun calendrier pour l'instant.", tribeInhabitantsEmpty: "Aucun habitant dans cette tribu pour l'instant.", tribeEventCreate: "Créer un événement", tribeEventsEmpty: "Aucun événement pour l'instant.", @@ -1893,9 +2146,14 @@ module.exports = { tribeMediaTypeDocument: "Document", tribeMediaTypeBookmark: "Signet", tribeContentDelete: "SUPPRIMER", + tribeReachTribe: "TRIBU", + tribeReachNotice: "OASIS le montre à tous les habitants et CLEARNET aussi sur le web ouvert. L'ouvrir laisse une copie lisible hors de la tribu : revenir à TRIBU le masque, mais les copies déjà partagées ne peuvent pas être effacées.", + tribeReachPrivate: "Le contenu d'une tribu privée ne peut rester que dans la tribu.", + tribeReachOpenedBy: "Ouvert par", + tribeReachChange: "Où c'est publié", tribeInviteCodeText: "Code d'invitation : ", oasisVersionLabel: "Version d’Oasis", - tribeInviteCodeHint: "Partagez ce code avec la personne que vous souhaitez inviter. Elle peut rejoindre via /invites → Tribes.", + tribeInviteCodeHint: "Partagez ce code avec la personne que vous souhaitez inviter. Elle peut rejoindre via /invites.", tribeStatusOpen: "OUVERT", tribeStatusClosed: "FERMÉ", tribeStatusInProgress: "EN COURS", @@ -1935,7 +2193,7 @@ module.exports = { tribeSearchMinChars: "Entrez au moins 2 caractères pour rechercher.", agendaTitle: "Agenda", agendaDescription: "Ici vous pouvez trouver tous vos éléments assignés.", - agendaSearchPlaceholder: "Rechercher dans l’agenda...", + agendaSearchPlaceholder: "Rechercher dans Agenda...", agendaFilterAll: "TOUS", agendaFilterToday: "AUJOURD’HUI", agendaFilterUpcoming: "À VENIR", @@ -1956,6 +2214,7 @@ module.exports = { agendaNoItems: "Aucune affectation trouvée.", agendaDiscardButton: "Écarter", agendaRestoreButton: "Restaurer", + agendaRemoveButton: "Retirer", agendaMembersCount: "Membres", agendaLocationLabel: "Localisation", agendaYes: "OUI", @@ -2022,8 +2281,8 @@ module.exports = { pmComposeTitle: "Envoyer un message", pmRecipients: "Destinataires", pmRecipientsHint: "Saisissez les IDs Oasis séparés par des virgules", - pmLimitsHint: "Jusqu'à 7 destinataires par message.", - pmTextPlaceholder: "Corps limité à 7000 caractères.", + pmLimitsHint: "Jusqu'à 7 habitants par message.", + pmTextPlaceholder: "Écris ton message.", pmSubject: "Sujet", pmSubjectHint: "Saisissez l’objet du message", pmText: "Message", @@ -2045,8 +2304,8 @@ module.exports = { pmCrypterCharsLabel: "caractères", pmInvalidRecipients: "Identifiant Oasis invalide (devrait ressembler à @…=.ed25519).", pmEncryptionLabel: "Chiffrement", - private: "Privés", - privateDescription: "Vos messages chiffrés.", + private: "Boîte", + privateDescription: "Gérez vos messages privés.", privateInbox: "BOÎTE DE RÉCEPTION", privateSent: "ENVOYÉS", privateDelete: "Supprimer", @@ -2055,6 +2314,7 @@ module.exports = { pmReply: "Répondre", pmReplies: "réponses", inReplyTo: "EN RÉPONSE À", + forumInThread: "DANS LE FIL", pmPreview: "Aperçu", pmPreviewTitle: "Aperçu", performed: "→", @@ -2106,20 +2366,19 @@ module.exports = { blockchainBack: 'Retour à l’explorateur', banking: 'Banque', bankingTitle: 'Banque', - bankingDescription: "L'économie ECOin : solde, revenu de base, taxes et valeur de l'industrie.", + bankingDescription: "L'économie collective d'ECOin.", bankOverview: 'Aperçu', bankEpochs: 'Époques', bankRules: 'Règles', pending: 'En attente', closed: 'Fermées', - bankViewTx: 'Voir la Tx', bankClaimNow: 'Réclamer', - bankClaimUBI: 'Réclamer RBU !', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: 'Réclamer RBU ', + bankClaimAndPay: "Réclamer et payer", + bankClaimedPending: "Réclamation en attente...", + bankStatusClaimed: "Réclamé", + bankStatusExpired: "Expiré", + bankPubOnly: "Opération réservée aux PUBs", bankEpoch: 'Époque', bankPool: 'Fonds (cette époque)', bankWeightsSum: 'Somme des poids', @@ -2138,8 +2397,8 @@ module.exports = { bankViewEpoch: 'Voir l’époque', bankYourIndustryBalance: "Votre part de l'industrie", bankYourUbiMonth: "Votre RBU (ce mois-ci)", - bankYourFundsMonth: "Vos fonds (ce mois-ci)", - bankIndustryBalance: "Production industrielle (estimée)", + bankYourFundsMonth: "Total des fonds", + bankIndustryBalance: "Production industrielle", bankUserBalance: 'Votre solde', bankAddresses: 'Adresses', bankNoAddresses: 'Aucune adresse trouvée.', @@ -2147,6 +2406,10 @@ module.exports = { bankAddress: 'Adresse', bankAddAddressTitle: 'Ajouter une adresse ECOIN', bankAddAddressUser: 'ID d’Oasis', + bankAddAddressLabel: "Étiquette (facultatif)", + bankAddAddressLabelPlaceholder: "Portefeuille de Bob, boutique d'Alice…", + bankAddAddressUserOptional: "Oasis ID (facultatif)", + bankAddressUnnamed: "Adresse sans nom", bankAddAddressAddress: 'Adresse ECOIN', bankAddAddressSave: 'Enregistrer', bankAddressAdded: 'Adresse ajoutée', @@ -2164,12 +2427,12 @@ module.exports = { bankAddressDeleteConfirm: 'Supprimer cette adresse ?', search: 'Rechercher !', bankLocal: 'Local', - bankFromOasis: 'Oasis', + bankFromOasis: "Réseau", bankingUserEngagementScore: "Score KARMA", pubIdLabel: "PUB ID", bankUbiAvailableNo: "PAS DE FONDS!", bankUbiAvailableOk: "DISPONIBLE!", - bankUbiAvailability: "RBU (réseau)", + bankUbiAvailability: "RBU", bankAlreadyClaimedThisMonth: "Déjà réclamé ce mois-ci", bankUbiThisMonth: "RBU (ce mois)", bankUbiLastClaimed: "RBU (dernière réclamation)", @@ -2199,6 +2462,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Chiffré en transit", shopAddFavorite: "Ajouter Favori", shopRemoveFavorite: "Retirer Favori", shopOpen: "OUVERTE", @@ -2206,6 +2470,13 @@ module.exports = { shopMakePrivate: "RENDRE PRIVÉE (E2E)", shopMakePublic: "RENDRE PUBLIQUE", shopProducts: "Produits", + shopFilterAll: "TOUTES", + shopFilterRecent: "RÉCENTES", + shopFilterMine: "MIENNES", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUITS", + shopFilterPrices: "PRIX", + shopFilterFavorites: "FAVORIS", shopProductAdd: "Ajouter Produit", shopProductTitle: "Produit", shopProductUpdate: "Modifier Produit", @@ -2233,7 +2504,7 @@ module.exports = { marketVisitItem: "VOIR L’ARTICLE", shopStatus: "STATUT", shopCreatedAt: "CRÉÉ", - shopSearchPlaceholder: "Rechercher des boutiques...", + shopSearchPlaceholder: "Rechercher dans Boutiques...", shopUrl: "URL", shopLocation: "Localisation", shopTags: "Étiquettes", @@ -2243,15 +2514,17 @@ module.exports = { shopTitlePlaceholder: "Nom de votre boutique", shopDescriptionPlaceholder: "Description détaillée de votre boutique", shopLocationPlaceholder: "Ville, Pays", - shopTagsPlaceholder: "tag1, tag2, tag3", + shopTagsPlaceholder: "Saisissez des tags séparés par des virgules", mapTitlePlaceholder: "Titre de la carte", shopProductFeatured: "Produit Vedette", - shopSendToMarket: "Send to Market", + shopSendToMarket: "Envoyer au marché", typeShop: "BOUTIQUES", typeShopProduct: "PRODUIT BOUTIQUE", bankExchange: 'Échange', bankExchangeCurrentValue: 'Valeur d’ECOin (1h)', - bankTotalSupply: 'Offre totale d’ECOin', + bankTotalSupply: "Offre ECOin totale", + bankPubsSupply: "Offre ECOin de la réserve des PUBs", + bankHoldingSupply: "Offre ECOin hors RBU", bankHoursOfWork: 'heures', bankUnitMs: "ms", bankUnitSeconds: "secondes", @@ -2265,14 +2538,15 @@ module.exports = { bankExchangeChartSupply: 'Offre', bankExchangeChartInflation: 'Inflation %', bankExchangeChartEmpty: 'Pas encore assez d’échantillons — reviens plus tard', - bankCurrentSupply: 'Offre actuelle d’ECOin', + bankCurrentSupply: "Offre ECOin actuelle", bankingSyncStatus: 'État d’ECOin', bankingSyncStatusSynced: 'Synchronisé', - bankingSyncStatusOutdated: 'Obsolète', + bankingSyncStatusOutdated: "Non synchronisé", statsTitle: 'Statistiques', statistics: "Statistiques", statsDescription: "Découvrez des statistiques sur votre réseau.", ALLButton: "TOUT", + RECENTButton: "RÉCENTS", MINEButton: "MIENS", TOMBSTONEButton: "SUPPRESSIONS", statsCreatedAt: "Créé le", @@ -2280,7 +2554,8 @@ module.exports = { statsYourOpinions: "Avis", statsNetworkContent: "Contenu", statsNetworkOpinions: "Avis", - statsEcoWalletLabel: "Portefeuille ECOIN", + statsEcoWalletLabel: "Portefeuille ECOin", + walletAddressCopyHint: "Cliquez pour sélectionner, puis copiez", statsEcoWalletNotConfigured: "Non configuré !", statsBookmark: "Signets", statsEvent: "Événements", @@ -2302,6 +2577,8 @@ module.exports = { statsTransfer: "Transferts", statsAiExchange: "IA", statsPUBs: 'PUBs', + statsSyncedPeers: "Nœuds", + statsTotalLabel: "Total", statsPost: "Publications", statsSize: "Total (taille)", statsBlockchainSize: "Blockchain (taille)", @@ -2353,8 +2630,14 @@ module.exports = { aiPrompt: "Fournissez une réponse informative et précise.", aiConfiguration: "Configurer le prompt", aiPromptUsed: "Invite", - aiClearHistory: "Effacer l’historique du chat", - aiServerError: "L’IA n’a pas pu répondre. Réessayez.", + aiClearHistory: "Effacer l'historique", + aiServerError: "Je ne peux pas répondre correctement. Réessayez.", + aiStatusLoading: "42 charge le modèle. Réessayez dans un instant.", + aiThinking: "Réflexion", + aiWaiting: "En attente", + aiStatusMissingModel: "Le modèle d'IA n'est pas installé : src/AI/oasis-42-1-chat.Q4_K_M.gguf est absent.", + aiExportFineTuning: "Exporter le Fine-Tuning", + aiFactsTitle: "Données de votre réseau", aiInputPlaceholder: "Qu'est-ce qu'Oasis ?", typeAiExchange: "IA", aiApproveTrain: "Ajouter à l’entraînement collectif", @@ -2383,7 +2666,7 @@ module.exports = { marketFilterDiscarded: "JETÉ", marketFilterRecent: "RÉCENT", marketFilterMyBids: "ENCHÈRES", - marketCreateButton: "Créer un article", + marketCreateButton: "Vendre quelque chose", marketItemType: "Type", marketItemTitle: "Titre", marketItemAvailable: "Date limite", @@ -2394,7 +2677,7 @@ module.exports = { marketItemCondition: "État", marketItemPrice: "Prix", marketItemTags: "Étiquettes", - marketItemTagsPlaceholder: "Entrez des tags séparés par des virgules", + marketItemTagsPlaceholder: "Saisissez des tags séparés par des virgules", marketItemDeadline: "Date limite", marketItemIncludesShipping: "Livraison incluse ?", marketItemHighestBid: "Meilleure offre", @@ -2411,7 +2694,7 @@ module.exports = { marketNoItems: "Aucun article disponible pour le moment.", marketYourBid: "Votre offre", marketCreateFormImageLabel: "Télécharger un média (max: 50 Mo)", - marketSearchPlaceholder: "Rechercher par titre ou tags", + marketSearchPlaceholder: "Rechercher dans Marché...", marketMinPriceLabel: "Prix minimum", marketMaxPriceLabel: "Prix maximum", marketSortRecent: "Les plus récents", @@ -2477,8 +2760,7 @@ module.exports = { housingSetClosed: "Rendre indisponible", housingCreateButton: "Créer un lieu", housingUpdateButton: "Mettre à jour", - housingDeleteButton: "Supprimer", - housingSearchPlaceholder: "Rechercher des lieux...", + housingSearchPlaceholder: "Rechercher dans Logement...", housingSearchButton: "Rechercher", housingMinPrice: "Prix min", housingMaxPrice: "Prix max", @@ -2556,12 +2838,11 @@ module.exports = { jobsExchangeSkill: "Compétence demandée en échange", jobTimePartial: "Partiel", jobTimeComplete: "Complet", - jobsDeleteButton: "SUPPRIMER", jobsUpdateButton: "METTRE À JOUR", jobsFilterApplied: "POSTULÉ", jobsAppliedTitle: "Mes candidatures", jobsAppliedBadge: "Candidaté", - jobsSearchPlaceholder: "Rechercher titre, tags, description...", + jobsSearchPlaceholder: "Rechercher dans Offres d’emploi...", jobsMinSalaryLabel: "Salaire min.", jobsMaxSalaryLabel: "Salaire max.", jobsSortRecent: "Plus récents", @@ -2573,15 +2854,15 @@ module.exports = { jobSetOpen: "Marquer ouvert", jobsTagsLabel: "Étiquettes", noJobsMatch: "Aucune offre ne correspond à votre recherche.", - industrySearchPlaceholder: "Rechercher des usines…", + industrySearchPlaceholder: "Rechercher dans Industrie...", industryAllSectors: "Tous les secteurs", industryVisitButton: "Visiter l’industrie", industryAdvanceTo: "Passer à", industryAmount: "Montant", industryApproveBuild: "Approuver", - industryBlueprint: "Blueprint", - industryBlueprints: "Blueprints", - industryBuild: "Build", + industryBlueprint: "Plan", + industryBlueprints: "Plans", + industryBuild: "Production", industryBuildNotes: "Notes", industryBuildStart: "Date de début", industryVoteUpdateButton: "Voter la mise à jour", @@ -2589,7 +2870,7 @@ module.exports = { industryRevokeVote: "Révoquer", industryTimeLeft: "Temps restant", industryBuildEnd: "Date de fin", - industryBuilds: "Builds", + industryBuilds: "Productions", industryBuildTitle: "Titre", industryContribute: "Contribuer", industryContributeButton: "Contribuer", @@ -2647,7 +2928,6 @@ module.exports = { industryBuildStatus_FAILED: "ÉCHOUÉ", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Une offre correspond à votre CV", jobsBotMatchIntro: "Une offre correspond à votre CV.", jobsBotMatchJob: "Emploi", @@ -2662,7 +2942,7 @@ module.exports = { housingBotYouRequestedTitle: "Vous avez demandé un lieu.", housingBotYouCancelledTitle: "Vous avez annulé une demande.", housingBotUnavailableTitle: "Un lieu que vous aviez demandé n’est plus disponible.", - housingBotUnavailableSentTitle: "Vous avez prévenu les personnes qui avaient demandé ce lieu.", + housingBotUnavailableSentTitle: "Vous avez prévenu les habitants qui avaient demandé ce lieu.", industryBotAdmittedTitle: "Vous avez été admis dans une usine.", industryBotApplicationTitle: "Nouvelle demande d'adhésion dans votre usine.", industryBotInvitedTitle: "Vous avez été invité dans une usine.", @@ -2674,7 +2954,7 @@ module.exports = { industryRulesIntro: "Industry permet aux habitants de posséder collectivement les moyens de production : une Usine est un atelier du réseau que personne ne possède à titre privé.", industryRulesSteward: "Le fondateur est l'intendant — un gardien, pas un propriétaire : il ne peut ni vendre, ni privatiser, ni dissoudre l'Usine seul.", industryRulesMembership: "La politique d'adhésion définit comment on rejoint : Ouverte (adhésion immédiate), Par vote (admis par le vote des membres) ou Sur invitation (un membre doit inviter d'abord).", - industryRulesQuorum: "Le quorum est le nombre minimum de membres qui doivent voter pour qu'une décision compte, afin qu'une petite Usine ne soit pas décidée par une seule personne.", + industryRulesQuorum: "Le quorum est le nombre minimum de membres qui doivent voter pour qu'une décision compte, afin qu'une petite Usine ne soit pas décidée par un seul habitant.", industryRulesMajority: "La majorité est la fraction de votes nécessaire (0,5 = moitié, 1 = unanimité) ; une décision passe quand les OUI atteignent au moins max(quorum, membres × majorité).", industryRulesDecisions: "Les membres votent pour admettre de nouveaux venus, approuver les builds et dissoudre l'Usine ; l'intendant ne peut pas passer outre ces décisions collectives.", industryRulesBlueprints: "Les Blueprints sont des recettes libres (COPYLEFT) — les intrants (matériaux, heures de travail, compétences) et la sortie (un bien physique ou numérique) — et chacun peut les forker.", @@ -2693,13 +2973,14 @@ module.exports = { industryPauseVotes: "Votes de statut", industryTitle: "Industrie", industryDescription: "Module pour gérer collectivement les moyens de production.", - typeIndustryBuild: "BUILD", - typeIndustryBlueprint: "BLUEPRINT", + typeIndustryBuild: "PRODUCTION", + typeIndustryBlueprint: "PLAN", typeIndustryAllocation: "DISTRIBUTION", typeIndustry: "INDUSTRIE", modulesIndustryLabel: "Industrie", modulesIndustryDescription: "Module pour gérer collectivement les moyens de production.", industryFilterAll: "TOUTES", + industryFilterRecent: "RÉCENTS", industryFilterMember: "MEMBRE", industryFilterBlueprints: "PLANS", industryFilterBuilds: "PRODUCTIONS", @@ -2708,6 +2989,7 @@ module.exports = { industryFilterPaused: "EN PAUSE", industryFilterDissolved: "DISSOUTES", industryAllTitle: "Industrie", + industryRecentTitle: "Usines récentes", industryMemberTitle: "Usines dont je fais partie", industryMineTitle: "Mes usines", industryActiveTitle: "En marche", @@ -2755,7 +3037,7 @@ module.exports = { industryPolicy_invite: "Sur invitation", projectsTitle: "Projets", projectsDescription: "Créez, financez et suivez des projets communautaires dans votre réseau.", - projectSearchPlaceholder: "Rechercher des projets...", + projectSearchPlaceholder: "Rechercher dans Projets...", projectCreateProject: "Créer un projet", projectCreateButton: "Créer un projet", projectUpdateButton: "METTRE À JOUR", @@ -2850,15 +3132,13 @@ module.exports = { modulesModuleName: "Nom", modulesModuleDescription: "Description", modulesModuleStatus: "Statut", - modulesTotalModulesLabel: "Modules chargés", + modulesTotalModulesLabel: "Total", modulesEnabledModulesLabel: "Activés", modulesDisabledModulesLabel: "Désactivés", modulesInvitesLabel: "Invitations", modulesInvitesDescription: "Module pour gérer et appliquer des codes d'invitation.", modulesWalletLabel: "Portefeuille", modulesWalletDescription: "Module pour gérer vos actifs numériques (ECOin).", - modulesLegacyLabel: "Héritage", - modulesLegacyDescription: "Module pour gérer rapidement et en toute sécurité votre secret (clé privée).", modulesCipherLabel: "Chiffreur", modulesCipherDescription: "Module pour crypter et décrypter votre texte de manière symétrique (en utilisant un mot de passe partagé).", modulesBookmarksLabel: "Signets", @@ -2920,20 +3200,23 @@ module.exports = { modulesProjectsLabel: "Projets", modulesProjectsDescription: "Module pour explorer, financer et gérer des projets.", modulesBankingLabel: "Banque", - modulesBankingDescription: "Module pour connaître la valeur réelle de ECOIN et distribuer une RBU en utilisant la trésorerie commune.", + modulesBankingDescription: "Module pour gérer le trésor commun : valeur de l’ECOIN, taxes et RBU.", modulesFavoritesLabel: "Favoris", modulesFavoritesDescription: "Module pour gérer votre contenu favori.", fileTooLargeTitle: "Fichier trop volumineux", fileTooLargeMessage: "Le fichier dépasse la taille maximale autorisée (50 Mo). Veuillez sélectionner un fichier plus petit.", goBack: "Retour", + errorDismiss: "OK", errorPageTitle: "Une erreur est survenue", + errorNoAllocation: "Aucune allocation trouvée pour cette réclamation.", + errorInvalidClaim: "Cette allocation ne peut pas être réclamée.", visibilityLabel: "Visibilité", + clearnetPrivateConflict: "Un contenu privé, masqué, fermé, sur invitation ou payant ne peut pas être publié sur la CLEARNET. Choisis OASIS ou rends-le public.", visibilityPublic: "Publique", visibilityHidden: "Cachée", visibilityMakePublic: "Rendre publique", visibilityMakeHidden: "Cacher", invitesInhabitantsTitle: "Habitants", - invitesInhabitantsFollow: "Suivre", aiNavPlaceholder: "Décrivez ce qu'il vous faut...", aiNavDisabled: "La navigation par IA est désactivée.", aiNavResultsTitle: "Suggestions AINav", @@ -2944,20 +3227,20 @@ module.exports = { modulesAINavDescription: "Module pour des requêtes en langage naturel sur le contenu du réseau.", directConnectDescription: "Connectez-vous directement à un pair en saisissant son adresse IP, port et clé publique. Le pair sera ajouté comme connexion suivie.", peerHost: "IP", - peerPort: "Port (par défaut : 8008)", + peerPort: "Port", peerPublicKey: "Clé publique (@...ed25519)", connectAndFollow: "Connecter", modulesPresetTitle: "Configurations Communes", - workflowsTitle: "Workflows", + workflowsTitle: "Flux de travail", workflowsDescription: "Un workflow définit le thème et les modules actifs.", workflowsSet: "Appliquer le workflow", workflow_default: "Par défaut", workflow_jobs: "Emploi", - workflow_ruling: "Gouvernance", - workflow_politics: "Politique", workflow_social: "Social", workflow_business: "Affaires", workflow_night: "Nuit", + workflow_kids: "Enfants", + workflow_activists: "Activisme", workflow_mobile: "Mobile", statsCarbonFootprintTitle: "Empreinte Carbone", statsCarbonTombstone: "Empreinte du tombstoning", @@ -2970,12 +3253,13 @@ module.exports = { visitContent: "Visiter le Contenu", favoriteAdd: "Épingler aux Favoris", pmContentTooltip: "Envoyer un Message Privé", + phoneCallTooltip: "Passer un Appel", favoriteRemove: "Retirer des Favoris", reportContent: "Signaler le Contenu", - bankEcoinHours: "Équivalence ECOin en temps", + bankEcoinHours: "Équivalence ECOin en temps (1 ECO)", mapsLabel: "Cartes", mapTitle: "Cartes", - mapDescription: "Explorez et gérez des cartes hors ligne dans votre réseau.", + mapDescription: "Gérez et partagez des cartes hors ligne dans votre réseau.", mapMineSectionTitle: "Vos Cartes", mapCreateSectionTitle: "Créer une Carte", mapUpdateSectionTitle: "Modifier la Carte", @@ -3004,7 +3288,7 @@ module.exports = { mapTypeOpen: "OUVERT", mapTypeClosed: "FERMÉ", mapTagsLabel: "Étiquettes", - mapTagsPlaceholder: "Entrez des étiquettes séparées par des virgules", + mapTagsPlaceholder: "Saisissez des tags séparés par des virgules", mapUrlLabel: "URL de la carte", mapMarkersTitle: "Marqueurs", mapMarkerDefault: "Marqueur", @@ -3015,9 +3299,22 @@ module.exports = { markerImageLabel: "Image du Marqueur", mapAddMarkerTitle: "Ajouter un Marqueur", mapAddMarkerButton: "Ajouter un Marqueur", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Effacer le marqueur", mapApplyZoom: "Appliquer Zoom", - mapSearchPlaceholder: "Chercher description, étiquettes, auteur...", + mapPlaceSearchPlaceholder: "Trouver un lieu ou un pays...", + mapNoPlaces: "Aucun lieu trouvé.", + mapClusterTitle: "marqueurs ici, cliquez pour zoomer", + mapDeleteMarker: "Supprimer le marqueur", + mapZoomIn: "Zoom avant", + mapZoomOut: "Zoom arrière", + mapFitMarkers: "Ajuster à tous les marqueurs", + mapPanLabel: "Déplacer la carte", + mapCenterHere: "Centrer ici", + mapNear: "près de", + mapPickedSpot: "Point choisi", + mapPickHint: "Cliquez sur la carte pour choisir un point.", + mapClearPick: "Effacer", + mapSearchPlaceholder: "Rechercher dans Cartes...", mapSearchButton: "Chercher", mapUpdatedAt: "Mis à jour", mapNoMatch: "Aucune carte ne correspond à votre recherche.", @@ -3028,31 +3325,602 @@ module.exports = { typeMapMarker: "MARQUEUR DE CARTE", modulesMapLabel: "Cartes", modulesMapDescription: "Module pour gérer et partager des cartes hors ligne.", + emergenciesTitle: "Urgences", + modulesEmergenciesLabel: "Urgences", + modulesEmergenciesDescription: "Module pour lancer, confirmer et suivre des urgences dans le réseau.", + emergenciesDescription: "Module pour lancer, confirmer et suivre des urgences dans le réseau.", + emergencyCreate: "Créer une urgence", + emergencyUpdate: "Mettre à jour", + emergencyDelete: "Supprimer", + emergencyTitleLabel: "Titre", + emergencyTitlePlaceholder: "Que se passe-t-il ?", + emergencyTextLabel: "Détails", + emergencyTextPlaceholder: "Où, depuis quand, que faire...", + emergencyCategoryLabel: "Catégorie", + emergencyCategoryWeather: "MÉTÉO", + emergencyCategoryInfrastructure: "INFRASTRUCTURE", + emergencyCategoryHealth: "SANTÉ", + emergencyCategorySecurity: "SÉCURITÉ", + emergencyCategoryLost: "OBJETS TROUVÉS", + emergencyCategoryNeighborhood: "VOISINAGE", + emergencyExpiresLabel: "Expire dans", + emergencyExpires1d: "1 jour", + emergencyExpires3d: "3 jours", + emergencyExpires7d: "1 semaine", + emergencyExpires30d: "1 mois", + emergencyExpiresNone: "Indéfini", + emergencyExpiresKeep: "Conserver la date actuelle", + emergencyTagsLabel: "Étiquettes", + emergencyTagsPlaceholder: "Saisissez des tags séparés par des virgules", + emergencyFilterAll: "TOUTES", + emergencyFilterMine: "MIENNES", + emergencyFilterRecent: "RÉCENTES", + emergencySeverityUnverified: "NON VÉRIFIÉE", + emergencySeverityLow: "FAIBLE", + emergencySeverityMedium: "MOYENNE", + emergencySeverityHigh: "ÉLEVÉE", + emergencyStatusActive: "ACTIVE", + emergencyStatusResolved: "RÉSOLUE", + emergencyStatusExpired: "EXPIRÉE", + emergencyConfirmations: "Confirmations", + emergencyConfirmButton: "Confirmer", + emergencyConfirmed: "Vous avez confirmé cette urgence", + emergencyResolveButton: "Résoudre", + emergencyReopenButton: "Rouvrir", + emergencyGenerateReport: "Générer le rapport", + emergencyUpdateEdited: "Modifiée", + emergencyLastUpdate: "DERNIÈRE MISE À JOUR", + emergencyUpdatesTitle: "Dernières mises à jour :", + emergencyNoUpdates: "Pas encore de mises à jour.", + emergencyAddUpdate: "Publier une mise à jour", + emergencyUpdatePlaceholder: "Qu’est-ce qui a changé ?", + emergencyNoItems: "Aucune urgence trouvée.", + emergencyExpiresAt: "Expire", + emergencyBannerLabel: "Urgence active", + emergencyCreateSectionTitle: "Créer une urgence", + emergencyUpdateSectionTitle: "Mettre à jour l’urgence", + emergencySearchPlaceholder: "Rechercher dans les urgences...", + typeEmergency: "URGENCES", + typeEmergencyConfirm: "CONFIRMATION D’URGENCE", + typeEmergencyUpdate: "MISE À JOUR D’URGENCE", + statsEmergency: "Urgences", + statsEmergencyConfirm: "Confirmations d’urgences", + statsEmergencyUpdate: "Mises à jour d’urgences", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Une urgence que vous suivez a une mise à jour.", + emergencyBotResolvedTitle: "Une urgence que vous suivez a été résolue.", + dataKindEmergencies: "URGENCES", + mailingTitle: "Listes de diffusion", + modulesMailingLabel: "Listes de diffusion", + modulesMailingDescription: "Découvrez et gérez des listes de diffusion dans votre réseau.", + modulesPhoneLabel: "Téléphone", + modulesPhoneDescription: "Module pour appeler d'autres habitants avec des appels vidéo chiffrés de bout en bout.", + mailingDescription: "Découvrez et gérez des listes de diffusion dans votre réseau.", + mailingCreate: "Créer une liste de diffusion", + mailingUpdate: "Mettre à jour", + mailingArchive: "Archiver", + mailingActivate: "Activer", + mailingCreateSectionTitle: "Créer une liste de diffusion", + mailingUpdateSectionTitle: "Mettre à jour la liste de diffusion", + mailingTitleLabel: "Titre", + mailingTitlePlaceholder: "Nom de la liste de diffusion", + mailingDescriptionLabel: "Description", + mailingDescriptionPlaceholder: "De quoi parle cette liste ?", + mailingTypeLabel: "Type de liste de diffusion", + mailingTypeOpen: "OUVERTE", + mailingTypeClosed: "FERMÉE", + mailingStatusLabel: "Statut", + mailingStatusActive: "ACTIVE", + mailingStatusArchived: "ARCHIVÉE", + mailingMembersLabel: "Inviter des habitants", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Abonnés", + mailingTagsLabel: "Étiquettes", + mailingTagsPlaceholder: "Saisissez des tags séparés par des virgules", + mailingFilterAll: "TOUTES", + mailingFilterMine: "MIENNES", + mailingFilterSubscribed: "ABONNÉES", + mailingFilterRecent: "RÉCENTES", + mailingHistoryDate: "DATE", + mailingHistoryThreads: "FILS", + mailingHistoryInhabitants: "HABITANTS", + mailingHistorySearchPlaceholder: "Rechercher dans l’historique...", + mailingNoMessages: "Pas encore de messages.", + mailingNoItems: "Aucune liste de diffusion trouvée.", + mailingSearchPlaceholder: "Rechercher dans les listes de diffusion...", + mailingWriteButton: "Écrire un message", + mailingWriteTitle: "Écrire à la liste", + mailingReplyTitle: "Répondre à la liste", + mailingSend: "Envoyer", + mailingTextPlaceholder: "Votre message atteint tous les participants de la liste.", + mailingParticipants: "Participants", + inboxFilterMailing: "Listes de diffusion", + mailingReplyPlaceholder: "Répondre à la liste...", + mailingReplyToList: "Répondre à la liste", + typeMailingList: "LISTE DE DIFFUSION", + statsMailingList: "Listes de diffusion", + mailingListLabel: "LISTE DE DIFFUSION", + logisticsTitle: "Logistique", + modulesLogisticsLabel: "Logistique", + modulesLogisticsDescription: "Découvrez et gérez des plans logistiques dans votre réseau.", + logisticsDescription: "Découvrez et gérez des plans logistiques dans votre réseau.", + logisticsCreate: "Créer une route", + logisticsUpdate: "Mettre à jour", + logisticsClose: "Clôturer", + logisticsReopen: "Rouvrir", + logisticsCreateSectionTitle: "Créer une route", + logisticsUpdateSectionTitle: "Mettre à jour la route", + logisticsTitleLabel: "Titre", + logisticsTitlePlaceholder: "Trajet vers le marché, camionnette disponible...", + logisticsKindLabel: "Type de logistique", + logisticsKindTrip: "TRAJET", + logisticsKindShipment: "ENVOI", + logisticsKindTrips: "TRAJETS", + logisticsKindShipments: "ENVOIS", + logisticsModeLabel: "Mode", + logisticsModeOffer: "J’OFFRE", + logisticsModeRequest: "JE CHERCHE", + logisticsModeOffers: "OFFRES", + logisticsModeRequests: "DEMANDES", + logisticsDescriptionLabel: "Description", + logisticsDescriptionPlaceholder: "Point de rendez-vous, ce que vous transportez, conditions...", + logisticsOriginLabel: "Origine", + logisticsOriginPlaceholder: "Point de départ", + logisticsDestinationLabel: "Destination", + logisticsDestinationPlaceholder: "Point d’arrivée", + logisticsDateLabel: "Date et heure", + logisticsRecurrenceLabel: "Récurrence", + logisticsFrequencyLabel: "Fréquence", + logisticsRecurrenceNone: "UNIQUE", + logisticsRecurrenceDaily: "QUOTIDIEN", + logisticsRecurrenceWeekly: "HEBDOMADAIRE", + logisticsRecurrenceMonthly: "MENSUEL", + logisticsSeatsLabel: "Places", + logisticsSizeLabel: "Taille", + logisticsSizePlaceholder: "ex. 2m x 1m", + logisticsWeightLabel: "Poids", + logisticsWeightPlaceholder: "ex. 40kg", + logisticsPriceFree: "GRATUIT", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TEMPS", + logisticsPriceLabel: "Prix", + logisticsHours: "h", + logisticsOrderRefLabel: "Lien de commande", + logisticsOrderRefPlaceholder: "/shops/... ou /market/... de la commande à livrer", + logisticsOrderLink: "Commande liée", + logisticsTagsLabel: "Étiquettes", + logisticsTagsPlaceholder: "Saisissez des tags séparés par des virgules", + logisticsFilterAll: "TOUTES", + logisticsFilterMine: "MIENNES", + logisticsFilterBooked: "RÉSERVÉES", + logisticsFilterRecent: "RÉCENTES", + logisticsFilterHistory: "HISTORIQUE", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "OUVERTE", + logisticsStatusUpcoming: "À VENIR", + logisticsStatusPast: "PASSÉES", + logisticsStatusClosed: "CLÔTURÉES", + logisticsZoneLabel: "Zone", + logisticsSearchPlaceholder: "Rechercher dans la logistique...", + logisticsNoItems: "Aucune route trouvée.", + logisticsBookingPending: "EN ATTENTE", + logisticsBookingConfirmed: "CONFIRMÉE", + logisticsBookingRejected: "REFUSÉE", + logisticsBookingCancelled: "ANNULÉE", + logisticsBookingDelivered: "LIVRÉE", + logisticsConfirm: "Confirmer", + logisticsReject: "Refuser", + logisticsDeliver: "Livrer", + logisticsReceiptLabel: "Reçu", + logisticsReceiptPlaceholder: "Note de livraison (facultatif)", + logisticsCancelBooking: "Annuler la réservation", + logisticsBookTitle: "Réserver cette route", + logisticsNotesLabel: "Notes", + logisticsNotesPlaceholder: "Ce que le responsable doit savoir", + logisticsBook: "Réserver", + logisticsBookingsTitle: "Réservations", + logisticsMyBookingTitle: "Votre réservation", + logisticsRatingsTitle: "Évaluations", + logisticsNoRatings: "Pas encore d’évaluations.", + logisticsRatingsWhenClosed: "Les évaluations s’ouvrent une fois la route clôturée.", + logisticsScoreLabel: "Note", + logisticsRatingPlaceholder: "Comment ça s’est passé ?", + logisticsRate: "Évaluer", + typeLogisticsRoute: "ROUTE", + typeLogisticsRating: "ÉVALUATION DE ROUTE", + statsLogisticsRoute: "Routes", + statsLogisticsRating: "Évaluations de routes", + logisticsRouteLabel: "ROUTE", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Module pour publier des chaînes de podcast.", + podcastsDescription: "Module pour publier des chaînes de podcast.", + podcastCreate: "Créer un podcast", + podcastUpdate: "Mettre à jour", + podcastDelete: "Supprimer", + podcastCreateSectionTitle: "Créer un podcast", + podcastUpdateSectionTitle: "Mettre à jour le podcast", + podcastTitleLabel: "Titre", + podcastTitlePlaceholder: "Nom de la chaîne", + podcastDescriptionLabel: "Description", + podcastDescriptionPlaceholder: "De quoi parle ce podcast ?", + podcastCategoryLabel: "Catégorie", + podcastCategoryNews: "ACTUALITÉS", + podcastCategoryMusic: "MUSIQUE", + podcastCategoryTalk: "DISCUSSION", + podcastCategoryEducation: "ÉDUCATION", + podcastCategoryTech: "TECH", + podcastCategoryCulture: "CULTURE", + podcastCategoryCommunity: "COMMUNAUTÉ", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "AUTRE", + podcastTagsLabel: "Étiquettes", + podcastCoverLabel: "Couverture (image ou vidéo)", + podcastEpisodeAudioLabel: "Fichier audio ou vidéo (max : 75MB)", + podcastTagsPlaceholder: "Saisissez des tags séparés par des virgules", + podcastFilterAll: "TOUS", + podcastFilterMine: "MIENS", + podcastFilterRecent: "RÉCENTS", + podcastFilterTop: "TOP", + podcastFilterViewers: "AUDITEURS", + podcastSearchPlaceholder: "Rechercher dans les podcasts...", + podcastNoItems: "Aucun podcast trouvé.", + podcastEpisodesLabel: "Épisodes", + podcastPlaysLabel: "Vues", + podcastLastEpisode: "Dernier épisode", + podcastEpisodesTitle: "Épisodes", + podcastNoEpisodes: "Pas encore d’épisodes.", + podcastEpisodeAdd: "Ajouter un épisode", + podcastEpisodeCreateTitle: "Publier un épisode", + podcastEpisodeUpdateTitle: "Mettre à jour l’épisode", + podcastEpisodeTitleLabel: "Titre de l’épisode", + podcastEpisodeTitlePlaceholder: "Titre de l’épisode", + podcastEpisodeDescriptionLabel: "Notes", + podcastEpisodeDescriptionPlaceholder: "Ce qui se passe dans cet épisode, liens, crédits...", + podcastEpisodePublish: "Publier", + podcastPlayed: "Écouté", + podcastMarkPlayed: "Marquer comme écouté", + typePodcast: "PODCAST", + typePodcastEpisode: "ÉPISODE", + typePodcastOpinion: "AVIS SUR L’ÉPISODE", + typePodcastPlay: "ÉCOUTE", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Épisodes", + statsPodcastPlay: "Écoutes", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Un blog que vous suivez a une nouvelle entrée.", + podcastBotEpisodeTitle: "Un podcast que vous suivez a un nouvel épisode.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "ÉPISODE", + campaignsTitle: "Campagnes", + modulesCampaignsLabel: "Campagnes", + modulesCampaignsDescription: "Découvrez et gérez des campagnes dans votre réseau.", + campaignsDescription: "Découvrez et gérez des campagnes dans votre réseau.", + campaignCreate: "Créer une campagne", + campaignUpdate: "Mettre à jour", + campaignDelete: "Supprimer", + campaignClose: "Clôturer", + campaignReopen: "Rouvrir", + campaignCreateSectionTitle: "Créer une campagne", + campaignUpdateSectionTitle: "Mettre à jour la campagne", + campaignTitleLabel: "Titre", + campaignTitlePlaceholder: "Que voulez-vous obtenir ?", + campaignTextLabel: "Texte", + campaignTextPlaceholder: "Expliquez la cause, qui décide et ce que vous demandez...", + campaignCategoryLabel: "Catégorie", + campaignCategoryEnvironment: "ENVIRONNEMENT", + campaignCategoryRights: "DROITS", + campaignCategoryHealth: "SANTÉ", + campaignCategoryEducation: "ÉDUCATION", + campaignCategoryInfrastructure: "INFRASTRUCTURE", + campaignCategoryCulture: "CULTURE", + campaignCategoryEconomy: "ÉCONOMIE", + campaignCategoryOther: "AUTRE", + campaignGoalLabel: "Objectif de signatures", + campaignDeadlineLabel: "Échéance", + campaignTagsLabel: "Étiquettes", + campaignTagsPlaceholder: "Saisissez des tags séparés par des virgules", + campaignFilterAll: "TOUTES", + campaignFilterMine: "MIENNES", + campaignFilterSigned: "SIGNÉES", + campaignFilterRecent: "RÉCENTES", + campaignFilterTop: "TOP", + campaignStatusOpen: "OUVERTE", + campaignStatusAchieved: "ATTEINTE", + campaignStatusUnachieved: "NON ATTEINTE", + campaignStatusClosed: "CLÔTURÉE", + campaignSearchPlaceholder: "Rechercher dans les campagnes...", + campaignNoItems: "Aucune campagne trouvée.", + campaignSignaturesLabel: "Signatures", + campaignSignaturesTitle: "Soutiens", + campaignNoSignatures: "Pas encore de signatures.", + campaignSignTitle: "Signer cette campagne", + campaignSignPlaceholder: "Pourquoi la soutenez-vous ? (facultatif)", + campaignSign: "Signer", + campaignSigned: "Vous avez déjà signé !", + campaignNotSigned: "Il manque encore votre signature", + campaignElevate: "Porter au Parlement", + campaignElevated: "Portée au Parlement", + campaignUpdatesTitle: "Mises à jour", + campaignNoUpdates: "Pas encore de mises à jour.", + campaignUpdatePlaceholder: "Que s’est-il passé ?", + campaignAddUpdate: "Publier la mise à jour", + campaignQrAlt: "QR pour signer cette campagne", + campaignQrCaption: "Scannez pour signer", + typeCampaign: "CAMPAGNE", + typeCampaignSignature: "SIGNATURE", + typeCampaignUpdate: "MISE À JOUR DE CAMPAGNE", + statsCampaign: "Campagnes", + statsCampaignSignature: "Signatures de campagnes", + statsCampaignUpdate: "Mises à jour de campagnes", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Une campagne que vous suivez a une mise à jour.", + campaignBotAchievedTitle: "Une campagne que vous suivez a atteint son objectif.", + campaignBotRaisedTitle: "Une campagne que vous avez signée a été portée au Parlement.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Un trajet que vous avez réservé a changé.", + logisticsBotClosedTitle: "Un trajet que vous avez réservé a été fermé.", + logisticsBotBookedTitle: "Quelqu'un a réservé votre trajet.", + logisticsBotCancelledTitle: "Une réservation sur votre trajet a été annulée.", + logisticsBotConfirmedTitle: "Votre réservation est confirmée : le colis est en route.", + logisticsBotRejectedTitle: "Votre réservation a été refusée.", + logisticsBotDeliveredTitle: "Votre colis est arrivé.", + campaignLabel: "CAMPAGNE", + campaignGenerateReport: "Générer le rapport", + logisticsGenerateReport: "Générer le rapport", + mailingGenerateReport: "Générer le rapport", + campaignUpdateEdited: "Modifiée", + mailingMessagesLabel: "Messages", + logisticsBookingsLabel: "Réservations", + backupTitle: "Sauvegarde", + modulesBackupLabel: "Sauvegarde", + modulesBackupDescription: "Module pour exporter et importer vos clés et faire des sauvegardes chiffrées.", + backupDescription: "Module pour exporter et importer vos clés et faire des sauvegardes chiffrées.", + backupTypeRecovery: "RÉCUPÉRATION", + backupTypeKeys: "EXPORTER LES CLÉS", + backupTypeFull: "SAUVEGARDE COMPLÈTE", + backupTypeRestore: "RESTAURER LA SAUVEGARDE", + backupTypeInstant: "INSTANTANÉE", + backupInstantDescription: "Un .zip brut de tout votre registre et de vos fichiers, sans votre clé secrète et sans mot de passe. Rapide, mais ", + backupInstantRisk: "quiconque a le fichier lit tout.", + backupKitPageDescription: "Le kit de récupération ne peut être ouvert que depuis cet appareil.", + backupKitId: "ID Oasis", + backupKitDate: "Généré", + backupKitQrAlt: "Code QR de votre clé secrète", + backupKitStep1: "Imprimez-le ou recopiez-le et rangez-le en lieu sûr, hors ligne.", + backupKitStep2: "Pour récupérer, installez Oasis sur un nouvel appareil et importez ce secret depuis Sauvegarde › Clés.", + backupKitStep3: "Détruisez cette page si elle est un jour exposée.", + backupKitPdfButton: "Générer le PDF", + backupScopeLabel: "Portée", + backupScopeAll: "TOUT", + backupScopeMine: "SEULEMENT MON CONTENU", + backupSinceLabel: "Depuis", + backupEstimateTitle: "Estimation", + backupFileInfo: "Votre copie chiffrée sera téléchargée sur votre appareil (nom : oasis-backup-DATE.oasisbk)", + backupExportButton: "Télécharger la sauvegarde", + backupRestorePassword: "Saisissez le mot de passe utilisé lors de la sauvegarde", + backupRestoreButton: "Restaurer la sauvegarde", + backupRestoredLine: "Sauvegarde restaurée.", + backupRestoredMessages: "Messages ajoutés", + backupRestoredSkipped: "Déjà présents", + backupRestoredBlobs: "Fichiers ajoutés", + backupRestoredState: "Réglages restaurés", + backupRestoredFailed: "Échecs", + backupRestoredForked: "Divergents", + backupRestoreRunning: "Restauration de la copie en cours. Cette page se rafraîchit automatiquement ; vous pouvez continuer à utiliser Oasis.", + backupRestoreElapsed: "Écoulé", + backupRestoreFailedLine: "La copie n’a pas pu être restaurée.", + backupRestoreForkMine: "Cet appareil a déjà publié des messages avec votre identité qui ne figurent pas dans la copie, elle n’a donc pas pu être appliquée par-dessus. Restaurez sur une installation propre juste après avoir importé vos clés, avant toute autre action.", + verificationTitle: "Vérification", + verificationDescription: "Vérifiez la chaîne de votre fil, détectez les bifurcations et passez en revue les fichiers orphelins ou manquants.", + verificationRun: "Lancer la vérification", + verificationTotalMessages: "messages dans le journal", + verificationFeed: "Votre fil", + verificationMessages: "messages", + verificationLastSequence: "dernière séquence", + verificationGaps: "Trous", + verificationBrokenLinks: "Liens rompus", + verificationBadHashes: "Hashs incorrects", + verificationBadSignatures: "Signatures incorrectes", + verificationDuplicates: "Séquences dupliquées", + verificationForks: "Bifurcations", + verificationBlobs: "Fichiers", + verificationBlobsPresent: "présents", + verificationBlobsReferenced: "référencés", + verificationBlobsOrphan: "orphelins", + verificationBlobsMissing: "manquants", + verificationBlobsMissingOwn: "Vos propres fichiers absents de cet appareil", + verificationBlobsMissingOthers: "Fichiers d'autres habitants pas encore téléchargés", + verificationBlobsUnavailable: "Impossible de lister les fichiers sur cet appareil.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Module pour découvrir et gérer des wikis.", + wikiDescription: "Module pour découvrir et gérer des wikis.", + wikiCreate: "Créer un wiki", + wikiUpdate: "Mettre à jour", + wikiDelete: "Supprimer", + wikiEdit: "Modifier", + wikiTitleLabel: "Titre", + wikiTitlePlaceholder: "Titre du wiki...", + wikiBodyLabel: "Contenu", + wikiBodyPlaceholder: "Reliez d’autres wikis avec [[Titre du wiki]] ou [[Titre du wiki|un libellé personnalisé]].\n\nMentionnez un habitant avec @id.\n\nÉtiquetez avec #oasis.\n\nCollez des liens comme https://solarnethub.com\n\nUne ligne vide ouvre un nouveau paragraphe ; les sauts de ligne simples sont conservés.\n\nJoignez n’importe quel média pris en charge : il sera intégré ici.", + formatBarLabel: "Format", + wikiStatusLabel: "Type de wiki", + wikiStatusOpen: "OUVERT", + wikiStatusClosed: "FERMÉ", + wikiTagsLabel: "Étiquettes", + wikiTagsPlaceholder: "Saisissez des tags séparés par des virgules", + wikiSummaryLabel: "Résumé de la modification", + wikiSummaryPlaceholder: "Qu’est-ce qui a changé ?", + wikiFilterAll: "TOUTES", + wikiFilterMine: "MIENNES", + wikiFilterRecent: "RÉCENTES", + wikiFilterLinked: "LIÉES", + wikiChanges: "Modifications récentes", + wikiNoPages: "Aucun wiki trouvé.", + wikiNoChanges: "Aucune modification pour l’instant.", + wikiHistory: "Historique", + wikiVersion: "Version", + wikiVersions: "versions", + wikiRestore: "Restaurer", + wikiDiff: "Modifications", + wikiViewVersion: "Voir", + wikiLinkedPages: "Pages liées", + wikiNoLinkedPages: "Cette page n'est encore liée à aucune autre.", + wikiMissingLink: "La page n’existe pas encore", + wikiDuplicateTitle: "Une page portant ce titre existe déjà. Changez le titre pour créer une autre page.", + wikiCreateSectionTitle: "Créer un wiki", + wikiUpdateSectionTitle: "Mettre à jour le wiki", + wikiSearchPlaceholder: "Rechercher dans le Wiki...", + wikiCurrentVersion: "Version actuelle", + wikiViewingVersion: "Vous consultez une ancienne version", + wikiBackToPage: "Retour à la page", + wikiLinkedChip: "Liée", + wikiRestoredSummary: "Version restaurée", + wikiExportPdf: "Générer la page", + wikiPreview: "Aperçu", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Créer un wiki", + tribeWikiEmpty: "Pas encore de wikis.", + typeWiki: "WIKI", padsTitle: "Pads", padTitle: "Pad", modulesPadsLabel: "Pads", - modulesPadsDescription: "Module pour gérer les éditeurs de texte collaboratifs.", + modulesPadsDescription: "Module pour gérer des éditeurs de texte collaboratifs.", padCreate: "Créer Pad", padUpdate: "Modifier", padDelete: "Supprimer", padTitleLabel: "Titre", padTitlePlaceholder: "Entrez le titre du pad...", padStatusLabel: "Statut", + padTypeLabel: "Type de pad", padStatusOpen: "OUVERT", padStatusInviteOnly: "SUR INVITATION", padStatusClosed: "FERMÉ", padDeadlineLabel: "Date limite", padTagsLabel: "Tags", - padTagsPlaceholder: "tag1, tag2, ...", + padTagsPlaceholder: "Saisissez des tags séparés par des virgules", padMembersLabel: "Membres", padCreated: "Créé", - padStartEditing: "COMMENCER À ÉDITER !", + padStartEditing: "Modifier", padEditorPlaceholder: "Commencez à écrire...", padSubmitEntry: "Envoyer", padNoEntries: "Aucune entrée pour l'instant.", padAllSectionTitle: "Tous les Pads", padMineSectionTitle: "Mes Pads", padsDescription: "Gérez des éditeurs de texte chiffrés collaboratifs dans votre réseau.", + roomsTitle: "Salons", + roomsDescription: "Découvrez et rejoignez des salons de visioconférence chiffrés dans votre réseau.", + modulesRoomsLabel: "Salons", + modulesRoomsDescription: "Module pour découvrir et rejoindre des salons de visioconférence chiffrés.", + roomFilterRecent: "RÉCENTS", + roomFilterMine: "LES MIENS", + roomFilterAll: "TOUS", + roomFilterLive: "EN DIRECT", + roomFilterOpen: "OUVERTS", + roomFilterInvite: "SUR INVITATION", + roomFilterClosed: "FERMÉS", + roomCreate: "Créer un salon", + roomUpdate: "Modifier", + roomDelete: "Supprimer", + roomClose: "Fermer", + roomCreateSectionTitle: "Créer un salon", + roomUpdateSectionTitle: "Modifier le salon", + roomTitlePlaceholder: "Titre du salon", + roomDescriptionLabel: "Description", + roomDescriptionPlaceholder: "De quoi allez-vous parler ?", + roomTypeLabel: "Type de salon", + roomTagsLabel: "Tags", + roomStatusOpen: "OUVERT", + roomStatusInviteOnly: "SUR INVITATION", + roomStatusClosed: "FERMÉ", + roomParticipants: "Participants", + roomJoin: "Rejoindre", + roomLeave: "Quitter", + roomFull: "Salon complet", + roomLiveChip: "EN DIRECT", + roomMuted: "micro coupé", + roomRec: "Enregistrer l'audio", + roomRecStop: "Arrêter l'enregistrement", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} est entré dans la salle", + roomEventLeft: "{name} a quitté la salle", + roomEventRecStart: "{name} a commencé à enregistrer", + roomEventRecStop: "{name} a arrêté d'enregistrer", + roomEventMuted: "{name} a coupé son micro", + roomEventUnmuted: "{name} a réactivé son micro", + roomEventHand: "{name} a levé la main", + roomRaiseHand: "Lever la main", + roomLowerHand: "Baisser la main", + confirmActionTitle: "Confirmer cette action", + confirmActionButton: "Confirmer", + confirmJoinText: "Vous allez rejoindre avec cette invitation.", + confirmFollowText: "Vous allez soutenir cet habitant.", + confirmSignText: "Vous allez signer cette campagne.", + confirmDownloadText: "Vous allez télécharger ce fichier sur votre appareil.", + roomRecordings: "Enregistrements", + roomClearNotices: "Effacer", + roomLineSwitchboard: "Standard", + roomLineDndError: "Le numéro de ce salon n'accepte pas d'appels.", + roomNumberHint: "Composez ce numéro depuis Téléphone pour rejoindre le salon.", + liveRefresh: "Rafraîchir", + roomNobody: "Personne n'est dans le salon pour le moment.", + roomSomeone: "{n} dans le salon en ce moment.", + roomYou: "vous", + roomYouAreIn: "Vous y êtes", + roomNobodyInside: "Il n'y a personne dans cette salle pour le moment.", + roomErrorFull: "Le salon est complet. Vous pourrez entrer quand quelqu'un partira.", + roomErrorUnreachable: "Le salon est injoignable pour le moment. Réessayez plus tard.", + roomErrorBusy: "Vous êtes déjà dans un appel ou dans un autre salon.", + roomErrorClosed: "Ce salon est fermé.", + roomAccessDenied: "Ce salon est sur invitation. Il vous faut une invitation pour entrer.", + roomsNoItems: "Aucun salon trouvé.", + roomSearchPlaceholder: "Rechercher des salons...", + roomInviteMode: "Invitation", + tribeSectionRooms: "SALONS", + tribeRoomCreate: "Créer un salon", + tribeRoomsEmpty: "Aucun salon pour l'instant.", + typeRoom: "SALONS", + typeRoomMember: "PARTICIPANT DE SALON", + statsRoom: "Salons", + statsRoomMember: "Participants aux salons", + favoritesFilterRooms: "SALONS", + invitesRoomsTitle: "Salons", + inviteCodeInvalid: "Ce code d'invitation n'est pas valide ou a déjà été utilisé.", + inviteAlreadyMember: "Vous en faites déjà partie. Vous pouvez l'ouvrir depuis son module.", + actionFailed: "Cela n'a pas pu être fait. Réessayez.", + publishTooShort: "Votre publication est trop courte.", + walletSendFailed: "Le paiement n'a pas pu être envoyé : {reason}", + pixeliaCoordsError: "Ces coordonnées sont en dehors de la grille.", + courtsErrTitle: "Un titre est requis.", + courtsErrRespondent: "Indiquez contre qui porte l'affaire.", + courtsErrRespondentId: "Ce n'est pas un Oasis ID valide.", + courtsErrMethod: "Choisissez comment l'affaire sera résolue.", + courtsErrCaseNotFound: "Cette affaire est introuvable.", + courtsErrBrief: "Rédigez votre réponse.", + courtsErrStance: "Choisissez votre position.", + courtsErrResult: "Indiquez le résultat.", + courtsErrTerms: "Rédigez les termes de l'accord.", + courtsErrMediator: "Ajoutez au moins un médiateur.", + courtsErrSelfMediator: "Vous ne pouvez pas être médiateur dans votre propre affaire.", + courtsErrJudge: "Choisissez un juge.", + courtsErrSelfJudge: "Vous ne pouvez pas juger votre propre affaire.", + courtsErrVisibility: "Choisissez qui peut voir l'affaire.", + courtsErrNomination: "Cette nomination est introuvable.", + opinionAlreadyGiven: "Vous avez déjà donné votre avis.", + cipherPasswordShort: "Le mot de passe doit contenir au moins 32 caractères.", + errorForbidden: "Vous n'avez pas accès à ceci.", + errorNotFound: "Introuvable.", + errorClosed: "C'est fermé.", + errorInviteOnly: "C'est sur invitation. Il vous faut une invitation.", + errorTooLong: "Ce texte est trop long.", + errorRequired: "Des informations obligatoires manquent.", + errorInvalid: "Certaines informations ne sont pas valides.", + errorNotAuthor: "Seul l'auteur peut faire cela.", + panicRemoved: "Votre blockchain a été supprimée.", padRecentSectionTitle: "Pads Récents", padOpenSectionTitle: "Pads Ouverts", padClosedSectionTitle: "Pads Fermés", @@ -3064,18 +3932,18 @@ module.exports = { padRemoveFavorite: "Retirer des Favoris", padClose: "Fermer", padBackToEditor: "Retour à l'éditeur", - padSearchPlaceholder: "Rechercher des pads...", + padSearchPlaceholder: "Rechercher dans Pads...", modulesChatsLabel: "Chats", - modulesChatsDescription: "Module pour découvrir et gérer les chats chiffrés.", + modulesChatsDescription: "Module pour découvrir et gérer des chats.", typeChat: "CHATS", typeChatMessage: "MESSAGE CHAT", chatsTitle: "Chats", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "Vos chats", + chatRecentTitle: "Chats récents", chatFavoritesTitle: "Favoris", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "Chats ouverts", + chatClosedTitle: "Chats fermés", chatDescription: "Description", chatCategory: "Catégorie", chatStatus: "STATUT", @@ -3090,34 +3958,172 @@ module.exports = { chatMessagesLabel: "Messages", chatThreadsLabel: "Fils", chatCreatedAt: "CRÉÉ", - chatSearchPlaceholder: "Rechercher chats...", + chatSearchPlaceholder: "Rechercher dans Chats...", chatStatusOpen: "OUVERT", chatStatusInviteOnly: "SUR INVITATION", chatStatusClosed: "FERMÉ", chatSendMessage: "Envoyer", chatJumpLatest: "Dernier message", + chatReply: "Répondre", + chatReplyCancel: "Annuler la réponse", + chatPin: "Épingler", + chatUnpin: "Désépingler", + chatPinned: "Épinglés", chatPickChat: "Choisissez un chat pour l'ouvrir", chatNoneYet: "Aucun chat disponible pour l'instant.", - activitySearchPlaceholder: "Chercher dans l'activité...", - trendingSearchPlaceholder: "Chercher dans les tendances...", - opinionsSearchPlaceholder: "Chercher dans les opinions...", - votesSearchPlaceholder: "Chercher dans les votations...", - welcomeStepUxTitle: "Choisissez votre interface", - welcomeStepUxText: "Choisissez l'apparence d'Oasis. Modifiable ensuite dans les réglages.", - welcomeStepUxAction: "Utiliser cette vue", + activitySearchPlaceholder: "Rechercher dans Activité...'activité...", + trendingSearchPlaceholder: "Rechercher dans Tendances...", + opinionsSearchPlaceholder: "Rechercher dans Opinions...", + votesSearchPlaceholder: "Rechercher dans Votations...", + gamesSearchPlaceholder: "Rechercher dans Jeux...", + schoolTitle: "École", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Une page wiki que vous suivez a été modifiée.", + wikiBotRestoredTitle: "Une version antérieure d’une page wiki que vous suivez a été restaurée.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Un nouvel étudiant s'est inscrit à votre cours.", + schoolBotInvitedTitle: "Vous avez été invité à un cours.", + schoolBotAdmittedTitle: "Vous avez été admis à un cours.", + schoolBotCertificateTitle: "Vous avez reçu un certificat.", + schoolBotPassedTitle: "Un étudiant a réussi votre cours.", + schoolBotLessonTitle: "Une nouvelle leçon a été publiée.", + invitesSchoolTitle: "École", + schoolEnrollLabel: "Rejoindre le cours", + schoolExamRetryLater: "Vous pourrez réessayer après 24 heures.", + schoolApproved: "RÉUSSI", + schoolExamPassMark: "Note de passage (0-10)", + schoolExamPassed: "RÉUSSI", + schoolExamPending: "En attente", + schoolExamFailed: "ÉCHOUÉ", + schoolExamAddQuestion: "Ajouter une question", + schoolExamCreateQuestion: "Créer une question", + schoolExamOption: "Option", + schoolExamCorrect: "Option correcte", + schoolExamPoints: "Points", + schoolMaterialTextPlaceholder: "Notes pour cette leçon...", + schoolMaterials: "Matériels", + schoolAddMaterial: "Ajouter du matériel", + schoolNoMaterials: "Pas encore de matériel.", + bankYourSchoolBalance: "Vos revenus de l'école", + schoolProgress: "Progression", + schoolProgressMark: "Marquer comme terminée", + schoolProgressDone: "Terminée", + schoolExams: "Examens", + schoolExamCreate: "Créer un examen", + schoolExamTitlePlaceholder: "Titre de l'examen", + schoolExamHelp: "Une question par ligne : Question ? ; option ; option correcte* ; option", + schoolExamQuestionsPlaceholder: "Qu'est-ce que SSB ? ; Une base de données ; Un protocole P2P* ; Un navigateur", + schoolExamSubmit: "Envoyer les réponses", + schoolExamScore: "Votre note", + schoolNoScoresYet: "Pas encore de résultats.", + schoolLessonUnit: "Unité / phase", + schoolLessonUnitPlaceholder: "p. ex. Phase 1", + schoolLessonOrder: "Ordre", + profileClearnetSchoolLabel: "École", + schoolStartDate: "Date de début", + schoolSessionDate: "Date de la session", + schoolCourseChat: "Chat du cours", + agendaFilterSchool: "ÉCOLE", + agendaFilterCampaigns: "CAMPAGNES", + agendaFilterLogistics: "LOGISTIQUE", + schoolCourseType: "Type de cours", + schoolTypeOpen: "OUVERT", + schoolTypePaid: "PAYANT", + schoolTypeInvite: "SUR INVITATION", + schoolLessonLocked: "Leçon chiffrée — demandez la clé du cours à l'enseignant.", + schoolGrantAccess: "Envoyer la clé du cours", + schoolKeySent: "Clé envoyée", + schoolOngoing: "En cours", + schoolFilterApplied: "INSCRIT", + schoolFilterOpen: "OUVERTS", + schoolFilterFavorites: "FAVORIS", + schoolPendingPayment: "Paiement en attente", + schoolPendingStudents: "Inscriptions en attente", + schoolViewBill: "Voir la facture", + schoolPaymentNote: "L'inscription génère une facture en ECO (un transfert). Votre inscription est confirmée une fois le transfert signé.", + schoolCloseCourse: "Fermer le cours", + schoolReopenCourse: "Rouvrir le cours", + schoolDescription: "Enseignez et apprenez des cours dans votre réseau.", + schoolFilterAll: "TOUS", + schoolFilterMine: "LES MIENS", + schoolFilterTop: "TOP", + schoolFilterRecent: "RÉCENTS", + schoolCreateButton: "Créer un Cours", + schoolCreateSectionTitle: "Créer un cours", + schoolUpdateSectionTitle: "Mettre à jour le cours", + schoolCourseTitlePlaceholder: "Nom de votre cours", + schoolCourseDescriptionPlaceholder: "Qu'allez-vous enseigner dans ce cours ?", + schoolCourseImage: "Image du cours (taille max : 75MB)", + schoolTags: "Étiquettes", + schoolTagsPlaceholder: "Saisissez des tags séparés par des virgules", + schoolPrice: "Prix (ECO)", + schoolVisibility: "Visibilité", + schoolVisibilityPublic: "Public", + schoolVisibilityInvite: "Sur invitation", + schoolStatus: "Statut", + schoolClosed: "Fermé", + schoolFree: "Gratuit", + schoolCreate: "Créer le cours", + schoolUpdate: "Mettre à jour le cours", + schoolSearchPlaceholder: "Rechercher dans École...", + schoolSortTitle: "Titre", + schoolNoCourses: "Aucun cours trouvé.", + schoolStudents: "Étudiants", + schoolTeacher: "Enseignant", + schoolCreatedAt: "CRÉÉ", + schoolLessons: "Leçons", + schoolNoLessons: "Pas encore de leçons.", + schoolEnrollToSee: "Inscrivez-vous pour voir les leçons de ce cours.", + schoolEnrollButton: "S'INSCRIRE", + schoolUnenrollButton: "Quitter le cours", + schoolInviteRequired: "Ce cours est sur invitation.", + schoolTeacherPanel: "Panneau de l'enseignant", + schoolStudentPanel: "Panneau de l'étudiant", + schoolAddLesson: "Créer une leçon", + schoolLessonTitlePlaceholder: "Titre de la leçon", + schoolLessonTextPlaceholder: "Décrivez les notions abordées dans cette leçon...", + schoolInviteStudents: "Inviter des étudiants", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "INVITER", + schoolInvited: "Invités", + schoolIssueCertificate: "Créer un certificat", + schoolCertified: "Certifié", + schoolCertificateTextPlaceholder: "Note du certificat (optionnel)", + schoolLessonOpen: "Ouvrir la leçon", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificats", + schoolNoCertificates: "Aucun certificat émis pour le moment.", + schoolEditButton: "Modifier", + schoolDeleteButton: "Supprimer", + typeSchool: "ÉCOLE", + schoolCourseLabel: "COURS", + emergencyLabel: "URGENCE", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Cours", + statsSchoolLesson: "Leçons", + statsSchoolEnroll: "Inscriptions", + statsSchoolCertificate: "Certificats de l'école", + modulesSchoolLabel: "École", + modulesSchoolDescription: "Module pour échanger des connaissances.", + welcomeStepUxAction: "Enregistrer la vue", + welcomeStepWorkflowTitle: "Quel usage d'OASIS avez-vous en tête ?", + welcomeStepWorkflowText: "Oasis a beaucoup de modules. Choisissez la configuration qui vous correspond et seuls les modules utiles resteront activés. Vous pourrez l'ajuster à tout moment dans Modules.", + welcomeStepWorkflowAction: "Enregistrer la configuration", + welcomeWorkflow_full: "Par défaut", chatRateLimitTitle: "Trop de messages", chatRateLimitMessage: "Vous avez atteint la limite de messages que ce salon accepte en une heure. Réessayez plus tard.", chatMessagePlaceholder: "Tapez votre message...", chatNoMessages: "Pas encore de messages.", chatTitlePlaceholder: "Titre du chat", chatDescriptionPlaceholder: "Description du chat", - chatTagsPlaceholder: "tag1, tag2, tag3", + chatTagsPlaceholder: "Saisissez des tags séparés par des virgules", chatImageLabel: "Sélectionnez un fichier image (.jpeg, .jpg, .png, .gif)", chatAuthor: "Auteur", chatAddFavorite: "Ajouter aux Favoris", chatRemoveFavorite: "Retirer des Favoris", chatPM: "MP", chatStatusLabel: "Statut", + chatTypeLabel: "Type de chat", chatCategoryLabel: "Catégorie", gamesTitle: "Jeux", gamesDescription: "Découvrez et jouez à des jeux.", @@ -3148,22 +4154,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Lancez une pièce et pariez sur pile ou face. Quelle est votre chance?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "Billard vu de dessus. Cliquez pour viser, maintenez pour charger la puissance. Empochez toutes les boules en un minimum de coups.", + gamesArtilleryTitle: "Artillerie", + gamesArtilleryDesc: "Visez avec votre canon, tenez compte du vent et touchez la cible. 5 manches, le moins de tirs l'emporte.", + gamesLabyrinthTitle: "Labyrinthe", + gamesLabyrinthDesc: "Échappez au labyrinthe avant d'épuiser vos mouvements. Chaque niveau est plus grand et plus difficile.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Mangez tous les points, évitez les fantômes. Les fantômes se déplacent en temps réel — ne vous arrêtez pas !", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", + gamesAudioPendulumTitle: "Pendule sonore", + gamesAudioPendulumDesc: "Simulateur de physique chaotique avec synthèse audio en temps réel. Les vitesses angulaires deviennent des fréquences, les pics des coups de percussion. Aucune simulation ne sonne pareil.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Mixez des visuels en direct sur deux platines : choisissez un motif pour chacune, réglez vitesse, couleur et zoom, enchaînez de l'une à l'autre et lancez des flashs en rythme. Tapez le tempo ou laissez le micro mener la pulsation.", + gamesTetrisDesc: "Les blocs qui tombent, un classique. Complétez des lignes pour marquer des points. Combien de temps tiendrez-vous ?", + gamesMinesweeperTitle: "Démineur", + gamesMinesweeperDesc: "La chasse aux mines classique. Déminez le plateau pour monter de niveau — grilles plus grandes, plus de mines, score plus élevé.", + gamesFilterScoring: "SCORES", + gamesHallOfFame: "Panthéon", + gamesHallPlayer: "Joueur", gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesNoScores: "Aucun score pour l'instant.", calendarsTitle: "Calendriers", calendarTitle: "Calendrier", modulesCalendarsLabel: "Calendriers", @@ -3171,15 +4181,15 @@ module.exports = { typeCalendar: "CALENDRIERS", calendarCreate: "Créer un calendrier", calendarUpdate: "Mettre à jour", - calendarDelete: "Supprimer", calendarTitleLabel: "Titre", calendarTitlePlaceholder: "Titre du calendrier...", calendarStatusLabel: "Statut", + calendarTypeLabel: "Type de calendrier", calendarStatusOpen: "OUVERT", calendarStatusClosed: "FERMÉ", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Échéance", calendarTagsLabel: "Étiquettes", - calendarTagsPlaceholder: "tag1, tag2...", + calendarTagsPlaceholder: "Saisissez des tags séparés par des virgules", calendarParticipantsLabel: "Participants", calendarCreated: "Créé", calendarJoin: "Rejoindre", @@ -3190,11 +4200,11 @@ module.exports = { calendarNotePlaceholder: "Ajouter une note...", calendarFirstDateLabel: "Date", calendarFirstNoteLabel: "Notes", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Intervalle", calendarIntervalNone: "Sans répétition", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Hebdomadaire", + calendarIntervalMonthly: "Mensuel", + calendarIntervalYearly: "Annuel", calendarFormDescription: "Description", calendarNoDates: "Aucune date ajoutée.", calendarNoNotes: "Aucune note.", @@ -3202,6 +4212,8 @@ module.exports = { calendarsDescription: "Découvrez et gérez les calendriers de votre réseau.", calendarMonthPrev: "← Précédent", calendarMonthNext: "Suivant →", + calendarMonthNames: "Janvier,Février,Mars,Avril,Mai,Juin,Juillet,Août,Septembre,Octobre,Novembre,Décembre", + calendarDayInitials: "L,M,M,J,V,S,D", calendarAllSectionTitle: "Calendriers", calendarRecentSectionTitle: "Calendriers Récents", calendarFavoritesSectionTitle: "Favoris", @@ -3211,9 +4223,9 @@ module.exports = { calendarCreateSectionTitle: "Créer un calendrier", calendarUpdateSectionTitle: "Mettre à jour le calendrier", calendarAddFavorite: "Ajouter aux favoris", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Supprimer", calendarRemoveFavorite: "Retirer des favoris", - calendarSearchPlaceholder: "Rechercher des calendriers...", + calendarSearchPlaceholder: "Rechercher dans Calendrier...", calendarAddEntry: "Ajouter une entrée", statsCalendar: "Calendriers", statsCalendarDate: "Dates de calendrier", @@ -3225,6 +4237,146 @@ module.exports = { tribeContentAccessDenied: "Accès Refusé", tribeContentAccessDeniedMsg: "Ce contenu appartient à une tribu. Vous devez en être membre pour y accéder.", tribeViewTribes: "Voir les Tribus", + tribeFilesEmpty: "Pas encore de fichiers.", + filesLabel: "Fichiers", + profileClearnetFilesLabel: "Fichiers", + filesTitle: "Fichiers", + filesDescription: "Explorez et gérez les fichiers de votre réseau.", + fileAllSectionTitle: "Fichiers", + fileMineSectionTitle: "Vos Fichiers", + fileRecentSectionTitle: "Fichiers Récents", + fileTopSectionTitle: "Meilleurs Fichiers", + fileFavoritesSectionTitle: "Favoris", + fileFilterAll: "TOUS", + fileFilterMine: "MES", + fileFilterRecent: "RÉCENTS", + fileFilterTop: "MEILLEURS", + fileFilterFavorites: "FAVORIS", + fileCreateSectionTitle: "Téléverser un Fichier", + fileUpdateSectionTitle: "Mettre à jour le Fichier", + fileCreateButton: "Téléverser un Fichier", + fileUpdateButton: "Mettre à jour", + fileAddFavoriteButton: "Ajouter aux Favoris", + fileRemoveFavoriteButton: "Retirer des Favoris", + fileFileLabel: "Sélectionnez un fichier (max: 50 Mo)", + calendarFilterRecent: "RÉCENTS", + calendarFilterAll: "TOUS", + calendarFilterMine: "À MOI", + calendarFilterFavorites: "FAVORIS", + calendarFilterOpen: "OUVERTS", + calendarFilterClosed: "FERMÉS", + padFilterRecent: "RÉCENTS", + padFilterAll: "TOUS", + padFilterMine: "À MOI", + padFilterOpen: "OUVERTS", + padFilterClosed: "FERMÉS", + chatFilterRecent: "RÉCENTS", + chatFilterAll: "TOUS", + chatFilterMine: "À MOI", + chatFilterFavorites: "FAVORIS", + chatFilterOpen: "OUVERTS", + chatFilterClosed: "FERMÉS", + licenseLabel: "Licence", + phoneTitle: "Téléphone", + phoneDescription: "Gérez vos appels vidéo chiffrés de bout en bout.", + phoneUnavailable: "Les appels ne sont pas disponibles sur cet appareil.", + phoneCallButton: "Appeler", + profileVisibilityPhone: "Numéro de téléphone", + phoneNumberUnknown: "Personne dans votre réseau n'a le numéro {number}.", + phoneNumberChoose: "Vérifiez qui vous voulez appeler au {number} :", + phoneAnswer: "Répondre", + phoneSpeaker: "Haut-parleur", + phoneEarpiece: "Écouteur", + phoneReject: "Refuser", + phoneHangup: "Raccrocher", + phoneMute: "Couper le micro", + phoneUnmute: "Réactiver le micro", + phoneCameraOn: "Caméra on", + phoneCameraOff: "Caméra off", + settingsPhoneVideo: "Vidéo", + settingsPhoneVideoOn: "Autorisée dans les appels et les salles", + settingsPhoneVideoOff: "Audio seulement", + phoneSilence: "Ne plus entendre", + phoneUnsilence: "Entendre à nouveau", + phoneCalling: "Appel vers", + phoneIsCalling: "vous appelle", + notifyNewPmFrom: "Nouveau MP de", + notifyNewPamFrom: "Nouveau PaM de", + notifyUpdateLabel: "Mise à jour", + updateInstalledLabel: "Version installée", + updateAvailableLabel: "Version disponible", + updateChangesLabel: "Modifications", + updateFilesLabel: "Fichiers", + updateOriginLabel: "Source", + phoneRecording: "Enregistrement du PaM pour", + phoneLeaveMessage: "Personne ne répond : laissez votre message après le bip.", + phoneLeaveMessageBusy: "Cette personne est déjà en ligne : laissez votre message après le bip.", + phoneLeaveMessageUnreachable: "Impossible de joindre cette personne : laissez votre message après le bip.", + phoneConnecting: "Connexion avec", + phoneSendPam: "Envoyer le PaM", + phoneCancel: "Annuler", + phoneSearchPlaceholder: "Rechercher dans Téléphone...", + phonePamTitle: "Enregistrements audio", + phoneNew: "Nouveau", + phoneDelete: "Supprimer", + phoneNoPams: "Vous n'avez aucun enregistrement audio.", + phoneHistoryTitle: "Historique", + phoneLatestTitle: "Derniers appels", + phoneCreateButton: "Nouvel appel", + phoneLimitsHint: "Jusqu'à 7 habitants par appel ; à plus d'un, c'est un appel conjoint.", + phoneErrorPub: "Impossible d'appeler un pub.", + phoneJointCall: "Appel conjoint", + phoneWith: "Avec", + phoneResult: "Résultat", + phoneWhen: "Quand", + phoneDuration: "Durée", + phoneOutgoing: "Sortant", + phoneIncoming: "Entrant", + phoneNoHistory: "Aucun journal pour l'instant.", + phoneOutcomeEnded: "Répondu", + phoneOutcomeMissed: "Manqué", + phoneOutcomeRejected: "Refusé", + phoneOutcomeNoanswer: "Pas de réponse", + phoneOutcomeCancelled: "Annulé", + phoneOutcomePam: "PaM envoyé", + phoneOutcomeFailed: "Échoué", + phoneDialAdd: "Ajouter", + phoneDialBack: "Effacer", + phoneDialClear: "Vider", + phoneDialPlaceholder: "Numéro ou Oasis ID", + phoneDialInhabitants: "Habitants", + phoneDialRemove: "Retirer", + phoneOwnNumber: "C'est votre propre numéro.", + phoneSettingsDesc: "Choisissez qui peut vous appeler.", + phoneDnd: "Ne pas déranger", + phoneOpen: "Ouvert", + phoneRecipientRefuses: "L'habitant que vous essayez d'appeler n'accepte pas les appels.", + phoneRefusedNotice: "Ces habitants n'acceptent pas les appels et n'ont pas été appelés :", + fileEmptyError: "Le fichier est vide. Choisissez un autre fichier.", + fileTitleLabel: "Titre", + fileTitlePlaceholder: "Titre", + fileDescriptionLabel: "Description", + fileDescriptionPlaceholder: "Description", + fileTagsLabel: "Étiquettes", + fileTagsPlaceholder: "Saisissez des tags séparés par des virgules", + fileSizeLabel: "Taille", + fileDownloadButton: "TÉLÉCHARGER", + fileDetailsButton: "DÉTAILS", + fileNoFile: "Aucun fichier fichier", + fileSearchPlaceholder: "Rechercher dans Fichiers...", + fileSearchButton: "Rechercher", + fileSortRecent: "Plus récents", + fileSortOldest: "Plus anciens", + fileSortTop: "Plus votés", + fileNoMatch: "Aucun fichier correspondant.", + fileUpdatedAt: "Mis à jour", + typeFile: "FICHIERS", + modulesFilesLabel: "Fichiers", + modulesFilesDescription: "Module pour découvrir et gérer les fichiers.", + tribeSectionFiles: "FICHIERS", + fileDescription: "Description", + fileDownload: "Télécharger", + fileTypeLabel: "Type", torrentsTitle: "Torrents", torrentsDescription: "Explorez et gérez les torrents de votre réseau.", torrentAllSectionTitle: "Torrents", @@ -3250,12 +4402,35 @@ module.exports = { torrentDescriptionLabel: "Description", torrentDescriptionPlaceholder: "Description", torrentTagsLabel: "Étiquettes", - torrentTagsPlaceholder: "Entrez des étiquettes séparées par des virgules", + torrentTagsPlaceholder: "Saisissez des tags séparés par des virgules", torrentSizeLabel: "Taille", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Générer un torrent", + torrentFromContentMissing: "Le fichier n'est pas encore disponible sur ce nœud. Réessayez plus tard.", + torrentDownloads: "Téléchargements", + torrentFetchOasis: "Télécharger dans Oasis", + torrentSaveFile: "Enregistrer le fichier", + torrentSeeds: "sources", + seedsLabel: "Sources", + seedAction: "Soutenir", + torrentSourceFile: "Fichier", + torrentStateQueued: "En attente", + torrentStateProbing: "Recherche de sources", + torrentStateDownloading: "Téléchargement", + torrentStateDone: "Terminé", + torrentStateFailed: "Échec", + torrentStateNoSource: "Aucune source connectée, en attente", + torrentStateCancelled: "Annulé", + torrentNoDownloads: "Aucun téléchargement pour l'instant.", + torrentOriginalSection: "Original", + torrentCancel: "Annuler", + torrentDownloadButton: "TÉLÉCHARGER", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DÉTAILS", torrentNoFile: "Aucun fichier torrent", noTorrents: "Aucun torrent trouvé.", - torrentSearchPlaceholder: "Rechercher titre, étiquettes, auteur...", + noFiles: "Aucun fichier trouvé.", + torrentSearchPlaceholder: "Rechercher dans Torrents...", torrentSearchButton: "Rechercher", torrentSortRecent: "Plus récents", torrentSortOldest: "Plus anciens", @@ -3263,10 +4438,12 @@ module.exports = { torrentNoMatch: "Aucun torrent correspondant.", torrentUpdatedAt: "Mis à jour", statsTorrent: "Torrents", + statsFile: "Fichiers", typeTorrent: "TORRENTS", modulesTorrentsLabel: "Torrents", modulesTorrentsDescription: "Module pour découvrir et gérer les torrents.", favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "FICHIERS", favoritesFilterMarket: "MARCHÉ", favoritesFilterEvents: "ÉVÉNEMENTS", favoritesFilterForum: "FORUM", @@ -3280,15 +4457,17 @@ module.exports = { favoritesFilterShopProducts: "ARTICLES BOUTIQUE", tribeSectionTorrents: "TORRENTS", tribeCreateTorrent: "Téléverser un Torrent", + tribeCreateFile: "Envoyer un fichier", settingsWishTitle: "Souhait", - settingsWishDesc: "Configurez le souhait de votre Avatar.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Détermine comment vous accédez à tout le contenu et votre expérience dans le réseau Oasis.", + settingsWishWhole: "Multivers", + settingsWishMutuals: "Soutien mutuel uniquement", + settingsWishOnlyLan: "LAN uniquement", + settingsWishLocal: "Local", settingsPmVisibilityTitle: "Messages Privés", settingsPmVisibilityDesc: "Configurez votre niveau d'exposition aux messages privés.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Multivers", + settingsPmVisibilityMutuals: "Soutien mutuel uniquement", pmMutualNotice: "L'envoi est un garde-fou UX. La réception est un filtre d'attention.", inhabitantsPendingFollowsTitle: "Demandes de soutien en attente", inhabitantsPendingAccept: "Accepter", @@ -3332,7 +4511,7 @@ module.exports = { tribeGovNoRevocations: "Aucune révocation pour l’instant", tribeGovNoHistorical: "Aucun enregistrement pour l’instant", logsTitle: "Journal", - logsDescription: "Enregistrez votre expérience sur le réseau.", + logsDescription: "Tenez un journal privé de vos expériences.", logsViewTitle: "Journal", logsColumnType: "Type", logsView: "Voir", @@ -3341,7 +4520,7 @@ module.exports = { logsCreateTitle: "Créer une entrée", logsWriteButton: "Écrire", logsTextPlaceholder: "Décrivez vos expériences...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "Assistant IA", logsModeManual: "Manuel", logsModeAI: "IA", logsColumnLog: "Journal", @@ -3360,19 +4539,20 @@ module.exports = { logsExportOne: "Exporter le journal", logsViewDetails: "Voir les détails", logsGenerateButton: "Générer le texte", - logsSearchText: "Chercher dans les journaux...", + logsSearchText: "Rechercher dans Journal...", logsSearchAnyType: "Tout type", logsSearchButton: "Chercher", logsEmpty: "Aucune entrée pour l'instant.", + logsNothingNew: "Il n'y a aucune nouvelle action à consigner.", modulesLogsLabel: "Journal", - modulesLogsDescription: "Module pour enregistrer (via un assistant IA) vos expériences.", + modulesLogsDescription: "Module pour tenir un journal privé de vos expériences.", statsLogsTitle: "Journal", typeLog: "JOURNAL", larpTitle: "L.A.R.P.", larpDescription: "Une couche de jeu de rôle grandeur nature pour l'expérimentation collaborative.", larpNoHousesMatch: "Aucune maison ne correspond à votre recherche.", - larpSearchPlaceholder: "Rechercher des maisons...", + larpSearchPlaceholder: "Rechercher dans L.A.R.P....", larpAllHouses: "Maisons :", larpFilterRuling: "RÈGNE", larpFilterHouses: "MAISONS", @@ -3388,46 +4568,47 @@ module.exports = { larpPostSubmit: "Publier", larpCycleLabel: "Cycle :", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", + bankRulesArchTaxNote: "La taxe ARCH reflète le coût à long terme de la croissance et de la maintenance de l'archive du réseau : plus vos données vivent depuis longtemps dans le réseau, plus votre part du coût de maintenance est importante.", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", + bankRulesChipNote: "La pastille de chaque élément est colorée par rapport au plus grand message jamais observé sur le réseau et atténuée par le nombre d'habitants actifs — un réseau plus grand répartit la charge et fait passer davantage d'éléments dans la bande verte.", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", + bankRulesEcoTaxNote: "La taxe ECO est déduite de la RBU brute lors du calcul de l'allocation de l'époque. Les ECO déduits ne sont pas redistribués pendant cette époque — ils restent dans le solde du pub et alimentent le fonds de l'époque suivante via les plafonds reserveMin / alpha.", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", + bankRulesKarmaNote: "Le karma est calculé à partir de vos actions (publications, votes, participation au parlement/aux tribunaux, etc.) avec un facteur de décroissance temporelle, moins les grammes de carbone générés par votre fil.", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", + bankTaxesArchTaxFootprintNote: "La taxe ARCH reflète le coût de la croissance et de la maintenance de l'archive du réseau. Elle est calculée à partir de l'écart de temps entre votre premier bloc publié et le bloc le plus récent du réseau — plus votre historique a vécu longtemps dans l'archive, plus votre part du coût de maintenance est importante.", + bankTaxesArchTaxNoteBold: "réduire directement la taxe ARCH", + bankTaxesArchTaxNoteIntro: "Votre taxe ARCH est déduite de l'excédent que le réseau génère au-delà de votre RBU ; la valeur de base de la RBU est fixée comme un minimum intangible. La taxe n'est jamais déduite du montant fixe qui revient à chaque habitant en tant que RBU. Les fonds déduits alimentent l'algorithme de redistribution des richesses et sont reversés à d'autres habitants via leurs demandes de RBU. Les habitants les plus récents et ceux qui se consacrent spécifiquement aux tâches d'archivage, de réplication et de maintenance auront probablement une tâche dédiée à maintenir l'archive du réseau en bonne santé en votre nom. Ou ils peuvent se consacrer à ", + bankTaxesArchTaxNoteOutro: " grâce à des accords d'élagage, à la déduplication des données redondantes et à une infrastructure de maintenance partagée, et leur contribution continue peut être exigée par le consensus du réseau.", + bankTaxesUserNoteChips: "La pastille Taxe ECO affichée à côté de chaque élément change de couleur (vert / jaune / rouge) selon la taille de l'élément par rapport au plus grand bloc jamais vu sur le réseau, atténuée par le nombre d'habitants actifs — un réseau solarpunk plus grand répartit la charge et fait passer davantage d'éléments dans la bande verte.", + bankTaxesUserNoteOtherParams: "La taxe ECO est actuellement calculée à partir de l'empreinte carbone de chaque bloc, mais elle pourra intégrer d'autres paramètres avec le temps — énergie dépensée pour la réplication, stockage redondant entre pairs, bande passante des blobs, coût de calcul du déchiffrement, empreinte de minage des transactions associées, ou toute autre charge mesurable que le réseau décide de valoriser.", gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - parliamentRevocationPublish: "Publish Revocation", - reportsAcceptanceCriteriaLabel: "Acceptance criteria", - reportsActualBehaviorPlaceholder: "What actually happens?", - reportsEvidenceLinksPlaceholder: "Paste links, message IDs, screenshots (if applicable), etc.", - reportsUserStoryLabel: "Inhabitant story", - reportsUserStoryPlaceholder: "As a , I want , so that .", - reportsWhatHappenedLabel: "What happened?", - reportsWhatHappenedPlaceholder: "Describe the incident with context and approximate dates.", + gamesNewRecord: "Nouveau record !", + indexingRefreshNote: "Cette page se recharge toutes les 10 secondes.", + indexingMessage: "Oasis synchronise le réseau de nœuds distribué. Veuillez patienter.", + indexingTitle: "Synchronisation", + padsNoItems: "Aucun pad trouvé.", + padVersionHistory: "Historique des versions", + padAttributionView: "Par autrice", + padVersionView: "Voir", + parliamentRevocationPublish: "Publier la révocation", + reportsAcceptanceCriteriaLabel: "Critères d'acceptation", + reportsActualBehaviorPlaceholder: "Que se passe-t-il réellement ?", + reportsEvidenceLinksPlaceholder: "Collez des liens, des identifiants de messages, des captures d'écran (le cas échéant), etc.", + reportsUserStoryLabel: "Récit d'habitant", + reportsUserStoryPlaceholder: "En tant que , je veux , afin de .", + reportsWhatHappenedLabel: "Que s'est-il passé ?", + reportsWhatHappenedPlaceholder: "Décrivez l'incident avec le contexte et des dates approximatives.", statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", + statsChatMessage: "Messages de chat", + statsGameScore: "Jeux", + statsWiki: "Pages wiki", statsPad: "Pads", - statsPadEntry: "Pad entries", - typeGameScore: "GAME SCORES", + statsPadEntry: "Entrées de pad", + typeGameScore: "SCORES DE JEU", bankTaxesLookupNote: "Entre un ID de bloc pour le rechercher dans ton flux local.", bankTaxesUserNoteChipsClick: "Clique sur n'importe quel chip pour inspecter son bloc dans le blockexplorer.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3442,6 +4623,14 @@ module.exports = { settingsReplicationTitle: "Réplication", settingsReplicationDesc: "Configure le nombre de sauts que ton pair suit à partir de ton propre flux lors de la réplication du contenu.", settingsReplicationHopsLabel: "Sauts", + settingsBlobCacheTitle: "Cache des médias", + settingsBlobCacheDesc: "Le contenu des autres habitants est conservé sur cet appareil. Choisissez l'espace qu'il peut occuper.", + settingsBlobCacheUsage: "Utilisation actuelle", + settingsBlobCacheFiles: "fichiers", + settingsBlobCacheLimit: "Limite", + settingsBlobCacheUnlimited: "Illimité", + settingsBlobCacheCleanNow: "Nettoyer maintenant", + settingsBlobCacheCleaned: "{n} fichiers supprimés, {size} libérés.", aiApproveTagsLabel: "Étiquettes (séparées par virgules)", aiApproveTagsPlaceholder: "ex. oasis, gouvernance, écologie", aiApproveRatingLabel: "Note", @@ -3469,13 +4658,11 @@ module.exports = { larpInviteBannerHint: "Partage ce code avec une personne en ACADEMIA. Il expire dans 30 cycles ou après une utilisation.", larpTestRankingTitle: "Score par maison", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Code d'invitation de maison", - invitesHouseJoinButton: "Rejoindre la Maison", - ecoTaxLabel: "ECO Tax", + ecoTaxLabel: "Taxe ECO", bankTaxes: "Impôts", - bankOverviewYourTaxes: "Tes impôts", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesArchTaxTitle: "ARCH Tax", + bankOverviewYourTaxes: "Total des impôts", + bankTaxesEcoTaxTitle: "Taxe ECO", + bankTaxesArchTaxTitle: "Taxe ARCH", bankTaxesUserAmount: "Ton ECO Tax (prix à rendre)", bankTaxesArchTaxUserAmount: "Ton ARCH Tax (prix à rendre)", bankTaxesArchTaxFirstBlock: "Âge de ton premier bloc", @@ -3503,7 +4690,7 @@ module.exports = { bankTaxesLookupType: "Type", bankTaxesLookupSize: "Taille", bankTaxesLookupCarbon: "Empreinte carbone", - bankTaxesLookupEcoin: "ECO Tax", + bankTaxesLookupEcoin: "Taxe ECO", bankExchangeEcoTaxAnnual: "ECOin Taxes (annuel)", bankExchangeEcoTaxMonthly: "ECOin Taxes (mensuel)", bankTaxesTypesLabel: "Sélectionne les impôts que tu veux payer", @@ -3511,14 +4698,14 @@ module.exports = { bankRulesPoolTitle: "Pool mensuel", bankRulesShareTitle: "Part par utilisateur", bankRulesKarmaTitle: "Karma (score d'engagement)", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesArchTaxTitle: "ARCH Tax", + bankRulesEcoTaxTitle: "Taxe ECO", + bankRulesArchTaxTitle: "Taxe ARCH", bankRulesChipTitle: "Bande de couleur du chip ECO Tax", bankRulesEpochKind: "Granularité d'epoch", bankRulesAlpha: "Alpha (part max. du solde du pub par epoch)", bankRulesReserveMin: "Réserve minimale (conservée dans le solde du pub)", bankRulesCapPerEpoch: "Plafond par epoch (absolu)", - bankRulesGraceDays: "Délai de claim", + bankRulesGraceDays: "Délai de grâce de la réclamation", bankRulesWMin: "Poids minimum (w_min)", bankRulesWMax: "Poids maximum (w_max)", bankRulesFloor: "Plancher par utilisateur (brut)", @@ -3530,12 +4717,11 @@ module.exports = { blockchainBlockNotAvailable: "Ce bloc n'est pas disponible dans ton feed local. Il appartient peut-être à un peer dont tu ne réplique pas encore les données.", blockchainBackToBlockexplorer: "Retour au blockexplorer", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeDetailTitle: "Transcode", + audioTranscodeButton: "TRANSCODER", + audioTranscodeDetailTitle: "Transcoder", audioTranscodeDetailDescription: "Décode la charge utile incrustée et la carte de composition d'origine de la blockchain.", - audioTranscodeStegoOasisId: "Par", audioTranscodeStegoTimestamp: "Généré le", - audioTranscodeStegoMessage: "TEXT", + audioTranscodeStegoMessage: "TEXTE", audioTranscodeStegoEmpty: "(aucun)", audioTranscodeStegoUnknown: "—", audioTranscodeStegoNotFound: "Aucune charge stéganographique n'a pu être décodée depuis cet audio.", @@ -3543,7 +4729,6 @@ module.exports = { audioBackToBcs: "Retour à BCS", melodyCompositionTitle: "Carte de composition blockchain", melodyFilterMine: "MIEN", - melodyByLabel: "Par", melodyAllEmpty: "Pas encore de compositions BCS d'autres habitants.", melodyNoteUnavailable: "le bloc n'est pas dans ton feed local", melodyUploadBcsTitle: "Publier", @@ -3569,7 +4754,7 @@ module.exports = { larpBadgeYou: "Toi", larpBadgeRuling: "Règne", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Module pour la couche de jeu de rôle grandeur nature d'Oasis (les 9 Maisons).", + modulesLarpDescription: "Module pour la couche de jeu de rôle grandeur nature.", larpProfileQ1: "Face à un problème complexe, que fais-tu en premier ?", larpProfileQ1O1: "Parler aux autres pour trouver un consensus", larpProfileQ1O2: "Construire un prototype à tester", @@ -3643,7 +4828,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Choisis le mode de navigation UX que tu veux pour ton interface.", uxModeMenus: "Blocs", - uxModeAINav: "AI", + uxModeChats: "Conversations", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Tags Tendance", + feedActiveTitle: "Habitants Actifs", + cnBrandSub: "Libre · P2P · Fédéré", + cnSyncedPeers: "Pairs synchronisés", + cnLicense: "Licence", + cnNotAccessible: "Le contenu n'est pas accessible pour le moment.", + cnUntitled: "Sans titre", + cnSearchPlaceholder: "Rechercher…", + cnHubTitle: "HUB Clearnet", + cnHubDescription: "Contenu public partagé par les habitants de ce réseau Oasis.", + cnHubEmpty: "Aucun contenu public n'a encore été partagé sur la Clearnet.", + cnPublicContent: "Contenu", + cnTribesContent: "Tribus", + cnNetworkContent: "Réseau", + cnCategoryEmpty: "Aucun contenu dans cette catégorie.", + cnInhabitantEmpty: "Cet habitant n'a pas encore publié de contenu sur la Clearnet.", + cnPrevPage: "← Précédent", + cnNextPage: "Suivant →", + cnPageOf: "Page {page} sur {pages}", + cnMoreInfo: "Plus d'infos →", + cnOpenCourse: "Cours ouvert", + cnAuction: "ENCHÈRE", + cnExchange: "ÉCHANGE", + cnRssDescription: "publiés par les habitants de ce HUB", + cnKindAudio: "Audio", + cnKindBlog: "Blog", + cnKindBookmark: "Marque-page", + cnKindDocument: "Document", + cnKindEvent: "Événement", + cnKindFeed: "Fil", + cnKindImage: "Image", + cnKindJob: "Emploi", + cnKindMarket: "Marché", + cnKindPodcast: "Podcast", + cnKindProject: "Projet", + cnKindCourse: "Cours", + cnKindShop: "Boutique", + cnKindTorrent: "Torrent", + cnKindFile: "Fichier", + cnKindVideo: "Vidéo", + cnKindWiki: "Wiki", + cnKindRoom: "Salon", + cnKindMap: "Carte", + uxModeAINav: "IA", } }; diff --git a/src/client/assets/translations/oasis_gl.js b/src/client/assets/translations/oasis_gl.js new file mode 100644 index 0000000..76d39db --- /dev/null +++ b/src/client/assets/translations/oasis_gl.js @@ -0,0 +1,4880 @@ +const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); +module.exports = { + gl: { + shopOrderStatusPaid: "Pagamento recibido", + shopOrderStatusReceived: "Recibido", + shopOrderMarkPaid: "Marcar pagamento recibido", + shopOrderConfirmReceived: "Confirmar recibido", + shopMyOrdersTitle: "Os meus pedidos", + shopPurchasesButton: "Compras", + shopMyOrdersDescription: "Os teus pedidos de compra.", + shopMyOrdersEmpty: "Aínda non tes compras.", + shopOrderPmSeller: "PM Vendedor", + shopOrderSeller: "Vendedor", + shopOrderInvoice: "Factura", + shopOrderInvoiceConcept: "Factura", + shopOrderStatusPending: "Pendente", + shopOrderStatusAccepted: "Aceptado", + shopOrderStatusRejected: "Rexeitado", + shopOrderStatusShipped: "Enviado", + shopOrderAccept: "Aceptar", + shopOrderReject: "Rexeitar", + shopOrderMarkShipped: "Marcar enviado", + shopOrderPmBuyer: "PM Comprador", + shopOrderContractConcept: "Pedido de tenda", + shopOrdersPending: "Pedidos pendentes", + all: "Todos", + mine: "Meus", + recent: "Recentes", + anonymous: "Anónimo", + unnamed: "Anónimo", + authorLabel: "Autoría", + delete: "Eliminar", + save: "Gardar", + vote: "Votar", + url: "URL", + price: "Prezo", + priority: "Prioridade", + severity: "Gravidade", + organizer: "Organizador", + edited: "editado", + isPublic: "Público", + searchIsPublicLabel: "Público", + privacyPrivate: "PRIVADO", + privacyPublic: "PÚBLICO", + notFound: "Non atopado", + no_results: "Sen datos", + searchButton: "Buscar", + mapZoomLabel: "Zoom", + modulesTitle: "Módulos", + viewJson: "Ver JSON", + matchScore: "Puntuación de coincidencia", + preferencesLabel: "Preferencias", + inhabitantProfileTitle: "Habitante", + contentWarningLabel: "Aviso de contido", + invalidPost: "Contido non válido", + invalidPostHint: "Esta mensaxe ten o texto baleiro ou non válido.", + spreadChron: "Réplicas", + spreaded: "Replicado", + spreadContentUnavailable: "Contido aínda non dispoñible (replicación pendente)", + filteredByTag: "Filtrado por etiqueta", + filteredByTagTitle: "Filtrado por etiqueta", + feedPublishedSuccess: "Publicación creada correctamente!", + feedMastodonFailed: "Publicado en Oasis, pero non se puido enviar a Mastodon.", + tribeFeedSent: "Mensaxe enviada correctamente!", + tribeContentEncrypted: "Contido cifrado", + tribeTorrentsEmpty: "Aínda non hai torrents.", + torrentDescription: "Descrición", + torrentDownload: "Descargar", + chatInviteMode: "Convidar", + padInviteMode: "Convidar", + noInviteMode: "Sen modo de convite", + noDeadline: "Sen data límite", + noStatus: "Sen estado", + noSeverity: "Sen gravidade", + calendarDeleteDate: "Eliminar data", + calendarIntervalUntil: "Ata", + bookmarkLastVisit: "Última visita", + profileClearnetBookmarksLabel: "Marcadores", + invitesPort: "Porto", + peerHostValidation: "IPv4 válida (p. ex. 192.168.1.100) ou nome de host (p. ex. pub.solarnethub.com)", + peerPortValidation: "Porto 1-65535", + peerKeyValidation: "Chave pública SSB ed25519 (@<44 caracteres base64>=.ed25519)", + inboxJobUnsubscribedTitle: "Baixa da túa oferta de emprego", + inboxProjectFollowedTitle: "Novo seguidor do teu proxecto", + inboxProjectUnfollowedTitle: "Deixou de seguir o teu proxecto", + pmHasFollowedYourProject: "seguiu o teu proxecto", + pmHasUnfollowedYourProject: "deixou de seguir o teu proxecto", + pmHasUnsubscribedFromYourJobOffer: "deuse de baixa da túa oferta de emprego", + marketDeleteButton: "Eliminar", + marketNoBids: "Aínda non se fixeron poxas.", + marketAuctionBidAmount: "Importe da poxa", + marketAuctionBidTime: "Hora da poxa", + marketAuctionUser: "Poxador", + parliamentActorInPower: "TRIBO GOBERNANTE", + parliamentWinningCandidature: "Candidatura gañadora", + projectBounty: "Recompensa", + projectFollowing: "SEGUINDO", + projectBackerAuthor: "Patrocinador", + projectBackerDate: "Data", + projectConfirmTransferButton: "Confirmar transferencia", + projectPendingTransfersTitle: "Transferencias pendentes", + transfersUpdateSectionTitle: "Actualizar transferencia", + taskUnassignedFrom: "Desasignado de", + taskDescriptionPlaceholder: "Descrición da tarefa", + exportPasswordLabel: "Contrasinal de exportación", + importPasswordPlaceholder: "Introduce o contrasinal", + searchCasesPlaceholder: "Buscar en Tribunais...", + shopBuyDeliveryAddressPlaceholder: "Enderezo de entrega", + shopBuyNotesPlaceholder: "Notas", + bankTaxesUserNoteBold: "reducir directamente o imposto ECO", + bankTaxesUserNoteIntro: "O teu imposto ECO dedúcese do excedente que a rede xera por riba da túa RBU; o valor base da RBU fíxase como mínimo inamovible. O imposto nunca se deduce da cantidade fixa que lle corresponde a cada habitante como RBU. Os fondos deducidos alimentan o algoritmo de redistribución da riqueza e canalízanse de volta cara a outros habitantes mediante as súas reclamacións de RBU. Eses outros habitantes que reciben máis RBU para os seus proxectos probablemente terán algunha tarefa dedicada a axudarte a reducir o teu imposto ECO. Ou poden dedicarse a ", + bankTaxesUserNoteOutro: ", e o consenso da rede pode requirir a súa contribución continua.", + courtsViewDetails: "Ver", + courtsViewDetailsShort: "Detalles", + courtsThDetails: "Detalles", + courtsDetailsAnswersTitle: "Respostas", + courtsDetailsEvidenceTitle: "Probas", + courtsDetailsSettlementsTitle: "Acordos", + courtsDetailsVerdictTitle: "Veredicto", + courtsDetailsNotPublic: "Non público", + courtsEvidenceSideAccuser: "Probas da acusación", + courtsEvidenceSideRespondent: "Probas da defensa", + courtsEvidenceSideUnknown: "Descoñecido", + courtsJudgeNotAssigned: "-", + courtsSupportCase: "Apoiar o caso", + courtsSettlementAcceptBtn: "Aceptar acordo", + courtsSupportCount: "Apoios", + reportsSetStatus: "Cambiar estado", + reportsStatusClosed: "PECHADO", + statsAvgPerInhabitant: "Media por habitante", + statsCarbonMaxAnnual: "Estimación máx. anual", + statsCarbonNetwork: "Total da rede", + statsCarbonOfEstMax: "da capacidade máx. estimada", + statsCarbonYearProgress: "do ano transcorrido", + statsNetworkTitle: "Rede", + statsStorageTitle: "Almacenamento", + statsEcoTaxTitle: "Taxa ECO", + statsAccountTitle: "Conta", + statsAveragesTitle: "Medias", + statsCarbonOfNetwork: "do total da rede", + statsCarbonUser: "A túa pegada", + statsContentCountColumn: "Cantidade", + statsContentTypeColumn: "Tipo", + statsMsgsPerDay: "Mensaxes/día (histórico)", + statsMyShare: "A túa parte da rede", + statsNetworkSpan: "Antigüidade da rede", + statsTombstoneRatioLabel: "Proporción de borrados", + statsTopTagsTitle: "Etiquetas principais", + statsTopTypesTitle: "Tipos de contido principais", + statsTotalMsgs: "Mensaxes totais", + extended: "Multiverso", + extendedDescription: [ + "Cando apoias a alguén, podes descargar as publicacións dos habitantes que apoia, e esas publicacións aparecerán aquí, ordenadas pola máis recente.", + ], + popular: "Destacadas", + popularDescription: [ + "Publicacións de habitantes da túa rede, ", + strong("ordenadas por apoios"), + ". Selecciona o período de tempo para obter unha lista.", + ], + week: "Semana", + month: "Mes", + year: "Ano", + latest: "Últimos", + latestDescription: [ + strong("Publicacións"), + " túas e de habitantes que apoias, ordenadas polas máis recentes.", + ], + topics: "Temas", + topicsDescription: [ + strong("Temas"), + " teus e de habitantes que apoias, ordenados polos máis recentes.", + ], + summaries: "Resumos", + summariesDescription: [ + strong("Temas e algúns comentarios"), + " teus e de habitantes que apoias, ordenados polos máis recentes.", + ], + threads: "Fíos", + threadsDescription: [ + strong("Publicacións que teñen comentarios"), + " de habitantes que apoias e do teu multiverso, ordenadas polas máis recentes.", + ], + profile: "Avatar", + inhabitants: "Habitantes", + peersReplicatedFeeds: "Replicados", + peersTorColumn: "TOR", + peersTorYes: "Accesible por Tor", + peersTorNo: "Non accesible por Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Versión", + graphos: "Graphos", + graphosDescription: "Mapa interactivo da rede ao teu redor.", + graphosFederation: "Federación", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Inalcanzables", + graphosFedLinks: "Ligazóns mutuas", + graphosFedIsolated: "Illados", + graphosFedNoData: "Sen datos", + graphosFedMutual: "Mutuo", + graphosFedOneWay: "Nun sentido", + graphosFedTransport: "Rede", + graphosFedWith: "Federado con", + graphosZoom: "Ampliar", + graphosFedInhabitants: "Habitantes", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "O teu nodo aínda non coñece ningún PUB.", + graphosViewingGraphOf: "Vendo", + graphosBackToMine: "← A miña rede", + graphosShowing: "Amosando", + graphosYou: "Ti", + graphosTotalNodes: "Nodos totais", + mentions: "Mencións", + mentionsDescription: [ + strong("Todas as mensaxes que te @mencionan"), + ".", + ], + noMentions: "Aínda non recibiches @mencións.", + mentionsSearchPlaceholder: "Buscar en Mencións...", + privateReminders: "RECORDATORIOS", + inboxFilterPms: "PMs", + inboxFilterNotifications: "Notificacións", + inboxMarkRead: "Marcar como lido", + inboxMarkUnread: "Marcar como non lido", + inboxUnreadChip: "Non lido", + inboxMarkAllRead: "Marcar todo como lido", + inboxDeleteShown: "Borrar todo", + inboxArchive: "Arquivar", + inboxUnarchive: "Desarquivar", + inboxFilterArchived: "Arquivados", + inboxEarlierMessages: "mensaxes anteriores", + inboxSortThread: "Por fío", + inboxSortRecent: "Máis recentes primeiro", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Notificacións do Inbox", + settingsInboxBotsDesc: "Escolle que bots poden avisarte no teu Inbox. Os bots desmarcados ocúltanse e os seus avisos non se contan.", + inboxFiltersLabel: "Filtros:", + inboxToggleToMutuals: "Cambiar a mutuos", + inboxToggleToWhole: "Cambiar a todos", + privateDate: "Data", + peers: "Nodos", + searchDescription: "Descrición", + searchFromLabel: "Desde", + searchToLabel: "Ata", + searchQueryLabel: "Busca", + searchPerPageLabel: "Resultados", + settings: "Configuración", + settingsDescription: "Configura o contorno da túa rede.", + continueReading: "Continuar lendo", + moreComments: "máis comentarios", + readThread: "ler o resto do fío", + pixeliaTitle: "Pixelia", + pixeliaDescription: "Debuxa píxeles nunha grella e colabor-ART con habitantes da túa rede.", + melodyTitle: "Melody", + melodyDescription: "Reproduce a melodía da túa blockchain — cada bloque convértese nunha nota.", + melodyEmpty: "Aínda non hai notas — a túa blockchain aínda non xerou ningún bloque.", + melodyTotalBlocks: "Notas", + melodyFilterAll: "TODAS", + melodyRegenerate: "Rexenerar", + melodyDownload: "Descargar BCS", + profileContentSectionTitle: "Contido do Avatar", + profileContentHelp: "Escolle que módulos se amosarán no teu avatar.", + profileSensorsHelp: "Métricas opcionais que se amosan no teu avatar.", + profileClearnetHelp: "Módulos que se poden consultar desde fóra de Oasis.", + profileHubAll: "Todo", + footerLicenseLabel: "Licenza", + profileVisibilityFediverse: "Multiverso", + contributorsTitle: "Contribucións", + colorLabel: "Escolle unha cor", + paintButton: "Debuxar!", + totalPixels: "Total de píxeles", + modules: "Módulos", + modulesViewTitle: "Módulos", + modulesViewDescription: "Define o teu contorno ideal activando e desactivando módulos.", + inbox: "Buzón", + inboxSearchPlaceholder: "Buscar no Buzón...", + multiverse: "Multiverso", + fediverse: "Multiverso", + fediverseDescription: "Xestiona as túas outras contas do multiverso, incluído enviar e recibir contido.", + fediverseStatus: "Estado", + fediverseDisconnected: "Multiverso desconectado. Comproba %LINK% ou o estado da conexión.", + fediverseSettingsTitle: "Multiverso", + fediverseInstanceLabel: "Enderezo", + fediverseTokenLabel: "Token de acceso", + fediverseConnect: "Conectar", + fediverseConnectedAs: "Conectado como", + nextcloudServerLabel: "Servidor", + nextcloudUserLabel: "Usuario", + nextcloudFolderLabel: "Cartafol", + nextcloudUploadButton: "Subir", + nextcloudFileLabel: "Escolle un ficheiro", + fileExternalLink: "Ligazón externa", + nextcloudErrConnect: "Non se puido chegar a Nextcloud con estes datos.", + nextcloudErrUpload: "A subida a Nextcloud fallou.", + nextcloudErrShare: "Nextcloud non deu unha ligazón pública para o ficheiro.", + fediverseDisconnect: "Desconectar", + fediverseEmptyLink: "a túa configuración", + fediverseComposePlaceholder: "Que estás a pensar?", + fediverseReplyPlaceholder: "Escribe a túa resposta…", + fediverseAttach: "Anexar multimedia", + fediversePublish: "Publicar", + fediverseReply: "Responder", + fediverseBoosted: "impulsou", + fediverseNoPosts: "A túa cronoloxía está baleira.", + fediverseTimeline: "Cronoloxías", + fediverseFollowers: "Seguidores", + fediverseFollowing: "Seguindo", + fediversePosts: "Publicacións", + fediverseJoined: "Uniuse", + fediverseRefresh: "Actualizar", + fediverseWrite: "Escribir", + fediversePreview: "Vista previa", + fediverseManage: "Xestionar", + fediverseError: "Non se puido contactar con Mastodon.", + fediverseErrInstance: "URL de instancia non válido.", + fediverseErrAuth: "Token non válido ou caducado.", + fediverseErrConnect: "Non se puido conectar coa instancia.", + fediverseErrToken: "O token é obrigatorio.", + fediverseErrPublic: "Non dispoñible en modo público.", + fediverseErrFetch: "Non se puido cargar a cronoloxía.", + fediverseErrPost: "Non se puido publicar.", + fediverseErrMedia: "Non se puido subir o ficheiro.", + fediverseErrEmpty: "Escribe algo ou anexa un ficheiro.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Número de teléfono", + telegramSendCode: "Enviar código", + telegramCodeHelp: "Introduce o código que Telegram che acaba de enviar (app ou SMS).", + telegramCodeLabel: "Código", + telegramPasswordHelp: "Esta conta ten verificación en dous pasos. Introduce o teu contrasinal.", + telegramPasswordLabel: "Contrasinal", + telegramVerify: "Verificar", + telegramCancel: "Cancelar", + telegramChats: "Chats", + telegramUnread: "Sen ler", + telegramOpen: "Abrir", + telegramSend: "Enviar", + telegramYou: "Ti", + telegramNoChats: "Aínda non hai chats.", + telegramNoMessages: "Aínda non hai mensaxes.", + telegramComposePlaceholder: "Escribe unha mensaxe…", + telegramChannel: "Canle", + telegramGroup: "Grupo", + telegramPrivate: "Privado", + telegramErrConnect: "Non se puido contactar con Telegram.", + telegramErrTimeout: "Telegram tardou demasiado en responder.", + telegramErrAuth: "Sesión de Telegram non válida ou caducada. Conéctaa de novo.", + telegramErrFetch: "Non se puideron cargar os chats.", + telegramErrSend: "Non se puido enviar a mensaxe.", + telegramErrMissing: "Fan falta o API ID, o API hash e o número de teléfono.", + telegramErrCode: "Código incorrecto. Téntao de novo.", + telegramErrCodeExpired: "O código caducou. Comeza de novo.", + telegramErrPassword: "Contrasinal incorrecto.", + telegramErrPhone: "Número de teléfono non válido.", + telegramErrApi: "API ID ou API hash non válidos.", + telegramErrFlood: "Demasiados intentos. Agarda un anaco e téntao de novo.", + telegramErrNoLogin: "Non hai ningún inicio de sesión en curso. Comeza de novo.", + peertubeUsernameLabel: "Usuaria", + peertubePasswordLabel: "Contrasinal", + peertubeSubscriptions: "Subscricións", + peertubeMyVideos: "Os meus vídeos", + peertubeVideos: "Vídeos", + peertubeFollowers: "Seguidoras", + peertubeViews: "visualizacións", + peertubeLikes: "gústame", + peertubeQuota: "Cota", + peertubeUpload: "Subir vídeo", + peertubeUploadHelp: "Escolle un ficheiro de vídeo. Súbese á túa canle e PeerTube procésao despois.", + peertubeTitleLabel: "Título", + peertubeDescriptionLabel: "Descrición", + peertubePrivacyLabel: "Privacidade", + peertubePrivacyPublic: "Público", + peertubePrivacyUnlisted: "Non listado", + peertubePrivacyPrivate: "Privado", + peertubeNoVideos: "Aínda non hai vídeos.", + peertubeComments: "Comentarios", + peertubeComment: "Comentar", + peertubeCommentPlaceholder: "Escribe un comentario…", + peertubeNoComments: "Aínda non hai comentarios.", + peertubeOpen: "Abrir en PeerTube", + peertubeWatch: "Ver", + peertubeChannel: "Canle", + peertubeNoStream: "Este vídeo aínda non se pode reproducir aquí. Ábreo en PeerTube.", + peertubeUploaded: "Vídeo subido. PeerTube estao a procesar.", + peertubeErrConnect: "Non se puido contactar con PeerTube.", + peertubeErrAuth: "Usuaria ou contrasinal incorrectos, ou a sesión caducou. Conéctaa de novo.", + peertubeErrInstance: "URL de instancia non válida.", + peertubeErrMissing: "Fan falta instancia, usuaria e contrasinal.", + peertubeErrFetch: "Non se puideron cargar os vídeos.", + peertubeErrUpload: "Non se puido subir o vídeo.", + peertubeErrTitle: "Fai falta un título.", + peertubeErrComment: "Non se puido publicar o comentario.", + peertubeErrNoFile: "Escolle un ficheiro de vídeo.", + bookmarksLabel: "Marcadores", + videosLabel: "Vídeos", + torrentsLabel: "Torrents", + docsLabel: "Documentos", + audiosLabel: "Audios", + tagsLabel: "Etiquetas", + imagesLabel: "Imaxes", + inhabitantsLabel: "Habitantes", + trendingLabel: "Tendencias", + eventsLabel: "Eventos", + apply: "Aplicar", + menuPersonal: "Persoal", + menuGovernance: "Gobernanza", + menuOffice: "Oficina", + menuNetwork: "Comunidade", + menuCreative: "Creativo", + menuEconomy: "Economía", + menuMedia: "Biblioteca", + menuTools: "Ferramentas", + comment: "Comentar", + subtopic: "Subtema", + json: "JSON", + unfollow: "Deixar de apoiar", + follow: "Apoiar", + block: "Bloquear", + unblock: "Desbloquear", + relationshipNotFollowing: "Non te apoia", + relationshipTheyFollow: "Apóiate", + relationshipMutuals: "Apoio mutuo", + relationshipFollowing: "Estás apoiando", + relationshipYou: "Ti", + relationshipBlocking: "Estás bloqueando", + relationshipBlockedBy: "Bloquéate", + relationshipMutualBlock: "Bloqueo mutuo", + relationshipNone: "Non estás apoiando", + relationshipBlockingPost: "Post bloqueado", + viewLikes: "Ver réplicas", + viewSupporters: "Ver apoios", + spreadedDescription: "Lista de contidos replicados por habitante.", + preview: "Previsualizar", + publish: "Publicar", + contentWarningPlaceholder: "Engade un título ao teu post (opcional)", + publishWarningPlaceholder: "Escribe o corpo da túa entrada.", + publishTooLong: "A túa publicación é demasiado longa. Acúrtaa, por favor.", + keepTextRetry: "Enviar de novo", + dateInPastError: "A data non pode ser anterior a hoxe", + publishCustomDescription: [ + "LEMBRA: Por mor da tecnoloxía blockchain, unha vez que publicas algo, non se pode editar nin borrar. Pénsao ben!", + ], + commentWarning: [ + "LEMBRA: Por mor da tecnoloxía blockchain, unha vez que publicas algo, non se pode editar nin borrar. Pénsao ben!", + ], + commentLabel: ({ publicOrPrivate, markdownUrl }) => [ + ], + publishLabel: ({ markdownUrl, linkTarget }) => [ + "LEMBRA: Por mor da tecnoloxía blockchain, unha vez que publicas algo, non se pode editar nin borrar. Pénsao ben!", + ], + replyLabel: ({ markdownUrl }) => [ + "LEMBRA: Por mor da tecnoloxía blockchain, unha vez que publicas algo, non se pode editar nin borrar. Pénsao ben!", + ], + publishCustomInfo: ({ href }) => [ + "Se tes experiencia, tamén podes ", + a({ href }, "escribir un post avanzado"), + ".", + ], + publishBasicInfo: ({ href }) => [ + "Se non tes experiencia, deberías ", + a({ href }, "escribir un post normal"), + ".", + ], + publishCustom: "Escribir un post avanzado", + messagePreview: "Vista previa", + mentionsMatching: "Mencións", + mentionsName: "Nome", + mentionsRelationship: "Relación", + updateBannerText: "Hai unha nova versión de Oasis dispoñible.", + updateBannerAction: "Actualizar agora!", + info: "Info", + settingsIntro: ({ version }) => [ + `[SNH] ꖒ OASIS [ v.${version} ]`, + ], + timeAgo: "hai", + sendTime: "escrito ", + theme: "Tema", + backupKeysExportButton: "Exportar", + backupKeysImportButton: "Importar", + ssbLogStream: "Blockchain", + ssbLogStreamDescription: "Configura o límite de mensaxes para os fluxos da blockchain.", + logWindowChip: "Últimos {n} streams da blockchain", + saveSettings: "Gardar a configuración", + exportDescription: "Establece un contrasinal (mínimo 32 caracteres de lonxitude) para cifrar o teu segredo", + exportDataTitle: "Copia instantánea", + exportDataDescription: "Descargar a túa blockchain (chave privada excluída!)", + exportDataButton: "Descargar a blockchain", + importDescription: "Importa o teu segredo cifrado (chave privada) para activar o teu avatar", + passwordImport: "Escribe o teu contrasinal para descifrar os datos que se gardarán no teu sistema (nome: secret)", + exportPasswordPlaceholder: "Usa minúsculas, maiúsculas, números e símbolos", + fileInfo: "O teu segredo cifrado descargarase no teu dispositivo (nome: oasis.enc)", + developer: "Desenvolvemento", + devTitle: "Desenvolvemento", + welcomeBannerText: "O teu avatar aínda non está configurado.", + aiSuggestionBanner: "Suxestión:", + aiSuggestionsEnable: "Suxestións da IA", + welcomeBannerAction: "Comezar", + welcomeTitle: "Benvida", + welcomeStepIdentity: "Identidade", + welcomeStepType: "Tipo", + welcomeStepUse: "Uso", + welcomeStepLook: "Aparencia", + welcomeStepCommunity: "Comunidade", + welcomeStepIntro: "Benvida", + welcomeHintIdentity: "O teu nome e a túa imaxe.", + welcomeHintType: "Como se abre Oasis para ti.", + welcomeHintUse: "Que módulos quedan activos.", + welcomeHintLook: "O tema que viste Oasis.", + welcomeHintCommunity: "Entra nun PUB para coñecer outros habitantes.", + welcomeHintIntro: "Envía un primeiro saúdo á comunidade.", + welcomeStepThemeAction: "Usar este tema", + welcomeJoinPubs: "Entrar nos PUBs", + welcomeNoFederation: "Sen federarse", + welcomeStepGreetingAction: "Enviar feed", + welcomeDescription: "Unhas poucas cousas que convén facer antes de comezar.", + welcomeSkip: "Agora non", + welcomeStepProfileAction: "Gardar o perfil", + welcomeBootstrapRunning: "Cargando a instantánea do pub. Ao rematar únese ao pub automaticamente; mentres tanto podes seguir usando Oasis.", + welcomeCompleteTitle: "Todo listo.", + welcomeCompleteText: "O teu nodo está preparado. A partir de aquí a rede medra cos habitantes aos que apoias.", + welcomeFindPeople: "Buscar habitantes", + devFilterFiles: "FICHEIROS", + devFilterSearch: "BUSCAR", + devFilterModules: "MÓDULOS", + devFilterTranslations: "TRADUCIÓNS", + devFilterStyles: "ESTILOS", + devFilterTests: "TESTS", + devVersion: "Versión", + devNodeVersion: "Node", + devStatsTests: "Tests", + devParentFolder: "Cartafol superior", + devOpenFolder: "abrir", + devDescription: "Explora o código que se está a executar neste nodo.", + devRoot: "raíz", + devSearchPlaceholder: "Texto que buscar no código", + devAnyExtension: "Calquera ficheiro", + devStatsFiles: "Ficheiros", + devStatsLines: "Liñas", + devStatsModules: "Módulos", + devStatsLanguages: "Idiomas", + devNotViewable: "non visualizable", + devEmptyFolder: "Este cartafol está baleiro.", + devSize: "Tamaño", + devShowing: "Amosando", + devLine: "liña", + devReportBug: "Informar dun erro", + devCreateTask: "Crear tarefa", + devRaw: "RAW", + devReportContext: "Atopado lendo o código de Oasis", + devTaskPrefix: "Revisar", + devPrevPage: "Liñas anteriores", + devNextPage: "Liñas seguintes", + devMatches: "coincidencias", + devFilesScanned: "ficheiros revisados", + devNoResults: "Aínda non hai coincidencias.", + devColModule: "Módulo", + devColState: "Estado", + devColModel: "Modelo", + devColView: "Vista", + devColRoutes: "Rutas", + devColTests: "Tests", + devOn: "activo", + devOff: "inactivo", + modulesDevLabel: "Desenvolvemento", + modulesDevDescription: "Módulo para xestionar o código de Oasis.", + themeIntro: + "Escolle un tema.", + setTheme: "Establecer o tema", + language: "Idioma", + languageDescription: + "Se queres usar outro idioma, selecciónao aquí.", + setLanguage: "Establecer o idioma", + peerConnectionsIntro: "Xestiona todas as túas conexións con outros nodos.", + peersConnectFailed: "Non se puido conectar: {reason}.", + peerStateLabel: "Estado", + peerAddressLabel: "Enderezo", + peerLastChangeLabel: "Último cambio", + peerStateConnected: "Conectado", + peerStateConnecting: "Conectando", + peerStateStaged: "Dispoñible", + peerErrSilent: "Sen actividade", + peerErrTimeout: "Non responde", + peerErrRefused: "Rexeita a conexión", + peerErrNotFound: "Enderezo descoñecido", + peerErrUnreachable: "Rede inaccesible", + peerErrKeys: "Usando chaves diferentes", + peerErrIdentity: "Non acepta a túa conexión", + peerErrTor: "Só TOR", + peerErrDropped: "Conexión cortada", + peerErrPaused: "Rede en pausa", + peerErrOther: "Erro de conexión", + peerOutdated: "Desactualizado", + peerVersionUnannounced: "Non anuncia a súa versión: un Oasis antigo", + peersTorMissing: "Este PUB só permite conexións dende TOR.", + peerConnectionsTitle: "Conexións", + online: "Conectado", + offline: "Desconectado", + discovered: "Descubertos", + unknown: "Descoñecidos", + lanBroadcastActive: "Activa (multicast UDP na LAN)", + peerSourceRpc: "RPC", + peerSourceGossip: "Gossip", + peerSourceEbt: "EBT", + peerSourceRecent: "Recente", + peerSourceLan: "LAN", + pub: "PUB", + supported: "Apoiado", + recommended: "Recomendado", + blocked: "Bloqueado", + noConnections: "Non hai nodos conectados.", + noDiscovered: "Non descubriches ningún nodo.", + noUnknownPeers: "Non hai nodos descoñecidos no grafo de amizades.", + peersTechnicalEmpty: "Aínda non hai nodos rexistrados.", + peerConnect: "Conectar", + peerDisconnect: "Desconectar", + peerRefresh: "Refrescar", + peerPruneIdle: "Quitar inactivos", + peersPause: "Pausar", + peersResume: "Retomar", + peersPausedNote: "En pausa: Oasis non se conecta con ninguén e ninguén se pode conectar a el.", + peersPauseHint: "Detén todas as conexións: Oasis non se conecta a outros nodos nin os acepta ata que retomes.", + peerExport: "Exportar", + peerImport: "Importar", + peerImportTitle: "Importar lista de nodos", + peerImportPlaceholder: "Pega un enderezo multiserver por liña, ou sube un ficheiro .txt…", + connectionActionIntro: + "", + indexes: "Índices", + indexesRebuilt: "Índices reconstruídos", + indexesRebuildFailed: "Non se puideron reconstruír os índices", + indexesFiles: "Ficheiros de índice", + indexesDescription: + "Reconstruír os índices é seguro e pode arranxar algúns erros da túa blockchain.", + homePageTitle: "Páxina de inicio", + homePageDescription: "Selecciona que módulo queres como páxina de inicio.", + saveHomePage: "Establecer a páxina de inicio", + invites: "Convites", + invitesDescription: "Xestiona e aplica códigos de convite na túa rede.", + invitesPubsHint: "Pega un código de convite dun pub para federarte con el e comezar a replicar os seus habitantes.", + invitesFederationsHint: "Pubs cos que estás federado: actualízaos, quita os inalcanzables ou exporta a lista.", + invitesTribesTitle: "Tribos", + invitesChatsTitle: "Chats", + invitesPadsTitle: "Pads", + invitesCalendarsTitle: "Calendarios", + invitesEventsTitle: "Eventos", + invitesForumsTitle: "Foros", + invitesMapsTitle: "Mapas", + invitesShopsTitle: "Tendas", + invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "PUBs Suxeridos", + invitesFilterOthers: "Outras", + invitesJoinTitle: "Convites", + invitesJoinHint: "Pega calquera código de convite, sexa do que sexa, ou o Oasis ID dun habitante.", + invitesJoinPlaceholder: "Código de convite ou Oasis ID", + invitesJoinButton: "Entrar", + invitesUsedTitle: "Últimos convites", + invitesUsedEmpty: "Aínda non utilizaches ningún convite.", + invitesUsedType: "Tipo", + invitesUsedName: "Nome", + invitesUsedDate: "Data", + invitesPubInviteCodePlaceholder: "Introduce o código de convite do PUB", + invitesAcceptInvite: "Entrar no PUB", + invitesPubUses: "Invitacións usadas", + invitesPubExhausted: "Este PUB non ten invitacións dispoñibles.", + invitesAlreadyFederated: "Xa estás federado con este pub.", + invitesJoinPubMode: "Este Oasis funciona como PUB, en modo de só lectura. Para unilo a outro PUB, usa no terminal: ./oasis.sh join ", + invitesJoinLocalOnly: "As invitacións só se aceptan desde o equipo onde funciona Oasis, ou coa ligazón de administración que mostra ao arrincar.", + invitesFederationsTitle: "Federacións", + invitesAcceptedInvites: "Federadas", + invitesNoFederatedPubs: "Non hai pubs federados.", + invitesPubsRefresh: "Refrescar", + invitesPubsClearUnreachable: "Limpar", + invitesPubsExport: "Exportar", + invitesPubsImport: "Importar", + invitesPubsImportTitle: "Importar pubs", + invitesPubsImportPlaceholder: "Pega un enderezo multiserver ou un código de convite por liña, ou sube un ficheiro .txt…", + genericError: "Produciuse un erro.", + panicMode: "Modo pánico!", + encryptData: "Establece un contrasinal (mínimo 32 caracteres de lonxitude) para cifrar a túa blockchain", + decryptData: "Introduce o contrasinal para descifrar a túa blockchain", + removeDataDescription: "COIDADO: Este proceso non se pode desfacer.", + removePanicButton: "BORRAR TODOS OS TEUS DATOS!", + searchTitle: "Buscar", + searchDescriptionLabel: "Busca contido na túa rede.", + searchPlaceholder:"Buscar contido...", + searchLocationLabel:"Localización", + searchPriceLabel:"Prezo", + searchCategoryLabel:"Categoría", + searchPriorityLabel:"Prioridade", + searchStatusLabel:"Estado", + statusLabel:"Estado", + noResultsFound:"Non se atoparon resultados.", + votesOption:"Opinións votadas", + voteYesLabel:"Si", + voteNoLabel:"Non", + allTypesLabel: "SEN LÍMITES", + YESLabel:"SI", + NOLabel:"NON", + votesOptionYesLabel:"Si", + votesOptionNoLabel:"Non", + votesOptionAbstentionLabel:"Abstención", + votesQuestionLabel:"Pregunta", + voteStatusOpen:"ABERTO", + voteStatusClosed:"PECHADO", + commentDescription: ({ parentUrl }) => [ + " comentou en ", + a({ href: parentUrl }, " fío"), + ], + commentTitle: ({ authorName }) => [`Comentou no post de @${authorName}`], + subtopicDescription: ({ parentUrl }) => [ + " creou un subtema desde ", + a({ href: parentUrl }, " un post"), + ], + subtopicTitle: ({ authorName }) => [`Creou un subtema no post de @${authorName}`], + oasisDescription: "OASIS Rede de Proxectos", + searchSubmit: "Buscar...", + votesLabel: "GOBERNO", + pdfFallbackLabel: "Documento PDF", + shopLabel: "TENDAS", + voteLabel: "VOTOS", + submit: "Aplicar", + editProfile: "Editar o Avatar", + oasisIdLabel: "OasisID", + spreadContent: "Replicar", + welcomePmSubject: "Hello World!", + welcomePmBody: "Dámosche a benvida a Oasis — unha rede social libre, peer-to-peer, federada e cifrada, cunha arquitectura distribuída e só por convite.\n\nAlgúns lugares interesantes para comezar:\n\n- [/avatar](/profile) — Edita o teu avatar, nome, descrición e que módulos se amosan no teu avatar público.\n- /settings — Axusta a túa instancia local: idioma, tema, módulos, privacidade e sincronización.\n- /invites — Engade un pub para federarte co resto da rede.\n- /peers — Mira quen está conectado, volve escanear a túa LAN, exporta ou importa listas de nodos.\n- /inhabitants — Descubre e visita os avatares doutros habitantes.\n- /tribes — Crea grupos privados con cifrado de extremo a extremo ou únete a eles.\n- /search — Busca o contido da rede por tipo.\n\nAlgúns lugares interesantes para conectar:\n\n- /multiverse — Xestiona as túas outras contas do multiverso, incluído enviar e recibir contido.\n\nEsta mensaxe enviouse de forma privada desde ti cara a ti, por iso non fixo falta conexión para recibila.", + profileSensorsSectionTitle: "Sensores", + profileVisibilityActivity: "Nivel de actividade", + profileDeviceLockedHint: "Este sensor está sempre activo: permite que outros vexan desde que dispositivo te conectas e non se pode desactivar.", + profileVisibilityKarma: "Puntuación KARMA", + profileVisibilityUbi: "RBU", + profileVisibilityWallet: "Carteira ECOin", + profileVisibilityEcoTax: "Taxa ECO", + profileVisibilityLarpSign: "Selo L.A.R.P.", + profileVisibilityGpg: "Chave GPG", + clearnetSectionTitle: "Clearnet", + profileClearnetShopsLabel: "Tendas", + profileClearnetJobsLabel: "Emprego", + profileClearnetEventsLabel: "Eventos", + profileClearnetProjectsLabel: "Proxectos", + profileClearnetPostsLabel: "Blogs", + profileClearnetAudiosLabel: "Audios", + profileClearnetVideosLabel: "Vídeos", + profileClearnetImagesLabel: "Imaxes", + profileClearnetDocumentsLabel: "Documentos", + profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Mercado", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", + editProfileDescription: + "", + profileName: "Nome", + profileImage: "Imaxe do Avatar", + profileGpgKey: "Chave pública GPG (.asc)", + profileGpgChip: "GPG", + profileGpgDownload: "Descargar", + profileGpgRemove: "Eliminar", + profileDescription: "Descrición", + hashtagDescription: + "Posts de habitantes da túa rede que fan referencia a este #hashtag, ordenados polo máis recente.", + rebuildName: "Reconstruír a blockchain", + wallet: "Carteira", + walletAddress: "Enderezo", + walletAmount: "Cantidade", + walletAddressLine: ({ address }) => `Enderezo: ${address}`, + walletAmountLine: ({ amount }) => `Cantidade: ${amount} ECO`, + walletBack: "Volver", + walletBalanceTitle: "Saldo", + walletWalletSendTitle: "Enviar", + walletReceiveTitle: "Recibir", + walletHistoryTitle: "Historial", + walletBalanceLine: ({ balance }) => `${balance} ECO`, + walletCnfrs: "Conf.", + walletConfirm: "Confirmar", + walletDescription: "Xestiona a túa carteira ECOin.", + walletDate: "Data", + walletFee: "Taxa (canto maior sexa a taxa, máis rápido se procesará a túa transacción)", + walletFeeLine: ({ fee }) => `Taxa: ECO ${fee}`, + walletHistory: "Historial", + walletReceive: "Recibir", + walletReset: "Restablecer", + walletSend: "Enviar", + walletPayingTransfer: "Pagando o transfer", + walletCreateTransferLabel: "Xerar contrato (transfer)", + walletTransferNotifiedNote: "Avisouse do pagamento á outra parte", + walletTransferCreatedNote: "Creouse un transfer (recibo) deste pagamento", + walletTransferNoPayeeNote: "Ningún habitante publicou este enderezo, así que non se creou ningún transfer", + walletTransferReceiptConcept: "Pagamento desde a carteira", + walletPaymentPmText: "envioute un pagamento", + walletPayeeLabel: "Destinatario", + walletConceptLabel: "Concepto", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Recibiches a túa RBU.", + bankingBotPaymentTitle: "Recibiches un pagamento.", + bankingBotUbiPaidText: "A túa Renda Básica Universal foi pagada", + bankUbiPubThisNode: "Este nodo paga a RBU", + bankUbiPubConnected: "PUB da RBU", + bankUbiPubNone: "De momento non hai PUBs que anuncien unha RBU.", + bankUbiAddressPublished: "Enderezo ECOin publicado", + bankUbiAddressMissing: "Publica o teu enderezo ECOin para recibir a RBU", + bankUbiClaimedThisMonth: "Reclamada este mes", + bankUbiPubBalance: "Carteira do PUB dispoñible para a RBU", + bankNoPubAddress: "O PUB da RBU aínda non publicou ningún enderezo ECOin.", + bankUbiTab: "RBU", + bankDonateEco: "DOAR!", + donateTooltip: "Doar ECOin", + bankNoUserAddress: "Este habitante non comparte ningún enderezo ECOin.", + bankUbiPubDefault: "Por defecto", + bankUbiPool: "Asignación", + bankUbiLastSeen: "Visto por última vez", + bankUbiLastPayout: "Última acción", + bankUbiPaidOut: "Repartido", + bankRangeToday: "Hoxe", + bankRangeWeek: "Semana", + bankRangeMonth: "Mes", + bankRangeYear: "Ano", + bankRangeFiveYears: "5 anos", + bankRangeAll: "Todo", + bankChartValueTitle: "Evolución do valor de ECOin", + bankChartValueHours: "Equivalencia en tempo (h)", + bankChartWealthTitle: "Redistribución da riqueza ECOin", + bankChartDistributed: "RBU repartida (ECO)", + bankChartRebalanceTitle: "Redistribución da riqueza entre PUBs", + bankChartRebalanced: "ECO movidos entre PUBs", + bankChartTaxes: "Impostos retidos (ECO)", + bankChartNoDataRange: "Non hai datos neste intervalo.", + bankChartNoDataYet: "Aínda non hai datos", + bankChartPerHour: "por hora", + bankChartPerDay: "por día", + bankChartPerMonth: "por mes", + bankTotalUbiDistributed: "RBU total redistribuída (rede)", + bankTotalTaxesWithheld: "Impostos totais retidos (rede)", + bankInflationIssuance: "Taxa de emisión (anual, por recompensa de bloque)", + bankRulesEligibilityTitle: "Quen pode recibir a RBU", + bankRulesEligibility1: "Un enderezo ECOin publicado no teu perfil (Perfil → Sensores → Carteira ECOin).", + bankRulesEligibility2: "Un feed con máis de 30 días e actividade na rede: karma por riba de cero.", + bankRulesEligibility3: "Unha reclamación por mes (epoch), desde Banca co botón Reclamar RBU. Pódese rexeitar a do mes en curso con Rexeitar RBU.", + bankRulesEligibility4: "O mínimo de 1 ECO págase sempre; os impostos só reducen a parte que o supera e nunca se cobran á parte.", + bankRulesEligibility5: "O PUB paga no seu seguinte ciclo e publica un transfer RBU que podes confirmar.", + bankRulesEligibility6: "Se falta o teu enderezo, o feed é demasiado novo, rexeitaches o mes ou o PUB non ten fondos, a reclamación sáltase e queda rexistrada no PUB.", + copyLinkTitle: "Copiar a ligazón", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Rexeitar RBU", + bankUbiRefusedThisMonth: "Rexeitaches a RBU deste mes.", + bankingBotUbiAvailableTitle: "Podes reclamar a túa RBU.", + bankingBotUbiAvailableText: "A túa RBU deste mes está dispoñible", + bankingBotConfirmTitle: "Tes un contrato pendente de confirmación", + bankingBotConfirmText: "Contratos pendentes de confirmación", + bankChartZoomHint: "Preme para ampliar", + bankChartPoolTitle: "Asignación da RBU (no tempo)", + bankChartNetworkUbiTitle: "RBU total redistribuída (rede)", + bankChartSupplyTitle: "Oferta circulante de ECOin (no tempo)", + bankChartInflationTitle: "Inflación de ECOin (no tempo)", + bankChartInflationSeries: "Taxa anualizada (%)", + bankChartPool: "Asignación total (ECO)", + bankChartKarmaTitle: "Puntuación KARMA (no tempo)", + bankChartFundsTitle: "Os teus fondos ECOin (no tempo)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin conectada", + bankWalletNotConfigured: "ECOin desconectada", + transfersPayWithWallet: "PAGAR!", + walletStatus: "Estado", + walletDisconnected: [ + "Tes a ", + strong("carteira de ECOin desconectada"), + ". Revisa ", + a({ href: '/settings#wallet' }, "a túa configuración"), + " ou o estado da túa conexión.", + ], + walletSentToLine: ({ destination, amount }) => `Enviar ECO ${amount} a ${destination}`, + walletNotConfigured: "A túa carteira de ECOin aínda non está configurada.", + walletSettingsDescription: "Integrar Oasis coa túa carteira de ECOin.", + walletSettingsMobileDescription: "Vincula Oasis coa app ECOin Wallet deste dispositivo.", + walletDownloadEcoin: "Descargar ECOin!", + walletRpcUrl: "URL do RPC", + walletStatusMessages: { + invalid_amount: "Cantidade non válida", + invalid_dest: "Enderezo de destino non válido", + invalid_fee: "Taxa non válida", + validation_errors: "Erros na validación", + send_tx_success: "Transacción realizada", + }, + walletTitle: "Carteira", + walletTotalCostLine: ({ totalCost }) => `Custo total: ECO ${totalCost}`, + walletTransactionId: "ID da transacción", + walletTxId: "ID Tx", + walletType: "Tipo", + walletUser: "Usuario", + walletPass: "Contrasinal", + walletConfiguration: "Configurar a carteira", + walletDisconnectButton: "Desconectar a carteira", + cipher: "Cifrador", + cipherTitle: "Cifrador", + cipherDescription: "Cifra e descifra o teu texto de forma simétrica (cun contrasinal compartido).", + randomPassword: "Contrasinal aleatorio", + cipherTextPlaceholder: "Introduce o texto que queres cifrar...", + cipherPasswordLabel: "Establece un contrasinal (mínimo 32 caracteres) para cifrar o teu texto", + cipherPasswordDecryptLabel: "Establece un contrasinal (mínimo 32 caracteres) para descifrar o teu texto", + cipherPasswordPlaceholder: "Introduce un contrasinal...", + cipherEncryptButton: "Cifrar", + cipherEncryptedMessageLabel: "Texto cifrado", + cipherDecryptedMessageLabel: "Texto descifrado", + cipherEncryptedTextPlaceholder: "Introduce o texto cifrado...", + cipherDecryptButton: "Descifrar", + password: "Contrasinal", + text: "Texto", + encryptedText: "Texto cifrado", + iv: "Vector de inicialización (IV)", + bookmarkTitle: "Marcadores", + bookmarkDescription: "Descobre e xestiona marcadores na túa rede.", + bookmarkAllSectionTitle: "Marcadores", + bookmarkMineSectionTitle: "Os teus marcadores", + bookmarkRecentSectionTitle: "Marcadores recentes", + bookmarkTopSectionTitle: "Marcadores top", + bookmarkFavoritesSectionTitle: "Favoritos", + bookmarkCreateSectionTitle: "Crear un marcador", + bookmarkUpdateSectionTitle: "Actualizar o marcador", + bookmarkFilterAll: "TODOS", + bookmarkFilterMine: "MEUS", + bookmarkFilterTop: "TOP", + bookmarkFilterFavorites: "FAVORITOS", + bookmarkFilterRecent: "RECENTES", + bookmarkCreateButton: "Crear un marcador", + bookmarkUpdateButton: "Actualizar", + bookmarkAddFavoriteButton: "Engadir a favoritos", + bookmarkRemoveFavoriteButton: "Quitar de favoritos", + bookmarkUrlLabel: "Ligazón", + bookmarkUrlPlaceholder: "https://solarnethub.com", + bookmarkDescriptionLabel: "Descrición", + bookmarkDescriptionPlaceholder: "Opcional", + bookmarkTagsLabel: "Etiquetas", + bookmarkTagsPlaceholder: "Introduce etiquetas separadas por comas", + bookmarkLastVisitLabel: "Última visita", + bookmarkSearchPlaceholder: "Buscar en Marcadores...", + bookmarkSortRecent: "Máis recentes", + bookmarkSortOldest: "Máis antigos", + bookmarkSortTop: "Máis votados", + bookmarkSearchButton: "Buscar", + bookmarkUpdatedAt: "Actualizado", + bookmarkNoMatch: "Non hai marcadores que coincidan coa túa busca.", + noBookmarks: "Non hai marcadores dispoñibles.", + noUrl: "Sen ligazón", + noCategory: "Sen categoría", + noLastVisit: "Sen última visita", + videoTitle: "Vídeos", + videoDescription: "Explora e xestiona contido de vídeo na túa rede.", + videoMineSectionTitle: "Os teus vídeos", + videoCreateSectionTitle: "Subir un vídeo", + videoUpdateSectionTitle: "Actualizar o vídeo", + videoAllSectionTitle: "Vídeos", + videoRecentSectionTitle: "Vídeos recentes", + videoTopSectionTitle: "Vídeos máis votados", + videoFavoritesSectionTitle: "Favoritos", + videoFilterAll: "TODOS", + videoFilterMine: "MEUS", + videoFilterRecent: "RECENTES", + videoFilterTop: "TOP", + videoFilterFavorites: "FAVORITOS", + videoCreateButton: "Subir un vídeo", + videoUpdateButton: "Actualizar", + videoDeleteButton: "Eliminar", + videoAddFavoriteButton: "Engadir a favoritos", + videoRemoveFavoriteButton: "Quitar de favoritos", + videoFileLabel: "Selecciona un ficheiro de vídeo (.mp4, .webm, .ogv, .mov)", + videoTagsLabel: "Etiquetas", + videoTagsPlaceholder: "Introduce etiquetas separadas por comas", + videoTitleLabel: "Título", + videoTitlePlaceholder: "Opcional", + videoDescriptionLabel: "Descrición", + videoDescriptionPlaceholder: "Opcional", + videoNoFile: "Non se proporcionou ningún ficheiro de vídeo", + noVideos: "Non hai vídeos dispoñibles.", + videoSearchPlaceholder: "Buscar en Vídeos...", + videoSortRecent: "Máis recentes", + videoSortOldest: "Máis antigos", + videoSortTop: "Máis votados", + videoSearchButton: "Buscar", + videoUpdatedAt: "Actualizado", + videoNoMatch: "Ningún vídeo coincide coa túa busca.", + documentTitle: "Documentos", + documentDescription: "Descobre e xestiona documentos na túa rede.", + documentAllSectionTitle: "Documentos", + documentMineSectionTitle: "Os teus documentos", + documentRecentSectionTitle: "Documentos recentes", + documentTopSectionTitle: "Documentos top", + documentFavoritesSectionTitle: "Favoritos", + documentCreateSectionTitle: "Subir un documento", + documentUpdateSectionTitle: "Editar o documento", + documentFilterAll: "TODO", + documentFilterMine: "MEUS", + documentFilterRecent: "RECENTES", + documentFilterTop: "TOP", + documentFilterFavorites: "FAVORITOS", + documentCreateButton: "Subir un documento", + documentUpdateButton: "Actualizar", + documentAddFavoriteButton: "Engadir a favoritos", + documentRemoveFavoriteButton: "Quitar de favoritos", + documentMessageAuthorButton: "MP", + documentFileLabel: "Subir un documento (.pdf)", + documentTagsLabel: "Etiquetas", + documentTagsPlaceholder: "Introduce etiquetas separadas por comas", + documentTitleLabel: "Título", + documentTitlePlaceholder: "Opcional", + documentDescriptionLabel: "Descrición", + documentDescriptionPlaceholder: "Opcional", + documentNoFile: "Sen ficheiro.", + noDocuments: "Non hai documentos dispoñibles.", + documentSearchPlaceholder: "Buscar en Documentos...", + documentSortRecent: "Máis recentes", + documentSortOldest: "Máis antigos", + documentSortTop: "Máis votados", + documentSearchButton: "Buscar", + documentNoMatch: "Non hai documentos que coincidan coa túa busca.", + documentUpdatedAt: "Actualizado", + audioTitle: "Audios", + audioDescription: "Explora e xestiona contido de audio na túa rede.", + audioMineSectionTitle: "Os teus audios", + audioCreateSectionTitle: "Subir un audio", + audioUpdateSectionTitle: "Actualizar o audio", + audioAllSectionTitle: "Audios", + audioRecentSectionTitle: "Audios recentes", + audioTopSectionTitle: "Audios máis votados", + audioFilterAll: "TODOS", + audioFilterMine: "MEUS", + audioFilterRecent: "RECENTES", + audioFilterTop: "TOP", + audioCreateButton: "Subir un audio", + audioUpdateButton: "Actualizar", + audioAddFavoriteButton: "Engadir a favoritos", + audioRemoveFavoriteButton: "Quitar de favoritos", + audioFileLabel: "Selecciona un ficheiro de audio (.mp3, .wav, .ogg)", + audioTagsLabel: "Etiquetas", + audioTagsPlaceholder: "Introduce etiquetas separadas por comas", + tagsPlaceholder: "Introduce etiquetas separadas por comas", + audioTitleLabel: "Título", + audioTitlePlaceholder: "Opcional", + audioDescriptionLabel: "Descrición", + audioDescriptionPlaceholder: "Opcional", + audioNoFile: "Non se proporcionou ningún ficheiro de audio", + noAudios: "Non hai audios dispoñibles.", + audioSearchPlaceholder: "Buscar en Audios...", + audioSortRecent: "Máis recentes", + audioSortOldest: "Máis antigos", + audioSortTop: "Máis votados", + audioSearchButton: "Buscar", + audioMessageAuthorButton: "MP", + audioUpdatedAt: "Actualizado", + audioNoMatch: "Ningún audio coincide coa túa busca.", + audioFavoritesSectionTitle: "Favoritos", + audioFilterFavorites: "FAVORITOS", + favoritesTitle: "Favoritos", + favoritesDescription: "Todos os teus elementos marcados como favoritos nun só lugar.", + favoritesSearchPlaceholder: "Buscar en Favoritos...", + favoritesFilterAll: "TODOS", + favoritesFilterRecent: "RECENTES", + favoritesFilterAudios: "AUDIOS", + favoritesFilterBookmarks: "MARCADORES", + favoritesFilterDocuments: "DOCUMENTOS", + favoritesFilterImages: "IMAXES", + favoritesFilterMaps: "MAPAS", + favoritesFilterPads: "PADS", + favoritesFilterChats: "CHATS", + favoritesFilterCalendars: "CALENDARIOS", + favoritesFilterVideos: "VÍDEOS", + favoritesNoItems: "Aínda non hai favoritos.", + yourContacts: "Os teus contactos", + allInhabitants: "Habitantes", + allCVs: "CV", + discoverPeople: "Descobre habitantes na túa rede.", + searchInhabitantsPlaceholder: "Buscar en Habitantes...", + filterLocation: "FILTRAR habitantes POR LOCALIZACIÓN …", + filterLanguage: "FILTRAR habitantes POR IDIOMA …", + filterSkills: "FILTRAR habitantes POR HABILIDADES …", + cvSkillsCount: "Habilidades", + applyFilters: "Aplicar os filtros", + locationLabel: "Localización", + languagesLabel: "Idiomas", + skillsLabel: "Habilidades", + commonSkills: "Habilidades comúns", + mutualFollowers: "Seguidores mutuos", + suggestedFollowsYou: "Ségueche", + latestInteractions: "Últimas interaccións", + suggestedSectionTitle: "Suxeridos", + topkarmaSectionTitle: "Mellor karma", + topecoSectionTitle: "Mellor Eco", + topactivitySectionTitle: "Máis actividade", + "TOP INACTIVITYButton": "MÁIS INACTIVOS", + topinactivitySectionTitle: "Habitantes menos activos", + blockedSectionTitle: "Bloqueados", + gallerySectionTitle: "GALERÍA", + topSectionTitle: "TOP", + blockedLabel: "Usuario bloqueado", + inhabitantviewDetails: "Ver os detalles", + keepReading: "Seguir lendo...", + oasisId: "ID", + noInhabitantsFound: "Aínda non se atoparon habitantes.", + inhabitantActivityLevel: "Nivel de actividade", + lifespanLabel: "Vixencia", + deviceLabel: "Dispositivo", + parliamentTitle: "Parlamento", + parliamentDescription: "Explora formas de goberno e políticas de xestión colectiva.", + parliamentFilterGovernment: "GOBERNO", + parliamentFilterCandidatures: "CANDIDATURAS", + parliamentFilterProposals: "PROPOSTAS", + parliamentFilterLaws: "LEIS", + parliamentFilterRevocations: "REVOGACIÓNS", + parliamentFilterHistorical: "HISTÓRICO", + parliamentFilterLeaders: "LÍDERES", + parliamentFilterRules: "REGRAS", + parliamentGovernmentCard: "Goberno actual", + parliamentSeatsGovernment: "Goberno", + parliamentSeatsOpposition: "Oposición", + parliamentSeatsAbstention: "Abstención", + parliamentSeatsQuorum: "Quórum de elección", + parliamentSeatsPass: "Limiar de aprobación", + parliamentSeatsByElection: "Por elección", + parliamentSeatsByHouses: "Por casa do LARP", + parliamentSeatsSeats: "escanos", + parliamentSeatsSeat: "escano", + parliamentSeatsLeader: "Líder", + parliamentSeatsVotesFor: "votos a", + parliamentSeatsNoHouse: "Sen casa", + parliamentActorInPowerInhabitant: "HABITANTE NO PODER", + parliamentActorInPowerTribe: "TRIBO NO PODER", + parliamentHistoricalGovernmentsTitle: "GOBERNOS", + parliamentHistoricalElectionsTitle: "CICLOS ELECTORAIS", + parliamentHistoricalLawsTitle: "HISTÓRICO", + parliamentHistoricalLeadersTitle: "LÍDERES", + parliamentThCycles: "CICLOS", + parliamentThTimesInPower: "MANDATOS", + parliamentThTotalCandidatures: "CANDIDATURAS", + parliamentThProposed: "LEIS PROPOSTAS", + parliamentThApproved: "LEIS APROBADAS", + parliamentThDeclined: "LEIS REXEITADAS", + parliamentThDiscarded: "LEIS DESCARTADAS", + parliamentMembers: "MEMBROS", + parliamentLegSince: "INICIO DO CICLO", + parliamentLegEnd: "FIN DO CICLO", + parliamentPoliciesProposal: "PROPOSTAS DE LEI", + parliamentPoliciesApproved: "LEIS APROBADAS", + parliamentPoliciesDeclined: "LEIS REXEITADAS", + parliamentPoliciesDiscarded: "LEIS DESCARTADAS", + parliamentEfficiency: "% EFICIENCIA", + parliamentRevocationFormTitle: "Revogar unha lei", + parliamentRevocationLaw: "Lei", + parliamentRevocationReasons: "Motivos", + parliamentRevocationPublish: "Publicar a revogación", + parliamentCurrentRevocationsTitle: "Revogacións actuais", + parliamentFutureRevocationsTitle: "Revogacións futuras", + parliamentPoliciesRevocated: "LEIS REVOGADAS", + parliamentPoliciesTitle: "HISTÓRICO", + parliamentLawsTitle: "LEIS APROBADAS", + parliamentRulesRevocations: "Calquera lei aprobada pode ser revogada co método de goberno vixente.", + parliamentNoStableGov: "Aínda non hai un goberno elixido.", + parliamentNoGovernments: "Aínda non hai gobernos no histórico.", + parliamentCandidatureFormTitle: "Propoñer unha candidatura", + parliamentCandidatureIdPh: "Oasis ID (@...) ou nome da tribo", + parliamentCandidatureMethod: "Método", + parliamentCandidatureProposeBtn: "Publicar a candidatura", + parliamentVoteBtn: "Votar", + parliamentProposalFormTitle: "Propoñer unha lei", + parliamentProposalDescription: "Descrición (≤1000)", + parliamentProposalPublish: "Publicar a proposta", + parliamentNoProposals: "Aínda non hai propostas de lei.", + parliamentNoLaws: "Aínda non hai leis aprobadas.", + parliamentMethodDEMOCRACY: "Democracia", + parliamentMethodMAJORITY: "Maioría (80%)", + parliamentMethodMINORITY: "Minoría (20%)", + parliamentMethodDICTATORSHIP: "Ditadura", + parliamentMethodKARMATOCRACY: "Karmatocracia", + parliamentThId: "ID", + parliamentThProposalDate: "Data da proposta", + parliamentThMethod: "Método", + parliamentThKarma: "Karma", + parliamentThSupports: "Apoios", + parliamentThVote: "Votar", + parliamentCurrentProposalsTitle: "Propostas actuais", + parliamentVotesSlashTotal: "Votos/Total", + parliamentVotesNeeded: "Votos necesarios", + parliamentFutureLawsTitle: "Futuras leis", + parliamentVoteAction: "Votar", + parliamentThLeader: "AVATAR", + parliamentPopulation: "Poboación", + parliamentThPresented: "CANDIDATURAS", + parliamentNoLeaders: "Aínda non hai líderes.", + parliamentCandidaturesListTitle: "Lista de candidaturas", + parliamentTimeRemaining: "Tempo restante", + parliamentElectionsStatusTitle: "Próximo goberno", + parliamentProposalDeadlineLabel: "Data límite", + parliamentViewReport: "Ver o informe", + parliamentProposalTimeLeft: "Tempo restante", + parliamentRulesTitle: "Como funciona o Parlamento", + parliamentRulesIntro: "As eleccións resólvense cada 2 meses; as candidaturas son continuas e reinícianse cando se elixe un goberno.", + parliamentRulesCandidates: "Calquera habitante pode propoñerse a si mesmo, a outro habitante ou a calquera tribo. Cada habitante pode propoñer ata 3 candidaturas por ciclo; rexéitanse os duplicados no mesmo ciclo.", + parliamentRulesElection: "Gaña a candidatura con máis votos no momento da resolución.", + parliamentRulesTies: "Desempate: maior karma do habitante; se persiste, perfil máis antigo; se persiste, proposta máis temperá; despois, orde lexicográfica por ID.", + parliamentRulesTerm: "A lapela Goberno amosa o goberno actual e as súas estatísticas.", + parliamentRulesMethods: "Formas: Anarquía (maioría simple), Democracia (50%+1), Maioría (80%), Minoría (20%), Karmatocracia (propostas con maior karma), Ditadura (aprobación instantánea).", + parliamentRulesAnarchy: "A Anarquía é o modo por defecto: se ao resolver non se elixe ningunha candidatura, proclámase a Anarquía. En Anarquía, calquera habitante pode propoñer leis.", + parliamentRulesProposals: "Se es o habitante gobernante ou membro da tribo gobernante, podes publicar propostas de lei. Os métodos non ditatoriais crean unha votación pública.", + parliamentRulesLimit: "Cada habitante pode publicar como máximo 3 propostas de lei por ciclo.", + parliamentRulesLaws: "Cando unha proposta alcanza o seu limiar, convértese en Lei e aparece na lapela Leis coa súa data de entrada en vigor.", + parliamentRulesHistorical: "En Histórico pódese ver cada ciclo de goberno que houbo e datos sobre a súa xestión.", + parliamentRulesLeaders: "En Líderes pódese ver unha clasificación de habitantes/tribos que gobernaron (ou se presentaron), ordenados por eficacia.", + parliamentRulesHemicycle: "O hemiciclo dá un escano por habitante: o bloque de goberno (votantes da candidatura gañadora e o líder), un bloque de oposición por cada outra candidatura do ciclo, e a abstención. As marcas descontinuas sinalan o quórum de elección (25 % do censo) e o limiar de aprobación do método vixente. \"Por casa do LARP\" colorea os mesmos escanos por casa.", + parliamentProposalVoteStatusLabel: "Estado da votación", + parliamentProposalOnTrackYes: "Limiar alcanzado", + parliamentProposalOnTrackNo: "Por debaixo do limiar", + courtsTitle: "Tribunais", + courtsDescription: "Explora formas de resolución de conflitos e de xestión colectiva da xustiza.", + courtsFilterCases: "CASOS", + courtsFilterMyCases: "MEUS", + courtsFilterJudges: "XUÍCES", + courtsFilterHistory: "HISTORIAL", + courtsFilterRules: "REGRAS", + courtsCaseFormTitle: "Abrir un caso", + courtsCaseRespondent: "Acusado / Parte demandada", + courtsCaseRespondentPh: "ID de Oasis (@...) ou nome da tribo", + courtsCaseSubjectPlaceholder: "Asunto ou descrición breve", + courtsCaseMethod: "Método de resolución", + courtsCaseSubmit: "Presentar o caso", + courtsNominateJudge: "Nomear un xuíz", + courtsJudgeId: "Xuíz", + courtsJudgeIdPh: "ID de Oasis (@...) ou nome do habitante", + courtsNominateBtn: "Nomear", + courtsAddEvidence: "Engadir probas", + courtsEvidenceText: "Texto", + courtsEvidenceLink: "Ligazón", + courtsEvidenceLinkPh: "https://…", + courtsEvidenceSubmit: "Anexar", + courtsAnswerText: "Resumo da resposta", + courtsAnswerSubmit: "Enviar a resposta", + courtsVerdictTitle: "Emitir veredicto", + courtsVerdictOrdersPh: "Accións, prazos, pasos restaurativos...", + courtsIssueVerdict: "Emitir veredicto", + courtsMediationPropose: "Propoñer un acordo", + courtsSettlementProposeBtn: "Propoñer", + courtsNominationsTitle: "Nomeamentos á xudicatura", + courtsThJudge: "Xuíz", + courtsThDate: "Data", + courtsThVote: "Votar", + courtsNoNominations: "Aínda non hai nomeamentos.", + courtsRespondentInvalid: "Debe ser un ID SSB válido (@...ed25519)", + courtsCaseMediatorsRespondentTitle: "Engadir mediadores da defensa", + courtsCaseMediatorsRespondent: "Mediadores (defensa)", + courtsMediatorsAccuserLabel: "Mediadores (acusación)", + courtsMediatorsRespondentLabel: "Mediadores (defensa)", + courtsMediatorsSubmit: "Gardar os mediadores", + courtsNoMyCases: "Aínda non tes conflitos.", + courtsNoHistory: "Aínda non hai xuízos rexistrados.", + courtsMethodDICTATOR: "Ditador", + courtsMethodPOPULAR: "Popular", + courtsMethodKARMATOCRACY: "Karmatocracia", + courtsCaseType: "Tipo de caso", + courtsPresetMinorConflict: "Conflito leve", + courtsPresetModerateConflict: "Conflito moderado", + courtsPresetSevereConflict: "Conflito grave", + courtsPresetHarassment: "Acoso ou abuso", + courtsPresetContentModeration: "Moderación de contido", + courtsPresetBanRestriction: "Expulsión ou restrición", + courtsPresetGovernanceDispute: "Disputa de goberno", + courtsStatusOPEN: "Aberto", + courtsStatusIN_PROGRESS: "En curso", + courtsStatusDECIDED: "Decidido", + courtsStatusCLOSED: "Pechado", + courtsStatusSOLVED: "Resolto", + courtsStatusUNSOLVED: "Sen resolver", + courtsStatusDISCARDED: "Descartado", + courtsRulesTitle: "Como funcionan os Tribunais", + courtsRulesIntro: "Os Tribunais son un proceso xestionado pola comunidade para resolver conflitos e promover a xustiza restaurativa. Priorízanse o diálogo, as probas claras e os remedios proporcionais.", + courtsRulesLifecycle: "Proceso: 1) Abrir o caso 2) Seleccionar o método 3) Presentar probas 4) Audiencia e deliberación 5) Veredicto e remedio 6) Cumprimento e peche 7) Apelación (se procede).", + courtsRulesRoles: "Acusación: abre o caso. Defensa: habitante ou tribo acusada. Método: mecanismo elixido pola comunidade para facilitar, avaliar as probas e emitir veredicto. Testemuña: achega testemuño ou probas. Mediadores: habitantes neutrais convidados pola acusación e/ou a defensa, con acceso a todos os detalles, que axudan a desescalar o conflito e a cocrear acordos.", + courtsRulesEvidence: "Descrición de ata 1000 caracteres. Anexa imaxes, audio, vídeo e documentos PDF relevantes e legais. Non compartas datos privados sensibles sen consentimento.", + courtsRulesDeliberation: "As audiencias poden ser públicas ou privadas. Os xuíces garanten o respecto, piden aclaracións e poden descartar material irrelevante ou ilegal.", + courtsRulesVerdict: "Priorízase a restauración: desculpas, acordos de mediación, moderación de contido, restricións temporais ou outras medidas proporcionais. O razoamento debe quedar rexistrado.", + courtsRulesAppeals: "Apelación: permitida cando hai probas novas ou un erro procesual claro. Debe presentarse nun prazo de 7 días salvo que se indique o contrario.", + courtsRulesPrivacy: "Respecta a privacidade e a seguridade. O doxing, o odio ou as ameazas elimínanse. Os xuíces poden editar ou selar partes do expediente para protexer habitantes en risco.", + courtsRulesMisconduct: "O acoso, a manipulación ou as probas fabricadas poden levar a unha resolución negativa inmediata.", + courtsRulesGlossary: "Caso: rexistro dun conflito. Probas: materiais que respaldan as afirmacións. Veredicto: decisión con remedio. Apelación: solicitude para revisar o veredicto.", + courtsFilterActions: "ACCIÓNS", + courtsThRole: "Rol", + courtsRoleAccuser: "Acusación", + courtsRoleDefence: "Defensa", + courtsRoleMediator: "Mediador", + courtsRoleJudge: "Xuíz", + courtsRoleDictator: "Ditador", + courtsAssignJudgeTitle: "Elixir un xuíz", + courtsAssignJudgeBtn: "Elixir un xuíz", + trendingTitle: "Tendencias", + exploreTrending: "Explora o contido máis popular da túa rede.", + votesButton: "GOBERNO", + shopProductButton: "TENDA", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISODIO", + campaignButton: "CAMPAÑA", + logisticsRouteButton: "RUTA", + trendingTotalOpinions: "Total de opinións", + trendingNoContentMessage: "Aínda non hai tendencias.", + trendingTotalCount: "Total de contadores", + tasksTitle: "Tarefas", + tasksDescription: "Descobre e xestiona tarefas na túa rede.", + taskSearchPlaceholder: "Buscar en Tarefas...", + taskTitleLabel: "Título", + taskDescriptionLabel: "Descrición", + taskStartTimeLabel: "Data de inicio", + taskEndTimeLabel: "Data de fin", + taskPriorityLabel: "Prioridade", + taskPriorityUrgent: "Urxente", + taskPriorityHigh: "Alta", + taskPriorityMedium: "Media", + taskPriorityLow: "Baixa", + taskLocationLabel: "Localización", + taskTagsLabel: "Etiquetas", + taskVisibilityLabel: "Visibilidade", + taskPublic: "público", + taskPrivate: "privado", + taskStatus: "Estado", + taskStatusOpen: "Aberta", + taskStatusInProgress: "En curso", + taskStatusClosed: "Pechada", + taskAssignedTo: "Asignada", + taskAssignedChip: "Asignada", + taskUnassignedChip: "Sen asignar", + taskAssignees: "Asignados", + taskAssignButton: "Asignarma", + taskUnassignButton: "Desasignar", + taskCreateButton: "Crear unha tarefa", + taskUpdateButton: "Actualizar", + taskDeleteButton: "Eliminar", + taskFilterAll: "TODAS", + taskFilterRecent: "RECENTES", + taskFilterMine: "MIÑAS", + taskFilterOpen: "ABERTAS", + taskFilterInProgress: "EN CURSO", + taskFilterClosed: "PECHADAS", + taskFilterAssigned: "ASIGNADAS", + taskFilterUrgent: "URXENTES", + taskFilterHigh: "ALTAS", + taskFilterMedium: "MEDIAS", + taskFilterLow: "BAIXAS", + taskAllSectionTitle: "Tarefas", + taskMineSectionTitle: "As túas tarefas", + taskCreateSectionTitle: "Crear unha tarefa", + taskUpdateSectionTitle: "Actualizar", + taskOpenTitle: "Tarefas abertas", + taskInProgressTitle: "Tarefas en curso", + taskClosedTitle: "Tarefas pechadas", + taskAssignedTitle: "Tarefas asignadas", + notasks: "Non hai tarefas dispoñibles.", + noLocation: "Non se especificou ningunha localización", + taskSetStatus: "Establecer o estado", + eventDateLabel: "Data", + eventRecurrenceLabel: "Recorrencia", + eventRecurrenceUntil: "Repetir ata", + eventRecurrenceHint: "Deixa baleira a data de repetición para un evento único.", + eventUpcomingDates: "Próximas datas", + eventsTitle: "Eventos", + eventsDescription: "Descobre e xestiona eventos na túa rede.", + eventSearchPlaceholder: "Buscar en Eventos...", + eventPrice: "Prezo", + eventStatus: "Estado", + eventAllSectionTitle: "Eventos", + eventMineSectionTitle: "Os teus eventos", + eventCreateSectionTitle: "Crear un evento", + eventUpdateSectionTitle: "Actualizar o evento", + eventCreateButton: "Crear un evento", + eventAddToCalendar: "Engadir ao calendario", + eventVisitCalendar: "Visitar o calendario", + viewTask: "Ver a tarefa", + viewEvent: "Ver o evento", + viewPad: "Ver o pad", + viewMap: "Ver o mapa", + viewCalendar: "Ver o calendario", + viewReport: "Ver o informe", + viewTransfer: "Ver a transferencia", + viewItem: "Ver o artigo", + viewShop: "Ver a tenda", + viewProject: "Ver o proxecto", + viewJob: "Ver o emprego", + viewPlace: "Ver o lugar", + generatePdf: "Xerar PDF", + sharePm: "Compartir por PM", + galleryImages: "Imaxes (tamaño máx.: 75MB cada unha)", + galleryPhotos: "Imaxes", + galleryAddPhoto: "Engadir unha imaxe", + galleryRemovePhoto: "Quitar", + galleryVideo: "Vídeo (tamaño máx.: 75MB)", + galleryAddVideo: "Engadir un vídeo", + galleryRemoveVideo: "Quitar o vídeo", + viewVotation: "Ver a votación", + voteCastTitle: "Emitir o voto", + voteResults: "Resultados", + eventTitleLabel: "Título", + eventDescriptionLabel: "Descrición", + eventDescriptionPlaceholder: "Introduce a descrición do evento...", + eventUpdateButton: "Actualizar", + eventTagsLabel: "Etiquetas", + eventPriceLabel: "Prezo", + eventUrlLabel: "URL", + eventAttendees: "Asistentes", + eventLocation: "Localización", + eventLocationLabel: "Localización", + noevents: "Non hai eventos dispoñibles.", + eventDate: "Data", + eventAttendButton: "Asistir ao evento", + eventFilterAll: "TODOS", + eventFilterRecent: "RECENTES", + eventFilterMine: "MEUS", + eventFilterToday: "HOXE", + eventFilterWeek: "ESTA SEMANA", + eventFilterMonth: "ESTE MES", + eventFilterYear: "ESTE ANO", + eventFilterArchived: "ARQUIVADOS", + eventPrivacyLabel: "Visibilidade", + eventPublic: "Público", + eventPrivate: "Privado", + eventAttended: "Asisto", + eventUnattended: "Non asisto", + eventStatusOpen: "Aberto", + eventStatusClosed: "Pechado", + tagsTitle: "Etiquetas", + tagsDescription: "Descobre e explora patróns de taxonomía na túa rede.", + tagsAllSectionTitle: "Etiquetas", + tagsTopSectionTitle: "Etiquetas principais", + tagsCloudSectionTitle: "Nube de etiquetas", + tagsFilterAll: "TODAS", + tagsFilterMine: "MIÑAS", + tagsFilterRecent: "RECENTES", + tagsMineSectionTitle: "As miñas etiquetas", + tagsRecentSectionTitle: "Etiquetas recentes", + tagsFilterTop: "TOP", + tagsFilterCloud: "NUBE", + tagsNoItems: "Non hai etiquetas dispoñibles.", + tagsSearchPlaceholder: "Buscar en Etiquetas...", + tagsTableHeaderTag: "ETIQUETA/LIGAZÓN:", + tagsTableHeaderCount: "CONTADOR:", + transfersTitle: "Transferencias", + transfersDescription: "Descobre e xestiona transferencias na túa rede.", + transfersFrom: "De", + transfersTo: "A", + transfersFilterAll: "TODAS", + transfersFilterRecent: "RECENTES", + transfersFilterMine: "MIÑAS", + transfersFilterMinePending: "PENDENTES", + transfersFilterMineConfirmed: "ASINADAS", + transfersFilterUBI: "RBU", + transfersFilterMarket: "MERCADO", + transfersFilterTop: "TOP", + transfersFilterPending: "PENDENTES", + transfersFilterUnconfirmed: "NON CONFIRMADAS", + transfersFilterClosed: "PECHADAS", + transfersFilterDiscarded: "DESCARTADAS", + transfersFilterEconomic: "ECONÓMICA", + transfersFilterTime: "TEMPO", + transfersFilterTrust: "CONFIANZA", + transfersCategory: "Categoría", + transfersCategoryEconomic: "Económica", + transfersCategoryTime: "Tempo", + transfersCategoryTrust: "Confianza", + transfersUnitEco: "ECO", + transfersUnitHours: "h", + transfersUnitTrust: "confianza", + transfersEconomicSectionTitle: "Transferencias económicas", + transfersTimeSectionTitle: "Transferencias de tempo", + transfersTrustSectionTitle: "Transferencias de confianza", + transfersCreateButton: "Crear unha transferencia", + transfersUpdateButton: "Actualizar", + transfersDeleteButton: "Eliminar", + transfersToUser: "ID de Oasis", + transfersToUserValidation: "ID de Oasis válido, p. ex. @…=.ed25519", + transfersConcept: "Concepto", + transfersAmount: "Cantidade", + transfersDeadline: "Prazo", + transfersTags: "Etiquetas", + transfersStatus: "Estado", + transfersConfirmations: "CONFIRMACIÓNS", + transfersExportContract: "Xerar a factura", + transfersConfirmButton: "Confirmar a transferencia", + transfersNoItems: "Non se atoparon transferencias.", + transfersMineSectionTitle: "As túas transferencias", + transfersUBISectionTitle: "Transferencias RBU", + transfersTopSectionTitle: "Transferencias principais", + transfersPendingSectionTitle: "Transferencias pendentes", + transfersUnconfirmedSectionTitle: "Transferencias non confirmadas", + transfersClosedSectionTitle: "Transferencias pechadas", + transfersDiscardedSectionTitle: "Transferencias descartadas", + transfersCreateSectionTitle: "Crear unha transferencia", + transfersAllSectionTitle: "Transferencias", + transfersSearchPlaceholder: "Buscar en Transferencias...", + transfersMinAmountLabel: "Importe mín.", + transfersMaxAmountLabel: "Importe máx.", + transfersSortRecent: "Máis recentes", + transfersSortAmount: "Maior importe", + transfersSortDeadline: "Prazo máis próximo", + transfersSearchButton: "Buscar", + transfersExpiredBadge: "CADUCADA", + transfersUpdatedAt: "Actualizado", + transfersNoMatch: "Non hai transferencias que coincidan coa túa busca.", + votationsTitle: "Votacións", + votationsDescription: "Descobre e xestiona votacións na túa rede.", + voteMineSectionTitle: "As túas votacións", + voteCreateSectionTitle: "Crear unha votación", + voteUpdateSectionTitle: "Actualizar", + voteOpenTitle: "Votacións abertas", + voteClosedTitle: "Votacións pechadas", + voteAllSectionTitle: "Goberno", + voteCreateButton: "Crear unha votación", + voteErrorTitle: "Revisa o formulario", + voteErrorDeadlineMin: "A data límite debe ser de polo menos {days} días desde agora.", + voteErrorGeneric: "Non se puido gardar a votación.", + voteLocked: "Esta votación xa recibiu votos ou opinións, ou pertence ao Parlamento ou aos Tribunais: xa non se pode editar nin borrar.", + voteUpdateButton: "Actualizar", + voteDeleteButton: "Eliminar", + voteOptionYes: "SI", + voteOptionNo: "NON", + voteOptionAbstention: "ABSTENCIÓN", + voteConfused: "CONFUNDIDO", + voteFollowMajority: "SEGUIR A MAIORÍA", + voteNotInterested: "SEN INTERESE", + voteFilterAll: "TODAS", + voteFilterRecent: "RECENTES", + voteFilterMine: "MIÑAS", + voteFilterOpen: "ABERTAS", + voteFilterClosed: "PECHADAS", + voteQuestionLabel: "Pregunta", + voteDeadlineLabel: "Prazo", + voteOptionsLabel: "O teu voto", + voteBreakdown: "Desagregación", + voteFinalResult: "RESULTADO", + voteNoQuorum: "SEN QUÓRUM", + voteTagsLabel: "Etiquetas", + voteDeadline: "Prazo", + voteStatus: "Estado", + voteTags: "Etiquetas", + voteOpinions: "Opinións", + novotes: "Non hai propostas de votación dispoñibles.", + voteCreatedAt: "Creado o", + voteNoQuestion: "Non se proporcionou ningunha pregunta", + voteUnknownCreator: "Creador descoñecido", + voteUnknownDate: "Data descoñecida", + voteDetailSectionTitle: "Detalles da votación", + voteCommentsLabel: "Comentarios", + voteCommentsForumButton: "Abrir a conversa", + voteCommentsSectionTitle: "Conversa aberta", + voteNoCommentsYet: "Aínda non hai comentarios. Sé a primeira persoa en opinar.", + commentsGoToLast: "Ir ao último comentario", + voteNewCommentPlaceholder: "Escribe aquí o teu comentario…", + voteNewCommentButton: "Publicar o comentario", + voteNewCommentLabel: "Engade un comentario", + cvTitle: "CV", + cvEditSectionTitle: "Editar o CV", + cvCreateSectionTitle: "Crear un CV", + cvDescription: "Xestiona e comparte as túas habilidades profesionais e a túa información.", + cvNameLabel: "Nome completo", + cvDescriptionLabel: "Resumo", + cvPhotoLabel: "Foto", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Visitar o CV", + cvPersonalExperiencesLabel: "Experiencias persoais", + cvPersonalSkillsLabel: "Habilidades persoais (separadas por comas)", + cvOasisExperiencesLabel: "Experiencias de contribución en Oasis", + cvOasisSkillsLabel: "Habilidades de contribución en Oasis (separadas por comas)", + cvEducationExperiencesLabel: "Experiencias educativas", + cvEducationalSkillsLabel: "Habilidades educativas (separadas por comas)", + cvProfessionalExperiencesLabel: "Experiencias profesionais", + cvProfessionalSkillsLabel: "Habilidades profesionais (separadas por comas)", + cvLanguagesLabel: "Idiomas", + cvLocationLabel: "Localización", + cvStatusLabel: "Estado", + cvPreferencesLabel: "Preferencias", + cvPersonal: "Persoal", + cvOasis: "Contribuínte de Oasis (opcional)", + cvOasisContributorView: "Contribución", + cvEducational: "Educativo (opcional)", + cvEducationalView: "Educativo", + cvProfessional: "Profesional (opcional)", + cvProfessionalView: "Profesional", + cvAvailability: "Dispoñibilidade (opcional)", + cvUpdateButton: "Actualizar", + cvCreateButton: "Crear un CV", + cvCreatedAt: "Creado o", + cvUpdatedAt: "Actualizado o", + cvEditButton: "Actualizar", + cvDeleteButton: "Eliminar", + cvNoCV: "Non se atopou ningún CV.", + blogSubject: "Asunto", + blogMessage: "Mensaxe", + blogImage: "Subir contido multimedia (máx.: 75MB)", + blogMedia: "Anexos (ata 8 imaxes e un vídeo)", + blogPublish: "Publicar", + pollsTitle: "Enquisas", + dataTitle: "Coincidencias", + dataDescription: "Explora e visualiza as coincidencias da túa rede.", + dataFilterAll: "TODO", + dataFilterMine: "MEUS", + dataFilterRecent: "RECENTES", + dataFilterTop: "TOP", + dataKindInhabitants: "HABITANTES", + dataKindJobs: "EMPREGOS", + dataKindProjects: "PROXECTOS", + dataKindEvents: "EVENTOS", + dataKindTribes: "TRIBOS", + dataKindMarket: "MERCADO", + dataKindHousing: "VIVENDA", + dataKindIndustry: "INDUSTRIA", + dataKindTasks: "TAREFAS", + dataKindReports: "INFORMES", + dataKindVotes: "VOTACIÓNS", + dataSearchPlaceholder: "Buscar en Coincidencias...", + dataCohesionTitle: "Coeficiente de cohesión (CC)", + dataCcShort: "CC", + dataCohesionHint: "Afinidade media entre todo o que publicou a rede.", + dataAffinity: "Afinidade", + dataCommonTerms: "Palabra clave", + dataReasonFollowing: "Apoias a este habitante", + dataReasonTribe: "Comparte tribo contigo", + dataReasonPinned: "Coincide con algo que fixaches", + dataReasonCv: "Coincide coas habilidades do teu CV", + dataReasonContent: "Relacionado co que publicas", + dataReasonRelated: "Relacionado cos teus intereses", + dataReasonAlike: "Publicado por un habitante afín a ti", + dataReasonSupportsYou: "Apóiate", + dataReasonMutual: "Apoio mutuo", + dataReasonRated: "Ben valorado pola rede", + dataReasonNear: "Preto de ti", + dataStatPeople: "CV", + dataStatConnected: "Conectados", + dataStatSkills: "Habilidades", + dataBestMatch: "Mellor coincidencia", + dataStatIsolated: "Illados", + dataStatEntities: "Entidades comparadas", + dataStatTerms: "Termos distintos", + dataStatPairs: "Pares conectados", + dataMatchesTitle: "Coincidencias", + dataMatchesHint: "Suxestións personalizadas do algoritmo.", + dataTermsTitle: "Palabras clave", + dataTermsHint: "Os termos que aparecen en máis contidos.", + dataConnections: "Coincidencias ligadas", + dataTopicTitle: "Todo o relacionado con", + dataTopicHint: "O que o algoritmo relaciona con este tema", + dataNoMatches: "Non hai datos cruzados que amosar.", + dataNoProfile: "Publica contido ou escribe o teu CV para que o algoritmo aprenda que che interesa.", + pollsDescription: "Módulo para preguntarlle á rede e contar as respostas.", + pollFilterAll: "TODAS", + pollFilterMine: "MIÑAS", + pollFilterRecent: "RECENTES", + pollFilterTop: "TOP", + pollFilterVoted: "VOTADAS", + pollFilterOpen: "ABERTAS", + pollFilterClosed: "PECHADAS", + pollCreateButton: "Crear unha enquisa", + pollCreateTitle: "Crear unha enquisa", + pollEditTitle: "Editar a enquisa", + pollQuestion: "Pregunta", + pollQuestionPlaceholder: "Que queres preguntar?", + pollOptions: "Opcións (unha por liña)", + pollOutcome: "Resultado", + pollNoVotesYet: "Aínda sen votos", + pollTie: "Empate", + pollOptionsPlaceholder: "Praza\nParque\nBar", + pollAnonymous: "Anónima", + pollAnonymousLabel: "Enquisa anónima", + pollMultiple: "Resposta múltiple", + pollMultipleLabel: "Permitir varias respostas", + pollDeadline: "Data límite", + pollTags: "Etiquetas", + pollPublishButton: "Publicar a enquisa", + pollUpdateButton: "Actualizar", + pollCloseButton: "Pechar", + pollDeleteButton: "Eliminar", + pollVoteButton: "Votar", + pollChangeVote: "Cambiar o voto", + pollVoters: "Votantes", + pollComments: "Comentarios", + pollStatusLabel: "Estado", + pollStatusOpen: "ABERTA", + pollStatusClosed: "PECHADA", + pollSearchPlaceholder: "Buscar en Enquisas...", + pollsNoItems: "Non se atoparon enquisas.", + viewPoll: "Ver a enquisa", + pollChatCreate: "Crear unha enquisa", + pollInChat: "Enquisa", + blogTitle: "Blogs", + blogDescription: "Módulo para descubrir e xestionar blogs.", + blogFilterAll: "TODOS", + blogFilterMine: "MEUS", + blogFilterRecent: "RECENTES", + blogFilterFavorites: "FAVORITOS", + blogFilterTop: "TOP", + blogCreateButton: "Crear un blog", + blogSearchPlaceholder: "Buscar en Blogs...", + blogComments: "Comentarios", + blogAllowComments: "Permitir comentarios", + blogCommentsClosed: "O autor pechou os comentarios deste blog.", + blogNoItems: "Non se atoparon blogs.", + viewBlog: "Ver o blog", + forumTitle: "Foros", + forumCategoryLabel: "Categoría", + forumTitleLabel: "Título", + forumTitlePlaceholder: "Título do foro...", + forumCreateButton: "Crear un foro", + forumCreateSectionTitle: "Crear un foro", + forumDescription: "Fala abertamente con habitantes da túa rede.", + forumSearchPlaceholder: "Buscar en Foros...", + forumFilterAll: "TODOS", + forumFilterMine: "MEUS", + forumFilterRecent: "RECENTES", + forumFilterTop: "TOP", + forumParticipants: "participantes", + forumMessages: "mensaxes", + forumMessageLabel: "Mensaxe", + forumMessagePlaceholder: "Escribe a túa mensaxe...", + forumSendButton: "Enviar", + noForums: "Aínda non hai foros dispoñibles.", + forumVisitButton: "Visitar o foro", + forumTopicLabel: "Tema", + imageTitle: "Imaxes", + imageDescription: "Explora e xestiona contido de imaxes na túa rede.", + imageMineSectionTitle: "As túas imaxes", + imageCreateSectionTitle: "Subir unha imaxe", + imageUpdateSectionTitle: "Actualizar a imaxe", + imageAllSectionTitle: "Imaxes", + imageRecentSectionTitle: "Imaxes recentes", + imageTopSectionTitle: "Imaxes top", + imageFavoritesSectionTitle: "Favoritos", + imageGallerySectionTitle: "Galería", + imageFilterAll: "TODAS", + imageFilterMine: "MIÑAS", + imageFilterRecent: "RECENTES", + imageFilterTop: "TOP", + imageFilterFavorites: "FAVORITOS", + imageFilterGallery: "GALERÍA", + imageCreateButton: "Subir unha imaxe", + imageUpdateButton: "Actualizar", + imageAddFavoriteButton: "Engadir a favoritos", + imageRemoveFavoriteButton: "Quitar de favoritos", + imageFileLabel: "Selecciona un ficheiro de imaxe (.jpeg, .jpg, .png, .gif)", + imageTagsLabel: "Etiquetas", + imageTagsPlaceholder: "Introduce etiquetas separadas por comas", + imageTitleLabel: "Título", + imageTitlePlaceholder: "Opcional", + imageDescriptionLabel: "Descrición", + imageDescriptionPlaceholder: "Opcional", + imageNoFile: "Non se proporcionou ningún ficheiro de imaxe", + noImages: "Non hai imaxes dispoñibles.", + imageSearchPlaceholder: "Buscar en Imaxes...", + imageSortRecent: "Máis recentes", + imageSortOldest: "Máis antigas", + imageSortTop: "Máis votadas", + imageSearchButton: "Buscar", + imageUpdatedAt: "Actualizado", + imageNoMatch: "Ningunha imaxe coincide coa túa busca.", + feedSearchPlaceholder: "Buscar en Feed...", + feedTitle: "Feed", + feedDetailTitle: "Feed", + feedOpenDiscussion: "Abrir o debate", + feedPostComment: "Publicar o comentario", + noComments: "Aínda non hai comentarios", + createFeedTitle: "Crear un feed", + createFeedButton: "Enviar o feed!", + feedPlaceholder: "Que está a pasar? (máximo 280 caracteres)", + TODAYButton: "HOXE", + totalOpinions: "Total de opinións", + moreVoted: "Máis votado", + noFeedsFound: "Non se atoparon feeds.", + createdAtLabel: "Creado o", + FeedshareYourOpinions: "Descobre e comparte textos breves na túa rede.", + refeedButton: "Reenviar", + alreadyRefeeded: "Xa realimentaches isto.", + activityTitle: "Actividade", + yourActivity: "A túa actividade", + globalActivity: "Actividade", + activityList: "Actividade", + activityDesc: "Consulta a actividade máis recente da túa rede.", + allButton: "TODO", + mineButton: "MIÑAS", + noActions: "Non hai actividade dispoñible.", + from: "De", + to: "A", + amount: "Cantidade", + concept: "Concepto", + description: "Descrición", + meme: "Meme", + typeRecent: "RECENTES", + typeTop: "TOP", + typePost: "BLOG", + activityUpdateLabel: "ACTUALIZACIÓN", + typeTribe: "TRIBOS", + typeLarp: "L.A.R.P.", + typeInhabitants: "HABITANTES", + typeAbout: "HABITANTES", + typeCurriculum: "CV", + typeImage: "IMAXES", + typeBookmark: "MARCADORES", + typeDocument: "DOCUMENTOS", + typeVotes: "VOTACIÓNS", + typeAudio: "AUDIOS", + typeMarket: "MERCADO", + typeJob: "EMPREGOS", + typeProject: "PROXECTOS", + typeVideo: "VÍDEOS", + typeVote: "DIFUSIÓN", + typeEvent: "EVENTOS", + typeTransfer: "TRANSFERENCIAS", + typeTask: "TAREFAS", + typePixelia: "PIXELIA", + typeForum: "FOROS", + typeReport: "INFORMES", + typeFeed: "FEEDS", + typeContact: "CONTACTO", + typePub: "PUB", + typeTombstone: "ELIMINADO", + typeBanking: "BANCA", + typeBankWallet: "BANCA/CARTEIRA", + typeBankClaim: "BANCA/RBU", + typeKarmaScore: "KARMA", + typeParliament: "PARLAMENTO", + typeSpread: "RÉPLICAS", + typeParliamentCandidature: "Parlamento · Candidatura", + typeParliamentTerm: "Parlamento · Mandato", + typeParliamentProposal:"Parlamento · Proposta", + typeParliamentRevocation:"Parlamento · Revogación", + typeParliamentLaw: "Parlamento · Nova lei", + typeCourts: "TRIBUNAIS", + typeCourtsCase: "Tribunais · Caso", + typeCourtsEvidence: "Tribunais · Proba", + typeCourtsAnswer: "Tribunais · Resposta", + typeCourtsVerdict: "Tribunais · Veredicto", + typeCourtsSettlement: "Tribunais · Acordo", + typeCourtsSettlementProposal: "Tribunais · Proposta de acordo", + typeCourtsSettlementAccepted: "Tribunais · Acordo aceptado", + typeCourtsNomination: "Tribunais · Nomeamento", + typeCourtsNominationVote: "Tribunais · Votación de nomeamento", + question: "Pregunta", + deadline: "Data límite", + status: "Estado", + votes: "Votos", + totalVotes: "Votos totais", + voteTotalVotes: "Votos totais", + name: "Nome", + skills: "Habilidades", + tags: "Etiquetas", + title: "Título", + date: "Data", + category: "Categoría", + attendees: "Asistentes", + location: "Localización", + contentWarning: "Asunto", + bankWalletConnected: "Carteira ECOin", + bankUbiReceived: "RBU recibida", + bankTx: "Tx", + bankEpochShort: "Época", + viewDetails: "Ver os detalles", + link: "Ligazón", + activityProjectFollow: "%OASIS% agora está %ACTION% este proxecto %PROJECT%", + activityProjectUnfollow: "%OASIS% agora está %ACTION% este proxecto %PROJECT%", + following: "SEGUINDO", + unfollowing: "DEIXANDO DE SEGUIR", + pledged: "ACHEGADO", + parliamentCandidatureId: "Candidatura", + parliamentGovMethod: "Método", + parliamentVotesReceived: "Votos recibidos", + parliamentMethodANARCHY: "Anarquía", + parliamentMethodVOTE: "Votación comunitaria", + parliamentMethodRANKED: "Voto preferencial", + parliamentMethodPLURALITY: "Pluralidade", + parliamentMethodCOUNCIL: "Consello", + parliamentMethodJURY: "Xurado", + parliamentAnarchy: "ANARQUÍA", + parliamentElectionsStart: "Inicio das eleccións", + parliamentElectionsEnd: "Fin das eleccións", + parliamentCurrentLeader: "Candidatura gañadora", + parliamentProposalTitle: "Título", + parliamentOpenVote: "Votación aberta", + parliamentStatus: "Estado", + parliamentLawQuestion: "Pregunta", + parliamentLawMethod: "Método", + parliamentLawProposer: "Proponente", + parliamentLawEnacted: "Promulgada", + parliamentLawVotes: "Votos", + createdAt: "Creado o", + courtsCaseTitle: "Caso", + courtsMethod: "Método", + courtsMethodJUDGE: "Xuíz", + courtsMethodJUDGES: "Panel de xuíces", + courtsMethodSINGLE_JUDGE: "Xuíz único", + courtsMethodJURY: "Xurado", + courtsMethodCOUNCIL: "Consello", + courtsMethodCOMMUNITY: "Comunidade", + courtsMethodARBITRATION: "Arbitraxe", + courtsMethodVOTE: "Votación comunitaria", + courtsAccuser: "Acusación", + courtsRespondent: "Defensa", + courtsThStatus: "Estado", + courtsThAnswerBy: "Responder antes de", + courtsThEvidenceBy: "Probas ata", + courtsThDecisionBy: "Decisión antes de", + courtsVotesNeeded: "Votos necesarios", + courtsVotesSlashTotal: "SI/TOTAL", + courtsOpenVote: "Votación aberta", + courtsAnswerTitle: "Resposta", + courtsVerdictResult: "Resultado", + courtsVerdictOrders: "Ordes", + courtsSettlementText: "Acordo", + courtsSettlementAccepted: "Aceptado", + courtsJudge: "Xuíz", + courtsThSupports: "Apoios", + courtsFilterOpenCase: "Abrir un caso", + courtsCaseMediatorsPh: "IDs de Oasis dos mediadores, separados por comas", + courtsMediatorsLabel: "Mediadores", + courtsThCase: "Caso", + courtsThCreatedAt: "Data de inicio", + courtsPublicPrefLabel: "Visibilidade tras a resolución", + courtsPublicPrefYes: "Acepto que este caso sexa totalmente público", + courtsPublicPrefNo: "Prefiro manter privados os detalles", + courtsPublicPrefSubmit: "Gardar a preferencia de visibilidade", + courtsMethodMEDIATION: "Mediación", + courtsNoCases: "Non hai casos.", + reportsTitle: "Informes", + reportsDescription: "Xestiona e segue os informes da túa rede.", + reportsSearchPlaceholder: "Buscar en Informes...", + reportsFilterAll: "TODOS", + reportsFilterRecent: "RECENTES", + reportsFilterTop: "TOP", + reportsFilterMine: "MEUS", + reportsFilterFeatures: "FUNCIÓNS", + reportsFilterBugs: "ERROS", + reportsFilterAbuse: "ABUSOS", + reportsFilterContent: "CONTIDO", + reportsFilterConfirmed: "CONFIRMADOS", + reportsFilterResolved: "RESOLTOS", + reportsFilterOpen: "ABERTOS", + reportsFilterUnderReview: "EN REVISIÓN", + reportsFilterInvalid: "NON VÁLIDOS", + reportsCreateButton: "Crear un informe", + reportsTitleLabel: "Título", + reportsCategory: "Categoría", + reportsCategoryFeatures: "Funcións", + reportsCategoryBugs: "Erros", + reportsCategoryAbuse: "Abuso", + reportsCategoryContent: "Contido", + reportsUpdateButton: "Actualizar", + reportsDeleteButton: "Eliminar", + reportsMineSectionTitle: "Os teus informes", + reportsFeaturesSectionTitle: "Solicitudes de funcións", + reportsBugsSectionTitle: "Erros", + reportsAbuseSectionTitle: "Informes de abuso", + reportsContentSectionTitle: "Problemas de contido", + reportsAllSectionTitle: "Informes", + reportsNoItems: "Non hai informes dispoñibles.", + reportsSeverity: "Gravidade", + reportsSeverityLow: "Baixa", + reportsSeverityMedium: "Media", + reportsSeverityHigh: "Alta", + reportsSeverityCritical: "Crítica", + reportsStatus: "Estado", + reportsStatusOpen: "Aberto", + reportsStatusUnderReview: "En revisión", + reportsStatusResolved: "Resolto", + reportsStatusInvalid: "Non válido", + reportsConfirmButton: "CONFIRMAR O INFORME!", + reportsConfirmations: "Confirmacións", + reportsConfirmedSectionTitle: "Informes confirmados", + reportsCreateTaskButton: "Crear unha tarefa", + reportsOpenSectionTitle: "Informes abertos", + reportsUnderReviewSectionTitle: "Informes en revisión", + reportsResolvedSectionTitle: "Informes resoltos", + reportsInvalidSectionTitle: "Informes non válidos", + reportsBugTemplateTitle: "Datos para reproducir (erros)", + reportsFeatureTemplateTitle: "Detalles (funcións)", + reportsAbuseTemplateTitle: "Detalles (abuso)", + reportsContentTemplateTitle: "Detalles (problemas de contido)", + reportsStepsToReproduceLabel: "Pasos para reproducilo", + reportsStepsToReproducePlaceholder: "1) ...\n2) ...\n3) ...", + reportsExpectedBehaviorLabel: "Resultado esperado", + reportsExpectedBehaviorPlaceholder: "Que debería pasar?", + reportsActualBehaviorLabel: "Resultado real", + reportsActualBehaviorPlaceholder: "Que pasa realmente?", + reportsEnvironmentLabel: "Contorno", + reportsEnvironmentPlaceholder: "Versión, dispositivo, SO, navegador, configuración, rexistros, etc.", + reportsReproduceRateLabel: "Frecuencia", + reportsReproduceRateAlways: "Sempre", + reportsReproduceRateOften: "A miúdo", + reportsReproduceRateSometimes: "Ás veces", + reportsReproduceRateRarely: "Raramente", + reportsReproduceRateUnable: "Non se pode reproducir", + reportsReproduceRateUnknown: "Non especificado", + reportsProblemStatementLabel: "Problema / necesidade", + reportsProblemStatementPlaceholder: "Que problema resolve esta función?", + reportsUserStoryLabel: "Historia de habitante", + reportsUserStoryPlaceholder: "Como , quero , para .", + reportsAcceptanceCriteriaLabel: "Criterios de aceptación", + reportsAcceptanceCriteriaPlaceholder: "- Dado...\n- Cando...\n- Entón...", + reportsWhatHappenedLabel: "Que ocorreu?", + reportsWhatHappenedPlaceholder: "Describe o incidente con contexto e datas aproximadas.", + reportsReportedUserLabel: "Habitante / entidade denunciada", + reportsReportedUserPlaceholder: "@habitante, feed, ID, ligazón, etc.", + reportsEvidenceLinksLabel: "Probas / ligazóns", + reportsEvidenceLinksPlaceholder: "Pega ligazóns, IDs de mensaxes, capturas (se procede), etc.", + reportsContentLocationLabel: "Onde está o contido", + reportsContentLocationPlaceholder: "Ligazón, ID, canle, fío, autor, etc.", + reportsWhyInappropriatePlaceholder: "Explica o motivo e o impacto.", + reportsRequestedActionLabel: "Acción solicitada", + reportsRequestedActionPlaceholder: "Eliminar, agochar, marcar, advertir, etc.", + tribesTitle: "Tribos", + tribeFilterAll: "TODAS", + tribeFilterRecent: "RECENTES", + tribeFilterMine: "MIÑAS", + tribeFilterMembership: "PERTENZA", + tribeFilterSubtribes: "SUBTRIBOS", + tribeFilterTop: "TOP", + tribeFilterGallery: "GALERÍA", + tribeFeedFilterTOP: "TOP", + tribeFeedFilterMINE: "MEUS", + tribeFeedFilterALL: "TODOS", + tribeFeedFilterRECENT: "RECENTES", + courtsStanceDENY: "Negar", + courtsStanceADMIT: "Admitir", + courtsStancePARTIAL: "Admitir en parte", + tribeAllSectionTitle: "Tribos", + tribeMineSectionTitle: "As túas tribos", + tribeCreateSectionTitle: "Crear unha tribo", + tribeUpdateSectionTitle: "Actualizar a tribo", + tribeGallerySectionTitle: "Galería de tribos", + tribeRecentSectionTitle: "Tribos recentes", + tribeTopSectionTitle: "Tribos populares", + tribeRootLabel: "RAÍZ", + tribeDescription: "Explora ou crea tribos na túa rede.", + tribeMainTribeLabel: "TRIBO PRINCIPAL", + tribeCreateButton: "Crear unha tribo", + tribeUpdateButton: "Actualizar", + tribeDeleteButton: "Eliminar", + tribeImageLabel: "Subir contido multimedia (máx.: 75MB)", + tribeTitleLabel: "Título", + searchTribesPlaceholder: "Buscar en Tribos...", + tribeTitlePlaceholder: "Nome da tribo", + tribeDescriptionLabel: "Descrición", + tribeDescriptionPlaceholder: "Describe esta tribo", + tribeLocationLabel: "Localización", + tribeLocationPlaceholder: "Onde está situada esta tribo?", + tribeTagsLabel: "Etiquetas", + tribeTagsPlaceholder: "Introduce etiquetas separadas por comas", + tribeInviteMode: "Modo de convite", + tribeModeLabel: "MODO", + tribeIsAnonymousLabel: "ESTADO", + tribeMembersCount: "Membros", + tribeEnterInvite: "INTRODUCIR O CONVITE", + subscriptionTitle: "Subscricións", + subscriptionOn: "Subscrito", + subscriptionOff: "Non subscrito", + subscriptionSubscribe: "Subscribirse", + subscriptionUnsubscribe: "Cancelar a subscrición", + subscriptionSubscribers: "Subscritores", + pmListLabel: "Enviar a", + pmListNone: "— Sen lista —", + pmListNoSubscribers: "Sen subscritores", + tribeLeaveButton: "SAÍR", + tribePublic: "PÚBLICA", + tribePrivate: "PRIVADA", + tribeGenerateInvite: "CONVITE ÚNICO", + tribeOpenInvitation: "CONVITE ABERTO", + tribeJoinOpen: "UNIRSE Á TRIBO", + tribeRemoveInvitation: "ELIMINAR O CONVITE", + tribeCreatedAt: "Creado o", + tribeAuthor: "Por", + tribeStrict: "Estrito", + tribeOpen: "Aberta", + tribeFeedRefeeds: "Redifusións", + tribeFeedRefeed: "Redifundir", + tribeFeedMessagePlaceholder: "Escribe un feed…", + tribeFeedSend: "Enviar", + tribeFeedEmpty: "Aínda non hai mensaxes de feed dispoñibles.", + noTribes: "Aínda non se atoparon tribos.", + tribeNotFound: "Non se atopou a tribo!", + createTribeTitle: "Crear unha tribo", + updateTribeTitle: "Actualizar a tribo", + tribeSectionInhabitants: "Habitantes", + tribeSectionVotations: "VOTACIÓNS", + tribeSectionEvents: "EVENTOS", + tribeSectionTasks: "TAREFAS", + tribeSectionFeed: "FEED", + tribeSectionForum: "FORO", + tribeSectionMedia: "Multimedia", + tribeSectionImages: "IMAXES", + tribeSectionAudios: "AUDIOS", + tribeSectionVideos: "VÍDEOS", + tribeSectionDocuments: "DOCUMENTOS", + tribeSectionBookmarks: "MARCADORES", + tribeSectionMaps: "MAPAS", + tribeSectionPads: "PADS", + tribeSectionChats: "CHATS", + tribeSectionCalendars: "CALENDARIOS", + tribePadCreate: "Crear un pad", + tribeChatCreate: "Crear un chat", + tribeCalendarCreate: "Crear un calendario", + tribePadsEmpty: "Aínda non hai pads.", + tribeChatsEmpty: "Aínda non hai chats.", + tribeCalendarsEmpty: "Aínda non hai calendarios.", + tribeInhabitantsEmpty: "Aínda non hai habitantes nesta tribo.", + tribeEventCreate: "Crear un evento", + tribeEventsEmpty: "Aínda non hai eventos.", + tribeEventTitle: "Título", + tribeEventDescription: "Descrición", + tribeEventDate: "Data", + tribeEventLocation: "Localización", + tribeEventAttend: "ASISTIR", + tribeEventUnattend: "NON ASISTIR", + tribeEventAttendees: "Asistentes", + tribeTaskCreate: "Crear unha tarefa", + tribeTasksEmpty: "Aínda non hai tarefas.", + tribeTaskTitle: "Título", + tribeTaskDescription: "Descrición", + tribeTaskPriority: "Prioridade", + tribeTaskDeadline: "Data límite", + tribeTaskAssignees: "Asignados", + tribeTaskStatusInProgress: "EN CURSO", + tribeTaskStatusClosed: "PECHAR", + tribeTaskAssign: "ASIGNAR", + tribeTaskUnassign: "DESASIGNAR", + tribeVotationCreate: "Crear unha votación", + tribeVotationsEmpty: "Aínda non hai votacións.", + tribeVotationTitle: "Título", + tribeVotationDescription: "Descrición", + tribeVotationOptions: "Opcións", + tribeVotationDeadline: "Data límite", + tribeVotationVote: "VOTAR", + tribeVotationResults: "Votos", + tribeVotationClose: "PECHAR A VOTACIÓN", + tribeVotationOptionPlaceholder: "Opción", + tribeForumCreate: "Crear un foro", + tribeForumEmpty: "Aínda non hai fíos.", + tribeForumTitle: "Título", + tribeForumText: "Mensaxe", + tribeForumCategory: "Categoría", + tribeForumReply: "Responder", + tribeForumReplies: "Respostas", + tribeMediaUpload: "Subir multimedia", + readDocument: "Ler o documento", + tribeCreateImage: "Crear unha imaxe", + tribeCreateAudio: "Crear un audio", + tribeCreateVideo: "Crear un vídeo", + tribeCreateDocument: "Crear un documento", + tribeCreateBookmark: "Crear un marcador", + tribeMediaEmpty: "Aínda non hai multimedia.", + tribeMediaTitle: "Título", + tribeMediaDescription: "Descrición", + tribeMediaTypeImage: "Imaxe", + tribeMediaTypeVideo: "Vídeo", + tribeMediaTypeAudio: "Audio", + tribeMediaTypeDocument: "Documento", + tribeMediaTypeBookmark: "Marcador", + tribeContentDelete: "ELIMINAR", + tribeReachTribe: "TRIBO", + tribeReachNotice: "OASIS amósao a todos os habitantes e CLEARNET tamén na web aberta. Abrilo deixa unha copia lexible fóra da tribo: volver a TRIBO agóchao, pero as copias xa compartidas non se poden borrar.", + tribeReachPrivate: "O contido dunha tribo privada só pode quedar dentro da tribo.", + tribeReachOpenedBy: "Aberto por", + tribeReachChange: "Onde se publica", + tribeInviteCodeText: "Código de convite: ", + oasisVersionLabel: "Versión de Oasis", + tribeInviteCodeHint: "Comparte este código con quen queiras convidar. Pode unirse desde /invites.", + tribeStatusOpen: "ABERTO", + tribeStatusClosed: "PECHADO", + tribeStatusInProgress: "EN CURSO", + tribePriorityLow: "BAIXA", + tribePriorityMedium: "MEDIA", + tribePriorityHigh: "ALTA", + tribePriorityCritical: "CRÍTICA", + tribeTaskFilterAll: "TODAS", + tribeMediaFilterAll: "TODOS", + tribeForumCatGeneral: "XERAL", + tribeForumCatProposal: "PROPOSTA", + tribeForumCatQuestion: "PREGUNTA", + tribeForumCatAnnouncement: "ANUNCIO", + tribeStatusLabel: "Estado", + tribeSubTribes: "SUBTRIBOS", + tribeSubTribesCreate: "Crear unha subtribo", + tribeSubTribesStrictDenied: "O modo estrito da tribo non che permite crear novas subtribos. Ponte en contacto co administrador.", + tribeActivityRefeed: "REFEED", + tribeSectionActivity: "ACTIVIDADE", + tribeSectionTrending: "TENDENCIAS", + tribeSectionTags: "ETIQUETAS", + tribeSectionSearch: "BUSCAR", + tribeActivityEmpty: "Aínda non hai actividade.", + tribeActivityCreated: "creou", + tribeActivityPosted: "publicou", + tribeActivityReplied: "respondeu", + tribeTrendingEmpty: "Aínda non hai contido en tendencia.", + tribeTrendingPeriodDay: "Hoxe", + tribeTrendingPeriodWeek: "Esta semana", + tribeTrendingPeriodAll: "Todo", + tribeTrendingEngagement: "interacción", + tribeTopCategory: "Máis votado", + tribeTagsEmpty: "Non se atoparon etiquetas.", + tribeSearchPlaceholder: "Buscar contido da tribo...", + tribeSearchEmpty: "Sen resultados.", + tribeSearchResults: "Resultados", + tribeSearchMinChars: "Introduce polo menos 2 caracteres para buscar.", + agendaTitle: "Axenda", + agendaDescription: "Aquí podes atopar todos os teus elementos asignados.", + agendaSearchPlaceholder: "Buscar na Axenda...", + agendaFilterAll: "TODOS", + agendaFilterToday: "HOXE", + agendaFilterUpcoming: "PRÓXIMOS", + agendaFilterOverdue: "VENCIDOS", + agendaFilterOpen: "ABERTOS", + agendaFilterClosed: "PECHADOS", + agendaFilterTasks: "TAREFAS", + agendaFilterMarket: "MERCADO", + agendaFilterTribes: "TRIBOS", + agendaFilterEvents: "EVENTOS", + agendaFilterReports: "INFORMES", + agendaFilterTransfers: "TRANSFERENCIAS", + agendaFilterJobs: "EMPREGOS", + agendaFilterIndustry: "INDUSTRIA", + agendaFilterHousing: "VIVENDA", + agendaFilterProjects: "PROXECTOS", + agendaFilterCalendars: "CALENDARIOS", + agendaNoItems: "Non se atoparon asignacións.", + agendaDiscardButton: "Descartar", + agendaRestoreButton: "Restaurar", + agendaRemoveButton: "Quitar", + agendaMembersCount: "Membros", + agendaLocationLabel: "Localización", + agendaYes: "SI", + agendaNo: "NON", + agendaInviteModeLabel: "Modo de convite", + agendaAnonymousLabel: "Modo anónimo", + agendaMembersLabel: "Membros", + agendareportCategory: "Categoría", + agendareportSeverity: "Gravidade", + agendareportStatus: "Estado", + agendaTransferConcept: "Concepto", + agendaTransferAmount: "Cantidade", + agendaTransferDeadline: "Prazo", + opinionsTitle: "Opinións", + shareYourOpinions: "Descobre e vota opinións na túa rede.", + author: "Por", + voteNow: "Vota agora", + alreadyVoted: "Xa opinaches.", + noOpinionsFound: "Non se atoparon opinións.", + TOPButton: "TOP", + opinionsTotalCount: "Total de opinións", + voteInteresting: "Interesante", + voteNecessary: "Necesario", + voteUseful: "Útil", + voteInformative: "Informativo", + voteWellResearched: "Ben investigado", + voteNeedsSources: "Necesita fontes", + voteWrong: "Incorrecto", + voteLowQuality: "Baixa calidade", + voteLove: "Amor", + voteClear: "Claro", + voteMisleading: "Enganoso", + voteOffTopic: "Fóra de tema", + voteDuplicate: "Duplicado", + voteClickbait: "Cebo de clics", + votePropaganda: "Propaganda", + voteFunny: "Divertido", + voteInspiring: "Inspirador", + voteUplifting: "Alentador", + voteUnnecessary: "Innecesario", + voteRejected: "Rexeitado", + voteConfusing: "Confuso", + voteTroll: "Troll", + voteNsfw: "NSFW", + voteViolent: "Violento", + voteToxic: "Tóxico", + voteHarassment: "Acoso", + voteHate: "Odio", + voteScam: "Estafa", + voteTriggering: "Provocador", + voteInsightful: "Perspicaz", + voteAccurate: "Preciso", + voteActionable: "Aplicable", + voteCreative: "Creativo", + voteSpam: "Spam", + voteAdultOnly: "Só adultos", + privateMessage: "MP", + pmSendTitle: "Mensaxes privadas", + pmSend: "Enviar!", + pmCancel: "Cancelar", + pmReset: "Restablecer", + pmSharedKeyHint: "Comparte esta chave co destinatario por outra canle. É necesaria para descifrar.", + pmDescription: "Usa este formulario para enviar unha mensaxe/ficheiro cifrado a outros habitantes.", + pmComposeTitle: "Enviar unha mensaxe", + pmRecipients: "Destinatarios", + pmRecipientsHint: "Introduce os IDs de Oasis separados por comas", + pmLimitsHint: "Ata 7 habitantes por mensaxe.", + pmTextPlaceholder: "Escribe a túa mensaxe.", + pmSubject: "Asunto", + pmSubjectHint: "Introduce o asunto da mensaxe", + pmText: "Mensaxe", + pmCrypterChip: "2xE2E", + pmCrypterDecryptButton: "Descifrar", + fileShareTitle: "Compartir un ficheiro", + fileShareRecipientPlaceholder: "@...=.ed25519", + fileShareFileLabel: "Ficheiro", + fileShareDownload: "Descargar", + fileShareMutualError: "Só podes compartir ficheiros con habitantes que se apoien mutuamente contigo.", + fileShareNoFile: "Non se seleccionou ningún ficheiro.", + fileShareTooLarge: "O ficheiro supera o tamaño permitido.", + fileShareFailed: "Non se puido preparar o ficheiro.", + fileShareSendError: "Non se puido enviar o ficheiro.", + fileShareUnavailable: "Este ficheiro non está dispoñible agora mesmo. Téntao máis tarde.", + pmCrypterBadKey: "A chave compartida é incorrecta!", + pmCrypterTooLong: "A mensaxe é demasiado longa para cifrala co Crypter. Acúrtaa e téntao de novo.", + pmCrypterCipherLabel: "Mensaxe recifrada", + pmCrypterCharsLabel: "caracteres", + pmInvalidRecipients: "Oasis ID non válido (debería ser como @…=.ed25519).", + pmEncryptionLabel: "Cifrado", + private: "Buzón", + privateDescription: "Xestiona as túas mensaxes privadas.", + privateInbox: "BUZÓN", + privateSent: "ENVIADAS", + privateDelete: "Borrar", + pmCreateButton: "Escribir unha MP", + noPrivateMessages: "Non hai mensaxes privadas.", + pmReply: "Responder", + pmReplies: "respostas", + inReplyTo: "EN RESPOSTA A", + forumInThread: "NO FÍO", + pmPreview: "Previsualizar", + pmPreviewTitle: "Vista previa", + performed: "→", + pmFromLabel: "De:", + pmToLabel: "Para:", + pmNoSubject: "(sen asunto)", + pmSubjectLabel: "Asunto:", + pmBotJobs: "JobsBot", + pmBotProjects: "ProjectsBot", + pmBotMarket: "MarketBot", + pmBotShops: "ShopsBot", + pmBotPolitical: "PoliticalBot", + politicalBotLarpTitle: "A casa gobernante do L.A.R.P cambiou.", + politicalBotParliamentTitle: "Comezou un novo ciclo de goberno no Parlamento.", + politicalBotTribeTitle: "Comezou un novo ciclo de goberno nunha das túas tribos.", + politicalBotLarpIntro: "Agora goberna {house} durante este ciclo: {cycle}", + politicalBotParliamentIntro: "O goberno actual é {gov}", + politicalBotParliamentIntroLeader: "O goberno actual é {gov}, exercido por {leader}", + politicalBotTribeIntro: "O goberno actual na tribo {tribe} é {gov}", + politicalBotTribeIntroLeader: "O goberno actual na tribo {tribe} é {gov}, exercido por {leader}", + politicalBotLeaderLabel: "Líder", + inboxJobSubscribedTitle: "Nova subscrición á túa oferta de emprego", + pmInhabitantWithId: "Habitante con ID OASIS:", + pmHasSubscribedToYourJobOffer: "subscribiuse á túa oferta de emprego", + inboxMarketItemSoldTitle: "Artigo vendido", + inboxShopSoldTitle: "Produto vendido", + pmYourItem: "O teu artigo", + pmHasBeenSoldTo: "vendeuse a", + pmFor: "por", + inboxProjectPledgedTitle: "Nova achega ao teu proxecto", + pmHasPledged: "achegou", + pmToYourProject: "ao teu proxecto", + blockchain: "Explorador", + blockchainDescription: "Explora e visualiza os bloques da cadea.", + blockchainNoBlocks: "Non se atoparon bloques na cadea.", + blockchainStatsTitle: "Estatísticas", + blockchainStatsTypeBreakdown: "Desagregación por tipo", + blockchainStatsTopAuthors: "Autores principais", + blockchainStatsCount: "Bloques", + blockchainBlockID: "ID do bloque", + blockchainBlockAuthor: "Autoría", + blockchainBlockCarbon: "Pegada de carbono", + blockchainBlockType: "Tipo", + blockchainBlockTimestamp: "Marca de tempo", + blockchainLatestDatagram: "Último datagrama", + blockchainDatagram: "Datagrama", + blockchainDetails: "Ver os detalles do bloque", + blockchainViewBlockexplorer: "Ver en Blockexplorer", + blockchainBack: "Volver a Blockexplorer", + banking: "Banca", + bankingTitle: "Banca", + bankingDescription: "A economía colectiva de ECOin.", + bankOverview: "Resumo", + bankEpochs: "Épocas", + bankRules: "Regras", + pending: "Pendentes", + closed: "Pechadas", + bankClaimNow: "Reclamar", + bankClaimUBI: "Reclamar RBU", + bankClaimAndPay: "Reclamar e pagar", + bankClaimedPending: "Reclamación pendente...", + bankStatusClaimed: "Reclamado", + bankStatusExpired: "Caducado", + bankPubOnly: "Operación só para PUBs", + bankEpoch: "Época", + bankPool: "Fondo (esta época)", + bankWeightsSum: "Suma de pesos", + bankNoAllocations: "Non se atoparon asignacións.", + bankNoEpochs: "Non se atoparon épocas.", + bankEpochAllocations: "Asignacións da época", + bankAllocId: "ID de asignación", + bankAllocDate: "Data", + bankAllocConcept: "Concepto", + bankAllocFrom: "De", + bankAllocTo: "Para", + bankAllocAmount: "Cantidade", + bankAllocStatus: "Estado", + bankEpochId: "ID de época", + bankRuleHash: "Hash da instantánea de regras", + bankViewEpoch: "Ver a época", + bankYourIndustryBalance: "A túa parte da industria", + bankYourUbiMonth: "A túa RBU (este mes)", + bankYourFundsMonth: "Fondos totais", + bankIndustryBalance: "Produción industrial", + bankUserBalance: "O teu saldo", + bankAddresses: "Enderezos", + bankNoAddresses: "Non se atoparon enderezos.", + bankUser: "ID de Oasis", + bankAddress: "Enderezo", + bankAddAddressTitle: "Engadir un enderezo de ECOin", + bankAddAddressUser: "ID de Oasis", + bankAddAddressLabel: "Etiqueta (opcional)", + bankAddAddressLabelPlaceholder: "Carteira de Bob, tenda de Alice…", + bankAddAddressUserOptional: "Oasis ID (opcional)", + bankAddressUnnamed: "Enderezo sen nome", + bankAddAddressAddress: "Enderezo de ECOin", + bankAddAddressSave: "Gardar", + bankAddressAdded: "Enderezo engadido", + bankAddressUpdated: "Enderezo actualizado", + bankAddressExists: "O enderezo xa existe", + bankAddressInvalid: "Enderezo non válido", + bankAddressDeleted: "Enderezo eliminado", + bankAddressNotFound: "Non se atopou o enderezo", + bankAddressForbidden: "Só podes configurar o teu propio enderezo de cobro", + bankAddressTotal: "Total de enderezos", + bankAddressSearch: "Buscar @habitante ou enderezo", + bankAddressActions: "Accións", + bankAddressDelete: "Eliminar", + bankAddressSource: "Fonte", + bankAddressDeleteConfirm: "Queres eliminar este enderezo?", + search: "Buscar!", + bankLocal: "Local", + bankFromOasis: "Rede", + pubIdLabel: "PUB ID", + bankUbiAvailableNo: "SEN FONDOS!", + bankUbiAvailableOk: "DISPOÑIBLE!", + bankUbiAvailability: "RBU", + bankAlreadyClaimedThisMonth: "Xa reclamada este mes", + bankUbiThisMonth: "RBU (este mes)", + bankUbiLastClaimed: "RBU (última reclamada)", + bankUbiNeverClaimed: "Nunca reclamada", + bankUbiTotalClaimed: "RBU (total reclamado)", + bankUbiPub: "PUB", + bankUbiInhabitant: "HABITANTE", + bankUbiClaimedAmount: "RECLAMADO (ECO)", + typeBankUbiResult: "BANCA - RBU", + bankNoPubConfigured: "Non hai ningún PUB configurado. Establece o ID do teu PUB en Configuración.", + shopsTitle: "Tendas", + shopDescription: "Descobre e xestiona tendas na rede.", + shopTitle: "Tenda", + shopUpload: "Crear unha tenda", + shopAllSectionTitle: "Tendas", + shopMineSectionTitle: "As miñas tendas", + shopRecentSectionTitle: "Tendas recentes", + shopTopSectionTitle: "Tendas destacadas", + shopProductsSectionTitle: "Produtos destacados", + shopPricesSectionTitle: "Produtos por prezo", + shopFavoritesSectionTitle: "Favoritas", + shopCreateSectionTitle: "Crear unha tenda", + shopUpdateSectionTitle: "Actualizar a tenda", + shopCreate: "Crear", + shopUpdate: "Actualizar", + shopDelete: "Eliminar", + shopReachClearnet: "Clearnet", + shopReachOasis: "Oasis", + encryptedChipLabel: "E2E", + transportChipLabel: "Cifrado en tránsito", + shopAddFavorite: "Engadir a favoritas", + shopRemoveFavorite: "Quitar de favoritas", + shopOpen: "ABERTA", + shopClosed: "PECHADA", + shopMakePrivate: "FACELA PRIVADA (E2E)", + shopMakePublic: "FACELA PÚBLICA", + shopProducts: "Produtos", + shopFilterAll: "TODAS", + shopFilterRecent: "RECENTES", + shopFilterMine: "MIÑAS", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUTOS", + shopFilterPrices: "PREZOS", + shopFilterFavorites: "FAVORITOS", + shopProductAdd: "Engadir un produto", + shopProductTitle: "Produto", + shopProductUpdate: "Actualizar o produto", + shopProductPrice: "Prezo (ECO)", + shopProductStock: "Stock", + shopProductUntitled: "Produto sen título", + shopUntitled: "Tenda sen título", + shopNoItems: "Non se atoparon tendas.", + shopNoProducts: "Aínda non hai produtos.", + shopOutOfStock: "Esgotado", + shopBuy: "Comprar", + shopBuyEncryptedNote: "Os teus datos de entrega envíanse cifrados; só o dono da tenda pode velos.", + shopBuyDeliveryAddress: "Enderezo de entrega", + shopBuyContact: "Contacto", + shopBuyContactPlaceholder: "correo, teléfono, etc.", + shopBuyNotes: "Notas", + shopBuyDeliveryRequired: "O enderezo de entrega é obrigatorio.", + shopOrdersTitle: "Pedidos", + shopOrdersDescription: "Pedidos recibidos por esta tenda.", + shopOrdersEmpty: "Aínda non hai pedidos.", + shopOrderProduct: "Produto", + shopOrderPrice: "Prezo", + shopOrderBuyer: "Comprador", + shopBackToShop: "Volver á tenda", + marketVisitItem: "VER O ARTIGO", + shopStatus: "ESTADO", + shopCreatedAt: "CREADA", + shopSearchPlaceholder: "Buscar en Tendas...", + shopUrl: "URL", + shopLocation: "Localización", + shopTags: "Etiquetas", + shopVisibility: "Visibilidade", + shopShortDescription: "Descrición curta", + shopShortDescriptionPlaceholder: "Descrición breve para as tarxetas (máx. 160 caracteres)", + shopTitlePlaceholder: "Nome da túa tenda", + shopDescriptionPlaceholder: "Descrición detallada da túa tenda", + shopLocationPlaceholder: "Cidade, País", + shopTagsPlaceholder: "Introduce etiquetas separadas por comas", + mapTitlePlaceholder: "Título do mapa", + shopProductFeatured: "Produto destacado", + shopSendToMarket: "Enviar ao mercado", + typeShop: "TENDAS", + typeShopProduct: "PRODUTO DE TENDA", + bankExchange: "Intercambio", + bankExchangeCurrentValue: "Valor de ECOin (1h)", + bankTotalSupply: "Oferta total de ECOin", + bankPubsSupply: "Oferta de ECOin do fondo dos PUBs", + bankHoldingSupply: "Oferta de ECOin fóra da RBU", + bankHoursOfWork: "horas", + bankUnitMs: "ms", + bankUnitSeconds: "segundos", + bankUnitMinutes: "minutos", + bankUnitDays: "días", + bankExchangeNoData: "Non hai datos dispoñibles", + bankInflation: "Inflación de ECOin (anual)", + bankInflationMonthly: "Inflación de ECOin (mensual)", + bankExchangeChartTitle: "Evolución do valor de ECOin ao longo do tempo", + bankExchangeChartValue: "Valor (ECO/h)", + bankExchangeChartSupply: "Oferta", + bankExchangeChartInflation: "Inflación %", + bankExchangeChartEmpty: "Aínda non hai mostras suficientes — volve máis tarde", + bankCurrentSupply: "Oferta actual de ECOin", + bankingSyncStatus: "Estado de ECOin", + bankingSyncStatusSynced: "Sincronizado", + bankingSyncStatusOutdated: "Sen sincronizar", + statsTitle: "Estatísticas", + statistics: "Estatísticas", + statsDescription: "Descobre estatísticas sobre a túa rede.", + ALLButton: "TODO", + RECENTButton: "RECENTES", + MINEButton: "MIÑAS", + TOMBSTONEButton: "ELIMINADOS", + statsCreatedAt: "Creado o", + statsYourContent: "Contido", + statsYourOpinions: "Opinións", + statsNetworkContent: "Contido", + statsNetworkOpinions: "Opinións", + statsEcoWalletLabel: "Carteira ECOin", + walletAddressCopyHint: "Preme para seleccionar e copiar", + statsEcoWalletNotConfigured: "Non configurada!", + statsBookmark: "Marcadores", + statsEvent: "Eventos", + statsTask: "Tarefas", + statsVotes: "Votos", + statsMarket: "Mercado", + statsForum: "Foros", + statsJob: "Empregos", + statsReport: "Informes", + statsFeed: "Feed", + statsTribe: "Tribos", + statsImage: "Imaxes", + statsAudio: "Audios", + statsVideo: "Vídeos", + statsDocument: "Documentos", + statsMap: "Mapas", + statsShop: "Tendas", + statsShopProduct: "Produtos de tenda", + statsTransfer: "Transferencias", + statsAiExchange: "IA", + statsPUBs: "PUBs", + statsSyncedPeers: "Nodos", + statsTotalLabel: "Total", + statsPost: "Publicacións", + statsSize: "Total (tamaño)", + statsBlockchainSize: "Blockchain (tamaño)", + statsBlobsSize: "Blobs (tamaño)", + statsActivity7d: "Actividade (últimos 7 días)", + statsActivity7dTotal: "Total 7 días", + statsActivity30dTotal: "Total 30 días", + day: "Día", + messages: "Mensaxes", + statsProject: "Proxectos", + statsProjectsTitle: "Proxectos", + statsProjectsTotal: "Total de proxectos", + statsProjectsActive: "Activos", + statsProjectsCompleted: "Completados", + statsProjectsPaused: "En pausa", + statsProjectsCancelled: "Cancelados", + statsProjectsGoalTotal: "Meta total", + statsProjectsPledgedTotal: "Achega total", + statsMarketTitle: "Mercado", + statsMarketTotal: "Artigos totais", + statsMarketForSale: "En venda", + statsMarketReserved: "Reservados", + statsMarketClosed: "Pechados", + statsMarketSold: "Vendidos", + statsUsersTitle: "Habitantes", + user: "Habitante", + statsTombstoneRatio: "Ratio de eliminados (%)", + bankingUserEngagementScore: "Puntuación de KARMA", + statsParliamentCandidature: "Candidaturas do parlamento", + statsParliamentTerm: "Mandatos do parlamento", + statsParliamentProposal: "Propostas do parlamento", + statsParliamentRevocation: "Revogacións do parlamento", + statsParliamentLaw: "Leis do parlamento", + statsCourtsCase: "Casos xudiciais", + statsCourtsEvidence: "Probas xudiciais", + statsCourtsAnswer: "Respostas xudiciais", + statsCourtsVerdict: "Veredictos xudiciais", + statsCourtsSettlement: "Acordos xudiciais", + statsCourtsSettlementProposal: "Propostas de acordo", + statsCourtsSettlementAccepted: "Acordos aceptados", + statsCourtsNomination: "Nomeamentos de xuíces", + statsCourtsNominationVote: "Votos de nomeamento", + ai: "IA", + aiTitle: "IA", + aiDescription: "Unha Intelixencia Artificial Colectiva (IAC) chamada '42' que aprende da túa rede.", + aiUserQuestion: "Pregunta", + aiResponseTitle: "Resposta", + aiSubmitButton: "Enviar!", + aiSettingsDescription: "Configura o teu prompt (máx. 128 caracteres) para o modelo de IA.", + aiPrompt: "Proporciona unha resposta informativa e precisa.", + aiConfiguration: "Configurar o prompt", + aiPromptUsed: "Indicación", + aiClearHistory: "Borrar o historial", + aiServerError: "Non podo responder ben. Téntao de novo.", + aiStatusLoading: "42 está a cargar o modelo. Pregunta de novo nun momento.", + aiThinking: "Pensando", + aiWaiting: "Agardando", + aiStatusMissingModel: "O modelo de IA non está instalado: falta src/AI/oasis-42-1-chat.Q4_K_M.gguf.", + aiExportFineTuning: "Exportar Fine-Tuning", + aiFactsTitle: "Datos da túa rede", + aiInputPlaceholder: "Que é Oasis?", + typeAiExchange: "IA", + aiApproveTrain: "Engadir ao adestramento colectivo", + aiRejectTrain: "Non adestrar", + aiTrainApproved: "Aprobado para o adestramento", + aiTrainRejected: "Rexeitado para o adestramento", + aiSnippetsUsed: "Fragmentos usados", + aiSnippetsLearned: "Fragmentos aprendidos", + statsAITraining: "Adestramento de IA", + aiCustomAnswerPlaceholder: "Escribe a túa resposta personalizada…", + marketMineSectionTitle: "Os teus artigos", + marketCreateSectionTitle: "Crear un artigo", + marketUpdateSectionTitle: "Actualizar", + marketAllSectionTitle: "Mercado", + marketTitle: "Mercado", + marketDescription: "Un mercado para intercambiar bens ou servizos na túa rede.", + marketFilterAll: "TODOS", + marketFilterMine: "MEUS", + marketFilterAuctions: "POXAS", + marketFilterItems: "INTERCAMBIO", + marketFilterNew: "NOVO", + marketFilterUsed: "USADO", + marketFilterBroken: "ROTO", + marketFilterForSale: "EN VENDA", + marketFilterSold: "VENDIDO", + marketFilterDiscarded: "DESCARTADO", + marketFilterRecent: "RECENTES", + marketFilterMyBids: "POXAS FEITAS", + marketCreateButton: "Vender algo", + marketItemType: "Tipo", + marketItemTitle: "Título", + marketItemAvailable: "Data límite", + marketItemDescription: "Descrición", + marketItemDescriptionPlaceholder: "Describe o artigo que vendes", + marketItemStatus: "Estado", + marketShopLabel: "Tenda", + marketItemCondition: "Condición", + marketItemPrice: "Prezo", + marketItemTags: "Etiquetas", + marketItemTagsPlaceholder: "Introduce etiquetas separadas por comas", + marketItemDeadline: "Data límite", + marketItemIncludesShipping: "Inclúe o envío?", + marketItemHighestBid: "Poxa máis alta", + marketItemStock: "Existencias", + marketOutOfStock: "Sen stock", + marketActionsUpdate: "Actualizar", + marketUpdateButton: "Actualizar", + marketActionsDelete: "Eliminar", + marketActionsChangeStatus: "CAMBIAR O ESTADO", + marketActionsBuy: "COMPRAR!", + marketAuctionBids: "Poxas actuais", + marketPlaceBidButton: "Poxar", + marketItemSeller: "Vendedor", + marketNoItems: "Aínda non hai artigos dispoñibles.", + marketYourBid: "A túa poxa", + marketCreateFormImageLabel: "Subir contido multimedia (máx.: 75MB)", + marketSearchPlaceholder: "Buscar no Mercado...", + marketMinPriceLabel: "Prezo mínimo", + marketMaxPriceLabel: "Prezo máximo", + marketSortRecent: "Máis recentes", + marketSortPrice: "Prezo", + marketSortDeadline: "Data límite", + marketSearchButton: "Buscar", + marketAuctionEndsIn: "Remata", + marketAuctionEnded: "Rematou", + marketNoItemsMatch: "Non hai artigos que coincidan coa túa busca.", + housingTitle: "Vivenda", + housingDescriptionText: "Descobre e xestiona lugares na túa rede.", + modulesHousingLabel: "Vivenda", + modulesHousingDescription: "Módulo para descubrir e xestionar lugares.", + typeHousing: "VIVENDA", + housingFilterAll: "TODO", + housingFilterMine: "MEUS", + housingFilterRequested: "SOLICITADOS", + housingFilterSale: "VENDA", + housingFilterRent: "ALUGUER", + housingFilterCouchsurfing: "COUCHSURFING", + housingFilterOpen: "DISPOÑIBLES", + housingFilterClosed: "NON DISPOÑIBLES", + housingFilterRecent: "RECENTES", + housingFilterTop: "TOP", + housingType: "Oferta", + housingTypeSALE: "En venda", + housingTypeRENT: "En aluguer", + housingTypeCOUCHSURFING: "Couchsurfing", + housingProperty: "Inmoble", + housingPropertyAPARTMENT: "Piso", + housingPropertyHOUSE: "Casa", + housingPropertyROOM: "Habitación", + housingPropertyLAND: "Terreo", + housingPropertyOTHER: "Outro", + housingTitleLabel: "Título", + housingTitlePlaceholder: "Nome do lugar", + housingDescription: "Descrición", + housingDescriptionPlaceholder: "Describe o lugar, o barrio, que inclúe: baños, xardín, camas…", + housingRules: "Normas", + housingRulesPlaceholder: "Mascotas, fumar, horas de silencio, estadía mínima…", + housingPlace: "Lugar", + housingPlacePlaceholder: "País, cidade, enderezo", + housingPrice: "Prezo", + housingPriceLabel: "Prezo (ECO)", + housingSizeLabel: "Superficie (m²)", + housingFree: "GRATIS", + housingPerMonth: "/mes", + housingRooms: "Habitacións", + housingSize: "Superficie", + housingCapacity: "Hóspedes", + housingAvailableFrom: "Dispoñible desde", + housingAvailableTo: "Dispoñible ata", + housingTags: "Etiquetas", + spreadEditWarning: "Este contido perderá as súas réplicas ao editalo", + housingRequests: "Solicitudes", + housingRequestButton: "Solicitar", + housingCancelButton: "Cancelar a solicitude", + housingRequestedBadge: "SOLICITADO", + housingStatus: "Estado", + housingStatusOPEN: "DISPOÑIBLE", + housingStatusCLOSED: "NON DISPOÑIBLE", + housingSetOpen: "Marcar como dispoñible", + housingSetClosed: "Marcar como non dispoñible", + housingCreateButton: "Crear un lugar", + housingUpdateButton: "Actualizar", + housingSearchPlaceholder: "Buscar en Vivenda...", + housingSearchButton: "Buscar", + housingMinPrice: "Prezo mín.", + housingMaxPrice: "Prezo máx.", + housingSortRecent: "Máis recentes", + housingSortPrice: "Prezo máis baixo", + housingSortRequests: "Máis solicitados", + housingSortRating: "Mellor valorados", + housingNoItems: "Non se atoparon lugares.", + jobsTitle: "Emprego", + jobsCandidatesTitle: "Candidatos suxeridos", + jobsCandidatesDescription: "Habitantes con competencias que encaixan con este emprego. Envíalles unha mensaxe privada para convidalos.", + jobsCandidatesPmBody: "Ola, creo que o teu perfil encaixa coa miña oferta de emprego", + jobsDescription: "Descobre e xestiona ofertas de emprego na túa rede.", + jobsFilterRecent: "RECENTES", + jobsFilterMine: "MEUS", + jobsFilterAll: "TODOS", + jobsFilterRemote: "REMOTOS", + jobsFilterOpen: "ABERTOS", + jobsFilterClosed: "PECHADOS", + jobsCV: "CV", + jobsCreateJob: "Publicar un emprego", + jobsRecentTitle: "Empregos recentes", + jobsMineTitle: "Os teus empregos", + jobsAllTitle: "Ofertas de emprego", + jobsRemoteTitle: "Empregos remotos", + jobsOpenTitle: "Empregos abertos", + jobsClosedTitle: "Empregos pechados", + jobsCVTitle: "CV", + jobsFilterPresencial: "PRESENCIAL", + jobsFilterFreelancer: "FREELANCE", + jobsFilterEmployee: "ASALARIADO", + jobsPresencialTitle: "Empregos presenciais", + jobsFreelancerTitle: "Empregos freelance", + jobsEmployeeTitle: "Empregos asalariados", + jobTitle: "Título", + jobLocation: "Localización", + jobSalary: "Salario (ECO/1h)", + jobVacants: "Vacantes", + jobDescription: "Descrición", + jobRequirements: "Requisitos", + jobLanguages: "Idiomas", + jobStatus: "Estado", + jobStatusOPEN: "ABERTA", + jobStatusCLOSED: "PECHADA", + jobSetClosed: "Marcar como PECHADA", + jobSubscribeButton: "Únete a esta oferta!", + jobUnsubscribeButton: "Sae desta oferta!", + jobTitlePlaceholder: "Introduce o título do emprego", + jobDescriptionPlaceholder: "Describe o posto", + jobRequirementsPlaceholder: "Introduce os requisitos", + jobLanguagesPlaceholder: "Inglés, francés, castelán, galego...", + jobTasksPlaceholder: "Lista de tarefas", + jobLocationPresencial: "Presencial", + jobLocationRemote: "Remoto", + jobVacantsPlaceholder: "Número de vacantes", + jobImage: "Subir contido multimedia (máx.: 75MB)", + jobTasks: "Tarefas", + jobType: "Tipo de emprego", + jobTime: "Tempo de traballo", + jobSubscribers: "Subscritores", + noSubscribers: "Sen subscritores", + jobsFilterTop: "TOP", + jobsTopTitle: "Empregos mellor pagados", + createJobButton: "Publicar un emprego", + noJobsFound: "Non se atoparon ofertas de emprego.", + jobAuthor: "Por", + jobTypeFreelance: "Autónomo", + jobTypeSalary: "Asalariado", + jobTypeExchange: "Intercambio de horas", + jobTypeEXCHANGE: "INTERCAMBIO DE HORAS", + jobsFilterExchange: "INTERCAMBIO", + jobsExchangeTitle: "Empregos por intercambio de horas", + jobsHoursOffered: "Horas ofrecidas", + jobsHoursRequested: "Horas pedidas a cambio", + jobsExchangeSkill: "Habilidade solicitada a cambio", + jobTimePartial: "Parcial", + jobTimeComplete: "Completa", + jobsUpdateButton: "ACTUALIZAR", + jobsFilterApplied: "CANDIDATURA", + jobsAppliedTitle: "As miñas candidaturas", + jobsAppliedBadge: "Inscrito", + jobsSearchPlaceholder: "Buscar en Emprego...", + jobsMinSalaryLabel: "Salario mín.", + jobsMaxSalaryLabel: "Salario máx.", + jobsSortRecent: "Máis recentes", + jobsSortSalary: "Maior salario", + jobsSortSubscribers: "Máis candidatos", + jobsSearchButton: "Buscar", + jobsApplicants: "Candidatos", + jobsUpdatedAt: "Actualizado", + jobSetOpen: "Marcar como aberta", + jobsTagsLabel: "Etiquetas", + noJobsMatch: "Non hai ofertas que coincidan coa túa busca.", + industrySearchPlaceholder: "Buscar en Industria...", + industryAllSectors: "Todos os sectores", + industryVisitButton: "Visitar Industria", + industryAdvanceTo: "Cambiar a", + industryAmount: "Cantidade", + industryApproveBuild: "Aprobar", + industryBlueprint: "Plano", + industryBlueprints: "Planos", + industryBuild: "Produción", + industryBuildNotes: "Notas", + industryBuildStart: "Data de inicio", + industryVoteUpdateButton: "Votar a actualización", + industryVoteDeleteButton: "Votar o borrado", + industryRevokeVote: "Revogar", + industryTimeLeft: "Tempo restante", + industryBuildEnd: "Data de fin", + industryBuilds: "Producións", + industryBuildTitle: "Título", + industryContribute: "Contribuír", + industryContributeButton: "Contribuír", + industryContributions: "Contribucións", + industryCreateBlueprintButton: "Crear un plano", + industryDistribute: "Distribuír", + industryDistributeButton: "Distribuír por cotas", + industryDistribution: "Distribución", + industryEcoAmount: "Cantidade (ECO)", + industryEditBlueprint: "Editar o plano", + industryFacility: "Fábrica", + industryKindLabel: "Tipo", + industryKind_labor: "Man de obra", + industryKind_material: "Material", + industryKind_eco: "Fondos", + industryLaborHours: "Man de obra (horas)", + industryHours: "Horas", + industryMaterialItem: "Material", + industryMaterials: "Materiais", + industryEstMaterials: "Total de materiais", + industryEstLabor: "Total de man de obra", + industryEstTotal: "Prezo estimado", + industryBuildingPrice: "Prezo de construción", + industryFinalPrice: "Prezo final", + industryBlueprintDescPlaceholder: "Especificacións, dimensións, peso, documentación…", + industryMaterialValue: "Valor (ECO)", + industryMember: "Membro", + industryNewBlueprint: "Novo plano", + industryNewBuild: "Propoñer unha produción", + industryEditBuild: "Editar a produción", + industryNeedBlueprint: "Crea primeiro un plano: cada produción fabrica un.", + industryNoBlueprints: "Aínda non hai planos.", + industryNoBuilds: "Aínda non hai producións.", + industryNote: "Nota", + industryOutput: "Saída", + industryOutputKind: "Tipo de produto", + industryOutputValue: "Valor de saída (ECO, p. ex. da venda)", + industryPostJob: "Crear un emprego", + industryPot: "Bote", + industryProposeBuildButton: "Propoñer a produción", + industryProposer: "Proponente", + industryAuthor: "Autor", + industrySellOnMarket: "Enviar ao mercado", + industrySendToProjects: "Crear un proxecto", + industrySkills: "Habilidades", + industryTreasury: "Tesouraría", + industryKind_physical: "Físico", + industryKind_digital: "Dixital", + industryBuildStatus_PROPOSED: "PROPOSTA", + industryBuildStatus_REJECTED: "REXEITADA", + industryBuildStatus_APPROVED: "APROBADA", + industryBuildStatus_STOCKING: "ABASTECÉNDOSE", + industryBuildStatus_IN_PRODUCTION: "EN PRODUCIÓN", + industryBuildStatus_COMPLETED: "COMPLETADA", + industryBuildStatus_FAILED: "FALLIDA", + pmBotIndustry: "IndustryBot", + pmBotHousing: "HousingBot", + jobsBotMatchTitle: "Un emprego encaixa co teu currículo", + jobsBotMatchIntro: "Un emprego encaixa co teu currículo.", + jobsBotMatchJob: "Emprego", + jobsBotMatchHint: "Recibes isto porque o teu currículo está xestionado por IA. Podes desactivalo no teu CV.", + cvAiManaged: "Xestionado por IA", + switchOn: "ACTIVADO", + switchOff: "DESACTIVADO", + cvAiManagedHint: "Se está activo, JobsBot avísate por mensaxe privada cando un emprego encaixa co teu currículo.", + cvMatchThreshold: "Avisarme a partir deste nivel de coincidencia", + housingBotRequestedTitle: "Alguén solicitou un dos teus lugares.", + housingBotCancelledTitle: "Cancelouse unha solicitude sobre un dos teus lugares.", + housingBotYouRequestedTitle: "Solicitaches un lugar.", + housingBotYouCancelledTitle: "Cancelaches unha solicitude.", + housingBotUnavailableTitle: "Un lugar que solicitaches xa non está dispoñible.", + housingBotUnavailableSentTitle: "Avisaches a quen solicitou este lugar.", + industryBotAdmittedTitle: "Admitíronte nunha fábrica.", + industryBotApplicationTitle: "Nova solicitude de ingreso na túa fábrica.", + industryBotInvitedTitle: "Convidáronte a unha fábrica.", + industryBotDissolvedTitle: "Disolveuse unha fábrica á que pertences.", + industryBotBuildApprovedTitle: "Aprobouse unha produción.", + industryBotDistributedTitle: "Distribuíuse a saída dunha produción.", + industryFilterRules: "REGRAS", + industryRulesTitle: "Regras", + industryRulesIntro: "Industria permite aos habitantes posuír colectivamente os medios de produción: unha Fábrica é un obradoiro da rede que ninguén posúe de forma privada.", + industryRulesSteward: "O fundador é o mordomo — un custodio, non un dono: non pode vender, privatizar nin disolver a Fábrica pola súa conta.", + industryRulesMembership: "A política de pertenza define como se unen os habitantes: Aberta (entras ao instante), Por voto (admíteche o voto dos membros) ou Só por convite (un membro debe convidarte antes).", + industryRulesQuorum: "O quórum é o número mínimo de membros que deben votar para que unha decisión conte, para que unha Fábrica pequena non a decida un só habitante.", + industryRulesMajority: "A maioría é a fracción de votos necesaria para aprobar (0,5 = metade, 1 = unanimidade); unha decisión apróbase cando os SI chegan polo menos a max(quórum, membros × maioría).", + industryRulesDecisions: "Os membros votan para admitir novos, aprobar producións e disolver a Fábrica; o mordomo non pode saltarse estas decisións colectivas.", + industryRulesBlueprints: "Os planos son receitas libres (COPYLEFT) — as entradas (materiais, horas de traballo, habilidades) e a saída (un ben físico ou dixital) — e calquera pode bifurcalas.", + industryRulesBuilds: "Unha produción é unha orde de fabricación: PROPOSTA → APROBADA (por voto) → ABASTECÉNDOSE → EN PRODUCIÓN → COMPLETADA, e só acepta contribucións unha vez aprobada.", + industryRulesContributions: "Os membros achegan traballo (horas), materiais (valor declarado en ECO) ou ECO a unha produción; cada achega gaña karma.", + industryRulesLaborRate: "A tarifa de traballo é o valor en ECO dunha hora de traballo nesta Fábrica; converte horas en karma para comparar esforzo e capital de forma xusta: karma = horas × tarifa + valor material + ECO.", + industryRulesShares: "A túa cota nunha produción é o teu karma ÷ karma total, e determina canto do valor de saída recibes.", + industryRulesDistribution: "Cando unha produción se completa, o mordomo reparte o bote (os fondos da produción máis o valor de venda) entre quen contribuíu, en proporción á súa participación.", + industryRulesTreasury: "O mordomo custodia as achegas en ECO como tesouraría da produción ata o reparto; todos os movementos quedan rexistrados na rede e as disputas poden ir aos Tribunais.", + industryJobs: "Empregos", + industryReportButton: "Informar", + industryCreateTask: "Crear unha tarefa", + industryOpenDispute: "Abrir unha disputa", + industryMaterialsPlaceholder: "placa-solar:2:120\nbatería:1:80\nkit:1:15", + industryCreateInvite: "Xerar un convite", + industryPauseVotes: "Votos de estado", + industryTitle: "Industria", + industryDescription: "Módulo para xestionar colectivamente os medios de produción.", + typeIndustryBuild: "PRODUCIÓN", + typeIndustryBlueprint: "PLANO", + typeIndustryAllocation: "DISTRIBUCIÓN", + typeIndustry: "INDUSTRIA", + modulesIndustryLabel: "Industria", + modulesIndustryDescription: "Módulo para xestionar colectivamente os medios de produción.", + industryFilterAll: "TODAS", + industryFilterRecent: "RECENTES", + industryFilterMember: "MEMBRO", + industryFilterBlueprints: "PLANOS", + industryFilterBuilds: "PRODUCIÓNS", + industryFilterMine: "MIÑAS", + industryFilterActive: "EN MARCHA", + industryFilterPaused: "EN PAUSA", + industryFilterDissolved: "DISOLTAS", + industryAllTitle: "Industria", + industryRecentTitle: "Fábricas recentes", + industryMemberTitle: "Fábricas nas que estou", + industryMineTitle: "As miñas fábricas", + industryActiveTitle: "En marcha", + industryPausedTitle: "Fábricas en pausa", + industryDissolvedTitle: "Fábricas disoltas", + industryNoFacilitiesFound: "Non se atoparon fábricas.", + industryCreateFacility: "Crear unha fábrica", + industryMembers: "Membros", + industryMemberBadge: "MEMBRO", + industryName: "Nome", + industryNamePlaceholder: "Nome da fábrica", + industryDescriptionLabel: "Descrición", + industryDescriptionPlaceholder: "Que produce esta fábrica?", + industrySector: "Sector", + industryMembershipPolicy: "Política de pertenza", + industryQuorum: "Quórum", + industryMajority: "Maioría", + industryLaborRate: "Tarifa de traballo", + industryCreateButton: "Crear a fábrica", + industryUpdateButton: "Actualizar", + industrySteward: "Mordomo", + industryStatusActive: "EN MARCHA", + industryStatusPaused: "EN PAUSA", + industryStatusDissolved: "DISOLTA", + industryStatusLabel: "Estado", + industryJoinButton: "Unirse", + industryApplyButton: "Solicitar o ingreso", + industryLeaveButton: "Saír", + industryInviteButton: "Convidar", + industryInvitePlaceholder: "@...=.ed25519", + industryInviteNeeded: "Necesitas un convite para unirte.", + industryPauseButton: "Votar a pausa", + industryResumeButton: "Votar a reanudación", + industryDeleteButton: "Eliminar", + industryPendingApplicants: "Solicitudes pendentes", + industryVotesYes: "si", + industryVotesNo: "non", + industryAlreadyVoted: "votado", + industryAdmit: "Admitir", + industryReject: "Rexeitar", + industryDissolveTitle: "Disolver a fábrica", + industryDissolveVote: "Votar a disolución", + industryPolicy_open: "Aberta", + industryPolicy_vote: "Por voto", + industryPolicy_invite: "Só por convite", + projectsTitle: "Proxectos", + projectsDescription: "Crea, financia e segue proxectos impulsados pola comunidade na túa rede.", + projectSearchPlaceholder: "Buscar en Proxectos...", + projectCreateProject: "Crear un proxecto", + projectCreateButton: "Crear un proxecto", + projectUpdateButton: "ACTUALIZAR", + projectDeleteButton: "ELIMINAR", + projectNoProjectsFound: "Non se atoparon proxectos.", + projectFilterAll: "TODOS", + projectFilterMine: "OS MEUS PROXECTOS", + projectFilterActive: "ACTIVOS", + projectFilterPaused: "EN PAUSA", + projectFilterCompleted: "COMPLETADOS", + projectFilterFollowing: "SEGUIDOS", + projectFilterRecent: "RECENTES", + projectFilterTop: "TOP", + projectAllTitle: "Proxectos", + projectMineTitle: "Os teus proxectos", + projectActiveTitle: "Proxectos activos", + projectPausedTitle: "Proxectos en pausa", + projectCompletedTitle: "Proxectos completados", + projectFollowingTitle: "Proxectos seguidos", + projectRecentTitle: "Proxectos recentes", + projectTopTitle: "Mellor financiados", + projectTitlePlaceholder: "Nome do proxecto", + projectImage: "Subir contido multimedia (máx.: 75MB)", + projectDescription: "Descrición", + projectDescriptionPlaceholder: "Conta a historia e os obxectivos…", + projectGoal: "Meta (ECO)", + projectGoalPlaceholder: "50000", + projectDeadline: "Data límite", + projectProgress: "Progreso inicial (%)", + projectStatus: "Estado", + projectFunding: "Financiamento", + projectPledged: "Comprometido", + projectSetStatus: "Establecer o estado", + projectSetProgress: "Actualizar o progreso", + projectFollowButton: "SEGUIR", + projectUnfollowButton: "DEIXAR DE SEGUIR", + projectStatusACTIVE: "ACTIVO", + projectStatusPAUSED: "EN PAUSA", + projectStatusCOMPLETED: "COMPLETADO", + projectStatusCANCELLED: "CANCELADO", + projectPledgeTitle: "Apoia este proxecto", + projectPledgePlaceholder: "Cantidade en ECO", + projectBounties: "Recompensas", + projectNoBounties: "Non se atoparon recompensas.", + projectTitle: "Título", + projectMilestoneSelect: "Seleccionar unha fita", + projectBountyCreateButton: "Crear unha recompensa", + projectBountyStatus: "Estado da recompensa", + projectBountyOpen: "aberta", + projectBountyClaimed: "reclamada", + projectBountyDone: "completada", + projectBountyClaimedBy: "reclamada por", + projectBountyClaimButton: "reclamar", + projectBountyCompleteButton: "Marcar como completada", + projectMilestones: "Fitos", + projectAddMilestoneTitle: "Novo fito", + projectMilestoneTitle: "Título do fito", + projectMilestoneTargetPercent: "Porcentaxe (%)", + projectMilestoneDueDate: "Data", + projectMilestoneStatus: "Estado do fito", + projectMilestoneOpen: "aberto", + projectMilestoneDone: "completado", + projectMilestoneDue: "vence", + projectMilestoneMarkDone: "Marcar como feito", + projectMilestoneTitlePlaceholder: "Introduce o título do fito", + projectMilestoneDescriptionPlaceholder: "Introduce a descrición deste fito", + projectMilestoneDescription: "Descrición do fito", + projectBudgetAssigned: "Asignado a recompensas", + projectBudgetOver: "⚠ Orzamento excedido: o asignado supera o obxectivo", + projectFollowers: "Seguidores", + projectFollowersTitle: "Seguidores", + projectFollowersNone: "Aínda non hai seguidores.", + projectMore: "máis", + projectYouFollowHint: "Segues este proxecto", + projectBackers: "Patrocinadores", + projectBackersTitle: "Patrocinadores", + projectBackersTotal: "Total de patrocinadores", + projectBackersTotalPledged: "Total achegado", + projectBackersYourPledge: "A túa achega", + projectBackersNone: "Aínda non hai achegas.", + projectFilterBackers: "MECENAS", + projectFilterApplied: "ACHEGANDO", + projectAppliedTitle: "ACHEGANDO", + projectBackersLeaderboardTitle: "Mecenas destacados", + projectNoBackersFound: "Non hai mecenas.", + projectBackerAmount: "Total achegado", + projectBackerPledges: "Achegas", + projectBackerProjects: "Proxectos", + projectSelectMilestoneOrBounty: "Seleccionar un fito ou unha recompensa", + projectPledgeButton: "Achegar", + footerLicense: "GPLv3", + modulesModuleName: "Nome", + modulesModuleDescription: "Descrición", + modulesModuleStatus: "Estado", + modulesTotalModulesLabel: "Total", + modulesEnabledModulesLabel: "Activados", + modulesDisabledModulesLabel: "Desactivados", + modulesInvitesLabel: "Convites", + modulesInvitesDescription: "Módulo para xestionar e aplicar códigos de convite.", + modulesWalletLabel: "Carteira", + modulesWalletDescription: "Módulo para xestionar os teus activos dixitais (ECOin).", + modulesCipherLabel: "Cifrado", + modulesCipherDescription: "Módulo para cifrar e descifrar o teu texto de forma simétrica (cun contrasinal compartido).", + modulesBookmarksLabel: "Marcadores", + modulesBookmarksDescription: "Módulo para descubrir e xestionar marcadores.", + modulesVideosLabel: "Vídeos", + modulesVideosDescription: "Módulo para descubrir e xestionar vídeos.", + modulesDocsLabel: "Documentos", + modulesDocsDescription: "Módulo para descubrir e xestionar documentos.", + modulesAudiosLabel: "Audios", + modulesAudiosDescription: "Módulo para descubrir e xestionar audios.", + modulesTagsLabel: "Etiquetas", + modulesTagsDescription: "Módulo para descubrir e explorar patróns de taxonomía (etiquetas).", + modulesImagesLabel: "Imaxes", + modulesImagesDescription: "Módulo para descubrir e xestionar imaxes.", + modulesTrendingLabel: "Tendencias", + modulesTrendingDescription: "Módulo para explorar o contido máis popular.", + modulesEventsLabel: "Eventos", + modulesEventsDescription: "Módulo para descubrir e xestionar eventos.", + modulesTasksLabel: "Tarefas", + modulesTasksDescription: "Módulo para descubrir e xestionar tarefas.", + modulesMarketLabel: "Mercado", + modulesMarketDescription: "Módulo para intercambiar bens ou servizos.", + modulesShopsLabel: "Tendas", + modulesShopsDescription: "Módulo para xestionar e descubrir tendas.", + modulesTribesLabel: "Tribos", + modulesTribesDescription: "Módulo para explorar ou crear tribos (grupos).", + modulesVotationsLabel: "Votacións", + modulesVotationsDescription: "Módulo para descubrir e xestionar votacións.", + modulesParliamentLabel: "Parlamento", + modulesParliamentDescription: "Módulo para elixir gobernos e votar leis.", + modulesCourtsLabel: "Tribunais", + modulesCourtsDescription: "Módulo para resolver conflitos e emitir veredictos.", + modulesReportsLabel: "Informes", + modulesReportsDescription: "Módulo para xestionar e facer o seguimento de informes sobre problemas, erros, abusos e avisos de contido.", + modulesOpinionsLabel: "Opinións", + modulesOpinionsDescription: "Módulo para descubrir e votar opinións.", + modulesTransfersLabel: "Transferencias", + modulesTransfersDescription: "Módulo para descubrir e xestionar contratos intelixentes (transferencias).", + modulesFediverseLabel: "Multiverso", + modulesBlogsLabel: "Blogs", + modulesBlogsDescription: "Módulo para descubrir e xestionar blogs.", + modulesPollsLabel: "Enquisas", + modulesPollsDescription: "Módulo para preguntarlle á rede e contar as respostas.", + modulesFediverseDescription: "Xestiona as túas outras contas do multiverso, incluído enviar e recibir contido.", + modulesFeedLabel: "Feed", + modulesFeedDescription: "Módulo para descubrir e compartir textos breves (feeds).", + modulesPixeliaLabel: "Pixelia", + modulesPixeliaDescription: "Módulo para debuxar nunha grella colaborativa.", + modulesMelodyLabel: "Melody", + modulesMelodyDescription: "Módulo para xerar e compartir o \"son\" da túa blockchain.", + modulesAgendaLabel: "Axenda", + modulesAgendaDescription: "Módulo para xestionar todos os teus elementos asignados.", + modulesAILabel: "IA", + modulesAIDescription: "Módulo para falar cun LLM chamado '42'.", + modulesForumLabel: "Foros", + modulesForumDescription: "Módulo para descubrir e xestionar foros.", + modulesJobsLabel: "Emprego", + modulesJobsDescription: "Módulo para descubrir e xestionar ofertas de emprego.", + modulesProjectsLabel: "Proxectos", + modulesProjectsDescription: "Módulo para explorar, financiar e xestionar proxectos.", + modulesBankingLabel: "Banca", + modulesBankingDescription: "Módulo para xestionar o tesouro común: valor de ECOin, impostos e RBU.", + modulesFavoritesLabel: "Favoritos", + modulesFavoritesDescription: "Módulo para xestionar o teu contido favorito.", + fileTooLargeTitle: "Ficheiro demasiado grande", + fileTooLargeMessage: "O ficheiro supera o tamaño máximo permitido (75 MB). Por favor, selecciona un ficheiro máis pequeno.", + goBack: "Volver", + errorDismiss: "De acordo", + errorPageTitle: "Produciuse un erro", + errorNoAllocation: "Non se atopou ningunha asignación para esta reclamación.", + errorInvalidClaim: "Esta asignación non se pode reclamar.", + visibilityLabel: "Visibilidade", + clearnetPrivateConflict: "Un contido privado, oculto, pechado, só por invitación ou de pago non se pode publicar na CLEARNET. Escolle OASIS ou faino público.", + visibilityPublic: "Pública", + visibilityHidden: "Oculta", + visibilityMakePublic: "Facela pública", + visibilityMakeHidden: "Facela oculta", + invitesInhabitantsTitle: "Habitantes", + aiNavPlaceholder: "Describe o que necesitas...", + aiNavDisabled: "A navegación con IA está desactivada.", + aiNavResultsTitle: "Suxestións de AINav", + aiNavQueryLabel: "Consulta", + aiNavResultMatch: "Coincidencia", + aiNavResultsEmpty: "Non hai rutas coincidentes. Proba con /search.", + modulesAINavLabel: "AINav", + modulesAINavDescription: "Módulo para facer peticións en linguaxe natural sobre o contido da rede.", + directConnectDescription: "Conéctate directamente a un nodo introducindo o seu enderezo IP, porto e chave pública. O nodo engadirase como conexión seguida.", + peerHost: "IP", + peerPort: "Porto", + peerPublicKey: "Chave pública (@...ed25519)", + connectAndFollow: "Conectar", + modulesPresetTitle: "Configuracións comúns", + workflowsTitle: "Fluxos de traballo", + workflowsDescription: "Un fluxo de traballo fixa o tema e que módulos están activos.", + workflowsSet: "Aplicar o fluxo de traballo", + workflow_default: "Por defecto", + workflow_jobs: "Emprego", + workflow_social: "Social", + workflow_business: "Negocios", + workflow_night: "Noite", + workflow_kids: "Nenos", + workflow_activists: "Activismo", + workflow_mobile: "Móbil", + statsCarbonFootprintTitle: "Pegada de carbono", + statsCarbonTombstone: "Pegada dos borrados (tombstoning)", + uploadMedia: "Subir contido multimedia (máx.: 75MB)", + invitesUnfollow: "Deixar de seguir", + invitesFollow: "Seguir", + invitesUnfollowedInvites: "Non federadas", + invitesNoUnfollowed: "Non hai pubs non federados.", + reportsWhyInappropriateLabel: "Por que é inadecuado?", + blockchainContentDeleted: "Este contido foi eliminado", + visitContent: "Visitar o contido", + favoriteAdd: "Fixar en favoritos", + pmContentTooltip: "Enviar unha mensaxe privada", + phoneCallTooltip: "Facer unha chamada", + favoriteRemove: "Quitar de favoritos", + reportContent: "Denunciar o contido", + bankEcoinHours: "Equivalencia de ECOin en tempo (1 ECO)", + mapsLabel: "Mapas", + mapTitle: "Mapas", + mapDescription: "Xestiona e comparte mapas sen conexión na túa rede.", + mapMineSectionTitle: "Os teus mapas", + mapCreateSectionTitle: "Crear un mapa", + mapUpdateSectionTitle: "Actualizar o mapa", + mapAllSectionTitle: "Mapas", + mapRecentSectionTitle: "Mapas recentes", + mapFavoritesSectionTitle: "Favoritos", + mapFilterAll: "TODOS", + mapFilterMine: "MEUS", + mapFilterRecent: "RECENTES", + mapFilterFavorites: "FAVORITOS", + mapUploadButton: "Crear un mapa", + mapCreateButton: "Crear un mapa", + mapUpdateButton: "Actualizar", + mapDeleteButton: "Eliminar", + mapAddFavoriteButton: "Engadir a favoritos", + mapRemoveFavoriteButton: "Quitar de favoritos", + mapLatLabel: "Latitude", + mapLatPlaceholder: "p. ex. 42.8782", + mapLngLabel: "Lonxitude", + mapLngPlaceholder: "p. ex. -8.5448", + mapDescriptionLabel: "Descrición", + mapDescriptionPlaceholder: "Describe o mapa ou a localización...", + mapTypeLabel: "Tipo de mapa", + mapInviteMode: "Convite", + mapTypeSingle: "ÚNICO", + mapTypeOpen: "ABERTO", + mapTypeClosed: "PECHADO", + mapTagsLabel: "Etiquetas", + mapTagsPlaceholder: "Introduce etiquetas separadas por comas", + mapUrlLabel: "URL do mapa", + mapMarkersTitle: "Marcadores", + mapMarkerDefault: "Marcador", + mapMarkerLatLabel: "Latitude do marcador", + mapMarkerLngLabel: "Lonxitude do marcador", + mapMarkerLabelField: "Etiqueta do marcador", + mapMarkerLabelPlaceholder: "Describe este marcador...", + markerImageLabel: "Imaxe do marcador", + mapAddMarkerTitle: "Engadir un marcador", + mapAddMarkerButton: "Engadir un marcador", + mapCleanMarkerButton: "Limpar o marcador", + mapApplyZoom: "Aplicar o zoom", + mapPlaceSearchPlaceholder: "Buscar un lugar ou país...", + mapNoPlaces: "Non se atoparon lugares.", + mapClusterTitle: "marcadores aquí, preme para achegar", + mapDeleteMarker: "Eliminar marcador", + mapZoomIn: "Achegar", + mapZoomOut: "Afastar", + mapFitMarkers: "Axustar a todos os marcadores", + mapPanLabel: "Mover o mapa", + mapCenterHere: "Centrar aquí", + mapNear: "preto de", + mapPickedSpot: "Punto escollido", + mapPickHint: "Preme no mapa para escoller un punto.", + mapClearPick: "Limpar", + mapSearchPlaceholder: "Buscar en Mapas...", + mapSearchButton: "Buscar", + mapUpdatedAt: "Actualizado", + mapNoMatch: "Ningún mapa coincide coa túa busca.", + noMaps: "Non hai mapas dispoñibles.", + mapLocationTitle: "Localización", + mapVisitLabel: "Visitar o mapa", + typeMap: "MAPAS", + typeMapMarker: "MARCADOR DE MAPA", + modulesMapLabel: "Mapas", + modulesMapDescription: "Módulo para xestionar e compartir mapas sen conexión.", + emergenciesTitle: "Emerxencias", + modulesEmergenciesLabel: "Emerxencias", + modulesEmergenciesDescription: "Módulo para lanzar, confirmar e seguir emerxencias na rede.", + emergenciesDescription: "Módulo para lanzar, confirmar e seguir emerxencias na rede.", + emergencyCreate: "Crear unha emerxencia", + emergencyUpdate: "Actualizar", + emergencyDelete: "Borrar", + emergencyTitleLabel: "Título", + emergencyTitlePlaceholder: "Que está a pasar?", + emergencyTextLabel: "Detalles", + emergencyTextPlaceholder: "Onde, desde cando, que facer...", + emergencyCategoryLabel: "Categoría", + emergencyCategoryWeather: "METEOROLOXÍA", + emergencyCategoryInfrastructure: "INFRAESTRUTURA", + emergencyCategoryHealth: "SAÚDE", + emergencyCategorySecurity: "SEGURIDADE", + emergencyCategoryLost: "PERDIDOS", + emergencyCategoryNeighborhood: "VECIÑANZA", + emergencyExpiresLabel: "Caduca en", + emergencyExpires1d: "1 día", + emergencyExpires3d: "3 días", + emergencyExpires7d: "1 semana", + emergencyExpires30d: "1 mes", + emergencyExpiresNone: "Indefinido", + emergencyExpiresKeep: "Manter a data actual", + emergencyTagsLabel: "Etiquetas", + emergencyTagsPlaceholder: "Introduce etiquetas separadas por comas", + emergencyFilterAll: "TODAS", + emergencyFilterMine: "MIÑAS", + emergencyFilterRecent: "RECENTES", + emergencySeverityUnverified: "SEN VERIFICAR", + emergencySeverityLow: "BAIXA", + emergencySeverityMedium: "MEDIA", + emergencySeverityHigh: "ALTA", + emergencyStatusActive: "ACTIVA", + emergencyStatusResolved: "RESOLTA", + emergencyStatusExpired: "CADUCADA", + emergencyConfirmations: "Confirmacións", + emergencyConfirmButton: "Confirmar", + emergencyConfirmed: "Confirmaches esta emerxencia", + emergencyResolveButton: "Resolver", + emergencyReopenButton: "Reabrir", + emergencyGenerateReport: "Xerar un informe", + emergencyUpdateEdited: "Editada", + emergencyLastUpdate: "ÚLTIMA ACTUALIZACIÓN", + emergencyUpdatesTitle: "Últimas actualizacións:", + emergencyNoUpdates: "Aínda non hai actualizacións.", + emergencyAddUpdate: "Publicar unha actualización", + emergencyUpdatePlaceholder: "Que cambiou?", + emergencyNoItems: "Non se atoparon emerxencias.", + emergencyExpiresAt: "Caduca", + emergencyBannerLabel: "Emerxencia activa", + emergencyCreateSectionTitle: "Crear unha emerxencia", + emergencyUpdateSectionTitle: "Actualizar a emerxencia", + emergencySearchPlaceholder: "Buscar en emerxencias...", + typeEmergency: "EMERXENCIAS", + typeEmergencyConfirm: "CONFIRMACIÓN DE EMERXENCIA", + typeEmergencyUpdate: "ACTUALIZACIÓN DE EMERXENCIA", + statsEmergency: "Emerxencias", + statsEmergencyConfirm: "Confirmacións de emerxencias", + statsEmergencyUpdate: "Actualizacións de emerxencias", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Unha emerxencia que segues ten unha actualización.", + emergencyBotResolvedTitle: "Unha emerxencia que segues resolveuse.", + dataKindEmergencies: "EMERXENCIAS", + mailingTitle: "Listas de correo", + modulesMailingLabel: "Listas de correo", + modulesMailingDescription: "Descobre e xestiona listas de correo na túa rede.", + modulesPhoneLabel: "Teléfono", + modulesPhoneDescription: "Módulo para chamar a outros habitantes con videochamadas cifradas de extremo a extremo.", + mailingDescription: "Descobre e xestiona listas de correo na túa rede.", + mailingCreate: "Crear unha lista de correo", + mailingUpdate: "Actualizar", + mailingArchive: "Arquivar", + mailingActivate: "Activar", + mailingCreateSectionTitle: "Crear unha lista de correo", + mailingUpdateSectionTitle: "Actualizar a lista de correo", + mailingTitleLabel: "Título", + mailingTitlePlaceholder: "Nome da lista de correo", + mailingDescriptionLabel: "Descrición", + mailingDescriptionPlaceholder: "De que trata esta lista?", + mailingTypeLabel: "Tipo de lista de correo", + mailingTypeOpen: "ABERTA", + mailingTypeClosed: "PECHADA", + mailingStatusLabel: "Estado", + mailingStatusActive: "ACTIVA", + mailingStatusArchived: "ARQUIVADA", + mailingMembersLabel: "Convidar habitantes", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Subscritores", + mailingTagsLabel: "Etiquetas", + mailingTagsPlaceholder: "Introduce etiquetas separadas por comas", + mailingFilterAll: "TODAS", + mailingFilterMine: "MIÑAS", + mailingFilterSubscribed: "SUBSCRITAS", + mailingFilterRecent: "RECENTES", + mailingHistoryDate: "DATA", + mailingHistoryThreads: "FÍOS", + mailingHistoryInhabitants: "HABITANTES", + mailingHistorySearchPlaceholder: "Buscar no historial...", + mailingNoMessages: "Aínda non hai mensaxes.", + mailingNoItems: "Non se atoparon listas de correo.", + mailingSearchPlaceholder: "Buscar en listas de correo...", + mailingWriteButton: "Escribir unha mensaxe", + mailingWriteTitle: "Escribir á lista", + mailingReplyTitle: "Responder á lista", + mailingSend: "Enviar", + mailingTextPlaceholder: "A túa mensaxe chega a todos os participantes da lista.", + mailingParticipants: "Participantes", + inboxFilterMailing: "Listas de correo", + mailingReplyPlaceholder: "Responder á lista...", + mailingReplyToList: "Responder á lista", + typeMailingList: "LISTA DE CORREO", + statsMailingList: "Listas de correo", + mailingListLabel: "LISTA DE CORREO", + logisticsTitle: "Loxística", + modulesLogisticsLabel: "Loxística", + modulesLogisticsDescription: "Descobre e xestiona plans loxísticos na túa rede.", + logisticsDescription: "Descobre e xestiona plans loxísticos na túa rede.", + logisticsCreate: "Crear unha ruta", + logisticsUpdate: "Actualizar", + logisticsClose: "Pechar", + logisticsReopen: "Reabrir", + logisticsCreateSectionTitle: "Crear unha ruta", + logisticsUpdateSectionTitle: "Actualizar a ruta", + logisticsTitleLabel: "Título", + logisticsTitlePlaceholder: "Viaxe ao mercado, furgoneta dispoñible...", + logisticsKindLabel: "Tipo de loxística", + logisticsKindTrip: "VIAXE", + logisticsKindShipment: "ENVÍO", + logisticsKindTrips: "VIAXES", + logisticsKindShipments: "ENVÍOS", + logisticsModeLabel: "Modo", + logisticsModeOffer: "OFREZO", + logisticsModeRequest: "BUSCO", + logisticsModeOffers: "OFERTAS", + logisticsModeRequests: "PETICIÓNS", + logisticsDescriptionLabel: "Descrición", + logisticsDescriptionPlaceholder: "Punto de encontro, que levas, condicións...", + logisticsOriginLabel: "Orixe", + logisticsOriginPlaceholder: "Onde comeza", + logisticsDestinationLabel: "Destino", + logisticsDestinationPlaceholder: "Onde remata", + logisticsDateLabel: "Data e hora", + logisticsRecurrenceLabel: "Recorrencia", + logisticsFrequencyLabel: "Frecuencia", + logisticsRecurrenceNone: "ÚNICO", + logisticsRecurrenceDaily: "DIARIO", + logisticsRecurrenceWeekly: "SEMANAL", + logisticsRecurrenceMonthly: "MENSUAL", + logisticsSeatsLabel: "Prazas", + logisticsSizeLabel: "Tamaño", + logisticsSizePlaceholder: "p. ex. 2m x 1m", + logisticsWeightLabel: "Peso", + logisticsWeightPlaceholder: "p. ex. 40kg", + logisticsPriceFree: "GRATIS", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TEMPO", + logisticsPriceLabel: "Prezo", + logisticsHours: "h", + logisticsOrderRefLabel: "Ligazón ao pedido", + logisticsOrderRefPlaceholder: "/shops/... ou /market/... do pedido que se vai entregar", + logisticsOrderLink: "Pedido ligado", + logisticsTagsLabel: "Etiquetas", + logisticsTagsPlaceholder: "Introduce etiquetas separadas por comas", + logisticsFilterAll: "TODAS", + logisticsFilterMine: "MIÑAS", + logisticsFilterBooked: "RESERVADAS", + logisticsFilterRecent: "RECENTES", + logisticsFilterHistory: "HISTORIAL", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "ABERTA", + logisticsStatusUpcoming: "PRÓXIMAS", + logisticsStatusPast: "PASADAS", + logisticsStatusClosed: "PECHADAS", + logisticsZoneLabel: "Zona", + logisticsSearchPlaceholder: "Buscar en loxística...", + logisticsNoItems: "Non se atoparon rutas.", + logisticsBookingPending: "PENDENTE", + logisticsBookingConfirmed: "CONFIRMADA", + logisticsBookingRejected: "REXEITADA", + logisticsBookingCancelled: "CANCELADA", + logisticsBookingDelivered: "ENTREGADA", + logisticsConfirm: "Confirmar", + logisticsReject: "Rexeitar", + logisticsDeliver: "Entregar", + logisticsReceiptLabel: "Recibo", + logisticsReceiptPlaceholder: "Nota de entrega (opcional)", + logisticsCancelBooking: "Cancelar a reserva", + logisticsBookTitle: "Reservar esta ruta", + logisticsNotesLabel: "Notas", + logisticsNotesPlaceholder: "O que deba saber a persoa responsable", + logisticsBook: "Reservar", + logisticsBookingsTitle: "Reservas", + logisticsMyBookingTitle: "A túa reserva", + logisticsRatingsTitle: "Valoracións", + logisticsNoRatings: "Aínda non hai valoracións.", + logisticsRatingsWhenClosed: "As valoracións ábrense ao pechar a ruta.", + logisticsScoreLabel: "Puntuación", + logisticsRatingPlaceholder: "Que tal foi?", + logisticsRate: "Valorar", + typeLogisticsRoute: "RUTA", + typeLogisticsRating: "VALORACIÓN DE RUTA", + statsLogisticsRoute: "Rutas", + statsLogisticsRating: "Valoracións de rutas", + logisticsRouteLabel: "RUTA", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Módulo para publicar canles de podcast.", + podcastsDescription: "Módulo para publicar canles de podcast.", + podcastCreate: "Crear un podcast", + podcastUpdate: "Actualizar", + podcastDelete: "Borrar", + podcastCreateSectionTitle: "Crear un podcast", + podcastUpdateSectionTitle: "Actualizar o podcast", + podcastTitleLabel: "Título", + podcastTitlePlaceholder: "Nome da canle", + podcastDescriptionLabel: "Descrición", + podcastDescriptionPlaceholder: "De que trata este podcast?", + podcastCategoryLabel: "Categoría", + podcastCategoryNews: "NOVAS", + podcastCategoryMusic: "MÚSICA", + podcastCategoryTalk: "CHARLA", + podcastCategoryEducation: "EDUCACIÓN", + podcastCategoryTech: "TECNOLOXÍA", + podcastCategoryCulture: "CULTURA", + podcastCategoryCommunity: "COMUNIDADE", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "OUTROS", + podcastTagsLabel: "Etiquetas", + podcastCoverLabel: "Portada (imaxe ou vídeo)", + podcastEpisodeAudioLabel: "Ficheiro de audio ou vídeo (máx.: 75MB)", + podcastTagsPlaceholder: "Introduce etiquetas separadas por comas", + podcastFilterAll: "TODOS", + podcastFilterMine: "MEUS", + podcastFilterRecent: "RECENTES", + podcastFilterTop: "TOP", + podcastFilterViewers: "OÍNTES", + podcastSearchPlaceholder: "Buscar en podcasts...", + podcastNoItems: "Non se atoparon podcasts.", + podcastEpisodesLabel: "Episodios", + podcastPlaysLabel: "Reproducións", + podcastLastEpisode: "Último episodio", + podcastEpisodesTitle: "Episodios", + podcastNoEpisodes: "Aínda non hai episodios.", + podcastEpisodeAdd: "Engadir un episodio", + podcastEpisodeCreateTitle: "Publicar un episodio", + podcastEpisodeUpdateTitle: "Actualizar o episodio", + podcastEpisodeTitleLabel: "Título do episodio", + podcastEpisodeTitlePlaceholder: "Título do episodio", + podcastEpisodeDescriptionLabel: "Notas", + podcastEpisodeDescriptionPlaceholder: "Que pasa neste episodio, ligazóns, créditos...", + podcastEpisodePublish: "Publicar", + podcastPlayed: "Reproducido", + podcastMarkPlayed: "Marcar como reproducido", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISODIO", + typePodcastOpinion: "OPINIÓN DE EPISODIO", + typePodcastPlay: "REPRODUCIÓN", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Episodios", + statsPodcastPlay: "Reproducións", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Un blog que segues ten unha entrada nova.", + podcastBotEpisodeTitle: "Un podcast que segues ten un episodio novo.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISODIO", + campaignsTitle: "Campañas", + modulesCampaignsLabel: "Campañas", + modulesCampaignsDescription: "Descobre e xestiona campañas na túa rede.", + campaignsDescription: "Descobre e xestiona campañas na túa rede.", + campaignCreate: "Crear unha campaña", + campaignUpdate: "Actualizar", + campaignDelete: "Borrar", + campaignClose: "Pechar", + campaignReopen: "Reabrir", + campaignCreateSectionTitle: "Crear unha campaña", + campaignUpdateSectionTitle: "Actualizar a campaña", + campaignTitleLabel: "Título", + campaignTitlePlaceholder: "Que queres conseguir?", + campaignTextLabel: "Texto", + campaignTextPlaceholder: "Explica a causa, quen decide e que pides...", + campaignCategoryLabel: "Categoría", + campaignCategoryEnvironment: "MEDIO AMBIENTE", + campaignCategoryRights: "DEREITOS", + campaignCategoryHealth: "SAÚDE", + campaignCategoryEducation: "EDUCACIÓN", + campaignCategoryInfrastructure: "INFRAESTRUTURA", + campaignCategoryCulture: "CULTURA", + campaignCategoryEconomy: "ECONOMÍA", + campaignCategoryOther: "OUTROS", + campaignGoalLabel: "Obxectivo de sinaturas", + campaignDeadlineLabel: "Prazo", + campaignTagsLabel: "Etiquetas", + campaignTagsPlaceholder: "Introduce etiquetas separadas por comas", + campaignFilterAll: "TODAS", + campaignFilterMine: "MIÑAS", + campaignFilterSigned: "ASINADAS", + campaignFilterRecent: "RECENTES", + campaignFilterTop: "TOP", + campaignStatusOpen: "ABERTA", + campaignStatusAchieved: "LOGRADA", + campaignStatusUnachieved: "NON LOGRADA", + campaignStatusClosed: "PECHADA", + campaignSearchPlaceholder: "Buscar en campañas...", + campaignNoItems: "Non se atoparon campañas.", + campaignSignaturesLabel: "Sinaturas", + campaignSignaturesTitle: "Apoios", + campaignNoSignatures: "Aínda non hai sinaturas.", + campaignSignTitle: "Asinar esta campaña", + campaignSignPlaceholder: "Por que a apoias? (opcional)", + campaignSign: "Asinar", + campaignSigned: "Xa a asinaches!", + campaignNotSigned: "Aínda falta a túa sinatura", + campaignElevate: "Elevar ao Parlamento", + campaignElevated: "Elevada ao Parlamento", + campaignUpdatesTitle: "Actualizacións", + campaignNoUpdates: "Aínda non hai actualizacións.", + campaignUpdatePlaceholder: "Que pasou?", + campaignAddUpdate: "Publicar unha actualización", + campaignQrAlt: "QR para asinar esta campaña", + campaignQrCaption: "Escanea para asinar", + typeCampaign: "CAMPAÑA", + typeCampaignSignature: "SINATURA", + typeCampaignUpdate: "ACTUALIZACIÓN DE CAMPAÑA", + statsCampaign: "Campañas", + statsCampaignSignature: "Sinaturas de campañas", + statsCampaignUpdate: "Actualizacións de campañas", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Unha campaña que segues ten unha actualización.", + campaignBotAchievedTitle: "Unha campaña que segues alcanzou o seu obxectivo.", + campaignBotRaisedTitle: "Unha campaña que asinaches elevouse ao Parlamento.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Unha ruta que reservaches cambiou.", + logisticsBotClosedTitle: "Unha ruta que reservaches pechouse.", + logisticsBotBookedTitle: "Alguén reservou a túa ruta.", + logisticsBotCancelledTitle: "Cancelouse unha reserva da túa ruta.", + logisticsBotConfirmedTitle: "A túa reserva confirmouse: o paquete está en camiño.", + logisticsBotRejectedTitle: "A túa reserva foi rexeitada.", + logisticsBotDeliveredTitle: "O teu paquete chegou.", + campaignLabel: "CAMPAÑA", + campaignGenerateReport: "Xerar un informe", + logisticsGenerateReport: "Xerar un informe", + mailingGenerateReport: "Xerar un informe", + campaignUpdateEdited: "Editada", + mailingMessagesLabel: "Mensaxes", + logisticsBookingsLabel: "Reservas", + backupTitle: "Copia de seguranza", + modulesBackupLabel: "Copia de seguranza", + modulesBackupDescription: "Módulo para exportar e importar as túas chaves e facer copias de seguranza cifradas.", + backupDescription: "Módulo para exportar e importar as túas chaves e facer copias de seguranza cifradas.", + backupTypeRecovery: "RECUPERACIÓN", + backupTypeKeys: "EXPORTAR CHAVES", + backupTypeFull: "COPIA COMPLETA", + backupTypeRestore: "RESTAURAR UNHA COPIA", + backupTypeInstant: "INSTANTÁNEA", + backupInstantDescription: "Un .zip simple con todo o teu rexistro e os teus ficheiros, sen a túa chave secreta e sen contrasinal. Rápida, pero ", + backupInstantRisk: "quen teña o ficheiro pode lelo todo.", + backupKitPageDescription: "O kit de recuperación só se pode abrir desde este dispositivo.", + backupKitId: "ID de Oasis", + backupKitDate: "Xerado", + backupKitQrAlt: "Código QR da túa chave secreta", + backupKitStep1: "Imprímeo ou cópiao a man e gárdao nun lugar seguro e sen conexión.", + backupKitStep2: "Para recuperalo, instala Oasis nun dispositivo novo e importa este segredo desde Copia de seguranza › Chaves.", + backupKitStep3: "Destrúe esta páxina se algunha vez queda exposta.", + backupKitPdfButton: "Xerar PDF", + backupScopeLabel: "Alcance", + backupScopeAll: "TODO", + backupScopeMine: "SÓ O MEU CONTIDO", + backupSinceLabel: "Desde", + backupEstimateTitle: "Estimación", + backupFileInfo: "A túa copia cifrada descargarase no teu dispositivo (nome: oasis-backup-DATA.oasisbk)", + backupExportButton: "Descargar a copia", + backupRestorePassword: "Escribe o contrasinal que usaches ao facer a copia", + backupRestoreButton: "Restaurar a copia", + backupRestoredLine: "Copia restaurada.", + backupRestoredMessages: "Mensaxes engadidas", + backupRestoredSkipped: "Xa existentes", + backupRestoredBlobs: "Ficheiros engadidos", + backupRestoredState: "Configuración restaurada", + backupRestoredFailed: "Fallidos", + backupRestoredForked: "Diverxentes", + backupRestoreRunning: "Estase restaurando a copia. Esta páxina actualízase soa; mentres tanto podes seguir usando Oasis.", + backupRestoreElapsed: "Transcorrido", + backupRestoreFailedLine: "Non se puido restaurar a copia.", + backupRestoreForkMine: "Este dispositivo xa publicou mensaxes coa túa identidade que non están na copia, así que non se puido aplicar enriba delas. Restáuraa nunha instalación limpa xusto despois de importar as túas chaves, antes de facer nada máis.", + verificationTitle: "Verificación", + verificationDescription: "Comproba a cadea do teu feed, detecta bifurcacións e revisa ficheiros orfos ou ausentes.", + verificationRun: "Executar a verificación", + verificationTotalMessages: "mensaxes no rexistro", + verificationFeed: "O teu feed", + verificationMessages: "mensaxes", + verificationLastSequence: "última secuencia", + verificationGaps: "Ocos", + verificationBrokenLinks: "Ligazóns rotas", + verificationBadHashes: "Hashes incorrectos", + verificationBadSignatures: "Sinaturas incorrectas", + verificationDuplicates: "Secuencias duplicadas", + verificationForks: "Bifurcacións", + verificationBlobs: "Ficheiros", + verificationBlobsPresent: "presentes", + verificationBlobsReferenced: "referenciados", + verificationBlobsOrphan: "orfos", + verificationBlobsMissing: "ausentes", + verificationBlobsMissingOwn: "Ficheiros teus que faltan neste dispositivo", + verificationBlobsMissingOthers: "Ficheiros doutros habitantes aínda sen descargar", + verificationBlobsUnavailable: "Non se puideron listar os ficheiros deste dispositivo.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Módulo para descubrir e xestionar wikis.", + wikiDescription: "Módulo para descubrir e xestionar wikis.", + wikiCreate: "Crear unha wiki", + wikiUpdate: "Actualizar", + wikiDelete: "Borrar", + wikiEdit: "Editar", + wikiTitleLabel: "Título", + wikiTitlePlaceholder: "Título da wiki...", + wikiBodyLabel: "Contido", + wikiBodyPlaceholder: "Liga outras wikis con [[Título da wiki]] ou [[Título da wiki|un texto propio]].\n\nMenciona un habitante con @id.\n\nEtiqueta con #oasis.\n\nPega ligazóns como https://solarnethub.com\n\nUnha liña en branco abre un parágrafo novo; os saltos de liña simples consérvanse.\n\nAnexa calquera medio compatible e incrustarase aquí.", + formatBarLabel: "Formato", + wikiStatusLabel: "Tipo de wiki", + wikiStatusOpen: "ABERTA", + wikiStatusClosed: "PECHADA", + wikiTagsLabel: "Etiquetas", + wikiTagsPlaceholder: "Introduce etiquetas separadas por comas", + wikiSummaryLabel: "Resumo do cambio", + wikiSummaryPlaceholder: "Que cambiou?", + wikiFilterAll: "TODAS", + wikiFilterMine: "MIÑAS", + wikiFilterRecent: "RECENTES", + wikiFilterLinked: "LIGADAS", + wikiChanges: "Cambios recentes", + wikiNoPages: "Non se atoparon wikis.", + wikiNoChanges: "Aínda non hai cambios.", + wikiHistory: "Historial", + wikiVersion: "Versión", + wikiVersions: "versións", + wikiRestore: "Restaurar", + wikiDiff: "Cambios", + wikiViewVersion: "Ver", + wikiLinkedPages: "Páxinas ligadas", + wikiNoLinkedPages: "Esta páxina aínda non se liga con ningunha outra.", + wikiMissingLink: "A páxina aínda non existe", + wikiDuplicateTitle: "Xa existe unha páxina con este título. Cambia o título para crear unha páxina distinta.", + wikiCreateSectionTitle: "Crear unha wiki", + wikiUpdateSectionTitle: "Actualizar a wiki", + wikiSearchPlaceholder: "Buscar na Wiki...", + wikiCurrentVersion: "Versión actual", + wikiViewingVersion: "Estás vendo unha versión antiga", + wikiBackToPage: "Volver á páxina", + wikiLinkedChip: "Ligada", + wikiRestoredSummary: "Versión restaurada", + wikiExportPdf: "Xerar a páxina", + wikiPreview: "Vista previa", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Crear unha wiki", + tribeWikiEmpty: "Aínda non hai wikis.", + typeWiki: "WIKI", + padsTitle: "Pads", + padTitle: "Pad", + modulesPadsLabel: "Pads", + modulesPadsDescription: "Módulo para xestionar editores de texto colaborativos.", + padCreate: "Crear un pad", + padUpdate: "Actualizar", + padDelete: "Eliminar", + padTitleLabel: "Título", + padTitlePlaceholder: "Escribe o título do pad...", + padStatusLabel: "Estado", + padTypeLabel: "Tipo de pad", + padStatusOpen: "ABERTO", + padStatusInviteOnly: "SÓ CONVIDADOS", + padStatusClosed: "PECHADO", + padDeadlineLabel: "Data límite", + padTagsLabel: "Etiquetas", + padTagsPlaceholder: "Introduce etiquetas separadas por comas", + padMembersLabel: "Membros", + padCreated: "Creado", + padStartEditing: "Editar", + padEditorPlaceholder: "Comeza a escribir...", + padSubmitEntry: "Enviar", + padNoEntries: "Aínda non hai entradas.", + padAllSectionTitle: "Todos os pads", + padMineSectionTitle: "Os meus pads", + padsDescription: "Xestiona editores de texto cifrados e colaborativos na túa rede.", + roomsTitle: "Salas", + roomsDescription: "Descobre e únete a salas de videoconferencia cifradas na túa rede.", + modulesRoomsLabel: "Salas", + modulesRoomsDescription: "Módulo para descubrir e unirse a salas de videoconferencia cifradas.", + roomFilterRecent: "RECENTES", + roomFilterMine: "MIÑAS", + roomFilterAll: "TODAS", + roomFilterLive: "EN DIRECTO", + roomFilterOpen: "ABERTAS", + roomFilterInvite: "SÓ CONVIDADOS", + roomFilterClosed: "PECHADAS", + roomCreate: "Crear sala", + roomUpdate: "Actualizar", + roomDelete: "Eliminar", + roomClose: "Pechar", + roomCreateSectionTitle: "Crear sala", + roomUpdateSectionTitle: "Actualizar sala", + roomTitlePlaceholder: "Título da sala", + roomDescriptionLabel: "Descrición", + roomDescriptionPlaceholder: "De que ides falar?", + roomTypeLabel: "Tipo de sala", + roomTagsLabel: "Etiquetas", + roomStatusOpen: "ABERTA", + roomStatusInviteOnly: "SÓ CONVIDADOS", + roomStatusClosed: "PECHADA", + roomParticipants: "Participantes", + roomJoin: "Entrar", + roomLeave: "Saír", + roomFull: "Sala chea", + roomLiveChip: "EN DIRECTO", + roomMuted: "silenciado", + roomRec: "Gravar audio", + roomRecStop: "Parar a gravación", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} entrou na sala", + roomEventLeft: "{name} saíu da sala", + roomEventRecStart: "{name} comezou a gravar", + roomEventRecStop: "{name} deixou de gravar", + roomEventMuted: "{name} silenciouse", + roomEventUnmuted: "{name} quitou o silencio", + roomEventHand: "{name} ergueu a man", + roomRaiseHand: "Erguer a man", + roomLowerHand: "Baixar a man", + confirmActionTitle: "Confirma esta acción", + confirmActionButton: "Confirmar", + confirmJoinText: "Vas unirte usando esta invitación.", + confirmFollowText: "Vas apoiar a este habitante.", + confirmSignText: "Vas asinar esta campaña.", + confirmDownloadText: "Vas descargar este ficheiro ao teu dispositivo.", + roomRecordings: "Gravacións", + roomClearNotices: "Limpar", + roomLineSwitchboard: "Centraliña", + roomLineDndError: "O número desta sala non acepta chamadas.", + roomNumberHint: "Marca este número desde Teléfono para entrar na sala.", + liveRefresh: "Refrescar", + roomNobody: "Agora mesmo non hai ninguén na sala.", + roomSomeone: "{n} na sala agora mesmo.", + roomYou: "ti", + roomYouAreIn: "Estás dentro", + roomNobodyInside: "Agora mesmo non hai ninguén nesta sala.", + roomErrorFull: "A sala está chea. Poderás entrar cando alguén saia.", + roomErrorUnreachable: "Agora mesmo non se pode chegar á sala. Téntao máis tarde.", + roomErrorBusy: "Xa estás nunha chamada ou noutra sala.", + roomErrorClosed: "Esta sala está pechada.", + roomAccessDenied: "Esta sala é só con convite. Precisas un convite para entrar.", + roomsNoItems: "Non se atoparon salas.", + roomSearchPlaceholder: "Buscar salas...", + roomInviteMode: "Convite", + tribeSectionRooms: "SALAS", + tribeRoomCreate: "Crear sala", + tribeRoomsEmpty: "Aínda non hai salas.", + typeRoom: "SALAS", + typeRoomMember: "PARTICIPANTE DE SALA", + statsRoom: "Salas", + statsRoomMember: "Participantes de salas", + favoritesFilterRooms: "SALAS", + invitesRoomsTitle: "Salas", + inviteCodeInvalid: "Este código de convite non é válido ou xa se usou.", + inviteAlreadyMember: "Xa formas parte. Podes abrilo desde o seu módulo.", + actionFailed: "Non se puido facer. Téntao de novo.", + publishTooShort: "A túa publicación é demasiado curta.", + walletSendFailed: "Non se puido enviar o pagamento: {reason}", + pixeliaCoordsError: "Esas coordenadas están fóra da grella.", + courtsErrTitle: "Cómpre un título.", + courtsErrRespondent: "Indica contra quen é o caso.", + courtsErrRespondentId: "Ese non é un Oasis ID válido.", + courtsErrMethod: "Escolle como se resolverá o caso.", + courtsErrCaseNotFound: "Non se atopou ese caso.", + courtsErrBrief: "Escribe a túa resposta.", + courtsErrStance: "Escolle a túa postura.", + courtsErrResult: "Escribe o resultado.", + courtsErrTerms: "Escribe os termos do acordo.", + courtsErrMediator: "Engade polo menos un mediador.", + courtsErrSelfMediator: "Non podes ser mediador no teu propio caso.", + courtsErrJudge: "Escolle un xuíz.", + courtsErrSelfJudge: "Non podes ser xuíz do teu propio caso.", + courtsErrVisibility: "Escolle quen pode ver o caso.", + courtsErrNomination: "Non se atopou esa nominación.", + opinionAlreadyGiven: "Xa deches a túa opinión.", + cipherPasswordShort: "O contrasinal debe ter polo menos 32 caracteres.", + errorForbidden: "Non tes acceso a isto.", + errorNotFound: "Non se atopou.", + errorClosed: "Está pechado.", + errorInviteOnly: "Isto é só con convite. Precisas un convite.", + errorTooLong: "Ese texto é demasiado longo.", + errorRequired: "Falta información obrigatoria.", + errorInvalid: "Parte da información non é válida.", + errorNotAuthor: "Só quen o creou pode facer isto.", + panicRemoved: "A túa blockchain eliminouse.", + padRecentSectionTitle: "Pads recentes", + padOpenSectionTitle: "Pads abertos", + padClosedSectionTitle: "Pads pechados", + padCreateSectionTitle: "Crear un pad novo", + padUpdateSectionTitle: "Actualizar o pad", + typePad: "PADS", + padNew: "NOVO", + padAddFavorite: "Engadir a favoritos", + padRemoveFavorite: "Quitar de favoritos", + padClose: "Pechar", + padBackToEditor: "Volver ao editor", + padSearchPlaceholder: "Buscar en Pads...", + + calendarsTitle: "Calendarios", + calendarTitle: "Calendario", + modulesCalendarsLabel: "Calendarios", + modulesCalendarsDescription: "Módulo para descubrir e xestionar calendarios.", + typeCalendar: "CALENDARIOS", + calendarCreate: "Crear un calendario", + calendarUpdate: "Actualizar", + calendarTitleLabel: "Título", + calendarTitlePlaceholder: "Título do calendario...", + calendarStatusLabel: "Estado", + calendarTypeLabel: "Tipo de calendario", + calendarStatusOpen: "ABERTO", + calendarStatusClosed: "PECHADO", + calendarDeadlineLabel: "Data límite", + calendarTagsLabel: "Etiquetas", + calendarTagsPlaceholder: "Introduce etiquetas separadas por comas", + calendarParticipantsLabel: "Participantes", + calendarCreated: "Creado", + calendarJoin: "Unirse ao calendario", + calendarGenerateInvite: "Xerar un convite", + calendarDateLabel: "Data", + calendarDatePlaceholder: "Describe esta data...", + calendarNoteLabel: "Notas", + calendarNotePlaceholder: "Engadir unha nota...", + calendarFirstDateLabel: "Data", + calendarFirstNoteLabel: "Notas", + calendarIntervalLabel: "Intervalo", + calendarIntervalNone: "Sen repetición", + calendarIntervalWeekly: "Semanal", + calendarIntervalMonthly: "Mensual", + calendarIntervalYearly: "Anual", + calendarFormDescription: "Descrición", + calendarNoDates: "Non hai datas engadidas.", + calendarNoNotes: "Sen notas.", + calendarsNoItems: "Non se atoparon calendarios.", + calendarsDescription: "Descobre e xestiona calendarios na túa rede.", + calendarMonthPrev: "← Anterior", + calendarMonthNext: "Seguinte →", + calendarMonthNames: "Xaneiro,Febreiro,Marzo,Abril,Maio,Xuño,Xullo,Agosto,Setembro,Outubro,Novembro,Decembro", + calendarDayInitials: "L,M,M,X,V,S,D", + calendarAllSectionTitle: "Calendarios", + calendarRecentSectionTitle: "Calendarios recentes", + calendarFavoritesSectionTitle: "Favoritos", + calendarMineSectionTitle: "Os teus calendarios", + calendarOpenSectionTitle: "Calendarios abertos", + calendarClosedSectionTitle: "Calendarios pechados", + calendarCreateSectionTitle: "Crear un calendario novo", + calendarUpdateSectionTitle: "Actualizar o calendario", + calendarAddFavorite: "Engadir a favoritos", + calendarDeleteNote: "Eliminar", + calendarRemoveFavorite: "Quitar de favoritos", + calendarSearchPlaceholder: "Buscar no calendario...", + calendarAddEntry: "Engadir unha entrada", + statsCalendar: "Calendarios", + statsCalendarDate: "Datas de calendario", + statsCalendarNote: "Notas de calendario", + + modulesChatsLabel: "Chats", + modulesChatsDescription: "Módulo para descubrir e xestionar chats.", + typeChat: "CHATS", + typeChatMessage: "MENSAXE DE CHAT", + chatsTitle: "Chats", + chatMineSectionTitle: "Os teus chats", + chatRecentTitle: "Chats recentes", + chatFavoritesTitle: "Favoritos", + chatOpenTitle: "Chats abertos", + chatClosedTitle: "Chats pechados", + chatDescription: "Descrición", + chatCategory: "Categoría", + chatStatus: "ESTADO", + chatCreate: "Crear un chat", + chatUpdate: "Actualizar", + chatDelete: "Borrar", + chatClose: "Pechar o chat", + chatVisitChat: "VER O CHAT", + chatUntitled: "Chat sen título", + chatNoItems: "Non se atoparon chats.", + chatParticipants: "Participantes", + chatMessagesLabel: "Mensaxes", + chatThreadsLabel: "Fíos", + chatCreatedAt: "CREADO", + chatSearchPlaceholder: "Buscar en Chats...", + chatStatusOpen: "ABERTO", + chatStatusInviteOnly: "SÓ CONVIDADOS", + chatStatusClosed: "PECHADO", + chatSendMessage: "Enviar", + chatJumpLatest: "Última mensaxe", + chatReply: "Responder", + chatReplyCancel: "Cancelar a resposta", + chatPin: "Fixar", + chatUnpin: "Desfixar", + chatPinned: "Fixadas", + chatPickChat: "Escolle un chat para abrilo", + chatNoneYet: "Aínda non hai ningún chat dispoñible.", + activitySearchPlaceholder: "Buscar en Actividade...", + trendingSearchPlaceholder: "Buscar en Tendencias...", + opinionsSearchPlaceholder: "Buscar en Opinións...", + votesSearchPlaceholder: "Buscar en Votacións...", + gamesSearchPlaceholder: "Buscar en Xogos...", + schoolTitle: "Escola", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Editouse unha páxina wiki que segues.", + wikiBotRestoredTitle: "Restaurouse unha versión anterior dunha páxina wiki que segues.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Un estudante novo apuntouse ao teu curso.", + schoolBotInvitedTitle: "Convidáronte a un curso.", + schoolBotAdmittedTitle: "Admitíronte nun curso.", + schoolBotCertificateTitle: "Recibiches un certificado.", + schoolBotPassedTitle: "Un estudante aprobou o teu curso.", + schoolBotLessonTitle: "Publicouse unha lección nova.", + invitesSchoolTitle: "Escola", + schoolEnrollLabel: "Unirse ao curso", + schoolExamRetryLater: "Poderás volver intentalo pasadas 24 horas.", + schoolApproved: "APROBADO", + schoolExamPassMark: "Nota de aprobado (0-10)", + schoolExamPassed: "APROBADO", + schoolExamPending: "Pendente", + schoolExamFailed: "SUSPENSO", + schoolExamAddQuestion: "Engadir unha pregunta", + schoolExamCreateQuestion: "Crear unha pregunta", + schoolExamOption: "Opción", + schoolExamCorrect: "Opción correcta", + schoolExamPoints: "Puntos", + schoolMaterialTextPlaceholder: "Notas para esta lección...", + schoolMaterials: "Materiais", + schoolAddMaterial: "Engadir material", + schoolNoMaterials: "Aínda non hai materiais.", + bankYourSchoolBalance: "Os teus ingresos da escola", + schoolProgress: "Progreso", + schoolProgressMark: "Marcar como completada", + schoolProgressDone: "Completada", + schoolExams: "Exames", + schoolExamCreate: "Crear un exame", + schoolExamTitlePlaceholder: "Título do exame", + schoolExamHelp: "Unha pregunta por liña: Pregunta? ; opción ; opción correcta* ; opción", + schoolExamQuestionsPlaceholder: "Que é SSB? ; Unha base de datos ; Un protocolo P2P* ; Un navegador", + schoolExamSubmit: "Enviar as respostas", + schoolExamScore: "A túa nota", + schoolNoScoresYet: "Aínda non hai resultados.", + schoolLessonUnit: "Unidade / fase", + schoolLessonUnitPlaceholder: "p. ex. Fase 1", + schoolLessonOrder: "Orde", + profileClearnetSchoolLabel: "Escola", + schoolStartDate: "Data de inicio", + schoolSessionDate: "Data da sesión", + schoolCourseChat: "Chat do curso", + agendaFilterSchool: "ESCOLA", + agendaFilterCampaigns: "CAMPAÑAS", + agendaFilterLogistics: "LOXÍSTICA", + schoolCourseType: "Tipo de curso", + schoolTypeOpen: "ABERTO", + schoolTypePaid: "DE PAGAMENTO", + schoolTypeInvite: "SÓ POR CONVITE", + schoolLessonLocked: "Lección cifrada — pídelle a chave do curso ao profesor.", + schoolGrantAccess: "Enviar a chave do curso", + schoolKeySent: "Chave enviada", + schoolOngoing: "En curso", + schoolFilterApplied: "APUNTADO", + schoolFilterOpen: "ABERTOS", + schoolFilterFavorites: "FAVORITOS", + schoolPendingPayment: "Pagamento pendente", + schoolPendingStudents: "Inscricións pendentes", + schoolViewBill: "Ver a factura", + schoolPaymentNote: "Ao inscribirte xérase unha factura en ECO (unha transferencia). A túa inscrición confírmase cando a transferencia queda asinada.", + schoolCloseCourse: "Pechar o curso", + schoolReopenCourse: "Reabrir o curso", + schoolDescription: "Ensina e aprende cursos na túa rede.", + schoolFilterAll: "TODOS", + schoolFilterMine: "MEUS", + schoolFilterTop: "TOP", + schoolFilterRecent: "RECENTES", + schoolCreateButton: "Crear un curso", + schoolCreateSectionTitle: "Crear un curso", + schoolUpdateSectionTitle: "Actualizar o curso", + schoolCourseTitlePlaceholder: "Nome do teu curso", + schoolCourseDescriptionPlaceholder: "Que vas ensinar neste curso?", + schoolCourseImage: "Imaxe do curso (tamaño máx.: 75MB)", + schoolTags: "Etiquetas", + schoolTagsPlaceholder: "Introduce etiquetas separadas por comas", + schoolPrice: "Prezo (ECO)", + schoolVisibility: "Visibilidade", + schoolVisibilityPublic: "Público", + schoolVisibilityInvite: "Por convite", + schoolStatus: "Estado", + schoolClosed: "Pechado", + schoolFree: "Gratis", + schoolCreate: "Crear o curso", + schoolUpdate: "Actualizar o curso", + schoolSearchPlaceholder: "Buscar na Escola...", + schoolSortTitle: "Título", + schoolNoCourses: "Non se atoparon cursos.", + schoolStudents: "Estudantes", + schoolTeacher: "Profesor", + schoolCreatedAt: "CREADO", + schoolLessons: "Leccións", + schoolNoLessons: "Aínda non hai leccións.", + schoolEnrollToSee: "Inscríbete para ver as leccións deste curso.", + schoolEnrollButton: "INSCRIBIRSE", + schoolUnenrollButton: "Abandonar o curso", + schoolInviteRequired: "Este curso é por convite.", + schoolTeacherPanel: "Panel do profesor", + schoolStudentPanel: "Panel do estudante", + schoolAddLesson: "Crear unha lección", + schoolLessonTitlePlaceholder: "Título da lección", + schoolLessonTextPlaceholder: "Describe as nocións que cobre esta lección...", + schoolInviteStudents: "Convidar estudantes", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "CONVIDAR", + schoolInvited: "Convidados", + schoolIssueCertificate: "Crear un certificado", + schoolCertified: "Certificado", + schoolCertificateTextPlaceholder: "Nota do certificado (opcional)", + schoolLessonOpen: "Abrir a lección", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificados", + schoolNoCertificates: "Aínda non se emitiron certificados.", + schoolEditButton: "Editar", + schoolDeleteButton: "Borrar", + typeSchool: "ESCOLA", + schoolCourseLabel: "CURSO", + emergencyLabel: "EMERXENCIA", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Cursos", + statsSchoolLesson: "Leccións", + statsSchoolEnroll: "Inscricións", + statsSchoolCertificate: "Certificados da escola", + modulesSchoolLabel: "Escola", + modulesSchoolDescription: "Módulo para intercambiar coñecemento.", + welcomeStepUxAction: "Gardar vista", + welcomeStepWorkflowTitle: "Que uso de OASIS tes en mente?", + welcomeStepWorkflowText: "Oasis ten moitos módulos. Escolle a configuración que encaixe contigo e só quedarán activados os útiles. Podes axustalo cando queiras en Módulos.", + welcomeStepWorkflowAction: "Gardar configuración", + welcomeWorkflow_full: "Por defecto", + chatRateLimitTitle: "Demasiadas mensaxes", + chatRateLimitMessage: "Alcanzaches o límite de mensaxes que esta sala acepta nunha hora. Téntao máis tarde.", + chatMessagePlaceholder: "Escribe a túa mensaxe...", + chatNoMessages: "Aínda non hai mensaxes.", + chatTitlePlaceholder: "Título do chat", + chatDescriptionPlaceholder: "Descrición do chat", + chatTagsPlaceholder: "Introduce etiquetas separadas por comas", + chatImageLabel: "Selecciona un ficheiro de imaxe (.jpeg, .jpg, .png, .gif)", + chatAuthor: "Autor", + chatAddFavorite: "Engadir a favoritos", + chatRemoveFavorite: "Quitar de favoritos", + chatPM: "MP", + chatStatusLabel: "Estado", + chatTypeLabel: "Tipo de chat", + chatCategoryLabel: "Categoría", + gamesTitle: "Xogos", + gamesDescription: "Descobre e xoga algúns xogos.", + gamesFilterAll: "TODOS", + gamesPlayButton: "XOGAR!", + modulesGamesLabel: "Xogos", + modulesGamesDescription: "Módulo para descubrir e xogar algúns xogos.", + modulesGraphosLabel: "Graphos", + modulesGraphosDescription: "Módulo para explorar a rede como un mapa interactivo de nodos.", + gamesCocolandTitle: "Cocoland", + gamesCocolandDesc: "Un coco con ollos que salta palmeiras e colecciona ECOins. Ata onde podes chegar?", + gamesTheFlowTitle: "ECOinflow", + gamesTheFlowDesc: "Conecta PUBs con habitantes mediante validadores, tendas e acumuladores. Sobrevive á ameaza CBDC!", + gamesNeonInfiltratorTitle: "Neon Infiltrator", + gamesNeonInfiltratorDesc: "Infíltrate na grella, recolle datos confidenciais, esquiva os drons de seguridade e escapa. Cantos niveis podes superar?", + gamesSpaceInvadersTitle: "Space Invaders", + gamesSpaceInvadersDesc: "Detén a invasión alieníxena! Destrúe vagas de invasores antes de que cheguen ao chan.", + gamesArkanoidTitle: "Arkanoid", + gamesArkanoidDesc: "Rompe todos os ladrillos coa túa pa e a pelota. Un clásico desafío arcade.", + gamesPingPongTitle: "PingPong", + gamesPingPongDesc: "Ping-pong clásico contra unha IA. Gaña quen chegue primeiro a 5 puntos.", + gamesAsteroidsTitle: "Asteroids", + gamesAsteroidsDesc: "Pilota a túa nave por un campo de asteroides mortal. Dispáralles antes de que te alcancen.", + gamesRockPaperScissorsTitle: "Pedra, papel, tesoiras", + gamesRockPaperScissorsDesc: "Pedra, papel ou tesoiras contra unha IA. Gaña quen leve dúas de tres roldas.", + gamesTikTakToeTitle: "TikTakToe", + gamesTikTakToeDesc: "Tres en raia clásico contra a IA. Consegue tres en liña para gañar.", + gamesFlipFlopTitle: "FlipFlop", + gamesFlipFlopDesc: "Lanza unha moeda e aposta cara ou cruz. Canta sorte tes?", + games8BallTitle: "8Ball Pool", + games8BallDesc: "Billar visto desde arriba. Preme para apuntar e mantén premido para cargar a potencia. Mete todas as bólas co menor número de tiros.", + gamesArtilleryTitle: "Artillaría", + gamesArtilleryDesc: "Apunta o canón, ten en conta o vento e acerta no branco. 5 roldas: gaña quen faga menos tiros.", + gamesLabyrinthTitle: "Labirinto", + gamesLabyrinthDesc: "Sae do labirinto antes de que se esgoten os movementos. Cada nivel é máis grande e máis difícil.", + gamesCocomanTitle: "Cocoman", + gamesCocomanDesc: "Come todos os puntos e esquiva as pantasmas. As pantasmas móvense en tempo real: non te deteñas!", + gamesTetrisTitle: "Tetris", + gamesAudioPendulumTitle: "Péndulo sonoro", + gamesAudioPendulumDesc: "Simulador de física caótica con síntese de audio en tempo real. As velocidades angulares convértense en frecuencias e os picos, en golpes de percusión. Non hai dúas simulacións que soen igual.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Pincha visuais en directo con dous pratos: escolle un patrón para cada un, axusta velocidade, cor e zoom, mestura entre ambos e lanza escintileos ao ritmo. Marca o tempo ou deixa que o micrófono leve o pulso.", + gamesTetrisDesc: "Os clásicos bloques que caen. Completa liñas para puntuar. Canto tempo aguantarás?", + gamesMinesweeperTitle: "Buscaminas", + gamesMinesweeperDesc: "A clásica caza de minas. Limpa o taboleiro para subir de nivel — grellas máis grandes, máis minas, máis puntuación.", + gamesFilterScoring: "PUNTUACIÓNS", + gamesHallOfFame: "Salón da fama", + gamesHallPlayer: "Xogador", + gamesHallScore: "Puntuación", + gamesNoScores: "Aínda non hai puntuacións.", + chatAccessDenied: "Non tes acceso a este chat. Solicita un convite para acceder ao contido.", + padAccessDenied: "Non tes acceso a este pad. Solicita un convite para acceder ao contido.", + contentAccessDenied: "Non tes acceso a este contido.", + blockAccessRestricted: "Acceso restrinxido", + tribeContentAccessDenied: "Acceso denegado", + tribeContentAccessDeniedMsg: "Este contido pertence a unha tribo. Debes ser membro para acceder.", + tribeViewTribes: "Ver as tribos", + tribeFilesEmpty: "Aínda non hai ficheiros.", + filesLabel: "Ficheiros", + profileClearnetFilesLabel: "Ficheiros", + filesTitle: "Ficheiros", + filesDescription: "Explora e xestiona ficheiros na túa rede.", + fileAllSectionTitle: "Ficheiros", + fileMineSectionTitle: "Os teus ficheiros", + fileRecentSectionTitle: "Ficheiros recentes", + fileTopSectionTitle: "Ficheiros destacados", + fileFavoritesSectionTitle: "Favoritos", + fileFilterAll: "TODOS", + fileFilterMine: "MEUS", + fileFilterRecent: "RECENTES", + fileFilterTop: "DESTACADOS", + fileFilterFavorites: "FAVORITOS", + fileCreateSectionTitle: "Subir un ficheiro", + fileUpdateSectionTitle: "Actualizar o ficheiro", + fileCreateButton: "Subir un ficheiro", + fileUpdateButton: "Actualizar", + fileAddFavoriteButton: "Engadir a favoritos", + fileRemoveFavoriteButton: "Quitar de favoritos", + fileFileLabel: "Selecciona un ficheiro (máx.: 75MB)", + calendarFilterRecent: "RECENTES", + calendarFilterAll: "TODOS", + calendarFilterMine: "MEUS", + calendarFilterFavorites: "FAVORITOS", + calendarFilterOpen: "ABERTOS", + calendarFilterClosed: "PECHADOS", + padFilterRecent: "RECENTES", + padFilterAll: "TODOS", + padFilterMine: "MEUS", + padFilterOpen: "ABERTOS", + padFilterClosed: "PECHADOS", + chatFilterRecent: "RECENTES", + chatFilterAll: "TODOS", + chatFilterMine: "MEUS", + chatFilterFavorites: "FAVORITOS", + chatFilterOpen: "ABERTOS", + chatFilterClosed: "PECHADOS", + licenseLabel: "Licenza", + phoneTitle: "Teléfono", + phoneDescription: "Xestiona as túas videochamadas cifradas de extremo a extremo.", + phoneUnavailable: "As chamadas non están dispoñibles neste dispositivo.", + phoneCallButton: "Chamar", + profileVisibilityPhone: "Número de teléfono", + phoneNumberUnknown: "Ninguén na túa rede ten o número {number}.", + phoneNumberChoose: "Comproba a quen queres chamar ao {number}:", + phoneAnswer: "Contestar", + phoneSpeaker: "Altofalante", + phoneEarpiece: "Auricular", + phoneReject: "Rexeitar", + phoneHangup: "Colgar", + phoneMute: "Silenciar", + phoneUnmute: "Activar micrófono", + phoneCameraOn: "Cámara on", + phoneCameraOff: "Cámara off", + settingsPhoneVideo: "Vídeo", + settingsPhoneVideoOn: "Permitido en chamadas e salas", + settingsPhoneVideoOff: "Só audio", + phoneSilence: "Non oír", + phoneUnsilence: "Volver oír", + phoneCalling: "Chamando a", + phoneIsCalling: "está a chamarte", + notifyNewPmFrom: "Nova MP de", + notifyNewPamFrom: "Novo PaM de", + notifyUpdateLabel: "Actualización", + updateInstalledLabel: "Versión instalada", + updateAvailableLabel: "Versión dispoñible", + updateChangesLabel: "Cambios", + updateFilesLabel: "Ficheiros", + updateOriginLabel: "Orixe", + phoneRecording: "Gravando PaM para", + phoneLeaveMessage: "Ninguén contesta: deixa a túa mensaxe despois do sinal.", + phoneLeaveMessageBusy: "Está noutra chamada: deixa a túa mensaxe despois do sinal.", + phoneLeaveMessageUnreachable: "Non se puido contactar: deixa a túa mensaxe despois do sinal.", + phoneConnecting: "Conectando con", + phoneSendPam: "Enviar PaM", + phoneCancel: "Cancelar", + phoneSearchPlaceholder: "Buscar en Teléfono...", + phonePamTitle: "Gravacións de audio", + phoneNew: "Novo", + phoneDelete: "Borrar", + phoneNoPams: "Non tes gravacións de audio.", + phoneHistoryTitle: "Historial", + phoneLatestTitle: "Últimas chamadas", + phoneCreateButton: "Nova chamada", + phoneLimitsHint: "Ata 7 habitantes por chamada; con máis dun é unha chamada conxunta.", + phoneErrorPub: "Non se pode chamar a un pub.", + phoneJointCall: "Chamada conxunta", + phoneWith: "Con", + phoneResult: "Resultado", + phoneWhen: "Cando", + phoneDuration: "Duración", + phoneOutgoing: "Saínte", + phoneIncoming: "Entrante", + phoneNoHistory: "Aínda non hai rexistros.", + phoneOutcomeEnded: "Atendida", + phoneOutcomeMissed: "Perdida", + phoneOutcomeRejected: "Rexeitada", + phoneOutcomeNoanswer: "Sen resposta", + phoneOutcomeCancelled: "Cancelada", + phoneOutcomePam: "PaM enviado", + phoneOutcomeFailed: "Fallida", + phoneDialAdd: "Engadir", + phoneDialBack: "Borrar", + phoneDialClear: "Limpar", + phoneDialPlaceholder: "Número ou Oasis ID", + phoneDialInhabitants: "Habitantes", + phoneDialRemove: "Quitar", + phoneOwnNumber: "Ese é o teu propio número.", + phoneSettingsDesc: "Escolle quen pode chamarte.", + phoneDnd: "Non molestar", + phoneOpen: "Aberto", + phoneRecipientRefuses: "O habitante ao que intentas chamar non permite recibir chamadas.", + phoneRefusedNotice: "Estes habitantes non permiten recibir chamadas e non se lles chamou:", + fileEmptyError: "O ficheiro está baleiro. Escolle outro ficheiro.", + fileTitleLabel: "Título", + fileTitlePlaceholder: "Título", + fileDescriptionLabel: "Descrición", + fileDescriptionPlaceholder: "Descrición", + fileTagsLabel: "Etiquetas", + fileTagsPlaceholder: "Introduce etiquetas separadas por comas", + fileSizeLabel: "Tamaño", + fileDownloadButton: "DESCARGA", + fileDetailsButton: "DETALLES", + fileNoFile: "Sen ficheiro ficheiro", + fileSearchPlaceholder: "Buscar en Ficheiros...", + fileSearchButton: "Buscar", + fileSortRecent: "Máis recentes", + fileSortOldest: "Máis antigos", + fileSortTop: "Máis votados", + fileNoMatch: "Non se atoparon ficheiros coincidentes.", + fileUpdatedAt: "Actualizado", + typeFile: "FICHEIROS", + modulesFilesLabel: "Ficheiros", + modulesFilesDescription: "Módulo para descubrir e xestionar ficheiros.", + tribeSectionFiles: "FICHEIROS", + fileDescription: "Descrición", + fileDownload: "Descargar", + fileTypeLabel: "Tipo", + torrentsTitle: "Torrents", + torrentsDescription: "Explora e xestiona torrents na túa rede.", + torrentAllSectionTitle: "Torrents", + torrentMineSectionTitle: "Os teus torrents", + torrentRecentSectionTitle: "Torrents recentes", + torrentTopSectionTitle: "Torrents destacados", + torrentFavoritesSectionTitle: "Favoritos", + torrentFilterAll: "TODOS", + torrentFilterMine: "MEUS", + torrentFilterRecent: "RECENTES", + torrentFilterTop: "DESTACADOS", + torrentFilterFavorites: "FAVORITOS", + torrentCreateSectionTitle: "Subir un torrent", + torrentUpdateSectionTitle: "Actualizar o torrent", + torrentCreateButton: "Subir un torrent", + torrentUpdateButton: "Actualizar", + torrentDeleteButton: "Eliminar", + torrentAddFavoriteButton: "Engadir a favoritos", + torrentRemoveFavoriteButton: "Quitar de favoritos", + torrentFileLabel: "Seleccionar un ficheiro torrent (.torrent)", + torrentTitleLabel: "Título", + torrentTitlePlaceholder: "Título", + torrentDescriptionLabel: "Descrición", + torrentDescriptionPlaceholder: "Descrición", + torrentTagsLabel: "Etiquetas", + torrentTagsPlaceholder: "Introduce etiquetas separadas por comas", + torrentSizeLabel: "Tamaño", + torrentFromContent: "Xerar torrent", + torrentFromContentMissing: "O ficheiro aínda non está dispoñible neste nodo. Téntao máis tarde.", + torrentDownloads: "Descargas", + torrentFetchOasis: "Descargar en Oasis", + torrentSaveFile: "Gardar ficheiro", + torrentSeeds: "sementes", + seedsLabel: "Sementes", + seedAction: "Apoiar", + torrentSourceFile: "Ficheiro", + torrentStateQueued: "En cola", + torrentStateProbing: "Buscando sementes", + torrentStateDownloading: "Descargando", + torrentStateDone: "Completada", + torrentStateFailed: "Fallida", + torrentStateNoSource: "Sen sementes conectadas, agardando", + torrentStateCancelled: "Cancelada", + torrentNoDownloads: "Aínda non hai descargas.", + torrentOriginalSection: "Orixinal", + torrentCancel: "Cancelar", + torrentDownloadButton: "DESCARGA", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETALLES", + torrentNoFile: "Sen ficheiro torrent", + noTorrents: "Non se atoparon torrents.", + noFiles: "Non se atoparon ficheiros.", + torrentSearchPlaceholder: "Buscar en Torrents...", + torrentSearchButton: "Buscar", + torrentSortRecent: "Máis recentes", + torrentSortOldest: "Máis antigos", + torrentSortTop: "Máis votados", + torrentNoMatch: "Non se atoparon torrents coincidentes.", + torrentUpdatedAt: "Actualizado", + statsTorrent: "Torrents", + statsFile: "Ficheiros", + typeTorrent: "TORRENTS", + modulesTorrentsLabel: "Torrents", + modulesTorrentsDescription: "Módulo para descubrir e xestionar torrents.", + favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "FICHEIROS", + favoritesFilterMarket: "MERCADO", + favoritesFilterEvents: "EVENTOS", + favoritesFilterForum: "FORO", + favoritesFilterHousing: "VIVENDA", + favoritesFilterJobs: "EMPREGOS", + favoritesFilterProjects: "PROXECTOS", + favoritesFilterReports: "INFORMES", + favoritesFilterTasks: "TAREFAS", + favoritesFilterTransfers: "TRANSFERENCIAS", + favoritesFilterVotes: "VOTACIÓNS", + favoritesFilterShopProducts: "ARTIGOS DE TENDA", + tribeSectionTorrents: "TORRENTS", + tribeCreateTorrent: "Subir un torrent", + tribeCreateFile: "Subir ficheiro", + settingsWishTitle: "Desexo", + settingsWishDesc: "Determina como accedes a todo o contido e a túa experiencia na rede Oasis.", + settingsWishWhole: "Multiverso", + settingsWishMutuals: "Só apoio mutuo", + settingsWishOnlyLan: "Só LAN", + settingsWishLocal: "Local", + settingsPmVisibilityTitle: "Mensaxes privadas", + settingsPmVisibilityDesc: "Configura o nivel de exposición a mensaxes privadas que queres ter na rede.", + settingsPmVisibilityWhole: "Multiverso", + settingsPmVisibilityMutuals: "Só apoio mutuo", + pmMutualNotice: "O envío é unha barreira de UX. A recepción é un filtro de atención (o cifrado xa está replicado).", + inhabitantsPendingFollowsTitle: "Solicitudes de apoio pendentes", + inhabitantsPendingAccept: "Aceptar", + inhabitantsPendingReject: "Rexeitar", + tribeSectionGovernance: "GOBERNO", + tribePadInviteRequired: "Non tes acceso ao pad. Pide un convite para acceder ao contido.", + tribeChatInviteRequired: "Non tes acceso ao chat. Pide un convite para acceder ao contido.", + tribeGovernanceDesc: "Goberno interno desta tribo.", + tribeGovernanceNoGov: "Sen goberno activo", + tribeGovernanceNoGovDesc: "Esta tribo aínda non ten goberno. Propón candidaturas para iniciar o proceso.", + tribeGovCardTitle: "Goberno actual", + tribeGovCycleSince: "INICIO DO CICLO", + tribeGovCycleEnd: "FIN DO CICLO", + tribeGovTimeRemaining: "TEMPO RESTANTE", + tribeGovPopulation: "POBOACIÓN", + tribeGovMethod: "MÉTODO", + tribeGovVotesReceived: "VOTOS RECIBIDOS", + tribeGovLeader: "LÍDER", + tribeGovFilterGovernment: "GOBERNO", + tribeGovFilterCandidatures: "CANDIDATURAS", + tribeGovFilterLaws: "LEIS", + tribeGovCandidatureId: "Candidatura", + tribeGovCandidatureMethod: "Método", + tribeGovCandidatureProposeBtn: "Publicar a candidatura", + tribeGovRuleTitle: "Título da regra", + tribeGovRuleBody: "Corpo da regra", + tribeGovProposals: "PROPOSTAS", + tribeGovRevocations: "REVOGACIÓNS", + tribeGovHistorical: "HISTÓRICO", + tribeGovRules: "REGRAS", + tribeGovernanceAlreadyPublished: "Esta tribo xa ten unha candidatura aberta no ciclo actual do parlamento global.", + tribeGovernanceProposeInternal: "Propoñer unha candidatura interna", + tribeGovernanceInternalCandidatures: "Candidaturas internas", + tribeGovernanceNoCandidatures: "Non hai candidaturas abertas.", + tribeGovernanceAddRule: "Engadir unha regra", + tribeGovernanceNoRules: "Aínda non hai regras.", + tribeGovernanceNoLeaders: "Aínda non hai líderes elixidos.", + tribeGovernanceComingSoon: "Proximamente no módulo de goberno desta tribo.", + tribeGovNoProposals: "Aínda non hai ningunha proposta", + tribeGovNoLaws: "Aínda non hai ningunha lei", + tribeGovNoRevocations: "Aínda non hai ningunha revogación", + tribeGovNoHistorical: "Aínda non hai ningún rexistro", + logsTitle: "Bitácora", + logsDescription: "Leva un diario privado das túas experiencias.", + logsViewTitle: "Bitácora", + logsColumnType: "Tipo", + logsView: "Ver", + logsUpdateButton: "Actualizar", + logsEditTitle: "Actualizar o rexistro", + logsCreateTitle: "Crear unha entrada", + logsWriteButton: "Escribir", + logsTextPlaceholder: "Describe as túas experiencias...", + logsModeAIWritten: "Asistente de IA", + logsModeManual: "Manual", + logsModeAI: "IA", + logsColumnLog: "Bitácora", + logsColumnDate: "Data", + logsDelete: "Eliminar", + logsEdit: "Actualizar", + logsFilterAlways: "SEMPRE", + logsFilterYear: "ÚLTIMO ANO", + logsFilterMonth: "ÚLTIMO MES", + logsFilterWeek: "ÚLTIMA SEMANA", + logsFilterToday: "HOXE", + logsComments: "Comentarios", + logsAuthorEntries: "Entradas", + logsCreate: "Crear unha entrada", + logsExport: "Exportar a bitácora", + logsExportOne: "Exportar o rexistro", + logsViewDetails: "Ver os detalles", + logsGenerateButton: "Xerar o texto", + logsSearchText: "Buscar na Bitácora...", + logsSearchAnyType: "Calquera tipo", + logsSearchButton: "Buscar", + logsEmpty: "Aínda non hai entradas.", + logsNothingNew: "Non hai accións novas que rexistrar.", + modulesLogsLabel: "Bitácora", + modulesLogsDescription: "Módulo para levar un diario privado das túas experiencias.", + statsLogsTitle: "Bitácora", + typeLog: "BITÁCORA", + + larpTitle: "L.A.R.P.", + larpDescription: "Unha capa de xogo de rol en vivo para a experimentación colaborativa.", + larpNoHousesMatch: "Ningunha casa coincide coa túa busca.", + larpSearchPlaceholder: "Buscar en L.A.R.P....", + larpAllHouses: "Casas:", + larpFilterRuling: "GOBERNA", + larpFilterHouses: "CASAS", + larpFilterRules: "FAQ", + larpRulesTitle: "FAQ do L.A.R.P.", + larpRulesEntryText: "Todo habitante comeza por defecto en ACADEMIA. Desde ACADEMIA, escolle unha casa no panel \"Unirse a unha casa\": isto abre o seu test de entrada. Responde as 10 preguntas e envía. Se superas o limiar (7/10), serás admitido automaticamente nesa casa; se non, serás rexeitado e permanecerás en ACADEMIA. En calquera caso o sistema rexistra o teu OasisID e o ciclo do intento, e impide outro intento ata que pase o período de espera de 30 ciclos.", + larpRulesGovernanceText: "Durante o seu ciclo, a casa gobernante luce a insignia \"Goberna\" e é a única cuxo Muro se amosa publicamente na lapela GOBERNA.", + larpRulesWallText: "Cada casa ten un Muro onde escriben os seus membros. O Muro dunha casa é privado para a casa, agás o da casa gobernante e o da ACADEMIA, que pode ler calquera.", + larpRulesSignText: "Os habitantes poden activalo (en /profile/edit > Sensores) para amosar a imaxe da súa casa actual como un selo no seu perfil, na súa tarxeta de autor e de habitante. O selo liga á páxina da casa.", + larpPostsTitle: "Muro", + larpPostsEmpty: "Aínda non hai publicacións.", + larpPostPlaceholder: "Que ten que dicir esta casa?", + larpPostSubmit: "Publicar", + larpCycleLabel: "Ciclo:", + bankRulesArchTaxFormula: "Taxa ARCH(usuario) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", + bankRulesArchTaxNote: "O imposto ARCH reflicte o custo a longo prazo de facer medrar e manter o arquivo da rede: canto máis antigos sexan os teus datos na rede, maior será a túa parte do custo de mantemento.", + bankRulesChipFormula: "band = ratio / reducer, onde ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). alto ≥ 0.66, medio ≥ 0.33, noutro caso baixo.", + bankRulesChipNote: "O chip de cada elemento colórese respecto á mensaxe máis grande vista na rede e suavízase segundo o número de habitantes activos — unha rede máis grande reparte a carga e empuxa máis elementos cara á banda verde.", + bankRulesEcoTaxFormula: "Taxa ECO(usuario) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", + bankRulesEcoTaxNote: "O imposto ECO dedúcese da RBU bruta cando se calcula a asignación da época. Os ECO deducidos non se redistribúen nesta época — quedan no saldo do pub e alimentan o fondo da próxima época a través dos límites reserveMin / alpha.", + bankRulesKarmaFormula: "karma = log10(1 + actions) + reputation", + bankRulesKarmaNote: "O karma derívase das túas accións (publicacións, votos, participación no parlamento/tribunais, etc.) cun factor de decaemento temporal, menos os gramos de carbono que xera o teu feed.", + bankRulesNetFormula: "net_UBI = floor + max(0, gross_UBI − floor) − ECO Tax − ARCH Tax", + bankRulesPoolFormula: "pool = min(pubBal − reserveMin, capPerEpoch, alpha · pubBal)", + bankRulesShareFormula: "share = clamp(1 + karma/100, w_min, w_max) / sum(weights)", + bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", + bankTaxesArchTaxFootprintNote: "O imposto ARCH reflicte o custo de facer medrar e manter o arquivo da rede. Calcúlase a partir do tempo transcorrido entre o primeiro bloque que publicaches e o bloque máis novo da rede — canto máis tempo viva a túa historia no arquivo, maior será a túa parte do custo de mantemento.", + bankTaxesArchTaxNoteBold: "reducir directamente o imposto ARCH", + bankTaxesArchTaxNoteIntro: "O teu imposto ARCH dedúcese do excedente que a rede xera por riba da túa RBU; o valor base da RBU é un mínimo inamovible. O imposto nunca se deduce do importe fixo que lle corresponde a cada habitante como RBU. Os fondos deducidos alimentan o algoritmo de redistribución da riqueza e canalízanse cara a outros habitantes a través das súas reclamacións de RBU. Os habitantes máis novos e os que se dediquen especificamente a tarefas de arquivo, replicación e mantemento probablemente teñan algunha tarefa dedicada a manter san o arquivo da rede no teu nome. Ou poden dedicarse a ", + bankTaxesArchTaxNoteOutro: " mediante acordos de poda, deduplicación de datos redundantes e infraestrutura de mantemento compartida, e o consenso da rede pode requirir a súa contribución continua.", + bankTaxesUserNoteChips: "O chip de imposto ECO que se amosa xunto a cada elemento cambia de cor (verde / amarelo / vermello) segundo o tamaño do elemento en relación co bloque máis grande visto na rede, suavizado polo número de habitantes activos — unha rede solar-punk máis grande reparte a carga e empuxa máis elementos cara á banda verde.", + bankTaxesUserNoteOtherParams: "Polo de agora o imposto ECO derívase da pegada de carbono de cada bloque, pero co tempo pode incorporar outros parámetros — enerxía gastada na replicación, almacenamento redundante entre nodos, largo de banda dos blobs, custo computacional do descifrado, pegada de minaría das transaccións asociadas ou calquera outra carga medible que a rede acorde valorar.", + gamesHallDate: "Data", + gamesNewRecord: "Novo récord!", + indexingRefreshNote: "Esta páxina recárgase cada 10 segundos.", + indexingMessage: "Oasis está sincronizando a rede de nodos distribuída. Por favor, agarda.", + indexingTitle: "Sincronizando", + padsNoItems: "Aínda non hai pads.", + padVersionHistory: "Historial", + padAttributionView: "Por autora", + padVersionView: "Ver a versión", + statsChat: "Chats", + statsChatMessage: "Mensaxes de chat", + statsGameScore: "Xogos", + statsWiki: "Páxinas wiki", + statsPad: "Pads", + statsPadEntry: "Entradas de pad", + typeGameScore: "PUNTUACIÓNS", + bankTaxesLookupNote: "Introduce un ID de bloque para buscalo no teu feed local.", + bankTaxesUserNoteChipsClick: "Preme en calquera chip para inspeccionar o seu bloque no blockexplorer.", + mapUrlPlaceholder: "/maps/MAP_ID", + larpRulesInviteEntryText: "Os membros das casas poden xerar códigos de convite que dan acceso directo á casa.", + larpRulesOneHouseText: "Só podes pertencer a unha casa á vez. Se non pertences a ningunha, estás en ACADEMIA. Cada páxina de casa (agás ACADEMIA) amosa aos seus membros un botón \"Saír da casa\" que devolve a pertenza a ACADEMIA. Saír NON anula o período de espera do test.", + larpRulesTestEntryText: "No test WILL cada opción pondera unha ou máis casas; ao envialo, o sistema suma as puntuacións e admíteche automaticamente na casa con máis puntos.", + larpWillTestHint: "Unha vez completado, asignaráseche a casa que máis se axuste ás túas respostas. As túas respostas individuais procésanse localmente e nunca se almacenan — só se publica no teu feed a asignación de casa resultante.", + larpWillTestTitle: "Test WILL", + settingsLanDesc: "Anuncia periodicamente este nodo a outras instancias de Oasis na mesma rede local. Desactívao para deter as difusións UDP.", + settingsLanEnable: "Activar a difusión LAN", + settingsLanTitle: "Difusión LAN", + settingsReplicationTitle: "Replicación", + settingsReplicationDesc: "Configura o número de saltos que o teu nodo segue desde o teu propio feed ao replicar contido.", + settingsReplicationHopsLabel: "Saltos", + settingsBlobCacheTitle: "Caché de medios", + settingsBlobCacheDesc: "O contido doutros habitantes gárdase neste dispositivo. Escolle canto espazo pode ocupar.", + settingsBlobCacheUsage: "Uso actual", + settingsBlobCacheFiles: "ficheiros", + settingsBlobCacheLimit: "Límite", + settingsBlobCacheUnlimited: "Sen límite", + settingsBlobCacheCleanNow: "Limpar agora", + settingsBlobCacheCleaned: "{n} ficheiros eliminados, {size} liberados.", + aiApproveTagsLabel: "Etiquetas (separadas por comas)", + aiApproveTagsPlaceholder: "p. ex. oasis, gobernanza, ecoloxía", + aiApproveRatingLabel: "Valoración", + aiExchangeLang: "Idioma", + aiExchangeTags: "Etiquetas", + aiExchangeRating: "Valoración", + aiExchangeHelpful: "Útil", + aiExchangeMarkHelpful: "+1 útil", + larpCyclesUntilRuling: "Próximo ciclo de goberno", + larpVisitTribe: "Visitar a tribo", + larpStartJourney: "Comezar a viaxe", + larpGoverning: "Goberna:", + larpStartHere: "Comeza aquí:", + larpMyHouse: "A miña casa:", + larpMembersCount: "Membros", + larpRolesLabel: "Roles", + larpFunctionLabel: "Función", + larpMonthLabel: "Ciclo de goberno", + larpLeaveToAcademia: "Volver á ACADEMIA", + larpAcademiaJoinTitle: "Casas L.A.R.P.", + larpInviteRedeem: "Unirse á casa", + larpInviteCreate: "Xerar un código de convite", + larpInvitePlaceholder: "Código de convite", + larpInviteBannerTitle: "Novo código de convite", + larpInviteBannerHint: "Comparte este código con alguén que estea en ACADEMIA. Caduca en 30 ciclos ou tras un uso.", + larpTestRankingTitle: "Puntuación por casa", + invitesHousesTitle: "L.A.R.P.", + ecoTaxLabel: "Taxa ECO", + bankTaxes: "Impostos", + bankOverviewYourTaxes: "Impostos totais", + bankTaxesEcoTaxTitle: "Taxa ECO", + bankTaxesArchTaxTitle: "Taxa ARCH", + bankTaxesUserAmount: "A túa taxa ECO (importe que devolver)", + bankTaxesArchTaxUserAmount: "A túa taxa ARCH (importe que devolver)", + bankTaxesArchTaxFirstBlock: "Idade do teu primeiro bloque", + bankTaxesArchTaxRate: "Tipo", + bankTaxesTotalBlocks: "Bloques totais", + bankTaxesTotalBytes: "Bytes totais", + bankTaxesTotalCarbon: "Carbono total", + bankTaxesTotalEcoin: "Taxa ECO total (toda a vida)", + bankTaxesEcoTaxLifetime: "Taxa ECO (toda a vida)", + bankTaxesAnnualEcoin: "Taxa ECO (anual)", + bankTaxesMonthlyEcoin: "Taxa ECO (mensual)", + bankTaxesArchTaxLifetime: "Taxa ARCH (toda a vida)", + bankTaxesArchTaxAnnual: "Taxa ARCH (anual)", + bankTaxesArchTaxMonthly: "Taxa ARCH (mensual)", + bankTaxesTotalLifetime: "Total (toda a vida)", + bankTaxesTotalAnnual: "Total (anual)", + bankTaxesTotalMonthly: "Total (mensual)", + bankTaxesRate: "Tipo", + bankTaxesSpan: "Período de mostraxe", + bankTaxesLookupTitle: "Inspeccionar un bloque", + bankTaxesLookupPlaceholder: "ID do bloque (p. ex. %abc...=.sha256)", + bankTaxesLookupButton: "Inspeccionar", + bankTaxesLookupNotFound: "Non se atopou ningún bloque con ese ID no teu feed local.", + bankTaxesLookupAuthor: "Autor", + bankTaxesLookupType: "Tipo", + bankTaxesLookupSize: "Tamaño", + bankTaxesLookupCarbon: "Pegada de carbono", + bankTaxesLookupEcoin: "Taxa ECO", + bankExchangeEcoTaxAnnual: "Impostos de ECOin (anual)", + bankExchangeEcoTaxMonthly: "Impostos de ECOin (mensual)", + bankTaxesTypesLabel: "Selecciona que impostos queres pagar", + bankTaxesTypesApply: "Establecer os meus impostos", + bankRulesPoolTitle: "Fondo mensual", + bankRulesShareTitle: "Reparto por usuario", + bankRulesKarmaTitle: "Karma (puntuación de implicación)", + bankRulesEcoTaxTitle: "Taxa ECO", + bankRulesArchTaxTitle: "Taxa ARCH", + bankRulesChipTitle: "Banda de cor do chip da taxa ECO", + bankRulesEpochKind: "Granularidade da época", + bankRulesAlpha: "Alpha (cota máx. do saldo do pub por época)", + bankRulesReserveMin: "Reserva mínima (mantense no saldo do pub)", + bankRulesCapPerEpoch: "Límite por época (absoluto)", + bankRulesGraceDays: "Período de graza da reclamación", + bankRulesWMin: "Peso mínimo (w_min)", + bankRulesWMax: "Peso máximo (w_max)", + bankRulesFloor: "Mínimo por usuario (bruto)", + bankRulesCapUser: "Límite por usuario e época", + bankRulesEcoTaxRate: "Tipo", + bankRulesArchTaxRate: "Tipo", + bankRulesCarbonFactor: "Factor de carbono", + statsTombstoneEmpty: "Aínda non hai borrados (tombstones) na rede.", + blockchainBlockNotAvailable: "Este bloque non está no teu feed local. Pode pertencer a un nodo que aínda non replicas.", + blockchainBackToBlockexplorer: "Volver a Blockexplorer", + audioFilterBcs: "BCS", + audioTranscodeButton: "TRANSCODIFICAR", + audioTranscodeDetailTitle: "Transcodificar", + audioTranscodeDetailDescription: "Descodifica a carga útil incrustada e o mapa de composición orixinal da blockchain.", + audioTranscodeStegoTimestamp: "Xerado o", + audioTranscodeStegoMessage: "TEXTO", + audioTranscodeStegoEmpty: "(ningún)", + audioTranscodeStegoUnknown: "—", + audioTranscodeStegoNotFound: "Non se puido descodificar ningunha carga esteganográfica neste audio.", + audioTranscodeCompositionEmpty: "Este audio non inclúe ningunha composición de blockchain gardada.", + audioBackToBcs: "Volver a BCS", + melodyCompositionTitle: "Mapa de composición da blockchain", + melodyFilterMine: "MIÑAS", + melodyAllEmpty: "Aínda non hai composicións BCS doutros habitantes.", + melodyNoteUnavailable: "o bloque non está no teu feed local", + melodyUploadBcsTitle: "Publicar", + melodyUploadBcsButton: "Publicar", + melodyUploadStegoLabel: "Mensaxe oculta (esteganografía)", + melodyUploadStegoMaxLabel: "máx. 280 caracteres", + melodyUploadStegoPlaceholder: "Opcional.", + melodyUploadBcsNameNote: "O audio publicarase co nome xerado automaticamente", + larpLastAttemptTitle: "O teu último intento", + larpLastAttemptHouse: "Casa", + larpLastAttemptWhen: "Cando", + larpLastAttemptCooldown: "Próximo intento en", + larpTestTitle: "Test de entrada", + larpTestIntro: "Responde as 10 preguntas. Necesitas polo menos 7 respostas correctas para seres admitido. Podes intentar un test cada 30 ciclos sexa cal sexa o resultado.", + larpTestSubmit: "Unirse á casa", + larpTestCooldownActive: "Próximo test dispoñible en", + larpTestCooldownDays: "ciclos", + larpTestResultTitle: "Resultado do test", + larpTestScore: "Puntuación", + larpTestNextAttempt: "Podes intentar outro test en 30 ciclos.", + larpGoToHouse: "Ir á túa casa", + larpBackToAcademia: "Volver á ACADEMIA", + larpBadgeYou: "Ti", + larpBadgeRuling: "Goberna", + modulesLarpLabel: "L.A.R.P.", + modulesLarpDescription: "Módulo para a capa de xogo de rol en vivo.", + larpProfileQ1: "Cando te enfrontas a un problema complexo, que fas primeiro?", + larpProfileQ1O1: "Falar con outras persoas para buscar consenso", + larpProfileQ1O2: "Construír un prototipo para probalo", + larpProfileQ1O3: "Investigar a bibliografía", + larpProfileQ1O4: "Sabotear o sistema que o provoca", + larpProfileQ2: "Que lle dá sentido ao teu traballo diario?", + larpProfileQ2O1: "Defender os habitantes que quero", + larpProfileQ2O2: "Crear algo tanxible", + larpProfileQ2O3: "Sandar ou coidar a vida", + larpProfileQ2O4: "Traballar con palabras e ideas", + larpProfileQ2O5: "Facer rir os demais", + larpProfileQ3: "Cando xorde un conflito no teu grupo, ti…", + larpProfileQ3O1: "Lideras o diálogo", + larpProfileQ3O2: "Tomas partido e mantéñeste firme", + larpProfileQ3O3: "Soltas unha broma para rebaixar a tensión", + larpProfileQ3O4: "Cuestionas se o conflito é real", + larpProfileQ3O5: "Buscas as causas ecolóxicas de raíz", + larpProfileQ4: "O teu proxecto favorito a longo prazo sería…", + larpProfileQ4O1: "Construír unha cidade", + larpProfileQ4O2: "Restaurar un bosque", + larpProfileQ4O3: "Redactar unha constitución", + larpProfileQ4O4: "Organizar un festival", + larpProfileQ4O5: "Montar unha rede de defensa", + larpProfileQ4O6: "Deseñar unha máquina nova", + larpProfileQ4O7: "Coidar un arquivo", + larpProfileQ4O8: "Desmontar unha orde inxusta", + larpProfileQ5: "Que fai unha boa líder?", + larpProfileQ5O1: "Alguén que escoita e media", + larpProfileQ5O2: "Alguén capaz de loitar e protexer", + larpProfileQ5O3: "Alguén que coñece a historia", + larpProfileQ5O4: "Alguén que te fai sorrir", + larpProfileQ6: "A túa relación coas regras:", + larpProfileQ6O1: "As regras xorden do diálogo e da lei", + larpProfileQ6O2: "As regras débense cumprir estritamente", + larpProfileQ6O3: "As regras hai que rompelas a miúdo", + larpProfileQ6O4: "As regras deben servir á vida", + larpProfileQ6O5: "As regras erguen infraestruturas sólidas", + larpProfileQ7: "Como xestionas a información?", + larpProfileQ7O1: "Coido dela e presérvoa", + larpProfileQ7O2: "Compártoa a través de historias", + larpProfileQ7O3: "Cuestiono as súas orixes", + larpProfileQ7O4: "Extraio o útil", + larpProfileQ7O5: "Úsoa para sandar", + larpProfileQ8: "A túa idea de éxito é…", + larpProfileQ8O1: "Unha máquina que funciona", + larpProfileQ8O2: "Unha comunidade en paz", + larpProfileQ8O3: "Un festival animado", + larpProfileQ8O4: "Unha familia segura", + larpProfileQ8O5: "Un arquivo intacto", + larpProfileQ8O6: "Un dogma resgado", + larpProfileQ8O7: "Unha colleita próspera", + larpProfileQ8O8: "Un edificio rematado", + larpProfileQ9: "Ao erguerte, o que queres é…", + larpProfileQ9O1: "Adestrar o corpo", + larpProfileQ9O2: "Ler ou escribir", + larpProfileQ9O3: "Cultivar ou cociñar", + larpProfileQ9O4: "Argallar con algo", + larpProfileQ9O5: "Cuestionar a autoridade", + larpProfileQ9O6: "Planificar un proxecto", + larpProfileQ9O7: "Falar coas túas amigas", + larpProfileQ9O8: "Facer arte", + larpProfileQ10: "A túa debilidade podería ser:", + larpProfileQ10O1: "Falar demasiado", + larpProfileQ10O2: "Ser demasiado pragmática", + larpProfileQ10O3: "Ser demasiado idealista", + larpProfileQ10O4: "Ser demasiado disruptiva", + larpProfileQ10O5: "Ser demasiado ríxida", + larpProfileQ10O6: "Ser demasiado despreocupada", + larpProfileQ10O7: "Ser demasiado cautelosa", + larpProfileQ10O8: "Ser demasiado ambiciosa", + uxModeTitle: "UX", + uxModeDescription: "Selecciona que modo de navegación UX queres para a túa interface.", + uxModeMenus: "Bloques", + uxModeChats: "Conversas", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Etiquetas en tendencia", + feedActiveTitle: "Habitantes activos", + cnBrandSub: "Libre · P2P · Federada", + cnSyncedPeers: "Nodos sincronizados", + cnLicense: "Licenza", + cnNotAccessible: "O contido non está accesible neste momento.", + cnUntitled: "Sen título", + cnSearchPlaceholder: "Buscar…", + cnHubTitle: "HUB Clearnet", + cnHubDescription: "Contido público compartido polos habitantes desta rede Oasis.", + cnHubEmpty: "Aínda non se compartiu contido público na Clearnet.", + cnPublicContent: "Contido", + cnTribesContent: "Tribos", + cnNetworkContent: "Rede", + cnCategoryEmpty: "Non hai contido nesta categoría.", + cnInhabitantEmpty: "Este habitante aínda non publicou contido na Clearnet.", + cnPrevPage: "← Anterior", + cnNextPage: "Seguinte →", + cnPageOf: "Páxina {page} de {pages}", + cnMoreInfo: "Máis información →", + cnOpenCourse: "Curso aberto", + cnAuction: "POXA", + cnExchange: "INTERCAMBIO", + cnRssDescription: "publicados polos habitantes deste HUB", + cnKindAudio: "Audio", + cnKindBlog: "Blog", + cnKindBookmark: "Marcador", + cnKindDocument: "Documento", + cnKindEvent: "Evento", + cnKindFeed: "Feed", + cnKindImage: "Imaxe", + cnKindJob: "Emprego", + cnKindMarket: "Mercado", + cnKindPodcast: "Podcast", + cnKindProject: "Proxecto", + cnKindCourse: "Curso", + cnKindShop: "Tenda", + cnKindTorrent: "Torrent", + cnKindFile: "Ficheiro", + cnKindVideo: "Vídeo", + cnKindWiki: "Wiki", + cnKindRoom: "Sala", + cnKindMap: "Mapa", + uxModeAINav: "IA", + + } +}; diff --git a/src/client/assets/translations/oasis_hi.js b/src/client/assets/translations/oasis_hi.js index 062769c..91474e7 100644 --- a/src/client/assets/translations/oasis_hi.js +++ b/src/client/assets/translations/oasis_hi.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "खरीद", shopMyOrdersDescription: "आपके खरीद ऑर्डर।", shopMyOrdersEmpty: "आपके पास अभी कोई खरीद नहीं है।", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM विक्रेता", shopOrderSeller: "विक्रेता", shopOrderInvoice: "चालान", shopOrderInvoiceConcept: "चालान", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "स्वीकार करें", shopOrderReject: "अस्वीकार करें", shopOrderMarkShipped: "भेजा गया चिह्नित करें", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM खरीदार", shopOrderContractConcept: "दुकान ऑर्डर", shopOrdersPending: "लंबित ऑर्डर", all: "सभी", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "सामग्री चेतावनी", invalidPost: "अमान्य सामग्री", invalidPostHint: "इस संदेश में अमान्य/खाली पाठ है।", - spreadBy: "द्वारा", spreadChron: "प्रसार", spreaded: "प्रसारित", spreadContentUnavailable: "सामग्री अभी उपलब्ध नहीं (प्रतिकृति लंबित)", filteredByTag: "टैग द्वारा फ़िल्टर", filteredByTagTitle: "टैग द्वारा फ़िल्टर", feedPublishedSuccess: "फ़ीड सफलतापूर्वक प्रकाशित!", + feedMastodonFailed: "Oasis में प्रकाशित हुआ, लेकिन Mastodon पर नहीं भेजा जा सका।", tribeFeedSent: "संदेश सफलतापूर्वक भेजा गया!", tribeContentEncrypted: "एन्क्रिप्टेड सामग्री", tribeTorrentsEmpty: "अभी तक कोई टोरेंट नहीं।", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "अंतिम विज़िट", profileClearnetBookmarksLabel: "बुकमार्क", invitesPort: "पोर्ट", - currentlyUnrecheable: "त्रुटि!", - peerHostValidation: "मान्य IPv4 (जैसे 192.168.1.100) या होस्टनाम (जैसे pub.example.com)", + peerHostValidation: "मान्य IPv4 (जैसे 192.168.1.100) या होस्टनाम (जैसे pub.solarnethub.com)", peerPortValidation: "पोर्ट 1-65535", peerKeyValidation: "SSB ed25519 सार्वजनिक कुंजी (@<44 वर्ण base64>=.ed25519)", inboxJobUnsubscribedTitle: "आपकी नौकरी से सदस्यता समाप्त", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "कार्य विवरण", exportPasswordLabel: "निर्यात पासवर्ड", importPasswordPlaceholder: "पासवर्ड दर्ज करें", - searchCasesPlaceholder: "मामले खोजें…", + searchCasesPlaceholder: "न्यायालय में खोजें...", shopBuyDeliveryAddressPlaceholder: "डिलीवरी पता", shopBuyNotesPlaceholder: "नोट्स", bankTaxesUserNoteBold: "ECO कर को सीधे कम करना", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "मामले का समर्थन करें", courtsSettlementAcceptBtn: "समझौता स्वीकारें", courtsSupportCount: "समर्थक", - courtsVerdictVoteTitle: "फैसले पर मतदान", - courtsVerdictVoteLabel: "फैसला मतदान", - courtsVerdictVoteAccept: "स्वीकार करें", - courtsVerdictVoteReject: "अस्वीकार करें", - courtsVerdictVoteSubmit: "मत भेजें", reportsSetStatus: "स्थिति निर्धारित करें", reportsStatusClosed: "बंद", statsAvgPerInhabitant: "प्रति निवासी औसत", @@ -189,9 +183,29 @@ module.exports = { ], profile: "अवतार", inhabitants: "निवासी", - peersReplicatedFeeds: "प्रतिकृत फ़ीड", + peersReplicatedFeeds: "प्रतिकृत", + peersTorColumn: "TOR", + peersTorYes: "Tor से पहुंचा जा सकता है", + peersTorNo: "Tor से नहीं पहुंचा जा सकता", + peersOasisId: "Oasis ID", + peersOasisVersion: "संस्करण", graphos: "ग्राफोस", graphosDescription: "आपके आसपास के नेटवर्क का इंटरैक्टिव मानचित्र।", + graphosFederation: "फ़ेडरेशन", + graphosFedPubs: "PUB", + graphosFedUnreachable: "पहुँच से बाहर", + graphosFedLinks: "पारस्परिक कड़ियाँ", + graphosFedIsolated: "अलग-थलग", + graphosFedNoData: "कोई डेटा नहीं", + graphosFedMutual: "पारस्परिक", + graphosFedOneWay: "एकतरफ़ा", + graphosFedTransport: "नेटवर्क", + graphosFedWith: "इनके साथ फ़ेडरेटेड", + graphosZoom: "बड़ा करें", + graphosFedInhabitants: "निवासी", + graphosFedClearnet: "इंटरनेट", + graphosFedBoth: "इंटरनेट + TOR", + graphosFedEmpty: "आपका नोड अभी किसी PUB को नहीं जानता।", graphosViewingGraphOf: "देख रहे", graphosBackToMine: "← मेरा नेटवर्क", graphosShowing: "दिखा रहे", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "आपको अभी तक कोई @उल्लेख नहीं मिला है।", - mentionsSearchPlaceholder: "उल्लेख खोजें...", + mentionsSearchPlaceholder: "उल्लेख में खोजें...", privateReminders: "रिमाइंडर", + inboxFilterPms: "निजी संदेश", + inboxFilterNotifications: "सूचनाएँ", + inboxMarkRead: "पढ़ा हुआ चिह्नित करें", + inboxMarkUnread: "अपठित चिह्नित करें", + inboxUnreadChip: "अपठित", + inboxMarkAllRead: "सभी को पठित चिह्नित करें", + inboxDeleteShown: "सभी हटाएँ", + inboxArchive: "संग्रहित करें", + inboxUnarchive: "संग्रह से निकालें", + inboxFilterArchived: "संग्रहित", + inboxEarlierMessages: "पिछले संदेश", + inboxSortThread: "थ्रेड के अनुसार", + inboxSortRecent: "नवीनतम पहले", + inboxAllBots: "बॉट", + settingsInboxBotsTitle: "इनबॉक्स सूचनाएँ", + settingsInboxBotsDesc: "चुनें कि कौन से बॉट आपके इनबॉक्स में सूचित कर सकते हैं। अचयनित बॉट छिपा दिए जाते हैं और उनकी सूचनाएँ गिनी नहीं जातीं।", inboxFiltersLabel: "फ़िल्टर:", inboxToggleToMutuals: "पारस्परिक पर स्विच करें", inboxToggleToWhole: "सभी पर स्विच करें", @@ -212,6 +242,7 @@ module.exports = { pmReply: "उत्तर दें", pmReplies: "उत्तर", inReplyTo: "के उत्तर में", + forumInThread: "थ्रेड में", pmPreview: "पूर्वावलोकन", pmPreviewTitle: "संदेश पूर्वावलोकन", peers: "पीयर", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "से", searchToLabel: "तक", searchQueryLabel: "क्वेरी", - searchPerPageLabel: "प्रति पृष्ठ परिणाम", + searchPerPageLabel: "परिणाम", settings: "सेटिंग्स", + settingsDescription: "अपने नेटवर्क का परिवेश सेट करें।", continueReading: "पढ़ना जारी रखें", moreComments: "और टिप्पणी", readThread: "बाकी थ्रेड पढ़ें", @@ -241,7 +273,6 @@ module.exports = { profileClearnetHelp: 'Oasis के बाहर से एक्सेस किए जा सकने वाले मॉड्यूल।', profileHubAll: 'सभी', footerLicenseLabel: 'लाइसेंस', - profileSwitchToOasis: 'Oasis पर वापस जाएँ', profileVisibilityFediverse: "मल्टीवर्स", contributorsTitle: "योगदानकर्ता", colorLabel: 'रंग चुनें', @@ -251,6 +282,7 @@ module.exports = { modulesViewTitle: "मॉड्यूल", modulesViewDescription: "मॉड्यूल सक्षम या अक्षम करके अपना वातावरण सेट करें।", inbox: "इनबॉक्स", + inboxSearchPlaceholder: "इनबॉक्स में खोजें...", multiverse: "मल्टीवर्स", fediverse: "मल्टीवर्स", fediverseDescription: "अपने अन्य मल्टीवर्स खातों को प्रबंधित करें, जिसमें सामग्री भेजना और प्राप्त करना शामिल है।", @@ -259,9 +291,17 @@ module.exports = { fediverseSettingsTitle: "मल्टीवर्स", fediverseInstanceLabel: "पता", fediverseTokenLabel: "एक्सेस टोकन", - fediverseTokenHelp: "अपना एक्सेस टोकन सेट करें। इसका उपयोग Oasis के भीतर से आपके Mastodon खाते को प्रबंधित करने के लिए किया जाएगा।", fediverseConnect: "कनेक्ट करें", fediverseConnectedAs: "इस रूप में कनेक्टेड", + nextcloudServerLabel: "सर्वर", + nextcloudUserLabel: "उपयोगकर्ता", + nextcloudFolderLabel: "फ़ोल्डर", + nextcloudUploadButton: "अपलोड", + nextcloudFileLabel: "एक फ़ाइल चुनें", + fileExternalLink: "बाहरी लिंक", + nextcloudErrConnect: "इन विवरणों से Nextcloud तक नहीं पहुँचा जा सका।", + nextcloudErrUpload: "Nextcloud पर अपलोड विफल रहा।", + nextcloudErrShare: "Nextcloud ने फ़ाइल के लिए सार्वजनिक लिंक नहीं दिया।", fediverseDisconnect: "डिस्कनेक्ट करें", fediverseEmptyLink: "सेटिंग्स", fediverseComposePlaceholder: "आप क्या सोच रहे हैं?", @@ -290,6 +330,76 @@ module.exports = { fediverseErrPost: "प्रकाशित नहीं हो सका।", fediverseErrMedia: "फ़ाइल अपलोड नहीं हो सकी।", fediverseErrEmpty: "कुछ लिखें या फ़ाइल जोड़ें।", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "फ़ोन नंबर", + telegramSendCode: "कोड भेजें", + telegramCodeHelp: "Telegram द्वारा अभी भेजा गया कोड दर्ज करें (ऐप या SMS)।", + telegramCodeLabel: "कोड", + telegramPasswordHelp: "इस खाते में दो-चरणीय सत्यापन है। अपना पासवर्ड दर्ज करें।", + telegramPasswordLabel: "पासवर्ड", + telegramVerify: "सत्यापित करें", + telegramCancel: "रद्द करें", + telegramChats: "चैट", + telegramUnread: "अपठित", + telegramOpen: "खोलें", + telegramSend: "भेजें", + telegramYou: "आप", + telegramNoChats: "अभी कोई चैट नहीं।", + telegramNoMessages: "अभी कोई संदेश नहीं।", + telegramComposePlaceholder: "संदेश लिखें…", + telegramChannel: "चैनल", + telegramGroup: "समूह", + telegramPrivate: "निजी", + telegramErrConnect: "Telegram से संपर्क नहीं हो सका।", + telegramErrTimeout: "Telegram ने जवाब देने में बहुत समय लिया।", + telegramErrAuth: "Telegram सत्र अमान्य या समाप्त। फिर से जोड़ें।", + telegramErrFetch: "चैट लोड नहीं हो सकीं।", + telegramErrSend: "संदेश नहीं भेजा जा सका।", + telegramErrMissing: "API ID, API hash और फ़ोन नंबर आवश्यक हैं।", + telegramErrCode: "अमान्य कोड। पुनः प्रयास करें।", + telegramErrCodeExpired: "कोड समाप्त हो गया। फिर से शुरू करें।", + telegramErrPassword: "गलत पासवर्ड।", + telegramErrPhone: "अमान्य फ़ोन नंबर।", + telegramErrApi: "अमान्य API ID या API hash।", + telegramErrFlood: "बहुत अधिक प्रयास। थोड़ी देर बाद पुनः प्रयास करें।", + telegramErrNoLogin: "कोई लॉगिन प्रगति में नहीं। फिर से शुरू करें।", + peertubeUsernameLabel: "उपयोगकर्ता नाम", + peertubePasswordLabel: "पासवर्ड", + peertubeSubscriptions: "सदस्यताएँ", + peertubeMyVideos: "मेरे वीडियो", + peertubeVideos: "वीडियो", + peertubeFollowers: "फ़ॉलोअर", + peertubeViews: "बार देखा गया", + peertubeLikes: "पसंद", + peertubeQuota: "कोटा", + peertubeUpload: "वीडियो अपलोड करें", + peertubeUploadHelp: "एक वीडियो फ़ाइल चुनें। यह आपके चैनल पर अपलोड होती है और बाद में PeerTube उसे प्रोसेस करता है।", + peertubeTitleLabel: "शीर्षक", + peertubeDescriptionLabel: "विवरण", + peertubePrivacyLabel: "गोपनीयता", + peertubePrivacyPublic: "सार्वजनिक", + peertubePrivacyUnlisted: "असूचीबद्ध", + peertubePrivacyPrivate: "निजी", + peertubeNoVideos: "अभी कोई वीडियो नहीं।", + peertubeComments: "टिप्पणियाँ", + peertubeComment: "टिप्पणी करें", + peertubeCommentPlaceholder: "टिप्पणी लिखें…", + peertubeNoComments: "अभी कोई टिप्पणी नहीं।", + peertubeOpen: "PeerTube पर खोलें", + peertubeWatch: "देखें", + peertubeChannel: "चैनल", + peertubeNoStream: "यह वीडियो अभी यहाँ नहीं चल सकता। इसे PeerTube पर खोलें।", + peertubeUploaded: "वीडियो अपलोड हो गया। PeerTube इसे प्रोसेस कर रहा है।", + peertubeErrConnect: "PeerTube से संपर्क नहीं हो सका।", + peertubeErrAuth: "उपयोगकर्ता नाम या पासवर्ड गलत है, या सत्र समाप्त हो गया। फिर से जोड़ें।", + peertubeErrInstance: "अमान्य इंस्टेंस URL।", + peertubeErrMissing: "इंस्टेंस, उपयोगकर्ता नाम और पासवर्ड आवश्यक हैं।", + peertubeErrFetch: "वीडियो लोड नहीं हो सके।", + peertubeErrUpload: "वीडियो अपलोड नहीं हो सका।", + peertubeErrTitle: "शीर्षक आवश्यक है।", + peertubeErrComment: "टिप्पणी प्रकाशित नहीं हो सकी।", + peertubeErrNoFile: "एक वीडियो फ़ाइल चुनें।", bookmarksLabel: "बुकमार्क", videosLabel: "वीडियो", torrentsLabel: "टोरेंट", @@ -312,7 +422,6 @@ module.exports = { comment: "टिप्पणी", subtopic: "उपविषय", json: "JSON", - createdBy: "द्वारा", unfollow: "समर्थन वापस लें", follow: "समर्थन करें", block: "ब्लॉक करें", @@ -328,12 +437,15 @@ module.exports = { relationshipNone: "आप समर्थन नहीं कर रहे हैं", relationshipBlockingPost: "ब्लॉक की गई पोस्ट", viewLikes: "प्रसार देखें", + viewSupporters: "समर्थक देखें", spreadedDescription: "निवासी द्वारा प्रसारित पोस्ट की सूची।", preview: "पूर्वावलोकन", publish: "लिखें", contentWarningPlaceholder: "पोस्ट में विषय जोड़ें (वैकल्पिक)", - publishWarningPlaceholder: "मुख्य पाठ 7000 वर्णों तक सीमित।", + publishWarningPlaceholder: "अपनी प्रविष्टि का मुख्य पाठ लिखें।", publishTooLong: "आपकी पोस्ट बहुत लंबी है। कृपया इसे छोटा करें।", + keepTextRetry: "फिर से भेजें", + dateInPastError: "तारीख़ बीते समय की नहीं हो सकती", publishCustomDescription: [ "याद रखें: ब्लॉकचेन तकनीक के कारण, एक बार पोस्ट प्रकाशित होने के बाद इसे संपादित या हटाया नहीं जा सकता।", ], @@ -363,9 +475,8 @@ module.exports = { mentionsMatching: "मिलते-जुलते उल्लेख", mentionsName: "नाम", mentionsRelationship: "संबंध", - updateit: "अपडेट प्राप्त करें!", updateBannerText: "Oasis का एक नया संस्करण उपलब्ध है।", - updateBannerAction: "अभी अपडेट करें →", + updateBannerAction: "अभी अपडेट करें!", info: "जानकारी", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -373,16 +484,14 @@ module.exports = { timeAgo: "पहले", sendTime: "लगभग ", theme: "थीम", - legacy: "कुंजियाँ", - legacyTitle: "कुंजियाँ", - legacyDescription: "अपनी गुप्त कुंजी (निजी कुंजी) को जल्दी और सुरक्षित रूप से प्रबंधित करें।", - legacyExportButton: "निर्यात", - legacyImportButton: "आयात", + backupKeysExportButton: "निर्यात", + backupKeysImportButton: "आयात", ssbLogStream: "ब्लॉकचेन", ssbLogStreamDescription: "ब्लॉकचेन स्ट्रीम के लिए संदेश सीमा कॉन्फ़िगर करें।", + logWindowChip: "अंतिम {n} ब्लॉकचेन स्ट्रीम", saveSettings: "सेटिंग्स सहेजें", exportDescription: "अपनी कुंजी एन्क्रिप्ट करने के लिए पासवर्ड सेट करें (न्यूनतम 32 अक्षर)", - exportDataTitle: "बैकअप", + exportDataTitle: "तत्काल बैकअप", exportDataDescription: "अपना डेटा डाउनलोड करें (गुप्त कुंजी शामिल नहीं!)", exportDataButton: "डेटाबेस डाउनलोड करें", importDescription: "अपना अवतार सक्रिय करने के लिए अपनी एन्क्रिप्टेड गुप्त कुंजी (निजी कुंजी) आयात करें", @@ -391,38 +500,33 @@ module.exports = { fileInfo: "आपकी एन्क्रिप्टेड गुप्त कुंजी आपके डिवाइस पर डाउनलोड की जाएगी (नाम: oasis.enc)", developer: "विकास", devTitle: "विकास", - welcomeBannerText: "OASIS में स्वागत है। अपना अवतार सेट करने के लिए इन आसान चरणों का पालन करें।", + welcomeBannerText: "आपका अवतार अभी सेट नहीं हुआ है।", aiSuggestionBanner: "सुझाव:", aiSuggestionsEnable: "AI सुझाव", - welcomeBannerAction: "शुरू करें!", + welcomeBannerAction: "शुरू करें", welcomeTitle: "स्वागत है", + welcomeStepIdentity: "पहचान", + welcomeStepType: "प्रकार", + welcomeStepUse: "उपयोग", + welcomeStepLook: "रूप", + welcomeStepCommunity: "समुदाय", + welcomeStepIntro: "स्वागत", + welcomeHintIdentity: "आपका नाम और चित्र।", + welcomeHintType: "Oasis आपके लिए कैसे खुलता है।", + welcomeHintUse: "कौन से मॉड्यूल चालू रहते हैं।", + welcomeHintLook: "Oasis की थीम।", + welcomeHintCommunity: "दूसरे निवासियों से मिलने के लिए किसी PUB से जुड़ें।", + welcomeHintIntro: "समुदाय को पहला अभिवादन भेजें।", + welcomeStepThemeAction: "यह थीम इस्तेमाल करें", + welcomeJoinPubs: "PUB से जुड़ें", + welcomeNoFederation: "बिना संघ के", welcomeStepGreetingAction: "भेजें", - welcomeJoinPubButton: "pub से जुड़ें", - welcomeStepLarpAction: "\"अकादमी\" में शामिल हों", - welcomeStepLarpText: "हमारे लाइव एक्शन रोल प्लेइंग खेल में शामिल हों।", - welcomeStepLarpTitle: "L.A.R.P. में शामिल हों", - welcomeStepGreetingTitle: "एक \"Hello world!\" भेजें", - welcomeStepGreetingText: "एक संदेश भेजें ताकि दूसरे निवासी आपको खोज सकें।", - welcomeJoinPub: "जुड़ें:", welcomeDescription: "शुरू करने से पहले कुछ ज़रूरी बातें।", - welcomeProgress: "पूर्ण", - welcomeStepDone: "हो गया", - welcomeStepPending: "बाकी", welcomeSkip: "अभी नहीं", - welcomeStepLanguageTitle: "अपनी भाषा चुनें", - welcomeStepLanguageText: "Oasis कई भाषाएँ बोलता है। आप इसे कभी भी बदल सकते हैं।", - welcomeStepProfileTitle: "अपनी प्रोफ़ाइल संपादित करें", - welcomeStepProfileText: "एक नाम चुनें, विवरण जोड़ें और तस्वीर लगाएँ ताकि दूसरे निवासी आपको पहचान सकें।", welcomeStepProfileAction: "प्रोफ़ाइल सहेजें", - welcomeStepFederationTitle: "मुख्य नेटवर्क से जुड़ें", - welcomeStepFederationText: "दूसरे निवासियों से मिलने और सिंक शुरू करने के लिए हमारे pub से जुड़ें।", - welcomeStepFederationAction: "आमंत्रण देखें", - welcomeStepBackupTitle: "अपनी ID का बैकअप लें", - welcomeStepBackupText: "आपकी पहचान इस डिवाइस पर एक कुंजी फ़ाइल है।", - welcomeStepBackupWarning: "यदि यह खो गई, तो कोई भी इसे वापस नहीं ला सकता।", - welcomeStepBackupAction: "बैकअप!", + welcomeBootstrapRunning: "पब का स्नैपशॉट लोड हो रहा है। पूरा होते ही पब से अपने आप जुड़ जाएगा; इस बीच आप Oasis इस्तेमाल करते रह सकते हैं।", welcomeCompleteTitle: "सब तैयार है।", - welcomeCompleteText: "आपका नोड तैयार है। यहाँ से नेटवर्क उन लोगों के साथ बढ़ता है जिन्हें आप फ़ॉलो करते हैं।", + welcomeCompleteText: "आपका नोड तैयार है। यहाँ से नेटवर्क उन निवासियों के साथ बढ़ता है जिनका आप समर्थन करते हैं।", welcomeFindPeople: "निवासी खोजें", devFilterFiles: "फ़ाइलें", devFilterSearch: "खोज", @@ -432,7 +536,7 @@ module.exports = { devFilterTests: "टेस्ट", devVersion: "संस्करण", devNodeVersion: "Node", - devStatsTests: "टेस्ट सूट", + devStatsTests: "टेस्ट", devParentFolder: "मूल फ़ोल्डर", devOpenFolder: "खोलें", devDescription: "इस नोड पर चल रहे स्रोत कोड का अन्वेषण करें।", @@ -476,6 +580,27 @@ module.exports = { "यदि आप दूसरी भाषा उपयोग करना चाहते हैं, तो यहाँ चुनें।", setLanguage: "भाषा सेट करें", peerConnectionsIntro: "अन्य पीयर के साथ अपने सभी कनेक्शन प्रबंधित करें।", + peersConnectFailed: "कनेक्ट नहीं हो सका: {reason}।", + peerStateLabel: "स्थिति", + peerAddressLabel: "पता", + peerLastChangeLabel: "अंतिम बदलाव", + peerStateConnected: "जुड़ा हुआ", + peerStateConnecting: "जुड़ रहा है", + peerStateStaged: "उपलब्ध", + peerErrSilent: "कोई गतिविधि नहीं", + peerErrTimeout: "जवाब नहीं दे रहा", + peerErrRefused: "कनेक्शन अस्वीकार करता है", + peerErrNotFound: "अज्ञात पता", + peerErrUnreachable: "नेटवर्क पहुँच से बाहर", + peerErrKeys: "अलग कुंजियाँ इस्तेमाल कर रहा है", + peerErrIdentity: "आपका कनेक्शन स्वीकार नहीं करता", + peerErrTor: "केवल TOR", + peerErrDropped: "कनेक्शन टूट गया", + peerErrPaused: "नेटवर्क रुका हुआ है", + peerErrOther: "कनेक्शन त्रुटि", + peerOutdated: "पुराना संस्करण", + peerVersionUnannounced: "अपना संस्करण घोषित नहीं करता: पुराना Oasis", + peersTorMissing: "यह PUB केवल TOR से कनेक्शन की अनुमति देता है।", peerConnectionsTitle: "कनेक्शन", online: "ऑनलाइन", offline: "ऑफ़लाइन", @@ -494,22 +619,25 @@ module.exports = { noConnections: "कोई पीयर कनेक्ट नहीं है।", noDiscovered: "कोई पीयर नहीं मिला।", noUnknownPeers: "मित्र-ग्राफ में कोई अज्ञात पीयर नहीं।", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "अभी तक कोई पीयर पंजीकृत नहीं है।", + peerConnect: "कनेक्ट करें", + peerDisconnect: "डिस्कनेक्ट करें", peerRefresh: "ताज़ा करें", peerPruneIdle: "निष्क्रिय हटाएँ", + peersPause: "रोकें", + peersResume: "फिर शुरू करें", + peersPausedNote: "रुका हुआ: Oasis किसी से नहीं जुड़ता और कोई उससे नहीं जुड़ सकता।", + peersPauseHint: "सभी कनेक्शन रोक देता है: जब तक आप फिर से शुरू नहीं करते, Oasis न अन्य नोड्स से जुड़ता है न उन्हें स्वीकार करता है।", peerExport: "निर्यात", peerImport: "आयात", peerImportTitle: "पीयर सूची आयात करें", peerImportPlaceholder: "प्रति पंक्ति एक multiserver पता पेस्ट करें, या एक .txt फ़ाइल अपलोड करें…", connectionActionIntro: "", - startNetworking: "नेटवर्किंग शुरू करें", - stopNetworking: "नेटवर्किंग बंद करें", - restartNetworking: "नेटवर्किंग पुनः आरंभ करें", - sync: "नेटवर्क सिंक करें", indexes: "इंडेक्स", + indexesRebuilt: "सूचकांक पुनर्निर्मित", + indexesRebuildFailed: "सूचकांक पुनर्निर्मित नहीं हो सके", + indexesFiles: "सूचकांक फ़ाइलें", indexesDescription: "अपने इंडेक्स पुनर्निर्माण करना सुरक्षित है, और कुछ प्रकार की त्रुटियों को ठीक कर सकता है।", homePageTitle: "होम", @@ -519,44 +647,33 @@ module.exports = { invitesDescription: "अपने नेटवर्क में आमंत्रण कोड प्रबंधित करें और लागू करें।", invitesPubsHint: "किसी पब का आमंत्रण कोड चिपकाएँ ताकि उससे संघ बने और उसके निवासियों की प्रतिकृति शुरू हो।", invitesFederationsHint: "जिन पब से आप जुड़े हैं: उन्हें ताज़ा करें, अनुपलब्ध हटाएँ या सूची निर्यात करें।", - invitesHousesHint: "LARP के किसी घर में शामिल होने और उसकी अर्थव्यवस्था में भाग लेने के लिए घर का कोड डालें।", - invitesTribesHint: "सदस्य बनने और निजी सामग्री तक पहुँचने के लिए ट्राइब का कोड डालें।", - invitesChatsHint: "बातचीत और उसके थ्रेड में शामिल होने के लिए चैट का कोड डालें।", - invitesPadsHint: "साझा दस्तावेज़ पर साथ लिखने के लिए पैड का कोड डालें।", - invitesCalendarsHint: "तारीख़ें और अनुस्मारक साझा करने के लिए कैलेंडर का कोड डालें।", - invitesEventsHint: "निजी आयोजन में शामिल होने के लिए इवेंट का कोड डालें।", - invitesForumsHint: "निजी फ़ोरम में पढ़ने और उत्तर देने के लिए फ़ोरम का कोड डालें।", - invitesMapsHint: "साझा नक़्शे पर जगहें देखने और जोड़ने के लिए मानचित्र का कोड डालें।", - invitesShopsHint: "किसी दुकान और उसके कैटलॉग में शामिल होने के लिए दुकान का कोड डालें।", - invitesInhabitantsHint: "एक-दूसरे को फ़ॉलो करने और सामग्री साझा करने के लिए निवासी का कोड डालें।", invitesTribesTitle: "जनजातियाँ", - invitesTribeInviteCodePlaceholder: "जनजाति आमंत्रण कोड दर्ज करें", - invitesTribeJoinButton: "जनजाति में शामिल हों", invitesChatsTitle: "चैट्स", - invitesChatInviteCodePlaceholder: "चैट आमंत्रण कोड दर्ज करें", - invitesChatJoinButton: "चैट में शामिल हों", invitesPadsTitle: "पैड्स", - invitesPadInviteCodePlaceholder: "पैड आमंत्रण कोड दर्ज करें", - invitesPadJoinButton: "पैड में शामिल हों", invitesCalendarsTitle: "कैलेंडर", - invitesCalendarInviteCodePlaceholder: "कैलेंडर आमंत्रण कोड दर्ज करें", - invitesCalendarJoinButton: "कैलेंडर में शामिल हों", invitesEventsTitle: "कार्यक्रम", - invitesEventInviteCodePlaceholder: "कार्यक्रम आमंत्रण कोड दर्ज करें", - invitesEventJoinButton: "कार्यक्रम में शामिल हों", invitesForumsTitle: "मंच", - invitesForumInviteCodePlaceholder: "मंच आमंत्रण कोड दर्ज करें", - invitesForumJoinButton: "मंच में शामिल हों", invitesMapsTitle: "मानचित्र", - invitesMapInviteCodePlaceholder: "मानचित्र आमंत्रण कोड दर्ज करें", - invitesMapJoinButton: "मानचित्र में शामिल हों", invitesShopsTitle: "दुकानें", - invitesShopInviteCodePlaceholder: "दुकान आमंत्रण कोड दर्ज करें", - invitesShopJoinButton: "दुकान में शामिल हों", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "सुझाए गए PUB", + invitesFilterOthers: "अन्य", + invitesJoinTitle: "आमंत्रण", + invitesJoinHint: "कोई भी आमंत्रण कोड चिपकाएँ, चाहे वह किसी के लिए भी हो, या किसी निवासी का Oasis ID।", + invitesJoinPlaceholder: "आमंत्रण कोड या Oasis ID", + invitesJoinButton: "जुड़ें", + invitesUsedTitle: "हाल के आमंत्रण", + invitesUsedEmpty: "आपने अभी तक कोई आमंत्रण उपयोग नहीं किया है।", + invitesUsedType: "प्रकार", + invitesUsedName: "नाम", + invitesUsedDate: "तारीख", invitesPubInviteCodePlaceholder: "PUB आमंत्रण कोड दर्ज करें", invitesAcceptInvite: "PUB में शामिल हों", + invitesPubUses: "उपयोग किए गए निमंत्रण", + invitesPubExhausted: "इस PUB के पास कोई निमंत्रण नहीं बचा है।", invitesAlreadyFederated: "आप पहले से ही इस पब के साथ फेडरेटेड हैं।", + invitesJoinPubMode: "यह Oasis एक PUB के रूप में, केवल-पढ़ने वाले मोड में चल रहा है। इसे किसी दूसरे PUB से जोड़ने के लिए टर्मिनल में चलाएँ: ./oasis.sh join <कोड>", + invitesJoinLocalOnly: "निमंत्रण केवल उसी डिवाइस से स्वीकार किए जा सकते हैं जिस पर Oasis चल रहा है, या शुरू होने पर दिखने वाले व्यवस्थापक लिंक से।", invitesFederationsTitle: "संघ", invitesAcceptedInvites: "संघीय", invitesUnfollow: "अनफ़ॉलो करें", @@ -564,16 +681,12 @@ module.exports = { invitesUnfollowedInvites: "असंघीय", invitesNoFederatedPubs: "कोई संघीय pub नहीं।", invitesNoUnfollowed: "कोई असंघीय pub नहीं।", - invitesUnreachablePubs: "अगम्य", - invitesNoUnreachablePubs: "कोई अगम्य pub नहीं।", invitesPubsRefresh: "ताज़ा करें", - invitesPubsClearUnreachable: "अगम्य हटाएँ", + invitesPubsClearUnreachable: "साफ़ करें", invitesPubsExport: "निर्यात", invitesPubsImport: "आयात", invitesPubsImportTitle: "Pubs आयात करें", invitesPubsImportPlaceholder: "प्रति पंक्ति एक multiserver पता या निमंत्रण कोड पेस्ट करें, या एक .txt फ़ाइल अपलोड करें…", - currentlyUnreachable: "त्रुटि!", - errorDetails: "त्रुटि विवरण", genericError: "एक त्रुटि हुई।", panicMode: "आपातकालीन मोड!", encryptData: "अपनी ब्लॉकचेन एन्क्रिप्ट करने के लिए पासवर्ड सेट करें (न्यूनतम 32 अक्षर)", @@ -600,7 +713,6 @@ module.exports = { votesOptionNoLabel:"नहीं", votesOptionAbstentionLabel:"तटस्थता", votesQuestionLabel:"प्रश्न", - votesCreatedByLabel:"द्वारा बनाया गया", voteStatusOpen:"खुला", voteStatusClosed:"बंद", commentDescription: ({ parentUrl }) => [ @@ -617,22 +729,22 @@ module.exports = { searchSubmit: "खोजें...", votesLabel: "मतदान", pdfFallbackLabel: "PDF दस्तावेज़", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "दुकानें", + voteLabel: "वोट", submit: "जमा करें", editProfile: "अवतार संपादित करें", oasisIdLabel: "OasisID", spreadContent: "प्रतिकृति बनाएँ", welcomePmSubject: "Hello World!", - welcomePmBody: "Oasis में आपका स्वागत है — एक स्वतंत्र, पीयर-टू-पीयर, फ़ेडरेटेड और एन्क्रिप्टेड सोशल नेटवर्क, जिसकी वास्तुकला वितरित और केवल-आमंत्रण आधारित है।\n\nशुरुआत के लिए कुछ रोचक स्थान:\n\n- /profile — अपना अवतार, नाम, विवरण और कौन से मॉड्यूल आपके सार्वजनिक पक्ष पर दिखेंगे, संपादित करें।\n- /invites — व्यापक नेटवर्क के साथ फेडरेट करने के लिए एक pub जोड़ें।\n- /peers — देखें कौन जुड़ा है, अपनी LAN फिर से स्कैन करें, पीयर सूचियाँ निर्यात या आयात करें।\n- /inhabitants — दूसरों के अवतार खोजें और देखें।\n- /tribes — एंड-टू-एंड एन्क्रिप्शन वाले निजी समूह बनाएँ या उनमें शामिल हों।\n- /inbox — निजी संदेश पढ़ें और भेजें।\n- /activity — हाल की गतिविधि देखें — आपकी और आपके नेटवर्क की।\n- /publish — पोस्ट लिखें, या चित्र, ऑडियो, वीडियो, दस्तावेज़ साझा करें।\n- /search — नेटवर्क की सामग्री को प्रकार के अनुसार खोजें।\n\nकनेक्ट करने के लिए कुछ दिलचस्प जगहें:\n\n- /multiverse — अपने अन्य मल्टीवर्स खातों को प्रबंधित करें, जिसमें सामग्री भेजना और प्राप्त करना शामिल है।\n\nयह संदेश आपके द्वारा निजी रूप से स्वयं को भेजा गया था, इसलिए इसे प्राप्त करने के लिए किसी कनेक्शन की आवश्यकता नहीं थी।", + welcomePmBody: "Oasis में आपका स्वागत है — एक स्वतंत्र, पीयर-टू-पीयर, फ़ेडरेटेड और एन्क्रिप्टेड सोशल नेटवर्क, जिसकी वास्तुकला वितरित और केवल-आमंत्रण आधारित है।\n\nशुरुआत के लिए कुछ रोचक स्थान:\n\n- [/avatar](/profile) — अपना अवतार, नाम, विवरण और कौन से मॉड्यूल आपके सार्वजनिक पक्ष पर दिखेंगे, संपादित करें।\n- /settings — अपना स्थानीय इंस्टेंस समायोजित करें: भाषा, थीम, मॉड्यूल, गोपनीयता और सिंक।\n- /invites — व्यापक नेटवर्क के साथ फेडरेट करने के लिए एक pub जोड़ें।\n- /peers — देखें कौन जुड़ा है, अपनी LAN फिर से स्कैन करें, पीयर सूचियाँ निर्यात या आयात करें।\n- /inhabitants — दूसरे निवासियों के अवतार खोजें और देखें।\n- /tribes — एंड-टू-एंड एन्क्रिप्शन वाले निजी समूह बनाएँ या उनमें शामिल हों।\n- /search — नेटवर्क की सामग्री को प्रकार के अनुसार खोजें।\n\nकनेक्ट करने के लिए कुछ दिलचस्प जगहें:\n\n- /multiverse — अपने अन्य मल्टीवर्स खातों को प्रबंधित करें, जिसमें सामग्री भेजना और प्राप्त करना शामिल है।\n\nयह संदेश आपके द्वारा निजी रूप से स्वयं को भेजा गया था, इसलिए इसे प्राप्त करने के लिए किसी कनेक्शन की आवश्यकता नहीं थी।", profileSensorsSectionTitle: "सेंसर", profileVisibilityActivity: "गतिविधि स्तर", profileDeviceLockedHint: "यह सेंसर हमेशा चालू रहता है: यह दूसरों को दिखाता है कि आप किस डिवाइस से कनेक्ट होते हैं और इसे अक्षम नहीं किया जा सकता।", profileVisibilityKarma: "KARMA स्कोर", profileVisibilityUbi: "UBI", - profileVisibilityWallet: "ECOIN वॉलेट", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityWallet: "ECOin वॉलेट", + profileVisibilityEcoTax: "ECO कर", + profileVisibilityLarpSign: "L.A.R.P. मुहर", profileVisibilityGpg: "GPG कुंजी", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "दुकानें", @@ -645,6 +757,10 @@ module.exports = { profileClearnetImagesLabel: "चित्र", profileClearnetDocumentsLabel: "दस्तावेज़", profileClearnetTorrentsLabel: "टॉरेंट", + profileClearnetPodcastsLabel: "पॉडकास्ट", + profileClearnetMarketLabel: "बाज़ार", + profileClearnetFeedLabel: "फ़ीड", + profileClearnetWikiLabel: "विकी", editProfileDescription: "", profileName: "नाम", @@ -670,7 +786,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "पुष्टि", walletConfirm: "पुष्टि करें", - walletDescription: "अपनी डिजिटल संपत्तियाँ प्रबंधित करें, जिसमें ECOin भेजना और प्राप्त करना, अपना शेष देखना और अपना लेनदेन इतिहास शामिल है।", + walletDescription: "अपना ECOin वॉलेट प्रबंधित करें।", walletDate: "दिनांक", walletFee: "शुल्क (शुल्क जितना अधिक होगा, आपका लेनदेन उतनी तेज़ी से संसाधित होगा)", walletFeeLine: ({ fee }) => `शुल्क: ECO ${fee}`, @@ -678,6 +794,86 @@ module.exports = { walletReceive: "प्राप्त करें", walletReset: "रीसेट", walletSend: "भेजें", + walletPayingTransfer: "ट्रांसफ़र का भुगतान", + walletCreateTransferLabel: "स्मार्ट कॉन्ट्रैक्ट बनाएँ (ट्रांसफर)", + walletTransferNotifiedNote: "दूसरे पक्ष को इस भुगतान की सूचना दी गई", + walletTransferCreatedNote: "इस भुगतान के लिए एक ट्रांसफ़र (रसीद) बनाया गया", + walletTransferNoPayeeNote: "किसी निवासी ने यह पता प्रकाशित नहीं किया, इसलिए ट्रांसफ़र नहीं बना", + walletTransferReceiptConcept: "वॉलेट से भुगतान", + walletPaymentPmText: "ने आपको भुगतान भेजा", + walletPayeeLabel: "प्राप्तकर्ता", + walletConceptLabel: "विवरण", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "आपको अपना UBI मिल गया है।", + bankingBotPaymentTitle: "आपको एक भुगतान मिला है।", + bankingBotUbiPaidText: "आपकी सार्वभौमिक बुनियादी आय का भुगतान कर दिया गया है", + bankUbiPubThisNode: "यह नोड UBI का भुगतान करता है", + bankUbiPubConnected: "UBI PUB", + bankUbiPubNone: "आपके नेटवर्क में अभी कोई PUB UBI की घोषणा नहीं कर रहा", + bankUbiAddressPublished: "ECOin पता प्रकाशित", + bankUbiAddressMissing: "UBI पाने के लिए अपना ECOin पता प्रकाशित करें", + bankUbiClaimedThisMonth: "इस महीने दावा किया गया", + bankUbiPubBalance: "UBI के लिए उपलब्ध PUB वॉलेट", + bankNoPubAddress: "UBI PUB ने अभी तक ECOin पता प्रकाशित नहीं किया है।", + bankUbiTab: "UBI", + bankDonateEco: "दान करें!", + donateTooltip: "ECOin दान करें", + bankNoUserAddress: "इस निवासी ने ECOin पता साझा नहीं किया है।", + bankUbiPubDefault: "डिफ़ॉल्ट", + bankUbiPool: "निधि", + bankUbiLastSeen: "अंतिम बार देखा गया", + bankUbiLastPayout: "अंतिम कार्रवाई", + bankUbiPaidOut: "वितरित", + bankRangeToday: "आज", + bankRangeWeek: "सप्ताह", + bankRangeMonth: "महीना", + bankRangeYear: "वर्ष", + bankRangeFiveYears: "5 वर्ष", + bankRangeAll: "सभी", + bankChartValueTitle: "ECOin मूल्य का विकास", + bankChartValueHours: "समय में समतुल्य (घं)", + bankChartWealthTitle: "ECOin धन का पुनर्वितरण", + bankChartDistributed: "वितरित UBI (ECO)", + bankChartRebalanceTitle: "PUBs के बीच धन का पुनर्वितरण", + bankChartRebalanced: "PUBs के बीच भेजे गए ECO", + bankChartTaxes: "रोके गए कर (ECO)", + bankChartNoDataRange: "इस अवधि में कोई डेटा नहीं।", + bankChartNoDataYet: "अभी कोई डेटा नहीं", + bankChartPerHour: "प्रति घंटा", + bankChartPerDay: "प्रति दिन", + bankChartPerMonth: "प्रति माह", + bankTotalUbiDistributed: "कुल पुनर्वितरित UBI (नेटवर्क)", + bankTotalTaxesWithheld: "कुल रोके गए कर (नेटवर्क)", + bankInflationIssuance: "जारी करने की दर (वार्षिक, ब्लॉक इनाम से)", + bankRulesEligibilityTitle: "UBI कौन प्राप्त कर सकता है", + bankRulesEligibility1: "प्रोफ़ाइल में प्रकाशित ECOin पता (प्रोफ़ाइल → सेंसर → ECOin वॉलेट)।", + bankRulesEligibility2: "30 दिन से पुरानी फ़ीड जिसमें नेटवर्क में गतिविधि हो: कर्म शून्य से ऊपर।", + bankRulesEligibility3: "प्रति माह (एपॉक) एक दावा, Banking से UBI दावा करें बटन द्वारा। चालू माह को UBI अस्वीकार करें से मना किया जा सकता है।", + bankRulesEligibility4: "1 ECO की न्यूनतम राशि हमेशा दी जाती है; कर केवल उससे ऊपर के हिस्से को घटाते हैं और कभी अलग से नहीं लिए जाते।", + bankRulesEligibility5: "PUB अगले चक्र में भुगतान करता है और एक UBI ट्रांसफर प्रकाशित करता है जिसे आप पुष्टि कर सकते हैं।", + bankRulesEligibility6: "यदि पता नहीं है, फ़ीड बहुत नई है, आपने माह अस्वीकार किया या PUB के पास धन नहीं है, तो दावा छोड़ दिया जाता है और PUB पर लॉग किया जाता है।", + copyLinkTitle: "लिंक कॉपी करें", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "UBI अस्वीकार करें", + bankUbiRefusedThisMonth: "आपने इस महीने का UBI अस्वीकार किया।", + bankingBotUbiAvailableTitle: "आप अपना UBI दावा कर सकते हैं।", + bankingBotUbiAvailableText: "इस महीने का आपका UBI उपलब्ध है", + bankingBotConfirmTitle: "आपके पास पुष्टि की प्रतीक्षा में एक स्मार्ट-कॉन्ट्रैक्ट है", + bankingBotConfirmText: "आपकी पुष्टि की प्रतीक्षा में स्मार्ट-कॉन्ट्रैक्ट", + bankChartZoomHint: "बड़ा करने के लिए क्लिक करें", + bankChartPoolTitle: "UBI निधि (समय के साथ)", + bankChartNetworkUbiTitle: "कुल पुनर्वितरित UBI (नेटवर्क)", + bankChartSupplyTitle: "ECOin परिचालित आपूर्ति (समय के साथ)", + bankChartInflationTitle: "ECOin मुद्रास्फीति (समय के साथ)", + bankChartInflationSeries: "वार्षिक दर (%)", + bankChartPool: "कुल निधि (ECO)", + bankChartKarmaTitle: "KARMA Scoring (समय के साथ)", + bankChartFundsTitle: "आपकी ECOin निधि (समय के साथ)", + bankChartKarma: "कर्म", + bankWalletReady: "ECOin कनेक्टेड", + bankWalletNotConfigured: "ECOin डिस्कनेक्ट", + transfersPayWithWallet: "भुगतान करें!", walletStatus: "स्थिति", walletDisconnected: [ "ECOin ", @@ -687,8 +883,11 @@ module.exports = { " या कनेक्शन स्थिति।", ], walletSentToLine: ({ destination, amount }) => `ECO ${amount} भेजा गया ${destination} को`, + walletNotConfigured: "आपका ECOin वॉलेट अभी कॉन्फ़िगर नहीं है।", walletSettingsDescription: "Oasis को अपने ECOin वॉलेट के साथ एकीकृत करें।", - walletSettingsDocLink: "ECOin स्थापना मार्गदर्शिका", + walletSettingsMobileDescription: "Oasis को इस डिवाइस के ECOin Wallet ऐप से जोड़ें।", + walletDownloadEcoin: "ECOin डाउनलोड करें!", + walletRpcUrl: "RPC URL", walletStatusMessages: { invalid_amount: "अमान्य राशि", invalid_dest: "अमान्य गंतव्य पता", @@ -704,6 +903,7 @@ module.exports = { walletUser: "उपयोगकर्ता नाम", walletPass: "पासवर्ड", walletConfiguration: "वॉलेट सेट करें", + walletDisconnectButton: "वॉलेट डिस्कनेक्ट करें", cipher: "क्रिप्टर", cipherTitle: "क्रिप्टर", cipherDescription: "अपने टेक्स्ट को सममित रूप से एन्क्रिप्ट और डिक्रिप्ट करें (साझा पासवर्ड का उपयोग करके)।", @@ -737,17 +937,16 @@ module.exports = { bookmarkFilterRecent: "हाल के", bookmarkCreateButton: "बुकमार्क बनाएँ", bookmarkUpdateButton: "अपडेट", - bookmarkDeleteButton: "हटाएँ", bookmarkAddFavoriteButton: "पसंदीदा जोड़ें", bookmarkRemoveFavoriteButton: "पसंदीदा हटाएँ", bookmarkUrlLabel: "लिंक", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "विवरण", bookmarkDescriptionPlaceholder: "वैकल्पिक", bookmarkTagsLabel: "टैग", bookmarkTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", bookmarkLastVisitLabel: "अंतिम विज़िट", - bookmarkSearchPlaceholder: "URL, टैग, श्रेणी, लेखक खोजें...", + bookmarkSearchPlaceholder: "बुकमार्क में खोजें...", bookmarkSortRecent: "सबसे हाल का", bookmarkSortOldest: "सबसे पुराना", bookmarkSortTop: "सर्वाधिक मतदान", @@ -786,7 +985,7 @@ module.exports = { videoDescriptionPlaceholder: "वैकल्पिक", videoNoFile: "कोई वीडियो फ़ाइल प्रदान नहीं की गई", noVideos: "कोई वीडियो उपलब्ध नहीं।", - videoSearchPlaceholder: "शीर्षक, टैग, लेखक खोजें...", + videoSearchPlaceholder: "वीडियो में खोजें...", videoSortRecent: "सबसे हाल का", videoSortOldest: "सबसे पुराना", videoSortTop: "सर्वाधिक मतदान", @@ -809,7 +1008,6 @@ module.exports = { documentFilterFavorites: "पसंदीदा", documentCreateButton: "दस्तावेज़ अपलोड करें", documentUpdateButton: "अपडेट", - documentDeleteButton: "हटाएँ", documentAddFavoriteButton: "पसंदीदा जोड़ें", documentRemoveFavoriteButton: "पसंदीदा से हटाएँ", documentMessageAuthorButton: "PM", @@ -822,7 +1020,7 @@ module.exports = { documentDescriptionPlaceholder: "वैकल्पिक", documentNoFile: "कोई फ़ाइल नहीं।", noDocuments: "कोई दस्तावेज़ उपलब्ध नहीं।", - documentSearchPlaceholder: "शीर्षक, टैग, विवरण, लेखक खोजें...", + documentSearchPlaceholder: "दस्तावेज़ में खोजें...", documentSortRecent: "सबसे हाल का", documentSortOldest: "सबसे पुराना", documentSortTop: "सर्वाधिक मतदान", @@ -843,19 +1041,19 @@ module.exports = { audioFilterTop: "शीर्ष", audioCreateButton: "ऑडियो अपलोड करें", audioUpdateButton: "अपडेट", - audioDeleteButton: "हटाएँ", audioAddFavoriteButton: "पसंदीदा जोड़ें", audioRemoveFavoriteButton: "पसंदीदा हटाएँ", audioFileLabel: "ऑडियो फ़ाइल चुनें (.mp3, .wav, .ogg)", audioTagsLabel: "टैग", audioTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + tagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", audioTitleLabel: "शीर्षक", audioTitlePlaceholder: "वैकल्पिक", audioDescriptionLabel: "विवरण", audioDescriptionPlaceholder: "वैकल्पिक", audioNoFile: "कोई ऑडियो फ़ाइल प्रदान नहीं की गई", noAudios: "कोई ऑडियो उपलब्ध नहीं।", - audioSearchPlaceholder: "शीर्षक, टैग, लेखक खोजें...", + audioSearchPlaceholder: "ऑडियो में खोजें...", audioSortRecent: "सबसे हाल का", audioSortOldest: "सबसे पुराना", audioSortTop: "सर्वाधिक मतदान", @@ -867,7 +1065,7 @@ module.exports = { audioFilterFavorites: "पसंदीदा", favoritesTitle: "पसंदीदा", favoritesDescription: "आपकी सभी पसंदीदा सामग्री एक ही स्थान पर।", - favoritesSearchPlaceholder: "पसंदीदा खोजें...", + favoritesSearchPlaceholder: "पसंदीदा में खोजें...", favoritesFilterAll: "सभी", favoritesFilterRecent: "हाल के", favoritesFilterAudios: "ऑडियो", @@ -879,13 +1077,12 @@ module.exports = { favoritesFilterChats: "चैट्स", favoritesFilterCalendars: "कैलेंडर", favoritesFilterVideos: "वीडियो", - favoritesRemoveButton: "पसंदीदा से हटाएँ", favoritesNoItems: "अभी तक कोई पसंदीदा नहीं।", yourContacts: "आपके संपर्क", allInhabitants: "निवासी", allCVs: "CVs", discoverPeople: "अपने नेटवर्क में निवासियों को खोजें।", - searchInhabitantsPlaceholder: "नाम से निवासी फ़िल्टर करें …", + searchInhabitantsPlaceholder: "निवासी में खोजें...", filterLocation: "स्थान से निवासी फ़िल्टर करें …", filterLanguage: "भाषा से निवासी फ़िल्टर करें …", filterSkills: "कौशल से निवासी फ़िल्टर करें …", @@ -909,7 +1106,6 @@ module.exports = { topSectionTitle: "शीर्ष", blockedLabel: "ब्लॉक किया गया उपयोगकर्ता", inhabitantviewDetails: "विवरण देखें", - cvVisitButton: "CV देखें", keepReading: "पढ़ते रहें...", oasisId: "ID", noInhabitantsFound: "अभी तक कोई निवासी नहीं मिला।", @@ -927,6 +1123,18 @@ module.exports = { parliamentFilterLeaders: "नेता", parliamentFilterRules: "नियम", parliamentGovernmentCard: "वर्तमान सरकार", + parliamentSeatsGovernment: "सरकार", + parliamentSeatsOpposition: "विपक्ष", + parliamentSeatsAbstention: "अनुपस्थित", + parliamentSeatsQuorum: "चुनाव कोरम", + parliamentSeatsPass: "अनुमोदन सीमा", + parliamentSeatsByElection: "चुनाव के अनुसार", + parliamentSeatsByHouses: "LARP हाउस के अनुसार", + parliamentSeatsSeats: "सीटें", + parliamentSeatsSeat: "सीट", + parliamentSeatsLeader: "नेता", + parliamentSeatsVotesFor: "के लिए वोट", + parliamentSeatsNoHouse: "कोई हाउस नहीं", parliamentActorInPowerInhabitant: 'सत्तारूढ़ निवासी', parliamentActorInPowerTribe: 'सत्तारूढ़ जनजाति', parliamentHistoricalGovernmentsTitle: 'सरकारें', @@ -994,6 +1202,7 @@ module.exports = { parliamentTimeRemaining: "शेष समय", parliamentElectionsStatusTitle: "अगली सरकार", parliamentProposalDeadlineLabel: "समय सीमा", + parliamentViewReport: "रिपोर्ट देखें", parliamentProposalTimeLeft: "शेष समय", parliamentRulesTitle: "संसद कैसे काम करती है", parliamentRulesIntro: "चुनाव हर 2 महीने में हल होते हैं; उम्मीदवारी निरंतर होती है और सरकार चुने जाने पर रीसेट हो जाती है।", @@ -1008,6 +1217,7 @@ module.exports = { parliamentRulesLaws: "जब कोई प्रस्ताव अपनी सीमा पूरी करता है, तो वह कानून बन जाता है और कानून टैब में लागू होने की तिथि के साथ दिखाई देता है।", parliamentRulesHistorical: "ऐतिहासिक टैब में आप हर सरकारी चक्र और उसके प्रबंधन का डेटा देख सकते हैं।", parliamentRulesLeaders: "नेता टैब में आप उन निवासियों/जनजातियों की रैंकिंग देख सकते हैं जिन्होंने शासन किया है (या उम्मीदवार रहे हैं), दक्षता के अनुसार क्रमबद्ध।", + parliamentRulesHemicycle: "अर्धवृत्त प्रत्येक निवासी को एक सीट देता है: सरकार का खंड (विजयी उम्मीदवारी के मतदाता और नेता), चक्र की हर अन्य उम्मीदवारी के लिए एक विपक्षी खंड, और अनुपस्थिति। बिंदीदार निशान चुनाव कोरम (जनगणना का 25%) और वर्तमान पद्धति की अनुमोदन सीमा दिखाते हैं। \"LARP हाउस के अनुसार\" उन्हीं सीटों को हाउस के अनुसार रंगता है।", parliamentProposalVoteStatusLabel: "मत स्थिति", parliamentProposalOnTrackYes: "सीमा पूरी हुई", parliamentProposalOnTrackNo: "सीमा से नीचे", @@ -1021,6 +1231,7 @@ module.exports = { courtsCaseFormTitle: "मामला खोलें", courtsCaseRespondent: "आरोपी / प्रतिवादी", courtsCaseRespondentPh: "Oasis ID (@...) या जनजाति का नाम", + courtsCaseSubjectPlaceholder: "विषय या संक्षिप्त विवरण", courtsCaseMethod: "समाधान पद्धति", courtsCaseSubmit: "मामला दायर करें", courtsNominateJudge: "न्यायाधीश नामांकित करें", @@ -1054,15 +1265,30 @@ module.exports = { courtsMethodDICTATOR: "तानाशाह", courtsMethodPOPULAR: "लोकप्रिय", courtsMethodKARMATOCRACY: "कर्मतंत्र", + courtsCaseType: "मामले का प्रकार", + courtsPresetMinorConflict: "मामूली विवाद", + courtsPresetModerateConflict: "मध्यम विवाद", + courtsPresetSevereConflict: "गंभीर विवाद", + courtsPresetHarassment: "उत्पीड़न या दुर्व्यवहार", + courtsPresetContentModeration: "सामग्री मॉडरेशन", + courtsPresetBanRestriction: "प्रतिबंध या सीमा", + courtsPresetGovernanceDispute: "शासन विवाद", + courtsStatusOPEN: "खुला", + courtsStatusIN_PROGRESS: "प्रगति में", + courtsStatusDECIDED: "निर्णीत", + courtsStatusCLOSED: "बंद", + courtsStatusSOLVED: "सुलझा", + courtsStatusUNSOLVED: "अनसुलझा", + courtsStatusDISCARDED: "खारिज", courtsRulesTitle: "न्यायालय कैसे काम करते हैं", courtsRulesIntro: "न्यायालय विवादों को हल करने और पुनर्स्थापनात्मक न्याय को बढ़ावा देने की एक सामुदायिक प्रक्रिया है। संवाद, स्पष्ट साक्ष्य और आनुपातिक उपायों को प्राथमिकता दी जाती है।", courtsRulesLifecycle: "प्रक्रिया: 1) मामला खोलें 2) पद्धति चुनें 3) साक्ष्य प्रस्तुत करें 4) सुनवाई और विचार-विमर्श 5) फैसला और उपाय 6) अनुपालन और समापन 7) अपील (यदि लागू हो)।", - courtsRulesRoles: "वादी: मामला खोलता है। बचाव: आरोपित व्यक्ति या जनजाति। पद्धति: समुदाय द्वारा चुनी गई व्यवस्था जो सुविधा प्रदान करती है, साक्ष्य का मूल्यांकन करती है, और फैसला जारी करती है। गवाह: गवाही या साक्ष्य प्रदान करता है। मध्यस्थ: वादी और/या बचाव द्वारा आमंत्रित तटस्थ लोग, जिनकी सभी विवरणों तक पहुँच है, जो विवाद को शांत करने और समझौते सह-निर्माण करने में मदद करते हैं।", + courtsRulesRoles: "वादी: मामला खोलता है। बचाव: आरोपित निवासी या जनजाति। पद्धति: समुदाय द्वारा चुनी गई व्यवस्था जो सुविधा प्रदान करती है, साक्ष्य का मूल्यांकन करती है, और फैसला जारी करती है। गवाह: गवाही या साक्ष्य प्रदान करता है। मध्यस्थ: वादी और/या बचाव द्वारा आमंत्रित तटस्थ निवासी, जिनकी सभी विवरणों तक पहुँच है, जो विवाद को शांत करने और समझौते सह-निर्माण करने में मदद करते हैं।", courtsRulesEvidence: "विवरण अधिकतम 1000 अक्षर। प्रासंगिक और वैध छवियाँ, ऑडियो, वीडियो और PDF दस्तावेज़ संलग्न करें। सहमति के बिना संवेदनशील निजी डेटा साझा न करें।", courtsRulesDeliberation: "सुनवाई सार्वजनिक या निजी हो सकती है। न्यायाधीश सम्मान सुनिश्चित करते हैं, स्पष्टीकरण का अनुरोध करते हैं, और अप्रासंगिक या गैरकानूनी सामग्री को हटा सकते हैं।", courtsRulesVerdict: "पुनर्स्थापना को प्राथमिकता दी जाती है: क्षमा याचना, मध्यस्थता समझौते, सामग्री मॉडरेशन, अस्थायी प्रतिबंध, या अन्य आनुपातिक उपाय। तर्क दर्ज किया जाना चाहिए।", courtsRulesAppeals: "अपील: नए साक्ष्य या स्पष्ट प्रक्रियात्मक त्रुटि होने पर अनुमति है। अन्यथा कहा न जाए तो 7 दिनों के भीतर दायर की जानी चाहिए।", - courtsRulesPrivacy: "गोपनीयता और सुरक्षा का सम्मान करें। डॉक्सिंग, घृणा, या धमकियाँ हटा दी जाती हैं। न्यायाधीश जोखिम वाले लोगों की सुरक्षा के लिए रिकॉर्ड के भागों को संपादित या सील कर सकते हैं।", + courtsRulesPrivacy: "गोपनीयता और सुरक्षा का सम्मान करें। डॉक्सिंग, घृणा, या धमकियाँ हटा दी जाती हैं। न्यायाधीश जोखिम वाले निवासियों की सुरक्षा के लिए रिकॉर्ड के भागों को संपादित या सील कर सकते हैं।", courtsRulesMisconduct: "उत्पीड़न, हेरफेर, या मनगढ़ंत साक्ष्य तत्काल नकारात्मक समाधान का कारण बन सकते हैं।", courtsRulesGlossary: "मामला: विवाद का रिकॉर्ड। साक्ष्य: दावों का समर्थन करने वाली सामग्री। फैसला: उपाय के साथ निर्णय। अपील: फैसले की समीक्षा का अनुरोध।", courtsFilterActions: "कार्रवाई", @@ -1078,12 +1304,16 @@ module.exports = { exploreTrending: "अपने नेटवर्क में सबसे लोकप्रिय सामग्री खोजें।", votesButton: "मतदान", shopProductButton: "दुकान", + podcastButton: "पॉडकास्ट", + podcastEpisodeButton: "एपिसोड", + campaignButton: "अभियान", + logisticsRouteButton: "मार्ग", trendingTotalOpinions: "कुल राय", trendingNoContentMessage: "अभी तक कोई ट्रेंडिंग नहीं है।", trendingTotalCount: "कुल संख्या", tasksTitle: "कार्य", tasksDescription: "अपने नेटवर्क में कार्य खोजें और प्रबंधित करें।", - taskSearchPlaceholder: "कार्य खोजें...", + taskSearchPlaceholder: "कार्य में खोजें...", taskTitleLabel: "शीर्षक", taskDescriptionLabel: "विवरण", taskStartTimeLabel: "प्रारंभ समय", @@ -1102,7 +1332,7 @@ module.exports = { taskStatusOpen: "खुला", taskStatusInProgress: "प्रगति में", taskStatusClosed: "बंद", - taskAssignedTo: "को सौंपा गया", + taskAssignedTo: "सौंपा गया", taskAssignedChip: "सौंपा गया", taskUnassignedChip: "असौंपा", taskAssignees: "सौंपे गए", @@ -1112,6 +1342,7 @@ module.exports = { taskUpdateButton: "अपडेट", taskDeleteButton: "हटाएँ", taskFilterAll: "सभी", + taskFilterRecent: "हाल के", taskFilterMine: "मेरे", taskFilterOpen: "खुले", taskFilterInProgress: "प्रगति में", @@ -1139,14 +1370,13 @@ module.exports = { eventUpcomingDates: "आगामी तिथियाँ", eventsTitle: "कार्यक्रम", eventsDescription: "अपने नेटवर्क में कार्यक्रम खोजें और प्रबंधित करें।", - eventSearchPlaceholder: "कार्यक्रम खोजें...", + eventSearchPlaceholder: "कार्यक्रम में खोजें...", eventPrice: "मूल्य", eventStatus: "स्थिति", eventAllSectionTitle: "कार्यक्रम", eventMineSectionTitle: "आपके कार्यक्रम", eventCreateSectionTitle: "कार्यक्रम बनाएँ", eventUpdateSectionTitle: "कार्यक्रम अपडेट करें", - eventDeleteButton: "हटाएँ", eventCreateButton: "कार्यक्रम बनाएँ", eventAddToCalendar: "कैलेंडर में जोड़ें", eventVisitCalendar: "कैलेंडर देखें", @@ -1164,11 +1394,11 @@ module.exports = { viewPlace: "स्थान देखें", generatePdf: "PDF बनाएँ", sharePm: "PM से साझा करें", - galleryImages: "छवियाँ (अधिकतम आकार: 50MB प्रत्येक)", + galleryImages: "छवियाँ (अधिकतम आकार: 75MB प्रत्येक)", galleryPhotos: "छवियाँ", galleryAddPhoto: "छवि जोड़ें", galleryRemovePhoto: "हटाएँ", - galleryVideo: "वीडियो (अधिकतम आकार: 50MB)", + galleryVideo: "वीडियो (अधिकतम आकार: 75MB)", galleryAddVideo: "वीडियो जोड़ें", galleryRemoveVideo: "वीडियो हटाएँ", viewVotation: "मतदान देखें", @@ -1188,6 +1418,7 @@ module.exports = { eventDate: "दिनांक", eventAttendButton: "कार्यक्रम में शामिल हों", eventFilterAll: "सभी", + eventFilterRecent: "हाल के", eventFilterMine: "मेरे", eventFilterToday: "आज", eventFilterWeek: "इस सप्ताह", @@ -1214,7 +1445,7 @@ module.exports = { tagsFilterTop: "शीर्ष", tagsFilterCloud: "क्लाउड", tagsNoItems: "कोई टैग उपलब्ध नहीं।", - tagsSearchPlaceholder: "टैग खोजें...", + tagsSearchPlaceholder: "टैग में खोजें...", tagsTableHeaderTag: "टैग/लिंक:", tagsTableHeaderCount: "गणना:", transfersTitle: "स्थानांतरण", @@ -1222,7 +1453,10 @@ module.exports = { transfersFrom: "से", transfersTo: "को", transfersFilterAll: "सभी", + transfersFilterRecent: "हाल के", transfersFilterMine: "मेरे", + transfersFilterMinePending: "लंबित", + transfersFilterMineConfirmed: "हस्ताक्षरित", transfersFilterUBI: "UBI", transfersFilterMarket: "बाज़ार", transfersFilterTop: "शीर्ष", @@ -1237,7 +1471,6 @@ module.exports = { transfersCategoryEconomic: "आर्थिक", transfersCategoryTime: "समय", transfersCategoryTrust: "भरोसा", - transfersCategoryApply: "लागू करें", transfersUnitEco: "ECO", transfersUnitHours: "घं", transfersUnitTrust: "भरोसा", @@ -1267,7 +1500,7 @@ module.exports = { transfersDiscardedSectionTitle: "निरस्त स्थानांतरण", transfersCreateSectionTitle: "स्थानांतरण बनाएँ", transfersAllSectionTitle: "स्थानांतरण", - transfersSearchPlaceholder: "अवधारणा, टैग, उपयोगकर्ता खोजें...", + transfersSearchPlaceholder: "स्थानांतरण में खोजें...", transfersMinAmountLabel: "न्यूनतम राशि", transfersMaxAmountLabel: "अधिकतम राशि", transfersSortRecent: "सबसे हाल का", @@ -1289,6 +1522,7 @@ module.exports = { voteErrorTitle: "फ़ॉर्म जाँचें", voteErrorDeadlineMin: "समय-सीमा अभी से कम से कम {days} दिन आगे होनी चाहिए।", voteErrorGeneric: "वोट सहेजा नहीं जा सका।", + voteLocked: "इस मतदान पर पहले ही वोट या राय आ चुकी है, या यह संसद या न्यायालयों का है: अब इसे संपादित या हटाया नहीं जा सकता।", voteUpdateButton: "अपडेट", voteDeleteButton: "हटाएँ", voteOptionYes: "हाँ", @@ -1298,6 +1532,7 @@ module.exports = { voteFollowMajority: "बहुमत का पालन करें", voteNotInterested: "रुचि नहीं", voteFilterAll: "सभी", + voteFilterRecent: "हाल के", voteFilterMine: "मेरे", voteFilterOpen: "खुले", voteFilterClosed: "बंद", @@ -1313,7 +1548,6 @@ module.exports = { voteTags: "टैग", voteOpinions: "राय", novotes: "कोई मतदान प्रस्ताव उपलब्ध नहीं।", - voteBy: "द्वारा", voteCreatedAt: "बनाया गया", voteNoQuestion: "कोई प्रश्न प्रदान नहीं किया गया", voteUnknownCreator: "अज्ञात निर्माता", @@ -1323,6 +1557,7 @@ module.exports = { voteCommentsForumButton: 'चर्चा खोलें', voteCommentsSectionTitle: 'चर्चा खोलें', voteNoCommentsYet: 'अभी तक कोई टिप्पणी नहीं है। पहले उत्तर देने वाले बनें।', + commentsGoToLast: "अंतिम टिप्पणी पर जाएँ", voteNewCommentPlaceholder: 'अपनी टिप्पणी यहाँ लिखें…', voteNewCommentButton: 'टिप्पणी पोस्ट करें', voteNewCommentLabel: 'टिप्पणी जोड़ें', @@ -1333,6 +1568,8 @@ module.exports = { cvNameLabel: "पूरा नाम", cvDescriptionLabel: "सारांश", cvPhotoLabel: "फ़ोटो", + cvPdfLabel: "सीवी (PDF)", + cvVisitButton: "CV देखें", cvPersonalExperiencesLabel: "व्यक्तिगत अनुभव", cvPersonalSkillsLabel: "व्यक्तिगत कौशल (अल्पविराम से अलग)", cvOasisExperiencesLabel: "Oasis योगदान अनुभव", @@ -1362,9 +1599,9 @@ module.exports = { cvNoCV: "कोई CV नहीं मिला।", blogSubject: "विषय", blogMessage: "संदेश", - blogImage: "मीडिया अपलोड करें (अधिकतम-आकार: 50MB)", + blogImage: "मीडिया अपलोड करें (अधिकतम-आकार: 75MB)", blogMedia: "अनुलग्नक (8 चित्र और एक वीडियो तक)", - blogPublish: "पूर्वावलोकन", + blogPublish: "प्रकाशित करें", pollsTitle: "सर्वेक्षण", dataTitle: "मिलान", dataDescription: "अपने नेटवर्क के मेल खोजें और देखें।", @@ -1383,12 +1620,23 @@ module.exports = { dataKindTasks: "कार्य", dataKindReports: "रिपोर्ट्स", dataKindVotes: "मतदान", - dataSearchPlaceholder: "क्रॉस-डेटा में खोजें...", + dataSearchPlaceholder: "मिलान में खोजें...", dataCohesionTitle: "संसक्ति गुणांक (CC)", dataCcShort: "CC", dataCohesionHint: "नेटवर्क ने जो कुछ प्रकाशित किया है, उसके बीच औसत समानता।", dataAffinity: "समानता", dataCommonTerms: "कीवर्ड", + dataReasonFollowing: "आप इस निवासी का समर्थन करते हैं", + dataReasonTribe: "आपके साथ एक जनजाति साझा करता है", + dataReasonPinned: "आपके पिन किए गए कंटेंट से मेल खाता है", + dataReasonCv: "आपके CV के कौशल से मेल खाता है", + dataReasonContent: "आपके प्रकाशित कंटेंट से संबंधित", + dataReasonRelated: "आपकी रुचियों से संबंधित", + dataReasonAlike: "आपके जैसे निवासी द्वारा प्रकाशित", + dataReasonSupportsYou: "आपका समर्थन करता है", + dataReasonMutual: "पारस्परिक समर्थन", + dataReasonRated: "नेटवर्क द्वारा अच्छी रेटिंग", + dataReasonNear: "आपके पास", dataStatPeople: "CVs", dataStatConnected: "जुड़े हुए", dataStatSkills: "कौशल", @@ -1441,7 +1689,7 @@ module.exports = { pollStatusLabel: "स्थिति", pollStatusOpen: "खुला", pollStatusClosed: "बंद", - pollSearchPlaceholder: "सर्वेक्षण खोजें...", + pollSearchPlaceholder: "सर्वेक्षण में खोजें...", pollsNoItems: "कोई सर्वेक्षण नहीं मिला।", viewPoll: "सर्वेक्षण देखें", pollChatCreate: "सर्वेक्षण बनाएँ", @@ -1454,7 +1702,7 @@ module.exports = { blogFilterFavorites: "पसंदीदा", blogFilterTop: "शीर्ष", blogCreateButton: "ब्लॉग बनाएँ", - blogSearchPlaceholder: "ब्लॉग खोजें...", + blogSearchPlaceholder: "ब्लॉग में खोजें...", blogComments: "टिप्पणियाँ", blogAllowComments: "टिप्पणियाँ अनुमति दें", blogCommentsClosed: "लेखक ने इस ब्लॉग की टिप्पणियाँ बंद कर दी हैं।", @@ -1467,12 +1715,11 @@ module.exports = { forumCreateButton: "मंच बनाएँ", forumCreateSectionTitle: "मंच बनाएँ", forumDescription: "अपने नेटवर्क के अन्य निवासियों से खुलकर बात करें।", - forumSearchPlaceholder: "फ़ोरम खोजें...", + forumSearchPlaceholder: "मंच में खोजें...", forumFilterAll: "सभी", forumFilterMine: "मेरे", forumFilterRecent: "हाल के", forumFilterTop: "शीर्ष", - forumDeleteButton: "हटाएँ", forumParticipants: "प्रतिभागी", forumMessages: "संदेश", forumMessageLabel: "संदेश", @@ -1499,7 +1746,6 @@ module.exports = { imageFilterGallery: "गैलरी", imageCreateButton: "छवि अपलोड करें", imageUpdateButton: "अपडेट", - imageDeleteButton: "हटाएँ", imageAddFavoriteButton: "पसंदीदा जोड़ें", imageRemoveFavoriteButton: "पसंदीदा हटाएँ", imageFileLabel: "छवि फ़ाइल चुनें (.jpeg, .jpg, .png, .gif)", @@ -1511,13 +1757,14 @@ module.exports = { imageDescriptionPlaceholder: "वैकल्पिक", imageNoFile: "कोई छवि फ़ाइल प्रदान नहीं की गई", noImages: "कोई छवि उपलब्ध नहीं।", - imageSearchPlaceholder: "शीर्षक, टैग, विवरण, लेखक खोजें...", + imageSearchPlaceholder: "छवियाँ में खोजें...", imageSortRecent: "सबसे हाल का", imageSortOldest: "सबसे पुराना", imageSortTop: "सर्वाधिक मतदान", imageSearchButton: "खोजें", imageUpdatedAt: "अपडेट किया गया", imageNoMatch: "आपकी खोज से कोई छवि मेल नहीं खाती।", + feedSearchPlaceholder: "फ़ीड में खोजें...", feedTitle: "फ़ीड", createFeedTitle: "फ़ीड बनाएँ", createFeedButton: "फ़ीड भेजें!", @@ -1532,7 +1779,7 @@ module.exports = { alreadyRefeeded: "आपने इसे पहले ही रीफ़ीड कर दिया है।", activityTitle: "गतिविधि", yourActivity: "आपकी गतिविधि", - globalActivity: "वैश्विक गतिविधि", + globalActivity: "गतिविधि", activityList: "गतिविधि", activityDesc: "अपने नेटवर्क में नवीनतम गतिविधि देखें।", allButton: "सभी", @@ -1547,7 +1794,8 @@ module.exports = { meme: "मीम", typeRecent: "हाल के", typeTop: "शीर्ष", - typePost: "ब्लॉग्स", + typePost: "ब्लॉग", + activityUpdateLabel: "अपडेट", typeTribe: "जनजातियाँ", typeLarp: "L.A.R.P.", typeInhabitants: "निवासी", @@ -1682,7 +1930,7 @@ module.exports = { courtsNoCases: 'कोई मामला नहीं।', reportsTitle: "रिपोर्ट", reportsDescription: "अपने नेटवर्क की रिपोर्टों को प्रबंधित करें और उन पर नज़र रखें।", - reportsSearchPlaceholder: "रिपोर्ट खोजें...", + reportsSearchPlaceholder: "रिपोर्ट में खोजें...", reportsFilterAll: "सभी", reportsFilterRecent: "हाल के", reportsFilterTop: "शीर्ष", @@ -1730,7 +1978,6 @@ module.exports = { reportsUnderReviewSectionTitle: "समीक्षाधीन रिपोर्ट", reportsResolvedSectionTitle: "हल की गई रिपोर्ट", reportsInvalidSectionTitle: "अमान्य रिपोर्ट", - reportsTemplateSectionTitle: 'रिपोर्ट टेम्पलेट', reportsBugTemplateTitle: 'पुनरुत्पादन विवरण (बग)', reportsFeatureTemplateTitle: 'विवरण (सुविधाएँ)', reportsAbuseTemplateTitle: 'विवरण (दुरुपयोग)', @@ -1790,17 +2037,15 @@ module.exports = { tribeGallerySectionTitle: "जनजाति गैलरी", tribeRecentSectionTitle: "हाल की जनजातियाँ", tribeTopSectionTitle: "लोकप्रिय जनजातियाँ", - tribeviewTribeButton: "जनजाति देखें", - tribeviewSubTribeButton: "उप-जनजाति देखें", tribeRootLabel: "मूल", tribeDescription: "अपने नेटवर्क में जनजातियाँ खोजें या बनाएँ।", tribeMainTribeLabel: "मुख्य जनजाति", tribeCreateButton: "जनजाति बनाएँ", tribeUpdateButton: "अपडेट", tribeDeleteButton: "हटाएँ", - tribeImageLabel: "मीडिया अपलोड करें (अधिकतम-आकार: 50MB)", + tribeImageLabel: "मीडिया अपलोड करें (अधिकतम-आकार: 75MB)", tribeTitleLabel: "शीर्षक", - searchTribesPlaceholder: "नाम से जनजातियाँ फ़िल्टर करें …", + searchTribesPlaceholder: "जनजातियाँ में खोजें...", tribeTitlePlaceholder: "जनजाति का नाम", tribeDescriptionLabel: "विवरण", tribeDescriptionPlaceholder: "इस जनजाति का वर्णन करें", @@ -1813,6 +2058,15 @@ module.exports = { tribeIsAnonymousLabel: "स्थिति", tribeMembersCount: "सदस्य", tribeEnterInvite: "कोड दर्ज करें", + subscriptionTitle: "सदस्यताएँ", + subscriptionOn: "सदस्यता ली गई", + subscriptionOff: "सदस्यता नहीं", + subscriptionSubscribe: "सदस्यता लें", + subscriptionUnsubscribe: "सदस्यता रद्द करें", + subscriptionSubscribers: "सदस्य", + pmListLabel: "भेजें", + pmListNone: "— कोई सूची नहीं —", + pmListNoSubscribers: "कोई सदस्य नहीं", tribeLeaveButton: "छोड़ें", tribePublic: "सार्वजनिक", tribePrivate: "निजी", @@ -1845,16 +2099,16 @@ module.exports = { tribeSectionVideos: "वीडियो", tribeSectionDocuments: "दस्तावेज़", tribeSectionBookmarks: "बुकमार्क", - tribeSectionMaps: "MAPS", - tribeSectionPads: "PADS", - tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionMaps: "मानचित्र", + tribeSectionPads: "पैड्स", + tribeSectionChats: "चैट्स", + tribeSectionCalendars: "कैलेंडर", + tribePadCreate: "पैड बनाएँ", + tribeChatCreate: "चैट बनाएँ", + tribeCalendarCreate: "कैलेंडर बनाएँ", + tribePadsEmpty: "अभी तक कोई पैड नहीं।", + tribeChatsEmpty: "अभी तक कोई चैट नहीं।", + tribeCalendarsEmpty: "अभी तक कोई कैलेंडर नहीं।", tribeInhabitantsEmpty: "इस जनजाति में अभी तक कोई निवासी नहीं।", tribeEventCreate: "कार्यक्रम बनाएँ", tribeEventsEmpty: "अभी तक कोई कार्यक्रम नहीं।", @@ -1909,9 +2163,14 @@ module.exports = { tribeMediaTypeDocument: "दस्तावेज़", tribeMediaTypeBookmark: "बुकमार्क", tribeContentDelete: "हटाएँ", + tribeReachTribe: "जनजाति", + tribeReachNotice: "OASIS इसे सभी निवासियों को दिखाता है और CLEARNET खुले वेब पर भी। इसे खोलने से जनजाति के बाहर एक पढ़ने योग्य प्रति रह जाती है: जनजाति में वापस करने से यह छिप जाता है, पर पहले साझा की गई प्रतियाँ मिटाई नहीं जा सकतीं।", + tribeReachPrivate: "निजी जनजाति की सामग्री केवल जनजाति के भीतर ही रह सकती है।", + tribeReachOpenedBy: "खोलने वाले", + tribeReachChange: "कहाँ प्रकाशित है", tribeInviteCodeText: "आमंत्रण कोड: ", oasisVersionLabel: "Oasis संस्करण", - tribeInviteCodeHint: "इस कोड को साझा करें जिसे आप आमंत्रित करना चाहते हैं। वे /invites → Tribes से शामिल हो सकते हैं।", + tribeInviteCodeHint: "इस कोड को साझा करें जिसे आप आमंत्रित करना चाहते हैं। वे /invites से शामिल हो सकते हैं।", tribeStatusOpen: "खुला", tribeStatusClosed: "बंद", tribeStatusInProgress: "प्रगति में", @@ -1951,7 +2210,7 @@ module.exports = { tribeSearchMinChars: "खोजने के लिए कम से कम 2 अक्षर दर्ज करें।", agendaTitle: "एजेंडा", agendaDescription: "यहाँ आप अपने सभी सौंपे गए आइटम पा सकते हैं।", - agendaSearchPlaceholder: "एजेंडा खोजें...", + agendaSearchPlaceholder: "एजेंडा में खोजें...", agendaFilterAll: "सभी", agendaFilterToday: "आज", agendaFilterUpcoming: "आगामी", @@ -1972,6 +2231,7 @@ module.exports = { agendaNoItems: "कोई सौंपे गए आइटम नहीं मिले।", agendaDiscardButton: "निरस्त करें", agendaRestoreButton: "पुनर्स्थापित करें", + agendaRemoveButton: "हटाएँ", agendaMembersCount: "सदस्य", agendaLocationLabel: "स्थान", agendaYes: "हाँ", @@ -2038,8 +2298,8 @@ module.exports = { pmComposeTitle: "संदेश भेजें", pmRecipients: "प्राप्तकर्ता", pmRecipientsHint: "अल्पविराम से अलग Oasis IDs दर्ज करें", - pmLimitsHint: "प्रति संदेश अधिकतम 7 प्राप्तकर्ता।", - pmTextPlaceholder: "मुख्य पाठ 7000 वर्णों तक सीमित।", + pmLimitsHint: "प्रति संदेश अधिकतम 7 निवासी।", + pmTextPlaceholder: "अपना संदेश लिखें।", pmSubject: "विषय", pmSubjectHint: "संदेश विषय दर्ज करें", pmText: "संदेश", @@ -2061,8 +2321,8 @@ module.exports = { pmCrypterCharsLabel: "अक्षर", pmInvalidRecipients: "अमान्य Oasis ID (यह @…=.ed25519 जैसा होना चाहिए)।", pmEncryptionLabel: "एन्क्रिप्शन", - private: "निजी", - privateDescription: "आपके एन्क्रिप्टेड संदेश।", + private: "इनबॉक्स", + privateDescription: "अपने निजी संदेश प्रबंधित करें।", privateInbox: "इनबॉक्स", privateSent: "भेजे गए", privateDelete: "हटाएँ", @@ -2097,7 +2357,7 @@ module.exports = { inboxProjectPledgedTitle: "आपकी परियोजना के लिए नई प्रतिज्ञा", pmHasPledged: "ने प्रतिज्ञा की है", pmToYourProject: "आपकी परियोजना को", - blockchain: 'BlockExplorer', + blockchain: "ब्लॉक एक्सप्लोरर", blockchainDescription: 'ब्लॉकचेन में ब्लॉक खोजें और विज़ुअलाइज़ करें।', blockchainNoBlocks: 'ब्लॉकचेन में कोई ब्लॉक नहीं मिला।', blockchainStatsTitle: 'आँकड़े', @@ -2118,24 +2378,24 @@ module.exports = { visitContent: "सामग्री देखें", favoriteAdd: "पसंदीदा में पिन करें", pmContentTooltip: "निजी संदेश भेजें", + phoneCallTooltip: "फ़ोन कॉल करें", favoriteRemove: "पसंदीदा से हटाएँ", reportContent: "सामग्री की रिपोर्ट करें", banking: 'बैंकिंग', bankingTitle: 'बैंकिंग', - bankingDescription: "ECOin अर्थव्यवस्था: शेष, यूबीआई, कर और उद्योग मूल्य।", + bankingDescription: "ECOin की सामूहिक अर्थव्यवस्था।", bankOverview: 'अवलोकन', bankEpochs: 'युग', bankRules: 'नियम', pending: 'लंबित', closed: 'बंद', - bankViewTx: 'Tx देखें', bankClaimNow: 'अभी दावा करें', - bankClaimUBI: 'UBI का दावा करें!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: 'UBI का दावा करें', + bankClaimAndPay: "दावा करें और भुगतान करें", + bankClaimedPending: "दावा लंबित...", + bankStatusClaimed: "दावा किया गया", + bankStatusExpired: "समाप्त", + bankPubOnly: "केवल PUB के लिए कार्य", bankEpoch: 'युग', bankPool: 'पूल (यह युग)', bankWeightsSum: 'भारों का योग', @@ -2154,8 +2414,8 @@ module.exports = { bankViewEpoch: 'युग देखें', bankYourIndustryBalance: "आपका औद्योगिक हिस्सा", bankYourUbiMonth: "आपकी UBI (इस माह)", - bankYourFundsMonth: "आपकी निधि (इस माह)", - bankIndustryBalance: "औद्योगिक उत्पादन (अनुमानित)", + bankYourFundsMonth: "कुल निधि", + bankIndustryBalance: "औद्योगिक उत्पादन", bankUserBalance: 'आपका शेष', bankAddresses: 'पते', bankNoAddresses: 'कोई पता नहीं मिला।', @@ -2163,6 +2423,10 @@ module.exports = { bankAddress: 'पता', bankAddAddressTitle: 'ECOIN पता जोड़ें', bankAddAddressUser: 'Oasis ID', + bankAddAddressLabel: "लेबल (वैकल्पिक)", + bankAddAddressLabelPlaceholder: "बॉब का वॉलेट, ऐलिस की दुकान…", + bankAddAddressUserOptional: "Oasis ID (वैकल्पिक)", + bankAddressUnnamed: "बिना नाम का पता", bankAddAddressAddress: 'ECOIN पता', bankAddAddressSave: 'सहेजें', bankAddressAdded: 'पता जोड़ा गया', @@ -2180,11 +2444,11 @@ module.exports = { bankAddressDeleteConfirm: 'यह पता हटाएँ?', search: 'खोजें!', bankLocal: 'स्थानीय', - bankFromOasis: 'Oasis', + bankFromOasis: "नेटवर्क", pubIdLabel: "PUB ID", bankUbiAvailableNo: "कोई धन नहीं!", bankUbiAvailableOk: "उपलब्ध!", - bankUbiAvailability: "UBI (नेटवर्क)", + bankUbiAvailability: "UBI", bankAlreadyClaimedThisMonth: "इस महीने पहले ही दावा किया गया", bankUbiThisMonth: "UBI (इस महीने)", bankUbiLastClaimed: "UBI (अंतिम दावा)", @@ -2214,6 +2478,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "ट्रांज़िट में एन्क्रिप्टेड", shopAddFavorite: "पसंदीदा जोड़ें", shopRemoveFavorite: "पसंदीदा हटाएं", shopOpen: "खुली", @@ -2221,6 +2486,13 @@ module.exports = { shopMakePrivate: "निजी बनाएं (E2E)", shopMakePublic: "सार्वजनिक बनाएं", shopProducts: "उत्पाद", + shopFilterAll: "सभी", + shopFilterRecent: "हाल के", + shopFilterMine: "मेरे", + shopFilterTop: "टॉप", + shopFilterProducts: "उत्पाद", + shopFilterPrices: "मूल्य", + shopFilterFavorites: "पसंदीदा", shopProductAdd: "उत्पाद जोड़ें", shopProductTitle: "उत्पाद", shopProductUpdate: "उत्पाद अपडेट", @@ -2248,7 +2520,7 @@ module.exports = { marketVisitItem: "आइटम देखें", shopStatus: "स्थिति", shopCreatedAt: "बनाया गया", - shopSearchPlaceholder: "दुकानें खोजें...", + shopSearchPlaceholder: "दुकानें में खोजें...", shopUrl: "URL", shopLocation: "स्थान", shopTags: "टैग", @@ -2258,37 +2530,40 @@ module.exports = { shopTitlePlaceholder: "आपकी दुकान का नाम", shopDescriptionPlaceholder: "आपकी दुकान का विस्तृत विवरण", shopLocationPlaceholder: "शहर, देश", - shopTagsPlaceholder: "टैग1, टैग2, टैग3", + shopTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", mapTitlePlaceholder: "मानचित्र शीर्षक", shopProductFeatured: "विशेष उत्पाद", - shopSendToMarket: "Send to Market", + shopSendToMarket: "बाज़ार में भेजें", typeShop: "दुकानें", typeShopProduct: "दुकान उत्पाद", bankExchange: 'विनिमय', bankExchangeCurrentValue: 'ECOin मूल्य (1h)', - bankTotalSupply: 'ECOin कुल आपूर्ति', - bankEcoinHours: "ECOin समय समतुल्यता", + bankTotalSupply: "ECOin कुल आपूर्ति", + bankPubsSupply: "PUBs पूल की ECOin आपूर्ति", + bankHoldingSupply: "UBI से बाहर ECOin आपूर्ति", + bankEcoinHours: "ECOin समय समतुल्यता (1 ECO)", bankHoursOfWork: 'घंटे', bankUnitMs: "मि.से.", bankUnitSeconds: "सेकंड", bankUnitMinutes: "मिनट", bankUnitDays: "दिन", bankExchangeNoData: 'कोई डेटा उपलब्ध नहीं', - bankInflation: 'ECOin मुद्रास्फीति (anual)', - bankInflationMonthly: 'ECOin मुद्रास्फीति (mensual)', + bankInflation: "ECOin मुद्रास्फीति (वार्षिक)", + bankInflationMonthly: "ECOin मुद्रास्फीति (मासिक)", bankExchangeChartTitle: 'समय के साथ ECOin मूल्य की प्रगति', bankExchangeChartValue: 'मूल्य (ECO/घंटा)', bankExchangeChartSupply: 'आपूर्ति', bankExchangeChartInflation: 'मुद्रास्फीति %', bankExchangeChartEmpty: 'अभी पर्याप्त नमूने नहीं — बाद में लौटें', - bankCurrentSupply: 'ECOin वर्तमान आपूर्ति', + bankCurrentSupply: "ECOin वर्तमान आपूर्ति", bankingSyncStatus: 'ECOin स्थिति', bankingSyncStatusSynced: 'सिंक किया गया', - bankingSyncStatusOutdated: 'पुराना', + bankingSyncStatusOutdated: "असिंक", statsTitle: 'सांख्यिकी', statistics: "सांख्यिकी", statsDescription: "अपने नेटवर्क के बारे में सांख्यिकी खोजें।", ALLButton: "सभी", + RECENTButton: "हाल के", MINEButton: "मेरे", TOMBSTONEButton: "टॉम्बस्टोन", statsCreatedAt: "बनाया गया", @@ -2296,7 +2571,8 @@ module.exports = { statsYourOpinions: "राय", statsNetworkContent: "सामग्री", statsNetworkOpinions: "राय", - statsEcoWalletLabel: "ECOIN वॉलेट", + statsEcoWalletLabel: "ECOin वॉलेट", + walletAddressCopyHint: "चुनने के लिए क्लिक करें, फिर कॉपी करें", statsEcoWalletNotConfigured: "कॉन्फ़िगर नहीं!", statsBookmark: "बुकमार्क", statsEvent: "कार्यक्रम", @@ -2318,6 +2594,8 @@ module.exports = { statsTransfer: "स्थानांतरण", statsAiExchange: "AI", statsPUBs: 'PUBs', + statsSyncedPeers: "पीयर", + statsTotalLabel: "कुल", statsPost: "पोस्ट", statsSize: "कुल (आकार)", statsBlockchainSize: "ब्लॉकचेन (आकार)", @@ -2369,8 +2647,14 @@ module.exports = { aiPrompt: "एक सूचनाप्रद और सटीक प्रतिक्रिया प्रदान करें।", aiConfiguration: "प्रॉम्प्ट सेट करें", aiPromptUsed: "प्रॉम्प्ट", - aiClearHistory: "चैट इतिहास साफ़ करें", - aiServerError: "AI उत्तर नहीं दे सका। कृपया पुनः प्रयास करें।", + aiClearHistory: "इतिहास साफ़ करें", + aiServerError: "मैं ठीक से उत्तर नहीं दे पा रहा हूँ। कृपया फिर से प्रयास करें।", + aiStatusLoading: "42 मॉडल लोड कर रहा है। थोड़ी देर में फिर पूछें।", + aiThinking: "सोच रहा है", + aiWaiting: "प्रतीक्षा में", + aiStatusMissingModel: "AI मॉडल स्थापित नहीं है: src/AI/oasis-42-1-chat.Q4_K_M.gguf नहीं मिला।", + aiExportFineTuning: "Fine-Tuning निर्यात करें", + aiFactsTitle: "आपके नेटवर्क का डेटा", aiInputPlaceholder: "Oasis क्या है?", typeAiExchange: "AI", aiApproveTrain: "सामूहिक प्रशिक्षण में जोड़ें", @@ -2399,7 +2683,7 @@ module.exports = { marketFilterDiscarded: "निरस्त", marketFilterRecent: "हाल के", marketFilterMyBids: "बोलियाँ", - marketCreateButton: "आइटम बनाएँ", + marketCreateButton: "कुछ बेचें", marketItemType: "प्रकार", marketItemTitle: "शीर्षक", marketItemAvailable: "समय सीमा", @@ -2426,8 +2710,8 @@ module.exports = { marketItemSeller: "विक्रेता", marketNoItems: "अभी तक कोई आइटम उपलब्ध नहीं।", marketYourBid: "आपकी बोली", - marketCreateFormImageLabel: "मीडिया अपलोड करें (अधिकतम-आकार: 50MB)", - marketSearchPlaceholder: "शीर्षक या टैग खोजें", + marketCreateFormImageLabel: "मीडिया अपलोड करें (अधिकतम-आकार: 75MB)", + marketSearchPlaceholder: "बाज़ार में खोजें...", marketMinPriceLabel: "न्यूनतम मूल्य", marketMaxPriceLabel: "अधिकतम मूल्य", marketSortRecent: "सबसे हाल का", @@ -2493,8 +2777,7 @@ module.exports = { housingSetClosed: "अनुपलब्ध करें", housingCreateButton: "जगह बनाएं", housingUpdateButton: "अपडेट", - housingDeleteButton: "हटाएं", - housingSearchPlaceholder: "जगहें खोजें...", + housingSearchPlaceholder: "आवास में खोजें...", housingSearchButton: "खोजें", housingMinPrice: "न्यूनतम कीमत", housingMaxPrice: "अधिकतम कीमत", @@ -2550,7 +2833,7 @@ module.exports = { jobLocationPresencial: "स्थानीय", jobLocationRemote: "दूरस्थ", jobVacantsPlaceholder: "पदों की संख्या", - jobImage: "मीडिया अपलोड करें (अधिकतम-आकार: 50MB)", + jobImage: "मीडिया अपलोड करें (अधिकतम-आकार: 75MB)", jobTasks: "कार्य", jobType: "नौकरी प्रकार", jobTime: "नौकरी समय", @@ -2572,12 +2855,11 @@ module.exports = { jobsExchangeSkill: "बदले में चाहिए कौशल", jobTimePartial: "अंशकालिक", jobTimeComplete: "पूर्णकालिक", - jobsDeleteButton: "हटाएँ", jobsUpdateButton: "अपडेट", jobsFilterApplied: "आवेदित", jobsAppliedTitle: "मेरे आवेदन", jobsAppliedBadge: "आवेदित", - jobsSearchPlaceholder: "शीर्षक, टैग, विवरण खोजें...", + jobsSearchPlaceholder: "नौकरियाँ में खोजें...", jobsMinSalaryLabel: "न्यूनतम वेतन", jobsMaxSalaryLabel: "अधिकतम वेतन", jobsSortRecent: "सबसे हाल का", @@ -2589,7 +2871,7 @@ module.exports = { jobSetOpen: "खुली सेट करें", jobsTagsLabel: "टैग", noJobsMatch: "आपकी खोज से कोई नौकरी मेल नहीं खाती।", - industrySearchPlaceholder: "फैक्टरियाँ खोजें…", + industrySearchPlaceholder: "उद्योग में खोजें...", industryAllSectors: "सभी क्षेत्र", industryVisitButton: "उद्योग देखें", industryAdvanceTo: "पर सेट करें", @@ -2663,7 +2945,6 @@ module.exports = { industryBuildStatus_FAILED: "विफल", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "एक नौकरी आपके CV से मेल खाती है", jobsBotMatchIntro: "एक नौकरी आपके CV से मेल खाती है।", jobsBotMatchJob: "नौकरी", @@ -2690,7 +2971,7 @@ module.exports = { industryRulesIntro: "Industry निवासियों को उत्पादन के साधनों का सामूहिक स्वामित्व देता है: एक फैक्टरी नेटवर्क-स्वामित्व वाली कार्यशाला है जिसे कोई निजी रूप से नहीं रखता।", industryRulesSteward: "संस्थापक प्रबंधक होता है — संरक्षक, मालिक नहीं: वह फैक्टरी को अकेले बेच, निजीकृत या विघटित नहीं कर सकता।", industryRulesMembership: "सदस्यता नीति तय करती है कि कैसे जुड़ें: खुला (तुरंत), मतदान द्वारा (सदस्यों के वोट से स्वीकृत) या केवल आमंत्रण (पहले किसी सदस्य को आमंत्रित करना होगा)।", - industryRulesQuorum: "कोरम वह न्यूनतम सदस्य संख्या है जिन्हें किसी निर्णय के गिनने के लिए मतदान करना चाहिए, ताकि छोटी फैक्टरी एक व्यक्ति द्वारा तय न हो।", + industryRulesQuorum: "कोरम वह न्यूनतम सदस्य संख्या है जिन्हें किसी निर्णय के गिनने के लिए मतदान करना चाहिए, ताकि छोटी फैक्टरी एक निवासी द्वारा तय न हो।", industryRulesMajority: "बहुमत पारित होने के लिए आवश्यक मतों का अंश है (0.5 = आधा, 1 = सर्वसम्मति); निर्णय तब पारित होता है जब हाँ वोट कम से कम max(कोरम, सदस्य × बहुमत) तक पहुँचें।", industryRulesDecisions: "सदस्य नए लोगों को स्वीकारने, बिल्ड स्वीकृत करने और फैक्टरी विघटित करने के लिए मतदान करते हैं; प्रबंधक इन सामूहिक निर्णयों को नहीं टाल सकता।", industryRulesBlueprints: "ब्लूप्रिंट स्वतंत्र (COPYLEFT) व्यंजन हैं — इनपुट (सामग्री, श्रम घंटे, कौशल) और आउटपुट (एक भौतिक या डिजिटल वस्तु) — और कोई भी इन्हें फोर्क कर सकता है।", @@ -2716,6 +2997,7 @@ module.exports = { modulesIndustryLabel: "उद्योग", modulesIndustryDescription: "उत्पादन के साधनों को सामूहिक रूप से प्रबंधित करने का मॉड्यूल।", industryFilterAll: "सभी", + industryFilterRecent: "हाल के", industryFilterMember: "सदस्य", industryFilterBlueprints: "ब्लूप्रिंट", industryFilterBuilds: "उत्पादन", @@ -2724,6 +3006,7 @@ module.exports = { industryFilterPaused: "रोकी गई", industryFilterDissolved: "विघटित", industryAllTitle: "उद्योग", + industryRecentTitle: "हाल की फैक्टरियाँ", industryMemberTitle: "जिन फैक्टरियों में मैं हूँ", industryMineTitle: "मेरी फैक्टरियाँ", industryActiveTitle: "चालू", @@ -2771,7 +3054,7 @@ module.exports = { industryPolicy_invite: "केवल आमंत्रण", projectsTitle: "परियोजनाएँ", projectsDescription: "अपने नेटवर्क में समुदाय-संचालित परियोजनाएँ बनाएँ, वित्तपोषित करें और अनुसरण करें।", - projectSearchPlaceholder: "परियोजनाएँ खोजें...", + projectSearchPlaceholder: "परियोजनाएँ में खोजें...", projectCreateProject: "परियोजना बनाएँ", projectCreateButton: "परियोजना बनाएँ", projectUpdateButton: "अपडेट", @@ -2794,7 +3077,7 @@ module.exports = { projectRecentTitle: "हाल की परियोजनाएँ", projectTopTitle: "शीर्ष वित्तपोषित", projectTitlePlaceholder: "परियोजना का नाम", - projectImage: "मीडिया अपलोड करें (अधिकतम-आकार: 50MB)", + projectImage: "मीडिया अपलोड करें (अधिकतम-आकार: 75MB)", projectDescription: "विवरण", projectDescriptionPlaceholder: "कहानी और लक्ष्य बताएँ…", projectGoal: "लक्ष्य (ECO)", @@ -2866,15 +3149,13 @@ module.exports = { modulesModuleName: "नाम", modulesModuleDescription: "विवरण", modulesModuleStatus: "स्थिति", - modulesTotalModulesLabel: "लोड किए गए मॉड्यूल", + modulesTotalModulesLabel: "कुल", modulesEnabledModulesLabel: "सक्षम", modulesDisabledModulesLabel: "अक्षम", modulesInvitesLabel: "आमंत्रण", modulesInvitesDescription: "आमंत्रण कोड प्रबंधित और लागू करने का मॉड्यूल।", modulesWalletLabel: "वॉलेट", modulesWalletDescription: "अपनी डिजिटल संपत्तियाँ (ECOin) प्रबंधित करने का मॉड्यूल।", - modulesLegacyLabel: "विरासत", - modulesLegacyDescription: "अपनी गुप्त कुंजी (निजी कुंजी) को जल्दी और सुरक्षित रूप से प्रबंधित करने का मॉड्यूल।", modulesCipherLabel: "सिफर", modulesCipherDescription: "अपने टेक्स्ट को सममित रूप से एन्क्रिप्ट और डिक्रिप्ट करने का मॉड्यूल (साझा पासवर्ड का उपयोग करके)।", modulesBookmarksLabel: "बुकमार्क", @@ -2936,20 +3217,23 @@ module.exports = { modulesProjectsLabel: "परियोजनाएँ", modulesProjectsDescription: "परियोजनाएँ खोजने, क्राउड-फंडिंग करने और प्रबंधित करने का मॉड्यूल।", modulesBankingLabel: "बैंकिंग", - modulesBankingDescription: "ECOIN का वास्तविक मूल्य निर्धारित करने और सामान्य कोषागार का उपयोग करके UBI वितरित करने का मॉड्यूल।", + modulesBankingDescription: "साझा कोष प्रबंधन मॉड्यूल: ECOIN मूल्य, कर और सार्वभौमिक बुनियादी आय।", modulesFavoritesLabel: "पसंदीदा", modulesFavoritesDescription: "अपनी पसंदीदा सामग्री प्रबंधित करने का मॉड्यूल।", fileTooLargeTitle: "फ़ाइल बहुत बड़ी", - fileTooLargeMessage: "फ़ाइल अधिकतम अनुमत आकार (50 MB) से अधिक है। कृपया एक छोटी फ़ाइल चुनें।", + fileTooLargeMessage: "फ़ाइल अधिकतम अनुमत आकार (75 MB) से अधिक है। कृपया एक छोटी फ़ाइल चुनें।", goBack: "वापस जाएँ", + errorDismiss: "ठीक", errorPageTitle: "कुछ गलत हो गया", + errorNoAllocation: "इस दावे के लिए कोई आवंटन नहीं मिला।", + errorInvalidClaim: "इस आवंटन का दावा नहीं किया जा सकता।", visibilityLabel: "दृश्यता", + clearnetPrivateConflict: "निजी, छिपी, बंद, केवल आमंत्रण वाली या सशुल्क सामग्री CLEARNET पर प्रकाशित नहीं की जा सकती। OASIS चुनें या इसे सार्वजनिक करें।", visibilityPublic: "सार्वजनिक", visibilityHidden: "छिपा हुआ", visibilityMakePublic: "सार्वजनिक करें", visibilityMakeHidden: "छिपाएँ", invitesInhabitantsTitle: "निवासी", - invitesInhabitantsFollow: "फॉलो करें", aiNavPlaceholder: "बताएँ आपको क्या चाहिए...", aiNavDisabled: "AI नेविगेशन अक्षम है।", aiNavResultsTitle: "AINav सुझाव", @@ -2960,7 +3244,7 @@ module.exports = { modulesAINavDescription: "नेटवर्क की सामग्री पर प्राकृतिक भाषा में प्रश्न करने का मॉड्यूल।", directConnectDescription: "किसी पीयर से सीधे कनेक्ट करने के लिए उनका IP पता, पोर्ट और सार्वजनिक कुंजी दर्ज करें। पीयर को एक अनुसरित कनेक्शन के रूप में जोड़ा जाएगा।", peerHost: "IP", - peerPort: "पोर्ट (डिफ़ॉल्ट: 8008)", + peerPort: "पोर्ट", peerPublicKey: "सार्वजनिक कुंजी (@...ed25519)", connectAndFollow: "कनेक्ट करें", modulesPresetTitle: "सामान्य कॉन्फ़िगरेशन", @@ -2969,15 +3253,15 @@ module.exports = { workflowsSet: "वर्कफ़्लो लागू करें", workflow_default: "डिफ़ॉल्ट", workflow_jobs: "रोज़गार", - workflow_ruling: "शासन", - workflow_politics: "राजनीति", workflow_social: "सामाजिक", workflow_business: "व्यापार", workflow_night: "रात", + workflow_kids: "बच्चे", + workflow_activists: "कार्यकर्ता", workflow_mobile: "मोबाइल", statsCarbonFootprintTitle: "कार्बन फुटप्रिंट", statsCarbonTombstone: "टॉम्बस्टोनिंग फुटप्रिंट", - uploadMedia: "मीडिया अपलोड करें (अधिकतम-आकार: 50MB)", + uploadMedia: "मीडिया अपलोड करें (अधिकतम-आकार: 75MB)", feedOpenDiscussion: "चर्चा खोलें", feedDetailTitle: "फ़ीड", feedPostComment: "टिप्पणी पोस्ट करें", @@ -2985,7 +3269,7 @@ module.exports = { noComments: "अभी तक कोई टिप्पणी नहीं", mapsLabel: "मानचित्र", mapTitle: "मानचित्र", - mapDescription: "अपने नेटवर्क में ऑफ़लाइन मानचित्र खोजें और प्रबंधित करें।", + mapDescription: "अपने नेटवर्क में ऑफ़लाइन मानचित्र प्रबंधित और साझा करें।", mapMineSectionTitle: "आपके मानचित्र", mapCreateSectionTitle: "मानचित्र बनाएं", mapUpdateSectionTitle: "मानचित्र अपडेट करें", @@ -3014,7 +3298,7 @@ module.exports = { mapTypeOpen: "खुला", mapTypeClosed: "बंद", mapTagsLabel: "टैग", - mapTagsPlaceholder: "अल्पविराम से अलग टैग दर्ज करें", + mapTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", mapUrlLabel: "मानचित्र URL", mapMarkersTitle: "मार्कर", mapMarkerDefault: "मार्कर", @@ -3025,9 +3309,22 @@ module.exports = { markerImageLabel: "मार्कर छवि", mapAddMarkerTitle: "मार्कर जोड़ें", mapAddMarkerButton: "मार्कर जोड़ें", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "मार्कर साफ़ करें", mapApplyZoom: "ज़ूम लागू करें", - mapSearchPlaceholder: "विवरण, टैग, लेखक खोजें...", + mapPlaceSearchPlaceholder: "कोई स्थान या देश खोजें...", + mapNoPlaces: "कोई स्थान नहीं मिला।", + mapClusterTitle: "मार्कर यहाँ, ज़ूम करने के लिए क्लिक करें", + mapDeleteMarker: "मार्कर हटाएँ", + mapZoomIn: "ज़ूम इन", + mapZoomOut: "ज़ूम आउट", + mapFitMarkers: "सभी मार्कर दिखाएँ", + mapPanLabel: "मानचित्र हिलाएँ", + mapCenterHere: "यहाँ केंद्रित करें", + mapNear: "के पास", + mapPickedSpot: "चुना गया स्थान", + mapPickHint: "स्थान चुनने के लिए मानचित्र पर क्लिक करें।", + mapClearPick: "साफ़ करें", + mapSearchPlaceholder: "मानचित्र में खोजें...", mapSearchButton: "खोजें", mapUpdatedAt: "अपडेट किया गया", mapNoMatch: "आपकी खोज से मेल खाने वाला कोई मानचित्र नहीं।", @@ -3038,31 +3335,602 @@ module.exports = { typeMapMarker: "मानचित्र मार्कर", modulesMapLabel: "मानचित्र", modulesMapDescription: "ऑफ़लाइन मानचित्रों को प्रबंधित और साझा करने का मॉड्यूल।", + emergenciesTitle: "आपातकाल", + modulesEmergenciesLabel: "आपातकाल", + modulesEmergenciesDescription: "नेटवर्क में आपातकाल घोषित करने, पुष्टि करने और फ़ॉलो करने का मॉड्यूल।", + emergenciesDescription: "नेटवर्क में आपातकाल घोषित करने, पुष्टि करने और फ़ॉलो करने का मॉड्यूल।", + emergencyCreate: "आपातकाल बनाएँ", + emergencyUpdate: "अपडेट", + emergencyDelete: "हटाएँ", + emergencyTitleLabel: "शीर्षक", + emergencyTitlePlaceholder: "क्या हो रहा है?", + emergencyTextLabel: "विवरण", + emergencyTextPlaceholder: "कहाँ, कब से, क्या करें...", + emergencyCategoryLabel: "श्रेणी", + emergencyCategoryWeather: "मौसम", + emergencyCategoryInfrastructure: "बुनियादी ढाँचा", + emergencyCategoryHealth: "स्वास्थ्य", + emergencyCategorySecurity: "सुरक्षा", + emergencyCategoryLost: "खोया-पाया", + emergencyCategoryNeighborhood: "पड़ोस", + emergencyExpiresLabel: "समाप्ति", + emergencyExpires1d: "1 दिन", + emergencyExpires3d: "3 दिन", + emergencyExpires7d: "1 सप्ताह", + emergencyExpires30d: "1 महीना", + emergencyExpiresNone: "अनिश्चित", + emergencyExpiresKeep: "वर्तमान तारीख रखें", + emergencyTagsLabel: "टैग", + emergencyTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + emergencyFilterAll: "सभी", + emergencyFilterMine: "मेरे", + emergencyFilterRecent: "हाल के", + emergencySeverityUnverified: "असत्यापित", + emergencySeverityLow: "कम", + emergencySeverityMedium: "मध्यम", + emergencySeverityHigh: "उच्च", + emergencyStatusActive: "सक्रिय", + emergencyStatusResolved: "हल हो गया", + emergencyStatusExpired: "समाप्त", + emergencyConfirmations: "पुष्टियाँ", + emergencyConfirmButton: "पुष्टि करें", + emergencyConfirmed: "आपने इस आपातकाल की पुष्टि की", + emergencyResolveButton: "हल करें", + emergencyReopenButton: "फिर से खोलें", + emergencyGenerateReport: "रिपोर्ट बनाएँ", + emergencyUpdateEdited: "संपादित", + emergencyLastUpdate: "अंतिम अपडेट", + emergencyUpdatesTitle: "अंतिम अपडेट:", + emergencyNoUpdates: "अभी कोई अपडेट नहीं।", + emergencyAddUpdate: "अपडेट पोस्ट करें", + emergencyUpdatePlaceholder: "क्या बदला?", + emergencyNoItems: "कोई आपातकाल नहीं मिला।", + emergencyExpiresAt: "समाप्ति", + emergencyBannerLabel: "सक्रिय आपातकाल", + emergencyCreateSectionTitle: "आपातकाल बनाएँ", + emergencyUpdateSectionTitle: "आपातकाल अपडेट करें", + emergencySearchPlaceholder: "आपातकाल में खोजें...", + typeEmergency: "आपातकाल", + typeEmergencyConfirm: "आपातकाल पुष्टि", + typeEmergencyUpdate: "आपातकाल अपडेट", + statsEmergency: "आपातकाल", + statsEmergencyConfirm: "आपातकाल पुष्टियाँ", + statsEmergencyUpdate: "आपातकाल अपडेट", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "आपके द्वारा फ़ॉलो किए गए आपातकाल में अपडेट है।", + emergencyBotResolvedTitle: "आपके द्वारा फ़ॉलो किया गया आपातकाल हल हो गया है।", + dataKindEmergencies: "आपातकाल", + mailingTitle: "मेलिंग सूचियाँ", + modulesMailingLabel: "मेलिंग सूचियाँ", + modulesMailingDescription: "अपने नेटवर्क में मेलिंग सूचियाँ खोजें और प्रबंधित करें।", + modulesPhoneLabel: "फ़ोन", + modulesPhoneDescription: "अन्य निवासियों को एंड-टू-एंड एन्क्रिप्टेड वीडियो कॉल करने का मॉड्यूल।", + mailingDescription: "अपने नेटवर्क में मेलिंग सूचियाँ खोजें और प्रबंधित करें।", + mailingCreate: "मेलिंग सूची बनाएँ", + mailingUpdate: "अपडेट करें", + mailingArchive: "संग्रहित करें", + mailingActivate: "सक्रिय करें", + mailingCreateSectionTitle: "मेलिंग सूची बनाएँ", + mailingUpdateSectionTitle: "मेलिंग सूची अपडेट करें", + mailingTitleLabel: "शीर्षक", + mailingTitlePlaceholder: "मेलिंग सूची का नाम", + mailingDescriptionLabel: "विवरण", + mailingDescriptionPlaceholder: "यह सूची किस बारे में है?", + mailingTypeLabel: "मेलिंग सूची का प्रकार", + mailingTypeOpen: "खुली", + mailingTypeClosed: "बंद", + mailingStatusLabel: "स्थिति", + mailingStatusActive: "सक्रिय", + mailingStatusArchived: "संग्रहित", + mailingMembersLabel: "निवासियों को आमंत्रित करें", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "सदस्य", + mailingTagsLabel: "टैग", + mailingTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + mailingFilterAll: "सभी", + mailingFilterMine: "मेरी", + mailingFilterSubscribed: "सदस्यता ली गई", + mailingFilterRecent: "हाल की", + mailingHistoryDate: "तारीख", + mailingHistoryThreads: "थ्रेड", + mailingHistoryInhabitants: "निवासी", + mailingHistorySearchPlaceholder: "इतिहास में खोजें...", + mailingNoMessages: "अभी कोई संदेश नहीं।", + mailingNoItems: "कोई मेलिंग सूची नहीं मिली।", + mailingSearchPlaceholder: "मेलिंग सूचियों में खोजें...", + mailingWriteButton: "संदेश लिखें", + mailingWriteTitle: "सूची को लिखें", + mailingReplyTitle: "सूची को उत्तर दें", + mailingSend: "भेजें", + mailingTextPlaceholder: "आपका संदेश सूची के हर प्रतिभागी तक पहुँचता है।", + mailingParticipants: "प्रतिभागी", + inboxFilterMailing: "मेलिंग सूचियाँ", + mailingReplyPlaceholder: "सूची को उत्तर दें...", + mailingReplyToList: "सूची को उत्तर दें", + typeMailingList: "मेलिंग सूची", + statsMailingList: "मेलिंग सूचियाँ", + mailingListLabel: "मेलिंग सूची", + logisticsTitle: "लॉजिस्टिक्स", + modulesLogisticsLabel: "लॉजिस्टिक्स", + modulesLogisticsDescription: "अपने नेटवर्क में लॉजिस्टिक योजनाएँ खोजें और प्रबंधित करें।", + logisticsDescription: "अपने नेटवर्क में लॉजिस्टिक योजनाएँ खोजें और प्रबंधित करें।", + logisticsCreate: "मार्ग बनाएँ", + logisticsUpdate: "अपडेट करें", + logisticsClose: "बंद करें", + logisticsReopen: "फिर से खोलें", + logisticsCreateSectionTitle: "मार्ग बनाएँ", + logisticsUpdateSectionTitle: "मार्ग अपडेट करें", + logisticsTitleLabel: "शीर्षक", + logisticsTitlePlaceholder: "बाज़ार तक सवारी, वैन उपलब्ध...", + logisticsKindLabel: "लॉजिस्टिक्स प्रकार", + logisticsKindTrip: "यात्रा", + logisticsKindShipment: "शिपमेंट", + logisticsKindTrips: "यात्राएँ", + logisticsKindShipments: "शिपमेंट", + logisticsModeLabel: "मोड", + logisticsModeOffer: "पेशकश", + logisticsModeRequest: "अनुरोध", + logisticsModeOffers: "पेशकशें", + logisticsModeRequests: "अनुरोध", + logisticsDescriptionLabel: "विवरण", + logisticsDescriptionPlaceholder: "मिलने की जगह, आप क्या ले जाते हैं, शर्तें...", + logisticsOriginLabel: "प्रारंभ", + logisticsOriginPlaceholder: "कहाँ से शुरू", + logisticsDestinationLabel: "गंतव्य", + logisticsDestinationPlaceholder: "कहाँ समाप्त", + logisticsDateLabel: "तारीख और समय", + logisticsRecurrenceLabel: "पुनरावृत्ति", + logisticsFrequencyLabel: "आवृत्ति", + logisticsRecurrenceNone: "एक बार", + logisticsRecurrenceDaily: "रोज़", + logisticsRecurrenceWeekly: "साप्ताहिक", + logisticsRecurrenceMonthly: "मासिक", + logisticsSeatsLabel: "सीटें", + logisticsSizeLabel: "आकार", + logisticsSizePlaceholder: "जैसे 2m x 1m", + logisticsWeightLabel: "वज़न", + logisticsWeightPlaceholder: "जैसे 40kg", + logisticsPriceFree: "मुफ़्त", + logisticsPriceEco: "ECO", + logisticsPriceTime: "समय", + logisticsPriceLabel: "मूल्य", + logisticsHours: "घं", + logisticsOrderRefLabel: "ऑर्डर लिंक", + logisticsOrderRefPlaceholder: "डिलीवर किए जाने वाले ऑर्डर का /shops/... या /market/...", + logisticsOrderLink: "जुड़ा ऑर्डर", + logisticsTagsLabel: "टैग", + logisticsTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + logisticsFilterAll: "सभी", + logisticsFilterMine: "मेरे", + logisticsFilterBooked: "बुक किए गए", + logisticsFilterRecent: "हाल के", + logisticsFilterHistory: "इतिहास", + logisticsFilterTop: "टॉप", + logisticsStatusOpen: "खुला", + logisticsStatusUpcoming: "आगामी", + logisticsStatusPast: "बीते", + logisticsStatusClosed: "बंद", + logisticsZoneLabel: "क्षेत्र", + logisticsSearchPlaceholder: "लॉजिस्टिक्स में खोजें...", + logisticsNoItems: "कोई मार्ग नहीं मिला।", + logisticsBookingPending: "लंबित", + logisticsBookingConfirmed: "पुष्टि", + logisticsBookingRejected: "अस्वीकृत", + logisticsBookingCancelled: "रद्द", + logisticsBookingDelivered: "डिलीवर", + logisticsConfirm: "पुष्टि करें", + logisticsReject: "अस्वीकार करें", + logisticsDeliver: "डिलीवर करें", + logisticsReceiptLabel: "रसीद", + logisticsReceiptPlaceholder: "डिलीवरी नोट (वैकल्पिक)", + logisticsCancelBooking: "बुकिंग रद्द करें", + logisticsBookTitle: "यह मार्ग बुक करें", + logisticsNotesLabel: "नोट्स", + logisticsNotesPlaceholder: "जो मालिक को पता होना चाहिए", + logisticsBook: "बुक करें", + logisticsBookingsTitle: "बुकिंग", + logisticsMyBookingTitle: "आपकी बुकिंग", + logisticsRatingsTitle: "रेटिंग", + logisticsNoRatings: "अभी कोई रेटिंग नहीं।", + logisticsRatingsWhenClosed: "मार्ग बंद होने पर रेटिंग खुलती है।", + logisticsScoreLabel: "स्कोर", + logisticsRatingPlaceholder: "कैसा रहा?", + logisticsRate: "रेट करें", + typeLogisticsRoute: "मार्ग", + typeLogisticsRating: "मार्ग रेटिंग", + statsLogisticsRoute: "मार्ग", + statsLogisticsRating: "मार्ग रेटिंग", + logisticsRouteLabel: "मार्ग", + podcastsTitle: "पॉडकास्ट", + modulesPodcastsLabel: "पॉडकास्ट", + modulesPodcastsDescription: "पॉडकास्ट चैनल प्रकाशित करने का मॉड्यूल।", + podcastsDescription: "पॉडकास्ट चैनल प्रकाशित करने का मॉड्यूल।", + podcastCreate: "पॉडकास्ट बनाएँ", + podcastUpdate: "अपडेट करें", + podcastDelete: "हटाएँ", + podcastCreateSectionTitle: "पॉडकास्ट बनाएँ", + podcastUpdateSectionTitle: "पॉडकास्ट अपडेट करें", + podcastTitleLabel: "शीर्षक", + podcastTitlePlaceholder: "चैनल का नाम", + podcastDescriptionLabel: "विवरण", + podcastDescriptionPlaceholder: "यह पॉडकास्ट किस बारे में है?", + podcastCategoryLabel: "श्रेणी", + podcastCategoryNews: "समाचार", + podcastCategoryMusic: "संगीत", + podcastCategoryTalk: "बातचीत", + podcastCategoryEducation: "शिक्षा", + podcastCategoryTech: "तकनीक", + podcastCategoryCulture: "संस्कृति", + podcastCategoryCommunity: "समुदाय", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "अन्य", + podcastTagsLabel: "टैग", + podcastCoverLabel: "कवर (छवि या वीडियो)", + podcastEpisodeAudioLabel: "ऑडियो या वीडियो फ़ाइल (अधिकतम: 75MB)", + podcastTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + podcastFilterAll: "सभी", + podcastFilterMine: "मेरे", + podcastFilterRecent: "हाल के", + podcastFilterTop: "टॉप", + podcastFilterViewers: "श्रोता", + podcastSearchPlaceholder: "पॉडकास्ट में खोजें...", + podcastNoItems: "कोई पॉडकास्ट नहीं मिला।", + podcastEpisodesLabel: "एपिसोड", + podcastPlaysLabel: "व्यूज़", + podcastLastEpisode: "अंतिम एपिसोड", + podcastEpisodesTitle: "एपिसोड", + podcastNoEpisodes: "अभी कोई एपिसोड नहीं।", + podcastEpisodeAdd: "एपिसोड जोड़ें", + podcastEpisodeCreateTitle: "एपिसोड प्रकाशित करें", + podcastEpisodeUpdateTitle: "एपिसोड अपडेट करें", + podcastEpisodeTitleLabel: "एपिसोड शीर्षक", + podcastEpisodeTitlePlaceholder: "एपिसोड का शीर्षक", + podcastEpisodeDescriptionLabel: "शो नोट्स", + podcastEpisodeDescriptionPlaceholder: "इस एपिसोड में क्या होता है, लिंक, श्रेय...", + podcastEpisodePublish: "प्रकाशित करें", + podcastPlayed: "सुना गया", + podcastMarkPlayed: "सुना हुआ चिह्नित करें", + typePodcast: "पॉडकास्ट", + typePodcastEpisode: "एपिसोड", + typePodcastOpinion: "एपिसोड राय", + typePodcastPlay: "एपिसोड प्ले", + statsPodcast: "पॉडकास्ट", + statsPodcastEpisode: "एपिसोड", + statsPodcastPlay: "एपिसोड प्ले", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "आपके द्वारा फ़ॉलो किए गए ब्लॉग में नई प्रविष्टि है।", + podcastBotEpisodeTitle: "आपके द्वारा फ़ॉलो किए गए पॉडकास्ट में नया एपिसोड है।", + podcastLabel: "पॉडकास्ट", + podcastEpisodeLabel: "एपिसोड", + campaignsTitle: "अभियान", + modulesCampaignsLabel: "अभियान", + modulesCampaignsDescription: "अपने नेटवर्क में अभियान खोजें और प्रबंधित करें।", + campaignsDescription: "अपने नेटवर्क में अभियान खोजें और प्रबंधित करें।", + campaignCreate: "अभियान बनाएँ", + campaignUpdate: "अपडेट करें", + campaignDelete: "हटाएँ", + campaignClose: "बंद करें", + campaignReopen: "फिर से खोलें", + campaignCreateSectionTitle: "अभियान बनाएँ", + campaignUpdateSectionTitle: "अभियान अपडेट करें", + campaignTitleLabel: "शीर्षक", + campaignTitlePlaceholder: "आप क्या हासिल करना चाहते हैं?", + campaignTextLabel: "पाठ", + campaignTextPlaceholder: "कारण समझाएँ, कौन तय करता है और आप क्या माँगते हैं...", + campaignCategoryLabel: "श्रेणी", + campaignCategoryEnvironment: "पर्यावरण", + campaignCategoryRights: "अधिकार", + campaignCategoryHealth: "स्वास्थ्य", + campaignCategoryEducation: "शिक्षा", + campaignCategoryInfrastructure: "बुनियादी ढाँचा", + campaignCategoryCulture: "संस्कृति", + campaignCategoryEconomy: "अर्थव्यवस्था", + campaignCategoryOther: "अन्य", + campaignGoalLabel: "हस्ताक्षर लक्ष्य", + campaignDeadlineLabel: "समय सीमा", + campaignTagsLabel: "टैग", + campaignTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + campaignFilterAll: "सभी", + campaignFilterMine: "मेरे", + campaignFilterSigned: "हस्ताक्षरित", + campaignFilterRecent: "हाल के", + campaignFilterTop: "टॉप", + campaignStatusOpen: "खुला", + campaignStatusAchieved: "प्राप्त", + campaignStatusUnachieved: "अप्राप्त", + campaignStatusClosed: "बंद", + campaignSearchPlaceholder: "अभियानों में खोजें...", + campaignNoItems: "कोई अभियान नहीं मिला।", + campaignSignaturesLabel: "हस्ताक्षर", + campaignSignaturesTitle: "समर्थक", + campaignNoSignatures: "अभी कोई हस्ताक्षर नहीं।", + campaignSignTitle: "इस अभियान पर हस्ताक्षर करें", + campaignSignPlaceholder: "आप इसका समर्थन क्यों करते हैं? (वैकल्पिक)", + campaignSign: "हस्ताक्षर करें", + campaignSigned: "आप पहले ही हस्ताक्षर कर चुके हैं!", + campaignNotSigned: "आपके हस्ताक्षर अभी बाकी हैं", + campaignElevate: "संसद तक ले जाएँ", + campaignElevated: "संसद तक ले जाया गया", + campaignUpdatesTitle: "अपडेट", + campaignNoUpdates: "अभी कोई अपडेट नहीं।", + campaignUpdatePlaceholder: "क्या हुआ?", + campaignAddUpdate: "अपडेट पोस्ट करें", + campaignQrAlt: "इस अभियान पर हस्ताक्षर के लिए QR", + campaignQrCaption: "हस्ताक्षर के लिए स्कैन करें", + typeCampaign: "अभियान", + typeCampaignSignature: "हस्ताक्षर", + typeCampaignUpdate: "अभियान अपडेट", + statsCampaign: "अभियान", + statsCampaignSignature: "अभियान हस्ताक्षर", + statsCampaignUpdate: "अभियान अपडेट", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "आपके द्वारा फ़ॉलो किए गए अभियान में अपडेट है।", + campaignBotAchievedTitle: "आपके द्वारा फ़ॉलो किया गया अभियान अपने लक्ष्य तक पहुँच गया।", + campaignBotRaisedTitle: "आपके द्वारा हस्ताक्षरित एक अभियान संसद में उठाया गया है।", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "आपके द्वारा बुक किया गया मार्ग बदल गया है।", + logisticsBotClosedTitle: "आपके द्वारा बुक किया गया मार्ग बंद हो गया है।", + logisticsBotBookedTitle: "किसी ने आपका मार्ग बुक किया है।", + logisticsBotCancelledTitle: "आपके मार्ग की एक बुकिंग रद्द कर दी गई।", + logisticsBotConfirmedTitle: "आपकी बुकिंग पुष्ट हुई: पैकेज रास्ते में है।", + logisticsBotRejectedTitle: "आपकी बुकिंग अस्वीकृत हुई।", + logisticsBotDeliveredTitle: "आपका पैकेज पहुँच गया है।", + campaignLabel: "अभियान", + campaignGenerateReport: "रिपोर्ट बनाएँ", + logisticsGenerateReport: "रिपोर्ट बनाएँ", + mailingGenerateReport: "रिपोर्ट बनाएँ", + campaignUpdateEdited: "संपादित", + mailingMessagesLabel: "संदेश", + logisticsBookingsLabel: "बुकिंग", + backupTitle: "बैकअप", + modulesBackupLabel: "बैकअप", + modulesBackupDescription: "अपनी कुंजियाँ निर्यात/आयात करने और एन्क्रिप्टेड बैकअप बनाने का मॉड्यूल।", + backupDescription: "अपनी कुंजियाँ निर्यात/आयात करने और एन्क्रिप्टेड बैकअप बनाने का मॉड्यूल।", + backupTypeRecovery: "रिकवरी", + backupTypeKeys: "कुंजियाँ निर्यात करें", + backupTypeFull: "पूर्ण बैकअप", + backupTypeRestore: "बैकअप पुनर्स्थापित करें", + backupTypeInstant: "तत्काल", + backupInstantDescription: "आपके पूरे लॉग और फ़ाइलों का सादा .zip, बिना आपकी गुप्त कुंजी और बिना पासवर्ड के। तेज़, पर ", + backupInstantRisk: "जिसे फ़ाइल मिले वह सब पढ़ सकता है।", + backupKitPageDescription: "रिकवरी किट केवल इसी डिवाइस से खोली जा सकती है।", + backupKitId: "Oasis ID", + backupKitDate: "बनाया गया", + backupKitQrAlt: "आपकी गुप्त कुंजी का QR कोड", + backupKitStep1: "इसे प्रिंट करें या हाथ से लिखें और सुरक्षित, ऑफ़लाइन जगह रखें।", + backupKitStep2: "पुनर्प्राप्ति के लिए नए डिवाइस पर Oasis इंस्टॉल करें और बैकअप › कुंजियाँ से यह सीक्रेट आयात करें।", + backupKitStep3: "यदि यह पृष्ठ कभी उजागर हो जाए तो इसे नष्ट कर दें।", + backupKitPdfButton: "PDF बनाएँ", + backupScopeLabel: "दायरा", + backupScopeAll: "सब कुछ", + backupScopeMine: "केवल मेरी सामग्री", + backupSinceLabel: "इस तारीख से", + backupEstimateTitle: "अनुमान", + backupFileInfo: "आपकी एन्क्रिप्टेड प्रति आपके डिवाइस पर डाउनलोड होगी (नाम: oasis-backup-तारीख.oasisbk)", + backupExportButton: "बैकअप डाउनलोड करें", + backupRestorePassword: "बैकअप बनाते समय उपयोग किया गया पासवर्ड लिखें", + backupRestoreButton: "बैकअप पुनर्स्थापित करें", + backupRestoredLine: "बैकअप पुनर्स्थापित हुआ।", + backupRestoredMessages: "संदेश जोड़े गए", + backupRestoredSkipped: "पहले से मौजूद", + backupRestoredBlobs: "फ़ाइलें जोड़ी गईं", + backupRestoredState: "सेटिंग्स बहाल", + backupRestoredFailed: "विफल", + backupRestoredForked: "भिन्न", + backupRestoreRunning: "बैकअप पुनर्स्थापित हो रहा है। यह पेज अपने आप ताज़ा होता है; इस बीच आप Oasis का उपयोग जारी रख सकते हैं।", + backupRestoreElapsed: "बीता समय", + backupRestoreFailedLine: "बैकअप पुनर्स्थापित नहीं हो सका।", + backupRestoreForkMine: "इस डिवाइस ने पहले ही आपकी पहचान से ऐसे संदेश प्रकाशित किए हैं जो बैकअप में नहीं हैं, इसलिए उन पर बैकअप लागू नहीं हो सका। कुंजियाँ आयात करने के तुरंत बाद, कुछ और करने से पहले, एक साफ़ इंस्टॉलेशन पर पुनर्स्थापित करें।", + verificationTitle: "सत्यापन", + verificationDescription: "अपनी फ़ीड की श्रृंखला जाँचें, फोर्क पहचानें और अनाथ या गायब फ़ाइलों की समीक्षा करें।", + verificationRun: "सत्यापन चलाएँ", + verificationTotalMessages: "लॉग में संदेश", + verificationFeed: "आपकी फ़ीड", + verificationMessages: "संदेश", + verificationLastSequence: "अंतिम क्रम", + verificationGaps: "अंतराल", + verificationBrokenLinks: "टूटी कड़ियाँ", + verificationBadHashes: "गलत हैश", + verificationBadSignatures: "गलत हस्ताक्षर", + verificationDuplicates: "दोहराए गए क्रम", + verificationForks: "फोर्क", + verificationBlobs: "फ़ाइलें", + verificationBlobsPresent: "मौजूद", + verificationBlobsReferenced: "संदर्भित", + verificationBlobsOrphan: "अनाथ", + verificationBlobsMissing: "गायब", + verificationBlobsMissingOwn: "इस उपकरण पर आपकी गायब फ़ाइलें", + verificationBlobsMissingOthers: "अन्य निवासियों की फ़ाइलें अभी डाउनलोड नहीं हुईं", + verificationBlobsUnavailable: "इस डिवाइस पर फ़ाइलें सूचीबद्ध नहीं की जा सकीं।", + wikiTitle: "विकी", + modulesWikiLabel: "विकी", + modulesWikiDescription: "विकी खोजने और प्रबंधित करने का मॉड्यूल।", + wikiDescription: "विकी खोजने और प्रबंधित करने का मॉड्यूल।", + wikiCreate: "विकी बनाएँ", + wikiUpdate: "अपडेट", + wikiDelete: "हटाएँ", + wikiEdit: "संपादित करें", + wikiTitleLabel: "शीर्षक", + wikiTitlePlaceholder: "विकी का शीर्षक...", + wikiBodyLabel: "सामग्री", + wikiBodyPlaceholder: "अन्य विकी को [[विकी का शीर्षक]] या [[विकी का शीर्षक|अपना लेबल]] से जोड़ें।\n\n@id से किसी निवासी का उल्लेख करें।\n\n#oasis से टैग करें।\n\nhttps://solarnethub.com जैसे लिंक चिपकाएँ\n\nखाली पंक्ति नया अनुच्छेद शुरू करती है; एकल लाइन ब्रेक बने रहते हैं।\n\nकोई भी समर्थित मीडिया जोड़ें और वह यहाँ एम्बेड हो जाएगा।", + formatBarLabel: "फ़ॉर्मैट", + wikiStatusLabel: "विकी प्रकार", + wikiStatusOpen: "खुली", + wikiStatusClosed: "बंद", + wikiTagsLabel: "टैग", + wikiTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + wikiSummaryLabel: "बदलाव का सारांश", + wikiSummaryPlaceholder: "क्या बदला?", + wikiFilterAll: "सभी", + wikiFilterMine: "मेरे", + wikiFilterRecent: "हाल के", + wikiFilterLinked: "जुड़े हुए", + wikiChanges: "हाल के बदलाव", + wikiNoPages: "कोई विकी नहीं मिली।", + wikiNoChanges: "अभी कोई बदलाव नहीं।", + wikiHistory: "इतिहास", + wikiVersion: "संस्करण", + wikiVersions: "संस्करण", + wikiRestore: "पुनर्स्थापित करें", + wikiDiff: "बदलाव", + wikiViewVersion: "देखें", + wikiLinkedPages: "जुड़े हुए पृष्ठ", + wikiNoLinkedPages: "यह पृष्ठ अभी किसी अन्य से नहीं जुड़ा है।", + wikiMissingLink: "पृष्ठ अभी मौजूद नहीं है", + wikiDuplicateTitle: "इस शीर्षक वाला पृष्ठ पहले से मौजूद है। दूसरा पृष्ठ बनाने के लिए शीर्षक बदलें।", + wikiCreateSectionTitle: "विकी बनाएँ", + wikiUpdateSectionTitle: "विकी अपडेट करें", + wikiSearchPlaceholder: "विकी में खोजें...", + wikiCurrentVersion: "वर्तमान संस्करण", + wikiViewingVersion: "आप एक पुराना संस्करण देख रहे हैं", + wikiBackToPage: "पृष्ठ पर वापस", + wikiLinkedChip: "जुड़ा", + wikiRestoredSummary: "पुनर्स्थापित संस्करण", + wikiExportPdf: "पृष्ठ बनाएँ", + wikiPreview: "पूर्वावलोकन", + tribeSectionWiki: "विकी", + tribeWikiCreate: "विकी बनाएँ", + tribeWikiEmpty: "अभी कोई विकी नहीं।", + typeWiki: "विकी", padsTitle: "पैड्स", padTitle: "पैड", modulesPadsLabel: "पैड्स", - modulesPadsDescription: "सहयोगी पाठ संपादकों को प्रबंधित करने का मॉड्यूल।", + modulesPadsDescription: "सहयोगी टेक्स्ट एडिटर प्रबंधित करने का मॉड्यूल।", padCreate: "पैड बनाएं", padUpdate: "अपडेट करें", padDelete: "हटाएं", padTitleLabel: "शीर्षक", padTitlePlaceholder: "पैड का शीर्षक दर्ज करें...", padStatusLabel: "स्थिति", + padTypeLabel: "पैड प्रकार", padStatusOpen: "खुला", padStatusInviteOnly: "केवल आमंत्रण", padStatusClosed: "बंद", padDeadlineLabel: "समय सीमा", padTagsLabel: "टैग", - padTagsPlaceholder: "टैग1, टैग2, ...", + padTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", padMembersLabel: "सदस्य", padCreated: "बनाया गया", - padStartEditing: "संपादन शुरू करें!", + padStartEditing: "संपादित करें", padEditorPlaceholder: "लिखना शुरू करें...", padSubmitEntry: "सबमिट करें", padNoEntries: "अभी कोई प्रविष्टि नहीं।", padAllSectionTitle: "सभी पैड्स", padMineSectionTitle: "मेरे पैड्स", padsDescription: "अपने नेटवर्क में सहयोगी एन्क्रिप्टेड टेक्स्ट संपादक प्रबंधित करें।", + roomsTitle: "रूम्स", + roomsDescription: "अपने नेटवर्क में एन्क्रिप्टेड वीडियो कॉन्फ्रेंस रूम खोजें और उनमें शामिल हों।", + modulesRoomsLabel: "रूम्स", + modulesRoomsDescription: "एन्क्रिप्टेड वीडियो कॉन्फ्रेंस रूम खोजने और उनमें शामिल होने का मॉड्यूल।", + roomFilterRecent: "हाल के", + roomFilterMine: "मेरे", + roomFilterAll: "सभी", + roomFilterLive: "लाइव", + roomFilterOpen: "खुले", + roomFilterInvite: "केवल आमंत्रण", + roomFilterClosed: "बंद", + roomCreate: "रूम बनाएं", + roomUpdate: "अपडेट करें", + roomDelete: "हटाएं", + roomClose: "बंद करें", + roomCreateSectionTitle: "रूम बनाएं", + roomUpdateSectionTitle: "रूम अपडेट करें", + roomTitlePlaceholder: "रूम का शीर्षक", + roomDescriptionLabel: "विवरण", + roomDescriptionPlaceholder: "आप किस बारे में बात करेंगे?", + roomTypeLabel: "रूम का प्रकार", + roomTagsLabel: "टैग", + roomStatusOpen: "खुला", + roomStatusInviteOnly: "केवल आमंत्रण", + roomStatusClosed: "बंद", + roomParticipants: "प्रतिभागी", + roomJoin: "शामिल हों", + roomLeave: "छोड़ें", + roomFull: "रूम भरा हुआ है", + roomLiveChip: "लाइव", + roomMuted: "म्यूट", + roomRec: "ऑडियो रिकॉर्ड करें", + roomRecStop: "रिकॉर्डिंग रोकें", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} कमरे में आए", + roomEventLeft: "{name} कमरे से गए", + roomEventRecStart: "{name} ने रिकॉर्डिंग शुरू की", + roomEventRecStop: "{name} ने रिकॉर्डिंग रोकी", + roomEventMuted: "{name} ने खुद को म्यूट किया", + roomEventUnmuted: "{name} ने म्यूट हटाया", + roomEventHand: "{name} ने हाथ उठाया", + roomRaiseHand: "हाथ उठाएँ", + roomLowerHand: "हाथ नीचे करें", + confirmActionTitle: "इस कार्रवाई की पुष्टि करें", + confirmActionButton: "पुष्टि करें", + confirmJoinText: "आप इस निमंत्रण से शामिल होने वाले हैं।", + confirmFollowText: "आप इस निवासी का समर्थन करने वाले हैं।", + confirmSignText: "आप इस अभियान पर हस्ताक्षर करने वाले हैं।", + confirmDownloadText: "आप यह फ़ाइल अपने डिवाइस पर डाउनलोड करने वाले हैं।", + roomRecordings: "रिकॉर्डिंग", + roomClearNotices: "साफ़ करें", + roomLineSwitchboard: "स्विचबोर्ड", + roomLineDndError: "इस रूम का नंबर कॉल स्वीकार नहीं कर रहा है।", + roomNumberHint: "रूम में शामिल होने के लिए फ़ोन से यह नंबर डायल करें।", + liveRefresh: "ताज़ा करें", + roomNobody: "अभी रूम में कोई नहीं है।", + roomSomeone: "अभी रूम में {n}।", + roomYou: "आप", + roomYouAreIn: "आप अंदर हैं", + roomNobodyInside: "इस समय इस कक्ष में कोई नहीं है।", + roomErrorFull: "रूम भरा हुआ है। किसी के जाने पर आप शामिल हो सकते हैं।", + roomErrorUnreachable: "अभी रूम तक पहुंचा नहीं जा सकता। बाद में फिर से प्रयास करें।", + roomErrorBusy: "आप पहले से किसी कॉल या दूसरे रूम में हैं।", + roomErrorClosed: "यह रूम बंद है।", + roomAccessDenied: "यह रूम केवल आमंत्रण पर है। शामिल होने के लिए आपको आमंत्रण चाहिए।", + roomsNoItems: "कोई रूम नहीं मिला।", + roomSearchPlaceholder: "रूम खोजें...", + roomInviteMode: "आमंत्रण", + tribeSectionRooms: "रूम्स", + tribeRoomCreate: "रूम बनाएं", + tribeRoomsEmpty: "अभी तक कोई रूम नहीं।", + typeRoom: "रूम्स", + typeRoomMember: "रूम प्रतिभागी", + statsRoom: "रूम्स", + statsRoomMember: "रूम प्रतिभागी", + favoritesFilterRooms: "रूम्स", + invitesRoomsTitle: "रूम्स", + inviteCodeInvalid: "यह आमंत्रण कोड मान्य नहीं है या पहले ही इस्तेमाल हो चुका है।", + inviteAlreadyMember: "आप पहले से शामिल हैं। आप इसे इसके मॉड्यूल से खोल सकते हैं।", + actionFailed: "यह नहीं हो सका। कृपया फिर से प्रयास करें।", + publishTooShort: "आपकी पोस्ट बहुत छोटी है।", + walletSendFailed: "भुगतान नहीं भेजा जा सका: {reason}", + pixeliaCoordsError: "ये निर्देशांक ग्रिड से बाहर हैं।", + courtsErrTitle: "शीर्षक आवश्यक है।", + courtsErrRespondent: "बताएं कि मामला किसके विरुद्ध है।", + courtsErrRespondentId: "यह मान्य Oasis ID नहीं है।", + courtsErrMethod: "चुनें कि मामला कैसे सुलझाया जाएगा।", + courtsErrCaseNotFound: "वह मामला नहीं मिला।", + courtsErrBrief: "अपना जवाब लिखें।", + courtsErrStance: "अपना पक्ष चुनें।", + courtsErrResult: "परिणाम लिखें।", + courtsErrTerms: "समझौते की शर्तें लिखें।", + courtsErrMediator: "कम से कम एक मध्यस्थ जोड़ें।", + courtsErrSelfMediator: "आप अपने ही मामले में मध्यस्थ नहीं हो सकते।", + courtsErrJudge: "एक न्यायाधीश चुनें।", + courtsErrSelfJudge: "आप अपने ही मामले के न्यायाधीश नहीं हो सकते।", + courtsErrVisibility: "चुनें कि मामला कौन देख सकता है।", + courtsErrNomination: "वह नामांकन नहीं मिला।", + opinionAlreadyGiven: "आप पहले ही अपनी राय दे चुके हैं।", + cipherPasswordShort: "पासवर्ड कम से कम 32 अक्षरों का होना चाहिए।", + errorForbidden: "आपको इसकी पहुंच नहीं है।", + errorNotFound: "यह नहीं मिला।", + errorClosed: "यह बंद है।", + errorInviteOnly: "यह केवल आमंत्रण पर है। आपको आमंत्रण चाहिए।", + errorTooLong: "यह पाठ बहुत लंबा है।", + errorRequired: "कुछ आवश्यक जानकारी नहीं है।", + errorInvalid: "कुछ जानकारी मान्य नहीं है।", + errorNotAuthor: "केवल लेखक ही ऐसा कर सकता है।", + panicRemoved: "आपकी ब्लॉकचेन हटा दी गई है।", padRecentSectionTitle: "हालिया पैड्स", padOpenSectionTitle: "खुले पैड्स", padClosedSectionTitle: "बंद पैड्स", @@ -3074,18 +3942,18 @@ module.exports = { padRemoveFavorite: "पसंदीदा से हटाएं", padClose: "बंद करें", padBackToEditor: "संपादक पर वापस", - padSearchPlaceholder: "पैड खोजें...", + padSearchPlaceholder: "पैड्स में खोजें...", modulesChatsLabel: "चैट्स", - modulesChatsDescription: "एन्क्रिप्टेड चैट खोजने और प्रबंधित करने का मॉड्यूल।", + modulesChatsDescription: "चैट खोजने और प्रबंधित करने का मॉड्यूल।", typeChat: "चैट्स", typeChatMessage: "चैट संदेश", chatsTitle: "चैट्स", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "आपकी चैट्स", + chatRecentTitle: "हाल की चैट्स", chatFavoritesTitle: "पसंदीदा", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "खुली चैट्स", + chatClosedTitle: "बंद चैट्स", chatDescription: "विवरण", chatCategory: "श्रेणी", chatStatus: "स्थिति", @@ -3100,34 +3968,172 @@ module.exports = { chatMessagesLabel: "संदेश", chatThreadsLabel: "थ्रेड", chatCreatedAt: "बनाया गया", - chatSearchPlaceholder: "चैट खोजें...", + chatSearchPlaceholder: "चैट्स में खोजें...", chatStatusOpen: "खुला", chatStatusInviteOnly: "केवल आमंत्रण", chatStatusClosed: "बंद", chatSendMessage: "भेजें", chatJumpLatest: "नवीनतम संदेश", + chatReply: "उत्तर दें", + chatReplyCancel: "उत्तर रद्द करें", + chatPin: "पिन करें", + chatUnpin: "अनपिन करें", + chatPinned: "पिन किए गए", chatPickChat: "खोलने के लिए एक चैट चुनें", chatNoneYet: "अभी तक कोई चैट उपलब्ध नहीं है।", activitySearchPlaceholder: "गतिविधि में खोजें...", trendingSearchPlaceholder: "ट्रेंडिंग में खोजें...", opinionsSearchPlaceholder: "राय में खोजें...", votesSearchPlaceholder: "मतदान में खोजें...", - welcomeStepUxTitle: "अपना इंटरफ़ेस चुनें", - welcomeStepUxText: "तय करें Oasis कैसा दिखे। बाद में सेटिंग्स में बदल सकते हैं।", - welcomeStepUxAction: "यह दृश्य उपयोग करें", + gamesSearchPlaceholder: "खेल में खोजें...", + schoolTitle: "विद्यालय", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "आपके द्वारा फ़ॉलो किया गया विकी पृष्ठ संपादित किया गया है।", + wikiBotRestoredTitle: "आपके द्वारा फ़ॉलो किए गए विकी पृष्ठ का पुराना संस्करण पुनर्स्थापित किया गया है।", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "एक नए छात्र ने आपके पाठ्यक्रम में नामांकन किया है।", + schoolBotInvitedTitle: "आपको एक पाठ्यक्रम में आमंत्रित किया गया है।", + schoolBotAdmittedTitle: "आपको एक पाठ्यक्रम में प्रवेश मिला है।", + schoolBotCertificateTitle: "आपको एक प्रमाणपत्र मिला है।", + schoolBotPassedTitle: "एक छात्र ने आपका पाठ्यक्रम उत्तीर्ण किया है।", + schoolBotLessonTitle: "एक नया पाठ प्रकाशित हुआ है।", + invitesSchoolTitle: "विद्यालय", + schoolEnrollLabel: "पाठ्यक्रम से जुड़ें", + schoolExamRetryLater: "आप 24 घंटे बाद फिर से प्रयास कर सकते हैं।", + schoolApproved: "उत्तीर्ण", + schoolExamPassMark: "उत्तीर्ण अंक (0-10)", + schoolExamPassed: "उत्तीर्ण", + schoolExamPending: "लंबित", + schoolExamFailed: "अनुत्तीर्ण", + schoolExamAddQuestion: "प्रश्न जोड़ें", + schoolExamCreateQuestion: "प्रश्न बनाएँ", + schoolExamOption: "विकल्प", + schoolExamCorrect: "सही विकल्प", + schoolExamPoints: "अंक", + schoolMaterialTextPlaceholder: "इस पाठ के लिए नोट्स...", + schoolMaterials: "सामग्री", + schoolAddMaterial: "सामग्री जोड़ें", + schoolNoMaterials: "अभी तक कोई सामग्री नहीं।", + bankYourSchoolBalance: "आपकी विद्यालय आय", + schoolProgress: "प्रगति", + schoolProgressMark: "पूर्ण के रूप में चिह्नित करें", + schoolProgressDone: "पूर्ण", + schoolExams: "परीक्षाएं", + schoolExamCreate: "परीक्षा बनाएं", + schoolExamTitlePlaceholder: "परीक्षा शीर्षक", + schoolExamHelp: "प्रति पंक्ति एक प्रश्न: प्रश्न? ; विकल्प ; सही विकल्प* ; विकल्प", + schoolExamQuestionsPlaceholder: "SSB क्या है? ; एक डेटाबेस ; एक P2P प्रोटोकॉल* ; एक ब्राउज़र", + schoolExamSubmit: "उत्तर भेजें", + schoolExamScore: "आपका स्कोर", + schoolNoScoresYet: "अभी तक कोई परिणाम नहीं।", + schoolLessonUnit: "इकाई / चरण", + schoolLessonUnitPlaceholder: "जैसे चरण 1", + schoolLessonOrder: "क्रम", + profileClearnetSchoolLabel: "विद्यालय", + schoolStartDate: "आरंभ तिथि", + schoolSessionDate: "सत्र तिथि", + schoolCourseChat: "पाठ्यक्रम चैट", + agendaFilterSchool: "विद्यालय", + agendaFilterCampaigns: "अभियान", + agendaFilterLogistics: "लॉजिस्टिक्स", + schoolCourseType: "पाठ्यक्रम प्रकार", + schoolTypeOpen: "खुला", + schoolTypePaid: "सशुल्क", + schoolTypeInvite: "केवल आमंत्रण", + schoolLessonLocked: "पाठ एन्क्रिप्टेड है — शिक्षक से पाठ्यक्रम की कुंजी मांगें।", + schoolGrantAccess: "पाठ्यक्रम कुंजी भेजें", + schoolKeySent: "कुंजी भेजी गई", + schoolOngoing: "जारी", + schoolFilterApplied: "नामांकित", + schoolFilterOpen: "खुले", + schoolFilterFavorites: "पसंदीदा", + schoolPendingPayment: "भुगतान लंबित", + schoolPendingStudents: "लंबित नामांकन", + schoolViewBill: "बिल देखें", + schoolPaymentNote: "नामांकन एक ECO बिल (एक ट्रांसफर) बनाता है। ट्रांसफर पर हस्ताक्षर होने पर आपका नामांकन पक्का होता है।", + schoolCloseCourse: "पाठ्यक्रम बंद करें", + schoolReopenCourse: "पाठ्यक्रम फिर खोलें", + schoolDescription: "अपने नेटवर्क में पाठ्यक्रम पढ़ाएं और सीखें।", + schoolFilterAll: "सभी", + schoolFilterMine: "मेरे", + schoolFilterTop: "शीर्ष", + schoolFilterRecent: "हाल के", + schoolCreateButton: "पाठ्यक्रम बनाएं", + schoolCreateSectionTitle: "पाठ्यक्रम बनाएं", + schoolUpdateSectionTitle: "पाठ्यक्रम अपडेट करें", + schoolCourseTitlePlaceholder: "आपके पाठ्यक्रम का नाम", + schoolCourseDescriptionPlaceholder: "इस पाठ्यक्रम में आप क्या पढ़ाएंगे?", + schoolCourseImage: "पाठ्यक्रम छवि (अधिकतम आकार: 75MB)", + schoolTags: "टैग", + schoolTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + schoolPrice: "मूल्य (ECO)", + schoolVisibility: "दृश्यता", + schoolVisibilityPublic: "सार्वजनिक", + schoolVisibilityInvite: "आमंत्रण द्वारा", + schoolStatus: "स्थिति", + schoolClosed: "बंद", + schoolFree: "मुफ़्त", + schoolCreate: "पाठ्यक्रम बनाएं", + schoolUpdate: "पाठ्यक्रम अपडेट करें", + schoolSearchPlaceholder: "विद्यालय में खोजें...", + schoolSortTitle: "शीर्षक", + schoolNoCourses: "कोई पाठ्यक्रम नहीं मिला।", + schoolStudents: "छात्र", + schoolTeacher: "शिक्षक", + schoolCreatedAt: "निर्मित", + schoolLessons: "पाठ", + schoolNoLessons: "अभी तक कोई पाठ नहीं।", + schoolEnrollToSee: "इस पाठ्यक्रम के पाठ देखने के लिए नामांकन करें।", + schoolEnrollButton: "नामांकन करें", + schoolUnenrollButton: "पाठ्यक्रम छोड़ें", + schoolInviteRequired: "यह पाठ्यक्रम आमंत्रण द्वारा है।", + schoolTeacherPanel: "शिक्षक पैनल", + schoolStudentPanel: "छात्र पैनल", + schoolAddLesson: "पाठ बनाएँ", + schoolLessonTitlePlaceholder: "पाठ का शीर्षक", + schoolLessonTextPlaceholder: "इस पाठ में शामिल अवधारणाओं का वर्णन करें...", + schoolInviteStudents: "छात्रों को आमंत्रित करें", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "आमंत्रित करें", + schoolInvited: "आमंत्रित", + schoolIssueCertificate: "प्रमाणपत्र बनाएँ", + schoolCertified: "प्रमाणित", + schoolCertificateTextPlaceholder: "प्रमाणपत्र नोट (वैकल्पिक)", + schoolLessonOpen: "पाठ खोलें", + schoolCertificatePdf: "PDF", + schoolCertificates: "प्रमाणपत्र", + schoolNoCertificates: "अभी तक कोई प्रमाणपत्र जारी नहीं हुआ।", + schoolEditButton: "संपादित करें", + schoolDeleteButton: "हटाएं", + typeSchool: "विद्यालय", + schoolCourseLabel: "पाठ्यक्रम", + emergencyLabel: "आपातकाल", + wikiPageLabel: "विकी", + statsSchoolCourse: "पाठ्यक्रम", + statsSchoolLesson: "पाठ", + statsSchoolEnroll: "नामांकन", + statsSchoolCertificate: "विद्यालय प्रमाणपत्र", + modulesSchoolLabel: "विद्यालय", + modulesSchoolDescription: "ज्ञान के आदान-प्रदान के लिए मॉड्यूल।", + welcomeStepUxAction: "दृश्य सहेजें", + welcomeStepWorkflowTitle: "आप OASIS का किस तरह उपयोग करना चाहते हैं?", + welcomeStepWorkflowText: "Oasis में कई मॉड्यूल हैं। अपने लिए सही कॉन्फ़िगरेशन चुनें और केवल उपयोगी मॉड्यूल चालू रहेंगे। आप इसे कभी भी मॉड्यूल में बदल सकते हैं।", + welcomeStepWorkflowAction: "कॉन्फ़िगरेशन सहेजें", + welcomeWorkflow_full: "डिफ़ॉल्ट", chatRateLimitTitle: "बहुत अधिक संदेश", chatRateLimitMessage: "आप इस कक्ष की प्रति घंटा संदेश सीमा तक पहुँच गए हैं। बाद में पुनः प्रयास करें।", chatMessagePlaceholder: "संदेश लिखें...", chatNoMessages: "अभी तक कोई संदेश नहीं।", chatTitlePlaceholder: "चैट का शीर्षक", chatDescriptionPlaceholder: "चैट का विवरण", - chatTagsPlaceholder: "टैग1, टैग2", + chatTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", chatImageLabel: "छवि फ़ाइल चुनें (.jpeg, .jpg, .png, .gif)", chatAuthor: "लेखक", chatAddFavorite: "पसंदीदा में जोड़ें", chatRemoveFavorite: "पसंदीदा से हटाएं", chatPM: "PM", chatStatusLabel: "स्थिति", + chatTypeLabel: "चैट प्रकार", chatCategoryLabel: "श्रेणी", gamesTitle: "खेल", gamesDescription: "कुछ खेल खोजें और खेलें।", @@ -3158,22 +4164,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "एक सिक्का उछालें और हेड या टेल पर दांव लगाएं। आप कितने भाग्यशाली हैं?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "ऊपर से दिखने वाला पूल। निशाना लगाने के लिए क्लिक करें, ताकत भरने के लिए दबाए रखें। कम से कम शॉट में सभी गेंदें पॉकेट करें।", + gamesArtilleryTitle: "तोपखाना", + gamesArtilleryDesc: "अपनी तोप से निशाना लगाएँ, हवा का ध्यान रखें और लक्ष्य पर वार करें। 5 राउंड, सबसे कम शॉट वाला जीतता है।", + gamesLabyrinthTitle: "भूलभुलैया", + gamesLabyrinthDesc: "चालें ख़त्म होने से पहले भूलभुलैया से निकलें। हर स्तर बड़ा और कठिन होता जाता है।", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "सारे बिंदु खाएँ और भूतों से बचें। भूत रियल टाइम में चलते हैं — चलते रहें!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "ध्वनि पेंडुलम", + gamesAudioPendulumDesc: "रियल-टाइम ऑडियो संश्लेषण वाला अराजक भौतिकी सिम्युलेटर। कोणीय वेग आवृत्तियाँ बन जाते हैं और शिखर ढोल की थाप। कोई भी दो सिमुलेशन एक जैसे नहीं सुनाई देते।", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "दो डेक पर लाइव विज़ुअल मिक्स करें: हर एक के लिए पैटर्न चुनें, गति, रंग और ज़ूम सेट करें, दोनों के बीच क्रॉसफ़ेड करें और ताल पर फ़्लैश चलाएँ। टेम्पो टैप करें या माइक्रोफ़ोन को धड़कन चलाने दें।", + gamesTetrisDesc: "क्लासिक गिरते ब्लॉक। अंक पाने के लिए पंक्तियाँ साफ़ करें। आप कितनी देर टिक पाएँगे?", + gamesMinesweeperTitle: "माइनस्वीपर", + gamesMinesweeperDesc: "क्लासिक माइन खोज। स्तर बढ़ाने के लिए बोर्ड साफ़ करें — बड़े ग्रिड, ज़्यादा माइन, ऊँचा स्कोर।", + gamesFilterScoring: "अंक", + gamesHallOfFame: "हॉल ऑफ़ फ़ेम", + gamesHallPlayer: "खिलाड़ी", + gamesHallScore: "स्कोर", + gamesNoScores: "अभी तक कोई स्कोर नहीं।", calendarsTitle: "कैलेंडर", calendarTitle: "कैलेंडर", modulesCalendarsLabel: "कैलेंडर", @@ -3181,15 +4191,15 @@ module.exports = { typeCalendar: "कैलेंडर", calendarCreate: "कैलेंडर बनाएं", calendarUpdate: "अपडेट करें", - calendarDelete: "हटाएं", calendarTitleLabel: "शीर्षक", calendarTitlePlaceholder: "कैलेंडर शीर्षक...", calendarStatusLabel: "स्थिति", + calendarTypeLabel: "कैलेंडर प्रकार", calendarStatusOpen: "खुला", calendarStatusClosed: "बंद", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "अंतिम तिथि", calendarTagsLabel: "टैग", - calendarTagsPlaceholder: "टैग1, टैग2...", + calendarTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", calendarParticipantsLabel: "प्रतिभागी", calendarCreated: "बनाया गया", calendarJoin: "जुड़ें", @@ -3200,11 +4210,11 @@ module.exports = { calendarNotePlaceholder: "नोट जोड़ें...", calendarFirstDateLabel: "तारीख", calendarFirstNoteLabel: "नोट्स", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "अंतराल", calendarIntervalNone: "कोई पुनरावृत्ति नहीं", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "साप्ताहिक", + calendarIntervalMonthly: "मासिक", + calendarIntervalYearly: "वार्षिक", calendarFormDescription: "विवरण", calendarNoDates: "कोई तारीख नहीं जोड़ी गई।", calendarNoNotes: "कोई नोट नहीं।", @@ -3212,6 +4222,8 @@ module.exports = { calendarsDescription: "अपने नेटवर्क में कैलेंडर खोजें और प्रबंधित करें।", calendarMonthPrev: "← पिछला", calendarMonthNext: "अगला →", + calendarMonthNames: "जनवरी,फ़रवरी,मार्च,अप्रैल,मई,जून,जुलाई,अगस्त,सितंबर,अक्टूबर,नवंबर,दिसंबर", + calendarDayInitials: "सो,मं,बु,गु,शु,श,र", calendarAllSectionTitle: "कैलेंडर", calendarRecentSectionTitle: "हाल के कैलेंडर", calendarFavoritesSectionTitle: "पसंदीदा", @@ -3221,9 +4233,9 @@ module.exports = { calendarCreateSectionTitle: "नया कैलेंडर बनाएं", calendarUpdateSectionTitle: "कैलेंडर अपडेट करें", calendarAddFavorite: "पसंदीदा में जोड़ें", - calendarDeleteNote: "Delete", + calendarDeleteNote: "हटाएँ", calendarRemoveFavorite: "पसंदीदा से हटाएं", - calendarSearchPlaceholder: "कैलेंडर खोजें...", + calendarSearchPlaceholder: "कैलेंडर में खोजें...", calendarAddEntry: "प्रविष्टि जोड़ें", statsCalendar: "कैलेंडर", statsCalendarDate: "कैलेंडर तारीखें", @@ -3235,6 +4247,146 @@ module.exports = { tribeContentAccessDenied: "पहुंच अस्वीकृत", tribeContentAccessDeniedMsg: "यह सामग्री एक जनजाति की है। इसे देखने के लिए आपको सदस्य होना चाहिए।", tribeViewTribes: "जनजातियाँ देखें", + tribeFilesEmpty: "अभी तक कोई टोरेंट नहीं।", + filesLabel: "टोरेंट", + profileClearnetFilesLabel: "टॉरेंट", + filesTitle: "टॉरेंट", + filesDescription: "अपने नेटवर्क में टॉरेंट खोजें और प्रबंधित करें।", + fileAllSectionTitle: "टॉरेंट", + fileMineSectionTitle: "आपके टॉरेंट", + fileRecentSectionTitle: "हालिया टॉरेंट", + fileTopSectionTitle: "शीर्ष टॉरेंट", + fileFavoritesSectionTitle: "पसंदीदा", + fileFilterAll: "सभी", + fileFilterMine: "मेरे", + fileFilterRecent: "हालिया", + fileFilterTop: "शीर्ष", + fileFilterFavorites: "पसंदीदा", + fileCreateSectionTitle: "टॉरेंट अपलोड करें", + fileUpdateSectionTitle: "टॉरेंट अपडेट करें", + fileCreateButton: "टॉरेंट अपलोड करें", + fileUpdateButton: "अपडेट करें", + fileAddFavoriteButton: "पसंदीदा में जोड़ें", + fileRemoveFavoriteButton: "पसंदीदा से हटाएं", + fileFileLabel: "एक फ़ाइल चुनें (अधिकतम-आकार: 75MB)", + calendarFilterRecent: "हाल के", + calendarFilterAll: "सभी", + calendarFilterMine: "मेरे", + calendarFilterFavorites: "पसंदीदा", + calendarFilterOpen: "खुले", + calendarFilterClosed: "बंद", + padFilterRecent: "हाल के", + padFilterAll: "सभी", + padFilterMine: "मेरे", + padFilterOpen: "खुले", + padFilterClosed: "बंद", + chatFilterRecent: "हाल के", + chatFilterAll: "सभी", + chatFilterMine: "मेरे", + chatFilterFavorites: "पसंदीदा", + chatFilterOpen: "खुले", + chatFilterClosed: "बंद", + licenseLabel: "लाइसेंस", + phoneTitle: "फ़ोन", + phoneDescription: "अपनी एंड-टू-एंड एन्क्रिप्टेड वीडियो कॉल प्रबंधित करें।", + phoneUnavailable: "इस डिवाइस पर कॉल उपलब्ध नहीं हैं।", + phoneCallButton: "कॉल करें", + profileVisibilityPhone: "फ़ोन नंबर", + phoneNumberUnknown: "आपके नेटवर्क में किसी के पास {number} नंबर नहीं है।", + phoneNumberChoose: "जाँचें कि आप {number} पर किसे कॉल करना चाहते हैं:", + phoneAnswer: "उत्तर दें", + phoneSpeaker: "स्पीकर", + phoneEarpiece: "ईयरपीस", + phoneReject: "अस्वीकार करें", + phoneHangup: "कॉल समाप्त करें", + phoneMute: "म्यूट करें", + phoneUnmute: "अनम्यूट करें", + phoneCameraOn: "कैमरा चालू", + phoneCameraOff: "कैमरा बंद", + settingsPhoneVideo: "वीडियो", + settingsPhoneVideoOn: "कॉल और कमरों में अनुमति", + settingsPhoneVideoOff: "केवल ऑडियो", + phoneSilence: "न सुनें", + phoneUnsilence: "फिर से सुनें", + phoneCalling: "कॉल कर रहे हैं:", + phoneIsCalling: "आपको कॉल कर रहे हैं", + notifyNewPmFrom: "नया PM:", + notifyNewPamFrom: "नया PaM:", + notifyUpdateLabel: "अपडेट", + updateInstalledLabel: "इंस्टॉल किया गया संस्करण", + updateAvailableLabel: "उपलब्ध संस्करण", + updateChangesLabel: "बदलाव", + updateFilesLabel: "फ़ाइलें", + updateOriginLabel: "स्रोत", + phoneRecording: "इनके लिए PaM रिकॉर्ड हो रहा है:", + phoneLeaveMessage: "किसी ने जवाब नहीं दिया: बीप के बाद अपना संदेश छोड़ें।", + phoneLeaveMessageBusy: "दूसरी कॉल पर हैं: बीप के बाद अपना संदेश छोड़ें।", + phoneLeaveMessageUnreachable: "संपर्क नहीं हो सका: बीप के बाद अपना संदेश छोड़ें।", + phoneConnecting: "कनेक्ट हो रहा है:", + phoneSendPam: "PaM भेजें", + phoneCancel: "रद्द करें", + phoneSearchPlaceholder: "फ़ोन में खोजें...", + phonePamTitle: "ऑडियो रिकॉर्ड", + phoneNew: "नया", + phoneDelete: "हटाएँ", + phoneNoPams: "आपके पास कोई ऑडियो रिकॉर्ड नहीं है।", + phoneHistoryTitle: "इतिहास", + phoneLatestTitle: "हाल की कॉल", + phoneCreateButton: "नई कॉल", + phoneLimitsHint: "प्रति कॉल अधिकतम 7 निवासी; एक से अधिक होने पर यह संयुक्त कॉल है।", + phoneErrorPub: "पब को कॉल नहीं किया जा सकता।", + phoneJointCall: "संयुक्त कॉल", + phoneWith: "किसके साथ", + phoneResult: "परिणाम", + phoneWhen: "कब", + phoneDuration: "अवधि", + phoneOutgoing: "आउटगोइंग", + phoneIncoming: "इनकमिंग", + phoneNoHistory: "अभी तक कोई लॉग नहीं।", + phoneOutcomeEnded: "उत्तर दिया", + phoneOutcomeMissed: "छूटी", + phoneOutcomeRejected: "अस्वीकृत", + phoneOutcomeNoanswer: "कोई जवाब नहीं", + phoneOutcomeCancelled: "रद्द", + phoneOutcomePam: "PaM भेजा गया", + phoneOutcomeFailed: "विफल", + phoneDialAdd: "जोड़ें", + phoneDialBack: "मिटाएं", + phoneDialClear: "साफ़ करें", + phoneDialPlaceholder: "नंबर या Oasis ID", + phoneDialInhabitants: "निवासी", + phoneDialRemove: "हटाएं", + phoneOwnNumber: "यह आपका अपना नंबर है।", + phoneSettingsDesc: "चुनें कि कौन आपको कॉल कर सकता है।", + phoneDnd: "परेशान न करें", + phoneOpen: "खुला", + phoneRecipientRefuses: "जिस निवासी को आप कॉल करना चाहते हैं, वह कॉल स्वीकार नहीं करता।", + phoneRefusedNotice: "ये निवासी कॉल स्वीकार नहीं करते और इन्हें कॉल नहीं किया गया:", + fileEmptyError: "फ़ाइल खाली है। कोई दूसरी फ़ाइल चुनें।", + fileTitleLabel: "शीर्षक", + fileTitlePlaceholder: "शीर्षक", + fileDescriptionLabel: "विवरण", + fileDescriptionPlaceholder: "विवरण", + fileTagsLabel: "टैग", + fileTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", + fileSizeLabel: "आकार", + fileDownloadButton: "डाउनलोड", + fileDetailsButton: "विवरण", + fileNoFile: "कोई टॉरेंट फ़ाइल नहीं", + fileSearchPlaceholder: "टॉरेंट में खोजें...", + fileSearchButton: "खोजें", + fileSortRecent: "सबसे हालिया", + fileSortOldest: "सबसे पुराने", + fileSortTop: "सबसे अधिक वोट", + fileNoMatch: "कोई मिलान टॉरेंट नहीं।", + fileUpdatedAt: "अपडेट किया गया", + typeFile: "टॉरेंट", + modulesFilesLabel: "फ़ाइलें", + modulesFilesDescription: "फ़ाइलें खोजने और प्रबंधित करने का मॉड्यूल।", + tribeSectionFiles: "टॉरेंट", + fileDescription: "विवरण", + fileDownload: "डाउनलोड", + fileTypeLabel: "प्रकार", torrentsTitle: "टॉरेंट", torrentsDescription: "अपने नेटवर्क में टॉरेंट खोजें और प्रबंधित करें।", torrentAllSectionTitle: "टॉरेंट", @@ -3262,10 +4414,33 @@ module.exports = { torrentTagsLabel: "टैग", torrentTagsPlaceholder: "अल्पविराम से अलग करके टैग दर्ज करें", torrentSizeLabel: "आकार", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "टोरेंट बनाएँ", + torrentFromContentMissing: "फ़ाइल अभी इस नोड पर उपलब्ध नहीं है। थोड़ी देर बाद फिर कोशिश करें।", + torrentDownloads: "डाउनलोड", + torrentFetchOasis: "Oasis में डाउनलोड करें", + torrentSaveFile: "फ़ाइल सहेजें", + torrentSeeds: "सीड", + seedsLabel: "सीड", + seedAction: "समर्थन करें", + torrentSourceFile: "फ़ाइल", + torrentStateQueued: "कतार में", + torrentStateProbing: "सीड खोजे जा रहे हैं", + torrentStateDownloading: "डाउनलोड हो रहा है", + torrentStateDone: "पूरा हुआ", + torrentStateFailed: "विफल", + torrentStateNoSource: "अभी कोई सीड ऑनलाइन नहीं, प्रतीक्षा में", + torrentStateCancelled: "रद्द", + torrentNoDownloads: "अभी कोई डाउनलोड नहीं।", + torrentOriginalSection: "मूल", + torrentCancel: "रद्द करें", + torrentDownloadButton: "डाउनलोड", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "विवरण", torrentNoFile: "कोई टॉरेंट फ़ाइल नहीं", noTorrents: "कोई टॉरेंट नहीं मिला।", - torrentSearchPlaceholder: "शीर्षक, टैग, लेखक खोजें...", + noFiles: "कोई टॉरेंट नहीं मिला।", + torrentSearchPlaceholder: "टॉरेंट में खोजें...", torrentSearchButton: "खोजें", torrentSortRecent: "सबसे हालिया", torrentSortOldest: "सबसे पुराने", @@ -3273,10 +4448,12 @@ module.exports = { torrentNoMatch: "कोई मिलान टॉरेंट नहीं।", torrentUpdatedAt: "अपडेट किया गया", statsTorrent: "टॉरेंट", + statsFile: "फ़ाइलें", typeTorrent: "टॉरेंट", modulesTorrentsLabel: "टॉरेंट", modulesTorrentsDescription: "टॉरेंट खोजने और प्रबंधित करने का मॉड्यूल।", favoritesFilterTorrents: "टॉरेंट", + favoritesFilterFiles: "फ़ाइलें", favoritesFilterMarket: "मार्केट", favoritesFilterEvents: "इवेंट्स", favoritesFilterForum: "फ़ोरम", @@ -3290,15 +4467,17 @@ module.exports = { favoritesFilterShopProducts: "दुकान वस्तुएं", tribeSectionTorrents: "टॉरेंट", tribeCreateTorrent: "टॉरेंट अपलोड करें", + tribeCreateFile: "फ़ाइल अपलोड करें", settingsWishTitle: "इच्छा", - settingsWishDesc: "अपने अवतार की इच्छा कॉन्फ़िगर करें।", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "तय करता है कि आप सारी सामग्री तक कैसे पहुँचते हैं और Oasis नेटवर्क में आपका अनुभव कैसा होता है।", + settingsWishWhole: "मल्टीवर्स", + settingsWishMutuals: "केवल पारस्परिक समर्थन", + settingsWishOnlyLan: "केवल LAN", + settingsWishLocal: "स्थानीय", settingsPmVisibilityTitle: "निजी संदेश", settingsPmVisibilityDesc: "नेटवर्क में निजी संदेश एक्सपोज़र स्तर कॉन्फ़िगर करें।", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "मल्टीवर्स", + settingsPmVisibilityMutuals: "केवल पारस्परिक समर्थन", pmMutualNotice: "आउटबाउंड UX गार्डरेल है। इनबाउंड ध्यान फ़िल्टर है।", inhabitantsPendingFollowsTitle: "लंबित समर्थन अनुरोध", inhabitantsPendingAccept: "स्वीकार", @@ -3342,7 +4521,7 @@ module.exports = { tribeGovNoRevocations: "अभी तक कोई निरसन नहीं", tribeGovNoHistorical: "अभी तक कोई रिकॉर्ड नहीं", logsTitle: "लॉग", - logsDescription: "नेटवर्क पर अपना अनुभव रिकॉर्ड करें।", + logsDescription: "अपने अनुभवों की निजी डायरी रखें।", logsViewTitle: "लॉग", logsColumnType: "प्रकार", logsView: "देखें", @@ -3351,7 +4530,7 @@ module.exports = { logsCreateTitle: "प्रविष्टि बनाएँ", logsWriteButton: "लिखें", logsTextPlaceholder: "अपने अनुभव वर्णित करें...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "AI सहायक", logsModeManual: "मैन्युअल", logsModeAI: "एआई", logsColumnLog: "लॉग", @@ -3374,19 +4553,20 @@ module.exports = { logsSearchAnyType: "कोई भी प्रकार", logsSearchButton: "खोजें", logsEmpty: "कोई प्रविष्टियाँ नहीं।", + logsNothingNew: "दर्ज करने के लिए कोई नई गतिविधि नहीं है।", modulesLogsLabel: "लॉग", - modulesLogsDescription: "(एआई सहायक के माध्यम से) अपने अनुभव रिकॉर्ड करने के लिए मॉड्यूल।", + modulesLogsDescription: "अपने अनुभवों की निजी डायरी रखने का मॉड्यूल।", statsLogsTitle: "लॉग", typeLog: "लॉग", larpTitle: "L.A.R.P.", larpDescription: "सहयोगात्मक प्रयोग के लिए एक लाइव-एक्शन रोल-प्लेइंग परत।", larpNoHousesMatch: "आपकी खोज से कोई हाउस मेल नहीं खाता।", - larpSearchPlaceholder: "हाउस खोजें...", + larpSearchPlaceholder: "L.A.R.P. में खोजें...", larpAllHouses: "गृह:", larpFilterRuling: "शासन", larpFilterHouses: "गृह", - larpFilterRules: "FAQ", + larpFilterRules: "सामान्य प्रश्न", larpRulesTitle: "L.A.R.P. FAQ", larpRulesEntryText: "हर निवासी डिफ़ॉल्ट रूप से ACADEMIA में शुरू होता है। ACADEMIA से, \"गृह में शामिल हों\" पैनल में कोई गृह चुनें: इससे उसका प्रवेश परीक्षण खुलता है। 10 प्रश्नों के उत्तर दें और सबमिट करें। यदि आप सीमा (7/10) पार करते हैं, तो आपको स्वचालित रूप से उस गृह में स्वीकार कर लिया जाता है; नहीं तो अस्वीकार कर ACADEMIA में रखा जाता है। दोनों ही मामलों में सिस्टम आपके OasisID और प्रयास के चक्र को दर्ज करता है और 30 चक्रों के कूलडाउन समाप्त होने तक पुनः प्रयास से रोकता है।", larpRulesGovernanceText: "अपने चक्र के दौरान, शासक गृह \"शासन\" बैज पहनता है और एकमात्र है जिसकी दीवार शासन टैब के तहत सार्वजनिक रूप से दिखाई जाती है।", @@ -3397,118 +4577,117 @@ module.exports = { larpPostPlaceholder: "यह गृह क्या कहना चाहता है?", larpPostSubmit: "प्रकाशित करें", larpCycleLabel: "चक्र:", - audioBackToBcs: "Back to BCS", + audioBackToBcs: "BCS पर वापस", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeCompositionEmpty: "This audio does not include a stored blockchain composition.", - audioTranscodeDetailDescription: "Decode the embedded payload and the original blockchain composition map.", - audioTranscodeDetailTitle: "Transcode", - audioTranscodeStegoEmpty: "(none)", - audioTranscodeStegoMessage: "TEXT", - audioTranscodeStegoNotFound: "No steganographic payload could be decoded from this audio.", - audioTranscodeStegoOasisId: "By", - audioTranscodeStegoTimestamp: "Generated at", + audioTranscodeButton: "ट्रांसकोड करें", + audioTranscodeCompositionEmpty: "इस ऑडियो में कोई सहेजी गई ब्लॉकचेन रचना नहीं है।", + audioTranscodeDetailDescription: "एम्बेड किया गया पेलोड और ब्लॉकचेन का मूल रचना-मानचित्र डिकोड करें।", + audioTranscodeDetailTitle: "ट्रांसकोड", + audioTranscodeStegoEmpty: "(कोई नहीं)", + audioTranscodeStegoMessage: "टेक्स्ट", + audioTranscodeStegoNotFound: "इस ऑडियो से कोई स्टेग्नोग्राफ़िक पेलोड डिकोड नहीं हो सका।", + audioTranscodeStegoTimestamp: "बनाया गया", audioTranscodeStegoUnknown: "—", - bankExchangeEcoTaxAnnual: "ECOin Taxes (annual)", - bankExchangeEcoTaxMonthly: "ECOin Taxes (monthly)", - bankOverviewYourTaxes: "Your taxes", - bankRulesAlpha: "Alpha (max share of pub balance per epoch)", + bankExchangeEcoTaxAnnual: "ECOin कर (वार्षिक)", + bankExchangeEcoTaxMonthly: "ECOin कर (मासिक)", + bankOverviewYourTaxes: "कुल कर", + bankRulesAlpha: "Alpha (प्रति युग pub शेष का अधिकतम हिस्सा)", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", - bankRulesArchTaxRate: "Rate", - bankRulesArchTaxTitle: "ARCH Tax", - bankRulesCapPerEpoch: "Cap per epoch (absolute)", - bankRulesCapUser: "Cap per user per epoch", - bankRulesCarbonFactor: "Carbon factor", + bankRulesArchTaxNote: "ARCH कर नेटवर्क संग्रह को बढ़ाने और बनाए रखने की दीर्घकालिक लागत दर्शाता है: नेटवर्क में आपका डेटा जितना पुराना है, रखरखाव लागत में आपका हिस्सा उतना बड़ा है।", + bankRulesArchTaxRate: "दर", + bankRulesArchTaxTitle: "ARCH कर", + bankRulesCapPerEpoch: "प्रति युग सीमा (निरपेक्ष)", + bankRulesCapUser: "प्रति उपयोगकर्ता प्रति युग सीमा", + bankRulesCarbonFactor: "कार्बन गुणक", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", - bankRulesChipTitle: "ECO Tax chip color band", + bankRulesChipNote: "हर आइटम की चिप का रंग नेटवर्क में देखे गए सबसे बड़े संदेश के सापेक्ष तय होता है और सक्रिय निवासियों की संख्या से नरम किया जाता है — बड़ा नेटवर्क भार बाँटता है और ज़्यादा आइटम को हरे दायरे में ले आता है।", + bankRulesChipTitle: "ECO कर चिप का रंग दायरा", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", - bankRulesEcoTaxRate: "Rate", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesEpochKind: "Epoch granularity", - bankRulesFloor: "Floor per user (gross)", - bankRulesGraceDays: "Claim grace period", + bankRulesEcoTaxNote: "युग (epoch) का आवंटन गणना करते समय ECO कर सकल UBI से काटा जाता है। काटे गए ECO इस युग में फिर से नहीं बाँटे जाते — वे pub के शेष में रहते हैं और reserveMin / alpha सीमाओं के ज़रिए अगले युग के कोष को पोषित करते हैं।", + bankRulesEcoTaxRate: "दर", + bankRulesEcoTaxTitle: "ECO कर", + bankRulesEpochKind: "युग की सूक्ष्मता", + bankRulesFloor: "प्रति उपयोगकर्ता न्यूनतम (सकल)", + bankRulesGraceDays: "दावे की अनुग्रह अवधि", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", - bankRulesKarmaTitle: "Karma (user engagement score)", + bankRulesKarmaNote: "कर्म आपकी गतिविधियों (पोस्ट, वोट, संसद/अदालतों में भागीदारी आदि) से समय के साथ घटने वाले गुणक सहित निकाला जाता है, जिसमें से आपकी फ़ीड से पैदा हुए कार्बन ग्राम घटा दिए जाते हैं।", + bankRulesKarmaTitle: "कर्म (उपयोगकर्ता सहभागिता स्कोर)", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", - bankRulesPoolTitle: "Monthly pool", - bankRulesReserveMin: "Reserve minimum (kept in pub balance)", + bankRulesPoolTitle: "मासिक कोष", + bankRulesReserveMin: "न्यूनतम आरक्षित (pub शेष में रखा जाता है)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", - bankRulesShareTitle: "Per-user share", + bankRulesShareTitle: "प्रति उपयोगकर्ता हिस्सा", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankRulesWMax: "Maximum weight (w_max)", - bankRulesWMin: "Minimum weight (w_min)", - bankTaxes: "Taxes", - bankTaxesAnnualEcoin: "ECO Tax (annual)", - bankTaxesArchTaxAnnual: "ARCH Tax (annual)", - bankTaxesArchTaxFirstBlock: "Your first block age", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxLifetime: "ARCH Tax (lifetime)", - bankTaxesArchTaxMonthly: "ARCH Tax (monthly)", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesArchTaxRate: "Rate", - bankTaxesArchTaxTitle: "ARCH Tax", - bankTaxesArchTaxUserAmount: "Your ARCH tax (price to return)", - bankTaxesEcoTaxLifetime: "ECO Tax (lifetime)", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesLookupAuthor: "Author", - bankTaxesLookupButton: "Inspect", - bankTaxesLookupCarbon: "Carbon footprint", - bankTaxesLookupEcoin: "ECO Tax", - bankTaxesLookupNotFound: "No block found in your local feed with that ID.", - bankTaxesLookupPlaceholder: "Block ID (e.g. %abc...=.sha256)", - bankTaxesLookupSize: "Size", - bankTaxesLookupTitle: "Inspect a block", - bankTaxesLookupType: "Type", - bankTaxesMonthlyEcoin: "ECO Tax (monthly)", - bankTaxesRate: "Rate", - bankTaxesSpan: "Sampling span", - bankTaxesTotalAnnual: "Total (annual)", - bankTaxesTotalBlocks: "Total blocks", - bankTaxesTotalBytes: "Total bytes", - bankTaxesTotalCarbon: "Total carbon", - bankTaxesTotalEcoin: "Total ECO tax (lifetime)", - bankTaxesTotalLifetime: "Total (lifetime)", - bankTaxesTotalMonthly: "Total (monthly)", - bankTaxesTypesApply: "Set my taxes", - bankTaxesTypesLabel: "Select which taxes you want to pay", - bankTaxesUserAmount: "Your ECO tax (price to return)", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - blockchainBackToBlockexplorer: "Back to blockexplorer", - blockchainBlockNotAvailable: "This block is not available in your local feed. It may belong to a peer you are not yet replicating from.", - ecoTaxLabel: "ECO Tax", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - melodyAllEmpty: "No BCS compositions from other inhabitants yet.", - melodyByLabel: "By", - melodyNoteUnavailable: "block not in your local feed", - melodyUploadBcsButton: "Publish", - melodyUploadBcsNameNote: "The audio will be published with the auto-generated name", - melodyUploadBcsTitle: "Publish", - melodyUploadStegoLabel: "Hidden message (steganography)", - melodyUploadStegoMaxLabel: "max 280 characters", - melodyUploadStegoPlaceholder: "Optional.", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", - statsPad: "Pads", - statsPadEntry: "Pad entries", - statsTombstoneEmpty: "No tombstones in the network yet.", - typeGameScore: "GAME SCORES", + bankRulesWMax: "अधिकतम भार (w_max)", + bankRulesWMin: "न्यूनतम भार (w_min)", + bankTaxes: "कर", + bankTaxesAnnualEcoin: "ECO कर (वार्षिक)", + bankTaxesArchTaxAnnual: "ARCH कर (वार्षिक)", + bankTaxesArchTaxFirstBlock: "आपके पहले ब्लॉक की आयु", + bankTaxesArchTaxFootprintNote: "ARCH कर नेटवर्क संग्रह को बढ़ाने और बनाए रखने की लागत दर्शाता है। इसकी गणना आपके पहले प्रकाशित ब्लॉक और नेटवर्क के सबसे नए ब्लॉक के बीच के समय-अंतराल से होती है — आपका इतिहास संग्रह में जितना लंबा रहा है, रखरखाव लागत में आपका हिस्सा उतना बड़ा है।", + bankTaxesArchTaxLifetime: "ARCH कर (आजीवन)", + bankTaxesArchTaxMonthly: "ARCH कर (मासिक)", + bankTaxesArchTaxNoteBold: "ARCH कर को सीधे घटाने", + bankTaxesArchTaxNoteIntro: "आपका ARCH कर उस अधिशेष से काटा जाता है जो नेटवर्क आपके UBI के ऊपर पैदा करता है; UBI का मूल मान एक अटल न्यूनतम के रूप में तय है। कर कभी भी उस निश्चित राशि से नहीं काटा जाता जो हर निवासी को UBI के रूप में मिलती है। काटी गई राशि धन-पुनर्वितरण एल्गोरिदम को पोषित करती है और दूसरे निवासियों के UBI दावों के ज़रिए उन तक वापस पहुँचती है। नए निवासी और वे जो विशेष रूप से संग्रहण, प्रतिकृति और रखरखाव के कामों में लगे हैं, संभवतः आपकी ओर से नेटवर्क संग्रह को स्वस्थ रखने का कोई काम करेंगे। या वे ख़ुद को ", + bankTaxesArchTaxNoteOutro: " में लगा सकते हैं, छँटाई समझौतों, अनावश्यक डेटा के दोहराव हटाने और साझा रखरखाव ढाँचे के ज़रिए, और नेटवर्क की सहमति उनके निरंतर योगदान की माँग कर सकती है।", + bankTaxesArchTaxRate: "दर", + bankTaxesArchTaxTitle: "ARCH कर", + bankTaxesArchTaxUserAmount: "आपका ARCH कर (लौटाने योग्य राशि)", + bankTaxesEcoTaxLifetime: "ECO कर (आजीवन)", + bankTaxesEcoTaxTitle: "ECO कर", + bankTaxesLookupAuthor: "लेखक", + bankTaxesLookupButton: "निरीक्षण करें", + bankTaxesLookupCarbon: "कार्बन फ़ुटप्रिंट", + bankTaxesLookupEcoin: "ECO कर", + bankTaxesLookupNotFound: "आपकी स्थानीय फ़ीड में उस ID का कोई ब्लॉक नहीं मिला।", + bankTaxesLookupPlaceholder: "ब्लॉक ID (जैसे %abc...=.sha256)", + bankTaxesLookupSize: "आकार", + bankTaxesLookupTitle: "ब्लॉक का निरीक्षण करें", + bankTaxesLookupType: "प्रकार", + bankTaxesMonthlyEcoin: "ECO कर (मासिक)", + bankTaxesRate: "दर", + bankTaxesSpan: "नमूना अवधि", + bankTaxesTotalAnnual: "कुल (वार्षिक)", + bankTaxesTotalBlocks: "कुल ब्लॉक", + bankTaxesTotalBytes: "कुल बाइट्स", + bankTaxesTotalCarbon: "कुल कार्बन", + bankTaxesTotalEcoin: "कुल ECO कर (आजीवन)", + bankTaxesTotalLifetime: "कुल (आजीवन)", + bankTaxesTotalMonthly: "कुल (मासिक)", + bankTaxesTypesApply: "मेरे कर तय करें", + bankTaxesTypesLabel: "चुनें कि आप कौन-से कर चुकाना चाहते हैं", + bankTaxesUserAmount: "आपका ECO कर (लौटाने योग्य राशि)", + bankTaxesUserNoteChips: "हर आइटम के पास दिखने वाली ECO कर चिप का रंग (हरा / पीला / लाल) आइटम के आकार के अनुसार बदलता है, जिसकी तुलना नेटवर्क पर देखे गए सबसे बड़े ब्लॉक से की जाती है और सक्रिय निवासियों की संख्या से नरम किया जाता है — बड़ा सोलर-पंक नेटवर्क भार बाँटता है और ज़्यादा आइटम को हरे दायरे में ले आता है।", + bankTaxesUserNoteOtherParams: "ECO कर अभी हर ब्लॉक के कार्बन फ़ुटप्रिंट से निकाला जाता है, लेकिन समय के साथ इसमें दूसरे मापदंड भी जुड़ सकते हैं — प्रतिकृति पर ख़र्च हुई ऊर्जा, पीयरों में अनावश्यक भंडारण, ब्लॉब बैंडविड्थ, डिक्रिप्शन की गणना लागत, संबंधित लेन-देन का माइनिंग फ़ुटप्रिंट, या कोई भी अन्य मापने योग्य भार जिसे नेटवर्क महत्व देने पर सहमत हो।", + blockchainBackToBlockexplorer: "ब्लॉक एक्सप्लोरर पर वापस", + blockchainBlockNotAvailable: "यह ब्लॉक आपकी स्थानीय फ़ीड में उपलब्ध नहीं है। यह किसी ऐसे पीयर का हो सकता है जिससे आप अभी प्रतिकृति नहीं करते।", + ecoTaxLabel: "ECO कर", + gamesHallDate: "तिथि", + gamesNewRecord: "नया रिकॉर्ड!", + indexingRefreshNote: "यह पृष्ठ हर 10 सेकंड में ताज़ा होता है।", + indexingMessage: "Oasis वितरित नोड्स के नेटवर्क को सिंक कर रहा है। कृपया प्रतीक्षा करें।", + indexingTitle: "समन्वयन जारी", + melodyAllEmpty: "अभी तक दूसरे निवासियों की कोई BCS रचना नहीं।", + melodyNoteUnavailable: "ब्लॉक आपकी स्थानीय फ़ीड में नहीं है", + melodyUploadBcsButton: "प्रकाशित करें", + melodyUploadBcsNameNote: "ऑडियो स्वतः बने नाम के साथ प्रकाशित होगा", + melodyUploadBcsTitle: "प्रकाशित करें", + melodyUploadStegoLabel: "छिपा संदेश (स्टेग्नोग्राफ़ी)", + melodyUploadStegoMaxLabel: "अधिकतम 280 अक्षर", + melodyUploadStegoPlaceholder: "वैकल्पिक।", + padsNoItems: "कोई पैड नहीं मिला।", + padVersionHistory: "संस्करण इतिहास", + padAttributionView: "लेखिका के अनुसार", + padVersionView: "देखें", + statsChat: "चैट्स", + statsChatMessage: "चैट संदेश", + statsGameScore: "खेल", + statsWiki: "विकी पृष्ठ", + statsPad: "पैड्स", + statsPadEntry: "पैड प्रविष्टियाँ", + statsTombstoneEmpty: "नेटवर्क में अभी तक कोई टॉम्बस्टोन नहीं।", + typeGameScore: "गेम स्कोर", bankTaxesLookupNote: "अपने स्थानीय फ़ीड में देखने के लिए ब्लॉक ID दर्ज करें।", bankTaxesUserNoteChipsClick: "ब्लॉकexplorer में इसके ब्लॉक का निरीक्षण करने के लिए किसी भी चिप पर क्लिक करें।", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3523,6 +4702,14 @@ module.exports = { settingsReplicationTitle: "प्रतिकृति", settingsReplicationDesc: "सामग्री की प्रतिकृति बनाते समय आपके पीयर द्वारा आपके अपने फ़ीड से आगे जाने वाले हॉप की संख्या कॉन्फ़िगर करें।", settingsReplicationHopsLabel: "हॉप", + settingsBlobCacheTitle: "मीडिया कैश", + settingsBlobCacheDesc: "दूसरे निवासियों की सामग्री इस डिवाइस पर रखी जाती है। चुनें कि वह कितनी जगह ले सकती है।", + settingsBlobCacheUsage: "वर्तमान उपयोग", + settingsBlobCacheFiles: "फ़ाइलें", + settingsBlobCacheLimit: "सीमा", + settingsBlobCacheUnlimited: "असीमित", + settingsBlobCacheCleanNow: "अभी साफ़ करें", + settingsBlobCacheCleaned: "{n} फ़ाइलें हटाई गईं, {size} खाली हुआ।", aiApproveTagsLabel: "टैग (अल्पविराम से अलग)", aiApproveTagsPlaceholder: "जैसे oasis, शासन, पारिस्थितिकी", aiApproveRatingLabel: "रेटिंग", @@ -3550,8 +4737,6 @@ module.exports = { larpInviteBannerHint: "इस कोड को ACADEMIA में किसी के साथ साझा करें। यह 30 चक्रों या एक उपयोग के बाद समाप्त हो जाता है।", larpTestRankingTitle: "गृह के अनुसार स्कोर", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "गृह आमंत्रण कोड", - invitesHouseJoinButton: "गृह में शामिल हों", larpLastAttemptTitle: "आपका पिछला प्रयास", larpLastAttemptHouse: "गृह", larpLastAttemptWhen: "कब", @@ -3569,7 +4754,7 @@ module.exports = { larpBadgeYou: "आप", larpBadgeRuling: "शासन", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Oasis की लाइव-एक्शन रोल-प्लेइंग परत (9 गृह) के लिए मॉड्यूल।", + modulesLarpDescription: "लाइव-एक्शन रोल-प्लेइंग परत का मॉड्यूल।", larpProfileQ1: "जब आप किसी जटिल समस्या का सामना करते हैं, तो पहले क्या करते हैं?", larpProfileQ1O1: "सहमति खोजने के लिए दूसरों से बात करना", larpProfileQ1O2: "परीक्षण के लिए प्रोटोटाइप बनाना", @@ -3643,6 +4828,51 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "अपने इंटरफ़ेस के लिए UX नेविगेशन मोड चुनें।", uxModeMenus: "ब्लॉक", + uxModeChats: "वार्तालाप", + uxModeFeed: "माइक्रोब्लॉगिंग", + feedTrendingTitle: "ट्रेंडिंग टैग", + feedActiveTitle: "सक्रिय निवासी", + cnBrandSub: "मुक्त · P2P · संघीय", + cnSyncedPeers: "सिंक किए गए पीयर", + cnLicense: "लाइसेंस", + cnNotAccessible: "यह सामग्री इस समय उपलब्ध नहीं है।", + cnUntitled: "बिना शीर्षक", + cnSearchPlaceholder: "खोजें…", + cnHubTitle: "Clearnet हब", + cnHubDescription: "इस Oasis नेटवर्क के निवासियों द्वारा साझा की गई सार्वजनिक सामग्री।", + cnHubEmpty: "अभी तक Clearnet पर कोई सार्वजनिक सामग्री साझा नहीं की गई है।", + cnPublicContent: "सामग्री", + cnTribesContent: "जनजातियाँ", + cnNetworkContent: "नेटवर्क", + cnCategoryEmpty: "इस श्रेणी में कोई सामग्री नहीं।", + cnInhabitantEmpty: "इस निवासी ने अभी तक Clearnet पर कोई सामग्री प्रकाशित नहीं की है।", + cnPrevPage: "← पिछला", + cnNextPage: "अगला →", + cnPageOf: "पृष्ठ {page} / {pages}", + cnMoreInfo: "अधिक जानकारी →", + cnOpenCourse: "खुला कोर्स", + cnAuction: "नीलामी", + cnExchange: "विनिमय", + cnRssDescription: "इस हब के निवासियों द्वारा प्रकाशित", + cnKindAudio: "ऑडियो", + cnKindBlog: "ब्लॉग", + cnKindBookmark: "बुकमार्क", + cnKindDocument: "दस्तावेज़", + cnKindEvent: "इवेंट", + cnKindFeed: "फ़ीड", + cnKindImage: "छवि", + cnKindJob: "नौकरी", + cnKindMarket: "बाज़ार", + cnKindPodcast: "पॉडकास्ट", + cnKindProject: "प्रोजेक्ट", + cnKindCourse: "कोर्स", + cnKindShop: "दुकान", + cnKindTorrent: "टॉरेंट", + cnKindFile: "फ़ाइल", + cnKindVideo: "वीडियो", + cnKindWiki: "विकी", + cnKindRoom: "रूम", + cnKindMap: "मानचित्र", uxModeAINav: "AI", } diff --git a/src/client/assets/translations/oasis_it.js b/src/client/assets/translations/oasis_it.js index e30d008..fbe10dc 100644 --- a/src/client/assets/translations/oasis_it.js +++ b/src/client/assets/translations/oasis_it.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "Acquisti", shopMyOrdersDescription: "I tuoi ordini di acquisto.", shopMyOrdersEmpty: "Non hai ancora acquisti.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM Venditore", shopOrderSeller: "Venditore", shopOrderInvoice: "Fattura", shopOrderInvoiceConcept: "Fattura", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Accetta", shopOrderReject: "Rifiuta", shopOrderMarkShipped: "Segna spedito", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM Acquirente", shopOrderContractConcept: "Ordine negozio", shopOrdersPending: "Ordini in sospeso", all: "Tutti", @@ -28,7 +28,7 @@ module.exports = { recent: "Recenti", anonymous: "Anonimo", unnamed: "Anonimo", - authorLabel: "AUTORE", + authorLabel: "Autore", delete: "Elimina", save: "Salva", vote: "Vota", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Avviso sul contenuto", invalidPost: "Contenuto non valido", invalidPostHint: "Questo messaggio ha testo vuoto/non valido.", - spreadBy: "Da", spreadChron: "Diffusione", spreaded: "Diffuso", spreadContentUnavailable: "Contenuto non ancora disponibile (replica in sospeso)", filteredByTag: "Filtrato per tag", filteredByTagTitle: "Filtrato per tag", feedPublishedSuccess: "Feed pubblicato con successo!", + feedMastodonFailed: "Pubblicato in Oasis, ma non è stato possibile inviarlo a Mastodon.", tribeFeedSent: "Messaggio inviato con successo!", tribeContentEncrypted: "Contenuto cifrato", tribeTorrentsEmpty: "Ancora nessun torrent.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Ultima visita", profileClearnetBookmarksLabel: "Segnalibri", invitesPort: "Porta", - currentlyUnrecheable: "ERRORE!", - peerHostValidation: "IPv4 valido (es. 192.168.1.100) o hostname (es. pub.example.com)", + peerHostValidation: "IPv4 valido (es. 192.168.1.100) o hostname (es. pub.solarnethub.com)", peerPortValidation: "Porta 1-65535", peerKeyValidation: "Chiave pubblica SSB ed25519 (@<44 caratteri base64>=.ed25519)", inboxJobUnsubscribedTitle: "Disiscrizione dalla tua offerta di lavoro", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Descrizione attività", exportPasswordLabel: "Password di esportazione", importPasswordPlaceholder: "Inserisci la password", - searchCasesPlaceholder: "Cerca casi…", + searchCasesPlaceholder: "Cerca in Tribunali...", shopBuyDeliveryAddressPlaceholder: "Indirizzo di consegna", shopBuyNotesPlaceholder: "Note", bankTaxesUserNoteBold: "ridurre direttamente la tassa ECO", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Sostieni il caso", courtsSettlementAcceptBtn: "Accetta l’accordo", courtsSupportCount: "Sostenitori", - courtsVerdictVoteTitle: "Vota sul verdetto", - courtsVerdictVoteLabel: "Voto sul verdetto", - courtsVerdictVoteAccept: "Accetta", - courtsVerdictVoteReject: "Rifiuta", - courtsVerdictVoteSubmit: "Invia voto", reportsSetStatus: "Imposta stato", reportsStatusClosed: "CHIUSO", statsAvgPerInhabitant: "Media per abitante", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Avatar", inhabitants: "Abitanti", - peersReplicatedFeeds: "Feed replicati", + peersReplicatedFeeds: "Replicati", + peersTorColumn: "TOR", + peersTorYes: "Raggiungibile tramite Tor", + peersTorNo: "Non raggiungibile tramite Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Versione", graphos: "Graphos", graphosDescription: "Mappa interattiva della rete attorno a te.", + graphosFederation: "Federazione", + graphosFedPubs: "PUB", + graphosFedUnreachable: "Irraggiungibili", + graphosFedLinks: "Collegamenti reciproci", + graphosFedIsolated: "Isolati", + graphosFedNoData: "Nessun dato", + graphosFedMutual: "Reciproco", + graphosFedOneWay: "A senso unico", + graphosFedTransport: "Rete", + graphosFedWith: "Federato con", + graphosZoom: "Ingrandisci", + graphosFedInhabitants: "Abitanti", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "Il tuo nodo non conosce ancora nessun PUB.", graphosViewingGraphOf: "Visualizzando", graphosBackToMine: "← La mia rete", graphosShowing: "Mostrando", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "Non hai ancora ricevuto @menzioni.", - mentionsSearchPlaceholder: "Cerca menzioni...", + mentionsSearchPlaceholder: "Cerca in Menzioni...", privateReminders: "PROMEMORIA", + inboxFilterPms: "PM", + inboxFilterNotifications: "Notifiche", + inboxMarkRead: "Segna come letto", + inboxMarkUnread: "Segna come non letto", + inboxUnreadChip: "Non letto", + inboxMarkAllRead: "Segna tutto come letto", + inboxDeleteShown: "Elimina tutto", + inboxArchive: "Archivia", + inboxUnarchive: "Ripristina", + inboxFilterArchived: "Archiviati", + inboxEarlierMessages: "messaggi precedenti", + inboxSortThread: "Per conversazione", + inboxSortRecent: "Più recenti prima", + inboxAllBots: "Bot", + settingsInboxBotsTitle: "Notifiche dell'Inbox", + settingsInboxBotsDesc: "Scegli quali bot possono avvisarti nella tua Inbox. I bot deselezionati vengono nascosti e i loro avvisi non vengono contati.", inboxFiltersLabel: "Filtri:", inboxToggleToMutuals: "Passa a reciproci", inboxToggleToWhole: "Passa a tutti", @@ -212,6 +242,7 @@ module.exports = { pmReply: "Rispondi", pmReplies: "risposte", inReplyTo: "IN RISPOSTA A", + forumInThread: "NEL THREAD", pmPreview: "Anteprima", pmPreviewTitle: "Anteprima messaggio", peers: "Peer", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "Da", searchToLabel: "A", searchQueryLabel: "Ricerca", - searchPerPageLabel: "Risultati per pagina", + searchPerPageLabel: "Risultati", settings: "Impostazioni", + settingsDescription: "Configura l'ambiente della tua rete.", continueReading: "Continua a leggere", moreComments: "altri commenti", readThread: "leggi il resto della discussione", @@ -239,7 +271,6 @@ module.exports = { profileClearnetHelp: 'Moduli accessibili dall’esterno di Oasis.', profileHubAll: 'Tutto', footerLicenseLabel: 'Licenza', - profileSwitchToOasis: 'Torna a Oasis', profileVisibilityFediverse: "Multiverso", contributorsTitle: "Contributori", colorLabel: 'Scegli un colore', @@ -249,6 +280,7 @@ module.exports = { modulesViewTitle: "Moduli", modulesViewDescription: "Configura il tuo ambiente attivando o disattivando i moduli.", inbox: "Posta in arrivo", + inboxSearchPlaceholder: "Cerca in Posta in arrivo...", multiverse: "Multiverso", fediverse: "Multiverso", fediverseDescription: "Gestisci i tuoi altri account del multiverso, inclusi l'invio e la ricezione di contenuti.", @@ -257,9 +289,17 @@ module.exports = { fediverseSettingsTitle: "Multiverso", fediverseInstanceLabel: "Indirizzo", fediverseTokenLabel: "Token di accesso", - fediverseTokenHelp: "Imposta il tuo token di accesso. Sarà usato per gestire il tuo account Mastodon da Oasis.", fediverseConnect: "Connetti", fediverseConnectedAs: "Connesso come", + nextcloudServerLabel: "Server", + nextcloudUserLabel: "Utente", + nextcloudFolderLabel: "Cartella", + nextcloudUploadButton: "Carica", + nextcloudFileLabel: "Scegli un file", + fileExternalLink: "Link esterno", + nextcloudErrConnect: "Impossibile raggiungere Nextcloud con questi dati.", + nextcloudErrUpload: "Il caricamento su Nextcloud non è riuscito.", + nextcloudErrShare: "Nextcloud non ha dato un link pubblico per il file.", fediverseDisconnect: "Disconnetti", fediverseEmptyLink: "impostazioni", fediverseComposePlaceholder: "A cosa stai pensando?", @@ -288,6 +328,76 @@ module.exports = { fediverseErrPost: "Impossibile pubblicare.", fediverseErrMedia: "Impossibile caricare il file.", fediverseErrEmpty: "Scrivi qualcosa o allega un file.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Numero di telefono", + telegramSendCode: "Invia codice", + telegramCodeHelp: "Inserisci il codice che Telegram ti ha appena inviato (app o SMS).", + telegramCodeLabel: "Codice", + telegramPasswordHelp: "Questo account ha la verifica in due passaggi. Inserisci la password.", + telegramPasswordLabel: "Password", + telegramVerify: "Verifica", + telegramCancel: "Annulla", + telegramChats: "Chat", + telegramUnread: "Non letti", + telegramOpen: "Apri", + telegramSend: "Invia", + telegramYou: "Tu", + telegramNoChats: "Nessuna chat per ora.", + telegramNoMessages: "Nessun messaggio per ora.", + telegramComposePlaceholder: "Scrivi un messaggio…", + telegramChannel: "Canale", + telegramGroup: "Gruppo", + telegramPrivate: "Privato", + telegramErrConnect: "Impossibile contattare Telegram.", + telegramErrTimeout: "Telegram ha impiegato troppo a rispondere.", + telegramErrAuth: "Sessione Telegram non valida o scaduta. Ricollegati.", + telegramErrFetch: "Impossibile caricare le chat.", + telegramErrSend: "Impossibile inviare il messaggio.", + telegramErrMissing: "Servono API ID, API hash e numero di telefono.", + telegramErrCode: "Codice non valido. Riprova.", + telegramErrCodeExpired: "Il codice è scaduto. Ricomincia.", + telegramErrPassword: "Password errata.", + telegramErrPhone: "Numero di telefono non valido.", + telegramErrApi: "API ID o API hash non validi.", + telegramErrFlood: "Troppi tentativi. Attendi e riprova.", + telegramErrNoLogin: "Nessun accesso in corso. Ricomincia.", + peertubeUsernameLabel: "Nome utente", + peertubePasswordLabel: "Password", + peertubeSubscriptions: "Iscrizioni", + peertubeMyVideos: "I miei video", + peertubeVideos: "Video", + peertubeFollowers: "Follower", + peertubeViews: "visualizzazioni", + peertubeLikes: "mi piace", + peertubeQuota: "Quota", + peertubeUpload: "Carica video", + peertubeUploadHelp: "Scegli un file video. Viene caricato sul tuo canale e PeerTube lo elabora in seguito.", + peertubeTitleLabel: "Titolo", + peertubeDescriptionLabel: "Descrizione", + peertubePrivacyLabel: "Privacy", + peertubePrivacyPublic: "Pubblico", + peertubePrivacyUnlisted: "Non in elenco", + peertubePrivacyPrivate: "Privato", + peertubeNoVideos: "Ancora nessun video.", + peertubeComments: "Commenti", + peertubeComment: "Commenta", + peertubeCommentPlaceholder: "Scrivi un commento…", + peertubeNoComments: "Ancora nessun commento.", + peertubeOpen: "Apri su PeerTube", + peertubeWatch: "Guarda", + peertubeChannel: "Canale", + peertubeNoStream: "Questo video non può ancora essere riprodotto qui. Aprilo su PeerTube.", + peertubeUploaded: "Video caricato. PeerTube lo sta elaborando.", + peertubeErrConnect: "Impossibile contattare PeerTube.", + peertubeErrAuth: "Nome utente o password errati, oppure la sessione è scaduta. Riconnettiti.", + peertubeErrInstance: "URL dell'istanza non valido.", + peertubeErrMissing: "Servono istanza, nome utente e password.", + peertubeErrFetch: "Impossibile caricare i video.", + peertubeErrUpload: "Impossibile caricare il video.", + peertubeErrTitle: "Titolo obbligatorio.", + peertubeErrComment: "Impossibile pubblicare il commento.", + peertubeErrNoFile: "Scegli un file video.", bookmarksLabel: "Segnalibri", videosLabel: "Video", torrentsLabel: "Torrents", @@ -310,7 +420,6 @@ module.exports = { comment: "Commento", subtopic: "Sotto-argomento", json: "JSON", - createdBy: "di", unfollow: "Rimuovi supporto", follow: "Supporta", block: "Blocca", @@ -326,12 +435,15 @@ module.exports = { relationshipNone: "Non stai supportando", relationshipBlockingPost: "Post bloccato", viewLikes: "Vedi diffusioni", + viewSupporters: "Vedi sostenitori", spreadedDescription: "Lista dei post diffusi dall'abitante.", preview: "Anteprima", publish: "Pubblica", contentWarningPlaceholder: "Aggiungi un oggetto al post (opzionale)", - publishWarningPlaceholder: "Corpo limitato a 7000 caratteri.", + publishWarningPlaceholder: "Scrivi il corpo del tuo articolo.", publishTooLong: "Il tuo post è troppo lungo. Accorcialo, per favore.", + keepTextRetry: "Invia di nuovo", + dateInPastError: "La data non può essere nel passato", publishCustomDescription: [ "RICORDA: A causa della tecnologia blockchain, una volta pubblicato un post non può essere modificato né eliminato.", ], @@ -361,9 +473,8 @@ module.exports = { mentionsMatching: "Menzioni corrispondenti", mentionsName: "Nome", mentionsRelationship: "Relazione", - updateit: "AGGIORNAMENTI!", updateBannerText: "È disponibile una nuova versione di Oasis.", - updateBannerAction: "Aggiorna ora →", + updateBannerAction: "Aggiorna ora!", info: "Info", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -371,16 +482,14 @@ module.exports = { timeAgo: "fa", sendTime: "circa ", theme: "Tema", - legacy: "Chiavi", - legacyTitle: "Chiavi", - legacyDescription: "Gestisci il tuo segreto (chiave privata) in modo rapido e sicuro.", - legacyExportButton: "Esporta", - legacyImportButton: "Importa", - ssbLogStream: "Blokchain", + backupKeysExportButton: "Esporta", + backupKeysImportButton: "Importa", + ssbLogStream: "Blockchain", ssbLogStreamDescription: "Configura il limite di messaggi per i flussi della Blockchain.", + logWindowChip: "Ultimi {n} stream della blockchain", saveSettings: "Salva impostazioni", exportDescription: "Imposta una password (minimo 32 caratteri) per crittografare la tua chiave", - exportDataTitle: "Backup", + exportDataTitle: "Copia istantanea", exportDataDescription: "Scarica i tuoi dati (chiave segreta esclusa!)", exportDataButton: "Scarica database", importDescription: "Importa il tuo segreto crittografato (chiave privata) per attivare il tuo avatar", @@ -389,38 +498,33 @@ module.exports = { fileInfo: "La tua chiave segreta crittografata verrà scaricata sul tuo dispositivo (nome: oasis.enc)", developer: "Sviluppo", devTitle: "Sviluppo", - welcomeBannerText: "Benvenuta in OASIS. Segui questi passi rapidi per configurare il tuo avatar.", + welcomeBannerText: "Il tuo avatar non è ancora configurato.", aiSuggestionBanner: "Suggerimento:", aiSuggestionsEnable: "Suggerimenti dell'IA", - welcomeBannerAction: "Inizia!", + welcomeBannerAction: "Inizia", welcomeTitle: "Benvenuta", + welcomeStepIdentity: "Identità", + welcomeStepType: "Tipo", + welcomeStepUse: "Uso", + welcomeStepLook: "Aspetto", + welcomeStepCommunity: "Comunità", + welcomeStepIntro: "Benvenuto", + welcomeHintIdentity: "Il tuo nome e la tua immagine.", + welcomeHintType: "Come si apre Oasis per te.", + welcomeHintUse: "Quali moduli restano attivi.", + welcomeHintLook: "Il tema che indossa Oasis.", + welcomeHintCommunity: "Entra in un PUB per conoscere altri abitanti.", + welcomeHintIntro: "Invia un primo saluto alla comunità.", + welcomeStepThemeAction: "Usa questo tema", + welcomeJoinPubs: "Entra nei PUB", + welcomeNoFederation: "Senza federarsi", welcomeStepGreetingAction: "Invia", - welcomeJoinPubButton: "Unisciti al pub", - welcomeStepLarpAction: "Unisciti a \"L Accademia\"", - welcomeStepLarpText: "Unisciti al nostro gioco di ruolo dal vivo.", - welcomeStepLarpTitle: "Unisciti al L.A.R.P.", - welcomeStepGreetingTitle: "Invia un \"Ciao mondo!\"", - welcomeStepGreetingText: "Invia un messaggio perché gli altri abitanti possano scoprirti.", - welcomeJoinPub: "Unisciti a", welcomeDescription: "Alcune cose da fare prima di iniziare.", - welcomeProgress: "Completato", - welcomeStepDone: "fatto", - welcomeStepPending: "in sospeso", welcomeSkip: "Non ora", - welcomeStepLanguageTitle: "Scegli la tua lingua", - welcomeStepLanguageText: "Oasis parla diverse lingue. Puoi cambiarla quando vuoi.", - welcomeStepProfileTitle: "Modifica il tuo profilo", - welcomeStepProfileText: "Scegli un nome, aggiungi una descrizione e metti un immagine perché gli altri abitanti ti riconoscano.", welcomeStepProfileAction: "Salva profilo", - welcomeStepFederationTitle: "Unisciti alla rete principale", - welcomeStepFederationText: "Collegati al nostro pub per incontrare altri abitanti e iniziare a replicare.", - welcomeStepFederationAction: "Vai agli inviti", - welcomeStepBackupTitle: "Fai il backup del tuo ID", - welcomeStepBackupText: "La tua identità è un file di chiavi su questo dispositivo.", - welcomeStepBackupWarning: "SE LO PERDI, NESSUNO POTRÀ RECUPERARLO PER TE.", - welcomeStepBackupAction: "Backup!", + welcomeBootstrapRunning: "Caricamento dell'istantanea del pub. Al termine ti unisci al pub automaticamente; nel frattempo puoi continuare a usare Oasis.", welcomeCompleteTitle: "Tutto pronto.", - welcomeCompleteText: "Il tuo nodo è pronto. Da qui la rete cresce con le persone che segui.", + welcomeCompleteText: "Il tuo nodo è pronto. Da qui la rete cresce con gli abitanti che sostieni.", welcomeFindPeople: "Trova abitanti", devFilterFiles: "FILE", devFilterSearch: "CERCA", @@ -430,7 +534,7 @@ module.exports = { devFilterTests: "TEST", devVersion: "Versione", devNodeVersion: "Node", - devStatsTests: "Suite di test", + devStatsTests: "Test", devParentFolder: "Cartella superiore", devOpenFolder: "apri", devDescription: "Esplora il codice sorgente in esecuzione su questo nodo.", @@ -474,6 +578,27 @@ module.exports = { "Se desideri usare un'altra lingua, selezionala qui.", setLanguage: "Imposta lingua", peerConnectionsIntro: "Gestisci tutte le tue connessioni con altri peer.", + peersConnectFailed: "Impossibile connettersi: {reason}.", + peerStateLabel: "Stato", + peerAddressLabel: "Indirizzo", + peerLastChangeLabel: "Ultima modifica", + peerStateConnected: "Connesso", + peerStateConnecting: "Connessione in corso", + peerStateStaged: "Disponibile", + peerErrSilent: "Nessuna attività", + peerErrTimeout: "Non risponde", + peerErrRefused: "Rifiuta la connessione", + peerErrNotFound: "Indirizzo sconosciuto", + peerErrUnreachable: "Rete irraggiungibile", + peerErrKeys: "Usa chiavi diverse", + peerErrIdentity: "Non accetta la tua connessione", + peerErrTor: "Solo TOR", + peerErrDropped: "Connessione interrotta", + peerErrPaused: "Rete in pausa", + peerErrOther: "Errore di connessione", + peerOutdated: "Non aggiornato", + peerVersionUnannounced: "Non annuncia la sua versione: un Oasis vecchio", + peersTorMissing: "Questo PUB accetta solo connessioni tramite TOR.", peerConnectionsTitle: "Connessioni", online: "Online", offline: "Offline", @@ -492,22 +617,25 @@ module.exports = { noConnections: "Nessun peer connesso.", noDiscovered: "Nessun peer scoperto.", noUnknownPeers: "Nessun peer sconosciuto nel grafo delle amicizie.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Nessun peer registrato per ora.", + peerConnect: "Connetti", + peerDisconnect: "Disconnetti", peerRefresh: "Aggiorna", peerPruneIdle: "Rimuovi inattivi", + peersPause: "Pausa", + peersResume: "Riprendi", + peersPausedNote: "In pausa: Oasis non si connette con nessuno e nessuno può connettersi.", + peersPauseHint: "Ferma tutte le connessioni: Oasis non si connette ad altri nodi né li accetta finché non riprendi.", peerExport: "Esporta", peerImport: "Importa", peerImportTitle: "Importa lista peer", peerImportPlaceholder: "Incolla un indirizzo multiserver per riga, o carica un file .txt…", connectionActionIntro: "", - startNetworking: "Avvia rete", - stopNetworking: "Ferma rete", - restartNetworking: "Riavvia rete", - sync: "Sincronizza rete", indexes: "Indici", + indexesRebuilt: "Indici ricostruiti", + indexesRebuildFailed: "Non è stato possibile ricostruire gli indici", + indexesFiles: "File di indice", indexesDescription: "Ricostruire gli indici è sicuro e può risolvere alcuni tipi di errori.", homePageTitle: "Home", @@ -517,44 +645,33 @@ module.exports = { invitesDescription: "Gestisci e applica codici invito nella tua rete.", invitesPubsHint: "Incolla un codice d'invito di un pub per federarti con lui e replicarne gli abitanti.", invitesFederationsHint: "Pub con cui sei federato: aggiornali, togli quelli irraggiungibili o esporta l'elenco.", - invitesHousesHint: "Inserisci un codice casa per unirti a una casa del LARP e partecipare alla sua economia.", - invitesTribesHint: "Inserisci un codice tribù per diventarne membro e accedere ai contenuti privati.", - invitesChatsHint: "Inserisci un codice chat per unirti alla conversazione e ai suoi thread.", - invitesPadsHint: "Inserisci un codice pad per scrivere insieme su un documento condiviso.", - invitesCalendarsHint: "Inserisci un codice calendario per condividere date e promemoria.", - invitesEventsHint: "Inserisci un codice evento per partecipare a un evento privato.", - invitesForumsHint: "Inserisci un codice forum per leggere e rispondere in un forum privato.", - invitesMapsHint: "Inserisci un codice mappa per vedere e aggiungere luoghi su una mappa condivisa.", - invitesShopsHint: "Inserisci un codice negozio per unirti a un negozio e al suo catalogo.", - invitesInhabitantsHint: "Inserisci un codice abitante per seguirvi a vicenda e scambiare contenuti.", invitesTribesTitle: "Tribù", - invitesTribeInviteCodePlaceholder: "Inserisci codice invito tribù", - invitesTribeJoinButton: "Unisciti alla tribù", invitesChatsTitle: "Chat", - invitesChatInviteCodePlaceholder: "Inserisci codice invito chat", - invitesChatJoinButton: "Unisciti alla chat", invitesPadsTitle: "Pad", - invitesPadInviteCodePlaceholder: "Inserisci codice invito pad", - invitesPadJoinButton: "Unisciti al pad", invitesCalendarsTitle: "Calendari", - invitesCalendarInviteCodePlaceholder: "Inserisci codice invito calendario", - invitesCalendarJoinButton: "Unisciti al calendario", invitesEventsTitle: "Eventi", - invitesEventInviteCodePlaceholder: "Inserisci codice invito evento", - invitesEventJoinButton: "Unisciti all'evento", invitesForumsTitle: "Forum", - invitesForumInviteCodePlaceholder: "Inserisci codice invito forum", - invitesForumJoinButton: "Unisciti al forum", invitesMapsTitle: "Mappe", - invitesMapInviteCodePlaceholder: "Inserisci codice invito mappa", - invitesMapJoinButton: "Unisciti alla mappa", invitesShopsTitle: "Negozi", - invitesShopInviteCodePlaceholder: "Inserisci il codice di invito del negozio", - invitesShopJoinButton: "Unisciti al negozio", invitesPubsTitle: "PUB", + invitesSuggestedPubs: "PUB Suggeriti", + invitesFilterOthers: "Altre", + invitesJoinTitle: "Inviti", + invitesJoinHint: "Incolla qualsiasi codice d'invito, per qualunque cosa sia, o l'Oasis ID di un abitante.", + invitesJoinPlaceholder: "Codice d'invito o Oasis ID", + invitesJoinButton: "Entra", + invitesUsedTitle: "Ultimi inviti", + invitesUsedEmpty: "Non hai ancora utilizzato nessun invito.", + invitesUsedType: "Tipo", + invitesUsedName: "Nome", + invitesUsedDate: "Data", invitesPubInviteCodePlaceholder: "Inserisci codice invito PUB", invitesAcceptInvite: "Unisciti al PUB", + invitesPubUses: "Inviti usati", + invitesPubExhausted: "Questo PUB non ha più inviti disponibili.", invitesAlreadyFederated: "Sei già federato con questo pub.", + invitesJoinPubMode: "Questo Oasis funziona come PUB, in sola lettura. Per unirlo a un altro PUB, usa il terminale: ./oasis.sh join ", + invitesJoinLocalOnly: "Gli inviti si accettano solo dal dispositivo su cui gira Oasis, o tramite il link di amministrazione mostrato all'avvio.", invitesFederationsTitle: "Federazioni", invitesAcceptedInvites: "Federate", invitesUnfollow: "Smetti di seguire", @@ -562,16 +679,12 @@ module.exports = { invitesUnfollowedInvites: "Non federate", invitesNoFederatedPubs: "Nessun pub federato.", invitesNoUnfollowed: "Nessun pub non federato.", - invitesUnreachablePubs: "Irraggiungibili", - invitesNoUnreachablePubs: "Nessun pub irraggiungibile.", invitesPubsRefresh: "Aggiorna", - invitesPubsClearUnreachable: "Rimuovi irraggiungibili", + invitesPubsClearUnreachable: "Pulisci", invitesPubsExport: "Esporta", invitesPubsImport: "Importa", invitesPubsImportTitle: "Importa pub", invitesPubsImportPlaceholder: "Incolla un indirizzo multiserver o un codice invito per riga, o carica un file .txt…", - currentlyUnreachable: "ERRORE!", - errorDetails: "Dettagli errore", genericError: "Si è verificato un errore.", panicMode: "Modalità panico!", encryptData: "Imposta una password (minimo 32 caratteri) per crittografare la tua blockchain", @@ -598,7 +711,6 @@ module.exports = { votesOptionNoLabel:"No", votesOptionAbstentionLabel:"Astensione", votesQuestionLabel:"Domanda", - votesCreatedByLabel:"Creato da", voteStatusOpen:"APERTO", voteStatusClosed:"CHIUSO", commentDescription: ({ parentUrl }) => [ @@ -615,22 +727,22 @@ module.exports = { searchSubmit: "Cerca...", votesLabel: "VOTAZIONI", pdfFallbackLabel: "Documento PDF", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "NEGOZI", + voteLabel: "VOTI", submit: "Invia", editProfile: "Modifica avatar", oasisIdLabel: "OasisID", spreadContent: "Replica", welcomePmSubject: "Hello World!", - welcomePmBody: "Benvenuto su Oasis — un social network libero, peer-to-peer, federato e cifrato, con un'architettura distribuita solo su invito.\n\nAlcuni posti interessanti da cui partire:\n\n- /profile — Modifica il tuo avatar, nome, descrizione e quali moduli appaiono sul tuo lato pubblico.\n- /invites — Aggiungi un pub per federarti con il resto della rete.\n- /peers — Vedi chi è connesso, riscansiona la LAN, esporta o importa liste di peer.\n- /inhabitants — Scopri e visita gli avatar di altre persone.\n- /tribes — Crea o unisciti a gruppi privati cifrati end-to-end.\n- /inbox — Leggi e invia messaggi privati.\n- /activity — Vedi l'attività recente, tua e della tua rete.\n- /publish — Scrivi un post o condividi un'immagine, un audio, un video o un documento.\n- /search — Cerca i contenuti della rete per tipo.\n\nAlcuni luoghi interessanti per connettersi:\n\n- /multiverse — Gestisci i tuoi altri account del multiverso, inclusi l'invio e la ricezione di contenuti.\n\nQuesto messaggio è stato inviato privatamente da te a te stesso, perciò non serviva alcuna connessione per riceverlo.", + welcomePmBody: "Benvenuto su Oasis — un social network libero, peer-to-peer, federato e cifrato, con un'architettura distribuita solo su invito.\n\nAlcuni posti interessanti da cui partire:\n\n- [/avatar](/profile) — Modifica il tuo avatar, nome, descrizione e quali moduli appaiono sul tuo lato pubblico.\n- /settings — Regola la tua istanza locale: lingua, tema, moduli, privacy e sincronizzazione.\n- /invites — Aggiungi un pub per federarti con il resto della rete.\n- /peers — Vedi chi è connesso, riscansiona la LAN, esporta o importa liste di peer.\n- /inhabitants — Scopri e visita gli avatar di altri abitanti.\n- /tribes — Crea o unisciti a gruppi privati cifrati end-to-end.\n- /search — Cerca i contenuti della rete per tipo.\n\nAlcuni luoghi interessanti per connettersi:\n\n- /multiverse — Gestisci i tuoi altri account del multiverso, inclusi l'invio e la ricezione di contenuti.\n\nQuesto messaggio è stato inviato privatamente da te a te stesso, perciò non serviva alcuna connessione per riceverlo.", profileSensorsSectionTitle: "Sensori", profileVisibilityActivity: "Livello di attività", profileDeviceLockedHint: "Questo sensore è sempre attivo: mostra agli altri da quale dispositivo ti connetti e non può essere disattivato.", profileVisibilityKarma: "Punteggio KARMA", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "Portafoglio ECOIN", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "RBU", + profileVisibilityWallet: "Portafoglio ECOin", + profileVisibilityEcoTax: "Tassa ECO", + profileVisibilityLarpSign: "Sigillo L.A.R.P.", profileVisibilityGpg: "Chiave GPG", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Negozi", @@ -643,6 +755,10 @@ module.exports = { profileClearnetImagesLabel: "Immagini", profileClearnetDocumentsLabel: "Documenti", profileClearnetTorrentsLabel: "Torrent", + profileClearnetPodcastsLabel: "Podcast", + profileClearnetMarketLabel: "Mercato", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wiki", editProfileDescription: "", profileName: "Nome", @@ -668,7 +784,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Conf.", walletConfirm: "Conferma", - walletDescription: "Gestisci i tuoi asset digitali, incluso inviare e ricevere ECOin, visualizzare il saldo e accedere alla cronologia delle transazioni.", + walletDescription: "Gestisci il tuo portafoglio ECOin.", walletDate: "Data", walletFee: "Commissione (più alta è la commissione, più veloce sarà l'elaborazione della transazione)", walletFeeLine: ({ fee }) => `Commissione: ECO ${fee}`, @@ -676,6 +792,86 @@ module.exports = { walletReceive: "Ricevi", walletReset: "Ripristina", walletSend: "Invia", + walletPayingTransfer: "Pagamento del transfer", + walletCreateTransferLabel: "Genera contratto (trasferimento)", + walletTransferNotifiedNote: "L'altra parte è stata avvisata di questo pagamento", + walletTransferCreatedNote: "È stato creato un transfer (ricevuta) per questo pagamento", + walletTransferNoPayeeNote: "Nessun abitante ha pubblicato questo indirizzo, nessun transfer creato", + walletTransferReceiptConcept: "Pagamento dal portafoglio", + walletPaymentPmText: "ti ha inviato un pagamento", + walletPayeeLabel: "Destinatario", + walletConceptLabel: "Causale", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Hai ricevuto il tuo RBU.", + bankingBotPaymentTitle: "Hai ricevuto un pagamento.", + bankingBotUbiPaidText: "Il tuo Reddito di Base Universale è stato pagato", + bankUbiPubThisNode: "Questo nodo paga il RBU", + bankUbiPubConnected: "PUB del RBU", + bankUbiPubNone: "Nessun PUB annuncia ancora il RBU nella tua rete", + bankUbiAddressPublished: "Indirizzo ECOin pubblicato", + bankUbiAddressMissing: "Pubblica il tuo indirizzo ECOin per ricevere il RBU", + bankUbiClaimedThisMonth: "Richiesto questo mese", + bankUbiPubBalance: "Portafoglio del PUB disponibile per il RBU", + bankNoPubAddress: "Il PUB del RBU non ha ancora pubblicato un indirizzo ECOin.", + bankUbiTab: "RBU", + bankDonateEco: "DONA!", + donateTooltip: "Dona ECOin", + bankNoUserAddress: "Questo abitante non condivide un indirizzo ECOin.", + bankUbiPubDefault: "Predefinito", + bankUbiPool: "Dotazione", + bankUbiLastSeen: "Visto l'ultima volta", + bankUbiLastPayout: "Ultima azione", + bankUbiPaidOut: "Distribuito", + bankRangeToday: "Oggi", + bankRangeWeek: "Settimana", + bankRangeMonth: "Mese", + bankRangeYear: "Anno", + bankRangeFiveYears: "5 anni", + bankRangeAll: "Tutto", + bankChartValueTitle: "Evoluzione del valore di ECOin", + bankChartValueHours: "Equivalenza in tempo (h)", + bankChartWealthTitle: "Redistribuzione della ricchezza ECOin", + bankChartDistributed: "RBU distribuito (ECO)", + bankChartRebalanceTitle: "Redistribuzione della ricchezza tra PUB", + bankChartRebalanced: "ECO spostati tra PUB", + bankChartTaxes: "Imposte trattenute (ECO)", + bankChartNoDataRange: "Nessun dato in questo intervallo.", + bankChartNoDataYet: "Ancora nessun dato", + bankChartPerHour: "all'ora", + bankChartPerDay: "al giorno", + bankChartPerMonth: "al mese", + bankTotalUbiDistributed: "RBU totale redistribuito (rete)", + bankTotalTaxesWithheld: "Imposte totali trattenute (rete)", + bankInflationIssuance: "Tasso di emissione (annuo, dalla ricompensa di blocco)", + bankRulesEligibilityTitle: "Chi può ricevere il RBU", + bankRulesEligibility1: "Un indirizzo ECOin pubblicato nel profilo (Profilo → Sensori → Portafoglio ECOin).", + bankRulesEligibility2: "Un feed con più di 30 giorni e attività nella rete: karma sopra lo zero.", + bankRulesEligibility3: "Una richiesta al mese (epoch), da Banking con il pulsante Richiedi RBU. Il mese in corso si può rifiutare con Rifiuta il RBU.", + bankRulesEligibility4: "La soglia di 1 ECO si paga sempre; le imposte riducono solo la parte che la supera e non si pagano mai a parte.", + bankRulesEligibility5: "Il PUB paga nel ciclo successivo e pubblica un trasferimento RBU che puoi confermare.", + bankRulesEligibility6: "Se manca l'indirizzo, il feed è troppo giovane, hai rifiutato il mese o il PUB non ha fondi, la richiesta viene saltata e registrata sul PUB.", + copyLinkTitle: "Copia link", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Rifiuta il RBU", + bankUbiRefusedThisMonth: "Hai rifiutato il RBU di questo mese.", + bankingBotUbiAvailableTitle: "Puoi richiedere il tuo RBU.", + bankingBotUbiAvailableText: "Il tuo RBU di questo mese è disponibile", + bankingBotConfirmTitle: "Hai uno smart-contract in attesa di conferma", + bankingBotConfirmText: "Smart-contract in attesa della tua conferma", + bankChartZoomHint: "Clicca per ingrandire", + bankChartPoolTitle: "Dotazione del RBU (nel tempo)", + bankChartNetworkUbiTitle: "RBU totale redistribuito (rete)", + bankChartSupplyTitle: "Offerta circolante di ECOin (nel tempo)", + bankChartInflationTitle: "Inflazione di ECOin (nel tempo)", + bankChartInflationSeries: "Tasso annualizzato (%)", + bankChartPool: "Dotazione totale (ECO)", + bankChartKarmaTitle: "KARMA Scoring (nel tempo)", + bankChartFundsTitle: "I tuoi fondi ECOin (nel tempo)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin connesso", + bankWalletNotConfigured: "ECOin disconnesso", + transfersPayWithWallet: "PAGA!", walletStatus: "Stato", walletDisconnected: [ "Portafoglio ECOin ", @@ -685,8 +881,11 @@ module.exports = { " o lo stato della connessione.", ], walletSentToLine: ({ destination, amount }) => `Inviati ECO ${amount} a ${destination}`, + walletNotConfigured: "Il tuo portafoglio ECOin non è ancora configurato.", walletSettingsDescription: "Integra Oasis con il tuo portafoglio ECOin.", - walletSettingsDocLink: "Guida all'installazione di ECOin", + walletSettingsMobileDescription: "Collega Oasis all'app ECOin Wallet di questo dispositivo.", + walletDownloadEcoin: "Scarica ECOin!", + walletRpcUrl: "URL RPC", walletStatusMessages: { invalid_amount: "Importo non valido", invalid_dest: "Indirizzo di destinazione non valido", @@ -702,6 +901,7 @@ module.exports = { walletUser: "Nome utente", walletPass: "Password", walletConfiguration: "Imposta portafoglio", + walletDisconnectButton: "Disconnetti wallet", cipher: "Crittografia", cipherTitle: "Crittografia", cipherDescription: "Crittografa e decifra il tuo testo simmetricamente (usando una password condivisa).", @@ -735,17 +935,16 @@ module.exports = { bookmarkFilterRecent: "RECENTI", bookmarkCreateButton: "Crea segnalibro", bookmarkUpdateButton: "Aggiorna", - bookmarkDeleteButton: "Elimina", bookmarkAddFavoriteButton: "Aggiungi preferito", bookmarkRemoveFavoriteButton: "Rimuovi preferito", bookmarkUrlLabel: "Link", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Descrizione", bookmarkDescriptionPlaceholder: "Opzionale", bookmarkTagsLabel: "Tag", bookmarkTagsPlaceholder: "Inserisci tag separati da virgole", bookmarkLastVisitLabel: "Ultima visita", - bookmarkSearchPlaceholder: "Cerca URL, tag, categoria, autore...", + bookmarkSearchPlaceholder: "Cerca in Segnalibri...", bookmarkSortRecent: "Più recenti", bookmarkSortOldest: "Più vecchi", bookmarkSortTop: "Più votati", @@ -784,7 +983,7 @@ module.exports = { videoDescriptionPlaceholder: "Opzionale", videoNoFile: "Nessun file video fornito", noVideos: "Nessun video disponibile.", - videoSearchPlaceholder: "Cerca titolo, tag, autore...", + videoSearchPlaceholder: "Cerca in Video...", videoSortRecent: "Più recenti", videoSortOldest: "Più vecchi", videoSortTop: "Più votati", @@ -807,7 +1006,6 @@ module.exports = { documentFilterFavorites: "PREFERITI", documentCreateButton: "Carica documento", documentUpdateButton: "Aggiorna", - documentDeleteButton: "Elimina", documentAddFavoriteButton: "Aggiungi preferito", documentRemoveFavoriteButton: "Rimuovi dai preferiti", documentMessageAuthorButton: "PM", @@ -820,7 +1018,7 @@ module.exports = { documentDescriptionPlaceholder: "Opzionale", documentNoFile: "Nessun file.", noDocuments: "Nessun documento disponibile.", - documentSearchPlaceholder: "Cerca titolo, tag, descrizione, autore...", + documentSearchPlaceholder: "Cerca in Documenti...", documentSortRecent: "Più recenti", documentSortOldest: "Più vecchi", documentSortTop: "Più votati", @@ -841,19 +1039,19 @@ module.exports = { audioFilterTop: "TOP", audioCreateButton: "Carica audio", audioUpdateButton: "Aggiorna", - audioDeleteButton: "Elimina", audioAddFavoriteButton: "Aggiungi preferito", audioRemoveFavoriteButton: "Rimuovi preferito", audioFileLabel: "Seleziona un file audio (.mp3, .wav, .ogg)", audioTagsLabel: "Tag", audioTagsPlaceholder: "Inserisci tag separati da virgole", + tagsPlaceholder: "Inserisci tag separati da virgole", audioTitleLabel: "Titolo", audioTitlePlaceholder: "Opzionale", audioDescriptionLabel: "Descrizione", audioDescriptionPlaceholder: "Opzionale", audioNoFile: "Nessun file audio fornito", noAudios: "Nessun audio disponibile.", - audioSearchPlaceholder: "Cerca titolo, tag, autore...", + audioSearchPlaceholder: "Cerca in Audio...", audioSortRecent: "Più recenti", audioSortOldest: "Più vecchi", audioSortTop: "Più votati", @@ -865,7 +1063,7 @@ module.exports = { audioFilterFavorites: "PREFERITI", favoritesTitle: "Preferiti", favoritesDescription: "Tutti i tuoi contenuti preferiti in un unico posto.", - favoritesSearchPlaceholder: "Cerca nei preferiti...", + favoritesSearchPlaceholder: "Cerca in Preferiti...", favoritesFilterAll: "TUTTI", favoritesFilterRecent: "RECENTI", favoritesFilterAudios: "AUDIO", @@ -877,13 +1075,12 @@ module.exports = { favoritesFilterChats: "CHAT", favoritesFilterCalendars: "CALENDARI", favoritesFilterVideos: "VIDEO", - favoritesRemoveButton: "Rimuovi dai preferiti", favoritesNoItems: "Nessun preferito ancora.", yourContacts: "I tuoi contatti", allInhabitants: "Abitanti", allCVs: "CV", discoverPeople: "Scopri abitanti nella tua rete.", - searchInhabitantsPlaceholder: "FILTRA abitanti PER NOME …", + searchInhabitantsPlaceholder: "Cerca in Abitanti...", filterLocation: "FILTRA abitanti PER POSIZIONE …", filterLanguage: "FILTRA abitanti PER LINGUA …", filterSkills: "FILTRA abitanti PER COMPETENZE …", @@ -907,7 +1104,6 @@ module.exports = { topSectionTitle: "TOP", blockedLabel: "Abitante bloccato", inhabitantviewDetails: "Vedi dettagli", - cvVisitButton: "Visita il CV", keepReading: "Continua a leggere...", oasisId: "ID", noInhabitantsFound: "Nessun abitante trovato ancora.", @@ -925,6 +1121,18 @@ module.exports = { parliamentFilterLeaders: "LEADER", parliamentFilterRules: "REGOLE", parliamentGovernmentCard: "Governo attuale", + parliamentSeatsGovernment: "Governo", + parliamentSeatsOpposition: "Opposizione", + parliamentSeatsAbstention: "Astensione", + parliamentSeatsQuorum: "Quorum elettorale", + parliamentSeatsPass: "Soglia di approvazione", + parliamentSeatsByElection: "Per elezione", + parliamentSeatsByHouses: "Per casa del LARP", + parliamentSeatsSeats: "seggi", + parliamentSeatsSeat: "seggio", + parliamentSeatsLeader: "Leader", + parliamentSeatsVotesFor: "voti per", + parliamentSeatsNoHouse: "Senza casa", parliamentActorInPowerInhabitant: 'ABITANTE AL POTERE', parliamentActorInPowerTribe: 'TRIBÙ AL POTERE', parliamentHistoricalGovernmentsTitle: 'GOVERNI', @@ -975,7 +1183,7 @@ module.exports = { parliamentMethodKARMATOCRACY: "Karmatocrazia", parliamentThId: "ID", parliamentThProposalDate: "Data proposta", - parliamentThMethod: "Method", + parliamentThMethod: "Metodo", parliamentThKarma: "Karma", parliamentThSupports: "Supporti", parliamentThVote: "Voto", @@ -992,6 +1200,7 @@ module.exports = { parliamentTimeRemaining: "Tempo rimanente", parliamentElectionsStatusTitle: "Prossimo governo", parliamentProposalDeadlineLabel: "Scadenza", + parliamentViewReport: "Vedi rapporto", parliamentProposalTimeLeft: "Tempo rimanente", parliamentRulesTitle: "Come funziona il Parlamento", parliamentRulesIntro: "Le elezioni si risolvono ogni 2 mesi; le candidature sono continue e si azzerano quando un governo viene scelto.", @@ -1006,6 +1215,7 @@ module.exports = { parliamentRulesLaws: "Quando una proposta raggiunge la soglia, diventa una Legge e appare nella scheda Leggi con la data di emanazione.", parliamentRulesHistorical: "Nella scheda Storico puoi vedere ogni ciclo di governo avvenuto e i dati sulla sua gestione.", parliamentRulesLeaders: "Nella scheda Leader puoi vedere una classifica degli abitanti/tribù che hanno governato (o si sono candidati), ordinata per efficienza.", + parliamentRulesHemicycle: "L'emiciclo assegna un seggio per abitante: il blocco di governo (gli elettori della candidatura vincente e il leader), un blocco di opposizione per ogni altra candidatura del ciclo, e l'astensione. I segni tratteggiati indicano il quorum elettorale (25% del censimento) e la soglia di approvazione del metodo in vigore. \"Per casa del LARP\" colora gli stessi seggi per casa.", parliamentProposalVoteStatusLabel: "Stato del voto", parliamentProposalOnTrackYes: "Soglia raggiunta", parliamentProposalOnTrackNo: "Sotto la soglia", @@ -1019,6 +1229,7 @@ module.exports = { courtsCaseFormTitle: "Apri caso", courtsCaseRespondent: "Accusato / Convenuto", courtsCaseRespondentPh: "Oasis ID (@...) o nome tribù", + courtsCaseSubjectPlaceholder: "Oggetto o breve descrizione", courtsCaseMethod: "Metodo di risoluzione", courtsCaseSubmit: "Presenta caso", courtsNominateJudge: "Nomina giudice", @@ -1052,15 +1263,30 @@ module.exports = { courtsMethodDICTATOR: "Dittatore", courtsMethodPOPULAR: "Popolare", courtsMethodKARMATOCRACY: "Karmatocrazia", + courtsCaseType: "Tipo di caso", + courtsPresetMinorConflict: "Conflitto lieve", + courtsPresetModerateConflict: "Conflitto moderato", + courtsPresetSevereConflict: "Conflitto grave", + courtsPresetHarassment: "Molestie o abusi", + courtsPresetContentModeration: "Moderazione dei contenuti", + courtsPresetBanRestriction: "Espulsione o restrizione", + courtsPresetGovernanceDispute: "Controversia di governance", + courtsStatusOPEN: "Aperto", + courtsStatusIN_PROGRESS: "In corso", + courtsStatusDECIDED: "Deciso", + courtsStatusCLOSED: "Chiuso", + courtsStatusSOLVED: "Risolto", + courtsStatusUNSOLVED: "Irrisolto", + courtsStatusDISCARDED: "Scartato", courtsRulesTitle: "Come funzionano i Tribunali", courtsRulesIntro: "I Tribunali sono un processo gestito dalla comunità per risolvere i conflitti e promuovere la giustizia riparativa. Dialogo, prove chiare e rimedi proporzionali hanno la priorità.", courtsRulesLifecycle: "Processo: 1) Apri caso 2) Seleziona metodo 3) Presenta prove 4) Udienza e deliberazione 5) Verdetto e rimedio 6) Adempimento e chiusura 7) Appello (se applicabile).", - courtsRulesRoles: "Accusatore: apre il caso. Difesa: persona o tribù accusata. Metodo: meccanismo scelto dalla comunità per facilitare, valutare le prove ed emettere un verdetto. Testimone: fornisce testimonianza o prove. Mediatori: persone neutrali invitate dall'accusatore e/o dalla difesa, con accesso a tutti i dettagli, che aiutano a de-escalare il conflitto e co-creare accordi.", + courtsRulesRoles: "Accusatore: apre il caso. Difesa: abitante o tribù accusata. Metodo: meccanismo scelto dalla comunità per facilitare, valutare le prove ed emettere un verdetto. Testimone: fornisce testimonianza o prove. Mediatori: abitanti neutrali invitati dall'accusatore e/o dalla difesa, con accesso a tutti i dettagli, che aiutano a de-escalare il conflitto e co-creare accordi.", courtsRulesEvidence: "Descrizione fino a 1000 caratteri. Allega immagini, audio, video e documenti PDF pertinenti e leciti. Non condividere dati privati sensibili senza consenso.", courtsRulesDeliberation: "Le udienze possono essere pubbliche o private. I giudici assicurano il rispetto, richiedono chiarimenti e possono scartare materiale irrilevante o illecito.", courtsRulesVerdict: "La riparazione è prioritaria: scuse, accordi di mediazione, moderazione dei contenuti, restrizioni temporanee o altre misure proporzionali. La motivazione deve essere registrata.", courtsRulesAppeals: "Appello: consentito quando ci sono nuove prove o un chiaro errore procedurale. Deve essere presentato entro 7 giorni salvo diversa indicazione.", - courtsRulesPrivacy: "Rispetta la privacy e la sicurezza. Doxing, odio o minacce vengono rimossi. I giudici possono modificare o sigillare parti del verbale per proteggere le persone a rischio.", + courtsRulesPrivacy: "Rispetta la privacy e la sicurezza. Doxing, odio o minacce vengono rimossi. I giudici possono modificare o sigillare parti del verbale per proteggere gli abitanti a rischio.", courtsRulesMisconduct: "Molestie, manipolazione o prove fabbricate possono portare a una risoluzione negativa immediata.", courtsRulesGlossary: "Caso: registro di un conflitto. Prove: materiali a supporto delle affermazioni. Verdetto: decisione con rimedio. Appello: richiesta di revisione del verdetto.", courtsFilterActions: "AZIONI", @@ -1076,12 +1302,16 @@ module.exports = { exploreTrending: "Esplora i contenuti più popolari nella tua rete.", votesButton: "VOTAZIONI", shopProductButton: "NEGOZIO", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISODIO", + campaignButton: "CAMPAGNA", + logisticsRouteButton: "PERCORSO", trendingTotalOpinions: "Opinioni totali", trendingNoContentMessage: "Ancora nessuna tendenza.", trendingTotalCount: "Conteggio totale", tasksTitle: "Compiti", tasksDescription: "Scopri e gestisci compiti nella tua rete.", - taskSearchPlaceholder: "Cerca attività...", + taskSearchPlaceholder: "Cerca in Compiti...", taskTitleLabel: "Titolo", taskDescriptionLabel: "Descrizione", taskStartTimeLabel: "Ora di inizio", @@ -1100,7 +1330,7 @@ module.exports = { taskStatusOpen: "Apri", taskStatusInProgress: "In corso", taskStatusClosed: "Chiuso", - taskAssignedTo: "Assegnato a", + taskAssignedTo: "Assegnato", taskAssignedChip: "Assegnata", taskUnassignedChip: "Non assegnata", taskAssignees: "Assegnatari", @@ -1110,6 +1340,7 @@ module.exports = { taskUpdateButton: "Aggiorna", taskDeleteButton: "Elimina", taskFilterAll: "TUTTI", + taskFilterRecent: "RECENTI", taskFilterMine: "MIEI", taskFilterOpen: "APERTI", taskFilterInProgress: "IN CORSO", @@ -1137,14 +1368,13 @@ module.exports = { eventUpcomingDates: "Prossime date", eventsTitle: "Eventi", eventsDescription: "Scopri e gestisci eventi nella tua rete.", - eventSearchPlaceholder: "Cerca eventi...", + eventSearchPlaceholder: "Cerca in Eventi...", eventPrice: "Prezzo", eventStatus: "Stato", eventAllSectionTitle: "Eventi", eventMineSectionTitle: "I tuoi eventi", eventCreateSectionTitle: "Crea evento", eventUpdateSectionTitle: "Aggiorna evento", - eventDeleteButton: "Elimina", eventCreateButton: "Crea evento", eventAddToCalendar: "Aggiungi al calendario", eventVisitCalendar: "Vai al calendario", @@ -1162,11 +1392,11 @@ module.exports = { viewPlace: "Vedi Luogo", generatePdf: "Genera PDF", sharePm: "Condividi via MP", - galleryImages: "Immagini (dimensione max: 50MB ciascuna)", + galleryImages: "Immagini (dimensione max: 75MB ciascuna)", galleryPhotos: "Immagini", galleryAddPhoto: "Aggiungi Immagine", galleryRemovePhoto: "Rimuovi", - galleryVideo: "Video (dimensione max: 50MB)", + galleryVideo: "Video (dimensione max: 75MB)", galleryAddVideo: "Aggiungi Video", galleryRemoveVideo: "Rimuovi video", viewVotation: "Visualizza votazione", @@ -1186,6 +1416,7 @@ module.exports = { eventDate: "Data", eventAttendButton: "Partecipa all'evento", eventFilterAll: "TUTTI", + eventFilterRecent: "RECENTI", eventFilterMine: "MIEI", eventFilterToday: "OGGI", eventFilterWeek: "QUESTA SETTIMANA", @@ -1212,7 +1443,7 @@ module.exports = { tagsFilterTop: "TOP", tagsFilterCloud: "NUVOLA", tagsNoItems: "Nessun tag disponibile.", - tagsSearchPlaceholder: "Cerca etichette...", + tagsSearchPlaceholder: "Cerca in Tag...", tagsTableHeaderTag: "TAG/LINK:", tagsTableHeaderCount: "CONTATORE:", transfersTitle: "Trasferimenti", @@ -1220,7 +1451,10 @@ module.exports = { transfersFrom: "Da", transfersTo: "A", transfersFilterAll: "TUTTI", + transfersFilterRecent: "RECENTI", transfersFilterMine: "MIEI", + transfersFilterMinePending: "IN ATTESA", + transfersFilterMineConfirmed: "FIRMATI", transfersFilterUBI: "RBU", transfersFilterMarket: "MERCATO", transfersFilterTop: "TOP", @@ -1235,7 +1469,6 @@ module.exports = { transfersCategoryEconomic: "Economica", transfersCategoryTime: "Tempo", transfersCategoryTrust: "Fiducia", - transfersCategoryApply: "Applica", transfersUnitEco: "ECO", transfersUnitHours: "h", transfersUnitTrust: "fiducia", @@ -1265,7 +1498,7 @@ module.exports = { transfersDiscardedSectionTitle: "Trasferimenti scartati", transfersCreateSectionTitle: "Crea trasferimento", transfersAllSectionTitle: "Trasferimenti", - transfersSearchPlaceholder: "Cerca concetto, tag, abitanti...", + transfersSearchPlaceholder: "Cerca in Trasferimenti...", transfersMinAmountLabel: "Importo minimo", transfersMaxAmountLabel: "Importo massimo", transfersSortRecent: "Più recenti", @@ -1287,6 +1520,7 @@ module.exports = { voteErrorTitle: "Controlla il modulo", voteErrorDeadlineMin: "La scadenza deve essere di almeno {days} giorni da ora.", voteErrorGeneric: "Non è stato possibile salvare la votazione.", + voteLocked: "Questa votazione ha già ricevuto voti o opinioni, o appartiene al Parlamento o ai Tribunali: non si può più modificare né eliminare.", voteUpdateButton: "Aggiorna", voteDeleteButton: "Elimina", voteOptionYes: "SÌ", @@ -1296,6 +1530,7 @@ module.exports = { voteFollowMajority: "SEGUI MAGGIORANZA", voteNotInterested: "NON INTERESSATO", voteFilterAll: "TUTTI", + voteFilterRecent: "RECENTI", voteFilterMine: "MIEI", voteFilterOpen: "APERTE", voteFilterClosed: "CHIUSE", @@ -1311,7 +1546,6 @@ module.exports = { voteTags: "Tag", voteOpinions: "Opinioni", novotes: "Nessuna proposta di voto disponibile.", - voteBy: "Di", voteCreatedAt: "Creato il", voteNoQuestion: "Nessuna domanda fornita", voteUnknownCreator: "Creatore sconosciuto", @@ -1321,6 +1555,7 @@ module.exports = { voteCommentsForumButton: 'Apri discussione', voteCommentsSectionTitle: 'Apri discussione', voteNoCommentsYet: 'Non ci sono ancora commenti. Sii il primo a rispondere.', + commentsGoToLast: "Vai all'ultimo commento", voteNewCommentPlaceholder: 'Scrivi il tuo commento qui…', voteNewCommentButton: 'Pubblica commento', voteNewCommentLabel: 'Aggiungi un commento', @@ -1331,6 +1566,8 @@ module.exports = { cvNameLabel: "Nome completo", cvDescriptionLabel: "Riepilogo", cvPhotoLabel: "Foto", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Visita il CV", cvPersonalExperiencesLabel: "Esperienze personali", cvPersonalSkillsLabel: "Competenze personali (separate da virgole)", cvOasisExperiencesLabel: "Esperienze di contributo Oasis", @@ -1360,9 +1597,9 @@ module.exports = { cvNoCV: "Nessun CV trovato.", blogSubject: "Oggetto", blogMessage: "Messaggio", - blogImage: "Carica media (max: 50MB)", + blogImage: "Carica media (max: 75MB)", blogMedia: "Allegati (fino a 8 immagini e un video)", - blogPublish: "Anteprima", + blogPublish: "Pubblica", pollsTitle: "Sondaggi", dataTitle: "Corrispondenze", dataDescription: "Esplora e visualizza le corrispondenze della tua rete.", @@ -1381,12 +1618,23 @@ module.exports = { dataKindTasks: "COMPITI", dataKindReports: "RAPPORTI", dataKindVotes: "VOTAZIONI", - dataSearchPlaceholder: "Cerca nei dati incrociati...", + dataSearchPlaceholder: "Cerca in Corrispondenze...", dataCohesionTitle: "Coefficiente di coesione (CC)", dataCcShort: "CC", dataCohesionHint: "Affinità media fra tutto ciò che la rete ha pubblicato.", dataAffinity: "Affinità", dataCommonTerms: "Parola chiave", + dataReasonFollowing: "Supporti questo abitante", + dataReasonTribe: "Condivide una tribù con te", + dataReasonPinned: "Coincide con qualcosa che hai fissato", + dataReasonCv: "Corrisponde alle competenze del tuo CV", + dataReasonContent: "Legato a ciò che pubblichi", + dataReasonRelated: "Legato ai tuoi interessi", + dataReasonAlike: "Pubblicato da un abitante affine a te", + dataReasonSupportsYou: "Ti supporta", + dataReasonMutual: "Supporto reciproco", + dataReasonRated: "Ben valutato dalla rete", + dataReasonNear: "Vicino a te", dataStatPeople: "CV", dataStatConnected: "Collegati", dataStatSkills: "Competenze", @@ -1439,7 +1687,7 @@ module.exports = { pollStatusLabel: "Stato", pollStatusOpen: "APERTO", pollStatusClosed: "CHIUSO", - pollSearchPlaceholder: "Cerca sondaggi...", + pollSearchPlaceholder: "Cerca in Sondaggi...", pollsNoItems: "Nessun sondaggio trovato.", viewPoll: "Vedi Sondaggio", pollChatCreate: "Crea Sondaggio", @@ -1452,7 +1700,7 @@ module.exports = { blogFilterFavorites: "PREFERITI", blogFilterTop: "TOP", blogCreateButton: "Crea Blog", - blogSearchPlaceholder: "Cerca blog...", + blogSearchPlaceholder: "Cerca in Blog...", blogComments: "Commenti", blogAllowComments: "Consenti commenti", blogCommentsClosed: "L'autore ha chiuso i commenti di questo blog.", @@ -1465,12 +1713,11 @@ module.exports = { forumCreateButton: "Crea forum", forumCreateSectionTitle: "Crea forum", forumDescription: "Parla apertamente con altri abitanti nella tua rete.", - forumSearchPlaceholder: "Cerca forum...", + forumSearchPlaceholder: "Cerca in Forum...", forumFilterAll: "TUTTI", forumFilterMine: "MIEI", forumFilterRecent: "RECENTI", forumFilterTop: "TOP", - forumDeleteButton: "Elimina", forumParticipants: "partecipanti", forumMessages: "messaggi", forumMessageLabel: "Messaggio", @@ -1497,7 +1744,6 @@ module.exports = { imageFilterGallery: "GALLERIA", imageCreateButton: "Carica immagine", imageUpdateButton: "Aggiorna", - imageDeleteButton: "Elimina", imageAddFavoriteButton: "Aggiungi preferito", imageRemoveFavoriteButton: "Rimuovi preferito", imageFileLabel: "Seleziona un file immagine (.jpeg, .jpg, .png, .gif)", @@ -1509,13 +1755,14 @@ module.exports = { imageDescriptionPlaceholder: "Opzionale", imageNoFile: "Nessun file immagine fornito", noImages: "Nessuna immagine disponibile.", - imageSearchPlaceholder: "Cerca titolo, tag, descrizione, autore...", + imageSearchPlaceholder: "Cerca in Immagini...", imageSortRecent: "Più recenti", imageSortOldest: "Più vecchie", imageSortTop: "Più votate", imageSearchButton: "Cerca", imageUpdatedAt: "Aggiornato", imageNoMatch: "Nessuna immagine corrisponde alla ricerca.", + feedSearchPlaceholder: "Cerca in Feed...", feedTitle: "Feed", createFeedTitle: "Crea Feed", createFeedButton: "Invia Feed!", @@ -1530,7 +1777,7 @@ module.exports = { alreadyRefeeded: "Hai già fatto il refeed.", activityTitle: "Attività", yourActivity: "La tua attività", - globalActivity: "Attività globale", + globalActivity: "Attività", activityList: "Attività", activityDesc: "Vedi l'ultima attività nella tua rete.", allButton: "TUTTI", @@ -1545,7 +1792,8 @@ module.exports = { meme: "Meme", typeRecent: "RECENTI", typeTop: "TOP", - typePost: "BLOGS", + typePost: "BLOG", + activityUpdateLabel: "AGGIORNAMENTO", typeTribe: "TRIBÙ", typeLarp: "L.A.R.P.", typeInhabitants: "ABITANTI", @@ -1570,10 +1818,10 @@ module.exports = { typeFeed: "FEEDS", typeContact: "CONTATTO", typePub: "PUB", - typeTombstone: "TOMBSTONE", + typeTombstone: "ELIMINATO", typeBanking: "BANCA", typeBankWallet: "BANCA/PORTAFOGLIO", - typeBankClaim: "BANCA/UBI", + typeBankClaim: "BANCA/RBU", typeKarmaScore: "KARMA", typeParliament: "PARLAMENTO", typeSpread: "DIFFUSIONI", @@ -1582,7 +1830,7 @@ module.exports = { typeParliamentProposal:"Parlamento · Proposta", typeParliamentRevocation:"Parlamento · Revoca", typeParliamentLaw: "Parlamento · Nuova legge", - typeCourts: "COURTS", + typeCourts: "TRIBUNALI", typeCourtsCase: "Tribunali · Caso", typeCourtsEvidence: "Tribunali · Prova", typeCourtsAnswer: "Tribunali · Risposta", @@ -1608,7 +1856,7 @@ module.exports = { location: "Posizione", contentWarning: "Oggetto", bankWalletConnected: "Portafoglio ECOin", - bankUbiReceived: "UBI ricevuto", + bankUbiReceived: "RBU ricevuto", bankTx: "Tx", bankEpochShort: "Epoca", bankingUserEngagementScore: "Punteggio KARMA", @@ -1620,9 +1868,9 @@ module.exports = { unfollowing: "NON SEGUENDO", pledged: "CONTRIBUITO", parliamentCandidatureId: "Candidature", - parliamentGovMethod: "Method", - parliamentVotesReceived: "Votes received", - parliamentMethodANARCHY: "Anarchy", + parliamentGovMethod: "Metodo", + parliamentVotesReceived: "Voti ricevuti", + parliamentMethodANARCHY: "Anarchia", parliamentMethodVOTE: "Voto comunitario", parliamentMethodRANKED: "Scelta graduata", parliamentMethodPLURALITY: "Pluralità", @@ -1680,7 +1928,7 @@ module.exports = { courtsNoCases: 'Nessun caso.', reportsTitle: "Rapporti", reportsDescription: "Gestisci e segui le segnalazioni della tua rete.", - reportsSearchPlaceholder: "Cerca segnalazioni...", + reportsSearchPlaceholder: "Cerca in Rapporti...", reportsFilterAll: "TUTTI", reportsFilterRecent: "RECENTI", reportsFilterTop: "TOP", @@ -1728,7 +1976,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Segnalazioni in revisione", reportsResolvedSectionTitle: "Segnalazioni risolte", reportsInvalidSectionTitle: "Segnalazioni non valide", - reportsTemplateSectionTitle: 'Modello di segnalazione', reportsBugTemplateTitle: 'Dettagli di riproduzione (Bug)', reportsFeatureTemplateTitle: 'Dettagli (Funzionalità)', reportsAbuseTemplateTitle: 'Dettagli (Abuso)', @@ -1788,17 +2035,15 @@ module.exports = { tribeGallerySectionTitle: "Galleria tribù", tribeRecentSectionTitle: "Tribù recenti", tribeTopSectionTitle: "Tribù popolari", - tribeviewTribeButton: "Visita tribù", - tribeviewSubTribeButton: "Visita Sotto-Tribù", tribeRootLabel: "RADICE", tribeDescription: "Esplora o crea tribù nella tua rete.", tribeMainTribeLabel: "TRIBÙ PRINCIPALE", tribeCreateButton: "Crea tribù", tribeUpdateButton: "Aggiorna", tribeDeleteButton: "Elimina", - tribeImageLabel: "Carica media (max: 50MB)", + tribeImageLabel: "Carica media (max: 75MB)", tribeTitleLabel: "Titolo", - searchTribesPlaceholder: "FILTRA tribù PER NOME …", + searchTribesPlaceholder: "Cerca in Tribù...", tribeTitlePlaceholder: "Nome della tribù", tribeDescriptionLabel: "Descrizione", tribeDescriptionPlaceholder: "Descrivi questa tribù", @@ -1811,6 +2056,15 @@ module.exports = { tribeIsAnonymousLabel: "STATO", tribeMembersCount: "Membri", tribeEnterInvite: "INSERISCI CODICE", + subscriptionTitle: "Iscrizioni", + subscriptionOn: "Iscritto", + subscriptionOff: "Non iscritto", + subscriptionSubscribe: "Iscriviti", + subscriptionUnsubscribe: "Annulla iscrizione", + subscriptionSubscribers: "Iscritti", + pmListLabel: "Invia a", + pmListNone: "— Nessuna lista —", + pmListNoSubscribers: "Nessun iscritto", tribeLeaveButton: "ESCI", tribePublic: "PUBBLICO", tribePrivate: "PRIVATO", @@ -1843,16 +2097,16 @@ module.exports = { tribeSectionVideos: "VIDEO", tribeSectionDocuments: "DOCUMENTI", tribeSectionBookmarks: "SEGNALIBRI", - tribeSectionMaps: "MAPS", - tribeSectionPads: "PADS", - tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionMaps: "MAPPE", + tribeSectionPads: "PAD", + tribeSectionChats: "CHAT", + tribeSectionCalendars: "CALENDARI", + tribePadCreate: "Crea pad", + tribeChatCreate: "Crea chat", + tribeCalendarCreate: "Crea calendario", + tribePadsEmpty: "Ancora nessun pad.", + tribeChatsEmpty: "Ancora nessuna chat.", + tribeCalendarsEmpty: "Ancora nessun calendario.", tribeInhabitantsEmpty: "Nessun abitante in questa tribù.", tribeEventCreate: "Crea evento", tribeEventsEmpty: "Nessun evento.", @@ -1907,9 +2161,14 @@ module.exports = { tribeMediaTypeDocument: "Documento", tribeMediaTypeBookmark: "Segnalibro", tribeContentDelete: "ELIMINA", + tribeReachTribe: "TRIBÙ", + tribeReachNotice: "OASIS lo mostra a tutti gli abitanti e CLEARNET anche sul web aperto. Aprirlo lascia una copia leggibile fuori dalla tribù: tornare a TRIBÙ lo nasconde, ma le copie già condivise non possono essere cancellate.", + tribeReachPrivate: "Il contenuto di una tribù privata può restare solo dentro la tribù.", + tribeReachOpenedBy: "Aperto da", + tribeReachChange: "Dove viene pubblicato", tribeInviteCodeText: "Codice invito: ", oasisVersionLabel: "Versione di Oasis", - tribeInviteCodeHint: "Condividi questo codice con chi vuoi invitare. Può unirsi tramite /invites → Tribes.", + tribeInviteCodeHint: "Condividi questo codice con chi vuoi invitare. Può unirsi tramite /invites.", tribeStatusOpen: "APERTO", tribeStatusClosed: "CHIUSO", tribeStatusInProgress: "IN CORSO", @@ -1949,7 +2208,7 @@ module.exports = { tribeSearchMinChars: "Inserisci almeno 2 caratteri per cercare.", agendaTitle: "Agenda", agendaDescription: "Qui trovi tutti gli elementi assegnati a te.", - agendaSearchPlaceholder: "Cerca nell'agenda...", + agendaSearchPlaceholder: "Cerca in Agenda...'agenda...", agendaFilterAll: "TUTTI", agendaFilterToday: "OGGI", agendaFilterUpcoming: "PROSSIMI", @@ -1970,6 +2229,7 @@ module.exports = { agendaNoItems: "Nessun incarico trovato.", agendaDiscardButton: "Scarta", agendaRestoreButton: "Ripristina", + agendaRemoveButton: "Rimuovi", agendaMembersCount: "Membri", agendaLocationLabel: "Posizione", agendaYes: "SÌ", @@ -1985,7 +2245,7 @@ module.exports = { agendaTransferDeadline: "Scadenza", opinionsTitle: "Opinioni", shareYourOpinions: "Scopri e vota opinioni nella tua rete.", - author: "By", + author: "Di", voteNow: "Vota ora", alreadyVoted: "Hai già espresso un'opinione.", noOpinionsFound: "Nessuna opinione trovata.", @@ -2036,8 +2296,8 @@ module.exports = { pmComposeTitle: "Invia un messaggio", pmRecipients: "Destinatari", pmRecipientsHint: "Inserisci Oasis ID separati da virgole", - pmLimitsHint: "Fino a 7 destinatari per messaggio.", - pmTextPlaceholder: "Corpo limitato a 7000 caratteri.", + pmLimitsHint: "Fino a 7 abitanti per messaggio.", + pmTextPlaceholder: "Scrivi il tuo messaggio.", pmSubject: "Oggetto", pmSubjectHint: "Inserisci l'oggetto del messaggio", pmText: "Messaggio", @@ -2059,8 +2319,8 @@ module.exports = { pmCrypterCharsLabel: "caratteri", pmInvalidRecipients: "Oasis ID non valido (dovrebbe essere tipo @…=.ed25519).", pmEncryptionLabel: "Cifratura", - private: "Privato", - privateDescription: "I tuoi messaggi crittografati.", + private: "Posta in arrivo", + privateDescription: "Gestisci i tuoi messaggi privati.", privateInbox: "POSTA IN ARRIVO", privateSent: "INVIATI", privateDelete: "Elimina", @@ -2095,7 +2355,7 @@ module.exports = { inboxProjectPledgedTitle: "Nuovo contributo al tuo progetto", pmHasPledged: "ha contribuito", pmToYourProject: "al tuo progetto", - blockchain: 'BlockExplorer', + blockchain: "Esploratore di blocchi", blockchainDescription: 'Esplora e visualizza i blocchi nella blockchain.', blockchainNoBlocks: 'Nessun blocco trovato nella blockchain.', blockchainStatsTitle: 'Statistiche', @@ -2116,24 +2376,24 @@ module.exports = { visitContent: "Visita il Contenuto", favoriteAdd: "Aggiungi ai Preferiti", pmContentTooltip: "Invia un Messaggio Privato", + phoneCallTooltip: "Fai una Chiamata", favoriteRemove: "Togli dai Preferiti", reportContent: "Segnala il Contenuto", - banking: 'Banking', - bankingTitle: 'Banking', - bankingDescription: "L'economia ECOin: saldo, reddito di base, tasse e valore dell'industria.", + banking: "Banca", + bankingTitle: "Banca", + bankingDescription: "L'economia collettiva di ECOin.", bankOverview: 'Panoramica', bankEpochs: 'Epoche', bankRules: 'Regole', pending: 'In attesa', closed: 'Chiuso', - bankViewTx: 'Vedi Tx', bankClaimNow: 'Riscuoti ora', - bankClaimUBI: 'Richiedi RBU!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: 'Richiedi RBU', + bankClaimAndPay: "Richiedi e paga", + bankClaimedPending: "Richiesta in sospeso...", + bankStatusClaimed: "Richiesto", + bankStatusExpired: "Scaduto", + bankPubOnly: "Operazione solo per PUB", bankEpoch: 'Epoca', bankPool: 'Pool (questa epoca)', bankWeightsSum: 'Somma dei pesi', @@ -2152,8 +2412,8 @@ module.exports = { bankViewEpoch: 'Vedi epoca', bankYourIndustryBalance: "La tua quota industriale", bankYourUbiMonth: "Il tuo RBU (questo mese)", - bankYourFundsMonth: "I tuoi fondi (questo mese)", - bankIndustryBalance: "Produzione industriale (stimata)", + bankYourFundsMonth: "Fondi totali", + bankIndustryBalance: "Produzione industriale", bankUserBalance: 'Il tuo saldo', bankAddresses: 'Indirizzi', bankNoAddresses: 'Nessun indirizzo trovato.', @@ -2161,6 +2421,10 @@ module.exports = { bankAddress: 'Indirizzo', bankAddAddressTitle: 'Aggiungi indirizzo ECOIN', bankAddAddressUser: 'Oasis ID', + bankAddAddressLabel: "Etichetta (facoltativa)", + bankAddAddressLabelPlaceholder: "Portafoglio di Bob, negozio di Alice…", + bankAddAddressUserOptional: "Oasis ID (facoltativo)", + bankAddressUnnamed: "Indirizzo senza nome", bankAddAddressAddress: 'Indirizzo ECOIN', bankAddAddressSave: 'Salva', bankAddressAdded: 'Indirizzo aggiunto', @@ -2178,11 +2442,11 @@ module.exports = { bankAddressDeleteConfirm: 'Eliminare questo indirizzo?', search: 'Cerca!', bankLocal: 'Locale', - bankFromOasis: 'Oasis', + bankFromOasis: "Rete", pubIdLabel: "PUB ID", bankUbiAvailableNo: "NESSUN FONDO!", bankUbiAvailableOk: "DISPONIBILE!", - bankUbiAvailability: "RBU (rete)", + bankUbiAvailability: "RBU", bankAlreadyClaimedThisMonth: "Già richiesto questo mese", bankUbiThisMonth: "RBU (questo mese)", bankUbiLastClaimed: "RBU (ultima richiesta)", @@ -2212,6 +2476,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Cifrato in transito", shopAddFavorite: "Aggiungi Preferito", shopRemoveFavorite: "Rimuovi Preferito", shopOpen: "APERTO", @@ -2219,6 +2484,13 @@ module.exports = { shopMakePrivate: "RENDI PRIVATO (E2E)", shopMakePublic: "RENDI PUBBLICO", shopProducts: "Prodotti", + shopFilterAll: "TUTTI", + shopFilterRecent: "RECENTI", + shopFilterMine: "MIEI", + shopFilterTop: "TOP", + shopFilterProducts: "PRODOTTI", + shopFilterPrices: "PREZZI", + shopFilterFavorites: "PREFERITI", shopProductAdd: "Aggiungi Prodotto", shopProductTitle: "Prodotto", shopProductUpdate: "Aggiorna Prodotto", @@ -2246,7 +2518,7 @@ module.exports = { marketVisitItem: "VEDI OGGETTO", shopStatus: "STATO", shopCreatedAt: "CREATO", - shopSearchPlaceholder: "Cerca negozi...", + shopSearchPlaceholder: "Cerca in Negozi...", shopUrl: "URL", shopLocation: "Posizione", shopTags: "Tag", @@ -2256,16 +2528,18 @@ module.exports = { shopTitlePlaceholder: "Nome del tuo negozio", shopDescriptionPlaceholder: "Descrizione dettagliata del tuo negozio", shopLocationPlaceholder: "Città, Paese", - shopTagsPlaceholder: "tag1, tag2, tag3", + shopTagsPlaceholder: "Inserisci tag separati da virgole", mapTitlePlaceholder: "Titolo della mappa", shopProductFeatured: "Prodotto in Evidenza", - shopSendToMarket: "Send to Market", + shopSendToMarket: "Invia al mercato", typeShop: "NEGOZI", typeShopProduct: "PRODOTTO NEGOZIO", bankExchange: 'Cambio', bankExchangeCurrentValue: 'Valore ECOin (1h)', - bankTotalSupply: 'Offerta totale ECOin', - bankEcoinHours: "Equivalenza ECOin in tempo", + bankTotalSupply: "Offerta ECOin totale", + bankPubsSupply: "Offerta ECOin del pool dei PUB", + bankHoldingSupply: "Offerta ECOin fuori dal RBU", + bankEcoinHours: "Equivalenza ECOin in tempo (1 ECO)", bankHoursOfWork: 'ore', bankUnitMs: "ms", bankUnitSeconds: "secondi", @@ -2279,22 +2553,24 @@ module.exports = { bankExchangeChartSupply: 'Offerta', bankExchangeChartInflation: 'Inflazione %', bankExchangeChartEmpty: 'Non ci sono ancora abbastanza campioni — torna più tardi', - bankCurrentSupply: 'Offerta attuale ECOin', + bankCurrentSupply: "Offerta ECOin attuale", bankingSyncStatus: 'Stato ECOin', bankingSyncStatusSynced: 'Sincronizzato', - bankingSyncStatusOutdated: 'Non aggiornato', + bankingSyncStatusOutdated: "Non sincronizzato", statsTitle: 'Statistiche', statistics: "Statistiche", statsDescription: "Scopri le statistiche della tua rete.", - ALLButton: "ALL", - MINEButton: "MINE", - TOMBSTONEButton: "TOMBSTONES", + ALLButton: "TUTTO", + RECENTButton: "RECENTI", + MINEButton: "MIEI", + TOMBSTONEButton: "ELIMINATI", statsCreatedAt: "Creato il", statsYourContent: "Contenuto", statsYourOpinions: "Opinioni", statsNetworkContent: "Contenuto", statsNetworkOpinions: "Opinioni", - statsEcoWalletLabel: "Portafoglio ECOIN", + statsEcoWalletLabel: "Portafoglio ECOin", + walletAddressCopyHint: "Clicca per selezionare, poi copia", statsEcoWalletNotConfigured: "Non configurato!", statsBookmark: "Segnalibri", statsEvent: "Eventi", @@ -2316,6 +2592,8 @@ module.exports = { statsTransfer: "Trasferimenti", statsAiExchange: "AI", statsPUBs: 'PUBs', + statsSyncedPeers: "Peer", + statsTotalLabel: "Totale", statsPost: "Post", statsSize: "Totale (dimensione)", statsBlockchainSize: "Blockchain (dimensione)", @@ -2367,8 +2645,14 @@ module.exports = { aiPrompt: "Fornisci una risposta informativa e precisa.", aiConfiguration: "Imposta prompt", aiPromptUsed: "Prompt", - aiClearHistory: "Cancella cronologia chat", - aiServerError: "L'AI non ha potuto rispondere. Riprova.", + aiClearHistory: "Cancella cronologia", + aiServerError: "Non riesco a rispondere bene. Riprova.", + aiStatusLoading: "42 sta caricando il modello. Riprova tra un momento.", + aiThinking: "Sta pensando", + aiWaiting: "In attesa", + aiStatusMissingModel: "Il modello di IA non è installato: manca src/AI/oasis-42-1-chat.Q4_K_M.gguf.", + aiExportFineTuning: "Esporta Fine-Tuning", + aiFactsTitle: "Dati dalla tua rete", aiInputPlaceholder: "Cos'è Oasis?", typeAiExchange: "AI", aiApproveTrain: "Aggiungi all'addestramento collettivo", @@ -2397,7 +2681,7 @@ module.exports = { marketFilterDiscarded: "SCARTATO", marketFilterRecent: "RECENTI", marketFilterMyBids: "OFFERTE", - marketCreateButton: "Crea articolo", + marketCreateButton: "Vendi qualcosa", marketItemType: "Tipo", marketItemTitle: "Titolo", marketItemAvailable: "Scadenza", @@ -2424,8 +2708,8 @@ module.exports = { marketItemSeller: "Venditore", marketNoItems: "Nessun articolo disponibile ancora.", marketYourBid: "La tua offerta", - marketCreateFormImageLabel: "Carica media (max: 50MB)", - marketSearchPlaceholder: "Cerca titolo o tag", + marketCreateFormImageLabel: "Carica media (max: 75MB)", + marketSearchPlaceholder: "Cerca in Mercato...", marketMinPriceLabel: "Prezzo minimo", marketMaxPriceLabel: "Prezzo massimo", marketSortRecent: "Più recenti", @@ -2491,8 +2775,7 @@ module.exports = { housingSetClosed: "Rendi non disponibile", housingCreateButton: "Crea luogo", housingUpdateButton: "Aggiorna", - housingDeleteButton: "Elimina", - housingSearchPlaceholder: "Cerca luoghi...", + housingSearchPlaceholder: "Cerca in Alloggi...", housingSearchButton: "Cerca", housingMinPrice: "Prezzo min", housingMaxPrice: "Prezzo max", @@ -2522,7 +2805,7 @@ module.exports = { jobsClosedTitle: "Lavori chiusi", jobsCVTitle: "CV", jobsFilterPresencial: "IN SEDE", - jobsFilterFreelancer: "FREELANCER", + jobsFilterFreelancer: "FREELANCE", jobsFilterEmployee: "DIPENDENTE", jobsPresencialTitle: "Lavori in sede", jobsFreelancerTitle: "Lavori freelance", @@ -2548,7 +2831,7 @@ module.exports = { jobLocationPresencial: "In sede", jobLocationRemote: "Da remoto", jobVacantsPlaceholder: "Numero di posizioni", - jobImage: "Carica media (max: 50MB)", + jobImage: "Carica media (max: 75MB)", jobTasks: "Compiti", jobType: "Tipo lavoro", jobTime: "Orario lavoro", @@ -2568,14 +2851,13 @@ module.exports = { jobsHoursOffered: "Ore offerte", jobsHoursRequested: "Ore richieste in cambio", jobsExchangeSkill: "Competenza richiesta in cambio", - jobTimePartial: "Part-time", - jobTimeComplete: "Full-time", - jobsDeleteButton: "ELIMINA", + jobTimePartial: "Tempo parziale", + jobTimeComplete: "Tempo pieno", jobsUpdateButton: "AGGIORNA", jobsFilterApplied: "CANDIDATURE", jobsAppliedTitle: "Le mie candidature", jobsAppliedBadge: "Candidato", - jobsSearchPlaceholder: "Cerca titolo, tag, descrizione...", + jobsSearchPlaceholder: "Cerca in Lavori...", jobsMinSalaryLabel: "Stipendio minimo", jobsMaxSalaryLabel: "Stipendio massimo", jobsSortRecent: "Più recenti", @@ -2587,15 +2869,15 @@ module.exports = { jobSetOpen: "Segna come aperto", jobsTagsLabel: "Tag", noJobsMatch: "Nessun lavoro corrisponde alla ricerca.", - industrySearchPlaceholder: "Cerca fabbriche…", + industrySearchPlaceholder: "Cerca in Industria...", industryAllSectors: "Tutti i settori", industryVisitButton: "Visita industria", industryAdvanceTo: "Imposta a", industryAmount: "Importo", industryApproveBuild: "Approva", - industryBlueprint: "Blueprint", + industryBlueprint: "Progetto", industryBlueprints: "Blueprint", - industryBuild: "Build", + industryBuild: "Produzione", industryBuildNotes: "Note", industryBuildStart: "Data di inizio", industryVoteUpdateButton: "Vota aggiornamento", @@ -2661,7 +2943,6 @@ module.exports = { industryBuildStatus_FAILED: "FALLITO", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Un lavoro corrisponde al tuo curriculum", jobsBotMatchIntro: "Un lavoro corrisponde al tuo curriculum.", jobsBotMatchJob: "Lavoro", @@ -2688,7 +2969,7 @@ module.exports = { industryRulesIntro: "Industry permette agli abitanti di possedere collettivamente i mezzi di produzione: una Fabbrica è un'officina della rete che nessuno possiede privatamente.", industryRulesSteward: "Il fondatore è l'amministratore — un custode, non un proprietario: non può vendere, privatizzare o sciogliere la Fabbrica da solo.", industryRulesMembership: "La politica di adesione stabilisce come si entra: Aperta (subito), Per voto (ammessi dal voto dei membri) o Solo su invito (un membro deve invitare prima).", - industryRulesQuorum: "Il quorum è il numero minimo di membri che devono votare perché una decisione conti, così una piccola Fabbrica non è decisa da una sola persona.", + industryRulesQuorum: "Il quorum è il numero minimo di membri che devono votare perché una decisione conti, così una piccola Fabbrica non è decisa da un solo abitante.", industryRulesMajority: "La maggioranza è la frazione di voti necessaria (0,5 = metà, 1 = unanimità); una decisione passa quando i SÌ raggiungono almeno max(quorum, membri × maggioranza).", industryRulesDecisions: "I membri votano per ammettere nuovi arrivati, approvare i build e sciogliere la Fabbrica; l'amministratore non può scavalcare queste decisioni collettive.", industryRulesBlueprints: "I Blueprint sono ricette libere (COPYLEFT) — gli input (materiali, ore di lavoro, competenze) e l'output (un bene fisico o digitale) — e chiunque può forkarle.", @@ -2707,13 +2988,14 @@ module.exports = { industryPauseVotes: "Voti di stato", industryTitle: "Industria", industryDescription: "Modulo per gestire collettivamente i mezzi di produzione.", - typeIndustryBuild: "BUILD", - typeIndustryBlueprint: "BLUEPRINT", + typeIndustryBuild: "PRODUZIONE", + typeIndustryBlueprint: "PROGETTO", typeIndustryAllocation: "DISTRIBUZIONE", typeIndustry: "INDUSTRIA", modulesIndustryLabel: "Industria", modulesIndustryDescription: "Modulo per gestire collettivamente i mezzi di produzione.", industryFilterAll: "TUTTE", + industryFilterRecent: "RECENTI", industryFilterMember: "MEMBRO", industryFilterBlueprints: "PROGETTI", industryFilterBuilds: "PRODUZIONI", @@ -2722,6 +3004,7 @@ module.exports = { industryFilterPaused: "IN PAUSA", industryFilterDissolved: "SCIOLTE", industryAllTitle: "Industria", + industryRecentTitle: "Fabbriche recenti", industryMemberTitle: "Fabbriche di cui faccio parte", industryMineTitle: "Le mie fabbriche", industryActiveTitle: "In funzione", @@ -2769,7 +3052,7 @@ module.exports = { industryPolicy_invite: "Solo su invito", projectsTitle: "Progetti", projectsDescription: "Crea, finanzia e segui progetti comunitari nella tua rete.", - projectSearchPlaceholder: "Cerca progetti...", + projectSearchPlaceholder: "Cerca in Progetti...", projectCreateProject: "Crea progetto", projectCreateButton: "Crea progetto", projectUpdateButton: "AGGIORNA", @@ -2792,7 +3075,7 @@ module.exports = { projectRecentTitle: "Progetti recenti", projectTopTitle: "Più finanziati", projectTitlePlaceholder: "Nome del progetto", - projectImage: "Carica media (max: 50MB)", + projectImage: "Carica media (max: 75MB)", projectDescription: "Descrizione", projectDescriptionPlaceholder: "Racconta la storia e gli obiettivi…", projectGoal: "Obiettivo (ECO)", @@ -2864,15 +3147,13 @@ module.exports = { modulesModuleName: "Nome", modulesModuleDescription: "Descrizione", modulesModuleStatus: "Stato", - modulesTotalModulesLabel: "Moduli caricati", + modulesTotalModulesLabel: "Totale", modulesEnabledModulesLabel: "Attivato", modulesDisabledModulesLabel: "Disattivato", modulesInvitesLabel: "Inviti", modulesInvitesDescription: "Modulo per gestire e applicare codici invito.", modulesWalletLabel: "Portafoglio", modulesWalletDescription: "Modulo per gestire i tuoi asset digitali (ECOin).", - modulesLegacyLabel: "Chiavi", - modulesLegacyDescription: "Modulo per gestire il tuo segreto (chiave privata) in modo rapido e sicuro.", modulesCipherLabel: "Crittografia", modulesCipherDescription: "Modulo per crittografare e decifrare il tuo testo simmetricamente (usando una password condivisa).", modulesBookmarksLabel: "Segnalibri", @@ -2934,20 +3215,23 @@ module.exports = { modulesProjectsLabel: "Progetti", modulesProjectsDescription: "Modulo per esplorare, finanziare collettivamente e gestire i progetti.", modulesBankingLabel: "Banca", - modulesBankingDescription: "Modulo per determinare il valore reale di ECOIN e distribuire una UBI usando la tesoreria comune.", + modulesBankingDescription: "Modulo per gestire il tesoro comune: valore di ECOIN, tasse e RBU.", modulesFavoritesLabel: "Preferiti", modulesFavoritesDescription: "Modulo per gestire i tuoi contenuti preferiti.", fileTooLargeTitle: "File troppo grande", - fileTooLargeMessage: "Il file supera la dimensione massima consentita (50 MB). Seleziona un file più piccolo.", + fileTooLargeMessage: "Il file supera la dimensione massima consentita (75 MB). Seleziona un file più piccolo.", goBack: "Torna indietro", + errorDismiss: "OK", errorPageTitle: "Si è verificato un errore", + errorNoAllocation: "Nessuna allocazione trovata per questa richiesta.", + errorInvalidClaim: "Questa allocazione non può essere riscossa.", visibilityLabel: "Visibilità", + clearnetPrivateConflict: "Un contenuto privato, nascosto, chiuso, solo su invito o a pagamento non può essere pubblicato sulla CLEARNET. Scegli OASIS o rendilo pubblico.", visibilityPublic: "Pubblica", visibilityHidden: "Nascosta", visibilityMakePublic: "Rendi pubblica", visibilityMakeHidden: "Rendi nascosta", invitesInhabitantsTitle: "Abitanti", - invitesInhabitantsFollow: "Segui", aiNavPlaceholder: "Descrivi cosa ti serve...", aiNavDisabled: "La navigazione con IA è disattivata.", aiNavResultsTitle: "Suggerimenti AINav", @@ -2958,7 +3242,7 @@ module.exports = { modulesAINavDescription: "Modulo per query in linguaggio naturale sui contenuti della rete.", directConnectDescription: "Connettiti direttamente a un peer inserendo indirizzo IP, porta e chiave pubblica.", peerHost: "IP", - peerPort: "Porta (predefinita: 8008)", + peerPort: "Porta", peerPublicKey: "Chiave pubblica (@...ed25519)", connectAndFollow: "Connetti", modulesPresetTitle: "Configurazioni Comuni", @@ -2967,15 +3251,15 @@ module.exports = { workflowsSet: "Applica workflow", workflow_default: "Predefinito", workflow_jobs: "Lavoro", - workflow_ruling: "Governo", - workflow_politics: "Politica", workflow_social: "Sociale", workflow_business: "Affari", workflow_night: "Notte", + workflow_kids: "Bambini", + workflow_activists: "Attivismo", workflow_mobile: "Mobile", statsCarbonFootprintTitle: "Impronta di carbonio", statsCarbonTombstone: "Impronta del tombstoning", - uploadMedia: "Carica media (max: 50MB)", + uploadMedia: "Carica media (max: 75MB)", courtsRespondentInvalid: "Deve essere un ID SSB valido (@...ed25519)", feedDetailTitle: "Feed", @@ -2984,7 +3268,7 @@ module.exports = { noComments: "Nessun commento ancora", mapsLabel: "Mappe", mapTitle: "Mappe", - mapDescription: "Esplora e gestisci mappe offline nella tua rete.", + mapDescription: "Gestisci e condividi mappe offline nella tua rete.", mapMineSectionTitle: "Le Tue Mappe", mapCreateSectionTitle: "Crea Mappa", mapUpdateSectionTitle: "Aggiorna Mappa", @@ -3024,9 +3308,22 @@ module.exports = { markerImageLabel: "Immagine del Marcatore", mapAddMarkerTitle: "Aggiungi Marcatore", mapAddMarkerButton: "Aggiungi Marcatore", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Rimuovi segnaposto", mapApplyZoom: "Applica Zoom", - mapSearchPlaceholder: "Cerca descrizione, tag, autore...", + mapPlaceSearchPlaceholder: "Cerca un luogo o un paese...", + mapNoPlaces: "Nessun luogo trovato.", + mapClusterTitle: "marcatori qui, clicca per ingrandire", + mapDeleteMarker: "Elimina marcatore", + mapZoomIn: "Ingrandisci", + mapZoomOut: "Riduci", + mapFitMarkers: "Adatta a tutti i marcatori", + mapPanLabel: "Sposta la mappa", + mapCenterHere: "Centra qui", + mapNear: "vicino a", + mapPickedSpot: "Punto scelto", + mapPickHint: "Clicca sulla mappa per scegliere un punto.", + mapClearPick: "Pulisci", + mapSearchPlaceholder: "Cerca in Mappe...", mapSearchButton: "Cerca", mapUpdatedAt: "Aggiornato", mapNoMatch: "Nessuna mappa corrisponde alla tua ricerca.", @@ -3037,6 +3334,465 @@ module.exports = { typeMapMarker: "MARCATORE MAPPA", modulesMapLabel: "Mappe", modulesMapDescription: "Modulo per gestire e condividere mappe offline.", + emergenciesTitle: "Emergenze", + modulesEmergenciesLabel: "Emergenze", + modulesEmergenciesDescription: "Modulo per lanciare, confermare e seguire emergenze nella rete.", + emergenciesDescription: "Modulo per lanciare, confermare e seguire emergenze nella rete.", + emergencyCreate: "Crea emergenza", + emergencyUpdate: "Aggiorna", + emergencyDelete: "Elimina", + emergencyTitleLabel: "Titolo", + emergencyTitlePlaceholder: "Cosa sta succedendo?", + emergencyTextLabel: "Dettagli", + emergencyTextPlaceholder: "Dove, da quando, cosa fare...", + emergencyCategoryLabel: "Categoria", + emergencyCategoryWeather: "METEO", + emergencyCategoryInfrastructure: "INFRASTRUTTURE", + emergencyCategoryHealth: "SALUTE", + emergencyCategorySecurity: "SICUREZZA", + emergencyCategoryLost: "OGGETTI SMARRITI", + emergencyCategoryNeighborhood: "VICINATO", + emergencyExpiresLabel: "Scade tra", + emergencyExpires1d: "1 giorno", + emergencyExpires3d: "3 giorni", + emergencyExpires7d: "1 settimana", + emergencyExpires30d: "1 mese", + emergencyExpiresNone: "Indefinito", + emergencyExpiresKeep: "Mantieni la data attuale", + emergencyTagsLabel: "Tag", + emergencyTagsPlaceholder: "Inserisci tag separati da virgole", + emergencyFilterAll: "TUTTE", + emergencyFilterMine: "MIE", + emergencyFilterRecent: "RECENTI", + emergencySeverityUnverified: "NON VERIFICATA", + emergencySeverityLow: "BASSA", + emergencySeverityMedium: "MEDIA", + emergencySeverityHigh: "ALTA", + emergencyStatusActive: "ATTIVA", + emergencyStatusResolved: "RISOLTA", + emergencyStatusExpired: "SCADUTA", + emergencyConfirmations: "Conferme", + emergencyConfirmButton: "Conferma", + emergencyConfirmed: "Hai confermato questa emergenza", + emergencyResolveButton: "Risolvi", + emergencyReopenButton: "Riapri", + emergencyGenerateReport: "Genera rapporto", + emergencyUpdateEdited: "Modificato", + emergencyLastUpdate: "ULTIMO AGGIORNAMENTO", + emergencyUpdatesTitle: "Ultimi aggiornamenti:", + emergencyNoUpdates: "Ancora nessun aggiornamento.", + emergencyAddUpdate: "Pubblica aggiornamento", + emergencyUpdatePlaceholder: "Cosa è cambiato?", + emergencyNoItems: "Nessuna emergenza trovata.", + emergencyExpiresAt: "Scade", + emergencyBannerLabel: "Emergenza attiva", + emergencyCreateSectionTitle: "Crea emergenza", + emergencyUpdateSectionTitle: "Aggiorna emergenza", + emergencySearchPlaceholder: "Cerca nelle emergenze...", + typeEmergency: "EMERGENZE", + typeEmergencyConfirm: "CONFERMA EMERGENZA", + typeEmergencyUpdate: "AGGIORNAMENTO EMERGENZA", + statsEmergency: "Emergenze", + statsEmergencyConfirm: "Conferme di emergenze", + statsEmergencyUpdate: "Aggiornamenti di emergenze", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Un’emergenza che segui ha un aggiornamento.", + emergencyBotResolvedTitle: "Un’emergenza che segui è stata risolta.", + dataKindEmergencies: "EMERGENZE", + mailingTitle: "Mailing list", + modulesMailingLabel: "Mailing list", + modulesMailingDescription: "Scopri e gestisci mailing list nella tua rete.", + modulesPhoneLabel: "Telefono", + modulesPhoneDescription: "Modulo per chiamare altri abitanti con videochiamate cifrate end-to-end.", + mailingDescription: "Scopri e gestisci mailing list nella tua rete.", + mailingCreate: "Crea mailing list", + mailingUpdate: "Aggiorna", + mailingArchive: "Archivia", + mailingActivate: "Attiva", + mailingCreateSectionTitle: "Crea mailing list", + mailingUpdateSectionTitle: "Aggiorna mailing list", + mailingTitleLabel: "Titolo", + mailingTitlePlaceholder: "Nome della mailing list", + mailingDescriptionLabel: "Descrizione", + mailingDescriptionPlaceholder: "Di cosa tratta questa lista?", + mailingTypeLabel: "Tipo di mailing list", + mailingTypeOpen: "APERTA", + mailingTypeClosed: "CHIUSA", + mailingStatusLabel: "Stato", + mailingStatusActive: "ATTIVA", + mailingStatusArchived: "ARCHIVIATA", + mailingMembersLabel: "Invita abitanti", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Iscritti", + mailingTagsLabel: "Tag", + mailingTagsPlaceholder: "Inserisci tag separati da virgole", + mailingFilterAll: "TUTTE", + mailingFilterMine: "MIE", + mailingFilterSubscribed: "ISCRITTE", + mailingFilterRecent: "RECENTI", + mailingHistoryDate: "DATA", + mailingHistoryThreads: "DISCUSSIONI", + mailingHistoryInhabitants: "ABITANTI", + mailingHistorySearchPlaceholder: "Cerca nella cronologia...", + mailingNoMessages: "Ancora nessun messaggio.", + mailingNoItems: "Nessuna mailing list trovata.", + mailingSearchPlaceholder: "Cerca nelle mailing list...", + mailingWriteButton: "Scrivi messaggio", + mailingWriteTitle: "Scrivi alla lista", + mailingReplyTitle: "Rispondi alla lista", + mailingSend: "Invia", + mailingTextPlaceholder: "Il tuo messaggio raggiunge tutti i partecipanti della lista.", + mailingParticipants: "Partecipanti", + inboxFilterMailing: "Mailing list", + mailingReplyPlaceholder: "Rispondi alla lista...", + mailingReplyToList: "Rispondi alla lista", + typeMailingList: "MAILING LIST", + statsMailingList: "Mailing list", + mailingListLabel: "MAILING LIST", + logisticsTitle: "Logistica", + modulesLogisticsLabel: "Logistica", + modulesLogisticsDescription: "Scopri e gestisci piani logistici nella tua rete.", + logisticsDescription: "Scopri e gestisci piani logistici nella tua rete.", + logisticsCreate: "Crea rotta", + logisticsUpdate: "Aggiorna", + logisticsClose: "Chiudi", + logisticsReopen: "Riapri", + logisticsCreateSectionTitle: "Crea rotta", + logisticsUpdateSectionTitle: "Aggiorna rotta", + logisticsTitleLabel: "Titolo", + logisticsTitlePlaceholder: "Viaggio al mercato, furgone disponibile...", + logisticsKindLabel: "Tipo di logistica", + logisticsKindTrip: "VIAGGIO", + logisticsKindShipment: "SPEDIZIONE", + logisticsKindTrips: "VIAGGI", + logisticsKindShipments: "SPEDIZIONI", + logisticsModeLabel: "Modalità", + logisticsModeOffer: "OFFRO", + logisticsModeRequest: "CERCO", + logisticsModeOffers: "OFFERTE", + logisticsModeRequests: "RICHIESTE", + logisticsDescriptionLabel: "Descrizione", + logisticsDescriptionPlaceholder: "Punto d’incontro, cosa trasporti, condizioni...", + logisticsOriginLabel: "Origine", + logisticsOriginPlaceholder: "Da dove parte", + logisticsDestinationLabel: "Destinazione", + logisticsDestinationPlaceholder: "Dove arriva", + logisticsDateLabel: "Data e ora", + logisticsRecurrenceLabel: "Ricorrenza", + logisticsFrequencyLabel: "Frequenza", + logisticsRecurrenceNone: "UNICA", + logisticsRecurrenceDaily: "GIORNALIERA", + logisticsRecurrenceWeekly: "SETTIMANALE", + logisticsRecurrenceMonthly: "MENSILE", + logisticsSeatsLabel: "Posti", + logisticsSizeLabel: "Dimensioni", + logisticsSizePlaceholder: "es. 2m x 1m", + logisticsWeightLabel: "Peso", + logisticsWeightPlaceholder: "es. 40kg", + logisticsPriceFree: "GRATIS", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TEMPO", + logisticsPriceLabel: "Prezzo", + logisticsHours: "h", + logisticsOrderRefLabel: "Link all’ordine", + logisticsOrderRefPlaceholder: "/shops/... o /market/... dell’ordine da consegnare", + logisticsOrderLink: "Ordine collegato", + logisticsTagsLabel: "Tag", + logisticsTagsPlaceholder: "Inserisci tag separati da virgole", + logisticsFilterAll: "TUTTE", + logisticsFilterMine: "MIE", + logisticsFilterBooked: "PRENOTATE", + logisticsFilterRecent: "RECENTI", + logisticsFilterHistory: "STORICO", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "APERTA", + logisticsStatusUpcoming: "PROSSIME", + logisticsStatusPast: "PASSATE", + logisticsStatusClosed: "CHIUSE", + logisticsZoneLabel: "Zona", + logisticsSearchPlaceholder: "Cerca nella logistica...", + logisticsNoItems: "Nessuna rotta trovata.", + logisticsBookingPending: "IN ATTESA", + logisticsBookingConfirmed: "CONFERMATA", + logisticsBookingRejected: "RIFIUTATA", + logisticsBookingCancelled: "ANNULLATA", + logisticsBookingDelivered: "CONSEGNATA", + logisticsConfirm: "Conferma", + logisticsReject: "Rifiuta", + logisticsDeliver: "Consegna", + logisticsReceiptLabel: "Ricevuta", + logisticsReceiptPlaceholder: "Nota di consegna (facoltativa)", + logisticsCancelBooking: "Annulla prenotazione", + logisticsBookTitle: "Prenota questa rotta", + logisticsNotesLabel: "Note", + logisticsNotesPlaceholder: "Ciò che il responsabile deve sapere", + logisticsBook: "Prenota", + logisticsBookingsTitle: "Prenotazioni", + logisticsMyBookingTitle: "La tua prenotazione", + logisticsRatingsTitle: "Valutazioni", + logisticsNoRatings: "Ancora nessuna valutazione.", + logisticsRatingsWhenClosed: "Le valutazioni si aprono alla chiusura della rotta.", + logisticsScoreLabel: "Punteggio", + logisticsRatingPlaceholder: "Com’è andata?", + logisticsRate: "Valuta", + typeLogisticsRoute: "ROTTA", + typeLogisticsRating: "VALUTAZIONE ROTTA", + statsLogisticsRoute: "Rotte", + statsLogisticsRating: "Valutazioni delle rotte", + logisticsRouteLabel: "ROTTA", + podcastsTitle: "Podcast", + modulesPodcastsLabel: "Podcast", + modulesPodcastsDescription: "Modulo per pubblicare canali podcast.", + podcastsDescription: "Modulo per pubblicare canali podcast.", + podcastCreate: "Crea podcast", + podcastUpdate: "Aggiorna", + podcastDelete: "Elimina", + podcastCreateSectionTitle: "Crea podcast", + podcastUpdateSectionTitle: "Aggiorna podcast", + podcastTitleLabel: "Titolo", + podcastTitlePlaceholder: "Nome del canale", + podcastDescriptionLabel: "Descrizione", + podcastDescriptionPlaceholder: "Di cosa parla questo podcast?", + podcastCategoryLabel: "Categoria", + podcastCategoryNews: "NOTIZIE", + podcastCategoryMusic: "MUSICA", + podcastCategoryTalk: "CONVERSAZIONE", + podcastCategoryEducation: "EDUCAZIONE", + podcastCategoryTech: "TECNOLOGIA", + podcastCategoryCulture: "CULTURA", + podcastCategoryCommunity: "COMUNITÀ", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "ALTRO", + podcastTagsLabel: "Tag", + podcastCoverLabel: "Copertina (immagine o video)", + podcastEpisodeAudioLabel: "File audio o video (max: 75MB)", + podcastTagsPlaceholder: "Inserisci tag separati da virgole", + podcastFilterAll: "TUTTI", + podcastFilterMine: "MIEI", + podcastFilterRecent: "RECENTI", + podcastFilterTop: "TOP", + podcastFilterViewers: "ASCOLTATORI", + podcastSearchPlaceholder: "Cerca nei podcast...", + podcastNoItems: "Nessun podcast trovato.", + podcastEpisodesLabel: "Episodi", + podcastPlaysLabel: "Visualizzazioni", + podcastLastEpisode: "Ultimo episodio", + podcastEpisodesTitle: "Episodi", + podcastNoEpisodes: "Ancora nessun episodio.", + podcastEpisodeAdd: "Aggiungi episodio", + podcastEpisodeCreateTitle: "Pubblica episodio", + podcastEpisodeUpdateTitle: "Aggiorna episodio", + podcastEpisodeTitleLabel: "Titolo dell’episodio", + podcastEpisodeTitlePlaceholder: "Titolo dell’episodio", + podcastEpisodeDescriptionLabel: "Note", + podcastEpisodeDescriptionPlaceholder: "Cosa succede in questo episodio, link, crediti...", + podcastEpisodePublish: "Pubblica", + podcastPlayed: "Ascoltato", + podcastMarkPlayed: "Segna come ascoltato", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISODIO", + typePodcastOpinion: "OPINIONE SULL’EPISODIO", + typePodcastPlay: "ASCOLTO", + statsPodcast: "Podcast", + statsPodcastEpisode: "Episodi", + statsPodcastPlay: "Ascolti", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Un blog che segui ha una nuova voce.", + podcastBotEpisodeTitle: "Un podcast che segui ha un nuovo episodio.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISODIO", + campaignsTitle: "Campagne", + modulesCampaignsLabel: "Campagne", + modulesCampaignsDescription: "Scopri e gestisci campagne nella tua rete.", + campaignsDescription: "Scopri e gestisci campagne nella tua rete.", + campaignCreate: "Crea campagna", + campaignUpdate: "Aggiorna", + campaignDelete: "Elimina", + campaignClose: "Chiudi", + campaignReopen: "Riapri", + campaignCreateSectionTitle: "Crea campagna", + campaignUpdateSectionTitle: "Aggiorna campagna", + campaignTitleLabel: "Titolo", + campaignTitlePlaceholder: "Cosa vuoi ottenere?", + campaignTextLabel: "Testo", + campaignTextPlaceholder: "Spiega la causa, chi decide e cosa chiedi...", + campaignCategoryLabel: "Categoria", + campaignCategoryEnvironment: "AMBIENTE", + campaignCategoryRights: "DIRITTI", + campaignCategoryHealth: "SALUTE", + campaignCategoryEducation: "EDUCAZIONE", + campaignCategoryInfrastructure: "INFRASTRUTTURE", + campaignCategoryCulture: "CULTURA", + campaignCategoryEconomy: "ECONOMIA", + campaignCategoryOther: "ALTRO", + campaignGoalLabel: "Obiettivo di firme", + campaignDeadlineLabel: "Scadenza", + campaignTagsLabel: "Tag", + campaignTagsPlaceholder: "Inserisci tag separati da virgole", + campaignFilterAll: "TUTTE", + campaignFilterMine: "MIE", + campaignFilterSigned: "FIRMATE", + campaignFilterRecent: "RECENTI", + campaignFilterTop: "TOP", + campaignStatusOpen: "APERTA", + campaignStatusAchieved: "RAGGIUNTA", + campaignStatusUnachieved: "NON RAGGIUNTA", + campaignStatusClosed: "CHIUSA", + campaignSearchPlaceholder: "Cerca nelle campagne...", + campaignNoItems: "Nessuna campagna trovata.", + campaignSignaturesLabel: "Firme", + campaignSignaturesTitle: "Sostenitori", + campaignNoSignatures: "Ancora nessuna firma.", + campaignSignTitle: "Firma questa campagna", + campaignSignPlaceholder: "Perché la sostieni? (facoltativo)", + campaignSign: "Firma", + campaignSigned: "Hai già firmato!", + campaignNotSigned: "Manca ancora la tua firma", + campaignElevate: "Porta in Parlamento", + campaignElevated: "Portata in Parlamento", + campaignUpdatesTitle: "Aggiornamenti", + campaignNoUpdates: "Ancora nessun aggiornamento.", + campaignUpdatePlaceholder: "Cosa è successo?", + campaignAddUpdate: "Pubblica aggiornamento", + campaignQrAlt: "QR per firmare questa campagna", + campaignQrCaption: "Scansiona per firmare", + typeCampaign: "CAMPAGNA", + typeCampaignSignature: "FIRMA", + typeCampaignUpdate: "AGGIORNAMENTO CAMPAGNA", + statsCampaign: "Campagne", + statsCampaignSignature: "Firme delle campagne", + statsCampaignUpdate: "Aggiornamenti delle campagne", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Una campagna che segui ha un aggiornamento.", + campaignBotAchievedTitle: "Una campagna che segui ha raggiunto l’obiettivo.", + campaignBotRaisedTitle: "Una campagna che hai firmato è stata portata in Parlamento.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Un percorso che hai prenotato è cambiato.", + logisticsBotClosedTitle: "Un percorso che hai prenotato è stato chiuso.", + logisticsBotBookedTitle: "Qualcuno ha prenotato il tuo percorso.", + logisticsBotCancelledTitle: "Una prenotazione sul tuo percorso è stata annullata.", + logisticsBotConfirmedTitle: "La tua prenotazione è confermata: il pacco è in viaggio.", + logisticsBotRejectedTitle: "La tua prenotazione è stata rifiutata.", + logisticsBotDeliveredTitle: "Il tuo pacco è arrivato.", + campaignLabel: "CAMPAGNA", + campaignGenerateReport: "Genera rapporto", + logisticsGenerateReport: "Genera rapporto", + mailingGenerateReport: "Genera rapporto", + campaignUpdateEdited: "Modificato", + mailingMessagesLabel: "Messaggi", + logisticsBookingsLabel: "Prenotazioni", + backupTitle: "Backup", + modulesBackupLabel: "Backup", + modulesBackupDescription: "Modulo per esportare e importare le tue chiavi e fare backup cifrati.", + backupDescription: "Modulo per esportare e importare le tue chiavi e fare backup cifrati.", + backupTypeRecovery: "RECUPERO", + backupTypeKeys: "ESPORTA CHIAVI", + backupTypeFull: "BACKUP COMPLETO", + backupTypeRestore: "RIPRISTINA BACKUP", + backupTypeInstant: "ISTANTANEA", + backupInstantDescription: "Uno .zip semplice di tutto il tuo registro e dei tuoi file, senza la chiave segreta e senza password. Rapido, ma ", + backupInstantRisk: "chi ha il file legge tutto.", + backupKitPageDescription: "Il kit di recupero può essere aperto solo da questo dispositivo.", + backupKitId: "ID Oasis", + backupKitDate: "Generato", + backupKitQrAlt: "Codice QR della tua chiave segreta", + backupKitStep1: "Stampalo o ricopialo a mano e conservalo in un luogo sicuro e offline.", + backupKitStep2: "Per recuperare, installa Oasis su un nuovo dispositivo e importa questo segreto da Backup › Chiavi.", + backupKitStep3: "Distruggi questa pagina se dovesse mai essere esposta.", + backupKitPdfButton: "Genera PDF", + backupScopeLabel: "Ambito", + backupScopeAll: "TUTTO", + backupScopeMine: "SOLO I MIEI CONTENUTI", + backupSinceLabel: "Da", + backupEstimateTitle: "Stima", + backupFileInfo: "La tua copia cifrata verrà scaricata sul tuo dispositivo (nome: oasis-backup-DATA.oasisbk)", + backupExportButton: "Scarica backup", + backupRestorePassword: "Scrivi la password usata quando è stato fatto il backup", + backupRestoreButton: "Ripristina backup", + backupRestoredLine: "Backup ripristinato.", + backupRestoredMessages: "Messaggi aggiunti", + backupRestoredSkipped: "Già presenti", + backupRestoredBlobs: "File aggiunti", + backupRestoredState: "Impostazioni ripristinate", + backupRestoredFailed: "Falliti", + backupRestoredForked: "Divergenti", + backupRestoreRunning: "Ripristino della copia in corso. Questa pagina si aggiorna da sola; nel frattempo puoi continuare a usare Oasis.", + backupRestoreElapsed: "Trascorso", + backupRestoreFailedLine: "Non è stato possibile ripristinare la copia.", + backupRestoreForkMine: "Questo dispositivo ha già pubblicato messaggi con la tua identità che non sono nella copia, quindi non è stato possibile applicarla sopra di essi. Ripristina su un’installazione pulita subito dopo aver importato le chiavi, prima di fare qualsiasi altra cosa.", + verificationTitle: "Verifica", + verificationDescription: "Controlla la catena del tuo feed, rileva biforcazioni e rivedi i file orfani o mancanti.", + verificationRun: "Esegui verifica", + verificationTotalMessages: "messaggi nel log", + verificationFeed: "Il tuo feed", + verificationMessages: "messaggi", + verificationLastSequence: "ultima sequenza", + verificationGaps: "Buchi", + verificationBrokenLinks: "Collegamenti rotti", + verificationBadHashes: "Hash errati", + verificationBadSignatures: "Firme errate", + verificationDuplicates: "Sequenze duplicate", + verificationForks: "Biforcazioni", + verificationBlobs: "File", + verificationBlobsPresent: "presenti", + verificationBlobsReferenced: "referenziati", + verificationBlobsOrphan: "orfani", + verificationBlobsMissing: "mancanti", + verificationBlobsMissingOwn: "File tuoi mancanti su questo dispositivo", + verificationBlobsMissingOthers: "File di altri abitanti non ancora scaricati", + verificationBlobsUnavailable: "Impossibile elencare i file su questo dispositivo.", + wikiTitle: "Wiki", + modulesWikiLabel: "Wiki", + modulesWikiDescription: "Modulo per scoprire e gestire wiki.", + wikiDescription: "Modulo per scoprire e gestire wiki.", + wikiCreate: "Crea wiki", + wikiUpdate: "Aggiorna", + wikiDelete: "Elimina", + wikiEdit: "Modifica", + wikiTitleLabel: "Titolo", + wikiTitlePlaceholder: "Titolo della wiki...", + wikiBodyLabel: "Contenuto", + wikiBodyPlaceholder: "Collega altre wiki con [[Titolo della wiki]] o [[Titolo della wiki|un’etichetta personalizzata]].\n\nMenziona un abitante con @id.\n\nTagga con #oasis.\n\nIncolla link come https://solarnethub.com\n\nUna riga vuota apre un nuovo paragrafo; le interruzioni di riga singole si conservano.\n\nAllega qualsiasi media supportato: verrà incorporato qui.", + formatBarLabel: "Formato", + wikiStatusLabel: "Tipo di wiki", + wikiStatusOpen: "APERTA", + wikiStatusClosed: "CHIUSA", + wikiTagsLabel: "Tag", + wikiTagsPlaceholder: "Inserisci tag separati da virgole", + wikiSummaryLabel: "Riepilogo della modifica", + wikiSummaryPlaceholder: "Cosa è cambiato?", + wikiFilterAll: "TUTTE", + wikiFilterMine: "MIE", + wikiFilterRecent: "RECENTI", + wikiFilterLinked: "COLLEGATE", + wikiChanges: "Modifiche recenti", + wikiNoPages: "Nessuna wiki trovata.", + wikiNoChanges: "Ancora nessuna modifica.", + wikiHistory: "Cronologia", + wikiVersion: "Versione", + wikiVersions: "versioni", + wikiRestore: "Ripristina", + wikiDiff: "Modifiche", + wikiViewVersion: "Vedi", + wikiLinkedPages: "Pagine collegate", + wikiNoLinkedPages: "Questa pagina non è ancora collegata a nessun altra.", + wikiMissingLink: "La pagina non esiste ancora", + wikiDuplicateTitle: "Esiste già una pagina con questo titolo. Cambia il titolo per creare una pagina diversa.", + wikiCreateSectionTitle: "Crea wiki", + wikiUpdateSectionTitle: "Aggiorna wiki", + wikiSearchPlaceholder: "Cerca nella Wiki...", + wikiCurrentVersion: "Versione attuale", + wikiViewingVersion: "Stai vedendo una versione precedente", + wikiBackToPage: "Torna alla pagina", + wikiLinkedChip: "Collegata", + wikiRestoredSummary: "Versione ripristinata", + wikiExportPdf: "Genera pagina", + wikiPreview: "Anteprima", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Crea wiki", + tribeWikiEmpty: "Ancora nessuna wiki.", + typeWiki: "WIKI", padsTitle: "Pad", padTitle: "Pad", modulesPadsLabel: "Pad", @@ -3047,21 +3803,133 @@ module.exports = { padTitleLabel: "Titolo", padTitlePlaceholder: "Inserisci il titolo del pad...", padStatusLabel: "Stato", + padTypeLabel: "Tipo di pad", padStatusOpen: "APERTO", padStatusInviteOnly: "SOLO SU INVITO", padStatusClosed: "CHIUSO", padDeadlineLabel: "Scadenza", padTagsLabel: "Tag", - padTagsPlaceholder: "tag1, tag2, ...", + padTagsPlaceholder: "Inserisci tag separati da virgole", padMembersLabel: "Membri", padCreated: "Creato", - padStartEditing: "INIZIA A MODIFICARE!", + padStartEditing: "Modifica", padEditorPlaceholder: "Inizia a scrivere...", padSubmitEntry: "Invia", padNoEntries: "Nessuna voce ancora.", padAllSectionTitle: "Tutti i Pad", padMineSectionTitle: "I Miei Pad", padsDescription: "Gestisci editor di testo collaborativi cifrati nella tua rete.", + roomsTitle: "Stanze", + roomsDescription: "Scopri ed entra nelle stanze di videoconferenza cifrate della tua rete.", + modulesRoomsLabel: "Stanze", + modulesRoomsDescription: "Modulo per scoprire ed entrare in stanze di videoconferenza cifrate.", + roomFilterRecent: "RECENTI", + roomFilterMine: "MIE", + roomFilterAll: "TUTTE", + roomFilterLive: "IN DIRETTA", + roomFilterOpen: "APERTE", + roomFilterInvite: "SOLO SU INVITO", + roomFilterClosed: "CHIUSE", + roomCreate: "Crea stanza", + roomUpdate: "Aggiorna", + roomDelete: "Elimina", + roomClose: "Chiudi", + roomCreateSectionTitle: "Crea stanza", + roomUpdateSectionTitle: "Aggiorna stanza", + roomTitlePlaceholder: "Titolo della stanza", + roomDescriptionLabel: "Descrizione", + roomDescriptionPlaceholder: "Di cosa parlerete?", + roomTypeLabel: "Tipo di stanza", + roomTagsLabel: "Tag", + roomStatusOpen: "APERTA", + roomStatusInviteOnly: "SOLO SU INVITO", + roomStatusClosed: "CHIUSA", + roomParticipants: "Partecipanti", + roomJoin: "Entra", + roomLeave: "Esci", + roomFull: "Stanza piena", + roomLiveChip: "IN DIRETTA", + roomMuted: "microfono disattivato", + roomRec: "Registra audio", + roomRecStop: "Ferma la registrazione", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} è entrato nella stanza", + roomEventLeft: "{name} ha lasciato la stanza", + roomEventRecStart: "{name} ha iniziato a registrare", + roomEventRecStop: "{name} ha smesso di registrare", + roomEventMuted: "{name} si è silenziato", + roomEventUnmuted: "{name} ha tolto il silenzio", + roomEventHand: "{name} ha alzato la mano", + roomRaiseHand: "Alza la mano", + roomLowerHand: "Abbassa la mano", + confirmActionTitle: "Conferma questa azione", + confirmActionButton: "Conferma", + confirmJoinText: "Stai per unirti con questo invito.", + confirmFollowText: "Stai per sostenere questo abitante.", + confirmSignText: "Stai per firmare questa campagna.", + confirmDownloadText: "Stai per scaricare questo file sul tuo dispositivo.", + roomRecordings: "Registrazioni", + roomClearNotices: "Pulisci", + roomLineSwitchboard: "Centralino", + roomLineDndError: "Il numero di questa stanza non accetta chiamate.", + roomNumberHint: "Componi questo numero da Telefono per entrare nella stanza.", + liveRefresh: "Aggiorna", + roomNobody: "Al momento non c'è nessuno nella stanza.", + roomSomeone: "{n} nella stanza in questo momento.", + roomYou: "tu", + roomYouAreIn: "Ci sei dentro", + roomNobodyInside: "Al momento non c'è nessuno in questa stanza.", + roomErrorFull: "La stanza è piena. Potrai entrare quando qualcuno esce.", + roomErrorUnreachable: "Al momento la stanza non è raggiungibile. Riprova più tardi.", + roomErrorBusy: "Sei già in una chiamata o in un'altra stanza.", + roomErrorClosed: "Questa stanza è chiusa.", + roomAccessDenied: "Questa stanza è solo su invito. Ti serve un invito per entrare.", + roomsNoItems: "Nessuna stanza trovata.", + roomSearchPlaceholder: "Cerca stanze...", + roomInviteMode: "Invito", + tribeSectionRooms: "STANZE", + tribeRoomCreate: "Crea stanza", + tribeRoomsEmpty: "Ancora nessuna stanza.", + typeRoom: "STANZE", + typeRoomMember: "PARTECIPANTE DI STANZA", + statsRoom: "Stanze", + statsRoomMember: "Partecipanti alle stanze", + favoritesFilterRooms: "STANZE", + invitesRoomsTitle: "Stanze", + inviteCodeInvalid: "Questo codice d'invito non è valido o è già stato usato.", + inviteAlreadyMember: "Ne fai già parte. Puoi aprirlo dal suo modulo.", + actionFailed: "Non è stato possibile farlo. Riprova.", + publishTooShort: "Il tuo post è troppo corto.", + walletSendFailed: "Non è stato possibile inviare il pagamento: {reason}", + pixeliaCoordsError: "Quelle coordinate sono fuori dalla griglia.", + courtsErrTitle: "Serve un titolo.", + courtsErrRespondent: "Indica contro chi è il caso.", + courtsErrRespondentId: "Questo non è un Oasis ID valido.", + courtsErrMethod: "Scegli come verrà risolto il caso.", + courtsErrCaseNotFound: "Quel caso non è stato trovato.", + courtsErrBrief: "Scrivi la tua risposta.", + courtsErrStance: "Scegli la tua posizione.", + courtsErrResult: "Scrivi il risultato.", + courtsErrTerms: "Scrivi i termini dell'accordo.", + courtsErrMediator: "Aggiungi almeno un mediatore.", + courtsErrSelfMediator: "Non puoi essere mediatore nel tuo stesso caso.", + courtsErrJudge: "Scegli un giudice.", + courtsErrSelfJudge: "Non puoi essere giudice del tuo stesso caso.", + courtsErrVisibility: "Scegli chi può vedere il caso.", + courtsErrNomination: "Quella candidatura non è stata trovata.", + opinionAlreadyGiven: "Hai già dato la tua opinione.", + cipherPasswordShort: "La password deve avere almeno 32 caratteri.", + errorForbidden: "Non hai accesso a questo.", + errorNotFound: "Non è stato trovato.", + errorClosed: "È chiuso.", + errorInviteOnly: "È solo su invito. Ti serve un invito.", + errorTooLong: "Quel testo è troppo lungo.", + errorRequired: "Mancano informazioni obbligatorie.", + errorInvalid: "Alcune informazioni non sono valide.", + errorNotAuthor: "Solo l'autore può farlo.", + panicRemoved: "La tua blockchain è stata eliminata.", padRecentSectionTitle: "Pad Recenti", padOpenSectionTitle: "Pad Aperti", padClosedSectionTitle: "Pad Chiusi", @@ -3073,18 +3941,18 @@ module.exports = { padRemoveFavorite: "Rimuovi dai Preferiti", padClose: "Chiudi", padBackToEditor: "Torna all'editor", - padSearchPlaceholder: "Cerca pad...", + padSearchPlaceholder: "Cerca in Pad...", modulesChatsLabel: "Chat", - modulesChatsDescription: "Modulo per scoprire e gestire chat cifrate.", + modulesChatsDescription: "Modulo per scoprire e gestire chat.", typeChat: "CHATS", typeChatMessage: "MESSAGGIO CHAT", chatsTitle: "Chat", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "Le tue chat", + chatRecentTitle: "Chat recenti", chatFavoritesTitle: "Preferiti", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "Chat aperte", + chatClosedTitle: "Chat chiuse", chatDescription: "Descrizione", chatCategory: "Categoria", chatStatus: "STATO", @@ -3099,34 +3967,172 @@ module.exports = { chatMessagesLabel: "Messaggi", chatThreadsLabel: "Discussioni", chatCreatedAt: "CREATO", - chatSearchPlaceholder: "Cerca chat...", + chatSearchPlaceholder: "Cerca in Chat...", chatStatusOpen: "APERTO", chatStatusInviteOnly: "SOLO SU INVITO", chatStatusClosed: "CHIUSO", chatSendMessage: "Invia", chatJumpLatest: "Ultimo Messaggio", + chatReply: "Rispondi", + chatReplyCancel: "Annulla risposta", + chatPin: "Fissa", + chatUnpin: "Sblocca", + chatPinned: "Fissati", chatPickChat: "Scegli una chat per aprirla", chatNoneYet: "Nessuna chat disponibile, per ora.", - activitySearchPlaceholder: "Cerca nell'attività...", - trendingSearchPlaceholder: "Cerca nelle tendenze...", - opinionsSearchPlaceholder: "Cerca nelle opinioni...", - votesSearchPlaceholder: "Cerca nelle votazioni...", - welcomeStepUxTitle: "Scegli la tua interfaccia", - welcomeStepUxText: "Decidi l'aspetto di Oasis. Potrai cambiarlo nelle impostazioni.", - welcomeStepUxAction: "Usa questa vista", + activitySearchPlaceholder: "Cerca in Attività...'attività...", + trendingSearchPlaceholder: "Cerca in Tendenze...", + opinionsSearchPlaceholder: "Cerca in Opinioni...", + votesSearchPlaceholder: "Cerca in Votazioni...", + gamesSearchPlaceholder: "Cerca in Giochi...", + schoolTitle: "Scuola", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Una pagina wiki che segui è stata modificata.", + wikiBotRestoredTitle: "È stata ripristinata una versione precedente di una pagina wiki che segui.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Un nuovo studente si è iscritto al tuo corso.", + schoolBotInvitedTitle: "Sei stato invitato a un corso.", + schoolBotAdmittedTitle: "Sei stato ammesso a un corso.", + schoolBotCertificateTitle: "Hai ricevuto un certificato.", + schoolBotPassedTitle: "Uno studente ha superato il tuo corso.", + schoolBotLessonTitle: "È stata pubblicata una nuova lezione.", + invitesSchoolTitle: "Scuola", + schoolEnrollLabel: "Unisciti al corso", + schoolExamRetryLater: "Potrai riprovare dopo 24 ore.", + schoolApproved: "SUPERATO", + schoolExamPassMark: "Soglia di superamento (0-10)", + schoolExamPassed: "SUPERATO", + schoolExamPending: "In attesa", + schoolExamFailed: "NON SUPERATO", + schoolExamAddQuestion: "Aggiungi domanda", + schoolExamCreateQuestion: "Crea domanda", + schoolExamOption: "Opzione", + schoolExamCorrect: "Opzione corretta", + schoolExamPoints: "Punti", + schoolMaterialTextPlaceholder: "Note per questa lezione...", + schoolMaterials: "Materiali", + schoolAddMaterial: "Aggiungi materiale", + schoolNoMaterials: "Ancora nessun materiale.", + bankYourSchoolBalance: "I tuoi guadagni della scuola", + schoolProgress: "Progresso", + schoolProgressMark: "Segna come completata", + schoolProgressDone: "Completata", + schoolExams: "Esami", + schoolExamCreate: "Crea esame", + schoolExamTitlePlaceholder: "Titolo dell'esame", + schoolExamHelp: "Una domanda per riga: Domanda? ; opzione ; opzione corretta* ; opzione", + schoolExamQuestionsPlaceholder: "Cos'è SSB? ; Un database ; Un protocollo P2P* ; Un browser", + schoolExamSubmit: "Invia risposte", + schoolExamScore: "Il tuo punteggio", + schoolNoScoresYet: "Ancora nessun risultato.", + schoolLessonUnit: "Unità / fase", + schoolLessonUnitPlaceholder: "es. Fase 1", + schoolLessonOrder: "Ordine", + profileClearnetSchoolLabel: "Scuola", + schoolStartDate: "Data di inizio", + schoolSessionDate: "Data della sessione", + schoolCourseChat: "Chat del corso", + agendaFilterSchool: "SCUOLA", + agendaFilterCampaigns: "CAMPAGNE", + agendaFilterLogistics: "LOGISTICA", + schoolCourseType: "Tipo di corso", + schoolTypeOpen: "APERTO", + schoolTypePaid: "A PAGAMENTO", + schoolTypeInvite: "SOLO SU INVITO", + schoolLessonLocked: "Lezione cifrata — chiedi all'insegnante la chiave del corso.", + schoolGrantAccess: "Invia chiave del corso", + schoolKeySent: "Chiave inviata", + schoolOngoing: "In corso", + schoolFilterApplied: "ISCRITTO", + schoolFilterOpen: "APERTI", + schoolFilterFavorites: "PREFERITI", + schoolPendingPayment: "Pagamento in sospeso", + schoolPendingStudents: "Iscrizioni in sospeso", + schoolViewBill: "Vedi fattura", + schoolPaymentNote: "L'iscrizione genera una fattura in ECO (un trasferimento). La tua iscrizione è confermata quando il trasferimento viene firmato.", + schoolCloseCourse: "Chiudi corso", + schoolReopenCourse: "Riapri corso", + schoolDescription: "Insegna e impara corsi nella tua rete.", + schoolFilterAll: "TUTTI", + schoolFilterMine: "MIEI", + schoolFilterTop: "TOP", + schoolFilterRecent: "RECENTI", + schoolCreateButton: "Crea Corso", + schoolCreateSectionTitle: "Crea un corso", + schoolUpdateSectionTitle: "Aggiorna corso", + schoolCourseTitlePlaceholder: "Nome del tuo corso", + schoolCourseDescriptionPlaceholder: "Cosa insegnerai in questo corso?", + schoolCourseImage: "Immagine del corso (dimensione max: 75MB)", + schoolTags: "Tag", + schoolTagsPlaceholder: "Inserisci tag separati da virgole", + schoolPrice: "Prezzo (ECO)", + schoolVisibility: "Visibilità", + schoolVisibilityPublic: "Pubblico", + schoolVisibilityInvite: "Su invito", + schoolStatus: "Stato", + schoolClosed: "Chiuso", + schoolFree: "Gratis", + schoolCreate: "Crea corso", + schoolUpdate: "Aggiorna corso", + schoolSearchPlaceholder: "Cerca in Scuola...", + schoolSortTitle: "Titolo", + schoolNoCourses: "Nessun corso trovato.", + schoolStudents: "Studenti", + schoolTeacher: "Insegnante", + schoolCreatedAt: "CREATO", + schoolLessons: "Lezioni", + schoolNoLessons: "Ancora nessuna lezione.", + schoolEnrollToSee: "Iscriviti per vedere le lezioni di questo corso.", + schoolEnrollButton: "ISCRIVITI", + schoolUnenrollButton: "Abbandona il corso", + schoolInviteRequired: "Questo corso è su invito.", + schoolTeacherPanel: "Pannello dell'insegnante", + schoolStudentPanel: "Pannello dello studente", + schoolAddLesson: "Crea lezione", + schoolLessonTitlePlaceholder: "Titolo della lezione", + schoolLessonTextPlaceholder: "Descrivi le nozioni trattate in questa lezione...", + schoolInviteStudents: "Invita studenti", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "INVITA", + schoolInvited: "Invitati", + schoolIssueCertificate: "Crea certificato", + schoolCertified: "Certificato", + schoolCertificateTextPlaceholder: "Nota del certificato (opzionale)", + schoolLessonOpen: "Apri lezione", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificati", + schoolNoCertificates: "Nessun certificato emesso finora.", + schoolEditButton: "Modifica", + schoolDeleteButton: "Elimina", + typeSchool: "SCUOLA", + schoolCourseLabel: "CORSO", + emergencyLabel: "EMERGENZA", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Corsi", + statsSchoolLesson: "Lezioni", + statsSchoolEnroll: "Iscrizioni", + statsSchoolCertificate: "Certificati della scuola", + modulesSchoolLabel: "Scuola", + modulesSchoolDescription: "Modulo per scambiare conoscenza.", + welcomeStepUxAction: "Salva vista", + welcomeStepWorkflowTitle: "Che uso di OASIS hai in mente?", + welcomeStepWorkflowText: "Oasis ha molti moduli. Scegli la configurazione adatta a te e resteranno attivi solo quelli utili. Puoi modificarla quando vuoi in Moduli.", + welcomeStepWorkflowAction: "Salva configurazione", + welcomeWorkflow_full: "Predefinito", chatRateLimitTitle: "Troppi messaggi", chatRateLimitMessage: "Hai raggiunto il limite di messaggi che questa stanza accetta in un'ora. Riprova più tardi.", chatMessagePlaceholder: "Scrivi il tuo messaggio...", chatNoMessages: "Ancora nessun messaggio.", chatTitlePlaceholder: "Titolo chat", chatDescriptionPlaceholder: "Descrizione chat", - chatTagsPlaceholder: "tag1, tag2, tag3", + chatTagsPlaceholder: "Inserisci tag separati da virgole", chatImageLabel: "Seleziona un file immagine (.jpeg, .jpg, .png, .gif)", chatAuthor: "Autore", chatAddFavorite: "Aggiungi ai Preferiti", chatRemoveFavorite: "Rimuovi dai Preferiti", chatPM: "MP", chatStatusLabel: "Stato", + chatTypeLabel: "Tipo di chat", chatCategoryLabel: "Categoria", gamesTitle: "Giochi", gamesDescription: "Scopri e gioca ad alcuni giochi.", @@ -3157,22 +4163,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Lancia una moneta e scommetti su testa o croce. Quanta fortuna hai?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "Biliardo visto dall'alto. Clicca per mirare, tieni premuto per caricare la potenza. Imbuca tutte le palle con il minor numero di tiri.", + gamesArtilleryTitle: "Artiglieria", + gamesArtilleryDesc: "Punta il cannone, tieni conto del vento e colpisci il bersaglio. 5 round, vince chi usa meno colpi.", + gamesLabyrinthTitle: "Labirinto", + gamesLabyrinthDesc: "Fuggi dal labirinto prima di esaurire le mosse. Ogni livello è più grande e più difficile.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Mangia tutti i puntini, evita i fantasmi. I fantasmi si muovono in tempo reale — non fermarti!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "Pendolo sonoro", + gamesAudioPendulumDesc: "Simulatore di fisica caotica con sintesi audio in tempo reale. Le velocità angolari diventano frequenze, i picchi colpi di percussione. Non esistono due simulazioni che suonino uguali.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Mixa visual dal vivo su due piatti: scegli un motivo per ciascuno, regola velocità, colore e zoom, sfuma tra i due e lancia flash a tempo. Batti il tempo o lascia che il microfono guidi il battito.", + gamesTetrisDesc: "I classici blocchi che cadono. Completa le linee per fare punti. Quanto resisterai?", + gamesMinesweeperTitle: "Campo minato", + gamesMinesweeperDesc: "La classica caccia alle mine. Ripulisci il campo per salire di livello — griglie più grandi, più mine, punteggio più alto.", + gamesFilterScoring: "PUNTEGGI", + gamesHallOfFame: "Albo d'oro", + gamesHallPlayer: "Giocatore", + gamesHallScore: "Punteggio", + gamesNoScores: "Ancora nessun punteggio.", calendarsTitle: "Calendari", calendarTitle: "Calendario", modulesCalendarsLabel: "Calendari", @@ -3180,15 +4190,15 @@ module.exports = { typeCalendar: "CALENDARI", calendarCreate: "Crea calendario", calendarUpdate: "Aggiorna", - calendarDelete: "Elimina", calendarTitleLabel: "Titolo", calendarTitlePlaceholder: "Titolo del calendario...", calendarStatusLabel: "Stato", + calendarTypeLabel: "Tipo di calendario", calendarStatusOpen: "APERTO", calendarStatusClosed: "CHIUSO", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Scadenza", calendarTagsLabel: "Tag", - calendarTagsPlaceholder: "tag1, tag2...", + calendarTagsPlaceholder: "Inserisci tag separati da virgole", calendarParticipantsLabel: "Partecipanti", calendarCreated: "Creato", calendarJoin: "Partecipa", @@ -3199,11 +4209,11 @@ module.exports = { calendarNotePlaceholder: "Aggiungi una nota...", calendarFirstDateLabel: "Data", calendarFirstNoteLabel: "Note", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Intervallo", calendarIntervalNone: "Senza ripetizione", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Settimanale", + calendarIntervalMonthly: "Mensile", + calendarIntervalYearly: "Annuale", calendarFormDescription: "Descrizione", calendarNoDates: "Nessuna data aggiunta.", calendarNoNotes: "Nessuna nota.", @@ -3211,6 +4221,8 @@ module.exports = { calendarsDescription: "Scopri e gestisci i calendari nella tua rete.", calendarMonthPrev: "← Precedente", calendarMonthNext: "Successivo →", + calendarMonthNames: "Gennaio,Febbraio,Marzo,Aprile,Maggio,Giugno,Luglio,Agosto,Settembre,Ottobre,Novembre,Dicembre", + calendarDayInitials: "L,M,M,G,V,S,D", calendarAllSectionTitle: "Calendari", calendarRecentSectionTitle: "Calendari Recenti", calendarFavoritesSectionTitle: "Preferiti", @@ -3220,9 +4232,9 @@ module.exports = { calendarCreateSectionTitle: "Crea nuovo calendario", calendarUpdateSectionTitle: "Aggiorna calendario", calendarAddFavorite: "Aggiungi ai preferiti", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Elimina", calendarRemoveFavorite: "Rimuovi dai preferiti", - calendarSearchPlaceholder: "Cerca calendari...", + calendarSearchPlaceholder: "Cerca in Calendario...", calendarAddEntry: "Aggiungi Voce", statsCalendar: "Calendari", statsCalendarDate: "Date calendario", @@ -3234,6 +4246,146 @@ module.exports = { tribeContentAccessDenied: "Accesso Negato", tribeContentAccessDeniedMsg: "Questo contenuto appartiene a una tribù. Devi essere membro per accedervi.", tribeViewTribes: "Visualizza Tribù", + tribeFilesEmpty: "Ancora nessun file.", + filesLabel: "File", + profileClearnetFilesLabel: "File", + filesTitle: "File", + filesDescription: "Esplora e gestisci i file nella tua rete.", + fileAllSectionTitle: "File", + fileMineSectionTitle: "I Tuoi File", + fileRecentSectionTitle: "File Recenti", + fileTopSectionTitle: "Migliori File", + fileFavoritesSectionTitle: "Preferiti", + fileFilterAll: "TUTTI", + fileFilterMine: "MIEI", + fileFilterRecent: "RECENTI", + fileFilterTop: "MIGLIORI", + fileFilterFavorites: "PREFERITI", + fileCreateSectionTitle: "Carica File", + fileUpdateSectionTitle: "Aggiorna File", + fileCreateButton: "Carica File", + fileUpdateButton: "Aggiorna", + fileAddFavoriteButton: "Aggiungi ai Preferiti", + fileRemoveFavoriteButton: "Rimuovi dai Preferiti", + fileFileLabel: "Seleziona un file (max: 75MB)", + calendarFilterRecent: "RECENTI", + calendarFilterAll: "TUTTI", + calendarFilterMine: "MIEI", + calendarFilterFavorites: "PREFERITI", + calendarFilterOpen: "APERTI", + calendarFilterClosed: "CHIUSI", + padFilterRecent: "RECENTI", + padFilterAll: "TUTTI", + padFilterMine: "MIEI", + padFilterOpen: "APERTI", + padFilterClosed: "CHIUSI", + chatFilterRecent: "RECENTI", + chatFilterAll: "TUTTI", + chatFilterMine: "MIEI", + chatFilterFavorites: "PREFERITI", + chatFilterOpen: "APERTI", + chatFilterClosed: "CHIUSI", + licenseLabel: "Licenza", + phoneTitle: "Telefono", + phoneDescription: "Gestisci le tue videochiamate cifrate end-to-end.", + phoneUnavailable: "Le chiamate non sono disponibili su questo dispositivo.", + phoneCallButton: "Chiama", + profileVisibilityPhone: "Numero di telefono", + phoneNumberUnknown: "Nessuno nella tua rete ha il numero {number}.", + phoneNumberChoose: "Controlla chi vuoi chiamare al {number}:", + phoneAnswer: "Rispondi", + phoneSpeaker: "Altoparlante", + phoneEarpiece: "Auricolare", + phoneReject: "Rifiuta", + phoneHangup: "Riaggancia", + phoneMute: "Disattiva microfono", + phoneUnmute: "Attiva microfono", + phoneCameraOn: "Fotocamera on", + phoneCameraOff: "Fotocamera off", + settingsPhoneVideo: "Video", + settingsPhoneVideoOn: "Consentito in chiamate e stanze", + settingsPhoneVideoOff: "Solo audio", + phoneSilence: "Non sentire", + phoneUnsilence: "Sentire di nuovo", + phoneCalling: "Chiamata a", + phoneIsCalling: "ti sta chiamando", + notifyNewPmFrom: "Nuovo MP da", + notifyNewPamFrom: "Nuovo PaM da", + notifyUpdateLabel: "Aggiornamento", + updateInstalledLabel: "Versione installata", + updateAvailableLabel: "Versione disponibile", + updateChangesLabel: "Modifiche", + updateFilesLabel: "File", + updateOriginLabel: "Origine", + phoneRecording: "Registrazione PaM per", + phoneLeaveMessage: "Nessuno risponde: lascia il tuo messaggio dopo il segnale acustico.", + phoneLeaveMessageBusy: "È in un'altra chiamata: lascia il tuo messaggio dopo il segnale.", + phoneLeaveMessageUnreachable: "Non è stato possibile contattarlo: lascia il tuo messaggio dopo il segnale.", + phoneConnecting: "Connessione con", + phoneSendPam: "Invia PaM", + phoneCancel: "Annulla", + phoneSearchPlaceholder: "Cerca in Telefono...", + phonePamTitle: "Registrazioni audio", + phoneNew: "Nuovo", + phoneDelete: "Elimina", + phoneNoPams: "Non hai registrazioni audio.", + phoneHistoryTitle: "Cronologia", + phoneLatestTitle: "Ultime chiamate", + phoneCreateButton: "Nuova chiamata", + phoneLimitsHint: "Fino a 7 abitanti per chiamata; con più di uno è una chiamata congiunta.", + phoneErrorPub: "Non si può chiamare un pub.", + phoneJointCall: "Chiamata congiunta", + phoneWith: "Con", + phoneResult: "Esito", + phoneWhen: "Quando", + phoneDuration: "Durata", + phoneOutgoing: "In uscita", + phoneIncoming: "In arrivo", + phoneNoHistory: "Ancora nessun registro.", + phoneOutcomeEnded: "Risposta", + phoneOutcomeMissed: "Persa", + phoneOutcomeRejected: "Rifiutata", + phoneOutcomeNoanswer: "Nessuna risposta", + phoneOutcomeCancelled: "Annullata", + phoneOutcomePam: "PaM inviato", + phoneOutcomeFailed: "Non riuscita", + phoneDialAdd: "Aggiungi", + phoneDialBack: "Cancella", + phoneDialClear: "Svuota", + phoneDialPlaceholder: "Numero o Oasis ID", + phoneDialInhabitants: "Abitanti", + phoneDialRemove: "Rimuovi", + phoneOwnNumber: "Questo è il tuo numero.", + phoneSettingsDesc: "Scegli chi può chiamarti.", + phoneDnd: "Non disturbare", + phoneOpen: "Aperto", + phoneRecipientRefuses: "L'abitante che stai cercando di chiamare non accetta chiamate.", + phoneRefusedNotice: "Questi abitanti non accettano chiamate e non sono stati chiamati:", + fileEmptyError: "Il file è vuoto. Scegli un altro file.", + fileTitleLabel: "Titolo", + fileTitlePlaceholder: "Titolo", + fileDescriptionLabel: "Descrizione", + fileDescriptionPlaceholder: "Descrizione", + fileTagsLabel: "Tag", + fileTagsPlaceholder: "Inserisci tag separati da virgole", + fileSizeLabel: "Dimensione", + fileDownloadButton: "SCARICA", + fileDetailsButton: "DETTAGLI", + fileNoFile: "Nessun file file", + fileSearchPlaceholder: "Cerca in File...", + fileSearchButton: "Cerca", + fileSortRecent: "Più recenti", + fileSortOldest: "Più vecchi", + fileSortTop: "Più votati", + fileNoMatch: "Nessun file corrispondente.", + fileUpdatedAt: "Aggiornato", + typeFile: "FILE", + modulesFilesLabel: "File", + modulesFilesDescription: "Modulo per scoprire e gestire gli file.", + tribeSectionFiles: "FILE", + fileDescription: "Descrizione", + fileDownload: "Scarica", + fileTypeLabel: "Tipo", torrentsTitle: "Torrent", torrentsDescription: "Esplora e gestisci i torrent nella tua rete.", torrentAllSectionTitle: "Torrent", @@ -3261,10 +4413,33 @@ module.exports = { torrentTagsLabel: "Tag", torrentTagsPlaceholder: "Inserisci tag separati da virgole", torrentSizeLabel: "Dimensione", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Genera torrent", + torrentFromContentMissing: "Il file non è ancora disponibile su questo nodo. Riprova tra un po'.", + torrentDownloads: "Download", + torrentFetchOasis: "Scarica in Oasis", + torrentSaveFile: "Salva file", + torrentSeeds: "seed", + seedsLabel: "Seed", + seedAction: "Supporta", + torrentSourceFile: "File", + torrentStateQueued: "In coda", + torrentStateProbing: "Ricerca seed", + torrentStateDownloading: "Scaricamento", + torrentStateDone: "Completato", + torrentStateFailed: "Fallito", + torrentStateNoSource: "Nessun seed connesso, in attesa", + torrentStateCancelled: "Annullato", + torrentNoDownloads: "Nessun download per ora.", + torrentOriginalSection: "Originale", + torrentCancel: "Annulla", + torrentDownloadButton: "SCARICA", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETTAGLI", torrentNoFile: "Nessun file torrent", noTorrents: "Nessun torrent trovato.", - torrentSearchPlaceholder: "Cerca titolo, tag, autore...", + noFiles: "Nessun file trovato.", + torrentSearchPlaceholder: "Cerca in Torrent...", torrentSearchButton: "Cerca", torrentSortRecent: "Più recenti", torrentSortOldest: "Più vecchi", @@ -3272,10 +4447,12 @@ module.exports = { torrentNoMatch: "Nessun torrent corrispondente.", torrentUpdatedAt: "Aggiornato", statsTorrent: "Torrent", + statsFile: "File", typeTorrent: "TORRENT", modulesTorrentsLabel: "Torrent", modulesTorrentsDescription: "Modulo per scoprire e gestire i torrent.", favoritesFilterTorrents: "TORRENT", + favoritesFilterFiles: "FILE", favoritesFilterMarket: "MERCATO", favoritesFilterEvents: "EVENTI", favoritesFilterForum: "FORUM", @@ -3289,15 +4466,17 @@ module.exports = { favoritesFilterShopProducts: "ARTICOLI NEGOZIO", tribeSectionTorrents: "TORRENT", tribeCreateTorrent: "Carica Torrent", + tribeCreateFile: "Carica file", settingsWishTitle: "Desiderio", - settingsWishDesc: "Configura il desiderio del tuo Avatar.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Determina come accedi a tutti i contenuti e la tua esperienza nella rete Oasis.", + settingsWishWhole: "Multiverso", + settingsWishMutuals: "Solo supporto reciproco", + settingsWishOnlyLan: "Solo LAN", + settingsWishLocal: "Locale", settingsPmVisibilityTitle: "Messaggi Privati", settingsPmVisibilityDesc: "Configura il livello di esposizione ai messaggi privati.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Multiverso", + settingsPmVisibilityMutuals: "Solo supporto reciproco", pmMutualNotice: "L'invio è un guardrail UX. La ricezione è un filtro di attenzione.", inhabitantsPendingFollowsTitle: "Richieste di supporto in attesa", inhabitantsPendingAccept: "Accetta", @@ -3341,7 +4520,7 @@ module.exports = { tribeGovNoRevocations: "Ancora nessuna revoca", tribeGovNoHistorical: "Ancora nessun record", logsTitle: "Diario", - logsDescription: "Registra la tua esperienza nella rete.", + logsDescription: "Tieni un diario privato delle tue esperienze.", logsViewTitle: "Diario", logsColumnType: "Tipo", logsView: "Vedi", @@ -3350,7 +4529,7 @@ module.exports = { logsCreateTitle: "Crea voce", logsWriteButton: "Scrivi", logsTextPlaceholder: "Descrivi le tue esperienze...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "Assistente AI", logsModeManual: "Manuale", logsModeAI: "IA", logsColumnLog: "Diario", @@ -3369,19 +4548,20 @@ module.exports = { logsExportOne: "Esporta registro", logsViewDetails: "Vedi dettagli", logsGenerateButton: "Genera testo", - logsSearchText: "Cerca nei diari...", + logsSearchText: "Cerca in Diario...", logsSearchAnyType: "Qualsiasi tipo", logsSearchButton: "Cerca", logsEmpty: "Nessuna voce ancora.", + logsNothingNew: "Non ci sono nuove azioni da registrare.", modulesLogsLabel: "Diario", - modulesLogsDescription: "Modulo per registrare (tramite assistente IA) le tue esperienze.", + modulesLogsDescription: "Modulo per tenere un diario privato delle tue esperienze.", statsLogsTitle: "Diario", typeLog: "DIARIO", larpTitle: "L.A.R.P.", larpDescription: "Uno strato di gioco di ruolo dal vivo per la sperimentazione collaborativa.", larpNoHousesMatch: "Nessuna casa corrisponde alla tua ricerca.", - larpSearchPlaceholder: "Cerca case...", + larpSearchPlaceholder: "Cerca in L.A.R.P....", larpAllHouses: "Case:", larpFilterRuling: "GOVERNA", larpFilterHouses: "CASE", @@ -3397,38 +4577,39 @@ module.exports = { larpPostSubmit: "Pubblica", larpCycleLabel: "Ciclo:", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", + bankRulesArchTaxNote: "La tassa ARCH riflette il costo a lungo termine di crescita e manutenzione dell'archivio della rete: più i tuoi dati sono vecchi nella rete, maggiore è la tua quota del costo di manutenzione.", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", + bankRulesChipNote: "Il chip di ogni elemento è colorato rispetto al messaggio più grande mai osservato nella rete e attenuato dal numero di abitanti attivi — una rete più grande distribuisce il carico e porta più elementi nella fascia verde.", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", + bankRulesEcoTaxNote: "La tassa ECO viene dedotta dal RBU lordo quando si calcola l'assegnazione dell'epoca. Gli ECO dedotti non vengono ridistribuiti in questa epoca — restano nel saldo del pub e alimentano il fondo dell'epoca successiva tramite i limiti reserveMin / alpha.", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", + bankRulesKarmaNote: "Il karma deriva dalle tue azioni (post, voti, partecipazione a parlamento/tribunali, ecc.) con un fattore di decadimento nel tempo, meno i grammi di carbonio generati dal tuo feed.", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", - statsPad: "Pads", - statsPadEntry: "Pad entries", - typeGameScore: "GAME SCORES", + bankTaxesArchTaxFootprintNote: "La tassa ARCH riflette il costo di crescita e manutenzione dell'archivio della rete. Si calcola dall'intervallo di tempo tra il tuo primo blocco pubblicato e il blocco più recente della rete — più a lungo la tua storia è rimasta nell'archivio, maggiore è la tua quota del costo di manutenzione.", + bankTaxesArchTaxNoteBold: "ridurre direttamente la tassa ARCH", + bankTaxesArchTaxNoteIntro: "La tua tassa ARCH viene dedotta dall'eccedenza che la rete genera oltre il tuo RBU; il valore di base del RBU è fissato come minimo inamovibile. La tassa non viene mai dedotta dall'importo fisso che spetta a ogni abitante come RBU. I fondi dedotti alimentano l'algoritmo di redistribuzione della ricchezza e vengono incanalati verso altri abitanti tramite le loro richieste di RBU. Gli abitanti più recenti e quelli dedicati specificamente ad attività di archiviazione, replica e manutenzione avranno probabilmente un compito dedicato a mantenere in salute l'archivio della rete per tuo conto. Oppure possono dedicarsi a ", + bankTaxesArchTaxNoteOutro: " tramite accordi di potatura, deduplicazione dei dati ridondanti e infrastrutture di manutenzione condivise, e il loro contributo continuo può essere richiesto dal consenso della rete.", + bankTaxesUserNoteChips: "Il chip della Tassa ECO mostrato accanto a ogni elemento cambia colore (verde / giallo / rosso) in base alla dimensione dell'elemento rispetto al blocco più grande mai visto nella rete, attenuato dal numero di abitanti attivi — una rete solarpunk più grande distribuisce il carico e porta più elementi nella fascia verde.", + bankTaxesUserNoteOtherParams: "La tassa ECO deriva attualmente dall'impronta di carbonio di ogni blocco, ma col tempo potrà includere altri parametri — energia spesa per la replica, archiviazione ridondante tra i peer, banda dei blob, costo computazionale della decifratura, impronta di mining delle transazioni associate o qualsiasi altro carico misurabile che la rete decida di valutare.", + gamesHallDate: "Data", + gamesNewRecord: "Nuovo record!", + indexingRefreshNote: "Questa pagina si ricarica ogni 10 secondi.", + indexingMessage: "Oasis sta sincronizzando la rete di nodi distribuita. Attendi, per favore.", + indexingTitle: "Sincronizzazione", + padsNoItems: "Nessun pad trovato.", + padVersionHistory: "Cronologia delle versioni", + padAttributionView: "Per autrice", + padVersionView: "Visualizza", + statsChat: "Chat", + statsChatMessage: "Messaggi di chat", + statsGameScore: "Giochi", + statsWiki: "Pagine wiki", + statsPad: "Pad", + statsPadEntry: "Voci dei pad", + typeGameScore: "PUNTEGGI DI GIOCO", bankTaxesLookupNote: "Inserisci un ID di blocco per cercarlo nel tuo feed locale.", bankTaxesUserNoteChipsClick: "Clicca su qualsiasi chip per ispezionare il suo blocco nel blockexplorer.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3443,6 +4624,14 @@ module.exports = { settingsReplicationTitle: "Replicazione", settingsReplicationDesc: "Configura il numero di salti che il tuo peer segue dal tuo feed durante la replica dei contenuti.", settingsReplicationHopsLabel: "Salti", + settingsBlobCacheTitle: "Cache dei media", + settingsBlobCacheDesc: "I contenuti degli altri abitanti vengono conservati su questo dispositivo. Scegli quanto spazio possono occupare.", + settingsBlobCacheUsage: "Uso attuale", + settingsBlobCacheFiles: "file", + settingsBlobCacheLimit: "Limite", + settingsBlobCacheUnlimited: "Illimitato", + settingsBlobCacheCleanNow: "Pulisci ora", + settingsBlobCacheCleaned: "{n} file rimossi, {size} liberati.", aiApproveTagsLabel: "Tag (separati da virgole)", aiApproveTagsPlaceholder: "es. oasis, governance, ecologia", aiApproveRatingLabel: "Valutazione", @@ -3470,13 +4659,11 @@ module.exports = { larpInviteBannerHint: "Condividi questo codice con qualcuno in ACADEMIA. Scade tra 30 cicli o dopo un singolo uso.", larpTestRankingTitle: "Punteggio per casa", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Codice di invito di casa", - invitesHouseJoinButton: "Unisciti alla Casa", - ecoTaxLabel: "ECO Tax", + ecoTaxLabel: "Tassa ECO", bankTaxes: "Tasse", - bankOverviewYourTaxes: "Le tue tasse", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesArchTaxTitle: "ARCH Tax", + bankOverviewYourTaxes: "Imposte totali", + bankTaxesEcoTaxTitle: "Tassa ECO", + bankTaxesArchTaxTitle: "Tassa ARCH", bankTaxesUserAmount: "La tua ECO Tax (prezzo da restituire)", bankTaxesArchTaxUserAmount: "La tua ARCH Tax (prezzo da restituire)", bankTaxesArchTaxFirstBlock: "Età del tuo primo blocco", @@ -3504,7 +4691,7 @@ module.exports = { bankTaxesLookupType: "Tipo", bankTaxesLookupSize: "Dimensione", bankTaxesLookupCarbon: "Impronta di carbonio", - bankTaxesLookupEcoin: "ECO Tax", + bankTaxesLookupEcoin: "Tassa ECO", bankExchangeEcoTaxAnnual: "ECOin Taxes (annuale)", bankExchangeEcoTaxMonthly: "ECOin Taxes (mensile)", bankTaxesTypesLabel: "Seleziona quali tasse vuoi pagare", @@ -3512,14 +4699,14 @@ module.exports = { bankRulesPoolTitle: "Pool mensile", bankRulesShareTitle: "Quota per utente", bankRulesKarmaTitle: "Karma (punteggio di partecipazione)", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesArchTaxTitle: "ARCH Tax", + bankRulesEcoTaxTitle: "Tassa ECO", + bankRulesArchTaxTitle: "Tassa ARCH", bankRulesChipTitle: "Banda di colore del chip ECO Tax", bankRulesEpochKind: "Granularità dell'epoch", bankRulesAlpha: "Alpha (quota massima del saldo del pub per epoch)", bankRulesReserveMin: "Riserva minima (mantenuta nel saldo del pub)", bankRulesCapPerEpoch: "Tetto per epoch (assoluto)", - bankRulesGraceDays: "Periodo di grazia del claim", + bankRulesGraceDays: "Periodo di grazia della richiesta", bankRulesWMin: "Peso minimo (w_min)", bankRulesWMax: "Peso massimo (w_max)", bankRulesFloor: "Pavimento per utente (lordo)", @@ -3531,12 +4718,11 @@ module.exports = { blockchainBlockNotAvailable: "Questo blocco non è disponibile nel tuo feed locale. Potrebbe appartenere a un peer da cui non stai ancora replicando.", blockchainBackToBlockexplorer: "Torna al blockexplorer", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeDetailTitle: "Transcode", + audioTranscodeButton: "TRANSCODIFICA", + audioTranscodeDetailTitle: "Transcodifica", audioTranscodeDetailDescription: "Decodifica il payload incorporato e la mappa di composizione originale della blockchain.", - audioTranscodeStegoOasisId: "Da", audioTranscodeStegoTimestamp: "Generato il", - audioTranscodeStegoMessage: "TEXT", + audioTranscodeStegoMessage: "TESTO", audioTranscodeStegoEmpty: "(nessuno)", audioTranscodeStegoUnknown: "—", audioTranscodeStegoNotFound: "Nessun payload steganografico è stato decodificato da questo audio.", @@ -3544,7 +4730,6 @@ module.exports = { audioBackToBcs: "Torna a BCS", melodyCompositionTitle: "Mappa di composizione blockchain", melodyFilterMine: "MIO", - melodyByLabel: "Da", melodyAllEmpty: "Ancora nessuna composizione BCS da altri abitanti.", melodyNoteUnavailable: "il blocco non è nel tuo feed locale", melodyUploadBcsTitle: "Pubblica", @@ -3570,14 +4755,14 @@ module.exports = { larpBadgeYou: "Tu", larpBadgeRuling: "Governa", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Modulo per lo strato di gioco di ruolo dal vivo di Oasis (le 9 Case).", + modulesLarpDescription: "Modulo per il livello di gioco di ruolo dal vivo.", larpProfileQ1: "Di fronte a un problema complesso, cosa fai per primo?", larpProfileQ1O1: "Parlare con altri per cercare il consenso", larpProfileQ1O2: "Costruire un prototipo da testare", larpProfileQ1O3: "Studiare la letteratura", larpProfileQ1O4: "Sabotare il sistema che lo provoca", larpProfileQ2: "Cosa dà senso al tuo lavoro quotidiano?", - larpProfileQ2O1: "Difendere le persone che amo", + larpProfileQ2O1: "Difendere gli abitanti che amo", larpProfileQ2O2: "Creare qualcosa di tangibile", larpProfileQ2O3: "Guarire o coltivare la vita", larpProfileQ2O4: "Lavorare con parole e idee", @@ -3644,6 +4829,51 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Seleziona la modalità di navigazione UX per la tua interfaccia.", uxModeMenus: "Blocchi", + uxModeChats: "Conversazioni", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Tag di Tendenza", + feedActiveTitle: "Abitanti Attivi", + cnBrandSub: "Libero · P2P · Federato", + cnSyncedPeers: "Peer sincronizzati", + cnLicense: "Licenza", + cnNotAccessible: "Il contenuto non è accessibile in questo momento.", + cnUntitled: "Senza titolo", + cnSearchPlaceholder: "Cerca…", + cnHubTitle: "HUB Clearnet", + cnHubDescription: "Contenuti pubblici condivisi dagli abitanti di questa rete Oasis.", + cnHubEmpty: "Nessun contenuto pubblico è ancora stato condiviso sulla Clearnet.", + cnPublicContent: "Contenuti", + cnTribesContent: "Tribù", + cnNetworkContent: "Rete", + cnCategoryEmpty: "Nessun contenuto in questa categoria.", + cnInhabitantEmpty: "Questo abitante non ha ancora pubblicato contenuti sulla Clearnet.", + cnPrevPage: "← Precedente", + cnNextPage: "Successiva →", + cnPageOf: "Pagina {page} di {pages}", + cnMoreInfo: "Maggiori informazioni →", + cnOpenCourse: "Corso aperto", + cnAuction: "ASTA", + cnExchange: "SCAMBIO", + cnRssDescription: "pubblicati dagli abitanti di questo HUB", + cnKindAudio: "Audio", + cnKindBlog: "Blog", + cnKindBookmark: "Segnalibro", + cnKindDocument: "Documento", + cnKindEvent: "Evento", + cnKindFeed: "Feed", + cnKindImage: "Immagine", + cnKindJob: "Lavoro", + cnKindMarket: "Mercato", + cnKindPodcast: "Podcast", + cnKindProject: "Progetto", + cnKindCourse: "Corso", + cnKindShop: "Negozio", + cnKindTorrent: "Torrent", + cnKindFile: "File", + cnKindVideo: "Video", + cnKindWiki: "Wiki", + cnKindRoom: "Stanza", + cnKindMap: "Mappa", uxModeAINav: "AI", } diff --git a/src/client/assets/translations/oasis_pt.js b/src/client/assets/translations/oasis_pt.js index 4d3ecd8..2180f61 100644 --- a/src/client/assets/translations/oasis_pt.js +++ b/src/client/assets/translations/oasis_pt.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "Compras", shopMyOrdersDescription: "As tuas encomendas de compra.", shopMyOrdersEmpty: "Ainda não tens compras.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM Vendedor", shopOrderSeller: "Vendedor", shopOrderInvoice: "Fatura", shopOrderInvoiceConcept: "Fatura", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Aceitar", shopOrderReject: "Rejeitar", shopOrderMarkShipped: "Marcar enviado", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM Comprador", shopOrderContractConcept: "Encomenda de loja", shopOrdersPending: "Encomendas pendentes", all: "Todos", @@ -28,7 +28,7 @@ module.exports = { recent: "Recentes", anonymous: "Anónimo", unnamed: "Anónimo", - authorLabel: "AUTOR", + authorLabel: "Autoria", delete: "Eliminar", save: "Guardar", vote: "Votar", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Aviso de conteúdo", invalidPost: "Conteúdo inválido", invalidPostHint: "Esta mensagem tem texto vazio/inválido.", - spreadBy: "Por", spreadChron: "Difusão", spreaded: "Difundido", spreadContentUnavailable: "Conteúdo ainda não disponível (replicação pendente)", filteredByTag: "Filtrado por etiqueta", filteredByTagTitle: "Filtrado por etiqueta", feedPublishedSuccess: "Feed publicado com sucesso!", + feedMastodonFailed: "Publicado no Oasis, mas não foi possível enviá-lo para o Mastodon.", tribeFeedSent: "Mensagem enviada com sucesso!", tribeContentEncrypted: "Conteúdo encriptado", tribeTorrentsEmpty: "Ainda sem torrents.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Última visita", profileClearnetBookmarksLabel: "Marcadores", invitesPort: "Porta", - currentlyUnrecheable: "ERRO!", - peerHostValidation: "IPv4 válido (ex. 192.168.1.100) ou nome de host (ex. pub.example.com)", + peerHostValidation: "IPv4 válido (ex. 192.168.1.100) ou nome de host (ex. pub.solarnethub.com)", peerPortValidation: "Porta 1-65535", peerKeyValidation: "Chave pública SSB ed25519 (@<44 caracteres base64>=.ed25519)", inboxJobUnsubscribedTitle: "Cancelamento da tua oferta de emprego", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Descrição da tarefa", exportPasswordLabel: "Senha de exportação", importPasswordPlaceholder: "Introduz a palavra-passe", - searchCasesPlaceholder: "Pesquisar casos…", + searchCasesPlaceholder: "Pesquisar em Tribunais...", shopBuyDeliveryAddressPlaceholder: "Endereço de entrega", shopBuyNotesPlaceholder: "Notas", bankTaxesUserNoteBold: "reduzir diretamente o imposto ECO", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Apoiar o caso", courtsSettlementAcceptBtn: "Aceitar acordo", courtsSupportCount: "Apoios", - courtsVerdictVoteTitle: "Votar no veredicto", - courtsVerdictVoteLabel: "Voto do veredicto", - courtsVerdictVoteAccept: "Aceitar", - courtsVerdictVoteReject: "Rejeitar", - courtsVerdictVoteSubmit: "Enviar voto", reportsSetStatus: "Definir estado", reportsStatusClosed: "FECHADO", statsAvgPerInhabitant: "Média por habitante", @@ -189,9 +183,29 @@ module.exports = { ], profile: "Avatar", inhabitants: "Habitantes", - peersReplicatedFeeds: "Feeds replicados", + peersReplicatedFeeds: "Replicados", + peersTorColumn: "TOR", + peersTorYes: "Acessível por Tor", + peersTorNo: "Não acessível por Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Versão", graphos: "Graphos", graphosDescription: "Mapa interativo da rede ao seu redor.", + graphosFederation: "Federação", + graphosFedPubs: "PUBs", + graphosFedUnreachable: "Inalcançáveis", + graphosFedLinks: "Ligações mútuas", + graphosFedIsolated: "Isolados", + graphosFedNoData: "Sem dados", + graphosFedMutual: "Mútuo", + graphosFedOneWay: "Num sentido", + graphosFedTransport: "Rede", + graphosFedWith: "Federado com", + graphosZoom: "Ampliar", + graphosFedInhabitants: "Habitantes", + graphosFedClearnet: "Internet", + graphosFedBoth: "Internet + TOR", + graphosFedEmpty: "O seu nó ainda não conhece nenhum PUB.", graphosViewingGraphOf: "A ver", graphosBackToMine: "← Minha rede", graphosShowing: "Mostrando", @@ -203,8 +217,24 @@ module.exports = { ".", ], noMentions: "Ainda não recebeste @menções.", - mentionsSearchPlaceholder: "Procurar menções...", + mentionsSearchPlaceholder: "Pesquisar em Menções...", privateReminders: "LEMBRETES", + inboxFilterPms: "MPs", + inboxFilterNotifications: "Notificações", + inboxMarkRead: "Marcar como lido", + inboxMarkUnread: "Marcar como não lido", + inboxUnreadChip: "Não lido", + inboxMarkAllRead: "Marcar tudo como lido", + inboxDeleteShown: "Apagar tudo", + inboxArchive: "Arquivar", + inboxUnarchive: "Desarquivar", + inboxFilterArchived: "Arquivados", + inboxEarlierMessages: "mensagens anteriores", + inboxSortThread: "Por conversa", + inboxSortRecent: "Mais recentes primeiro", + inboxAllBots: "Bots", + settingsInboxBotsTitle: "Notificações do Inbox", + settingsInboxBotsDesc: "Escolhe quais bots podem avisar-te no teu Inbox. Os bots desmarcados ficam ocultos e os seus avisos não são contados.", inboxFiltersLabel: "Filtros:", inboxToggleToMutuals: "Mudar para mútuos", inboxToggleToWhole: "Mudar para todos", @@ -212,6 +242,7 @@ module.exports = { pmReply: "Responder", pmReplies: "respostas", inReplyTo: "EM RESPOSTA A", + forumInThread: "NO TÓPICO", pmPreview: "Pré-visualizar", pmPreviewTitle: "Pré-visualização da mensagem", peers: "Pares", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "De", searchToLabel: "Até", searchQueryLabel: "Pesquisa", - searchPerPageLabel: "Resultados por página", + searchPerPageLabel: "Resultados", settings: "Definições", + settingsDescription: "Configura o ambiente da tua rede.", continueReading: "Continuar a ler", moreComments: "mais comentários", readThread: "ler o resto da conversa", @@ -239,7 +271,6 @@ module.exports = { profileClearnetHelp: 'Módulos acessíveis a partir de fora do Oasis.', profileHubAll: 'Tudo', footerLicenseLabel: 'Licença', - profileSwitchToOasis: 'Voltar para Oasis', profileVisibilityFediverse: "Multiverso", contributorsTitle: "Contribuidores", colorLabel: 'Escolhe uma cor', @@ -249,6 +280,7 @@ module.exports = { modulesViewTitle: "Módulos", modulesViewDescription: "Configura o teu ambiente ativando ou desativando módulos.", inbox: "Caixa de entrada", + inboxSearchPlaceholder: "Pesquisar em Caixa de entrada...", multiverse: "Multiverso", fediverse: "Multiverso", fediverseDescription: "Faça a gestão das suas outras contas do multiverso, incluindo enviar e receber conteúdo.", @@ -257,9 +289,17 @@ module.exports = { fediverseSettingsTitle: "Multiverso", fediverseInstanceLabel: "Endereço", fediverseTokenLabel: "Token de acesso", - fediverseTokenHelp: "Defina o seu token de acesso. Será usado para gerir a sua conta de Mastodon a partir do Oasis.", fediverseConnect: "Ligar", fediverseConnectedAs: "Conectado como", + nextcloudServerLabel: "Servidor", + nextcloudUserLabel: "Utilizador", + nextcloudFolderLabel: "Pasta", + nextcloudUploadButton: "Enviar", + nextcloudFileLabel: "Escolhe um ficheiro", + fileExternalLink: "Ligação externa", + nextcloudErrConnect: "Não foi possível chegar ao Nextcloud com estes dados.", + nextcloudErrUpload: "O envio para o Nextcloud falhou.", + nextcloudErrShare: "O Nextcloud não deu uma ligação pública para o ficheiro.", fediverseDisconnect: "Desconectar", fediverseEmptyLink: "suas definições", fediverseComposePlaceholder: "No que está pensando?", @@ -288,6 +328,76 @@ module.exports = { fediverseErrPost: "Não foi possível publicar.", fediverseErrMedia: "Não foi possível enviar o ficheiro.", fediverseErrEmpty: "Escreva algo ou anexe um ficheiro.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Número de telefone", + telegramSendCode: "Enviar código", + telegramCodeHelp: "Introduz o código que o Telegram te acabou de enviar (app ou SMS).", + telegramCodeLabel: "Código", + telegramPasswordHelp: "Esta conta tem verificação em dois passos. Introduz a tua palavra-passe.", + telegramPasswordLabel: "Palavra-passe", + telegramVerify: "Verificar", + telegramCancel: "Cancelar", + telegramChats: "Conversas", + telegramUnread: "Não lidas", + telegramOpen: "Abrir", + telegramSend: "Enviar", + telegramYou: "Tu", + telegramNoChats: "Ainda não há conversas.", + telegramNoMessages: "Ainda não há mensagens.", + telegramComposePlaceholder: "Escreve uma mensagem…", + telegramChannel: "Canal", + telegramGroup: "Grupo", + telegramPrivate: "Privado", + telegramErrConnect: "Não foi possível contactar o Telegram.", + telegramErrTimeout: "O Telegram demorou demasiado a responder.", + telegramErrAuth: "Sessão do Telegram inválida ou expirada. Liga de novo.", + telegramErrFetch: "Não foi possível carregar as conversas.", + telegramErrSend: "Não foi possível enviar a mensagem.", + telegramErrMissing: "São necessários API ID, API hash e número de telefone.", + telegramErrCode: "Código inválido. Tenta de novo.", + telegramErrCodeExpired: "O código expirou. Começa de novo.", + telegramErrPassword: "Palavra-passe errada.", + telegramErrPhone: "Número de telefone inválido.", + telegramErrApi: "API ID ou API hash inválidos.", + telegramErrFlood: "Demasiadas tentativas. Espera um pouco e tenta de novo.", + telegramErrNoLogin: "Nenhum início de sessão em curso. Começa de novo.", + peertubeUsernameLabel: "Utilizadora", + peertubePasswordLabel: "Palavra-passe", + peertubeSubscriptions: "Subscrições", + peertubeMyVideos: "Os meus vídeos", + peertubeVideos: "Vídeos", + peertubeFollowers: "Seguidoras", + peertubeViews: "visualizações", + peertubeLikes: "gostos", + peertubeQuota: "Quota", + peertubeUpload: "Carregar vídeo", + peertubeUploadHelp: "Escolhe um ficheiro de vídeo. É enviado para o teu canal e o PeerTube processa-o depois.", + peertubeTitleLabel: "Título", + peertubeDescriptionLabel: "Descrição", + peertubePrivacyLabel: "Privacidade", + peertubePrivacyPublic: "Público", + peertubePrivacyUnlisted: "Não listado", + peertubePrivacyPrivate: "Privado", + peertubeNoVideos: "Ainda não há vídeos.", + peertubeComments: "Comentários", + peertubeComment: "Comentar", + peertubeCommentPlaceholder: "Escreve um comentário…", + peertubeNoComments: "Ainda não há comentários.", + peertubeOpen: "Abrir no PeerTube", + peertubeWatch: "Ver", + peertubeChannel: "Canal", + peertubeNoStream: "Este vídeo ainda não pode ser reproduzido aqui. Abre-o no PeerTube.", + peertubeUploaded: "Vídeo enviado. O PeerTube está a processá-lo.", + peertubeErrConnect: "Não foi possível contactar o PeerTube.", + peertubeErrAuth: "Utilizadora ou palavra-passe incorretas, ou a sessão expirou. Liga de novo.", + peertubeErrInstance: "URL de instância inválido.", + peertubeErrMissing: "São necessários instância, utilizadora e palavra-passe.", + peertubeErrFetch: "Não foi possível carregar os vídeos.", + peertubeErrUpload: "Não foi possível enviar o vídeo.", + peertubeErrTitle: "Título obrigatório.", + peertubeErrComment: "Não foi possível publicar o comentário.", + peertubeErrNoFile: "Escolhe um ficheiro de vídeo.", bookmarksLabel: "Marcadores", videosLabel: "Vídeos", torrentsLabel: "Torrents", @@ -310,7 +420,6 @@ module.exports = { comment: "Comentário", subtopic: "Subtópico", json: "JSON", - createdBy: "por", unfollow: "Deixar de apoiar", follow: "Apoiar", block: "Bloquear", @@ -326,12 +435,15 @@ module.exports = { relationshipNone: "Não estás a apoiar", relationshipBlockingPost: "Publicação bloqueada", viewLikes: "Ver difusões", + viewSupporters: "Ver apoiantes", spreadedDescription: "Lista de publicações difundidas pelo habitante.", preview: "Pré-visualizar", publish: "Publicar", contentWarningPlaceholder: "Adiciona um assunto à publicação (opcional)", - publishWarningPlaceholder: "Corpo limitado a 7000 caracteres.", + publishWarningPlaceholder: "Escreve o corpo da tua entrada.", publishTooLong: "A tua publicação é demasiado longa. Encurta-a, por favor.", + keepTextRetry: "Enviar de novo", + dateInPastError: "A data não pode ser anterior a hoje", publishCustomDescription: [ "LEMBRA-TE: Devido à tecnologia blockchain, uma vez publicado, não pode ser editado nem eliminado.", ], @@ -361,9 +473,8 @@ module.exports = { mentionsMatching: "Menções correspondentes", mentionsName: "Nome", mentionsRelationship: "Relacionamento", - updateit: "OBTER ATUALIZAÇÕES!", updateBannerText: "Uma nova versão do Oasis está disponível.", - updateBannerAction: "Atualizar agora →", + updateBannerAction: "Atualizar agora!", info: "Info", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -371,16 +482,14 @@ module.exports = { timeAgo: "atrás", sendTime: "há ", theme: "Tema", - legacy: "Chaves", - legacyTitle: "Chaves", - legacyDescription: "Gere o teu segredo (chave privada) de forma rápida e segura.", - legacyExportButton: "Exportar", - legacyImportButton: "Importar", - ssbLogStream: "Blokchain", + backupKeysExportButton: "Exportar", + backupKeysImportButton: "Importar", + ssbLogStream: "Blockchain", ssbLogStreamDescription: "Configura o limite de mensagens para os fluxos da Blockchain.", + logWindowChip: "Últimos {n} streams da blockchain", saveSettings: "Guardar definições", exportDescription: "Define uma senha (mínimo 32 caracteres) para encriptar a tua chave", - exportDataTitle: "Cópia de segurança", + exportDataTitle: "Cópia instantânea", exportDataDescription: "Descarrega os teus dados (chave secreta excluída!)", exportDataButton: "Descarregar base de dados", importDescription: "Importa o teu segredo encriptado (chave privada) para ativar o teu avatar", @@ -389,38 +498,33 @@ module.exports = { fileInfo: "A tua chave secreta encriptada será descarregada para o teu dispositivo (nome: oasis.enc)", developer: "Desenvolvimento", devTitle: "Desenvolvimento", - welcomeBannerText: "Bem-vinda ao OASIS. Segue estes passos rápidos para configurar o teu avatar.", + welcomeBannerText: "O teu avatar ainda não está configurado.", aiSuggestionBanner: "Sugestão:", aiSuggestionsEnable: "Sugestões da IA", - welcomeBannerAction: "Começar!", + welcomeBannerAction: "Começar", welcomeTitle: "Bem-vinda", + welcomeStepIdentity: "Identidade", + welcomeStepType: "Tipo", + welcomeStepUse: "Uso", + welcomeStepLook: "Aparência", + welcomeStepCommunity: "Comunidade", + welcomeStepIntro: "Boas-vindas", + welcomeHintIdentity: "O teu nome e a tua imagem.", + welcomeHintType: "Como o Oasis se abre para ti.", + welcomeHintUse: "Que módulos ficam ativos.", + welcomeHintLook: "O tema que o Oasis veste.", + welcomeHintCommunity: "Entra num PUB para conheceres outros habitantes.", + welcomeHintIntro: "Envia uma primeira saudação à comunidade.", + welcomeStepThemeAction: "Usar este tema", + welcomeJoinPubs: "Entrar nos PUBs", + welcomeNoFederation: "Sem federação", welcomeStepGreetingAction: "Enviar", - welcomeJoinPubButton: "Juntar-se ao pub", - welcomeStepLarpAction: "Juntar-se à \"Academia\"", - welcomeStepLarpText: "Junta-te ao nosso jogo de representação ao vivo.", - welcomeStepLarpTitle: "Junta-te ao L.A.R.P.", - welcomeStepGreetingTitle: "Envia um \"Olá mundo!\"", - welcomeStepGreetingText: "Envia uma mensagem para que outros habitantes te possam descobrir.", - welcomeJoinPub: "Juntar-se a", welcomeDescription: "Algumas coisas que vale a pena fazer antes de começar.", - welcomeProgress: "Concluído", - welcomeStepDone: "feito", - welcomeStepPending: "pendente", welcomeSkip: "Agora não", - welcomeStepLanguageTitle: "Escolhe o teu idioma", - welcomeStepLanguageText: "O Oasis fala vários idiomas. Podes mudar quando quiseres.", - welcomeStepProfileTitle: "Edita o teu perfil", - welcomeStepProfileText: "Escolhe um nome, acrescenta uma descrição e põe uma imagem para que os outros habitantes te reconheçam.", welcomeStepProfileAction: "Guardar perfil", - welcomeStepFederationTitle: "Junta-te à rede principal", - welcomeStepFederationText: "Liga-te ao nosso pub para encontrar outros habitantes e começar a replicar.", - welcomeStepFederationAction: "Ir aos convites", - welcomeStepBackupTitle: "Faz cópia do teu ID", - welcomeStepBackupText: "A tua identidade é um ficheiro de chaves neste dispositivo.", - welcomeStepBackupWarning: "SE O PERDERES, NINGUÉM O PODE RECUPERAR POR TI.", - welcomeStepBackupAction: "Cópia de segurança!", + welcomeBootstrapRunning: "A carregar o instantâneo do pub. Ao terminar junta-se ao pub automaticamente; entretanto podes continuar a usar o Oasis.", welcomeCompleteTitle: "Tudo pronto.", - welcomeCompleteText: "O teu nó está pronto. A partir daqui a rede cresce com as pessoas que segues.", + welcomeCompleteText: "O teu nó está pronto. A partir daqui a rede cresce com os habitantes que apoias.", welcomeFindPeople: "Procurar habitantes", devFilterFiles: "FICHEIROS", devFilterSearch: "PROCURAR", @@ -430,7 +534,7 @@ module.exports = { devFilterTests: "TESTES", devVersion: "Versão", devNodeVersion: "Node", - devStatsTests: "Suites de testes", + devStatsTests: "Testes", devParentFolder: "Pasta superior", devOpenFolder: "abrir", devDescription: "Explora o código-fonte em execução neste nó.", @@ -474,6 +578,27 @@ module.exports = { "Se preferires usar outro idioma, seleciona-o aqui.", setLanguage: "Definir idioma", peerConnectionsIntro: "Gere todas as tuas ligações com outros pares.", + peersConnectFailed: "Não foi possível ligar: {reason}.", + peerStateLabel: "Estado", + peerAddressLabel: "Endereço", + peerLastChangeLabel: "Última alteração", + peerStateConnected: "Ligado", + peerStateConnecting: "A ligar", + peerStateStaged: "Disponível", + peerErrSilent: "Sem atividade", + peerErrTimeout: "Não responde", + peerErrRefused: "Recusa a ligação", + peerErrNotFound: "Endereço desconhecido", + peerErrUnreachable: "Rede inacessível", + peerErrKeys: "A usar chaves diferentes", + peerErrIdentity: "Não aceita a sua ligação", + peerErrTor: "Só TOR", + peerErrDropped: "Ligação cortada", + peerErrPaused: "Rede em pausa", + peerErrOther: "Erro de ligação", + peerOutdated: "Desatualizado", + peerVersionUnannounced: "Não anuncia a sua versão: um Oasis antigo", + peersTorMissing: "Este PUB só permite ligações através do TOR.", peerConnectionsTitle: "Ligações", online: "Online", offline: "Offline", @@ -492,22 +617,25 @@ module.exports = { noConnections: "Nenhum par conectado.", noDiscovered: "Nenhum par descoberto.", noUnknownPeers: "Sem pares desconhecidos no grafo de amizades.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Ainda não há pares registados.", + peerConnect: "Ligar", + peerDisconnect: "Desligar", peerRefresh: "Atualizar", peerPruneIdle: "Remover inativos", + peersPause: "Pausar", + peersResume: "Retomar", + peersPausedNote: "Em pausa: o Oasis não se liga a ninguém e ninguém se pode ligar a ele.", + peersPauseHint: "Para todas as ligações: o Oasis não se liga a outros nós nem os aceita até retomares.", peerExport: "Exportar", peerImport: "Importar", peerImportTitle: "Importar lista de peers", peerImportPlaceholder: "Cola um endereço multiserver por linha, ou carrega um ficheiro .txt…", connectionActionIntro: "", - startNetworking: "Iniciar rede", - stopNetworking: "Parar rede", - restartNetworking: "Reiniciar rede", - sync: "Sincronizar rede", indexes: "Índices", + indexesRebuilt: "Índices reconstruídos", + indexesRebuildFailed: "Não foi possível reconstruir os índices", + indexesFiles: "Ficheiros de índice", indexesDescription: "Reconstruir os teus índices é seguro e pode corrigir alguns tipos de erros.", homePageTitle: "Início", @@ -517,44 +645,33 @@ module.exports = { invitesDescription: "Gere e aplica códigos de convite na tua rede.", invitesPubsHint: "Cola um código de convite de um pub para te federares com ele e replicar os seus habitantes.", invitesFederationsHint: "Pubs com que estás federado: atualiza-os, remove os inacessíveis ou exporta a lista.", - invitesHousesHint: "Introduz um código de casa para entrares numa casa do LARP e participar na sua economia.", - invitesTribesHint: "Introduz um código de tribo para te tornares membro e aceder ao conteúdo privado.", - invitesChatsHint: "Introduz um código de chat para entrares na conversa e nos seus tópicos.", - invitesPadsHint: "Introduz um código de pad para escreverem juntos num documento partilhado.", - invitesCalendarsHint: "Introduz um código de calendário para partilhar datas e lembretes.", - invitesEventsHint: "Introduz um código de evento para entrares num evento privado.", - invitesForumsHint: "Introduz um código de fórum para leres e responderes num fórum privado.", - invitesMapsHint: "Introduz um código de mapa para veres e acrescentares lugares num mapa partilhado.", - invitesShopsHint: "Introduz um código de loja para entrares numa loja e no seu catálogo.", - invitesInhabitantsHint: "Introduz um código de habitante para se seguirem e trocarem conteúdo.", invitesTribesTitle: "Tribos", - invitesTribeInviteCodePlaceholder: "Introduz código de convite da tribo", - invitesTribeJoinButton: "Aderir à tribo", invitesChatsTitle: "Chats", - invitesChatInviteCodePlaceholder: "Introduz código de convite do chat", - invitesChatJoinButton: "Entrar no chat", invitesPadsTitle: "Pads", - invitesPadInviteCodePlaceholder: "Introduz código de convite do pad", - invitesPadJoinButton: "Entrar no pad", invitesCalendarsTitle: "Calendários", - invitesCalendarInviteCodePlaceholder: "Introduz código de convite do calendário", - invitesCalendarJoinButton: "Entrar no calendário", invitesEventsTitle: "Eventos", - invitesEventInviteCodePlaceholder: "Introduz código de convite do evento", - invitesEventJoinButton: "Entrar no evento", invitesForumsTitle: "Fóruns", - invitesForumInviteCodePlaceholder: "Introduz código de convite do fórum", - invitesForumJoinButton: "Entrar no fórum", invitesMapsTitle: "Mapas", - invitesMapInviteCodePlaceholder: "Introduz código de convite do mapa", - invitesMapJoinButton: "Entrar no mapa", invitesShopsTitle: "Lojas", - invitesShopInviteCodePlaceholder: "Insira o código de convite da loja", - invitesShopJoinButton: "Entrar na loja", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "PUBs Sugeridos", + invitesFilterOthers: "Outras", + invitesJoinTitle: "Convites", + invitesJoinHint: "Cola qualquer código de convite, seja do que for, ou o Oasis ID de um habitante.", + invitesJoinPlaceholder: "Código de convite ou Oasis ID", + invitesJoinButton: "Entrar", + invitesUsedTitle: "Últimos convites", + invitesUsedEmpty: "Ainda não utilizaste nenhum convite.", + invitesUsedType: "Tipo", + invitesUsedName: "Nome", + invitesUsedDate: "Data", invitesPubInviteCodePlaceholder: "Introduz código de convite PUB", invitesAcceptInvite: "Aderir ao PUB", + invitesPubUses: "Convites usados", + invitesPubExhausted: "Este PUB não tem convites disponíveis.", invitesAlreadyFederated: "Você já está federado com este pub.", + invitesJoinPubMode: "Este Oasis funciona como PUB, em modo só de leitura. Para o juntar a outro PUB, usa o terminal: ./oasis.sh join ", + invitesJoinLocalOnly: "Os convites só podem ser aceites a partir do dispositivo onde o Oasis corre, ou com a ligação de administração que mostra ao arrancar.", invitesFederationsTitle: "Federações", invitesAcceptedInvites: "Federadas", invitesUnfollow: "Deixar de seguir", @@ -562,16 +679,12 @@ module.exports = { invitesUnfollowedInvites: "Não federadas", invitesNoFederatedPubs: "Sem pubs federados.", invitesNoUnfollowed: "Sem pubs não federados.", - invitesUnreachablePubs: "Inalcançáveis", - invitesNoUnreachablePubs: "Sem pubs inalcançáveis.", invitesPubsRefresh: "Atualizar", - invitesPubsClearUnreachable: "Remover inalcançáveis", + invitesPubsClearUnreachable: "Limpar", invitesPubsExport: "Exportar", invitesPubsImport: "Importar", invitesPubsImportTitle: "Importar pubs", invitesPubsImportPlaceholder: "Cola um endereço multiserver ou código de convite por linha, ou carrega um ficheiro .txt…", - currentlyUnreachable: "ERRO!", - errorDetails: "Detalhes do erro", genericError: "Ocorreu um erro.", panicMode: "Modo pânico!", encryptData: "Define uma senha (mínimo 32 caracteres) para encriptar a tua blockchain", @@ -598,7 +711,6 @@ module.exports = { votesOptionNoLabel:"Não", votesOptionAbstentionLabel:"Abstenção", votesQuestionLabel:"Pergunta", - votesCreatedByLabel:"Criado por", voteStatusOpen:"ABERTO", voteStatusClosed:"FECHADO", commentDescription: ({ parentUrl }) => [ @@ -615,22 +727,22 @@ module.exports = { searchSubmit: "Pesquisar...", votesLabel: "VOTAÇÕES", pdfFallbackLabel: "Documento PDF", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "LOJAS", + voteLabel: "VOTOS", submit: "Enviar", editProfile: "Editar avatar", oasisIdLabel: "OasisID", spreadContent: "Replicar", welcomePmSubject: "Hello World!", - welcomePmBody: "Bem-vindo ao Oasis — uma rede social livre, peer-to-peer, federada e cifrada, com uma arquitetura distribuída apenas por convite.\n\nAlguns locais interessantes por onde começar:\n\n- /profile — Edita o teu avatar, nome, descrição e quais módulos aparecem no teu lado público.\n- /invites — Adiciona um pub para te federares com o resto da rede.\n- /peers — Vê quem está ligado, faz nova varredura da LAN, exporta ou importa listas de peers.\n- /inhabitants — Descobre e visita os avatares de outras pessoas.\n- /tribes — Cria ou junta-te a grupos privados com cifragem ponta-a-ponta.\n- /inbox — Lê e envia mensagens privadas.\n- /activity — Vê a atividade recente, tua e da tua rede.\n- /publish — Escreve um post ou partilha uma imagem, áudio, vídeo ou documento.\n- /search — Pesquise o conteúdo da rede por tipo.\n\nAlguns lugares interessantes para conectar:\n\n- /multiverse — Faça a gestão das suas outras contas do multiverso, incluindo enviar e receber conteúdo.\n\nEsta mensagem foi enviada de forma privada de ti para ti mesmo, por isso não foi necessária qualquer ligação para a receber.", + welcomePmBody: "Bem-vindo ao Oasis — uma rede social livre, peer-to-peer, federada e cifrada, com uma arquitetura distribuída apenas por convite.\n\nAlguns locais interessantes por onde começar:\n\n- [/avatar](/profile) — Edita o teu avatar, nome, descrição e quais módulos aparecem no teu lado público.\n- /settings — Ajusta a tua instância local: idioma, tema, módulos, privacidade e sincronização.\n- /invites — Adiciona um pub para te federares com o resto da rede.\n- /peers — Vê quem está ligado, faz nova varredura da LAN, exporta ou importa listas de peers.\n- /inhabitants — Descobre e visita os avatares de outros habitantes.\n- /tribes — Cria ou junta-te a grupos privados com cifragem ponta-a-ponta.\n- /search — Pesquise o conteúdo da rede por tipo.\n\nAlguns lugares interessantes para conectar:\n\n- /multiverse — Faça a gestão das suas outras contas do multiverso, incluindo enviar e receber conteúdo.\n\nEsta mensagem foi enviada de forma privada de ti para ti mesmo, por isso não foi necessária qualquer ligação para a receber.", profileSensorsSectionTitle: "Sensores", profileVisibilityActivity: "Nível de atividade", profileDeviceLockedHint: "Este sensor está sempre ativo: permite que outros vejam de que dispositivo te conectas e não pode ser desativado.", profileVisibilityKarma: "Pontuação KARMA", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "Carteira ECOIN", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "RBU", + profileVisibilityWallet: "Carteira ECOin", + profileVisibilityEcoTax: "Taxa ECO", + profileVisibilityLarpSign: "Selo L.A.R.P.", profileVisibilityGpg: "Chave GPG", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Lojas", @@ -643,6 +755,10 @@ module.exports = { profileClearnetImagesLabel: "Imagens", profileClearnetDocumentsLabel: "Documentos", profileClearnetTorrentsLabel: "Torrents", + profileClearnetPodcastsLabel: "Podcasts", + profileClearnetMarketLabel: "Mercado", + profileClearnetFeedLabel: "Feed", + profileClearnetWikiLabel: "Wikis", editProfileDescription: "", profileName: "Nome", @@ -668,7 +784,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Conf.", walletConfirm: "Confirmar", - walletDescription: "Gere os teus ativos digitais, incluindo enviar e receber ECOin, ver o teu saldo e aceder ao teu histórico de transações.", + walletDescription: "Gere a tua carteira ECOin.", walletDate: "Data", walletFee: "Taxa (quanto maior a taxa, mais rápido a tua transação será processada)", walletFeeLine: ({ fee }) => `Taxa: ECO ${fee}`, @@ -676,6 +792,86 @@ module.exports = { walletReceive: "Receber", walletReset: "Repor", walletSend: "Enviar", + walletPayingTransfer: "A pagar transfer", + walletCreateTransferLabel: "Gerar contrato (transferência)", + walletTransferNotifiedNote: "A outra parte foi avisada deste pagamento", + walletTransferCreatedNote: "Foi criado um transfer (recibo) deste pagamento", + walletTransferNoPayeeNote: "Nenhum habitante publicou este endereço, por isso não se criou transfer", + walletTransferReceiptConcept: "Pagamento a partir da carteira", + walletPaymentPmText: "enviou-te um pagamento", + walletPayeeLabel: "Destinatário", + walletConceptLabel: "Conceito", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Recebeste o teu RBU.", + bankingBotPaymentTitle: "Recebeste um pagamento.", + bankingBotUbiPaidText: "O teu Rendimento Básico Universal foi pago", + bankUbiPubThisNode: "Este nó paga o RBU", + bankUbiPubConnected: "PUB do RBU", + bankUbiPubNone: "Nenhum PUB anuncia ainda RBU na tua rede", + bankUbiAddressPublished: "Endereço ECOin publicado", + bankUbiAddressMissing: "Publica o teu endereço ECOin para receber o RBU", + bankUbiClaimedThisMonth: "Reclamado este mês", + bankUbiPubBalance: "Carteira do PUB disponível para o RBU", + bankNoPubAddress: "O PUB do RBU ainda não publicou um endereço ECOin.", + bankUbiTab: "RBU", + bankDonateEco: "DOAR!", + donateTooltip: "Doar ECOin", + bankNoUserAddress: "Este habitante não partilha um endereço ECOin.", + bankUbiPubDefault: "Predefinido", + bankUbiPool: "Dote", + bankUbiLastSeen: "Visto pela última vez", + bankUbiLastPayout: "Última ação", + bankUbiPaidOut: "Distribuído", + bankRangeToday: "Hoje", + bankRangeWeek: "Semana", + bankRangeMonth: "Mês", + bankRangeYear: "Ano", + bankRangeFiveYears: "5 anos", + bankRangeAll: "Tudo", + bankChartValueTitle: "Evolução do valor de ECOin", + bankChartValueHours: "Equivalência em tempo (h)", + bankChartWealthTitle: "Redistribuição da riqueza ECOin", + bankChartDistributed: "RBU distribuído (ECO)", + bankChartRebalanceTitle: "Redistribuição da riqueza entre PUBs", + bankChartRebalanced: "ECO movidos entre PUBs", + bankChartTaxes: "Impostos retidos (ECO)", + bankChartNoDataRange: "Sem dados neste intervalo.", + bankChartNoDataYet: "Ainda sem dados", + bankChartPerHour: "por hora", + bankChartPerDay: "por dia", + bankChartPerMonth: "por mês", + bankTotalUbiDistributed: "RBU total redistribuído (rede)", + bankTotalTaxesWithheld: "Impostos totais retidos (rede)", + bankInflationIssuance: "Taxa de emissão (anual, pela recompensa de bloco)", + bankRulesEligibilityTitle: "Quem pode receber o RBU", + bankRulesEligibility1: "Um endereço ECOin publicado no perfil (Perfil → Sensores → Carteira ECOin).", + bankRulesEligibility2: "Um feed com mais de 30 dias e atividade na rede: karma acima de zero.", + bankRulesEligibility3: "Uma reclamação por mês (epoch), em Banking com o botão Reclamar RBU. O mês em curso pode recusar-se com Recusar RBU.", + bankRulesEligibility4: "O mínimo de 1 ECO paga-se sempre; os impostos só reduzem a parte acima dele e nunca se cobram à parte.", + bankRulesEligibility5: "O PUB paga no ciclo seguinte e publica um transfer RBU que podes confirmar.", + bankRulesEligibility6: "Se falta o endereço, o feed é demasiado novo, recusaste o mês ou o PUB não tem fundos, a reclamação é ignorada e registada no PUB.", + copyLinkTitle: "Copiar ligação", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Recusar RBU", + bankUbiRefusedThisMonth: "Recusaste o RBU deste mês.", + bankingBotUbiAvailableTitle: "Podes reclamar o teu RBU.", + bankingBotUbiAvailableText: "O teu RBU deste mês está disponível", + bankingBotConfirmTitle: "Tens um smart-contract pendente de confirmação", + bankingBotConfirmText: "Smart-contracts à espera da tua confirmação", + bankChartZoomHint: "Clica para ampliar", + bankChartPoolTitle: "Dote do RBU (ao longo do tempo)", + bankChartNetworkUbiTitle: "RBU total redistribuído (rede)", + bankChartSupplyTitle: "Oferta circulante de ECOin (ao longo do tempo)", + bankChartInflationTitle: "Inflação de ECOin (ao longo do tempo)", + bankChartInflationSeries: "Taxa anualizada (%)", + bankChartPool: "Dote total (ECO)", + bankChartKarmaTitle: "KARMA Scoring (ao longo do tempo)", + bankChartFundsTitle: "Os seus fundos ECOin (ao longo do tempo)", + bankChartKarma: "Karma", + bankWalletReady: "ECOin ligado", + bankWalletNotConfigured: "ECOin desligado", + transfersPayWithWallet: "PAGAR!", walletStatus: "Estado", walletDisconnected: [ "ECOin ", @@ -685,8 +881,11 @@ module.exports = { " ou o estado da ligação.", ], walletSentToLine: ({ destination, amount }) => `Enviado ECO ${amount} para ${destination}`, + walletNotConfigured: "A sua carteira ECOin ainda não está configurada.", walletSettingsDescription: "Integra o Oasis com a tua carteira ECOin.", - walletSettingsDocLink: "Guia de instalação ECOin", + walletSettingsMobileDescription: "Ligue o Oasis à app ECOin Wallet deste dispositivo.", + walletDownloadEcoin: "Descarregar ECOin!", + walletRpcUrl: "URL do RPC", walletStatusMessages: { invalid_amount: "Montante inválido", invalid_dest: "Endereço de destino inválido", @@ -702,6 +901,7 @@ module.exports = { walletUser: "Nome de utilizador", walletPass: "Senha", walletConfiguration: "Definir carteira", + walletDisconnectButton: "Desligar carteira", cipher: "Encriptação", cipherTitle: "Encriptação", cipherDescription: "Encripta e decifra o teu texto simetricamente (usando uma senha partilhada).", @@ -735,17 +935,16 @@ module.exports = { bookmarkFilterRecent: "RECENTES", bookmarkCreateButton: "Criar marcador", bookmarkUpdateButton: "Atualizar", - bookmarkDeleteButton: "Eliminar", bookmarkAddFavoriteButton: "Adicionar favorito", bookmarkRemoveFavoriteButton: "Remover favorito", bookmarkUrlLabel: "Link", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Descrição", bookmarkDescriptionPlaceholder: "Opcional", bookmarkTagsLabel: "Tags", bookmarkTagsPlaceholder: "Introduz tags separadas por vírgulas", bookmarkLastVisitLabel: "Última visita", - bookmarkSearchPlaceholder: "Pesquisar URL, tags, categoria, autor...", + bookmarkSearchPlaceholder: "Pesquisar em Marcadores...", bookmarkSortRecent: "Mais recentes", bookmarkSortOldest: "Mais antigos", bookmarkSortTop: "Mais votados", @@ -784,7 +983,7 @@ module.exports = { videoDescriptionPlaceholder: "Opcional", videoNoFile: "Nenhum ficheiro de vídeo fornecido", noVideos: "Sem vídeos disponíveis.", - videoSearchPlaceholder: "Pesquisar título, tags, autor...", + videoSearchPlaceholder: "Pesquisar em Vídeos...", videoSortRecent: "Mais recentes", videoSortOldest: "Mais antigos", videoSortTop: "Mais votados", @@ -807,7 +1006,6 @@ module.exports = { documentFilterFavorites: "FAVORITOS", documentCreateButton: "Carregar documento", documentUpdateButton: "Atualizar", - documentDeleteButton: "Eliminar", documentAddFavoriteButton: "Adicionar favorito", documentRemoveFavoriteButton: "Remover dos favoritos", documentMessageAuthorButton: "MP", @@ -820,7 +1018,7 @@ module.exports = { documentDescriptionPlaceholder: "Opcional", documentNoFile: "Sem ficheiro.", noDocuments: "Sem documentos disponíveis.", - documentSearchPlaceholder: "Pesquisar título, tags, descrição, autor...", + documentSearchPlaceholder: "Pesquisar em Documentos...", documentSortRecent: "Mais recentes", documentSortOldest: "Mais antigos", documentSortTop: "Mais votados", @@ -841,19 +1039,19 @@ module.exports = { audioFilterTop: "TOP", audioCreateButton: "Carregar áudio", audioUpdateButton: "Atualizar", - audioDeleteButton: "Eliminar", audioAddFavoriteButton: "Adicionar favorito", audioRemoveFavoriteButton: "Remover favorito", audioFileLabel: "Seleciona um ficheiro de áudio (.mp3, .wav, .ogg)", audioTagsLabel: "Tags", audioTagsPlaceholder: "Introduz tags separadas por vírgulas", + tagsPlaceholder: "Introduz tags separadas por vírgulas", audioTitleLabel: "Título", audioTitlePlaceholder: "Opcional", audioDescriptionLabel: "Descrição", audioDescriptionPlaceholder: "Opcional", audioNoFile: "Nenhum ficheiro de áudio fornecido", noAudios: "Sem áudios disponíveis.", - audioSearchPlaceholder: "Pesquisar título, tags, autor...", + audioSearchPlaceholder: "Pesquisar em Áudios...", audioSortRecent: "Mais recentes", audioSortOldest: "Mais antigos", audioSortTop: "Mais votados", @@ -865,7 +1063,7 @@ module.exports = { audioFilterFavorites: "FAVORITOS", favoritesTitle: "Favoritos", favoritesDescription: "Todos os teus conteúdos favoritos num só lugar.", - favoritesSearchPlaceholder: "Pesquisar nos favoritos...", + favoritesSearchPlaceholder: "Pesquisar em Favoritos...", favoritesFilterAll: "TODOS", favoritesFilterRecent: "RECENTES", favoritesFilterAudios: "ÁUDIOS", @@ -877,13 +1075,12 @@ module.exports = { favoritesFilterChats: "CHATS", favoritesFilterCalendars: "CALENDÁRIOS", favoritesFilterVideos: "VÍDEOS", - favoritesRemoveButton: "Remover dos favoritos", favoritesNoItems: "Ainda sem favoritos.", yourContacts: "Os teus contactos", allInhabitants: "Habitantes", allCVs: "CVs", discoverPeople: "Descobre habitantes na tua rede.", - searchInhabitantsPlaceholder: "FILTRAR habitantes POR NOME …", + searchInhabitantsPlaceholder: "Pesquisar em Habitantes...", filterLocation: "FILTRAR habitantes POR LOCALIZAÇÃO …", filterLanguage: "FILTRAR habitantes POR IDIOMA …", filterSkills: "FILTRAR habitantes POR COMPETÊNCIAS …", @@ -907,7 +1104,6 @@ module.exports = { topSectionTitle: "TOP", blockedLabel: "Habitante bloqueado", inhabitantviewDetails: "Ver detalhes", - cvVisitButton: "Visitar CV", keepReading: "Continuar lendo...", oasisId: "ID", noInhabitantsFound: "Ainda sem habitantes encontrados.", @@ -925,6 +1121,18 @@ module.exports = { parliamentFilterLeaders: "LÍDERES", parliamentFilterRules: "REGRAS", parliamentGovernmentCard: "Governo atual", + parliamentSeatsGovernment: "Governo", + parliamentSeatsOpposition: "Oposição", + parliamentSeatsAbstention: "Abstenção", + parliamentSeatsQuorum: "Quórum de eleição", + parliamentSeatsPass: "Limiar de aprovação", + parliamentSeatsByElection: "Por eleição", + parliamentSeatsByHouses: "Por casa do LARP", + parliamentSeatsSeats: "assentos", + parliamentSeatsSeat: "assento", + parliamentSeatsLeader: "Líder", + parliamentSeatsVotesFor: "votos para", + parliamentSeatsNoHouse: "Sem casa", parliamentActorInPowerInhabitant: 'HABITANTE NO PODER', parliamentActorInPowerTribe: 'TRIBO NO PODER', parliamentHistoricalGovernmentsTitle: 'GOVERNOS', @@ -975,7 +1183,7 @@ module.exports = { parliamentMethodKARMATOCRACY: "Karmatocracia", parliamentThId: "ID", parliamentThProposalDate: "Data da proposta", - parliamentThMethod: "Method", + parliamentThMethod: "Método", parliamentThKarma: "Karma", parliamentThSupports: "Apoios", parliamentThVote: "Votar", @@ -992,6 +1200,7 @@ module.exports = { parliamentTimeRemaining: "Tempo restante", parliamentElectionsStatusTitle: "Próximo governo", parliamentProposalDeadlineLabel: "Prazo", + parliamentViewReport: "Ver relatório", parliamentProposalTimeLeft: "Tempo restante", parliamentRulesTitle: "Como funciona o Parlamento", parliamentRulesIntro: "As eleições resolvem-se a cada 2 meses; as candidaturas são contínuas e reiniciam quando um governo é escolhido.", @@ -1006,6 +1215,7 @@ module.exports = { parliamentRulesLaws: "Quando uma proposta atinge o limiar, torna-se Lei e aparece no separador Leis com a data de promulgação.", parliamentRulesHistorical: "No separador Histórico podes ver cada ciclo de governo que ocorreu e dados sobre a sua gestão.", parliamentRulesLeaders: "No separador Líderes podes ver um ranking de habitantes/tribos que governaram (ou se candidataram), ordenados por eficiência.", + parliamentRulesHemicycle: "O hemiciclo atribui um assento por habitante: o bloco do governo (os eleitores da candidatura vencedora e o líder), um bloco de oposição por cada outra candidatura do ciclo, e a abstenção. As marcas tracejadas mostram o quórum de eleição (25 % do censo) e o limiar de aprovação do método em vigor. \"Por casa do LARP\" colore os mesmos assentos por casa.", parliamentProposalVoteStatusLabel: "Estado da votação", parliamentProposalOnTrackYes: "Limiar atingido", parliamentProposalOnTrackNo: "Abaixo do limiar", @@ -1019,6 +1229,7 @@ module.exports = { courtsCaseFormTitle: "Abrir caso", courtsCaseRespondent: "Acusado / Demandado", courtsCaseRespondentPh: "Oasis ID (@...) ou nome da tribo", + courtsCaseSubjectPlaceholder: "Assunto ou descrição breve", courtsCaseMethod: "Método de resolução", courtsCaseSubmit: "Apresentar caso", courtsNominateJudge: "Nomear juiz", @@ -1052,15 +1263,30 @@ module.exports = { courtsMethodDICTATOR: "Ditador", courtsMethodPOPULAR: "Popular", courtsMethodKARMATOCRACY: "Karmatocracia", + courtsCaseType: "Tipo de caso", + courtsPresetMinorConflict: "Conflito leve", + courtsPresetModerateConflict: "Conflito moderado", + courtsPresetSevereConflict: "Conflito grave", + courtsPresetHarassment: "Assédio ou abuso", + courtsPresetContentModeration: "Moderação de conteúdo", + courtsPresetBanRestriction: "Banimento ou restrição", + courtsPresetGovernanceDispute: "Disputa de governança", + courtsStatusOPEN: "Aberto", + courtsStatusIN_PROGRESS: "Em curso", + courtsStatusDECIDED: "Decidido", + courtsStatusCLOSED: "Fechado", + courtsStatusSOLVED: "Resolvido", + courtsStatusUNSOLVED: "Não resolvido", + courtsStatusDISCARDED: "Descartado", courtsRulesTitle: "Como funcionam os Tribunais", courtsRulesIntro: "Os Tribunais são um processo comunitário para resolver conflitos e promover justiça restaurativa. Diálogo, provas claras e medidas proporcionais são priorizados.", courtsRulesLifecycle: "Processo: 1) Abrir caso 2) Selecionar método 3) Apresentar provas 4) Audiência e deliberação 5) Veredicto e medida 6) Cumprimento e encerramento 7) Recurso (se aplicável).", - courtsRulesRoles: "Acusador: abre o caso. Defesa: pessoa ou tribo acusada. Método: mecanismo escolhido pela comunidade para facilitar, avaliar provas e emitir veredicto. Testemunha: fornece depoimento ou provas. Mediadores: pessoas neutras convidadas pelo acusador e/ou defesa, com acesso a todos os detalhes, que ajudam a desescalar o conflito e co-criar acordos.", + courtsRulesRoles: "Acusador: abre o caso. Defesa: habitante ou tribo acusada. Método: mecanismo escolhido pela comunidade para facilitar, avaliar provas e emitir veredicto. Testemunha: fornece depoimento ou provas. Mediadores: habitantes neutros convidados pelo acusador e/ou defesa, com acesso a todos os detalhes, que ajudam a desescalar o conflito e co-criar acordos.", courtsRulesEvidence: "Descrição até 1000 caracteres. Anexa imagens, áudio, vídeo e documentos PDF relevantes e legais. Não partilhes dados privados sensíveis sem consentimento.", courtsRulesDeliberation: "As audiências podem ser públicas ou privadas. Os juízes garantem respeito, pedem esclarecimentos e podem descartar material irrelevante ou ilegal.", courtsRulesVerdict: "A restauração é priorizada: pedidos de desculpa, acordos de mediação, moderação de conteúdo, restrições temporárias ou outras medidas proporcionais. A fundamentação deve ser registada.", courtsRulesAppeals: "Recurso: permitido quando há novas provas ou erro processual claro. Deve ser apresentado em 7 dias, salvo indicação contrária.", - courtsRulesPrivacy: "Respeita a privacidade e segurança. Doxing, ódio ou ameaças são removidos. Os juízes podem editar ou selar partes do registo para proteger pessoas em risco.", + courtsRulesPrivacy: "Respeita a privacidade e segurança. Doxing, ódio ou ameaças são removidos. Os juízes podem editar ou selar partes do registo para proteger habitantes em risco.", courtsRulesMisconduct: "Assédio, manipulação ou provas fabricadas podem levar a resolução negativa imediata.", courtsRulesGlossary: "Caso: registo de um conflito. Provas: materiais que sustentam alegações. Veredicto: decisão com medida. Recurso: pedido de revisão do veredicto.", courtsFilterActions: "AÇÕES", @@ -1076,12 +1302,16 @@ module.exports = { exploreTrending: "Explora o conteúdo mais popular na tua rede.", votesButton: "VOTAÇÕES", shopProductButton: "LOJA", + podcastButton: "PODCAST", + podcastEpisodeButton: "EPISÓDIO", + campaignButton: "CAMPANHA", + logisticsRouteButton: "ROTA", trendingTotalOpinions: "Total de opiniões", trendingNoContentMessage: "Ainda não há tendências.", trendingTotalCount: "Contagem total", tasksTitle: "Tarefas", tasksDescription: "Descobre e gere tarefas na tua rede.", - taskSearchPlaceholder: "Pesquisar tarefas...", + taskSearchPlaceholder: "Pesquisar em Tarefas...", taskTitleLabel: "Título", taskDescriptionLabel: "Descrição", taskStartTimeLabel: "Hora de início", @@ -1100,7 +1330,7 @@ module.exports = { taskStatusOpen: "Abrir", taskStatusInProgress: "Em progresso", taskStatusClosed: "Fechado", - taskAssignedTo: "Atribuído a", + taskAssignedTo: "Atribuído", taskAssignedChip: "Atribuída", taskUnassignedChip: "Por atribuir", taskAssignees: "Atribuídos", @@ -1110,6 +1340,7 @@ module.exports = { taskUpdateButton: "Atualizar", taskDeleteButton: "Eliminar", taskFilterAll: "TODOS", + taskFilterRecent: "RECENTES", taskFilterMine: "MEUS", taskFilterOpen: "ABERTO", taskFilterInProgress: "EM PROGRESSO", @@ -1137,14 +1368,13 @@ module.exports = { eventUpcomingDates: "Próximas datas", eventsTitle: "Eventos", eventsDescription: "Descobre e gere eventos na tua rede.", - eventSearchPlaceholder: "Pesquisar eventos...", + eventSearchPlaceholder: "Pesquisar em Eventos...", eventPrice: "Preço", eventStatus: "Estado", eventAllSectionTitle: "Eventos", eventMineSectionTitle: "Os teus eventos", eventCreateSectionTitle: "Criar evento", eventUpdateSectionTitle: "Atualizar evento", - eventDeleteButton: "Eliminar", eventCreateButton: "Criar evento", eventAddToCalendar: "Adicionar ao calendário", eventVisitCalendar: "Ver calendário", @@ -1162,11 +1392,11 @@ module.exports = { viewPlace: "Ver Lugar", generatePdf: "Gerar PDF", sharePm: "Partilhar por MP", - galleryImages: "Imagens (tamanho máx: 50MB cada)", + galleryImages: "Imagens (tamanho máx: 75MB cada)", galleryPhotos: "Imagens", galleryAddPhoto: "Adicionar Imagem", galleryRemovePhoto: "Remover", - galleryVideo: "Vídeo (tamanho máx: 50MB)", + galleryVideo: "Vídeo (tamanho máx: 75MB)", galleryAddVideo: "Adicionar Vídeo", galleryRemoveVideo: "Remover vídeo", viewVotation: "Ver votação", @@ -1186,6 +1416,7 @@ module.exports = { eventDate: "Data", eventAttendButton: "Participar no evento", eventFilterAll: "TODOS", + eventFilterRecent: "RECENTES", eventFilterMine: "MEUS", eventFilterToday: "HOJE", eventFilterWeek: "ESTA SEMANA", @@ -1212,7 +1443,7 @@ module.exports = { tagsFilterTop: "TOP", tagsFilterCloud: "NUVEM", tagsNoItems: "Sem tags disponíveis.", - tagsSearchPlaceholder: "Procurar etiquetas...", + tagsSearchPlaceholder: "Pesquisar em Tags...", tagsTableHeaderTag: "TAG/LINK:", tagsTableHeaderCount: "CONTADOR:", transfersTitle: "Transferências", @@ -1220,7 +1451,10 @@ module.exports = { transfersFrom: "De", transfersTo: "Para", transfersFilterAll: "TODOS", + transfersFilterRecent: "RECENTES", transfersFilterMine: "MEUS", + transfersFilterMinePending: "PENDENTES", + transfersFilterMineConfirmed: "ASSINADOS", transfersFilterUBI: "RBU", transfersFilterMarket: "MERCADO", transfersFilterTop: "TOP", @@ -1235,7 +1469,6 @@ module.exports = { transfersCategoryEconomic: "Económica", transfersCategoryTime: "Tempo", transfersCategoryTrust: "Confiança", - transfersCategoryApply: "Aplicar", transfersUnitEco: "ECO", transfersUnitHours: "h", transfersUnitTrust: "confiança", @@ -1265,7 +1498,7 @@ module.exports = { transfersDiscardedSectionTitle: "Transferências descartadas", transfersCreateSectionTitle: "Criar transferência", transfersAllSectionTitle: "Transferências", - transfersSearchPlaceholder: "Pesquisar conceito, tags, utilizadores...", + transfersSearchPlaceholder: "Pesquisar em Transferências...", transfersMinAmountLabel: "Montante mínimo", transfersMaxAmountLabel: "Montante máximo", transfersSortRecent: "Mais recentes", @@ -1287,15 +1520,17 @@ module.exports = { voteErrorTitle: "Verifica o formulário", voteErrorDeadlineMin: "O prazo tem de ser de pelo menos {days} dias a partir de agora.", voteErrorGeneric: "Não foi possível guardar a votação.", + voteLocked: "Esta votação já recebeu votos ou opiniões, ou pertence ao Parlamento ou aos Tribunais: já não pode ser editada nem apagada.", voteUpdateButton: "Atualizar", voteDeleteButton: "Eliminar", - voteOptionYes: "YES", + voteOptionYes: "SIM", voteOptionNo: "NO", voteOptionAbstention: "ABSTENTION", voteConfused: "CONFUSO", voteFollowMajority: "SEGUIR MAIORIA", voteNotInterested: "SEM INTERESSE", voteFilterAll: "TODOS", + voteFilterRecent: "RECENTES", voteFilterMine: "MEUS", voteFilterOpen: "ABERTO", voteFilterClosed: "FECHADO", @@ -1311,7 +1546,6 @@ module.exports = { voteTags: "Tags", voteOpinions: "Opiniões", novotes: "Sem propostas de votação disponíveis.", - voteBy: "Por", voteCreatedAt: "Criado em", voteNoQuestion: "Sem questão fornecida", voteUnknownCreator: "Criador desconhecido", @@ -1321,6 +1555,7 @@ module.exports = { voteCommentsForumButton: 'Abrir discussão', voteCommentsSectionTitle: 'Abrir discussão', voteNoCommentsYet: 'Ainda sem comentários. Sê o primeiro a responder.', + commentsGoToLast: "Ir para o último comentário", voteNewCommentPlaceholder: 'Escreve o teu comentário aqui…', voteNewCommentButton: 'Publicar comentário', voteNewCommentLabel: 'Adicionar comentário', @@ -1331,6 +1566,8 @@ module.exports = { cvNameLabel: "Nome completo", cvDescriptionLabel: "Resumo", cvPhotoLabel: "Foto", + cvPdfLabel: "CV (PDF)", + cvVisitButton: "Visitar CV", cvPersonalExperiencesLabel: "Experiências pessoais", cvPersonalSkillsLabel: "Competências pessoais (separadas por vírgulas)", cvOasisExperiencesLabel: "Experiências de contribuição Oasis", @@ -1360,9 +1597,9 @@ module.exports = { cvNoCV: "Sem CV encontrado.", blogSubject: "Assunto", blogMessage: "Mensagem", - blogImage: "Carregar mídia (máx: 50MB)", + blogImage: "Carregar mídia (máx: 75MB)", blogMedia: "Anexos (até 8 imagens e um vídeo)", - blogPublish: "Pré-visualizar", + blogPublish: "Publicar", pollsTitle: "Sondagens", dataTitle: "Correspondências", dataDescription: "Explora e visualiza as correspondências da tua rede.", @@ -1381,12 +1618,23 @@ module.exports = { dataKindTasks: "TAREFAS", dataKindReports: "RELATÓRIOS", dataKindVotes: "VOTAÇÕES", - dataSearchPlaceholder: "Procurar nos dados cruzados...", + dataSearchPlaceholder: "Pesquisar em Correspondências...", dataCohesionTitle: "Coeficiente de coesão (CC)", dataCcShort: "CC", dataCohesionHint: "Afinidade média entre tudo o que a rede publicou.", dataAffinity: "Afinidade", dataCommonTerms: "Palavra-chave", + dataReasonFollowing: "Apoias este habitante", + dataReasonTribe: "Partilha uma tribo contigo", + dataReasonPinned: "Coincide com algo que fixaste", + dataReasonCv: "Coincide com as competências do teu CV", + dataReasonContent: "Relacionado com o que publicas", + dataReasonRelated: "Relacionado com os teus interesses", + dataReasonAlike: "Publicado por um habitante afim a ti", + dataReasonSupportsYou: "Apoia-te", + dataReasonMutual: "Apoio mútuo", + dataReasonRated: "Bem avaliado pela rede", + dataReasonNear: "Perto de ti", dataStatPeople: "CVs", dataStatConnected: "Ligados", dataStatSkills: "Habilidades", @@ -1439,7 +1687,7 @@ module.exports = { pollStatusLabel: "Estado", pollStatusOpen: "ABERTA", pollStatusClosed: "FECHADA", - pollSearchPlaceholder: "Procurar sondagens...", + pollSearchPlaceholder: "Pesquisar em Sondagens...", pollsNoItems: "Nenhuma sondagem encontrada.", viewPoll: "Ver Sondagem", pollChatCreate: "Criar Sondagem", @@ -1452,7 +1700,7 @@ module.exports = { blogFilterFavorites: "FAVORITOS", blogFilterTop: "TOP", blogCreateButton: "Criar Blog", - blogSearchPlaceholder: "Procurar blogs...", + blogSearchPlaceholder: "Pesquisar em Blogs...", blogComments: "Comentários", blogAllowComments: "Permitir comentários", blogCommentsClosed: "O autor fechou os comentários deste blog.", @@ -1465,12 +1713,11 @@ module.exports = { forumCreateButton: "Criar fórum", forumCreateSectionTitle: "Criar fórum", forumDescription: "Conversa abertamente com outros habitantes na tua rede.", - forumSearchPlaceholder: "Pesquisar fóruns...", + forumSearchPlaceholder: "Pesquisar em Fóruns...", forumFilterAll: "TODOS", forumFilterMine: "MEUS", forumFilterRecent: "RECENTES", forumFilterTop: "TOP", - forumDeleteButton: "Eliminar", forumParticipants: "participantes", forumMessages: "mensagens", forumMessageLabel: "Mensagem", @@ -1497,7 +1744,6 @@ module.exports = { imageFilterGallery: "GALERIA", imageCreateButton: "Carregar imagem", imageUpdateButton: "Atualizar", - imageDeleteButton: "Eliminar", imageAddFavoriteButton: "Adicionar favorito", imageRemoveFavoriteButton: "Remover favorito", imageFileLabel: "Seleciona um ficheiro de imagem (.jpeg, .jpg, .png, .gif)", @@ -1509,13 +1755,14 @@ module.exports = { imageDescriptionPlaceholder: "Opcional", imageNoFile: "Nenhum ficheiro de imagem fornecido", noImages: "Sem imagens disponíveis.", - imageSearchPlaceholder: "Pesquisar título, tags, descrição, autor...", + imageSearchPlaceholder: "Pesquisar em Imagens...", imageSortRecent: "Mais recentes", imageSortOldest: "Mais antigos", imageSortTop: "Mais votados", imageSearchButton: "Pesquisa", imageUpdatedAt: "Atualizado", imageNoMatch: "Nenhuma imagem corresponde à tua pesquisa.", + feedSearchPlaceholder: "Pesquisar em Feed...", feedTitle: "Feed", createFeedTitle: "Criar Feed", createFeedButton: "Enviar Feed!", @@ -1530,7 +1777,7 @@ module.exports = { alreadyRefeeded: "Já fizeste refeed disto.", activityTitle: "Atividade", yourActivity: "A tua atividade", - globalActivity: "Atividade global", + globalActivity: "Atividade", activityList: "Atividade", activityDesc: "Vê a atividade mais recente na tua rede.", allButton: "TODOS", @@ -1545,7 +1792,8 @@ module.exports = { meme: "Meme", typeRecent: "RECENTES", typeTop: "TOP", - typePost: "BLOGS", + typePost: "BLOG", + activityUpdateLabel: "ATUALIZAÇÃO", typeTribe: "TRIBOS", typeLarp: "L.A.R.P.", typeInhabitants: "HABITANTES", @@ -1570,19 +1818,19 @@ module.exports = { typeFeed: "FEEDS", typeContact: "CONTACTO", typePub: "PUB", - typeTombstone: "TOMBSTONE", + typeTombstone: "ELIMINADO", typeBanking: "BANCA", typeBankWallet: "BANCA/CARTEIRA", - typeBankClaim: "BANCA/UBI", + typeBankClaim: "BANCA/RBU", typeKarmaScore: "KARMA", - typeParliament: "PARLIAMENT", + typeParliament: "PARLAMENTO", typeSpread: "DIFUSÕES", - typeParliamentCandidature: "Parliament · Candidature", - typeParliamentTerm: "Parliament · Term", - typeParliamentProposal:"Parliament · Proposal", + typeParliamentCandidature: "Parlamento · Candidatura", + typeParliamentTerm: "Parlamento · Mandato", + typeParliamentProposal:"Parlamento · Proposta", typeParliamentRevocation:"Parlamento · Revogação", - typeParliamentLaw: "Parliament · New Law", - typeCourts: "COURTS", + typeParliamentLaw: "Parlamento · Nova lei", + typeCourts: "TRIBUNAIS", typeCourtsCase: "Tribunais · Caso", typeCourtsEvidence: "Tribunais · Prova", typeCourtsAnswer: "Tribunais · Resposta", @@ -1596,8 +1844,8 @@ module.exports = { deadline: "Prazo", status: "Estado", votes: "Votos", - totalVotes: "Total Votes", - voteTotalVotes: "Total Votes", + totalVotes: "Total de votos", + voteTotalVotes: "Total de votos", name: "Nome", skills: "Competências", tags: "Tags", @@ -1608,7 +1856,7 @@ module.exports = { location: "Localização", contentWarning: "Assunto", bankWalletConnected: "Carteira ECOin", - bankUbiReceived: "UBI recebido", + bankUbiReceived: "RBU recebido", bankTx: "Tx", bankEpochShort: "Época", bankingUserEngagementScore: "Pontuação KARMA", @@ -1620,30 +1868,30 @@ module.exports = { unfollowing: "A DEIXAR DE SEGUIR", pledged: "COMPROMETIDO", parliamentCandidatureId: "Candidature", - parliamentGovMethod: "Method", - parliamentVotesReceived: "Votes received", - parliamentMethodANARCHY: "Anarchy", + parliamentGovMethod: "Método", + parliamentVotesReceived: "Votos recebidos", + parliamentMethodANARCHY: "Anarquia", parliamentMethodVOTE: "Votação comunitária", parliamentMethodRANKED: "Escolha por ranking", parliamentMethodPLURALITY: "Pluralidade", parliamentMethodCOUNCIL: "Conselho", parliamentMethodJURY: "Júri", parliamentAnarchy: "ANARQUIA", - parliamentElectionsStart: "Elections start", - parliamentElectionsEnd: "Elections end", - parliamentCurrentLeader: "Winning candidature", + parliamentElectionsStart: "Início das eleições", + parliamentElectionsEnd: "Fim das eleições", + parliamentCurrentLeader: "Candidatura vencedora", parliamentProposalTitle: "Título", - parliamentOpenVote: "Open vote", + parliamentOpenVote: "Votação aberta", parliamentStatus: "Estado", parliamentLawQuestion: "Question", - parliamentLawMethod: "Method", - parliamentLawProposer: "Proposer", - parliamentLawEnacted: "Enacted at", + parliamentLawMethod: "Método", + parliamentLawProposer: "Proponente", + parliamentLawEnacted: "Promulgada em", parliamentLawVotes: "Votações", - createdAt: "Created at", - courtsCaseTitle: "Case", - courtsMethod: "Method", - courtsMethodJUDGE: "Judge", + createdAt: "Criado em", + courtsCaseTitle: "Caso", + courtsMethod: "Método", + courtsMethodJUDGE: "Juiz", courtsMethodJUDGES: "Painel de juízes", courtsMethodSINGLE_JUDGE: "Juiz único", courtsMethodJURY: "Júri", @@ -1651,42 +1899,42 @@ module.exports = { courtsMethodCOMMUNITY: "Comunidade", courtsMethodARBITRATION: "Arbitragem", courtsMethodVOTE: "Votação comunitária", - courtsAccuser: "Accuser", - courtsRespondent: "Respondent", + courtsAccuser: "Acusação", + courtsRespondent: "Defesa", courtsThStatus: "Estado", - courtsThAnswerBy: "Answer by", - courtsThEvidenceBy: "Evidence by", - courtsThDecisionBy: "Decision by", - courtsVotesNeeded: "Votes needed", - courtsVotesSlashTotal: "YES/TOTAL", - courtsOpenVote: "Open vote", - courtsAnswerTitle: "Answer", - courtsVerdictResult: "Result", - courtsVerdictOrders: "Orders", - courtsSettlementText: "Settlement", + courtsThAnswerBy: "Responder até", + courtsThEvidenceBy: "Provas até", + courtsThDecisionBy: "Decisão até", + courtsVotesNeeded: "Votos necessários", + courtsVotesSlashTotal: "SIM/TOTAL", + courtsOpenVote: "Votação aberta", + courtsAnswerTitle: "Resposta", + courtsVerdictResult: "Resultado", + courtsVerdictOrders: "Ordens", + courtsSettlementText: "Acordo", courtsSettlementAccepted: "Aceite", courtsJudge: "Juiz", - courtsThSupports: "Supports", - courtsFilterOpenCase: 'Open Case', - courtsCaseMediatorsPh: 'Mediator Oasis IDs, separated by comma', + courtsThSupports: "Apoios", + courtsFilterOpenCase: "Abrir caso", + courtsCaseMediatorsPh: "IDs de Oasis dos mediadores, separados por vírgulas", courtsMediatorsLabel: 'Mediadores', - courtsThCase: 'Case', - courtsThCreatedAt: 'Start date', - courtsPublicPrefLabel: 'Visibility after resolution', - courtsPublicPrefYes: 'I agree this case can be fully public', - courtsPublicPrefNo: 'I prefer to keep the details private', - courtsPublicPrefSubmit: 'Save visibility preference', - courtsMethodMEDIATION: 'Mediation', - courtsNoCases: 'No cases.', + courtsThCase: "Caso", + courtsThCreatedAt: "Data de início", + courtsPublicPrefLabel: "Visibilidade após a resolução", + courtsPublicPrefYes: "Aceito que este caso seja totalmente público", + courtsPublicPrefNo: "Prefiro manter os detalhes privados", + courtsPublicPrefSubmit: "Guardar preferência de visibilidade", + courtsMethodMEDIATION: "Mediação", + courtsNoCases: "Não há casos.", reportsTitle: "Relatórios", reportsDescription: "Gere e acompanha os relatos da tua rede.", - reportsSearchPlaceholder: "Pesquisar relatórios...", + reportsSearchPlaceholder: "Pesquisar em Relatórios...", reportsFilterAll: "TODOS", reportsFilterRecent: "RECENTES", reportsFilterTop: "TOP", reportsFilterMine: "MEUS", reportsFilterFeatures: "FUNCIONALIDADES", - reportsFilterBugs: "BUGS", + reportsFilterBugs: "ERROS", reportsFilterAbuse: "ABUSO", reportsFilterContent: "CONTEÚDO", reportsFilterConfirmed: "CONFIRMADO", @@ -1698,14 +1946,14 @@ module.exports = { reportsTitleLabel: "Título", reportsCategory: "Categoria", reportsCategoryFeatures: "Funcionalidades", - reportsCategoryBugs: "Bugs", + reportsCategoryBugs: "Erros", reportsCategoryAbuse: "Abuso", reportsCategoryContent: "Conteúdo", reportsUpdateButton: "Atualizar", reportsDeleteButton: "Eliminar", reportsMineSectionTitle: "Os teus relatórios", reportsFeaturesSectionTitle: "Pedidos de funcionalidades", - reportsBugsSectionTitle: "Bugs", + reportsBugsSectionTitle: "Erros", reportsAbuseSectionTitle: "Relatórios de abuso", reportsContentSectionTitle: "Problemas de conteúdo", reportsAllSectionTitle: "Relatórios", @@ -1728,7 +1976,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Relatórios em análise", reportsResolvedSectionTitle: "Relatórios resolvidos", reportsInvalidSectionTitle: "Relatórios inválidos", - reportsTemplateSectionTitle: 'Modelo de relatório', reportsBugTemplateTitle: 'Detalhes de reprodução (Bugs)', reportsFeatureTemplateTitle: 'Detalhes (Funcionalidades)', reportsAbuseTemplateTitle: 'Detalhes (Abuso)', @@ -1788,17 +2035,15 @@ module.exports = { tribeGallerySectionTitle: "Galeria de tribos", tribeRecentSectionTitle: "Tribos recentes", tribeTopSectionTitle: "Tribos populares", - tribeviewTribeButton: "Visitar tribo", - tribeviewSubTribeButton: "Visitar sub-tribo", tribeRootLabel: "RAIZ", tribeDescription: "Explora ou cria tribos na tua rede.", tribeMainTribeLabel: "TRIBO PRINCIPAL", tribeCreateButton: "Criar tribo", tribeUpdateButton: "Atualizar", tribeDeleteButton: "Eliminar", - tribeImageLabel: "Carregar mídia (máx: 50MB)", + tribeImageLabel: "Carregar mídia (máx: 75MB)", tribeTitleLabel: "Título", - searchTribesPlaceholder: "FILTRAR tribos POR NOME …", + searchTribesPlaceholder: "Pesquisar em Tribos...", tribeTitlePlaceholder: "Nome da tribo", tribeDescriptionLabel: "Descrição", tribeDescriptionPlaceholder: "Descreve esta tribo", @@ -1809,8 +2054,17 @@ module.exports = { tribeInviteMode: "Modo de convite", tribeModeLabel: "MODE", tribeIsAnonymousLabel: "ESTADO", - tribeMembersCount: "Members", + tribeMembersCount: "Membros", tribeEnterInvite: "INSERIR CONVITE", + subscriptionTitle: "Subscrições", + subscriptionOn: "Subscrito", + subscriptionOff: "Não subscrito", + subscriptionSubscribe: "Subscrever", + subscriptionUnsubscribe: "Cancelar subscrição", + subscriptionSubscribers: "Subscritores", + pmListLabel: "Enviar para", + pmListNone: "— Sem lista —", + pmListNoSubscribers: "Sem subscritores", tribeLeaveButton: "SAIR", tribePublic: "PÚBLICO", tribePrivate: "PRIVADO", @@ -1843,16 +2097,16 @@ module.exports = { tribeSectionVideos: "VÍDEOS", tribeSectionDocuments: "DOCUMENTOS", tribeSectionBookmarks: "MARCADORES", - tribeSectionMaps: "MAPS", + tribeSectionMaps: "MAPAS", tribeSectionPads: "PADS", tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionCalendars: "CALENDÁRIOS", + tribePadCreate: "Criar pad", + tribeChatCreate: "Criar chat", + tribeCalendarCreate: "Criar calendário", + tribePadsEmpty: "Ainda não há pads.", + tribeChatsEmpty: "Ainda não há chats.", + tribeCalendarsEmpty: "Ainda não há calendários.", tribeInhabitantsEmpty: "Ainda sem habitantes nesta tribo.", tribeEventCreate: "Criar evento", tribeEventsEmpty: "Ainda sem eventos.", @@ -1907,9 +2161,14 @@ module.exports = { tribeMediaTypeDocument: "Documento", tribeMediaTypeBookmark: "Favorito", tribeContentDelete: "ELIMINAR", + tribeReachTribe: "TRIBO", + tribeReachNotice: "OASIS mostra-o a todos os habitantes e CLEARNET também na web aberta. Abri-lo deixa uma cópia legível fora da tribo: voltar a TRIBO oculta-o, mas as cópias já partilhadas não podem ser apagadas.", + tribeReachPrivate: "O conteúdo de uma tribo privada só pode ficar dentro da tribo.", + tribeReachOpenedBy: "Aberto por", + tribeReachChange: "Onde é publicado", tribeInviteCodeText: "Código de convite: ", oasisVersionLabel: "Versão do Oasis", - tribeInviteCodeHint: "Partilha este código com quem queres convidar. Pode entrar via /invites → Tribes.", + tribeInviteCodeHint: "Partilha este código com quem queres convidar. Pode entrar via /invites.", tribeStatusOpen: "ABERTO", tribeStatusClosed: "FECHADO", tribeStatusInProgress: "EM PROGRESSO", @@ -1949,7 +2208,7 @@ module.exports = { tribeSearchMinChars: "Introduz pelo menos 2 caracteres para pesquisar.", agendaTitle: "Agenda", agendaDescription: "Aqui podes encontrar todos os teus itens atribuídos.", - agendaSearchPlaceholder: "Pesquisar na agenda...", + agendaSearchPlaceholder: "Pesquisar em Agenda...", agendaFilterAll: "TODOS", agendaFilterToday: "HOJE", agendaFilterUpcoming: "PRÓXIMOS", @@ -1970,6 +2229,7 @@ module.exports = { agendaNoItems: "Sem atribuições encontradas.", agendaDiscardButton: "Descartar", agendaRestoreButton: "Restaurar", + agendaRemoveButton: "Remover", agendaMembersCount: "Membros", agendaLocationLabel: "Localização", agendaYes: "SIM", @@ -1982,12 +2242,12 @@ module.exports = { agendareportStatus: "Estado", agendaTransferConcept: "Concept", agendaTransferAmount: "Montante", - agendaTransferDeadline: "Deadline", + agendaTransferDeadline: "Prazo", opinionsTitle: "Opiniões", shareYourOpinions: "Descobre e vota em opiniões na tua rede.", - author: "By", + author: "Por", voteNow: "Votar agora", - alreadyVoted: "You have already opined.", + alreadyVoted: "Já opinaste.", noOpinionsFound: "Sem opiniões encontradas.", TOPButton: "TOP", opinionsTotalCount: "Total de opiniões", @@ -2036,8 +2296,8 @@ module.exports = { pmComposeTitle: "Enviar uma mensagem", pmRecipients: "Destinatários", pmRecipientsHint: "Introduz Oasis IDs separados por vírgulas", - pmLimitsHint: "Até 7 destinatários por mensagem.", - pmTextPlaceholder: "Corpo limitado a 7000 caracteres.", + pmLimitsHint: "Até 7 habitantes por mensagem.", + pmTextPlaceholder: "Escreve a tua mensagem.", pmSubject: "Assunto", pmSubjectHint: "Introduz o assunto da mensagem", pmText: "Mensagem", @@ -2059,13 +2319,13 @@ module.exports = { pmCrypterCharsLabel: "caracteres", pmInvalidRecipients: "Oasis ID inválido (deveria ser como @…=.ed25519).", pmEncryptionLabel: "Cifragem", - private: "Privado", - privateDescription: "Your encrypted messages.", + private: "Caixa de entrada", + privateDescription: "Gere as tuas mensagens privadas.", privateInbox: "CAIXA DE ENTRADA", privateSent: "ENVIADAS", privateDelete: "Eliminar", pmCreateButton: "Escrever MP", - noPrivateMessages: "No private messages.", + noPrivateMessages: "Não há mensagens privadas.", pmFromLabel: "De:", pmToLabel: "Para:", pmNoSubject: "(sem assunto)", @@ -2095,94 +2355,98 @@ module.exports = { inboxProjectPledgedTitle: "Novo compromisso com o teu projeto", pmHasPledged: "comprometeu", pmToYourProject: "ao teu projeto", - blockchain: 'BlockExplorer', - blockchainDescription: 'Explore and visualize the blocks in the blockchain.', - blockchainNoBlocks: 'No blocks found in the blockchain.', + blockchain: "Explorador de blocos", + blockchainDescription: "Explora e visualiza os blocos da blockchain.", + blockchainNoBlocks: "Não foram encontrados blocos na blockchain.", blockchainStatsTitle: 'Estatísticas', blockchainStatsTypeBreakdown: 'Distribuição por tipo', blockchainStatsTopAuthors: 'Autores principais', blockchainStatsCount: 'Blocos', - blockchainBlockID: 'Block ID', - blockchainBlockAuthor: 'Author', + blockchainBlockID: "ID do bloco", + blockchainBlockAuthor: "Autor", blockchainBlockCarbon: 'Pegada de carbono', blockchainBlockType: 'Type', - blockchainBlockTimestamp: 'Timestamp', + blockchainBlockTimestamp: "Carimbo temporal", blockchainLatestDatagram: 'Último Datagrama', blockchainDatagram: "Datagrama", blockchainDetails: "Ver Detalhes do Bloco", blockchainViewBlockexplorer: "Ver no Blockexplorer", - blockchainBack: 'Back to Blockexplorer', + blockchainBack: "Voltar ao explorador de blocos", blockchainContentDeleted: "Este conteúdo foi eliminado (tombstone)", visitContent: "Visitar Conteúdo", favoriteAdd: "Fixar nos Favoritos", pmContentTooltip: "Enviar Mensagem Privada", + phoneCallTooltip: "Fazer uma Chamada", favoriteRemove: "Remover dos Favoritos", reportContent: "Denunciar Conteúdo", - banking: 'Banking', - bankingTitle: 'Banking', - bankingDescription: "A economia ECOin: saldo, RBU, taxas e valor da indústria.", - bankOverview: 'Overview', - bankEpochs: 'Epochs', - bankRules: 'Rules', - pending: 'Pending', - closed: 'Closed', - bankViewTx: 'View Tx', - bankClaimNow: 'Claim now', - bankClaimUBI: 'Reivindicar RBU!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', - bankEpoch: 'Epoch', - bankPool: 'Pool (this epoch)', - bankWeightsSum: 'Sum of weights', - bankNoAllocations: 'No allocations found.', - bankNoEpochs: 'No epochs found.', - bankEpochAllocations: 'Epoch allocations', - bankAllocId: 'Allocation ID', + banking: "Banca", + bankingTitle: "Banca", + bankingDescription: "A economia coletiva de ECOin.", + bankOverview: "Resumo", + bankEpochs: "Épocas", + bankRules: "Regras", + pending: "Pendentes", + closed: "Fechadas", + bankClaimNow: "Reivindicar agora", + bankClaimUBI: 'Reivindicar RBU', + bankClaimAndPay: "Reivindicar e pagar", + bankClaimedPending: "Reivindicação pendente...", + bankStatusClaimed: "Reivindicado", + bankStatusExpired: "Expirado", + bankPubOnly: "Operação só para PUBs", + bankEpoch: "Época", + bankPool: "Fundo (esta época)", + bankWeightsSum: "Soma dos pesos", + bankNoAllocations: "Não foram encontradas atribuições.", + bankNoEpochs: "Não foram encontradas épocas.", + bankEpochAllocations: "Atribuições da época", + bankAllocId: "ID da atribuição", bankAllocDate: 'Date', bankAllocConcept: 'Concept', - bankAllocFrom: 'From', - bankAllocTo: 'To', - bankAllocAmount: 'Amount', + bankAllocFrom: "De", + bankAllocTo: "Para", + bankAllocAmount: "Montante", bankAllocStatus: 'Status', - bankEpochId: 'Epoch ID', - bankRuleHash: 'Rules Snapshot Hash', - bankViewEpoch: 'View Epoch', + bankEpochId: "ID da época", + bankRuleHash: "Hash do instantâneo das regras", + bankViewEpoch: "Ver época", bankYourIndustryBalance: "A tua parte da indústria", bankYourUbiMonth: "O teu RBU (este mês)", - bankYourFundsMonth: "Os teus fundos (este mês)", - bankIndustryBalance: "Produção industrial (estimada)", - bankUserBalance: 'Your Balance', - bankAddresses: 'Addresses', - bankNoAddresses: 'No addresses found.', + bankYourFundsMonth: "Fundos totais", + bankIndustryBalance: "Produção industrial", + bankUserBalance: "O teu saldo", + bankAddresses: "Endereços", + bankNoAddresses: "Não foram encontrados endereços.", bankUser: 'Oasis ID', - bankAddress: 'Address', - bankAddAddressTitle: 'Add ECOIN address', + bankAddress: "Endereço", + bankAddAddressTitle: "Adicionar endereço ECOin", bankAddAddressUser: 'Oasis ID', - bankAddAddressAddress: 'ECOIN Address', - bankAddAddressSave: 'Save', - bankAddressAdded: 'Address added', - bankAddressUpdated: 'Address updated', - bankAddressExists: 'Address already exists', - bankAddressInvalid: 'Invalid address', - bankAddressDeleted: 'Address deleted', - bankAddressNotFound: 'Address not found', + bankAddAddressLabel: "Etiqueta (opcional)", + bankAddAddressLabelPlaceholder: "Carteira do Bob, loja da Alice…", + bankAddAddressUserOptional: "Oasis ID (opcional)", + bankAddressUnnamed: "Endereço sem nome", + bankAddAddressAddress: "Endereço ECOin", + bankAddAddressSave: "Guardar", + bankAddressAdded: "Endereço adicionado", + bankAddressUpdated: "Endereço atualizado", + bankAddressExists: "O endereço já existe", + bankAddressInvalid: "Endereço inválido", + bankAddressDeleted: "Endereço eliminado", + bankAddressNotFound: "Endereço não encontrado", bankAddressForbidden: 'Só podes configurar o teu próprio endereço de cobrança', - bankAddressTotal: 'Total Addresses', - bankAddressSearch: 'Search @inhabitant or address', + bankAddressTotal: "Total de endereços", + bankAddressSearch: "Procurar @habitante ou endereço", bankAddressActions: 'Actions', - bankAddressDelete: 'Delete', + bankAddressDelete: "Eliminar", bankAddressSource: 'Source', - bankAddressDeleteConfirm: 'Delete this address?', - search: 'Search!', + bankAddressDeleteConfirm: "Eliminar este endereço?", + search: "Procurar!", bankLocal: 'Local', - bankFromOasis: 'Oasis', + bankFromOasis: "Rede", pubIdLabel: "PUB ID", bankUbiAvailableNo: "SEM FUNDOS!", bankUbiAvailableOk: "DISPONÍVEL!", - bankUbiAvailability: "RBU (rede)", + bankUbiAvailability: "RBU", bankAlreadyClaimedThisMonth: "Já reclamado este mês", bankUbiThisMonth: "RBU (este mês)", bankUbiLastClaimed: "RBU (última reclamada)", @@ -2212,6 +2476,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Cifrado em trânsito", shopAddFavorite: "Adicionar Favorito", shopRemoveFavorite: "Remover Favorito", shopOpen: "ABERTA", @@ -2219,6 +2484,13 @@ module.exports = { shopMakePrivate: "TORNAR PRIVADA (E2E)", shopMakePublic: "TORNAR PÚBLICA", shopProducts: "Produtos", + shopFilterAll: "TODAS", + shopFilterRecent: "RECENTES", + shopFilterMine: "MINHAS", + shopFilterTop: "TOP", + shopFilterProducts: "PRODUTOS", + shopFilterPrices: "PREÇOS", + shopFilterFavorites: "FAVORITOS", shopProductAdd: "Adicionar Produto", shopProductTitle: "Produto", shopProductUpdate: "Atualizar Produto", @@ -2246,7 +2518,7 @@ module.exports = { marketVisitItem: "VER ARTIGO", shopStatus: "ESTADO", shopCreatedAt: "CRIADO", - shopSearchPlaceholder: "Pesquisar lojas...", + shopSearchPlaceholder: "Pesquisar em Lojas...", shopUrl: "URL", shopLocation: "Localização", shopTags: "Tags", @@ -2256,22 +2528,24 @@ module.exports = { shopTitlePlaceholder: "Nome da sua loja", shopDescriptionPlaceholder: "Descricao detalhada da sua loja", shopLocationPlaceholder: "Cidade, Pais", - shopTagsPlaceholder: "tag1, tag2, tag3", + shopTagsPlaceholder: "Introduz tags separadas por vírgulas", mapTitlePlaceholder: "Titulo do mapa", shopProductFeatured: "Produto em Destaque", - shopSendToMarket: "Send to Market", + shopSendToMarket: "Enviar para o mercado", typeShop: "LOJAS", typeShopProduct: "PRODUTO DE LOJA", - bankExchange: 'Exchange', - bankExchangeCurrentValue: 'ECOin Value (1h)', - bankTotalSupply: 'ECOin Total Supply', - bankEcoinHours: "Equivalência ECOin em tempo", - bankHoursOfWork: 'hours', + bankExchange: "Câmbio", + bankExchangeCurrentValue: "Valor de ECOin (1h)", + bankTotalSupply: "Oferta ECOin total", + bankPubsSupply: "Oferta ECOin da reserva dos PUBs", + bankHoldingSupply: "Oferta ECOin fora do RBU", + bankEcoinHours: "Equivalência ECOin em tempo (1 ECO)", + bankHoursOfWork: "horas", bankUnitMs: "ms", bankUnitSeconds: "segundos", bankUnitMinutes: "minutos", bankUnitDays: "dias", - bankExchangeNoData: 'No data available', + bankExchangeNoData: "Não há dados disponíveis", bankInflation: 'ECOin Inflation (anual)', bankInflationMonthly: 'ECOin Inflation (mensual)', bankExchangeChartTitle: 'Progressão do valor de ECOin ao longo do tempo', @@ -2279,22 +2553,24 @@ module.exports = { bankExchangeChartSupply: 'Oferta', bankExchangeChartInflation: 'Inflação %', bankExchangeChartEmpty: 'Ainda não há amostras suficientes — volte mais tarde', - bankCurrentSupply: 'ECOin Current Supply', - bankingSyncStatus: 'ECOin Status', - bankingSyncStatusSynced: 'Synced', - bankingSyncStatusOutdated: 'Outdated', - statsTitle: 'Statistics', + bankCurrentSupply: "Oferta ECOin atual", + bankingSyncStatus: "Estado de ECOin", + bankingSyncStatusSynced: "Sincronizado", + bankingSyncStatusOutdated: "Não sincronizado", + statsTitle: "Estatísticas", statistics: "Estatísticas", statsDescription: "Descobre estatísticas sobre a tua rede.", - ALLButton: "ALL", - MINEButton: "MINE", - TOMBSTONEButton: "TOMBSTONES", + ALLButton: "TUDO", + RECENTButton: "RECENTES", + MINEButton: "MEUS", + TOMBSTONEButton: "ELIMINADOS", statsCreatedAt: "Criado em", statsYourContent: "Conteúdo", statsYourOpinions: "Opiniões", statsNetworkContent: "Conteúdo", statsNetworkOpinions: "Opiniões", - statsEcoWalletLabel: "Carteira ECOIN", + statsEcoWalletLabel: "Carteira ECOin", + walletAddressCopyHint: "Clica para selecionar e copiar", statsEcoWalletNotConfigured: "Não configurada!", statsBookmark: "Marcadores", statsEvent: "Eventos", @@ -2316,6 +2592,8 @@ module.exports = { statsTransfer: "Transferências", statsAiExchange: "IA", statsPUBs: 'PUBs', + statsSyncedPeers: "Pares", + statsTotalLabel: "Total", statsPost: "Publicações", statsSize: "Total (tamanho)", statsBlockchainSize: "Blockchain (tamanho)", @@ -2323,7 +2601,7 @@ module.exports = { statsActivity7d: "Atividade (últimos 7 dias)", statsActivity7dTotal: "Total 7 dias", statsActivity30dTotal: "Total 30 dias", - day: "Day", + day: "Dia", messages: "Mensagens", statsProject: "Projetos", statsProjectsTitle: "Projetos", @@ -2367,17 +2645,23 @@ module.exports = { aiPrompt: "Fornece uma resposta informativa e precisa.", aiConfiguration: "Definir prompt", aiPromptUsed: "Prompt", - aiClearHistory: "Limpar histórico de conversa", - aiServerError: "A IA não conseguiu responder. Por favor, tenta novamente.", - aiInputPlaceholder: "What is Oasis?", + aiClearHistory: "Limpar histórico", + aiServerError: "Não consigo responder bem. Tenta de novo.", + aiStatusLoading: "42 está a carregar o modelo. Pergunta de novo dentro de momentos.", + aiThinking: "A pensar", + aiWaiting: "À espera", + aiStatusMissingModel: "O modelo de IA não está instalado: falta src/AI/oasis-42-1-chat.Q4_K_M.gguf.", + aiExportFineTuning: "Exportar Fine-Tuning", + aiFactsTitle: "Dados da tua rede", + aiInputPlaceholder: "O que é o Oasis?", typeAiExchange: "IA", aiApproveTrain: "Adicionar ao treino coletivo", aiRejectTrain: "Não treinar", aiTrainApproved: "Aprovado para treino", aiTrainRejected: "Rejeitado para treino", aiSnippetsUsed: "Excertos usados", - aiSnippetsLearned: "Snippets learned", - statsAITraining: "AI training", + aiSnippetsLearned: "Fragmentos aprendidos", + statsAITraining: "Treino de IA", aiCustomAnswerPlaceholder: "Escreve a tua resposta personalizada…", marketMineSectionTitle: "Os teus itens", marketCreateSectionTitle: "Criar item", @@ -2397,7 +2681,7 @@ module.exports = { marketFilterDiscarded: "DESCARTADO", marketFilterRecent: "RECENTES", marketFilterMyBids: "LICITAÇÕES", - marketCreateButton: "Criar item", + marketCreateButton: "Vender algo", marketItemType: "Tipo", marketItemTitle: "Título", marketItemAvailable: "Prazo", @@ -2424,8 +2708,8 @@ module.exports = { marketItemSeller: "Vendedor", marketNoItems: "Ainda sem itens disponíveis.", marketYourBid: "A tua licitação", - marketCreateFormImageLabel: "Carregar mídia (máx: 50MB)", - marketSearchPlaceholder: "Pesquisar título ou tags", + marketCreateFormImageLabel: "Carregar mídia (máx: 75MB)", + marketSearchPlaceholder: "Pesquisar em Mercado...", marketMinPriceLabel: "Preço mínimo", marketMaxPriceLabel: "Preço máximo", marketSortRecent: "Mais recentes", @@ -2491,8 +2775,7 @@ module.exports = { housingSetClosed: "Tornar indisponível", housingCreateButton: "Criar lugar", housingUpdateButton: "Atualizar", - housingDeleteButton: "Apagar", - housingSearchPlaceholder: "Procurar lugares...", + housingSearchPlaceholder: "Pesquisar em Habitação...", housingSearchButton: "Procurar", housingMinPrice: "Preço mín.", housingMaxPrice: "Preço máx.", @@ -2548,7 +2831,7 @@ module.exports = { jobLocationPresencial: "Presencial", jobLocationRemote: "Remoto", jobVacantsPlaceholder: "Número de posições", - jobImage: "Carregar mídia (máx: 50MB)", + jobImage: "Carregar mídia (máx: 75MB)", jobTasks: "Tarefas", jobType: "Tipo de emprego", jobTime: "Horário", @@ -2559,7 +2842,7 @@ module.exports = { createJobButton: "Publicar emprego", noJobsFound: "Sem ofertas de emprego encontradas.", jobAuthor: "Por", - jobTypeFreelance: "Freelancer", + jobTypeFreelance: "Independente", jobTypeSalary: "Empregado", jobTypeExchange: "Troca de horas", jobTypeEXCHANGE: "TROCA DE HORAS", @@ -2568,14 +2851,13 @@ module.exports = { jobsHoursOffered: "Horas oferecidas", jobsHoursRequested: "Horas pedidas em troca", jobsExchangeSkill: "Habilidade pedida em troca", - jobTimePartial: "Part-time", - jobTimeComplete: "Full-time", - jobsDeleteButton: "ELIMINAR", + jobTimePartial: "Tempo parcial", + jobTimeComplete: "Tempo inteiro", jobsUpdateButton: "ATUALIZAR", jobsFilterApplied: "CANDIDATADO", jobsAppliedTitle: "As minhas candidaturas", jobsAppliedBadge: "Candidatado", - jobsSearchPlaceholder: "Pesquisar título, tags, descrição...", + jobsSearchPlaceholder: "Pesquisar em Empregos...", jobsMinSalaryLabel: "Salário mínimo", jobsMaxSalaryLabel: "Salário máximo", jobsSortRecent: "Mais recentes", @@ -2584,18 +2866,18 @@ module.exports = { jobsSearchButton: "Pesquisa", jobsApplicants: "Candidatos", jobsUpdatedAt: "Atualizado", - jobSetOpen: "Set open", + jobSetOpen: "Marcar como aberto", jobsTagsLabel: "Tags", noJobsMatch: "Nenhum emprego corresponde à tua pesquisa.", - industrySearchPlaceholder: "Procurar fábricas…", + industrySearchPlaceholder: "Pesquisar em Indústria...", industryAllSectors: "Todos os setores", industryVisitButton: "Visitar indústria", industryAdvanceTo: "Definir para", industryAmount: "Quantia", industryApproveBuild: "Aprovar", - industryBlueprint: "Blueprint", - industryBlueprints: "Blueprints", - industryBuild: "Build", + industryBlueprint: "Plano", + industryBlueprints: "Planos", + industryBuild: "Produção", industryBuildNotes: "Notas", industryBuildStart: "Data de início", industryVoteUpdateButton: "Votar atualização", @@ -2603,7 +2885,7 @@ module.exports = { industryRevokeVote: "Revogar", industryTimeLeft: "Tempo restante", industryBuildEnd: "Data de fim", - industryBuilds: "Builds", + industryBuilds: "Produções", industryBuildTitle: "Título", industryContribute: "Contribuir", industryContributeButton: "Contribuir", @@ -2661,7 +2943,6 @@ module.exports = { industryBuildStatus_FAILED: "FALHADO", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Um emprego corresponde ao teu currículo", jobsBotMatchIntro: "Um emprego corresponde ao teu currículo.", jobsBotMatchJob: "Emprego", @@ -2688,7 +2969,7 @@ module.exports = { industryRulesIntro: "Industry permite aos habitantes possuir coletivamente os meios de produção: uma Fábrica é uma oficina da rede que ninguém possui de forma privada.", industryRulesSteward: "O fundador é o administrador — um guardião, não um dono: não pode vender, privatizar nem dissolver a Fábrica sozinho.", industryRulesMembership: "A política de adesão define como se entra: Aberta (na hora), Por voto (admitido pelo voto dos membros) ou Apenas convite (um membro deve convidar primeiro).", - industryRulesQuorum: "O quórum é o número mínimo de membros que devem votar para uma decisão contar, para que uma Fábrica pequena não seja decidida por uma só pessoa.", + industryRulesQuorum: "O quórum é o número mínimo de membros que devem votar para uma decisão contar, para que uma Fábrica pequena não seja decidida por um só habitante.", industryRulesMajority: "A maioria é a fração de votos necessária (0,5 = metade, 1 = unanimidade); uma decisão passa quando os SIM atingem pelo menos max(quórum, membros × maioria).", industryRulesDecisions: "Os membros votam para admitir novos, aprovar builds e dissolver a Fábrica; o administrador não pode ignorar estas decisões coletivas.", industryRulesBlueprints: "Os Blueprints são receitas livres (COPYLEFT) — as entradas (materiais, horas de trabalho, competências) e a saída (um bem físico ou digital) — e qualquer um pode forká-las.", @@ -2707,13 +2988,14 @@ module.exports = { industryPauseVotes: "Votos de estado", industryTitle: "Indústria", industryDescription: "Módulo para gerir os meios de produção de forma coletiva.", - typeIndustryBuild: "BUILD", - typeIndustryBlueprint: "BLUEPRINT", + typeIndustryBuild: "PRODUÇÃO", + typeIndustryBlueprint: "PLANO", typeIndustryAllocation: "DISTRIBUIÇÃO", typeIndustry: "INDÚSTRIA", modulesIndustryLabel: "Indústria", modulesIndustryDescription: "Módulo para gerir os meios de produção de forma coletiva.", industryFilterAll: "TODAS", + industryFilterRecent: "RECENTES", industryFilterMember: "MEMBRO", industryFilterBlueprints: "PLANOS", industryFilterBuilds: "PRODUÇÕES", @@ -2722,6 +3004,7 @@ module.exports = { industryFilterPaused: "PAUSADAS", industryFilterDissolved: "DISSOLVIDAS", industryAllTitle: "Indústria", + industryRecentTitle: "Fábricas recentes", industryMemberTitle: "Fábricas de que faço parte", industryMineTitle: "Minhas fábricas", industryActiveTitle: "Em funcionamento", @@ -2769,7 +3052,7 @@ module.exports = { industryPolicy_invite: "Apenas convite", projectsTitle: "Projetos", projectsDescription: "Cria, financia e acompanha projetos comunitários na tua rede.", - projectSearchPlaceholder: "Pesquisar projetos...", + projectSearchPlaceholder: "Pesquisar em Projetos...", projectCreateProject: "Criar projeto", projectCreateButton: "Criar projeto", projectUpdateButton: "ATUALIZAR", @@ -2792,7 +3075,7 @@ module.exports = { projectRecentTitle: "Projetos recentes", projectTopTitle: "Mais financiados", projectTitlePlaceholder: "Nome do projeto", - projectImage: "Carregar mídia (máx: 50MB)", + projectImage: "Carregar mídia (máx: 75MB)", projectDescription: "Descrição", projectDescriptionPlaceholder: "Conta a história e os objetivos…", projectGoal: "Objetivo (ECO)", @@ -2833,7 +3116,7 @@ module.exports = { projectMilestoneOpen: "aberto", projectMilestoneDone: "concluído", projectMilestoneDue: "prazo", - projectMilestoneMarkDone: "Mark as Done", + projectMilestoneMarkDone: "Marcar como concluído", projectMilestoneTitlePlaceholder: "Introduz o título do marco", projectMilestoneDescriptionPlaceholder: "Introduz a descrição para este marco", projectMilestoneDescription: "Descrição do marco", @@ -2864,15 +3147,13 @@ module.exports = { modulesModuleName: "Nome", modulesModuleDescription: "Descrição", modulesModuleStatus: "Estado", - modulesTotalModulesLabel: "Módulos carregados", + modulesTotalModulesLabel: "Total", modulesEnabledModulesLabel: "Ativado", modulesDisabledModulesLabel: "Desativado", modulesInvitesLabel: "Convites", modulesInvitesDescription: "Módulo para gerir e aplicar códigos de convite.", modulesWalletLabel: "Carteira", modulesWalletDescription: "Módulo para gerir os teus ativos digitais (ECOin).", - modulesLegacyLabel: "Chaves", - modulesLegacyDescription: "Módulo para gerir o teu segredo (chave privada) de forma rápida e segura.", modulesCipherLabel: "Encriptação", modulesCipherDescription: "Módulo para encriptar e decifrar o teu texto simetricamente (com senha partilhada).", modulesBookmarksLabel: "Marcadores", @@ -2925,7 +3206,7 @@ module.exports = { modulesMelodyDescription: "Módulo para gerar e partilhar o \"som\" da tua blockchain.", modulesAgendaLabel: "Agenda", modulesAgendaDescription: "Módulo para gerir todos os teus itens atribuídos.", - modulesAILabel: "AI", + modulesAILabel: "IA", modulesAIDescription: "Módulo para conversar com um LLM chamado '42'.", modulesForumLabel: "Fóruns", modulesForumDescription: "Módulo para descobrir e gerir fóruns.", @@ -2934,20 +3215,23 @@ module.exports = { modulesProjectsLabel: "Projetos", modulesProjectsDescription: "Módulo para explorar, financiar coletivamente e gerir projetos.", modulesBankingLabel: "Banca", - modulesBankingDescription: "Módulo para determinar o valor real do ECOIN e distribuir um UBI usando o tesouro comum.", + modulesBankingDescription: "Módulo para gerir o tesouro comum: valor do ECOIN, impostos e RBU.", modulesFavoritesLabel: "Favoritos", modulesFavoritesDescription: "Módulo para gerir o teu conteúdo favorito.", fileTooLargeTitle: "Ficheiro demasiado grande", - fileTooLargeMessage: "O ficheiro excede o tamanho máximo permitido (50 MB). Por favor, seleciona um ficheiro mais pequeno.", + fileTooLargeMessage: "O ficheiro excede o tamanho máximo permitido (75 MB). Por favor, seleciona um ficheiro mais pequeno.", goBack: "Voltar", + errorDismiss: "OK", errorPageTitle: "Algo correu mal", + errorNoAllocation: "Nenhuma alocação encontrada para esta reivindicação.", + errorInvalidClaim: "Esta alocação não pode ser reivindicada.", visibilityLabel: "Visibilidade", + clearnetPrivateConflict: "Um conteúdo privado, oculto, fechado, só por convite ou pago não pode ser publicado na CLEARNET. Escolhe OASIS ou torna-o público.", visibilityPublic: "Pública", visibilityHidden: "Oculta", visibilityMakePublic: "Tornar pública", visibilityMakeHidden: "Ocultar", invitesInhabitantsTitle: "Habitantes", - invitesInhabitantsFollow: "Seguir", aiNavPlaceholder: "Descreve o que precisas...", aiNavDisabled: "A navegação por IA está desativada.", aiNavResultsTitle: "Sugestões AINav", @@ -2958,24 +3242,24 @@ module.exports = { modulesAINavDescription: "Módulo para consultas em linguagem natural sobre o conteúdo da rede.", directConnectDescription: "Conecta-te diretamente a um par inserindo o endereço IP, porta e chave pública.", peerHost: "IP", - peerPort: "Porta (predefinida: 8008)", + peerPort: "Porta", peerPublicKey: "Chave pública (@...ed25519)", connectAndFollow: "Conectar", modulesPresetTitle: "Configurações Comuns", - workflowsTitle: "Workflows", + workflowsTitle: "Fluxos de trabalho", workflowsDescription: "Um workflow define o tema e quais módulos estão ativos.", workflowsSet: "Aplicar workflow", workflow_default: "Predefinido", workflow_jobs: "Emprego", - workflow_ruling: "Governo", - workflow_politics: "Política", workflow_social: "Social", workflow_business: "Negócios", workflow_night: "Noite", + workflow_kids: "Crianças", + workflow_activists: "Ativismo", workflow_mobile: "Móvel", statsCarbonFootprintTitle: "Pegada de carbono", statsCarbonTombstone: "Pegada do tombstoning", - uploadMedia: "Carregar mídia (máx: 50MB)", + uploadMedia: "Carregar mídia (máx: 75MB)", courtsRespondentInvalid: "Demandado inválido", feedDetailTitle: "Detalhe do feed", @@ -2984,7 +3268,7 @@ module.exports = { noComments: "Ainda sem comentários.", mapsLabel: "Mapas", mapTitle: "Mapas", - mapDescription: "Explore e gerencie mapas offline na sua rede.", + mapDescription: "Gerencie e compartilhe mapas offline na sua rede.", mapMineSectionTitle: "Seus Mapas", mapCreateSectionTitle: "Criar Mapa", mapUpdateSectionTitle: "Atualizar Mapa", @@ -3013,7 +3297,7 @@ module.exports = { mapTypeOpen: "ABERTO", mapTypeClosed: "FECHADO", mapTagsLabel: "Tags", - mapTagsPlaceholder: "Insira tags separadas por vírgulas", + mapTagsPlaceholder: "Introduz tags separadas por vírgulas", mapUrlLabel: "URL do mapa", mapMarkersTitle: "Marcadores", mapMarkerDefault: "Marcador", @@ -3024,9 +3308,22 @@ module.exports = { markerImageLabel: "Imagem do Marcador", mapAddMarkerTitle: "Adicionar Marcador", mapAddMarkerButton: "Adicionar Marcador", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Limpar marcador", mapApplyZoom: "Aplicar Zoom", - mapSearchPlaceholder: "Buscar descrição, tags, autor...", + mapPlaceSearchPlaceholder: "Procurar um lugar ou país...", + mapNoPlaces: "Nenhum lugar encontrado.", + mapClusterTitle: "marcadores aqui, clique para aproximar", + mapDeleteMarker: "Eliminar marcador", + mapZoomIn: "Aproximar", + mapZoomOut: "Afastar", + mapFitMarkers: "Ajustar a todos os marcadores", + mapPanLabel: "Mover o mapa", + mapCenterHere: "Centrar aqui", + mapNear: "perto de", + mapPickedSpot: "Ponto escolhido", + mapPickHint: "Clique no mapa para escolher um ponto.", + mapClearPick: "Limpar", + mapSearchPlaceholder: "Pesquisar em Mapas...", mapSearchButton: "Buscar", mapUpdatedAt: "Atualizado", mapNoMatch: "Nenhum mapa corresponde à sua busca.", @@ -3037,6 +3334,465 @@ module.exports = { typeMapMarker: "MARCADOR DE MAPA", modulesMapLabel: "Mapas", modulesMapDescription: "Módulo para gerenciar e compartilhar mapas offline.", + emergenciesTitle: "Emergências", + modulesEmergenciesLabel: "Emergências", + modulesEmergenciesDescription: "Módulo para lançar, confirmar e seguir emergências na rede.", + emergenciesDescription: "Módulo para lançar, confirmar e seguir emergências na rede.", + emergencyCreate: "Criar emergência", + emergencyUpdate: "Atualizar", + emergencyDelete: "Apagar", + emergencyTitleLabel: "Título", + emergencyTitlePlaceholder: "O que está a acontecer?", + emergencyTextLabel: "Detalhes", + emergencyTextPlaceholder: "Onde, desde quando, o que fazer...", + emergencyCategoryLabel: "Categoria", + emergencyCategoryWeather: "METEOROLOGIA", + emergencyCategoryInfrastructure: "INFRAESTRUTURA", + emergencyCategoryHealth: "SAÚDE", + emergencyCategorySecurity: "SEGURANÇA", + emergencyCategoryLost: "PERDIDOS E ACHADOS", + emergencyCategoryNeighborhood: "VIZINHANÇA", + emergencyExpiresLabel: "Expira em", + emergencyExpires1d: "1 dia", + emergencyExpires3d: "3 dias", + emergencyExpires7d: "1 semana", + emergencyExpires30d: "1 mês", + emergencyExpiresNone: "Indefinido", + emergencyExpiresKeep: "Manter a data atual", + emergencyTagsLabel: "Etiquetas", + emergencyTagsPlaceholder: "Introduz tags separadas por vírgulas", + emergencyFilterAll: "TODOS", + emergencyFilterMine: "MEUS", + emergencyFilterRecent: "RECENTES", + emergencySeverityUnverified: "NÃO VERIFICADO", + emergencySeverityLow: "BAIXA", + emergencySeverityMedium: "MÉDIA", + emergencySeverityHigh: "ALTA", + emergencyStatusActive: "ATIVO", + emergencyStatusResolved: "RESOLVIDO", + emergencyStatusExpired: "EXPIRADO", + emergencyConfirmations: "Confirmações", + emergencyConfirmButton: "Confirmar", + emergencyConfirmed: "Confirmaste esta emergência", + emergencyResolveButton: "Resolver", + emergencyReopenButton: "Reabrir", + emergencyGenerateReport: "Gerar relatório", + emergencyUpdateEdited: "Editada", + emergencyLastUpdate: "ÚLTIMA ATUALIZAÇÃO", + emergencyUpdatesTitle: "Últimas atualizações:", + emergencyNoUpdates: "Ainda sem atualizações.", + emergencyAddUpdate: "Publicar atualização", + emergencyUpdatePlaceholder: "O que mudou?", + emergencyNoItems: "Nenhuma emergência encontrada.", + emergencyExpiresAt: "Expira", + emergencyBannerLabel: "Emergência ativa", + emergencyCreateSectionTitle: "Criar emergência", + emergencyUpdateSectionTitle: "Atualizar emergência", + emergencySearchPlaceholder: "Pesquisar em emergências...", + typeEmergency: "EMERGÊNCIAS", + typeEmergencyConfirm: "CONFIRMAÇÃO DE EMERGÊNCIA", + typeEmergencyUpdate: "ATUALIZAÇÃO DE EMERGÊNCIA", + statsEmergency: "Emergências", + statsEmergencyConfirm: "Confirmações de emergências", + statsEmergencyUpdate: "Atualizações de emergências", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "Uma emergência que segues tem uma atualização.", + emergencyBotResolvedTitle: "Uma emergência que segues foi resolvida.", + dataKindEmergencies: "EMERGÊNCIAS", + mailingTitle: "Listas de correio", + modulesMailingLabel: "Listas de correio", + modulesMailingDescription: "Descobre e gere listas de correio na tua rede.", + modulesPhoneLabel: "Telefone", + modulesPhoneDescription: "Módulo para ligar a outros habitantes com videochamadas cifradas de ponta a ponta.", + mailingDescription: "Descobre e gere listas de correio na tua rede.", + mailingCreate: "Criar lista de correio", + mailingUpdate: "Atualizar", + mailingArchive: "Arquivar", + mailingActivate: "Ativar", + mailingCreateSectionTitle: "Criar lista de correio", + mailingUpdateSectionTitle: "Atualizar lista de correio", + mailingTitleLabel: "Título", + mailingTitlePlaceholder: "Nome da lista de correio", + mailingDescriptionLabel: "Descrição", + mailingDescriptionPlaceholder: "Sobre o que é esta lista?", + mailingTypeLabel: "Tipo de lista de correio", + mailingTypeOpen: "ABERTA", + mailingTypeClosed: "FECHADA", + mailingStatusLabel: "Estado", + mailingStatusActive: "ATIVA", + mailingStatusArchived: "ARQUIVADA", + mailingMembersLabel: "Convidar habitantes", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Subscritores", + mailingTagsLabel: "Etiquetas", + mailingTagsPlaceholder: "Introduz tags separadas por vírgulas", + mailingFilterAll: "TODAS", + mailingFilterMine: "MINHAS", + mailingFilterSubscribed: "SUBSCRITAS", + mailingFilterRecent: "RECENTES", + mailingHistoryDate: "DATA", + mailingHistoryThreads: "TÓPICOS", + mailingHistoryInhabitants: "HABITANTES", + mailingHistorySearchPlaceholder: "Pesquisar no histórico...", + mailingNoMessages: "Ainda não há mensagens.", + mailingNoItems: "Nenhuma lista de correio encontrada.", + mailingSearchPlaceholder: "Pesquisar em listas de correio...", + mailingWriteButton: "Escrever mensagem", + mailingWriteTitle: "Escrever à lista", + mailingReplyTitle: "Responder à lista", + mailingSend: "Enviar", + mailingTextPlaceholder: "A tua mensagem chega a todos os participantes da lista.", + mailingParticipants: "Participantes", + inboxFilterMailing: "Listas de correio", + mailingReplyPlaceholder: "Responder à lista...", + mailingReplyToList: "Responder à lista", + typeMailingList: "LISTA DE CORREIO", + statsMailingList: "Listas de correio", + mailingListLabel: "LISTA DE CORREIO", + logisticsTitle: "Logística", + modulesLogisticsLabel: "Logística", + modulesLogisticsDescription: "Descobre e gere planos logísticos na tua rede.", + logisticsDescription: "Descobre e gere planos logísticos na tua rede.", + logisticsCreate: "Criar rota", + logisticsUpdate: "Atualizar", + logisticsClose: "Fechar", + logisticsReopen: "Reabrir", + logisticsCreateSectionTitle: "Criar rota", + logisticsUpdateSectionTitle: "Atualizar rota", + logisticsTitleLabel: "Título", + logisticsTitlePlaceholder: "Viagem ao mercado, carrinha disponível...", + logisticsKindLabel: "Tipo de logística", + logisticsKindTrip: "VIAGEM", + logisticsKindShipment: "ENVIO", + logisticsKindTrips: "VIAGENS", + logisticsKindShipments: "ENVIOS", + logisticsModeLabel: "Modo", + logisticsModeOffer: "OFEREÇO", + logisticsModeRequest: "PROCURO", + logisticsModeOffers: "OFERTAS", + logisticsModeRequests: "PEDIDOS", + logisticsDescriptionLabel: "Descrição", + logisticsDescriptionPlaceholder: "Ponto de encontro, o que levas, condições...", + logisticsOriginLabel: "Origem", + logisticsOriginPlaceholder: "Onde começa", + logisticsDestinationLabel: "Destino", + logisticsDestinationPlaceholder: "Onde termina", + logisticsDateLabel: "Data e hora", + logisticsRecurrenceLabel: "Recorrência", + logisticsFrequencyLabel: "Frequência", + logisticsRecurrenceNone: "ÚNICA", + logisticsRecurrenceDaily: "DIÁRIA", + logisticsRecurrenceWeekly: "SEMANAL", + logisticsRecurrenceMonthly: "MENSAL", + logisticsSeatsLabel: "Lugares", + logisticsSizeLabel: "Tamanho", + logisticsSizePlaceholder: "p. ex. 2m x 1m", + logisticsWeightLabel: "Peso", + logisticsWeightPlaceholder: "p. ex. 40kg", + logisticsPriceFree: "GRÁTIS", + logisticsPriceEco: "ECO", + logisticsPriceTime: "TEMPO", + logisticsPriceLabel: "Preço", + logisticsHours: "h", + logisticsOrderRefLabel: "Ligação ao pedido", + logisticsOrderRefPlaceholder: "/shops/... ou /market/... do pedido a entregar", + logisticsOrderLink: "Pedido ligado", + logisticsTagsLabel: "Etiquetas", + logisticsTagsPlaceholder: "Introduz tags separadas por vírgulas", + logisticsFilterAll: "TODAS", + logisticsFilterMine: "MINHAS", + logisticsFilterBooked: "RESERVADAS", + logisticsFilterRecent: "RECENTES", + logisticsFilterHistory: "HISTÓRICO", + logisticsFilterTop: "TOP", + logisticsStatusOpen: "ABERTA", + logisticsStatusUpcoming: "PRÓXIMAS", + logisticsStatusPast: "PASSADAS", + logisticsStatusClosed: "FECHADAS", + logisticsZoneLabel: "Zona", + logisticsSearchPlaceholder: "Pesquisar em logística...", + logisticsNoItems: "Nenhuma rota encontrada.", + logisticsBookingPending: "PENDENTE", + logisticsBookingConfirmed: "CONFIRMADA", + logisticsBookingRejected: "RECUSADA", + logisticsBookingCancelled: "CANCELADA", + logisticsBookingDelivered: "ENTREGUE", + logisticsConfirm: "Confirmar", + logisticsReject: "Recusar", + logisticsDeliver: "Entregar", + logisticsReceiptLabel: "Recibo", + logisticsReceiptPlaceholder: "Nota de entrega (opcional)", + logisticsCancelBooking: "Cancelar reserva", + logisticsBookTitle: "Reservar esta rota", + logisticsNotesLabel: "Notas", + logisticsNotesPlaceholder: "O que o responsável deve saber", + logisticsBook: "Reservar", + logisticsBookingsTitle: "Reservas", + logisticsMyBookingTitle: "A tua reserva", + logisticsRatingsTitle: "Avaliações", + logisticsNoRatings: "Ainda não há avaliações.", + logisticsRatingsWhenClosed: "As avaliações abrem quando a rota fecha.", + logisticsScoreLabel: "Pontuação", + logisticsRatingPlaceholder: "Como correu?", + logisticsRate: "Avaliar", + typeLogisticsRoute: "ROTA", + typeLogisticsRating: "AVALIAÇÃO DE ROTA", + statsLogisticsRoute: "Rotas", + statsLogisticsRating: "Avaliações de rotas", + logisticsRouteLabel: "ROTA", + podcastsTitle: "Podcasts", + modulesPodcastsLabel: "Podcasts", + modulesPodcastsDescription: "Módulo para publicar canais de podcast.", + podcastsDescription: "Módulo para publicar canais de podcast.", + podcastCreate: "Criar podcast", + podcastUpdate: "Atualizar", + podcastDelete: "Eliminar", + podcastCreateSectionTitle: "Criar podcast", + podcastUpdateSectionTitle: "Atualizar podcast", + podcastTitleLabel: "Título", + podcastTitlePlaceholder: "Nome do canal", + podcastDescriptionLabel: "Descrição", + podcastDescriptionPlaceholder: "Sobre o que é este podcast?", + podcastCategoryLabel: "Categoria", + podcastCategoryNews: "NOTÍCIAS", + podcastCategoryMusic: "MÚSICA", + podcastCategoryTalk: "CONVERSA", + podcastCategoryEducation: "EDUCAÇÃO", + podcastCategoryTech: "TECNOLOGIA", + podcastCategoryCulture: "CULTURA", + podcastCategoryCommunity: "COMUNIDADE", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "OUTROS", + podcastTagsLabel: "Etiquetas", + podcastCoverLabel: "Capa (imagem ou vídeo)", + podcastEpisodeAudioLabel: "Ficheiro de áudio ou vídeo (máx: 75MB)", + podcastTagsPlaceholder: "Introduz tags separadas por vírgulas", + podcastFilterAll: "TODOS", + podcastFilterMine: "MEUS", + podcastFilterRecent: "RECENTES", + podcastFilterTop: "TOP", + podcastFilterViewers: "OUVINTES", + podcastSearchPlaceholder: "Pesquisar em podcasts...", + podcastNoItems: "Nenhum podcast encontrado.", + podcastEpisodesLabel: "Episódios", + podcastPlaysLabel: "Visualizações", + podcastLastEpisode: "Último episódio", + podcastEpisodesTitle: "Episódios", + podcastNoEpisodes: "Ainda não há episódios.", + podcastEpisodeAdd: "Adicionar episódio", + podcastEpisodeCreateTitle: "Publicar episódio", + podcastEpisodeUpdateTitle: "Atualizar episódio", + podcastEpisodeTitleLabel: "Título do episódio", + podcastEpisodeTitlePlaceholder: "Título do episódio", + podcastEpisodeDescriptionLabel: "Notas", + podcastEpisodeDescriptionPlaceholder: "O que acontece neste episódio, ligações, créditos...", + podcastEpisodePublish: "Publicar", + podcastPlayed: "Reproduzido", + podcastMarkPlayed: "Marcar como reproduzido", + typePodcast: "PODCAST", + typePodcastEpisode: "EPISÓDIO", + typePodcastOpinion: "OPINIÃO DO EPISÓDIO", + typePodcastPlay: "REPRODUÇÃO", + statsPodcast: "Podcasts", + statsPodcastEpisode: "Episódios", + statsPodcastPlay: "Reproduções", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "Um blog que segues tem uma nova entrada.", + podcastBotEpisodeTitle: "Um podcast que segues tem um novo episódio.", + podcastLabel: "PODCAST", + podcastEpisodeLabel: "EPISÓDIO", + campaignsTitle: "Campanhas", + modulesCampaignsLabel: "Campanhas", + modulesCampaignsDescription: "Descobre e gere campanhas na tua rede.", + campaignsDescription: "Descobre e gere campanhas na tua rede.", + campaignCreate: "Criar campanha", + campaignUpdate: "Atualizar", + campaignDelete: "Eliminar", + campaignClose: "Fechar", + campaignReopen: "Reabrir", + campaignCreateSectionTitle: "Criar campanha", + campaignUpdateSectionTitle: "Atualizar campanha", + campaignTitleLabel: "Título", + campaignTitlePlaceholder: "O que queres alcançar?", + campaignTextLabel: "Texto", + campaignTextPlaceholder: "Explica a causa, quem decide e o que pedes...", + campaignCategoryLabel: "Categoria", + campaignCategoryEnvironment: "AMBIENTE", + campaignCategoryRights: "DIREITOS", + campaignCategoryHealth: "SAÚDE", + campaignCategoryEducation: "EDUCAÇÃO", + campaignCategoryInfrastructure: "INFRAESTRUTURA", + campaignCategoryCulture: "CULTURA", + campaignCategoryEconomy: "ECONOMIA", + campaignCategoryOther: "OUTROS", + campaignGoalLabel: "Objetivo de assinaturas", + campaignDeadlineLabel: "Prazo", + campaignTagsLabel: "Etiquetas", + campaignTagsPlaceholder: "Introduz tags separadas por vírgulas", + campaignFilterAll: "TODAS", + campaignFilterMine: "MINHAS", + campaignFilterSigned: "ASSINADAS", + campaignFilterRecent: "RECENTES", + campaignFilterTop: "TOP", + campaignStatusOpen: "ABERTA", + campaignStatusAchieved: "ALCANÇADA", + campaignStatusUnachieved: "NÃO ALCANÇADA", + campaignStatusClosed: "FECHADA", + campaignSearchPlaceholder: "Pesquisar em campanhas...", + campaignNoItems: "Nenhuma campanha encontrada.", + campaignSignaturesLabel: "Assinaturas", + campaignSignaturesTitle: "Apoiantes", + campaignNoSignatures: "Ainda não há assinaturas.", + campaignSignTitle: "Assinar esta campanha", + campaignSignPlaceholder: "Porque a apoias? (opcional)", + campaignSign: "Assinar", + campaignSigned: "Já assinaste!", + campaignNotSigned: "Ainda falta a tua assinatura", + campaignElevate: "Levar ao Parlamento", + campaignElevated: "Levada ao Parlamento", + campaignUpdatesTitle: "Atualizações", + campaignNoUpdates: "Ainda não há atualizações.", + campaignUpdatePlaceholder: "O que aconteceu?", + campaignAddUpdate: "Publicar atualização", + campaignQrAlt: "QR para assinar esta campanha", + campaignQrCaption: "Digitaliza para assinar", + typeCampaign: "CAMPANHA", + typeCampaignSignature: "ASSINATURA", + typeCampaignUpdate: "ATUALIZAÇÃO DE CAMPANHA", + statsCampaign: "Campanhas", + statsCampaignSignature: "Assinaturas de campanhas", + statsCampaignUpdate: "Atualizações de campanhas", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "Uma campanha que segues tem uma atualização.", + campaignBotAchievedTitle: "Uma campanha que segues atingiu o objetivo.", + campaignBotRaisedTitle: "Uma campanha que assinaste foi levada ao Parlamento.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Uma rota que reservaste mudou.", + logisticsBotClosedTitle: "Uma rota que reservaste foi fechada.", + logisticsBotBookedTitle: "Alguém reservou a tua rota.", + logisticsBotCancelledTitle: "Uma reserva na tua rota foi cancelada.", + logisticsBotConfirmedTitle: "A tua reserva foi confirmada: o pacote está a caminho.", + logisticsBotRejectedTitle: "A tua reserva foi rejeitada.", + logisticsBotDeliveredTitle: "O teu pacote chegou.", + campaignLabel: "CAMPANHA", + campaignGenerateReport: "Gerar relatório", + logisticsGenerateReport: "Gerar relatório", + mailingGenerateReport: "Gerar relatório", + campaignUpdateEdited: "Editada", + mailingMessagesLabel: "Mensagens", + logisticsBookingsLabel: "Reservas", + backupTitle: "Cópia de segurança", + modulesBackupLabel: "Cópia de segurança", + modulesBackupDescription: "Módulo para exportar e importar as tuas chaves e fazer cópias de segurança cifradas.", + backupDescription: "Módulo para exportar e importar as tuas chaves e fazer cópias de segurança cifradas.", + backupTypeRecovery: "RECUPERAÇÃO", + backupTypeKeys: "EXPORTAR CHAVES", + backupTypeFull: "CÓPIA COMPLETA", + backupTypeRestore: "RESTAURAR CÓPIA", + backupTypeInstant: "INSTANTÂNEA", + backupInstantDescription: "Um .zip simples de todo o teu registo e ficheiros, sem a tua chave secreta e sem palavra-passe. Rápido, mas ", + backupInstantRisk: "quem tiver o ficheiro lê tudo.", + backupKitPageDescription: "O kit de recuperação só pode ser aberto a partir deste dispositivo.", + backupKitId: "ID Oasis", + backupKitDate: "Gerado", + backupKitQrAlt: "Código QR da tua chave secreta", + backupKitStep1: "Imprime-o ou copia-o à mão e guarda-o num lugar seguro e offline.", + backupKitStep2: "Para recuperar, instala o Oasis num dispositivo novo e importa este segredo em Cópia de segurança › Chaves.", + backupKitStep3: "Destrói esta página se alguma vez ficar exposta.", + backupKitPdfButton: "Gerar PDF", + backupScopeLabel: "Âmbito", + backupScopeAll: "TUDO", + backupScopeMine: "SÓ O MEU CONTEÚDO", + backupSinceLabel: "Desde", + backupEstimateTitle: "Estimativa", + backupFileInfo: "A tua cópia cifrada será descarregada para o teu dispositivo (nome: oasis-backup-DATA.oasisbk)", + backupExportButton: "Descarregar cópia", + backupRestorePassword: "Escreve a palavra-passe usada ao fazer a cópia", + backupRestoreButton: "Restaurar cópia", + backupRestoredLine: "Cópia restaurada.", + backupRestoredMessages: "Mensagens adicionadas", + backupRestoredSkipped: "Já existentes", + backupRestoredBlobs: "Ficheiros adicionados", + backupRestoredState: "Configurações restauradas", + backupRestoredFailed: "Falhados", + backupRestoredForked: "Divergentes", + backupRestoreRunning: "Restaurando a cópia. Esta página atualiza-se sozinha; entretanto podes continuar a usar o Oasis.", + backupRestoreElapsed: "Decorrido", + backupRestoreFailedLine: "Não foi possível restaurar a cópia.", + backupRestoreForkMine: "Este dispositivo já publicou mensagens com a tua identidade que não estão na cópia, por isso não foi possível aplicá-la por cima. Restaura numa instalação limpa logo após importar as tuas chaves, antes de fazer qualquer outra coisa.", + verificationTitle: "Verificação", + verificationDescription: "Verifica a cadeia do teu feed, deteta bifurcações e revê ficheiros órfãos ou em falta.", + verificationRun: "Executar verificação", + verificationTotalMessages: "mensagens no registo", + verificationFeed: "O teu feed", + verificationMessages: "mensagens", + verificationLastSequence: "última sequência", + verificationGaps: "Lacunas", + verificationBrokenLinks: "Ligações quebradas", + verificationBadHashes: "Hashes incorretos", + verificationBadSignatures: "Assinaturas incorretas", + verificationDuplicates: "Sequências duplicadas", + verificationForks: "Bifurcações", + verificationBlobs: "Ficheiros", + verificationBlobsPresent: "presentes", + verificationBlobsReferenced: "referenciados", + verificationBlobsOrphan: "órfãos", + verificationBlobsMissing: "em falta", + verificationBlobsMissingOwn: "Ficheiros teus em falta neste dispositivo", + verificationBlobsMissingOthers: "Ficheiros de outros habitantes ainda por descarregar", + verificationBlobsUnavailable: "Não foi possível listar os ficheiros neste dispositivo.", + wikiTitle: "Wikis", + modulesWikiLabel: "Wikis", + modulesWikiDescription: "Módulo para descobrir e gerir wikis.", + wikiDescription: "Módulo para descobrir e gerir wikis.", + wikiCreate: "Criar wiki", + wikiUpdate: "Atualizar", + wikiDelete: "Apagar", + wikiEdit: "Editar", + wikiTitleLabel: "Título", + wikiTitlePlaceholder: "Título da wiki...", + wikiBodyLabel: "Conteúdo", + wikiBodyPlaceholder: "Liga outras wikis com [[Título da wiki]] ou [[Título da wiki|um texto próprio]].\n\nMenciona um habitante com @id.\n\nEtiqueta com #oasis.\n\nCola ligações como https://solarnethub.com\n\nUma linha em branco abre um novo parágrafo; as quebras de linha simples mantêm-se.\n\nAnexa qualquer media suportado e será incorporado aqui.", + formatBarLabel: "Formato", + wikiStatusLabel: "Tipo de wiki", + wikiStatusOpen: "ABERTA", + wikiStatusClosed: "FECHADA", + wikiTagsLabel: "Etiquetas", + wikiTagsPlaceholder: "Introduz tags separadas por vírgulas", + wikiSummaryLabel: "Resumo da alteração", + wikiSummaryPlaceholder: "O que mudou?", + wikiFilterAll: "TODAS", + wikiFilterMine: "MINHAS", + wikiFilterRecent: "RECENTES", + wikiFilterLinked: "LIGADAS", + wikiChanges: "Alterações recentes", + wikiNoPages: "Nenhuma wiki encontrada.", + wikiNoChanges: "Ainda sem alterações.", + wikiHistory: "Histórico", + wikiVersion: "Versão", + wikiVersions: "versões", + wikiRestore: "Restaurar", + wikiDiff: "Alterações", + wikiViewVersion: "Ver", + wikiLinkedPages: "Páginas ligadas", + wikiNoLinkedPages: "Esta página ainda não está ligada a nenhuma outra.", + wikiMissingLink: "A página ainda não existe", + wikiDuplicateTitle: "Já existe uma página com este título. Muda o título para criar uma página diferente.", + wikiCreateSectionTitle: "Criar wiki", + wikiUpdateSectionTitle: "Atualizar wiki", + wikiSearchPlaceholder: "Pesquisar na Wiki...", + wikiCurrentVersion: "Versão atual", + wikiViewingVersion: "Estás a ver uma versão antiga", + wikiBackToPage: "Voltar à página", + wikiLinkedChip: "Ligada", + wikiRestoredSummary: "Versão restaurada", + wikiExportPdf: "Gerar página", + wikiPreview: "Pré-visualizar", + tribeSectionWiki: "WIKI", + tribeWikiCreate: "Criar wiki", + tribeWikiEmpty: "Ainda sem wikis.", + typeWiki: "WIKI", padsTitle: "Pads", padTitle: "Pad", modulesPadsLabel: "Pads", @@ -3047,21 +3803,133 @@ module.exports = { padTitleLabel: "Título", padTitlePlaceholder: "Escreva o título do pad...", padStatusLabel: "Estado", + padTypeLabel: "Tipo de pad", padStatusOpen: "ABERTO", padStatusInviteOnly: "APENAS POR CONVITE", padStatusClosed: "FECHADO", padDeadlineLabel: "Prazo", padTagsLabel: "Tags", - padTagsPlaceholder: "tag1, tag2, ...", + padTagsPlaceholder: "Introduz tags separadas por vírgulas", padMembersLabel: "Membros", padCreated: "Criado", - padStartEditing: "COMEÇAR A EDITAR!", + padStartEditing: "Editar", padEditorPlaceholder: "Comece a escrever...", padSubmitEntry: "Enviar", padNoEntries: "Sem entradas ainda.", padAllSectionTitle: "Todos os Pads", padMineSectionTitle: "Os Meus Pads", padsDescription: "Gere editores de texto colaborativos cifrados na tua rede.", + roomsTitle: "Salas", + roomsDescription: "Descobre e entra em salas de videoconferência cifradas na tua rede.", + modulesRoomsLabel: "Salas", + modulesRoomsDescription: "Módulo para descobrir e entrar em salas de videoconferência cifradas.", + roomFilterRecent: "RECENTES", + roomFilterMine: "MINHAS", + roomFilterAll: "TODAS", + roomFilterLive: "EM DIRETO", + roomFilterOpen: "ABERTAS", + roomFilterInvite: "APENAS POR CONVITE", + roomFilterClosed: "FECHADAS", + roomCreate: "Criar sala", + roomUpdate: "Atualizar", + roomDelete: "Eliminar", + roomClose: "Fechar", + roomCreateSectionTitle: "Criar sala", + roomUpdateSectionTitle: "Atualizar sala", + roomTitlePlaceholder: "Título da sala", + roomDescriptionLabel: "Descrição", + roomDescriptionPlaceholder: "Sobre o que vão falar?", + roomTypeLabel: "Tipo de sala", + roomTagsLabel: "Tags", + roomStatusOpen: "ABERTA", + roomStatusInviteOnly: "APENAS POR CONVITE", + roomStatusClosed: "FECHADA", + roomParticipants: "Participantes", + roomJoin: "Entrar", + roomLeave: "Sair", + roomFull: "Sala cheia", + roomLiveChip: "EM DIRETO", + roomMuted: "silenciado", + roomRec: "Gravar áudio", + roomRecStop: "Parar a gravação", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} entrou na sala", + roomEventLeft: "{name} saiu da sala", + roomEventRecStart: "{name} começou a gravar", + roomEventRecStop: "{name} parou de gravar", + roomEventMuted: "{name} silenciou-se", + roomEventUnmuted: "{name} tirou o silêncio", + roomEventHand: "{name} levantou a mão", + roomRaiseHand: "Levantar a mão", + roomLowerHand: "Baixar a mão", + confirmActionTitle: "Confirma esta ação", + confirmActionButton: "Confirmar", + confirmJoinText: "Vais juntar-te usando este convite.", + confirmFollowText: "Vais apoiar este habitante.", + confirmSignText: "Vais assinar esta campanha.", + confirmDownloadText: "Vais transferir este ficheiro para o teu dispositivo.", + roomRecordings: "Gravações", + roomClearNotices: "Limpar", + roomLineSwitchboard: "Central", + roomLineDndError: "O número desta sala não aceita chamadas.", + roomNumberHint: "Marque este número a partir do Telefone para entrar na sala.", + liveRefresh: "Atualizar", + roomNobody: "Neste momento não está ninguém na sala.", + roomSomeone: "{n} na sala neste momento.", + roomYou: "tu", + roomYouAreIn: "Está dentro", + roomNobodyInside: "Neste momento não há ninguém nesta sala.", + roomErrorFull: "A sala está cheia. Podes entrar quando alguém sair.", + roomErrorUnreachable: "Neste momento não é possível chegar à sala. Tenta mais tarde.", + roomErrorBusy: "Já estás numa chamada ou noutra sala.", + roomErrorClosed: "Esta sala está fechada.", + roomAccessDenied: "Esta sala é apenas por convite. Precisas de um convite para entrar.", + roomsNoItems: "Não foram encontradas salas.", + roomSearchPlaceholder: "Procurar salas...", + roomInviteMode: "Convite", + tribeSectionRooms: "SALAS", + tribeRoomCreate: "Criar sala", + tribeRoomsEmpty: "Ainda não há salas.", + typeRoom: "SALAS", + typeRoomMember: "PARTICIPANTE DE SALA", + statsRoom: "Salas", + statsRoomMember: "Participantes de salas", + favoritesFilterRooms: "SALAS", + invitesRoomsTitle: "Salas", + inviteCodeInvalid: "Este código de convite não é válido ou já foi usado.", + inviteAlreadyMember: "Já fazes parte. Podes abri-lo a partir do seu módulo.", + actionFailed: "Não foi possível fazê-lo. Tenta de novo.", + publishTooShort: "A tua publicação é demasiado curta.", + walletSendFailed: "Não foi possível enviar o pagamento: {reason}", + pixeliaCoordsError: "Essas coordenadas estão fora da grelha.", + courtsErrTitle: "É necessário um título.", + courtsErrRespondent: "Indica contra quem é o caso.", + courtsErrRespondentId: "Esse não é um Oasis ID válido.", + courtsErrMethod: "Escolhe como o caso será resolvido.", + courtsErrCaseNotFound: "Esse caso não foi encontrado.", + courtsErrBrief: "Escreve a tua resposta.", + courtsErrStance: "Escolhe a tua posição.", + courtsErrResult: "Escreve o resultado.", + courtsErrTerms: "Escreve os termos do acordo.", + courtsErrMediator: "Adiciona pelo menos um mediador.", + courtsErrSelfMediator: "Não podes ser mediador no teu próprio caso.", + courtsErrJudge: "Escolhe um juiz.", + courtsErrSelfJudge: "Não podes ser juiz do teu próprio caso.", + courtsErrVisibility: "Escolhe quem pode ver o caso.", + courtsErrNomination: "Essa nomeação não foi encontrada.", + opinionAlreadyGiven: "Já deste a tua opinião.", + cipherPasswordShort: "A palavra-passe deve ter pelo menos 32 caracteres.", + errorForbidden: "Não tens acesso a isto.", + errorNotFound: "Não foi encontrado.", + errorClosed: "Está fechado.", + errorInviteOnly: "Isto é apenas por convite. Precisas de um convite.", + errorTooLong: "Esse texto é demasiado longo.", + errorRequired: "Falta informação obrigatória.", + errorInvalid: "Parte da informação não é válida.", + errorNotAuthor: "Só quem o criou pode fazer isto.", + panicRemoved: "A tua blockchain foi eliminada.", padRecentSectionTitle: "Pads Recentes", padOpenSectionTitle: "Pads Abertos", padClosedSectionTitle: "Pads Fechados", @@ -3073,18 +3941,18 @@ module.exports = { padRemoveFavorite: "Remover dos Favoritos", padClose: "Fechar", padBackToEditor: "Voltar ao editor", - padSearchPlaceholder: "Pesquisar pads...", + padSearchPlaceholder: "Pesquisar em Pads...", modulesChatsLabel: "Chats", - modulesChatsDescription: "Módulo para descobrir e gerir chats cifrados.", + modulesChatsDescription: "Módulo para descobrir e gerir chats.", typeChat: "CHATS", typeChatMessage: "MENSAGEM CHAT", chatsTitle: "Chats", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "Os teus chats", + chatRecentTitle: "Chats recentes", chatFavoritesTitle: "Favoritos", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "Chats abertos", + chatClosedTitle: "Chats fechados", chatDescription: "Descrição", chatCategory: "Categoria", chatStatus: "ESTADO", @@ -3099,34 +3967,172 @@ module.exports = { chatMessagesLabel: "Mensagens", chatThreadsLabel: "Tópicos", chatCreatedAt: "CRIADO", - chatSearchPlaceholder: "Pesquisar chats...", + chatSearchPlaceholder: "Pesquisar em Chats...", chatStatusOpen: "ABERTO", chatStatusInviteOnly: "SÓ POR CONVITE", chatStatusClosed: "FECHADO", chatSendMessage: "Enviar", chatJumpLatest: "Última Mensagem", + chatReply: "Responder", + chatReplyCancel: "Cancelar resposta", + chatPin: "Fixar", + chatUnpin: "Desafixar", + chatPinned: "Fixados", chatPickChat: "Escolhe um chat para o abrir", chatNoneYet: "Ainda não há nenhum chat disponível.", - activitySearchPlaceholder: "Procurar na atividade...", - trendingSearchPlaceholder: "Procurar nas tendências...", - opinionsSearchPlaceholder: "Procurar nas opiniões...", - votesSearchPlaceholder: "Procurar nas votações...", - welcomeStepUxTitle: "Escolhe a tua interface", - welcomeStepUxText: "Decide como se vê o Oasis. Podes mudar depois nas definições.", - welcomeStepUxAction: "Usar esta vista", + activitySearchPlaceholder: "Pesquisar em Atividade...", + trendingSearchPlaceholder: "Pesquisar em Tendências...", + opinionsSearchPlaceholder: "Pesquisar em Opiniões...", + votesSearchPlaceholder: "Pesquisar em Votações...", + gamesSearchPlaceholder: "Pesquisar em Jogos...", + schoolTitle: "Escola", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Uma página wiki que segues foi editada.", + wikiBotRestoredTitle: "Foi restaurada uma versão anterior de uma página wiki que segues.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Um novo estudante se inscreveu no seu curso.", + schoolBotInvitedTitle: "Você foi convidado para um curso.", + schoolBotAdmittedTitle: "Você foi admitido em um curso.", + schoolBotCertificateTitle: "Você recebeu um certificado.", + schoolBotPassedTitle: "Um estudante foi aprovado no seu curso.", + schoolBotLessonTitle: "Uma nova lição foi publicada.", + invitesSchoolTitle: "Escola", + schoolEnrollLabel: "Juntar-se ao curso", + schoolExamRetryLater: "Você poderá tentar novamente após 24 horas.", + schoolApproved: "APROVADO", + schoolExamPassMark: "Nota de aprovação (0-10)", + schoolExamPassed: "APROVADO", + schoolExamPending: "Pendente", + schoolExamFailed: "REPROVADO", + schoolExamAddQuestion: "Adicionar pergunta", + schoolExamCreateQuestion: "Criar pergunta", + schoolExamOption: "Opção", + schoolExamCorrect: "Opção correta", + schoolExamPoints: "Pontos", + schoolMaterialTextPlaceholder: "Notas para esta lição...", + schoolMaterials: "Materiais", + schoolAddMaterial: "Adicionar material", + schoolNoMaterials: "Ainda não há materiais.", + bankYourSchoolBalance: "Seus ganhos da escola", + schoolProgress: "Progresso", + schoolProgressMark: "Marcar como concluída", + schoolProgressDone: "Concluída", + schoolExams: "Exames", + schoolExamCreate: "Criar exame", + schoolExamTitlePlaceholder: "Título do exame", + schoolExamHelp: "Uma pergunta por linha: Pergunta? ; opção ; opção correta* ; opção", + schoolExamQuestionsPlaceholder: "O que é SSB? ; Um banco de dados ; Um protocolo P2P* ; Um navegador", + schoolExamSubmit: "Enviar respostas", + schoolExamScore: "Sua nota", + schoolNoScoresYet: "Ainda não há resultados.", + schoolLessonUnit: "Unidade / fase", + schoolLessonUnitPlaceholder: "ex. Fase 1", + schoolLessonOrder: "Ordem", + profileClearnetSchoolLabel: "Escola", + schoolStartDate: "Data de início", + schoolSessionDate: "Data da sessão", + schoolCourseChat: "Chat do curso", + agendaFilterSchool: "ESCOLA", + agendaFilterCampaigns: "CAMPANHAS", + agendaFilterLogistics: "LOGÍSTICA", + schoolCourseType: "Tipo de curso", + schoolTypeOpen: "ABERTO", + schoolTypePaid: "PAGO", + schoolTypeInvite: "SÓ POR CONVITE", + schoolLessonLocked: "Lição cifrada — peça a chave do curso ao professor.", + schoolGrantAccess: "Enviar chave do curso", + schoolKeySent: "Chave enviada", + schoolOngoing: "Em curso", + schoolFilterApplied: "INSCRITO", + schoolFilterOpen: "ABERTOS", + schoolFilterFavorites: "FAVORITOS", + schoolPendingPayment: "Pagamento pendente", + schoolPendingStudents: "Inscrições pendentes", + schoolViewBill: "Ver fatura", + schoolPaymentNote: "A inscrição gera uma fatura em ECO (uma transferência). Sua inscrição é confirmada quando a transferência é assinada.", + schoolCloseCourse: "Fechar curso", + schoolReopenCourse: "Reabrir curso", + schoolDescription: "Ensine e aprenda cursos na sua rede.", + schoolFilterAll: "TODOS", + schoolFilterMine: "MEUS", + schoolFilterTop: "TOP", + schoolFilterRecent: "RECENTES", + schoolCreateButton: "Criar Curso", + schoolCreateSectionTitle: "Criar um curso", + schoolUpdateSectionTitle: "Atualizar curso", + schoolCourseTitlePlaceholder: "Nome do seu curso", + schoolCourseDescriptionPlaceholder: "O que você vai ensinar neste curso?", + schoolCourseImage: "Imagem do curso (tamanho máx: 75MB)", + schoolTags: "Tags", + schoolTagsPlaceholder: "Introduz tags separadas por vírgulas", + schoolPrice: "Preço (ECO)", + schoolVisibility: "Visibilidade", + schoolVisibilityPublic: "Público", + schoolVisibilityInvite: "Por convite", + schoolStatus: "Estado", + schoolClosed: "Fechado", + schoolFree: "Grátis", + schoolCreate: "Criar curso", + schoolUpdate: "Atualizar curso", + schoolSearchPlaceholder: "Pesquisar em Escola...", + schoolSortTitle: "Título", + schoolNoCourses: "Nenhum curso encontrado.", + schoolStudents: "Estudantes", + schoolTeacher: "Professor", + schoolCreatedAt: "CRIADO", + schoolLessons: "Lições", + schoolNoLessons: "Ainda não há lições.", + schoolEnrollToSee: "Inscreva-se para ver as lições deste curso.", + schoolEnrollButton: "INSCREVER-SE", + schoolUnenrollButton: "Abandonar curso", + schoolInviteRequired: "Este curso é por convite.", + schoolTeacherPanel: "Painel do professor", + schoolStudentPanel: "Painel do estudante", + schoolAddLesson: "Criar lição", + schoolLessonTitlePlaceholder: "Título da lição", + schoolLessonTextPlaceholder: "Descreva as noções abordadas nesta lição...", + schoolInviteStudents: "Convidar estudantes", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "CONVIDAR", + schoolInvited: "Convidados", + schoolIssueCertificate: "Criar certificado", + schoolCertified: "Certificado", + schoolCertificateTextPlaceholder: "Nota do certificado (opcional)", + schoolLessonOpen: "Abrir lição", + schoolCertificatePdf: "PDF", + schoolCertificates: "Certificados", + schoolNoCertificates: "Nenhum certificado emitido ainda.", + schoolEditButton: "Editar", + schoolDeleteButton: "Apagar", + typeSchool: "ESCOLA", + schoolCourseLabel: "CURSO", + emergencyLabel: "EMERGÊNCIA", + wikiPageLabel: "WIKI", + statsSchoolCourse: "Cursos", + statsSchoolLesson: "Lições", + statsSchoolEnroll: "Inscrições", + statsSchoolCertificate: "Certificados da escola", + modulesSchoolLabel: "Escola", + modulesSchoolDescription: "Módulo para trocar conhecimento.", + welcomeStepUxAction: "Guardar vista", + welcomeStepWorkflowTitle: "Que uso do OASIS tens em mente?", + welcomeStepWorkflowText: "O Oasis tem muitos módulos. Escolhe a configuração que combina contigo e só os úteis ficam ativados. Podes ajustá-la quando quiseres em Módulos.", + welcomeStepWorkflowAction: "Guardar configuração", + welcomeWorkflow_full: "Predefinido", chatRateLimitTitle: "Demasiadas mensagens", chatRateLimitMessage: "Atingiste o limite de mensagens que esta sala aceita numa hora. Tenta mais tarde.", chatMessagePlaceholder: "Escreva a sua mensagem...", chatNoMessages: "Sem mensagens ainda.", chatTitlePlaceholder: "Título do chat", chatDescriptionPlaceholder: "Descrição do chat", - chatTagsPlaceholder: "tag1, tag2, tag3", + chatTagsPlaceholder: "Introduz tags separadas por vírgulas", chatImageLabel: "Seleciona um ficheiro de imagem (.jpeg, .jpg, .png, .gif)", chatAuthor: "Autor", chatAddFavorite: "Adicionar aos Favoritos", chatRemoveFavorite: "Remover dos Favoritos", chatPM: "MP", chatStatusLabel: "Estado", + chatTypeLabel: "Tipo de chat", chatCategoryLabel: "Categoria", gamesTitle: "Jogos", gamesDescription: "Descubra e jogue alguns jogos.", @@ -3157,22 +4163,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Lance uma moeda e aposte em cara ou coroa. Qual é a sua sorte?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "Bilhar visto de cima. Clica para apontar e mantém premido para carregar a força. Mete todas as bolas com o menor número de tacadas.", + gamesArtilleryTitle: "Artilharia", + gamesArtilleryDesc: "Aponta o canhão, tem em conta o vento e acerta no alvo. 5 rondas: ganha quem fizer menos disparos.", + gamesLabyrinthTitle: "Labirinto", + gamesLabyrinthDesc: "Foge do labirinto antes de ficares sem movimentos. Cada nível é maior e mais difícil.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Come todos os pontos e evita os fantasmas. Os fantasmas movem-se em tempo real — não pares!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "Pêndulo sonoro", + gamesAudioPendulumDesc: "Simulador de física caótica com síntese de áudio em tempo real. As velocidades angulares tornam-se frequências e os picos, batidas de percussão. Não há duas simulações que soem iguais.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Mistura visuais ao vivo em dois pratos: escolhe um padrão para cada um, ajusta velocidade, cor e zoom, faz a transição entre ambos e lança flashes ao ritmo. Marca o tempo ou deixa o microfone comandar a pulsação.", + gamesTetrisDesc: "Os clássicos blocos que caem. Completa linhas para pontuar. Quanto tempo aguentas?", + gamesMinesweeperTitle: "Caça-minas", + gamesMinesweeperDesc: "A clássica caça às minas. Limpa o tabuleiro para subir de nível — grelhas maiores, mais minas, mais pontuação.", + gamesFilterScoring: "PONTUAÇÕES", + gamesHallOfFame: "Hall da fama", + gamesHallPlayer: "Jogador", + gamesHallScore: "Pontuação", + gamesNoScores: "Ainda não há pontuações.", calendarsTitle: "Calendários", calendarTitle: "Calendário", modulesCalendarsLabel: "Calendários", @@ -3180,15 +4190,15 @@ module.exports = { typeCalendar: "CALENDÁRIOS", calendarCreate: "Criar calendário", calendarUpdate: "Atualizar", - calendarDelete: "Eliminar", calendarTitleLabel: "Título", calendarTitlePlaceholder: "Título do calendário...", calendarStatusLabel: "Estado", + calendarTypeLabel: "Tipo de calendário", calendarStatusOpen: "ABERTO", calendarStatusClosed: "FECHADO", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Prazo", calendarTagsLabel: "Etiquetas", - calendarTagsPlaceholder: "etiqueta1, etiqueta2...", + calendarTagsPlaceholder: "Introduz tags separadas por vírgulas", calendarParticipantsLabel: "Participantes", calendarCreated: "Criado", calendarJoin: "Participar", @@ -3199,11 +4209,11 @@ module.exports = { calendarNotePlaceholder: "Adicionar uma nota...", calendarFirstDateLabel: "Data", calendarFirstNoteLabel: "Notas", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Intervalo", calendarIntervalNone: "Sem repetição", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Semanal", + calendarIntervalMonthly: "Mensal", + calendarIntervalYearly: "Anual", calendarFormDescription: "Descrição", calendarNoDates: "Nenhuma data adicionada.", calendarNoNotes: "Nenhuma nota.", @@ -3211,6 +4221,8 @@ module.exports = { calendarsDescription: "Descubra e gerencie calendários na sua rede.", calendarMonthPrev: "← Anterior", calendarMonthNext: "Seguinte →", + calendarMonthNames: "Janeiro,Fevereiro,Março,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro", + calendarDayInitials: "S,T,Q,Q,S,S,D", calendarAllSectionTitle: "Calendários", calendarRecentSectionTitle: "Calendários Recentes", calendarFavoritesSectionTitle: "Favoritos", @@ -3220,9 +4232,9 @@ module.exports = { calendarCreateSectionTitle: "Criar novo calendário", calendarUpdateSectionTitle: "Atualizar calendário", calendarAddFavorite: "Adicionar aos favoritos", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Eliminar", calendarRemoveFavorite: "Remover dos favoritos", - calendarSearchPlaceholder: "Pesquisar calendários...", + calendarSearchPlaceholder: "Pesquisar em Calendário...", calendarAddEntry: "Adicionar Entrada", statsCalendar: "Calendários", statsCalendarDate: "Datas de calendário", @@ -3234,6 +4246,146 @@ module.exports = { tribeContentAccessDenied: "Acesso Negado", tribeContentAccessDeniedMsg: "Este conteúdo pertence a uma tribo. Você deve ser membro para acessá-lo.", tribeViewTribes: "Ver Tribos", + tribeFilesEmpty: "Ainda sem ficheiros.", + filesLabel: "Ficheiros", + profileClearnetFilesLabel: "Ficheiros", + filesTitle: "Ficheiros", + filesDescription: "Explore e gerencie ficheiros na sua rede.", + fileAllSectionTitle: "Ficheiros", + fileMineSectionTitle: "Seus Ficheiros", + fileRecentSectionTitle: "Ficheiros Recentes", + fileTopSectionTitle: "Melhores Ficheiros", + fileFavoritesSectionTitle: "Favoritos", + fileFilterAll: "TODOS", + fileFilterMine: "MEUS", + fileFilterRecent: "RECENTES", + fileFilterTop: "MELHORES", + fileFilterFavorites: "FAVORITOS", + fileCreateSectionTitle: "Enviar Ficheiro", + fileUpdateSectionTitle: "Atualizar Ficheiro", + fileCreateButton: "Enviar Ficheiro", + fileUpdateButton: "Atualizar", + fileAddFavoriteButton: "Adicionar aos Favoritos", + fileRemoveFavoriteButton: "Remover dos Favoritos", + fileFileLabel: "Seleciona um ficheiro (máx: 75MB)", + calendarFilterRecent: "RECENTES", + calendarFilterAll: "TODOS", + calendarFilterMine: "MEUS", + calendarFilterFavorites: "FAVORITOS", + calendarFilterOpen: "ABERTO", + calendarFilterClosed: "FECHADO", + padFilterRecent: "RECENTES", + padFilterAll: "TODOS", + padFilterMine: "MEUS", + padFilterOpen: "ABERTO", + padFilterClosed: "FECHADO", + chatFilterRecent: "RECENTES", + chatFilterAll: "TODOS", + chatFilterMine: "MEUS", + chatFilterFavorites: "FAVORITOS", + chatFilterOpen: "ABERTO", + chatFilterClosed: "FECHADO", + licenseLabel: "Licença", + phoneTitle: "Telefone", + phoneDescription: "Gere as tuas videochamadas cifradas de ponta a ponta.", + phoneUnavailable: "As chamadas não estão disponíveis neste dispositivo.", + phoneCallButton: "Ligar", + profileVisibilityPhone: "Número de telefone", + phoneNumberUnknown: "Ninguém na tua rede tem o número {number}.", + phoneNumberChoose: "Confirma a quem queres ligar para o {number}:", + phoneAnswer: "Atender", + phoneSpeaker: "Altifalante", + phoneEarpiece: "Auricular", + phoneReject: "Recusar", + phoneHangup: "Desligar", + phoneMute: "Silenciar", + phoneUnmute: "Ativar microfone", + phoneCameraOn: "Câmara on", + phoneCameraOff: "Câmara off", + settingsPhoneVideo: "Vídeo", + settingsPhoneVideoOn: "Permitido em chamadas e salas", + settingsPhoneVideoOff: "Só áudio", + phoneSilence: "Não ouvir", + phoneUnsilence: "Voltar a ouvir", + phoneCalling: "Ligando para", + phoneIsCalling: "está ligando para você", + notifyNewPmFrom: "Nova MP de", + notifyNewPamFrom: "Novo PaM de", + notifyUpdateLabel: "Atualização", + updateInstalledLabel: "Versão instalada", + updateAvailableLabel: "Versão disponível", + updateChangesLabel: "Alterações", + updateFilesLabel: "Arquivos", + updateOriginLabel: "Origem", + phoneRecording: "Gravando PaM para", + phoneLeaveMessage: "Ninguém atende: deixa a tua mensagem depois do sinal.", + phoneLeaveMessageBusy: "Está noutra chamada: deixe a sua mensagem depois do sinal.", + phoneLeaveMessageUnreachable: "Não foi possível contactar: deixe a sua mensagem depois do sinal.", + phoneConnecting: "A ligar a", + phoneSendPam: "Enviar PaM", + phoneCancel: "Cancelar", + phoneSearchPlaceholder: "Pesquisar em Telefone...", + phonePamTitle: "Gravações de áudio", + phoneNew: "Novo", + phoneDelete: "Apagar", + phoneNoPams: "Não tens gravações de áudio.", + phoneHistoryTitle: "Histórico", + phoneLatestTitle: "Últimas chamadas", + phoneCreateButton: "Nova chamada", + phoneLimitsHint: "Até 7 habitantes por chamada; com mais de um é uma chamada conjunta.", + phoneErrorPub: "Não é possível ligar para um pub.", + phoneJointCall: "Chamada conjunta", + phoneWith: "Com", + phoneResult: "Resultado", + phoneWhen: "Quando", + phoneDuration: "Duração", + phoneOutgoing: "Efetuada", + phoneIncoming: "Recebida", + phoneNoHistory: "Ainda não há registos.", + phoneOutcomeEnded: "Atendida", + phoneOutcomeMissed: "Perdida", + phoneOutcomeRejected: "Recusada", + phoneOutcomeNoanswer: "Sem resposta", + phoneOutcomeCancelled: "Cancelada", + phoneOutcomePam: "PaM enviado", + phoneOutcomeFailed: "Falhou", + phoneDialAdd: "Adicionar", + phoneDialBack: "Apagar", + phoneDialClear: "Limpar", + phoneDialPlaceholder: "Número ou Oasis ID", + phoneDialInhabitants: "Habitantes", + phoneDialRemove: "Remover", + phoneOwnNumber: "Esse é o teu próprio número.", + phoneSettingsDesc: "Escolha quem pode ligar para você.", + phoneDnd: "Não incomodar", + phoneOpen: "Aberto", + phoneRecipientRefuses: "O habitante a quem estás a tentar ligar não aceita chamadas.", + phoneRefusedNotice: "Estes habitantes não aceitam chamadas e não foram chamados:", + fileEmptyError: "O ficheiro está vazio. Escolhe outro ficheiro.", + fileTitleLabel: "Título", + fileTitlePlaceholder: "Título", + fileDescriptionLabel: "Descrição", + fileDescriptionPlaceholder: "Descrição", + fileTagsLabel: "Tags", + fileTagsPlaceholder: "Introduz tags separadas por vírgulas", + fileSizeLabel: "Tamanho", + fileDownloadButton: "DESCARREGAR", + fileDetailsButton: "DETALHES", + fileNoFile: "Nenhum arquivo ficheiro", + fileSearchPlaceholder: "Pesquisar em Ficheiros...", + fileSearchButton: "Pesquisar", + fileSortRecent: "Mais recentes", + fileSortOldest: "Mais antigos", + fileSortTop: "Mais votados", + fileNoMatch: "Nenhum ficheiro correspondente.", + fileUpdatedAt: "Atualizado", + typeFile: "FICHEIROS", + modulesFilesLabel: "Ficheiros", + modulesFilesDescription: "Módulo para descobrir e gerir áudios.", + tribeSectionFiles: "FICHEIROS", + fileDescription: "Descrição", + fileDownload: "Baixar", + fileTypeLabel: "Tipo", torrentsTitle: "Torrents", torrentsDescription: "Explore e gerencie torrents na sua rede.", torrentAllSectionTitle: "Torrents", @@ -3259,12 +4411,35 @@ module.exports = { torrentDescriptionLabel: "Descrição", torrentDescriptionPlaceholder: "Descrição", torrentTagsLabel: "Tags", - torrentTagsPlaceholder: "Insira tags separadas por vírgulas", + torrentTagsPlaceholder: "Introduz tags separadas por vírgulas", torrentSizeLabel: "Tamanho", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Gerar torrent", + torrentFromContentMissing: "O ficheiro ainda não está disponível neste nó. Tenta de novo mais tarde.", + torrentDownloads: "Transferências", + torrentFetchOasis: "Transferir no Oasis", + torrentSaveFile: "Guardar ficheiro", + torrentSeeds: "seeds", + seedsLabel: "Sementes", + seedAction: "Apoiar", + torrentSourceFile: "Ficheiro", + torrentStateQueued: "Em fila", + torrentStateProbing: "À procura de seeds", + torrentStateDownloading: "A transferir", + torrentStateDone: "Concluída", + torrentStateFailed: "Falhou", + torrentStateNoSource: "Sem seeds ligados, à espera", + torrentStateCancelled: "Cancelada", + torrentNoDownloads: "Ainda não há transferências.", + torrentOriginalSection: "Original", + torrentCancel: "Cancelar", + torrentDownloadButton: "DESCARREGAR", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "DETALHES", torrentNoFile: "Nenhum arquivo torrent", noTorrents: "Nenhum torrent encontrado.", - torrentSearchPlaceholder: "Pesquisar título, tags, autor...", + noFiles: "Nenhum ficheiro encontrado.", + torrentSearchPlaceholder: "Pesquisar em Torrents...", torrentSearchButton: "Pesquisar", torrentSortRecent: "Mais recentes", torrentSortOldest: "Mais antigos", @@ -3272,10 +4447,12 @@ module.exports = { torrentNoMatch: "Nenhum torrent correspondente.", torrentUpdatedAt: "Atualizado", statsTorrent: "Torrents", + statsFile: "Ficheiros", typeTorrent: "TORRENTS", modulesTorrentsLabel: "Torrents", modulesTorrentsDescription: "Módulo para descobrir e gerenciar torrents.", favoritesFilterTorrents: "TORRENTS", + favoritesFilterFiles: "FICHEIROS", favoritesFilterMarket: "MERCADO", favoritesFilterEvents: "EVENTOS", favoritesFilterForum: "FÓRUM", @@ -3289,15 +4466,17 @@ module.exports = { favoritesFilterShopProducts: "ITENS DA LOJA", tribeSectionTorrents: "TORRENTS", tribeCreateTorrent: "Enviar Torrent", + tribeCreateFile: "Carregar ficheiro", settingsWishTitle: "Desejo", - settingsWishDesc: "Configure o desejo do seu Avatar.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Determina como acedes a todo o conteúdo e a tua experiência na rede Oasis.", + settingsWishWhole: "Multiverso", + settingsWishMutuals: "Só apoio mútuo", + settingsWishOnlyLan: "Só LAN", + settingsWishLocal: "Local", settingsPmVisibilityTitle: "Mensagens Privadas", settingsPmVisibilityDesc: "Configure seu nível de exposição a mensagens privadas.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Multiverso", + settingsPmVisibilityMutuals: "Só apoio mútuo", pmMutualNotice: "O envio é uma barreira de UX. O recebimento é um filtro de atenção.", inhabitantsPendingFollowsTitle: "Pedidos de apoio pendentes", inhabitantsPendingAccept: "Aceitar", @@ -3341,7 +4520,7 @@ module.exports = { tribeGovNoRevocations: "Ainda nenhuma revogação", tribeGovNoHistorical: "Ainda nenhum registo", logsTitle: "Diário", - logsDescription: "Regista a sua experiência na rede.", + logsDescription: "Mantém um diário privado das tuas experiências.", logsViewTitle: "Diário", logsColumnType: "Tipo", logsView: "Ver", @@ -3350,7 +4529,7 @@ module.exports = { logsCreateTitle: "Criar entrada", logsWriteButton: "Escrever", logsTextPlaceholder: "Descreva as suas experiências...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "Assistente IA", logsModeManual: "Manual", logsModeAI: "IA", logsColumnLog: "Diário", @@ -3369,19 +4548,20 @@ module.exports = { logsExportOne: "Exportar registo", logsViewDetails: "Ver detalhes", logsGenerateButton: "Gerar texto", - logsSearchText: "Procurar nos diários...", + logsSearchText: "Pesquisar em Diário...", logsSearchAnyType: "Qualquer tipo", logsSearchButton: "Procurar", logsEmpty: "Sem entradas.", + logsNothingNew: "Não há ações novas para registar.", modulesLogsLabel: "Diário", - modulesLogsDescription: "Módulo para registar (via assistente de IA) as suas experiências.", + modulesLogsDescription: "Módulo para manter um diário privado das tuas experiências.", statsLogsTitle: "Diário", typeLog: "DIÁRIO", larpTitle: "L.A.R.P.", larpDescription: "Uma camada de jogo de interpretação ao vivo para experimentação colaborativa.", larpNoHousesMatch: "Nenhuma casa corresponde à sua pesquisa.", - larpSearchPlaceholder: "Pesquisar casas...", + larpSearchPlaceholder: "Pesquisar em L.A.R.P....", larpAllHouses: "Casas:", larpFilterRuling: "GOVERNA", larpFilterHouses: "CASAS", @@ -3396,54 +4576,55 @@ module.exports = { larpPostPlaceholder: "O que tem esta casa a dizer?", larpPostSubmit: "Publicar", larpCycleLabel: "Ciclo:", - audioTranscodeCompositionEmpty: "This audio does not include a stored blockchain composition.", - audioTranscodeDetailDescription: "Decode the embedded payload and the original blockchain composition map.", - audioTranscodeStegoNotFound: "No steganographic payload could be decoded from this audio.", - bankRulesAlpha: "Alpha (max share of pub balance per epoch)", + audioTranscodeCompositionEmpty: "Este áudio não inclui nenhuma composição de blockchain guardada.", + audioTranscodeDetailDescription: "Descodifica a carga incorporada e o mapa de composição original da blockchain.", + audioTranscodeStegoNotFound: "Não foi possível descodificar nenhuma carga esteganográfica deste áudio.", + bankRulesAlpha: "Alpha (quota máx. do saldo do pub por época)", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", - bankRulesArchTaxRate: "Rate", - bankRulesCapPerEpoch: "Cap per epoch (absolute)", - bankRulesCapUser: "Cap per user per epoch", - bankRulesCarbonFactor: "Carbon factor", + bankRulesArchTaxNote: "A taxa ARCH reflete o custo a longo prazo de fazer crescer e manter o arquivo da rede: quanto mais antigos forem os teus dados na rede, maior será a tua parte do custo de manutenção.", + bankRulesArchTaxRate: "Tarifa", + bankRulesCapPerEpoch: "Limite por época (absoluto)", + bankRulesCapUser: "Limite por utilizador por época", + bankRulesCarbonFactor: "Fator de carbono", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", + bankRulesChipNote: "O chip de cada elemento é colorido em relação à maior mensagem alguma vez observada na rede e suavizado pelo número de habitantes ativos — uma rede maior distribui a carga e empurra mais elementos para a faixa verde.", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", - bankRulesEcoTaxRate: "Rate", - bankRulesEpochKind: "Epoch granularity", - bankRulesFloor: "Floor per user (gross)", - bankRulesGraceDays: "Claim grace period", + bankRulesEcoTaxNote: "A taxa ECO é deduzida do RBU bruto quando se calcula a atribuição da época. Os ECO deduzidos não são redistribuídos nesta época — ficam no saldo do pub e alimentam o fundo da época seguinte através dos limites reserveMin / alpha.", + bankRulesEcoTaxRate: "Tarifa", + bankRulesEpochKind: "Granularidade da época", + bankRulesFloor: "Mínimo por utilizador (bruto)", + bankRulesGraceDays: "Período de carência da reivindicação", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", + bankRulesKarmaNote: "O karma deriva das tuas ações (publicações, votos, participação no parlamento/tribunais, etc.) com um fator de decaimento temporal, menos os gramas de carbono gerados pelo teu feed.", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", - bankRulesReserveMin: "Reserve minimum (kept in pub balance)", + bankRulesReserveMin: "Reserva mínima (mantida no saldo do pub)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankRulesWMax: "Maximum weight (w_max)", - bankRulesWMin: "Minimum weight (w_min)", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", + bankRulesWMax: "Peso máximo (w_max)", + bankRulesWMin: "Peso mínimo (w_min)", + bankTaxesArchTaxFootprintNote: "A taxa ARCH reflete o custo de fazer crescer e manter o arquivo da rede. É calculada a partir do intervalo de tempo entre o teu primeiro bloco publicado e o bloco mais recente da rede — quanto mais tempo a tua história viver no arquivo, maior será a tua parte do custo de manutenção.", + bankTaxesArchTaxNoteBold: "reduzir diretamente a taxa ARCH", + bankTaxesArchTaxNoteIntro: "A tua taxa ARCH é deduzida do excedente que a rede gera acima do teu RBU; o valor base do RBU é fixado como um mínimo inamovível. A taxa nunca é deduzida do montante fixo que cabe a cada habitante como RBU. Os fundos deduzidos alimentam o algoritmo de redistribuição da riqueza e são canalizados de volta para outros habitantes através das suas reivindicações de RBU. Os habitantes mais recentes e os que se dedicam especificamente a tarefas de arquivo, replicação e manutenção terão provavelmente alguma tarefa dedicada a manter saudável o arquivo da rede em teu nome. Ou podem dedicar-se a ", + bankTaxesArchTaxNoteOutro: " através de acordos de poda, deduplicação de dados redundantes e infraestrutura de manutenção partilhada, e o seu contributo contínuo pode ser exigido pelo consenso da rede.", + bankTaxesUserNoteChips: "O chip da Taxa ECO mostrado junto a cada elemento muda de cor (verde / amarelo / vermelho) consoante o tamanho do elemento em relação ao maior bloco alguma vez visto na rede, suavizado pelo número de habitantes ativos — uma rede solarpunk maior distribui a carga e empurra mais elementos para a faixa verde.", + bankTaxesUserNoteOtherParams: "A taxa ECO deriva atualmente da pegada de carbono de cada bloco, mas pode incorporar outros parâmetros com o tempo — energia gasta na replicação, armazenamento redundante entre pares, largura de banda dos blobs, custo computacional da decifração, pegada de mineração das transações associadas ou qualquer outra carga mensurável que a rede acorde valorizar.", + gamesHallDate: "Data", + gamesNewRecord: "Novo recorde!", + indexingRefreshNote: "Esta página recarrega a cada 10 segundos.", + indexingMessage: "O Oasis está sincronizando a rede distribuída de nós. Por favor, aguarde.", + indexingTitle: "A sincronizar", + padsNoItems: "Nenhum pad encontrado.", + padVersionHistory: "Histórico de versões", + padAttributionView: "Por autora", + padVersionView: "Ver", statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", + statsChatMessage: "Mensagens de chat", + statsGameScore: "Jogos", + statsWiki: "Páginas wiki", statsPad: "Pads", - statsPadEntry: "Pad entries", - typeGameScore: "GAME SCORES", + statsPadEntry: "Entradas de pad", + typeGameScore: "PONTUAÇÕES DE JOGO", bankTaxesLookupNote: "Introduz um ID de bloco para o procurar no teu feed local.", bankTaxesUserNoteChipsClick: "Clica em qualquer chip para inspecionar o seu bloco no blockexplorer.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3458,6 +4639,14 @@ module.exports = { settingsReplicationTitle: "Replicação", settingsReplicationDesc: "Configura o número de saltos que o teu peer segue a partir do teu próprio feed ao replicar conteúdo.", settingsReplicationHopsLabel: "Saltos", + settingsBlobCacheTitle: "Cache de média", + settingsBlobCacheDesc: "O conteúdo de outros habitantes fica guardado neste dispositivo. Escolhe quanto espaço pode ocupar.", + settingsBlobCacheUsage: "Utilização atual", + settingsBlobCacheFiles: "ficheiros", + settingsBlobCacheLimit: "Limite", + settingsBlobCacheUnlimited: "Sem limite", + settingsBlobCacheCleanNow: "Limpar agora", + settingsBlobCacheCleaned: "{n} ficheiros removidos, {size} libertados.", aiApproveTagsLabel: "Etiquetas (separadas por vírgulas)", aiApproveTagsPlaceholder: "p.ex. oasis, governança, ecologia", aiApproveRatingLabel: "Avaliação", @@ -3485,13 +4674,11 @@ module.exports = { larpInviteBannerHint: "Partilha este código com alguém em ACADEMIA. Expira em 30 ciclos ou após um único uso.", larpTestRankingTitle: "Pontuação por casa", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Código de convite de casa", - invitesHouseJoinButton: "Entrar na Casa", - ecoTaxLabel: "ECO Tax", + ecoTaxLabel: "Taxa ECO", bankTaxes: "Impostos", - bankOverviewYourTaxes: "Os teus impostos", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesArchTaxTitle: "ARCH Tax", + bankOverviewYourTaxes: "Impostos totais", + bankTaxesEcoTaxTitle: "Taxa ECO", + bankTaxesArchTaxTitle: "Taxa ARCH", bankTaxesUserAmount: "A tua ECO Tax (preço a devolver)", bankTaxesArchTaxUserAmount: "A tua ARCH Tax (preço a devolver)", bankTaxesArchTaxFirstBlock: "Idade do teu primeiro bloco", @@ -3519,7 +4706,7 @@ module.exports = { bankTaxesLookupType: "Tipo", bankTaxesLookupSize: "Tamanho", bankTaxesLookupCarbon: "Pegada de carbono", - bankTaxesLookupEcoin: "ECO Tax", + bankTaxesLookupEcoin: "Taxa ECO", bankExchangeEcoTaxAnnual: "ECOin Taxes (anual)", bankExchangeEcoTaxMonthly: "ECOin Taxes (mensal)", bankTaxesTypesLabel: "Seleciona que impostos queres pagar", @@ -3527,24 +4714,22 @@ module.exports = { bankRulesPoolTitle: "Pool mensal", bankRulesShareTitle: "Parcela por utilizador", bankRulesKarmaTitle: "Karma (pontuação de envolvimento)", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesArchTaxTitle: "ARCH Tax", + bankRulesEcoTaxTitle: "Taxa ECO", + bankRulesArchTaxTitle: "Taxa ARCH", bankRulesChipTitle: "Faixa de cor do chip ECO Tax", statsTombstoneEmpty: "Ainda sem tombstones na rede.", blockchainBlockNotAvailable: "Este bloco não está disponível no teu feed local. Pode pertencer a um peer do qual ainda não replicas.", blockchainBackToBlockexplorer: "Voltar ao blockexplorer", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", + audioTranscodeButton: "TRANSCODIFICAR", audioTranscodeDetailTitle: "Transcodificar", - audioTranscodeStegoOasisId: "Por", audioTranscodeStegoTimestamp: "Gerado em", - audioTranscodeStegoMessage: "TEXT", + audioTranscodeStegoMessage: "TEXTO", audioTranscodeStegoEmpty: "(nenhum)", audioTranscodeStegoUnknown: "—", audioBackToBcs: "Voltar ao BCS", melodyCompositionTitle: "Mapa de composição da blockchain", melodyFilterMine: "MEU", - melodyByLabel: "Por", melodyAllEmpty: "Ainda sem composições BCS de outros habitantes.", melodyNoteUnavailable: "o bloco não está no teu feed local", melodyUploadBcsTitle: "Publicar", @@ -3570,14 +4755,14 @@ module.exports = { larpBadgeYou: "Tu", larpBadgeRuling: "Governa", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Módulo para a camada de jogo de interpretação ao vivo do Oasis (as 9 Casas).", + modulesLarpDescription: "Módulo para a camada de jogo de papéis ao vivo.", larpProfileQ1: "Diante de um problema complexo, o que fazes primeiro?", larpProfileQ1O1: "Falar com outras pessoas para procurar consenso", larpProfileQ1O2: "Construir um protótipo para testar", larpProfileQ1O3: "Investigar a literatura", larpProfileQ1O4: "Sabotar o sistema que o provoca", larpProfileQ2: "O que dá sentido ao teu trabalho diário?", - larpProfileQ2O1: "Defender as pessoas que amo", + larpProfileQ2O1: "Defender os habitantes que amo", larpProfileQ2O2: "Criar algo tangível", larpProfileQ2O3: "Curar ou cuidar da vida", larpProfileQ2O4: "Trabalhar com palavras e ideias", @@ -3644,7 +4829,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Seleciona o modo de navegação UX para a tua interface.", uxModeMenus: "Blocos", - uxModeAINav: "AI", + uxModeChats: "Conversas", + uxModeFeed: "Microblogging", + feedTrendingTitle: "Tags em Tendência", + feedActiveTitle: "Habitantes Ativos", + cnBrandSub: "Livre · P2P · Federada", + cnSyncedPeers: "Pares sincronizados", + cnLicense: "Licença", + cnNotAccessible: "O conteúdo não está acessível neste momento.", + cnUntitled: "Sem título", + cnSearchPlaceholder: "Procurar…", + cnHubTitle: "HUB Clearnet", + cnHubDescription: "Conteúdo público partilhado pelos habitantes desta rede Oasis.", + cnHubEmpty: "Ainda não foi partilhado conteúdo público na Clearnet.", + cnPublicContent: "Conteúdo", + cnTribesContent: "Tribos", + cnNetworkContent: "Rede", + cnCategoryEmpty: "Não há conteúdo nesta categoria.", + cnInhabitantEmpty: "Este habitante ainda não publicou conteúdo na Clearnet.", + cnPrevPage: "← Anterior", + cnNextPage: "Seguinte →", + cnPageOf: "Página {page} de {pages}", + cnMoreInfo: "Mais informações →", + cnOpenCourse: "Curso aberto", + cnAuction: "LEILÃO", + cnExchange: "TROCA", + cnRssDescription: "publicados pelos habitantes deste HUB", + cnKindAudio: "Áudio", + cnKindBlog: "Blog", + cnKindBookmark: "Marcador", + cnKindDocument: "Documento", + cnKindEvent: "Evento", + cnKindFeed: "Feed", + cnKindImage: "Imagem", + cnKindJob: "Emprego", + cnKindMarket: "Mercado", + cnKindPodcast: "Podcast", + cnKindProject: "Projeto", + cnKindCourse: "Curso", + cnKindShop: "Loja", + cnKindTorrent: "Torrent", + cnKindFile: "Ficheiro", + cnKindVideo: "Vídeo", + cnKindWiki: "Wiki", + cnKindRoom: "Sala", + cnKindMap: "Mapa", + uxModeAINav: "IA", } }; diff --git a/src/client/assets/translations/oasis_ru.js b/src/client/assets/translations/oasis_ru.js index cd9fee9..c16775f 100644 --- a/src/client/assets/translations/oasis_ru.js +++ b/src/client/assets/translations/oasis_ru.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "Покупки", shopMyOrdersDescription: "Ваши заказы.", shopMyOrdersEmpty: "У вас пока нет покупок.", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM Продавец", shopOrderSeller: "Продавец", shopOrderInvoice: "Счёт", shopOrderInvoiceConcept: "Счёт", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "Принять", shopOrderReject: "Отклонить", shopOrderMarkShipped: "Отметить отправленным", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM Покупатель", shopOrderContractConcept: "Заказ магазина", shopOrdersPending: "Ожидающие заказы", all: "Все", @@ -28,7 +28,7 @@ module.exports = { recent: "Недавние", anonymous: "Аноним", unnamed: "Аноним", - authorLabel: "АВТОР", + authorLabel: "Автор", delete: "Удалить", save: "Сохранить", vote: "Голосовать", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "Предупреждение о контенте", invalidPost: "Недопустимое содержимое", invalidPostHint: "В этом сообщении пустой/недопустимый текст.", - spreadBy: "От", spreadChron: "Распространение", spreaded: "Распространено", spreadContentUnavailable: "Контент пока недоступен (ожидается репликация)", filteredByTag: "Отфильтровано по тегу", filteredByTagTitle: "Отфильтровано по тегу", feedPublishedSuccess: "Лента успешно опубликована!", + feedMastodonFailed: "Опубликовано в Oasis, но отправить в Mastodon не удалось.", tribeFeedSent: "Сообщение успешно отправлено!", tribeContentEncrypted: "Зашифрованное содержимое", tribeTorrentsEmpty: "Торрентов пока нет.", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "Последний визит", profileClearnetBookmarksLabel: "Закладки", invitesPort: "Порт", - currentlyUnrecheable: "ОШИБКА!", - peerHostValidation: "Корректный IPv4 (напр. 192.168.1.100) или имя хоста (напр. pub.example.com)", + peerHostValidation: "Корректный IPv4 (напр. 192.168.1.100) или имя хоста (напр. pub.solarnethub.com)", peerPortValidation: "Порт 1-65535", peerKeyValidation: "Открытый ключ SSB ed25519 (@<44 символа base64>=.ed25519)", inboxJobUnsubscribedTitle: "Отписка от вашей вакансии", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "Описание задачи", exportPasswordLabel: "Пароль экспорта", importPasswordPlaceholder: "Введите пароль", - searchCasesPlaceholder: "Поиск дел…", + searchCasesPlaceholder: "Поиск в Суды...", shopBuyDeliveryAddressPlaceholder: "Адрес доставки", shopBuyNotesPlaceholder: "Заметки", bankTaxesUserNoteBold: "прямое снижение ECO-налога", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "Поддержать дело", courtsSettlementAcceptBtn: "Принять соглашение", courtsSupportCount: "Поддержавшие", - courtsVerdictVoteTitle: "Голосование по вердикту", - courtsVerdictVoteLabel: "Голос по вердикту", - courtsVerdictVoteAccept: "Принять", - courtsVerdictVoteReject: "Отклонить", - courtsVerdictVoteSubmit: "Отправить голос", reportsSetStatus: "Установить статус", reportsStatusClosed: "ЗАКРЫТО", statsAvgPerInhabitant: "Среднее на жителя", @@ -190,9 +184,29 @@ module.exports = { ], profile: "Аватар", inhabitants: "Жители", - peersReplicatedFeeds: "Реплицированные ленты", + peersReplicatedFeeds: "Реплицированные", + peersTorColumn: "TOR", + peersTorYes: "Доступен через Tor", + peersTorNo: "Недоступен через Tor", + peersOasisId: "Oasis ID", + peersOasisVersion: "Версия", graphos: "Графос", graphosDescription: "Интерактивная карта сети вокруг вас.", + graphosFederation: "Федерация", + graphosFedPubs: "PUB", + graphosFedUnreachable: "Недоступны", + graphosFedLinks: "Взаимные связи", + graphosFedIsolated: "Изолированы", + graphosFedNoData: "Нет данных", + graphosFedMutual: "Взаимная", + graphosFedOneWay: "Односторонняя", + graphosFedTransport: "Сеть", + graphosFedWith: "Федерирован с", + graphosZoom: "Увеличить", + graphosFedInhabitants: "Жители", + graphosFedClearnet: "Интернет", + graphosFedBoth: "Интернет + TOR", + graphosFedEmpty: "Ваш узел пока не знает ни одного PUB.", graphosViewingGraphOf: "Просмотр", graphosBackToMine: "← Моя сеть", graphosShowing: "Показано", @@ -204,10 +218,27 @@ module.exports = { ".", ], noMentions: "Вы ещё не получали @упоминаний.", - mentionsSearchPlaceholder: "Поиск упоминаний...", + mentionsSearchPlaceholder: "Поиск в Упоминания...", private: "Входящие", + privateDescription: "Управляйте своими личными сообщениями.", privateInbox: "ВХОДЯЩИЕ", privateReminders: "НАПОМИНАНИЯ", + inboxFilterPms: "ЛС", + inboxFilterNotifications: "Уведомления", + inboxMarkRead: "Отметить прочитанным", + inboxMarkUnread: "Отметить непрочитанным", + inboxUnreadChip: "Не прочитано", + inboxMarkAllRead: "Отметить все прочитанными", + inboxDeleteShown: "Удалить все", + inboxArchive: "В архив", + inboxUnarchive: "Из архива", + inboxFilterArchived: "Архив", + inboxEarlierMessages: "предыдущих сообщений", + inboxSortThread: "По темам", + inboxSortRecent: "Сначала новые", + inboxAllBots: "Боты", + settingsInboxBotsTitle: "Уведомления Inbox", + settingsInboxBotsDesc: "Выберите, какие боты могут уведомлять вас в Inbox. Снятые боты скрываются, и их уведомления не учитываются.", inboxFiltersLabel: "Фильтры:", inboxToggleToMutuals: "Переключить на взаимных", inboxToggleToWhole: "Переключить на всех", @@ -218,18 +249,19 @@ module.exports = { pmReply: "Ответить", pmReplies: "ответы", inReplyTo: "В ОТВЕТ НА", + forumInThread: "В ТЕМЕ", pmPreview: "Предпросмотр", pmPreviewTitle: "Предпросмотр сообщения", noPrivateMessages: "Вы ещё не получали личных сообщений.", peers: "Узлы", - privateDescription: ["Личные сообщения ",strong("зашифрованы вашим открытым ключом")," и могут иметь максимум 7 получателей."], search: "Поиск", searchDescription: "Описание", searchFromLabel: "С", searchToLabel: "До", searchQueryLabel: "Запрос", - searchPerPageLabel: "Результатов на странице", + searchPerPageLabel: "Результаты", settings: "Настройки", + settingsDescription: "Настройте окружение вашей сети.", continueReading: "Продолжить чтение", moreComments: "ещё комментарий", readThread: "прочитать остальное обсуждение", @@ -250,7 +282,6 @@ module.exports = { profileClearnetHelp: 'Модули, доступные извне Oasis.', profileHubAll: 'Все', footerLicenseLabel: 'Лицензия', - profileSwitchToOasis: 'Вернуться в Oasis', profileVisibilityFediverse: "Мультивселенная", contributorsTitle: "Участники", colorLabel: 'Выберите цвет', @@ -260,6 +291,7 @@ module.exports = { modulesViewTitle: "Модули", modulesViewDescription: "Настройте свою среду, включая или отключая модули.", inbox: "Входящие", + inboxSearchPlaceholder: "Поиск в Входящие...", multiverse: "Мультивселенная", fediverse: "Мультивселенная", fediverseDescription: "Управляйте своими другими аккаунтами мультивселенной, включая отправку и получение контента.", @@ -268,9 +300,17 @@ module.exports = { fediverseSettingsTitle: "Мультивселенная", fediverseInstanceLabel: "Адрес", fediverseTokenLabel: "Токен доступа", - fediverseTokenHelp: "Укажите ваш токен доступа. Он будет использоваться для управления вашим аккаунтом Mastodon из Oasis.", fediverseConnect: "Подключить", fediverseConnectedAs: "Подключено как", + nextcloudServerLabel: "Сервер", + nextcloudUserLabel: "Пользователь", + nextcloudFolderLabel: "Папка", + nextcloudUploadButton: "Загрузить", + nextcloudFileLabel: "Выберите файл", + fileExternalLink: "Внешняя ссылка", + nextcloudErrConnect: "Не удалось связаться с Nextcloud с этими данными.", + nextcloudErrUpload: "Загрузка в Nextcloud не удалась.", + nextcloudErrShare: "Nextcloud не дал публичную ссылку на файл.", fediverseDisconnect: "Отключить", fediverseEmptyLink: "настроек", fediverseComposePlaceholder: "О чём вы думаете?", @@ -299,6 +339,76 @@ module.exports = { fediverseErrPost: "Не удалось опубликовать.", fediverseErrMedia: "Не удалось загрузить файл.", fediverseErrEmpty: "Напишите что-нибудь или прикрепите файл.", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "Номер телефона", + telegramSendCode: "Отправить код", + telegramCodeHelp: "Введите код, который Telegram только что отправил (приложение или SMS).", + telegramCodeLabel: "Код", + telegramPasswordHelp: "У аккаунта включена двухэтапная проверка. Введите облачный пароль.", + telegramPasswordLabel: "Пароль", + telegramVerify: "Подтвердить", + telegramCancel: "Отмена", + telegramChats: "Чаты", + telegramUnread: "Непрочитанные", + telegramOpen: "Открыть", + telegramSend: "Отправить", + telegramYou: "Вы", + telegramNoChats: "Чатов пока нет.", + telegramNoMessages: "Сообщений пока нет.", + telegramComposePlaceholder: "Напишите сообщение…", + telegramChannel: "Канал", + telegramGroup: "Группа", + telegramPrivate: "Личный", + telegramErrConnect: "Не удалось связаться с Telegram.", + telegramErrTimeout: "Telegram слишком долго не отвечал.", + telegramErrAuth: "Сессия Telegram недействительна или истекла. Подключитесь снова.", + telegramErrFetch: "Не удалось загрузить чаты.", + telegramErrSend: "Не удалось отправить сообщение.", + telegramErrMissing: "Нужны API ID, API hash и номер телефона.", + telegramErrCode: "Неверный код. Попробуйте ещё раз.", + telegramErrCodeExpired: "Код истёк. Начните заново.", + telegramErrPassword: "Неверный пароль.", + telegramErrPhone: "Неверный номер телефона.", + telegramErrApi: "Неверные API ID или API hash.", + telegramErrFlood: "Слишком много попыток. Подождите и попробуйте снова.", + telegramErrNoLogin: "Вход не начат. Начните заново.", + peertubeUsernameLabel: "Имя пользователя", + peertubePasswordLabel: "Пароль", + peertubeSubscriptions: "Подписки", + peertubeMyVideos: "Мои видео", + peertubeVideos: "Видео", + peertubeFollowers: "Подписчики", + peertubeViews: "просмотров", + peertubeLikes: "лайков", + peertubeQuota: "Квота", + peertubeUpload: "Загрузить видео", + peertubeUploadHelp: "Выберите видеофайл. Он загружается в ваш канал, затем PeerTube его обрабатывает.", + peertubeTitleLabel: "Название", + peertubeDescriptionLabel: "Описание", + peertubePrivacyLabel: "Приватность", + peertubePrivacyPublic: "Публичное", + peertubePrivacyUnlisted: "По ссылке", + peertubePrivacyPrivate: "Приватное", + peertubeNoVideos: "Видео пока нет.", + peertubeComments: "Комментарии", + peertubeComment: "Комментировать", + peertubeCommentPlaceholder: "Напишите комментарий…", + peertubeNoComments: "Комментариев пока нет.", + peertubeOpen: "Открыть на PeerTube", + peertubeWatch: "Смотреть", + peertubeChannel: "Канал", + peertubeNoStream: "Это видео пока нельзя воспроизвести здесь. Откройте его на PeerTube.", + peertubeUploaded: "Видео загружено. PeerTube обрабатывает его.", + peertubeErrConnect: "Не удалось связаться с PeerTube.", + peertubeErrAuth: "Неверное имя пользователя или пароль, либо сессия истекла. Подключитесь снова.", + peertubeErrInstance: "Неверный адрес инстанса.", + peertubeErrMissing: "Нужны инстанс, имя пользователя и пароль.", + peertubeErrFetch: "Не удалось загрузить видео.", + peertubeErrUpload: "Не удалось загрузить видео на сервер.", + peertubeErrTitle: "Нужно название.", + peertubeErrComment: "Не удалось опубликовать комментарий.", + peertubeErrNoFile: "Выберите видеофайл.", bookmarksLabel: "Закладки", videosLabel: "Видео", torrentsLabel: "Торренты", @@ -321,7 +431,6 @@ module.exports = { comment: "Комментарий", subtopic: "Подтема", json: "JSON", - createdBy: "автор", unfollow: "Отменить поддержку", follow: "Поддержать", block: "Заблокировать", @@ -337,12 +446,15 @@ module.exports = { relationshipNone: "Вы не поддерживаете", relationshipBlockingPost: "Заблокированная запись", viewLikes: "Посмотреть распространения", + viewSupporters: "Посмотреть сторонников", spreadedDescription: "Список записей, распространённых жителем.", preview: "Предпросмотр", publish: "Написать", contentWarningPlaceholder: "Добавьте тему к записи (необязательно)", - publishWarningPlaceholder: "Текст ограничен 7000 символами.", + publishWarningPlaceholder: "Напишите текст вашей записи.", publishTooLong: "Ваш пост слишком длинный. Пожалуйста, сократите его.", + keepTextRetry: "Отправить снова", + dateInPastError: "Дата не может быть в прошлом", publishCustomDescription: [ "ПОМНИТЕ: Благодаря технологии блокчейн, после публикации запись невозможно отредактировать или удалить.", ], @@ -372,9 +484,8 @@ module.exports = { mentionsMatching: "Совпадающие упоминания", mentionsName: "Имя", mentionsRelationship: "Отношение", - updateit: "ОБНОВИТЬ!", updateBannerText: "Доступна новая версия Oasis.", - updateBannerAction: "Обновить сейчас →", + updateBannerAction: "Обновить сейчас!", info: "Информация", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -382,16 +493,14 @@ module.exports = { timeAgo: "назад", sendTime: "примерно ", theme: "Тема", - legacy: "Ключи", - legacyTitle: "Ключи", - legacyDescription: "Быстро и безопасно управляйте своим секретом (приватным ключом).", - legacyExportButton: "Экспорт", - legacyImportButton: "Импорт", + backupKeysExportButton: "Экспорт", + backupKeysImportButton: "Импорт", ssbLogStream: "Блокчейн", ssbLogStreamDescription: "Настройте лимит сообщений для потоков блокчейна.", + logWindowChip: "Последние {n} потоков блокчейна", saveSettings: "Сохранить настройки", exportDescription: "Установите пароль (минимум 32 символа) для шифрования вашего ключа", - exportDataTitle: "Резервная копия", + exportDataTitle: "Мгновенная копия", exportDataDescription: "Скачайте ваши данные (без секретного ключа!)", exportDataButton: "Скачать базу данных", importDescription: "Импортируйте ваш зашифрованный секрет (приватный ключ) для активации аватара", @@ -400,38 +509,33 @@ module.exports = { fileInfo: "Ваш зашифрованный секретный ключ будет скачан на ваше устройство (имя файла: oasis.enc)", developer: "Разработка", devTitle: "Разработка", - welcomeBannerText: "Добро пожаловать в OASIS. Выполните эти быстрые шаги, чтобы настроить свой аватар.", + welcomeBannerText: "Твой аватар ещё не настроен.", aiSuggestionBanner: "Совет:", aiSuggestionsEnable: "Советы ИИ", - welcomeBannerAction: "Начать!", + welcomeBannerAction: "Начать", welcomeTitle: "Добро пожаловать", + welcomeStepIdentity: "Личность", + welcomeStepType: "Тип", + welcomeStepUse: "Использование", + welcomeStepLook: "Внешний вид", + welcomeStepCommunity: "Сообщество", + welcomeStepIntro: "Приветствие", + welcomeHintIdentity: "Ваше имя и изображение.", + welcomeHintType: "Как Oasis открывается для вас.", + welcomeHintUse: "Какие модули остаются включёнными.", + welcomeHintLook: "Тема, которую носит Oasis.", + welcomeHintCommunity: "Войдите в PUB, чтобы познакомиться с другими жителями.", + welcomeHintIntro: "Отправьте первое приветствие сообществу.", + welcomeStepThemeAction: "Использовать эту тему", + welcomeJoinPubs: "Войти в PUB", + welcomeNoFederation: "Без федерации", welcomeStepGreetingAction: "Отправить", - welcomeJoinPubButton: "Войти в паб", - welcomeStepLarpAction: "Вступить в «Академию»", - welcomeStepLarpText: "Присоединяйтесь к нашей ролевой игре живого действия.", - welcomeStepLarpTitle: "Присоединиться к L.A.R.P.", - welcomeStepGreetingTitle: "Отправьте «Привет, мир!»", - welcomeStepGreetingText: "Отправьте сообщение, чтобы другие жители могли вас найти.", - welcomeJoinPub: "Присоединиться:", welcomeDescription: "Несколько вещей, которые стоит сделать перед началом.", - welcomeProgress: "Выполнено", - welcomeStepDone: "готово", - welcomeStepPending: "не сделано", welcomeSkip: "Не сейчас", - welcomeStepLanguageTitle: "Выберите язык", - welcomeStepLanguageText: "Oasis говорит на нескольких языках. Изменить можно в любой момент.", - welcomeStepProfileTitle: "Заполните профиль", - welcomeStepProfileText: "Выберите имя, добавьте описание и поставьте изображение, чтобы другие жители вас узнавали.", welcomeStepProfileAction: "Сохранить профиль", - welcomeStepFederationTitle: "Присоединитесь к основной сети", - welcomeStepFederationText: "Подключитесь к нашему пабу, чтобы встретить других жителей и начать репликацию.", - welcomeStepFederationAction: "К приглашениям", - welcomeStepBackupTitle: "Сохраните свой ID", - welcomeStepBackupText: "Ваша личность — это файл ключа на этом устройстве.", - welcomeStepBackupWarning: "ЕСЛИ ВЫ ЕГО ПОТЕРЯЕТЕ, НИКТО НЕ СМОЖЕТ ЕГО ВОССТАНОВИТЬ.", - welcomeStepBackupAction: "Резервная копия!", + welcomeBootstrapRunning: "Загружается снимок паба. По завершении паб будет подключён автоматически; тем временем можно продолжать пользоваться Oasis.", welcomeCompleteTitle: "Всё готово.", - welcomeCompleteText: "Ваш узел готов. Дальше сеть растёт вместе с теми, на кого вы подписаны.", + welcomeCompleteText: "Ваш узел готов. Дальше сеть растёт вместе с жителями, которых вы поддерживаете.", welcomeFindPeople: "Найти жителей", devFilterFiles: "ФАЙЛЫ", devFilterSearch: "ПОИСК", @@ -441,7 +545,7 @@ module.exports = { devFilterTests: "ТЕСТЫ", devVersion: "Версия", devNodeVersion: "Node", - devStatsTests: "Наборы тестов", + devStatsTests: "Тесты", devParentFolder: "Родительская папка", devOpenFolder: "открыть", devDescription: "Изучайте исходный код, работающий на этом узле.", @@ -485,6 +589,27 @@ module.exports = { "Если вы хотите использовать другой язык, выберите его здесь.", setLanguage: "Установить язык", peerConnectionsIntro: "Управляйте всеми подключениями к другим узлам.", + peersConnectFailed: "Не удалось подключиться: {reason}.", + peerStateLabel: "Состояние", + peerAddressLabel: "Адрес", + peerLastChangeLabel: "Последнее изменение", + peerStateConnected: "Подключён", + peerStateConnecting: "Подключение", + peerStateStaged: "Доступен", + peerErrSilent: "Нет активности", + peerErrTimeout: "Не отвечает", + peerErrRefused: "Отклоняет подключение", + peerErrNotFound: "Неизвестный адрес", + peerErrUnreachable: "Сеть недоступна", + peerErrKeys: "Использует другие ключи", + peerErrIdentity: "Не принимает ваше подключение", + peerErrTor: "Только TOR", + peerErrDropped: "Соединение прервано", + peerErrPaused: "Сеть приостановлена", + peerErrOther: "Ошибка подключения", + peerOutdated: "Устарел", + peerVersionUnannounced: "Не сообщает свою версию: старый Oasis", + peersTorMissing: "Этот PUB принимает подключения только через TOR.", peerConnectionsTitle: "Подключения", online: "В сети", offline: "Не в сети", @@ -503,22 +628,25 @@ module.exports = { noConnections: "Нет подключённых узлов.", noDiscovered: "Нет обнаруженных узлов.", noUnknownPeers: "Нет неизвестных пиров в графе друзей.", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "Пока нет зарегистрированных узлов.", + peerConnect: "Подключить", + peerDisconnect: "Отключить", peerRefresh: "Обновить", peerPruneIdle: "Удалить неактивные", + peersPause: "Пауза", + peersResume: "Продолжить", + peersPausedNote: "На паузе: Oasis ни с кем не соединяется, и к нему никто не может подключиться.", + peersPauseHint: "Останавливает все соединения: Oasis не подключается к другим узлам и не принимает их, пока вы не возобновите.", peerExport: "Экспорт", peerImport: "Импорт", peerImportTitle: "Импорт списка пиров", peerImportPlaceholder: "Вставьте по одному multiserver-адресу на строку или загрузите .txt файл…", connectionActionIntro: "", - startNetworking: "Запустить сеть", - stopNetworking: "Остановить сеть", - restartNetworking: "Перезапустить сеть", - sync: "Синхронизировать сеть", indexes: "Индексы", + indexesRebuilt: "Индексы перестроены", + indexesRebuildFailed: "Не удалось перестроить индексы", + indexesFiles: "Файлы индексов", indexesDescription: "Перестройка индексов безопасна и может исправить некоторые типы ошибок.", homePageTitle: "Главная", @@ -528,44 +656,33 @@ module.exports = { invitesDescription: "Управляйте и применяйте коды приглашений в вашей сети.", invitesPubsHint: "Вставьте пригласительный код паба, чтобы федерироваться с ним и начать реплицировать его жителей.", invitesFederationsHint: "Пабы, с которыми вы федерированы: обновите их, удалите недоступные или экспортируйте список.", - invitesHousesHint: "Введите код дома, чтобы вступить в дом LARP и участвовать в его экономике.", - invitesTribesHint: "Введите код племени, чтобы стать участником и получить доступ к его закрытому содержимому.", - invitesChatsHint: "Введите код чата, чтобы присоединиться к беседе и её темам.", - invitesPadsHint: "Введите код пада, чтобы вместе писать в общем документе.", - invitesCalendarsHint: "Введите код календаря, чтобы делиться датами и напоминаниями.", - invitesEventsHint: "Введите код события, чтобы присоединиться к закрытому событию.", - invitesForumsHint: "Введите код форума, чтобы читать и отвечать в закрытом форуме.", - invitesMapsHint: "Введите код карты, чтобы видеть и добавлять места на общей карте.", - invitesShopsHint: "Введите код магазина, чтобы присоединиться к магазину и его каталогу.", - invitesInhabitantsHint: "Введите код жителя, чтобы подписаться друг на друга и обмениваться содержимым.", invitesTribesTitle: "Племена", - invitesTribeInviteCodePlaceholder: "Введите код приглашения племени", - invitesTribeJoinButton: "Вступить в племя", invitesChatsTitle: "Чаты", - invitesChatInviteCodePlaceholder: "Введите код приглашения чата", - invitesChatJoinButton: "Войти в чат", invitesPadsTitle: "Пады", - invitesPadInviteCodePlaceholder: "Введите код приглашения пада", - invitesPadJoinButton: "Войти в пад", invitesCalendarsTitle: "Календари", - invitesCalendarInviteCodePlaceholder: "Введите код приглашения календаря", - invitesCalendarJoinButton: "Войти в календарь", invitesEventsTitle: "События", - invitesEventInviteCodePlaceholder: "Введите код приглашения события", - invitesEventJoinButton: "Войти в событие", invitesForumsTitle: "Форумы", - invitesForumInviteCodePlaceholder: "Введите код приглашения форума", - invitesForumJoinButton: "Войти на форум", invitesMapsTitle: "Карты", - invitesMapInviteCodePlaceholder: "Введите код приглашения карты", - invitesMapJoinButton: "Войти на карту", invitesShopsTitle: "Магазины", - invitesShopInviteCodePlaceholder: "Введите код приглашения магазина", - invitesShopJoinButton: "Войти в магазин", invitesPubsTitle: "PUB", + invitesSuggestedPubs: "Рекомендуемые PUB", + invitesFilterOthers: "Другие", + invitesJoinTitle: "Приглашения", + invitesJoinHint: "Вставьте любой пригласительный код, для чего бы он ни был, или Oasis ID жителя.", + invitesJoinPlaceholder: "Пригласительный код или Oasis ID", + invitesJoinButton: "Войти", + invitesUsedTitle: "Последние приглашения", + invitesUsedEmpty: "Вы ещё не использовали ни одного приглашения.", + invitesUsedType: "Тип", + invitesUsedName: "Название", + invitesUsedDate: "Дата", invitesPubInviteCodePlaceholder: "Введите код приглашения PUB", invitesAcceptInvite: "Присоединиться к PUB", + invitesPubUses: "Использовано приглашений", + invitesPubExhausted: "У этого PUB не осталось приглашений.", invitesAlreadyFederated: "Вы уже федерированы с этим пабом.", + invitesJoinPubMode: "Этот Oasis работает как PUB в режиме только для чтения. Чтобы присоединить его к другому PUB, выполните в терминале: ./oasis.sh join <код>", + invitesJoinLocalOnly: "Приглашения можно принять только на устройстве, где работает Oasis, или по ссылке администратора, которую он показывает при запуске.", invitesFederationsTitle: "Федерации", invitesAcceptedInvites: "Федеративные", invitesUnfollow: "Отписаться", @@ -573,16 +690,12 @@ module.exports = { invitesUnfollowedInvites: "Нефедеративные", invitesNoFederatedPubs: "Нет федеративных pub-ов.", invitesNoUnfollowed: "Нет нефедеративных pub-ов.", - invitesUnreachablePubs: "Недоступные", - invitesNoUnreachablePubs: "Нет недоступных pub-ов.", invitesPubsRefresh: "Обновить", - invitesPubsClearUnreachable: "Удалить недоступные", + invitesPubsClearUnreachable: "Очистить", invitesPubsExport: "Экспорт", invitesPubsImport: "Импорт", invitesPubsImportTitle: "Импорт pub-ов", invitesPubsImportPlaceholder: "Вставьте один multiserver-адрес или код приглашения на строку, или загрузите .txt файл…", - currentlyUnreachable: "ОШИБКА!", - errorDetails: "Подробности ошибки", genericError: "Произошла ошибка.", panicMode: "Режим паники!", encryptData: "Установите пароль (минимум 32 символа) для шифрования вашего блокчейна", @@ -609,7 +722,6 @@ module.exports = { votesOptionNoLabel:"Против", votesOptionAbstentionLabel:"Воздержание", votesQuestionLabel:"Вопрос", - votesCreatedByLabel:"Создано", voteStatusOpen:"ОТКРЫТО", voteStatusClosed:"ЗАКРЫТО", commentDescription: ({ parentUrl }) => [ @@ -626,22 +738,22 @@ module.exports = { searchSubmit: "Искать...", votesLabel: "ГОЛОСОВАНИЯ", pdfFallbackLabel: "PDF документ", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "МАГАЗИНЫ", + voteLabel: "ГОЛОСА", submit: "Отправить", editProfile: "Редактировать аватар", oasisIdLabel: "OasisID", spreadContent: "Реплицировать", welcomePmSubject: "Hello World!", - welcomePmBody: "Добро пожаловать в Oasis — свободная, одноранговая, федеративная и зашифрованная социальная сеть с распределённой архитектурой только по приглашениям.\n\nНесколько интересных мест, с которых можно начать:\n\n- /profile — Отредактируй аватар, имя, описание и какие модули видны на твоей публичной стороне.\n- /invites — Добавь pub, чтобы федерироваться с остальной сетью.\n- /peers — Посмотри, кто на связи, пересканируй LAN, экспортируй или импортируй списки пиров.\n- /inhabitants — Открывай и заходи к аватарам других людей.\n- /tribes — Создавай или вступай в приватные группы со сквозным шифрованием.\n- /inbox — Читай и отправляй приватные сообщения.\n- /activity — Смотри свежую активность — свою и твоей сети.\n- /publish — Напиши пост или поделись изображением, аудио, видео или документом.\n- /search — Ищите содержимое сети по типу.\n\nНесколько интересных мест для подключения:\n\n- /мультивселенной — Управляйте своими другими аккаунтами мультивселенной, включая отправку и получение контента.\n\nЭто сообщение было отправлено приватно от тебя самому себе, поэтому для его получения не требовалось соединение.", + welcomePmBody: "Добро пожаловать в Oasis — свободная, одноранговая, федеративная и зашифрованная социальная сеть с распределённой архитектурой только по приглашениям.\n\nНесколько интересных мест, с которых можно начать:\n\n- [/avatar](/profile) — Отредактируй аватар, имя, описание и какие модули видны на твоей публичной стороне.\n- /settings — Настройте локальный экземпляр: язык, тема, модули, приватность и синхронизация.\n- /invites — Добавь pub, чтобы федерироваться с остальной сетью.\n- /peers — Посмотри, кто на связи, пересканируй LAN, экспортируй или импортируй списки пиров.\n- /inhabitants — Открывай и заходи к аватарам других жителей.\n- /tribes — Создавай или вступай в приватные группы со сквозным шифрованием.\n- /search — Ищите содержимое сети по типу.\n\nНесколько интересных мест для подключения:\n\n- /мультивселенной — Управляйте своими другими аккаунтами мультивселенной, включая отправку и получение контента.\n\nЭто сообщение было отправлено приватно от тебя самому себе, поэтому для его получения не требовалось соединение.", profileSensorsSectionTitle: "Сенсоры", profileVisibilityActivity: "Уровень активности", profileDeviceLockedHint: "Этот датчик всегда включён: он показывает другим, с какого устройства вы подключаетесь, и не может быть отключён.", profileVisibilityKarma: "Карма", - profileVisibilityUbi: "UBI", - profileVisibilityWallet: "Кошелёк ECOIN", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityUbi: "ББД", + profileVisibilityWallet: "Кошелёк ECOin", + profileVisibilityEcoTax: "ECO-налог", + profileVisibilityLarpSign: "Печать L.A.R.P.", profileVisibilityGpg: "Ключ GPG", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "Магазины", @@ -654,6 +766,10 @@ module.exports = { profileClearnetImagesLabel: "Изображения", profileClearnetDocumentsLabel: "Документы", profileClearnetTorrentsLabel: "Торренты", + profileClearnetPodcastsLabel: "Подкасты", + profileClearnetMarketLabel: "Рынок", + profileClearnetFeedLabel: "Лента", + profileClearnetWikiLabel: "Вики", editProfileDescription: "", profileName: "Имя", @@ -679,7 +795,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "Подтв.", walletConfirm: "Подтвердить", - walletDescription: "Управляйте цифровыми активами: отправка и получение ECOin, просмотр баланса и история транзакций.", + walletDescription: "Управляйте своим кошельком ECOin.", walletDate: "Дата", walletFee: "Комиссия (чем выше комиссия, тем быстрее обработка транзакции)", walletFeeLine: ({ fee }) => `Комиссия: ECO ${fee}`, @@ -687,6 +803,86 @@ module.exports = { walletReceive: "Получить", walletReset: "Сбросить", walletSend: "Отправить", + walletPayingTransfer: "Оплата перевода", + walletCreateTransferLabel: "Создать смарт-контракт (перевод)", + walletTransferNotifiedNote: "Другая сторона уведомлена об этом платеже", + walletTransferCreatedNote: "Для этого платежа создан перевод (квитанция)", + walletTransferNoPayeeNote: "Никто не опубликовал этот адрес, перевод не создан", + walletTransferReceiptConcept: "Платёж из кошелька", + walletPaymentPmText: "отправил(а) вам платёж", + walletPayeeLabel: "Получатель", + walletConceptLabel: "Назначение", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "Вы получили свой ББД.", + bankingBotPaymentTitle: "Вы получили платёж.", + bankingBotUbiPaidText: "Ваш безусловный базовый доход выплачен", + bankUbiPubThisNode: "Этот узел выплачивает ББД", + bankUbiPubConnected: "PUB ББД", + bankUbiPubNone: "Ни один PUB в вашей сети пока не объявляет ББД", + bankUbiAddressPublished: "Адрес ECOin опубликован", + bankUbiAddressMissing: "Опубликуйте адрес ECOin, чтобы получать ББД", + bankUbiClaimedThisMonth: "Запрошено в этом месяце", + bankUbiPubBalance: "Кошелёк PUB, доступный для ББД", + bankNoPubAddress: "PUB ББД ещё не опубликовал адрес ECOin.", + bankUbiTab: "ББД", + bankDonateEco: "ПОЖЕРТВОВАТЬ!", + donateTooltip: "Пожертвовать ECOin", + bankNoUserAddress: "Этот житель не делится адресом ECOin.", + bankUbiPubDefault: "По умолчанию", + bankUbiPool: "Фонд", + bankUbiLastSeen: "Последний раз в сети", + bankUbiLastPayout: "Последнее действие", + bankUbiPaidOut: "Выплачено", + bankRangeToday: "Сегодня", + bankRangeWeek: "Неделя", + bankRangeMonth: "Месяц", + bankRangeYear: "Год", + bankRangeFiveYears: "5 лет", + bankRangeAll: "Всё", + bankChartValueTitle: "Динамика стоимости ECOin", + bankChartValueHours: "Эквивалент во времени (ч)", + bankChartWealthTitle: "Перераспределение богатства ECOin", + bankChartDistributed: "Выплаченный ББД (ECO)", + bankChartRebalanceTitle: "Перераспределение богатства между PUB", + bankChartRebalanced: "ECO, переданные между PUB", + bankChartTaxes: "Удержанные налоги (ECO)", + bankChartNoDataRange: "Нет данных за этот период.", + bankChartNoDataYet: "Данных пока нет", + bankChartPerHour: "в час", + bankChartPerDay: "в день", + bankChartPerMonth: "в месяц", + bankTotalUbiDistributed: "Всего перераспределено ББД (сеть)", + bankTotalTaxesWithheld: "Всего удержано налогов (сеть)", + bankInflationIssuance: "Темп эмиссии (годовой, по награде за блок)", + bankRulesEligibilityTitle: "Кто может получать ББД", + bankRulesEligibility1: "Адрес ECOin, опубликованный в профиле (Профиль → Сенсоры → Кошелёк ECOin).", + bankRulesEligibility2: "Лента старше 30 дней с активностью в сети: карма выше нуля.", + bankRulesEligibility3: "Один запрос в месяц (эпоху), из Banking кнопкой Запросить ББД. От текущего месяца можно отказаться кнопкой Отказаться от ББД.", + bankRulesEligibility4: "Минимум в 1 ECO выплачивается всегда; налоги уменьшают только часть сверх него и никогда не взимаются отдельно.", + bankRulesEligibility5: "PUB платит в следующем цикле и публикует перевод ББД, который вы можете подтвердить.", + bankRulesEligibility6: "Если нет адреса, лента слишком молода, месяц отклонён или у PUB нет средств, запрос пропускается и записывается в журнал PUB.", + copyLinkTitle: "Скопировать ссылку", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "Отказаться от ББД", + bankUbiRefusedThisMonth: "Вы отказались от ББД этого месяца.", + bankingBotUbiAvailableTitle: "Вы можете запросить ББД.", + bankingBotUbiAvailableText: "Ваш ББД за этот месяц доступен", + bankingBotConfirmTitle: "У вас есть смарт-контракт, ожидающий подтверждения", + bankingBotConfirmText: "Смарт-контракты, ожидающие вашего подтверждения", + bankChartZoomHint: "Нажмите, чтобы увеличить", + bankChartPoolTitle: "Фонд ББД (во времени)", + bankChartNetworkUbiTitle: "Всего перераспределено ББД (сеть)", + bankChartSupplyTitle: "Оборотное предложение ECOin (во времени)", + bankChartInflationTitle: "Инфляция ECOin (во времени)", + bankChartInflationSeries: "Годовая ставка (%)", + bankChartPool: "Общий фонд (ECO)", + bankChartKarmaTitle: "KARMA Scoring (во времени)", + bankChartFundsTitle: "Ваши средства ECOin (во времени)", + bankChartKarma: "Карма", + bankWalletReady: "ECOin подключён", + bankWalletNotConfigured: "ECOin отключён", + transfersPayWithWallet: "ОПЛАТИТЬ!", walletStatus: "Статус", walletDisconnected: [ "ECOin ", @@ -696,8 +892,11 @@ module.exports = { " или состояние подключения.", ], walletSentToLine: ({ destination, amount }) => `Отправлено ECO ${amount} на ${destination}`, + walletNotConfigured: "Ваш кошелёк ECOin ещё не настроен.", walletSettingsDescription: "Интегрируйте Oasis с вашим кошельком ECOin.", - walletSettingsDocLink: "Руководство по установке ECOin", + walletSettingsMobileDescription: "Свяжите Oasis с приложением ECOin Wallet на этом устройстве.", + walletDownloadEcoin: "Скачать ECOin!", + walletRpcUrl: "URL RPC", walletStatusMessages: { invalid_amount: "Неверная сумма", invalid_dest: "Неверный адрес назначения", @@ -713,6 +912,7 @@ module.exports = { walletUser: "Имя пользователя", walletPass: "Пароль", walletConfiguration: "Настроить кошелёк", + walletDisconnectButton: "Отключить кошелёк", cipher: "Шифратор", cipherTitle: "Шифратор", cipherDescription: "Симметричное шифрование и расшифровка текста (с использованием общего пароля).", @@ -746,17 +946,16 @@ module.exports = { bookmarkFilterRecent: "НЕДАВНИЕ", bookmarkCreateButton: "Создать закладку", bookmarkUpdateButton: "Обновить", - bookmarkDeleteButton: "Удалить", bookmarkAddFavoriteButton: "В избранное", bookmarkRemoveFavoriteButton: "Из избранного", bookmarkUrlLabel: "Ссылка", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "Описание", bookmarkDescriptionPlaceholder: "Необязательно", bookmarkTagsLabel: "Теги", bookmarkTagsPlaceholder: "Введите теги через запятую", bookmarkLastVisitLabel: "Последний визит", - bookmarkSearchPlaceholder: "Искать URL, теги, категорию, автора...", + bookmarkSearchPlaceholder: "Поиск в Закладки...", bookmarkSortRecent: "Новейшие", bookmarkSortOldest: "Старейшие", bookmarkSortTop: "Больше голосов", @@ -795,7 +994,7 @@ module.exports = { videoDescriptionPlaceholder: "Необязательно", videoNoFile: "Видеофайл не указан", noVideos: "Нет доступных видео.", - videoSearchPlaceholder: "Искать название, теги, автора...", + videoSearchPlaceholder: "Поиск в Видео...", videoSortRecent: "Новейшие", videoSortOldest: "Старейшие", videoSortTop: "Больше голосов", @@ -818,7 +1017,6 @@ module.exports = { documentFilterFavorites: "ИЗБРАННОЕ", documentCreateButton: "Загрузить документ", documentUpdateButton: "Обновить", - documentDeleteButton: "Удалить", documentAddFavoriteButton: "В избранное", documentRemoveFavoriteButton: "Из избранного", documentMessageAuthorButton: "ЛС", @@ -831,7 +1029,7 @@ module.exports = { documentDescriptionPlaceholder: "Необязательно", documentNoFile: "Нет файла.", noDocuments: "Нет доступных документов.", - documentSearchPlaceholder: "Искать название, теги, описание, автора...", + documentSearchPlaceholder: "Поиск в Документы...", documentSortRecent: "Новейшие", documentSortOldest: "Старейшие", documentSortTop: "Больше голосов", @@ -852,19 +1050,19 @@ module.exports = { audioFilterTop: "ЛУЧШИЕ", audioCreateButton: "Загрузить аудио", audioUpdateButton: "Обновить", - audioDeleteButton: "Удалить", audioAddFavoriteButton: "В избранное", audioRemoveFavoriteButton: "Из избранного", audioFileLabel: "Выберите аудиофайл (.mp3, .wav, .ogg)", audioTagsLabel: "Теги", audioTagsPlaceholder: "Введите теги через запятую", + tagsPlaceholder: "Введите теги через запятую", audioTitleLabel: "Название", audioTitlePlaceholder: "Необязательно", audioDescriptionLabel: "Описание", audioDescriptionPlaceholder: "Необязательно", audioNoFile: "Аудиофайл не указан", noAudios: "Нет доступных аудио.", - audioSearchPlaceholder: "Искать название, теги, автора...", + audioSearchPlaceholder: "Поиск в Аудио...", audioSortRecent: "Новейшие", audioSortOldest: "Старейшие", audioSortTop: "Больше голосов", @@ -876,7 +1074,7 @@ module.exports = { audioFilterFavorites: "ИЗБРАННОЕ", favoritesTitle: "Избранное", favoritesDescription: "Весь ваш избранный контент в одном месте.", - favoritesSearchPlaceholder: "Поиск в избранном...", + favoritesSearchPlaceholder: "Поиск в Избранное...", favoritesFilterAll: "ВСЕ", favoritesFilterRecent: "НЕДАВНИЕ", favoritesFilterAudios: "АУДИО", @@ -888,13 +1086,12 @@ module.exports = { favoritesFilterChats: "ЧАТЫ", favoritesFilterCalendars: "КАЛЕНДАРИ", favoritesFilterVideos: "ВИДЕО", - favoritesRemoveButton: "Убрать из избранного", favoritesNoItems: "Пока нет избранного.", yourContacts: "Ваши контакты", allInhabitants: "Жители", allCVs: "Резюме", discoverPeople: "Находите жителей в вашей сети.", - searchInhabitantsPlaceholder: "ФИЛЬТР жителей ПО ИМЕНИ...", + searchInhabitantsPlaceholder: "Поиск в Жители...", filterLocation: "ФИЛЬТР жителей ПО МЕСТОПОЛОЖЕНИЮ...", filterLanguage: "ФИЛЬТР жителей ПО ЯЗЫКУ...", filterSkills: "ФИЛЬТР жителей ПО НАВЫКАМ...", @@ -918,7 +1115,6 @@ module.exports = { topSectionTitle: "ТОП", blockedLabel: "Заблокированный пользователь", inhabitantviewDetails: "Подробнее", - cvVisitButton: "Открыть резюме", keepReading: "Продолжить чтение...", oasisId: "ID", noInhabitantsFound: "Жители пока не найдены.", @@ -935,6 +1131,18 @@ module.exports = { parliamentFilterLeaders: "ЛИДЕРЫ", parliamentFilterRules: "ПРАВИЛА", parliamentGovernmentCard: "Текущее правительство", + parliamentSeatsGovernment: "Правительство", + parliamentSeatsOpposition: "Оппозиция", + parliamentSeatsAbstention: "Воздержались", + parliamentSeatsQuorum: "Кворум выборов", + parliamentSeatsPass: "Порог одобрения", + parliamentSeatsByElection: "По выборам", + parliamentSeatsByHouses: "По домам LARP", + parliamentSeatsSeats: "мест", + parliamentSeatsSeat: "место", + parliamentSeatsLeader: "Лидер", + parliamentSeatsVotesFor: "голоса за", + parliamentSeatsNoHouse: "Без дома", parliamentActorInPowerInhabitant: 'ПРАВЯЩИЙ ЖИТЕЛЬ', parliamentActorInPowerTribe: 'ПРАВЯЩЕЕ ПЛЕМЯ', parliamentHistoricalGovernmentsTitle: 'ПРАВИТЕЛЬСТВА', @@ -1002,6 +1210,7 @@ module.exports = { parliamentTimeRemaining: "Оставшееся время", parliamentElectionsStatusTitle: "Следующее правительство", parliamentProposalDeadlineLabel: "Крайний срок", + parliamentViewReport: "Посмотреть отчёт", parliamentProposalTimeLeft: "Осталось времени", parliamentRulesTitle: "Как работает Парламент", parliamentRulesIntro: "Выборы проводятся каждые 2 месяца; кандидатуры подаются непрерывно и сбрасываются при выборе правительства.", @@ -1016,6 +1225,7 @@ module.exports = { parliamentRulesLaws: "Когда законопроект набирает необходимый порог, он становится Законом и появляется на вкладке «Законы» с датой принятия.", parliamentRulesHistorical: "На вкладке «История» вы можете увидеть каждый цикл правления и данные о его управлении.", parliamentRulesLeaders: "На вкладке «Лидеры» вы можете увидеть рейтинг жителей/племён, которые правили (или выдвигались), отсортированный по эффективности.", + parliamentRulesHemicycle: "Полукруг даёт одно место каждому жителю: блок правительства (избиратели победившей кандидатуры и лидер), по блоку оппозиции на каждую другую кандидатуру цикла и воздержавшиеся. Пунктирные отметки показывают кворум выборов (25 % от переписи) и порог одобрения текущего метода. «По домам LARP» раскрашивает те же места по домам.", parliamentProposalVoteStatusLabel: "Статус голосования", parliamentProposalOnTrackYes: "Порог достигнут", parliamentProposalOnTrackNo: "Ниже порога", @@ -1029,6 +1239,7 @@ module.exports = { courtsCaseFormTitle: "Открыть дело", courtsCaseRespondent: "Обвиняемый / Ответчик", courtsCaseRespondentPh: "Oasis ID (@...) или название племени", + courtsCaseSubjectPlaceholder: "Тема или краткое описание", courtsCaseMethod: "Метод разрешения", courtsCaseSubmit: "Подать дело", courtsNominateJudge: "Выдвинуть судью", @@ -1062,15 +1273,30 @@ module.exports = { courtsMethodDICTATOR: "Диктатор", courtsMethodPOPULAR: "Народный", courtsMethodKARMATOCRACY: "Карматократия", + courtsCaseType: "Тип дела", + courtsPresetMinorConflict: "Незначительный конфликт", + courtsPresetModerateConflict: "Умеренный конфликт", + courtsPresetSevereConflict: "Серьёзный конфликт", + courtsPresetHarassment: "Домогательства или злоупотребления", + courtsPresetContentModeration: "Модерация контента", + courtsPresetBanRestriction: "Бан или ограничение", + courtsPresetGovernanceDispute: "Спор об управлении", + courtsStatusOPEN: "Открыто", + courtsStatusIN_PROGRESS: "В процессе", + courtsStatusDECIDED: "Решено", + courtsStatusCLOSED: "Закрыто", + courtsStatusSOLVED: "Урегулировано", + courtsStatusUNSOLVED: "Не урегулировано", + courtsStatusDISCARDED: "Отклонено", courtsRulesTitle: "Как работают Суды", courtsRulesIntro: "Суды — это общественный процесс разрешения конфликтов и содействия восстановительному правосудию. Приоритет отдаётся диалогу, чётким доказательствам и соразмерным мерам.", courtsRulesLifecycle: "Процесс: 1) Открытие дела 2) Выбор метода 3) Представление доказательств 4) Слушание и обсуждение 5) Вердикт и средства защиты 6) Исполнение и закрытие 7) Апелляция (при необходимости).", - courtsRulesRoles: "Обвинитель: открывает дело. Защита: обвиняемое лицо или племя. Метод: механизм, выбранный сообществом для содействия, оценки доказательств и вынесения вердикта. Свидетель: предоставляет показания или доказательства. Посредники: нейтральные лица, приглашённые обвинителем и/или защитой, имеющие доступ ко всем деталям, которые помогают деэскалировать конфликт и совместно выработать соглашения.", + courtsRulesRoles: "Обвинитель: открывает дело. Защита: обвиняемый житель или племя. Метод: механизм, выбранный сообществом для содействия, оценки доказательств и вынесения вердикта. Свидетель: предоставляет показания или доказательства. Посредники: нейтральные жители, приглашённые обвинителем и/или защитой, имеющие доступ ко всем деталям, которые помогают деэскалировать конфликт и совместно выработать соглашения.", courtsRulesEvidence: "Описание до 1000 символов. Прикрепляйте релевантные и законные изображения, аудио, видео и PDF-документы. Не передавайте конфиденциальные личные данные без согласия.", courtsRulesDeliberation: "Слушания могут быть публичными или закрытыми. Судьи обеспечивают уважение, запрашивают уточнения и могут отклонить нерелевантные или незаконные материалы.", courtsRulesVerdict: "Приоритет отдаётся восстановлению: извинения, соглашения о медиации, модерация контента, временные ограничения или другие соразмерные меры. Обоснование должно быть зафиксировано.", courtsRulesAppeals: "Апелляция: допускается при наличии новых доказательств или явной процедурной ошибки. Должна быть подана в течение 7 дней, если не указано иное.", - courtsRulesPrivacy: "Уважайте конфиденциальность и безопасность. Доксинг, ненависть или угрозы удаляются. Судьи могут редактировать или засекречивать части записи для защиты людей в зоне риска.", + courtsRulesPrivacy: "Уважайте конфиденциальность и безопасность. Доксинг, ненависть или угрозы удаляются. Судьи могут редактировать или засекречивать части записи для защиты жителей в зоне риска.", courtsRulesMisconduct: "Преследование, манипуляции или фабрикация доказательств могут привести к немедленному негативному решению.", courtsRulesGlossary: "Дело: запись о конфликте. Доказательство: материалы, подтверждающие утверждения. Вердикт: решение с мерой пресечения. Апелляция: запрос на пересмотр вердикта.", courtsFilterActions: "ДЕЙСТВИЯ", @@ -1086,12 +1312,16 @@ module.exports = { exploreTrending: "Изучайте самый популярный контент в вашей сети.", votesButton: "ГОЛОСОВАНИЯ", shopProductButton: "МАГАЗИН", + podcastButton: "ПОДКАСТ", + podcastEpisodeButton: "ЭПИЗОД", + campaignButton: "КАМПАНИЯ", + logisticsRouteButton: "МАРШРУТ", trendingTotalOpinions: "Всего мнений", trendingNoContentMessage: "Трендов пока нет.", trendingTotalCount: "Всего", tasksTitle: "Задачи", tasksDescription: "Находите и управляйте задачами в вашей сети.", - taskSearchPlaceholder: "Поиск задач...", + taskSearchPlaceholder: "Поиск в Задачи...", taskTitleLabel: "Название", taskDescriptionLabel: "Описание", taskStartTimeLabel: "Время начала", @@ -1120,6 +1350,7 @@ module.exports = { taskUpdateButton: "Обновить", taskDeleteButton: "Удалить", taskFilterAll: "ВСЕ", + taskFilterRecent: "НЕДАВНИЕ", taskFilterMine: "МОИ", taskFilterOpen: "ОТКРЫТЫЕ", taskFilterInProgress: "В РАБОТЕ", @@ -1147,14 +1378,13 @@ module.exports = { eventUpcomingDates: "Ближайшие даты", eventsTitle: "События", eventsDescription: "Находите и управляйте событиями в вашей сети.", - eventSearchPlaceholder: "Поиск событий...", + eventSearchPlaceholder: "Поиск в События...", eventPrice: "Цена", eventStatus: "Статус", eventAllSectionTitle: "События", eventMineSectionTitle: "Ваши события", eventCreateSectionTitle: "Создать событие", eventUpdateSectionTitle: "Обновить событие", - eventDeleteButton: "Удалить", eventCreateButton: "Создать событие", eventAddToCalendar: "Добавить в календарь", eventVisitCalendar: "Открыть календарь", @@ -1196,6 +1426,7 @@ module.exports = { eventDate: "Дата", eventAttendButton: "Принять участие", eventFilterAll: "ВСЕ", + eventFilterRecent: "НЕДАВНИЕ", eventFilterMine: "МОИ", eventFilterToday: "СЕГОДНЯ", eventFilterWeek: "ЭТА НЕДЕЛЯ", @@ -1222,7 +1453,7 @@ module.exports = { tagsFilterTop: "ЛУЧШИЕ", tagsFilterCloud: "ОБЛАКО", tagsNoItems: "Нет доступных тегов.", - tagsSearchPlaceholder: "Поиск тегов...", + tagsSearchPlaceholder: "Поиск в Теги...", tagsTableHeaderTag: "ТЕГ/ССЫЛКА:", tagsTableHeaderCount: "СЧЁТЧИК:", transfersTitle: "Переводы", @@ -1230,8 +1461,11 @@ module.exports = { transfersFrom: "От", transfersTo: "Кому", transfersFilterAll: "ВСЕ", + transfersFilterRecent: "НЕДАВНИЕ", transfersFilterMine: "МОИ", - transfersFilterUBI: "UBI", + transfersFilterMinePending: "ОЖИДАЮТ", + transfersFilterMineConfirmed: "ПОДПИСАННЫЕ", + transfersFilterUBI: "ББД", transfersFilterMarket: "РЫНОК", transfersFilterTop: "ЛУЧШИЕ", transfersFilterPending: "ОЖИДАЮЩИЕ", @@ -1245,7 +1479,6 @@ module.exports = { transfersCategoryEconomic: "Экономический", transfersCategoryTime: "Время", transfersCategoryTrust: "Доверие", - transfersCategoryApply: "Применить", transfersUnitEco: "ECO", transfersUnitHours: "ч", transfersUnitTrust: "доверие", @@ -1275,7 +1508,7 @@ module.exports = { transfersDiscardedSectionTitle: "Отменённые переводы", transfersCreateSectionTitle: "Создать перевод", transfersAllSectionTitle: "Переводы", - transfersSearchPlaceholder: "Искать концепцию, теги, пользователей...", + transfersSearchPlaceholder: "Поиск в Переводы...", transfersMinAmountLabel: "Мин. сумма", transfersMaxAmountLabel: "Макс. сумма", transfersSortRecent: "Новейшие", @@ -1297,6 +1530,7 @@ module.exports = { voteErrorTitle: "Проверьте форму", voteErrorDeadlineMin: "Срок должен быть не менее {days} дней от текущего момента.", voteErrorGeneric: "Не удалось сохранить голосование.", + voteLocked: "За это голосование уже голосовали или оставляли мнения, или оно принадлежит Парламенту или Судам: его больше нельзя изменить или удалить.", voteUpdateButton: "Обновить", voteDeleteButton: "Удалить", voteOptionYes: "ЗА", @@ -1306,6 +1540,7 @@ module.exports = { voteFollowMajority: "СЛЕДОВАТЬ БОЛЬШИНСТВУ", voteNotInterested: "НЕ ИНТЕРЕСНО", voteFilterAll: "ВСЕ", + voteFilterRecent: "НЕДАВНИЕ", voteFilterMine: "МОИ", voteFilterOpen: "ОТКРЫТЫЕ", voteFilterClosed: "ЗАКРЫТЫЕ", @@ -1321,7 +1556,6 @@ module.exports = { voteTags: "Теги", voteOpinions: "Мнения", novotes: "Нет доступных голосований.", - voteBy: "Автор", voteCreatedAt: "Создано", voteNoQuestion: "Вопрос не указан", voteUnknownCreator: "Неизвестный автор", @@ -1331,6 +1565,7 @@ module.exports = { voteCommentsForumButton: 'Открыть обсуждение', voteCommentsSectionTitle: 'Открыть обсуждение', voteNoCommentsYet: 'Комментариев пока нет. Будьте первым.', + commentsGoToLast: "Перейти к последнему комментарию", voteNewCommentPlaceholder: 'Напишите ваш комментарий...', voteNewCommentButton: 'Отправить комментарий', voteNewCommentLabel: 'Добавить комментарий', @@ -1341,6 +1576,8 @@ module.exports = { cvNameLabel: "Полное имя", cvDescriptionLabel: "Описание", cvPhotoLabel: "Фото", + cvPdfLabel: "Резюме (PDF)", + cvVisitButton: "Открыть резюме", cvPersonalExperiencesLabel: "Личный опыт", cvPersonalSkillsLabel: "Личные навыки (через запятую)", cvOasisExperiencesLabel: "Опыт вклада в Oasis", @@ -1370,9 +1607,9 @@ module.exports = { cvNoCV: "Резюме не найдено.", blogSubject: "Тема", blogMessage: "Сообщение", - blogImage: "Загрузить медиа (макс: 50MB)", + blogImage: "Загрузить медиа (макс: 75MB)", blogMedia: "Вложения (до 8 изображений и одно видео)", - blogPublish: "Предварительный просмотр", + blogPublish: "Опубликовать", pollsTitle: "Опросы", dataTitle: "Совпадения", dataDescription: "Исследуйте и визуализируйте совпадения в вашей сети.", @@ -1391,12 +1628,23 @@ module.exports = { dataKindTasks: "ЗАДАЧИ", dataKindReports: "ОТЧЁТЫ", dataKindVotes: "ГОЛОСОВАНИЯ", - dataSearchPlaceholder: "Поиск по перекрёстным данным...", + dataSearchPlaceholder: "Поиск в Совпадения...", dataCohesionTitle: "Коэффициент сплочённости (CC)", dataCcShort: "CC", dataCohesionHint: "Средняя близость между всем, что опубликовано в сети.", dataAffinity: "Сходство", dataCommonTerms: "Ключевое слово", + dataReasonFollowing: "Вы поддерживаете этого жителя", + dataReasonTribe: "Состоит с вами в одном племени", + dataReasonPinned: "Совпадает с тем, что вы закрепили", + dataReasonCv: "Совпадает с навыками из вашего резюме", + dataReasonContent: "Связано с тем, что вы публикуете", + dataReasonRelated: "Связано с вашими интересами", + dataReasonAlike: "Опубликовано близким вам по духу жителем", + dataReasonSupportsYou: "Поддерживает вас", + dataReasonMutual: "Взаимная поддержка", + dataReasonRated: "Высоко оценено сетью", + dataReasonNear: "Рядом с вами", dataStatPeople: "Резюме", dataStatConnected: "Связаны", dataStatSkills: "Навыки", @@ -1449,7 +1697,7 @@ module.exports = { pollStatusLabel: "Статус", pollStatusOpen: "ОТКРЫТ", pollStatusClosed: "ЗАКРЫТ", - pollSearchPlaceholder: "Поиск опросов...", + pollSearchPlaceholder: "Поиск в Опросы...", pollsNoItems: "Опросы не найдены.", viewPoll: "Смотреть опрос", pollChatCreate: "Создать опрос", @@ -1462,7 +1710,7 @@ module.exports = { blogFilterFavorites: "ИЗБРАННОЕ", blogFilterTop: "ТОП", blogCreateButton: "Создать блог", - blogSearchPlaceholder: "Поиск блогов...", + blogSearchPlaceholder: "Поиск в Блоги...", blogComments: "Комментарии", blogAllowComments: "Разрешить комментарии", blogCommentsClosed: "Автор закрыл комментарии к этому блогу.", @@ -1475,12 +1723,11 @@ module.exports = { forumCreateButton: "Создать форум", forumCreateSectionTitle: "Создать форум", forumDescription: "Общайтесь открыто с другими жителями вашей сети.", - forumSearchPlaceholder: "Поиск форумов...", + forumSearchPlaceholder: "Поиск в Форумы...", forumFilterAll: "ВСЕ", forumFilterMine: "МОИ", forumFilterRecent: "НЕДАВНИЕ", forumFilterTop: "ТОП", - forumDeleteButton: "Удалить", forumParticipants: "участники", forumMessages: "сообщения", forumMessageLabel: "Сообщение", @@ -1507,7 +1754,6 @@ module.exports = { imageFilterGallery: "ГАЛЕРЕЯ", imageCreateButton: "Загрузить изображение", imageUpdateButton: "Обновить", - imageDeleteButton: "Удалить", imageAddFavoriteButton: "Добавить в избранное", imageRemoveFavoriteButton: "Удалить из избранного", imageFileLabel: "Выбрать файл изображения (.jpeg, .jpg, .png, .gif)", @@ -1519,18 +1765,20 @@ module.exports = { imageDescriptionPlaceholder: "Необязательно", imageNoFile: "Файл изображения не предоставлен", noImages: "Изображений нет.", - imageSearchPlaceholder: "Поиск по названию, тегам, описанию, автору...", + imageSearchPlaceholder: "Поиск в Изображения...", imageSortRecent: "Самые новые", imageSortOldest: "Самые старые", imageSortTop: "С наибольшим рейтингом", imageSearchButton: "Поиск", imageUpdatedAt: "Обновлено", imageNoMatch: "Изображения не найдены.", + feedSearchPlaceholder: "Поиск в Лента...", feedTitle: "Лента", createFeedTitle: "Создать пост", createFeedButton: "Отправить пост!", feedPlaceholder: "Что происходит? (макс 280 символов)", ALLButton: "Посты", + RECENTButton: "НЕДАВНИЕ", MINEButton: "Ваши посты", TODAYButton: "СЕГОДНЯ", TOPButton: "Топ постов", @@ -1545,7 +1793,7 @@ module.exports = { alreadyRefeeded: "Вы уже переопубликовали это.", activityTitle: "Активность", yourActivity: "Ваша активность", - globalActivity: "Глобальная активность", + globalActivity: "Активность", activityList: "Активность", activityDesc: "Смотрите последнюю активность в вашей сети.", allButton: "ВСЕ", @@ -1560,7 +1808,8 @@ module.exports = { meme: "Мем", typeRecent: "НЕДАВНИЕ", typeTop: "ТОП", - typePost: "БЛОГИ", + typePost: "БЛОГ", + activityUpdateLabel: "ОБНОВЛЕНИЕ", typeTribe: "ПЛЕМЕНА", typeLarp: "L.A.R.P.", typeInhabitants: "ЖИТЕЛИ", @@ -1695,7 +1944,7 @@ module.exports = { courtsNoCases: "Дел нет.", reportsTitle: "Отчёты", reportsDescription: "Управляйте отчётами вашей сети и следите за ними.", - reportsSearchPlaceholder: "Поиск отчётов...", + reportsSearchPlaceholder: "Поиск в Отчёты...", reportsFilterAll: "ВСЕ", reportsFilterRecent: "НОВЫЕ", reportsFilterTop: "ТОП", @@ -1743,7 +1992,6 @@ module.exports = { reportsUnderReviewSectionTitle: "Отчёты на рассмотрении", reportsResolvedSectionTitle: "Решённые отчёты", reportsInvalidSectionTitle: "Недействительные отчёты", - reportsTemplateSectionTitle: "Шаблон отчёта", reportsBugTemplateTitle: "Детали воспроизведения (Ошибки)", reportsFeatureTemplateTitle: "Детали (Функции)", reportsAbuseTemplateTitle: "Детали (Злоупотребления)", @@ -1803,17 +2051,15 @@ module.exports = { tribeGallerySectionTitle: "Галерея племён", tribeRecentSectionTitle: "Недавние племена", tribeTopSectionTitle: "Популярные племена", - tribeviewTribeButton: "Посетить племя", - tribeviewSubTribeButton: "Посетить под-племя", tribeRootLabel: "КОРЕНЬ", tribeDescription: "Исследуйте или создавайте племена в вашей сети.", tribeMainTribeLabel: "ГЛАВНОЕ ПЛЕМЯ", tribeCreateButton: "Создать племя", tribeUpdateButton: "Обновить", tribeDeleteButton: "Удалить", - tribeImageLabel: "Загрузить медиа (макс: 50MB)", + tribeImageLabel: "Загрузить медиа (макс: 75MB)", tribeTitleLabel: "Название", - searchTribesPlaceholder: "ФИЛЬТР племён ПО ИМЕНИ …", + searchTribesPlaceholder: "Поиск в Племена...", tribeTitlePlaceholder: "Название племени", tribeDescriptionLabel: "Описание", tribeDescriptionPlaceholder: "Опишите это племя", @@ -1826,6 +2072,15 @@ module.exports = { tribeIsAnonymousLabel: "СТАТУС", tribeMembersCount: "Участники", tribeEnterInvite: "ВВЕСТИ КОД", + subscriptionTitle: "Подписки", + subscriptionOn: "Подписан", + subscriptionOff: "Не подписан", + subscriptionSubscribe: "Подписаться", + subscriptionUnsubscribe: "Отписаться", + subscriptionSubscribers: "Подписчики", + pmListLabel: "Отправить", + pmListNone: "— Без списка —", + pmListNoSubscribers: "Нет подписчиков", tribeLeaveButton: "ПОКИНУТЬ", tribePublic: "ПУБЛИЧНОЕ", tribePrivate: "ЧАСТНОЕ", @@ -1858,16 +2113,16 @@ module.exports = { tribeSectionVideos: "ВИДЕО", tribeSectionDocuments: "ДОКУМЕНТЫ", tribeSectionBookmarks: "ЗАКЛАДКИ", - tribeSectionMaps: "MAPS", - tribeSectionPads: "PADS", - tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionMaps: "КАРТЫ", + tribeSectionPads: "ПАДЫ", + tribeSectionChats: "ЧАТЫ", + tribeSectionCalendars: "КАЛЕНДАРИ", + tribePadCreate: "Создать пад", + tribeChatCreate: "Создать чат", + tribeCalendarCreate: "Создать календарь", + tribePadsEmpty: "Пока нет падов.", + tribeChatsEmpty: "Пока нет чатов.", + tribeCalendarsEmpty: "Пока нет календарей.", tribeInhabitantsEmpty: "В этом племени пока нет жителей.", tribeEventCreate: "Создать событие", tribeEventsEmpty: "Событий пока нет.", @@ -1922,9 +2177,14 @@ module.exports = { tribeMediaTypeDocument: "Документ", tribeMediaTypeBookmark: "Закладка", tribeContentDelete: "УДАЛИТЬ", + tribeReachTribe: "ПЛЕМЯ", + tribeReachNotice: "OASIS показывает это всем жителям, а CLEARNET — ещё и в открытом вебе. Открытие оставляет читаемую копию вне племени: возврат к ПЛЕМЯ скрывает её, но уже распространённые копии удалить нельзя.", + tribeReachPrivate: "Содержимое частного племени может оставаться только внутри племени.", + tribeReachOpenedBy: "Открыл(а)", + tribeReachChange: "Где публикуется", tribeInviteCodeText: "Код приглашения: ", oasisVersionLabel: "Версия Oasis", - tribeInviteCodeHint: "Поделитесь этим кодом с тем, кого хотите пригласить. Он может присоединиться через /invites → Tribes.", + tribeInviteCodeHint: "Поделитесь этим кодом с тем, кого хотите пригласить. Он может присоединиться через /invites.", tribeStatusOpen: "ОТКРЫТОЕ", tribeStatusClosed: "ЗАКРЫТОЕ", tribeStatusInProgress: "В ПРОЦЕССЕ", @@ -1964,7 +2224,7 @@ module.exports = { tribeSearchMinChars: "Введите не менее 2 символов для поиска.", agendaTitle: "Повестка дня", agendaDescription: "Здесь вы можете найти все назначенные вам элементы.", - agendaSearchPlaceholder: "Поиск в повестке...", + agendaSearchPlaceholder: "Поиск в Повестка дня...", agendaFilterAll: "ВСЕ", agendaFilterToday: "СЕГОДНЯ", agendaFilterUpcoming: "БЛИЖАЙШИЕ", @@ -1985,6 +2245,7 @@ module.exports = { agendaNoItems: "Назначений не найдено.", agendaDiscardButton: "Отклонить", agendaRestoreButton: "Восстановить", + agendaRemoveButton: "Убрать", agendaMembersCount: "Участники", agendaLocationLabel: "Местоположение", agendaYes: "ДА", @@ -2048,8 +2309,8 @@ module.exports = { pmComposeTitle: "Отправить сообщение", pmRecipients: "Получатели", pmRecipientsHint: "Введите Oasis ID через запятую", - pmLimitsHint: "До 7 получателей на сообщение.", - pmTextPlaceholder: "Текст ограничен 7000 символами.", + pmLimitsHint: "До 7 жителей на сообщение.", + pmTextPlaceholder: "Напишите ваше сообщение.", pmSubject: "Тема", pmSubjectHint: "Введите тему сообщения", pmText: "Сообщение", @@ -2098,7 +2359,7 @@ module.exports = { inboxProjectPledgedTitle: "Новый взнос в ваш проект", pmHasPledged: "внёс", pmToYourProject: "в ваш проект", - blockchain: "BlockExplorer", + blockchain: "Обозреватель блоков", blockchainDescription: "Исследуйте и визуализируйте блоки в блокчейне.", blockchainNoBlocks: "Блоков в блокчейне не найдено.", blockchainStatsTitle: "Статистика", @@ -2119,24 +2380,24 @@ module.exports = { visitContent: "Открыть контент", favoriteAdd: "Закрепить в избранном", pmContentTooltip: "Отправить личное сообщение", + phoneCallTooltip: "Позвонить", favoriteRemove: "Убрать из избранного", reportContent: "Пожаловаться на контент", banking: "Банкинг", bankingTitle: "Банкинг", - bankingDescription: "Экономика ECOin: баланс, базовый доход, налоги и ценность промышленности.", + bankingDescription: "Коллективная экономика ECOin.", bankOverview: "Обзор", bankEpochs: "Эпохи", bankRules: "Правила", pending: "В ожидании", closed: "Закрыто", - bankViewTx: "Просмотр транзакции", bankClaimNow: "Получить сейчас", - bankClaimUBI: "Получить UBI!", - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: "Получить UBI", + bankClaimAndPay: "Получить и оплатить", + bankClaimedPending: "Заявка в обработке...", + bankStatusClaimed: "Получено", + bankStatusExpired: "Истекло", + bankPubOnly: "Операция только для PUB", bankEpoch: "Эпоха", bankPool: "Пул (эта эпоха)", bankWeightsSum: "Сумма весов", @@ -2154,8 +2415,8 @@ module.exports = { bankViewEpoch: "Просмотр эпохи", bankYourIndustryBalance: "Ваша доля в промышленности", bankYourUbiMonth: "Ваш БОД (в этом месяце)", - bankYourFundsMonth: "Ваши средства (в этом месяце)", - bankIndustryBalance: "Промышленное производство (оценка)", + bankYourFundsMonth: "Итого средства", + bankIndustryBalance: "Промышленное производство", bankUserBalance: "Ваш баланс", bankAddresses: "Адреса", bankNoAddresses: "Адресов не найдено.", @@ -2163,6 +2424,10 @@ module.exports = { bankAddress: "Адрес", bankAddAddressTitle: "Добавить адрес ECOIN", bankAddAddressUser: "Oasis ID", + bankAddAddressLabel: "Метка (необязательно)", + bankAddAddressLabelPlaceholder: "Кошелёк Боба, магазин Алисы…", + bankAddAddressUserOptional: "Oasis ID (необязательно)", + bankAddressUnnamed: "Адрес без имени", bankAddAddressAddress: "Адрес ECOIN", bankAddAddressSave: "Сохранить", bankAddressAdded: "Адрес добавлен", @@ -2179,11 +2444,11 @@ module.exports = { bankAddressSource: "Источник", bankAddressDeleteConfirm: "Удалить этот адрес?", bankLocal: "Локальный", - bankFromOasis: "Oasis", + bankFromOasis: "Сеть", pubIdLabel: "PUB ID", bankUbiAvailableNo: "НЕТ СРЕДСТВ!", bankUbiAvailableOk: "ДОСТУПНО!", - bankUbiAvailability: "БОД (сеть)", + bankUbiAvailability: "ББД", bankAlreadyClaimedThisMonth: "Уже получено в этом месяце", bankUbiThisMonth: "БОД (этот месяц)", bankUbiLastClaimed: "БОД (последнее получение)", @@ -2213,6 +2478,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "Шифруется при передаче", shopAddFavorite: "Добавить в избранное", shopRemoveFavorite: "Убрать из избранного", shopOpen: "ОТКРЫТ", @@ -2220,6 +2486,13 @@ module.exports = { shopMakePrivate: "СДЕЛАТЬ ПРИВАТНОЙ (E2E)", shopMakePublic: "СДЕЛАТЬ ПУБЛИЧНОЙ", shopProducts: "Товары", + shopFilterAll: "ВСЕ", + shopFilterRecent: "НЕДАВНИЕ", + shopFilterMine: "МОИ", + shopFilterTop: "ТОП", + shopFilterProducts: "ТОВАРЫ", + shopFilterPrices: "ЦЕНЫ", + shopFilterFavorites: "ИЗБРАННОЕ", shopProductAdd: "Добавить товар", shopProductTitle: "Товар", shopProductUpdate: "Обновить товар", @@ -2247,7 +2520,7 @@ module.exports = { marketVisitItem: "ОТКРЫТЬ ТОВАР", shopStatus: "СТАТУС", shopCreatedAt: "СОЗДАН", - shopSearchPlaceholder: "Поиск магазинов...", + shopSearchPlaceholder: "Поиск в Магазины...", shopUrl: "URL", shopLocation: "Местоположение", shopTags: "Теги", @@ -2257,33 +2530,35 @@ module.exports = { shopTitlePlaceholder: "Название вашего магазина", shopDescriptionPlaceholder: "Подробное описание вашего магазина", shopLocationPlaceholder: "Город, Страна", - shopTagsPlaceholder: "тег1, тег2, тег3", + shopTagsPlaceholder: "Введите теги через запятую", mapTitlePlaceholder: "Название карты", shopProductFeatured: "Рекомендуемый товар", - shopSendToMarket: "Send to Market", + shopSendToMarket: "Отправить на рынок", typeShop: "МАГАЗИНЫ", typeShopProduct: "ТОВАР МАГАЗИНА", bankExchange: "Обмен", bankExchangeCurrentValue: "Стоимость ECOin (1ч)", - bankTotalSupply: "Общий объём ECOin", - bankEcoinHours: "Эквивалент ECOin во времени", + bankTotalSupply: "Общее предложение ECOin", + bankPubsSupply: "Предложение ECOin в пуле PUB", + bankHoldingSupply: "Предложение ECOin вне ББД", + bankEcoinHours: "Эквивалент ECOin во времени (1 ECO)", bankHoursOfWork: "часов", bankUnitMs: "мс", bankUnitSeconds: "секунды", bankUnitMinutes: "минуты", bankUnitDays: "дни", bankExchangeNoData: "Данные недоступны", - bankInflation: "Инфляция ECOin (anual)", - bankInflationMonthly: "Инфляция ECOin (mensual)", + bankInflation: "Инфляция ECOin (годовая)", + bankInflationMonthly: "Инфляция ECOin (месячная)", bankExchangeChartTitle: "Динамика стоимости ECOin со временем", bankExchangeChartValue: "Стоимость (ECO/ч)", bankExchangeChartSupply: "Предложение", bankExchangeChartInflation: "Инфляция %", bankExchangeChartEmpty: "Пока недостаточно данных — загляните позже", - bankCurrentSupply: "Текущий объём ECOin", + bankCurrentSupply: "Текущее предложение ECOin", bankingSyncStatus: "Статус ECOin", bankingSyncStatusSynced: "Синхронизирован", - bankingSyncStatusOutdated: "Устарел", + bankingSyncStatusOutdated: "Не синхронизирован", statsTitle: "Статистика", statistics: "Статистика", statsDescription: "Откройте статистику вашей сети.", @@ -2293,7 +2568,8 @@ module.exports = { statsYourOpinions: "Мнения", statsNetworkContent: "Контент", statsNetworkOpinions: "Мнения", - statsEcoWalletLabel: "Кошелёк ECOIN", + statsEcoWalletLabel: "Кошелёк ECOin", + walletAddressCopyHint: "Нажмите, чтобы выделить, затем скопируйте", statsEcoWalletNotConfigured: "Не настроен!", statsBookmark: "Закладки", statsEvent: "События", @@ -2316,6 +2592,8 @@ module.exports = { statsTransfer: "Переводы", statsAiExchange: "ИИ", statsPUBs: "PUBs", + statsSyncedPeers: "Узлы", + statsTotalLabel: "Всего", statsPost: "Посты", statsSize: "Итого (размер)", statsBlockchainSize: "Блокчейн (размер)", @@ -2366,8 +2644,14 @@ module.exports = { aiPrompt: "Предоставьте информативный и точный ответ.", aiConfiguration: "Установить запрос", aiPromptUsed: "Запрос", - aiClearHistory: "Очистить историю чата", - aiServerError: "ИИ не смог ответить. Пожалуйста, попробуйте снова.", + aiClearHistory: "Очистить историю", + aiServerError: "Я не могу ответить как следует. Попробуйте ещё раз.", + aiStatusLoading: "42 загружает модель. Спросите снова через мгновение.", + aiThinking: "Думает", + aiWaiting: "Ожидание", + aiStatusMissingModel: "Модель ИИ не установлена: отсутствует src/AI/oasis-42-1-chat.Q4_K_M.gguf.", + aiExportFineTuning: "Экспорт Fine-Tuning", + aiFactsTitle: "Данные из вашей сети", typeAiExchange: "ИИ", aiApproveTrain: "Добавить в коллективное обучение", aiRejectTrain: "Не обучать", @@ -2394,7 +2678,7 @@ module.exports = { marketFilterDiscarded: "ОТКЛОНЁННЫЕ", marketFilterRecent: "НЕДАВНИЕ", marketFilterMyBids: "СТАВКИ", - marketCreateButton: "Создать товар", + marketCreateButton: "Продать что-то", marketItemType: "Тип", marketItemTitle: "Название", marketItemAvailable: "Срок", @@ -2421,8 +2705,8 @@ module.exports = { marketItemSeller: "Продавец", marketNoItems: "Товаров пока нет.", marketYourBid: "Ваша ставка", - marketCreateFormImageLabel: "Загрузить медиа (макс: 50MB)", - marketSearchPlaceholder: "Поиск по названию или тегам", + marketCreateFormImageLabel: "Загрузить медиа (макс: 75MB)", + marketSearchPlaceholder: "Поиск в Рынок...", marketMinPriceLabel: "Мин. цена", marketMaxPriceLabel: "Макс. цена", marketSortRecent: "Самые новые", @@ -2488,8 +2772,7 @@ module.exports = { housingSetClosed: "Сделать недоступным", housingCreateButton: "Создать место", housingUpdateButton: "Обновить", - housingDeleteButton: "Удалить", - housingSearchPlaceholder: "Поиск мест...", + housingSearchPlaceholder: "Поиск в Жильё...", housingSearchButton: "Искать", housingMinPrice: "Мин. цена", housingMaxPrice: "Макс. цена", @@ -2546,7 +2829,7 @@ module.exports = { jobLocationPresencial: "На месте", jobLocationRemote: "Удалённо", jobVacantsPlaceholder: "Количество позиций", - jobImage: "Загрузить медиа (макс: 50MB)", + jobImage: "Загрузить медиа (макс: 75MB)", jobTasks: "Задачи", jobType: "Тип занятости", jobTime: "Время занятости", @@ -2568,12 +2851,11 @@ module.exports = { jobsExchangeSkill: "Запрашиваемый навык в обмен", jobTimePartial: "Неполная занятость", jobTimeComplete: "Полная занятость", - jobsDeleteButton: "УДАЛИТЬ", jobsUpdateButton: "ОБНОВИТЬ", jobsFilterApplied: "ПОДАННЫЕ", jobsAppliedTitle: "Мои заявки", jobsAppliedBadge: "Подано", - jobsSearchPlaceholder: "Поиск по названию, тегам, описанию...", + jobsSearchPlaceholder: "Поиск в Вакансии...", jobsMinSalaryLabel: "Мин. зарплата", jobsMaxSalaryLabel: "Макс. зарплата", jobsSortRecent: "Самые новые", @@ -2584,7 +2866,7 @@ module.exports = { jobsUpdatedAt: "Обновлено", jobsTagsLabel: "Теги", noJobsMatch: "Вакансий не найдено.", - industrySearchPlaceholder: "Поиск фабрик…", + industrySearchPlaceholder: "Поиск в Промышленность...", industryAllSectors: "Все секторы", industryVisitButton: "Открыть промышленность", industryAdvanceTo: "Перевести в", @@ -2658,7 +2940,6 @@ module.exports = { industryBuildStatus_FAILED: "ПРОВАЛЕНО", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "Вакансия совпадает с вашим резюме", jobsBotMatchIntro: "Вакансия совпадает с вашим резюме.", jobsBotMatchJob: "Вакансия", @@ -2685,7 +2966,7 @@ module.exports = { industryRulesIntro: "Industry позволяет жителям коллективно владеть средствами производства: Фабрика — это принадлежащая сети мастерская, которой никто не владеет частно.", industryRulesSteward: "Основатель — управляющий, хранитель, а не владелец: он не может в одиночку продать, приватизировать или распустить Фабрику.", industryRulesMembership: "Политика членства задаёт способ вступления: Открытая (сразу), Голосованием (принимают голосом участников) или Только по приглашению (сначала должен пригласить участник).", - industryRulesQuorum: "Кворум — минимальное число участников, которые должны проголосовать, чтобы решение засчиталось, чтобы маленькую Фабрику не решал один человек.", + industryRulesQuorum: "Кворум — минимальное число участников, которые должны проголосовать, чтобы решение засчиталось, чтобы маленькую Фабрику не решал один житель.", industryRulesMajority: "Большинство — доля голосов для принятия (0,5 = половина, 1 = единогласие); решение проходит, когда голоса ЗА достигают хотя бы max(кворум, участники × большинство).", industryRulesDecisions: "Участники голосуют за приём новых, одобрение сборок и роспуск Фабрики; управляющий не может обойти эти коллективные решения.", industryRulesBlueprints: "Чертежи — свободные (COPYLEFT) рецепты: входы (материалы, часы труда, навыки) и выход (физический или цифровой продукт), и любой может их форкнуть.", @@ -2711,6 +2992,7 @@ module.exports = { modulesIndustryLabel: "Промышленность", modulesIndustryDescription: "Модуль для коллективного управления средствами производства.", industryFilterAll: "ВСЕ", + industryFilterRecent: "НЕДАВНИЕ", industryFilterMember: "УЧАСТНИК", industryFilterBlueprints: "ЧЕРТЕЖИ", industryFilterBuilds: "ПРОИЗВОДСТВА", @@ -2719,6 +3001,7 @@ module.exports = { industryFilterPaused: "НА ПАУЗЕ", industryFilterDissolved: "РАСПУЩЕННЫЕ", industryAllTitle: "Промышленность", + industryRecentTitle: "Недавние фабрики", industryMemberTitle: "Фабрики, где я состою", industryMineTitle: "Мои фабрики", industryActiveTitle: "Работает", @@ -2766,7 +3049,7 @@ module.exports = { industryPolicy_invite: "Только по приглашению", projectsTitle: "Проекты", projectsDescription: "Создавайте, финансируйте и следите за проектами сообщества в вашей сети.", - projectSearchPlaceholder: "Поиск проектов...", + projectSearchPlaceholder: "Поиск в Проекты...", projectCreateProject: "Создать проект", projectCreateButton: "Создать проект", projectUpdateButton: "ОБНОВИТЬ", @@ -2789,7 +3072,7 @@ module.exports = { projectRecentTitle: "Последние проекты", projectTopTitle: "Топ по финансированию", projectTitlePlaceholder: "Название проекта", - projectImage: "Загрузить медиа (макс: 50MB)", + projectImage: "Загрузить медиа (макс: 75MB)", projectDescription: "Описание", projectDescriptionPlaceholder: "Расскажите историю и цели…", projectGoal: "Цель (ECO)", @@ -2861,15 +3144,13 @@ module.exports = { modulesModuleName: "Имя", modulesModuleDescription: "Описание", modulesModuleStatus: "Статус", - modulesTotalModulesLabel: "Загруженные модули", + modulesTotalModulesLabel: "Всего", modulesEnabledModulesLabel: "Включённые", modulesDisabledModulesLabel: "Отключённые", modulesInvitesLabel: "Приглашения", modulesInvitesDescription: "Модуль для управления и применения кодов приглашений.", modulesWalletLabel: "Кошелёк", modulesWalletDescription: "Модуль для управления вашими цифровыми активами (ECOin).", - modulesLegacyLabel: "Наследие", - modulesLegacyDescription: "Модуль для быстрого и безопасного управления вашим секретом (приватным ключом).", modulesCipherLabel: "Шифр", modulesCipherDescription: "Модуль для симметричного шифрования и расшифровки текста (с использованием общего пароля).", modulesBookmarksLabel: "Закладки", @@ -2931,20 +3212,23 @@ module.exports = { modulesProjectsLabel: "Проекты", modulesProjectsDescription: "Модуль для исследования, краудфандинга и управления проектами.", modulesBankingLabel: "Банкинг", - modulesBankingDescription: "Модуль для определения реальной стоимости ECOIN и распределения UBI из общей казны.", + modulesBankingDescription: "Модуль управления общей казной: стоимость ECOIN, налоги и БОД.", modulesFavoritesLabel: "Избранное", modulesFavoritesDescription: "Модуль для управления избранным контентом.", fileTooLargeTitle: "Файл слишком большой", fileTooLargeMessage: "Файл превышает максимально допустимый размер (50 МБ). Пожалуйста, выберите файл меньшего размера.", goBack: "Назад", + errorDismiss: "ОК", errorPageTitle: "Что-то пошло не так", + errorNoAllocation: "Распределение для этого запроса не найдено.", + errorInvalidClaim: "Это распределение нельзя получить.", visibilityLabel: "Видимость", + clearnetPrivateConflict: "Приватный, скрытый, закрытый, доступный только по приглашению или платный контент нельзя опубликовать в CLEARNET. Выберите OASIS или сделайте его публичным.", visibilityPublic: "Публичная", visibilityHidden: "Скрытая", visibilityMakePublic: "Сделать публичным", visibilityMakeHidden: "Скрыть", invitesInhabitantsTitle: "Жители", - invitesInhabitantsFollow: "Подписаться", aiNavPlaceholder: "Опишите, что вам нужно...", aiNavDisabled: "Навигация с ИИ отключена.", aiNavResultsTitle: "Подсказки AINav", @@ -2955,7 +3239,7 @@ module.exports = { modulesAINavDescription: "Модуль для запросов на естественном языке к содержимому сети.", directConnectDescription: "Подключитесь напрямую к узлу, введя его IP-адрес, порт и публичный ключ. Узел будет добавлен как отслеживаемое соединение.", peerHost: "IP", - peerPort: "Порт (по умолчанию: 8008)", + peerPort: "Порт", peerPublicKey: "Публичный ключ (@...ed25519)", connectAndFollow: "Подключиться", modulesPresetTitle: "Общие конфигурации", @@ -2964,15 +3248,15 @@ module.exports = { workflowsSet: "Применить режим", workflow_default: "По умолчанию", workflow_jobs: "Работа", - workflow_ruling: "Управление", - workflow_politics: "Политика", workflow_social: "Общение", workflow_business: "Бизнес", workflow_night: "Ночь", + workflow_kids: "Дети", + workflow_activists: "Активизм", workflow_mobile: "Мобильный", statsCarbonFootprintTitle: "Углеродный след", statsCarbonTombstone: "Углеродный след надгробий", - uploadMedia: "Загрузить медиа (макс: 50MB)", + uploadMedia: "Загрузить медиа (макс: 75MB)", feedOpenDiscussion: "Открыть обсуждение", feedDetailTitle: "Лента", feedPostComment: "Опубликовать комментарий", @@ -2985,7 +3269,7 @@ module.exports = { deviceLabel: "Устройство", mapsLabel: "Карты", mapTitle: "Карты", - mapDescription: "Исследуйте и управляйте офлайн-картами в вашей сети.", + mapDescription: "Управляйте офлайн-картами и делитесь ими в вашей сети.", mapMineSectionTitle: "Ваши Карты", mapCreateSectionTitle: "Создать Карту", mapUpdateSectionTitle: "Обновить Карту", @@ -3025,9 +3309,22 @@ module.exports = { markerImageLabel: "Изображение Маркера", mapAddMarkerTitle: "Добавить Маркер", mapAddMarkerButton: "Добавить Маркер", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "Очистить метку", mapApplyZoom: "Применить масштаб", - mapSearchPlaceholder: "Поиск по описанию, тегам, автору...", + mapPlaceSearchPlaceholder: "Найти место или страну...", + mapNoPlaces: "Места не найдены.", + mapClusterTitle: "меток здесь, нажмите для приближения", + mapDeleteMarker: "Удалить метку", + mapZoomIn: "Приблизить", + mapZoomOut: "Отдалить", + mapFitMarkers: "Показать все метки", + mapPanLabel: "Переместить карту", + mapCenterHere: "Центрировать здесь", + mapNear: "рядом с", + mapPickedSpot: "Выбранная точка", + mapPickHint: "Нажмите на карту, чтобы выбрать точку.", + mapClearPick: "Очистить", + mapSearchPlaceholder: "Поиск в Карты...", mapSearchButton: "Искать", mapUpdatedAt: "Обновлено", mapNoMatch: "Нет карт, соответствующих вашему запросу.", @@ -3038,6 +3335,465 @@ module.exports = { typeMapMarker: "МАРКЕР КАРТЫ", modulesMapLabel: "Карты", modulesMapDescription: "Модуль для управления и обмена офлайн-картами.", + emergenciesTitle: "Чрезвычайные ситуации", + modulesEmergenciesLabel: "Чрезвычайные ситуации", + modulesEmergenciesDescription: "Модуль для объявления, подтверждения и отслеживания чрезвычайных ситуаций в сети.", + emergenciesDescription: "Модуль для объявления, подтверждения и отслеживания чрезвычайных ситуаций в сети.", + emergencyCreate: "Создать ЧС", + emergencyUpdate: "Обновить", + emergencyDelete: "Удалить", + emergencyTitleLabel: "Заголовок", + emergencyTitlePlaceholder: "Что происходит?", + emergencyTextLabel: "Подробности", + emergencyTextPlaceholder: "Где, с какого момента, что делать...", + emergencyCategoryLabel: "Категория", + emergencyCategoryWeather: "ПОГОДА", + emergencyCategoryInfrastructure: "ИНФРАСТРУКТУРА", + emergencyCategoryHealth: "ЗДОРОВЬЕ", + emergencyCategorySecurity: "БЕЗОПАСНОСТЬ", + emergencyCategoryLost: "ПОТЕРИ И НАХОДКИ", + emergencyCategoryNeighborhood: "СОСЕДСТВО", + emergencyExpiresLabel: "Истекает через", + emergencyExpires1d: "1 день", + emergencyExpires3d: "3 дня", + emergencyExpires7d: "1 неделя", + emergencyExpires30d: "1 месяц", + emergencyExpiresNone: "Не определено", + emergencyExpiresKeep: "Оставить текущую дату", + emergencyTagsLabel: "Теги", + emergencyTagsPlaceholder: "Введите теги через запятую", + emergencyFilterAll: "ВСЕ", + emergencyFilterMine: "МОИ", + emergencyFilterRecent: "НЕДАВНИЕ", + emergencySeverityUnverified: "НЕ ПОДТВЕРЖДЕНО", + emergencySeverityLow: "НИЗКАЯ", + emergencySeverityMedium: "СРЕДНЯЯ", + emergencySeverityHigh: "ВЫСОКАЯ", + emergencyStatusActive: "АКТИВНО", + emergencyStatusResolved: "РЕШЕНО", + emergencyStatusExpired: "ИСТЕКЛО", + emergencyConfirmations: "Подтверждения", + emergencyConfirmButton: "Подтвердить", + emergencyConfirmed: "Вы подтвердили эту чрезвычайную ситуацию", + emergencyResolveButton: "Закрыть", + emergencyReopenButton: "Открыть снова", + emergencyGenerateReport: "Создать отчёт", + emergencyUpdateEdited: "Изменено", + emergencyLastUpdate: "ПОСЛЕДНЕЕ ОБНОВЛЕНИЕ", + emergencyUpdatesTitle: "Последние обновления:", + emergencyNoUpdates: "Обновлений пока нет.", + emergencyAddUpdate: "Опубликовать обновление", + emergencyUpdatePlaceholder: "Что изменилось?", + emergencyNoItems: "Чрезвычайные ситуации не найдены.", + emergencyExpiresAt: "Истекает", + emergencyBannerLabel: "Активная ЧС", + emergencyCreateSectionTitle: "Создать ЧС", + emergencyUpdateSectionTitle: "Обновить ЧС", + emergencySearchPlaceholder: "Поиск по чрезвычайным ситуациям...", + typeEmergency: "ЧС", + typeEmergencyConfirm: "ПОДТВЕРЖДЕНИЕ ЧС", + typeEmergencyUpdate: "ОБНОВЛЕНИЕ ЧС", + statsEmergency: "Чрезвычайные ситуации", + statsEmergencyConfirm: "Подтверждения ЧС", + statsEmergencyUpdate: "Обновления ЧС", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "В отслеживаемой вами ЧС появилось обновление.", + emergencyBotResolvedTitle: "Отслеживаемая вами ЧС разрешена.", + dataKindEmergencies: "ЧС", + mailingTitle: "Списки рассылки", + modulesMailingLabel: "Списки рассылки", + modulesMailingDescription: "Находите и управляйте списками рассылки в своей сети.", + modulesPhoneLabel: "Телефон", + modulesPhoneDescription: "Модуль для видеозвонков другим жителям со сквозным шифрованием.", + mailingDescription: "Находите и управляйте списками рассылки в своей сети.", + mailingCreate: "Создать список рассылки", + mailingUpdate: "Обновить", + mailingArchive: "В архив", + mailingActivate: "Активировать", + mailingCreateSectionTitle: "Создать список рассылки", + mailingUpdateSectionTitle: "Обновить список рассылки", + mailingTitleLabel: "Название", + mailingTitlePlaceholder: "Название списка рассылки", + mailingDescriptionLabel: "Описание", + mailingDescriptionPlaceholder: "О чём этот список?", + mailingTypeLabel: "Тип списка рассылки", + mailingTypeOpen: "ОТКРЫТЫЙ", + mailingTypeClosed: "ЗАКРЫТЫЙ", + mailingStatusLabel: "Статус", + mailingStatusActive: "АКТИВНЫЙ", + mailingStatusArchived: "В АРХИВЕ", + mailingMembersLabel: "Пригласить жителей", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "Подписчики", + mailingTagsLabel: "Теги", + mailingTagsPlaceholder: "Введите теги через запятую", + mailingFilterAll: "ВСЕ", + mailingFilterMine: "МОИ", + mailingFilterSubscribed: "ПОДПИСКИ", + mailingFilterRecent: "НЕДАВНИЕ", + mailingHistoryDate: "ДАТА", + mailingHistoryThreads: "ТЕМЫ", + mailingHistoryInhabitants: "ЖИТЕЛИ", + mailingHistorySearchPlaceholder: "Поиск по истории...", + mailingNoMessages: "Сообщений пока нет.", + mailingNoItems: "Списки рассылки не найдены.", + mailingSearchPlaceholder: "Поиск по спискам рассылки...", + mailingWriteButton: "Написать сообщение", + mailingWriteTitle: "Написать в список", + mailingReplyTitle: "Ответить в список", + mailingSend: "Отправить", + mailingTextPlaceholder: "Ваше сообщение получат все участники списка.", + mailingParticipants: "Участники", + inboxFilterMailing: "Списки рассылки", + mailingReplyPlaceholder: "Ответить в список...", + mailingReplyToList: "Ответить в список", + typeMailingList: "СПИСОК РАССЫЛКИ", + statsMailingList: "Списки рассылки", + mailingListLabel: "СПИСОК РАССЫЛКИ", + logisticsTitle: "Логистика", + modulesLogisticsLabel: "Логистика", + modulesLogisticsDescription: "Находите и управляйте логистическими планами в своей сети.", + logisticsDescription: "Находите и управляйте логистическими планами в своей сети.", + logisticsCreate: "Создать маршрут", + logisticsUpdate: "Обновить", + logisticsClose: "Закрыть", + logisticsReopen: "Открыть снова", + logisticsCreateSectionTitle: "Создать маршрут", + logisticsUpdateSectionTitle: "Обновить маршрут", + logisticsTitleLabel: "Название", + logisticsTitlePlaceholder: "Поездка на рынок, есть фургон...", + logisticsKindLabel: "Тип логистики", + logisticsKindTrip: "ПОЕЗДКА", + logisticsKindShipment: "ПЕРЕВОЗКА", + logisticsKindTrips: "ПОЕЗДКИ", + logisticsKindShipments: "ПЕРЕВОЗКИ", + logisticsModeLabel: "Режим", + logisticsModeOffer: "ПРЕДЛАГАЮ", + logisticsModeRequest: "ИЩУ", + logisticsModeOffers: "ПРЕДЛОЖЕНИЯ", + logisticsModeRequests: "ЗАПРОСЫ", + logisticsDescriptionLabel: "Описание", + logisticsDescriptionPlaceholder: "Место встречи, что везёте, условия...", + logisticsOriginLabel: "Откуда", + logisticsOriginPlaceholder: "Где начинается", + logisticsDestinationLabel: "Куда", + logisticsDestinationPlaceholder: "Где заканчивается", + logisticsDateLabel: "Дата и время", + logisticsRecurrenceLabel: "Повторение", + logisticsFrequencyLabel: "Периодичность", + logisticsRecurrenceNone: "ОДНОКРАТНО", + logisticsRecurrenceDaily: "ЕЖЕДНЕВНО", + logisticsRecurrenceWeekly: "ЕЖЕНЕДЕЛЬНО", + logisticsRecurrenceMonthly: "ЕЖЕМЕСЯЧНО", + logisticsSeatsLabel: "Места", + logisticsSizeLabel: "Размер", + logisticsSizePlaceholder: "напр. 2m x 1m", + logisticsWeightLabel: "Вес", + logisticsWeightPlaceholder: "напр. 40kg", + logisticsPriceFree: "БЕСПЛАТНО", + logisticsPriceEco: "ECO", + logisticsPriceTime: "ВРЕМЯ", + logisticsPriceLabel: "Цена", + logisticsHours: "ч", + logisticsOrderRefLabel: "Ссылка на заказ", + logisticsOrderRefPlaceholder: "/shops/... или /market/... заказа для доставки", + logisticsOrderLink: "Связанный заказ", + logisticsTagsLabel: "Теги", + logisticsTagsPlaceholder: "Введите теги через запятую", + logisticsFilterAll: "ВСЕ", + logisticsFilterMine: "МОИ", + logisticsFilterBooked: "ЗАБРОНИРОВАННЫЕ", + logisticsFilterRecent: "НЕДАВНИЕ", + logisticsFilterHistory: "ИСТОРИЯ", + logisticsFilterTop: "ТОП", + logisticsStatusOpen: "ОТКРЫТ", + logisticsStatusUpcoming: "ПРЕДСТОЯЩИЕ", + logisticsStatusPast: "ПРОШЕДШИЕ", + logisticsStatusClosed: "ЗАКРЫТЫЕ", + logisticsZoneLabel: "Зона", + logisticsSearchPlaceholder: "Поиск по логистике...", + logisticsNoItems: "Маршруты не найдены.", + logisticsBookingPending: "ОЖИДАЕТ", + logisticsBookingConfirmed: "ПОДТВЕРЖДЕНО", + logisticsBookingRejected: "ОТКЛОНЕНО", + logisticsBookingCancelled: "ОТМЕНЕНО", + logisticsBookingDelivered: "ДОСТАВЛЕНО", + logisticsConfirm: "Подтвердить", + logisticsReject: "Отклонить", + logisticsDeliver: "Доставить", + logisticsReceiptLabel: "Квитанция", + logisticsReceiptPlaceholder: "Примечание к доставке (необязательно)", + logisticsCancelBooking: "Отменить бронь", + logisticsBookTitle: "Забронировать маршрут", + logisticsNotesLabel: "Заметки", + logisticsNotesPlaceholder: "Что должен знать владелец", + logisticsBook: "Забронировать", + logisticsBookingsTitle: "Брони", + logisticsMyBookingTitle: "Ваша бронь", + logisticsRatingsTitle: "Оценки", + logisticsNoRatings: "Оценок пока нет.", + logisticsRatingsWhenClosed: "Оценки открываются после закрытия маршрута.", + logisticsScoreLabel: "Балл", + logisticsRatingPlaceholder: "Как всё прошло?", + logisticsRate: "Оценить", + typeLogisticsRoute: "МАРШРУТ", + typeLogisticsRating: "ОЦЕНКА МАРШРУТА", + statsLogisticsRoute: "Маршруты", + statsLogisticsRating: "Оценки маршрутов", + logisticsRouteLabel: "МАРШРУТ", + podcastsTitle: "Подкасты", + modulesPodcastsLabel: "Подкасты", + modulesPodcastsDescription: "Модуль для публикации подкаст-каналов.", + podcastsDescription: "Модуль для публикации подкаст-каналов.", + podcastCreate: "Создать подкаст", + podcastUpdate: "Обновить", + podcastDelete: "Удалить", + podcastCreateSectionTitle: "Создать подкаст", + podcastUpdateSectionTitle: "Обновить подкаст", + podcastTitleLabel: "Название", + podcastTitlePlaceholder: "Название канала", + podcastDescriptionLabel: "Описание", + podcastDescriptionPlaceholder: "О чём этот подкаст?", + podcastCategoryLabel: "Категория", + podcastCategoryNews: "НОВОСТИ", + podcastCategoryMusic: "МУЗЫКА", + podcastCategoryTalk: "БЕСЕДЫ", + podcastCategoryEducation: "ОБРАЗОВАНИЕ", + podcastCategoryTech: "ТЕХНОЛОГИИ", + podcastCategoryCulture: "КУЛЬТУРА", + podcastCategoryCommunity: "СООБЩЕСТВО", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "ДРУГОЕ", + podcastTagsLabel: "Теги", + podcastCoverLabel: "Обложка (изображение или видео)", + podcastEpisodeAudioLabel: "Аудио или видеофайл (макс: 75MB)", + podcastTagsPlaceholder: "Введите теги через запятую", + podcastFilterAll: "ВСЕ", + podcastFilterMine: "МОИ", + podcastFilterRecent: "НЕДАВНИЕ", + podcastFilterTop: "ТОП", + podcastFilterViewers: "СЛУШАТЕЛИ", + podcastSearchPlaceholder: "Поиск по подкастам...", + podcastNoItems: "Подкасты не найдены.", + podcastEpisodesLabel: "Выпуски", + podcastPlaysLabel: "Просмотры", + podcastLastEpisode: "Последний выпуск", + podcastEpisodesTitle: "Выпуски", + podcastNoEpisodes: "Выпусков пока нет.", + podcastEpisodeAdd: "Добавить выпуск", + podcastEpisodeCreateTitle: "Опубликовать выпуск", + podcastEpisodeUpdateTitle: "Обновить выпуск", + podcastEpisodeTitleLabel: "Название выпуска", + podcastEpisodeTitlePlaceholder: "Название выпуска", + podcastEpisodeDescriptionLabel: "Заметки", + podcastEpisodeDescriptionPlaceholder: "Что происходит в выпуске, ссылки, авторы...", + podcastEpisodePublish: "Опубликовать", + podcastPlayed: "Прослушано", + podcastMarkPlayed: "Отметить как прослушанное", + typePodcast: "ПОДКАСТ", + typePodcastEpisode: "ВЫПУСК", + typePodcastOpinion: "МНЕНИЕ О ВЫПУСКЕ", + typePodcastPlay: "ПРОСЛУШИВАНИЕ", + statsPodcast: "Подкасты", + statsPodcastEpisode: "Выпуски", + statsPodcastPlay: "Прослушивания", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "В блоге, на который вы подписаны, новая запись.", + podcastBotEpisodeTitle: "У подкаста, на который вы подписаны, новый выпуск.", + podcastLabel: "ПОДКАСТ", + podcastEpisodeLabel: "ВЫПУСК", + campaignsTitle: "Кампании", + modulesCampaignsLabel: "Кампании", + modulesCampaignsDescription: "Находите и управляйте кампаниями в своей сети.", + campaignsDescription: "Находите и управляйте кампаниями в своей сети.", + campaignCreate: "Создать кампанию", + campaignUpdate: "Обновить", + campaignDelete: "Удалить", + campaignClose: "Закрыть", + campaignReopen: "Открыть снова", + campaignCreateSectionTitle: "Создать кампанию", + campaignUpdateSectionTitle: "Обновить кампанию", + campaignTitleLabel: "Название", + campaignTitlePlaceholder: "Чего вы хотите добиться?", + campaignTextLabel: "Текст", + campaignTextPlaceholder: "Объясните суть, кто решает и чего вы требуете...", + campaignCategoryLabel: "Категория", + campaignCategoryEnvironment: "ЭКОЛОГИЯ", + campaignCategoryRights: "ПРАВА", + campaignCategoryHealth: "ЗДОРОВЬЕ", + campaignCategoryEducation: "ОБРАЗОВАНИЕ", + campaignCategoryInfrastructure: "ИНФРАСТРУКТУРА", + campaignCategoryCulture: "КУЛЬТУРА", + campaignCategoryEconomy: "ЭКОНОМИКА", + campaignCategoryOther: "ДРУГОЕ", + campaignGoalLabel: "Цель по подписям", + campaignDeadlineLabel: "Срок", + campaignTagsLabel: "Теги", + campaignTagsPlaceholder: "Введите теги через запятую", + campaignFilterAll: "ВСЕ", + campaignFilterMine: "МОИ", + campaignFilterSigned: "ПОДПИСАННЫЕ", + campaignFilterRecent: "НЕДАВНИЕ", + campaignFilterTop: "ТОП", + campaignStatusOpen: "ОТКРЫТА", + campaignStatusAchieved: "ДОСТИГНУТА", + campaignStatusUnachieved: "НЕ ДОСТИГНУТА", + campaignStatusClosed: "ЗАКРЫТА", + campaignSearchPlaceholder: "Поиск по кампаниям...", + campaignNoItems: "Кампании не найдены.", + campaignSignaturesLabel: "Подписи", + campaignSignaturesTitle: "Сторонники", + campaignNoSignatures: "Подписей пока нет.", + campaignSignTitle: "Подписать кампанию", + campaignSignPlaceholder: "Почему вы её поддерживаете? (необязательно)", + campaignSign: "Подписать", + campaignSigned: "Вы уже подписали!", + campaignNotSigned: "Вашей подписи ещё не хватает", + campaignElevate: "Передать в Парламент", + campaignElevated: "Передана в Парламент", + campaignUpdatesTitle: "Обновления", + campaignNoUpdates: "Обновлений пока нет.", + campaignUpdatePlaceholder: "Что произошло?", + campaignAddUpdate: "Опубликовать обновление", + campaignQrAlt: "QR для подписи кампании", + campaignQrCaption: "Отсканируйте, чтобы подписать", + typeCampaign: "КАМПАНИЯ", + typeCampaignSignature: "ПОДПИСЬ", + typeCampaignUpdate: "ОБНОВЛЕНИЕ КАМПАНИИ", + statsCampaign: "Кампании", + statsCampaignSignature: "Подписи кампаний", + statsCampaignUpdate: "Обновления кампаний", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "У кампании, за которой вы следите, есть обновление.", + campaignBotAchievedTitle: "Кампания, за которой вы следите, достигла цели.", + campaignBotRaisedTitle: "Кампания, которую вы подписали, вынесена в Парламент.", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "Забронированный вами маршрут изменился.", + logisticsBotClosedTitle: "Забронированный вами маршрут закрыт.", + logisticsBotBookedTitle: "Кто-то забронировал ваш маршрут.", + logisticsBotCancelledTitle: "Бронирование на вашем маршруте отменено.", + logisticsBotConfirmedTitle: "Ваше бронирование подтверждено: посылка в пути.", + logisticsBotRejectedTitle: "Ваше бронирование отклонено.", + logisticsBotDeliveredTitle: "Ваша посылка доставлена.", + campaignLabel: "КАМПАНИЯ", + campaignGenerateReport: "Создать отчёт", + logisticsGenerateReport: "Создать отчёт", + mailingGenerateReport: "Создать отчёт", + campaignUpdateEdited: "Изменено", + mailingMessagesLabel: "Сообщения", + logisticsBookingsLabel: "Брони", + backupTitle: "Резервная копия", + modulesBackupLabel: "Резервная копия", + modulesBackupDescription: "Модуль для экспорта и импорта ключей и создания зашифрованных резервных копий.", + backupDescription: "Модуль для экспорта и импорта ключей и создания зашифрованных резервных копий.", + backupTypeRecovery: "ВОССТАНОВЛЕНИЕ", + backupTypeKeys: "ЭКСПОРТ КЛЮЧЕЙ", + backupTypeFull: "ПОЛНАЯ КОПИЯ", + backupTypeRestore: "ВОССТАНОВИТЬ КОПИЮ", + backupTypeInstant: "МГНОВЕННО", + backupInstantDescription: "Обычный .zip со всем вашим журналом и файлами, без секретного ключа и без пароля. Быстро, но ", + backupInstantRisk: "тот, у кого окажется файл, прочтёт всё.", + backupKitPageDescription: "Набор для восстановления можно открыть только с этого устройства.", + backupKitId: "Oasis ID", + backupKitDate: "Создано", + backupKitQrAlt: "QR-код вашего секретного ключа", + backupKitStep1: "Распечатайте или перепишите и храните в надёжном месте офлайн.", + backupKitStep2: "Для восстановления установите Oasis на новом устройстве и импортируйте этот секрет в разделе Резервная копия › Ключи.", + backupKitStep3: "Уничтожьте эту страницу, если она когда-либо будет раскрыта.", + backupKitPdfButton: "Создать PDF", + backupScopeLabel: "Охват", + backupScopeAll: "ВСЁ", + backupScopeMine: "ТОЛЬКО МОЙ КОНТЕНТ", + backupSinceLabel: "С даты", + backupEstimateTitle: "Оценка", + backupFileInfo: "Зашифрованная копия будет загружена на устройство (имя: oasis-backup-ДАТА.oasisbk)", + backupExportButton: "Скачать копию", + backupRestorePassword: "Введите пароль, использованный при создании копии", + backupRestoreButton: "Восстановить копию", + backupRestoredLine: "Копия восстановлена.", + backupRestoredMessages: "Добавлено сообщений", + backupRestoredSkipped: "Уже были", + backupRestoredBlobs: "Добавлено файлов", + backupRestoredState: "Восстановлено настроек", + backupRestoredFailed: "Ошибок", + backupRestoredForked: "Расходятся", + backupRestoreRunning: "Восстановление копии. Страница обновляется автоматически; тем временем можно продолжать пользоваться Oasis.", + backupRestoreElapsed: "Прошло", + backupRestoreFailedLine: "Не удалось восстановить копию.", + backupRestoreForkMine: "Это устройство уже опубликовало сообщения с вашей идентичностью, которых нет в копии, поэтому применить её поверх них не удалось. Восстанавливайте на чистой установке сразу после импорта ключей, прежде чем делать что-либо ещё.", + verificationTitle: "Проверка", + verificationDescription: "Проверьте цепочку своей ленты, найдите ответвления и просмотрите осиротевшие или отсутствующие файлы.", + verificationRun: "Запустить проверку", + verificationTotalMessages: "сообщений в журнале", + verificationFeed: "Ваша лента", + verificationMessages: "сообщений", + verificationLastSequence: "последняя последовательность", + verificationGaps: "Пропуски", + verificationBrokenLinks: "Разорванные ссылки", + verificationBadHashes: "Неверные хеши", + verificationBadSignatures: "Неверные подписи", + verificationDuplicates: "Дублированные номера", + verificationForks: "Ответвления", + verificationBlobs: "Файлы", + verificationBlobsPresent: "есть", + verificationBlobsReferenced: "упомянуто", + verificationBlobsOrphan: "осиротевших", + verificationBlobsMissing: "отсутствует", + verificationBlobsMissingOwn: "Ваши файлы, отсутствующие на этом устройстве", + verificationBlobsMissingOthers: "Файлы других жителей ещё не загружены", + verificationBlobsUnavailable: "Не удалось получить список файлов на этом устройстве.", + wikiTitle: "Вики", + modulesWikiLabel: "Вики", + modulesWikiDescription: "Модуль для поиска и управления вики.", + wikiDescription: "Модуль для поиска и управления вики.", + wikiCreate: "Создать вики", + wikiUpdate: "Обновить", + wikiDelete: "Удалить", + wikiEdit: "Редактировать", + wikiTitleLabel: "Заголовок", + wikiTitlePlaceholder: "Название вики...", + wikiBodyLabel: "Содержимое", + wikiBodyPlaceholder: "Ссылайтесь на другие вики через [[Название вики]] или [[Название вики|своя подпись]].\n\nУпоминайте жителей через @id.\n\nСтавьте теги #oasis.\n\nВставляйте ссылки вроде https://solarnethub.com\n\nПустая строка начинает новый абзац; одиночные переносы сохраняются.\n\nПрикрепите любой поддерживаемый медиафайл — он встроится сюда.", + formatBarLabel: "Формат", + wikiStatusLabel: "Тип вики", + wikiStatusOpen: "ОТКРЫТА", + wikiStatusClosed: "ЗАКРЫТА", + wikiTagsLabel: "Теги", + wikiTagsPlaceholder: "Введите теги через запятую", + wikiSummaryLabel: "Описание изменения", + wikiSummaryPlaceholder: "Что изменилось?", + wikiFilterAll: "ВСЕ", + wikiFilterMine: "МОИ", + wikiFilterRecent: "НЕДАВНИЕ", + wikiFilterLinked: "СВЯЗАННЫЕ", + wikiChanges: "Последние изменения", + wikiNoPages: "Вики не найдены.", + wikiNoChanges: "Изменений пока нет.", + wikiHistory: "История", + wikiVersion: "Версия", + wikiVersions: "версий", + wikiRestore: "Восстановить", + wikiDiff: "Изменения", + wikiViewVersion: "Смотреть", + wikiLinkedPages: "Связанные страницы", + wikiNoLinkedPages: "Эта страница пока не связана ни с одной другой.", + wikiMissingLink: "Страница ещё не существует", + wikiDuplicateTitle: "Страница с таким названием уже существует. Измените название, чтобы создать другую страницу.", + wikiCreateSectionTitle: "Создать вики", + wikiUpdateSectionTitle: "Обновить вики", + wikiSearchPlaceholder: "Поиск по вики...", + wikiCurrentVersion: "Текущая версия", + wikiViewingVersion: "Вы смотрите старую версию", + wikiBackToPage: "Назад к странице", + wikiLinkedChip: "Связана", + wikiRestoredSummary: "Восстановленная версия", + wikiExportPdf: "Создать страницу", + wikiPreview: "Предпросмотр", + tribeSectionWiki: "ВИКИ", + tribeWikiCreate: "Создать вики", + tribeWikiEmpty: "Вики пока нет.", + typeWiki: "ВИКИ", padsTitle: "Пады", padTitle: "Пад", modulesPadsLabel: "Пады", @@ -3048,21 +3804,133 @@ module.exports = { padTitleLabel: "Заголовок", padTitlePlaceholder: "Введите заголовок пада...", padStatusLabel: "Статус", + padTypeLabel: "Тип пада", padStatusOpen: "ОТКРЫТ", padStatusInviteOnly: "ТОЛЬКО ПО ПРИГЛАШЕНИЮ", padStatusClosed: "ЗАКРЫТ", padDeadlineLabel: "Срок", padTagsLabel: "Теги", - padTagsPlaceholder: "тег1, тег2, ...", + padTagsPlaceholder: "Введите теги через запятую", padMembersLabel: "Участники", padCreated: "Создано", - padStartEditing: "НАЧАТЬ РЕДАКТИРОВАНИЕ!", + padStartEditing: "Редактировать", padEditorPlaceholder: "Начните писать...", padSubmitEntry: "Отправить", padNoEntries: "Записей пока нет.", padAllSectionTitle: "Все Пады", padMineSectionTitle: "Мои Пады", padsDescription: "Управляйте совместными зашифрованными текстовыми редакторами в вашей сети.", + roomsTitle: "Комнаты", + roomsDescription: "Находите зашифрованные комнаты видеоконференций в вашей сети и присоединяйтесь к ним.", + modulesRoomsLabel: "Комнаты", + modulesRoomsDescription: "Модуль для поиска зашифрованных комнат видеоконференций и входа в них.", + roomFilterRecent: "НЕДАВНИЕ", + roomFilterMine: "МОИ", + roomFilterAll: "ВСЕ", + roomFilterLive: "В ЭФИРЕ", + roomFilterOpen: "ОТКРЫТЫЕ", + roomFilterInvite: "ТОЛЬКО ПО ПРИГЛАШЕНИЮ", + roomFilterClosed: "ЗАКРЫТЫЕ", + roomCreate: "Создать комнату", + roomUpdate: "Обновить", + roomDelete: "Удалить", + roomClose: "Закрыть", + roomCreateSectionTitle: "Создать комнату", + roomUpdateSectionTitle: "Обновить комнату", + roomTitlePlaceholder: "Название комнаты", + roomDescriptionLabel: "Описание", + roomDescriptionPlaceholder: "О чём вы будете говорить?", + roomTypeLabel: "Тип комнаты", + roomTagsLabel: "Теги", + roomStatusOpen: "ОТКРЫТА", + roomStatusInviteOnly: "ТОЛЬКО ПО ПРИГЛАШЕНИЮ", + roomStatusClosed: "ЗАКРЫТА", + roomParticipants: "Участники", + roomJoin: "Войти", + roomLeave: "Выйти", + roomFull: "Комната заполнена", + roomLiveChip: "В ЭФИРЕ", + roomMuted: "микрофон выключен", + roomRec: "Записать аудио", + roomRecStop: "Остановить запись", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} вошёл в комнату", + roomEventLeft: "{name} вышел из комнаты", + roomEventRecStart: "{name} начал запись", + roomEventRecStop: "{name} остановил запись", + roomEventMuted: "{name} выключил микрофон", + roomEventUnmuted: "{name} включил микрофон", + roomEventHand: "{name} поднял руку", + roomRaiseHand: "Поднять руку", + roomLowerHand: "Опустить руку", + confirmActionTitle: "Подтвердите это действие", + confirmActionButton: "Подтвердить", + confirmJoinText: "Вы собираетесь присоединиться по этому приглашению.", + confirmFollowText: "Вы собираетесь поддержать этого жителя.", + confirmSignText: "Вы собираетесь подписать эту кампанию.", + confirmDownloadText: "Вы собираетесь скачать этот файл на своё устройство.", + roomRecordings: "Записи", + roomClearNotices: "Очистить", + roomLineSwitchboard: "Коммутатор", + roomLineDndError: "Номер этой комнаты не принимает звонки.", + roomNumberHint: "Наберите этот номер в Телефоне, чтобы войти в комнату.", + liveRefresh: "Обновить", + roomNobody: "Сейчас в комнате никого нет.", + roomSomeone: "Сейчас в комнате: {n}.", + roomYou: "вы", + roomYouAreIn: "Вы внутри", + roomNobodyInside: "Сейчас в этой комнате никого нет.", + roomErrorFull: "Комната заполнена. Вы сможете войти, когда кто-нибудь выйдет.", + roomErrorUnreachable: "Сейчас до комнаты не достучаться. Попробуйте позже.", + roomErrorBusy: "Вы уже в звонке или в другой комнате.", + roomErrorClosed: "Эта комната закрыта.", + roomAccessDenied: "Эта комната только по приглашению. Чтобы войти, нужно приглашение.", + roomsNoItems: "Комнаты не найдены.", + roomSearchPlaceholder: "Искать комнаты...", + roomInviteMode: "Приглашение", + tribeSectionRooms: "КОМНАТЫ", + tribeRoomCreate: "Создать комнату", + tribeRoomsEmpty: "Пока нет комнат.", + typeRoom: "КОМНАТЫ", + typeRoomMember: "УЧАСТНИК КОМНАТЫ", + statsRoom: "Комнаты", + statsRoomMember: "Участники комнат", + favoritesFilterRooms: "КОМНАТЫ", + invitesRoomsTitle: "Комнаты", + inviteCodeInvalid: "Этот код приглашения недействителен или уже использован.", + inviteAlreadyMember: "Вы уже участвуете. Откройте его в соответствующем модуле.", + actionFailed: "Не удалось это сделать. Попробуйте ещё раз.", + publishTooShort: "Ваша публикация слишком короткая.", + walletSendFailed: "Не удалось отправить платёж: {reason}", + pixeliaCoordsError: "Эти координаты находятся за пределами сетки.", + courtsErrTitle: "Нужен заголовок.", + courtsErrRespondent: "Укажите, против кого дело.", + courtsErrRespondentId: "Это недействительный Oasis ID.", + courtsErrMethod: "Выберите, как будет решено дело.", + courtsErrCaseNotFound: "Это дело не найдено.", + courtsErrBrief: "Напишите свой ответ.", + courtsErrStance: "Выберите свою позицию.", + courtsErrResult: "Укажите результат.", + courtsErrTerms: "Укажите условия соглашения.", + courtsErrMediator: "Добавьте хотя бы одного посредника.", + courtsErrSelfMediator: "Вы не можете быть посредником в своём деле.", + courtsErrJudge: "Выберите судью.", + courtsErrSelfJudge: "Вы не можете быть судьёй в своём деле.", + courtsErrVisibility: "Выберите, кто может видеть дело.", + courtsErrNomination: "Эта номинация не найдена.", + opinionAlreadyGiven: "Вы уже высказали своё мнение.", + cipherPasswordShort: "Пароль должен содержать не менее 32 символов.", + errorForbidden: "У вас нет доступа к этому.", + errorNotFound: "Не найдено.", + errorClosed: "Это закрыто.", + errorInviteOnly: "Только по приглашению. Нужно приглашение.", + errorTooLong: "Этот текст слишком длинный.", + errorRequired: "Не хватает обязательных данных.", + errorInvalid: "Часть данных недействительна.", + errorNotAuthor: "Это может сделать только автор.", + panicRemoved: "Ваш блокчейн удалён.", padRecentSectionTitle: "Недавние Пады", padOpenSectionTitle: "Открытые Пады", padClosedSectionTitle: "Закрытые Пады", @@ -3074,18 +3942,18 @@ module.exports = { padRemoveFavorite: "Удалить из Избранного", padClose: "Закрыть", padBackToEditor: "Назад к редактору", - padSearchPlaceholder: "Поиск падов...", + padSearchPlaceholder: "Поиск в Пады...", modulesChatsLabel: "Чаты", - modulesChatsDescription: "Модуль для обнаружения и управления зашифрованными чатами.", + modulesChatsDescription: "Модуль для поиска и управления чатами.", typeChat: "ЧАТЫ", typeChatMessage: "СООБЩЕНИЕ ЧАТА", chatsTitle: "Чаты", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "Ваши чаты", + chatRecentTitle: "Недавние чаты", chatFavoritesTitle: "Избранное", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "Открытые чаты", + chatClosedTitle: "Закрытые чаты", chatDescription: "Описание", chatCategory: "Категория", chatStatus: "СТАТУС", @@ -3100,34 +3968,172 @@ module.exports = { chatMessagesLabel: "Сообщения", chatThreadsLabel: "Темы", chatCreatedAt: "СОЗДАН", - chatSearchPlaceholder: "Поиск чатов...", + chatSearchPlaceholder: "Поиск в Чаты...", chatStatusOpen: "ОТКРЫТЫЙ", chatStatusInviteOnly: "ТОЛЬКО ПО ПРИГЛАШЕНИЮ", chatStatusClosed: "ЗАКРЫТЫЙ", chatSendMessage: "Отправить", chatJumpLatest: "Последнее сообщение", + chatReply: "Ответить", + chatReplyCancel: "Отменить ответ", + chatPin: "Закрепить", + chatUnpin: "Открепить", + chatPinned: "Закреплённые", chatPickChat: "Выберите чат, чтобы открыть его", chatNoneYet: "Пока нет доступных чатов.", - activitySearchPlaceholder: "Поиск по активности...", - trendingSearchPlaceholder: "Поиск в трендах...", - opinionsSearchPlaceholder: "Поиск в мнениях...", - votesSearchPlaceholder: "Поиск в голосованиях...", - welcomeStepUxTitle: "Выберите интерфейс", - welcomeStepUxText: "Выберите вид Oasis. Потом можно изменить в настройках.", - welcomeStepUxAction: "Использовать этот вид", + activitySearchPlaceholder: "Поиск в Активность...", + trendingSearchPlaceholder: "Поиск в В тренде...", + opinionsSearchPlaceholder: "Поиск в Мнения...", + votesSearchPlaceholder: "Поиск в Голосования...", + gamesSearchPlaceholder: "Поиск в Игры...", + schoolTitle: "Школа", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "Вики-страница, за которой вы следите, была изменена.", + wikiBotRestoredTitle: "Восстановлена более ранняя версия вики-страницы, за которой вы следите.", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "Новый студент записался на ваш курс.", + schoolBotInvitedTitle: "Вас пригласили на курс.", + schoolBotAdmittedTitle: "Вас приняли на курс.", + schoolBotCertificateTitle: "Вы получили сертификат.", + schoolBotPassedTitle: "Студент успешно прошёл ваш курс.", + schoolBotLessonTitle: "Опубликован новый урок.", + invitesSchoolTitle: "Школа", + schoolEnrollLabel: "Присоединиться к курсу", + schoolExamRetryLater: "Повторить можно через 24 часа.", + schoolApproved: "СДАНО", + schoolExamPassMark: "Проходной балл (0-10)", + schoolExamPassed: "СДАН", + schoolExamPending: "Ожидается", + schoolExamFailed: "НЕ СДАН", + schoolExamAddQuestion: "Добавить вопрос", + schoolExamCreateQuestion: "Создать вопрос", + schoolExamOption: "Вариант", + schoolExamCorrect: "Правильный вариант", + schoolExamPoints: "Баллы", + schoolMaterialTextPlaceholder: "Заметки к этому уроку...", + schoolMaterials: "Материалы", + schoolAddMaterial: "Добавить материал", + schoolNoMaterials: "Материалов пока нет.", + bankYourSchoolBalance: "Ваш доход от школы", + schoolProgress: "Прогресс", + schoolProgressMark: "Отметить как пройденный", + schoolProgressDone: "Пройден", + schoolExams: "Экзамены", + schoolExamCreate: "Создать экзамен", + schoolExamTitlePlaceholder: "Название экзамена", + schoolExamHelp: "Один вопрос в строке: Вопрос? ; вариант ; правильный вариант* ; вариант", + schoolExamQuestionsPlaceholder: "Что такое SSB? ; База данных ; P2P-протокол* ; Браузер", + schoolExamSubmit: "Отправить ответы", + schoolExamScore: "Ваш результат", + schoolNoScoresYet: "Результатов пока нет.", + schoolLessonUnit: "Раздел / фаза", + schoolLessonUnitPlaceholder: "напр. Фаза 1", + schoolLessonOrder: "Порядок", + profileClearnetSchoolLabel: "Школа", + schoolStartDate: "Дата начала", + schoolSessionDate: "Дата занятия", + schoolCourseChat: "Чат курса", + agendaFilterSchool: "ШКОЛА", + agendaFilterCampaigns: "КАМПАНИИ", + agendaFilterLogistics: "ЛОГИСТИКА", + schoolCourseType: "Тип курса", + schoolTypeOpen: "ОТКРЫТЫЙ", + schoolTypePaid: "ПЛАТНЫЙ", + schoolTypeInvite: "ТОЛЬКО ПО ПРИГЛАШЕНИЮ", + schoolLessonLocked: "Урок зашифрован — запросите ключ курса у преподавателя.", + schoolGrantAccess: "Отправить ключ курса", + schoolKeySent: "Ключ отправлен", + schoolOngoing: "Идёт", + schoolFilterApplied: "ЗАПИСАН", + schoolFilterOpen: "ОТКРЫТЫЕ", + schoolFilterFavorites: "ИЗБРАННОЕ", + schoolPendingPayment: "Ожидается оплата", + schoolPendingStudents: "Ожидающие записи", + schoolViewBill: "Посмотреть счёт", + schoolPaymentNote: "Запись создаёт счёт в ECO (перевод). Запись подтверждается после подписания перевода.", + schoolCloseCourse: "Закрыть курс", + schoolReopenCourse: "Открыть курс снова", + schoolDescription: "Преподавайте и изучайте курсы в вашей сети.", + schoolFilterAll: "ВСЕ", + schoolFilterMine: "МОИ", + schoolFilterTop: "ТОП", + schoolFilterRecent: "НЕДАВНИЕ", + schoolCreateButton: "Создать Курс", + schoolCreateSectionTitle: "Создать курс", + schoolUpdateSectionTitle: "Обновить курс", + schoolCourseTitlePlaceholder: "Название вашего курса", + schoolCourseDescriptionPlaceholder: "Чему вы будете учить на этом курсе?", + schoolCourseImage: "Изображение курса (макс. размер: 75MB)", + schoolTags: "Теги", + schoolTagsPlaceholder: "Введите теги через запятую", + schoolPrice: "Цена (ECO)", + schoolVisibility: "Видимость", + schoolVisibilityPublic: "Публичный", + schoolVisibilityInvite: "По приглашению", + schoolStatus: "Статус", + schoolClosed: "Закрыт", + schoolFree: "Бесплатно", + schoolCreate: "Создать курс", + schoolUpdate: "Обновить курс", + schoolSearchPlaceholder: "Поиск в Школа...", + schoolSortTitle: "Название", + schoolNoCourses: "Курсы не найдены.", + schoolStudents: "Студенты", + schoolTeacher: "Преподаватель", + schoolCreatedAt: "СОЗДАН", + schoolLessons: "Уроки", + schoolNoLessons: "Уроков пока нет.", + schoolEnrollToSee: "Запишитесь, чтобы увидеть уроки этого курса.", + schoolEnrollButton: "ЗАПИСАТЬСЯ", + schoolUnenrollButton: "Покинуть курс", + schoolInviteRequired: "Этот курс по приглашению.", + schoolTeacherPanel: "Панель преподавателя", + schoolStudentPanel: "Панель студента", + schoolAddLesson: "Создать урок", + schoolLessonTitlePlaceholder: "Название урока", + schoolLessonTextPlaceholder: "Опишите, что изучается в этом уроке...", + schoolInviteStudents: "Пригласить студентов", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "ПРИГЛАСИТЬ", + schoolInvited: "Приглашённые", + schoolIssueCertificate: "Создать сертификат", + schoolCertified: "Сертифицирован", + schoolCertificateTextPlaceholder: "Примечание к сертификату (необязательно)", + schoolLessonOpen: "Открыть урок", + schoolCertificatePdf: "PDF", + schoolCertificates: "Сертификаты", + schoolNoCertificates: "Сертификаты ещё не выданы.", + schoolEditButton: "Редактировать", + schoolDeleteButton: "Удалить", + typeSchool: "ШКОЛА", + schoolCourseLabel: "КУРС", + emergencyLabel: "ЧС", + wikiPageLabel: "ВИКИ", + statsSchoolCourse: "Курсы", + statsSchoolLesson: "Уроки", + statsSchoolEnroll: "Записи", + statsSchoolCertificate: "Сертификаты школы", + modulesSchoolLabel: "Школа", + modulesSchoolDescription: "Модуль для обмена знаниями.", + welcomeStepUxAction: "Сохранить вид", + welcomeStepWorkflowTitle: "Как вы собираетесь использовать OASIS?", + welcomeStepWorkflowText: "В Oasis много модулей. Выберите подходящую вам настройку, и включёнными останутся только полезные. Её можно изменить в любой момент в разделе «Модули».", + welcomeStepWorkflowAction: "Сохранить настройку", + welcomeWorkflow_full: "По умолчанию", chatRateLimitTitle: "Слишком много сообщений", chatRateLimitMessage: "Вы достигли лимита сообщений этой комнаты за час. Попробуйте позже.", chatMessagePlaceholder: "Введите сообщение...", chatNoMessages: "Сообщений пока нет.", chatTitlePlaceholder: "Название чата", chatDescriptionPlaceholder: "Описание чата", - chatTagsPlaceholder: "тег1, тег2, тег3", + chatTagsPlaceholder: "Введите теги через запятую", chatImageLabel: "Выбрать файл изображения (.jpeg, .jpg, .png, .gif)", chatAuthor: "Автор", chatAddFavorite: "Добавить в Избранное", chatRemoveFavorite: "Убрать из Избранного", chatPM: "ЛС", chatStatusLabel: "Статус", + chatTypeLabel: "Тип чата", chatCategoryLabel: "Категория", gamesTitle: "Игры", gamesDescription: "Откройте и играйте в игры.", @@ -3158,22 +4164,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "Подбросьте монету и угадайте орёл или решку. Насколько вы удачливы?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "Бильярд с видом сверху. Щёлкните, чтобы прицелиться, удерживайте, чтобы набрать силу. Забейте все шары за наименьшее число ударов.", + gamesArtilleryTitle: "Артиллерия", + gamesArtilleryDesc: "Наведите пушку, учтите ветер и поразите цель. 5 раундов, побеждает тот, кто сделал меньше выстрелов.", + gamesLabyrinthTitle: "Лабиринт", + gamesLabyrinthDesc: "Выберитесь из лабиринта, пока не закончились ходы. Каждый уровень больше и сложнее.", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "Съешьте все точки и избегайте призраков. Призраки двигаются в реальном времени — не останавливайтесь!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "Звуковой маятник", + gamesAudioPendulumDesc: "Симулятор хаотической физики с синтезом звука в реальном времени. Угловые скорости превращаются в частоты, пики — в удары барабана. Ни одна симуляция не звучит одинаково.", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "Сводите визуалы вживую на двух деках: выберите узор для каждой, настройте скорость, цвет и масштаб, переходите между ними и пускайте вспышки в такт. Отбейте темп или доверьте пульс микрофону.", + gamesTetrisDesc: "Классические падающие блоки. Заполняйте линии, чтобы набирать очки. Сколько продержитесь?", + gamesMinesweeperTitle: "Сапёр", + gamesMinesweeperDesc: "Классическая охота на мины. Очистите поле, чтобы перейти на новый уровень — больше поле, больше мин, выше счёт.", + gamesFilterScoring: "ОЧКИ", + gamesHallOfFame: "Зал славы", + gamesHallPlayer: "Игрок", + gamesHallScore: "Счёт", + gamesNoScores: "Пока нет результатов.", calendarsTitle: "Календари", calendarTitle: "Календарь", modulesCalendarsLabel: "Календари", @@ -3181,15 +4191,15 @@ module.exports = { typeCalendar: "КАЛЕНДАРИ", calendarCreate: "Создать календарь", calendarUpdate: "Обновить", - calendarDelete: "Удалить", calendarTitleLabel: "Название", calendarTitlePlaceholder: "Название календаря...", calendarStatusLabel: "Статус", + calendarTypeLabel: "Тип календаря", calendarStatusOpen: "ОТКРЫТЫЙ", calendarStatusClosed: "ЗАКРЫТЫЙ", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "Срок", calendarTagsLabel: "Теги", - calendarTagsPlaceholder: "тег1, тег2...", + calendarTagsPlaceholder: "Введите теги через запятую", calendarParticipantsLabel: "Участники", calendarCreated: "Создан", calendarJoin: "Присоединиться", @@ -3200,11 +4210,11 @@ module.exports = { calendarNotePlaceholder: "Добавить заметку...", calendarFirstDateLabel: "Дата", calendarFirstNoteLabel: "Заметки", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "Интервал", calendarIntervalNone: "Без повторения", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "Еженедельно", + calendarIntervalMonthly: "Ежемесячно", + calendarIntervalYearly: "Ежегодно", calendarFormDescription: "Описание", calendarNoDates: "Даты не добавлены.", calendarNoNotes: "Заметок нет.", @@ -3212,6 +4222,8 @@ module.exports = { calendarsDescription: "Находите и управляйте календарями в вашей сети.", calendarMonthPrev: "← Назад", calendarMonthNext: "Вперёд →", + calendarMonthNames: "Январь,Февраль,Март,Апрель,Май,Июнь,Июль,Август,Сентябрь,Октябрь,Ноябрь,Декабрь", + calendarDayInitials: "Пн,Вт,Ср,Чт,Пт,Сб,Вс", calendarAllSectionTitle: "Календари", calendarRecentSectionTitle: "Недавние календари", calendarFavoritesSectionTitle: "Избранное", @@ -3221,9 +4233,9 @@ module.exports = { calendarCreateSectionTitle: "Создать новый календарь", calendarUpdateSectionTitle: "Обновить календарь", calendarAddFavorite: "Добавить в избранное", - calendarDeleteNote: "Delete", + calendarDeleteNote: "Удалить", calendarRemoveFavorite: "Убрать из избранного", - calendarSearchPlaceholder: "Поиск календарей...", + calendarSearchPlaceholder: "Поиск в Календарь...", calendarAddEntry: "Добавить запись", statsCalendar: "Календари", statsCalendarDate: "Даты календаря", @@ -3235,6 +4247,146 @@ module.exports = { tribeContentAccessDenied: "Доступ Запрещён", tribeContentAccessDeniedMsg: "Этот контент принадлежит племени. Вы должны быть участником, чтобы получить к нему доступ.", tribeViewTribes: "Посмотреть Племена", + tribeFilesEmpty: "Торрентов пока нет.", + filesLabel: "Торренты", + profileClearnetFilesLabel: "Торренты", + filesTitle: "Торренты", + filesDescription: "Исследуйте и управляйте торрентами в вашей сети.", + fileAllSectionTitle: "Торренты", + fileMineSectionTitle: "Ваши Торренты", + fileRecentSectionTitle: "Недавние Торренты", + fileTopSectionTitle: "Лучшие Торренты", + fileFavoritesSectionTitle: "Избранное", + fileFilterAll: "ВСЕ", + fileFilterMine: "МОИ", + fileFilterRecent: "НЕДАВНИЕ", + fileFilterTop: "ЛУЧШИЕ", + fileFilterFavorites: "ИЗБРАННОЕ", + fileCreateSectionTitle: "Загрузить Торрент", + fileUpdateSectionTitle: "Обновить Торрент", + fileCreateButton: "Загрузить Торрент", + fileUpdateButton: "Обновить", + fileAddFavoriteButton: "Добавить в Избранное", + fileRemoveFavoriteButton: "Убрать из Избранного", + fileFileLabel: "Выберите файл (макс: 75MB)", + calendarFilterRecent: "НЕДАВНИЕ", + calendarFilterAll: "ВСЕ", + calendarFilterMine: "МОИ", + calendarFilterFavorites: "ИЗБРАННОЕ", + calendarFilterOpen: "ОТКРЫТЫЕ", + calendarFilterClosed: "ЗАКРЫТЫЕ", + padFilterRecent: "НЕДАВНИЕ", + padFilterAll: "ВСЕ", + padFilterMine: "МОИ", + padFilterOpen: "ОТКРЫТЫЕ", + padFilterClosed: "ЗАКРЫТЫЕ", + chatFilterRecent: "НЕДАВНИЕ", + chatFilterAll: "ВСЕ", + chatFilterMine: "МОИ", + chatFilterFavorites: "ИЗБРАННОЕ", + chatFilterOpen: "ОТКРЫТЫЕ", + chatFilterClosed: "ЗАКРЫТЫЕ", + licenseLabel: "Лицензия", + phoneTitle: "Телефон", + phoneDescription: "Управляйте своими видеозвонками со сквозным шифрованием.", + phoneUnavailable: "Звонки недоступны на этом устройстве.", + phoneCallButton: "Позвонить", + profileVisibilityPhone: "Номер телефона", + phoneNumberUnknown: "Ни у кого в вашей сети нет номера {number}.", + phoneNumberChoose: "Проверьте, кому вы хотите позвонить по номеру {number}:", + phoneAnswer: "Ответить", + phoneSpeaker: "Динамик", + phoneEarpiece: "Наушник", + phoneReject: "Отклонить", + phoneHangup: "Завершить", + phoneMute: "Выключить микрофон", + phoneUnmute: "Включить микрофон", + phoneCameraOn: "Камера вкл", + phoneCameraOff: "Камера выкл", + settingsPhoneVideo: "Видео", + settingsPhoneVideoOn: "Разрешено в звонках и комнатах", + settingsPhoneVideoOff: "Только звук", + phoneSilence: "Не слышать", + phoneUnsilence: "Слышать снова", + phoneCalling: "Вызов:", + phoneIsCalling: "звонит вам", + notifyNewPmFrom: "Новое ЛС от", + notifyNewPamFrom: "Новое PaM от", + notifyUpdateLabel: "Обновление", + updateInstalledLabel: "Установленная версия", + updateAvailableLabel: "Доступная версия", + updateChangesLabel: "Изменения", + updateFilesLabel: "Файлы", + updateOriginLabel: "Источник", + phoneRecording: "Запись PaM для", + phoneLeaveMessage: "Никто не ответил: оставьте сообщение после сигнала.", + phoneLeaveMessageBusy: "Абонент разговаривает: оставьте сообщение после сигнала.", + phoneLeaveMessageUnreachable: "Не удалось связаться: оставьте сообщение после сигнала.", + phoneConnecting: "Соединение с", + phoneSendPam: "Отправить PaM", + phoneCancel: "Отмена", + phoneSearchPlaceholder: "Поиск в Телефоне...", + phonePamTitle: "Аудиозаписи", + phoneNew: "Новое", + phoneDelete: "Удалить", + phoneNoPams: "У вас нет аудиозаписей.", + phoneHistoryTitle: "История", + phoneLatestTitle: "Последние звонки", + phoneCreateButton: "Новый звонок", + phoneLimitsHint: "До 7 жителей на звонок; если больше одного — это совместный звонок.", + phoneErrorPub: "Пабам звонить нельзя.", + phoneJointCall: "Совместный звонок", + phoneWith: "С кем", + phoneResult: "Результат", + phoneWhen: "Когда", + phoneDuration: "Длительность", + phoneOutgoing: "Исходящий", + phoneIncoming: "Входящий", + phoneNoHistory: "Записей пока нет.", + phoneOutcomeEnded: "Принят", + phoneOutcomeMissed: "Пропущен", + phoneOutcomeRejected: "Отклонён", + phoneOutcomeNoanswer: "Нет ответа", + phoneOutcomeCancelled: "Отменён", + phoneOutcomePam: "PaM отправлен", + phoneOutcomeFailed: "Не удалось", + phoneDialAdd: "Добавить", + phoneDialBack: "Стереть", + phoneDialClear: "Очистить", + phoneDialPlaceholder: "Номер или Oasis ID", + phoneDialInhabitants: "Жители", + phoneDialRemove: "Убрать", + phoneOwnNumber: "Это ваш собственный номер.", + phoneSettingsDesc: "Выберите, кто может вам звонить.", + phoneDnd: "Не беспокоить", + phoneOpen: "Открыто", + phoneRecipientRefuses: "Житель, которому вы пытаетесь позвонить, не принимает звонки.", + phoneRefusedNotice: "Эти жители не принимают звонки, им не звонили:", + fileEmptyError: "Файл пустой. Выберите другой файл.", + fileTitleLabel: "Название", + fileTitlePlaceholder: "Название", + fileDescriptionLabel: "Описание", + fileDescriptionPlaceholder: "Описание", + fileTagsLabel: "Теги", + fileTagsPlaceholder: "Введите теги через запятую", + fileSizeLabel: "Размер", + fileDownloadButton: "СКАЧАТЬ", + fileDetailsButton: "ПОДРОБНОСТИ", + fileNoFile: "Нет торрент-файла", + fileSearchPlaceholder: "Поиск в Торренты...", + fileSearchButton: "Поиск", + fileSortRecent: "Самые новые", + fileSortOldest: "Самые старые", + fileSortTop: "Самые популярные", + fileNoMatch: "Совпадений не найдено.", + fileUpdatedAt: "Обновлено", + typeFile: "ТОРРЕНТЫ", + modulesFilesLabel: "Файлы", + modulesFilesDescription: "Модуль для открытия и управления аудио.", + tribeSectionFiles: "ТОРРЕНТЫ", + fileDescription: "Описание", + fileDownload: "Скачать", + fileTypeLabel: "Тип", torrentsTitle: "Торренты", torrentsDescription: "Исследуйте и управляйте торрентами в вашей сети.", torrentAllSectionTitle: "Торренты", @@ -3262,10 +4414,33 @@ module.exports = { torrentTagsLabel: "Теги", torrentTagsPlaceholder: "Введите теги через запятую", torrentSizeLabel: "Размер", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "Создать торрент", + torrentFromContentMissing: "Файл ещё недоступен на этом узле. Попробуйте позже.", + torrentDownloads: "Загрузки", + torrentFetchOasis: "Скачать в Oasis", + torrentSaveFile: "Сохранить файл", + torrentSeeds: "сиды", + seedsLabel: "Сиды", + seedAction: "Поддержать", + torrentSourceFile: "Файл", + torrentStateQueued: "В очереди", + torrentStateProbing: "Поиск сидов", + torrentStateDownloading: "Загрузка", + torrentStateDone: "Завершено", + torrentStateFailed: "Ошибка", + torrentStateNoSource: "Нет подключённых сидов, ожидание", + torrentStateCancelled: "Отменено", + torrentNoDownloads: "Загрузок пока нет.", + torrentOriginalSection: "Оригинал", + torrentCancel: "Отмена", + torrentDownloadButton: "СКАЧАТЬ", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "ПОДРОБНОСТИ", torrentNoFile: "Нет торрент-файла", noTorrents: "Торренты не найдены.", - torrentSearchPlaceholder: "Поиск по названию, тегам, автору...", + noFiles: "Торренты не найдены.", + torrentSearchPlaceholder: "Поиск в Торренты...", torrentSearchButton: "Поиск", torrentSortRecent: "Самые новые", torrentSortOldest: "Самые старые", @@ -3273,10 +4448,12 @@ module.exports = { torrentNoMatch: "Совпадений не найдено.", torrentUpdatedAt: "Обновлено", statsTorrent: "Торренты", + statsFile: "Файлы", typeTorrent: "ТОРРЕНТЫ", modulesTorrentsLabel: "Торренты", modulesTorrentsDescription: "Модуль для поиска и управления торрентами.", favoritesFilterTorrents: "ТОРРЕНТЫ", + favoritesFilterFiles: "ФАЙЛЫ", favoritesFilterMarket: "РЫНОК", favoritesFilterEvents: "СОБЫТИЯ", favoritesFilterForum: "ФОРУМ", @@ -3290,15 +4467,17 @@ module.exports = { favoritesFilterShopProducts: "ТОВАРЫ МАГАЗИНА", tribeSectionTorrents: "ТОРРЕНТЫ", tribeCreateTorrent: "Загрузить Торрент", + tribeCreateFile: "Загрузить файл", settingsWishTitle: "Желание", - settingsWishDesc: "Настройте желание вашего Аватара.", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "Определяет, как вы получаете доступ ко всему контенту и ваш опыт в сети Oasis.", + settingsWishWhole: "Мультивселенная", + settingsWishMutuals: "Только взаимная поддержка", + settingsWishOnlyLan: "Только LAN", + settingsWishLocal: "Локально", settingsPmVisibilityTitle: "Личные сообщения", settingsPmVisibilityDesc: "Настройте уровень открытости личных сообщений.", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "Мультивселенная", + settingsPmVisibilityMutuals: "Только взаимная поддержка", pmMutualNotice: "Исходящие — защита UX. Входящие — фильтр внимания.", inhabitantsPendingFollowsTitle: "Ожидающие запросы на поддержку", inhabitantsPendingAccept: "Принять", @@ -3342,7 +4521,7 @@ module.exports = { tribeGovNoRevocations: "Пока нет ни одного отзыва", tribeGovNoHistorical: "Пока нет ни одной записи", logsTitle: "Журнал", - logsDescription: "Записывайте свой опыт в сети.", + logsDescription: "Ведите личный дневник ваших впечатлений.", logsViewTitle: "Журнал", logsColumnType: "Тип", logsView: "Смотреть", @@ -3351,7 +4530,7 @@ module.exports = { logsCreateTitle: "Создать запись", logsWriteButton: "Написать", logsTextPlaceholder: "Опишите ваши впечатления...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "ИИ-ассистент", logsModeManual: "Вручную", logsModeAI: "ИИ", logsColumnLog: "Журнал", @@ -3370,23 +4549,24 @@ module.exports = { logsExportOne: "Экспортировать запись", logsViewDetails: "Подробнее", logsGenerateButton: "Сгенерировать текст", - logsSearchText: "Искать в журналах...", + logsSearchText: "Поиск в Журнал...", logsSearchAnyType: "Любой тип", logsSearchButton: "Искать", logsEmpty: "Записей пока нет.", + logsNothingNew: "Нет новых действий для записи.", modulesLogsLabel: "Журнал", - modulesLogsDescription: "Модуль для записи (с помощью ИИ-ассистента) ваших впечатлений.", + modulesLogsDescription: "Модуль для ведения личного дневника ваших впечатлений.", statsLogsTitle: "Журнал", typeLog: "ЖУРНАЛ", larpTitle: "L.A.R.P.", larpDescription: "Слой ролевой игры в реальном времени для совместных экспериментов.", larpNoHousesMatch: "Нет домов, соответствующих запросу.", - larpSearchPlaceholder: "Поиск домов...", + larpSearchPlaceholder: "Поиск в L.A.R.P....", larpAllHouses: "Дома:", larpFilterRuling: "ПРАВИТ", larpFilterHouses: "ДОМА", - larpFilterRules: "FAQ", + larpFilterRules: "ЧАВО", larpRulesTitle: "L.A.R.P. FAQ", larpRulesEntryText: "Каждый житель по умолчанию начинает в ACADEMIA. Из ACADEMIA выберите дом в панели «Вступить в дом»: это откроет вступительный тест. Ответьте на 10 вопросов и отправьте. Если вы преодолеете порог (7/10), вы будете автоматически приняты в этот дом; иначе вас отклонят, и вы останетесь в ACADEMIA. В любом случае система записывает ваш OasisID и цикл попытки и не даёт повторить попытку до окончания периода ожидания в 30 циклов.", larpRulesGovernanceText: "В свой цикл правящий дом носит значок «Правит» и является единственным, чья Стена показывается публично под вкладкой ПРАВИТ.", @@ -3397,118 +4577,117 @@ module.exports = { larpPostPlaceholder: "Что должен сказать этот дом?", larpPostSubmit: "Опубликовать", larpCycleLabel: "Цикл:", - audioBackToBcs: "Back to BCS", + audioBackToBcs: "Назад к BCS", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeCompositionEmpty: "This audio does not include a stored blockchain composition.", - audioTranscodeDetailDescription: "Decode the embedded payload and the original blockchain composition map.", - audioTranscodeDetailTitle: "Transcode", - audioTranscodeStegoEmpty: "(none)", - audioTranscodeStegoMessage: "TEXT", - audioTranscodeStegoNotFound: "No steganographic payload could be decoded from this audio.", - audioTranscodeStegoOasisId: "By", - audioTranscodeStegoTimestamp: "Generated at", + audioTranscodeButton: "ПЕРЕКОДИРОВАТЬ", + audioTranscodeCompositionEmpty: "Это аудио не содержит сохранённой композиции блокчейна.", + audioTranscodeDetailDescription: "Декодировать встроенную полезную нагрузку и исходную карту композиции блокчейна.", + audioTranscodeDetailTitle: "Перекодирование", + audioTranscodeStegoEmpty: "(нет)", + audioTranscodeStegoMessage: "ТЕКСТ", + audioTranscodeStegoNotFound: "Не удалось декодировать стеганографическую нагрузку из этого аудио.", + audioTranscodeStegoTimestamp: "Создано", audioTranscodeStegoUnknown: "—", - bankExchangeEcoTaxAnnual: "ECOin Taxes (annual)", - bankExchangeEcoTaxMonthly: "ECOin Taxes (monthly)", - bankOverviewYourTaxes: "Your taxes", - bankRulesAlpha: "Alpha (max share of pub balance per epoch)", + bankExchangeEcoTaxAnnual: "Налоги ECOin (в год)", + bankExchangeEcoTaxMonthly: "Налоги ECOin (в месяц)", + bankOverviewYourTaxes: "Итого налоги", + bankRulesAlpha: "Alpha (макс. доля баланса паба за эпоху)", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", - bankRulesArchTaxRate: "Rate", - bankRulesArchTaxTitle: "ARCH Tax", - bankRulesCapPerEpoch: "Cap per epoch (absolute)", - bankRulesCapUser: "Cap per user per epoch", - bankRulesCarbonFactor: "Carbon factor", + bankRulesArchTaxNote: "ARCH-налог отражает долгосрочную стоимость роста и поддержки архива сети: чем старше ваши данные в сети, тем больше ваша доля расходов на обслуживание.", + bankRulesArchTaxRate: "Ставка", + bankRulesArchTaxTitle: "ARCH-налог", + bankRulesCapPerEpoch: "Лимит на эпоху (абсолютный)", + bankRulesCapUser: "Лимит на пользователя за эпоху", + bankRulesCarbonFactor: "Углеродный коэффициент", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", - bankRulesChipTitle: "ECO Tax chip color band", + bankRulesChipNote: "Метка каждого элемента окрашивается относительно самого большого сообщения, когда-либо замеченного в сети, с поправкой на число активных жителей — более крупная сеть распределяет нагрузку и переводит больше элементов в зелёную зону.", + bankRulesChipTitle: "Цветовая шкала метки ECO-налога", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", - bankRulesEcoTaxRate: "Rate", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesEpochKind: "Epoch granularity", - bankRulesFloor: "Floor per user (gross)", - bankRulesGraceDays: "Claim grace period", + bankRulesEcoTaxNote: "ECO-налог вычитается из валового ББД при расчёте распределения эпохи. Вычтенные ECO не перераспределяются в этой эпохе — они остаются на балансе паба и пополняют фонд следующей эпохи через ограничения reserveMin / alpha.", + bankRulesEcoTaxRate: "Ставка", + bankRulesEcoTaxTitle: "ECO-налог", + bankRulesEpochKind: "Гранулярность эпохи", + bankRulesFloor: "Минимум на пользователя (брутто)", + bankRulesGraceDays: "Льготный период для заявки", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", - bankRulesKarmaTitle: "Karma (user engagement score)", + bankRulesKarmaNote: "Карма вычисляется по вашим действиям (публикации, голоса, участие в парламенте/судах и т. д.) с коэффициентом затухания во времени, за вычетом граммов углерода, созданных вашей лентой.", + bankRulesKarmaTitle: "Карма (оценка вовлечённости)", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", - bankRulesPoolTitle: "Monthly pool", - bankRulesReserveMin: "Reserve minimum (kept in pub balance)", + bankRulesPoolTitle: "Ежемесячный фонд", + bankRulesReserveMin: "Минимальный резерв (остаётся на балансе паба)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", - bankRulesShareTitle: "Per-user share", + bankRulesShareTitle: "Доля на пользователя", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankRulesWMax: "Maximum weight (w_max)", - bankRulesWMin: "Minimum weight (w_min)", - bankTaxes: "Taxes", - bankTaxesAnnualEcoin: "ECO Tax (annual)", - bankTaxesArchTaxAnnual: "ARCH Tax (annual)", - bankTaxesArchTaxFirstBlock: "Your first block age", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxLifetime: "ARCH Tax (lifetime)", - bankTaxesArchTaxMonthly: "ARCH Tax (monthly)", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesArchTaxRate: "Rate", - bankTaxesArchTaxTitle: "ARCH Tax", - bankTaxesArchTaxUserAmount: "Your ARCH tax (price to return)", - bankTaxesEcoTaxLifetime: "ECO Tax (lifetime)", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesLookupAuthor: "Author", - bankTaxesLookupButton: "Inspect", - bankTaxesLookupCarbon: "Carbon footprint", - bankTaxesLookupEcoin: "ECO Tax", - bankTaxesLookupNotFound: "No block found in your local feed with that ID.", - bankTaxesLookupPlaceholder: "Block ID (e.g. %abc...=.sha256)", - bankTaxesLookupSize: "Size", - bankTaxesLookupTitle: "Inspect a block", - bankTaxesLookupType: "Type", - bankTaxesMonthlyEcoin: "ECO Tax (monthly)", - bankTaxesRate: "Rate", - bankTaxesSpan: "Sampling span", - bankTaxesTotalAnnual: "Total (annual)", - bankTaxesTotalBlocks: "Total blocks", - bankTaxesTotalBytes: "Total bytes", - bankTaxesTotalCarbon: "Total carbon", - bankTaxesTotalEcoin: "Total ECO tax (lifetime)", - bankTaxesTotalLifetime: "Total (lifetime)", - bankTaxesTotalMonthly: "Total (monthly)", - bankTaxesTypesApply: "Set my taxes", - bankTaxesTypesLabel: "Select which taxes you want to pay", - bankTaxesUserAmount: "Your ECO tax (price to return)", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - blockchainBackToBlockexplorer: "Back to blockexplorer", - blockchainBlockNotAvailable: "This block is not available in your local feed. It may belong to a peer you are not yet replicating from.", - ecoTaxLabel: "ECO Tax", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - melodyAllEmpty: "No BCS compositions from other inhabitants yet.", - melodyByLabel: "By", - melodyNoteUnavailable: "block not in your local feed", - melodyUploadBcsButton: "Publish", - melodyUploadBcsNameNote: "The audio will be published with the auto-generated name", - melodyUploadBcsTitle: "Publish", - melodyUploadStegoLabel: "Hidden message (steganography)", - melodyUploadStegoMaxLabel: "max 280 characters", - melodyUploadStegoPlaceholder: "Optional.", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", - statsPad: "Pads", - statsPadEntry: "Pad entries", - statsTombstoneEmpty: "No tombstones in the network yet.", - typeGameScore: "GAME SCORES", + bankRulesWMax: "Максимальный вес (w_max)", + bankRulesWMin: "Минимальный вес (w_min)", + bankTaxes: "Налоги", + bankTaxesAnnualEcoin: "ECO-налог (в год)", + bankTaxesArchTaxAnnual: "ARCH-налог (в год)", + bankTaxesArchTaxFirstBlock: "Возраст вашего первого блока", + bankTaxesArchTaxFootprintNote: "ARCH-налог отражает стоимость роста и поддержки архива сети. Он рассчитывается по промежутку времени между вашим первым опубликованным блоком и самым новым блоком в сети — чем дольше ваша история живёт в архиве, тем больше ваша доля расходов на обслуживание.", + bankTaxesArchTaxLifetime: "ARCH-налог (за всё время)", + bankTaxesArchTaxMonthly: "ARCH-налог (в месяц)", + bankTaxesArchTaxNoteBold: "прямому снижению ARCH-налога", + bankTaxesArchTaxNoteIntro: "Ваш ARCH-налог вычитается из излишка, который сеть создаёт сверх вашего ББД; базовое значение ББД установлено как незыблемый минимум. Налог никогда не вычитается из фиксированной суммы, положенной каждому жителю в качестве ББД. Вычтенные средства питают алгоритм перераспределения богатства и возвращаются другим жителям через их заявки на ББД. У новых жителей и тех, кто специально занимается архивированием, репликацией и обслуживанием, скорее всего, будет задача поддерживать архив сети в порядке от вашего имени. Или они могут посвятить себя ", + bankTaxesArchTaxNoteOutro: " с помощью соглашений об обрезке, дедупликации избыточных данных и общей инфраструктуры обслуживания, а их постоянный вклад может потребоваться по консенсусу сети.", + bankTaxesArchTaxRate: "Ставка", + bankTaxesArchTaxTitle: "ARCH-налог", + bankTaxesArchTaxUserAmount: "Ваш ARCH-налог (сумма к возврату)", + bankTaxesEcoTaxLifetime: "ECO-налог (за всё время)", + bankTaxesEcoTaxTitle: "ECO-налог", + bankTaxesLookupAuthor: "Автор", + bankTaxesLookupButton: "Проверить", + bankTaxesLookupCarbon: "Углеродный след", + bankTaxesLookupEcoin: "ECO-налог", + bankTaxesLookupNotFound: "В вашей локальной ленте не найден блок с таким ID.", + bankTaxesLookupPlaceholder: "ID блока (напр. %abc...=.sha256)", + bankTaxesLookupSize: "Размер", + bankTaxesLookupTitle: "Проверить блок", + bankTaxesLookupType: "Тип", + bankTaxesMonthlyEcoin: "ECO-налог (в месяц)", + bankTaxesRate: "Ставка", + bankTaxesSpan: "Период выборки", + bankTaxesTotalAnnual: "Итого (в год)", + bankTaxesTotalBlocks: "Всего блоков", + bankTaxesTotalBytes: "Всего байт", + bankTaxesTotalCarbon: "Всего углерода", + bankTaxesTotalEcoin: "Итого ECO-налог (за всё время)", + bankTaxesTotalLifetime: "Итого (за всё время)", + bankTaxesTotalMonthly: "Итого (в месяц)", + bankTaxesTypesApply: "Установить мои налоги", + bankTaxesTypesLabel: "Выберите, какие налоги вы хотите платить", + bankTaxesUserAmount: "Ваш ECO-налог (сумма к возврату)", + bankTaxesUserNoteChips: "Метка ECO-налога рядом с каждым элементом меняет цвет (зелёный / жёлтый / красный) в зависимости от размера элемента относительно самого большого блока, когда-либо виденного в сети, с поправкой на число активных жителей — более крупная солнечно-панковская сеть распределяет нагрузку и переводит больше элементов в зелёную зону.", + bankTaxesUserNoteOtherParams: "Сейчас ECO-налог рассчитывается по углеродному следу каждого блока, но со временем может учитывать и другие параметры — энергию, затраченную на репликацию, избыточное хранение у узлов, пропускную способность для блобов, вычислительную стоимость расшифровки, майнинговый след связанных транзакций или любую другую измеримую нагрузку, которую сеть договорится учитывать.", + blockchainBackToBlockexplorer: "Назад к обозревателю блоков", + blockchainBlockNotAvailable: "Этот блок недоступен в вашей локальной ленте. Возможно, он принадлежит узлу, с которого вы ещё не реплицируете.", + ecoTaxLabel: "ECO-налог", + gamesHallDate: "Дата", + gamesNewRecord: "Новый рекорд!", + indexingRefreshNote: "Страница обновляется каждые 10 секунд.", + indexingMessage: "Oasis синхронизирует распределённую сеть узлов. Пожалуйста, подождите.", + indexingTitle: "Синхронизация", + melodyAllEmpty: "Пока нет BCS-композиций других жителей.", + melodyNoteUnavailable: "блока нет в вашей локальной ленте", + melodyUploadBcsButton: "Опубликовать", + melodyUploadBcsNameNote: "Аудио будет опубликовано с автоматически созданным именем", + melodyUploadBcsTitle: "Опубликовать", + melodyUploadStegoLabel: "Скрытое сообщение (стеганография)", + melodyUploadStegoMaxLabel: "не более 280 символов", + melodyUploadStegoPlaceholder: "Необязательно.", + padsNoItems: "Пады не найдены.", + padVersionHistory: "История версий", + padAttributionView: "По авторам", + padVersionView: "Просмотр", + statsChat: "Чаты", + statsChatMessage: "Сообщения чата", + statsGameScore: "Игры", + statsWiki: "Вики-страницы", + statsPad: "Пады", + statsPadEntry: "Записи падов", + statsTombstoneEmpty: "В сети пока нет удалений (tombstones).", + typeGameScore: "ИГРОВЫЕ ОЧКИ", bankTaxesLookupNote: "Введите ID блока, чтобы найти его в вашем локальном канале.", bankTaxesUserNoteChipsClick: "Нажмите на любой чип, чтобы проверить его блок в blockexplorer.", mapUrlPlaceholder: "/maps/MAP_ID", @@ -3523,6 +4702,14 @@ module.exports = { settingsReplicationTitle: "Репликация", settingsReplicationDesc: "Настройте количество переходов, которые ваш узел делает от вашего собственного канала при репликации содержимого.", settingsReplicationHopsLabel: "Переходы", + settingsBlobCacheTitle: "Кэш медиа", + settingsBlobCacheDesc: "Контент других жителей хранится на этом устройстве. Выберите, сколько места он может занимать.", + settingsBlobCacheUsage: "Текущее использование", + settingsBlobCacheFiles: "файлов", + settingsBlobCacheLimit: "Лимит", + settingsBlobCacheUnlimited: "Без ограничения", + settingsBlobCacheCleanNow: "Очистить сейчас", + settingsBlobCacheCleaned: "Удалено файлов: {n}, освобождено {size}.", aiApproveTagsLabel: "Теги (через запятую)", aiApproveTagsPlaceholder: "напр. oasis, управление, экология", aiApproveRatingLabel: "Оценка", @@ -3550,8 +4737,6 @@ module.exports = { larpInviteBannerHint: "Поделитесь этим кодом с кем-то в ACADEMIA. Срок действия — 30 циклов или одно использование.", larpTestRankingTitle: "Очки по домам", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "Код приглашения дома", - invitesHouseJoinButton: "Вступить в Дом", larpLastAttemptTitle: "Ваша последняя попытка", larpLastAttemptHouse: "Дом", larpLastAttemptWhen: "Когда", @@ -3569,7 +4754,7 @@ module.exports = { larpBadgeYou: "Вы", larpBadgeRuling: "Правит", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Модуль слоя ролевой игры в реальном времени Oasis (9 Домов).", + modulesLarpDescription: "Модуль для слоя живой ролевой игры.", larpProfileQ1: "Столкнувшись со сложной проблемой, что вы делаете в первую очередь?", larpProfileQ1O1: "Говорить с другими, чтобы найти консенсус", larpProfileQ1O2: "Создать прототип для проверки", @@ -3643,7 +4828,52 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "Выберите режим UX-навигации для вашего интерфейса.", uxModeMenus: "Блоки", - uxModeAINav: "AI", + uxModeChats: "Беседы", + uxModeFeed: "Микроблогинг", + feedTrendingTitle: "Популярные теги", + feedActiveTitle: "Активные жители", + cnBrandSub: "Свободная · P2P · Федеративная", + cnSyncedPeers: "Синхронизированные узлы", + cnLicense: "Лицензия", + cnNotAccessible: "Контент сейчас недоступен.", + cnUntitled: "Без названия", + cnSearchPlaceholder: "Поиск…", + cnHubTitle: "Clearnet HUB", + cnHubDescription: "Публичный контент, которым делятся жители этой сети Oasis.", + cnHubEmpty: "В Clearnet пока не опубликовано публичного контента.", + cnPublicContent: "Контент", + cnTribesContent: "Племена", + cnNetworkContent: "Сеть", + cnCategoryEmpty: "В этой категории нет контента.", + cnInhabitantEmpty: "Этот житель пока не публиковал контент в Clearnet.", + cnPrevPage: "← Назад", + cnNextPage: "Далее →", + cnPageOf: "Страница {page} из {pages}", + cnMoreInfo: "Подробнее →", + cnOpenCourse: "Открытый курс", + cnAuction: "АУКЦИОН", + cnExchange: "ОБМЕН", + cnRssDescription: "опубликовано жителями этого HUB", + cnKindAudio: "Аудио", + cnKindBlog: "Блог", + cnKindBookmark: "Закладка", + cnKindDocument: "Документ", + cnKindEvent: "Событие", + cnKindFeed: "Лента", + cnKindImage: "Изображение", + cnKindJob: "Вакансия", + cnKindMarket: "Рынок", + cnKindPodcast: "Подкаст", + cnKindProject: "Проект", + cnKindCourse: "Курс", + cnKindShop: "Магазин", + cnKindTorrent: "Торрент", + cnKindFile: "Файл", + cnKindVideo: "Видео", + cnKindWiki: "Вики", + cnKindRoom: "Комната", + cnKindMap: "Карта", + uxModeAINav: "ИИ", } }; diff --git a/src/client/assets/translations/oasis_zh.js b/src/client/assets/translations/oasis_zh.js index 4080916..f55987b 100644 --- a/src/client/assets/translations/oasis_zh.js +++ b/src/client/assets/translations/oasis_zh.js @@ -9,7 +9,7 @@ module.exports = { shopPurchasesButton: "购买记录", shopMyOrdersDescription: "你的购买订单。", shopMyOrdersEmpty: "你还没有购买记录。", - shopOrderPmSeller: "PM Seller", + shopOrderPmSeller: "PM 卖家", shopOrderSeller: "卖家", shopOrderInvoice: "发票", shopOrderInvoiceConcept: "发票", @@ -20,7 +20,7 @@ module.exports = { shopOrderAccept: "接受", shopOrderReject: "拒绝", shopOrderMarkShipped: "标记为已发货", - shopOrderPmBuyer: "PM Buyer", + shopOrderPmBuyer: "PM 买家", shopOrderContractConcept: "商店订单", shopOrdersPending: "待处理订单", all: "全部", @@ -54,13 +54,13 @@ module.exports = { contentWarningLabel: "内容警告", invalidPost: "无效内容", invalidPostHint: "此消息文本无效或为空。", - spreadBy: "由", spreadChron: "传播", spreaded: "已传播", spreadContentUnavailable: "内容尚不可用(等待同步)", filteredByTag: "按标签筛选", filteredByTagTitle: "按标签筛选", feedPublishedSuccess: "动态发布成功!", + feedMastodonFailed: "已在 Oasis 发布,但无法发送到 Mastodon。", tribeFeedSent: "消息发送成功!", tribeContentEncrypted: "加密内容", tribeTorrentsEmpty: "暂无种子。", @@ -77,8 +77,7 @@ module.exports = { bookmarkLastVisit: "最近访问", profileClearnetBookmarksLabel: "书签", invitesPort: "端口", - currentlyUnrecheable: "错误!", - peerHostValidation: "有效的 IPv4(如 192.168.1.100)或主机名(如 pub.example.com)", + peerHostValidation: "有效的 IPv4(如 192.168.1.100)或主机名(如 pub.solarnethub.com)", peerPortValidation: "端口 1-65535", peerKeyValidation: "SSB ed25519 公钥 (@<44 字符 base64>=.ed25519)", inboxJobUnsubscribedTitle: "取消订阅你的招聘", @@ -105,7 +104,7 @@ module.exports = { taskDescriptionPlaceholder: "任务描述", exportPasswordLabel: "导出密码", importPasswordPlaceholder: "输入密码", - searchCasesPlaceholder: "搜索案件…", + searchCasesPlaceholder: "在法庭中搜索...", shopBuyDeliveryAddressPlaceholder: "收货地址", shopBuyNotesPlaceholder: "备注", bankTaxesUserNoteBold: "直接降低 ECO 税", @@ -126,11 +125,6 @@ module.exports = { courtsSupportCase: "支持该案", courtsSettlementAcceptBtn: "接受和解", courtsSupportCount: "支持者", - courtsVerdictVoteTitle: "对裁决投票", - courtsVerdictVoteLabel: "裁决投票", - courtsVerdictVoteAccept: "接受", - courtsVerdictVoteReject: "拒绝", - courtsVerdictVoteSubmit: "提交投票", reportsSetStatus: "设置状态", reportsStatusClosed: "已关闭", statsAvgPerInhabitant: "人均", @@ -189,9 +183,29 @@ module.exports = { ], profile: "头像", inhabitants: "居民", - peersReplicatedFeeds: "复制的 Feed", + peersReplicatedFeeds: "已复制", + peersTorColumn: "TOR", + peersTorYes: "可通过 Tor 访问", + peersTorNo: "无法通过 Tor 访问", + peersOasisId: "Oasis ID", + peersOasisVersion: "版本", graphos: "Graphos", graphosDescription: "你周围网络的互动地图。", + graphosFederation: "联邦", + graphosFedPubs: "PUB", + graphosFedUnreachable: "无法访问", + graphosFedLinks: "双向链接", + graphosFedIsolated: "孤立", + graphosFedNoData: "无数据", + graphosFedMutual: "双向", + graphosFedOneWay: "单向", + graphosFedTransport: "网络", + graphosFedWith: "联邦对象", + graphosZoom: "放大", + graphosFedInhabitants: "居民", + graphosFedClearnet: "互联网", + graphosFedBoth: "互联网 + TOR", + graphosFedEmpty: "你的节点还不知道任何 PUB。", graphosViewingGraphOf: "正在查看", graphosBackToMine: "← 我的网络", graphosShowing: "显示", @@ -203,8 +217,24 @@ module.exports = { "。", ], noMentions: "你还没有收到@提及。", - mentionsSearchPlaceholder: "搜索提及...", + mentionsSearchPlaceholder: "在提及中搜索...", privateReminders: "提醒", + inboxFilterPms: "私信", + inboxFilterNotifications: "通知", + inboxMarkRead: "标为已读", + inboxMarkUnread: "标为未读", + inboxUnreadChip: "未读", + inboxMarkAllRead: "全部标为已读", + inboxDeleteShown: "全部删除", + inboxArchive: "归档", + inboxUnarchive: "取消归档", + inboxFilterArchived: "已归档", + inboxEarlierMessages: "条较早的消息", + inboxSortThread: "按会话", + inboxSortRecent: "最新优先", + inboxAllBots: "机器人", + settingsInboxBotsTitle: "收件箱通知", + settingsInboxBotsDesc: "选择哪些机器人可以在收件箱中通知你。未勾选的机器人将被隐藏,其通知不计入计数。", inboxFiltersLabel: "筛选:", inboxToggleToMutuals: "切换为相互关注", inboxToggleToWhole: "切换为所有人", @@ -212,6 +242,7 @@ module.exports = { pmReply: "回复", pmReplies: "回复", inReplyTo: "回复", + forumInThread: "主题内", pmPreview: "预览", pmPreviewTitle: "消息预览", peers: "节点", @@ -219,8 +250,9 @@ module.exports = { searchFromLabel: "从", searchToLabel: "到", searchQueryLabel: "查询", - searchPerPageLabel: "每页结果数", + searchPerPageLabel: "结果", settings: "设置", + settingsDescription: "设置你的网络环境。", continueReading: "继续阅读", moreComments: "更多评论", readThread: "阅读剩余讨论串", @@ -241,7 +273,6 @@ module.exports = { profileClearnetHelp: '可以从 Oasis 外部访问的模块。', profileHubAll: '全部', footerLicenseLabel: '许可证', - profileSwitchToOasis: '返回 Oasis', profileVisibilityFediverse: "多元宇宙", contributorsTitle: "贡献者", colorLabel: '选择颜色', @@ -251,6 +282,7 @@ module.exports = { modulesViewTitle: "模块", modulesViewDescription: "通过启用或禁用模块来设置你的环境。", inbox: "收件箱", + inboxSearchPlaceholder: "在收件箱中搜索...", multiverse: "多元宇宙", fediverse: "多元宇宙", fediverseDescription: "管理你的其他多元宇宙账户,包括发送和接收内容。", @@ -259,9 +291,17 @@ module.exports = { fediverseSettingsTitle: "多元宇宙", fediverseInstanceLabel: "地址", fediverseTokenLabel: "访问令牌", - fediverseTokenHelp: "设置你的访问令牌。它将用于在 Oasis 中管理你的 Mastodon 账户。", fediverseConnect: "连接", fediverseConnectedAs: "已连接为", + nextcloudServerLabel: "服务器", + nextcloudUserLabel: "用户", + nextcloudFolderLabel: "文件夹", + nextcloudUploadButton: "上传", + nextcloudFileLabel: "选择文件", + fileExternalLink: "外部链接", + nextcloudErrConnect: "无法用这些信息连接到 Nextcloud。", + nextcloudErrUpload: "上传到 Nextcloud 失败。", + nextcloudErrShare: "Nextcloud 没有为该文件提供公开链接。", fediverseDisconnect: "断开连接", fediverseEmptyLink: "设置", fediverseComposePlaceholder: "在想什么?", @@ -290,6 +330,76 @@ module.exports = { fediverseErrPost: "无法发布。", fediverseErrMedia: "无法上传文件。", fediverseErrEmpty: "写点什么或添加文件。", + telegramApiIdLabel: "ID", + telegramApiHashLabel: "API hash", + telegramPhoneLabel: "手机号", + telegramSendCode: "发送验证码", + telegramCodeHelp: "输入 Telegram 刚发送的登录码(应用或短信)。", + telegramCodeLabel: "登录码", + telegramPasswordHelp: "该账号启用了两步验证,请输入云密码。", + telegramPasswordLabel: "密码", + telegramVerify: "验证", + telegramCancel: "取消", + telegramChats: "聊天", + telegramUnread: "未读", + telegramOpen: "打开", + telegramSend: "发送", + telegramYou: "你", + telegramNoChats: "还没有聊天。", + telegramNoMessages: "还没有消息。", + telegramComposePlaceholder: "输入消息…", + telegramChannel: "频道", + telegramGroup: "群组", + telegramPrivate: "私聊", + telegramErrConnect: "无法连接 Telegram。", + telegramErrTimeout: "Telegram 响应超时。", + telegramErrAuth: "Telegram 会话无效或已过期,请重新连接。", + telegramErrFetch: "无法加载聊天。", + telegramErrSend: "无法发送消息。", + telegramErrMissing: "需要 API ID、API hash 和手机号。", + telegramErrCode: "验证码无效,请重试。", + telegramErrCodeExpired: "验证码已过期,请重新开始。", + telegramErrPassword: "密码错误。", + telegramErrPhone: "手机号无效。", + telegramErrApi: "API ID 或 API hash 无效。", + telegramErrFlood: "尝试次数过多,请稍后再试。", + telegramErrNoLogin: "没有进行中的登录,请重新开始。", + peertubeUsernameLabel: "用户名", + peertubePasswordLabel: "密码", + peertubeSubscriptions: "订阅", + peertubeMyVideos: "我的视频", + peertubeVideos: "视频", + peertubeFollowers: "关注者", + peertubeViews: "次观看", + peertubeLikes: "赞", + peertubeQuota: "配额", + peertubeUpload: "上传视频", + peertubeUploadHelp: "选择一个视频文件。它会上传到你的频道,随后由 PeerTube 处理。", + peertubeTitleLabel: "标题", + peertubeDescriptionLabel: "描述", + peertubePrivacyLabel: "隐私", + peertubePrivacyPublic: "公开", + peertubePrivacyUnlisted: "不公开列出", + peertubePrivacyPrivate: "私密", + peertubeNoVideos: "还没有视频。", + peertubeComments: "评论", + peertubeComment: "评论", + peertubeCommentPlaceholder: "写下评论…", + peertubeNoComments: "还没有评论。", + peertubeOpen: "在 PeerTube 打开", + peertubeWatch: "观看", + peertubeChannel: "频道", + peertubeNoStream: "此视频暂时无法在这里播放。请在 PeerTube 打开。", + peertubeUploaded: "视频已上传。PeerTube 正在处理。", + peertubeErrConnect: "无法连接 PeerTube。", + peertubeErrAuth: "用户名或密码错误,或会话已过期。请重新连接。", + peertubeErrInstance: "实例地址无效。", + peertubeErrMissing: "需要实例、用户名和密码。", + peertubeErrFetch: "无法加载视频。", + peertubeErrUpload: "无法上传视频。", + peertubeErrTitle: "需要标题。", + peertubeErrComment: "无法发布评论。", + peertubeErrNoFile: "请选择视频文件。", bookmarksLabel: "书签", videosLabel: "视频", torrentsLabel: "种子", @@ -312,7 +422,6 @@ module.exports = { comment: "评论", subtopic: "子话题", json: "JSON", - createdBy: "作者", unfollow: "取消支持", follow: "支持", block: "屏蔽", @@ -328,12 +437,15 @@ module.exports = { relationshipNone: "你未支持", relationshipBlockingPost: "已屏蔽的帖子", viewLikes: "查看传播", + viewSupporters: "查看支持者", spreadedDescription: "该居民传播的帖子列表。", preview: "预览", publish: "写作", contentWarningPlaceholder: "为帖子添加主题(可选)", - publishWarningPlaceholder: "正文上限 7000 个字符。", + publishWarningPlaceholder: "写下你的文章正文。", publishTooLong: "你的帖子太长了,请缩短。", + keepTextRetry: "重新发送", + dateInPastError: "日期不能早于今天", publishCustomDescription: [ "提醒:由于区块链技术,帖子一旦发布就无法编辑或删除。", ], @@ -363,9 +475,8 @@ module.exports = { mentionsMatching: "匹配的提及", mentionsName: "名称", mentionsRelationship: "关系", - updateit: "获取更新!", updateBannerText: "Oasis 有新版本可用。", - updateBannerAction: "立即更新 →", + updateBannerAction: "立即更新!", info: "信息", settingsIntro: ({ version }) => [ `[SNH] ꖒ OASIS [ v.${version} ]`, @@ -373,16 +484,14 @@ module.exports = { timeAgo: "前", sendTime: "大约 ", theme: "主题", - legacy: "密钥", - legacyTitle: "密钥", - legacyDescription: "快速安全地管理你的密钥(私钥)。", - legacyExportButton: "导出", - legacyImportButton: "导入", + backupKeysExportButton: "导出", + backupKeysImportButton: "导入", ssbLogStream: "区块链", ssbLogStreamDescription: "配置区块链流的消息限制。", + logWindowChip: "最近 {n} 条区块链流", saveSettings: "保存设置", exportDescription: "设置密码(至少32个字符)以加密你的密钥", - exportDataTitle: "备份", + exportDataTitle: "即时备份", exportDataDescription: "下载你的数据(不包含私钥!)", exportDataButton: "下载数据库", importDescription: "导入你的加密私钥以启用你的头像", @@ -391,38 +500,33 @@ module.exports = { fileInfo: "你的加密私钥将下载到你的设备(文件名:oasis.enc)", developer: "开发", devTitle: "开发", - welcomeBannerText: "欢迎来到 OASIS。按这几个步骤设置你的化身。", + welcomeBannerText: "你的头像还没有设置。", aiSuggestionBanner: "建议:", aiSuggestionsEnable: "AI 建议", - welcomeBannerAction: "开始设置!", + welcomeBannerAction: "开始", welcomeTitle: "欢迎", + welcomeStepIdentity: "身份", + welcomeStepType: "类型", + welcomeStepUse: "用途", + welcomeStepLook: "外观", + welcomeStepCommunity: "社区", + welcomeStepIntro: "欢迎", + welcomeHintIdentity: "你的名字和头像。", + welcomeHintType: "Oasis 为你打开的方式。", + welcomeHintUse: "保留哪些模块。", + welcomeHintLook: "Oasis 使用的主题。", + welcomeHintCommunity: "加入一个 PUB 认识其他居民。", + welcomeHintIntro: "向社区发送第一条问候。", + welcomeStepThemeAction: "使用此主题", + welcomeJoinPubs: "加入 PUB", + welcomeNoFederation: "不加入联邦", welcomeStepGreetingAction: "发送", - welcomeJoinPubButton: "加入 pub", - welcomeStepLarpAction: "加入「学院」", - welcomeStepLarpText: "加入我们的实境角色扮演游戏。", - welcomeStepLarpTitle: "加入 L.A.R.P.", - welcomeStepGreetingTitle: "发送一句「Hello world!」", - welcomeStepGreetingText: "发一条消息,让其他居民能发现你。", - welcomeJoinPub: "加入", welcomeDescription: "开始之前值得做的几件事。", - welcomeProgress: "已完成", - welcomeStepDone: "完成", - welcomeStepPending: "待办", welcomeSkip: "暂不设置", - welcomeStepLanguageTitle: "选择你的语言", - welcomeStepLanguageText: "Oasis 支持多种语言,随时都可以更改。", - welcomeStepProfileTitle: "编辑你的资料", - welcomeStepProfileText: "取个名字、写段介绍并设置头像,让其他居民认得你。", welcomeStepProfileAction: "保存资料", - welcomeStepFederationTitle: "加入主网络", - welcomeStepFederationText: "连接到我们的 pub,认识其他居民并开始同步。", - welcomeStepFederationAction: "前往邀请", - welcomeStepBackupTitle: "备份你的 ID", - welcomeStepBackupText: "你的身份就是本机上的一个密钥文件。", - welcomeStepBackupWarning: "一旦丢失,没有任何人能帮你找回。", - welcomeStepBackupAction: "立即备份!", + welcomeBootstrapRunning: "正在加载 pub 的快照。完成后会自动加入该 pub;在此期间你可以继续使用 Oasis。", welcomeCompleteTitle: "全部就绪。", - welcomeCompleteText: "你的节点已经就绪。从这里开始,网络随着你关注的人一起成长。", + welcomeCompleteText: "你的节点已经就绪。从这里开始,网络随着你支持的居民一起成长。", welcomeFindPeople: "寻找居民", devFilterFiles: "文件", devFilterSearch: "搜索", @@ -432,7 +536,7 @@ module.exports = { devFilterTests: "测试", devVersion: "版本", devNodeVersion: "Node", - devStatsTests: "测试套件", + devStatsTests: "测试", devParentFolder: "上级文件夹", devOpenFolder: "打开", devDescription: "浏览本节点上运行的源代码。", @@ -476,6 +580,27 @@ module.exports = { "如果你想使用其他语言,请在此选择。", setLanguage: "设置语言", peerConnectionsIntro: "管理与其他节点的所有连接。", + peersConnectFailed: "无法连接:{reason}。", + peerStateLabel: "状态", + peerAddressLabel: "地址", + peerLastChangeLabel: "最后变更", + peerStateConnected: "已连接", + peerStateConnecting: "正在连接", + peerStateStaged: "可用", + peerErrSilent: "无活动", + peerErrTimeout: "无响应", + peerErrRefused: "拒绝连接", + peerErrNotFound: "未知地址", + peerErrUnreachable: "网络不可达", + peerErrKeys: "使用不同的密钥", + peerErrIdentity: "不接受你的连接", + peerErrTor: "仅限 TOR", + peerErrDropped: "连接中断", + peerErrPaused: "网络已暂停", + peerErrOther: "连接错误", + peerOutdated: "已过时", + peerVersionUnannounced: "未公布其版本:旧版 Oasis", + peersTorMissing: "此 PUB 仅允许通过 TOR 连接。", peerConnectionsTitle: "连接", online: "在线", offline: "离线", @@ -494,22 +619,25 @@ module.exports = { noConnections: "没有已连接的节点。", noDiscovered: "没有已发现的节点。", noUnknownPeers: "好友图中没有未知节点。", - peersTechnicalEmpty: "No peers registered yet.", - peerConnect: "Connect", - peerDisconnect: "Disconnect", + peersTechnicalEmpty: "尚未登记任何节点。", + peerConnect: "连接", + peerDisconnect: "断开", peerRefresh: "刷新", peerPruneIdle: "移除空闲", + peersPause: "暂停", + peersResume: "继续", + peersPausedNote: "已暂停:Oasis 不会连接任何人,也没有人能连接到它。", + peersPauseHint: "停止所有连接:在你恢复之前,Oasis 不会连接其他节点,也不接受它们的连接。", peerExport: "导出", peerImport: "导入", peerImportTitle: "导入对等节点列表", peerImportPlaceholder: "每行粘贴一个 multiserver 地址,或上传 .txt 文件……", connectionActionIntro: "", - startNetworking: "启动网络", - stopNetworking: "停止网络", - restartNetworking: "重启网络", - sync: "同步网络", indexes: "索引", + indexesRebuilt: "索引已重建", + indexesRebuildFailed: "无法重建索引", + indexesFiles: "索引文件", indexesDescription: "重建索引是安全的,可能修复某些类型的错误。", homePageTitle: "主页", @@ -519,44 +647,33 @@ module.exports = { invitesDescription: "管理和应用你网络中的邀请码。", invitesPubsHint: "粘贴 pub 邀请码,与它建立联邦并开始复制其居民。", invitesFederationsHint: "已联邦的 pub:刷新、移除无法连接的,或导出列表。", - invitesHousesHint: "输入房屋代码以加入 LARP 房屋并参与其经济。", - invitesTribesHint: "输入部落代码以成为成员并访问其私有内容。", - invitesChatsHint: "输入聊天代码以加入对话及其话题。", - invitesPadsHint: "输入协作页代码,与他人共同编辑同一份文档。", - invitesCalendarsHint: "输入日历代码以共享日期与提醒。", - invitesEventsHint: "输入活动代码以加入私密活动。", - invitesForumsHint: "输入论坛代码以在私密论坛中阅读与回复。", - invitesMapsHint: "输入地图代码,在共享地图上查看和添加地点。", - invitesShopsHint: "输入店铺代码以加入店铺及其目录。", - invitesInhabitantsHint: "输入居民代码以互相关注并交换内容。", invitesTribesTitle: "部落", - invitesTribeInviteCodePlaceholder: "输入部落邀请码", - invitesTribeJoinButton: "加入部落", invitesChatsTitle: "聊天", - invitesChatInviteCodePlaceholder: "输入聊天邀请码", - invitesChatJoinButton: "加入聊天", invitesPadsTitle: "协作板", - invitesPadInviteCodePlaceholder: "输入协作板邀请码", - invitesPadJoinButton: "加入协作板", invitesCalendarsTitle: "日历", - invitesCalendarInviteCodePlaceholder: "输入日历邀请码", - invitesCalendarJoinButton: "加入日历", invitesEventsTitle: "活动", - invitesEventInviteCodePlaceholder: "输入活动邀请码", - invitesEventJoinButton: "加入活动", invitesForumsTitle: "论坛", - invitesForumInviteCodePlaceholder: "输入论坛邀请码", - invitesForumJoinButton: "加入论坛", invitesMapsTitle: "地图", - invitesMapInviteCodePlaceholder: "输入地图邀请码", - invitesMapJoinButton: "加入地图", invitesShopsTitle: "商店", - invitesShopInviteCodePlaceholder: "输入商店邀请码", - invitesShopJoinButton: "加入商店", invitesPubsTitle: "PUBs", + invitesSuggestedPubs: "推荐的 PUB", + invitesFilterOthers: "其他", + invitesJoinTitle: "邀请", + invitesJoinHint: "粘贴任何邀请码(无论用于什么),或某位居民的 Oasis ID。", + invitesJoinPlaceholder: "邀请码或 Oasis ID", + invitesJoinButton: "加入", + invitesUsedTitle: "最近的邀请", + invitesUsedEmpty: "你还没有使用过任何邀请。", + invitesUsedType: "类型", + invitesUsedName: "名称", + invitesUsedDate: "日期", invitesPubInviteCodePlaceholder: "输入 PUB 邀请码", invitesAcceptInvite: "加入 PUB", + invitesPubUses: "已用邀请", + invitesPubExhausted: "此 PUB 已没有可用的邀请。", invitesAlreadyFederated: "您已与该 pub 建立联邦。", + invitesJoinPubMode: "此 Oasis 以只读模式作为 PUB 运行。要将其加入另一个 PUB,请在终端中使用:./oasis.sh join <邀请码>", + invitesJoinLocalOnly: "只能在运行 Oasis 的设备上接受邀请,或使用其启动时显示的管理链接。", invitesFederationsTitle: "联邦", invitesAcceptedInvites: "已联邦", invitesUnfollow: "取消关注", @@ -564,16 +681,12 @@ module.exports = { invitesUnfollowedInvites: "未联邦", invitesNoFederatedPubs: "没有已联邦的 pub。", invitesNoUnfollowed: "没有未联邦的 pub。", - invitesUnreachablePubs: "不可达", - invitesNoUnreachablePubs: "没有不可达的 pub。", invitesPubsRefresh: "刷新", - invitesPubsClearUnreachable: "移除不可达", + invitesPubsClearUnreachable: "清理", invitesPubsExport: "导出", invitesPubsImport: "导入", invitesPubsImportTitle: "导入 pubs", invitesPubsImportPlaceholder: "每行粘贴一个 multiserver 地址或邀请码,或上传 .txt 文件……", - currentlyUnreachable: "错误!", - errorDetails: "错误详情", genericError: "发生了一个错误。", panicMode: "紧急模式!", encryptData: "设置密码(至少32个字符)以加密你的区块链", @@ -600,7 +713,6 @@ module.exports = { votesOptionNoLabel:"反对", votesOptionAbstentionLabel:"弃权", votesQuestionLabel:"问题", - votesCreatedByLabel:"创建者", voteStatusOpen:"进行中", voteStatusClosed:"已关闭", commentDescription: ({ parentUrl }) => [ @@ -618,22 +730,22 @@ module.exports = { searchSubmit: "搜索...", votesLabel: "投票", pdfFallbackLabel: "PDF 文档", - shopLabel: "SHOPS", - voteLabel: "VOTES", + shopLabel: "商店", + voteLabel: "投票", submit: "提交", editProfile: "编辑头像", oasisIdLabel: "OasisID", spreadContent: "复制", welcomePmSubject: "Hello World!", - welcomePmBody: "欢迎来到 Oasis — 一个自由、点对点、联邦化且加密的社交网络,采用仅限邀请的分布式架构。\n\n一些值得开始探索的地方:\n\n- /profile —— 编辑你的头像、名字、简介以及在公开侧显示哪些模块。\n- /invites —— 添加一个 pub,与更广泛的网络联邦。\n- /peers —— 查看谁已连接,重新扫描局域网,导出或导入对等节点列表。\n- /inhabitants —— 发现并访问其他人的头像。\n- /tribes —— 创建或加入端到端加密的私人小组。\n- /inbox —— 阅读和发送私信。\n- /activity —— 查看最近的活动,你的和你网络中的。\n- /publish —— 写一篇帖子,或分享图片、音频、视频、文档。\n- /search — 按类型搜索网络内容。\n\n一些值得连接的地方:\n\n- /multiverse — 管理你的其他多元宇宙账户,包括发送和接收内容。\n\n这条消息是私下从你发给你自己的,因此无需任何连接即可接收。", + welcomePmBody: "欢迎来到 Oasis — 一个自由、点对点、联邦化且加密的社交网络,采用仅限邀请的分布式架构。\n\n一些值得开始探索的地方:\n\n- [/avatar](/profile) —— 编辑你的头像、名字、简介以及在公开侧显示哪些模块。\n- /settings — 调整你的本地实例:语言、主题、模块、隐私和同步。\n- /invites —— 添加一个 pub,与更广泛的网络联邦。\n- /peers —— 查看谁已连接,重新扫描局域网,导出或导入对等节点列表。\n- /inhabitants —— 发现并访问其他居民的头像。\n- /tribes —— 创建或加入端到端加密的私人小组。\n- /search — 按类型搜索网络内容。\n\n一些值得连接的地方:\n\n- /multiverse — 管理你的其他多元宇宙账户,包括发送和接收内容。\n\n这条消息是私下从你发给你自己的,因此无需任何连接即可接收。", profileSensorsSectionTitle: "传感器", profileVisibilityActivity: "活跃度", profileDeviceLockedHint: "此传感器始终开启:它让其他人看到你从哪个设备连接,并且无法关闭。", profileVisibilityKarma: "KARMA 评分", profileVisibilityUbi: "UBI", - profileVisibilityWallet: "ECOIN 钱包", - profileVisibilityEcoTax: "ECO Tax", - profileVisibilityLarpSign: "L.A.R.P. Sign", + profileVisibilityWallet: "ECOin 钱包", + profileVisibilityEcoTax: "ECO 税", + profileVisibilityLarpSign: "L.A.R.P. 徽记", profileVisibilityGpg: "GPG 密钥", clearnetSectionTitle: "Clearnet", profileClearnetShopsLabel: "店铺", @@ -646,6 +758,10 @@ module.exports = { profileClearnetImagesLabel: "图片", profileClearnetDocumentsLabel: "文档", profileClearnetTorrentsLabel: "种子", + profileClearnetPodcastsLabel: "播客", + profileClearnetMarketLabel: "市场", + profileClearnetFeedLabel: "动态", + profileClearnetWikiLabel: "维基", editProfileDescription: "", profileName: "名称", @@ -671,7 +787,7 @@ module.exports = { walletBalanceLine: ({ balance }) => `${balance} ECO`, walletCnfrs: "确认数", walletConfirm: "确认", - walletDescription: "管理你的数字资产,包括发送和接收 ECOin、查看余额和访问交易历史。", + walletDescription: "管理您的 ECOin 钱包。", walletDate: "日期", walletFee: "手续费(手续费越高,交易处理越快)", walletFeeLine: ({ fee }) => `手续费:ECO ${fee}`, @@ -679,6 +795,86 @@ module.exports = { walletReceive: "接收", walletReset: "重置", walletSend: "发送", + walletPayingTransfer: "正在支付转账", + walletCreateTransferLabel: "生成智能合约(转账)", + walletTransferNotifiedNote: "已通知对方本次付款", + walletTransferCreatedNote: "已为本次付款创建转账(收据)", + walletTransferNoPayeeNote: "没有居民发布过此地址,因此未创建转账", + walletTransferReceiptConcept: "钱包付款", + walletPaymentPmText: "向你发送了一笔付款", + walletPayeeLabel: "收款人", + walletConceptLabel: "事由", + pmBotBanking: "BankingBot", + bankingBotUbiPaidTitle: "您已收到 UBI。", + bankingBotPaymentTitle: "您已收到一笔付款。", + bankingBotUbiPaidText: "您的全民基本收入已发放", + bankUbiPubThisNode: "本节点负责发放 UBI", + bankUbiPubConnected: "UBI PUB", + bankUbiPubNone: "您的网络中尚无 PUB 宣告 UBI", + bankUbiAddressPublished: "ECOin 地址已发布", + bankUbiAddressMissing: "发布您的 ECOin 地址以领取 UBI", + bankUbiClaimedThisMonth: "本月已领取", + bankUbiPubBalance: "可用于 UBI 的 PUB 钱包", + bankNoPubAddress: "UBI PUB 尚未发布 ECOin 地址。", + bankUbiTab: "UBI", + bankDonateEco: "捐赠!", + donateTooltip: "捐赠 ECOin", + bankNoUserAddress: "该居民未公开 ECOin 地址。", + bankUbiPubDefault: "默认", + bankUbiPool: "资金池", + bankUbiLastSeen: "最后在线", + bankUbiLastPayout: "最近操作", + bankUbiPaidOut: "已发放", + bankRangeToday: "今天", + bankRangeWeek: "本周", + bankRangeMonth: "本月", + bankRangeYear: "本年", + bankRangeFiveYears: "5 年", + bankRangeAll: "全部", + bankChartValueTitle: "ECOin 价值演变", + bankChartValueHours: "时间等值(小时)", + bankChartWealthTitle: "ECOin 财富再分配", + bankChartDistributed: "已发放 UBI(ECO)", + bankChartRebalanceTitle: "PUB 之间的财富再分配", + bankChartRebalanced: "在 PUB 之间转移的 ECO", + bankChartTaxes: "已扣税款(ECO)", + bankChartNoDataRange: "此范围内没有数据。", + bankChartNoDataYet: "暂无数据", + bankChartPerHour: "每小时", + bankChartPerDay: "每天", + bankChartPerMonth: "每月", + bankTotalUbiDistributed: "已重新分配的 UBI 总额(全网)", + bankTotalTaxesWithheld: "已扣税款总额(全网)", + bankInflationIssuance: "发行率(年化,按区块奖励)", + bankRulesEligibilityTitle: "谁可以领取 UBI", + bankRulesEligibility1: "在个人资料中发布的 ECOin 地址(资料 → 传感器 → ECOin 钱包)。", + bankRulesEligibility2: "超过 30 天且在网络中有活动的 feed:业力大于零。", + bankRulesEligibility3: "每月(每个周期)一次领取,在 Banking 中点击领取 UBI。可用拒绝 UBI 放弃当月。", + bankRulesEligibility4: "1 ECO 的底线始终发放;税款只减少超出部分,从不另行收取。", + bankRulesEligibility5: "PUB 在下一周期支付并发布一笔可供确认的 UBI 转账。", + bankRulesEligibility6: "若缺少地址、feed 太新、已拒绝当月或 PUB 无资金,领取将被跳过并记录在 PUB 上。", + copyLinkTitle: "复制链接", + linkBoxOasis: "Oasis", + linkBoxClearnet: "Clearnet", + bankRefuseUBI: "拒绝 UBI", + bankUbiRefusedThisMonth: "您已拒绝本月的 UBI。", + bankingBotUbiAvailableTitle: "您可以领取 UBI 了。", + bankingBotUbiAvailableText: "您本月的 UBI 已可领取", + bankingBotConfirmTitle: "您有一份等待确认的智能合约", + bankingBotConfirmText: "等待您确认的智能合约", + bankChartZoomHint: "点击放大", + bankChartPoolTitle: "UBI 资金池(变化)", + bankChartNetworkUbiTitle: "已重新分配的 UBI 总额(全网)", + bankChartSupplyTitle: "ECOin 流通供应量(变化)", + bankChartInflationTitle: "ECOin 通胀(变化)", + bankChartInflationSeries: "年化率(%)", + bankChartPool: "资金池总额(ECO)", + bankChartKarmaTitle: "KARMA Scoring(变化)", + bankChartFundsTitle: "你的 ECOin 资金(变化)", + bankChartKarma: "业力", + bankWalletReady: "ECOin 已连接", + bankWalletNotConfigured: "ECOin 未连接", + transfersPayWithWallet: "支付!", walletStatus: "状态", walletDisconnected: [ "ECOin ", @@ -688,8 +884,11 @@ module.exports = { " 或连接状态。", ], walletSentToLine: ({ destination, amount }) => `已发送 ECO ${amount} 至 ${destination}`, + walletNotConfigured: "你的 ECOin 钱包尚未配置。", walletSettingsDescription: "将 Oasis 与你的 ECOin 钱包集成。", - walletSettingsDocLink: "ECOin 安装指南", + walletSettingsMobileDescription: "将 Oasis 与此设备上的 ECOin Wallet 应用关联。", + walletDownloadEcoin: "下载 ECOin!", + walletRpcUrl: "RPC 地址", walletStatusMessages: { invalid_amount: "无效金额", invalid_dest: "无效目标地址", @@ -705,6 +904,7 @@ module.exports = { walletUser: "用户名", walletPass: "密码", walletConfiguration: "设置钱包", + walletDisconnectButton: "断开钱包", cipher: "加密器", cipherTitle: "加密器", cipherDescription: "使用共享密码对文本进行对称加密和解密。", @@ -738,17 +938,16 @@ module.exports = { bookmarkFilterRecent: "最近", bookmarkCreateButton: "创建书签", bookmarkUpdateButton: "更新", - bookmarkDeleteButton: "删除", bookmarkAddFavoriteButton: "添加收藏", bookmarkRemoveFavoriteButton: "取消收藏", bookmarkUrlLabel: "链接", - bookmarkUrlPlaceholder: "https://example.com", + bookmarkUrlPlaceholder: "https://solarnethub.com", bookmarkDescriptionLabel: "描述", bookmarkDescriptionPlaceholder: "可选", bookmarkTagsLabel: "标签", bookmarkTagsPlaceholder: "输入标签,用逗号分隔", bookmarkLastVisitLabel: "上次访问", - bookmarkSearchPlaceholder: "搜索 URL、标签、类别、作者...", + bookmarkSearchPlaceholder: "在书签中搜索...", bookmarkSortRecent: "最近", bookmarkSortOldest: "最早", bookmarkSortTop: "最多投票", @@ -787,7 +986,7 @@ module.exports = { videoDescriptionPlaceholder: "可选", videoNoFile: "未提供视频文件", noVideos: "没有可用的视频。", - videoSearchPlaceholder: "搜索标题、标签、作者...", + videoSearchPlaceholder: "在视频中搜索...", videoSortRecent: "最近", videoSortOldest: "最早", videoSortTop: "最多投票", @@ -810,7 +1009,6 @@ module.exports = { documentFilterFavorites: "收藏", documentCreateButton: "上传文档", documentUpdateButton: "更新", - documentDeleteButton: "删除", documentAddFavoriteButton: "添加收藏", documentRemoveFavoriteButton: "取消收藏", documentMessageAuthorButton: "私信", @@ -823,7 +1021,7 @@ module.exports = { documentDescriptionPlaceholder: "可选", documentNoFile: "无文件。", noDocuments: "没有可用的文档。", - documentSearchPlaceholder: "搜索标题、标签、描述、作者...", + documentSearchPlaceholder: "在文档中搜索...", documentSortRecent: "最近", documentSortOldest: "最早", documentSortTop: "最多投票", @@ -844,19 +1042,19 @@ module.exports = { audioFilterTop: "热门", audioCreateButton: "上传音频", audioUpdateButton: "更新", - audioDeleteButton: "删除", audioAddFavoriteButton: "添加收藏", audioRemoveFavoriteButton: "取消收藏", audioFileLabel: "选择音频文件 (.mp3, .wav, .ogg)", audioTagsLabel: "标签", audioTagsPlaceholder: "输入标签,用逗号分隔", + tagsPlaceholder: "输入标签,用逗号分隔", audioTitleLabel: "标题", audioTitlePlaceholder: "可选", audioDescriptionLabel: "描述", audioDescriptionPlaceholder: "可选", audioNoFile: "未提供音频文件", noAudios: "没有可用的音频。", - audioSearchPlaceholder: "搜索标题、标签、作者...", + audioSearchPlaceholder: "在音频中搜索...", audioSortRecent: "最近", audioSortOldest: "最早", audioSortTop: "最多投票", @@ -868,7 +1066,7 @@ module.exports = { audioFilterFavorites: "收藏", favoritesTitle: "收藏", favoritesDescription: "所有你收藏的内容集中在一处。", - favoritesSearchPlaceholder: "搜索收藏...", + favoritesSearchPlaceholder: "在收藏中搜索...", favoritesFilterAll: "全部", favoritesFilterRecent: "最近", favoritesFilterAudios: "音频", @@ -880,13 +1078,12 @@ module.exports = { favoritesFilterChats: "聊天", favoritesFilterCalendars: "日历", favoritesFilterVideos: "视频", - favoritesRemoveButton: "取消收藏", favoritesNoItems: "还没有收藏。", yourContacts: "你的联系人", allInhabitants: "居民", allCVs: "简历", discoverPeople: "发现你网络中的居民。", - searchInhabitantsPlaceholder: "按名称筛选居民...", + searchInhabitantsPlaceholder: "在居民中搜索...", filterLocation: "按位置筛选居民...", filterLanguage: "按语言筛选居民...", filterSkills: "按技能筛选居民...", @@ -910,7 +1107,6 @@ module.exports = { topSectionTitle: "排行", blockedLabel: "已屏蔽用户", inhabitantviewDetails: "查看详情", - cvVisitButton: "查看简历", keepReading: "继续阅读...", oasisId: "ID", noInhabitantsFound: "尚未找到居民。", @@ -928,6 +1124,18 @@ module.exports = { parliamentFilterLeaders: "领导", parliamentFilterRules: "规则", parliamentGovernmentCard: "现任政府", + parliamentSeatsGovernment: "政府", + parliamentSeatsOpposition: "反对派", + parliamentSeatsAbstention: "弃权", + parliamentSeatsQuorum: "选举法定人数", + parliamentSeatsPass: "通过门槛", + parliamentSeatsByElection: "按选举", + parliamentSeatsByHouses: "按 LARP 家族", + parliamentSeatsSeats: "席", + parliamentSeatsSeat: "席", + parliamentSeatsLeader: "领袖", + parliamentSeatsVotesFor: "投给", + parliamentSeatsNoHouse: "无家族", parliamentActorInPowerInhabitant: '执政居民', parliamentActorInPowerTribe: '执政部落', parliamentHistoricalGovernmentsTitle: '政府', @@ -979,7 +1187,7 @@ module.exports = { parliamentThId: "ID", parliamentThProposalDate: "提议日期", parliamentThMethod: "方式", - parliamentThKarma: "Karma", + parliamentThKarma: "业力", parliamentThSupports: "支持", parliamentThVote: "投票", parliamentCurrentProposalsTitle: "当前提案", @@ -995,6 +1203,7 @@ module.exports = { parliamentTimeRemaining: "剩余时间", parliamentElectionsStatusTitle: "下届政府", parliamentProposalDeadlineLabel: "截止日期", + parliamentViewReport: "查看报告", parliamentProposalTimeLeft: "剩余时间", parliamentRulesTitle: "议会运作方式", parliamentRulesIntro: "选举每2个月进行一次;候选是持续的,在政府选定后重置。", @@ -1009,6 +1218,7 @@ module.exports = { parliamentRulesLaws: "当提案达到其门槛时,它成为法律并出现在法律标签中,附有生效日期。", parliamentRulesHistorical: "在历史标签中,你可以看到已发生的每个政府周期及其管理数据。", parliamentRulesLeaders: "在领袖标签中,你可以看到曾经执政(或作为候选人)的居民/部落排名,按效率排序。", + parliamentRulesHemicycle: "半圆议席图为每位居民分配一席:政府席(获胜候选的投票者与领袖)、每个其他候选各一个反对席,以及弃权席。虚线标记显示选举法定人数(人口的 25%)和当前方法的通过门槛。“按 LARP 家族”以家族为同一批席位着色。", parliamentProposalVoteStatusLabel: "投票状态", parliamentProposalOnTrackYes: "已达门槛", parliamentProposalOnTrackNo: "低于门槛", @@ -1022,6 +1232,7 @@ module.exports = { courtsCaseFormTitle: "开立案件", courtsCaseRespondent: "被告/答辩人", courtsCaseRespondentPh: "Oasis ID (@...) 或部落名称", + courtsCaseSubjectPlaceholder: "主题或简短描述", courtsCaseMethod: "解决方式", courtsCaseSubmit: "提交案件", courtsNominateJudge: "提名法官", @@ -1055,15 +1266,30 @@ module.exports = { courtsMethodDICTATOR: "独裁者", courtsMethodPOPULAR: "民意", courtsMethodKARMATOCRACY: "Karma 制", + courtsCaseType: "案件类型", + courtsPresetMinorConflict: "轻微冲突", + courtsPresetModerateConflict: "中度冲突", + courtsPresetSevereConflict: "严重冲突", + courtsPresetHarassment: "骚扰或虐待", + courtsPresetContentModeration: "内容审核", + courtsPresetBanRestriction: "封禁或限制", + courtsPresetGovernanceDispute: "治理争议", + courtsStatusOPEN: "开放", + courtsStatusIN_PROGRESS: "进行中", + courtsStatusDECIDED: "已裁决", + courtsStatusCLOSED: "已关闭", + courtsStatusSOLVED: "已解决", + courtsStatusUNSOLVED: "未解决", + courtsStatusDISCARDED: "已驳回", courtsRulesTitle: "法庭运作方式", courtsRulesIntro: "法庭是社区驱动的冲突解决和恢复性司法流程。优先考虑对话、清晰证据和适当补救。", courtsRulesLifecycle: "流程:1) 开立案件 2) 选择方式 3) 提交证据 4) 听证和审议 5) 裁决和补救 6) 执行和结案 7) 上诉(如适用)。", - courtsRulesRoles: "原告:开立案件。辩护方:被告人或部落。方式:社区选择的用于促进、评估证据和发布裁决的机制。证人:提供证词或证据。调解员:由原告和/或辩护方邀请的中立人士,可访问所有细节,帮助缓和冲突并共同达成协议。", + courtsRulesRoles: "原告:开立案件。辩护方:被告居民或部落。方式:社区选择的用于促进、评估证据和发布裁决的机制。证人:提供证词或证据。调解员:由原告和/或辩护方邀请的中立居民,可访问所有细节,帮助缓和冲突并共同达成协议。", courtsRulesEvidence: "描述不超过1000字。附加相关合法的图片、音频、视频和 PDF 文档。未经同意不要分享敏感私人数据。", courtsRulesDeliberation: "听证可以是公开的或私密的。法官确保尊重、要求澄清,并可能排除不相关或不合法的材料。", courtsRulesVerdict: "优先考虑修复:道歉、调解协议、内容审核、临时限制或其他适当措施。必须记录推理过程。", courtsRulesAppeals: "上诉:在有新证据或明显程序错误时允许。除非另有规定,必须在7天内提出。", - courtsRulesPrivacy: "尊重隐私和安全。人肉搜索、仇恨或威胁将被删除。法官可以编辑或封存记录的部分内容以保护有风险的人。", + courtsRulesPrivacy: "尊重隐私和安全。人肉搜索、仇恨或威胁将被删除。法官可以编辑或封存记录的部分内容以保护有风险的居民。", courtsRulesMisconduct: "骚扰、操纵或伪造证据可能导致立即不利裁决。", courtsRulesGlossary: "案件:冲突记录。证据:支持诉求的材料。裁决:附有补救措施的决定。上诉:请求审查裁决。", courtsFilterActions: "操作", @@ -1079,12 +1305,16 @@ module.exports = { exploreTrending: "探索你网络中最受欢迎的内容。", votesButton: "投票", shopProductButton: "商店", + podcastButton: "播客", + podcastEpisodeButton: "单集", + campaignButton: "活动", + logisticsRouteButton: "路线", trendingTotalOpinions: "总观点数", trendingNoContentMessage: "暂时还没有热门内容。", trendingTotalCount: "总计", tasksTitle: "任务", tasksDescription: "发现和管理你网络中的任务。", - taskSearchPlaceholder: "搜索任务...", + taskSearchPlaceholder: "在任务中搜索...", taskTitleLabel: "标题", taskDescriptionLabel: "描述", taskStartTimeLabel: "开始时间", @@ -1103,7 +1333,7 @@ module.exports = { taskStatusOpen: "开放", taskStatusInProgress: "进行中", taskStatusClosed: "已关闭", - taskAssignedTo: "分配给", + taskAssignedTo: "分配", taskAssignedChip: "已认领", taskUnassignedChip: "未认领", taskAssignees: "被分配者", @@ -1113,6 +1343,7 @@ module.exports = { taskUpdateButton: "更新", taskDeleteButton: "删除", taskFilterAll: "全部", + taskFilterRecent: "最近", taskFilterMine: "我的", taskFilterOpen: "开放", taskFilterInProgress: "进行中", @@ -1140,14 +1371,13 @@ module.exports = { eventUpcomingDates: "接下来的日期", eventsTitle: "活动", eventsDescription: "发现和管理你网络中的活动。", - eventSearchPlaceholder: "搜索活动...", + eventSearchPlaceholder: "在活动中搜索...", eventPrice: "价格", eventStatus: "状态", eventAllSectionTitle: "活动", eventMineSectionTitle: "你的活动", eventCreateSectionTitle: "创建活动", eventUpdateSectionTitle: "更新活动", - eventDeleteButton: "删除", eventCreateButton: "创建活动", eventAddToCalendar: "添加到日历", eventVisitCalendar: "查看日历", @@ -1165,11 +1395,11 @@ module.exports = { viewPlace: "查看地点", generatePdf: "生成 PDF", sharePm: "通过私信分享", - galleryImages: "图片(每张最大 50MB)", + galleryImages: "图片(每张最大 75MB)", galleryPhotos: "图片", galleryAddPhoto: "添加图片", galleryRemovePhoto: "移除", - galleryVideo: "视频(最大 50MB)", + galleryVideo: "视频(最大 75MB)", galleryAddVideo: "添加视频", galleryRemoveVideo: "移除视频", viewVotation: "查看投票", @@ -1189,6 +1419,7 @@ module.exports = { eventDate: "日期", eventAttendButton: "参加活动", eventFilterAll: "全部", + eventFilterRecent: "最近", eventFilterMine: "我的", eventFilterToday: "今天", eventFilterWeek: "本周", @@ -1215,7 +1446,7 @@ module.exports = { tagsFilterTop: "热门", tagsFilterCloud: "标签云", tagsNoItems: "没有可用的标签。", - tagsSearchPlaceholder: "搜索标签...", + tagsSearchPlaceholder: "在标签中搜索...", tagsTableHeaderTag: "标签/链接:", tagsTableHeaderCount: "计数:", transfersTitle: "转账", @@ -1223,7 +1454,10 @@ module.exports = { transfersFrom: "发起人", transfersTo: "接收人", transfersFilterAll: "全部", + transfersFilterRecent: "最近", transfersFilterMine: "我的", + transfersFilterMinePending: "待确认", + transfersFilterMineConfirmed: "已签署", transfersFilterUBI: "UBI", transfersFilterMarket: "市场", transfersFilterTop: "热门", @@ -1238,7 +1472,6 @@ module.exports = { transfersCategoryEconomic: "经济", transfersCategoryTime: "时间", transfersCategoryTrust: "信任", - transfersCategoryApply: "应用", transfersUnitEco: "ECO", transfersUnitHours: "小时", transfersUnitTrust: "信任", @@ -1268,7 +1501,7 @@ module.exports = { transfersDiscardedSectionTitle: "已废弃转账", transfersCreateSectionTitle: "创建转账", transfersAllSectionTitle: "转账", - transfersSearchPlaceholder: "搜索概念、标签、用户...", + transfersSearchPlaceholder: "在转账中搜索...", transfersMinAmountLabel: "最低金额", transfersMaxAmountLabel: "最高金额", transfersSortRecent: "最近", @@ -1290,6 +1523,7 @@ module.exports = { voteErrorTitle: "请检查表单", voteErrorDeadlineMin: "截止时间至少要在 {days} 天之后。", voteErrorGeneric: "无法保存投票。", + voteLocked: "此投票已收到投票或意见,或属于议会或法院:无法再编辑或删除。", voteUpdateButton: "更新", voteDeleteButton: "删除", voteOptionYes: "赞成", @@ -1299,6 +1533,7 @@ module.exports = { voteFollowMajority: "跟随多数", voteNotInterested: "不感兴趣", voteFilterAll: "全部", + voteFilterRecent: "最近", voteFilterMine: "我的", voteFilterOpen: "进行中", voteFilterClosed: "已关闭", @@ -1314,7 +1549,6 @@ module.exports = { voteTags: "标签", voteOpinions: "观点", novotes: "没有可用的投票提案。", - voteBy: "作者", voteCreatedAt: "创建于", voteNoQuestion: "未提供问题", voteUnknownCreator: "未知创建者", @@ -1324,6 +1558,7 @@ module.exports = { voteCommentsForumButton: '公开讨论', voteCommentsSectionTitle: '公开讨论', voteNoCommentsYet: '还没有评论。成为第一个回复的人。', + commentsGoToLast: "跳到最后一条评论", voteNewCommentPlaceholder: '在此写下你的评论…', voteNewCommentButton: '发表评论', voteNewCommentLabel: '添加评论', @@ -1334,6 +1569,8 @@ module.exports = { cvNameLabel: "全名", cvDescriptionLabel: "摘要", cvPhotoLabel: "照片", + cvPdfLabel: "简历 (PDF)", + cvVisitButton: "查看简历", cvPersonalExperiencesLabel: "个人经历", cvPersonalSkillsLabel: "个人技能(逗号分隔)", cvOasisExperiencesLabel: "Oasis 贡献经历", @@ -1363,9 +1600,9 @@ module.exports = { cvNoCV: "未找到简历。", blogSubject: "主题", blogMessage: "消息", - blogImage: "上传媒体(最大:50MB)", + blogImage: "上传媒体(最大:75MB)", blogMedia: "附件(最多 8 张图片和 1 个视频)", - blogPublish: "预览", + blogPublish: "发布", pollsTitle: "投票", dataTitle: "匹配", dataDescription: "探索并可视化你的网络中的匹配。", @@ -1384,12 +1621,23 @@ module.exports = { dataKindTasks: "任务", dataKindReports: "报告", dataKindVotes: "投票", - dataSearchPlaceholder: "搜索交叉数据...", + dataSearchPlaceholder: "在匹配中搜索...", dataCohesionTitle: "凝聚系数 (CC)", dataCcShort: "CC", dataCohesionHint: "网络已发布内容之间的平均相近度。", dataAffinity: "匹配度", dataCommonTerms: "关键词", + dataReasonFollowing: "你正在支持这位居民", + dataReasonTribe: "与你同属一个部落", + dataReasonPinned: "与你置顶的内容相符", + dataReasonCv: "与你简历中的技能相符", + dataReasonContent: "与你发布的内容相关", + dataReasonRelated: "与你的兴趣相关", + dataReasonAlike: "由与你相似的居民发布", + dataReasonSupportsYou: "支持你", + dataReasonMutual: "相互支持", + dataReasonRated: "网络评价很高", + dataReasonNear: "在你附近", dataStatPeople: "简历", dataStatConnected: "已连接", dataStatSkills: "技能", @@ -1442,7 +1690,7 @@ module.exports = { pollStatusLabel: "状态", pollStatusOpen: "进行中", pollStatusClosed: "已结束", - pollSearchPlaceholder: "搜索投票...", + pollSearchPlaceholder: "在投票中搜索...", pollsNoItems: "未找到投票。", viewPoll: "查看投票", pollChatCreate: "创建投票", @@ -1455,7 +1703,7 @@ module.exports = { blogFilterFavorites: "收藏", blogFilterTop: "热门", blogCreateButton: "创建博客", - blogSearchPlaceholder: "搜索博客...", + blogSearchPlaceholder: "在博客中搜索...", blogComments: "评论", blogAllowComments: "允许评论", blogCommentsClosed: "作者已关闭此博客的评论。", @@ -1468,12 +1716,11 @@ module.exports = { forumCreateButton: "创建论坛", forumCreateSectionTitle: "创建论坛", forumDescription: "与你网络中的其他居民公开交流。", - forumSearchPlaceholder: "搜索论坛...", + forumSearchPlaceholder: "在论坛中搜索...", forumFilterAll: "全部", forumFilterMine: "我的", forumFilterRecent: "最近", forumFilterTop: "热门", - forumDeleteButton: "删除", forumParticipants: "参与者", forumMessages: "消息", forumMessageLabel: "消息", @@ -1500,7 +1747,6 @@ module.exports = { imageFilterGallery: "画廊", imageCreateButton: "上传图片", imageUpdateButton: "更新", - imageDeleteButton: "删除", imageAddFavoriteButton: "添加收藏", imageRemoveFavoriteButton: "取消收藏", imageFileLabel: "选择图片文件 (.jpeg, .jpg, .png, .gif)", @@ -1512,13 +1758,14 @@ module.exports = { imageDescriptionPlaceholder: "可选", imageNoFile: "未提供图片文件", noImages: "没有可用的图片。", - imageSearchPlaceholder: "搜索标题、标签、描述、作者...", + imageSearchPlaceholder: "在图片中搜索...", imageSortRecent: "最近", imageSortOldest: "最早", imageSortTop: "最多投票", imageSearchButton: "搜索", imageUpdatedAt: "已更新", imageNoMatch: "没有匹配的图片。", + feedSearchPlaceholder: "在动态中搜索...", feedTitle: "动态", createFeedTitle: "创建动态", createFeedButton: "发送动态!", @@ -1533,7 +1780,7 @@ module.exports = { alreadyRefeeded: "你已经转发过了。", activityTitle: "活动记录", yourActivity: "你的活动", - globalActivity: "全局活动", + globalActivity: "动态", activityList: "活动记录", activityDesc: "查看你网络中的最新活动。", allButton: "全部", @@ -1549,6 +1796,7 @@ module.exports = { typeRecent: "最近", typeTop: "排行", typePost: "博客", + activityUpdateLabel: "更新", typeTribe: "部落", typeLarp: "L.A.R.P.", typeInhabitants: "居民", @@ -1683,7 +1931,7 @@ module.exports = { courtsNoCases: '没有案件。', reportsTitle: "报告", reportsDescription: "管理并跟踪你的网络中的报告。", - reportsSearchPlaceholder: "搜索举报...", + reportsSearchPlaceholder: "在报告中搜索...", reportsFilterAll: "全部", reportsFilterRecent: "最新", reportsFilterTop: "热门", @@ -1731,7 +1979,6 @@ module.exports = { reportsUnderReviewSectionTitle: "审查中的报告", reportsResolvedSectionTitle: "已解决的报告", reportsInvalidSectionTitle: "无效报告", - reportsTemplateSectionTitle: '报告模板', reportsBugTemplateTitle: '复现详情(漏洞)', reportsFeatureTemplateTitle: '详情(功能)', reportsAbuseTemplateTitle: '详情(滥用)', @@ -1791,17 +2038,15 @@ module.exports = { tribeGallerySectionTitle: "部落画廊", tribeRecentSectionTitle: "最近的部落", tribeTopSectionTitle: "热门部落", - tribeviewTribeButton: "访问部落", - tribeviewSubTribeButton: "访问子部落", tribeRootLabel: "根", tribeDescription: "探索或创建你网络中的部落。", tribeMainTribeLabel: "主部落", tribeCreateButton: "创建部落", tribeUpdateButton: "更新", tribeDeleteButton: "删除", - tribeImageLabel: "上传媒体(最大:50MB)", + tribeImageLabel: "上传媒体(最大:75MB)", tribeTitleLabel: "标题", - searchTribesPlaceholder: "按名称筛选部落...", + searchTribesPlaceholder: "在部落中搜索...", tribeTitlePlaceholder: "部落名称", tribeDescriptionLabel: "描述", tribeDescriptionPlaceholder: "描述这个部落", @@ -1814,6 +2059,15 @@ module.exports = { tribeIsAnonymousLabel: "状态", tribeMembersCount: "成员", tribeEnterInvite: "输入邀请码", + subscriptionTitle: "订阅", + subscriptionOn: "已订阅", + subscriptionOff: "未订阅", + subscriptionSubscribe: "订阅", + subscriptionUnsubscribe: "取消订阅", + subscriptionSubscribers: "订阅者", + pmListLabel: "发送至", + pmListNone: "— 无列表 —", + pmListNoSubscribers: "没有订阅者", tribeLeaveButton: "离开", tribePublic: "公开", tribePrivate: "私密", @@ -1846,16 +2100,16 @@ module.exports = { tribeSectionVideos: "视频", tribeSectionDocuments: "文档", tribeSectionBookmarks: "书签", - tribeSectionMaps: "MAPS", - tribeSectionPads: "PADS", - tribeSectionChats: "CHATS", - tribeSectionCalendars: "CALENDARS", - tribePadCreate: "Create Pad", - tribeChatCreate: "Create Chat", - tribeCalendarCreate: "Create Calendar", - tribePadsEmpty: "No pads, yet.", - tribeChatsEmpty: "No chats, yet.", - tribeCalendarsEmpty: "No calendars, yet.", + tribeSectionMaps: "地图", + tribeSectionPads: "协作板", + tribeSectionChats: "聊天", + tribeSectionCalendars: "日历", + tribePadCreate: "创建协作板", + tribeChatCreate: "创建聊天", + tribeCalendarCreate: "创建日历", + tribePadsEmpty: "暂无协作板。", + tribeChatsEmpty: "暂无聊天。", + tribeCalendarsEmpty: "暂无日历。", tribeInhabitantsEmpty: "此部落中还没有居民。", tribeEventCreate: "创建活动", tribeEventsEmpty: "还没有活动。", @@ -1910,9 +2164,14 @@ module.exports = { tribeMediaTypeDocument: "文档", tribeMediaTypeBookmark: "书签", tribeContentDelete: "删除", + tribeReachTribe: "部落", + tribeReachNotice: "OASIS 会向所有居民显示,CLEARNET 还会在开放网络上显示。公开后会在部落之外留下可读副本:改回部落会将其隐藏,但已经分享出去的副本无法删除。", + tribeReachPrivate: "私密部落的内容只能留在部落内部。", + tribeReachOpenedBy: "公开者", + tribeReachChange: "发布位置", tribeInviteCodeText: "邀请码:", oasisVersionLabel: "Oasis 版本", - tribeInviteCodeHint: "将此邀请码分享给你想邀请的人,他们可在 /invites → Tribes 加入。", + tribeInviteCodeHint: "将此邀请码分享给你想邀请的人,他们可在 /invites 加入。", tribeStatusOpen: "开放", tribeStatusClosed: "已关闭", tribeStatusInProgress: "进行中", @@ -1952,7 +2211,7 @@ module.exports = { tribeSearchMinChars: "请输入至少2个字符进行搜索。", agendaTitle: "日程", agendaDescription: "在这里你可以找到所有分配给你的项目。", - agendaSearchPlaceholder: "搜索议程...", + agendaSearchPlaceholder: "在日程中搜索...", agendaFilterAll: "全部", agendaFilterToday: "今天", agendaFilterUpcoming: "即将到来", @@ -1973,6 +2232,7 @@ module.exports = { agendaNoItems: "未找到分配项目。", agendaDiscardButton: "丢弃", agendaRestoreButton: "恢复", + agendaRemoveButton: "移除", agendaMembersCount: "成员", agendaLocationLabel: "位置", agendaYes: "是", @@ -2039,8 +2299,8 @@ module.exports = { pmComposeTitle: "发送消息", pmRecipients: "收件人", pmRecipientsHint: "输入 Oasis ID,用逗号分隔", - pmLimitsHint: "每条消息最多 7 个收件人。", - pmTextPlaceholder: "正文上限 7000 个字符。", + pmLimitsHint: "每条消息最多 7 位居民。", + pmTextPlaceholder: "写下你的消息。", pmSubject: "主题", pmSubjectHint: "输入消息主题", pmText: "消息", @@ -2062,8 +2322,8 @@ module.exports = { pmCrypterCharsLabel: "字符", pmInvalidRecipients: "无效的 Oasis ID(应形如 @…=.ed25519)。", pmEncryptionLabel: "加密", - private: "私密", - privateDescription: "你的加密消息。", + private: "收件箱", + privateDescription: "管理你的私信。", privateInbox: "收件箱", privateSent: "已发送", privateDelete: "删除", @@ -2119,24 +2379,24 @@ module.exports = { visitContent: "查看内容", favoriteAdd: "固定到收藏", pmContentTooltip: "发送私信", + phoneCallTooltip: "拨打电话", favoriteRemove: "从收藏移除", reportContent: "举报内容", banking: '银行', bankingTitle: '银行', - bankingDescription: "ECOin 经济:余额、全民基本收入、税收与工业价值。", + bankingDescription: "ECOin 集体经济。", bankOverview: '概览', bankEpochs: '纪元', bankRules: '规则', pending: '待处理', closed: '已关闭', - bankViewTx: '查看交易', bankClaimNow: '立即领取', - bankClaimUBI: '领取 UBI!', - bankClaimAndPay: 'Claim & Pay', - bankClaimedPending: 'Claim pending...', - bankStatusClaimed: 'Claimed', - bankStatusExpired: 'Expired', - bankPubOnly: 'PUB-only operation', + bankClaimUBI: '领取 UBI', + bankClaimAndPay: "领取并支付", + bankClaimedPending: "领取处理中...", + bankStatusClaimed: "已领取", + bankStatusExpired: "已过期", + bankPubOnly: "仅限 PUB 的操作", bankEpoch: '纪元', bankPool: '资金池(本纪元)', bankWeightsSum: '权重总和', @@ -2155,8 +2415,8 @@ module.exports = { bankViewEpoch: '查看纪元', bankYourIndustryBalance: "你的工业份额", bankYourUbiMonth: "你本月的全民基本收入", - bankYourFundsMonth: "你本月的资金", - bankIndustryBalance: "工业产值(估算)", + bankYourFundsMonth: "资金合计", + bankIndustryBalance: "工业产值", bankUserBalance: '你的余额', bankAddresses: '地址', bankNoAddresses: '未找到地址。', @@ -2164,6 +2424,10 @@ module.exports = { bankAddress: '地址', bankAddAddressTitle: '添加 ECOIN 地址', bankAddAddressUser: 'Oasis ID', + bankAddAddressLabel: "标签(可选)", + bankAddAddressLabelPlaceholder: "Bob 的钱包、Alice 的商店…", + bankAddAddressUserOptional: "Oasis ID(可选)", + bankAddressUnnamed: "未命名地址", bankAddAddressAddress: 'ECOIN 地址', bankAddAddressSave: '保存', bankAddressAdded: '地址已添加', @@ -2181,11 +2445,11 @@ module.exports = { bankAddressDeleteConfirm: '删除此地址?', search: '搜索!', bankLocal: '本地', - bankFromOasis: 'Oasis', + bankFromOasis: "网络", pubIdLabel: "PUB ID", bankUbiAvailableNo: "资金不足!", bankUbiAvailableOk: "可用!", - bankUbiAvailability: "全民基本收入(网络)", + bankUbiAvailability: "UBI", bankAlreadyClaimedThisMonth: "本月已领取", bankUbiThisMonth: "UBI(本月)", bankUbiLastClaimed: "UBI(上次领取)", @@ -2215,6 +2479,7 @@ module.exports = { shopReachClearnet: "Clearnet", shopReachOasis: "Oasis", encryptedChipLabel: "E2E", + transportChipLabel: "传输加密", shopAddFavorite: "添加收藏", shopRemoveFavorite: "取消收藏", shopOpen: "开放", @@ -2222,6 +2487,13 @@ module.exports = { shopMakePrivate: "设为私密 (E2E)", shopMakePublic: "设为公开", shopProducts: "产品", + shopFilterAll: "全部", + shopFilterRecent: "最近", + shopFilterMine: "我的", + shopFilterTop: "热门", + shopFilterProducts: "产品", + shopFilterPrices: "价格", + shopFilterFavorites: "收藏", shopProductAdd: "添加产品", shopProductTitle: "产品", shopProductUpdate: "更新产品", @@ -2249,7 +2521,7 @@ module.exports = { marketVisitItem: "查看商品", shopStatus: "状态", shopCreatedAt: "创建时间", - shopSearchPlaceholder: "搜索商店...", + shopSearchPlaceholder: "在商店中搜索...", shopUrl: "网址", shopLocation: "位置", shopTags: "标签", @@ -2259,37 +2531,40 @@ module.exports = { shopTitlePlaceholder: "您的店铺名称", shopDescriptionPlaceholder: "您的店铺详细描述", shopLocationPlaceholder: "城市,国家", - shopTagsPlaceholder: "标签1, 标签2, 标签3", + shopTagsPlaceholder: "输入标签,用逗号分隔", mapTitlePlaceholder: "地图标题", shopProductFeatured: "推荐产品", - shopSendToMarket: "Send to Market", + shopSendToMarket: "发送到市场", typeShop: "商店", typeShopProduct: "店铺商品", bankExchange: '交易所', bankExchangeCurrentValue: 'ECOin 价值(1小时)', - bankTotalSupply: 'ECOin 总供应量', - bankEcoinHours: "ECOin 时间等价", + bankTotalSupply: "ECOin 总供应量", + bankPubsSupply: "PUB 资金池的 ECOin 供应量", + bankHoldingSupply: "UBI 之外的 ECOin 供应量", + bankEcoinHours: "ECOin 时间等价 (1 ECO)", bankHoursOfWork: '小时', bankUnitMs: "毫秒", bankUnitSeconds: "秒", bankUnitMinutes: "分钟", bankUnitDays: "天", bankExchangeNoData: '无可用数据', - bankInflation: 'ECOin 通胀率 (anual)', - bankInflationMonthly: 'ECOin 通胀率 (mensual)', + bankInflation: "ECOin 通胀率 (年)", + bankInflationMonthly: "ECOin 通胀率 (月)", bankExchangeChartTitle: 'ECOin 价值随时间的演进', bankExchangeChartValue: '价值 (ECO/小时)', bankExchangeChartSupply: '供应量', bankExchangeChartInflation: '通胀率 %', bankExchangeChartEmpty: '采样不足 — 稍后再来', - bankCurrentSupply: 'ECOin 当前供应量', + bankCurrentSupply: "ECOin 当前供应量", bankingSyncStatus: 'ECOin 状态', bankingSyncStatusSynced: '已同步', - bankingSyncStatusOutdated: '已过时', + bankingSyncStatusOutdated: "未同步", statsTitle: '统计', statistics: "统计", statsDescription: "发现你网络的统计数据。", ALLButton: "全部", + RECENTButton: "最近", MINEButton: "我的", TOMBSTONEButton: "墓碑", statsCreatedAt: "创建于", @@ -2297,7 +2572,8 @@ module.exports = { statsYourOpinions: "观点", statsNetworkContent: "内容", statsNetworkOpinions: "观点", - statsEcoWalletLabel: "ECOIN 钱包", + statsEcoWalletLabel: "ECOin 钱包", + walletAddressCopyHint: "点击选中后复制", statsEcoWalletNotConfigured: "未配置!", statsBookmark: "书签", statsEvent: "活动", @@ -2319,6 +2595,8 @@ module.exports = { statsTransfer: "转账", statsAiExchange: "AI", statsPUBs: 'PUB', + statsSyncedPeers: "节点", + statsTotalLabel: "总数", statsPost: "帖子", statsSize: "总计(大小)", statsBlockchainSize: "区块链(大小)", @@ -2370,8 +2648,14 @@ module.exports = { aiPrompt: "提供信息丰富且精确的回答。", aiConfiguration: "设置提示语", aiPromptUsed: "提示语", - aiClearHistory: "清除聊天记录", - aiServerError: "AI 无法回答。请重试。", + aiClearHistory: "清除历史", + aiServerError: "我无法正常回答,请再试一次。", + aiStatusLoading: "42 正在加载模型,请稍后再问。", + aiThinking: "思考中", + aiWaiting: "等待中", + aiStatusMissingModel: "AI 模型未安装:缺少 src/AI/oasis-42-1-chat.Q4_K_M.gguf。", + aiExportFineTuning: "导出微调数据", + aiFactsTitle: "来自你网络的数据", aiInputPlaceholder: "什么是 Oasis?", typeAiExchange: "AI", aiApproveTrain: "添加到集体训练", @@ -2400,7 +2684,7 @@ module.exports = { marketFilterDiscarded: "已废弃", marketFilterRecent: "最近", marketFilterMyBids: "出价", - marketCreateButton: "创建商品", + marketCreateButton: "出售物品", marketItemType: "类型", marketItemTitle: "标题", marketItemAvailable: "截止日期", @@ -2427,8 +2711,8 @@ module.exports = { marketItemSeller: "卖家", marketNoItems: "暂无可用商品。", marketYourBid: "你的出价", - marketCreateFormImageLabel: "上传媒体(最大:50MB)", - marketSearchPlaceholder: "搜索标题或标签", + marketCreateFormImageLabel: "上传媒体(最大:75MB)", + marketSearchPlaceholder: "在市场中搜索...", marketMinPriceLabel: "最低价格", marketMaxPriceLabel: "最高价格", marketSortRecent: "最近", @@ -2494,8 +2778,7 @@ module.exports = { housingSetClosed: "设为不可入住", housingCreateButton: "创建住所", housingUpdateButton: "更新", - housingDeleteButton: "删除", - housingSearchPlaceholder: "搜索住所...", + housingSearchPlaceholder: "在住所中搜索...", housingSearchButton: "搜索", housingMinPrice: "最低价格", housingMaxPrice: "最高价格", @@ -2551,7 +2834,7 @@ module.exports = { jobLocationPresencial: "现场", jobLocationRemote: "远程", jobVacantsPlaceholder: "职位数量", - jobImage: "上传媒体(最大:50MB)", + jobImage: "上传媒体(最大:75MB)", jobTasks: "任务", jobType: "工作类型", jobTime: "工作时间", @@ -2573,12 +2856,11 @@ module.exports = { jobsExchangeSkill: "交换需要的技能", jobTimePartial: "兼职", jobTimeComplete: "全职", - jobsDeleteButton: "删除", jobsUpdateButton: "更新", jobsFilterApplied: "已申请", jobsAppliedTitle: "我的申请", jobsAppliedBadge: "已申请", - jobsSearchPlaceholder: "搜索标题、标签、描述...", + jobsSearchPlaceholder: "在工作中搜索...", jobsMinSalaryLabel: "最低薪资", jobsMaxSalaryLabel: "最高薪资", jobsSortRecent: "最近", @@ -2590,7 +2872,7 @@ module.exports = { jobSetOpen: "设为开放", jobsTagsLabel: "标签", noJobsMatch: "没有匹配的工作。", - industrySearchPlaceholder: "搜索工厂…", + industrySearchPlaceholder: "在工业中搜索...", industryAllSectors: "所有部门", industryVisitButton: "访问工业", industryAdvanceTo: "设为", @@ -2664,7 +2946,6 @@ module.exports = { industryBuildStatus_FAILED: "已失败", pmBotIndustry: "IndustryBot", pmBotHousing: "HousingBot", - pmBotJobs: "JobsBot", jobsBotMatchTitle: "有一份工作与你的简历匹配", jobsBotMatchIntro: "有一份工作与你的简历匹配。", jobsBotMatchJob: "工作", @@ -2691,7 +2972,7 @@ module.exports = { industryRulesIntro: "Industry 让居民集体拥有生产资料:一家工厂是网络所有的作坊,无人私有。", industryRulesSteward: "创始人是管理人——托管者,而非所有者:不能独自出售、私有化或解散工厂。", industryRulesMembership: "成员政策决定如何加入:开放(立即加入)、投票(由成员投票接纳)或仅邀请(需成员先邀请)。", - industryRulesQuorum: "法定人数是一项决定生效所需投票的最少成员数,以免小工厂由一人决定。", + industryRulesQuorum: "法定人数是一项决定生效所需投票的最少成员数,以免小工厂由一位居民决定。", industryRulesMajority: "多数是通过所需的投票比例(0.5=一半,1=全体一致);当赞成票达到至少 max(法定人数, 成员×多数) 时决定通过。", industryRulesDecisions: "成员投票接纳新人、批准构建和解散工厂;管理人不能凌驾于这些集体决定之上。", industryRulesBlueprints: "蓝图是自由(COPYLEFT)配方——投入(材料、工时、技能)与产出(实体或数字商品)——任何人都可以复刻。", @@ -2717,6 +2998,7 @@ module.exports = { modulesIndustryLabel: "工业", modulesIndustryDescription: "用于集体管理生产资料的模块。", industryFilterAll: "全部", + industryFilterRecent: "最近", industryFilterMember: "成员", industryFilterBlueprints: "蓝图", industryFilterBuilds: "生产", @@ -2725,6 +3007,7 @@ module.exports = { industryFilterPaused: "已暂停", industryFilterDissolved: "已解散", industryAllTitle: "工业", + industryRecentTitle: "最近的工厂", industryMemberTitle: "我参与的工厂", industryMineTitle: "我的工厂", industryActiveTitle: "运行中", @@ -2772,7 +3055,7 @@ module.exports = { industryPolicy_invite: "仅邀请", projectsTitle: "项目", projectsDescription: "创建、资助和关注你网络中的社区驱动项目。", - projectSearchPlaceholder: "搜索项目...", + projectSearchPlaceholder: "在项目中搜索...", projectCreateProject: "创建项目", projectCreateButton: "创建项目", projectUpdateButton: "更新", @@ -2795,7 +3078,7 @@ module.exports = { projectRecentTitle: "最近的项目", projectTopTitle: "最高筹资", projectTitlePlaceholder: "项目名称", - projectImage: "上传媒体(最大:50MB)", + projectImage: "上传媒体(最大:75MB)", projectDescription: "描述", projectDescriptionPlaceholder: "讲述故事和目标…", projectGoal: "目标 (ECO)", @@ -2867,15 +3150,13 @@ module.exports = { modulesModuleName: "名称", modulesModuleDescription: "描述", modulesModuleStatus: "状态", - modulesTotalModulesLabel: "已加载模块", + modulesTotalModulesLabel: "总计", modulesEnabledModulesLabel: "已启用", modulesDisabledModulesLabel: "已禁用", modulesInvitesLabel: "邀请", modulesInvitesDescription: "管理和应用邀请码的模块。", modulesWalletLabel: "钱包", modulesWalletDescription: "管理你的数字资产 (ECOin) 的模块。", - modulesLegacyLabel: "密钥", - modulesLegacyDescription: "快速安全地管理你的私钥的模块。", modulesCipherLabel: "加密器", modulesCipherDescription: "使用共享密码对文本进行对称加密和解密的模块。", modulesBookmarksLabel: "书签", @@ -2937,20 +3218,23 @@ module.exports = { modulesProjectsLabel: "项目", modulesProjectsDescription: "探索、众筹和管理项目的模块。", modulesBankingLabel: "银行", - modulesBankingDescription: "确定 ECOIN 真实价值并使用公共金库分配 UBI 的模块。", + modulesBankingDescription: "用于管理共同金库的模块:ECOIN 价值、税收与全民基本收入。", modulesFavoritesLabel: "收藏", modulesFavoritesDescription: "管理你收藏内容的模块。", fileTooLargeTitle: "文件过大", - fileTooLargeMessage: "文件超过了允许的最大大小(50 MB)。请选择较小的文件。", + fileTooLargeMessage: "文件超过了允许的最大大小(75 MB)。请选择较小的文件。", goBack: "返回", + errorDismiss: "好", errorPageTitle: "出现了问题", + errorNoAllocation: "未找到与此申领对应的分配。", + errorInvalidClaim: "此分配无法申领。", visibilityLabel: "可见性", + clearnetPrivateConflict: "私密、隐藏、关闭、仅限邀请或付费的内容不能发布到 CLEARNET。请选择 OASIS 或将其设为公开。", visibilityPublic: "公开", visibilityHidden: "隐藏", visibilityMakePublic: "设为公开", visibilityMakeHidden: "设为隐藏", invitesInhabitantsTitle: "居民", - invitesInhabitantsFollow: "关注", aiNavPlaceholder: "描述你需要什么...", aiNavDisabled: "AI 导航已停用。", aiNavResultsTitle: "AINav 建议", @@ -2961,7 +3245,7 @@ module.exports = { modulesAINavDescription: "用于以自然语言查询网络内容的模块。", directConnectDescription: "通过输入对方的 IP 地址、端口和公钥直接连接到节点。该节点将被添加为已关注的连接。", peerHost: "IP", - peerPort: "端口(默认:8008)", + peerPort: "端口", peerPublicKey: "公钥 (@...ed25519)", connectAndFollow: "连接", modulesPresetTitle: "常用配置", @@ -2970,15 +3254,15 @@ module.exports = { workflowsSet: "应用工作模式", workflow_default: "默认", workflow_jobs: "工作", - workflow_ruling: "治理", - workflow_politics: "政治", workflow_social: "社交", workflow_business: "商务", workflow_night: "夜间", + workflow_kids: "儿童", + workflow_activists: "活动人士", workflow_mobile: "移动端", statsCarbonFootprintTitle: "碳足迹", statsCarbonTombstone: "墓碑碳足迹", - uploadMedia: "上传媒体(最大:50MB)", + uploadMedia: "上传媒体(最大:75MB)", feedOpenDiscussion: "公开讨论", feedDetailTitle: "动态", feedPostComment: "发表评论", @@ -2986,7 +3270,7 @@ module.exports = { noComments: "还没有评论", mapsLabel: "地图", mapTitle: "地图", - mapDescription: "在您的网络中探索和管理离线地图。", + mapDescription: "在您的网络中管理和分享离线地图。", mapMineSectionTitle: "我的地图", mapCreateSectionTitle: "创建地图", mapUpdateSectionTitle: "更新地图", @@ -3015,7 +3299,7 @@ module.exports = { mapTypeOpen: "开放", mapTypeClosed: "封闭", mapTagsLabel: "标签", - mapTagsPlaceholder: "输入以逗号分隔的标签", + mapTagsPlaceholder: "输入标签,用逗号分隔", mapUrlLabel: "地图链接", mapMarkersTitle: "标记", mapMarkerDefault: "标记", @@ -3026,9 +3310,22 @@ module.exports = { markerImageLabel: "标记图片", mapAddMarkerTitle: "添加标记", mapAddMarkerButton: "添加标记", - mapCleanMarkerButton: "Clean Marker", + mapCleanMarkerButton: "清除标记", mapApplyZoom: "应用缩放", - mapSearchPlaceholder: "搜索描述、标签、作者...", + mapPlaceSearchPlaceholder: "查找地点或国家...", + mapNoPlaces: "未找到地点。", + mapClusterTitle: "个标记在此,点击放大", + mapDeleteMarker: "删除标记", + mapZoomIn: "放大", + mapZoomOut: "缩小", + mapFitMarkers: "显示所有标记", + mapPanLabel: "移动地图", + mapCenterHere: "在此居中", + mapNear: "靠近", + mapPickedSpot: "所选地点", + mapPickHint: "点击地图选择一个地点。", + mapClearPick: "清除", + mapSearchPlaceholder: "在地图中搜索...", mapSearchButton: "搜索", mapUpdatedAt: "已更新", mapNoMatch: "没有匹配的地图。", @@ -3039,31 +3336,602 @@ module.exports = { typeMapMarker: "地图标记", modulesMapLabel: "地图", modulesMapDescription: "管理和分享离线地图的模块。", + emergenciesTitle: "紧急情况", + modulesEmergenciesLabel: "紧急情况", + modulesEmergenciesDescription: "用于在网络中发布、确认和跟进紧急情况的模块。", + emergenciesDescription: "用于在网络中发布、确认和跟进紧急情况的模块。", + emergencyCreate: "创建紧急情况", + emergencyUpdate: "更新", + emergencyDelete: "删除", + emergencyTitleLabel: "标题", + emergencyTitlePlaceholder: "发生了什么?", + emergencyTextLabel: "详情", + emergencyTextPlaceholder: "地点、开始时间、应对措施...", + emergencyCategoryLabel: "类别", + emergencyCategoryWeather: "天气", + emergencyCategoryInfrastructure: "基础设施", + emergencyCategoryHealth: "健康", + emergencyCategorySecurity: "安全", + emergencyCategoryLost: "失物招领", + emergencyCategoryNeighborhood: "社区", + emergencyExpiresLabel: "有效期", + emergencyExpires1d: "1 天", + emergencyExpires3d: "3 天", + emergencyExpires7d: "1 周", + emergencyExpires30d: "1 个月", + emergencyExpiresNone: "未定", + emergencyExpiresKeep: "保留当前日期", + emergencyTagsLabel: "标签", + emergencyTagsPlaceholder: "输入标签,用逗号分隔", + emergencyFilterAll: "全部", + emergencyFilterMine: "我的", + emergencyFilterRecent: "最近", + emergencySeverityUnverified: "未核实", + emergencySeverityLow: "低", + emergencySeverityMedium: "中", + emergencySeverityHigh: "高", + emergencyStatusActive: "进行中", + emergencyStatusResolved: "已解决", + emergencyStatusExpired: "已过期", + emergencyConfirmations: "确认数", + emergencyConfirmButton: "确认", + emergencyConfirmed: "你已确认此紧急情况", + emergencyResolveButton: "标记为已解决", + emergencyReopenButton: "重新开放", + emergencyGenerateReport: "生成报告", + emergencyUpdateEdited: "已编辑", + emergencyLastUpdate: "最新更新", + emergencyUpdatesTitle: "最新更新:", + emergencyNoUpdates: "暂无更新。", + emergencyAddUpdate: "发布更新", + emergencyUpdatePlaceholder: "有什么变化?", + emergencyNoItems: "未找到紧急情况。", + emergencyExpiresAt: "到期", + emergencyBannerLabel: "进行中的紧急情况", + emergencyCreateSectionTitle: "创建紧急情况", + emergencyUpdateSectionTitle: "更新紧急情况", + emergencySearchPlaceholder: "在紧急情况中搜索...", + typeEmergency: "紧急情况", + typeEmergencyConfirm: "紧急情况确认", + typeEmergencyUpdate: "紧急情况更新", + statsEmergency: "紧急情况", + statsEmergencyConfirm: "紧急情况确认", + statsEmergencyUpdate: "紧急情况更新", + pmBotEmergencies: "EmergencyBot", + emergencyBotUpdatedTitle: "你关注的紧急情况有更新。", + emergencyBotResolvedTitle: "你关注的紧急情况已解决。", + dataKindEmergencies: "紧急情况", + mailingTitle: "邮件列表", + modulesMailingLabel: "邮件列表", + modulesMailingDescription: "在你的网络中发现和管理邮件列表。", + modulesPhoneLabel: "电话", + modulesPhoneDescription: "与其他居民进行端到端加密视频通话的模块。", + mailingDescription: "在你的网络中发现和管理邮件列表。", + mailingCreate: "创建邮件列表", + mailingUpdate: "更新", + mailingArchive: "归档", + mailingActivate: "激活", + mailingCreateSectionTitle: "创建邮件列表", + mailingUpdateSectionTitle: "更新邮件列表", + mailingTitleLabel: "标题", + mailingTitlePlaceholder: "邮件列表名称", + mailingDescriptionLabel: "描述", + mailingDescriptionPlaceholder: "这个列表是关于什么的?", + mailingTypeLabel: "邮件列表类型", + mailingTypeOpen: "开放", + mailingTypeClosed: "封闭", + mailingStatusLabel: "状态", + mailingStatusActive: "活跃", + mailingStatusArchived: "已归档", + mailingMembersLabel: "邀请居民", + mailingMembersPlaceholder: "@id1.ed25519, @id2.ed25519, ...", + mailingSubscribers: "订阅者", + mailingTagsLabel: "标签", + mailingTagsPlaceholder: "输入标签,用逗号分隔", + mailingFilterAll: "全部", + mailingFilterMine: "我的", + mailingFilterSubscribed: "已订阅", + mailingFilterRecent: "最近", + mailingHistoryDate: "日期", + mailingHistoryThreads: "话题", + mailingHistoryInhabitants: "居民", + mailingHistorySearchPlaceholder: "在历史中搜索...", + mailingNoMessages: "还没有消息。", + mailingNoItems: "未找到邮件列表。", + mailingSearchPlaceholder: "在邮件列表中搜索...", + mailingWriteButton: "写消息", + mailingWriteTitle: "写信给列表", + mailingReplyTitle: "回复列表", + mailingSend: "发送", + mailingTextPlaceholder: "你的消息会送达列表中的每个参与者。", + mailingParticipants: "参与者", + inboxFilterMailing: "邮件列表", + mailingReplyPlaceholder: "回复列表...", + mailingReplyToList: "回复列表", + typeMailingList: "邮件列表", + statsMailingList: "邮件列表", + mailingListLabel: "邮件列表", + logisticsTitle: "物流", + modulesLogisticsLabel: "物流", + modulesLogisticsDescription: "在你的网络中发现和管理物流计划。", + logisticsDescription: "在你的网络中发现和管理物流计划。", + logisticsCreate: "创建路线", + logisticsUpdate: "更新", + logisticsClose: "关闭", + logisticsReopen: "重新开放", + logisticsCreateSectionTitle: "创建路线", + logisticsUpdateSectionTitle: "更新路线", + logisticsTitleLabel: "标题", + logisticsTitlePlaceholder: "去市场的顺风车,有货车...", + logisticsKindLabel: "物流类型", + logisticsKindTrip: "出行", + logisticsKindShipment: "运送", + logisticsKindTrips: "出行", + logisticsKindShipments: "运送", + logisticsModeLabel: "模式", + logisticsModeOffer: "提供", + logisticsModeRequest: "寻求", + logisticsModeOffers: "提供", + logisticsModeRequests: "请求", + logisticsDescriptionLabel: "描述", + logisticsDescriptionPlaceholder: "集合地点、携带物品、条件...", + logisticsOriginLabel: "起点", + logisticsOriginPlaceholder: "从哪里出发", + logisticsDestinationLabel: "终点", + logisticsDestinationPlaceholder: "到哪里结束", + logisticsDateLabel: "日期和时间", + logisticsRecurrenceLabel: "重复", + logisticsFrequencyLabel: "频率", + logisticsRecurrenceNone: "单次", + logisticsRecurrenceDaily: "每天", + logisticsRecurrenceWeekly: "每周", + logisticsRecurrenceMonthly: "每月", + logisticsSeatsLabel: "座位", + logisticsSizeLabel: "尺寸", + logisticsSizePlaceholder: "例如 2m x 1m", + logisticsWeightLabel: "重量", + logisticsWeightPlaceholder: "例如 40kg", + logisticsPriceFree: "免费", + logisticsPriceEco: "ECO", + logisticsPriceTime: "时间", + logisticsPriceLabel: "价格", + logisticsHours: "小时", + logisticsOrderRefLabel: "订单链接", + logisticsOrderRefPlaceholder: "待送达订单的 /shops/... 或 /market/...", + logisticsOrderLink: "关联订单", + logisticsTagsLabel: "标签", + logisticsTagsPlaceholder: "输入标签,用逗号分隔", + logisticsFilterAll: "全部", + logisticsFilterMine: "我的", + logisticsFilterBooked: "已预订", + logisticsFilterRecent: "最近", + logisticsFilterHistory: "历史", + logisticsFilterTop: "热门", + logisticsStatusOpen: "开放", + logisticsStatusUpcoming: "即将", + logisticsStatusPast: "已过", + logisticsStatusClosed: "已关闭", + logisticsZoneLabel: "区域", + logisticsSearchPlaceholder: "在物流中搜索...", + logisticsNoItems: "未找到路线。", + logisticsBookingPending: "待处理", + logisticsBookingConfirmed: "已确认", + logisticsBookingRejected: "已拒绝", + logisticsBookingCancelled: "已取消", + logisticsBookingDelivered: "已送达", + logisticsConfirm: "确认", + logisticsReject: "拒绝", + logisticsDeliver: "送达", + logisticsReceiptLabel: "收据", + logisticsReceiptPlaceholder: "送达备注(可选)", + logisticsCancelBooking: "取消预订", + logisticsBookTitle: "预订此路线", + logisticsNotesLabel: "备注", + logisticsNotesPlaceholder: "负责人需要了解的信息", + logisticsBook: "预订", + logisticsBookingsTitle: "预订", + logisticsMyBookingTitle: "你的预订", + logisticsRatingsTitle: "评价", + logisticsNoRatings: "还没有评价。", + logisticsRatingsWhenClosed: "路线关闭后开放评价。", + logisticsScoreLabel: "评分", + logisticsRatingPlaceholder: "进行得如何?", + logisticsRate: "评价", + typeLogisticsRoute: "路线", + typeLogisticsRating: "路线评价", + statsLogisticsRoute: "路线", + statsLogisticsRating: "路线评价", + logisticsRouteLabel: "路线", + podcastsTitle: "播客", + modulesPodcastsLabel: "播客", + modulesPodcastsDescription: "用于发布播客频道的模块。", + podcastsDescription: "用于发布播客频道的模块。", + podcastCreate: "创建播客", + podcastUpdate: "更新", + podcastDelete: "删除", + podcastCreateSectionTitle: "创建播客", + podcastUpdateSectionTitle: "更新播客", + podcastTitleLabel: "标题", + podcastTitlePlaceholder: "频道名称", + podcastDescriptionLabel: "描述", + podcastDescriptionPlaceholder: "这个播客是关于什么的?", + podcastCategoryLabel: "分类", + podcastCategoryNews: "新闻", + podcastCategoryMusic: "音乐", + podcastCategoryTalk: "谈话", + podcastCategoryEducation: "教育", + podcastCategoryTech: "科技", + podcastCategoryCulture: "文化", + podcastCategoryCommunity: "社区", + podcastCategoryOasis: "OASIS", + podcastCategoryOther: "其他", + podcastTagsLabel: "标签", + podcastCoverLabel: "封面(图片或视频)", + podcastEpisodeAudioLabel: "音频或视频文件(最大 75MB)", + podcastTagsPlaceholder: "输入标签,用逗号分隔", + podcastFilterAll: "全部", + podcastFilterMine: "我的", + podcastFilterRecent: "最近", + podcastFilterTop: "热门", + podcastFilterViewers: "听众", + podcastSearchPlaceholder: "在播客中搜索...", + podcastNoItems: "未找到播客。", + podcastEpisodesLabel: "剧集", + podcastPlaysLabel: "观看次数", + podcastLastEpisode: "最新剧集", + podcastEpisodesTitle: "剧集", + podcastNoEpisodes: "还没有剧集。", + podcastEpisodeAdd: "添加剧集", + podcastEpisodeCreateTitle: "发布剧集", + podcastEpisodeUpdateTitle: "更新剧集", + podcastEpisodeTitleLabel: "剧集标题", + podcastEpisodeTitlePlaceholder: "剧集标题", + podcastEpisodeDescriptionLabel: "节目笔记", + podcastEpisodeDescriptionPlaceholder: "本集内容、链接、致谢...", + podcastEpisodePublish: "发布", + podcastPlayed: "已播放", + podcastMarkPlayed: "标记为已播放", + typePodcast: "播客", + typePodcastEpisode: "剧集", + typePodcastOpinion: "剧集评价", + typePodcastPlay: "剧集播放", + statsPodcast: "播客", + statsPodcastEpisode: "剧集", + statsPodcastPlay: "剧集播放", + pmBotPodcasts: "PodcastBot", + pmBotBlogs: "BlogsBot", + blogBotNewTitle: "你关注的博客有新文章。", + podcastBotEpisodeTitle: "你关注的播客有新剧集。", + podcastLabel: "播客", + podcastEpisodeLabel: "剧集", + campaignsTitle: "倡议", + modulesCampaignsLabel: "倡议", + modulesCampaignsDescription: "在你的网络中发现和管理倡议。", + campaignsDescription: "在你的网络中发现和管理倡议。", + campaignCreate: "发起倡议", + campaignUpdate: "更新", + campaignDelete: "删除", + campaignClose: "关闭", + campaignReopen: "重新开放", + campaignCreateSectionTitle: "发起倡议", + campaignUpdateSectionTitle: "更新倡议", + campaignTitleLabel: "标题", + campaignTitlePlaceholder: "你想达成什么?", + campaignTextLabel: "正文", + campaignTextPlaceholder: "说明诉求、由谁决定以及你要求什么...", + campaignCategoryLabel: "分类", + campaignCategoryEnvironment: "环境", + campaignCategoryRights: "权利", + campaignCategoryHealth: "健康", + campaignCategoryEducation: "教育", + campaignCategoryInfrastructure: "基础设施", + campaignCategoryCulture: "文化", + campaignCategoryEconomy: "经济", + campaignCategoryOther: "其他", + campaignGoalLabel: "签名目标", + campaignDeadlineLabel: "截止日期", + campaignTagsLabel: "标签", + campaignTagsPlaceholder: "输入标签,用逗号分隔", + campaignFilterAll: "全部", + campaignFilterMine: "我的", + campaignFilterSigned: "已签名", + campaignFilterRecent: "最近", + campaignFilterTop: "热门", + campaignStatusOpen: "进行中", + campaignStatusAchieved: "已达成", + campaignStatusUnachieved: "未达成", + campaignStatusClosed: "已关闭", + campaignSearchPlaceholder: "在倡议中搜索...", + campaignNoItems: "未找到倡议。", + campaignSignaturesLabel: "签名", + campaignSignaturesTitle: "支持者", + campaignNoSignatures: "还没有签名。", + campaignSignTitle: "签署此倡议", + campaignSignPlaceholder: "你为什么支持?(可选)", + campaignSign: "签名", + campaignSigned: "你已签名!", + campaignNotSigned: "还缺你的签名", + campaignElevate: "提交议会", + campaignElevated: "已提交议会", + campaignUpdatesTitle: "进展", + campaignNoUpdates: "还没有进展。", + campaignUpdatePlaceholder: "发生了什么?", + campaignAddUpdate: "发布进展", + campaignQrAlt: "签署此倡议的二维码", + campaignQrCaption: "扫码签名", + typeCampaign: "倡议", + typeCampaignSignature: "签名", + typeCampaignUpdate: "倡议进展", + statsCampaign: "倡议", + statsCampaignSignature: "倡议签名", + statsCampaignUpdate: "倡议进展", + pmBotCampaigns: "CampaignBot", + campaignBotUpdatedTitle: "你关注的倡议有新进展。", + campaignBotAchievedTitle: "你关注的倡议已达成目标。", + campaignBotRaisedTitle: "你签署的一个活动已提交议会。", + pmBotLogistics: "LogisticsBot", + logisticsBotUpdatedTitle: "你预订的路线已更改。", + logisticsBotClosedTitle: "你预订的路线已关闭。", + logisticsBotBookedTitle: "有人预订了你的路线。", + logisticsBotCancelledTitle: "你路线上的一个预订已取消。", + logisticsBotConfirmedTitle: "你的预订已确认:包裹已在途中。", + logisticsBotRejectedTitle: "你的预订已被拒绝。", + logisticsBotDeliveredTitle: "你的包裹已送达。", + campaignLabel: "倡议", + campaignGenerateReport: "生成报告", + logisticsGenerateReport: "生成报告", + mailingGenerateReport: "生成报告", + campaignUpdateEdited: "已编辑", + mailingMessagesLabel: "消息", + logisticsBookingsLabel: "预订", + backupTitle: "备份", + modulesBackupLabel: "备份", + modulesBackupDescription: "用于导出和导入密钥并创建加密备份的模块。", + backupDescription: "用于导出和导入密钥并创建加密备份的模块。", + backupTypeRecovery: "恢复", + backupTypeKeys: "导出密钥", + backupTypeFull: "完整备份", + backupTypeRestore: "恢复备份", + backupTypeInstant: "即时", + backupInstantDescription: "一个普通的 .zip,包含你全部的日志与文件,不含你的私钥,也没有密码。很快,但", + backupInstantRisk: "拿到文件的人能读到里面的一切。", + backupKitPageDescription: "恢复工具包只能在本设备上打开。", + backupKitId: "Oasis ID", + backupKitDate: "生成时间", + backupKitQrAlt: "你的私钥二维码", + backupKitStep1: "打印或手抄,并离线存放在安全的地方。", + backupKitStep2: "恢复时,在新设备上安装 Oasis,并在 备份 › 密钥 中导入此私钥。", + backupKitStep3: "如果此页面泄露,请立即销毁。", + backupKitPdfButton: "生成 PDF", + backupScopeLabel: "范围", + backupScopeAll: "全部", + backupScopeMine: "仅我的内容", + backupSinceLabel: "起始日期", + backupEstimateTitle: "估算", + backupFileInfo: "加密副本将下载到你的设备(文件名:oasis-backup-日期.oasisbk)", + backupExportButton: "下载备份", + backupRestorePassword: "输入创建备份时使用的密码", + backupRestoreButton: "恢复备份", + backupRestoredLine: "备份已恢复。", + backupRestoredMessages: "新增消息", + backupRestoredSkipped: "已存在", + backupRestoredBlobs: "新增文件", + backupRestoredState: "已恢复设置", + backupRestoredFailed: "失败", + backupRestoredForked: "分叉", + backupRestoreRunning: "正在恢复备份。此页面会自动刷新;期间你可以继续使用 Oasis。", + backupRestoreElapsed: "已用时", + backupRestoreFailedLine: "无法恢复备份。", + backupRestoreForkMine: "此设备已用你的身份发布了备份中没有的消息,因此无法在其之上应用备份。请在全新安装中导入密钥后立即恢复,且不要先做其他操作。", + verificationTitle: "校验", + verificationDescription: "检查你的动态链、检测分叉,并查看孤立或缺失的文件。", + verificationRun: "运行校验", + verificationTotalMessages: "日志中的消息", + verificationFeed: "你的动态", + verificationMessages: "条消息", + verificationLastSequence: "最后序号", + verificationGaps: "缺口", + verificationBrokenLinks: "断链", + verificationBadHashes: "错误哈希", + verificationBadSignatures: "错误签名", + verificationDuplicates: "重复序号", + verificationForks: "分叉", + verificationBlobs: "文件", + verificationBlobsPresent: "存在", + verificationBlobsReferenced: "被引用", + verificationBlobsOrphan: "孤立", + verificationBlobsMissing: "缺失", + verificationBlobsMissingOwn: "此设备上缺失的你自己的文件", + verificationBlobsMissingOthers: "其他居民的文件尚未下载", + verificationBlobsUnavailable: "无法在此设备上列出文件。", + wikiTitle: "维基", + modulesWikiLabel: "维基", + modulesWikiDescription: "用于发现和管理维基的模块。", + wikiDescription: "用于发现和管理维基的模块。", + wikiCreate: "创建维基", + wikiUpdate: "更新", + wikiDelete: "删除", + wikiEdit: "编辑", + wikiTitleLabel: "标题", + wikiTitlePlaceholder: "维基标题...", + wikiBodyLabel: "内容", + wikiBodyPlaceholder: "用 [[维基标题]] 或 [[维基标题|自定义文字]] 链接其他维基。\n\n用 @id 提及居民。\n\n用 #oasis 添加标签。\n\n粘贴链接,如 https://solarnethub.com\n\n空行开始新段落;单个换行会被保留。\n\n附加任何受支持的媒体,它会嵌入到这里。", + formatBarLabel: "格式", + wikiStatusLabel: "维基类型", + wikiStatusOpen: "开放", + wikiStatusClosed: "关闭", + wikiTagsLabel: "标签", + wikiTagsPlaceholder: "输入标签,用逗号分隔", + wikiSummaryLabel: "修改摘要", + wikiSummaryPlaceholder: "改了什么?", + wikiFilterAll: "全部", + wikiFilterMine: "我的", + wikiFilterRecent: "最近", + wikiFilterLinked: "已关联", + wikiChanges: "最近更改", + wikiNoPages: "未找到维基。", + wikiNoChanges: "暂无更改。", + wikiHistory: "历史", + wikiVersion: "版本", + wikiVersions: "个版本", + wikiRestore: "恢复", + wikiDiff: "更改", + wikiViewVersion: "查看", + wikiLinkedPages: "关联页面", + wikiNoLinkedPages: "此页面尚未与其他页面互链。", + wikiMissingLink: "页面尚不存在", + wikiDuplicateTitle: "已存在使用该标题的页面。请修改标题以创建另一个页面。", + wikiCreateSectionTitle: "创建维基", + wikiUpdateSectionTitle: "更新维基", + wikiSearchPlaceholder: "在维基中搜索...", + wikiCurrentVersion: "当前版本", + wikiViewingVersion: "你正在查看旧版本", + wikiBackToPage: "返回页面", + wikiLinkedChip: "已关联", + wikiRestoredSummary: "已恢复的版本", + wikiExportPdf: "生成页面", + wikiPreview: "预览", + tribeSectionWiki: "维基", + tribeWikiCreate: "创建维基", + tribeWikiEmpty: "还没有维基。", + typeWiki: "维基", padsTitle: "协作板", padTitle: "协作板", modulesPadsLabel: "协作板", - modulesPadsDescription: "管理协作文本编辑器的模块。", + modulesPadsDescription: "用于管理协作文本编辑器的模块。", padCreate: "创建协作板", padUpdate: "更新", padDelete: "删除", padTitleLabel: "标题", padTitlePlaceholder: "输入协作板标题...", padStatusLabel: "状态", + padTypeLabel: "Pad 类型", padStatusOpen: "开放", padStatusInviteOnly: "仅限受邀者", padStatusClosed: "关闭", padDeadlineLabel: "截止日期", padTagsLabel: "标签", - padTagsPlaceholder: "标签1, 标签2, ...", + padTagsPlaceholder: "输入标签,用逗号分隔", padMembersLabel: "成员", padCreated: "创建时间", - padStartEditing: "开始编辑!", + padStartEditing: "编辑", padEditorPlaceholder: "开始写作...", padSubmitEntry: "提交", padNoEntries: "暂无内容。", padAllSectionTitle: "全部协作板", padMineSectionTitle: "我的协作板", padsDescription: "在您的网络中管理协作加密的文本编辑器。", + roomsTitle: "房间", + roomsDescription: "发现并加入您网络中的加密视频会议房间。", + modulesRoomsLabel: "房间", + modulesRoomsDescription: "用于发现并加入加密视频会议房间的模块。", + roomFilterRecent: "最近", + roomFilterMine: "我的", + roomFilterAll: "全部", + roomFilterLive: "直播中", + roomFilterOpen: "开放", + roomFilterInvite: "仅限受邀者", + roomFilterClosed: "已关闭", + roomCreate: "创建房间", + roomUpdate: "更新", + roomDelete: "删除", + roomClose: "关闭", + roomCreateSectionTitle: "创建房间", + roomUpdateSectionTitle: "更新房间", + roomTitlePlaceholder: "房间标题", + roomDescriptionLabel: "描述", + roomDescriptionPlaceholder: "你们要聊什么?", + roomTypeLabel: "房间类型", + roomTagsLabel: "标签", + roomStatusOpen: "开放", + roomStatusInviteOnly: "仅限受邀者", + roomStatusClosed: "已关闭", + roomParticipants: "参与者", + roomJoin: "加入", + roomLeave: "离开", + roomFull: "房间已满", + roomLiveChip: "直播中", + roomMuted: "已静音", + roomRec: "录制音频", + roomRecStop: "停止录音", + roomRecording: "REC", + roomNotificationsOn: "ON", + roomNotificationsOff: "OFF", + roomEventJoined: "{name} 进入了房间", + roomEventLeft: "{name} 离开了房间", + roomEventRecStart: "{name} 开始录音", + roomEventRecStop: "{name} 停止录音", + roomEventMuted: "{name} 已静音", + roomEventUnmuted: "{name} 已取消静音", + roomEventHand: "{name} 举手了", + roomRaiseHand: "举手", + roomLowerHand: "放下手", + confirmActionTitle: "确认此操作", + confirmActionButton: "确认", + confirmJoinText: "你即将使用此邀请加入。", + confirmFollowText: "你即将支持这位居民。", + confirmSignText: "你即将签署此活动。", + confirmDownloadText: "你即将把此文件下载到你的设备。", + roomRecordings: "录音记录", + roomClearNotices: "清除", + roomLineSwitchboard: "总机", + roomLineDndError: "此房间的号码不接受来电。", + roomNumberHint: "在电话中拨打此号码即可进入房间。", + liveRefresh: "刷新", + roomNobody: "目前房间里没有人。", + roomSomeone: "目前房间里有 {n} 人。", + roomYou: "你", + roomYouAreIn: "你已在其中", + roomNobodyInside: "此房间现在没有人。", + roomErrorFull: "房间已满。有人离开后即可加入。", + roomErrorUnreachable: "目前无法连接到该房间,请稍后再试。", + roomErrorBusy: "你已在通话中或在另一个房间里。", + roomErrorClosed: "此房间已关闭。", + roomAccessDenied: "此房间仅限受邀者。你需要邀请才能加入。", + roomsNoItems: "未找到房间。", + roomSearchPlaceholder: "搜索房间……", + roomInviteMode: "邀请", + tribeSectionRooms: "房间", + tribeRoomCreate: "创建房间", + tribeRoomsEmpty: "暂无房间。", + typeRoom: "房间", + typeRoomMember: "房间参与者", + statsRoom: "房间", + statsRoomMember: "房间参与者", + favoritesFilterRooms: "房间", + invitesRoomsTitle: "房间", + inviteCodeInvalid: "此邀请码无效或已被使用。", + inviteAlreadyMember: "你已经加入了,可以在对应模块中打开。", + actionFailed: "无法完成此操作,请重试。", + publishTooShort: "你的帖子太短了。", + walletSendFailed: "无法发送付款:{reason}", + pixeliaCoordsError: "这些坐标超出了网格范围。", + courtsErrTitle: "需要填写标题。", + courtsErrRespondent: "请说明案件针对谁。", + courtsErrRespondentId: "这不是有效的 Oasis ID。", + courtsErrMethod: "请选择案件的解决方式。", + courtsErrCaseNotFound: "找不到该案件。", + courtsErrBrief: "请填写你的答复。", + courtsErrStance: "请选择你的立场。", + courtsErrResult: "请填写结果。", + courtsErrTerms: "请填写协议条款。", + courtsErrMediator: "请至少添加一名调解人。", + courtsErrSelfMediator: "你不能在自己的案件中担任调解人。", + courtsErrJudge: "请选择一名法官。", + courtsErrSelfJudge: "你不能担任自己案件的法官。", + courtsErrVisibility: "请选择谁可以查看该案件。", + courtsErrNomination: "找不到该提名。", + opinionAlreadyGiven: "你已经发表过意见了。", + cipherPasswordShort: "密码长度至少为 32 个字符。", + errorForbidden: "你无权访问此内容。", + errorNotFound: "未找到。", + errorClosed: "已关闭。", + errorInviteOnly: "仅限受邀者,你需要邀请。", + errorTooLong: "文本太长。", + errorRequired: "缺少必填信息。", + errorInvalid: "部分信息无效。", + errorNotAuthor: "只有作者可以执行此操作。", + panicRemoved: "你的区块链已被删除。", padRecentSectionTitle: "最近协作板", padOpenSectionTitle: "开放协作板", padClosedSectionTitle: "关闭协作板", @@ -3075,18 +3943,18 @@ module.exports = { padRemoveFavorite: "从收藏中移除", padClose: "关闭", padBackToEditor: "返回编辑器", - padSearchPlaceholder: "搜索共享文本...", + padSearchPlaceholder: "在协作板中搜索...", modulesChatsLabel: "聊天", - modulesChatsDescription: "发现和管理加密聊天室的模块。", + modulesChatsDescription: "用于发现和管理聊天的模块。", typeChat: "聊天", typeChatMessage: "聊天消息", chatsTitle: "聊天", - chatMineSectionTitle: "Your Chats", - chatRecentTitle: "Recent Chats", + chatMineSectionTitle: "你的聊天", + chatRecentTitle: "最近的聊天", chatFavoritesTitle: "收藏", - chatOpenTitle: "Open Chats", - chatClosedTitle: "Closed Chats", + chatOpenTitle: "开放的聊天", + chatClosedTitle: "已关闭的聊天", chatDescription: "描述", chatCategory: "类别", chatStatus: "状态", @@ -3101,34 +3969,172 @@ module.exports = { chatMessagesLabel: "消息", chatThreadsLabel: "话题", chatCreatedAt: "创建时间", - chatSearchPlaceholder: "搜索聊天...", + chatSearchPlaceholder: "在聊天中搜索...", chatStatusOpen: "开放", chatStatusInviteOnly: "仅限邀请", chatStatusClosed: "已关闭", chatSendMessage: "发送", chatJumpLatest: "最新消息", + chatReply: "回复", + chatReplyCancel: "取消回复", + chatPin: "置顶", + chatUnpin: "取消置顶", + chatPinned: "已置顶", chatPickChat: "选择一个聊天以打开", chatNoneYet: "目前还没有可用的聊天。", - activitySearchPlaceholder: "在动态中搜索...", + activitySearchPlaceholder: "在活动记录中搜索...", trendingSearchPlaceholder: "在热门中搜索...", opinionsSearchPlaceholder: "在观点中搜索...", - votesSearchPlaceholder: "在表决中搜索...", - welcomeStepUxTitle: "选择你的界面", - welcomeStepUxText: "决定 Oasis 的外观。之后可在设置中更改。", - welcomeStepUxAction: "使用此视图", + votesSearchPlaceholder: "在投票中搜索...", + gamesSearchPlaceholder: "在游戏中搜索...", + schoolTitle: "学校", + pmBotWiki: "WikiBot", + wikiBotEditedTitle: "你关注的维基页面已被编辑。", + wikiBotRestoredTitle: "你关注的维基页面已恢复到较早的版本。", + pmBotSchool: "EducaBot", + schoolBotEnrolledTitle: "一名新学生报名了您的课程。", + schoolBotInvitedTitle: "您已被邀请加入课程。", + schoolBotAdmittedTitle: "您已被课程录取。", + schoolBotCertificateTitle: "您已获得证书。", + schoolBotPassedTitle: "一名学生通过了您的课程。", + schoolBotLessonTitle: "已发布新课时。", + invitesSchoolTitle: "学校", + schoolEnrollLabel: "加入课程", + schoolExamRetryLater: "24 小时后可重试。", + schoolApproved: "已通过", + schoolExamPassMark: "及格分(0-10)", + schoolExamPassed: "通过", + schoolExamPending: "待完成", + schoolExamFailed: "未通过", + schoolExamAddQuestion: "添加问题", + schoolExamCreateQuestion: "创建问题", + schoolExamOption: "选项", + schoolExamCorrect: "正确选项", + schoolExamPoints: "分数", + schoolMaterialTextPlaceholder: "本课时的备注...", + schoolMaterials: "资料", + schoolAddMaterial: "添加资料", + schoolNoMaterials: "暂无资料。", + bankYourSchoolBalance: "您的学校收入", + schoolProgress: "进度", + schoolProgressMark: "标记为已完成", + schoolProgressDone: "已完成", + schoolExams: "考试", + schoolExamCreate: "创建考试", + schoolExamTitlePlaceholder: "考试标题", + schoolExamHelp: "每行一个问题:问题?; 选项 ; 正确选项* ; 选项", + schoolExamQuestionsPlaceholder: "SSB 是什么?; 一个数据库 ; 一个 P2P 协议* ; 一个浏览器", + schoolExamSubmit: "提交答案", + schoolExamScore: "您的得分", + schoolNoScoresYet: "暂无成绩。", + schoolLessonUnit: "单元/阶段", + schoolLessonUnitPlaceholder: "例如:阶段 1", + schoolLessonOrder: "顺序", + profileClearnetSchoolLabel: "学校", + schoolStartDate: "开始日期", + schoolSessionDate: "课次日期", + schoolCourseChat: "课程聊天", + agendaFilterSchool: "学校", + agendaFilterCampaigns: "活动", + agendaFilterLogistics: "物流", + schoolCourseType: "课程类型", + schoolTypeOpen: "开放", + schoolTypePaid: "付费", + schoolTypeInvite: "仅限邀请", + schoolLessonLocked: "课时已加密——请向教师索取课程密钥。", + schoolGrantAccess: "发送课程密钥", + schoolKeySent: "密钥已发送", + schoolOngoing: "进行中", + schoolFilterApplied: "已报名", + schoolFilterOpen: "开放", + schoolFilterFavorites: "收藏", + schoolPendingPayment: "待付款", + schoolPendingStudents: "待确认报名", + schoolViewBill: "查看账单", + schoolPaymentNote: "报名会生成一张 ECO 账单(转账)。转账签署后,您的报名即被确认。", + schoolCloseCourse: "关闭课程", + schoolReopenCourse: "重新开放课程", + schoolDescription: "在您的网络中教授和学习课程。", + schoolFilterAll: "全部", + schoolFilterMine: "我的", + schoolFilterTop: "热门", + schoolFilterRecent: "最新", + schoolCreateButton: "创建课程", + schoolCreateSectionTitle: "创建课程", + schoolUpdateSectionTitle: "更新课程", + schoolCourseTitlePlaceholder: "课程名称", + schoolCourseDescriptionPlaceholder: "你将在这门课程中教什么?", + schoolCourseImage: "课程图片(最大:75MB)", + schoolTags: "标签", + schoolTagsPlaceholder: "输入标签,用逗号分隔", + schoolPrice: "价格(ECO)", + schoolVisibility: "可见性", + schoolVisibilityPublic: "公开", + schoolVisibilityInvite: "邀请制", + schoolStatus: "状态", + schoolClosed: "关闭", + schoolFree: "免费", + schoolCreate: "创建课程", + schoolUpdate: "更新课程", + schoolSearchPlaceholder: "在学校中搜索...", + schoolSortTitle: "标题", + schoolNoCourses: "未找到课程。", + schoolStudents: "学生", + schoolTeacher: "教师", + schoolCreatedAt: "创建时间", + schoolLessons: "课时", + schoolNoLessons: "暂无课时。", + schoolEnrollToSee: "报名后即可查看本课程的课时。", + schoolEnrollButton: "报名", + schoolUnenrollButton: "退出课程", + schoolInviteRequired: "本课程为邀请制。", + schoolTeacherPanel: "教师面板", + schoolStudentPanel: "学生面板", + schoolAddLesson: "创建课时", + schoolLessonTitlePlaceholder: "课时标题", + schoolLessonTextPlaceholder: "描述本课时涵盖的内容...", + schoolInviteStudents: "邀请学生", + schoolInvitePlaceholder: "@id1, @id2...", + schoolInviteButton: "邀请", + schoolInvited: "已邀请", + schoolIssueCertificate: "创建证书", + schoolCertified: "已认证", + schoolCertificateTextPlaceholder: "证书备注(可选)", + schoolLessonOpen: "打开课程", + schoolCertificatePdf: "PDF", + schoolCertificates: "证书", + schoolNoCertificates: "尚未颁发证书。", + schoolEditButton: "编辑", + schoolDeleteButton: "删除", + typeSchool: "学校", + schoolCourseLabel: "课程", + emergencyLabel: "紧急情况", + wikiPageLabel: "维基", + statsSchoolCourse: "课程", + statsSchoolLesson: "课时", + statsSchoolEnroll: "报名", + statsSchoolCertificate: "学校证书", + modulesSchoolLabel: "学校", + modulesSchoolDescription: "知识交流模块。", + welcomeStepUxAction: "保存视图", + welcomeStepWorkflowTitle: "你打算如何使用 OASIS?", + welcomeStepWorkflowText: "Oasis 有很多模块。选择适合你的配置,只保留有用的模块。之后随时可以在“模块”中调整。", + welcomeStepWorkflowAction: "保存配置", + welcomeWorkflow_full: "默认", chatRateLimitTitle: "消息过多", chatRateLimitMessage: "你已达到该房间每小时的消息上限,请稍后再试。", chatMessagePlaceholder: "输入您的消息...", chatNoMessages: "暂无消息。", chatTitlePlaceholder: "聊天标题", chatDescriptionPlaceholder: "聊天描述", - chatTagsPlaceholder: "标签1, 标签2", + chatTagsPlaceholder: "输入标签,用逗号分隔", chatImageLabel: "选择图片文件 (.jpeg, .jpg, .png, .gif)", chatAuthor: "作者", chatAddFavorite: "添加到收藏", chatRemoveFavorite: "从收藏中移除", chatPM: "私信", chatStatusLabel: "状态", + chatTypeLabel: "聊天类型", chatCategoryLabel: "类别", gamesTitle: "游戏", gamesDescription: "发现并玩一些游戏。", @@ -3159,22 +4165,26 @@ module.exports = { gamesFlipFlopTitle: "FlipFlop", gamesFlipFlopDesc: "抛硬币,猜正面还是反面。你有多幸运?", games8BallTitle: "8Ball Pool", - games8BallDesc: "Top-down pool. Click to aim, hold to charge power. Pot all balls in the fewest shots.", - gamesArtilleryTitle: "Artillery", - gamesArtilleryDesc: "Aim your cannon, factor in the wind, and hit the target. 5 rounds, fewest shots wins.", - gamesLabyrinthTitle: "Labyrinth", - gamesLabyrinthDesc: "Escape the maze before your moves run out. Each level gets bigger and harder.", + games8BallDesc: "俯视视角台球。点击瞄准,按住蓄力。用最少的击球次数打进所有球。", + gamesArtilleryTitle: "炮兵", + gamesArtilleryDesc: "瞄准你的大炮,考虑风向,击中目标。共 5 回合,用炮最少者获胜。", + gamesLabyrinthTitle: "迷宫", + gamesLabyrinthDesc: "在步数用完之前逃出迷宫。每一关都更大、更难。", gamesCocomanTitle: "Cocoman", - gamesCocomanDesc: "Eat all the dots, avoid the ghosts. Turn-based Pac-Man — every key press counts.", + gamesCocomanDesc: "吃掉所有豆子,避开幽灵。幽灵实时移动——别停下来!", gamesTetrisTitle: "Tetris", - gamesAudioPendulumTitle: "Audio Pendulum", - gamesAudioPendulumDesc: "Chaotic physics simulator with real-time audio synthesis. Angular velocities become frequencies, peaks become drum hits. No two simulations sound alike.", - gamesTetrisDesc: "Classic falling blocks. Clear lines to score. How long can you last?", - gamesFilterScoring: "SCORING", - gamesHallOfFame: "Hall of Fame", - gamesHallPlayer: "Player", - gamesHallScore: "Score", - gamesNoScores: "No scores yet.", + gamesAudioPendulumTitle: "声音摆", + gamesAudioPendulumDesc: "带有实时音频合成的混沌物理模拟器。角速度变成频率,峰值变成鼓点。没有两次模拟听起来相同。", + gamesVisualDjTitle: "Visual DJ", + gamesVisualDjDesc: "在两个唱盘上现场混合视觉效果:为每个唱盘选择图案,调节速度、色彩和缩放,在两者之间推拉切换,并随节拍闪光。点击打拍子,或让麦克风驱动脉动。", + gamesTetrisDesc: "经典的下落方块。消除整行得分。你能坚持多久?", + gamesMinesweeperTitle: "扫雷", + gamesMinesweeperDesc: "经典扫雷。清空棋盘即可升级——更大的网格、更多的地雷、更高的分数。", + gamesFilterScoring: "得分", + gamesHallOfFame: "名人堂", + gamesHallPlayer: "玩家", + gamesHallScore: "得分", + gamesNoScores: "暂无得分。", calendarsTitle: "日历", calendarTitle: "日历", modulesCalendarsLabel: "日历", @@ -3182,15 +4192,15 @@ module.exports = { typeCalendar: "日历", calendarCreate: "创建日历", calendarUpdate: "更新", - calendarDelete: "删除", calendarTitleLabel: "标题", calendarTitlePlaceholder: "日历标题...", calendarStatusLabel: "状态", + calendarTypeLabel: "日历类型", calendarStatusOpen: "开放", calendarStatusClosed: "关闭", - calendarDeadlineLabel: "Deadline", + calendarDeadlineLabel: "截止日期", calendarTagsLabel: "标签", - calendarTagsPlaceholder: "标签1, 标签2...", + calendarTagsPlaceholder: "输入标签,用逗号分隔", calendarParticipantsLabel: "参与者", calendarCreated: "已创建", calendarJoin: "加入", @@ -3201,11 +4211,11 @@ module.exports = { calendarNotePlaceholder: "添加笔记...", calendarFirstDateLabel: "日期", calendarFirstNoteLabel: "备注", - calendarIntervalLabel: "Interval", + calendarIntervalLabel: "间隔", calendarIntervalNone: "不重复", - calendarIntervalWeekly: "Weekly", - calendarIntervalMonthly: "Monthly", - calendarIntervalYearly: "Yearly", + calendarIntervalWeekly: "每周", + calendarIntervalMonthly: "每月", + calendarIntervalYearly: "每年", calendarFormDescription: "描述", calendarNoDates: "尚未添加日期。", calendarNoNotes: "暂无笔记。", @@ -3213,6 +4223,8 @@ module.exports = { calendarsDescription: "在您的网络中发现和管理日历。", calendarMonthPrev: "← 上一月", calendarMonthNext: "下一月 →", + calendarMonthNames: "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月", + calendarDayInitials: "一,二,三,四,五,六,日", calendarAllSectionTitle: "日历", calendarRecentSectionTitle: "最近的日历", calendarFavoritesSectionTitle: "收藏", @@ -3222,9 +4234,9 @@ module.exports = { calendarCreateSectionTitle: "创建新日历", calendarUpdateSectionTitle: "更新日历", calendarAddFavorite: "添加到收藏", - calendarDeleteNote: "Delete", + calendarDeleteNote: "删除", calendarRemoveFavorite: "从收藏移除", - calendarSearchPlaceholder: "搜索日历...", + calendarSearchPlaceholder: "在日历中搜索...", calendarAddEntry: "添加条目", statsCalendar: "日历", statsCalendarDate: "日历日期", @@ -3236,6 +4248,146 @@ module.exports = { tribeContentAccessDenied: "访问被拒绝", tribeContentAccessDeniedMsg: "此内容属于一个部落。您必须是成员才能访问它。", tribeViewTribes: "查看部落", + tribeFilesEmpty: "暂无种子。", + filesLabel: "种子", + profileClearnetFilesLabel: "种子", + filesTitle: "种子", + filesDescription: "浏览和管理网络中的种子。", + fileAllSectionTitle: "种子", + fileMineSectionTitle: "我的种子", + fileRecentSectionTitle: "最近的种子", + fileTopSectionTitle: "热门种子", + fileFavoritesSectionTitle: "收藏", + fileFilterAll: "全部", + fileFilterMine: "我的", + fileFilterRecent: "最近", + fileFilterTop: "热门", + fileFilterFavorites: "收藏", + fileCreateSectionTitle: "上传种子", + fileUpdateSectionTitle: "更新种子", + fileCreateButton: "上传种子", + fileUpdateButton: "更新", + fileAddFavoriteButton: "添加到收藏", + fileRemoveFavoriteButton: "从收藏移除", + fileFileLabel: "选择文件(最大:75MB)", + calendarFilterRecent: "最近", + calendarFilterAll: "全部", + calendarFilterMine: "我的", + calendarFilterFavorites: "收藏", + calendarFilterOpen: "开放", + calendarFilterClosed: "已关闭", + padFilterRecent: "最近", + padFilterAll: "全部", + padFilterMine: "我的", + padFilterOpen: "开放", + padFilterClosed: "已关闭", + chatFilterRecent: "最近", + chatFilterAll: "全部", + chatFilterMine: "我的", + chatFilterFavorites: "收藏", + chatFilterOpen: "开放", + chatFilterClosed: "已关闭", + licenseLabel: "许可协议", + phoneTitle: "电话", + phoneDescription: "管理你的端到端加密视频通话。", + phoneUnavailable: "此设备上无法使用通话。", + phoneCallButton: "呼叫", + profileVisibilityPhone: "电话号码", + phoneNumberUnknown: "你的网络中没有人使用号码 {number}。", + phoneNumberChoose: "请确认你要拨打 {number} 联系的人:", + phoneAnswer: "接听", + phoneSpeaker: "扬声器", + phoneEarpiece: "听筒", + phoneReject: "拒绝", + phoneHangup: "挂断", + phoneMute: "静音", + phoneUnmute: "取消静音", + phoneCameraOn: "摄像头 开", + phoneCameraOff: "摄像头 关", + settingsPhoneVideo: "视频", + settingsPhoneVideoOn: "通话和房间中允许", + settingsPhoneVideoOff: "仅音频", + phoneSilence: "不再收听", + phoneUnsilence: "恢复收听", + phoneCalling: "正在呼叫", + phoneIsCalling: "正在呼叫你", + notifyNewPmFrom: "新私信,来自", + notifyNewPamFrom: "新 PaM,来自", + notifyUpdateLabel: "更新", + updateInstalledLabel: "已安装版本", + updateAvailableLabel: "可用版本", + updateChangesLabel: "更改", + updateFilesLabel: "文件", + updateOriginLabel: "来源", + phoneRecording: "正在录制 PaM,发给", + phoneLeaveMessage: "无人接听:请在提示音后留言。", + phoneLeaveMessageBusy: "对方正在通话中:请在提示音后留言。", + phoneLeaveMessageUnreachable: "无法联系到对方:请在提示音后留言。", + phoneConnecting: "正在连接", + phoneSendPam: "发送 PaM", + phoneCancel: "取消", + phoneSearchPlaceholder: "在电话中搜索...", + phonePamTitle: "音频录音", + phoneNew: "新", + phoneDelete: "删除", + phoneNoPams: "你没有音频录音。", + phoneHistoryTitle: "历史记录", + phoneLatestTitle: "最近通话", + phoneCreateButton: "新通话", + phoneLimitsHint: "每次通话最多 7 位居民;多于一位即为联合通话。", + phoneErrorPub: "不能呼叫 pub。", + phoneJointCall: "联合通话", + phoneWith: "对象", + phoneResult: "结果", + phoneWhen: "时间", + phoneDuration: "时长", + phoneOutgoing: "呼出", + phoneIncoming: "呼入", + phoneNoHistory: "暂无记录。", + phoneOutcomeEnded: "已接听", + phoneOutcomeMissed: "未接", + phoneOutcomeRejected: "已拒绝", + phoneOutcomeNoanswer: "无人接听", + phoneOutcomeCancelled: "已取消", + phoneOutcomePam: "已发送 PaM", + phoneOutcomeFailed: "失败", + phoneDialAdd: "添加", + phoneDialBack: "删除", + phoneDialClear: "清空", + phoneDialPlaceholder: "号码或 Oasis ID", + phoneDialInhabitants: "居民", + phoneDialRemove: "移除", + phoneOwnNumber: "这是你自己的号码。", + phoneSettingsDesc: "选择谁可以呼叫你。", + phoneDnd: "免打扰", + phoneOpen: "开放", + phoneRecipientRefuses: "你要呼叫的居民不接受来电。", + phoneRefusedNotice: "这些居民不接受来电,未被呼叫:", + fileEmptyError: "文件为空。请选择其他文件。", + fileTitleLabel: "标题", + fileTitlePlaceholder: "标题", + fileDescriptionLabel: "描述", + fileDescriptionPlaceholder: "描述", + fileTagsLabel: "标签", + fileTagsPlaceholder: "输入标签,用逗号分隔", + fileSizeLabel: "大小", + fileDownloadButton: "下载", + fileDetailsButton: "详情", + fileNoFile: "没有种子文件", + fileSearchPlaceholder: "在种子中搜索...", + fileSearchButton: "搜索", + fileSortRecent: "最新", + fileSortOldest: "最旧", + fileSortTop: "最多投票", + fileNoMatch: "没有匹配的种子。", + fileUpdatedAt: "已更新", + typeFile: "种子", + modulesFilesLabel: "文件", + modulesFilesDescription: "发现和管理文件的模块。", + tribeSectionFiles: "种子", + fileDescription: "描述", + fileDownload: "下载", + fileTypeLabel: "类型", torrentsTitle: "种子", torrentsDescription: "浏览和管理网络中的种子。", torrentAllSectionTitle: "种子", @@ -3261,12 +4413,35 @@ module.exports = { torrentDescriptionLabel: "描述", torrentDescriptionPlaceholder: "描述", torrentTagsLabel: "标签", - torrentTagsPlaceholder: "输入以逗号分隔的标签", + torrentTagsPlaceholder: "输入标签,用逗号分隔", torrentSizeLabel: "大小", - torrentDownloadButton: "DOWNLOAD IT!", + torrentFromContent: "生成种子", + torrentFromContentMissing: "该文件在本节点尚不可用,请稍后再试。", + torrentDownloads: "下载", + torrentFetchOasis: "在 Oasis 中下载", + torrentSaveFile: "保存文件", + torrentSeeds: "做种", + seedsLabel: "做种数", + seedAction: "支持", + torrentSourceFile: "文件", + torrentStateQueued: "排队中", + torrentStateProbing: "正在寻找做种者", + torrentStateDownloading: "下载中", + torrentStateDone: "已完成", + torrentStateFailed: "失败", + torrentStateNoSource: "暂无在线做种者,等待中", + torrentStateCancelled: "已取消", + torrentNoDownloads: "还没有下载。", + torrentOriginalSection: "原始文件", + torrentCancel: "取消", + torrentDownloadButton: "下载", + torrentOasisButton: "OASIS", + torrentExternalButton: "EXTERNAL", + torrentDetailsButton: "详情", torrentNoFile: "没有种子文件", noTorrents: "未找到种子。", - torrentSearchPlaceholder: "搜索标题、标签、作者...", + noFiles: "未找到种子。", + torrentSearchPlaceholder: "在种子中搜索...", torrentSearchButton: "搜索", torrentSortRecent: "最新", torrentSortOldest: "最旧", @@ -3274,10 +4449,12 @@ module.exports = { torrentNoMatch: "没有匹配的种子。", torrentUpdatedAt: "已更新", statsTorrent: "种子", + statsFile: "文件", typeTorrent: "种子", modulesTorrentsLabel: "种子", modulesTorrentsDescription: "发现和管理种子的模块。", favoritesFilterTorrents: "种子", + favoritesFilterFiles: "文件", favoritesFilterMarket: "市场", favoritesFilterEvents: "活动", favoritesFilterForum: "论坛", @@ -3291,15 +4468,17 @@ module.exports = { favoritesFilterShopProducts: "商店商品", tribeSectionTorrents: "种子", tribeCreateTorrent: "上传种子", + tribeCreateFile: "上传文件", settingsWishTitle: "愿望", - settingsWishDesc: "配置您的头像愿望。", - settingsWishWhole: "Multiverse", - settingsWishMutuals: "Only mutual-support", - settingsWishOnlyLan: "Only LAN", + settingsWishDesc: "决定你如何访问全部内容以及你在 Oasis 网络中的体验。", + settingsWishWhole: "多元宇宙", + settingsWishMutuals: "仅限互相支持", + settingsWishOnlyLan: "仅限局域网", + settingsWishLocal: "本地", settingsPmVisibilityTitle: "私信", settingsPmVisibilityDesc: "配置您希望在网络中的私信曝光级别。", - settingsPmVisibilityWhole: "Multiverse", - settingsPmVisibilityMutuals: "Only mutual-support", + settingsPmVisibilityWhole: "多元宇宙", + settingsPmVisibilityMutuals: "仅限互相支持", pmMutualNotice: "出站是 UX 保护。入站是关注过滤器。", inhabitantsPendingFollowsTitle: "待处理的支持请求", inhabitantsPendingAccept: "接受", @@ -3343,7 +4522,7 @@ module.exports = { tribeGovNoRevocations: "暂无任何撤销", tribeGovNoHistorical: "暂无任何记录", logsTitle: "日志", - logsDescription: "记录您在网络中的体验。", + logsDescription: "记录你个人经历的私人日记。", logsViewTitle: "日志", logsColumnType: "类型", logsView: "查看", @@ -3352,7 +4531,7 @@ module.exports = { logsCreateTitle: "创建条目", logsWriteButton: "撰写", logsTextPlaceholder: "描述您的体验...", - logsModeAIWritten: "AI-Assistant", + logsModeAIWritten: "AI 助手", logsModeManual: "手动", logsModeAI: "AI", logsColumnLog: "日志", @@ -3375,19 +4554,20 @@ module.exports = { logsSearchAnyType: "任意类型", logsSearchButton: "搜索", logsEmpty: "暂无条目。", + logsNothingNew: "没有可记录的新操作。", modulesLogsLabel: "日志", - modulesLogsDescription: "用于(通过 AI 助手)记录体验的模块。", + modulesLogsDescription: "用于记录你个人经历的私人日记模块。", statsLogsTitle: "日志", typeLog: "日志", larpTitle: "L.A.R.P.", larpDescription: "用于协作实验的实时角色扮演层。", larpNoHousesMatch: "没有匹配的家族。", - larpSearchPlaceholder: "搜索家族...", + larpSearchPlaceholder: "在L.A.R.P.中搜索...", larpAllHouses: "家族:", larpFilterRuling: "执政", larpFilterHouses: "家族", - larpFilterRules: "FAQ", + larpFilterRules: "常见问题", larpRulesTitle: "L.A.R.P. FAQ", larpRulesEntryText: "每位居民默认从 ACADEMIA 开始。在 ACADEMIA 中,从“加入家族”面板选择一个家族:这将打开其入会测试。回答 10 道问题并提交。若达到阈值(7/10),你将被自动接纳进入该家族;否则将被拒绝,留在 ACADEMIA 中。无论如何,系统会记录你的 OasisID 和尝试周期,并在 30 个周期的冷却期结束前阻止再次尝试。", larpRulesGovernanceText: "在其执政周期内,执政家族佩戴“执政”徽章,是唯一其墙面在“执政”标签下公开显示的家族。", @@ -3398,117 +4578,116 @@ module.exports = { larpPostPlaceholder: "这个家族想说什么?", larpPostSubmit: "发布", larpCycleLabel: "周期:", - audioBackToBcs: "Back to BCS", + audioBackToBcs: "返回 BCS", audioFilterBcs: "BCS", - audioTranscodeButton: "TRANSCODE", - audioTranscodeCompositionEmpty: "This audio does not include a stored blockchain composition.", - audioTranscodeDetailDescription: "Decode the embedded payload and the original blockchain composition map.", - audioTranscodeDetailTitle: "Transcode", - audioTranscodeStegoEmpty: "(none)", - audioTranscodeStegoMessage: "TEXT", - audioTranscodeStegoNotFound: "No steganographic payload could be decoded from this audio.", - audioTranscodeStegoOasisId: "By", - audioTranscodeStegoTimestamp: "Generated at", + audioTranscodeButton: "转码", + audioTranscodeCompositionEmpty: "此音频不包含已保存的区块链作曲。", + audioTranscodeDetailDescription: "解码嵌入的载荷和原始区块链作曲图。", + audioTranscodeDetailTitle: "转码", + audioTranscodeStegoEmpty: "(无)", + audioTranscodeStegoMessage: "文本", + audioTranscodeStegoNotFound: "无法从此音频中解码出隐写载荷。", + audioTranscodeStegoTimestamp: "生成时间", audioTranscodeStegoUnknown: "—", - bankExchangeEcoTaxAnnual: "ECOin Taxes (annual)", - bankExchangeEcoTaxMonthly: "ECOin Taxes (monthly)", - bankOverviewYourTaxes: "Your taxes", - bankRulesAlpha: "Alpha (max share of pub balance per epoch)", + bankExchangeEcoTaxAnnual: "ECOin 税费(年)", + bankExchangeEcoTaxMonthly: "ECOin 税费(月)", + bankOverviewYourTaxes: "税款合计", + bankRulesAlpha: "Alpha(每个周期 pub 余额的最大份额)", bankRulesArchTaxFormula: "ARCH Tax(user) = max(0, (newestBlockTs − userFirstBlockTs) / 86400000) × ecoinPerDayOfHistory", - bankRulesArchTaxNote: "ARCH Tax reflects the long-term cost of growing and maintaining the network archive: the older your data lives in the network, the larger your share of the maintenance cost.", - bankRulesArchTaxRate: "Rate", - bankRulesArchTaxTitle: "ARCH Tax", - bankRulesCapPerEpoch: "Cap per epoch (absolute)", - bankRulesCapUser: "Cap per user per epoch", - bankRulesCarbonFactor: "Carbon factor", + bankRulesArchTaxNote: "ARCH 税反映了扩展和维护网络档案的长期成本:你的数据在网络中存在得越久,你分担的维护成本就越大。", + bankRulesArchTaxRate: "税率", + bankRulesArchTaxTitle: "ARCH 税", + bankRulesCapPerEpoch: "每个周期上限(绝对值)", + bankRulesCapUser: "每位用户每个周期的上限", + bankRulesCarbonFactor: "碳系数", bankRulesChipFormula: "band = ratio / reducer, where ratio = sizeBytes / maxBlockBytes, reducer = 1 + log10(inhabitants). high ≥ 0.66, mid ≥ 0.33, otherwise low.", - bankRulesChipNote: "Each item's chip is colored relative to the largest message ever observed in the network and softened by the number of active inhabitants — a larger network distributes the load and pushes more items into the green band.", - bankRulesChipTitle: "ECO Tax chip color band", + bankRulesChipNote: "每个项目的标签颜色取决于它相对于网络中观察到的最大消息的大小,并按活跃居民数量进行平滑——更大的网络会分摊负载,让更多项目进入绿色区间。", + bankRulesChipTitle: "ECO 税标签颜色区间", bankRulesEcoTaxFormula: "ECO Tax(user) = (userBytes / 1 MiB) × 0.095 × ecoinPerGramCO2", - bankRulesEcoTaxNote: "ECO Tax is deducted from the gross UBI when the epoch allocation is computed. The deducted ECO are not redistributed in this epoch — they remain in the pub balance and feed the next epoch's pool through reserveMin / alpha caps.", - bankRulesEcoTaxRate: "Rate", - bankRulesEcoTaxTitle: "ECO Tax", - bankRulesEpochKind: "Epoch granularity", - bankRulesFloor: "Floor per user (gross)", - bankRulesGraceDays: "Claim grace period", + bankRulesEcoTaxNote: "在计算每个周期(epoch)的分配时,ECO 税从 UBI 总额中扣除。扣除的 ECO 不会在本周期重新分配——它们留在 pub 余额中,并通过 reserveMin / alpha 上限注入下一周期的资金池。", + bankRulesEcoTaxRate: "税率", + bankRulesEcoTaxTitle: "ECO 税", + bankRulesEpochKind: "周期粒度", + bankRulesFloor: "每位用户的保底(总额)", + bankRulesGraceDays: "领取宽限期", bankRulesKarmaFormula: "karma = max(0, round(scoreFromActions − carbonGramsForUser))", - bankRulesKarmaNote: "Karma is derived from your actions (posts, votes, parliament/courts participation, etc.) with a time-decay factor, minus the carbon grams generated by your feed.", - bankRulesKarmaTitle: "Karma (user engagement score)", + bankRulesKarmaNote: "业力来自你的行为(发帖、投票、参与议会/法庭等),带有随时间衰减的系数,并减去你的动态所产生的碳克数。", + bankRulesKarmaTitle: "业力(用户参与度评分)", bankRulesNetFormula: "net_UBI(user) = max(0, gross_UBI − ECO Tax)", bankRulesPoolFormula: "Pool = min(pubBal − reserveMin, capPerEpoch, alpha × pubBal)", - bankRulesPoolTitle: "Monthly pool", - bankRulesReserveMin: "Reserve minimum (kept in pub balance)", + bankRulesPoolTitle: "月度资金池", + bankRulesReserveMin: "最低储备(保留在 pub 余额中)", bankRulesShareFormula: "gross_UBI(user) = clamp(Pool × w / Σw, floor_user, cap_user_epoch)", - bankRulesShareTitle: "Per-user share", + bankRulesShareTitle: "每位用户的份额", bankRulesWeightFormula: "w = clamp(1 + karma/100, w_min, w_max)", - bankRulesWMax: "Maximum weight (w_max)", - bankRulesWMin: "Minimum weight (w_min)", - bankTaxes: "Taxes", - bankTaxesAnnualEcoin: "ECO Tax (annual)", - bankTaxesArchTaxAnnual: "ARCH Tax (annual)", - bankTaxesArchTaxFirstBlock: "Your first block age", - bankTaxesArchTaxFootprintNote: "ARCH Tax reflects the cost of growing and maintaining the network archive. It is computed from the time gap between your first published block and the newest block in the network — the longer your history has lived in the archive, the larger your share of the maintenance cost.", - bankTaxesArchTaxLifetime: "ARCH Tax (lifetime)", - bankTaxesArchTaxMonthly: "ARCH Tax (monthly)", - bankTaxesArchTaxNoteBold: "reducing the ARCH tax directly", - bankTaxesArchTaxNoteIntro: "Your ARCH tax is deducted from the surplus the network generates on top of your UBI; the base UBI value is set as an immovable minimum. The tax is never deducted from the fixed amount that corresponds to each inhabitant as UBI. The deducted funds feed the wealth-redistribution algorithm and are channeled back to other inhabitants via their UBI claims. Newer inhabitants and those specifically dedicated to archival, replication and maintenance tasks will likely have some task dedicated to keeping the network archive healthy on your behalf. Or they may dedicate themselves to ", - bankTaxesArchTaxNoteOutro: " through pruning agreements, deduplication of redundant data, and shared maintenance infrastructure, and their ongoing contribution may be required by networking consensus.", - bankTaxesArchTaxRate: "Rate", - bankTaxesArchTaxTitle: "ARCH Tax", - bankTaxesArchTaxUserAmount: "Your ARCH tax (price to return)", - bankTaxesEcoTaxLifetime: "ECO Tax (lifetime)", - bankTaxesEcoTaxTitle: "ECO Tax", - bankTaxesLookupAuthor: "Author", - bankTaxesLookupButton: "Inspect", - bankTaxesLookupCarbon: "Carbon footprint", - bankTaxesLookupEcoin: "ECO Tax", - bankTaxesLookupNotFound: "No block found in your local feed with that ID.", - bankTaxesLookupPlaceholder: "Block ID (e.g. %abc...=.sha256)", - bankTaxesLookupSize: "Size", - bankTaxesLookupTitle: "Inspect a block", - bankTaxesLookupType: "Type", - bankTaxesMonthlyEcoin: "ECO Tax (monthly)", - bankTaxesRate: "Rate", - bankTaxesSpan: "Sampling span", - bankTaxesTotalAnnual: "Total (annual)", - bankTaxesTotalBlocks: "Total blocks", - bankTaxesTotalBytes: "Total bytes", - bankTaxesTotalCarbon: "Total carbon", - bankTaxesTotalEcoin: "Total ECO tax (lifetime)", - bankTaxesTotalLifetime: "Total (lifetime)", - bankTaxesTotalMonthly: "Total (monthly)", - bankTaxesTypesApply: "Set my taxes", - bankTaxesTypesLabel: "Select which taxes you want to pay", - bankTaxesUserAmount: "Your ECO tax (price to return)", - bankTaxesUserNoteChips: "The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.", - bankTaxesUserNoteOtherParams: "ECO Tax is currently derived from the carbon footprint of each block, but it may incorporate other parameters over time — energy spent on replication, redundant storage across peers, blob bandwidth, computational cost of decryption, mining footprint of associated transactions, or any other measurable load the network agrees to value.", - blockchainBackToBlockexplorer: "Back to blockexplorer", - blockchainBlockNotAvailable: "This block is not available in your local feed. It may belong to a peer you are not yet replicating from.", - ecoTaxLabel: "ECO Tax", - gamesHallDate: "Date", - gamesNewRecord: "New Record", - indexingEditProfileLink: "Set up my avatar", - indexingMessage: "Oasis is trying to syncronize a huge network of inhabitants. Just wait!", - indexingRefreshNote: "This page refreshes every 10 seconds.", - indexingTitle: "Synchronizing", - melodyAllEmpty: "No BCS compositions from other inhabitants yet.", - melodyByLabel: "By", - melodyNoteUnavailable: "block not in your local feed", - melodyUploadBcsButton: "Publish", - melodyUploadBcsNameNote: "The audio will be published with the auto-generated name", - melodyUploadBcsTitle: "Publish", - melodyUploadStegoLabel: "Hidden message (steganography)", - melodyUploadStegoMaxLabel: "max 280 characters", - melodyUploadStegoPlaceholder: "Optional.", - padsNoItems: "No pads found.", - padVersionHistory: "Version History", - padVersionView: "View", - statsChat: "Chats", - statsChatMessage: "Chat messages", - statsGameScore: "Gaming", - statsPad: "Pads", - statsPadEntry: "Pad entries", - statsTombstoneEmpty: "No tombstones in the network yet.", + bankRulesWMax: "最大权重 (w_max)", + bankRulesWMin: "最小权重 (w_min)", + bankTaxes: "税费", + bankTaxesAnnualEcoin: "ECO 税(年)", + bankTaxesArchTaxAnnual: "ARCH 税(年)", + bankTaxesArchTaxFirstBlock: "你的首个区块的时长", + bankTaxesArchTaxFootprintNote: "ARCH 税反映了扩展和维护网络档案的成本。它根据你发布的第一个区块与网络中最新区块之间的时间差计算——你的历史在档案中存在得越久,你分担的维护成本就越大。", + bankTaxesArchTaxLifetime: "ARCH 税(累计)", + bankTaxesArchTaxMonthly: "ARCH 税(月)", + bankTaxesArchTaxNoteBold: "直接降低 ARCH 税", + bankTaxesArchTaxNoteIntro: "你的 ARCH 税从网络在你的 UBI 之上产生的盈余中扣除;UBI 的基础值被设定为不可动摇的最低值。该税绝不会从每位居民作为 UBI 应得的固定金额中扣除。扣除的资金会注入财富再分配算法,并通过其他居民的 UBI 领取重新流向他们。较新的居民以及专门从事归档、复制和维护任务的居民,很可能会有专门的任务代你保持网络档案的健康。或者,他们可以致力于", + bankTaxesArchTaxNoteOutro: ",方式包括修剪协议、冗余数据去重以及共享维护基础设施,而他们的持续贡献可能会由网络共识提出要求。", + bankTaxesArchTaxRate: "税率", + bankTaxesArchTaxTitle: "ARCH 税", + bankTaxesArchTaxUserAmount: "你的 ARCH 税(需返还的金额)", + bankTaxesEcoTaxLifetime: "ECO 税(累计)", + bankTaxesEcoTaxTitle: "ECO 税", + bankTaxesLookupAuthor: "作者", + bankTaxesLookupButton: "检查", + bankTaxesLookupCarbon: "碳足迹", + bankTaxesLookupEcoin: "ECO 税", + bankTaxesLookupNotFound: "在你的本地动态中未找到该 ID 的区块。", + bankTaxesLookupPlaceholder: "区块 ID(例如 %abc...=.sha256)", + bankTaxesLookupSize: "大小", + bankTaxesLookupTitle: "检查区块", + bankTaxesLookupType: "类型", + bankTaxesMonthlyEcoin: "ECO 税(月)", + bankTaxesRate: "税率", + bankTaxesSpan: "采样区间", + bankTaxesTotalAnnual: "总计(年)", + bankTaxesTotalBlocks: "区块总数", + bankTaxesTotalBytes: "总字节数", + bankTaxesTotalCarbon: "碳总量", + bankTaxesTotalEcoin: "ECO 税总计(累计)", + bankTaxesTotalLifetime: "总计(累计)", + bankTaxesTotalMonthly: "总计(月)", + bankTaxesTypesApply: "设置我的税", + bankTaxesTypesLabel: "选择你要缴纳的税种", + bankTaxesUserAmount: "你的 ECO 税(需返还的金额)", + bankTaxesUserNoteChips: "每个项目旁显示的 ECO 税标签会根据该项目相对于网络中见过的最大区块的大小改变颜色(绿 / 黄 / 红),并按活跃居民数量进行平滑——更大的太阳朋克网络会分摊负载,让更多项目进入绿色区间。", + bankTaxesUserNoteOtherParams: "ECO 税目前根据每个区块的碳足迹计算,但随着时间推移可能纳入其他参数——复制所消耗的能量、节点间的冗余存储、blob 带宽、解密的计算成本、相关交易的挖矿足迹,或网络同意计价的任何其他可衡量负载。", + blockchainBackToBlockexplorer: "返回区块浏览器", + blockchainBlockNotAvailable: "此区块在你的本地动态中不可用。它可能属于你尚未从其复制的节点。", + ecoTaxLabel: "ECO 税", + gamesHallDate: "日期", + gamesNewRecord: "新纪录!", + indexingRefreshNote: "本页面每 10 秒刷新一次。", + indexingMessage: "Oasis 正在同步分布式节点网络。请稍候。", + indexingTitle: "正在同步", + melodyAllEmpty: "暂无其他居民的 BCS 作品。", + melodyNoteUnavailable: "该区块不在你的本地动态中", + melodyUploadBcsButton: "发布", + melodyUploadBcsNameNote: "音频将以自动生成的名称发布", + melodyUploadBcsTitle: "发布", + melodyUploadStegoLabel: "隐藏消息(隐写术)", + melodyUploadStegoMaxLabel: "最多 280 个字符", + melodyUploadStegoPlaceholder: "可选。", + padsNoItems: "未找到协作板。", + padVersionHistory: "版本历史", + padAttributionView: "按作者", + padVersionView: "查看", + statsChat: "聊天", + statsChatMessage: "聊天消息", + statsGameScore: "游戏", + statsWiki: "维基页面", + statsPad: "协作板", + statsPadEntry: "协作板条目", + statsTombstoneEmpty: "网络中暂无已删除记录(tombstone)。", typeGameScore: "GAME SCORE", bankTaxesLookupNote: "输入区块 ID 在本地数据流中查询。", bankTaxesUserNoteChipsClick: "点击任何标签以在 blockexplorer 中检查其区块。", @@ -3524,6 +4703,14 @@ module.exports = { settingsReplicationTitle: "复制", settingsReplicationDesc: "配置在复制内容时,你的节点从自己的信息流出发跟随的跳数。", settingsReplicationHopsLabel: "跳数", + settingsBlobCacheTitle: "媒体缓存", + settingsBlobCacheDesc: "其他居民的内容会保存在本设备上。请选择它最多可占用的空间。", + settingsBlobCacheUsage: "当前占用", + settingsBlobCacheFiles: "个文件", + settingsBlobCacheLimit: "上限", + settingsBlobCacheUnlimited: "不限", + settingsBlobCacheCleanNow: "立即清理", + settingsBlobCacheCleaned: "已删除 {n} 个文件,释放 {size}。", aiApproveTagsLabel: "标签(逗号分隔)", aiApproveTagsPlaceholder: "例如 oasis、治理、生态", aiApproveRatingLabel: "评分", @@ -3551,8 +4738,6 @@ module.exports = { larpInviteBannerHint: "将此代码分享给 ACADEMIA 中的某人。30 个周期后过期,或使用一次后失效。", larpTestRankingTitle: "各家族分数", invitesHousesTitle: "L.A.R.P.", - invitesHouseInviteCodePlaceholder: "家族邀请码", - invitesHouseJoinButton: "加入家族", larpLastAttemptTitle: "你最近的尝试", larpLastAttemptHouse: "家族", larpLastAttemptWhen: "时间", @@ -3570,14 +4755,14 @@ module.exports = { larpBadgeYou: "你", larpBadgeRuling: "执政", modulesLarpLabel: "L.A.R.P.", - modulesLarpDescription: "Oasis 实时角色扮演层(9 个家族)的模块。", + modulesLarpDescription: "用于实景角色扮演层的模块。", larpProfileQ1: "面对复杂问题时,你首先做什么?", larpProfileQ1O1: "与他人交谈以达成共识", larpProfileQ1O2: "构建原型进行测试", larpProfileQ1O3: "研究相关文献", larpProfileQ1O4: "颠覆产生它的系统", larpProfileQ2: "什么赋予你日常工作意义?", - larpProfileQ2O1: "保护我所爱的人", + larpProfileQ2O1: "保护我所爱的居民", larpProfileQ2O2: "创造有形之物", larpProfileQ2O3: "疗愈或滋养生命", larpProfileQ2O4: "雕琢文字与想法", @@ -3644,6 +4829,51 @@ module.exports = { uxModeTitle: "UX", uxModeDescription: "选择你想要的 UX 导航模式。", uxModeMenus: "块", + uxModeChats: "对话", + uxModeFeed: "微博客", + feedTrendingTitle: "热门标签", + feedActiveTitle: "活跃居民", + cnBrandSub: "自由 · P2P · 联邦", + cnSyncedPeers: "已同步节点", + cnLicense: "许可证", + cnNotAccessible: "该内容目前无法访问。", + cnUntitled: "无标题", + cnSearchPlaceholder: "搜索…", + cnHubTitle: "Clearnet 中心", + cnHubDescription: "由本 Oasis 网络的居民分享的公开内容。", + cnHubEmpty: "尚未有公开内容分享到 Clearnet。", + cnPublicContent: "内容", + cnTribesContent: "部落", + cnNetworkContent: "网络", + cnCategoryEmpty: "此分类下没有内容。", + cnInhabitantEmpty: "该居民尚未在 Clearnet 发布内容。", + cnPrevPage: "← 上一页", + cnNextPage: "下一页 →", + cnPageOf: "第 {page} 页,共 {pages} 页", + cnMoreInfo: "更多信息 →", + cnOpenCourse: "开放课程", + cnAuction: "拍卖", + cnExchange: "交换", + cnRssDescription: "由本中心的居民发布", + cnKindAudio: "音频", + cnKindBlog: "博客", + cnKindBookmark: "书签", + cnKindDocument: "文档", + cnKindEvent: "活动", + cnKindFeed: "动态", + cnKindImage: "图片", + cnKindJob: "职位", + cnKindMarket: "市场", + cnKindPodcast: "播客", + cnKindProject: "项目", + cnKindCourse: "课程", + cnKindShop: "商店", + cnKindTorrent: "种子", + cnKindFile: "文件", + cnKindVideo: "视频", + cnKindWiki: "维基", + cnKindRoom: "房间", + cnKindMap: "地图", uxModeAINav: "AI", } diff --git a/src/client/gui.js b/src/client/gui.js index dfe02ae..bc4374b 100644 --- a/src/client/gui.js +++ b/src/client/gui.js @@ -10,10 +10,13 @@ const { printMetadata } = require('../server/ssb_metadata'); const updateFlagPath = path.join(__dirname, "../server/.update_required"); let internalSSB = null; -try { - const { server } = require('../server/SSB_server'); - internalSSB = server; -} catch {} +let internalOpen = null; +if (!process.env.OASIS_TEST) { + try { + const SSB_server = require('../server/SSB_server'); + internalOpen = () => SSB_server.open().then((srv) => { internalSSB = srv; return srv; }); + } catch {} +} if (process.env.OASIS_TEST) { ssbConfig.path = fs.mkdtempSync(path.join(os.tmpdir(), "oasis-")); @@ -24,10 +27,33 @@ const socketPath = path.join(ssbConfig.path, "socket"); const publicInteger = ssbConfig.keys.public.replace(".ed25519", ""); const remote = `unix:${socketPath}~noauth:${publicInteger}`; +const windowLogStream = (ssb) => { + if (!ssb || ssb._logStreamWindowed || typeof ssb.createLogStream !== "function") return ssb; + const pull = require("../server/node_modules/pull-stream"); + const defer = require("../server/node_modules/pull-defer"); + const orig = ssb.createLogStream; + ssb.createLogStream = function (opts) { + const o = opts || {}; + const plain = o.limit && !o.reverse && !o.live && !o.raw && o.old === undefined && !o.gt && !o.lt; + if (!plain) return orig.call(ssb, opts); + const src = defer.source(); + pull( + orig.call(ssb, { ...o, reverse: true }), + pull.collect((err, msgs) => { + if (err) return src.abort(err); + src.resolve(pull.values(msgs.reverse())); + }) + ); + return src; + }; + ssb._logStreamWindowed = true; + return ssb; +}; + const connect = (options) => new Promise((resolve, reject) => { ssbClient(process.env.OASIS_TEST ? ssbConfig.keys : null, options) - .then(resolve) + .then((ssb) => resolve(windowLogStream(ssb))) .catch(reject); }); @@ -89,8 +115,15 @@ module.exports = ({ offline, port = 3000, host = 'localhost', isPublic = false } return new Promise((resolve, reject) => { if (internalSSB) { const { printMetadata, colors } = require('../server/ssb_metadata'); - printMetadata('OASIS GUI', colors.yellow, port, host, offline, isPublic); - return resolve(internalSSB); + printMetadata('OASIS UX', colors.yellow, port, host, offline, isPublic); + return resolve(windowLogStream(internalSSB)); + } + if (internalOpen) { + return internalOpen().then((srv) => { + const { printMetadata, colors } = require('../server/ssb_metadata'); + printMetadata('OASIS UX', colors.yellow, port, host, offline, isPublic); + resolve(windowLogStream(srv)); + }).catch(reject); } if (clientHandle && clientHandle.closed === false) { @@ -105,7 +138,7 @@ module.exports = ({ offline, port = 3000, host = 'localhost', isPublic = false } reject(new Error("Closing Oasis")); } else { const { printMetadata, colors } = require('../server/ssb_metadata'); - printMetadata('OASIS GUI', colors.yellow, port, host, offline, isPublic); + printMetadata('OASIS UX', colors.yellow, port, host, offline, isPublic); resolve(ssb); } }).catch(reject); diff --git a/src/client/middleware.js b/src/client/middleware.js index 86147d0..a804895 100644 --- a/src/client/middleware.js +++ b/src/client/middleware.js @@ -8,20 +8,21 @@ const mount = require(path.join(__dirname, "../server/node_modules/koa-mount")); function obfuscateClearnetHtml(html) { if (typeof html !== 'string' || html.length === 0) return html; const preserve = []; + const tag = require('crypto').randomBytes(12).toString('hex'); const stash = (re) => { html = html.replace(re, (m) => { preserve.push(m); - return `${preserve.length - 1}`; + return `\u0001${tag}:${preserve.length - 1}\u0001`; }); }; stash(//gi); stash(//gi); stash(//gi); html = html.replace(//g, ''); - html = html.replace(/>[\s\n\r\t]+<'); + html = html.replace(/>[ \t]*[\r\n][\s]*<'); html = html.replace(/[ \t]{2,}/g, ' '); html = html.replace(/[\r\n]+/g, ''); - html = html.replace(/(\d+)/g, (_, i) => preserve[Number(i)] || ''); + html = html.replace(new RegExp(`\\u0001${tag}:(\\d+)\\u0001`, 'g'), (_, i) => preserve[Number(i)] || ''); return html; } @@ -42,42 +43,17 @@ const collectLocalIPs = () => { module.exports = ({ host, port, middleware, allowHost }) => { const assets = new Koa() - assets.use(koaStatic(join(__dirname, "..", "client", "assets"), { maxage: 60 * 60 * 1000 })); + assets.use(async (ctx, next) => { + ctx.set("Cache-Control", ctx.query && ctx.query.v ? "public, max-age=31536000, immutable" : "public, max-age=3600"); + await next(); + }); + assets.use(koaStatic(join(__dirname, "..", "client", "assets"))); const app = new Koa(); const validHosts = []; - const isClearnetPath = (request) => { - const url = String(request.url || ''); - return url === '/c' || url.startsWith('/c/') || url.startsWith('/c?'); - }; - - const isValidRequest = (request) => { - if (isClearnetPath(request)) return request.method === 'GET'; - if (validHosts.includes(request.hostname) !== true) { - return false; - } - if (request.method !== "GET") { - if (request.header.referer == null) { - return false; - } - - try { - const refererUrl = new URL(request.header.referer); - if (validHosts.includes(refererUrl.hostname) !== true) { - return false; - } - - if (refererUrl.pathname.startsWith("/blob/")) { - return false; - } - } catch (e) { - return false; - } - } - - return true; - }; + const { isClearnetPath, isTrustedRequest, buildCsp } = require(path.join(__dirname, "..", "backend", "request_guards")); + const isValidRequest = (request) => isTrustedRequest(request, validHosts); const httpDebug = process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true'; @@ -98,17 +74,21 @@ module.exports = ({ host, port, middleware, allowHost }) => { }); app.use(mount("/assets", assets)); + app.use(mount("/c/assets", assets)); - const maptiles = new Koa(); - maptiles.use(koaStatic(join(__dirname, "..", "maps", "tiles"))); - app.use(mount("/maptiles", maptiles)); - - const mapcache = new Koa(); - mapcache.use(koaStatic(join(__dirname, "..", "maps", "cache"))); - app.use(mount("/mapcache", mapcache)); - + const mapTiles = new Koa(); + mapTiles.use(koaStatic(join(__dirname, "..", "maps", "tiles"), { maxage: 30 * 24 * 60 * 60 * 1000, immutable: true })); + app.use(mount("/maptiles", mapTiles)); + app.use(mount("/c/maptiles", mapTiles)); const gamesStatic = new Koa(); + gamesStatic.use(async (ctx, next) => { + ctx.set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' data: blob: mediastream:; connect-src 'none'; form-action 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'self'"); + ctx.set("X-Content-Type-Options", "nosniff"); + ctx.set("X-Frame-Options", "SAMEORIGIN"); + ctx.set("Referrer-Policy", "same-origin"); + await next(); + }); gamesStatic.use(koaStatic(join(__dirname, "..", "games"))); app.use(mount("/game-assets", gamesStatic)); @@ -120,34 +100,7 @@ module.exports = ({ host, port, middleware, allowHost }) => { if (httpDebug) console.log(`[http] ${ctx.method} ${ctx.path}`); const isClearnet = isClearnetPath(ctx.request); - const csp = isClearnet - ? [ - "default-src 'self'", - "script-src 'none'", - "style-src 'self' 'unsafe-inline'", - "img-src 'self' data:", - "media-src 'self' blob:", - "connect-src 'self'", - "form-action 'self'", - "object-src 'none'", - "base-uri 'none'", - "frame-ancestors 'none'" - ].join("; ") - : [ - "default-src 'self'", - "script-src 'self' http://localhost:3000/js", - "style-src 'self'", - "img-src 'self'", - "media-src 'self' blob:", - "worker-src 'self' blob:", - "frame-src 'self'", - "form-action 'self'", - "object-src 'none'", - "base-uri 'none'", - "frame-ancestors 'none'" - ].join("; "); - - ctx.set("Content-Security-Policy", csp); + ctx.set("Content-Security-Policy", buildCsp(isClearnet, { frames: ctx.method === "GET" && /^\/games\/[a-z0-9]+\/?$/.test(ctx.path) })); ctx.set("X-Frame-Options", "SAMEORIGIN"); ctx.set("X-Content-Type-Options", "nosniff"); @@ -155,12 +108,10 @@ module.exports = ({ host, port, middleware, allowHost }) => { ctx.set("Referrer-Policy", "same-origin"); ctx.set("Permissions-Policy", "speaker=(self)"); - const validHostsString = validHosts.join(" or "); - ctx.assert( isValidRequest(ctx.request), 400, - `Request must be addressed to ${validHostsString} and non-GET requests must contain non-blob referer.` + "Request must be addressed to this node and non-GET requests must come from its own pages." ); await next(); @@ -173,13 +124,10 @@ module.exports = ({ host, port, middleware, allowHost }) => { } }); - // pdf viewer - const pdfjsPath = path.join(__dirname, '../server/node_modules/pdfjs-dist/build/pdf.min.js'); - app.use(koaStatic(pdfjsPath)); - middleware.forEach((m) => app.use(m)); - const server = app.listen({ host, port }); + const server = require("http").createServer({ maxHeaderSize: 256 * 1024 }, app.callback()).listen({ host, port }); + server.on("listening", () => { const address = server.address(); diff --git a/src/client/public/docs/ecoin.md b/src/client/public/docs/ecoin.md deleted file mode 100644 index 6449f16..0000000 --- a/src/client/public/docs/ecoin.md +++ /dev/null @@ -1,115 +0,0 @@ - -ECOin - Copyright (c) - 2014/2025 - GPLv3 - epsylon@riseup.net (https://ecoin.03c8.net) - -=========================================== -# SOURCES for P2P Crypto-Currency (ECOin) # -=========================================== - -Testing machine is: Debian GNU/Linux 12 (bookworm) (x86_64). - -All of the commands should be executed in a shell. - ------------------------------- - -(0.) Clone the github tree to get the source code: - - + Official: - - git clone http://code.03c8.net/epsylon/ecoin - - + Mirror: - - git clone https://github.com/epsylon/ecoin - ------------------------------- - -=================================================== -# SERVER -ecoind- for P2P Crypto-Currency (ECOin) # -=================================================== - -(0.) Version libraries: - - - Libboost -> source code 1.68 provided at: src/boost_1_68_0 - -(1.) Install dependencies: - - sudo apt-get install build-essential libssl-dev libssl3 libdb5.3-dev libdb5.3++-dev libleveldb-dev miniupnpc libminiupnpc-dev - - + Optionally install qrencode (and set USE_QRCODE=1): - - sudo apt-get install libqrencode-dev - -(2.) Now you should be able to build ecoind: - - cd src/ - make -f makefile.linux USE_UPNP=- USE_IPV6=- - strip ecoind - - An executable named 'ecoind' will be built. - - Now you can launch: ./ecoind to run your ECOin server. - ------------------------------- - -============================================ -# WALLET for P2P Crypto-Currency (ECOin) # -============================================ - -(0.) Version libraries: - - - Libboost -> source code 1.68 provided at: src/boost_1_68_0 - -(1.) First, make sure that the required packages for Qt5 development (an the others required for building the daemon) are installed: - - sudo apt-get install qt5-qmake qtbase5-dev build-essential libssl-dev libssl3 libdb5.3-dev libdb5.3++-dev libleveldb-dev miniupnpc libminiupnpc-dev - - + Optionally install qrencode (and set USE_QRCODE=1): - - sudo apt-get install libqrencode-dev - -(2.) Then execute the following: - - qmake USE_UPNP=- USE_IPV6=- - make - - An executable named 'ecoin-qt' will be built. - - Now you can launch: ./ecoin-qt to run your ECOin wallet/GUI. - ------------------------------- -======================================== -+ CPU MINER for ECOin (Unix/GNU-Linux) # -======================================== - -See doc/MINING.txt for detailed instructions on running /ecoin-miner/ on different platforms. - - + GNU/Linux: - - cd miner/ - sh build.sh - - An executable named 'cpuminer' will be built. - - Now you can launch: ./cpuminer to run your ECOin PoW miner. - -====================================== - -For a list of command-line options: - - ./ecoind --help - -To start the ECOin daemon: - - ./ecoind -daemon - -For ECOin-QT Wallet - - ./ecoin-qt - -For debugging: - - tail -f /home/$USER/.ecoin/debug.log - -====================================== - - diff --git a/src/client/public/js/pdf-viewer.js b/src/client/public/js/pdf-viewer.js index 11265ff..5dd09a8 100644 --- a/src/client/public/js/pdf-viewer.js +++ b/src/client/public/js/pdf-viewer.js @@ -1,32 +1,68 @@ -document.addEventListener('DOMContentLoaded', () => { - if (typeof pdfjsLib === 'undefined') return; - pdfjsLib.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.min.mjs'; +const showPdfFallback = () => { + document.querySelectorAll('.pdf-viewer-container').forEach(container => { + if (container.dataset.pdfReady) return; + container.dataset.pdfReady = '1'; + container.textContent = ''; + const url = container.getAttribute('data-pdf-url'); + if (url) { + const link = document.createElement('a'); + link.href = url; + link.textContent = '📄 PDF'; + container.appendChild(link); + } + }); +}; + +const initPdfViewers = async () => { + let pdfjsLib; + try { + pdfjsLib = await import('/js/pdf.min.mjs?v=102'); + pdfjsLib.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.min.mjs?v=102'; + } catch (err) { + showPdfFallback(); + return; + } document.querySelectorAll('.pdf-viewer-container').forEach(async container => { const pdfUrl = container.getAttribute('data-pdf-url'); - if (!pdfUrl) return; + if (!pdfUrl || container.dataset.pdfReady) return; + container.dataset.pdfReady = '1'; + + let pdf; + try { + pdf = await pdfjsLib.getDocument(pdfUrl).promise; + } catch (err) { + container.textContent = ''; + const fallback = document.createElement('a'); + fallback.href = pdfUrl; + fallback.textContent = '📄 PDF'; + container.appendChild(fallback); + return; + } - const pdf = await pdfjsLib.getDocument(pdfUrl).promise; let currentPage = 1; let scale = 1.5; let rotation = 0; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); + const canvasWrap = document.createElement('div'); + canvasWrap.className = 'pdf-canvas-wrap'; + canvasWrap.appendChild(canvas); container.innerHTML = ''; - container.appendChild(canvas); + container.appendChild(canvasWrap); const controls = document.createElement('div'); controls.className = 'pdf-controls'; controls.innerHTML = ` - - - - - - - - + + + + + + + + `; container.appendChild(controls); @@ -48,29 +84,48 @@ document.addEventListener('DOMContentLoaded', () => { } }; + try { + const firstPage = await pdf.getPage(1); + const baseViewport = firstPage.getViewport({ scale: 1, rotation: 0 }); + const avail = canvasWrap.clientWidth || container.clientWidth; + if (avail > 0 && baseViewport.width > 0) { + scale = Math.min(1.5, Math.max(0.5, (avail - 40) / baseViewport.width)); + } + } catch (e) {} + renderPage(currentPage); - controls.querySelector('#prev').onclick = () => goToPage(-1); - controls.querySelector('#next').onclick = () => goToPage(1); - controls.querySelector('#zoomIn').onclick = () => { scale += 0.2; renderPage(currentPage); }; - controls.querySelector('#zoomOut').onclick = () => { scale = Math.max(0.5, scale - 0.2); renderPage(currentPage); }; - controls.querySelector('#rotate').onclick = () => { rotation = (rotation + 90) % 360; renderPage(currentPage); }; - controls.querySelector('#download').onclick = () => { + controls.querySelector('.pdf-prev').onclick = () => goToPage(-1); + controls.querySelector('.pdf-next').onclick = () => goToPage(1); + controls.querySelector('.pdf-zoom-in').onclick = () => { scale += 0.2; renderPage(currentPage); }; + controls.querySelector('.pdf-zoom-out').onclick = () => { scale = Math.max(0.5, scale - 0.2); renderPage(currentPage); }; + controls.querySelector('.pdf-rotate').onclick = () => { rotation = (rotation + 90) % 360; renderPage(currentPage); }; + controls.querySelector('.pdf-download').onclick = () => { const a = document.createElement('a'); a.href = pdfUrl; a.download = 'document.pdf'; a.click(); }; - controls.querySelector('#fullscreen').onclick = () => { - if (canvas.requestFullscreen) canvas.requestFullscreen(); - else if (canvas.webkitRequestFullscreen) canvas.webkitRequestFullscreen(); - else if (canvas.mozRequestFullScreen) canvas.mozRequestFullScreen(); - else if (canvas.msRequestFullscreen) canvas.msRequestFullscreen(); + controls.querySelector('.pdf-fullscreen').onclick = async () => { + const maximized = container.classList.toggle('pdf-maximized'); + document.body.classList.toggle('pdf-maximized-body', maximized); + try { + const p1 = await pdf.getPage(currentPage); + const vw1 = p1.getViewport({ scale: 1, rotation }); + const avail = Math.max(260, (maximized ? window.innerWidth : (canvasWrap.clientWidth || container.clientWidth || 360)) - 24); + scale = maximized ? avail / vw1.width : Math.min(1.5, avail / vw1.width); + await renderPage(currentPage); + } catch (_) {} }; - controls.querySelector('#metadata').onclick = async () => { + controls.querySelector('.pdf-metadata').onclick = async () => { const info = await pdf.getMetadata(); alert(`Title: ${info.info.Title || 'N/A'}\nAuthor: ${info.info.Author || 'N/A'}\nPDF Producer: ${info.info.Producer || 'N/A'}`); }; }); -}); +}; +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initPdfViewers); +} else { + initPdfViewers(); +} diff --git a/src/client/public/js/pdf.min.mjs b/src/client/public/js/pdf.min.mjs index 07dd89e..3b3c62f 100644 --- a/src/client/public/js/pdf.min.mjs +++ b/src/client/public/js/pdf.min.mjs @@ -19,4 +19,11 @@ * @licend The above is the entire license notice for the * JavaScript code in this page */ -const t=!("object"!=typeof process||process+""!="[object process]"||process.versions.nw||process.versions.electron&&process.type&&"browser"!==process.type),e=[.001,0,0,.001,0,0],i=1.35,s=1,n=2,a=4,r=16,o=32,l=64,h=128,d=256,c={DISABLE:0,ENABLE:1,ENABLE_FORMS:2,ENABLE_STORAGE:3},u={DISABLE:-1,NONE:0,FREETEXT:3,HIGHLIGHT:9,STAMP:13,INK:15,SIGNATURE:101},p={RESIZE:1,CREATE:2,FREETEXT_SIZE:11,FREETEXT_COLOR:12,FREETEXT_OPACITY:13,INK_COLOR:21,INK_THICKNESS:22,INK_OPACITY:23,HIGHLIGHT_COLOR:31,HIGHLIGHT_DEFAULT_COLOR:32,HIGHLIGHT_THICKNESS:33,HIGHLIGHT_FREE:34,HIGHLIGHT_SHOW_ALL:35,DRAW_STEP:41},g={PRINT:4,MODIFY_CONTENTS:8,COPY:16,MODIFY_ANNOTATIONS:32,FILL_INTERACTIVE_FORMS:256,COPY_FOR_ACCESSIBILITY:512,ASSEMBLE:1024,PRINT_HIGH_QUALITY:2048},f=0,m=1,b=2,A=3,w=3,y=4,v={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},_={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},x=1,E=2,S=3,C=4,T=5,M={ERRORS:0,WARNINGS:1,INFOS:5},D={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotation:80,endAnnotation:81,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91,setStrokeTransparent:92,setFillTransparent:93,rawFillPath:94},P=0,k=1,I=2,R=3,F={NEED_PASSWORD:1,INCORRECT_PASSWORD:2};let L=M.WARNINGS;function setVerbosityLevel(t){Number.isInteger(t)&&(L=t)}function getVerbosityLevel(){return L}function info(t){L>=M.INFOS&&console.log(`Info: ${t}`)}function warn(t){L>=M.WARNINGS&&console.log(`Warning: ${t}`)}function unreachable(t){throw new Error(t)}function assert(t,e){t||unreachable(e)}function createValidAbsoluteUrl(t,e=null,i=null){if(!t)return null;if(i&&"string"==typeof t){if(i.addDefaultProtocol&&t.startsWith("www.")){const e=t.match(/\./g);e?.length>=2&&(t=`http://${t}`)}if(i.tryConvertEncoding)try{t=function stringToUTF8String(t){return decodeURIComponent(escape(t))}(t)}catch{}}const s=e?URL.parse(t,e):URL.parse(t);return function _isValidProtocol(t){switch(t?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(s)?s:null}function updateUrlHash(t,e,i=!1){const s=URL.parse(t);if(s){s.hash=e;return s.href}return i&&createValidAbsoluteUrl(t,"http://example.com")?t.split("#",1)[0]+""+(e?`#${e}`:""):""}function shadow(t,e,i,s=!1){Object.defineProperty(t,e,{value:i,enumerable:!s,configurable:!0,writable:!1});return i}const O=function BaseExceptionClosure(){function BaseException(t,e){this.message=t;this.name=e}BaseException.prototype=new Error;BaseException.constructor=BaseException;return BaseException}();class PasswordException extends O{constructor(t,e){super(t,"PasswordException");this.code=e}}class UnknownErrorException extends O{constructor(t,e){super(t,"UnknownErrorException");this.details=e}}class InvalidPDFException extends O{constructor(t){super(t,"InvalidPDFException")}}class ResponseException extends O{constructor(t,e,i){super(t,"ResponseException");this.status=e;this.missing=i}}class FormatError extends O{constructor(t){super(t,"FormatError")}}class AbortException extends O{constructor(t){super(t,"AbortException")}}function bytesToString(t){"object"==typeof t&&void 0!==t?.length||unreachable("Invalid argument for bytesToString");const e=t.length,i=8192;if(et.toString(16).padStart(2,"0")));class Util{static makeHexColor(t,e,i){return`#${N[t]}${N[e]}${N[i]}`}static scaleMinMax(t,e){let i;if(t[0]){if(t[0]<0){i=e[0];e[0]=e[2];e[2]=i}e[0]*=t[0];e[2]*=t[0];if(t[3]<0){i=e[1];e[1]=e[3];e[3]=i}e[1]*=t[3];e[3]*=t[3]}else{i=e[0];e[0]=e[1];e[1]=i;i=e[2];e[2]=e[3];e[3]=i;if(t[1]<0){i=e[1];e[1]=e[3];e[3]=i}e[1]*=t[1];e[3]*=t[1];if(t[2]<0){i=e[0];e[0]=e[2];e[2]=i}e[0]*=t[2];e[2]*=t[2]}e[0]+=t[4];e[1]+=t[5];e[2]+=t[4];e[3]+=t[5]}static transform(t,e){return[t[0]*e[0]+t[2]*e[1],t[1]*e[0]+t[3]*e[1],t[0]*e[2]+t[2]*e[3],t[1]*e[2]+t[3]*e[3],t[0]*e[4]+t[2]*e[5]+t[4],t[1]*e[4]+t[3]*e[5]+t[5]]}static applyTransform(t,e,i=0){const s=t[i],n=t[i+1];t[i]=s*e[0]+n*e[2]+e[4];t[i+1]=s*e[1]+n*e[3]+e[5]}static applyTransformToBezier(t,e,i=0){const s=e[0],n=e[1],a=e[2],r=e[3],o=e[4],l=e[5];for(let e=0;e<6;e+=2){const h=t[i+e],d=t[i+e+1];t[i+e]=h*s+d*a+o;t[i+e+1]=h*n+d*r+l}}static applyInverseTransform(t,e){const i=t[0],s=t[1],n=e[0]*e[3]-e[1]*e[2];t[0]=(i*e[3]-s*e[2]+e[2]*e[5]-e[4]*e[3])/n;t[1]=(-i*e[1]+s*e[0]+e[4]*e[1]-e[5]*e[0])/n}static axialAlignedBoundingBox(t,e,i){const s=e[0],n=e[1],a=e[2],r=e[3],o=e[4],l=e[5],h=t[0],d=t[1],c=t[2],u=t[3];let p=s*h+o,g=p,f=s*c+o,m=f,b=r*d+l,A=b,w=r*u+l,y=w;if(0!==n||0!==a){const t=n*h,e=n*c,i=a*d,s=a*u;p+=i;m+=i;f+=s;g+=s;b+=t;y+=t;w+=e;A+=e}i[0]=Math.min(i[0],p,f,g,m);i[1]=Math.min(i[1],b,w,A,y);i[2]=Math.max(i[2],p,f,g,m);i[3]=Math.max(i[3],b,w,A,y)}static inverseTransform(t){const e=t[0]*t[3]-t[1]*t[2];return[t[3]/e,-t[1]/e,-t[2]/e,t[0]/e,(t[2]*t[5]-t[4]*t[3])/e,(t[4]*t[1]-t[5]*t[0])/e]}static singularValueDecompose2dScale(t,e){const i=t[0],s=t[1],n=t[2],a=t[3],r=i**2+s**2,o=i*n+s*a,l=n**2+a**2,h=(r+l)/2,d=Math.sqrt(h**2-(r*l-o**2));e[0]=Math.sqrt(h+d||1);e[1]=Math.sqrt(h-d||1)}static normalizeRect(t){const e=t.slice(0);if(t[0]>t[2]){e[0]=t[2];e[2]=t[0]}if(t[1]>t[3]){e[1]=t[3];e[3]=t[1]}return e}static intersect(t,e){const i=Math.max(Math.min(t[0],t[2]),Math.min(e[0],e[2])),s=Math.min(Math.max(t[0],t[2]),Math.max(e[0],e[2]));if(i>s)return null;const n=Math.max(Math.min(t[1],t[3]),Math.min(e[1],e[3])),a=Math.min(Math.max(t[1],t[3]),Math.max(e[1],e[3]));return n>a?null:[i,n,s,a]}static pointBoundingBox(t,e,i){i[0]=Math.min(i[0],t);i[1]=Math.min(i[1],e);i[2]=Math.max(i[2],t);i[3]=Math.max(i[3],e)}static rectBoundingBox(t,e,i,s,n){n[0]=Math.min(n[0],t,i);n[1]=Math.min(n[1],e,s);n[2]=Math.max(n[2],t,i);n[3]=Math.max(n[3],e,s)}static#t(t,e,i,s,n,a,r,o,l,h){if(l<=0||l>=1)return;const d=1-l,c=l*l,u=c*l,p=d*(d*(d*t+3*l*e)+3*c*i)+u*s,g=d*(d*(d*n+3*l*a)+3*c*r)+u*o;h[0]=Math.min(h[0],p);h[1]=Math.min(h[1],g);h[2]=Math.max(h[2],p);h[3]=Math.max(h[3],g)}static#e(t,e,i,s,n,a,r,o,l,h,d,c){if(Math.abs(l)<1e-12){Math.abs(h)>=1e-12&&this.#t(t,e,i,s,n,a,r,o,-d/h,c);return}const u=h**2-4*d*l;if(u<0)return;const p=Math.sqrt(u),g=2*l;this.#t(t,e,i,s,n,a,r,o,(-h+p)/g,c);this.#t(t,e,i,s,n,a,r,o,(-h-p)/g,c)}static bezierBoundingBox(t,e,i,s,n,a,r,o,l){l[0]=Math.min(l[0],t,r);l[1]=Math.min(l[1],e,o);l[2]=Math.max(l[2],t,r);l[3]=Math.max(l[3],e,o);this.#e(t,i,n,r,e,s,a,o,3*(3*(i-n)-t+r),6*(t-2*i+n),3*(i-t),l);this.#e(t,i,n,r,e,s,a,o,3*(3*(s-a)-e+o),6*(e-2*s+a),3*(s-e),l)}}let B=null,H=null;function normalizeUnicode(t){if(!B){B=/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;H=new Map([["ſt","ſt"]])}return t.replaceAll(B,((t,e,i)=>e?e.normalize("NFKC"):H.get(i)))}function getUuid(){if("function"==typeof crypto.randomUUID)return crypto.randomUUID();const t=new Uint8Array(32);crypto.getRandomValues(t);return bytesToString(t)}const U="pdfjs_internal_id_";function MathClamp(t,e,i){return Math.min(Math.max(t,e),i)}function toBase64Util(t){return Uint8Array.prototype.toBase64?t.toBase64():btoa(bytesToString(t))}"function"!=typeof Promise.try&&(Promise.try=function(t,...e){return new Promise((i=>{i(t(...e))}))});"function"!=typeof Math.sumPrecise&&(Math.sumPrecise=function(t){return t.reduce(((t,e)=>t+e),0)});const z="http://www.w3.org/2000/svg";class PixelsPerInch{static CSS=96;static PDF=72;static PDF_TO_CSS_UNITS=this.CSS/this.PDF}async function fetchData(t,e="text"){if(isValidFetchUrl(t,document.baseURI)){const i=await fetch(t);if(!i.ok)throw new Error(i.statusText);switch(e){case"arraybuffer":return i.arrayBuffer();case"blob":return i.blob();case"json":return i.json()}return i.text()}return new Promise(((i,s)=>{const n=new XMLHttpRequest;n.open("GET",t,!0);n.responseType=e;n.onreadystatechange=()=>{if(n.readyState===XMLHttpRequest.DONE)if(200!==n.status&&0!==n.status)s(new Error(n.statusText));else{switch(e){case"arraybuffer":case"blob":case"json":i(n.response);return}i(n.responseText)}};n.send(null)}))}class PageViewport{constructor({viewBox:t,userUnit:e,scale:i,rotation:s,offsetX:n=0,offsetY:a=0,dontFlip:r=!1}){this.viewBox=t;this.userUnit=e;this.scale=i;this.rotation=s;this.offsetX=n;this.offsetY=a;i*=e;const o=(t[2]+t[0])/2,l=(t[3]+t[1])/2;let h,d,c,u,p,g,f,m;(s%=360)<0&&(s+=360);switch(s){case 180:h=-1;d=0;c=0;u=1;break;case 90:h=0;d=1;c=1;u=0;break;case 270:h=0;d=-1;c=-1;u=0;break;case 0:h=1;d=0;c=0;u=-1;break;default:throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees.")}if(r){c=-c;u=-u}if(0===h){p=Math.abs(l-t[1])*i+n;g=Math.abs(o-t[0])*i+a;f=(t[3]-t[1])*i;m=(t[2]-t[0])*i}else{p=Math.abs(o-t[0])*i+n;g=Math.abs(l-t[1])*i+a;f=(t[2]-t[0])*i;m=(t[3]-t[1])*i}this.transform=[h*i,d*i,c*i,u*i,p-h*i*o-c*i*l,g-d*i*o-u*i*l];this.width=f;this.height=m}get rawDims(){const t=this.viewBox;return shadow(this,"rawDims",{pageWidth:t[2]-t[0],pageHeight:t[3]-t[1],pageX:t[0],pageY:t[1]})}clone({scale:t=this.scale,rotation:e=this.rotation,offsetX:i=this.offsetX,offsetY:s=this.offsetY,dontFlip:n=!1}={}){return new PageViewport({viewBox:this.viewBox.slice(),userUnit:this.userUnit,scale:t,rotation:e,offsetX:i,offsetY:s,dontFlip:n})}convertToViewportPoint(t,e){const i=[t,e];Util.applyTransform(i,this.transform);return i}convertToViewportRectangle(t){const e=[t[0],t[1]];Util.applyTransform(e,this.transform);const i=[t[2],t[3]];Util.applyTransform(i,this.transform);return[e[0],e[1],i[0],i[1]]}convertToPdfPoint(t,e){const i=[t,e];Util.applyInverseTransform(i,this.transform);return i}}class RenderingCancelledException extends O{constructor(t,e=0){super(t,"RenderingCancelledException");this.extraDelay=e}}function isDataScheme(t){const e=t.length;let i=0;for(;i=1&&s<=12?s-1:0;let n=parseInt(e[3],10);n=n>=1&&n<=31?n:1;let a=parseInt(e[4],10);a=a>=0&&a<=23?a:0;let r=parseInt(e[5],10);r=r>=0&&r<=59?r:0;let o=parseInt(e[6],10);o=o>=0&&o<=59?o:0;const l=e[7]||"Z";let h=parseInt(e[8],10);h=h>=0&&h<=23?h:0;let d=parseInt(e[9],10)||0;d=d>=0&&d<=59?d:0;if("-"===l){a+=h;r+=d}else if("+"===l){a-=h;r-=d}return new Date(Date.UTC(i,s,n,a,r,o))}}function getXfaPageViewport(t,{scale:e=1,rotation:i=0}){const{width:s,height:n}=t.attributes.style,a=[0,0,parseInt(s),parseInt(n)];return new PageViewport({viewBox:a,userUnit:1,scale:e,rotation:i})}function getRGB(t){if(t.startsWith("#")){const e=parseInt(t.slice(1),16);return[(16711680&e)>>16,(65280&e)>>8,255&e]}if(t.startsWith("rgb("))return t.slice(4,-1).split(",").map((t=>parseInt(t)));if(t.startsWith("rgba("))return t.slice(5,-1).split(",").map((t=>parseInt(t))).slice(0,3);warn(`Not a valid color format: "${t}"`);return[0,0,0]}function getCurrentTransform(t){const{a:e,b:i,c:s,d:n,e:a,f:r}=t.getTransform();return[e,i,s,n,a,r]}function getCurrentTransformInverse(t){const{a:e,b:i,c:s,d:n,e:a,f:r}=t.getTransform().invertSelf();return[e,i,s,n,a,r]}function setLayerDimensions(t,e,i=!1,s=!0){if(e instanceof PageViewport){const{pageWidth:s,pageHeight:n}=e.rawDims,{style:a}=t,r=util_FeatureTest.isCSSRoundSupported,o=`var(--total-scale-factor) * ${s}px`,l=`var(--total-scale-factor) * ${n}px`,h=r?`round(down, ${o}, var(--scale-round-x))`:`calc(${o})`,d=r?`round(down, ${l}, var(--scale-round-y))`:`calc(${l})`;if(i&&e.rotation%180!=0){a.width=d;a.height=h}else{a.width=h;a.height=d}}s&&t.setAttribute("data-main-rotation",e.rotation)}class OutputScale{constructor(){const{pixelRatio:t}=OutputScale;this.sx=t;this.sy=t}get scaled(){return 1!==this.sx||1!==this.sy}get symmetric(){return this.sx===this.sy}limitCanvas(t,e,i,s){let n=1/0,a=1/0,r=1/0;i>0&&(n=Math.sqrt(i/(t*e)));if(-1!==s){a=s/t;r=s/e}const o=Math.min(n,a,r);if(this.sx>o||this.sy>o){this.sx=o;this.sy=o;return!0}return!1}static get pixelRatio(){return globalThis.devicePixelRatio||1}}const G=["image/apng","image/avif","image/bmp","image/gif","image/jpeg","image/png","image/svg+xml","image/webp","image/x-icon"];class EditorToolbar{#s=null;#n=null;#a;#r=null;#o=null;#l=null;static#h=null;constructor(t){this.#a=t;EditorToolbar.#h||=Object.freeze({freetext:"pdfjs-editor-remove-freetext-button",highlight:"pdfjs-editor-remove-highlight-button",ink:"pdfjs-editor-remove-ink-button",stamp:"pdfjs-editor-remove-stamp-button",signature:"pdfjs-editor-remove-signature-button"})}render(){const t=this.#s=document.createElement("div");t.classList.add("editToolbar","hidden");t.setAttribute("role","toolbar");const e=this.#a._uiManager._signal;t.addEventListener("contextmenu",noContextMenu,{signal:e});t.addEventListener("pointerdown",EditorToolbar.#d,{signal:e});const i=this.#r=document.createElement("div");i.className="buttons";t.append(i);const s=this.#a.toolbarPosition;if(s){const{style:e}=t,i="ltr"===this.#a._uiManager.direction?1-s[0]:s[0];e.insetInlineEnd=100*i+"%";e.top=`calc(${100*s[1]}% + var(--editor-toolbar-vert-offset))`}this.#c();return t}get div(){return this.#s}static#d(t){t.stopPropagation()}#u(t){this.#a._focusEventsAllowed=!1;stopEvent(t)}#p(t){this.#a._focusEventsAllowed=!0;stopEvent(t)}#g(t){const e=this.#a._uiManager._signal;t.addEventListener("focusin",this.#u.bind(this),{capture:!0,signal:e});t.addEventListener("focusout",this.#p.bind(this),{capture:!0,signal:e});t.addEventListener("contextmenu",noContextMenu,{signal:e})}hide(){this.#s.classList.add("hidden");this.#n?.hideDropdown()}show(){this.#s.classList.remove("hidden");this.#o?.shown()}#c(){const{editorType:t,_uiManager:e}=this.#a,i=document.createElement("button");i.className="delete";i.tabIndex=0;i.setAttribute("data-l10n-id",EditorToolbar.#h[t]);this.#g(i);i.addEventListener("click",(t=>{e.delete()}),{signal:e._signal});this.#r.append(i)}get#f(){const t=document.createElement("div");t.className="divider";return t}async addAltText(t){const e=await t.render();this.#g(e);this.#r.prepend(e,this.#f);this.#o=t}addColorPicker(t){this.#n=t;const e=t.renderButton();this.#g(e);this.#r.prepend(e,this.#f)}async addEditSignatureButton(t){const e=this.#l=await t.renderEditButton(this.#a);this.#g(e);this.#r.prepend(e,this.#f)}updateEditSignatureButton(t){this.#l&&(this.#l.title=t)}remove(){this.#s.remove();this.#n?.destroy();this.#n=null}}class HighlightToolbar{#r=null;#s=null;#m;constructor(t){this.#m=t}#b(){const t=this.#s=document.createElement("div");t.className="editToolbar";t.setAttribute("role","toolbar");t.addEventListener("contextmenu",noContextMenu,{signal:this.#m._signal});const e=this.#r=document.createElement("div");e.className="buttons";t.append(e);this.#A();return t}#w(t,e){let i=0,s=0;for(const n of t){const t=n.y+n.height;if(ti){s=a;i=t}else e?a>s&&(s=a):a{this.#m.highlightSelection("floating_button")}),{signal:i});this.#r.append(t)}}function bindEvents(t,e,i){for(const s of i)e.addEventListener(s,t[s].bind(t))}class IdManager{#y=0;get id(){return"pdfjs_internal_editor_"+this.#y++}}class ImageManager{#v=getUuid();#y=0;#_=null;static get _isSVGFittingCanvas(){const t=new OffscreenCanvas(1,3).getContext("2d",{willReadFrequently:!0}),e=new Image;e.src='data:image/svg+xml;charset=UTF-8,';return shadow(this,"_isSVGFittingCanvas",e.decode().then((()=>{t.drawImage(e,0,0,1,1,0,0,1,3);return 0===new Uint32Array(t.getImageData(0,0,1,1).data.buffer)[0]})))}async#x(t,e){this.#_||=new Map;let i=this.#_.get(t);if(null===i)return null;if(i?.bitmap){i.refCounter+=1;return i}try{i||={bitmap:null,id:`image_${this.#v}_${this.#y++}`,refCounter:0,isSvg:!1};let t;if("string"==typeof e){i.url=e;t=await fetchData(e,"blob")}else e instanceof File?t=i.file=e:e instanceof Blob&&(t=e);if("image/svg+xml"===t.type){const e=ImageManager._isSVGFittingCanvas,s=new FileReader,n=new Image,a=new Promise(((t,a)=>{n.onload=()=>{i.bitmap=n;i.isSvg=!0;t()};s.onload=async()=>{const t=i.svgUrl=s.result;n.src=await e?`${t}#svgView(preserveAspectRatio(none))`:t};n.onerror=s.onerror=a}));s.readAsDataURL(t);await a}else i.bitmap=await createImageBitmap(t);i.refCounter=1}catch(t){warn(t);i=null}this.#_.set(t,i);i&&this.#_.set(i.id,i);return i}async getFromFile(t){const{lastModified:e,name:i,size:s,type:n}=t;return this.#x(`${e}_${i}_${s}_${n}`,t)}async getFromUrl(t){return this.#x(t,t)}async getFromBlob(t,e){const i=await e;return this.#x(t,i)}async getFromId(t){this.#_||=new Map;const e=this.#_.get(t);if(!e)return null;if(e.bitmap){e.refCounter+=1;return e}if(e.file)return this.getFromFile(e.file);if(e.blobPromise){const{blobPromise:t}=e;delete e.blobPromise;return this.getFromBlob(e.id,t)}return this.getFromUrl(e.url)}getFromCanvas(t,e){this.#_||=new Map;let i=this.#_.get(t);if(i?.bitmap){i.refCounter+=1;return i}const s=new OffscreenCanvas(e.width,e.height);s.getContext("2d").drawImage(e,0,0);i={bitmap:s.transferToImageBitmap(),id:`image_${this.#v}_${this.#y++}`,refCounter:1,isSvg:!1};this.#_.set(t,i);this.#_.set(i.id,i);return i}getSvgUrl(t){const e=this.#_.get(t);return e?.isSvg?e.svgUrl:null}deleteId(t){this.#_||=new Map;const e=this.#_.get(t);if(!e)return;e.refCounter-=1;if(0!==e.refCounter)return;const{bitmap:i}=e;if(!e.url&&!e.file){const t=new OffscreenCanvas(i.width,i.height);t.getContext("bitmaprenderer").transferFromImageBitmap(i);e.blobPromise=t.convertToBlob()}i.close?.();e.bitmap=null}isValidId(t){return t.startsWith(`image_${this.#v}_`)}}class CommandManager{#E=[];#S=!1;#C;#T=-1;constructor(t=128){this.#C=t}add({cmd:t,undo:e,post:i,mustExec:s,type:n=NaN,overwriteIfSameType:a=!1,keepUndo:r=!1}){s&&t();if(this.#S)return;const o={cmd:t,undo:e,post:i,type:n};if(-1===this.#T){this.#E.length>0&&(this.#E.length=0);this.#T=0;this.#E.push(o);return}if(a&&this.#E[this.#T].type===n){r&&(o.undo=this.#E[this.#T].undo);this.#E[this.#T]=o;return}const l=this.#T+1;if(l===this.#C)this.#E.splice(0,1);else{this.#T=l;l=0;e--)if(this.#E[e].type!==t){this.#E.splice(e+1,this.#T-e);this.#T=e;return}this.#E.length=0;this.#T=-1}}destroy(){this.#E=null}}class KeyboardManager{constructor(t){this.buffer=[];this.callbacks=new Map;this.allKeys=new Set;const{isMac:e}=util_FeatureTest.platform;for(const[i,s,n={}]of t)for(const t of i){const i=t.startsWith("mac+");if(e&&i){this.callbacks.set(t.slice(4),{callback:s,options:n});this.allKeys.add(t.split("+").at(-1))}else if(!e&&!i){this.callbacks.set(t,{callback:s,options:n});this.allKeys.add(t.split("+").at(-1))}}}#M(t){t.altKey&&this.buffer.push("alt");t.ctrlKey&&this.buffer.push("ctrl");t.metaKey&&this.buffer.push("meta");t.shiftKey&&this.buffer.push("shift");this.buffer.push(t.key);const e=this.buffer.join("+");this.buffer.length=0;return e}exec(t,e){if(!this.allKeys.has(e.key))return;const i=this.callbacks.get(this.#M(e));if(!i)return;const{callback:s,options:{bubbles:n=!1,args:a=[],checker:r=null}}=i;if(!r||r(t,e)){s.bind(t,...a,e)();n||stopEvent(e)}}}class ColorManager{static _colorsMapping=new Map([["CanvasText",[0,0,0]],["Canvas",[255,255,255]]]);get _colors(){const t=new Map([["CanvasText",null],["Canvas",null]]);!function getColorValues(t){const e=document.createElement("span");e.style.visibility="hidden";e.style.colorScheme="only light";document.body.append(e);for(const i of t.keys()){e.style.color=i;const s=window.getComputedStyle(e).color;t.set(i,getRGB(s))}e.remove()}(t);return shadow(this,"_colors",t)}convert(t){const e=getRGB(t);if(!window.matchMedia("(forced-colors: active)").matches)return e;for(const[t,i]of this._colors)if(i.every(((t,i)=>t===e[i])))return ColorManager._colorsMapping.get(t);return e}getHexCode(t){const e=this._colors.get(t);return e?Util.makeHexColor(...e):t}}class AnnotationEditorUIManager{#D=new AbortController;#P=null;#k=new Map;#I=new Map;#R=null;#F=null;#L=null;#O=new CommandManager;#N=null;#B=null;#H=0;#U=new Set;#z=null;#G=null;#$=new Set;_editorUndoBar=null;#W=!1;#V=!1;#j=!1;#q=null;#X=null;#K=null;#Y=null;#Q=!1;#J=null;#Z=new IdManager;#tt=!1;#et=!1;#it=null;#st=null;#nt=null;#at=null;#rt=null;#ot=u.NONE;#lt=new Set;#ht=null;#dt=null;#ct=null;#ut=null;#pt={isEditing:!1,isEmpty:!0,hasSomethingToUndo:!1,hasSomethingToRedo:!1,hasSelectedEditor:!1,hasSelectedText:!1};#gt=[0,0];#ft=null;#mt=null;#bt=null;#At=null;static TRANSLATE_SMALL=1;static TRANSLATE_BIG=10;static get _keyboardManager(){const t=AnnotationEditorUIManager.prototype,arrowChecker=t=>t.#mt.contains(document.activeElement)&&"BUTTON"!==document.activeElement.tagName&&t.hasSomethingToControl(),textInputChecker=(t,{target:e})=>{if(e instanceof HTMLInputElement){const{type:t}=e;return"text"!==t&&"number"!==t}return!0},e=this.TRANSLATE_SMALL,i=this.TRANSLATE_BIG;return shadow(this,"_keyboardManager",new KeyboardManager([[["ctrl+a","mac+meta+a"],t.selectAll,{checker:textInputChecker}],[["ctrl+z","mac+meta+z"],t.undo,{checker:textInputChecker}],[["ctrl+y","ctrl+shift+z","mac+meta+shift+z","ctrl+shift+Z","mac+meta+shift+Z"],t.redo,{checker:textInputChecker}],[["Backspace","alt+Backspace","ctrl+Backspace","shift+Backspace","mac+Backspace","mac+alt+Backspace","mac+ctrl+Backspace","Delete","ctrl+Delete","shift+Delete","mac+Delete"],t.delete,{checker:textInputChecker}],[["Enter","mac+Enter"],t.addNewEditorFromKeyboard,{checker:(t,{target:e})=>!(e instanceof HTMLButtonElement)&&t.#mt.contains(e)&&!t.isEnterHandled}],[[" ","mac+ "],t.addNewEditorFromKeyboard,{checker:(t,{target:e})=>!(e instanceof HTMLButtonElement)&&t.#mt.contains(document.activeElement)}],[["Escape","mac+Escape"],t.unselectAll],[["ArrowLeft","mac+ArrowLeft"],t.translateSelectedEditors,{args:[-e,0],checker:arrowChecker}],[["ctrl+ArrowLeft","mac+shift+ArrowLeft"],t.translateSelectedEditors,{args:[-i,0],checker:arrowChecker}],[["ArrowRight","mac+ArrowRight"],t.translateSelectedEditors,{args:[e,0],checker:arrowChecker}],[["ctrl+ArrowRight","mac+shift+ArrowRight"],t.translateSelectedEditors,{args:[i,0],checker:arrowChecker}],[["ArrowUp","mac+ArrowUp"],t.translateSelectedEditors,{args:[0,-e],checker:arrowChecker}],[["ctrl+ArrowUp","mac+shift+ArrowUp"],t.translateSelectedEditors,{args:[0,-i],checker:arrowChecker}],[["ArrowDown","mac+ArrowDown"],t.translateSelectedEditors,{args:[0,e],checker:arrowChecker}],[["ctrl+ArrowDown","mac+shift+ArrowDown"],t.translateSelectedEditors,{args:[0,i],checker:arrowChecker}]]))}constructor(t,e,i,s,n,a,r,o,l,h,d,c,u,p){const g=this._signal=this.#D.signal;this.#mt=t;this.#bt=e;this.#R=i;this.#dt=s;this._eventBus=n;n._on("editingaction",this.onEditingAction.bind(this),{signal:g});n._on("pagechanging",this.onPageChanging.bind(this),{signal:g});n._on("scalechanging",this.onScaleChanging.bind(this),{signal:g});n._on("rotationchanging",this.onRotationChanging.bind(this),{signal:g});n._on("setpreference",this.onSetPreference.bind(this),{signal:g});n._on("switchannotationeditorparams",(t=>this.updateParams(t.type,t.value)),{signal:g});this.#wt();this.#yt();this.#vt();this.#F=a.annotationStorage;this.#q=a.filterFactory;this.#ct=r;this.#Y=o||null;this.#W=l;this.#V=h;this.#j=d;this.#rt=c||null;this.viewParameters={realScale:PixelsPerInch.PDF_TO_CSS_UNITS,rotation:0};this.isShiftKeyDown=!1;this._editorUndoBar=u||null;this._supportsPinchToZoom=!1!==p}destroy(){this.#At?.resolve();this.#At=null;this.#D?.abort();this.#D=null;this._signal=null;for(const t of this.#I.values())t.destroy();this.#I.clear();this.#k.clear();this.#$.clear();this.#at?.clear();this.#P=null;this.#lt.clear();this.#O.destroy();this.#R?.destroy();this.#dt?.destroy();this.#J?.hide();this.#J=null;this.#nt?.destroy();this.#nt=null;if(this.#X){clearTimeout(this.#X);this.#X=null}if(this.#ft){clearTimeout(this.#ft);this.#ft=null}this._editorUndoBar?.destroy()}combinedSignal(t){return AbortSignal.any([this._signal,t.signal])}get mlManager(){return this.#rt}get useNewAltTextFlow(){return this.#V}get useNewAltTextWhenAddingImage(){return this.#j}get hcmFilter(){return shadow(this,"hcmFilter",this.#ct?this.#q.addHCMFilter(this.#ct.foreground,this.#ct.background):"none")}get direction(){return shadow(this,"direction",getComputedStyle(this.#mt).direction)}get highlightColors(){return shadow(this,"highlightColors",this.#Y?new Map(this.#Y.split(",").map((t=>t.split("=").map((t=>t.trim()))))):null)}get highlightColorNames(){return shadow(this,"highlightColorNames",this.highlightColors?new Map(Array.from(this.highlightColors,(t=>t.reverse()))):null)}setCurrentDrawingSession(t){if(t){this.unselectAll();this.disableUserSelect(!0)}else this.disableUserSelect(!1);this.#B=t}setMainHighlightColorPicker(t){this.#nt=t}editAltText(t,e=!1){this.#R?.editAltText(this,t,e)}getSignature(t){this.#dt?.getSignature({uiManager:this,editor:t})}get signatureManager(){return this.#dt}switchToMode(t,e){this._eventBus.on("annotationeditormodechanged",e,{once:!0,signal:this._signal});this._eventBus.dispatch("showannotationeditorui",{source:this,mode:t})}setPreference(t,e){this._eventBus.dispatch("setpreference",{source:this,name:t,value:e})}onSetPreference({name:t,value:e}){if("enableNewAltTextWhenAddingImage"===t)this.#j=e}onPageChanging({pageNumber:t}){this.#H=t-1}focusMainContainer(){this.#mt.focus()}findParent(t,e){for(const i of this.#I.values()){const{x:s,y:n,width:a,height:r}=i.div.getBoundingClientRect();if(t>=s&&t<=s+a&&e>=n&&e<=n+r)return i}return null}disableUserSelect(t=!1){this.#bt.classList.toggle("noUserSelect",t)}addShouldRescale(t){this.#$.add(t)}removeShouldRescale(t){this.#$.delete(t)}onScaleChanging({scale:t}){this.commitOrRemove();this.viewParameters.realScale=t*PixelsPerInch.PDF_TO_CSS_UNITS;for(const t of this.#$)t.onScaleChanging();this.#B?.onScaleChanging()}onRotationChanging({pagesRotation:t}){this.commitOrRemove();this.viewParameters.rotation=t}#_t({anchorNode:t}){return t.nodeType===Node.TEXT_NODE?t.parentElement:t}#xt(t){const{currentLayer:e}=this;if(e.hasTextLayer(t))return e;for(const e of this.#I.values())if(e.hasTextLayer(t))return e;return null}highlightSelection(t=""){const e=document.getSelection();if(!e||e.isCollapsed)return;const{anchorNode:i,anchorOffset:s,focusNode:n,focusOffset:a}=e,r=e.toString(),o=this.#_t(e).closest(".textLayer"),l=this.getSelectionBoxes(o);if(!l)return;e.empty();const h=this.#xt(o),d=this.#ot===u.NONE,callback=()=>{h?.createAndAddNewEditor({x:0,y:0},!1,{methodOfCreation:t,boxes:l,anchorNode:i,anchorOffset:s,focusNode:n,focusOffset:a,text:r});d&&this.showAllEditors("highlight",!0,!0)};d?this.switchToMode(u.HIGHLIGHT,callback):callback()}#Et(){const t=document.getSelection();if(!t||t.isCollapsed)return;const e=this.#_t(t).closest(".textLayer"),i=this.getSelectionBoxes(e);if(i){this.#J||=new HighlightToolbar(this);this.#J.show(e,i,"ltr"===this.direction)}}addToAnnotationStorage(t){t.isEmpty()||!this.#F||this.#F.has(t.id)||this.#F.setValue(t.id,t)}#St(){const t=document.getSelection();if(!t||t.isCollapsed){if(this.#ht){this.#J?.hide();this.#ht=null;this.#Ct({hasSelectedText:!1})}return}const{anchorNode:e}=t;if(e===this.#ht)return;const i=this.#_t(t).closest(".textLayer");if(i){this.#J?.hide();this.#ht=e;this.#Ct({hasSelectedText:!0});if(this.#ot===u.HIGHLIGHT||this.#ot===u.NONE){this.#ot===u.HIGHLIGHT&&this.showAllEditors("highlight",!0,!0);this.#Q=this.isShiftKeyDown;if(!this.isShiftKeyDown){const t=this.#ot===u.HIGHLIGHT?this.#xt(i):null;t?.toggleDrawing();const e=new AbortController,s=this.combinedSignal(e),pointerup=i=>{if("pointerup"!==i.type||0===i.button){e.abort();t?.toggleDrawing(!0);"pointerup"===i.type&&this.#Tt("main_toolbar")}};window.addEventListener("pointerup",pointerup,{signal:s});window.addEventListener("blur",pointerup,{signal:s})}}}else if(this.#ht){this.#J?.hide();this.#ht=null;this.#Ct({hasSelectedText:!1})}}#Tt(t=""){this.#ot===u.HIGHLIGHT?this.highlightSelection(t):this.#W&&this.#Et()}#wt(){document.addEventListener("selectionchange",this.#St.bind(this),{signal:this._signal})}#Mt(){if(this.#K)return;this.#K=new AbortController;const t=this.combinedSignal(this.#K);window.addEventListener("focus",this.focus.bind(this),{signal:t});window.addEventListener("blur",this.blur.bind(this),{signal:t})}#Dt(){this.#K?.abort();this.#K=null}blur(){this.isShiftKeyDown=!1;if(this.#Q){this.#Q=!1;this.#Tt("main_toolbar")}if(!this.hasSelection)return;const{activeElement:t}=document;for(const e of this.#lt)if(e.div.contains(t)){this.#st=[e,t];e._focusEventsAllowed=!1;break}}focus(){if(!this.#st)return;const[t,e]=this.#st;this.#st=null;e.addEventListener("focusin",(()=>{t._focusEventsAllowed=!0}),{once:!0,signal:this._signal});e.focus()}#vt(){if(this.#it)return;this.#it=new AbortController;const t=this.combinedSignal(this.#it);window.addEventListener("keydown",this.keydown.bind(this),{signal:t});window.addEventListener("keyup",this.keyup.bind(this),{signal:t})}#Pt(){this.#it?.abort();this.#it=null}#kt(){if(this.#N)return;this.#N=new AbortController;const t=this.combinedSignal(this.#N);document.addEventListener("copy",this.copy.bind(this),{signal:t});document.addEventListener("cut",this.cut.bind(this),{signal:t});document.addEventListener("paste",this.paste.bind(this),{signal:t})}#It(){this.#N?.abort();this.#N=null}#yt(){const t=this._signal;document.addEventListener("dragover",this.dragOver.bind(this),{signal:t});document.addEventListener("drop",this.drop.bind(this),{signal:t})}addEditListeners(){this.#vt();this.#kt()}removeEditListeners(){this.#Pt();this.#It()}dragOver(t){for(const{type:e}of t.dataTransfer.items)for(const i of this.#G)if(i.isHandlingMimeForPasting(e)){t.dataTransfer.dropEffect="copy";t.preventDefault();return}}drop(t){for(const e of t.dataTransfer.items)for(const i of this.#G)if(i.isHandlingMimeForPasting(e.type)){i.paste(e,this.currentLayer);t.preventDefault();return}}copy(t){t.preventDefault();this.#P?.commitOrRemove();if(!this.hasSelection)return;const e=[];for(const t of this.#lt){const i=t.serialize(!0);i&&e.push(i)}0!==e.length&&t.clipboardData.setData("application/pdfjs",JSON.stringify(e))}cut(t){this.copy(t);this.delete()}async paste(t){t.preventDefault();const{clipboardData:e}=t;for(const t of e.items)for(const e of this.#G)if(e.isHandlingMimeForPasting(t.type)){e.paste(t,this.currentLayer);return}let i=e.getData("application/pdfjs");if(!i)return;try{i=JSON.parse(i)}catch(t){warn(`paste: "${t.message}".`);return}if(!Array.isArray(i))return;this.unselectAll();const s=this.currentLayer;try{const t=[];for(const e of i){const i=await s.deserialize(e);if(!i)return;t.push(i)}const cmd=()=>{for(const e of t)this.#Rt(e);this.#Ft(t)},undo=()=>{for(const e of t)e.remove()};this.addCommands({cmd,undo,mustExec:!0})}catch(t){warn(`paste: "${t.message}".`)}}keydown(t){this.isShiftKeyDown||"Shift"!==t.key||(this.isShiftKeyDown=!0);this.#ot===u.NONE||this.isEditorHandlingKeyboard||AnnotationEditorUIManager._keyboardManager.exec(this,t)}keyup(t){if(this.isShiftKeyDown&&"Shift"===t.key){this.isShiftKeyDown=!1;if(this.#Q){this.#Q=!1;this.#Tt("main_toolbar")}}}onEditingAction({name:t}){switch(t){case"undo":case"redo":case"delete":case"selectAll":this[t]();break;case"highlightSelection":this.highlightSelection("context_menu")}}#Ct(t){if(Object.entries(t).some((([t,e])=>this.#pt[t]!==e))){this._eventBus.dispatch("annotationeditorstateschanged",{source:this,details:Object.assign(this.#pt,t)});this.#ot===u.HIGHLIGHT&&!1===t.hasSelectedEditor&&this.#Lt([[p.HIGHLIGHT_FREE,!0]])}}#Lt(t){this._eventBus.dispatch("annotationeditorparamschanged",{source:this,details:t})}setEditingState(t){if(t){this.#Mt();this.#kt();this.#Ct({isEditing:this.#ot!==u.NONE,isEmpty:this.#Ot(),hasSomethingToUndo:this.#O.hasSomethingToUndo(),hasSomethingToRedo:this.#O.hasSomethingToRedo(),hasSelectedEditor:!1})}else{this.#Dt();this.#It();this.#Ct({isEditing:!1});this.disableUserSelect(!1)}}registerEditorTypes(t){if(!this.#G){this.#G=t;for(const t of this.#G)this.#Lt(t.defaultPropertiesToUpdate)}}getId(){return this.#Z.id}get currentLayer(){return this.#I.get(this.#H)}getLayer(t){return this.#I.get(t)}get currentPageIndex(){return this.#H}addLayer(t){this.#I.set(t.pageIndex,t);this.#tt?t.enable():t.disable()}removeLayer(t){this.#I.delete(t.pageIndex)}async updateMode(t,e=null,i=!1){if(this.#ot!==t){if(this.#At){await this.#At.promise;if(!this.#At)return}this.#At=Promise.withResolvers();this.#B?.commitOrRemove();this.#ot=t;if(t!==u.NONE){t===u.SIGNATURE&&await(this.#dt?.loadSignatures());this.setEditingState(!0);await this.#Nt();this.unselectAll();for(const e of this.#I.values())e.updateMode(t);if(e){for(const t of this.#k.values())if(t.annotationElementId===e){this.setSelected(t);t.enterInEditMode()}else t.unselect();this.#At.resolve()}else{i&&this.addNewEditorFromKeyboard();this.#At.resolve()}}else{this.setEditingState(!1);this.#Bt();this._editorUndoBar?.hide();this.#At.resolve()}}}addNewEditorFromKeyboard(){this.currentLayer.canCreateNewEmptyEditor()&&this.currentLayer.addNewEditor()}updateToolbar(t){t!==this.#ot&&this._eventBus.dispatch("switchannotationeditormode",{source:this,mode:t})}updateParams(t,e){if(this.#G){switch(t){case p.CREATE:this.currentLayer.addNewEditor(e);return;case p.HIGHLIGHT_DEFAULT_COLOR:this.#nt?.updateColor(e);break;case p.HIGHLIGHT_SHOW_ALL:this._eventBus.dispatch("reporttelemetry",{source:this,details:{type:"editing",data:{type:"highlight",action:"toggle_visibility"}}});(this.#ut||=new Map).set(t,e);this.showAllEditors("highlight",e)}for(const i of this.#lt)i.updateParams(t,e);for(const i of this.#G)i.updateDefaultParams(t,e)}}showAllEditors(t,e,i=!1){for(const i of this.#k.values())i.editorType===t&&i.show(e);(this.#ut?.get(p.HIGHLIGHT_SHOW_ALL)??!0)!==e&&this.#Lt([[p.HIGHLIGHT_SHOW_ALL,e]])}enableWaiting(t=!1){if(this.#et!==t){this.#et=t;for(const e of this.#I.values()){t?e.disableClick():e.enableClick();e.div.classList.toggle("waiting",t)}}}async#Nt(){if(!this.#tt){this.#tt=!0;const t=[];for(const e of this.#I.values())t.push(e.enable());await Promise.all(t);for(const t of this.#k.values())t.enable()}}#Bt(){this.unselectAll();if(this.#tt){this.#tt=!1;for(const t of this.#I.values())t.disable();for(const t of this.#k.values())t.disable()}}getEditors(t){const e=[];for(const i of this.#k.values())i.pageIndex===t&&e.push(i);return e}getEditor(t){return this.#k.get(t)}addEditor(t){this.#k.set(t.id,t)}removeEditor(t){if(t.div.contains(document.activeElement)){this.#X&&clearTimeout(this.#X);this.#X=setTimeout((()=>{this.focusMainContainer();this.#X=null}),0)}this.#k.delete(t.id);t.annotationElementId&&this.#at?.delete(t.annotationElementId);this.unselect(t);t.annotationElementId&&this.#U.has(t.annotationElementId)||this.#F?.remove(t.id)}addDeletedAnnotationElement(t){this.#U.add(t.annotationElementId);this.addChangedExistingAnnotation(t);t.deleted=!0}isDeletedAnnotationElement(t){return this.#U.has(t)}removeDeletedAnnotationElement(t){this.#U.delete(t.annotationElementId);this.removeChangedExistingAnnotation(t);t.deleted=!1}#Rt(t){const e=this.#I.get(t.pageIndex);if(e)e.addOrRebuild(t);else{this.addEditor(t);this.addToAnnotationStorage(t)}}setActiveEditor(t){if(this.#P!==t){this.#P=t;t&&this.#Lt(t.propertiesToUpdate)}}get#Ht(){let t=null;for(t of this.#lt);return t}updateUI(t){this.#Ht===t&&this.#Lt(t.propertiesToUpdate)}updateUIForDefaultProperties(t){this.#Lt(t.defaultPropertiesToUpdate)}toggleSelected(t){if(this.#lt.has(t)){this.#lt.delete(t);t.unselect();this.#Ct({hasSelectedEditor:this.hasSelection})}else{this.#lt.add(t);t.select();this.#Lt(t.propertiesToUpdate);this.#Ct({hasSelectedEditor:!0})}}setSelected(t){this.#B?.commitOrRemove();for(const e of this.#lt)e!==t&&e.unselect();this.#lt.clear();this.#lt.add(t);t.select();this.#Lt(t.propertiesToUpdate);this.#Ct({hasSelectedEditor:!0})}isSelected(t){return this.#lt.has(t)}get firstSelectedEditor(){return this.#lt.values().next().value}unselect(t){t.unselect();this.#lt.delete(t);this.#Ct({hasSelectedEditor:this.hasSelection})}get hasSelection(){return 0!==this.#lt.size}get isEnterHandled(){return 1===this.#lt.size&&this.firstSelectedEditor.isEnterHandled}undo(){this.#O.undo();this.#Ct({hasSomethingToUndo:this.#O.hasSomethingToUndo(),hasSomethingToRedo:!0,isEmpty:this.#Ot()});this._editorUndoBar?.hide()}redo(){this.#O.redo();this.#Ct({hasSomethingToUndo:!0,hasSomethingToRedo:this.#O.hasSomethingToRedo(),isEmpty:this.#Ot()})}addCommands(t){this.#O.add(t);this.#Ct({hasSomethingToUndo:!0,hasSomethingToRedo:!1,isEmpty:this.#Ot()})}cleanUndoStack(t){this.#O.cleanType(t)}#Ot(){if(0===this.#k.size)return!0;if(1===this.#k.size)for(const t of this.#k.values())return t.isEmpty();return!1}delete(){this.commitOrRemove();const t=this.currentLayer?.endDrawingSession(!0);if(!this.hasSelection&&!t)return;const e=t?[t]:[...this.#lt],undo=()=>{for(const t of e)this.#Rt(t)};this.addCommands({cmd:()=>{this._editorUndoBar?.show(undo,1===e.length?e[0].editorType:e.length);for(const t of e)t.remove()},undo,mustExec:!0})}commitOrRemove(){this.#P?.commitOrRemove()}hasSomethingToControl(){return this.#P||this.hasSelection}#Ft(t){for(const t of this.#lt)t.unselect();this.#lt.clear();for(const e of t)if(!e.isEmpty()){this.#lt.add(e);e.select()}this.#Ct({hasSelectedEditor:this.hasSelection})}selectAll(){for(const t of this.#lt)t.commit();this.#Ft(this.#k.values())}unselectAll(){if(this.#P){this.#P.commitOrRemove();if(this.#ot!==u.NONE)return}if(!this.#B?.commitOrRemove()&&this.hasSelection){for(const t of this.#lt)t.unselect();this.#lt.clear();this.#Ct({hasSelectedEditor:!1})}}translateSelectedEditors(t,e,i=!1){i||this.commitOrRemove();if(!this.hasSelection)return;this.#gt[0]+=t;this.#gt[1]+=e;const[s,n]=this.#gt,a=[...this.#lt];this.#ft&&clearTimeout(this.#ft);this.#ft=setTimeout((()=>{this.#ft=null;this.#gt[0]=this.#gt[1]=0;this.addCommands({cmd:()=>{for(const t of a)if(this.#k.has(t.id)){t.translateInPage(s,n);t.translationDone()}},undo:()=>{for(const t of a)if(this.#k.has(t.id)){t.translateInPage(-s,-n);t.translationDone()}},mustExec:!1})}),1e3);for(const i of a){i.translateInPage(t,e);i.translationDone()}}setUpDragSession(){if(this.hasSelection){this.disableUserSelect(!0);this.#z=new Map;for(const t of this.#lt)this.#z.set(t,{savedX:t.x,savedY:t.y,savedPageIndex:t.pageIndex,newX:0,newY:0,newPageIndex:-1})}}endDragSession(){if(!this.#z)return!1;this.disableUserSelect(!1);const t=this.#z;this.#z=null;let e=!1;for(const[{x:i,y:s,pageIndex:n},a]of t){a.newX=i;a.newY=s;a.newPageIndex=n;e||=i!==a.savedX||s!==a.savedY||n!==a.savedPageIndex}if(!e)return!1;const move=(t,e,i,s)=>{if(this.#k.has(t.id)){const n=this.#I.get(s);if(n)t._setParentAndPosition(n,e,i);else{t.pageIndex=s;t.x=e;t.y=i}}};this.addCommands({cmd:()=>{for(const[e,{newX:i,newY:s,newPageIndex:n}]of t)move(e,i,s,n)},undo:()=>{for(const[e,{savedX:i,savedY:s,savedPageIndex:n}]of t)move(e,i,s,n)},mustExec:!0});return!0}dragSelectedEditors(t,e){if(this.#z)for(const i of this.#z.keys())i.drag(t,e)}rebuild(t){if(null===t.parent){const e=this.getLayer(t.pageIndex);if(e){e.changeParent(t);e.addOrRebuild(t)}else{this.addEditor(t);this.addToAnnotationStorage(t);t.rebuild()}}else t.parent.addOrRebuild(t)}get isEditorHandlingKeyboard(){return this.getActive()?.shouldGetKeyboardEvents()||1===this.#lt.size&&this.firstSelectedEditor.shouldGetKeyboardEvents()}isActive(t){return this.#P===t}getActive(){return this.#P}getMode(){return this.#ot}get imageManager(){return shadow(this,"imageManager",new ImageManager)}getSelectionBoxes(t){if(!t)return null;const e=document.getSelection();for(let i=0,s=e.rangeCount;i({x:(e-s)/a,y:1-(t+r-i)/n,width:o/a,height:r/n});break;case"180":r=(t,e,r,o)=>({x:1-(t+r-i)/n,y:1-(e+o-s)/a,width:r/n,height:o/a});break;case"270":r=(t,e,r,o)=>({x:1-(e+o-s)/a,y:(t-i)/n,width:o/a,height:r/n});break;default:r=(t,e,r,o)=>({x:(t-i)/n,y:(e-s)/a,width:r/n,height:o/a})}const o=[];for(let t=0,i=e.rangeCount;tt.stopPropagation()),{signal:i});const onClick=t=>{t.preventDefault();this.#a._uiManager.editAltText(this.#a);this.#Kt&&this.#a._reportTelemetry({action:"pdfjs.image.alt_text.image_status_label_clicked",data:{label:this.#Qt}})};t.addEventListener("click",onClick,{capture:!0,signal:i});t.addEventListener("keydown",(e=>{if(e.target===t&&"Enter"===e.key){this.#Vt=!0;onClick(e)}}),{signal:i});await this.#Jt();return t}get#Qt(){return(this.#o?"added":null===this.#o&&this.guessedText&&"review")||"missing"}finish(){if(this.#zt){this.#zt.focus({focusVisible:this.#Vt});this.#Vt=!1}}isEmpty(){return this.#Kt?null===this.#o:!this.#o&&!this.#Ut}hasData(){return this.#Kt?null!==this.#o||!!this.#qt:this.isEmpty()}get guessedText(){return this.#qt}async setGuessedText(t){if(null===this.#o){this.#qt=t;this.#Xt=await AltText._l10n.get("pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer",{generatedAltText:t});this.#Jt()}}toggleAltTextBadge(t=!1){if(this.#Kt&&!this.#o){if(!this.#jt){const t=this.#jt=document.createElement("div");t.className="noAltTextBadge";this.#a.div.append(t)}this.#jt.classList.toggle("hidden",!t)}else{this.#jt?.remove();this.#jt=null}}serialize(t){let e=this.#o;t||this.#qt!==e||(e=this.#Xt);return{altText:e,decorative:this.#Ut,guessedText:this.#qt,textWithDisclaimer:this.#Xt}}get data(){return{altText:this.#o,decorative:this.#Ut}}set data({altText:t,decorative:e,guessedText:i,textWithDisclaimer:s,cancel:n=!1}){if(i){this.#qt=i;this.#Xt=s}if(this.#o!==t||this.#Ut!==e){if(!n){this.#o=t;this.#Ut=e}this.#Jt()}}toggle(t=!1){if(this.#zt){if(!t&&this.#Wt){clearTimeout(this.#Wt);this.#Wt=null}this.#zt.disabled=!t}}shown(){this.#a._reportTelemetry({action:"pdfjs.image.alt_text.image_status_label_displayed",data:{label:this.#Qt}})}destroy(){this.#zt?.remove();this.#zt=null;this.#Gt=null;this.#$t=null;this.#jt?.remove();this.#jt=null}async#Jt(){const t=this.#zt;if(!t)return;if(this.#Kt){t.classList.toggle("done",!!this.#o);t.setAttribute("data-l10n-id",AltText.#Yt[this.#Qt]);this.#Gt?.setAttribute("data-l10n-id",AltText.#Yt[`${this.#Qt}-label`]);if(!this.#o){this.#$t?.remove();return}}else{if(!this.#o&&!this.#Ut){t.classList.remove("done");this.#$t?.remove();return}t.classList.add("done");t.setAttribute("data-l10n-id","pdfjs-editor-alt-text-edit-button")}let e=this.#$t;if(!e){this.#$t=e=document.createElement("span");e.className="tooltip";e.setAttribute("role","tooltip");e.id=`alt-text-tooltip-${this.#a.id}`;const i=100,s=this.#a._uiManager._signal;s.addEventListener("abort",(()=>{clearTimeout(this.#Wt);this.#Wt=null}),{once:!0});t.addEventListener("mouseenter",(()=>{this.#Wt=setTimeout((()=>{this.#Wt=null;this.#$t.classList.add("show");this.#a._reportTelemetry({action:"alt_text_tooltip"})}),i)}),{signal:s});t.addEventListener("mouseleave",(()=>{if(this.#Wt){clearTimeout(this.#Wt);this.#Wt=null}this.#$t?.classList.remove("show")}),{signal:s})}if(this.#Ut)e.setAttribute("data-l10n-id","pdfjs-editor-alt-text-decorative-tooltip");else{e.removeAttribute("data-l10n-id");e.textContent=this.#o}e.parentNode||t.append(e);const i=this.#a.getElementForAltText();i?.setAttribute("aria-describedby",e.id)}}class TouchManager{#mt;#Zt=!1;#te=null;#ee;#ie;#se;#ne;#ae=null;#re;#oe=null;#le;#he=null;constructor({container:t,isPinchingDisabled:e=null,isPinchingStopped:i=null,onPinchStart:s=null,onPinching:n=null,onPinchEnd:a=null,signal:r}){this.#mt=t;this.#te=i;this.#ee=e;this.#ie=s;this.#se=n;this.#ne=a;this.#le=new AbortController;this.#re=AbortSignal.any([r,this.#le.signal]);t.addEventListener("touchstart",this.#de.bind(this),{passive:!1,signal:this.#re})}get MIN_TOUCH_DISTANCE_TO_PINCH(){return 35/OutputScale.pixelRatio}#de(t){if(this.#ee?.())return;if(1===t.touches.length){if(this.#ae)return;const t=this.#ae=new AbortController,e=AbortSignal.any([this.#re,t.signal]),i=this.#mt,s={capture:!0,signal:e,passive:!1},cancelPointerDown=t=>{if("touch"===t.pointerType){this.#ae?.abort();this.#ae=null}};i.addEventListener("pointerdown",(t=>{if("touch"===t.pointerType){stopEvent(t);cancelPointerDown(t)}}),s);i.addEventListener("pointerup",cancelPointerDown,s);i.addEventListener("pointercancel",cancelPointerDown,s);return}if(!this.#he){this.#he=new AbortController;const t=AbortSignal.any([this.#re,this.#he.signal]),e=this.#mt,i={signal:t,capture:!1,passive:!1};e.addEventListener("touchmove",this.#ce.bind(this),i);const s=this.#ue.bind(this);e.addEventListener("touchend",s,i);e.addEventListener("touchcancel",s,i);i.capture=!0;e.addEventListener("pointerdown",stopEvent,i);e.addEventListener("pointermove",stopEvent,i);e.addEventListener("pointercancel",stopEvent,i);e.addEventListener("pointerup",stopEvent,i);this.#ie?.()}stopEvent(t);if(2!==t.touches.length||this.#te?.()){this.#oe=null;return}let[e,i]=t.touches;e.identifier>i.identifier&&([e,i]=[i,e]);this.#oe={touch0X:e.screenX,touch0Y:e.screenY,touch1X:i.screenX,touch1Y:i.screenY}}#ce(t){if(!this.#oe||2!==t.touches.length)return;stopEvent(t);let[e,i]=t.touches;e.identifier>i.identifier&&([e,i]=[i,e]);const{screenX:s,screenY:n}=e,{screenX:a,screenY:r}=i,o=this.#oe,{touch0X:l,touch0Y:h,touch1X:d,touch1Y:c}=o,u=d-l,p=c-h,g=a-s,f=r-n,m=Math.hypot(g,f)||1,b=Math.hypot(u,p)||1;if(!this.#Zt&&Math.abs(b-m)<=TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH)return;o.touch0X=s;o.touch0Y=n;o.touch1X=a;o.touch1Y=r;if(!this.#Zt){this.#Zt=!0;return}const A=[(s+a)/2,(n+r)/2];this.#se?.(A,b,m)}#ue(t){if(!(t.touches.length>=2)){if(this.#he){this.#he.abort();this.#he=null;this.#ne?.()}if(this.#oe){stopEvent(t);this.#oe=null;this.#Zt=!1}}}destroy(){this.#le?.abort();this.#le=null;this.#ae?.abort();this.#ae=null}}class AnnotationEditor{#pe=null;#ge=null;#o=null;#fe=!1;#me=null;#be="";#Ae=!1;#we=null;#ye=null;#ve=null;#_e=null;#xe="";#Ee=!1;#Se=null;#Ce=!1;#Te=!1;#Me=!1;#De=null;#Pe=0;#ke=0;#Ie=null;#Re=null;_isCopy=!1;_editToolbar=null;_initialOptions=Object.create(null);_initialData=null;_isVisible=!0;_uiManager=null;_focusEventsAllowed=!0;static _l10n=null;static _l10nResizer=null;#Fe=!1;#Le=AnnotationEditor._zIndex++;static _borderLineWidth=-1;static _colorManager=new ColorManager;static _zIndex=1;static _telemetryTimeout=1e3;static get _resizerKeyboardManager(){const t=AnnotationEditor.prototype._resizeWithKeyboard,e=AnnotationEditorUIManager.TRANSLATE_SMALL,i=AnnotationEditorUIManager.TRANSLATE_BIG;return shadow(this,"_resizerKeyboardManager",new KeyboardManager([[["ArrowLeft","mac+ArrowLeft"],t,{args:[-e,0]}],[["ctrl+ArrowLeft","mac+shift+ArrowLeft"],t,{args:[-i,0]}],[["ArrowRight","mac+ArrowRight"],t,{args:[e,0]}],[["ctrl+ArrowRight","mac+shift+ArrowRight"],t,{args:[i,0]}],[["ArrowUp","mac+ArrowUp"],t,{args:[0,-e]}],[["ctrl+ArrowUp","mac+shift+ArrowUp"],t,{args:[0,-i]}],[["ArrowDown","mac+ArrowDown"],t,{args:[0,e]}],[["ctrl+ArrowDown","mac+shift+ArrowDown"],t,{args:[0,i]}],[["Escape","mac+Escape"],AnnotationEditor.prototype._stopResizingWithKeyboard]]))}constructor(t){this.parent=t.parent;this.id=t.id;this.width=this.height=null;this.pageIndex=t.parent.pageIndex;this.name=t.name;this.div=null;this._uiManager=t.uiManager;this.annotationElementId=null;this._willKeepAspectRatio=!1;this._initialOptions.isCentered=t.isCentered;this._structTreeParentId=null;const{rotation:e,rawDims:{pageWidth:i,pageHeight:s,pageX:n,pageY:a}}=this.parent.viewport;this.rotation=e;this.pageRotation=(360+e-this._uiManager.viewParameters.rotation)%360;this.pageDimensions=[i,s];this.pageTranslation=[n,a];const[r,o]=this.parentDimensions;this.x=t.x/r;this.y=t.y/o;this.isAttachedToDOM=!1;this.deleted=!1}get editorType(){return Object.getPrototypeOf(this).constructor._type}static get isDrawer(){return!1}static get _defaultLineColor(){return shadow(this,"_defaultLineColor",this._colorManager.getHexCode("CanvasText"))}static deleteAnnotationElement(t){const e=new FakeEditor({id:t.parent.getNextId(),parent:t.parent,uiManager:t._uiManager});e.annotationElementId=t.annotationElementId;e.deleted=!0;e._uiManager.addToAnnotationStorage(e)}static initialize(t,e){AnnotationEditor._l10n??=t;AnnotationEditor._l10nResizer||=Object.freeze({topLeft:"pdfjs-editor-resizer-top-left",topMiddle:"pdfjs-editor-resizer-top-middle",topRight:"pdfjs-editor-resizer-top-right",middleRight:"pdfjs-editor-resizer-middle-right",bottomRight:"pdfjs-editor-resizer-bottom-right",bottomMiddle:"pdfjs-editor-resizer-bottom-middle",bottomLeft:"pdfjs-editor-resizer-bottom-left",middleLeft:"pdfjs-editor-resizer-middle-left"});if(-1!==AnnotationEditor._borderLineWidth)return;const i=getComputedStyle(document.documentElement);AnnotationEditor._borderLineWidth=parseFloat(i.getPropertyValue("--outline-width"))||0}static updateDefaultParams(t,e){}static get defaultPropertiesToUpdate(){return[]}static isHandlingMimeForPasting(t){return!1}static paste(t,e){unreachable("Not implemented")}get propertiesToUpdate(){return[]}get _isDraggable(){return this.#Fe}set _isDraggable(t){this.#Fe=t;this.div?.classList.toggle("draggable",t)}get isEnterHandled(){return!0}center(){const[t,e]=this.pageDimensions;switch(this.parentRotation){case 90:this.x-=this.height*e/(2*t);this.y+=this.width*t/(2*e);break;case 180:this.x+=this.width/2;this.y+=this.height/2;break;case 270:this.x+=this.height*e/(2*t);this.y-=this.width*t/(2*e);break;default:this.x-=this.width/2;this.y-=this.height/2}this.fixAndSetPosition()}addCommands(t){this._uiManager.addCommands(t)}get currentLayer(){return this._uiManager.currentLayer}setInBackground(){this.div.style.zIndex=0}setInForeground(){this.div.style.zIndex=this.#Le}setParent(t){if(null!==t){this.pageIndex=t.pageIndex;this.pageDimensions=t.pageDimensions}else this.#Oe();this.parent=t}focusin(t){this._focusEventsAllowed&&(this.#Ee?this.#Ee=!1:this.parent.setSelected(this))}focusout(t){if(!this._focusEventsAllowed)return;if(!this.isAttachedToDOM)return;const e=t.relatedTarget;if(!e?.closest(`#${this.id}`)){t.preventDefault();this.parent?.isMultipleSelection||this.commitOrRemove()}}commitOrRemove(){this.isEmpty()?this.remove():this.commit()}commit(){this.addToAnnotationStorage()}addToAnnotationStorage(){this._uiManager.addToAnnotationStorage(this)}setAt(t,e,i,s){const[n,a]=this.parentDimensions;[i,s]=this.screenToPageTranslation(i,s);this.x=(t+i)/n;this.y=(e+s)/a;this.fixAndSetPosition()}_moveAfterPaste(t,e){const[i,s]=this.parentDimensions;this.setAt(t*i,e*s,this.width*i,this.height*s);this._onTranslated()}#Ne([t,e],i,s){[i,s]=this.screenToPageTranslation(i,s);this.x+=i/t;this.y+=s/e;this._onTranslating(this.x,this.y);this.fixAndSetPosition()}translate(t,e){this.#Ne(this.parentDimensions,t,e)}translateInPage(t,e){this.#Se||=[this.x,this.y,this.width,this.height];this.#Ne(this.pageDimensions,t,e);this.div.scrollIntoView({block:"nearest"})}translationDone(){this._onTranslated(this.x,this.y)}drag(t,e){this.#Se||=[this.x,this.y,this.width,this.height];const{div:i,parentDimensions:[s,n]}=this;this.x+=t/s;this.y+=e/n;if(this.parent&&(this.x<0||this.x>1||this.y<0||this.y>1)){const{x:t,y:e}=this.div.getBoundingClientRect();if(this.parent.findNewParent(this,t,e)){this.x-=Math.floor(this.x);this.y-=Math.floor(this.y)}}let{x:a,y:r}=this;const[o,l]=this.getBaseTranslation();a+=o;r+=l;const{style:h}=i;h.left=`${(100*a).toFixed(2)}%`;h.top=`${(100*r).toFixed(2)}%`;this._onTranslating(a,r);i.scrollIntoView({block:"nearest"})}_onTranslating(t,e){}_onTranslated(t,e){}get _hasBeenMoved(){return!!this.#Se&&(this.#Se[0]!==this.x||this.#Se[1]!==this.y)}get _hasBeenResized(){return!!this.#Se&&(this.#Se[2]!==this.width||this.#Se[3]!==this.height)}getBaseTranslation(){const[t,e]=this.parentDimensions,{_borderLineWidth:i}=AnnotationEditor,s=i/t,n=i/e;switch(this.rotation){case 90:return[-s,n];case 180:return[s,n];case 270:return[s,-n];default:return[-s,-n]}}get _mustFixPosition(){return!0}fixAndSetPosition(t=this.rotation){const{div:{style:e},pageDimensions:[i,s]}=this;let{x:n,y:a,width:r,height:o}=this;r*=i;o*=s;n*=i;a*=s;if(this._mustFixPosition)switch(t){case 0:n=MathClamp(n,0,i-r);a=MathClamp(a,0,s-o);break;case 90:n=MathClamp(n,0,i-o);a=MathClamp(a,r,s);break;case 180:n=MathClamp(n,r,i);a=MathClamp(a,o,s);break;case 270:n=MathClamp(n,o,i);a=MathClamp(a,0,s-r)}this.x=n/=i;this.y=a/=s;const[l,h]=this.getBaseTranslation();n+=l;a+=h;e.left=`${(100*n).toFixed(2)}%`;e.top=`${(100*a).toFixed(2)}%`;this.moveInDOM()}static#Be(t,e,i){switch(i){case 90:return[e,-t];case 180:return[-t,-e];case 270:return[-e,t];default:return[t,e]}}screenToPageTranslation(t,e){return AnnotationEditor.#Be(t,e,this.parentRotation)}pageTranslationToScreen(t,e){return AnnotationEditor.#Be(t,e,360-this.parentRotation)}#He(t){switch(t){case 90:{const[t,e]=this.pageDimensions;return[0,-t/e,e/t,0]}case 180:return[-1,0,0,-1];case 270:{const[t,e]=this.pageDimensions;return[0,t/e,-e/t,0]}default:return[1,0,0,1]}}get parentScale(){return this._uiManager.viewParameters.realScale}get parentRotation(){return(this._uiManager.viewParameters.rotation+this.pageRotation)%360}get parentDimensions(){const{parentScale:t,pageDimensions:[e,i]}=this;return[e*t,i*t]}setDims(t,e){const[i,s]=this.parentDimensions,{style:n}=this.div;n.width=`${(100*t/i).toFixed(2)}%`;this.#Ae||(n.height=`${(100*e/s).toFixed(2)}%`)}fixDims(){const{style:t}=this.div,{height:e,width:i}=t,s=i.endsWith("%"),n=!this.#Ae&&e.endsWith("%");if(s&&n)return;const[a,r]=this.parentDimensions;s||(t.width=`${(100*parseFloat(i)/a).toFixed(2)}%`);this.#Ae||n||(t.height=`${(100*parseFloat(e)/r).toFixed(2)}%`)}getInitialTranslation(){return[0,0]}#Ue(){if(this.#we)return;this.#we=document.createElement("div");this.#we.classList.add("resizers");const t=this._willKeepAspectRatio?["topLeft","topRight","bottomRight","bottomLeft"]:["topLeft","topMiddle","topRight","middleRight","bottomRight","bottomMiddle","bottomLeft","middleLeft"],e=this._uiManager._signal;for(const i of t){const t=document.createElement("div");this.#we.append(t);t.classList.add("resizer",i);t.setAttribute("data-resizer-name",i);t.addEventListener("pointerdown",this.#ze.bind(this,i),{signal:e});t.addEventListener("contextmenu",noContextMenu,{signal:e});t.tabIndex=-1}this.div.prepend(this.#we)}#ze(t,e){e.preventDefault();const{isMac:i}=util_FeatureTest.platform;if(0!==e.button||e.ctrlKey&&i)return;this.#o?.toggle(!1);const s=this._isDraggable;this._isDraggable=!1;this.#ye=[e.screenX,e.screenY];const n=new AbortController,a=this._uiManager.combinedSignal(n);this.parent.togglePointerEvents(!1);window.addEventListener("pointermove",this.#Ge.bind(this,t),{passive:!0,capture:!0,signal:a});window.addEventListener("touchmove",stopEvent,{passive:!1,signal:a});window.addEventListener("contextmenu",noContextMenu,{signal:a});this.#ve={savedX:this.x,savedY:this.y,savedWidth:this.width,savedHeight:this.height};const r=this.parent.div.style.cursor,o=this.div.style.cursor;this.div.style.cursor=this.parent.div.style.cursor=window.getComputedStyle(e.target).cursor;const pointerUpCallback=()=>{n.abort();this.parent.togglePointerEvents(!0);this.#o?.toggle(!0);this._isDraggable=s;this.parent.div.style.cursor=r;this.div.style.cursor=o;this.#$e()};window.addEventListener("pointerup",pointerUpCallback,{signal:a});window.addEventListener("blur",pointerUpCallback,{signal:a})}#We(t,e,i,s){this.width=i;this.height=s;this.x=t;this.y=e;const[n,a]=this.parentDimensions;this.setDims(n*i,a*s);this.fixAndSetPosition();this._onResized()}_onResized(){}#$e(){if(!this.#ve)return;const{savedX:t,savedY:e,savedWidth:i,savedHeight:s}=this.#ve;this.#ve=null;const n=this.x,a=this.y,r=this.width,o=this.height;n===t&&a===e&&r===i&&o===s||this.addCommands({cmd:this.#We.bind(this,n,a,r,o),undo:this.#We.bind(this,t,e,i,s),mustExec:!0})}static _round(t){return Math.round(1e4*t)/1e4}#Ge(t,e){const[i,s]=this.parentDimensions,n=this.x,a=this.y,r=this.width,o=this.height,l=AnnotationEditor.MIN_SIZE/i,h=AnnotationEditor.MIN_SIZE/s,d=this.#He(this.rotation),transf=(t,e)=>[d[0]*t+d[2]*e,d[1]*t+d[3]*e],c=this.#He(360-this.rotation);let u,p,g=!1,f=!1;switch(t){case"topLeft":g=!0;u=(t,e)=>[0,0];p=(t,e)=>[t,e];break;case"topMiddle":u=(t,e)=>[t/2,0];p=(t,e)=>[t/2,e];break;case"topRight":g=!0;u=(t,e)=>[t,0];p=(t,e)=>[0,e];break;case"middleRight":f=!0;u=(t,e)=>[t,e/2];p=(t,e)=>[0,e/2];break;case"bottomRight":g=!0;u=(t,e)=>[t,e];p=(t,e)=>[0,0];break;case"bottomMiddle":u=(t,e)=>[t/2,e];p=(t,e)=>[t/2,0];break;case"bottomLeft":g=!0;u=(t,e)=>[0,e];p=(t,e)=>[t,0];break;case"middleLeft":f=!0;u=(t,e)=>[0,e/2];p=(t,e)=>[t,e/2]}const m=u(r,o),b=p(r,o);let A=transf(...b);const w=AnnotationEditor._round(n+A[0]),y=AnnotationEditor._round(a+A[1]);let v,_,x=1,E=1;if(e.fromKeyboard)({deltaX:v,deltaY:_}=e);else{const{screenX:t,screenY:i}=e,[s,n]=this.#ye;[v,_]=this.screenToPageTranslation(t-s,i-n);this.#ye[0]=t;this.#ye[1]=i}[v,_]=(S=v/i,C=_/s,[c[0]*S+c[2]*C,c[1]*S+c[3]*C]);var S,C;if(g){const t=Math.hypot(r,o);x=E=Math.max(Math.min(Math.hypot(b[0]-m[0]-v,b[1]-m[1]-_)/t,1/r,1/o),l/r,h/o)}else f?x=MathClamp(Math.abs(b[0]-m[0]-v),l,1)/r:E=MathClamp(Math.abs(b[1]-m[1]-_),h,1)/o;const T=AnnotationEditor._round(r*x),M=AnnotationEditor._round(o*E);A=transf(...p(T,M));const D=w-A[0],P=y-A[1];this.#Se||=[this.x,this.y,this.width,this.height];this.width=T;this.height=M;this.x=D;this.y=P;this.setDims(i*T,s*M);this.fixAndSetPosition();this._onResizing()}_onResizing(){}altTextFinish(){this.#o?.finish()}async addEditToolbar(){if(this._editToolbar||this.#Te)return this._editToolbar;this._editToolbar=new EditorToolbar(this);this.div.append(this._editToolbar.render());this.#o&&await this._editToolbar.addAltText(this.#o);return this._editToolbar}removeEditToolbar(){if(this._editToolbar){this._editToolbar.remove();this._editToolbar=null;this.#o?.destroy()}}addContainer(t){const e=this._editToolbar?.div;e?e.before(t):this.div.append(t)}getClientDimensions(){return this.div.getBoundingClientRect()}async addAltTextButton(){if(!this.#o){AltText.initialize(AnnotationEditor._l10n);this.#o=new AltText(this);if(this.#pe){this.#o.data=this.#pe;this.#pe=null}await this.addEditToolbar()}}get altTextData(){return this.#o?.data}set altTextData(t){this.#o&&(this.#o.data=t)}get guessedAltText(){return this.#o?.guessedText}async setGuessedAltText(t){await(this.#o?.setGuessedText(t))}serializeAltText(t){return this.#o?.serialize(t)}hasAltText(){return!!this.#o&&!this.#o.isEmpty()}hasAltTextData(){return this.#o?.hasData()??!1}render(){const t=this.div=document.createElement("div");t.setAttribute("data-editor-rotation",(360-this.rotation)%360);t.className=this.name;t.setAttribute("id",this.id);t.tabIndex=this.#fe?-1:0;t.setAttribute("role","application");this.defaultL10nId&&t.setAttribute("data-l10n-id",this.defaultL10nId);this._isVisible||t.classList.add("hidden");this.setInForeground();this.#Ve();const[e,i]=this.parentDimensions;if(this.parentRotation%180!=0){t.style.maxWidth=`${(100*i/e).toFixed(2)}%`;t.style.maxHeight=`${(100*e/i).toFixed(2)}%`}const[s,n]=this.getInitialTranslation();this.translate(s,n);bindEvents(this,t,["keydown","pointerdown"]);this.isResizable&&this._uiManager._supportsPinchToZoom&&(this.#Re||=new TouchManager({container:t,isPinchingDisabled:()=>!this.isSelected,onPinchStart:this.#je.bind(this),onPinching:this.#qe.bind(this),onPinchEnd:this.#Xe.bind(this),signal:this._uiManager._signal}));this._uiManager._editorUndoBar?.hide();return t}#je(){this.#ve={savedX:this.x,savedY:this.y,savedWidth:this.width,savedHeight:this.height};this.#o?.toggle(!1);this.parent.togglePointerEvents(!1)}#qe(t,e,i){let s=i/e*.7+1-.7;if(1===s)return;const n=this.#He(this.rotation),transf=(t,e)=>[n[0]*t+n[2]*e,n[1]*t+n[3]*e],[a,r]=this.parentDimensions,o=this.x,l=this.y,h=this.width,d=this.height,c=AnnotationEditor.MIN_SIZE/a,u=AnnotationEditor.MIN_SIZE/r;s=Math.max(Math.min(s,1/h,1/d),c/h,u/d);const p=AnnotationEditor._round(h*s),g=AnnotationEditor._round(d*s);if(p===h&&g===d)return;this.#Se||=[o,l,h,d];const f=transf(h/2,d/2),m=AnnotationEditor._round(o+f[0]),b=AnnotationEditor._round(l+f[1]),A=transf(p/2,g/2);this.x=m-A[0];this.y=b-A[1];this.width=p;this.height=g;this.setDims(a*p,r*g);this.fixAndSetPosition();this._onResizing()}#Xe(){this.#o?.toggle(!0);this.parent.togglePointerEvents(!0);this.#$e()}pointerdown(t){const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)t.preventDefault();else{this.#Ee=!0;this._isDraggable?this.#Ke(t):this.#Ye(t)}}get isSelected(){return this._uiManager.isSelected(this)}#Ye(t){const{isMac:e}=util_FeatureTest.platform;t.ctrlKey&&!e||t.shiftKey||t.metaKey&&e?this.parent.toggleSelected(this):this.parent.setSelected(this)}#Ke(t){const{isSelected:e}=this;this._uiManager.setUpDragSession();let i=!1;const s=new AbortController,n=this._uiManager.combinedSignal(s),a={capture:!0,passive:!1,signal:n},cancelDrag=t=>{s.abort();this.#me=null;this.#Ee=!1;this._uiManager.endDragSession()||this.#Ye(t);i&&this._onStopDragging()};if(e){this.#Pe=t.clientX;this.#ke=t.clientY;this.#me=t.pointerId;this.#be=t.pointerType;window.addEventListener("pointermove",(t=>{if(!i){i=!0;this._onStartDragging()}const{clientX:e,clientY:s,pointerId:n}=t;if(n!==this.#me){stopEvent(t);return}const[a,r]=this.screenToPageTranslation(e-this.#Pe,s-this.#ke);this.#Pe=e;this.#ke=s;this._uiManager.dragSelectedEditors(a,r)}),a);window.addEventListener("touchmove",stopEvent,a);window.addEventListener("pointerdown",(t=>{t.pointerType===this.#be&&(this.#Re||t.isPrimary)&&cancelDrag(t);stopEvent(t)}),a)}const pointerUpCallback=t=>{this.#me&&this.#me!==t.pointerId?stopEvent(t):cancelDrag(t)};window.addEventListener("pointerup",pointerUpCallback,{signal:n});window.addEventListener("blur",pointerUpCallback,{signal:n})}_onStartDragging(){}_onStopDragging(){}moveInDOM(){this.#De&&clearTimeout(this.#De);this.#De=setTimeout((()=>{this.#De=null;this.parent?.moveEditorInDOM(this)}),0)}_setParentAndPosition(t,e,i){t.changeParent(this);this.x=e;this.y=i;this.fixAndSetPosition();this._onTranslated()}getRect(t,e,i=this.rotation){const s=this.parentScale,[n,a]=this.pageDimensions,[r,o]=this.pageTranslation,l=t/s,h=e/s,d=this.x*n,c=this.y*a,u=this.width*n,p=this.height*a;switch(i){case 0:return[d+l+r,a-c-h-p+o,d+l+u+r,a-c-h+o];case 90:return[d+h+r,a-c+l+o,d+h+p+r,a-c+l+u+o];case 180:return[d-l-u+r,a-c+h+o,d-l+r,a-c+h+p+o];case 270:return[d-h-p+r,a-c-l-u+o,d-h+r,a-c-l+o];default:throw new Error("Invalid rotation")}}getRectInCurrentCoords(t,e){const[i,s,n,a]=t,r=n-i,o=a-s;switch(this.rotation){case 0:return[i,e-a,r,o];case 90:return[i,e-s,o,r];case 180:return[n,e-s,r,o];case 270:return[n,e-a,o,r];default:throw new Error("Invalid rotation")}}onceAdded(t){}isEmpty(){return!1}enableEditMode(){this.#Te=!0}disableEditMode(){this.#Te=!1}isInEditMode(){return this.#Te}shouldGetKeyboardEvents(){return this.#Me}needsToBeRebuilt(){return this.div&&!this.isAttachedToDOM}get isOnScreen(){const{top:t,left:e,bottom:i,right:s}=this.getClientDimensions(),{innerHeight:n,innerWidth:a}=window;return e0&&t0}#Ve(){if(this.#_e||!this.div)return;this.#_e=new AbortController;const t=this._uiManager.combinedSignal(this.#_e);this.div.addEventListener("focusin",this.focusin.bind(this),{signal:t});this.div.addEventListener("focusout",this.focusout.bind(this),{signal:t})}rebuild(){this.#Ve()}rotate(t){}resize(){}serializeDeleted(){return{id:this.annotationElementId,deleted:!0,pageIndex:this.pageIndex,popupRef:this._initialData?.popupRef||""}}serialize(t=!1,e=null){unreachable("An editor must be serializable")}static async deserialize(t,e,i){const s=new this.prototype.constructor({parent:e,id:e.getNextId(),uiManager:i});s.rotation=t.rotation;s.#pe=t.accessibilityData;s._isCopy=t.isCopy||!1;const[n,a]=s.pageDimensions,[r,o,l,h]=s.getRectInCurrentCoords(t.rect,a);s.x=r/n;s.y=o/a;s.width=l/n;s.height=h/a;return s}get hasBeenModified(){return!!this.annotationElementId&&(this.deleted||null!==this.serialize())}remove(){this.#_e?.abort();this.#_e=null;this.isEmpty()||this.commit();this.parent?this.parent.remove(this):this._uiManager.removeEditor(this);if(this.#De){clearTimeout(this.#De);this.#De=null}this.#Oe();this.removeEditToolbar();if(this.#Ie){for(const t of this.#Ie.values())clearTimeout(t);this.#Ie=null}this.parent=null;this.#Re?.destroy();this.#Re=null}get isResizable(){return!1}makeResizable(){if(this.isResizable){this.#Ue();this.#we.classList.remove("hidden")}}get toolbarPosition(){return null}keydown(t){if(!this.isResizable||t.target!==this.div||"Enter"!==t.key)return;this._uiManager.setSelected(this);this.#ve={savedX:this.x,savedY:this.y,savedWidth:this.width,savedHeight:this.height};const e=this.#we.children;if(!this.#ge){this.#ge=Array.from(e);const t=this.#Qe.bind(this),i=this.#Je.bind(this),s=this._uiManager._signal;for(const e of this.#ge){const n=e.getAttribute("data-resizer-name");e.setAttribute("role","spinbutton");e.addEventListener("keydown",t,{signal:s});e.addEventListener("blur",i,{signal:s});e.addEventListener("focus",this.#Ze.bind(this,n),{signal:s});e.setAttribute("data-l10n-id",AnnotationEditor._l10nResizer[n])}}const i=this.#ge[0];let s=0;for(const t of e){if(t===i)break;s++}const n=(360-this.rotation+this.parentRotation)%360/90*(this.#ge.length/4);if(n!==s){if(ns)for(let t=0;t{this.div?.classList.contains("selectedEditor")&&this._editToolbar?.show()}))}unselect(){this.#we?.classList.add("hidden");this.div?.classList.remove("selectedEditor");this.div?.contains(document.activeElement)&&this._uiManager.currentLayer.div.focus({preventScroll:!0});this._editToolbar?.hide();this.#o?.toggleAltTextBadge(!0)}updateParams(t,e){}disableEditing(){}enableEditing(){}enterInEditMode(){}getElementForAltText(){return this.div}get contentDiv(){return this.div}get isEditing(){return this.#Ce}set isEditing(t){this.#Ce=t;if(this.parent)if(t){this.parent.setSelected(this);this.parent.setActiveEditor(this)}else this.parent.setActiveEditor(null)}setAspectRatio(t,e){this.#Ae=!0;const i=t/e,{style:s}=this.div;s.aspectRatio=i;s.height="auto"}static get MIN_SIZE(){return 16}static canCreateNewEmptyEditor(){return!0}get telemetryInitialData(){return{action:"added"}}get telemetryFinalData(){return null}_reportTelemetry(t,e=!1){if(e){this.#Ie||=new Map;const{action:e}=t;let i=this.#Ie.get(e);i&&clearTimeout(i);i=setTimeout((()=>{this._reportTelemetry(t);this.#Ie.delete(e);0===this.#Ie.size&&(this.#Ie=null)}),AnnotationEditor._telemetryTimeout);this.#Ie.set(e,i)}else{t.type||=this.editorType;this._uiManager._eventBus.dispatch("reporttelemetry",{source:this,details:{type:"editing",data:t}})}}show(t=this._isVisible){this.div.classList.toggle("hidden",!t);this._isVisible=t}enable(){this.div&&(this.div.tabIndex=0);this.#fe=!1}disable(){this.div&&(this.div.tabIndex=-1);this.#fe=!0}renderAnnotationElement(t){let e=t.container.querySelector(".annotationContent");if(e){if("CANVAS"===e.nodeName){const t=e;e=document.createElement("div");e.classList.add("annotationContent",this.editorType);t.before(e)}}else{e=document.createElement("div");e.classList.add("annotationContent",this.editorType);t.container.prepend(e)}return e}resetAnnotationElement(t){const{firstChild:e}=t.container;"DIV"===e?.nodeName&&e.classList.contains("annotationContent")&&e.remove()}}class FakeEditor extends AnnotationEditor{constructor(t){super(t);this.annotationElementId=t.annotationElementId;this.deleted=!0}serialize(){return this.serializeDeleted()}}const $=3285377520,W=4294901760,V=65535;class MurmurHash3_64{constructor(t){this.h1=t?4294967295&t:$;this.h2=t?4294967295&t:$}update(t){let e,i;if("string"==typeof t){e=new Uint8Array(2*t.length);i=0;for(let s=0,n=t.length;s>>8;e[i++]=255&n}}}else{if(!ArrayBuffer.isView(t))throw new Error("Invalid data format, must be a string or TypedArray.");e=t.slice();i=e.byteLength}const s=i>>2,n=i-4*s,a=new Uint32Array(e.buffer,0,s);let r=0,o=0,l=this.h1,h=this.h2;const d=3432918353,c=461845907,u=11601,p=13715;for(let t=0;t>>17;r=r*c&W|r*p&V;l^=r;l=l<<13|l>>>19;l=5*l+3864292196}else{o=a[t];o=o*d&W|o*u&V;o=o<<15|o>>>17;o=o*c&W|o*p&V;h^=o;h=h<<13|h>>>19;h=5*h+3864292196}r=0;switch(n){case 3:r^=e[4*s+2]<<16;case 2:r^=e[4*s+1]<<8;case 1:r^=e[4*s];r=r*d&W|r*u&V;r=r<<15|r>>>17;r=r*c&W|r*p&V;1&s?l^=r:h^=r}this.h1=l;this.h2=h}hexdigest(){let t=this.h1,e=this.h2;t^=e>>>1;t=3981806797*t&W|36045*t&V;e=4283543511*e&W|(2950163797*(e<<16|t>>>16)&W)>>>16;t^=e>>>1;t=444984403*t&W|60499*t&V;e=3301882366*e&W|(3120437893*(e<<16|t>>>16)&W)>>>16;t^=e>>>1;return(t>>>0).toString(16).padStart(8,"0")+(e>>>0).toString(16).padStart(8,"0")}}const j=Object.freeze({map:null,hash:"",transfer:void 0});class AnnotationStorage{#ei=!1;#ii=null;#si=new Map;constructor(){this.onSetModified=null;this.onResetModified=null;this.onAnnotationEditor=null}getValue(t,e){const i=this.#si.get(t);return void 0===i?e:Object.assign(e,i)}getRawValue(t){return this.#si.get(t)}remove(t){this.#si.delete(t);0===this.#si.size&&this.resetModified();if("function"==typeof this.onAnnotationEditor){for(const t of this.#si.values())if(t instanceof AnnotationEditor)return;this.onAnnotationEditor(null)}}setValue(t,e){const i=this.#si.get(t);let s=!1;if(void 0!==i){for(const[t,n]of Object.entries(e))if(i[t]!==n){s=!0;i[t]=n}}else{s=!0;this.#si.set(t,e)}s&&this.#ni();e instanceof AnnotationEditor&&"function"==typeof this.onAnnotationEditor&&this.onAnnotationEditor(e.constructor._type)}has(t){return this.#si.has(t)}get size(){return this.#si.size}#ni(){if(!this.#ei){this.#ei=!0;"function"==typeof this.onSetModified&&this.onSetModified()}}resetModified(){if(this.#ei){this.#ei=!1;"function"==typeof this.onResetModified&&this.onResetModified()}}get print(){return new PrintAnnotationStorage(this)}get serializable(){if(0===this.#si.size)return j;const t=new Map,e=new MurmurHash3_64,i=[],s=Object.create(null);let n=!1;for(const[i,a]of this.#si){const r=a instanceof AnnotationEditor?a.serialize(!1,s):a;if(r){t.set(i,r);e.update(`${i}:${JSON.stringify(r)}`);n||=!!r.bitmap}}if(n)for(const e of t.values())e.bitmap&&i.push(e.bitmap);return t.size>0?{map:t,hash:e.hexdigest(),transfer:i}:j}get editorStats(){let t=null;const e=new Map;for(const i of this.#si.values()){if(!(i instanceof AnnotationEditor))continue;const s=i.telemetryFinalData;if(!s)continue;const{type:n}=s;e.has(n)||e.set(n,Object.getPrototypeOf(i).constructor);t||=Object.create(null);const a=t[n]||=new Map;for(const[t,e]of Object.entries(s)){if("type"===t)continue;let i=a.get(t);if(!i){i=new Map;a.set(t,i)}const s=i.get(e)??0;i.set(e,s+1)}}for(const[i,s]of e)t[i]=s.computeTelemetryFinalData(t[i]);return t}resetModifiedIds(){this.#ii=null}get modifiedIds(){if(this.#ii)return this.#ii;const t=[];for(const e of this.#si.values())e instanceof AnnotationEditor&&e.annotationElementId&&e.serialize()&&t.push(e.annotationElementId);return this.#ii={ids:new Set(t),hash:t.join(",")}}[Symbol.iterator](){return this.#si.entries()}}class PrintAnnotationStorage extends AnnotationStorage{#ai;constructor(t){super();const{map:e,hash:i,transfer:s}=t.serializable,n=structuredClone(e,s?{transfer:s}:null);this.#ai={map:n,hash:i,transfer:s}}get print(){unreachable("Should not call PrintAnnotationStorage.print")}get serializable(){return this.#ai}get modifiedIds(){return shadow(this,"modifiedIds",{ids:new Set,hash:""})}}class FontLoader{#ri=new Set;constructor({ownerDocument:t=globalThis.document,styleElement:e=null}){this._document=t;this.nativeFontFaces=new Set;this.styleElement=null;this.loadingRequests=[];this.loadTestFontId=0}addNativeFontFace(t){this.nativeFontFaces.add(t);this._document.fonts.add(t)}removeNativeFontFace(t){this.nativeFontFaces.delete(t);this._document.fonts.delete(t)}insertRule(t){if(!this.styleElement){this.styleElement=this._document.createElement("style");this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement)}const e=this.styleElement.sheet;e.insertRule(t,e.cssRules.length)}clear(){for(const t of this.nativeFontFaces)this._document.fonts.delete(t);this.nativeFontFaces.clear();this.#ri.clear();if(this.styleElement){this.styleElement.remove();this.styleElement=null}}async loadSystemFont({systemFontInfo:t,disableFontFace:e,_inspectFont:i}){if(t&&!this.#ri.has(t.loadedName)){assert(!e,"loadSystemFont shouldn't be called when `disableFontFace` is set.");if(this.isFontLoadingAPISupported){const{loadedName:e,src:s,style:n}=t,a=new FontFace(e,s,n);this.addNativeFontFace(a);try{await a.load();this.#ri.add(e);i?.(t)}catch{warn(`Cannot load system font: ${t.baseFontName}, installing it could help to improve PDF rendering.`);this.removeNativeFontFace(a)}}else unreachable("Not implemented: loadSystemFont without the Font Loading API.")}}async bind(t){if(t.attached||t.missingFile&&!t.systemFontInfo)return;t.attached=!0;if(t.systemFontInfo){await this.loadSystemFont(t);return}if(this.isFontLoadingAPISupported){const e=t.createNativeFontFace();if(e){this.addNativeFontFace(e);try{await e.loaded}catch(i){warn(`Failed to load font '${e.family}': '${i}'.`);t.disableFontFace=!0;throw i}}return}const e=t.createFontFaceRule();if(e){this.insertRule(e);if(this.isSyncFontLoadingSupported)return;await new Promise((e=>{const i=this._queueLoadingCallback(e);this._prepareFontLoadEvent(t,i)}))}}get isFontLoadingAPISupported(){return shadow(this,"isFontLoadingAPISupported",!!this._document?.fonts)}get isSyncFontLoadingSupported(){return shadow(this,"isSyncFontLoadingSupported",t||util_FeatureTest.platform.isFirefox)}_queueLoadingCallback(t){const{loadingRequests:e}=this,i={done:!1,complete:function completeRequest(){assert(!i.done,"completeRequest() cannot be called twice.");i.done=!0;for(;e.length>0&&e[0].done;){const t=e.shift();setTimeout(t.callback,0)}},callback:t};e.push(i);return i}get _loadTestFont(){return shadow(this,"_loadTestFont",atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQAFQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAAALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgAAAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACMAooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4DIP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAAAAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUAAQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgABAAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABYAAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAAAC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQACAQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA=="))}_prepareFontLoadEvent(t,e){function int32(t,e){return t.charCodeAt(e)<<24|t.charCodeAt(e+1)<<16|t.charCodeAt(e+2)<<8|255&t.charCodeAt(e+3)}function spliceString(t,e,i,s){return t.substring(0,e)+s+t.substring(e+i)}let i,s;const n=this._document.createElement("canvas");n.width=1;n.height=1;const a=n.getContext("2d");let r=0;const o=`lt${Date.now()}${this.loadTestFontId++}`;let l=this._loadTestFont;l=spliceString(l,976,o.length,o);const h=1482184792;let d=int32(l,16);for(i=0,s=o.length-3;i>24&255,t>>16&255,t>>8&255,255&t)}(d));const c=`@font-face {font-family:"${o}";src:${`url(data:font/opentype;base64,${btoa(l)});`}}`;this.insertRule(c);const u=this._document.createElement("div");u.style.visibility="hidden";u.style.width=u.style.height="10px";u.style.position="absolute";u.style.top=u.style.left="0px";for(const e of[t.loadedName,o]){const t=this._document.createElement("span");t.textContent="Hi";t.style.fontFamily=e;u.append(t)}this._document.body.append(u);!function isFontReady(t,e){if(++r>30){warn("Load test font never loaded.");e();return}a.font="30px "+t;a.fillText(".",0,20);a.getImageData(0,0,1,1).data[3]>0?e():setTimeout(isFontReady.bind(null,t,e))}(o,(()=>{u.remove();e.complete()}))}}class FontFaceObject{constructor(t,e=null){this.compiledGlyphs=Object.create(null);for(const e in t)this[e]=t[e];this._inspectFont=e}createNativeFontFace(){if(!this.data||this.disableFontFace)return null;let t;if(this.cssFontInfo){const e={weight:this.cssFontInfo.fontWeight};this.cssFontInfo.italicAngle&&(e.style=`oblique ${this.cssFontInfo.italicAngle}deg`);t=new FontFace(this.cssFontInfo.fontFamily,this.data,e)}else t=new FontFace(this.loadedName,this.data,{});this._inspectFont?.(this);return t}createFontFaceRule(){if(!this.data||this.disableFontFace)return null;const t=`url(data:${this.mimetype};base64,${toBase64Util(this.data)});`;let e;if(this.cssFontInfo){let i=`font-weight: ${this.cssFontInfo.fontWeight};`;this.cssFontInfo.italicAngle&&(i+=`font-style: oblique ${this.cssFontInfo.italicAngle}deg;`);e=`@font-face {font-family:"${this.cssFontInfo.fontFamily}";${i}src:${t}}`}else e=`@font-face {font-family:"${this.loadedName}";src:${t}}`;this._inspectFont?.(this,t);return e}getPathGenerator(t,e){if(void 0!==this.compiledGlyphs[e])return this.compiledGlyphs[e];const i=this.loadedName+"_path_"+e;let s;try{s=t.get(i)}catch(t){warn(`getPathGenerator - ignoring character: "${t}".`)}const n=new Path2D(s||"");this.fontExtraProperties||t.delete(i);return this.compiledGlyphs[e]=n}}const q=1,X=2,K=1,Y=2,Q=3,J=4,Z=5,tt=6,et=7,it=8;function onFn(){}function wrapReason(t){if(t instanceof AbortException||t instanceof InvalidPDFException||t instanceof PasswordException||t instanceof ResponseException||t instanceof UnknownErrorException)return t;t instanceof Error||"object"==typeof t&&null!==t||unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');switch(t.name){case"AbortException":return new AbortException(t.message);case"InvalidPDFException":return new InvalidPDFException(t.message);case"PasswordException":return new PasswordException(t.message,t.code);case"ResponseException":return new ResponseException(t.message,t.status,t.missing);case"UnknownErrorException":return new UnknownErrorException(t.message,t.details)}return new UnknownErrorException(t.message,t.toString())}class MessageHandler{#oi=new AbortController;constructor(t,e,i){this.sourceName=t;this.targetName=e;this.comObj=i;this.callbackId=1;this.streamId=1;this.streamSinks=Object.create(null);this.streamControllers=Object.create(null);this.callbackCapabilities=Object.create(null);this.actionHandler=Object.create(null);i.addEventListener("message",this.#li.bind(this),{signal:this.#oi.signal})}#li({data:t}){if(t.targetName!==this.sourceName)return;if(t.stream){this.#hi(t);return}if(t.callback){const e=t.callbackId,i=this.callbackCapabilities[e];if(!i)throw new Error(`Cannot resolve callback ${e}`);delete this.callbackCapabilities[e];if(t.callback===q)i.resolve(t.data);else{if(t.callback!==X)throw new Error("Unexpected callback case");i.reject(wrapReason(t.reason))}return}const e=this.actionHandler[t.action];if(!e)throw new Error(`Unknown action from worker: ${t.action}`);if(t.callbackId){const i=this.sourceName,s=t.sourceName,n=this.comObj;Promise.try(e,t.data).then((function(e){n.postMessage({sourceName:i,targetName:s,callback:q,callbackId:t.callbackId,data:e})}),(function(e){n.postMessage({sourceName:i,targetName:s,callback:X,callbackId:t.callbackId,reason:wrapReason(e)})}))}else t.streamId?this.#di(t):e(t.data)}on(t,e){const i=this.actionHandler;if(i[t])throw new Error(`There is already an actionName called "${t}"`);i[t]=e}send(t,e,i){this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:t,data:e},i)}sendWithPromise(t,e,i){const s=this.callbackId++,n=Promise.withResolvers();this.callbackCapabilities[s]=n;try{this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:t,callbackId:s,data:e},i)}catch(t){n.reject(t)}return n.promise}sendWithStream(t,e,i,s){const n=this.streamId++,a=this.sourceName,r=this.targetName,o=this.comObj;return new ReadableStream({start:i=>{const l=Promise.withResolvers();this.streamControllers[n]={controller:i,startCall:l,pullCall:null,cancelCall:null,isClosed:!1};o.postMessage({sourceName:a,targetName:r,action:t,streamId:n,data:e,desiredSize:i.desiredSize},s);return l.promise},pull:t=>{const e=Promise.withResolvers();this.streamControllers[n].pullCall=e;o.postMessage({sourceName:a,targetName:r,stream:tt,streamId:n,desiredSize:t.desiredSize});return e.promise},cancel:t=>{assert(t instanceof Error,"cancel must have a valid reason");const e=Promise.withResolvers();this.streamControllers[n].cancelCall=e;this.streamControllers[n].isClosed=!0;o.postMessage({sourceName:a,targetName:r,stream:K,streamId:n,reason:wrapReason(t)});return e.promise}},i)}#di(t){const e=t.streamId,i=this.sourceName,s=t.sourceName,n=this.comObj,a=this,r=this.actionHandler[t.action],o={enqueue(t,a=1,r){if(this.isCancelled)return;const o=this.desiredSize;this.desiredSize-=a;if(o>0&&this.desiredSize<=0){this.sinkCapability=Promise.withResolvers();this.ready=this.sinkCapability.promise}n.postMessage({sourceName:i,targetName:s,stream:J,streamId:e,chunk:t},r)},close(){if(!this.isCancelled){this.isCancelled=!0;n.postMessage({sourceName:i,targetName:s,stream:Q,streamId:e});delete a.streamSinks[e]}},error(t){assert(t instanceof Error,"error must have a valid reason");if(!this.isCancelled){this.isCancelled=!0;n.postMessage({sourceName:i,targetName:s,stream:Z,streamId:e,reason:wrapReason(t)})}},sinkCapability:Promise.withResolvers(),onPull:null,onCancel:null,isCancelled:!1,desiredSize:t.desiredSize,ready:null};o.sinkCapability.resolve();o.ready=o.sinkCapability.promise;this.streamSinks[e]=o;Promise.try(r,t.data,o).then((function(){n.postMessage({sourceName:i,targetName:s,stream:it,streamId:e,success:!0})}),(function(t){n.postMessage({sourceName:i,targetName:s,stream:it,streamId:e,reason:wrapReason(t)})}))}#hi(t){const e=t.streamId,i=this.sourceName,s=t.sourceName,n=this.comObj,a=this.streamControllers[e],r=this.streamSinks[e];switch(t.stream){case it:t.success?a.startCall.resolve():a.startCall.reject(wrapReason(t.reason));break;case et:t.success?a.pullCall.resolve():a.pullCall.reject(wrapReason(t.reason));break;case tt:if(!r){n.postMessage({sourceName:i,targetName:s,stream:et,streamId:e,success:!0});break}r.desiredSize<=0&&t.desiredSize>0&&r.sinkCapability.resolve();r.desiredSize=t.desiredSize;Promise.try(r.onPull||onFn).then((function(){n.postMessage({sourceName:i,targetName:s,stream:et,streamId:e,success:!0})}),(function(t){n.postMessage({sourceName:i,targetName:s,stream:et,streamId:e,reason:wrapReason(t)})}));break;case J:assert(a,"enqueue should have stream controller");if(a.isClosed)break;a.controller.enqueue(t.chunk);break;case Q:assert(a,"close should have stream controller");if(a.isClosed)break;a.isClosed=!0;a.controller.close();this.#ci(a,e);break;case Z:assert(a,"error should have stream controller");a.controller.error(wrapReason(t.reason));this.#ci(a,e);break;case Y:t.success?a.cancelCall.resolve():a.cancelCall.reject(wrapReason(t.reason));this.#ci(a,e);break;case K:if(!r)break;const o=wrapReason(t.reason);Promise.try(r.onCancel||onFn,o).then((function(){n.postMessage({sourceName:i,targetName:s,stream:Y,streamId:e,success:!0})}),(function(t){n.postMessage({sourceName:i,targetName:s,stream:Y,streamId:e,reason:wrapReason(t)})}));r.sinkCapability.reject(o);r.isCancelled=!0;delete this.streamSinks[e];break;default:throw new Error("Unexpected stream case")}}async#ci(t,e){await Promise.allSettled([t.startCall?.promise,t.pullCall?.promise,t.cancelCall?.promise]);delete this.streamControllers[e]}destroy(){this.#oi?.abort();this.#oi=null}}class BaseCanvasFactory{#ui=!1;constructor({enableHWA:t=!1}){this.#ui=t}create(t,e){if(t<=0||e<=0)throw new Error("Invalid canvas size");const i=this._createCanvas(t,e);return{canvas:i,context:i.getContext("2d",{willReadFrequently:!this.#ui})}}reset(t,e,i){if(!t.canvas)throw new Error("Canvas is not specified");if(e<=0||i<=0)throw new Error("Invalid canvas size");t.canvas.width=e;t.canvas.height=i}destroy(t){if(!t.canvas)throw new Error("Canvas is not specified");t.canvas.width=0;t.canvas.height=0;t.canvas=null;t.context=null}_createCanvas(t,e){unreachable("Abstract method `_createCanvas` called.")}}class DOMCanvasFactory extends BaseCanvasFactory{constructor({ownerDocument:t=globalThis.document,enableHWA:e=!1}){super({enableHWA:e});this._document=t}_createCanvas(t,e){const i=this._document.createElement("canvas");i.width=t;i.height=e;return i}}class BaseCMapReaderFactory{constructor({baseUrl:t=null,isCompressed:e=!0}){this.baseUrl=t;this.isCompressed=e}async fetch({name:t}){if(!this.baseUrl)throw new Error("Ensure that the `cMapUrl` and `cMapPacked` API parameters are provided.");if(!t)throw new Error("CMap name must be specified.");const e=this.baseUrl+t+(this.isCompressed?".bcmap":"");return this._fetch(e).then((t=>({cMapData:t,isCompressed:this.isCompressed}))).catch((t=>{throw new Error(`Unable to load ${this.isCompressed?"binary ":""}CMap at: ${e}`)}))}async _fetch(t){unreachable("Abstract method `_fetch` called.")}}class DOMCMapReaderFactory extends BaseCMapReaderFactory{async _fetch(t){const e=await fetchData(t,this.isCompressed?"arraybuffer":"text");return e instanceof ArrayBuffer?new Uint8Array(e):stringToBytes(e)}}class BaseFilterFactory{addFilter(t){return"none"}addHCMFilter(t,e){return"none"}addAlphaFilter(t){return"none"}addLuminosityFilter(t){return"none"}addHighlightHCMFilter(t,e,i,s,n){return"none"}destroy(t=!1){}}class DOMFilterFactory extends BaseFilterFactory{#pi;#gi;#fi;#mi;#bi;#Ai;#y=0;constructor({docId:t,ownerDocument:e=globalThis.document}){super();this.#mi=t;this.#bi=e}get#_(){return this.#gi||=new Map}get#wi(){return this.#Ai||=new Map}get#yi(){if(!this.#fi){const t=this.#bi.createElement("div"),{style:e}=t;e.visibility="hidden";e.contain="strict";e.width=e.height=0;e.position="absolute";e.top=e.left=0;e.zIndex=-1;const i=this.#bi.createElementNS(z,"svg");i.setAttribute("width",0);i.setAttribute("height",0);this.#fi=this.#bi.createElementNS(z,"defs");t.append(i);i.append(this.#fi);this.#bi.body.append(t)}return this.#fi}#vi(t){if(1===t.length){const e=t[0],i=new Array(256);for(let t=0;t<256;t++)i[t]=e[t]/255;const s=i.join(",");return[s,s,s]}const[e,i,s]=t,n=new Array(256),a=new Array(256),r=new Array(256);for(let t=0;t<256;t++){n[t]=e[t]/255;a[t]=i[t]/255;r[t]=s[t]/255}return[n.join(","),a.join(","),r.join(",")]}#_i(t){if(void 0===this.#pi){this.#pi="";const t=this.#bi.URL;t!==this.#bi.baseURI&&(isDataScheme(t)?warn('#createUrl: ignore "data:"-URL for performance reasons.'):this.#pi=updateUrlHash(t,""))}return`url(${this.#pi}#${t})`}addFilter(t){if(!t)return"none";let e=this.#_.get(t);if(e)return e;const[i,s,n]=this.#vi(t),a=1===t.length?i:`${i}${s}${n}`;e=this.#_.get(a);if(e){this.#_.set(t,e);return e}const r=`g_${this.#mi}_transfer_map_${this.#y++}`,o=this.#_i(r);this.#_.set(t,o);this.#_.set(a,o);const l=this.#xi(r);this.#Ei(i,s,n,l);return o}addHCMFilter(t,e){const i=`${t}-${e}`,s="base";let n=this.#wi.get(s);if(n?.key===i)return n.url;if(n){n.filter?.remove();n.key=i;n.url="none";n.filter=null}else{n={key:i,url:"none",filter:null};this.#wi.set(s,n)}if(!t||!e)return n.url;const a=this.#Si(t);t=Util.makeHexColor(...a);const r=this.#Si(e);e=Util.makeHexColor(...r);this.#yi.style.color="";if("#000000"===t&&"#ffffff"===e||t===e)return n.url;const o=new Array(256);for(let t=0;t<=255;t++){const e=t/255;o[t]=e<=.03928?e/12.92:((e+.055)/1.055)**2.4}const l=o.join(","),h=`g_${this.#mi}_hcm_filter`,d=n.filter=this.#xi(h);this.#Ei(l,l,l,d);this.#Ci(d);const getSteps=(t,e)=>{const i=a[t]/255,s=r[t]/255,n=new Array(e+1);for(let t=0;t<=e;t++)n[t]=i+t/e*(s-i);return n.join(",")};this.#Ei(getSteps(0,5),getSteps(1,5),getSteps(2,5),d);n.url=this.#_i(h);return n.url}addAlphaFilter(t){let e=this.#_.get(t);if(e)return e;const[i]=this.#vi([t]),s=`alpha_${i}`;e=this.#_.get(s);if(e){this.#_.set(t,e);return e}const n=`g_${this.#mi}_alpha_map_${this.#y++}`,a=this.#_i(n);this.#_.set(t,a);this.#_.set(s,a);const r=this.#xi(n);this.#Ti(i,r);return a}addLuminosityFilter(t){let e,i,s=this.#_.get(t||"luminosity");if(s)return s;if(t){[e]=this.#vi([t]);i=`luminosity_${e}`}else i="luminosity";s=this.#_.get(i);if(s){this.#_.set(t,s);return s}const n=`g_${this.#mi}_luminosity_map_${this.#y++}`,a=this.#_i(n);this.#_.set(t,a);this.#_.set(i,a);const r=this.#xi(n);this.#Mi(r);t&&this.#Ti(e,r);return a}addHighlightHCMFilter(t,e,i,s,n){const a=`${e}-${i}-${s}-${n}`;let r=this.#wi.get(t);if(r?.key===a)return r.url;if(r){r.filter?.remove();r.key=a;r.url="none";r.filter=null}else{r={key:a,url:"none",filter:null};this.#wi.set(t,r)}if(!e||!i)return r.url;const[o,l]=[e,i].map(this.#Si.bind(this));let h=Math.round(.2126*o[0]+.7152*o[1]+.0722*o[2]),d=Math.round(.2126*l[0]+.7152*l[1]+.0722*l[2]),[c,u]=[s,n].map(this.#Si.bind(this));d{const s=new Array(256),n=(d-h)/i,a=t/255,r=(e-t)/(255*i);let o=0;for(let t=0;t<=i;t++){const e=Math.round(h+t*n),i=a+t*r;for(let t=o;t<=e;t++)s[t]=i;o=e+1}for(let t=o;t<256;t++)s[t]=s[o-1];return s.join(",")},p=`g_${this.#mi}_hcm_${t}_filter`,g=r.filter=this.#xi(p);this.#Ci(g);this.#Ei(getSteps(c[0],u[0],5),getSteps(c[1],u[1],5),getSteps(c[2],u[2],5),g);r.url=this.#_i(p);return r.url}destroy(t=!1){if(!t||!this.#Ai?.size){this.#fi?.parentNode.parentNode.remove();this.#fi=null;this.#gi?.clear();this.#gi=null;this.#Ai?.clear();this.#Ai=null;this.#y=0}}#Mi(t){const e=this.#bi.createElementNS(z,"feColorMatrix");e.setAttribute("type","matrix");e.setAttribute("values","0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0");t.append(e)}#Ci(t){const e=this.#bi.createElementNS(z,"feColorMatrix");e.setAttribute("type","matrix");e.setAttribute("values","0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0");t.append(e)}#xi(t){const e=this.#bi.createElementNS(z,"filter");e.setAttribute("color-interpolation-filters","sRGB");e.setAttribute("id",t);this.#yi.append(e);return e}#Di(t,e,i){const s=this.#bi.createElementNS(z,e);s.setAttribute("type","discrete");s.setAttribute("tableValues",i);t.append(s)}#Ei(t,e,i,s){const n=this.#bi.createElementNS(z,"feComponentTransfer");s.append(n);this.#Di(n,"feFuncR",t);this.#Di(n,"feFuncG",e);this.#Di(n,"feFuncB",i)}#Ti(t,e){const i=this.#bi.createElementNS(z,"feComponentTransfer");e.append(i);this.#Di(i,"feFuncA",t)}#Si(t){this.#yi.style.color=t;return getRGB(getComputedStyle(this.#yi).getPropertyValue("color"))}}class BaseStandardFontDataFactory{constructor({baseUrl:t=null}){this.baseUrl=t}async fetch({filename:t}){if(!this.baseUrl)throw new Error("Ensure that the `standardFontDataUrl` API parameter is provided.");if(!t)throw new Error("Font filename must be specified.");const e=`${this.baseUrl}${t}`;return this._fetch(e).catch((t=>{throw new Error(`Unable to load font data at: ${e}`)}))}async _fetch(t){unreachable("Abstract method `_fetch` called.")}}class DOMStandardFontDataFactory extends BaseStandardFontDataFactory{async _fetch(t){const e=await fetchData(t,"arraybuffer");return new Uint8Array(e)}}class BaseWasmFactory{constructor({baseUrl:t=null}){this.baseUrl=t}async fetch({filename:t}){if(!this.baseUrl)throw new Error("Ensure that the `wasmUrl` API parameter is provided.");if(!t)throw new Error("Wasm filename must be specified.");const e=`${this.baseUrl}${t}`;return this._fetch(e).catch((t=>{throw new Error(`Unable to load wasm data at: ${e}`)}))}async _fetch(t){unreachable("Abstract method `_fetch` called.")}}class DOMWasmFactory extends BaseWasmFactory{async _fetch(t){const e=await fetchData(t,"arraybuffer");return new Uint8Array(e)}}t&&warn("Please use the `legacy` build in Node.js environments.");async function node_utils_fetchData(t){const e=process.getBuiltinModule("fs"),i=await e.promises.readFile(t);return new Uint8Array(i)}class NodeFilterFactory extends BaseFilterFactory{}class NodeCanvasFactory extends BaseCanvasFactory{_createCanvas(t,e){return process.getBuiltinModule("module").createRequire(import.meta.url)("@napi-rs/canvas").createCanvas(t,e)}}class NodeCMapReaderFactory extends BaseCMapReaderFactory{async _fetch(t){return node_utils_fetchData(t)}}class NodeStandardFontDataFactory extends BaseStandardFontDataFactory{async _fetch(t){return node_utils_fetchData(t)}}class NodeWasmFactory extends BaseWasmFactory{async _fetch(t){return node_utils_fetchData(t)}}const st="Fill",nt="Stroke",at="Shading";function applyBoundingBox(t,e){if(!e)return;const i=e[2]-e[0],s=e[3]-e[1],n=new Path2D;n.rect(e[0],e[1],i,s);t.clip(n)}class BaseShadingPattern{isModifyingCurrentTransform(){return!1}getPattern(){unreachable("Abstract method `getPattern` called.")}}class RadialAxialShadingPattern extends BaseShadingPattern{constructor(t){super();this._type=t[1];this._bbox=t[2];this._colorStops=t[3];this._p0=t[4];this._p1=t[5];this._r0=t[6];this._r1=t[7];this.matrix=null}_createGradient(t){let e;"axial"===this._type?e=t.createLinearGradient(this._p0[0],this._p0[1],this._p1[0],this._p1[1]):"radial"===this._type&&(e=t.createRadialGradient(this._p0[0],this._p0[1],this._r0,this._p1[0],this._p1[1],this._r1));for(const t of this._colorStops)e.addColorStop(t[0],t[1]);return e}getPattern(t,e,i,s){let n;if(s===nt||s===st){const a=e.current.getClippedPathBoundingBox(s,getCurrentTransform(t))||[0,0,0,0],r=Math.ceil(a[2]-a[0])||1,o=Math.ceil(a[3]-a[1])||1,l=e.cachedCanvases.getCanvas("pattern",r,o),h=l.context;h.clearRect(0,0,h.canvas.width,h.canvas.height);h.beginPath();h.rect(0,0,h.canvas.width,h.canvas.height);h.translate(-a[0],-a[1]);i=Util.transform(i,[1,0,0,1,a[0],a[1]]);h.transform(...e.baseTransform);this.matrix&&h.transform(...this.matrix);applyBoundingBox(h,this._bbox);h.fillStyle=this._createGradient(h);h.fill();n=t.createPattern(l.canvas,"no-repeat");const d=new DOMMatrix(i);n.setTransform(d)}else{applyBoundingBox(t,this._bbox);n=this._createGradient(t)}return n}}function drawTriangle(t,e,i,s,n,a,r,o){const l=e.coords,h=e.colors,d=t.data,c=4*t.width;let u;if(l[i+1]>l[s+1]){u=i;i=s;s=u;u=a;a=r;r=u}if(l[s+1]>l[n+1]){u=s;s=n;n=u;u=r;r=o;o=u}if(l[i+1]>l[s+1]){u=i;i=s;s=u;u=a;a=r;r=u}const p=(l[i]+e.offsetX)*e.scaleX,g=(l[i+1]+e.offsetY)*e.scaleY,f=(l[s]+e.offsetX)*e.scaleX,m=(l[s+1]+e.offsetY)*e.scaleY,b=(l[n]+e.offsetX)*e.scaleX,A=(l[n+1]+e.offsetY)*e.scaleY;if(g>=A)return;const w=h[a],y=h[a+1],v=h[a+2],_=h[r],x=h[r+1],E=h[r+2],S=h[o],C=h[o+1],T=h[o+2],M=Math.round(g),D=Math.round(A);let P,k,I,R,F,L,O,N;for(let t=M;t<=D;t++){if(tA?1:m===A?0:(m-t)/(m-A);P=f-(f-b)*e;k=_-(_-S)*e;I=x-(x-C)*e;R=E-(E-T)*e}let e;e=tA?1:(g-t)/(g-A);F=p-(p-b)*e;L=w-(w-S)*e;O=y-(y-C)*e;N=v-(v-T)*e;const i=Math.round(Math.min(P,F)),s=Math.round(Math.max(P,F));let n=c*t+4*i;for(let t=i;t<=s;t++){e=(P-t)/(P-F);e<0?e=0:e>1&&(e=1);d[n++]=k-(k-L)*e|0;d[n++]=I-(I-O)*e|0;d[n++]=R-(R-N)*e|0;d[n++]=255}}}function drawFigure(t,e,i){const s=e.coords,n=e.colors;let a,r;switch(e.type){case"lattice":const o=e.verticesPerRow,l=Math.floor(s.length/o)-1,h=o-1;for(a=0;a=Math.ceil(p*A)?y=o:_=!0;S>=Math.ceil(g*w)?v=l:x=!0;const C=this.getSizeAndScale(y,this.ctx.canvas.width,A),T=this.getSizeAndScale(v,this.ctx.canvas.height,w),M=t.cachedCanvases.getCanvas("pattern",C.size,T.size),D=M.context,P=r.createCanvasGraphics(D);P.groupLevel=t.groupLevel;this.setFillAndStrokeStyleToContext(P,s,a);D.translate(-C.scale*h,-T.scale*d);P.transform(C.scale,0,0,T.scale,0,0);D.save();this.clipBbox(P,h,d,c,u);P.baseTransform=getCurrentTransform(P.ctx);P.executeOperatorList(i);P.endDrawing();D.restore();if(_||x){const e=M.canvas;_&&(y=o);x&&(v=l);const i=this.getSizeAndScale(y,this.ctx.canvas.width,A),s=this.getSizeAndScale(v,this.ctx.canvas.height,w),n=i.size,a=s.size,r=t.cachedCanvases.getCanvas("pattern-workaround",n,a),c=r.context,u=_?Math.floor(p/o):0,f=x?Math.floor(g/l):0;for(let t=0;t<=u;t++)for(let i=0;i<=f;i++)c.drawImage(e,n*t,a*i,n,a,0,0,n,a);return{canvas:r.canvas,scaleX:i.scale,scaleY:s.scale,offsetX:h,offsetY:d}}return{canvas:M.canvas,scaleX:C.scale,scaleY:T.scale,offsetX:h,offsetY:d}}getSizeAndScale(t,e,i){const s=Math.max(TilingPattern.MAX_PATTERN_SIZE,e);let n=Math.ceil(t*i);n>=s?n=s:i=n/t;return{scale:i,size:n}}clipBbox(t,e,i,s,n){const a=s-e,r=n-i;t.ctx.rect(e,i,a,r);Util.axialAlignedBoundingBox([e,i,s,n],getCurrentTransform(t.ctx),t.current.minMax);t.clip();t.endPath()}setFillAndStrokeStyleToContext(t,e,i){const s=t.ctx,n=t.current;switch(e){case rt:const t=this.ctx;s.fillStyle=t.fillStyle;s.strokeStyle=t.strokeStyle;n.fillColor=t.fillStyle;n.strokeColor=t.strokeStyle;break;case ot:const a=Util.makeHexColor(i[0],i[1],i[2]);s.fillStyle=a;s.strokeStyle=a;n.fillColor=a;n.strokeColor=a;break;default:throw new FormatError(`Unsupported paint type: ${e}`)}}isModifyingCurrentTransform(){return!1}getPattern(t,e,i,s){let n=i;if(s!==at){n=Util.transform(n,e.baseTransform);this.matrix&&(n=Util.transform(n,this.matrix))}const a=this.createPatternCanvas(e);let r=new DOMMatrix(n);r=r.translate(a.offsetX,a.offsetY);r=r.scale(1/a.scaleX,1/a.scaleY);const o=t.createPattern(a.canvas,"repeat");o.setTransform(r);return o}}function convertBlackAndWhiteToRGBA({src:t,srcPos:e=0,dest:i,width:s,height:n,nonBlackColor:a=4294967295,inverseDecode:r=!1}){const o=util_FeatureTest.isLittleEndian?4278190080:255,[l,h]=r?[a,o]:[o,a],d=s>>3,c=7&s,u=t.length;i=new Uint32Array(i.buffer);let p=0;for(let s=0;s>2),f=i.length,m=s+7>>3,b=4294967295,A=util_FeatureTest.isLittleEndian?4278190080:255;for(u=0;um?s:8*t-7,r=-8&a;let o=0,c=0;for(;n>=1}}for(;l=a){g=n;f=s*g}l=0;for(p=f;p--;){c[l++]=d[h++];c[l++]=d[h++];c[l++]=d[h++];c[l++]=255}t.putImageData(o,0,u*lt)}}}function putBinaryImageMask(t,e){if(e.bitmap){t.drawImage(e.bitmap,0,0);return}const i=e.height,s=e.width,n=i%lt,a=(i-n)/lt,r=0===n?a:a+1,o=t.createImageData(s,lt);let l=0;const h=e.data,d=o.data;for(let e=0;e10&&"function"==typeof i,h=l?Date.now()+15:0;let d=0;const c=this.commonObjs,u=this.objs;let p;for(;;){if(void 0!==s&&r===s.nextBreakPoint){s.breakIt(r,i);return r}p=a[r];if(p!==D.dependency)this[p].apply(this,n[r]);else for(const t of n[r]){const e=t.startsWith("g_")?c:u;if(!e.has(t)){e.get(t,i);return r}}r++;if(r===o)return r;if(l&&++d>10){if(Date.now()>h){i();return r}d=0}}}#Pi(){for(;this.stateStack.length||this.inSMaskMode;)this.restore();this.current.activeSMask=null;this.ctx.restore();if(this.transparentCanvas){this.ctx=this.compositeCtx;this.ctx.save();this.ctx.setTransform(1,0,0,1,0,0);this.ctx.drawImage(this.transparentCanvas,0,0);this.ctx.restore();this.transparentCanvas=null}}endDrawing(){this.#Pi();this.cachedCanvases.clear();this.cachedPatterns.clear();for(const t of this._cachedBitmapsMap.values()){for(const e of t.values())"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement&&(e.width=e.height=0);t.clear()}this._cachedBitmapsMap.clear();this.#ki()}#ki(){if(this.pageColors){const t=this.filterFactory.addHCMFilter(this.pageColors.foreground,this.pageColors.background);if("none"!==t){const e=this.ctx.filter;this.ctx.filter=t;this.ctx.drawImage(this.ctx.canvas,0,0);this.ctx.filter=e}}}_scaleImage(t,e){const i=t.width??t.displayWidth,s=t.height??t.displayHeight;let n,a,r=Math.max(Math.hypot(e[0],e[1]),1),o=Math.max(Math.hypot(e[2],e[3]),1),l=i,h=s,d="prescale1";for(;r>2&&l>1||o>2&&h>1;){let e=l,i=h;if(r>2&&l>1){e=l>=16384?Math.floor(l/2)-1||1:Math.ceil(l/2);r/=l/e}if(o>2&&h>1){i=h>=16384?Math.floor(h/2)-1||1:Math.ceil(h)/2;o/=h/i}n=this.cachedCanvases.getCanvas(d,e,i);a=n.context;a.clearRect(0,0,e,i);a.drawImage(t,0,0,l,h,0,0,e,i);t=n.canvas;l=e;h=i;d="prescale1"===d?"prescale2":"prescale1"}return{img:t,paintWidth:l,paintHeight:h}}_createMaskCanvas(t){const e=this.ctx,{width:i,height:s}=t,n=this.current.fillColor,a=this.current.patternFill,r=getCurrentTransform(e);let o,l,h,d;if((t.bitmap||t.data)&&t.count>1){const e=t.bitmap||t.data.buffer;l=JSON.stringify(a?r:[r.slice(0,4),n]);o=this._cachedBitmapsMap.get(e);if(!o){o=new Map;this._cachedBitmapsMap.set(e,o)}const i=o.get(l);if(i&&!a){return{canvas:i,offsetX:Math.round(Math.min(r[0],r[2])+r[4]),offsetY:Math.round(Math.min(r[1],r[3])+r[5])}}h=i}if(!h){d=this.cachedCanvases.getCanvas("maskCanvas",i,s);putBinaryImageMask(d.context,t)}let c=Util.transform(r,[1/i,0,0,-1/s,0,0]);c=Util.transform(c,[1,0,0,1,0,-s]);const u=ct.slice();Util.axialAlignedBoundingBox([0,0,i,s],c,u);const[p,g,f,m]=u,b=Math.round(f-p)||1,A=Math.round(m-g)||1,w=this.cachedCanvases.getCanvas("fillCanvas",b,A),y=w.context,v=p,_=g;y.translate(-v,-_);y.transform(...c);if(!h){h=this._scaleImage(d.canvas,getCurrentTransformInverse(y));h=h.img;o&&a&&o.set(l,h)}y.imageSmoothingEnabled=getImageSmoothingEnabled(getCurrentTransform(y),t.interpolate);drawImageAtIntegerCoords(y,h,0,0,h.width,h.height,0,0,i,s);y.globalCompositeOperation="source-in";const x=Util.transform(getCurrentTransformInverse(y),[1,0,0,1,-v,-_]);y.fillStyle=a?n.getPattern(e,this,x,st):n;y.fillRect(0,0,i,s);if(o&&!a){this.cachedCanvases.delete("fillCanvas");o.set(l,w.canvas)}return{canvas:w.canvas,offsetX:Math.round(v),offsetY:Math.round(_)}}setLineWidth(t){t!==this.current.lineWidth&&(this._cachedScaleForStroking[0]=-1);this.current.lineWidth=t;this.ctx.lineWidth=t}setLineCap(t){this.ctx.lineCap=ut[t]}setLineJoin(t){this.ctx.lineJoin=pt[t]}setMiterLimit(t){this.ctx.miterLimit=t}setDash(t,e){const i=this.ctx;if(void 0!==i.setLineDash){i.setLineDash(t);i.lineDashOffset=e}}setRenderingIntent(t){}setFlatness(t){}setGState(t){for(const[e,i]of t)switch(e){case"LW":this.setLineWidth(i);break;case"LC":this.setLineCap(i);break;case"LJ":this.setLineJoin(i);break;case"ML":this.setMiterLimit(i);break;case"D":this.setDash(i[0],i[1]);break;case"RI":this.setRenderingIntent(i);break;case"FL":this.setFlatness(i);break;case"Font":this.setFont(i[0],i[1]);break;case"CA":this.current.strokeAlpha=i;break;case"ca":this.ctx.globalAlpha=this.current.fillAlpha=i;break;case"BM":this.ctx.globalCompositeOperation=i;break;case"SMask":this.current.activeSMask=i?this.tempSMask:null;this.tempSMask=null;this.checkSMaskState();break;case"TR":this.ctx.filter=this.current.transferMaps=this.filterFactory.addFilter(i)}}get inSMaskMode(){return!!this.suspendedCtx}checkSMaskState(){const t=this.inSMaskMode;this.current.activeSMask&&!t?this.beginSMaskMode():!this.current.activeSMask&&t&&this.endSMaskMode()}beginSMaskMode(){if(this.inSMaskMode)throw new Error("beginSMaskMode called while already in smask mode");const t=this.ctx.canvas.width,e=this.ctx.canvas.height,i="smaskGroupAt"+this.groupLevel,s=this.cachedCanvases.getCanvas(i,t,e);this.suspendedCtx=this.ctx;const n=this.ctx=s.context;n.setTransform(this.suspendedCtx.getTransform());copyCtxState(this.suspendedCtx,n);!function mirrorContextOperations(t,e){if(t._removeMirroring)throw new Error("Context is already forwarding operations.");t.__originalSave=t.save;t.__originalRestore=t.restore;t.__originalRotate=t.rotate;t.__originalScale=t.scale;t.__originalTranslate=t.translate;t.__originalTransform=t.transform;t.__originalSetTransform=t.setTransform;t.__originalResetTransform=t.resetTransform;t.__originalClip=t.clip;t.__originalMoveTo=t.moveTo;t.__originalLineTo=t.lineTo;t.__originalBezierCurveTo=t.bezierCurveTo;t.__originalRect=t.rect;t.__originalClosePath=t.closePath;t.__originalBeginPath=t.beginPath;t._removeMirroring=()=>{t.save=t.__originalSave;t.restore=t.__originalRestore;t.rotate=t.__originalRotate;t.scale=t.__originalScale;t.translate=t.__originalTranslate;t.transform=t.__originalTransform;t.setTransform=t.__originalSetTransform;t.resetTransform=t.__originalResetTransform;t.clip=t.__originalClip;t.moveTo=t.__originalMoveTo;t.lineTo=t.__originalLineTo;t.bezierCurveTo=t.__originalBezierCurveTo;t.rect=t.__originalRect;t.closePath=t.__originalClosePath;t.beginPath=t.__originalBeginPath;delete t._removeMirroring};t.save=function(){e.save();this.__originalSave()};t.restore=function(){e.restore();this.__originalRestore()};t.translate=function(t,i){e.translate(t,i);this.__originalTranslate(t,i)};t.scale=function(t,i){e.scale(t,i);this.__originalScale(t,i)};t.transform=function(t,i,s,n,a,r){e.transform(t,i,s,n,a,r);this.__originalTransform(t,i,s,n,a,r)};t.setTransform=function(t,i,s,n,a,r){e.setTransform(t,i,s,n,a,r);this.__originalSetTransform(t,i,s,n,a,r)};t.resetTransform=function(){e.resetTransform();this.__originalResetTransform()};t.rotate=function(t){e.rotate(t);this.__originalRotate(t)};t.clip=function(t){e.clip(t);this.__originalClip(t)};t.moveTo=function(t,i){e.moveTo(t,i);this.__originalMoveTo(t,i)};t.lineTo=function(t,i){e.lineTo(t,i);this.__originalLineTo(t,i)};t.bezierCurveTo=function(t,i,s,n,a,r){e.bezierCurveTo(t,i,s,n,a,r);this.__originalBezierCurveTo(t,i,s,n,a,r)};t.rect=function(t,i,s,n){e.rect(t,i,s,n);this.__originalRect(t,i,s,n)};t.closePath=function(){e.closePath();this.__originalClosePath()};t.beginPath=function(){e.beginPath();this.__originalBeginPath()}}(n,this.suspendedCtx);this.setGState([["BM","source-over"]])}endSMaskMode(){if(!this.inSMaskMode)throw new Error("endSMaskMode called while not in smask mode");this.ctx._removeMirroring();copyCtxState(this.ctx,this.suspendedCtx);this.ctx=this.suspendedCtx;this.suspendedCtx=null}compose(t){if(!this.current.activeSMask)return;if(t){t[0]=Math.floor(t[0]);t[1]=Math.floor(t[1]);t[2]=Math.ceil(t[2]);t[3]=Math.ceil(t[3])}else t=[0,0,this.ctx.canvas.width,this.ctx.canvas.height];const e=this.current.activeSMask,i=this.suspendedCtx;this.composeSMask(i,e,this.ctx,t);this.ctx.save();this.ctx.setTransform(1,0,0,1,0,0);this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);this.ctx.restore()}composeSMask(t,e,i,s){const n=s[0],a=s[1],r=s[2]-n,o=s[3]-a;if(0!==r&&0!==o){this.genericComposeSMask(e.context,i,r,o,e.subtype,e.backdrop,e.transferMap,n,a,e.offsetX,e.offsetY);t.save();t.globalAlpha=1;t.globalCompositeOperation="source-over";t.setTransform(1,0,0,1,0,0);t.drawImage(i.canvas,0,0);t.restore()}}genericComposeSMask(t,e,i,s,n,a,r,o,l,h,d){let c=t.canvas,u=o-h,p=l-d;if(a){const e=Util.makeHexColor(...a);if(u<0||p<0||u+i>c.width||p+s>c.height){const t=this.cachedCanvases.getCanvas("maskExtension",i,s),n=t.context;n.drawImage(c,-u,-p);n.globalCompositeOperation="destination-atop";n.fillStyle=e;n.fillRect(0,0,i,s);n.globalCompositeOperation="source-over";c=t.canvas;u=p=0}else{t.save();t.globalAlpha=1;t.setTransform(1,0,0,1,0,0);const n=new Path2D;n.rect(u,p,i,s);t.clip(n);t.globalCompositeOperation="destination-atop";t.fillStyle=e;t.fillRect(u,p,i,s);t.restore()}}e.save();e.globalAlpha=1;e.setTransform(1,0,0,1,0,0);"Alpha"===n&&r?e.filter=this.filterFactory.addAlphaFilter(r):"Luminosity"===n&&(e.filter=this.filterFactory.addLuminosityFilter(r));const g=new Path2D;g.rect(o,l,i,s);e.clip(g);e.globalCompositeOperation="destination-in";e.drawImage(c,u,p,i,s,o,l,i,s);e.restore()}save(){this.inSMaskMode&©CtxState(this.ctx,this.suspendedCtx);this.ctx.save();const t=this.current;this.stateStack.push(t);this.current=t.clone()}restore(){if(0!==this.stateStack.length){this.current=this.stateStack.pop();this.ctx.restore();this.inSMaskMode&©CtxState(this.suspendedCtx,this.ctx);this.checkSMaskState();this.pendingClip=null;this._cachedScaleForStroking[0]=-1;this._cachedGetSinglePixelWidth=null}else this.inSMaskMode&&this.endSMaskMode()}transform(t,e,i,s,n,a){this.ctx.transform(t,e,i,s,n,a);this._cachedScaleForStroking[0]=-1;this._cachedGetSinglePixelWidth=null}constructPath(t,e,i){let[s]=e;if(i){if(!(s instanceof Path2D)){const t=e[0]=new Path2D;for(let e=0,i=s.length;e100&&(h=100);this.current.fontSizeScale=i/h;this.ctx.font=`${l} ${o} ${h}px ${r}`}setTextRenderingMode(t){this.current.textRenderingMode=t}setTextRise(t){this.current.textRise=t}moveText(t,e){this.current.x=this.current.lineX+=t;this.current.y=this.current.lineY+=e}setLeadingMoveText(t,e){this.setLeading(-e);this.moveText(t,e)}setTextMatrix(t){const{current:e}=this;e.textMatrix=t;e.textMatrixScale=Math.hypot(t[0],t[1]);e.x=e.lineX=0;e.y=e.lineY=0}nextLine(){this.moveText(0,this.current.leading)}#Ii(t,e,i){const s=new Path2D;s.addPath(t,new DOMMatrix(i).invertSelf().multiplySelf(e));return s}paintChar(t,e,i,s,n){const a=this.ctx,r=this.current,o=r.font,l=r.textRenderingMode,h=r.fontSize/r.fontSizeScale,d=l&w,c=!!(l&y),u=r.patternFill&&!o.missingFile,p=r.patternStroke&&!o.missingFile;let g;(o.disableFontFace||c||u||p)&&(g=o.getPathGenerator(this.commonObjs,t));if(o.disableFontFace||u||p){a.save();a.translate(e,i);a.scale(h,-h);let t;if(d===f||d===b)if(s){t=a.getTransform();a.setTransform(...s);a.fill(this.#Ii(g,t,s))}else a.fill(g);if(d===m||d===b)if(n){t||=a.getTransform();a.setTransform(...n);const{a:e,b:i,c:s,d:r}=t,o=Util.inverseTransform(n),l=Util.transform([e,i,s,r,0,0],o);Util.singularValueDecompose2dScale(l,dt);a.lineWidth*=Math.max(dt[0],dt[1])/h;a.stroke(this.#Ii(g,t,n))}else{a.lineWidth/=h;a.stroke(g)}a.restore()}else{d!==f&&d!==b||a.fillText(t,e,i);d!==m&&d!==b||a.strokeText(t,e,i)}if(c){(this.pendingTextPaths||=[]).push({transform:getCurrentTransform(a),x:e,y:i,fontSize:h,path:g})}}get isFontSubpixelAAEnabled(){const{context:t}=this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled",10,10);t.scale(1.5,1);t.fillText("I",0,10);const e=t.getImageData(0,0,10,10).data;let i=!1;for(let t=3;t0&&e[t]<255){i=!0;break}return shadow(this,"isFontSubpixelAAEnabled",i)}showText(t){const e=this.current,i=e.font;if(i.isType3Font)return this.showType3Text(t);const s=e.fontSize;if(0===s)return;const n=this.ctx,a=e.fontSizeScale,r=e.charSpacing,o=e.wordSpacing,l=e.fontDirection,h=e.textHScale*l,d=t.length,c=i.vertical,u=c?1:-1,p=i.defaultVMetrics,g=s*e.fontMatrix[0],A=e.textRenderingMode===f&&!i.disableFontFace&&!e.patternFill;n.save();e.textMatrix&&n.transform(...e.textMatrix);n.translate(e.x,e.y+e.textRise);l>0?n.scale(h,-1):n.scale(h,1);let y,v;if(e.patternFill){n.save();const t=e.fillColor.getPattern(n,this,getCurrentTransformInverse(n),st);y=getCurrentTransform(n);n.restore();n.fillStyle=t}if(e.patternStroke){n.save();const t=e.strokeColor.getPattern(n,this,getCurrentTransformInverse(n),nt);v=getCurrentTransform(n);n.restore();n.strokeStyle=t}let _=e.lineWidth;const x=e.textMatrixScale;if(0===x||0===_){const t=e.textRenderingMode&w;t!==m&&t!==b||(_=this.getSinglePixelWidth())}else _/=x;if(1!==a){n.scale(a,a);_/=a}n.lineWidth=_;if(i.isInvalidPDFjsFont){const i=[];let s=0;for(const e of t){i.push(e.unicode);s+=e.width}n.fillText(i.join(""),0,0);e.x+=s*g*h;n.restore();this.compose();return}let E,S=0;for(E=0;E0){const t=1e3*n.measureText(f).width/s*a;if(_new CanvasGraphics(t,this.commonObjs,this.objs,this.canvasFactory,this.filterFactory,{optionalContentConfig:this.optionalContentConfig,markedContentStack:this.markedContentStack})};e=new TilingPattern(t,this.ctx,s,i)}else e=this._getPattern(t[1],t[2]);return e}setStrokeColorN(){this.current.strokeColor=this.getColorN_Pattern(arguments);this.current.patternStroke=!0}setFillColorN(){this.current.fillColor=this.getColorN_Pattern(arguments);this.current.patternFill=!0}setStrokeRGBColor(t,e,i){this.ctx.strokeStyle=this.current.strokeColor=Util.makeHexColor(t,e,i);this.current.patternStroke=!1}setStrokeTransparent(){this.ctx.strokeStyle=this.current.strokeColor="transparent";this.current.patternStroke=!1}setFillRGBColor(t,e,i){this.ctx.fillStyle=this.current.fillColor=Util.makeHexColor(t,e,i);this.current.patternFill=!1}setFillTransparent(){this.ctx.fillStyle=this.current.fillColor="transparent";this.current.patternFill=!1}_getPattern(t,e=null){let i;if(this.cachedPatterns.has(t))i=this.cachedPatterns.get(t);else{i=function getShadingPattern(t){switch(t[0]){case"RadialAxial":return new RadialAxialShadingPattern(t);case"Mesh":return new MeshShadingPattern(t);case"Dummy":return new DummyShadingPattern}throw new Error(`Unknown IR type: ${t[0]}`)}(this.getObject(t));this.cachedPatterns.set(t,i)}e&&(i.matrix=e);return i}shadingFill(t){if(!this.contentVisible)return;const e=this.ctx;this.save();const i=this._getPattern(t);e.fillStyle=i.getPattern(e,this,getCurrentTransformInverse(e),at);const s=getCurrentTransformInverse(e);if(s){const{width:t,height:i}=e.canvas,n=ct.slice();Util.axialAlignedBoundingBox([0,0,t,i],s,n);const[a,r,o,l]=n;this.ctx.fillRect(a,r,o-a,l-r)}else this.ctx.fillRect(-1e10,-1e10,2e10,2e10);this.compose(this.current.getClippedPathBoundingBox());this.restore()}beginInlineImage(){unreachable("Should not call beginInlineImage")}beginImageData(){unreachable("Should not call beginImageData")}paintFormXObjectBegin(t,e){if(this.contentVisible){this.save();this.baseTransformStack.push(this.baseTransform);t&&this.transform(...t);this.baseTransform=getCurrentTransform(this.ctx);if(e){Util.axialAlignedBoundingBox(e,this.baseTransform,this.current.minMax);const[t,i,s,n]=e,a=new Path2D;a.rect(t,i,s-t,n-i);this.ctx.clip(a);this.endPath()}}}paintFormXObjectEnd(){if(this.contentVisible){this.restore();this.baseTransform=this.baseTransformStack.pop()}}beginGroup(t){if(!this.contentVisible)return;this.save();if(this.inSMaskMode){this.endSMaskMode();this.current.activeSMask=null}const e=this.ctx;t.isolated||info("TODO: Support non-isolated groups.");t.knockout&&warn("Knockout groups not supported.");const i=getCurrentTransform(e);t.matrix&&e.transform(...t.matrix);if(!t.bbox)throw new Error("Bounding box is required.");let s=ct.slice();Util.axialAlignedBoundingBox(t.bbox,getCurrentTransform(e),s);const n=[0,0,e.canvas.width,e.canvas.height];s=Util.intersect(s,n)||[0,0,0,0];const a=Math.floor(s[0]),r=Math.floor(s[1]),o=Math.max(Math.ceil(s[2])-a,1),l=Math.max(Math.ceil(s[3])-r,1);this.current.startNewPathAndClipBox([0,0,o,l]);let h="groupAt"+this.groupLevel;t.smask&&(h+="_smask_"+this.smaskCounter++%2);const d=this.cachedCanvases.getCanvas(h,o,l),c=d.context;c.translate(-a,-r);c.transform(...i);let u=new Path2D;const[p,g,f,m]=t.bbox;u.rect(p,g,f-p,m-g);if(t.matrix){const e=new Path2D;e.addPath(u,new DOMMatrix(t.matrix));u=e}c.clip(u);if(t.smask)this.smaskStack.push({canvas:d.canvas,context:c,offsetX:a,offsetY:r,subtype:t.smask.subtype,backdrop:t.smask.backdrop,transferMap:t.smask.transferMap||null,startTransformInverse:null});else{e.setTransform(1,0,0,1,0,0);e.translate(a,r);e.save()}copyCtxState(e,c);this.ctx=c;this.setGState([["BM","source-over"],["ca",1],["CA",1]]);this.groupStack.push(e);this.groupLevel++}endGroup(t){if(!this.contentVisible)return;this.groupLevel--;const e=this.ctx,i=this.groupStack.pop();this.ctx=i;this.ctx.imageSmoothingEnabled=!1;if(t.smask){this.tempSMask=this.smaskStack.pop();this.restore()}else{this.ctx.restore();const t=getCurrentTransform(this.ctx);this.restore();this.ctx.save();this.ctx.setTransform(...t);const i=ct.slice();Util.axialAlignedBoundingBox([0,0,e.canvas.width,e.canvas.height],t,i);this.ctx.drawImage(e.canvas,0,0);this.ctx.restore();this.compose(i)}}beginAnnotation(t,e,i,s,n){this.#Pi();resetCtxToDefault(this.ctx);this.ctx.save();this.save();this.baseTransform&&this.ctx.setTransform(...this.baseTransform);if(e){const s=e[2]-e[0],a=e[3]-e[1];if(n&&this.annotationCanvasMap){(i=i.slice())[4]-=e[0];i[5]-=e[1];(e=e.slice())[0]=e[1]=0;e[2]=s;e[3]=a;Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx),dt);const{viewportScale:n}=this,r=Math.ceil(s*this.outputScaleX*n),o=Math.ceil(a*this.outputScaleY*n);this.annotationCanvas=this.canvasFactory.create(r,o);const{canvas:l,context:h}=this.annotationCanvas;this.annotationCanvasMap.set(t,l);this.annotationCanvas.savedCtx=this.ctx;this.ctx=h;this.ctx.save();this.ctx.setTransform(dt[0],0,0,-dt[1],0,a*dt[1]);resetCtxToDefault(this.ctx)}else{resetCtxToDefault(this.ctx);this.endPath();const t=new Path2D;t.rect(e[0],e[1],s,a);this.ctx.clip(t)}}this.current=new CanvasExtraState(this.ctx.canvas.width,this.ctx.canvas.height);this.transform(...i);this.transform(...s)}endAnnotation(){if(this.annotationCanvas){this.ctx.restore();this.#ki();this.ctx=this.annotationCanvas.savedCtx;delete this.annotationCanvas.savedCtx;delete this.annotationCanvas}}paintImageMaskXObject(t){if(!this.contentVisible)return;const e=t.count;(t=this.getObject(t.data,t)).count=e;const i=this.ctx,s=this._createMaskCanvas(t),n=s.canvas;i.save();i.setTransform(1,0,0,1,0,0);i.drawImage(n,s.offsetX,s.offsetY);i.restore();this.compose()}paintImageMaskXObjectRepeat(t,e,i=0,s=0,n,a){if(!this.contentVisible)return;t=this.getObject(t.data,t);const r=this.ctx;r.save();const o=getCurrentTransform(r);r.transform(e,i,s,n,0,0);const l=this._createMaskCanvas(t);r.setTransform(1,0,0,1,l.offsetX-o[4],l.offsetY-o[5]);for(let t=0,h=a.length;te?h/e:1;r=l>e?l/e:1}}this._cachedScaleForStroking[0]=a;this._cachedScaleForStroking[1]=r}return this._cachedScaleForStroking}rescaleAndStroke(t,e){const{ctx:i,current:{lineWidth:s}}=this,[n,a]=this.getScaleForStroking();if(n===a){i.lineWidth=(s||1)*n;i.stroke(t);return}const r=i.getLineDash();e&&i.save();i.scale(n,a);ht.a=1/n;ht.d=1/a;const o=new Path2D;o.addPath(t,ht);if(r.length>0){const t=Math.max(n,a);i.setLineDash(r.map((e=>e/t)));i.lineDashOffset/=t}i.lineWidth=s||1;i.stroke(o);e&&i.restore()}isContentVisible(){for(let t=this.markedContentStack.length-1;t>=0;t--)if(!this.markedContentStack[t].visible)return!1;return!0}}for(const t in D)void 0!==CanvasGraphics.prototype[t]&&(CanvasGraphics.prototype[D[t]]=CanvasGraphics.prototype[t]);class GlobalWorkerOptions{static#Ri=null;static#Fi="";static get workerPort(){return this.#Ri}static set workerPort(t){if(!("undefined"!=typeof Worker&&t instanceof Worker)&&null!==t)throw new Error("Invalid `workerPort` type.");this.#Ri=t}static get workerSrc(){return this.#Fi}static set workerSrc(t){if("string"!=typeof t)throw new Error("Invalid `workerSrc` type.");this.#Fi=t}}class Metadata{#Li;#Oi;constructor({parsedData:t,rawData:e}){this.#Li=t;this.#Oi=e}getRaw(){return this.#Oi}get(t){return this.#Li.get(t)??null}[Symbol.iterator](){return this.#Li.entries()}}const mt=Symbol("INTERNAL");class OptionalContentGroup{#Ni=!1;#Bi=!1;#Hi=!1;#Ui=!0;constructor(t,{name:e,intent:i,usage:s,rbGroups:r}){this.#Ni=!!(t&n);this.#Bi=!!(t&a);this.name=e;this.intent=i;this.usage=s;this.rbGroups=r}get visible(){if(this.#Hi)return this.#Ui;if(!this.#Ui)return!1;const{print:t,view:e}=this.usage;return this.#Ni?"OFF"!==e?.viewState:!this.#Bi||"OFF"!==t?.printState}_setVisible(t,e,i=!1){t!==mt&&unreachable("Internal method `_setVisible` called.");this.#Hi=i;this.#Ui=e}}class OptionalContentConfig{#zi=null;#Gi=new Map;#$i=null;#Wi=null;constructor(t,e=n){this.renderingIntent=e;this.name=null;this.creator=null;if(null!==t){this.name=t.name;this.creator=t.creator;this.#Wi=t.order;for(const i of t.groups)this.#Gi.set(i.id,new OptionalContentGroup(e,i));if("OFF"===t.baseState)for(const t of this.#Gi.values())t._setVisible(mt,!1);for(const e of t.on)this.#Gi.get(e)._setVisible(mt,!0);for(const e of t.off)this.#Gi.get(e)._setVisible(mt,!1);this.#$i=this.getHash()}}#Vi(t){const e=t.length;if(e<2)return!0;const i=t[0];for(let s=1;s0){const t=n instanceof Uint8Array&&n.byteLength===n.buffer.byteLength?n.buffer:new Uint8Array(n).buffer;this._queuedChunks.push(t)}this._pdfDataRangeTransport=t;this._isStreamingSupported=!i;this._isRangeSupported=!e;this._contentLength=s;this._fullRequestReader=null;this._rangeReaders=[];t.addRangeListener(((t,e)=>{this._onReceiveData({begin:t,chunk:e})}));t.addProgressListener(((t,e)=>{this._onProgress({loaded:t,total:e})}));t.addProgressiveReadListener((t=>{this._onReceiveData({chunk:t})}));t.addProgressiveDoneListener((()=>{this._onProgressiveDone()}));t.transportReady()}_onReceiveData({begin:t,chunk:e}){const i=e instanceof Uint8Array&&e.byteLength===e.buffer.byteLength?e.buffer:new Uint8Array(e).buffer;if(void 0===t)this._fullRequestReader?this._fullRequestReader._enqueue(i):this._queuedChunks.push(i);else{assert(this._rangeReaders.some((function(e){if(e._begin!==t)return!1;e._enqueue(i);return!0})),"_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found.")}}get _progressiveDataLength(){return this._fullRequestReader?._loaded??0}_onProgress(t){void 0===t.total?this._rangeReaders[0]?.onProgress?.({loaded:t.loaded}):this._fullRequestReader?.onProgress?.({loaded:t.loaded,total:t.total})}_onProgressiveDone(){this._fullRequestReader?.progressiveDone();this._progressiveDone=!0}_removeRangeReader(t){const e=this._rangeReaders.indexOf(t);e>=0&&this._rangeReaders.splice(e,1)}getFullReader(){assert(!this._fullRequestReader,"PDFDataTransportStream.getFullReader can only be called once.");const t=this._queuedChunks;this._queuedChunks=null;return new PDFDataTransportStreamReader(this,t,this._progressiveDone,this._contentDispositionFilename)}getRangeReader(t,e){if(e<=this._progressiveDataLength)return null;const i=new PDFDataTransportStreamRangeReader(this,t,e);this._pdfDataRangeTransport.requestDataRange(t,e);this._rangeReaders.push(i);return i}cancelAllRequests(t){this._fullRequestReader?.cancel(t);for(const e of this._rangeReaders.slice(0))e.cancel(t);this._pdfDataRangeTransport.abort()}}class PDFDataTransportStreamReader{constructor(t,e,i=!1,s=null){this._stream=t;this._done=i||!1;this._filename=isPdfFile(s)?s:null;this._queuedChunks=e||[];this._loaded=0;for(const t of this._queuedChunks)this._loaded+=t.byteLength;this._requests=[];this._headersReady=Promise.resolve();t._fullRequestReader=this;this.onProgress=null}_enqueue(t){if(!this._done){if(this._requests.length>0){this._requests.shift().resolve({value:t,done:!1})}else this._queuedChunks.push(t);this._loaded+=t.byteLength}}get headersReady(){return this._headersReady}get filename(){return this._filename}get isRangeSupported(){return this._stream._isRangeSupported}get isStreamingSupported(){return this._stream._isStreamingSupported}get contentLength(){return this._stream._contentLength}async read(){if(this._queuedChunks.length>0){return{value:this._queuedChunks.shift(),done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0}progressiveDone(){this._done||(this._done=!0)}}class PDFDataTransportStreamRangeReader{constructor(t,e,i){this._stream=t;this._begin=e;this._end=i;this._queuedChunk=null;this._requests=[];this._done=!1;this.onProgress=null}_enqueue(t){if(!this._done){if(0===this._requests.length)this._queuedChunk=t;else{this._requests.shift().resolve({value:t,done:!1});for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0}this._done=!0;this._stream._removeRangeReader(this)}}get isStreamingSupported(){return!1}async read(){if(this._queuedChunk){const t=this._queuedChunk;this._queuedChunk=null;return{value:t,done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._stream._removeRangeReader(this)}}function createHeaders(t,e){const i=new Headers;if(!t||!e||"object"!=typeof e)return i;for(const t in e){const s=e[t];void 0!==s&&i.append(t,s)}return i}function getResponseOrigin(t){return URL.parse(t)?.origin??null}function validateRangeRequestCapabilities({responseHeaders:t,isHttp:e,rangeChunkSize:i,disableRange:s}){const n={allowRangeRequests:!1,suggestedLength:void 0},a=parseInt(t.get("Content-Length"),10);if(!Number.isInteger(a))return n;n.suggestedLength=a;if(a<=2*i)return n;if(s||!e)return n;if("bytes"!==t.get("Accept-Ranges"))return n;if("identity"!==(t.get("Content-Encoding")||"identity"))return n;n.allowRangeRequests=!0;return n}function extractFilenameFromHeader(t){const e=t.get("Content-Disposition");if(e){let t=function getFilenameFromContentDispositionHeader(t){let e=!0,i=toParamRegExp("filename\\*","i").exec(t);if(i){i=i[1];let t=rfc2616unquote(i);t=unescape(t);t=rfc5987decode(t);t=rfc2047decode(t);return fixupEncoding(t)}i=function rfc2231getparam(t){const e=[];let i;const s=toParamRegExp("filename\\*((?!0\\d)\\d+)(\\*?)","ig");for(;null!==(i=s.exec(t));){let[,t,s,n]=i;t=parseInt(t,10);if(t in e){if(0===t)break}else e[t]=[s,n]}const n=[];for(let t=0;t{t._responseOrigin=getResponseOrigin(e.url);if(!validateResponseStatus(e.status))throw createResponseError(e.status,s);this._reader=e.body.getReader();this._headersCapability.resolve();const i=e.headers,{allowRangeRequests:n,suggestedLength:a}=validateRangeRequestCapabilities({responseHeaders:i,isHttp:t.isHttp,rangeChunkSize:this._rangeChunkSize,disableRange:this._disableRange});this._isRangeSupported=n;this._contentLength=a||this._contentLength;this._filename=extractFilenameFromHeader(i);!this._isStreamingSupported&&this._isRangeSupported&&this.cancel(new AbortException("Streaming is disabled."))})).catch(this._headersCapability.reject);this.onProgress=null}get headersReady(){return this._headersCapability.promise}get filename(){return this._filename}get contentLength(){return this._contentLength}get isRangeSupported(){return this._isRangeSupported}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._headersCapability.promise;const{value:t,done:e}=await this._reader.read();if(e)return{value:t,done:e};this._loaded+=t.byteLength;this.onProgress?.({loaded:this._loaded,total:this._contentLength});return{value:getArrayBuffer(t),done:!1}}cancel(t){this._reader?.cancel(t);this._abortController.abort()}}class PDFFetchStreamRangeReader{constructor(t,e,i){this._stream=t;this._reader=null;this._loaded=0;const s=t.source;this._withCredentials=s.withCredentials||!1;this._readCapability=Promise.withResolvers();this._isStreamingSupported=!s.disableStream;this._abortController=new AbortController;const n=new Headers(t.headers);n.append("Range",`bytes=${e}-${i-1}`);const a=s.url;fetch(a,createFetchOptions(n,this._withCredentials,this._abortController)).then((e=>{const i=getResponseOrigin(e.url);if(i!==t._responseOrigin)throw new Error(`Expected range response-origin "${i}" to match "${t._responseOrigin}".`);if(!validateResponseStatus(e.status))throw createResponseError(e.status,a);this._readCapability.resolve();this._reader=e.body.getReader()})).catch(this._readCapability.reject);this.onProgress=null}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._readCapability.promise;const{value:t,done:e}=await this._reader.read();if(e)return{value:t,done:e};this._loaded+=t.byteLength;this.onProgress?.({loaded:this._loaded});return{value:getArrayBuffer(t),done:!1}}cancel(t){this._reader?.cancel(t);this._abortController.abort()}}class NetworkManager{_responseOrigin=null;constructor({url:t,httpHeaders:e,withCredentials:i}){this.url=t;this.isHttp=/^https?:/i.test(t);this.headers=createHeaders(this.isHttp,e);this.withCredentials=i||!1;this.currXhrId=0;this.pendingRequests=Object.create(null)}request(t){const e=new XMLHttpRequest,i=this.currXhrId++,s=this.pendingRequests[i]={xhr:e};e.open("GET",this.url);e.withCredentials=this.withCredentials;for(const[t,i]of this.headers)e.setRequestHeader(t,i);if(this.isHttp&&"begin"in t&&"end"in t){e.setRequestHeader("Range",`bytes=${t.begin}-${t.end-1}`);s.expectedStatus=206}else s.expectedStatus=200;e.responseType="arraybuffer";assert(t.onError,"Expected `onError` callback to be provided.");e.onerror=()=>{t.onError(e.status)};e.onreadystatechange=this.onStateChange.bind(this,i);e.onprogress=this.onProgress.bind(this,i);s.onHeadersReceived=t.onHeadersReceived;s.onDone=t.onDone;s.onError=t.onError;s.onProgress=t.onProgress;e.send(null);return i}onProgress(t,e){const i=this.pendingRequests[t];i&&i.onProgress?.(e)}onStateChange(t,e){const i=this.pendingRequests[t];if(!i)return;const s=i.xhr;if(s.readyState>=2&&i.onHeadersReceived){i.onHeadersReceived();delete i.onHeadersReceived}if(4!==s.readyState)return;if(!(t in this.pendingRequests))return;delete this.pendingRequests[t];if(0===s.status&&this.isHttp){i.onError(s.status);return}const n=s.status||200;if(!(200===n&&206===i.expectedStatus)&&n!==i.expectedStatus){i.onError(s.status);return}const a=function network_getArrayBuffer(t){const e=t.response;return"string"!=typeof e?e:stringToBytes(e).buffer}(s);if(206===n){const t=s.getResponseHeader("Content-Range"),e=/bytes (\d+)-(\d+)\/(\d+)/.exec(t);if(e)i.onDone({begin:parseInt(e[1],10),chunk:a});else{warn('Missing or invalid "Content-Range" header.');i.onError(0)}}else a?i.onDone({begin:0,chunk:a}):i.onError(s.status)}getRequestXhr(t){return this.pendingRequests[t].xhr}isPendingRequest(t){return t in this.pendingRequests}abortRequest(t){const e=this.pendingRequests[t].xhr;delete this.pendingRequests[t];e.abort()}}class PDFNetworkStream{constructor(t){this._source=t;this._manager=new NetworkManager(t);this._rangeChunkSize=t.rangeChunkSize;this._fullRequestReader=null;this._rangeRequestReaders=[]}_onRangeRequestReaderClosed(t){const e=this._rangeRequestReaders.indexOf(t);e>=0&&this._rangeRequestReaders.splice(e,1)}getFullReader(){assert(!this._fullRequestReader,"PDFNetworkStream.getFullReader can only be called once.");this._fullRequestReader=new PDFNetworkStreamFullRequestReader(this._manager,this._source);return this._fullRequestReader}getRangeReader(t,e){const i=new PDFNetworkStreamRangeRequestReader(this._manager,t,e);i.onClosed=this._onRangeRequestReaderClosed.bind(this);this._rangeRequestReaders.push(i);return i}cancelAllRequests(t){this._fullRequestReader?.cancel(t);for(const e of this._rangeRequestReaders.slice(0))e.cancel(t)}}class PDFNetworkStreamFullRequestReader{constructor(t,e){this._manager=t;this._url=e.url;this._fullRequestId=t.request({onHeadersReceived:this._onHeadersReceived.bind(this),onDone:this._onDone.bind(this),onError:this._onError.bind(this),onProgress:this._onProgress.bind(this)});this._headersCapability=Promise.withResolvers();this._disableRange=e.disableRange||!1;this._contentLength=e.length;this._rangeChunkSize=e.rangeChunkSize;this._rangeChunkSize||this._disableRange||(this._disableRange=!0);this._isStreamingSupported=!1;this._isRangeSupported=!1;this._cachedChunks=[];this._requests=[];this._done=!1;this._storedError=void 0;this._filename=null;this.onProgress=null}_onHeadersReceived(){const t=this._fullRequestId,e=this._manager.getRequestXhr(t);this._manager._responseOrigin=getResponseOrigin(e.responseURL);const i=e.getAllResponseHeaders(),s=new Headers(i?i.trimStart().replace(/[^\S ]+$/,"").split(/[\r\n]+/).map((t=>{const[e,...i]=t.split(": ");return[e,i.join(": ")]})):[]),{allowRangeRequests:n,suggestedLength:a}=validateRangeRequestCapabilities({responseHeaders:s,isHttp:this._manager.isHttp,rangeChunkSize:this._rangeChunkSize,disableRange:this._disableRange});n&&(this._isRangeSupported=!0);this._contentLength=a||this._contentLength;this._filename=extractFilenameFromHeader(s);this._isRangeSupported&&this._manager.abortRequest(t);this._headersCapability.resolve()}_onDone(t){if(t)if(this._requests.length>0){this._requests.shift().resolve({value:t.chunk,done:!1})}else this._cachedChunks.push(t.chunk);this._done=!0;if(!(this._cachedChunks.length>0)){for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0}}_onError(t){this._storedError=createResponseError(t,this._url);this._headersCapability.reject(this._storedError);for(const t of this._requests)t.reject(this._storedError);this._requests.length=0;this._cachedChunks.length=0}_onProgress(t){this.onProgress?.({loaded:t.loaded,total:t.lengthComputable?t.total:this._contentLength})}get filename(){return this._filename}get isRangeSupported(){return this._isRangeSupported}get isStreamingSupported(){return this._isStreamingSupported}get contentLength(){return this._contentLength}get headersReady(){return this._headersCapability.promise}async read(){await this._headersCapability.promise;if(this._storedError)throw this._storedError;if(this._cachedChunks.length>0){return{value:this._cachedChunks.shift(),done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;this._headersCapability.reject(t);for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._manager.isPendingRequest(this._fullRequestId)&&this._manager.abortRequest(this._fullRequestId);this._fullRequestReader=null}}class PDFNetworkStreamRangeRequestReader{constructor(t,e,i){this._manager=t;this._url=t.url;this._requestId=t.request({begin:e,end:i,onHeadersReceived:this._onHeadersReceived.bind(this),onDone:this._onDone.bind(this),onError:this._onError.bind(this),onProgress:this._onProgress.bind(this)});this._requests=[];this._queuedChunk=null;this._done=!1;this._storedError=void 0;this.onProgress=null;this.onClosed=null}_onHeadersReceived(){const t=getResponseOrigin(this._manager.getRequestXhr(this._requestId)?.responseURL);if(t!==this._manager._responseOrigin){this._storedError=new Error(`Expected range response-origin "${t}" to match "${this._manager._responseOrigin}".`);this._onError(0)}}_close(){this.onClosed?.(this)}_onDone(t){const e=t.chunk;if(this._requests.length>0){this._requests.shift().resolve({value:e,done:!1})}else this._queuedChunk=e;this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._close()}_onError(t){this._storedError??=createResponseError(t,this._url);for(const t of this._requests)t.reject(this._storedError);this._requests.length=0;this._queuedChunk=null}_onProgress(t){this.isStreamingSupported||this.onProgress?.({loaded:t.loaded})}get isStreamingSupported(){return!1}async read(){if(this._storedError)throw this._storedError;if(null!==this._queuedChunk){const t=this._queuedChunk;this._queuedChunk=null;return{value:t,done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._manager.isPendingRequest(this._requestId)&&this._manager.abortRequest(this._requestId);this._close()}}const bt=/^[a-z][a-z0-9\-+.]+:/i;class PDFNodeStream{constructor(t){this.source=t;this.url=function parseUrlOrPath(t){if(bt.test(t))return new URL(t);const e=process.getBuiltinModule("url");return new URL(e.pathToFileURL(t))}(t.url);assert("file:"===this.url.protocol,"PDFNodeStream only supports file:// URLs.");this._fullRequestReader=null;this._rangeRequestReaders=[]}get _progressiveDataLength(){return this._fullRequestReader?._loaded??0}getFullReader(){assert(!this._fullRequestReader,"PDFNodeStream.getFullReader can only be called once.");this._fullRequestReader=new PDFNodeStreamFsFullReader(this);return this._fullRequestReader}getRangeReader(t,e){if(e<=this._progressiveDataLength)return null;const i=new PDFNodeStreamFsRangeReader(this,t,e);this._rangeRequestReaders.push(i);return i}cancelAllRequests(t){this._fullRequestReader?.cancel(t);for(const e of this._rangeRequestReaders.slice(0))e.cancel(t)}}class PDFNodeStreamFsFullReader{constructor(t){this._url=t.url;this._done=!1;this._storedError=null;this.onProgress=null;const e=t.source;this._contentLength=e.length;this._loaded=0;this._filename=null;this._disableRange=e.disableRange||!1;this._rangeChunkSize=e.rangeChunkSize;this._rangeChunkSize||this._disableRange||(this._disableRange=!0);this._isStreamingSupported=!e.disableStream;this._isRangeSupported=!e.disableRange;this._readableStream=null;this._readCapability=Promise.withResolvers();this._headersCapability=Promise.withResolvers();const i=process.getBuiltinModule("fs");i.promises.lstat(this._url).then((t=>{this._contentLength=t.size;this._setReadableStream(i.createReadStream(this._url));this._headersCapability.resolve()}),(t=>{"ENOENT"===t.code&&(t=createResponseError(0,this._url.href));this._storedError=t;this._headersCapability.reject(t)}))}get headersReady(){return this._headersCapability.promise}get filename(){return this._filename}get contentLength(){return this._contentLength}get isRangeSupported(){return this._isRangeSupported}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._readCapability.promise;if(this._done)return{value:void 0,done:!0};if(this._storedError)throw this._storedError;const t=this._readableStream.read();if(null===t){this._readCapability=Promise.withResolvers();return this.read()}this._loaded+=t.length;this.onProgress?.({loaded:this._loaded,total:this._contentLength});return{value:new Uint8Array(t).buffer,done:!1}}cancel(t){this._readableStream?this._readableStream.destroy(t):this._error(t)}_error(t){this._storedError=t;this._readCapability.resolve()}_setReadableStream(t){this._readableStream=t;t.on("readable",(()=>{this._readCapability.resolve()}));t.on("end",(()=>{t.destroy();this._done=!0;this._readCapability.resolve()}));t.on("error",(t=>{this._error(t)}));!this._isStreamingSupported&&this._isRangeSupported&&this._error(new AbortException("streaming is disabled"));this._storedError&&this._readableStream.destroy(this._storedError)}}class PDFNodeStreamFsRangeReader{constructor(t,e,i){this._url=t.url;this._done=!1;this._storedError=null;this.onProgress=null;this._loaded=0;this._readableStream=null;this._readCapability=Promise.withResolvers();const s=t.source;this._isStreamingSupported=!s.disableStream;const n=process.getBuiltinModule("fs");this._setReadableStream(n.createReadStream(this._url,{start:e,end:i-1}))}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._readCapability.promise;if(this._done)return{value:void 0,done:!0};if(this._storedError)throw this._storedError;const t=this._readableStream.read();if(null===t){this._readCapability=Promise.withResolvers();return this.read()}this._loaded+=t.length;this.onProgress?.({loaded:this._loaded});return{value:new Uint8Array(t).buffer,done:!1}}cancel(t){this._readableStream?this._readableStream.destroy(t):this._error(t)}_error(t){this._storedError=t;this._readCapability.resolve()}_setReadableStream(t){this._readableStream=t;t.on("readable",(()=>{this._readCapability.resolve()}));t.on("end",(()=>{t.destroy();this._done=!0;this._readCapability.resolve()}));t.on("error",(t=>{this._error(t)}));this._storedError&&this._readableStream.destroy(this._storedError)}}class TextLayer{#ji=Promise.withResolvers();#mt=null;#qi=!1;#Xi=!!globalThis.FontInspector?.enabled;#Ki=null;#Yi=null;#Qi=0;#Ji=0;#Zi=null;#ts=null;#es=0;#is=0;#ss=Object.create(null);#ns=[];#as=null;#rs=[];#os=new WeakMap;#ls=null;static#hs=new Map;static#ds=new Map;static#cs=new WeakMap;static#us=null;static#ps=new Set;constructor({textContentSource:t,container:e,viewport:i}){if(t instanceof ReadableStream)this.#as=t;else{if("object"!=typeof t)throw new Error('No "textContentSource" parameter specified.');this.#as=new ReadableStream({start(e){e.enqueue(t);e.close()}})}this.#mt=this.#ts=e;this.#is=i.scale*OutputScale.pixelRatio;this.#es=i.rotation;this.#Yi={div:null,properties:null,ctx:null};const{pageWidth:s,pageHeight:n,pageX:a,pageY:r}=i.rawDims;this.#ls=[1,0,0,-1,-a,r+n];this.#Ji=s;this.#Qi=n;TextLayer.#gs();setLayerDimensions(e,i);this.#ji.promise.finally((()=>{TextLayer.#ps.delete(this);this.#Yi=null;this.#ss=null})).catch((()=>{}))}static get fontFamilyMap(){const{isWindows:t,isFirefox:e}=util_FeatureTest.platform;return shadow(this,"fontFamilyMap",new Map([["sans-serif",(t&&e?"Calibri, ":"")+"sans-serif"],["monospace",(t&&e?"Lucida Console, ":"")+"monospace"]]))}render(){const pump=()=>{this.#Zi.read().then((({value:t,done:e})=>{if(e)this.#ji.resolve();else{this.#Ki??=t.lang;Object.assign(this.#ss,t.styles);this.#fs(t.items);pump()}}),this.#ji.reject)};this.#Zi=this.#as.getReader();TextLayer.#ps.add(this);pump();return this.#ji.promise}update({viewport:t,onBefore:e=null}){const i=t.scale*OutputScale.pixelRatio,s=t.rotation;if(s!==this.#es){e?.();this.#es=s;setLayerDimensions(this.#ts,{rotation:s})}if(i!==this.#is){e?.();this.#is=i;const t={div:null,properties:null,ctx:TextLayer.#ms(this.#Ki)};for(const e of this.#rs){t.properties=this.#os.get(e);t.div=e;this.#bs(t)}}}cancel(){const t=new AbortException("TextLayer task cancelled.");this.#Zi?.cancel(t).catch((()=>{}));this.#Zi=null;this.#ji.reject(t)}get textDivs(){return this.#rs}get textContentItemsStr(){return this.#ns}#fs(t){if(this.#qi)return;this.#Yi.ctx??=TextLayer.#ms(this.#Ki);const e=this.#rs,i=this.#ns;for(const s of t){if(e.length>1e5){warn("Ignoring additional textDivs for performance reasons.");this.#qi=!0;return}if(void 0!==s.str){i.push(s.str);this.#As(s)}else if("beginMarkedContentProps"===s.type||"beginMarkedContent"===s.type){const t=this.#mt;this.#mt=document.createElement("span");this.#mt.classList.add("markedContent");null!==s.id&&this.#mt.setAttribute("id",`${s.id}`);t.append(this.#mt)}else"endMarkedContent"===s.type&&(this.#mt=this.#mt.parentNode)}}#As(t){const e=document.createElement("span"),i={angle:0,canvasWidth:0,hasText:""!==t.str,hasEOL:t.hasEOL,fontSize:0};this.#rs.push(e);const s=Util.transform(this.#ls,t.transform);let n=Math.atan2(s[1],s[0]);const a=this.#ss[t.fontName];a.vertical&&(n+=Math.PI/2);let r=this.#Xi&&a.fontSubstitution||a.fontFamily;r=TextLayer.fontFamilyMap.get(r)||r;const o=Math.hypot(s[2],s[3]),l=o*TextLayer.#ws(r,a,this.#Ki);let h,d;if(0===n){h=s[4];d=s[5]-l}else{h=s[4]+l*Math.sin(n);d=s[5]-l*Math.cos(n)}const c="calc(var(--total-scale-factor) *",u=e.style;if(this.#mt===this.#ts){u.left=`${(100*h/this.#Ji).toFixed(2)}%`;u.top=`${(100*d/this.#Qi).toFixed(2)}%`}else{u.left=`${c}${h.toFixed(2)}px)`;u.top=`${c}${d.toFixed(2)}px)`}u.fontSize=`${c}${(TextLayer.#us*o).toFixed(2)}px)`;u.fontFamily=r;i.fontSize=o;e.setAttribute("role","presentation");e.textContent=t.str;e.dir=t.dir;this.#Xi&&(e.dataset.fontName=a.fontSubstitutionLoadedName||t.fontName);0!==n&&(i.angle=n*(180/Math.PI));let p=!1;if(t.str.length>1)p=!0;else if(" "!==t.str&&t.transform[0]!==t.transform[3]){const e=Math.abs(t.transform[0]),i=Math.abs(t.transform[3]);e!==i&&Math.max(e,i)/Math.min(e,i)>1.5&&(p=!0)}p&&(i.canvasWidth=a.vertical?t.height:t.width);this.#os.set(e,i);this.#Yi.div=e;this.#Yi.properties=i;this.#bs(this.#Yi);i.hasText&&this.#mt.append(e);if(i.hasEOL){const t=document.createElement("br");t.setAttribute("role","presentation");this.#mt.append(t)}}#bs(t){const{div:e,properties:i,ctx:s}=t,{style:n}=e;let a="";TextLayer.#us>1&&(a=`scale(${1/TextLayer.#us})`);if(0!==i.canvasWidth&&i.hasText){const{fontFamily:t}=n,{canvasWidth:r,fontSize:o}=i;TextLayer.#ys(s,o*this.#is,t);const{width:l}=s.measureText(e.textContent);l>0&&(a=`scaleX(${r*this.#is/l}) ${a}`)}0!==i.angle&&(a=`rotate(${i.angle}deg) ${a}`);a.length>0&&(n.transform=a)}static cleanup(){if(!(this.#ps.size>0)){this.#hs.clear();for(const{canvas:t}of this.#ds.values())t.remove();this.#ds.clear()}}static#ms(t=null){let e=this.#ds.get(t||="");if(!e){const i=document.createElement("canvas");i.className="hiddenCanvasElement";i.lang=t;document.body.append(i);e=i.getContext("2d",{alpha:!1,willReadFrequently:!0});this.#ds.set(t,e);this.#cs.set(e,{size:0,family:""})}return e}static#ys(t,e,i){const s=this.#cs.get(t);if(e!==s.size||i!==s.family){t.font=`${e}px ${i}`;s.size=e;s.family=i}}static#gs(){if(null!==this.#us)return;const t=document.createElement("div");t.style.opacity=0;t.style.lineHeight=1;t.style.fontSize="1px";t.style.position="absolute";t.textContent="X";document.body.append(t);this.#us=t.getBoundingClientRect().height;t.remove()}static#ws(t,e,i){const s=this.#hs.get(t);if(s)return s;const n=this.#ms(i);n.canvas.width=n.canvas.height=30;this.#ys(n,30,t);const a=n.measureText(""),r=a.fontBoundingBoxAscent,o=Math.abs(a.fontBoundingBoxDescent);n.canvas.width=n.canvas.height=0;let l=.8;if(r)l=r/(r+o);else{util_FeatureTest.platform.isFirefox&&warn("Enable the `dom.textMetrics.fontBoundingBox.enabled` preference in `about:config` to improve TextLayer rendering.");e.ascent?l=e.ascent:e.descent&&(l=1+e.descent)}this.#hs.set(t,l);return l}}class XfaText{static textContent(t){const e=[],i={items:e,styles:Object.create(null)};!function walk(t){if(!t)return;let i=null;const s=t.name;if("#text"===s)i=t.value;else{if(!XfaText.shouldBuildText(s))return;t?.attributes?.textContent?i=t.attributes.textContent:t.value&&(i=t.value)}null!==i&&e.push({str:i});if(t.children)for(const e of t.children)walk(e)}(t);return i}static shouldBuildText(t){return!("textarea"===t||"input"===t||"option"===t||"select"===t)}}function getDocument(e={}){"string"==typeof e||e instanceof URL?e={url:e}:(e instanceof ArrayBuffer||ArrayBuffer.isView(e))&&(e={data:e});const i=new PDFDocumentLoadingTask,{docId:s}=i,n=e.url?function getUrlProp(e){if(e instanceof URL)return e.href;if("string"==typeof e){if(t)return e;const i=URL.parse(e,window.location);if(i)return i.href}throw new Error("Invalid PDF url data: either string or URL-object is expected in the url property.")}(e.url):null,a=e.data?function getDataProp(e){if(t&&"undefined"!=typeof Buffer&&e instanceof Buffer)throw new Error("Please provide binary data as `Uint8Array`, rather than `Buffer`.");if(e instanceof Uint8Array&&e.byteLength===e.buffer.byteLength)return e;if("string"==typeof e)return stringToBytes(e);if(e instanceof ArrayBuffer||ArrayBuffer.isView(e)||"object"==typeof e&&!isNaN(e?.length))return new Uint8Array(e);throw new Error("Invalid PDF binary data: either TypedArray, string, or array-like object is expected in the data property.")}(e.data):null,r=e.httpHeaders||null,o=!0===e.withCredentials,l=e.password??null,h=e.range instanceof PDFDataRangeTransport?e.range:null,d=Number.isInteger(e.rangeChunkSize)&&e.rangeChunkSize>0?e.rangeChunkSize:65536;let c=e.worker instanceof PDFWorker?e.worker:null;const u=e.verbosity,p="string"!=typeof e.docBaseUrl||isDataScheme(e.docBaseUrl)?null:e.docBaseUrl,g=getFactoryUrlProp(e.cMapUrl),f=!1!==e.cMapPacked,m=e.CMapReaderFactory||(t?NodeCMapReaderFactory:DOMCMapReaderFactory),b=getFactoryUrlProp(e.iccUrl),A=getFactoryUrlProp(e.standardFontDataUrl),w=e.StandardFontDataFactory||(t?NodeStandardFontDataFactory:DOMStandardFontDataFactory),y=getFactoryUrlProp(e.wasmUrl),v=e.WasmFactory||(t?NodeWasmFactory:DOMWasmFactory),_=!0!==e.stopAtErrors,x=Number.isInteger(e.maxImageSize)&&e.maxImageSize>-1?e.maxImageSize:-1,E=!1!==e.isEvalSupported,S="boolean"==typeof e.isOffscreenCanvasSupported?e.isOffscreenCanvasSupported:!t,C="boolean"==typeof e.isImageDecoderSupported?e.isImageDecoderSupported:!t&&(util_FeatureTest.platform.isFirefox||!globalThis.chrome),T=Number.isInteger(e.canvasMaxAreaInBytes)?e.canvasMaxAreaInBytes:-1,M="boolean"==typeof e.disableFontFace?e.disableFontFace:t,D=!0===e.fontExtraProperties,P=!0===e.enableXfa,k=e.ownerDocument||globalThis.document,I=!0===e.disableRange,R=!0===e.disableStream,F=!0===e.disableAutoFetch,L=!0===e.pdfBug,O=e.CanvasFactory||(t?NodeCanvasFactory:DOMCanvasFactory),N=e.FilterFactory||(t?NodeFilterFactory:DOMFilterFactory),B=!0===e.enableHWA,H=!1!==e.useWasm,U=h?h.length:e.length??NaN,z="boolean"==typeof e.useSystemFonts?e.useSystemFonts:!t&&!M,G="boolean"==typeof e.useWorkerFetch?e.useWorkerFetch:!!(m===DOMCMapReaderFactory&&w===DOMStandardFontDataFactory&&v===DOMWasmFactory&&g&&A&&y&&isValidFetchUrl(g,document.baseURI)&&isValidFetchUrl(A,document.baseURI)&&isValidFetchUrl(y,document.baseURI));setVerbosityLevel(u);const $={canvasFactory:new O({ownerDocument:k,enableHWA:B}),filterFactory:new N({docId:s,ownerDocument:k}),cMapReaderFactory:G?null:new m({baseUrl:g,isCompressed:f}),standardFontDataFactory:G?null:new w({baseUrl:A}),wasmFactory:G?null:new v({baseUrl:y})};if(!c){const t={verbosity:u,port:GlobalWorkerOptions.workerPort};c=t.port?PDFWorker.fromPort(t):new PDFWorker(t);i._worker=c}const W={docId:s,apiVersion:"5.2.133",data:a,password:l,disableAutoFetch:F,rangeChunkSize:d,length:U,docBaseUrl:p,enableXfa:P,evaluatorOptions:{maxImageSize:x,disableFontFace:M,ignoreErrors:_,isEvalSupported:E,isOffscreenCanvasSupported:S,isImageDecoderSupported:C,canvasMaxAreaInBytes:T,fontExtraProperties:D,useSystemFonts:z,useWasm:H,useWorkerFetch:G,cMapUrl:g,iccUrl:b,standardFontDataUrl:A,wasmUrl:y}},V={ownerDocument:k,pdfBug:L,styleElement:null,loadingParams:{disableAutoFetch:F,enableXfa:P}};c.promise.then((function(){if(i.destroyed)throw new Error("Loading aborted");if(c.destroyed)throw new Error("Worker was destroyed");const e=c.messageHandler.sendWithPromise("GetDocRequest",W,a?[a.buffer]:null);let l;if(h)l=new PDFDataTransportStream(h,{disableRange:I,disableStream:R});else if(!a){if(!n)throw new Error("getDocument - no `url` parameter provided.");let e;if(t)if(isValidFetchUrl(n)){if("undefined"==typeof fetch||"undefined"==typeof Response||!("body"in Response.prototype))throw new Error("getDocument - the Fetch API was disabled in Node.js, see `--no-experimental-fetch`.");e=PDFFetchStream}else e=PDFNodeStream;else e=isValidFetchUrl(n)?PDFFetchStream:PDFNetworkStream;l=new e({url:n,length:U,httpHeaders:r,withCredentials:o,rangeChunkSize:d,disableRange:I,disableStream:R})}return e.then((t=>{if(i.destroyed)throw new Error("Loading aborted");if(c.destroyed)throw new Error("Worker was destroyed");const e=new MessageHandler(s,t,c.port),n=new WorkerTransport(e,i,l,V,$);i._transport=n;e.send("Ready",null)}))})).catch(i._capability.reject);return i}function getFactoryUrlProp(t){if("string"!=typeof t)return null;if(t.endsWith("/"))return t;throw new Error(`Invalid factory url: "${t}" must include trailing slash.`)}const isRefProxy=t=>"object"==typeof t&&Number.isInteger(t?.num)&&t.num>=0&&Number.isInteger(t?.gen)&&t.gen>=0,At=function _isValidExplicitDest(t,e,i){if(!Array.isArray(i)||i.length<2)return!1;const[s,n,...a]=i;if(!t(s)&&!Number.isInteger(s))return!1;if(!e(n))return!1;const r=a.length;let o=!0;switch(n.name){case"XYZ":if(r<2||r>3)return!1;break;case"Fit":case"FitB":return 0===r;case"FitH":case"FitBH":case"FitV":case"FitBV":if(r>1)return!1;break;case"FitR":if(4!==r)return!1;o=!1;break;default:return!1}for(const t of a)if(!("number"==typeof t||o&&null===t))return!1;return!0}.bind(null,isRefProxy,(t=>"object"==typeof t&&"string"==typeof t?.name));class PDFDocumentLoadingTask{static#mi=0;_capability=Promise.withResolvers();_transport=null;_worker=null;docId="d"+PDFDocumentLoadingTask.#mi++;destroyed=!1;onPassword=null;onProgress=null;get promise(){return this._capability.promise}async destroy(){this.destroyed=!0;try{this._worker?.port&&(this._worker._pendingDestroy=!0);await(this._transport?.destroy())}catch(t){this._worker?.port&&delete this._worker._pendingDestroy;throw t}this._transport=null;this._worker?.destroy();this._worker=null}async getData(){return this._transport.getData()}}class PDFDataRangeTransport{constructor(t,e,i=!1,s=null){this.length=t;this.initialData=e;this.progressiveDone=i;this.contentDispositionFilename=s;this._rangeListeners=[];this._progressListeners=[];this._progressiveReadListeners=[];this._progressiveDoneListeners=[];this._readyCapability=Promise.withResolvers()}addRangeListener(t){this._rangeListeners.push(t)}addProgressListener(t){this._progressListeners.push(t)}addProgressiveReadListener(t){this._progressiveReadListeners.push(t)}addProgressiveDoneListener(t){this._progressiveDoneListeners.push(t)}onDataRange(t,e){for(const i of this._rangeListeners)i(t,e)}onDataProgress(t,e){this._readyCapability.promise.then((()=>{for(const i of this._progressListeners)i(t,e)}))}onDataProgressiveRead(t){this._readyCapability.promise.then((()=>{for(const e of this._progressiveReadListeners)e(t)}))}onDataProgressiveDone(){this._readyCapability.promise.then((()=>{for(const t of this._progressiveDoneListeners)t()}))}transportReady(){this._readyCapability.resolve()}requestDataRange(t,e){unreachable("Abstract method PDFDataRangeTransport.requestDataRange")}abort(){}}class PDFDocumentProxy{constructor(t,e){this._pdfInfo=t;this._transport=e}get annotationStorage(){return this._transport.annotationStorage}get canvasFactory(){return this._transport.canvasFactory}get filterFactory(){return this._transport.filterFactory}get numPages(){return this._pdfInfo.numPages}get fingerprints(){return this._pdfInfo.fingerprints}get isPureXfa(){return shadow(this,"isPureXfa",!!this._transport._htmlForXfa)}get allXfaHtml(){return this._transport._htmlForXfa}getPage(t){return this._transport.getPage(t)}getPageIndex(t){return this._transport.getPageIndex(t)}getDestinations(){return this._transport.getDestinations()}getDestination(t){return this._transport.getDestination(t)}getPageLabels(){return this._transport.getPageLabels()}getPageLayout(){return this._transport.getPageLayout()}getPageMode(){return this._transport.getPageMode()}getViewerPreferences(){return this._transport.getViewerPreferences()}getOpenAction(){return this._transport.getOpenAction()}getAttachments(){return this._transport.getAttachments()}getJSActions(){return this._transport.getDocJSActions()}getOutline(){return this._transport.getOutline()}getOptionalContentConfig({intent:t="display"}={}){const{renderingIntent:e}=this._transport.getRenderingIntent(t);return this._transport.getOptionalContentConfig(e)}getPermissions(){return this._transport.getPermissions()}getMetadata(){return this._transport.getMetadata()}getMarkInfo(){return this._transport.getMarkInfo()}getData(){return this._transport.getData()}saveDocument(){return this._transport.saveDocument()}getDownloadInfo(){return this._transport.downloadInfoCapability.promise}cleanup(t=!1){return this._transport.startCleanup(t||this.isPureXfa)}destroy(){return this.loadingTask.destroy()}cachedPageNumber(t){return this._transport.cachedPageNumber(t)}get loadingParams(){return this._transport.loadingParams}get loadingTask(){return this._transport.loadingTask}getFieldObjects(){return this._transport.getFieldObjects()}hasJSActions(){return this._transport.hasJSActions()}getCalculationOrderIds(){return this._transport.getCalculationOrderIds()}}class PDFPageProxy{#vs=!1;constructor(t,e,i,s=!1){this._pageIndex=t;this._pageInfo=e;this._transport=i;this._stats=s?new StatTimer:null;this._pdfBug=s;this.commonObjs=i.commonObjs;this.objs=new PDFObjects;this._intentStates=new Map;this.destroyed=!1}get pageNumber(){return this._pageIndex+1}get rotate(){return this._pageInfo.rotate}get ref(){return this._pageInfo.ref}get userUnit(){return this._pageInfo.userUnit}get view(){return this._pageInfo.view}getViewport({scale:t,rotation:e=this.rotate,offsetX:i=0,offsetY:s=0,dontFlip:n=!1}={}){return new PageViewport({viewBox:this.view,userUnit:this.userUnit,scale:t,rotation:e,offsetX:i,offsetY:s,dontFlip:n})}getAnnotations({intent:t="display"}={}){const{renderingIntent:e}=this._transport.getRenderingIntent(t);return this._transport.getAnnotations(this._pageIndex,e)}getJSActions(){return this._transport.getPageJSActions(this._pageIndex)}get filterFactory(){return this._transport.filterFactory}get isPureXfa(){return shadow(this,"isPureXfa",!!this._transport._htmlForXfa)}async getXfa(){return this._transport._htmlForXfa?.children[this._pageIndex]||null}render({canvasContext:t,viewport:e,intent:i="display",annotationMode:s=c.ENABLE,transform:n=null,background:r=null,optionalContentConfigPromise:o=null,annotationCanvasMap:l=null,pageColors:h=null,printAnnotationStorage:d=null,isEditing:u=!1}){this._stats?.time("Overall");const p=this._transport.getRenderingIntent(i,s,d,u),{renderingIntent:g,cacheKey:f}=p;this.#vs=!1;o||=this._transport.getOptionalContentConfig(g);let m=this._intentStates.get(f);if(!m){m=Object.create(null);this._intentStates.set(f,m)}if(m.streamReaderCancelTimeout){clearTimeout(m.streamReaderCancelTimeout);m.streamReaderCancelTimeout=null}const b=!!(g&a);if(!m.displayReadyCapability){m.displayReadyCapability=Promise.withResolvers();m.operatorList={fnArray:[],argsArray:[],lastChunk:!1,separateAnnots:null};this._stats?.time("Page Request");this._pumpOperatorList(p)}const complete=t=>{m.renderTasks.delete(A);b&&(this.#vs=!0);this.#_s();if(t){A.capability.reject(t);this._abortOperatorList({intentState:m,reason:t instanceof Error?t:new Error(t)})}else A.capability.resolve();if(this._stats){this._stats.timeEnd("Rendering");this._stats.timeEnd("Overall");globalThis.Stats?.enabled&&globalThis.Stats.add(this.pageNumber,this._stats)}},A=new InternalRenderTask({callback:complete,params:{canvasContext:t,viewport:e,transform:n,background:r},objs:this.objs,commonObjs:this.commonObjs,annotationCanvasMap:l,operatorList:m.operatorList,pageIndex:this._pageIndex,canvasFactory:this._transport.canvasFactory,filterFactory:this._transport.filterFactory,useRequestAnimationFrame:!b,pdfBug:this._pdfBug,pageColors:h});(m.renderTasks||=new Set).add(A);const w=A.task;Promise.all([m.displayReadyCapability.promise,o]).then((([t,e])=>{if(this.destroyed)complete();else{this._stats?.time("Rendering");if(!(e.renderingIntent&g))throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` and `PDFDocumentProxy.getOptionalContentConfig` methods.");A.initializeGraphics({transparency:t,optionalContentConfig:e});A.operatorListChanged()}})).catch(complete);return w}getOperatorList({intent:t="display",annotationMode:e=c.ENABLE,printAnnotationStorage:i=null,isEditing:s=!1}={}){const n=this._transport.getRenderingIntent(t,e,i,s,!0);let a,r=this._intentStates.get(n.cacheKey);if(!r){r=Object.create(null);this._intentStates.set(n.cacheKey,r)}if(!r.opListReadCapability){a=Object.create(null);a.operatorListChanged=function operatorListChanged(){if(r.operatorList.lastChunk){r.opListReadCapability.resolve(r.operatorList);r.renderTasks.delete(a)}};r.opListReadCapability=Promise.withResolvers();(r.renderTasks||=new Set).add(a);r.operatorList={fnArray:[],argsArray:[],lastChunk:!1,separateAnnots:null};this._stats?.time("Page Request");this._pumpOperatorList(n)}return r.opListReadCapability.promise}streamTextContent({includeMarkedContent:t=!1,disableNormalization:e=!1}={}){return this._transport.messageHandler.sendWithStream("GetTextContent",{pageIndex:this._pageIndex,includeMarkedContent:!0===t,disableNormalization:!0===e},{highWaterMark:100,size:t=>t.items.length})}getTextContent(t={}){if(this._transport._htmlForXfa)return this.getXfa().then((t=>XfaText.textContent(t)));const e=this.streamTextContent(t);return new Promise((function(t,i){const s=e.getReader(),n={items:[],styles:Object.create(null),lang:null};!function pump(){s.read().then((function({value:e,done:i}){if(i)t(n);else{n.lang??=e.lang;Object.assign(n.styles,e.styles);n.items.push(...e.items);pump()}}),i)}()}))}getStructTree(){return this._transport.getStructTree(this._pageIndex)}_destroy(){this.destroyed=!0;const t=[];for(const e of this._intentStates.values()){this._abortOperatorList({intentState:e,reason:new Error("Page was destroyed."),force:!0});if(!e.opListReadCapability)for(const i of e.renderTasks){t.push(i.completed);i.cancel()}}this.objs.clear();this.#vs=!1;return Promise.all(t)}cleanup(t=!1){this.#vs=!0;const e=this.#_s();t&&e&&(this._stats&&=new StatTimer);return e}#_s(){if(!this.#vs||this.destroyed)return!1;for(const{renderTasks:t,operatorList:e}of this._intentStates.values())if(t.size>0||!e.lastChunk)return!1;this._intentStates.clear();this.objs.clear();this.#vs=!1;return!0}_startRenderPage(t,e){const i=this._intentStates.get(e);if(i){this._stats?.timeEnd("Page Request");i.displayReadyCapability?.resolve(t)}}_renderPageChunk(t,e){for(let i=0,s=t.length;i{r.read().then((({value:t,done:e})=>{if(e)o.streamReader=null;else if(!this._transport.destroyed){this._renderPageChunk(t,o);pump()}}),(t=>{o.streamReader=null;if(!this._transport.destroyed){if(o.operatorList){o.operatorList.lastChunk=!0;for(const t of o.renderTasks)t.operatorListChanged();this.#_s()}if(o.displayReadyCapability)o.displayReadyCapability.reject(t);else{if(!o.opListReadCapability)throw t;o.opListReadCapability.reject(t)}}}))};pump()}_abortOperatorList({intentState:t,reason:e,force:i=!1}){if(t.streamReader){if(t.streamReaderCancelTimeout){clearTimeout(t.streamReaderCancelTimeout);t.streamReaderCancelTimeout=null}if(!i){if(t.renderTasks.size>0)return;if(e instanceof RenderingCancelledException){let i=100;e.extraDelay>0&&e.extraDelay<1e3&&(i+=e.extraDelay);t.streamReaderCancelTimeout=setTimeout((()=>{t.streamReaderCancelTimeout=null;this._abortOperatorList({intentState:t,reason:e,force:!0})}),i);return}}t.streamReader.cancel(new AbortException(e.message)).catch((()=>{}));t.streamReader=null;if(!this._transport.destroyed){for(const[e,i]of this._intentStates)if(i===t){this._intentStates.delete(e);break}this.cleanup()}}}get stats(){return this._stats}}class LoopbackPort{#xs=new Map;#Es=Promise.resolve();postMessage(t,e){const i={data:structuredClone(t,e?{transfer:e}:null)};this.#Es.then((()=>{for(const[t]of this.#xs)t.call(this,i)}))}addEventListener(t,e,i=null){let s=null;if(i?.signal instanceof AbortSignal){const{signal:n}=i;if(n.aborted){warn("LoopbackPort - cannot use an `aborted` signal.");return}const onAbort=()=>this.removeEventListener(t,e);s=()=>n.removeEventListener("abort",onAbort);n.addEventListener("abort",onAbort)}this.#xs.set(e,s)}removeEventListener(t,e){const i=this.#xs.get(e);i?.();this.#xs.delete(e)}terminate(){for(const[,t]of this.#xs)t?.();this.#xs.clear()}}class PDFWorker{static#Ss=0;static#Cs=!1;static#Ts;static{if(t){this.#Cs=!0;GlobalWorkerOptions.workerSrc||="./pdf.worker.mjs"}this._isSameOrigin=(t,e)=>{const i=URL.parse(t);if(!i?.origin||"null"===i.origin)return!1;const s=new URL(e,i);return i.origin===s.origin};this._createCDNWrapper=t=>{const e=`await import("${t}");`;return URL.createObjectURL(new Blob([e],{type:"text/javascript"}))}}constructor({name:t=null,port:e=null,verbosity:i=getVerbosityLevel()}={}){this.name=t;this.destroyed=!1;this.verbosity=i;this._readyCapability=Promise.withResolvers();this._port=null;this._webWorker=null;this._messageHandler=null;if(e){if(PDFWorker.#Ts?.has(e))throw new Error("Cannot use more than one PDFWorker per port.");(PDFWorker.#Ts||=new WeakMap).set(e,this);this._initializeFromPort(e)}else this._initialize()}get promise(){return this._readyCapability.promise}#Ms(){this._readyCapability.resolve();this._messageHandler.send("configure",{verbosity:this.verbosity})}get port(){return this._port}get messageHandler(){return this._messageHandler}_initializeFromPort(t){this._port=t;this._messageHandler=new MessageHandler("main","worker",t);this._messageHandler.on("ready",(function(){}));this.#Ms()}_initialize(){if(PDFWorker.#Cs||PDFWorker.#Ds){this._setupFakeWorker();return}let{workerSrc:t}=PDFWorker;try{PDFWorker._isSameOrigin(window.location,t)||(t=PDFWorker._createCDNWrapper(new URL(t,window.location).href));const e=new Worker(t,{type:"module"}),i=new MessageHandler("main","worker",e),terminateEarly=()=>{s.abort();i.destroy();e.terminate();this.destroyed?this._readyCapability.reject(new Error("Worker was destroyed")):this._setupFakeWorker()},s=new AbortController;e.addEventListener("error",(()=>{this._webWorker||terminateEarly()}),{signal:s.signal});i.on("test",(t=>{s.abort();if(!this.destroyed&&t){this._messageHandler=i;this._port=e;this._webWorker=e;this.#Ms()}else terminateEarly()}));i.on("ready",(t=>{s.abort();if(this.destroyed)terminateEarly();else try{sendTest()}catch{this._setupFakeWorker()}}));const sendTest=()=>{const t=new Uint8Array;i.send("test",t,[t.buffer])};sendTest();return}catch{info("The worker has been disabled.")}this._setupFakeWorker()}_setupFakeWorker(){if(!PDFWorker.#Cs){warn("Setting up fake worker.");PDFWorker.#Cs=!0}PDFWorker._setupFakeWorkerGlobal.then((t=>{if(this.destroyed){this._readyCapability.reject(new Error("Worker was destroyed"));return}const e=new LoopbackPort;this._port=e;const i="fake"+PDFWorker.#Ss++,s=new MessageHandler(i+"_worker",i,e);t.setup(s,e);this._messageHandler=new MessageHandler(i,i+"_worker",e);this.#Ms()})).catch((t=>{this._readyCapability.reject(new Error(`Setting up fake worker failed: "${t.message}".`))}))}destroy(){this.destroyed=!0;this._webWorker?.terminate();this._webWorker=null;PDFWorker.#Ts?.delete(this._port);this._port=null;this._messageHandler?.destroy();this._messageHandler=null}static fromPort(t){if(!t?.port)throw new Error("PDFWorker.fromPort - invalid method signature.");const e=this.#Ts?.get(t.port);if(e){if(e._pendingDestroy)throw new Error("PDFWorker.fromPort - the worker is being destroyed.\nPlease remember to await `PDFDocumentLoadingTask.destroy()`-calls.");return e}return new PDFWorker(t)}static get workerSrc(){if(GlobalWorkerOptions.workerSrc)return GlobalWorkerOptions.workerSrc;throw new Error('No "GlobalWorkerOptions.workerSrc" specified.')}static get#Ds(){try{return globalThis.pdfjsWorker?.WorkerMessageHandler||null}catch{return null}}static get _setupFakeWorkerGlobal(){return shadow(this,"_setupFakeWorkerGlobal",(async()=>{if(this.#Ds)return this.#Ds;return(await import(this.workerSrc)).WorkerMessageHandler})())}}class WorkerTransport{#Ps=new Map;#ks=new Map;#Is=new Map;#Rs=new Map;#Fs=null;constructor(t,e,i,s,n){this.messageHandler=t;this.loadingTask=e;this.commonObjs=new PDFObjects;this.fontLoader=new FontLoader({ownerDocument:s.ownerDocument,styleElement:s.styleElement});this.loadingParams=s.loadingParams;this._params=s;this.canvasFactory=n.canvasFactory;this.filterFactory=n.filterFactory;this.cMapReaderFactory=n.cMapReaderFactory;this.standardFontDataFactory=n.standardFontDataFactory;this.wasmFactory=n.wasmFactory;this.destroyed=!1;this.destroyCapability=null;this._networkStream=i;this._fullReader=null;this._lastProgress=null;this.downloadInfoCapability=Promise.withResolvers();this.setupMessageHandler()}#Ls(t,e=null){const i=this.#Ps.get(t);if(i)return i;const s=this.messageHandler.sendWithPromise(t,e);this.#Ps.set(t,s);return s}get annotationStorage(){return shadow(this,"annotationStorage",new AnnotationStorage)}getRenderingIntent(t,e=c.ENABLE,i=null,u=!1,p=!1){let g=n,f=j;switch(t){case"any":g=s;break;case"display":break;case"print":g=a;break;default:warn(`getRenderingIntent - invalid intent: ${t}`)}const m=g&a&&i instanceof PrintAnnotationStorage?i:this.annotationStorage;switch(e){case c.DISABLE:g+=l;break;case c.ENABLE:break;case c.ENABLE_FORMS:g+=r;break;case c.ENABLE_STORAGE:g+=o;f=m.serializable;break;default:warn(`getRenderingIntent - invalid annotationMode: ${e}`)}u&&(g+=h);p&&(g+=d);const{ids:b,hash:A}=m.modifiedIds;return{renderingIntent:g,cacheKey:[g,f.hash,A].join("_"),annotationStorageSerializable:f,modifiedIds:b}}destroy(){if(this.destroyCapability)return this.destroyCapability.promise;this.destroyed=!0;this.destroyCapability=Promise.withResolvers();this.#Fs?.reject(new Error("Worker was destroyed during onPassword callback"));const t=[];for(const e of this.#ks.values())t.push(e._destroy());this.#ks.clear();this.#Is.clear();this.#Rs.clear();this.hasOwnProperty("annotationStorage")&&this.annotationStorage.resetModified();const e=this.messageHandler.sendWithPromise("Terminate",null);t.push(e);Promise.all(t).then((()=>{this.commonObjs.clear();this.fontLoader.clear();this.#Ps.clear();this.filterFactory.destroy();TextLayer.cleanup();this._networkStream?.cancelAllRequests(new AbortException("Worker was terminated."));this.messageHandler?.destroy();this.messageHandler=null;this.destroyCapability.resolve()}),this.destroyCapability.reject);return this.destroyCapability.promise}setupMessageHandler(){const{messageHandler:t,loadingTask:e}=this;t.on("GetReader",((t,e)=>{assert(this._networkStream,"GetReader - no `IPDFStream` instance available.");this._fullReader=this._networkStream.getFullReader();this._fullReader.onProgress=t=>{this._lastProgress={loaded:t.loaded,total:t.total}};e.onPull=()=>{this._fullReader.read().then((function({value:t,done:i}){if(i)e.close();else{assert(t instanceof ArrayBuffer,"GetReader - expected an ArrayBuffer.");e.enqueue(new Uint8Array(t),1,[t])}})).catch((t=>{e.error(t)}))};e.onCancel=t=>{this._fullReader.cancel(t);e.ready.catch((t=>{if(!this.destroyed)throw t}))}}));t.on("ReaderHeadersReady",(async t=>{await this._fullReader.headersReady;const{isStreamingSupported:i,isRangeSupported:s,contentLength:n}=this._fullReader;if(!i||!s){this._lastProgress&&e.onProgress?.(this._lastProgress);this._fullReader.onProgress=t=>{e.onProgress?.({loaded:t.loaded,total:t.total})}}return{isStreamingSupported:i,isRangeSupported:s,contentLength:n}}));t.on("GetRangeReader",((t,e)=>{assert(this._networkStream,"GetRangeReader - no `IPDFStream` instance available.");const i=this._networkStream.getRangeReader(t.begin,t.end);if(i){e.onPull=()=>{i.read().then((function({value:t,done:i}){if(i)e.close();else{assert(t instanceof ArrayBuffer,"GetRangeReader - expected an ArrayBuffer.");e.enqueue(new Uint8Array(t),1,[t])}})).catch((t=>{e.error(t)}))};e.onCancel=t=>{i.cancel(t);e.ready.catch((t=>{if(!this.destroyed)throw t}))}}else e.close()}));t.on("GetDoc",(({pdfInfo:t})=>{this._numPages=t.numPages;this._htmlForXfa=t.htmlForXfa;delete t.htmlForXfa;e._capability.resolve(new PDFDocumentProxy(t,this))}));t.on("DocException",(t=>{e._capability.reject(wrapReason(t))}));t.on("PasswordRequest",(t=>{this.#Fs=Promise.withResolvers();try{if(!e.onPassword)throw wrapReason(t);const updatePassword=t=>{t instanceof Error?this.#Fs.reject(t):this.#Fs.resolve({password:t})};e.onPassword(updatePassword,t.code)}catch(t){this.#Fs.reject(t)}return this.#Fs.promise}));t.on("DataLoaded",(t=>{e.onProgress?.({loaded:t.length,total:t.length});this.downloadInfoCapability.resolve(t)}));t.on("StartRenderPage",(t=>{if(this.destroyed)return;this.#ks.get(t.pageIndex)._startRenderPage(t.transparency,t.cacheKey)}));t.on("commonobj",(([e,i,s])=>{if(this.destroyed)return null;if(this.commonObjs.has(e))return null;switch(i){case"Font":if("error"in s){const t=s.error;warn(`Error during font loading: ${t}`);this.commonObjs.resolve(e,t);break}const n=this._params.pdfBug&&globalThis.FontInspector?.enabled?(t,e)=>globalThis.FontInspector.fontAdded(t,e):null,a=new FontFaceObject(s,n);this.fontLoader.bind(a).catch((()=>t.sendWithPromise("FontFallback",{id:e}))).finally((()=>{!a.fontExtraProperties&&a.data&&(a.data=null);this.commonObjs.resolve(e,a)}));break;case"CopyLocalImage":const{imageRef:r}=s;assert(r,"The imageRef must be defined.");for(const t of this.#ks.values())for(const[,i]of t.objs)if(i?.ref===r){if(!i.dataLen)return null;this.commonObjs.resolve(e,structuredClone(i));return i.dataLen}break;case"FontPath":case"Image":case"Pattern":this.commonObjs.resolve(e,s);break;default:throw new Error(`Got unknown common object type ${i}`)}return null}));t.on("obj",(([t,e,i,s])=>{if(this.destroyed)return;const n=this.#ks.get(e);if(!n.objs.has(t))if(0!==n._intentStates.size)switch(i){case"Image":case"Pattern":n.objs.resolve(t,s);break;default:throw new Error(`Got unknown object type ${i}`)}else s?.bitmap?.close()}));t.on("DocProgress",(t=>{this.destroyed||e.onProgress?.({loaded:t.loaded,total:t.total})}));t.on("FetchBinaryData",(async t=>{if(this.destroyed)throw new Error("Worker was destroyed.");const e=this[t.type];if(!e)throw new Error(`${t.type} not initialized, see the \`useWorkerFetch\` parameter.`);return e.fetch(t)}))}getData(){return this.messageHandler.sendWithPromise("GetData",null)}saveDocument(){this.annotationStorage.size<=0&&warn("saveDocument called while `annotationStorage` is empty, please use the getData-method instead.");const{map:t,transfer:e}=this.annotationStorage.serializable;return this.messageHandler.sendWithPromise("SaveDocument",{isPureXfa:!!this._htmlForXfa,numPages:this._numPages,annotationStorage:t,filename:this._fullReader?.filename??null},e).finally((()=>{this.annotationStorage.resetModified()}))}getPage(t){if(!Number.isInteger(t)||t<=0||t>this._numPages)return Promise.reject(new Error("Invalid page request."));const e=t-1,i=this.#Is.get(e);if(i)return i;const s=this.messageHandler.sendWithPromise("GetPage",{pageIndex:e}).then((i=>{if(this.destroyed)throw new Error("Transport destroyed");i.refStr&&this.#Rs.set(i.refStr,t);const s=new PDFPageProxy(e,i,this,this._params.pdfBug);this.#ks.set(e,s);return s}));this.#Is.set(e,s);return s}getPageIndex(t){return isRefProxy(t)?this.messageHandler.sendWithPromise("GetPageIndex",{num:t.num,gen:t.gen}):Promise.reject(new Error("Invalid pageIndex request."))}getAnnotations(t,e){return this.messageHandler.sendWithPromise("GetAnnotations",{pageIndex:t,intent:e})}getFieldObjects(){return this.#Ls("GetFieldObjects")}hasJSActions(){return this.#Ls("HasJSActions")}getCalculationOrderIds(){return this.messageHandler.sendWithPromise("GetCalculationOrderIds",null)}getDestinations(){return this.messageHandler.sendWithPromise("GetDestinations",null)}getDestination(t){return"string"!=typeof t?Promise.reject(new Error("Invalid destination request.")):this.messageHandler.sendWithPromise("GetDestination",{id:t})}getPageLabels(){return this.messageHandler.sendWithPromise("GetPageLabels",null)}getPageLayout(){return this.messageHandler.sendWithPromise("GetPageLayout",null)}getPageMode(){return this.messageHandler.sendWithPromise("GetPageMode",null)}getViewerPreferences(){return this.messageHandler.sendWithPromise("GetViewerPreferences",null)}getOpenAction(){return this.messageHandler.sendWithPromise("GetOpenAction",null)}getAttachments(){return this.messageHandler.sendWithPromise("GetAttachments",null)}getDocJSActions(){return this.#Ls("GetDocJSActions")}getPageJSActions(t){return this.messageHandler.sendWithPromise("GetPageJSActions",{pageIndex:t})}getStructTree(t){return this.messageHandler.sendWithPromise("GetStructTree",{pageIndex:t})}getOutline(){return this.messageHandler.sendWithPromise("GetOutline",null)}getOptionalContentConfig(t){return this.#Ls("GetOptionalContentConfig").then((e=>new OptionalContentConfig(e,t)))}getPermissions(){return this.messageHandler.sendWithPromise("GetPermissions",null)}getMetadata(){const t="GetMetadata",e=this.#Ps.get(t);if(e)return e;const i=this.messageHandler.sendWithPromise(t,null).then((t=>({info:t[0],metadata:t[1]?new Metadata(t[1]):null,contentDispositionFilename:this._fullReader?.filename??null,contentLength:this._fullReader?.contentLength??null})));this.#Ps.set(t,i);return i}getMarkInfo(){return this.messageHandler.sendWithPromise("GetMarkInfo",null)}async startCleanup(t=!1){if(!this.destroyed){await this.messageHandler.sendWithPromise("Cleanup",null);for(const t of this.#ks.values()){if(!t.cleanup())throw new Error(`startCleanup: Page ${t.pageNumber} is currently rendering.`)}this.commonObjs.clear();t||this.fontLoader.clear();this.#Ps.clear();this.filterFactory.destroy(!0);TextLayer.cleanup()}}cachedPageNumber(t){if(!isRefProxy(t))return null;const e=0===t.gen?`${t.num}R`:`${t.num}R${t.gen}`;return this.#Rs.get(e)??null}}const wt=Symbol("INITIAL_DATA");class PDFObjects{#Os=Object.create(null);#Ns(t){return this.#Os[t]||={...Promise.withResolvers(),data:wt}}get(t,e=null){if(e){const i=this.#Ns(t);i.promise.then((()=>e(i.data)));return null}const i=this.#Os[t];if(!i||i.data===wt)throw new Error(`Requesting object that isn't resolved yet ${t}.`);return i.data}has(t){const e=this.#Os[t];return!!e&&e.data!==wt}delete(t){const e=this.#Os[t];if(!e||e.data===wt)return!1;delete this.#Os[t];return!0}resolve(t,e=null){const i=this.#Ns(t);i.data=e;i.resolve()}clear(){for(const t in this.#Os){const{data:e}=this.#Os[t];e?.bitmap?.close()}this.#Os=Object.create(null)}*[Symbol.iterator](){for(const t in this.#Os){const{data:e}=this.#Os[t];e!==wt&&(yield[t,e])}}}class RenderTask{#Bs=null;onContinue=null;onError=null;constructor(t){this.#Bs=t}get promise(){return this.#Bs.capability.promise}cancel(t=0){this.#Bs.cancel(null,t)}get separateAnnots(){const{separateAnnots:t}=this.#Bs.operatorList;if(!t)return!1;const{annotationCanvasMap:e}=this.#Bs;return t.form||t.canvas&&e?.size>0}}class InternalRenderTask{#Hs=null;static#Us=new WeakSet;constructor({callback:t,params:e,objs:i,commonObjs:s,annotationCanvasMap:n,operatorList:a,pageIndex:r,canvasFactory:o,filterFactory:l,useRequestAnimationFrame:h=!1,pdfBug:d=!1,pageColors:c=null}){this.callback=t;this.params=e;this.objs=i;this.commonObjs=s;this.annotationCanvasMap=n;this.operatorListIdx=null;this.operatorList=a;this._pageIndex=r;this.canvasFactory=o;this.filterFactory=l;this._pdfBug=d;this.pageColors=c;this.running=!1;this.graphicsReadyCallback=null;this.graphicsReady=!1;this._useRequestAnimationFrame=!0===h&&"undefined"!=typeof window;this.cancelled=!1;this.capability=Promise.withResolvers();this.task=new RenderTask(this);this._cancelBound=this.cancel.bind(this);this._continueBound=this._continue.bind(this);this._scheduleNextBound=this._scheduleNext.bind(this);this._nextBound=this._next.bind(this);this._canvas=e.canvasContext.canvas}get completed(){return this.capability.promise.catch((function(){}))}initializeGraphics({transparency:t=!1,optionalContentConfig:e}){if(this.cancelled)return;if(this._canvas){if(InternalRenderTask.#Us.has(this._canvas))throw new Error("Cannot use the same canvas during multiple render() operations. Use different canvas or ensure previous operations were cancelled or completed.");InternalRenderTask.#Us.add(this._canvas)}if(this._pdfBug&&globalThis.StepperManager?.enabled){this.stepper=globalThis.StepperManager.create(this._pageIndex);this.stepper.init(this.operatorList);this.stepper.nextBreakPoint=this.stepper.getNextBreakPoint()}const{canvasContext:i,viewport:s,transform:n,background:a}=this.params;this.gfx=new CanvasGraphics(i,this.commonObjs,this.objs,this.canvasFactory,this.filterFactory,{optionalContentConfig:e},this.annotationCanvasMap,this.pageColors);this.gfx.beginDrawing({transform:n,viewport:s,transparency:t,background:a});this.operatorListIdx=0;this.graphicsReady=!0;this.graphicsReadyCallback?.()}cancel(t=null,e=0){this.running=!1;this.cancelled=!0;this.gfx?.endDrawing();if(this.#Hs){window.cancelAnimationFrame(this.#Hs);this.#Hs=null}InternalRenderTask.#Us.delete(this._canvas);t||=new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex+1}`,e);this.callback(t);this.task.onError?.(t)}operatorListChanged(){if(this.graphicsReady){this.stepper?.updateOperatorList(this.operatorList);this.running||this._continue()}else this.graphicsReadyCallback||=this._continueBound}_continue(){this.running=!0;this.cancelled||(this.task.onContinue?this.task.onContinue(this._scheduleNextBound):this._scheduleNext())}_scheduleNext(){this._useRequestAnimationFrame?this.#Hs=window.requestAnimationFrame((()=>{this.#Hs=null;this._nextBound().catch(this._cancelBound)})):Promise.resolve().then(this._nextBound).catch(this._cancelBound)}async _next(){if(!this.cancelled){this.operatorListIdx=this.gfx.executeOperatorList(this.operatorList,this.operatorListIdx,this._continueBound,this.stepper);if(this.operatorListIdx===this.operatorList.argsArray.length){this.running=!1;if(this.operatorList.lastChunk){this.gfx.endDrawing();InternalRenderTask.#Us.delete(this._canvas);this.callback()}}}}}const yt="5.2.133",vt="4f7761353";function makeColorComp(t){return Math.floor(255*Math.max(0,Math.min(1,t))).toString(16).padStart(2,"0")}function scaleAndClamp(t){return Math.max(0,Math.min(255,255*t))}class ColorConverters{static CMYK_G([t,e,i,s]){return["G",1-Math.min(1,.3*t+.59*i+.11*e+s)]}static G_CMYK([t]){return["CMYK",0,0,0,1-t]}static G_RGB([t]){return["RGB",t,t,t]}static G_rgb([t]){return[t=scaleAndClamp(t),t,t]}static G_HTML([t]){const e=makeColorComp(t);return`#${e}${e}${e}`}static RGB_G([t,e,i]){return["G",.3*t+.59*e+.11*i]}static RGB_rgb(t){return t.map(scaleAndClamp)}static RGB_HTML(t){return`#${t.map(makeColorComp).join("")}`}static T_HTML(){return"#00000000"}static T_rgb(){return[null]}static CMYK_RGB([t,e,i,s]){return["RGB",1-Math.min(1,t+s),1-Math.min(1,i+s),1-Math.min(1,e+s)]}static CMYK_rgb([t,e,i,s]){return[scaleAndClamp(1-Math.min(1,t+s)),scaleAndClamp(1-Math.min(1,i+s)),scaleAndClamp(1-Math.min(1,e+s))]}static CMYK_HTML(t){const e=this.CMYK_RGB(t).slice(1);return this.RGB_HTML(e)}static RGB_CMYK([t,e,i]){const s=1-t,n=1-e,a=1-i;return["CMYK",s,n,a,Math.min(s,n,a)]}}class BaseSVGFactory{create(t,e,i=!1){if(t<=0||e<=0)throw new Error("Invalid SVG dimensions");const s=this._createSVG("svg:svg");s.setAttribute("version","1.1");if(!i){s.setAttribute("width",`${t}px`);s.setAttribute("height",`${e}px`)}s.setAttribute("preserveAspectRatio","none");s.setAttribute("viewBox",`0 0 ${t} ${e}`);return s}createElement(t){if("string"!=typeof t)throw new Error("Invalid SVG element type");return this._createSVG(t)}_createSVG(t){unreachable("Abstract method `_createSVG` called.")}}class DOMSVGFactory extends BaseSVGFactory{_createSVG(t){return document.createElementNS(z,t)}}class XfaLayer{static setupStorage(t,e,i,s,n){const a=s.getValue(e,{value:null});switch(i.name){case"textarea":null!==a.value&&(t.textContent=a.value);if("print"===n)break;t.addEventListener("input",(t=>{s.setValue(e,{value:t.target.value})}));break;case"input":if("radio"===i.attributes.type||"checkbox"===i.attributes.type){a.value===i.attributes.xfaOn?t.setAttribute("checked",!0):a.value===i.attributes.xfaOff&&t.removeAttribute("checked");if("print"===n)break;t.addEventListener("change",(t=>{s.setValue(e,{value:t.target.checked?t.target.getAttribute("xfaOn"):t.target.getAttribute("xfaOff")})}))}else{null!==a.value&&t.setAttribute("value",a.value);if("print"===n)break;t.addEventListener("input",(t=>{s.setValue(e,{value:t.target.value})}))}break;case"select":if(null!==a.value){t.setAttribute("value",a.value);for(const t of i.children)t.attributes.value===a.value?t.attributes.selected=!0:t.attributes.hasOwnProperty("selected")&&delete t.attributes.selected}t.addEventListener("input",(t=>{const i=t.target.options,n=-1===i.selectedIndex?"":i[i.selectedIndex].value;s.setValue(e,{value:n})}))}}static setAttributes({html:t,element:e,storage:i=null,intent:s,linkService:n}){const{attributes:a}=e,r=t instanceof HTMLAnchorElement;"radio"===a.type&&(a.name=`${a.name}-${s}`);for(const[e,i]of Object.entries(a))if(null!=i)switch(e){case"class":i.length&&t.setAttribute(e,i.join(" "));break;case"dataId":break;case"id":t.setAttribute("data-element-id",i);break;case"style":Object.assign(t.style,i);break;case"textContent":t.textContent=i;break;default:(!r||"href"!==e&&"newWindow"!==e)&&t.setAttribute(e,i)}r&&n.addLinkAttributes(t,a.href,a.newWindow);i&&a.dataId&&this.setupStorage(t,a.dataId,e,i)}static render(t){const e=t.annotationStorage,i=t.linkService,s=t.xfaHtml,n=t.intent||"display",a=document.createElement(s.name);s.attributes&&this.setAttributes({html:a,element:s,intent:n,linkService:i});const r="richText"!==n,o=t.div;o.append(a);if(t.viewport){const e=`matrix(${t.viewport.transform.join(",")})`;o.style.transform=e}r&&o.setAttribute("class","xfaLayer xfaFont");const l=[];if(0===s.children.length){if(s.value){const t=document.createTextNode(s.value);a.append(t);r&&XfaText.shouldBuildText(s.name)&&l.push(t)}return{textDivs:l}}const h=[[s,-1,a]];for(;h.length>0;){const[t,s,a]=h.at(-1);if(s+1===t.children.length){h.pop();continue}const o=t.children[++h.at(-1)[1]];if(null===o)continue;const{name:d}=o;if("#text"===d){const t=document.createTextNode(o.value);l.push(t);a.append(t);continue}const c=o?.attributes?.xmlns?document.createElementNS(o.attributes.xmlns,d):document.createElement(d);a.append(c);o.attributes&&this.setAttributes({html:c,element:o,storage:e,intent:n,linkService:i});if(o.children?.length>0)h.push([o,-1,c]);else if(o.value){const t=document.createTextNode(o.value);r&&XfaText.shouldBuildText(d)&&l.push(t);c.append(t)}}for(const t of o.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea"))t.setAttribute("readOnly",!0);return{textDivs:l}}static update(t){const e=`matrix(${t.viewport.transform.join(",")})`;t.div.style.transform=e;t.div.hidden=!1}}const _t=1e3,xt=new WeakSet;class AnnotationElementFactory{static create(t){switch(t.data.annotationType){case _.LINK:return new LinkAnnotationElement(t);case _.TEXT:return new TextAnnotationElement(t);case _.WIDGET:switch(t.data.fieldType){case"Tx":return new TextWidgetAnnotationElement(t);case"Btn":return t.data.radioButton?new RadioButtonWidgetAnnotationElement(t):t.data.checkBox?new CheckboxWidgetAnnotationElement(t):new PushButtonWidgetAnnotationElement(t);case"Ch":return new ChoiceWidgetAnnotationElement(t);case"Sig":return new SignatureWidgetAnnotationElement(t)}return new WidgetAnnotationElement(t);case _.POPUP:return new PopupAnnotationElement(t);case _.FREETEXT:return new FreeTextAnnotationElement(t);case _.LINE:return new LineAnnotationElement(t);case _.SQUARE:return new SquareAnnotationElement(t);case _.CIRCLE:return new CircleAnnotationElement(t);case _.POLYLINE:return new PolylineAnnotationElement(t);case _.CARET:return new CaretAnnotationElement(t);case _.INK:return new InkAnnotationElement(t);case _.POLYGON:return new PolygonAnnotationElement(t);case _.HIGHLIGHT:return new HighlightAnnotationElement(t);case _.UNDERLINE:return new UnderlineAnnotationElement(t);case _.SQUIGGLY:return new SquigglyAnnotationElement(t);case _.STRIKEOUT:return new StrikeOutAnnotationElement(t);case _.STAMP:return new StampAnnotationElement(t);case _.FILEATTACHMENT:return new FileAttachmentAnnotationElement(t);default:return new AnnotationElement(t)}}}class AnnotationElement{#zs=null;#Gs=!1;#$s=null;constructor(t,{isRenderable:e=!1,ignoreBorder:i=!1,createQuadrilaterals:s=!1}={}){this.isRenderable=e;this.data=t.data;this.layer=t.layer;this.linkService=t.linkService;this.downloadManager=t.downloadManager;this.imageResourcesPath=t.imageResourcesPath;this.renderForms=t.renderForms;this.svgFactory=t.svgFactory;this.annotationStorage=t.annotationStorage;this.enableScripting=t.enableScripting;this.hasJSActions=t.hasJSActions;this._fieldObjects=t.fieldObjects;this.parent=t.parent;e&&(this.container=this._createContainer(i));s&&this._createQuadrilaterals()}static _hasPopupData({titleObj:t,contentsObj:e,richText:i}){return!!(t?.str||e?.str||i?.str)}get _isEditable(){return this.data.isEditable}get hasPopupData(){return AnnotationElement._hasPopupData(this.data)}updateEdited(t){if(!this.container)return;this.#zs||={rect:this.data.rect.slice(0)};const{rect:e}=t;e&&this.#Ws(e);this.#$s?.popup.updateEdited(t)}resetEdited(){if(this.#zs){this.#Ws(this.#zs.rect);this.#$s?.popup.resetEdited();this.#zs=null}}#Ws(t){const{container:{style:e},data:{rect:i,rotation:s},parent:{viewport:{rawDims:{pageWidth:n,pageHeight:a,pageX:r,pageY:o}}}}=this;i?.splice(0,4,...t);e.left=100*(t[0]-r)/n+"%";e.top=100*(a-t[3]+o)/a+"%";if(0===s){e.width=100*(t[2]-t[0])/n+"%";e.height=100*(t[3]-t[1])/a+"%"}else this.setRotation(s)}_createContainer(t){const{data:e,parent:{page:i,viewport:s}}=this,n=document.createElement("section");n.setAttribute("data-annotation-id",e.id);this instanceof WidgetAnnotationElement||(n.tabIndex=_t);const{style:a}=n;a.zIndex=this.parent.zIndex++;e.alternativeText&&(n.title=e.alternativeText);e.noRotate&&n.classList.add("norotate");if(!e.rect||this instanceof PopupAnnotationElement){const{rotation:t}=e;e.hasOwnCanvas||0===t||this.setRotation(t,n);return n}const{width:r,height:o}=this;if(!t&&e.borderStyle.width>0){a.borderWidth=`${e.borderStyle.width}px`;const t=e.borderStyle.horizontalCornerRadius,i=e.borderStyle.verticalCornerRadius;if(t>0||i>0){const e=`calc(${t}px * var(--total-scale-factor)) / calc(${i}px * var(--total-scale-factor))`;a.borderRadius=e}else if(this instanceof RadioButtonWidgetAnnotationElement){const t=`calc(${r}px * var(--total-scale-factor)) / calc(${o}px * var(--total-scale-factor))`;a.borderRadius=t}switch(e.borderStyle.style){case x:a.borderStyle="solid";break;case E:a.borderStyle="dashed";break;case S:warn("Unimplemented border style: beveled");break;case C:warn("Unimplemented border style: inset");break;case T:a.borderBottomStyle="solid"}const s=e.borderColor||null;if(s){this.#Gs=!0;a.borderColor=Util.makeHexColor(0|s[0],0|s[1],0|s[2])}else a.borderWidth=0}const l=Util.normalizeRect([e.rect[0],i.view[3]-e.rect[1]+i.view[1],e.rect[2],i.view[3]-e.rect[3]+i.view[1]]),{pageWidth:h,pageHeight:d,pageX:c,pageY:u}=s.rawDims;a.left=100*(l[0]-c)/h+"%";a.top=100*(l[1]-u)/d+"%";const{rotation:p}=e;if(e.hasOwnCanvas||0===p){a.width=100*r/h+"%";a.height=100*o/d+"%"}else this.setRotation(p,n);return n}setRotation(t,e=this.container){if(!this.data.rect)return;const{pageWidth:i,pageHeight:s}=this.parent.viewport.rawDims;let{width:n,height:a}=this;t%180!=0&&([n,a]=[a,n]);e.style.width=100*n/i+"%";e.style.height=100*a/s+"%";e.setAttribute("data-main-rotation",(360-t)%360)}get _commonActions(){const setColor=(t,e,i)=>{const s=i.detail[t],n=s[0],a=s.slice(1);i.target.style[e]=ColorConverters[`${n}_HTML`](a);this.annotationStorage.setValue(this.data.id,{[e]:ColorConverters[`${n}_rgb`](a)})};return shadow(this,"_commonActions",{display:t=>{const{display:e}=t.detail,i=e%2==1;this.container.style.visibility=i?"hidden":"visible";this.annotationStorage.setValue(this.data.id,{noView:i,noPrint:1===e||2===e})},print:t=>{this.annotationStorage.setValue(this.data.id,{noPrint:!t.detail.print})},hidden:t=>{const{hidden:e}=t.detail;this.container.style.visibility=e?"hidden":"visible";this.annotationStorage.setValue(this.data.id,{noPrint:e,noView:e})},focus:t=>{setTimeout((()=>t.target.focus({preventScroll:!1})),0)},userName:t=>{t.target.title=t.detail.userName},readonly:t=>{t.target.disabled=t.detail.readonly},required:t=>{this._setRequired(t.target,t.detail.required)},bgColor:t=>{setColor("bgColor","backgroundColor",t)},fillColor:t=>{setColor("fillColor","backgroundColor",t)},fgColor:t=>{setColor("fgColor","color",t)},textColor:t=>{setColor("textColor","color",t)},borderColor:t=>{setColor("borderColor","borderColor",t)},strokeColor:t=>{setColor("strokeColor","borderColor",t)},rotation:t=>{const e=t.detail.rotation;this.setRotation(e);this.annotationStorage.setValue(this.data.id,{rotation:e})}})}_dispatchEventFromSandbox(t,e){const i=this._commonActions;for(const s of Object.keys(e.detail)){const n=t[s]||i[s];n?.(e)}}_setDefaultPropertiesFromJS(t){if(!this.enableScripting)return;const e=this.annotationStorage.getRawValue(this.data.id);if(!e)return;const i=this._commonActions;for(const[s,n]of Object.entries(e)){const a=i[s];if(a){a({detail:{[s]:n},target:t});delete e[s]}}}_createQuadrilaterals(){if(!this.container)return;const{quadPoints:t}=this.data;if(!t)return;const[e,i,s,n]=this.data.rect.map((t=>Math.fround(t)));if(8===t.length){const[a,r,o,l]=t.subarray(2,6);if(s===a&&n===r&&e===o&&i===l)return}const{style:a}=this.container;let r;if(this.#Gs){const{borderColor:t,borderWidth:e}=a;a.borderWidth=0;r=["url('data:image/svg+xml;utf8,",'',``];this.container.classList.add("hasBorder")}const o=s-e,l=n-i,{svgFactory:h}=this,d=h.createElement("svg");d.classList.add("quadrilateralsContainer");d.setAttribute("width",0);d.setAttribute("height",0);const c=h.createElement("defs");d.append(c);const u=h.createElement("clipPath"),p=`clippath_${this.data.id}`;u.setAttribute("id",p);u.setAttribute("clipPathUnits","objectBoundingBox");c.append(u);for(let i=2,s=t.length;i`)}if(this.#Gs){r.push("')");a.backgroundImage=r.join("")}this.container.append(d);this.container.style.clipPath=`url(#${p})`}_createPopup(){const{data:t}=this,e=this.#$s=new PopupAnnotationElement({data:{color:t.color,titleObj:t.titleObj,modificationDate:t.modificationDate,contentsObj:t.contentsObj,richText:t.richText,parentRect:t.rect,borderStyle:0,id:`popup_${t.id}`,rotation:t.rotation},parent:this.parent,elements:[this]});this.parent.div.append(e.render())}render(){unreachable("Abstract method `AnnotationElement.render` called")}_getElementsByName(t,e=null){const i=[];if(this._fieldObjects){const s=this._fieldObjects[t];if(s)for(const{page:t,id:n,exportValues:a}of s){if(-1===t)continue;if(n===e)continue;const s="string"==typeof a?a:null,r=document.querySelector(`[data-element-id="${n}"]`);!r||xt.has(r)?i.push({id:n,exportValue:s,domElement:r}):warn(`_getElementsByName - element not allowed: ${n}`)}return i}for(const s of document.getElementsByName(t)){const{exportValue:t}=s,n=s.getAttribute("data-element-id");n!==e&&(xt.has(s)&&i.push({id:n,exportValue:t,domElement:s}))}return i}show(){this.container&&(this.container.hidden=!1);this.popup?.maybeShow()}hide(){this.container&&(this.container.hidden=!0);this.popup?.forceHide()}getElementsToTriggerPopup(){return this.container}addHighlightArea(){const t=this.getElementsToTriggerPopup();if(Array.isArray(t))for(const e of t)e.classList.add("highlightArea");else t.classList.add("highlightArea")}_editOnDoubleClick(){if(!this._isEditable)return;const{annotationEditorType:t,data:{id:e}}=this;this.container.addEventListener("dblclick",(()=>{this.linkService.eventBus?.dispatch("switchannotationeditormode",{source:this,mode:t,editId:e})}))}get width(){return this.data.rect[2]-this.data.rect[0]}get height(){return this.data.rect[3]-this.data.rect[1]}}class LinkAnnotationElement extends AnnotationElement{constructor(t,e=null){super(t,{isRenderable:!0,ignoreBorder:!!e?.ignoreBorder,createQuadrilaterals:!0});this.isTooltipOnly=t.data.isTooltipOnly}render(){const{data:t,linkService:e}=this,i=document.createElement("a");i.setAttribute("data-element-id",t.id);let s=!1;if(t.url){e.addLinkAttributes(i,t.url,t.newWindow);s=!0}else if(t.action){this._bindNamedAction(i,t.action);s=!0}else if(t.attachment){this.#Vs(i,t.attachment,t.attachmentDest);s=!0}else if(t.setOCGState){this.#js(i,t.setOCGState);s=!0}else if(t.dest){this._bindLink(i,t.dest);s=!0}else{if(t.actions&&(t.actions.Action||t.actions["Mouse Up"]||t.actions["Mouse Down"])&&this.enableScripting&&this.hasJSActions){this._bindJSAction(i,t);s=!0}if(t.resetForm){this._bindResetFormAction(i,t.resetForm);s=!0}else if(this.isTooltipOnly&&!s){this._bindLink(i,"");s=!0}}this.container.classList.add("linkAnnotation");s&&this.container.append(i);return this.container}#qs(){this.container.setAttribute("data-internal-link","")}_bindLink(t,e){t.href=this.linkService.getDestinationHash(e);t.onclick=()=>{e&&this.linkService.goToDestination(e);return!1};(e||""===e)&&this.#qs()}_bindNamedAction(t,e){t.href=this.linkService.getAnchorUrl("");t.onclick=()=>{this.linkService.executeNamedAction(e);return!1};this.#qs()}#Vs(t,e,i=null){t.href=this.linkService.getAnchorUrl("");e.description&&(t.title=e.description);t.onclick=()=>{this.downloadManager?.openOrDownloadData(e.content,e.filename,i);return!1};this.#qs()}#js(t,e){t.href=this.linkService.getAnchorUrl("");t.onclick=()=>{this.linkService.executeSetOCGState(e);return!1};this.#qs()}_bindJSAction(t,e){t.href=this.linkService.getAnchorUrl("");const i=new Map([["Action","onclick"],["Mouse Up","onmouseup"],["Mouse Down","onmousedown"]]);for(const s of Object.keys(e.actions)){const n=i.get(s);n&&(t[n]=()=>{this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e.id,name:s}});return!1})}t.onclick||(t.onclick=()=>!1);this.#qs()}_bindResetFormAction(t,e){const i=t.onclick;i||(t.href=this.linkService.getAnchorUrl(""));this.#qs();if(this._fieldObjects)t.onclick=()=>{i?.();const{fields:t,refs:s,include:n}=e,a=[];if(0!==t.length||0!==s.length){const e=new Set(s);for(const i of t){const t=this._fieldObjects[i]||[];for(const{id:i}of t)e.add(i)}for(const t of Object.values(this._fieldObjects))for(const i of t)e.has(i.id)===n&&a.push(i)}else for(const t of Object.values(this._fieldObjects))a.push(...t);const r=this.annotationStorage,o=[];for(const t of a){const{id:e}=t;o.push(e);switch(t.type){case"text":{const i=t.defaultValue||"";r.setValue(e,{value:i});break}case"checkbox":case"radiobutton":{const i=t.defaultValue===t.exportValues;r.setValue(e,{value:i});break}case"combobox":case"listbox":{const i=t.defaultValue||"";r.setValue(e,{value:i});break}default:continue}const i=document.querySelector(`[data-element-id="${e}"]`);i&&(xt.has(i)?i.dispatchEvent(new Event("resetform")):warn(`_bindResetFormAction - element not allowed: ${e}`))}this.enableScripting&&this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:"app",ids:o,name:"ResetForm"}});return!1};else{warn('_bindResetFormAction - "resetForm" action not supported, ensure that the `fieldObjects` parameter is provided.');i||(t.onclick=()=>!1)}}}class TextAnnotationElement extends AnnotationElement{constructor(t){super(t,{isRenderable:!0})}render(){this.container.classList.add("textAnnotation");const t=document.createElement("img");t.src=this.imageResourcesPath+"annotation-"+this.data.name.toLowerCase()+".svg";t.setAttribute("data-l10n-id","pdfjs-text-annotation-type");t.setAttribute("data-l10n-args",JSON.stringify({type:this.data.name}));!this.data.popupRef&&this.hasPopupData&&this._createPopup();this.container.append(t);return this.container}}class WidgetAnnotationElement extends AnnotationElement{render(){return this.container}showElementAndHideCanvas(t){if(this.data.hasOwnCanvas){"CANVAS"===t.previousSibling?.nodeName&&(t.previousSibling.hidden=!0);t.hidden=!1}}_getKeyModifier(t){return util_FeatureTest.platform.isMac?t.metaKey:t.ctrlKey}_setEventListener(t,e,i,s,n){i.includes("mouse")?t.addEventListener(i,(t=>{this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:this.data.id,name:s,value:n(t),shift:t.shiftKey,modifier:this._getKeyModifier(t)}})})):t.addEventListener(i,(t=>{if("blur"===i){if(!e.focused||!t.relatedTarget)return;e.focused=!1}else if("focus"===i){if(e.focused)return;e.focused=!0}n&&this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:this.data.id,name:s,value:n(t)}})}))}_setEventListeners(t,e,i,s){for(const[n,a]of i)if("Action"===a||this.data.actions?.[a]){"Focus"!==a&&"Blur"!==a||(e||={focused:!1});this._setEventListener(t,e,n,a,s);"Focus"!==a||this.data.actions?.Blur?"Blur"!==a||this.data.actions?.Focus||this._setEventListener(t,e,"focus","Focus",null):this._setEventListener(t,e,"blur","Blur",null)}}_setBackgroundColor(t){const e=this.data.backgroundColor||null;t.style.backgroundColor=null===e?"transparent":Util.makeHexColor(e[0],e[1],e[2])}_setTextStyle(t){const e=["left","center","right"],{fontColor:s}=this.data.defaultAppearanceData,n=this.data.defaultAppearanceData.fontSize||9,a=t.style;let r;const roundToOneDecimal=t=>Math.round(10*t)/10;if(this.data.multiLine){const t=Math.abs(this.data.rect[3]-this.data.rect[1]-2),e=t/(Math.round(t/(i*n))||1);r=Math.min(n,roundToOneDecimal(e/i))}else{const t=Math.abs(this.data.rect[3]-this.data.rect[1]-2);r=Math.min(n,roundToOneDecimal(t/i))}a.fontSize=`calc(${r}px * var(--total-scale-factor))`;a.color=Util.makeHexColor(s[0],s[1],s[2]);null!==this.data.textAlignment&&(a.textAlign=e[this.data.textAlignment])}_setRequired(t,e){e?t.setAttribute("required",!0):t.removeAttribute("required");t.setAttribute("aria-required",e)}}class TextWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms||t.data.hasOwnCanvas||!t.data.hasAppearance&&!!t.data.fieldValue})}setPropertyOnSiblings(t,e,i,s){const n=this.annotationStorage;for(const a of this._getElementsByName(t.name,t.id)){a.domElement&&(a.domElement[e]=i);n.setValue(a.id,{[s]:i})}}render(){const t=this.annotationStorage,e=this.data.id;this.container.classList.add("textWidgetAnnotation");let i=null;if(this.renderForms){const s=t.getValue(e,{value:this.data.fieldValue});let n=s.value||"";const a=t.getValue(e,{charLimit:this.data.maxLen}).charLimit;a&&n.length>a&&(n=n.slice(0,a));let r=s.formattedValue||this.data.textContent?.join("\n")||null;r&&this.data.comb&&(r=r.replaceAll(/\s+/g,""));const o={userValue:n,formattedValue:r,lastCommittedValue:null,commitKey:1,focused:!1};if(this.data.multiLine){i=document.createElement("textarea");i.textContent=r??n;this.data.doNotScroll&&(i.style.overflowY="hidden")}else{i=document.createElement("input");i.type=this.data.password?"password":"text";i.setAttribute("value",r??n);this.data.doNotScroll&&(i.style.overflowX="hidden")}this.data.hasOwnCanvas&&(i.hidden=!0);xt.add(i);i.setAttribute("data-element-id",e);i.disabled=this.data.readOnly;i.name=this.data.fieldName;i.tabIndex=_t;this._setRequired(i,this.data.required);a&&(i.maxLength=a);i.addEventListener("input",(s=>{t.setValue(e,{value:s.target.value});this.setPropertyOnSiblings(i,"value",s.target.value,"value");o.formattedValue=null}));i.addEventListener("resetform",(t=>{const e=this.data.defaultFieldValue??"";i.value=o.userValue=e;o.formattedValue=null}));let blurListener=t=>{const{formattedValue:e}=o;null!=e&&(t.target.value=e);t.target.scrollLeft=0};if(this.enableScripting&&this.hasJSActions){i.addEventListener("focus",(t=>{if(o.focused)return;const{target:e}=t;o.userValue&&(e.value=o.userValue);o.lastCommittedValue=e.value;o.commitKey=1;this.data.actions?.Focus||(o.focused=!0)}));i.addEventListener("updatefromsandbox",(i=>{this.showElementAndHideCanvas(i.target);const s={value(i){o.userValue=i.detail.value??"";t.setValue(e,{value:o.userValue.toString()});i.target.value=o.userValue},formattedValue(i){const{formattedValue:s}=i.detail;o.formattedValue=s;null!=s&&i.target!==document.activeElement&&(i.target.value=s);t.setValue(e,{formattedValue:s})},selRange(t){t.target.setSelectionRange(...t.detail.selRange)},charLimit:i=>{const{charLimit:s}=i.detail,{target:n}=i;if(0===s){n.removeAttribute("maxLength");return}n.setAttribute("maxLength",s);let a=o.userValue;if(a&&!(a.length<=s)){a=a.slice(0,s);n.value=o.userValue=a;t.setValue(e,{value:a});this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:a,willCommit:!0,commitKey:1,selStart:n.selectionStart,selEnd:n.selectionEnd}})}}};this._dispatchEventFromSandbox(s,i)}));i.addEventListener("keydown",(t=>{o.commitKey=1;let i=-1;"Escape"===t.key?i=0:"Enter"!==t.key||this.data.multiLine?"Tab"===t.key&&(o.commitKey=3):i=2;if(-1===i)return;const{value:s}=t.target;if(o.lastCommittedValue!==s){o.lastCommittedValue=s;o.userValue=s;this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:s,willCommit:!0,commitKey:i,selStart:t.target.selectionStart,selEnd:t.target.selectionEnd}})}}));const s=blurListener;blurListener=null;i.addEventListener("blur",(t=>{if(!o.focused||!t.relatedTarget)return;this.data.actions?.Blur||(o.focused=!1);const{value:i}=t.target;o.userValue=i;o.lastCommittedValue!==i&&this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:i,willCommit:!0,commitKey:o.commitKey,selStart:t.target.selectionStart,selEnd:t.target.selectionEnd}});s(t)}));this.data.actions?.Keystroke&&i.addEventListener("beforeinput",(t=>{o.lastCommittedValue=null;const{data:i,target:s}=t,{value:n,selectionStart:a,selectionEnd:r}=s;let l=a,h=r;switch(t.inputType){case"deleteWordBackward":{const t=n.substring(0,a).match(/\w*[^\w]*$/);t&&(l-=t[0].length);break}case"deleteWordForward":{const t=n.substring(a).match(/^[^\w]*\w*/);t&&(h+=t[0].length);break}case"deleteContentBackward":a===r&&(l-=1);break;case"deleteContentForward":a===r&&(h+=1)}t.preventDefault();this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:n,change:i||"",willCommit:!1,selStart:l,selEnd:h}})}));this._setEventListeners(i,o,[["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"]],(t=>t.target.value))}blurListener&&i.addEventListener("blur",blurListener);if(this.data.comb){const t=(this.data.rect[2]-this.data.rect[0])/a;i.classList.add("comb");i.style.letterSpacing=`calc(${t}px * var(--total-scale-factor) - 1ch)`}}else{i=document.createElement("div");i.textContent=this.data.fieldValue;i.style.verticalAlign="middle";i.style.display="table-cell";this.data.hasOwnCanvas&&(i.hidden=!0)}this._setTextStyle(i);this._setBackgroundColor(i);this._setDefaultPropertiesFromJS(i);this.container.append(i);return this.container}}class SignatureWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:!!t.data.hasOwnCanvas})}}class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms})}render(){const t=this.annotationStorage,e=this.data,i=e.id;let s=t.getValue(i,{value:e.exportValue===e.fieldValue}).value;if("string"==typeof s){s="Off"!==s;t.setValue(i,{value:s})}this.container.classList.add("buttonWidgetAnnotation","checkBox");const n=document.createElement("input");xt.add(n);n.setAttribute("data-element-id",i);n.disabled=e.readOnly;this._setRequired(n,this.data.required);n.type="checkbox";n.name=e.fieldName;s&&n.setAttribute("checked",!0);n.setAttribute("exportValue",e.exportValue);n.tabIndex=_t;n.addEventListener("change",(s=>{const{name:n,checked:a}=s.target;for(const s of this._getElementsByName(n,i)){const i=a&&s.exportValue===e.exportValue;s.domElement&&(s.domElement.checked=i);t.setValue(s.id,{value:i})}t.setValue(i,{value:a})}));n.addEventListener("resetform",(t=>{const i=e.defaultFieldValue||"Off";t.target.checked=i===e.exportValue}));if(this.enableScripting&&this.hasJSActions){n.addEventListener("updatefromsandbox",(e=>{const s={value(e){e.target.checked="Off"!==e.detail.value;t.setValue(i,{value:e.target.checked})}};this._dispatchEventFromSandbox(s,e)}));this._setEventListeners(n,null,[["change","Validate"],["change","Action"],["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"]],(t=>t.target.checked))}this._setBackgroundColor(n);this._setDefaultPropertiesFromJS(n);this.container.append(n);return this.container}}class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms})}render(){this.container.classList.add("buttonWidgetAnnotation","radioButton");const t=this.annotationStorage,e=this.data,i=e.id;let s=t.getValue(i,{value:e.fieldValue===e.buttonValue}).value;if("string"==typeof s){s=s!==e.buttonValue;t.setValue(i,{value:s})}if(s)for(const s of this._getElementsByName(e.fieldName,i))t.setValue(s.id,{value:!1});const n=document.createElement("input");xt.add(n);n.setAttribute("data-element-id",i);n.disabled=e.readOnly;this._setRequired(n,this.data.required);n.type="radio";n.name=e.fieldName;s&&n.setAttribute("checked",!0);n.tabIndex=_t;n.addEventListener("change",(e=>{const{name:s,checked:n}=e.target;for(const e of this._getElementsByName(s,i))t.setValue(e.id,{value:!1});t.setValue(i,{value:n})}));n.addEventListener("resetform",(t=>{const i=e.defaultFieldValue;t.target.checked=null!=i&&i===e.buttonValue}));if(this.enableScripting&&this.hasJSActions){const s=e.buttonValue;n.addEventListener("updatefromsandbox",(e=>{const n={value:e=>{const n=s===e.detail.value;for(const s of this._getElementsByName(e.target.name)){const e=n&&s.id===i;s.domElement&&(s.domElement.checked=e);t.setValue(s.id,{value:e})}}};this._dispatchEventFromSandbox(n,e)}));this._setEventListeners(n,null,[["change","Validate"],["change","Action"],["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"]],(t=>t.target.checked))}this._setBackgroundColor(n);this._setDefaultPropertiesFromJS(n);this.container.append(n);return this.container}}class PushButtonWidgetAnnotationElement extends LinkAnnotationElement{constructor(t){super(t,{ignoreBorder:t.data.hasAppearance})}render(){const t=super.render();t.classList.add("buttonWidgetAnnotation","pushButton");const e=t.lastChild;if(this.enableScripting&&this.hasJSActions&&e){this._setDefaultPropertiesFromJS(e);e.addEventListener("updatefromsandbox",(t=>{this._dispatchEventFromSandbox({},t)}))}return t}}class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms})}render(){this.container.classList.add("choiceWidgetAnnotation");const t=this.annotationStorage,e=this.data.id,i=t.getValue(e,{value:this.data.fieldValue}),s=document.createElement("select");xt.add(s);s.setAttribute("data-element-id",e);s.disabled=this.data.readOnly;this._setRequired(s,this.data.required);s.name=this.data.fieldName;s.tabIndex=_t;let n=this.data.combo&&this.data.options.length>0;if(!this.data.combo){s.size=this.data.options.length;this.data.multiSelect&&(s.multiple=!0)}s.addEventListener("resetform",(t=>{const e=this.data.defaultFieldValue;for(const t of s.options)t.selected=t.value===e}));for(const t of this.data.options){const e=document.createElement("option");e.textContent=t.displayValue;e.value=t.exportValue;if(i.value.includes(t.exportValue)){e.setAttribute("selected",!0);n=!1}s.append(e)}let a=null;if(n){const t=document.createElement("option");t.value=" ";t.setAttribute("hidden",!0);t.setAttribute("selected",!0);s.prepend(t);a=()=>{t.remove();s.removeEventListener("input",a);a=null};s.addEventListener("input",a)}const getValue=t=>{const e=t?"value":"textContent",{options:i,multiple:n}=s;return n?Array.prototype.filter.call(i,(t=>t.selected)).map((t=>t[e])):-1===i.selectedIndex?null:i[i.selectedIndex][e]};let r=getValue(!1);const getItems=t=>{const e=t.target.options;return Array.prototype.map.call(e,(t=>({displayValue:t.textContent,exportValue:t.value})))};if(this.enableScripting&&this.hasJSActions){s.addEventListener("updatefromsandbox",(i=>{const n={value(i){a?.();const n=i.detail.value,o=new Set(Array.isArray(n)?n:[n]);for(const t of s.options)t.selected=o.has(t.value);t.setValue(e,{value:getValue(!0)});r=getValue(!1)},multipleSelection(t){s.multiple=!0},remove(i){const n=s.options,a=i.detail.remove;n[a].selected=!1;s.remove(a);if(n.length>0){-1===Array.prototype.findIndex.call(n,(t=>t.selected))&&(n[0].selected=!0)}t.setValue(e,{value:getValue(!0),items:getItems(i)});r=getValue(!1)},clear(i){for(;0!==s.length;)s.remove(0);t.setValue(e,{value:null,items:[]});r=getValue(!1)},insert(i){const{index:n,displayValue:a,exportValue:o}=i.detail.insert,l=s.children[n],h=document.createElement("option");h.textContent=a;h.value=o;l?l.before(h):s.append(h);t.setValue(e,{value:getValue(!0),items:getItems(i)});r=getValue(!1)},items(i){const{items:n}=i.detail;for(;0!==s.length;)s.remove(0);for(const t of n){const{displayValue:e,exportValue:i}=t,n=document.createElement("option");n.textContent=e;n.value=i;s.append(n)}s.options.length>0&&(s.options[0].selected=!0);t.setValue(e,{value:getValue(!0),items:getItems(i)});r=getValue(!1)},indices(i){const s=new Set(i.detail.indices);for(const t of i.target.options)t.selected=s.has(t.index);t.setValue(e,{value:getValue(!0)});r=getValue(!1)},editable(t){t.target.disabled=!t.detail.editable}};this._dispatchEventFromSandbox(n,i)}));s.addEventListener("input",(i=>{const s=getValue(!0),n=getValue(!1);t.setValue(e,{value:s});i.preventDefault();this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:r,change:n,changeEx:s,willCommit:!1,commitKey:1,keyDown:!1}})}));this._setEventListeners(s,null,[["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"],["input","Action"],["input","Validate"]],(t=>t.target.value))}else s.addEventListener("input",(function(i){t.setValue(e,{value:getValue(!0)})}));this.data.combo&&this._setTextStyle(s);this._setBackgroundColor(s);this._setDefaultPropertiesFromJS(s);this.container.append(s);return this.container}}class PopupAnnotationElement extends AnnotationElement{constructor(t){const{data:e,elements:i}=t;super(t,{isRenderable:AnnotationElement._hasPopupData(e)});this.elements=i;this.popup=null}render(){this.container.classList.add("popupAnnotation");const t=this.popup=new PopupElement({container:this.container,color:this.data.color,titleObj:this.data.titleObj,modificationDate:this.data.modificationDate,contentsObj:this.data.contentsObj,richText:this.data.richText,rect:this.data.rect,parentRect:this.data.parentRect||null,parent:this.parent,elements:this.elements,open:this.data.open}),e=[];for(const i of this.elements){i.popup=t;i.container.ariaHasPopup="dialog";e.push(i.data.id);i.addHighlightArea()}this.container.setAttribute("aria-controls",e.map((t=>`${U}${t}`)).join(","));return this.container}}class PopupElement{#Xs=this.#Ks.bind(this);#Ys=this.#Qs.bind(this);#Js=this.#Zs.bind(this);#tn=this.#en.bind(this);#in=null;#mt=null;#sn=null;#nn=null;#an=null;#rn=null;#on=null;#ln=!1;#hn=null;#T=null;#dn=null;#cn=null;#un=null;#zs=null;#pn=!1;constructor({container:t,color:e,elements:i,titleObj:s,modificationDate:n,contentsObj:a,richText:r,parent:o,rect:l,parentRect:h,open:d}){this.#mt=t;this.#un=s;this.#sn=a;this.#cn=r;this.#rn=o;this.#in=e;this.#dn=l;this.#on=h;this.#an=i;this.#nn=PDFDateString.toDateObject(n);this.trigger=i.flatMap((t=>t.getElementsToTriggerPopup()));for(const t of this.trigger){t.addEventListener("click",this.#tn);t.addEventListener("mouseenter",this.#Js);t.addEventListener("mouseleave",this.#Ys);t.classList.add("popupTriggerArea")}for(const t of i)t.container?.addEventListener("keydown",this.#Xs);this.#mt.hidden=!0;d&&this.#en()}render(){if(this.#hn)return;const t=this.#hn=document.createElement("div");t.className="popup";if(this.#in){const e=t.style.outlineColor=Util.makeHexColor(...this.#in);t.style.backgroundColor=`color-mix(in srgb, ${e} 30%, white)`}const e=document.createElement("span");e.className="header";const i=document.createElement("h1");e.append(i);({dir:i.dir,str:i.textContent}=this.#un);t.append(e);if(this.#nn){const t=document.createElement("span");t.classList.add("popupDate");t.setAttribute("data-l10n-id","pdfjs-annotation-date-time-string");t.setAttribute("data-l10n-args",JSON.stringify({dateObj:this.#nn.valueOf()}));e.append(t)}const s=this.#gn;if(s){XfaLayer.render({xfaHtml:s,intent:"richText",div:t});t.lastChild.classList.add("richText","popupContent")}else{const e=this._formatContents(this.#sn);t.append(e)}this.#mt.append(t)}get#gn(){const t=this.#cn,e=this.#sn;return!t?.str||e?.str&&e.str!==t.str?null:this.#cn.html||null}get#fn(){return this.#gn?.attributes?.style?.fontSize||0}get#mn(){return this.#gn?.attributes?.style?.color||null}#bn(t){const e=[],i={str:t,html:{name:"div",attributes:{dir:"auto"},children:[{name:"p",children:e}]}},s={style:{color:this.#mn,fontSize:this.#fn?`calc(${this.#fn}px * var(--total-scale-factor))`:""}};for(const i of t.split("\n"))e.push({name:"span",value:i,attributes:s});return i}_formatContents({str:t,dir:e}){const i=document.createElement("p");i.classList.add("popupContent");i.dir=e;const s=t.split(/(?:\r\n?|\n)/);for(let t=0,e=s.length;t=0&&n.setAttribute("stroke-width",e||1);if(i)for(let t=0,e=this.#En.length;t{"Enter"===t.key&&(s?t.metaKey:t.ctrlKey)&&this.#Tn()}));!e.popupRef&&this.hasPopupData?this._createPopup():i.classList.add("popupTriggerArea");t.append(i);return t}getElementsToTriggerPopup(){return this.#Cn}addHighlightArea(){this.container.classList.add("highlightArea")}#Tn(){this.downloadManager?.openOrDownloadData(this.content,this.filename)}}class AnnotationLayer{#Mn=null;#Dn=null;#Pn=new Map;#kn=null;constructor({div:t,accessibilityManager:e,annotationCanvasMap:i,annotationEditorUIManager:s,page:n,viewport:a,structTreeLayer:r}){this.div=t;this.#Mn=e;this.#Dn=i;this.#kn=r||null;this.page=n;this.viewport=a;this.zIndex=0;this._annotationEditorUIManager=s}hasEditableAnnotations(){return this.#Pn.size>0}async#In(t,e){const i=t.firstChild||t,s=i.id=`${U}${e}`,n=await(this.#kn?.getAriaAttributes(s));if(n)for(const[t,e]of n)i.setAttribute(t,e);this.div.append(t);this.#Mn?.moveElementInDOM(this.div,t,i,!1)}async render(t){const{annotations:e}=t,i=this.div;setLayerDimensions(i,this.viewport);const s=new Map,n={data:null,layer:i,linkService:t.linkService,downloadManager:t.downloadManager,imageResourcesPath:t.imageResourcesPath||"",renderForms:!1!==t.renderForms,svgFactory:new DOMSVGFactory,annotationStorage:t.annotationStorage||new AnnotationStorage,enableScripting:!0===t.enableScripting,hasJSActions:t.hasJSActions,fieldObjects:t.fieldObjects,parent:this,elements:null};for(const t of e){if(t.noHTML)continue;const e=t.annotationType===_.POPUP;if(e){const e=s.get(t.id);if(!e)continue;n.elements=e}else if(t.rect[2]===t.rect[0]||t.rect[3]===t.rect[1])continue;n.data=t;const i=AnnotationElementFactory.create(n);if(!i.isRenderable)continue;if(!e&&t.popupRef){const e=s.get(t.popupRef);e?e.push(i):s.set(t.popupRef,[i])}const a=i.render();t.hidden&&(a.style.visibility="hidden");await this.#In(a,t.id);if(i._isEditable){this.#Pn.set(i.data.id,i);this._annotationEditorUIManager?.renderAnnotationElement(i)}}this.#Rn()}async addLinkAnnotations(t,e){const i={data:null,layer:this.div,linkService:e,svgFactory:new DOMSVGFactory,parent:this};for(const e of t){e.borderStyle||=AnnotationLayer._defaultBorderStyle;i.data=e;const t=AnnotationElementFactory.create(i);if(!t.isRenderable)continue;const s=t.render();await this.#In(s,e.id)}}update({viewport:t}){const e=this.div;this.viewport=t;setLayerDimensions(e,{rotation:t.rotation});this.#Rn();e.hidden=!1}#Rn(){if(!this.#Dn)return;const t=this.div;for(const[e,i]of this.#Dn){const s=t.querySelector(`[data-annotation-id="${e}"]`);if(!s)continue;i.className="annotationContent";const{firstChild:n}=s;n?"CANVAS"===n.nodeName?n.replaceWith(i):n.classList.contains("annotationContent")?n.after(i):n.before(i):s.append(i);const a=this.#Pn.get(e);if(a)if(a._hasNoCanvas){this._annotationEditorUIManager?.setMissingCanvas(e,s.id,i);a._hasNoCanvas=!1}else a.canvas=i}this.#Dn.clear()}getEditableAnnotations(){return Array.from(this.#Pn.values())}getEditableAnnotation(t){return this.#Pn.get(t)}static get _defaultBorderStyle(){return shadow(this,"_defaultBorderStyle",Object.freeze({width:1,rawWidth:1,style:x,dashArray:[3],horizontalCornerRadius:0,verticalCornerRadius:0}))}}const Et=/\r\n?|\n/g;class FreeTextEditor extends AnnotationEditor{#in;#Fn="";#Ln=`${this.id}-editor`;#On=null;#fn;static _freeTextDefaultContent="";static _internalPadding=0;static _defaultColor=null;static _defaultFontSize=10;static get _keyboardManager(){const t=FreeTextEditor.prototype,arrowChecker=t=>t.isEmpty(),e=AnnotationEditorUIManager.TRANSLATE_SMALL,i=AnnotationEditorUIManager.TRANSLATE_BIG;return shadow(this,"_keyboardManager",new KeyboardManager([[["ctrl+s","mac+meta+s","ctrl+p","mac+meta+p"],t.commitOrRemove,{bubbles:!0}],[["ctrl+Enter","mac+meta+Enter","Escape","mac+Escape"],t.commitOrRemove],[["ArrowLeft","mac+ArrowLeft"],t._translateEmpty,{args:[-e,0],checker:arrowChecker}],[["ctrl+ArrowLeft","mac+shift+ArrowLeft"],t._translateEmpty,{args:[-i,0],checker:arrowChecker}],[["ArrowRight","mac+ArrowRight"],t._translateEmpty,{args:[e,0],checker:arrowChecker}],[["ctrl+ArrowRight","mac+shift+ArrowRight"],t._translateEmpty,{args:[i,0],checker:arrowChecker}],[["ArrowUp","mac+ArrowUp"],t._translateEmpty,{args:[0,-e],checker:arrowChecker}],[["ctrl+ArrowUp","mac+shift+ArrowUp"],t._translateEmpty,{args:[0,-i],checker:arrowChecker}],[["ArrowDown","mac+ArrowDown"],t._translateEmpty,{args:[0,e],checker:arrowChecker}],[["ctrl+ArrowDown","mac+shift+ArrowDown"],t._translateEmpty,{args:[0,i],checker:arrowChecker}]]))}static _type="freetext";static _editorType=u.FREETEXT;constructor(t){super({...t,name:"freeTextEditor"});this.#in=t.color||FreeTextEditor._defaultColor||AnnotationEditor._defaultLineColor;this.#fn=t.fontSize||FreeTextEditor._defaultFontSize}static initialize(t,e){AnnotationEditor.initialize(t,e);const i=getComputedStyle(document.documentElement);this._internalPadding=parseFloat(i.getPropertyValue("--freetext-padding"))}static updateDefaultParams(t,e){switch(t){case p.FREETEXT_SIZE:FreeTextEditor._defaultFontSize=e;break;case p.FREETEXT_COLOR:FreeTextEditor._defaultColor=e}}updateParams(t,e){switch(t){case p.FREETEXT_SIZE:this.#Nn(e);break;case p.FREETEXT_COLOR:this.#Bn(e)}}static get defaultPropertiesToUpdate(){return[[p.FREETEXT_SIZE,FreeTextEditor._defaultFontSize],[p.FREETEXT_COLOR,FreeTextEditor._defaultColor||AnnotationEditor._defaultLineColor]]}get propertiesToUpdate(){return[[p.FREETEXT_SIZE,this.#fn],[p.FREETEXT_COLOR,this.#in]]}#Nn(t){const setFontsize=t=>{this.editorDiv.style.fontSize=`calc(${t}px * var(--total-scale-factor))`;this.translate(0,-(t-this.#fn)*this.parentScale);this.#fn=t;this.#Hn()},e=this.#fn;this.addCommands({cmd:setFontsize.bind(this,t),undo:setFontsize.bind(this,e),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.FREETEXT_SIZE,overwriteIfSameType:!0,keepUndo:!0})}#Bn(t){const setColor=t=>{this.#in=this.editorDiv.style.color=t},e=this.#in;this.addCommands({cmd:setColor.bind(this,t),undo:setColor.bind(this,e),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.FREETEXT_COLOR,overwriteIfSameType:!0,keepUndo:!0})}_translateEmpty(t,e){this._uiManager.translateSelectedEditors(t,e,!0)}getInitialTranslation(){const t=this.parentScale;return[-FreeTextEditor._internalPadding*t,-(FreeTextEditor._internalPadding+this.#fn)*t]}rebuild(){if(this.parent){super.rebuild();null!==this.div&&(this.isAttachedToDOM||this.parent.add(this))}}enableEditMode(){if(this.isInEditMode())return;this.parent.setEditingState(!1);this.parent.updateToolbar(u.FREETEXT);super.enableEditMode();this.overlayDiv.classList.remove("enabled");this.editorDiv.contentEditable=!0;this._isDraggable=!1;this.div.removeAttribute("aria-activedescendant");this.#On=new AbortController;const t=this._uiManager.combinedSignal(this.#On);this.editorDiv.addEventListener("keydown",this.editorDivKeydown.bind(this),{signal:t});this.editorDiv.addEventListener("focus",this.editorDivFocus.bind(this),{signal:t});this.editorDiv.addEventListener("blur",this.editorDivBlur.bind(this),{signal:t});this.editorDiv.addEventListener("input",this.editorDivInput.bind(this),{signal:t});this.editorDiv.addEventListener("paste",this.editorDivPaste.bind(this),{signal:t})}disableEditMode(){if(this.isInEditMode()){this.parent.setEditingState(!0);super.disableEditMode();this.overlayDiv.classList.add("enabled");this.editorDiv.contentEditable=!1;this.div.setAttribute("aria-activedescendant",this.#Ln);this._isDraggable=!0;this.#On?.abort();this.#On=null;this.div.focus({preventScroll:!0});this.isEditing=!1;this.parent.div.classList.add("freetextEditing")}}focusin(t){if(this._focusEventsAllowed){super.focusin(t);t.target!==this.editorDiv&&this.editorDiv.focus()}}onceAdded(t){if(!this.width){this.enableEditMode();t&&this.editorDiv.focus();this._initialOptions?.isCentered&&this.center();this._initialOptions=null}}isEmpty(){return!this.editorDiv||""===this.editorDiv.innerText.trim()}remove(){this.isEditing=!1;if(this.parent){this.parent.setEditingState(!0);this.parent.div.classList.add("freetextEditing")}super.remove()}#Un(){const t=[];this.editorDiv.normalize();let e=null;for(const i of this.editorDiv.childNodes)if(e?.nodeType!==Node.TEXT_NODE||"BR"!==i.nodeName){t.push(FreeTextEditor.#zn(i));e=i}return t.join("\n")}#Hn(){const[t,e]=this.parentDimensions;let i;if(this.isAttachedToDOM)i=this.div.getBoundingClientRect();else{const{currentLayer:t,div:e}=this,s=e.style.display,n=e.classList.contains("hidden");e.classList.remove("hidden");e.style.display="hidden";t.div.append(this.div);i=e.getBoundingClientRect();e.remove();e.style.display=s;e.classList.toggle("hidden",n)}if(this.rotation%180==this.parentRotation%180){this.width=i.width/t;this.height=i.height/e}else{this.width=i.height/t;this.height=i.width/e}this.fixAndSetPosition()}commit(){if(!this.isInEditMode())return;super.commit();this.disableEditMode();const t=this.#Fn,e=this.#Fn=this.#Un().trimEnd();if(t===e)return;const setText=t=>{this.#Fn=t;if(t){this.#Gn();this._uiManager.rebuild(this);this.#Hn()}else this.remove()};this.addCommands({cmd:()=>{setText(e)},undo:()=>{setText(t)},mustExec:!1});this.#Hn()}shouldGetKeyboardEvents(){return this.isInEditMode()}enterInEditMode(){this.enableEditMode();this.editorDiv.focus()}dblclick(t){this.enterInEditMode()}keydown(t){if(t.target===this.div&&"Enter"===t.key){this.enterInEditMode();t.preventDefault()}}editorDivKeydown(t){FreeTextEditor._keyboardManager.exec(this,t)}editorDivFocus(t){this.isEditing=!0}editorDivBlur(t){this.isEditing=!1}editorDivInput(t){this.parent.div.classList.toggle("freetextEditing",this.isEmpty())}disableEditing(){this.editorDiv.setAttribute("role","comment");this.editorDiv.removeAttribute("aria-multiline")}enableEditing(){this.editorDiv.setAttribute("role","textbox");this.editorDiv.setAttribute("aria-multiline",!0)}render(){if(this.div)return this.div;let t,e;if(this._isCopy||this.annotationElementId){t=this.x;e=this.y}super.render();this.editorDiv=document.createElement("div");this.editorDiv.className="internal";this.editorDiv.setAttribute("id",this.#Ln);this.editorDiv.setAttribute("data-l10n-id","pdfjs-free-text2");this.editorDiv.setAttribute("data-l10n-attrs","default-content");this.enableEditing();this.editorDiv.contentEditable=!0;const{style:i}=this.editorDiv;i.fontSize=`calc(${this.#fn}px * var(--total-scale-factor))`;i.color=this.#in;this.div.append(this.editorDiv);this.overlayDiv=document.createElement("div");this.overlayDiv.classList.add("overlay","enabled");this.div.append(this.overlayDiv);bindEvents(this,this.div,["dblclick","keydown"]);if(this._isCopy||this.annotationElementId){const[i,s]=this.parentDimensions;if(this.annotationElementId){const{position:n}=this._initialData;let[a,r]=this.getInitialTranslation();[a,r]=this.pageTranslationToScreen(a,r);const[o,l]=this.pageDimensions,[h,d]=this.pageTranslation;let c,u;switch(this.rotation){case 0:c=t+(n[0]-h)/o;u=e+this.height-(n[1]-d)/l;break;case 90:c=t+(n[0]-h)/o;u=e-(n[1]-d)/l;[a,r]=[r,-a];break;case 180:c=t-this.width+(n[0]-h)/o;u=e-(n[1]-d)/l;[a,r]=[-a,-r];break;case 270:c=t+(n[0]-h-this.height*l)/o;u=e+(n[1]-d-this.width*o)/l;[a,r]=[-r,a]}this.setAt(c*i,u*s,a,r)}else this._moveAfterPaste(t,e);this.#Gn();this._isDraggable=!0;this.editorDiv.contentEditable=!1}else{this._isDraggable=!1;this.editorDiv.contentEditable=!0}return this.div}static#zn(t){return(t.nodeType===Node.TEXT_NODE?t.nodeValue:t.innerText).replaceAll(Et,"")}editorDivPaste(t){const e=t.clipboardData||window.clipboardData,{types:i}=e;if(1===i.length&&"text/plain"===i[0])return;t.preventDefault();const s=FreeTextEditor.#$n(e.getData("text")||"").replaceAll(Et,"\n");if(!s)return;const n=window.getSelection();if(!n.rangeCount)return;this.editorDiv.normalize();n.deleteFromDocument();const a=n.getRangeAt(0);if(!s.includes("\n")){a.insertNode(document.createTextNode(s));this.editorDiv.normalize();n.collapseToStart();return}const{startContainer:r,startOffset:o}=a,l=[],h=[];if(r.nodeType===Node.TEXT_NODE){const t=r.parentElement;h.push(r.nodeValue.slice(o).replaceAll(Et,""));if(t!==this.editorDiv){let e=l;for(const i of this.editorDiv.childNodes)i!==t?e.push(FreeTextEditor.#zn(i)):e=h}l.push(r.nodeValue.slice(0,o).replaceAll(Et,""))}else if(r===this.editorDiv){let t=l,e=0;for(const i of this.editorDiv.childNodes){e++===o&&(t=h);t.push(FreeTextEditor.#zn(i))}}this.#Fn=`${l.join("\n")}${s}${h.join("\n")}`;this.#Gn();const d=new Range;let c=Math.sumPrecise(l.map((t=>t.length)));for(const{firstChild:t}of this.editorDiv.childNodes)if(t.nodeType===Node.TEXT_NODE){const e=t.nodeValue.length;if(c<=e){d.setStart(t,c);d.setEnd(t,c);break}c-=e}n.removeAllRanges();n.addRange(d)}#Gn(){this.editorDiv.replaceChildren();if(this.#Fn)for(const t of this.#Fn.split("\n")){const e=document.createElement("div");e.append(t?document.createTextNode(t):document.createElement("br"));this.editorDiv.append(e)}}#Wn(){return this.#Fn.replaceAll(" "," ")}static#$n(t){return t.replaceAll(" "," ")}get contentDiv(){return this.editorDiv}static async deserialize(t,e,i){let s=null;if(t instanceof FreeTextAnnotationElement){const{data:{defaultAppearanceData:{fontSize:e,fontColor:i},rect:n,rotation:a,id:r,popupRef:o},textContent:l,textPosition:h,parent:{page:{pageNumber:d}}}=t;if(!l||0===l.length)return null;s=t={annotationType:u.FREETEXT,color:Array.from(i),fontSize:e,value:l.join("\n"),position:h,pageIndex:d-1,rect:n.slice(0),rotation:a,id:r,deleted:!1,popupRef:o}}const n=await super.deserialize(t,e,i);n.#fn=t.fontSize;n.#in=Util.makeHexColor(...t.color);n.#Fn=FreeTextEditor.#$n(t.value);n.annotationElementId=t.id||null;n._initialData=s;return n}serialize(t=!1){if(this.isEmpty())return null;if(this.deleted)return this.serializeDeleted();const e=FreeTextEditor._internalPadding*this.parentScale,i=this.getRect(e,e),s=AnnotationEditor._colorManager.convert(this.isAttachedToDOM?getComputedStyle(this.editorDiv).color:this.#in),n={annotationType:u.FREETEXT,color:s,fontSize:this.#fn,value:this.#Wn(),pageIndex:this.pageIndex,rect:i,rotation:this.rotation,structTreeParentId:this._structTreeParentId};if(t){n.isCopy=!0;return n}if(this.annotationElementId&&!this.#Vn(n))return null;n.id=this.annotationElementId;return n}#Vn(t){const{value:e,fontSize:i,color:s,pageIndex:n}=this._initialData;return this._hasBeenMoved||t.value!==e||t.fontSize!==i||t.color.some(((t,e)=>t!==s[e]))||t.pageIndex!==n}renderAnnotationElement(t){const e=super.renderAnnotationElement(t);if(this.deleted)return e;const{style:i}=e;i.fontSize=`calc(${this.#fn}px * var(--total-scale-factor))`;i.color=this.#in;e.replaceChildren();for(const t of this.#Fn.split("\n")){const i=document.createElement("div");i.append(t?document.createTextNode(t):document.createElement("br"));e.append(i)}const s=FreeTextEditor._internalPadding*this.parentScale;t.updateEdited({rect:this.getRect(s,s),popupContent:this.#Fn});return e}resetAnnotationElement(t){super.resetAnnotationElement(t);t.resetEdited()}}class Outline{static PRECISION=1e-4;toSVGPath(){unreachable("Abstract method `toSVGPath` must be implemented.")}get box(){unreachable("Abstract getter `box` must be implemented.")}serialize(t,e){unreachable("Abstract method `serialize` must be implemented.")}static _rescale(t,e,i,s,n,a){a||=new Float32Array(t.length);for(let r=0,o=t.length;r=6;t-=6)isNaN(e[t])?i.push(`L${e[t+4]} ${e[t+5]}`):i.push(`C${e[t]} ${e[t+1]} ${e[t+2]} ${e[t+3]} ${e[t+4]} ${e[t+5]}`);this.#ca(i);return i.join(" ")}#ha(){const[t,e,i,s]=this.#jn,[n,a,r,o]=this.#la();return`M${(this.#Qn[2]-t)/i} ${(this.#Qn[3]-e)/s} L${(this.#Qn[4]-t)/i} ${(this.#Qn[5]-e)/s} L${n} ${a} L${r} ${o} L${(this.#Qn[16]-t)/i} ${(this.#Qn[17]-e)/s} L${(this.#Qn[14]-t)/i} ${(this.#Qn[15]-e)/s} Z`}#ca(t){const e=this.#qn;t.push(`L${e[4]} ${e[5]} Z`)}#da(t){const[e,i,s,n]=this.#jn,a=this.#Qn.subarray(4,6),r=this.#Qn.subarray(16,18),[o,l,h,d]=this.#la();t.push(`L${(a[0]-e)/s} ${(a[1]-i)/n} L${o} ${l} L${h} ${d} L${(r[0]-e)/s} ${(r[1]-i)/n}`)}newFreeDrawOutline(t,e,i,s,n,a){return new FreeDrawOutline(t,e,i,s,n,a)}getOutlines(){const t=this.#Yn,e=this.#qn,i=this.#Qn,[s,n,a,r]=this.#jn,o=new Float32Array((this.#na?.length??0)+2);for(let t=0,e=o.length-2;t=6;t-=6)for(let i=0;i<6;i+=2)if(isNaN(e[t+i])){l[h]=l[h+1]=NaN;h+=2}else{l[h]=e[t+i];l[h+1]=e[t+i+1];h+=2}this.#ga(l,h);return this.newFreeDrawOutline(l,o,this.#jn,this.#ia,this.#Xn,this.#Kn)}#ua(t){const e=this.#Qn,[i,s,n,a]=this.#jn,[r,o,l,h]=this.#la(),d=new Float32Array(36);d.set([NaN,NaN,NaN,NaN,(e[2]-i)/n,(e[3]-s)/a,NaN,NaN,NaN,NaN,(e[4]-i)/n,(e[5]-s)/a,NaN,NaN,NaN,NaN,r,o,NaN,NaN,NaN,NaN,l,h,NaN,NaN,NaN,NaN,(e[16]-i)/n,(e[17]-s)/a,NaN,NaN,NaN,NaN,(e[14]-i)/n,(e[15]-s)/a],0);return this.newFreeDrawOutline(d,t,this.#jn,this.#ia,this.#Xn,this.#Kn)}#ga(t,e){const i=this.#qn;t.set([NaN,NaN,NaN,NaN,i[4],i[5]],e);return e+6}#pa(t,e){const i=this.#Qn.subarray(4,6),s=this.#Qn.subarray(16,18),[n,a,r,o]=this.#jn,[l,h,d,c]=this.#la();t.set([NaN,NaN,NaN,NaN,(i[0]-n)/r,(i[1]-a)/o,NaN,NaN,NaN,NaN,l,h,NaN,NaN,NaN,NaN,d,c,NaN,NaN,NaN,NaN,(s[0]-n)/r,(s[1]-a)/o],e);return e+24}}class FreeDrawOutline extends Outline{#jn;#fa=new Float32Array(4);#Xn;#Kn;#na;#ia;#ma;constructor(t,e,i,s,n,a){super();this.#ma=t;this.#na=e;this.#jn=i;this.#ia=s;this.#Xn=n;this.#Kn=a;this.lastPoint=[NaN,NaN];this.#ba(a);const[r,o,l,h]=this.#fa;for(let e=0,i=t.length;et[0]-e[0]||t[1]-e[1]||t[2]-e[2]));const t=[];for(const e of this.#wa)if(e[3]){t.push(...this.#va(e));this.#_a(e)}else{this.#xa(e);t.push(...this.#va(e))}return this.#Ea(t)}#Ea(t){const e=[],i=new Set;for(const i of t){const[t,s,n]=i;e.push([t,s,i],[t,n,i])}e.sort(((t,e)=>t[1]-e[1]||t[0]-e[0]));for(let t=0,s=e.length;t0;){const t=i.values().next().value;let[e,a,r,o,l]=t;i.delete(t);let h=e,d=a;n=[e,r];s.push(n);for(;;){let t;if(i.has(o))t=o;else{if(!i.has(l))break;t=l}i.delete(t);[e,a,r,o,l]=t;if(h!==e){n.push(h,d,e,d===a?a:r);h=e}d=d===a?r:a}n.push(h,d)}return new HighlightOutline(s,this.#jn,this.#Aa)}#Sa(t){const e=this.#ya;let i=0,s=e.length-1;for(;i<=s;){const n=i+s>>1,a=e[n][0];if(a===t)return n;a=0;s--){const[i,n]=this.#ya[s];if(i!==t)break;if(i===t&&n===e){this.#ya.splice(s,1);return}}}#va(t){const[e,i,s]=t,n=[[e,i,s]],a=this.#Sa(s);for(let t=0;t=i)if(o>s)n[t][1]=s;else{if(1===a)return[];n.splice(t,1);t--;a--}else{n[t][2]=i;o>s&&n.push([e,s,o])}}}return n}}class HighlightOutline extends Outline{#jn;#Ca;constructor(t,e,i){super();this.#Ca=t;this.#jn=e;this.lastPoint=i}toSVGPath(){const t=[];for(const e of this.#Ca){let[i,s]=e;t.push(`M${i} ${s}`);for(let n=2;n-1){this.#Ya=!0;this.#er(t);this.#ir()}else if(this.#$a){this.#za=t.anchorNode;this.#Ga=t.anchorOffset;this.#ja=t.focusNode;this.#qa=t.focusOffset;this.#sr();this.#ir();this.rotate(this.rotation)}}get telemetryInitialData(){return{action:"added",type:this.#Ya?"free_highlight":"highlight",color:this._uiManager.highlightColorNames.get(this.color),thickness:this.#sa,methodOfCreation:this.#tr}}get telemetryFinalData(){return{type:"highlight",color:this._uiManager.highlightColorNames.get(this.color)}}static computeTelemetryFinalData(t){return{numberOfColors:t.get("color").size}}#sr(){const t=new HighlightOutliner(this.#$a,.001);this.#Ka=t.getOutlines();[this.x,this.y,this.width,this.height]=this.#Ka.box;const e=new HighlightOutliner(this.#$a,.0025,.001,"ltr"===this._uiManager.direction);this.#Va=e.getOutlines();const{lastPoint:i}=this.#Va;this.#Aa=[(i[0]-this.x)/this.width,(i[1]-this.y)/this.height]}#er({highlightOutlines:t,highlightId:e,clipPathId:i}){this.#Ka=t;this.#Va=t.getNewOutline(this.#sa/2+1.5,.0025);if(e>=0){this.#y=e;this.#Wa=i;this.parent.drawLayer.finalizeDraw(e,{bbox:t.box,path:{d:t.toSVGPath()}});this.#Ja=this.parent.drawLayer.drawOutline({rootClass:{highlightOutline:!0,free:!0},bbox:this.#Va.box,path:{d:this.#Va.toSVGPath()}},!0)}else if(this.parent){const e=this.parent.viewport.rotation;this.parent.drawLayer.updateProperties(this.#y,{bbox:HighlightEditor.#nr(this.#Ka.box,(e-this.rotation+360)%360),path:{d:t.toSVGPath()}});this.parent.drawLayer.updateProperties(this.#Ja,{bbox:HighlightEditor.#nr(this.#Va.box,e),path:{d:this.#Va.toSVGPath()}})}const[s,n,a,r]=t.box;switch(this.rotation){case 0:this.x=s;this.y=n;this.width=a;this.height=r;break;case 90:{const[t,e]=this.parentDimensions;this.x=n;this.y=1-s;this.width=a*e/t;this.height=r*t/e;break}case 180:this.x=1-s;this.y=1-n;this.width=a;this.height=r;break;case 270:{const[t,e]=this.parentDimensions;this.x=1-n;this.y=s;this.width=a*e/t;this.height=r*t/e;break}}const{lastPoint:o}=this.#Va;this.#Aa=[(o[0]-s)/a,(o[1]-n)/r]}static initialize(t,e){AnnotationEditor.initialize(t,e);HighlightEditor._defaultColor||=e.highlightColors?.values().next().value||"#fff066"}static updateDefaultParams(t,e){switch(t){case p.HIGHLIGHT_DEFAULT_COLOR:HighlightEditor._defaultColor=e;break;case p.HIGHLIGHT_THICKNESS:HighlightEditor._defaultThickness=e}}translateInPage(t,e){}get toolbarPosition(){return this.#Aa}updateParams(t,e){switch(t){case p.HIGHLIGHT_COLOR:this.#Bn(e);break;case p.HIGHLIGHT_THICKNESS:this.#ar(e)}}static get defaultPropertiesToUpdate(){return[[p.HIGHLIGHT_DEFAULT_COLOR,HighlightEditor._defaultColor],[p.HIGHLIGHT_THICKNESS,HighlightEditor._defaultThickness]]}get propertiesToUpdate(){return[[p.HIGHLIGHT_COLOR,this.color||HighlightEditor._defaultColor],[p.HIGHLIGHT_THICKNESS,this.#sa||HighlightEditor._defaultThickness],[p.HIGHLIGHT_FREE,this.#Ya]]}#Bn(t){const setColorAndOpacity=(t,e)=>{this.color=t;this.#Qa=e;this.parent?.drawLayer.updateProperties(this.#y,{root:{fill:t,"fill-opacity":e}});this.#n?.updateColor(t)},e=this.color,i=this.#Qa;this.addCommands({cmd:setColorAndOpacity.bind(this,t,HighlightEditor._defaultOpacity),undo:setColorAndOpacity.bind(this,e,i),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.HIGHLIGHT_COLOR,overwriteIfSameType:!0,keepUndo:!0});this._reportTelemetry({action:"color_changed",color:this._uiManager.highlightColorNames.get(t)},!0)}#ar(t){const e=this.#sa,setThickness=t=>{this.#sa=t;this.#rr(t)};this.addCommands({cmd:setThickness.bind(this,t),undo:setThickness.bind(this,e),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.INK_THICKNESS,overwriteIfSameType:!0,keepUndo:!0});this._reportTelemetry({action:"thickness_changed",thickness:t},!0)}async addEditToolbar(){const t=await super.addEditToolbar();if(!t)return null;if(this._uiManager.highlightColors){this.#n=new ColorPicker({editor:this});t.addColorPicker(this.#n)}return t}disableEditing(){super.disableEditing();this.div.classList.toggle("disabled",!0)}enableEditing(){super.enableEditing();this.div.classList.toggle("disabled",!1)}fixAndSetPosition(){return super.fixAndSetPosition(this.#or())}getBaseTranslation(){return[0,0]}getRect(t,e){return super.getRect(t,e,this.#or())}onceAdded(t){this.annotationElementId||this.parent.addUndoableEditor(this);t&&this.div.focus()}remove(){this.#lr();this._reportTelemetry({action:"deleted"});super.remove()}rebuild(){if(this.parent){super.rebuild();if(null!==this.div){this.#ir();this.isAttachedToDOM||this.parent.add(this)}}}setParent(t){let e=!1;if(this.parent&&!t)this.#lr();else if(t){this.#ir(t);e=!this.parent&&this.div?.classList.contains("selectedEditor")}super.setParent(t);this.show(this._isVisible);e&&this.select()}#rr(t){if(!this.#Ya)return;this.#er({highlightOutlines:this.#Ka.getNewOutline(t/2)});this.fixAndSetPosition();const[e,i]=this.parentDimensions;this.setDims(this.width*e,this.height*i)}#lr(){if(null!==this.#y&&this.parent){this.parent.drawLayer.remove(this.#y);this.#y=null;this.parent.drawLayer.remove(this.#Ja);this.#Ja=null}}#ir(t=this.parent){if(null===this.#y){({id:this.#y,clipPathId:this.#Wa}=t.drawLayer.draw({bbox:this.#Ka.box,root:{viewBox:"0 0 1 1",fill:this.color,"fill-opacity":this.#Qa},rootClass:{highlight:!0,free:this.#Ya},path:{d:this.#Ka.toSVGPath()}},!1,!0));this.#Ja=t.drawLayer.drawOutline({rootClass:{highlightOutline:!0,free:this.#Ya},bbox:this.#Va.box,path:{d:this.#Va.toSVGPath()}},this.#Ya);this.#Xa&&(this.#Xa.style.clipPath=this.#Wa)}}static#nr([t,e,i,s],n){switch(n){case 90:return[1-e-s,t,s,i];case 180:return[1-t-i,1-e-s,i,s];case 270:return[e,1-t-i,s,i]}return[t,e,i,s]}rotate(t){const{drawLayer:e}=this.parent;let i;if(this.#Ya){t=(t-this.rotation+360)%360;i=HighlightEditor.#nr(this.#Ka.box,t)}else i=HighlightEditor.#nr([this.x,this.y,this.width,this.height],t);e.updateProperties(this.#y,{bbox:i,root:{"data-main-rotation":t}});e.updateProperties(this.#Ja,{bbox:HighlightEditor.#nr(this.#Va.box,t),root:{"data-main-rotation":t}})}render(){if(this.div)return this.div;const t=super.render();if(this.#Za){t.setAttribute("aria-label",this.#Za);t.setAttribute("role","mark")}this.#Ya?t.classList.add("free"):this.div.addEventListener("keydown",this.#hr.bind(this),{signal:this._uiManager._signal});const e=this.#Xa=document.createElement("div");t.append(e);e.setAttribute("aria-hidden","true");e.className="internal";e.style.clipPath=this.#Wa;const[i,s]=this.parentDimensions;this.setDims(this.width*i,this.height*s);bindEvents(this,this.#Xa,["pointerover","pointerleave"]);this.enableEditing();return t}pointerover(){this.isSelected||this.parent?.drawLayer.updateProperties(this.#Ja,{rootClass:{hovered:!0}})}pointerleave(){this.isSelected||this.parent?.drawLayer.updateProperties(this.#Ja,{rootClass:{hovered:!1}})}#hr(t){HighlightEditor._keyboardManager.exec(this,t)}_moveCaret(t){this.parent.unselect(this);switch(t){case 0:case 2:this.#dr(!0);break;case 1:case 3:this.#dr(!1)}}#dr(t){if(!this.#za)return;const e=window.getSelection();t?e.setPosition(this.#za,this.#Ga):e.setPosition(this.#ja,this.#qa)}select(){super.select();this.#Ja&&this.parent?.drawLayer.updateProperties(this.#Ja,{rootClass:{hovered:!1,selected:!0}})}unselect(){super.unselect();if(this.#Ja){this.parent?.drawLayer.updateProperties(this.#Ja,{rootClass:{selected:!1}});this.#Ya||this.#dr(!1)}}get _mustFixPosition(){return!this.#Ya}show(t=this._isVisible){super.show(t);if(this.parent){this.parent.drawLayer.updateProperties(this.#y,{rootClass:{hidden:!t}});this.parent.drawLayer.updateProperties(this.#Ja,{rootClass:{hidden:!t}})}}#or(){return this.#Ya?this.rotation:0}#cr(){if(this.#Ya)return null;const[t,e]=this.pageDimensions,[i,s]=this.pageTranslation,n=this.#$a,a=new Float32Array(8*n.length);let r=0;for(const{x:o,y:l,width:h,height:d}of n){const n=o*t+i,c=(1-l)*e+s;a[r]=a[r+4]=n;a[r+1]=a[r+3]=c;a[r+2]=a[r+6]=n+h*t;a[r+5]=a[r+7]=c-d*e;r+=8}return a}#ur(t){return this.#Ka.serialize(t,this.#or())}static startHighlighting(t,e,{target:i,x:s,y:n}){const{x:a,y:r,width:o,height:l}=i.getBoundingClientRect(),h=new AbortController,d=t.combinedSignal(h),pointerUpCallback=e=>{h.abort();this.#pr(t,e)};window.addEventListener("blur",pointerUpCallback,{signal:d});window.addEventListener("pointerup",pointerUpCallback,{signal:d});window.addEventListener("pointerdown",stopEvent,{capture:!0,passive:!1,signal:d});window.addEventListener("contextmenu",noContextMenu,{signal:d});i.addEventListener("pointermove",this.#gr.bind(this,t),{signal:d});this._freeHighlight=new FreeHighlightOutliner({x:s,y:n},[a,r,o,l],t.scale,this._defaultThickness/2,e,.001);({id:this._freeHighlightId,clipPathId:this._freeHighlightClipId}=t.drawLayer.draw({bbox:[0,0,1,1],root:{viewBox:"0 0 1 1",fill:this._defaultColor,"fill-opacity":this._defaultOpacity},rootClass:{highlight:!0,free:!0},path:{d:this._freeHighlight.toSVGPath()}},!0,!0))}static#gr(t,e){this._freeHighlight.add(e)&&t.drawLayer.updateProperties(this._freeHighlightId,{path:{d:this._freeHighlight.toSVGPath()}})}static#pr(t,e){this._freeHighlight.isEmpty()?t.drawLayer.remove(this._freeHighlightId):t.createAndAddNewEditor(e,!1,{highlightId:this._freeHighlightId,highlightOutlines:this._freeHighlight.getOutlines(),clipPathId:this._freeHighlightClipId,methodOfCreation:"main_toolbar"});this._freeHighlightId=-1;this._freeHighlight=null;this._freeHighlightClipId=""}static async deserialize(t,e,i){let s=null;if(t instanceof HighlightAnnotationElement){const{data:{quadPoints:e,rect:i,rotation:n,id:a,color:r,opacity:o,popupRef:l},parent:{page:{pageNumber:h}}}=t;s=t={annotationType:u.HIGHLIGHT,color:Array.from(r),opacity:o,quadPoints:e,boxes:null,pageIndex:h-1,rect:i.slice(0),rotation:n,id:a,deleted:!1,popupRef:l}}else if(t instanceof InkAnnotationElement){const{data:{inkLists:e,rect:i,rotation:n,id:a,color:r,borderStyle:{rawWidth:o},popupRef:l},parent:{page:{pageNumber:h}}}=t;s=t={annotationType:u.HIGHLIGHT,color:Array.from(r),thickness:o,inkLists:e,boxes:null,pageIndex:h-1,rect:i.slice(0),rotation:n,id:a,deleted:!1,popupRef:l}}const{color:n,quadPoints:a,inkLists:r,opacity:o}=t,l=await super.deserialize(t,e,i);l.color=Util.makeHexColor(...n);l.#Qa=o||1;r&&(l.#sa=t.thickness);l.annotationElementId=t.id||null;l._initialData=s;const[h,d]=l.pageDimensions,[c,p]=l.pageTranslation;if(a){const t=l.#$a=[];for(let e=0;et!==e[i]))}renderAnnotationElement(t){t.updateEdited({rect:this.getRect(0,0)});return null}static canCreateNewEmptyEditor(){return!1}}class DrawingOptions{#fr=Object.create(null);updateProperty(t,e){this[t]=e;this.updateSVGProperty(t,e)}updateProperties(t){if(t)for(const[e,i]of Object.entries(t))e.startsWith("_")||this.updateProperty(e,i)}updateSVGProperty(t,e){this.#fr[t]=e}toSVGProperties(){const t=this.#fr;this.#fr=Object.create(null);return{root:t}}reset(){this.#fr=Object.create(null)}updateAll(t=this){this.updateProperties(t)}clone(){unreachable("Not implemented")}}class DrawingEditor extends AnnotationEditor{#mr=null;#br;_drawId=null;static _currentDrawId=-1;static _currentParent=null;static#Ar=null;static#wr=null;static#yr=null;static#vr=NaN;static#_r=null;static#xr=null;static#Er=NaN;static _INNER_MARGIN=3;constructor(t){super(t);this.#br=t.mustBeCommitted||!1;this._addOutlines(t)}_addOutlines(t){if(t.drawOutlines){this.#Sr(t);this.#ir()}}#Sr({drawOutlines:t,drawId:e,drawingOptions:i}){this.#mr=t;this._drawingOptions||=i;if(e>=0){this._drawId=e;this.parent.drawLayer.finalizeDraw(e,t.defaultProperties)}else this._drawId=this.#Cr(t,this.parent);this.#Tr(t.box)}#Cr(t,e){const{id:i}=e.drawLayer.draw(DrawingEditor._mergeSVGProperties(this._drawingOptions.toSVGProperties(),t.defaultSVGProperties),!1,!1);return i}static _mergeSVGProperties(t,e){const i=new Set(Object.keys(t));for(const[s,n]of Object.entries(e))i.has(s)?Object.assign(t[s],n):t[s]=n;return t}static getDefaultDrawingOptions(t){unreachable("Not implemented")}static get typesMap(){unreachable("Not implemented")}static get isDrawer(){return!0}static get supportMultipleDrawings(){return!1}static updateDefaultParams(t,e){const i=this.typesMap.get(t);i&&this._defaultDrawingOptions.updateProperty(i,e);if(this._currentParent){DrawingEditor.#Ar.updateProperty(i,e);this._currentParent.drawLayer.updateProperties(this._currentDrawId,this._defaultDrawingOptions.toSVGProperties())}}updateParams(t,e){const i=this.constructor.typesMap.get(t);i&&this._updateProperty(t,i,e)}static get defaultPropertiesToUpdate(){const t=[],e=this._defaultDrawingOptions;for(const[i,s]of this.typesMap)t.push([i,e[s]]);return t}get propertiesToUpdate(){const t=[],{_drawingOptions:e}=this;for(const[i,s]of this.constructor.typesMap)t.push([i,e[s]]);return t}_updateProperty(t,e,i){const s=this._drawingOptions,n=s[e],setter=t=>{s.updateProperty(e,t);const i=this.#mr.updateProperty(e,t);i&&this.#Tr(i);this.parent?.drawLayer.updateProperties(this._drawId,s.toSVGProperties())};this.addCommands({cmd:setter.bind(this,i),undo:setter.bind(this,n),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:t,overwriteIfSameType:!0,keepUndo:!0})}_onResizing(){this.parent?.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties(this.#mr.getPathResizingSVGProperties(this.#Mr()),{bbox:this.#Dr()}))}_onResized(){this.parent?.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties(this.#mr.getPathResizedSVGProperties(this.#Mr()),{bbox:this.#Dr()}))}_onTranslating(t,e){this.parent?.drawLayer.updateProperties(this._drawId,{bbox:this.#Dr()})}_onTranslated(){this.parent?.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties(this.#mr.getPathTranslatedSVGProperties(this.#Mr(),this.parentDimensions),{bbox:this.#Dr()}))}_onStartDragging(){this.parent?.drawLayer.updateProperties(this._drawId,{rootClass:{moving:!0}})}_onStopDragging(){this.parent?.drawLayer.updateProperties(this._drawId,{rootClass:{moving:!1}})}commit(){super.commit();this.disableEditMode();this.disableEditing()}disableEditing(){super.disableEditing();this.div.classList.toggle("disabled",!0)}enableEditing(){super.enableEditing();this.div.classList.toggle("disabled",!1)}getBaseTranslation(){return[0,0]}get isResizable(){return!0}onceAdded(t){this.annotationElementId||this.parent.addUndoableEditor(this);this._isDraggable=!0;if(this.#br){this.#br=!1;this.commit();this.parent.setSelected(this);t&&this.isOnScreen&&this.div.focus()}}remove(){this.#lr();super.remove()}rebuild(){if(this.parent){super.rebuild();if(null!==this.div){this.#ir();this.#Tr(this.#mr.box);this.isAttachedToDOM||this.parent.add(this)}}}setParent(t){let e=!1;if(this.parent&&!t){this._uiManager.removeShouldRescale(this);this.#lr()}else if(t){this._uiManager.addShouldRescale(this);this.#ir(t);e=!this.parent&&this.div?.classList.contains("selectedEditor")}super.setParent(t);e&&this.select()}#lr(){if(null!==this._drawId&&this.parent){this.parent.drawLayer.remove(this._drawId);this._drawId=null;this._drawingOptions.reset()}}#ir(t=this.parent){if(null===this._drawId||this.parent!==t)if(null===this._drawId){this._drawingOptions.updateAll();this._drawId=this.#Cr(this.#mr,t)}else this.parent.drawLayer.updateParent(this._drawId,t.drawLayer)}#Pr([t,e,i,s]){const{parentDimensions:[n,a],rotation:r}=this;switch(r){case 90:return[e,1-t,i*(a/n),s*(n/a)];case 180:return[1-t,1-e,i,s];case 270:return[1-e,t,i*(a/n),s*(n/a)];default:return[t,e,i,s]}}#Mr(){const{x:t,y:e,width:i,height:s,parentDimensions:[n,a],rotation:r}=this;switch(r){case 90:return[1-e,t,i*(n/a),s*(a/n)];case 180:return[1-t,1-e,i,s];case 270:return[e,1-t,i*(n/a),s*(a/n)];default:return[t,e,i,s]}}#Tr(t){[this.x,this.y,this.width,this.height]=this.#Pr(t);if(this.div){this.fixAndSetPosition();const[t,e]=this.parentDimensions;this.setDims(this.width*t,this.height*e)}this._onResized()}#Dr(){const{x:t,y:e,width:i,height:s,rotation:n,parentRotation:a,parentDimensions:[r,o]}=this;switch((4*n+a)/90){case 1:return[1-e-s,t,s,i];case 2:return[1-t-i,1-e-s,i,s];case 3:return[e,1-t-i,s,i];case 4:return[t,e-i*(r/o),s*(o/r),i*(r/o)];case 5:return[1-e,t,i*(r/o),s*(o/r)];case 6:return[1-t-s*(o/r),1-e,s*(o/r),i*(r/o)];case 7:return[e-i*(r/o),1-t-s*(o/r),i*(r/o),s*(o/r)];case 8:return[t-i,e-s,i,s];case 9:return[1-e,t-i,s,i];case 10:return[1-t,1-e,i,s];case 11:return[e-s,1-t,s,i];case 12:return[t-s*(o/r),e,s*(o/r),i*(r/o)];case 13:return[1-e-i*(r/o),t-s*(o/r),i*(r/o),s*(o/r)];case 14:return[1-t,1-e-i*(r/o),s*(o/r),i*(r/o)];case 15:return[e,1-t,i*(r/o),s*(o/r)];default:return[t,e,i,s]}}rotate(){this.parent&&this.parent.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties({bbox:this.#Dr()},this.#mr.updateRotation((this.parentRotation-this.rotation+360)%360)))}onScaleChanging(){this.parent&&this.#Tr(this.#mr.updateParentDimensions(this.parentDimensions,this.parent.scale))}static onScaleChangingWhenDrawing(){}render(){if(this.div)return this.div;let t,e;if(this._isCopy){t=this.x;e=this.y}const i=super.render();i.classList.add("draw");const s=document.createElement("div");i.append(s);s.setAttribute("aria-hidden","true");s.className="internal";const[n,a]=this.parentDimensions;this.setDims(this.width*n,this.height*a);this._uiManager.addShouldRescale(this);this.disableEditing();this._isCopy&&this._moveAfterPaste(t,e);return i}static createDrawerInstance(t,e,i,s,n){unreachable("Not implemented")}static startDrawing(t,e,i,s){const{target:n,offsetX:a,offsetY:r,pointerId:o,pointerType:l}=s;if(DrawingEditor.#_r&&DrawingEditor.#_r!==l)return;const{viewport:{rotation:h}}=t,{width:d,height:c}=n.getBoundingClientRect(),u=DrawingEditor.#wr=new AbortController,p=t.combinedSignal(u);DrawingEditor.#vr||=o;DrawingEditor.#_r??=l;window.addEventListener("pointerup",(t=>{DrawingEditor.#vr===t.pointerId?this._endDraw(t):DrawingEditor.#xr?.delete(t.pointerId)}),{signal:p});window.addEventListener("pointercancel",(t=>{DrawingEditor.#vr===t.pointerId?this._currentParent.endDrawingSession():DrawingEditor.#xr?.delete(t.pointerId)}),{signal:p});window.addEventListener("pointerdown",(t=>{if(DrawingEditor.#_r===t.pointerType){(DrawingEditor.#xr||=new Set).add(t.pointerId);if(DrawingEditor.#Ar.isCancellable()){DrawingEditor.#Ar.removeLastElement();DrawingEditor.#Ar.isEmpty()?this._currentParent.endDrawingSession(!0):this._endDraw(null)}}}),{capture:!0,passive:!1,signal:p});window.addEventListener("contextmenu",noContextMenu,{signal:p});n.addEventListener("pointermove",this._drawMove.bind(this),{signal:p});n.addEventListener("touchmove",(t=>{t.timeStamp===DrawingEditor.#Er&&stopEvent(t)}),{signal:p});t.toggleDrawing();e._editorUndoBar?.hide();if(DrawingEditor.#Ar)t.drawLayer.updateProperties(this._currentDrawId,DrawingEditor.#Ar.startNew(a,r,d,c,h));else{e.updateUIForDefaultProperties(this);DrawingEditor.#Ar=this.createDrawerInstance(a,r,d,c,h);DrawingEditor.#yr=this.getDefaultDrawingOptions();this._currentParent=t;({id:this._currentDrawId}=t.drawLayer.draw(this._mergeSVGProperties(DrawingEditor.#yr.toSVGProperties(),DrawingEditor.#Ar.defaultSVGProperties),!0,!1))}}static _drawMove(t){DrawingEditor.#Er=-1;if(!DrawingEditor.#Ar)return;const{offsetX:e,offsetY:i,pointerId:s}=t;if(DrawingEditor.#vr===s)if(DrawingEditor.#xr?.size>=1)this._endDraw(t);else{this._currentParent.drawLayer.updateProperties(this._currentDrawId,DrawingEditor.#Ar.add(e,i));DrawingEditor.#Er=t.timeStamp;stopEvent(t)}}static _cleanup(t){if(t){this._currentDrawId=-1;this._currentParent=null;DrawingEditor.#Ar=null;DrawingEditor.#yr=null;DrawingEditor.#_r=null;DrawingEditor.#Er=NaN}if(DrawingEditor.#wr){DrawingEditor.#wr.abort();DrawingEditor.#wr=null;DrawingEditor.#vr=NaN;DrawingEditor.#xr=null}}static _endDraw(t){const e=this._currentParent;if(e){e.toggleDrawing(!0);this._cleanup(!1);t?.target===e.div&&e.drawLayer.updateProperties(this._currentDrawId,DrawingEditor.#Ar.end(t.offsetX,t.offsetY));if(this.supportMultipleDrawings){const t=DrawingEditor.#Ar,i=this._currentDrawId,s=t.getLastElement();e.addCommands({cmd:()=>{e.drawLayer.updateProperties(i,t.setLastElement(s))},undo:()=>{e.drawLayer.updateProperties(i,t.removeLastElement())},mustExec:!1,type:p.DRAW_STEP})}else this.endDrawing(!1)}}static endDrawing(t){const e=this._currentParent;if(!e)return null;e.toggleDrawing(!0);e.cleanUndoStack(p.DRAW_STEP);if(!DrawingEditor.#Ar.isEmpty()){const{pageDimensions:[i,s],scale:n}=e,a=e.createAndAddNewEditor({offsetX:0,offsetY:0},!1,{drawId:this._currentDrawId,drawOutlines:DrawingEditor.#Ar.getOutlines(i*n,s*n,n,this._INNER_MARGIN),drawingOptions:DrawingEditor.#yr,mustBeCommitted:!t});this._cleanup(!0);return a}e.drawLayer.remove(this._currentDrawId);this._cleanup(!0);return null}createDrawingOptions(t){}static deserializeDraw(t,e,i,s,n,a){unreachable("Not implemented")}static async deserialize(t,e,i){const{rawDims:{pageWidth:s,pageHeight:n,pageX:a,pageY:r}}=e.viewport,o=this.deserializeDraw(a,r,s,n,this._INNER_MARGIN,t),l=await super.deserialize(t,e,i);l.createDrawingOptions(t);l.#Sr({drawOutlines:o});l.#ir();l.onScaleChanging();l.rotate();return l}serializeDraw(t){const[e,i]=this.pageTranslation,[s,n]=this.pageDimensions;return this.#mr.serialize([e,i,s,n],t)}renderAnnotationElement(t){t.updateEdited({rect:this.getRect(0,0)});return null}static canCreateNewEmptyEditor(){return!1}}class InkDrawOutliner{#Qn=new Float64Array(6);#wn;#kr;#es;#sa;#na;#Ir="";#Rr=0;#Ca=new InkDrawOutline;#Fr;#Lr;constructor(t,e,i,s,n,a){this.#Fr=i;this.#Lr=s;this.#es=n;this.#sa=a;[t,e]=this.#Or(t,e);const r=this.#wn=[NaN,NaN,NaN,NaN,t,e];this.#na=[t,e];this.#kr=[{line:r,points:this.#na}];this.#Qn.set(r,0)}updateProperty(t,e){"stroke-width"===t&&(this.#sa=e)}#Or(t,e){return Outline._normalizePoint(t,e,this.#Fr,this.#Lr,this.#es)}isEmpty(){return!this.#kr||0===this.#kr.length}isCancellable(){return this.#na.length<=10}add(t,e){[t,e]=this.#Or(t,e);const[i,s,n,a]=this.#Qn.subarray(2,6),r=t-n,o=e-a;if(Math.hypot(this.#Fr*r,this.#Lr*o)<=2)return null;this.#na.push(t,e);if(isNaN(i)){this.#Qn.set([n,a,t,e],2);this.#wn.push(NaN,NaN,NaN,NaN,t,e);return{path:{d:this.toSVGPath()}}}isNaN(this.#Qn[0])&&this.#wn.splice(6,6);this.#Qn.set([i,s,n,a,t,e],0);this.#wn.push(...Outline.createBezierPoints(i,s,n,a,t,e));return{path:{d:this.toSVGPath()}}}end(t,e){const i=this.add(t,e);return i||(2===this.#na.length?{path:{d:this.toSVGPath()}}:null)}startNew(t,e,i,s,n){this.#Fr=i;this.#Lr=s;this.#es=n;[t,e]=this.#Or(t,e);const a=this.#wn=[NaN,NaN,NaN,NaN,t,e];this.#na=[t,e];const r=this.#kr.at(-1);if(r){r.line=new Float32Array(r.line);r.points=new Float32Array(r.points)}this.#kr.push({line:a,points:this.#na});this.#Qn.set(a,0);this.#Rr=0;this.toSVGPath();return null}getLastElement(){return this.#kr.at(-1)}setLastElement(t){if(!this.#kr)return this.#Ca.setLastElement(t);this.#kr.push(t);this.#wn=t.line;this.#na=t.points;this.#Rr=0;return{path:{d:this.toSVGPath()}}}removeLastElement(){if(!this.#kr)return this.#Ca.removeLastElement();this.#kr.pop();this.#Ir="";for(let t=0,e=this.#kr.length;tt??NaN)),d,c,u,p),points:g(r[t].map((t=>t??NaN)),d,c,u,p)});const f=new this.prototype.constructor;f.build(h,i,s,1,o,l,n);return f}#zr(t=this.#sa){const e=this.#Xn+t/2*this.#Br;return this.#es%180==0?[e/this.#Fr,e/this.#Lr]:[e/this.#Lr,e/this.#Fr]}#Ur(){const[t,e,i,s]=this.#fa,[n,a]=this.#zr(0);return[t+n,e+a,i-2*n,s-2*a]}#Hr(){const t=this.#fa=new Float32Array([1/0,1/0,-1/0,-1/0]);for(const{line:e}of this.#kr){if(e.length<=12){for(let i=4,s=e.length;it!==e[i]))||t.thickness!==i||t.opacity!==s||t.pageIndex!==n}renderAnnotationElement(t){const{points:e,rect:i}=this.serializeDraw(!1);t.updateEdited({rect:i,thickness:this._drawingOptions["stroke-width"],points:e});return null}}class ContourDrawOutline extends InkDrawOutline{toSVGPath(){let t=super.toSVGPath();t.endsWith("Z")||(t+="Z");return t}}class SignatureExtractor{static#Gr={maxDim:512,sigmaSFactor:.02,sigmaR:25,kernelSize:16};static#$r(t,e,i,s){s-=e;return 0===(i-=t)?s>0?0:4:1===i?s+6:2-s}static#Wr=new Int32Array([0,1,-1,1,-1,0,-1,-1,0,-1,1,-1,1,0,1,1]);static#Vr(t,e,i,s,n,a,r){const o=this.#$r(i,s,n,a);for(let n=0;n<8;n++){const a=(-n+o-r+16)%8;if(0!==t[(i+this.#Wr[2*a])*e+(s+this.#Wr[2*a+1])])return a}return-1}static#jr(t,e,i,s,n,a,r){const o=this.#$r(i,s,n,a);for(let n=0;n<8;n++){const a=(n+o+r+16)%8;if(0!==t[(i+this.#Wr[2*a])*e+(s+this.#Wr[2*a+1])])return a}return-1}static#qr(t,e,i,s){const n=t.length,a=new Int32Array(n);for(let e=0;e=1&&0===a[s+1])){1!==n&&(r=Math.abs(n));continue}o+=1;d+=1;n>1&&(r=n)}const c=[i,t],u=d===i+1,p={isHole:u,points:c,id:o,parent:0};l.push(p);let g;for(const t of l)if(t.id===r){g=t;break}g?g.isHole?p.parent=u?g.parent:r:p.parent=u?r:g.parent:p.parent=u?r:0;const f=this.#Vr(a,e,t,i,h,d,0);if(-1===f){a[s]=-o;1!==a[s]&&(r=Math.abs(a[s]));continue}let m=this.#Wr[2*f],b=this.#Wr[2*f+1];const A=t+m,w=i+b;h=A;d=w;let y=t,v=i;for(;;){const n=this.#jr(a,e,y,v,h,d,1);m=this.#Wr[2*n];b=this.#Wr[2*n+1];const l=y+m,u=v+b;c.push(u,l);const p=y*e+v;0===a[p+1]?a[p]=-o:1===a[p]&&(a[p]=o);if(l===t&&u===i&&y===A&&v===w){1!==a[s]&&(r=Math.abs(a[s]));break}h=y;d=v;y=l;v=u}}}return l}static#Xr(t,e,i,s){if(i-e<=4){for(let n=e;ny){v=s;y=e}}if(y>(l*w)**2){this.#Xr(t,e,v+2,s);this.#Xr(t,v,i,s)}else s.push(n,a)}static#Kr(t){const e=[],i=t.length;this.#Xr(t,0,i,e);e.push(t[i-2],t[i-1]);return e.length<=4?null:e}static#Yr(t,e,i,s,n,a){const r=new Float32Array(a**2),o=-2*s**2,l=a>>1;for(let t=0;t=i))for(let i=0;i=e)continue;const p=t[u*e+s],f=r[o*a+i]*h[Math.abs(p-d)];c+=p*f;g+=f}}p[u[o]=Math.round(c/g)]++}return[u,p]}static#Qr(t){const e=new Uint32Array(256);for(const i of t)e[i]++;return e}static#Jr(t){const e=t.length,i=new Uint8ClampedArray(e>>2);let s=-1/0,n=1/0;for(let e=0,a=i.length;es&&(s=a);a0!==t));let a=n,r=n;for(e=n;e<256;e++){const n=t[e];if(n>i){if(e-a>s){s=e-a;r=e-1}i=n;a=e}}for(e=r-1;e>=0&&!(t[e]>t[e+1]);e--);return e}static#to(t){const e=t,{width:i,height:s}=t,{maxDim:n}=this.#Gr;let a=i,r=s;if(i>n||s>n){let o=i,l=s,h=Math.log2(Math.max(i,s)/n);const d=Math.floor(h);h=h===d?d-1:d;for(let i=0;in&&(a=Math.ceil(a/2));r>n&&(r=Math.ceil(r/2));const i=new OffscreenCanvas(a,r);i.getContext("2d").drawImage(t,0,0,o,l,0,0,a,r);o=a;l=r;t!==e&&t.close();t=i.transferToImageBitmap()}const c=Math.min(n/a,n/r);a=Math.round(a*c);r=Math.round(r*c)}const o=new OffscreenCanvas(a,r).getContext("2d",{willReadFrequently:!0});o.filter="grayscale(1)";o.drawImage(t,0,0,t.width,t.height,0,0,a,r);const l=o.getImageData(0,0,a,r).data;return[this.#Jr(l),a,r]}static extractContoursFromText(t,{fontFamily:e,fontStyle:i,fontWeight:s},n,a,r,o){let l=new OffscreenCanvas(1,1),h=l.getContext("2d",{alpha:!1});const d=h.font=`${i} ${s} 200px ${e}`,{actualBoundingBoxLeft:c,actualBoundingBoxRight:u,actualBoundingBoxAscent:p,actualBoundingBoxDescent:g,fontBoundingBoxAscent:f,fontBoundingBoxDescent:m,width:b}=h.measureText(t),A=1.5,w=Math.ceil(Math.max(Math.abs(c)+Math.abs(u)||0,b)*A),y=Math.ceil(Math.max(Math.abs(p)+Math.abs(g)||200,Math.abs(f)+Math.abs(m)||200)*A);l=new OffscreenCanvas(w,y);h=l.getContext("2d",{alpha:!0,willReadFrequently:!0});h.font=d;h.filter="grayscale(1)";h.fillStyle="white";h.fillRect(0,0,w,y);h.fillStyle="black";h.fillText(t,.5*w/2,1.5*y/2);const v=this.#Jr(h.getImageData(0,0,w,y).data),_=this.#Qr(v),x=this.#Zr(_),E=this.#qr(v,w,y,x);return this.processDrawnLines({lines:{curves:E,width:w,height:y},pageWidth:n,pageHeight:a,rotation:r,innerMargin:o,mustSmooth:!0,areContours:!0})}static process(t,e,i,s,n){const[a,r,o]=this.#to(t),[l,h]=this.#Yr(a,r,o,Math.hypot(r,o)*this.#Gr.sigmaSFactor,this.#Gr.sigmaR,this.#Gr.kernelSize),d=this.#Zr(h),c=this.#qr(l,r,o,d);return this.processDrawnLines({lines:{curves:c,width:r,height:o},pageWidth:e,pageHeight:i,rotation:s,innerMargin:n,mustSmooth:!0,areContours:!0})}static processDrawnLines({lines:t,pageWidth:e,pageHeight:i,rotation:s,innerMargin:n,mustSmooth:a,areContours:r}){s%180!=0&&([e,i]=[i,e]);const{curves:o,width:l,height:h}=t,d=t.thickness??0,c=[],u=Math.min(e/l,i/h),p=u/e,g=u/i,f=[];for(const{points:t}of o){const e=a?this.#Kr(t):t;if(!e)continue;f.push(e);const i=e.length,s=new Float32Array(i),n=new Float32Array(3*(2===i?2:i-2));c.push({line:n,points:s});if(2===i){s[0]=e[0]*p;s[1]=e[1]*g;n.set([NaN,NaN,NaN,NaN,s[0],s[1]],0);continue}let[r,o,l,h]=e;r*=p;o*=g;l*=p;h*=g;s.set([r,o,l,h],0);n.set([NaN,NaN,NaN,NaN,r,o],0);for(let t=4;t=-128&&o<=127?Int8Array:r>=-32768&&o<=32767?Int16Array:Int32Array;const h=t.length,d=8+3*h,c=new Uint32Array(d);let u=0;c[u++]=d*Uint32Array.BYTES_PER_ELEMENT+(l-2*h)*a.BYTES_PER_ELEMENT;c[u++]=0;c[u++]=s;c[u++]=n;c[u++]=e?0:1;c[u++]=Math.max(0,Math.floor(i??0));c[u++]=h;c[u++]=a.BYTES_PER_ELEMENT;for(const e of t){c[u++]=e.length-2;c[u++]=e[0];c[u++]=e[1]}const p=new CompressionStream("deflate-raw"),g=p.writable.getWriter();await g.ready;g.write(c);const f=a.prototype.constructor;for(const e of t){const t=new f(e.length-2);for(let i=2,s=e.length;i{await n.ready;await n.close()})).catch((()=>{}));let a=null,r=0;for await(const t of i){a||=new Uint8Array(new Uint32Array(t.buffer,0,4)[0]);a.set(t,r);r+=t.length}const o=new Uint32Array(a.buffer,0,a.length>>2),l=o[1];if(0!==l)throw new Error(`Invalid version: ${l}`);const h=o[2],d=o[3],c=0===o[4],u=o[5],p=o[6],g=o[7],f=[],m=(8+3*p)*Uint32Array.BYTES_PER_ELEMENT;let b;switch(g){case Int8Array.BYTES_PER_ELEMENT:b=new Int8Array(a.buffer,m);break;case Int16Array.BYTES_PER_ELEMENT:b=new Int16Array(a.buffer,m);break;case Int32Array.BYTES_PER_ELEMENT:b=new Int32Array(a.buffer,m)}r=0;for(let t=0;t{e?.updateEditSignatureButton(t)}))}getSignaturePreview(){const{newCurves:t,areContours:e,thickness:i,width:s,height:n}=this.#so,a=Math.max(s,n);return{areContours:e,outline:SignatureExtractor.processDrawnLines({lines:{curves:t.map((t=>({points:t}))),thickness:i,width:s,height:n},pageWidth:a,pageHeight:a,rotation:0,innerMargin:0,mustSmooth:!1,areContours:e}).outline}}async addEditToolbar(){const t=await super.addEditToolbar();if(!t)return null;if(this._uiManager.signatureManager&&null!==this.#io){await t.addEditSignatureButton(this._uiManager.signatureManager,this.#no,this.#io);t.show()}return t}addSignature(t,e,i,s){const{x:n,y:a}=this,{outline:r}=this.#so=t;this.#eo=r instanceof ContourDrawOutline;this.#io=i;this.div.setAttribute("data-l10n-args",JSON.stringify({description:i}));let o;if(this.#eo)o=SignatureEditor.getDefaultDrawingOptions();else{o=SignatureEditor._defaultDrawnSignatureOptions.clone();o.updateProperties({"stroke-width":r.thickness})}this._addOutlines({drawOutlines:r,drawingOptions:o});const[l,h]=this.parentDimensions,[,d]=this.pageDimensions;let c=e/d;c=c>=1?.5:c;this.width*=c/this.height;if(this.width>=1){c*=.9/this.width;this.width=.9}this.height=c;this.setDims(l*this.width,h*this.height);this.x=n;this.y=a;this.center();this._onResized();this.onScaleChanging();this.rotate();this._uiManager.addToAnnotationStorage(this);this.setUuid(s);this._reportTelemetry({action:"pdfjs.signature.inserted",data:{hasBeenSaved:!!s,hasDescription:!!i}});this.div.hidden=!1}getFromImage(t){const{rawDims:{pageWidth:e,pageHeight:i},rotation:s}=this.parent.viewport;return SignatureExtractor.process(t,e,i,s,SignatureEditor._INNER_MARGIN)}getFromText(t,e){const{rawDims:{pageWidth:i,pageHeight:s},rotation:n}=this.parent.viewport;return SignatureExtractor.extractContoursFromText(t,e,i,s,n,SignatureEditor._INNER_MARGIN)}getDrawnSignature(t){const{rawDims:{pageWidth:e,pageHeight:i},rotation:s}=this.parent.viewport;return SignatureExtractor.processDrawnLines({lines:t,pageWidth:e,pageHeight:i,rotation:s,innerMargin:SignatureEditor._INNER_MARGIN,mustSmooth:!1,areContours:!1})}createDrawingOptions({areContours:t,thickness:e}){if(t)this._drawingOptions=SignatureEditor.getDefaultDrawingOptions();else{this._drawingOptions=SignatureEditor._defaultDrawnSignatureOptions.clone();this._drawingOptions.updateProperties({"stroke-width":e})}}serialize(t=!1){if(this.isEmpty())return null;const{lines:e,points:i,rect:s}=this.serializeDraw(t),{_drawingOptions:{"stroke-width":n}}=this,a={annotationType:u.SIGNATURE,isSignature:!0,areContours:this.#eo,color:[0,0,0],thickness:this.#eo?0:n,pageIndex:this.pageIndex,rect:s,rotation:this.rotation,structTreeParentId:this._structTreeParentId};if(t){a.paths={lines:e,points:i};a.uuid=this.#no;a.isCopy=!0}else a.lines=e;this.#io&&(a.accessibilityData={type:"Figure",alt:this.#io});return a}static deserializeDraw(t,e,i,s,n,a){return a.areContours?ContourDrawOutline.deserialize(t,e,i,s,n,a):InkDrawOutline.deserialize(t,e,i,s,n,a)}static async deserialize(t,e,i){const s=await super.deserialize(t,e,i);s.#eo=t.areContours;s.#io=t.accessibilityData?.alt||"";s.#no=t.uuid;return s}}class StampEditor extends AnnotationEditor{#ao=null;#ro=null;#oo=null;#lo=null;#ho=null;#do="";#co=null;#uo=!1;#po=null;#go=!1;#fo=!1;static _type="stamp";static _editorType=u.STAMP;constructor(t){super({...t,name:"stampEditor"});this.#lo=t.bitmapUrl;this.#ho=t.bitmapFile;this.defaultL10nId="pdfjs-editor-stamp-editor"}static initialize(t,e){AnnotationEditor.initialize(t,e)}static isHandlingMimeForPasting(t){return G.includes(t)}static paste(t,e){e.pasteEditor(u.STAMP,{bitmapFile:t.getAsFile()})}altTextFinish(){this._uiManager.useNewAltTextFlow&&(this.div.hidden=!1);super.altTextFinish()}get telemetryFinalData(){return{type:"stamp",hasAltText:!!this.altTextData?.altText}}static computeTelemetryFinalData(t){const e=t.get("hasAltText");return{hasAltText:e.get(!0)??0,hasNoAltText:e.get(!1)??0}}#mo(t,e=!1){if(t){this.#ao=t.bitmap;if(!e){this.#ro=t.id;this.#go=t.isSvg}t.file&&(this.#do=t.file.name);this.#bo()}else this.remove()}#Ao(){this.#oo=null;this._uiManager.enableWaiting(!1);if(this.#co)if(this._uiManager.useNewAltTextWhenAddingImage&&this._uiManager.useNewAltTextFlow&&this.#ao){this._editToolbar.hide();this._uiManager.editAltText(this,!0)}else{if(!this._uiManager.useNewAltTextWhenAddingImage&&this._uiManager.useNewAltTextFlow&&this.#ao){this._reportTelemetry({action:"pdfjs.image.image_added",data:{alt_text_modal:!1,alt_text_type:"empty"}});try{this.mlGuessAltText()}catch{}}this.div.focus()}}async mlGuessAltText(t=null,e=!0){if(this.hasAltTextData())return null;const{mlManager:i}=this._uiManager;if(!i)throw new Error("No ML.");if(!await i.isEnabledFor("altText"))throw new Error("ML isn't enabled for alt text.");const{data:s,width:n,height:a}=t||this.copyCanvas(null,null,!0).imageData,r=await i.guess({name:"altText",request:{data:s,width:n,height:a,channels:s.length/(n*a)}});if(!r)throw new Error("No response from the AI service.");if(r.error)throw new Error("Error from the AI service.");if(r.cancel)return null;if(!r.output)throw new Error("No valid response from the AI service.");const o=r.output;await this.setGuessedAltText(o);e&&!this.hasAltTextData()&&(this.altTextData={alt:o,decorative:!1});return o}#wo(){if(this.#ro){this._uiManager.enableWaiting(!0);this._uiManager.imageManager.getFromId(this.#ro).then((t=>this.#mo(t,!0))).finally((()=>this.#Ao()));return}if(this.#lo){const t=this.#lo;this.#lo=null;this._uiManager.enableWaiting(!0);this.#oo=this._uiManager.imageManager.getFromUrl(t).then((t=>this.#mo(t))).finally((()=>this.#Ao()));return}if(this.#ho){const t=this.#ho;this.#ho=null;this._uiManager.enableWaiting(!0);this.#oo=this._uiManager.imageManager.getFromFile(t).then((t=>this.#mo(t))).finally((()=>this.#Ao()));return}const t=document.createElement("input");t.type="file";t.accept=G.join(",");const e=this._uiManager._signal;this.#oo=new Promise((i=>{t.addEventListener("change",(async()=>{if(t.files&&0!==t.files.length){this._uiManager.enableWaiting(!0);const e=await this._uiManager.imageManager.getFromFile(t.files[0]);this._reportTelemetry({action:"pdfjs.image.image_selected",data:{alt_text_modal:this._uiManager.useNewAltTextFlow}});this.#mo(e)}else this.remove();i()}),{signal:e});t.addEventListener("cancel",(()=>{this.remove();i()}),{signal:e})})).finally((()=>this.#Ao()));t.click()}remove(){if(this.#ro){this.#ao=null;this._uiManager.imageManager.deleteId(this.#ro);this.#co?.remove();this.#co=null;if(this.#po){clearTimeout(this.#po);this.#po=null}}super.remove()}rebuild(){if(this.parent){super.rebuild();if(null!==this.div){this.#ro&&null===this.#co&&this.#wo();this.isAttachedToDOM||this.parent.add(this)}}else this.#ro&&this.#wo()}onceAdded(t){this._isDraggable=!0;t&&this.div.focus()}isEmpty(){return!(this.#oo||this.#ao||this.#lo||this.#ho||this.#ro||this.#uo)}get isResizable(){return!0}render(){if(this.div)return this.div;let t,e;if(this._isCopy){t=this.x;e=this.y}super.render();this.div.hidden=!0;this.addAltTextButton();this.#uo||(this.#ao?this.#bo():this.#wo());this._isCopy&&this._moveAfterPaste(t,e);this._uiManager.addShouldRescale(this);return this.div}setCanvas(t,e){const{id:i,bitmap:s}=this._uiManager.imageManager.getFromCanvas(t,e);e.remove();if(i&&this._uiManager.imageManager.isValidId(i)){this.#ro=i;s&&(this.#ao=s);this.#uo=!1;this.#bo()}}_onResized(){this.onScaleChanging()}onScaleChanging(){if(!this.parent)return;null!==this.#po&&clearTimeout(this.#po);this.#po=setTimeout((()=>{this.#po=null;this.#yo()}),200)}#bo(){const{div:t}=this;let{width:e,height:i}=this.#ao;const[s,n]=this.pageDimensions,a=.75;if(this.width){e=this.width*s;i=this.height*n}else if(e>a*s||i>a*n){const t=Math.min(a*s/e,a*n/i);e*=t;i*=t}const[r,o]=this.parentDimensions;this.setDims(e*r/s,i*o/n);this._uiManager.enableWaiting(!1);const l=this.#co=document.createElement("canvas");l.setAttribute("role","img");this.addContainer(l);this.width=e/s;this.height=i/n;this._initialOptions?.isCentered?this.center():this.fixAndSetPosition();this._initialOptions=null;this._uiManager.useNewAltTextWhenAddingImage&&this._uiManager.useNewAltTextFlow&&!this.annotationElementId||(t.hidden=!1);this.#yo();if(!this.#fo){this.parent.addUndoableEditor(this);this.#fo=!0}this._reportTelemetry({action:"inserted_image"});this.#do&&this.div.setAttribute("aria-description",this.#do)}copyCanvas(t,e,i=!1){t||(t=224);const{width:s,height:n}=this.#ao,a=new OutputScale;let r=this.#ao,o=s,l=n,h=null;if(e){if(s>e||n>e){const t=Math.min(e/s,e/n);o=Math.floor(s*t);l=Math.floor(n*t)}h=document.createElement("canvas");const t=h.width=Math.ceil(o*a.sx),i=h.height=Math.ceil(l*a.sy);this.#go||(r=this.#vo(t,i));const d=h.getContext("2d");d.filter=this._uiManager.hcmFilter;let c="white",u="#cfcfd8";if("none"!==this._uiManager.hcmFilter)u="black";else if(window.matchMedia?.("(prefers-color-scheme: dark)").matches){c="#8f8f9d";u="#42414d"}const p=15,g=p*a.sx,f=p*a.sy,m=new OffscreenCanvas(2*g,2*f),b=m.getContext("2d");b.fillStyle=c;b.fillRect(0,0,2*g,2*f);b.fillStyle=u;b.fillRect(0,0,g,f);b.fillRect(g,f,g,f);d.fillStyle=d.createPattern(m,"repeat");d.fillRect(0,0,t,i);d.drawImage(r,0,0,r.width,r.height,0,0,t,i)}let d=null;if(i){let e,i;if(a.symmetric&&r.widtht||n>t){const a=Math.min(t/s,t/n);e=Math.floor(s*a);i=Math.floor(n*a);this.#go||(r=this.#vo(e,i))}}const o=new OffscreenCanvas(e,i).getContext("2d",{willReadFrequently:!0});o.drawImage(r,0,0,r.width,r.height,0,0,e,i);d={width:e,height:i,data:o.getImageData(0,0,e,i).data}}return{canvas:h,width:o,height:l,imageData:d}}#vo(t,e){const{width:i,height:s}=this.#ao;let n=i,a=s,r=this.#ao;for(;n>2*t||a>2*e;){const i=n,s=a;n>2*t&&(n=n>=16384?Math.floor(n/2)-1:Math.ceil(n/2));a>2*e&&(a=a>=16384?Math.floor(a/2)-1:Math.ceil(a/2));const o=new OffscreenCanvas(n,a);o.getContext("2d").drawImage(r,0,0,i,s,0,0,n,a);r=o.transferToImageBitmap()}return r}#yo(){const[t,e]=this.parentDimensions,{width:i,height:s}=this,n=new OutputScale,a=Math.ceil(i*t*n.sx),r=Math.ceil(s*e*n.sy),o=this.#co;if(!o||o.width===a&&o.height===r)return;o.width=a;o.height=r;const l=this.#go?this.#ao:this.#vo(a,r),h=o.getContext("2d");h.filter=this._uiManager.hcmFilter;h.drawImage(l,0,0,l.width,l.height,0,0,a,r)}#_o(t){if(t){if(this.#go){const t=this._uiManager.imageManager.getSvgUrl(this.#ro);if(t)return t}const t=document.createElement("canvas");({width:t.width,height:t.height}=this.#ao);t.getContext("2d").drawImage(this.#ao,0,0);return t.toDataURL()}if(this.#go){const[t,e]=this.pageDimensions,i=Math.round(this.width*t*PixelsPerInch.PDF_TO_CSS_UNITS),s=Math.round(this.height*e*PixelsPerInch.PDF_TO_CSS_UNITS),n=new OffscreenCanvas(i,s);n.getContext("2d").drawImage(this.#ao,0,0,this.#ao.width,this.#ao.height,0,0,i,s);return n.transferToImageBitmap()}return structuredClone(this.#ao)}static async deserialize(t,e,i){let s=null,n=!1;if(t instanceof StampAnnotationElement){const{data:{rect:a,rotation:r,id:o,structParent:l,popupRef:h},container:d,parent:{page:{pageNumber:c}},canvas:p}=t;let g,f;if(p){delete t.canvas;({id:g,bitmap:f}=i.imageManager.getFromCanvas(d.id,p));p.remove()}else{n=!0;t._hasNoCanvas=!0}const m=(await e._structTree.getAriaAttributes(`${U}${o}`))?.get("aria-label")||"";s=t={annotationType:u.STAMP,bitmapId:g,bitmap:f,pageIndex:c-1,rect:a.slice(0),rotation:r,id:o,deleted:!1,accessibilityData:{decorative:!1,altText:m},isSvg:!1,structParent:l,popupRef:h}}const a=await super.deserialize(t,e,i),{rect:r,bitmap:o,bitmapUrl:l,bitmapId:h,isSvg:d,accessibilityData:c}=t;if(n){i.addMissingCanvas(t.id,a);a.#uo=!0}else if(h&&i.imageManager.isValidId(h)){a.#ro=h;o&&(a.#ao=o)}else a.#lo=l;a.#go=d;const[p,g]=a.pageDimensions;a.width=(r[2]-r[0])/p;a.height=(r[3]-r[1])/g;a.annotationElementId=t.id||null;c&&(a.altTextData=c);a._initialData=s;a.#fo=!!s;return a}serialize(t=!1,e=null){if(this.isEmpty())return null;if(this.deleted)return this.serializeDeleted();const i={annotationType:u.STAMP,bitmapId:this.#ro,pageIndex:this.pageIndex,rect:this.getRect(0,0),rotation:this.rotation,isSvg:this.#go,structTreeParentId:this._structTreeParentId};if(t){i.bitmapUrl=this.#_o(!0);i.accessibilityData=this.serializeAltText(!0);i.isCopy=!0;return i}const{decorative:s,altText:n}=this.serializeAltText(!1);!s&&n&&(i.accessibilityData={type:"Figure",alt:n});if(this.annotationElementId){const t=this.#Vn(i);if(t.isSame)return null;t.isSameAltText?delete i.accessibilityData:i.accessibilityData.structParent=this._initialData.structParent??-1}i.id=this.annotationElementId;if(null===e)return i;e.stamps||=new Map;const a=this.#go?(i.rect[2]-i.rect[0])*(i.rect[3]-i.rect[1]):null;if(e.stamps.has(this.#ro)){if(this.#go){const t=e.stamps.get(this.#ro);if(a>t.area){t.area=a;t.serialized.bitmap.close();t.serialized.bitmap=this.#_o(!1)}}}else{e.stamps.set(this.#ro,{area:a,serialized:i});i.bitmap=this.#_o(!1)}return i}#Vn(t){const{pageIndex:e,accessibilityData:{altText:i}}=this._initialData,s=t.pageIndex===e,n=(t.accessibilityData?.alt||"")===i;return{isSame:!this._hasBeenMoved&&!this._hasBeenResized&&s&&n,isSameAltText:n}}renderAnnotationElement(t){t.updateEdited({rect:this.getRect(0,0)});return null}}class AnnotationEditorLayer{#Mn;#xo=!1;#Eo=null;#So=null;#Co=null;#To=new Map;#Mo=!1;#Do=!1;#Po=!1;#ko=null;#Io=null;#Ro=null;#Fo=null;#m;static _initialized=!1;static#G=new Map([FreeTextEditor,InkEditor,StampEditor,HighlightEditor,SignatureEditor].map((t=>[t._editorType,t])));constructor({uiManager:t,pageIndex:e,div:i,structTreeLayer:s,accessibilityManager:n,annotationLayer:a,drawLayer:r,textLayer:o,viewport:l,l10n:h}){const d=[...AnnotationEditorLayer.#G.values()];if(!AnnotationEditorLayer._initialized){AnnotationEditorLayer._initialized=!0;for(const e of d)e.initialize(h,t)}t.registerEditorTypes(d);this.#m=t;this.pageIndex=e;this.div=i;this.#Mn=n;this.#Eo=a;this.viewport=l;this.#Ro=o;this.drawLayer=r;this._structTree=s;this.#m.addLayer(this)}get isEmpty(){return 0===this.#To.size}get isInvisible(){return this.isEmpty&&this.#m.getMode()===u.NONE}updateToolbar(t){this.#m.updateToolbar(t)}updateMode(t=this.#m.getMode()){this.#Lo();switch(t){case u.NONE:this.disableTextSelection();this.togglePointerEvents(!1);this.toggleAnnotationLayerPointerEvents(!0);this.disableClick();return;case u.INK:this.disableTextSelection();this.togglePointerEvents(!0);this.enableClick();break;case u.HIGHLIGHT:this.enableTextSelection();this.togglePointerEvents(!1);this.disableClick();break;default:this.disableTextSelection();this.togglePointerEvents(!0);this.enableClick()}this.toggleAnnotationLayerPointerEvents(!1);const{classList:e}=this.div;for(const i of AnnotationEditorLayer.#G.values())e.toggle(`${i._type}Editing`,t===i._editorType);this.div.hidden=!1}hasTextLayer(t){return t===this.#Ro?.div}setEditingState(t){this.#m.setEditingState(t)}addCommands(t){this.#m.addCommands(t)}cleanUndoStack(t){this.#m.cleanUndoStack(t)}toggleDrawing(t=!1){this.div.classList.toggle("drawing",!t)}togglePointerEvents(t=!1){this.div.classList.toggle("disabled",!t)}toggleAnnotationLayerPointerEvents(t=!1){this.#Eo?.div.classList.toggle("disabled",!t)}async enable(){this.#Po=!0;this.div.tabIndex=0;this.togglePointerEvents(!0);const t=new Set;for(const e of this.#To.values()){e.enableEditing();e.show(!0);if(e.annotationElementId){this.#m.removeChangedExistingAnnotation(e);t.add(e.annotationElementId)}}if(!this.#Eo){this.#Po=!1;return}const e=this.#Eo.getEditableAnnotations();for(const i of e){i.hide();if(this.#m.isDeletedAnnotationElement(i.data.id))continue;if(t.has(i.data.id))continue;const e=await this.deserialize(i);if(e){this.addOrRebuild(e);e.enableEditing()}}this.#Po=!1}disable(){this.#Do=!0;this.div.tabIndex=-1;this.togglePointerEvents(!1);const t=new Map,e=new Map;for(const i of this.#To.values()){i.disableEditing();if(i.annotationElementId)if(null===i.serialize()){e.set(i.annotationElementId,i);this.getEditableAnnotation(i.annotationElementId)?.show();i.remove()}else t.set(i.annotationElementId,i)}if(this.#Eo){const i=this.#Eo.getEditableAnnotations();for(const s of i){const{id:i}=s.data;if(this.#m.isDeletedAnnotationElement(i))continue;let n=e.get(i);if(n){n.resetAnnotationElement(s);n.show(!1);s.show()}else{n=t.get(i);if(n){this.#m.addChangedExistingAnnotation(n);n.renderAnnotationElement(s)&&n.show(!1)}s.show()}}}this.#Lo();this.isEmpty&&(this.div.hidden=!0);const{classList:i}=this.div;for(const t of AnnotationEditorLayer.#G.values())i.remove(`${t._type}Editing`);this.disableTextSelection();this.toggleAnnotationLayerPointerEvents(!0);this.#Do=!1}getEditableAnnotation(t){return this.#Eo?.getEditableAnnotation(t)||null}setActiveEditor(t){this.#m.getActive()!==t&&this.#m.setActiveEditor(t)}enableTextSelection(){this.div.tabIndex=-1;if(this.#Ro?.div&&!this.#Fo){this.#Fo=new AbortController;const t=this.#m.combinedSignal(this.#Fo);this.#Ro.div.addEventListener("pointerdown",this.#Oo.bind(this),{signal:t});this.#Ro.div.classList.add("highlighting")}}disableTextSelection(){this.div.tabIndex=0;if(this.#Ro?.div&&this.#Fo){this.#Fo.abort();this.#Fo=null;this.#Ro.div.classList.remove("highlighting")}}#Oo(t){this.#m.unselectAll();const{target:e}=t;if(e===this.#Ro.div||("img"===e.getAttribute("role")||e.classList.contains("endOfContent"))&&this.#Ro.div.contains(e)){const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)return;this.#m.showAllEditors("highlight",!0,!0);this.#Ro.div.classList.add("free");this.toggleDrawing();HighlightEditor.startHighlighting(this,"ltr"===this.#m.direction,{target:this.#Ro.div,x:t.x,y:t.y});this.#Ro.div.addEventListener("pointerup",(()=>{this.#Ro.div.classList.remove("free");this.toggleDrawing(!0)}),{once:!0,signal:this.#m._signal});t.preventDefault()}}enableClick(){if(this.#So)return;this.#So=new AbortController;const t=this.#m.combinedSignal(this.#So);this.div.addEventListener("pointerdown",this.pointerdown.bind(this),{signal:t});const e=this.pointerup.bind(this);this.div.addEventListener("pointerup",e,{signal:t});this.div.addEventListener("pointercancel",e,{signal:t})}disableClick(){this.#So?.abort();this.#So=null}attach(t){this.#To.set(t.id,t);const{annotationElementId:e}=t;e&&this.#m.isDeletedAnnotationElement(e)&&this.#m.removeDeletedAnnotationElement(t)}detach(t){this.#To.delete(t.id);this.#Mn?.removePointerInTextLayer(t.contentDiv);!this.#Do&&t.annotationElementId&&this.#m.addDeletedAnnotationElement(t)}remove(t){this.detach(t);this.#m.removeEditor(t);t.div.remove();t.isAttachedToDOM=!1}changeParent(t){if(t.parent!==this){if(t.parent&&t.annotationElementId){this.#m.addDeletedAnnotationElement(t.annotationElementId);AnnotationEditor.deleteAnnotationElement(t);t.annotationElementId=null}this.attach(t);t.parent?.detach(t);t.setParent(this);if(t.div&&t.isAttachedToDOM){t.div.remove();this.div.append(t.div)}}}add(t){if(t.parent!==this||!t.isAttachedToDOM){this.changeParent(t);this.#m.addEditor(t);this.attach(t);if(!t.isAttachedToDOM){const e=t.render();this.div.append(e);t.isAttachedToDOM=!0}t.fixAndSetPosition();t.onceAdded(!this.#Po);this.#m.addToAnnotationStorage(t);t._reportTelemetry(t.telemetryInitialData)}}moveEditorInDOM(t){if(!t.isAttachedToDOM)return;const{activeElement:e}=document;if(t.div.contains(e)&&!this.#Co){t._focusEventsAllowed=!1;this.#Co=setTimeout((()=>{this.#Co=null;if(t.div.contains(document.activeElement))t._focusEventsAllowed=!0;else{t.div.addEventListener("focusin",(()=>{t._focusEventsAllowed=!0}),{once:!0,signal:this.#m._signal});e.focus()}}),0)}t._structTreeParentId=this.#Mn?.moveElementInDOM(this.div,t.div,t.contentDiv,!0)}addOrRebuild(t){if(t.needsToBeRebuilt()){t.parent||=this;t.rebuild();t.show()}else this.add(t)}addUndoableEditor(t){this.addCommands({cmd:()=>t._uiManager.rebuild(t),undo:()=>{t.remove()},mustExec:!1})}getNextId(){return this.#m.getId()}get#No(){return AnnotationEditorLayer.#G.get(this.#m.getMode())}combinedSignal(t){return this.#m.combinedSignal(t)}#Bo(t){const e=this.#No;return e?new e.prototype.constructor(t):null}canCreateNewEmptyEditor(){return this.#No?.canCreateNewEmptyEditor()}async pasteEditor(t,e){this.#m.updateToolbar(t);await this.#m.updateMode(t);const{offsetX:i,offsetY:s}=this.#Ho(),n=this.getNextId(),a=this.#Bo({parent:this,id:n,x:i,y:s,uiManager:this.#m,isCentered:!0,...e});a&&this.add(a)}async deserialize(t){return await(AnnotationEditorLayer.#G.get(t.annotationType??t.annotationEditorType)?.deserialize(t,this,this.#m))||null}createAndAddNewEditor(t,e,i={}){const s=this.getNextId(),n=this.#Bo({parent:this,id:s,x:t.offsetX,y:t.offsetY,uiManager:this.#m,isCentered:e,...i});n&&this.add(n);return n}#Ho(){const{x:t,y:e,width:i,height:s}=this.div.getBoundingClientRect(),n=Math.max(0,t),a=Math.max(0,e),r=(n+Math.min(window.innerWidth,t+i))/2-t,o=(a+Math.min(window.innerHeight,e+s))/2-e,[l,h]=this.viewport.rotation%180==0?[r,o]:[o,r];return{offsetX:l,offsetY:h}}addNewEditor(t={}){this.createAndAddNewEditor(this.#Ho(),!0,t)}setSelected(t){this.#m.setSelected(t)}toggleSelected(t){this.#m.toggleSelected(t)}unselect(t){this.#m.unselect(t)}pointerup(t){const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)return;if(t.target!==this.div)return;if(!this.#Mo)return;this.#Mo=!1;if(this.#No?.isDrawer&&this.#No.supportMultipleDrawings)return;if(!this.#xo){this.#xo=!0;return}const i=this.#m.getMode();i!==u.STAMP&&i!==u.SIGNATURE?this.createAndAddNewEditor(t,!1):this.#m.unselectAll()}pointerdown(t){this.#m.getMode()===u.HIGHLIGHT&&this.enableTextSelection();if(this.#Mo){this.#Mo=!1;return}const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)return;if(t.target!==this.div)return;this.#Mo=!0;if(this.#No?.isDrawer){this.startDrawingSession(t);return}const i=this.#m.getActive();this.#xo=!i||i.isEmpty()}startDrawingSession(t){this.div.focus({preventScroll:!0});if(this.#ko){this.#No.startDrawing(this,this.#m,!1,t);return}this.#m.setCurrentDrawingSession(this);this.#ko=new AbortController;const e=this.#m.combinedSignal(this.#ko);this.div.addEventListener("blur",(({relatedTarget:t})=>{if(t&&!this.div.contains(t)){this.#Io=null;this.commitOrRemove()}}),{signal:e});this.#No.startDrawing(this,this.#m,!1,t)}pause(t){if(t){const{activeElement:t}=document;this.div.contains(t)&&(this.#Io=t)}else this.#Io&&setTimeout((()=>{this.#Io?.focus();this.#Io=null}),0)}endDrawingSession(t=!1){if(!this.#ko)return null;this.#m.setCurrentDrawingSession(null);this.#ko.abort();this.#ko=null;this.#Io=null;return this.#No.endDrawing(t)}findNewParent(t,e,i){const s=this.#m.findParent(e,i);if(null===s||s===this)return!1;s.changeParent(t);return!0}commitOrRemove(){if(this.#ko){this.endDrawingSession();return!0}return!1}onScaleChanging(){this.#ko&&this.#No.onScaleChangingWhenDrawing(this)}destroy(){this.commitOrRemove();if(this.#m.getActive()?.parent===this){this.#m.commitOrRemove();this.#m.setActiveEditor(null)}if(this.#Co){clearTimeout(this.#Co);this.#Co=null}for(const t of this.#To.values()){this.#Mn?.removePointerInTextLayer(t.contentDiv);t.setParent(null);t.isAttachedToDOM=!1;t.div.remove()}this.div=null;this.#To.clear();this.#m.removeLayer(this)}#Lo(){for(const t of this.#To.values())t.isEmpty()&&t.remove()}render({viewport:t}){this.viewport=t;setLayerDimensions(this.div,t);for(const t of this.#m.getEditors(this.pageIndex)){this.add(t);t.rebuild()}this.updateMode()}update({viewport:t}){this.#m.commitOrRemove();this.#Lo();const e=this.viewport.rotation,i=t.rotation;this.viewport=t;setLayerDimensions(this.div,{rotation:i});if(e!==i)for(const t of this.#To.values())t.rotate(i)}get pageDimensions(){const{pageWidth:t,pageHeight:e}=this.viewport.rawDims;return[t,e]}get scale(){return this.#m.viewParameters.realScale}}class DrawLayer{#rn=null;#Uo=new Map;#zo=new Map;static#y=0;constructor({pageIndex:t}){this.pageIndex=t}setParent(t){if(this.#rn){if(this.#rn!==t){if(this.#Uo.size>0)for(const e of this.#Uo.values()){e.remove();t.append(e)}this.#rn=t}}else this.#rn=t}static get _svgFactory(){return shadow(this,"_svgFactory",new DOMSVGFactory)}static#Go(t,[e,i,s,n]){const{style:a}=t;a.top=100*i+"%";a.left=100*e+"%";a.width=100*s+"%";a.height=100*n+"%"}#$o(){const t=DrawLayer._svgFactory.create(1,1,!0);this.#rn.append(t);t.setAttribute("aria-hidden",!0);return t}#Wo(t,e){const i=DrawLayer._svgFactory.createElement("clipPath");t.append(i);const s=`clip_${e}`;i.setAttribute("id",s);i.setAttribute("clipPathUnits","objectBoundingBox");const n=DrawLayer._svgFactory.createElement("use");i.append(n);n.setAttribute("href",`#${e}`);n.classList.add("clip");return s}#Vo(t,e){for(const[i,s]of Object.entries(e))null===s?t.removeAttribute(i):t.setAttribute(i,s)}draw(t,e=!1,i=!1){const s=DrawLayer.#y++,n=this.#$o(),a=DrawLayer._svgFactory.createElement("defs");n.append(a);const r=DrawLayer._svgFactory.createElement("path");a.append(r);const o=`path_p${this.pageIndex}_${s}`;r.setAttribute("id",o);r.setAttribute("vector-effect","non-scaling-stroke");e&&this.#zo.set(s,r);const l=i?this.#Wo(a,o):null,h=DrawLayer._svgFactory.createElement("use");n.append(h);h.setAttribute("href",`#${o}`);this.updateProperties(n,t);this.#Uo.set(s,n);return{id:s,clipPathId:`url(#${l})`}}drawOutline(t,e){const i=DrawLayer.#y++,s=this.#$o(),n=DrawLayer._svgFactory.createElement("defs");s.append(n);const a=DrawLayer._svgFactory.createElement("path");n.append(a);const r=`path_p${this.pageIndex}_${i}`;a.setAttribute("id",r);a.setAttribute("vector-effect","non-scaling-stroke");let o;if(e){const t=DrawLayer._svgFactory.createElement("mask");n.append(t);o=`mask_p${this.pageIndex}_${i}`;t.setAttribute("id",o);t.setAttribute("maskUnits","objectBoundingBox");const e=DrawLayer._svgFactory.createElement("rect");t.append(e);e.setAttribute("width","1");e.setAttribute("height","1");e.setAttribute("fill","white");const s=DrawLayer._svgFactory.createElement("use");t.append(s);s.setAttribute("href",`#${r}`);s.setAttribute("stroke","none");s.setAttribute("fill","black");s.setAttribute("fill-rule","nonzero");s.classList.add("mask")}const l=DrawLayer._svgFactory.createElement("use");s.append(l);l.setAttribute("href",`#${r}`);o&&l.setAttribute("mask",`url(#${o})`);const h=l.cloneNode();s.append(h);l.classList.add("mainOutline");h.classList.add("secondaryOutline");this.updateProperties(s,t);this.#Uo.set(i,s);return i}finalizeDraw(t,e){this.#zo.delete(t);this.updateProperties(t,e)}updateProperties(t,e){if(!e)return;const{root:i,bbox:s,rootClass:n,path:a}=e,r="number"==typeof t?this.#Uo.get(t):t;if(r){i&&this.#Vo(r,i);s&&DrawLayer.#Go(r,s);if(n){const{classList:t}=r;for(const[e,i]of Object.entries(n))t.toggle(e,i)}if(a){const t=r.firstChild.firstChild;this.#Vo(t,a)}}}updateParent(t,e){if(e===this)return;const i=this.#Uo.get(t);if(i){e.#rn.append(i);this.#Uo.delete(t);e.#Uo.set(t,i)}}remove(t){this.#zo.delete(t);if(null!==this.#rn){this.#Uo.get(t).remove();this.#Uo.delete(t)}}destroy(){this.#rn=null;for(const t of this.#Uo.values())t.remove();this.#Uo.clear();this.#zo.clear()}}globalThis.pdfjsTestingUtils={HighlightOutliner};globalThis.pdfjsLib={AbortException,AnnotationEditorLayer,AnnotationEditorParamsType:p,AnnotationEditorType:u,AnnotationEditorUIManager,AnnotationLayer,AnnotationMode:c,AnnotationType:_,build:vt,ColorPicker,createValidAbsoluteUrl,DOMSVGFactory,DrawLayer,FeatureTest:util_FeatureTest,fetchData,getDocument,getFilenameFromUrl,getPdfFilenameFromUrl,getUuid,getXfaPageViewport,GlobalWorkerOptions,ImageKind:v,InvalidPDFException,isDataScheme,isPdfFile,isValidExplicitDest:At,MathClamp,noContextMenu,normalizeUnicode,OPS:D,OutputScale,PasswordResponses:F,PDFDataRangeTransport,PDFDateString,PDFWorker,PermissionFlag:g,PixelsPerInch,RenderingCancelledException,ResponseException,setLayerDimensions,shadow,SignatureExtractor,stopEvent,SupportedImageMimeTypes:G,TextLayer,TouchManager,updateUrlHash,Util,VerbosityLevel:M,version:yt,XfaLayer};export{AbortException,AnnotationEditorLayer,p as AnnotationEditorParamsType,u as AnnotationEditorType,AnnotationEditorUIManager,AnnotationLayer,c as AnnotationMode,_ as AnnotationType,ColorPicker,DOMSVGFactory,DrawLayer,util_FeatureTest as FeatureTest,GlobalWorkerOptions,v as ImageKind,InvalidPDFException,MathClamp,D as OPS,OutputScale,PDFDataRangeTransport,PDFDateString,PDFWorker,F as PasswordResponses,g as PermissionFlag,PixelsPerInch,RenderingCancelledException,ResponseException,SignatureExtractor,G as SupportedImageMimeTypes,TextLayer,TouchManager,Util,M as VerbosityLevel,XfaLayer,vt as build,createValidAbsoluteUrl,fetchData,getDocument,getFilenameFromUrl,getPdfFilenameFromUrl,getUuid,getXfaPageViewport,isDataScheme,isPdfFile,At as isValidExplicitDest,noContextMenu,normalizeUnicode,setLayerDimensions,shadow,stopEvent,updateUrlHash,yt as version}; \ No newline at end of file +/** + * pdfjsVersion = 5.3.31 + * pdfjsBuild = 47ad820d9 + */ +const t=!("object"!=typeof process||process+""!="[object process]"||process.versions.nw||process.versions.electron&&process.type&&"browser"!==process.type),e=[.001,0,0,.001,0,0],i=1.35,s=1,n=2,a=4,r=16,o=32,l=64,h=128,d=256,c={DISABLE:0,ENABLE:1,ENABLE_FORMS:2,ENABLE_STORAGE:3},u={DISABLE:-1,NONE:0,FREETEXT:3,HIGHLIGHT:9,STAMP:13,INK:15,SIGNATURE:101},p={RESIZE:1,CREATE:2,FREETEXT_SIZE:11,FREETEXT_COLOR:12,FREETEXT_OPACITY:13,INK_COLOR:21,INK_THICKNESS:22,INK_OPACITY:23,HIGHLIGHT_COLOR:31,HIGHLIGHT_DEFAULT_COLOR:32,HIGHLIGHT_THICKNESS:33,HIGHLIGHT_FREE:34,HIGHLIGHT_SHOW_ALL:35,DRAW_STEP:41},g={PRINT:4,MODIFY_CONTENTS:8,COPY:16,MODIFY_ANNOTATIONS:32,FILL_INTERACTIVE_FORMS:256,COPY_FOR_ACCESSIBILITY:512,ASSEMBLE:1024,PRINT_HIGH_QUALITY:2048},f=0,m=1,b=2,A=3,w=3,y=4,v={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},x={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},E=1,_=2,S=3,C=4,T=5,M={ERRORS:0,WARNINGS:1,INFOS:5},D={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotation:80,endAnnotation:81,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91,setStrokeTransparent:92,setFillTransparent:93,rawFillPath:94},P=0,k=1,I=2,R=3,F={NEED_PASSWORD:1,INCORRECT_PASSWORD:2};let L=M.WARNINGS;function setVerbosityLevel(t){Number.isInteger(t)&&(L=t)}function getVerbosityLevel(){return L}function info(t){L>=M.INFOS&&console.log(`Info: ${t}`)}function warn(t){L>=M.WARNINGS&&console.log(`Warning: ${t}`)}function unreachable(t){throw new Error(t)}function assert(t,e){t||unreachable(e)}function createValidAbsoluteUrl(t,e=null,i=null){if(!t)return null;if(i&&"string"==typeof t){if(i.addDefaultProtocol&&t.startsWith("www.")){const e=t.match(/\./g);e?.length>=2&&(t=`http://${t}`)}if(i.tryConvertEncoding)try{t=function stringToUTF8String(t){return decodeURIComponent(escape(t))}(t)}catch{}}const s=e?URL.parse(t,e):URL.parse(t);return function _isValidProtocol(t){switch(t?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(s)?s:null}function updateUrlHash(t,e,i=!1){const s=URL.parse(t);if(s){s.hash=e;return s.href}return i&&createValidAbsoluteUrl(t,"http://example.com")?t.split("#",1)[0]+""+(e?`#${e}`:""):""}function shadow(t,e,i,s=!1){Object.defineProperty(t,e,{value:i,enumerable:!s,configurable:!0,writable:!1});return i}const O=function BaseExceptionClosure(){function BaseException(t,e){this.message=t;this.name=e}BaseException.prototype=new Error;BaseException.constructor=BaseException;return BaseException}();class PasswordException extends O{constructor(t,e){super(t,"PasswordException");this.code=e}}class UnknownErrorException extends O{constructor(t,e){super(t,"UnknownErrorException");this.details=e}}class InvalidPDFException extends O{constructor(t){super(t,"InvalidPDFException")}}class ResponseException extends O{constructor(t,e,i){super(t,"ResponseException");this.status=e;this.missing=i}}class FormatError extends O{constructor(t){super(t,"FormatError")}}class AbortException extends O{constructor(t){super(t,"AbortException")}}function bytesToString(t){"object"==typeof t&&void 0!==t?.length||unreachable("Invalid argument for bytesToString");const e=t.length,i=8192;if(et.toString(16).padStart(2,"0")));class Util{static makeHexColor(t,e,i){return`#${N[t]}${N[e]}${N[i]}`}static scaleMinMax(t,e){let i;if(t[0]){if(t[0]<0){i=e[0];e[0]=e[2];e[2]=i}e[0]*=t[0];e[2]*=t[0];if(t[3]<0){i=e[1];e[1]=e[3];e[3]=i}e[1]*=t[3];e[3]*=t[3]}else{i=e[0];e[0]=e[1];e[1]=i;i=e[2];e[2]=e[3];e[3]=i;if(t[1]<0){i=e[1];e[1]=e[3];e[3]=i}e[1]*=t[1];e[3]*=t[1];if(t[2]<0){i=e[0];e[0]=e[2];e[2]=i}e[0]*=t[2];e[2]*=t[2]}e[0]+=t[4];e[1]+=t[5];e[2]+=t[4];e[3]+=t[5]}static transform(t,e){return[t[0]*e[0]+t[2]*e[1],t[1]*e[0]+t[3]*e[1],t[0]*e[2]+t[2]*e[3],t[1]*e[2]+t[3]*e[3],t[0]*e[4]+t[2]*e[5]+t[4],t[1]*e[4]+t[3]*e[5]+t[5]]}static applyTransform(t,e,i=0){const s=t[i],n=t[i+1];t[i]=s*e[0]+n*e[2]+e[4];t[i+1]=s*e[1]+n*e[3]+e[5]}static applyTransformToBezier(t,e,i=0){const s=e[0],n=e[1],a=e[2],r=e[3],o=e[4],l=e[5];for(let e=0;e<6;e+=2){const h=t[i+e],d=t[i+e+1];t[i+e]=h*s+d*a+o;t[i+e+1]=h*n+d*r+l}}static applyInverseTransform(t,e){const i=t[0],s=t[1],n=e[0]*e[3]-e[1]*e[2];t[0]=(i*e[3]-s*e[2]+e[2]*e[5]-e[4]*e[3])/n;t[1]=(-i*e[1]+s*e[0]+e[4]*e[1]-e[5]*e[0])/n}static axialAlignedBoundingBox(t,e,i){const s=e[0],n=e[1],a=e[2],r=e[3],o=e[4],l=e[5],h=t[0],d=t[1],c=t[2],u=t[3];let p=s*h+o,g=p,f=s*c+o,m=f,b=r*d+l,A=b,w=r*u+l,y=w;if(0!==n||0!==a){const t=n*h,e=n*c,i=a*d,s=a*u;p+=i;m+=i;f+=s;g+=s;b+=t;y+=t;w+=e;A+=e}i[0]=Math.min(i[0],p,f,g,m);i[1]=Math.min(i[1],b,w,A,y);i[2]=Math.max(i[2],p,f,g,m);i[3]=Math.max(i[3],b,w,A,y)}static inverseTransform(t){const e=t[0]*t[3]-t[1]*t[2];return[t[3]/e,-t[1]/e,-t[2]/e,t[0]/e,(t[2]*t[5]-t[4]*t[3])/e,(t[4]*t[1]-t[5]*t[0])/e]}static singularValueDecompose2dScale(t,e){const i=t[0],s=t[1],n=t[2],a=t[3],r=i**2+s**2,o=i*n+s*a,l=n**2+a**2,h=(r+l)/2,d=Math.sqrt(h**2-(r*l-o**2));e[0]=Math.sqrt(h+d||1);e[1]=Math.sqrt(h-d||1)}static normalizeRect(t){const e=t.slice(0);if(t[0]>t[2]){e[0]=t[2];e[2]=t[0]}if(t[1]>t[3]){e[1]=t[3];e[3]=t[1]}return e}static intersect(t,e){const i=Math.max(Math.min(t[0],t[2]),Math.min(e[0],e[2])),s=Math.min(Math.max(t[0],t[2]),Math.max(e[0],e[2]));if(i>s)return null;const n=Math.max(Math.min(t[1],t[3]),Math.min(e[1],e[3])),a=Math.min(Math.max(t[1],t[3]),Math.max(e[1],e[3]));return n>a?null:[i,n,s,a]}static pointBoundingBox(t,e,i){i[0]=Math.min(i[0],t);i[1]=Math.min(i[1],e);i[2]=Math.max(i[2],t);i[3]=Math.max(i[3],e)}static rectBoundingBox(t,e,i,s,n){n[0]=Math.min(n[0],t,i);n[1]=Math.min(n[1],e,s);n[2]=Math.max(n[2],t,i);n[3]=Math.max(n[3],e,s)}static#t(t,e,i,s,n,a,r,o,l,h){if(l<=0||l>=1)return;const d=1-l,c=l*l,u=c*l,p=d*(d*(d*t+3*l*e)+3*c*i)+u*s,g=d*(d*(d*n+3*l*a)+3*c*r)+u*o;h[0]=Math.min(h[0],p);h[1]=Math.min(h[1],g);h[2]=Math.max(h[2],p);h[3]=Math.max(h[3],g)}static#e(t,e,i,s,n,a,r,o,l,h,d,c){if(Math.abs(l)<1e-12){Math.abs(h)>=1e-12&&this.#t(t,e,i,s,n,a,r,o,-d/h,c);return}const u=h**2-4*d*l;if(u<0)return;const p=Math.sqrt(u),g=2*l;this.#t(t,e,i,s,n,a,r,o,(-h+p)/g,c);this.#t(t,e,i,s,n,a,r,o,(-h-p)/g,c)}static bezierBoundingBox(t,e,i,s,n,a,r,o,l){l[0]=Math.min(l[0],t,r);l[1]=Math.min(l[1],e,o);l[2]=Math.max(l[2],t,r);l[3]=Math.max(l[3],e,o);this.#e(t,i,n,r,e,s,a,o,3*(3*(i-n)-t+r),6*(t-2*i+n),3*(i-t),l);this.#e(t,i,n,r,e,s,a,o,3*(3*(s-a)-e+o),6*(e-2*s+a),3*(s-e),l)}}let B=null,H=null;function normalizeUnicode(t){if(!B){B=/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;H=new Map([["ſt","ſt"]])}return t.replaceAll(B,((t,e,i)=>e?e.normalize("NFKC"):H.get(i)))}function getUuid(){if("function"==typeof crypto.randomUUID)return crypto.randomUUID();const t=new Uint8Array(32);crypto.getRandomValues(t);return bytesToString(t)}const U="pdfjs_internal_id_";function MathClamp(t,e,i){return Math.min(Math.max(t,e),i)}function toBase64Util(t){return Uint8Array.prototype.toBase64?t.toBase64():btoa(bytesToString(t))}"function"!=typeof Promise.try&&(Promise.try=function(t,...e){return new Promise((i=>{i(t(...e))}))});"function"!=typeof Math.sumPrecise&&(Math.sumPrecise=function(t){return t.reduce(((t,e)=>t+e),0)});const z="http://www.w3.org/2000/svg";class PixelsPerInch{static CSS=96;static PDF=72;static PDF_TO_CSS_UNITS=this.CSS/this.PDF}async function fetchData(t,e="text"){if(isValidFetchUrl(t,document.baseURI)){const i=await fetch(t);if(!i.ok)throw new Error(i.statusText);switch(e){case"arraybuffer":return i.arrayBuffer();case"blob":return i.blob();case"json":return i.json()}return i.text()}return new Promise(((i,s)=>{const n=new XMLHttpRequest;n.open("GET",t,!0);n.responseType=e;n.onreadystatechange=()=>{if(n.readyState===XMLHttpRequest.DONE)if(200!==n.status&&0!==n.status)s(new Error(n.statusText));else{switch(e){case"arraybuffer":case"blob":case"json":i(n.response);return}i(n.responseText)}};n.send(null)}))}class PageViewport{constructor({viewBox:t,userUnit:e,scale:i,rotation:s,offsetX:n=0,offsetY:a=0,dontFlip:r=!1}){this.viewBox=t;this.userUnit=e;this.scale=i;this.rotation=s;this.offsetX=n;this.offsetY=a;i*=e;const o=(t[2]+t[0])/2,l=(t[3]+t[1])/2;let h,d,c,u,p,g,f,m;(s%=360)<0&&(s+=360);switch(s){case 180:h=-1;d=0;c=0;u=1;break;case 90:h=0;d=1;c=1;u=0;break;case 270:h=0;d=-1;c=-1;u=0;break;case 0:h=1;d=0;c=0;u=-1;break;default:throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees.")}if(r){c=-c;u=-u}if(0===h){p=Math.abs(l-t[1])*i+n;g=Math.abs(o-t[0])*i+a;f=(t[3]-t[1])*i;m=(t[2]-t[0])*i}else{p=Math.abs(o-t[0])*i+n;g=Math.abs(l-t[1])*i+a;f=(t[2]-t[0])*i;m=(t[3]-t[1])*i}this.transform=[h*i,d*i,c*i,u*i,p-h*i*o-c*i*l,g-d*i*o-u*i*l];this.width=f;this.height=m}get rawDims(){const t=this.viewBox;return shadow(this,"rawDims",{pageWidth:t[2]-t[0],pageHeight:t[3]-t[1],pageX:t[0],pageY:t[1]})}clone({scale:t=this.scale,rotation:e=this.rotation,offsetX:i=this.offsetX,offsetY:s=this.offsetY,dontFlip:n=!1}={}){return new PageViewport({viewBox:this.viewBox.slice(),userUnit:this.userUnit,scale:t,rotation:e,offsetX:i,offsetY:s,dontFlip:n})}convertToViewportPoint(t,e){const i=[t,e];Util.applyTransform(i,this.transform);return i}convertToViewportRectangle(t){const e=[t[0],t[1]];Util.applyTransform(e,this.transform);const i=[t[2],t[3]];Util.applyTransform(i,this.transform);return[e[0],e[1],i[0],i[1]]}convertToPdfPoint(t,e){const i=[t,e];Util.applyInverseTransform(i,this.transform);return i}}class RenderingCancelledException extends O{constructor(t,e=0){super(t,"RenderingCancelledException");this.extraDelay=e}}function isDataScheme(t){const e=t.length;let i=0;for(;i=1&&s<=12?s-1:0;let n=parseInt(e[3],10);n=n>=1&&n<=31?n:1;let a=parseInt(e[4],10);a=a>=0&&a<=23?a:0;let r=parseInt(e[5],10);r=r>=0&&r<=59?r:0;let o=parseInt(e[6],10);o=o>=0&&o<=59?o:0;const l=e[7]||"Z";let h=parseInt(e[8],10);h=h>=0&&h<=23?h:0;let d=parseInt(e[9],10)||0;d=d>=0&&d<=59?d:0;if("-"===l){a+=h;r+=d}else if("+"===l){a-=h;r-=d}return new Date(Date.UTC(i,s,n,a,r,o))}}function getXfaPageViewport(t,{scale:e=1,rotation:i=0}){const{width:s,height:n}=t.attributes.style,a=[0,0,parseInt(s),parseInt(n)];return new PageViewport({viewBox:a,userUnit:1,scale:e,rotation:i})}function getRGB(t){if(t.startsWith("#")){const e=parseInt(t.slice(1),16);return[(16711680&e)>>16,(65280&e)>>8,255&e]}if(t.startsWith("rgb("))return t.slice(4,-1).split(",").map((t=>parseInt(t)));if(t.startsWith("rgba("))return t.slice(5,-1).split(",").map((t=>parseInt(t))).slice(0,3);warn(`Not a valid color format: "${t}"`);return[0,0,0]}function getCurrentTransform(t){const{a:e,b:i,c:s,d:n,e:a,f:r}=t.getTransform();return[e,i,s,n,a,r]}function getCurrentTransformInverse(t){const{a:e,b:i,c:s,d:n,e:a,f:r}=t.getTransform().invertSelf();return[e,i,s,n,a,r]}function setLayerDimensions(t,e,i=!1,s=!0){if(e instanceof PageViewport){const{pageWidth:s,pageHeight:n}=e.rawDims,{style:a}=t,r=util_FeatureTest.isCSSRoundSupported,o=`var(--total-scale-factor) * ${s}px`,l=`var(--total-scale-factor) * ${n}px`,h=r?`round(down, ${o}, var(--scale-round-x))`:`calc(${o})`,d=r?`round(down, ${l}, var(--scale-round-y))`:`calc(${l})`;if(i&&e.rotation%180!=0){a.width=d;a.height=h}else{a.width=h;a.height=d}}s&&t.setAttribute("data-main-rotation",e.rotation)}class OutputScale{constructor(){const{pixelRatio:t}=OutputScale;this.sx=t;this.sy=t}get scaled(){return 1!==this.sx||1!==this.sy}get symmetric(){return this.sx===this.sy}limitCanvas(t,e,i,s,n=-1){let a=1/0,r=1/0,o=1/0;(i=OutputScale.capPixels(i,n))>0&&(a=Math.sqrt(i/(t*e)));if(-1!==s){r=s/t;o=s/e}const l=Math.min(a,r,o);if(this.sx>l||this.sy>l){this.sx=l;this.sy=l;return!0}return!1}static get pixelRatio(){return globalThis.devicePixelRatio||1}static capPixels(t,e){if(e>=0){const i=Math.ceil(window.screen.availWidth*window.screen.availHeight*this.pixelRatio**2*(1+e/100));return t>0?Math.min(t,i):i}return t}}const G=["image/apng","image/avif","image/bmp","image/gif","image/jpeg","image/png","image/svg+xml","image/webp","image/x-icon"];class EditorToolbar{#s=null;#n=null;#a;#r=null;#o=null;#l=null;static#h=null;constructor(t){this.#a=t;EditorToolbar.#h||=Object.freeze({freetext:"pdfjs-editor-remove-freetext-button",highlight:"pdfjs-editor-remove-highlight-button",ink:"pdfjs-editor-remove-ink-button",stamp:"pdfjs-editor-remove-stamp-button",signature:"pdfjs-editor-remove-signature-button"})}render(){const t=this.#s=document.createElement("div");t.classList.add("editToolbar","hidden");t.setAttribute("role","toolbar");const e=this.#a._uiManager._signal;t.addEventListener("contextmenu",noContextMenu,{signal:e});t.addEventListener("pointerdown",EditorToolbar.#d,{signal:e});const i=this.#r=document.createElement("div");i.className="buttons";t.append(i);const s=this.#a.toolbarPosition;if(s){const{style:e}=t,i="ltr"===this.#a._uiManager.direction?1-s[0]:s[0];e.insetInlineEnd=100*i+"%";e.top=`calc(${100*s[1]}% + var(--editor-toolbar-vert-offset))`}this.#c();return t}get div(){return this.#s}static#d(t){t.stopPropagation()}#u(t){this.#a._focusEventsAllowed=!1;stopEvent(t)}#p(t){this.#a._focusEventsAllowed=!0;stopEvent(t)}#g(t){const e=this.#a._uiManager._signal;t.addEventListener("focusin",this.#u.bind(this),{capture:!0,signal:e});t.addEventListener("focusout",this.#p.bind(this),{capture:!0,signal:e});t.addEventListener("contextmenu",noContextMenu,{signal:e})}hide(){this.#s.classList.add("hidden");this.#n?.hideDropdown()}show(){this.#s.classList.remove("hidden");this.#o?.shown()}#c(){const{editorType:t,_uiManager:e}=this.#a,i=document.createElement("button");i.className="delete";i.tabIndex=0;i.setAttribute("data-l10n-id",EditorToolbar.#h[t]);this.#g(i);i.addEventListener("click",(t=>{e.delete()}),{signal:e._signal});this.#r.append(i)}get#f(){const t=document.createElement("div");t.className="divider";return t}async addAltText(t){const e=await t.render();this.#g(e);this.#r.prepend(e,this.#f);this.#o=t}addColorPicker(t){this.#n=t;const e=t.renderButton();this.#g(e);this.#r.prepend(e,this.#f)}async addEditSignatureButton(t){const e=this.#l=await t.renderEditButton(this.#a);this.#g(e);this.#r.prepend(e,this.#f)}updateEditSignatureButton(t){this.#l&&(this.#l.title=t)}remove(){this.#s.remove();this.#n?.destroy();this.#n=null}}class HighlightToolbar{#r=null;#s=null;#m;constructor(t){this.#m=t}#b(){const t=this.#s=document.createElement("div");t.className="editToolbar";t.setAttribute("role","toolbar");t.addEventListener("contextmenu",noContextMenu,{signal:this.#m._signal});const e=this.#r=document.createElement("div");e.className="buttons";t.append(e);this.#A();return t}#w(t,e){let i=0,s=0;for(const n of t){const t=n.y+n.height;if(ti){s=a;i=t}else e?a>s&&(s=a):a{this.#m.highlightSelection("floating_button")}),{signal:i});this.#r.append(t)}}function bindEvents(t,e,i){for(const s of i)e.addEventListener(s,t[s].bind(t))}class IdManager{#y=0;get id(){return"pdfjs_internal_editor_"+this.#y++}}class ImageManager{#v=getUuid();#y=0;#x=null;static get _isSVGFittingCanvas(){const t=new OffscreenCanvas(1,3).getContext("2d",{willReadFrequently:!0}),e=new Image;e.src='data:image/svg+xml;charset=UTF-8,';return shadow(this,"_isSVGFittingCanvas",e.decode().then((()=>{t.drawImage(e,0,0,1,1,0,0,1,3);return 0===new Uint32Array(t.getImageData(0,0,1,1).data.buffer)[0]})))}async#E(t,e){this.#x||=new Map;let i=this.#x.get(t);if(null===i)return null;if(i?.bitmap){i.refCounter+=1;return i}try{i||={bitmap:null,id:`image_${this.#v}_${this.#y++}`,refCounter:0,isSvg:!1};let t;if("string"==typeof e){i.url=e;t=await fetchData(e,"blob")}else e instanceof File?t=i.file=e:e instanceof Blob&&(t=e);if("image/svg+xml"===t.type){const e=ImageManager._isSVGFittingCanvas,s=new FileReader,n=new Image,a=new Promise(((t,a)=>{n.onload=()=>{i.bitmap=n;i.isSvg=!0;t()};s.onload=async()=>{const t=i.svgUrl=s.result;n.src=await e?`${t}#svgView(preserveAspectRatio(none))`:t};n.onerror=s.onerror=a}));s.readAsDataURL(t);await a}else i.bitmap=await createImageBitmap(t);i.refCounter=1}catch(t){warn(t);i=null}this.#x.set(t,i);i&&this.#x.set(i.id,i);return i}async getFromFile(t){const{lastModified:e,name:i,size:s,type:n}=t;return this.#E(`${e}_${i}_${s}_${n}`,t)}async getFromUrl(t){return this.#E(t,t)}async getFromBlob(t,e){const i=await e;return this.#E(t,i)}async getFromId(t){this.#x||=new Map;const e=this.#x.get(t);if(!e)return null;if(e.bitmap){e.refCounter+=1;return e}if(e.file)return this.getFromFile(e.file);if(e.blobPromise){const{blobPromise:t}=e;delete e.blobPromise;return this.getFromBlob(e.id,t)}return this.getFromUrl(e.url)}getFromCanvas(t,e){this.#x||=new Map;let i=this.#x.get(t);if(i?.bitmap){i.refCounter+=1;return i}const s=new OffscreenCanvas(e.width,e.height);s.getContext("2d").drawImage(e,0,0);i={bitmap:s.transferToImageBitmap(),id:`image_${this.#v}_${this.#y++}`,refCounter:1,isSvg:!1};this.#x.set(t,i);this.#x.set(i.id,i);return i}getSvgUrl(t){const e=this.#x.get(t);return e?.isSvg?e.svgUrl:null}deleteId(t){this.#x||=new Map;const e=this.#x.get(t);if(!e)return;e.refCounter-=1;if(0!==e.refCounter)return;const{bitmap:i}=e;if(!e.url&&!e.file){const t=new OffscreenCanvas(i.width,i.height);t.getContext("bitmaprenderer").transferFromImageBitmap(i);e.blobPromise=t.convertToBlob()}i.close?.();e.bitmap=null}isValidId(t){return t.startsWith(`image_${this.#v}_`)}}class CommandManager{#_=[];#S=!1;#C;#T=-1;constructor(t=128){this.#C=t}add({cmd:t,undo:e,post:i,mustExec:s,type:n=NaN,overwriteIfSameType:a=!1,keepUndo:r=!1}){s&&t();if(this.#S)return;const o={cmd:t,undo:e,post:i,type:n};if(-1===this.#T){this.#_.length>0&&(this.#_.length=0);this.#T=0;this.#_.push(o);return}if(a&&this.#_[this.#T].type===n){r&&(o.undo=this.#_[this.#T].undo);this.#_[this.#T]=o;return}const l=this.#T+1;if(l===this.#C)this.#_.splice(0,1);else{this.#T=l;l=0;e--)if(this.#_[e].type!==t){this.#_.splice(e+1,this.#T-e);this.#T=e;return}this.#_.length=0;this.#T=-1}}destroy(){this.#_=null}}class KeyboardManager{constructor(t){this.buffer=[];this.callbacks=new Map;this.allKeys=new Set;const{isMac:e}=util_FeatureTest.platform;for(const[i,s,n={}]of t)for(const t of i){const i=t.startsWith("mac+");if(e&&i){this.callbacks.set(t.slice(4),{callback:s,options:n});this.allKeys.add(t.split("+").at(-1))}else if(!e&&!i){this.callbacks.set(t,{callback:s,options:n});this.allKeys.add(t.split("+").at(-1))}}}#M(t){t.altKey&&this.buffer.push("alt");t.ctrlKey&&this.buffer.push("ctrl");t.metaKey&&this.buffer.push("meta");t.shiftKey&&this.buffer.push("shift");this.buffer.push(t.key);const e=this.buffer.join("+");this.buffer.length=0;return e}exec(t,e){if(!this.allKeys.has(e.key))return;const i=this.callbacks.get(this.#M(e));if(!i)return;const{callback:s,options:{bubbles:n=!1,args:a=[],checker:r=null}}=i;if(!r||r(t,e)){s.bind(t,...a,e)();n||stopEvent(e)}}}class ColorManager{static _colorsMapping=new Map([["CanvasText",[0,0,0]],["Canvas",[255,255,255]]]);get _colors(){const t=new Map([["CanvasText",null],["Canvas",null]]);!function getColorValues(t){const e=document.createElement("span");e.style.visibility="hidden";e.style.colorScheme="only light";document.body.append(e);for(const i of t.keys()){e.style.color=i;const s=window.getComputedStyle(e).color;t.set(i,getRGB(s))}e.remove()}(t);return shadow(this,"_colors",t)}convert(t){const e=getRGB(t);if(!window.matchMedia("(forced-colors: active)").matches)return e;for(const[t,i]of this._colors)if(i.every(((t,i)=>t===e[i])))return ColorManager._colorsMapping.get(t);return e}getHexCode(t){const e=this._colors.get(t);return e?Util.makeHexColor(...e):t}}class AnnotationEditorUIManager{#D=new AbortController;#P=null;#k=new Map;#I=new Map;#R=null;#F=null;#L=null;#O=new CommandManager;#N=null;#B=null;#H=0;#U=new Set;#z=null;#G=null;#$=new Set;_editorUndoBar=null;#W=!1;#V=!1;#j=!1;#q=null;#X=null;#K=null;#Y=null;#Q=!1;#J=null;#Z=new IdManager;#tt=!1;#et=!1;#it=null;#st=null;#nt=null;#at=null;#rt=null;#ot=u.NONE;#lt=new Set;#ht=null;#dt=null;#ct=null;#ut=null;#pt={isEditing:!1,isEmpty:!0,hasSomethingToUndo:!1,hasSomethingToRedo:!1,hasSelectedEditor:!1,hasSelectedText:!1};#gt=[0,0];#ft=null;#mt=null;#bt=null;#At=null;static TRANSLATE_SMALL=1;static TRANSLATE_BIG=10;static get _keyboardManager(){const t=AnnotationEditorUIManager.prototype,arrowChecker=t=>t.#mt.contains(document.activeElement)&&"BUTTON"!==document.activeElement.tagName&&t.hasSomethingToControl(),textInputChecker=(t,{target:e})=>{if(e instanceof HTMLInputElement){const{type:t}=e;return"text"!==t&&"number"!==t}return!0},e=this.TRANSLATE_SMALL,i=this.TRANSLATE_BIG;return shadow(this,"_keyboardManager",new KeyboardManager([[["ctrl+a","mac+meta+a"],t.selectAll,{checker:textInputChecker}],[["ctrl+z","mac+meta+z"],t.undo,{checker:textInputChecker}],[["ctrl+y","ctrl+shift+z","mac+meta+shift+z","ctrl+shift+Z","mac+meta+shift+Z"],t.redo,{checker:textInputChecker}],[["Backspace","alt+Backspace","ctrl+Backspace","shift+Backspace","mac+Backspace","mac+alt+Backspace","mac+ctrl+Backspace","Delete","ctrl+Delete","shift+Delete","mac+Delete"],t.delete,{checker:textInputChecker}],[["Enter","mac+Enter"],t.addNewEditorFromKeyboard,{checker:(t,{target:e})=>!(e instanceof HTMLButtonElement)&&t.#mt.contains(e)&&!t.isEnterHandled}],[[" ","mac+ "],t.addNewEditorFromKeyboard,{checker:(t,{target:e})=>!(e instanceof HTMLButtonElement)&&t.#mt.contains(document.activeElement)}],[["Escape","mac+Escape"],t.unselectAll],[["ArrowLeft","mac+ArrowLeft"],t.translateSelectedEditors,{args:[-e,0],checker:arrowChecker}],[["ctrl+ArrowLeft","mac+shift+ArrowLeft"],t.translateSelectedEditors,{args:[-i,0],checker:arrowChecker}],[["ArrowRight","mac+ArrowRight"],t.translateSelectedEditors,{args:[e,0],checker:arrowChecker}],[["ctrl+ArrowRight","mac+shift+ArrowRight"],t.translateSelectedEditors,{args:[i,0],checker:arrowChecker}],[["ArrowUp","mac+ArrowUp"],t.translateSelectedEditors,{args:[0,-e],checker:arrowChecker}],[["ctrl+ArrowUp","mac+shift+ArrowUp"],t.translateSelectedEditors,{args:[0,-i],checker:arrowChecker}],[["ArrowDown","mac+ArrowDown"],t.translateSelectedEditors,{args:[0,e],checker:arrowChecker}],[["ctrl+ArrowDown","mac+shift+ArrowDown"],t.translateSelectedEditors,{args:[0,i],checker:arrowChecker}]]))}constructor(t,e,i,s,n,a,r,o,l,h,d,c,u,p){const g=this._signal=this.#D.signal;this.#mt=t;this.#bt=e;this.#R=i;this.#dt=s;this._eventBus=n;n._on("editingaction",this.onEditingAction.bind(this),{signal:g});n._on("pagechanging",this.onPageChanging.bind(this),{signal:g});n._on("scalechanging",this.onScaleChanging.bind(this),{signal:g});n._on("rotationchanging",this.onRotationChanging.bind(this),{signal:g});n._on("setpreference",this.onSetPreference.bind(this),{signal:g});n._on("switchannotationeditorparams",(t=>this.updateParams(t.type,t.value)),{signal:g});this.#wt();this.#yt();this.#vt();this.#F=a.annotationStorage;this.#q=a.filterFactory;this.#ct=r;this.#Y=o||null;this.#W=l;this.#V=h;this.#j=d;this.#rt=c||null;this.viewParameters={realScale:PixelsPerInch.PDF_TO_CSS_UNITS,rotation:0};this.isShiftKeyDown=!1;this._editorUndoBar=u||null;this._supportsPinchToZoom=!1!==p}destroy(){this.#At?.resolve();this.#At=null;this.#D?.abort();this.#D=null;this._signal=null;for(const t of this.#I.values())t.destroy();this.#I.clear();this.#k.clear();this.#$.clear();this.#at?.clear();this.#P=null;this.#lt.clear();this.#O.destroy();this.#R?.destroy();this.#dt?.destroy();this.#J?.hide();this.#J=null;this.#nt?.destroy();this.#nt=null;if(this.#X){clearTimeout(this.#X);this.#X=null}if(this.#ft){clearTimeout(this.#ft);this.#ft=null}this._editorUndoBar?.destroy()}combinedSignal(t){return AbortSignal.any([this._signal,t.signal])}get mlManager(){return this.#rt}get useNewAltTextFlow(){return this.#V}get useNewAltTextWhenAddingImage(){return this.#j}get hcmFilter(){return shadow(this,"hcmFilter",this.#ct?this.#q.addHCMFilter(this.#ct.foreground,this.#ct.background):"none")}get direction(){return shadow(this,"direction",getComputedStyle(this.#mt).direction)}get highlightColors(){return shadow(this,"highlightColors",this.#Y?new Map(this.#Y.split(",").map((t=>t.split("=").map((t=>t.trim()))))):null)}get highlightColorNames(){return shadow(this,"highlightColorNames",this.highlightColors?new Map(Array.from(this.highlightColors,(t=>t.reverse()))):null)}setCurrentDrawingSession(t){if(t){this.unselectAll();this.disableUserSelect(!0)}else this.disableUserSelect(!1);this.#B=t}setMainHighlightColorPicker(t){this.#nt=t}editAltText(t,e=!1){this.#R?.editAltText(this,t,e)}getSignature(t){this.#dt?.getSignature({uiManager:this,editor:t})}get signatureManager(){return this.#dt}switchToMode(t,e){this._eventBus.on("annotationeditormodechanged",e,{once:!0,signal:this._signal});this._eventBus.dispatch("showannotationeditorui",{source:this,mode:t})}setPreference(t,e){this._eventBus.dispatch("setpreference",{source:this,name:t,value:e})}onSetPreference({name:t,value:e}){if("enableNewAltTextWhenAddingImage"===t)this.#j=e}onPageChanging({pageNumber:t}){this.#H=t-1}focusMainContainer(){this.#mt.focus()}findParent(t,e){for(const i of this.#I.values()){const{x:s,y:n,width:a,height:r}=i.div.getBoundingClientRect();if(t>=s&&t<=s+a&&e>=n&&e<=n+r)return i}return null}disableUserSelect(t=!1){this.#bt.classList.toggle("noUserSelect",t)}addShouldRescale(t){this.#$.add(t)}removeShouldRescale(t){this.#$.delete(t)}onScaleChanging({scale:t}){this.commitOrRemove();this.viewParameters.realScale=t*PixelsPerInch.PDF_TO_CSS_UNITS;for(const t of this.#$)t.onScaleChanging();this.#B?.onScaleChanging()}onRotationChanging({pagesRotation:t}){this.commitOrRemove();this.viewParameters.rotation=t}#xt({anchorNode:t}){return t.nodeType===Node.TEXT_NODE?t.parentElement:t}#Et(t){const{currentLayer:e}=this;if(e.hasTextLayer(t))return e;for(const e of this.#I.values())if(e.hasTextLayer(t))return e;return null}highlightSelection(t=""){const e=document.getSelection();if(!e||e.isCollapsed)return;const{anchorNode:i,anchorOffset:s,focusNode:n,focusOffset:a}=e,r=e.toString(),o=this.#xt(e).closest(".textLayer"),l=this.getSelectionBoxes(o);if(!l)return;e.empty();const h=this.#Et(o),d=this.#ot===u.NONE,callback=()=>{h?.createAndAddNewEditor({x:0,y:0},!1,{methodOfCreation:t,boxes:l,anchorNode:i,anchorOffset:s,focusNode:n,focusOffset:a,text:r});d&&this.showAllEditors("highlight",!0,!0)};d?this.switchToMode(u.HIGHLIGHT,callback):callback()}#_t(){const t=document.getSelection();if(!t||t.isCollapsed)return;const e=this.#xt(t).closest(".textLayer"),i=this.getSelectionBoxes(e);if(i){this.#J||=new HighlightToolbar(this);this.#J.show(e,i,"ltr"===this.direction)}}addToAnnotationStorage(t){t.isEmpty()||!this.#F||this.#F.has(t.id)||this.#F.setValue(t.id,t)}#St(){const t=document.getSelection();if(!t||t.isCollapsed){if(this.#ht){this.#J?.hide();this.#ht=null;this.#Ct({hasSelectedText:!1})}return}const{anchorNode:e}=t;if(e===this.#ht)return;const i=this.#xt(t).closest(".textLayer");if(i){this.#J?.hide();this.#ht=e;this.#Ct({hasSelectedText:!0});if(this.#ot===u.HIGHLIGHT||this.#ot===u.NONE){this.#ot===u.HIGHLIGHT&&this.showAllEditors("highlight",!0,!0);this.#Q=this.isShiftKeyDown;if(!this.isShiftKeyDown){const t=this.#ot===u.HIGHLIGHT?this.#Et(i):null;t?.toggleDrawing();const e=new AbortController,s=this.combinedSignal(e),pointerup=i=>{if("pointerup"!==i.type||0===i.button){e.abort();t?.toggleDrawing(!0);"pointerup"===i.type&&this.#Tt("main_toolbar")}};window.addEventListener("pointerup",pointerup,{signal:s});window.addEventListener("blur",pointerup,{signal:s})}}}else if(this.#ht){this.#J?.hide();this.#ht=null;this.#Ct({hasSelectedText:!1})}}#Tt(t=""){this.#ot===u.HIGHLIGHT?this.highlightSelection(t):this.#W&&this.#_t()}#wt(){document.addEventListener("selectionchange",this.#St.bind(this),{signal:this._signal})}#Mt(){if(this.#K)return;this.#K=new AbortController;const t=this.combinedSignal(this.#K);window.addEventListener("focus",this.focus.bind(this),{signal:t});window.addEventListener("blur",this.blur.bind(this),{signal:t})}#Dt(){this.#K?.abort();this.#K=null}blur(){this.isShiftKeyDown=!1;if(this.#Q){this.#Q=!1;this.#Tt("main_toolbar")}if(!this.hasSelection)return;const{activeElement:t}=document;for(const e of this.#lt)if(e.div.contains(t)){this.#st=[e,t];e._focusEventsAllowed=!1;break}}focus(){if(!this.#st)return;const[t,e]=this.#st;this.#st=null;e.addEventListener("focusin",(()=>{t._focusEventsAllowed=!0}),{once:!0,signal:this._signal});e.focus()}#vt(){if(this.#it)return;this.#it=new AbortController;const t=this.combinedSignal(this.#it);window.addEventListener("keydown",this.keydown.bind(this),{signal:t});window.addEventListener("keyup",this.keyup.bind(this),{signal:t})}#Pt(){this.#it?.abort();this.#it=null}#kt(){if(this.#N)return;this.#N=new AbortController;const t=this.combinedSignal(this.#N);document.addEventListener("copy",this.copy.bind(this),{signal:t});document.addEventListener("cut",this.cut.bind(this),{signal:t});document.addEventListener("paste",this.paste.bind(this),{signal:t})}#It(){this.#N?.abort();this.#N=null}#yt(){const t=this._signal;document.addEventListener("dragover",this.dragOver.bind(this),{signal:t});document.addEventListener("drop",this.drop.bind(this),{signal:t})}addEditListeners(){this.#vt();this.#kt()}removeEditListeners(){this.#Pt();this.#It()}dragOver(t){for(const{type:e}of t.dataTransfer.items)for(const i of this.#G)if(i.isHandlingMimeForPasting(e)){t.dataTransfer.dropEffect="copy";t.preventDefault();return}}drop(t){for(const e of t.dataTransfer.items)for(const i of this.#G)if(i.isHandlingMimeForPasting(e.type)){i.paste(e,this.currentLayer);t.preventDefault();return}}copy(t){t.preventDefault();this.#P?.commitOrRemove();if(!this.hasSelection)return;const e=[];for(const t of this.#lt){const i=t.serialize(!0);i&&e.push(i)}0!==e.length&&t.clipboardData.setData("application/pdfjs",JSON.stringify(e))}cut(t){this.copy(t);this.delete()}async paste(t){t.preventDefault();const{clipboardData:e}=t;for(const t of e.items)for(const e of this.#G)if(e.isHandlingMimeForPasting(t.type)){e.paste(t,this.currentLayer);return}let i=e.getData("application/pdfjs");if(!i)return;try{i=JSON.parse(i)}catch(t){warn(`paste: "${t.message}".`);return}if(!Array.isArray(i))return;this.unselectAll();const s=this.currentLayer;try{const t=[];for(const e of i){const i=await s.deserialize(e);if(!i)return;t.push(i)}const cmd=()=>{for(const e of t)this.#Rt(e);this.#Ft(t)},undo=()=>{for(const e of t)e.remove()};this.addCommands({cmd,undo,mustExec:!0})}catch(t){warn(`paste: "${t.message}".`)}}keydown(t){this.isShiftKeyDown||"Shift"!==t.key||(this.isShiftKeyDown=!0);this.#ot===u.NONE||this.isEditorHandlingKeyboard||AnnotationEditorUIManager._keyboardManager.exec(this,t)}keyup(t){if(this.isShiftKeyDown&&"Shift"===t.key){this.isShiftKeyDown=!1;if(this.#Q){this.#Q=!1;this.#Tt("main_toolbar")}}}onEditingAction({name:t}){switch(t){case"undo":case"redo":case"delete":case"selectAll":this[t]();break;case"highlightSelection":this.highlightSelection("context_menu")}}#Ct(t){if(Object.entries(t).some((([t,e])=>this.#pt[t]!==e))){this._eventBus.dispatch("annotationeditorstateschanged",{source:this,details:Object.assign(this.#pt,t)});this.#ot===u.HIGHLIGHT&&!1===t.hasSelectedEditor&&this.#Lt([[p.HIGHLIGHT_FREE,!0]])}}#Lt(t){this._eventBus.dispatch("annotationeditorparamschanged",{source:this,details:t})}setEditingState(t){if(t){this.#Mt();this.#kt();this.#Ct({isEditing:this.#ot!==u.NONE,isEmpty:this.#Ot(),hasSomethingToUndo:this.#O.hasSomethingToUndo(),hasSomethingToRedo:this.#O.hasSomethingToRedo(),hasSelectedEditor:!1})}else{this.#Dt();this.#It();this.#Ct({isEditing:!1});this.disableUserSelect(!1)}}registerEditorTypes(t){if(!this.#G){this.#G=t;for(const t of this.#G)this.#Lt(t.defaultPropertiesToUpdate)}}getId(){return this.#Z.id}get currentLayer(){return this.#I.get(this.#H)}getLayer(t){return this.#I.get(t)}get currentPageIndex(){return this.#H}addLayer(t){this.#I.set(t.pageIndex,t);this.#tt?t.enable():t.disable()}removeLayer(t){this.#I.delete(t.pageIndex)}async updateMode(t,e=null,i=!1){if(this.#ot!==t){if(this.#At){await this.#At.promise;if(!this.#At)return}this.#At=Promise.withResolvers();this.#B?.commitOrRemove();this.#ot=t;if(t!==u.NONE){t===u.SIGNATURE&&await(this.#dt?.loadSignatures());this.setEditingState(!0);await this.#Nt();this.unselectAll();for(const e of this.#I.values())e.updateMode(t);if(e){for(const t of this.#k.values())if(t.annotationElementId===e||t.id===e){this.setSelected(t);t.enterInEditMode()}else t.unselect();this.#At.resolve()}else{i&&this.addNewEditorFromKeyboard();this.#At.resolve()}}else{this.setEditingState(!1);this.#Bt();this._editorUndoBar?.hide();this.#At.resolve()}}}addNewEditorFromKeyboard(){this.currentLayer.canCreateNewEmptyEditor()&&this.currentLayer.addNewEditor()}updateToolbar(t){t.mode!==this.#ot&&this._eventBus.dispatch("switchannotationeditormode",{source:this,...t})}updateParams(t,e){if(this.#G){switch(t){case p.CREATE:this.currentLayer.addNewEditor(e);return;case p.HIGHLIGHT_DEFAULT_COLOR:this.#nt?.updateColor(e);break;case p.HIGHLIGHT_SHOW_ALL:this._eventBus.dispatch("reporttelemetry",{source:this,details:{type:"editing",data:{type:"highlight",action:"toggle_visibility"}}});(this.#ut||=new Map).set(t,e);this.showAllEditors("highlight",e)}for(const i of this.#lt)i.updateParams(t,e);for(const i of this.#G)i.updateDefaultParams(t,e)}}showAllEditors(t,e,i=!1){for(const i of this.#k.values())i.editorType===t&&i.show(e);(this.#ut?.get(p.HIGHLIGHT_SHOW_ALL)??!0)!==e&&this.#Lt([[p.HIGHLIGHT_SHOW_ALL,e]])}enableWaiting(t=!1){if(this.#et!==t){this.#et=t;for(const e of this.#I.values()){t?e.disableClick():e.enableClick();e.div.classList.toggle("waiting",t)}}}async#Nt(){if(!this.#tt){this.#tt=!0;const t=[];for(const e of this.#I.values())t.push(e.enable());await Promise.all(t);for(const t of this.#k.values())t.enable()}}#Bt(){this.unselectAll();if(this.#tt){this.#tt=!1;for(const t of this.#I.values())t.disable();for(const t of this.#k.values())t.disable()}}getEditors(t){const e=[];for(const i of this.#k.values())i.pageIndex===t&&e.push(i);return e}getEditor(t){return this.#k.get(t)}addEditor(t){this.#k.set(t.id,t)}removeEditor(t){if(t.div.contains(document.activeElement)){this.#X&&clearTimeout(this.#X);this.#X=setTimeout((()=>{this.focusMainContainer();this.#X=null}),0)}this.#k.delete(t.id);t.annotationElementId&&this.#at?.delete(t.annotationElementId);this.unselect(t);t.annotationElementId&&this.#U.has(t.annotationElementId)||this.#F?.remove(t.id)}addDeletedAnnotationElement(t){this.#U.add(t.annotationElementId);this.addChangedExistingAnnotation(t);t.deleted=!0}isDeletedAnnotationElement(t){return this.#U.has(t)}removeDeletedAnnotationElement(t){this.#U.delete(t.annotationElementId);this.removeChangedExistingAnnotation(t);t.deleted=!1}#Rt(t){const e=this.#I.get(t.pageIndex);if(e)e.addOrRebuild(t);else{this.addEditor(t);this.addToAnnotationStorage(t)}}setActiveEditor(t){if(this.#P!==t){this.#P=t;t&&this.#Lt(t.propertiesToUpdate)}}get#Ht(){let t=null;for(t of this.#lt);return t}updateUI(t){this.#Ht===t&&this.#Lt(t.propertiesToUpdate)}updateUIForDefaultProperties(t){this.#Lt(t.defaultPropertiesToUpdate)}toggleSelected(t){if(this.#lt.has(t)){this.#lt.delete(t);t.unselect();this.#Ct({hasSelectedEditor:this.hasSelection})}else{this.#lt.add(t);t.select();this.#Lt(t.propertiesToUpdate);this.#Ct({hasSelectedEditor:!0})}}setSelected(t){this.#B?.commitOrRemove();for(const e of this.#lt)e!==t&&e.unselect();this.#lt.clear();this.#lt.add(t);t.select();this.#Lt(t.propertiesToUpdate);this.#Ct({hasSelectedEditor:!0})}isSelected(t){return this.#lt.has(t)}get firstSelectedEditor(){return this.#lt.values().next().value}unselect(t){t.unselect();this.#lt.delete(t);this.#Ct({hasSelectedEditor:this.hasSelection})}get hasSelection(){return 0!==this.#lt.size}get isEnterHandled(){return 1===this.#lt.size&&this.firstSelectedEditor.isEnterHandled}undo(){this.#O.undo();this.#Ct({hasSomethingToUndo:this.#O.hasSomethingToUndo(),hasSomethingToRedo:!0,isEmpty:this.#Ot()});this._editorUndoBar?.hide()}redo(){this.#O.redo();this.#Ct({hasSomethingToUndo:!0,hasSomethingToRedo:this.#O.hasSomethingToRedo(),isEmpty:this.#Ot()})}addCommands(t){this.#O.add(t);this.#Ct({hasSomethingToUndo:!0,hasSomethingToRedo:!1,isEmpty:this.#Ot()})}cleanUndoStack(t){this.#O.cleanType(t)}#Ot(){if(0===this.#k.size)return!0;if(1===this.#k.size)for(const t of this.#k.values())return t.isEmpty();return!1}delete(){this.commitOrRemove();const t=this.currentLayer?.endDrawingSession(!0);if(!this.hasSelection&&!t)return;const e=t?[t]:[...this.#lt],undo=()=>{for(const t of e)this.#Rt(t)};this.addCommands({cmd:()=>{this._editorUndoBar?.show(undo,1===e.length?e[0].editorType:e.length);for(const t of e)t.remove()},undo,mustExec:!0})}commitOrRemove(){this.#P?.commitOrRemove()}hasSomethingToControl(){return this.#P||this.hasSelection}#Ft(t){for(const t of this.#lt)t.unselect();this.#lt.clear();for(const e of t)if(!e.isEmpty()){this.#lt.add(e);e.select()}this.#Ct({hasSelectedEditor:this.hasSelection})}selectAll(){for(const t of this.#lt)t.commit();this.#Ft(this.#k.values())}unselectAll(){if(this.#P){this.#P.commitOrRemove();if(this.#ot!==u.NONE)return}if(!this.#B?.commitOrRemove()&&this.hasSelection){for(const t of this.#lt)t.unselect();this.#lt.clear();this.#Ct({hasSelectedEditor:!1})}}translateSelectedEditors(t,e,i=!1){i||this.commitOrRemove();if(!this.hasSelection)return;this.#gt[0]+=t;this.#gt[1]+=e;const[s,n]=this.#gt,a=[...this.#lt];this.#ft&&clearTimeout(this.#ft);this.#ft=setTimeout((()=>{this.#ft=null;this.#gt[0]=this.#gt[1]=0;this.addCommands({cmd:()=>{for(const t of a)if(this.#k.has(t.id)){t.translateInPage(s,n);t.translationDone()}},undo:()=>{for(const t of a)if(this.#k.has(t.id)){t.translateInPage(-s,-n);t.translationDone()}},mustExec:!1})}),1e3);for(const i of a){i.translateInPage(t,e);i.translationDone()}}setUpDragSession(){if(this.hasSelection){this.disableUserSelect(!0);this.#z=new Map;for(const t of this.#lt)this.#z.set(t,{savedX:t.x,savedY:t.y,savedPageIndex:t.pageIndex,newX:0,newY:0,newPageIndex:-1})}}endDragSession(){if(!this.#z)return!1;this.disableUserSelect(!1);const t=this.#z;this.#z=null;let e=!1;for(const[{x:i,y:s,pageIndex:n},a]of t){a.newX=i;a.newY=s;a.newPageIndex=n;e||=i!==a.savedX||s!==a.savedY||n!==a.savedPageIndex}if(!e)return!1;const move=(t,e,i,s)=>{if(this.#k.has(t.id)){const n=this.#I.get(s);if(n)t._setParentAndPosition(n,e,i);else{t.pageIndex=s;t.x=e;t.y=i}}};this.addCommands({cmd:()=>{for(const[e,{newX:i,newY:s,newPageIndex:n}]of t)move(e,i,s,n)},undo:()=>{for(const[e,{savedX:i,savedY:s,savedPageIndex:n}]of t)move(e,i,s,n)},mustExec:!0});return!0}dragSelectedEditors(t,e){if(this.#z)for(const i of this.#z.keys())i.drag(t,e)}rebuild(t){if(null===t.parent){const e=this.getLayer(t.pageIndex);if(e){e.changeParent(t);e.addOrRebuild(t)}else{this.addEditor(t);this.addToAnnotationStorage(t);t.rebuild()}}else t.parent.addOrRebuild(t)}get isEditorHandlingKeyboard(){return this.getActive()?.shouldGetKeyboardEvents()||1===this.#lt.size&&this.firstSelectedEditor.shouldGetKeyboardEvents()}isActive(t){return this.#P===t}getActive(){return this.#P}getMode(){return this.#ot}get imageManager(){return shadow(this,"imageManager",new ImageManager)}getSelectionBoxes(t){if(!t)return null;const e=document.getSelection();for(let i=0,s=e.rangeCount;i({x:(e-s)/a,y:1-(t+r-i)/n,width:o/a,height:r/n});break;case"180":r=(t,e,r,o)=>({x:1-(t+r-i)/n,y:1-(e+o-s)/a,width:r/n,height:o/a});break;case"270":r=(t,e,r,o)=>({x:1-(e+o-s)/a,y:(t-i)/n,width:o/a,height:r/n});break;default:r=(t,e,r,o)=>({x:(t-i)/n,y:(e-s)/a,width:r/n,height:o/a})}const o=[];for(let t=0,i=e.rangeCount;tt.stopPropagation()),{signal:i});const onClick=t=>{t.preventDefault();this.#a._uiManager.editAltText(this.#a);this.#Kt&&this.#a._reportTelemetry({action:"pdfjs.image.alt_text.image_status_label_clicked",data:{label:this.#Qt}})};t.addEventListener("click",onClick,{capture:!0,signal:i});t.addEventListener("keydown",(e=>{if(e.target===t&&"Enter"===e.key){this.#Vt=!0;onClick(e)}}),{signal:i});await this.#Jt();return t}get#Qt(){return(this.#o?"added":null===this.#o&&this.guessedText&&"review")||"missing"}finish(){if(this.#zt){this.#zt.focus({focusVisible:this.#Vt});this.#Vt=!1}}isEmpty(){return this.#Kt?null===this.#o:!this.#o&&!this.#Ut}hasData(){return this.#Kt?null!==this.#o||!!this.#qt:this.isEmpty()}get guessedText(){return this.#qt}async setGuessedText(t){if(null===this.#o){this.#qt=t;this.#Xt=await AltText._l10n.get("pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer",{generatedAltText:t});this.#Jt()}}toggleAltTextBadge(t=!1){if(this.#Kt&&!this.#o){if(!this.#jt){const t=this.#jt=document.createElement("div");t.className="noAltTextBadge";this.#a.div.append(t)}this.#jt.classList.toggle("hidden",!t)}else{this.#jt?.remove();this.#jt=null}}serialize(t){let e=this.#o;t||this.#qt!==e||(e=this.#Xt);return{altText:e,decorative:this.#Ut,guessedText:this.#qt,textWithDisclaimer:this.#Xt}}get data(){return{altText:this.#o,decorative:this.#Ut}}set data({altText:t,decorative:e,guessedText:i,textWithDisclaimer:s,cancel:n=!1}){if(i){this.#qt=i;this.#Xt=s}if(this.#o!==t||this.#Ut!==e){if(!n){this.#o=t;this.#Ut=e}this.#Jt()}}toggle(t=!1){if(this.#zt){if(!t&&this.#Wt){clearTimeout(this.#Wt);this.#Wt=null}this.#zt.disabled=!t}}shown(){this.#a._reportTelemetry({action:"pdfjs.image.alt_text.image_status_label_displayed",data:{label:this.#Qt}})}destroy(){this.#zt?.remove();this.#zt=null;this.#Gt=null;this.#$t=null;this.#jt?.remove();this.#jt=null}async#Jt(){const t=this.#zt;if(!t)return;if(this.#Kt){t.classList.toggle("done",!!this.#o);t.setAttribute("data-l10n-id",AltText.#Yt[this.#Qt]);this.#Gt?.setAttribute("data-l10n-id",AltText.#Yt[`${this.#Qt}-label`]);if(!this.#o){this.#$t?.remove();return}}else{if(!this.#o&&!this.#Ut){t.classList.remove("done");this.#$t?.remove();return}t.classList.add("done");t.setAttribute("data-l10n-id","pdfjs-editor-alt-text-edit-button")}let e=this.#$t;if(!e){this.#$t=e=document.createElement("span");e.className="tooltip";e.setAttribute("role","tooltip");e.id=`alt-text-tooltip-${this.#a.id}`;const i=100,s=this.#a._uiManager._signal;s.addEventListener("abort",(()=>{clearTimeout(this.#Wt);this.#Wt=null}),{once:!0});t.addEventListener("mouseenter",(()=>{this.#Wt=setTimeout((()=>{this.#Wt=null;this.#$t.classList.add("show");this.#a._reportTelemetry({action:"alt_text_tooltip"})}),i)}),{signal:s});t.addEventListener("mouseleave",(()=>{if(this.#Wt){clearTimeout(this.#Wt);this.#Wt=null}this.#$t?.classList.remove("show")}),{signal:s})}if(this.#Ut)e.setAttribute("data-l10n-id","pdfjs-editor-alt-text-decorative-tooltip");else{e.removeAttribute("data-l10n-id");e.textContent=this.#o}e.parentNode||t.append(e);const i=this.#a.getElementForAltText();i?.setAttribute("aria-describedby",e.id)}}class TouchManager{#mt;#Zt=!1;#te=null;#ee;#ie;#se;#ne;#ae=null;#re;#oe=null;#le;#he=null;constructor({container:t,isPinchingDisabled:e=null,isPinchingStopped:i=null,onPinchStart:s=null,onPinching:n=null,onPinchEnd:a=null,signal:r}){this.#mt=t;this.#te=i;this.#ee=e;this.#ie=s;this.#se=n;this.#ne=a;this.#le=new AbortController;this.#re=AbortSignal.any([r,this.#le.signal]);t.addEventListener("touchstart",this.#de.bind(this),{passive:!1,signal:this.#re})}get MIN_TOUCH_DISTANCE_TO_PINCH(){return 35/OutputScale.pixelRatio}#de(t){if(this.#ee?.())return;if(1===t.touches.length){if(this.#ae)return;const t=this.#ae=new AbortController,e=AbortSignal.any([this.#re,t.signal]),i=this.#mt,s={capture:!0,signal:e,passive:!1},cancelPointerDown=t=>{if("touch"===t.pointerType){this.#ae?.abort();this.#ae=null}};i.addEventListener("pointerdown",(t=>{if("touch"===t.pointerType){stopEvent(t);cancelPointerDown(t)}}),s);i.addEventListener("pointerup",cancelPointerDown,s);i.addEventListener("pointercancel",cancelPointerDown,s);return}if(!this.#he){this.#he=new AbortController;const t=AbortSignal.any([this.#re,this.#he.signal]),e=this.#mt,i={signal:t,capture:!1,passive:!1};e.addEventListener("touchmove",this.#ce.bind(this),i);const s=this.#ue.bind(this);e.addEventListener("touchend",s,i);e.addEventListener("touchcancel",s,i);i.capture=!0;e.addEventListener("pointerdown",stopEvent,i);e.addEventListener("pointermove",stopEvent,i);e.addEventListener("pointercancel",stopEvent,i);e.addEventListener("pointerup",stopEvent,i);this.#ie?.()}stopEvent(t);if(2!==t.touches.length||this.#te?.()){this.#oe=null;return}let[e,i]=t.touches;e.identifier>i.identifier&&([e,i]=[i,e]);this.#oe={touch0X:e.screenX,touch0Y:e.screenY,touch1X:i.screenX,touch1Y:i.screenY}}#ce(t){if(!this.#oe||2!==t.touches.length)return;stopEvent(t);let[e,i]=t.touches;e.identifier>i.identifier&&([e,i]=[i,e]);const{screenX:s,screenY:n}=e,{screenX:a,screenY:r}=i,o=this.#oe,{touch0X:l,touch0Y:h,touch1X:d,touch1Y:c}=o,u=d-l,p=c-h,g=a-s,f=r-n,m=Math.hypot(g,f)||1,b=Math.hypot(u,p)||1;if(!this.#Zt&&Math.abs(b-m)<=TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH)return;o.touch0X=s;o.touch0Y=n;o.touch1X=a;o.touch1Y=r;if(!this.#Zt){this.#Zt=!0;return}const A=[(s+a)/2,(n+r)/2];this.#se?.(A,b,m)}#ue(t){if(!(t.touches.length>=2)){if(this.#he){this.#he.abort();this.#he=null;this.#ne?.()}if(this.#oe){stopEvent(t);this.#oe=null;this.#Zt=!1}}}destroy(){this.#le?.abort();this.#le=null;this.#ae?.abort();this.#ae=null}}class AnnotationEditor{#pe=null;#ge=null;#o=null;#fe=!1;#me=null;#be="";#Ae=!1;#we=null;#ye=null;#ve=null;#xe=null;#Ee="";#_e=!1;#Se=null;#Ce=!1;#Te=!1;#Me=!1;#De=null;#Pe=0;#ke=0;#Ie=null;#Re=null;isSelected=!1;_isCopy=!1;_editToolbar=null;_initialOptions=Object.create(null);_initialData=null;_isVisible=!0;_uiManager=null;_focusEventsAllowed=!0;static _l10n=null;static _l10nResizer=null;#Fe=!1;#Le=AnnotationEditor._zIndex++;static _borderLineWidth=-1;static _colorManager=new ColorManager;static _zIndex=1;static _telemetryTimeout=1e3;static get _resizerKeyboardManager(){const t=AnnotationEditor.prototype._resizeWithKeyboard,e=AnnotationEditorUIManager.TRANSLATE_SMALL,i=AnnotationEditorUIManager.TRANSLATE_BIG;return shadow(this,"_resizerKeyboardManager",new KeyboardManager([[["ArrowLeft","mac+ArrowLeft"],t,{args:[-e,0]}],[["ctrl+ArrowLeft","mac+shift+ArrowLeft"],t,{args:[-i,0]}],[["ArrowRight","mac+ArrowRight"],t,{args:[e,0]}],[["ctrl+ArrowRight","mac+shift+ArrowRight"],t,{args:[i,0]}],[["ArrowUp","mac+ArrowUp"],t,{args:[0,-e]}],[["ctrl+ArrowUp","mac+shift+ArrowUp"],t,{args:[0,-i]}],[["ArrowDown","mac+ArrowDown"],t,{args:[0,e]}],[["ctrl+ArrowDown","mac+shift+ArrowDown"],t,{args:[0,i]}],[["Escape","mac+Escape"],AnnotationEditor.prototype._stopResizingWithKeyboard]]))}constructor(t){this.parent=t.parent;this.id=t.id;this.width=this.height=null;this.pageIndex=t.parent.pageIndex;this.name=t.name;this.div=null;this._uiManager=t.uiManager;this.annotationElementId=null;this._willKeepAspectRatio=!1;this._initialOptions.isCentered=t.isCentered;this._structTreeParentId=null;const{rotation:e,rawDims:{pageWidth:i,pageHeight:s,pageX:n,pageY:a}}=this.parent.viewport;this.rotation=e;this.pageRotation=(360+e-this._uiManager.viewParameters.rotation)%360;this.pageDimensions=[i,s];this.pageTranslation=[n,a];const[r,o]=this.parentDimensions;this.x=t.x/r;this.y=t.y/o;this.isAttachedToDOM=!1;this.deleted=!1}get editorType(){return Object.getPrototypeOf(this).constructor._type}static get isDrawer(){return!1}static get _defaultLineColor(){return shadow(this,"_defaultLineColor",this._colorManager.getHexCode("CanvasText"))}static deleteAnnotationElement(t){const e=new FakeEditor({id:t.parent.getNextId(),parent:t.parent,uiManager:t._uiManager});e.annotationElementId=t.annotationElementId;e.deleted=!0;e._uiManager.addToAnnotationStorage(e)}static initialize(t,e){AnnotationEditor._l10n??=t;AnnotationEditor._l10nResizer||=Object.freeze({topLeft:"pdfjs-editor-resizer-top-left",topMiddle:"pdfjs-editor-resizer-top-middle",topRight:"pdfjs-editor-resizer-top-right",middleRight:"pdfjs-editor-resizer-middle-right",bottomRight:"pdfjs-editor-resizer-bottom-right",bottomMiddle:"pdfjs-editor-resizer-bottom-middle",bottomLeft:"pdfjs-editor-resizer-bottom-left",middleLeft:"pdfjs-editor-resizer-middle-left"});if(-1!==AnnotationEditor._borderLineWidth)return;const i=getComputedStyle(document.documentElement);AnnotationEditor._borderLineWidth=parseFloat(i.getPropertyValue("--outline-width"))||0}static updateDefaultParams(t,e){}static get defaultPropertiesToUpdate(){return[]}static isHandlingMimeForPasting(t){return!1}static paste(t,e){unreachable("Not implemented")}get propertiesToUpdate(){return[]}get _isDraggable(){return this.#Fe}set _isDraggable(t){this.#Fe=t;this.div?.classList.toggle("draggable",t)}get isEnterHandled(){return!0}center(){const[t,e]=this.pageDimensions;switch(this.parentRotation){case 90:this.x-=this.height*e/(2*t);this.y+=this.width*t/(2*e);break;case 180:this.x+=this.width/2;this.y+=this.height/2;break;case 270:this.x+=this.height*e/(2*t);this.y-=this.width*t/(2*e);break;default:this.x-=this.width/2;this.y-=this.height/2}this.fixAndSetPosition()}addCommands(t){this._uiManager.addCommands(t)}get currentLayer(){return this._uiManager.currentLayer}setInBackground(){this.div.style.zIndex=0}setInForeground(){this.div.style.zIndex=this.#Le}setParent(t){if(null!==t){this.pageIndex=t.pageIndex;this.pageDimensions=t.pageDimensions}else this.#Oe();this.parent=t}focusin(t){this._focusEventsAllowed&&(this.#_e?this.#_e=!1:this.parent.setSelected(this))}focusout(t){if(!this._focusEventsAllowed)return;if(!this.isAttachedToDOM)return;const e=t.relatedTarget;if(!e?.closest(`#${this.id}`)){t.preventDefault();this.parent?.isMultipleSelection||this.commitOrRemove()}}commitOrRemove(){this.isEmpty()?this.remove():this.commit()}commit(){this.addToAnnotationStorage()}addToAnnotationStorage(){this._uiManager.addToAnnotationStorage(this)}setAt(t,e,i,s){const[n,a]=this.parentDimensions;[i,s]=this.screenToPageTranslation(i,s);this.x=(t+i)/n;this.y=(e+s)/a;this.fixAndSetPosition()}_moveAfterPaste(t,e){const[i,s]=this.parentDimensions;this.setAt(t*i,e*s,this.width*i,this.height*s);this._onTranslated()}#Ne([t,e],i,s){[i,s]=this.screenToPageTranslation(i,s);this.x+=i/t;this.y+=s/e;this._onTranslating(this.x,this.y);this.fixAndSetPosition()}translate(t,e){this.#Ne(this.parentDimensions,t,e)}translateInPage(t,e){this.#Se||=[this.x,this.y,this.width,this.height];this.#Ne(this.pageDimensions,t,e);this.div.scrollIntoView({block:"nearest"})}translationDone(){this._onTranslated(this.x,this.y)}drag(t,e){this.#Se||=[this.x,this.y,this.width,this.height];const{div:i,parentDimensions:[s,n]}=this;this.x+=t/s;this.y+=e/n;if(this.parent&&(this.x<0||this.x>1||this.y<0||this.y>1)){const{x:t,y:e}=this.div.getBoundingClientRect();if(this.parent.findNewParent(this,t,e)){this.x-=Math.floor(this.x);this.y-=Math.floor(this.y)}}let{x:a,y:r}=this;const[o,l]=this.getBaseTranslation();a+=o;r+=l;const{style:h}=i;h.left=`${(100*a).toFixed(2)}%`;h.top=`${(100*r).toFixed(2)}%`;this._onTranslating(a,r);i.scrollIntoView({block:"nearest"})}_onTranslating(t,e){}_onTranslated(t,e){}get _hasBeenMoved(){return!!this.#Se&&(this.#Se[0]!==this.x||this.#Se[1]!==this.y)}get _hasBeenResized(){return!!this.#Se&&(this.#Se[2]!==this.width||this.#Se[3]!==this.height)}getBaseTranslation(){const[t,e]=this.parentDimensions,{_borderLineWidth:i}=AnnotationEditor,s=i/t,n=i/e;switch(this.rotation){case 90:return[-s,n];case 180:return[s,n];case 270:return[s,-n];default:return[-s,-n]}}get _mustFixPosition(){return!0}fixAndSetPosition(t=this.rotation){const{div:{style:e},pageDimensions:[i,s]}=this;let{x:n,y:a,width:r,height:o}=this;r*=i;o*=s;n*=i;a*=s;if(this._mustFixPosition)switch(t){case 0:n=MathClamp(n,0,i-r);a=MathClamp(a,0,s-o);break;case 90:n=MathClamp(n,0,i-o);a=MathClamp(a,r,s);break;case 180:n=MathClamp(n,r,i);a=MathClamp(a,o,s);break;case 270:n=MathClamp(n,o,i);a=MathClamp(a,0,s-r)}this.x=n/=i;this.y=a/=s;const[l,h]=this.getBaseTranslation();n+=l;a+=h;e.left=`${(100*n).toFixed(2)}%`;e.top=`${(100*a).toFixed(2)}%`;this.moveInDOM()}static#Be(t,e,i){switch(i){case 90:return[e,-t];case 180:return[-t,-e];case 270:return[-e,t];default:return[t,e]}}screenToPageTranslation(t,e){return AnnotationEditor.#Be(t,e,this.parentRotation)}pageTranslationToScreen(t,e){return AnnotationEditor.#Be(t,e,360-this.parentRotation)}#He(t){switch(t){case 90:{const[t,e]=this.pageDimensions;return[0,-t/e,e/t,0]}case 180:return[-1,0,0,-1];case 270:{const[t,e]=this.pageDimensions;return[0,t/e,-e/t,0]}default:return[1,0,0,1]}}get parentScale(){return this._uiManager.viewParameters.realScale}get parentRotation(){return(this._uiManager.viewParameters.rotation+this.pageRotation)%360}get parentDimensions(){const{parentScale:t,pageDimensions:[e,i]}=this;return[e*t,i*t]}setDims(t,e){const[i,s]=this.parentDimensions,{style:n}=this.div;n.width=`${(100*t/i).toFixed(2)}%`;this.#Ae||(n.height=`${(100*e/s).toFixed(2)}%`)}fixDims(){const{style:t}=this.div,{height:e,width:i}=t,s=i.endsWith("%"),n=!this.#Ae&&e.endsWith("%");if(s&&n)return;const[a,r]=this.parentDimensions;s||(t.width=`${(100*parseFloat(i)/a).toFixed(2)}%`);this.#Ae||n||(t.height=`${(100*parseFloat(e)/r).toFixed(2)}%`)}getInitialTranslation(){return[0,0]}#Ue(){if(this.#we)return;this.#we=document.createElement("div");this.#we.classList.add("resizers");const t=this._willKeepAspectRatio?["topLeft","topRight","bottomRight","bottomLeft"]:["topLeft","topMiddle","topRight","middleRight","bottomRight","bottomMiddle","bottomLeft","middleLeft"],e=this._uiManager._signal;for(const i of t){const t=document.createElement("div");this.#we.append(t);t.classList.add("resizer",i);t.setAttribute("data-resizer-name",i);t.addEventListener("pointerdown",this.#ze.bind(this,i),{signal:e});t.addEventListener("contextmenu",noContextMenu,{signal:e});t.tabIndex=-1}this.div.prepend(this.#we)}#ze(t,e){e.preventDefault();const{isMac:i}=util_FeatureTest.platform;if(0!==e.button||e.ctrlKey&&i)return;this.#o?.toggle(!1);const s=this._isDraggable;this._isDraggable=!1;this.#ye=[e.screenX,e.screenY];const n=new AbortController,a=this._uiManager.combinedSignal(n);this.parent.togglePointerEvents(!1);window.addEventListener("pointermove",this.#Ge.bind(this,t),{passive:!0,capture:!0,signal:a});window.addEventListener("touchmove",stopEvent,{passive:!1,signal:a});window.addEventListener("contextmenu",noContextMenu,{signal:a});this.#ve={savedX:this.x,savedY:this.y,savedWidth:this.width,savedHeight:this.height};const r=this.parent.div.style.cursor,o=this.div.style.cursor;this.div.style.cursor=this.parent.div.style.cursor=window.getComputedStyle(e.target).cursor;const pointerUpCallback=()=>{n.abort();this.parent.togglePointerEvents(!0);this.#o?.toggle(!0);this._isDraggable=s;this.parent.div.style.cursor=r;this.div.style.cursor=o;this.#$e()};window.addEventListener("pointerup",pointerUpCallback,{signal:a});window.addEventListener("blur",pointerUpCallback,{signal:a})}#We(t,e,i,s){this.width=i;this.height=s;this.x=t;this.y=e;const[n,a]=this.parentDimensions;this.setDims(n*i,a*s);this.fixAndSetPosition();this._onResized()}_onResized(){}#$e(){if(!this.#ve)return;const{savedX:t,savedY:e,savedWidth:i,savedHeight:s}=this.#ve;this.#ve=null;const n=this.x,a=this.y,r=this.width,o=this.height;n===t&&a===e&&r===i&&o===s||this.addCommands({cmd:this.#We.bind(this,n,a,r,o),undo:this.#We.bind(this,t,e,i,s),mustExec:!0})}static _round(t){return Math.round(1e4*t)/1e4}#Ge(t,e){const[i,s]=this.parentDimensions,n=this.x,a=this.y,r=this.width,o=this.height,l=AnnotationEditor.MIN_SIZE/i,h=AnnotationEditor.MIN_SIZE/s,d=this.#He(this.rotation),transf=(t,e)=>[d[0]*t+d[2]*e,d[1]*t+d[3]*e],c=this.#He(360-this.rotation);let u,p,g=!1,f=!1;switch(t){case"topLeft":g=!0;u=(t,e)=>[0,0];p=(t,e)=>[t,e];break;case"topMiddle":u=(t,e)=>[t/2,0];p=(t,e)=>[t/2,e];break;case"topRight":g=!0;u=(t,e)=>[t,0];p=(t,e)=>[0,e];break;case"middleRight":f=!0;u=(t,e)=>[t,e/2];p=(t,e)=>[0,e/2];break;case"bottomRight":g=!0;u=(t,e)=>[t,e];p=(t,e)=>[0,0];break;case"bottomMiddle":u=(t,e)=>[t/2,e];p=(t,e)=>[t/2,0];break;case"bottomLeft":g=!0;u=(t,e)=>[0,e];p=(t,e)=>[t,0];break;case"middleLeft":f=!0;u=(t,e)=>[0,e/2];p=(t,e)=>[t,e/2]}const m=u(r,o),b=p(r,o);let A=transf(...b);const w=AnnotationEditor._round(n+A[0]),y=AnnotationEditor._round(a+A[1]);let v,x,E=1,_=1;if(e.fromKeyboard)({deltaX:v,deltaY:x}=e);else{const{screenX:t,screenY:i}=e,[s,n]=this.#ye;[v,x]=this.screenToPageTranslation(t-s,i-n);this.#ye[0]=t;this.#ye[1]=i}[v,x]=(S=v/i,C=x/s,[c[0]*S+c[2]*C,c[1]*S+c[3]*C]);var S,C;if(g){const t=Math.hypot(r,o);E=_=Math.max(Math.min(Math.hypot(b[0]-m[0]-v,b[1]-m[1]-x)/t,1/r,1/o),l/r,h/o)}else f?E=MathClamp(Math.abs(b[0]-m[0]-v),l,1)/r:_=MathClamp(Math.abs(b[1]-m[1]-x),h,1)/o;const T=AnnotationEditor._round(r*E),M=AnnotationEditor._round(o*_);A=transf(...p(T,M));const D=w-A[0],P=y-A[1];this.#Se||=[this.x,this.y,this.width,this.height];this.width=T;this.height=M;this.x=D;this.y=P;this.setDims(i*T,s*M);this.fixAndSetPosition();this._onResizing()}_onResizing(){}altTextFinish(){this.#o?.finish()}async addEditToolbar(){if(this._editToolbar||this.#Te)return this._editToolbar;this._editToolbar=new EditorToolbar(this);this.div.append(this._editToolbar.render());this.#o&&await this._editToolbar.addAltText(this.#o);return this._editToolbar}removeEditToolbar(){if(this._editToolbar){this._editToolbar.remove();this._editToolbar=null;this.#o?.destroy()}}addContainer(t){const e=this._editToolbar?.div;e?e.before(t):this.div.append(t)}getClientDimensions(){return this.div.getBoundingClientRect()}async addAltTextButton(){if(!this.#o){AltText.initialize(AnnotationEditor._l10n);this.#o=new AltText(this);if(this.#pe){this.#o.data=this.#pe;this.#pe=null}await this.addEditToolbar()}}get altTextData(){return this.#o?.data}set altTextData(t){this.#o&&(this.#o.data=t)}get guessedAltText(){return this.#o?.guessedText}async setGuessedAltText(t){await(this.#o?.setGuessedText(t))}serializeAltText(t){return this.#o?.serialize(t)}hasAltText(){return!!this.#o&&!this.#o.isEmpty()}hasAltTextData(){return this.#o?.hasData()??!1}render(){const t=this.div=document.createElement("div");t.setAttribute("data-editor-rotation",(360-this.rotation)%360);t.className=this.name;t.setAttribute("id",this.id);t.tabIndex=this.#fe?-1:0;t.setAttribute("role","application");this.defaultL10nId&&t.setAttribute("data-l10n-id",this.defaultL10nId);this._isVisible||t.classList.add("hidden");this.setInForeground();this.#Ve();const[e,i]=this.parentDimensions;if(this.parentRotation%180!=0){t.style.maxWidth=`${(100*i/e).toFixed(2)}%`;t.style.maxHeight=`${(100*e/i).toFixed(2)}%`}const[s,n]=this.getInitialTranslation();this.translate(s,n);bindEvents(this,t,["keydown","pointerdown","dblclick"]);this.isResizable&&this._uiManager._supportsPinchToZoom&&(this.#Re||=new TouchManager({container:t,isPinchingDisabled:()=>!this.isSelected,onPinchStart:this.#je.bind(this),onPinching:this.#qe.bind(this),onPinchEnd:this.#Xe.bind(this),signal:this._uiManager._signal}));this._uiManager._editorUndoBar?.hide();return t}#je(){this.#ve={savedX:this.x,savedY:this.y,savedWidth:this.width,savedHeight:this.height};this.#o?.toggle(!1);this.parent.togglePointerEvents(!1)}#qe(t,e,i){let s=i/e*.7+1-.7;if(1===s)return;const n=this.#He(this.rotation),transf=(t,e)=>[n[0]*t+n[2]*e,n[1]*t+n[3]*e],[a,r]=this.parentDimensions,o=this.x,l=this.y,h=this.width,d=this.height,c=AnnotationEditor.MIN_SIZE/a,u=AnnotationEditor.MIN_SIZE/r;s=Math.max(Math.min(s,1/h,1/d),c/h,u/d);const p=AnnotationEditor._round(h*s),g=AnnotationEditor._round(d*s);if(p===h&&g===d)return;this.#Se||=[o,l,h,d];const f=transf(h/2,d/2),m=AnnotationEditor._round(o+f[0]),b=AnnotationEditor._round(l+f[1]),A=transf(p/2,g/2);this.x=m-A[0];this.y=b-A[1];this.width=p;this.height=g;this.setDims(a*p,r*g);this.fixAndSetPosition();this._onResizing()}#Xe(){this.#o?.toggle(!0);this.parent.togglePointerEvents(!0);this.#$e()}pointerdown(t){const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)t.preventDefault();else{this.#_e=!0;this._isDraggable?this.#Ke(t):this.#Ye(t)}}#Ye(t){const{isMac:e}=util_FeatureTest.platform;t.ctrlKey&&!e||t.shiftKey||t.metaKey&&e?this.parent.toggleSelected(this):this.parent.setSelected(this)}#Ke(t){const{isSelected:e}=this;this._uiManager.setUpDragSession();let i=!1;const s=new AbortController,n=this._uiManager.combinedSignal(s),a={capture:!0,passive:!1,signal:n},cancelDrag=t=>{s.abort();this.#me=null;this.#_e=!1;this._uiManager.endDragSession()||this.#Ye(t);i&&this._onStopDragging()};if(e){this.#Pe=t.clientX;this.#ke=t.clientY;this.#me=t.pointerId;this.#be=t.pointerType;window.addEventListener("pointermove",(t=>{if(!i){i=!0;this._onStartDragging()}const{clientX:e,clientY:s,pointerId:n}=t;if(n!==this.#me){stopEvent(t);return}const[a,r]=this.screenToPageTranslation(e-this.#Pe,s-this.#ke);this.#Pe=e;this.#ke=s;this._uiManager.dragSelectedEditors(a,r)}),a);window.addEventListener("touchmove",stopEvent,a);window.addEventListener("pointerdown",(t=>{t.pointerType===this.#be&&(this.#Re||t.isPrimary)&&cancelDrag(t);stopEvent(t)}),a)}const pointerUpCallback=t=>{this.#me&&this.#me!==t.pointerId?stopEvent(t):cancelDrag(t)};window.addEventListener("pointerup",pointerUpCallback,{signal:n});window.addEventListener("blur",pointerUpCallback,{signal:n})}_onStartDragging(){}_onStopDragging(){}moveInDOM(){this.#De&&clearTimeout(this.#De);this.#De=setTimeout((()=>{this.#De=null;this.parent?.moveEditorInDOM(this)}),0)}_setParentAndPosition(t,e,i){t.changeParent(this);this.x=e;this.y=i;this.fixAndSetPosition();this._onTranslated()}getRect(t,e,i=this.rotation){const s=this.parentScale,[n,a]=this.pageDimensions,[r,o]=this.pageTranslation,l=t/s,h=e/s,d=this.x*n,c=this.y*a,u=this.width*n,p=this.height*a;switch(i){case 0:return[d+l+r,a-c-h-p+o,d+l+u+r,a-c-h+o];case 90:return[d+h+r,a-c+l+o,d+h+p+r,a-c+l+u+o];case 180:return[d-l-u+r,a-c+h+o,d-l+r,a-c+h+p+o];case 270:return[d-h-p+r,a-c-l-u+o,d-h+r,a-c-l+o];default:throw new Error("Invalid rotation")}}getRectInCurrentCoords(t,e){const[i,s,n,a]=t,r=n-i,o=a-s;switch(this.rotation){case 0:return[i,e-a,r,o];case 90:return[i,e-s,o,r];case 180:return[n,e-s,r,o];case 270:return[n,e-a,o,r];default:throw new Error("Invalid rotation")}}onceAdded(t){}isEmpty(){return!1}enableEditMode(){if(this.isInEditMode())return!1;this.parent.setEditingState(!1);this.#Te=!0;return!0}disableEditMode(){if(!this.isInEditMode())return!1;this.parent.setEditingState(!0);this.#Te=!1;return!0}isInEditMode(){return this.#Te}shouldGetKeyboardEvents(){return this.#Me}needsToBeRebuilt(){return this.div&&!this.isAttachedToDOM}get isOnScreen(){const{top:t,left:e,bottom:i,right:s}=this.getClientDimensions(),{innerHeight:n,innerWidth:a}=window;return e0&&t0}#Ve(){if(this.#xe||!this.div)return;this.#xe=new AbortController;const t=this._uiManager.combinedSignal(this.#xe);this.div.addEventListener("focusin",this.focusin.bind(this),{signal:t});this.div.addEventListener("focusout",this.focusout.bind(this),{signal:t})}rebuild(){this.#Ve()}rotate(t){}resize(){}serializeDeleted(){return{id:this.annotationElementId,deleted:!0,pageIndex:this.pageIndex,popupRef:this._initialData?.popupRef||""}}serialize(t=!1,e=null){unreachable("An editor must be serializable")}static async deserialize(t,e,i){const s=new this.prototype.constructor({parent:e,id:e.getNextId(),uiManager:i});s.rotation=t.rotation;s.#pe=t.accessibilityData;s._isCopy=t.isCopy||!1;const[n,a]=s.pageDimensions,[r,o,l,h]=s.getRectInCurrentCoords(t.rect,a);s.x=r/n;s.y=o/a;s.width=l/n;s.height=h/a;return s}get hasBeenModified(){return!!this.annotationElementId&&(this.deleted||null!==this.serialize())}remove(){this.#xe?.abort();this.#xe=null;this.isEmpty()||this.commit();this.parent?this.parent.remove(this):this._uiManager.removeEditor(this);if(this.#De){clearTimeout(this.#De);this.#De=null}this.#Oe();this.removeEditToolbar();if(this.#Ie){for(const t of this.#Ie.values())clearTimeout(t);this.#Ie=null}this.parent=null;this.#Re?.destroy();this.#Re=null}get isResizable(){return!1}makeResizable(){if(this.isResizable){this.#Ue();this.#we.classList.remove("hidden")}}get toolbarPosition(){return null}keydown(t){if(!this.isResizable||t.target!==this.div||"Enter"!==t.key)return;this._uiManager.setSelected(this);this.#ve={savedX:this.x,savedY:this.y,savedWidth:this.width,savedHeight:this.height};const e=this.#we.children;if(!this.#ge){this.#ge=Array.from(e);const t=this.#Qe.bind(this),i=this.#Je.bind(this),s=this._uiManager._signal;for(const e of this.#ge){const n=e.getAttribute("data-resizer-name");e.setAttribute("role","spinbutton");e.addEventListener("keydown",t,{signal:s});e.addEventListener("blur",i,{signal:s});e.addEventListener("focus",this.#Ze.bind(this,n),{signal:s});e.setAttribute("data-l10n-id",AnnotationEditor._l10nResizer[n])}}const i=this.#ge[0];let s=0;for(const t of e){if(t===i)break;s++}const n=(360-this.rotation+this.parentRotation)%360/90*(this.#ge.length/4);if(n!==s){if(ns)for(let t=0;t{this.div?.classList.contains("selectedEditor")&&this._editToolbar?.show()}))}}unselect(){if(this.isSelected){this.isSelected=!1;this.#we?.classList.add("hidden");this.div?.classList.remove("selectedEditor");this.div?.contains(document.activeElement)&&this._uiManager.currentLayer.div.focus({preventScroll:!0});this._editToolbar?.hide();this.#o?.toggleAltTextBadge(!0)}}updateParams(t,e){}disableEditing(){}enableEditing(){}get canChangeContent(){return!1}enterInEditMode(){if(this.canChangeContent){this.enableEditMode();this.div.focus()}}dblclick(t){this.enterInEditMode();this.parent.updateToolbar({mode:this.constructor._editorType,editId:this.id})}getElementForAltText(){return this.div}get contentDiv(){return this.div}get isEditing(){return this.#Ce}set isEditing(t){this.#Ce=t;if(this.parent)if(t){this.parent.setSelected(this);this.parent.setActiveEditor(this)}else this.parent.setActiveEditor(null)}setAspectRatio(t,e){this.#Ae=!0;const i=t/e,{style:s}=this.div;s.aspectRatio=i;s.height="auto"}static get MIN_SIZE(){return 16}static canCreateNewEmptyEditor(){return!0}get telemetryInitialData(){return{action:"added"}}get telemetryFinalData(){return null}_reportTelemetry(t,e=!1){if(e){this.#Ie||=new Map;const{action:e}=t;let i=this.#Ie.get(e);i&&clearTimeout(i);i=setTimeout((()=>{this._reportTelemetry(t);this.#Ie.delete(e);0===this.#Ie.size&&(this.#Ie=null)}),AnnotationEditor._telemetryTimeout);this.#Ie.set(e,i)}else{t.type||=this.editorType;this._uiManager._eventBus.dispatch("reporttelemetry",{source:this,details:{type:"editing",data:t}})}}show(t=this._isVisible){this.div.classList.toggle("hidden",!t);this._isVisible=t}enable(){this.div&&(this.div.tabIndex=0);this.#fe=!1}disable(){this.div&&(this.div.tabIndex=-1);this.#fe=!0}renderAnnotationElement(t){let e=t.container.querySelector(".annotationContent");if(e){if("CANVAS"===e.nodeName){const t=e;e=document.createElement("div");e.classList.add("annotationContent",this.editorType);t.before(e)}}else{e=document.createElement("div");e.classList.add("annotationContent",this.editorType);t.container.prepend(e)}return e}resetAnnotationElement(t){const{firstChild:e}=t.container;"DIV"===e?.nodeName&&e.classList.contains("annotationContent")&&e.remove()}}class FakeEditor extends AnnotationEditor{constructor(t){super(t);this.annotationElementId=t.annotationElementId;this.deleted=!0}serialize(){return this.serializeDeleted()}}const $=3285377520,W=4294901760,V=65535;class MurmurHash3_64{constructor(t){this.h1=t?4294967295&t:$;this.h2=t?4294967295&t:$}update(t){let e,i;if("string"==typeof t){e=new Uint8Array(2*t.length);i=0;for(let s=0,n=t.length;s>>8;e[i++]=255&n}}}else{if(!ArrayBuffer.isView(t))throw new Error("Invalid data format, must be a string or TypedArray.");e=t.slice();i=e.byteLength}const s=i>>2,n=i-4*s,a=new Uint32Array(e.buffer,0,s);let r=0,o=0,l=this.h1,h=this.h2;const d=3432918353,c=461845907,u=11601,p=13715;for(let t=0;t>>17;r=r*c&W|r*p&V;l^=r;l=l<<13|l>>>19;l=5*l+3864292196}else{o=a[t];o=o*d&W|o*u&V;o=o<<15|o>>>17;o=o*c&W|o*p&V;h^=o;h=h<<13|h>>>19;h=5*h+3864292196}r=0;switch(n){case 3:r^=e[4*s+2]<<16;case 2:r^=e[4*s+1]<<8;case 1:r^=e[4*s];r=r*d&W|r*u&V;r=r<<15|r>>>17;r=r*c&W|r*p&V;1&s?l^=r:h^=r}this.h1=l;this.h2=h}hexdigest(){let t=this.h1,e=this.h2;t^=e>>>1;t=3981806797*t&W|36045*t&V;e=4283543511*e&W|(2950163797*(e<<16|t>>>16)&W)>>>16;t^=e>>>1;t=444984403*t&W|60499*t&V;e=3301882366*e&W|(3120437893*(e<<16|t>>>16)&W)>>>16;t^=e>>>1;return(t>>>0).toString(16).padStart(8,"0")+(e>>>0).toString(16).padStart(8,"0")}}const j=Object.freeze({map:null,hash:"",transfer:void 0});class AnnotationStorage{#ei=!1;#ii=null;#si=new Map;constructor(){this.onSetModified=null;this.onResetModified=null;this.onAnnotationEditor=null}getValue(t,e){const i=this.#si.get(t);return void 0===i?e:Object.assign(e,i)}getRawValue(t){return this.#si.get(t)}remove(t){this.#si.delete(t);0===this.#si.size&&this.resetModified();if("function"==typeof this.onAnnotationEditor){for(const t of this.#si.values())if(t instanceof AnnotationEditor)return;this.onAnnotationEditor(null)}}setValue(t,e){const i=this.#si.get(t);let s=!1;if(void 0!==i){for(const[t,n]of Object.entries(e))if(i[t]!==n){s=!0;i[t]=n}}else{s=!0;this.#si.set(t,e)}s&&this.#ni();e instanceof AnnotationEditor&&"function"==typeof this.onAnnotationEditor&&this.onAnnotationEditor(e.constructor._type)}has(t){return this.#si.has(t)}get size(){return this.#si.size}#ni(){if(!this.#ei){this.#ei=!0;"function"==typeof this.onSetModified&&this.onSetModified()}}resetModified(){if(this.#ei){this.#ei=!1;"function"==typeof this.onResetModified&&this.onResetModified()}}get print(){return new PrintAnnotationStorage(this)}get serializable(){if(0===this.#si.size)return j;const t=new Map,e=new MurmurHash3_64,i=[],s=Object.create(null);let n=!1;for(const[i,a]of this.#si){const r=a instanceof AnnotationEditor?a.serialize(!1,s):a;if(r){t.set(i,r);e.update(`${i}:${JSON.stringify(r)}`);n||=!!r.bitmap}}if(n)for(const e of t.values())e.bitmap&&i.push(e.bitmap);return t.size>0?{map:t,hash:e.hexdigest(),transfer:i}:j}get editorStats(){let t=null;const e=new Map;for(const i of this.#si.values()){if(!(i instanceof AnnotationEditor))continue;const s=i.telemetryFinalData;if(!s)continue;const{type:n}=s;e.has(n)||e.set(n,Object.getPrototypeOf(i).constructor);t||=Object.create(null);const a=t[n]||=new Map;for(const[t,e]of Object.entries(s)){if("type"===t)continue;let i=a.get(t);if(!i){i=new Map;a.set(t,i)}const s=i.get(e)??0;i.set(e,s+1)}}for(const[i,s]of e)t[i]=s.computeTelemetryFinalData(t[i]);return t}resetModifiedIds(){this.#ii=null}get modifiedIds(){if(this.#ii)return this.#ii;const t=[];for(const e of this.#si.values())e instanceof AnnotationEditor&&e.annotationElementId&&e.serialize()&&t.push(e.annotationElementId);return this.#ii={ids:new Set(t),hash:t.join(",")}}[Symbol.iterator](){return this.#si.entries()}}class PrintAnnotationStorage extends AnnotationStorage{#ai;constructor(t){super();const{map:e,hash:i,transfer:s}=t.serializable,n=structuredClone(e,s?{transfer:s}:null);this.#ai={map:n,hash:i,transfer:s}}get print(){unreachable("Should not call PrintAnnotationStorage.print")}get serializable(){return this.#ai}get modifiedIds(){return shadow(this,"modifiedIds",{ids:new Set,hash:""})}}class FontLoader{#ri=new Set;constructor({ownerDocument:t=globalThis.document,styleElement:e=null}){this._document=t;this.nativeFontFaces=new Set;this.styleElement=null;this.loadingRequests=[];this.loadTestFontId=0}addNativeFontFace(t){this.nativeFontFaces.add(t);this._document.fonts.add(t)}removeNativeFontFace(t){this.nativeFontFaces.delete(t);this._document.fonts.delete(t)}insertRule(t){if(!this.styleElement){this.styleElement=this._document.createElement("style");this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement)}const e=this.styleElement.sheet;e.insertRule(t,e.cssRules.length)}clear(){for(const t of this.nativeFontFaces)this._document.fonts.delete(t);this.nativeFontFaces.clear();this.#ri.clear();if(this.styleElement){this.styleElement.remove();this.styleElement=null}}async loadSystemFont({systemFontInfo:t,disableFontFace:e,_inspectFont:i}){if(t&&!this.#ri.has(t.loadedName)){assert(!e,"loadSystemFont shouldn't be called when `disableFontFace` is set.");if(this.isFontLoadingAPISupported){const{loadedName:e,src:s,style:n}=t,a=new FontFace(e,s,n);this.addNativeFontFace(a);try{await a.load();this.#ri.add(e);i?.(t)}catch{warn(`Cannot load system font: ${t.baseFontName}, installing it could help to improve PDF rendering.`);this.removeNativeFontFace(a)}}else unreachable("Not implemented: loadSystemFont without the Font Loading API.")}}async bind(t){if(t.attached||t.missingFile&&!t.systemFontInfo)return;t.attached=!0;if(t.systemFontInfo){await this.loadSystemFont(t);return}if(this.isFontLoadingAPISupported){const e=t.createNativeFontFace();if(e){this.addNativeFontFace(e);try{await e.loaded}catch(i){warn(`Failed to load font '${e.family}': '${i}'.`);t.disableFontFace=!0;throw i}}return}const e=t.createFontFaceRule();if(e){this.insertRule(e);if(this.isSyncFontLoadingSupported)return;await new Promise((e=>{const i=this._queueLoadingCallback(e);this._prepareFontLoadEvent(t,i)}))}}get isFontLoadingAPISupported(){return shadow(this,"isFontLoadingAPISupported",!!this._document?.fonts)}get isSyncFontLoadingSupported(){return shadow(this,"isSyncFontLoadingSupported",t||util_FeatureTest.platform.isFirefox)}_queueLoadingCallback(t){const{loadingRequests:e}=this,i={done:!1,complete:function completeRequest(){assert(!i.done,"completeRequest() cannot be called twice.");i.done=!0;for(;e.length>0&&e[0].done;){const t=e.shift();setTimeout(t.callback,0)}},callback:t};e.push(i);return i}get _loadTestFont(){return shadow(this,"_loadTestFont",atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQAFQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAAALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgAAAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACMAooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4DIP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAAAAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUAAQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgABAAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABYAAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAAAC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQACAQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA=="))}_prepareFontLoadEvent(t,e){function int32(t,e){return t.charCodeAt(e)<<24|t.charCodeAt(e+1)<<16|t.charCodeAt(e+2)<<8|255&t.charCodeAt(e+3)}function spliceString(t,e,i,s){return t.substring(0,e)+s+t.substring(e+i)}let i,s;const n=this._document.createElement("canvas");n.width=1;n.height=1;const a=n.getContext("2d");let r=0;const o=`lt${Date.now()}${this.loadTestFontId++}`;let l=this._loadTestFont;l=spliceString(l,976,o.length,o);const h=1482184792;let d=int32(l,16);for(i=0,s=o.length-3;i>24&255,t>>16&255,t>>8&255,255&t)}(d));const c=`@font-face {font-family:"${o}";src:${`url(data:font/opentype;base64,${btoa(l)});`}}`;this.insertRule(c);const u=this._document.createElement("div");u.style.visibility="hidden";u.style.width=u.style.height="10px";u.style.position="absolute";u.style.top=u.style.left="0px";for(const e of[t.loadedName,o]){const t=this._document.createElement("span");t.textContent="Hi";t.style.fontFamily=e;u.append(t)}this._document.body.append(u);!function isFontReady(t,e){if(++r>30){warn("Load test font never loaded.");e();return}a.font="30px "+t;a.fillText(".",0,20);a.getImageData(0,0,1,1).data[3]>0?e():setTimeout(isFontReady.bind(null,t,e))}(o,(()=>{u.remove();e.complete()}))}}class FontFaceObject{constructor(t,e=null){this.compiledGlyphs=Object.create(null);for(const e in t)this[e]=t[e];this._inspectFont=e}createNativeFontFace(){if(!this.data||this.disableFontFace)return null;let t;if(this.cssFontInfo){const e={weight:this.cssFontInfo.fontWeight};this.cssFontInfo.italicAngle&&(e.style=`oblique ${this.cssFontInfo.italicAngle}deg`);t=new FontFace(this.cssFontInfo.fontFamily,this.data,e)}else t=new FontFace(this.loadedName,this.data,{});this._inspectFont?.(this);return t}createFontFaceRule(){if(!this.data||this.disableFontFace)return null;const t=`url(data:${this.mimetype};base64,${toBase64Util(this.data)});`;let e;if(this.cssFontInfo){let i=`font-weight: ${this.cssFontInfo.fontWeight};`;this.cssFontInfo.italicAngle&&(i+=`font-style: oblique ${this.cssFontInfo.italicAngle}deg;`);e=`@font-face {font-family:"${this.cssFontInfo.fontFamily}";${i}src:${t}}`}else e=`@font-face {font-family:"${this.loadedName}";src:${t}}`;this._inspectFont?.(this,t);return e}getPathGenerator(t,e){if(void 0!==this.compiledGlyphs[e])return this.compiledGlyphs[e];const i=this.loadedName+"_path_"+e;let s;try{s=t.get(i)}catch(t){warn(`getPathGenerator - ignoring character: "${t}".`)}const n=new Path2D(s||"");this.fontExtraProperties||t.delete(i);return this.compiledGlyphs[e]=n}}function getFactoryUrlProp(t){if("string"!=typeof t)return null;if(t.endsWith("/"))return t;throw new Error(`Invalid factory url: "${t}" must include trailing slash.`)}const isRefProxy=t=>"object"==typeof t&&Number.isInteger(t?.num)&&t.num>=0&&Number.isInteger(t?.gen)&&t.gen>=0,q=function _isValidExplicitDest(t,e,i){if(!Array.isArray(i)||i.length<2)return!1;const[s,n,...a]=i;if(!t(s)&&!Number.isInteger(s))return!1;if(!e(n))return!1;const r=a.length;let o=!0;switch(n.name){case"XYZ":if(r<2||r>3)return!1;break;case"Fit":case"FitB":return 0===r;case"FitH":case"FitBH":case"FitV":case"FitBV":if(r>1)return!1;break;case"FitR":if(4!==r)return!1;o=!1;break;default:return!1}for(const t of a)if(!("number"==typeof t||o&&null===t))return!1;return!0}.bind(null,isRefProxy,(t=>"object"==typeof t&&"string"==typeof t?.name));class LoopbackPort{#oi=new Map;#li=Promise.resolve();postMessage(t,e){const i={data:structuredClone(t,e?{transfer:e}:null)};this.#li.then((()=>{for(const[t]of this.#oi)t.call(this,i)}))}addEventListener(t,e,i=null){let s=null;if(i?.signal instanceof AbortSignal){const{signal:n}=i;if(n.aborted){warn("LoopbackPort - cannot use an `aborted` signal.");return}const onAbort=()=>this.removeEventListener(t,e);s=()=>n.removeEventListener("abort",onAbort);n.addEventListener("abort",onAbort)}this.#oi.set(e,s)}removeEventListener(t,e){const i=this.#oi.get(e);i?.();this.#oi.delete(e)}terminate(){for(const[,t]of this.#oi)t?.();this.#oi.clear()}}const X=1,K=2,Y=1,Q=2,J=3,Z=4,tt=5,et=6,it=7,st=8;function onFn(){}function wrapReason(t){if(t instanceof AbortException||t instanceof InvalidPDFException||t instanceof PasswordException||t instanceof ResponseException||t instanceof UnknownErrorException)return t;t instanceof Error||"object"==typeof t&&null!==t||unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');switch(t.name){case"AbortException":return new AbortException(t.message);case"InvalidPDFException":return new InvalidPDFException(t.message);case"PasswordException":return new PasswordException(t.message,t.code);case"ResponseException":return new ResponseException(t.message,t.status,t.missing);case"UnknownErrorException":return new UnknownErrorException(t.message,t.details)}return new UnknownErrorException(t.message,t.toString())}class MessageHandler{#hi=new AbortController;constructor(t,e,i){this.sourceName=t;this.targetName=e;this.comObj=i;this.callbackId=1;this.streamId=1;this.streamSinks=Object.create(null);this.streamControllers=Object.create(null);this.callbackCapabilities=Object.create(null);this.actionHandler=Object.create(null);i.addEventListener("message",this.#di.bind(this),{signal:this.#hi.signal})}#di({data:t}){if(t.targetName!==this.sourceName)return;if(t.stream){this.#ci(t);return}if(t.callback){const e=t.callbackId,i=this.callbackCapabilities[e];if(!i)throw new Error(`Cannot resolve callback ${e}`);delete this.callbackCapabilities[e];if(t.callback===X)i.resolve(t.data);else{if(t.callback!==K)throw new Error("Unexpected callback case");i.reject(wrapReason(t.reason))}return}const e=this.actionHandler[t.action];if(!e)throw new Error(`Unknown action from worker: ${t.action}`);if(t.callbackId){const i=this.sourceName,s=t.sourceName,n=this.comObj;Promise.try(e,t.data).then((function(e){n.postMessage({sourceName:i,targetName:s,callback:X,callbackId:t.callbackId,data:e})}),(function(e){n.postMessage({sourceName:i,targetName:s,callback:K,callbackId:t.callbackId,reason:wrapReason(e)})}))}else t.streamId?this.#ui(t):e(t.data)}on(t,e){const i=this.actionHandler;if(i[t])throw new Error(`There is already an actionName called "${t}"`);i[t]=e}send(t,e,i){this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:t,data:e},i)}sendWithPromise(t,e,i){const s=this.callbackId++,n=Promise.withResolvers();this.callbackCapabilities[s]=n;try{this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:t,callbackId:s,data:e},i)}catch(t){n.reject(t)}return n.promise}sendWithStream(t,e,i,s){const n=this.streamId++,a=this.sourceName,r=this.targetName,o=this.comObj;return new ReadableStream({start:i=>{const l=Promise.withResolvers();this.streamControllers[n]={controller:i,startCall:l,pullCall:null,cancelCall:null,isClosed:!1};o.postMessage({sourceName:a,targetName:r,action:t,streamId:n,data:e,desiredSize:i.desiredSize},s);return l.promise},pull:t=>{const e=Promise.withResolvers();this.streamControllers[n].pullCall=e;o.postMessage({sourceName:a,targetName:r,stream:et,streamId:n,desiredSize:t.desiredSize});return e.promise},cancel:t=>{assert(t instanceof Error,"cancel must have a valid reason");const e=Promise.withResolvers();this.streamControllers[n].cancelCall=e;this.streamControllers[n].isClosed=!0;o.postMessage({sourceName:a,targetName:r,stream:Y,streamId:n,reason:wrapReason(t)});return e.promise}},i)}#ui(t){const e=t.streamId,i=this.sourceName,s=t.sourceName,n=this.comObj,a=this,r=this.actionHandler[t.action],o={enqueue(t,a=1,r){if(this.isCancelled)return;const o=this.desiredSize;this.desiredSize-=a;if(o>0&&this.desiredSize<=0){this.sinkCapability=Promise.withResolvers();this.ready=this.sinkCapability.promise}n.postMessage({sourceName:i,targetName:s,stream:Z,streamId:e,chunk:t},r)},close(){if(!this.isCancelled){this.isCancelled=!0;n.postMessage({sourceName:i,targetName:s,stream:J,streamId:e});delete a.streamSinks[e]}},error(t){assert(t instanceof Error,"error must have a valid reason");if(!this.isCancelled){this.isCancelled=!0;n.postMessage({sourceName:i,targetName:s,stream:tt,streamId:e,reason:wrapReason(t)})}},sinkCapability:Promise.withResolvers(),onPull:null,onCancel:null,isCancelled:!1,desiredSize:t.desiredSize,ready:null};o.sinkCapability.resolve();o.ready=o.sinkCapability.promise;this.streamSinks[e]=o;Promise.try(r,t.data,o).then((function(){n.postMessage({sourceName:i,targetName:s,stream:st,streamId:e,success:!0})}),(function(t){n.postMessage({sourceName:i,targetName:s,stream:st,streamId:e,reason:wrapReason(t)})}))}#ci(t){const e=t.streamId,i=this.sourceName,s=t.sourceName,n=this.comObj,a=this.streamControllers[e],r=this.streamSinks[e];switch(t.stream){case st:t.success?a.startCall.resolve():a.startCall.reject(wrapReason(t.reason));break;case it:t.success?a.pullCall.resolve():a.pullCall.reject(wrapReason(t.reason));break;case et:if(!r){n.postMessage({sourceName:i,targetName:s,stream:it,streamId:e,success:!0});break}r.desiredSize<=0&&t.desiredSize>0&&r.sinkCapability.resolve();r.desiredSize=t.desiredSize;Promise.try(r.onPull||onFn).then((function(){n.postMessage({sourceName:i,targetName:s,stream:it,streamId:e,success:!0})}),(function(t){n.postMessage({sourceName:i,targetName:s,stream:it,streamId:e,reason:wrapReason(t)})}));break;case Z:assert(a,"enqueue should have stream controller");if(a.isClosed)break;a.controller.enqueue(t.chunk);break;case J:assert(a,"close should have stream controller");if(a.isClosed)break;a.isClosed=!0;a.controller.close();this.#pi(a,e);break;case tt:assert(a,"error should have stream controller");a.controller.error(wrapReason(t.reason));this.#pi(a,e);break;case Q:t.success?a.cancelCall.resolve():a.cancelCall.reject(wrapReason(t.reason));this.#pi(a,e);break;case Y:if(!r)break;const o=wrapReason(t.reason);Promise.try(r.onCancel||onFn,o).then((function(){n.postMessage({sourceName:i,targetName:s,stream:Q,streamId:e,success:!0})}),(function(t){n.postMessage({sourceName:i,targetName:s,stream:Q,streamId:e,reason:wrapReason(t)})}));r.sinkCapability.reject(o);r.isCancelled=!0;delete this.streamSinks[e];break;default:throw new Error("Unexpected stream case")}}async#pi(t,e){await Promise.allSettled([t.startCall?.promise,t.pullCall?.promise,t.cancelCall?.promise]);delete this.streamControllers[e]}destroy(){this.#hi?.abort();this.#hi=null}}class BaseCanvasFactory{#gi=!1;constructor({enableHWA:t=!1}){this.#gi=t}create(t,e){if(t<=0||e<=0)throw new Error("Invalid canvas size");const i=this._createCanvas(t,e);return{canvas:i,context:i.getContext("2d",{willReadFrequently:!this.#gi})}}reset(t,e,i){if(!t.canvas)throw new Error("Canvas is not specified");if(e<=0||i<=0)throw new Error("Invalid canvas size");t.canvas.width=e;t.canvas.height=i}destroy(t){if(!t.canvas)throw new Error("Canvas is not specified");t.canvas.width=0;t.canvas.height=0;t.canvas=null;t.context=null}_createCanvas(t,e){unreachable("Abstract method `_createCanvas` called.")}}class DOMCanvasFactory extends BaseCanvasFactory{constructor({ownerDocument:t=globalThis.document,enableHWA:e=!1}){super({enableHWA:e});this._document=t}_createCanvas(t,e){const i=this._document.createElement("canvas");i.width=t;i.height=e;return i}}class BaseCMapReaderFactory{constructor({baseUrl:t=null,isCompressed:e=!0}){this.baseUrl=t;this.isCompressed=e}async fetch({name:t}){if(!this.baseUrl)throw new Error("Ensure that the `cMapUrl` and `cMapPacked` API parameters are provided.");if(!t)throw new Error("CMap name must be specified.");const e=this.baseUrl+t+(this.isCompressed?".bcmap":"");return this._fetch(e).then((t=>({cMapData:t,isCompressed:this.isCompressed}))).catch((t=>{throw new Error(`Unable to load ${this.isCompressed?"binary ":""}CMap at: ${e}`)}))}async _fetch(t){unreachable("Abstract method `_fetch` called.")}}class DOMCMapReaderFactory extends BaseCMapReaderFactory{async _fetch(t){const e=await fetchData(t,this.isCompressed?"arraybuffer":"text");return e instanceof ArrayBuffer?new Uint8Array(e):stringToBytes(e)}}class BaseFilterFactory{addFilter(t){return"none"}addHCMFilter(t,e){return"none"}addAlphaFilter(t){return"none"}addLuminosityFilter(t){return"none"}addHighlightHCMFilter(t,e,i,s,n){return"none"}destroy(t=!1){}}class DOMFilterFactory extends BaseFilterFactory{#fi;#mi;#bi;#Ai;#wi;#yi;#y=0;constructor({docId:t,ownerDocument:e=globalThis.document}){super();this.#Ai=t;this.#wi=e}get#x(){return this.#mi||=new Map}get#vi(){return this.#yi||=new Map}get#xi(){if(!this.#bi){const t=this.#wi.createElement("div"),{style:e}=t;e.visibility="hidden";e.contain="strict";e.width=e.height=0;e.position="absolute";e.top=e.left=0;e.zIndex=-1;const i=this.#wi.createElementNS(z,"svg");i.setAttribute("width",0);i.setAttribute("height",0);this.#bi=this.#wi.createElementNS(z,"defs");t.append(i);i.append(this.#bi);this.#wi.body.append(t)}return this.#bi}#Ei(t){if(1===t.length){const e=t[0],i=new Array(256);for(let t=0;t<256;t++)i[t]=e[t]/255;const s=i.join(",");return[s,s,s]}const[e,i,s]=t,n=new Array(256),a=new Array(256),r=new Array(256);for(let t=0;t<256;t++){n[t]=e[t]/255;a[t]=i[t]/255;r[t]=s[t]/255}return[n.join(","),a.join(","),r.join(",")]}#_i(t){if(void 0===this.#fi){this.#fi="";const t=this.#wi.URL;t!==this.#wi.baseURI&&(isDataScheme(t)?warn('#createUrl: ignore "data:"-URL for performance reasons.'):this.#fi=updateUrlHash(t,""))}return`url(${this.#fi}#${t})`}addFilter(t){if(!t)return"none";let e=this.#x.get(t);if(e)return e;const[i,s,n]=this.#Ei(t),a=1===t.length?i:`${i}${s}${n}`;e=this.#x.get(a);if(e){this.#x.set(t,e);return e}const r=`g_${this.#Ai}_transfer_map_${this.#y++}`,o=this.#_i(r);this.#x.set(t,o);this.#x.set(a,o);const l=this.#Si(r);this.#Ci(i,s,n,l);return o}addHCMFilter(t,e){const i=`${t}-${e}`,s="base";let n=this.#vi.get(s);if(n?.key===i)return n.url;if(n){n.filter?.remove();n.key=i;n.url="none";n.filter=null}else{n={key:i,url:"none",filter:null};this.#vi.set(s,n)}if(!t||!e)return n.url;const a=this.#Ti(t);t=Util.makeHexColor(...a);const r=this.#Ti(e);e=Util.makeHexColor(...r);this.#xi.style.color="";if("#000000"===t&&"#ffffff"===e||t===e)return n.url;const o=new Array(256);for(let t=0;t<=255;t++){const e=t/255;o[t]=e<=.03928?e/12.92:((e+.055)/1.055)**2.4}const l=o.join(","),h=`g_${this.#Ai}_hcm_filter`,d=n.filter=this.#Si(h);this.#Ci(l,l,l,d);this.#Mi(d);const getSteps=(t,e)=>{const i=a[t]/255,s=r[t]/255,n=new Array(e+1);for(let t=0;t<=e;t++)n[t]=i+t/e*(s-i);return n.join(",")};this.#Ci(getSteps(0,5),getSteps(1,5),getSteps(2,5),d);n.url=this.#_i(h);return n.url}addAlphaFilter(t){let e=this.#x.get(t);if(e)return e;const[i]=this.#Ei([t]),s=`alpha_${i}`;e=this.#x.get(s);if(e){this.#x.set(t,e);return e}const n=`g_${this.#Ai}_alpha_map_${this.#y++}`,a=this.#_i(n);this.#x.set(t,a);this.#x.set(s,a);const r=this.#Si(n);this.#Di(i,r);return a}addLuminosityFilter(t){let e,i,s=this.#x.get(t||"luminosity");if(s)return s;if(t){[e]=this.#Ei([t]);i=`luminosity_${e}`}else i="luminosity";s=this.#x.get(i);if(s){this.#x.set(t,s);return s}const n=`g_${this.#Ai}_luminosity_map_${this.#y++}`,a=this.#_i(n);this.#x.set(t,a);this.#x.set(i,a);const r=this.#Si(n);this.#Pi(r);t&&this.#Di(e,r);return a}addHighlightHCMFilter(t,e,i,s,n){const a=`${e}-${i}-${s}-${n}`;let r=this.#vi.get(t);if(r?.key===a)return r.url;if(r){r.filter?.remove();r.key=a;r.url="none";r.filter=null}else{r={key:a,url:"none",filter:null};this.#vi.set(t,r)}if(!e||!i)return r.url;const[o,l]=[e,i].map(this.#Ti.bind(this));let h=Math.round(.2126*o[0]+.7152*o[1]+.0722*o[2]),d=Math.round(.2126*l[0]+.7152*l[1]+.0722*l[2]),[c,u]=[s,n].map(this.#Ti.bind(this));d{const s=new Array(256),n=(d-h)/i,a=t/255,r=(e-t)/(255*i);let o=0;for(let t=0;t<=i;t++){const e=Math.round(h+t*n),i=a+t*r;for(let t=o;t<=e;t++)s[t]=i;o=e+1}for(let t=o;t<256;t++)s[t]=s[o-1];return s.join(",")},p=`g_${this.#Ai}_hcm_${t}_filter`,g=r.filter=this.#Si(p);this.#Mi(g);this.#Ci(getSteps(c[0],u[0],5),getSteps(c[1],u[1],5),getSteps(c[2],u[2],5),g);r.url=this.#_i(p);return r.url}destroy(t=!1){if(!t||!this.#yi?.size){this.#bi?.parentNode.parentNode.remove();this.#bi=null;this.#mi?.clear();this.#mi=null;this.#yi?.clear();this.#yi=null;this.#y=0}}#Pi(t){const e=this.#wi.createElementNS(z,"feColorMatrix");e.setAttribute("type","matrix");e.setAttribute("values","0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0");t.append(e)}#Mi(t){const e=this.#wi.createElementNS(z,"feColorMatrix");e.setAttribute("type","matrix");e.setAttribute("values","0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0");t.append(e)}#Si(t){const e=this.#wi.createElementNS(z,"filter");e.setAttribute("color-interpolation-filters","sRGB");e.setAttribute("id",t);this.#xi.append(e);return e}#ki(t,e,i){const s=this.#wi.createElementNS(z,e);s.setAttribute("type","discrete");s.setAttribute("tableValues",i);t.append(s)}#Ci(t,e,i,s){const n=this.#wi.createElementNS(z,"feComponentTransfer");s.append(n);this.#ki(n,"feFuncR",t);this.#ki(n,"feFuncG",e);this.#ki(n,"feFuncB",i)}#Di(t,e){const i=this.#wi.createElementNS(z,"feComponentTransfer");e.append(i);this.#ki(i,"feFuncA",t)}#Ti(t){this.#xi.style.color=t;return getRGB(getComputedStyle(this.#xi).getPropertyValue("color"))}}class BaseStandardFontDataFactory{constructor({baseUrl:t=null}){this.baseUrl=t}async fetch({filename:t}){if(!this.baseUrl)throw new Error("Ensure that the `standardFontDataUrl` API parameter is provided.");if(!t)throw new Error("Font filename must be specified.");const e=`${this.baseUrl}${t}`;return this._fetch(e).catch((t=>{throw new Error(`Unable to load font data at: ${e}`)}))}async _fetch(t){unreachable("Abstract method `_fetch` called.")}}class DOMStandardFontDataFactory extends BaseStandardFontDataFactory{async _fetch(t){const e=await fetchData(t,"arraybuffer");return new Uint8Array(e)}}class BaseWasmFactory{constructor({baseUrl:t=null}){this.baseUrl=t}async fetch({filename:t}){if(!this.baseUrl)throw new Error("Ensure that the `wasmUrl` API parameter is provided.");if(!t)throw new Error("Wasm filename must be specified.");const e=`${this.baseUrl}${t}`;return this._fetch(e).catch((t=>{throw new Error(`Unable to load wasm data at: ${e}`)}))}async _fetch(t){unreachable("Abstract method `_fetch` called.")}}class DOMWasmFactory extends BaseWasmFactory{async _fetch(t){const e=await fetchData(t,"arraybuffer");return new Uint8Array(e)}}t&&warn("Please use the `legacy` build in Node.js environments.");async function node_utils_fetchData(t){const e=process.getBuiltinModule("fs"),i=await e.promises.readFile(t);return new Uint8Array(i)}class NodeFilterFactory extends BaseFilterFactory{}class NodeCanvasFactory extends BaseCanvasFactory{_createCanvas(t,e){return process.getBuiltinModule("module").createRequire(import.meta.url)("@napi-rs/canvas").createCanvas(t,e)}}class NodeCMapReaderFactory extends BaseCMapReaderFactory{async _fetch(t){return node_utils_fetchData(t)}}class NodeStandardFontDataFactory extends BaseStandardFontDataFactory{async _fetch(t){return node_utils_fetchData(t)}}class NodeWasmFactory extends BaseWasmFactory{async _fetch(t){return node_utils_fetchData(t)}}const nt="Fill",at="Stroke",rt="Shading";function applyBoundingBox(t,e){if(!e)return;const i=e[2]-e[0],s=e[3]-e[1],n=new Path2D;n.rect(e[0],e[1],i,s);t.clip(n)}class BaseShadingPattern{isModifyingCurrentTransform(){return!1}getPattern(){unreachable("Abstract method `getPattern` called.")}}class RadialAxialShadingPattern extends BaseShadingPattern{constructor(t){super();this._type=t[1];this._bbox=t[2];this._colorStops=t[3];this._p0=t[4];this._p1=t[5];this._r0=t[6];this._r1=t[7];this.matrix=null}_createGradient(t){let e;"axial"===this._type?e=t.createLinearGradient(this._p0[0],this._p0[1],this._p1[0],this._p1[1]):"radial"===this._type&&(e=t.createRadialGradient(this._p0[0],this._p0[1],this._r0,this._p1[0],this._p1[1],this._r1));for(const t of this._colorStops)e.addColorStop(t[0],t[1]);return e}getPattern(t,e,i,s){let n;if(s===at||s===nt){const a=e.current.getClippedPathBoundingBox(s,getCurrentTransform(t))||[0,0,0,0],r=Math.ceil(a[2]-a[0])||1,o=Math.ceil(a[3]-a[1])||1,l=e.cachedCanvases.getCanvas("pattern",r,o),h=l.context;h.clearRect(0,0,h.canvas.width,h.canvas.height);h.beginPath();h.rect(0,0,h.canvas.width,h.canvas.height);h.translate(-a[0],-a[1]);i=Util.transform(i,[1,0,0,1,a[0],a[1]]);h.transform(...e.baseTransform);this.matrix&&h.transform(...this.matrix);applyBoundingBox(h,this._bbox);h.fillStyle=this._createGradient(h);h.fill();n=t.createPattern(l.canvas,"no-repeat");const d=new DOMMatrix(i);n.setTransform(d)}else{applyBoundingBox(t,this._bbox);n=this._createGradient(t)}return n}}function drawTriangle(t,e,i,s,n,a,r,o){const l=e.coords,h=e.colors,d=t.data,c=4*t.width;let u;if(l[i+1]>l[s+1]){u=i;i=s;s=u;u=a;a=r;r=u}if(l[s+1]>l[n+1]){u=s;s=n;n=u;u=r;r=o;o=u}if(l[i+1]>l[s+1]){u=i;i=s;s=u;u=a;a=r;r=u}const p=(l[i]+e.offsetX)*e.scaleX,g=(l[i+1]+e.offsetY)*e.scaleY,f=(l[s]+e.offsetX)*e.scaleX,m=(l[s+1]+e.offsetY)*e.scaleY,b=(l[n]+e.offsetX)*e.scaleX,A=(l[n+1]+e.offsetY)*e.scaleY;if(g>=A)return;const w=h[a],y=h[a+1],v=h[a+2],x=h[r],E=h[r+1],_=h[r+2],S=h[o],C=h[o+1],T=h[o+2],M=Math.round(g),D=Math.round(A);let P,k,I,R,F,L,O,N;for(let t=M;t<=D;t++){if(tA?1:m===A?0:(m-t)/(m-A);P=f-(f-b)*e;k=x-(x-S)*e;I=E-(E-C)*e;R=_-(_-T)*e}let e;e=tA?1:(g-t)/(g-A);F=p-(p-b)*e;L=w-(w-S)*e;O=y-(y-C)*e;N=v-(v-T)*e;const i=Math.round(Math.min(P,F)),s=Math.round(Math.max(P,F));let n=c*t+4*i;for(let t=i;t<=s;t++){e=(P-t)/(P-F);e<0?e=0:e>1&&(e=1);d[n++]=k-(k-L)*e|0;d[n++]=I-(I-O)*e|0;d[n++]=R-(R-N)*e|0;d[n++]=255}}}function drawFigure(t,e,i){const s=e.coords,n=e.colors;let a,r;switch(e.type){case"lattice":const o=e.verticesPerRow,l=Math.floor(s.length/o)-1,h=o-1;for(a=0;a=Math.ceil(p*A)?y=o:x=!0;S>=Math.ceil(g*w)?v=l:E=!0;const C=this.getSizeAndScale(y,this.ctx.canvas.width,A),T=this.getSizeAndScale(v,this.ctx.canvas.height,w),M=t.cachedCanvases.getCanvas("pattern",C.size,T.size),D=M.context,P=r.createCanvasGraphics(D);P.groupLevel=t.groupLevel;this.setFillAndStrokeStyleToContext(P,s,a);D.translate(-C.scale*h,-T.scale*d);P.transform(C.scale,0,0,T.scale,0,0);D.save();this.clipBbox(P,h,d,c,u);P.baseTransform=getCurrentTransform(P.ctx);P.executeOperatorList(i);P.endDrawing();D.restore();if(x||E){const e=M.canvas;x&&(y=o);E&&(v=l);const i=this.getSizeAndScale(y,this.ctx.canvas.width,A),s=this.getSizeAndScale(v,this.ctx.canvas.height,w),n=i.size,a=s.size,r=t.cachedCanvases.getCanvas("pattern-workaround",n,a),c=r.context,u=x?Math.floor(p/o):0,f=E?Math.floor(g/l):0;for(let t=0;t<=u;t++)for(let i=0;i<=f;i++)c.drawImage(e,n*t,a*i,n,a,0,0,n,a);return{canvas:r.canvas,scaleX:i.scale,scaleY:s.scale,offsetX:h,offsetY:d}}return{canvas:M.canvas,scaleX:C.scale,scaleY:T.scale,offsetX:h,offsetY:d}}getSizeAndScale(t,e,i){const s=Math.max(TilingPattern.MAX_PATTERN_SIZE,e);let n=Math.ceil(t*i);n>=s?n=s:i=n/t;return{scale:i,size:n}}clipBbox(t,e,i,s,n){const a=s-e,r=n-i;t.ctx.rect(e,i,a,r);Util.axialAlignedBoundingBox([e,i,s,n],getCurrentTransform(t.ctx),t.current.minMax);t.clip();t.endPath()}setFillAndStrokeStyleToContext(t,e,i){const s=t.ctx,n=t.current;switch(e){case ot:const{fillStyle:t,strokeStyle:a}=this.ctx;s.fillStyle=n.fillColor=t;s.strokeStyle=n.strokeColor=a;break;case lt:s.fillStyle=s.strokeStyle=i;n.fillColor=n.strokeColor=i;break;default:throw new FormatError(`Unsupported paint type: ${e}`)}}isModifyingCurrentTransform(){return!1}getPattern(t,e,i,s){let n=i;if(s!==rt){n=Util.transform(n,e.baseTransform);this.matrix&&(n=Util.transform(n,this.matrix))}const a=this.createPatternCanvas(e);let r=new DOMMatrix(n);r=r.translate(a.offsetX,a.offsetY);r=r.scale(1/a.scaleX,1/a.scaleY);const o=t.createPattern(a.canvas,"repeat");o.setTransform(r);return o}}function convertBlackAndWhiteToRGBA({src:t,srcPos:e=0,dest:i,width:s,height:n,nonBlackColor:a=4294967295,inverseDecode:r=!1}){const o=util_FeatureTest.isLittleEndian?4278190080:255,[l,h]=r?[a,o]:[o,a],d=s>>3,c=7&s,u=t.length;i=new Uint32Array(i.buffer);let p=0;for(let s=0;s>2),f=i.length,m=s+7>>3,b=4294967295,A=util_FeatureTest.isLittleEndian?4278190080:255;for(u=0;um?s:8*t-7,r=-8&a;let o=0,c=0;for(;n>=1}}for(;l=a){g=n;f=s*g}l=0;for(p=f;p--;){c[l++]=d[h++];c[l++]=d[h++];c[l++]=d[h++];c[l++]=255}t.putImageData(o,0,u*ht)}}}function putBinaryImageMask(t,e){if(e.bitmap){t.drawImage(e.bitmap,0,0);return}const i=e.height,s=e.width,n=i%ht,a=(i-n)/ht,r=0===n?a:a+1,o=t.createImageData(s,ht);let l=0;const h=e.data,d=o.data;for(let e=0;e10&&"function"==typeof i,h=l?Date.now()+15:0;let d=0;const c=this.commonObjs,u=this.objs;let p;for(;;){if(void 0!==s&&r===s.nextBreakPoint){s.breakIt(r,i);return r}p=a[r];if(p!==D.dependency)this[p].apply(this,n[r]);else for(const t of n[r]){const e=t.startsWith("g_")?c:u;if(!e.has(t)){e.get(t,i);return r}}r++;if(r===o)return r;if(l&&++d>10){if(Date.now()>h){i();return r}d=0}}}#Ii(){for(;this.stateStack.length||this.inSMaskMode;)this.restore();this.current.activeSMask=null;this.ctx.restore();if(this.transparentCanvas){this.ctx=this.compositeCtx;this.ctx.save();this.ctx.setTransform(1,0,0,1,0,0);this.ctx.drawImage(this.transparentCanvas,0,0);this.ctx.restore();this.transparentCanvas=null}}endDrawing(){this.#Ii();this.cachedCanvases.clear();this.cachedPatterns.clear();for(const t of this._cachedBitmapsMap.values()){for(const e of t.values())"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement&&(e.width=e.height=0);t.clear()}this._cachedBitmapsMap.clear();this.#Ri()}#Ri(){if(this.pageColors){const t=this.filterFactory.addHCMFilter(this.pageColors.foreground,this.pageColors.background);if("none"!==t){const e=this.ctx.filter;this.ctx.filter=t;this.ctx.drawImage(this.ctx.canvas,0,0);this.ctx.filter=e}}}_scaleImage(t,e){const i=t.width??t.displayWidth,s=t.height??t.displayHeight;let n,a,r=Math.max(Math.hypot(e[0],e[1]),1),o=Math.max(Math.hypot(e[2],e[3]),1),l=i,h=s,d="prescale1";for(;r>2&&l>1||o>2&&h>1;){let e=l,i=h;if(r>2&&l>1){e=l>=16384?Math.floor(l/2)-1||1:Math.ceil(l/2);r/=l/e}if(o>2&&h>1){i=h>=16384?Math.floor(h/2)-1||1:Math.ceil(h)/2;o/=h/i}n=this.cachedCanvases.getCanvas(d,e,i);a=n.context;a.clearRect(0,0,e,i);a.drawImage(t,0,0,l,h,0,0,e,i);t=n.canvas;l=e;h=i;d="prescale1"===d?"prescale2":"prescale1"}return{img:t,paintWidth:l,paintHeight:h}}_createMaskCanvas(t){const e=this.ctx,{width:i,height:s}=t,n=this.current.fillColor,a=this.current.patternFill,r=getCurrentTransform(e);let o,l,h,d;if((t.bitmap||t.data)&&t.count>1){const e=t.bitmap||t.data.buffer;l=JSON.stringify(a?r:[r.slice(0,4),n]);o=this._cachedBitmapsMap.get(e);if(!o){o=new Map;this._cachedBitmapsMap.set(e,o)}const i=o.get(l);if(i&&!a){return{canvas:i,offsetX:Math.round(Math.min(r[0],r[2])+r[4]),offsetY:Math.round(Math.min(r[1],r[3])+r[5])}}h=i}if(!h){d=this.cachedCanvases.getCanvas("maskCanvas",i,s);putBinaryImageMask(d.context,t)}let c=Util.transform(r,[1/i,0,0,-1/s,0,0]);c=Util.transform(c,[1,0,0,1,0,-s]);const u=ut.slice();Util.axialAlignedBoundingBox([0,0,i,s],c,u);const[p,g,f,m]=u,b=Math.round(f-p)||1,A=Math.round(m-g)||1,w=this.cachedCanvases.getCanvas("fillCanvas",b,A),y=w.context,v=p,x=g;y.translate(-v,-x);y.transform(...c);if(!h){h=this._scaleImage(d.canvas,getCurrentTransformInverse(y));h=h.img;o&&a&&o.set(l,h)}y.imageSmoothingEnabled=getImageSmoothingEnabled(getCurrentTransform(y),t.interpolate);drawImageAtIntegerCoords(y,h,0,0,h.width,h.height,0,0,i,s);y.globalCompositeOperation="source-in";const E=Util.transform(getCurrentTransformInverse(y),[1,0,0,1,-v,-x]);y.fillStyle=a?n.getPattern(e,this,E,nt):n;y.fillRect(0,0,i,s);if(o&&!a){this.cachedCanvases.delete("fillCanvas");o.set(l,w.canvas)}return{canvas:w.canvas,offsetX:Math.round(v),offsetY:Math.round(x)}}setLineWidth(t){t!==this.current.lineWidth&&(this._cachedScaleForStroking[0]=-1);this.current.lineWidth=t;this.ctx.lineWidth=t}setLineCap(t){this.ctx.lineCap=pt[t]}setLineJoin(t){this.ctx.lineJoin=gt[t]}setMiterLimit(t){this.ctx.miterLimit=t}setDash(t,e){const i=this.ctx;if(void 0!==i.setLineDash){i.setLineDash(t);i.lineDashOffset=e}}setRenderingIntent(t){}setFlatness(t){}setGState(t){for(const[e,i]of t)switch(e){case"LW":this.setLineWidth(i);break;case"LC":this.setLineCap(i);break;case"LJ":this.setLineJoin(i);break;case"ML":this.setMiterLimit(i);break;case"D":this.setDash(i[0],i[1]);break;case"RI":this.setRenderingIntent(i);break;case"FL":this.setFlatness(i);break;case"Font":this.setFont(i[0],i[1]);break;case"CA":this.current.strokeAlpha=i;break;case"ca":this.ctx.globalAlpha=this.current.fillAlpha=i;break;case"BM":this.ctx.globalCompositeOperation=i;break;case"SMask":this.current.activeSMask=i?this.tempSMask:null;this.tempSMask=null;this.checkSMaskState();break;case"TR":this.ctx.filter=this.current.transferMaps=this.filterFactory.addFilter(i)}}get inSMaskMode(){return!!this.suspendedCtx}checkSMaskState(){const t=this.inSMaskMode;this.current.activeSMask&&!t?this.beginSMaskMode():!this.current.activeSMask&&t&&this.endSMaskMode()}beginSMaskMode(){if(this.inSMaskMode)throw new Error("beginSMaskMode called while already in smask mode");const t=this.ctx.canvas.width,e=this.ctx.canvas.height,i="smaskGroupAt"+this.groupLevel,s=this.cachedCanvases.getCanvas(i,t,e);this.suspendedCtx=this.ctx;const n=this.ctx=s.context;n.setTransform(this.suspendedCtx.getTransform());copyCtxState(this.suspendedCtx,n);!function mirrorContextOperations(t,e){if(t._removeMirroring)throw new Error("Context is already forwarding operations.");t.__originalSave=t.save;t.__originalRestore=t.restore;t.__originalRotate=t.rotate;t.__originalScale=t.scale;t.__originalTranslate=t.translate;t.__originalTransform=t.transform;t.__originalSetTransform=t.setTransform;t.__originalResetTransform=t.resetTransform;t.__originalClip=t.clip;t.__originalMoveTo=t.moveTo;t.__originalLineTo=t.lineTo;t.__originalBezierCurveTo=t.bezierCurveTo;t.__originalRect=t.rect;t.__originalClosePath=t.closePath;t.__originalBeginPath=t.beginPath;t._removeMirroring=()=>{t.save=t.__originalSave;t.restore=t.__originalRestore;t.rotate=t.__originalRotate;t.scale=t.__originalScale;t.translate=t.__originalTranslate;t.transform=t.__originalTransform;t.setTransform=t.__originalSetTransform;t.resetTransform=t.__originalResetTransform;t.clip=t.__originalClip;t.moveTo=t.__originalMoveTo;t.lineTo=t.__originalLineTo;t.bezierCurveTo=t.__originalBezierCurveTo;t.rect=t.__originalRect;t.closePath=t.__originalClosePath;t.beginPath=t.__originalBeginPath;delete t._removeMirroring};t.save=function(){e.save();this.__originalSave()};t.restore=function(){e.restore();this.__originalRestore()};t.translate=function(t,i){e.translate(t,i);this.__originalTranslate(t,i)};t.scale=function(t,i){e.scale(t,i);this.__originalScale(t,i)};t.transform=function(t,i,s,n,a,r){e.transform(t,i,s,n,a,r);this.__originalTransform(t,i,s,n,a,r)};t.setTransform=function(t,i,s,n,a,r){e.setTransform(t,i,s,n,a,r);this.__originalSetTransform(t,i,s,n,a,r)};t.resetTransform=function(){e.resetTransform();this.__originalResetTransform()};t.rotate=function(t){e.rotate(t);this.__originalRotate(t)};t.clip=function(t){e.clip(t);this.__originalClip(t)};t.moveTo=function(t,i){e.moveTo(t,i);this.__originalMoveTo(t,i)};t.lineTo=function(t,i){e.lineTo(t,i);this.__originalLineTo(t,i)};t.bezierCurveTo=function(t,i,s,n,a,r){e.bezierCurveTo(t,i,s,n,a,r);this.__originalBezierCurveTo(t,i,s,n,a,r)};t.rect=function(t,i,s,n){e.rect(t,i,s,n);this.__originalRect(t,i,s,n)};t.closePath=function(){e.closePath();this.__originalClosePath()};t.beginPath=function(){e.beginPath();this.__originalBeginPath()}}(n,this.suspendedCtx);this.setGState([["BM","source-over"]])}endSMaskMode(){if(!this.inSMaskMode)throw new Error("endSMaskMode called while not in smask mode");this.ctx._removeMirroring();copyCtxState(this.ctx,this.suspendedCtx);this.ctx=this.suspendedCtx;this.suspendedCtx=null}compose(t){if(!this.current.activeSMask)return;if(t){t[0]=Math.floor(t[0]);t[1]=Math.floor(t[1]);t[2]=Math.ceil(t[2]);t[3]=Math.ceil(t[3])}else t=[0,0,this.ctx.canvas.width,this.ctx.canvas.height];const e=this.current.activeSMask,i=this.suspendedCtx;this.composeSMask(i,e,this.ctx,t);this.ctx.save();this.ctx.setTransform(1,0,0,1,0,0);this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);this.ctx.restore()}composeSMask(t,e,i,s){const n=s[0],a=s[1],r=s[2]-n,o=s[3]-a;if(0!==r&&0!==o){this.genericComposeSMask(e.context,i,r,o,e.subtype,e.backdrop,e.transferMap,n,a,e.offsetX,e.offsetY);t.save();t.globalAlpha=1;t.globalCompositeOperation="source-over";t.setTransform(1,0,0,1,0,0);t.drawImage(i.canvas,0,0);t.restore()}}genericComposeSMask(t,e,i,s,n,a,r,o,l,h,d){let c=t.canvas,u=o-h,p=l-d;if(a)if(u<0||p<0||u+i>c.width||p+s>c.height){const t=this.cachedCanvases.getCanvas("maskExtension",i,s),e=t.context;e.drawImage(c,-u,-p);e.globalCompositeOperation="destination-atop";e.fillStyle=a;e.fillRect(0,0,i,s);e.globalCompositeOperation="source-over";c=t.canvas;u=p=0}else{t.save();t.globalAlpha=1;t.setTransform(1,0,0,1,0,0);const e=new Path2D;e.rect(u,p,i,s);t.clip(e);t.globalCompositeOperation="destination-atop";t.fillStyle=a;t.fillRect(u,p,i,s);t.restore()}e.save();e.globalAlpha=1;e.setTransform(1,0,0,1,0,0);"Alpha"===n&&r?e.filter=this.filterFactory.addAlphaFilter(r):"Luminosity"===n&&(e.filter=this.filterFactory.addLuminosityFilter(r));const g=new Path2D;g.rect(o,l,i,s);e.clip(g);e.globalCompositeOperation="destination-in";e.drawImage(c,u,p,i,s,o,l,i,s);e.restore()}save(){this.inSMaskMode&©CtxState(this.ctx,this.suspendedCtx);this.ctx.save();const t=this.current;this.stateStack.push(t);this.current=t.clone()}restore(){if(0!==this.stateStack.length){this.current=this.stateStack.pop();this.ctx.restore();this.inSMaskMode&©CtxState(this.suspendedCtx,this.ctx);this.checkSMaskState();this.pendingClip=null;this._cachedScaleForStroking[0]=-1;this._cachedGetSinglePixelWidth=null}else this.inSMaskMode&&this.endSMaskMode()}transform(t,e,i,s,n,a){this.ctx.transform(t,e,i,s,n,a);this._cachedScaleForStroking[0]=-1;this._cachedGetSinglePixelWidth=null}constructPath(t,e,i){let[s]=e;if(i){if(!(s instanceof Path2D)){const t=e[0]=new Path2D;for(let e=0,i=s.length;e100&&(h=100);this.current.fontSizeScale=i/h;this.ctx.font=`${l} ${o} ${h}px ${r}`}setTextRenderingMode(t){this.current.textRenderingMode=t}setTextRise(t){this.current.textRise=t}moveText(t,e){this.current.x=this.current.lineX+=t;this.current.y=this.current.lineY+=e}setLeadingMoveText(t,e){this.setLeading(-e);this.moveText(t,e)}setTextMatrix(t){const{current:e}=this;e.textMatrix=t;e.textMatrixScale=Math.hypot(t[0],t[1]);e.x=e.lineX=0;e.y=e.lineY=0}nextLine(){this.moveText(0,this.current.leading)}#Fi(t,e,i){const s=new Path2D;s.addPath(t,new DOMMatrix(i).invertSelf().multiplySelf(e));return s}paintChar(t,e,i,s,n){const a=this.ctx,r=this.current,o=r.font,l=r.textRenderingMode,h=r.fontSize/r.fontSizeScale,d=l&w,c=!!(l&y),u=r.patternFill&&!o.missingFile,p=r.patternStroke&&!o.missingFile;let g;(o.disableFontFace||c||u||p)&&(g=o.getPathGenerator(this.commonObjs,t));if(o.disableFontFace||u||p){a.save();a.translate(e,i);a.scale(h,-h);let t;if(d===f||d===b)if(s){t=a.getTransform();a.setTransform(...s);a.fill(this.#Fi(g,t,s))}else a.fill(g);if(d===m||d===b)if(n){t||=a.getTransform();a.setTransform(...n);const{a:e,b:i,c:s,d:r}=t,o=Util.inverseTransform(n),l=Util.transform([e,i,s,r,0,0],o);Util.singularValueDecompose2dScale(l,ct);a.lineWidth*=Math.max(ct[0],ct[1])/h;a.stroke(this.#Fi(g,t,n))}else{a.lineWidth/=h;a.stroke(g)}a.restore()}else{d!==f&&d!==b||a.fillText(t,e,i);d!==m&&d!==b||a.strokeText(t,e,i)}if(c){(this.pendingTextPaths||=[]).push({transform:getCurrentTransform(a),x:e,y:i,fontSize:h,path:g})}}get isFontSubpixelAAEnabled(){const{context:t}=this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled",10,10);t.scale(1.5,1);t.fillText("I",0,10);const e=t.getImageData(0,0,10,10).data;let i=!1;for(let t=3;t0&&e[t]<255){i=!0;break}return shadow(this,"isFontSubpixelAAEnabled",i)}showText(t){const e=this.current,i=e.font;if(i.isType3Font)return this.showType3Text(t);const s=e.fontSize;if(0===s)return;const n=this.ctx,a=e.fontSizeScale,r=e.charSpacing,o=e.wordSpacing,l=e.fontDirection,h=e.textHScale*l,d=t.length,c=i.vertical,u=c?1:-1,p=i.defaultVMetrics,g=s*e.fontMatrix[0],A=e.textRenderingMode===f&&!i.disableFontFace&&!e.patternFill;n.save();e.textMatrix&&n.transform(...e.textMatrix);n.translate(e.x,e.y+e.textRise);l>0?n.scale(h,-1):n.scale(h,1);let y,v;if(e.patternFill){n.save();const t=e.fillColor.getPattern(n,this,getCurrentTransformInverse(n),nt);y=getCurrentTransform(n);n.restore();n.fillStyle=t}if(e.patternStroke){n.save();const t=e.strokeColor.getPattern(n,this,getCurrentTransformInverse(n),at);v=getCurrentTransform(n);n.restore();n.strokeStyle=t}let x=e.lineWidth;const E=e.textMatrixScale;if(0===E||0===x){const t=e.textRenderingMode&w;t!==m&&t!==b||(x=this.getSinglePixelWidth())}else x/=E;if(1!==a){n.scale(a,a);x/=a}n.lineWidth=x;if(i.isInvalidPDFjsFont){const i=[];let s=0;for(const e of t){i.push(e.unicode);s+=e.width}n.fillText(i.join(""),0,0);e.x+=s*g*h;n.restore();this.compose();return}let _,S=0;for(_=0;_0){const t=1e3*n.measureText(f).width/s*a;if(xnew CanvasGraphics(t,this.commonObjs,this.objs,this.canvasFactory,this.filterFactory,{optionalContentConfig:this.optionalContentConfig,markedContentStack:this.markedContentStack})};e=new TilingPattern(t,this.ctx,s,i)}else e=this._getPattern(t[1],t[2]);return e}setStrokeColorN(){this.current.strokeColor=this.getColorN_Pattern(arguments);this.current.patternStroke=!0}setFillColorN(){this.current.fillColor=this.getColorN_Pattern(arguments);this.current.patternFill=!0}setStrokeRGBColor(t){this.ctx.strokeStyle=this.current.strokeColor=t;this.current.patternStroke=!1}setStrokeTransparent(){this.ctx.strokeStyle=this.current.strokeColor="transparent";this.current.patternStroke=!1}setFillRGBColor(t){this.ctx.fillStyle=this.current.fillColor=t;this.current.patternFill=!1}setFillTransparent(){this.ctx.fillStyle=this.current.fillColor="transparent";this.current.patternFill=!1}_getPattern(t,e=null){let i;if(this.cachedPatterns.has(t))i=this.cachedPatterns.get(t);else{i=function getShadingPattern(t){switch(t[0]){case"RadialAxial":return new RadialAxialShadingPattern(t);case"Mesh":return new MeshShadingPattern(t);case"Dummy":return new DummyShadingPattern}throw new Error(`Unknown IR type: ${t[0]}`)}(this.getObject(t));this.cachedPatterns.set(t,i)}e&&(i.matrix=e);return i}shadingFill(t){if(!this.contentVisible)return;const e=this.ctx;this.save();const i=this._getPattern(t);e.fillStyle=i.getPattern(e,this,getCurrentTransformInverse(e),rt);const s=getCurrentTransformInverse(e);if(s){const{width:t,height:i}=e.canvas,n=ut.slice();Util.axialAlignedBoundingBox([0,0,t,i],s,n);const[a,r,o,l]=n;this.ctx.fillRect(a,r,o-a,l-r)}else this.ctx.fillRect(-1e10,-1e10,2e10,2e10);this.compose(this.current.getClippedPathBoundingBox());this.restore()}beginInlineImage(){unreachable("Should not call beginInlineImage")}beginImageData(){unreachable("Should not call beginImageData")}paintFormXObjectBegin(t,e){if(this.contentVisible){this.save();this.baseTransformStack.push(this.baseTransform);t&&this.transform(...t);this.baseTransform=getCurrentTransform(this.ctx);if(e){Util.axialAlignedBoundingBox(e,this.baseTransform,this.current.minMax);const[t,i,s,n]=e,a=new Path2D;a.rect(t,i,s-t,n-i);this.ctx.clip(a);this.endPath()}}}paintFormXObjectEnd(){if(this.contentVisible){this.restore();this.baseTransform=this.baseTransformStack.pop()}}beginGroup(t){if(!this.contentVisible)return;this.save();if(this.inSMaskMode){this.endSMaskMode();this.current.activeSMask=null}const e=this.ctx;t.isolated||info("TODO: Support non-isolated groups.");t.knockout&&warn("Knockout groups not supported.");const i=getCurrentTransform(e);t.matrix&&e.transform(...t.matrix);if(!t.bbox)throw new Error("Bounding box is required.");let s=ut.slice();Util.axialAlignedBoundingBox(t.bbox,getCurrentTransform(e),s);const n=[0,0,e.canvas.width,e.canvas.height];s=Util.intersect(s,n)||[0,0,0,0];const a=Math.floor(s[0]),r=Math.floor(s[1]),o=Math.max(Math.ceil(s[2])-a,1),l=Math.max(Math.ceil(s[3])-r,1);this.current.startNewPathAndClipBox([0,0,o,l]);let h="groupAt"+this.groupLevel;t.smask&&(h+="_smask_"+this.smaskCounter++%2);const d=this.cachedCanvases.getCanvas(h,o,l),c=d.context;c.translate(-a,-r);c.transform(...i);let u=new Path2D;const[p,g,f,m]=t.bbox;u.rect(p,g,f-p,m-g);if(t.matrix){const e=new Path2D;e.addPath(u,new DOMMatrix(t.matrix));u=e}c.clip(u);if(t.smask)this.smaskStack.push({canvas:d.canvas,context:c,offsetX:a,offsetY:r,subtype:t.smask.subtype,backdrop:t.smask.backdrop,transferMap:t.smask.transferMap||null,startTransformInverse:null});else{e.setTransform(1,0,0,1,0,0);e.translate(a,r);e.save()}copyCtxState(e,c);this.ctx=c;this.setGState([["BM","source-over"],["ca",1],["CA",1]]);this.groupStack.push(e);this.groupLevel++}endGroup(t){if(!this.contentVisible)return;this.groupLevel--;const e=this.ctx,i=this.groupStack.pop();this.ctx=i;this.ctx.imageSmoothingEnabled=!1;if(t.smask){this.tempSMask=this.smaskStack.pop();this.restore()}else{this.ctx.restore();const t=getCurrentTransform(this.ctx);this.restore();this.ctx.save();this.ctx.setTransform(...t);const i=ut.slice();Util.axialAlignedBoundingBox([0,0,e.canvas.width,e.canvas.height],t,i);this.ctx.drawImage(e.canvas,0,0);this.ctx.restore();this.compose(i)}}beginAnnotation(t,e,i,s,n){this.#Ii();resetCtxToDefault(this.ctx);this.ctx.save();this.save();this.baseTransform&&this.ctx.setTransform(...this.baseTransform);if(e){const s=e[2]-e[0],a=e[3]-e[1];if(n&&this.annotationCanvasMap){(i=i.slice())[4]-=e[0];i[5]-=e[1];(e=e.slice())[0]=e[1]=0;e[2]=s;e[3]=a;Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx),ct);const{viewportScale:n}=this,r=Math.ceil(s*this.outputScaleX*n),o=Math.ceil(a*this.outputScaleY*n);this.annotationCanvas=this.canvasFactory.create(r,o);const{canvas:l,context:h}=this.annotationCanvas;this.annotationCanvasMap.set(t,l);this.annotationCanvas.savedCtx=this.ctx;this.ctx=h;this.ctx.save();this.ctx.setTransform(ct[0],0,0,-ct[1],0,a*ct[1]);resetCtxToDefault(this.ctx)}else{resetCtxToDefault(this.ctx);this.endPath();const t=new Path2D;t.rect(e[0],e[1],s,a);this.ctx.clip(t)}}this.current=new CanvasExtraState(this.ctx.canvas.width,this.ctx.canvas.height);this.transform(...i);this.transform(...s)}endAnnotation(){if(this.annotationCanvas){this.ctx.restore();this.#Ri();this.ctx=this.annotationCanvas.savedCtx;delete this.annotationCanvas.savedCtx;delete this.annotationCanvas}}paintImageMaskXObject(t){if(!this.contentVisible)return;const e=t.count;(t=this.getObject(t.data,t)).count=e;const i=this.ctx,s=this._createMaskCanvas(t),n=s.canvas;i.save();i.setTransform(1,0,0,1,0,0);i.drawImage(n,s.offsetX,s.offsetY);i.restore();this.compose()}paintImageMaskXObjectRepeat(t,e,i=0,s=0,n,a){if(!this.contentVisible)return;t=this.getObject(t.data,t);const r=this.ctx;r.save();const o=getCurrentTransform(r);r.transform(e,i,s,n,0,0);const l=this._createMaskCanvas(t);r.setTransform(1,0,0,1,l.offsetX-o[4],l.offsetY-o[5]);for(let t=0,h=a.length;te?h/e:1;r=l>e?l/e:1}}this._cachedScaleForStroking[0]=a;this._cachedScaleForStroking[1]=r}return this._cachedScaleForStroking}rescaleAndStroke(t,e){const{ctx:i,current:{lineWidth:s}}=this,[n,a]=this.getScaleForStroking();if(n===a){i.lineWidth=(s||1)*n;i.stroke(t);return}const r=i.getLineDash();e&&i.save();i.scale(n,a);dt.a=1/n;dt.d=1/a;const o=new Path2D;o.addPath(t,dt);if(r.length>0){const t=Math.max(n,a);i.setLineDash(r.map((e=>e/t)));i.lineDashOffset/=t}i.lineWidth=s||1;i.stroke(o);e&&i.restore()}isContentVisible(){for(let t=this.markedContentStack.length-1;t>=0;t--)if(!this.markedContentStack[t].visible)return!1;return!0}}for(const t in D)void 0!==CanvasGraphics.prototype[t]&&(CanvasGraphics.prototype[D[t]]=CanvasGraphics.prototype[t]);class GlobalWorkerOptions{static#Li=null;static#Oi="";static get workerPort(){return this.#Li}static set workerPort(t){if(!("undefined"!=typeof Worker&&t instanceof Worker)&&null!==t)throw new Error("Invalid `workerPort` type.");this.#Li=t}static get workerSrc(){return this.#Oi}static set workerSrc(t){if("string"!=typeof t)throw new Error("Invalid `workerSrc` type.");this.#Oi=t}}class Metadata{#Ni;#Bi;constructor({parsedData:t,rawData:e}){this.#Ni=t;this.#Bi=e}getRaw(){return this.#Bi}get(t){return this.#Ni.get(t)??null}[Symbol.iterator](){return this.#Ni.entries()}}const bt=Symbol("INTERNAL");class OptionalContentGroup{#Hi=!1;#Ui=!1;#zi=!1;#Gi=!0;constructor(t,{name:e,intent:i,usage:s,rbGroups:r}){this.#Hi=!!(t&n);this.#Ui=!!(t&a);this.name=e;this.intent=i;this.usage=s;this.rbGroups=r}get visible(){if(this.#zi)return this.#Gi;if(!this.#Gi)return!1;const{print:t,view:e}=this.usage;return this.#Hi?"OFF"!==e?.viewState:!this.#Ui||"OFF"!==t?.printState}_setVisible(t,e,i=!1){t!==bt&&unreachable("Internal method `_setVisible` called.");this.#zi=i;this.#Gi=e}}class OptionalContentConfig{#$i=null;#Wi=new Map;#Vi=null;#ji=null;constructor(t,e=n){this.renderingIntent=e;this.name=null;this.creator=null;if(null!==t){this.name=t.name;this.creator=t.creator;this.#ji=t.order;for(const i of t.groups)this.#Wi.set(i.id,new OptionalContentGroup(e,i));if("OFF"===t.baseState)for(const t of this.#Wi.values())t._setVisible(bt,!1);for(const e of t.on)this.#Wi.get(e)._setVisible(bt,!0);for(const e of t.off)this.#Wi.get(e)._setVisible(bt,!1);this.#Vi=this.getHash()}}#qi(t){const e=t.length;if(e<2)return!0;const i=t[0];for(let s=1;s0){const t=n instanceof Uint8Array&&n.byteLength===n.buffer.byteLength?n.buffer:new Uint8Array(n).buffer;this._queuedChunks.push(t)}this._pdfDataRangeTransport=t;this._isStreamingSupported=!i;this._isRangeSupported=!e;this._contentLength=s;this._fullRequestReader=null;this._rangeReaders=[];t.addRangeListener(((t,e)=>{this._onReceiveData({begin:t,chunk:e})}));t.addProgressListener(((t,e)=>{this._onProgress({loaded:t,total:e})}));t.addProgressiveReadListener((t=>{this._onReceiveData({chunk:t})}));t.addProgressiveDoneListener((()=>{this._onProgressiveDone()}));t.transportReady()}_onReceiveData({begin:t,chunk:e}){const i=e instanceof Uint8Array&&e.byteLength===e.buffer.byteLength?e.buffer:new Uint8Array(e).buffer;if(void 0===t)this._fullRequestReader?this._fullRequestReader._enqueue(i):this._queuedChunks.push(i);else{assert(this._rangeReaders.some((function(e){if(e._begin!==t)return!1;e._enqueue(i);return!0})),"_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found.")}}get _progressiveDataLength(){return this._fullRequestReader?._loaded??0}_onProgress(t){void 0===t.total?this._rangeReaders[0]?.onProgress?.({loaded:t.loaded}):this._fullRequestReader?.onProgress?.({loaded:t.loaded,total:t.total})}_onProgressiveDone(){this._fullRequestReader?.progressiveDone();this._progressiveDone=!0}_removeRangeReader(t){const e=this._rangeReaders.indexOf(t);e>=0&&this._rangeReaders.splice(e,1)}getFullReader(){assert(!this._fullRequestReader,"PDFDataTransportStream.getFullReader can only be called once.");const t=this._queuedChunks;this._queuedChunks=null;return new PDFDataTransportStreamReader(this,t,this._progressiveDone,this._contentDispositionFilename)}getRangeReader(t,e){if(e<=this._progressiveDataLength)return null;const i=new PDFDataTransportStreamRangeReader(this,t,e);this._pdfDataRangeTransport.requestDataRange(t,e);this._rangeReaders.push(i);return i}cancelAllRequests(t){this._fullRequestReader?.cancel(t);for(const e of this._rangeReaders.slice(0))e.cancel(t);this._pdfDataRangeTransport.abort()}}class PDFDataTransportStreamReader{constructor(t,e,i=!1,s=null){this._stream=t;this._done=i||!1;this._filename=isPdfFile(s)?s:null;this._queuedChunks=e||[];this._loaded=0;for(const t of this._queuedChunks)this._loaded+=t.byteLength;this._requests=[];this._headersReady=Promise.resolve();t._fullRequestReader=this;this.onProgress=null}_enqueue(t){if(!this._done){if(this._requests.length>0){this._requests.shift().resolve({value:t,done:!1})}else this._queuedChunks.push(t);this._loaded+=t.byteLength}}get headersReady(){return this._headersReady}get filename(){return this._filename}get isRangeSupported(){return this._stream._isRangeSupported}get isStreamingSupported(){return this._stream._isStreamingSupported}get contentLength(){return this._stream._contentLength}async read(){if(this._queuedChunks.length>0){return{value:this._queuedChunks.shift(),done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0}progressiveDone(){this._done||(this._done=!0)}}class PDFDataTransportStreamRangeReader{constructor(t,e,i){this._stream=t;this._begin=e;this._end=i;this._queuedChunk=null;this._requests=[];this._done=!1;this.onProgress=null}_enqueue(t){if(!this._done){if(0===this._requests.length)this._queuedChunk=t;else{this._requests.shift().resolve({value:t,done:!1});for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0}this._done=!0;this._stream._removeRangeReader(this)}}get isStreamingSupported(){return!1}async read(){if(this._queuedChunk){const t=this._queuedChunk;this._queuedChunk=null;return{value:t,done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._stream._removeRangeReader(this)}}function createHeaders(t,e){const i=new Headers;if(!t||!e||"object"!=typeof e)return i;for(const t in e){const s=e[t];void 0!==s&&i.append(t,s)}return i}function getResponseOrigin(t){return URL.parse(t)?.origin??null}function validateRangeRequestCapabilities({responseHeaders:t,isHttp:e,rangeChunkSize:i,disableRange:s}){const n={allowRangeRequests:!1,suggestedLength:void 0},a=parseInt(t.get("Content-Length"),10);if(!Number.isInteger(a))return n;n.suggestedLength=a;if(a<=2*i)return n;if(s||!e)return n;if("bytes"!==t.get("Accept-Ranges"))return n;if("identity"!==(t.get("Content-Encoding")||"identity"))return n;n.allowRangeRequests=!0;return n}function extractFilenameFromHeader(t){const e=t.get("Content-Disposition");if(e){let t=function getFilenameFromContentDispositionHeader(t){let e=!0,i=toParamRegExp("filename\\*","i").exec(t);if(i){i=i[1];let t=rfc2616unquote(i);t=unescape(t);t=rfc5987decode(t);t=rfc2047decode(t);return fixupEncoding(t)}i=function rfc2231getparam(t){const e=[];let i;const s=toParamRegExp("filename\\*((?!0\\d)\\d+)(\\*?)","ig");for(;null!==(i=s.exec(t));){let[,t,s,n]=i;t=parseInt(t,10);if(t in e){if(0===t)break}else e[t]=[s,n]}const n=[];for(let t=0;t{t._responseOrigin=getResponseOrigin(e.url);if(!validateResponseStatus(e.status))throw createResponseError(e.status,s);this._reader=e.body.getReader();this._headersCapability.resolve();const i=e.headers,{allowRangeRequests:n,suggestedLength:a}=validateRangeRequestCapabilities({responseHeaders:i,isHttp:t.isHttp,rangeChunkSize:this._rangeChunkSize,disableRange:this._disableRange});this._isRangeSupported=n;this._contentLength=a||this._contentLength;this._filename=extractFilenameFromHeader(i);!this._isStreamingSupported&&this._isRangeSupported&&this.cancel(new AbortException("Streaming is disabled."))})).catch(this._headersCapability.reject);this.onProgress=null}get headersReady(){return this._headersCapability.promise}get filename(){return this._filename}get contentLength(){return this._contentLength}get isRangeSupported(){return this._isRangeSupported}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._headersCapability.promise;const{value:t,done:e}=await this._reader.read();if(e)return{value:t,done:e};this._loaded+=t.byteLength;this.onProgress?.({loaded:this._loaded,total:this._contentLength});return{value:getArrayBuffer(t),done:!1}}cancel(t){this._reader?.cancel(t);this._abortController.abort()}}class PDFFetchStreamRangeReader{constructor(t,e,i){this._stream=t;this._reader=null;this._loaded=0;const s=t.source;this._withCredentials=s.withCredentials||!1;this._readCapability=Promise.withResolvers();this._isStreamingSupported=!s.disableStream;this._abortController=new AbortController;const n=new Headers(t.headers);n.append("Range",`bytes=${e}-${i-1}`);const a=s.url;fetch(a,createFetchOptions(n,this._withCredentials,this._abortController)).then((e=>{const i=getResponseOrigin(e.url);if(i!==t._responseOrigin)throw new Error(`Expected range response-origin "${i}" to match "${t._responseOrigin}".`);if(!validateResponseStatus(e.status))throw createResponseError(e.status,a);this._readCapability.resolve();this._reader=e.body.getReader()})).catch(this._readCapability.reject);this.onProgress=null}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._readCapability.promise;const{value:t,done:e}=await this._reader.read();if(e)return{value:t,done:e};this._loaded+=t.byteLength;this.onProgress?.({loaded:this._loaded});return{value:getArrayBuffer(t),done:!1}}cancel(t){this._reader?.cancel(t);this._abortController.abort()}}class NetworkManager{_responseOrigin=null;constructor({url:t,httpHeaders:e,withCredentials:i}){this.url=t;this.isHttp=/^https?:/i.test(t);this.headers=createHeaders(this.isHttp,e);this.withCredentials=i||!1;this.currXhrId=0;this.pendingRequests=Object.create(null)}request(t){const e=new XMLHttpRequest,i=this.currXhrId++,s=this.pendingRequests[i]={xhr:e};e.open("GET",this.url);e.withCredentials=this.withCredentials;for(const[t,i]of this.headers)e.setRequestHeader(t,i);if(this.isHttp&&"begin"in t&&"end"in t){e.setRequestHeader("Range",`bytes=${t.begin}-${t.end-1}`);s.expectedStatus=206}else s.expectedStatus=200;e.responseType="arraybuffer";assert(t.onError,"Expected `onError` callback to be provided.");e.onerror=()=>{t.onError(e.status)};e.onreadystatechange=this.onStateChange.bind(this,i);e.onprogress=this.onProgress.bind(this,i);s.onHeadersReceived=t.onHeadersReceived;s.onDone=t.onDone;s.onError=t.onError;s.onProgress=t.onProgress;e.send(null);return i}onProgress(t,e){const i=this.pendingRequests[t];i&&i.onProgress?.(e)}onStateChange(t,e){const i=this.pendingRequests[t];if(!i)return;const s=i.xhr;if(s.readyState>=2&&i.onHeadersReceived){i.onHeadersReceived();delete i.onHeadersReceived}if(4!==s.readyState)return;if(!(t in this.pendingRequests))return;delete this.pendingRequests[t];if(0===s.status&&this.isHttp){i.onError(s.status);return}const n=s.status||200;if(!(200===n&&206===i.expectedStatus)&&n!==i.expectedStatus){i.onError(s.status);return}const a=function network_getArrayBuffer(t){const e=t.response;return"string"!=typeof e?e:stringToBytes(e).buffer}(s);if(206===n){const t=s.getResponseHeader("Content-Range"),e=/bytes (\d+)-(\d+)\/(\d+)/.exec(t);if(e)i.onDone({begin:parseInt(e[1],10),chunk:a});else{warn('Missing or invalid "Content-Range" header.');i.onError(0)}}else a?i.onDone({begin:0,chunk:a}):i.onError(s.status)}getRequestXhr(t){return this.pendingRequests[t].xhr}isPendingRequest(t){return t in this.pendingRequests}abortRequest(t){const e=this.pendingRequests[t].xhr;delete this.pendingRequests[t];e.abort()}}class PDFNetworkStream{constructor(t){this._source=t;this._manager=new NetworkManager(t);this._rangeChunkSize=t.rangeChunkSize;this._fullRequestReader=null;this._rangeRequestReaders=[]}_onRangeRequestReaderClosed(t){const e=this._rangeRequestReaders.indexOf(t);e>=0&&this._rangeRequestReaders.splice(e,1)}getFullReader(){assert(!this._fullRequestReader,"PDFNetworkStream.getFullReader can only be called once.");this._fullRequestReader=new PDFNetworkStreamFullRequestReader(this._manager,this._source);return this._fullRequestReader}getRangeReader(t,e){const i=new PDFNetworkStreamRangeRequestReader(this._manager,t,e);i.onClosed=this._onRangeRequestReaderClosed.bind(this);this._rangeRequestReaders.push(i);return i}cancelAllRequests(t){this._fullRequestReader?.cancel(t);for(const e of this._rangeRequestReaders.slice(0))e.cancel(t)}}class PDFNetworkStreamFullRequestReader{constructor(t,e){this._manager=t;this._url=e.url;this._fullRequestId=t.request({onHeadersReceived:this._onHeadersReceived.bind(this),onDone:this._onDone.bind(this),onError:this._onError.bind(this),onProgress:this._onProgress.bind(this)});this._headersCapability=Promise.withResolvers();this._disableRange=e.disableRange||!1;this._contentLength=e.length;this._rangeChunkSize=e.rangeChunkSize;this._rangeChunkSize||this._disableRange||(this._disableRange=!0);this._isStreamingSupported=!1;this._isRangeSupported=!1;this._cachedChunks=[];this._requests=[];this._done=!1;this._storedError=void 0;this._filename=null;this.onProgress=null}_onHeadersReceived(){const t=this._fullRequestId,e=this._manager.getRequestXhr(t);this._manager._responseOrigin=getResponseOrigin(e.responseURL);const i=e.getAllResponseHeaders(),s=new Headers(i?i.trimStart().replace(/[^\S ]+$/,"").split(/[\r\n]+/).map((t=>{const[e,...i]=t.split(": ");return[e,i.join(": ")]})):[]),{allowRangeRequests:n,suggestedLength:a}=validateRangeRequestCapabilities({responseHeaders:s,isHttp:this._manager.isHttp,rangeChunkSize:this._rangeChunkSize,disableRange:this._disableRange});n&&(this._isRangeSupported=!0);this._contentLength=a||this._contentLength;this._filename=extractFilenameFromHeader(s);this._isRangeSupported&&this._manager.abortRequest(t);this._headersCapability.resolve()}_onDone(t){if(t)if(this._requests.length>0){this._requests.shift().resolve({value:t.chunk,done:!1})}else this._cachedChunks.push(t.chunk);this._done=!0;if(!(this._cachedChunks.length>0)){for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0}}_onError(t){this._storedError=createResponseError(t,this._url);this._headersCapability.reject(this._storedError);for(const t of this._requests)t.reject(this._storedError);this._requests.length=0;this._cachedChunks.length=0}_onProgress(t){this.onProgress?.({loaded:t.loaded,total:t.lengthComputable?t.total:this._contentLength})}get filename(){return this._filename}get isRangeSupported(){return this._isRangeSupported}get isStreamingSupported(){return this._isStreamingSupported}get contentLength(){return this._contentLength}get headersReady(){return this._headersCapability.promise}async read(){await this._headersCapability.promise;if(this._storedError)throw this._storedError;if(this._cachedChunks.length>0){return{value:this._cachedChunks.shift(),done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;this._headersCapability.reject(t);for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._manager.isPendingRequest(this._fullRequestId)&&this._manager.abortRequest(this._fullRequestId);this._fullRequestReader=null}}class PDFNetworkStreamRangeRequestReader{constructor(t,e,i){this._manager=t;this._url=t.url;this._requestId=t.request({begin:e,end:i,onHeadersReceived:this._onHeadersReceived.bind(this),onDone:this._onDone.bind(this),onError:this._onError.bind(this),onProgress:this._onProgress.bind(this)});this._requests=[];this._queuedChunk=null;this._done=!1;this._storedError=void 0;this.onProgress=null;this.onClosed=null}_onHeadersReceived(){const t=getResponseOrigin(this._manager.getRequestXhr(this._requestId)?.responseURL);if(t!==this._manager._responseOrigin){this._storedError=new Error(`Expected range response-origin "${t}" to match "${this._manager._responseOrigin}".`);this._onError(0)}}_close(){this.onClosed?.(this)}_onDone(t){const e=t.chunk;if(this._requests.length>0){this._requests.shift().resolve({value:e,done:!1})}else this._queuedChunk=e;this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._close()}_onError(t){this._storedError??=createResponseError(t,this._url);for(const t of this._requests)t.reject(this._storedError);this._requests.length=0;this._queuedChunk=null}_onProgress(t){this.isStreamingSupported||this.onProgress?.({loaded:t.loaded})}get isStreamingSupported(){return!1}async read(){if(this._storedError)throw this._storedError;if(null!==this._queuedChunk){const t=this._queuedChunk;this._queuedChunk=null;return{value:t,done:!1}}if(this._done)return{value:void 0,done:!0};const t=Promise.withResolvers();this._requests.push(t);return t.promise}cancel(t){this._done=!0;for(const t of this._requests)t.resolve({value:void 0,done:!0});this._requests.length=0;this._manager.isPendingRequest(this._requestId)&&this._manager.abortRequest(this._requestId);this._close()}}const At=/^[a-z][a-z0-9\-+.]+:/i;class PDFNodeStream{constructor(t){this.source=t;this.url=function parseUrlOrPath(t){if(At.test(t))return new URL(t);const e=process.getBuiltinModule("url");return new URL(e.pathToFileURL(t))}(t.url);assert("file:"===this.url.protocol,"PDFNodeStream only supports file:// URLs.");this._fullRequestReader=null;this._rangeRequestReaders=[]}get _progressiveDataLength(){return this._fullRequestReader?._loaded??0}getFullReader(){assert(!this._fullRequestReader,"PDFNodeStream.getFullReader can only be called once.");this._fullRequestReader=new PDFNodeStreamFsFullReader(this);return this._fullRequestReader}getRangeReader(t,e){if(e<=this._progressiveDataLength)return null;const i=new PDFNodeStreamFsRangeReader(this,t,e);this._rangeRequestReaders.push(i);return i}cancelAllRequests(t){this._fullRequestReader?.cancel(t);for(const e of this._rangeRequestReaders.slice(0))e.cancel(t)}}class PDFNodeStreamFsFullReader{constructor(t){this._url=t.url;this._done=!1;this._storedError=null;this.onProgress=null;const e=t.source;this._contentLength=e.length;this._loaded=0;this._filename=null;this._disableRange=e.disableRange||!1;this._rangeChunkSize=e.rangeChunkSize;this._rangeChunkSize||this._disableRange||(this._disableRange=!0);this._isStreamingSupported=!e.disableStream;this._isRangeSupported=!e.disableRange;this._readableStream=null;this._readCapability=Promise.withResolvers();this._headersCapability=Promise.withResolvers();const i=process.getBuiltinModule("fs");i.promises.lstat(this._url).then((t=>{this._contentLength=t.size;this._setReadableStream(i.createReadStream(this._url));this._headersCapability.resolve()}),(t=>{"ENOENT"===t.code&&(t=createResponseError(0,this._url.href));this._storedError=t;this._headersCapability.reject(t)}))}get headersReady(){return this._headersCapability.promise}get filename(){return this._filename}get contentLength(){return this._contentLength}get isRangeSupported(){return this._isRangeSupported}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._readCapability.promise;if(this._done)return{value:void 0,done:!0};if(this._storedError)throw this._storedError;const t=this._readableStream.read();if(null===t){this._readCapability=Promise.withResolvers();return this.read()}this._loaded+=t.length;this.onProgress?.({loaded:this._loaded,total:this._contentLength});return{value:new Uint8Array(t).buffer,done:!1}}cancel(t){this._readableStream?this._readableStream.destroy(t):this._error(t)}_error(t){this._storedError=t;this._readCapability.resolve()}_setReadableStream(t){this._readableStream=t;t.on("readable",(()=>{this._readCapability.resolve()}));t.on("end",(()=>{t.destroy();this._done=!0;this._readCapability.resolve()}));t.on("error",(t=>{this._error(t)}));!this._isStreamingSupported&&this._isRangeSupported&&this._error(new AbortException("streaming is disabled"));this._storedError&&this._readableStream.destroy(this._storedError)}}class PDFNodeStreamFsRangeReader{constructor(t,e,i){this._url=t.url;this._done=!1;this._storedError=null;this.onProgress=null;this._loaded=0;this._readableStream=null;this._readCapability=Promise.withResolvers();const s=t.source;this._isStreamingSupported=!s.disableStream;const n=process.getBuiltinModule("fs");this._setReadableStream(n.createReadStream(this._url,{start:e,end:i-1}))}get isStreamingSupported(){return this._isStreamingSupported}async read(){await this._readCapability.promise;if(this._done)return{value:void 0,done:!0};if(this._storedError)throw this._storedError;const t=this._readableStream.read();if(null===t){this._readCapability=Promise.withResolvers();return this.read()}this._loaded+=t.length;this.onProgress?.({loaded:this._loaded});return{value:new Uint8Array(t).buffer,done:!1}}cancel(t){this._readableStream?this._readableStream.destroy(t):this._error(t)}_error(t){this._storedError=t;this._readCapability.resolve()}_setReadableStream(t){this._readableStream=t;t.on("readable",(()=>{this._readCapability.resolve()}));t.on("end",(()=>{t.destroy();this._done=!0;this._readCapability.resolve()}));t.on("error",(t=>{this._error(t)}));this._storedError&&this._readableStream.destroy(this._storedError)}}const wt=Symbol("INITIAL_DATA");class PDFObjects{#Xi=Object.create(null);#Ki(t){return this.#Xi[t]||={...Promise.withResolvers(),data:wt}}get(t,e=null){if(e){const i=this.#Ki(t);i.promise.then((()=>e(i.data)));return null}const i=this.#Xi[t];if(!i||i.data===wt)throw new Error(`Requesting object that isn't resolved yet ${t}.`);return i.data}has(t){const e=this.#Xi[t];return!!e&&e.data!==wt}delete(t){const e=this.#Xi[t];if(!e||e.data===wt)return!1;delete this.#Xi[t];return!0}resolve(t,e=null){const i=this.#Ki(t);i.data=e;i.resolve()}clear(){for(const t in this.#Xi){const{data:e}=this.#Xi[t];e?.bitmap?.close()}this.#Xi=Object.create(null)}*[Symbol.iterator](){for(const t in this.#Xi){const{data:e}=this.#Xi[t];e!==wt&&(yield[t,e])}}}class TextLayer{#Yi=Promise.withResolvers();#mt=null;#Qi=!1;#Ji=!!globalThis.FontInspector?.enabled;#Zi=null;#ts=null;#es=0;#is=0;#ss=null;#ns=null;#as=0;#rs=0;#os=Object.create(null);#ls=[];#hs=null;#ds=[];#cs=new WeakMap;#us=null;static#ps=new Map;static#gs=new Map;static#fs=new WeakMap;static#ms=null;static#bs=new Set;constructor({textContentSource:t,container:e,viewport:i}){if(t instanceof ReadableStream)this.#hs=t;else{if("object"!=typeof t)throw new Error('No "textContentSource" parameter specified.');this.#hs=new ReadableStream({start(e){e.enqueue(t);e.close()}})}this.#mt=this.#ns=e;this.#rs=i.scale*OutputScale.pixelRatio;this.#as=i.rotation;this.#ts={div:null,properties:null,ctx:null};const{pageWidth:s,pageHeight:n,pageX:a,pageY:r}=i.rawDims;this.#us=[1,0,0,-1,-a,r+n];this.#is=s;this.#es=n;TextLayer.#As();setLayerDimensions(e,i);this.#Yi.promise.finally((()=>{TextLayer.#bs.delete(this);this.#ts=null;this.#os=null})).catch((()=>{}))}static get fontFamilyMap(){const{isWindows:t,isFirefox:e}=util_FeatureTest.platform;return shadow(this,"fontFamilyMap",new Map([["sans-serif",(t&&e?"Calibri, ":"")+"sans-serif"],["monospace",(t&&e?"Lucida Console, ":"")+"monospace"]]))}render(){const pump=()=>{this.#ss.read().then((({value:t,done:e})=>{if(e)this.#Yi.resolve();else{this.#Zi??=t.lang;Object.assign(this.#os,t.styles);this.#ws(t.items);pump()}}),this.#Yi.reject)};this.#ss=this.#hs.getReader();TextLayer.#bs.add(this);pump();return this.#Yi.promise}update({viewport:t,onBefore:e=null}){const i=t.scale*OutputScale.pixelRatio,s=t.rotation;if(s!==this.#as){e?.();this.#as=s;setLayerDimensions(this.#ns,{rotation:s})}if(i!==this.#rs){e?.();this.#rs=i;const t={div:null,properties:null,ctx:TextLayer.#ys(this.#Zi)};for(const e of this.#ds){t.properties=this.#cs.get(e);t.div=e;this.#vs(t)}}}cancel(){const t=new AbortException("TextLayer task cancelled.");this.#ss?.cancel(t).catch((()=>{}));this.#ss=null;this.#Yi.reject(t)}get textDivs(){return this.#ds}get textContentItemsStr(){return this.#ls}#ws(t){if(this.#Qi)return;this.#ts.ctx??=TextLayer.#ys(this.#Zi);const e=this.#ds,i=this.#ls;for(const s of t){if(e.length>1e5){warn("Ignoring additional textDivs for performance reasons.");this.#Qi=!0;return}if(void 0!==s.str){i.push(s.str);this.#xs(s)}else if("beginMarkedContentProps"===s.type||"beginMarkedContent"===s.type){const t=this.#mt;this.#mt=document.createElement("span");this.#mt.classList.add("markedContent");null!==s.id&&this.#mt.setAttribute("id",`${s.id}`);t.append(this.#mt)}else"endMarkedContent"===s.type&&(this.#mt=this.#mt.parentNode)}}#xs(t){const e=document.createElement("span"),i={angle:0,canvasWidth:0,hasText:""!==t.str,hasEOL:t.hasEOL,fontSize:0};this.#ds.push(e);const s=Util.transform(this.#us,t.transform);let n=Math.atan2(s[1],s[0]);const a=this.#os[t.fontName];a.vertical&&(n+=Math.PI/2);let r=this.#Ji&&a.fontSubstitution||a.fontFamily;r=TextLayer.fontFamilyMap.get(r)||r;const o=Math.hypot(s[2],s[3]),l=o*TextLayer.#Es(r,a,this.#Zi);let h,d;if(0===n){h=s[4];d=s[5]-l}else{h=s[4]+l*Math.sin(n);d=s[5]-l*Math.cos(n)}const c="calc(var(--total-scale-factor) *",u=e.style;if(this.#mt===this.#ns){u.left=`${(100*h/this.#is).toFixed(2)}%`;u.top=`${(100*d/this.#es).toFixed(2)}%`}else{u.left=`${c}${h.toFixed(2)}px)`;u.top=`${c}${d.toFixed(2)}px)`}u.fontSize=`${c}${(TextLayer.#ms*o).toFixed(2)}px)`;u.fontFamily=r;i.fontSize=o;e.setAttribute("role","presentation");e.textContent=t.str;e.dir=t.dir;this.#Ji&&(e.dataset.fontName=a.fontSubstitutionLoadedName||t.fontName);0!==n&&(i.angle=n*(180/Math.PI));let p=!1;if(t.str.length>1)p=!0;else if(" "!==t.str&&t.transform[0]!==t.transform[3]){const e=Math.abs(t.transform[0]),i=Math.abs(t.transform[3]);e!==i&&Math.max(e,i)/Math.min(e,i)>1.5&&(p=!0)}p&&(i.canvasWidth=a.vertical?t.height:t.width);this.#cs.set(e,i);this.#ts.div=e;this.#ts.properties=i;this.#vs(this.#ts);i.hasText&&this.#mt.append(e);if(i.hasEOL){const t=document.createElement("br");t.setAttribute("role","presentation");this.#mt.append(t)}}#vs(t){const{div:e,properties:i,ctx:s}=t,{style:n}=e;let a="";TextLayer.#ms>1&&(a=`scale(${1/TextLayer.#ms})`);if(0!==i.canvasWidth&&i.hasText){const{fontFamily:t}=n,{canvasWidth:r,fontSize:o}=i;TextLayer.#_s(s,o*this.#rs,t);const{width:l}=s.measureText(e.textContent);l>0&&(a=`scaleX(${r*this.#rs/l}) ${a}`)}0!==i.angle&&(a=`rotate(${i.angle}deg) ${a}`);a.length>0&&(n.transform=a)}static cleanup(){if(!(this.#bs.size>0)){this.#ps.clear();for(const{canvas:t}of this.#gs.values())t.remove();this.#gs.clear()}}static#ys(t=null){let e=this.#gs.get(t||="");if(!e){const i=document.createElement("canvas");i.className="hiddenCanvasElement";i.lang=t;document.body.append(i);e=i.getContext("2d",{alpha:!1,willReadFrequently:!0});this.#gs.set(t,e);this.#fs.set(e,{size:0,family:""})}return e}static#_s(t,e,i){const s=this.#fs.get(t);if(e!==s.size||i!==s.family){t.font=`${e}px ${i}`;s.size=e;s.family=i}}static#As(){if(null!==this.#ms)return;const t=document.createElement("div");t.style.opacity=0;t.style.lineHeight=1;t.style.fontSize="1px";t.style.position="absolute";t.textContent="X";document.body.append(t);this.#ms=t.getBoundingClientRect().height;t.remove()}static#Es(t,e,i){const s=this.#ps.get(t);if(s)return s;const n=this.#ys(i);n.canvas.width=n.canvas.height=30;this.#_s(n,30,t);const a=n.measureText(""),r=a.fontBoundingBoxAscent,o=Math.abs(a.fontBoundingBoxDescent);n.canvas.width=n.canvas.height=0;let l=.8;if(r)l=r/(r+o);else{util_FeatureTest.platform.isFirefox&&warn("Enable the `dom.textMetrics.fontBoundingBox.enabled` preference in `about:config` to improve TextLayer rendering.");e.ascent?l=e.ascent:e.descent&&(l=1+e.descent)}this.#ps.set(t,l);return l}}class XfaText{static textContent(t){const e=[],i={items:e,styles:Object.create(null)};!function walk(t){if(!t)return;let i=null;const s=t.name;if("#text"===s)i=t.value;else{if(!XfaText.shouldBuildText(s))return;t?.attributes?.textContent?i=t.attributes.textContent:t.value&&(i=t.value)}null!==i&&e.push({str:i});if(t.children)for(const e of t.children)walk(e)}(t);return i}static shouldBuildText(t){return!("textarea"===t||"input"===t||"option"===t||"select"===t)}}function getDocument(e={}){"string"==typeof e||e instanceof URL?e={url:e}:(e instanceof ArrayBuffer||ArrayBuffer.isView(e))&&(e={data:e});const i=new PDFDocumentLoadingTask,{docId:s}=i,n=e.url?function getUrlProp(e){if(e instanceof URL)return e.href;if("string"==typeof e){if(t)return e;const i=URL.parse(e,window.location);if(i)return i.href}throw new Error("Invalid PDF url data: either string or URL-object is expected in the url property.")}(e.url):null,a=e.data?function getDataProp(e){if(t&&"undefined"!=typeof Buffer&&e instanceof Buffer)throw new Error("Please provide binary data as `Uint8Array`, rather than `Buffer`.");if(e instanceof Uint8Array&&e.byteLength===e.buffer.byteLength)return e;if("string"==typeof e)return stringToBytes(e);if(e instanceof ArrayBuffer||ArrayBuffer.isView(e)||"object"==typeof e&&!isNaN(e?.length))return new Uint8Array(e);throw new Error("Invalid PDF binary data: either TypedArray, string, or array-like object is expected in the data property.")}(e.data):null,r=e.httpHeaders||null,o=!0===e.withCredentials,l=e.password??null,h=e.range instanceof PDFDataRangeTransport?e.range:null,d=Number.isInteger(e.rangeChunkSize)&&e.rangeChunkSize>0?e.rangeChunkSize:65536;let c=e.worker instanceof PDFWorker?e.worker:null;const u=e.verbosity,p="string"!=typeof e.docBaseUrl||isDataScheme(e.docBaseUrl)?null:e.docBaseUrl,g=getFactoryUrlProp(e.cMapUrl),f=!1!==e.cMapPacked,m=e.CMapReaderFactory||(t?NodeCMapReaderFactory:DOMCMapReaderFactory),b=getFactoryUrlProp(e.iccUrl),A=getFactoryUrlProp(e.standardFontDataUrl),w=e.StandardFontDataFactory||(t?NodeStandardFontDataFactory:DOMStandardFontDataFactory),y=getFactoryUrlProp(e.wasmUrl),v=e.WasmFactory||(t?NodeWasmFactory:DOMWasmFactory),x=!0!==e.stopAtErrors,E=Number.isInteger(e.maxImageSize)&&e.maxImageSize>-1?e.maxImageSize:-1,_=!1!==e.isEvalSupported,S="boolean"==typeof e.isOffscreenCanvasSupported?e.isOffscreenCanvasSupported:!t,C="boolean"==typeof e.isImageDecoderSupported?e.isImageDecoderSupported:!t&&(util_FeatureTest.platform.isFirefox||!globalThis.chrome),T=Number.isInteger(e.canvasMaxAreaInBytes)?e.canvasMaxAreaInBytes:-1,M="boolean"==typeof e.disableFontFace?e.disableFontFace:t,D=!0===e.fontExtraProperties,P=!0===e.enableXfa,k=e.ownerDocument||globalThis.document,I=!0===e.disableRange,R=!0===e.disableStream,F=!0===e.disableAutoFetch,L=!0===e.pdfBug,O=e.CanvasFactory||(t?NodeCanvasFactory:DOMCanvasFactory),N=e.FilterFactory||(t?NodeFilterFactory:DOMFilterFactory),B=!0===e.enableHWA,H=!1!==e.useWasm,U=h?h.length:e.length??NaN,z="boolean"==typeof e.useSystemFonts?e.useSystemFonts:!t&&!M,G="boolean"==typeof e.useWorkerFetch?e.useWorkerFetch:!!(m===DOMCMapReaderFactory&&w===DOMStandardFontDataFactory&&v===DOMWasmFactory&&g&&A&&y&&isValidFetchUrl(g,document.baseURI)&&isValidFetchUrl(A,document.baseURI)&&isValidFetchUrl(y,document.baseURI));setVerbosityLevel(u);const $={canvasFactory:new O({ownerDocument:k,enableHWA:B}),filterFactory:new N({docId:s,ownerDocument:k}),cMapReaderFactory:G?null:new m({baseUrl:g,isCompressed:f}),standardFontDataFactory:G?null:new w({baseUrl:A}),wasmFactory:G?null:new v({baseUrl:y})};if(!c){c=PDFWorker.create({verbosity:u,port:GlobalWorkerOptions.workerPort});i._worker=c}const W={docId:s,apiVersion:"5.3.31",data:a,password:l,disableAutoFetch:F,rangeChunkSize:d,length:U,docBaseUrl:p,enableXfa:P,evaluatorOptions:{maxImageSize:E,disableFontFace:M,ignoreErrors:x,isEvalSupported:_,isOffscreenCanvasSupported:S,isImageDecoderSupported:C,canvasMaxAreaInBytes:T,fontExtraProperties:D,useSystemFonts:z,useWasm:H,useWorkerFetch:G,cMapUrl:g,iccUrl:b,standardFontDataUrl:A,wasmUrl:y}},V={ownerDocument:k,pdfBug:L,styleElement:null,loadingParams:{disableAutoFetch:F,enableXfa:P}};c.promise.then((function(){if(i.destroyed)throw new Error("Loading aborted");if(c.destroyed)throw new Error("Worker was destroyed");const e=c.messageHandler.sendWithPromise("GetDocRequest",W,a?[a.buffer]:null);let l;if(h)l=new PDFDataTransportStream(h,{disableRange:I,disableStream:R});else if(!a){if(!n)throw new Error("getDocument - no `url` parameter provided.");const e=isValidFetchUrl(n)?PDFFetchStream:t?PDFNodeStream:PDFNetworkStream;l=new e({url:n,length:U,httpHeaders:r,withCredentials:o,rangeChunkSize:d,disableRange:I,disableStream:R})}return e.then((t=>{if(i.destroyed)throw new Error("Loading aborted");if(c.destroyed)throw new Error("Worker was destroyed");const e=new MessageHandler(s,t,c.port),n=new WorkerTransport(e,i,l,V,$);i._transport=n;e.send("Ready",null)}))})).catch(i._capability.reject);return i}class PDFDocumentLoadingTask{static#Ai=0;_capability=Promise.withResolvers();_transport=null;_worker=null;docId="d"+PDFDocumentLoadingTask.#Ai++;destroyed=!1;onPassword=null;onProgress=null;get promise(){return this._capability.promise}async destroy(){this.destroyed=!0;try{this._worker?.port&&(this._worker._pendingDestroy=!0);await(this._transport?.destroy())}catch(t){this._worker?.port&&delete this._worker._pendingDestroy;throw t}this._transport=null;this._worker?.destroy();this._worker=null}async getData(){return this._transport.getData()}}class PDFDataRangeTransport{#Yi=Promise.withResolvers();#Ss=[];#Cs=[];#Ts=[];#Ms=[];constructor(t,e,i=!1,s=null){this.length=t;this.initialData=e;this.progressiveDone=i;this.contentDispositionFilename=s}addRangeListener(t){this.#Ms.push(t)}addProgressListener(t){this.#Ts.push(t)}addProgressiveReadListener(t){this.#Cs.push(t)}addProgressiveDoneListener(t){this.#Ss.push(t)}onDataRange(t,e){for(const i of this.#Ms)i(t,e)}onDataProgress(t,e){this.#Yi.promise.then((()=>{for(const i of this.#Ts)i(t,e)}))}onDataProgressiveRead(t){this.#Yi.promise.then((()=>{for(const e of this.#Cs)e(t)}))}onDataProgressiveDone(){this.#Yi.promise.then((()=>{for(const t of this.#Ss)t()}))}transportReady(){this.#Yi.resolve()}requestDataRange(t,e){unreachable("Abstract method PDFDataRangeTransport.requestDataRange")}abort(){}}class PDFDocumentProxy{constructor(t,e){this._pdfInfo=t;this._transport=e}get annotationStorage(){return this._transport.annotationStorage}get canvasFactory(){return this._transport.canvasFactory}get filterFactory(){return this._transport.filterFactory}get numPages(){return this._pdfInfo.numPages}get fingerprints(){return this._pdfInfo.fingerprints}get isPureXfa(){return shadow(this,"isPureXfa",!!this._transport._htmlForXfa)}get allXfaHtml(){return this._transport._htmlForXfa}getPage(t){return this._transport.getPage(t)}getPageIndex(t){return this._transport.getPageIndex(t)}getDestinations(){return this._transport.getDestinations()}getDestination(t){return this._transport.getDestination(t)}getPageLabels(){return this._transport.getPageLabels()}getPageLayout(){return this._transport.getPageLayout()}getPageMode(){return this._transport.getPageMode()}getViewerPreferences(){return this._transport.getViewerPreferences()}getOpenAction(){return this._transport.getOpenAction()}getAttachments(){return this._transport.getAttachments()}getJSActions(){return this._transport.getDocJSActions()}getOutline(){return this._transport.getOutline()}getOptionalContentConfig({intent:t="display"}={}){const{renderingIntent:e}=this._transport.getRenderingIntent(t);return this._transport.getOptionalContentConfig(e)}getPermissions(){return this._transport.getPermissions()}getMetadata(){return this._transport.getMetadata()}getMarkInfo(){return this._transport.getMarkInfo()}getData(){return this._transport.getData()}saveDocument(){return this._transport.saveDocument()}getDownloadInfo(){return this._transport.downloadInfoCapability.promise}cleanup(t=!1){return this._transport.startCleanup(t||this.isPureXfa)}destroy(){return this.loadingTask.destroy()}cachedPageNumber(t){return this._transport.cachedPageNumber(t)}get loadingParams(){return this._transport.loadingParams}get loadingTask(){return this._transport.loadingTask}getFieldObjects(){return this._transport.getFieldObjects()}hasJSActions(){return this._transport.hasJSActions()}getCalculationOrderIds(){return this._transport.getCalculationOrderIds()}}class PDFPageProxy{#Ds=!1;constructor(t,e,i,s=!1){this._pageIndex=t;this._pageInfo=e;this._transport=i;this._stats=s?new StatTimer:null;this._pdfBug=s;this.commonObjs=i.commonObjs;this.objs=new PDFObjects;this._intentStates=new Map;this.destroyed=!1}get pageNumber(){return this._pageIndex+1}get rotate(){return this._pageInfo.rotate}get ref(){return this._pageInfo.ref}get userUnit(){return this._pageInfo.userUnit}get view(){return this._pageInfo.view}getViewport({scale:t,rotation:e=this.rotate,offsetX:i=0,offsetY:s=0,dontFlip:n=!1}={}){return new PageViewport({viewBox:this.view,userUnit:this.userUnit,scale:t,rotation:e,offsetX:i,offsetY:s,dontFlip:n})}getAnnotations({intent:t="display"}={}){const{renderingIntent:e}=this._transport.getRenderingIntent(t);return this._transport.getAnnotations(this._pageIndex,e)}getJSActions(){return this._transport.getPageJSActions(this._pageIndex)}get filterFactory(){return this._transport.filterFactory}get isPureXfa(){return shadow(this,"isPureXfa",!!this._transport._htmlForXfa)}async getXfa(){return this._transport._htmlForXfa?.children[this._pageIndex]||null}render({canvasContext:t,viewport:e,intent:i="display",annotationMode:s=c.ENABLE,transform:n=null,background:r=null,optionalContentConfigPromise:o=null,annotationCanvasMap:l=null,pageColors:h=null,printAnnotationStorage:d=null,isEditing:u=!1}){this._stats?.time("Overall");const p=this._transport.getRenderingIntent(i,s,d,u),{renderingIntent:g,cacheKey:f}=p;this.#Ds=!1;o||=this._transport.getOptionalContentConfig(g);let m=this._intentStates.get(f);if(!m){m=Object.create(null);this._intentStates.set(f,m)}if(m.streamReaderCancelTimeout){clearTimeout(m.streamReaderCancelTimeout);m.streamReaderCancelTimeout=null}const b=!!(g&a);if(!m.displayReadyCapability){m.displayReadyCapability=Promise.withResolvers();m.operatorList={fnArray:[],argsArray:[],lastChunk:!1,separateAnnots:null};this._stats?.time("Page Request");this._pumpOperatorList(p)}const complete=t=>{m.renderTasks.delete(A);b&&(this.#Ds=!0);this.#Ps();if(t){A.capability.reject(t);this._abortOperatorList({intentState:m,reason:t instanceof Error?t:new Error(t)})}else A.capability.resolve();if(this._stats){this._stats.timeEnd("Rendering");this._stats.timeEnd("Overall");globalThis.Stats?.enabled&&globalThis.Stats.add(this.pageNumber,this._stats)}},A=new InternalRenderTask({callback:complete,params:{canvasContext:t,viewport:e,transform:n,background:r},objs:this.objs,commonObjs:this.commonObjs,annotationCanvasMap:l,operatorList:m.operatorList,pageIndex:this._pageIndex,canvasFactory:this._transport.canvasFactory,filterFactory:this._transport.filterFactory,useRequestAnimationFrame:!b,pdfBug:this._pdfBug,pageColors:h});(m.renderTasks||=new Set).add(A);const w=A.task;Promise.all([m.displayReadyCapability.promise,o]).then((([t,e])=>{if(this.destroyed)complete();else{this._stats?.time("Rendering");if(!(e.renderingIntent&g))throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` and `PDFDocumentProxy.getOptionalContentConfig` methods.");A.initializeGraphics({transparency:t,optionalContentConfig:e});A.operatorListChanged()}})).catch(complete);return w}getOperatorList({intent:t="display",annotationMode:e=c.ENABLE,printAnnotationStorage:i=null,isEditing:s=!1}={}){const n=this._transport.getRenderingIntent(t,e,i,s,!0);let a,r=this._intentStates.get(n.cacheKey);if(!r){r=Object.create(null);this._intentStates.set(n.cacheKey,r)}if(!r.opListReadCapability){a=Object.create(null);a.operatorListChanged=function operatorListChanged(){if(r.operatorList.lastChunk){r.opListReadCapability.resolve(r.operatorList);r.renderTasks.delete(a)}};r.opListReadCapability=Promise.withResolvers();(r.renderTasks||=new Set).add(a);r.operatorList={fnArray:[],argsArray:[],lastChunk:!1,separateAnnots:null};this._stats?.time("Page Request");this._pumpOperatorList(n)}return r.opListReadCapability.promise}streamTextContent({includeMarkedContent:t=!1,disableNormalization:e=!1}={}){return this._transport.messageHandler.sendWithStream("GetTextContent",{pageIndex:this._pageIndex,includeMarkedContent:!0===t,disableNormalization:!0===e},{highWaterMark:100,size:t=>t.items.length})}getTextContent(t={}){if(this._transport._htmlForXfa)return this.getXfa().then((t=>XfaText.textContent(t)));const e=this.streamTextContent(t);return new Promise((function(t,i){const s=e.getReader(),n={items:[],styles:Object.create(null),lang:null};!function pump(){s.read().then((function({value:e,done:i}){if(i)t(n);else{n.lang??=e.lang;Object.assign(n.styles,e.styles);n.items.push(...e.items);pump()}}),i)}()}))}getStructTree(){return this._transport.getStructTree(this._pageIndex)}_destroy(){this.destroyed=!0;const t=[];for(const e of this._intentStates.values()){this._abortOperatorList({intentState:e,reason:new Error("Page was destroyed."),force:!0});if(!e.opListReadCapability)for(const i of e.renderTasks){t.push(i.completed);i.cancel()}}this.objs.clear();this.#Ds=!1;return Promise.all(t)}cleanup(t=!1){this.#Ds=!0;const e=this.#Ps();t&&e&&(this._stats&&=new StatTimer);return e}#Ps(){if(!this.#Ds||this.destroyed)return!1;for(const{renderTasks:t,operatorList:e}of this._intentStates.values())if(t.size>0||!e.lastChunk)return!1;this._intentStates.clear();this.objs.clear();this.#Ds=!1;return!0}_startRenderPage(t,e){const i=this._intentStates.get(e);if(i){this._stats?.timeEnd("Page Request");i.displayReadyCapability?.resolve(t)}}_renderPageChunk(t,e){for(let i=0,s=t.length;i{r.read().then((({value:t,done:e})=>{if(e)o.streamReader=null;else if(!this._transport.destroyed){this._renderPageChunk(t,o);pump()}}),(t=>{o.streamReader=null;if(!this._transport.destroyed){if(o.operatorList){o.operatorList.lastChunk=!0;for(const t of o.renderTasks)t.operatorListChanged();this.#Ps()}if(o.displayReadyCapability)o.displayReadyCapability.reject(t);else{if(!o.opListReadCapability)throw t;o.opListReadCapability.reject(t)}}}))};pump()}_abortOperatorList({intentState:t,reason:e,force:i=!1}){if(t.streamReader){if(t.streamReaderCancelTimeout){clearTimeout(t.streamReaderCancelTimeout);t.streamReaderCancelTimeout=null}if(!i){if(t.renderTasks.size>0)return;if(e instanceof RenderingCancelledException){let i=100;e.extraDelay>0&&e.extraDelay<1e3&&(i+=e.extraDelay);t.streamReaderCancelTimeout=setTimeout((()=>{t.streamReaderCancelTimeout=null;this._abortOperatorList({intentState:t,reason:e,force:!0})}),i);return}}t.streamReader.cancel(new AbortException(e.message)).catch((()=>{}));t.streamReader=null;if(!this._transport.destroyed){for(const[e,i]of this._intentStates)if(i===t){this._intentStates.delete(e);break}this.cleanup()}}}get stats(){return this._stats}}class PDFWorker{#Yi=Promise.withResolvers();#ks=null;#Li=null;#Is=null;static#Rs=0;static#Fs=!1;static#Ls=new WeakMap;static{if(t){this.#Fs=!0;GlobalWorkerOptions.workerSrc||="./pdf.worker.mjs"}this._isSameOrigin=(t,e)=>{const i=URL.parse(t);if(!i?.origin||"null"===i.origin)return!1;const s=new URL(e,i);return i.origin===s.origin};this._createCDNWrapper=t=>{const e=`await import("${t}");`;return URL.createObjectURL(new Blob([e],{type:"text/javascript"}))};this.fromPort=t=>{!function deprecated(t){console.log("Deprecated API usage: "+t)}("`PDFWorker.fromPort` - please use `PDFWorker.create` instead.");if(!t?.port)throw new Error("PDFWorker.fromPort - invalid method signature.");return this.create(t)}}constructor({name:t=null,port:e=null,verbosity:i=getVerbosityLevel()}={}){this.name=t;this.destroyed=!1;this.verbosity=i;if(e){if(PDFWorker.#Ls.has(e))throw new Error("Cannot use more than one PDFWorker per port.");PDFWorker.#Ls.set(e,this);this.#Os(e)}else this.#Ns()}get promise(){return this.#Yi.promise}#Bs(){this.#Yi.resolve();this.#ks.send("configure",{verbosity:this.verbosity})}get port(){return this.#Li}get messageHandler(){return this.#ks}#Os(t){this.#Li=t;this.#ks=new MessageHandler("main","worker",t);this.#ks.on("ready",(()=>{}));this.#Bs()}#Ns(){if(PDFWorker.#Fs||PDFWorker.#Hs){this.#Us();return}let{workerSrc:t}=PDFWorker;try{PDFWorker._isSameOrigin(window.location,t)||(t=PDFWorker._createCDNWrapper(new URL(t,window.location).href));const e=new Worker(t,{type:"module"}),i=new MessageHandler("main","worker",e),terminateEarly=()=>{s.abort();i.destroy();e.terminate();this.destroyed?this.#Yi.reject(new Error("Worker was destroyed")):this.#Us()},s=new AbortController;e.addEventListener("error",(()=>{this.#Is||terminateEarly()}),{signal:s.signal});i.on("test",(t=>{s.abort();if(!this.destroyed&&t){this.#ks=i;this.#Li=e;this.#Is=e;this.#Bs()}else terminateEarly()}));i.on("ready",(t=>{s.abort();if(this.destroyed)terminateEarly();else try{sendTest()}catch{this.#Us()}}));const sendTest=()=>{const t=new Uint8Array;i.send("test",t,[t.buffer])};sendTest();return}catch{info("The worker has been disabled.")}this.#Us()}#Us(){if(!PDFWorker.#Fs){warn("Setting up fake worker.");PDFWorker.#Fs=!0}PDFWorker._setupFakeWorkerGlobal.then((t=>{if(this.destroyed){this.#Yi.reject(new Error("Worker was destroyed"));return}const e=new LoopbackPort;this.#Li=e;const i="fake"+PDFWorker.#Rs++,s=new MessageHandler(i+"_worker",i,e);t.setup(s,e);this.#ks=new MessageHandler(i,i+"_worker",e);this.#Bs()})).catch((t=>{this.#Yi.reject(new Error(`Setting up fake worker failed: "${t.message}".`))}))}destroy(){this.destroyed=!0;this.#Is?.terminate();this.#Is=null;PDFWorker.#Ls.delete(this.#Li);this.#Li=null;this.#ks?.destroy();this.#ks=null}static create(t){const e=this.#Ls.get(t?.port);if(e){if(e._pendingDestroy)throw new Error("PDFWorker.create - the worker is being destroyed.\nPlease remember to await `PDFDocumentLoadingTask.destroy()`-calls.");return e}return new PDFWorker(t)}static get workerSrc(){if(GlobalWorkerOptions.workerSrc)return GlobalWorkerOptions.workerSrc;throw new Error('No "GlobalWorkerOptions.workerSrc" specified.')}static get#Hs(){try{return globalThis.pdfjsWorker?.WorkerMessageHandler||null}catch{return null}}static get _setupFakeWorkerGlobal(){return shadow(this,"_setupFakeWorkerGlobal",(async()=>{if(this.#Hs)return this.#Hs;return(await import( +/*webpackIgnore: true*/ +/*@vite-ignore*/ +this.workerSrc)).WorkerMessageHandler})())}}class WorkerTransport{#zs=new Map;#Gs=new Map;#$s=new Map;#Ws=new Map;#Vs=null;constructor(t,e,i,s,n){this.messageHandler=t;this.loadingTask=e;this.commonObjs=new PDFObjects;this.fontLoader=new FontLoader({ownerDocument:s.ownerDocument,styleElement:s.styleElement});this.loadingParams=s.loadingParams;this._params=s;this.canvasFactory=n.canvasFactory;this.filterFactory=n.filterFactory;this.cMapReaderFactory=n.cMapReaderFactory;this.standardFontDataFactory=n.standardFontDataFactory;this.wasmFactory=n.wasmFactory;this.destroyed=!1;this.destroyCapability=null;this._networkStream=i;this._fullReader=null;this._lastProgress=null;this.downloadInfoCapability=Promise.withResolvers();this.setupMessageHandler()}#js(t,e=null){const i=this.#zs.get(t);if(i)return i;const s=this.messageHandler.sendWithPromise(t,e);this.#zs.set(t,s);return s}get annotationStorage(){return shadow(this,"annotationStorage",new AnnotationStorage)}getRenderingIntent(t,e=c.ENABLE,i=null,u=!1,p=!1){let g=n,f=j;switch(t){case"any":g=s;break;case"display":break;case"print":g=a;break;default:warn(`getRenderingIntent - invalid intent: ${t}`)}const m=g&a&&i instanceof PrintAnnotationStorage?i:this.annotationStorage;switch(e){case c.DISABLE:g+=l;break;case c.ENABLE:break;case c.ENABLE_FORMS:g+=r;break;case c.ENABLE_STORAGE:g+=o;f=m.serializable;break;default:warn(`getRenderingIntent - invalid annotationMode: ${e}`)}u&&(g+=h);p&&(g+=d);const{ids:b,hash:A}=m.modifiedIds;return{renderingIntent:g,cacheKey:[g,f.hash,A].join("_"),annotationStorageSerializable:f,modifiedIds:b}}destroy(){if(this.destroyCapability)return this.destroyCapability.promise;this.destroyed=!0;this.destroyCapability=Promise.withResolvers();this.#Vs?.reject(new Error("Worker was destroyed during onPassword callback"));const t=[];for(const e of this.#Gs.values())t.push(e._destroy());this.#Gs.clear();this.#$s.clear();this.#Ws.clear();this.hasOwnProperty("annotationStorage")&&this.annotationStorage.resetModified();const e=this.messageHandler.sendWithPromise("Terminate",null);t.push(e);Promise.all(t).then((()=>{this.commonObjs.clear();this.fontLoader.clear();this.#zs.clear();this.filterFactory.destroy();TextLayer.cleanup();this._networkStream?.cancelAllRequests(new AbortException("Worker was terminated."));this.messageHandler?.destroy();this.messageHandler=null;this.destroyCapability.resolve()}),this.destroyCapability.reject);return this.destroyCapability.promise}setupMessageHandler(){const{messageHandler:t,loadingTask:e}=this;t.on("GetReader",((t,e)=>{assert(this._networkStream,"GetReader - no `IPDFStream` instance available.");this._fullReader=this._networkStream.getFullReader();this._fullReader.onProgress=t=>{this._lastProgress={loaded:t.loaded,total:t.total}};e.onPull=()=>{this._fullReader.read().then((function({value:t,done:i}){if(i)e.close();else{assert(t instanceof ArrayBuffer,"GetReader - expected an ArrayBuffer.");e.enqueue(new Uint8Array(t),1,[t])}})).catch((t=>{e.error(t)}))};e.onCancel=t=>{this._fullReader.cancel(t);e.ready.catch((t=>{if(!this.destroyed)throw t}))}}));t.on("ReaderHeadersReady",(async t=>{await this._fullReader.headersReady;const{isStreamingSupported:i,isRangeSupported:s,contentLength:n}=this._fullReader;if(!i||!s){this._lastProgress&&e.onProgress?.(this._lastProgress);this._fullReader.onProgress=t=>{e.onProgress?.({loaded:t.loaded,total:t.total})}}return{isStreamingSupported:i,isRangeSupported:s,contentLength:n}}));t.on("GetRangeReader",((t,e)=>{assert(this._networkStream,"GetRangeReader - no `IPDFStream` instance available.");const i=this._networkStream.getRangeReader(t.begin,t.end);if(i){e.onPull=()=>{i.read().then((function({value:t,done:i}){if(i)e.close();else{assert(t instanceof ArrayBuffer,"GetRangeReader - expected an ArrayBuffer.");e.enqueue(new Uint8Array(t),1,[t])}})).catch((t=>{e.error(t)}))};e.onCancel=t=>{i.cancel(t);e.ready.catch((t=>{if(!this.destroyed)throw t}))}}else e.close()}));t.on("GetDoc",(({pdfInfo:t})=>{this._numPages=t.numPages;this._htmlForXfa=t.htmlForXfa;delete t.htmlForXfa;e._capability.resolve(new PDFDocumentProxy(t,this))}));t.on("DocException",(t=>{e._capability.reject(wrapReason(t))}));t.on("PasswordRequest",(t=>{this.#Vs=Promise.withResolvers();try{if(!e.onPassword)throw wrapReason(t);const updatePassword=t=>{t instanceof Error?this.#Vs.reject(t):this.#Vs.resolve({password:t})};e.onPassword(updatePassword,t.code)}catch(t){this.#Vs.reject(t)}return this.#Vs.promise}));t.on("DataLoaded",(t=>{e.onProgress?.({loaded:t.length,total:t.length});this.downloadInfoCapability.resolve(t)}));t.on("StartRenderPage",(t=>{if(this.destroyed)return;this.#Gs.get(t.pageIndex)._startRenderPage(t.transparency,t.cacheKey)}));t.on("commonobj",(([e,i,s])=>{if(this.destroyed)return null;if(this.commonObjs.has(e))return null;switch(i){case"Font":if("error"in s){const t=s.error;warn(`Error during font loading: ${t}`);this.commonObjs.resolve(e,t);break}const n=this._params.pdfBug&&globalThis.FontInspector?.enabled?(t,e)=>globalThis.FontInspector.fontAdded(t,e):null,a=new FontFaceObject(s,n);this.fontLoader.bind(a).catch((()=>t.sendWithPromise("FontFallback",{id:e}))).finally((()=>{!a.fontExtraProperties&&a.data&&(a.data=null);this.commonObjs.resolve(e,a)}));break;case"CopyLocalImage":const{imageRef:r}=s;assert(r,"The imageRef must be defined.");for(const t of this.#Gs.values())for(const[,i]of t.objs)if(i?.ref===r){if(!i.dataLen)return null;this.commonObjs.resolve(e,structuredClone(i));return i.dataLen}break;case"FontPath":case"Image":case"Pattern":this.commonObjs.resolve(e,s);break;default:throw new Error(`Got unknown common object type ${i}`)}return null}));t.on("obj",(([t,e,i,s])=>{if(this.destroyed)return;const n=this.#Gs.get(e);if(!n.objs.has(t))if(0!==n._intentStates.size)switch(i){case"Image":case"Pattern":n.objs.resolve(t,s);break;default:throw new Error(`Got unknown object type ${i}`)}else s?.bitmap?.close()}));t.on("DocProgress",(t=>{this.destroyed||e.onProgress?.({loaded:t.loaded,total:t.total})}));t.on("FetchBinaryData",(async t=>{if(this.destroyed)throw new Error("Worker was destroyed.");const e=this[t.type];if(!e)throw new Error(`${t.type} not initialized, see the \`useWorkerFetch\` parameter.`);return e.fetch(t)}))}getData(){return this.messageHandler.sendWithPromise("GetData",null)}saveDocument(){this.annotationStorage.size<=0&&warn("saveDocument called while `annotationStorage` is empty, please use the getData-method instead.");const{map:t,transfer:e}=this.annotationStorage.serializable;return this.messageHandler.sendWithPromise("SaveDocument",{isPureXfa:!!this._htmlForXfa,numPages:this._numPages,annotationStorage:t,filename:this._fullReader?.filename??null},e).finally((()=>{this.annotationStorage.resetModified()}))}getPage(t){if(!Number.isInteger(t)||t<=0||t>this._numPages)return Promise.reject(new Error("Invalid page request."));const e=t-1,i=this.#$s.get(e);if(i)return i;const s=this.messageHandler.sendWithPromise("GetPage",{pageIndex:e}).then((i=>{if(this.destroyed)throw new Error("Transport destroyed");i.refStr&&this.#Ws.set(i.refStr,t);const s=new PDFPageProxy(e,i,this,this._params.pdfBug);this.#Gs.set(e,s);return s}));this.#$s.set(e,s);return s}getPageIndex(t){return isRefProxy(t)?this.messageHandler.sendWithPromise("GetPageIndex",{num:t.num,gen:t.gen}):Promise.reject(new Error("Invalid pageIndex request."))}getAnnotations(t,e){return this.messageHandler.sendWithPromise("GetAnnotations",{pageIndex:t,intent:e})}getFieldObjects(){return this.#js("GetFieldObjects")}hasJSActions(){return this.#js("HasJSActions")}getCalculationOrderIds(){return this.messageHandler.sendWithPromise("GetCalculationOrderIds",null)}getDestinations(){return this.messageHandler.sendWithPromise("GetDestinations",null)}getDestination(t){return"string"!=typeof t?Promise.reject(new Error("Invalid destination request.")):this.messageHandler.sendWithPromise("GetDestination",{id:t})}getPageLabels(){return this.messageHandler.sendWithPromise("GetPageLabels",null)}getPageLayout(){return this.messageHandler.sendWithPromise("GetPageLayout",null)}getPageMode(){return this.messageHandler.sendWithPromise("GetPageMode",null)}getViewerPreferences(){return this.messageHandler.sendWithPromise("GetViewerPreferences",null)}getOpenAction(){return this.messageHandler.sendWithPromise("GetOpenAction",null)}getAttachments(){return this.messageHandler.sendWithPromise("GetAttachments",null)}getDocJSActions(){return this.#js("GetDocJSActions")}getPageJSActions(t){return this.messageHandler.sendWithPromise("GetPageJSActions",{pageIndex:t})}getStructTree(t){return this.messageHandler.sendWithPromise("GetStructTree",{pageIndex:t})}getOutline(){return this.messageHandler.sendWithPromise("GetOutline",null)}getOptionalContentConfig(t){return this.#js("GetOptionalContentConfig").then((e=>new OptionalContentConfig(e,t)))}getPermissions(){return this.messageHandler.sendWithPromise("GetPermissions",null)}getMetadata(){const t="GetMetadata",e=this.#zs.get(t);if(e)return e;const i=this.messageHandler.sendWithPromise(t,null).then((t=>({info:t[0],metadata:t[1]?new Metadata(t[1]):null,contentDispositionFilename:this._fullReader?.filename??null,contentLength:this._fullReader?.contentLength??null})));this.#zs.set(t,i);return i}getMarkInfo(){return this.messageHandler.sendWithPromise("GetMarkInfo",null)}async startCleanup(t=!1){if(!this.destroyed){await this.messageHandler.sendWithPromise("Cleanup",null);for(const t of this.#Gs.values()){if(!t.cleanup())throw new Error(`startCleanup: Page ${t.pageNumber} is currently rendering.`)}this.commonObjs.clear();t||this.fontLoader.clear();this.#zs.clear();this.filterFactory.destroy(!0);TextLayer.cleanup()}}cachedPageNumber(t){if(!isRefProxy(t))return null;const e=0===t.gen?`${t.num}R`:`${t.num}R${t.gen}`;return this.#Ws.get(e)??null}}class RenderTask{#qs=null;onContinue=null;onError=null;constructor(t){this.#qs=t}get promise(){return this.#qs.capability.promise}cancel(t=0){this.#qs.cancel(null,t)}get separateAnnots(){const{separateAnnots:t}=this.#qs.operatorList;if(!t)return!1;const{annotationCanvasMap:e}=this.#qs;return t.form||t.canvas&&e?.size>0}}class InternalRenderTask{#Xs=null;static#Ks=new WeakSet;constructor({callback:t,params:e,objs:i,commonObjs:s,annotationCanvasMap:n,operatorList:a,pageIndex:r,canvasFactory:o,filterFactory:l,useRequestAnimationFrame:h=!1,pdfBug:d=!1,pageColors:c=null}){this.callback=t;this.params=e;this.objs=i;this.commonObjs=s;this.annotationCanvasMap=n;this.operatorListIdx=null;this.operatorList=a;this._pageIndex=r;this.canvasFactory=o;this.filterFactory=l;this._pdfBug=d;this.pageColors=c;this.running=!1;this.graphicsReadyCallback=null;this.graphicsReady=!1;this._useRequestAnimationFrame=!0===h&&"undefined"!=typeof window;this.cancelled=!1;this.capability=Promise.withResolvers();this.task=new RenderTask(this);this._cancelBound=this.cancel.bind(this);this._continueBound=this._continue.bind(this);this._scheduleNextBound=this._scheduleNext.bind(this);this._nextBound=this._next.bind(this);this._canvas=e.canvasContext.canvas}get completed(){return this.capability.promise.catch((function(){}))}initializeGraphics({transparency:t=!1,optionalContentConfig:e}){if(this.cancelled)return;if(this._canvas){if(InternalRenderTask.#Ks.has(this._canvas))throw new Error("Cannot use the same canvas during multiple render() operations. Use different canvas or ensure previous operations were cancelled or completed.");InternalRenderTask.#Ks.add(this._canvas)}if(this._pdfBug&&globalThis.StepperManager?.enabled){this.stepper=globalThis.StepperManager.create(this._pageIndex);this.stepper.init(this.operatorList);this.stepper.nextBreakPoint=this.stepper.getNextBreakPoint()}const{canvasContext:i,viewport:s,transform:n,background:a}=this.params;this.gfx=new CanvasGraphics(i,this.commonObjs,this.objs,this.canvasFactory,this.filterFactory,{optionalContentConfig:e},this.annotationCanvasMap,this.pageColors);this.gfx.beginDrawing({transform:n,viewport:s,transparency:t,background:a});this.operatorListIdx=0;this.graphicsReady=!0;this.graphicsReadyCallback?.()}cancel(t=null,e=0){this.running=!1;this.cancelled=!0;this.gfx?.endDrawing();if(this.#Xs){window.cancelAnimationFrame(this.#Xs);this.#Xs=null}InternalRenderTask.#Ks.delete(this._canvas);t||=new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex+1}`,e);this.callback(t);this.task.onError?.(t)}operatorListChanged(){if(this.graphicsReady){this.stepper?.updateOperatorList(this.operatorList);this.running||this._continue()}else this.graphicsReadyCallback||=this._continueBound}_continue(){this.running=!0;this.cancelled||(this.task.onContinue?this.task.onContinue(this._scheduleNextBound):this._scheduleNext())}_scheduleNext(){this._useRequestAnimationFrame?this.#Xs=window.requestAnimationFrame((()=>{this.#Xs=null;this._nextBound().catch(this._cancelBound)})):Promise.resolve().then(this._nextBound).catch(this._cancelBound)}async _next(){if(!this.cancelled){this.operatorListIdx=this.gfx.executeOperatorList(this.operatorList,this.operatorListIdx,this._continueBound,this.stepper);if(this.operatorListIdx===this.operatorList.argsArray.length){this.running=!1;if(this.operatorList.lastChunk){this.gfx.endDrawing();InternalRenderTask.#Ks.delete(this._canvas);this.callback()}}}}}const yt="5.3.31",vt="47ad820d9";function makeColorComp(t){return Math.floor(255*Math.max(0,Math.min(1,t))).toString(16).padStart(2,"0")}function scaleAndClamp(t){return Math.max(0,Math.min(255,255*t))}class ColorConverters{static CMYK_G([t,e,i,s]){return["G",1-Math.min(1,.3*t+.59*i+.11*e+s)]}static G_CMYK([t]){return["CMYK",0,0,0,1-t]}static G_RGB([t]){return["RGB",t,t,t]}static G_rgb([t]){return[t=scaleAndClamp(t),t,t]}static G_HTML([t]){const e=makeColorComp(t);return`#${e}${e}${e}`}static RGB_G([t,e,i]){return["G",.3*t+.59*e+.11*i]}static RGB_rgb(t){return t.map(scaleAndClamp)}static RGB_HTML(t){return`#${t.map(makeColorComp).join("")}`}static T_HTML(){return"#00000000"}static T_rgb(){return[null]}static CMYK_RGB([t,e,i,s]){return["RGB",1-Math.min(1,t+s),1-Math.min(1,i+s),1-Math.min(1,e+s)]}static CMYK_rgb([t,e,i,s]){return[scaleAndClamp(1-Math.min(1,t+s)),scaleAndClamp(1-Math.min(1,i+s)),scaleAndClamp(1-Math.min(1,e+s))]}static CMYK_HTML(t){const e=this.CMYK_RGB(t).slice(1);return this.RGB_HTML(e)}static RGB_CMYK([t,e,i]){const s=1-t,n=1-e,a=1-i;return["CMYK",s,n,a,Math.min(s,n,a)]}}class BaseSVGFactory{create(t,e,i=!1){if(t<=0||e<=0)throw new Error("Invalid SVG dimensions");const s=this._createSVG("svg:svg");s.setAttribute("version","1.1");if(!i){s.setAttribute("width",`${t}px`);s.setAttribute("height",`${e}px`)}s.setAttribute("preserveAspectRatio","none");s.setAttribute("viewBox",`0 0 ${t} ${e}`);return s}createElement(t){if("string"!=typeof t)throw new Error("Invalid SVG element type");return this._createSVG(t)}_createSVG(t){unreachable("Abstract method `_createSVG` called.")}}class DOMSVGFactory extends BaseSVGFactory{_createSVG(t){return document.createElementNS(z,t)}}class XfaLayer{static setupStorage(t,e,i,s,n){const a=s.getValue(e,{value:null});switch(i.name){case"textarea":null!==a.value&&(t.textContent=a.value);if("print"===n)break;t.addEventListener("input",(t=>{s.setValue(e,{value:t.target.value})}));break;case"input":if("radio"===i.attributes.type||"checkbox"===i.attributes.type){a.value===i.attributes.xfaOn?t.setAttribute("checked",!0):a.value===i.attributes.xfaOff&&t.removeAttribute("checked");if("print"===n)break;t.addEventListener("change",(t=>{s.setValue(e,{value:t.target.checked?t.target.getAttribute("xfaOn"):t.target.getAttribute("xfaOff")})}))}else{null!==a.value&&t.setAttribute("value",a.value);if("print"===n)break;t.addEventListener("input",(t=>{s.setValue(e,{value:t.target.value})}))}break;case"select":if(null!==a.value){t.setAttribute("value",a.value);for(const t of i.children)t.attributes.value===a.value?t.attributes.selected=!0:t.attributes.hasOwnProperty("selected")&&delete t.attributes.selected}t.addEventListener("input",(t=>{const i=t.target.options,n=-1===i.selectedIndex?"":i[i.selectedIndex].value;s.setValue(e,{value:n})}))}}static setAttributes({html:t,element:e,storage:i=null,intent:s,linkService:n}){const{attributes:a}=e,r=t instanceof HTMLAnchorElement;"radio"===a.type&&(a.name=`${a.name}-${s}`);for(const[e,i]of Object.entries(a))if(null!=i)switch(e){case"class":i.length&&t.setAttribute(e,i.join(" "));break;case"dataId":break;case"id":t.setAttribute("data-element-id",i);break;case"style":Object.assign(t.style,i);break;case"textContent":t.textContent=i;break;default:(!r||"href"!==e&&"newWindow"!==e)&&t.setAttribute(e,i)}r&&n.addLinkAttributes(t,a.href,a.newWindow);i&&a.dataId&&this.setupStorage(t,a.dataId,e,i)}static render(t){const e=t.annotationStorage,i=t.linkService,s=t.xfaHtml,n=t.intent||"display",a=document.createElement(s.name);s.attributes&&this.setAttributes({html:a,element:s,intent:n,linkService:i});const r="richText"!==n,o=t.div;o.append(a);if(t.viewport){const e=`matrix(${t.viewport.transform.join(",")})`;o.style.transform=e}r&&o.setAttribute("class","xfaLayer xfaFont");const l=[];if(0===s.children.length){if(s.value){const t=document.createTextNode(s.value);a.append(t);r&&XfaText.shouldBuildText(s.name)&&l.push(t)}return{textDivs:l}}const h=[[s,-1,a]];for(;h.length>0;){const[t,s,a]=h.at(-1);if(s+1===t.children.length){h.pop();continue}const o=t.children[++h.at(-1)[1]];if(null===o)continue;const{name:d}=o;if("#text"===d){const t=document.createTextNode(o.value);l.push(t);a.append(t);continue}const c=o?.attributes?.xmlns?document.createElementNS(o.attributes.xmlns,d):document.createElement(d);a.append(c);o.attributes&&this.setAttributes({html:c,element:o,storage:e,intent:n,linkService:i});if(o.children?.length>0)h.push([o,-1,c]);else if(o.value){const t=document.createTextNode(o.value);r&&XfaText.shouldBuildText(d)&&l.push(t);c.append(t)}}for(const t of o.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea"))t.setAttribute("readOnly",!0);return{textDivs:l}}static update(t){const e=`matrix(${t.viewport.transform.join(",")})`;t.div.style.transform=e;t.div.hidden=!1}}const xt=1e3,Et=new WeakSet;class AnnotationElementFactory{static create(t){switch(t.data.annotationType){case x.LINK:return new LinkAnnotationElement(t);case x.TEXT:return new TextAnnotationElement(t);case x.WIDGET:switch(t.data.fieldType){case"Tx":return new TextWidgetAnnotationElement(t);case"Btn":return t.data.radioButton?new RadioButtonWidgetAnnotationElement(t):t.data.checkBox?new CheckboxWidgetAnnotationElement(t):new PushButtonWidgetAnnotationElement(t);case"Ch":return new ChoiceWidgetAnnotationElement(t);case"Sig":return new SignatureWidgetAnnotationElement(t)}return new WidgetAnnotationElement(t);case x.POPUP:return new PopupAnnotationElement(t);case x.FREETEXT:return new FreeTextAnnotationElement(t);case x.LINE:return new LineAnnotationElement(t);case x.SQUARE:return new SquareAnnotationElement(t);case x.CIRCLE:return new CircleAnnotationElement(t);case x.POLYLINE:return new PolylineAnnotationElement(t);case x.CARET:return new CaretAnnotationElement(t);case x.INK:return new InkAnnotationElement(t);case x.POLYGON:return new PolygonAnnotationElement(t);case x.HIGHLIGHT:return new HighlightAnnotationElement(t);case x.UNDERLINE:return new UnderlineAnnotationElement(t);case x.SQUIGGLY:return new SquigglyAnnotationElement(t);case x.STRIKEOUT:return new StrikeOutAnnotationElement(t);case x.STAMP:return new StampAnnotationElement(t);case x.FILEATTACHMENT:return new FileAttachmentAnnotationElement(t);default:return new AnnotationElement(t)}}}class AnnotationElement{#Ys=null;#Qs=!1;#Js=null;constructor(t,{isRenderable:e=!1,ignoreBorder:i=!1,createQuadrilaterals:s=!1}={}){this.isRenderable=e;this.data=t.data;this.layer=t.layer;this.linkService=t.linkService;this.downloadManager=t.downloadManager;this.imageResourcesPath=t.imageResourcesPath;this.renderForms=t.renderForms;this.svgFactory=t.svgFactory;this.annotationStorage=t.annotationStorage;this.enableScripting=t.enableScripting;this.hasJSActions=t.hasJSActions;this._fieldObjects=t.fieldObjects;this.parent=t.parent;e&&(this.container=this._createContainer(i));s&&this._createQuadrilaterals()}static _hasPopupData({titleObj:t,contentsObj:e,richText:i}){return!!(t?.str||e?.str||i?.str)}get _isEditable(){return this.data.isEditable}get hasPopupData(){return AnnotationElement._hasPopupData(this.data)}updateEdited(t){if(!this.container)return;this.#Ys||={rect:this.data.rect.slice(0)};const{rect:e}=t;e&&this.#Zs(e);this.#Js?.popup.updateEdited(t)}resetEdited(){if(this.#Ys){this.#Zs(this.#Ys.rect);this.#Js?.popup.resetEdited();this.#Ys=null}}#Zs(t){const{container:{style:e},data:{rect:i,rotation:s},parent:{viewport:{rawDims:{pageWidth:n,pageHeight:a,pageX:r,pageY:o}}}}=this;i?.splice(0,4,...t);e.left=100*(t[0]-r)/n+"%";e.top=100*(a-t[3]+o)/a+"%";if(0===s){e.width=100*(t[2]-t[0])/n+"%";e.height=100*(t[3]-t[1])/a+"%"}else this.setRotation(s)}_createContainer(t){const{data:e,parent:{page:i,viewport:s}}=this,n=document.createElement("section");n.setAttribute("data-annotation-id",e.id);this instanceof WidgetAnnotationElement||(n.tabIndex=xt);const{style:a}=n;a.zIndex=this.parent.zIndex++;e.alternativeText&&(n.title=e.alternativeText);e.noRotate&&n.classList.add("norotate");if(!e.rect||this instanceof PopupAnnotationElement){const{rotation:t}=e;e.hasOwnCanvas||0===t||this.setRotation(t,n);return n}const{width:r,height:o}=this;if(!t&&e.borderStyle.width>0){a.borderWidth=`${e.borderStyle.width}px`;const t=e.borderStyle.horizontalCornerRadius,i=e.borderStyle.verticalCornerRadius;if(t>0||i>0){const e=`calc(${t}px * var(--total-scale-factor)) / calc(${i}px * var(--total-scale-factor))`;a.borderRadius=e}else if(this instanceof RadioButtonWidgetAnnotationElement){const t=`calc(${r}px * var(--total-scale-factor)) / calc(${o}px * var(--total-scale-factor))`;a.borderRadius=t}switch(e.borderStyle.style){case E:a.borderStyle="solid";break;case _:a.borderStyle="dashed";break;case S:warn("Unimplemented border style: beveled");break;case C:warn("Unimplemented border style: inset");break;case T:a.borderBottomStyle="solid"}const s=e.borderColor||null;if(s){this.#Qs=!0;a.borderColor=Util.makeHexColor(0|s[0],0|s[1],0|s[2])}else a.borderWidth=0}const l=Util.normalizeRect([e.rect[0],i.view[3]-e.rect[1]+i.view[1],e.rect[2],i.view[3]-e.rect[3]+i.view[1]]),{pageWidth:h,pageHeight:d,pageX:c,pageY:u}=s.rawDims;a.left=100*(l[0]-c)/h+"%";a.top=100*(l[1]-u)/d+"%";const{rotation:p}=e;if(e.hasOwnCanvas||0===p){a.width=100*r/h+"%";a.height=100*o/d+"%"}else this.setRotation(p,n);return n}setRotation(t,e=this.container){if(!this.data.rect)return;const{pageWidth:i,pageHeight:s}=this.parent.viewport.rawDims;let{width:n,height:a}=this;t%180!=0&&([n,a]=[a,n]);e.style.width=100*n/i+"%";e.style.height=100*a/s+"%";e.setAttribute("data-main-rotation",(360-t)%360)}get _commonActions(){const setColor=(t,e,i)=>{const s=i.detail[t],n=s[0],a=s.slice(1);i.target.style[e]=ColorConverters[`${n}_HTML`](a);this.annotationStorage.setValue(this.data.id,{[e]:ColorConverters[`${n}_rgb`](a)})};return shadow(this,"_commonActions",{display:t=>{const{display:e}=t.detail,i=e%2==1;this.container.style.visibility=i?"hidden":"visible";this.annotationStorage.setValue(this.data.id,{noView:i,noPrint:1===e||2===e})},print:t=>{this.annotationStorage.setValue(this.data.id,{noPrint:!t.detail.print})},hidden:t=>{const{hidden:e}=t.detail;this.container.style.visibility=e?"hidden":"visible";this.annotationStorage.setValue(this.data.id,{noPrint:e,noView:e})},focus:t=>{setTimeout((()=>t.target.focus({preventScroll:!1})),0)},userName:t=>{t.target.title=t.detail.userName},readonly:t=>{t.target.disabled=t.detail.readonly},required:t=>{this._setRequired(t.target,t.detail.required)},bgColor:t=>{setColor("bgColor","backgroundColor",t)},fillColor:t=>{setColor("fillColor","backgroundColor",t)},fgColor:t=>{setColor("fgColor","color",t)},textColor:t=>{setColor("textColor","color",t)},borderColor:t=>{setColor("borderColor","borderColor",t)},strokeColor:t=>{setColor("strokeColor","borderColor",t)},rotation:t=>{const e=t.detail.rotation;this.setRotation(e);this.annotationStorage.setValue(this.data.id,{rotation:e})}})}_dispatchEventFromSandbox(t,e){const i=this._commonActions;for(const s of Object.keys(e.detail)){const n=t[s]||i[s];n?.(e)}}_setDefaultPropertiesFromJS(t){if(!this.enableScripting)return;const e=this.annotationStorage.getRawValue(this.data.id);if(!e)return;const i=this._commonActions;for(const[s,n]of Object.entries(e)){const a=i[s];if(a){a({detail:{[s]:n},target:t});delete e[s]}}}_createQuadrilaterals(){if(!this.container)return;const{quadPoints:t}=this.data;if(!t)return;const[e,i,s,n]=this.data.rect.map((t=>Math.fround(t)));if(8===t.length){const[a,r,o,l]=t.subarray(2,6);if(s===a&&n===r&&e===o&&i===l)return}const{style:a}=this.container;let r;if(this.#Qs){const{borderColor:t,borderWidth:e}=a;a.borderWidth=0;r=["url('data:image/svg+xml;utf8,",'',``];this.container.classList.add("hasBorder")}const o=s-e,l=n-i,{svgFactory:h}=this,d=h.createElement("svg");d.classList.add("quadrilateralsContainer");d.setAttribute("width",0);d.setAttribute("height",0);const c=h.createElement("defs");d.append(c);const u=h.createElement("clipPath"),p=`clippath_${this.data.id}`;u.setAttribute("id",p);u.setAttribute("clipPathUnits","objectBoundingBox");c.append(u);for(let i=2,s=t.length;i`)}if(this.#Qs){r.push("')");a.backgroundImage=r.join("")}this.container.append(d);this.container.style.clipPath=`url(#${p})`}_createPopup(){const{data:t}=this,e=this.#Js=new PopupAnnotationElement({data:{color:t.color,titleObj:t.titleObj,modificationDate:t.modificationDate,contentsObj:t.contentsObj,richText:t.richText,parentRect:t.rect,borderStyle:0,id:`popup_${t.id}`,rotation:t.rotation},parent:this.parent,elements:[this]});this.parent.div.append(e.render())}render(){unreachable("Abstract method `AnnotationElement.render` called")}_getElementsByName(t,e=null){const i=[];if(this._fieldObjects){const s=this._fieldObjects[t];if(s)for(const{page:t,id:n,exportValues:a}of s){if(-1===t)continue;if(n===e)continue;const s="string"==typeof a?a:null,r=document.querySelector(`[data-element-id="${n}"]`);!r||Et.has(r)?i.push({id:n,exportValue:s,domElement:r}):warn(`_getElementsByName - element not allowed: ${n}`)}return i}for(const s of document.getElementsByName(t)){const{exportValue:t}=s,n=s.getAttribute("data-element-id");n!==e&&(Et.has(s)&&i.push({id:n,exportValue:t,domElement:s}))}return i}show(){this.container&&(this.container.hidden=!1);this.popup?.maybeShow()}hide(){this.container&&(this.container.hidden=!0);this.popup?.forceHide()}getElementsToTriggerPopup(){return this.container}addHighlightArea(){const t=this.getElementsToTriggerPopup();if(Array.isArray(t))for(const e of t)e.classList.add("highlightArea");else t.classList.add("highlightArea")}_editOnDoubleClick(){if(!this._isEditable)return;const{annotationEditorType:t,data:{id:e}}=this;this.container.addEventListener("dblclick",(()=>{this.linkService.eventBus?.dispatch("switchannotationeditormode",{source:this,mode:t,editId:e})}))}get width(){return this.data.rect[2]-this.data.rect[0]}get height(){return this.data.rect[3]-this.data.rect[1]}}class LinkAnnotationElement extends AnnotationElement{constructor(t,e=null){super(t,{isRenderable:!0,ignoreBorder:!!e?.ignoreBorder,createQuadrilaterals:!0});this.isTooltipOnly=t.data.isTooltipOnly}render(){const{data:t,linkService:e}=this,i=document.createElement("a");i.setAttribute("data-element-id",t.id);let s=!1;if(t.url){e.addLinkAttributes(i,t.url,t.newWindow);s=!0}else if(t.action){this._bindNamedAction(i,t.action);s=!0}else if(t.attachment){this.#tn(i,t.attachment,t.attachmentDest);s=!0}else if(t.setOCGState){this.#en(i,t.setOCGState);s=!0}else if(t.dest){this._bindLink(i,t.dest);s=!0}else{if(t.actions&&(t.actions.Action||t.actions["Mouse Up"]||t.actions["Mouse Down"])&&this.enableScripting&&this.hasJSActions){this._bindJSAction(i,t);s=!0}if(t.resetForm){this._bindResetFormAction(i,t.resetForm);s=!0}else if(this.isTooltipOnly&&!s){this._bindLink(i,"");s=!0}}this.container.classList.add("linkAnnotation");s&&this.container.append(i);return this.container}#in(){this.container.setAttribute("data-internal-link","")}_bindLink(t,e){t.href=this.linkService.getDestinationHash(e);t.onclick=()=>{e&&this.linkService.goToDestination(e);return!1};(e||""===e)&&this.#in()}_bindNamedAction(t,e){t.href=this.linkService.getAnchorUrl("");t.onclick=()=>{this.linkService.executeNamedAction(e);return!1};this.#in()}#tn(t,e,i=null){t.href=this.linkService.getAnchorUrl("");e.description&&(t.title=e.description);t.onclick=()=>{this.downloadManager?.openOrDownloadData(e.content,e.filename,i);return!1};this.#in()}#en(t,e){t.href=this.linkService.getAnchorUrl("");t.onclick=()=>{this.linkService.executeSetOCGState(e);return!1};this.#in()}_bindJSAction(t,e){t.href=this.linkService.getAnchorUrl("");const i=new Map([["Action","onclick"],["Mouse Up","onmouseup"],["Mouse Down","onmousedown"]]);for(const s of Object.keys(e.actions)){const n=i.get(s);n&&(t[n]=()=>{this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e.id,name:s}});return!1})}t.onclick||(t.onclick=()=>!1);this.#in()}_bindResetFormAction(t,e){const i=t.onclick;i||(t.href=this.linkService.getAnchorUrl(""));this.#in();if(this._fieldObjects)t.onclick=()=>{i?.();const{fields:t,refs:s,include:n}=e,a=[];if(0!==t.length||0!==s.length){const e=new Set(s);for(const i of t){const t=this._fieldObjects[i]||[];for(const{id:i}of t)e.add(i)}for(const t of Object.values(this._fieldObjects))for(const i of t)e.has(i.id)===n&&a.push(i)}else for(const t of Object.values(this._fieldObjects))a.push(...t);const r=this.annotationStorage,o=[];for(const t of a){const{id:e}=t;o.push(e);switch(t.type){case"text":{const i=t.defaultValue||"";r.setValue(e,{value:i});break}case"checkbox":case"radiobutton":{const i=t.defaultValue===t.exportValues;r.setValue(e,{value:i});break}case"combobox":case"listbox":{const i=t.defaultValue||"";r.setValue(e,{value:i});break}default:continue}const i=document.querySelector(`[data-element-id="${e}"]`);i&&(Et.has(i)?i.dispatchEvent(new Event("resetform")):warn(`_bindResetFormAction - element not allowed: ${e}`))}this.enableScripting&&this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:"app",ids:o,name:"ResetForm"}});return!1};else{warn('_bindResetFormAction - "resetForm" action not supported, ensure that the `fieldObjects` parameter is provided.');i||(t.onclick=()=>!1)}}}class TextAnnotationElement extends AnnotationElement{constructor(t){super(t,{isRenderable:!0})}render(){this.container.classList.add("textAnnotation");const t=document.createElement("img");t.src=this.imageResourcesPath+"annotation-"+this.data.name.toLowerCase()+".svg";t.setAttribute("data-l10n-id","pdfjs-text-annotation-type");t.setAttribute("data-l10n-args",JSON.stringify({type:this.data.name}));!this.data.popupRef&&this.hasPopupData&&this._createPopup();this.container.append(t);return this.container}}class WidgetAnnotationElement extends AnnotationElement{render(){return this.container}showElementAndHideCanvas(t){if(this.data.hasOwnCanvas){"CANVAS"===t.previousSibling?.nodeName&&(t.previousSibling.hidden=!0);t.hidden=!1}}_getKeyModifier(t){return util_FeatureTest.platform.isMac?t.metaKey:t.ctrlKey}_setEventListener(t,e,i,s,n){i.includes("mouse")?t.addEventListener(i,(t=>{this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:this.data.id,name:s,value:n(t),shift:t.shiftKey,modifier:this._getKeyModifier(t)}})})):t.addEventListener(i,(t=>{if("blur"===i){if(!e.focused||!t.relatedTarget)return;e.focused=!1}else if("focus"===i){if(e.focused)return;e.focused=!0}n&&this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:this.data.id,name:s,value:n(t)}})}))}_setEventListeners(t,e,i,s){for(const[n,a]of i)if("Action"===a||this.data.actions?.[a]){"Focus"!==a&&"Blur"!==a||(e||={focused:!1});this._setEventListener(t,e,n,a,s);"Focus"!==a||this.data.actions?.Blur?"Blur"!==a||this.data.actions?.Focus||this._setEventListener(t,e,"focus","Focus",null):this._setEventListener(t,e,"blur","Blur",null)}}_setBackgroundColor(t){const e=this.data.backgroundColor||null;t.style.backgroundColor=null===e?"transparent":Util.makeHexColor(e[0],e[1],e[2])}_setTextStyle(t){const e=["left","center","right"],{fontColor:s}=this.data.defaultAppearanceData,n=this.data.defaultAppearanceData.fontSize||9,a=t.style;let r;const roundToOneDecimal=t=>Math.round(10*t)/10;if(this.data.multiLine){const t=Math.abs(this.data.rect[3]-this.data.rect[1]-2),e=t/(Math.round(t/(i*n))||1);r=Math.min(n,roundToOneDecimal(e/i))}else{const t=Math.abs(this.data.rect[3]-this.data.rect[1]-2);r=Math.min(n,roundToOneDecimal(t/i))}a.fontSize=`calc(${r}px * var(--total-scale-factor))`;a.color=Util.makeHexColor(s[0],s[1],s[2]);null!==this.data.textAlignment&&(a.textAlign=e[this.data.textAlignment])}_setRequired(t,e){e?t.setAttribute("required",!0):t.removeAttribute("required");t.setAttribute("aria-required",e)}}class TextWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms||t.data.hasOwnCanvas||!t.data.hasAppearance&&!!t.data.fieldValue})}setPropertyOnSiblings(t,e,i,s){const n=this.annotationStorage;for(const a of this._getElementsByName(t.name,t.id)){a.domElement&&(a.domElement[e]=i);n.setValue(a.id,{[s]:i})}}render(){const t=this.annotationStorage,e=this.data.id;this.container.classList.add("textWidgetAnnotation");let i=null;if(this.renderForms){const s=t.getValue(e,{value:this.data.fieldValue});let n=s.value||"";const a=t.getValue(e,{charLimit:this.data.maxLen}).charLimit;a&&n.length>a&&(n=n.slice(0,a));let r=s.formattedValue||this.data.textContent?.join("\n")||null;r&&this.data.comb&&(r=r.replaceAll(/\s+/g,""));const o={userValue:n,formattedValue:r,lastCommittedValue:null,commitKey:1,focused:!1};if(this.data.multiLine){i=document.createElement("textarea");i.textContent=r??n;this.data.doNotScroll&&(i.style.overflowY="hidden")}else{i=document.createElement("input");i.type=this.data.password?"password":"text";i.setAttribute("value",r??n);this.data.doNotScroll&&(i.style.overflowX="hidden")}this.data.hasOwnCanvas&&(i.hidden=!0);Et.add(i);i.setAttribute("data-element-id",e);i.disabled=this.data.readOnly;i.name=this.data.fieldName;i.tabIndex=xt;this._setRequired(i,this.data.required);a&&(i.maxLength=a);i.addEventListener("input",(s=>{t.setValue(e,{value:s.target.value});this.setPropertyOnSiblings(i,"value",s.target.value,"value");o.formattedValue=null}));i.addEventListener("resetform",(t=>{const e=this.data.defaultFieldValue??"";i.value=o.userValue=e;o.formattedValue=null}));let blurListener=t=>{const{formattedValue:e}=o;null!=e&&(t.target.value=e);t.target.scrollLeft=0};if(this.enableScripting&&this.hasJSActions){i.addEventListener("focus",(t=>{if(o.focused)return;const{target:e}=t;o.userValue&&(e.value=o.userValue);o.lastCommittedValue=e.value;o.commitKey=1;this.data.actions?.Focus||(o.focused=!0)}));i.addEventListener("updatefromsandbox",(i=>{this.showElementAndHideCanvas(i.target);const s={value(i){o.userValue=i.detail.value??"";t.setValue(e,{value:o.userValue.toString()});i.target.value=o.userValue},formattedValue(i){const{formattedValue:s}=i.detail;o.formattedValue=s;null!=s&&i.target!==document.activeElement&&(i.target.value=s);t.setValue(e,{formattedValue:s})},selRange(t){t.target.setSelectionRange(...t.detail.selRange)},charLimit:i=>{const{charLimit:s}=i.detail,{target:n}=i;if(0===s){n.removeAttribute("maxLength");return}n.setAttribute("maxLength",s);let a=o.userValue;if(a&&!(a.length<=s)){a=a.slice(0,s);n.value=o.userValue=a;t.setValue(e,{value:a});this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:a,willCommit:!0,commitKey:1,selStart:n.selectionStart,selEnd:n.selectionEnd}})}}};this._dispatchEventFromSandbox(s,i)}));i.addEventListener("keydown",(t=>{o.commitKey=1;let i=-1;"Escape"===t.key?i=0:"Enter"!==t.key||this.data.multiLine?"Tab"===t.key&&(o.commitKey=3):i=2;if(-1===i)return;const{value:s}=t.target;if(o.lastCommittedValue!==s){o.lastCommittedValue=s;o.userValue=s;this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:s,willCommit:!0,commitKey:i,selStart:t.target.selectionStart,selEnd:t.target.selectionEnd}})}}));const s=blurListener;blurListener=null;i.addEventListener("blur",(t=>{if(!o.focused||!t.relatedTarget)return;this.data.actions?.Blur||(o.focused=!1);const{value:i}=t.target;o.userValue=i;o.lastCommittedValue!==i&&this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:i,willCommit:!0,commitKey:o.commitKey,selStart:t.target.selectionStart,selEnd:t.target.selectionEnd}});s(t)}));this.data.actions?.Keystroke&&i.addEventListener("beforeinput",(t=>{o.lastCommittedValue=null;const{data:i,target:s}=t,{value:n,selectionStart:a,selectionEnd:r}=s;let l=a,h=r;switch(t.inputType){case"deleteWordBackward":{const t=n.substring(0,a).match(/\w*[^\w]*$/);t&&(l-=t[0].length);break}case"deleteWordForward":{const t=n.substring(a).match(/^[^\w]*\w*/);t&&(h+=t[0].length);break}case"deleteContentBackward":a===r&&(l-=1);break;case"deleteContentForward":a===r&&(h+=1)}t.preventDefault();this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:n,change:i||"",willCommit:!1,selStart:l,selEnd:h}})}));this._setEventListeners(i,o,[["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"]],(t=>t.target.value))}blurListener&&i.addEventListener("blur",blurListener);if(this.data.comb){const t=(this.data.rect[2]-this.data.rect[0])/a;i.classList.add("comb");i.style.letterSpacing=`calc(${t}px * var(--total-scale-factor) - 1ch)`}}else{i=document.createElement("div");i.textContent=this.data.fieldValue;i.style.verticalAlign="middle";i.style.display="table-cell";this.data.hasOwnCanvas&&(i.hidden=!0)}this._setTextStyle(i);this._setBackgroundColor(i);this._setDefaultPropertiesFromJS(i);this.container.append(i);return this.container}}class SignatureWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:!!t.data.hasOwnCanvas})}}class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms})}render(){const t=this.annotationStorage,e=this.data,i=e.id;let s=t.getValue(i,{value:e.exportValue===e.fieldValue}).value;if("string"==typeof s){s="Off"!==s;t.setValue(i,{value:s})}this.container.classList.add("buttonWidgetAnnotation","checkBox");const n=document.createElement("input");Et.add(n);n.setAttribute("data-element-id",i);n.disabled=e.readOnly;this._setRequired(n,this.data.required);n.type="checkbox";n.name=e.fieldName;s&&n.setAttribute("checked",!0);n.setAttribute("exportValue",e.exportValue);n.tabIndex=xt;n.addEventListener("change",(s=>{const{name:n,checked:a}=s.target;for(const s of this._getElementsByName(n,i)){const i=a&&s.exportValue===e.exportValue;s.domElement&&(s.domElement.checked=i);t.setValue(s.id,{value:i})}t.setValue(i,{value:a})}));n.addEventListener("resetform",(t=>{const i=e.defaultFieldValue||"Off";t.target.checked=i===e.exportValue}));if(this.enableScripting&&this.hasJSActions){n.addEventListener("updatefromsandbox",(e=>{const s={value(e){e.target.checked="Off"!==e.detail.value;t.setValue(i,{value:e.target.checked})}};this._dispatchEventFromSandbox(s,e)}));this._setEventListeners(n,null,[["change","Validate"],["change","Action"],["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"]],(t=>t.target.checked))}this._setBackgroundColor(n);this._setDefaultPropertiesFromJS(n);this.container.append(n);return this.container}}class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms})}render(){this.container.classList.add("buttonWidgetAnnotation","radioButton");const t=this.annotationStorage,e=this.data,i=e.id;let s=t.getValue(i,{value:e.fieldValue===e.buttonValue}).value;if("string"==typeof s){s=s!==e.buttonValue;t.setValue(i,{value:s})}if(s)for(const s of this._getElementsByName(e.fieldName,i))t.setValue(s.id,{value:!1});const n=document.createElement("input");Et.add(n);n.setAttribute("data-element-id",i);n.disabled=e.readOnly;this._setRequired(n,this.data.required);n.type="radio";n.name=e.fieldName;s&&n.setAttribute("checked",!0);n.tabIndex=xt;n.addEventListener("change",(e=>{const{name:s,checked:n}=e.target;for(const e of this._getElementsByName(s,i))t.setValue(e.id,{value:!1});t.setValue(i,{value:n})}));n.addEventListener("resetform",(t=>{const i=e.defaultFieldValue;t.target.checked=null!=i&&i===e.buttonValue}));if(this.enableScripting&&this.hasJSActions){const s=e.buttonValue;n.addEventListener("updatefromsandbox",(e=>{const n={value:e=>{const n=s===e.detail.value;for(const s of this._getElementsByName(e.target.name)){const e=n&&s.id===i;s.domElement&&(s.domElement.checked=e);t.setValue(s.id,{value:e})}}};this._dispatchEventFromSandbox(n,e)}));this._setEventListeners(n,null,[["change","Validate"],["change","Action"],["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"]],(t=>t.target.checked))}this._setBackgroundColor(n);this._setDefaultPropertiesFromJS(n);this.container.append(n);return this.container}}class PushButtonWidgetAnnotationElement extends LinkAnnotationElement{constructor(t){super(t,{ignoreBorder:t.data.hasAppearance})}render(){const t=super.render();t.classList.add("buttonWidgetAnnotation","pushButton");const e=t.lastChild;if(this.enableScripting&&this.hasJSActions&&e){this._setDefaultPropertiesFromJS(e);e.addEventListener("updatefromsandbox",(t=>{this._dispatchEventFromSandbox({},t)}))}return t}}class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement{constructor(t){super(t,{isRenderable:t.renderForms})}render(){this.container.classList.add("choiceWidgetAnnotation");const t=this.annotationStorage,e=this.data.id,i=t.getValue(e,{value:this.data.fieldValue}),s=document.createElement("select");Et.add(s);s.setAttribute("data-element-id",e);s.disabled=this.data.readOnly;this._setRequired(s,this.data.required);s.name=this.data.fieldName;s.tabIndex=xt;let n=this.data.combo&&this.data.options.length>0;if(!this.data.combo){s.size=this.data.options.length;this.data.multiSelect&&(s.multiple=!0)}s.addEventListener("resetform",(t=>{const e=this.data.defaultFieldValue;for(const t of s.options)t.selected=t.value===e}));for(const t of this.data.options){const e=document.createElement("option");e.textContent=t.displayValue;e.value=t.exportValue;if(i.value.includes(t.exportValue)){e.setAttribute("selected",!0);n=!1}s.append(e)}let a=null;if(n){const t=document.createElement("option");t.value=" ";t.setAttribute("hidden",!0);t.setAttribute("selected",!0);s.prepend(t);a=()=>{t.remove();s.removeEventListener("input",a);a=null};s.addEventListener("input",a)}const getValue=t=>{const e=t?"value":"textContent",{options:i,multiple:n}=s;return n?Array.prototype.filter.call(i,(t=>t.selected)).map((t=>t[e])):-1===i.selectedIndex?null:i[i.selectedIndex][e]};let r=getValue(!1);const getItems=t=>{const e=t.target.options;return Array.prototype.map.call(e,(t=>({displayValue:t.textContent,exportValue:t.value})))};if(this.enableScripting&&this.hasJSActions){s.addEventListener("updatefromsandbox",(i=>{const n={value(i){a?.();const n=i.detail.value,o=new Set(Array.isArray(n)?n:[n]);for(const t of s.options)t.selected=o.has(t.value);t.setValue(e,{value:getValue(!0)});r=getValue(!1)},multipleSelection(t){s.multiple=!0},remove(i){const n=s.options,a=i.detail.remove;n[a].selected=!1;s.remove(a);if(n.length>0){-1===Array.prototype.findIndex.call(n,(t=>t.selected))&&(n[0].selected=!0)}t.setValue(e,{value:getValue(!0),items:getItems(i)});r=getValue(!1)},clear(i){for(;0!==s.length;)s.remove(0);t.setValue(e,{value:null,items:[]});r=getValue(!1)},insert(i){const{index:n,displayValue:a,exportValue:o}=i.detail.insert,l=s.children[n],h=document.createElement("option");h.textContent=a;h.value=o;l?l.before(h):s.append(h);t.setValue(e,{value:getValue(!0),items:getItems(i)});r=getValue(!1)},items(i){const{items:n}=i.detail;for(;0!==s.length;)s.remove(0);for(const t of n){const{displayValue:e,exportValue:i}=t,n=document.createElement("option");n.textContent=e;n.value=i;s.append(n)}s.options.length>0&&(s.options[0].selected=!0);t.setValue(e,{value:getValue(!0),items:getItems(i)});r=getValue(!1)},indices(i){const s=new Set(i.detail.indices);for(const t of i.target.options)t.selected=s.has(t.index);t.setValue(e,{value:getValue(!0)});r=getValue(!1)},editable(t){t.target.disabled=!t.detail.editable}};this._dispatchEventFromSandbox(n,i)}));s.addEventListener("input",(i=>{const s=getValue(!0),n=getValue(!1);t.setValue(e,{value:s});i.preventDefault();this.linkService.eventBus?.dispatch("dispatcheventinsandbox",{source:this,detail:{id:e,name:"Keystroke",value:r,change:n,changeEx:s,willCommit:!1,commitKey:1,keyDown:!1}})}));this._setEventListeners(s,null,[["focus","Focus"],["blur","Blur"],["mousedown","Mouse Down"],["mouseenter","Mouse Enter"],["mouseleave","Mouse Exit"],["mouseup","Mouse Up"],["input","Action"],["input","Validate"]],(t=>t.target.value))}else s.addEventListener("input",(function(i){t.setValue(e,{value:getValue(!0)})}));this.data.combo&&this._setTextStyle(s);this._setBackgroundColor(s);this._setDefaultPropertiesFromJS(s);this.container.append(s);return this.container}}class PopupAnnotationElement extends AnnotationElement{constructor(t){const{data:e,elements:i}=t;super(t,{isRenderable:AnnotationElement._hasPopupData(e)});this.elements=i;this.popup=null}render(){this.container.classList.add("popupAnnotation");const t=this.popup=new PopupElement({container:this.container,color:this.data.color,titleObj:this.data.titleObj,modificationDate:this.data.modificationDate,contentsObj:this.data.contentsObj,richText:this.data.richText,rect:this.data.rect,parentRect:this.data.parentRect||null,parent:this.parent,elements:this.elements,open:this.data.open}),e=[];for(const i of this.elements){i.popup=t;i.container.ariaHasPopup="dialog";e.push(i.data.id);i.addHighlightArea()}this.container.setAttribute("aria-controls",e.map((t=>`${U}${t}`)).join(","));return this.container}}class PopupElement{#sn=this.#nn.bind(this);#an=this.#rn.bind(this);#on=this.#ln.bind(this);#hn=this.#dn.bind(this);#cn=null;#mt=null;#un=null;#pn=null;#gn=null;#fn=null;#mn=null;#bn=!1;#An=null;#T=null;#wn=null;#yn=null;#vn=null;#Ys=null;#xn=!1;constructor({container:t,color:e,elements:i,titleObj:s,modificationDate:n,contentsObj:a,richText:r,parent:o,rect:l,parentRect:h,open:d}){this.#mt=t;this.#vn=s;this.#un=a;this.#yn=r;this.#fn=o;this.#cn=e;this.#wn=l;this.#mn=h;this.#gn=i;this.#pn=PDFDateString.toDateObject(n);this.trigger=i.flatMap((t=>t.getElementsToTriggerPopup()));for(const t of this.trigger){t.addEventListener("click",this.#hn);t.addEventListener("mouseenter",this.#on);t.addEventListener("mouseleave",this.#an);t.classList.add("popupTriggerArea")}for(const t of i)t.container?.addEventListener("keydown",this.#sn);this.#mt.hidden=!0;d&&this.#dn()}render(){if(this.#An)return;const t=this.#An=document.createElement("div");t.className="popup";if(this.#cn){const e=t.style.outlineColor=Util.makeHexColor(...this.#cn);t.style.backgroundColor=`color-mix(in srgb, ${e} 30%, white)`}const e=document.createElement("span");e.className="header";const i=document.createElement("h1");e.append(i);({dir:i.dir,str:i.textContent}=this.#vn);t.append(e);if(this.#pn){const t=document.createElement("span");t.classList.add("popupDate");t.setAttribute("data-l10n-id","pdfjs-annotation-date-time-string");t.setAttribute("data-l10n-args",JSON.stringify({dateObj:this.#pn.valueOf()}));e.append(t)}const s=this.#En;if(s){XfaLayer.render({xfaHtml:s,intent:"richText",div:t});t.lastChild.classList.add("richText","popupContent")}else{const e=this._formatContents(this.#un);t.append(e)}this.#mt.append(t)}get#En(){const t=this.#yn,e=this.#un;return!t?.str||e?.str&&e.str!==t.str?null:this.#yn.html||null}get#_n(){return this.#En?.attributes?.style?.fontSize||0}get#Sn(){return this.#En?.attributes?.style?.color||null}#Cn(t){const e=[],i={str:t,html:{name:"div",attributes:{dir:"auto"},children:[{name:"p",children:e}]}},s={style:{color:this.#Sn,fontSize:this.#_n?`calc(${this.#_n}px * var(--total-scale-factor))`:""}};for(const i of t.split("\n"))e.push({name:"span",value:i,attributes:s});return i}_formatContents({str:t,dir:e}){const i=document.createElement("p");i.classList.add("popupContent");i.dir=e;const s=t.split(/(?:\r\n?|\n)/);for(let t=0,e=s.length;t=0&&n.setAttribute("stroke-width",e||1);if(i)for(let t=0,e=this.#Rn.length;t{"Enter"===t.key&&(s?t.metaKey:t.ctrlKey)&&this.#On()}));!e.popupRef&&this.hasPopupData?this._createPopup():i.classList.add("popupTriggerArea");t.append(i);return t}getElementsToTriggerPopup(){return this.#Ln}addHighlightArea(){this.container.classList.add("highlightArea")}#On(){this.downloadManager?.openOrDownloadData(this.content,this.filename)}}class AnnotationLayer{#Nn=null;#Bn=null;#Hn=new Map;#Un=null;constructor({div:t,accessibilityManager:e,annotationCanvasMap:i,annotationEditorUIManager:s,page:n,viewport:a,structTreeLayer:r}){this.div=t;this.#Nn=e;this.#Bn=i;this.#Un=r||null;this.page=n;this.viewport=a;this.zIndex=0;this._annotationEditorUIManager=s}hasEditableAnnotations(){return this.#Hn.size>0}async#zn(t,e){const i=t.firstChild||t,s=i.id=`${U}${e}`,n=await(this.#Un?.getAriaAttributes(s));if(n)for(const[t,e]of n)i.setAttribute(t,e);this.div.append(t);this.#Nn?.moveElementInDOM(this.div,t,i,!1)}async render(t){const{annotations:e}=t,i=this.div;setLayerDimensions(i,this.viewport);const s=new Map,n={data:null,layer:i,linkService:t.linkService,downloadManager:t.downloadManager,imageResourcesPath:t.imageResourcesPath||"",renderForms:!1!==t.renderForms,svgFactory:new DOMSVGFactory,annotationStorage:t.annotationStorage||new AnnotationStorage,enableScripting:!0===t.enableScripting,hasJSActions:t.hasJSActions,fieldObjects:t.fieldObjects,parent:this,elements:null};for(const t of e){if(t.noHTML)continue;const e=t.annotationType===x.POPUP;if(e){const e=s.get(t.id);if(!e)continue;n.elements=e}else if(t.rect[2]===t.rect[0]||t.rect[3]===t.rect[1])continue;n.data=t;const i=AnnotationElementFactory.create(n);if(!i.isRenderable)continue;if(!e&&t.popupRef){const e=s.get(t.popupRef);e?e.push(i):s.set(t.popupRef,[i])}const a=i.render();t.hidden&&(a.style.visibility="hidden");await this.#zn(a,t.id);if(i._isEditable){this.#Hn.set(i.data.id,i);this._annotationEditorUIManager?.renderAnnotationElement(i)}}this.#Gn()}async addLinkAnnotations(t,e){const i={data:null,layer:this.div,linkService:e,svgFactory:new DOMSVGFactory,parent:this};for(const e of t){e.borderStyle||=AnnotationLayer._defaultBorderStyle;i.data=e;const t=AnnotationElementFactory.create(i);if(!t.isRenderable)continue;const s=t.render();await this.#zn(s,e.id)}}update({viewport:t}){const e=this.div;this.viewport=t;setLayerDimensions(e,{rotation:t.rotation});this.#Gn();e.hidden=!1}#Gn(){if(!this.#Bn)return;const t=this.div;for(const[e,i]of this.#Bn){const s=t.querySelector(`[data-annotation-id="${e}"]`);if(!s)continue;i.className="annotationContent";const{firstChild:n}=s;n?"CANVAS"===n.nodeName?n.replaceWith(i):n.classList.contains("annotationContent")?n.after(i):n.before(i):s.append(i);const a=this.#Hn.get(e);if(a)if(a._hasNoCanvas){this._annotationEditorUIManager?.setMissingCanvas(e,s.id,i);a._hasNoCanvas=!1}else a.canvas=i}this.#Bn.clear()}getEditableAnnotations(){return Array.from(this.#Hn.values())}getEditableAnnotation(t){return this.#Hn.get(t)}static get _defaultBorderStyle(){return shadow(this,"_defaultBorderStyle",Object.freeze({width:1,rawWidth:1,style:E,dashArray:[3],horizontalCornerRadius:0,verticalCornerRadius:0}))}}const _t=/\r\n?|\n/g;class FreeTextEditor extends AnnotationEditor{#cn;#$n="";#Wn=`${this.id}-editor`;#Vn=null;#_n;static _freeTextDefaultContent="";static _internalPadding=0;static _defaultColor=null;static _defaultFontSize=10;static get _keyboardManager(){const t=FreeTextEditor.prototype,arrowChecker=t=>t.isEmpty(),e=AnnotationEditorUIManager.TRANSLATE_SMALL,i=AnnotationEditorUIManager.TRANSLATE_BIG;return shadow(this,"_keyboardManager",new KeyboardManager([[["ctrl+s","mac+meta+s","ctrl+p","mac+meta+p"],t.commitOrRemove,{bubbles:!0}],[["ctrl+Enter","mac+meta+Enter","Escape","mac+Escape"],t.commitOrRemove],[["ArrowLeft","mac+ArrowLeft"],t._translateEmpty,{args:[-e,0],checker:arrowChecker}],[["ctrl+ArrowLeft","mac+shift+ArrowLeft"],t._translateEmpty,{args:[-i,0],checker:arrowChecker}],[["ArrowRight","mac+ArrowRight"],t._translateEmpty,{args:[e,0],checker:arrowChecker}],[["ctrl+ArrowRight","mac+shift+ArrowRight"],t._translateEmpty,{args:[i,0],checker:arrowChecker}],[["ArrowUp","mac+ArrowUp"],t._translateEmpty,{args:[0,-e],checker:arrowChecker}],[["ctrl+ArrowUp","mac+shift+ArrowUp"],t._translateEmpty,{args:[0,-i],checker:arrowChecker}],[["ArrowDown","mac+ArrowDown"],t._translateEmpty,{args:[0,e],checker:arrowChecker}],[["ctrl+ArrowDown","mac+shift+ArrowDown"],t._translateEmpty,{args:[0,i],checker:arrowChecker}]]))}static _type="freetext";static _editorType=u.FREETEXT;constructor(t){super({...t,name:"freeTextEditor"});this.#cn=t.color||FreeTextEditor._defaultColor||AnnotationEditor._defaultLineColor;this.#_n=t.fontSize||FreeTextEditor._defaultFontSize}static initialize(t,e){AnnotationEditor.initialize(t,e);const i=getComputedStyle(document.documentElement);this._internalPadding=parseFloat(i.getPropertyValue("--freetext-padding"))}static updateDefaultParams(t,e){switch(t){case p.FREETEXT_SIZE:FreeTextEditor._defaultFontSize=e;break;case p.FREETEXT_COLOR:FreeTextEditor._defaultColor=e}}updateParams(t,e){switch(t){case p.FREETEXT_SIZE:this.#jn(e);break;case p.FREETEXT_COLOR:this.#qn(e)}}static get defaultPropertiesToUpdate(){return[[p.FREETEXT_SIZE,FreeTextEditor._defaultFontSize],[p.FREETEXT_COLOR,FreeTextEditor._defaultColor||AnnotationEditor._defaultLineColor]]}get propertiesToUpdate(){return[[p.FREETEXT_SIZE,this.#_n],[p.FREETEXT_COLOR,this.#cn]]}#jn(t){const setFontsize=t=>{this.editorDiv.style.fontSize=`calc(${t}px * var(--total-scale-factor))`;this.translate(0,-(t-this.#_n)*this.parentScale);this.#_n=t;this.#Xn()},e=this.#_n;this.addCommands({cmd:setFontsize.bind(this,t),undo:setFontsize.bind(this,e),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.FREETEXT_SIZE,overwriteIfSameType:!0,keepUndo:!0})}#qn(t){const setColor=t=>{this.#cn=this.editorDiv.style.color=t},e=this.#cn;this.addCommands({cmd:setColor.bind(this,t),undo:setColor.bind(this,e),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.FREETEXT_COLOR,overwriteIfSameType:!0,keepUndo:!0})}_translateEmpty(t,e){this._uiManager.translateSelectedEditors(t,e,!0)}getInitialTranslation(){const t=this.parentScale;return[-FreeTextEditor._internalPadding*t,-(FreeTextEditor._internalPadding+this.#_n)*t]}rebuild(){if(this.parent){super.rebuild();null!==this.div&&(this.isAttachedToDOM||this.parent.add(this))}}enableEditMode(){if(!super.enableEditMode())return!1;this.overlayDiv.classList.remove("enabled");this.editorDiv.contentEditable=!0;this._isDraggable=!1;this.div.removeAttribute("aria-activedescendant");this.#Vn=new AbortController;const t=this._uiManager.combinedSignal(this.#Vn);this.editorDiv.addEventListener("keydown",this.editorDivKeydown.bind(this),{signal:t});this.editorDiv.addEventListener("focus",this.editorDivFocus.bind(this),{signal:t});this.editorDiv.addEventListener("blur",this.editorDivBlur.bind(this),{signal:t});this.editorDiv.addEventListener("input",this.editorDivInput.bind(this),{signal:t});this.editorDiv.addEventListener("paste",this.editorDivPaste.bind(this),{signal:t});return!0}disableEditMode(){if(!super.disableEditMode())return!1;this.overlayDiv.classList.add("enabled");this.editorDiv.contentEditable=!1;this.div.setAttribute("aria-activedescendant",this.#Wn);this._isDraggable=!0;this.#Vn?.abort();this.#Vn=null;this.div.focus({preventScroll:!0});this.isEditing=!1;this.parent.div.classList.add("freetextEditing");return!0}focusin(t){if(this._focusEventsAllowed){super.focusin(t);t.target!==this.editorDiv&&this.editorDiv.focus()}}onceAdded(t){if(!this.width){this.enableEditMode();t&&this.editorDiv.focus();this._initialOptions?.isCentered&&this.center();this._initialOptions=null}}isEmpty(){return!this.editorDiv||""===this.editorDiv.innerText.trim()}remove(){this.isEditing=!1;if(this.parent){this.parent.setEditingState(!0);this.parent.div.classList.add("freetextEditing")}super.remove()}#Kn(){const t=[];this.editorDiv.normalize();let e=null;for(const i of this.editorDiv.childNodes)if(e?.nodeType!==Node.TEXT_NODE||"BR"!==i.nodeName){t.push(FreeTextEditor.#Yn(i));e=i}return t.join("\n")}#Xn(){const[t,e]=this.parentDimensions;let i;if(this.isAttachedToDOM)i=this.div.getBoundingClientRect();else{const{currentLayer:t,div:e}=this,s=e.style.display,n=e.classList.contains("hidden");e.classList.remove("hidden");e.style.display="hidden";t.div.append(this.div);i=e.getBoundingClientRect();e.remove();e.style.display=s;e.classList.toggle("hidden",n)}if(this.rotation%180==this.parentRotation%180){this.width=i.width/t;this.height=i.height/e}else{this.width=i.height/t;this.height=i.width/e}this.fixAndSetPosition()}commit(){if(!this.isInEditMode())return;super.commit();this.disableEditMode();const t=this.#$n,e=this.#$n=this.#Kn().trimEnd();if(t===e)return;const setText=t=>{this.#$n=t;if(t){this.#Qn();this._uiManager.rebuild(this);this.#Xn()}else this.remove()};this.addCommands({cmd:()=>{setText(e)},undo:()=>{setText(t)},mustExec:!1});this.#Xn()}shouldGetKeyboardEvents(){return this.isInEditMode()}enterInEditMode(){this.enableEditMode();this.editorDiv.focus()}keydown(t){if(t.target===this.div&&"Enter"===t.key){this.enterInEditMode();t.preventDefault()}}editorDivKeydown(t){FreeTextEditor._keyboardManager.exec(this,t)}editorDivFocus(t){this.isEditing=!0}editorDivBlur(t){this.isEditing=!1}editorDivInput(t){this.parent.div.classList.toggle("freetextEditing",this.isEmpty())}disableEditing(){this.editorDiv.setAttribute("role","comment");this.editorDiv.removeAttribute("aria-multiline")}enableEditing(){this.editorDiv.setAttribute("role","textbox");this.editorDiv.setAttribute("aria-multiline",!0)}get canChangeContent(){return!0}render(){if(this.div)return this.div;let t,e;if(this._isCopy||this.annotationElementId){t=this.x;e=this.y}super.render();this.editorDiv=document.createElement("div");this.editorDiv.className="internal";this.editorDiv.setAttribute("id",this.#Wn);this.editorDiv.setAttribute("data-l10n-id","pdfjs-free-text2");this.editorDiv.setAttribute("data-l10n-attrs","default-content");this.enableEditing();this.editorDiv.contentEditable=!0;const{style:i}=this.editorDiv;i.fontSize=`calc(${this.#_n}px * var(--total-scale-factor))`;i.color=this.#cn;this.div.append(this.editorDiv);this.overlayDiv=document.createElement("div");this.overlayDiv.classList.add("overlay","enabled");this.div.append(this.overlayDiv);if(this._isCopy||this.annotationElementId){const[i,s]=this.parentDimensions;if(this.annotationElementId){const{position:n}=this._initialData;let[a,r]=this.getInitialTranslation();[a,r]=this.pageTranslationToScreen(a,r);const[o,l]=this.pageDimensions,[h,d]=this.pageTranslation;let c,u;switch(this.rotation){case 0:c=t+(n[0]-h)/o;u=e+this.height-(n[1]-d)/l;break;case 90:c=t+(n[0]-h)/o;u=e-(n[1]-d)/l;[a,r]=[r,-a];break;case 180:c=t-this.width+(n[0]-h)/o;u=e-(n[1]-d)/l;[a,r]=[-a,-r];break;case 270:c=t+(n[0]-h-this.height*l)/o;u=e+(n[1]-d-this.width*o)/l;[a,r]=[-r,a]}this.setAt(c*i,u*s,a,r)}else this._moveAfterPaste(t,e);this.#Qn();this._isDraggable=!0;this.editorDiv.contentEditable=!1}else{this._isDraggable=!1;this.editorDiv.contentEditable=!0}return this.div}static#Yn(t){return(t.nodeType===Node.TEXT_NODE?t.nodeValue:t.innerText).replaceAll(_t,"")}editorDivPaste(t){const e=t.clipboardData||window.clipboardData,{types:i}=e;if(1===i.length&&"text/plain"===i[0])return;t.preventDefault();const s=FreeTextEditor.#Jn(e.getData("text")||"").replaceAll(_t,"\n");if(!s)return;const n=window.getSelection();if(!n.rangeCount)return;this.editorDiv.normalize();n.deleteFromDocument();const a=n.getRangeAt(0);if(!s.includes("\n")){a.insertNode(document.createTextNode(s));this.editorDiv.normalize();n.collapseToStart();return}const{startContainer:r,startOffset:o}=a,l=[],h=[];if(r.nodeType===Node.TEXT_NODE){const t=r.parentElement;h.push(r.nodeValue.slice(o).replaceAll(_t,""));if(t!==this.editorDiv){let e=l;for(const i of this.editorDiv.childNodes)i!==t?e.push(FreeTextEditor.#Yn(i)):e=h}l.push(r.nodeValue.slice(0,o).replaceAll(_t,""))}else if(r===this.editorDiv){let t=l,e=0;for(const i of this.editorDiv.childNodes){e++===o&&(t=h);t.push(FreeTextEditor.#Yn(i))}}this.#$n=`${l.join("\n")}${s}${h.join("\n")}`;this.#Qn();const d=new Range;let c=Math.sumPrecise(l.map((t=>t.length)));for(const{firstChild:t}of this.editorDiv.childNodes)if(t.nodeType===Node.TEXT_NODE){const e=t.nodeValue.length;if(c<=e){d.setStart(t,c);d.setEnd(t,c);break}c-=e}n.removeAllRanges();n.addRange(d)}#Qn(){this.editorDiv.replaceChildren();if(this.#$n)for(const t of this.#$n.split("\n")){const e=document.createElement("div");e.append(t?document.createTextNode(t):document.createElement("br"));this.editorDiv.append(e)}}#Zn(){return this.#$n.replaceAll(" "," ")}static#Jn(t){return t.replaceAll(" "," ")}get contentDiv(){return this.editorDiv}static async deserialize(t,e,i){let s=null;if(t instanceof FreeTextAnnotationElement){const{data:{defaultAppearanceData:{fontSize:e,fontColor:i},rect:n,rotation:a,id:r,popupRef:o},textContent:l,textPosition:h,parent:{page:{pageNumber:d}}}=t;if(!l||0===l.length)return null;s=t={annotationType:u.FREETEXT,color:Array.from(i),fontSize:e,value:l.join("\n"),position:h,pageIndex:d-1,rect:n.slice(0),rotation:a,id:r,deleted:!1,popupRef:o}}const n=await super.deserialize(t,e,i);n.#_n=t.fontSize;n.#cn=Util.makeHexColor(...t.color);n.#$n=FreeTextEditor.#Jn(t.value);n.annotationElementId=t.id||null;n._initialData=s;return n}serialize(t=!1){if(this.isEmpty())return null;if(this.deleted)return this.serializeDeleted();const e=FreeTextEditor._internalPadding*this.parentScale,i=this.getRect(e,e),s=AnnotationEditor._colorManager.convert(this.isAttachedToDOM?getComputedStyle(this.editorDiv).color:this.#cn),n={annotationType:u.FREETEXT,color:s,fontSize:this.#_n,value:this.#Zn(),pageIndex:this.pageIndex,rect:i,rotation:this.rotation,structTreeParentId:this._structTreeParentId};if(t){n.isCopy=!0;return n}if(this.annotationElementId&&!this.#ta(n))return null;n.id=this.annotationElementId;return n}#ta(t){const{value:e,fontSize:i,color:s,pageIndex:n}=this._initialData;return this._hasBeenMoved||t.value!==e||t.fontSize!==i||t.color.some(((t,e)=>t!==s[e]))||t.pageIndex!==n}renderAnnotationElement(t){const e=super.renderAnnotationElement(t);if(this.deleted)return e;const{style:i}=e;i.fontSize=`calc(${this.#_n}px * var(--total-scale-factor))`;i.color=this.#cn;e.replaceChildren();for(const t of this.#$n.split("\n")){const i=document.createElement("div");i.append(t?document.createTextNode(t):document.createElement("br"));e.append(i)}const s=FreeTextEditor._internalPadding*this.parentScale;t.updateEdited({rect:this.getRect(s,s),popupContent:this.#$n});return e}resetAnnotationElement(t){super.resetAnnotationElement(t);t.resetEdited()}}class Outline{static PRECISION=1e-4;toSVGPath(){unreachable("Abstract method `toSVGPath` must be implemented.")}get box(){unreachable("Abstract getter `box` must be implemented.")}serialize(t,e){unreachable("Abstract method `serialize` must be implemented.")}static _rescale(t,e,i,s,n,a){a||=new Float32Array(t.length);for(let r=0,o=t.length;r=6;t-=6)isNaN(e[t])?i.push(`L${e[t+4]} ${e[t+5]}`):i.push(`C${e[t]} ${e[t+1]} ${e[t+2]} ${e[t+3]} ${e[t+4]} ${e[t+5]}`);this.#ya(i);return i.join(" ")}#Aa(){const[t,e,i,s]=this.#ea,[n,a,r,o]=this.#ba();return`M${(this.#ra[2]-t)/i} ${(this.#ra[3]-e)/s} L${(this.#ra[4]-t)/i} ${(this.#ra[5]-e)/s} L${n} ${a} L${r} ${o} L${(this.#ra[16]-t)/i} ${(this.#ra[17]-e)/s} L${(this.#ra[14]-t)/i} ${(this.#ra[15]-e)/s} Z`}#ya(t){const e=this.#ia;t.push(`L${e[4]} ${e[5]} Z`)}#wa(t){const[e,i,s,n]=this.#ea,a=this.#ra.subarray(4,6),r=this.#ra.subarray(16,18),[o,l,h,d]=this.#ba();t.push(`L${(a[0]-e)/s} ${(a[1]-i)/n} L${o} ${l} L${h} ${d} L${(r[0]-e)/s} ${(r[1]-i)/n}`)}newFreeDrawOutline(t,e,i,s,n,a){return new FreeDrawOutline(t,e,i,s,n,a)}getOutlines(){const t=this.#aa,e=this.#ia,i=this.#ra,[s,n,a,r]=this.#ea,o=new Float32Array((this.#pa?.length??0)+2);for(let t=0,e=o.length-2;t=6;t-=6)for(let i=0;i<6;i+=2)if(isNaN(e[t+i])){l[h]=l[h+1]=NaN;h+=2}else{l[h]=e[t+i];l[h+1]=e[t+i+1];h+=2}this.#Ea(l,h);return this.newFreeDrawOutline(l,o,this.#ea,this.#ca,this.#sa,this.#na)}#va(t){const e=this.#ra,[i,s,n,a]=this.#ea,[r,o,l,h]=this.#ba(),d=new Float32Array(36);d.set([NaN,NaN,NaN,NaN,(e[2]-i)/n,(e[3]-s)/a,NaN,NaN,NaN,NaN,(e[4]-i)/n,(e[5]-s)/a,NaN,NaN,NaN,NaN,r,o,NaN,NaN,NaN,NaN,l,h,NaN,NaN,NaN,NaN,(e[16]-i)/n,(e[17]-s)/a,NaN,NaN,NaN,NaN,(e[14]-i)/n,(e[15]-s)/a],0);return this.newFreeDrawOutline(d,t,this.#ea,this.#ca,this.#sa,this.#na)}#Ea(t,e){const i=this.#ia;t.set([NaN,NaN,NaN,NaN,i[4],i[5]],e);return e+6}#xa(t,e){const i=this.#ra.subarray(4,6),s=this.#ra.subarray(16,18),[n,a,r,o]=this.#ea,[l,h,d,c]=this.#ba();t.set([NaN,NaN,NaN,NaN,(i[0]-n)/r,(i[1]-a)/o,NaN,NaN,NaN,NaN,l,h,NaN,NaN,NaN,NaN,d,c,NaN,NaN,NaN,NaN,(s[0]-n)/r,(s[1]-a)/o],e);return e+24}}class FreeDrawOutline extends Outline{#ea;#_a=new Float32Array(4);#sa;#na;#pa;#ca;#Sa;constructor(t,e,i,s,n,a){super();this.#Sa=t;this.#pa=e;this.#ea=i;this.#ca=s;this.#sa=n;this.#na=a;this.lastPoint=[NaN,NaN];this.#Ca(a);const[r,o,l,h]=this.#_a;for(let e=0,i=t.length;et[0]-e[0]||t[1]-e[1]||t[2]-e[2]));const t=[];for(const e of this.#Ma)if(e[3]){t.push(...this.#Pa(e));this.#ka(e)}else{this.#Ia(e);t.push(...this.#Pa(e))}return this.#Ra(t)}#Ra(t){const e=[],i=new Set;for(const i of t){const[t,s,n]=i;e.push([t,s,i],[t,n,i])}e.sort(((t,e)=>t[1]-e[1]||t[0]-e[0]));for(let t=0,s=e.length;t0;){const t=i.values().next().value;let[e,a,r,o,l]=t;i.delete(t);let h=e,d=a;n=[e,r];s.push(n);for(;;){let t;if(i.has(o))t=o;else{if(!i.has(l))break;t=l}i.delete(t);[e,a,r,o,l]=t;if(h!==e){n.push(h,d,e,d===a?a:r);h=e}d=d===a?r:a}n.push(h,d)}return new HighlightOutline(s,this.#ea,this.#Ta)}#Fa(t){const e=this.#Da;let i=0,s=e.length-1;for(;i<=s;){const n=i+s>>1,a=e[n][0];if(a===t)return n;a=0;s--){const[i,n]=this.#Da[s];if(i!==t)break;if(i===t&&n===e){this.#Da.splice(s,1);return}}}#Pa(t){const[e,i,s]=t,n=[[e,i,s]],a=this.#Fa(s);for(let t=0;t=i)if(o>s)n[t][1]=s;else{if(1===a)return[];n.splice(t,1);t--;a--}else{n[t][2]=i;o>s&&n.push([e,s,o])}}}return n}}class HighlightOutline extends Outline{#ea;#La;constructor(t,e,i){super();this.#La=t;this.#ea=e;this.lastPoint=i}toSVGPath(){const t=[];for(const e of this.#La){let[i,s]=e;t.push(`M${i} ${s}`);for(let n=2;n-1){this.#ar=!0;this.#dr(t);this.#cr()}else if(this.#Ja){this.#Ya=t.anchorNode;this.#Qa=t.anchorOffset;this.#er=t.focusNode;this.#ir=t.focusOffset;this.#ur();this.#cr();this.rotate(this.rotation)}}get telemetryInitialData(){return{action:"added",type:this.#ar?"free_highlight":"highlight",color:this._uiManager.highlightColorNames.get(this.color),thickness:this.#ua,methodOfCreation:this.#hr}}get telemetryFinalData(){return{type:"highlight",color:this._uiManager.highlightColorNames.get(this.color)}}static computeTelemetryFinalData(t){return{numberOfColors:t.get("color").size}}#ur(){const t=new HighlightOutliner(this.#Ja,.001);this.#nr=t.getOutlines();[this.x,this.y,this.width,this.height]=this.#nr.box;const e=new HighlightOutliner(this.#Ja,.0025,.001,"ltr"===this._uiManager.direction);this.#tr=e.getOutlines();const{lastPoint:i}=this.#tr;this.#Ta=[(i[0]-this.x)/this.width,(i[1]-this.y)/this.height]}#dr({highlightOutlines:t,highlightId:e,clipPathId:i}){this.#nr=t;this.#tr=t.getNewOutline(this.#ua/2+1.5,.0025);if(e>=0){this.#y=e;this.#Za=i;this.parent.drawLayer.finalizeDraw(e,{bbox:t.box,path:{d:t.toSVGPath()}});this.#or=this.parent.drawLayer.drawOutline({rootClass:{highlightOutline:!0,free:!0},bbox:this.#tr.box,path:{d:this.#tr.toSVGPath()}},!0)}else if(this.parent){const e=this.parent.viewport.rotation;this.parent.drawLayer.updateProperties(this.#y,{bbox:HighlightEditor.#pr(this.#nr.box,(e-this.rotation+360)%360),path:{d:t.toSVGPath()}});this.parent.drawLayer.updateProperties(this.#or,{bbox:HighlightEditor.#pr(this.#tr.box,e),path:{d:this.#tr.toSVGPath()}})}const[s,n,a,r]=t.box;switch(this.rotation){case 0:this.x=s;this.y=n;this.width=a;this.height=r;break;case 90:{const[t,e]=this.parentDimensions;this.x=n;this.y=1-s;this.width=a*e/t;this.height=r*t/e;break}case 180:this.x=1-s;this.y=1-n;this.width=a;this.height=r;break;case 270:{const[t,e]=this.parentDimensions;this.x=1-n;this.y=s;this.width=a*e/t;this.height=r*t/e;break}}const{lastPoint:o}=this.#tr;this.#Ta=[(o[0]-s)/a,(o[1]-n)/r]}static initialize(t,e){AnnotationEditor.initialize(t,e);HighlightEditor._defaultColor||=e.highlightColors?.values().next().value||"#fff066"}static updateDefaultParams(t,e){switch(t){case p.HIGHLIGHT_DEFAULT_COLOR:HighlightEditor._defaultColor=e;break;case p.HIGHLIGHT_THICKNESS:HighlightEditor._defaultThickness=e}}translateInPage(t,e){}get toolbarPosition(){return this.#Ta}updateParams(t,e){switch(t){case p.HIGHLIGHT_COLOR:this.#qn(e);break;case p.HIGHLIGHT_THICKNESS:this.#gr(e)}}static get defaultPropertiesToUpdate(){return[[p.HIGHLIGHT_DEFAULT_COLOR,HighlightEditor._defaultColor],[p.HIGHLIGHT_THICKNESS,HighlightEditor._defaultThickness]]}get propertiesToUpdate(){return[[p.HIGHLIGHT_COLOR,this.color||HighlightEditor._defaultColor],[p.HIGHLIGHT_THICKNESS,this.#ua||HighlightEditor._defaultThickness],[p.HIGHLIGHT_FREE,this.#ar]]}#qn(t){const setColorAndOpacity=(t,e)=>{this.color=t;this.#rr=e;this.parent?.drawLayer.updateProperties(this.#y,{root:{fill:t,"fill-opacity":e}});this.#n?.updateColor(t)},e=this.color,i=this.#rr;this.addCommands({cmd:setColorAndOpacity.bind(this,t,HighlightEditor._defaultOpacity),undo:setColorAndOpacity.bind(this,e,i),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.HIGHLIGHT_COLOR,overwriteIfSameType:!0,keepUndo:!0});this._reportTelemetry({action:"color_changed",color:this._uiManager.highlightColorNames.get(t)},!0)}#gr(t){const e=this.#ua,setThickness=t=>{this.#ua=t;this.#fr(t)};this.addCommands({cmd:setThickness.bind(this,t),undo:setThickness.bind(this,e),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:p.INK_THICKNESS,overwriteIfSameType:!0,keepUndo:!0});this._reportTelemetry({action:"thickness_changed",thickness:t},!0)}async addEditToolbar(){const t=await super.addEditToolbar();if(!t)return null;if(this._uiManager.highlightColors){this.#n=new ColorPicker({editor:this});t.addColorPicker(this.#n)}return t}disableEditing(){super.disableEditing();this.div.classList.toggle("disabled",!0)}enableEditing(){super.enableEditing();this.div.classList.toggle("disabled",!1)}fixAndSetPosition(){return super.fixAndSetPosition(this.#mr())}getBaseTranslation(){return[0,0]}getRect(t,e){return super.getRect(t,e,this.#mr())}onceAdded(t){this.annotationElementId||this.parent.addUndoableEditor(this);t&&this.div.focus()}remove(){this.#br();this._reportTelemetry({action:"deleted"});super.remove()}rebuild(){if(this.parent){super.rebuild();if(null!==this.div){this.#cr();this.isAttachedToDOM||this.parent.add(this)}}}setParent(t){let e=!1;if(this.parent&&!t)this.#br();else if(t){this.#cr(t);e=!this.parent&&this.div?.classList.contains("selectedEditor")}super.setParent(t);this.show(this._isVisible);e&&this.select()}#fr(t){if(!this.#ar)return;this.#dr({highlightOutlines:this.#nr.getNewOutline(t/2)});this.fixAndSetPosition();const[e,i]=this.parentDimensions;this.setDims(this.width*e,this.height*i)}#br(){if(null!==this.#y&&this.parent){this.parent.drawLayer.remove(this.#y);this.#y=null;this.parent.drawLayer.remove(this.#or);this.#or=null}}#cr(t=this.parent){if(null===this.#y){({id:this.#y,clipPathId:this.#Za}=t.drawLayer.draw({bbox:this.#nr.box,root:{viewBox:"0 0 1 1",fill:this.color,"fill-opacity":this.#rr},rootClass:{highlight:!0,free:this.#ar},path:{d:this.#nr.toSVGPath()}},!1,!0));this.#or=t.drawLayer.drawOutline({rootClass:{highlightOutline:!0,free:this.#ar},bbox:this.#tr.box,path:{d:this.#tr.toSVGPath()}},this.#ar);this.#sr&&(this.#sr.style.clipPath=this.#Za)}}static#pr([t,e,i,s],n){switch(n){case 90:return[1-e-s,t,s,i];case 180:return[1-t-i,1-e-s,i,s];case 270:return[e,1-t-i,s,i]}return[t,e,i,s]}rotate(t){const{drawLayer:e}=this.parent;let i;if(this.#ar){t=(t-this.rotation+360)%360;i=HighlightEditor.#pr(this.#nr.box,t)}else i=HighlightEditor.#pr([this.x,this.y,this.width,this.height],t);e.updateProperties(this.#y,{bbox:i,root:{"data-main-rotation":t}});e.updateProperties(this.#or,{bbox:HighlightEditor.#pr(this.#tr.box,t),root:{"data-main-rotation":t}})}render(){if(this.div)return this.div;const t=super.render();if(this.#lr){t.setAttribute("aria-label",this.#lr);t.setAttribute("role","mark")}this.#ar?t.classList.add("free"):this.div.addEventListener("keydown",this.#Ar.bind(this),{signal:this._uiManager._signal});const e=this.#sr=document.createElement("div");t.append(e);e.setAttribute("aria-hidden","true");e.className="internal";e.style.clipPath=this.#Za;const[i,s]=this.parentDimensions;this.setDims(this.width*i,this.height*s);bindEvents(this,this.#sr,["pointerover","pointerleave"]);this.enableEditing();return t}pointerover(){this.isSelected||this.parent?.drawLayer.updateProperties(this.#or,{rootClass:{hovered:!0}})}pointerleave(){this.isSelected||this.parent?.drawLayer.updateProperties(this.#or,{rootClass:{hovered:!1}})}#Ar(t){HighlightEditor._keyboardManager.exec(this,t)}_moveCaret(t){this.parent.unselect(this);switch(t){case 0:case 2:this.#wr(!0);break;case 1:case 3:this.#wr(!1)}}#wr(t){if(!this.#Ya)return;const e=window.getSelection();t?e.setPosition(this.#Ya,this.#Qa):e.setPosition(this.#er,this.#ir)}select(){super.select();this.#or&&this.parent?.drawLayer.updateProperties(this.#or,{rootClass:{hovered:!1,selected:!0}})}unselect(){super.unselect();if(this.#or){this.parent?.drawLayer.updateProperties(this.#or,{rootClass:{selected:!1}});this.#ar||this.#wr(!1)}}get _mustFixPosition(){return!this.#ar}show(t=this._isVisible){super.show(t);if(this.parent){this.parent.drawLayer.updateProperties(this.#y,{rootClass:{hidden:!t}});this.parent.drawLayer.updateProperties(this.#or,{rootClass:{hidden:!t}})}}#mr(){return this.#ar?this.rotation:0}#yr(){if(this.#ar)return null;const[t,e]=this.pageDimensions,[i,s]=this.pageTranslation,n=this.#Ja,a=new Float32Array(8*n.length);let r=0;for(const{x:o,y:l,width:h,height:d}of n){const n=o*t+i,c=(1-l)*e+s;a[r]=a[r+4]=n;a[r+1]=a[r+3]=c;a[r+2]=a[r+6]=n+h*t;a[r+5]=a[r+7]=c-d*e;r+=8}return a}#vr(t){return this.#nr.serialize(t,this.#mr())}static startHighlighting(t,e,{target:i,x:s,y:n}){const{x:a,y:r,width:o,height:l}=i.getBoundingClientRect(),h=new AbortController,d=t.combinedSignal(h),pointerUpCallback=e=>{h.abort();this.#xr(t,e)};window.addEventListener("blur",pointerUpCallback,{signal:d});window.addEventListener("pointerup",pointerUpCallback,{signal:d});window.addEventListener("pointerdown",stopEvent,{capture:!0,passive:!1,signal:d});window.addEventListener("contextmenu",noContextMenu,{signal:d});i.addEventListener("pointermove",this.#Er.bind(this,t),{signal:d});this._freeHighlight=new FreeHighlightOutliner({x:s,y:n},[a,r,o,l],t.scale,this._defaultThickness/2,e,.001);({id:this._freeHighlightId,clipPathId:this._freeHighlightClipId}=t.drawLayer.draw({bbox:[0,0,1,1],root:{viewBox:"0 0 1 1",fill:this._defaultColor,"fill-opacity":this._defaultOpacity},rootClass:{highlight:!0,free:!0},path:{d:this._freeHighlight.toSVGPath()}},!0,!0))}static#Er(t,e){this._freeHighlight.add(e)&&t.drawLayer.updateProperties(this._freeHighlightId,{path:{d:this._freeHighlight.toSVGPath()}})}static#xr(t,e){this._freeHighlight.isEmpty()?t.drawLayer.remove(this._freeHighlightId):t.createAndAddNewEditor(e,!1,{highlightId:this._freeHighlightId,highlightOutlines:this._freeHighlight.getOutlines(),clipPathId:this._freeHighlightClipId,methodOfCreation:"main_toolbar"});this._freeHighlightId=-1;this._freeHighlight=null;this._freeHighlightClipId=""}static async deserialize(t,e,i){let s=null;if(t instanceof HighlightAnnotationElement){const{data:{quadPoints:e,rect:i,rotation:n,id:a,color:r,opacity:o,popupRef:l},parent:{page:{pageNumber:h}}}=t;s=t={annotationType:u.HIGHLIGHT,color:Array.from(r),opacity:o,quadPoints:e,boxes:null,pageIndex:h-1,rect:i.slice(0),rotation:n,id:a,deleted:!1,popupRef:l}}else if(t instanceof InkAnnotationElement){const{data:{inkLists:e,rect:i,rotation:n,id:a,color:r,borderStyle:{rawWidth:o},popupRef:l},parent:{page:{pageNumber:h}}}=t;s=t={annotationType:u.HIGHLIGHT,color:Array.from(r),thickness:o,inkLists:e,boxes:null,pageIndex:h-1,rect:i.slice(0),rotation:n,id:a,deleted:!1,popupRef:l}}const{color:n,quadPoints:a,inkLists:r,opacity:o}=t,l=await super.deserialize(t,e,i);l.color=Util.makeHexColor(...n);l.#rr=o||1;r&&(l.#ua=t.thickness);l.annotationElementId=t.id||null;l._initialData=s;const[h,d]=l.pageDimensions,[c,p]=l.pageTranslation;if(a){const t=l.#Ja=[];for(let e=0;et!==e[i]))}renderAnnotationElement(t){t.updateEdited({rect:this.getRect(0,0)});return null}static canCreateNewEmptyEditor(){return!1}}class DrawingOptions{#_r=Object.create(null);updateProperty(t,e){this[t]=e;this.updateSVGProperty(t,e)}updateProperties(t){if(t)for(const[e,i]of Object.entries(t))e.startsWith("_")||this.updateProperty(e,i)}updateSVGProperty(t,e){this.#_r[t]=e}toSVGProperties(){const t=this.#_r;this.#_r=Object.create(null);return{root:t}}reset(){this.#_r=Object.create(null)}updateAll(t=this){this.updateProperties(t)}clone(){unreachable("Not implemented")}}class DrawingEditor extends AnnotationEditor{#Sr=null;#Cr;_drawId=null;static _currentDrawId=-1;static _currentParent=null;static#Tr=null;static#Mr=null;static#Dr=null;static#Pr=NaN;static#kr=null;static#Ir=null;static#Rr=NaN;static _INNER_MARGIN=3;constructor(t){super(t);this.#Cr=t.mustBeCommitted||!1;this._addOutlines(t)}_addOutlines(t){if(t.drawOutlines){this.#Fr(t);this.#cr()}}#Fr({drawOutlines:t,drawId:e,drawingOptions:i}){this.#Sr=t;this._drawingOptions||=i;if(e>=0){this._drawId=e;this.parent.drawLayer.finalizeDraw(e,t.defaultProperties)}else this._drawId=this.#Lr(t,this.parent);this.#Or(t.box)}#Lr(t,e){const{id:i}=e.drawLayer.draw(DrawingEditor._mergeSVGProperties(this._drawingOptions.toSVGProperties(),t.defaultSVGProperties),!1,!1);return i}static _mergeSVGProperties(t,e){const i=new Set(Object.keys(t));for(const[s,n]of Object.entries(e))i.has(s)?Object.assign(t[s],n):t[s]=n;return t}static getDefaultDrawingOptions(t){unreachable("Not implemented")}static get typesMap(){unreachable("Not implemented")}static get isDrawer(){return!0}static get supportMultipleDrawings(){return!1}static updateDefaultParams(t,e){const i=this.typesMap.get(t);i&&this._defaultDrawingOptions.updateProperty(i,e);if(this._currentParent){DrawingEditor.#Tr.updateProperty(i,e);this._currentParent.drawLayer.updateProperties(this._currentDrawId,this._defaultDrawingOptions.toSVGProperties())}}updateParams(t,e){const i=this.constructor.typesMap.get(t);i&&this._updateProperty(t,i,e)}static get defaultPropertiesToUpdate(){const t=[],e=this._defaultDrawingOptions;for(const[i,s]of this.typesMap)t.push([i,e[s]]);return t}get propertiesToUpdate(){const t=[],{_drawingOptions:e}=this;for(const[i,s]of this.constructor.typesMap)t.push([i,e[s]]);return t}_updateProperty(t,e,i){const s=this._drawingOptions,n=s[e],setter=t=>{s.updateProperty(e,t);const i=this.#Sr.updateProperty(e,t);i&&this.#Or(i);this.parent?.drawLayer.updateProperties(this._drawId,s.toSVGProperties())};this.addCommands({cmd:setter.bind(this,i),undo:setter.bind(this,n),post:this._uiManager.updateUI.bind(this._uiManager,this),mustExec:!0,type:t,overwriteIfSameType:!0,keepUndo:!0})}_onResizing(){this.parent?.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties(this.#Sr.getPathResizingSVGProperties(this.#Nr()),{bbox:this.#Br()}))}_onResized(){this.parent?.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties(this.#Sr.getPathResizedSVGProperties(this.#Nr()),{bbox:this.#Br()}))}_onTranslating(t,e){this.parent?.drawLayer.updateProperties(this._drawId,{bbox:this.#Br()})}_onTranslated(){this.parent?.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties(this.#Sr.getPathTranslatedSVGProperties(this.#Nr(),this.parentDimensions),{bbox:this.#Br()}))}_onStartDragging(){this.parent?.drawLayer.updateProperties(this._drawId,{rootClass:{moving:!0}})}_onStopDragging(){this.parent?.drawLayer.updateProperties(this._drawId,{rootClass:{moving:!1}})}commit(){super.commit();this.disableEditMode();this.disableEditing()}disableEditing(){super.disableEditing();this.div.classList.toggle("disabled",!0)}enableEditing(){super.enableEditing();this.div.classList.toggle("disabled",!1)}getBaseTranslation(){return[0,0]}get isResizable(){return!0}onceAdded(t){this.annotationElementId||this.parent.addUndoableEditor(this);this._isDraggable=!0;if(this.#Cr){this.#Cr=!1;this.commit();this.parent.setSelected(this);t&&this.isOnScreen&&this.div.focus()}}remove(){this.#br();super.remove()}rebuild(){if(this.parent){super.rebuild();if(null!==this.div){this.#cr();this.#Or(this.#Sr.box);this.isAttachedToDOM||this.parent.add(this)}}}setParent(t){let e=!1;if(this.parent&&!t){this._uiManager.removeShouldRescale(this);this.#br()}else if(t){this._uiManager.addShouldRescale(this);this.#cr(t);e=!this.parent&&this.div?.classList.contains("selectedEditor")}super.setParent(t);e&&this.select()}#br(){if(null!==this._drawId&&this.parent){this.parent.drawLayer.remove(this._drawId);this._drawId=null;this._drawingOptions.reset()}}#cr(t=this.parent){if(null===this._drawId||this.parent!==t)if(null===this._drawId){this._drawingOptions.updateAll();this._drawId=this.#Lr(this.#Sr,t)}else this.parent.drawLayer.updateParent(this._drawId,t.drawLayer)}#Hr([t,e,i,s]){const{parentDimensions:[n,a],rotation:r}=this;switch(r){case 90:return[e,1-t,i*(a/n),s*(n/a)];case 180:return[1-t,1-e,i,s];case 270:return[1-e,t,i*(a/n),s*(n/a)];default:return[t,e,i,s]}}#Nr(){const{x:t,y:e,width:i,height:s,parentDimensions:[n,a],rotation:r}=this;switch(r){case 90:return[1-e,t,i*(n/a),s*(a/n)];case 180:return[1-t,1-e,i,s];case 270:return[e,1-t,i*(n/a),s*(a/n)];default:return[t,e,i,s]}}#Or(t){[this.x,this.y,this.width,this.height]=this.#Hr(t);if(this.div){this.fixAndSetPosition();const[t,e]=this.parentDimensions;this.setDims(this.width*t,this.height*e)}this._onResized()}#Br(){const{x:t,y:e,width:i,height:s,rotation:n,parentRotation:a,parentDimensions:[r,o]}=this;switch((4*n+a)/90){case 1:return[1-e-s,t,s,i];case 2:return[1-t-i,1-e-s,i,s];case 3:return[e,1-t-i,s,i];case 4:return[t,e-i*(r/o),s*(o/r),i*(r/o)];case 5:return[1-e,t,i*(r/o),s*(o/r)];case 6:return[1-t-s*(o/r),1-e,s*(o/r),i*(r/o)];case 7:return[e-i*(r/o),1-t-s*(o/r),i*(r/o),s*(o/r)];case 8:return[t-i,e-s,i,s];case 9:return[1-e,t-i,s,i];case 10:return[1-t,1-e,i,s];case 11:return[e-s,1-t,s,i];case 12:return[t-s*(o/r),e,s*(o/r),i*(r/o)];case 13:return[1-e-i*(r/o),t-s*(o/r),i*(r/o),s*(o/r)];case 14:return[1-t,1-e-i*(r/o),s*(o/r),i*(r/o)];case 15:return[e,1-t,i*(r/o),s*(o/r)];default:return[t,e,i,s]}}rotate(){this.parent&&this.parent.drawLayer.updateProperties(this._drawId,DrawingEditor._mergeSVGProperties({bbox:this.#Br()},this.#Sr.updateRotation((this.parentRotation-this.rotation+360)%360)))}onScaleChanging(){this.parent&&this.#Or(this.#Sr.updateParentDimensions(this.parentDimensions,this.parent.scale))}static onScaleChangingWhenDrawing(){}render(){if(this.div)return this.div;let t,e;if(this._isCopy){t=this.x;e=this.y}const i=super.render();i.classList.add("draw");const s=document.createElement("div");i.append(s);s.setAttribute("aria-hidden","true");s.className="internal";const[n,a]=this.parentDimensions;this.setDims(this.width*n,this.height*a);this._uiManager.addShouldRescale(this);this.disableEditing();this._isCopy&&this._moveAfterPaste(t,e);return i}static createDrawerInstance(t,e,i,s,n){unreachable("Not implemented")}static startDrawing(t,e,i,s){const{target:n,offsetX:a,offsetY:r,pointerId:o,pointerType:l}=s;if(DrawingEditor.#kr&&DrawingEditor.#kr!==l)return;const{viewport:{rotation:h}}=t,{width:d,height:c}=n.getBoundingClientRect(),u=DrawingEditor.#Mr=new AbortController,p=t.combinedSignal(u);DrawingEditor.#Pr||=o;DrawingEditor.#kr??=l;window.addEventListener("pointerup",(t=>{DrawingEditor.#Pr===t.pointerId?this._endDraw(t):DrawingEditor.#Ir?.delete(t.pointerId)}),{signal:p});window.addEventListener("pointercancel",(t=>{DrawingEditor.#Pr===t.pointerId?this._currentParent.endDrawingSession():DrawingEditor.#Ir?.delete(t.pointerId)}),{signal:p});window.addEventListener("pointerdown",(t=>{if(DrawingEditor.#kr===t.pointerType){(DrawingEditor.#Ir||=new Set).add(t.pointerId);if(DrawingEditor.#Tr.isCancellable()){DrawingEditor.#Tr.removeLastElement();DrawingEditor.#Tr.isEmpty()?this._currentParent.endDrawingSession(!0):this._endDraw(null)}}}),{capture:!0,passive:!1,signal:p});window.addEventListener("contextmenu",noContextMenu,{signal:p});n.addEventListener("pointermove",this._drawMove.bind(this),{signal:p});n.addEventListener("touchmove",(t=>{t.timeStamp===DrawingEditor.#Rr&&stopEvent(t)}),{signal:p});t.toggleDrawing();e._editorUndoBar?.hide();if(DrawingEditor.#Tr)t.drawLayer.updateProperties(this._currentDrawId,DrawingEditor.#Tr.startNew(a,r,d,c,h));else{e.updateUIForDefaultProperties(this);DrawingEditor.#Tr=this.createDrawerInstance(a,r,d,c,h);DrawingEditor.#Dr=this.getDefaultDrawingOptions();this._currentParent=t;({id:this._currentDrawId}=t.drawLayer.draw(this._mergeSVGProperties(DrawingEditor.#Dr.toSVGProperties(),DrawingEditor.#Tr.defaultSVGProperties),!0,!1))}}static _drawMove(t){DrawingEditor.#Rr=-1;if(!DrawingEditor.#Tr)return;const{offsetX:e,offsetY:i,pointerId:s}=t;if(DrawingEditor.#Pr===s)if(DrawingEditor.#Ir?.size>=1)this._endDraw(t);else{this._currentParent.drawLayer.updateProperties(this._currentDrawId,DrawingEditor.#Tr.add(e,i));DrawingEditor.#Rr=t.timeStamp;stopEvent(t)}}static _cleanup(t){if(t){this._currentDrawId=-1;this._currentParent=null;DrawingEditor.#Tr=null;DrawingEditor.#Dr=null;DrawingEditor.#kr=null;DrawingEditor.#Rr=NaN}if(DrawingEditor.#Mr){DrawingEditor.#Mr.abort();DrawingEditor.#Mr=null;DrawingEditor.#Pr=NaN;DrawingEditor.#Ir=null}}static _endDraw(t){const e=this._currentParent;if(e){e.toggleDrawing(!0);this._cleanup(!1);t?.target===e.div&&e.drawLayer.updateProperties(this._currentDrawId,DrawingEditor.#Tr.end(t.offsetX,t.offsetY));if(this.supportMultipleDrawings){const t=DrawingEditor.#Tr,i=this._currentDrawId,s=t.getLastElement();e.addCommands({cmd:()=>{e.drawLayer.updateProperties(i,t.setLastElement(s))},undo:()=>{e.drawLayer.updateProperties(i,t.removeLastElement())},mustExec:!1,type:p.DRAW_STEP})}else this.endDrawing(!1)}}static endDrawing(t){const e=this._currentParent;if(!e)return null;e.toggleDrawing(!0);e.cleanUndoStack(p.DRAW_STEP);if(!DrawingEditor.#Tr.isEmpty()){const{pageDimensions:[i,s],scale:n}=e,a=e.createAndAddNewEditor({offsetX:0,offsetY:0},!1,{drawId:this._currentDrawId,drawOutlines:DrawingEditor.#Tr.getOutlines(i*n,s*n,n,this._INNER_MARGIN),drawingOptions:DrawingEditor.#Dr,mustBeCommitted:!t});this._cleanup(!0);return a}e.drawLayer.remove(this._currentDrawId);this._cleanup(!0);return null}createDrawingOptions(t){}static deserializeDraw(t,e,i,s,n,a){unreachable("Not implemented")}static async deserialize(t,e,i){const{rawDims:{pageWidth:s,pageHeight:n,pageX:a,pageY:r}}=e.viewport,o=this.deserializeDraw(a,r,s,n,this._INNER_MARGIN,t),l=await super.deserialize(t,e,i);l.createDrawingOptions(t);l.#Fr({drawOutlines:o});l.#cr();l.onScaleChanging();l.rotate();return l}serializeDraw(t){const[e,i]=this.pageTranslation,[s,n]=this.pageDimensions;return this.#Sr.serialize([e,i,s,n],t)}renderAnnotationElement(t){t.updateEdited({rect:this.getRect(0,0)});return null}static canCreateNewEmptyEditor(){return!1}}class InkDrawOutliner{#ra=new Float64Array(6);#Mn;#Ur;#as;#ua;#pa;#zr="";#Gr=0;#La=new InkDrawOutline;#$r;#Wr;constructor(t,e,i,s,n,a){this.#$r=i;this.#Wr=s;this.#as=n;this.#ua=a;[t,e]=this.#Vr(t,e);const r=this.#Mn=[NaN,NaN,NaN,NaN,t,e];this.#pa=[t,e];this.#Ur=[{line:r,points:this.#pa}];this.#ra.set(r,0)}updateProperty(t,e){"stroke-width"===t&&(this.#ua=e)}#Vr(t,e){return Outline._normalizePoint(t,e,this.#$r,this.#Wr,this.#as)}isEmpty(){return!this.#Ur||0===this.#Ur.length}isCancellable(){return this.#pa.length<=10}add(t,e){[t,e]=this.#Vr(t,e);const[i,s,n,a]=this.#ra.subarray(2,6),r=t-n,o=e-a;if(Math.hypot(this.#$r*r,this.#Wr*o)<=2)return null;this.#pa.push(t,e);if(isNaN(i)){this.#ra.set([n,a,t,e],2);this.#Mn.push(NaN,NaN,NaN,NaN,t,e);return{path:{d:this.toSVGPath()}}}isNaN(this.#ra[0])&&this.#Mn.splice(6,6);this.#ra.set([i,s,n,a,t,e],0);this.#Mn.push(...Outline.createBezierPoints(i,s,n,a,t,e));return{path:{d:this.toSVGPath()}}}end(t,e){const i=this.add(t,e);return i||(2===this.#pa.length?{path:{d:this.toSVGPath()}}:null)}startNew(t,e,i,s,n){this.#$r=i;this.#Wr=s;this.#as=n;[t,e]=this.#Vr(t,e);const a=this.#Mn=[NaN,NaN,NaN,NaN,t,e];this.#pa=[t,e];const r=this.#Ur.at(-1);if(r){r.line=new Float32Array(r.line);r.points=new Float32Array(r.points)}this.#Ur.push({line:a,points:this.#pa});this.#ra.set(a,0);this.#Gr=0;this.toSVGPath();return null}getLastElement(){return this.#Ur.at(-1)}setLastElement(t){if(!this.#Ur)return this.#La.setLastElement(t);this.#Ur.push(t);this.#Mn=t.line;this.#pa=t.points;this.#Gr=0;return{path:{d:this.toSVGPath()}}}removeLastElement(){if(!this.#Ur)return this.#La.removeLastElement();this.#Ur.pop();this.#zr="";for(let t=0,e=this.#Ur.length;tt??NaN)),d,c,u,p),points:g(r[t].map((t=>t??NaN)),d,c,u,p)});const f=new this.prototype.constructor;f.build(h,i,s,1,o,l,n);return f}#Yr(t=this.#ua){const e=this.#sa+t/2*this.#qr;return this.#as%180==0?[e/this.#$r,e/this.#Wr]:[e/this.#Wr,e/this.#$r]}#Kr(){const[t,e,i,s]=this.#_a,[n,a]=this.#Yr(0);return[t+n,e+a,i-2*n,s-2*a]}#Xr(){const t=this.#_a=new Float32Array([1/0,1/0,-1/0,-1/0]);for(const{line:e}of this.#Ur){if(e.length<=12){for(let i=4,s=e.length;it!==e[i]))||t.thickness!==i||t.opacity!==s||t.pageIndex!==n}renderAnnotationElement(t){const{points:e,rect:i}=this.serializeDraw(!1);t.updateEdited({rect:i,thickness:this._drawingOptions["stroke-width"],points:e});return null}}class ContourDrawOutline extends InkDrawOutline{toSVGPath(){let t=super.toSVGPath();t.endsWith("Z")||(t+="Z");return t}}class SignatureExtractor{static#Qr={maxDim:512,sigmaSFactor:.02,sigmaR:25,kernelSize:16};static#Jr(t,e,i,s){s-=e;return 0===(i-=t)?s>0?0:4:1===i?s+6:2-s}static#Zr=new Int32Array([0,1,-1,1,-1,0,-1,-1,0,-1,1,-1,1,0,1,1]);static#to(t,e,i,s,n,a,r){const o=this.#Jr(i,s,n,a);for(let n=0;n<8;n++){const a=(-n+o-r+16)%8;if(0!==t[(i+this.#Zr[2*a])*e+(s+this.#Zr[2*a+1])])return a}return-1}static#eo(t,e,i,s,n,a,r){const o=this.#Jr(i,s,n,a);for(let n=0;n<8;n++){const a=(n+o+r+16)%8;if(0!==t[(i+this.#Zr[2*a])*e+(s+this.#Zr[2*a+1])])return a}return-1}static#io(t,e,i,s){const n=t.length,a=new Int32Array(n);for(let e=0;e=1&&0===a[s+1])){1!==n&&(r=Math.abs(n));continue}o+=1;d+=1;n>1&&(r=n)}const c=[i,t],u=d===i+1,p={isHole:u,points:c,id:o,parent:0};l.push(p);let g;for(const t of l)if(t.id===r){g=t;break}g?g.isHole?p.parent=u?g.parent:r:p.parent=u?r:g.parent:p.parent=u?r:0;const f=this.#to(a,e,t,i,h,d,0);if(-1===f){a[s]=-o;1!==a[s]&&(r=Math.abs(a[s]));continue}let m=this.#Zr[2*f],b=this.#Zr[2*f+1];const A=t+m,w=i+b;h=A;d=w;let y=t,v=i;for(;;){const n=this.#eo(a,e,y,v,h,d,1);m=this.#Zr[2*n];b=this.#Zr[2*n+1];const l=y+m,u=v+b;c.push(u,l);const p=y*e+v;0===a[p+1]?a[p]=-o:1===a[p]&&(a[p]=o);if(l===t&&u===i&&y===A&&v===w){1!==a[s]&&(r=Math.abs(a[s]));break}h=y;d=v;y=l;v=u}}}return l}static#so(t,e,i,s){if(i-e<=4){for(let n=e;ny){v=s;y=e}}if(y>(l*w)**2){this.#so(t,e,v+2,s);this.#so(t,v,i,s)}else s.push(n,a)}static#no(t){const e=[],i=t.length;this.#so(t,0,i,e);e.push(t[i-2],t[i-1]);return e.length<=4?null:e}static#ao(t,e,i,s,n,a){const r=new Float32Array(a**2),o=-2*s**2,l=a>>1;for(let t=0;t=i))for(let i=0;i=e)continue;const p=t[u*e+s],f=r[o*a+i]*h[Math.abs(p-d)];c+=p*f;g+=f}}p[u[o]=Math.round(c/g)]++}return[u,p]}static#ro(t){const e=new Uint32Array(256);for(const i of t)e[i]++;return e}static#oo(t){const e=t.length,i=new Uint8ClampedArray(e>>2);let s=-1/0,n=1/0;for(let e=0,a=i.length;es&&(s=a);a0!==t));let a=n,r=n;for(e=n;e<256;e++){const n=t[e];if(n>i){if(e-a>s){s=e-a;r=e-1}i=n;a=e}}for(e=r-1;e>=0&&!(t[e]>t[e+1]);e--);return e}static#ho(t){const e=t,{width:i,height:s}=t,{maxDim:n}=this.#Qr;let a=i,r=s;if(i>n||s>n){let o=i,l=s,h=Math.log2(Math.max(i,s)/n);const d=Math.floor(h);h=h===d?d-1:d;for(let i=0;i=-128&&o<=127?Int8Array:r>=-32768&&o<=32767?Int16Array:Int32Array;const h=t.length,d=8+3*h,c=new Uint32Array(d);let u=0;c[u++]=d*Uint32Array.BYTES_PER_ELEMENT+(l-2*h)*a.BYTES_PER_ELEMENT;c[u++]=0;c[u++]=s;c[u++]=n;c[u++]=e?0:1;c[u++]=Math.max(0,Math.floor(i??0));c[u++]=h;c[u++]=a.BYTES_PER_ELEMENT;for(const e of t){c[u++]=e.length-2;c[u++]=e[0];c[u++]=e[1]}const p=new CompressionStream("deflate-raw"),g=p.writable.getWriter();await g.ready;g.write(c);const f=a.prototype.constructor;for(const e of t){const t=new f(e.length-2);for(let i=2,s=e.length;i{await n.ready;await n.close()})).catch((()=>{}));let a=null,r=0;for await(const t of i){a||=new Uint8Array(new Uint32Array(t.buffer,0,4)[0]);a.set(t,r);r+=t.length}const o=new Uint32Array(a.buffer,0,a.length>>2),l=o[1];if(0!==l)throw new Error(`Invalid version: ${l}`);const h=o[2],d=o[3],c=0===o[4],u=o[5],p=o[6],g=o[7],f=[],m=(8+3*p)*Uint32Array.BYTES_PER_ELEMENT;let b;switch(g){case Int8Array.BYTES_PER_ELEMENT:b=new Int8Array(a.buffer,m);break;case Int16Array.BYTES_PER_ELEMENT:b=new Int16Array(a.buffer,m);break;case Int32Array.BYTES_PER_ELEMENT:b=new Int32Array(a.buffer,m)}r=0;for(let t=0;t{e?.updateEditSignatureButton(t)}))}getSignaturePreview(){const{newCurves:t,areContours:e,thickness:i,width:s,height:n}=this.#uo,a=Math.max(s,n);return{areContours:e,outline:SignatureExtractor.processDrawnLines({lines:{curves:t.map((t=>({points:t}))),thickness:i,width:s,height:n},pageWidth:a,pageHeight:a,rotation:0,innerMargin:0,mustSmooth:!1,areContours:e}).outline}}async addEditToolbar(){const t=await super.addEditToolbar();if(!t)return null;if(this._uiManager.signatureManager&&null!==this.#co){await t.addEditSignatureButton(this._uiManager.signatureManager,this.#po,this.#co);t.show()}return t}addSignature(t,e,i,s){const{x:n,y:a}=this,{outline:r}=this.#uo=t;this.#do=r instanceof ContourDrawOutline;this.#co=i;this.div.setAttribute("data-l10n-args",JSON.stringify({description:i}));let o;if(this.#do)o=SignatureEditor.getDefaultDrawingOptions();else{o=SignatureEditor._defaultDrawnSignatureOptions.clone();o.updateProperties({"stroke-width":r.thickness})}this._addOutlines({drawOutlines:r,drawingOptions:o});const[l,h]=this.parentDimensions,[,d]=this.pageDimensions;let c=e/d;c=c>=1?.5:c;this.width*=c/this.height;if(this.width>=1){c*=.9/this.width;this.width=.9}this.height=c;this.setDims(l*this.width,h*this.height);this.x=n;this.y=a;this.center();this._onResized();this.onScaleChanging();this.rotate();this._uiManager.addToAnnotationStorage(this);this.setUuid(s);this._reportTelemetry({action:"pdfjs.signature.inserted",data:{hasBeenSaved:!!s,hasDescription:!!i}});this.div.hidden=!1}getFromImage(t){const{rawDims:{pageWidth:e,pageHeight:i},rotation:s}=this.parent.viewport;return SignatureExtractor.process(t,e,i,s,SignatureEditor._INNER_MARGIN)}getFromText(t,e){const{rawDims:{pageWidth:i,pageHeight:s},rotation:n}=this.parent.viewport;return SignatureExtractor.extractContoursFromText(t,e,i,s,n,SignatureEditor._INNER_MARGIN)}getDrawnSignature(t){const{rawDims:{pageWidth:e,pageHeight:i},rotation:s}=this.parent.viewport;return SignatureExtractor.processDrawnLines({lines:t,pageWidth:e,pageHeight:i,rotation:s,innerMargin:SignatureEditor._INNER_MARGIN,mustSmooth:!1,areContours:!1})}createDrawingOptions({areContours:t,thickness:e}){if(t)this._drawingOptions=SignatureEditor.getDefaultDrawingOptions();else{this._drawingOptions=SignatureEditor._defaultDrawnSignatureOptions.clone();this._drawingOptions.updateProperties({"stroke-width":e})}}serialize(t=!1){if(this.isEmpty())return null;const{lines:e,points:i,rect:s}=this.serializeDraw(t),{_drawingOptions:{"stroke-width":n}}=this,a={annotationType:u.SIGNATURE,isSignature:!0,areContours:this.#do,color:[0,0,0],thickness:this.#do?0:n,pageIndex:this.pageIndex,rect:s,rotation:this.rotation,structTreeParentId:this._structTreeParentId};if(t){a.paths={lines:e,points:i};a.uuid=this.#po;a.isCopy=!0}else a.lines=e;this.#co&&(a.accessibilityData={type:"Figure",alt:this.#co});return a}static deserializeDraw(t,e,i,s,n,a){return a.areContours?ContourDrawOutline.deserialize(t,e,i,s,n,a):InkDrawOutline.deserialize(t,e,i,s,n,a)}static async deserialize(t,e,i){const s=await super.deserialize(t,e,i);s.#do=t.areContours;s.#co=t.accessibilityData?.alt||"";s.#po=t.uuid;return s}}class StampEditor extends AnnotationEditor{#go=null;#fo=null;#mo=null;#bo=null;#Ao=null;#wo="";#yo=null;#vo=!1;#xo=null;#Eo=!1;#_o=!1;static _type="stamp";static _editorType=u.STAMP;constructor(t){super({...t,name:"stampEditor"});this.#bo=t.bitmapUrl;this.#Ao=t.bitmapFile;this.defaultL10nId="pdfjs-editor-stamp-editor"}static initialize(t,e){AnnotationEditor.initialize(t,e)}static isHandlingMimeForPasting(t){return G.includes(t)}static paste(t,e){e.pasteEditor({mode:u.STAMP},{bitmapFile:t.getAsFile()})}altTextFinish(){this._uiManager.useNewAltTextFlow&&(this.div.hidden=!1);super.altTextFinish()}get telemetryFinalData(){return{type:"stamp",hasAltText:!!this.altTextData?.altText}}static computeTelemetryFinalData(t){const e=t.get("hasAltText");return{hasAltText:e.get(!0)??0,hasNoAltText:e.get(!1)??0}}#So(t,e=!1){if(t){this.#go=t.bitmap;if(!e){this.#fo=t.id;this.#Eo=t.isSvg}t.file&&(this.#wo=t.file.name);this.#Co()}else this.remove()}#To(){this.#mo=null;this._uiManager.enableWaiting(!1);if(this.#yo)if(this._uiManager.useNewAltTextWhenAddingImage&&this._uiManager.useNewAltTextFlow&&this.#go){this._editToolbar.hide();this._uiManager.editAltText(this,!0)}else{if(!this._uiManager.useNewAltTextWhenAddingImage&&this._uiManager.useNewAltTextFlow&&this.#go){this._reportTelemetry({action:"pdfjs.image.image_added",data:{alt_text_modal:!1,alt_text_type:"empty"}});try{this.mlGuessAltText()}catch{}}this.div.focus()}}async mlGuessAltText(t=null,e=!0){if(this.hasAltTextData())return null;const{mlManager:i}=this._uiManager;if(!i)throw new Error("No ML.");if(!await i.isEnabledFor("altText"))throw new Error("ML isn't enabled for alt text.");const{data:s,width:n,height:a}=t||this.copyCanvas(null,null,!0).imageData,r=await i.guess({name:"altText",request:{data:s,width:n,height:a,channels:s.length/(n*a)}});if(!r)throw new Error("No response from the AI service.");if(r.error)throw new Error("Error from the AI service.");if(r.cancel)return null;if(!r.output)throw new Error("No valid response from the AI service.");const o=r.output;await this.setGuessedAltText(o);e&&!this.hasAltTextData()&&(this.altTextData={alt:o,decorative:!1});return o}#Mo(){if(this.#fo){this._uiManager.enableWaiting(!0);this._uiManager.imageManager.getFromId(this.#fo).then((t=>this.#So(t,!0))).finally((()=>this.#To()));return}if(this.#bo){const t=this.#bo;this.#bo=null;this._uiManager.enableWaiting(!0);this.#mo=this._uiManager.imageManager.getFromUrl(t).then((t=>this.#So(t))).finally((()=>this.#To()));return}if(this.#Ao){const t=this.#Ao;this.#Ao=null;this._uiManager.enableWaiting(!0);this.#mo=this._uiManager.imageManager.getFromFile(t).then((t=>this.#So(t))).finally((()=>this.#To()));return}const t=document.createElement("input");t.type="file";t.accept=G.join(",");const e=this._uiManager._signal;this.#mo=new Promise((i=>{t.addEventListener("change",(async()=>{if(t.files&&0!==t.files.length){this._uiManager.enableWaiting(!0);const e=await this._uiManager.imageManager.getFromFile(t.files[0]);this._reportTelemetry({action:"pdfjs.image.image_selected",data:{alt_text_modal:this._uiManager.useNewAltTextFlow}});this.#So(e)}else this.remove();i()}),{signal:e});t.addEventListener("cancel",(()=>{this.remove();i()}),{signal:e})})).finally((()=>this.#To()));t.click()}remove(){if(this.#fo){this.#go=null;this._uiManager.imageManager.deleteId(this.#fo);this.#yo?.remove();this.#yo=null;if(this.#xo){clearTimeout(this.#xo);this.#xo=null}}super.remove()}rebuild(){if(this.parent){super.rebuild();if(null!==this.div){this.#fo&&null===this.#yo&&this.#Mo();this.isAttachedToDOM||this.parent.add(this)}}else this.#fo&&this.#Mo()}onceAdded(t){this._isDraggable=!0;t&&this.div.focus()}isEmpty(){return!(this.#mo||this.#go||this.#bo||this.#Ao||this.#fo||this.#vo)}get isResizable(){return!0}render(){if(this.div)return this.div;let t,e;if(this._isCopy){t=this.x;e=this.y}super.render();this.div.hidden=!0;this.addAltTextButton();this.#vo||(this.#go?this.#Co():this.#Mo());this._isCopy&&this._moveAfterPaste(t,e);this._uiManager.addShouldRescale(this);return this.div}setCanvas(t,e){const{id:i,bitmap:s}=this._uiManager.imageManager.getFromCanvas(t,e);e.remove();if(i&&this._uiManager.imageManager.isValidId(i)){this.#fo=i;s&&(this.#go=s);this.#vo=!1;this.#Co()}}_onResized(){this.onScaleChanging()}onScaleChanging(){if(!this.parent)return;null!==this.#xo&&clearTimeout(this.#xo);this.#xo=setTimeout((()=>{this.#xo=null;this.#Do()}),200)}#Co(){const{div:t}=this;let{width:e,height:i}=this.#go;const[s,n]=this.pageDimensions,a=.75;if(this.width){e=this.width*s;i=this.height*n}else if(e>a*s||i>a*n){const t=Math.min(a*s/e,a*n/i);e*=t;i*=t}const[r,o]=this.parentDimensions;this.setDims(e*r/s,i*o/n);this._uiManager.enableWaiting(!1);const l=this.#yo=document.createElement("canvas");l.setAttribute("role","img");this.addContainer(l);this.width=e/s;this.height=i/n;this._initialOptions?.isCentered?this.center():this.fixAndSetPosition();this._initialOptions=null;this._uiManager.useNewAltTextWhenAddingImage&&this._uiManager.useNewAltTextFlow&&!this.annotationElementId||(t.hidden=!1);this.#Do();if(!this.#_o){this.parent.addUndoableEditor(this);this.#_o=!0}this._reportTelemetry({action:"inserted_image"});this.#wo&&this.div.setAttribute("aria-description",this.#wo)}copyCanvas(t,e,i=!1){t||(t=224);const{width:s,height:n}=this.#go,a=new OutputScale;let r=this.#go,o=s,l=n,h=null;if(e){if(s>e||n>e){const t=Math.min(e/s,e/n);o=Math.floor(s*t);l=Math.floor(n*t)}h=document.createElement("canvas");const t=h.width=Math.ceil(o*a.sx),i=h.height=Math.ceil(l*a.sy);this.#Eo||(r=this.#Po(t,i));const d=h.getContext("2d");d.filter=this._uiManager.hcmFilter;let c="white",u="#cfcfd8";if("none"!==this._uiManager.hcmFilter)u="black";else if(window.matchMedia?.("(prefers-color-scheme: dark)").matches){c="#8f8f9d";u="#42414d"}const p=15,g=p*a.sx,f=p*a.sy,m=new OffscreenCanvas(2*g,2*f),b=m.getContext("2d");b.fillStyle=c;b.fillRect(0,0,2*g,2*f);b.fillStyle=u;b.fillRect(0,0,g,f);b.fillRect(g,f,g,f);d.fillStyle=d.createPattern(m,"repeat");d.fillRect(0,0,t,i);d.drawImage(r,0,0,r.width,r.height,0,0,t,i)}let d=null;if(i){let e,i;if(a.symmetric&&r.widtht||n>t){const a=Math.min(t/s,t/n);e=Math.floor(s*a);i=Math.floor(n*a);this.#Eo||(r=this.#Po(e,i))}}const o=new OffscreenCanvas(e,i).getContext("2d",{willReadFrequently:!0});o.drawImage(r,0,0,r.width,r.height,0,0,e,i);d={width:e,height:i,data:o.getImageData(0,0,e,i).data}}return{canvas:h,width:o,height:l,imageData:d}}#Po(t,e){const{width:i,height:s}=this.#go;let n=i,a=s,r=this.#go;for(;n>2*t||a>2*e;){const i=n,s=a;n>2*t&&(n=n>=16384?Math.floor(n/2)-1:Math.ceil(n/2));a>2*e&&(a=a>=16384?Math.floor(a/2)-1:Math.ceil(a/2));const o=new OffscreenCanvas(n,a);o.getContext("2d").drawImage(r,0,0,i,s,0,0,n,a);r=o.transferToImageBitmap()}return r}#Do(){const[t,e]=this.parentDimensions,{width:i,height:s}=this,n=new OutputScale,a=Math.ceil(i*t*n.sx),r=Math.ceil(s*e*n.sy),o=this.#yo;if(!o||o.width===a&&o.height===r)return;o.width=a;o.height=r;const l=this.#Eo?this.#go:this.#Po(a,r),h=o.getContext("2d");h.filter=this._uiManager.hcmFilter;h.drawImage(l,0,0,l.width,l.height,0,0,a,r)}#ko(t){if(t){if(this.#Eo){const t=this._uiManager.imageManager.getSvgUrl(this.#fo);if(t)return t}const t=document.createElement("canvas");({width:t.width,height:t.height}=this.#go);t.getContext("2d").drawImage(this.#go,0,0);return t.toDataURL()}if(this.#Eo){const[t,e]=this.pageDimensions,i=Math.round(this.width*t*PixelsPerInch.PDF_TO_CSS_UNITS),s=Math.round(this.height*e*PixelsPerInch.PDF_TO_CSS_UNITS),n=new OffscreenCanvas(i,s);n.getContext("2d").drawImage(this.#go,0,0,this.#go.width,this.#go.height,0,0,i,s);return n.transferToImageBitmap()}return structuredClone(this.#go)}static async deserialize(t,e,i){let s=null,n=!1;if(t instanceof StampAnnotationElement){const{data:{rect:a,rotation:r,id:o,structParent:l,popupRef:h},container:d,parent:{page:{pageNumber:c}},canvas:p}=t;let g,f;if(p){delete t.canvas;({id:g,bitmap:f}=i.imageManager.getFromCanvas(d.id,p));p.remove()}else{n=!0;t._hasNoCanvas=!0}const m=(await e._structTree.getAriaAttributes(`${U}${o}`))?.get("aria-label")||"";s=t={annotationType:u.STAMP,bitmapId:g,bitmap:f,pageIndex:c-1,rect:a.slice(0),rotation:r,id:o,deleted:!1,accessibilityData:{decorative:!1,altText:m},isSvg:!1,structParent:l,popupRef:h}}const a=await super.deserialize(t,e,i),{rect:r,bitmap:o,bitmapUrl:l,bitmapId:h,isSvg:d,accessibilityData:c}=t;if(n){i.addMissingCanvas(t.id,a);a.#vo=!0}else if(h&&i.imageManager.isValidId(h)){a.#fo=h;o&&(a.#go=o)}else a.#bo=l;a.#Eo=d;const[p,g]=a.pageDimensions;a.width=(r[2]-r[0])/p;a.height=(r[3]-r[1])/g;a.annotationElementId=t.id||null;c&&(a.altTextData=c);a._initialData=s;a.#_o=!!s;return a}serialize(t=!1,e=null){if(this.isEmpty())return null;if(this.deleted)return this.serializeDeleted();const i={annotationType:u.STAMP,bitmapId:this.#fo,pageIndex:this.pageIndex,rect:this.getRect(0,0),rotation:this.rotation,isSvg:this.#Eo,structTreeParentId:this._structTreeParentId};if(t){i.bitmapUrl=this.#ko(!0);i.accessibilityData=this.serializeAltText(!0);i.isCopy=!0;return i}const{decorative:s,altText:n}=this.serializeAltText(!1);!s&&n&&(i.accessibilityData={type:"Figure",alt:n});if(this.annotationElementId){const t=this.#ta(i);if(t.isSame)return null;t.isSameAltText?delete i.accessibilityData:i.accessibilityData.structParent=this._initialData.structParent??-1}i.id=this.annotationElementId;if(null===e)return i;e.stamps||=new Map;const a=this.#Eo?(i.rect[2]-i.rect[0])*(i.rect[3]-i.rect[1]):null;if(e.stamps.has(this.#fo)){if(this.#Eo){const t=e.stamps.get(this.#fo);if(a>t.area){t.area=a;t.serialized.bitmap.close();t.serialized.bitmap=this.#ko(!1)}}}else{e.stamps.set(this.#fo,{area:a,serialized:i});i.bitmap=this.#ko(!1)}return i}#ta(t){const{pageIndex:e,accessibilityData:{altText:i}}=this._initialData,s=t.pageIndex===e,n=(t.accessibilityData?.alt||"")===i;return{isSame:!this._hasBeenMoved&&!this._hasBeenResized&&s&&n,isSameAltText:n}}renderAnnotationElement(t){t.updateEdited({rect:this.getRect(0,0)});return null}}class AnnotationEditorLayer{#Nn;#Io=!1;#Ro=null;#Fo=null;#Lo=null;#Oo=new Map;#No=!1;#Bo=!1;#Ho=!1;#Uo=null;#zo=null;#Go=null;#$o=null;#m;static _initialized=!1;static#G=new Map([FreeTextEditor,InkEditor,StampEditor,HighlightEditor,SignatureEditor].map((t=>[t._editorType,t])));constructor({uiManager:t,pageIndex:e,div:i,structTreeLayer:s,accessibilityManager:n,annotationLayer:a,drawLayer:r,textLayer:o,viewport:l,l10n:h}){const d=[...AnnotationEditorLayer.#G.values()];if(!AnnotationEditorLayer._initialized){AnnotationEditorLayer._initialized=!0;for(const e of d)e.initialize(h,t)}t.registerEditorTypes(d);this.#m=t;this.pageIndex=e;this.div=i;this.#Nn=n;this.#Ro=a;this.viewport=l;this.#Go=o;this.drawLayer=r;this._structTree=s;this.#m.addLayer(this)}get isEmpty(){return 0===this.#Oo.size}get isInvisible(){return this.isEmpty&&this.#m.getMode()===u.NONE}updateToolbar(t){this.#m.updateToolbar(t)}updateMode(t=this.#m.getMode()){this.#Wo();switch(t){case u.NONE:this.disableTextSelection();this.togglePointerEvents(!1);this.toggleAnnotationLayerPointerEvents(!0);this.disableClick();return;case u.INK:this.disableTextSelection();this.togglePointerEvents(!0);this.enableClick();break;case u.HIGHLIGHT:this.enableTextSelection();this.togglePointerEvents(!1);this.disableClick();break;default:this.disableTextSelection();this.togglePointerEvents(!0);this.enableClick()}this.toggleAnnotationLayerPointerEvents(!1);const{classList:e}=this.div;for(const i of AnnotationEditorLayer.#G.values())e.toggle(`${i._type}Editing`,t===i._editorType);this.div.hidden=!1}hasTextLayer(t){return t===this.#Go?.div}setEditingState(t){this.#m.setEditingState(t)}addCommands(t){this.#m.addCommands(t)}cleanUndoStack(t){this.#m.cleanUndoStack(t)}toggleDrawing(t=!1){this.div.classList.toggle("drawing",!t)}togglePointerEvents(t=!1){this.div.classList.toggle("disabled",!t)}toggleAnnotationLayerPointerEvents(t=!1){this.#Ro?.div.classList.toggle("disabled",!t)}async enable(){this.#Ho=!0;this.div.tabIndex=0;this.togglePointerEvents(!0);const t=new Set;for(const e of this.#Oo.values()){e.enableEditing();e.show(!0);if(e.annotationElementId){this.#m.removeChangedExistingAnnotation(e);t.add(e.annotationElementId)}}if(!this.#Ro){this.#Ho=!1;return}const e=this.#Ro.getEditableAnnotations();for(const i of e){i.hide();if(this.#m.isDeletedAnnotationElement(i.data.id))continue;if(t.has(i.data.id))continue;const e=await this.deserialize(i);if(e){this.addOrRebuild(e);e.enableEditing()}}this.#Ho=!1}disable(){this.#Bo=!0;this.div.tabIndex=-1;this.togglePointerEvents(!1);const t=new Map,e=new Map;for(const i of this.#Oo.values()){i.disableEditing();if(i.annotationElementId)if(null===i.serialize()){e.set(i.annotationElementId,i);this.getEditableAnnotation(i.annotationElementId)?.show();i.remove()}else t.set(i.annotationElementId,i)}if(this.#Ro){const i=this.#Ro.getEditableAnnotations();for(const s of i){const{id:i}=s.data;if(this.#m.isDeletedAnnotationElement(i))continue;let n=e.get(i);if(n){n.resetAnnotationElement(s);n.show(!1);s.show()}else{n=t.get(i);if(n){this.#m.addChangedExistingAnnotation(n);n.renderAnnotationElement(s)&&n.show(!1)}s.show()}}}this.#Wo();this.isEmpty&&(this.div.hidden=!0);const{classList:i}=this.div;for(const t of AnnotationEditorLayer.#G.values())i.remove(`${t._type}Editing`);this.disableTextSelection();this.toggleAnnotationLayerPointerEvents(!0);this.#Bo=!1}getEditableAnnotation(t){return this.#Ro?.getEditableAnnotation(t)||null}setActiveEditor(t){this.#m.getActive()!==t&&this.#m.setActiveEditor(t)}enableTextSelection(){this.div.tabIndex=-1;if(this.#Go?.div&&!this.#$o){this.#$o=new AbortController;const t=this.#m.combinedSignal(this.#$o);this.#Go.div.addEventListener("pointerdown",this.#Vo.bind(this),{signal:t});this.#Go.div.classList.add("highlighting")}}disableTextSelection(){this.div.tabIndex=0;if(this.#Go?.div&&this.#$o){this.#$o.abort();this.#$o=null;this.#Go.div.classList.remove("highlighting")}}#Vo(t){this.#m.unselectAll();const{target:e}=t;if(e===this.#Go.div||("img"===e.getAttribute("role")||e.classList.contains("endOfContent"))&&this.#Go.div.contains(e)){const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)return;this.#m.showAllEditors("highlight",!0,!0);this.#Go.div.classList.add("free");this.toggleDrawing();HighlightEditor.startHighlighting(this,"ltr"===this.#m.direction,{target:this.#Go.div,x:t.x,y:t.y});this.#Go.div.addEventListener("pointerup",(()=>{this.#Go.div.classList.remove("free");this.toggleDrawing(!0)}),{once:!0,signal:this.#m._signal});t.preventDefault()}}enableClick(){if(this.#Fo)return;this.#Fo=new AbortController;const t=this.#m.combinedSignal(this.#Fo);this.div.addEventListener("pointerdown",this.pointerdown.bind(this),{signal:t});const e=this.pointerup.bind(this);this.div.addEventListener("pointerup",e,{signal:t});this.div.addEventListener("pointercancel",e,{signal:t})}disableClick(){this.#Fo?.abort();this.#Fo=null}attach(t){this.#Oo.set(t.id,t);const{annotationElementId:e}=t;e&&this.#m.isDeletedAnnotationElement(e)&&this.#m.removeDeletedAnnotationElement(t)}detach(t){this.#Oo.delete(t.id);this.#Nn?.removePointerInTextLayer(t.contentDiv);!this.#Bo&&t.annotationElementId&&this.#m.addDeletedAnnotationElement(t)}remove(t){this.detach(t);this.#m.removeEditor(t);t.div.remove();t.isAttachedToDOM=!1}changeParent(t){if(t.parent!==this){if(t.parent&&t.annotationElementId){this.#m.addDeletedAnnotationElement(t.annotationElementId);AnnotationEditor.deleteAnnotationElement(t);t.annotationElementId=null}this.attach(t);t.parent?.detach(t);t.setParent(this);if(t.div&&t.isAttachedToDOM){t.div.remove();this.div.append(t.div)}}}add(t){if(t.parent!==this||!t.isAttachedToDOM){this.changeParent(t);this.#m.addEditor(t);this.attach(t);if(!t.isAttachedToDOM){const e=t.render();this.div.append(e);t.isAttachedToDOM=!0}t.fixAndSetPosition();t.onceAdded(!this.#Ho);this.#m.addToAnnotationStorage(t);t._reportTelemetry(t.telemetryInitialData)}}moveEditorInDOM(t){if(!t.isAttachedToDOM)return;const{activeElement:e}=document;if(t.div.contains(e)&&!this.#Lo){t._focusEventsAllowed=!1;this.#Lo=setTimeout((()=>{this.#Lo=null;if(t.div.contains(document.activeElement))t._focusEventsAllowed=!0;else{t.div.addEventListener("focusin",(()=>{t._focusEventsAllowed=!0}),{once:!0,signal:this.#m._signal});e.focus()}}),0)}t._structTreeParentId=this.#Nn?.moveElementInDOM(this.div,t.div,t.contentDiv,!0)}addOrRebuild(t){if(t.needsToBeRebuilt()){t.parent||=this;t.rebuild();t.show()}else this.add(t)}addUndoableEditor(t){this.addCommands({cmd:()=>t._uiManager.rebuild(t),undo:()=>{t.remove()},mustExec:!1})}getNextId(){return this.#m.getId()}get#jo(){return AnnotationEditorLayer.#G.get(this.#m.getMode())}combinedSignal(t){return this.#m.combinedSignal(t)}#qo(t){const e=this.#jo;return e?new e.prototype.constructor(t):null}canCreateNewEmptyEditor(){return this.#jo?.canCreateNewEmptyEditor()}async pasteEditor(t,e){this.updateToolbar(t);await this.#m.updateMode(t.mode);const{offsetX:i,offsetY:s}=this.#Xo(),n=this.getNextId(),a=this.#qo({parent:this,id:n,x:i,y:s,uiManager:this.#m,isCentered:!0,...e});a&&this.add(a)}async deserialize(t){return await(AnnotationEditorLayer.#G.get(t.annotationType??t.annotationEditorType)?.deserialize(t,this,this.#m))||null}createAndAddNewEditor(t,e,i={}){const s=this.getNextId(),n=this.#qo({parent:this,id:s,x:t.offsetX,y:t.offsetY,uiManager:this.#m,isCentered:e,...i});n&&this.add(n);return n}#Xo(){const{x:t,y:e,width:i,height:s}=this.div.getBoundingClientRect(),n=Math.max(0,t),a=Math.max(0,e),r=(n+Math.min(window.innerWidth,t+i))/2-t,o=(a+Math.min(window.innerHeight,e+s))/2-e,[l,h]=this.viewport.rotation%180==0?[r,o]:[o,r];return{offsetX:l,offsetY:h}}addNewEditor(t={}){this.createAndAddNewEditor(this.#Xo(),!0,t)}setSelected(t){this.#m.setSelected(t)}toggleSelected(t){this.#m.toggleSelected(t)}unselect(t){this.#m.unselect(t)}pointerup(t){const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)return;if(t.target!==this.div)return;if(!this.#No)return;this.#No=!1;if(this.#jo?.isDrawer&&this.#jo.supportMultipleDrawings)return;if(!this.#Io){this.#Io=!0;return}const i=this.#m.getMode();i!==u.STAMP&&i!==u.SIGNATURE?this.createAndAddNewEditor(t,!1):this.#m.unselectAll()}pointerdown(t){this.#m.getMode()===u.HIGHLIGHT&&this.enableTextSelection();if(this.#No){this.#No=!1;return}const{isMac:e}=util_FeatureTest.platform;if(0!==t.button||t.ctrlKey&&e)return;if(t.target!==this.div)return;this.#No=!0;if(this.#jo?.isDrawer){this.startDrawingSession(t);return}const i=this.#m.getActive();this.#Io=!i||i.isEmpty()}startDrawingSession(t){this.div.focus({preventScroll:!0});if(this.#Uo){this.#jo.startDrawing(this,this.#m,!1,t);return}this.#m.setCurrentDrawingSession(this);this.#Uo=new AbortController;const e=this.#m.combinedSignal(this.#Uo);this.div.addEventListener("blur",(({relatedTarget:t})=>{if(t&&!this.div.contains(t)){this.#zo=null;this.commitOrRemove()}}),{signal:e});this.#jo.startDrawing(this,this.#m,!1,t)}pause(t){if(t){const{activeElement:t}=document;this.div.contains(t)&&(this.#zo=t)}else this.#zo&&setTimeout((()=>{this.#zo?.focus();this.#zo=null}),0)}endDrawingSession(t=!1){if(!this.#Uo)return null;this.#m.setCurrentDrawingSession(null);this.#Uo.abort();this.#Uo=null;this.#zo=null;return this.#jo.endDrawing(t)}findNewParent(t,e,i){const s=this.#m.findParent(e,i);if(null===s||s===this)return!1;s.changeParent(t);return!0}commitOrRemove(){if(this.#Uo){this.endDrawingSession();return!0}return!1}onScaleChanging(){this.#Uo&&this.#jo.onScaleChangingWhenDrawing(this)}destroy(){this.commitOrRemove();if(this.#m.getActive()?.parent===this){this.#m.commitOrRemove();this.#m.setActiveEditor(null)}if(this.#Lo){clearTimeout(this.#Lo);this.#Lo=null}for(const t of this.#Oo.values()){this.#Nn?.removePointerInTextLayer(t.contentDiv);t.setParent(null);t.isAttachedToDOM=!1;t.div.remove()}this.div=null;this.#Oo.clear();this.#m.removeLayer(this)}#Wo(){for(const t of this.#Oo.values())t.isEmpty()&&t.remove()}render({viewport:t}){this.viewport=t;setLayerDimensions(this.div,t);for(const t of this.#m.getEditors(this.pageIndex)){this.add(t);t.rebuild()}this.updateMode()}update({viewport:t}){this.#m.commitOrRemove();this.#Wo();const e=this.viewport.rotation,i=t.rotation;this.viewport=t;setLayerDimensions(this.div,{rotation:i});if(e!==i)for(const t of this.#Oo.values())t.rotate(i)}get pageDimensions(){const{pageWidth:t,pageHeight:e}=this.viewport.rawDims;return[t,e]}get scale(){return this.#m.viewParameters.realScale}}class DrawLayer{#fn=null;#Ko=new Map;#Yo=new Map;static#y=0;constructor({pageIndex:t}){this.pageIndex=t}setParent(t){if(this.#fn){if(this.#fn!==t){if(this.#Ko.size>0)for(const e of this.#Ko.values()){e.remove();t.append(e)}this.#fn=t}}else this.#fn=t}static get _svgFactory(){return shadow(this,"_svgFactory",new DOMSVGFactory)}static#Qo(t,[e,i,s,n]){const{style:a}=t;a.top=100*i+"%";a.left=100*e+"%";a.width=100*s+"%";a.height=100*n+"%"}#Jo(){const t=DrawLayer._svgFactory.create(1,1,!0);this.#fn.append(t);t.setAttribute("aria-hidden",!0);return t}#Zo(t,e){const i=DrawLayer._svgFactory.createElement("clipPath");t.append(i);const s=`clip_${e}`;i.setAttribute("id",s);i.setAttribute("clipPathUnits","objectBoundingBox");const n=DrawLayer._svgFactory.createElement("use");i.append(n);n.setAttribute("href",`#${e}`);n.classList.add("clip");return s}#tl(t,e){for(const[i,s]of Object.entries(e))null===s?t.removeAttribute(i):t.setAttribute(i,s)}draw(t,e=!1,i=!1){const s=DrawLayer.#y++,n=this.#Jo(),a=DrawLayer._svgFactory.createElement("defs");n.append(a);const r=DrawLayer._svgFactory.createElement("path");a.append(r);const o=`path_p${this.pageIndex}_${s}`;r.setAttribute("id",o);r.setAttribute("vector-effect","non-scaling-stroke");e&&this.#Yo.set(s,r);const l=i?this.#Zo(a,o):null,h=DrawLayer._svgFactory.createElement("use");n.append(h);h.setAttribute("href",`#${o}`);this.updateProperties(n,t);this.#Ko.set(s,n);return{id:s,clipPathId:`url(#${l})`}}drawOutline(t,e){const i=DrawLayer.#y++,s=this.#Jo(),n=DrawLayer._svgFactory.createElement("defs");s.append(n);const a=DrawLayer._svgFactory.createElement("path");n.append(a);const r=`path_p${this.pageIndex}_${i}`;a.setAttribute("id",r);a.setAttribute("vector-effect","non-scaling-stroke");let o;if(e){const t=DrawLayer._svgFactory.createElement("mask");n.append(t);o=`mask_p${this.pageIndex}_${i}`;t.setAttribute("id",o);t.setAttribute("maskUnits","objectBoundingBox");const e=DrawLayer._svgFactory.createElement("rect");t.append(e);e.setAttribute("width","1");e.setAttribute("height","1");e.setAttribute("fill","white");const s=DrawLayer._svgFactory.createElement("use");t.append(s);s.setAttribute("href",`#${r}`);s.setAttribute("stroke","none");s.setAttribute("fill","black");s.setAttribute("fill-rule","nonzero");s.classList.add("mask")}const l=DrawLayer._svgFactory.createElement("use");s.append(l);l.setAttribute("href",`#${r}`);o&&l.setAttribute("mask",`url(#${o})`);const h=l.cloneNode();s.append(h);l.classList.add("mainOutline");h.classList.add("secondaryOutline");this.updateProperties(s,t);this.#Ko.set(i,s);return i}finalizeDraw(t,e){this.#Yo.delete(t);this.updateProperties(t,e)}updateProperties(t,e){if(!e)return;const{root:i,bbox:s,rootClass:n,path:a}=e,r="number"==typeof t?this.#Ko.get(t):t;if(r){i&&this.#tl(r,i);s&&DrawLayer.#Qo(r,s);if(n){const{classList:t}=r;for(const[e,i]of Object.entries(n))t.toggle(e,i)}if(a){const t=r.firstChild.firstChild;this.#tl(t,a)}}}updateParent(t,e){if(e===this)return;const i=this.#Ko.get(t);if(i){e.#fn.append(i);this.#Ko.delete(t);e.#Ko.set(t,i)}}remove(t){this.#Yo.delete(t);if(null!==this.#fn){this.#Ko.get(t).remove();this.#Ko.delete(t)}}destroy(){this.#fn=null;for(const t of this.#Ko.values())t.remove();this.#Ko.clear();this.#Yo.clear()}}globalThis._pdfjsTestingUtils={HighlightOutliner};globalThis.pdfjsLib={AbortException,AnnotationEditorLayer,AnnotationEditorParamsType:p,AnnotationEditorType:u,AnnotationEditorUIManager,AnnotationLayer,AnnotationMode:c,AnnotationType:x,build:vt,ColorPicker,createValidAbsoluteUrl,DOMSVGFactory,DrawLayer,FeatureTest:util_FeatureTest,fetchData,getDocument,getFilenameFromUrl,getPdfFilenameFromUrl,getUuid,getXfaPageViewport,GlobalWorkerOptions,ImageKind:v,InvalidPDFException,isDataScheme,isPdfFile,isValidExplicitDest:q,MathClamp,noContextMenu,normalizeUnicode,OPS:D,OutputScale,PasswordResponses:F,PDFDataRangeTransport,PDFDateString,PDFWorker,PermissionFlag:g,PixelsPerInch,RenderingCancelledException,ResponseException,setLayerDimensions,shadow,SignatureExtractor,stopEvent,SupportedImageMimeTypes:G,TextLayer,TouchManager,updateUrlHash,Util,VerbosityLevel:M,version:yt,XfaLayer};export{AbortException,AnnotationEditorLayer,p as AnnotationEditorParamsType,u as AnnotationEditorType,AnnotationEditorUIManager,AnnotationLayer,c as AnnotationMode,x as AnnotationType,ColorPicker,DOMSVGFactory,DrawLayer,util_FeatureTest as FeatureTest,GlobalWorkerOptions,v as ImageKind,InvalidPDFException,MathClamp,D as OPS,OutputScale,PDFDataRangeTransport,PDFDateString,PDFWorker,F as PasswordResponses,g as PermissionFlag,PixelsPerInch,RenderingCancelledException,ResponseException,SignatureExtractor,G as SupportedImageMimeTypes,TextLayer,TouchManager,Util,M as VerbosityLevel,XfaLayer,vt as build,createValidAbsoluteUrl,fetchData,getDocument,getFilenameFromUrl,getPdfFilenameFromUrl,getUuid,getXfaPageViewport,isDataScheme,isPdfFile,q as isValidExplicitDest,noContextMenu,normalizeUnicode,setLayerDimensions,shadow,stopEvent,updateUrlHash,yt as version}; \ No newline at end of file diff --git a/src/client/public/js/pdf.worker.min.mjs b/src/client/public/js/pdf.worker.min.mjs index f963616..de36edc 100644 --- a/src/client/public/js/pdf.worker.min.mjs +++ b/src/client/public/js/pdf.worker.min.mjs @@ -19,4 +19,11 @@ * @licend The above is the entire license notice for the * JavaScript code in this page */ -const e=!("object"!=typeof process||process+""!="[object process]"||process.versions.nw||process.versions.electron&&process.type&&"browser"!==process.type),t=[.001,0,0,.001,0,0],a=1.35,r=.35,i=.25925925925925924,n=1,s=2,o=4,c=8,l=16,h=64,u=128,d=256,f="pdfjs_internal_editor_",g=3,p=9,m=13,b=15,y=101,w={PRINT:4,MODIFY_CONTENTS:8,COPY:16,MODIFY_ANNOTATIONS:32,FILL_INTERACTIVE_FORMS:256,COPY_FOR_ACCESSIBILITY:512,ASSEMBLE:1024,PRINT_HIGH_QUALITY:2048},S=0,x=4,k=1,C=2,v=3,F=1,O=2,T=3,M=4,D=5,R=6,N=7,E=8,L=9,_=10,j=11,U=12,X=13,q=14,H=15,W=16,z=17,$=20,G="Group",V="R",K=1,J=2,Y=4,Z=16,Q=32,ee=128,te=512,ae=1,re=2,ie=4096,ne=8192,se=32768,oe=65536,ce=131072,le=1048576,he=2097152,ue=8388608,de=16777216,fe=1,ge=2,pe=3,me=4,be=5,ye={E:"Mouse Enter",X:"Mouse Exit",D:"Mouse Down",U:"Mouse Up",Fo:"Focus",Bl:"Blur",PO:"PageOpen",PC:"PageClose",PV:"PageVisible",PI:"PageInvisible",K:"Keystroke",F:"Format",V:"Validate",C:"Calculate"},we={WC:"WillClose",WS:"WillSave",DS:"DidSave",WP:"WillPrint",DP:"DidPrint"},Se={O:"PageOpen",C:"PageClose"},xe=1,Ae=5,ke=1,Ce=2,ve=3,Fe=4,Ie=5,Oe=6,Te=7,Me=8,De=9,Be=10,Re=11,Ne=12,Ee=13,Pe=14,Le=15,_e=16,je=17,Ue=18,Xe=19,qe=20,He=21,We=22,ze=23,$e=24,Ge=25,Ve=26,Ke=27,Je=28,Ye=29,Ze=30,Qe=31,et=32,tt=33,at=34,rt=35,it=36,nt=37,st=38,ot=39,ct=40,lt=41,ht=42,ut=43,dt=44,ft=45,gt=46,pt=47,mt=48,bt=49,yt=50,wt=51,St=52,xt=53,At=54,kt=55,Ct=56,vt=57,Ft=58,It=59,Ot=60,Tt=61,Mt=62,Dt=63,Bt=64,Rt=65,Nt=66,Et=67,Pt=68,Lt=69,_t=70,jt=71,Ut=72,Xt=73,qt=74,Ht=75,Wt=76,zt=77,$t=80,Gt=81,Vt=83,Kt=84,Jt=85,Yt=86,Zt=87,Qt=88,ea=89,ta=90,aa=91,ra=92,ia=93,na=94,sa=0,oa=1,ca=2,la=3,ha=1,ua=2;let da=xe;function getVerbosityLevel(){return da}function info(e){da>=Ae&&console.log(`Info: ${e}`)}function warn(e){da>=xe&&console.log(`Warning: ${e}`)}function unreachable(e){throw new Error(e)}function assert(e,t){e||unreachable(t)}function createValidAbsoluteUrl(e,t=null,a=null){if(!e)return null;if(a&&"string"==typeof e){if(a.addDefaultProtocol&&e.startsWith("www.")){const t=e.match(/\./g);t?.length>=2&&(e=`http://${e}`)}if(a.tryConvertEncoding)try{e=stringToUTF8String(e)}catch{}}const r=t?URL.parse(e,t):URL.parse(e);return function _isValidProtocol(e){switch(e?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(r)?r:null}function shadow(e,t,a,r=!1){Object.defineProperty(e,t,{value:a,enumerable:!r,configurable:!0,writable:!1});return a}const fa=function BaseExceptionClosure(){function BaseException(e,t){this.message=e;this.name=t}BaseException.prototype=new Error;BaseException.constructor=BaseException;return BaseException}();class PasswordException extends fa{constructor(e,t){super(e,"PasswordException");this.code=t}}class UnknownErrorException extends fa{constructor(e,t){super(e,"UnknownErrorException");this.details=t}}class InvalidPDFException extends fa{constructor(e){super(e,"InvalidPDFException")}}class ResponseException extends fa{constructor(e,t,a){super(e,"ResponseException");this.status=t;this.missing=a}}class FormatError extends fa{constructor(e){super(e,"FormatError")}}class AbortException extends fa{constructor(e){super(e,"AbortException")}}function bytesToString(e){"object"==typeof e&&void 0!==e?.length||unreachable("Invalid argument for bytesToString");const t=e.length,a=8192;if(t>24&255,e>>16&255,e>>8&255,255&e)}function objectSize(e){return Object.keys(e).length}class FeatureTest{static get isLittleEndian(){return shadow(this,"isLittleEndian",function isLittleEndian(){const e=new Uint8Array(4);e[0]=1;return 1===new Uint32Array(e.buffer,0,1)[0]}())}static get isEvalSupported(){return shadow(this,"isEvalSupported",function isEvalSupported(){try{new Function("");return!0}catch{return!1}}())}static get isOffscreenCanvasSupported(){return shadow(this,"isOffscreenCanvasSupported","undefined"!=typeof OffscreenCanvas)}static get isImageDecoderSupported(){return shadow(this,"isImageDecoderSupported","undefined"!=typeof ImageDecoder)}static get platform(){if("undefined"!=typeof navigator&&"string"==typeof navigator?.platform&&"string"==typeof navigator?.userAgent){const{platform:e,userAgent:t}=navigator;return shadow(this,"platform",{isAndroid:t.includes("Android"),isLinux:e.includes("Linux"),isMac:e.includes("Mac"),isWindows:e.includes("Win"),isFirefox:t.includes("Firefox")})}return shadow(this,"platform",{isAndroid:!1,isLinux:!1,isMac:!1,isWindows:!1,isFirefox:!1})}static get isCSSRoundSupported(){return shadow(this,"isCSSRoundSupported",globalThis.CSS?.supports?.("width: round(1.5px, 1px)"))}}const ga=Array.from(Array(256).keys(),(e=>e.toString(16).padStart(2,"0")));class Util{static makeHexColor(e,t,a){return`#${ga[e]}${ga[t]}${ga[a]}`}static scaleMinMax(e,t){let a;if(e[0]){if(e[0]<0){a=t[0];t[0]=t[2];t[2]=a}t[0]*=e[0];t[2]*=e[0];if(e[3]<0){a=t[1];t[1]=t[3];t[3]=a}t[1]*=e[3];t[3]*=e[3]}else{a=t[0];t[0]=t[1];t[1]=a;a=t[2];t[2]=t[3];t[3]=a;if(e[1]<0){a=t[1];t[1]=t[3];t[3]=a}t[1]*=e[1];t[3]*=e[1];if(e[2]<0){a=t[0];t[0]=t[2];t[2]=a}t[0]*=e[2];t[2]*=e[2]}t[0]+=e[4];t[1]+=e[5];t[2]+=e[4];t[3]+=e[5]}static transform(e,t){return[e[0]*t[0]+e[2]*t[1],e[1]*t[0]+e[3]*t[1],e[0]*t[2]+e[2]*t[3],e[1]*t[2]+e[3]*t[3],e[0]*t[4]+e[2]*t[5]+e[4],e[1]*t[4]+e[3]*t[5]+e[5]]}static applyTransform(e,t,a=0){const r=e[a],i=e[a+1];e[a]=r*t[0]+i*t[2]+t[4];e[a+1]=r*t[1]+i*t[3]+t[5]}static applyTransformToBezier(e,t,a=0){const r=t[0],i=t[1],n=t[2],s=t[3],o=t[4],c=t[5];for(let t=0;t<6;t+=2){const l=e[a+t],h=e[a+t+1];e[a+t]=l*r+h*n+o;e[a+t+1]=l*i+h*s+c}}static applyInverseTransform(e,t){const a=e[0],r=e[1],i=t[0]*t[3]-t[1]*t[2];e[0]=(a*t[3]-r*t[2]+t[2]*t[5]-t[4]*t[3])/i;e[1]=(-a*t[1]+r*t[0]+t[4]*t[1]-t[5]*t[0])/i}static axialAlignedBoundingBox(e,t,a){const r=t[0],i=t[1],n=t[2],s=t[3],o=t[4],c=t[5],l=e[0],h=e[1],u=e[2],d=e[3];let f=r*l+o,g=f,p=r*u+o,m=p,b=s*h+c,y=b,w=s*d+c,S=w;if(0!==i||0!==n){const e=i*l,t=i*u,a=n*h,r=n*d;f+=a;m+=a;p+=r;g+=r;b+=e;S+=e;w+=t;y+=t}a[0]=Math.min(a[0],f,p,g,m);a[1]=Math.min(a[1],b,w,y,S);a[2]=Math.max(a[2],f,p,g,m);a[3]=Math.max(a[3],b,w,y,S)}static inverseTransform(e){const t=e[0]*e[3]-e[1]*e[2];return[e[3]/t,-e[1]/t,-e[2]/t,e[0]/t,(e[2]*e[5]-e[4]*e[3])/t,(e[4]*e[1]-e[5]*e[0])/t]}static singularValueDecompose2dScale(e,t){const a=e[0],r=e[1],i=e[2],n=e[3],s=a**2+r**2,o=a*i+r*n,c=i**2+n**2,l=(s+c)/2,h=Math.sqrt(l**2-(s*c-o**2));t[0]=Math.sqrt(l+h||1);t[1]=Math.sqrt(l-h||1)}static normalizeRect(e){const t=e.slice(0);if(e[0]>e[2]){t[0]=e[2];t[2]=e[0]}if(e[1]>e[3]){t[1]=e[3];t[3]=e[1]}return t}static intersect(e,t){const a=Math.max(Math.min(e[0],e[2]),Math.min(t[0],t[2])),r=Math.min(Math.max(e[0],e[2]),Math.max(t[0],t[2]));if(a>r)return null;const i=Math.max(Math.min(e[1],e[3]),Math.min(t[1],t[3])),n=Math.min(Math.max(e[1],e[3]),Math.max(t[1],t[3]));return i>n?null:[a,i,r,n]}static pointBoundingBox(e,t,a){a[0]=Math.min(a[0],e);a[1]=Math.min(a[1],t);a[2]=Math.max(a[2],e);a[3]=Math.max(a[3],t)}static rectBoundingBox(e,t,a,r,i){i[0]=Math.min(i[0],e,a);i[1]=Math.min(i[1],t,r);i[2]=Math.max(i[2],e,a);i[3]=Math.max(i[3],t,r)}static#e(e,t,a,r,i,n,s,o,c,l){if(c<=0||c>=1)return;const h=1-c,u=c*c,d=u*c,f=h*(h*(h*e+3*c*t)+3*u*a)+d*r,g=h*(h*(h*i+3*c*n)+3*u*s)+d*o;l[0]=Math.min(l[0],f);l[1]=Math.min(l[1],g);l[2]=Math.max(l[2],f);l[3]=Math.max(l[3],g)}static#t(e,t,a,r,i,n,s,o,c,l,h,u){if(Math.abs(c)<1e-12){Math.abs(l)>=1e-12&&this.#e(e,t,a,r,i,n,s,o,-h/l,u);return}const d=l**2-4*h*c;if(d<0)return;const f=Math.sqrt(d),g=2*c;this.#e(e,t,a,r,i,n,s,o,(-l+f)/g,u);this.#e(e,t,a,r,i,n,s,o,(-l-f)/g,u)}static bezierBoundingBox(e,t,a,r,i,n,s,o,c){c[0]=Math.min(c[0],e,s);c[1]=Math.min(c[1],t,o);c[2]=Math.max(c[2],e,s);c[3]=Math.max(c[3],t,o);this.#t(e,a,i,s,t,r,n,o,3*(3*(a-i)-e+s),6*(e-2*a+i),3*(a-e),c);this.#t(e,a,i,s,t,r,n,o,3*(3*(r-n)-t+o),6*(t-2*r+n),3*(r-t),c)}}const pa=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364];function stringToPDFString(e){if(e[0]>="ï"){let t;if("þ"===e[0]&&"ÿ"===e[1]){t="utf-16be";e.length%2==1&&(e=e.slice(0,-1))}else if("ÿ"===e[0]&&"þ"===e[1]){t="utf-16le";e.length%2==1&&(e=e.slice(0,-1))}else"ï"===e[0]&&"»"===e[1]&&"¿"===e[2]&&(t="utf-8");if(t)try{const a=new TextDecoder(t,{fatal:!0}),r=stringToBytes(e),i=a.decode(r);return i.includes("")?i.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g,""):i}catch(e){warn(`stringToPDFString: "${e}".`)}}const t=[];for(let a=0,r=e.length;aga[e])).join("")}"function"!=typeof Promise.try&&(Promise.try=function(e,...t){return new Promise((a=>{a(e(...t))}))});"function"!=typeof Math.sumPrecise&&(Math.sumPrecise=function(e){return e.reduce(((e,t)=>e+t),0)});const ya=Symbol("CIRCULAR_REF"),wa=Symbol("EOF");let Sa=Object.create(null),xa=Object.create(null),Aa=Object.create(null);class Name{constructor(e){this.name=e}static get(e){return xa[e]||=new Name(e)}}class Cmd{constructor(e){this.cmd=e}static get(e){return Sa[e]||=new Cmd(e)}}const ka=function nonSerializableClosure(){return ka};class Dict{constructor(e=null){this._map=new Map;this.xref=e;this.objId=null;this.suppressEncryption=!1;this.__nonSerializable__=ka}assignXref(e){this.xref=e}get size(){return this._map.size}get(e,t,a){let r=this._map.get(e);if(void 0===r&&void 0!==t){r=this._map.get(t);void 0===r&&void 0!==a&&(r=this._map.get(a))}return r instanceof Ref&&this.xref?this.xref.fetch(r,this.suppressEncryption):r}async getAsync(e,t,a){let r=this._map.get(e);if(void 0===r&&void 0!==t){r=this._map.get(t);void 0===r&&void 0!==a&&(r=this._map.get(a))}return r instanceof Ref&&this.xref?this.xref.fetchAsync(r,this.suppressEncryption):r}getArray(e,t,a){let r=this._map.get(e);if(void 0===r&&void 0!==t){r=this._map.get(t);void 0===r&&void 0!==a&&(r=this._map.get(a))}r instanceof Ref&&this.xref&&(r=this.xref.fetch(r,this.suppressEncryption));if(Array.isArray(r)){r=r.slice();for(let e=0,t=r.length;e{unreachable("Should not call `set` on the empty dictionary.")};return shadow(this,"empty",e)}static merge({xref:e,dictArray:t,mergeSubDicts:a=!1}){const r=new Dict(e),i=new Map;for(const e of t)if(e instanceof Dict)for(const[t,r]of e._map){let e=i.get(t);if(void 0===e){e=[];i.set(t,e)}else if(!(a&&r instanceof Dict))continue;e.push(r)}for(const[t,a]of i){if(1===a.length||!(a[0]instanceof Dict)){r._map.set(t,a[0]);continue}const i=new Dict(e);for(const e of a)for(const[t,a]of e._map)i._map.has(t)||i._map.set(t,a);i.size>0&&r._map.set(t,i)}i.clear();return r.size>0?r:Dict.empty}clone(){const e=new Dict(this.xref);for(const t of this.getKeys())e.set(t,this.getRaw(t));return e}delete(e){delete this._map[e]}}class Ref{constructor(e,t){this.num=e;this.gen=t}toString(){return 0===this.gen?`${this.num}R`:`${this.num}R${this.gen}`}static fromString(e){const t=Aa[e];if(t)return t;const a=/^(\d+)R(\d*)$/.exec(e);return a&&"0"!==a[1]?Aa[e]=new Ref(parseInt(a[1]),a[2]?parseInt(a[2]):0):null}static get(e,t){const a=0===t?`${e}R`:`${e}R${t}`;return Aa[a]||=new Ref(e,t)}}class RefSet{constructor(e=null){this._set=new Set(e?._set)}has(e){return this._set.has(e.toString())}put(e){this._set.add(e.toString())}remove(e){this._set.delete(e.toString())}[Symbol.iterator](){return this._set.values()}clear(){this._set.clear()}}class RefSetCache{constructor(){this._map=new Map}get size(){return this._map.size}get(e){return this._map.get(e.toString())}has(e){return this._map.has(e.toString())}put(e,t){this._map.set(e.toString(),t)}putAlias(e,t){this._map.set(e.toString(),this.get(t))}[Symbol.iterator](){return this._map.values()}clear(){this._map.clear()}*values(){yield*this._map.values()}*items(){for(const[e,t]of this._map)yield[Ref.fromString(e),t]}}function isName(e,t){return e instanceof Name&&(void 0===t||e.name===t)}function isCmd(e,t){return e instanceof Cmd&&(void 0===t||e.cmd===t)}function isDict(e,t){return e instanceof Dict&&(void 0===t||isName(e.get("Type"),t))}function isRefsEqual(e,t){return e.num===t.num&&e.gen===t.gen}class BaseStream{get length(){unreachable("Abstract getter `length` accessed")}get isEmpty(){unreachable("Abstract getter `isEmpty` accessed")}get isDataLoaded(){return shadow(this,"isDataLoaded",!0)}getByte(){unreachable("Abstract method `getByte` called")}getBytes(e){unreachable("Abstract method `getBytes` called")}async getImageData(e,t){return this.getBytes(e,t)}async asyncGetBytes(){unreachable("Abstract method `asyncGetBytes` called")}get isAsync(){return!1}get isAsyncDecoder(){return!1}get canAsyncDecodeImageFromBuffer(){return!1}async getTransferableImage(){return null}peekByte(){const e=this.getByte();-1!==e&&this.pos--;return e}peekBytes(e){const t=this.getBytes(e);this.pos-=t.length;return t}getUint16(){const e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t}getInt32(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()}getByteRange(e,t){unreachable("Abstract method `getByteRange` called")}getString(e){return bytesToString(this.getBytes(e))}skip(e){this.pos+=e||1}reset(){unreachable("Abstract method `reset` called")}moveStart(){unreachable("Abstract method `moveStart` called")}makeSubStream(e,t,a=null){unreachable("Abstract method `makeSubStream` called")}getBaseStreams(){return null}}const Ca=/^[1-9]\.\d$/,va=2**31-1,Fa=[1,0,0,1,0,0],Ia=["ColorSpace","ExtGState","Font","Pattern","Properties","Shading","XObject"],Oa=["ExtGState","Font","Properties","XObject"];function getLookupTableFactory(e){let t;return function(){if(e){t=Object.create(null);e(t);e=null}return t}}class MissingDataException extends fa{constructor(e,t){super(`Missing data [${e}, ${t})`,"MissingDataException");this.begin=e;this.end=t}}class ParserEOFException extends fa{constructor(e){super(e,"ParserEOFException")}}class XRefEntryException extends fa{constructor(e){super(e,"XRefEntryException")}}class XRefParseException extends fa{constructor(e){super(e,"XRefParseException")}}function arrayBuffersToBytes(e){const t=e.length;if(0===t)return new Uint8Array(0);if(1===t)return new Uint8Array(e[0]);let a=0;for(let r=0;r0,"The number should be a positive integer.");const a="M".repeat(e/1e3|0)+Ta[e%1e3/100|0]+Ta[10+(e%100/10|0)]+Ta[20+e%10];return t?a.toLowerCase():a}function log2(e){return e>0?Math.ceil(Math.log2(e)):0}function readInt8(e,t){return e[t]<<24>>24}function readInt16(e,t){return(e[t]<<24|e[t+1]<<16)>>16}function readUint16(e,t){return e[t]<<8|e[t+1]}function readUint32(e,t){return(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0}function isWhiteSpace(e){return 32===e||9===e||13===e||10===e}function isNumberArray(e,t){return Array.isArray(e)?(null===t||e.length===t)&&e.every((e=>"number"==typeof e)):ArrayBuffer.isView(e)&&!(e instanceof BigInt64Array||e instanceof BigUint64Array)&&(null===t||e.length===t)}function lookupMatrix(e,t){return isNumberArray(e,6)?e:t}function lookupRect(e,t){return isNumberArray(e,4)?e:t}function lookupNormalRect(e,t){return isNumberArray(e,4)?Util.normalizeRect(e):t}function parseXFAPath(e){const t=/(.+)\[(\d+)\]$/;return e.split(".").map((e=>{const a=e.match(t);return a?{name:a[1],pos:parseInt(a[2],10)}:{name:e,pos:0}}))}function escapePDFName(e){const t=[];let a=0;for(let r=0,i=e.length;r126||35===i||40===i||41===i||60===i||62===i||91===i||93===i||123===i||125===i||47===i||37===i){a"\n"===e?"\\n":"\r"===e?"\\r":`\\${e}`))}function _collectJS(e,t,a,r){if(!e)return;let i=null;if(e instanceof Ref){if(r.has(e))return;i=e;r.put(i);e=t.fetch(e)}if(Array.isArray(e))for(const i of e)_collectJS(i,t,a,r);else if(e instanceof Dict){if(isName(e.get("S"),"JavaScript")){const t=e.get("JS");let r;t instanceof BaseStream?r=t.getString():"string"==typeof t&&(r=t);r&&=stringToPDFString(r).replaceAll("\0","");r&&a.push(r)}_collectJS(e.getRaw("Next"),t,a,r)}i&&r.remove(i)}function collectActions(e,t,a){const r=Object.create(null),i=getInheritableProperty({dict:t,key:"AA",stopWhenFound:!1});if(i)for(let t=i.length-1;t>=0;t--){const n=i[t];if(n instanceof Dict)for(const t of n.getKeys()){const i=a[t];if(!i)continue;const s=[];_collectJS(n.getRaw(t),e,s,new RefSet);s.length>0&&(r[i]=s)}}if(t.has("A")){const a=[];_collectJS(t.get("A"),e,a,new RefSet);a.length>0&&(r.Action=a)}return objectSize(r)>0?r:null}const Ma={60:"<",62:">",38:"&",34:""",39:"'"};function*codePointIter(e){for(let t=0,a=e.length;t55295&&(a<57344||a>65533)&&t++;yield a}}function encodeToXmlString(e){const t=[];let a=0;for(let r=0,i=e.length;r55295&&(i<57344||i>65533)&&r++;a=r+1}}if(0===t.length)return e;a: ${e}.`);return!1}return!0}function validateCSSFont(e){const t=new Set(["100","200","300","400","500","600","700","800","900","1000","normal","bold","bolder","lighter"]),{fontFamily:a,fontWeight:r,italicAngle:i}=e;if(!validateFontName(a,!0))return!1;const n=r?r.toString():"";e.fontWeight=t.has(n)?n:"400";const s=parseFloat(i);e.italicAngle=isNaN(s)||s<-90||s>90?"14":i.toString();return!0}function recoverJsURL(e){const t=new RegExp("^\\s*("+["app.launchURL","window.open","xfa.host.gotoURL"].join("|").replaceAll(".","\\.")+")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))","i").exec(e);return t?.[2]?{url:t[2],newWindow:"app.launchURL"===t[1]&&"true"===t[3]}:null}function numberToString(e){if(Number.isInteger(e))return e.toString();const t=Math.round(100*e);return t%100==0?(t/100).toString():t%10==0?e.toFixed(1):e.toFixed(2)}function getNewAnnotationsMap(e){if(!e)return null;const t=new Map;for(const[a,r]of e){if(!a.startsWith(f))continue;let e=t.get(r.pageIndex);if(!e){e=[];t.set(r.pageIndex,e)}e.push(r)}return t.size>0?t:null}function stringToAsciiOrUTF16BE(e){return function isAscii(e){return/^[\x00-\x7F]*$/.test(e)}(e)?e:stringToUTF16String(e,!0)}function stringToUTF16HexString(e){const t=[];for(let a=0,r=e.length;a>8&255],ga[255&r])}return t.join("")}function stringToUTF16String(e,t=!1){const a=[];t&&a.push("þÿ");for(let t=0,r=e.length;t>8&255),String.fromCharCode(255&r))}return a.join("")}function getRotationMatrix(e,t,a){switch(e){case 90:return[0,1,-1,0,t,0];case 180:return[-1,0,0,-1,t,a];case 270:return[0,-1,1,0,0,a];default:throw new Error("Invalid rotation")}}function getSizeInBytes(e){return Math.ceil(Math.ceil(Math.log2(1+e))/8)}class QCMS{static _module=null;static _mustAddAlpha=!1;static _destBuffer=null}let Da;const Ba="undefined"!=typeof TextDecoder?new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}):{decode:()=>{throw Error("TextDecoder not available")}};"undefined"!=typeof TextDecoder&&Ba.decode();let Ra=null;function getUint8ArrayMemory0(){null!==Ra&&0!==Ra.byteLength||(Ra=new Uint8Array(Da.memory.buffer));return Ra}let Na=0;function passArray8ToWasm0(e,t){const a=t(1*e.length,1)>>>0;getUint8ArrayMemory0().set(e,a/1);Na=e.length;return a}const Ea=Object.freeze({RGB8:0,0:"RGB8",RGBA8:1,1:"RGBA8",BGRA8:2,2:"BGRA8",Gray8:3,3:"Gray8",GrayA8:4,4:"GrayA8",CMYK:5,5:"CMYK"}),Pa=Object.freeze({Perceptual:0,0:"Perceptual",RelativeColorimetric:1,1:"RelativeColorimetric",Saturation:2,2:"Saturation",AbsoluteColorimetric:3,3:"AbsoluteColorimetric"});function __wbg_get_imports(){const e={wbg:{}};e.wbg.__wbg_copyresult_b08ee7d273f295dd=function(e,t){!function copy_result(e,t){const{_module:a,_mustAddAlpha:r,_destBuffer:i}=QCMS,n=new Uint8Array(a.memory.buffer,e,t);if(n.length!==i.length)if(r)for(let e=0,t=0,a=n.length;e>>0,t>>>0)};e.wbg.__wbg_copyrgb_d60ce17bb05d9b67=function(e){!function copy_rgb(e){QCMS._destBuffer.set(new Uint8Array(QCMS._module.memory.buffer,e,3))}(e>>>0)};e.wbg.__wbindgen_init_externref_table=function(){const e=Da.__wbindgen_export_0,t=e.grow(4);e.set(0,void 0);e.set(t+0,void 0);e.set(t+1,null);e.set(t+2,!0);e.set(t+3,!1)};e.wbg.__wbindgen_throw=function(e,t){throw new Error(function getStringFromWasm0(e,t){e>>>=0;return Ba.decode(getUint8ArrayMemory0().subarray(e,e+t))}(e,t))};return e}function __wbg_finalize_init(e,t){Da=e.exports;__wbg_init.__wbindgen_wasm_module=t;Ra=null;Da.__wbindgen_start();return Da}async function __wbg_init(e){if(void 0!==Da)return Da;void 0!==e&&(Object.getPrototypeOf(e)===Object.prototype?({module_or_path:e}=e):console.warn("using deprecated parameters for the initialization function; pass a single object instead"));void 0===e&&(e=new URL("qcms_bg.wasm",import.meta.url));const t=__wbg_get_imports();("string"==typeof e||"function"==typeof Request&&e instanceof Request||"function"==typeof URL&&e instanceof URL)&&(e=fetch(e));const{instance:a,module:r}=await async function __wbg_load(e,t){if("function"==typeof Response&&e instanceof Response){if("function"==typeof WebAssembly.instantiateStreaming)try{return await WebAssembly.instantiateStreaming(e,t)}catch(t){if("application/wasm"==e.headers.get("Content-Type"))throw t;console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",t)}const a=await e.arrayBuffer();return await WebAssembly.instantiate(a,t)}{const a=await WebAssembly.instantiate(e,t);return a instanceof WebAssembly.Instance?{instance:a,module:e}:a}}(await e,t);return __wbg_finalize_init(a,r)}class ColorSpace{constructor(e,t){this.name=e;this.numComps=t}getRgb(e,t){const a=new Uint8ClampedArray(3);this.getRgbItem(e,t,a,0);return a}getRgbItem(e,t,a,r){unreachable("Should not call ColorSpace.getRgbItem")}getRgbBuffer(e,t,a,r,i,n,s){unreachable("Should not call ColorSpace.getRgbBuffer")}getOutputLength(e,t){unreachable("Should not call ColorSpace.getOutputLength")}isPassthrough(e){return!1}isDefaultDecode(e,t){return ColorSpace.isDefaultDecode(e,this.numComps)}fillRgb(e,t,a,r,i,n,s,o,c){const l=t*a;let h=null;const u=1<u&&"DeviceGray"!==this.name&&"DeviceRGB"!==this.name){const t=s<=8?new Uint8Array(u):new Uint16Array(u);for(let e=0;e=.99554525?1:MathClamp(1.055*e**(1/2.4)-.055,0,1)}#p(e){return e<0?-this.#p(-e):e>8?((e+16)/116)**3:e*CalRGBCS.#h}#m(e,t,a){if(0===e[0]&&0===e[1]&&0===e[2]){a[0]=t[0];a[1]=t[1];a[2]=t[2];return}const r=this.#p(0),i=(1-r)/(1-this.#p(e[0])),n=1-i,s=(1-r)/(1-this.#p(e[1])),o=1-s,c=(1-r)/(1-this.#p(e[2])),l=1-c;a[0]=t[0]*i+n;a[1]=t[1]*s+o;a[2]=t[2]*c+l}#b(e,t,a){if(1===e[0]&&1===e[2]){a[0]=t[0];a[1]=t[1];a[2]=t[2];return}const r=a;this.#u(CalRGBCS.#r,t,r);const i=CalRGBCS.#o;this.#d(e,r,i);this.#u(CalRGBCS.#i,i,a)}#y(e,t,a){const r=a;this.#u(CalRGBCS.#r,t,r);const i=CalRGBCS.#o;this.#f(e,r,i);this.#u(CalRGBCS.#i,i,a)}#a(e,t,a,r,i){const n=MathClamp(e[t]*i,0,1),s=MathClamp(e[t+1]*i,0,1),o=MathClamp(e[t+2]*i,0,1),c=1===n?1:n**this.GR,l=1===s?1:s**this.GG,h=1===o?1:o**this.GB,u=this.MXA*c+this.MXB*l+this.MXC*h,d=this.MYA*c+this.MYB*l+this.MYC*h,f=this.MZA*c+this.MZB*l+this.MZC*h,g=CalRGBCS.#c;g[0]=u;g[1]=d;g[2]=f;const p=CalRGBCS.#l;this.#b(this.whitePoint,g,p);const m=CalRGBCS.#c;this.#m(this.blackPoint,p,m);const b=CalRGBCS.#l;this.#y(CalRGBCS.#s,m,b);const y=CalRGBCS.#c;this.#u(CalRGBCS.#n,b,y);a[r]=255*this.#g(y[0]);a[r+1]=255*this.#g(y[1]);a[r+2]=255*this.#g(y[2])}getRgbItem(e,t,a,r){this.#a(e,t,a,r,1)}getRgbBuffer(e,t,a,r,i,n,s){const o=1/((1<this.amax||this.bmin>this.bmax){info("Invalid Range, falling back to defaults");this.amin=-100;this.amax=100;this.bmin=-100;this.bmax=100}}#w(e){return e>=6/29?e**3:108/841*(e-4/29)}#S(e,t,a,r){return a+e*(r-a)/t}#a(e,t,a,r,i){let n=e[t],s=e[t+1],o=e[t+2];if(!1!==a){n=this.#S(n,a,0,100);s=this.#S(s,a,this.amin,this.amax);o=this.#S(o,a,this.bmin,this.bmax)}s>this.amax?s=this.amax:sthis.bmax?o=this.bmax:o{!function qcms_drop_transformer(e){Da.qcms_drop_transformer(e)}(e)}));constructor(e,t,a){if(!IccColorSpace.isUsable)throw new Error("No ICC color space support");super(t,a);let r;switch(a){case 1:r=Ea.Gray8;this.#A=(e,t)=>function qcms_convert_one(e,t){Da.qcms_convert_one(e,t)}(this.#x,255*e[t]);break;case 3:r=Ea.RGB8;this.#A=(e,t)=>function qcms_convert_three(e,t,a,r){Da.qcms_convert_three(e,t,a,r)}(this.#x,255*e[t],255*e[t+1],255*e[t+2]);break;case 4:r=Ea.CMYK;this.#A=(e,t)=>function qcms_convert_four(e,t,a,r,i){Da.qcms_convert_four(e,t,a,r,i)}(this.#x,255*e[t],255*e[t+1],255*e[t+2],255*e[t+3]);break;default:throw new Error(`Unsupported number of components: ${a}`)}this.#x=function qcms_transformer_from_memory(e,t,a){const r=passArray8ToWasm0(e,Da.__wbindgen_malloc),i=Na;return Da.qcms_transformer_from_memory(r,i,t,a)>>>0}(e,r,Pa.Perceptual);if(!this.#x)throw new Error("Failed to create ICC color space");IccColorSpace.#v.register(this,this.#x)}getRgbItem(e,t,a,r){QCMS._destBuffer=a.subarray(r,r+3);this.#A(e,t);QCMS._destBuffer=null}getRgbBuffer(e,t,a,r,i,n,s){e=e.subarray(t,t+a*this.numComps);if(8!==n){const t=255/((1<=this.end?-1:this.bytes[this.pos++]}getBytes(e){const t=this.bytes,a=this.pos,r=this.end;if(!e)return t.subarray(a,r);let i=a+e;i>r&&(i=r);this.pos=i;return t.subarray(a,i)}getByteRange(e,t){e<0&&(e=0);t>this.end&&(t=this.end);return this.bytes.subarray(e,t)}reset(){this.pos=this.start}moveStart(){this.start=this.pos}makeSubStream(e,t,a=null){return new Stream(this.bytes.buffer,e,t,a)}}class StringStream extends Stream{constructor(e){super(stringToBytes(e))}}class NullStream extends Stream{constructor(){super(new Uint8Array(0))}}class ChunkedStream extends Stream{constructor(e,t,a){super(new Uint8Array(e),0,e,null);this.chunkSize=t;this._loadedChunks=new Set;this.numChunks=Math.ceil(e/t);this.manager=a;this.progressiveDataLength=0;this.lastSuccessfulEnsureByteChunk=-1}getMissingChunks(){const e=[];for(let t=0,a=this.numChunks;t=this.end?this.numChunks:Math.floor(t/this.chunkSize);for(let e=a;ethis.numChunks)&&t!==this.lastSuccessfulEnsureByteChunk){if(!this._loadedChunks.has(t))throw new MissingDataException(e,e+1);this.lastSuccessfulEnsureByteChunk=t}}ensureRange(e,t){if(e>=t)return;if(t<=this.progressiveDataLength)return;const a=Math.floor(e/this.chunkSize);if(a>this.numChunks)return;const r=Math.min(Math.floor((t-1)/this.chunkSize)+1,this.numChunks);for(let i=a;i=this.end)return-1;e>=this.progressiveDataLength&&this.ensureByte(e);return this.bytes[this.pos++]}getBytes(e){const t=this.bytes,a=this.pos,r=this.end;if(!e){r>this.progressiveDataLength&&this.ensureRange(a,r);return t.subarray(a,r)}let i=a+e;i>r&&(i=r);i>this.progressiveDataLength&&this.ensureRange(a,i);this.pos=i;return t.subarray(a,i)}getByteRange(e,t){e<0&&(e=0);t>this.end&&(t=this.end);t>this.progressiveDataLength&&this.ensureRange(e,t);return this.bytes.subarray(e,t)}makeSubStream(e,t,a=null){t?e+t>this.progressiveDataLength&&this.ensureRange(e,e+t):e>=this.progressiveDataLength&&this.ensureByte(e);function ChunkedStreamSubstream(){}ChunkedStreamSubstream.prototype=Object.create(this);ChunkedStreamSubstream.prototype.getMissingChunks=function(){const e=this.chunkSize,t=Math.floor(this.start/e),a=Math.floor((this.end-1)/e)+1,r=[];for(let e=t;e{const readChunk=({value:n,done:s})=>{try{if(s){const t=arrayBuffersToBytes(r);r=null;e(t);return}i+=n.byteLength;a.isStreamingSupported&&this.onProgress({loaded:i});r.push(n);a.read().then(readChunk,t)}catch(e){t(e)}};a.read().then(readChunk,t)})).then((t=>{this.aborted||this.onReceiveData({chunk:t,begin:e})}))}requestAllChunks(e=!1){if(!e){const e=this.stream.getMissingChunks();this._requestChunks(e)}return this._loadedStreamCapability.promise}_requestChunks(e){const t=this.currRequestId++,a=new Set;this._chunksNeededByRequest.set(t,a);for(const t of e)this.stream.hasChunk(t)||a.add(t);if(0===a.size)return Promise.resolve();const r=Promise.withResolvers();this._promisesByRequest.set(t,r);const i=[];for(const e of a){let a=this._requestsByChunk.get(e);if(!a){a=[];this._requestsByChunk.set(e,a);i.push(e)}a.push(t)}if(i.length>0){const e=this.groupChunks(i);for(const t of e){const e=t.beginChunk*this.chunkSize,a=Math.min(t.endChunk*this.chunkSize,this.length);this.sendRequest(e,a).catch(r.reject)}}return r.promise.catch((e=>{if(!this.aborted)throw e}))}getStream(){return this.stream}requestRange(e,t){t=Math.min(t,this.length);const a=this.getBeginChunk(e),r=this.getEndChunk(t),i=[];for(let e=a;ee-t));return this._requestChunks(t)}groupChunks(e){const t=[];let a=-1,r=-1;for(let i=0,n=e.length;i=0&&r+1!==n){t.push({beginChunk:a,endChunk:r+1});a=n}i+1===e.length&&t.push({beginChunk:a,endChunk:n+1});r=n}return t}onProgress(e){this.msgHandler.send("DocProgress",{loaded:this.stream.numChunksLoaded*this.chunkSize+e.loaded,total:this.length})}onReceiveData(e){const t=e.chunk,a=void 0===e.begin,r=a?this.progressiveDataLength:e.begin,i=r+t.byteLength,n=Math.floor(r/this.chunkSize),s=i0||o.push(a)}}}if(!this.disableAutoFetch&&0===this._requestsByChunk.size){let e;if(1===this.stream.numChunksLoaded){const t=this.stream.numChunks-1;this.stream.hasChunk(t)||(e=t)}else e=this.stream.nextEmptyChunk(s);Number.isInteger(e)&&this._requestChunks([e])}for(const e of o){const t=this._promisesByRequest.get(e);this._promisesByRequest.delete(e);t.resolve()}this.msgHandler.send("DocProgress",{loaded:this.stream.numChunksLoaded*this.chunkSize,total:this.length})}onError(e){this._loadedStreamCapability.reject(e)}getBeginChunk(e){return Math.floor(e/this.chunkSize)}getEndChunk(e){return Math.floor((e-1)/this.chunkSize)+1}abort(e){this.aborted=!0;this.pdfNetworkStream?.cancelAllRequests(e);for(const t of this._promisesByRequest.values())t.reject(e)}}function convertToRGBA(e){switch(e.kind){case k:return convertBlackAndWhiteToRGBA(e);case C:return function convertRGBToRGBA({src:e,srcPos:t=0,dest:a,destPos:r=0,width:i,height:n}){let s=0;const o=i*n*3,c=o>>2,l=new Uint32Array(e.buffer,t,c);if(FeatureTest.isLittleEndian){for(;s>>24|t<<8|4278190080;a[r+2]=t>>>16|i<<16|4278190080;a[r+3]=i>>>8|4278190080}for(let i=4*s,n=t+o;i>>8|255;a[r+2]=t<<16|i>>>16|255;a[r+3]=i<<8|255}for(let i=4*s,n=t+o;i>3,u=7&r,d=e.length;a=new Uint32Array(a.buffer);let f=0;for(let r=0;ra||t>a)return!0;const r=e*t;if(this._hasMaxArea)return r>this.MAX_AREA;if(r(this.MAX_AREA=this.#I**2)}static get MAX_DIM(){return shadow(this,"MAX_DIM",this._guessMax(2048,65537,0,1))}static get MAX_AREA(){this._hasMaxArea=!0;return shadow(this,"MAX_AREA",this._guessMax(this.#I,this.MAX_DIM,128,0)**2)}static set MAX_AREA(e){if(e>=0){this._hasMaxArea=!0;shadow(this,"MAX_AREA",e)}}static setOptions({canvasMaxAreaInBytes:e=-1,isImageDecoderSupported:t=!1}){this._hasMaxArea||(this.MAX_AREA=e>>2);this.#O=t}static _areGoodDims(e,t){try{const a=new OffscreenCanvas(e,t),r=a.getContext("2d");r.fillRect(0,0,1,1);const i=r.getImageData(0,0,1,1).data[3];a.width=a.height=1;return 0!==i}catch{return!1}}static _guessMax(e,t,a,r){for(;e+a+1va){const e=this.#T();if(e)return e}const r=this._encodeBMP();let i,n;if(await ImageResizer.canUseImageDecoder){i=new ImageDecoder({data:r,type:"image/bmp",preferAnimation:!1,transfer:[r.buffer]});n=i.decode().catch((e=>{warn(`BMP image decoding failed: ${e}`);return createImageBitmap(new Blob([this._encodeBMP().buffer],{type:"image/bmp"}))})).finally((()=>{i.close()}))}else n=createImageBitmap(new Blob([r.buffer],{type:"image/bmp"}));const{MAX_AREA:s,MAX_DIM:o}=ImageResizer,c=Math.max(t/o,a/o,Math.sqrt(t*a/s)),l=Math.max(c,2),h=Math.round(10*(c+1.25))/10/l,u=Math.floor(Math.log2(h)),d=new Array(u+2).fill(2);d[0]=l;d.splice(-1,1,h/(1<>s,c=r>>s;let l,h=r;try{l=new Uint8Array(n)}catch{let e=Math.floor(Math.log2(n+1));for(;;)try{l=new Uint8Array(2**e-1);break}catch{e-=1}h=Math.floor((2**e-1)/(4*a));const t=a*h*4;t>s;e>3,s=a+3&-4;if(a!==s){const e=new Uint8Array(s*t);let r=0;for(let n=0,o=t*a;ni&&(r=i)}else{for(;!this.eof;)this.readBlock(t);r=this.bufferLength}this.pos=r;return this.buffer.subarray(a,r)}async getImageData(e,t){if(!this.canAsyncDecodeImageFromBuffer)return this.isAsyncDecoder?this.decodeImage(null,t):this.getBytes(e,t);const a=await this.stream.asyncGetBytes();return this.decodeImage(a,t)}reset(){this.pos=0}makeSubStream(e,t,a=null){if(void 0===t)for(;!this.eof;)this.readBlock();else{const a=e+t;for(;this.bufferLength<=a&&!this.eof;)this.readBlock()}return new Stream(this.buffer,e,t,a)}getBaseStreams(){return this.str?this.str.getBaseStreams():null}}class StreamsSequenceStream extends DecodeStream{constructor(e,t=null){e=e.filter((e=>e instanceof BaseStream));let a=0;for(const t of e)a+=t instanceof DecodeStream?t._rawMinBufferLength:t.length;super(a);this.streams=e;this._onError=t}readBlock(){const e=this.streams;if(0===e.length){this.eof=!0;return}const t=e.shift();let a;try{a=t.getBytes()}catch(e){if(this._onError){this._onError(e,t.dict?.objId);return}throw e}const r=this.bufferLength,i=r+a.length;this.ensureBuffer(i).set(a,r);this.bufferLength=i}getBaseStreams(){const e=[];for(const t of this.streams){const a=t.getBaseStreams();a&&e.push(...a)}return e.length>0?e:null}}class ColorSpaceUtils{static parse({cs:e,xref:t,resources:a=null,pdfFunctionFactory:r,globalColorSpaceCache:i,localColorSpaceCache:n,asyncIfNotCached:s=!1}){const o={xref:t,resources:a,pdfFunctionFactory:r,globalColorSpaceCache:i,localColorSpaceCache:n};let c,l,h;if(e instanceof Ref){l=e;const a=i.getByRef(l)||n.getByRef(l);if(a)return a;e=t.fetch(e)}if(e instanceof Name){c=e.name;const t=n.getByName(c);if(t)return t}try{h=this.#M(e,o)}catch(e){if(s&&!(e instanceof MissingDataException))return Promise.reject(e);throw e}if(c||l){n.set(c,l,h);l&&i.set(null,l,h)}return s?Promise.resolve(h):h}static#D(e,t){const{globalColorSpaceCache:a}=t;let r;if(e instanceof Ref){r=e;const t=a.getByRef(r);if(t)return t}const i=this.#M(e,t);r&&a.set(null,r,i);return i}static#M(e,t){const{xref:a,resources:r,pdfFunctionFactory:i,globalColorSpaceCache:n}=t;if((e=a.fetchIfRef(e))instanceof Name)switch(e.name){case"G":case"DeviceGray":return this.gray;case"RGB":case"DeviceRGB":return this.rgb;case"DeviceRGBA":return this.rgba;case"CMYK":case"DeviceCMYK":return this.cmyk;case"Pattern":return new PatternCS(null);default:if(r instanceof Dict){const a=r.get("ColorSpace");if(a instanceof Dict){const r=a.get(e.name);if(r){if(r instanceof Name)return this.#M(r,t);e=r;break}}}warn(`Unrecognized ColorSpace: ${e.name}`);return this.gray}if(Array.isArray(e)){const r=a.fetchIfRef(e[0]).name;let s,o,c,l,h,u;switch(r){case"G":case"DeviceGray":return this.gray;case"RGB":case"DeviceRGB":return this.rgb;case"CMYK":case"DeviceCMYK":return this.cmyk;case"CalGray":s=a.fetchIfRef(e[1]);l=s.getArray("WhitePoint");h=s.getArray("BlackPoint");u=s.get("Gamma");return new CalGrayCS(l,h,u);case"CalRGB":s=a.fetchIfRef(e[1]);l=s.getArray("WhitePoint");h=s.getArray("BlackPoint");u=s.getArray("Gamma");const d=s.getArray("Matrix");return new CalRGBCS(l,h,u,d);case"ICCBased":const f=e[1]instanceof Ref;if(f){const t=n.getByRef(e[1]);if(t)return t}const g=a.fetchIfRef(e[1]),p=g.dict;o=p.get("N");if(IccColorSpace.isUsable)try{const t=new IccColorSpace(g.getBytes(),"ICCBased",o);f&&n.set(null,e[1],t);return t}catch(t){if(t instanceof MissingDataException)throw t;warn(`ICCBased color space (${e[1]}): "${t}".`)}const m=p.getRaw("Alternate");if(m){const e=this.#D(m,t);if(e.numComps===o)return e;warn("ICCBased color space: Ignoring incorrect /Alternate entry.")}if(1===o)return this.gray;if(3===o)return this.rgb;if(4===o)return this.cmyk;break;case"Pattern":c=e[1]||null;c&&(c=this.#D(c,t));return new PatternCS(c);case"I":case"Indexed":c=this.#D(e[1],t);const b=MathClamp(a.fetchIfRef(e[2]),0,255),y=a.fetchIfRef(e[3]);return new IndexedCS(c,b,y);case"Separation":case"DeviceN":const w=a.fetchIfRef(e[1]);o=Array.isArray(w)?w.length:1;c=this.#D(e[2],t);const S=i.create(e[3]);return new AlternateCS(o,c,S);case"Lab":s=a.fetchIfRef(e[1]);l=s.getArray("WhitePoint");h=s.getArray("BlackPoint");const x=s.getArray("Range");return new LabCS(l,h,x);default:warn(`Unimplemented ColorSpace object: ${r}`);return this.gray}}warn(`Unrecognized ColorSpace object: ${e}`);return this.gray}static get gray(){return shadow(this,"gray",new DeviceGrayCS)}static get rgb(){return shadow(this,"rgb",new DeviceRgbCS)}static get rgba(){return shadow(this,"rgba",new DeviceRgbaCS)}static get cmyk(){if(CmykICCBasedCS.isUsable)try{return shadow(this,"cmyk",new CmykICCBasedCS)}catch{warn("CMYK fallback: DeviceCMYK")}return shadow(this,"cmyk",new DeviceCmykCS)}}class JpegError extends fa{constructor(e){super(e,"JpegError")}}class DNLMarkerError extends fa{constructor(e,t){super(e,"DNLMarkerError");this.scanLines=t}}class EOIMarkerError extends fa{constructor(e){super(e,"EOIMarkerError")}}const _a=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),ja=4017,Ua=799,Xa=3406,qa=2276,Ha=1567,Wa=3784,za=5793,$a=2896;function buildHuffmanTable(e,t){let a,r,i=0,n=16;for(;n>0&&!e[n-1];)n--;const s=[{children:[],index:0}];let o,c=s[0];for(a=0;a0;)c=s.pop();c.index++;s.push(c);for(;s.length<=a;){s.push(o={children:[],index:0});c.children[c.index]=o.children;c=o}i++}if(a+10){g--;return f>>g&1}f=e[t++];if(255===f){const r=e[t++];if(r){if(220===r&&l){const r=readUint16(e,t+=2);t+=2;if(r>0&&r!==a.scanLines)throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",r)}else if(217===r){if(l){const e=y*(8===a.precision?8:0);if(e>0&&Math.round(a.scanLines/e)>=5)throw new DNLMarkerError("Found EOI marker (0xFFD9) while parsing scan data, possibly caused by incorrect `scanLines` parameter",e)}throw new EOIMarkerError("Found EOI marker (0xFFD9) while parsing scan data")}throw new JpegError(`unexpected marker ${(f<<8|r).toString(16)}`)}}g=7;return f>>>7}function decodeHuffman(e){let t=e;for(;;){t=t[readBit()];switch(typeof t){case"number":return t;case"object":continue}throw new JpegError("invalid huffman sequence")}}function receive(e){let t=0;for(;e>0;){t=t<<1|readBit();e--}return t}function receiveAndExtend(e){if(1===e)return 1===readBit()?1:-1;const t=receive(e);return t>=1<0){p--;return}let a=n;const r=s;for(;a<=r;){const r=decodeHuffman(e.huffmanTableAC),i=15&r,n=r>>4;if(0===i){if(n<15){p=receive(n)+(1<>4;if(0===i)if(l<15){p=receive(l)+(1<>4;if(0===r){if(n<15)break;i+=16;continue}i+=n;const s=_a[i];e.blockData[t+s]=receiveAndExtend(r);i++}};let O,T=0;const M=1===w?r[0].blocksPerLine*r[0].blocksPerColumn:h*a.mcusPerColumn;let D,R;for(;T<=M;){const a=i?Math.min(M-T,i):M;if(a>0){for(x=0;x0?"unexpected":"excessive"} MCU data, current marker is: ${O.invalid}`);t=O.offset}if(!(O.marker>=65488&&O.marker<=65495))break;t+=2}return t-d}function quantizeAndInverse(e,t,a){const r=e.quantizationTable,i=e.blockData;let n,s,o,c,l,h,u,d,f,g,p,m,b,y,w,S,x;if(!r)throw new JpegError("missing required Quantization Table.");for(let e=0;e<64;e+=8){f=i[t+e];g=i[t+e+1];p=i[t+e+2];m=i[t+e+3];b=i[t+e+4];y=i[t+e+5];w=i[t+e+6];S=i[t+e+7];f*=r[e];if(g|p|m|b|y|w|S){g*=r[e+1];p*=r[e+2];m*=r[e+3];b*=r[e+4];y*=r[e+5];w*=r[e+6];S*=r[e+7];n=za*f+128>>8;s=za*b+128>>8;o=p;c=w;l=$a*(g-S)+128>>8;d=$a*(g+S)+128>>8;h=m<<4;u=y<<4;n=n+s+1>>1;s=n-s;x=o*Wa+c*Ha+128>>8;o=o*Ha-c*Wa+128>>8;c=x;l=l+u+1>>1;u=l-u;d=d+h+1>>1;h=d-h;n=n+c+1>>1;c=n-c;s=s+o+1>>1;o=s-o;x=l*qa+d*Xa+2048>>12;l=l*Xa-d*qa+2048>>12;d=x;x=h*Ua+u*ja+2048>>12;h=h*ja-u*Ua+2048>>12;u=x;a[e]=n+d;a[e+7]=n-d;a[e+1]=s+u;a[e+6]=s-u;a[e+2]=o+h;a[e+5]=o-h;a[e+3]=c+l;a[e+4]=c-l}else{x=za*f+512>>10;a[e]=x;a[e+1]=x;a[e+2]=x;a[e+3]=x;a[e+4]=x;a[e+5]=x;a[e+6]=x;a[e+7]=x}}for(let e=0;e<8;++e){f=a[e];g=a[e+8];p=a[e+16];m=a[e+24];b=a[e+32];y=a[e+40];w=a[e+48];S=a[e+56];if(g|p|m|b|y|w|S){n=za*f+2048>>12;s=za*b+2048>>12;o=p;c=w;l=$a*(g-S)+2048>>12;d=$a*(g+S)+2048>>12;h=m;u=y;n=4112+(n+s+1>>1);s=n-s;x=o*Wa+c*Ha+2048>>12;o=o*Ha-c*Wa+2048>>12;c=x;l=l+u+1>>1;u=l-u;d=d+h+1>>1;h=d-h;n=n+c+1>>1;c=n-c;s=s+o+1>>1;o=s-o;x=l*qa+d*Xa+2048>>12;l=l*Xa-d*qa+2048>>12;d=x;x=h*Ua+u*ja+2048>>12;h=h*ja-u*Ua+2048>>12;u=x;f=n+d;S=n-d;g=s+u;w=s-u;p=o+h;y=o-h;m=c+l;b=c-l;f<16?f=0:f>=4080?f=255:f>>=4;g<16?g=0:g>=4080?g=255:g>>=4;p<16?p=0:p>=4080?p=255:p>>=4;m<16?m=0:m>=4080?m=255:m>>=4;b<16?b=0:b>=4080?b=255:b>>=4;y<16?y=0:y>=4080?y=255:y>>=4;w<16?w=0:w>=4080?w=255:w>>=4;S<16?S=0:S>=4080?S=255:S>>=4;i[t+e]=f;i[t+e+8]=g;i[t+e+16]=p;i[t+e+24]=m;i[t+e+32]=b;i[t+e+40]=y;i[t+e+48]=w;i[t+e+56]=S}else{x=za*f+8192>>14;x=x<-2040?0:x>=2024?255:x+2056>>4;i[t+e]=x;i[t+e+8]=x;i[t+e+16]=x;i[t+e+24]=x;i[t+e+32]=x;i[t+e+40]=x;i[t+e+48]=x;i[t+e+56]=x}}}function buildComponentData(e,t){const a=t.blocksPerLine,r=t.blocksPerColumn,i=new Int16Array(64);for(let e=0;e=r)return null;const n=readUint16(e,t);if(n>=65472&&n<=65534)return{invalid:null,marker:n,offset:t};let s=readUint16(e,i);for(;!(s>=65472&&s<=65534);){if(++i>=r)return null;s=readUint16(e,i)}return{invalid:n.toString(16),marker:s,offset:i}}function prepareComponents(e){const t=Math.ceil(e.samplesPerLine/8/e.maxH),a=Math.ceil(e.scanLines/8/e.maxV);for(const r of e.components){const i=Math.ceil(Math.ceil(e.samplesPerLine/8)*r.h/e.maxH),n=Math.ceil(Math.ceil(e.scanLines/8)*r.v/e.maxV),s=t*r.h,o=64*(a*r.v)*(s+1);r.blockData=new Int16Array(o);r.blocksPerLine=i;r.blocksPerColumn=n}e.mcusPerLine=t;e.mcusPerColumn=a}function readDataBlock(e,t){const a=readUint16(e,t);let r=(t+=2)+a-2;const i=findNextFileMarker(e,r,t);if(i?.invalid){warn("readDataBlock - incorrect length, current marker is: "+i.invalid);r=i.offset}const n=e.subarray(t,r);return{appData:n,oldOffset:t,newOffset:t+n.length}}function skipData(e,t){const a=readUint16(e,t),r=(t+=2)+a-2,i=findNextFileMarker(e,r,t);return i?.invalid?i.offset:r}class JpegImage{constructor({decodeTransform:e=null,colorTransform:t=-1}={}){this._decodeTransform=e;this._colorTransform=t}static canUseImageDecoder(e,t=-1){let a=null,r=0,i=null,n=readUint16(e,r);r+=2;if(65496!==n)throw new JpegError("SOI not found");n=readUint16(e,r);r+=2;e:for(;65497!==n;){switch(n){case 65505:const{appData:t,oldOffset:s,newOffset:o}=readDataBlock(e,r);r=o;if(69===t[0]&&120===t[1]&&105===t[2]&&102===t[3]&&0===t[4]&&0===t[5]){if(a)throw new JpegError("Duplicate EXIF-blocks found.");a={exifStart:s+6,exifEnd:o}}n=readUint16(e,r);r+=2;continue;case 65472:case 65473:case 65474:i=e[r+7];break e;case 65535:255!==e[r]&&r--}r=skipData(e,r);n=readUint16(e,r);r+=2}return 4===i||3===i&&0===t?null:a||{}}parse(e,{dnlScanLines:t=null}={}){let a,r,i=0,n=null,s=null,o=0;const c=[],l=[],h=[];let u=readUint16(e,i);i+=2;if(65496!==u)throw new JpegError("SOI not found");u=readUint16(e,i);i+=2;e:for(;65497!==u;){let d,f,g;switch(u){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514:case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:const{appData:p,newOffset:m}=readDataBlock(e,i);i=m;65504===u&&74===p[0]&&70===p[1]&&73===p[2]&&70===p[3]&&0===p[4]&&(n={version:{major:p[5],minor:p[6]},densityUnits:p[7],xDensity:p[8]<<8|p[9],yDensity:p[10]<<8|p[11],thumbWidth:p[12],thumbHeight:p[13],thumbData:p.subarray(14,14+3*p[12]*p[13])});65518===u&&65===p[0]&&100===p[1]&&111===p[2]&&98===p[3]&&101===p[4]&&(s={version:p[5]<<8|p[6],flags0:p[7]<<8|p[8],flags1:p[9]<<8|p[10],transformCode:p[11]});break;case 65499:const b=readUint16(e,i);i+=2;const y=b+i-2;let w;for(;i>4){if(t>>4!=1)throw new JpegError("DQT - invalid table spec");for(f=0;f<64;f++){w=_a[f];a[w]=readUint16(e,i);i+=2}}else for(f=0;f<64;f++){w=_a[f];a[w]=e[i++]}c[15&t]=a}break;case 65472:case 65473:case 65474:if(a)throw new JpegError("Only single frame JPEGs supported");i+=2;a={};a.extended=65473===u;a.progressive=65474===u;a.precision=e[i++];const S=readUint16(e,i);i+=2;a.scanLines=t||S;a.samplesPerLine=readUint16(e,i);i+=2;a.components=[];a.componentIds={};const x=e[i++];let k=0,C=0;for(d=0;d>4,n=15&e[i+1];k>4?l:h)[15&t]=buildHuffmanTable(a,n)}break;case 65501:i+=2;r=readUint16(e,i);i+=2;break;case 65498:const F=1==++o&&!t;i+=2;const O=e[i++],T=[];for(d=0;d>4];n.huffmanTableAC=l[15&s];T.push(n)}const M=e[i++],D=e[i++],R=e[i++];try{i+=decodeScan(e,i,a,T,r,M,D,R>>4,15&R,F)}catch(t){if(t instanceof DNLMarkerError){warn(`${t.message} -- attempting to re-parse the JPEG image.`);return this.parse(e,{dnlScanLines:t.scanLines})}if(t instanceof EOIMarkerError){warn(`${t.message} -- ignoring the rest of the image data.`);break e}throw t}break;case 65500:i+=4;break;case 65535:255!==e[i]&&i--;break;default:const N=findNextFileMarker(e,i-2,i-3);if(N?.invalid){warn("JpegImage.parse - unexpected data, current marker is: "+N.invalid);i=N.offset;break}if(!N||i>=e.length-1){warn("JpegImage.parse - reached the end of the image data without finding an EOI marker (0xFFD9).");break e}throw new JpegError("JpegImage.parse - unknown marker: "+u.toString(16))}u=readUint16(e,i);i+=2}if(!a)throw new JpegError("JpegImage.parse - no frame data found.");this.width=a.samplesPerLine;this.height=a.scanLines;this.jfif=n;this.adobe=s;this.components=[];for(const e of a.components){const t=c[e.quantizationId];t&&(e.quantizationTable=t);this.components.push({index:e.index,output:buildComponentData(0,e),scaleX:e.h/a.maxH,scaleY:e.v/a.maxV,blocksPerLine:e.blocksPerLine,blocksPerColumn:e.blocksPerColumn})}this.numComponents=this.components.length}_getLinearizedBlockData(e,t,a=!1){const r=this.width/e,i=this.height/t;let n,s,o,c,l,h,u,d,f,g,p,m=0;const b=this.components.length,y=e*t*b,w=new Uint8ClampedArray(y),S=new Uint32Array(e),x=4294967288;let k;for(u=0;u>8)+C[f+1];return w}get _isColorConversionNeeded(){return this.adobe?!!this.adobe.transformCode:3===this.numComponents?0!==this._colorTransform&&(82!==this.components[0].index||71!==this.components[1].index||66!==this.components[2].index):1===this._colorTransform}_convertYccToRgb(e){let t,a,r;for(let i=0,n=e.length;i4)throw new JpegError("Unsupported color mode");const n=this._getLinearizedBlockData(e,t,i);if(1===this.numComponents&&(a||r)){const e=n.length*(a?4:3),t=new Uint8ClampedArray(e);let r=0;if(a)!function grayToRGBA(e,t){if(FeatureTest.isLittleEndian)for(let a=0,r=e.length;a0&&(e=e.subarray(t));break}return e}decodeImage(e){if(this.eof)return this.buffer;e=this.#B(e||this.bytes);const t=new JpegImage(this.jpegOptions);t.parse(e);const a=t.getData({width:this.drawWidth,height:this.drawHeight,forceRGBA:this.forceRGBA,forceRGB:this.forceRGB,isSourcePDF:!0});this.buffer=a;this.bufferLength=a.length;this.eof=!0;return this.buffer}get canAsyncDecodeImageFromBuffer(){return this.stream.isAsync}async getTransferableImage(){if(!await JpegStream.canUseImageDecoder)return null;const e=this.jpegOptions;if(e.decodeTransform)return null;let t;try{const a=this.canAsyncDecodeImageFromBuffer&&await this.stream.asyncGetBytes()||this.bytes;if(!a)return null;let r=this.#B(a);const i=JpegImage.canUseImageDecoder(r,e.colorTransform);if(!i)return null;if(i.exifStart){r=r.slice();r.fill(0,i.exifStart,i.exifEnd)}t=new ImageDecoder({data:r,type:"image/jpeg",preferAnimation:!1});return(await t.decode()).image}catch(e){warn(`getTransferableImage - failed: "${e}".`);return null}finally{t?.close()}}}var Ga,Va=(Ga=import.meta.url,async function(e={}){var t,a,r,i=e,n=new Promise(((e,r)=>{t=e;a=r})),s=Object.assign({},i),o="./this.program",quit_=(e,t)=>{throw t},c="";"undefined"!=typeof document&&document.currentScript&&(c=document.currentScript.src);Ga&&(c=Ga);c=c.startsWith("blob:")?"":c.slice(0,c.replace(/[?#].*/,"").lastIndexOf("/")+1);r=async e=>{var t=await fetch(e,{credentials:"same-origin"});if(t.ok)return t.arrayBuffer();throw new Error(t.status+" : "+t.url)};var l=i.print||console.log.bind(console),h=i.printErr||console.error.bind(console);Object.assign(i,s);s=null;i.arguments&&i.arguments;i.thisProgram&&(o=i.thisProgram);var u,d,f,g,p,m,b=i.wasmBinary,y=!1;function updateMemoryViews(){var e=u.buffer;i.HEAP8=f=new Int8Array(e);i.HEAP16=new Int16Array(e);i.HEAPU8=g=new Uint8Array(e);i.HEAPU16=new Uint16Array(e);i.HEAP32=p=new Int32Array(e);i.HEAPU32=m=new Uint32Array(e);i.HEAPF32=new Float32Array(e);i.HEAPF64=new Float64Array(e);i.HEAP64=new BigInt64Array(e);i.HEAPU64=new BigUint64Array(e)}var w,S=0,x=null;function abort(e){i.onAbort?.(e);h(e="Aborted("+e+")");y=!0;e+=". Build with -sASSERTIONS for more info.";var t=new WebAssembly.RuntimeError(e);a(t);throw t}function findWasmBinary(){return i.locateFile?function locateFile(e){return i.locateFile?i.locateFile(e,c):c+e}("openjpeg.wasm"):new URL("openjpeg.wasm",import.meta.url).href}async function getWasmBinary(e){if(!b)try{var t=await r(e);return new Uint8Array(t)}catch{}return function getBinarySync(e){if(e==w&&b)return new Uint8Array(b);throw"both async and sync fetching of the wasm failed"}(e)}async function instantiateAsync(e,t,a){if(!e&&"function"==typeof WebAssembly.instantiateStreaming)try{var r=fetch(t,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(r,a)}catch(e){h(`wasm streaming compile failed: ${e}`);h("falling back to ArrayBuffer instantiation")}return async function instantiateArrayBuffer(e,t){try{var a=await getWasmBinary(e);return await WebAssembly.instantiate(a,t)}catch(e){h(`failed to asynchronously prepare wasm: ${e}`);abort(e)}}(t,a)}class ExitStatus{name="ExitStatus";constructor(e){this.message=`Program terminated with exit(${e})`;this.status=e}}var callRuntimeCallbacks=e=>{for(;e.length>0;)e.shift()(i)},k=[],addOnPostRun=e=>k.unshift(e),C=[],addOnPreRun=e=>C.unshift(e),v=i.noExitRuntime||!0,F=0,O={},handleException=e=>{if(e instanceof ExitStatus||"unwind"==e)return d;quit_(0,e)},keepRuntimeAlive=()=>v||F>0,_proc_exit=e=>{d=e;if(!keepRuntimeAlive()){i.onExit?.(e);y=!0}quit_(0,new ExitStatus(e))},_exit=(e,t)=>{d=e;_proc_exit(e)},callUserCallback=e=>{if(!y)try{e();(()=>{if(!keepRuntimeAlive())try{_exit(d)}catch(e){handleException(e)}})()}catch(e){handleException(e)}},growMemory=e=>{var t=(e-u.buffer.byteLength+65535)/65536|0;try{u.grow(t);updateMemoryViews();return 1}catch(e){}},T={},getEnvStrings=()=>{if(!getEnvStrings.strings){var e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:o||"./this.program"};for(var t in T)void 0===T[t]?delete e[t]:e[t]=T[t];var a=[];for(var t in e)a.push(`${t}=${e[t]}`);getEnvStrings.strings=a}return getEnvStrings.strings},M=[null,[],[]],D="undefined"!=typeof TextDecoder?new TextDecoder:void 0,UTF8ArrayToString=(e,t=0,a=NaN)=>{for(var r=t+a,i=t;e[i]&&!(i>=r);)++i;if(i-t>16&&e.buffer&&D)return D.decode(e.subarray(t,i));for(var n="";t>10,56320|1023&l)}}else n+=String.fromCharCode((31&s)<<6|o)}else n+=String.fromCharCode(s)}return n},printChar=(e,t)=>{var a=M[e];if(0===t||10===t){(1===e?l:h)(UTF8ArrayToString(a));a.length=0}else a.push(t)},UTF8ToString=(e,t)=>e?UTF8ArrayToString(g,e,t):"",R={l:()=>abort(""),k:()=>{v=!1;F=0},m:(e,t)=>{if(O[e]){clearTimeout(O[e].id);delete O[e]}if(!t)return 0;var a=setTimeout((()=>{delete O[e];callUserCallback((()=>E(e,performance.now())))}),t);O[e]={id:a,timeout_ms:t};return 0},g:function _copy_pixels_1(e,t){e>>=2;const a=i.imageData=new Uint8ClampedArray(t),r=p.subarray(e,e+t);a.set(r)},f:function _copy_pixels_3(e,t,a,r){e>>=2;t>>=2;a>>=2;const n=i.imageData=new Uint8ClampedArray(3*r),s=p.subarray(e,e+r),o=p.subarray(t,t+r),c=p.subarray(a,a+r);for(let e=0;e>=2;t>>=2;a>>=2;r>>=2;const s=i.imageData=new Uint8ClampedArray(4*n),o=p.subarray(e,e+n),c=p.subarray(t,t+n),l=p.subarray(a,a+n),h=p.subarray(r,r+n);for(let e=0;e{var t,a,r=g.length,i=2147483648;if((e>>>=0)>i)return!1;for(var n=1;n<=4;n*=2){var s=r*(1+.2/n);s=Math.min(s,e+100663296);var o=Math.min(i,(t=Math.max(e,s),a=65536,Math.ceil(t/a)*a));if(growMemory(o))return!0}return!1},p:(e,t)=>{var a=0;getEnvStrings().forEach(((r,i)=>{var n=t+a;m[e+4*i>>2]=n;((e,t)=>{for(var a=0;a{var a=getEnvStrings();m[e>>2]=a.length;var r=0;a.forEach((e=>r+=e.length+1));m[t>>2]=r;return 0},b:e=>52,o:function _fd_seek(e,t,a,r){t=(i=t)<-9007199254740992||i>9007199254740992?NaN:Number(i);var i;return 70},c:(e,t,a,r)=>{for(var i=0,n=0;n>2],o=m[t+4>>2];t+=8;for(var c=0;c>2]=i;return 0},r:function _gray_to_rgba(e,t){e>>=2;const a=i.imageData=new Uint8ClampedArray(4*t),r=p.subarray(e,e+t);for(let e=0;e>=2;t>>=2;const r=i.imageData=new Uint8ClampedArray(4*a),n=p.subarray(e,e+a),s=p.subarray(t,t+a);for(let e=0;e>=2;t>>=2;a>>=2;const n=i.imageData=new Uint8ClampedArray(4*r),s=p.subarray(e,e+r),o=p.subarray(t,t+r),c=p.subarray(a,a+r);for(let e=0;e{i.instantiateWasm(e,((e,a)=>{receiveInstance(e);t(e.exports)}))}));w??=findWasmBinary();try{var t=function receiveInstantiationResult(e){return receiveInstance(e.instance)}(await instantiateAsync(b,w,e));return t}catch(e){a(e);return Promise.reject(e)}}(),E=(N.t,i._malloc=N.u,i._free=N.v,i._jp2_decode=N.x,N.y);if(i.preInit){"function"==typeof i.preInit&&(i.preInit=[i.preInit]);for(;i.preInit.length>0;)i.preInit.pop()()}!function run(){if(S>0)x=run;else{!function preRun(){if(i.preRun){"function"==typeof i.preRun&&(i.preRun=[i.preRun]);for(;i.preRun.length;)addOnPreRun(i.preRun.shift())}callRuntimeCallbacks(C)}();if(S>0)x=run;else if(i.setStatus){i.setStatus("Running...");setTimeout((()=>{setTimeout((()=>i.setStatus("")),1);doRun()}),1)}else doRun()}function doRun(){i.calledRun=!0;if(!y){!function initRuntime(){N.t()}();t(i);i.onRuntimeInitialized?.();!function postRun(){if(i.postRun){"function"==typeof i.postRun&&(i.postRun=[i.postRun]);for(;i.postRun.length;)addOnPostRun(i.postRun.shift())}callRuntimeCallbacks(k)}()}}}();return n});const Ka=Va;class JpxError extends fa{constructor(e){super(e,"JpxError")}}class JpxImage{static#R=null;static#N=null;static#E=null;static#k=!0;static#P=!0;static#C=null;static setOptions({handler:e,useWasm:t,useWorkerFetch:a,wasmUrl:r}){this.#k=t;this.#P=a;this.#C=r;a||(this.#N=e)}static async#L(e){const t=`${this.#C}openjpeg_nowasm_fallback.js`;let a=null;try{a=(await import(t)).default()}catch(e){warn(`JpxImage#getJsModule: ${e}`)}e(a)}static async#_(e,t,a){const r="openjpeg.wasm";try{this.#R||(this.#P?this.#R=await fetchBinaryData(`${this.#C}${r}`):this.#R=await this.#N.sendWithPromise("FetchBinaryData",{type:"wasmFactory",filename:r}));return a((await WebAssembly.instantiate(this.#R,t)).instance)}catch(t){warn(`JpxImage#instantiateWasm: ${t}`);this.#L(e);return null}finally{this.#N=null}}static async decode(e,{numComponents:t=4,isIndexedColormap:a=!1,smaskInData:r=!1}={}){if(!this.#E){const{promise:e,resolve:t}=Promise.withResolvers(),a=[e];this.#k?a.push(Ka({warn,instantiateWasm:this.#_.bind(this,t)})):this.#L(t);this.#E=Promise.race(a)}const i=await this.#E;if(!i)throw new JpxError("OpenJPEG failed to initialize");let n;try{const s=e.length;n=i._malloc(s);i.HEAPU8.set(e,n);if(i._jp2_decode(n,s,t>0?t:0,!!a,!!r)){const{errorMessages:e}=i;if(e){delete i.errorMessages;throw new JpxError(e)}throw new JpxError("Unknown error")}const{imageData:o}=i;i.imageData=null;return o}finally{n&&i._free(n)}}static cleanup(){this.#E=null}static parseImageProperties(e){let t=e.getByte();for(;t>=0;){const a=t;t=e.getByte();if(65361===(a<<8|t)){e.skip(4);const t=e.getInt32()>>>0,a=e.getInt32()>>>0,r=e.getInt32()>>>0,i=e.getInt32()>>>0;e.skip(16);return{width:t-r,height:a-i,bitsPerComponent:8,componentsCount:e.getUint16()}}}throw new JpxError("No size marker found in JPX stream")}}function addState(e,t,a,r,i){let n=e;for(let e=0,a=t.length-1;e1e3){l=Math.max(l,d);f+=u+2;d=0;u=0}h.push({transform:t,x:d,y:f,w:a.width,h:a.height});d+=a.width+2;u=Math.max(u,a.height)}const g=Math.max(l,d)+1,p=f+u+1,m=new Uint8Array(g*p*4),b=g<<2;for(let e=0;e=0;){t[n-4]=t[n];t[n-3]=t[n+1];t[n-2]=t[n+2];t[n-1]=t[n+3];t[n+a]=t[n+a-4];t[n+a+1]=t[n+a-3];t[n+a+2]=t[n+a-2];t[n+a+3]=t[n+a-1];n-=b}}const y={width:g,height:p};if(e.isOffscreenCanvasSupported){const e=new OffscreenCanvas(g,p);e.getContext("2d").putImageData(new ImageData(new Uint8ClampedArray(m.buffer),g,p),0,0);y.bitmap=e.transferToImageBitmap();y.data=null}else{y.kind=v;y.data=m}a.splice(n,4*c,Zt);r.splice(n,4*c,[y,h]);return n+1}));addState(Ja,[Be,Ne,Vt,Re],null,(function iterateImageMaskGroup(e,t){const a=e.fnArray,r=(t-(e.iCurr-3))%4;switch(r){case 0:return a[t]===Be;case 1:return a[t]===Ne;case 2:return a[t]===Vt;case 3:return a[t]===Re}throw new Error(`iterateImageMaskGroup - invalid pos: ${r}`)}),(function foundImageMaskGroup(e,t){const a=e.fnArray,r=e.argsArray,i=e.iCurr,n=i-3,s=i-2,o=i-1;let c=Math.floor((t-n)/4);if(c<10)return t-(t-n)%4;let l,h,u=!1;const d=r[o][0],f=r[s][0],g=r[s][1],p=r[s][2],m=r[s][3];if(g===p){u=!0;l=s+4;let e=o+4;for(let t=1;t=4&&a[n-4]===a[s]&&a[n-3]===a[o]&&a[n-2]===a[c]&&a[n-1]===a[l]&&r[n-4][0]===h&&r[n-4][1]===u){d++;f-=5}let g=f+4;for(let e=1;e{const t=e.argsArray,a=t[e.iCurr-1][0];if(a!==qe&&a!==He&&a!==$e&&a!==Ge&&a!==Ve&&a!==Ke)return!0;const r=t[e.iCurr-2];return 1===r[0]&&0===r[1]&&0===r[2]&&1===r[3]}),(()=>!1),((e,t)=>{const{fnArray:a,argsArray:r}=e,i=e.iCurr,n=i-3,s=i-2,o=r[i-1],c=r[s],[,[l],h]=o;Util.scaleMinMax(c,h);for(let e=0,t=l.length;e=a)break}r=(r||Ja)[e[t]];if(r&&!Array.isArray(r)){n.iCurr=t;t++;if(!r.checkFn||(0,r.checkFn)(n)){i=r;r=null}else r=null}else t++}this.state=r;this.match=i;this.lastProcessed=t}flush(){for(;this.match;){const e=this.queue.fnArray.length;this.lastProcessed=(0,this.match.processFn)(this.context,e);this.match=null;this.state=null;this._optimize()}}reset(){this.state=null;this.match=null;this.lastProcessed=0}}class OperatorList{static CHUNK_SIZE=1e3;static CHUNK_SIZE_ABOUT=this.CHUNK_SIZE-5;static isOffscreenCanvasSupported=!1;constructor(e=0,t){this._streamSink=t;this.fnArray=[];this.argsArray=[];this.optimizer=!t||e&d?new NullOptimizer(this):new QueueOptimizer(this);this.dependencies=new Set;this._totalLength=0;this.weight=0;this._resolved=t?null:Promise.resolve()}static setOptions({isOffscreenCanvasSupported:e}){this.isOffscreenCanvasSupported=e}get length(){return this.argsArray.length}get ready(){return this._resolved||this._streamSink.ready}get totalLength(){return this._totalLength+this.length}addOp(e,t){this.optimizer.push(e,t);this.weight++;this._streamSink&&(this.weight>=OperatorList.CHUNK_SIZE||this.weight>=OperatorList.CHUNK_SIZE_ABOUT&&(e===Re||e===et))&&this.flush()}addImageOps(e,t,a,r=!1){if(r){this.addOp(Be);this.addOp(De,[[["SMask",!1]]])}void 0!==a&&this.addOp(_t,["OC",a]);this.addOp(e,t);void 0!==a&&this.addOp(jt,[]);r&&this.addOp(Re)}addDependency(e){if(!this.dependencies.has(e)){this.dependencies.add(e);this.addOp(ke,[e])}}addDependencies(e){for(const t of e)this.addDependency(t)}addOpList(e){if(e instanceof OperatorList){for(const t of e.dependencies)this.dependencies.add(t);for(let t=0,a=e.length;t>>0}function hexToStr(e,t){return 1===t?String.fromCharCode(e[0],e[1]):3===t?String.fromCharCode(e[0],e[1],e[2],e[3]):String.fromCharCode(...e.subarray(0,t+1))}function addHex(e,t,a){let r=0;for(let i=a;i>=0;i--){r+=e[i]+t[i];e[i]=255&r;r>>=8}}function incHex(e,t){let a=1;for(let r=t;r>=0&&a>0;r--){a+=e[r];e[r]=255&a;a>>=8}}const Ya=16;class BinaryCMapStream{constructor(e){this.buffer=e;this.pos=0;this.end=e.length;this.tmpBuf=new Uint8Array(19)}readByte(){return this.pos>=this.end?-1:this.buffer[this.pos++]}readNumber(){let e,t=0;do{const a=this.readByte();if(a<0)throw new FormatError("unexpected EOF in bcmap");e=!(128&a);t=t<<7|127&a}while(!e);return t}readSigned(){const e=this.readNumber();return 1&e?~(e>>>1):e>>>1}readHex(e,t){e.set(this.buffer.subarray(this.pos,this.pos+t+1));this.pos+=t+1}readHexNumber(e,t){let a;const r=this.tmpBuf;let i=0;do{const e=this.readByte();if(e<0)throw new FormatError("unexpected EOF in bcmap");a=!(128&e);r[i++]=127&e}while(!a);let n=t,s=0,o=0;for(;n>=0;){for(;o<8&&r.length>0;){s|=r[--i]<>=8;o-=8}}readHexSigned(e,t){this.readHexNumber(e,t);const a=1&e[t]?255:0;let r=0;for(let i=0;i<=t;i++){r=(1&r)<<8|e[i];e[i]=r>>1^a}}readString(){const e=this.readNumber(),t=new Array(e);for(let a=0;a=0;){const e=d>>5;if(7===e){switch(31&d){case 0:r.readString();break;case 1:n=r.readString()}continue}const a=!!(16&d),i=15&d;if(i+1>Ya)throw new Error("BinaryCMapReader.process: Invalid dataSize.");const f=1,g=r.readNumber();switch(e){case 0:r.readHex(s,i);r.readHexNumber(o,i);addHex(o,s,i);t.addCodespaceRange(i+1,hexToInt(s,i),hexToInt(o,i));for(let e=1;e=0;--i){r[a+i]=255&s;s>>=8}}}}class AsciiHexStream extends DecodeStream{constructor(e,t){t&&(t*=.5);super(t);this.str=e;this.dict=e.dict;this.firstDigit=-1}readBlock(){const e=this.str.getBytes(8e3);if(!e.length){this.eof=!0;return}const t=e.length+1>>1,a=this.ensureBuffer(this.bufferLength+t);let r=this.bufferLength,i=this.firstDigit;for(const t of e){let e;if(t>=48&&t<=57)e=15&t;else{if(!(t>=65&&t<=70||t>=97&&t<=102)){if(62===t){this.eof=!0;break}continue}e=9+(15&t)}if(i<0)i=e;else{a[r++]=i<<4|e;i=-1}}if(i>=0&&this.eof){a[r++]=i<<4;i=-1}this.firstDigit=i;this.bufferLength=r}}const Za=-1,Qa=[[-1,-1],[-1,-1],[7,8],[7,7],[6,6],[6,6],[6,5],[6,5],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2]],er=[[-1,-1],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[12,1984],[12,2048],[12,2112],[12,2176],[12,2240],[12,2304],[11,1856],[11,1856],[11,1920],[11,1920],[12,2368],[12,2432],[12,2496],[12,2560]],tr=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[8,29],[8,29],[8,30],[8,30],[8,45],[8,45],[8,46],[8,46],[7,22],[7,22],[7,22],[7,22],[7,23],[7,23],[7,23],[7,23],[8,47],[8,47],[8,48],[8,48],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[7,20],[7,20],[7,20],[7,20],[8,33],[8,33],[8,34],[8,34],[8,35],[8,35],[8,36],[8,36],[8,37],[8,37],[8,38],[8,38],[7,19],[7,19],[7,19],[7,19],[8,31],[8,31],[8,32],[8,32],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[8,53],[8,53],[8,54],[8,54],[7,26],[7,26],[7,26],[7,26],[8,39],[8,39],[8,40],[8,40],[8,41],[8,41],[8,42],[8,42],[8,43],[8,43],[8,44],[8,44],[7,21],[7,21],[7,21],[7,21],[7,28],[7,28],[7,28],[7,28],[8,61],[8,61],[8,62],[8,62],[8,63],[8,63],[8,0],[8,0],[8,320],[8,320],[8,384],[8,384],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[7,27],[7,27],[7,27],[7,27],[8,59],[8,59],[8,60],[8,60],[9,1472],[9,1536],[9,1600],[9,1728],[7,18],[7,18],[7,18],[7,18],[7,24],[7,24],[7,24],[7,24],[8,49],[8,49],[8,50],[8,50],[8,51],[8,51],[8,52],[8,52],[7,25],[7,25],[7,25],[7,25],[8,55],[8,55],[8,56],[8,56],[8,57],[8,57],[8,58],[8,58],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[8,448],[8,448],[8,512],[8,512],[9,704],[9,768],[8,640],[8,640],[8,576],[8,576],[9,832],[9,896],[9,960],[9,1024],[9,1088],[9,1152],[9,1216],[9,1280],[9,1344],[9,1408],[7,256],[7,256],[7,256],[7,256],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7]],ar=[[-1,-1],[-1,-1],[12,-2],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[11,1792],[11,1792],[12,1984],[12,1984],[12,2048],[12,2048],[12,2112],[12,2112],[12,2176],[12,2176],[12,2240],[12,2240],[12,2304],[12,2304],[11,1856],[11,1856],[11,1856],[11,1856],[11,1920],[11,1920],[11,1920],[11,1920],[12,2368],[12,2368],[12,2432],[12,2432],[12,2496],[12,2496],[12,2560],[12,2560],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[12,52],[12,52],[13,640],[13,704],[13,768],[13,832],[12,55],[12,55],[12,56],[12,56],[13,1280],[13,1344],[13,1408],[13,1472],[12,59],[12,59],[12,60],[12,60],[13,1536],[13,1600],[11,24],[11,24],[11,24],[11,24],[11,25],[11,25],[11,25],[11,25],[13,1664],[13,1728],[12,320],[12,320],[12,384],[12,384],[12,448],[12,448],[13,512],[13,576],[12,53],[12,53],[12,54],[12,54],[13,896],[13,960],[13,1024],[13,1088],[13,1152],[13,1216],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64]],rr=[[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[11,23],[11,23],[12,50],[12,51],[12,44],[12,45],[12,46],[12,47],[12,57],[12,58],[12,61],[12,256],[10,16],[10,16],[10,16],[10,16],[10,17],[10,17],[10,17],[10,17],[12,48],[12,49],[12,62],[12,63],[12,30],[12,31],[12,32],[12,33],[12,40],[12,41],[11,22],[11,22],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[12,128],[12,192],[12,26],[12,27],[12,28],[12,29],[11,19],[11,19],[11,20],[11,20],[12,34],[12,35],[12,36],[12,37],[12,38],[12,39],[11,21],[11,21],[12,42],[12,43],[10,0],[10,0],[10,0],[10,0],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12]],ir=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[6,9],[6,8],[5,7],[5,7],[4,6],[4,6],[4,6],[4,6],[4,5],[4,5],[4,5],[4,5],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2]];class CCITTFaxDecoder{constructor(e,t={}){if("function"!=typeof e?.next)throw new Error('CCITTFaxDecoder - invalid "source" parameter.');this.source=e;this.eof=!1;this.encoding=t.K||0;this.eoline=t.EndOfLine||!1;this.byteAlign=t.EncodedByteAlign||!1;this.columns=t.Columns||1728;this.rows=t.Rows||0;this.eoblock=t.EndOfBlock??!0;this.black=t.BlackIs1||!1;this.codingLine=new Uint32Array(this.columns+1);this.refLine=new Uint32Array(this.columns+2);this.codingLine[0]=this.columns;this.codingPos=0;this.row=0;this.nextLine2D=this.encoding<0;this.inputBits=0;this.inputBuf=0;this.outputBits=0;this.rowsDone=!1;let a;for(;0===(a=this._lookBits(12));)this._eatBits(1);1===a&&this._eatBits(12);if(this.encoding>0){this.nextLine2D=!this._lookBits(1);this._eatBits(1)}}readNextChar(){if(this.eof)return-1;const e=this.refLine,t=this.codingLine,a=this.columns;let r,i,n,s,o;if(0===this.outputBits){this.rowsDone&&(this.eof=!0);if(this.eof)return-1;this.err=!1;let n,o,c;if(this.nextLine2D){for(s=0;t[s]=64);do{o+=c=this._getWhiteCode()}while(c>=64)}else{do{n+=c=this._getWhiteCode()}while(c>=64);do{o+=c=this._getBlackCode()}while(c>=64)}this._addPixels(t[this.codingPos]+n,i);t[this.codingPos]0?--r:++r;for(;e[r]<=t[this.codingPos]&&e[r]0?--r:++r;for(;e[r]<=t[this.codingPos]&&e[r]0?--r:++r;for(;e[r]<=t[this.codingPos]&&e[r]=64);else do{n+=c=this._getWhiteCode()}while(c>=64);this._addPixels(t[this.codingPos]+n,i);i^=1}}let l=!1;this.byteAlign&&(this.inputBits&=-8);if(this.eoblock||this.row!==this.rows-1){n=this._lookBits(12);if(this.eoline)for(;n!==Za&&1!==n;){this._eatBits(1);n=this._lookBits(12)}else for(;0===n;){this._eatBits(1);n=this._lookBits(12)}if(1===n){this._eatBits(12);l=!0}else n===Za&&(this.eof=!0)}else this.rowsDone=!0;if(!this.eof&&this.encoding>0&&!this.rowsDone){this.nextLine2D=!this._lookBits(1);this._eatBits(1)}if(this.eoblock&&l&&this.byteAlign){n=this._lookBits(12);if(1===n){this._eatBits(12);if(this.encoding>0){this._lookBits(1);this._eatBits(1)}if(this.encoding>=0)for(s=0;s<4;++s){n=this._lookBits(12);1!==n&&info("bad rtc code: "+n);this._eatBits(12);if(this.encoding>0){this._lookBits(1);this._eatBits(1)}}this.eof=!0}}else if(this.err&&this.eoline){for(;;){n=this._lookBits(13);if(n===Za){this.eof=!0;return-1}if(n>>1==1)break;this._eatBits(1)}this._eatBits(12);if(this.encoding>0){this._eatBits(1);this.nextLine2D=!(1&n)}}this.outputBits=t[0]>0?t[this.codingPos=0]:t[this.codingPos=1];this.row++}if(this.outputBits>=8){o=1&this.codingPos?0:255;this.outputBits-=8;if(0===this.outputBits&&t[this.codingPos]n){o<<=n;1&this.codingPos||(o|=255>>8-n);this.outputBits-=n;n=0}else{o<<=this.outputBits;1&this.codingPos||(o|=255>>8-this.outputBits);n-=this.outputBits;this.outputBits=0;if(t[this.codingPos]0){o<<=n;n=0}}}while(n)}this.black&&(o^=255);return o}_addPixels(e,t){const a=this.codingLine;let r=this.codingPos;if(e>a[r]){if(e>this.columns){info("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}this.codingPos=r}_addPixelsNeg(e,t){const a=this.codingLine;let r=this.codingPos;if(e>a[r]){if(e>this.columns){info("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}else if(e0&&e=i){const t=a[e-i];if(t[0]===r){this._eatBits(r);return[!0,t[1],!0]}}}return[!1,0,!1]}_getTwoDimCode(){let e,t=0;if(this.eoblock){t=this._lookBits(7);e=Qa[t];if(e?.[0]>0){this._eatBits(e[0]);return e[1]}}else{const e=this._findTableCode(1,7,Qa);if(e[0]&&e[2])return e[1]}info("Bad two dim code");return Za}_getWhiteCode(){let e,t=0;if(this.eoblock){t=this._lookBits(12);if(t===Za)return 1;e=t>>5?tr[t>>3]:er[t];if(e[0]>0){this._eatBits(e[0]);return e[1]}}else{let e=this._findTableCode(1,9,tr);if(e[0])return e[1];e=this._findTableCode(11,12,er);if(e[0])return e[1]}info("bad white code");this._eatBits(1);return 1}_getBlackCode(){let e,t;if(this.eoblock){e=this._lookBits(13);if(e===Za)return 1;t=e>>7?!(e>>9)&&e>>7?rr[(e>>1)-64]:ir[e>>7]:ar[e];if(t[0]>0){this._eatBits(t[0]);return t[1]}}else{let e=this._findTableCode(2,6,ir);if(e[0])return e[1];e=this._findTableCode(7,12,rr,64);if(e[0])return e[1];e=this._findTableCode(10,13,ar);if(e[0])return e[1]}info("bad black code");this._eatBits(1);return 1}_lookBits(e){let t;for(;this.inputBits>16-e;this.inputBuf=this.inputBuf<<8|t;this.inputBits+=8}return this.inputBuf>>this.inputBits-e&65535>>16-e}_eatBits(e){(this.inputBits-=e)<0&&(this.inputBits=0)}}class CCITTFaxStream extends DecodeStream{constructor(e,t,a){super(t);this.str=e;this.dict=e.dict;a instanceof Dict||(a=Dict.empty);const r={next:()=>e.getByte()};this.ccittFaxDecoder=new CCITTFaxDecoder(r,{K:a.get("K"),EndOfLine:a.get("EndOfLine"),EncodedByteAlign:a.get("EncodedByteAlign"),Columns:a.get("Columns"),Rows:a.get("Rows"),EndOfBlock:a.get("EndOfBlock"),BlackIs1:a.get("BlackIs1")})}readBlock(){for(;!this.eof;){const e=this.ccittFaxDecoder.readNextChar();if(-1===e){this.eof=!0;return}this.ensureBuffer(this.bufferLength+1);this.buffer[this.bufferLength++]=e}}}const nr=new Int32Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),sr=new Int32Array([3,4,5,6,7,8,9,10,65547,65549,65551,65553,131091,131095,131099,131103,196643,196651,196659,196667,262211,262227,262243,262259,327811,327843,327875,327907,258,258,258]),or=new Int32Array([1,2,3,4,65541,65543,131081,131085,196625,196633,262177,262193,327745,327777,393345,393409,459009,459137,524801,525057,590849,591361,657409,658433,724993,727041,794625,798721,868353,876545]),cr=[new Int32Array([459008,524368,524304,524568,459024,524400,524336,590016,459016,524384,524320,589984,524288,524416,524352,590048,459012,524376,524312,589968,459028,524408,524344,590032,459020,524392,524328,59e4,524296,524424,524360,590064,459010,524372,524308,524572,459026,524404,524340,590024,459018,524388,524324,589992,524292,524420,524356,590056,459014,524380,524316,589976,459030,524412,524348,590040,459022,524396,524332,590008,524300,524428,524364,590072,459009,524370,524306,524570,459025,524402,524338,590020,459017,524386,524322,589988,524290,524418,524354,590052,459013,524378,524314,589972,459029,524410,524346,590036,459021,524394,524330,590004,524298,524426,524362,590068,459011,524374,524310,524574,459027,524406,524342,590028,459019,524390,524326,589996,524294,524422,524358,590060,459015,524382,524318,589980,459031,524414,524350,590044,459023,524398,524334,590012,524302,524430,524366,590076,459008,524369,524305,524569,459024,524401,524337,590018,459016,524385,524321,589986,524289,524417,524353,590050,459012,524377,524313,589970,459028,524409,524345,590034,459020,524393,524329,590002,524297,524425,524361,590066,459010,524373,524309,524573,459026,524405,524341,590026,459018,524389,524325,589994,524293,524421,524357,590058,459014,524381,524317,589978,459030,524413,524349,590042,459022,524397,524333,590010,524301,524429,524365,590074,459009,524371,524307,524571,459025,524403,524339,590022,459017,524387,524323,589990,524291,524419,524355,590054,459013,524379,524315,589974,459029,524411,524347,590038,459021,524395,524331,590006,524299,524427,524363,590070,459011,524375,524311,524575,459027,524407,524343,590030,459019,524391,524327,589998,524295,524423,524359,590062,459015,524383,524319,589982,459031,524415,524351,590046,459023,524399,524335,590014,524303,524431,524367,590078,459008,524368,524304,524568,459024,524400,524336,590017,459016,524384,524320,589985,524288,524416,524352,590049,459012,524376,524312,589969,459028,524408,524344,590033,459020,524392,524328,590001,524296,524424,524360,590065,459010,524372,524308,524572,459026,524404,524340,590025,459018,524388,524324,589993,524292,524420,524356,590057,459014,524380,524316,589977,459030,524412,524348,590041,459022,524396,524332,590009,524300,524428,524364,590073,459009,524370,524306,524570,459025,524402,524338,590021,459017,524386,524322,589989,524290,524418,524354,590053,459013,524378,524314,589973,459029,524410,524346,590037,459021,524394,524330,590005,524298,524426,524362,590069,459011,524374,524310,524574,459027,524406,524342,590029,459019,524390,524326,589997,524294,524422,524358,590061,459015,524382,524318,589981,459031,524414,524350,590045,459023,524398,524334,590013,524302,524430,524366,590077,459008,524369,524305,524569,459024,524401,524337,590019,459016,524385,524321,589987,524289,524417,524353,590051,459012,524377,524313,589971,459028,524409,524345,590035,459020,524393,524329,590003,524297,524425,524361,590067,459010,524373,524309,524573,459026,524405,524341,590027,459018,524389,524325,589995,524293,524421,524357,590059,459014,524381,524317,589979,459030,524413,524349,590043,459022,524397,524333,590011,524301,524429,524365,590075,459009,524371,524307,524571,459025,524403,524339,590023,459017,524387,524323,589991,524291,524419,524355,590055,459013,524379,524315,589975,459029,524411,524347,590039,459021,524395,524331,590007,524299,524427,524363,590071,459011,524375,524311,524575,459027,524407,524343,590031,459019,524391,524327,589999,524295,524423,524359,590063,459015,524383,524319,589983,459031,524415,524351,590047,459023,524399,524335,590015,524303,524431,524367,590079]),9],lr=[new Int32Array([327680,327696,327688,327704,327684,327700,327692,327708,327682,327698,327690,327706,327686,327702,327694,0,327681,327697,327689,327705,327685,327701,327693,327709,327683,327699,327691,327707,327687,327703,327695,0]),5];class FlateStream extends DecodeStream{constructor(e,t){super(t);this.str=e;this.dict=e.dict;const a=e.getByte(),r=e.getByte();if(-1===a||-1===r)throw new FormatError(`Invalid header in flate stream: ${a}, ${r}`);if(8!=(15&a))throw new FormatError(`Unknown compression method in flate stream: ${a}, ${r}`);if(((a<<8)+r)%31!=0)throw new FormatError(`Bad FCHECK in flate stream: ${a}, ${r}`);if(32&r)throw new FormatError(`FDICT bit set in flate stream: ${a}, ${r}`);this.codeSize=0;this.codeBuf=0}async getImageData(e,t){const a=await this.asyncGetBytes();return a?.subarray(0,e)||this.getBytes(e)}async asyncGetBytes(){this.str.reset();const e=this.str.getBytes();try{const{readable:t,writable:a}=new DecompressionStream("deflate"),r=a.getWriter();await r.ready;r.write(e).then((async()=>{await r.ready;await r.close()})).catch((()=>{}));const i=[];let n=0;for await(const e of t){i.push(e);n+=e.byteLength}const s=new Uint8Array(n);let o=0;for(const e of i){s.set(e,o);o+=e.byteLength}return s}catch{this.str=new Stream(e,2,e.length,this.str.dict);this.reset();return null}}get isAsync(){return!0}getBits(e){const t=this.str;let a,r=this.codeSize,i=this.codeBuf;for(;r>e;this.codeSize=r-=e;return a}getCode(e){const t=this.str,a=e[0],r=e[1];let i,n=this.codeSize,s=this.codeBuf;for(;n>16,l=65535&o;if(c<1||n>c;this.codeSize=n-c;return l}generateHuffmanTable(e){const t=e.length;let a,r=0;for(a=0;ar&&(r=e[a]);const i=1<>=1}for(a=e;a>=1;if(0===t){let t;if(-1===(t=r.getByte())){this.#j("Bad block header in flate stream");return}let a=t;if(-1===(t=r.getByte())){this.#j("Bad block header in flate stream");return}a|=t<<8;if(-1===(t=r.getByte())){this.#j("Bad block header in flate stream");return}let i=t;if(-1===(t=r.getByte())){this.#j("Bad block header in flate stream");return}i|=t<<8;if(i!==(65535&~a)&&(0!==a||0!==i))throw new FormatError("Bad uncompressed block length in flate stream");this.codeBuf=0;this.codeSize=0;const n=this.bufferLength,s=n+a;e=this.ensureBuffer(s);this.bufferLength=s;if(0===a)-1===r.peekByte()&&(this.eof=!0);else{const t=r.getBytes(a);e.set(t,n);t.length0;)h[o++]=f}i=this.generateHuffmanTable(h.subarray(0,e));n=this.generateHuffmanTable(h.subarray(e,l))}}e=this.buffer;let s=e?e.length:0,o=this.bufferLength;for(;;){let t=this.getCode(i);if(t<256){if(o+1>=s){e=this.ensureBuffer(o+1);s=e.length}e[o++]=t;continue}if(256===t){this.bufferLength=o;return}t-=257;t=sr[t];let r=t>>16;r>0&&(r=this.getBits(r));a=(65535&t)+r;t=this.getCode(n);t=or[t];r=t>>16;r>0&&(r=this.getBits(r));const c=(65535&t)+r;if(o+a>=s){e=this.ensureBuffer(o+a);s=e.length}for(let t=0;t>9&127;this.clow=this.clow<<7&65535;this.ct-=7;this.a=32768}byteIn(){const e=this.data;let t=this.bp;if(255===e[t])if(e[t+1]>143){this.clow+=65280;this.ct=8}else{t++;this.clow+=e[t]<<9;this.ct=7;this.bp=t}else{t++;this.clow+=t65535){this.chigh+=this.clow>>16;this.clow&=65535}}readBit(e,t){let a=e[t]>>1,r=1&e[t];const i=hr[a],n=i.qe;let s,o=this.a-n;if(this.chigh>15&1;this.clow=this.clow<<1&65535;this.ct--}while(!(32768&o));this.a=o;e[t]=a<<1|r;return s}}class Jbig2Error extends fa{constructor(e){super(e,"Jbig2Error")}}class ContextCache{getContexts(e){return e in this?this[e]:this[e]=new Int8Array(65536)}}class DecodingContext{constructor(e,t,a){this.data=e;this.start=t;this.end=a}get decoder(){return shadow(this,"decoder",new ArithmeticDecoder(this.data,this.start,this.end))}get contextCache(){return shadow(this,"contextCache",new ContextCache)}}function decodeInteger(e,t,a){const r=e.getContexts(t);let i=1;function readBits(e){let t=0;for(let n=0;n>>0}const n=readBits(1),s=readBits(1)?readBits(1)?readBits(1)?readBits(1)?readBits(1)?readBits(32)+4436:readBits(12)+340:readBits(8)+84:readBits(6)+20:readBits(4)+4:readBits(2);let o;0===n?o=s:s>0&&(o=-s);return o>=-2147483648&&o<=va?o:null}function decodeIAID(e,t,a){const r=e.getContexts("IAID");let i=1;for(let e=0;ee.y-t.y||e.x-t.x));const h=l.length,u=new Int8Array(h),d=new Int8Array(h),f=[];let g,p,m=0,b=0,y=0,w=0;for(p=0;p=v&&E=F){q=q<<1&m;for(p=0;p=0&&_=0){j=D[L][_];j&&(q|=j<=e?l<<=1:l=l<<1|x[o][c]}for(f=0;f=w||c<0||c>=y?l<<=1:l=l<<1|r[o][c]}const g=k.readBit(C,l);t[s]=g}}return x}function decodeTextRegion(e,t,a,r,i,n,s,o,c,l,h,u,d,f,g,p,m,b,y){if(e&&t)throw new Jbig2Error("refinement with Huffman is not supported");const w=[];let S,x;for(S=0;S1&&(i=e?y.readBits(b):decodeInteger(C,"IAIT",k));const n=s*v+i,F=e?f.symbolIDTable.decode(y):decodeIAID(C,k,c),O=t&&(e?y.readBit():decodeInteger(C,"IARI",k));let T=o[F],M=T[0].length,D=T.length;if(O){const e=decodeInteger(C,"IARDW",k),t=decodeInteger(C,"IARDH",k);M+=e;D+=t;T=decodeRefinement(M,D,g,T,(e>>1)+decodeInteger(C,"IARDX",k),(t>>1)+decodeInteger(C,"IARDY",k),!1,p,m)}let R=0;l?1&u?R=D-1:r+=D-1:u>1?r+=M-1:R=M-1;const N=n-(1&u?0:D-1),E=r-(2&u?M-1:0);let L,_,j;if(l)for(L=0;L>5&7;const c=[31&s];let l=t+6;if(7===s){o=536870911&readUint32(e,l-1);l+=3;let t=o+7>>3;c[0]=e[l++];for(;--t>0;)c.push(e[l++])}else if(5===s||6===s)throw new Jbig2Error("invalid referred-to flags");a.retainBits=c;let h=4;a.number<=256?h=1:a.number<=65536&&(h=2);const u=[];let d,f;for(d=0;d>>24&255;n[3]=t.height>>16&255;n[4]=t.height>>8&255;n[5]=255&t.height;for(d=l,f=e.length;d>2&3;e.huffmanDWSelector=t>>4&3;e.bitmapSizeSelector=t>>6&1;e.aggregationInstancesSelector=t>>7&1;e.bitmapCodingContextUsed=!!(256&t);e.bitmapCodingContextRetained=!!(512&t);e.template=t>>10&3;e.refinementTemplate=t>>12&1;l+=2;if(!e.huffman){c=0===e.template?4:1;s=[];for(o=0;o>2&3;h.stripSize=1<>4&3;h.transposed=!!(64&u);h.combinationOperator=u>>7&3;h.defaultPixelValue=u>>9&1;h.dsOffset=u<<17>>27;h.refinementTemplate=u>>15&1;if(h.huffman){const e=readUint16(r,l);l+=2;h.huffmanFS=3&e;h.huffmanDS=e>>2&3;h.huffmanDT=e>>4&3;h.huffmanRefinementDW=e>>6&3;h.huffmanRefinementDH=e>>8&3;h.huffmanRefinementDX=e>>10&3;h.huffmanRefinementDY=e>>12&3;h.huffmanRefinementSizeSelector=!!(16384&e)}if(h.refinement&&!h.refinementTemplate){s=[];for(o=0;o<2;o++){s.push({x:readInt8(r,l),y:readInt8(r,l+1)});l+=2}h.refinementAt=s}h.numberOfSymbolInstances=readUint32(r,l);l+=4;n=[h,a.referredTo,r,l,i];break;case 16:const d={},f=r[l++];d.mmr=!!(1&f);d.template=f>>1&3;d.patternWidth=r[l++];d.patternHeight=r[l++];d.maxPatternIndex=readUint32(r,l);l+=4;n=[d,a.number,r,l,i];break;case 22:case 23:const g={};g.info=readRegionSegmentInformation(r,l);l+=mr;const p=r[l++];g.mmr=!!(1&p);g.template=p>>1&3;g.enableSkip=!!(8&p);g.combinationOperator=p>>4&7;g.defaultPixelValue=p>>7&1;g.gridWidth=readUint32(r,l);l+=4;g.gridHeight=readUint32(r,l);l+=4;g.gridOffsetX=4294967295&readUint32(r,l);l+=4;g.gridOffsetY=4294967295&readUint32(r,l);l+=4;g.gridVectorX=readUint16(r,l);l+=2;g.gridVectorY=readUint16(r,l);l+=2;n=[g,a.referredTo,r,l,i];break;case 38:case 39:const m={};m.info=readRegionSegmentInformation(r,l);l+=mr;const b=r[l++];m.mmr=!!(1&b);m.template=b>>1&3;m.prediction=!!(8&b);if(!m.mmr){c=0===m.template?4:1;s=[];for(o=0;o>2&1;y.combinationOperator=w>>3&3;y.requiresBuffer=!!(32&w);y.combinationOperatorOverride=!!(64&w);n=[y];break;case 49:case 50:case 51:case 62:break;case 53:n=[a.number,r,l,i];break;default:throw new Jbig2Error(`segment type ${a.typeName}(${a.type}) is not implemented`)}const h="on"+a.typeName;h in t&&t[h].apply(t,n)}function processSegments(e,t){for(let a=0,r=e.length;a>3,a=new Uint8ClampedArray(t*e.height);e.defaultPixelValue&&a.fill(255);this.buffer=a}drawBitmap(e,t){const a=this.currentPageInfo,r=e.width,i=e.height,n=a.width+7>>3,s=a.combinationOperatorOverride?e.combinationOperator:a.combinationOperator,o=this.buffer,c=128>>(7&e.x);let l,h,u,d,f=e.y*n+(e.x>>3);switch(s){case 0:for(l=0;l>=1;if(!u){u=128;d++}}f+=n}break;case 2:for(l=0;l>=1;if(!u){u=128;d++}}f+=n}break;default:throw new Jbig2Error(`operator ${s} is not supported`)}}onImmediateGenericRegion(e,t,a,r){const i=e.info,n=new DecodingContext(t,a,r),s=decodeBitmap(e.mmr,i.width,i.height,e.template,e.prediction,null,e.at,n);this.drawBitmap(i,s)}onImmediateLosslessGenericRegion(){this.onImmediateGenericRegion(...arguments)}onSymbolDictionary(e,t,a,r,i,n){let s,o;if(e.huffman){s=function getSymbolDictionaryHuffmanTables(e,t,a){let r,i,n,s,o=0;switch(e.huffmanDHSelector){case 0:case 1:r=getStandardTable(e.huffmanDHSelector+4);break;case 3:r=getCustomHuffmanTable(o,t,a);o++;break;default:throw new Jbig2Error("invalid Huffman DH selector")}switch(e.huffmanDWSelector){case 0:case 1:i=getStandardTable(e.huffmanDWSelector+2);break;case 3:i=getCustomHuffmanTable(o,t,a);o++;break;default:throw new Jbig2Error("invalid Huffman DW selector")}if(e.bitmapSizeSelector){n=getCustomHuffmanTable(o,t,a);o++}else n=getStandardTable(1);s=e.aggregationInstancesSelector?getCustomHuffmanTable(o,t,a):getStandardTable(1);return{tableDeltaHeight:r,tableDeltaWidth:i,tableBitmapSize:n,tableAggregateInstances:s}}(e,a,this.customTables);o=new Reader(r,i,n)}let c=this.symbols;c||(this.symbols=c={});const l=[];for(const e of a){const t=c[e];t&&l.push(...t)}const h=new DecodingContext(r,i,n);c[t]=function decodeSymbolDictionary(e,t,a,r,i,n,s,o,c,l,h,u){if(e&&t)throw new Jbig2Error("symbol refinement with Huffman is not supported");const d=[];let f=0,g=log2(a.length+r);const p=h.decoder,m=h.contextCache;let b,y;if(e){b=getStandardTable(1);y=[];g=Math.max(g,1)}for(;d.length1)w=decodeTextRegion(e,t,r,f,0,i,1,a.concat(d),g,0,0,1,0,n,c,l,h,0,u);else{const e=decodeIAID(m,p,g),t=decodeInteger(m,"IARDX",p),i=decodeInteger(m,"IARDY",p);w=decodeRefinement(r,f,c,e=32){let a,r,s;switch(t){case 32:if(0===e)throw new Jbig2Error("no previous value in symbol ID table");r=i.readBits(2)+3;a=n[e-1].prefixLength;break;case 33:r=i.readBits(3)+3;a=0;break;case 34:r=i.readBits(7)+11;a=0;break;default:throw new Jbig2Error("invalid code length in symbol ID table")}for(s=0;s=0;m--){T=e?decodeMMRBitmap(O,c,l,!0):decodeBitmap(!1,c,l,a,!1,null,v,g);F[m]=T}for(M=0;M=0;b--){R^=F[b][M][D];N|=R<>8;_=u+M*d-D*f>>8;if(L>=0&&L+x<=r&&_>=0&&_+k<=i)for(m=0;m=i)){U=p[t];j=E[m];for(b=0;b=0&&e>1&7),c=1+(r>>4&7),l=[];let h,u,d=i;do{h=s.readBits(o);u=s.readBits(c);l.push(new HuffmanLine([d,h,u,0]));d+=1<>t&1;if(t<=0)this.children[a]=new HuffmanTreeNode(e);else{let r=this.children[a];r||(this.children[a]=r=new HuffmanTreeNode(null));r.buildTree(e,t-1)}}decodeNode(e){if(this.isLeaf){if(this.isOOB)return null;const t=e.readBits(this.rangeLength);return this.rangeLow+(this.isLowerRange?-t:t)}const t=this.children[e.readBit()];if(!t)throw new Jbig2Error("invalid Huffman data");return t.decodeNode(e)}}class HuffmanTable{constructor(e,t){t||this.assignPrefixCodes(e);this.rootNode=new HuffmanTreeNode(null);for(let t=0,a=e.length;t0&&this.rootNode.buildTree(a,a.prefixLength-1)}}decode(e){return this.rootNode.decodeNode(e)}assignPrefixCodes(e){const t=e.length;let a=0;for(let r=0;r=this.end)throw new Jbig2Error("end of data while reading bit");this.currentByte=this.data[this.position++];this.shift=7}const e=this.currentByte>>this.shift&1;this.shift--;return e}readBits(e){let t,a=0;for(t=e-1;t>=0;t--)a|=this.readBit()<=this.end?-1:this.data[this.position++]}}function getCustomHuffmanTable(e,t,a){let r=0;for(let i=0,n=t.length;i>a&1;a--}}if(r&&!o){const e=5;for(let t=0;t>>t&(1<0;if(e<256){d[0]=e;f=1}else{if(!(e>=258)){if(256===e){h=9;s=258;f=0;continue}this.eof=!0;delete this.lzwState;break}if(e=0;t--){d[t]=o[a];a=l[a]}}else d[f++]=d[0]}if(i){l[s]=u;c[s]=c[u]+1;o[s]=d[0];s++;h=s+n&s+n-1?h:0|Math.min(Math.log(s+n)/.6931471805599453+1,12)}u=e;g+=f;if(r15))throw new FormatError(`Unsupported predictor: ${r}`);this.readBlock=2===r?this.readBlockTiff:this.readBlockPng;this.str=e;this.dict=e.dict;const i=this.colors=a.get("Colors")||1,n=this.bits=a.get("BPC","BitsPerComponent")||8,s=this.columns=a.get("Columns")||1;this.pixBytes=i*n+7>>3;this.rowBytes=s*i*n+7>>3;return this}readBlockTiff(){const e=this.rowBytes,t=this.bufferLength,a=this.ensureBuffer(t+e),r=this.bits,i=this.colors,n=this.str.getBytes(e);this.eof=!n.length;if(this.eof)return;let s,o=0,c=0,l=0,h=0,u=t;if(1===r&&1===i)for(s=0;s>1;e^=e>>2;e^=e>>4;o=(1&e)<<7;a[u++]=e}else if(8===r){for(s=0;s>8&255;a[u++]=255&e}}else{const e=new Uint8Array(i+1),u=(1<>l-r)&u;l-=r;c=c<=8){a[f++]=c>>h-8&255;h-=8}}h>0&&(a[f++]=(c<<8-h)+(o&(1<<8-h)-1))}this.bufferLength+=e}readBlockPng(){const e=this.rowBytes,t=this.pixBytes,a=this.str.getByte(),r=this.str.getBytes(e);this.eof=!r.length;if(this.eof)return;const i=this.bufferLength,n=this.ensureBuffer(i+e);let s=n.subarray(i-e,i);0===s.length&&(s=new Uint8Array(e));let o,c,l,h=i;switch(a){case 0:for(o=0;o>1)+r[o];for(;o>1)+r[o]&255;h++}break;case 4:for(o=0;o0){const e=this.str.getBytes(r);t.set(e,a);a+=r}}else{r=257-r;t=this.ensureBuffer(a+r+1);t.fill(e[1],a,a+r);a+=r}this.bufferLength=a}}class Parser{constructor({lexer:e,xref:t,allowStreams:a=!1,recoveryMode:r=!1}){this.lexer=e;this.xref=t;this.allowStreams=a;this.recoveryMode=r;this.imageCache=Object.create(null);this._imageId=0;this.refill()}refill(){this.buf1=this.lexer.getObj();this.buf2=this.lexer.getObj()}shift(){if(this.buf2 instanceof Cmd&&"ID"===this.buf2.cmd){this.buf1=this.buf2;this.buf2=null}else{this.buf1=this.buf2;this.buf2=this.lexer.getObj()}}tryShift(){try{this.shift();return!0}catch(e){if(e instanceof MissingDataException)throw e;return!1}}getObj(e=null){const t=this.buf1;this.shift();if(t instanceof Cmd)switch(t.cmd){case"BI":return this.makeInlineImage(e);case"[":const a=[];for(;!isCmd(this.buf1,"]")&&this.buf1!==wa;)a.push(this.getObj(e));if(this.buf1===wa){if(this.recoveryMode)return a;throw new ParserEOFException("End of file inside array.")}this.shift();return a;case"<<":const r=new Dict(this.xref);for(;!isCmd(this.buf1,">>")&&this.buf1!==wa;){if(!(this.buf1 instanceof Name)){info("Malformed dictionary: key must be a name object");this.shift();continue}const t=this.buf1.name;this.shift();if(this.buf1===wa)break;r.set(t,this.getObj(e))}if(this.buf1===wa){if(this.recoveryMode)return r;throw new ParserEOFException("End of file inside dictionary.")}if(isCmd(this.buf2,"stream"))return this.allowStreams?this.makeStream(r,e):r;this.shift();return r;default:return t}if(Number.isInteger(t)){if(Number.isInteger(this.buf1)&&isCmd(this.buf2,"R")){const e=Ref.get(t,this.buf1);this.shift();this.shift();return e}return t}return"string"==typeof t&&e?e.decryptString(t):t}findDefaultInlineStreamEnd(e){const{knownCommands:t}=this.lexer,a=e.pos;let r,i,n=0;for(;-1!==(r=e.getByte());)if(0===n)n=69===r?1:0;else if(1===n)n=73===r?2:0;else if(32===r||10===r||13===r){i=e.pos;const a=e.peekBytes(15),s=a.length;if(0===s)break;for(let e=0;e127))){n=0;break}}if(2!==n)continue;if(!t){warn("findDefaultInlineStreamEnd - `lexer.knownCommands` is undefined.");continue}const o=new Lexer(new Stream(e.peekBytes(75)),t);o._hexStringWarn=()=>{};let c=0;for(;;){const e=o.getObj();if(e===wa){n=0;break}if(e instanceof Cmd){const a=t[e.cmd];if(!a){n=0;break}if(a.variableArgs?c<=a.numArgs:c===a.numArgs)break;c=0}else c++}if(2===n)break}else n=0;if(-1===r){warn("findDefaultInlineStreamEnd: Reached the end of the stream without finding a valid EI marker");if(i){warn('... trying to recover by using the last "EI" occurrence.');e.skip(-(e.pos-i))}}let s=4;e.skip(-s);r=e.peekByte();e.skip(s);isWhiteSpace(r)||s--;return e.pos-s-a}findDCTDecodeInlineStreamEnd(e){const t=e.pos;let a,r,i=!1;for(;-1!==(a=e.getByte());)if(255===a){switch(e.getByte()){case 0:break;case 255:e.skip(-1);break;case 217:i=!0;break;case 192:case 193:case 194:case 195:case 197:case 198:case 199:case 201:case 202:case 203:case 205:case 206:case 207:case 196:case 204:case 218:case 219:case 220:case 221:case 222:case 223:case 224:case 225:case 226:case 227:case 228:case 229:case 230:case 231:case 232:case 233:case 234:case 235:case 236:case 237:case 238:case 239:case 254:r=e.getUint16();r>2?e.skip(r-2):e.skip(-2)}if(i)break}const n=e.pos-t;if(-1===a){warn("Inline DCTDecode image stream: EOI marker not found, searching for /EI/ instead.");e.skip(-n);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return n}findASCII85DecodeInlineStreamEnd(e){const t=e.pos;let a;for(;-1!==(a=e.getByte());)if(126===a){const t=e.pos;a=e.peekByte();for(;isWhiteSpace(a);){e.skip();a=e.peekByte()}if(62===a){e.skip();break}if(e.pos>t){const t=e.peekBytes(2);if(69===t[0]&&73===t[1])break}}const r=e.pos-t;if(-1===a){warn("Inline ASCII85Decode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-r);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return r}findASCIIHexDecodeInlineStreamEnd(e){const t=e.pos;let a;for(;-1!==(a=e.getByte())&&62!==a;);const r=e.pos-t;if(-1===a){warn("Inline ASCIIHexDecode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-r);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return r}inlineStreamSkipEI(e){let t,a=0;for(;-1!==(t=e.getByte());)if(0===a)a=69===t?1:0;else if(1===a)a=73===t?2:0;else if(2===a)break}makeInlineImage(e){const t=this.lexer,a=t.stream,r=Object.create(null);let i;for(;!isCmd(this.buf1,"ID")&&this.buf1!==wa;){if(!(this.buf1 instanceof Name))throw new FormatError("Dictionary key must be a name object");const t=this.buf1.name;this.shift();if(this.buf1===wa)break;r[t]=this.getObj(e)}-1!==t.beginInlineImagePos&&(i=a.pos-t.beginInlineImagePos);const n=this.xref.fetchIfRef(r.F||r.Filter);let s;if(n instanceof Name)s=n.name;else if(Array.isArray(n)){const e=this.xref.fetchIfRef(n[0]);e instanceof Name&&(s=e.name)}const o=a.pos;let c,l;switch(s){case"DCT":case"DCTDecode":c=this.findDCTDecodeInlineStreamEnd(a);break;case"A85":case"ASCII85Decode":c=this.findASCII85DecodeInlineStreamEnd(a);break;case"AHx":case"ASCIIHexDecode":c=this.findASCIIHexDecodeInlineStreamEnd(a);break;default:c=this.findDefaultInlineStreamEnd(a)}if(c<1e3&&i>0){const e=a.pos;a.pos=t.beginInlineImagePos;l=function getInlineImageCacheKey(e){const t=[],a=e.length;let r=0;for(;r=r){let r=!1;for(const e of i){const t=e.length;let i=0;for(;i=n){r=!0;break}if(i>=t){if(isWhiteSpace(s[c+o+i])){info(`Found "${bytesToString([...a,...e])}" when searching for endstream command.`);r=!0}break}}if(r){t.pos+=c;return t.pos-e}}c++}t.pos+=o}return-1}makeStream(e,t){const a=this.lexer;let r=a.stream;a.skipToNextLine();const i=r.pos-1;let n=e.get("Length");if(!Number.isInteger(n)){info(`Bad length "${n&&n.toString()}" in stream.`);n=0}r.pos=i+n;a.nextChar();if(this.tryShift()&&isCmd(this.buf2,"endstream"))this.shift();else{n=this.#U(i);if(n<0)throw new FormatError("Missing endstream command.");a.nextChar();this.shift();this.shift()}this.shift();r=r.makeSubStream(i,n,e);t&&(r=t.createStream(r,n));r=this.filter(r,e,n);r.dict=e;return r}filter(e,t,a){let r=t.get("F","Filter"),i=t.get("DP","DecodeParms");if(r instanceof Name){Array.isArray(i)&&warn("/DecodeParms should not be an Array, when /Filter is a Name.");return this.makeFilter(e,r.name,a,i)}let n=a;if(Array.isArray(r)){const t=r,a=i;for(let s=0,o=t.length;s=48&&e<=57?15&e:e>=65&&e<=70||e>=97&&e<=102?9+(15&e):-1}class Lexer{constructor(e,t=null){this.stream=e;this.nextChar();this.strBuf=[];this.knownCommands=t;this._hexStringNumWarn=0;this.beginInlineImagePos=-1}nextChar(){return this.currentChar=this.stream.getByte()}peekChar(){return this.stream.peekByte()}getNumber(){let e=this.currentChar,t=!1,a=0,r=1;if(45===e){r=-1;e=this.nextChar();45===e&&(e=this.nextChar())}else 43===e&&(e=this.nextChar());if(10===e||13===e)do{e=this.nextChar()}while(10===e||13===e);if(46===e){a=10;e=this.nextChar()}if(e<48||e>57){const t=`Invalid number: ${String.fromCharCode(e)} (charCode ${e})`;if(isWhiteSpace(e)||40===e||60===e||-1===e){info(`Lexer.getNumber - "${t}".`);return 0}throw new FormatError(t)}let i=e-48,n=0,s=1;for(;(e=this.nextChar())>=0;)if(e>=48&&e<=57){const r=e-48;if(t)n=10*n+r;else{0!==a&&(a*=10);i=10*i+r}}else if(46===e){if(0!==a)break;a=1}else if(45===e)warn("Badly formatted number: minus sign in the middle");else{if(69!==e&&101!==e)break;e=this.peekChar();if(43===e||45===e){s=45===e?-1:1;this.nextChar()}else if(e<48||e>57)break;t=!0}0!==a&&(i/=a);t&&(i*=10**(s*n));return r*i}getString(){let e=1,t=!1;const a=this.strBuf;a.length=0;let r=this.nextChar();for(;;){let i=!1;switch(0|r){case-1:warn("Unterminated string");t=!0;break;case 40:++e;a.push("(");break;case 41:if(0==--e){this.nextChar();t=!0}else a.push(")");break;case 92:r=this.nextChar();switch(r){case-1:warn("Unterminated string");t=!0;break;case 110:a.push("\n");break;case 114:a.push("\r");break;case 116:a.push("\t");break;case 98:a.push("\b");break;case 102:a.push("\f");break;case 92:case 40:case 41:a.push(String.fromCharCode(r));break;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:let e=15&r;r=this.nextChar();i=!0;if(r>=48&&r<=55){e=(e<<3)+(15&r);r=this.nextChar();if(r>=48&&r<=55){i=!1;e=(e<<3)+(15&r)}}a.push(String.fromCharCode(e));break;case 13:10===this.peekChar()&&this.nextChar();break;case 10:break;default:a.push(String.fromCharCode(r))}break;default:a.push(String.fromCharCode(r))}if(t)break;i||(r=this.nextChar())}return a.join("")}getName(){let e,t;const a=this.strBuf;a.length=0;for(;(e=this.nextChar())>=0&&!yr[e];)if(35===e){e=this.nextChar();if(yr[e]){warn("Lexer_getName: NUMBER SIGN (#) should be followed by a hexadecimal number.");a.push("#");break}const r=toHexDigit(e);if(-1!==r){t=e;e=this.nextChar();const i=toHexDigit(e);if(-1===i){warn(`Lexer_getName: Illegal digit (${String.fromCharCode(e)}) in hexadecimal number.`);a.push("#",String.fromCharCode(t));if(yr[e])break;a.push(String.fromCharCode(e));continue}a.push(String.fromCharCode(r<<4|i))}else a.push("#",String.fromCharCode(e))}else a.push(String.fromCharCode(e));a.length>127&&warn(`Name token is longer than allowed by the spec: ${a.length}`);return Name.get(a.join(""))}_hexStringWarn(e){5!=this._hexStringNumWarn++?this._hexStringNumWarn>5||warn(`getHexString - ignoring invalid character: ${e}`):warn("getHexString - ignoring additional invalid characters.")}getHexString(){const e=this.strBuf;e.length=0;let t=this.currentChar,a=-1,r=-1;this._hexStringNumWarn=0;for(;;){if(t<0){warn("Unterminated hex string");break}if(62===t){this.nextChar();break}if(1!==yr[t]){r=toHexDigit(t);if(-1===r)this._hexStringWarn(t);else if(-1===a)a=r;else{e.push(String.fromCharCode(a<<4|r));a=-1}t=this.nextChar()}else t=this.nextChar()}-1!==a&&e.push(String.fromCharCode(a<<4));return e.join("")}getObj(){let e=!1,t=this.currentChar;for(;;){if(t<0)return wa;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(1!==yr[t])break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return this.getNumber();case 40:return this.getString();case 47:return this.getName();case 91:this.nextChar();return Cmd.get("[");case 93:this.nextChar();return Cmd.get("]");case 60:t=this.nextChar();if(60===t){this.nextChar();return Cmd.get("<<")}return this.getHexString();case 62:t=this.nextChar();if(62===t){this.nextChar();return Cmd.get(">>")}return Cmd.get(">");case 123:this.nextChar();return Cmd.get("{");case 125:this.nextChar();return Cmd.get("}");case 41:this.nextChar();throw new FormatError(`Illegal character: ${t}`)}let a=String.fromCharCode(t);if(t<32||t>127){const e=this.peekChar();if(e>=32&&e<=127){this.nextChar();return Cmd.get(a)}}const r=this.knownCommands;let i=void 0!==r?.[a];for(;(t=this.nextChar())>=0&&!yr[t];){const e=a+String.fromCharCode(t);if(i&&void 0===r[e])break;if(128===a.length)throw new FormatError(`Command token too long: ${a.length}`);a=e;i=void 0!==r?.[a]}if("true"===a)return!0;if("false"===a)return!1;if("null"===a)return null;"BI"===a&&(this.beginInlineImagePos=this.stream.pos);return Cmd.get(a)}skipToNextLine(){let e=this.currentChar;for(;e>=0;){if(13===e){e=this.nextChar();10===e&&this.nextChar();break}if(10===e){this.nextChar();break}e=this.nextChar()}}}class Linearization{static create(e){function getInt(e,t,a=!1){const r=e.get(t);if(Number.isInteger(r)&&(a?r>=0:r>0))return r;throw new Error(`The "${t}" parameter in the linearization dictionary is invalid.`)}const t=new Parser({lexer:new Lexer(e),xref:null}),a=t.getObj(),r=t.getObj(),i=t.getObj(),n=t.getObj();let s,o;if(!(Number.isInteger(a)&&Number.isInteger(r)&&isCmd(i,"obj")&&n instanceof Dict&&"number"==typeof(s=n.get("Linearized"))&&s>0))return null;if((o=getInt(n,"L"))!==e.length)throw new Error('The "L" parameter in the linearization dictionary does not equal the stream length.');return{length:o,hints:function getHints(e){const t=e.get("H");let a;if(Array.isArray(t)&&(2===(a=t.length)||4===a)){for(let e=0;e0))throw new Error(`Hint (${e}) in the linearization dictionary is invalid.`)}return t}throw new Error("Hint array in the linearization dictionary is invalid.")}(n),objectNumberFirst:getInt(n,"O"),endFirst:getInt(n,"E"),numPages:getInt(n,"N"),mainXRefEntriesOffset:getInt(n,"T"),pageFirst:n.has("P")?getInt(n,"P",!0):0}}}const wr=["Adobe-GB1-UCS2","Adobe-CNS1-UCS2","Adobe-Japan1-UCS2","Adobe-Korea1-UCS2","78-EUC-H","78-EUC-V","78-H","78-RKSJ-H","78-RKSJ-V","78-V","78ms-RKSJ-H","78ms-RKSJ-V","83pv-RKSJ-H","90ms-RKSJ-H","90ms-RKSJ-V","90msp-RKSJ-H","90msp-RKSJ-V","90pv-RKSJ-H","90pv-RKSJ-V","Add-H","Add-RKSJ-H","Add-RKSJ-V","Add-V","Adobe-CNS1-0","Adobe-CNS1-1","Adobe-CNS1-2","Adobe-CNS1-3","Adobe-CNS1-4","Adobe-CNS1-5","Adobe-CNS1-6","Adobe-GB1-0","Adobe-GB1-1","Adobe-GB1-2","Adobe-GB1-3","Adobe-GB1-4","Adobe-GB1-5","Adobe-Japan1-0","Adobe-Japan1-1","Adobe-Japan1-2","Adobe-Japan1-3","Adobe-Japan1-4","Adobe-Japan1-5","Adobe-Japan1-6","Adobe-Korea1-0","Adobe-Korea1-1","Adobe-Korea1-2","B5-H","B5-V","B5pc-H","B5pc-V","CNS-EUC-H","CNS-EUC-V","CNS1-H","CNS1-V","CNS2-H","CNS2-V","ETHK-B5-H","ETHK-B5-V","ETen-B5-H","ETen-B5-V","ETenms-B5-H","ETenms-B5-V","EUC-H","EUC-V","Ext-H","Ext-RKSJ-H","Ext-RKSJ-V","Ext-V","GB-EUC-H","GB-EUC-V","GB-H","GB-V","GBK-EUC-H","GBK-EUC-V","GBK2K-H","GBK2K-V","GBKp-EUC-H","GBKp-EUC-V","GBT-EUC-H","GBT-EUC-V","GBT-H","GBT-V","GBTpc-EUC-H","GBTpc-EUC-V","GBpc-EUC-H","GBpc-EUC-V","H","HKdla-B5-H","HKdla-B5-V","HKdlb-B5-H","HKdlb-B5-V","HKgccs-B5-H","HKgccs-B5-V","HKm314-B5-H","HKm314-B5-V","HKm471-B5-H","HKm471-B5-V","HKscs-B5-H","HKscs-B5-V","Hankaku","Hiragana","KSC-EUC-H","KSC-EUC-V","KSC-H","KSC-Johab-H","KSC-Johab-V","KSC-V","KSCms-UHC-H","KSCms-UHC-HW-H","KSCms-UHC-HW-V","KSCms-UHC-V","KSCpc-EUC-H","KSCpc-EUC-V","Katakana","NWP-H","NWP-V","RKSJ-H","RKSJ-V","Roman","UniCNS-UCS2-H","UniCNS-UCS2-V","UniCNS-UTF16-H","UniCNS-UTF16-V","UniCNS-UTF32-H","UniCNS-UTF32-V","UniCNS-UTF8-H","UniCNS-UTF8-V","UniGB-UCS2-H","UniGB-UCS2-V","UniGB-UTF16-H","UniGB-UTF16-V","UniGB-UTF32-H","UniGB-UTF32-V","UniGB-UTF8-H","UniGB-UTF8-V","UniJIS-UCS2-H","UniJIS-UCS2-HW-H","UniJIS-UCS2-HW-V","UniJIS-UCS2-V","UniJIS-UTF16-H","UniJIS-UTF16-V","UniJIS-UTF32-H","UniJIS-UTF32-V","UniJIS-UTF8-H","UniJIS-UTF8-V","UniJIS2004-UTF16-H","UniJIS2004-UTF16-V","UniJIS2004-UTF32-H","UniJIS2004-UTF32-V","UniJIS2004-UTF8-H","UniJIS2004-UTF8-V","UniJISPro-UCS2-HW-V","UniJISPro-UCS2-V","UniJISPro-UTF8-V","UniJISX0213-UTF32-H","UniJISX0213-UTF32-V","UniJISX02132004-UTF32-H","UniJISX02132004-UTF32-V","UniKS-UCS2-H","UniKS-UCS2-V","UniKS-UTF16-H","UniKS-UTF16-V","UniKS-UTF32-H","UniKS-UTF32-V","UniKS-UTF8-H","UniKS-UTF8-V","V","WP-Symbol"],Sr=2**24-1;class CMap{constructor(e=!1){this.codespaceRanges=[[],[],[],[]];this.numCodespaceRanges=0;this._map=[];this.name="";this.vertical=!1;this.useCMap=null;this.builtInCMap=e}addCodespaceRange(e,t,a){this.codespaceRanges[e-1].push(t,a);this.numCodespaceRanges++}mapCidRange(e,t,a){if(t-e>Sr)throw new Error("mapCidRange - ignoring data above MAX_MAP_RANGE.");for(;e<=t;)this._map[e++]=a++}mapBfRange(e,t,a){if(t-e>Sr)throw new Error("mapBfRange - ignoring data above MAX_MAP_RANGE.");const r=a.length-1;for(;e<=t;){this._map[e++]=a;const t=a.charCodeAt(r)+1;t>255?a=a.substring(0,r-1)+String.fromCharCode(a.charCodeAt(r-1)+1)+"\0":a=a.substring(0,r)+String.fromCharCode(t)}}mapBfRangeToArray(e,t,a){if(t-e>Sr)throw new Error("mapBfRangeToArray - ignoring data above MAX_MAP_RANGE.");const r=a.length;let i=0;for(;e<=t&&i>>0;const s=i[n];for(let e=0,t=s.length;e=t&&r<=i){a.charcode=r;a.length=n+1;return}}}a.charcode=0;a.length=1}getCharCodeLength(e){const t=this.codespaceRanges;for(let a=0,r=t.length;a=i&&e<=n)return a+1}}return 1}get length(){return this._map.length}get isIdentityCMap(){if("Identity-H"!==this.name&&"Identity-V"!==this.name)return!1;if(65536!==this._map.length)return!1;for(let e=0;e<65536;e++)if(this._map[e]!==e)return!1;return!0}}class IdentityCMap extends CMap{constructor(e,t){super();this.vertical=e;this.addCodespaceRange(t,0,65535)}mapCidRange(e,t,a){unreachable("should not call mapCidRange")}mapBfRange(e,t,a){unreachable("should not call mapBfRange")}mapBfRangeToArray(e,t,a){unreachable("should not call mapBfRangeToArray")}mapOne(e,t){unreachable("should not call mapCidOne")}lookup(e){return Number.isInteger(e)&&e<=65535?e:void 0}contains(e){return Number.isInteger(e)&&e<=65535}forEach(e){for(let t=0;t<=65535;t++)e(t,t)}charCodeOf(e){return Number.isInteger(e)&&e<=65535?e:-1}getMap(){const e=new Array(65536);for(let t=0;t<=65535;t++)e[t]=t;return e}get length(){return 65536}get isIdentityCMap(){unreachable("should not access .isIdentityCMap")}}function strToInt(e){let t=0;for(let a=0;a>>0}function expectString(e){if("string"!=typeof e)throw new FormatError("Malformed CMap: expected string.")}function expectInt(e){if(!Number.isInteger(e))throw new FormatError("Malformed CMap: expected int.")}function parseBfChar(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endbfchar"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectString(a);const i=a;e.mapOne(r,i)}}function parseBfRange(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endbfrange"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectString(a);const i=strToInt(a);a=t.getObj();if(Number.isInteger(a)||"string"==typeof a){const t=Number.isInteger(a)?String.fromCharCode(a):a;e.mapBfRange(r,i,t)}else{if(!isCmd(a,"["))break;{a=t.getObj();const n=[];for(;!isCmd(a,"]")&&a!==wa;){n.push(a);a=t.getObj()}e.mapBfRangeToArray(r,i,n)}}}throw new FormatError("Invalid bf range.")}function parseCidChar(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endcidchar"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectInt(a);const i=a;e.mapOne(r,i)}}function parseCidRange(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endcidrange"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectString(a);const i=strToInt(a);a=t.getObj();expectInt(a);const n=a;e.mapCidRange(r,i,n)}}function parseCodespaceRange(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endcodespacerange"))return;if("string"!=typeof a)break;const r=strToInt(a);a=t.getObj();if("string"!=typeof a)break;const i=strToInt(a);e.addCodespaceRange(a.length,r,i)}throw new FormatError("Invalid codespace range.")}function parseWMode(e,t){const a=t.getObj();Number.isInteger(a)&&(e.vertical=!!a)}function parseCMapName(e,t){const a=t.getObj();a instanceof Name&&(e.name=a.name)}async function parseCMap(e,t,a,r){let i,n;e:for(;;)try{const a=t.getObj();if(a===wa)break;if(a instanceof Name){"WMode"===a.name?parseWMode(e,t):"CMapName"===a.name&&parseCMapName(e,t);i=a}else if(a instanceof Cmd)switch(a.cmd){case"endcmap":break e;case"usecmap":i instanceof Name&&(n=i.name);break;case"begincodespacerange":parseCodespaceRange(e,t);break;case"beginbfchar":parseBfChar(e,t);break;case"begincidchar":parseCidChar(e,t);break;case"beginbfrange":parseBfRange(e,t);break;case"begincidrange":parseCidRange(e,t)}}catch(e){if(e instanceof MissingDataException)throw e;warn("Invalid cMap data: "+e);continue}!r&&n&&(r=n);return r?extendCMap(e,a,r):e}async function extendCMap(e,t,a){e.useCMap=await createBuiltInCMap(a,t);if(0===e.numCodespaceRanges){const t=e.useCMap.codespaceRanges;for(let a=0;aextendCMap(i,t,e)));const n=new Lexer(new Stream(a));return parseCMap(i,n,t,null)}class CMapFactory{static async create({encoding:e,fetchBuiltInCMap:t,useCMap:a}){if(e instanceof Name)return createBuiltInCMap(e.name,t);if(e instanceof BaseStream){const r=await parseCMap(new CMap,new Lexer(e),t,a);return r.isIdentityCMap?createBuiltInCMap(r.name,t):r}throw new Error("Encoding required.")}}const xr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","","asuperior","bsuperior","centsuperior","dsuperior","esuperior","","","","isuperior","","","lsuperior","msuperior","nsuperior","osuperior","","","rsuperior","ssuperior","tsuperior","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdownsmall","centoldstyle","Lslashsmall","","","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","","Dotaccentsmall","","","Macronsmall","","","figuredash","hypheninferior","","","Ogoneksmall","Ringsmall","Cedillasmall","","","","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],Ar=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","","threequartersemdash","","questionsmall","","","","","Ethsmall","","","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","","","","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hypheninferior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","asuperior","centsuperior","","","","","Aacutesmall","Agravesmall","Acircumflexsmall","Adieresissmall","Atildesmall","Aringsmall","Ccedillasmall","Eacutesmall","Egravesmall","Ecircumflexsmall","Edieresissmall","Iacutesmall","Igravesmall","Icircumflexsmall","Idieresissmall","Ntildesmall","Oacutesmall","Ogravesmall","Ocircumflexsmall","Odieresissmall","Otildesmall","Uacutesmall","Ugravesmall","Ucircumflexsmall","Udieresissmall","","eightsuperior","fourinferior","threeinferior","sixinferior","eightinferior","seveninferior","Scaronsmall","","centinferior","twoinferior","","Dieresissmall","","Caronsmall","osuperior","fiveinferior","","commainferior","periodinferior","Yacutesmall","","dollarinferior","","","Thornsmall","","nineinferior","zeroinferior","Zcaronsmall","AEsmall","Oslashsmall","questiondownsmall","oneinferior","Lslashsmall","","","","","","","Cedillasmall","","","","","","OEsmall","figuredash","hyphensuperior","","","","","exclamdownsmall","","Ydieresissmall","","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","ninesuperior","zerosuperior","","esuperior","rsuperior","tsuperior","","","isuperior","ssuperior","dsuperior","","","","","","lsuperior","Ogoneksmall","Brevesmall","Macronsmall","bsuperior","nsuperior","msuperior","commasuperior","periodsuperior","Dotaccentsmall","Ringsmall","","","",""],kr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","space","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron"],Cr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","","endash","dagger","daggerdbl","periodcentered","","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","","questiondown","","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","","ring","cedilla","","hungarumlaut","ogonek","caron","emdash","","","","","","","","","","","","","","","","","AE","","ordfeminine","","","","","Lslash","Oslash","OE","ordmasculine","","","","","","ae","","","","dotlessi","","","lslash","oslash","oe","germandbls","","","",""],vr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","bullet","Euro","bullet","quotesinglbase","florin","quotedblbase","ellipsis","dagger","daggerdbl","circumflex","perthousand","Scaron","guilsinglleft","OE","bullet","Zcaron","bullet","bullet","quoteleft","quoteright","quotedblleft","quotedblright","bullet","endash","emdash","tilde","trademark","scaron","guilsinglright","oe","bullet","zcaron","Ydieresis","space","exclamdown","cent","sterling","currency","yen","brokenbar","section","dieresis","copyright","ordfeminine","guillemotleft","logicalnot","hyphen","registered","macron","degree","plusminus","twosuperior","threesuperior","acute","mu","paragraph","periodcentered","cedilla","onesuperior","ordmasculine","guillemotright","onequarter","onehalf","threequarters","questiondown","Agrave","Aacute","Acircumflex","Atilde","Adieresis","Aring","AE","Ccedilla","Egrave","Eacute","Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth","Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply","Oslash","Ugrave","Uacute","Ucircumflex","Udieresis","Yacute","Thorn","germandbls","agrave","aacute","acircumflex","atilde","adieresis","aring","ae","ccedilla","egrave","eacute","ecircumflex","edieresis","igrave","iacute","icircumflex","idieresis","eth","ntilde","ograve","oacute","ocircumflex","otilde","odieresis","divide","oslash","ugrave","uacute","ucircumflex","udieresis","yacute","thorn","ydieresis"],Fr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","universal","numbersign","existential","percent","ampersand","suchthat","parenleft","parenright","asteriskmath","plus","comma","minus","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","congruent","Alpha","Beta","Chi","Delta","Epsilon","Phi","Gamma","Eta","Iota","theta1","Kappa","Lambda","Mu","Nu","Omicron","Pi","Theta","Rho","Sigma","Tau","Upsilon","sigma1","Omega","Xi","Psi","Zeta","bracketleft","therefore","bracketright","perpendicular","underscore","radicalex","alpha","beta","chi","delta","epsilon","phi","gamma","eta","iota","phi1","kappa","lambda","mu","nu","omicron","pi","theta","rho","sigma","tau","upsilon","omega1","omega","xi","psi","zeta","braceleft","bar","braceright","similar","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Euro","Upsilon1","minute","lessequal","fraction","infinity","florin","club","diamond","heart","spade","arrowboth","arrowleft","arrowup","arrowright","arrowdown","degree","plusminus","second","greaterequal","multiply","proportional","partialdiff","bullet","divide","notequal","equivalence","approxequal","ellipsis","arrowvertex","arrowhorizex","carriagereturn","aleph","Ifraktur","Rfraktur","weierstrass","circlemultiply","circleplus","emptyset","intersection","union","propersuperset","reflexsuperset","notsubset","propersubset","reflexsubset","element","notelement","angle","gradient","registerserif","copyrightserif","trademarkserif","product","radical","dotmath","logicalnot","logicaland","logicalor","arrowdblboth","arrowdblleft","arrowdblup","arrowdblright","arrowdbldown","lozenge","angleleft","registersans","copyrightsans","trademarksans","summation","parenlefttp","parenleftex","parenleftbt","bracketlefttp","bracketleftex","bracketleftbt","bracelefttp","braceleftmid","braceleftbt","braceex","","angleright","integral","integraltp","integralex","integralbt","parenrighttp","parenrightex","parenrightbt","bracketrighttp","bracketrightex","bracketrightbt","bracerighttp","bracerightmid","bracerightbt",""],Ir=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13","a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25","a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34","a35","a36","a37","a38","a39","a40","a41","a42","a43","a44","a45","a46","a47","a48","a49","a50","a51","a52","a53","a54","a55","a56","a57","a58","a59","a60","a61","a62","a63","a64","a65","a66","a67","a68","a69","a70","a71","a72","a73","a74","a203","a75","a204","a76","a77","a78","a79","a81","a82","a83","a84","a97","a98","a99","a100","","a89","a90","a93","a94","a91","a92","a205","a85","a206","a86","a87","a88","a95","a96","","","","","","","","","","","","","","","","","","","","a101","a102","a103","a104","a106","a107","a108","a112","a111","a110","a109","a120","a121","a122","a123","a124","a125","a126","a127","a128","a129","a130","a131","a132","a133","a134","a135","a136","a137","a138","a139","a140","a141","a142","a143","a144","a145","a146","a147","a148","a149","a150","a151","a152","a153","a154","a155","a156","a157","a158","a159","a160","a161","a163","a164","a196","a165","a192","a166","a167","a168","a169","a170","a171","a172","a173","a162","a174","a175","a176","a177","a178","a179","a193","a180","a199","a181","a200","a182","","a201","a183","a184","a197","a185","a194","a198","a186","a195","a187","a188","a189","a190","a191",""];function getEncoding(e){switch(e){case"WinAnsiEncoding":return vr;case"StandardEncoding":return Cr;case"MacRomanEncoding":return kr;case"SymbolSetEncoding":return Fr;case"ZapfDingbatsEncoding":return Ir;case"ExpertEncoding":return xr;case"MacExpertEncoding":return Ar;default:return null}}const Or=getLookupTableFactory((function(e){e.A=65;e.AE=198;e.AEacute=508;e.AEmacron=482;e.AEsmall=63462;e.Aacute=193;e.Aacutesmall=63457;e.Abreve=258;e.Abreveacute=7854;e.Abrevecyrillic=1232;e.Abrevedotbelow=7862;e.Abrevegrave=7856;e.Abrevehookabove=7858;e.Abrevetilde=7860;e.Acaron=461;e.Acircle=9398;e.Acircumflex=194;e.Acircumflexacute=7844;e.Acircumflexdotbelow=7852;e.Acircumflexgrave=7846;e.Acircumflexhookabove=7848;e.Acircumflexsmall=63458;e.Acircumflextilde=7850;e.Acute=63177;e.Acutesmall=63412;e.Acyrillic=1040;e.Adblgrave=512;e.Adieresis=196;e.Adieresiscyrillic=1234;e.Adieresismacron=478;e.Adieresissmall=63460;e.Adotbelow=7840;e.Adotmacron=480;e.Agrave=192;e.Agravesmall=63456;e.Ahookabove=7842;e.Aiecyrillic=1236;e.Ainvertedbreve=514;e.Alpha=913;e.Alphatonos=902;e.Amacron=256;e.Amonospace=65313;e.Aogonek=260;e.Aring=197;e.Aringacute=506;e.Aringbelow=7680;e.Aringsmall=63461;e.Asmall=63329;e.Atilde=195;e.Atildesmall=63459;e.Aybarmenian=1329;e.B=66;e.Bcircle=9399;e.Bdotaccent=7682;e.Bdotbelow=7684;e.Becyrillic=1041;e.Benarmenian=1330;e.Beta=914;e.Bhook=385;e.Blinebelow=7686;e.Bmonospace=65314;e.Brevesmall=63220;e.Bsmall=63330;e.Btopbar=386;e.C=67;e.Caarmenian=1342;e.Cacute=262;e.Caron=63178;e.Caronsmall=63221;e.Ccaron=268;e.Ccedilla=199;e.Ccedillaacute=7688;e.Ccedillasmall=63463;e.Ccircle=9400;e.Ccircumflex=264;e.Cdot=266;e.Cdotaccent=266;e.Cedillasmall=63416;e.Chaarmenian=1353;e.Cheabkhasiancyrillic=1212;e.Checyrillic=1063;e.Chedescenderabkhasiancyrillic=1214;e.Chedescendercyrillic=1206;e.Chedieresiscyrillic=1268;e.Cheharmenian=1347;e.Chekhakassiancyrillic=1227;e.Cheverticalstrokecyrillic=1208;e.Chi=935;e.Chook=391;e.Circumflexsmall=63222;e.Cmonospace=65315;e.Coarmenian=1361;e.Csmall=63331;e.D=68;e.DZ=497;e.DZcaron=452;e.Daarmenian=1332;e.Dafrican=393;e.Dcaron=270;e.Dcedilla=7696;e.Dcircle=9401;e.Dcircumflexbelow=7698;e.Dcroat=272;e.Ddotaccent=7690;e.Ddotbelow=7692;e.Decyrillic=1044;e.Deicoptic=1006;e.Delta=8710;e.Deltagreek=916;e.Dhook=394;e.Dieresis=63179;e.DieresisAcute=63180;e.DieresisGrave=63181;e.Dieresissmall=63400;e.Digammagreek=988;e.Djecyrillic=1026;e.Dlinebelow=7694;e.Dmonospace=65316;e.Dotaccentsmall=63223;e.Dslash=272;e.Dsmall=63332;e.Dtopbar=395;e.Dz=498;e.Dzcaron=453;e.Dzeabkhasiancyrillic=1248;e.Dzecyrillic=1029;e.Dzhecyrillic=1039;e.E=69;e.Eacute=201;e.Eacutesmall=63465;e.Ebreve=276;e.Ecaron=282;e.Ecedillabreve=7708;e.Echarmenian=1333;e.Ecircle=9402;e.Ecircumflex=202;e.Ecircumflexacute=7870;e.Ecircumflexbelow=7704;e.Ecircumflexdotbelow=7878;e.Ecircumflexgrave=7872;e.Ecircumflexhookabove=7874;e.Ecircumflexsmall=63466;e.Ecircumflextilde=7876;e.Ecyrillic=1028;e.Edblgrave=516;e.Edieresis=203;e.Edieresissmall=63467;e.Edot=278;e.Edotaccent=278;e.Edotbelow=7864;e.Efcyrillic=1060;e.Egrave=200;e.Egravesmall=63464;e.Eharmenian=1335;e.Ehookabove=7866;e.Eightroman=8551;e.Einvertedbreve=518;e.Eiotifiedcyrillic=1124;e.Elcyrillic=1051;e.Elevenroman=8554;e.Emacron=274;e.Emacronacute=7702;e.Emacrongrave=7700;e.Emcyrillic=1052;e.Emonospace=65317;e.Encyrillic=1053;e.Endescendercyrillic=1186;e.Eng=330;e.Enghecyrillic=1188;e.Enhookcyrillic=1223;e.Eogonek=280;e.Eopen=400;e.Epsilon=917;e.Epsilontonos=904;e.Ercyrillic=1056;e.Ereversed=398;e.Ereversedcyrillic=1069;e.Escyrillic=1057;e.Esdescendercyrillic=1194;e.Esh=425;e.Esmall=63333;e.Eta=919;e.Etarmenian=1336;e.Etatonos=905;e.Eth=208;e.Ethsmall=63472;e.Etilde=7868;e.Etildebelow=7706;e.Euro=8364;e.Ezh=439;e.Ezhcaron=494;e.Ezhreversed=440;e.F=70;e.Fcircle=9403;e.Fdotaccent=7710;e.Feharmenian=1366;e.Feicoptic=996;e.Fhook=401;e.Fitacyrillic=1138;e.Fiveroman=8548;e.Fmonospace=65318;e.Fourroman=8547;e.Fsmall=63334;e.G=71;e.GBsquare=13191;e.Gacute=500;e.Gamma=915;e.Gammaafrican=404;e.Gangiacoptic=1002;e.Gbreve=286;e.Gcaron=486;e.Gcedilla=290;e.Gcircle=9404;e.Gcircumflex=284;e.Gcommaaccent=290;e.Gdot=288;e.Gdotaccent=288;e.Gecyrillic=1043;e.Ghadarmenian=1346;e.Ghemiddlehookcyrillic=1172;e.Ghestrokecyrillic=1170;e.Gheupturncyrillic=1168;e.Ghook=403;e.Gimarmenian=1331;e.Gjecyrillic=1027;e.Gmacron=7712;e.Gmonospace=65319;e.Grave=63182;e.Gravesmall=63328;e.Gsmall=63335;e.Gsmallhook=667;e.Gstroke=484;e.H=72;e.H18533=9679;e.H18543=9642;e.H18551=9643;e.H22073=9633;e.HPsquare=13259;e.Haabkhasiancyrillic=1192;e.Hadescendercyrillic=1202;e.Hardsigncyrillic=1066;e.Hbar=294;e.Hbrevebelow=7722;e.Hcedilla=7720;e.Hcircle=9405;e.Hcircumflex=292;e.Hdieresis=7718;e.Hdotaccent=7714;e.Hdotbelow=7716;e.Hmonospace=65320;e.Hoarmenian=1344;e.Horicoptic=1e3;e.Hsmall=63336;e.Hungarumlaut=63183;e.Hungarumlautsmall=63224;e.Hzsquare=13200;e.I=73;e.IAcyrillic=1071;e.IJ=306;e.IUcyrillic=1070;e.Iacute=205;e.Iacutesmall=63469;e.Ibreve=300;e.Icaron=463;e.Icircle=9406;e.Icircumflex=206;e.Icircumflexsmall=63470;e.Icyrillic=1030;e.Idblgrave=520;e.Idieresis=207;e.Idieresisacute=7726;e.Idieresiscyrillic=1252;e.Idieresissmall=63471;e.Idot=304;e.Idotaccent=304;e.Idotbelow=7882;e.Iebrevecyrillic=1238;e.Iecyrillic=1045;e.Ifraktur=8465;e.Igrave=204;e.Igravesmall=63468;e.Ihookabove=7880;e.Iicyrillic=1048;e.Iinvertedbreve=522;e.Iishortcyrillic=1049;e.Imacron=298;e.Imacroncyrillic=1250;e.Imonospace=65321;e.Iniarmenian=1339;e.Iocyrillic=1025;e.Iogonek=302;e.Iota=921;e.Iotaafrican=406;e.Iotadieresis=938;e.Iotatonos=906;e.Ismall=63337;e.Istroke=407;e.Itilde=296;e.Itildebelow=7724;e.Izhitsacyrillic=1140;e.Izhitsadblgravecyrillic=1142;e.J=74;e.Jaarmenian=1345;e.Jcircle=9407;e.Jcircumflex=308;e.Jecyrillic=1032;e.Jheharmenian=1355;e.Jmonospace=65322;e.Jsmall=63338;e.K=75;e.KBsquare=13189;e.KKsquare=13261;e.Kabashkircyrillic=1184;e.Kacute=7728;e.Kacyrillic=1050;e.Kadescendercyrillic=1178;e.Kahookcyrillic=1219;e.Kappa=922;e.Kastrokecyrillic=1182;e.Kaverticalstrokecyrillic=1180;e.Kcaron=488;e.Kcedilla=310;e.Kcircle=9408;e.Kcommaaccent=310;e.Kdotbelow=7730;e.Keharmenian=1364;e.Kenarmenian=1343;e.Khacyrillic=1061;e.Kheicoptic=998;e.Khook=408;e.Kjecyrillic=1036;e.Klinebelow=7732;e.Kmonospace=65323;e.Koppacyrillic=1152;e.Koppagreek=990;e.Ksicyrillic=1134;e.Ksmall=63339;e.L=76;e.LJ=455;e.LL=63167;e.Lacute=313;e.Lambda=923;e.Lcaron=317;e.Lcedilla=315;e.Lcircle=9409;e.Lcircumflexbelow=7740;e.Lcommaaccent=315;e.Ldot=319;e.Ldotaccent=319;e.Ldotbelow=7734;e.Ldotbelowmacron=7736;e.Liwnarmenian=1340;e.Lj=456;e.Ljecyrillic=1033;e.Llinebelow=7738;e.Lmonospace=65324;e.Lslash=321;e.Lslashsmall=63225;e.Lsmall=63340;e.M=77;e.MBsquare=13190;e.Macron=63184;e.Macronsmall=63407;e.Macute=7742;e.Mcircle=9410;e.Mdotaccent=7744;e.Mdotbelow=7746;e.Menarmenian=1348;e.Mmonospace=65325;e.Msmall=63341;e.Mturned=412;e.Mu=924;e.N=78;e.NJ=458;e.Nacute=323;e.Ncaron=327;e.Ncedilla=325;e.Ncircle=9411;e.Ncircumflexbelow=7754;e.Ncommaaccent=325;e.Ndotaccent=7748;e.Ndotbelow=7750;e.Nhookleft=413;e.Nineroman=8552;e.Nj=459;e.Njecyrillic=1034;e.Nlinebelow=7752;e.Nmonospace=65326;e.Nowarmenian=1350;e.Nsmall=63342;e.Ntilde=209;e.Ntildesmall=63473;e.Nu=925;e.O=79;e.OE=338;e.OEsmall=63226;e.Oacute=211;e.Oacutesmall=63475;e.Obarredcyrillic=1256;e.Obarreddieresiscyrillic=1258;e.Obreve=334;e.Ocaron=465;e.Ocenteredtilde=415;e.Ocircle=9412;e.Ocircumflex=212;e.Ocircumflexacute=7888;e.Ocircumflexdotbelow=7896;e.Ocircumflexgrave=7890;e.Ocircumflexhookabove=7892;e.Ocircumflexsmall=63476;e.Ocircumflextilde=7894;e.Ocyrillic=1054;e.Odblacute=336;e.Odblgrave=524;e.Odieresis=214;e.Odieresiscyrillic=1254;e.Odieresissmall=63478;e.Odotbelow=7884;e.Ogoneksmall=63227;e.Ograve=210;e.Ogravesmall=63474;e.Oharmenian=1365;e.Ohm=8486;e.Ohookabove=7886;e.Ohorn=416;e.Ohornacute=7898;e.Ohorndotbelow=7906;e.Ohorngrave=7900;e.Ohornhookabove=7902;e.Ohorntilde=7904;e.Ohungarumlaut=336;e.Oi=418;e.Oinvertedbreve=526;e.Omacron=332;e.Omacronacute=7762;e.Omacrongrave=7760;e.Omega=8486;e.Omegacyrillic=1120;e.Omegagreek=937;e.Omegaroundcyrillic=1146;e.Omegatitlocyrillic=1148;e.Omegatonos=911;e.Omicron=927;e.Omicrontonos=908;e.Omonospace=65327;e.Oneroman=8544;e.Oogonek=490;e.Oogonekmacron=492;e.Oopen=390;e.Oslash=216;e.Oslashacute=510;e.Oslashsmall=63480;e.Osmall=63343;e.Ostrokeacute=510;e.Otcyrillic=1150;e.Otilde=213;e.Otildeacute=7756;e.Otildedieresis=7758;e.Otildesmall=63477;e.P=80;e.Pacute=7764;e.Pcircle=9413;e.Pdotaccent=7766;e.Pecyrillic=1055;e.Peharmenian=1354;e.Pemiddlehookcyrillic=1190;e.Phi=934;e.Phook=420;e.Pi=928;e.Piwrarmenian=1363;e.Pmonospace=65328;e.Psi=936;e.Psicyrillic=1136;e.Psmall=63344;e.Q=81;e.Qcircle=9414;e.Qmonospace=65329;e.Qsmall=63345;e.R=82;e.Raarmenian=1356;e.Racute=340;e.Rcaron=344;e.Rcedilla=342;e.Rcircle=9415;e.Rcommaaccent=342;e.Rdblgrave=528;e.Rdotaccent=7768;e.Rdotbelow=7770;e.Rdotbelowmacron=7772;e.Reharmenian=1360;e.Rfraktur=8476;e.Rho=929;e.Ringsmall=63228;e.Rinvertedbreve=530;e.Rlinebelow=7774;e.Rmonospace=65330;e.Rsmall=63346;e.Rsmallinverted=641;e.Rsmallinvertedsuperior=694;e.S=83;e.SF010000=9484;e.SF020000=9492;e.SF030000=9488;e.SF040000=9496;e.SF050000=9532;e.SF060000=9516;e.SF070000=9524;e.SF080000=9500;e.SF090000=9508;e.SF100000=9472;e.SF110000=9474;e.SF190000=9569;e.SF200000=9570;e.SF210000=9558;e.SF220000=9557;e.SF230000=9571;e.SF240000=9553;e.SF250000=9559;e.SF260000=9565;e.SF270000=9564;e.SF280000=9563;e.SF360000=9566;e.SF370000=9567;e.SF380000=9562;e.SF390000=9556;e.SF400000=9577;e.SF410000=9574;e.SF420000=9568;e.SF430000=9552;e.SF440000=9580;e.SF450000=9575;e.SF460000=9576;e.SF470000=9572;e.SF480000=9573;e.SF490000=9561;e.SF500000=9560;e.SF510000=9554;e.SF520000=9555;e.SF530000=9579;e.SF540000=9578;e.Sacute=346;e.Sacutedotaccent=7780;e.Sampigreek=992;e.Scaron=352;e.Scarondotaccent=7782;e.Scaronsmall=63229;e.Scedilla=350;e.Schwa=399;e.Schwacyrillic=1240;e.Schwadieresiscyrillic=1242;e.Scircle=9416;e.Scircumflex=348;e.Scommaaccent=536;e.Sdotaccent=7776;e.Sdotbelow=7778;e.Sdotbelowdotaccent=7784;e.Seharmenian=1357;e.Sevenroman=8550;e.Shaarmenian=1351;e.Shacyrillic=1064;e.Shchacyrillic=1065;e.Sheicoptic=994;e.Shhacyrillic=1210;e.Shimacoptic=1004;e.Sigma=931;e.Sixroman=8549;e.Smonospace=65331;e.Softsigncyrillic=1068;e.Ssmall=63347;e.Stigmagreek=986;e.T=84;e.Tau=932;e.Tbar=358;e.Tcaron=356;e.Tcedilla=354;e.Tcircle=9417;e.Tcircumflexbelow=7792;e.Tcommaaccent=354;e.Tdotaccent=7786;e.Tdotbelow=7788;e.Tecyrillic=1058;e.Tedescendercyrillic=1196;e.Tenroman=8553;e.Tetsecyrillic=1204;e.Theta=920;e.Thook=428;e.Thorn=222;e.Thornsmall=63486;e.Threeroman=8546;e.Tildesmall=63230;e.Tiwnarmenian=1359;e.Tlinebelow=7790;e.Tmonospace=65332;e.Toarmenian=1337;e.Tonefive=444;e.Tonesix=388;e.Tonetwo=423;e.Tretroflexhook=430;e.Tsecyrillic=1062;e.Tshecyrillic=1035;e.Tsmall=63348;e.Twelveroman=8555;e.Tworoman=8545;e.U=85;e.Uacute=218;e.Uacutesmall=63482;e.Ubreve=364;e.Ucaron=467;e.Ucircle=9418;e.Ucircumflex=219;e.Ucircumflexbelow=7798;e.Ucircumflexsmall=63483;e.Ucyrillic=1059;e.Udblacute=368;e.Udblgrave=532;e.Udieresis=220;e.Udieresisacute=471;e.Udieresisbelow=7794;e.Udieresiscaron=473;e.Udieresiscyrillic=1264;e.Udieresisgrave=475;e.Udieresismacron=469;e.Udieresissmall=63484;e.Udotbelow=7908;e.Ugrave=217;e.Ugravesmall=63481;e.Uhookabove=7910;e.Uhorn=431;e.Uhornacute=7912;e.Uhorndotbelow=7920;e.Uhorngrave=7914;e.Uhornhookabove=7916;e.Uhorntilde=7918;e.Uhungarumlaut=368;e.Uhungarumlautcyrillic=1266;e.Uinvertedbreve=534;e.Ukcyrillic=1144;e.Umacron=362;e.Umacroncyrillic=1262;e.Umacrondieresis=7802;e.Umonospace=65333;e.Uogonek=370;e.Upsilon=933;e.Upsilon1=978;e.Upsilonacutehooksymbolgreek=979;e.Upsilonafrican=433;e.Upsilondieresis=939;e.Upsilondieresishooksymbolgreek=980;e.Upsilonhooksymbol=978;e.Upsilontonos=910;e.Uring=366;e.Ushortcyrillic=1038;e.Usmall=63349;e.Ustraightcyrillic=1198;e.Ustraightstrokecyrillic=1200;e.Utilde=360;e.Utildeacute=7800;e.Utildebelow=7796;e.V=86;e.Vcircle=9419;e.Vdotbelow=7806;e.Vecyrillic=1042;e.Vewarmenian=1358;e.Vhook=434;e.Vmonospace=65334;e.Voarmenian=1352;e.Vsmall=63350;e.Vtilde=7804;e.W=87;e.Wacute=7810;e.Wcircle=9420;e.Wcircumflex=372;e.Wdieresis=7812;e.Wdotaccent=7814;e.Wdotbelow=7816;e.Wgrave=7808;e.Wmonospace=65335;e.Wsmall=63351;e.X=88;e.Xcircle=9421;e.Xdieresis=7820;e.Xdotaccent=7818;e.Xeharmenian=1341;e.Xi=926;e.Xmonospace=65336;e.Xsmall=63352;e.Y=89;e.Yacute=221;e.Yacutesmall=63485;e.Yatcyrillic=1122;e.Ycircle=9422;e.Ycircumflex=374;e.Ydieresis=376;e.Ydieresissmall=63487;e.Ydotaccent=7822;e.Ydotbelow=7924;e.Yericyrillic=1067;e.Yerudieresiscyrillic=1272;e.Ygrave=7922;e.Yhook=435;e.Yhookabove=7926;e.Yiarmenian=1349;e.Yicyrillic=1031;e.Yiwnarmenian=1362;e.Ymonospace=65337;e.Ysmall=63353;e.Ytilde=7928;e.Yusbigcyrillic=1130;e.Yusbigiotifiedcyrillic=1132;e.Yuslittlecyrillic=1126;e.Yuslittleiotifiedcyrillic=1128;e.Z=90;e.Zaarmenian=1334;e.Zacute=377;e.Zcaron=381;e.Zcaronsmall=63231;e.Zcircle=9423;e.Zcircumflex=7824;e.Zdot=379;e.Zdotaccent=379;e.Zdotbelow=7826;e.Zecyrillic=1047;e.Zedescendercyrillic=1176;e.Zedieresiscyrillic=1246;e.Zeta=918;e.Zhearmenian=1338;e.Zhebrevecyrillic=1217;e.Zhecyrillic=1046;e.Zhedescendercyrillic=1174;e.Zhedieresiscyrillic=1244;e.Zlinebelow=7828;e.Zmonospace=65338;e.Zsmall=63354;e.Zstroke=437;e.a=97;e.aabengali=2438;e.aacute=225;e.aadeva=2310;e.aagujarati=2694;e.aagurmukhi=2566;e.aamatragurmukhi=2622;e.aarusquare=13059;e.aavowelsignbengali=2494;e.aavowelsigndeva=2366;e.aavowelsigngujarati=2750;e.abbreviationmarkarmenian=1375;e.abbreviationsigndeva=2416;e.abengali=2437;e.abopomofo=12570;e.abreve=259;e.abreveacute=7855;e.abrevecyrillic=1233;e.abrevedotbelow=7863;e.abrevegrave=7857;e.abrevehookabove=7859;e.abrevetilde=7861;e.acaron=462;e.acircle=9424;e.acircumflex=226;e.acircumflexacute=7845;e.acircumflexdotbelow=7853;e.acircumflexgrave=7847;e.acircumflexhookabove=7849;e.acircumflextilde=7851;e.acute=180;e.acutebelowcmb=791;e.acutecmb=769;e.acutecomb=769;e.acutedeva=2388;e.acutelowmod=719;e.acutetonecmb=833;e.acyrillic=1072;e.adblgrave=513;e.addakgurmukhi=2673;e.adeva=2309;e.adieresis=228;e.adieresiscyrillic=1235;e.adieresismacron=479;e.adotbelow=7841;e.adotmacron=481;e.ae=230;e.aeacute=509;e.aekorean=12624;e.aemacron=483;e.afii00208=8213;e.afii08941=8356;e.afii10017=1040;e.afii10018=1041;e.afii10019=1042;e.afii10020=1043;e.afii10021=1044;e.afii10022=1045;e.afii10023=1025;e.afii10024=1046;e.afii10025=1047;e.afii10026=1048;e.afii10027=1049;e.afii10028=1050;e.afii10029=1051;e.afii10030=1052;e.afii10031=1053;e.afii10032=1054;e.afii10033=1055;e.afii10034=1056;e.afii10035=1057;e.afii10036=1058;e.afii10037=1059;e.afii10038=1060;e.afii10039=1061;e.afii10040=1062;e.afii10041=1063;e.afii10042=1064;e.afii10043=1065;e.afii10044=1066;e.afii10045=1067;e.afii10046=1068;e.afii10047=1069;e.afii10048=1070;e.afii10049=1071;e.afii10050=1168;e.afii10051=1026;e.afii10052=1027;e.afii10053=1028;e.afii10054=1029;e.afii10055=1030;e.afii10056=1031;e.afii10057=1032;e.afii10058=1033;e.afii10059=1034;e.afii10060=1035;e.afii10061=1036;e.afii10062=1038;e.afii10063=63172;e.afii10064=63173;e.afii10065=1072;e.afii10066=1073;e.afii10067=1074;e.afii10068=1075;e.afii10069=1076;e.afii10070=1077;e.afii10071=1105;e.afii10072=1078;e.afii10073=1079;e.afii10074=1080;e.afii10075=1081;e.afii10076=1082;e.afii10077=1083;e.afii10078=1084;e.afii10079=1085;e.afii10080=1086;e.afii10081=1087;e.afii10082=1088;e.afii10083=1089;e.afii10084=1090;e.afii10085=1091;e.afii10086=1092;e.afii10087=1093;e.afii10088=1094;e.afii10089=1095;e.afii10090=1096;e.afii10091=1097;e.afii10092=1098;e.afii10093=1099;e.afii10094=1100;e.afii10095=1101;e.afii10096=1102;e.afii10097=1103;e.afii10098=1169;e.afii10099=1106;e.afii10100=1107;e.afii10101=1108;e.afii10102=1109;e.afii10103=1110;e.afii10104=1111;e.afii10105=1112;e.afii10106=1113;e.afii10107=1114;e.afii10108=1115;e.afii10109=1116;e.afii10110=1118;e.afii10145=1039;e.afii10146=1122;e.afii10147=1138;e.afii10148=1140;e.afii10192=63174;e.afii10193=1119;e.afii10194=1123;e.afii10195=1139;e.afii10196=1141;e.afii10831=63175;e.afii10832=63176;e.afii10846=1241;e.afii299=8206;e.afii300=8207;e.afii301=8205;e.afii57381=1642;e.afii57388=1548;e.afii57392=1632;e.afii57393=1633;e.afii57394=1634;e.afii57395=1635;e.afii57396=1636;e.afii57397=1637;e.afii57398=1638;e.afii57399=1639;e.afii57400=1640;e.afii57401=1641;e.afii57403=1563;e.afii57407=1567;e.afii57409=1569;e.afii57410=1570;e.afii57411=1571;e.afii57412=1572;e.afii57413=1573;e.afii57414=1574;e.afii57415=1575;e.afii57416=1576;e.afii57417=1577;e.afii57418=1578;e.afii57419=1579;e.afii57420=1580;e.afii57421=1581;e.afii57422=1582;e.afii57423=1583;e.afii57424=1584;e.afii57425=1585;e.afii57426=1586;e.afii57427=1587;e.afii57428=1588;e.afii57429=1589;e.afii57430=1590;e.afii57431=1591;e.afii57432=1592;e.afii57433=1593;e.afii57434=1594;e.afii57440=1600;e.afii57441=1601;e.afii57442=1602;e.afii57443=1603;e.afii57444=1604;e.afii57445=1605;e.afii57446=1606;e.afii57448=1608;e.afii57449=1609;e.afii57450=1610;e.afii57451=1611;e.afii57452=1612;e.afii57453=1613;e.afii57454=1614;e.afii57455=1615;e.afii57456=1616;e.afii57457=1617;e.afii57458=1618;e.afii57470=1607;e.afii57505=1700;e.afii57506=1662;e.afii57507=1670;e.afii57508=1688;e.afii57509=1711;e.afii57511=1657;e.afii57512=1672;e.afii57513=1681;e.afii57514=1722;e.afii57519=1746;e.afii57534=1749;e.afii57636=8362;e.afii57645=1470;e.afii57658=1475;e.afii57664=1488;e.afii57665=1489;e.afii57666=1490;e.afii57667=1491;e.afii57668=1492;e.afii57669=1493;e.afii57670=1494;e.afii57671=1495;e.afii57672=1496;e.afii57673=1497;e.afii57674=1498;e.afii57675=1499;e.afii57676=1500;e.afii57677=1501;e.afii57678=1502;e.afii57679=1503;e.afii57680=1504;e.afii57681=1505;e.afii57682=1506;e.afii57683=1507;e.afii57684=1508;e.afii57685=1509;e.afii57686=1510;e.afii57687=1511;e.afii57688=1512;e.afii57689=1513;e.afii57690=1514;e.afii57694=64298;e.afii57695=64299;e.afii57700=64331;e.afii57705=64287;e.afii57716=1520;e.afii57717=1521;e.afii57718=1522;e.afii57723=64309;e.afii57793=1460;e.afii57794=1461;e.afii57795=1462;e.afii57796=1467;e.afii57797=1464;e.afii57798=1463;e.afii57799=1456;e.afii57800=1458;e.afii57801=1457;e.afii57802=1459;e.afii57803=1474;e.afii57804=1473;e.afii57806=1465;e.afii57807=1468;e.afii57839=1469;e.afii57841=1471;e.afii57842=1472;e.afii57929=700;e.afii61248=8453;e.afii61289=8467;e.afii61352=8470;e.afii61573=8236;e.afii61574=8237;e.afii61575=8238;e.afii61664=8204;e.afii63167=1645;e.afii64937=701;e.agrave=224;e.agujarati=2693;e.agurmukhi=2565;e.ahiragana=12354;e.ahookabove=7843;e.aibengali=2448;e.aibopomofo=12574;e.aideva=2320;e.aiecyrillic=1237;e.aigujarati=2704;e.aigurmukhi=2576;e.aimatragurmukhi=2632;e.ainarabic=1593;e.ainfinalarabic=65226;e.aininitialarabic=65227;e.ainmedialarabic=65228;e.ainvertedbreve=515;e.aivowelsignbengali=2504;e.aivowelsigndeva=2376;e.aivowelsigngujarati=2760;e.akatakana=12450;e.akatakanahalfwidth=65393;e.akorean=12623;e.alef=1488;e.alefarabic=1575;e.alefdageshhebrew=64304;e.aleffinalarabic=65166;e.alefhamzaabovearabic=1571;e.alefhamzaabovefinalarabic=65156;e.alefhamzabelowarabic=1573;e.alefhamzabelowfinalarabic=65160;e.alefhebrew=1488;e.aleflamedhebrew=64335;e.alefmaddaabovearabic=1570;e.alefmaddaabovefinalarabic=65154;e.alefmaksuraarabic=1609;e.alefmaksurafinalarabic=65264;e.alefmaksurainitialarabic=65267;e.alefmaksuramedialarabic=65268;e.alefpatahhebrew=64302;e.alefqamatshebrew=64303;e.aleph=8501;e.allequal=8780;e.alpha=945;e.alphatonos=940;e.amacron=257;e.amonospace=65345;e.ampersand=38;e.ampersandmonospace=65286;e.ampersandsmall=63270;e.amsquare=13250;e.anbopomofo=12578;e.angbopomofo=12580;e.angbracketleft=12296;e.angbracketright=12297;e.angkhankhuthai=3674;e.angle=8736;e.anglebracketleft=12296;e.anglebracketleftvertical=65087;e.anglebracketright=12297;e.anglebracketrightvertical=65088;e.angleleft=9001;e.angleright=9002;e.angstrom=8491;e.anoteleia=903;e.anudattadeva=2386;e.anusvarabengali=2434;e.anusvaradeva=2306;e.anusvaragujarati=2690;e.aogonek=261;e.apaatosquare=13056;e.aparen=9372;e.apostrophearmenian=1370;e.apostrophemod=700;e.apple=63743;e.approaches=8784;e.approxequal=8776;e.approxequalorimage=8786;e.approximatelyequal=8773;e.araeaekorean=12686;e.araeakorean=12685;e.arc=8978;e.arighthalfring=7834;e.aring=229;e.aringacute=507;e.aringbelow=7681;e.arrowboth=8596;e.arrowdashdown=8675;e.arrowdashleft=8672;e.arrowdashright=8674;e.arrowdashup=8673;e.arrowdblboth=8660;e.arrowdbldown=8659;e.arrowdblleft=8656;e.arrowdblright=8658;e.arrowdblup=8657;e.arrowdown=8595;e.arrowdownleft=8601;e.arrowdownright=8600;e.arrowdownwhite=8681;e.arrowheaddownmod=709;e.arrowheadleftmod=706;e.arrowheadrightmod=707;e.arrowheadupmod=708;e.arrowhorizex=63719;e.arrowleft=8592;e.arrowleftdbl=8656;e.arrowleftdblstroke=8653;e.arrowleftoverright=8646;e.arrowleftwhite=8678;e.arrowright=8594;e.arrowrightdblstroke=8655;e.arrowrightheavy=10142;e.arrowrightoverleft=8644;e.arrowrightwhite=8680;e.arrowtableft=8676;e.arrowtabright=8677;e.arrowup=8593;e.arrowupdn=8597;e.arrowupdnbse=8616;e.arrowupdownbase=8616;e.arrowupleft=8598;e.arrowupleftofdown=8645;e.arrowupright=8599;e.arrowupwhite=8679;e.arrowvertex=63718;e.asciicircum=94;e.asciicircummonospace=65342;e.asciitilde=126;e.asciitildemonospace=65374;e.ascript=593;e.ascriptturned=594;e.asmallhiragana=12353;e.asmallkatakana=12449;e.asmallkatakanahalfwidth=65383;e.asterisk=42;e.asteriskaltonearabic=1645;e.asteriskarabic=1645;e.asteriskmath=8727;e.asteriskmonospace=65290;e.asterisksmall=65121;e.asterism=8258;e.asuperior=63209;e.asymptoticallyequal=8771;e.at=64;e.atilde=227;e.atmonospace=65312;e.atsmall=65131;e.aturned=592;e.aubengali=2452;e.aubopomofo=12576;e.audeva=2324;e.augujarati=2708;e.augurmukhi=2580;e.aulengthmarkbengali=2519;e.aumatragurmukhi=2636;e.auvowelsignbengali=2508;e.auvowelsigndeva=2380;e.auvowelsigngujarati=2764;e.avagrahadeva=2365;e.aybarmenian=1377;e.ayin=1506;e.ayinaltonehebrew=64288;e.ayinhebrew=1506;e.b=98;e.babengali=2476;e.backslash=92;e.backslashmonospace=65340;e.badeva=2348;e.bagujarati=2732;e.bagurmukhi=2604;e.bahiragana=12400;e.bahtthai=3647;e.bakatakana=12496;e.bar=124;e.barmonospace=65372;e.bbopomofo=12549;e.bcircle=9425;e.bdotaccent=7683;e.bdotbelow=7685;e.beamedsixteenthnotes=9836;e.because=8757;e.becyrillic=1073;e.beharabic=1576;e.behfinalarabic=65168;e.behinitialarabic=65169;e.behiragana=12409;e.behmedialarabic=65170;e.behmeeminitialarabic=64671;e.behmeemisolatedarabic=64520;e.behnoonfinalarabic=64621;e.bekatakana=12505;e.benarmenian=1378;e.bet=1489;e.beta=946;e.betasymbolgreek=976;e.betdagesh=64305;e.betdageshhebrew=64305;e.bethebrew=1489;e.betrafehebrew=64332;e.bhabengali=2477;e.bhadeva=2349;e.bhagujarati=2733;e.bhagurmukhi=2605;e.bhook=595;e.bihiragana=12403;e.bikatakana=12499;e.bilabialclick=664;e.bindigurmukhi=2562;e.birusquare=13105;e.blackcircle=9679;e.blackdiamond=9670;e.blackdownpointingtriangle=9660;e.blackleftpointingpointer=9668;e.blackleftpointingtriangle=9664;e.blacklenticularbracketleft=12304;e.blacklenticularbracketleftvertical=65083;e.blacklenticularbracketright=12305;e.blacklenticularbracketrightvertical=65084;e.blacklowerlefttriangle=9699;e.blacklowerrighttriangle=9698;e.blackrectangle=9644;e.blackrightpointingpointer=9658;e.blackrightpointingtriangle=9654;e.blacksmallsquare=9642;e.blacksmilingface=9787;e.blacksquare=9632;e.blackstar=9733;e.blackupperlefttriangle=9700;e.blackupperrighttriangle=9701;e.blackuppointingsmalltriangle=9652;e.blackuppointingtriangle=9650;e.blank=9251;e.blinebelow=7687;e.block=9608;e.bmonospace=65346;e.bobaimaithai=3610;e.bohiragana=12412;e.bokatakana=12508;e.bparen=9373;e.bqsquare=13251;e.braceex=63732;e.braceleft=123;e.braceleftbt=63731;e.braceleftmid=63730;e.braceleftmonospace=65371;e.braceleftsmall=65115;e.bracelefttp=63729;e.braceleftvertical=65079;e.braceright=125;e.bracerightbt=63742;e.bracerightmid=63741;e.bracerightmonospace=65373;e.bracerightsmall=65116;e.bracerighttp=63740;e.bracerightvertical=65080;e.bracketleft=91;e.bracketleftbt=63728;e.bracketleftex=63727;e.bracketleftmonospace=65339;e.bracketlefttp=63726;e.bracketright=93;e.bracketrightbt=63739;e.bracketrightex=63738;e.bracketrightmonospace=65341;e.bracketrighttp=63737;e.breve=728;e.brevebelowcmb=814;e.brevecmb=774;e.breveinvertedbelowcmb=815;e.breveinvertedcmb=785;e.breveinverteddoublecmb=865;e.bridgebelowcmb=810;e.bridgeinvertedbelowcmb=826;e.brokenbar=166;e.bstroke=384;e.bsuperior=63210;e.btopbar=387;e.buhiragana=12406;e.bukatakana=12502;e.bullet=8226;e.bulletinverse=9688;e.bulletoperator=8729;e.bullseye=9678;e.c=99;e.caarmenian=1390;e.cabengali=2458;e.cacute=263;e.cadeva=2330;e.cagujarati=2714;e.cagurmukhi=2586;e.calsquare=13192;e.candrabindubengali=2433;e.candrabinducmb=784;e.candrabindudeva=2305;e.candrabindugujarati=2689;e.capslock=8682;e.careof=8453;e.caron=711;e.caronbelowcmb=812;e.caroncmb=780;e.carriagereturn=8629;e.cbopomofo=12568;e.ccaron=269;e.ccedilla=231;e.ccedillaacute=7689;e.ccircle=9426;e.ccircumflex=265;e.ccurl=597;e.cdot=267;e.cdotaccent=267;e.cdsquare=13253;e.cedilla=184;e.cedillacmb=807;e.cent=162;e.centigrade=8451;e.centinferior=63199;e.centmonospace=65504;e.centoldstyle=63394;e.centsuperior=63200;e.chaarmenian=1401;e.chabengali=2459;e.chadeva=2331;e.chagujarati=2715;e.chagurmukhi=2587;e.chbopomofo=12564;e.cheabkhasiancyrillic=1213;e.checkmark=10003;e.checyrillic=1095;e.chedescenderabkhasiancyrillic=1215;e.chedescendercyrillic=1207;e.chedieresiscyrillic=1269;e.cheharmenian=1395;e.chekhakassiancyrillic=1228;e.cheverticalstrokecyrillic=1209;e.chi=967;e.chieuchacirclekorean=12919;e.chieuchaparenkorean=12823;e.chieuchcirclekorean=12905;e.chieuchkorean=12618;e.chieuchparenkorean=12809;e.chochangthai=3594;e.chochanthai=3592;e.chochingthai=3593;e.chochoethai=3596;e.chook=392;e.cieucacirclekorean=12918;e.cieucaparenkorean=12822;e.cieuccirclekorean=12904;e.cieuckorean=12616;e.cieucparenkorean=12808;e.cieucuparenkorean=12828;e.circle=9675;e.circlecopyrt=169;e.circlemultiply=8855;e.circleot=8857;e.circleplus=8853;e.circlepostalmark=12342;e.circlewithlefthalfblack=9680;e.circlewithrighthalfblack=9681;e.circumflex=710;e.circumflexbelowcmb=813;e.circumflexcmb=770;e.clear=8999;e.clickalveolar=450;e.clickdental=448;e.clicklateral=449;e.clickretroflex=451;e.club=9827;e.clubsuitblack=9827;e.clubsuitwhite=9831;e.cmcubedsquare=13220;e.cmonospace=65347;e.cmsquaredsquare=13216;e.coarmenian=1409;e.colon=58;e.colonmonetary=8353;e.colonmonospace=65306;e.colonsign=8353;e.colonsmall=65109;e.colontriangularhalfmod=721;e.colontriangularmod=720;e.comma=44;e.commaabovecmb=787;e.commaaboverightcmb=789;e.commaaccent=63171;e.commaarabic=1548;e.commaarmenian=1373;e.commainferior=63201;e.commamonospace=65292;e.commareversedabovecmb=788;e.commareversedmod=701;e.commasmall=65104;e.commasuperior=63202;e.commaturnedabovecmb=786;e.commaturnedmod=699;e.compass=9788;e.congruent=8773;e.contourintegral=8750;e.control=8963;e.controlACK=6;e.controlBEL=7;e.controlBS=8;e.controlCAN=24;e.controlCR=13;e.controlDC1=17;e.controlDC2=18;e.controlDC3=19;e.controlDC4=20;e.controlDEL=127;e.controlDLE=16;e.controlEM=25;e.controlENQ=5;e.controlEOT=4;e.controlESC=27;e.controlETB=23;e.controlETX=3;e.controlFF=12;e.controlFS=28;e.controlGS=29;e.controlHT=9;e.controlLF=10;e.controlNAK=21;e.controlNULL=0;e.controlRS=30;e.controlSI=15;e.controlSO=14;e.controlSOT=2;e.controlSTX=1;e.controlSUB=26;e.controlSYN=22;e.controlUS=31;e.controlVT=11;e.copyright=169;e.copyrightsans=63721;e.copyrightserif=63193;e.cornerbracketleft=12300;e.cornerbracketlefthalfwidth=65378;e.cornerbracketleftvertical=65089;e.cornerbracketright=12301;e.cornerbracketrighthalfwidth=65379;e.cornerbracketrightvertical=65090;e.corporationsquare=13183;e.cosquare=13255;e.coverkgsquare=13254;e.cparen=9374;e.cruzeiro=8354;e.cstretched=663;e.curlyand=8911;e.curlyor=8910;e.currency=164;e.cyrBreve=63185;e.cyrFlex=63186;e.cyrbreve=63188;e.cyrflex=63189;e.d=100;e.daarmenian=1380;e.dabengali=2470;e.dadarabic=1590;e.dadeva=2342;e.dadfinalarabic=65214;e.dadinitialarabic=65215;e.dadmedialarabic=65216;e.dagesh=1468;e.dageshhebrew=1468;e.dagger=8224;e.daggerdbl=8225;e.dagujarati=2726;e.dagurmukhi=2598;e.dahiragana=12384;e.dakatakana=12480;e.dalarabic=1583;e.dalet=1491;e.daletdagesh=64307;e.daletdageshhebrew=64307;e.dalethebrew=1491;e.dalfinalarabic=65194;e.dammaarabic=1615;e.dammalowarabic=1615;e.dammatanaltonearabic=1612;e.dammatanarabic=1612;e.danda=2404;e.dargahebrew=1447;e.dargalefthebrew=1447;e.dasiapneumatacyrilliccmb=1157;e.dblGrave=63187;e.dblanglebracketleft=12298;e.dblanglebracketleftvertical=65085;e.dblanglebracketright=12299;e.dblanglebracketrightvertical=65086;e.dblarchinvertedbelowcmb=811;e.dblarrowleft=8660;e.dblarrowright=8658;e.dbldanda=2405;e.dblgrave=63190;e.dblgravecmb=783;e.dblintegral=8748;e.dbllowline=8215;e.dbllowlinecmb=819;e.dbloverlinecmb=831;e.dblprimemod=698;e.dblverticalbar=8214;e.dblverticallineabovecmb=782;e.dbopomofo=12553;e.dbsquare=13256;e.dcaron=271;e.dcedilla=7697;e.dcircle=9427;e.dcircumflexbelow=7699;e.dcroat=273;e.ddabengali=2465;e.ddadeva=2337;e.ddagujarati=2721;e.ddagurmukhi=2593;e.ddalarabic=1672;e.ddalfinalarabic=64393;e.dddhadeva=2396;e.ddhabengali=2466;e.ddhadeva=2338;e.ddhagujarati=2722;e.ddhagurmukhi=2594;e.ddotaccent=7691;e.ddotbelow=7693;e.decimalseparatorarabic=1643;e.decimalseparatorpersian=1643;e.decyrillic=1076;e.degree=176;e.dehihebrew=1453;e.dehiragana=12391;e.deicoptic=1007;e.dekatakana=12487;e.deleteleft=9003;e.deleteright=8998;e.delta=948;e.deltaturned=397;e.denominatorminusonenumeratorbengali=2552;e.dezh=676;e.dhabengali=2471;e.dhadeva=2343;e.dhagujarati=2727;e.dhagurmukhi=2599;e.dhook=599;e.dialytikatonos=901;e.dialytikatonoscmb=836;e.diamond=9830;e.diamondsuitwhite=9826;e.dieresis=168;e.dieresisacute=63191;e.dieresisbelowcmb=804;e.dieresiscmb=776;e.dieresisgrave=63192;e.dieresistonos=901;e.dihiragana=12386;e.dikatakana=12482;e.dittomark=12291;e.divide=247;e.divides=8739;e.divisionslash=8725;e.djecyrillic=1106;e.dkshade=9619;e.dlinebelow=7695;e.dlsquare=13207;e.dmacron=273;e.dmonospace=65348;e.dnblock=9604;e.dochadathai=3598;e.dodekthai=3604;e.dohiragana=12393;e.dokatakana=12489;e.dollar=36;e.dollarinferior=63203;e.dollarmonospace=65284;e.dollaroldstyle=63268;e.dollarsmall=65129;e.dollarsuperior=63204;e.dong=8363;e.dorusquare=13094;e.dotaccent=729;e.dotaccentcmb=775;e.dotbelowcmb=803;e.dotbelowcomb=803;e.dotkatakana=12539;e.dotlessi=305;e.dotlessj=63166;e.dotlessjstrokehook=644;e.dotmath=8901;e.dottedcircle=9676;e.doubleyodpatah=64287;e.doubleyodpatahhebrew=64287;e.downtackbelowcmb=798;e.downtackmod=725;e.dparen=9375;e.dsuperior=63211;e.dtail=598;e.dtopbar=396;e.duhiragana=12389;e.dukatakana=12485;e.dz=499;e.dzaltone=675;e.dzcaron=454;e.dzcurl=677;e.dzeabkhasiancyrillic=1249;e.dzecyrillic=1109;e.dzhecyrillic=1119;e.e=101;e.eacute=233;e.earth=9793;e.ebengali=2447;e.ebopomofo=12572;e.ebreve=277;e.ecandradeva=2317;e.ecandragujarati=2701;e.ecandravowelsigndeva=2373;e.ecandravowelsigngujarati=2757;e.ecaron=283;e.ecedillabreve=7709;e.echarmenian=1381;e.echyiwnarmenian=1415;e.ecircle=9428;e.ecircumflex=234;e.ecircumflexacute=7871;e.ecircumflexbelow=7705;e.ecircumflexdotbelow=7879;e.ecircumflexgrave=7873;e.ecircumflexhookabove=7875;e.ecircumflextilde=7877;e.ecyrillic=1108;e.edblgrave=517;e.edeva=2319;e.edieresis=235;e.edot=279;e.edotaccent=279;e.edotbelow=7865;e.eegurmukhi=2575;e.eematragurmukhi=2631;e.efcyrillic=1092;e.egrave=232;e.egujarati=2703;e.eharmenian=1383;e.ehbopomofo=12573;e.ehiragana=12360;e.ehookabove=7867;e.eibopomofo=12575;e.eight=56;e.eightarabic=1640;e.eightbengali=2542;e.eightcircle=9319;e.eightcircleinversesansserif=10129;e.eightdeva=2414;e.eighteencircle=9329;e.eighteenparen=9349;e.eighteenperiod=9369;e.eightgujarati=2798;e.eightgurmukhi=2670;e.eighthackarabic=1640;e.eighthangzhou=12328;e.eighthnotebeamed=9835;e.eightideographicparen=12839;e.eightinferior=8328;e.eightmonospace=65304;e.eightoldstyle=63288;e.eightparen=9339;e.eightperiod=9359;e.eightpersian=1784;e.eightroman=8567;e.eightsuperior=8312;e.eightthai=3672;e.einvertedbreve=519;e.eiotifiedcyrillic=1125;e.ekatakana=12456;e.ekatakanahalfwidth=65396;e.ekonkargurmukhi=2676;e.ekorean=12628;e.elcyrillic=1083;e.element=8712;e.elevencircle=9322;e.elevenparen=9342;e.elevenperiod=9362;e.elevenroman=8570;e.ellipsis=8230;e.ellipsisvertical=8942;e.emacron=275;e.emacronacute=7703;e.emacrongrave=7701;e.emcyrillic=1084;e.emdash=8212;e.emdashvertical=65073;e.emonospace=65349;e.emphasismarkarmenian=1371;e.emptyset=8709;e.enbopomofo=12579;e.encyrillic=1085;e.endash=8211;e.endashvertical=65074;e.endescendercyrillic=1187;e.eng=331;e.engbopomofo=12581;e.enghecyrillic=1189;e.enhookcyrillic=1224;e.enspace=8194;e.eogonek=281;e.eokorean=12627;e.eopen=603;e.eopenclosed=666;e.eopenreversed=604;e.eopenreversedclosed=606;e.eopenreversedhook=605;e.eparen=9376;e.epsilon=949;e.epsilontonos=941;e.equal=61;e.equalmonospace=65309;e.equalsmall=65126;e.equalsuperior=8316;e.equivalence=8801;e.erbopomofo=12582;e.ercyrillic=1088;e.ereversed=600;e.ereversedcyrillic=1101;e.escyrillic=1089;e.esdescendercyrillic=1195;e.esh=643;e.eshcurl=646;e.eshortdeva=2318;e.eshortvowelsigndeva=2374;e.eshreversedloop=426;e.eshsquatreversed=645;e.esmallhiragana=12359;e.esmallkatakana=12455;e.esmallkatakanahalfwidth=65386;e.estimated=8494;e.esuperior=63212;e.eta=951;e.etarmenian=1384;e.etatonos=942;e.eth=240;e.etilde=7869;e.etildebelow=7707;e.etnahtafoukhhebrew=1425;e.etnahtafoukhlefthebrew=1425;e.etnahtahebrew=1425;e.etnahtalefthebrew=1425;e.eturned=477;e.eukorean=12641;e.euro=8364;e.evowelsignbengali=2503;e.evowelsigndeva=2375;e.evowelsigngujarati=2759;e.exclam=33;e.exclamarmenian=1372;e.exclamdbl=8252;e.exclamdown=161;e.exclamdownsmall=63393;e.exclammonospace=65281;e.exclamsmall=63265;e.existential=8707;e.ezh=658;e.ezhcaron=495;e.ezhcurl=659;e.ezhreversed=441;e.ezhtail=442;e.f=102;e.fadeva=2398;e.fagurmukhi=2654;e.fahrenheit=8457;e.fathaarabic=1614;e.fathalowarabic=1614;e.fathatanarabic=1611;e.fbopomofo=12552;e.fcircle=9429;e.fdotaccent=7711;e.feharabic=1601;e.feharmenian=1414;e.fehfinalarabic=65234;e.fehinitialarabic=65235;e.fehmedialarabic=65236;e.feicoptic=997;e.female=9792;e.ff=64256;e.f_f=64256;e.ffi=64259;e.f_f_i=64259;e.ffl=64260;e.f_f_l=64260;e.fi=64257;e.f_i=64257;e.fifteencircle=9326;e.fifteenparen=9346;e.fifteenperiod=9366;e.figuredash=8210;e.filledbox=9632;e.filledrect=9644;e.finalkaf=1498;e.finalkafdagesh=64314;e.finalkafdageshhebrew=64314;e.finalkafhebrew=1498;e.finalmem=1501;e.finalmemhebrew=1501;e.finalnun=1503;e.finalnunhebrew=1503;e.finalpe=1507;e.finalpehebrew=1507;e.finaltsadi=1509;e.finaltsadihebrew=1509;e.firsttonechinese=713;e.fisheye=9673;e.fitacyrillic=1139;e.five=53;e.fivearabic=1637;e.fivebengali=2539;e.fivecircle=9316;e.fivecircleinversesansserif=10126;e.fivedeva=2411;e.fiveeighths=8541;e.fivegujarati=2795;e.fivegurmukhi=2667;e.fivehackarabic=1637;e.fivehangzhou=12325;e.fiveideographicparen=12836;e.fiveinferior=8325;e.fivemonospace=65301;e.fiveoldstyle=63285;e.fiveparen=9336;e.fiveperiod=9356;e.fivepersian=1781;e.fiveroman=8564;e.fivesuperior=8309;e.fivethai=3669;e.fl=64258;e.f_l=64258;e.florin=402;e.fmonospace=65350;e.fmsquare=13209;e.fofanthai=3615;e.fofathai=3613;e.fongmanthai=3663;e.forall=8704;e.four=52;e.fourarabic=1636;e.fourbengali=2538;e.fourcircle=9315;e.fourcircleinversesansserif=10125;e.fourdeva=2410;e.fourgujarati=2794;e.fourgurmukhi=2666;e.fourhackarabic=1636;e.fourhangzhou=12324;e.fourideographicparen=12835;e.fourinferior=8324;e.fourmonospace=65300;e.fournumeratorbengali=2551;e.fouroldstyle=63284;e.fourparen=9335;e.fourperiod=9355;e.fourpersian=1780;e.fourroman=8563;e.foursuperior=8308;e.fourteencircle=9325;e.fourteenparen=9345;e.fourteenperiod=9365;e.fourthai=3668;e.fourthtonechinese=715;e.fparen=9377;e.fraction=8260;e.franc=8355;e.g=103;e.gabengali=2455;e.gacute=501;e.gadeva=2327;e.gafarabic=1711;e.gaffinalarabic=64403;e.gafinitialarabic=64404;e.gafmedialarabic=64405;e.gagujarati=2711;e.gagurmukhi=2583;e.gahiragana=12364;e.gakatakana=12460;e.gamma=947;e.gammalatinsmall=611;e.gammasuperior=736;e.gangiacoptic=1003;e.gbopomofo=12557;e.gbreve=287;e.gcaron=487;e.gcedilla=291;e.gcircle=9430;e.gcircumflex=285;e.gcommaaccent=291;e.gdot=289;e.gdotaccent=289;e.gecyrillic=1075;e.gehiragana=12370;e.gekatakana=12466;e.geometricallyequal=8785;e.gereshaccenthebrew=1436;e.gereshhebrew=1523;e.gereshmuqdamhebrew=1437;e.germandbls=223;e.gershayimaccenthebrew=1438;e.gershayimhebrew=1524;e.getamark=12307;e.ghabengali=2456;e.ghadarmenian=1394;e.ghadeva=2328;e.ghagujarati=2712;e.ghagurmukhi=2584;e.ghainarabic=1594;e.ghainfinalarabic=65230;e.ghaininitialarabic=65231;e.ghainmedialarabic=65232;e.ghemiddlehookcyrillic=1173;e.ghestrokecyrillic=1171;e.gheupturncyrillic=1169;e.ghhadeva=2394;e.ghhagurmukhi=2650;e.ghook=608;e.ghzsquare=13203;e.gihiragana=12366;e.gikatakana=12462;e.gimarmenian=1379;e.gimel=1490;e.gimeldagesh=64306;e.gimeldageshhebrew=64306;e.gimelhebrew=1490;e.gjecyrillic=1107;e.glottalinvertedstroke=446;e.glottalstop=660;e.glottalstopinverted=662;e.glottalstopmod=704;e.glottalstopreversed=661;e.glottalstopreversedmod=705;e.glottalstopreversedsuperior=740;e.glottalstopstroke=673;e.glottalstopstrokereversed=674;e.gmacron=7713;e.gmonospace=65351;e.gohiragana=12372;e.gokatakana=12468;e.gparen=9378;e.gpasquare=13228;e.gradient=8711;e.grave=96;e.gravebelowcmb=790;e.gravecmb=768;e.gravecomb=768;e.gravedeva=2387;e.gravelowmod=718;e.gravemonospace=65344;e.gravetonecmb=832;e.greater=62;e.greaterequal=8805;e.greaterequalorless=8923;e.greatermonospace=65310;e.greaterorequivalent=8819;e.greaterorless=8823;e.greateroverequal=8807;e.greatersmall=65125;e.gscript=609;e.gstroke=485;e.guhiragana=12368;e.guillemotleft=171;e.guillemotright=187;e.guilsinglleft=8249;e.guilsinglright=8250;e.gukatakana=12464;e.guramusquare=13080;e.gysquare=13257;e.h=104;e.haabkhasiancyrillic=1193;e.haaltonearabic=1729;e.habengali=2489;e.hadescendercyrillic=1203;e.hadeva=2361;e.hagujarati=2745;e.hagurmukhi=2617;e.haharabic=1581;e.hahfinalarabic=65186;e.hahinitialarabic=65187;e.hahiragana=12399;e.hahmedialarabic=65188;e.haitusquare=13098;e.hakatakana=12495;e.hakatakanahalfwidth=65418;e.halantgurmukhi=2637;e.hamzaarabic=1569;e.hamzalowarabic=1569;e.hangulfiller=12644;e.hardsigncyrillic=1098;e.harpoonleftbarbup=8636;e.harpoonrightbarbup=8640;e.hasquare=13258;e.hatafpatah=1458;e.hatafpatah16=1458;e.hatafpatah23=1458;e.hatafpatah2f=1458;e.hatafpatahhebrew=1458;e.hatafpatahnarrowhebrew=1458;e.hatafpatahquarterhebrew=1458;e.hatafpatahwidehebrew=1458;e.hatafqamats=1459;e.hatafqamats1b=1459;e.hatafqamats28=1459;e.hatafqamats34=1459;e.hatafqamatshebrew=1459;e.hatafqamatsnarrowhebrew=1459;e.hatafqamatsquarterhebrew=1459;e.hatafqamatswidehebrew=1459;e.hatafsegol=1457;e.hatafsegol17=1457;e.hatafsegol24=1457;e.hatafsegol30=1457;e.hatafsegolhebrew=1457;e.hatafsegolnarrowhebrew=1457;e.hatafsegolquarterhebrew=1457;e.hatafsegolwidehebrew=1457;e.hbar=295;e.hbopomofo=12559;e.hbrevebelow=7723;e.hcedilla=7721;e.hcircle=9431;e.hcircumflex=293;e.hdieresis=7719;e.hdotaccent=7715;e.hdotbelow=7717;e.he=1492;e.heart=9829;e.heartsuitblack=9829;e.heartsuitwhite=9825;e.hedagesh=64308;e.hedageshhebrew=64308;e.hehaltonearabic=1729;e.heharabic=1607;e.hehebrew=1492;e.hehfinalaltonearabic=64423;e.hehfinalalttwoarabic=65258;e.hehfinalarabic=65258;e.hehhamzaabovefinalarabic=64421;e.hehhamzaaboveisolatedarabic=64420;e.hehinitialaltonearabic=64424;e.hehinitialarabic=65259;e.hehiragana=12408;e.hehmedialaltonearabic=64425;e.hehmedialarabic=65260;e.heiseierasquare=13179;e.hekatakana=12504;e.hekatakanahalfwidth=65421;e.hekutaarusquare=13110;e.henghook=615;e.herutusquare=13113;e.het=1495;e.hethebrew=1495;e.hhook=614;e.hhooksuperior=689;e.hieuhacirclekorean=12923;e.hieuhaparenkorean=12827;e.hieuhcirclekorean=12909;e.hieuhkorean=12622;e.hieuhparenkorean=12813;e.hihiragana=12402;e.hikatakana=12498;e.hikatakanahalfwidth=65419;e.hiriq=1460;e.hiriq14=1460;e.hiriq21=1460;e.hiriq2d=1460;e.hiriqhebrew=1460;e.hiriqnarrowhebrew=1460;e.hiriqquarterhebrew=1460;e.hiriqwidehebrew=1460;e.hlinebelow=7830;e.hmonospace=65352;e.hoarmenian=1392;e.hohipthai=3627;e.hohiragana=12411;e.hokatakana=12507;e.hokatakanahalfwidth=65422;e.holam=1465;e.holam19=1465;e.holam26=1465;e.holam32=1465;e.holamhebrew=1465;e.holamnarrowhebrew=1465;e.holamquarterhebrew=1465;e.holamwidehebrew=1465;e.honokhukthai=3630;e.hookabovecomb=777;e.hookcmb=777;e.hookpalatalizedbelowcmb=801;e.hookretroflexbelowcmb=802;e.hoonsquare=13122;e.horicoptic=1001;e.horizontalbar=8213;e.horncmb=795;e.hotsprings=9832;e.house=8962;e.hparen=9379;e.hsuperior=688;e.hturned=613;e.huhiragana=12405;e.huiitosquare=13107;e.hukatakana=12501;e.hukatakanahalfwidth=65420;e.hungarumlaut=733;e.hungarumlautcmb=779;e.hv=405;e.hyphen=45;e.hypheninferior=63205;e.hyphenmonospace=65293;e.hyphensmall=65123;e.hyphensuperior=63206;e.hyphentwo=8208;e.i=105;e.iacute=237;e.iacyrillic=1103;e.ibengali=2439;e.ibopomofo=12583;e.ibreve=301;e.icaron=464;e.icircle=9432;e.icircumflex=238;e.icyrillic=1110;e.idblgrave=521;e.ideographearthcircle=12943;e.ideographfirecircle=12939;e.ideographicallianceparen=12863;e.ideographiccallparen=12858;e.ideographiccentrecircle=12965;e.ideographicclose=12294;e.ideographiccomma=12289;e.ideographiccommaleft=65380;e.ideographiccongratulationparen=12855;e.ideographiccorrectcircle=12963;e.ideographicearthparen=12847;e.ideographicenterpriseparen=12861;e.ideographicexcellentcircle=12957;e.ideographicfestivalparen=12864;e.ideographicfinancialcircle=12950;e.ideographicfinancialparen=12854;e.ideographicfireparen=12843;e.ideographichaveparen=12850;e.ideographichighcircle=12964;e.ideographiciterationmark=12293;e.ideographiclaborcircle=12952;e.ideographiclaborparen=12856;e.ideographicleftcircle=12967;e.ideographiclowcircle=12966;e.ideographicmedicinecircle=12969;e.ideographicmetalparen=12846;e.ideographicmoonparen=12842;e.ideographicnameparen=12852;e.ideographicperiod=12290;e.ideographicprintcircle=12958;e.ideographicreachparen=12867;e.ideographicrepresentparen=12857;e.ideographicresourceparen=12862;e.ideographicrightcircle=12968;e.ideographicsecretcircle=12953;e.ideographicselfparen=12866;e.ideographicsocietyparen=12851;e.ideographicspace=12288;e.ideographicspecialparen=12853;e.ideographicstockparen=12849;e.ideographicstudyparen=12859;e.ideographicsunparen=12848;e.ideographicsuperviseparen=12860;e.ideographicwaterparen=12844;e.ideographicwoodparen=12845;e.ideographiczero=12295;e.ideographmetalcircle=12942;e.ideographmooncircle=12938;e.ideographnamecircle=12948;e.ideographsuncircle=12944;e.ideographwatercircle=12940;e.ideographwoodcircle=12941;e.ideva=2311;e.idieresis=239;e.idieresisacute=7727;e.idieresiscyrillic=1253;e.idotbelow=7883;e.iebrevecyrillic=1239;e.iecyrillic=1077;e.ieungacirclekorean=12917;e.ieungaparenkorean=12821;e.ieungcirclekorean=12903;e.ieungkorean=12615;e.ieungparenkorean=12807;e.igrave=236;e.igujarati=2695;e.igurmukhi=2567;e.ihiragana=12356;e.ihookabove=7881;e.iibengali=2440;e.iicyrillic=1080;e.iideva=2312;e.iigujarati=2696;e.iigurmukhi=2568;e.iimatragurmukhi=2624;e.iinvertedbreve=523;e.iishortcyrillic=1081;e.iivowelsignbengali=2496;e.iivowelsigndeva=2368;e.iivowelsigngujarati=2752;e.ij=307;e.ikatakana=12452;e.ikatakanahalfwidth=65394;e.ikorean=12643;e.ilde=732;e.iluyhebrew=1452;e.imacron=299;e.imacroncyrillic=1251;e.imageorapproximatelyequal=8787;e.imatragurmukhi=2623;e.imonospace=65353;e.increment=8710;e.infinity=8734;e.iniarmenian=1387;e.integral=8747;e.integralbottom=8993;e.integralbt=8993;e.integralex=63733;e.integraltop=8992;e.integraltp=8992;e.intersection=8745;e.intisquare=13061;e.invbullet=9688;e.invcircle=9689;e.invsmileface=9787;e.iocyrillic=1105;e.iogonek=303;e.iota=953;e.iotadieresis=970;e.iotadieresistonos=912;e.iotalatin=617;e.iotatonos=943;e.iparen=9380;e.irigurmukhi=2674;e.ismallhiragana=12355;e.ismallkatakana=12451;e.ismallkatakanahalfwidth=65384;e.issharbengali=2554;e.istroke=616;e.isuperior=63213;e.iterationhiragana=12445;e.iterationkatakana=12541;e.itilde=297;e.itildebelow=7725;e.iubopomofo=12585;e.iucyrillic=1102;e.ivowelsignbengali=2495;e.ivowelsigndeva=2367;e.ivowelsigngujarati=2751;e.izhitsacyrillic=1141;e.izhitsadblgravecyrillic=1143;e.j=106;e.jaarmenian=1393;e.jabengali=2460;e.jadeva=2332;e.jagujarati=2716;e.jagurmukhi=2588;e.jbopomofo=12560;e.jcaron=496;e.jcircle=9433;e.jcircumflex=309;e.jcrossedtail=669;e.jdotlessstroke=607;e.jecyrillic=1112;e.jeemarabic=1580;e.jeemfinalarabic=65182;e.jeeminitialarabic=65183;e.jeemmedialarabic=65184;e.jeharabic=1688;e.jehfinalarabic=64395;e.jhabengali=2461;e.jhadeva=2333;e.jhagujarati=2717;e.jhagurmukhi=2589;e.jheharmenian=1403;e.jis=12292;e.jmonospace=65354;e.jparen=9381;e.jsuperior=690;e.k=107;e.kabashkircyrillic=1185;e.kabengali=2453;e.kacute=7729;e.kacyrillic=1082;e.kadescendercyrillic=1179;e.kadeva=2325;e.kaf=1499;e.kafarabic=1603;e.kafdagesh=64315;e.kafdageshhebrew=64315;e.kaffinalarabic=65242;e.kafhebrew=1499;e.kafinitialarabic=65243;e.kafmedialarabic=65244;e.kafrafehebrew=64333;e.kagujarati=2709;e.kagurmukhi=2581;e.kahiragana=12363;e.kahookcyrillic=1220;e.kakatakana=12459;e.kakatakanahalfwidth=65398;e.kappa=954;e.kappasymbolgreek=1008;e.kapyeounmieumkorean=12657;e.kapyeounphieuphkorean=12676;e.kapyeounpieupkorean=12664;e.kapyeounssangpieupkorean=12665;e.karoriisquare=13069;e.kashidaautoarabic=1600;e.kashidaautonosidebearingarabic=1600;e.kasmallkatakana=12533;e.kasquare=13188;e.kasraarabic=1616;e.kasratanarabic=1613;e.kastrokecyrillic=1183;e.katahiraprolongmarkhalfwidth=65392;e.kaverticalstrokecyrillic=1181;e.kbopomofo=12558;e.kcalsquare=13193;e.kcaron=489;e.kcedilla=311;e.kcircle=9434;e.kcommaaccent=311;e.kdotbelow=7731;e.keharmenian=1412;e.kehiragana=12369;e.kekatakana=12465;e.kekatakanahalfwidth=65401;e.kenarmenian=1391;e.kesmallkatakana=12534;e.kgreenlandic=312;e.khabengali=2454;e.khacyrillic=1093;e.khadeva=2326;e.khagujarati=2710;e.khagurmukhi=2582;e.khaharabic=1582;e.khahfinalarabic=65190;e.khahinitialarabic=65191;e.khahmedialarabic=65192;e.kheicoptic=999;e.khhadeva=2393;e.khhagurmukhi=2649;e.khieukhacirclekorean=12920;e.khieukhaparenkorean=12824;e.khieukhcirclekorean=12906;e.khieukhkorean=12619;e.khieukhparenkorean=12810;e.khokhaithai=3586;e.khokhonthai=3589;e.khokhuatthai=3587;e.khokhwaithai=3588;e.khomutthai=3675;e.khook=409;e.khorakhangthai=3590;e.khzsquare=13201;e.kihiragana=12365;e.kikatakana=12461;e.kikatakanahalfwidth=65399;e.kiroguramusquare=13077;e.kiromeetorusquare=13078;e.kirosquare=13076;e.kiyeokacirclekorean=12910;e.kiyeokaparenkorean=12814;e.kiyeokcirclekorean=12896;e.kiyeokkorean=12593;e.kiyeokparenkorean=12800;e.kiyeoksioskorean=12595;e.kjecyrillic=1116;e.klinebelow=7733;e.klsquare=13208;e.kmcubedsquare=13222;e.kmonospace=65355;e.kmsquaredsquare=13218;e.kohiragana=12371;e.kohmsquare=13248;e.kokaithai=3585;e.kokatakana=12467;e.kokatakanahalfwidth=65402;e.kooposquare=13086;e.koppacyrillic=1153;e.koreanstandardsymbol=12927;e.koroniscmb=835;e.kparen=9382;e.kpasquare=13226;e.ksicyrillic=1135;e.ktsquare=13263;e.kturned=670;e.kuhiragana=12367;e.kukatakana=12463;e.kukatakanahalfwidth=65400;e.kvsquare=13240;e.kwsquare=13246;e.l=108;e.labengali=2482;e.lacute=314;e.ladeva=2354;e.lagujarati=2738;e.lagurmukhi=2610;e.lakkhangyaothai=3653;e.lamaleffinalarabic=65276;e.lamalefhamzaabovefinalarabic=65272;e.lamalefhamzaaboveisolatedarabic=65271;e.lamalefhamzabelowfinalarabic=65274;e.lamalefhamzabelowisolatedarabic=65273;e.lamalefisolatedarabic=65275;e.lamalefmaddaabovefinalarabic=65270;e.lamalefmaddaaboveisolatedarabic=65269;e.lamarabic=1604;e.lambda=955;e.lambdastroke=411;e.lamed=1500;e.lameddagesh=64316;e.lameddageshhebrew=64316;e.lamedhebrew=1500;e.lamfinalarabic=65246;e.lamhahinitialarabic=64714;e.laminitialarabic=65247;e.lamjeeminitialarabic=64713;e.lamkhahinitialarabic=64715;e.lamlamhehisolatedarabic=65010;e.lammedialarabic=65248;e.lammeemhahinitialarabic=64904;e.lammeeminitialarabic=64716;e.largecircle=9711;e.lbar=410;e.lbelt=620;e.lbopomofo=12556;e.lcaron=318;e.lcedilla=316;e.lcircle=9435;e.lcircumflexbelow=7741;e.lcommaaccent=316;e.ldot=320;e.ldotaccent=320;e.ldotbelow=7735;e.ldotbelowmacron=7737;e.leftangleabovecmb=794;e.lefttackbelowcmb=792;e.less=60;e.lessequal=8804;e.lessequalorgreater=8922;e.lessmonospace=65308;e.lessorequivalent=8818;e.lessorgreater=8822;e.lessoverequal=8806;e.lesssmall=65124;e.lezh=622;e.lfblock=9612;e.lhookretroflex=621;e.lira=8356;e.liwnarmenian=1388;e.lj=457;e.ljecyrillic=1113;e.ll=63168;e.lladeva=2355;e.llagujarati=2739;e.llinebelow=7739;e.llladeva=2356;e.llvocalicbengali=2529;e.llvocalicdeva=2401;e.llvocalicvowelsignbengali=2531;e.llvocalicvowelsigndeva=2403;e.lmiddletilde=619;e.lmonospace=65356;e.lmsquare=13264;e.lochulathai=3628;e.logicaland=8743;e.logicalnot=172;e.logicalnotreversed=8976;e.logicalor=8744;e.lolingthai=3621;e.longs=383;e.lowlinecenterline=65102;e.lowlinecmb=818;e.lowlinedashed=65101;e.lozenge=9674;e.lparen=9383;e.lslash=322;e.lsquare=8467;e.lsuperior=63214;e.ltshade=9617;e.luthai=3622;e.lvocalicbengali=2444;e.lvocalicdeva=2316;e.lvocalicvowelsignbengali=2530;e.lvocalicvowelsigndeva=2402;e.lxsquare=13267;e.m=109;e.mabengali=2478;e.macron=175;e.macronbelowcmb=817;e.macroncmb=772;e.macronlowmod=717;e.macronmonospace=65507;e.macute=7743;e.madeva=2350;e.magujarati=2734;e.magurmukhi=2606;e.mahapakhhebrew=1444;e.mahapakhlefthebrew=1444;e.mahiragana=12414;e.maichattawalowleftthai=63637;e.maichattawalowrightthai=63636;e.maichattawathai=3659;e.maichattawaupperleftthai=63635;e.maieklowleftthai=63628;e.maieklowrightthai=63627;e.maiekthai=3656;e.maiekupperleftthai=63626;e.maihanakatleftthai=63620;e.maihanakatthai=3633;e.maitaikhuleftthai=63625;e.maitaikhuthai=3655;e.maitholowleftthai=63631;e.maitholowrightthai=63630;e.maithothai=3657;e.maithoupperleftthai=63629;e.maitrilowleftthai=63634;e.maitrilowrightthai=63633;e.maitrithai=3658;e.maitriupperleftthai=63632;e.maiyamokthai=3654;e.makatakana=12510;e.makatakanahalfwidth=65423;e.male=9794;e.mansyonsquare=13127;e.maqafhebrew=1470;e.mars=9794;e.masoracirclehebrew=1455;e.masquare=13187;e.mbopomofo=12551;e.mbsquare=13268;e.mcircle=9436;e.mcubedsquare=13221;e.mdotaccent=7745;e.mdotbelow=7747;e.meemarabic=1605;e.meemfinalarabic=65250;e.meeminitialarabic=65251;e.meemmedialarabic=65252;e.meemmeeminitialarabic=64721;e.meemmeemisolatedarabic=64584;e.meetorusquare=13133;e.mehiragana=12417;e.meizierasquare=13182;e.mekatakana=12513;e.mekatakanahalfwidth=65426;e.mem=1502;e.memdagesh=64318;e.memdageshhebrew=64318;e.memhebrew=1502;e.menarmenian=1396;e.merkhahebrew=1445;e.merkhakefulahebrew=1446;e.merkhakefulalefthebrew=1446;e.merkhalefthebrew=1445;e.mhook=625;e.mhzsquare=13202;e.middledotkatakanahalfwidth=65381;e.middot=183;e.mieumacirclekorean=12914;e.mieumaparenkorean=12818;e.mieumcirclekorean=12900;e.mieumkorean=12609;e.mieumpansioskorean=12656;e.mieumparenkorean=12804;e.mieumpieupkorean=12654;e.mieumsioskorean=12655;e.mihiragana=12415;e.mikatakana=12511;e.mikatakanahalfwidth=65424;e.minus=8722;e.minusbelowcmb=800;e.minuscircle=8854;e.minusmod=727;e.minusplus=8723;e.minute=8242;e.miribaarusquare=13130;e.mirisquare=13129;e.mlonglegturned=624;e.mlsquare=13206;e.mmcubedsquare=13219;e.mmonospace=65357;e.mmsquaredsquare=13215;e.mohiragana=12418;e.mohmsquare=13249;e.mokatakana=12514;e.mokatakanahalfwidth=65427;e.molsquare=13270;e.momathai=3617;e.moverssquare=13223;e.moverssquaredsquare=13224;e.mparen=9384;e.mpasquare=13227;e.mssquare=13235;e.msuperior=63215;e.mturned=623;e.mu=181;e.mu1=181;e.muasquare=13186;e.muchgreater=8811;e.muchless=8810;e.mufsquare=13196;e.mugreek=956;e.mugsquare=13197;e.muhiragana=12416;e.mukatakana=12512;e.mukatakanahalfwidth=65425;e.mulsquare=13205;e.multiply=215;e.mumsquare=13211;e.munahhebrew=1443;e.munahlefthebrew=1443;e.musicalnote=9834;e.musicalnotedbl=9835;e.musicflatsign=9837;e.musicsharpsign=9839;e.mussquare=13234;e.muvsquare=13238;e.muwsquare=13244;e.mvmegasquare=13241;e.mvsquare=13239;e.mwmegasquare=13247;e.mwsquare=13245;e.n=110;e.nabengali=2472;e.nabla=8711;e.nacute=324;e.nadeva=2344;e.nagujarati=2728;e.nagurmukhi=2600;e.nahiragana=12394;e.nakatakana=12490;e.nakatakanahalfwidth=65413;e.napostrophe=329;e.nasquare=13185;e.nbopomofo=12555;e.nbspace=160;e.ncaron=328;e.ncedilla=326;e.ncircle=9437;e.ncircumflexbelow=7755;e.ncommaaccent=326;e.ndotaccent=7749;e.ndotbelow=7751;e.nehiragana=12397;e.nekatakana=12493;e.nekatakanahalfwidth=65416;e.newsheqelsign=8362;e.nfsquare=13195;e.ngabengali=2457;e.ngadeva=2329;e.ngagujarati=2713;e.ngagurmukhi=2585;e.ngonguthai=3591;e.nhiragana=12435;e.nhookleft=626;e.nhookretroflex=627;e.nieunacirclekorean=12911;e.nieunaparenkorean=12815;e.nieuncieuckorean=12597;e.nieuncirclekorean=12897;e.nieunhieuhkorean=12598;e.nieunkorean=12596;e.nieunpansioskorean=12648;e.nieunparenkorean=12801;e.nieunsioskorean=12647;e.nieuntikeutkorean=12646;e.nihiragana=12395;e.nikatakana=12491;e.nikatakanahalfwidth=65414;e.nikhahitleftthai=63641;e.nikhahitthai=3661;e.nine=57;e.ninearabic=1641;e.ninebengali=2543;e.ninecircle=9320;e.ninecircleinversesansserif=10130;e.ninedeva=2415;e.ninegujarati=2799;e.ninegurmukhi=2671;e.ninehackarabic=1641;e.ninehangzhou=12329;e.nineideographicparen=12840;e.nineinferior=8329;e.ninemonospace=65305;e.nineoldstyle=63289;e.nineparen=9340;e.nineperiod=9360;e.ninepersian=1785;e.nineroman=8568;e.ninesuperior=8313;e.nineteencircle=9330;e.nineteenparen=9350;e.nineteenperiod=9370;e.ninethai=3673;e.nj=460;e.njecyrillic=1114;e.nkatakana=12531;e.nkatakanahalfwidth=65437;e.nlegrightlong=414;e.nlinebelow=7753;e.nmonospace=65358;e.nmsquare=13210;e.nnabengali=2467;e.nnadeva=2339;e.nnagujarati=2723;e.nnagurmukhi=2595;e.nnnadeva=2345;e.nohiragana=12398;e.nokatakana=12494;e.nokatakanahalfwidth=65417;e.nonbreakingspace=160;e.nonenthai=3603;e.nonuthai=3609;e.noonarabic=1606;e.noonfinalarabic=65254;e.noonghunnaarabic=1722;e.noonghunnafinalarabic=64415;e.nooninitialarabic=65255;e.noonjeeminitialarabic=64722;e.noonjeemisolatedarabic=64587;e.noonmedialarabic=65256;e.noonmeeminitialarabic=64725;e.noonmeemisolatedarabic=64590;e.noonnoonfinalarabic=64653;e.notcontains=8716;e.notelement=8713;e.notelementof=8713;e.notequal=8800;e.notgreater=8815;e.notgreaternorequal=8817;e.notgreaternorless=8825;e.notidentical=8802;e.notless=8814;e.notlessnorequal=8816;e.notparallel=8742;e.notprecedes=8832;e.notsubset=8836;e.notsucceeds=8833;e.notsuperset=8837;e.nowarmenian=1398;e.nparen=9385;e.nssquare=13233;e.nsuperior=8319;e.ntilde=241;e.nu=957;e.nuhiragana=12396;e.nukatakana=12492;e.nukatakanahalfwidth=65415;e.nuktabengali=2492;e.nuktadeva=2364;e.nuktagujarati=2748;e.nuktagurmukhi=2620;e.numbersign=35;e.numbersignmonospace=65283;e.numbersignsmall=65119;e.numeralsigngreek=884;e.numeralsignlowergreek=885;e.numero=8470;e.nun=1504;e.nundagesh=64320;e.nundageshhebrew=64320;e.nunhebrew=1504;e.nvsquare=13237;e.nwsquare=13243;e.nyabengali=2462;e.nyadeva=2334;e.nyagujarati=2718;e.nyagurmukhi=2590;e.o=111;e.oacute=243;e.oangthai=3629;e.obarred=629;e.obarredcyrillic=1257;e.obarreddieresiscyrillic=1259;e.obengali=2451;e.obopomofo=12571;e.obreve=335;e.ocandradeva=2321;e.ocandragujarati=2705;e.ocandravowelsigndeva=2377;e.ocandravowelsigngujarati=2761;e.ocaron=466;e.ocircle=9438;e.ocircumflex=244;e.ocircumflexacute=7889;e.ocircumflexdotbelow=7897;e.ocircumflexgrave=7891;e.ocircumflexhookabove=7893;e.ocircumflextilde=7895;e.ocyrillic=1086;e.odblacute=337;e.odblgrave=525;e.odeva=2323;e.odieresis=246;e.odieresiscyrillic=1255;e.odotbelow=7885;e.oe=339;e.oekorean=12634;e.ogonek=731;e.ogonekcmb=808;e.ograve=242;e.ogujarati=2707;e.oharmenian=1413;e.ohiragana=12362;e.ohookabove=7887;e.ohorn=417;e.ohornacute=7899;e.ohorndotbelow=7907;e.ohorngrave=7901;e.ohornhookabove=7903;e.ohorntilde=7905;e.ohungarumlaut=337;e.oi=419;e.oinvertedbreve=527;e.okatakana=12458;e.okatakanahalfwidth=65397;e.okorean=12631;e.olehebrew=1451;e.omacron=333;e.omacronacute=7763;e.omacrongrave=7761;e.omdeva=2384;e.omega=969;e.omega1=982;e.omegacyrillic=1121;e.omegalatinclosed=631;e.omegaroundcyrillic=1147;e.omegatitlocyrillic=1149;e.omegatonos=974;e.omgujarati=2768;e.omicron=959;e.omicrontonos=972;e.omonospace=65359;e.one=49;e.onearabic=1633;e.onebengali=2535;e.onecircle=9312;e.onecircleinversesansserif=10122;e.onedeva=2407;e.onedotenleader=8228;e.oneeighth=8539;e.onefitted=63196;e.onegujarati=2791;e.onegurmukhi=2663;e.onehackarabic=1633;e.onehalf=189;e.onehangzhou=12321;e.oneideographicparen=12832;e.oneinferior=8321;e.onemonospace=65297;e.onenumeratorbengali=2548;e.oneoldstyle=63281;e.oneparen=9332;e.oneperiod=9352;e.onepersian=1777;e.onequarter=188;e.oneroman=8560;e.onesuperior=185;e.onethai=3665;e.onethird=8531;e.oogonek=491;e.oogonekmacron=493;e.oogurmukhi=2579;e.oomatragurmukhi=2635;e.oopen=596;e.oparen=9386;e.openbullet=9702;e.option=8997;e.ordfeminine=170;e.ordmasculine=186;e.orthogonal=8735;e.oshortdeva=2322;e.oshortvowelsigndeva=2378;e.oslash=248;e.oslashacute=511;e.osmallhiragana=12361;e.osmallkatakana=12457;e.osmallkatakanahalfwidth=65387;e.ostrokeacute=511;e.osuperior=63216;e.otcyrillic=1151;e.otilde=245;e.otildeacute=7757;e.otildedieresis=7759;e.oubopomofo=12577;e.overline=8254;e.overlinecenterline=65098;e.overlinecmb=773;e.overlinedashed=65097;e.overlinedblwavy=65100;e.overlinewavy=65099;e.overscore=175;e.ovowelsignbengali=2507;e.ovowelsigndeva=2379;e.ovowelsigngujarati=2763;e.p=112;e.paampssquare=13184;e.paasentosquare=13099;e.pabengali=2474;e.pacute=7765;e.padeva=2346;e.pagedown=8671;e.pageup=8670;e.pagujarati=2730;e.pagurmukhi=2602;e.pahiragana=12401;e.paiyannoithai=3631;e.pakatakana=12497;e.palatalizationcyrilliccmb=1156;e.palochkacyrillic=1216;e.pansioskorean=12671;e.paragraph=182;e.parallel=8741;e.parenleft=40;e.parenleftaltonearabic=64830;e.parenleftbt=63725;e.parenleftex=63724;e.parenleftinferior=8333;e.parenleftmonospace=65288;e.parenleftsmall=65113;e.parenleftsuperior=8317;e.parenlefttp=63723;e.parenleftvertical=65077;e.parenright=41;e.parenrightaltonearabic=64831;e.parenrightbt=63736;e.parenrightex=63735;e.parenrightinferior=8334;e.parenrightmonospace=65289;e.parenrightsmall=65114;e.parenrightsuperior=8318;e.parenrighttp=63734;e.parenrightvertical=65078;e.partialdiff=8706;e.paseqhebrew=1472;e.pashtahebrew=1433;e.pasquare=13225;e.patah=1463;e.patah11=1463;e.patah1d=1463;e.patah2a=1463;e.patahhebrew=1463;e.patahnarrowhebrew=1463;e.patahquarterhebrew=1463;e.patahwidehebrew=1463;e.pazerhebrew=1441;e.pbopomofo=12550;e.pcircle=9439;e.pdotaccent=7767;e.pe=1508;e.pecyrillic=1087;e.pedagesh=64324;e.pedageshhebrew=64324;e.peezisquare=13115;e.pefinaldageshhebrew=64323;e.peharabic=1662;e.peharmenian=1402;e.pehebrew=1508;e.pehfinalarabic=64343;e.pehinitialarabic=64344;e.pehiragana=12410;e.pehmedialarabic=64345;e.pekatakana=12506;e.pemiddlehookcyrillic=1191;e.perafehebrew=64334;e.percent=37;e.percentarabic=1642;e.percentmonospace=65285;e.percentsmall=65130;e.period=46;e.periodarmenian=1417;e.periodcentered=183;e.periodhalfwidth=65377;e.periodinferior=63207;e.periodmonospace=65294;e.periodsmall=65106;e.periodsuperior=63208;e.perispomenigreekcmb=834;e.perpendicular=8869;e.perthousand=8240;e.peseta=8359;e.pfsquare=13194;e.phabengali=2475;e.phadeva=2347;e.phagujarati=2731;e.phagurmukhi=2603;e.phi=966;e.phi1=981;e.phieuphacirclekorean=12922;e.phieuphaparenkorean=12826;e.phieuphcirclekorean=12908;e.phieuphkorean=12621;e.phieuphparenkorean=12812;e.philatin=632;e.phinthuthai=3642;e.phisymbolgreek=981;e.phook=421;e.phophanthai=3614;e.phophungthai=3612;e.phosamphaothai=3616;e.pi=960;e.pieupacirclekorean=12915;e.pieupaparenkorean=12819;e.pieupcieuckorean=12662;e.pieupcirclekorean=12901;e.pieupkiyeokkorean=12658;e.pieupkorean=12610;e.pieupparenkorean=12805;e.pieupsioskiyeokkorean=12660;e.pieupsioskorean=12612;e.pieupsiostikeutkorean=12661;e.pieupthieuthkorean=12663;e.pieuptikeutkorean=12659;e.pihiragana=12404;e.pikatakana=12500;e.pisymbolgreek=982;e.piwrarmenian=1411;e.planckover2pi=8463;e.planckover2pi1=8463;e.plus=43;e.plusbelowcmb=799;e.pluscircle=8853;e.plusminus=177;e.plusmod=726;e.plusmonospace=65291;e.plussmall=65122;e.plussuperior=8314;e.pmonospace=65360;e.pmsquare=13272;e.pohiragana=12413;e.pointingindexdownwhite=9759;e.pointingindexleftwhite=9756;e.pointingindexrightwhite=9758;e.pointingindexupwhite=9757;e.pokatakana=12509;e.poplathai=3611;e.postalmark=12306;e.postalmarkface=12320;e.pparen=9387;e.precedes=8826;e.prescription=8478;e.primemod=697;e.primereversed=8245;e.product=8719;e.projective=8965;e.prolongedkana=12540;e.propellor=8984;e.propersubset=8834;e.propersuperset=8835;e.proportion=8759;e.proportional=8733;e.psi=968;e.psicyrillic=1137;e.psilipneumatacyrilliccmb=1158;e.pssquare=13232;e.puhiragana=12407;e.pukatakana=12503;e.pvsquare=13236;e.pwsquare=13242;e.q=113;e.qadeva=2392;e.qadmahebrew=1448;e.qafarabic=1602;e.qaffinalarabic=65238;e.qafinitialarabic=65239;e.qafmedialarabic=65240;e.qamats=1464;e.qamats10=1464;e.qamats1a=1464;e.qamats1c=1464;e.qamats27=1464;e.qamats29=1464;e.qamats33=1464;e.qamatsde=1464;e.qamatshebrew=1464;e.qamatsnarrowhebrew=1464;e.qamatsqatanhebrew=1464;e.qamatsqatannarrowhebrew=1464;e.qamatsqatanquarterhebrew=1464;e.qamatsqatanwidehebrew=1464;e.qamatsquarterhebrew=1464;e.qamatswidehebrew=1464;e.qarneyparahebrew=1439;e.qbopomofo=12561;e.qcircle=9440;e.qhook=672;e.qmonospace=65361;e.qof=1511;e.qofdagesh=64327;e.qofdageshhebrew=64327;e.qofhebrew=1511;e.qparen=9388;e.quarternote=9833;e.qubuts=1467;e.qubuts18=1467;e.qubuts25=1467;e.qubuts31=1467;e.qubutshebrew=1467;e.qubutsnarrowhebrew=1467;e.qubutsquarterhebrew=1467;e.qubutswidehebrew=1467;e.question=63;e.questionarabic=1567;e.questionarmenian=1374;e.questiondown=191;e.questiondownsmall=63423;e.questiongreek=894;e.questionmonospace=65311;e.questionsmall=63295;e.quotedbl=34;e.quotedblbase=8222;e.quotedblleft=8220;e.quotedblmonospace=65282;e.quotedblprime=12318;e.quotedblprimereversed=12317;e.quotedblright=8221;e.quoteleft=8216;e.quoteleftreversed=8219;e.quotereversed=8219;e.quoteright=8217;e.quoterightn=329;e.quotesinglbase=8218;e.quotesingle=39;e.quotesinglemonospace=65287;e.r=114;e.raarmenian=1404;e.rabengali=2480;e.racute=341;e.radeva=2352;e.radical=8730;e.radicalex=63717;e.radoverssquare=13230;e.radoverssquaredsquare=13231;e.radsquare=13229;e.rafe=1471;e.rafehebrew=1471;e.ragujarati=2736;e.ragurmukhi=2608;e.rahiragana=12425;e.rakatakana=12521;e.rakatakanahalfwidth=65431;e.ralowerdiagonalbengali=2545;e.ramiddlediagonalbengali=2544;e.ramshorn=612;e.ratio=8758;e.rbopomofo=12566;e.rcaron=345;e.rcedilla=343;e.rcircle=9441;e.rcommaaccent=343;e.rdblgrave=529;e.rdotaccent=7769;e.rdotbelow=7771;e.rdotbelowmacron=7773;e.referencemark=8251;e.reflexsubset=8838;e.reflexsuperset=8839;e.registered=174;e.registersans=63720;e.registerserif=63194;e.reharabic=1585;e.reharmenian=1408;e.rehfinalarabic=65198;e.rehiragana=12428;e.rekatakana=12524;e.rekatakanahalfwidth=65434;e.resh=1512;e.reshdageshhebrew=64328;e.reshhebrew=1512;e.reversedtilde=8765;e.reviahebrew=1431;e.reviamugrashhebrew=1431;e.revlogicalnot=8976;e.rfishhook=638;e.rfishhookreversed=639;e.rhabengali=2525;e.rhadeva=2397;e.rho=961;e.rhook=637;e.rhookturned=635;e.rhookturnedsuperior=693;e.rhosymbolgreek=1009;e.rhotichookmod=734;e.rieulacirclekorean=12913;e.rieulaparenkorean=12817;e.rieulcirclekorean=12899;e.rieulhieuhkorean=12608;e.rieulkiyeokkorean=12602;e.rieulkiyeoksioskorean=12649;e.rieulkorean=12601;e.rieulmieumkorean=12603;e.rieulpansioskorean=12652;e.rieulparenkorean=12803;e.rieulphieuphkorean=12607;e.rieulpieupkorean=12604;e.rieulpieupsioskorean=12651;e.rieulsioskorean=12605;e.rieulthieuthkorean=12606;e.rieultikeutkorean=12650;e.rieulyeorinhieuhkorean=12653;e.rightangle=8735;e.righttackbelowcmb=793;e.righttriangle=8895;e.rihiragana=12426;e.rikatakana=12522;e.rikatakanahalfwidth=65432;e.ring=730;e.ringbelowcmb=805;e.ringcmb=778;e.ringhalfleft=703;e.ringhalfleftarmenian=1369;e.ringhalfleftbelowcmb=796;e.ringhalfleftcentered=723;e.ringhalfright=702;e.ringhalfrightbelowcmb=825;e.ringhalfrightcentered=722;e.rinvertedbreve=531;e.rittorusquare=13137;e.rlinebelow=7775;e.rlongleg=636;e.rlonglegturned=634;e.rmonospace=65362;e.rohiragana=12429;e.rokatakana=12525;e.rokatakanahalfwidth=65435;e.roruathai=3619;e.rparen=9389;e.rrabengali=2524;e.rradeva=2353;e.rragurmukhi=2652;e.rreharabic=1681;e.rrehfinalarabic=64397;e.rrvocalicbengali=2528;e.rrvocalicdeva=2400;e.rrvocalicgujarati=2784;e.rrvocalicvowelsignbengali=2500;e.rrvocalicvowelsigndeva=2372;e.rrvocalicvowelsigngujarati=2756;e.rsuperior=63217;e.rtblock=9616;e.rturned=633;e.rturnedsuperior=692;e.ruhiragana=12427;e.rukatakana=12523;e.rukatakanahalfwidth=65433;e.rupeemarkbengali=2546;e.rupeesignbengali=2547;e.rupiah=63197;e.ruthai=3620;e.rvocalicbengali=2443;e.rvocalicdeva=2315;e.rvocalicgujarati=2699;e.rvocalicvowelsignbengali=2499;e.rvocalicvowelsigndeva=2371;e.rvocalicvowelsigngujarati=2755;e.s=115;e.sabengali=2488;e.sacute=347;e.sacutedotaccent=7781;e.sadarabic=1589;e.sadeva=2360;e.sadfinalarabic=65210;e.sadinitialarabic=65211;e.sadmedialarabic=65212;e.sagujarati=2744;e.sagurmukhi=2616;e.sahiragana=12373;e.sakatakana=12469;e.sakatakanahalfwidth=65403;e.sallallahoualayhewasallamarabic=65018;e.samekh=1505;e.samekhdagesh=64321;e.samekhdageshhebrew=64321;e.samekhhebrew=1505;e.saraaathai=3634;e.saraaethai=3649;e.saraaimaimalaithai=3652;e.saraaimaimuanthai=3651;e.saraamthai=3635;e.saraathai=3632;e.saraethai=3648;e.saraiileftthai=63622;e.saraiithai=3637;e.saraileftthai=63621;e.saraithai=3636;e.saraothai=3650;e.saraueeleftthai=63624;e.saraueethai=3639;e.saraueleftthai=63623;e.sarauethai=3638;e.sarauthai=3640;e.sarauuthai=3641;e.sbopomofo=12569;e.scaron=353;e.scarondotaccent=7783;e.scedilla=351;e.schwa=601;e.schwacyrillic=1241;e.schwadieresiscyrillic=1243;e.schwahook=602;e.scircle=9442;e.scircumflex=349;e.scommaaccent=537;e.sdotaccent=7777;e.sdotbelow=7779;e.sdotbelowdotaccent=7785;e.seagullbelowcmb=828;e.second=8243;e.secondtonechinese=714;e.section=167;e.seenarabic=1587;e.seenfinalarabic=65202;e.seeninitialarabic=65203;e.seenmedialarabic=65204;e.segol=1462;e.segol13=1462;e.segol1f=1462;e.segol2c=1462;e.segolhebrew=1462;e.segolnarrowhebrew=1462;e.segolquarterhebrew=1462;e.segoltahebrew=1426;e.segolwidehebrew=1462;e.seharmenian=1405;e.sehiragana=12379;e.sekatakana=12475;e.sekatakanahalfwidth=65406;e.semicolon=59;e.semicolonarabic=1563;e.semicolonmonospace=65307;e.semicolonsmall=65108;e.semivoicedmarkkana=12444;e.semivoicedmarkkanahalfwidth=65439;e.sentisquare=13090;e.sentosquare=13091;e.seven=55;e.sevenarabic=1639;e.sevenbengali=2541;e.sevencircle=9318;e.sevencircleinversesansserif=10128;e.sevendeva=2413;e.seveneighths=8542;e.sevengujarati=2797;e.sevengurmukhi=2669;e.sevenhackarabic=1639;e.sevenhangzhou=12327;e.sevenideographicparen=12838;e.seveninferior=8327;e.sevenmonospace=65303;e.sevenoldstyle=63287;e.sevenparen=9338;e.sevenperiod=9358;e.sevenpersian=1783;e.sevenroman=8566;e.sevensuperior=8311;e.seventeencircle=9328;e.seventeenparen=9348;e.seventeenperiod=9368;e.seventhai=3671;e.sfthyphen=173;e.shaarmenian=1399;e.shabengali=2486;e.shacyrillic=1096;e.shaddaarabic=1617;e.shaddadammaarabic=64609;e.shaddadammatanarabic=64606;e.shaddafathaarabic=64608;e.shaddakasraarabic=64610;e.shaddakasratanarabic=64607;e.shade=9618;e.shadedark=9619;e.shadelight=9617;e.shademedium=9618;e.shadeva=2358;e.shagujarati=2742;e.shagurmukhi=2614;e.shalshelethebrew=1427;e.shbopomofo=12565;e.shchacyrillic=1097;e.sheenarabic=1588;e.sheenfinalarabic=65206;e.sheeninitialarabic=65207;e.sheenmedialarabic=65208;e.sheicoptic=995;e.sheqel=8362;e.sheqelhebrew=8362;e.sheva=1456;e.sheva115=1456;e.sheva15=1456;e.sheva22=1456;e.sheva2e=1456;e.shevahebrew=1456;e.shevanarrowhebrew=1456;e.shevaquarterhebrew=1456;e.shevawidehebrew=1456;e.shhacyrillic=1211;e.shimacoptic=1005;e.shin=1513;e.shindagesh=64329;e.shindageshhebrew=64329;e.shindageshshindot=64300;e.shindageshshindothebrew=64300;e.shindageshsindot=64301;e.shindageshsindothebrew=64301;e.shindothebrew=1473;e.shinhebrew=1513;e.shinshindot=64298;e.shinshindothebrew=64298;e.shinsindot=64299;e.shinsindothebrew=64299;e.shook=642;e.sigma=963;e.sigma1=962;e.sigmafinal=962;e.sigmalunatesymbolgreek=1010;e.sihiragana=12375;e.sikatakana=12471;e.sikatakanahalfwidth=65404;e.siluqhebrew=1469;e.siluqlefthebrew=1469;e.similar=8764;e.sindothebrew=1474;e.siosacirclekorean=12916;e.siosaparenkorean=12820;e.sioscieuckorean=12670;e.sioscirclekorean=12902;e.sioskiyeokkorean=12666;e.sioskorean=12613;e.siosnieunkorean=12667;e.siosparenkorean=12806;e.siospieupkorean=12669;e.siostikeutkorean=12668;e.six=54;e.sixarabic=1638;e.sixbengali=2540;e.sixcircle=9317;e.sixcircleinversesansserif=10127;e.sixdeva=2412;e.sixgujarati=2796;e.sixgurmukhi=2668;e.sixhackarabic=1638;e.sixhangzhou=12326;e.sixideographicparen=12837;e.sixinferior=8326;e.sixmonospace=65302;e.sixoldstyle=63286;e.sixparen=9337;e.sixperiod=9357;e.sixpersian=1782;e.sixroman=8565;e.sixsuperior=8310;e.sixteencircle=9327;e.sixteencurrencydenominatorbengali=2553;e.sixteenparen=9347;e.sixteenperiod=9367;e.sixthai=3670;e.slash=47;e.slashmonospace=65295;e.slong=383;e.slongdotaccent=7835;e.smileface=9786;e.smonospace=65363;e.sofpasuqhebrew=1475;e.softhyphen=173;e.softsigncyrillic=1100;e.sohiragana=12381;e.sokatakana=12477;e.sokatakanahalfwidth=65407;e.soliduslongoverlaycmb=824;e.solidusshortoverlaycmb=823;e.sorusithai=3625;e.sosalathai=3624;e.sosothai=3595;e.sosuathai=3626;e.space=32;e.spacehackarabic=32;e.spade=9824;e.spadesuitblack=9824;e.spadesuitwhite=9828;e.sparen=9390;e.squarebelowcmb=827;e.squarecc=13252;e.squarecm=13213;e.squarediagonalcrosshatchfill=9641;e.squarehorizontalfill=9636;e.squarekg=13199;e.squarekm=13214;e.squarekmcapital=13262;e.squareln=13265;e.squarelog=13266;e.squaremg=13198;e.squaremil=13269;e.squaremm=13212;e.squaremsquared=13217;e.squareorthogonalcrosshatchfill=9638;e.squareupperlefttolowerrightfill=9639;e.squareupperrighttolowerleftfill=9640;e.squareverticalfill=9637;e.squarewhitewithsmallblack=9635;e.srsquare=13275;e.ssabengali=2487;e.ssadeva=2359;e.ssagujarati=2743;e.ssangcieuckorean=12617;e.ssanghieuhkorean=12677;e.ssangieungkorean=12672;e.ssangkiyeokkorean=12594;e.ssangnieunkorean=12645;e.ssangpieupkorean=12611;e.ssangsioskorean=12614;e.ssangtikeutkorean=12600;e.ssuperior=63218;e.sterling=163;e.sterlingmonospace=65505;e.strokelongoverlaycmb=822;e.strokeshortoverlaycmb=821;e.subset=8834;e.subsetnotequal=8842;e.subsetorequal=8838;e.succeeds=8827;e.suchthat=8715;e.suhiragana=12377;e.sukatakana=12473;e.sukatakanahalfwidth=65405;e.sukunarabic=1618;e.summation=8721;e.sun=9788;e.superset=8835;e.supersetnotequal=8843;e.supersetorequal=8839;e.svsquare=13276;e.syouwaerasquare=13180;e.t=116;e.tabengali=2468;e.tackdown=8868;e.tackleft=8867;e.tadeva=2340;e.tagujarati=2724;e.tagurmukhi=2596;e.taharabic=1591;e.tahfinalarabic=65218;e.tahinitialarabic=65219;e.tahiragana=12383;e.tahmedialarabic=65220;e.taisyouerasquare=13181;e.takatakana=12479;e.takatakanahalfwidth=65408;e.tatweelarabic=1600;e.tau=964;e.tav=1514;e.tavdages=64330;e.tavdagesh=64330;e.tavdageshhebrew=64330;e.tavhebrew=1514;e.tbar=359;e.tbopomofo=12554;e.tcaron=357;e.tccurl=680;e.tcedilla=355;e.tcheharabic=1670;e.tchehfinalarabic=64379;e.tchehinitialarabic=64380;e.tchehmedialarabic=64381;e.tcircle=9443;e.tcircumflexbelow=7793;e.tcommaaccent=355;e.tdieresis=7831;e.tdotaccent=7787;e.tdotbelow=7789;e.tecyrillic=1090;e.tedescendercyrillic=1197;e.teharabic=1578;e.tehfinalarabic=65174;e.tehhahinitialarabic=64674;e.tehhahisolatedarabic=64524;e.tehinitialarabic=65175;e.tehiragana=12390;e.tehjeeminitialarabic=64673;e.tehjeemisolatedarabic=64523;e.tehmarbutaarabic=1577;e.tehmarbutafinalarabic=65172;e.tehmedialarabic=65176;e.tehmeeminitialarabic=64676;e.tehmeemisolatedarabic=64526;e.tehnoonfinalarabic=64627;e.tekatakana=12486;e.tekatakanahalfwidth=65411;e.telephone=8481;e.telephoneblack=9742;e.telishagedolahebrew=1440;e.telishaqetanahebrew=1449;e.tencircle=9321;e.tenideographicparen=12841;e.tenparen=9341;e.tenperiod=9361;e.tenroman=8569;e.tesh=679;e.tet=1496;e.tetdagesh=64312;e.tetdageshhebrew=64312;e.tethebrew=1496;e.tetsecyrillic=1205;e.tevirhebrew=1435;e.tevirlefthebrew=1435;e.thabengali=2469;e.thadeva=2341;e.thagujarati=2725;e.thagurmukhi=2597;e.thalarabic=1584;e.thalfinalarabic=65196;e.thanthakhatlowleftthai=63640;e.thanthakhatlowrightthai=63639;e.thanthakhatthai=3660;e.thanthakhatupperleftthai=63638;e.theharabic=1579;e.thehfinalarabic=65178;e.thehinitialarabic=65179;e.thehmedialarabic=65180;e.thereexists=8707;e.therefore=8756;e.theta=952;e.theta1=977;e.thetasymbolgreek=977;e.thieuthacirclekorean=12921;e.thieuthaparenkorean=12825;e.thieuthcirclekorean=12907;e.thieuthkorean=12620;e.thieuthparenkorean=12811;e.thirteencircle=9324;e.thirteenparen=9344;e.thirteenperiod=9364;e.thonangmonthothai=3601;e.thook=429;e.thophuthaothai=3602;e.thorn=254;e.thothahanthai=3607;e.thothanthai=3600;e.thothongthai=3608;e.thothungthai=3606;e.thousandcyrillic=1154;e.thousandsseparatorarabic=1644;e.thousandsseparatorpersian=1644;e.three=51;e.threearabic=1635;e.threebengali=2537;e.threecircle=9314;e.threecircleinversesansserif=10124;e.threedeva=2409;e.threeeighths=8540;e.threegujarati=2793;e.threegurmukhi=2665;e.threehackarabic=1635;e.threehangzhou=12323;e.threeideographicparen=12834;e.threeinferior=8323;e.threemonospace=65299;e.threenumeratorbengali=2550;e.threeoldstyle=63283;e.threeparen=9334;e.threeperiod=9354;e.threepersian=1779;e.threequarters=190;e.threequartersemdash=63198;e.threeroman=8562;e.threesuperior=179;e.threethai=3667;e.thzsquare=13204;e.tihiragana=12385;e.tikatakana=12481;e.tikatakanahalfwidth=65409;e.tikeutacirclekorean=12912;e.tikeutaparenkorean=12816;e.tikeutcirclekorean=12898;e.tikeutkorean=12599;e.tikeutparenkorean=12802;e.tilde=732;e.tildebelowcmb=816;e.tildecmb=771;e.tildecomb=771;e.tildedoublecmb=864;e.tildeoperator=8764;e.tildeoverlaycmb=820;e.tildeverticalcmb=830;e.timescircle=8855;e.tipehahebrew=1430;e.tipehalefthebrew=1430;e.tippigurmukhi=2672;e.titlocyrilliccmb=1155;e.tiwnarmenian=1407;e.tlinebelow=7791;e.tmonospace=65364;e.toarmenian=1385;e.tohiragana=12392;e.tokatakana=12488;e.tokatakanahalfwidth=65412;e.tonebarextrahighmod=741;e.tonebarextralowmod=745;e.tonebarhighmod=742;e.tonebarlowmod=744;e.tonebarmidmod=743;e.tonefive=445;e.tonesix=389;e.tonetwo=424;e.tonos=900;e.tonsquare=13095;e.topatakthai=3599;e.tortoiseshellbracketleft=12308;e.tortoiseshellbracketleftsmall=65117;e.tortoiseshellbracketleftvertical=65081;e.tortoiseshellbracketright=12309;e.tortoiseshellbracketrightsmall=65118;e.tortoiseshellbracketrightvertical=65082;e.totaothai=3605;e.tpalatalhook=427;e.tparen=9391;e.trademark=8482;e.trademarksans=63722;e.trademarkserif=63195;e.tretroflexhook=648;e.triagdn=9660;e.triaglf=9668;e.triagrt=9658;e.triagup=9650;e.ts=678;e.tsadi=1510;e.tsadidagesh=64326;e.tsadidageshhebrew=64326;e.tsadihebrew=1510;e.tsecyrillic=1094;e.tsere=1461;e.tsere12=1461;e.tsere1e=1461;e.tsere2b=1461;e.tserehebrew=1461;e.tserenarrowhebrew=1461;e.tserequarterhebrew=1461;e.tserewidehebrew=1461;e.tshecyrillic=1115;e.tsuperior=63219;e.ttabengali=2463;e.ttadeva=2335;e.ttagujarati=2719;e.ttagurmukhi=2591;e.tteharabic=1657;e.ttehfinalarabic=64359;e.ttehinitialarabic=64360;e.ttehmedialarabic=64361;e.tthabengali=2464;e.tthadeva=2336;e.tthagujarati=2720;e.tthagurmukhi=2592;e.tturned=647;e.tuhiragana=12388;e.tukatakana=12484;e.tukatakanahalfwidth=65410;e.tusmallhiragana=12387;e.tusmallkatakana=12483;e.tusmallkatakanahalfwidth=65391;e.twelvecircle=9323;e.twelveparen=9343;e.twelveperiod=9363;e.twelveroman=8571;e.twentycircle=9331;e.twentyhangzhou=21316;e.twentyparen=9351;e.twentyperiod=9371;e.two=50;e.twoarabic=1634;e.twobengali=2536;e.twocircle=9313;e.twocircleinversesansserif=10123;e.twodeva=2408;e.twodotenleader=8229;e.twodotleader=8229;e.twodotleadervertical=65072;e.twogujarati=2792;e.twogurmukhi=2664;e.twohackarabic=1634;e.twohangzhou=12322;e.twoideographicparen=12833;e.twoinferior=8322;e.twomonospace=65298;e.twonumeratorbengali=2549;e.twooldstyle=63282;e.twoparen=9333;e.twoperiod=9353;e.twopersian=1778;e.tworoman=8561;e.twostroke=443;e.twosuperior=178;e.twothai=3666;e.twothirds=8532;e.u=117;e.uacute=250;e.ubar=649;e.ubengali=2441;e.ubopomofo=12584;e.ubreve=365;e.ucaron=468;e.ucircle=9444;e.ucircumflex=251;e.ucircumflexbelow=7799;e.ucyrillic=1091;e.udattadeva=2385;e.udblacute=369;e.udblgrave=533;e.udeva=2313;e.udieresis=252;e.udieresisacute=472;e.udieresisbelow=7795;e.udieresiscaron=474;e.udieresiscyrillic=1265;e.udieresisgrave=476;e.udieresismacron=470;e.udotbelow=7909;e.ugrave=249;e.ugujarati=2697;e.ugurmukhi=2569;e.uhiragana=12358;e.uhookabove=7911;e.uhorn=432;e.uhornacute=7913;e.uhorndotbelow=7921;e.uhorngrave=7915;e.uhornhookabove=7917;e.uhorntilde=7919;e.uhungarumlaut=369;e.uhungarumlautcyrillic=1267;e.uinvertedbreve=535;e.ukatakana=12454;e.ukatakanahalfwidth=65395;e.ukcyrillic=1145;e.ukorean=12636;e.umacron=363;e.umacroncyrillic=1263;e.umacrondieresis=7803;e.umatragurmukhi=2625;e.umonospace=65365;e.underscore=95;e.underscoredbl=8215;e.underscoremonospace=65343;e.underscorevertical=65075;e.underscorewavy=65103;e.union=8746;e.universal=8704;e.uogonek=371;e.uparen=9392;e.upblock=9600;e.upperdothebrew=1476;e.upsilon=965;e.upsilondieresis=971;e.upsilondieresistonos=944;e.upsilonlatin=650;e.upsilontonos=973;e.uptackbelowcmb=797;e.uptackmod=724;e.uragurmukhi=2675;e.uring=367;e.ushortcyrillic=1118;e.usmallhiragana=12357;e.usmallkatakana=12453;e.usmallkatakanahalfwidth=65385;e.ustraightcyrillic=1199;e.ustraightstrokecyrillic=1201;e.utilde=361;e.utildeacute=7801;e.utildebelow=7797;e.uubengali=2442;e.uudeva=2314;e.uugujarati=2698;e.uugurmukhi=2570;e.uumatragurmukhi=2626;e.uuvowelsignbengali=2498;e.uuvowelsigndeva=2370;e.uuvowelsigngujarati=2754;e.uvowelsignbengali=2497;e.uvowelsigndeva=2369;e.uvowelsigngujarati=2753;e.v=118;e.vadeva=2357;e.vagujarati=2741;e.vagurmukhi=2613;e.vakatakana=12535;e.vav=1493;e.vavdagesh=64309;e.vavdagesh65=64309;e.vavdageshhebrew=64309;e.vavhebrew=1493;e.vavholam=64331;e.vavholamhebrew=64331;e.vavvavhebrew=1520;e.vavyodhebrew=1521;e.vcircle=9445;e.vdotbelow=7807;e.vecyrillic=1074;e.veharabic=1700;e.vehfinalarabic=64363;e.vehinitialarabic=64364;e.vehmedialarabic=64365;e.vekatakana=12537;e.venus=9792;e.verticalbar=124;e.verticallineabovecmb=781;e.verticallinebelowcmb=809;e.verticallinelowmod=716;e.verticallinemod=712;e.vewarmenian=1406;e.vhook=651;e.vikatakana=12536;e.viramabengali=2509;e.viramadeva=2381;e.viramagujarati=2765;e.visargabengali=2435;e.visargadeva=2307;e.visargagujarati=2691;e.vmonospace=65366;e.voarmenian=1400;e.voicediterationhiragana=12446;e.voicediterationkatakana=12542;e.voicedmarkkana=12443;e.voicedmarkkanahalfwidth=65438;e.vokatakana=12538;e.vparen=9393;e.vtilde=7805;e.vturned=652;e.vuhiragana=12436;e.vukatakana=12532;e.w=119;e.wacute=7811;e.waekorean=12633;e.wahiragana=12431;e.wakatakana=12527;e.wakatakanahalfwidth=65436;e.wakorean=12632;e.wasmallhiragana=12430;e.wasmallkatakana=12526;e.wattosquare=13143;e.wavedash=12316;e.wavyunderscorevertical=65076;e.wawarabic=1608;e.wawfinalarabic=65262;e.wawhamzaabovearabic=1572;e.wawhamzaabovefinalarabic=65158;e.wbsquare=13277;e.wcircle=9446;e.wcircumflex=373;e.wdieresis=7813;e.wdotaccent=7815;e.wdotbelow=7817;e.wehiragana=12433;e.weierstrass=8472;e.wekatakana=12529;e.wekorean=12638;e.weokorean=12637;e.wgrave=7809;e.whitebullet=9702;e.whitecircle=9675;e.whitecircleinverse=9689;e.whitecornerbracketleft=12302;e.whitecornerbracketleftvertical=65091;e.whitecornerbracketright=12303;e.whitecornerbracketrightvertical=65092;e.whitediamond=9671;e.whitediamondcontainingblacksmalldiamond=9672;e.whitedownpointingsmalltriangle=9663;e.whitedownpointingtriangle=9661;e.whiteleftpointingsmalltriangle=9667;e.whiteleftpointingtriangle=9665;e.whitelenticularbracketleft=12310;e.whitelenticularbracketright=12311;e.whiterightpointingsmalltriangle=9657;e.whiterightpointingtriangle=9655;e.whitesmallsquare=9643;e.whitesmilingface=9786;e.whitesquare=9633;e.whitestar=9734;e.whitetelephone=9743;e.whitetortoiseshellbracketleft=12312;e.whitetortoiseshellbracketright=12313;e.whiteuppointingsmalltriangle=9653;e.whiteuppointingtriangle=9651;e.wihiragana=12432;e.wikatakana=12528;e.wikorean=12639;e.wmonospace=65367;e.wohiragana=12434;e.wokatakana=12530;e.wokatakanahalfwidth=65382;e.won=8361;e.wonmonospace=65510;e.wowaenthai=3623;e.wparen=9394;e.wring=7832;e.wsuperior=695;e.wturned=653;e.wynn=447;e.x=120;e.xabovecmb=829;e.xbopomofo=12562;e.xcircle=9447;e.xdieresis=7821;e.xdotaccent=7819;e.xeharmenian=1389;e.xi=958;e.xmonospace=65368;e.xparen=9395;e.xsuperior=739;e.y=121;e.yaadosquare=13134;e.yabengali=2479;e.yacute=253;e.yadeva=2351;e.yaekorean=12626;e.yagujarati=2735;e.yagurmukhi=2607;e.yahiragana=12420;e.yakatakana=12516;e.yakatakanahalfwidth=65428;e.yakorean=12625;e.yamakkanthai=3662;e.yasmallhiragana=12419;e.yasmallkatakana=12515;e.yasmallkatakanahalfwidth=65388;e.yatcyrillic=1123;e.ycircle=9448;e.ycircumflex=375;e.ydieresis=255;e.ydotaccent=7823;e.ydotbelow=7925;e.yeharabic=1610;e.yehbarreearabic=1746;e.yehbarreefinalarabic=64431;e.yehfinalarabic=65266;e.yehhamzaabovearabic=1574;e.yehhamzaabovefinalarabic=65162;e.yehhamzaaboveinitialarabic=65163;e.yehhamzaabovemedialarabic=65164;e.yehinitialarabic=65267;e.yehmedialarabic=65268;e.yehmeeminitialarabic=64733;e.yehmeemisolatedarabic=64600;e.yehnoonfinalarabic=64660;e.yehthreedotsbelowarabic=1745;e.yekorean=12630;e.yen=165;e.yenmonospace=65509;e.yeokorean=12629;e.yeorinhieuhkorean=12678;e.yerahbenyomohebrew=1450;e.yerahbenyomolefthebrew=1450;e.yericyrillic=1099;e.yerudieresiscyrillic=1273;e.yesieungkorean=12673;e.yesieungpansioskorean=12675;e.yesieungsioskorean=12674;e.yetivhebrew=1434;e.ygrave=7923;e.yhook=436;e.yhookabove=7927;e.yiarmenian=1397;e.yicyrillic=1111;e.yikorean=12642;e.yinyang=9775;e.yiwnarmenian=1410;e.ymonospace=65369;e.yod=1497;e.yoddagesh=64313;e.yoddageshhebrew=64313;e.yodhebrew=1497;e.yodyodhebrew=1522;e.yodyodpatahhebrew=64287;e.yohiragana=12424;e.yoikorean=12681;e.yokatakana=12520;e.yokatakanahalfwidth=65430;e.yokorean=12635;e.yosmallhiragana=12423;e.yosmallkatakana=12519;e.yosmallkatakanahalfwidth=65390;e.yotgreek=1011;e.yoyaekorean=12680;e.yoyakorean=12679;e.yoyakthai=3618;e.yoyingthai=3597;e.yparen=9396;e.ypogegrammeni=890;e.ypogegrammenigreekcmb=837;e.yr=422;e.yring=7833;e.ysuperior=696;e.ytilde=7929;e.yturned=654;e.yuhiragana=12422;e.yuikorean=12684;e.yukatakana=12518;e.yukatakanahalfwidth=65429;e.yukorean=12640;e.yusbigcyrillic=1131;e.yusbigiotifiedcyrillic=1133;e.yuslittlecyrillic=1127;e.yuslittleiotifiedcyrillic=1129;e.yusmallhiragana=12421;e.yusmallkatakana=12517;e.yusmallkatakanahalfwidth=65389;e.yuyekorean=12683;e.yuyeokorean=12682;e.yyabengali=2527;e.yyadeva=2399;e.z=122;e.zaarmenian=1382;e.zacute=378;e.zadeva=2395;e.zagurmukhi=2651;e.zaharabic=1592;e.zahfinalarabic=65222;e.zahinitialarabic=65223;e.zahiragana=12374;e.zahmedialarabic=65224;e.zainarabic=1586;e.zainfinalarabic=65200;e.zakatakana=12470;e.zaqefgadolhebrew=1429;e.zaqefqatanhebrew=1428;e.zarqahebrew=1432;e.zayin=1494;e.zayindagesh=64310;e.zayindageshhebrew=64310;e.zayinhebrew=1494;e.zbopomofo=12567;e.zcaron=382;e.zcircle=9449;e.zcircumflex=7825;e.zcurl=657;e.zdot=380;e.zdotaccent=380;e.zdotbelow=7827;e.zecyrillic=1079;e.zedescendercyrillic=1177;e.zedieresiscyrillic=1247;e.zehiragana=12380;e.zekatakana=12476;e.zero=48;e.zeroarabic=1632;e.zerobengali=2534;e.zerodeva=2406;e.zerogujarati=2790;e.zerogurmukhi=2662;e.zerohackarabic=1632;e.zeroinferior=8320;e.zeromonospace=65296;e.zerooldstyle=63280;e.zeropersian=1776;e.zerosuperior=8304;e.zerothai=3664;e.zerowidthjoiner=65279;e.zerowidthnonjoiner=8204;e.zerowidthspace=8203;e.zeta=950;e.zhbopomofo=12563;e.zhearmenian=1386;e.zhebrevecyrillic=1218;e.zhecyrillic=1078;e.zhedescendercyrillic=1175;e.zhedieresiscyrillic=1245;e.zihiragana=12376;e.zikatakana=12472;e.zinorhebrew=1454;e.zlinebelow=7829;e.zmonospace=65370;e.zohiragana=12382;e.zokatakana=12478;e.zparen=9397;e.zretroflexhook=656;e.zstroke=438;e.zuhiragana=12378;e.zukatakana=12474;e[".notdef"]=0;e.angbracketleftbig=9001;e.angbracketleftBig=9001;e.angbracketleftbigg=9001;e.angbracketleftBigg=9001;e.angbracketrightBig=9002;e.angbracketrightbig=9002;e.angbracketrightBigg=9002;e.angbracketrightbigg=9002;e.arrowhookleft=8618;e.arrowhookright=8617;e.arrowlefttophalf=8636;e.arrowleftbothalf=8637;e.arrownortheast=8599;e.arrownorthwest=8598;e.arrowrighttophalf=8640;e.arrowrightbothalf=8641;e.arrowsoutheast=8600;e.arrowsouthwest=8601;e.backslashbig=8726;e.backslashBig=8726;e.backslashBigg=8726;e.backslashbigg=8726;e.bardbl=8214;e.bracehtipdownleft=65079;e.bracehtipdownright=65079;e.bracehtipupleft=65080;e.bracehtipupright=65080;e.braceleftBig=123;e.braceleftbig=123;e.braceleftbigg=123;e.braceleftBigg=123;e.bracerightBig=125;e.bracerightbig=125;e.bracerightbigg=125;e.bracerightBigg=125;e.bracketleftbig=91;e.bracketleftBig=91;e.bracketleftbigg=91;e.bracketleftBigg=91;e.bracketrightBig=93;e.bracketrightbig=93;e.bracketrightbigg=93;e.bracketrightBigg=93;e.ceilingleftbig=8968;e.ceilingleftBig=8968;e.ceilingleftBigg=8968;e.ceilingleftbigg=8968;e.ceilingrightbig=8969;e.ceilingrightBig=8969;e.ceilingrightbigg=8969;e.ceilingrightBigg=8969;e.circledotdisplay=8857;e.circledottext=8857;e.circlemultiplydisplay=8855;e.circlemultiplytext=8855;e.circleplusdisplay=8853;e.circleplustext=8853;e.contintegraldisplay=8750;e.contintegraltext=8750;e.coproductdisplay=8720;e.coproducttext=8720;e.floorleftBig=8970;e.floorleftbig=8970;e.floorleftbigg=8970;e.floorleftBigg=8970;e.floorrightbig=8971;e.floorrightBig=8971;e.floorrightBigg=8971;e.floorrightbigg=8971;e.hatwide=770;e.hatwider=770;e.hatwidest=770;e.intercal=7488;e.integraldisplay=8747;e.integraltext=8747;e.intersectiondisplay=8898;e.intersectiontext=8898;e.logicalanddisplay=8743;e.logicalandtext=8743;e.logicalordisplay=8744;e.logicalortext=8744;e.parenleftBig=40;e.parenleftbig=40;e.parenleftBigg=40;e.parenleftbigg=40;e.parenrightBig=41;e.parenrightbig=41;e.parenrightBigg=41;e.parenrightbigg=41;e.prime=8242;e.productdisplay=8719;e.producttext=8719;e.radicalbig=8730;e.radicalBig=8730;e.radicalBigg=8730;e.radicalbigg=8730;e.radicalbt=8730;e.radicaltp=8730;e.radicalvertex=8730;e.slashbig=47;e.slashBig=47;e.slashBigg=47;e.slashbigg=47;e.summationdisplay=8721;e.summationtext=8721;e.tildewide=732;e.tildewider=732;e.tildewidest=732;e.uniondisplay=8899;e.unionmultidisplay=8846;e.unionmultitext=8846;e.unionsqdisplay=8852;e.unionsqtext=8852;e.uniontext=8899;e.vextenddouble=8741;e.vextendsingle=8739})),Tr=getLookupTableFactory((function(e){e.space=32;e.a1=9985;e.a2=9986;e.a202=9987;e.a3=9988;e.a4=9742;e.a5=9990;e.a119=9991;e.a118=9992;e.a117=9993;e.a11=9755;e.a12=9758;e.a13=9996;e.a14=9997;e.a15=9998;e.a16=9999;e.a105=1e4;e.a17=10001;e.a18=10002;e.a19=10003;e.a20=10004;e.a21=10005;e.a22=10006;e.a23=10007;e.a24=10008;e.a25=10009;e.a26=10010;e.a27=10011;e.a28=10012;e.a6=10013;e.a7=10014;e.a8=10015;e.a9=10016;e.a10=10017;e.a29=10018;e.a30=10019;e.a31=10020;e.a32=10021;e.a33=10022;e.a34=10023;e.a35=9733;e.a36=10025;e.a37=10026;e.a38=10027;e.a39=10028;e.a40=10029;e.a41=10030;e.a42=10031;e.a43=10032;e.a44=10033;e.a45=10034;e.a46=10035;e.a47=10036;e.a48=10037;e.a49=10038;e.a50=10039;e.a51=10040;e.a52=10041;e.a53=10042;e.a54=10043;e.a55=10044;e.a56=10045;e.a57=10046;e.a58=10047;e.a59=10048;e.a60=10049;e.a61=10050;e.a62=10051;e.a63=10052;e.a64=10053;e.a65=10054;e.a66=10055;e.a67=10056;e.a68=10057;e.a69=10058;e.a70=10059;e.a71=9679;e.a72=10061;e.a73=9632;e.a74=10063;e.a203=10064;e.a75=10065;e.a204=10066;e.a76=9650;e.a77=9660;e.a78=9670;e.a79=10070;e.a81=9687;e.a82=10072;e.a83=10073;e.a84=10074;e.a97=10075;e.a98=10076;e.a99=10077;e.a100=10078;e.a101=10081;e.a102=10082;e.a103=10083;e.a104=10084;e.a106=10085;e.a107=10086;e.a108=10087;e.a112=9827;e.a111=9830;e.a110=9829;e.a109=9824;e.a120=9312;e.a121=9313;e.a122=9314;e.a123=9315;e.a124=9316;e.a125=9317;e.a126=9318;e.a127=9319;e.a128=9320;e.a129=9321;e.a130=10102;e.a131=10103;e.a132=10104;e.a133=10105;e.a134=10106;e.a135=10107;e.a136=10108;e.a137=10109;e.a138=10110;e.a139=10111;e.a140=10112;e.a141=10113;e.a142=10114;e.a143=10115;e.a144=10116;e.a145=10117;e.a146=10118;e.a147=10119;e.a148=10120;e.a149=10121;e.a150=10122;e.a151=10123;e.a152=10124;e.a153=10125;e.a154=10126;e.a155=10127;e.a156=10128;e.a157=10129;e.a158=10130;e.a159=10131;e.a160=10132;e.a161=8594;e.a163=8596;e.a164=8597;e.a196=10136;e.a165=10137;e.a192=10138;e.a166=10139;e.a167=10140;e.a168=10141;e.a169=10142;e.a170=10143;e.a171=10144;e.a172=10145;e.a173=10146;e.a162=10147;e.a174=10148;e.a175=10149;e.a176=10150;e.a177=10151;e.a178=10152;e.a179=10153;e.a193=10154;e.a180=10155;e.a199=10156;e.a181=10157;e.a200=10158;e.a182=10159;e.a201=10161;e.a183=10162;e.a184=10163;e.a197=10164;e.a185=10165;e.a194=10166;e.a198=10167;e.a186=10168;e.a195=10169;e.a187=10170;e.a188=10171;e.a189=10172;e.a190=10173;e.a191=10174;e.a89=10088;e.a90=10089;e.a93=10090;e.a94=10091;e.a91=10092;e.a92=10093;e.a205=10094;e.a85=10095;e.a206=10096;e.a86=10097;e.a87=10098;e.a88=10099;e.a95=10100;e.a96=10101;e[".notdef"]=0})),Mr=getLookupTableFactory((function(e){e[63721]=169;e[63193]=169;e[63720]=174;e[63194]=174;e[63722]=8482;e[63195]=8482;e[63729]=9127;e[63730]=9128;e[63731]=9129;e[63740]=9131;e[63741]=9132;e[63742]=9133;e[63726]=9121;e[63727]=9122;e[63728]=9123;e[63737]=9124;e[63738]=9125;e[63739]=9126;e[63723]=9115;e[63724]=9116;e[63725]=9117;e[63734]=9118;e[63735]=9119;e[63736]=9120}));function getUnicodeForGlyph(e,t){let a=t[e];if(void 0!==a)return a;if(!e)return-1;if("u"===e[0]){const t=e.length;let r;if(7===t&&"n"===e[1]&&"i"===e[2])r=e.substring(3);else{if(!(t>=5&&t<=7))return-1;r=e.substring(1)}if(r===r.toUpperCase()){a=parseInt(r,16);if(a>=0)return a}}return-1}const Dr=[[0,127],[128,255],[256,383],[384,591],[592,687,7424,7551,7552,7615],[688,767,42752,42783],[768,879,7616,7679],[880,1023],[11392,11519],[1024,1279,1280,1327,11744,11775,42560,42655],[1328,1423],[1424,1535],[42240,42559],[1536,1791,1872,1919],[1984,2047],[2304,2431],[2432,2559],[2560,2687],[2688,2815],[2816,2943],[2944,3071],[3072,3199],[3200,3327],[3328,3455],[3584,3711],[3712,3839],[4256,4351,11520,11567],[6912,7039],[4352,4607],[7680,7935,11360,11391,42784,43007],[7936,8191],[8192,8303,11776,11903],[8304,8351],[8352,8399],[8400,8447],[8448,8527],[8528,8591],[8592,8703,10224,10239,10496,10623,11008,11263],[8704,8959,10752,11007,10176,10223,10624,10751],[8960,9215],[9216,9279],[9280,9311],[9312,9471],[9472,9599],[9600,9631],[9632,9727],[9728,9983],[9984,10175],[12288,12351],[12352,12447],[12448,12543,12784,12799],[12544,12591,12704,12735],[12592,12687],[43072,43135],[12800,13055],[13056,13311],[44032,55215],[55296,57343],[67840,67871],[19968,40959,11904,12031,12032,12255,12272,12287,13312,19903,131072,173791,12688,12703],[57344,63743],[12736,12783,63744,64255,194560,195103],[64256,64335],[64336,65023],[65056,65071],[65040,65055],[65104,65135],[65136,65279],[65280,65519],[65520,65535],[3840,4095],[1792,1871],[1920,1983],[3456,3583],[4096,4255],[4608,4991,4992,5023,11648,11743],[5024,5119],[5120,5759],[5760,5791],[5792,5887],[6016,6143],[6144,6319],[10240,10495],[40960,42127],[5888,5919,5920,5951,5952,5983,5984,6015],[66304,66351],[66352,66383],[66560,66639],[118784,119039,119040,119295,119296,119375],[119808,120831],[1044480,1048573],[65024,65039,917760,917999],[917504,917631],[6400,6479],[6480,6527],[6528,6623],[6656,6687],[11264,11359],[11568,11647],[19904,19967],[43008,43055],[65536,65663,65664,65791,65792,65855],[65856,65935],[66432,66463],[66464,66527],[66640,66687],[66688,66735],[67584,67647],[68096,68191],[119552,119647],[73728,74751,74752,74879],[119648,119679],[7040,7103],[7168,7247],[7248,7295],[43136,43231],[43264,43311],[43312,43359],[43520,43615],[65936,65999],[66e3,66047],[66208,66271,66176,66207,67872,67903],[127024,127135,126976,127023]];function getUnicodeRangeFor(e,t=-1){if(-1!==t){const a=Dr[t];for(let r=0,i=a.length;r=a[r]&&e<=a[r+1])return t}for(let t=0,a=Dr.length;t=a[r]&&e<=a[r+1])return t}return-1}const Rr=new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$","u"),Nr=new Map;const Er=!0,Pr=1,Lr=2,_r=4,jr=32,Ur=[".notdef",".null","nonmarkingreturn","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","nonbreakingspace","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron","Lslash","lslash","Scaron","scaron","Zcaron","zcaron","brokenbar","Eth","eth","Yacute","yacute","Thorn","thorn","minus","multiply","onesuperior","twosuperior","threesuperior","onehalf","onequarter","threequarters","franc","Gbreve","gbreve","Idotaccent","Scedilla","scedilla","Cacute","cacute","Ccaron","ccaron","dcroat"];function recoverGlyphName(e,t){if(void 0!==t[e])return e;const a=getUnicodeForGlyph(e,t);if(-1!==a)for(const e in t)if(t[e]===a)return e;info("Unable to recover a standard glyph name for: "+e);return e}function type1FontGlyphMapping(e,t,a){const r=Object.create(null);let i,n,s;const o=!!(e.flags&_r);if(e.isInternalFont){s=t;for(n=0;n=0?i:0}}else if(e.baseEncodingName){s=getEncoding(e.baseEncodingName);for(n=0;n=0?i:0}}else if(o)for(n in t)r[n]=t[n];else{s=Cr;for(n=0;n=0?i:0}}const c=e.differences;let l;if(c)for(n in c){const e=c[n];i=a.indexOf(e);if(-1===i){l||(l=Or());const t=recoverGlyphName(e,l);t!==e&&(i=a.indexOf(t))}r[n]=i>=0?i:0}return r}function normalizeFontName(e){return e.replaceAll(/[,_]/g,"-").replaceAll(/\s/g,"")}const Xr=getLookupTableFactory((e=>{e[8211]=65074;e[8212]=65073;e[8229]=65072;e[8230]=65049;e[12289]=65041;e[12290]=65042;e[12296]=65087;e[12297]=65088;e[12298]=65085;e[12299]=65086;e[12300]=65089;e[12301]=65090;e[12302]=65091;e[12303]=65092;e[12304]=65083;e[12305]=65084;e[12308]=65081;e[12309]=65082;e[12310]=65047;e[12311]=65048;e[65103]=65076;e[65281]=65045;e[65288]=65077;e[65289]=65078;e[65292]=65040;e[65306]=65043;e[65307]=65044;e[65311]=65046;e[65339]=65095;e[65341]=65096;e[65343]=65075;e[65371]=65079;e[65373]=65080}));const qr=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron"],Hr=[".notdef","space","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],Wr=[".notdef","space","dollaroldstyle","dollarsuperior","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","hyphensuperior","colonmonetary","onefitted","rupiah","centoldstyle","figuredash","hypheninferior","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior"],zr=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall","001.000","001.001","001.002","001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"],$r=391,Gr=[null,{id:"hstem",min:2,stackClearing:!0,stem:!0},null,{id:"vstem",min:2,stackClearing:!0,stem:!0},{id:"vmoveto",min:1,stackClearing:!0},{id:"rlineto",min:2,resetStack:!0},{id:"hlineto",min:1,resetStack:!0},{id:"vlineto",min:1,resetStack:!0},{id:"rrcurveto",min:6,resetStack:!0},null,{id:"callsubr",min:1,undefStack:!0},{id:"return",min:0,undefStack:!0},null,null,{id:"endchar",min:0,stackClearing:!0},null,null,null,{id:"hstemhm",min:2,stackClearing:!0,stem:!0},{id:"hintmask",min:0,stackClearing:!0},{id:"cntrmask",min:0,stackClearing:!0},{id:"rmoveto",min:2,stackClearing:!0},{id:"hmoveto",min:1,stackClearing:!0},{id:"vstemhm",min:2,stackClearing:!0,stem:!0},{id:"rcurveline",min:8,resetStack:!0},{id:"rlinecurve",min:8,resetStack:!0},{id:"vvcurveto",min:4,resetStack:!0},{id:"hhcurveto",min:4,resetStack:!0},null,{id:"callgsubr",min:1,undefStack:!0},{id:"vhcurveto",min:4,resetStack:!0},{id:"hvcurveto",min:4,resetStack:!0}],Vr=[null,null,null,{id:"and",min:2,stackDelta:-1},{id:"or",min:2,stackDelta:-1},{id:"not",min:1,stackDelta:0},null,null,null,{id:"abs",min:1,stackDelta:0},{id:"add",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]+e[t-1]}},{id:"sub",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]-e[t-1]}},{id:"div",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]/e[t-1]}},null,{id:"neg",min:1,stackDelta:0,stackFn(e,t){e[t-1]=-e[t-1]}},{id:"eq",min:2,stackDelta:-1},null,null,{id:"drop",min:1,stackDelta:-1},null,{id:"put",min:2,stackDelta:-2},{id:"get",min:1,stackDelta:0},{id:"ifelse",min:4,stackDelta:-3},{id:"random",min:0,stackDelta:1},{id:"mul",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]*e[t-1]}},null,{id:"sqrt",min:1,stackDelta:0},{id:"dup",min:1,stackDelta:1},{id:"exch",min:2,stackDelta:0},{id:"index",min:2,stackDelta:0},{id:"roll",min:3,stackDelta:-2},null,null,null,{id:"hflex",min:7,resetStack:!0},{id:"flex",min:13,resetStack:!0},{id:"hflex1",min:9,resetStack:!0},{id:"flex1",min:11,resetStack:!0}];class CFFParser{constructor(e,t,a){this.bytes=e.getBytes();this.properties=t;this.seacAnalysisEnabled=!!a}parse(){const e=this.properties,t=new CFF;this.cff=t;const a=this.parseHeader(),r=this.parseIndex(a.endPos),i=this.parseIndex(r.endPos),n=this.parseIndex(i.endPos),s=this.parseIndex(n.endPos),o=this.parseDict(i.obj.get(0)),c=this.createDict(CFFTopDict,o,t.strings);t.header=a.obj;t.names=this.parseNameIndex(r.obj);t.strings=this.parseStringIndex(n.obj);t.topDict=c;t.globalSubrIndex=s.obj;this.parsePrivateDict(t.topDict);t.isCIDFont=c.hasName("ROS");const l=c.getByName("CharStrings"),h=this.parseIndex(l).obj,u=c.getByName("FontMatrix");u&&(e.fontMatrix=u);const d=c.getByName("FontBBox");if(d){e.ascent=Math.max(d[3],d[1]);e.descent=Math.min(d[1],d[3]);e.ascentScaled=!0}let f,g;if(t.isCIDFont){const e=this.parseIndex(c.getByName("FDArray")).obj;for(let a=0,r=e.count;a=t)throw new FormatError("Invalid CFF header");if(0!==a){info("cff data is shifted");e=e.subarray(a);this.bytes=e}const r=e[0],i=e[1],n=e[2],s=e[3];return{obj:new CFFHeader(r,i,n,s),endPos:n}}parseDict(e){let t=0;function parseOperand(){let a=e[t++];if(30===a)return function parseFloatOperand(){let a="";const r=15,i=["0","1","2","3","4","5","6","7","8","9",".","E","E-",null,"-"],n=e.length;for(;t>4,o=15&n;if(s===r)break;a+=i[s];if(o===r)break;a+=i[o]}return parseFloat(a)}();if(28===a){a=readInt16(e,t);t+=2;return a}if(29===a){a=e[t++];a=a<<8|e[t++];a=a<<8|e[t++];a=a<<8|e[t++];return a}if(a>=32&&a<=246)return a-139;if(a>=247&&a<=250)return 256*(a-247)+e[t++]+108;if(a>=251&&a<=254)return-256*(a-251)-e[t++]-108;warn('CFFParser_parseDict: "'+a+'" is a reserved command.');return NaN}let a=[];const r=[];t=0;const i=e.length;for(;t10)return!1;let i=e.stackSize;const n=e.stack;let s=t.length;for(let o=0;o=4){i-=4;if(this.seacAnalysisEnabled){e.seac=n.slice(i,i+4);return!1}}l=Gr[c]}else if(c>=32&&c<=246){n[i]=c-139;i++}else if(c>=247&&c<=254){n[i]=c<251?(c-247<<8)+t[o]+108:-(c-251<<8)-t[o]-108;o++;i++}else if(255===c){n[i]=(t[o]<<24|t[o+1]<<16|t[o+2]<<8|t[o+3])/65536;o+=4;i++}else if(19===c||20===c){e.hints+=i>>1;if(0===e.hints){t.copyWithin(o-1,o,-1);o-=1;s-=1;continue}o+=e.hints+7>>3;i%=2;l=Gr[c]}else{if(10===c||29===c){const t=10===c?a:r;if(!t){l=Gr[c];warn("Missing subrsIndex for "+l.id);return!1}let s=32768;t.count<1240?s=107:t.count<33900&&(s=1131);const o=n[--i]+s;if(o<0||o>=t.count||isNaN(o)){l=Gr[c];warn("Out of bounds subrIndex for "+l.id);return!1}e.stackSize=i;e.callDepth++;if(!this.parseCharString(e,t.get(o),a,r))return!1;e.callDepth--;i=e.stackSize;continue}if(11===c){e.stackSize=i;return!0}if(0===c&&o===t.length){t[o-1]=14;l=Gr[14]}else{if(9===c){t.copyWithin(o-1,o,-1);o-=1;s-=1;continue}l=Gr[c]}}if(l){if(l.stem){e.hints+=i>>1;if(3===c||23===c)e.hasVStems=!0;else if(e.hasVStems&&(1===c||18===c)){warn("CFF stem hints are in wrong order");t[o-1]=1===c?3:23}}if("min"in l&&!e.undefStack&&i=2&&l.stem?i%=2:i>1&&warn("Found too many parameters for stack-clearing command");i>0&&(e.width=n[i-1])}if("stackDelta"in l){"stackFn"in l&&l.stackFn(n,i);i+=l.stackDelta}else if(l.stackClearing)i=0;else if(l.resetStack){i=0;e.undefStack=!1}else if(l.undefStack){i=0;e.undefStack=!0;e.firstStackClearing=!1}}}s=i.length){warn("Invalid fd index for glyph index.");u=!1}if(u){f=i[e].privateDict;d=f.subrsIndex}}else t&&(d=t);u&&(u=this.parseCharString(h,c,d,a));if(null!==h.width){const e=f.getByName("nominalWidthX");o[l]=e+h.width}else{const e=f.getByName("defaultWidthX");o[l]=e}null!==h.seac&&(s[l]=h.seac);u||e.set(l,new Uint8Array([14]))}return{charStrings:e,seacs:s,widths:o}}emptyPrivateDictionary(e){const t=this.createDict(CFFPrivateDict,[],e.strings);e.setByKey(18,[0,0]);e.privateDict=t}parsePrivateDict(e){if(!e.hasName("Private")){this.emptyPrivateDictionary(e);return}const t=e.getByName("Private");if(!Array.isArray(t)||2!==t.length){e.removeByName("Private");return}const a=t[0],r=t[1];if(0===a||r>=this.bytes.length){this.emptyPrivateDictionary(e);return}const i=r+a,n=this.bytes.subarray(r,i),s=this.parseDict(n),o=this.createDict(CFFPrivateDict,s,e.strings);e.privateDict=o;0===o.getByName("ExpansionFactor")&&o.setByName("ExpansionFactor",.06);if(!o.getByName("Subrs"))return;const c=o.getByName("Subrs"),l=r+c;if(0===c||l>=this.bytes.length){this.emptyPrivateDictionary(e);return}const h=this.parseIndex(l);o.subrsIndex=h.obj}parseCharsets(e,t,a,r){if(0===e)return new CFFCharset(!0,Yr.ISO_ADOBE,qr);if(1===e)return new CFFCharset(!0,Yr.EXPERT,Hr);if(2===e)return new CFFCharset(!0,Yr.EXPERT_SUBSET,Wr);const i=this.bytes,n=e,s=i[e++],o=[r?0:".notdef"];let c,l,h;t-=1;switch(s){case 0:for(h=0;h=65535){warn("Not enough space in charstrings to duplicate first glyph.");return}const e=this.charStrings.get(0);this.charStrings.add(e);this.isCIDFont&&this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0])}hasGlyphId(e){if(e<0||e>=this.charStrings.count)return!1;return this.charStrings.get(e).length>0}}class CFFHeader{constructor(e,t,a,r){this.major=e;this.minor=t;this.hdrSize=a;this.offSize=r}}class CFFStrings{constructor(){this.strings=[]}get(e){return e>=0&&e<=390?zr[e]:e-$r<=this.strings.length?this.strings[e-$r]:zr[0]}getSID(e){let t=zr.indexOf(e);if(-1!==t)return t;t=this.strings.indexOf(e);return-1!==t?t+$r:-1}add(e){this.strings.push(e)}get count(){return this.strings.length}}class CFFIndex{constructor(){this.objects=[];this.length=0}add(e){this.length+=e.length;this.objects.push(e)}set(e,t){this.length+=t.length-this.objects[e].length;this.objects[e]=t}get(e){return this.objects[e]}get count(){return this.objects.length}}class CFFDict{constructor(e,t){this.keyToNameMap=e.keyToNameMap;this.nameToKeyMap=e.nameToKeyMap;this.defaults=e.defaults;this.types=e.types;this.opcodes=e.opcodes;this.order=e.order;this.strings=t;this.values=Object.create(null)}setByKey(e,t){if(!(e in this.keyToNameMap))return!1;if(0===t.length)return!0;for(const a of t)if(isNaN(a)){warn(`Invalid CFFDict value: "${t}" for key "${e}".`);return!0}const a=this.types[e];"num"!==a&&"sid"!==a&&"offset"!==a||(t=t[0]);this.values[e]=t;return!0}setByName(e,t){if(!(e in this.nameToKeyMap))throw new FormatError(`Invalid dictionary name "${e}"`);this.values[this.nameToKeyMap[e]]=t}hasName(e){return this.nameToKeyMap[e]in this.values}getByName(e){if(!(e in this.nameToKeyMap))throw new FormatError(`Invalid dictionary name ${e}"`);const t=this.nameToKeyMap[e];return t in this.values?this.values[t]:this.defaults[t]}removeByName(e){delete this.values[this.nameToKeyMap[e]]}static createTables(e){const t={keyToNameMap:{},nameToKeyMap:{},defaults:{},types:{},opcodes:{},order:[]};for(const a of e){const e=Array.isArray(a[0])?(a[0][0]<<8)+a[0][1]:a[0];t.keyToNameMap[e]=a[1];t.nameToKeyMap[a[1]]=e;t.types[e]=a[2];t.defaults[e]=a[3];t.opcodes[e]=Array.isArray(a[0])?a[0]:[a[0]];t.order.push(e)}return t}}const Kr=[[[12,30],"ROS",["sid","sid","num"],null],[[12,20],"SyntheticBase","num",null],[0,"version","sid",null],[1,"Notice","sid",null],[[12,0],"Copyright","sid",null],[2,"FullName","sid",null],[3,"FamilyName","sid",null],[4,"Weight","sid",null],[[12,1],"isFixedPitch","num",0],[[12,2],"ItalicAngle","num",0],[[12,3],"UnderlinePosition","num",-100],[[12,4],"UnderlineThickness","num",50],[[12,5],"PaintType","num",0],[[12,6],"CharstringType","num",2],[[12,7],"FontMatrix",["num","num","num","num","num","num"],[.001,0,0,.001,0,0]],[13,"UniqueID","num",null],[5,"FontBBox",["num","num","num","num"],[0,0,0,0]],[[12,8],"StrokeWidth","num",0],[14,"XUID","array",null],[15,"charset","offset",0],[16,"Encoding","offset",0],[17,"CharStrings","offset",0],[18,"Private",["offset","offset"],null],[[12,21],"PostScript","sid",null],[[12,22],"BaseFontName","sid",null],[[12,23],"BaseFontBlend","delta",null],[[12,31],"CIDFontVersion","num",0],[[12,32],"CIDFontRevision","num",0],[[12,33],"CIDFontType","num",0],[[12,34],"CIDCount","num",8720],[[12,35],"UIDBase","num",null],[[12,37],"FDSelect","offset",null],[[12,36],"FDArray","offset",null],[[12,38],"FontName","sid",null]];class CFFTopDict extends CFFDict{static get tables(){return shadow(this,"tables",this.createTables(Kr))}constructor(e){super(CFFTopDict.tables,e);this.privateDict=null}}const Jr=[[6,"BlueValues","delta",null],[7,"OtherBlues","delta",null],[8,"FamilyBlues","delta",null],[9,"FamilyOtherBlues","delta",null],[[12,9],"BlueScale","num",.039625],[[12,10],"BlueShift","num",7],[[12,11],"BlueFuzz","num",1],[10,"StdHW","num",null],[11,"StdVW","num",null],[[12,12],"StemSnapH","delta",null],[[12,13],"StemSnapV","delta",null],[[12,14],"ForceBold","num",0],[[12,17],"LanguageGroup","num",0],[[12,18],"ExpansionFactor","num",.06],[[12,19],"initialRandomSeed","num",0],[20,"defaultWidthX","num",0],[21,"nominalWidthX","num",0],[19,"Subrs","offset",null]];class CFFPrivateDict extends CFFDict{static get tables(){return shadow(this,"tables",this.createTables(Jr))}constructor(e){super(CFFPrivateDict.tables,e);this.subrsIndex=null}}const Yr={ISO_ADOBE:0,EXPERT:1,EXPERT_SUBSET:2};class CFFCharset{constructor(e,t,a,r){this.predefined=e;this.format=t;this.charset=a;this.raw=r}}class CFFEncoding{constructor(e,t,a,r){this.predefined=e;this.format=t;this.encoding=a;this.raw=r}}class CFFFDSelect{constructor(e,t){this.format=e;this.fdSelect=t}getFDIndex(e){return e<0||e>=this.fdSelect.length?-1:this.fdSelect[e]}}class CFFOffsetTracker{constructor(){this.offsets=Object.create(null)}isTracking(e){return e in this.offsets}track(e,t){if(e in this.offsets)throw new FormatError(`Already tracking location of ${e}`);this.offsets[e]=t}offset(e){for(const t in this.offsets)this.offsets[t]+=e}setEntryLocation(e,t,a){if(!(e in this.offsets))throw new FormatError(`Not tracking location of ${e}`);const r=a.data,i=this.offsets[e];for(let e=0,a=t.length;e>24&255;r[s]=l>>16&255;r[o]=l>>8&255;r[c]=255&l}}}class CFFCompiler{constructor(e){this.cff=e}compile(){const e=this.cff,t={data:[],length:0,add(e){try{this.data.push(...e)}catch{this.data=this.data.concat(e)}this.length=this.data.length}},a=this.compileHeader(e.header);t.add(a);const r=this.compileNameIndex(e.names);t.add(r);if(e.isCIDFont&&e.topDict.hasName("FontMatrix")){const t=e.topDict.getByName("FontMatrix");e.topDict.removeByName("FontMatrix");for(const a of e.fdArray){let e=t.slice(0);a.hasName("FontMatrix")&&(e=Util.transform(e,a.getByName("FontMatrix")));a.setByName("FontMatrix",e)}}const i=e.topDict.getByName("XUID");i?.length>16&&e.topDict.removeByName("XUID");e.topDict.setByName("charset",0);let n=this.compileTopDicts([e.topDict],t.length,e.isCIDFont);t.add(n.output);const s=n.trackers[0],o=this.compileStringIndex(e.strings.strings);t.add(o);const c=this.compileIndex(e.globalSubrIndex);t.add(c);if(e.encoding&&e.topDict.hasName("Encoding"))if(e.encoding.predefined)s.setEntryLocation("Encoding",[e.encoding.format],t);else{const a=this.compileEncoding(e.encoding);s.setEntryLocation("Encoding",[t.length],t);t.add(a)}const l=this.compileCharset(e.charset,e.charStrings.count,e.strings,e.isCIDFont);s.setEntryLocation("charset",[t.length],t);t.add(l);const h=this.compileCharStrings(e.charStrings);s.setEntryLocation("CharStrings",[t.length],t);t.add(h);if(e.isCIDFont){s.setEntryLocation("FDSelect",[t.length],t);const a=this.compileFDSelect(e.fdSelect);t.add(a);n=this.compileTopDicts(e.fdArray,t.length,!0);s.setEntryLocation("FDArray",[t.length],t);t.add(n.output);const r=n.trackers;this.compilePrivateDicts(e.fdArray,r,t)}this.compilePrivateDicts([e.topDict],[s],t);t.add([0]);return t.data}encodeNumber(e){return Number.isInteger(e)?this.encodeInteger(e):this.encodeFloat(e)}static get EncodeFloatRegExp(){return shadow(this,"EncodeFloatRegExp",/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/)}encodeFloat(e){let t=e.toString();const a=CFFCompiler.EncodeFloatRegExp.exec(t);if(a){const r=parseFloat("1e"+((a[2]?+a[2]:0)+a[1].length));t=(Math.round(e*r)/r).toString()}let r,i,n="";for(r=0,i=t.length;r=-107&&e<=107?[e+139]:e>=108&&e<=1131?[247+((e-=108)>>8),255&e]:e>=-1131&&e<=-108?[251+((e=-e-108)>>8),255&e]:e>=-32768&&e<=32767?[28,e>>8&255,255&e]:[29,e>>24&255,e>>16&255,e>>8&255,255&e];return t}compileHeader(e){return[e.major,e.minor,4,e.offSize]}compileNameIndex(e){const t=new CFFIndex;for(const a of e){const e=Math.min(a.length,127);let r=new Array(e);for(let t=0;t"~"||"["===e||"]"===e||"("===e||")"===e||"{"===e||"}"===e||"<"===e||">"===e||"/"===e||"%"===e)&&(e="_");r[t]=e}r=r.join("");""===r&&(r="Bad_Font_Name");t.add(stringToBytes(r))}return this.compileIndex(t)}compileTopDicts(e,t,a){const r=[];let i=new CFFIndex;for(const n of e){if(a){n.removeByName("CIDFontVersion");n.removeByName("CIDFontRevision");n.removeByName("CIDFontType");n.removeByName("CIDCount");n.removeByName("UIDBase")}const e=new CFFOffsetTracker,s=this.compileDict(n,e);r.push(e);i.add(s);e.offset(t)}i=this.compileIndex(i,r);return{trackers:r,output:i}}compilePrivateDicts(e,t,a){for(let r=0,i=e.length;r>8&255,255&e])}else{i=new Uint8Array(1+2*n);i[0]=0;let t=0;const r=e.charset.length;let s=!1;for(let n=1;n>8&255;i[n+1]=255&o}}return this.compileTypedArray(i)}compileEncoding(e){return this.compileTypedArray(e.raw)}compileFDSelect(e){const t=e.format;let a,r;switch(t){case 0:a=new Uint8Array(1+e.fdSelect.length);a[0]=t;for(r=0;r>8&255,255&i,n];for(r=1;r>8&255,255&r,t);n=t}}const o=(s.length-3)/3;s[1]=o>>8&255;s[2]=255&o;s.push(r>>8&255,255&r);a=new Uint8Array(s)}return this.compileTypedArray(a)}compileTypedArray(e){return Array.from(e)}compileIndex(e,t=[]){const a=e.objects,r=a.length;if(0===r)return[0,0];const i=[r>>8&255,255&r];let n,s,o=1;for(n=0;n>8&255,255&c):3===s?i.push(c>>16&255,c>>8&255,255&c):i.push(c>>>24&255,c>>16&255,c>>8&255,255&c);a[n]&&(c+=a[n].length)}for(n=0;n=this.firstChar&&e<=this.lastChar?e:-1}amend(e){unreachable("Should not call amend()")}}class CFFFont{constructor(e,t){this.properties=t;const a=new CFFParser(e,t,Er);this.cff=a.parse();this.cff.duplicateFirstGlyph();const r=new CFFCompiler(this.cff);this.seacs=this.cff.seacs;try{this.data=r.compile()}catch{warn("Failed to compile font "+t.loadedName);this.data=e}this._createBuiltInEncoding()}get numGlyphs(){return this.cff.charStrings.count}getCharset(){return this.cff.charset.charset}getGlyphMapping(){const e=this.cff,t=this.properties,{cidToGidMap:a,cMap:r}=t,i=e.charset.charset;let n,s;if(t.composite){let t,o;if(a?.length>0){t=Object.create(null);for(let e=0,r=a.length;e=0){const r=a[t];r&&(i[e]=r)}}i.length>0&&(this.properties.builtInEncoding=i)}}function getFloat214(e,t){return readInt16(e,t)/16384}function getSubroutineBias(e){const t=e.length;let a=32768;t<1240?a=107:t<33900&&(a=1131);return a}function parseCmap(e,t,a){const r=1===readUint16(e,t+2)?readUint32(e,t+8):readUint32(e,t+16),i=readUint16(e,t+r);let n,s,o;if(4===i){readUint16(e,t+r+2);const a=readUint16(e,t+r+6)>>1;s=t+r+14;n=[];for(o=0;o>1;a0;)h.push({flags:n})}for(a=0;a>1;y=!0;break;case 4:s+=i.pop();moveTo(n,s);y=!0;break;case 5:for(;i.length>0;){n+=i.shift();s+=i.shift();lineTo(n,s)}break;case 6:for(;i.length>0;){n+=i.shift();lineTo(n,s);if(0===i.length)break;s+=i.shift();lineTo(n,s)}break;case 7:for(;i.length>0;){s+=i.shift();lineTo(n,s);if(0===i.length)break;n+=i.shift();lineTo(n,s)}break;case 8:for(;i.length>0;){l=n+i.shift();u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+i.shift();bezierCurveTo(l,u,h,d,n,s)}break;case 10:m=i.pop();b=null;if(a.isCFFCIDFont){const e=a.fdSelect.getFDIndex(r);if(e>=0&&eMath.abs(s-t)?n+=i.shift():s+=i.shift();bezierCurveTo(l,u,h,d,n,s);break;default:throw new FormatError(`unknown operator: 12 ${w}`)}break;case 14:if(i.length>=4){const e=i.pop(),r=i.pop();s=i.pop();n=i.pop();t.save();t.translate(n,s);let o=lookupCmap(a.cmap,String.fromCharCode(a.glyphNameMap[Cr[e]]));compileCharString(a.glyphs[o.glyphId],t,a,o.glyphId);t.restore();o=lookupCmap(a.cmap,String.fromCharCode(a.glyphNameMap[Cr[r]]));compileCharString(a.glyphs[o.glyphId],t,a,o.glyphId)}return;case 19:case 20:o+=i.length>>1;c+=o+7>>3;y=!0;break;case 21:s+=i.pop();n+=i.pop();moveTo(n,s);y=!0;break;case 22:n+=i.pop();moveTo(n,s);y=!0;break;case 24:for(;i.length>2;){l=n+i.shift();u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+i.shift();bezierCurveTo(l,u,h,d,n,s)}n+=i.shift();s+=i.shift();lineTo(n,s);break;case 25:for(;i.length>6;){n+=i.shift();s+=i.shift();lineTo(n,s)}l=n+i.shift();u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+i.shift();bezierCurveTo(l,u,h,d,n,s);break;case 26:i.length%2&&(n+=i.shift());for(;i.length>0;){l=n;u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h;s=d+i.shift();bezierCurveTo(l,u,h,d,n,s)}break;case 27:i.length%2&&(s+=i.shift());for(;i.length>0;){l=n+i.shift();u=s;h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d;bezierCurveTo(l,u,h,d,n,s)}break;case 28:i.push(readInt16(e,c));c+=2;break;case 29:m=i.pop()+a.gsubrsBias;b=a.gsubrs[m];b&&parse(b);break;case 30:for(;i.length>0;){l=n;u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s);if(0===i.length)break;l=n+i.shift();u=s;h=l+i.shift();d=u+i.shift();s=d+i.shift();n=h+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s)}break;case 31:for(;i.length>0;){l=n+i.shift();u=s;h=l+i.shift();d=u+i.shift();s=d+i.shift();n=h+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s);if(0===i.length)break;l=n;u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s)}break;default:if(w<32)throw new FormatError(`unknown operator: ${w}`);if(w<247)i.push(w-139);else if(w<251)i.push(256*(w-247)+e[c++]+108);else if(w<255)i.push(256*-(w-251)-e[c++]-108);else{i.push((e[c]<<24|e[c+1]<<16|e[c+2]<<8|e[c+3])/65536);c+=4}}y&&(i.length=0)}}(e)}class Commands{cmds=[];transformStack=[];currentTransform=[1,0,0,1,0,0];add(e,t){if(t){const{currentTransform:a}=this;for(let e=0,r=t.length;e=0&&e2*readUint16(e,t)}const n=[];let s=i(t,0);for(let a=r;ae.getSize()+3&-4)))}write(){const e=this.getSize(),t=new DataView(new ArrayBuffer(e)),a=e>131070,r=a?4:2,i=new DataView(new ArrayBuffer((this.glyphs.length+1)*r));a?i.setUint32(0,0):i.setUint16(0,0);let n=0,s=0;for(const e of this.glyphs){n+=e.write(n,t);n=n+3&-4;s+=r;a?i.setUint32(s,n):i.setUint16(s,n>>1)}return{isLocationLong:a,loca:new Uint8Array(i.buffer),glyf:new Uint8Array(t.buffer)}}scale(e){for(let t=0,a=this.glyphs.length;te.getSize())));return this.header.getSize()+e}write(e,t){if(!this.header)return 0;const a=e;e+=this.header.write(e,t);if(this.simple)e+=this.simple.write(e,t);else for(const a of this.composites)e+=a.write(e,t);return e-a}scale(e){if(!this.header)return;const t=(this.header.xMin+this.header.xMax)/2;this.header.scale(t,e);if(this.simple)this.simple.scale(t,e);else for(const a of this.composites)a.scale(t,e)}}class GlyphHeader{constructor({numberOfContours:e,xMin:t,yMin:a,xMax:r,yMax:i}){this.numberOfContours=e;this.xMin=t;this.yMin=a;this.xMax=r;this.yMax=i}static parse(e,t){return[10,new GlyphHeader({numberOfContours:t.getInt16(e),xMin:t.getInt16(e+2),yMin:t.getInt16(e+4),xMax:t.getInt16(e+6),yMax:t.getInt16(e+8)})]}getSize(){return 10}write(e,t){t.setInt16(e,this.numberOfContours);t.setInt16(e+2,this.xMin);t.setInt16(e+4,this.yMin);t.setInt16(e+6,this.xMax);t.setInt16(e+8,this.yMax);return 10}scale(e,t){this.xMin=Math.round(e+(this.xMin-e)*t);this.xMax=Math.round(e+(this.xMax-e)*t)}}class Contour{constructor({flags:e,xCoordinates:t,yCoordinates:a}){this.xCoordinates=t;this.yCoordinates=a;this.flags=e}}class SimpleGlyph{constructor({contours:e,instructions:t}){this.contours=e;this.instructions=t}static parse(e,t,a){const r=[];for(let i=0;i255?e+=2:o>0&&(e+=1);t=n;o=Math.abs(s-a);o>255?e+=2:o>0&&(e+=1);a=s}}return e}write(e,t){const a=e,r=[],i=[],n=[];let s=0,o=0;for(const a of this.contours){for(let e=0,t=a.xCoordinates.length;e=0?18:2;r.push(e)}else r.push(l)}s=c;const h=a.yCoordinates[e];l=h-o;if(0===l){t|=32;i.push(0)}else{const e=Math.abs(l);if(e<=255){t|=l>=0?36:4;i.push(e)}else i.push(l)}o=h;n.push(t)}t.setUint16(e,r.length-1);e+=2}t.setUint16(e,this.instructions.length);e+=2;if(this.instructions.length){new Uint8Array(t.buffer,0,t.buffer.byteLength).set(this.instructions,e);e+=this.instructions.length}for(const a of n)t.setUint8(e++,a);for(let a=0,i=r.length;a=-128&&this.argument1<=127&&this.argument2>=-128&&this.argument2<=127||(e+=2):this.argument1>=0&&this.argument1<=255&&this.argument2>=0&&this.argument2<=255||(e+=2);return e}write(e,t){const a=e;2&this.flags?this.argument1>=-128&&this.argument1<=127&&this.argument2>=-128&&this.argument2<=127||(this.flags|=1):this.argument1>=0&&this.argument1<=255&&this.argument2>=0&&this.argument2<=255||(this.flags|=1);t.setUint16(e,this.flags);t.setUint16(e+2,this.glyphIndex);e+=4;if(1&this.flags){if(2&this.flags){t.setInt16(e,this.argument1);t.setInt16(e+2,this.argument2)}else{t.setUint16(e,this.argument1);t.setUint16(e+2,this.argument2)}e+=4}else{t.setUint8(e,this.argument1);t.setUint8(e+1,this.argument2);e+=2}if(256&this.flags){t.setUint16(e,this.instructions.length);e+=2;if(this.instructions.length){new Uint8Array(t.buffer,0,t.buffer.byteLength).set(this.instructions,e);e+=this.instructions.length}}return e-a}scale(e,t){}}function writeInt16(e,t,a){e[t]=a>>8&255;e[t+1]=255&a}function writeInt32(e,t,a){e[t]=a>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}function writeData(e,t,a){if(a instanceof Uint8Array)e.set(a,t);else if("string"==typeof a)for(let r=0,i=a.length;ra;){a<<=1;r++}const i=a*t;return{range:i,entry:r,rangeShift:t*e-i}}toArray(){let e=this.sfnt;const t=this.tables,a=Object.keys(t);a.sort();const r=a.length;let i,n,s,o,c,l=12+16*r;const h=[l];for(i=0;i>>0;h.push(l)}const u=new Uint8Array(l);for(i=0;i>>0}writeInt32(u,l+4,e);writeInt32(u,l+8,h[i]);writeInt32(u,l+12,t[c].length);l+=16}return u}addTable(e,t){if(e in this.tables)throw new Error("Table "+e+" already exists");this.tables[e]=t}}const ci=[4],li=[5],hi=[6],ui=[7],di=[8],fi=[12,35],gi=[14],pi=[21],mi=[22],bi=[30],yi=[31];class Type1CharString{constructor(){this.width=0;this.lsb=0;this.flexing=!1;this.output=[];this.stack=[]}convert(e,t,a){const r=e.length;let i,n,s,o=!1;for(let c=0;cr)return!0;const i=r-e;for(let e=i;e>8&255,255&t);else{t=65536*t|0;this.output.push(255,t>>24&255,t>>16&255,t>>8&255,255&t)}}this.output.push(...t);a?this.stack.splice(i,e):this.stack.length=0;return!1}}function isHexDigit(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function decrypt(e,t,a){if(a>=e.length)return new Uint8Array(0);let r,i,n=0|t;for(r=0;r>8;n=52845*(t+n)+22719&65535}return o}function isSpecial(e){return 47===e||91===e||93===e||123===e||125===e||40===e||41===e}class Type1Parser{constructor(e,t,a){if(t){const t=e.getBytes(),a=!((isHexDigit(t[0])||isWhiteSpace(t[0]))&&isHexDigit(t[1])&&isHexDigit(t[2])&&isHexDigit(t[3])&&isHexDigit(t[4])&&isHexDigit(t[5])&&isHexDigit(t[6])&&isHexDigit(t[7]));e=new Stream(a?decrypt(t,55665,4):function decryptAscii(e,t,a){let r=0|t;const i=e.length,n=new Uint8Array(i>>>1);let s,o;for(s=0,o=0;s>8;r=52845*(e+r)+22719&65535}}return n.slice(a,o)}(t,55665,4))}this.seacAnalysisEnabled=!!a;this.stream=e;this.nextChar()}readNumberArray(){this.getToken();const e=[];for(;;){const t=this.getToken();if(null===t||"]"===t||"}"===t)break;e.push(parseFloat(t||0))}return e}readNumber(){const e=this.getToken();return parseFloat(e||0)}readInt(){const e=this.getToken();return 0|parseInt(e||0,10)}readBoolean(){return"true"===this.getToken()?1:0}nextChar(){return this.currentChar=this.stream.getByte()}prevChar(){this.stream.skip(-2);return this.currentChar=this.stream.getByte()}getToken(){let e=!1,t=this.currentChar;for(;;){if(-1===t)return null;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!isWhiteSpace(t))break;t=this.nextChar()}if(isSpecial(t)){this.nextChar();return String.fromCharCode(t)}let a="";do{a+=String.fromCharCode(t);t=this.nextChar()}while(t>=0&&!isWhiteSpace(t)&&!isSpecial(t));return a}readCharStrings(e,t){return-1===t?e:decrypt(e,4330,t)}extractFontProgram(e){const t=this.stream,a=[],r=[],i=Object.create(null);i.lenIV=4;const n={subrs:[],charstrings:[],properties:{privateData:i}};let s,o,c,l;for(;null!==(s=this.getToken());)if("/"===s){s=this.getToken();switch(s){case"CharStrings":this.getToken();this.getToken();this.getToken();this.getToken();for(;;){s=this.getToken();if(null===s||"end"===s)break;if("/"!==s)continue;const e=this.getToken();o=this.readInt();this.getToken();c=o>0?t.getBytes(o):new Uint8Array(0);l=n.properties.privateData.lenIV;const a=this.readCharStrings(c,l);this.nextChar();s=this.getToken();"noaccess"===s?this.getToken():"/"===s&&this.prevChar();r.push({glyph:e,encoded:a})}break;case"Subrs":this.readInt();this.getToken();for(;"dup"===this.getToken();){const e=this.readInt();o=this.readInt();this.getToken();c=o>0?t.getBytes(o):new Uint8Array(0);l=n.properties.privateData.lenIV;const r=this.readCharStrings(c,l);this.nextChar();s=this.getToken();"noaccess"===s&&this.getToken();a[e]=r}break;case"BlueValues":case"OtherBlues":case"FamilyBlues":case"FamilyOtherBlues":const e=this.readNumberArray();e.length>0&&e.length,0;break;case"StemSnapH":case"StemSnapV":n.properties.privateData[s]=this.readNumberArray();break;case"StdHW":case"StdVW":n.properties.privateData[s]=this.readNumberArray()[0];break;case"BlueShift":case"lenIV":case"BlueFuzz":case"BlueScale":case"LanguageGroup":n.properties.privateData[s]=this.readNumber();break;case"ExpansionFactor":n.properties.privateData[s]=this.readNumber()||.06;break;case"ForceBold":n.properties.privateData[s]=this.readBoolean()}}for(const{encoded:t,glyph:i}of r){const r=new Type1CharString,s=r.convert(t,a,this.seacAnalysisEnabled);let o=r.output;s&&(o=[14]);const c={glyphName:i,charstring:o,width:r.width,lsb:r.lsb,seac:r.seac};".notdef"===i?n.charstrings.unshift(c):n.charstrings.push(c);if(e.builtInEncoding){const t=e.builtInEncoding.indexOf(i);t>-1&&void 0===e.widths[t]&&t>=e.firstChar&&t<=e.lastChar&&(e.widths[t]=r.width)}}return n}extractFontHeader(e){let t;for(;null!==(t=this.getToken());)if("/"===t){t=this.getToken();switch(t){case"FontMatrix":const a=this.readNumberArray();e.fontMatrix=a;break;case"Encoding":const r=this.getToken();let i;if(/^\d+$/.test(r)){i=[];const e=0|parseInt(r,10);this.getToken();for(let a=0;a=i){s+=a;for(;s=0&&(r[e]=i)}}return type1FontGlyphMapping(e,r,a)}hasGlyphId(e){if(e<0||e>=this.numGlyphs)return!1;if(0===e)return!0;return this.charstrings[e-1].charstring.length>0}getSeacs(e){const t=[];for(let a=0,r=e.length;a0;e--)t[e]-=t[e-1];f.setByName(e,t)}n.topDict.privateDict=f;const p=new CFFIndex;for(h=0,u=r.length;h0&&e.toUnicode.amend(t)}class fonts_Glyph{constructor(e,t,a,r,i,n,s,o,c){this.originalCharCode=e;this.fontChar=t;this.unicode=a;this.accent=r;this.width=i;this.vmetric=n;this.operatorListId=s;this.isSpace=o;this.isInFont=c}get category(){return shadow(this,"category",function getCharUnicodeCategory(e){const t=Nr.get(e);if(t)return t;const a=e.match(Rr),r={isWhitespace:!!a?.[1],isZeroWidthDiacritic:!!a?.[2],isInvisibleFormatMark:!!a?.[3]};Nr.set(e,r);return r}(this.unicode),!0)}}function int16(e,t){return(e<<8)+t}function writeSignedInt16(e,t,a){e[t+1]=a;e[t]=a>>>8}function signedInt16(e,t){const a=(e<<8)+t;return 32768&a?a-65536:a}function string16(e){return String.fromCharCode(e>>8&255,255&e)}function safeString16(e){e>32767?e=32767:e<-32768&&(e=-32768);return String.fromCharCode(e>>8&255,255&e)}function isTrueTypeCollectionFile(e){return"ttcf"===bytesToString(e.peekBytes(4))}function getFontFileType(e,{type:t,subtype:a,composite:r}){let i,n;if(function isTrueTypeFile(e){const t=e.peekBytes(4);return 65536===readUint32(t,0)||"true"===bytesToString(t)}(e)||isTrueTypeCollectionFile(e))i=r?"CIDFontType2":"TrueType";else if(function isOpenTypeFile(e){return"OTTO"===bytesToString(e.peekBytes(4))}(e))i=r?"CIDFontType2":"OpenType";else if(function isType1File(e){const t=e.peekBytes(2);return 37===t[0]&&33===t[1]||128===t[0]&&1===t[1]}(e))i=r?"CIDFontType0":"MMType1"===t?"MMType1":"Type1";else if(function isCFFFile(e){const t=e.peekBytes(4);return t[0]>=1&&t[3]>=1&&t[3]<=4}(e))if(r){i="CIDFontType0";n="CIDFontType0C"}else{i="MMType1"===t?"MMType1":"Type1";n="Type1C"}else{warn("getFontFileType: Unable to detect correct font file Type/Subtype.");i=t;n=a}return[i,n]}function applyStandardFontGlyphMap(e,t){for(const a in t)e[+a]=t[a]}function buildToFontChar(e,t,a){const r=[];let i;for(let a=0,n=e.length;ah){c++;if(c>=wi.length){warn("Ran out of space in font private use area.");break}l=wi[c][0];h=wi[c][1]}const p=l++;0===g&&(g=a);let m=r.get(f);if("string"==typeof m)if(1===m.length)m=m.codePointAt(0);else{if(!u){u=new Map;for(let e=64256;e<=64335;e++){const t=String.fromCharCode(e).normalize("NFKD");t.length>1&&u.set(t,e)}}m=u.get(m)||m.codePointAt(0)}if(m&&!(d=m,wi[0][0]<=d&&d<=wi[0][1]||wi[1][0]<=d&&d<=wi[1][1])&&!o.has(g)){n.set(m,g);o.add(g)}i[p]=g;s[f]=p}var d;return{toFontChar:s,charCodeToGlyphId:i,toUnicodeExtraMap:n,nextAvailableFontCharCode:l}}function createCmapTable(e,t,a){const r=function getRanges(e,t,a){const r=[];for(const t in e)e[t]>=a||r.push({fontCharCode:0|t,glyphId:e[t]});if(t)for(const[e,i]of t)i>=a||r.push({fontCharCode:e,glyphId:i});0===r.length&&r.push({fontCharCode:0,glyphId:0});r.sort(((e,t)=>e.fontCharCode-t.fontCharCode));const i=[],n=r.length;for(let e=0;e65535?2:1;let n,s,o,c,l="\0\0"+string16(i)+"\0\0"+string32(4+8*i);for(n=r.length-1;n>=0&&!(r[n][0]<=65535);--n);const h=n+1;r[n][0]<65535&&65535===r[n][1]&&(r[n][1]=65534);const u=r[n][1]<65535?1:0,d=h+u,f=OpenTypeFileBuilder.getSearchParams(d,2);let g,p,m,b,y="",w="",S="",x="",k="",C=0;for(n=0,s=h;n0){w+="ÿÿ";y+="ÿÿ";S+="\0";x+="\0\0"}const v="\0\0"+string16(2*d)+string16(f.range)+string16(f.entry)+string16(f.rangeShift)+w+"\0\0"+y+S+x+k;let F="",O="";if(i>1){l+="\0\0\n"+string32(4+8*i+4+v.length);F="";for(n=0,s=r.length;ne||!o)&&(o=e);c 123 are reserved for internal usage");s|=1<65535&&(c=65535)}else{o=0;c=255}const h=e.bbox||[0,0,0,0],u=a.unitsPerEm||(e.fontMatrix?1/Math.max(...e.fontMatrix.slice(0,4).map(Math.abs)):1e3),d=e.ascentScaled?1:u/Si,f=a.ascent||Math.round(d*(e.ascent||h[3]));let g=a.descent||Math.round(d*(e.descent||h[1]));g>0&&e.descent>0&&h[1]<0&&(g=-g);const p=a.yMax||f,m=-a.yMin||-g;return"\0$ô\0\0\0Š»\0\0\0ŒŠ»\0\0ß\x001\0\0\0\0"+String.fromCharCode(e.fixedPitch?9:0)+"\0\0\0\0\0\0"+string32(r)+string32(i)+string32(n)+string32(s)+"*21*"+string16(e.italicAngle?1:0)+string16(o||e.firstChar)+string16(c||e.lastChar)+string16(f)+string16(g)+"\0d"+string16(p)+string16(m)+"\0\0\0\0\0\0\0\0"+string16(e.xHeight)+string16(e.capHeight)+string16(0)+string16(o||e.firstChar)+"\0"}function createPostTable(e){return"\0\0\0"+string32(Math.floor(65536*e.italicAngle))+"\0\0\0\0"+string32(e.fixedPitch?1:0)+"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}function createPostscriptName(e){return e.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g,"").slice(0,63)}function createNameTable(e,t){t||(t=[[],[]]);const a=[t[0][0]||"Original licence",t[0][1]||e,t[0][2]||"Unknown",t[0][3]||"uniqueID",t[0][4]||e,t[0][5]||"Version 0.11",t[0][6]||createPostscriptName(e),t[0][7]||"Unknown",t[0][8]||"Unknown",t[0][9]||"Unknown"],r=[];let i,n,s,o,c;for(i=0,n=a.length;i0;if((s||o)&&"CIDFontType2"===a&&this.cidEncoding.startsWith("Identity-")){const a=e.cidToGidMap,r=[];applyStandardFontGlyphMap(r,ri());/Arial-?Black/i.test(t)?applyStandardFontGlyphMap(r,ii()):/Calibri/i.test(t)&&applyStandardFontGlyphMap(r,ni());if(a){for(const e in r){const t=r[e];void 0!==a[t]&&(r[+e]=a[t])}a.length!==this.toUnicode.length&&e.hasIncludedToUnicodeMap&&this.toUnicode instanceof IdentityToUnicodeMap&&this.toUnicode.forEach((function(e,t){const i=r[e];void 0===a[i]&&(r[+e]=t)}))}this.toUnicode instanceof IdentityToUnicodeMap||this.toUnicode.forEach((function(e,t){r[+e]=t}));this.toFontChar=r;this.toUnicode=new ToUnicodeMap(r)}else if(/Symbol/i.test(r))this.toFontChar=buildToFontChar(Fr,Or(),this.differences);else if(/Dingbats/i.test(r))this.toFontChar=buildToFontChar(Ir,Tr(),this.differences);else if(s||o){const e=buildToFontChar(this.defaultEncoding,Or(),this.differences);"CIDFontType2"!==a||this.cidEncoding.startsWith("Identity-")||this.toUnicode instanceof IdentityToUnicodeMap||this.toUnicode.forEach((function(t,a){e[+t]=a}));this.toFontChar=e}else{const e=Or(),a=[];this.toUnicode.forEach(((t,r)=>{if(!this.composite){const a=getUnicodeForGlyph(this.differences[t]||this.defaultEncoding[t],e);-1!==a&&(r=a)}a[+t]=r}));this.composite&&this.toUnicode instanceof IdentityToUnicodeMap&&/Tahoma|Verdana/i.test(t)&&applyStandardFontGlyphMap(a,ri());this.toFontChar=a}amendFallbackToUnicode(e);this.loadedName=r.split("-",1)[0]}checkAndRepair(e,t,a){const r=["OS/2","cmap","head","hhea","hmtx","maxp","name","post","loca","glyf","fpgm","prep","cvt ","CFF "];function readTables(e,t){const a=Object.create(null);a["OS/2"]=null;a.cmap=null;a.head=null;a.hhea=null;a.hmtx=null;a.maxp=null;a.name=null;a.post=null;for(let i=0;i>>0,r=e.getInt32()>>>0,i=e.getInt32()>>>0,n=e.pos;e.pos=e.start||0;e.skip(r);const s=e.getBytes(i);e.pos=n;if("head"===t){s[8]=s[9]=s[10]=s[11]=0;s[17]|=32}return{tag:t,checksum:a,length:i,offset:r,data:s}}function readOpenTypeHeader(e){return{version:e.getString(4),numTables:e.getUint16(),searchRange:e.getUint16(),entrySelector:e.getUint16(),rangeShift:e.getUint16()}}function sanitizeGlyph(e,t,a,r,i,n){const s={length:0,sizeOfInstructions:0};if(t<0||t>=e.length||a>e.length||a-t<=12)return s;const o=e.subarray(t,a),c=signedInt16(o[2],o[3]),l=signedInt16(o[4],o[5]),h=signedInt16(o[6],o[7]),u=signedInt16(o[8],o[9]);if(c>h){writeSignedInt16(o,2,h);writeSignedInt16(o,6,c)}if(l>u){writeSignedInt16(o,4,u);writeSignedInt16(o,8,l)}const d=signedInt16(o[0],o[1]);if(d<0){if(d<-1)return s;r.set(o,i);s.length=o.length;return s}let f,g=10,p=0;for(f=0;fo.length)return s;if(!n&&b>0){r.set(o.subarray(0,m),i);r.set([0,0],i+m);r.set(o.subarray(y,S),i+m+2);S-=b;o.length-S>3&&(S=S+3&-4);s.length=S;return s}if(o.length-S>3){S=S+3&-4;r.set(o.subarray(0,S),i);s.length=S;return s}r.set(o,i);s.length=o.length;return s}function readNameTable(e){const a=(t.start||0)+e.offset;t.pos=a;const r=[[],[]],i=[],n=e.length,s=a+n;if(0!==t.getUint16()||n<6)return[r,i];const o=t.getUint16(),c=t.getUint16();let l,h;for(l=0;ls)continue;t.pos=n;const o=e.name;if(e.encoding){let a="";for(let r=0,i=e.length;r0&&(l+=e-1)}}else{if(m||y){warn("TT: nested FDEFs not allowed");p=!0}m=!0;u=l;s=d.pop();t.functionsDefined[s]={data:c,i:l}}else if(!m&&!y){s=d.at(-1);if(isNaN(s))info("TT: CALL empty stack (or invalid entry).");else{t.functionsUsed[s]=!0;if(s in t.functionsStackDeltas){const e=d.length+t.functionsStackDeltas[s];if(e<0){warn("TT: CALL invalid functions stack delta.");t.hintsValid=!1;return}d.length=e}else if(s in t.functionsDefined&&!g.includes(s)){f.push({data:c,i:l,stackTop:d.length-1});g.push(s);o=t.functionsDefined[s];if(!o){warn("TT: CALL non-existent function");t.hintsValid=!1;return}c=o.data;l=o.i}}}if(!m&&!y){let t=0;e<=142?t=i[e]:e>=192&&e<=223?t=-1:e>=224&&(t=-2);if(e>=113&&e<=117){r=d.pop();isNaN(r)||(t=2*-r)}for(;t<0&&d.length>0;){d.pop();t++}for(;t>0;){d.push(NaN);t--}}}t.tooComplexToFollowFunctions=p;const w=[c];l>c.length&&w.push(new Uint8Array(l-c.length));if(u>h){warn("TT: complementing a missing function tail");w.push(new Uint8Array([34,45]))}!function foldTTTable(e,t){if(t.length>1){let a,r,i=0;for(a=0,r=t.length;a>>0,n=[];for(let t=0;t>>0);const s={ttcTag:t,majorVersion:a,minorVersion:r,numFonts:i,offsetTable:n};switch(a){case 1:return s;case 2:s.dsigTag=e.getInt32()>>>0;s.dsigLength=e.getInt32()>>>0;s.dsigOffset=e.getInt32()>>>0;return s}throw new FormatError(`Invalid TrueType Collection majorVersion: ${a}.`)}(e),i=t.split("+");let n;for(let s=0;s0||!(a.cMap instanceof IdentityCMap));if("OTTO"===n.version&&!t||!s.head||!s.hhea||!s.maxp||!s.post){c=new Stream(s["CFF "].data);o=new CFFFont(c,a);return this.convert(e,o,a)}delete s.glyf;delete s.loca;delete s.fpgm;delete s.prep;delete s["cvt "];this.isOpenType=!0}if(!s.maxp)throw new FormatError('Required "maxp" table is not found');t.pos=(t.start||0)+s.maxp.offset;let h=t.getInt32();const u=t.getUint16();if(65536!==h&&20480!==h){if(6===s.maxp.length)h=20480;else{if(!(s.maxp.length>=32))throw new FormatError('"maxp" table has a wrong version number');h=65536}!function writeUint32(e,t,a){e[t+3]=255&a;e[t+2]=a>>>8;e[t+1]=a>>>16;e[t]=a>>>24}(s.maxp.data,0,h)}if(a.scaleFactors?.length===u&&l){const{scaleFactors:e}=a,t=int16(s.head.data[50],s.head.data[51]),r=new GlyfTable({glyfTable:s.glyf.data,isGlyphLocationsLong:t,locaTable:s.loca.data,numGlyphs:u});r.scale(e);const{glyf:i,loca:n,isLocationLong:o}=r.write();s.glyf.data=i;s.loca.data=n;if(o!==!!t){s.head.data[50]=0;s.head.data[51]=o?1:0}const c=s.hmtx.data;for(let t=0;t>8&255;c[a+1]=255&r;writeSignedInt16(c,a+2,Math.round(e[t]*signedInt16(c[a+2],c[a+3])))}}let d=u+1,f=!0;if(d>65535){f=!1;d=u;warn("Not enough space in glyfs to duplicate first glyph.")}let g=0,p=0;if(h>=65536&&s.maxp.length>=32){t.pos+=8;if(t.getUint16()>2){s.maxp.data[14]=0;s.maxp.data[15]=2}t.pos+=4;g=t.getUint16();t.pos+=4;p=t.getUint16()}s.maxp.data[4]=d>>8;s.maxp.data[5]=255&d;const m=function sanitizeTTPrograms(e,t,a,r){const i={functionsDefined:[],functionsUsed:[],functionsStackDeltas:[],tooComplexToFollowFunctions:!1,hintsValid:!0};e&&sanitizeTTProgram(e,i);t&&sanitizeTTProgram(t,i);e&&function checkInvalidFunctions(e,t){if(!e.tooComplexToFollowFunctions)if(e.functionsDefined.length>t){warn("TT: more functions defined than expected");e.hintsValid=!1}else for(let a=0,r=e.functionsUsed.length;at){warn("TT: invalid function id: "+a);e.hintsValid=!1;return}if(e.functionsUsed[a]&&!e.functionsDefined[a]){warn("TT: undefined function: "+a);e.hintsValid=!1;return}}}(i,r);if(a&&1&a.length){const e=new Uint8Array(a.length+1);e.set(a.data);a.data=e}return i.hintsValid}(s.fpgm,s.prep,s["cvt "],g);if(!m){delete s.fpgm;delete s.prep;delete s["cvt "]}!function sanitizeMetrics(e,t,a,r,i,n){if(!t){a&&(a.data=null);return}e.pos=(e.start||0)+t.offset;e.pos+=4;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;const s=e.getUint16();e.pos+=8;e.pos+=2;let o=e.getUint16();if(0!==s){if(!(2&int16(r.data[44],r.data[45]))){t.data[22]=0;t.data[23]=0}}if(o>i){info(`The numOfMetrics (${o}) should not be greater than the numGlyphs (${i}).`);o=i;t.data[34]=(65280&o)>>8;t.data[35]=255&o}const c=i-o-(a.length-4*o>>1);if(c>0){const e=new Uint8Array(a.length+2*c);e.set(a.data);if(n){e[a.length]=a.data[2];e[a.length+1]=a.data[3]}a.data=e}}(t,s.hhea,s.hmtx,s.head,d,f);if(!s.head)throw new FormatError('Required "head" table is not found');!function sanitizeHead(e,t,a){const r=e.data,i=function int32(e,t,a,r){return(e<<24)+(t<<16)+(a<<8)+r}(r[0],r[1],r[2],r[3]);if(i>>16!=1){info("Attempting to fix invalid version in head table: "+i);r[0]=0;r[1]=1;r[2]=0;r[3]=0}const n=int16(r[50],r[51]);if(n<0||n>1){info("Attempting to fix invalid indexToLocFormat in head table: "+n);const e=t+1;if(a===e<<1){r[50]=0;r[51]=0}else{if(a!==e<<2)throw new FormatError("Could not fix indexToLocFormat: "+n);r[50]=0;r[51]=1}}}(s.head,u,l?s.loca.length:0);let b=Object.create(null);if(l){const e=int16(s.head.data[50],s.head.data[51]),t=function sanitizeGlyphLocations(e,t,a,r,i,n,s){let o,c,l;if(r){o=4;c=function fontItemDecodeLong(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]};l=function fontItemEncodeLong(e,t,a){e[t]=a>>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}}else{o=2;c=function fontItemDecode(e,t){return e[t]<<9|e[t+1]<<1};l=function fontItemEncode(e,t,a){e[t]=a>>9&255;e[t+1]=a>>1&255}}const h=n?a+1:a,u=o*(1+h),d=new Uint8Array(u);d.set(e.data.subarray(0,u));e.data=d;const f=t.data,g=f.length,p=new Uint8Array(g);let m,b;const y=[];for(m=0,b=0;mg&&(e=g);y.push({index:m,offset:e,endOffset:0})}y.sort(((e,t)=>e.offset-t.offset));for(m=0;me.index-t.index));for(m=0;ms&&(s=e.sizeOfInstructions);x+=t;l(d,b,x)}if(0===x){const e=new Uint8Array([0,1,0,0,0,0,0,0,0,0,0,0,0,0,49,0]);for(m=0,b=o;ma+x)t.data=p.subarray(0,a+x);else{t.data=new Uint8Array(a+x);t.data.set(p.subarray(0,x))}t.data.set(p.subarray(0,a),x);l(e.data,d.length-o,x+a)}else t.data=p.subarray(0,x);return{missingGlyphs:S,maxSizeOfInstructions:s}}(s.loca,s.glyf,u,e,m,f,p);b=t.missingGlyphs;if(h>=65536&&s.maxp.length>=32){s.maxp.data[26]=t.maxSizeOfInstructions>>8;s.maxp.data[27]=255&t.maxSizeOfInstructions}}if(!s.hhea)throw new FormatError('Required "hhea" table is not found');if(0===s.hhea.data[10]&&0===s.hhea.data[11]){s.hhea.data[10]=255;s.hhea.data[11]=255}const y={unitsPerEm:int16(s.head.data[18],s.head.data[19]),yMax:signedInt16(s.head.data[42],s.head.data[43]),yMin:signedInt16(s.head.data[38],s.head.data[39]),ascent:signedInt16(s.hhea.data[4],s.hhea.data[5]),descent:signedInt16(s.hhea.data[6],s.hhea.data[7]),lineGap:signedInt16(s.hhea.data[8],s.hhea.data[9])};this.ascent=y.ascent/y.unitsPerEm;this.descent=y.descent/y.unitsPerEm;this.lineGap=y.lineGap/y.unitsPerEm;if(this.cssFontInfo?.lineHeight){this.lineHeight=this.cssFontInfo.metrics.lineHeight;this.lineGap=this.cssFontInfo.metrics.lineGap}else this.lineHeight=this.ascent-this.descent+this.lineGap;s.post&&function readPostScriptTable(e,a,r){const i=(t.start||0)+e.offset;t.pos=i;const n=i+e.length,s=t.getInt32();t.skip(28);let o,c,l=!0;switch(s){case 65536:o=Ur;break;case 131072:const e=t.getUint16();if(e!==r){l=!1;break}const i=[];for(c=0;c=32768){l=!1;break}i.push(e)}if(!l)break;const h=[],u=[];for(;t.pos65535)throw new FormatError("Max size of CID is 65,535");let i=-1;t?i=r:void 0!==e[r]&&(i=e[r]);i>=0&&i>>0;let h=!1;if(o?.platformId!==i||o?.encodingId!==n){if(0!==i||0!==n&&1!==n&&3!==n)if(1===i&&0===n)h=!0;else if(3!==i||1!==n||!r&&o){if(a&&3===i&&0===n){h=!0;let a=!0;if(e>3;e.push(r);a=Math.max(r,a)}const r=[];for(let e=0;e<=a;e++)r.push({firstCode:t.getUint16(),entryCount:t.getUint16(),idDelta:signedInt16(t.getByte(),t.getByte()),idRangePos:t.pos+t.getUint16()});for(let a=0;a<256;a++)if(0===e[a]){t.pos=r[0].idRangePos+2*a;f=t.getUint16();u.push({charCode:a,glyphId:f})}else{const i=r[e[a]];for(d=0;d>1;t.skip(6);const a=[];let r;for(r=0;r>1)-(e-r);i.offsetIndex=s;o=Math.max(o,s+i.end-i.start+1)}else i.offsetIndex=-1}const c=[];for(d=0;d>>0;for(d=0;d>>0,a=t.getInt32()>>>0;let r=t.getInt32()>>>0;for(let t=e;t<=a;t++)u.push({charCode:t,glyphId:r++})}}}u.sort(((e,t)=>e.charCode-t.charCode));const g=[],p=new Set;for(const e of u){const{charCode:t}=e;if(!p.has(t)){p.add(t);g.push(e)}}return{platformId:o.platformId,encodingId:o.encodingId,mappings:g,hasShortCmap:h}}(s.cmap,t,this.isSymbolicFont,a.hasEncoding),r=e.platformId,i=e.encodingId,n=e.mappings;let o=[],c=!1;!a.hasEncoding||"MacRomanEncoding"!==a.baseEncodingName&&"WinAnsiEncoding"!==a.baseEncodingName||(o=getEncoding(a.baseEncodingName));if(a.hasEncoding&&!this.isSymbolicFont&&(3===r&&1===i||1===r&&0===i)){const e=Or();for(let t=0;t<256;t++){let s;s=void 0!==this.differences[t]?this.differences[t]:o.length&&""!==o[t]?o[t]:Cr[t];if(!s)continue;const c=recoverGlyphName(s,e);let l;3===r&&1===i?l=e[c]:1===r&&0===i&&(l=kr.indexOf(c));if(void 0===l){if(!a.glyphNames&&a.hasIncludedToUnicodeMap&&!(this.toUnicode instanceof IdentityToUnicodeMap)){const e=this.toUnicode.get(t);e&&(l=e.codePointAt(0))}if(void 0===l)continue}for(const e of n)if(e.charCode===l){w[t]=e.glyphId;break}}}else if(0===r){for(const e of n)w[e.charCode]=e.glyphId;c=!0}else if(3===r&&0===i)for(const e of n){let t=e.charCode;t>=61440&&t<=61695&&(t&=255);w[t]=e.glyphId}else for(const e of n)w[e.charCode]=e.glyphId;if(a.glyphNames&&(o.length||this.differences.length))for(let e=0;e<256;++e){if(!c&&void 0!==w[e])continue;const t=this.differences[e]||o[e];if(!t)continue;const r=a.glyphNames.indexOf(t);r>0&&hasGlyph(r)&&(w[e]=r)}}0===w.length&&(w[0]=0);let S=d-1;f||(S=0);if(!a.cssFontInfo){const e=adjustMapping(w,hasGlyph,S,this.toUnicode);this.toFontChar=e.toFontChar;s.cmap={tag:"cmap",data:createCmapTable(e.charCodeToGlyphId,e.toUnicodeExtraMap,d)};s["OS/2"]&&function validateOS2Table(e,t){t.pos=(t.start||0)+e.offset;const a=t.getUint16();t.skip(60);const r=t.getUint16();if(a<4&&768&r)return!1;if(t.getUint16()>t.getUint16())return!1;t.skip(6);if(0===t.getUint16())return!1;e.data[8]=e.data[9]=0;return!0}(s["OS/2"],t)||(s["OS/2"]={tag:"OS/2",data:createOS2Table(a,e.charCodeToGlyphId,y)})}if(!l)try{c=new Stream(s["CFF "].data);o=new CFFParser(c,a,Er).parse();o.duplicateFirstGlyph();const e=new CFFCompiler(o);s["CFF "].data=e.compile()}catch{warn("Failed to compile font "+a.loadedName)}if(s.name){const[t,r]=readNameTable(s.name);s.name.data=createNameTable(e,t);this.psName=t[0][6]||null;a.composite||function adjustTrueTypeToUnicode(e,t,a){if(e.isInternalFont)return;if(e.hasIncludedToUnicodeMap)return;if(e.hasEncoding)return;if(e.toUnicode instanceof IdentityToUnicodeMap)return;if(!t)return;if(0===a.length)return;if(e.defaultEncoding===vr)return;for(const e of a)if(!isWinNameRecord(e))return;const r=vr,i=[],n=Or();for(const e in r){const t=r[e];if(""===t)continue;const a=n[t];void 0!==a&&(i[e]=String.fromCharCode(a))}i.length>0&&e.toUnicode.amend(i)}(a,this.isSymbolicFont,r)}else s.name={tag:"name",data:createNameTable(this.name)};const x=new OpenTypeFileBuilder(n.version);for(const e in s)x.addTable(e,s[e].data);return x.toArray()}convert(e,a,r){r.fixedPitch=!1;r.builtInEncoding&&function adjustType1ToUnicode(e,t){if(e.isInternalFont)return;if(e.hasIncludedToUnicodeMap)return;if(t===e.defaultEncoding)return;if(e.toUnicode instanceof IdentityToUnicodeMap)return;const a=[],r=Or();for(const i in t){if(e.hasEncoding&&(e.baseEncodingName||void 0!==e.differences[i]))continue;const n=getUnicodeForGlyph(t[i],r);-1!==n&&(a[i]=String.fromCharCode(n))}a.length>0&&e.toUnicode.amend(a)}(r,r.builtInEncoding);let i=1;a instanceof CFFFont&&(i=a.numGlyphs-1);const n=a.getGlyphMapping(r);let s=null,o=n,c=null;if(!r.cssFontInfo){s=adjustMapping(n,a.hasGlyphId.bind(a),i,this.toUnicode);this.toFontChar=s.toFontChar;o=s.charCodeToGlyphId;c=s.toUnicodeExtraMap}const l=a.numGlyphs;function getCharCodes(e,t){let a=null;for(const r in e)t===e[r]&&(a||=[]).push(0|r);return a}function createCharCode(e,t){for(const a in e)if(t===e[a])return 0|a;s.charCodeToGlyphId[s.nextAvailableFontCharCode]=t;return s.nextAvailableFontCharCode++}const h=a.seacs;if(s&&h?.length){const e=r.fontMatrix||t,i=a.getCharset(),o=Object.create(null);for(let t in h){t|=0;const a=h[t],r=Cr[a[2]],c=Cr[a[3]],l=i.indexOf(r),u=i.indexOf(c);if(l<0||u<0)continue;const d={x:a[0]*e[0]+a[1]*e[2]+e[4],y:a[0]*e[1]+a[1]*e[3]+e[5]},f=getCharCodes(n,t);if(f)for(const e of f){const t=s.charCodeToGlyphId,a=createCharCode(t,l),r=createCharCode(t,u);o[e]={baseFontCharCode:a,accentFontCharCode:r,accentOffset:d}}}r.seacMap=o}const u=r.fontMatrix?1/Math.max(...r.fontMatrix.slice(0,4).map(Math.abs)):1e3,d=new OpenTypeFileBuilder("OTTO");d.addTable("CFF ",a.data);d.addTable("OS/2",createOS2Table(r,o));d.addTable("cmap",createCmapTable(o,c,l));d.addTable("head","\0\0\0\0\0\0\0\0\0\0_<õ\0\0"+safeString16(u)+"\0\0\0\0ž\v~'\0\0\0\0ž\v~'\0\0"+safeString16(r.descent)+"ÿ"+safeString16(r.ascent)+string16(r.italicAngle?2:0)+"\0\0\0\0\0\0\0");d.addTable("hhea","\0\0\0"+safeString16(r.ascent)+safeString16(r.descent)+"\0\0ÿÿ\0\0\0\0\0\0"+safeString16(r.capHeight)+safeString16(Math.tan(r.italicAngle)*r.xHeight)+"\0\0\0\0\0\0\0\0\0\0\0\0"+string16(l));d.addTable("hmtx",function fontFieldsHmtx(){const e=a.charstrings,t=a.cff?a.cff.widths:null;let r="\0\0\0\0";for(let a=1,i=l;a=65520&&e<=65535?0:e>=62976&&e<=63743?Mr()[e]||e:173===e?45:e}(a)}this.isType3Font&&(i=a);let h=null;if(this.seacMap?.[e]){l=!0;const t=this.seacMap[e];a=t.baseFontCharCode;h={fontChar:String.fromCodePoint(t.accentFontCharCode),offset:t.accentOffset}}let u="";"number"==typeof a&&(a<=1114111?u=String.fromCodePoint(a):warn(`charToGlyph - invalid fontCharCode: ${a}`));if(this.missingFile&&this.vertical&&1===u.length){const e=Xr()[u.charCodeAt(0)];e&&(u=c=String.fromCharCode(e))}n=new fonts_Glyph(e,u,c,h,r,o,i,t,l);return this._glyphCache[e]=n}charsToGlyphs(e){let t=this._charsCache[e];if(t)return t;t=[];if(this.cMap){const a=Object.create(null),r=e.length;let i=0;for(;it.length%2==1,r=this.toUnicode instanceof IdentityToUnicodeMap?e=>this.toUnicode.charCodeOf(e):e=>this.toUnicode.charCodeOf(String.fromCodePoint(e));for(let i=0,n=e.length;i55295&&(n<57344||n>65533)&&i++;if(this.toUnicode){const e=r(n);if(-1!==e){if(hasCurrentBufErrors()){t.push(a.join(""));a.length=0}for(let t=(this.cMap?this.cMap.getCharCodeLength(e):1)-1;t>=0;t--)a.push(String.fromCharCode(e>>8*t&255));continue}}if(!hasCurrentBufErrors()){t.push(a.join(""));a.length=0}a.push(String.fromCodePoint(n))}t.push(a.join(""));return t}}class ErrorFont{constructor(e){this.error=e;this.loadedName="g_font_error";this.missingFile=!0}charsToGlyphs(){return[]}encodeString(e){return[e]}exportData(){return{error:this.error}}}const ki=2,Ci=3,vi=4,Fi=5,Ii=6,Oi=7;class Pattern{constructor(){unreachable("Cannot initialize Pattern.")}static parseShading(e,t,a,r,i,n){const s=e instanceof BaseStream?e.dict:e,o=s.get("ShadingType");try{switch(o){case ki:case Ci:return new RadialAxialShading(s,t,a,r,i,n);case vi:case Fi:case Ii:case Oi:return new MeshShading(e,t,a,r,i,n);default:throw new FormatError("Unsupported ShadingType: "+o)}}catch(e){if(e instanceof MissingDataException)throw e;warn(e);return new DummyShading}}}class BaseShading{static SMALL_NUMBER=1e-6;getIR(){unreachable("Abstract method `getIR` called.")}}class RadialAxialShading extends BaseShading{constructor(e,t,a,r,i,n){super();this.shadingType=e.get("ShadingType");let s=0;this.shadingType===ki?s=4:this.shadingType===Ci&&(s=6);this.coordsArr=e.getArray("Coords");if(!isNumberArray(this.coordsArr,s))throw new FormatError("RadialAxialShading: Invalid /Coords array.");const o=ColorSpaceUtils.parse({cs:e.getRaw("CS")||e.getRaw("ColorSpace"),xref:t,resources:a,pdfFunctionFactory:r,globalColorSpaceCache:i,localColorSpaceCache:n});this.bbox=lookupNormalRect(e.getArray("BBox"),null);let c=0,l=1;const h=e.getArray("Domain");isNumberArray(h,2)&&([c,l]=h);let u=!1,d=!1;const f=e.getArray("Extend");(function isBooleanArray(e,t){return Array.isArray(e)&&(null===t||e.length===t)&&e.every((e=>"boolean"==typeof e))})(f,2)&&([u,d]=f);if(!(this.shadingType!==Ci||u&&d)){const[e,t,a,r,i,n]=this.coordsArr,s=Math.hypot(e-r,t-i);a<=n+s&&n<=a+s&&warn("Unsupported radial gradient.")}this.extendStart=u;this.extendEnd=d;const g=e.getRaw("Function"),p=r.create(g,!0),m=(l-c)/840,b=this.colorStops=[];if(c>=l||m<=0){info("Bad shading domain.");return}const y=new Float32Array(o.numComps),w=new Float32Array(1);let S,x=0;w[0]=c;p(w,0,y,0);let k=o.getRgb(y,0);const C=Util.makeHexColor(k[0],k[1],k[2]);b.push([0,C]);let v=1;w[0]=c+m;p(w,0,y,0);let F=o.getRgb(y,0),O=F[0]-k[0]+1,T=F[1]-k[1]+1,M=F[2]-k[2]+1,D=F[0]-k[0]-1,R=F[1]-k[1]-1,N=F[2]-k[2]-1;for(let e=2;e<840;e++){w[0]=c+e*m;p(w,0,y,0);S=o.getRgb(y,0);const t=e-x;O=Math.min(O,(S[0]-k[0]+1)/t);T=Math.min(T,(S[1]-k[1]+1)/t);M=Math.min(M,(S[2]-k[2]+1)/t);D=Math.max(D,(S[0]-k[0]-1)/t);R=Math.max(R,(S[1]-k[1]-1)/t);N=Math.max(N,(S[2]-k[2]-1)/t);if(!(D<=O&&R<=T&&N<=M)){const e=Util.makeHexColor(F[0],F[1],F[2]);b.push([v/840,e]);O=S[0]-F[0]+1;T=S[1]-F[1]+1;M=S[2]-F[2]+1;D=S[0]-F[0]-1;R=S[1]-F[1]-1;N=S[2]-F[2]-1;x=v;k=F}v=e;F=S}const E=Util.makeHexColor(F[0],F[1],F[2]);b.push([1,E]);let L="transparent";if(e.has("Background")){S=o.getRgb(e.get("Background"),0);L=Util.makeHexColor(S[0],S[1],S[2])}if(!u){b.unshift([0,L]);b[1][0]+=BaseShading.SMALL_NUMBER}if(!d){b.at(-1)[0]-=BaseShading.SMALL_NUMBER;b.push([1,L])}this.colorStops=b}getIR(){const{coordsArr:e,shadingType:t}=this;let a,r,i,n,s;if(t===ki){r=[e[0],e[1]];i=[e[2],e[3]];n=null;s=null;a="axial"}else if(t===Ci){r=[e[0],e[1]];i=[e[3],e[4]];n=e[2];s=e[5];a="radial"}else unreachable(`getPattern type unknown: ${t}`);return["RadialAxial",a,this.bbox,this.colorStops,r,i,n,s]}}class MeshStreamReader{constructor(e,t){this.stream=e;this.context=t;this.buffer=0;this.bufferLength=0;const a=t.numComps;this.tmpCompsBuf=new Float32Array(a);const r=t.colorSpace.numComps;this.tmpCsCompsBuf=t.colorFn?new Float32Array(r):this.tmpCompsBuf}get hasData(){if(this.stream.end)return this.stream.pos0)return!0;const e=this.stream.getByte();if(e<0)return!1;this.buffer=e;this.bufferLength=8;return!0}readBits(e){const{stream:t}=this;let{buffer:a,bufferLength:r}=this;if(32===e){if(0===r)return t.getInt32()>>>0;a=a<<24|t.getByte()<<16|t.getByte()<<8|t.getByte();const e=t.getByte();this.buffer=e&(1<>r)>>>0}if(8===e&&0===r)return t.getByte();for(;r>r}align(){this.buffer=0;this.bufferLength=0}readFlag(){return this.readBits(this.context.bitsPerFlag)}readCoordinate(){const{bitsPerCoordinate:e,decode:t}=this.context,a=this.readBits(e),r=this.readBits(e),i=e<32?1/((1<n?n:e;t=t>s?s:t;a=ae*i[t])):a;let s,o=-2;const c=[];for(const[e,t]of r.map(((e,t)=>[e,t])).sort((([e],[t])=>e-t)))if(-1!==e)if(e===o+1){s.push(n[t]);o+=1}else{o=e;s=[n[t]];c.push(e,s)}return c}(e),a=new Dict(null);a.set("BaseFont",Name.get(e));a.set("Type",Name.get("Font"));a.set("Subtype",Name.get("CIDFontType2"));a.set("Encoding",Name.get("Identity-H"));a.set("CIDToGIDMap",Name.get("Identity"));a.set("W",t);a.set("FirstChar",t[0]);a.set("LastChar",t.at(-2)+t.at(-1).length-1);const r=new Dict(null);a.set("FontDescriptor",r);const i=new Dict(null);i.set("Ordering","Identity");i.set("Registry","Adobe");i.set("Supplement",0);a.set("CIDSystemInfo",i);return a}class PostScriptParser{constructor(e){this.lexer=e;this.operators=[];this.token=null;this.prev=null}nextToken(){this.prev=this.token;this.token=this.lexer.getToken()}accept(e){if(this.token.type===e){this.nextToken();return!0}return!1}expect(e){if(this.accept(e))return!0;throw new FormatError(`Unexpected symbol: found ${this.token.type} expected ${e}.`)}parse(){this.nextToken();this.expect(Sn.LBRACE);this.parseBlock();this.expect(Sn.RBRACE);return this.operators}parseBlock(){for(;;)if(this.accept(Sn.NUMBER))this.operators.push(this.prev.value);else if(this.accept(Sn.OPERATOR))this.operators.push(this.prev.value);else{if(!this.accept(Sn.LBRACE))return;this.parseCondition()}}parseCondition(){const e=this.operators.length;this.operators.push(null,null);this.parseBlock();this.expect(Sn.RBRACE);if(this.accept(Sn.IF)){this.operators[e]=this.operators.length;this.operators[e+1]="jz"}else{if(!this.accept(Sn.LBRACE))throw new FormatError("PS Function: error parsing conditional.");{const t=this.operators.length;this.operators.push(null,null);const a=this.operators.length;this.parseBlock();this.expect(Sn.RBRACE);this.expect(Sn.IFELSE);this.operators[t]=this.operators.length;this.operators[t+1]="j";this.operators[e]=a;this.operators[e+1]="jz"}}}}const Sn={LBRACE:0,RBRACE:1,NUMBER:2,OPERATOR:3,IF:4,IFELSE:5};class PostScriptToken{static get opCache(){return shadow(this,"opCache",Object.create(null))}constructor(e,t){this.type=e;this.value=t}static getOperator(e){return PostScriptToken.opCache[e]||=new PostScriptToken(Sn.OPERATOR,e)}static get LBRACE(){return shadow(this,"LBRACE",new PostScriptToken(Sn.LBRACE,"{"))}static get RBRACE(){return shadow(this,"RBRACE",new PostScriptToken(Sn.RBRACE,"}"))}static get IF(){return shadow(this,"IF",new PostScriptToken(Sn.IF,"IF"))}static get IFELSE(){return shadow(this,"IFELSE",new PostScriptToken(Sn.IFELSE,"IFELSE"))}}class PostScriptLexer{constructor(e){this.stream=e;this.nextChar();this.strBuf=[]}nextChar(){return this.currentChar=this.stream.getByte()}getToken(){let e=!1,t=this.currentChar;for(;;){if(t<0)return wa;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!isWhiteSpace(t))break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return new PostScriptToken(Sn.NUMBER,this.getNumber());case 123:this.nextChar();return PostScriptToken.LBRACE;case 125:this.nextChar();return PostScriptToken.RBRACE}const a=this.strBuf;a.length=0;a[0]=String.fromCharCode(t);for(;(t=this.nextChar())>=0&&(t>=65&&t<=90||t>=97&&t<=122);)a.push(String.fromCharCode(t));const r=a.join("");switch(r.toLowerCase()){case"if":return PostScriptToken.IF;case"ifelse":return PostScriptToken.IFELSE;default:return PostScriptToken.getOperator(r)}}getNumber(){let e=this.currentChar;const t=this.strBuf;t.length=0;t[0]=String.fromCharCode(e);for(;(e=this.nextChar())>=0&&(e>=48&&e<=57||45===e||46===e);)t.push(String.fromCharCode(e));const a=parseFloat(t.join(""));if(isNaN(a))throw new FormatError(`Invalid floating point number: ${a}`);return a}}class BaseLocalCache{constructor(e){this._onlyRefs=!0===e?.onlyRefs;if(!this._onlyRefs){this._nameRefMap=new Map;this._imageMap=new Map}this._imageCache=new RefSetCache}getByName(e){this._onlyRefs&&unreachable("Should not call `getByName` method.");const t=this._nameRefMap.get(e);return t?this.getByRef(t):this._imageMap.get(e)||null}getByRef(e){return this._imageCache.get(e)||null}set(e,t,a){unreachable("Abstract method `set` called.")}}class LocalImageCache extends BaseLocalCache{set(e,t=null,a){if("string"!=typeof e)throw new Error('LocalImageCache.set - expected "name" argument.');if(t){if(this._imageCache.has(t))return;this._nameRefMap.set(e,t);this._imageCache.put(t,a)}else this._imageMap.has(e)||this._imageMap.set(e,a)}}class LocalColorSpaceCache extends BaseLocalCache{set(e=null,t=null,a){if("string"!=typeof e&&!t)throw new Error('LocalColorSpaceCache.set - expected "name" and/or "ref" argument.');if(t){if(this._imageCache.has(t))return;null!==e&&this._nameRefMap.set(e,t);this._imageCache.put(t,a)}else this._imageMap.has(e)||this._imageMap.set(e,a)}}class LocalFunctionCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('LocalFunctionCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}}class LocalGStateCache extends BaseLocalCache{set(e,t=null,a){if("string"!=typeof e)throw new Error('LocalGStateCache.set - expected "name" argument.');if(t){if(this._imageCache.has(t))return;this._nameRefMap.set(e,t);this._imageCache.put(t,a)}else this._imageMap.has(e)||this._imageMap.set(e,a)}}class LocalTilingPatternCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('LocalTilingPatternCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}}class RegionalImageCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('RegionalImageCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}}class GlobalColorSpaceCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('GlobalColorSpaceCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}clear(){this._imageCache.clear()}}class GlobalImageCache{static NUM_PAGES_THRESHOLD=2;static MIN_IMAGES_TO_CACHE=10;static MAX_BYTE_SIZE=5e7;#X=new RefSet;constructor(){this._refCache=new RefSetCache;this._imageCache=new RefSetCache}get#q(){let e=0;for(const t of this._imageCache)e+=t.byteSize;return e}get#H(){return!(this._imageCache.size+e)):null}class PDFFunction{static getSampleArray(e,t,a,r){let i,n,s=1;for(i=0,n=e.length;i>c)*h;l&=(1<0&&(d=n[u-1]);let f=a[1];u>1,c=r.length>>1,l=new PostScriptEvaluator(s),h=Object.create(null);let u=8192;const d=new Float32Array(c);return function constructPostScriptFn(e,t,a,r){let n,s,f="";const g=d;for(n=0;ne&&(s=e)}m[n]=s}if(u>0){u--;h[f]=m}a.set(m,r)}}}function isPDFFunction(e){let t;if(e instanceof Dict)t=e;else{if(!(e instanceof BaseStream))return!1;t=e.dict}return t.has("FunctionType")}class PostScriptStack{static MAX_STACK_SIZE=100;constructor(e){this.stack=e?Array.from(e):[]}push(e){if(this.stack.length>=PostScriptStack.MAX_STACK_SIZE)throw new Error("PostScript function stack overflow.");this.stack.push(e)}pop(){if(this.stack.length<=0)throw new Error("PostScript function stack underflow.");return this.stack.pop()}copy(e){if(this.stack.length+e>=PostScriptStack.MAX_STACK_SIZE)throw new Error("PostScript function stack overflow.");const t=this.stack;for(let a=t.length-e,r=e-1;r>=0;r--,a++)t.push(t[a])}index(e){this.push(this.stack[this.stack.length-e-1])}roll(e,t){const a=this.stack,r=a.length-e,i=a.length-1,n=r+(t-Math.floor(t/e)*e);for(let e=r,t=i;e0?t.push(s<>o);break;case"ceiling":s=t.pop();t.push(Math.ceil(s));break;case"copy":s=t.pop();t.copy(s);break;case"cos":s=t.pop();t.push(Math.cos(s%360/180*Math.PI));break;case"cvi":s=0|t.pop();t.push(s);break;case"cvr":break;case"div":o=t.pop();s=t.pop();t.push(s/o);break;case"dup":t.copy(1);break;case"eq":o=t.pop();s=t.pop();t.push(s===o);break;case"exch":t.roll(2,1);break;case"exp":o=t.pop();s=t.pop();t.push(s**o);break;case"false":t.push(!1);break;case"floor":s=t.pop();t.push(Math.floor(s));break;case"ge":o=t.pop();s=t.pop();t.push(s>=o);break;case"gt":o=t.pop();s=t.pop();t.push(s>o);break;case"idiv":o=t.pop();s=t.pop();t.push(s/o|0);break;case"index":s=t.pop();t.index(s);break;case"le":o=t.pop();s=t.pop();t.push(s<=o);break;case"ln":s=t.pop();t.push(Math.log(s));break;case"log":s=t.pop();t.push(Math.log10(s));break;case"lt":o=t.pop();s=t.pop();t.push(s=t?new AstLiteral(t):e.max<=t?e:new AstMin(e,t)}class PostScriptCompiler{compile(e,t,a){const r=[],i=[],n=t.length>>1,s=a.length>>1;let o,c,l,h,u,d,f,g,p=0;for(let e=0;et.min){o.unshift("Math.max(",n,", ");o.push(")")}if(s4){r=!0;t=0}else{r=!1;t=1}const c=[];for(n=0;n=0&&"ET"===Cn[e];--e)Cn[e]="EN";for(let e=n+1;e0&&(t=Cn[n-1]);let a=u;e+1g&&isOdd(g)&&(m=g)}for(g=p;g>=m;--g){let e=-1;for(n=0,s=c.length;n=0){reverseValues(kn,e,n);e=-1}}else e<0&&(e=n);e>=0&&reverseValues(kn,e,c.length)}for(n=0,s=kn.length;n"!==e||(kn[n]="")}return createBidiText(kn.join(""),r)}const vn={style:"normal",weight:"normal"},Fn={style:"normal",weight:"bold"},In={style:"italic",weight:"normal"},On={style:"italic",weight:"bold"},Tn=new Map([["Times-Roman",{local:["Times New Roman","Times-Roman","Times","Liberation Serif","Nimbus Roman","Nimbus Roman L","Tinos","Thorndale","TeX Gyre Termes","FreeSerif","Linux Libertine O","Libertinus Serif","DejaVu Serif","Bitstream Vera Serif","Ubuntu"],style:vn,ultimate:"serif"}],["Times-Bold",{alias:"Times-Roman",style:Fn,ultimate:"serif"}],["Times-Italic",{alias:"Times-Roman",style:In,ultimate:"serif"}],["Times-BoldItalic",{alias:"Times-Roman",style:On,ultimate:"serif"}],["Helvetica",{local:["Helvetica","Helvetica Neue","Arial","Arial Nova","Liberation Sans","Arimo","Nimbus Sans","Nimbus Sans L","A030","TeX Gyre Heros","FreeSans","DejaVu Sans","Albany","Bitstream Vera Sans","Arial Unicode MS","Microsoft Sans Serif","Apple Symbols","Cantarell"],path:"LiberationSans-Regular.ttf",style:vn,ultimate:"sans-serif"}],["Helvetica-Bold",{alias:"Helvetica",path:"LiberationSans-Bold.ttf",style:Fn,ultimate:"sans-serif"}],["Helvetica-Oblique",{alias:"Helvetica",path:"LiberationSans-Italic.ttf",style:In,ultimate:"sans-serif"}],["Helvetica-BoldOblique",{alias:"Helvetica",path:"LiberationSans-BoldItalic.ttf",style:On,ultimate:"sans-serif"}],["Courier",{local:["Courier","Courier New","Liberation Mono","Nimbus Mono","Nimbus Mono L","Cousine","Cumberland","TeX Gyre Cursor","FreeMono","Linux Libertine Mono O","Libertinus Mono"],style:vn,ultimate:"monospace"}],["Courier-Bold",{alias:"Courier",style:Fn,ultimate:"monospace"}],["Courier-Oblique",{alias:"Courier",style:In,ultimate:"monospace"}],["Courier-BoldOblique",{alias:"Courier",style:On,ultimate:"monospace"}],["ArialBlack",{local:["Arial Black"],style:{style:"normal",weight:"900"},fallback:"Helvetica-Bold"}],["ArialBlack-Bold",{alias:"ArialBlack"}],["ArialBlack-Italic",{alias:"ArialBlack",style:{style:"italic",weight:"900"},fallback:"Helvetica-BoldOblique"}],["ArialBlack-BoldItalic",{alias:"ArialBlack-Italic"}],["ArialNarrow",{local:["Arial Narrow","Liberation Sans Narrow","Helvetica Condensed","Nimbus Sans Narrow","TeX Gyre Heros Cn"],style:vn,fallback:"Helvetica"}],["ArialNarrow-Bold",{alias:"ArialNarrow",style:Fn,fallback:"Helvetica-Bold"}],["ArialNarrow-Italic",{alias:"ArialNarrow",style:In,fallback:"Helvetica-Oblique"}],["ArialNarrow-BoldItalic",{alias:"ArialNarrow",style:On,fallback:"Helvetica-BoldOblique"}],["Calibri",{local:["Calibri","Carlito"],style:vn,fallback:"Helvetica"}],["Calibri-Bold",{alias:"Calibri",style:Fn,fallback:"Helvetica-Bold"}],["Calibri-Italic",{alias:"Calibri",style:In,fallback:"Helvetica-Oblique"}],["Calibri-BoldItalic",{alias:"Calibri",style:On,fallback:"Helvetica-BoldOblique"}],["Wingdings",{local:["Wingdings","URW Dingbats"],style:vn}],["Wingdings-Regular",{alias:"Wingdings"}],["Wingdings-Bold",{alias:"Wingdings"}]]),Mn=new Map([["Arial-Black","ArialBlack"]]);function getFamilyName(e){const t=new Set(["thin","extralight","ultralight","demilight","semilight","light","book","regular","normal","medium","demibold","semibold","bold","extrabold","ultrabold","black","heavy","extrablack","ultrablack","roman","italic","oblique","ultracondensed","extracondensed","condensed","semicondensed","normal","semiexpanded","expanded","extraexpanded","ultraexpanded","bolditalic"]);return e.split(/[- ,+]+/g).filter((e=>!t.has(e.toLowerCase()))).join(" ")}function generateFont({alias:e,local:t,path:a,fallback:r,style:i,ultimate:n},s,o,c=!0,l=!0,h=""){const u={style:null,ultimate:null};if(t){const e=h?` ${h}`:"";for(const a of t)s.push(`local(${a}${e})`)}if(e){const t=Tn.get(e),n=h||function getStyleToAppend(e){switch(e){case Fn:return"Bold";case In:return"Italic";case On:return"Bold Italic";default:if("bold"===e?.weight)return"Bold";if("italic"===e?.style)return"Italic"}return""}(i);Object.assign(u,generateFont(t,s,o,c&&!r,l&&!a,n))}i&&(u.style=i);n&&(u.ultimate=n);if(c&&r){const e=Tn.get(r),{ultimate:t}=generateFont(e,s,o,c,l&&!a,h);u.ultimate||=t}l&&a&&o&&s.push(`url(${o}${a})`);return u}function getFontSubstitution(e,t,a,r,i,n){if(r.startsWith("InvalidPDFjsFont_"))return null;"TrueType"!==n&&"Type1"!==n||!/^[A-Z]{6}\+/.test(r)||(r=r.slice(7));const s=r=normalizeFontName(r);let o=e.get(s);if(o)return o;let c=Tn.get(r);if(!c)for(const[e,t]of Mn)if(r.startsWith(e)){r=`${t}${r.substring(e.length)}`;c=Tn.get(r);break}let l=!1;if(!c){c=Tn.get(i);l=!0}const h=`${t.getDocId()}_s${t.createFontId()}`;if(!c){if(!validateFontName(r)){warn(`Cannot substitute the font because of its name: ${r}`);e.set(s,null);return null}const t=/bold/gi.test(r),a=/oblique|italic/gi.test(r),i=t&&a&&On||t&&Fn||a&&In||vn;o={css:`"${getFamilyName(r)}",${h}`,guessFallback:!0,loadedName:h,baseFontName:r,src:`local(${r})`,style:i};e.set(s,o);return o}const u=[];l&&validateFontName(r)&&u.push(`local(${r})`);const{style:d,ultimate:f}=generateFont(c,u,a),g=null===f,p=g?"":`,${f}`;o={css:`"${getFamilyName(r)}",${h}${p}`,guessFallback:g,loadedName:h,baseFontName:r,src:u.join(","),style:d};e.set(s,o);return o}const Dn=3285377520,Bn=4294901760,Rn=65535;class MurmurHash3_64{constructor(e){this.h1=e?4294967295&e:Dn;this.h2=e?4294967295&e:Dn}update(e){let t,a;if("string"==typeof e){t=new Uint8Array(2*e.length);a=0;for(let r=0,i=e.length;r>>8;t[a++]=255&i}}}else{if(!ArrayBuffer.isView(e))throw new Error("Invalid data format, must be a string or TypedArray.");t=e.slice();a=t.byteLength}const r=a>>2,i=a-4*r,n=new Uint32Array(t.buffer,0,r);let s=0,o=0,c=this.h1,l=this.h2;const h=3432918353,u=461845907,d=11601,f=13715;for(let e=0;e>>17;s=s*u&Bn|s*f&Rn;c^=s;c=c<<13|c>>>19;c=5*c+3864292196}else{o=n[e];o=o*h&Bn|o*d&Rn;o=o<<15|o>>>17;o=o*u&Bn|o*f&Rn;l^=o;l=l<<13|l>>>19;l=5*l+3864292196}s=0;switch(i){case 3:s^=t[4*r+2]<<16;case 2:s^=t[4*r+1]<<8;case 1:s^=t[4*r];s=s*h&Bn|s*d&Rn;s=s<<15|s>>>17;s=s*u&Bn|s*f&Rn;1&r?c^=s:l^=s}this.h1=c;this.h2=l}hexdigest(){let e=this.h1,t=this.h2;e^=t>>>1;e=3981806797*e&Bn|36045*e&Rn;t=4283543511*t&Bn|(2950163797*(t<<16|e>>>16)&Bn)>>>16;e^=t>>>1;e=444984403*e&Bn|60499*e&Rn;t=3301882366*t&Bn|(3120437893*(t<<16|e>>>16)&Bn)>>>16;e^=t>>>1;return(e>>>0).toString(16).padStart(8,"0")+(t>>>0).toString(16).padStart(8,"0")}}function resizeImageMask(e,t,a,r,i,n){const s=i*n;let o;o=t<=8?new Uint8Array(s):t<=16?new Uint16Array(s):new Uint32Array(s);const c=a/i,l=r/n;let h,u,d,f,g=0;const p=new Uint16Array(i),m=a;for(h=0;h0&&Number.isInteger(a.height)&&a.height>0&&(a.width!==f||a.height!==g)){warn("PDFImage - using the Width/Height of the image data, rather than the image dictionary.");f=a.width;g=a.height}else{const e="number"==typeof f&&f>0,t="number"==typeof g&&g>0;if(!e||!t){if(!a.fallbackDims)throw new FormatError(`Invalid image width: ${f} or height: ${g}`);warn("PDFImage - using the Width/Height of the parent image, for SMask/Mask data.");e||(f=a.fallbackDims.width);t||(g=a.fallbackDims.height)}}this.width=f;this.height=g;this.interpolate=h.get("I","Interpolate");this.imageMask=h.get("IM","ImageMask")||!1;this.matte=h.get("Matte")||!1;let p=a.bitsPerComponent;if(!p){p=h.get("BPC","BitsPerComponent");if(!p){if(!this.imageMask)throw new FormatError(`Bits per component missing in image: ${this.imageMask}`);p=1}}this.bpc=p;if(!this.imageMask){let i=h.getRaw("CS")||h.getRaw("ColorSpace");const n=!!i;if(n)this.jpxDecoderOptions?.smaskInData&&(i=Name.get("DeviceRGBA"));else if(this.jpxDecoderOptions)i=Name.get("DeviceRGBA");else switch(a.numComps){case 1:i=Name.get("DeviceGray");break;case 3:i=Name.get("DeviceRGB");break;case 4:i=Name.get("DeviceCMYK");break;default:throw new Error(`Images with ${a.numComps} color components not supported.`)}this.colorSpace=ColorSpaceUtils.parse({cs:i,xref:e,resources:r?t:null,pdfFunctionFactory:o,globalColorSpaceCache:c,localColorSpaceCache:l});this.numComps=this.colorSpace.numComps;if(this.jpxDecoderOptions){this.jpxDecoderOptions.numComponents=n?this.numComps:0;this.jpxDecoderOptions.isIndexedColormap="Indexed"===this.colorSpace.name}}this.decode=h.getArray("D","Decode");this.needsDecode=!1;if(this.decode&&(this.colorSpace&&!this.colorSpace.isDefaultDecode(this.decode,p)||s&&!ColorSpace.isDefaultDecode(this.decode,1))){this.needsDecode=!0;const e=(1<0,c=(r+7>>3)*i,l=e.getBytes(c),h=1===r&&1===i&&o===(0===l.length||!!(128&l[0]));if(h)return{isSingleOpaquePixel:h};if(t){if(ImageResizer.needsToBeResized(r,i)){const e=new Uint8ClampedArray(r*i*4);convertBlackAndWhiteToRGBA({src:l,dest:e,width:r,height:i,nonBlackColor:0,inverseDecode:o});return ImageResizer.createImage({kind:v,data:e,width:r,height:i,interpolate:n})}const e=new OffscreenCanvas(r,i),t=e.getContext("2d"),a=t.createImageData(r,i);convertBlackAndWhiteToRGBA({src:l,dest:a.data,width:r,height:i,nonBlackColor:0,inverseDecode:o});t.putImageData(a,0,0);return{data:null,width:r,height:i,interpolate:n,bitmap:e.transferToImageBitmap()}}const u=l.byteLength;let d;if(e instanceof DecodeStream&&(!o||c===u))d=l;else if(o){d=new Uint8Array(c);d.set(l);d.fill(255,u)}else d=new Uint8Array(l);if(o)for(let e=0;e>7&1;s[d+1]=u>>6&1;s[d+2]=u>>5&1;s[d+3]=u>>4&1;s[d+4]=u>>3&1;s[d+5]=u>>2&1;s[d+6]=u>>1&1;s[d+7]=1&u;d+=8}if(d>=1}}}}else{let a=0;u=0;for(d=0,h=n;d>r;i<0?i=0:i>l&&(i=l);s[d]=i;u&=(1<s[r+1]){t=255;break}}o[h]=t}}}if(o)for(h=0,d=3,u=t*r;h>3,h=t&&ImageResizer.needsToBeResized(a,r);if(!this.smask&&!this.mask&&"DeviceRGBA"===this.colorSpace.name){i.kind=v;const e=i.data=await this.getImageBytes(o*s*4,{});return t?h?ImageResizer.createImage(i,!1):this.createBitmap(v,a,r,e):i}if(!e){let e;"DeviceGray"===this.colorSpace.name&&1===c?e=k:"DeviceRGB"!==this.colorSpace.name||8!==c||this.needsDecode||(e=C);if(e&&!this.smask&&!this.mask&&a===s&&r===o){const n=await this.#W(s,o);if(n)return n;const c=await this.getImageBytes(o*l,{});if(t)return h?ImageResizer.createImage({data:c,kind:e,width:a,height:r,interpolate:this.interpolate},this.needsDecode):this.createBitmap(e,s,o,c);i.kind=e;i.data=c;if(this.needsDecode){assert(e===k,"PDFImage.createImageData: The image must be grayscale.");const t=i.data;for(let e=0,a=t.length;e>3,s=await this.getImageBytes(r*n,{internal:!0}),o=this.getComponents(s);let c,l;if(1===i){l=a*r;if(this.needsDecode)for(c=0;c0&&r[0].count++}class TimeSlotManager{static TIME_SLOT_DURATION_MS=20;static CHECK_TIME_EVERY=100;constructor(){this.reset()}check(){if(++this.checkedo){const e="Image exceeded maximum allowed size and was removed.";if(!c)throw new Error(e);warn(e);return}let g;h.has("OC")&&(g=await this.parseMarkedContentProps(h.get("OC"),e));let p,m,b;if(h.get("IM","ImageMask")||!1){p=await PDFImage.createMask({image:t,isOffscreenCanvasSupported:l&&!this.parsingType3Font});if(p.isSingleOpaquePixel){m=ta;b=[];r.addImageOps(m,b,g);if(i){const e={fn:m,args:b,optionalContent:g};n.set(i,u,e);u&&this._regionalImageCache.set(null,u,e)}return}if(this.parsingType3Font){b=function compileType3Glyph({data:e,width:t,height:a}){if(t>1e3||a>1e3)return null;const r=new Uint8Array([0,2,4,0,1,0,5,4,8,10,0,8,0,2,1,0]),i=t+1,n=new Uint8Array(i*(a+1));let s,o,c;const l=t+7&-8,h=new Uint8Array(l*a);let u=0;for(const t of e){let e=128;for(;e>0;){h[u++]=t&e?0:255;e>>=1}}let d=0;u=0;if(0!==h[u]){n[0]=1;++d}for(o=1;o>2)+(h[u+1]?4:0)+(h[u-l+1]?8:0);if(r[e]){n[c+o]=r[e];++d}u++}if(h[u-l]!==h[u]){n[c+o]=h[u]?2:4;++d}if(d>1e3)return null}u=l*(a-1);c=s*i;if(0!==h[u]){n[c]=8;++d}for(o=1;o1e3)return null;const f=new Int32Array([0,i,-1,0,-i,0,0,0,1]),g=[],{a:p,b:m,c:b,d:y,e:w,f:S}=(new DOMMatrix).scaleSelf(1/t,-1/a).translateSelf(0,-a);for(s=0;d&&s<=a;s++){let e=s*i;const a=e+t;for(;e>4;n[e]&=l>>2|l<<2}r=e%i;o=e/i|0;g.push(oa,p*r+b*o+w,m*r+y*o+S);n[e]||--d}while(c!==e);--s}return[na,[new Float32Array(g)],new Float32Array([0,0,t,a])]}(p);if(b){r.addImageOps(aa,b,g);return}warn("Cannot compile Type3 glyph.");r.addImageOps(Vt,[p],g);return}const e=`mask_${this.idFactory.createObjId()}`;r.addDependency(e);p.dataLen=p.bitmap?p.width*p.height*4:p.data.length;this._sendImgData(e,p);m=Vt;b=[{data:e,width:p.width,height:p.height,interpolate:p.interpolate,count:1}];r.addImageOps(m,b,g);if(i){const t={objId:e,fn:m,args:b,optionalContent:g};n.set(i,u,t);u&&this._regionalImageCache.set(null,u,t)}return}const y=h.has("SMask")||h.has("Mask");if(a&&d+f<200&&!y){try{const i=new PDFImage({xref:this.xref,res:e,image:t,isInline:a,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:s});p=await i.createImageData(!0,!1);r.addImageOps(Yt,[p],g)}catch(e){const t=`Unable to decode inline image: "${e}".`;if(!c)throw new Error(t);warn(t)}return}let w=`img_${this.idFactory.createObjId()}`,S=!1,x=null;if(this.parsingType3Font)w=`${this.idFactory.getDocId()}_type3_${w}`;else if(i&&u){S=this.globalImageCache.shouldCache(u,this.pageIndex);if(S){assert(!a,"Cannot cache an inline image globally.");w=`${this.idFactory.getDocId()}_${w}`}}r.addDependency(w);m=Jt;b=[w,d,f];r.addImageOps(m,b,g,y);if(S){x={objId:w,fn:m,args:b,optionalContent:g,hasMask:y,byteSize:0};if(this.globalImageCache.hasDecodeFailed(u)){this.globalImageCache.setData(u,x);this._sendImgData(w,null,S);return}if(d*f>25e4||y){const e=await this.handler.sendWithPromise("commonobj",[w,"CopyLocalImage",{imageRef:u}]);if(e){this.globalImageCache.setData(u,x);this.globalImageCache.addByteSize(u,e);return}}}PDFImage.buildImage({xref:this.xref,res:e,image:t,isInline:a,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:s}).then((async e=>{p=await e.createImageData(!1,l);p.dataLen=p.bitmap?p.width*p.height*4:p.data.length;p.ref=u;S&&this.globalImageCache.addByteSize(u,p.dataLen);return this._sendImgData(w,p,S)})).catch((e=>{warn(`Unable to decode image "${w}": "${e}".`);u&&this.globalImageCache.addDecodeFailed(u);return this._sendImgData(w,null,S)}));if(i){const e={objId:w,fn:m,args:b,optionalContent:g,hasMask:y};n.set(i,u,e);if(u){this._regionalImageCache.set(null,u,e);if(S){assert(x,"The global cache-data must be available.");this.globalImageCache.setData(u,x)}}}}handleSMask(e,t,a,r,i,n,s){const o=e.get("G"),c={subtype:e.get("S").name,backdrop:e.get("BC")},l=e.get("TR");if(isPDFFunction(l)){const e=this._pdfFunctionFactory.create(l),t=new Uint8Array(256),a=new Float32Array(1);for(let r=0;r<256;r++){a[0]=r/255;e(a,0,a,0);t[r]=255*a[0]|0}c.transferMap=t}return this.buildFormXObject(t,o,c,a,r,i.state.clone({newPath:!0}),n,s)}handleTransferFunction(e){let t;if(Array.isArray(e))t=e;else{if(!isPDFFunction(e))return null;t=[e]}const a=[];let r=0,i=0;for(const e of t){const t=this.xref.fetchIfRef(e);r++;if(isName(t,"Identity")){a.push(null);continue}if(!isPDFFunction(t))return null;const n=this._pdfFunctionFactory.create(t),s=new Uint8Array(256),o=new Float32Array(1);for(let e=0;e<256;e++){o[0]=e/255;n(o,0,o,0);s[e]=255*o[0]|0}a.push(s);i++}return 1!==r&&4!==r||0===i?null:a}handleTilingType(e,t,a,r,i,n,s,o){const c=new OperatorList,l=Dict.merge({xref:this.xref,dictArray:[i.get("Resources"),a]});return this.getOperatorList({stream:r,task:s,resources:l,operatorList:c}).then((function(){const a=c.getIR(),r=getTilingPatternIR(a,i,t);n.addDependencies(c.dependencies);n.addOp(e,r);i.objId&&o.set(null,i.objId,{operatorListIR:a,dict:i})})).catch((e=>{if(!(e instanceof AbortException)){if(!this.options.ignoreErrors)throw e;warn(`handleTilingType - ignoring pattern: "${e}".`)}}))}async handleSetFont(e,t,a,r,i,n,s=null,o=null){const c=t?.[0]instanceof Name?t[0].name:null,l=await this.loadFont(c,a,e,i,s,o);l.font.isType3Font&&r.addDependencies(l.type3Dependencies);n.font=l.font;l.send(this.handler);return l.loadedName}handleText(e,t){const a=t.font,r=a.charsToGlyphs(e);if(a.data){(!!(t.textRenderingMode&x)||"Pattern"===t.fillColorSpace.name||a.disableFontFace)&&PartialEvaluator.buildFontPaths(a,r,this.handler,this.options)}return r}ensureStateFont(e){if(e.font)return;const t=new FormatError("Missing setFont (Tf) operator before text rendering operator.");if(!this.options.ignoreErrors)throw t;warn(`ensureStateFont: "${t}".`)}async setGState({resources:e,gState:t,operatorList:a,cacheKey:r,task:i,stateManager:n,localGStateCache:s,localColorSpaceCache:o,seenRefs:c}){const l=t.objId;let h=!0;const u=[];let d=Promise.resolve();for(const[r,s]of t)switch(r){case"Type":break;case"LW":if("number"!=typeof s){warn(`Invalid LW (line width): ${s}`);break}u.push([r,Math.abs(s)]);break;case"LC":case"LJ":case"ML":case"D":case"RI":case"FL":case"CA":case"ca":u.push([r,s]);break;case"Font":h=!1;d=d.then((()=>this.handleSetFont(e,null,s[0],a,i,n.state).then((function(e){a.addDependency(e);u.push([r,[e,s[1]]])}))));break;case"BM":u.push([r,normalizeBlendMode(s)]);break;case"SMask":if(isName(s,"None")){u.push([r,!1]);break}if(s instanceof Dict){h=!1;d=d.then((()=>this.handleSMask(s,e,a,i,n,o,c)));u.push([r,!0])}else warn("Unsupported SMask type");break;case"TR":const t=this.handleTransferFunction(s);u.push([r,t]);break;case"OP":case"op":case"OPM":case"BG":case"BG2":case"UCR":case"UCR2":case"TR2":case"HT":case"SM":case"SA":case"AIS":case"TK":info("graphic state operator "+r);break;default:info("Unknown graphic state operator "+r)}await d;u.length>0&&a.addOp(De,[u]);h&&s.set(r,l,u)}loadFont(e,t,a,r,i=null,n=null){const errorFont=async()=>new TranslatedFont({loadedName:"g_font_error",font:new ErrorFont(`Font "${e}" is not available.`),dict:t});let s;if(t)t instanceof Ref&&(s=t);else{const t=a.get("Font");t&&(s=t.getRaw(e))}if(s){if(this.type3FontRefs?.has(s))return errorFont();if(this.fontCache.has(s))return this.fontCache.get(s);try{t=this.xref.fetchIfRef(s)}catch(e){warn(`loadFont - lookup failed: "${e}".`)}}if(!(t instanceof Dict)){if(!this.options.ignoreErrors&&!this.parsingType3Font){warn(`Font "${e}" is not available.`);return errorFont()}warn(`Font "${e}" is not available -- attempting to fallback to a default font.`);t=i||PartialEvaluator.fallbackFontDict}if(t.cacheKey&&this.fontCache.has(t.cacheKey))return this.fontCache.get(t.cacheKey);const{promise:o,resolve:c}=Promise.withResolvers();let l;try{l=this.preEvaluateFont(t);l.cssFontInfo=n}catch(e){warn(`loadFont - preEvaluateFont failed: "${e}".`);return errorFont()}const{descriptor:h,hash:u}=l,d=s instanceof Ref;let f;if(u&&h instanceof Dict){const e=h.fontAliases||=Object.create(null);if(e[u]){const t=e[u].aliasRef;if(d&&t&&this.fontCache.has(t)){this.fontCache.putAlias(s,t);return this.fontCache.get(s)}}else e[u]={fontID:this.idFactory.createFontId()};d&&(e[u].aliasRef=s);f=e[u].fontID}else f=this.idFactory.createFontId();assert(f?.startsWith("f"),'The "fontID" must be (correctly) defined.');if(d)this.fontCache.put(s,o);else{t.cacheKey=`cacheKey_${f}`;this.fontCache.put(t.cacheKey,o)}t.loadedName=`${this.idFactory.getDocId()}_${f}`;this.translateFont(l).then((async e=>{const i=new TranslatedFont({loadedName:t.loadedName,font:e,dict:t});if(e.isType3Font)try{await i.loadType3Data(this,a,r)}catch(e){throw new Error(`Type3 font load error: ${e}`)}c(i)})).catch((e=>{warn(`loadFont - translateFont failed: "${e}".`);c(new TranslatedFont({loadedName:t.loadedName,font:new ErrorFont(e?.message),dict:t}))}));return o}buildPath(e,t,a){const{pathMinMax:r,pathBuffer:i}=a;switch(0|e){case Xe:{const e=a.currentPointX=t[0],n=a.currentPointY=t[1],s=t[2],o=t[3],c=e+s,l=n+o;0===s||0===o?i.push(sa,e,n,oa,c,l,la):i.push(sa,e,n,oa,c,n,oa,c,l,oa,e,l,la);Util.rectBoundingBox(e,n,c,l,r);break}case Ee:{const e=a.currentPointX=t[0],n=a.currentPointY=t[1];i.push(sa,e,n);Util.pointBoundingBox(e,n,r);break}case Pe:{const e=a.currentPointX=t[0],n=a.currentPointY=t[1];i.push(oa,e,n);Util.pointBoundingBox(e,n,r);break}case Le:{const e=a.currentPointX,n=a.currentPointY,[s,o,c,l,h,u]=t;a.currentPointX=h;a.currentPointY=u;i.push(ca,s,o,c,l,h,u);Util.bezierBoundingBox(e,n,s,o,c,l,h,u,r);break}case _e:{const e=a.currentPointX,n=a.currentPointY,[s,o,c,l]=t;a.currentPointX=c;a.currentPointY=l;i.push(ca,e,n,s,o,c,l);Util.bezierBoundingBox(e,n,e,n,s,o,c,l,r);break}case je:{const e=a.currentPointX,n=a.currentPointY,[s,o,c,l]=t;a.currentPointX=c;a.currentPointY=l;i.push(ca,s,o,c,l,c,l);Util.bezierBoundingBox(e,n,s,o,c,l,c,l,r);break}case Ue:i.push(la)}}_getColorSpace(e,t,a){return ColorSpaceUtils.parse({cs:e,xref:this.xref,resources:t,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:a,asyncIfNotCached:!0})}async _handleColorSpace(e){try{return await e}catch(e){if(e instanceof AbortException)return null;if(this.options.ignoreErrors){warn(`_handleColorSpace - ignoring ColorSpace: "${e}".`);return null}throw e}}parseShading({shading:e,resources:t,localColorSpaceCache:a,localShadingPatternCache:r}){let i,n=r.get(e);if(n)return n;try{i=Pattern.parseShading(e,this.xref,t,this._pdfFunctionFactory,this.globalColorSpaceCache,a).getIR()}catch(t){if(t instanceof AbortException)return null;if(this.options.ignoreErrors){warn(`parseShading - ignoring shading: "${t}".`);r.set(e,null);return null}throw t}n=`pattern_${this.idFactory.createObjId()}`;this.parsingType3Font&&(n=`${this.idFactory.getDocId()}_type3_${n}`);r.set(e,n);this.parsingType3Font?this.handler.send("commonobj",[n,"Pattern",i]):this.handler.send("obj",[n,this.pageIndex,"Pattern",i]);return n}handleColorN(e,t,a,r,i,n,s,o,c,l){const h=a.pop();if(h instanceof Name){const u=i.getRaw(h.name),d=u instanceof Ref&&c.getByRef(u);if(d)try{const i=r.base?r.base.getRgb(a,0):null,n=getTilingPatternIR(d.operatorListIR,d.dict,i);e.addOp(t,n);return}catch{}const f=this.xref.fetchIfRef(u);if(f){const i=f instanceof BaseStream?f.dict:f,h=i.get("PatternType");if(h===En){const o=r.base?r.base.getRgb(a,0):null;return this.handleTilingType(t,o,n,f,i,e,s,c)}if(h===Pn){const a=i.get("Shading"),r=this.parseShading({shading:a,resources:n,localColorSpaceCache:o,localShadingPatternCache:l});if(r){const a=lookupMatrix(i.getArray("Matrix"),null);e.addOp(t,["Shading",r,a])}return}throw new FormatError(`Unknown PatternType: ${h}`)}}throw new FormatError(`Unknown PatternName: ${h}`)}_parseVisibilityExpression(e,t,a){if(++t>10){warn("Visibility expression is too deeply nested");return}const r=e.length,i=this.xref.fetchIfRef(e[0]);if(!(r<2)&&i instanceof Name){switch(i.name){case"And":case"Or":case"Not":a.push(i.name);break;default:warn(`Invalid operator ${i.name} in visibility expression`);return}for(let i=1;i0)return{type:"OCMD",expression:t}}const t=a.get("OCGs");if(Array.isArray(t)||t instanceof Dict){const e=[];if(Array.isArray(t))for(const a of t)e.push(a.toString());else e.push(t.objId);return{type:r,ids:e,policy:a.get("P")instanceof Name?a.get("P").name:null,expression:null}}if(t instanceof Ref)return{type:r,id:t.toString()}}return null}getOperatorList({stream:e,task:t,resources:a,operatorList:r,initialState:i=null,fallbackFontDict:n=null,prevRefs:s=null}){const o=e.dict?.objId,c=new RefSet(s);if(o){if(s?.has(o))throw new Error(`getOperatorList - ignoring circular reference: ${o}`);c.put(o)}a||=Dict.empty;i||=new EvalState;if(!r)throw new Error('getOperatorList: missing "operatorList" parameter');const l=this,h=this.xref,u=new LocalImageCache,d=new LocalColorSpaceCache,f=new LocalGStateCache,g=new LocalTilingPatternCache,p=new Map,m=a.get("XObject")||Dict.empty,b=a.get("Pattern")||Dict.empty,y=new StateManager(i),w=new EvaluatorPreprocessor(e,h,y),S=new TimeSlotManager;function closePendingRestoreOPS(e){for(let e=0,t=w.savedStatesDepth;e{y.state.fillColorSpace=e||ColorSpaceUtils.gray})));return}case yt:{const t=l._getColorSpace(e[0],a,d);if(t instanceof ColorSpace){y.state.strokeColorSpace=t;continue}next(l._handleColorSpace(t).then((e=>{y.state.strokeColorSpace=e||ColorSpaceUtils.gray})));return}case At:C=y.state.fillColorSpace;e=C.getRgb(e,0);i=It;break;case St:C=y.state.strokeColorSpace;e=C.getRgb(e,0);i=Ft;break;case vt:y.state.fillColorSpace=ColorSpaceUtils.gray;e=ColorSpaceUtils.gray.getRgb(e,0);i=It;break;case Ct:y.state.strokeColorSpace=ColorSpaceUtils.gray;e=ColorSpaceUtils.gray.getRgb(e,0);i=Ft;break;case Tt:y.state.fillColorSpace=ColorSpaceUtils.cmyk;e=ColorSpaceUtils.cmyk.getRgb(e,0);i=It;break;case Ot:y.state.strokeColorSpace=ColorSpaceUtils.cmyk;e=ColorSpaceUtils.cmyk.getRgb(e,0);i=Ft;break;case It:y.state.fillColorSpace=ColorSpaceUtils.rgb;e=ColorSpaceUtils.rgb.getRgb(e,0);break;case Ft:y.state.strokeColorSpace=ColorSpaceUtils.rgb;e=ColorSpaceUtils.rgb.getRgb(e,0);break;case kt:C=y.state.patternFillColorSpace;if(!C){if(isNumberArray(e,null)){e=ColorSpaceUtils.gray.getRgb(e,0);i=It;break}e=[];i=ia;break}if("Pattern"===C.name){next(l.handleColorN(r,kt,e,C,b,a,t,d,g,p));return}e=C.getRgb(e,0);i=It;break;case xt:C=y.state.patternStrokeColorSpace;if(!C){if(isNumberArray(e,null)){e=ColorSpaceUtils.gray.getRgb(e,0);i=Ft;break}e=[];i=ra;break}if("Pattern"===C.name){next(l.handleColorN(r,xt,e,C,b,a,t,d,g,p));return}e=C.getRgb(e,0);i=Ft;break;case Mt:let O;try{const t=a.get("Shading");if(!t)throw new FormatError("No shading resource found");O=t.get(e[0].name);if(!O)throw new FormatError("No shading object found")}catch(e){if(e instanceof AbortException)continue;if(l.options.ignoreErrors){warn(`getOperatorList - ignoring Shading: "${e}".`);continue}throw e}const T=l.parseShading({shading:O,resources:a,localColorSpaceCache:d,localShadingPatternCache:p});if(!T)continue;e=[T];i=Mt;break;case De:F=e[0]instanceof Name;v=e[0].name;if(F){const t=f.getByName(v);if(t){t.length>0&&r.addOp(De,[t]);e=null;continue}}next(new Promise((function(e,i){if(!F)throw new FormatError("GState must be referred to by name.");const n=a.get("ExtGState");if(!(n instanceof Dict))throw new FormatError("ExtGState should be a dictionary.");const s=n.get(v);if(!(s instanceof Dict))throw new FormatError("GState should be a dictionary.");l.setGState({resources:a,gState:s,operatorList:r,cacheKey:v,task:t,stateManager:y,localGStateCache:f,localColorSpaceCache:d,seenRefs:c}).then(e,i)})).catch((function(e){if(!(e instanceof AbortException)){if(!l.options.ignoreErrors)throw e;warn(`getOperatorList - ignoring ExtGState: "${e}".`)}})));return;case Ce:{const[t]=e;if("number"!=typeof t){warn(`Invalid setLineWidth: ${t}`);continue}e[0]=Math.abs(t);break}case Ee:case Pe:case Le:case _e:case je:case Ue:case Xe:l.buildPath(i,e,y.state);continue;case qe:case He:case We:case ze:case $e:case Ge:case Ve:case Ke:case Je:{const{state:{pathBuffer:e,pathMinMax:t}}=y;i!==He&&i!==Ve&&i!==Ke||e.push(la);if(0===e.length)r.addOp(aa,[i,[null],null]);else{r.addOp(aa,[i,[new Float32Array(e)],t.slice()]);e.length=0;t.set([1/0,1/0,-1/0,-1/0],0)}continue}case ht:r.addOp(i,[new Float32Array(e)]);continue;case Et:case Pt:case Ut:case Xt:continue;case _t:if(!(e[0]instanceof Name)){warn(`Expected name for beginMarkedContentProps arg0=${e[0]}`);r.addOp(_t,["OC",null]);continue}if("OC"===e[0].name){next(l.parseMarkedContentProps(e[1],a).then((e=>{r.addOp(_t,["OC",e])})).catch((e=>{if(!(e instanceof AbortException)){if(!l.options.ignoreErrors)throw e;warn(`getOperatorList - ignoring beginMarkedContentProps: "${e}".`);r.addOp(_t,["OC",null])}})));return}e=[e[0].name,e[1]instanceof Dict?e[1].get("MCID"):null];break;default:if(null!==e){for(x=0,k=e.length;x{if(!(e instanceof AbortException)){if(!this.options.ignoreErrors)throw e;warn(`getOperatorList - ignoring errors during "${t.name}" task: "${e}".`);closePendingRestoreOPS()}}))}getTextContent({stream:e,task:a,resources:r,stateManager:i=null,includeMarkedContent:n=!1,sink:s,seenStyles:o=new Set,viewBox:c,lang:l=null,markedContentData:h=null,disableNormalization:u=!1,keepWhiteSpace:d=!1,prevRefs:f=null}){const g=e.dict?.objId,p=new RefSet(f);if(g){if(f?.has(g))throw new Error(`getTextContent - ignoring circular reference: ${g}`);p.put(g)}r||=Dict.empty;i||=new StateManager(new TextState);n&&(h||={level:0});const m={items:[],styles:Object.create(null),lang:l},b={initialized:!1,str:[],totalWidth:0,totalHeight:0,width:0,height:0,vertical:!1,prevTransform:null,textAdvanceScale:0,spaceInFlowMin:0,spaceInFlowMax:0,trackingSpaceMin:1/0,negativeSpaceMax:-1/0,notASpace:-1/0,transform:null,fontName:null,hasEOL:!1},y=[" "," "];let w=0;function saveLastChar(e){const t=(w+1)%2,a=" "!==y[w]&&" "===y[t];y[w]=e;w=t;return!d&&a}function shouldAddWhitepsace(){return!d&&" "!==y[w]&&" "===y[(w+1)%2]}function resetLastChars(){y[0]=y[1]=" ";w=0}const S=this,x=this.xref,k=[];let C=null;const v=new LocalImageCache,F=new LocalGStateCache,O=new EvaluatorPreprocessor(e,x,i);let T;function pushWhitespace({width:e=0,height:t=0,transform:a=b.prevTransform,fontName:r=b.fontName}){m.items.push({str:" ",dir:"ltr",width:e,height:t,transform:a,fontName:r,hasEOL:!1})}function getCurrentTextTransform(){const e=T.font,a=[T.fontSize*T.textHScale,0,0,T.fontSize,0,T.textRise];if(e.isType3Font&&(T.fontSize<=1||e.isCharBBox)&&!isArrayEqual(T.fontMatrix,t)){const t=e.bbox[3]-e.bbox[1];t>0&&(a[3]*=t*T.fontMatrix[3])}return Util.transform(T.ctm,Util.transform(T.textMatrix,a))}function ensureTextContentItem(){if(b.initialized)return b;const{font:e,loadedName:t}=T;if(!o.has(t)){o.add(t);m.styles[t]={fontFamily:e.fallbackName,ascent:e.ascent,descent:e.descent,vertical:e.vertical};if(S.options.fontExtraProperties&&e.systemFontInfo){const a=m.styles[t];a.fontSubstitution=e.systemFontInfo.css;a.fontSubstitutionLoadedName=e.systemFontInfo.loadedName}}b.fontName=t;const a=b.transform=getCurrentTextTransform();if(e.vertical){b.width=b.totalWidth=Math.hypot(a[0],a[1]);b.height=b.totalHeight=0;b.vertical=!0}else{b.width=b.totalWidth=0;b.height=b.totalHeight=Math.hypot(a[2],a[3]);b.vertical=!1}const r=Math.hypot(T.textLineMatrix[0],T.textLineMatrix[1]),i=Math.hypot(T.ctm[0],T.ctm[1]);b.textAdvanceScale=i*r;const{fontSize:n}=T;b.trackingSpaceMin=.102*n;b.notASpace=.03*n;b.negativeSpaceMax=-.2*n;b.spaceInFlowMin=.102*n;b.spaceInFlowMax=.6*n;b.hasEOL=!1;b.initialized=!0;return b}function updateAdvanceScale(){if(!b.initialized)return;const e=Math.hypot(T.textLineMatrix[0],T.textLineMatrix[1]),t=Math.hypot(T.ctm[0],T.ctm[1])*e;if(t!==b.textAdvanceScale){if(b.vertical){b.totalHeight+=b.height*b.textAdvanceScale;b.height=0}else{b.totalWidth+=b.width*b.textAdvanceScale;b.width=0}b.textAdvanceScale=t}}function runBidiTransform(e){let t=e.str.join("");u||(t=function normalizeUnicode(e){if(!ma){ma=/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;ba=new Map([["ſt","ſt"]])}return e.replaceAll(ma,((e,t,a)=>t?t.normalize("NFKC"):ba.get(a)))}(t));const a=bidi(t,-1,e.vertical);return{str:a.str,dir:a.dir,width:Math.abs(e.totalWidth),height:Math.abs(e.totalHeight),transform:e.transform,fontName:e.fontName,hasEOL:e.hasEOL}}async function handleSetFont(e,i){const n=await S.loadFont(e,i,r,a);T.loadedName=n.loadedName;T.font=n.font;T.fontMatrix=n.font.fontMatrix||t}function applyInverseRotation(e,t,a){const r=Math.hypot(a[0],a[1]);return[(a[0]*e+a[1]*t)/r,(a[2]*e+a[3]*t)/r]}function compareWithLastPosition(e){const t=getCurrentTextTransform();let a=t[4],r=t[5];if(T.font?.vertical){if(ac[2]||r+ec[3])return!1}else if(a+ec[2]||rc[3])return!1;if(!T.font||!b.prevTransform)return!0;let i=b.prevTransform[4],n=b.prevTransform[5];if(i===a&&n===r)return!0;let s=-1;t[0]&&0===t[1]&&0===t[2]?s=t[0]>0?0:180:t[1]&&0===t[0]&&0===t[3]&&(s=t[1]>0?90:270);switch(s){case 0:break;case 90:[a,r]=[r,a];[i,n]=[n,i];break;case 180:[a,r,i,n]=[-a,-r,-i,-n];break;case 270:[a,r]=[-r,-a];[i,n]=[-n,-i];break;default:[a,r]=applyInverseRotation(a,r,t);[i,n]=applyInverseRotation(i,n,b.prevTransform)}if(T.font.vertical){const e=(n-r)/b.textAdvanceScale,t=a-i,s=Math.sign(b.height);if(e.5*b.width){appendEOL();return!0}resetLastChars();flushTextContentItem();return!0}if(Math.abs(t)>b.width){appendEOL();return!0}e<=s*b.notASpace&&resetLastChars();if(e<=s*b.trackingSpaceMin)if(shouldAddWhitepsace()){resetLastChars();flushTextContentItem();pushWhitespace({height:Math.abs(e)})}else b.height+=e;else if(!addFakeSpaces(e,b.prevTransform,s))if(0===b.str.length){resetLastChars();pushWhitespace({height:Math.abs(e)})}else b.height+=e;Math.abs(t)>.25*b.width&&flushTextContentItem();return!0}const o=(a-i)/b.textAdvanceScale,l=r-n,h=Math.sign(b.width);if(o.5*b.height){appendEOL();return!0}resetLastChars();flushTextContentItem();return!0}if(Math.abs(l)>b.height){appendEOL();return!0}o<=h*b.notASpace&&resetLastChars();if(o<=h*b.trackingSpaceMin)if(shouldAddWhitepsace()){resetLastChars();flushTextContentItem();pushWhitespace({width:Math.abs(o)})}else b.width+=o;else if(!addFakeSpaces(o,b.prevTransform,h))if(0===b.str.length){resetLastChars();pushWhitespace({width:Math.abs(o)})}else b.width+=o;Math.abs(l)>.25*b.height&&flushTextContentItem();return!0}function buildTextContentItem({chars:e,extraSpacing:t}){const a=T.font;if(!e){const e=T.charSpacing+t;e&&(a.vertical?T.translateTextMatrix(0,-e):T.translateTextMatrix(e*T.textHScale,0));d&&compareWithLastPosition(0);return}const r=a.charsToGlyphs(e),i=T.fontMatrix[0]*T.fontSize;for(let e=0,n=r.length;e0){const e=k.join("");k.length=0;buildTextContentItem({chars:e,extraSpacing:0})}break;case dt:if(!i.state.font){S.ensureStateFont(i.state);continue}buildTextContentItem({chars:w[0],extraSpacing:0});break;case gt:if(!i.state.font){S.ensureStateFont(i.state);continue}T.carriageReturn();buildTextContentItem({chars:w[0],extraSpacing:0});break;case pt:if(!i.state.font){S.ensureStateFont(i.state);continue}T.wordSpacing=w[0];T.charSpacing=w[1];T.carriageReturn();buildTextContentItem({chars:w[2],extraSpacing:0});break;case Nt:flushTextContentItem();C??=r.get("XObject")||Dict.empty;y=w[0]instanceof Name;b=w[0].name;if(y&&v.getByName(b))break;next(new Promise((function(e,t){if(!y)throw new FormatError("XObject must be referred to by name.");let f=C.getRaw(b);if(f instanceof Ref){if(v.getByRef(f)){e();return}if(S.globalImageCache.getData(f,S.pageIndex)){e();return}f=x.fetch(f)}if(!(f instanceof BaseStream))throw new FormatError("XObject should be a stream");const{dict:g}=f,m=g.get("Subtype");if(!(m instanceof Name))throw new FormatError("XObject should have a Name subtype");if("Form"!==m.name){v.set(b,g.objId,!0);e();return}const w=i.state.clone(),k=new StateManager(w),F=lookupMatrix(g.getArray("Matrix"),null);F&&k.transform(F);const O=g.get("Resources");enqueueChunk();const T={enqueueInvoked:!1,enqueue(e,t){this.enqueueInvoked=!0;s.enqueue(e,t)},get desiredSize(){return s.desiredSize},get ready(){return s.ready}};S.getTextContent({stream:f,task:a,resources:O instanceof Dict?O:r,stateManager:k,includeMarkedContent:n,sink:T,seenStyles:o,viewBox:c,lang:l,markedContentData:h,disableNormalization:u,keepWhiteSpace:d,prevRefs:p}).then((function(){T.enqueueInvoked||v.set(b,g.objId,!0);e()}),t)})).catch((function(e){if(!(e instanceof AbortException)){if(!S.options.ignoreErrors)throw e;warn(`getTextContent - ignoring XObject: "${e}".`)}})));return;case De:y=w[0]instanceof Name;b=w[0].name;if(y&&F.getByName(b))break;next(new Promise((function(e,t){if(!y)throw new FormatError("GState must be referred to by name.");const a=r.get("ExtGState");if(!(a instanceof Dict))throw new FormatError("ExtGState should be a dictionary.");const i=a.get(b);if(!(i instanceof Dict))throw new FormatError("GState should be a dictionary.");const n=i.get("Font");if(n){flushTextContentItem();T.fontName=null;T.fontSize=n[1];handleSetFont(null,n[0]).then(e,t)}else{F.set(b,i.objId,!0);e()}})).catch((function(e){if(!(e instanceof AbortException)){if(!S.options.ignoreErrors)throw e;warn(`getTextContent - ignoring ExtGState: "${e}".`)}})));return;case Lt:flushTextContentItem();if(n){h.level++;m.items.push({type:"beginMarkedContent",tag:w[0]instanceof Name?w[0].name:null})}break;case _t:flushTextContentItem();if(n){h.level++;let e=null;w[1]instanceof Dict&&(e=w[1].get("MCID"));m.items.push({type:"beginMarkedContentProps",id:Number.isInteger(e)?`${S.idFactory.getPageObjId()}_mc${e}`:null,tag:w[0]instanceof Name?w[0].name:null})}break;case jt:flushTextContentItem();if(n){if(0===h.level)break;h.level--;m.items.push({type:"endMarkedContent"})}break;case Re:!e||e.font===T.font&&e.fontSize===T.fontSize&&e.fontName===T.fontName||flushTextContentItem()}if(m.items.length>=s.desiredSize){g=!0;break}}if(g)next(Ln);else{flushTextContentItem();enqueueChunk();e()}})).catch((e=>{if(!(e instanceof AbortException)){if(!this.options.ignoreErrors)throw e;warn(`getTextContent - ignoring errors during "${a.name}" task: "${e}".`);flushTextContentItem();enqueueChunk()}}))}async extractDataStructures(e,t){const a=this.xref;let r;const i=this.readToUnicode(t.toUnicode);if(t.composite){const a=e.get("CIDSystemInfo");a instanceof Dict&&(t.cidSystemInfo={registry:stringToPDFString(a.get("Registry")),ordering:stringToPDFString(a.get("Ordering")),supplement:a.get("Supplement")});try{const t=e.get("CIDToGIDMap");t instanceof BaseStream&&(r=t.getBytes())}catch(e){if(!this.options.ignoreErrors)throw e;warn(`extractDataStructures - ignoring CIDToGIDMap data: "${e}".`)}}const n=[];let s,o=null;if(e.has("Encoding")){s=e.get("Encoding");if(s instanceof Dict){o=s.get("BaseEncoding");o=o instanceof Name?o.name:null;if(s.has("Differences")){const e=s.get("Differences");let t=0;for(const r of e){const e=a.fetchIfRef(r);if("number"==typeof e)t=e;else{if(!(e instanceof Name))throw new FormatError(`Invalid entry in 'Differences' array: ${e}`);n[t++]=e.name}}}}else if(s instanceof Name)o=s.name;else{const e="Encoding is not a Name nor a Dict";if(!this.options.ignoreErrors)throw new FormatError(e);warn(e)}"MacRomanEncoding"!==o&&"MacExpertEncoding"!==o&&"WinAnsiEncoding"!==o&&(o=null)}const c=!t.file||t.isInternalFont,l=ai()[t.name];o&&c&&l&&(o=null);if(o)t.defaultEncoding=getEncoding(o);else{const e=!!(t.flags&_r),a=!!(t.flags&jr);s=Cr;"TrueType"!==t.type||a||(s=vr);if(e||l){s=kr;c&&(/Symbol/i.test(t.name)?s=Fr:/Dingbats/i.test(t.name)?s=Ir:/Wingdings/i.test(t.name)&&(s=vr))}t.defaultEncoding=s}t.differences=n;t.baseEncodingName=o;t.hasEncoding=!!o||n.length>0;t.dict=e;t.toUnicode=await i;const h=await this.buildToUnicode(t);t.toUnicode=h;r&&(t.cidToGidMap=this.readCidToGidMap(r,h));return t}_simpleFontToUnicode(e,t=!1){assert(!e.composite,"Must be a simple font.");const a=[],r=e.defaultEncoding.slice(),i=e.baseEncodingName,n=e.differences;for(const e in n){const t=n[e];".notdef"!==t&&(r[e]=t)}const s=Or();for(const n in r){let o=r[n];if(""===o)continue;let c=s[o];if(void 0!==c){a[n]=String.fromCharCode(c);continue}let l=0;switch(o[0]){case"G":3===o.length&&(l=parseInt(o.substring(1),16));break;case"g":5===o.length&&(l=parseInt(o.substring(1),16));break;case"C":case"c":if(o.length>=3&&o.length<=4){const a=o.substring(1);if(t){l=parseInt(a,16);break}l=+a;if(Number.isNaN(l)&&Number.isInteger(parseInt(a,16)))return this._simpleFontToUnicode(e,!0)}break;case"u":c=getUnicodeForGlyph(o,s);-1!==c&&(l=c);break;default:switch(o){case"f_h":case"f_t":case"T_h":a[n]=o.replaceAll("_","");continue}}if(l>0&&l<=1114111&&Number.isInteger(l)){if(i&&l===+n){const e=getEncoding(i);if(e&&(o=e[n])){a[n]=String.fromCharCode(s[o]);continue}}a[n]=String.fromCodePoint(l)}}return a}async buildToUnicode(e){e.hasIncludedToUnicodeMap=e.toUnicode?.length>0;if(e.hasIncludedToUnicodeMap){!e.composite&&e.hasEncoding&&(e.fallbackToUnicode=this._simpleFontToUnicode(e));return e.toUnicode}if(!e.composite)return new ToUnicodeMap(this._simpleFontToUnicode(e));if(e.composite&&(e.cMap.builtInCMap&&!(e.cMap instanceof IdentityCMap)||"Adobe"===e.cidSystemInfo?.registry&&("GB1"===e.cidSystemInfo.ordering||"CNS1"===e.cidSystemInfo.ordering||"Japan1"===e.cidSystemInfo.ordering||"Korea1"===e.cidSystemInfo.ordering))){const{registry:t,ordering:a}=e.cidSystemInfo,r=Name.get(`${t}-${a}-UCS2`),i=await CMapFactory.create({encoding:r,fetchBuiltInCMap:this._fetchBuiltInCMapBound,useCMap:null}),n=[],s=[];e.cMap.forEach((function(e,t){if(t>65535)throw new FormatError("Max size of CID is 65,535");const a=i.lookup(t);if(a){s.length=0;for(let e=0,t=a.length;e>1;(0!==i||t.has(n))&&(a[n]=i)}return a}extractWidths(e,t,a){const r=this.xref;let i=[],n=0;const s=[];let o;if(a.composite){const t=e.get("DW");n="number"==typeof t?Math.ceil(t):1e3;const c=e.get("W");if(Array.isArray(c))for(let e=0,t=c.length;e{const t=c.get(e),r=new OperatorList;return n.getOperatorList({stream:t,task:a,resources:l,operatorList:r}).then((()=>{switch(r.fnArray[0]){case bt:this.#G(r,g);break;case mt:g||this.#V(r)}h[e]=r.getIR();for(const e of r.dependencies)i.add(e)})).catch((function(t){warn(`Type3 font resource "${e}" is not available.`);const a=new OperatorList;h[e]=a.getIR()}))}));this.#$=o.then((()=>{r.charProcOperatorList=h;if(this._bbox){r.isCharBBox=!0;r.bbox=this._bbox}}));return this.#$}#G(e,t=NaN){const a=Util.normalizeRect(e.argsArray[0].slice(2)),r=a[2]-a[0],i=a[3]-a[1],n=Math.hypot(r,i);if(0===r||0===i){e.fnArray.splice(0,1);e.argsArray.splice(0,1)}else if(0===t||Math.round(n/t)>=10){this._bbox??=[1/0,1/0,-1/0,-1/0];Util.rectBoundingBox(...a,this._bbox)}let s=0,o=e.length;for(;s=Ee&&n<=Je;if(i.variableArgs)o>s&&info(`Command ${r}: expected [0, ${s}] args, but received ${o} args.`);else{if(o!==s){const e=this.nonProcessedArgs;for(;o>s;){e.push(t.shift());o--}for(;oEvaluatorPreprocessor.MAX_INVALID_PATH_OPS)throw new FormatError(`Invalid ${e}`);warn(`Skipping ${e}`);null!==t&&(t.length=0);continue}}this.preprocessCommand(n,t);e.fn=n;e.args=t;return!0}if(a===wa)return!1;if(null!==a){null===t&&(t=[]);t.push(a);if(t.length>33)throw new FormatError("Too many arguments")}}}preprocessCommand(e,t){switch(0|e){case Be:this.stateManager.save();break;case Re:this.stateManager.restore();break;case Ne:this.stateManager.transform(t)}}}class DefaultAppearanceEvaluator extends EvaluatorPreprocessor{constructor(e){super(new StringStream(e))}parse(){const e={fn:0,args:[]},t={fontSize:0,fontName:"",fontColor:new Uint8ClampedArray(3)};try{for(;;){e.args.length=0;if(!this.read(e))break;if(0!==this.savedStatesDepth)continue;const{fn:a,args:r}=e;switch(0|a){case nt:const[e,a]=r;e instanceof Name&&(t.fontName=e.name);"number"==typeof a&&a>0&&(t.fontSize=a);break;case It:ColorSpaceUtils.rgb.getRgbItem(r,0,t.fontColor,0);break;case vt:ColorSpaceUtils.gray.getRgbItem(r,0,t.fontColor,0);break;case Tt:ColorSpaceUtils.cmyk.getRgbItem(r,0,t.fontColor,0)}}}catch(e){warn(`parseDefaultAppearance - ignoring errors: "${e}".`)}return t}}function parseDefaultAppearance(e){return new DefaultAppearanceEvaluator(e).parse()}class AppearanceStreamEvaluator extends EvaluatorPreprocessor{constructor(e,t,a,r){super(e);this.stream=e;this.evaluatorOptions=t;this.xref=a;this.globalColorSpaceCache=r;this.resources=e.dict?.get("Resources")}parse(){const e={fn:0,args:[]};let t={scaleFactor:1,fontSize:0,fontName:"",fontColor:new Uint8ClampedArray(3),fillColorSpace:ColorSpaceUtils.gray},a=!1;const r=[];try{for(;;){e.args.length=0;if(a||!this.read(e))break;const{fn:i,args:n}=e;switch(0|i){case Be:r.push({scaleFactor:t.scaleFactor,fontSize:t.fontSize,fontName:t.fontName,fontColor:t.fontColor.slice(),fillColorSpace:t.fillColorSpace});break;case Re:t=r.pop()||t;break;case ht:t.scaleFactor*=Math.hypot(n[0],n[1]);break;case nt:const[e,i]=n;e instanceof Name&&(t.fontName=e.name);"number"==typeof i&&i>0&&(t.fontSize=i*t.scaleFactor);break;case wt:t.fillColorSpace=ColorSpaceUtils.parse({cs:n[0],xref:this.xref,resources:this.resources,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:this._localColorSpaceCache});break;case At:t.fillColorSpace.getRgbItem(n,0,t.fontColor,0);break;case It:ColorSpaceUtils.rgb.getRgbItem(n,0,t.fontColor,0);break;case vt:ColorSpaceUtils.gray.getRgbItem(n,0,t.fontColor,0);break;case Tt:ColorSpaceUtils.cmyk.getRgbItem(n,0,t.fontColor,0);break;case dt:case ft:case gt:case pt:a=!0}}}catch(e){warn(`parseAppearanceStream - ignoring errors: "${e}".`)}this.stream.reset();delete t.scaleFactor;delete t.fillColorSpace;return t}get _localColorSpaceCache(){return shadow(this,"_localColorSpaceCache",new LocalColorSpaceCache)}get _pdfFunctionFactory(){return shadow(this,"_pdfFunctionFactory",new PDFFunctionFactory({xref:this.xref,isEvalSupported:this.evaluatorOptions.isEvalSupported}))}}function getPdfColor(e,t){if(e[0]===e[1]&&e[1]===e[2]){return`${numberToString(e[0]/255)} ${t?"g":"G"}`}return Array.from(e,(e=>numberToString(e/255))).join(" ")+" "+(t?"rg":"RG")}class FakeUnicodeFont{constructor(e,t){this.xref=e;this.widths=null;this.firstChar=1/0;this.lastChar=-1/0;this.fontFamily=t;const a=new OffscreenCanvas(1,1);this.ctxMeasure=a.getContext("2d",{willReadFrequently:!0});FakeUnicodeFont._fontNameId||(FakeUnicodeFont._fontNameId=1);this.fontName=Name.get(`InvalidPDFjsFont_${t}_${FakeUnicodeFont._fontNameId++}`)}get fontDescriptorRef(){if(!FakeUnicodeFont._fontDescriptorRef){const e=new Dict(this.xref);e.set("Type",Name.get("FontDescriptor"));e.set("FontName",this.fontName);e.set("FontFamily","MyriadPro Regular");e.set("FontBBox",[0,0,0,0]);e.set("FontStretch",Name.get("Normal"));e.set("FontWeight",400);e.set("ItalicAngle",0);FakeUnicodeFont._fontDescriptorRef=this.xref.getNewPersistentRef(e)}return FakeUnicodeFont._fontDescriptorRef}get descendantFontRef(){const e=new Dict(this.xref);e.set("BaseFont",this.fontName);e.set("Type",Name.get("Font"));e.set("Subtype",Name.get("CIDFontType0"));e.set("CIDToGIDMap",Name.get("Identity"));e.set("FirstChar",this.firstChar);e.set("LastChar",this.lastChar);e.set("FontDescriptor",this.fontDescriptorRef);e.set("DW",1e3);const t=[],a=[...this.widths.entries()].sort();let r=null,i=null;for(const[e,n]of a)if(r)if(e===r+i.length)i.push(n);else{t.push(r,i);r=e;i=[n]}else{r=e;i=[n]}r&&t.push(r,i);e.set("W",t);const n=new Dict(this.xref);n.set("Ordering","Identity");n.set("Registry","Adobe");n.set("Supplement",0);e.set("CIDSystemInfo",n);return this.xref.getNewPersistentRef(e)}get baseFontRef(){const e=new Dict(this.xref);e.set("BaseFont",this.fontName);e.set("Type",Name.get("Font"));e.set("Subtype",Name.get("Type0"));e.set("Encoding",Name.get("Identity-H"));e.set("DescendantFonts",[this.descendantFontRef]);e.set("ToUnicode",Name.get("Identity-H"));return this.xref.getNewPersistentRef(e)}get resources(){const e=new Dict(this.xref),t=new Dict(this.xref);t.set(this.fontName.name,this.baseFontRef);e.set("Font",t);return e}_createContext(){this.widths=new Map;this.ctxMeasure.font=`1000px ${this.fontFamily}`;return this.ctxMeasure}createFontResources(e){const t=this._createContext();for(const a of e.split(/\r\n?|\n/))for(const e of a.split("")){const a=e.charCodeAt(0);if(this.widths.has(a))continue;const r=t.measureText(e),i=Math.ceil(r.width);this.widths.set(a,i);this.firstChar=Math.min(a,this.firstChar);this.lastChar=Math.max(a,this.lastChar)}return this.resources}static getFirstPositionInfo(e,t,i){const[n,s,o,c]=e;let l=o-n,h=c-s;t%180!=0&&([l,h]=[h,l]);const u=a*i;return{coords:[0,h+r*i-u],bbox:[0,0,l,h],matrix:0!==t?getRotationMatrix(t,h,u):void 0}}createAppearance(e,t,i,n,s,o){const c=this._createContext(),l=[];let h=-1/0;for(const t of e.split(/\r\n?|\n/)){l.push(t);const e=c.measureText(t).width;h=Math.max(h,e);for(const e of codePointIter(t)){const t=String.fromCodePoint(e);let a=this.widths.get(e);if(void 0===a){const r=c.measureText(t);a=Math.ceil(r.width);this.widths.set(e,a);this.firstChar=Math.min(e,this.firstChar);this.lastChar=Math.max(e,this.lastChar)}}}h*=n/1e3;const[u,d,f,g]=t;let p=f-u,m=g-d;i%180!=0&&([p,m]=[m,p]);let b=1;h>p&&(b=p/h);let y=1;const w=a*n,S=r*n,x=w*l.length;x>m&&(y=m/x);const k=n*Math.min(b,y),C=["q",`0 0 ${numberToString(p)} ${numberToString(m)} re W n`,"BT",`1 0 0 1 0 ${numberToString(m+S)} Tm 0 Tc ${getPdfColor(s,!0)}`,`/${this.fontName.name} ${numberToString(k)} Tf`],{resources:v}=this;if(1!==(o="number"==typeof o&&o>=0&&o<=1?o:1)){C.push("/R0 gs");const e=new Dict(this.xref),t=new Dict(this.xref);t.set("ca",o);t.set("CA",o);t.set("Type",Name.get("ExtGState"));e.set("R0",t);v.set("ExtGState",e)}const F=numberToString(w);for(const e of l)C.push(`0 -${F} Td <${stringToUTF16HexString(e)}> Tj`);C.push("ET","Q");const O=C.join("\n"),T=new Dict(this.xref);T.set("Subtype",Name.get("Form"));T.set("Type",Name.get("XObject"));T.set("BBox",[0,0,p,m]);T.set("Length",O.length);T.set("Resources",v);if(i){const e=getRotationMatrix(i,p,m);T.set("Matrix",e)}const M=new StringStream(O);M.dict=T;return M}}class NameOrNumberTree{constructor(e,t,a){this.root=e;this.xref=t;this._type=a}getAll(){const e=new Map;if(!this.root)return e;const t=this.xref,a=new RefSet;a.put(this.root);const r=[this.root];for(;r.length>0;){const i=t.fetchIfRef(r.shift());if(!(i instanceof Dict))continue;if(i.has("Kids")){const e=i.get("Kids");if(!Array.isArray(e))continue;for(const t of e){if(a.has(t))throw new FormatError(`Duplicate entry in "${this._type}" tree.`);r.push(t);a.put(t)}continue}const n=i.get(this._type);if(Array.isArray(n))for(let a=0,r=n.length;a10){warn(`Search depth limit reached for "${this._type}" tree.`);return null}const i=a.get("Kids");if(!Array.isArray(i))return null;let n=0,s=i.length-1;for(;n<=s;){const r=n+s>>1,o=t.fetchIfRef(i[r]),c=o.get("Limits");if(et.fetchIfRef(c[1]))){a=o;break}n=r+1}}if(n>s)return null}const i=a.get(this._type);if(Array.isArray(i)){let a=0,r=i.length-2;for(;a<=r;){const n=a+r>>1,s=n+(1&n),o=t.fetchIfRef(i[s]);if(eo))return i[s+1];a=s+2}}}return null}get(e){return this.xref.fetchIfRef(this.getRaw(e))}}class NameTree extends NameOrNumberTree{constructor(e,t){super(e,t,"Names")}}class NumberTree extends NameOrNumberTree{constructor(e,t){super(e,t,"Nums")}}function clearGlobalCaches(){!function clearPatternCaches(){Ti=Object.create(null)}();!function clearPrimitiveCaches(){Sa=Object.create(null);xa=Object.create(null);Aa=Object.create(null)}();!function clearUnicodeCaches(){Nr.clear()}();JpxImage.cleanup()}function pickPlatformItem(e){return e instanceof Dict?e.has("UF")?e.get("UF"):e.has("F")?e.get("F"):e.has("Unix")?e.get("Unix"):e.has("Mac")?e.get("Mac"):e.has("DOS")?e.get("DOS"):null:null}class FileSpec{#K=!1;constructor(e,t,a=!1){if(e instanceof Dict){this.xref=t;this.root=e;e.has("FS")&&(this.fs=e.get("FS"));e.has("RF")&&warn("Related file specifications are not supported");a||(e.has("EF")?this.#K=!0:warn("Non-embedded file specifications are not supported"))}}get filename(){let e="";const t=pickPlatformItem(this.root);t&&"string"==typeof t&&(e=stringToPDFString(t).replaceAll("\\\\","\\").replaceAll("\\/","/").replaceAll("\\","/"));return shadow(this,"filename",e||"unnamed")}get content(){if(!this.#K)return null;this._contentRef||=pickPlatformItem(this.root?.get("EF"));let e=null;if(this._contentRef){const t=this.xref.fetchIfRef(this._contentRef);t instanceof BaseStream?e=t.getBytes():warn("Embedded file specification points to non-existing/invalid content")}else warn("Embedded file specification does not have any content");return e}get description(){let e="";const t=this.root?.get("Desc");t&&"string"==typeof t&&(e=stringToPDFString(t));return shadow(this,"description",e)}get serializable(){return{rawFilename:this.filename,filename:(e=this.filename,e.substring(e.lastIndexOf("/")+1)),content:this.content,description:this.description};var e}}const _n=0,jn=-2,Un=-3,Xn=-4,qn=-5,Hn=-6,Wn=-9;function isWhitespace(e,t){const a=e[t];return" "===a||"\n"===a||"\r"===a||"\t"===a}class XMLParserBase{_resolveEntities(e){return e.replaceAll(/&([^;]+);/g,((e,t)=>{if("#x"===t.substring(0,2))return String.fromCodePoint(parseInt(t.substring(2),16));if("#"===t.substring(0,1))return String.fromCodePoint(parseInt(t.substring(1),10));switch(t){case"lt":return"<";case"gt":return">";case"amp":return"&";case"quot":return'"';case"apos":return"'"}return this.onResolveEntity(t)}))}_parseContent(e,t){const a=[];let r=t;function skipWs(){for(;r"!==e[r]&&"/"!==e[r];)++r;const i=e.substring(t,r);skipWs();for(;r"!==e[r]&&"/"!==e[r]&&"?"!==e[r];){skipWs();let t="",i="";for(;r"!==e[a]&&"?"!==e[a]&&"/"!==e[a];)++a;const r=e.substring(t,a);!function skipWs(){for(;a"!==e[a+1]);)++a;return{name:r,value:e.substring(i,a),parsed:a-t}}parseXml(e){let t=0;for(;t",a);if(t<0){this.onError(Wn);return}this.onEndElement(e.substring(a,t));a=t+1;break;case"?":++a;const r=this._parseProcessingInstruction(e,a);if("?>"!==e.substring(a+r.parsed,a+r.parsed+2)){this.onError(Un);return}this.onPi(r.name,r.value);a+=r.parsed+2;break;case"!":if("--"===e.substring(a+1,a+3)){t=e.indexOf("--\x3e",a+3);if(t<0){this.onError(qn);return}this.onComment(e.substring(a+3,t));a=t+3}else if("[CDATA["===e.substring(a+1,a+8)){t=e.indexOf("]]>",a+8);if(t<0){this.onError(jn);return}this.onCdata(e.substring(a+8,t));a=t+3}else{if("DOCTYPE"!==e.substring(a+1,a+8)){this.onError(Hn);return}{const r=e.indexOf("[",a+8);let i=!1;t=e.indexOf(">",a+8);if(t<0){this.onError(Xn);return}if(r>0&&t>r){t=e.indexOf("]>",a+8);if(t<0){this.onError(Xn);return}i=!0}const n=e.substring(a+8,t+(i?1:0));this.onDoctype(n);a=t+(i?2:1)}}break;default:const i=this._parseContent(e,a);if(null===i){this.onError(Hn);return}let n=!1;if("/>"===e.substring(a+i.parsed,a+i.parsed+2))n=!0;else if(">"!==e.substring(a+i.parsed,a+i.parsed+1)){this.onError(Wn);return}this.onBeginElement(i.name,i.attributes,n);a+=i.parsed+(n?2:1)}}else{for(;ae.textContent)).join(""):this.nodeValue||""}get children(){return this.childNodes||[]}hasChildNodes(){return this.childNodes?.length>0}searchNode(e,t){if(t>=e.length)return this;const a=e[t];if(a.name.startsWith("#")&&t0){r.push([i,0]);i=i.childNodes[0]}else{if(0===r.length)return null;for(;0!==r.length;){const[e,t]=r.pop(),a=t+1;if(a");for(const t of this.childNodes)t.dump(e);e.push(``)}else this.nodeValue?e.push(`>${encodeToXmlString(this.nodeValue)}`):e.push("/>")}else e.push(encodeToXmlString(this.nodeValue))}}class SimpleXMLParser extends XMLParserBase{constructor({hasAttributes:e=!1,lowerCaseName:t=!1}){super();this._currentFragment=null;this._stack=null;this._errorCode=_n;this._hasAttributes=e;this._lowerCaseName=t}parseFromString(e){this._currentFragment=[];this._stack=[];this._errorCode=_n;this.parseXml(e);if(this._errorCode!==_n)return;const[t]=this._currentFragment;return t?{documentElement:t}:void 0}onText(e){if(function isWhitespaceString(e){for(let t=0,a=e.length;t\\376\\377([^<]+)/g,(function(e,t){const a=t.replaceAll(/\\([0-3])([0-7])([0-7])/g,(function(e,t,a,r){return String.fromCharCode(64*t+8*a+1*r)})).replaceAll(/&(amp|apos|gt|lt|quot);/g,(function(e,t){switch(t){case"amp":return"&";case"apos":return"'";case"gt":return">";case"lt":return"<";case"quot":return'"'}throw new Error(`_repair: ${t} isn't defined.`)})),r=[">"];for(let e=0,t=a.length;e=32&&t<127&&60!==t&&62!==t&&38!==t?r.push(String.fromCharCode(t)):r.push("&#x"+(65536+t).toString(16).substring(1)+";")}return r.join("")}))}_getSequence(e){const t=e.nodeName;return"rdf:bag"!==t&&"rdf:seq"!==t&&"rdf:alt"!==t?null:e.childNodes.filter((e=>"rdf:li"===e.nodeName))}_parseArray(e){if(!e.hasChildNodes())return;const[t]=e.childNodes,a=this._getSequence(t)||[];this._metadataMap.set(e.nodeName,a.map((e=>e.textContent.trim())))}_parse(e){let t=e.documentElement;if("rdf:rdf"!==t.nodeName){t=t.firstChild;for(;t&&"rdf:rdf"!==t.nodeName;)t=t.nextSibling}if(t&&"rdf:rdf"===t.nodeName&&t.hasChildNodes())for(const e of t.childNodes)if("rdf:description"===e.nodeName)for(const t of e.childNodes){const e=t.nodeName;switch(e){case"#text":continue;case"dc:creator":case"dc:subject":this._parseArray(t);continue}this._metadataMap.set(e,t.textContent.trim())}}get serializable(){return{parsedData:this._metadataMap,rawData:this._data}}}const zn=1,$n=2,Gn=3,Vn=4,Kn=5;class StructTreeRoot{constructor(e,t,a){this.xref=e;this.dict=t;this.ref=a instanceof Ref?a:null;this.roleMap=new Map;this.structParentIds=null}init(){this.readRoleMap()}#J(e,t,a){if(!(e instanceof Ref)||t<0)return;this.structParentIds||=new RefSetCache;let r=this.structParentIds.get(e);if(!r){r=[];this.structParentIds.put(e,r)}r.push([t,a])}addAnnotationIdToPage(e,t){this.#J(e,t,Vn)}readRoleMap(){const e=this.dict.get("RoleMap");if(e instanceof Dict)for(const[t,a]of e)a instanceof Name&&this.roleMap.set(t,a.name)}static async canCreateStructureTree({catalogRef:e,pdfManager:t,newAnnotationsByPage:a}){if(!(e instanceof Ref)){warn("Cannot save the struct tree: no catalog reference.");return!1}let r=0,i=!0;for(const[e,n]of a){const{ref:a}=await t.getPage(e);if(!(a instanceof Ref)){warn(`Cannot save the struct tree: page ${e} has no ref.`);i=!0;break}for(const e of n)if(e.accessibilityData?.type){e.parentTreeId=r++;i=!1}}if(i){for(const e of a.values())for(const t of e)delete t.parentTreeId;return!1}return!0}static async createStructureTree({newAnnotationsByPage:e,xref:t,catalogRef:a,pdfManager:r,changes:i}){const n=r.catalog.cloneDict(),s=new RefSetCache;s.put(a,n);const o=t.getNewTemporaryRef();n.set("StructTreeRoot",o);const c=new Dict(t);c.set("Type",Name.get("StructTreeRoot"));const l=t.getNewTemporaryRef();c.set("ParentTree",l);const h=[];c.set("K",h);s.put(o,c);const u=new Dict(t),d=[];u.set("Nums",d);const f=await this.#Y({newAnnotationsByPage:e,structTreeRootRef:o,structTreeRoot:null,kids:h,nums:d,xref:t,pdfManager:r,changes:i,cache:s});c.set("ParentTreeNextKey",f);s.put(l,u);for(const[e,t]of s.items())i.put(e,{data:t})}async canUpdateStructTree({pdfManager:e,newAnnotationsByPage:t}){if(!this.ref){warn("Cannot update the struct tree: no root reference.");return!1}let a=this.dict.get("ParentTreeNextKey");if(!Number.isInteger(a)||a<0){warn("Cannot update the struct tree: invalid next key.");return!1}const r=this.dict.get("ParentTree");if(!(r instanceof Dict)){warn("Cannot update the struct tree: ParentTree isn't a dict.");return!1}const i=r.get("Nums");if(!Array.isArray(i)){warn("Cannot update the struct tree: nums isn't an array.");return!1}const n=new NumberTree(r,this.xref);for(const a of t.keys()){const{pageDict:t}=await e.getPage(a);if(!t.has("StructParents"))continue;const r=t.get("StructParents");if(!Number.isInteger(r)||!Array.isArray(n.get(r))){warn(`Cannot save the struct tree: page ${a} has a wrong id.`);return!1}}let s=!0;for(const[r,i]of t){const{pageDict:t}=await e.getPage(r);StructTreeRoot.#Z({elements:i,xref:this.xref,pageDict:t,numberTree:n});for(const e of i)if(e.accessibilityData?.type){e.accessibilityData.structParent>=0||(e.parentTreeId=a++);s=!1}}if(s){for(const e of t.values())for(const t of e){delete t.parentTreeId;delete t.structTreeParent}return!1}return!0}async updateStructureTree({newAnnotationsByPage:e,pdfManager:t,changes:a}){const{ref:r,xref:i}=this,n=this.dict.clone(),s=new RefSetCache;s.put(r,n);let o,c=n.getRaw("ParentTree");if(c instanceof Ref)o=i.fetch(c);else{o=c;c=i.getNewTemporaryRef();n.set("ParentTree",c)}o=o.clone();s.put(c,o);let l=o.getRaw("Nums"),h=null;if(l instanceof Ref){h=l;l=i.fetch(h)}l=l.slice();h||o.set("Nums",l);const u=await StructTreeRoot.#Y({newAnnotationsByPage:e,structTreeRootRef:r,structTreeRoot:this,kids:null,nums:l,xref:i,pdfManager:t,changes:a,cache:s});if(-1!==u){n.set("ParentTreeNextKey",u);h&&s.put(h,l);for(const[e,t]of s.items())a.put(e,{data:t})}}static async#Y({newAnnotationsByPage:e,structTreeRootRef:t,structTreeRoot:a,kids:r,nums:i,xref:n,pdfManager:s,changes:o,cache:c}){const l=Name.get("OBJR");let h,u=-1;for(const[d,f]of e){const e=await s.getPage(d),{ref:g}=e,p=g instanceof Ref;for(const{accessibilityData:s,ref:m,parentTreeId:b,structTreeParent:y}of f){if(!s?.type)continue;const{structParent:f}=s;if(a&&Number.isInteger(f)&&f>=0){let t=(h||=new Map).get(d);if(void 0===t){t=new StructTreePage(a,e.pageDict).collectObjects(g);h.set(d,t)}const r=t?.get(f);if(r){const e=n.fetch(r).clone();StructTreeRoot.#Q(e,s);o.put(r,{data:e});continue}}u=Math.max(u,b);const w=n.getNewTemporaryRef(),S=new Dict(n);StructTreeRoot.#Q(S,s);await this.#ee({structTreeParent:y,tagDict:S,newTagRef:w,structTreeRootRef:t,fallbackKids:r,xref:n,cache:c});const x=new Dict(n);S.set("K",x);x.set("Type",l);p&&x.set("Pg",g);x.set("Obj",m);c.put(w,S);i.push(b,w)}}return u+1}static#Q(e,{type:t,title:a,lang:r,alt:i,expanded:n,actualText:s}){e.set("S",Name.get(t));a&&e.set("T",stringToAsciiOrUTF16BE(a));r&&e.set("Lang",stringToAsciiOrUTF16BE(r));i&&e.set("Alt",stringToAsciiOrUTF16BE(i));n&&e.set("E",stringToAsciiOrUTF16BE(n));s&&e.set("ActualText",stringToAsciiOrUTF16BE(s))}static#Z({elements:e,xref:t,pageDict:a,numberTree:r}){const i=new Map;for(const t of e)if(t.structTreeParentId){const e=parseInt(t.structTreeParentId.split("_mc")[1],10);let a=i.get(e);if(!a){a=[];i.set(e,a)}a.push(t)}const n=a.get("StructParents");if(!Number.isInteger(n))return;const s=r.get(n),updateElement=(e,a,r)=>{const n=i.get(e);if(n){const e=a.getRaw("P"),i=t.fetchIfRef(e);if(e instanceof Ref&&i instanceof Dict){const e={ref:r,dict:a};for(const t of n)t.structTreeParent=e}return!0}return!1};for(const e of s){if(!(e instanceof Ref))continue;const a=t.fetch(e),r=a.get("K");if(Number.isInteger(r))updateElement(r,a,e);else if(Array.isArray(r))for(let i of r){i=t.fetchIfRef(i);if(Number.isInteger(i)&&updateElement(i,a,e))break;if(!(i instanceof Dict))continue;if(!isName(i.get("Type"),"MCR"))break;const r=i.get("MCID");if(Number.isInteger(r)&&updateElement(r,a,e))break}}}static async#ee({structTreeParent:e,tagDict:t,newTagRef:a,structTreeRootRef:r,fallbackKids:i,xref:n,cache:s}){let o,c=null;if(e){({ref:c}=e);o=e.dict.getRaw("P")||r}else o=r;t.set("P",o);const l=n.fetchIfRef(o);if(!l){i.push(a);return}let h=s.get(o);if(!h){h=l.clone();s.put(o,h)}const u=h.getRaw("K");let d=u instanceof Ref?s.get(u):null;if(!d){d=n.fetchIfRef(u);d=Array.isArray(d)?d.slice():[u];const e=n.getNewTemporaryRef();h.set("K",e);s.put(e,d)}const f=d.indexOf(c);d.splice(f>=0?f+1:d.length,0,a)}}class StructElementNode{constructor(e,t){this.tree=e;this.xref=e.xref;this.dict=t;this.kids=[];this.parseKids()}get role(){const e=this.dict.get("S"),t=e instanceof Name?e.name:"",{root:a}=this.tree;return a.roleMap.get(t)??t}parseKids(){let e=null;const t=this.dict.getRaw("Pg");t instanceof Ref&&(e=t.toString());const a=this.dict.get("K");if(Array.isArray(a))for(const t of a){const a=this.parseKid(e,this.xref.fetchIfRef(t));a&&this.kids.push(a)}else{const t=this.parseKid(e,a);t&&this.kids.push(t)}}parseKid(e,t){if(Number.isInteger(t))return this.tree.pageDict.objId!==e?null:new StructElement({type:zn,mcid:t,pageObjId:e});if(!(t instanceof Dict))return null;const a=t.getRaw("Pg");a instanceof Ref&&(e=a.toString());const r=t.get("Type")instanceof Name?t.get("Type").name:null;if("MCR"===r){if(this.tree.pageDict.objId!==e)return null;const a=t.getRaw("Stm");return new StructElement({type:$n,refObjId:a instanceof Ref?a.toString():null,pageObjId:e,mcid:t.get("MCID")})}if("OBJR"===r){if(this.tree.pageDict.objId!==e)return null;const a=t.getRaw("Obj");return new StructElement({type:Gn,refObjId:a instanceof Ref?a.toString():null,pageObjId:e})}return new StructElement({type:Kn,dict:t})}}class StructElement{constructor({type:e,dict:t=null,mcid:a=null,pageObjId:r=null,refObjId:i=null}){this.type=e;this.dict=t;this.mcid=a;this.pageObjId=r;this.refObjId=i;this.parentNode=null}}class StructTreePage{constructor(e,t){this.root=e;this.xref=e?.xref??null;this.rootDict=e?.dict??null;this.pageDict=t;this.nodes=[]}collectObjects(e){if(!(this.root&&this.rootDict&&e instanceof Ref))return null;const t=this.rootDict.get("ParentTree");if(!t)return null;const a=this.root.structParentIds?.get(e);if(!a)return null;const r=new Map,i=new NumberTree(t,this.xref);for(const[e]of a){const t=i.getRaw(e);t instanceof Ref&&r.set(e,t)}return r}parse(e){if(!(this.root&&this.rootDict&&e instanceof Ref))return;const t=this.rootDict.get("ParentTree");if(!t)return;const a=this.pageDict.get("StructParents"),r=this.root.structParentIds?.get(e);if(!Number.isInteger(a)&&!r)return;const i=new Map,n=new NumberTree(t,this.xref);if(Number.isInteger(a)){const e=n.get(a);if(Array.isArray(e))for(const t of e)t instanceof Ref&&this.addNode(this.xref.fetch(t),i)}if(r)for(const[e,t]of r){const a=n.get(e);if(a){const e=this.addNode(this.xref.fetchIfRef(a),i);1===e?.kids?.length&&e.kids[0].type===Gn&&(e.kids[0].type=t)}}}addNode(e,t,a=0){if(a>40){warn("StructTree MAX_DEPTH reached.");return null}if(!(e instanceof Dict))return null;if(t.has(e))return t.get(e);const r=new StructElementNode(this,e);t.set(e,r);const i=e.get("P");if(!(i instanceof Dict)||isName(i.get("Type"),"StructTreeRoot")){this.addTopLevelNode(e,r)||t.delete(e);return r}const n=this.addNode(i,t,a+1);if(!n)return r;let s=!1;for(const t of n.kids)if(t.type===Kn&&t.dict===e){t.parentNode=r;s=!0}s||t.delete(e);return r}addTopLevelNode(e,t){const a=this.rootDict.get("K");if(!a)return!1;if(a instanceof Dict){if(a.objId!==e.objId)return!1;this.nodes[0]=t;return!0}if(!Array.isArray(a))return!0;let r=!1;for(let i=0;i40){warn("StructTree too deep to be fully serialized.");return}const r=Object.create(null);r.role=e.role;r.children=[];t.children.push(r);let i=e.dict.get("Alt");"string"!=typeof i&&(i=e.dict.get("ActualText"));"string"==typeof i&&(r.alt=stringToPDFString(i));const n=e.dict.get("A");if(n instanceof Dict){const e=lookupNormalRect(n.getArray("BBox"),null);if(e)r.bbox=e;else{const e=n.get("Width"),t=n.get("Height");"number"==typeof e&&e>0&&"number"==typeof t&&t>0&&(r.bbox=[0,0,e,t])}}const s=e.dict.get("Lang");"string"==typeof s&&(r.lang=stringToPDFString(s));for(const t of e.kids){const e=t.type===Kn?t.parentNode:null;e?nodeToSerializable(e,r,a+1):t.type===zn||t.type===$n?r.children.push({type:"content",id:`p${t.pageObjId}_mc${t.mcid}`}):t.type===Gn?r.children.push({type:"object",id:t.refObjId}):t.type===Vn&&r.children.push({type:"annotation",id:`pdfjs_internal_id_${t.refObjId}`})}}const e=Object.create(null);e.children=[];e.role="Root";for(const t of this.nodes)t&&nodeToSerializable(t,e);return e}}const Jn=function _isValidExplicitDest(e,t,a){if(!Array.isArray(a)||a.length<2)return!1;const[r,i,...n]=a;if(!e(r)&&!Number.isInteger(r))return!1;if(!t(i))return!1;const s=n.length;let o=!0;switch(i.name){case"XYZ":if(s<2||s>3)return!1;break;case"Fit":case"FitB":return 0===s;case"FitH":case"FitBH":case"FitV":case"FitBV":if(s>1)return!1;break;case"FitR":if(4!==s)return!1;o=!1;break;default:return!1}for(const e of n)if(!("number"==typeof e||o&&null===e))return!1;return!0}.bind(null,(e=>e instanceof Ref),isName);function fetchDest(e){e instanceof Dict&&(e=e.get("D"));return Jn(e)?e:null}function fetchRemoteDest(e){let t=e.get("D");if(t){t instanceof Name&&(t=t.name);if("string"==typeof t)return stringToPDFString(t);if(Jn(t))return JSON.stringify(t)}return null}class Catalog{constructor(e,t){this.pdfManager=e;this.xref=t;this._catDict=t.getCatalogObj();if(!(this._catDict instanceof Dict))throw new FormatError("Catalog object is not a dictionary.");this.toplevelPagesDict;this._actualNumPages=null;this.fontCache=new RefSetCache;this.builtInCMapCache=new Map;this.standardFontDataCache=new Map;this.globalColorSpaceCache=new GlobalColorSpaceCache;this.globalImageCache=new GlobalImageCache;this.pageKidsCountCache=new RefSetCache;this.pageIndexCache=new RefSetCache;this.pageDictCache=new RefSetCache;this.nonBlendModesSet=new RefSet;this.systemFontCache=new Map}cloneDict(){return this._catDict.clone()}get version(){const e=this._catDict.get("Version");if(e instanceof Name){if(Ca.test(e.name))return shadow(this,"version",e.name);warn(`Invalid PDF catalog version: ${e.name}`)}return shadow(this,"version",null)}get lang(){const e=this._catDict.get("Lang");return shadow(this,"lang",e&&"string"==typeof e?stringToPDFString(e):null)}get needsRendering(){const e=this._catDict.get("NeedsRendering");return shadow(this,"needsRendering","boolean"==typeof e&&e)}get collection(){let e=null;try{const t=this._catDict.get("Collection");t instanceof Dict&&t.size>0&&(e=t)}catch(e){if(e instanceof MissingDataException)throw e;info("Cannot fetch Collection entry; assuming no collection is present.")}return shadow(this,"collection",e)}get acroForm(){let e=null;try{const t=this._catDict.get("AcroForm");t instanceof Dict&&t.size>0&&(e=t)}catch(e){if(e instanceof MissingDataException)throw e;info("Cannot fetch AcroForm entry; assuming no forms are present.")}return shadow(this,"acroForm",e)}get acroFormRef(){const e=this._catDict.getRaw("AcroForm");return shadow(this,"acroFormRef",e instanceof Ref?e:null)}get metadata(){const e=this._catDict.getRaw("Metadata");if(!(e instanceof Ref))return shadow(this,"metadata",null);let t=null;try{const a=this.xref.fetch(e,!this.xref.encrypt?.encryptMetadata);if(a instanceof BaseStream&&a.dict instanceof Dict){const e=a.dict.get("Type"),r=a.dict.get("Subtype");if(isName(e,"Metadata")&&isName(r,"XML")){const e=stringToUTF8String(a.getString());e&&(t=new MetadataParser(e).serializable)}}}catch(e){if(e instanceof MissingDataException)throw e;info(`Skipping invalid Metadata: "${e}".`)}return shadow(this,"metadata",t)}get markInfo(){let e=null;try{e=this._readMarkInfo()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read mark info.")}return shadow(this,"markInfo",e)}_readMarkInfo(){const e=this._catDict.get("MarkInfo");if(!(e instanceof Dict))return null;const t={Marked:!1,UserProperties:!1,Suspects:!1};for(const a in t){const r=e.get(a);"boolean"==typeof r&&(t[a]=r)}return t}get structTreeRoot(){let e=null;try{e=this.#te()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable read to structTreeRoot info.")}return shadow(this,"structTreeRoot",e)}#te(){const e=this._catDict.getRaw("StructTreeRoot"),t=this.xref.fetchIfRef(e);if(!(t instanceof Dict))return null;const a=new StructTreeRoot(this.xref,t,e);a.init();return a}get toplevelPagesDict(){const e=this._catDict.get("Pages");if(!(e instanceof Dict))throw new FormatError("Invalid top-level pages dictionary.");return shadow(this,"toplevelPagesDict",e)}get documentOutline(){let e=null;try{e=this._readDocumentOutline()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read document outline.")}return shadow(this,"documentOutline",e)}_readDocumentOutline(){let e=this._catDict.get("Outlines");if(!(e instanceof Dict))return null;e=e.getRaw("First");if(!(e instanceof Ref))return null;const t={items:[]},a=[{obj:e,parent:t}],r=new RefSet;r.put(e);const i=this.xref,n=new Uint8ClampedArray(3);for(;a.length>0;){const t=a.shift(),s=i.fetchIfRef(t.obj);if(null===s)continue;s.has("Title")||warn("Invalid outline item encountered.");const o={url:null,dest:null,action:null};Catalog.parseDestDictionary({destDict:s,resultObj:o,docBaseUrl:this.baseUrl,docAttachments:this.attachments});const c=s.get("Title"),l=s.get("F")||0,h=s.getArray("C"),u=s.get("Count");let d=n;!isNumberArray(h,3)||0===h[0]&&0===h[1]&&0===h[2]||(d=ColorSpaceUtils.rgb.getRgb(h,0));const f={action:o.action,attachment:o.attachment,dest:o.dest,url:o.url,unsafeUrl:o.unsafeUrl,newWindow:o.newWindow,setOCGState:o.setOCGState,title:"string"==typeof c?stringToPDFString(c):"",color:d,count:Number.isInteger(u)?u:void 0,bold:!!(2&l),italic:!!(1&l),items:[]};t.parent.items.push(f);e=s.getRaw("First");if(e instanceof Ref&&!r.has(e)){a.push({obj:e,parent:f});r.put(e)}e=s.getRaw("Next");if(e instanceof Ref&&!r.has(e)){a.push({obj:e,parent:t.parent});r.put(e)}}return t.items.length>0?t.items:null}get permissions(){let e=null;try{e=this._readPermissions()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read permissions.")}return shadow(this,"permissions",e)}_readPermissions(){const e=this.xref.trailer.get("Encrypt");if(!(e instanceof Dict))return null;let t=e.get("P");if("number"!=typeof t)return null;t+=2**32;const a=[];for(const e in w){const r=w[e];t&r&&a.push(r)}return a}get optionalContentConfig(){let e=null;try{const t=this._catDict.get("OCProperties");if(!t)return shadow(this,"optionalContentConfig",null);const a=t.get("D");if(!a)return shadow(this,"optionalContentConfig",null);const r=t.get("OCGs");if(!Array.isArray(r))return shadow(this,"optionalContentConfig",null);const i=new RefSetCache;for(const e of r)e instanceof Ref&&!i.has(e)&&i.put(e,this.#ae(e));e=this.#re(a,i)}catch(e){if(e instanceof MissingDataException)throw e;warn(`Unable to read optional content config: ${e}`)}return shadow(this,"optionalContentConfig",e)}#ae(e){const t=this.xref.fetch(e),a={id:e.toString(),name:null,intent:null,usage:{print:null,view:null},rbGroups:[]},r=t.get("Name");"string"==typeof r&&(a.name=stringToPDFString(r));let i=t.getArray("Intent");Array.isArray(i)||(i=[i]);i.every((e=>e instanceof Name))&&(a.intent=i.map((e=>e.name)));const n=t.get("Usage");if(!(n instanceof Dict))return a;const s=a.usage,o=n.get("Print");if(o instanceof Dict){const e=o.get("PrintState");if(e instanceof Name)switch(e.name){case"ON":case"OFF":s.print={printState:e.name}}}const c=n.get("View");if(c instanceof Dict){const e=c.get("ViewState");if(e instanceof Name)switch(e.name){case"ON":case"OFF":s.view={viewState:e.name}}}return a}#re(e,t){function parseOnOff(e){const a=[];if(Array.isArray(e))for(const r of e)r instanceof Ref&&t.has(r)&&a.push(r.toString());return a}function parseOrder(e,a=0){if(!Array.isArray(e))return null;const i=[];for(const n of e){if(n instanceof Ref&&t.has(n)){r.put(n);i.push(n.toString());continue}const e=parseNestedOrder(n,a);e&&i.push(e)}if(a>0)return i;const n=[];for(const[e]of t.items())r.has(e)||n.push(e.toString());n.length&&i.push({name:null,order:n});return i}function parseNestedOrder(e,t){if(++t>i){warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");return null}const r=a.fetchIfRef(e);if(!Array.isArray(r))return null;const n=a.fetchIfRef(r[0]);if("string"!=typeof n)return null;const s=parseOrder(r.slice(1),t);return s?.length?{name:stringToPDFString(n),order:s}:null}const a=this.xref,r=new RefSet,i=10;!function parseRBGroups(e){if(Array.isArray(e))for(const r of e){const e=a.fetchIfRef(r);if(!Array.isArray(e)||!e.length)continue;const i=new Set;for(const a of e)if(a instanceof Ref&&t.has(a)&&!i.has(a.toString())){i.add(a.toString());t.get(a).rbGroups.push(i)}}}(e.get("RBGroups"));return{name:"string"==typeof e.get("Name")?stringToPDFString(e.get("Name")):null,creator:"string"==typeof e.get("Creator")?stringToPDFString(e.get("Creator")):null,baseState:e.get("BaseState")instanceof Name?e.get("BaseState").name:null,on:parseOnOff(e.get("ON")),off:parseOnOff(e.get("OFF")),order:parseOrder(e.get("Order")),groups:[...t]}}setActualNumPages(e=null){this._actualNumPages=e}get hasActualNumPages(){return null!==this._actualNumPages}get _pagesCount(){const e=this.toplevelPagesDict.get("Count");if(!Number.isInteger(e))throw new FormatError("Page count in top-level pages dictionary is not an integer.");return shadow(this,"_pagesCount",e)}get numPages(){return this.hasActualNumPages?this._actualNumPages:this._pagesCount}get destinations(){const e=this.#ie(),t=Object.create(null);for(const a of e)if(a instanceof NameTree)for(const[e,r]of a.getAll()){const a=fetchDest(r);a&&(t[stringToPDFString(e)]=a)}else if(a instanceof Dict)for(const[e,r]of a){const a=fetchDest(r);a&&(t[stringToPDFString(e)]||=a)}return shadow(this,"destinations",t)}getDestination(e){const t=this.#ie();for(const a of t)if(a instanceof NameTree||a instanceof Dict){const t=fetchDest(a.get(e));if(t)return t}if(t.length){const t=this.destinations[e];if(t)return t}return null}#ie(){const e=this._catDict.get("Names"),t=[];e?.has("Dests")&&t.push(new NameTree(e.getRaw("Dests"),this.xref));this._catDict.has("Dests")&&t.push(this._catDict.get("Dests"));return t}get pageLabels(){let e=null;try{e=this._readPageLabels()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read page labels.")}return shadow(this,"pageLabels",e)}_readPageLabels(){const e=this._catDict.getRaw("PageLabels");if(!e)return null;const t=new Array(this.numPages);let a=null,r="";const i=new NumberTree(e,this.xref).getAll();let n="",s=1;for(let e=0,o=this.numPages;e=1))throw new FormatError("Invalid start in PageLabel dictionary.");s=e}else s=1}switch(a){case"D":n=s;break;case"R":case"r":n=toRomanNumerals(s,"r"===a);break;case"A":case"a":const e=26,t="a"===a?97:65,r=s-1;n=String.fromCharCode(t+r%e).repeat(Math.floor(r/e)+1);break;default:if(a)throw new FormatError(`Invalid style "${a}" in PageLabel dictionary.`);n=""}t[e]=r+n;s++}return t}get pageLayout(){const e=this._catDict.get("PageLayout");let t="";if(e instanceof Name)switch(e.name){case"SinglePage":case"OneColumn":case"TwoColumnLeft":case"TwoColumnRight":case"TwoPageLeft":case"TwoPageRight":t=e.name}return shadow(this,"pageLayout",t)}get pageMode(){const e=this._catDict.get("PageMode");let t="UseNone";if(e instanceof Name)switch(e.name){case"UseNone":case"UseOutlines":case"UseThumbs":case"FullScreen":case"UseOC":case"UseAttachments":t=e.name}return shadow(this,"pageMode",t)}get viewerPreferences(){const e=this._catDict.get("ViewerPreferences");if(!(e instanceof Dict))return shadow(this,"viewerPreferences",null);let t=null;for(const[a,r]of e){let e;switch(a){case"HideToolbar":case"HideMenubar":case"HideWindowUI":case"FitWindow":case"CenterWindow":case"DisplayDocTitle":case"PickTrayByPDFSize":"boolean"==typeof r&&(e=r);break;case"NonFullScreenPageMode":if(r instanceof Name)switch(r.name){case"UseNone":case"UseOutlines":case"UseThumbs":case"UseOC":e=r.name;break;default:e="UseNone"}break;case"Direction":if(r instanceof Name)switch(r.name){case"L2R":case"R2L":e=r.name;break;default:e="L2R"}break;case"ViewArea":case"ViewClip":case"PrintArea":case"PrintClip":if(r instanceof Name)switch(r.name){case"MediaBox":case"CropBox":case"BleedBox":case"TrimBox":case"ArtBox":e=r.name;break;default:e="CropBox"}break;case"PrintScaling":if(r instanceof Name)switch(r.name){case"None":case"AppDefault":e=r.name;break;default:e="AppDefault"}break;case"Duplex":if(r instanceof Name)switch(r.name){case"Simplex":case"DuplexFlipShortEdge":case"DuplexFlipLongEdge":e=r.name;break;default:e="None"}break;case"PrintPageRange":if(Array.isArray(r)&&r.length%2==0){r.every(((e,t,a)=>Number.isInteger(e)&&e>0&&(0===t||e>=a[t-1])&&e<=this.numPages))&&(e=r)}break;case"NumCopies":Number.isInteger(r)&&r>0&&(e=r);break;default:warn(`Ignoring non-standard key in ViewerPreferences: ${a}.`);continue}if(void 0!==e){t??=Object.create(null);t[a]=e}else warn(`Bad value, for key "${a}", in ViewerPreferences: ${r}.`)}return shadow(this,"viewerPreferences",t)}get openAction(){const e=this._catDict.get("OpenAction"),t=Object.create(null);if(e instanceof Dict){const a=new Dict(this.xref);a.set("A",e);const r={url:null,dest:null,action:null};Catalog.parseDestDictionary({destDict:a,resultObj:r});Array.isArray(r.dest)?t.dest=r.dest:r.action&&(t.action=r.action)}else Array.isArray(e)&&(t.dest=e);return shadow(this,"openAction",objectSize(t)>0?t:null)}get attachments(){const e=this._catDict.get("Names");let t=null;if(e instanceof Dict&&e.has("EmbeddedFiles")){const a=new NameTree(e.getRaw("EmbeddedFiles"),this.xref);for(const[e,r]of a.getAll()){const a=new FileSpec(r,this.xref);t??=Object.create(null);t[stringToPDFString(e)]=a.serializable}}return shadow(this,"attachments",t)}get xfaImages(){const e=this._catDict.get("Names");let t=null;if(e instanceof Dict&&e.has("XFAImages")){const a=new NameTree(e.getRaw("XFAImages"),this.xref);for(const[e,r]of a.getAll()){t??=new Dict(this.xref);t.set(stringToPDFString(e),r)}}return shadow(this,"xfaImages",t)}_collectJavaScript(){const e=this._catDict.get("Names");let t=null;function appendIfJavaScriptDict(e,a){if(!(a instanceof Dict))return;if(!isName(a.get("S"),"JavaScript"))return;let r=a.get("JS");if(r instanceof BaseStream)r=r.getString();else if("string"!=typeof r)return;r=stringToPDFString(r).replaceAll("\0","");r&&(t||=new Map).set(e,r)}if(e instanceof Dict&&e.has("JavaScript")){const t=new NameTree(e.getRaw("JavaScript"),this.xref);for(const[e,a]of t.getAll())appendIfJavaScriptDict(stringToPDFString(e),a)}const a=this._catDict.get("OpenAction");a&&appendIfJavaScriptDict("OpenAction",a);return t}get jsActions(){const e=this._collectJavaScript();let t=collectActions(this.xref,this._catDict,we);if(e){t||=Object.create(null);for(const[a,r]of e)a in t?t[a].push(r):t[a]=[r]}return shadow(this,"jsActions",t)}async cleanup(e=!1){clearGlobalCaches();this.globalColorSpaceCache.clear();this.globalImageCache.clear(e);this.pageKidsCountCache.clear();this.pageIndexCache.clear();this.pageDictCache.clear();this.nonBlendModesSet.clear();for(const{dict:e}of await Promise.all(this.fontCache))delete e.cacheKey;this.fontCache.clear();this.builtInCMapCache.clear();this.standardFontDataCache.clear();this.systemFontCache.clear()}async getPageDict(e){const t=[this.toplevelPagesDict],a=new RefSet,r=this._catDict.getRaw("Pages");r instanceof Ref&&a.put(r);const i=this.xref,n=this.pageKidsCountCache,s=this.pageIndexCache,o=this.pageDictCache;let c=0;for(;t.length;){const r=t.pop();if(r instanceof Ref){const l=n.get(r);if(l>=0&&c+l<=e){c+=l;continue}if(a.has(r))throw new FormatError("Pages tree contains circular reference.");a.put(r);const h=await(o.get(r)||i.fetchAsync(r));if(h instanceof Dict){let t=h.getRaw("Type");t instanceof Ref&&(t=await i.fetchAsync(t));if(isName(t,"Page")||!h.has("Kids")){n.has(r)||n.put(r,1);s.has(r)||s.put(r,c);if(c===e)return[h,r];c++;continue}}t.push(h);continue}if(!(r instanceof Dict))throw new FormatError("Page dictionary kid reference points to wrong type of object.");const{objId:l}=r;let h=r.getRaw("Count");h instanceof Ref&&(h=await i.fetchAsync(h));if(Number.isInteger(h)&&h>=0){l&&!n.has(l)&&n.put(l,h);if(c+h<=e){c+=h;continue}}let u=r.getRaw("Kids");u instanceof Ref&&(u=await i.fetchAsync(u));if(!Array.isArray(u)){let t=r.getRaw("Type");t instanceof Ref&&(t=await i.fetchAsync(t));if(isName(t,"Page")||!r.has("Kids")){if(c===e)return[r,null];c++;continue}throw new FormatError("Page dictionary kids object is not an array.")}for(let e=u.length-1;e>=0;e--){const a=u[e];t.push(a);r===this.toplevelPagesDict&&a instanceof Ref&&!o.has(a)&&o.put(a,i.fetchAsync(a))}}throw new Error(`Page index ${e} not found.`)}async getAllPageDicts(e=!1){const{ignoreErrors:t}=this.pdfManager.evaluatorOptions,a=[{currentNode:this.toplevelPagesDict,posInKids:0}],r=new RefSet,i=this._catDict.getRaw("Pages");i instanceof Ref&&r.put(i);const n=new Map,s=this.xref,o=this.pageIndexCache;let c=0;function addPageDict(e,t){t&&!o.has(t)&&o.put(t,c);n.set(c++,[e,t])}function addPageError(a){if(a instanceof XRefEntryException&&!e)throw a;if(e&&t&&0===c){warn(`getAllPageDicts - Skipping invalid first page: "${a}".`);a=Dict.empty}n.set(c++,[a,null])}for(;a.length>0;){const e=a.at(-1),{currentNode:t,posInKids:i}=e;let n=t.getRaw("Kids");if(n instanceof Ref)try{n=await s.fetchAsync(n)}catch(e){addPageError(e);break}if(!Array.isArray(n)){addPageError(new FormatError("Page dictionary kids object is not an array."));break}if(i>=n.length){a.pop();continue}const o=n[i];let c;if(o instanceof Ref){if(r.has(o)){addPageError(new FormatError("Pages tree contains circular reference."));break}r.put(o);try{c=await s.fetchAsync(o)}catch(e){addPageError(e);break}}else c=o;if(!(c instanceof Dict)){addPageError(new FormatError("Page dictionary kid reference points to wrong type of object."));break}let l=c.getRaw("Type");if(l instanceof Ref)try{l=await s.fetchAsync(l)}catch(e){addPageError(e);break}isName(l,"Page")||!c.has("Kids")?addPageDict(c,o instanceof Ref?o:null):a.push({currentNode:c,posInKids:0});e.posInKids++}return n}getPageIndex(e){const t=this.pageIndexCache.get(e);if(void 0!==t)return Promise.resolve(t);const a=this.xref;let r=0;const next=t=>function pagesBeforeRef(t){let r,i=0;return a.fetchAsync(t).then((function(a){if(isRefsEqual(t,e)&&!isDict(a,"Page")&&!(a instanceof Dict&&!a.has("Type")&&a.has("Contents")))throw new FormatError("The reference does not point to a /Page dictionary.");if(!a)return null;if(!(a instanceof Dict))throw new FormatError("Node must be a dictionary.");r=a.getRaw("Parent");return a.getAsync("Parent")})).then((function(e){if(!e)return null;if(!(e instanceof Dict))throw new FormatError("Parent must be a dictionary.");return e.getAsync("Kids")})).then((function(e){if(!e)return null;const n=[];let s=!1;for(const r of e){if(!(r instanceof Ref))throw new FormatError("Kid must be a reference.");if(isRefsEqual(r,t)){s=!0;break}n.push(a.fetchAsync(r).then((function(e){if(!(e instanceof Dict))throw new FormatError("Kid node must be a dictionary.");e.has("Count")?i+=e.get("Count"):i++})))}if(!s)throw new FormatError("Kid reference not found in parent's kids.");return Promise.all(n).then((()=>[i,r]))}))}(t).then((t=>{if(!t){this.pageIndexCache.put(e,r);return r}const[a,i]=t;r+=a;return next(i)}));return next(e)}get baseUrl(){const e=this._catDict.get("URI");if(e instanceof Dict){const t=e.get("Base");if("string"==typeof t){const e=createValidAbsoluteUrl(t,null,{tryConvertEncoding:!0});if(e)return shadow(this,"baseUrl",e.href)}}return shadow(this,"baseUrl",this.pdfManager.docBaseUrl)}static parseDestDictionary({destDict:e,resultObj:t,docBaseUrl:a=null,docAttachments:r=null}){if(!(e instanceof Dict)){warn("parseDestDictionary: `destDict` must be a dictionary.");return}let i,n,s=e.get("A");if(!(s instanceof Dict))if(e.has("Dest"))s=e.get("Dest");else{s=e.get("AA");s instanceof Dict&&(s.has("D")?s=s.get("D"):s.has("U")&&(s=s.get("U")))}if(s instanceof Dict){const e=s.get("S");if(!(e instanceof Name)){warn("parseDestDictionary: Invalid type in Action dictionary.");return}const a=e.name;switch(a){case"ResetForm":const e=s.get("Flags"),o=!(1&("number"==typeof e?e:0)),c=[],l=[];for(const e of s.get("Fields")||[])e instanceof Ref?l.push(e.toString()):"string"==typeof e&&c.push(stringToPDFString(e));t.resetForm={fields:c,refs:l,include:o};break;case"URI":i=s.get("URI");i instanceof Name&&(i="/"+i.name);break;case"GoTo":n=s.get("D");break;case"Launch":case"GoToR":const h=s.get("F");if(h instanceof Dict){const e=new FileSpec(h,null,!0),{rawFilename:t}=e.serializable;i=t}else"string"==typeof h&&(i=h);const u=fetchRemoteDest(s);u&&"string"==typeof i&&(i=i.split("#",1)[0]+"#"+u);const d=s.get("NewWindow");"boolean"==typeof d&&(t.newWindow=d);break;case"GoToE":const f=s.get("T");let g;if(r&&f instanceof Dict){const e=f.get("R"),t=f.get("N");isName(e,"C")&&"string"==typeof t&&(g=r[stringToPDFString(t)])}if(g){t.attachment=g;const e=fetchRemoteDest(s);e&&(t.attachmentDest=e)}else warn('parseDestDictionary - unimplemented "GoToE" action.');break;case"Named":const p=s.get("N");p instanceof Name&&(t.action=p.name);break;case"SetOCGState":const m=s.get("State"),b=s.get("PreserveRB");if(!Array.isArray(m)||0===m.length)break;const y=[];for(const e of m)if(e instanceof Name)switch(e.name){case"ON":case"OFF":case"Toggle":y.push(e.name)}else e instanceof Ref&&y.push(e.toString());if(y.length!==m.length)break;t.setOCGState={state:y,preserveRB:"boolean"!=typeof b||b};break;case"JavaScript":const w=s.get("JS");let S;w instanceof BaseStream?S=w.getString():"string"==typeof w&&(S=w);const x=S&&recoverJsURL(stringToPDFString(S));if(x){i=x.url;t.newWindow=x.newWindow;break}default:if("JavaScript"===a||"SubmitForm"===a)break;warn(`parseDestDictionary - unsupported action: "${a}".`)}}else e.has("Dest")&&(n=e.get("Dest"));if("string"==typeof i){const e=createValidAbsoluteUrl(i,a,{addDefaultProtocol:!0,tryConvertEncoding:!0});e&&(t.url=e.href);t.unsafeUrl=i}if(n){n instanceof Name&&(n=n.name);"string"==typeof n?t.dest=stringToPDFString(n):Jn(n)&&(t.dest=n)}}}function addChildren(e,t){if(e instanceof Dict)e=e.getRawValues();else if(e instanceof BaseStream)e=e.dict.getRawValues();else if(!Array.isArray(e))return;for(const r of e)((a=r)instanceof Ref||a instanceof Dict||a instanceof BaseStream||Array.isArray(a))&&t.push(r);var a}class ObjectLoader{constructor(e,t,a){this.dict=e;this.keys=t;this.xref=a;this.refSet=null}async load(){if(this.xref.stream.isDataLoaded)return;const{keys:e,dict:t}=this;this.refSet=new RefSet;const a=[];for(const r of e){const e=t.getRaw(r);void 0!==e&&a.push(e)}return this._walk(a)}async _walk(e){const t=[],a=[];for(;e.length;){let r=e.pop();if(r instanceof Ref){if(this.refSet.has(r))continue;try{this.refSet.put(r);r=this.xref.fetch(r)}catch(e){if(!(e instanceof MissingDataException)){warn(`ObjectLoader._walk - requesting all data: "${e}".`);this.refSet=null;const{manager:t}=this.xref.stream;return t.requestAllChunks()}t.push(r);a.push({begin:e.begin,end:e.end})}}if(r instanceof BaseStream){const e=r.getBaseStreams();if(e){let i=!1;for(const t of e)if(!t.isDataLoaded){i=!0;a.push({begin:t.start,end:t.end})}i&&t.push(r)}}addChildren(r,e)}if(a.length){await this.xref.stream.manager.requestRanges(a);for(const e of t)e instanceof Ref&&this.refSet.remove(e);return this._walk(t)}this.refSet=null}}const Yn=Symbol(),Zn=Symbol(),Qn=Symbol(),es=Symbol(),ts=Symbol(),as=Symbol(),rs=Symbol(),is=Symbol(),ns=Symbol(),ss=Symbol("content"),os=Symbol("data"),cs=Symbol(),ls=Symbol("extra"),hs=Symbol(),us=Symbol(),ds=Symbol(),fs=Symbol(),gs=Symbol(),ps=Symbol(),ms=Symbol(),bs=Symbol(),ys=Symbol(),ws=Symbol(),Ss=Symbol(),xs=Symbol(),As=Symbol(),ks=Symbol(),Cs=Symbol(),vs=Symbol(),Fs=Symbol(),Is=Symbol(),Os=Symbol(),Ts=Symbol(),Ms=Symbol(),Ds=Symbol(),Bs=Symbol(),Rs=Symbol(),Ns=Symbol(),Es=Symbol(),Ls=Symbol(),_s=Symbol(),js=Symbol(),Us=Symbol(),Xs=Symbol(),qs=Symbol(),Hs=Symbol("namespaceId"),Ws=Symbol("nodeName"),zs=Symbol(),$s=Symbol(),Gs=Symbol(),Vs=Symbol(),Ks=Symbol(),Js=Symbol(),Ys=Symbol(),Zs=Symbol(),Qs=Symbol("root"),eo=Symbol(),to=Symbol(),ao=Symbol(),ro=Symbol(),io=Symbol(),no=Symbol(),so=Symbol(),oo=Symbol(),co=Symbol(),lo=Symbol(),ho=Symbol(),uo=Symbol("uid"),fo=Symbol(),go={config:{id:0,check:e=>e.startsWith("http://www.xfa.org/schema/xci/")},connectionSet:{id:1,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-connection-set/")},datasets:{id:2,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-data/")},form:{id:3,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-form/")},localeSet:{id:4,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-locale-set/")},pdf:{id:5,check:e=>"http://ns.adobe.com/xdp/pdf/"===e},signature:{id:6,check:e=>"http://www.w3.org/2000/09/xmldsig#"===e},sourceSet:{id:7,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-source-set/")},stylesheet:{id:8,check:e=>"http://www.w3.org/1999/XSL/Transform"===e},template:{id:9,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-template/")},xdc:{id:10,check:e=>e.startsWith("http://www.xfa.org/schema/xdc/")},xdp:{id:11,check:e=>"http://ns.adobe.com/xdp/"===e},xfdf:{id:12,check:e=>"http://ns.adobe.com/xfdf/"===e},xhtml:{id:13,check:e=>"http://www.w3.org/1999/xhtml"===e},xmpmeta:{id:14,check:e=>"http://ns.adobe.com/xmpmeta/"===e}},po={pt:e=>e,cm:e=>e/2.54*72,mm:e=>e/25.4*72,in:e=>72*e,px:e=>e},mo=/([+-]?\d+\.?\d*)(.*)/;function stripQuotes(e){return e.startsWith("'")||e.startsWith('"')?e.slice(1,-1):e}function getInteger({data:e,defaultValue:t,validate:a}){if(!e)return t;e=e.trim();const r=parseInt(e,10);return!isNaN(r)&&a(r)?r:t}function getFloat({data:e,defaultValue:t,validate:a}){if(!e)return t;e=e.trim();const r=parseFloat(e);return!isNaN(r)&&a(r)?r:t}function getKeyword({data:e,defaultValue:t,validate:a}){return e&&a(e=e.trim())?e:t}function getStringOption(e,t){return getKeyword({data:e,defaultValue:t[0],validate:e=>t.includes(e)})}function getMeasurement(e,t="0"){t||="0";if(!e)return getMeasurement(t);const a=e.trim().match(mo);if(!a)return getMeasurement(t);const[,r,i]=a,n=parseFloat(r);if(isNaN(n))return getMeasurement(t);if(0===n)return 0;const s=po[i];return s?s(n):n}function getRatio(e){if(!e)return{num:1,den:1};const t=e.split(":",2).map((e=>parseFloat(e.trim()))).filter((e=>!isNaN(e)));1===t.length&&t.push(1);if(0===t.length)return{num:1,den:1};const[a,r]=t;return{num:a,den:r}}function getRelevant(e){return e?e.trim().split(/\s+/).map((e=>({excluded:"-"===e[0],viewname:e.substring(1)}))):[]}class HTMLResult{static get FAILURE(){return shadow(this,"FAILURE",new HTMLResult(!1,null,null,null))}static get EMPTY(){return shadow(this,"EMPTY",new HTMLResult(!0,null,null,null))}constructor(e,t,a,r){this.success=e;this.html=t;this.bbox=a;this.breakNode=r}isBreak(){return!!this.breakNode}static breakNode(e){return new HTMLResult(!1,null,null,e)}static success(e,t=null){return new HTMLResult(!0,e,t,null)}}class FontFinder{constructor(e){this.fonts=new Map;this.cache=new Map;this.warned=new Set;this.defaultFont=null;this.add(e)}add(e,t=null){for(const t of e)this.addPdfFont(t);for(const e of this.fonts.values())e.regular||(e.regular=e.italic||e.bold||e.bolditalic);if(!t||0===t.size)return;const a=this.fonts.get("PdfJS-Fallback-PdfJS-XFA");for(const e of t)this.fonts.set(e,a)}addPdfFont(e){const t=e.cssFontInfo,a=t.fontFamily;let r=this.fonts.get(a);if(!r){r=Object.create(null);this.fonts.set(a,r);this.defaultFont||(this.defaultFont=r)}let i="";const n=parseFloat(t.fontWeight);0!==parseFloat(t.italicAngle)?i=n>=700?"bolditalic":"italic":n>=700&&(i="bold");if(!i){(e.name.includes("Bold")||e.psName?.includes("Bold"))&&(i="bold");(e.name.includes("Italic")||e.name.endsWith("It")||e.psName?.includes("Italic")||e.psName?.endsWith("It"))&&(i+="italic")}i||(i="regular");r[i]=e}getDefault(){return this.defaultFont}find(e,t=!0){let a=this.fonts.get(e)||this.cache.get(e);if(a)return a;const r=/,|-|_| |bolditalic|bold|italic|regular|it/gi;let i=e.replaceAll(r,"");a=this.fonts.get(i);if(a){this.cache.set(e,a);return a}i=i.toLowerCase();const n=[];for(const[e,t]of this.fonts.entries())e.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(t);if(0===n.length)for(const[,e]of this.fonts.entries())e.regular.name?.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(e);if(0===n.length){i=i.replaceAll(/psmt|mt/gi,"");for(const[e,t]of this.fonts.entries())e.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(t)}if(0===n.length)for(const e of this.fonts.values())e.regular.name?.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(e);if(n.length>=1){1!==n.length&&t&&warn(`XFA - Too many choices to guess the correct font: ${e}`);this.cache.set(e,n[0]);return n[0]}if(t&&!this.warned.has(e)){this.warned.add(e);warn(`XFA - Cannot find the font: ${e}`)}return null}}function selectFont(e,t){return"italic"===e.posture?"bold"===e.weight?t.bolditalic:t.italic:"bold"===e.weight?t.bold:t.regular}class FontInfo{constructor(e,t,a,r){this.lineHeight=a;this.paraMargin=t||{top:0,bottom:0,left:0,right:0};if(!e){[this.pdfFont,this.xfaFont]=this.defaultFont(r);return}this.xfaFont={typeface:e.typeface,posture:e.posture,weight:e.weight,size:e.size,letterSpacing:e.letterSpacing};const i=r.find(e.typeface);if(i){this.pdfFont=selectFont(e,i);this.pdfFont||([this.pdfFont,this.xfaFont]=this.defaultFont(r))}else[this.pdfFont,this.xfaFont]=this.defaultFont(r)}defaultFont(e){const t=e.find("Helvetica",!1)||e.find("Myriad Pro",!1)||e.find("Arial",!1)||e.getDefault();if(t?.regular){const e=t.regular;return[e,{typeface:e.cssFontInfo.fontFamily,posture:"normal",weight:"normal",size:10,letterSpacing:0}]}return[null,{typeface:"Courier",posture:"normal",weight:"normal",size:10,letterSpacing:0}]}}class FontSelector{constructor(e,t,a,r){this.fontFinder=r;this.stack=[new FontInfo(e,t,a,r)]}pushData(e,t,a){const r=this.stack.at(-1);for(const t of["typeface","posture","weight","size","letterSpacing"])e[t]||(e[t]=r.xfaFont[t]);for(const e of["top","bottom","left","right"])isNaN(t[e])&&(t[e]=r.paraMargin[e]);const i=new FontInfo(e,t,a||r.lineHeight,this.fontFinder);i.pdfFont||(i.pdfFont=r.pdfFont);this.stack.push(i)}popFont(){this.stack.pop()}topFont(){return this.stack.at(-1)}}class TextMeasure{constructor(e,t,a,r){this.glyphs=[];this.fontSelector=new FontSelector(e,t,a,r);this.extraHeight=0}pushData(e,t,a){this.fontSelector.pushData(e,t,a)}popFont(e){return this.fontSelector.popFont()}addPara(){const e=this.fontSelector.topFont();this.extraHeight+=e.paraMargin.top+e.paraMargin.bottom}addString(e){if(!e)return;const t=this.fontSelector.topFont(),a=t.xfaFont.size;if(t.pdfFont){const r=t.xfaFont.letterSpacing,i=t.pdfFont,n=i.lineHeight||1.2,s=t.lineHeight||Math.max(1.2,n)*a,o=n-(void 0===i.lineGap?.2:i.lineGap),c=Math.max(1,o)*a,l=a/1e3,h=i.defaultWidth||i.charsToGlyphs(" ")[0].width;for(const t of e.split(/[\u2029\n]/)){const e=i.encodeString(t).join(""),a=i.charsToGlyphs(e);for(const e of a){const t=e.width||h;this.glyphs.push([t*l+r,s,c,e.unicode,!1])}this.glyphs.push([0,0,0,"\n",!0])}this.glyphs.pop()}else{for(const t of e.split(/[\u2029\n]/)){for(const e of t.split(""))this.glyphs.push([a,1.2*a,a,e,!1]);this.glyphs.push([0,0,0,"\n",!0])}this.glyphs.pop()}}compute(e){let t=-1,a=0,r=0,i=0,n=0,s=0,o=!1,c=!0;for(let l=0,h=this.glyphs.length;le){r=Math.max(r,n);n=0;i+=s;s=m;t=-1;a=0;o=!0;c=!1}else{s=Math.max(m,s);a=n;n+=h;t=l}else if(n+h>e){i+=s;s=m;if(-1!==t){l=t;r=Math.max(r,a);n=0;t=-1;a=0}else{r=Math.max(r,n);n=h}o=!0;c=!1}else{n+=h;s=Math.max(m,s)}}r=Math.max(r,n);i+=s+this.extraHeight;return{width:1.02*r,height:i,isBroken:o}}}const bo=/^[^.[]+/,yo=/^[^\]]+/,wo=0,So=1,xo=2,Ao=3,ko=4,Co=new Map([["$data",(e,t)=>e.datasets?e.datasets.data:e],["$record",(e,t)=>(e.datasets?e.datasets.data:e)[xs]()[0]],["$template",(e,t)=>e.template],["$connectionSet",(e,t)=>e.connectionSet],["$form",(e,t)=>e.form],["$layout",(e,t)=>e.layout],["$host",(e,t)=>e.host],["$dataWindow",(e,t)=>e.dataWindow],["$event",(e,t)=>e.event],["!",(e,t)=>e.datasets],["$xfa",(e,t)=>e],["xfa",(e,t)=>e],["$",(e,t)=>t]]),vo=new WeakMap;function parseExpression(e,t,a=!0){let r=e.match(bo);if(!r)return null;let[i]=r;const n=[{name:i,cacheName:"."+i,index:0,js:null,formCalc:null,operator:wo}];let s=i.length;for(;s0&&h.push(e)}if(0!==h.length||o||0!==c)e=isFinite(l)?h.filter((e=>le[l])):h.flat();else{const a=t[vs]();if(!(t=a))return null;c=-1;e=[t]}}return 0===e.length?null:e}function createDataNode(e,t,a){const r=parseExpression(a);if(!r)return null;if(r.some((e=>e.operator===So)))return null;const i=Co.get(r[0].name);let n=0;if(i){e=i(e,t);n=1}else e=t||e;for(let t=r.length;ne[so]())).join("")}get[Oo](){const e=Object.getPrototypeOf(this);if(!e._attributes){const t=e._attributes=new Set;for(const e of Object.getOwnPropertyNames(this)){if(null===this[e]||this[e]instanceof XFAObject||this[e]instanceof XFAObjectArray)break;t.add(e)}}return shadow(this,Oo,e._attributes)}[Es](e){let t=this;for(;t;){if(t===e)return!0;t=t[vs]()}return!1}[vs](){return this[Uo]}[Cs](){return this[vs]()}[xs](e=null){return e?this[e]:this[Mo]}[cs](){const e=Object.create(null);this[ss]&&(e.$content=this[ss]);for(const t of Object.getOwnPropertyNames(this)){const a=this[t];null!==a&&(a instanceof XFAObject?e[t]=a[cs]():a instanceof XFAObjectArray?a.isEmpty()||(e[t]=a.dump()):e[t]=a)}return e}[ho](){return null}[co](){return HTMLResult.EMPTY}*[As](){for(const e of this[xs]())yield e}*[No](e,t){for(const a of this[As]())if(!e||t===e.has(a[Ws])){const e=this[gs](),t=a[co](e);t.success||(this[ls].failingNode=a);yield t}}[us](){return null}[Zn](e,t){this[ls].children.push(e)}[gs](){}[es]({filter:e=null,include:t=!0}){if(this[ls].generator){const e=this[gs](),t=this[ls].failingNode[co](e);if(!t.success)return t;t.html&&this[Zn](t.html,t.bbox);delete this[ls].failingNode}else this[ls].generator=this[No](e,t);for(;;){const e=this[ls].generator.next();if(e.done)break;const t=e.value;if(!t.success)return t;t.html&&this[Zn](t.html,t.bbox)}this[ls].generator=null;return HTMLResult.EMPTY}[ro](e){this[qo]=new Set(Object.keys(e))}[Po](e){const t=this[Oo],a=this[qo];return[...e].filter((e=>t.has(e)&&!a.has(e)))}[eo](e,t=new Set){for(const a of this[Mo])a[Xo](e,t)}[Xo](e,t){const a=this[Eo](e,t);a?this[Fo](a,e,t):this[eo](e,t)}[Eo](e,t){const{use:a,usehref:r}=this;if(!a&&!r)return null;let i=null,n=null,s=null,o=a;if(r){o=r;r.startsWith("#som(")&&r.endsWith(")")?n=r.slice(5,-1):r.startsWith(".#som(")&&r.endsWith(")")?n=r.slice(6,-1):r.startsWith("#")?s=r.slice(1):r.startsWith(".#")&&(s=r.slice(2))}else a.startsWith("#")?s=a.slice(1):n=a;this.use=this.usehref="";if(s)i=e.get(s);else{i=searchNode(e.get(Qs),this,n,!0,!1);i&&(i=i[0])}if(!i){warn(`XFA - Invalid prototype reference: ${o}.`);return null}if(i[Ws]!==this[Ws]){warn(`XFA - Incompatible prototype: ${i[Ws]} !== ${this[Ws]}.`);return null}if(t.has(i)){warn("XFA - Cycle detected in prototypes use.");return null}t.add(i);const c=i[Eo](e,t);c&&i[Fo](c,e,t);i[eo](e,t);t.delete(i);return i}[Fo](e,t,a){if(a.has(e)){warn("XFA - Cycle detected in prototypes use.");return}!this[ss]&&e[ss]&&(this[ss]=e[ss]);new Set(a).add(e);for(const t of this[Po](e[qo])){this[t]=e[t];this[qo]&&this[qo].add(t)}for(const r of Object.getOwnPropertyNames(this)){if(this[Oo].has(r))continue;const i=this[r],n=e[r];if(i instanceof XFAObjectArray){for(const e of i[Mo])e[Xo](t,a);for(let r=i[Mo].length,s=n[Mo].length;rXFAObject[Do](e))):"object"==typeof e&&null!==e?Object.assign({},e):e}[is](){const e=Object.create(Object.getPrototypeOf(this));for(const t of Object.getOwnPropertySymbols(this))try{e[t]=this[t]}catch{shadow(e,t,this[t])}e[uo]=`${e[Ws]}${Wo++}`;e[Mo]=[];for(const t of Object.getOwnPropertyNames(this)){if(this[Oo].has(t)){e[t]=XFAObject[Do](this[t]);continue}const a=this[t];e[t]=a instanceof XFAObjectArray?new XFAObjectArray(a[_o]):null}for(const t of this[Mo]){const a=t[Ws],r=t[is]();e[Mo].push(r);r[Uo]=e;null===e[a]?e[a]=r:e[a][Mo].push(r)}return e}[xs](e=null){return e?this[Mo].filter((t=>t[Ws]===e)):this[Mo]}[ps](e){return this[e]}[ms](e,t,a=!0){return Array.from(this[bs](e,t,a))}*[bs](e,t,a=!0){if("parent"!==e){for(const a of this[Mo]){a[Ws]===e&&(yield a);a.name===e&&(yield a);(t||a[Us]())&&(yield*a[bs](e,t,!1))}a&&this[Oo].has(e)&&(yield new XFAAttribute(this,e,this[e]))}else yield this[Uo]}}class XFAObjectArray{constructor(e=1/0){this[_o]=e;this[Mo]=[]}get isXFAObject(){return!1}get isXFAObjectArray(){return!0}push(e){if(this[Mo].length<=this[_o]){this[Mo].push(e);return!0}warn(`XFA - node "${e[Ws]}" accepts no more than ${this[_o]} children`);return!1}isEmpty(){return 0===this[Mo].length}dump(){return 1===this[Mo].length?this[Mo][0][cs]():this[Mo].map((e=>e[cs]()))}[is](){const e=new XFAObjectArray(this[_o]);e[Mo]=this[Mo].map((e=>e[is]()));return e}get children(){return this[Mo]}clear(){this[Mo].length=0}}class XFAAttribute{constructor(e,t,a){this[Uo]=e;this[Ws]=t;this[ss]=a;this[ns]=!1;this[uo]="attribute"+Wo++}[vs](){return this[Uo]}[Ns](){return!0}[ys](){return this[ss].trim()}[io](e){e=e.value||"";this[ss]=e.toString()}[so](){return this[ss]}[Es](e){return this[Uo]===e||this[Uo][Es](e)}}class XmlObject extends XFAObject{constructor(e,t,a={}){super(e,t);this[ss]="";this[Bo]=null;if("#text"!==t){const e=new Map;this[Io]=e;for(const[t,r]of Object.entries(a))e.set(t,new XFAAttribute(this,t,r));if(a.hasOwnProperty(zs)){const e=a[zs].xfa.dataNode;void 0!==e&&("dataGroup"===e?this[Bo]=!1:"dataValue"===e&&(this[Bo]=!0))}}this[ns]=!1}[lo](e){const t=this[Ws];if("#text"===t){e.push(encodeToXmlString(this[ss]));return}const a=utf8StringToString(t),r=this[Hs]===zo?"xfa:":"";e.push(`<${r}${a}`);for(const[t,a]of this[Io].entries()){const r=utf8StringToString(t);e.push(` ${r}="${encodeToXmlString(a[ss])}"`)}null!==this[Bo]&&(this[Bo]?e.push(' xfa:dataNode="dataValue"'):e.push(' xfa:dataNode="dataGroup"'));if(this[ss]||0!==this[Mo].length){e.push(">");if(this[ss])"string"==typeof this[ss]?e.push(encodeToXmlString(this[ss])):this[ss][lo](e);else for(const t of this[Mo])t[lo](e);e.push(``)}else e.push("/>")}[$s](e){if(this[ss]){const e=new XmlObject(this[Hs],"#text");this[Qn](e);e[ss]=this[ss];this[ss]=""}this[Qn](e);return!0}[Vs](e){this[ss]+=e}[hs](){if(this[ss]&&this[Mo].length>0){const e=new XmlObject(this[Hs],"#text");this[Qn](e);e[ss]=this[ss];delete this[ss]}}[co](){return"#text"===this[Ws]?HTMLResult.success({name:"#text",value:this[ss]}):HTMLResult.EMPTY}[xs](e=null){return e?this[Mo].filter((t=>t[Ws]===e)):this[Mo]}[fs](){return this[Io]}[ps](e){const t=this[Io].get(e);return void 0!==t?t:this[xs](e)}*[bs](e,t){const a=this[Io].get(e);a&&(yield a);for(const a of this[Mo]){a[Ws]===e&&(yield a);t&&(yield*a[bs](e,t))}}*[ds](e,t){const a=this[Io].get(e);!a||t&&a[ns]||(yield a);for(const a of this[Mo])yield*a[ds](e,t)}*[Ss](e,t,a){for(const r of this[Mo]){r[Ws]!==e||a&&r[ns]||(yield r);t&&(yield*r[Ss](e,t,a))}}[Ns](){return null===this[Bo]?0===this[Mo].length||this[Mo][0][Hs]===go.xhtml.id:this[Bo]}[ys](){return null===this[Bo]?0===this[Mo].length?this[ss].trim():this[Mo][0][Hs]===go.xhtml.id?this[Mo][0][so]().trim():null:this[ss].trim()}[io](e){e=e.value||"";this[ss]=e.toString()}[cs](e=!1){const t=Object.create(null);e&&(t.$ns=this[Hs]);this[ss]&&(t.$content=this[ss]);t.$name=this[Ws];t.children=[];for(const a of this[Mo])t.children.push(a[cs](e));t.attributes=Object.create(null);for(const[e,a]of this[Io])t.attributes[e]=a[ss];return t}}class ContentObject extends XFAObject{constructor(e,t){super(e,t);this[ss]=""}[Vs](e){this[ss]+=e}[hs](){}}class OptionObject extends ContentObject{constructor(e,t,a){super(e,t);this[jo]=a}[hs](){this[ss]=getKeyword({data:this[ss],defaultValue:this[jo][0],validate:e=>this[jo].includes(e)})}[ts](e){super[ts](e);delete this[jo]}}class StringObject extends ContentObject{[hs](){this[ss]=this[ss].trim()}}class IntegerObject extends ContentObject{constructor(e,t,a,r){super(e,t);this[Ro]=a;this[Ho]=r}[hs](){this[ss]=getInteger({data:this[ss],defaultValue:this[Ro],validate:this[Ho]})}[ts](e){super[ts](e);delete this[Ro];delete this[Ho]}}class Option01 extends IntegerObject{constructor(e,t){super(e,t,0,(e=>1===e))}}class Option10 extends IntegerObject{constructor(e,t){super(e,t,1,(e=>0===e))}}function measureToString(e){return"string"==typeof e?"0px":Number.isInteger(e)?`${e}px`:`${e.toFixed(2)}px`}const $o={anchorType(e,t){const a=e[Cs]();if(a&&(!a.layout||"position"===a.layout)){"transform"in t||(t.transform="");switch(e.anchorType){case"bottomCenter":t.transform+="translate(-50%, -100%)";break;case"bottomLeft":t.transform+="translate(0,-100%)";break;case"bottomRight":t.transform+="translate(-100%,-100%)";break;case"middleCenter":t.transform+="translate(-50%,-50%)";break;case"middleLeft":t.transform+="translate(0,-50%)";break;case"middleRight":t.transform+="translate(-100%,-50%)";break;case"topCenter":t.transform+="translate(-50%,0)";break;case"topRight":t.transform+="translate(-100%,0)"}}},dimensions(e,t){const a=e[Cs]();let r=e.w;const i=e.h;if(a.layout?.includes("row")){const t=a[ls],i=e.colSpan;let n;if(-1===i){n=Math.sumPrecise(t.columnWidths.slice(t.currentColumn));t.currentColumn=0}else{n=Math.sumPrecise(t.columnWidths.slice(t.currentColumn,t.currentColumn+i));t.currentColumn=(t.currentColumn+e.colSpan)%t.columnWidths.length}isNaN(n)||(r=e.w=n)}t.width=""!==r?measureToString(r):"auto";t.height=""!==i?measureToString(i):"auto"},position(e,t){const a=e[Cs]();if(!a?.layout||"position"===a.layout){t.position="absolute";t.left=measureToString(e.x);t.top=measureToString(e.y)}},rotate(e,t){if(e.rotate){"transform"in t||(t.transform="");t.transform+=`rotate(-${e.rotate}deg)`;t.transformOrigin="top left"}},presence(e,t){switch(e.presence){case"invisible":t.visibility="hidden";break;case"hidden":case"inactive":t.display="none"}},hAlign(e,t){if("para"===e[Ws])switch(e.hAlign){case"justifyAll":t.textAlign="justify-all";break;case"radix":t.textAlign="left";break;default:t.textAlign=e.hAlign}else switch(e.hAlign){case"left":t.alignSelf="start";break;case"center":t.alignSelf="center";break;case"right":t.alignSelf="end"}},margin(e,t){e.margin&&(t.margin=e.margin[ho]().margin)}};function setMinMaxDimensions(e,t){if("position"===e[Cs]().layout){e.minW>0&&(t.minWidth=measureToString(e.minW));e.maxW>0&&(t.maxWidth=measureToString(e.maxW));e.minH>0&&(t.minHeight=measureToString(e.minH));e.maxH>0&&(t.maxHeight=measureToString(e.maxH))}}function layoutText(e,t,a,r,i,n){const s=new TextMeasure(t,a,r,i);"string"==typeof e?s.addString(e):e[Ks](s);return s.compute(n)}function layoutNode(e,t){let a=null,r=null,i=!1;if((!e.w||!e.h)&&e.value){let n=0,s=0;if(e.margin){n=e.margin.leftInset+e.margin.rightInset;s=e.margin.topInset+e.margin.bottomInset}let o=null,c=null;if(e.para){c=Object.create(null);o=""===e.para.lineHeight?null:e.para.lineHeight;c.top=""===e.para.spaceAbove?0:e.para.spaceAbove;c.bottom=""===e.para.spaceBelow?0:e.para.spaceBelow;c.left=""===e.para.marginLeft?0:e.para.marginLeft;c.right=""===e.para.marginRight?0:e.para.marginRight}let l=e.font;if(!l){const t=e[Fs]();let a=e[vs]();for(;a&&a!==t;){if(a.font){l=a.font;break}a=a[vs]()}}const h=(e.w||t.width)-n,u=e[Is].fontFinder;if(e.value.exData&&e.value.exData[ss]&&"text/html"===e.value.exData.contentType){const t=layoutText(e.value.exData[ss],l,c,o,u,h);r=t.width;a=t.height;i=t.isBroken}else{const t=e.value[so]();if(t){const e=layoutText(t,l,c,o,u,h);r=e.width;a=e.height;i=e.isBroken}}null===r||e.w||(r+=n);null===a||e.h||(a+=s)}return{w:r,h:a,isBroken:i}}function computeBbox(e,t,a){let r;if(""!==e.w&&""!==e.h)r=[e.x,e.y,e.w,e.h];else{if(!a)return null;let i=e.w;if(""===i){if(0===e.maxW){const t=e[Cs]();i="position"===t.layout&&""!==t.w?0:e.minW}else i=Math.min(e.maxW,a.width);t.attributes.style.width=measureToString(i)}let n=e.h;if(""===n){if(0===e.maxH){const t=e[Cs]();n="position"===t.layout&&""!==t.h?0:e.minH}else n=Math.min(e.maxH,a.height);t.attributes.style.height=measureToString(n)}r=[e.x,e.y,i,n]}return r}function fixDimensions(e){const t=e[Cs]();if(t.layout?.includes("row")){const a=t[ls],r=e.colSpan;let i;i=-1===r?Math.sumPrecise(a.columnWidths.slice(a.currentColumn)):Math.sumPrecise(a.columnWidths.slice(a.currentColumn,a.currentColumn+r));isNaN(i)||(e.w=i)}t.layout&&"position"!==t.layout&&(e.x=e.y=0);"table"===e.layout&&""===e.w&&Array.isArray(e.columnWidths)&&(e.w=Math.sumPrecise(e.columnWidths))}function layoutClass(e){switch(e.layout){case"position":default:return"xfaPosition";case"lr-tb":return"xfaLrTb";case"rl-row":return"xfaRlRow";case"rl-tb":return"xfaRlTb";case"row":return"xfaRow";case"table":return"xfaTable";case"tb":return"xfaTb"}}function toStyle(e,...t){const a=Object.create(null);for(const r of t){const t=e[r];if(null!==t)if($o.hasOwnProperty(r))$o[r](e,a);else if(t instanceof XFAObject){const e=t[ho]();e?Object.assign(a,e):warn(`(DEBUG) - XFA - style for ${r} not implemented yet`)}}return a}function createWrapper(e,t){const{attributes:a}=t,{style:r}=a,i={name:"div",attributes:{class:["xfaWrapper"],style:Object.create(null)},children:[]};a.class.push("xfaWrapped");if(e.border){const{widths:a,insets:n}=e.border[ls];let s,o,c=n[0],l=n[3];const h=n[0]+n[2],u=n[1]+n[3];switch(e.border.hand){case"even":c-=a[0]/2;l-=a[3]/2;s=`calc(100% + ${(a[1]+a[3])/2-u}px)`;o=`calc(100% + ${(a[0]+a[2])/2-h}px)`;break;case"left":c-=a[0];l-=a[3];s=`calc(100% + ${a[1]+a[3]-u}px)`;o=`calc(100% + ${a[0]+a[2]-h}px)`;break;case"right":s=u?`calc(100% - ${u}px)`:"100%";o=h?`calc(100% - ${h}px)`:"100%"}const d=["xfaBorder"];isPrintOnly(e.border)&&d.push("xfaPrintOnly");const f={name:"div",attributes:{class:d,style:{top:`${c}px`,left:`${l}px`,width:s,height:o}},children:[]};for(const e of["border","borderWidth","borderColor","borderRadius","borderStyle"])if(void 0!==r[e]){f.attributes.style[e]=r[e];delete r[e]}i.children.push(f,t)}else i.children.push(t);for(const e of["background","backgroundClip","top","left","width","height","minWidth","minHeight","maxWidth","maxHeight","transform","transformOrigin","visibility"])if(void 0!==r[e]){i.attributes.style[e]=r[e];delete r[e]}i.attributes.style.position="absolute"===r.position?"absolute":"relative";delete r.position;if(r.alignSelf){i.attributes.style.alignSelf=r.alignSelf;delete r.alignSelf}return i}function fixTextIndent(e){const t=getMeasurement(e.textIndent,"0px");if(t>=0)return;const a="padding"+("left"===("right"===e.textAlign?"right":"left")?"Left":"Right"),r=getMeasurement(e[a],"0px");e[a]=r-t+"px"}function setAccess(e,t){switch(e.access){case"nonInteractive":t.push("xfaNonInteractive");break;case"readOnly":t.push("xfaReadOnly");break;case"protected":t.push("xfaDisabled")}}function isPrintOnly(e){return e.relevant.length>0&&!e.relevant[0].excluded&&"print"===e.relevant[0].viewname}function getCurrentPara(e){const t=e[Fs]()[ls].paraStack;return t.length?t.at(-1):null}function setPara(e,t,a){if(a.attributes.class?.includes("xfaRich")){if(t){""===e.h&&(t.height="auto");""===e.w&&(t.width="auto")}const r=getCurrentPara(e);if(r){const e=a.attributes.style;e.display="flex";e.flexDirection="column";switch(r.vAlign){case"top":e.justifyContent="start";break;case"bottom":e.justifyContent="end";break;case"middle":e.justifyContent="center"}const t=r[ho]();for(const[a,r]of Object.entries(t))a in e||(e[a]=r)}}}function setFontFamily(e,t,a,r){if(!a){delete r.fontFamily;return}const i=stripQuotes(e.typeface);r.fontFamily=`"${i}"`;const n=a.find(i);if(n){const{fontFamily:a}=n.regular.cssFontInfo;a!==i&&(r.fontFamily=`"${a}"`);const s=getCurrentPara(t);if(s&&""!==s.lineHeight)return;if(r.lineHeight)return;const o=selectFont(e,n);o&&(r.lineHeight=Math.max(1.2,o.lineHeight))}}function fixURL(e){const t=createValidAbsoluteUrl(e,null,{addDefaultProtocol:!0,tryConvertEncoding:!0});return t?t.href:null}function createLine(e,t){return{name:"div",attributes:{class:["lr-tb"===e.layout?"xfaLr":"xfaRl"]},children:t}}function flushHTML(e){if(!e[ls])return null;const t={name:"div",attributes:e[ls].attributes,children:e[ls].children};if(e[ls].failingNode){const a=e[ls].failingNode[us]();a&&(e.layout.endsWith("-tb")?t.children.push(createLine(e,[a])):t.children.push(a))}return 0===t.children.length?null:t}function addHTML(e,t,a){const r=e[ls],i=r.availableSpace,[n,s,o,c]=a;switch(e.layout){case"position":r.width=Math.max(r.width,n+o);r.height=Math.max(r.height,s+c);r.children.push(t);break;case"lr-tb":case"rl-tb":if(!r.line||1===r.attempt){r.line=createLine(e,[]);r.children.push(r.line);r.numberInLine=0}r.numberInLine+=1;r.line.children.push(t);if(0===r.attempt){r.currentWidth+=o;r.height=Math.max(r.height,r.prevHeight+c)}else{r.currentWidth=o;r.prevHeight=r.height;r.height+=c;r.attempt=0}r.width=Math.max(r.width,r.currentWidth);break;case"rl-row":case"row":{r.children.push(t);r.width+=o;r.height=Math.max(r.height,c);const e=measureToString(r.height);for(const t of r.children)t.attributes.style.height=e;break}case"table":case"tb":r.width=MathClamp(o,r.width,i.width);r.height+=c;r.children.push(t)}}function getAvailableSpace(e){const t=e[ls].availableSpace,a=e.margin?e.margin.topInset+e.margin.bottomInset:0,r=e.margin?e.margin.leftInset+e.margin.rightInset:0;switch(e.layout){case"lr-tb":case"rl-tb":return 0===e[ls].attempt?{width:t.width-r-e[ls].currentWidth,height:t.height-a-e[ls].prevHeight}:{width:t.width-r,height:t.height-a-e[ls].height};case"rl-row":case"row":return{width:Math.sumPrecise(e[ls].columnWidths.slice(e[ls].currentColumn)),height:t.height-r};case"table":case"tb":return{width:t.width-r,height:t.height-a-e[ls].height};default:return t}}function checkDimensions(e,t){if(null===e[Fs]()[ls].firstUnsplittable)return!0;if(0===e.w||0===e.h)return!0;const a=e[Cs](),r=a[ls]?.attempt||0,[,i,n,s]=function getTransformedBBox(e){let t,a,r=""===e.w?NaN:e.w,i=""===e.h?NaN:e.h,[n,s]=[0,0];switch(e.anchorType||""){case"bottomCenter":[n,s]=[r/2,i];break;case"bottomLeft":[n,s]=[0,i];break;case"bottomRight":[n,s]=[r,i];break;case"middleCenter":[n,s]=[r/2,i/2];break;case"middleLeft":[n,s]=[0,i/2];break;case"middleRight":[n,s]=[r,i/2];break;case"topCenter":[n,s]=[r/2,0];break;case"topRight":[n,s]=[r,0]}switch(e.rotate||0){case 0:[t,a]=[-n,-s];break;case 90:[t,a]=[-s,n];[r,i]=[i,-r];break;case 180:[t,a]=[n,s];[r,i]=[-r,-i];break;case 270:[t,a]=[s,-n];[r,i]=[-i,r]}return[e.x+t+Math.min(0,r),e.y+a+Math.min(0,i),Math.abs(r),Math.abs(i)]}(e);switch(a.layout){case"lr-tb":case"rl-tb":return 0===r?e[Fs]()[ls].noLayoutFailure?""!==e.w?Math.round(n-t.width)<=2:t.width>2:!(""!==e.h&&Math.round(s-t.height)>2)&&(""!==e.w?Math.round(n-t.width)<=2||0===a[ls].numberInLine&&t.height>2:t.width>2):!!e[Fs]()[ls].noLayoutFailure||!(""!==e.h&&Math.round(s-t.height)>2)&&((""===e.w||Math.round(n-t.width)<=2||!a[js]())&&t.height>2);case"table":case"tb":return!!e[Fs]()[ls].noLayoutFailure||(""===e.h||e[_s]()?(""===e.w||Math.round(n-t.width)<=2||!a[js]())&&t.height>2:Math.round(s-t.height)<=2);case"position":if(e[Fs]()[ls].noLayoutFailure)return!0;if(""===e.h||Math.round(s+i-t.height)<=2)return!0;return s+i>e[Fs]()[ls].currentContentArea.h;case"rl-row":case"row":return!!e[Fs]()[ls].noLayoutFailure||(""===e.h||Math.round(s-t.height)<=2);default:return!0}}const Go=go.template.id,Vo="http://www.w3.org/2000/svg",Ko=/^H(\d+)$/,Jo=new Set(["image/gif","image/jpeg","image/jpg","image/pjpeg","image/png","image/apng","image/x-png","image/bmp","image/x-ms-bmp","image/tiff","image/tif","application/octet-stream"]),Yo=[[[66,77],"image/bmp"],[[255,216,255],"image/jpeg"],[[73,73,42,0],"image/tiff"],[[77,77,0,42],"image/tiff"],[[71,73,70,56,57,97],"image/gif"],[[137,80,78,71,13,10,26,10],"image/png"]];function getBorderDims(e){if(!e||!e.border)return{w:0,h:0};const t=e.border[ws]();return t?{w:t.widths[0]+t.widths[2]+t.insets[0]+t.insets[2],h:t.widths[1]+t.widths[3]+t.insets[1]+t.insets[3]}:{w:0,h:0}}function hasMargin(e){return e.margin&&(e.margin.topInset||e.margin.rightInset||e.margin.bottomInset||e.margin.leftInset)}function _setValue(e,t){if(!e.value){const t=new Value({});e[Qn](t);e.value=t}e.value[io](t)}function*getContainedChildren(e){for(const t of e[xs]())t instanceof SubformSet?yield*t[As]():yield t}function isRequired(e){return"error"===e.validate?.nullTest}function setTabIndex(e){for(;e;){if(!e.traversal){e[no]=e[vs]()[no];return}if(e[no])return;let t=null;for(const a of e.traversal[xs]())if("next"===a.operation){t=a;break}if(!t||!t.ref){e[no]=e[vs]()[no];return}const a=e[Fs]();e[no]=++a[no];const r=a[to](t.ref,e);if(!r)return;e=r[0]}}function applyAssist(e,t){const a=e.assist;if(a){const e=a[co]();e&&(t.title=e);const r=a.role.match(Ko);if(r){const e="heading",a=r[1];t.role=e;t["aria-level"]=a}}if("table"===e.layout)t.role="table";else if("row"===e.layout)t.role="row";else{const a=e[vs]();"row"===a.layout&&(t.role="TH"===a.assist?.role?"columnheader":"cell")}}function ariaLabel(e){if(!e.assist)return null;const t=e.assist;return t.speak&&""!==t.speak[ss]?t.speak[ss]:t.toolTip?t.toolTip[ss]:null}function valueToHtml(e){return HTMLResult.success({name:"div",attributes:{class:["xfaRich"],style:Object.create(null)},children:[{name:"span",attributes:{style:Object.create(null)},value:e}]})}function setFirstUnsplittable(e){const t=e[Fs]();if(null===t[ls].firstUnsplittable){t[ls].firstUnsplittable=e;t[ls].noLayoutFailure=!0}}function unsetFirstUnsplittable(e){const t=e[Fs]();t[ls].firstUnsplittable===e&&(t[ls].noLayoutFailure=!1)}function handleBreak(e){if(e[ls])return!1;e[ls]=Object.create(null);if("auto"===e.targetType)return!1;const t=e[Fs]();let a=null;if(e.target){a=t[to](e.target,e[vs]());if(!a)return!1;a=a[0]}const{currentPageArea:r,currentContentArea:i}=t[ls];if("pageArea"===e.targetType){a instanceof PageArea||(a=null);if(e.startNew){e[ls].target=a||r;return!0}if(a&&a!==r){e[ls].target=a;return!0}return!1}a instanceof ContentArea||(a=null);const n=a&&a[vs]();let s,o=n;if(e.startNew)if(a){const e=n.contentArea.children,t=e.indexOf(i),r=e.indexOf(a);-1!==t&&te;r[ls].noLayoutFailure=!0;const s=t[co](a);e[Zn](s.html,s.bbox);r[ls].noLayoutFailure=i;t[Cs]=n}class AppearanceFilter extends StringObject{constructor(e){super(Go,"appearanceFilter");this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Arc extends XFAObject{constructor(e){super(Go,"arc",!0);this.circular=getInteger({data:e.circular,defaultValue:0,validate:e=>1===e});this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.startAngle=getFloat({data:e.startAngle,defaultValue:0,validate:e=>!0});this.sweepAngle=getFloat({data:e.sweepAngle,defaultValue:360,validate:e=>!0});this.use=e.use||"";this.usehref=e.usehref||"";this.edge=null;this.fill=null}[co](){const e=this.edge||new Edge({}),t=e[ho](),a=Object.create(null);"visible"===this.fill?.presence?Object.assign(a,this.fill[ho]()):a.fill="transparent";a.strokeWidth=measureToString("visible"===e.presence?e.thickness:0);a.stroke=t.color;let r;const i={xmlns:Vo,style:{width:"100%",height:"100%",overflow:"visible"}};if(360===this.sweepAngle)r={name:"ellipse",attributes:{xmlns:Vo,cx:"50%",cy:"50%",rx:"50%",ry:"50%",style:a}};else{const e=this.startAngle*Math.PI/180,t=this.sweepAngle*Math.PI/180,n=this.sweepAngle>180?1:0,[s,o,c,l]=[50*(1+Math.cos(e)),50*(1-Math.sin(e)),50*(1+Math.cos(e+t)),50*(1-Math.sin(e+t))];r={name:"path",attributes:{xmlns:Vo,d:`M ${s} ${o} A 50 50 0 ${n} 0 ${c} ${l}`,vectorEffect:"non-scaling-stroke",style:a}};Object.assign(i,{viewBox:"0 0 100 100",preserveAspectRatio:"none"})}const n={name:"svg",children:[r],attributes:i};if(hasMargin(this[vs]()[vs]()))return HTMLResult.success({name:"div",attributes:{style:{display:"inline",width:"100%",height:"100%"}},children:[n]});n.attributes.style.position="absolute";return HTMLResult.success(n)}}class Area extends XFAObject{constructor(e){super(Go,"area",!0);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.id=e.id||"";this.name=e.name||"";this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.desc=null;this.extras=null;this.area=new XFAObjectArray;this.draw=new XFAObjectArray;this.exObject=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.field=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray}*[As](){yield*getContainedChildren(this)}[Us](){return!0}[Rs](){return!0}[Zn](e,t){const[a,r,i,n]=t;this[ls].width=Math.max(this[ls].width,a+i);this[ls].height=Math.max(this[ls].height,r+n);this[ls].children.push(e)}[gs](){return this[ls].availableSpace}[co](e){const t=toStyle(this,"position"),a={style:t,id:this[uo],class:["xfaArea"]};isPrintOnly(this)&&a.class.push("xfaPrintOnly");this.name&&(a.xfaName=this.name);const r=[];this[ls]={children:r,width:0,height:0,availableSpace:e};const i=this[es]({filter:new Set(["area","draw","field","exclGroup","subform","subformSet"]),include:!0});if(!i.success){if(i.isBreak())return i;delete this[ls];return HTMLResult.FAILURE}t.width=measureToString(this[ls].width);t.height=measureToString(this[ls].height);const n={name:"div",attributes:a,children:r},s=[this.x,this.y,this[ls].width,this[ls].height];delete this[ls];return HTMLResult.success(n,s)}}class Assist extends XFAObject{constructor(e){super(Go,"assist",!0);this.id=e.id||"";this.role=e.role||"";this.use=e.use||"";this.usehref=e.usehref||"";this.speak=null;this.toolTip=null}[co](){return this.toolTip?.[ss]||null}}class Barcode extends XFAObject{constructor(e){super(Go,"barcode",!0);this.charEncoding=getKeyword({data:e.charEncoding?e.charEncoding.toLowerCase():"",defaultValue:"",validate:e=>["utf-8","big-five","fontspecific","gbk","gb-18030","gb-2312","ksc-5601","none","shift-jis","ucs-2","utf-16"].includes(e)||e.match(/iso-8859-\d{2}/)});this.checksum=getStringOption(e.checksum,["none","1mod10","1mod10_1mod11","2mod10","auto"]);this.dataColumnCount=getInteger({data:e.dataColumnCount,defaultValue:-1,validate:e=>e>=0});this.dataLength=getInteger({data:e.dataLength,defaultValue:-1,validate:e=>e>=0});this.dataPrep=getStringOption(e.dataPrep,["none","flateCompress"]);this.dataRowCount=getInteger({data:e.dataRowCount,defaultValue:-1,validate:e=>e>=0});this.endChar=e.endChar||"";this.errorCorrectionLevel=getInteger({data:e.errorCorrectionLevel,defaultValue:-1,validate:e=>e>=0&&e<=8});this.id=e.id||"";this.moduleHeight=getMeasurement(e.moduleHeight,"5mm");this.moduleWidth=getMeasurement(e.moduleWidth,"0.25mm");this.printCheckDigit=getInteger({data:e.printCheckDigit,defaultValue:0,validate:e=>1===e});this.rowColumnRatio=getRatio(e.rowColumnRatio);this.startChar=e.startChar||"";this.textLocation=getStringOption(e.textLocation,["below","above","aboveEmbedded","belowEmbedded","none"]);this.truncate=getInteger({data:e.truncate,defaultValue:0,validate:e=>1===e});this.type=getStringOption(e.type?e.type.toLowerCase():"",["aztec","codabar","code2of5industrial","code2of5interleaved","code2of5matrix","code2of5standard","code3of9","code3of9extended","code11","code49","code93","code128","code128a","code128b","code128c","code128sscc","datamatrix","ean8","ean8add2","ean8add5","ean13","ean13add2","ean13add5","ean13pwcd","fim","logmars","maxicode","msi","pdf417","pdf417macro","plessey","postauscust2","postauscust3","postausreplypaid","postausstandard","postukrm4scc","postusdpbc","postusimb","postusstandard","postus5zip","qrcode","rfid","rss14","rss14expanded","rss14limited","rss14stacked","rss14stackedomni","rss14truncated","telepen","ucc128","ucc128random","ucc128sscc","upca","upcaadd2","upcaadd5","upcapwcd","upce","upceadd2","upceadd5","upcean2","upcean5","upsmaxicode"]);this.upsMode=getStringOption(e.upsMode,["usCarrier","internationalCarrier","secureSymbol","standardSymbol"]);this.use=e.use||"";this.usehref=e.usehref||"";this.wideNarrowRatio=getRatio(e.wideNarrowRatio);this.encrypt=null;this.extras=null}}class Bind extends XFAObject{constructor(e){super(Go,"bind",!0);this.match=getStringOption(e.match,["once","dataRef","global","none"]);this.ref=e.ref||"";this.picture=null}}class BindItems extends XFAObject{constructor(e){super(Go,"bindItems");this.connection=e.connection||"";this.labelRef=e.labelRef||"";this.ref=e.ref||"";this.valueRef=e.valueRef||""}}class Bookend extends XFAObject{constructor(e){super(Go,"bookend");this.id=e.id||"";this.leader=e.leader||"";this.trailer=e.trailer||"";this.use=e.use||"";this.usehref=e.usehref||""}}class BooleanElement extends Option01{constructor(e){super(Go,"boolean");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[co](e){return valueToHtml(1===this[ss]?"1":"0")}}class Border extends XFAObject{constructor(e){super(Go,"border",!0);this.break=getStringOption(e.break,["close","open"]);this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.corner=new XFAObjectArray(4);this.edge=new XFAObjectArray(4);this.extras=null;this.fill=null;this.margin=null}[ws](){if(!this[ls]){const e=this.edge.children.slice();if(e.length<4){const t=e.at(-1)||new Edge({});for(let a=e.length;a<4;a++)e.push(t)}const t=e.map((e=>e.thickness)),a=[0,0,0,0];if(this.margin){a[0]=this.margin.topInset;a[1]=this.margin.rightInset;a[2]=this.margin.bottomInset;a[3]=this.margin.leftInset}this[ls]={widths:t,insets:a,edges:e}}return this[ls]}[ho](){const{edges:e}=this[ws](),t=e.map((e=>{const t=e[ho]();t.color||="#000000";return t})),a=Object.create(null);this.margin&&Object.assign(a,this.margin[ho]());"visible"===this.fill?.presence&&Object.assign(a,this.fill[ho]());if(this.corner.children.some((e=>0!==e.radius))){const e=this.corner.children.map((e=>e[ho]()));if(2===e.length||3===e.length){const t=e.at(-1);for(let a=e.length;a<4;a++)e.push(t)}a.borderRadius=e.map((e=>e.radius)).join(" ")}switch(this.presence){case"invisible":case"hidden":a.borderStyle="";break;case"inactive":a.borderStyle="none";break;default:a.borderStyle=t.map((e=>e.style)).join(" ")}a.borderWidth=t.map((e=>e.width)).join(" ");a.borderColor=t.map((e=>e.color)).join(" ");return a}}class Break extends XFAObject{constructor(e){super(Go,"break",!0);this.after=getStringOption(e.after,["auto","contentArea","pageArea","pageEven","pageOdd"]);this.afterTarget=e.afterTarget||"";this.before=getStringOption(e.before,["auto","contentArea","pageArea","pageEven","pageOdd"]);this.beforeTarget=e.beforeTarget||"";this.bookendLeader=e.bookendLeader||"";this.bookendTrailer=e.bookendTrailer||"";this.id=e.id||"";this.overflowLeader=e.overflowLeader||"";this.overflowTarget=e.overflowTarget||"";this.overflowTrailer=e.overflowTrailer||"";this.startNew=getInteger({data:e.startNew,defaultValue:0,validate:e=>1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}}class BreakAfter extends XFAObject{constructor(e){super(Go,"breakAfter",!0);this.id=e.id||"";this.leader=e.leader||"";this.startNew=getInteger({data:e.startNew,defaultValue:0,validate:e=>1===e});this.target=e.target||"";this.targetType=getStringOption(e.targetType,["auto","contentArea","pageArea"]);this.trailer=e.trailer||"";this.use=e.use||"";this.usehref=e.usehref||"";this.script=null}}class BreakBefore extends XFAObject{constructor(e){super(Go,"breakBefore",!0);this.id=e.id||"";this.leader=e.leader||"";this.startNew=getInteger({data:e.startNew,defaultValue:0,validate:e=>1===e});this.target=e.target||"";this.targetType=getStringOption(e.targetType,["auto","contentArea","pageArea"]);this.trailer=e.trailer||"";this.use=e.use||"";this.usehref=e.usehref||"";this.script=null}[co](e){this[ls]={};return HTMLResult.FAILURE}}class Button extends XFAObject{constructor(e){super(Go,"button",!0);this.highlight=getStringOption(e.highlight,["inverted","none","outline","push"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[co](e){const t=this[vs]()[vs](),a={name:"button",attributes:{id:this[uo],class:["xfaButton"],style:{}},children:[]};for(const e of t.event.children){if("click"!==e.activity||!e.script)continue;const t=recoverJsURL(e.script[ss]);if(!t)continue;const r=fixURL(t.url);r&&a.children.push({name:"a",attributes:{id:"link"+this[uo],href:r,newWindow:t.newWindow,class:["xfaLink"],style:{}},children:[]})}return HTMLResult.success(a)}}class Calculate extends XFAObject{constructor(e){super(Go,"calculate",!0);this.id=e.id||"";this.override=getStringOption(e.override,["disabled","error","ignore","warning"]);this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.message=null;this.script=null}}class Caption extends XFAObject{constructor(e){super(Go,"caption",!0);this.id=e.id||"";this.placement=getStringOption(e.placement,["left","bottom","inline","right","top"]);this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.reserve=Math.ceil(getMeasurement(e.reserve));this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.font=null;this.margin=null;this.para=null;this.value=null}[io](e){_setValue(this,e)}[ws](e){if(!this[ls]){let{width:t,height:a}=e;switch(this.placement){case"left":case"right":case"inline":t=this.reserve<=0?t:this.reserve;break;case"top":case"bottom":a=this.reserve<=0?a:this.reserve}this[ls]=layoutNode(this,{width:t,height:a})}return this[ls]}[co](e){if(!this.value)return HTMLResult.EMPTY;this[Ys]();const t=this.value[co](e).html;if(!t){this[Js]();return HTMLResult.EMPTY}const a=this.reserve;if(this.reserve<=0){const{w:t,h:a}=this[ws](e);switch(this.placement){case"left":case"right":case"inline":this.reserve=t;break;case"top":case"bottom":this.reserve=a}}const r=[];"string"==typeof t?r.push({name:"#text",value:t}):r.push(t);const i=toStyle(this,"font","margin","visibility");switch(this.placement){case"left":case"right":this.reserve>0&&(i.width=measureToString(this.reserve));break;case"top":case"bottom":this.reserve>0&&(i.height=measureToString(this.reserve))}setPara(this,null,t);this[Js]();this.reserve=a;return HTMLResult.success({name:"div",attributes:{style:i,class:["xfaCaption"]},children:r})}}class Certificate extends StringObject{constructor(e){super(Go,"certificate");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Certificates extends XFAObject{constructor(e){super(Go,"certificates",!0);this.credentialServerPolicy=getStringOption(e.credentialServerPolicy,["optional","required"]);this.id=e.id||"";this.url=e.url||"";this.urlPolicy=e.urlPolicy||"";this.use=e.use||"";this.usehref=e.usehref||"";this.encryption=null;this.issuers=null;this.keyUsage=null;this.oids=null;this.signing=null;this.subjectDNs=null}}class CheckButton extends XFAObject{constructor(e){super(Go,"checkButton",!0);this.id=e.id||"";this.mark=getStringOption(e.mark,["default","check","circle","cross","diamond","square","star"]);this.shape=getStringOption(e.shape,["square","round"]);this.size=getMeasurement(e.size,"10pt");this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}[co](e){const t=toStyle("margin"),a=measureToString(this.size);t.width=t.height=a;let r,i,n;const s=this[vs]()[vs](),o=s.items.children.length&&s.items.children[0][co]().html||[],c={on:(void 0!==o[0]?o[0]:"on").toString(),off:(void 0!==o[1]?o[1]:"off").toString()},l=(s.value?.[so]()||"off")===c.on||void 0,h=s[Cs](),u=s[uo];let d;if(h instanceof ExclGroup){n=h[uo];r="radio";i="xfaRadio";d=h[os]?.[uo]||h[uo]}else{r="checkbox";i="xfaCheckbox";d=s[os]?.[uo]||s[uo]}const f={name:"input",attributes:{class:[i],style:t,fieldId:u,dataId:d,type:r,checked:l,xfaOn:c.on,xfaOff:c.off,"aria-label":ariaLabel(s),"aria-required":!1}};n&&(f.attributes.name=n);if(isRequired(s)){f.attributes["aria-required"]=!0;f.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[f]})}}class ChoiceList extends XFAObject{constructor(e){super(Go,"choiceList",!0);this.commitOn=getStringOption(e.commitOn,["select","exit"]);this.id=e.id||"";this.open=getStringOption(e.open,["userControl","always","multiSelect","onEntry"]);this.textEntry=getInteger({data:e.textEntry,defaultValue:0,validate:e=>1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}[co](e){const t=toStyle(this,"border","margin"),a=this[vs]()[vs](),r={fontSize:`calc(${a.font?.size||10}px * var(--total-scale-factor))`},i=[];if(a.items.children.length>0){const e=a.items;let t=0,n=0;if(2===e.children.length){t=e.children[0].save;n=1-t}const s=e.children[t][co]().html,o=e.children[n][co]().html;let c=!1;const l=a.value?.[so]()||"";for(let e=0,t=s.length;eMathClamp(parseInt(e.trim(),10),0,255))).map((e=>isNaN(e)?0:e));if(n.length<3)return{r:a,g:r,b:i};[a,r,i]=n;return{r:a,g:r,b:i}}(e.value):"";this.extras=null}[Os](){return!1}[ho](){return this.value?Util.makeHexColor(this.value.r,this.value.g,this.value.b):null}}class Comb extends XFAObject{constructor(e){super(Go,"comb");this.id=e.id||"";this.numberOfCells=getInteger({data:e.numberOfCells,defaultValue:0,validate:e=>e>=0});this.use=e.use||"";this.usehref=e.usehref||""}}class Connect extends XFAObject{constructor(e){super(Go,"connect",!0);this.connection=e.connection||"";this.id=e.id||"";this.ref=e.ref||"";this.usage=getStringOption(e.usage,["exportAndImport","exportOnly","importOnly"]);this.use=e.use||"";this.usehref=e.usehref||"";this.picture=null}}class ContentArea extends XFAObject{constructor(e){super(Go,"contentArea",!0);this.h=getMeasurement(e.h);this.id=e.id||"";this.name=e.name||"";this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.w=getMeasurement(e.w);this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.desc=null;this.extras=null}[co](e){const t={left:measureToString(this.x),top:measureToString(this.y),width:measureToString(this.w),height:measureToString(this.h)},a=["xfaContentarea"];isPrintOnly(this)&&a.push("xfaPrintOnly");return HTMLResult.success({name:"div",children:[],attributes:{style:t,class:a,id:this[uo]}})}}class Corner extends XFAObject{constructor(e){super(Go,"corner",!0);this.id=e.id||"";this.inverted=getInteger({data:e.inverted,defaultValue:0,validate:e=>1===e});this.join=getStringOption(e.join,["square","round"]);this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.radius=getMeasurement(e.radius);this.stroke=getStringOption(e.stroke,["solid","dashDot","dashDotDot","dashed","dotted","embossed","etched","lowered","raised"]);this.thickness=getMeasurement(e.thickness,"0.5pt");this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[ho](){const e=toStyle(this,"visibility");e.radius=measureToString("square"===this.join?0:this.radius);return e}}class DateElement extends ContentObject{constructor(e){super(Go,"date");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){const e=this[ss].trim();this[ss]=e?new Date(e):null}[co](e){return valueToHtml(this[ss]?this[ss].toString():"")}}class DateTime extends ContentObject{constructor(e){super(Go,"dateTime");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){const e=this[ss].trim();this[ss]=e?new Date(e):null}[co](e){return valueToHtml(this[ss]?this[ss].toString():"")}}class DateTimeEdit extends XFAObject{constructor(e){super(Go,"dateTimeEdit",!0);this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.picker=getStringOption(e.picker,["host","none"]);this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.comb=null;this.extras=null;this.margin=null}[co](e){const t=toStyle(this,"border","font","margin"),a=this[vs]()[vs](),r={name:"input",attributes:{type:"text",fieldId:a[uo],dataId:a[os]?.[uo]||a[uo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(a),"aria-required":!1}};if(isRequired(a)){r.attributes["aria-required"]=!0;r.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[r]})}}class Decimal extends ContentObject{constructor(e){super(Go,"decimal");this.fracDigits=getInteger({data:e.fracDigits,defaultValue:2,validate:e=>!0});this.id=e.id||"";this.leadDigits=getInteger({data:e.leadDigits,defaultValue:-1,validate:e=>!0});this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){const e=parseFloat(this[ss].trim());this[ss]=isNaN(e)?null:e}[co](e){return valueToHtml(null!==this[ss]?this[ss].toString():"")}}class DefaultUi extends XFAObject{constructor(e){super(Go,"defaultUi",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}}class Desc extends XFAObject{constructor(e){super(Go,"desc",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}}class DigestMethod extends OptionObject{constructor(e){super(Go,"digestMethod",["","SHA1","SHA256","SHA512","RIPEMD160"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class DigestMethods extends XFAObject{constructor(e){super(Go,"digestMethods",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.digestMethod=new XFAObjectArray}}class Draw extends XFAObject{constructor(e){super(Go,"draw",!0);this.anchorType=getStringOption(e.anchorType,["topLeft","bottomCenter","bottomLeft","bottomRight","middleCenter","middleLeft","middleRight","topCenter","topRight"]);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.locale=e.locale||"";this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.rotate=getInteger({data:e.rotate,defaultValue:0,validate:e=>e%90==0});this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.border=null;this.caption=null;this.desc=null;this.extras=null;this.font=null;this.keep=null;this.margin=null;this.para=null;this.traversal=null;this.ui=null;this.value=null;this.setProperty=new XFAObjectArray}[io](e){_setValue(this,e)}[co](e){setTabIndex(this);if("hidden"===this.presence||"inactive"===this.presence)return HTMLResult.EMPTY;fixDimensions(this);this[Ys]();const t=this.w,a=this.h,{w:r,h:i,isBroken:n}=layoutNode(this,e);if(r&&""===this.w){if(n&&this[Cs]()[js]()){this[Js]();return HTMLResult.FAILURE}this.w=r}i&&""===this.h&&(this.h=i);setFirstUnsplittable(this);if(!checkDimensions(this,e)){this.w=t;this.h=a;this[Js]();return HTMLResult.FAILURE}unsetFirstUnsplittable(this);const s=toStyle(this,"font","hAlign","dimensions","position","presence","rotate","anchorType","border","margin");setMinMaxDimensions(this,s);if(s.margin){s.padding=s.margin;delete s.margin}const o=["xfaDraw"];this.font&&o.push("xfaFont");isPrintOnly(this)&&o.push("xfaPrintOnly");const c={style:s,id:this[uo],class:o};this.name&&(c.xfaName=this.name);const l={name:"div",attributes:c,children:[]};applyAssist(this,c);const h=computeBbox(this,l,e),u=this.value?this.value[co](e).html:null;if(null===u){this.w=t;this.h=a;this[Js]();return HTMLResult.success(createWrapper(this,l),h)}l.children.push(u);setPara(this,s,u);this.w=t;this.h=a;this[Js]();return HTMLResult.success(createWrapper(this,l),h)}}class Edge extends XFAObject{constructor(e){super(Go,"edge",!0);this.cap=getStringOption(e.cap,["square","butt","round"]);this.id=e.id||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.stroke=getStringOption(e.stroke,["solid","dashDot","dashDotDot","dashed","dotted","embossed","etched","lowered","raised"]);this.thickness=getMeasurement(e.thickness,"0.5pt");this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[ho](){const e=toStyle(this,"visibility");Object.assign(e,{linecap:this.cap,width:measureToString(this.thickness),color:this.color?this.color[ho]():"#000000",style:""});if("visible"!==this.presence)e.style="none";else switch(this.stroke){case"solid":e.style="solid";break;case"dashDot":case"dashDotDot":case"dashed":e.style="dashed";break;case"dotted":e.style="dotted";break;case"embossed":e.style="ridge";break;case"etched":e.style="groove";break;case"lowered":e.style="inset";break;case"raised":e.style="outset"}return e}}class Encoding extends OptionObject{constructor(e){super(Go,"encoding",["adbe.x509.rsa_sha1","adbe.pkcs7.detached","adbe.pkcs7.sha1"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Encodings extends XFAObject{constructor(e){super(Go,"encodings",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.encoding=new XFAObjectArray}}class Encrypt extends XFAObject{constructor(e){super(Go,"encrypt",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=null}}class EncryptData extends XFAObject{constructor(e){super(Go,"encryptData",!0);this.id=e.id||"";this.operation=getStringOption(e.operation,["encrypt","decrypt"]);this.target=e.target||"";this.use=e.use||"";this.usehref=e.usehref||"";this.filter=null;this.manifest=null}}class Encryption extends XFAObject{constructor(e){super(Go,"encryption",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=new XFAObjectArray}}class EncryptionMethod extends OptionObject{constructor(e){super(Go,"encryptionMethod",["","AES256-CBC","TRIPLEDES-CBC","AES128-CBC","AES192-CBC"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class EncryptionMethods extends XFAObject{constructor(e){super(Go,"encryptionMethods",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.encryptionMethod=new XFAObjectArray}}class Event extends XFAObject{constructor(e){super(Go,"event",!0);this.activity=getStringOption(e.activity,["click","change","docClose","docReady","enter","exit","full","indexChange","initialize","mouseDown","mouseEnter","mouseExit","mouseUp","postExecute","postOpen","postPrint","postSave","postSign","postSubmit","preExecute","preOpen","prePrint","preSave","preSign","preSubmit","ready","validationState"]);this.id=e.id||"";this.listen=getStringOption(e.listen,["refOnly","refAndDescendents"]);this.name=e.name||"";this.ref=e.ref||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.encryptData=null;this.execute=null;this.script=null;this.signData=null;this.submit=null}}class ExData extends ContentObject{constructor(e){super(Go,"exData");this.contentType=e.contentType||"";this.href=e.href||"";this.id=e.id||"";this.maxLength=getInteger({data:e.maxLength,defaultValue:-1,validate:e=>e>=-1});this.name=e.name||"";this.rid=e.rid||"";this.transferEncoding=getStringOption(e.transferEncoding,["none","base64","package"]);this.use=e.use||"";this.usehref=e.usehref||""}[Bs](){return"text/html"===this.contentType}[$s](e){if("text/html"===this.contentType&&e[Hs]===go.xhtml.id){this[ss]=e;return!0}if("text/xml"===this.contentType){this[ss]=e;return!0}return!1}[co](e){return"text/html"===this.contentType&&this[ss]?this[ss][co](e):HTMLResult.EMPTY}}class ExObject extends XFAObject{constructor(e){super(Go,"exObject",!0);this.archive=e.archive||"";this.classId=e.classId||"";this.codeBase=e.codeBase||"";this.codeType=e.codeType||"";this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.exObject=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}}class ExclGroup extends XFAObject{constructor(e){super(Go,"exclGroup",!0);this.access=getStringOption(e.access,["open","nonInteractive","protected","readOnly"]);this.accessKey=e.accessKey||"";this.anchorType=getStringOption(e.anchorType,["topLeft","bottomCenter","bottomLeft","bottomRight","middleCenter","middleLeft","middleRight","topCenter","topRight"]);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.layout=getStringOption(e.layout,["position","lr-tb","rl-row","rl-tb","row","table","tb"]);this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.bind=null;this.border=null;this.calculate=null;this.caption=null;this.desc=null;this.extras=null;this.margin=null;this.para=null;this.traversal=null;this.validate=null;this.connect=new XFAObjectArray;this.event=new XFAObjectArray;this.field=new XFAObjectArray;this.setProperty=new XFAObjectArray}[Rs](){return!0}[Os](){return!0}[io](e){for(const t of this.field.children){if(!t.value){const e=new Value({});t[Qn](e);t.value=e}t.value[io](e)}}[js](){return this.layout.endsWith("-tb")&&0===this[ls].attempt&&this[ls].numberInLine>0||this[vs]()[js]()}[_s](){const e=this[Cs]();if(!e[_s]())return!1;if(void 0!==this[ls]._isSplittable)return this[ls]._isSplittable;if("position"===this.layout||this.layout.includes("row")){this[ls]._isSplittable=!1;return!1}if(e.layout?.endsWith("-tb")&&0!==e[ls].numberInLine)return!1;this[ls]._isSplittable=!0;return!0}[us](){return flushHTML(this)}[Zn](e,t){addHTML(this,e,t)}[gs](){return getAvailableSpace(this)}[co](e){setTabIndex(this);if("hidden"===this.presence||"inactive"===this.presence||0===this.h||0===this.w)return HTMLResult.EMPTY;fixDimensions(this);const t=[],a={id:this[uo],class:[]};setAccess(this,a.class);this[ls]||=Object.create(null);Object.assign(this[ls],{children:t,attributes:a,attempt:0,line:null,numberInLine:0,availableSpace:{width:Math.min(this.w||1/0,e.width),height:Math.min(this.h||1/0,e.height)},width:0,height:0,prevHeight:0,currentWidth:0});const r=this[_s]();r||setFirstUnsplittable(this);if(!checkDimensions(this,e))return HTMLResult.FAILURE;const i=new Set(["field"]);if(this.layout.includes("row")){const e=this[Cs]().columnWidths;if(Array.isArray(e)&&e.length>0){this[ls].columnWidths=e;this[ls].currentColumn=0}}const n=toStyle(this,"anchorType","dimensions","position","presence","border","margin","hAlign"),s=["xfaExclgroup"],o=layoutClass(this);o&&s.push(o);isPrintOnly(this)&&s.push("xfaPrintOnly");a.style=n;a.class=s;this.name&&(a.xfaName=this.name);this[Ys]();const c="lr-tb"===this.layout||"rl-tb"===this.layout,l=c?2:1;for(;this[ls].attempte>=1||-1===e});this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.locale=e.locale||"";this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.rotate=getInteger({data:e.rotate,defaultValue:0,validate:e=>e%90==0});this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.bind=null;this.border=null;this.calculate=null;this.caption=null;this.desc=null;this.extras=null;this.font=null;this.format=null;this.items=new XFAObjectArray(2);this.keep=null;this.margin=null;this.para=null;this.traversal=null;this.ui=null;this.validate=null;this.value=null;this.bindItems=new XFAObjectArray;this.connect=new XFAObjectArray;this.event=new XFAObjectArray;this.setProperty=new XFAObjectArray}[Rs](){return!0}[io](e){_setValue(this,e)}[co](e){setTabIndex(this);if(!this.ui){this.ui=new Ui({});this.ui[Is]=this[Is];this[Qn](this.ui);let e;switch(this.items.children.length){case 0:e=new TextEdit({});this.ui.textEdit=e;break;case 1:e=new CheckButton({});this.ui.checkButton=e;break;case 2:e=new ChoiceList({});this.ui.choiceList=e}this.ui[Qn](e)}if(!this.ui||"hidden"===this.presence||"inactive"===this.presence||0===this.h||0===this.w)return HTMLResult.EMPTY;this.caption&&delete this.caption[ls];this[Ys]();const t=this.caption?this.caption[co](e).html:null,a=this.w,r=this.h;let i=0,n=0;if(this.margin){i=this.margin.leftInset+this.margin.rightInset;n=this.margin.topInset+this.margin.bottomInset}let s=null;if(""===this.w||""===this.h){let t=null,a=null,r=0,o=0;if(this.ui.checkButton)r=o=this.ui.checkButton.size;else{const{w:t,h:a}=layoutNode(this,e);if(null!==t){r=t;o=a}else o=function fonts_getMetrics(e,t=!1){let a=null;if(e){const t=stripQuotes(e.typeface),r=e[Is].fontFinder.find(t);a=selectFont(e,r)}if(!a)return{lineHeight:12,lineGap:2,lineNoGap:10};const r=e.size||10,i=a.lineHeight?Math.max(t?0:1.2,a.lineHeight):1.2,n=void 0===a.lineGap?.2:a.lineGap;return{lineHeight:i*r,lineGap:n*r,lineNoGap:Math.max(1,i-n)*r}}(this.font,!0).lineNoGap}s=getBorderDims(this.ui[ws]());r+=s.w;o+=s.h;if(this.caption){const{w:i,h:n,isBroken:s}=this.caption[ws](e);if(s&&this[Cs]()[js]()){this[Js]();return HTMLResult.FAILURE}t=i;a=n;switch(this.caption.placement){case"left":case"right":case"inline":t+=r;break;case"top":case"bottom":a+=o}}else{t=r;a=o}if(t&&""===this.w){t+=i;this.w=Math.min(this.maxW<=0?1/0:this.maxW,this.minW+1e>=1&&e<=5});this.appearanceFilter=null;this.certificates=null;this.digestMethods=null;this.encodings=null;this.encryptionMethods=null;this.handler=null;this.lockDocument=null;this.mdp=null;this.reasons=null;this.timeStamp=null}}class Float extends ContentObject{constructor(e){super(Go,"float");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){const e=parseFloat(this[ss].trim());this[ss]=isNaN(e)?null:e}[co](e){return valueToHtml(null!==this[ss]?this[ss].toString():"")}}class template_Font extends XFAObject{constructor(e){super(Go,"font",!0);this.baselineShift=getMeasurement(e.baselineShift);this.fontHorizontalScale=getFloat({data:e.fontHorizontalScale,defaultValue:100,validate:e=>e>=0});this.fontVerticalScale=getFloat({data:e.fontVerticalScale,defaultValue:100,validate:e=>e>=0});this.id=e.id||"";this.kerningMode=getStringOption(e.kerningMode,["none","pair"]);this.letterSpacing=getMeasurement(e.letterSpacing,"0");this.lineThrough=getInteger({data:e.lineThrough,defaultValue:0,validate:e=>1===e||2===e});this.lineThroughPeriod=getStringOption(e.lineThroughPeriod,["all","word"]);this.overline=getInteger({data:e.overline,defaultValue:0,validate:e=>1===e||2===e});this.overlinePeriod=getStringOption(e.overlinePeriod,["all","word"]);this.posture=getStringOption(e.posture,["normal","italic"]);this.size=getMeasurement(e.size,"10pt");this.typeface=e.typeface||"Courier";this.underline=getInteger({data:e.underline,defaultValue:0,validate:e=>1===e||2===e});this.underlinePeriod=getStringOption(e.underlinePeriod,["all","word"]);this.use=e.use||"";this.usehref=e.usehref||"";this.weight=getStringOption(e.weight,["normal","bold"]);this.extras=null;this.fill=null}[ts](e){super[ts](e);this[Is].usedTypefaces.add(this.typeface)}[ho](){const e=toStyle(this,"fill"),t=e.color;if(t)if("#000000"===t)delete e.color;else if(!t.startsWith("#")){e.background=t;e.backgroundClip="text";e.color="transparent"}this.baselineShift&&(e.verticalAlign=measureToString(this.baselineShift));e.fontKerning="none"===this.kerningMode?"none":"normal";e.letterSpacing=measureToString(this.letterSpacing);if(0!==this.lineThrough){e.textDecoration="line-through";2===this.lineThrough&&(e.textDecorationStyle="double")}if(0!==this.overline){e.textDecoration="overline";2===this.overline&&(e.textDecorationStyle="double")}e.fontStyle=this.posture;e.fontSize=measureToString(.99*this.size);setFontFamily(this,this,this[Is].fontFinder,e);if(0!==this.underline){e.textDecoration="underline";2===this.underline&&(e.textDecorationStyle="double")}e.fontWeight=this.weight;return e}}class Format extends XFAObject{constructor(e){super(Go,"format",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.picture=null}}class Handler extends StringObject{constructor(e){super(Go,"handler");this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Hyphenation extends XFAObject{constructor(e){super(Go,"hyphenation");this.excludeAllCaps=getInteger({data:e.excludeAllCaps,defaultValue:0,validate:e=>1===e});this.excludeInitialCap=getInteger({data:e.excludeInitialCap,defaultValue:0,validate:e=>1===e});this.hyphenate=getInteger({data:e.hyphenate,defaultValue:0,validate:e=>1===e});this.id=e.id||"";this.pushCharacterCount=getInteger({data:e.pushCharacterCount,defaultValue:3,validate:e=>e>=0});this.remainCharacterCount=getInteger({data:e.remainCharacterCount,defaultValue:3,validate:e=>e>=0});this.use=e.use||"";this.usehref=e.usehref||"";this.wordCharacterCount=getInteger({data:e.wordCharacterCount,defaultValue:7,validate:e=>e>=0})}}class Image extends StringObject{constructor(e){super(Go,"image");this.aspect=getStringOption(e.aspect,["fit","actual","height","none","width"]);this.contentType=e.contentType||"";this.href=e.href||"";this.id=e.id||"";this.name=e.name||"";this.transferEncoding=getStringOption(e.transferEncoding,["base64","none","package"]);this.use=e.use||"";this.usehref=e.usehref||""}[co](){if(this.contentType&&!Jo.has(this.contentType.toLowerCase()))return HTMLResult.EMPTY;let e=this[Is].images&&this[Is].images.get(this.href);if(!e&&(this.href||!this[ss]))return HTMLResult.EMPTY;e||"base64"!==this.transferEncoding||(e=function fromBase64Util(e){return Uint8Array.fromBase64?Uint8Array.fromBase64(e):stringToBytes(atob(e))}(this[ss]));if(!e)return HTMLResult.EMPTY;if(!this.contentType){for(const[t,a]of Yo)if(e.length>t.length&&t.every(((t,a)=>t===e[a]))){this.contentType=a;break}if(!this.contentType)return HTMLResult.EMPTY}const t=new Blob([e],{type:this.contentType});let a;switch(this.aspect){case"fit":case"actual":break;case"height":a={height:"100%",objectFit:"fill"};break;case"none":a={width:"100%",height:"100%",objectFit:"fill"};break;case"width":a={width:"100%",objectFit:"fill"}}const r=this[vs]();return HTMLResult.success({name:"img",attributes:{class:["xfaImage"],style:a,src:URL.createObjectURL(t),alt:r?ariaLabel(r[vs]()):null}})}}class ImageEdit extends XFAObject{constructor(e){super(Go,"imageEdit",!0);this.data=getStringOption(e.data,["link","embed"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}[co](e){return"embed"===this.data?HTMLResult.success({name:"div",children:[],attributes:{}}):HTMLResult.EMPTY}}class Integer extends ContentObject{constructor(e){super(Go,"integer");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){const e=parseInt(this[ss].trim(),10);this[ss]=isNaN(e)?null:e}[co](e){return valueToHtml(null!==this[ss]?this[ss].toString():"")}}class Issuers extends XFAObject{constructor(e){super(Go,"issuers",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=new XFAObjectArray}}class Items extends XFAObject{constructor(e){super(Go,"items",!0);this.id=e.id||"";this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.ref=e.ref||"";this.save=getInteger({data:e.save,defaultValue:0,validate:e=>1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}[co](){const e=[];for(const t of this[xs]())e.push(t[so]());return HTMLResult.success(e)}}class Keep extends XFAObject{constructor(e){super(Go,"keep",!0);this.id=e.id||"";const t=["none","contentArea","pageArea"];this.intact=getStringOption(e.intact,t);this.next=getStringOption(e.next,t);this.previous=getStringOption(e.previous,t);this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}}class KeyUsage extends XFAObject{constructor(e){super(Go,"keyUsage");const t=["","yes","no"];this.crlSign=getStringOption(e.crlSign,t);this.dataEncipherment=getStringOption(e.dataEncipherment,t);this.decipherOnly=getStringOption(e.decipherOnly,t);this.digitalSignature=getStringOption(e.digitalSignature,t);this.encipherOnly=getStringOption(e.encipherOnly,t);this.id=e.id||"";this.keyAgreement=getStringOption(e.keyAgreement,t);this.keyCertSign=getStringOption(e.keyCertSign,t);this.keyEncipherment=getStringOption(e.keyEncipherment,t);this.nonRepudiation=getStringOption(e.nonRepudiation,t);this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Line extends XFAObject{constructor(e){super(Go,"line",!0);this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.slope=getStringOption(e.slope,["\\","/"]);this.use=e.use||"";this.usehref=e.usehref||"";this.edge=null}[co](){const e=this[vs]()[vs](),t=this.edge||new Edge({}),a=t[ho](),r=Object.create(null),i="visible"===t.presence?t.thickness:0;r.strokeWidth=measureToString(i);r.stroke=a.color;let n,s,o,c,l="100%",h="100%";if(e.w<=i){[n,s,o,c]=["50%",0,"50%","100%"];l=r.strokeWidth}else if(e.h<=i){[n,s,o,c]=[0,"50%","100%","50%"];h=r.strokeWidth}else"\\"===this.slope?[n,s,o,c]=[0,0,"100%","100%"]:[n,s,o,c]=[0,"100%","100%",0];const u={name:"svg",children:[{name:"line",attributes:{xmlns:Vo,x1:n,y1:s,x2:o,y2:c,style:r}}],attributes:{xmlns:Vo,width:l,height:h,style:{overflow:"visible"}}};if(hasMargin(e))return HTMLResult.success({name:"div",attributes:{style:{display:"inline",width:"100%",height:"100%"}},children:[u]});u.attributes.style.position="absolute";return HTMLResult.success(u)}}class Linear extends XFAObject{constructor(e){super(Go,"linear",!0);this.id=e.id||"";this.type=getStringOption(e.type,["toRight","toBottom","toLeft","toTop"]);this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[ho](e){e=e?e[ho]():"#FFFFFF";return`linear-gradient(${this.type.replace(/([RBLT])/," $1").toLowerCase()}, ${e}, ${this.color?this.color[ho]():"#000000"})`}}class LockDocument extends ContentObject{constructor(e){super(Go,"lockDocument");this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}[hs](){this[ss]=getStringOption(this[ss],["auto","0","1"])}}class Manifest extends XFAObject{constructor(e){super(Go,"manifest",!0);this.action=getStringOption(e.action,["include","all","exclude"]);this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.ref=new XFAObjectArray}}class Margin extends XFAObject{constructor(e){super(Go,"margin",!0);this.bottomInset=getMeasurement(e.bottomInset,"0");this.id=e.id||"";this.leftInset=getMeasurement(e.leftInset,"0");this.rightInset=getMeasurement(e.rightInset,"0");this.topInset=getMeasurement(e.topInset,"0");this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[ho](){return{margin:measureToString(this.topInset)+" "+measureToString(this.rightInset)+" "+measureToString(this.bottomInset)+" "+measureToString(this.leftInset)}}}class Mdp extends XFAObject{constructor(e){super(Go,"mdp");this.id=e.id||"";this.permissions=getInteger({data:e.permissions,defaultValue:2,validate:e=>1===e||3===e});this.signatureType=getStringOption(e.signatureType,["filler","author"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Medium extends XFAObject{constructor(e){super(Go,"medium");this.id=e.id||"";this.imagingBBox=function getBBox(e){const t=-1;if(!e)return{x:t,y:t,width:t,height:t};const a=e.split(",",4).map((e=>getMeasurement(e.trim(),"-1")));if(a.length<4||a[2]<0||a[3]<0)return{x:t,y:t,width:t,height:t};const[r,i,n,s]=a;return{x:r,y:i,width:n,height:s}}(e.imagingBBox);this.long=getMeasurement(e.long);this.orientation=getStringOption(e.orientation,["portrait","landscape"]);this.short=getMeasurement(e.short);this.stock=e.stock||"";this.trayIn=getStringOption(e.trayIn,["auto","delegate","pageFront"]);this.trayOut=getStringOption(e.trayOut,["auto","delegate"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Message extends XFAObject{constructor(e){super(Go,"message",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.text=new XFAObjectArray}}class NumericEdit extends XFAObject{constructor(e){super(Go,"numericEdit",!0);this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.comb=null;this.extras=null;this.margin=null}[co](e){const t=toStyle(this,"border","font","margin"),a=this[vs]()[vs](),r={name:"input",attributes:{type:"text",fieldId:a[uo],dataId:a[os]?.[uo]||a[uo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(a),"aria-required":!1}};if(isRequired(a)){r.attributes["aria-required"]=!0;r.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[r]})}}class Occur extends XFAObject{constructor(e){super(Go,"occur",!0);this.id=e.id||"";this.initial=""!==e.initial?getInteger({data:e.initial,defaultValue:"",validate:e=>!0}):"";this.max=""!==e.max?getInteger({data:e.max,defaultValue:1,validate:e=>!0}):"";this.min=""!==e.min?getInteger({data:e.min,defaultValue:1,validate:e=>!0}):"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[ts](){const e=this[vs](),t=this.min;""===this.min&&(this.min=e instanceof PageArea||e instanceof PageSet?0:1);""===this.max&&(this.max=""===t?e instanceof PageArea||e instanceof PageSet?-1:1:this.min);-1!==this.max&&this.max!0});this.name=e.name||"";this.numbered=getInteger({data:e.numbered,defaultValue:1,validate:e=>!0});this.oddOrEven=getStringOption(e.oddOrEven,["any","even","odd"]);this.pagePosition=getStringOption(e.pagePosition,["any","first","last","only","rest"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.desc=null;this.extras=null;this.medium=null;this.occur=null;this.area=new XFAObjectArray;this.contentArea=new XFAObjectArray;this.draw=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.field=new XFAObjectArray;this.subform=new XFAObjectArray}[Xs](){if(!this[ls]){this[ls]={numberOfUse:0};return!0}return!this.occur||-1===this.occur.max||this[ls].numberOfUsee.oddOrEven===t&&e.pagePosition===a));if(r)return r;r=this.pageArea.children.find((e=>"any"===e.oddOrEven&&e.pagePosition===a));if(r)return r;r=this.pageArea.children.find((e=>"any"===e.oddOrEven&&"any"===e.pagePosition));return r||this.pageArea.children[0]}}class Para extends XFAObject{constructor(e){super(Go,"para",!0);this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.lineHeight=e.lineHeight?getMeasurement(e.lineHeight,"0pt"):"";this.marginLeft=e.marginLeft?getMeasurement(e.marginLeft,"0pt"):"";this.marginRight=e.marginRight?getMeasurement(e.marginRight,"0pt"):"";this.orphans=getInteger({data:e.orphans,defaultValue:0,validate:e=>e>=0});this.preserve=e.preserve||"";this.radixOffset=e.radixOffset?getMeasurement(e.radixOffset,"0pt"):"";this.spaceAbove=e.spaceAbove?getMeasurement(e.spaceAbove,"0pt"):"";this.spaceBelow=e.spaceBelow?getMeasurement(e.spaceBelow,"0pt"):"";this.tabDefault=e.tabDefault?getMeasurement(this.tabDefault):"";this.tabStops=(e.tabStops||"").trim().split(/\s+/).map(((e,t)=>t%2==1?getMeasurement(e):e));this.textIndent=e.textIndent?getMeasurement(e.textIndent,"0pt"):"";this.use=e.use||"";this.usehref=e.usehref||"";this.vAlign=getStringOption(e.vAlign,["top","bottom","middle"]);this.widows=getInteger({data:e.widows,defaultValue:0,validate:e=>e>=0});this.hyphenation=null}[ho](){const e=toStyle(this,"hAlign");""!==this.marginLeft&&(e.paddingLeft=measureToString(this.marginLeft));""!==this.marginRight&&(e.paddingRight=measureToString(this.marginRight));""!==this.spaceAbove&&(e.paddingTop=measureToString(this.spaceAbove));""!==this.spaceBelow&&(e.paddingBottom=measureToString(this.spaceBelow));if(""!==this.textIndent){e.textIndent=measureToString(this.textIndent);fixTextIndent(e)}this.lineHeight>0&&(e.lineHeight=measureToString(this.lineHeight));""!==this.tabDefault&&(e.tabSize=measureToString(this.tabDefault));this.tabStops.length;this.hyphenatation&&Object.assign(e,this.hyphenatation[ho]());return e}}class PasswordEdit extends XFAObject{constructor(e){super(Go,"passwordEdit",!0);this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.passwordChar=e.passwordChar||"*";this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}}class template_Pattern extends XFAObject{constructor(e){super(Go,"pattern",!0);this.id=e.id||"";this.type=getStringOption(e.type,["crossHatch","crossDiagonal","diagonalLeft","diagonalRight","horizontal","vertical"]);this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[ho](e){e=e?e[ho]():"#FFFFFF";const t=this.color?this.color[ho]():"#000000",a="repeating-linear-gradient",r=`${e},${e} 5px,${t} 5px,${t} 10px`;switch(this.type){case"crossHatch":return`${a}(to top,${r}) ${a}(to right,${r})`;case"crossDiagonal":return`${a}(45deg,${r}) ${a}(-45deg,${r})`;case"diagonalLeft":return`${a}(45deg,${r})`;case"diagonalRight":return`${a}(-45deg,${r})`;case"horizontal":return`${a}(to top,${r})`;case"vertical":return`${a}(to right,${r})`}return""}}class Picture extends StringObject{constructor(e){super(Go,"picture");this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Proto extends XFAObject{constructor(e){super(Go,"proto",!0);this.appearanceFilter=new XFAObjectArray;this.arc=new XFAObjectArray;this.area=new XFAObjectArray;this.assist=new XFAObjectArray;this.barcode=new XFAObjectArray;this.bindItems=new XFAObjectArray;this.bookend=new XFAObjectArray;this.boolean=new XFAObjectArray;this.border=new XFAObjectArray;this.break=new XFAObjectArray;this.breakAfter=new XFAObjectArray;this.breakBefore=new XFAObjectArray;this.button=new XFAObjectArray;this.calculate=new XFAObjectArray;this.caption=new XFAObjectArray;this.certificate=new XFAObjectArray;this.certificates=new XFAObjectArray;this.checkButton=new XFAObjectArray;this.choiceList=new XFAObjectArray;this.color=new XFAObjectArray;this.comb=new XFAObjectArray;this.connect=new XFAObjectArray;this.contentArea=new XFAObjectArray;this.corner=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.dateTimeEdit=new XFAObjectArray;this.decimal=new XFAObjectArray;this.defaultUi=new XFAObjectArray;this.desc=new XFAObjectArray;this.digestMethod=new XFAObjectArray;this.digestMethods=new XFAObjectArray;this.draw=new XFAObjectArray;this.edge=new XFAObjectArray;this.encoding=new XFAObjectArray;this.encodings=new XFAObjectArray;this.encrypt=new XFAObjectArray;this.encryptData=new XFAObjectArray;this.encryption=new XFAObjectArray;this.encryptionMethod=new XFAObjectArray;this.encryptionMethods=new XFAObjectArray;this.event=new XFAObjectArray;this.exData=new XFAObjectArray;this.exObject=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.execute=new XFAObjectArray;this.extras=new XFAObjectArray;this.field=new XFAObjectArray;this.fill=new XFAObjectArray;this.filter=new XFAObjectArray;this.float=new XFAObjectArray;this.font=new XFAObjectArray;this.format=new XFAObjectArray;this.handler=new XFAObjectArray;this.hyphenation=new XFAObjectArray;this.image=new XFAObjectArray;this.imageEdit=new XFAObjectArray;this.integer=new XFAObjectArray;this.issuers=new XFAObjectArray;this.items=new XFAObjectArray;this.keep=new XFAObjectArray;this.keyUsage=new XFAObjectArray;this.line=new XFAObjectArray;this.linear=new XFAObjectArray;this.lockDocument=new XFAObjectArray;this.manifest=new XFAObjectArray;this.margin=new XFAObjectArray;this.mdp=new XFAObjectArray;this.medium=new XFAObjectArray;this.message=new XFAObjectArray;this.numericEdit=new XFAObjectArray;this.occur=new XFAObjectArray;this.oid=new XFAObjectArray;this.oids=new XFAObjectArray;this.overflow=new XFAObjectArray;this.pageArea=new XFAObjectArray;this.pageSet=new XFAObjectArray;this.para=new XFAObjectArray;this.passwordEdit=new XFAObjectArray;this.pattern=new XFAObjectArray;this.picture=new XFAObjectArray;this.radial=new XFAObjectArray;this.reason=new XFAObjectArray;this.reasons=new XFAObjectArray;this.rectangle=new XFAObjectArray;this.ref=new XFAObjectArray;this.script=new XFAObjectArray;this.setProperty=new XFAObjectArray;this.signData=new XFAObjectArray;this.signature=new XFAObjectArray;this.signing=new XFAObjectArray;this.solid=new XFAObjectArray;this.speak=new XFAObjectArray;this.stipple=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray;this.subjectDN=new XFAObjectArray;this.subjectDNs=new XFAObjectArray;this.submit=new XFAObjectArray;this.text=new XFAObjectArray;this.textEdit=new XFAObjectArray;this.time=new XFAObjectArray;this.timeStamp=new XFAObjectArray;this.toolTip=new XFAObjectArray;this.traversal=new XFAObjectArray;this.traverse=new XFAObjectArray;this.ui=new XFAObjectArray;this.validate=new XFAObjectArray;this.value=new XFAObjectArray;this.variables=new XFAObjectArray}}class Radial extends XFAObject{constructor(e){super(Go,"radial",!0);this.id=e.id||"";this.type=getStringOption(e.type,["toEdge","toCenter"]);this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[ho](e){e=e?e[ho]():"#FFFFFF";const t=this.color?this.color[ho]():"#000000";return`radial-gradient(circle at center, ${"toEdge"===this.type?`${e},${t}`:`${t},${e}`})`}}class Reason extends StringObject{constructor(e){super(Go,"reason");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Reasons extends XFAObject{constructor(e){super(Go,"reasons",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.reason=new XFAObjectArray}}class Rectangle extends XFAObject{constructor(e){super(Go,"rectangle",!0);this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.corner=new XFAObjectArray(4);this.edge=new XFAObjectArray(4);this.fill=null}[co](){const e=this.edge.children.length?this.edge.children[0]:new Edge({}),t=e[ho](),a=Object.create(null);"visible"===this.fill?.presence?Object.assign(a,this.fill[ho]()):a.fill="transparent";a.strokeWidth=measureToString("visible"===e.presence?e.thickness:0);a.stroke=t.color;const r=(this.corner.children.length?this.corner.children[0]:new Corner({}))[ho](),i={name:"svg",children:[{name:"rect",attributes:{xmlns:Vo,width:"100%",height:"100%",x:0,y:0,rx:r.radius,ry:r.radius,style:a}}],attributes:{xmlns:Vo,style:{overflow:"visible"},width:"100%",height:"100%"}};if(hasMargin(this[vs]()[vs]()))return HTMLResult.success({name:"div",attributes:{style:{display:"inline",width:"100%",height:"100%"}},children:[i]});i.attributes.style.position="absolute";return HTMLResult.success(i)}}class RefElement extends StringObject{constructor(e){super(Go,"ref");this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Script extends StringObject{constructor(e){super(Go,"script");this.binding=e.binding||"";this.contentType=e.contentType||"";this.id=e.id||"";this.name=e.name||"";this.runAt=getStringOption(e.runAt,["client","both","server"]);this.use=e.use||"";this.usehref=e.usehref||""}}class SetProperty extends XFAObject{constructor(e){super(Go,"setProperty");this.connection=e.connection||"";this.ref=e.ref||"";this.target=e.target||""}}class SignData extends XFAObject{constructor(e){super(Go,"signData",!0);this.id=e.id||"";this.operation=getStringOption(e.operation,["sign","clear","verify"]);this.ref=e.ref||"";this.target=e.target||"";this.use=e.use||"";this.usehref=e.usehref||"";this.filter=null;this.manifest=null}}class Signature extends XFAObject{constructor(e){super(Go,"signature",!0);this.id=e.id||"";this.type=getStringOption(e.type,["PDF1.3","PDF1.6"]);this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.filter=null;this.manifest=null;this.margin=null}}class Signing extends XFAObject{constructor(e){super(Go,"signing",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=new XFAObjectArray}}class Solid extends XFAObject{constructor(e){super(Go,"solid",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[ho](e){return e?e[ho]():"#FFFFFF"}}class Speak extends StringObject{constructor(e){super(Go,"speak");this.disable=getInteger({data:e.disable,defaultValue:0,validate:e=>1===e});this.id=e.id||"";this.priority=getStringOption(e.priority,["custom","caption","name","toolTip"]);this.rid=e.rid||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Stipple extends XFAObject{constructor(e){super(Go,"stipple",!0);this.id=e.id||"";this.rate=getInteger({data:e.rate,defaultValue:50,validate:e=>e>=0&&e<=100});this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[ho](e){const t=this.rate/100;return Util.makeHexColor(Math.round(e.value.r*(1-t)+this.value.r*t),Math.round(e.value.g*(1-t)+this.value.g*t),Math.round(e.value.b*(1-t)+this.value.b*t))}}class Subform extends XFAObject{constructor(e){super(Go,"subform",!0);this.access=getStringOption(e.access,["open","nonInteractive","protected","readOnly"]);this.allowMacro=getInteger({data:e.allowMacro,defaultValue:0,validate:e=>1===e});this.anchorType=getStringOption(e.anchorType,["topLeft","bottomCenter","bottomLeft","bottomRight","middleCenter","middleLeft","middleRight","topCenter","topRight"]);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.columnWidths=(e.columnWidths||"").trim().split(/\s+/).map((e=>"-1"===e?-1:getMeasurement(e)));this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.layout=getStringOption(e.layout,["position","lr-tb","rl-row","rl-tb","row","table","tb"]);this.locale=e.locale||"";this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.mergeMode=getStringOption(e.mergeMode,["consumeData","matchTemplate"]);this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.restoreState=getStringOption(e.restoreState,["manual","auto"]);this.scope=getStringOption(e.scope,["name","none"]);this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.bind=null;this.bookend=null;this.border=null;this.break=null;this.calculate=null;this.desc=null;this.extras=null;this.keep=null;this.margin=null;this.occur=null;this.overflow=null;this.pageSet=null;this.para=null;this.traversal=null;this.validate=null;this.variables=null;this.area=new XFAObjectArray;this.breakAfter=new XFAObjectArray;this.breakBefore=new XFAObjectArray;this.connect=new XFAObjectArray;this.draw=new XFAObjectArray;this.event=new XFAObjectArray;this.exObject=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.field=new XFAObjectArray;this.proto=new XFAObjectArray;this.setProperty=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray}[Cs](){const e=this[vs]();return e instanceof SubformSet?e[Cs]():e}[Rs](){return!0}[js](){return this.layout.endsWith("-tb")&&0===this[ls].attempt&&this[ls].numberInLine>0||this[vs]()[js]()}*[As](){yield*getContainedChildren(this)}[us](){return flushHTML(this)}[Zn](e,t){addHTML(this,e,t)}[gs](){return getAvailableSpace(this)}[_s](){const e=this[Cs]();if(!e[_s]())return!1;if(void 0!==this[ls]._isSplittable)return this[ls]._isSplittable;if("position"===this.layout||this.layout.includes("row")){this[ls]._isSplittable=!1;return!1}if(this.keep&&"none"!==this.keep.intact){this[ls]._isSplittable=!1;return!1}if(e.layout?.endsWith("-tb")&&0!==e[ls].numberInLine)return!1;this[ls]._isSplittable=!0;return!0}[co](e){setTabIndex(this);if(this.break){if("auto"!==this.break.after||""!==this.break.afterTarget){const e=new BreakAfter({targetType:this.break.after,target:this.break.afterTarget,startNew:this.break.startNew.toString()});e[Is]=this[Is];this[Qn](e);this.breakAfter.push(e)}if("auto"!==this.break.before||""!==this.break.beforeTarget){const e=new BreakBefore({targetType:this.break.before,target:this.break.beforeTarget,startNew:this.break.startNew.toString()});e[Is]=this[Is];this[Qn](e);this.breakBefore.push(e)}if(""!==this.break.overflowTarget){const e=new Overflow({target:this.break.overflowTarget,leader:this.break.overflowLeader,trailer:this.break.overflowTrailer});e[Is]=this[Is];this[Qn](e);this.overflow.push(e)}this[Zs](this.break);this.break=null}if("hidden"===this.presence||"inactive"===this.presence)return HTMLResult.EMPTY;(this.breakBefore.children.length>1||this.breakAfter.children.length>1)&&warn("XFA - Several breakBefore or breakAfter in subforms: please file a bug.");if(this.breakBefore.children.length>=1){const e=this.breakBefore.children[0];if(handleBreak(e))return HTMLResult.breakNode(e)}if(this[ls]?.afterBreakAfter)return HTMLResult.EMPTY;fixDimensions(this);const t=[],a={id:this[uo],class:[]};setAccess(this,a.class);this[ls]||=Object.create(null);Object.assign(this[ls],{children:t,line:null,attributes:a,attempt:0,numberInLine:0,availableSpace:{width:Math.min(this.w||1/0,e.width),height:Math.min(this.h||1/0,e.height)},width:0,height:0,prevHeight:0,currentWidth:0});const r=this[Fs](),i=r[ls].noLayoutFailure,n=this[_s]();n||setFirstUnsplittable(this);if(!checkDimensions(this,e))return HTMLResult.FAILURE;const s=new Set(["area","draw","exclGroup","field","subform","subformSet"]);if(this.layout.includes("row")){const e=this[Cs]().columnWidths;if(Array.isArray(e)&&e.length>0){this[ls].columnWidths=e;this[ls].currentColumn=0}}const o=toStyle(this,"anchorType","dimensions","position","presence","border","margin","hAlign"),c=["xfaSubform"],l=layoutClass(this);l&&c.push(l);a.style=o;a.class=c;this.name&&(a.xfaName=this.name);if(this.overflow){const t=this.overflow[ws]();if(t.addLeader){t.addLeader=!1;handleOverflow(this,t.leader,e)}}this[Ys]();const h="lr-tb"===this.layout||"rl-tb"===this.layout,u=h?2:1;for(;this[ls].attempt=1){const e=this.breakAfter.children[0];if(handleBreak(e)){this[ls].afterBreakAfter=y;return HTMLResult.breakNode(e)}}delete this[ls];return y}}class SubformSet extends XFAObject{constructor(e){super(Go,"subformSet",!0);this.id=e.id||"";this.name=e.name||"";this.relation=getStringOption(e.relation,["ordered","choice","unordered"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.bookend=null;this.break=null;this.desc=null;this.extras=null;this.occur=null;this.overflow=null;this.breakAfter=new XFAObjectArray;this.breakBefore=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray}*[As](){yield*getContainedChildren(this)}[Cs](){let e=this[vs]();for(;!(e instanceof Subform);)e=e[vs]();return e}[Rs](){return!0}}class SubjectDN extends ContentObject{constructor(e){super(Go,"subjectDN");this.delimiter=e.delimiter||",";this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){this[ss]=new Map(this[ss].split(this.delimiter).map((e=>{(e=e.split("=",2))[0]=e[0].trim();return e})))}}class SubjectDNs extends XFAObject{constructor(e){super(Go,"subjectDNs",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.subjectDN=new XFAObjectArray}}class Submit extends XFAObject{constructor(e){super(Go,"submit",!0);this.embedPDF=getInteger({data:e.embedPDF,defaultValue:0,validate:e=>1===e});this.format=getStringOption(e.format,["xdp","formdata","pdf","urlencoded","xfd","xml"]);this.id=e.id||"";this.target=e.target||"";this.textEncoding=getKeyword({data:e.textEncoding?e.textEncoding.toLowerCase():"",defaultValue:"",validate:e=>["utf-8","big-five","fontspecific","gbk","gb-18030","gb-2312","ksc-5601","none","shift-jis","ucs-2","utf-16"].includes(e)||e.match(/iso-8859-\d{2}/)});this.use=e.use||"";this.usehref=e.usehref||"";this.xdpContent=e.xdpContent||"";this.encrypt=null;this.encryptData=new XFAObjectArray;this.signData=new XFAObjectArray}}class Template extends XFAObject{constructor(e){super(Go,"template",!0);this.baseProfile=getStringOption(e.baseProfile,["full","interactiveForms"]);this.extras=null;this.subform=new XFAObjectArray}[hs](){0===this.subform.children.length&&warn("XFA - No subforms in template node.");this.subform.children.length>=2&&warn("XFA - Several subforms in template node: please file a bug.");this[no]=5e3}[_s](){return!0}[to](e,t){return e.startsWith("#")?[this[Ts].get(e.slice(1))]:searchNode(this,t,e,!0,!0)}*[oo](){if(!this.subform.children.length)return HTMLResult.success({name:"div",children:[]});this[ls]={overflowNode:null,firstUnsplittable:null,currentContentArea:null,currentPageArea:null,noLayoutFailure:!1,pageNumber:1,pagePosition:"first",oddOrEven:"odd",blankOrNotBlank:"nonBlank",paraStack:[]};const e=this.subform.children[0];e.pageSet[as]();const t=e.pageSet.pageArea.children,a={name:"div",children:[]};let r=null,i=null,n=null;if(e.breakBefore.children.length>=1){i=e.breakBefore.children[0];n=i.target}else if(e.subform.children.length>=1&&e.subform.children[0].breakBefore.children.length>=1){i=e.subform.children[0].breakBefore.children[0];n=i.target}else if(e.break?.beforeTarget){i=e.break;n=i.beforeTarget}else if(e.subform.children.length>=1&&e.subform.children[0].break?.beforeTarget){i=e.subform.children[0].break;n=i.beforeTarget}if(i){const e=this[to](n,i[vs]());if(e instanceof PageArea){r=e;i[ls]={}}}r||=t[0];r[ls]={numberOfUse:1};const s=r[vs]();s[ls]={numberOfUse:1,pageIndex:s.pageArea.children.indexOf(r),pageSetIndex:0};let o,c=null,l=null,h=!0,u=0,d=0;for(;;){if(h)u=0;else{a.children.pop();if(3==++u){warn("XFA - Something goes wrong: please file a bug.");return a}}o=null;this[ls].currentPageArea=r;const t=r[co]().html;a.children.push(t);if(c){this[ls].noLayoutFailure=!0;t.children.push(c[co](r[ls].space).html);c=null}if(l){this[ls].noLayoutFailure=!0;t.children.push(l[co](r[ls].space).html);l=null}const i=r.contentArea.children,n=t.children.filter((e=>e.attributes.class.includes("xfaContentarea")));h=!1;this[ls].firstUnsplittable=null;this[ls].noLayoutFailure=!1;const flush=t=>{const a=e[us]();if(a){h||=a.children?.length>0;n[t].children.push(a)}};for(let t=d,r=i.length;t0;n[t].children.push(u.html)}else!h&&a.children.length>1&&a.children.pop();return a}if(u.isBreak()){const e=u.breakNode;flush(t);if("auto"===e.targetType)continue;if(e.leader){c=this[to](e.leader,e[vs]());c=c?c[0]:null}if(e.trailer){l=this[to](e.trailer,e[vs]());l=l?l[0]:null}if("pageArea"===e.targetType){o=e[ls].target;t=1/0}else if(e[ls].target){o=e[ls].target;d=e[ls].index+1;t=1/0}else t=e[ls].index}else if(this[ls].overflowNode){const e=this[ls].overflowNode;this[ls].overflowNode=null;const a=e[ws](),r=a.target;a.addLeader=null!==a.leader;a.addTrailer=null!==a.trailer;flush(t);const n=t;t=1/0;if(r instanceof PageArea)o=r;else if(r instanceof ContentArea){const e=i.indexOf(r);if(-1!==e)e>n?t=e-1:d=e;else{o=r[vs]();d=o.contentArea.children.indexOf(r)}}}else flush(t)}this[ls].pageNumber+=1;o&&(o[Xs]()?o[ls].numberOfUse+=1:o=null);r=o||r[ks]();yield null}}}class Text extends ContentObject{constructor(e){super(Go,"text");this.id=e.id||"";this.maxChars=getInteger({data:e.maxChars,defaultValue:0,validate:e=>e>=0});this.name=e.name||"";this.rid=e.rid||"";this.use=e.use||"";this.usehref=e.usehref||""}[Yn](){return!0}[$s](e){if(e[Hs]===go.xhtml.id){this[ss]=e;return!0}warn(`XFA - Invalid content in Text: ${e[Ws]}.`);return!1}[Vs](e){this[ss]instanceof XFAObject||super[Vs](e)}[hs](){"string"==typeof this[ss]&&(this[ss]=this[ss].replaceAll("\r\n","\n"))}[ws](){return"string"==typeof this[ss]?this[ss].split(/[\u2029\u2028\n]/).filter((e=>!!e)).join("\n"):this[ss][so]()}[co](e){if("string"==typeof this[ss]){const e=valueToHtml(this[ss]).html;if(this[ss].includes("\u2029")){e.name="div";e.children=[];this[ss].split("\u2029").map((e=>e.split(/[\u2028\n]/).flatMap((e=>[{name:"span",value:e},{name:"br"}])))).forEach((t=>{e.children.push({name:"p",children:t})}))}else if(/[\u2028\n]/.test(this[ss])){e.name="div";e.children=[];this[ss].split(/[\u2028\n]/).forEach((t=>{e.children.push({name:"span",value:t},{name:"br"})}))}return HTMLResult.success(e)}return this[ss][co](e)}}class TextEdit extends XFAObject{constructor(e){super(Go,"textEdit",!0);this.allowRichText=getInteger({data:e.allowRichText,defaultValue:0,validate:e=>1===e});this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.multiLine=getInteger({data:e.multiLine,defaultValue:"",validate:e=>0===e||1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.vScrollPolicy=getStringOption(e.vScrollPolicy,["auto","off","on"]);this.border=null;this.comb=null;this.extras=null;this.margin=null}[co](e){const t=toStyle(this,"border","font","margin");let a;const r=this[vs]()[vs]();""===this.multiLine&&(this.multiLine=r instanceof Draw?1:0);a=1===this.multiLine?{name:"textarea",attributes:{dataId:r[os]?.[uo]||r[uo],fieldId:r[uo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(r),"aria-required":!1}}:{name:"input",attributes:{type:"text",dataId:r[os]?.[uo]||r[uo],fieldId:r[uo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(r),"aria-required":!1}};if(isRequired(r)){a.attributes["aria-required"]=!0;a.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[a]})}}class Time extends StringObject{constructor(e){super(Go,"time");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[hs](){const e=this[ss].trim();this[ss]=e?new Date(e):null}[co](e){return valueToHtml(this[ss]?this[ss].toString():"")}}class TimeStamp extends XFAObject{constructor(e){super(Go,"timeStamp");this.id=e.id||"";this.server=e.server||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class ToolTip extends StringObject{constructor(e){super(Go,"toolTip");this.id=e.id||"";this.rid=e.rid||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Traversal extends XFAObject{constructor(e){super(Go,"traversal",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.traverse=new XFAObjectArray}}class Traverse extends XFAObject{constructor(e){super(Go,"traverse",!0);this.id=e.id||"";this.operation=getStringOption(e.operation,["next","back","down","first","left","right","up"]);this.ref=e.ref||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.script=null}get name(){return this.operation}[Us](){return!1}}class Ui extends XFAObject{constructor(e){super(Go,"ui",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.picture=null;this.barcode=null;this.button=null;this.checkButton=null;this.choiceList=null;this.dateTimeEdit=null;this.defaultUi=null;this.imageEdit=null;this.numericEdit=null;this.passwordEdit=null;this.signature=null;this.textEdit=null}[ws](){if(void 0===this[ls]){for(const e of Object.getOwnPropertyNames(this)){if("extras"===e||"picture"===e)continue;const t=this[e];if(t instanceof XFAObject){this[ls]=t;return t}}this[ls]=null}return this[ls]}[co](e){const t=this[ws]();return t?t[co](e):HTMLResult.EMPTY}}class Validate extends XFAObject{constructor(e){super(Go,"validate",!0);this.formatTest=getStringOption(e.formatTest,["warning","disabled","error"]);this.id=e.id||"";this.nullTest=getStringOption(e.nullTest,["disabled","error","warning"]);this.scriptTest=getStringOption(e.scriptTest,["error","disabled","warning"]);this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.message=null;this.picture=null;this.script=null}}class Value extends XFAObject{constructor(e){super(Go,"value",!0);this.id=e.id||"";this.override=getInteger({data:e.override,defaultValue:0,validate:e=>1===e});this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.arc=null;this.boolean=null;this.date=null;this.dateTime=null;this.decimal=null;this.exData=null;this.float=null;this.image=null;this.integer=null;this.line=null;this.rectangle=null;this.text=null;this.time=null}[io](e){const t=this[vs]();if(t instanceof Field&&t.ui?.imageEdit){if(!this.image){this.image=new Image({});this[Qn](this.image)}this.image[ss]=e[ss];return}const a=e[Ws];if(null===this[a]){for(const e of Object.getOwnPropertyNames(this)){const t=this[e];if(t instanceof XFAObject){this[e]=null;this[Zs](t)}}this[e[Ws]]=e;this[Qn](e)}else this[a][ss]=e[ss]}[so](){if(this.exData)return"string"==typeof this.exData[ss]?this.exData[ss].trim():this.exData[ss][so]().trim();for(const e of Object.getOwnPropertyNames(this)){if("image"===e)continue;const t=this[e];if(t instanceof XFAObject)return(t[ss]||"").toString().trim()}return null}[co](e){for(const t of Object.getOwnPropertyNames(this)){const a=this[t];if(a instanceof XFAObject)return a[co](e)}return HTMLResult.EMPTY}}class Variables extends XFAObject{constructor(e){super(Go,"variables",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.manifest=new XFAObjectArray;this.script=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}[Us](){return!0}}class TemplateNamespace{static[fo](e,t){if(TemplateNamespace.hasOwnProperty(e)){const a=TemplateNamespace[e](t);a[ro](t);return a}}static appearanceFilter(e){return new AppearanceFilter(e)}static arc(e){return new Arc(e)}static area(e){return new Area(e)}static assist(e){return new Assist(e)}static barcode(e){return new Barcode(e)}static bind(e){return new Bind(e)}static bindItems(e){return new BindItems(e)}static bookend(e){return new Bookend(e)}static boolean(e){return new BooleanElement(e)}static border(e){return new Border(e)}static break(e){return new Break(e)}static breakAfter(e){return new BreakAfter(e)}static breakBefore(e){return new BreakBefore(e)}static button(e){return new Button(e)}static calculate(e){return new Calculate(e)}static caption(e){return new Caption(e)}static certificate(e){return new Certificate(e)}static certificates(e){return new Certificates(e)}static checkButton(e){return new CheckButton(e)}static choiceList(e){return new ChoiceList(e)}static color(e){return new Color(e)}static comb(e){return new Comb(e)}static connect(e){return new Connect(e)}static contentArea(e){return new ContentArea(e)}static corner(e){return new Corner(e)}static date(e){return new DateElement(e)}static dateTime(e){return new DateTime(e)}static dateTimeEdit(e){return new DateTimeEdit(e)}static decimal(e){return new Decimal(e)}static defaultUi(e){return new DefaultUi(e)}static desc(e){return new Desc(e)}static digestMethod(e){return new DigestMethod(e)}static digestMethods(e){return new DigestMethods(e)}static draw(e){return new Draw(e)}static edge(e){return new Edge(e)}static encoding(e){return new Encoding(e)}static encodings(e){return new Encodings(e)}static encrypt(e){return new Encrypt(e)}static encryptData(e){return new EncryptData(e)}static encryption(e){return new Encryption(e)}static encryptionMethod(e){return new EncryptionMethod(e)}static encryptionMethods(e){return new EncryptionMethods(e)}static event(e){return new Event(e)}static exData(e){return new ExData(e)}static exObject(e){return new ExObject(e)}static exclGroup(e){return new ExclGroup(e)}static execute(e){return new Execute(e)}static extras(e){return new Extras(e)}static field(e){return new Field(e)}static fill(e){return new Fill(e)}static filter(e){return new Filter(e)}static float(e){return new Float(e)}static font(e){return new template_Font(e)}static format(e){return new Format(e)}static handler(e){return new Handler(e)}static hyphenation(e){return new Hyphenation(e)}static image(e){return new Image(e)}static imageEdit(e){return new ImageEdit(e)}static integer(e){return new Integer(e)}static issuers(e){return new Issuers(e)}static items(e){return new Items(e)}static keep(e){return new Keep(e)}static keyUsage(e){return new KeyUsage(e)}static line(e){return new Line(e)}static linear(e){return new Linear(e)}static lockDocument(e){return new LockDocument(e)}static manifest(e){return new Manifest(e)}static margin(e){return new Margin(e)}static mdp(e){return new Mdp(e)}static medium(e){return new Medium(e)}static message(e){return new Message(e)}static numericEdit(e){return new NumericEdit(e)}static occur(e){return new Occur(e)}static oid(e){return new Oid(e)}static oids(e){return new Oids(e)}static overflow(e){return new Overflow(e)}static pageArea(e){return new PageArea(e)}static pageSet(e){return new PageSet(e)}static para(e){return new Para(e)}static passwordEdit(e){return new PasswordEdit(e)}static pattern(e){return new template_Pattern(e)}static picture(e){return new Picture(e)}static proto(e){return new Proto(e)}static radial(e){return new Radial(e)}static reason(e){return new Reason(e)}static reasons(e){return new Reasons(e)}static rectangle(e){return new Rectangle(e)}static ref(e){return new RefElement(e)}static script(e){return new Script(e)}static setProperty(e){return new SetProperty(e)}static signData(e){return new SignData(e)}static signature(e){return new Signature(e)}static signing(e){return new Signing(e)}static solid(e){return new Solid(e)}static speak(e){return new Speak(e)}static stipple(e){return new Stipple(e)}static subform(e){return new Subform(e)}static subformSet(e){return new SubformSet(e)}static subjectDN(e){return new SubjectDN(e)}static subjectDNs(e){return new SubjectDNs(e)}static submit(e){return new Submit(e)}static template(e){return new Template(e)}static text(e){return new Text(e)}static textEdit(e){return new TextEdit(e)}static time(e){return new Time(e)}static timeStamp(e){return new TimeStamp(e)}static toolTip(e){return new ToolTip(e)}static traversal(e){return new Traversal(e)}static traverse(e){return new Traverse(e)}static ui(e){return new Ui(e)}static validate(e){return new Validate(e)}static value(e){return new Value(e)}static variables(e){return new Variables(e)}}const Zo=go.datasets.id;function createText(e){const t=new Text({});t[ss]=e;return t}class Binder{constructor(e){this.root=e;this.datasets=e.datasets;this.data=e.datasets?.data||new XmlObject(go.datasets.id,"data");this.emptyMerge=0===this.data[xs]().length;this.root.form=this.form=e.template[is]()}_isConsumeData(){return!this.emptyMerge&&this._mergeMode}_isMatchTemplate(){return!this._isConsumeData()}bind(){this._bindElement(this.form,this.data);return this.form}getData(){return this.data}_bindValue(e,t,a){e[os]=t;if(e[Os]())if(t[Ns]()){const a=t[ys]();e[io](createText(a))}else if(e instanceof Field&&"multiSelect"===e.ui?.choiceList?.open){const a=t[xs]().map((e=>e[ss].trim())).join("\n");e[io](createText(a))}else this._isConsumeData()&&warn("XFA - Nodes haven't the same type.");else!t[Ns]()||this._isMatchTemplate()?this._bindElement(e,t):warn("XFA - Nodes haven't the same type.")}_findDataByNameToConsume(e,t,a,r){if(!e)return null;let i,n;for(let r=0;r<3;r++){i=a[Ss](e,!1,!0);for(;;){n=i.next().value;if(!n)break;if(t===n[Ns]())return n}if(a[Hs]===go.datasets.id&&"data"===a[Ws])break;a=a[vs]()}if(!r)return null;i=this.data[Ss](e,!0,!1);n=i.next().value;if(n)return n;i=this.data[ds](e,!0);n=i.next().value;return n?.[Ns]()?n:null}_setProperties(e,t){if(e.hasOwnProperty("setProperty"))for(const{ref:a,target:r,connection:i}of e.setProperty.children){if(i)continue;if(!a)continue;const n=searchNode(this.root,t,a,!1,!1);if(!n){warn(`XFA - Invalid reference: ${a}.`);continue}const[s]=n;if(!s[Es](this.data)){warn("XFA - Invalid node: must be a data node.");continue}const o=searchNode(this.root,e,r,!1,!1);if(!o){warn(`XFA - Invalid target: ${r}.`);continue}const[c]=o;if(!c[Es](e)){warn("XFA - Invalid target: must be a property or subproperty.");continue}const l=c[vs]();if(c instanceof SetProperty||l instanceof SetProperty){warn("XFA - Invalid target: cannot be a setProperty or one of its properties.");continue}if(c instanceof BindItems||l instanceof BindItems){warn("XFA - Invalid target: cannot be a bindItems or one of its properties.");continue}const h=s[so](),u=c[Ws];if(c instanceof XFAAttribute){const e=Object.create(null);e[u]=h;const t=Reflect.construct(Object.getPrototypeOf(l).constructor,[e]);l[u]=t[u]}else if(c.hasOwnProperty(ss)){c[os]=s;c[ss]=h;c[hs]()}else warn("XFA - Invalid node to use in setProperty")}}_bindItems(e,t){if(!e.hasOwnProperty("items")||!e.hasOwnProperty("bindItems")||e.bindItems.isEmpty())return;for(const t of e.items.children)e[Zs](t);e.items.clear();const a=new Items({}),r=new Items({});e[Qn](a);e.items.push(a);e[Qn](r);e.items.push(r);for(const{ref:i,labelRef:n,valueRef:s,connection:o}of e.bindItems.children){if(o)continue;if(!i)continue;const e=searchNode(this.root,t,i,!1,!1);if(e)for(const t of e){if(!t[Es](this.datasets)){warn(`XFA - Invalid ref (${i}): must be a datasets child.`);continue}const e=searchNode(this.root,t,n,!0,!1);if(!e){warn(`XFA - Invalid label: ${n}.`);continue}const[o]=e;if(!o[Es](this.datasets)){warn("XFA - Invalid label: must be a datasets child.");continue}const c=searchNode(this.root,t,s,!0,!1);if(!c){warn(`XFA - Invalid value: ${s}.`);continue}const[l]=c;if(!l[Es](this.datasets)){warn("XFA - Invalid value: must be a datasets child.");continue}const h=createText(o[so]()),u=createText(l[so]());a[Qn](h);a.text.push(h);r[Qn](u);r.text.push(u)}else warn(`XFA - Invalid reference: ${i}.`)}}_bindOccurrences(e,t,a){let r;if(t.length>1){r=e[is]();r[Zs](r.occur);r.occur=null}this._bindValue(e,t[0],a);this._setProperties(e,t[0]);this._bindItems(e,t[0]);if(1===t.length)return;const i=e[vs](),n=e[Ws],s=i[Ms](e);for(let e=1,o=t.length;et.name===e.name)).length:a[r].children.length;const n=a[Ms](e)+1,s=t.initial-i;if(s){const t=e[is]();t[Zs](t.occur);t.occur=null;a[r].push(t);a[Ds](n,t);for(let e=1;e0)this._bindOccurrences(r,[e[0]],null);else if(this.emptyMerge){const e=t[Hs]===Zo?-1:t[Hs],a=r[os]=new XmlObject(e,r.name||"root");t[Qn](a);this._bindElement(r,a)}continue}if(!r[Rs]())continue;let e=!1,i=null,n=null,s=null;if(r.bind){switch(r.bind.match){case"none":this._setAndBind(r,t);continue;case"global":e=!0;break;case"dataRef":if(!r.bind.ref){warn(`XFA - ref is empty in node ${r[Ws]}.`);this._setAndBind(r,t);continue}n=r.bind.ref}r.bind.picture&&(i=r.bind.picture[ss])}const[o,c]=this._getOccurInfo(r);if(n){s=searchNode(this.root,t,n,!0,!1);if(null===s){s=createDataNode(this.data,t,n);if(!s)continue;this._isConsumeData()&&(s[ns]=!0);this._setAndBind(r,s);continue}this._isConsumeData()&&(s=s.filter((e=>!e[ns])));s.length>c?s=s.slice(0,c):0===s.length&&(s=null);s&&this._isConsumeData()&&s.forEach((e=>{e[ns]=!0}))}else{if(!r.name){this._setAndBind(r,t);continue}if(this._isConsumeData()){const a=[];for(;a.length0?a:null}else{s=t[Ss](r.name,!1,this.emptyMerge).next().value;if(!s){if(0===o){a.push(r);continue}const e=t[Hs]===Zo?-1:t[Hs];s=r[os]=new XmlObject(e,r.name);this.emptyMerge&&(s[ns]=!0);t[Qn](s);this._setAndBind(r,s);continue}this.emptyMerge&&(s[ns]=!0);s=[s]}}s?this._bindOccurrences(r,s,i):o>0?this._setAndBind(r,t):a.push(r)}a.forEach((e=>e[vs]()[Zs](e)))}}class DataHandler{constructor(e,t){this.data=t;this.dataset=e.datasets||null}serialize(e){const t=[[-1,this.data[xs]()]];for(;t.length>0;){const a=t.at(-1),[r,i]=a;if(r+1===i.length){t.pop();continue}const n=i[++a[0]],s=e.get(n[uo]);if(s)n[io](s);else{const t=n[fs]();for(const a of t.values()){const t=e.get(a[uo]);if(t){a[io](t);break}}}const o=n[xs]();o.length>0&&t.push([-1,o])}const a=[''];if(this.dataset)for(const e of this.dataset[xs]())"data"!==e[Ws]&&e[lo](a);this.data[lo](a);a.push("");return a.join("")}}const Qo=go.config.id;class Acrobat extends XFAObject{constructor(e){super(Qo,"acrobat",!0);this.acrobat7=null;this.autoSave=null;this.common=null;this.validate=null;this.validateApprovalSignatures=null;this.submitUrl=new XFAObjectArray}}class Acrobat7 extends XFAObject{constructor(e){super(Qo,"acrobat7",!0);this.dynamicRender=null}}class ADBE_JSConsole extends OptionObject{constructor(e){super(Qo,"ADBE_JSConsole",["delegate","Enable","Disable"])}}class ADBE_JSDebugger extends OptionObject{constructor(e){super(Qo,"ADBE_JSDebugger",["delegate","Enable","Disable"])}}class AddSilentPrint extends Option01{constructor(e){super(Qo,"addSilentPrint")}}class AddViewerPreferences extends Option01{constructor(e){super(Qo,"addViewerPreferences")}}class AdjustData extends Option10{constructor(e){super(Qo,"adjustData")}}class AdobeExtensionLevel extends IntegerObject{constructor(e){super(Qo,"adobeExtensionLevel",0,(e=>e>=1&&e<=8))}}class Agent extends XFAObject{constructor(e){super(Qo,"agent",!0);this.name=e.name?e.name.trim():"";this.common=new XFAObjectArray}}class AlwaysEmbed extends ContentObject{constructor(e){super(Qo,"alwaysEmbed")}}class Amd extends StringObject{constructor(e){super(Qo,"amd")}}class config_Area extends XFAObject{constructor(e){super(Qo,"area");this.level=getInteger({data:e.level,defaultValue:0,validate:e=>e>=1&&e<=3});this.name=getStringOption(e.name,["","barcode","coreinit","deviceDriver","font","general","layout","merge","script","signature","sourceSet","templateCache"])}}class Attributes extends OptionObject{constructor(e){super(Qo,"attributes",["preserve","delegate","ignore"])}}class AutoSave extends OptionObject{constructor(e){super(Qo,"autoSave",["disabled","enabled"])}}class Base extends StringObject{constructor(e){super(Qo,"base")}}class BatchOutput extends XFAObject{constructor(e){super(Qo,"batchOutput");this.format=getStringOption(e.format,["none","concat","zip","zipCompress"])}}class BehaviorOverride extends ContentObject{constructor(e){super(Qo,"behaviorOverride")}[hs](){this[ss]=new Map(this[ss].trim().split(/\s+/).filter((e=>e.includes(":"))).map((e=>e.split(":",2))))}}class Cache extends XFAObject{constructor(e){super(Qo,"cache",!0);this.templateCache=null}}class Change extends Option01{constructor(e){super(Qo,"change")}}class Common extends XFAObject{constructor(e){super(Qo,"common",!0);this.data=null;this.locale=null;this.localeSet=null;this.messaging=null;this.suppressBanner=null;this.template=null;this.validationMessaging=null;this.versionControl=null;this.log=new XFAObjectArray}}class Compress extends XFAObject{constructor(e){super(Qo,"compress");this.scope=getStringOption(e.scope,["imageOnly","document"])}}class CompressLogicalStructure extends Option01{constructor(e){super(Qo,"compressLogicalStructure")}}class CompressObjectStream extends Option10{constructor(e){super(Qo,"compressObjectStream")}}class Compression extends XFAObject{constructor(e){super(Qo,"compression",!0);this.compressLogicalStructure=null;this.compressObjectStream=null;this.level=null;this.type=null}}class Config extends XFAObject{constructor(e){super(Qo,"config",!0);this.acrobat=null;this.present=null;this.trace=null;this.agent=new XFAObjectArray}}class Conformance extends OptionObject{constructor(e){super(Qo,"conformance",["A","B"])}}class ContentCopy extends Option01{constructor(e){super(Qo,"contentCopy")}}class Copies extends IntegerObject{constructor(e){super(Qo,"copies",1,(e=>e>=1))}}class Creator extends StringObject{constructor(e){super(Qo,"creator")}}class CurrentPage extends IntegerObject{constructor(e){super(Qo,"currentPage",0,(e=>e>=0))}}class Data extends XFAObject{constructor(e){super(Qo,"data",!0);this.adjustData=null;this.attributes=null;this.incrementalLoad=null;this.outputXSL=null;this.range=null;this.record=null;this.startNode=null;this.uri=null;this.window=null;this.xsl=null;this.excludeNS=new XFAObjectArray;this.transform=new XFAObjectArray}}class Debug extends XFAObject{constructor(e){super(Qo,"debug",!0);this.uri=null}}class DefaultTypeface extends ContentObject{constructor(e){super(Qo,"defaultTypeface");this.writingScript=getStringOption(e.writingScript,["*","Arabic","Cyrillic","EastEuropeanRoman","Greek","Hebrew","Japanese","Korean","Roman","SimplifiedChinese","Thai","TraditionalChinese","Vietnamese"])}}class Destination extends OptionObject{constructor(e){super(Qo,"destination",["pdf","pcl","ps","webClient","zpl"])}}class DocumentAssembly extends Option01{constructor(e){super(Qo,"documentAssembly")}}class Driver extends XFAObject{constructor(e){super(Qo,"driver",!0);this.name=e.name?e.name.trim():"";this.fontInfo=null;this.xdc=null}}class DuplexOption extends OptionObject{constructor(e){super(Qo,"duplexOption",["simplex","duplexFlipLongEdge","duplexFlipShortEdge"])}}class DynamicRender extends OptionObject{constructor(e){super(Qo,"dynamicRender",["forbidden","required"])}}class Embed extends Option01{constructor(e){super(Qo,"embed")}}class config_Encrypt extends Option01{constructor(e){super(Qo,"encrypt")}}class config_Encryption extends XFAObject{constructor(e){super(Qo,"encryption",!0);this.encrypt=null;this.encryptionLevel=null;this.permissions=null}}class EncryptionLevel extends OptionObject{constructor(e){super(Qo,"encryptionLevel",["40bit","128bit"])}}class Enforce extends StringObject{constructor(e){super(Qo,"enforce")}}class Equate extends XFAObject{constructor(e){super(Qo,"equate");this.force=getInteger({data:e.force,defaultValue:1,validate:e=>0===e});this.from=e.from||"";this.to=e.to||""}}class EquateRange extends XFAObject{constructor(e){super(Qo,"equateRange");this.from=e.from||"";this.to=e.to||"";this._unicodeRange=e.unicodeRange||""}get unicodeRange(){const e=[],t=/U\+([0-9a-fA-F]+)/,a=this._unicodeRange;for(let r of a.split(",").map((e=>e.trim())).filter((e=>!!e))){r=r.split("-",2).map((e=>{const a=e.match(t);return a?parseInt(a[1],16):0}));1===r.length&&r.push(r[0]);e.push(r)}return shadow(this,"unicodeRange",e)}}class Exclude extends ContentObject{constructor(e){super(Qo,"exclude")}[hs](){this[ss]=this[ss].trim().split(/\s+/).filter((e=>e&&["calculate","close","enter","exit","initialize","ready","validate"].includes(e)))}}class ExcludeNS extends StringObject{constructor(e){super(Qo,"excludeNS")}}class FlipLabel extends OptionObject{constructor(e){super(Qo,"flipLabel",["usePrinterSetting","on","off"])}}class config_FontInfo extends XFAObject{constructor(e){super(Qo,"fontInfo",!0);this.embed=null;this.map=null;this.subsetBelow=null;this.alwaysEmbed=new XFAObjectArray;this.defaultTypeface=new XFAObjectArray;this.neverEmbed=new XFAObjectArray}}class FormFieldFilling extends Option01{constructor(e){super(Qo,"formFieldFilling")}}class GroupParent extends StringObject{constructor(e){super(Qo,"groupParent")}}class IfEmpty extends OptionObject{constructor(e){super(Qo,"ifEmpty",["dataValue","dataGroup","ignore","remove"])}}class IncludeXDPContent extends StringObject{constructor(e){super(Qo,"includeXDPContent")}}class IncrementalLoad extends OptionObject{constructor(e){super(Qo,"incrementalLoad",["none","forwardOnly"])}}class IncrementalMerge extends Option01{constructor(e){super(Qo,"incrementalMerge")}}class Interactive extends Option01{constructor(e){super(Qo,"interactive")}}class Jog extends OptionObject{constructor(e){super(Qo,"jog",["usePrinterSetting","none","pageSet"])}}class LabelPrinter extends XFAObject{constructor(e){super(Qo,"labelPrinter",!0);this.name=getStringOption(e.name,["zpl","dpl","ipl","tcpl"]);this.batchOutput=null;this.flipLabel=null;this.fontInfo=null;this.xdc=null}}class Layout extends OptionObject{constructor(e){super(Qo,"layout",["paginate","panel"])}}class Level extends IntegerObject{constructor(e){super(Qo,"level",0,(e=>e>0))}}class Linearized extends Option01{constructor(e){super(Qo,"linearized")}}class Locale extends StringObject{constructor(e){super(Qo,"locale")}}class LocaleSet extends StringObject{constructor(e){super(Qo,"localeSet")}}class Log extends XFAObject{constructor(e){super(Qo,"log",!0);this.mode=null;this.threshold=null;this.to=null;this.uri=null}}class MapElement extends XFAObject{constructor(e){super(Qo,"map",!0);this.equate=new XFAObjectArray;this.equateRange=new XFAObjectArray}}class MediumInfo extends XFAObject{constructor(e){super(Qo,"mediumInfo",!0);this.map=null}}class config_Message extends XFAObject{constructor(e){super(Qo,"message",!0);this.msgId=null;this.severity=null}}class Messaging extends XFAObject{constructor(e){super(Qo,"messaging",!0);this.message=new XFAObjectArray}}class Mode extends OptionObject{constructor(e){super(Qo,"mode",["append","overwrite"])}}class ModifyAnnots extends Option01{constructor(e){super(Qo,"modifyAnnots")}}class MsgId extends IntegerObject{constructor(e){super(Qo,"msgId",1,(e=>e>=1))}}class NameAttr extends StringObject{constructor(e){super(Qo,"nameAttr")}}class NeverEmbed extends ContentObject{constructor(e){super(Qo,"neverEmbed")}}class NumberOfCopies extends IntegerObject{constructor(e){super(Qo,"numberOfCopies",null,(e=>e>=2&&e<=5))}}class OpenAction extends XFAObject{constructor(e){super(Qo,"openAction",!0);this.destination=null}}class Output extends XFAObject{constructor(e){super(Qo,"output",!0);this.to=null;this.type=null;this.uri=null}}class OutputBin extends StringObject{constructor(e){super(Qo,"outputBin")}}class OutputXSL extends XFAObject{constructor(e){super(Qo,"outputXSL",!0);this.uri=null}}class Overprint extends OptionObject{constructor(e){super(Qo,"overprint",["none","both","draw","field"])}}class Packets extends StringObject{constructor(e){super(Qo,"packets")}[hs](){"*"!==this[ss]&&(this[ss]=this[ss].trim().split(/\s+/).filter((e=>["config","datasets","template","xfdf","xslt"].includes(e))))}}class PageOffset extends XFAObject{constructor(e){super(Qo,"pageOffset");this.x=getInteger({data:e.x,defaultValue:"useXDCSetting",validate:e=>!0});this.y=getInteger({data:e.y,defaultValue:"useXDCSetting",validate:e=>!0})}}class PageRange extends StringObject{constructor(e){super(Qo,"pageRange")}[hs](){const e=this[ss].trim().split(/\s+/).map((e=>parseInt(e,10))),t=[];for(let a=0,r=e.length;a!1))}}class Pcl extends XFAObject{constructor(e){super(Qo,"pcl",!0);this.name=e.name||"";this.batchOutput=null;this.fontInfo=null;this.jog=null;this.mediumInfo=null;this.outputBin=null;this.pageOffset=null;this.staple=null;this.xdc=null}}class Pdf extends XFAObject{constructor(e){super(Qo,"pdf",!0);this.name=e.name||"";this.adobeExtensionLevel=null;this.batchOutput=null;this.compression=null;this.creator=null;this.encryption=null;this.fontInfo=null;this.interactive=null;this.linearized=null;this.openAction=null;this.pdfa=null;this.producer=null;this.renderPolicy=null;this.scriptModel=null;this.silentPrint=null;this.submitFormat=null;this.tagged=null;this.version=null;this.viewerPreferences=null;this.xdc=null}}class Pdfa extends XFAObject{constructor(e){super(Qo,"pdfa",!0);this.amd=null;this.conformance=null;this.includeXDPContent=null;this.part=null}}class Permissions extends XFAObject{constructor(e){super(Qo,"permissions",!0);this.accessibleContent=null;this.change=null;this.contentCopy=null;this.documentAssembly=null;this.formFieldFilling=null;this.modifyAnnots=null;this.plaintextMetadata=null;this.print=null;this.printHighQuality=null}}class PickTrayByPDFSize extends Option01{constructor(e){super(Qo,"pickTrayByPDFSize")}}class config_Picture extends StringObject{constructor(e){super(Qo,"picture")}}class PlaintextMetadata extends Option01{constructor(e){super(Qo,"plaintextMetadata")}}class Presence extends OptionObject{constructor(e){super(Qo,"presence",["preserve","dissolve","dissolveStructure","ignore","remove"])}}class Present extends XFAObject{constructor(e){super(Qo,"present",!0);this.behaviorOverride=null;this.cache=null;this.common=null;this.copies=null;this.destination=null;this.incrementalMerge=null;this.layout=null;this.output=null;this.overprint=null;this.pagination=null;this.paginationOverride=null;this.script=null;this.validate=null;this.xdp=null;this.driver=new XFAObjectArray;this.labelPrinter=new XFAObjectArray;this.pcl=new XFAObjectArray;this.pdf=new XFAObjectArray;this.ps=new XFAObjectArray;this.submitUrl=new XFAObjectArray;this.webClient=new XFAObjectArray;this.zpl=new XFAObjectArray}}class Print extends Option01{constructor(e){super(Qo,"print")}}class PrintHighQuality extends Option01{constructor(e){super(Qo,"printHighQuality")}}class PrintScaling extends OptionObject{constructor(e){super(Qo,"printScaling",["appdefault","noScaling"])}}class PrinterName extends StringObject{constructor(e){super(Qo,"printerName")}}class Producer extends StringObject{constructor(e){super(Qo,"producer")}}class Ps extends XFAObject{constructor(e){super(Qo,"ps",!0);this.name=e.name||"";this.batchOutput=null;this.fontInfo=null;this.jog=null;this.mediumInfo=null;this.outputBin=null;this.staple=null;this.xdc=null}}class Range extends ContentObject{constructor(e){super(Qo,"range")}[hs](){this[ss]=this[ss].split(",",2).map((e=>e.split("-").map((e=>parseInt(e.trim(),10))))).filter((e=>e.every((e=>!isNaN(e))))).map((e=>{1===e.length&&e.push(e[0]);return e}))}}class Record extends ContentObject{constructor(e){super(Qo,"record")}[hs](){this[ss]=this[ss].trim();const e=parseInt(this[ss],10);!isNaN(e)&&e>=0&&(this[ss]=e)}}class Relevant extends ContentObject{constructor(e){super(Qo,"relevant")}[hs](){this[ss]=this[ss].trim().split(/\s+/)}}class Rename extends ContentObject{constructor(e){super(Qo,"rename")}[hs](){this[ss]=this[ss].trim();(this[ss].toLowerCase().startsWith("xml")||new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*","u").test(this[ss]))&&warn("XFA - Rename: invalid XFA name")}}class RenderPolicy extends OptionObject{constructor(e){super(Qo,"renderPolicy",["server","client"])}}class RunScripts extends OptionObject{constructor(e){super(Qo,"runScripts",["both","client","none","server"])}}class config_Script extends XFAObject{constructor(e){super(Qo,"script",!0);this.currentPage=null;this.exclude=null;this.runScripts=null}}class ScriptModel extends OptionObject{constructor(e){super(Qo,"scriptModel",["XFA","none"])}}class Severity extends OptionObject{constructor(e){super(Qo,"severity",["ignore","error","information","trace","warning"])}}class SilentPrint extends XFAObject{constructor(e){super(Qo,"silentPrint",!0);this.addSilentPrint=null;this.printerName=null}}class Staple extends XFAObject{constructor(e){super(Qo,"staple");this.mode=getStringOption(e.mode,["usePrinterSetting","on","off"])}}class StartNode extends StringObject{constructor(e){super(Qo,"startNode")}}class StartPage extends IntegerObject{constructor(e){super(Qo,"startPage",0,(e=>!0))}}class SubmitFormat extends OptionObject{constructor(e){super(Qo,"submitFormat",["html","delegate","fdf","xml","pdf"])}}class SubmitUrl extends StringObject{constructor(e){super(Qo,"submitUrl")}}class SubsetBelow extends IntegerObject{constructor(e){super(Qo,"subsetBelow",100,(e=>e>=0&&e<=100))}}class SuppressBanner extends Option01{constructor(e){super(Qo,"suppressBanner")}}class Tagged extends Option01{constructor(e){super(Qo,"tagged")}}class config_Template extends XFAObject{constructor(e){super(Qo,"template",!0);this.base=null;this.relevant=null;this.startPage=null;this.uri=null;this.xsl=null}}class Threshold extends OptionObject{constructor(e){super(Qo,"threshold",["trace","error","information","warning"])}}class To extends OptionObject{constructor(e){super(Qo,"to",["null","memory","stderr","stdout","system","uri"])}}class TemplateCache extends XFAObject{constructor(e){super(Qo,"templateCache");this.maxEntries=getInteger({data:e.maxEntries,defaultValue:5,validate:e=>e>=0})}}class Trace extends XFAObject{constructor(e){super(Qo,"trace",!0);this.area=new XFAObjectArray}}class Transform extends XFAObject{constructor(e){super(Qo,"transform",!0);this.groupParent=null;this.ifEmpty=null;this.nameAttr=null;this.picture=null;this.presence=null;this.rename=null;this.whitespace=null}}class Type extends OptionObject{constructor(e){super(Qo,"type",["none","ascii85","asciiHex","ccittfax","flate","lzw","runLength","native","xdp","mergedXDP"])}}class Uri extends StringObject{constructor(e){super(Qo,"uri")}}class config_Validate extends OptionObject{constructor(e){super(Qo,"validate",["preSubmit","prePrint","preExecute","preSave"])}}class ValidateApprovalSignatures extends ContentObject{constructor(e){super(Qo,"validateApprovalSignatures")}[hs](){this[ss]=this[ss].trim().split(/\s+/).filter((e=>["docReady","postSign"].includes(e)))}}class ValidationMessaging extends OptionObject{constructor(e){super(Qo,"validationMessaging",["allMessagesIndividually","allMessagesTogether","firstMessageOnly","noMessages"])}}class Version extends OptionObject{constructor(e){super(Qo,"version",["1.7","1.6","1.5","1.4","1.3","1.2"])}}class VersionControl extends XFAObject{constructor(e){super(Qo,"VersionControl");this.outputBelow=getStringOption(e.outputBelow,["warn","error","update"]);this.sourceAbove=getStringOption(e.sourceAbove,["warn","error"]);this.sourceBelow=getStringOption(e.sourceBelow,["update","maintain"])}}class ViewerPreferences extends XFAObject{constructor(e){super(Qo,"viewerPreferences",!0);this.ADBE_JSConsole=null;this.ADBE_JSDebugger=null;this.addViewerPreferences=null;this.duplexOption=null;this.enforce=null;this.numberOfCopies=null;this.pageRange=null;this.pickTrayByPDFSize=null;this.printScaling=null}}class WebClient extends XFAObject{constructor(e){super(Qo,"webClient",!0);this.name=e.name?e.name.trim():"";this.fontInfo=null;this.xdc=null}}class Whitespace extends OptionObject{constructor(e){super(Qo,"whitespace",["preserve","ltrim","normalize","rtrim","trim"])}}class Window extends ContentObject{constructor(e){super(Qo,"window")}[hs](){const e=this[ss].split(",",2).map((e=>parseInt(e.trim(),10)));if(e.some((e=>isNaN(e))))this[ss]=[0,0];else{1===e.length&&e.push(e[0]);this[ss]=e}}}class Xdc extends XFAObject{constructor(e){super(Qo,"xdc",!0);this.uri=new XFAObjectArray;this.xsl=new XFAObjectArray}}class Xdp extends XFAObject{constructor(e){super(Qo,"xdp",!0);this.packets=null}}class Xsl extends XFAObject{constructor(e){super(Qo,"xsl",!0);this.debug=null;this.uri=null}}class Zpl extends XFAObject{constructor(e){super(Qo,"zpl",!0);this.name=e.name?e.name.trim():"";this.batchOutput=null;this.flipLabel=null;this.fontInfo=null;this.xdc=null}}class ConfigNamespace{static[fo](e,t){if(ConfigNamespace.hasOwnProperty(e))return ConfigNamespace[e](t)}static acrobat(e){return new Acrobat(e)}static acrobat7(e){return new Acrobat7(e)}static ADBE_JSConsole(e){return new ADBE_JSConsole(e)}static ADBE_JSDebugger(e){return new ADBE_JSDebugger(e)}static addSilentPrint(e){return new AddSilentPrint(e)}static addViewerPreferences(e){return new AddViewerPreferences(e)}static adjustData(e){return new AdjustData(e)}static adobeExtensionLevel(e){return new AdobeExtensionLevel(e)}static agent(e){return new Agent(e)}static alwaysEmbed(e){return new AlwaysEmbed(e)}static amd(e){return new Amd(e)}static area(e){return new config_Area(e)}static attributes(e){return new Attributes(e)}static autoSave(e){return new AutoSave(e)}static base(e){return new Base(e)}static batchOutput(e){return new BatchOutput(e)}static behaviorOverride(e){return new BehaviorOverride(e)}static cache(e){return new Cache(e)}static change(e){return new Change(e)}static common(e){return new Common(e)}static compress(e){return new Compress(e)}static compressLogicalStructure(e){return new CompressLogicalStructure(e)}static compressObjectStream(e){return new CompressObjectStream(e)}static compression(e){return new Compression(e)}static config(e){return new Config(e)}static conformance(e){return new Conformance(e)}static contentCopy(e){return new ContentCopy(e)}static copies(e){return new Copies(e)}static creator(e){return new Creator(e)}static currentPage(e){return new CurrentPage(e)}static data(e){return new Data(e)}static debug(e){return new Debug(e)}static defaultTypeface(e){return new DefaultTypeface(e)}static destination(e){return new Destination(e)}static documentAssembly(e){return new DocumentAssembly(e)}static driver(e){return new Driver(e)}static duplexOption(e){return new DuplexOption(e)}static dynamicRender(e){return new DynamicRender(e)}static embed(e){return new Embed(e)}static encrypt(e){return new config_Encrypt(e)}static encryption(e){return new config_Encryption(e)}static encryptionLevel(e){return new EncryptionLevel(e)}static enforce(e){return new Enforce(e)}static equate(e){return new Equate(e)}static equateRange(e){return new EquateRange(e)}static exclude(e){return new Exclude(e)}static excludeNS(e){return new ExcludeNS(e)}static flipLabel(e){return new FlipLabel(e)}static fontInfo(e){return new config_FontInfo(e)}static formFieldFilling(e){return new FormFieldFilling(e)}static groupParent(e){return new GroupParent(e)}static ifEmpty(e){return new IfEmpty(e)}static includeXDPContent(e){return new IncludeXDPContent(e)}static incrementalLoad(e){return new IncrementalLoad(e)}static incrementalMerge(e){return new IncrementalMerge(e)}static interactive(e){return new Interactive(e)}static jog(e){return new Jog(e)}static labelPrinter(e){return new LabelPrinter(e)}static layout(e){return new Layout(e)}static level(e){return new Level(e)}static linearized(e){return new Linearized(e)}static locale(e){return new Locale(e)}static localeSet(e){return new LocaleSet(e)}static log(e){return new Log(e)}static map(e){return new MapElement(e)}static mediumInfo(e){return new MediumInfo(e)}static message(e){return new config_Message(e)}static messaging(e){return new Messaging(e)}static mode(e){return new Mode(e)}static modifyAnnots(e){return new ModifyAnnots(e)}static msgId(e){return new MsgId(e)}static nameAttr(e){return new NameAttr(e)}static neverEmbed(e){return new NeverEmbed(e)}static numberOfCopies(e){return new NumberOfCopies(e)}static openAction(e){return new OpenAction(e)}static output(e){return new Output(e)}static outputBin(e){return new OutputBin(e)}static outputXSL(e){return new OutputXSL(e)}static overprint(e){return new Overprint(e)}static packets(e){return new Packets(e)}static pageOffset(e){return new PageOffset(e)}static pageRange(e){return new PageRange(e)}static pagination(e){return new Pagination(e)}static paginationOverride(e){return new PaginationOverride(e)}static part(e){return new Part(e)}static pcl(e){return new Pcl(e)}static pdf(e){return new Pdf(e)}static pdfa(e){return new Pdfa(e)}static permissions(e){return new Permissions(e)}static pickTrayByPDFSize(e){return new PickTrayByPDFSize(e)}static picture(e){return new config_Picture(e)}static plaintextMetadata(e){return new PlaintextMetadata(e)}static presence(e){return new Presence(e)}static present(e){return new Present(e)}static print(e){return new Print(e)}static printHighQuality(e){return new PrintHighQuality(e)}static printScaling(e){return new PrintScaling(e)}static printerName(e){return new PrinterName(e)}static producer(e){return new Producer(e)}static ps(e){return new Ps(e)}static range(e){return new Range(e)}static record(e){return new Record(e)}static relevant(e){return new Relevant(e)}static rename(e){return new Rename(e)}static renderPolicy(e){return new RenderPolicy(e)}static runScripts(e){return new RunScripts(e)}static script(e){return new config_Script(e)}static scriptModel(e){return new ScriptModel(e)}static severity(e){return new Severity(e)}static silentPrint(e){return new SilentPrint(e)}static staple(e){return new Staple(e)}static startNode(e){return new StartNode(e)}static startPage(e){return new StartPage(e)}static submitFormat(e){return new SubmitFormat(e)}static submitUrl(e){return new SubmitUrl(e)}static subsetBelow(e){return new SubsetBelow(e)}static suppressBanner(e){return new SuppressBanner(e)}static tagged(e){return new Tagged(e)}static template(e){return new config_Template(e)}static templateCache(e){return new TemplateCache(e)}static threshold(e){return new Threshold(e)}static to(e){return new To(e)}static trace(e){return new Trace(e)}static transform(e){return new Transform(e)}static type(e){return new Type(e)}static uri(e){return new Uri(e)}static validate(e){return new config_Validate(e)}static validateApprovalSignatures(e){return new ValidateApprovalSignatures(e)}static validationMessaging(e){return new ValidationMessaging(e)}static version(e){return new Version(e)}static versionControl(e){return new VersionControl(e)}static viewerPreferences(e){return new ViewerPreferences(e)}static webClient(e){return new WebClient(e)}static whitespace(e){return new Whitespace(e)}static window(e){return new Window(e)}static xdc(e){return new Xdc(e)}static xdp(e){return new Xdp(e)}static xsl(e){return new Xsl(e)}static zpl(e){return new Zpl(e)}}const ec=go.connectionSet.id;class ConnectionSet extends XFAObject{constructor(e){super(ec,"connectionSet",!0);this.wsdlConnection=new XFAObjectArray;this.xmlConnection=new XFAObjectArray;this.xsdConnection=new XFAObjectArray}}class EffectiveInputPolicy extends XFAObject{constructor(e){super(ec,"effectiveInputPolicy");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class EffectiveOutputPolicy extends XFAObject{constructor(e){super(ec,"effectiveOutputPolicy");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Operation extends StringObject{constructor(e){super(ec,"operation");this.id=e.id||"";this.input=e.input||"";this.name=e.name||"";this.output=e.output||"";this.use=e.use||"";this.usehref=e.usehref||""}}class RootElement extends StringObject{constructor(e){super(ec,"rootElement");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class SoapAction extends StringObject{constructor(e){super(ec,"soapAction");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class SoapAddress extends StringObject{constructor(e){super(ec,"soapAddress");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class connection_set_Uri extends StringObject{constructor(e){super(ec,"uri");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class WsdlAddress extends StringObject{constructor(e){super(ec,"wsdlAddress");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class WsdlConnection extends XFAObject{constructor(e){super(ec,"wsdlConnection",!0);this.dataDescription=e.dataDescription||"";this.name=e.name||"";this.effectiveInputPolicy=null;this.effectiveOutputPolicy=null;this.operation=null;this.soapAction=null;this.soapAddress=null;this.wsdlAddress=null}}class XmlConnection extends XFAObject{constructor(e){super(ec,"xmlConnection",!0);this.dataDescription=e.dataDescription||"";this.name=e.name||"";this.uri=null}}class XsdConnection extends XFAObject{constructor(e){super(ec,"xsdConnection",!0);this.dataDescription=e.dataDescription||"";this.name=e.name||"";this.rootElement=null;this.uri=null}}class ConnectionSetNamespace{static[fo](e,t){if(ConnectionSetNamespace.hasOwnProperty(e))return ConnectionSetNamespace[e](t)}static connectionSet(e){return new ConnectionSet(e)}static effectiveInputPolicy(e){return new EffectiveInputPolicy(e)}static effectiveOutputPolicy(e){return new EffectiveOutputPolicy(e)}static operation(e){return new Operation(e)}static rootElement(e){return new RootElement(e)}static soapAction(e){return new SoapAction(e)}static soapAddress(e){return new SoapAddress(e)}static uri(e){return new connection_set_Uri(e)}static wsdlAddress(e){return new WsdlAddress(e)}static wsdlConnection(e){return new WsdlConnection(e)}static xmlConnection(e){return new XmlConnection(e)}static xsdConnection(e){return new XsdConnection(e)}}const tc=go.datasets.id;class datasets_Data extends XmlObject{constructor(e){super(tc,"data",e)}[Ls](){return!0}}class Datasets extends XFAObject{constructor(e){super(tc,"datasets",!0);this.data=null;this.Signature=null}[$s](e){const t=e[Ws];("data"===t&&e[Hs]===tc||"Signature"===t&&e[Hs]===go.signature.id)&&(this[t]=e);this[Qn](e)}}class DatasetsNamespace{static[fo](e,t){if(DatasetsNamespace.hasOwnProperty(e))return DatasetsNamespace[e](t)}static datasets(e){return new Datasets(e)}static data(e){return new datasets_Data(e)}}const ac=go.localeSet.id;class CalendarSymbols extends XFAObject{constructor(e){super(ac,"calendarSymbols",!0);this.name="gregorian";this.dayNames=new XFAObjectArray(2);this.eraNames=null;this.meridiemNames=null;this.monthNames=new XFAObjectArray(2)}}class CurrencySymbol extends StringObject{constructor(e){super(ac,"currencySymbol");this.name=getStringOption(e.name,["symbol","isoname","decimal"])}}class CurrencySymbols extends XFAObject{constructor(e){super(ac,"currencySymbols",!0);this.currencySymbol=new XFAObjectArray(3)}}class DatePattern extends StringObject{constructor(e){super(ac,"datePattern");this.name=getStringOption(e.name,["full","long","med","short"])}}class DatePatterns extends XFAObject{constructor(e){super(ac,"datePatterns",!0);this.datePattern=new XFAObjectArray(4)}}class DateTimeSymbols extends ContentObject{constructor(e){super(ac,"dateTimeSymbols")}}class Day extends StringObject{constructor(e){super(ac,"day")}}class DayNames extends XFAObject{constructor(e){super(ac,"dayNames",!0);this.abbr=getInteger({data:e.abbr,defaultValue:0,validate:e=>1===e});this.day=new XFAObjectArray(7)}}class Era extends StringObject{constructor(e){super(ac,"era")}}class EraNames extends XFAObject{constructor(e){super(ac,"eraNames",!0);this.era=new XFAObjectArray(2)}}class locale_set_Locale extends XFAObject{constructor(e){super(ac,"locale",!0);this.desc=e.desc||"";this.name="isoname";this.calendarSymbols=null;this.currencySymbols=null;this.datePatterns=null;this.dateTimeSymbols=null;this.numberPatterns=null;this.numberSymbols=null;this.timePatterns=null;this.typeFaces=null}}class locale_set_LocaleSet extends XFAObject{constructor(e){super(ac,"localeSet",!0);this.locale=new XFAObjectArray}}class Meridiem extends StringObject{constructor(e){super(ac,"meridiem")}}class MeridiemNames extends XFAObject{constructor(e){super(ac,"meridiemNames",!0);this.meridiem=new XFAObjectArray(2)}}class Month extends StringObject{constructor(e){super(ac,"month")}}class MonthNames extends XFAObject{constructor(e){super(ac,"monthNames",!0);this.abbr=getInteger({data:e.abbr,defaultValue:0,validate:e=>1===e});this.month=new XFAObjectArray(12)}}class NumberPattern extends StringObject{constructor(e){super(ac,"numberPattern");this.name=getStringOption(e.name,["full","long","med","short"])}}class NumberPatterns extends XFAObject{constructor(e){super(ac,"numberPatterns",!0);this.numberPattern=new XFAObjectArray(4)}}class NumberSymbol extends StringObject{constructor(e){super(ac,"numberSymbol");this.name=getStringOption(e.name,["decimal","grouping","percent","minus","zero"])}}class NumberSymbols extends XFAObject{constructor(e){super(ac,"numberSymbols",!0);this.numberSymbol=new XFAObjectArray(5)}}class TimePattern extends StringObject{constructor(e){super(ac,"timePattern");this.name=getStringOption(e.name,["full","long","med","short"])}}class TimePatterns extends XFAObject{constructor(e){super(ac,"timePatterns",!0);this.timePattern=new XFAObjectArray(4)}}class TypeFace extends XFAObject{constructor(e){super(ac,"typeFace",!0);this.name=""|e.name}}class TypeFaces extends XFAObject{constructor(e){super(ac,"typeFaces",!0);this.typeFace=new XFAObjectArray}}class LocaleSetNamespace{static[fo](e,t){if(LocaleSetNamespace.hasOwnProperty(e))return LocaleSetNamespace[e](t)}static calendarSymbols(e){return new CalendarSymbols(e)}static currencySymbol(e){return new CurrencySymbol(e)}static currencySymbols(e){return new CurrencySymbols(e)}static datePattern(e){return new DatePattern(e)}static datePatterns(e){return new DatePatterns(e)}static dateTimeSymbols(e){return new DateTimeSymbols(e)}static day(e){return new Day(e)}static dayNames(e){return new DayNames(e)}static era(e){return new Era(e)}static eraNames(e){return new EraNames(e)}static locale(e){return new locale_set_Locale(e)}static localeSet(e){return new locale_set_LocaleSet(e)}static meridiem(e){return new Meridiem(e)}static meridiemNames(e){return new MeridiemNames(e)}static month(e){return new Month(e)}static monthNames(e){return new MonthNames(e)}static numberPattern(e){return new NumberPattern(e)}static numberPatterns(e){return new NumberPatterns(e)}static numberSymbol(e){return new NumberSymbol(e)}static numberSymbols(e){return new NumberSymbols(e)}static timePattern(e){return new TimePattern(e)}static timePatterns(e){return new TimePatterns(e)}static typeFace(e){return new TypeFace(e)}static typeFaces(e){return new TypeFaces(e)}}const rc=go.signature.id;class signature_Signature extends XFAObject{constructor(e){super(rc,"signature",!0)}}class SignatureNamespace{static[fo](e,t){if(SignatureNamespace.hasOwnProperty(e))return SignatureNamespace[e](t)}static signature(e){return new signature_Signature(e)}}const ic=go.stylesheet.id;class Stylesheet extends XFAObject{constructor(e){super(ic,"stylesheet",!0)}}class StylesheetNamespace{static[fo](e,t){if(StylesheetNamespace.hasOwnProperty(e))return StylesheetNamespace[e](t)}static stylesheet(e){return new Stylesheet(e)}}const nc=go.xdp.id;class xdp_Xdp extends XFAObject{constructor(e){super(nc,"xdp",!0);this.uuid=e.uuid||"";this.timeStamp=e.timeStamp||"";this.config=null;this.connectionSet=null;this.datasets=null;this.localeSet=null;this.stylesheet=new XFAObjectArray;this.template=null}[Gs](e){const t=go[e[Ws]];return t&&e[Hs]===t.id}}class XdpNamespace{static[fo](e,t){if(XdpNamespace.hasOwnProperty(e))return XdpNamespace[e](t)}static xdp(e){return new xdp_Xdp(e)}}const sc=go.xhtml.id,oc=Symbol(),cc=new Set(["color","font","font-family","font-size","font-stretch","font-style","font-weight","margin","margin-bottom","margin-left","margin-right","margin-top","letter-spacing","line-height","orphans","page-break-after","page-break-before","page-break-inside","tab-interval","tab-stop","text-align","text-decoration","text-indent","vertical-align","widows","kerning-mode","xfa-font-horizontal-scale","xfa-font-vertical-scale","xfa-spacerun","xfa-tab-stops"]),lc=new Map([["page-break-after","breakAfter"],["page-break-before","breakBefore"],["page-break-inside","breakInside"],["kerning-mode",e=>"none"===e?"none":"normal"],["xfa-font-horizontal-scale",e=>`scaleX(${Math.max(0,parseInt(e)/100).toFixed(2)})`],["xfa-font-vertical-scale",e=>`scaleY(${Math.max(0,parseInt(e)/100).toFixed(2)})`],["xfa-spacerun",""],["xfa-tab-stops",""],["font-size",(e,t)=>measureToString(.99*(e=t.fontSize=Math.abs(getMeasurement(e))))],["letter-spacing",e=>measureToString(getMeasurement(e))],["line-height",e=>measureToString(getMeasurement(e))],["margin",e=>measureToString(getMeasurement(e))],["margin-bottom",e=>measureToString(getMeasurement(e))],["margin-left",e=>measureToString(getMeasurement(e))],["margin-right",e=>measureToString(getMeasurement(e))],["margin-top",e=>measureToString(getMeasurement(e))],["text-indent",e=>measureToString(getMeasurement(e))],["font-family",e=>e],["vertical-align",e=>measureToString(getMeasurement(e))]]),hc=/\s+/g,uc=/[\r\n]+/g,dc=/\r\n?/g;function mapStyle(e,t,a){const r=Object.create(null);if(!e)return r;const i=Object.create(null);for(const[t,a]of e.split(";").map((e=>e.split(":",2)))){const e=lc.get(t);if(""===e)continue;let n=a;e&&(n="string"==typeof e?e:e(a,i));t.endsWith("scale")?r.transform=r.transform?`${r[t]} ${n}`:n:r[t.replaceAll(/-([a-zA-Z])/g,((e,t)=>t.toUpperCase()))]=n}r.fontFamily&&setFontFamily({typeface:r.fontFamily,weight:r.fontWeight||"normal",posture:r.fontStyle||"normal",size:i.fontSize||0},t,t[Is].fontFinder,r);if(a&&r.verticalAlign&&"0px"!==r.verticalAlign&&r.fontSize){const e=.583,t=.333,a=getMeasurement(r.fontSize);r.fontSize=measureToString(a*e);r.verticalAlign=measureToString(Math.sign(getMeasurement(r.verticalAlign))*a*t)}a&&r.fontSize&&(r.fontSize=`calc(${r.fontSize} * var(--total-scale-factor))`);fixTextIndent(r);return r}const fc=new Set(["body","html"]);class XhtmlObject extends XmlObject{constructor(e,t){super(sc,t);this[oc]=!1;this.style=e.style||""}[ts](e){super[ts](e);this.style=function checkStyle(e){return e.style?e.style.split(";").filter((e=>!!e.trim())).map((e=>e.split(":",2).map((e=>e.trim())))).filter((([t,a])=>{"font-family"===t&&e[Is].usedTypefaces.add(a);return cc.has(t)})).map((e=>e.join(":"))).join(";"):""}(this)}[Yn](){return!fc.has(this[Ws])}[Vs](e,t=!1){if(t)this[oc]=!0;else{e=e.replaceAll(uc,"");this.style.includes("xfa-spacerun:yes")||(e=e.replaceAll(hc," "))}e&&(this[ss]+=e)}[Ks](e,t=!0){const a=Object.create(null),r={top:NaN,bottom:NaN,left:NaN,right:NaN};let i=null;for(const[e,t]of this.style.split(";").map((e=>e.split(":",2))))switch(e){case"font-family":a.typeface=stripQuotes(t);break;case"font-size":a.size=getMeasurement(t);break;case"font-weight":a.weight=t;break;case"font-style":a.posture=t;break;case"letter-spacing":a.letterSpacing=getMeasurement(t);break;case"margin":const e=t.split(/ \t/).map((e=>getMeasurement(e)));switch(e.length){case 1:r.top=r.bottom=r.left=r.right=e[0];break;case 2:r.top=r.bottom=e[0];r.left=r.right=e[1];break;case 3:r.top=e[0];r.bottom=e[2];r.left=r.right=e[1];break;case 4:r.top=e[0];r.left=e[1];r.bottom=e[2];r.right=e[3]}break;case"margin-top":r.top=getMeasurement(t);break;case"margin-bottom":r.bottom=getMeasurement(t);break;case"margin-left":r.left=getMeasurement(t);break;case"margin-right":r.right=getMeasurement(t);break;case"line-height":i=getMeasurement(t)}e.pushData(a,r,i);if(this[ss])e.addString(this[ss]);else for(const t of this[xs]())"#text"!==t[Ws]?t[Ks](e):e.addString(t[ss]);t&&e.popFont()}[co](e){const t=[];this[ls]={children:t};this[es]({});if(0===t.length&&!this[ss])return HTMLResult.EMPTY;let a;a=this[oc]?this[ss]?this[ss].replaceAll(dc,"\n"):void 0:this[ss]||void 0;return HTMLResult.success({name:this[Ws],attributes:{href:this.href,style:mapStyle(this.style,this,this[oc])},children:t,value:a})}}class A extends XhtmlObject{constructor(e){super(e,"a");this.href=fixURL(e.href)||""}}class B extends XhtmlObject{constructor(e){super(e,"b")}[Ks](e){e.pushFont({weight:"bold"});super[Ks](e);e.popFont()}}class Body extends XhtmlObject{constructor(e){super(e,"body")}[co](e){const t=super[co](e),{html:a}=t;if(!a)return HTMLResult.EMPTY;a.name="div";a.attributes.class=["xfaRich"];return t}}class Br extends XhtmlObject{constructor(e){super(e,"br")}[so](){return"\n"}[Ks](e){e.addString("\n")}[co](e){return HTMLResult.success({name:"br"})}}class Html extends XhtmlObject{constructor(e){super(e,"html")}[co](e){const t=[];this[ls]={children:t};this[es]({});if(0===t.length)return HTMLResult.success({name:"div",attributes:{class:["xfaRich"],style:{}},value:this[ss]||""});if(1===t.length){const e=t[0];if(e.attributes?.class.includes("xfaRich"))return HTMLResult.success(e)}return HTMLResult.success({name:"div",attributes:{class:["xfaRich"],style:{}},children:t})}}class I extends XhtmlObject{constructor(e){super(e,"i")}[Ks](e){e.pushFont({posture:"italic"});super[Ks](e);e.popFont()}}class Li extends XhtmlObject{constructor(e){super(e,"li")}}class Ol extends XhtmlObject{constructor(e){super(e,"ol")}}class P extends XhtmlObject{constructor(e){super(e,"p")}[Ks](e){super[Ks](e,!1);e.addString("\n");e.addPara();e.popFont()}[so](){return this[vs]()[xs]().at(-1)===this?super[so]():super[so]()+"\n"}}class Span extends XhtmlObject{constructor(e){super(e,"span")}}class Sub extends XhtmlObject{constructor(e){super(e,"sub")}}class Sup extends XhtmlObject{constructor(e){super(e,"sup")}}class Ul extends XhtmlObject{constructor(e){super(e,"ul")}}class XhtmlNamespace{static[fo](e,t){if(XhtmlNamespace.hasOwnProperty(e))return XhtmlNamespace[e](t)}static a(e){return new A(e)}static b(e){return new B(e)}static body(e){return new Body(e)}static br(e){return new Br(e)}static html(e){return new Html(e)}static i(e){return new I(e)}static li(e){return new Li(e)}static ol(e){return new Ol(e)}static p(e){return new P(e)}static span(e){return new Span(e)}static sub(e){return new Sub(e)}static sup(e){return new Sup(e)}static ul(e){return new Ul(e)}}const gc={config:ConfigNamespace,connection:ConnectionSetNamespace,datasets:DatasetsNamespace,localeSet:LocaleSetNamespace,signature:SignatureNamespace,stylesheet:StylesheetNamespace,template:TemplateNamespace,xdp:XdpNamespace,xhtml:XhtmlNamespace};class UnknownNamespace{constructor(e){this.namespaceId=e}[fo](e,t){return new XmlObject(this.namespaceId,e,t)}}class Root extends XFAObject{constructor(e){super(-1,"root",Object.create(null));this.element=null;this[Ts]=e}[$s](e){this.element=e;return!0}[hs](){super[hs]();if(this.element.template instanceof Template){this[Ts].set(Qs,this.element);this.element.template[eo](this[Ts]);this.element.template[Ts]=this[Ts]}}}class Empty extends XFAObject{constructor(){super(-1,"",Object.create(null))}[$s](e){return!1}}class Builder{constructor(e=null){this._namespaceStack=[];this._nsAgnosticLevel=0;this._namespacePrefixes=new Map;this._namespaces=new Map;this._nextNsId=Math.max(...Object.values(go).map((({id:e})=>e)));this._currentNamespace=e||new UnknownNamespace(++this._nextNsId)}buildRoot(e){return new Root(e)}build({nsPrefix:e,name:t,attributes:a,namespace:r,prefixes:i}){const n=null!==r;if(n){this._namespaceStack.push(this._currentNamespace);this._currentNamespace=this._searchNamespace(r)}i&&this._addNamespacePrefix(i);if(a.hasOwnProperty(zs)){const e=gc.datasets,t=a[zs];let r=null;for(const[a,i]of Object.entries(t)){if(this._getNamespaceToUse(a)===e){r={xfa:i};break}}r?a[zs]=r:delete a[zs]}const s=this._getNamespaceToUse(e),o=s?.[fo](t,a)||new Empty;o[Ls]()&&this._nsAgnosticLevel++;(n||i||o[Ls]())&&(o[rs]={hasNamespace:n,prefixes:i,nsAgnostic:o[Ls]()});return o}isNsAgnostic(){return this._nsAgnosticLevel>0}_searchNamespace(e){let t=this._namespaces.get(e);if(t)return t;for(const[a,{check:r}]of Object.entries(go))if(r(e)){t=gc[a];if(t){this._namespaces.set(e,t);return t}break}t=new UnknownNamespace(++this._nextNsId);this._namespaces.set(e,t);return t}_addNamespacePrefix(e){for(const{prefix:t,value:a}of e){const e=this._searchNamespace(a);let r=this._namespacePrefixes.get(t);if(!r){r=[];this._namespacePrefixes.set(t,r)}r.push(e)}}_getNamespaceToUse(e){if(!e)return this._currentNamespace;const t=this._namespacePrefixes.get(e);if(t?.length>0)return t.at(-1);warn(`Unknown namespace prefix: ${e}.`);return null}clean(e){const{hasNamespace:t,prefixes:a,nsAgnostic:r}=e;t&&(this._currentNamespace=this._namespaceStack.pop());a&&a.forEach((({prefix:e})=>{this._namespacePrefixes.get(e).pop()}));r&&this._nsAgnosticLevel--}}class XFAParser extends XMLParserBase{constructor(e=null,t=!1){super();this._builder=new Builder(e);this._stack=[];this._globalData={usedTypefaces:new Set};this._ids=new Map;this._current=this._builder.buildRoot(this._ids);this._errorCode=_n;this._whiteRegex=/^\s+$/;this._nbsps=/\xa0+/g;this._richText=t}parse(e){this.parseXml(e);if(this._errorCode===_n){this._current[hs]();return this._current.element}}onText(e){e=e.replace(this._nbsps,(e=>e.slice(1)+" "));this._richText||this._current[Yn]()?this._current[Vs](e,this._richText):this._whiteRegex.test(e)||this._current[Vs](e.trim())}onCdata(e){this._current[Vs](e)}_mkAttributes(e,t){let a=null,r=null;const i=Object.create({});for(const{name:n,value:s}of e)if("xmlns"===n)a?warn(`XFA - multiple namespace definition in <${t}>`):a=s;else if(n.startsWith("xmlns:")){const e=n.substring(6);r??=[];r.push({prefix:e,value:s})}else{const e=n.indexOf(":");if(-1===e)i[n]=s;else{const t=i[zs]??=Object.create(null),[a,r]=[n.slice(0,e),n.slice(e+1)];(t[a]||=Object.create(null))[r]=s}}return[a,r,i]}_getNameAndPrefix(e,t){const a=e.indexOf(":");return-1===a?[e,null]:[e.substring(a+1),t?"":e.substring(0,a)]}onBeginElement(e,t,a){const[r,i,n]=this._mkAttributes(t,e),[s,o]=this._getNameAndPrefix(e,this._builder.isNsAgnostic()),c=this._builder.build({nsPrefix:o,name:s,attributes:n,namespace:r,prefixes:i});c[Is]=this._globalData;if(a){c[hs]();this._current[$s](c)&&c[ao](this._ids);c[ts](this._builder)}else{this._stack.push(this._current);this._current=c}}onEndElement(e){const t=this._current;if(t[Bs]()&&"string"==typeof t[ss]){const e=new XFAParser;e._globalData=this._globalData;const a=e.parse(t[ss]);t[ss]=null;t[$s](a)}t[hs]();this._current=this._stack.pop();this._current[$s](t)&&t[ao](this._ids);t[ts](this._builder)}onError(e){this._errorCode=e}}class XFAFactory{constructor(e){try{this.root=(new XFAParser).parse(XFAFactory._createDocument(e));const t=new Binder(this.root);this.form=t.bind();this.dataHandler=new DataHandler(this.root,t.getData());this.form[Is].template=this.form}catch(e){warn(`XFA - an error occurred during parsing and binding: ${e}`)}}isValid(){return this.root&&this.form}_createPagesHelper(){const e=this.form[oo]();return new Promise(((t,a)=>{const nextIteration=()=>{try{const a=e.next();a.done?t(a.value):setTimeout(nextIteration,0)}catch(e){a(e)}};setTimeout(nextIteration,0)}))}async _createPages(){try{this.pages=await this._createPagesHelper();this.dims=this.pages.children.map((e=>{const{width:t,height:a}=e.attributes.style;return[0,0,parseInt(t),parseInt(a)]}))}catch(e){warn(`XFA - an error occurred during layout: ${e}`)}}getBoundingBox(e){return this.dims[e]}async getNumPages(){this.pages||await this._createPages();return this.dims.length}setImages(e){this.form[Is].images=e}setFonts(e){this.form[Is].fontFinder=new FontFinder(e);const t=[];for(let e of this.form[Is].usedTypefaces){e=stripQuotes(e);this.form[Is].fontFinder.find(e)||t.push(e)}return t.length>0?t:null}appendFonts(e,t){this.form[Is].fontFinder.add(e,t)}async getPages(){this.pages||await this._createPages();const e=this.pages;this.pages=null;return e}serializeData(e){return this.dataHandler.serialize(e)}static _createDocument(e){return e["/xdp:xdp"]?Object.values(e).join(""):e["xdp:xdp"]}static getRichTextAsHtml(e){if(!e||"string"!=typeof e)return null;try{let t=new XFAParser(XhtmlNamespace,!0).parse(e);if(!["body","xhtml"].includes(t[Ws])){const e=XhtmlNamespace.body({});e[Qn](t);t=e}const a=t[co]();if(!a.success)return null;const{html:r}=a,{attributes:i}=r;if(i){i.class&&(i.class=i.class.filter((e=>!e.startsWith("xfa"))));i.dir="auto"}return{html:r,str:t[so]()}}catch(e){warn(`XFA - an error occurred during parsing of rich text: ${e}`)}return null}}class AnnotationFactory{static createGlobals(e){return Promise.all([e.ensureCatalog("acroForm"),e.ensureDoc("xfaDatasets"),e.ensureCatalog("structTreeRoot"),e.ensureCatalog("baseUrl"),e.ensureCatalog("attachments"),e.ensureCatalog("globalColorSpaceCache")]).then((([t,a,r,i,n,s])=>({pdfManager:e,acroForm:t instanceof Dict?t:Dict.empty,xfaDatasets:a,structTreeRoot:r,baseUrl:i,attachments:n,globalColorSpaceCache:s})),(e=>{warn(`createGlobals: "${e}".`);return null}))}static async create(e,t,a,r,i,n,s){const o=i?await this._getPageIndex(e,t,a.pdfManager):null;return a.pdfManager.ensure(this,"_create",[e,t,a,r,i,n,o,s])}static _create(e,t,a,r,i=!1,n=null,s=null,o=null){const c=e.fetchIfRef(t);if(!(c instanceof Dict))return;const{acroForm:l,pdfManager:h}=a,u=t instanceof Ref?t.toString():`annot_${r.createObjId()}`;let d=c.get("Subtype");d=d instanceof Name?d.name:null;const f={xref:e,ref:t,dict:c,subtype:d,id:u,annotationGlobals:a,collectFields:i,orphanFields:n,needAppearances:!i&&!0===l.get("NeedAppearances"),pageIndex:s,evaluatorOptions:h.evaluatorOptions,pageRef:o};switch(d){case"Link":return new LinkAnnotation(f);case"Text":return new TextAnnotation(f);case"Widget":let e=getInheritableProperty({dict:c,key:"FT"});e=e instanceof Name?e.name:null;switch(e){case"Tx":return new TextWidgetAnnotation(f);case"Btn":return new ButtonWidgetAnnotation(f);case"Ch":return new ChoiceWidgetAnnotation(f);case"Sig":return new SignatureWidgetAnnotation(f)}warn(`Unimplemented widget field type "${e}", falling back to base field type.`);return new WidgetAnnotation(f);case"Popup":return new PopupAnnotation(f);case"FreeText":return new FreeTextAnnotation(f);case"Line":return new LineAnnotation(f);case"Square":return new SquareAnnotation(f);case"Circle":return new CircleAnnotation(f);case"PolyLine":return new PolylineAnnotation(f);case"Polygon":return new PolygonAnnotation(f);case"Caret":return new CaretAnnotation(f);case"Ink":return new InkAnnotation(f);case"Highlight":return new HighlightAnnotation(f);case"Underline":return new UnderlineAnnotation(f);case"Squiggly":return new SquigglyAnnotation(f);case"StrikeOut":return new StrikeOutAnnotation(f);case"Stamp":return new StampAnnotation(f);case"FileAttachment":return new FileAttachmentAnnotation(f);default:i||warn(d?`Unimplemented annotation type "${d}", falling back to base annotation.`:"Annotation is missing the required /Subtype.");return new Annotation(f)}}static async _getPageIndex(e,t,a){try{const r=await e.fetchIfRefAsync(t);if(!(r instanceof Dict))return-1;const i=r.getRaw("P");if(i instanceof Ref)try{return await a.ensureCatalog("getPageIndex",[i])}catch(e){info(`_getPageIndex -- not a valid page reference: "${e}".`)}if(r.has("Kids"))return-1;const n=await a.ensureDoc("numPages");for(let e=0;ee/255))}function getQuadPoints(e,t){const a=e.getArray("QuadPoints");if(!isNumberArray(a,null)||0===a.length||a.length%8>0)return null;const r=new Float32Array(a.length);for(let e=0,i=a.length;et[2]||gt[3]))return null;r.set([d,p,f,p,d,g,f,g],e)}return r}function getTransformMatrix(e,t,a){const r=new Float32Array([1/0,1/0,-1/0,-1/0]);Util.axialAlignedBoundingBox(t,a,r);const[i,n,s,o]=r;if(i===s||n===o)return[1,0,0,1,e[0],e[1]];const c=(e[2]-e[0])/(s-i),l=(e[3]-e[1])/(o-n);return[c,0,0,l,e[0]-i*c,e[1]-n*l]}class Annotation{constructor(e){const{dict:t,xref:a,annotationGlobals:r,ref:i,orphanFields:n}=e,s=n?.get(i);s&&t.set("Parent",s);this.setTitle(t.get("T"));this.setContents(t.get("Contents"));this.setModificationDate(t.get("M"));this.setFlags(t.get("F"));this.setRectangle(t.getArray("Rect"));this.setColor(t.getArray("C"));this.setBorderStyle(t);this.setAppearance(t);this.setOptionalContent(t);const o=t.get("MK");this.setBorderAndBackgroundColors(o);this.setRotation(o,t);this.ref=e.ref instanceof Ref?e.ref:null;this._streams=[];this.appearance&&this._streams.push(this.appearance);const c=!!(this.flags&ee),l=!!(this.flags&te);this.data={annotationFlags:this.flags,borderStyle:this.borderStyle,color:this.color,backgroundColor:this.backgroundColor,borderColor:this.borderColor,rotation:this.rotation,contentsObj:this._contents,hasAppearance:!!this.appearance,id:e.id,modificationDate:this.modificationDate,rect:this.rectangle,subtype:e.subtype,hasOwnCanvas:!1,noRotate:!!(this.flags&Z),noHTML:c&&l,isEditable:!1,structParent:-1};if(r.structTreeRoot){let a=t.get("StructParent");this.data.structParent=a=Number.isInteger(a)&&a>=0?a:-1;r.structTreeRoot.addAnnotationIdToPage(e.pageRef,a)}if(e.collectFields){const r=t.get("Kids");if(Array.isArray(r)){const e=[];for(const t of r)t instanceof Ref&&e.push(t.toString());0!==e.length&&(this.data.kidIds=e)}this.data.actions=collectActions(a,t,ye);this.data.fieldName=this._constructFieldName(t);this.data.pageIndex=e.pageIndex}const h=t.get("IT");h instanceof Name&&(this.data.it=h.name);this._isOffscreenCanvasSupported=e.evaluatorOptions.isOffscreenCanvasSupported;this._fallbackFontDict=null;this._needAppearances=!1}_hasFlag(e,t){return!!(e&t)}_buildFlags(e,t){let{flags:a}=this;if(void 0===e){if(void 0===t)return;return t?a&~Y:a&~J|Y}if(e){a|=Y;return t?a&~Q|J:a&~J|Q}a&=~(J|Q);return t?a&~Y:a|Y}_isViewable(e){return!this._hasFlag(e,K)&&!this._hasFlag(e,Q)}_isPrintable(e){return this._hasFlag(e,Y)&&!this._hasFlag(e,J)&&!this._hasFlag(e,K)}mustBeViewed(e,t){const a=e?.get(this.data.id)?.noView;return void 0!==a?!a:this.viewable&&!this._hasFlag(this.flags,J)}mustBePrinted(e){const t=e?.get(this.data.id)?.noPrint;return void 0!==t?!t:this.printable}mustBeViewedWhenEditing(e,t=null){return e?!this.data.isEditable:!t?.has(this.data.id)}get viewable(){return null!==this.data.quadPoints&&(0===this.flags||this._isViewable(this.flags))}get printable(){return null!==this.data.quadPoints&&(0!==this.flags&&this._isPrintable(this.flags))}_parseStringHelper(e){const t="string"==typeof e?stringToPDFString(e):"";return{str:t,dir:t&&"rtl"===bidi(t).dir?"rtl":"ltr"}}setDefaultAppearance(e){const{dict:t,annotationGlobals:a}=e,r=getInheritableProperty({dict:t,key:"DA"})||a.acroForm.get("DA");this._defaultAppearance="string"==typeof r?r:"";this.data.defaultAppearanceData=parseDefaultAppearance(this._defaultAppearance)}setTitle(e){this._title=this._parseStringHelper(e)}setContents(e){this._contents=this._parseStringHelper(e)}setModificationDate(e){this.modificationDate="string"==typeof e?e:null}setFlags(e){this.flags=Number.isInteger(e)&&e>0?e:0;this.flags&K&&"Annotation"!==this.constructor.name&&(this.flags^=K)}hasFlag(e){return this._hasFlag(this.flags,e)}setRectangle(e){this.rectangle=lookupNormalRect(e,[0,0,0,0])}setColor(e){this.color=getRgbColor(e)}setLineEndings(e){this.lineEndings=["None","None"];if(Array.isArray(e)&&2===e.length)for(let t=0;t<2;t++){const a=e[t];if(a instanceof Name)switch(a.name){case"None":continue;case"Square":case"Circle":case"Diamond":case"OpenArrow":case"ClosedArrow":case"Butt":case"ROpenArrow":case"RClosedArrow":case"Slash":this.lineEndings[t]=a.name;continue}warn(`Ignoring invalid lineEnding: ${a}`)}}setRotation(e,t){this.rotation=0;let a=e instanceof Dict?e.get("R")||0:t.get("Rotate")||0;if(Number.isInteger(a)&&0!==a){a%=360;a<0&&(a+=360);a%90==0&&(this.rotation=a)}}setBorderAndBackgroundColors(e){if(e instanceof Dict){this.borderColor=getRgbColor(e.getArray("BC"),null);this.backgroundColor=getRgbColor(e.getArray("BG"),null)}else this.borderColor=this.backgroundColor=null}setBorderStyle(e){this.borderStyle=new AnnotationBorderStyle;if(e instanceof Dict)if(e.has("BS")){const t=e.get("BS");if(t instanceof Dict){const e=t.get("Type");if(!e||isName(e,"Border")){this.borderStyle.setWidth(t.get("W"),this.rectangle);this.borderStyle.setStyle(t.get("S"));this.borderStyle.setDashArray(t.getArray("D"))}}}else if(e.has("Border")){const t=e.getArray("Border");if(Array.isArray(t)&&t.length>=3){this.borderStyle.setHorizontalCornerRadius(t[0]);this.borderStyle.setVerticalCornerRadius(t[1]);this.borderStyle.setWidth(t[2],this.rectangle);4===t.length&&this.borderStyle.setDashArray(t[3],!0)}}else this.borderStyle.setWidth(0)}setAppearance(e){this.appearance=null;const t=e.get("AP");if(!(t instanceof Dict))return;const a=t.get("N");if(a instanceof BaseStream){this.appearance=a;return}if(!(a instanceof Dict))return;const r=e.get("AS");if(!(r instanceof Name&&a.has(r.name)))return;const i=a.get(r.name);i instanceof BaseStream&&(this.appearance=i)}setOptionalContent(e){this.oc=null;const t=e.get("OC");t instanceof Name?warn("setOptionalContent: Support for /Name-entry is not implemented."):t instanceof Dict&&(this.oc=t)}loadResources(e,t){return t.dict.getAsync("Resources").then((t=>{if(!t)return;return new ObjectLoader(t,e,t.xref).load().then((()=>t))}))}async getOperatorList(e,t,a,r){const{hasOwnCanvas:i,id:n,rect:o}=this.data;let c=this.appearance;const l=!!(i&&a&s);if(l&&(0===this.width||0===this.height)){this.data.hasOwnCanvas=!1;return{opList:new OperatorList,separateForm:!1,separateCanvas:!1}}if(!c){if(!l)return{opList:new OperatorList,separateForm:!1,separateCanvas:!1};c=new StringStream("");c.dict=new Dict}const h=c.dict,u=await this.loadResources(Ia,c),d=lookupRect(h.getArray("BBox"),[0,0,1,1]),f=lookupMatrix(h.getArray("Matrix"),Fa),g=getTransformMatrix(o,d,f),p=new OperatorList;let m;this.oc&&(m=await e.parseMarkedContentProps(this.oc,null));void 0!==m&&p.addOp(_t,["OC",m]);p.addOp($t,[n,o,g,f,l]);await e.getOperatorList({stream:c,task:t,resources:u,operatorList:p,fallbackFontDict:this._fallbackFontDict});p.addOp(Gt,[]);void 0!==m&&p.addOp(jt,[]);this.reset();return{opList:p,separateForm:!1,separateCanvas:l}}async save(e,t,a,r){return null}get hasTextContent(){return!1}async extractTextContent(e,t,a){if(!this.appearance)return;const r=await this.loadResources(Oa,this.appearance),i=[],n=[];let s=null;const o={desiredSize:Math.Infinity,ready:!0,enqueue(e,t){for(const t of e.items)if(void 0!==t.str){s||=t.transform.slice(-2);n.push(t.str);if(t.hasEOL){i.push(n.join("").trimEnd());n.length=0}}}};await e.getTextContent({stream:this.appearance,task:t,resources:r,includeMarkedContent:!0,keepWhiteSpace:!0,sink:o,viewBox:a});this.reset();n.length&&i.push(n.join("").trimEnd());if(i.length>1||i[0]){const e=this.appearance.dict,t=lookupRect(e.getArray("BBox"),null),a=lookupMatrix(e.getArray("Matrix"),null);this.data.textPosition=this._transformPoint(s,t,a);this.data.textContent=i}}_transformPoint(e,t,a){const{rect:r}=this.data;t||=[0,0,1,1];a||=[1,0,0,1,0,0];const i=getTransformMatrix(r,t,a);i[4]-=r[0];i[5]-=r[1];const n=e.slice();Util.applyTransform(n,i);Util.applyTransform(n,a);return n}getFieldObject(){return this.data.kidIds?{id:this.data.id,actions:this.data.actions,name:this.data.fieldName,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,type:"",kidIds:this.data.kidIds,page:this.data.pageIndex,rotation:this.rotation}:null}reset(){for(const e of this._streams)e.reset()}_constructFieldName(e){if(!e.has("T")&&!e.has("Parent")){warn("Unknown field name, falling back to empty field name.");return""}if(!e.has("Parent"))return stringToPDFString(e.get("T"));const t=[];e.has("T")&&t.unshift(stringToPDFString(e.get("T")));let a=e;const r=new RefSet;e.objId&&r.put(e.objId);for(;a.has("Parent");){a=a.get("Parent");if(!(a instanceof Dict)||a.objId&&r.has(a.objId))break;a.objId&&r.put(a.objId);a.has("T")&&t.unshift(stringToPDFString(a.get("T")))}return t.join(".")}get width(){return this.data.rect[2]-this.data.rect[0]}get height(){return this.data.rect[3]-this.data.rect[1]}}class AnnotationBorderStyle{constructor(){this.width=1;this.rawWidth=1;this.style=fe;this.dashArray=[3];this.horizontalCornerRadius=0;this.verticalCornerRadius=0}setWidth(e,t=[0,0,0,0]){if(e instanceof Name)this.width=0;else if("number"==typeof e){if(e>0){this.rawWidth=e;const a=(t[2]-t[0])/2,r=(t[3]-t[1])/2;if(a>0&&r>0&&(e>a||e>r)){warn(`AnnotationBorderStyle.setWidth - ignoring width: ${e}`);e=1}}this.width=e}}setStyle(e){if(e instanceof Name)switch(e.name){case"S":this.style=fe;break;case"D":this.style=ge;break;case"B":this.style=pe;break;case"I":this.style=me;break;case"U":this.style=be}}setDashArray(e,t=!1){if(Array.isArray(e)){let a=!0,r=!0;for(const t of e){if(!(+t>=0)){a=!1;break}t>0&&(r=!1)}if(0===e.length||a&&!r){this.dashArray=e;t&&this.setStyle(Name.get("D"))}else this.width=0}else e&&(this.width=0)}setHorizontalCornerRadius(e){Number.isInteger(e)&&(this.horizontalCornerRadius=e)}setVerticalCornerRadius(e){Number.isInteger(e)&&(this.verticalCornerRadius=e)}}class MarkupAnnotation extends Annotation{constructor(e){super(e);const{dict:t}=e;if(t.has("IRT")){const e=t.getRaw("IRT");this.data.inReplyTo=e instanceof Ref?e.toString():null;const a=t.get("RT");this.data.replyType=a instanceof Name?a.name:V}let a=null;if(this.data.replyType===G){const e=t.get("IRT");this.setTitle(e.get("T"));this.data.titleObj=this._title;this.setContents(e.get("Contents"));this.data.contentsObj=this._contents;if(e.has("CreationDate")){this.setCreationDate(e.get("CreationDate"));this.data.creationDate=this.creationDate}else this.data.creationDate=null;if(e.has("M")){this.setModificationDate(e.get("M"));this.data.modificationDate=this.modificationDate}else this.data.modificationDate=null;a=e.getRaw("Popup");if(e.has("C")){this.setColor(e.getArray("C"));this.data.color=this.color}else this.data.color=null}else{this.data.titleObj=this._title;this.setCreationDate(t.get("CreationDate"));this.data.creationDate=this.creationDate;a=t.getRaw("Popup");t.has("C")||(this.data.color=null)}this.data.popupRef=a instanceof Ref?a.toString():null;t.has("RC")&&(this.data.richText=XFAFactory.getRichTextAsHtml(t.get("RC")))}setCreationDate(e){this.creationDate="string"==typeof e?e:null}_setDefaultAppearance({xref:e,extra:t,strokeColor:a,fillColor:r,blendMode:i,strokeAlpha:n,fillAlpha:s,pointsCallback:o}){const c=this.data.rect=[1/0,1/0,-1/0,-1/0],l=["q"];t&&l.push(t);a&&l.push(`${a[0]} ${a[1]} ${a[2]} RG`);r&&l.push(`${r[0]} ${r[1]} ${r[2]} rg`);const h=this.data.quadPoints||Float32Array.from([this.rectangle[0],this.rectangle[3],this.rectangle[2],this.rectangle[3],this.rectangle[0],this.rectangle[1],this.rectangle[2],this.rectangle[1]]);for(let e=0,t=h.length;e"string"==typeof e)).map((e=>stringToPDFString(e))):e instanceof Name?stringToPDFString(e.name):"string"==typeof e?stringToPDFString(e):null}hasFieldFlag(e){return!!(this.data.fieldFlags&e)}_isViewable(e){return!0}mustBeViewed(e,t){return t?this.viewable:super.mustBeViewed(e,t)&&!this._hasFlag(this.flags,Q)}getRotationMatrix(e){let t=e?.get(this.data.id)?.rotation;void 0===t&&(t=this.rotation);return 0===t?Fa:getRotationMatrix(t,this.width,this.height)}getBorderAndBackgroundAppearances(e){let t=e?.get(this.data.id)?.rotation;void 0===t&&(t=this.rotation);if(!this.backgroundColor&&!this.borderColor)return"";const a=0===t||180===t?`0 0 ${this.width} ${this.height} re`:`0 0 ${this.height} ${this.width} re`;let r="";this.backgroundColor&&(r=`${getPdfColor(this.backgroundColor,!0)} ${a} f `);if(this.borderColor){r+=`${this.borderStyle.width||1} w ${getPdfColor(this.borderColor,!1)} ${a} S `}return r}async getOperatorList(e,t,a,r){if(a&l&&!(this instanceof SignatureWidgetAnnotation)&&!this.data.noHTML&&!this.data.hasOwnCanvas)return{opList:new OperatorList,separateForm:!0,separateCanvas:!1};if(!this._hasText)return super.getOperatorList(e,t,a,r);const i=await this._getAppearance(e,t,a,r);if(this.appearance&&null===i)return super.getOperatorList(e,t,a,r);const n=new OperatorList;if(!this._defaultAppearance||null===i)return{opList:n,separateForm:!1,separateCanvas:!1};const o=!!(this.data.hasOwnCanvas&&a&s),c=[0,0,this.width,this.height],h=getTransformMatrix(this.data.rect,c,[1,0,0,1,0,0]);let u;this.oc&&(u=await e.parseMarkedContentProps(this.oc,null));void 0!==u&&n.addOp(_t,["OC",u]);n.addOp($t,[this.data.id,this.data.rect,h,this.getRotationMatrix(r),o]);const d=new StringStream(i);await e.getOperatorList({stream:d,task:t,resources:this._fieldResources.mergedResources,operatorList:n});n.addOp(Gt,[]);void 0!==u&&n.addOp(jt,[]);return{opList:n,separateForm:!1,separateCanvas:o}}_getMKDict(e){const t=new Dict(null);e&&t.set("R",e);this.borderColor&&t.set("BC",getPdfColorArray(this.borderColor));this.backgroundColor&&t.set("BG",getPdfColorArray(this.backgroundColor));return t.size>0?t:null}amendSavedDict(e,t){}setValue(e,t,a,r){const{dict:i,ref:n}=function getParentToUpdate(e,t,a){const r=new RefSet,i=e,n={dict:null,ref:null};for(;e instanceof Dict&&!r.has(t);){r.put(t);if(e.has("T"))break;if(!((t=e.getRaw("Parent"))instanceof Ref))return n;e=a.fetch(t)}if(e instanceof Dict&&e!==i){n.dict=e;n.ref=t}return n}(e,this.ref,a);if(i){if(!r.has(n)){const e=i.clone();e.set("V",t);r.put(n,{data:e});return e}}else e.set("V",t);return null}async save(e,t,a,r){const i=a?.get(this.data.id),n=this._buildFlags(i?.noView,i?.noPrint);let s=i?.value,o=i?.rotation;if(s===this.data.fieldValue||void 0===s){if(!this._hasValueFromXFA&&void 0===o&&void 0===n)return;s||=this.data.fieldValue}if(void 0===o&&!this._hasValueFromXFA&&Array.isArray(s)&&Array.isArray(this.data.fieldValue)&&isArrayEqual(s,this.data.fieldValue)&&void 0===n)return;void 0===o&&(o=this.rotation);let l=null;if(!this._needAppearances){l=await this._getAppearance(e,t,c,a);if(null===l&&void 0===n)return}let h=!1;if(l?.needAppearances){h=!0;l=null}const{xref:u}=e,d=u.fetchIfRef(this.ref);if(!(d instanceof Dict))return;const f=new Dict(u);for(const e of d.getKeys())"AP"!==e&&f.set(e,d.getRaw(e));if(void 0!==n){f.set("F",n);if(null===l&&!h){const e=d.getRaw("AP");e&&f.set("AP",e)}}const g={path:this.data.fieldName,value:s},p=this.setValue(f,Array.isArray(s)?s.map(stringToAsciiOrUTF16BE):stringToAsciiOrUTF16BE(s),u,r);this.amendSavedDict(a,p||f);const m=this._getMKDict(o);m&&f.set("MK",m);r.put(this.ref,{data:f,xfa:g,needAppearances:h});if(null!==l){const e=u.getNewTemporaryRef(),t=new Dict(u);f.set("AP",t);t.set("N",e);const i=this._getSaveFieldResources(u),n=new StringStream(l),s=n.dict=new Dict(u);s.set("Subtype",Name.get("Form"));s.set("Resources",i);s.set("BBox",[0,0,this.width,this.height]);const o=this.getRotationMatrix(a);o!==Fa&&s.set("Matrix",o);r.put(e,{data:n,xfa:null,needAppearances:!1})}f.set("M",`D:${getModificationDate()}`)}async _getAppearance(e,t,a,r){if(this.data.password)return null;const n=r?.get(this.data.id);let s,o;if(n){s=n.formattedValue||n.value;o=n.rotation}if(void 0===o&&void 0===s&&!this._needAppearances&&(!this._hasValueFromXFA||this.appearance))return null;const l=this.getBorderAndBackgroundAppearances(r);if(void 0===s){s=this.data.fieldValue;if(!s)return`/Tx BMC q ${l}Q EMC`}Array.isArray(s)&&1===s.length&&(s=s[0]);assert("string"==typeof s,"Expected `value` to be a string.");s=s.trimEnd();if(this.data.combo){const e=this.data.options.find((({exportValue:e})=>s===e));s=e?.displayValue||s}if(""===s)return`/Tx BMC q ${l}Q EMC`;void 0===o&&(o=this.rotation);let h,u=-1;if(this.data.multiLine){h=s.split(/\r\n?|\n/).map((e=>e.normalize("NFC")));u=h.length}else h=[s.replace(/\r\n?|\n/,"").normalize("NFC")];let{width:d,height:f}=this;90!==o&&270!==o||([d,f]=[f,d]);this._defaultAppearance||(this.data.defaultAppearanceData=parseDefaultAppearance(this._defaultAppearance="/Helvetica 0 Tf 0 g"));let g,p,m,b=await WidgetAnnotation._getFontData(e,t,this.data.defaultAppearanceData,this._fieldResources.mergedResources);const y=[];let w=!1;for(const e of h){const t=b.encodeString(e);t.length>1&&(w=!0);y.push(t.join(""))}if(w&&a&c)return{needAppearances:!0};if(w&&this._isOffscreenCanvasSupported){const a=this.data.comb?"monospace":"sans-serif",r=new FakeUnicodeFont(e.xref,a),i=r.createFontResources(h.join("")),n=i.getRaw("Font");if(this._fieldResources.mergedResources.has("Font")){const e=this._fieldResources.mergedResources.get("Font");for(const t of n.getKeys())e.set(t,n.getRaw(t))}else this._fieldResources.mergedResources.set("Font",n);const o=r.fontName.name;b=await WidgetAnnotation._getFontData(e,t,{fontName:o,fontSize:0},i);for(let e=0,t=y.length;e2)return`/Tx BMC q ${l}BT `+g+` 1 0 0 1 ${numberToString(2)} ${numberToString(C)} Tm (${escapeString(y[0])}) Tj ET Q EMC`;return`/Tx BMC q ${l}BT `+g+` 1 0 0 1 0 0 Tm ${this._renderText(y[0],b,p,d,k,{shift:0},2,C)} ET Q EMC`}static async _getFontData(e,t,a,r){const i=new OperatorList,n={font:null,clone(){return this}},{fontName:s,fontSize:o}=a;await e.handleSetFont(r,[s&&Name.get(s),o],null,i,t,n,null);return n.font}_getTextWidth(e,t){return Math.sumPrecise(t.charsToGlyphs(e).map((e=>e.width)))/1e3}_computeFontSize(e,t,r,i,n){let{fontSize:s}=this.data.defaultAppearanceData,o=(s||12)*a,c=Math.round(e/o);if(!s){const roundWithTwoDigits=e=>Math.floor(100*e)/100;if(-1===n){const n=this._getTextWidth(r,i);s=roundWithTwoDigits(Math.min(e/a,t/n));c=1}else{const l=r.split(/\r\n?|\n/),h=[];for(const e of l){const t=i.encodeString(e).join(""),a=i.charsToGlyphs(t),r=i.getCharPositions(t);h.push({line:t,glyphs:a,positions:r})}const isTooBig=a=>{let r=0;for(const n of h){r+=this._splitLine(null,i,a,t,n).length*a;if(r>e)return!0}return!1};c=Math.max(c,n);for(;;){o=e/c;s=roundWithTwoDigits(o/a);if(!isTooBig(s))break;c++}}const{fontName:l,fontColor:h}=this.data.defaultAppearanceData;this._defaultAppearance=function createDefaultAppearance({fontSize:e,fontName:t,fontColor:a}){return`/${escapePDFName(t)} ${e} Tf ${getPdfColor(a,!0)}`}({fontSize:s,fontName:l,fontColor:h})}return[this._defaultAppearance,s,e/c]}_renderText(e,t,a,r,i,n,s,o){let c;if(1===i){c=(r-this._getTextWidth(e,t)*a)/2}else if(2===i){c=r-this._getTextWidth(e,t)*a-s}else c=s;const l=numberToString(c-n.shift);n.shift=c;return`${l} ${o=numberToString(o)} Td (${escapeString(e)}) Tj`}_getSaveFieldResources(e){const{localResources:t,appearanceResources:a,acroFormResources:r}=this._fieldResources,i=this.data.defaultAppearanceData?.fontName;if(!i)return t||Dict.empty;for(const e of[t,a])if(e instanceof Dict){const t=e.get("Font");if(t instanceof Dict&&t.has(i))return e}if(r instanceof Dict){const a=r.get("Font");if(a instanceof Dict&&a.has(i)){const r=new Dict(e);r.set(i,a.getRaw(i));const n=new Dict(e);n.set("Font",r);return Dict.merge({xref:e,dictArray:[n,t],mergeSubDicts:!0})}}return t||Dict.empty}getFieldObject(){return null}}class TextWidgetAnnotation extends WidgetAnnotation{constructor(e){super(e);const{dict:t}=e;if(t.has("PMD")){this.flags|=J;this.data.hidden=!0;warn("Barcodes are not supported")}this.data.hasOwnCanvas=this.data.readOnly&&!this.data.noHTML;this._hasText=!0;"string"!=typeof this.data.fieldValue&&(this.data.fieldValue="");let a=getInheritableProperty({dict:t,key:"Q"});(!Number.isInteger(a)||a<0||a>2)&&(a=null);this.data.textAlignment=a;let r=getInheritableProperty({dict:t,key:"MaxLen"});(!Number.isInteger(r)||r<0)&&(r=0);this.data.maxLen=r;this.data.multiLine=this.hasFieldFlag(ie);this.data.comb=this.hasFieldFlag(de)&&!this.data.multiLine&&!this.data.password&&!this.hasFieldFlag(le)&&0!==this.data.maxLen;this.data.doNotScroll=this.hasFieldFlag(ue)}get hasTextContent(){return!!this.appearance&&!this._needAppearances}_getCombAppearance(e,t,a,r,i,n,s,o,c,l,h){const u=i/this.data.maxLen,d=this.getBorderAndBackgroundAppearances(h),f=[],g=t.getCharPositions(a);for(const[e,t]of g)f.push(`(${escapeString(a.substring(e,t))}) Tj`);const p=f.join(` ${numberToString(u)} 0 Td `);return`/Tx BMC q ${d}BT `+e+` 1 0 0 1 ${numberToString(s)} ${numberToString(o+c)} Tm ${p} ET Q EMC`}_getMultilineAppearance(e,t,a,r,i,n,s,o,c,l,h,u){const d=[],f=i-2*o,g={shift:0};for(let e=0,n=t.length;er){c.push(e.substring(d,a));d=a;f=p;l=-1;u=-1}else{f+=p;l=a;h=i;u=t}else if(f+p>r)if(-1!==l){c.push(e.substring(d,h));d=h;t=u+1;l=-1;f=0}else{c.push(e.substring(d,a));d=a;f=p}else f+=p}dt?`\\${t}`:"\\s+"));new RegExp(`^\\s*${n}\\s*$`).test(this.data.fieldValue)&&(this.data.textContent=this.data.fieldValue.split("\n"))}getFieldObject(){return{id:this.data.id,value:this.data.fieldValue,defaultValue:this.data.defaultFieldValue||"",multiline:this.data.multiLine,password:this.data.password,charLimit:this.data.maxLen,comb:this.data.comb,editable:!this.data.readOnly,hidden:this.data.hidden,name:this.data.fieldName,rect:this.data.rect,actions:this.data.actions,page:this.data.pageIndex,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,rotation:this.rotation,type:"text"}}}class ButtonWidgetAnnotation extends WidgetAnnotation{constructor(e){super(e);this.checkedAppearance=null;this.uncheckedAppearance=null;const t=this.hasFieldFlag(se),a=this.hasFieldFlag(oe);this.data.checkBox=!t&&!a;this.data.radioButton=t&&!a;this.data.pushButton=a;this.data.isTooltipOnly=!1;if(this.data.checkBox)this._processCheckBox(e);else if(this.data.radioButton)this._processRadioButton(e);else if(this.data.pushButton){this.data.hasOwnCanvas=!0;this.data.noHTML=!1;this._processPushButton(e)}else warn("Invalid field flags for button widget annotation")}async getOperatorList(e,t,a,r){if(this.data.pushButton)return super.getOperatorList(e,t,a,!1,r);let i=null,n=null;if(r){const e=r.get(this.data.id);i=e?e.value:null;n=e?e.rotation:null}if(null===i&&this.appearance)return super.getOperatorList(e,t,a,r);null==i&&(i=this.data.checkBox?this.data.fieldValue===this.data.exportValue:this.data.fieldValue===this.data.buttonValue);const s=i?this.checkedAppearance:this.uncheckedAppearance;if(s){const i=this.appearance,o=lookupMatrix(s.dict.getArray("Matrix"),Fa);n&&s.dict.set("Matrix",this.getRotationMatrix(r));this.appearance=s;const c=super.getOperatorList(e,t,a,r);this.appearance=i;s.dict.set("Matrix",o);return c}return{opList:new OperatorList,separateForm:!1,separateCanvas:!1}}async save(e,t,a,r){this.data.checkBox?this._saveCheckbox(e,t,a,r):this.data.radioButton&&this._saveRadioButton(e,t,a,r)}async _saveCheckbox(e,t,a,r){if(!a)return;const i=a.get(this.data.id),n=this._buildFlags(i?.noView,i?.noPrint);let s=i?.rotation,o=i?.value;if(void 0===s&&void 0===n){if(void 0===o)return;if(this.data.fieldValue===this.data.exportValue===o)return}let c=e.xref.fetchIfRef(this.ref);if(!(c instanceof Dict))return;c=c.clone();void 0===s&&(s=this.rotation);void 0===o&&(o=this.data.fieldValue===this.data.exportValue);const l={path:this.data.fieldName,value:o?this.data.exportValue:""},h=Name.get(o?this.data.exportValue:"Off");this.setValue(c,h,e.xref,r);c.set("AS",h);c.set("M",`D:${getModificationDate()}`);void 0!==n&&c.set("F",n);const u=this._getMKDict(s);u&&c.set("MK",u);r.put(this.ref,{data:c,xfa:l,needAppearances:!1})}async _saveRadioButton(e,t,a,r){if(!a)return;const i=a.get(this.data.id),n=this._buildFlags(i?.noView,i?.noPrint);let s=i?.rotation,o=i?.value;if(void 0===s&&void 0===n){if(void 0===o)return;if(this.data.fieldValue===this.data.buttonValue===o)return}let c=e.xref.fetchIfRef(this.ref);if(!(c instanceof Dict))return;c=c.clone();void 0===o&&(o=this.data.fieldValue===this.data.buttonValue);void 0===s&&(s=this.rotation);const l={path:this.data.fieldName,value:o?this.data.buttonValue:""},h=Name.get(o?this.data.buttonValue:"Off");o&&this.setValue(c,h,e.xref,r);c.set("AS",h);c.set("M",`D:${getModificationDate()}`);void 0!==n&&c.set("F",n);const u=this._getMKDict(s);u&&c.set("MK",u);r.put(this.ref,{data:c,xfa:l,needAppearances:!1})}_getDefaultCheckedAppearance(e,t){const{width:a,height:r}=this,i=[0,0,a,r],n=.8*Math.min(a,r);let s,o;if("check"===t){s={width:.755*n,height:.705*n};o="3"}else if("disc"===t){s={width:.791*n,height:.705*n};o="l"}else unreachable(`_getDefaultCheckedAppearance - unsupported type: ${t}`);const c=`q BT /PdfJsZaDb ${n} Tf 0 g ${numberToString((a-s.width)/2)} ${numberToString((r-s.height)/2)} Td (${o}) Tj ET Q`,l=new Dict(e.xref);l.set("FormType",1);l.set("Subtype",Name.get("Form"));l.set("Type",Name.get("XObject"));l.set("BBox",i);l.set("Matrix",[1,0,0,1,0,0]);l.set("Length",c.length);const h=new Dict(e.xref),u=new Dict(e.xref);u.set("PdfJsZaDb",this.fallbackFontDict);h.set("Font",u);l.set("Resources",h);this.checkedAppearance=new StringStream(c);this.checkedAppearance.dict=l;this._streams.push(this.checkedAppearance)}_processCheckBox(e){const t=e.dict.get("AP");if(!(t instanceof Dict))return;const a=t.get("N");if(!(a instanceof Dict))return;const r=this._decodeFormValue(e.dict.get("AS"));"string"==typeof r&&(this.data.fieldValue=r);const i=null!==this.data.fieldValue&&"Off"!==this.data.fieldValue?this.data.fieldValue:"Yes",n=a.getKeys();if(0===n.length)n.push("Off",i);else if(1===n.length)"Off"===n[0]?n.push(i):n.unshift("Off");else if(n.includes(i)){n.length=0;n.push("Off",i)}else{const e=n.find((e=>"Off"!==e));n.length=0;n.push("Off",e)}n.includes(this.data.fieldValue)||(this.data.fieldValue="Off");this.data.exportValue=n[1];const s=a.get(this.data.exportValue);this.checkedAppearance=s instanceof BaseStream?s:null;const o=a.get("Off");this.uncheckedAppearance=o instanceof BaseStream?o:null;this.checkedAppearance?this._streams.push(this.checkedAppearance):this._getDefaultCheckedAppearance(e,"check");this.uncheckedAppearance&&this._streams.push(this.uncheckedAppearance);this._fallbackFontDict=this.fallbackFontDict;null===this.data.defaultFieldValue&&(this.data.defaultFieldValue="Off")}_processRadioButton(e){this.data.buttonValue=null;const t=e.dict.get("Parent");if(t instanceof Dict){this.parent=e.dict.getRaw("Parent");const a=t.get("V");a instanceof Name&&(this.data.fieldValue=this._decodeFormValue(a))}const a=e.dict.get("AP");if(!(a instanceof Dict))return;const r=a.get("N");if(!(r instanceof Dict))return;for(const e of r.getKeys())if("Off"!==e){this.data.buttonValue=this._decodeFormValue(e);break}const i=r.get(this.data.buttonValue);this.checkedAppearance=i instanceof BaseStream?i:null;const n=r.get("Off");this.uncheckedAppearance=n instanceof BaseStream?n:null;this.checkedAppearance?this._streams.push(this.checkedAppearance):this._getDefaultCheckedAppearance(e,"disc");this.uncheckedAppearance&&this._streams.push(this.uncheckedAppearance);this._fallbackFontDict=this.fallbackFontDict;null===this.data.defaultFieldValue&&(this.data.defaultFieldValue="Off")}_processPushButton(e){const{dict:t,annotationGlobals:a}=e;if(t.has("A")||t.has("AA")||this.data.alternativeText){this.data.isTooltipOnly=!t.has("A")&&!t.has("AA");Catalog.parseDestDictionary({destDict:t,resultObj:this.data,docBaseUrl:a.baseUrl,docAttachments:a.attachments})}else warn("Push buttons without action dictionaries are not supported")}getFieldObject(){let e,t="button";if(this.data.checkBox){t="checkbox";e=this.data.exportValue}else if(this.data.radioButton){t="radiobutton";e=this.data.buttonValue}return{id:this.data.id,value:this.data.fieldValue||"Off",defaultValue:this.data.defaultFieldValue,exportValues:e,editable:!this.data.readOnly,name:this.data.fieldName,rect:this.data.rect,hidden:this.data.hidden,actions:this.data.actions,page:this.data.pageIndex,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,rotation:this.rotation,type:t}}get fallbackFontDict(){const e=new Dict;e.set("BaseFont",Name.get("ZapfDingbats"));e.set("Type",Name.get("FallbackType"));e.set("Subtype",Name.get("FallbackType"));e.set("Encoding",Name.get("ZapfDingbatsEncoding"));return shadow(this,"fallbackFontDict",e)}}class ChoiceWidgetAnnotation extends WidgetAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.indices=t.getArray("I");this.hasIndices=Array.isArray(this.indices)&&this.indices.length>0;this.data.options=[];const r=getInheritableProperty({dict:t,key:"Opt"});if(Array.isArray(r))for(let e=0,t=r.length;e=0&&t0&&(this.data.options=this.data.fieldValue.map((e=>({exportValue:e,displayValue:e}))));this.data.combo=this.hasFieldFlag(ce);this.data.multiSelect=this.hasFieldFlag(he);this._hasText=!0}getFieldObject(){const e=this.data.combo?"combobox":"listbox",t=this.data.fieldValue.length>0?this.data.fieldValue[0]:null;return{id:this.data.id,value:t,defaultValue:this.data.defaultFieldValue,editable:!this.data.readOnly,name:this.data.fieldName,rect:this.data.rect,numItems:this.data.fieldValue.length,multipleSelection:this.data.multiSelect,hidden:this.data.hidden,actions:this.data.actions,items:this.data.options,page:this.data.pageIndex,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,rotation:this.rotation,type:e}}amendSavedDict(e,t){if(!this.hasIndices)return;let a=e?.get(this.data.id)?.value;Array.isArray(a)||(a=[a]);const r=[],{options:i}=this.data;for(let e=0,t=0,n=i.length;ea){a=r;t=e}}[f,g]=this._computeFontSize(e,c-4,t,d,-1)}const p=g*a,m=(p-g)/2,b=Math.floor(l/p);let y=0;if(u.length>0){const e=Math.min(...u),t=Math.max(...u);y=Math.max(0,t-b+1);y>e&&(y=e)}const w=Math.min(y+b+1,h),S=["/Tx BMC q",`1 1 ${c} ${l} re W n`];if(u.length){S.push("0.600006 0.756866 0.854904 rg");for(const e of u)y<=e&&ee.trimEnd()));const{coords:e,bbox:t,matrix:r}=FakeUnicodeFont.getFirstPositionInfo(this.rectangle,this.rotation,a);this.data.textPosition=this._transformPoint(e,t,r)}if(this._isOffscreenCanvasSupported){const i=e.dict.get("CA"),n=new FakeUnicodeFont(r,"sans-serif");this.appearance=n.createAppearance(this._contents.str,this.rectangle,this.rotation,a,t,i);this._streams.push(this.appearance)}else warn("FreeTextAnnotation: OffscreenCanvas is not supported, annotation may not render correctly.")}}get hasTextContent(){return this._hasAppearance}static createNewDict(e,t,{apRef:a,ap:r}){const{color:i,fontSize:n,oldAnnotation:s,rect:o,rotation:c,user:l,value:h}=e,u=s||new Dict(t);u.set("Type",Name.get("Annot"));u.set("Subtype",Name.get("FreeText"));if(s){u.set("M",`D:${getModificationDate()}`);u.delete("RC")}else u.set("CreationDate",`D:${getModificationDate()}`);u.set("Rect",o);const d=`/Helv ${n} Tf ${getPdfColor(i,!0)}`;u.set("DA",d);u.set("Contents",stringToAsciiOrUTF16BE(h));u.set("F",4);u.set("Border",[0,0,0]);u.set("Rotate",c);l&&u.set("T",stringToAsciiOrUTF16BE(l));if(a||r){const e=new Dict(t);u.set("AP",e);a?e.set("N",a):e.set("N",r)}return u}static async createNewAppearanceStream(e,t,r){const{baseFontRef:i,evaluator:n,task:s}=r,{color:o,fontSize:c,rect:l,rotation:h,value:u}=e,d=new Dict(t),f=new Dict(t);if(i)f.set("Helv",i);else{const e=new Dict(t);e.set("BaseFont",Name.get("Helvetica"));e.set("Type",Name.get("Font"));e.set("Subtype",Name.get("Type1"));e.set("Encoding",Name.get("WinAnsiEncoding"));f.set("Helv",e)}d.set("Font",f);const g=await WidgetAnnotation._getFontData(n,s,{fontName:"Helv",fontSize:c},d),[p,m,b,y]=l;let w=b-p,S=y-m;h%180!=0&&([w,S]=[S,w]);const x=u.split("\n"),k=c/1e3;let C=-1/0;const v=[];for(let e of x){const t=g.encodeString(e);if(t.length>1)return null;e=t.join("");v.push(e);let a=0;const r=g.charsToGlyphs(e);for(const e of r)a+=e.width*k;C=Math.max(C,a)}let F=1;C>w&&(F=w/C);let O=1;const T=a*c,M=1*c,D=T*x.length;D>S&&(O=S/D);const R=c*Math.min(F,O);let N,E,L;switch(h){case 0:L=[1,0,0,1];E=[l[0],l[1],w,S];N=[l[0],l[3]-M];break;case 90:L=[0,1,-1,0];E=[l[1],-l[2],w,S];N=[l[1],-l[0]-M];break;case 180:L=[-1,0,0,-1];E=[-l[2],-l[3],w,S];N=[-l[2],-l[1]-M];break;case 270:L=[0,-1,1,0];E=[-l[3],l[0],w,S];N=[-l[3],l[2]-M]}const _=["q",`${L.join(" ")} 0 0 cm`,`${E.join(" ")} re W n`,"BT",`${getPdfColor(o,!0)}`,`0 Tc /Helv ${numberToString(R)} Tf`];_.push(`${N.join(" ")} Td (${escapeString(v[0])}) Tj`);const j=numberToString(T);for(let e=1,t=v.length;e{e.push(`${r[0]} ${r[1]} m`,`${r[2]} ${r[3]} l`,"S");return[t[0]-c,t[7]-c,t[2]+c,t[3]+c]}})}}}class SquareAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=D;this.data.hasOwnCanvas=this.data.noRotate;this.data.noHTML=!1;if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA"),i=getRgbColor(t.getArray("IC"),null),n=i?getPdfColorArray(i):null,s=n?r:null;if(0===this.borderStyle.width&&!n)return;this._setDefaultAppearance({xref:a,extra:`${this.borderStyle.width} w`,strokeColor:e,fillColor:n,strokeAlpha:r,fillAlpha:s,pointsCallback:(e,t)=>{const a=t[4]+this.borderStyle.width/2,r=t[5]+this.borderStyle.width/2,i=t[6]-t[4]-this.borderStyle.width,s=t[3]-t[7]-this.borderStyle.width;e.push(`${a} ${r} ${i} ${s} re`);n?e.push("B"):e.push("S");return[t[0],t[7],t[2],t[3]]}})}}}class CircleAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=R;if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA"),i=getRgbColor(t.getArray("IC"),null),n=i?getPdfColorArray(i):null,s=n?r:null;if(0===this.borderStyle.width&&!n)return;const o=4/3*Math.tan(Math.PI/8);this._setDefaultAppearance({xref:a,extra:`${this.borderStyle.width} w`,strokeColor:e,fillColor:n,strokeAlpha:r,fillAlpha:s,pointsCallback:(e,t)=>{const a=t[0]+this.borderStyle.width/2,r=t[1]-this.borderStyle.width/2,i=t[6]-this.borderStyle.width/2,s=t[7]+this.borderStyle.width/2,c=a+(i-a)/2,l=r+(s-r)/2,h=(i-a)/2*o,u=(s-r)/2*o;e.push(`${c} ${s} m`,`${c+h} ${s} ${i} ${l+u} ${i} ${l} c`,`${i} ${l-u} ${c+h} ${r} ${c} ${r} c`,`${c-h} ${r} ${a} ${l-u} ${a} ${l} c`,`${a} ${l+u} ${c-h} ${s} ${c} ${s} c`,"h");n?e.push("B"):e.push("S");return[t[0],t[7],t[2],t[3]]}})}}}class PolylineAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=E;this.data.hasOwnCanvas=this.data.noRotate;this.data.noHTML=!1;this.data.vertices=null;if(!(this instanceof PolygonAnnotation)){this.setLineEndings(t.getArray("LE"));this.data.lineEndings=this.lineEndings}const r=t.getArray("Vertices");if(!isNumberArray(r,null))return;const i=this.data.vertices=Float32Array.from(r);if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA"),n=this.borderStyle.width||1,s=2*n,o=[1/0,1/0,-1/0,-1/0];for(let e=0,t=i.length;e{for(let t=0,a=i.length;t{for(const t of this.data.inkLists){for(let a=0,r=t.length;a{e.push(`${t[0]} ${t[1]} m`,`${t[2]} ${t[3]} l`,`${t[6]} ${t[7]} l`,`${t[4]} ${t[5]} l`,"f");return[t[0],t[7],t[2],t[3]]}})}}else this.data.popupRef=null}static createNewDict(e,t,{apRef:a,ap:r}){const{color:i,oldAnnotation:n,opacity:s,rect:o,rotation:c,user:l,quadPoints:h}=e,u=n||new Dict(t);u.set("Type",Name.get("Annot"));u.set("Subtype",Name.get("Highlight"));u.set(n?"M":"CreationDate",`D:${getModificationDate()}`);u.set("CreationDate",`D:${getModificationDate()}`);u.set("Rect",o);u.set("F",4);u.set("Border",[0,0,0]);u.set("Rotate",c);u.set("QuadPoints",h);u.set("C",getPdfColorArray(i));u.set("CA",s);l&&u.set("T",stringToAsciiOrUTF16BE(l));if(a||r){const e=new Dict(t);u.set("AP",e);e.set("N",a||r)}return u}static async createNewAppearanceStream(e,t,a){const{color:r,rect:i,outlines:n,opacity:s}=e,o=[`${getPdfColor(r,!0)}`,"/R0 gs"],c=[];for(const e of n){c.length=0;c.push(`${numberToString(e[0])} ${numberToString(e[1])} m`);for(let t=2,a=e.length;t{e.push(`${t[4]} ${t[5]+1.3} m`,`${t[6]} ${t[7]+1.3} l`,"S");return[t[0],t[7],t[2],t[3]]}})}}else this.data.popupRef=null}}class SquigglyAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=j;if(this.data.quadPoints=getQuadPoints(t,null)){if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA");this._setDefaultAppearance({xref:a,extra:"[] 0 d 1 w",strokeColor:e,strokeAlpha:r,pointsCallback:(e,t)=>{const a=(t[1]-t[5])/6;let r=a,i=t[4];const n=t[5],s=t[6];e.push(`${i} ${n+r} m`);do{i+=2;r=0===r?a:0;e.push(`${i} ${n+r} l`)}while(i{e.push((t[0]+t[4])/2+" "+(t[1]+t[5])/2+" m",(t[2]+t[6])/2+" "+(t[3]+t[7])/2+" l","S");return[t[0],t[7],t[2],t[3]]}})}}else this.data.popupRef=null}}class StampAnnotation extends MarkupAnnotation{#ne=null;constructor(e){super(e);this.data.annotationType=X;this.data.hasOwnCanvas=this.data.noRotate;this.data.isEditable=!this.data.noHTML;this.data.noHTML=!1}mustBeViewedWhenEditing(e,t=null){if(e){if(!this.data.isEditable)return!0;this.#ne??=this.data.hasOwnCanvas;this.data.hasOwnCanvas=!0;return!0}if(null!==this.#ne){this.data.hasOwnCanvas=this.#ne;this.#ne=null}return!t?.has(this.data.id)}static async createImage(e,t){const{width:a,height:r}=e,i=new OffscreenCanvas(a,r),n=i.getContext("2d",{alpha:!0});n.drawImage(e,0,0);const s=n.getImageData(0,0,a,r).data,o=new Uint32Array(s.buffer),c=o.some(FeatureTest.isLittleEndian?e=>e>>>24!=255:e=>!!(255&~e));if(c){n.fillStyle="white";n.fillRect(0,0,a,r);n.drawImage(e,0,0)}const l=i.convertToBlob({type:"image/jpeg",quality:1}).then((e=>e.arrayBuffer())),h=Name.get("XObject"),u=Name.get("Image"),d=new Dict(t);d.set("Type",h);d.set("Subtype",u);d.set("BitsPerComponent",8);d.set("ColorSpace",Name.get("DeviceRGB"));d.set("Filter",Name.get("DCTDecode"));d.set("BBox",[0,0,a,r]);d.set("Width",a);d.set("Height",r);let f=null;if(c){const e=new Uint8Array(o.length);if(FeatureTest.isLittleEndian)for(let t=0,a=o.length;t>>24;else for(let t=0,a=o.length;t=0&&n<=1?n:null}}const pc={get r(){return shadow(this,"r",new Uint8Array([7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21]))},get k(){return shadow(this,"k",new Int32Array([-680876936,-389564586,606105819,-1044525330,-176418897,1200080426,-1473231341,-45705983,1770035416,-1958414417,-42063,-1990404162,1804603682,-40341101,-1502002290,1236535329,-165796510,-1069501632,643717713,-373897302,-701558691,38016083,-660478335,-405537848,568446438,-1019803690,-187363961,1163531501,-1444681467,-51403784,1735328473,-1926607734,-378558,-2022574463,1839030562,-35309556,-1530992060,1272893353,-155497632,-1094730640,681279174,-358537222,-722521979,76029189,-640364487,-421815835,530742520,-995338651,-198630844,1126891415,-1416354905,-57434055,1700485571,-1894986606,-1051523,-2054922799,1873313359,-30611744,-1560198380,1309151649,-145523070,-1120210379,718787259,-343485551]))}};function calculateMD5(e,t,a){let r=1732584193,i=-271733879,n=-1732584194,s=271733878;const o=a+72&-64,c=new Uint8Array(o);let l,h;for(l=0;l>5&255;c[l++]=a>>13&255;c[l++]=a>>21&255;c[l++]=a>>>29&255;l+=3;const d=new Int32Array(16),{k:f,r:g}=pc;for(l=0;l>>32-n)|0;a=r}r=r+a|0;i=i+o|0;n=n+u|0;s=s+p|0}return new Uint8Array([255&r,r>>8&255,r>>16&255,r>>>24&255,255&i,i>>8&255,i>>16&255,i>>>24&255,255&n,n>>8&255,n>>16&255,n>>>24&255,255&s,s>>8&255,s>>16&255,s>>>24&255])}function decodeString(e){try{return stringToUTF8String(e)}catch(t){warn(`UTF-8 decoding failed: "${t}".`);return e}}class DatasetXMLParser extends SimpleXMLParser{constructor(e){super(e);this.node=null}onEndElement(e){const t=super.onEndElement(e);if(t&&"xfa:datasets"===e){this.node=t;throw new Error("Aborting DatasetXMLParser.")}}}class DatasetReader{constructor(e){if(e.datasets)this.node=new SimpleXMLParser({hasAttributes:!0}).parseFromString(e.datasets).documentElement;else{const t=new DatasetXMLParser({hasAttributes:!0});try{t.parseFromString(e["xdp:xdp"])}catch{}this.node=t.node}}getValue(e){if(!this.node||!e)return"";const t=this.node.searchNode(parseXFAPath(e),0);if(!t)return"";const a=t.firstChild;return"value"===a?.nodeName?t.children.map((e=>decodeString(e.textContent))):decodeString(t.textContent)}}class Word64{constructor(e,t){this.high=0|e;this.low=0|t}and(e){this.high&=e.high;this.low&=e.low}xor(e){this.high^=e.high;this.low^=e.low}shiftRight(e){if(e>=32){this.low=this.high>>>e-32|0;this.high=0}else{this.low=this.low>>>e|this.high<<32-e;this.high=this.high>>>e|0}}rotateRight(e){let t,a;if(32&e){a=this.low;t=this.high}else{t=this.low;a=this.high}e&=31;this.low=t>>>e|a<<32-e;this.high=a>>>e|t<<32-e}not(){this.high=~this.high;this.low=~this.low}add(e){const t=(this.low>>>0)+(e.low>>>0);let a=(this.high>>>0)+(e.high>>>0);t>4294967295&&(a+=1);this.low=0|t;this.high=0|a}copyTo(e,t){e[t]=this.high>>>24&255;e[t+1]=this.high>>16&255;e[t+2]=this.high>>8&255;e[t+3]=255&this.high;e[t+4]=this.low>>>24&255;e[t+5]=this.low>>16&255;e[t+6]=this.low>>8&255;e[t+7]=255&this.low}assign(e){this.high=e.high;this.low=e.low}}const mc={get k(){return shadow(this,"k",[new Word64(1116352408,3609767458),new Word64(1899447441,602891725),new Word64(3049323471,3964484399),new Word64(3921009573,2173295548),new Word64(961987163,4081628472),new Word64(1508970993,3053834265),new Word64(2453635748,2937671579),new Word64(2870763221,3664609560),new Word64(3624381080,2734883394),new Word64(310598401,1164996542),new Word64(607225278,1323610764),new Word64(1426881987,3590304994),new Word64(1925078388,4068182383),new Word64(2162078206,991336113),new Word64(2614888103,633803317),new Word64(3248222580,3479774868),new Word64(3835390401,2666613458),new Word64(4022224774,944711139),new Word64(264347078,2341262773),new Word64(604807628,2007800933),new Word64(770255983,1495990901),new Word64(1249150122,1856431235),new Word64(1555081692,3175218132),new Word64(1996064986,2198950837),new Word64(2554220882,3999719339),new Word64(2821834349,766784016),new Word64(2952996808,2566594879),new Word64(3210313671,3203337956),new Word64(3336571891,1034457026),new Word64(3584528711,2466948901),new Word64(113926993,3758326383),new Word64(338241895,168717936),new Word64(666307205,1188179964),new Word64(773529912,1546045734),new Word64(1294757372,1522805485),new Word64(1396182291,2643833823),new Word64(1695183700,2343527390),new Word64(1986661051,1014477480),new Word64(2177026350,1206759142),new Word64(2456956037,344077627),new Word64(2730485921,1290863460),new Word64(2820302411,3158454273),new Word64(3259730800,3505952657),new Word64(3345764771,106217008),new Word64(3516065817,3606008344),new Word64(3600352804,1432725776),new Word64(4094571909,1467031594),new Word64(275423344,851169720),new Word64(430227734,3100823752),new Word64(506948616,1363258195),new Word64(659060556,3750685593),new Word64(883997877,3785050280),new Word64(958139571,3318307427),new Word64(1322822218,3812723403),new Word64(1537002063,2003034995),new Word64(1747873779,3602036899),new Word64(1955562222,1575990012),new Word64(2024104815,1125592928),new Word64(2227730452,2716904306),new Word64(2361852424,442776044),new Word64(2428436474,593698344),new Word64(2756734187,3733110249),new Word64(3204031479,2999351573),new Word64(3329325298,3815920427),new Word64(3391569614,3928383900),new Word64(3515267271,566280711),new Word64(3940187606,3454069534),new Word64(4118630271,4000239992),new Word64(116418474,1914138554),new Word64(174292421,2731055270),new Word64(289380356,3203993006),new Word64(460393269,320620315),new Word64(685471733,587496836),new Word64(852142971,1086792851),new Word64(1017036298,365543100),new Word64(1126000580,2618297676),new Word64(1288033470,3409855158),new Word64(1501505948,4234509866),new Word64(1607167915,987167468),new Word64(1816402316,1246189591)])}};function ch(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.not();i.and(r);e.xor(i)}function maj(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.and(r);e.xor(i);i.assign(a);i.and(r);e.xor(i)}function sigma(e,t,a){e.assign(t);e.rotateRight(28);a.assign(t);a.rotateRight(34);e.xor(a);a.assign(t);a.rotateRight(39);e.xor(a)}function sigmaPrime(e,t,a){e.assign(t);e.rotateRight(14);a.assign(t);a.rotateRight(18);e.xor(a);a.assign(t);a.rotateRight(41);e.xor(a)}function littleSigma(e,t,a){e.assign(t);e.rotateRight(1);a.assign(t);a.rotateRight(8);e.xor(a);a.assign(t);a.shiftRight(7);e.xor(a)}function littleSigmaPrime(e,t,a){e.assign(t);e.rotateRight(19);a.assign(t);a.rotateRight(61);e.xor(a);a.assign(t);a.shiftRight(6);e.xor(a)}function calculateSHA512(e,t,a,r=!1){let i,n,s,o,c,l,h,u;if(r){i=new Word64(3418070365,3238371032);n=new Word64(1654270250,914150663);s=new Word64(2438529370,812702999);o=new Word64(355462360,4144912697);c=new Word64(1731405415,4290775857);l=new Word64(2394180231,1750603025);h=new Word64(3675008525,1694076839);u=new Word64(1203062813,3204075428)}else{i=new Word64(1779033703,4089235720);n=new Word64(3144134277,2227873595);s=new Word64(1013904242,4271175723);o=new Word64(2773480762,1595750129);c=new Word64(1359893119,2917565137);l=new Word64(2600822924,725511199);h=new Word64(528734635,4215389547);u=new Word64(1541459225,327033209)}const d=128*Math.ceil((a+17)/128),f=new Uint8Array(d);let g,p;for(g=0;g>>29&255;f[g++]=a>>21&255;f[g++]=a>>13&255;f[g++]=a>>5&255;f[g++]=a<<3&255;const b=new Array(80);for(g=0;g<80;g++)b[g]=new Word64(0,0);const{k:y}=mc;let w=new Word64(0,0),S=new Word64(0,0),x=new Word64(0,0),k=new Word64(0,0),C=new Word64(0,0),v=new Word64(0,0),F=new Word64(0,0),O=new Word64(0,0);const T=new Word64(0,0),M=new Word64(0,0),D=new Word64(0,0),R=new Word64(0,0);let N,E;for(g=0;g>>t|e<<32-t}function calculate_sha256_ch(e,t,a){return e&t^~e&a}function calculate_sha256_maj(e,t,a){return e&t^e&a^t&a}function calculate_sha256_sigma(e){return rotr(e,2)^rotr(e,13)^rotr(e,22)}function calculate_sha256_sigmaPrime(e){return rotr(e,6)^rotr(e,11)^rotr(e,25)}function calculate_sha256_littleSigma(e){return rotr(e,7)^rotr(e,18)^e>>>3}function calculateSHA256(e,t,a){let r=1779033703,i=3144134277,n=1013904242,s=2773480762,o=1359893119,c=2600822924,l=528734635,h=1541459225;const u=64*Math.ceil((a+9)/64),d=new Uint8Array(u);let f,g;for(f=0;f>>29&255;d[f++]=a>>21&255;d[f++]=a>>13&255;d[f++]=a>>5&255;d[f++]=a<<3&255;const m=new Uint32Array(64),{k:b}=bc;for(f=0;f>>10)+m[g-7]+calculate_sha256_littleSigma(m[g-15])+m[g-16]|0;let e,t,a=r,u=i,p=n,w=s,S=o,x=c,k=l,C=h;for(g=0;g<64;++g){e=C+calculate_sha256_sigmaPrime(S)+calculate_sha256_ch(S,x,k)+b[g]+m[g];t=calculate_sha256_sigma(a)+calculate_sha256_maj(a,u,p);C=k;k=x;x=S;S=w+e|0;w=p;p=u;u=a;a=e+t|0}r=r+a|0;i=i+u|0;n=n+p|0;s=s+w|0;o=o+S|0;c=c+x|0;l=l+k|0;h=h+C|0}var y;return new Uint8Array([r>>24&255,r>>16&255,r>>8&255,255&r,i>>24&255,i>>16&255,i>>8&255,255&i,n>>24&255,n>>16&255,n>>8&255,255&n,s>>24&255,s>>16&255,s>>8&255,255&s,o>>24&255,o>>16&255,o>>8&255,255&o,c>>24&255,c>>16&255,c>>8&255,255&c,l>>24&255,l>>16&255,l>>8&255,255&l,h>>24&255,h>>16&255,h>>8&255,255&h])}class DecryptStream extends DecodeStream{constructor(e,t,a){super(t);this.str=e;this.dict=e.dict;this.decrypt=a;this.nextChunk=null;this.initialized=!1}readBlock(){let e;if(this.initialized)e=this.nextChunk;else{e=this.str.getBytes(512);this.initialized=!0}if(!e?.length){this.eof=!0;return}this.nextChunk=this.str.getBytes(512);const t=this.nextChunk?.length>0;e=(0,this.decrypt)(e,!t);const a=this.bufferLength,r=a+e.length;this.ensureBuffer(r).set(e,a);this.bufferLength=r}}class ARCFourCipher{constructor(e){this.a=0;this.b=0;const t=new Uint8Array(256),a=e.length;for(let e=0;e<256;++e)t[e]=e;for(let r=0,i=0;r<256;++r){const n=t[r];i=i+n+e[r%a]&255;t[r]=t[i];t[i]=n}this.s=t}encryptBlock(e){let t=this.a,a=this.b;const r=this.s,i=e.length,n=new Uint8Array(i);for(let s=0;st<128?t<<1:t<<1^27));constructor(){this.buffer=new Uint8Array(16);this.bufferPosition=0}_expandKey(e){unreachable("Cannot call `_expandKey` on the base class")}_decrypt(e,t){let a,r,i;const n=new Uint8Array(16);n.set(e);for(let e=0,a=this._keySize;e<16;++e,++a)n[e]^=t[a];for(let e=this._cyclesOfRepetition-1;e>=1;--e){a=n[13];n[13]=n[9];n[9]=n[5];n[5]=n[1];n[1]=a;a=n[14];r=n[10];n[14]=n[6];n[10]=n[2];n[6]=a;n[2]=r;a=n[15];r=n[11];i=n[7];n[15]=n[3];n[11]=a;n[7]=r;n[3]=i;for(let e=0;e<16;++e)n[e]=this._inv_s[n[e]];for(let a=0,r=16*e;a<16;++a,++r)n[a]^=t[r];for(let e=0;e<16;e+=4){const t=this._mix[n[e]],r=this._mix[n[e+1]],i=this._mix[n[e+2]],s=this._mix[n[e+3]];a=t^r>>>8^r<<24^i>>>16^i<<16^s>>>24^s<<8;n[e]=a>>>24&255;n[e+1]=a>>16&255;n[e+2]=a>>8&255;n[e+3]=255&a}}a=n[13];n[13]=n[9];n[9]=n[5];n[5]=n[1];n[1]=a;a=n[14];r=n[10];n[14]=n[6];n[10]=n[2];n[6]=a;n[2]=r;a=n[15];r=n[11];i=n[7];n[15]=n[3];n[11]=a;n[7]=r;n[3]=i;for(let e=0;e<16;++e){n[e]=this._inv_s[n[e]];n[e]^=t[e]}return n}_encrypt(e,t){const a=this._s;let r,i,n;const s=new Uint8Array(16);s.set(e);for(let e=0;e<16;++e)s[e]^=t[e];for(let e=1;e=r;--a)if(e[a]!==t){t=0;break}o-=t;n[n.length-1]=e.subarray(0,16-t)}}const c=new Uint8Array(o);for(let e=0,t=0,a=n.length;e=256&&(o=255&(27^o))}for(let t=0;t<4;++t){a[e]=r^=a[e-32];e++;a[e]=i^=a[e-32];e++;a[e]=n^=a[e-32];e++;a[e]=s^=a[e-32];e++}}return a}}class PDFBase{_hash(e,t,a){unreachable("Abstract method `_hash` called")}checkOwnerPassword(e,t,a,r){const i=new Uint8Array(e.length+56);i.set(e,0);i.set(t,e.length);i.set(a,e.length+t.length);return isArrayEqual(this._hash(e,i,a),r)}checkUserPassword(e,t,a){const r=new Uint8Array(e.length+8);r.set(e,0);r.set(t,e.length);return isArrayEqual(this._hash(e,r,[]),a)}getOwnerKey(e,t,a,r){const i=new Uint8Array(e.length+56);i.set(e,0);i.set(t,e.length);i.set(a,e.length+t.length);const n=this._hash(e,i,a);return new AES256Cipher(n).decryptBlock(r,!1,new Uint8Array(16))}getUserKey(e,t,a){const r=new Uint8Array(e.length+8);r.set(e,0);r.set(t,e.length);const i=this._hash(e,r,[]);return new AES256Cipher(i).decryptBlock(a,!1,new Uint8Array(16))}}class PDF17 extends PDFBase{_hash(e,t,a){return calculateSHA256(t,0,t.length)}}class PDF20 extends PDFBase{_hash(e,t,a){let r=calculateSHA256(t,0,t.length).subarray(0,32),i=[0],n=0;for(;n<64||i.at(-1)>n-32;){const t=e.length+r.length+a.length,l=new Uint8Array(t);let h=0;l.set(e,h);h+=e.length;l.set(r,h);h+=r.length;l.set(a,h);const u=new Uint8Array(64*t);for(let e=0,a=0;e<64;e++,a+=t)u.set(l,a);i=new AES128Cipher(r.subarray(0,16)).encrypt(u,r.subarray(16,32));const d=Math.sumPrecise(i.slice(0,16))%3;0===d?r=calculateSHA256(i,0,i.length):1===d?r=(s=i,o=0,c=i.length,calculateSHA512(s,o,c,!0)):2===d&&(r=calculateSHA512(i,0,i.length));n++}var s,o,c;return r.subarray(0,32)}}class CipherTransform{constructor(e,t){this.StringCipherConstructor=e;this.StreamCipherConstructor=t}createStream(e,t){const a=new this.StreamCipherConstructor;return new DecryptStream(e,t,(function cipherTransformDecryptStream(e,t){return a.decryptBlock(e,t)}))}decryptString(e){const t=new this.StringCipherConstructor;let a=stringToBytes(e);a=t.decryptBlock(a,!0);return bytesToString(a)}encryptString(e){const t=new this.StringCipherConstructor;if(t instanceof AESBaseCipher){const a=16-e.length%16;e+=String.fromCharCode(a).repeat(a);const r=new Uint8Array(16);crypto.getRandomValues(r);let i=stringToBytes(e);i=t.encrypt(i,r);const n=new Uint8Array(16+i.length);n.set(r);n.set(i,16);return bytesToString(n)}let a=stringToBytes(e);a=t.encrypt(a);return bytesToString(a)}}class CipherTransformFactory{static get _defaultPasswordBytes(){return shadow(this,"_defaultPasswordBytes",new Uint8Array([40,191,78,94,78,117,138,65,100,0,78,86,255,250,1,8,46,46,0,182,208,104,62,128,47,12,169,254,100,83,105,122]))}#oe(e,t,a,r,i,n,s,o,c,l,h,u){if(t){const e=Math.min(127,t.length);t=t.subarray(0,e)}else t=[];const d=6===e?new PDF20:new PDF17;return d.checkUserPassword(t,o,s)?d.getUserKey(t,c,h):t.length&&d.checkOwnerPassword(t,r,n,a)?d.getOwnerKey(t,i,n,l):null}#ce(e,t,a,r,i,n,s,o){const c=40+a.length+e.length,l=new Uint8Array(c);let h,u,d=0;if(t){u=Math.min(32,t.length);for(;d>8&255;l[d++]=i>>16&255;l[d++]=i>>>24&255;l.set(e,d);d+=e.length;if(n>=4&&!o){l.fill(255,d,d+4);d+=4}let f=calculateMD5(l,0,d);const g=s>>3;if(n>=3)for(h=0;h<50;++h)f=calculateMD5(f,0,g);const p=f.subarray(0,g);let m,b;if(n>=3){d=0;l.set(CipherTransformFactory._defaultPasswordBytes,d);d+=32;l.set(e,d);d+=e.length;m=new ARCFourCipher(p);b=m.encryptBlock(calculateMD5(l,0,d));u=p.length;const t=new Uint8Array(u);for(h=1;h<=19;++h){for(let e=0;er[t]===e))?p:null}#le(e,t,a,r){const i=new Uint8Array(32);let n=0;const s=Math.min(32,e.length);for(;n>3;if(a>=3)for(o=0;o<50;++o)c=calculateMD5(c,0,c.length);let h,u;if(a>=3){u=t;const e=new Uint8Array(l);for(o=19;o>=0;o--){for(let t=0;t>8&255;n[s++]=e>>16&255;n[s++]=255&t;n[s++]=t>>8&255;if(r){n[s++]=115;n[s++]=65;n[s++]=108;n[s++]=84}return calculateMD5(n,0,s).subarray(0,Math.min(i+5,16))}#ue(e,t,a,r,i){if(!(t instanceof Name))throw new FormatError("Invalid crypt filter name.");const n=this,s=e.get(t.name),o=s?.get("CFM");if(!o||"None"===o.name)return function(){return new NullCipher};if("V2"===o.name)return function(){return new ARCFourCipher(n.#he(a,r,i,!1))};if("AESV2"===o.name)return function(){return new AES128Cipher(n.#he(a,r,i,!0))};if("AESV3"===o.name)return function(){return new AES256Cipher(i)};throw new FormatError("Unknown crypto method")}constructor(e,t,a){const r=e.get("Filter");if(!isName(r,"Standard"))throw new FormatError("unknown encryption method");this.filterName=r.name;this.dict=e;const i=e.get("V");if(!Number.isInteger(i)||1!==i&&2!==i&&4!==i&&5!==i)throw new FormatError("unsupported encryption algorithm");this.algorithm=i;let n=e.get("Length");if(!n)if(i<=3)n=40;else{const t=e.get("CF"),a=e.get("StmF");if(t instanceof Dict&&a instanceof Name){t.suppressEncryption=!0;const e=t.get(a.name);n=e?.get("Length")||128;n<40&&(n<<=3)}}if(!Number.isInteger(n)||n<40||n%8!=0)throw new FormatError("invalid key length");const s=stringToBytes(e.get("O")),o=stringToBytes(e.get("U")),c=s.subarray(0,32),l=o.subarray(0,32),h=e.get("P"),u=e.get("R"),d=(4===i||5===i)&&!1!==e.get("EncryptMetadata");this.encryptMetadata=d;const f=stringToBytes(t);let g,p;if(a){if(6===u)try{a=utf8StringToString(a)}catch{warn("CipherTransformFactory: Unable to convert UTF8 encoded password.")}g=stringToBytes(a)}if(5!==i)p=this.#ce(f,g,c,l,h,u,n,d);else{const t=s.subarray(32,40),a=s.subarray(40,48),r=o.subarray(0,48),i=o.subarray(32,40),n=o.subarray(40,48),h=stringToBytes(e.get("OE")),d=stringToBytes(e.get("UE")),f=stringToBytes(e.get("Perms"));p=this.#oe(u,g,c,t,a,r,l,i,n,h,d,f)}if(!p){if(!a)throw new PasswordException("No password given",ha);const e=this.#le(g,c,u,n);p=this.#ce(f,e,c,l,h,u,n,d)}if(!p)throw new PasswordException("Incorrect Password",ua);if(4===i&&p.length<16){this.encryptionKey=new Uint8Array(16);this.encryptionKey.set(p)}else this.encryptionKey=p;if(i>=4){const t=e.get("CF");t instanceof Dict&&(t.suppressEncryption=!0);this.cf=t;this.stmf=e.get("StmF")||Name.get("Identity");this.strf=e.get("StrF")||Name.get("Identity");this.eff=e.get("EFF")||this.stmf}}createCipherTransform(e,t){if(4===this.algorithm||5===this.algorithm)return new CipherTransform(this.#ue(this.cf,this.strf,e,t,this.encryptionKey),this.#ue(this.cf,this.stmf,e,t,this.encryptionKey));const a=this.#he(e,t,this.encryptionKey,!1),cipherConstructor=function(){return new ARCFourCipher(a)};return new CipherTransform(cipherConstructor,cipherConstructor)}}class XRef{#de=null;constructor(e,t){this.stream=e;this.pdfManager=t;this.entries=[];this._xrefStms=new Set;this._cacheMap=new Map;this._pendingRefs=new RefSet;this._newPersistentRefNum=null;this._newTemporaryRefNum=null;this._persistentRefsCache=null}getNewPersistentRef(e){null===this._newPersistentRefNum&&(this._newPersistentRefNum=this.entries.length||1);const t=this._newPersistentRefNum++;this._cacheMap.set(t,e);return Ref.get(t,0)}getNewTemporaryRef(){if(null===this._newTemporaryRefNum){this._newTemporaryRefNum=this.entries.length||1;if(this._newPersistentRefNum){this._persistentRefsCache=new Map;for(let e=this._newTemporaryRefNum;e0;){const[s,o]=n;if(!Number.isInteger(s)||!Number.isInteger(o))throw new FormatError(`Invalid XRef range fields: ${s}, ${o}`);if(!Number.isInteger(a)||!Number.isInteger(r)||!Number.isInteger(i))throw new FormatError(`Invalid XRef entry fields length: ${s}, ${o}`);for(let n=t.entryNum;n=e.length);){a+=String.fromCharCode(r);r=e[t]}return a}function skipUntil(e,t,a){const r=a.length,i=e.length;let n=0;for(;t=r)break;t++;n++}return n}const e=/\b(endobj|\d+\s+\d+\s+obj|xref|trailer\s*<<)\b/g,t=/\b(startxref|\d+\s+\d+\s+obj)\b/g,a=/^(\d+)\s+(\d+)\s+obj\b/,r=new Uint8Array([116,114,97,105,108,101,114]),i=new Uint8Array([115,116,97,114,116,120,114,101,102]),n=new Uint8Array([47,88,82,101,102]);this.entries.length=0;this._cacheMap.clear();const s=this.stream;s.pos=0;const o=s.getBytes(),c=bytesToString(o),l=o.length;let h=s.start;const u=[],d=[];for(;h=l)break;f=o[h]}while(10!==f&&13!==f);continue}const g=readToken(o,h);let p;if(g.startsWith("xref")&&(4===g.length||/\s/.test(g[4]))){h+=skipUntil(o,h,r);u.push(h);h+=skipUntil(o,h,i)}else if(p=a.exec(g)){const t=0|p[1],a=0|p[2],r=h+g.length;let i,u=!1;if(this.entries[t]){if(this.entries[t].gen===a)try{new Parser({lexer:new Lexer(s.makeSubStream(r))}).getObj();u=!0}catch(e){e instanceof ParserEOFException?warn(`indexObjects -- checking object (${g}): "${e}".`):u=!0}}else u=!0;u&&(this.entries[t]={offset:h-s.start,gen:a,uncompressed:!0});e.lastIndex=r;const f=e.exec(c);if(f){i=e.lastIndex+1-h;if("endobj"!==f[1]){warn(`indexObjects: Found "${f[1]}" inside of another "obj", caused by missing "endobj" -- trying to recover.`);i-=f[1].length+1}}else i=l-h;const m=o.subarray(h,h+i),b=skipUntil(m,0,n);if(b0?Math.max(...this._xrefStms):null)}getEntry(e){const t=this.entries[e];return t&&!t.free&&t.offset?t:null}fetchIfRef(e,t=!1){return e instanceof Ref?this.fetch(e,t):e}fetch(e,t=!1){if(!(e instanceof Ref))throw new Error("ref object is not a reference");const a=e.num,r=this._cacheMap.get(a);if(void 0!==r){r instanceof Dict&&!r.objId&&(r.objId=e.toString());return r}let i=this.getEntry(a);if(null===i)return i;if(this._pendingRefs.has(e)){this._pendingRefs.remove(e);warn(`Ignoring circular reference: ${e}.`);return ya}this._pendingRefs.put(e);try{i=i.uncompressed?this.fetchUncompressed(e,i,t):this.fetchCompressed(e,i,t);this._pendingRefs.remove(e)}catch(t){this._pendingRefs.remove(e);throw t}i instanceof Dict?i.objId=e.toString():i instanceof BaseStream&&(i.dict.objId=e.toString());return i}fetchUncompressed(e,t,a=!1){const r=e.gen;let i=e.num;if(t.gen!==r){const n=`Inconsistent generation in XRef: ${e}`;if(this._generationFallback&&t.gen0&&t[3]-t[1]>0)return t;warn(`Empty, or invalid, /${e} entry.`)}return null}get mediaBox(){return shadow(this,"mediaBox",this._getBoundingBox("MediaBox")||yc)}get cropBox(){return shadow(this,"cropBox",this._getBoundingBox("CropBox")||this.mediaBox)}get userUnit(){const e=this.pageDict.get("UserUnit");return shadow(this,"userUnit","number"==typeof e&&e>0?e:1)}get view(){const{cropBox:e,mediaBox:t}=this;if(e!==t&&!isArrayEqual(e,t)){const a=Util.intersect(e,t);if(a&&a[2]-a[0]>0&&a[3]-a[1]>0)return shadow(this,"view",a);warn("Empty /CropBox and /MediaBox intersection.")}return shadow(this,"view",t)}get rotate(){let e=this._getInheritableProperty("Rotate")||0;e%90!=0?e=0:e>=360?e%=360:e<0&&(e=(e%360+360)%360);return shadow(this,"rotate",e)}_onSubStreamError(e,t){if(!this.evaluatorOptions.ignoreErrors)throw e;warn(`getContentStream - ignoring sub-stream (${t}): "${e}".`)}async getContentStream(){const e=await this.pdfManager.ensure(this,"content");return e instanceof BaseStream?e:Array.isArray(e)?new StreamsSequenceStream(e,this._onSubStreamError.bind(this)):new NullStream}get xfaData(){return shadow(this,"xfaData",this.xfaFactory?{bbox:this.xfaFactory.getBoundingBox(this.pageIndex)}:null)}async#fe(e,t,a){const r=[];for(const i of e)if(i.id){const e=Ref.fromString(i.id);if(!e){warn(`A non-linked annotation cannot be modified: ${i.id}`);continue}if(i.deleted){t.put(e,e);if(i.popupRef){const e=Ref.fromString(i.popupRef);e&&t.put(e,e)}continue}a?.put(e);i.ref=e;r.push(this.xref.fetchAsync(e).then((e=>{e instanceof Dict&&(i.oldAnnotation=e.clone())}),(()=>{warn(`Cannot fetch \`oldAnnotation\` for: ${e}.`)})));delete i.id}await Promise.all(r)}async saveNewAnnotations(e,t,a,r,i){if(this.xfaFactory)throw new Error("XFA: Cannot save new annotations.");const n=new PartialEvaluator({xref:this.xref,handler:e,pageIndex:this.pageIndex,idFactory:this._localIdFactory,fontCache:this.fontCache,builtInCMapCache:this.builtInCMapCache,standardFontDataCache:this.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:this.globalImageCache,systemFontCache:this.systemFontCache,options:this.evaluatorOptions}),s=new RefSetCache,o=new RefSet;await this.#fe(a,s,o);const c=this.pageDict,l=this.annotations.filter((e=>!(e instanceof Ref&&s.has(e)))),h=await AnnotationFactory.saveNewAnnotations(n,t,a,r,i);for(const{ref:e}of h.annotations)e instanceof Ref&&!o.has(e)&&l.push(e);const u=c.clone();u.set("Annots",l);i.put(this.ref,{data:u});for(const e of s)i.put(e,{data:null})}async save(e,t,a,r){const i=new PartialEvaluator({xref:this.xref,handler:e,pageIndex:this.pageIndex,idFactory:this._localIdFactory,fontCache:this.fontCache,builtInCMapCache:this.builtInCMapCache,standardFontDataCache:this.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:this.globalImageCache,systemFontCache:this.systemFontCache,options:this.evaluatorOptions}),n=await this._parsedAnnotations,s=[];for(const e of n)s.push(e.save(i,t,a,r).catch((function(e){warn(`save - ignoring annotation data during "${t.name}" task: "${e}".`);return null})));return Promise.all(s)}async loadResources(e){await(this.resourcesPromise??=this.pdfManager.ensure(this,"resources"));const t=new ObjectLoader(this.resources,e,this.xref);await t.load()}async#ge(e,t){const a=e?.get("Resources");if(!(a instanceof Dict&&a.size))return this.resources;const r=new ObjectLoader(a,t,this.xref);await r.load();return Dict.merge({xref:this.xref,dictArray:[a,this.resources],mergeSubDicts:!0})}async getOperatorList({handler:e,sink:t,task:a,intent:r,cacheKey:i,annotationStorage:c=null,modifiedIds:d=null}){const g=this.getContentStream(),p=this.loadResources(Ia),m=new PartialEvaluator({xref:this.xref,handler:e,pageIndex:this.pageIndex,idFactory:this._localIdFactory,fontCache:this.fontCache,builtInCMapCache:this.builtInCMapCache,standardFontDataCache:this.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:this.globalImageCache,systemFontCache:this.systemFontCache,options:this.evaluatorOptions}),b=this.xfaFactory?null:getNewAnnotationsMap(c),y=b?.get(this.pageIndex);let w=Promise.resolve(null),S=null;if(y){const e=this.pdfManager.ensureDoc("annotationGlobals");let t;const r=new Set;for(const{bitmapId:e,bitmap:t}of y)!e||t||r.has(e)||r.add(e);const{isOffscreenCanvasSupported:i}=this.evaluatorOptions;if(r.size>0){const e=y.slice();for(const[t,a]of c)t.startsWith(f)&&a.bitmap&&r.has(a.bitmapId)&&e.push(a);t=AnnotationFactory.generateImages(e,this.xref,i)}else t=AnnotationFactory.generateImages(y,this.xref,i);S=new RefSet;w=Promise.all([e,this.#fe(y,S,null)]).then((([e])=>e?AnnotationFactory.printNewAnnotations(e,m,a,y,t):null))}const x=Promise.all([g,p]).then((async([n])=>{const s=await this.#ge(n.dict,Ia),o=new OperatorList(r,t);e.send("StartRenderPage",{transparency:m.hasBlendModes(s,this.nonBlendModesSet),pageIndex:this.pageIndex,cacheKey:i});await m.getOperatorList({stream:n,task:a,resources:s,operatorList:o});return o}));let[k,C,v]=await Promise.all([x,this._parsedAnnotations,w]);if(v){C=C.filter((e=>!(e.ref&&S.has(e.ref))));for(let e=0,t=v.length;ee.ref&&isRefsEqual(e.ref,a.refToReplace)));if(r>=0){C.splice(r,1,a);v.splice(e--,1);t--}}}C=C.concat(v)}if(0===C.length||r&h){k.flush(!0);return{length:k.totalLength}}const F=!!(r&l),O=!!(r&u),T=!!(r&n),M=!!(r&s),D=!!(r&o),R=[];for(const e of C)(T||M&&e.mustBeViewed(c,F)&&e.mustBeViewedWhenEditing(O,d)||D&&e.mustBePrinted(c))&&R.push(e.getOperatorList(m,a,r,c).catch((function(e){warn(`getOperatorList - ignoring annotation data during "${a.name}" task: "${e}".`);return{opList:null,separateForm:!1,separateCanvas:!1}})));const N=await Promise.all(R);let E=!1,L=!1;for(const{opList:e,separateForm:t,separateCanvas:a}of N){k.addOpList(e);E||=t;L||=a}k.flush(!0,{form:E,canvas:L});return{length:k.totalLength}}async extractTextContent({handler:e,task:t,includeMarkedContent:a,disableNormalization:r,sink:i}){const n=this.getContentStream(),s=this.loadResources(Oa),o=this.pdfManager.ensureCatalog("lang"),[c,,l]=await Promise.all([n,s,o]),h=await this.#ge(c.dict,Oa);return new PartialEvaluator({xref:this.xref,handler:e,pageIndex:this.pageIndex,idFactory:this._localIdFactory,fontCache:this.fontCache,builtInCMapCache:this.builtInCMapCache,standardFontDataCache:this.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:this.globalImageCache,systemFontCache:this.systemFontCache,options:this.evaluatorOptions}).getTextContent({stream:c,task:t,resources:h,includeMarkedContent:a,disableNormalization:r,sink:i,viewBox:this.view,lang:l})}async getStructTree(){const e=await this.pdfManager.ensureCatalog("structTreeRoot");if(!e)return null;await this._parsedAnnotations;try{const t=await this.pdfManager.ensure(this,"_parseStructTree",[e]);return await this.pdfManager.ensure(t,"serializable")}catch(e){warn(`getStructTree: "${e}".`);return null}}_parseStructTree(e){const t=new StructTreePage(e,this.pageDict);t.parse(this.ref);return t}async getAnnotationsData(e,t,a){const r=await this._parsedAnnotations;if(0===r.length)return r;const i=[],c=[];let l;const h=!!(a&n),u=!!(a&s),d=!!(a&o);for(const a of r){const r=h||u&&a.viewable;(r||d&&a.printable)&&i.push(a.data);if(a.hasTextContent&&r){l||=new PartialEvaluator({xref:this.xref,handler:e,pageIndex:this.pageIndex,idFactory:this._localIdFactory,fontCache:this.fontCache,builtInCMapCache:this.builtInCMapCache,standardFontDataCache:this.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:this.globalImageCache,systemFontCache:this.systemFontCache,options:this.evaluatorOptions});c.push(a.extractTextContent(l,t,[-1/0,-1/0,1/0,1/0]).catch((function(e){warn(`getAnnotationsData - ignoring textContent during "${t.name}" task: "${e}".`)})))}}await Promise.all(c);return i}get annotations(){const e=this._getInheritableProperty("Annots");return shadow(this,"annotations",Array.isArray(e)?e:[])}get _parsedAnnotations(){return shadow(this,"_parsedAnnotations",this.pdfManager.ensure(this,"annotations").then((async e=>{if(0===e.length)return e;const[t,a]=await Promise.all([this.pdfManager.ensureDoc("annotationGlobals"),this.pdfManager.ensureDoc("fieldObjects")]);if(!t)return[];const r=a?.orphanFields,i=[];for(const a of e)i.push(AnnotationFactory.create(this.xref,a,t,this._localIdFactory,!1,r,this.ref).catch((function(e){warn(`_parsedAnnotations: "${e}".`);return null})));const n=[];let s,o;for(const e of await Promise.all(i))e&&(e instanceof WidgetAnnotation?(o||=[]).push(e):e instanceof PopupAnnotation?(s||=[]).push(e):n.push(e));o&&n.push(...o);s&&n.push(...s);return n})))}get jsActions(){return shadow(this,"jsActions",collectActions(this.xref,this.pageDict,Se))}}const wc=new Uint8Array([37,80,68,70,45]),Sc=new Uint8Array([115,116,97,114,116,120,114,101,102]),xc=new Uint8Array([101,110,100,111,98,106]);function find(e,t,a=1024,r=!1){const i=t.length,n=e.peekBytes(a),s=n.length-i;if(s<=0)return!1;if(r){const a=i-1;let r=n.length-1;for(;r>=a;){let s=0;for(;s=i){e.pos+=r-a;return!0}r--}}else{let a=0;for(;a<=s;){let r=0;for(;r=i){e.pos+=a;return!0}a++}}return!1}class PDFDocument{constructor(e,t){if(t.length<=0)throw new InvalidPDFException("The PDF file is empty, i.e. its size is zero bytes.");this.pdfManager=e;this.stream=t;this.xref=new XRef(t,e);this._pagePromises=new Map;this._version=null;const a={font:0};this._globalIdFactory=class{static getDocId(){return`g_${e.docId}`}static createFontId(){return"f"+ ++a.font}static createObjId(){unreachable("Abstract method `createObjId` called.")}static getPageObjId(){unreachable("Abstract method `getPageObjId` called.")}}}parse(e){this.xref.parse(e);this.catalog=new Catalog(this.pdfManager,this.xref)}get linearization(){let e=null;try{e=Linearization.create(this.stream)}catch(e){if(e instanceof MissingDataException)throw e;info(e)}return shadow(this,"linearization",e)}get startXRef(){const e=this.stream;let t=0;if(this.linearization){e.reset();if(find(e,xc)){e.skip(6);let a=e.peekByte();for(;isWhiteSpace(a);){e.pos++;a=e.peekByte()}t=e.pos-e.start}}else{const a=1024,r=Sc.length;let i=!1,n=e.end;for(;!i&&n>0;){n-=a-r;n<0&&(n=0);e.pos=n;i=find(e,Sc,a,!0)}if(i){e.skip(9);let a;do{a=e.getByte()}while(isWhiteSpace(a));let r="";for(;a>=32&&a<=57;){r+=String.fromCharCode(a);a=e.getByte()}t=parseInt(r,10);isNaN(t)&&(t=0)}}return shadow(this,"startXRef",t)}checkHeader(){const e=this.stream;e.reset();if(!find(e,wc))return;e.moveStart();e.skip(wc.length);let t,a="";for(;(t=e.getByte())>32&&a.length<7;)a+=String.fromCharCode(t);Ca.test(a)?this._version=a:warn(`Invalid PDF header version: ${a}`)}parseStartXRef(){this.xref.setStartXRef(this.startXRef)}get numPages(){let e=0;e=this.catalog.hasActualNumPages?this.catalog.numPages:this.xfaFactory?this.xfaFactory.getNumPages():this.linearization?this.linearization.numPages:this.catalog.numPages;return shadow(this,"numPages",e)}_hasOnlyDocumentSignatures(e,t=0){return!!Array.isArray(e)&&e.every((e=>{if(!((e=this.xref.fetchIfRef(e))instanceof Dict))return!1;if(e.has("Kids")){if(++t>10){warn("_hasOnlyDocumentSignatures: maximum recursion depth reached");return!1}return this._hasOnlyDocumentSignatures(e.get("Kids"),t)}const a=isName(e.get("FT"),"Sig"),r=e.get("Rect"),i=Array.isArray(r)&&r.every((e=>0===e));return a&&i}))}get _xfaStreams(){const{acroForm:e}=this.catalog;if(!e)return null;const t=e.get("XFA"),a=new Map(["xdp:xdp","template","datasets","config","connectionSet","localeSet","stylesheet","/xdp:xdp"].map((e=>[e,null])));if(t instanceof BaseStream&&!t.isEmpty){a.set("xdp:xdp",t);return a}if(!Array.isArray(t)||0===t.length)return null;for(let e=0,r=t.length;e0;e.hasFields=r;const i=t.get("XFA");e.hasXfa=Array.isArray(i)&&i.length>0||i instanceof BaseStream&&!i.isEmpty;const n=!!(1&t.get("SigFlags")),s=n&&this._hasOnlyDocumentSignatures(a);e.hasAcroForm=r&&!s;e.hasSignatures=n}catch(e){if(e instanceof MissingDataException)throw e;warn(`Cannot fetch form information: "${e}".`)}return shadow(this,"formInfo",e)}get documentInfo(){const e={PDFFormatVersion:this.version,Language:this.catalog.lang,EncryptFilterName:this.xref.encrypt?this.xref.encrypt.filterName:null,IsLinearized:!!this.linearization,IsAcroFormPresent:this.formInfo.hasAcroForm,IsXFAPresent:this.formInfo.hasXfa,IsCollectionPresent:!!this.catalog.collection,IsSignaturesPresent:this.formInfo.hasSignatures};let t;try{t=this.xref.trailer.get("Info")}catch(e){if(e instanceof MissingDataException)throw e;info("The document information dictionary is invalid.")}if(!(t instanceof Dict))return shadow(this,"documentInfo",e);for(const[a,r]of t){switch(a){case"Title":case"Author":case"Subject":case"Keywords":case"Creator":case"Producer":case"CreationDate":case"ModDate":if("string"==typeof r){e[a]=stringToPDFString(r);continue}break;case"Trapped":if(r instanceof Name){e[a]=r;continue}break;default:let t;switch(typeof r){case"string":t=stringToPDFString(r);break;case"number":case"boolean":t=r;break;default:r instanceof Name&&(t=r)}if(void 0===t){warn(`Bad value, for custom key "${a}", in Info: ${r}.`);continue}e.Custom??=Object.create(null);e.Custom[a]=t;continue}warn(`Bad value, for key "${a}", in Info: ${r}.`)}return shadow(this,"documentInfo",e)}get fingerprints(){const e="\0".repeat(16);function validate(t){return"string"==typeof t&&16===t.length&&t!==e}const t=this.xref.trailer.get("ID");let a,r;if(Array.isArray(t)&&validate(t[0])){a=stringToBytes(t[0]);t[1]!==t[0]&&validate(t[1])&&(r=stringToBytes(t[1]))}else a=calculateMD5(this.stream.getByteRange(0,1024),0,1024);return shadow(this,"fingerprints",[toHexUtil(a),r?toHexUtil(r):null])}async _getLinearizationPage(e){const{catalog:t,linearization:a,xref:r}=this,i=Ref.get(a.objectNumberFirst,0);try{const e=await r.fetchAsync(i);if(e instanceof Dict){let a=e.getRaw("Type");a instanceof Ref&&(a=await r.fetchAsync(a));if(isName(a,"Page")||!e.has("Type")&&!e.has("Kids")&&e.has("Contents")){t.pageKidsCountCache.has(i)||t.pageKidsCountCache.put(i,1);t.pageIndexCache.has(i)||t.pageIndexCache.put(i,0);return[e,i]}}throw new FormatError("The Linearization dictionary doesn't point to a valid Page dictionary.")}catch(a){warn(`_getLinearizationPage: "${a.message}".`);return t.getPageDict(e)}}getPage(e){const t=this._pagePromises.get(e);if(t)return t;const{catalog:a,linearization:r,xfaFactory:i}=this;let n;n=i?Promise.resolve([Dict.empty,null]):r?.pageFirst===e?this._getLinearizationPage(e):a.getPageDict(e);n=n.then((([t,r])=>new Page({pdfManager:this.pdfManager,xref:this.xref,pageIndex:e,pageDict:t,ref:r,globalIdFactory:this._globalIdFactory,fontCache:a.fontCache,builtInCMapCache:a.builtInCMapCache,standardFontDataCache:a.standardFontDataCache,globalColorSpaceCache:a.globalColorSpaceCache,globalImageCache:a.globalImageCache,systemFontCache:a.systemFontCache,nonBlendModesSet:a.nonBlendModesSet,xfaFactory:i})));this._pagePromises.set(e,n);return n}async checkFirstPage(e=!1){if(!e)try{await this.getPage(0)}catch(e){if(e instanceof XRefEntryException){this._pagePromises.delete(0);await this.cleanup();throw new XRefParseException}}}async checkLastPage(e=!1){const{catalog:t,pdfManager:a}=this;t.setActualNumPages();let r;try{await Promise.all([a.ensureDoc("xfaFactory"),a.ensureDoc("linearization"),a.ensureCatalog("numPages")]);if(this.xfaFactory)return;r=this.linearization?this.linearization.numPages:t.numPages;if(!Number.isInteger(r))throw new FormatError("Page count is not an integer.");if(r<=1)return;await this.getPage(r-1)}catch(i){this._pagePromises.delete(r-1);await this.cleanup();if(i instanceof XRefEntryException&&!e)throw new XRefParseException;warn(`checkLastPage - invalid /Pages tree /Count: ${r}.`);let n;try{n=await t.getAllPageDicts(e)}catch(a){if(a instanceof XRefEntryException&&!e)throw new XRefParseException;t.setActualNumPages(1);return}for(const[e,[r,i]]of n){let n;if(r instanceof Error){n=Promise.reject(r);n.catch((()=>{}))}else n=Promise.resolve(new Page({pdfManager:a,xref:this.xref,pageIndex:e,pageDict:r,ref:i,globalIdFactory:this._globalIdFactory,fontCache:t.fontCache,builtInCMapCache:t.builtInCMapCache,standardFontDataCache:t.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:t.globalImageCache,systemFontCache:t.systemFontCache,nonBlendModesSet:t.nonBlendModesSet,xfaFactory:null}));this._pagePromises.set(e,n)}t.setActualNumPages(n.size)}}async fontFallback(e,t){const{catalog:a,pdfManager:r}=this;for(const i of await Promise.all(a.fontCache))if(i.loadedName===e){i.fallback(t,r.evaluatorOptions);return}}async cleanup(e=!1){return this.catalog?this.catalog.cleanup(e):clearGlobalCaches()}async#pe(e,t,a,r,i,n,s){const{xref:o}=this;if(!(a instanceof Ref)||n.has(a))return;n.put(a);const c=await o.fetchAsync(a);if(!(c instanceof Dict))return;let l=await c.getAsync("Subtype");l=l instanceof Name?l.name:null;if("Link"===l)return;if(c.has("T")){const t=stringToPDFString(await c.getAsync("T"));e=""===e?t:`${e}.${t}`}else{let a=c;for(;;){a=a.getRaw("Parent")||t;if(a instanceof Ref){if(n.has(a))break;a=await o.fetchAsync(a)}if(!(a instanceof Dict))break;if(a.has("T")){const t=stringToPDFString(await a.getAsync("T"));e=""===e?t:`${e}.${t}`;break}}}t&&!c.has("Parent")&&isName(c.get("Subtype"),"Widget")&&s.put(a,t);r.has(e)||r.set(e,[]);r.get(e).push(AnnotationFactory.create(o,a,i,null,!0,s,null).then((e=>e?.getFieldObject())).catch((function(e){warn(`#collectFieldObjects: "${e}".`);return null})));if(!c.has("Kids"))return;const h=await c.getAsync("Kids");if(Array.isArray(h))for(const t of h)await this.#pe(e,a,t,r,i,n,s)}get fieldObjects(){return shadow(this,"fieldObjects",this.pdfManager.ensureDoc("formInfo").then((async e=>{if(!e.hasFields)return null;const[t,a]=await Promise.all([this.pdfManager.ensureDoc("annotationGlobals"),this.pdfManager.ensureCatalog("acroForm")]);if(!t)return null;const r=new RefSet,i=Object.create(null),n=new Map,s=new RefSetCache;for(const e of await a.getAsync("Fields"))await this.#pe("",null,e,n,t,r,s);const o=[];for(const[e,t]of n)o.push(Promise.all(t).then((t=>{(t=t.filter((e=>!!e))).length>0&&(i[e]=t)})));await Promise.all(o);return{allFields:objectSize(i)>0?i:null,orphanFields:s}})))}get hasJSActions(){return shadow(this,"hasJSActions",this.pdfManager.ensureDoc("_parseHasJSActions"))}async _parseHasJSActions(){const[e,t]=await Promise.all([this.pdfManager.ensureCatalog("jsActions"),this.pdfManager.ensureDoc("fieldObjects")]);return!!e||!!t?.allFields&&Object.values(t.allFields).some((e=>e.some((e=>null!==e.actions))))}get calculationOrderIds(){const e=this.catalog.acroForm?.get("CO");if(!Array.isArray(e)||0===e.length)return shadow(this,"calculationOrderIds",null);const t=[];for(const a of e)a instanceof Ref&&t.push(a.toString());return shadow(this,"calculationOrderIds",t.length?t:null)}get annotationGlobals(){return shadow(this,"annotationGlobals",AnnotationFactory.createGlobals(this.pdfManager))}}class BasePdfManager{constructor({docBaseUrl:e,docId:t,enableXfa:a,evaluatorOptions:r,handler:i,password:n}){this._docBaseUrl=function parseDocBaseUrl(e){if(e){const t=createValidAbsoluteUrl(e);if(t)return t.href;warn(`Invalid absolute docBaseUrl: "${e}".`)}return null}(e);this._docId=t;this._password=n;this.enableXfa=a;r.isOffscreenCanvasSupported&&=FeatureTest.isOffscreenCanvasSupported;r.isImageDecoderSupported&&=FeatureTest.isImageDecoderSupported;this.evaluatorOptions=Object.freeze(r);ImageResizer.setOptions(r);JpegStream.setOptions(r);OperatorList.setOptions(r);const s={...r,handler:i};JpxImage.setOptions(s);IccColorSpace.setOptions(s);CmykICCBasedCS.setOptions(s)}get docId(){return this._docId}get password(){return this._password}get docBaseUrl(){return this._docBaseUrl}get catalog(){return this.pdfDocument.catalog}ensureDoc(e,t){return this.ensure(this.pdfDocument,e,t)}ensureXRef(e,t){return this.ensure(this.pdfDocument.xref,e,t)}ensureCatalog(e,t){return this.ensure(this.pdfDocument.catalog,e,t)}getPage(e){return this.pdfDocument.getPage(e)}fontFallback(e,t){return this.pdfDocument.fontFallback(e,t)}loadXfaFonts(e,t){return this.pdfDocument.loadXfaFonts(e,t)}loadXfaImages(){return this.pdfDocument.loadXfaImages()}serializeXfaData(e){return this.pdfDocument.serializeXfaData(e)}cleanup(e=!1){return this.pdfDocument.cleanup(e)}async ensure(e,t,a){unreachable("Abstract method `ensure` called")}requestRange(e,t){unreachable("Abstract method `requestRange` called")}requestLoadedStream(e=!1){unreachable("Abstract method `requestLoadedStream` called")}sendProgressiveData(e){unreachable("Abstract method `sendProgressiveData` called")}updatePassword(e){this._password=e}terminate(e){unreachable("Abstract method `terminate` called")}}class LocalPdfManager extends BasePdfManager{constructor(e){super(e);const t=new Stream(e.source);this.pdfDocument=new PDFDocument(this,t);this._loadedStreamPromise=Promise.resolve(t)}async ensure(e,t,a){const r=e[t];return"function"==typeof r?r.apply(e,a):r}requestRange(e,t){return Promise.resolve()}requestLoadedStream(e=!1){return this._loadedStreamPromise}terminate(e){}}class NetworkPdfManager extends BasePdfManager{constructor(e){super(e);this.streamManager=new ChunkedStreamManager(e.source,{msgHandler:e.handler,length:e.length,disableAutoFetch:e.disableAutoFetch,rangeChunkSize:e.rangeChunkSize});this.pdfDocument=new PDFDocument(this,this.streamManager.getStream())}async ensure(e,t,a){try{const r=e[t];return"function"==typeof r?r.apply(e,a):r}catch(r){if(!(r instanceof MissingDataException))throw r;await this.requestRange(r.begin,r.end);return this.ensure(e,t,a)}}requestRange(e,t){return this.streamManager.requestRange(e,t)}requestLoadedStream(e=!1){return this.streamManager.requestAllChunks(e)}sendProgressiveData(e){this.streamManager.onReceiveData({chunk:e})}terminate(e){this.streamManager.abort(e)}}const Ac=1,kc=2,Cc=1,vc=2,Fc=3,Ic=4,Oc=5,Tc=6,Mc=7,Dc=8;function onFn(){}function wrapReason(e){if(e instanceof AbortException||e instanceof InvalidPDFException||e instanceof PasswordException||e instanceof ResponseException||e instanceof UnknownErrorException)return e;e instanceof Error||"object"==typeof e&&null!==e||unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');switch(e.name){case"AbortException":return new AbortException(e.message);case"InvalidPDFException":return new InvalidPDFException(e.message);case"PasswordException":return new PasswordException(e.message,e.code);case"ResponseException":return new ResponseException(e.message,e.status,e.missing);case"UnknownErrorException":return new UnknownErrorException(e.message,e.details)}return new UnknownErrorException(e.message,e.toString())}class MessageHandler{#me=new AbortController;constructor(e,t,a){this.sourceName=e;this.targetName=t;this.comObj=a;this.callbackId=1;this.streamId=1;this.streamSinks=Object.create(null);this.streamControllers=Object.create(null);this.callbackCapabilities=Object.create(null);this.actionHandler=Object.create(null);a.addEventListener("message",this.#be.bind(this),{signal:this.#me.signal})}#be({data:e}){if(e.targetName!==this.sourceName)return;if(e.stream){this.#ye(e);return}if(e.callback){const t=e.callbackId,a=this.callbackCapabilities[t];if(!a)throw new Error(`Cannot resolve callback ${t}`);delete this.callbackCapabilities[t];if(e.callback===Ac)a.resolve(e.data);else{if(e.callback!==kc)throw new Error("Unexpected callback case");a.reject(wrapReason(e.reason))}return}const t=this.actionHandler[e.action];if(!t)throw new Error(`Unknown action from worker: ${e.action}`);if(e.callbackId){const a=this.sourceName,r=e.sourceName,i=this.comObj;Promise.try(t,e.data).then((function(t){i.postMessage({sourceName:a,targetName:r,callback:Ac,callbackId:e.callbackId,data:t})}),(function(t){i.postMessage({sourceName:a,targetName:r,callback:kc,callbackId:e.callbackId,reason:wrapReason(t)})}))}else e.streamId?this.#we(e):t(e.data)}on(e,t){const a=this.actionHandler;if(a[e])throw new Error(`There is already an actionName called "${e}"`);a[e]=t}send(e,t,a){this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:e,data:t},a)}sendWithPromise(e,t,a){const r=this.callbackId++,i=Promise.withResolvers();this.callbackCapabilities[r]=i;try{this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:e,callbackId:r,data:t},a)}catch(e){i.reject(e)}return i.promise}sendWithStream(e,t,a,r){const i=this.streamId++,n=this.sourceName,s=this.targetName,o=this.comObj;return new ReadableStream({start:a=>{const c=Promise.withResolvers();this.streamControllers[i]={controller:a,startCall:c,pullCall:null,cancelCall:null,isClosed:!1};o.postMessage({sourceName:n,targetName:s,action:e,streamId:i,data:t,desiredSize:a.desiredSize},r);return c.promise},pull:e=>{const t=Promise.withResolvers();this.streamControllers[i].pullCall=t;o.postMessage({sourceName:n,targetName:s,stream:Tc,streamId:i,desiredSize:e.desiredSize});return t.promise},cancel:e=>{assert(e instanceof Error,"cancel must have a valid reason");const t=Promise.withResolvers();this.streamControllers[i].cancelCall=t;this.streamControllers[i].isClosed=!0;o.postMessage({sourceName:n,targetName:s,stream:Cc,streamId:i,reason:wrapReason(e)});return t.promise}},a)}#we(e){const t=e.streamId,a=this.sourceName,r=e.sourceName,i=this.comObj,n=this,s=this.actionHandler[e.action],o={enqueue(e,n=1,s){if(this.isCancelled)return;const o=this.desiredSize;this.desiredSize-=n;if(o>0&&this.desiredSize<=0){this.sinkCapability=Promise.withResolvers();this.ready=this.sinkCapability.promise}i.postMessage({sourceName:a,targetName:r,stream:Ic,streamId:t,chunk:e},s)},close(){if(!this.isCancelled){this.isCancelled=!0;i.postMessage({sourceName:a,targetName:r,stream:Fc,streamId:t});delete n.streamSinks[t]}},error(e){assert(e instanceof Error,"error must have a valid reason");if(!this.isCancelled){this.isCancelled=!0;i.postMessage({sourceName:a,targetName:r,stream:Oc,streamId:t,reason:wrapReason(e)})}},sinkCapability:Promise.withResolvers(),onPull:null,onCancel:null,isCancelled:!1,desiredSize:e.desiredSize,ready:null};o.sinkCapability.resolve();o.ready=o.sinkCapability.promise;this.streamSinks[t]=o;Promise.try(s,e.data,o).then((function(){i.postMessage({sourceName:a,targetName:r,stream:Dc,streamId:t,success:!0})}),(function(e){i.postMessage({sourceName:a,targetName:r,stream:Dc,streamId:t,reason:wrapReason(e)})}))}#ye(e){const t=e.streamId,a=this.sourceName,r=e.sourceName,i=this.comObj,n=this.streamControllers[t],s=this.streamSinks[t];switch(e.stream){case Dc:e.success?n.startCall.resolve():n.startCall.reject(wrapReason(e.reason));break;case Mc:e.success?n.pullCall.resolve():n.pullCall.reject(wrapReason(e.reason));break;case Tc:if(!s){i.postMessage({sourceName:a,targetName:r,stream:Mc,streamId:t,success:!0});break}s.desiredSize<=0&&e.desiredSize>0&&s.sinkCapability.resolve();s.desiredSize=e.desiredSize;Promise.try(s.onPull||onFn).then((function(){i.postMessage({sourceName:a,targetName:r,stream:Mc,streamId:t,success:!0})}),(function(e){i.postMessage({sourceName:a,targetName:r,stream:Mc,streamId:t,reason:wrapReason(e)})}));break;case Ic:assert(n,"enqueue should have stream controller");if(n.isClosed)break;n.controller.enqueue(e.chunk);break;case Fc:assert(n,"close should have stream controller");if(n.isClosed)break;n.isClosed=!0;n.controller.close();this.#Se(n,t);break;case Oc:assert(n,"error should have stream controller");n.controller.error(wrapReason(e.reason));this.#Se(n,t);break;case vc:e.success?n.cancelCall.resolve():n.cancelCall.reject(wrapReason(e.reason));this.#Se(n,t);break;case Cc:if(!s)break;const o=wrapReason(e.reason);Promise.try(s.onCancel||onFn,o).then((function(){i.postMessage({sourceName:a,targetName:r,stream:vc,streamId:t,success:!0})}),(function(e){i.postMessage({sourceName:a,targetName:r,stream:vc,streamId:t,reason:wrapReason(e)})}));s.sinkCapability.reject(o);s.isCancelled=!0;delete this.streamSinks[t];break;default:throw new Error("Unexpected stream case")}}async#Se(e,t){await Promise.allSettled([e.startCall?.promise,e.pullCall?.promise,e.cancelCall?.promise]);delete this.streamControllers[t]}destroy(){this.#me?.abort();this.#me=null}}async function writeObject(e,t,a,{encrypt:r=null}){const i=r?.createCipherTransform(e.num,e.gen);a.push(`${e.num} ${e.gen} obj\n`);t instanceof Dict?await writeDict(t,a,i):t instanceof BaseStream?await writeStream(t,a,i):(Array.isArray(t)||ArrayBuffer.isView(t))&&await writeArray(t,a,i);a.push("\nendobj\n")}async function writeDict(e,t,a){t.push("<<");for(const r of e.getKeys()){t.push(` /${escapePDFName(r)} `);await writeValue(e.getRaw(r),t,a)}t.push(">>")}async function writeStream(e,t,a){let r=e.getBytes();const{dict:i}=e,[n,s]=await Promise.all([i.getAsync("Filter"),i.getAsync("DecodeParms")]),o=isName(Array.isArray(n)?await i.xref.fetchIfRefAsync(n[0]):n,"FlateDecode");if(r.length>=256||o)try{const e=new CompressionStream("deflate"),t=e.writable.getWriter();await t.ready;t.write(r).then((async()=>{await t.ready;await t.close()})).catch((()=>{}));const a=await new Response(e.readable).arrayBuffer();r=new Uint8Array(a);let c,l;if(n){if(!o){c=Array.isArray(n)?[Name.get("FlateDecode"),...n]:[Name.get("FlateDecode"),n];s&&(l=Array.isArray(s)?[null,...s]:[null,s])}}else c=Name.get("FlateDecode");c&&i.set("Filter",c);l&&i.set("DecodeParms",l)}catch(e){info(`writeStream - cannot compress data: "${e}".`)}let c=bytesToString(r);a&&(c=a.encryptString(c));i.set("Length",c.length);await writeDict(i,t,a);t.push(" stream\n",c,"\nendstream")}async function writeArray(e,t,a){t.push("[");let r=!0;for(const i of e){r?r=!1:t.push(" ");await writeValue(i,t,a)}t.push("]")}async function writeValue(e,t,a){if(e instanceof Name)t.push(`/${escapePDFName(e.name)}`);else if(e instanceof Ref)t.push(`${e.num} ${e.gen} R`);else if(Array.isArray(e)||ArrayBuffer.isView(e))await writeArray(e,t,a);else if("string"==typeof e){a&&(e=a.encryptString(e));t.push(`(${escapeString(e)})`)}else"number"==typeof e?t.push(numberToString(e)):"boolean"==typeof e?t.push(e.toString()):e instanceof Dict?await writeDict(e,t,a):e instanceof BaseStream?await writeStream(e,t,a):null===e?t.push("null"):warn(`Unhandled value in writer: ${typeof e}, please file a bug.`)}function writeInt(e,t,a,r){for(let i=t+a-1;i>a-1;i--){r[i]=255&e;e>>=8}return a+t}function writeString(e,t,a){const r=e.length;for(let i=0;i1&&(n=a.documentElement.searchNode([i.at(-1)],0));n?n.childNodes=Array.isArray(r)?r.map((e=>new SimpleDOMNode("value",e))):[new SimpleDOMNode("#text",r)]:warn(`Node not found for path: ${t}`)}const r=[];a.documentElement.dump(r);return r.join("")}(r.fetchIfRef(t).getString(),a)}const i=new StringStream(e);i.dict=new Dict(r);i.dict.set("Type",Name.get("EmbeddedFile"));a.put(t,{data:i})}function getIndexes(e){const t=[];for(const{ref:a}of e)a.num===t.at(-2)+t.at(-1)?t[t.length-1]+=1:t.push(a.num,1);return t}function computeIDs(e,t,a){if(Array.isArray(t.fileIds)&&t.fileIds.length>0){const r=function computeMD5(e,t){const a=Math.floor(Date.now()/1e3),r=t.filename||"",i=[a.toString(),r,e.toString(),...t.infoMap.values()],n=Math.sumPrecise(i.map((e=>e.length))),s=new Uint8Array(n);let o=0;for(const e of i)o=writeString(e,o,s);return bytesToString(calculateMD5(s,0,s.length))}(e,t);a.set("ID",[t.fileIds[0],r])}}async function incrementalUpdate({originalData:e,xrefInfo:t,changes:a,xref:r=null,hasXfa:i=!1,xfaDatasetsRef:n=null,hasXfaDatasetsEntry:s=!1,needAppearances:o,acroFormRef:c=null,acroForm:l=null,xfaData:h=null,useXrefStream:u=!1}){await async function updateAcroform({xref:e,acroForm:t,acroFormRef:a,hasXfa:r,hasXfaDatasetsEntry:i,xfaDatasetsRef:n,needAppearances:s,changes:o}){!r||i||n||warn("XFA - Cannot save it");if(!s&&(!r||!n||i))return;const c=t.clone();if(r&&!i){const e=t.get("XFA").slice();e.splice(2,0,"datasets");e.splice(3,0,n);c.set("XFA",e)}s&&c.set("NeedAppearances",!0);o.put(a,{data:c})}({xref:r,acroForm:l,acroFormRef:c,hasXfa:i,hasXfaDatasetsEntry:s,xfaDatasetsRef:n,needAppearances:o,changes:a});i&&updateXFA({xfaData:h,xfaDatasetsRef:n,changes:a,xref:r});const d=function getTrailerDict(e,t,a){const r=new Dict(null);r.set("Prev",e.startXRef);const i=e.newRef;if(a){t.put(i,{data:""});r.set("Size",i.num+1);r.set("Type",Name.get("XRef"))}else r.set("Size",i.num);null!==e.rootRef&&r.set("Root",e.rootRef);null!==e.infoRef&&r.set("Info",e.infoRef);null!==e.encryptRef&&r.set("Encrypt",e.encryptRef);return r}(t,a,u),f=[],g=await async function writeChanges(e,t,a=[]){const r=[];for(const[i,{data:n}]of e.items())if(null!==n&&"string"!=typeof n){await writeObject(i,n,a,t);r.push({ref:i,data:a.join("")});a.length=0}else r.push({ref:i,data:n});return r.sort(((e,t)=>e.ref.num-t.ref.num))}(a,r,f);let p=e.length;const m=e.at(-1);if(10!==m&&13!==m){f.push("\n");p+=1}for(const{data:e}of g)null!==e&&f.push(e);await(u?async function getXRefStreamTable(e,t,a,r,i){const n=[];let s=0,o=0;for(const{ref:e,data:r}of a){let a;s=Math.max(s,t);if(null!==r){a=Math.min(e.gen,65535);n.push([1,t,a]);t+=r.length}else{a=Math.min(e.gen+1,65535);n.push([0,0,a])}o=Math.max(o,a)}r.set("Index",getIndexes(a));const c=[1,getSizeInBytes(s),getSizeInBytes(o)];r.set("W",c);computeIDs(t,e,r);const l=Math.sumPrecise(c),h=new Uint8Array(l*n.length),u=new Stream(h);u.dict=r;let d=0;for(const[e,t,a]of n){d=writeInt(e,c[0],d,h);d=writeInt(t,c[1],d,h);d=writeInt(a,c[2],d,h)}await writeObject(e.newRef,u,i,{});i.push("startxref\n",t.toString(),"\n%%EOF\n")}(t,p,g,d,f):async function getXRefTable(e,t,a,r,i){i.push("xref\n");const n=getIndexes(a);let s=0;for(const{ref:e,data:r}of a){if(e.num===n[s]){i.push(`${n[s]} ${n[s+1]}\n`);s+=2}if(null!==r){i.push(`${t.toString().padStart(10,"0")} ${Math.min(e.gen,65535).toString().padStart(5,"0")} n\r\n`);t+=r.length}else i.push(`0000000000 ${Math.min(e.gen+1,65535).toString().padStart(5,"0")} f\r\n`)}computeIDs(t,e,r);i.push("trailer\n");await writeDict(r,i);i.push("\nstartxref\n",t.toString(),"\n%%EOF\n")}(t,p,g,d,f));const b=e.length+Math.sumPrecise(f.map((e=>e.length))),y=new Uint8Array(b);y.set(e);let w=e.length;for(const e of f)w=writeString(e,w,y);return y}class PDFWorkerStream{constructor(e){this._msgHandler=e;this._contentLength=null;this._fullRequestReader=null;this._rangeRequestReaders=[]}getFullReader(){assert(!this._fullRequestReader,"PDFWorkerStream.getFullReader can only be called once.");this._fullRequestReader=new PDFWorkerStreamReader(this._msgHandler);return this._fullRequestReader}getRangeReader(e,t){const a=new PDFWorkerStreamRangeReader(e,t,this._msgHandler);this._rangeRequestReaders.push(a);return a}cancelAllRequests(e){this._fullRequestReader?.cancel(e);for(const t of this._rangeRequestReaders.slice(0))t.cancel(e)}}class PDFWorkerStreamReader{constructor(e){this._msgHandler=e;this.onProgress=null;this._contentLength=null;this._isRangeSupported=!1;this._isStreamingSupported=!1;const t=this._msgHandler.sendWithStream("GetReader");this._reader=t.getReader();this._headersReady=this._msgHandler.sendWithPromise("ReaderHeadersReady").then((e=>{this._isStreamingSupported=e.isStreamingSupported;this._isRangeSupported=e.isRangeSupported;this._contentLength=e.contentLength}))}get headersReady(){return this._headersReady}get contentLength(){return this._contentLength}get isStreamingSupported(){return this._isStreamingSupported}get isRangeSupported(){return this._isRangeSupported}async read(){const{value:e,done:t}=await this._reader.read();return t?{value:void 0,done:!0}:{value:e.buffer,done:!1}}cancel(e){this._reader.cancel(e)}}class PDFWorkerStreamRangeReader{constructor(e,t,a){this._msgHandler=a;this.onProgress=null;const r=this._msgHandler.sendWithStream("GetRangeReader",{begin:e,end:t});this._reader=r.getReader()}get isStreamingSupported(){return!1}async read(){const{value:e,done:t}=await this._reader.read();return t?{value:void 0,done:!0}:{value:e.buffer,done:!1}}cancel(e){this._reader.cancel(e)}}class WorkerTask{constructor(e){this.name=e;this.terminated=!1;this._capability=Promise.withResolvers()}get finished(){return this._capability.promise}finish(){this._capability.resolve()}terminate(){this.terminated=!0}ensureNotTerminated(){if(this.terminated)throw new Error("Worker task was terminated")}}class WorkerMessageHandler{static{"undefined"==typeof window&&!e&&"undefined"!=typeof self&&"function"==typeof self.postMessage&&"onmessage"in self&&this.initializeFromPort(self)}static setup(e,t){let a=!1;e.on("test",(t=>{if(!a){a=!0;e.send("test",t instanceof Uint8Array)}}));e.on("configure",(e=>{!function setVerbosityLevel(e){Number.isInteger(e)&&(da=e)}(e.verbosity)}));e.on("GetDocRequest",(e=>this.createDocumentHandler(e,t)))}static createDocumentHandler(e,t){let a,r=!1,i=null;const n=new Set,s=getVerbosityLevel(),{docId:o,apiVersion:c}=e,l="5.2.133";if(c!==l)throw new Error(`The API version "${c}" does not match the Worker version "${l}".`);const buildMsg=(e,t)=>`The \`${e}.prototype\` contains unexpected enumerable property "${t}", thus breaking e.g. \`for...in\` iteration of ${e}s.`;for(const e in{})throw new Error(buildMsg("Object",e));for(const e in[])throw new Error(buildMsg("Array",e));const h=o+"_worker";let u=new MessageHandler(h,o,t);function ensureNotTerminated(){if(r)throw new Error("Worker was terminated")}function startWorkerTask(e){n.add(e)}function finishWorkerTask(e){e.finish();n.delete(e)}async function loadDocument(e){await a.ensureDoc("checkHeader");await a.ensureDoc("parseStartXRef");await a.ensureDoc("parse",[e]);await a.ensureDoc("checkFirstPage",[e]);await a.ensureDoc("checkLastPage",[e]);const t=await a.ensureDoc("isPureXfa");if(t){const e=new WorkerTask("loadXfaFonts");startWorkerTask(e);await Promise.all([a.loadXfaFonts(u,e).catch((e=>{})).then((()=>finishWorkerTask(e))),a.loadXfaImages()])}const[r,i]=await Promise.all([a.ensureDoc("numPages"),a.ensureDoc("fingerprints")]);return{numPages:r,fingerprints:i,htmlForXfa:t?await a.ensureDoc("htmlForXfa"):null}}function setupDoc(e){function onSuccess(e){ensureNotTerminated();u.send("GetDoc",{pdfInfo:e})}function onFailure(e){ensureNotTerminated();if(e instanceof PasswordException){const t=new WorkerTask(`PasswordException: response ${e.code}`);startWorkerTask(t);u.sendWithPromise("PasswordRequest",e).then((function({password:e}){finishWorkerTask(t);a.updatePassword(e);pdfManagerReady()})).catch((function(){finishWorkerTask(t);u.send("DocException",e)}))}else u.send("DocException",wrapReason(e))}function pdfManagerReady(){ensureNotTerminated();loadDocument(!1).then(onSuccess,(function(e){ensureNotTerminated();e instanceof XRefParseException?a.requestLoadedStream().then((function(){ensureNotTerminated();loadDocument(!0).then(onSuccess,onFailure)})):onFailure(e)}))}ensureNotTerminated();(async function getPdfManager({data:e,password:t,disableAutoFetch:a,rangeChunkSize:r,length:n,docBaseUrl:s,enableXfa:c,evaluatorOptions:l}){const h={source:null,disableAutoFetch:a,docBaseUrl:s,docId:o,enableXfa:c,evaluatorOptions:l,handler:u,length:n,password:t,rangeChunkSize:r};if(e){h.source=e;return new LocalPdfManager(h)}const d=new PDFWorkerStream(u),f=d.getFullReader(),g=Promise.withResolvers();let p,m=[],b=0;f.headersReady.then((function(){if(f.isRangeSupported){h.source=d;h.length=f.contentLength;h.disableAutoFetch||=f.isStreamingSupported;p=new NetworkPdfManager(h);for(const e of m)p.sendProgressiveData(e);m=[];g.resolve(p);i=null}})).catch((function(e){g.reject(e);i=null}));new Promise((function(e,t){const readChunk=function({value:e,done:a}){try{ensureNotTerminated();if(a){if(!p){const e=arrayBuffersToBytes(m);m=[];n&&e.length!==n&&warn("reported HTTP length is different from actual");h.source=e;p=new LocalPdfManager(h);g.resolve(p)}i=null;return}b+=e.byteLength;f.isStreamingSupported||u.send("DocProgress",{loaded:b,total:Math.max(b,f.contentLength||0)});p?p.sendProgressiveData(e):m.push(e);f.read().then(readChunk,t)}catch(e){t(e)}};f.read().then(readChunk,t)})).catch((function(e){g.reject(e);i=null}));i=e=>{d.cancelAllRequests(e)};return g.promise})(e).then((function(e){if(r){e.terminate(new AbortException("Worker was terminated."));throw new Error("Worker was terminated")}a=e;a.requestLoadedStream(!0).then((e=>{u.send("DataLoaded",{length:e.bytes.byteLength})}))})).then(pdfManagerReady,onFailure)}u.on("GetPage",(function(e){return a.getPage(e.pageIndex).then((function(e){return Promise.all([a.ensure(e,"rotate"),a.ensure(e,"ref"),a.ensure(e,"userUnit"),a.ensure(e,"view")]).then((function([e,t,a,r]){return{rotate:e,ref:t,refStr:t?.toString()??null,userUnit:a,view:r}}))}))}));u.on("GetPageIndex",(function(e){const t=Ref.get(e.num,e.gen);return a.ensureCatalog("getPageIndex",[t])}));u.on("GetDestinations",(function(e){return a.ensureCatalog("destinations")}));u.on("GetDestination",(function(e){return a.ensureCatalog("getDestination",[e.id])}));u.on("GetPageLabels",(function(e){return a.ensureCatalog("pageLabels")}));u.on("GetPageLayout",(function(e){return a.ensureCatalog("pageLayout")}));u.on("GetPageMode",(function(e){return a.ensureCatalog("pageMode")}));u.on("GetViewerPreferences",(function(e){return a.ensureCatalog("viewerPreferences")}));u.on("GetOpenAction",(function(e){return a.ensureCatalog("openAction")}));u.on("GetAttachments",(function(e){return a.ensureCatalog("attachments")}));u.on("GetDocJSActions",(function(e){return a.ensureCatalog("jsActions")}));u.on("GetPageJSActions",(function({pageIndex:e}){return a.getPage(e).then((e=>a.ensure(e,"jsActions")))}));u.on("GetOutline",(function(e){return a.ensureCatalog("documentOutline")}));u.on("GetOptionalContentConfig",(function(e){return a.ensureCatalog("optionalContentConfig")}));u.on("GetPermissions",(function(e){return a.ensureCatalog("permissions")}));u.on("GetMetadata",(function(e){return Promise.all([a.ensureDoc("documentInfo"),a.ensureCatalog("metadata")])}));u.on("GetMarkInfo",(function(e){return a.ensureCatalog("markInfo")}));u.on("GetData",(function(e){return a.requestLoadedStream().then((e=>e.bytes))}));u.on("GetAnnotations",(function({pageIndex:e,intent:t}){return a.getPage(e).then((function(a){const r=new WorkerTask(`GetAnnotations: page ${e}`);startWorkerTask(r);return a.getAnnotationsData(u,r,t).then((e=>{finishWorkerTask(r);return e}),(e=>{finishWorkerTask(r);throw e}))}))}));u.on("GetFieldObjects",(function(e){return a.ensureDoc("fieldObjects").then((e=>e?.allFields||null))}));u.on("HasJSActions",(function(e){return a.ensureDoc("hasJSActions")}));u.on("GetCalculationOrderIds",(function(e){return a.ensureDoc("calculationOrderIds")}));u.on("SaveDocument",(async function({isPureXfa:e,numPages:t,annotationStorage:r,filename:i}){const n=[a.requestLoadedStream(),a.ensureCatalog("acroForm"),a.ensureCatalog("acroFormRef"),a.ensureDoc("startXRef"),a.ensureDoc("xref"),a.ensureDoc("linearization"),a.ensureCatalog("structTreeRoot")],s=new RefSetCache,o=[],c=e?null:getNewAnnotationsMap(r),[l,h,d,f,g,p,m]=await Promise.all(n),b=g.trailer.getRaw("Root")||null;let y;if(c){m?await m.canUpdateStructTree({pdfManager:a,newAnnotationsByPage:c})&&(y=m):await StructTreeRoot.canCreateStructureTree({catalogRef:b,pdfManager:a,newAnnotationsByPage:c})&&(y=null);const e=AnnotationFactory.generateImages(r.values(),g,a.evaluatorOptions.isOffscreenCanvasSupported),t=void 0===y?o:[];for(const[r,i]of c)t.push(a.getPage(r).then((t=>{const a=new WorkerTask(`Save (editor): page ${r}`);startWorkerTask(a);return t.saveNewAnnotations(u,a,i,e,s).finally((function(){finishWorkerTask(a)}))})));null===y?o.push(Promise.all(t).then((async()=>{await StructTreeRoot.createStructureTree({newAnnotationsByPage:c,xref:g,catalogRef:b,pdfManager:a,changes:s})}))):y&&o.push(Promise.all(t).then((async()=>{await y.updateStructureTree({newAnnotationsByPage:c,pdfManager:a,changes:s})})))}if(e)o.push(a.serializeXfaData(r));else for(let e=0;ee.needAppearances)),k=h instanceof Dict&&h.get("XFA")||null;let C=null,v=!1;if(Array.isArray(k)){for(let e=0,t=k.length;e{g.resetNewTemporaryRef()}))}));u.on("GetOperatorList",(function(e,t){const r=e.pageIndex;a.getPage(r).then((function(a){const i=new WorkerTask(`GetOperatorList: page ${r}`);startWorkerTask(i);const n=s>=Ae?Date.now():0;a.getOperatorList({handler:u,sink:t,task:i,intent:e.intent,cacheKey:e.cacheKey,annotationStorage:e.annotationStorage,modifiedIds:e.modifiedIds}).then((function(e){finishWorkerTask(i);n&&info(`page=${r+1} - getOperatorList: time=${Date.now()-n}ms, len=${e.length}`);t.close()}),(function(e){finishWorkerTask(i);i.terminated||t.error(e)}))}))}));u.on("GetTextContent",(function(e,t){const{pageIndex:r,includeMarkedContent:i,disableNormalization:n}=e;a.getPage(r).then((function(e){const a=new WorkerTask("GetTextContent: page "+r);startWorkerTask(a);const o=s>=Ae?Date.now():0;e.extractTextContent({handler:u,task:a,sink:t,includeMarkedContent:i,disableNormalization:n}).then((function(){finishWorkerTask(a);o&&info(`page=${r+1} - getTextContent: time=`+(Date.now()-o)+"ms");t.close()}),(function(e){finishWorkerTask(a);a.terminated||t.error(e)}))}))}));u.on("GetStructTree",(function(e){return a.getPage(e.pageIndex).then((e=>a.ensure(e,"getStructTree")))}));u.on("FontFallback",(function(e){return a.fontFallback(e.id,u)}));u.on("Cleanup",(function(e){return a.cleanup(!0)}));u.on("Terminate",(function(e){r=!0;const t=[];if(a){a.terminate(new AbortException("Worker was terminated."));const e=a.cleanup();t.push(e);a=null}else clearGlobalCaches();i?.(new AbortException("Worker was terminated."));for(const e of n){t.push(e.finished);e.terminate()}return Promise.all(t).then((function(){u.destroy();u=null}))}));u.on("Ready",(function(t){setupDoc(e);e=null}));return h}static initializeFromPort(e){const t=new MessageHandler("worker","main",e);this.setup(t,e);t.send("ready",null)}}globalThis.pdfjsWorker={WorkerMessageHandler};export{WorkerMessageHandler}; \ No newline at end of file +/** + * pdfjsVersion = 5.3.31 + * pdfjsBuild = 47ad820d9 + */ +const e=!("object"!=typeof process||process+""!="[object process]"||process.versions.nw||process.versions.electron&&process.type&&"browser"!==process.type),t=[.001,0,0,.001,0,0],a=1.35,r=.35,i=.25925925925925924,n=1,s=2,o=4,c=8,l=16,h=64,u=128,d=256,f="pdfjs_internal_editor_",g=3,p=9,m=13,b=15,y=101,w={PRINT:4,MODIFY_CONTENTS:8,COPY:16,MODIFY_ANNOTATIONS:32,FILL_INTERACTIVE_FORMS:256,COPY_FOR_ACCESSIBILITY:512,ASSEMBLE:1024,PRINT_HIGH_QUALITY:2048},S=0,x=4,k=1,C=2,v=3,F=1,O=2,T=3,M=4,D=5,R=6,N=7,E=8,L=9,j=10,_=11,U=12,X=13,q=14,H=15,W=16,z=17,$=20,G="Group",V="R",K=1,J=2,Y=4,Z=16,Q=32,ee=128,te=512,ae=1,re=2,ie=4096,ne=8192,se=32768,oe=65536,ce=131072,le=1048576,he=2097152,ue=8388608,de=16777216,fe=1,ge=2,pe=3,me=4,be=5,ye={E:"Mouse Enter",X:"Mouse Exit",D:"Mouse Down",U:"Mouse Up",Fo:"Focus",Bl:"Blur",PO:"PageOpen",PC:"PageClose",PV:"PageVisible",PI:"PageInvisible",K:"Keystroke",F:"Format",V:"Validate",C:"Calculate"},we={WC:"WillClose",WS:"WillSave",DS:"DidSave",WP:"WillPrint",DP:"DidPrint"},Se={O:"PageOpen",C:"PageClose"},xe=1,Ae=5,ke=1,Ce=2,ve=3,Fe=4,Ie=5,Oe=6,Te=7,Me=8,De=9,Be=10,Re=11,Ne=12,Ee=13,Pe=14,Le=15,je=16,_e=17,Ue=18,Xe=19,qe=20,He=21,We=22,ze=23,$e=24,Ge=25,Ve=26,Ke=27,Je=28,Ye=29,Ze=30,Qe=31,et=32,tt=33,at=34,rt=35,it=36,nt=37,st=38,ot=39,ct=40,lt=41,ht=42,ut=43,dt=44,ft=45,gt=46,pt=47,mt=48,bt=49,yt=50,wt=51,St=52,xt=53,At=54,kt=55,Ct=56,vt=57,Ft=58,It=59,Ot=60,Tt=61,Mt=62,Dt=63,Bt=64,Rt=65,Nt=66,Et=67,Pt=68,Lt=69,jt=70,_t=71,Ut=72,Xt=73,qt=74,Ht=75,Wt=76,zt=77,$t=80,Gt=81,Vt=83,Kt=84,Jt=85,Yt=86,Zt=87,Qt=88,ea=89,ta=90,aa=91,ra=92,ia=93,na=94,sa=0,oa=1,ca=2,la=3,ha=1,ua=2;let da=xe;function getVerbosityLevel(){return da}function info(e){da>=Ae&&console.log(`Info: ${e}`)}function warn(e){da>=xe&&console.log(`Warning: ${e}`)}function unreachable(e){throw new Error(e)}function assert(e,t){e||unreachable(t)}function createValidAbsoluteUrl(e,t=null,a=null){if(!e)return null;if(a&&"string"==typeof e){if(a.addDefaultProtocol&&e.startsWith("www.")){const t=e.match(/\./g);t?.length>=2&&(e=`http://${e}`)}if(a.tryConvertEncoding)try{e=stringToUTF8String(e)}catch{}}const r=t?URL.parse(e,t):URL.parse(e);return function _isValidProtocol(e){switch(e?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}(r)?r:null}function shadow(e,t,a,r=!1){Object.defineProperty(e,t,{value:a,enumerable:!r,configurable:!0,writable:!1});return a}const fa=function BaseExceptionClosure(){function BaseException(e,t){this.message=e;this.name=t}BaseException.prototype=new Error;BaseException.constructor=BaseException;return BaseException}();class PasswordException extends fa{constructor(e,t){super(e,"PasswordException");this.code=t}}class UnknownErrorException extends fa{constructor(e,t){super(e,"UnknownErrorException");this.details=t}}class InvalidPDFException extends fa{constructor(e){super(e,"InvalidPDFException")}}class ResponseException extends fa{constructor(e,t,a){super(e,"ResponseException");this.status=t;this.missing=a}}class FormatError extends fa{constructor(e){super(e,"FormatError")}}class AbortException extends fa{constructor(e){super(e,"AbortException")}}function bytesToString(e){"object"==typeof e&&void 0!==e?.length||unreachable("Invalid argument for bytesToString");const t=e.length,a=8192;if(t>24&255,e>>16&255,e>>8&255,255&e)}function objectSize(e){return Object.keys(e).length}class FeatureTest{static get isLittleEndian(){return shadow(this,"isLittleEndian",function isLittleEndian(){const e=new Uint8Array(4);e[0]=1;return 1===new Uint32Array(e.buffer,0,1)[0]}())}static get isEvalSupported(){return shadow(this,"isEvalSupported",function isEvalSupported(){try{new Function("");return!0}catch{return!1}}())}static get isOffscreenCanvasSupported(){return shadow(this,"isOffscreenCanvasSupported","undefined"!=typeof OffscreenCanvas)}static get isImageDecoderSupported(){return shadow(this,"isImageDecoderSupported","undefined"!=typeof ImageDecoder)}static get platform(){const{platform:e,userAgent:t}=navigator;return shadow(this,"platform",{isAndroid:t.includes("Android"),isLinux:e.includes("Linux"),isMac:e.includes("Mac"),isWindows:e.includes("Win"),isFirefox:t.includes("Firefox")})}static get isCSSRoundSupported(){return shadow(this,"isCSSRoundSupported",globalThis.CSS?.supports?.("width: round(1.5px, 1px)"))}}const ga=Array.from(Array(256).keys(),(e=>e.toString(16).padStart(2,"0")));class Util{static makeHexColor(e,t,a){return`#${ga[e]}${ga[t]}${ga[a]}`}static scaleMinMax(e,t){let a;if(e[0]){if(e[0]<0){a=t[0];t[0]=t[2];t[2]=a}t[0]*=e[0];t[2]*=e[0];if(e[3]<0){a=t[1];t[1]=t[3];t[3]=a}t[1]*=e[3];t[3]*=e[3]}else{a=t[0];t[0]=t[1];t[1]=a;a=t[2];t[2]=t[3];t[3]=a;if(e[1]<0){a=t[1];t[1]=t[3];t[3]=a}t[1]*=e[1];t[3]*=e[1];if(e[2]<0){a=t[0];t[0]=t[2];t[2]=a}t[0]*=e[2];t[2]*=e[2]}t[0]+=e[4];t[1]+=e[5];t[2]+=e[4];t[3]+=e[5]}static transform(e,t){return[e[0]*t[0]+e[2]*t[1],e[1]*t[0]+e[3]*t[1],e[0]*t[2]+e[2]*t[3],e[1]*t[2]+e[3]*t[3],e[0]*t[4]+e[2]*t[5]+e[4],e[1]*t[4]+e[3]*t[5]+e[5]]}static applyTransform(e,t,a=0){const r=e[a],i=e[a+1];e[a]=r*t[0]+i*t[2]+t[4];e[a+1]=r*t[1]+i*t[3]+t[5]}static applyTransformToBezier(e,t,a=0){const r=t[0],i=t[1],n=t[2],s=t[3],o=t[4],c=t[5];for(let t=0;t<6;t+=2){const l=e[a+t],h=e[a+t+1];e[a+t]=l*r+h*n+o;e[a+t+1]=l*i+h*s+c}}static applyInverseTransform(e,t){const a=e[0],r=e[1],i=t[0]*t[3]-t[1]*t[2];e[0]=(a*t[3]-r*t[2]+t[2]*t[5]-t[4]*t[3])/i;e[1]=(-a*t[1]+r*t[0]+t[4]*t[1]-t[5]*t[0])/i}static axialAlignedBoundingBox(e,t,a){const r=t[0],i=t[1],n=t[2],s=t[3],o=t[4],c=t[5],l=e[0],h=e[1],u=e[2],d=e[3];let f=r*l+o,g=f,p=r*u+o,m=p,b=s*h+c,y=b,w=s*d+c,S=w;if(0!==i||0!==n){const e=i*l,t=i*u,a=n*h,r=n*d;f+=a;m+=a;p+=r;g+=r;b+=e;S+=e;w+=t;y+=t}a[0]=Math.min(a[0],f,p,g,m);a[1]=Math.min(a[1],b,w,y,S);a[2]=Math.max(a[2],f,p,g,m);a[3]=Math.max(a[3],b,w,y,S)}static inverseTransform(e){const t=e[0]*e[3]-e[1]*e[2];return[e[3]/t,-e[1]/t,-e[2]/t,e[0]/t,(e[2]*e[5]-e[4]*e[3])/t,(e[4]*e[1]-e[5]*e[0])/t]}static singularValueDecompose2dScale(e,t){const a=e[0],r=e[1],i=e[2],n=e[3],s=a**2+r**2,o=a*i+r*n,c=i**2+n**2,l=(s+c)/2,h=Math.sqrt(l**2-(s*c-o**2));t[0]=Math.sqrt(l+h||1);t[1]=Math.sqrt(l-h||1)}static normalizeRect(e){const t=e.slice(0);if(e[0]>e[2]){t[0]=e[2];t[2]=e[0]}if(e[1]>e[3]){t[1]=e[3];t[3]=e[1]}return t}static intersect(e,t){const a=Math.max(Math.min(e[0],e[2]),Math.min(t[0],t[2])),r=Math.min(Math.max(e[0],e[2]),Math.max(t[0],t[2]));if(a>r)return null;const i=Math.max(Math.min(e[1],e[3]),Math.min(t[1],t[3])),n=Math.min(Math.max(e[1],e[3]),Math.max(t[1],t[3]));return i>n?null:[a,i,r,n]}static pointBoundingBox(e,t,a){a[0]=Math.min(a[0],e);a[1]=Math.min(a[1],t);a[2]=Math.max(a[2],e);a[3]=Math.max(a[3],t)}static rectBoundingBox(e,t,a,r,i){i[0]=Math.min(i[0],e,a);i[1]=Math.min(i[1],t,r);i[2]=Math.max(i[2],e,a);i[3]=Math.max(i[3],t,r)}static#e(e,t,a,r,i,n,s,o,c,l){if(c<=0||c>=1)return;const h=1-c,u=c*c,d=u*c,f=h*(h*(h*e+3*c*t)+3*u*a)+d*r,g=h*(h*(h*i+3*c*n)+3*u*s)+d*o;l[0]=Math.min(l[0],f);l[1]=Math.min(l[1],g);l[2]=Math.max(l[2],f);l[3]=Math.max(l[3],g)}static#t(e,t,a,r,i,n,s,o,c,l,h,u){if(Math.abs(c)<1e-12){Math.abs(l)>=1e-12&&this.#e(e,t,a,r,i,n,s,o,-h/l,u);return}const d=l**2-4*h*c;if(d<0)return;const f=Math.sqrt(d),g=2*c;this.#e(e,t,a,r,i,n,s,o,(-l+f)/g,u);this.#e(e,t,a,r,i,n,s,o,(-l-f)/g,u)}static bezierBoundingBox(e,t,a,r,i,n,s,o,c){c[0]=Math.min(c[0],e,s);c[1]=Math.min(c[1],t,o);c[2]=Math.max(c[2],e,s);c[3]=Math.max(c[3],t,o);this.#t(e,a,i,s,t,r,n,o,3*(3*(a-i)-e+s),6*(e-2*a+i),3*(a-e),c);this.#t(e,a,i,s,t,r,n,o,3*(3*(r-n)-t+o),6*(t-2*r+n),3*(r-t),c)}}const pa=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364];function stringToPDFString(e,t=!1){if(e[0]>="ï"){let a;if("þ"===e[0]&&"ÿ"===e[1]){a="utf-16be";e.length%2==1&&(e=e.slice(0,-1))}else if("ÿ"===e[0]&&"þ"===e[1]){a="utf-16le";e.length%2==1&&(e=e.slice(0,-1))}else"ï"===e[0]&&"»"===e[1]&&"¿"===e[2]&&(a="utf-8");if(a)try{const r=new TextDecoder(a,{fatal:!0}),i=stringToBytes(e),n=r.decode(i);return t||!n.includes("")?n:n.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g,"")}catch(e){warn(`stringToPDFString: "${e}".`)}}const a=[];for(let r=0,i=e.length;rga[e])).join("")}"function"!=typeof Promise.try&&(Promise.try=function(e,...t){return new Promise((a=>{a(e(...t))}))});"function"!=typeof Math.sumPrecise&&(Math.sumPrecise=function(e){return e.reduce(((e,t)=>e+t),0)});const ya=Symbol("CIRCULAR_REF"),wa=Symbol("EOF");let Sa=Object.create(null),xa=Object.create(null),Aa=Object.create(null);class Name{constructor(e){this.name=e}static get(e){return xa[e]||=new Name(e)}}class Cmd{constructor(e){this.cmd=e}static get(e){return Sa[e]||=new Cmd(e)}}const ka=function nonSerializableClosure(){return ka};class Dict{constructor(e=null){this._map=new Map;this.xref=e;this.objId=null;this.suppressEncryption=!1;this.__nonSerializable__=ka}assignXref(e){this.xref=e}get size(){return this._map.size}get(e,t,a){let r=this._map.get(e);if(void 0===r&&void 0!==t){r=this._map.get(t);void 0===r&&void 0!==a&&(r=this._map.get(a))}return r instanceof Ref&&this.xref?this.xref.fetch(r,this.suppressEncryption):r}async getAsync(e,t,a){let r=this._map.get(e);if(void 0===r&&void 0!==t){r=this._map.get(t);void 0===r&&void 0!==a&&(r=this._map.get(a))}return r instanceof Ref&&this.xref?this.xref.fetchAsync(r,this.suppressEncryption):r}getArray(e,t,a){let r=this._map.get(e);if(void 0===r&&void 0!==t){r=this._map.get(t);void 0===r&&void 0!==a&&(r=this._map.get(a))}r instanceof Ref&&this.xref&&(r=this.xref.fetch(r,this.suppressEncryption));if(Array.isArray(r)){r=r.slice();for(let e=0,t=r.length;e{unreachable("Should not call `set` on the empty dictionary.")};return shadow(this,"empty",e)}static merge({xref:e,dictArray:t,mergeSubDicts:a=!1}){const r=new Dict(e),i=new Map;for(const e of t)if(e instanceof Dict)for(const[t,r]of e._map){let e=i.get(t);if(void 0===e){e=[];i.set(t,e)}else if(!(a&&r instanceof Dict))continue;e.push(r)}for(const[t,a]of i){if(1===a.length||!(a[0]instanceof Dict)){r._map.set(t,a[0]);continue}const i=new Dict(e);for(const e of a)for(const[t,a]of e._map)i._map.has(t)||i._map.set(t,a);i.size>0&&r._map.set(t,i)}i.clear();return r.size>0?r:Dict.empty}clone(){const e=new Dict(this.xref);for(const t of this.getKeys())e.set(t,this.getRaw(t));return e}delete(e){delete this._map[e]}}class Ref{constructor(e,t){this.num=e;this.gen=t}toString(){return 0===this.gen?`${this.num}R`:`${this.num}R${this.gen}`}static fromString(e){const t=Aa[e];if(t)return t;const a=/^(\d+)R(\d*)$/.exec(e);return a&&"0"!==a[1]?Aa[e]=new Ref(parseInt(a[1]),a[2]?parseInt(a[2]):0):null}static get(e,t){const a=0===t?`${e}R`:`${e}R${t}`;return Aa[a]||=new Ref(e,t)}}class RefSet{constructor(e=null){this._set=new Set(e?._set)}has(e){return this._set.has(e.toString())}put(e){this._set.add(e.toString())}remove(e){this._set.delete(e.toString())}[Symbol.iterator](){return this._set.values()}clear(){this._set.clear()}}class RefSetCache{constructor(){this._map=new Map}get size(){return this._map.size}get(e){return this._map.get(e.toString())}has(e){return this._map.has(e.toString())}put(e,t){this._map.set(e.toString(),t)}putAlias(e,t){this._map.set(e.toString(),this.get(t))}[Symbol.iterator](){return this._map.values()}clear(){this._map.clear()}*values(){yield*this._map.values()}*items(){for(const[e,t]of this._map)yield[Ref.fromString(e),t]}}function isName(e,t){return e instanceof Name&&(void 0===t||e.name===t)}function isCmd(e,t){return e instanceof Cmd&&(void 0===t||e.cmd===t)}function isDict(e,t){return e instanceof Dict&&(void 0===t||isName(e.get("Type"),t))}function isRefsEqual(e,t){return e.num===t.num&&e.gen===t.gen}class BaseStream{get length(){unreachable("Abstract getter `length` accessed")}get isEmpty(){unreachable("Abstract getter `isEmpty` accessed")}get isDataLoaded(){return shadow(this,"isDataLoaded",!0)}getByte(){unreachable("Abstract method `getByte` called")}getBytes(e){unreachable("Abstract method `getBytes` called")}async getImageData(e,t){return this.getBytes(e,t)}async asyncGetBytes(){unreachable("Abstract method `asyncGetBytes` called")}get isAsync(){return!1}get isAsyncDecoder(){return!1}get canAsyncDecodeImageFromBuffer(){return!1}async getTransferableImage(){return null}peekByte(){const e=this.getByte();-1!==e&&this.pos--;return e}peekBytes(e){const t=this.getBytes(e);this.pos-=t.length;return t}getUint16(){const e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t}getInt32(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()}getByteRange(e,t){unreachable("Abstract method `getByteRange` called")}getString(e){return bytesToString(this.getBytes(e))}skip(e){this.pos+=e||1}reset(){unreachable("Abstract method `reset` called")}moveStart(){unreachable("Abstract method `moveStart` called")}makeSubStream(e,t,a=null){unreachable("Abstract method `makeSubStream` called")}getBaseStreams(){return null}}const Ca=/^[1-9]\.\d$/,va=2**31-1,Fa=[1,0,0,1,0,0],Ia=["ColorSpace","ExtGState","Font","Pattern","Properties","Shading","XObject"],Oa=["ExtGState","Font","Properties","XObject"];function getLookupTableFactory(e){let t;return function(){if(e){t=Object.create(null);e(t);e=null}return t}}class MissingDataException extends fa{constructor(e,t){super(`Missing data [${e}, ${t})`,"MissingDataException");this.begin=e;this.end=t}}class ParserEOFException extends fa{constructor(e){super(e,"ParserEOFException")}}class XRefEntryException extends fa{constructor(e){super(e,"XRefEntryException")}}class XRefParseException extends fa{constructor(e){super(e,"XRefParseException")}}function arrayBuffersToBytes(e){const t=e.length;if(0===t)return new Uint8Array(0);if(1===t)return new Uint8Array(e[0]);let a=0;for(let r=0;r0,"The number should be a positive integer.");const a="M".repeat(e/1e3|0)+Ta[e%1e3/100|0]+Ta[10+(e%100/10|0)]+Ta[20+e%10];return t?a.toLowerCase():a}function log2(e){return e>0?Math.ceil(Math.log2(e)):0}function readInt8(e,t){return e[t]<<24>>24}function readInt16(e,t){return(e[t]<<24|e[t+1]<<16)>>16}function readUint16(e,t){return e[t]<<8|e[t+1]}function readUint32(e,t){return(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0}function isWhiteSpace(e){return 32===e||9===e||13===e||10===e}function isNumberArray(e,t){return Array.isArray(e)?(null===t||e.length===t)&&e.every((e=>"number"==typeof e)):ArrayBuffer.isView(e)&&!(e instanceof BigInt64Array||e instanceof BigUint64Array)&&(null===t||e.length===t)}function lookupMatrix(e,t){return isNumberArray(e,6)?e:t}function lookupRect(e,t){return isNumberArray(e,4)?e:t}function lookupNormalRect(e,t){return isNumberArray(e,4)?Util.normalizeRect(e):t}function parseXFAPath(e){const t=/(.+)\[(\d+)\]$/;return e.split(".").map((e=>{const a=e.match(t);return a?{name:a[1],pos:parseInt(a[2],10)}:{name:e,pos:0}}))}function escapePDFName(e){const t=[];let a=0;for(let r=0,i=e.length;r126||35===i||40===i||41===i||60===i||62===i||91===i||93===i||123===i||125===i||47===i||37===i){a"\n"===e?"\\n":"\r"===e?"\\r":`\\${e}`))}function _collectJS(e,t,a,r){if(!e)return;let i=null;if(e instanceof Ref){if(r.has(e))return;i=e;r.put(i);e=t.fetch(e)}if(Array.isArray(e))for(const i of e)_collectJS(i,t,a,r);else if(e instanceof Dict){if(isName(e.get("S"),"JavaScript")){const t=e.get("JS");let r;t instanceof BaseStream?r=t.getString():"string"==typeof t&&(r=t);r&&=stringToPDFString(r,!0).replaceAll("\0","");r&&a.push(r)}_collectJS(e.getRaw("Next"),t,a,r)}i&&r.remove(i)}function collectActions(e,t,a){const r=Object.create(null),i=getInheritableProperty({dict:t,key:"AA",stopWhenFound:!1});if(i)for(let t=i.length-1;t>=0;t--){const n=i[t];if(n instanceof Dict)for(const t of n.getKeys()){const i=a[t];if(!i)continue;const s=[];_collectJS(n.getRaw(t),e,s,new RefSet);s.length>0&&(r[i]=s)}}if(t.has("A")){const a=[];_collectJS(t.get("A"),e,a,new RefSet);a.length>0&&(r.Action=a)}return objectSize(r)>0?r:null}const Ma={60:"<",62:">",38:"&",34:""",39:"'"};function*codePointIter(e){for(let t=0,a=e.length;t55295&&(a<57344||a>65533)&&t++;yield a}}function encodeToXmlString(e){const t=[];let a=0;for(let r=0,i=e.length;r55295&&(i<57344||i>65533)&&r++;a=r+1}}if(0===t.length)return e;a: ${e}.`);return!1}return!0}function validateCSSFont(e){const t=new Set(["100","200","300","400","500","600","700","800","900","1000","normal","bold","bolder","lighter"]),{fontFamily:a,fontWeight:r,italicAngle:i}=e;if(!validateFontName(a,!0))return!1;const n=r?r.toString():"";e.fontWeight=t.has(n)?n:"400";const s=parseFloat(i);e.italicAngle=isNaN(s)||s<-90||s>90?"14":i.toString();return!0}function recoverJsURL(e){const t=new RegExp("^\\s*("+["app.launchURL","window.open","xfa.host.gotoURL"].join("|").replaceAll(".","\\.")+")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))","i").exec(e);return t?.[2]?{url:t[2],newWindow:"app.launchURL"===t[1]&&"true"===t[3]}:null}function numberToString(e){if(Number.isInteger(e))return e.toString();const t=Math.round(100*e);return t%100==0?(t/100).toString():t%10==0?e.toFixed(1):e.toFixed(2)}function getNewAnnotationsMap(e){if(!e)return null;const t=new Map;for(const[a,r]of e){if(!a.startsWith(f))continue;let e=t.get(r.pageIndex);if(!e){e=[];t.set(r.pageIndex,e)}e.push(r)}return t.size>0?t:null}function stringToAsciiOrUTF16BE(e){return function isAscii(e){return/^[\x00-\x7F]*$/.test(e)}(e)?e:stringToUTF16String(e,!0)}function stringToUTF16HexString(e){const t=[];for(let a=0,r=e.length;a>8&255],ga[255&r])}return t.join("")}function stringToUTF16String(e,t=!1){const a=[];t&&a.push("þÿ");for(let t=0,r=e.length;t>8&255),String.fromCharCode(255&r))}return a.join("")}function getRotationMatrix(e,t,a){switch(e){case 90:return[0,1,-1,0,t,0];case 180:return[-1,0,0,-1,t,a];case 270:return[0,-1,1,0,0,a];default:throw new Error("Invalid rotation")}}function getSizeInBytes(e){return Math.ceil(Math.ceil(Math.log2(1+e))/8)}class QCMS{static#a=null;static _memory=null;static _mustAddAlpha=!1;static _destBuffer=null;static _destOffset=0;static _destLength=0;static _cssColor="";static _makeHexColor=null;static get _memoryArray(){const e=this.#a;return e?.byteLength?e:this.#a=new Uint8Array(this._memory.buffer)}}let Da;const Ba="undefined"!=typeof TextDecoder?new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}):{decode:()=>{throw Error("TextDecoder not available")}};"undefined"!=typeof TextDecoder&&Ba.decode();let Ra=null;function getUint8ArrayMemory0(){null!==Ra&&0!==Ra.byteLength||(Ra=new Uint8Array(Da.memory.buffer));return Ra}let Na=0;function passArray8ToWasm0(e,t){const a=t(1*e.length,1)>>>0;getUint8ArrayMemory0().set(e,a/1);Na=e.length;return a}const Ea=Object.freeze({RGB8:0,0:"RGB8",RGBA8:1,1:"RGBA8",BGRA8:2,2:"BGRA8",Gray8:3,3:"Gray8",GrayA8:4,4:"GrayA8",CMYK:5,5:"CMYK"}),Pa=Object.freeze({Perceptual:0,0:"Perceptual",RelativeColorimetric:1,1:"RelativeColorimetric",Saturation:2,2:"Saturation",AbsoluteColorimetric:3,3:"AbsoluteColorimetric"});function __wbg_get_imports(){const e={wbg:{}};e.wbg.__wbg_copyresult_b08ee7d273f295dd=function(e,t){!function copy_result(e,t){const{_mustAddAlpha:a,_destBuffer:r,_destOffset:i,_destLength:n,_memoryArray:s}=QCMS;if(t!==n)if(a)for(let a=e,n=e+t,o=i;a>>0,t>>>0)};e.wbg.__wbg_copyrgb_d60ce17bb05d9b67=function(e){!function copy_rgb(e){const{_destBuffer:t,_destOffset:a,_memoryArray:r}=QCMS;t[a]=r[e];t[a+1]=r[e+1];t[a+2]=r[e+2]}(e>>>0)};e.wbg.__wbg_makecssRGB_893bf0cd9fdb302d=function(e){!function make_cssRGB(e){const{_memoryArray:t}=QCMS;QCMS._cssColor=QCMS._makeHexColor(t[e],t[e+1],t[e+2])}(e>>>0)};e.wbg.__wbindgen_init_externref_table=function(){const e=Da.__wbindgen_export_0,t=e.grow(4);e.set(0,void 0);e.set(t+0,void 0);e.set(t+1,null);e.set(t+2,!0);e.set(t+3,!1)};e.wbg.__wbindgen_throw=function(e,t){throw new Error(function getStringFromWasm0(e,t){e>>>=0;return Ba.decode(getUint8ArrayMemory0().subarray(e,e+t))}(e,t))};return e}function __wbg_finalize_init(e,t){Da=e.exports;__wbg_init.__wbindgen_wasm_module=t;Ra=null;Da.__wbindgen_start();return Da}async function __wbg_init(e){if(void 0!==Da)return Da;void 0!==e&&(Object.getPrototypeOf(e)===Object.prototype?({module_or_path:e}=e):console.warn("using deprecated parameters for the initialization function; pass a single object instead"));const t=__wbg_get_imports();("string"==typeof e||"function"==typeof Request&&e instanceof Request||"function"==typeof URL&&e instanceof URL)&&(e=fetch(e));const{instance:a,module:r}=await async function __wbg_load(e,t){if("function"==typeof Response&&e instanceof Response){if("function"==typeof WebAssembly.instantiateStreaming)try{return await WebAssembly.instantiateStreaming(e,t)}catch(t){if("application/wasm"==e.headers.get("Content-Type"))throw t;console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",t)}const a=await e.arrayBuffer();return await WebAssembly.instantiate(a,t)}{const a=await WebAssembly.instantiate(e,t);return a instanceof WebAssembly.Instance?{instance:a,module:e}:a}}(await e,t);return __wbg_finalize_init(a,r)}class ColorSpace{static#r=new Uint8ClampedArray(3);constructor(e,t){this.name=e;this.numComps=t}getRgb(e,t,a=new Uint8ClampedArray(3)){this.getRgbItem(e,t,a,0);return a}getRgbHex(e,t){const a=this.getRgb(e,t,ColorSpace.#r);return Util.makeHexColor(a[0],a[1],a[2])}getRgbItem(e,t,a,r){unreachable("Should not call ColorSpace.getRgbItem")}getRgbBuffer(e,t,a,r,i,n,s){unreachable("Should not call ColorSpace.getRgbBuffer")}getOutputLength(e,t){unreachable("Should not call ColorSpace.getOutputLength")}isPassthrough(e){return!1}isDefaultDecode(e,t){return ColorSpace.isDefaultDecode(e,this.numComps)}fillRgb(e,t,a,r,i,n,s,o,c){const l=t*a;let h=null;const u=1<u&&"DeviceGray"!==this.name&&"DeviceRGB"!==this.name){const t=s<=8?new Uint8Array(u):new Uint16Array(u);for(let e=0;e=.99554525?1:MathClamp(1.055*e**(1/2.4)-.055,0,1)}#b(e){return e<0?-this.#b(-e):e>8?((e+16)/116)**3:e*CalRGBCS.#d}#y(e,t,a){if(0===e[0]&&0===e[1]&&0===e[2]){a[0]=t[0];a[1]=t[1];a[2]=t[2];return}const r=this.#b(0),i=(1-r)/(1-this.#b(e[0])),n=1-i,s=(1-r)/(1-this.#b(e[1])),o=1-s,c=(1-r)/(1-this.#b(e[2])),l=1-c;a[0]=t[0]*i+n;a[1]=t[1]*s+o;a[2]=t[2]*c+l}#w(e,t,a){if(1===e[0]&&1===e[2]){a[0]=t[0];a[1]=t[1];a[2]=t[2];return}const r=a;this.#f(CalRGBCS.#n,t,r);const i=CalRGBCS.#l;this.#g(e,r,i);this.#f(CalRGBCS.#s,i,a)}#S(e,t,a){const r=a;this.#f(CalRGBCS.#n,t,r);const i=CalRGBCS.#l;this.#p(e,r,i);this.#f(CalRGBCS.#s,i,a)}#i(e,t,a,r,i){const n=MathClamp(e[t]*i,0,1),s=MathClamp(e[t+1]*i,0,1),o=MathClamp(e[t+2]*i,0,1),c=1===n?1:n**this.GR,l=1===s?1:s**this.GG,h=1===o?1:o**this.GB,u=this.MXA*c+this.MXB*l+this.MXC*h,d=this.MYA*c+this.MYB*l+this.MYC*h,f=this.MZA*c+this.MZB*l+this.MZC*h,g=CalRGBCS.#h;g[0]=u;g[1]=d;g[2]=f;const p=CalRGBCS.#u;this.#w(this.whitePoint,g,p);const m=CalRGBCS.#h;this.#y(this.blackPoint,p,m);const b=CalRGBCS.#u;this.#S(CalRGBCS.#c,m,b);const y=CalRGBCS.#h;this.#f(CalRGBCS.#o,b,y);a[r]=255*this.#m(y[0]);a[r+1]=255*this.#m(y[1]);a[r+2]=255*this.#m(y[2])}getRgbItem(e,t,a,r){this.#i(e,t,a,r,1)}getRgbBuffer(e,t,a,r,i,n,s){const o=1/((1<this.amax||this.bmin>this.bmax){info("Invalid Range, falling back to defaults");this.amin=-100;this.amax=100;this.bmin=-100;this.bmax=100}}#x(e){return e>=6/29?e**3:108/841*(e-4/29)}#A(e,t,a,r){return a+e*(r-a)/t}#i(e,t,a,r,i){let n=e[t],s=e[t+1],o=e[t+2];if(!1!==a){n=this.#A(n,a,0,100);s=this.#A(s,a,this.amin,this.amax);o=this.#A(o,a,this.bmin,this.bmax)}s>this.amax?s=this.amax:sthis.bmax?o=this.bmax:o{!function qcms_drop_transformer(e){Da.qcms_drop_transformer(e)}(e)}));constructor(e,t,a){if(!IccColorSpace.isUsable)throw new Error("No ICC color space support");super(t,a);let r;switch(a){case 1:r=Ea.Gray8;this.#C=(e,t,a)=>function qcms_convert_one(e,t,a){Da.qcms_convert_one(e,t,a)}(this.#k,255*e[t],a);break;case 3:r=Ea.RGB8;this.#C=(e,t,a)=>function qcms_convert_three(e,t,a,r,i){Da.qcms_convert_three(e,t,a,r,i)}(this.#k,255*e[t],255*e[t+1],255*e[t+2],a);break;case 4:r=Ea.CMYK;this.#C=(e,t,a)=>function qcms_convert_four(e,t,a,r,i,n){Da.qcms_convert_four(e,t,a,r,i,n)}(this.#k,255*e[t],255*e[t+1],255*e[t+2],255*e[t+3],a);break;default:throw new Error(`Unsupported number of components: ${a}`)}this.#k=function qcms_transformer_from_memory(e,t,a){const r=passArray8ToWasm0(e,Da.__wbindgen_malloc),i=Na;return Da.qcms_transformer_from_memory(r,i,t,a)>>>0}(e,r,Pa.Perceptual);if(!this.#k)throw new Error("Failed to create ICC color space");IccColorSpace.#I.register(this,this.#k)}getRgbHex(e,t){this.#C(e,t,!0);return QCMS._cssColor}getRgbItem(e,t,a,r){QCMS._destBuffer=a;QCMS._destOffset=r;QCMS._destLength=3;this.#C(e,t,!1);QCMS._destBuffer=null}getRgbBuffer(e,t,a,r,i,n,s){e=e.subarray(t,t+a*this.numComps);if(8!==n){const t=255/((1<=this.end?-1:this.bytes[this.pos++]}getBytes(e){const t=this.bytes,a=this.pos,r=this.end;if(!e)return t.subarray(a,r);let i=a+e;i>r&&(i=r);this.pos=i;return t.subarray(a,i)}getByteRange(e,t){e<0&&(e=0);t>this.end&&(t=this.end);return this.bytes.subarray(e,t)}reset(){this.pos=this.start}moveStart(){this.start=this.pos}makeSubStream(e,t,a=null){return new Stream(this.bytes.buffer,e,t,a)}}class StringStream extends Stream{constructor(e){super(stringToBytes(e))}}class NullStream extends Stream{constructor(){super(new Uint8Array(0))}}class ChunkedStream extends Stream{constructor(e,t,a){super(new Uint8Array(e),0,e,null);this.chunkSize=t;this._loadedChunks=new Set;this.numChunks=Math.ceil(e/t);this.manager=a;this.progressiveDataLength=0;this.lastSuccessfulEnsureByteChunk=-1}getMissingChunks(){const e=[];for(let t=0,a=this.numChunks;t=this.end?this.numChunks:Math.floor(t/this.chunkSize);for(let e=a;ethis.numChunks)&&t!==this.lastSuccessfulEnsureByteChunk){if(!this._loadedChunks.has(t))throw new MissingDataException(e,e+1);this.lastSuccessfulEnsureByteChunk=t}}ensureRange(e,t){if(e>=t)return;if(t<=this.progressiveDataLength)return;const a=Math.floor(e/this.chunkSize);if(a>this.numChunks)return;const r=Math.min(Math.floor((t-1)/this.chunkSize)+1,this.numChunks);for(let i=a;i=this.end)return-1;e>=this.progressiveDataLength&&this.ensureByte(e);return this.bytes[this.pos++]}getBytes(e){const t=this.bytes,a=this.pos,r=this.end;if(!e){r>this.progressiveDataLength&&this.ensureRange(a,r);return t.subarray(a,r)}let i=a+e;i>r&&(i=r);i>this.progressiveDataLength&&this.ensureRange(a,i);this.pos=i;return t.subarray(a,i)}getByteRange(e,t){e<0&&(e=0);t>this.end&&(t=this.end);t>this.progressiveDataLength&&this.ensureRange(e,t);return this.bytes.subarray(e,t)}makeSubStream(e,t,a=null){t?e+t>this.progressiveDataLength&&this.ensureRange(e,e+t):e>=this.progressiveDataLength&&this.ensureByte(e);function ChunkedStreamSubstream(){}ChunkedStreamSubstream.prototype=Object.create(this);ChunkedStreamSubstream.prototype.getMissingChunks=function(){const e=this.chunkSize,t=Math.floor(this.start/e),a=Math.floor((this.end-1)/e)+1,r=[];for(let e=t;e{const readChunk=({value:n,done:s})=>{try{if(s){const t=arrayBuffersToBytes(r);r=null;e(t);return}i+=n.byteLength;a.isStreamingSupported&&this.onProgress({loaded:i});r.push(n);a.read().then(readChunk,t)}catch(e){t(e)}};a.read().then(readChunk,t)})).then((t=>{this.aborted||this.onReceiveData({chunk:t,begin:e})}))}requestAllChunks(e=!1){if(!e){const e=this.stream.getMissingChunks();this._requestChunks(e)}return this._loadedStreamCapability.promise}_requestChunks(e){const t=this.currRequestId++,a=new Set;this._chunksNeededByRequest.set(t,a);for(const t of e)this.stream.hasChunk(t)||a.add(t);if(0===a.size)return Promise.resolve();const r=Promise.withResolvers();this._promisesByRequest.set(t,r);const i=[];for(const e of a){let a=this._requestsByChunk.get(e);if(!a){a=[];this._requestsByChunk.set(e,a);i.push(e)}a.push(t)}if(i.length>0){const e=this.groupChunks(i);for(const t of e){const e=t.beginChunk*this.chunkSize,a=Math.min(t.endChunk*this.chunkSize,this.length);this.sendRequest(e,a).catch(r.reject)}}return r.promise.catch((e=>{if(!this.aborted)throw e}))}getStream(){return this.stream}requestRange(e,t){t=Math.min(t,this.length);const a=this.getBeginChunk(e),r=this.getEndChunk(t),i=[];for(let e=a;ee-t));return this._requestChunks(t)}groupChunks(e){const t=[];let a=-1,r=-1;for(let i=0,n=e.length;i=0&&r+1!==n){t.push({beginChunk:a,endChunk:r+1});a=n}i+1===e.length&&t.push({beginChunk:a,endChunk:n+1});r=n}return t}onProgress(e){this.msgHandler.send("DocProgress",{loaded:this.stream.numChunksLoaded*this.chunkSize+e.loaded,total:this.length})}onReceiveData(e){const t=e.chunk,a=void 0===e.begin,r=a?this.progressiveDataLength:e.begin,i=r+t.byteLength,n=Math.floor(r/this.chunkSize),s=i0||o.push(a)}}}if(!this.disableAutoFetch&&0===this._requestsByChunk.size){let e;if(1===this.stream.numChunksLoaded){const t=this.stream.numChunks-1;this.stream.hasChunk(t)||(e=t)}else e=this.stream.nextEmptyChunk(s);Number.isInteger(e)&&this._requestChunks([e])}for(const e of o){const t=this._promisesByRequest.get(e);this._promisesByRequest.delete(e);t.resolve()}this.msgHandler.send("DocProgress",{loaded:this.stream.numChunksLoaded*this.chunkSize,total:this.length})}onError(e){this._loadedStreamCapability.reject(e)}getBeginChunk(e){return Math.floor(e/this.chunkSize)}getEndChunk(e){return Math.floor((e-1)/this.chunkSize)+1}abort(e){this.aborted=!0;this.pdfNetworkStream?.cancelAllRequests(e);for(const t of this._promisesByRequest.values())t.reject(e)}}function convertToRGBA(e){switch(e.kind){case k:return convertBlackAndWhiteToRGBA(e);case C:return function convertRGBToRGBA({src:e,srcPos:t=0,dest:a,destPos:r=0,width:i,height:n}){let s=0;const o=i*n*3,c=o>>2,l=new Uint32Array(e.buffer,t,c);if(FeatureTest.isLittleEndian){for(;s>>24|t<<8|4278190080;a[r+2]=t>>>16|i<<16|4278190080;a[r+3]=i>>>8|4278190080}for(let i=4*s,n=t+o;i>>8|255;a[r+2]=t<<16|i>>>16|255;a[r+3]=i<<8|255}for(let i=4*s,n=t+o;i>3,u=7&r,d=e.length;a=new Uint32Array(a.buffer);let f=0;for(let r=0;ra||t>a)return!0;const r=e*t;if(this._hasMaxArea)return r>this.MAX_AREA;if(r(this.MAX_AREA=this.#T**2)}static getReducePowerForJPX(e,t,a){const r=e*t,i=2**30/(4*a);if(!this.needsToBeResized(e,t))return r>i?Math.ceil(Math.log2(r/i)):0;const{MAX_DIM:n,MAX_AREA:s}=this,o=Math.max(e/n,t/n,Math.sqrt(r/Math.min(i,s)));return Math.ceil(Math.log2(o))}static get MAX_DIM(){return shadow(this,"MAX_DIM",this._guessMax(2048,65537,0,1))}static get MAX_AREA(){this._hasMaxArea=!0;return shadow(this,"MAX_AREA",this._guessMax(this.#T,this.MAX_DIM,128,0)**2)}static set MAX_AREA(e){if(e>=0){this._hasMaxArea=!0;shadow(this,"MAX_AREA",e)}}static setOptions({canvasMaxAreaInBytes:e=-1,isImageDecoderSupported:t=!1}){this._hasMaxArea||(this.MAX_AREA=e>>2);this.#M=t}static _areGoodDims(e,t){try{const a=new OffscreenCanvas(e,t),r=a.getContext("2d");r.fillRect(0,0,1,1);const i=r.getImageData(0,0,1,1).data[3];a.width=a.height=1;return 0!==i}catch{return!1}}static _guessMax(e,t,a,r){for(;e+a+1va){const e=this.#D();if(e)return e}const r=this._encodeBMP();let i,n;if(await ImageResizer.canUseImageDecoder){i=new ImageDecoder({data:r,type:"image/bmp",preferAnimation:!1,transfer:[r.buffer]});n=i.decode().catch((e=>{warn(`BMP image decoding failed: ${e}`);return createImageBitmap(new Blob([this._encodeBMP().buffer],{type:"image/bmp"}))})).finally((()=>{i.close()}))}else n=createImageBitmap(new Blob([r.buffer],{type:"image/bmp"}));const{MAX_AREA:s,MAX_DIM:o}=ImageResizer,c=Math.max(t/o,a/o,Math.sqrt(t*a/s)),l=Math.max(c,2),h=Math.round(10*(c+1.25))/10/l,u=Math.floor(Math.log2(h)),d=new Array(u+2).fill(2);d[0]=l;d.splice(-1,1,h/(1<>s,c=r>>s;let l,h=r;try{l=new Uint8Array(n)}catch{let e=Math.floor(Math.log2(n+1));for(;;)try{l=new Uint8Array(2**e-1);break}catch{e-=1}h=Math.floor((2**e-1)/(4*a));const t=a*h*4;t>s;e>3,s=a+3&-4;if(a!==s){const e=new Uint8Array(s*t);let r=0;for(let n=0,o=t*a;ni&&(r=i)}else{for(;!this.eof;)this.readBlock(t);r=this.bufferLength}this.pos=r;return this.buffer.subarray(a,r)}async getImageData(e,t){if(!this.canAsyncDecodeImageFromBuffer)return this.isAsyncDecoder?this.decodeImage(null,t):this.getBytes(e,t);const a=await this.stream.asyncGetBytes();return this.decodeImage(a,t)}reset(){this.pos=0}makeSubStream(e,t,a=null){if(void 0===t)for(;!this.eof;)this.readBlock();else{const a=e+t;for(;this.bufferLength<=a&&!this.eof;)this.readBlock()}return new Stream(this.buffer,e,t,a)}getBaseStreams(){return this.str?this.str.getBaseStreams():null}}class StreamsSequenceStream extends DecodeStream{constructor(e,t=null){e=e.filter((e=>e instanceof BaseStream));let a=0;for(const t of e)a+=t instanceof DecodeStream?t._rawMinBufferLength:t.length;super(a);this.streams=e;this._onError=t}readBlock(){const e=this.streams;if(0===e.length){this.eof=!0;return}const t=e.shift();let a;try{a=t.getBytes()}catch(e){if(this._onError){this._onError(e,t.dict?.objId);return}throw e}const r=this.bufferLength,i=r+a.length;this.ensureBuffer(i).set(a,r);this.bufferLength=i}getBaseStreams(){const e=[];for(const t of this.streams){const a=t.getBaseStreams();a&&e.push(...a)}return e.length>0?e:null}}class ColorSpaceUtils{static parse({cs:e,xref:t,resources:a=null,pdfFunctionFactory:r,globalColorSpaceCache:i,localColorSpaceCache:n,asyncIfNotCached:s=!1}){const o={xref:t,resources:a,pdfFunctionFactory:r,globalColorSpaceCache:i,localColorSpaceCache:n};let c,l,h;if(e instanceof Ref){l=e;const a=i.getByRef(l)||n.getByRef(l);if(a)return a;e=t.fetch(e)}if(e instanceof Name){c=e.name;const t=n.getByName(c);if(t)return t}try{h=this.#B(e,o)}catch(e){if(s&&!(e instanceof MissingDataException))return Promise.reject(e);throw e}if(c||l){n.set(c,l,h);l&&i.set(null,l,h)}return s?Promise.resolve(h):h}static#R(e,t){const{globalColorSpaceCache:a}=t;let r;if(e instanceof Ref){r=e;const t=a.getByRef(r);if(t)return t}const i=this.#B(e,t);r&&a.set(null,r,i);return i}static#B(e,t){const{xref:a,resources:r,pdfFunctionFactory:i,globalColorSpaceCache:n}=t;if((e=a.fetchIfRef(e))instanceof Name)switch(e.name){case"G":case"DeviceGray":return this.gray;case"RGB":case"DeviceRGB":return this.rgb;case"DeviceRGBA":return this.rgba;case"CMYK":case"DeviceCMYK":return this.cmyk;case"Pattern":return new PatternCS(null);default:if(r instanceof Dict){const a=r.get("ColorSpace");if(a instanceof Dict){const r=a.get(e.name);if(r){if(r instanceof Name)return this.#B(r,t);e=r;break}}}warn(`Unrecognized ColorSpace: ${e.name}`);return this.gray}if(Array.isArray(e)){const r=a.fetchIfRef(e[0]).name;let s,o,c,l,h,u;switch(r){case"G":case"DeviceGray":return this.gray;case"RGB":case"DeviceRGB":return this.rgb;case"CMYK":case"DeviceCMYK":return this.cmyk;case"CalGray":s=a.fetchIfRef(e[1]);l=s.getArray("WhitePoint");h=s.getArray("BlackPoint");u=s.get("Gamma");return new CalGrayCS(l,h,u);case"CalRGB":s=a.fetchIfRef(e[1]);l=s.getArray("WhitePoint");h=s.getArray("BlackPoint");u=s.getArray("Gamma");const d=s.getArray("Matrix");return new CalRGBCS(l,h,u,d);case"ICCBased":const f=e[1]instanceof Ref;if(f){const t=n.getByRef(e[1]);if(t)return t}const g=a.fetchIfRef(e[1]),p=g.dict;o=p.get("N");if(IccColorSpace.isUsable)try{const t=new IccColorSpace(g.getBytes(),"ICCBased",o);f&&n.set(null,e[1],t);return t}catch(t){if(t instanceof MissingDataException)throw t;warn(`ICCBased color space (${e[1]}): "${t}".`)}const m=p.getRaw("Alternate");if(m){const e=this.#R(m,t);if(e.numComps===o)return e;warn("ICCBased color space: Ignoring incorrect /Alternate entry.")}if(1===o)return this.gray;if(3===o)return this.rgb;if(4===o)return this.cmyk;break;case"Pattern":c=e[1]||null;c&&(c=this.#R(c,t));return new PatternCS(c);case"I":case"Indexed":c=this.#R(e[1],t);const b=MathClamp(a.fetchIfRef(e[2]),0,255),y=a.fetchIfRef(e[3]);return new IndexedCS(c,b,y);case"Separation":case"DeviceN":const w=a.fetchIfRef(e[1]);o=Array.isArray(w)?w.length:1;c=this.#R(e[2],t);const S=i.create(e[3]);return new AlternateCS(o,c,S);case"Lab":s=a.fetchIfRef(e[1]);l=s.getArray("WhitePoint");h=s.getArray("BlackPoint");const x=s.getArray("Range");return new LabCS(l,h,x);default:warn(`Unimplemented ColorSpace object: ${r}`);return this.gray}}warn(`Unrecognized ColorSpace object: ${e}`);return this.gray}static get gray(){return shadow(this,"gray",new DeviceGrayCS)}static get rgb(){return shadow(this,"rgb",new DeviceRgbCS)}static get rgba(){return shadow(this,"rgba",new DeviceRgbaCS)}static get cmyk(){if(CmykICCBasedCS.isUsable)try{return shadow(this,"cmyk",new CmykICCBasedCS)}catch{warn("CMYK fallback: DeviceCMYK")}return shadow(this,"cmyk",new DeviceCmykCS)}}class JpegError extends fa{constructor(e){super(e,"JpegError")}}class DNLMarkerError extends fa{constructor(e,t){super(e,"DNLMarkerError");this.scanLines=t}}class EOIMarkerError extends fa{constructor(e){super(e,"EOIMarkerError")}}const ja=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),_a=4017,Ua=799,Xa=3406,qa=2276,Ha=1567,Wa=3784,za=5793,$a=2896;function buildHuffmanTable(e,t){let a,r,i=0,n=16;for(;n>0&&!e[n-1];)n--;const s=[{children:[],index:0}];let o,c=s[0];for(a=0;a0;)c=s.pop();c.index++;s.push(c);for(;s.length<=a;){s.push(o={children:[],index:0});c.children[c.index]=o.children;c=o}i++}if(a+10){g--;return f>>g&1}f=e[t++];if(255===f){const r=e[t++];if(r){if(220===r&&l){const r=readUint16(e,t+=2);t+=2;if(r>0&&r!==a.scanLines)throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",r)}else if(217===r){if(l){const e=y*(8===a.precision?8:0);if(e>0&&Math.round(a.scanLines/e)>=5)throw new DNLMarkerError("Found EOI marker (0xFFD9) while parsing scan data, possibly caused by incorrect `scanLines` parameter",e)}throw new EOIMarkerError("Found EOI marker (0xFFD9) while parsing scan data")}throw new JpegError(`unexpected marker ${(f<<8|r).toString(16)}`)}}g=7;return f>>>7}function decodeHuffman(e){let t=e;for(;;){t=t[readBit()];switch(typeof t){case"number":return t;case"object":continue}throw new JpegError("invalid huffman sequence")}}function receive(e){let t=0;for(;e>0;){t=t<<1|readBit();e--}return t}function receiveAndExtend(e){if(1===e)return 1===readBit()?1:-1;const t=receive(e);return t>=1<0){p--;return}let a=n;const r=s;for(;a<=r;){const r=decodeHuffman(e.huffmanTableAC),i=15&r,n=r>>4;if(0===i){if(n<15){p=receive(n)+(1<>4;if(0===i)if(l<15){p=receive(l)+(1<>4;if(0===r){if(n<15)break;i+=16;continue}i+=n;const s=ja[i];e.blockData[t+s]=receiveAndExtend(r);i++}};let O,T=0;const M=1===w?r[0].blocksPerLine*r[0].blocksPerColumn:h*a.mcusPerColumn;let D,R;for(;T<=M;){const a=i?Math.min(M-T,i):M;if(a>0){for(x=0;x0?"unexpected":"excessive"} MCU data, current marker is: ${O.invalid}`);t=O.offset}if(!(O.marker>=65488&&O.marker<=65495))break;t+=2}return t-d}function quantizeAndInverse(e,t,a){const r=e.quantizationTable,i=e.blockData;let n,s,o,c,l,h,u,d,f,g,p,m,b,y,w,S,x;if(!r)throw new JpegError("missing required Quantization Table.");for(let e=0;e<64;e+=8){f=i[t+e];g=i[t+e+1];p=i[t+e+2];m=i[t+e+3];b=i[t+e+4];y=i[t+e+5];w=i[t+e+6];S=i[t+e+7];f*=r[e];if(g|p|m|b|y|w|S){g*=r[e+1];p*=r[e+2];m*=r[e+3];b*=r[e+4];y*=r[e+5];w*=r[e+6];S*=r[e+7];n=za*f+128>>8;s=za*b+128>>8;o=p;c=w;l=$a*(g-S)+128>>8;d=$a*(g+S)+128>>8;h=m<<4;u=y<<4;n=n+s+1>>1;s=n-s;x=o*Wa+c*Ha+128>>8;o=o*Ha-c*Wa+128>>8;c=x;l=l+u+1>>1;u=l-u;d=d+h+1>>1;h=d-h;n=n+c+1>>1;c=n-c;s=s+o+1>>1;o=s-o;x=l*qa+d*Xa+2048>>12;l=l*Xa-d*qa+2048>>12;d=x;x=h*Ua+u*_a+2048>>12;h=h*_a-u*Ua+2048>>12;u=x;a[e]=n+d;a[e+7]=n-d;a[e+1]=s+u;a[e+6]=s-u;a[e+2]=o+h;a[e+5]=o-h;a[e+3]=c+l;a[e+4]=c-l}else{x=za*f+512>>10;a[e]=x;a[e+1]=x;a[e+2]=x;a[e+3]=x;a[e+4]=x;a[e+5]=x;a[e+6]=x;a[e+7]=x}}for(let e=0;e<8;++e){f=a[e];g=a[e+8];p=a[e+16];m=a[e+24];b=a[e+32];y=a[e+40];w=a[e+48];S=a[e+56];if(g|p|m|b|y|w|S){n=za*f+2048>>12;s=za*b+2048>>12;o=p;c=w;l=$a*(g-S)+2048>>12;d=$a*(g+S)+2048>>12;h=m;u=y;n=4112+(n+s+1>>1);s=n-s;x=o*Wa+c*Ha+2048>>12;o=o*Ha-c*Wa+2048>>12;c=x;l=l+u+1>>1;u=l-u;d=d+h+1>>1;h=d-h;n=n+c+1>>1;c=n-c;s=s+o+1>>1;o=s-o;x=l*qa+d*Xa+2048>>12;l=l*Xa-d*qa+2048>>12;d=x;x=h*Ua+u*_a+2048>>12;h=h*_a-u*Ua+2048>>12;u=x;f=n+d;S=n-d;g=s+u;w=s-u;p=o+h;y=o-h;m=c+l;b=c-l;f<16?f=0:f>=4080?f=255:f>>=4;g<16?g=0:g>=4080?g=255:g>>=4;p<16?p=0:p>=4080?p=255:p>>=4;m<16?m=0:m>=4080?m=255:m>>=4;b<16?b=0:b>=4080?b=255:b>>=4;y<16?y=0:y>=4080?y=255:y>>=4;w<16?w=0:w>=4080?w=255:w>>=4;S<16?S=0:S>=4080?S=255:S>>=4;i[t+e]=f;i[t+e+8]=g;i[t+e+16]=p;i[t+e+24]=m;i[t+e+32]=b;i[t+e+40]=y;i[t+e+48]=w;i[t+e+56]=S}else{x=za*f+8192>>14;x=x<-2040?0:x>=2024?255:x+2056>>4;i[t+e]=x;i[t+e+8]=x;i[t+e+16]=x;i[t+e+24]=x;i[t+e+32]=x;i[t+e+40]=x;i[t+e+48]=x;i[t+e+56]=x}}}function buildComponentData(e,t){const a=t.blocksPerLine,r=t.blocksPerColumn,i=new Int16Array(64);for(let e=0;e=r)return null;const n=readUint16(e,t);if(n>=65472&&n<=65534)return{invalid:null,marker:n,offset:t};let s=readUint16(e,i);for(;!(s>=65472&&s<=65534);){if(++i>=r)return null;s=readUint16(e,i)}return{invalid:n.toString(16),marker:s,offset:i}}function prepareComponents(e){const t=Math.ceil(e.samplesPerLine/8/e.maxH),a=Math.ceil(e.scanLines/8/e.maxV);for(const r of e.components){const i=Math.ceil(Math.ceil(e.samplesPerLine/8)*r.h/e.maxH),n=Math.ceil(Math.ceil(e.scanLines/8)*r.v/e.maxV),s=t*r.h,o=64*(a*r.v)*(s+1);r.blockData=new Int16Array(o);r.blocksPerLine=i;r.blocksPerColumn=n}e.mcusPerLine=t;e.mcusPerColumn=a}function readDataBlock(e,t){const a=readUint16(e,t);let r=(t+=2)+a-2;const i=findNextFileMarker(e,r,t);if(i?.invalid){warn("readDataBlock - incorrect length, current marker is: "+i.invalid);r=i.offset}const n=e.subarray(t,r);return{appData:n,oldOffset:t,newOffset:t+n.length}}function skipData(e,t){const a=readUint16(e,t),r=(t+=2)+a-2,i=findNextFileMarker(e,r,t);return i?.invalid?i.offset:r}class JpegImage{constructor({decodeTransform:e=null,colorTransform:t=-1}={}){this._decodeTransform=e;this._colorTransform=t}static canUseImageDecoder(e,t=-1){let a=null,r=0,i=null,n=readUint16(e,r);r+=2;if(65496!==n)throw new JpegError("SOI not found");n=readUint16(e,r);r+=2;e:for(;65497!==n;){switch(n){case 65505:const{appData:t,oldOffset:s,newOffset:o}=readDataBlock(e,r);r=o;if(69===t[0]&&120===t[1]&&105===t[2]&&102===t[3]&&0===t[4]&&0===t[5]){if(a)throw new JpegError("Duplicate EXIF-blocks found.");a={exifStart:s+6,exifEnd:o}}n=readUint16(e,r);r+=2;continue;case 65472:case 65473:case 65474:i=e[r+7];break e;case 65535:255!==e[r]&&r--}r=skipData(e,r);n=readUint16(e,r);r+=2}return 4===i||3===i&&0===t?null:a||{}}parse(e,{dnlScanLines:t=null}={}){let a,r,i=0,n=null,s=null,o=0;const c=[],l=[],h=[];let u=readUint16(e,i);i+=2;if(65496!==u)throw new JpegError("SOI not found");u=readUint16(e,i);i+=2;e:for(;65497!==u;){let d,f,g;switch(u){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514:case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:const{appData:p,newOffset:m}=readDataBlock(e,i);i=m;65504===u&&74===p[0]&&70===p[1]&&73===p[2]&&70===p[3]&&0===p[4]&&(n={version:{major:p[5],minor:p[6]},densityUnits:p[7],xDensity:p[8]<<8|p[9],yDensity:p[10]<<8|p[11],thumbWidth:p[12],thumbHeight:p[13],thumbData:p.subarray(14,14+3*p[12]*p[13])});65518===u&&65===p[0]&&100===p[1]&&111===p[2]&&98===p[3]&&101===p[4]&&(s={version:p[5]<<8|p[6],flags0:p[7]<<8|p[8],flags1:p[9]<<8|p[10],transformCode:p[11]});break;case 65499:const b=readUint16(e,i);i+=2;const y=b+i-2;let w;for(;i>4){if(t>>4!=1)throw new JpegError("DQT - invalid table spec");for(f=0;f<64;f++){w=ja[f];a[w]=readUint16(e,i);i+=2}}else for(f=0;f<64;f++){w=ja[f];a[w]=e[i++]}c[15&t]=a}break;case 65472:case 65473:case 65474:if(a)throw new JpegError("Only single frame JPEGs supported");i+=2;a={};a.extended=65473===u;a.progressive=65474===u;a.precision=e[i++];const S=readUint16(e,i);i+=2;a.scanLines=t||S;a.samplesPerLine=readUint16(e,i);i+=2;a.components=[];a.componentIds={};const x=e[i++];let k=0,C=0;for(d=0;d>4,n=15&e[i+1];k>4?l:h)[15&t]=buildHuffmanTable(a,n)}break;case 65501:i+=2;r=readUint16(e,i);i+=2;break;case 65498:const F=1==++o&&!t;i+=2;const O=e[i++],T=[];for(d=0;d>4];n.huffmanTableAC=l[15&s];T.push(n)}const M=e[i++],D=e[i++],R=e[i++];try{i+=decodeScan(e,i,a,T,r,M,D,R>>4,15&R,F)}catch(t){if(t instanceof DNLMarkerError){warn(`${t.message} -- attempting to re-parse the JPEG image.`);return this.parse(e,{dnlScanLines:t.scanLines})}if(t instanceof EOIMarkerError){warn(`${t.message} -- ignoring the rest of the image data.`);break e}throw t}break;case 65500:i+=4;break;case 65535:255!==e[i]&&i--;break;default:const N=findNextFileMarker(e,i-2,i-3);if(N?.invalid){warn("JpegImage.parse - unexpected data, current marker is: "+N.invalid);i=N.offset;break}if(!N||i>=e.length-1){warn("JpegImage.parse - reached the end of the image data without finding an EOI marker (0xFFD9).");break e}throw new JpegError("JpegImage.parse - unknown marker: "+u.toString(16))}u=readUint16(e,i);i+=2}if(!a)throw new JpegError("JpegImage.parse - no frame data found.");this.width=a.samplesPerLine;this.height=a.scanLines;this.jfif=n;this.adobe=s;this.components=[];for(const e of a.components){const t=c[e.quantizationId];t&&(e.quantizationTable=t);this.components.push({index:e.index,output:buildComponentData(0,e),scaleX:e.h/a.maxH,scaleY:e.v/a.maxV,blocksPerLine:e.blocksPerLine,blocksPerColumn:e.blocksPerColumn})}this.numComponents=this.components.length}_getLinearizedBlockData(e,t,a=!1){const r=this.width/e,i=this.height/t;let n,s,o,c,l,h,u,d,f,g,p,m=0;const b=this.components.length,y=e*t*b,w=new Uint8ClampedArray(y),S=new Uint32Array(e),x=4294967288;let k;for(u=0;u>8)+C[f+1];return w}get _isColorConversionNeeded(){return this.adobe?!!this.adobe.transformCode:3===this.numComponents?0!==this._colorTransform&&(82!==this.components[0].index||71!==this.components[1].index||66!==this.components[2].index):1===this._colorTransform}_convertYccToRgb(e){let t,a,r;for(let i=0,n=e.length;i4)throw new JpegError("Unsupported color mode");const n=this._getLinearizedBlockData(e,t,i);if(1===this.numComponents&&(a||r)){const e=n.length*(a?4:3),t=new Uint8ClampedArray(e);let r=0;if(a)!function grayToRGBA(e,t){if(FeatureTest.isLittleEndian)for(let a=0,r=e.length;a0&&(e=e.subarray(t));break}return e}decodeImage(e){if(this.eof)return this.buffer;e=this.#N(e||this.bytes);const t=new JpegImage(this.jpegOptions);t.parse(e);const a=t.getData({width:this.drawWidth,height:this.drawHeight,forceRGBA:this.forceRGBA,forceRGB:this.forceRGB,isSourcePDF:!0});this.buffer=a;this.bufferLength=a.length;this.eof=!0;return this.buffer}get canAsyncDecodeImageFromBuffer(){return this.stream.isAsync}async getTransferableImage(){if(!await JpegStream.canUseImageDecoder)return null;const e=this.jpegOptions;if(e.decodeTransform)return null;let t;try{const a=this.canAsyncDecodeImageFromBuffer&&await this.stream.asyncGetBytes()||this.bytes;if(!a)return null;let r=this.#N(a);const i=JpegImage.canUseImageDecoder(r,e.colorTransform);if(!i)return null;if(i.exifStart){r=r.slice();r.fill(0,i.exifStart,i.exifEnd)}t=new ImageDecoder({data:r,type:"image/jpeg",preferAnimation:!1});return(await t.decode()).image}catch(e){warn(`getTransferableImage - failed: "${e}".`);return null}finally{t?.close()}}}var OpenJPEG=async function(e={}){var t,a,r=e,i=new Promise(((e,r)=>{t=e;a=r})),n="./this.program",quit_=(e,t)=>{throw t},s=import.meta.url;try{new URL(".",s).href}catch{}var o,c,l,h,u,d,f=console.log.bind(console),g=console.error.bind(console),p=!1;function updateMemoryViews(){var e=o.buffer;l=new Int8Array(e);new Int16Array(e);h=new Uint8Array(e);new Uint16Array(e);u=new Int32Array(e);d=new Uint32Array(e);new Float32Array(e);new Float64Array(e);new BigInt64Array(e);new BigUint64Array(e)}var m=0,b=null;class ExitStatus{name="ExitStatus";constructor(e){this.message=`Program terminated with exit(${e})`;this.status=e}}var callRuntimeCallbacks=e=>{for(;e.length>0;)e.shift()(r)},y=[],addOnPostRun=e=>y.push(e),w=[],addOnPreRun=e=>w.push(e),S=!0,x=0,k={},handleException=e=>{if(e instanceof ExitStatus||"unwind"==e)return c;quit_(0,e)},keepRuntimeAlive=()=>S||x>0,_proc_exit=e=>{c=e;if(!keepRuntimeAlive()){r.onExit?.(e);p=!0}quit_(0,new ExitStatus(e))},_exit=(e,t)=>{c=e;_proc_exit(e)},callUserCallback=e=>{if(!p)try{e();(()=>{if(!keepRuntimeAlive())try{_exit(c)}catch(e){handleException(e)}})()}catch(e){handleException(e)}},growMemory=e=>{var t=(e-o.buffer.byteLength+65535)/65536|0;try{o.grow(t);updateMemoryViews();return 1}catch(e){}},C={},getEnvStrings=()=>{if(!getEnvStrings.strings){var e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:n||"./this.program"};for(var t in C)void 0===C[t]?delete e[t]:e[t]=C[t];var a=[];for(var t in e)a.push(`${t}=${e[t]}`);getEnvStrings.strings=a}return getEnvStrings.strings},lengthBytesUTF8=e=>{for(var t=0,a=0;a=55296&&r<=57343){t+=4;++a}else t+=3}return t},v=[null,[],[]],F="undefined"!=typeof TextDecoder?new TextDecoder:void 0,UTF8ArrayToString=(e,t=0,a=NaN)=>{for(var r=t+a,i=t;e[i]&&!(i>=r);)++i;if(i-t>16&&e.buffer&&F)return F.decode(e.subarray(t,i));for(var n="";t>10,56320|1023&l)}}else n+=String.fromCharCode((31&s)<<6|o)}else n+=String.fromCharCode(s)}return n},printChar=(e,t)=>{var a=v[e];if(0===t||10===t){(1===e?f:g)(UTF8ArrayToString(a));a.length=0}else a.push(t)},UTF8ToString=(e,t)=>e?UTF8ArrayToString(h,e,t):"";r.noExitRuntime&&(S=r.noExitRuntime);r.print&&(f=r.print);r.printErr&&(g=r.printErr);r.wasmBinary&&r.wasmBinary;r.arguments&&r.arguments;r.thisProgram&&(n=r.thisProgram);r.writeArrayToMemory=(e,t)=>{l.set(e,t)};var O={l:()=>function abort(e){r.onAbort?.(e);g(e="Aborted("+e+")");p=!0;e+=". Build with -sASSERTIONS for more info.";var t=new WebAssembly.RuntimeError(e);a(t);throw t}(""),k:()=>{S=!1;x=0},m:(e,t)=>{if(k[e]){clearTimeout(k[e].id);delete k[e]}if(!t)return 0;var a=setTimeout((()=>{delete k[e];callUserCallback((()=>M(e,performance.now())))}),t);k[e]={id:a,timeout_ms:t};return 0},g:function _copy_pixels_1(e,t){e>>=2;const a=r.imageData=new Uint8ClampedArray(t),i=u.subarray(e,e+t);a.set(i)},f:function _copy_pixels_3(e,t,a,i){e>>=2;t>>=2;a>>=2;const n=r.imageData=new Uint8ClampedArray(3*i),s=u.subarray(e,e+i),o=u.subarray(t,t+i),c=u.subarray(a,a+i);for(let e=0;e>=2;t>>=2;a>>=2;i>>=2;const s=r.imageData=new Uint8ClampedArray(4*n),o=u.subarray(e,e+n),c=u.subarray(t,t+n),l=u.subarray(a,a+n),h=u.subarray(i,i+n);for(let e=0;e{var t,a,r=h.length,i=2147483648;if((e>>>=0)>i)return!1;for(var n=1;n<=4;n*=2){var s=r*(1+.2/n);s=Math.min(s,e+100663296);var o=Math.min(i,(t=Math.max(e,s),a=65536,Math.ceil(t/a)*a));if(growMemory(o))return!0}return!1},p:(e,t)=>{var a=0,r=0;for(var i of getEnvStrings()){var n=t+a;d[e+r>>2]=n;a+=((e,t,a,r)=>{if(!(r>0))return 0;for(var i=a,n=a+r-1,s=0;s=55296&&o<=57343&&(o=65536+((1023&o)<<10)|1023&e.charCodeAt(++s));if(o<=127){if(a>=n)break;t[a++]=o}else if(o<=2047){if(a+1>=n)break;t[a++]=192|o>>6;t[a++]=128|63&o}else if(o<=65535){if(a+2>=n)break;t[a++]=224|o>>12;t[a++]=128|o>>6&63;t[a++]=128|63&o}else{if(a+3>=n)break;t[a++]=240|o>>18;t[a++]=128|o>>12&63;t[a++]=128|o>>6&63;t[a++]=128|63&o}}t[a]=0;return a-i})(i,h,n,1/0)+1;r+=4}return 0},q:(e,t)=>{var a=getEnvStrings();d[e>>2]=a.length;var r=0;for(var i of a)r+=lengthBytesUTF8(i)+1;d[t>>2]=r;return 0},b:e=>52,o:function _fd_seek(e,t,a,r){t=(i=t)<-9007199254740992||i>9007199254740992?NaN:Number(i);var i;return 70},c:(e,t,a,r)=>{for(var i=0,n=0;n>2],o=d[t+4>>2];t+=8;for(var c=0;c>2]=i;return 0},r:function _gray_to_rgba(e,t){e>>=2;const a=r.imageData=new Uint8ClampedArray(4*t),i=u.subarray(e,e+t);for(let e=0;e>=2;t>>=2;const i=r.imageData=new Uint8ClampedArray(4*a),n=u.subarray(e,e+a),s=u.subarray(t,t+a);for(let e=0;e>=2;t>>=2;a>>=2;const n=r.imageData=new Uint8ClampedArray(4*i),s=u.subarray(e,e+i),o=u.subarray(t,t+i),c=u.subarray(a,a+i);for(let e=0;e{r.instantiateWasm(e,((e,a)=>{t(receiveInstance(e))}))}))}(),M=(T.t,r._malloc=T.u,r._free=T.v,r._jp2_decode=T.w,T.x);!function preInit(){if(r.preInit){"function"==typeof r.preInit&&(r.preInit=[r.preInit]);for(;r.preInit.length>0;)r.preInit.shift()()}}();!function run(){if(m>0)b=run;else{!function preRun(){if(r.preRun){"function"==typeof r.preRun&&(r.preRun=[r.preRun]);for(;r.preRun.length;)addOnPreRun(r.preRun.shift())}callRuntimeCallbacks(w)}();if(m>0)b=run;else if(r.setStatus){r.setStatus("Running...");setTimeout((()=>{setTimeout((()=>r.setStatus("")),1);doRun()}),1)}else doRun()}function doRun(){r.calledRun=!0;if(!p){!function initRuntime(){T.t()}();t(r);r.onRuntimeInitialized?.();!function postRun(){if(r.postRun){"function"==typeof r.postRun&&(r.postRun=[r.postRun]);for(;r.postRun.length;)addOnPostRun(r.postRun.shift())}callRuntimeCallbacks(y)}()}}}();return i};const Ga=OpenJPEG;class JpxError extends fa{constructor(e){super(e,"JpxError")}}class JpxImage{static#E=null;static#P=null;static#L=null;static#v=!0;static#j=!0;static#F=null;static setOptions({handler:e,useWasm:t,useWorkerFetch:a,wasmUrl:r}){this.#v=t;this.#j=a;this.#F=r;a||(this.#P=e)}static async#_(e){const t=`${this.#F}openjpeg_nowasm_fallback.js`;let a=null;try{a=(await import( +/*webpackIgnore: true*/ +/*@vite-ignore*/ +t)).default()}catch(e){warn(`JpxImage#getJsModule: ${e}`)}e(a)}static async#U(e,t,a){const r="openjpeg.wasm";try{this.#E||(this.#j?this.#E=await fetchBinaryData(`${this.#F}${r}`):this.#E=await this.#P.sendWithPromise("FetchBinaryData",{type:"wasmFactory",filename:r}));return a((await WebAssembly.instantiate(this.#E,t)).instance)}catch(t){warn(`JpxImage#instantiateWasm: ${t}`);this.#_(e);return null}finally{this.#P=null}}static async decode(e,{numComponents:t=4,isIndexedColormap:a=!1,smaskInData:r=!1,reducePower:i=0}={}){if(!this.#L){const{promise:e,resolve:t}=Promise.withResolvers(),a=[e];this.#v?a.push(Ga({warn,instantiateWasm:this.#U.bind(this,t)})):this.#_(t);this.#L=Promise.race(a)}const n=await this.#L;if(!n)throw new JpxError("OpenJPEG failed to initialize");let s;try{const o=e.length;s=n._malloc(o);n.writeArrayToMemory(e,s);if(n._jp2_decode(s,o,t>0?t:0,!!a,!!r,i)){const{errorMessages:e}=n;if(e){delete n.errorMessages;throw new JpxError(e)}throw new JpxError("Unknown error")}const{imageData:c}=n;n.imageData=null;return c}finally{s&&n._free(s)}}static cleanup(){this.#L=null}static parseImageProperties(e){let t=e.getByte();for(;t>=0;){const a=t;t=e.getByte();if(65361===(a<<8|t)){e.skip(4);const t=e.getInt32()>>>0,a=e.getInt32()>>>0,r=e.getInt32()>>>0,i=e.getInt32()>>>0;e.skip(16);return{width:t-r,height:a-i,bitsPerComponent:8,componentsCount:e.getUint16()}}}throw new JpxError("No size marker found in JPX stream")}}function addState(e,t,a,r,i){let n=e;for(let e=0,a=t.length-1;e1e3){l=Math.max(l,d);f+=u+2;d=0;u=0}h.push({transform:t,x:d,y:f,w:a.width,h:a.height});d+=a.width+2;u=Math.max(u,a.height)}const g=Math.max(l,d)+1,p=f+u+1,m=new Uint8Array(g*p*4),b=g<<2;for(let e=0;e=0;){t[n-4]=t[n];t[n-3]=t[n+1];t[n-2]=t[n+2];t[n-1]=t[n+3];t[n+a]=t[n+a-4];t[n+a+1]=t[n+a-3];t[n+a+2]=t[n+a-2];t[n+a+3]=t[n+a-1];n-=b}}const y={width:g,height:p};if(e.isOffscreenCanvasSupported){const e=new OffscreenCanvas(g,p);e.getContext("2d").putImageData(new ImageData(new Uint8ClampedArray(m.buffer),g,p),0,0);y.bitmap=e.transferToImageBitmap();y.data=null}else{y.kind=v;y.data=m}a.splice(n,4*c,Zt);r.splice(n,4*c,[y,h]);return n+1}));addState(Va,[Be,Ne,Vt,Re],null,(function iterateImageMaskGroup(e,t){const a=e.fnArray,r=(t-(e.iCurr-3))%4;switch(r){case 0:return a[t]===Be;case 1:return a[t]===Ne;case 2:return a[t]===Vt;case 3:return a[t]===Re}throw new Error(`iterateImageMaskGroup - invalid pos: ${r}`)}),(function foundImageMaskGroup(e,t){const a=e.fnArray,r=e.argsArray,i=e.iCurr,n=i-3,s=i-2,o=i-1;let c=Math.floor((t-n)/4);if(c<10)return t-(t-n)%4;let l,h,u=!1;const d=r[o][0],f=r[s][0],g=r[s][1],p=r[s][2],m=r[s][3];if(g===p){u=!0;l=s+4;let e=o+4;for(let t=1;t=4&&a[n-4]===a[s]&&a[n-3]===a[o]&&a[n-2]===a[c]&&a[n-1]===a[l]&&r[n-4][0]===h&&r[n-4][1]===u){d++;f-=5}let g=f+4;for(let e=1;e{const t=e.argsArray,a=t[e.iCurr-1][0];if(a!==qe&&a!==He&&a!==$e&&a!==Ge&&a!==Ve&&a!==Ke)return!0;const r=t[e.iCurr-2];return 1===r[0]&&0===r[1]&&0===r[2]&&1===r[3]}),(()=>!1),((e,t)=>{const{fnArray:a,argsArray:r}=e,i=e.iCurr,n=i-3,s=i-2,o=r[i-1],c=r[s],[,[l],h]=o;if(h){Util.scaleMinMax(c,h);for(let e=0,t=l.length;e=a)break}r=(r||Va)[e[t]];if(r&&!Array.isArray(r)){n.iCurr=t;t++;if(!r.checkFn||(0,r.checkFn)(n)){i=r;r=null}else r=null}else t++}this.state=r;this.match=i;this.lastProcessed=t}flush(){for(;this.match;){const e=this.queue.fnArray.length;this.lastProcessed=(0,this.match.processFn)(this.context,e);this.match=null;this.state=null;this._optimize()}}reset(){this.state=null;this.match=null;this.lastProcessed=0}}class OperatorList{static CHUNK_SIZE=1e3;static CHUNK_SIZE_ABOUT=this.CHUNK_SIZE-5;static isOffscreenCanvasSupported=!1;constructor(e=0,t){this._streamSink=t;this.fnArray=[];this.argsArray=[];this.optimizer=!t||e&d?new NullOptimizer(this):new QueueOptimizer(this);this.dependencies=new Set;this._totalLength=0;this.weight=0;this._resolved=t?null:Promise.resolve()}static setOptions({isOffscreenCanvasSupported:e}){this.isOffscreenCanvasSupported=e}get length(){return this.argsArray.length}get ready(){return this._resolved||this._streamSink.ready}get totalLength(){return this._totalLength+this.length}addOp(e,t){this.optimizer.push(e,t);this.weight++;this._streamSink&&(this.weight>=OperatorList.CHUNK_SIZE||this.weight>=OperatorList.CHUNK_SIZE_ABOUT&&(e===Re||e===et))&&this.flush()}addImageOps(e,t,a,r=!1){if(r){this.addOp(Be);this.addOp(De,[[["SMask",!1]]])}void 0!==a&&this.addOp(jt,["OC",a]);this.addOp(e,t);void 0!==a&&this.addOp(_t,[]);r&&this.addOp(Re)}addDependency(e){if(!this.dependencies.has(e)){this.dependencies.add(e);this.addOp(ke,[e])}}addDependencies(e){for(const t of e)this.addDependency(t)}addOpList(e){if(e instanceof OperatorList){for(const t of e.dependencies)this.dependencies.add(t);for(let t=0,a=e.length;t>>0}function hexToStr(e,t){return 1===t?String.fromCharCode(e[0],e[1]):3===t?String.fromCharCode(e[0],e[1],e[2],e[3]):String.fromCharCode(...e.subarray(0,t+1))}function addHex(e,t,a){let r=0;for(let i=a;i>=0;i--){r+=e[i]+t[i];e[i]=255&r;r>>=8}}function incHex(e,t){let a=1;for(let r=t;r>=0&&a>0;r--){a+=e[r];e[r]=255&a;a>>=8}}const Ka=16;class BinaryCMapStream{constructor(e){this.buffer=e;this.pos=0;this.end=e.length;this.tmpBuf=new Uint8Array(19)}readByte(){return this.pos>=this.end?-1:this.buffer[this.pos++]}readNumber(){let e,t=0;do{const a=this.readByte();if(a<0)throw new FormatError("unexpected EOF in bcmap");e=!(128&a);t=t<<7|127&a}while(!e);return t}readSigned(){const e=this.readNumber();return 1&e?~(e>>>1):e>>>1}readHex(e,t){e.set(this.buffer.subarray(this.pos,this.pos+t+1));this.pos+=t+1}readHexNumber(e,t){let a;const r=this.tmpBuf;let i=0;do{const e=this.readByte();if(e<0)throw new FormatError("unexpected EOF in bcmap");a=!(128&e);r[i++]=127&e}while(!a);let n=t,s=0,o=0;for(;n>=0;){for(;o<8&&r.length>0;){s|=r[--i]<>=8;o-=8}}readHexSigned(e,t){this.readHexNumber(e,t);const a=1&e[t]?255:0;let r=0;for(let i=0;i<=t;i++){r=(1&r)<<8|e[i];e[i]=r>>1^a}}readString(){const e=this.readNumber(),t=new Array(e);for(let a=0;a=0;){const e=d>>5;if(7===e){switch(31&d){case 0:r.readString();break;case 1:n=r.readString()}continue}const a=!!(16&d),i=15&d;if(i+1>Ka)throw new Error("BinaryCMapReader.process: Invalid dataSize.");const f=1,g=r.readNumber();switch(e){case 0:r.readHex(s,i);r.readHexNumber(o,i);addHex(o,s,i);t.addCodespaceRange(i+1,hexToInt(s,i),hexToInt(o,i));for(let e=1;e=0;--i){r[a+i]=255&s;s>>=8}}}}class AsciiHexStream extends DecodeStream{constructor(e,t){t&&(t*=.5);super(t);this.str=e;this.dict=e.dict;this.firstDigit=-1}readBlock(){const e=this.str.getBytes(8e3);if(!e.length){this.eof=!0;return}const t=e.length+1>>1,a=this.ensureBuffer(this.bufferLength+t);let r=this.bufferLength,i=this.firstDigit;for(const t of e){let e;if(t>=48&&t<=57)e=15&t;else{if(!(t>=65&&t<=70||t>=97&&t<=102)){if(62===t){this.eof=!0;break}continue}e=9+(15&t)}if(i<0)i=e;else{a[r++]=i<<4|e;i=-1}}if(i>=0&&this.eof){a[r++]=i<<4;i=-1}this.firstDigit=i;this.bufferLength=r}}const Ja=-1,Ya=[[-1,-1],[-1,-1],[7,8],[7,7],[6,6],[6,6],[6,5],[6,5],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2]],Za=[[-1,-1],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[12,1984],[12,2048],[12,2112],[12,2176],[12,2240],[12,2304],[11,1856],[11,1856],[11,1920],[11,1920],[12,2368],[12,2432],[12,2496],[12,2560]],Qa=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[8,29],[8,29],[8,30],[8,30],[8,45],[8,45],[8,46],[8,46],[7,22],[7,22],[7,22],[7,22],[7,23],[7,23],[7,23],[7,23],[8,47],[8,47],[8,48],[8,48],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[7,20],[7,20],[7,20],[7,20],[8,33],[8,33],[8,34],[8,34],[8,35],[8,35],[8,36],[8,36],[8,37],[8,37],[8,38],[8,38],[7,19],[7,19],[7,19],[7,19],[8,31],[8,31],[8,32],[8,32],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[8,53],[8,53],[8,54],[8,54],[7,26],[7,26],[7,26],[7,26],[8,39],[8,39],[8,40],[8,40],[8,41],[8,41],[8,42],[8,42],[8,43],[8,43],[8,44],[8,44],[7,21],[7,21],[7,21],[7,21],[7,28],[7,28],[7,28],[7,28],[8,61],[8,61],[8,62],[8,62],[8,63],[8,63],[8,0],[8,0],[8,320],[8,320],[8,384],[8,384],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[7,27],[7,27],[7,27],[7,27],[8,59],[8,59],[8,60],[8,60],[9,1472],[9,1536],[9,1600],[9,1728],[7,18],[7,18],[7,18],[7,18],[7,24],[7,24],[7,24],[7,24],[8,49],[8,49],[8,50],[8,50],[8,51],[8,51],[8,52],[8,52],[7,25],[7,25],[7,25],[7,25],[8,55],[8,55],[8,56],[8,56],[8,57],[8,57],[8,58],[8,58],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[8,448],[8,448],[8,512],[8,512],[9,704],[9,768],[8,640],[8,640],[8,576],[8,576],[9,832],[9,896],[9,960],[9,1024],[9,1088],[9,1152],[9,1216],[9,1280],[9,1344],[9,1408],[7,256],[7,256],[7,256],[7,256],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7]],er=[[-1,-1],[-1,-1],[12,-2],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[11,1792],[11,1792],[12,1984],[12,1984],[12,2048],[12,2048],[12,2112],[12,2112],[12,2176],[12,2176],[12,2240],[12,2240],[12,2304],[12,2304],[11,1856],[11,1856],[11,1856],[11,1856],[11,1920],[11,1920],[11,1920],[11,1920],[12,2368],[12,2368],[12,2432],[12,2432],[12,2496],[12,2496],[12,2560],[12,2560],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[12,52],[12,52],[13,640],[13,704],[13,768],[13,832],[12,55],[12,55],[12,56],[12,56],[13,1280],[13,1344],[13,1408],[13,1472],[12,59],[12,59],[12,60],[12,60],[13,1536],[13,1600],[11,24],[11,24],[11,24],[11,24],[11,25],[11,25],[11,25],[11,25],[13,1664],[13,1728],[12,320],[12,320],[12,384],[12,384],[12,448],[12,448],[13,512],[13,576],[12,53],[12,53],[12,54],[12,54],[13,896],[13,960],[13,1024],[13,1088],[13,1152],[13,1216],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64]],tr=[[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[11,23],[11,23],[12,50],[12,51],[12,44],[12,45],[12,46],[12,47],[12,57],[12,58],[12,61],[12,256],[10,16],[10,16],[10,16],[10,16],[10,17],[10,17],[10,17],[10,17],[12,48],[12,49],[12,62],[12,63],[12,30],[12,31],[12,32],[12,33],[12,40],[12,41],[11,22],[11,22],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[12,128],[12,192],[12,26],[12,27],[12,28],[12,29],[11,19],[11,19],[11,20],[11,20],[12,34],[12,35],[12,36],[12,37],[12,38],[12,39],[11,21],[11,21],[12,42],[12,43],[10,0],[10,0],[10,0],[10,0],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12]],ar=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[6,9],[6,8],[5,7],[5,7],[4,6],[4,6],[4,6],[4,6],[4,5],[4,5],[4,5],[4,5],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2]];class CCITTFaxDecoder{constructor(e,t={}){if("function"!=typeof e?.next)throw new Error('CCITTFaxDecoder - invalid "source" parameter.');this.source=e;this.eof=!1;this.encoding=t.K||0;this.eoline=t.EndOfLine||!1;this.byteAlign=t.EncodedByteAlign||!1;this.columns=t.Columns||1728;this.rows=t.Rows||0;this.eoblock=t.EndOfBlock??!0;this.black=t.BlackIs1||!1;this.codingLine=new Uint32Array(this.columns+1);this.refLine=new Uint32Array(this.columns+2);this.codingLine[0]=this.columns;this.codingPos=0;this.row=0;this.nextLine2D=this.encoding<0;this.inputBits=0;this.inputBuf=0;this.outputBits=0;this.rowsDone=!1;let a;for(;0===(a=this._lookBits(12));)this._eatBits(1);1===a&&this._eatBits(12);if(this.encoding>0){this.nextLine2D=!this._lookBits(1);this._eatBits(1)}}readNextChar(){if(this.eof)return-1;const e=this.refLine,t=this.codingLine,a=this.columns;let r,i,n,s,o;if(0===this.outputBits){this.rowsDone&&(this.eof=!0);if(this.eof)return-1;this.err=!1;let n,o,c;if(this.nextLine2D){for(s=0;t[s]=64);do{o+=c=this._getWhiteCode()}while(c>=64)}else{do{n+=c=this._getWhiteCode()}while(c>=64);do{o+=c=this._getBlackCode()}while(c>=64)}this._addPixels(t[this.codingPos]+n,i);t[this.codingPos]0?--r:++r;for(;e[r]<=t[this.codingPos]&&e[r]0?--r:++r;for(;e[r]<=t[this.codingPos]&&e[r]0?--r:++r;for(;e[r]<=t[this.codingPos]&&e[r]=64);else do{n+=c=this._getWhiteCode()}while(c>=64);this._addPixels(t[this.codingPos]+n,i);i^=1}}let l=!1;this.byteAlign&&(this.inputBits&=-8);if(this.eoblock||this.row!==this.rows-1){n=this._lookBits(12);if(this.eoline)for(;n!==Ja&&1!==n;){this._eatBits(1);n=this._lookBits(12)}else for(;0===n;){this._eatBits(1);n=this._lookBits(12)}if(1===n){this._eatBits(12);l=!0}else n===Ja&&(this.eof=!0)}else this.rowsDone=!0;if(!this.eof&&this.encoding>0&&!this.rowsDone){this.nextLine2D=!this._lookBits(1);this._eatBits(1)}if(this.eoblock&&l&&this.byteAlign){n=this._lookBits(12);if(1===n){this._eatBits(12);if(this.encoding>0){this._lookBits(1);this._eatBits(1)}if(this.encoding>=0)for(s=0;s<4;++s){n=this._lookBits(12);1!==n&&info("bad rtc code: "+n);this._eatBits(12);if(this.encoding>0){this._lookBits(1);this._eatBits(1)}}this.eof=!0}}else if(this.err&&this.eoline){for(;;){n=this._lookBits(13);if(n===Ja){this.eof=!0;return-1}if(n>>1==1)break;this._eatBits(1)}this._eatBits(12);if(this.encoding>0){this._eatBits(1);this.nextLine2D=!(1&n)}}this.outputBits=t[0]>0?t[this.codingPos=0]:t[this.codingPos=1];this.row++}if(this.outputBits>=8){o=1&this.codingPos?0:255;this.outputBits-=8;if(0===this.outputBits&&t[this.codingPos]n){o<<=n;1&this.codingPos||(o|=255>>8-n);this.outputBits-=n;n=0}else{o<<=this.outputBits;1&this.codingPos||(o|=255>>8-this.outputBits);n-=this.outputBits;this.outputBits=0;if(t[this.codingPos]0){o<<=n;n=0}}}while(n)}this.black&&(o^=255);return o}_addPixels(e,t){const a=this.codingLine;let r=this.codingPos;if(e>a[r]){if(e>this.columns){info("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}this.codingPos=r}_addPixelsNeg(e,t){const a=this.codingLine;let r=this.codingPos;if(e>a[r]){if(e>this.columns){info("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}else if(e0&&e=i){const t=a[e-i];if(t[0]===r){this._eatBits(r);return[!0,t[1],!0]}}}return[!1,0,!1]}_getTwoDimCode(){let e,t=0;if(this.eoblock){t=this._lookBits(7);e=Ya[t];if(e?.[0]>0){this._eatBits(e[0]);return e[1]}}else{const e=this._findTableCode(1,7,Ya);if(e[0]&&e[2])return e[1]}info("Bad two dim code");return Ja}_getWhiteCode(){let e,t=0;if(this.eoblock){t=this._lookBits(12);if(t===Ja)return 1;e=t>>5?Qa[t>>3]:Za[t];if(e[0]>0){this._eatBits(e[0]);return e[1]}}else{let e=this._findTableCode(1,9,Qa);if(e[0])return e[1];e=this._findTableCode(11,12,Za);if(e[0])return e[1]}info("bad white code");this._eatBits(1);return 1}_getBlackCode(){let e,t;if(this.eoblock){e=this._lookBits(13);if(e===Ja)return 1;t=e>>7?!(e>>9)&&e>>7?tr[(e>>1)-64]:ar[e>>7]:er[e];if(t[0]>0){this._eatBits(t[0]);return t[1]}}else{let e=this._findTableCode(2,6,ar);if(e[0])return e[1];e=this._findTableCode(7,12,tr,64);if(e[0])return e[1];e=this._findTableCode(10,13,er);if(e[0])return e[1]}info("bad black code");this._eatBits(1);return 1}_lookBits(e){let t;for(;this.inputBits>16-e;this.inputBuf=this.inputBuf<<8|t;this.inputBits+=8}return this.inputBuf>>this.inputBits-e&65535>>16-e}_eatBits(e){(this.inputBits-=e)<0&&(this.inputBits=0)}}class CCITTFaxStream extends DecodeStream{constructor(e,t,a){super(t);this.str=e;this.dict=e.dict;a instanceof Dict||(a=Dict.empty);const r={next:()=>e.getByte()};this.ccittFaxDecoder=new CCITTFaxDecoder(r,{K:a.get("K"),EndOfLine:a.get("EndOfLine"),EncodedByteAlign:a.get("EncodedByteAlign"),Columns:a.get("Columns"),Rows:a.get("Rows"),EndOfBlock:a.get("EndOfBlock"),BlackIs1:a.get("BlackIs1")})}readBlock(){for(;!this.eof;){const e=this.ccittFaxDecoder.readNextChar();if(-1===e){this.eof=!0;return}this.ensureBuffer(this.bufferLength+1);this.buffer[this.bufferLength++]=e}}}const rr=new Int32Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),ir=new Int32Array([3,4,5,6,7,8,9,10,65547,65549,65551,65553,131091,131095,131099,131103,196643,196651,196659,196667,262211,262227,262243,262259,327811,327843,327875,327907,258,258,258]),nr=new Int32Array([1,2,3,4,65541,65543,131081,131085,196625,196633,262177,262193,327745,327777,393345,393409,459009,459137,524801,525057,590849,591361,657409,658433,724993,727041,794625,798721,868353,876545]),sr=[new Int32Array([459008,524368,524304,524568,459024,524400,524336,590016,459016,524384,524320,589984,524288,524416,524352,590048,459012,524376,524312,589968,459028,524408,524344,590032,459020,524392,524328,59e4,524296,524424,524360,590064,459010,524372,524308,524572,459026,524404,524340,590024,459018,524388,524324,589992,524292,524420,524356,590056,459014,524380,524316,589976,459030,524412,524348,590040,459022,524396,524332,590008,524300,524428,524364,590072,459009,524370,524306,524570,459025,524402,524338,590020,459017,524386,524322,589988,524290,524418,524354,590052,459013,524378,524314,589972,459029,524410,524346,590036,459021,524394,524330,590004,524298,524426,524362,590068,459011,524374,524310,524574,459027,524406,524342,590028,459019,524390,524326,589996,524294,524422,524358,590060,459015,524382,524318,589980,459031,524414,524350,590044,459023,524398,524334,590012,524302,524430,524366,590076,459008,524369,524305,524569,459024,524401,524337,590018,459016,524385,524321,589986,524289,524417,524353,590050,459012,524377,524313,589970,459028,524409,524345,590034,459020,524393,524329,590002,524297,524425,524361,590066,459010,524373,524309,524573,459026,524405,524341,590026,459018,524389,524325,589994,524293,524421,524357,590058,459014,524381,524317,589978,459030,524413,524349,590042,459022,524397,524333,590010,524301,524429,524365,590074,459009,524371,524307,524571,459025,524403,524339,590022,459017,524387,524323,589990,524291,524419,524355,590054,459013,524379,524315,589974,459029,524411,524347,590038,459021,524395,524331,590006,524299,524427,524363,590070,459011,524375,524311,524575,459027,524407,524343,590030,459019,524391,524327,589998,524295,524423,524359,590062,459015,524383,524319,589982,459031,524415,524351,590046,459023,524399,524335,590014,524303,524431,524367,590078,459008,524368,524304,524568,459024,524400,524336,590017,459016,524384,524320,589985,524288,524416,524352,590049,459012,524376,524312,589969,459028,524408,524344,590033,459020,524392,524328,590001,524296,524424,524360,590065,459010,524372,524308,524572,459026,524404,524340,590025,459018,524388,524324,589993,524292,524420,524356,590057,459014,524380,524316,589977,459030,524412,524348,590041,459022,524396,524332,590009,524300,524428,524364,590073,459009,524370,524306,524570,459025,524402,524338,590021,459017,524386,524322,589989,524290,524418,524354,590053,459013,524378,524314,589973,459029,524410,524346,590037,459021,524394,524330,590005,524298,524426,524362,590069,459011,524374,524310,524574,459027,524406,524342,590029,459019,524390,524326,589997,524294,524422,524358,590061,459015,524382,524318,589981,459031,524414,524350,590045,459023,524398,524334,590013,524302,524430,524366,590077,459008,524369,524305,524569,459024,524401,524337,590019,459016,524385,524321,589987,524289,524417,524353,590051,459012,524377,524313,589971,459028,524409,524345,590035,459020,524393,524329,590003,524297,524425,524361,590067,459010,524373,524309,524573,459026,524405,524341,590027,459018,524389,524325,589995,524293,524421,524357,590059,459014,524381,524317,589979,459030,524413,524349,590043,459022,524397,524333,590011,524301,524429,524365,590075,459009,524371,524307,524571,459025,524403,524339,590023,459017,524387,524323,589991,524291,524419,524355,590055,459013,524379,524315,589975,459029,524411,524347,590039,459021,524395,524331,590007,524299,524427,524363,590071,459011,524375,524311,524575,459027,524407,524343,590031,459019,524391,524327,589999,524295,524423,524359,590063,459015,524383,524319,589983,459031,524415,524351,590047,459023,524399,524335,590015,524303,524431,524367,590079]),9],or=[new Int32Array([327680,327696,327688,327704,327684,327700,327692,327708,327682,327698,327690,327706,327686,327702,327694,0,327681,327697,327689,327705,327685,327701,327693,327709,327683,327699,327691,327707,327687,327703,327695,0]),5];class FlateStream extends DecodeStream{constructor(e,t){super(t);this.str=e;this.dict=e.dict;const a=e.getByte(),r=e.getByte();if(-1===a||-1===r)throw new FormatError(`Invalid header in flate stream: ${a}, ${r}`);if(8!=(15&a))throw new FormatError(`Unknown compression method in flate stream: ${a}, ${r}`);if(((a<<8)+r)%31!=0)throw new FormatError(`Bad FCHECK in flate stream: ${a}, ${r}`);if(32&r)throw new FormatError(`FDICT bit set in flate stream: ${a}, ${r}`);this.codeSize=0;this.codeBuf=0}async getImageData(e,t){const a=await this.asyncGetBytes();return a?a.length<=e?a:a.subarray(0,e):this.getBytes(e)}async asyncGetBytes(){this.str.reset();const e=this.str.getBytes();try{const{readable:t,writable:a}=new DecompressionStream("deflate"),r=a.getWriter();await r.ready;r.write(e).then((async()=>{await r.ready;await r.close()})).catch((()=>{}));const i=[];let n=0;for await(const e of t){i.push(e);n+=e.byteLength}const s=new Uint8Array(n);let o=0;for(const e of i){s.set(e,o);o+=e.byteLength}return s}catch{this.str=new Stream(e,2,e.length,this.str.dict);this.reset();return null}}get isAsync(){return!0}getBits(e){const t=this.str;let a,r=this.codeSize,i=this.codeBuf;for(;r>e;this.codeSize=r-=e;return a}getCode(e){const t=this.str,a=e[0],r=e[1];let i,n=this.codeSize,s=this.codeBuf;for(;n>16,l=65535&o;if(c<1||n>c;this.codeSize=n-c;return l}generateHuffmanTable(e){const t=e.length;let a,r=0;for(a=0;ar&&(r=e[a]);const i=1<>=1}for(a=e;a>=1;if(0===t){let t;if(-1===(t=r.getByte())){this.#X("Bad block header in flate stream");return}let a=t;if(-1===(t=r.getByte())){this.#X("Bad block header in flate stream");return}a|=t<<8;if(-1===(t=r.getByte())){this.#X("Bad block header in flate stream");return}let i=t;if(-1===(t=r.getByte())){this.#X("Bad block header in flate stream");return}i|=t<<8;if(i!==(65535&~a)&&(0!==a||0!==i))throw new FormatError("Bad uncompressed block length in flate stream");this.codeBuf=0;this.codeSize=0;const n=this.bufferLength,s=n+a;e=this.ensureBuffer(s);this.bufferLength=s;if(0===a)-1===r.peekByte()&&(this.eof=!0);else{const t=r.getBytes(a);e.set(t,n);t.length0;)h[o++]=f}i=this.generateHuffmanTable(h.subarray(0,e));n=this.generateHuffmanTable(h.subarray(e,l))}}e=this.buffer;let s=e?e.length:0,o=this.bufferLength;for(;;){let t=this.getCode(i);if(t<256){if(o+1>=s){e=this.ensureBuffer(o+1);s=e.length}e[o++]=t;continue}if(256===t){this.bufferLength=o;return}t-=257;t=ir[t];let r=t>>16;r>0&&(r=this.getBits(r));a=(65535&t)+r;t=this.getCode(n);t=nr[t];r=t>>16;r>0&&(r=this.getBits(r));const c=(65535&t)+r;if(o+a>=s){e=this.ensureBuffer(o+a);s=e.length}for(let t=0;t>9&127;this.clow=this.clow<<7&65535;this.ct-=7;this.a=32768}byteIn(){const e=this.data;let t=this.bp;if(255===e[t])if(e[t+1]>143){this.clow+=65280;this.ct=8}else{t++;this.clow+=e[t]<<9;this.ct=7;this.bp=t}else{t++;this.clow+=t65535){this.chigh+=this.clow>>16;this.clow&=65535}}readBit(e,t){let a=e[t]>>1,r=1&e[t];const i=cr[a],n=i.qe;let s,o=this.a-n;if(this.chigh>15&1;this.clow=this.clow<<1&65535;this.ct--}while(!(32768&o));this.a=o;e[t]=a<<1|r;return s}}class Jbig2Error extends fa{constructor(e){super(e,"Jbig2Error")}}class ContextCache{getContexts(e){return e in this?this[e]:this[e]=new Int8Array(65536)}}class DecodingContext{constructor(e,t,a){this.data=e;this.start=t;this.end=a}get decoder(){return shadow(this,"decoder",new ArithmeticDecoder(this.data,this.start,this.end))}get contextCache(){return shadow(this,"contextCache",new ContextCache)}}function decodeInteger(e,t,a){const r=e.getContexts(t);let i=1;function readBits(e){let t=0;for(let n=0;n>>0}const n=readBits(1),s=readBits(1)?readBits(1)?readBits(1)?readBits(1)?readBits(1)?readBits(32)+4436:readBits(12)+340:readBits(8)+84:readBits(6)+20:readBits(4)+4:readBits(2);let o;0===n?o=s:s>0&&(o=-s);return o>=-2147483648&&o<=va?o:null}function decodeIAID(e,t,a){const r=e.getContexts("IAID");let i=1;for(let e=0;ee.y-t.y||e.x-t.x));const h=l.length,u=new Int8Array(h),d=new Int8Array(h),f=[];let g,p,m=0,b=0,y=0,w=0;for(p=0;p=v&&E=F){q=q<<1&m;for(p=0;p=0&&j=0){_=D[L][j];_&&(q|=_<=e?l<<=1:l=l<<1|x[o][c]}for(f=0;f=w||c<0||c>=y?l<<=1:l=l<<1|r[o][c]}const g=k.readBit(C,l);t[s]=g}}return x}function decodeTextRegion(e,t,a,r,i,n,s,o,c,l,h,u,d,f,g,p,m,b,y){if(e&&t)throw new Jbig2Error("refinement with Huffman is not supported");const w=[];let S,x;for(S=0;S1&&(i=e?y.readBits(b):decodeInteger(C,"IAIT",k));const n=s*v+i,F=e?f.symbolIDTable.decode(y):decodeIAID(C,k,c),O=t&&(e?y.readBit():decodeInteger(C,"IARI",k));let T=o[F],M=T[0].length,D=T.length;if(O){const e=decodeInteger(C,"IARDW",k),t=decodeInteger(C,"IARDH",k);M+=e;D+=t;T=decodeRefinement(M,D,g,T,(e>>1)+decodeInteger(C,"IARDX",k),(t>>1)+decodeInteger(C,"IARDY",k),!1,p,m)}let R=0;l?1&u?R=D-1:r+=D-1:u>1?r+=M-1:R=M-1;const N=n-(1&u?0:D-1),E=r-(2&u?M-1:0);let L,j,_;if(l)for(L=0;L>5&7;const c=[31&s];let l=t+6;if(7===s){o=536870911&readUint32(e,l-1);l+=3;let t=o+7>>3;c[0]=e[l++];for(;--t>0;)c.push(e[l++])}else if(5===s||6===s)throw new Jbig2Error("invalid referred-to flags");a.retainBits=c;let h=4;a.number<=256?h=1:a.number<=65536&&(h=2);const u=[];let d,f;for(d=0;d>>24&255;n[3]=t.height>>16&255;n[4]=t.height>>8&255;n[5]=255&t.height;for(d=l,f=e.length;d>2&3;e.huffmanDWSelector=t>>4&3;e.bitmapSizeSelector=t>>6&1;e.aggregationInstancesSelector=t>>7&1;e.bitmapCodingContextUsed=!!(256&t);e.bitmapCodingContextRetained=!!(512&t);e.template=t>>10&3;e.refinementTemplate=t>>12&1;l+=2;if(!e.huffman){c=0===e.template?4:1;s=[];for(o=0;o>2&3;h.stripSize=1<>4&3;h.transposed=!!(64&u);h.combinationOperator=u>>7&3;h.defaultPixelValue=u>>9&1;h.dsOffset=u<<17>>27;h.refinementTemplate=u>>15&1;if(h.huffman){const e=readUint16(r,l);l+=2;h.huffmanFS=3&e;h.huffmanDS=e>>2&3;h.huffmanDT=e>>4&3;h.huffmanRefinementDW=e>>6&3;h.huffmanRefinementDH=e>>8&3;h.huffmanRefinementDX=e>>10&3;h.huffmanRefinementDY=e>>12&3;h.huffmanRefinementSizeSelector=!!(16384&e)}if(h.refinement&&!h.refinementTemplate){s=[];for(o=0;o<2;o++){s.push({x:readInt8(r,l),y:readInt8(r,l+1)});l+=2}h.refinementAt=s}h.numberOfSymbolInstances=readUint32(r,l);l+=4;n=[h,a.referredTo,r,l,i];break;case 16:const d={},f=r[l++];d.mmr=!!(1&f);d.template=f>>1&3;d.patternWidth=r[l++];d.patternHeight=r[l++];d.maxPatternIndex=readUint32(r,l);l+=4;n=[d,a.number,r,l,i];break;case 22:case 23:const g={};g.info=readRegionSegmentInformation(r,l);l+=gr;const p=r[l++];g.mmr=!!(1&p);g.template=p>>1&3;g.enableSkip=!!(8&p);g.combinationOperator=p>>4&7;g.defaultPixelValue=p>>7&1;g.gridWidth=readUint32(r,l);l+=4;g.gridHeight=readUint32(r,l);l+=4;g.gridOffsetX=4294967295&readUint32(r,l);l+=4;g.gridOffsetY=4294967295&readUint32(r,l);l+=4;g.gridVectorX=readUint16(r,l);l+=2;g.gridVectorY=readUint16(r,l);l+=2;n=[g,a.referredTo,r,l,i];break;case 38:case 39:const m={};m.info=readRegionSegmentInformation(r,l);l+=gr;const b=r[l++];m.mmr=!!(1&b);m.template=b>>1&3;m.prediction=!!(8&b);if(!m.mmr){c=0===m.template?4:1;s=[];for(o=0;o>2&1;y.combinationOperator=w>>3&3;y.requiresBuffer=!!(32&w);y.combinationOperatorOverride=!!(64&w);n=[y];break;case 49:case 50:case 51:case 62:break;case 53:n=[a.number,r,l,i];break;default:throw new Jbig2Error(`segment type ${a.typeName}(${a.type}) is not implemented`)}const h="on"+a.typeName;h in t&&t[h].apply(t,n)}function processSegments(e,t){for(let a=0,r=e.length;a>3,a=new Uint8ClampedArray(t*e.height);e.defaultPixelValue&&a.fill(255);this.buffer=a}drawBitmap(e,t){const a=this.currentPageInfo,r=e.width,i=e.height,n=a.width+7>>3,s=a.combinationOperatorOverride?e.combinationOperator:a.combinationOperator,o=this.buffer,c=128>>(7&e.x);let l,h,u,d,f=e.y*n+(e.x>>3);switch(s){case 0:for(l=0;l>=1;if(!u){u=128;d++}}f+=n}break;case 2:for(l=0;l>=1;if(!u){u=128;d++}}f+=n}break;default:throw new Jbig2Error(`operator ${s} is not supported`)}}onImmediateGenericRegion(e,t,a,r){const i=e.info,n=new DecodingContext(t,a,r),s=decodeBitmap(e.mmr,i.width,i.height,e.template,e.prediction,null,e.at,n);this.drawBitmap(i,s)}onImmediateLosslessGenericRegion(){this.onImmediateGenericRegion(...arguments)}onSymbolDictionary(e,t,a,r,i,n){let s,o;if(e.huffman){s=function getSymbolDictionaryHuffmanTables(e,t,a){let r,i,n,s,o=0;switch(e.huffmanDHSelector){case 0:case 1:r=getStandardTable(e.huffmanDHSelector+4);break;case 3:r=getCustomHuffmanTable(o,t,a);o++;break;default:throw new Jbig2Error("invalid Huffman DH selector")}switch(e.huffmanDWSelector){case 0:case 1:i=getStandardTable(e.huffmanDWSelector+2);break;case 3:i=getCustomHuffmanTable(o,t,a);o++;break;default:throw new Jbig2Error("invalid Huffman DW selector")}if(e.bitmapSizeSelector){n=getCustomHuffmanTable(o,t,a);o++}else n=getStandardTable(1);s=e.aggregationInstancesSelector?getCustomHuffmanTable(o,t,a):getStandardTable(1);return{tableDeltaHeight:r,tableDeltaWidth:i,tableBitmapSize:n,tableAggregateInstances:s}}(e,a,this.customTables);o=new Reader(r,i,n)}let c=this.symbols;c||(this.symbols=c={});const l=[];for(const e of a){const t=c[e];t&&l.push(...t)}const h=new DecodingContext(r,i,n);c[t]=function decodeSymbolDictionary(e,t,a,r,i,n,s,o,c,l,h,u){if(e&&t)throw new Jbig2Error("symbol refinement with Huffman is not supported");const d=[];let f=0,g=log2(a.length+r);const p=h.decoder,m=h.contextCache;let b,y;if(e){b=getStandardTable(1);y=[];g=Math.max(g,1)}for(;d.length1)w=decodeTextRegion(e,t,r,f,0,i,1,a.concat(d),g,0,0,1,0,n,c,l,h,0,u);else{const e=decodeIAID(m,p,g),t=decodeInteger(m,"IARDX",p),i=decodeInteger(m,"IARDY",p);w=decodeRefinement(r,f,c,e=32){let a,r,s;switch(t){case 32:if(0===e)throw new Jbig2Error("no previous value in symbol ID table");r=i.readBits(2)+3;a=n[e-1].prefixLength;break;case 33:r=i.readBits(3)+3;a=0;break;case 34:r=i.readBits(7)+11;a=0;break;default:throw new Jbig2Error("invalid code length in symbol ID table")}for(s=0;s=0;m--){T=e?decodeMMRBitmap(O,c,l,!0):decodeBitmap(!1,c,l,a,!1,null,v,g);F[m]=T}for(M=0;M=0;b--){R^=F[b][M][D];N|=R<>8;j=u+M*d-D*f>>8;if(L>=0&&L+x<=r&&j>=0&&j+k<=i)for(m=0;m=i)){U=p[t];_=E[m];for(b=0;b=0&&e>1&7),c=1+(r>>4&7),l=[];let h,u,d=i;do{h=s.readBits(o);u=s.readBits(c);l.push(new HuffmanLine([d,h,u,0]));d+=1<>t&1;if(t<=0)this.children[a]=new HuffmanTreeNode(e);else{let r=this.children[a];r||(this.children[a]=r=new HuffmanTreeNode(null));r.buildTree(e,t-1)}}decodeNode(e){if(this.isLeaf){if(this.isOOB)return null;const t=e.readBits(this.rangeLength);return this.rangeLow+(this.isLowerRange?-t:t)}const t=this.children[e.readBit()];if(!t)throw new Jbig2Error("invalid Huffman data");return t.decodeNode(e)}}class HuffmanTable{constructor(e,t){t||this.assignPrefixCodes(e);this.rootNode=new HuffmanTreeNode(null);for(let t=0,a=e.length;t0&&this.rootNode.buildTree(a,a.prefixLength-1)}}decode(e){return this.rootNode.decodeNode(e)}assignPrefixCodes(e){const t=e.length;let a=0;for(let r=0;r=this.end)throw new Jbig2Error("end of data while reading bit");this.currentByte=this.data[this.position++];this.shift=7}const e=this.currentByte>>this.shift&1;this.shift--;return e}readBits(e){let t,a=0;for(t=e-1;t>=0;t--)a|=this.readBit()<=this.end?-1:this.data[this.position++]}}function getCustomHuffmanTable(e,t,a){let r=0;for(let i=0,n=t.length;i>a&1;a--}}if(r&&!o){const e=5;for(let t=0;t>>t&(1<0;if(e<256){d[0]=e;f=1}else{if(!(e>=258)){if(256===e){h=9;s=258;f=0;continue}this.eof=!0;delete this.lzwState;break}if(e=0;t--){d[t]=o[a];a=l[a]}}else d[f++]=d[0]}if(i){l[s]=u;c[s]=c[u]+1;o[s]=d[0];s++;h=s+n&s+n-1?h:0|Math.min(Math.log(s+n)/.6931471805599453+1,12)}u=e;g+=f;if(r15))throw new FormatError(`Unsupported predictor: ${r}`);this.readBlock=2===r?this.readBlockTiff:this.readBlockPng;this.str=e;this.dict=e.dict;const i=this.colors=a.get("Colors")||1,n=this.bits=a.get("BPC","BitsPerComponent")||8,s=this.columns=a.get("Columns")||1;this.pixBytes=i*n+7>>3;this.rowBytes=s*i*n+7>>3;return this}readBlockTiff(){const e=this.rowBytes,t=this.bufferLength,a=this.ensureBuffer(t+e),r=this.bits,i=this.colors,n=this.str.getBytes(e);this.eof=!n.length;if(this.eof)return;let s,o=0,c=0,l=0,h=0,u=t;if(1===r&&1===i)for(s=0;s>1;e^=e>>2;e^=e>>4;o=(1&e)<<7;a[u++]=e}else if(8===r){for(s=0;s>8&255;a[u++]=255&e}}else{const e=new Uint8Array(i+1),u=(1<>l-r)&u;l-=r;c=c<=8){a[f++]=c>>h-8&255;h-=8}}h>0&&(a[f++]=(c<<8-h)+(o&(1<<8-h)-1))}this.bufferLength+=e}readBlockPng(){const e=this.rowBytes,t=this.pixBytes,a=this.str.getByte(),r=this.str.getBytes(e);this.eof=!r.length;if(this.eof)return;const i=this.bufferLength,n=this.ensureBuffer(i+e);let s=n.subarray(i-e,i);0===s.length&&(s=new Uint8Array(e));let o,c,l,h=i;switch(a){case 0:for(o=0;o>1)+r[o];for(;o>1)+r[o]&255;h++}break;case 4:for(o=0;o0){const e=this.str.getBytes(r);t.set(e,a);a+=r}}else{r=257-r;t=this.ensureBuffer(a+r+1);t.fill(e[1],a,a+r);a+=r}this.bufferLength=a}}class Parser{constructor({lexer:e,xref:t,allowStreams:a=!1,recoveryMode:r=!1}){this.lexer=e;this.xref=t;this.allowStreams=a;this.recoveryMode=r;this.imageCache=Object.create(null);this._imageId=0;this.refill()}refill(){this.buf1=this.lexer.getObj();this.buf2=this.lexer.getObj()}shift(){if(this.buf2 instanceof Cmd&&"ID"===this.buf2.cmd){this.buf1=this.buf2;this.buf2=null}else{this.buf1=this.buf2;this.buf2=this.lexer.getObj()}}tryShift(){try{this.shift();return!0}catch(e){if(e instanceof MissingDataException)throw e;return!1}}getObj(e=null){const t=this.buf1;this.shift();if(t instanceof Cmd)switch(t.cmd){case"BI":return this.makeInlineImage(e);case"[":const a=[];for(;!isCmd(this.buf1,"]")&&this.buf1!==wa;)a.push(this.getObj(e));if(this.buf1===wa){if(this.recoveryMode)return a;throw new ParserEOFException("End of file inside array.")}this.shift();return a;case"<<":const r=new Dict(this.xref);for(;!isCmd(this.buf1,">>")&&this.buf1!==wa;){if(!(this.buf1 instanceof Name)){info("Malformed dictionary: key must be a name object");this.shift();continue}const t=this.buf1.name;this.shift();if(this.buf1===wa)break;r.set(t,this.getObj(e))}if(this.buf1===wa){if(this.recoveryMode)return r;throw new ParserEOFException("End of file inside dictionary.")}if(isCmd(this.buf2,"stream"))return this.allowStreams?this.makeStream(r,e):r;this.shift();return r;default:return t}if(Number.isInteger(t)){if(Number.isInteger(this.buf1)&&isCmd(this.buf2,"R")){const e=Ref.get(t,this.buf1);this.shift();this.shift();return e}return t}return"string"==typeof t&&e?e.decryptString(t):t}findDefaultInlineStreamEnd(e){const{knownCommands:t}=this.lexer,a=e.pos;let r,i,n=0;for(;-1!==(r=e.getByte());)if(0===n)n=69===r?1:0;else if(1===n)n=73===r?2:0;else if(32===r||10===r||13===r){i=e.pos;const a=e.peekBytes(15),s=a.length;if(0===s)break;for(let e=0;e127))){n=0;break}}if(2!==n)continue;if(!t){warn("findDefaultInlineStreamEnd - `lexer.knownCommands` is undefined.");continue}const o=new Lexer(new Stream(e.peekBytes(75)),t);o._hexStringWarn=()=>{};let c=0;for(;;){const e=o.getObj();if(e===wa){n=0;break}if(e instanceof Cmd){const a=t[e.cmd];if(!a){n=0;break}if(a.variableArgs?c<=a.numArgs:c===a.numArgs)break;c=0}else c++}if(2===n)break}else n=0;if(-1===r){warn("findDefaultInlineStreamEnd: Reached the end of the stream without finding a valid EI marker");if(i){warn('... trying to recover by using the last "EI" occurrence.');e.skip(-(e.pos-i))}}let s=4;e.skip(-s);r=e.peekByte();e.skip(s);isWhiteSpace(r)||s--;return e.pos-s-a}findDCTDecodeInlineStreamEnd(e){const t=e.pos;let a,r,i=!1;for(;-1!==(a=e.getByte());)if(255===a){switch(e.getByte()){case 0:break;case 255:e.skip(-1);break;case 217:i=!0;break;case 192:case 193:case 194:case 195:case 197:case 198:case 199:case 201:case 202:case 203:case 205:case 206:case 207:case 196:case 204:case 218:case 219:case 220:case 221:case 222:case 223:case 224:case 225:case 226:case 227:case 228:case 229:case 230:case 231:case 232:case 233:case 234:case 235:case 236:case 237:case 238:case 239:case 254:r=e.getUint16();r>2?e.skip(r-2):e.skip(-2)}if(i)break}const n=e.pos-t;if(-1===a){warn("Inline DCTDecode image stream: EOI marker not found, searching for /EI/ instead.");e.skip(-n);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return n}findASCII85DecodeInlineStreamEnd(e){const t=e.pos;let a;for(;-1!==(a=e.getByte());)if(126===a){const t=e.pos;a=e.peekByte();for(;isWhiteSpace(a);){e.skip();a=e.peekByte()}if(62===a){e.skip();break}if(e.pos>t){const t=e.peekBytes(2);if(69===t[0]&&73===t[1])break}}const r=e.pos-t;if(-1===a){warn("Inline ASCII85Decode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-r);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return r}findASCIIHexDecodeInlineStreamEnd(e){const t=e.pos;let a;for(;-1!==(a=e.getByte())&&62!==a;);const r=e.pos-t;if(-1===a){warn("Inline ASCIIHexDecode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-r);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return r}inlineStreamSkipEI(e){let t,a=0;for(;-1!==(t=e.getByte());)if(0===a)a=69===t?1:0;else if(1===a)a=73===t?2:0;else if(2===a)break}makeInlineImage(e){const t=this.lexer,a=t.stream,r=Object.create(null);let i;for(;!isCmd(this.buf1,"ID")&&this.buf1!==wa;){if(!(this.buf1 instanceof Name))throw new FormatError("Dictionary key must be a name object");const t=this.buf1.name;this.shift();if(this.buf1===wa)break;r[t]=this.getObj(e)}-1!==t.beginInlineImagePos&&(i=a.pos-t.beginInlineImagePos);const n=this.xref.fetchIfRef(r.F||r.Filter);let s;if(n instanceof Name)s=n.name;else if(Array.isArray(n)){const e=this.xref.fetchIfRef(n[0]);e instanceof Name&&(s=e.name)}const o=a.pos;let c,l;switch(s){case"DCT":case"DCTDecode":c=this.findDCTDecodeInlineStreamEnd(a);break;case"A85":case"ASCII85Decode":c=this.findASCII85DecodeInlineStreamEnd(a);break;case"AHx":case"ASCIIHexDecode":c=this.findASCIIHexDecodeInlineStreamEnd(a);break;default:c=this.findDefaultInlineStreamEnd(a)}if(c<1e3&&i>0){const e=a.pos;a.pos=t.beginInlineImagePos;l=function getInlineImageCacheKey(e){const t=[],a=e.length;let r=0;for(;r=r){let r=!1;for(const e of i){const t=e.length;let i=0;for(;i=n){r=!0;break}if(i>=t){if(isWhiteSpace(s[c+o+i])){info(`Found "${bytesToString([...a,...e])}" when searching for endstream command.`);r=!0}break}}if(r){t.pos+=c;return t.pos-e}}c++}t.pos+=o}return-1}makeStream(e,t){const a=this.lexer;let r=a.stream;a.skipToNextLine();const i=r.pos-1;let n=e.get("Length");if(!Number.isInteger(n)){info(`Bad length "${n&&n.toString()}" in stream.`);n=0}r.pos=i+n;a.nextChar();if(this.tryShift()&&isCmd(this.buf2,"endstream"))this.shift();else{n=this.#q(i);if(n<0)throw new FormatError("Missing endstream command.");a.nextChar();this.shift();this.shift()}this.shift();r=r.makeSubStream(i,n,e);t&&(r=t.createStream(r,n));r=this.filter(r,e,n);r.dict=e;return r}filter(e,t,a){let r=t.get("F","Filter"),i=t.get("DP","DecodeParms");if(r instanceof Name){Array.isArray(i)&&warn("/DecodeParms should not be an Array, when /Filter is a Name.");return this.makeFilter(e,r.name,a,i)}let n=a;if(Array.isArray(r)){const t=r,a=i;for(let s=0,o=t.length;s=48&&e<=57?15&e:e>=65&&e<=70||e>=97&&e<=102?9+(15&e):-1}class Lexer{constructor(e,t=null){this.stream=e;this.nextChar();this.strBuf=[];this.knownCommands=t;this._hexStringNumWarn=0;this.beginInlineImagePos=-1}nextChar(){return this.currentChar=this.stream.getByte()}peekChar(){return this.stream.peekByte()}getNumber(){let e=this.currentChar,t=!1,a=0,r=1;if(45===e){r=-1;e=this.nextChar();45===e&&(e=this.nextChar())}else 43===e&&(e=this.nextChar());if(10===e||13===e)do{e=this.nextChar()}while(10===e||13===e);if(46===e){a=10;e=this.nextChar()}if(e<48||e>57){const t=`Invalid number: ${String.fromCharCode(e)} (charCode ${e})`;if(isWhiteSpace(e)||40===e||60===e||-1===e){info(`Lexer.getNumber - "${t}".`);return 0}throw new FormatError(t)}let i=e-48,n=0,s=1;for(;(e=this.nextChar())>=0;)if(e>=48&&e<=57){const r=e-48;if(t)n=10*n+r;else{0!==a&&(a*=10);i=10*i+r}}else if(46===e){if(0!==a)break;a=1}else if(45===e)warn("Badly formatted number: minus sign in the middle");else{if(69!==e&&101!==e)break;e=this.peekChar();if(43===e||45===e){s=45===e?-1:1;this.nextChar()}else if(e<48||e>57)break;t=!0}0!==a&&(i/=a);t&&(i*=10**(s*n));return r*i}getString(){let e=1,t=!1;const a=this.strBuf;a.length=0;let r=this.nextChar();for(;;){let i=!1;switch(0|r){case-1:warn("Unterminated string");t=!0;break;case 40:++e;a.push("(");break;case 41:if(0==--e){this.nextChar();t=!0}else a.push(")");break;case 92:r=this.nextChar();switch(r){case-1:warn("Unterminated string");t=!0;break;case 110:a.push("\n");break;case 114:a.push("\r");break;case 116:a.push("\t");break;case 98:a.push("\b");break;case 102:a.push("\f");break;case 92:case 40:case 41:a.push(String.fromCharCode(r));break;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:let e=15&r;r=this.nextChar();i=!0;if(r>=48&&r<=55){e=(e<<3)+(15&r);r=this.nextChar();if(r>=48&&r<=55){i=!1;e=(e<<3)+(15&r)}}a.push(String.fromCharCode(e));break;case 13:10===this.peekChar()&&this.nextChar();break;case 10:break;default:a.push(String.fromCharCode(r))}break;default:a.push(String.fromCharCode(r))}if(t)break;i||(r=this.nextChar())}return a.join("")}getName(){let e,t;const a=this.strBuf;a.length=0;for(;(e=this.nextChar())>=0&&!mr[e];)if(35===e){e=this.nextChar();if(mr[e]){warn("Lexer_getName: NUMBER SIGN (#) should be followed by a hexadecimal number.");a.push("#");break}const r=toHexDigit(e);if(-1!==r){t=e;e=this.nextChar();const i=toHexDigit(e);if(-1===i){warn(`Lexer_getName: Illegal digit (${String.fromCharCode(e)}) in hexadecimal number.`);a.push("#",String.fromCharCode(t));if(mr[e])break;a.push(String.fromCharCode(e));continue}a.push(String.fromCharCode(r<<4|i))}else a.push("#",String.fromCharCode(e))}else a.push(String.fromCharCode(e));a.length>127&&warn(`Name token is longer than allowed by the spec: ${a.length}`);return Name.get(a.join(""))}_hexStringWarn(e){5!=this._hexStringNumWarn++?this._hexStringNumWarn>5||warn(`getHexString - ignoring invalid character: ${e}`):warn("getHexString - ignoring additional invalid characters.")}getHexString(){const e=this.strBuf;e.length=0;let t=this.currentChar,a=-1,r=-1;this._hexStringNumWarn=0;for(;;){if(t<0){warn("Unterminated hex string");break}if(62===t){this.nextChar();break}if(1!==mr[t]){r=toHexDigit(t);if(-1===r)this._hexStringWarn(t);else if(-1===a)a=r;else{e.push(String.fromCharCode(a<<4|r));a=-1}t=this.nextChar()}else t=this.nextChar()}-1!==a&&e.push(String.fromCharCode(a<<4));return e.join("")}getObj(){let e=!1,t=this.currentChar;for(;;){if(t<0)return wa;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(1!==mr[t])break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return this.getNumber();case 40:return this.getString();case 47:return this.getName();case 91:this.nextChar();return Cmd.get("[");case 93:this.nextChar();return Cmd.get("]");case 60:t=this.nextChar();if(60===t){this.nextChar();return Cmd.get("<<")}return this.getHexString();case 62:t=this.nextChar();if(62===t){this.nextChar();return Cmd.get(">>")}return Cmd.get(">");case 123:this.nextChar();return Cmd.get("{");case 125:this.nextChar();return Cmd.get("}");case 41:this.nextChar();throw new FormatError(`Illegal character: ${t}`)}let a=String.fromCharCode(t);if(t<32||t>127){const e=this.peekChar();if(e>=32&&e<=127){this.nextChar();return Cmd.get(a)}}const r=this.knownCommands;let i=void 0!==r?.[a];for(;(t=this.nextChar())>=0&&!mr[t];){const e=a+String.fromCharCode(t);if(i&&void 0===r[e])break;if(128===a.length)throw new FormatError(`Command token too long: ${a.length}`);a=e;i=void 0!==r?.[a]}if("true"===a)return!0;if("false"===a)return!1;if("null"===a)return null;"BI"===a&&(this.beginInlineImagePos=this.stream.pos);return Cmd.get(a)}skipToNextLine(){let e=this.currentChar;for(;e>=0;){if(13===e){e=this.nextChar();10===e&&this.nextChar();break}if(10===e){this.nextChar();break}e=this.nextChar()}}}class Linearization{static create(e){function getInt(e,t,a=!1){const r=e.get(t);if(Number.isInteger(r)&&(a?r>=0:r>0))return r;throw new Error(`The "${t}" parameter in the linearization dictionary is invalid.`)}const t=new Parser({lexer:new Lexer(e),xref:null}),a=t.getObj(),r=t.getObj(),i=t.getObj(),n=t.getObj();let s,o;if(!(Number.isInteger(a)&&Number.isInteger(r)&&isCmd(i,"obj")&&n instanceof Dict&&"number"==typeof(s=n.get("Linearized"))&&s>0))return null;if((o=getInt(n,"L"))!==e.length)throw new Error('The "L" parameter in the linearization dictionary does not equal the stream length.');return{length:o,hints:function getHints(e){const t=e.get("H");let a;if(Array.isArray(t)&&(2===(a=t.length)||4===a)){for(let e=0;e0))throw new Error(`Hint (${e}) in the linearization dictionary is invalid.`)}return t}throw new Error("Hint array in the linearization dictionary is invalid.")}(n),objectNumberFirst:getInt(n,"O"),endFirst:getInt(n,"E"),numPages:getInt(n,"N"),mainXRefEntriesOffset:getInt(n,"T"),pageFirst:n.has("P")?getInt(n,"P",!0):0}}}const br=["Adobe-GB1-UCS2","Adobe-CNS1-UCS2","Adobe-Japan1-UCS2","Adobe-Korea1-UCS2","78-EUC-H","78-EUC-V","78-H","78-RKSJ-H","78-RKSJ-V","78-V","78ms-RKSJ-H","78ms-RKSJ-V","83pv-RKSJ-H","90ms-RKSJ-H","90ms-RKSJ-V","90msp-RKSJ-H","90msp-RKSJ-V","90pv-RKSJ-H","90pv-RKSJ-V","Add-H","Add-RKSJ-H","Add-RKSJ-V","Add-V","Adobe-CNS1-0","Adobe-CNS1-1","Adobe-CNS1-2","Adobe-CNS1-3","Adobe-CNS1-4","Adobe-CNS1-5","Adobe-CNS1-6","Adobe-GB1-0","Adobe-GB1-1","Adobe-GB1-2","Adobe-GB1-3","Adobe-GB1-4","Adobe-GB1-5","Adobe-Japan1-0","Adobe-Japan1-1","Adobe-Japan1-2","Adobe-Japan1-3","Adobe-Japan1-4","Adobe-Japan1-5","Adobe-Japan1-6","Adobe-Korea1-0","Adobe-Korea1-1","Adobe-Korea1-2","B5-H","B5-V","B5pc-H","B5pc-V","CNS-EUC-H","CNS-EUC-V","CNS1-H","CNS1-V","CNS2-H","CNS2-V","ETHK-B5-H","ETHK-B5-V","ETen-B5-H","ETen-B5-V","ETenms-B5-H","ETenms-B5-V","EUC-H","EUC-V","Ext-H","Ext-RKSJ-H","Ext-RKSJ-V","Ext-V","GB-EUC-H","GB-EUC-V","GB-H","GB-V","GBK-EUC-H","GBK-EUC-V","GBK2K-H","GBK2K-V","GBKp-EUC-H","GBKp-EUC-V","GBT-EUC-H","GBT-EUC-V","GBT-H","GBT-V","GBTpc-EUC-H","GBTpc-EUC-V","GBpc-EUC-H","GBpc-EUC-V","H","HKdla-B5-H","HKdla-B5-V","HKdlb-B5-H","HKdlb-B5-V","HKgccs-B5-H","HKgccs-B5-V","HKm314-B5-H","HKm314-B5-V","HKm471-B5-H","HKm471-B5-V","HKscs-B5-H","HKscs-B5-V","Hankaku","Hiragana","KSC-EUC-H","KSC-EUC-V","KSC-H","KSC-Johab-H","KSC-Johab-V","KSC-V","KSCms-UHC-H","KSCms-UHC-HW-H","KSCms-UHC-HW-V","KSCms-UHC-V","KSCpc-EUC-H","KSCpc-EUC-V","Katakana","NWP-H","NWP-V","RKSJ-H","RKSJ-V","Roman","UniCNS-UCS2-H","UniCNS-UCS2-V","UniCNS-UTF16-H","UniCNS-UTF16-V","UniCNS-UTF32-H","UniCNS-UTF32-V","UniCNS-UTF8-H","UniCNS-UTF8-V","UniGB-UCS2-H","UniGB-UCS2-V","UniGB-UTF16-H","UniGB-UTF16-V","UniGB-UTF32-H","UniGB-UTF32-V","UniGB-UTF8-H","UniGB-UTF8-V","UniJIS-UCS2-H","UniJIS-UCS2-HW-H","UniJIS-UCS2-HW-V","UniJIS-UCS2-V","UniJIS-UTF16-H","UniJIS-UTF16-V","UniJIS-UTF32-H","UniJIS-UTF32-V","UniJIS-UTF8-H","UniJIS-UTF8-V","UniJIS2004-UTF16-H","UniJIS2004-UTF16-V","UniJIS2004-UTF32-H","UniJIS2004-UTF32-V","UniJIS2004-UTF8-H","UniJIS2004-UTF8-V","UniJISPro-UCS2-HW-V","UniJISPro-UCS2-V","UniJISPro-UTF8-V","UniJISX0213-UTF32-H","UniJISX0213-UTF32-V","UniJISX02132004-UTF32-H","UniJISX02132004-UTF32-V","UniKS-UCS2-H","UniKS-UCS2-V","UniKS-UTF16-H","UniKS-UTF16-V","UniKS-UTF32-H","UniKS-UTF32-V","UniKS-UTF8-H","UniKS-UTF8-V","V","WP-Symbol"],yr=2**24-1;class CMap{constructor(e=!1){this.codespaceRanges=[[],[],[],[]];this.numCodespaceRanges=0;this._map=[];this.name="";this.vertical=!1;this.useCMap=null;this.builtInCMap=e}addCodespaceRange(e,t,a){this.codespaceRanges[e-1].push(t,a);this.numCodespaceRanges++}mapCidRange(e,t,a){if(t-e>yr)throw new Error("mapCidRange - ignoring data above MAX_MAP_RANGE.");for(;e<=t;)this._map[e++]=a++}mapBfRange(e,t,a){if(t-e>yr)throw new Error("mapBfRange - ignoring data above MAX_MAP_RANGE.");const r=a.length-1;for(;e<=t;){this._map[e++]=a;const t=a.charCodeAt(r)+1;t>255?a=a.substring(0,r-1)+String.fromCharCode(a.charCodeAt(r-1)+1)+"\0":a=a.substring(0,r)+String.fromCharCode(t)}}mapBfRangeToArray(e,t,a){if(t-e>yr)throw new Error("mapBfRangeToArray - ignoring data above MAX_MAP_RANGE.");const r=a.length;let i=0;for(;e<=t&&i>>0;const s=i[n];for(let e=0,t=s.length;e=t&&r<=i){a.charcode=r;a.length=n+1;return}}}a.charcode=0;a.length=1}getCharCodeLength(e){const t=this.codespaceRanges;for(let a=0,r=t.length;a=i&&e<=n)return a+1}}return 1}get length(){return this._map.length}get isIdentityCMap(){if("Identity-H"!==this.name&&"Identity-V"!==this.name)return!1;if(65536!==this._map.length)return!1;for(let e=0;e<65536;e++)if(this._map[e]!==e)return!1;return!0}}class IdentityCMap extends CMap{constructor(e,t){super();this.vertical=e;this.addCodespaceRange(t,0,65535)}mapCidRange(e,t,a){unreachable("should not call mapCidRange")}mapBfRange(e,t,a){unreachable("should not call mapBfRange")}mapBfRangeToArray(e,t,a){unreachable("should not call mapBfRangeToArray")}mapOne(e,t){unreachable("should not call mapCidOne")}lookup(e){return Number.isInteger(e)&&e<=65535?e:void 0}contains(e){return Number.isInteger(e)&&e<=65535}forEach(e){for(let t=0;t<=65535;t++)e(t,t)}charCodeOf(e){return Number.isInteger(e)&&e<=65535?e:-1}getMap(){const e=new Array(65536);for(let t=0;t<=65535;t++)e[t]=t;return e}get length(){return 65536}get isIdentityCMap(){unreachable("should not access .isIdentityCMap")}}function strToInt(e){let t=0;for(let a=0;a>>0}function expectString(e){if("string"!=typeof e)throw new FormatError("Malformed CMap: expected string.")}function expectInt(e){if(!Number.isInteger(e))throw new FormatError("Malformed CMap: expected int.")}function parseBfChar(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endbfchar"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectString(a);const i=a;e.mapOne(r,i)}}function parseBfRange(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endbfrange"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectString(a);const i=strToInt(a);a=t.getObj();if(Number.isInteger(a)||"string"==typeof a){const t=Number.isInteger(a)?String.fromCharCode(a):a;e.mapBfRange(r,i,t)}else{if(!isCmd(a,"["))break;{a=t.getObj();const n=[];for(;!isCmd(a,"]")&&a!==wa;){n.push(a);a=t.getObj()}e.mapBfRangeToArray(r,i,n)}}}throw new FormatError("Invalid bf range.")}function parseCidChar(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endcidchar"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectInt(a);const i=a;e.mapOne(r,i)}}function parseCidRange(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endcidrange"))return;expectString(a);const r=strToInt(a);a=t.getObj();expectString(a);const i=strToInt(a);a=t.getObj();expectInt(a);const n=a;e.mapCidRange(r,i,n)}}function parseCodespaceRange(e,t){for(;;){let a=t.getObj();if(a===wa)break;if(isCmd(a,"endcodespacerange"))return;if("string"!=typeof a)break;const r=strToInt(a);a=t.getObj();if("string"!=typeof a)break;const i=strToInt(a);e.addCodespaceRange(a.length,r,i)}throw new FormatError("Invalid codespace range.")}function parseWMode(e,t){const a=t.getObj();Number.isInteger(a)&&(e.vertical=!!a)}function parseCMapName(e,t){const a=t.getObj();a instanceof Name&&(e.name=a.name)}async function parseCMap(e,t,a,r){let i,n;e:for(;;)try{const a=t.getObj();if(a===wa)break;if(a instanceof Name){"WMode"===a.name?parseWMode(e,t):"CMapName"===a.name&&parseCMapName(e,t);i=a}else if(a instanceof Cmd)switch(a.cmd){case"endcmap":break e;case"usecmap":i instanceof Name&&(n=i.name);break;case"begincodespacerange":parseCodespaceRange(e,t);break;case"beginbfchar":parseBfChar(e,t);break;case"begincidchar":parseCidChar(e,t);break;case"beginbfrange":parseBfRange(e,t);break;case"begincidrange":parseCidRange(e,t)}}catch(e){if(e instanceof MissingDataException)throw e;warn("Invalid cMap data: "+e);continue}!r&&n&&(r=n);return r?extendCMap(e,a,r):e}async function extendCMap(e,t,a){e.useCMap=await createBuiltInCMap(a,t);if(0===e.numCodespaceRanges){const t=e.useCMap.codespaceRanges;for(let a=0;aextendCMap(i,t,e)));const n=new Lexer(new Stream(a));return parseCMap(i,n,t,null)}class CMapFactory{static async create({encoding:e,fetchBuiltInCMap:t,useCMap:a}){if(e instanceof Name)return createBuiltInCMap(e.name,t);if(e instanceof BaseStream){const r=await parseCMap(new CMap,new Lexer(e),t,a);return r.isIdentityCMap?createBuiltInCMap(r.name,t):r}throw new Error("Encoding required.")}}const wr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","","asuperior","bsuperior","centsuperior","dsuperior","esuperior","","","","isuperior","","","lsuperior","msuperior","nsuperior","osuperior","","","rsuperior","ssuperior","tsuperior","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdownsmall","centoldstyle","Lslashsmall","","","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","","Dotaccentsmall","","","Macronsmall","","","figuredash","hypheninferior","","","Ogoneksmall","Ringsmall","Cedillasmall","","","","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],Sr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","","threequartersemdash","","questionsmall","","","","","Ethsmall","","","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","","","","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hypheninferior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","asuperior","centsuperior","","","","","Aacutesmall","Agravesmall","Acircumflexsmall","Adieresissmall","Atildesmall","Aringsmall","Ccedillasmall","Eacutesmall","Egravesmall","Ecircumflexsmall","Edieresissmall","Iacutesmall","Igravesmall","Icircumflexsmall","Idieresissmall","Ntildesmall","Oacutesmall","Ogravesmall","Ocircumflexsmall","Odieresissmall","Otildesmall","Uacutesmall","Ugravesmall","Ucircumflexsmall","Udieresissmall","","eightsuperior","fourinferior","threeinferior","sixinferior","eightinferior","seveninferior","Scaronsmall","","centinferior","twoinferior","","Dieresissmall","","Caronsmall","osuperior","fiveinferior","","commainferior","periodinferior","Yacutesmall","","dollarinferior","","","Thornsmall","","nineinferior","zeroinferior","Zcaronsmall","AEsmall","Oslashsmall","questiondownsmall","oneinferior","Lslashsmall","","","","","","","Cedillasmall","","","","","","OEsmall","figuredash","hyphensuperior","","","","","exclamdownsmall","","Ydieresissmall","","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","ninesuperior","zerosuperior","","esuperior","rsuperior","tsuperior","","","isuperior","ssuperior","dsuperior","","","","","","lsuperior","Ogoneksmall","Brevesmall","Macronsmall","bsuperior","nsuperior","msuperior","commasuperior","periodsuperior","Dotaccentsmall","Ringsmall","","","",""],xr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","space","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron"],Ar=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","","endash","dagger","daggerdbl","periodcentered","","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","","questiondown","","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","","ring","cedilla","","hungarumlaut","ogonek","caron","emdash","","","","","","","","","","","","","","","","","AE","","ordfeminine","","","","","Lslash","Oslash","OE","ordmasculine","","","","","","ae","","","","dotlessi","","","lslash","oslash","oe","germandbls","","","",""],kr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","bullet","Euro","bullet","quotesinglbase","florin","quotedblbase","ellipsis","dagger","daggerdbl","circumflex","perthousand","Scaron","guilsinglleft","OE","bullet","Zcaron","bullet","bullet","quoteleft","quoteright","quotedblleft","quotedblright","bullet","endash","emdash","tilde","trademark","scaron","guilsinglright","oe","bullet","zcaron","Ydieresis","space","exclamdown","cent","sterling","currency","yen","brokenbar","section","dieresis","copyright","ordfeminine","guillemotleft","logicalnot","hyphen","registered","macron","degree","plusminus","twosuperior","threesuperior","acute","mu","paragraph","periodcentered","cedilla","onesuperior","ordmasculine","guillemotright","onequarter","onehalf","threequarters","questiondown","Agrave","Aacute","Acircumflex","Atilde","Adieresis","Aring","AE","Ccedilla","Egrave","Eacute","Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth","Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply","Oslash","Ugrave","Uacute","Ucircumflex","Udieresis","Yacute","Thorn","germandbls","agrave","aacute","acircumflex","atilde","adieresis","aring","ae","ccedilla","egrave","eacute","ecircumflex","edieresis","igrave","iacute","icircumflex","idieresis","eth","ntilde","ograve","oacute","ocircumflex","otilde","odieresis","divide","oslash","ugrave","uacute","ucircumflex","udieresis","yacute","thorn","ydieresis"],Cr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","universal","numbersign","existential","percent","ampersand","suchthat","parenleft","parenright","asteriskmath","plus","comma","minus","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","congruent","Alpha","Beta","Chi","Delta","Epsilon","Phi","Gamma","Eta","Iota","theta1","Kappa","Lambda","Mu","Nu","Omicron","Pi","Theta","Rho","Sigma","Tau","Upsilon","sigma1","Omega","Xi","Psi","Zeta","bracketleft","therefore","bracketright","perpendicular","underscore","radicalex","alpha","beta","chi","delta","epsilon","phi","gamma","eta","iota","phi1","kappa","lambda","mu","nu","omicron","pi","theta","rho","sigma","tau","upsilon","omega1","omega","xi","psi","zeta","braceleft","bar","braceright","similar","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Euro","Upsilon1","minute","lessequal","fraction","infinity","florin","club","diamond","heart","spade","arrowboth","arrowleft","arrowup","arrowright","arrowdown","degree","plusminus","second","greaterequal","multiply","proportional","partialdiff","bullet","divide","notequal","equivalence","approxequal","ellipsis","arrowvertex","arrowhorizex","carriagereturn","aleph","Ifraktur","Rfraktur","weierstrass","circlemultiply","circleplus","emptyset","intersection","union","propersuperset","reflexsuperset","notsubset","propersubset","reflexsubset","element","notelement","angle","gradient","registerserif","copyrightserif","trademarkserif","product","radical","dotmath","logicalnot","logicaland","logicalor","arrowdblboth","arrowdblleft","arrowdblup","arrowdblright","arrowdbldown","lozenge","angleleft","registersans","copyrightsans","trademarksans","summation","parenlefttp","parenleftex","parenleftbt","bracketlefttp","bracketleftex","bracketleftbt","bracelefttp","braceleftmid","braceleftbt","braceex","","angleright","integral","integraltp","integralex","integralbt","parenrighttp","parenrightex","parenrightbt","bracketrighttp","bracketrightex","bracketrightbt","bracerighttp","bracerightmid","bracerightbt",""],vr=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13","a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25","a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34","a35","a36","a37","a38","a39","a40","a41","a42","a43","a44","a45","a46","a47","a48","a49","a50","a51","a52","a53","a54","a55","a56","a57","a58","a59","a60","a61","a62","a63","a64","a65","a66","a67","a68","a69","a70","a71","a72","a73","a74","a203","a75","a204","a76","a77","a78","a79","a81","a82","a83","a84","a97","a98","a99","a100","","a89","a90","a93","a94","a91","a92","a205","a85","a206","a86","a87","a88","a95","a96","","","","","","","","","","","","","","","","","","","","a101","a102","a103","a104","a106","a107","a108","a112","a111","a110","a109","a120","a121","a122","a123","a124","a125","a126","a127","a128","a129","a130","a131","a132","a133","a134","a135","a136","a137","a138","a139","a140","a141","a142","a143","a144","a145","a146","a147","a148","a149","a150","a151","a152","a153","a154","a155","a156","a157","a158","a159","a160","a161","a163","a164","a196","a165","a192","a166","a167","a168","a169","a170","a171","a172","a173","a162","a174","a175","a176","a177","a178","a179","a193","a180","a199","a181","a200","a182","","a201","a183","a184","a197","a185","a194","a198","a186","a195","a187","a188","a189","a190","a191",""];function getEncoding(e){switch(e){case"WinAnsiEncoding":return kr;case"StandardEncoding":return Ar;case"MacRomanEncoding":return xr;case"SymbolSetEncoding":return Cr;case"ZapfDingbatsEncoding":return vr;case"ExpertEncoding":return wr;case"MacExpertEncoding":return Sr;default:return null}}const Fr=getLookupTableFactory((function(e){e.A=65;e.AE=198;e.AEacute=508;e.AEmacron=482;e.AEsmall=63462;e.Aacute=193;e.Aacutesmall=63457;e.Abreve=258;e.Abreveacute=7854;e.Abrevecyrillic=1232;e.Abrevedotbelow=7862;e.Abrevegrave=7856;e.Abrevehookabove=7858;e.Abrevetilde=7860;e.Acaron=461;e.Acircle=9398;e.Acircumflex=194;e.Acircumflexacute=7844;e.Acircumflexdotbelow=7852;e.Acircumflexgrave=7846;e.Acircumflexhookabove=7848;e.Acircumflexsmall=63458;e.Acircumflextilde=7850;e.Acute=63177;e.Acutesmall=63412;e.Acyrillic=1040;e.Adblgrave=512;e.Adieresis=196;e.Adieresiscyrillic=1234;e.Adieresismacron=478;e.Adieresissmall=63460;e.Adotbelow=7840;e.Adotmacron=480;e.Agrave=192;e.Agravesmall=63456;e.Ahookabove=7842;e.Aiecyrillic=1236;e.Ainvertedbreve=514;e.Alpha=913;e.Alphatonos=902;e.Amacron=256;e.Amonospace=65313;e.Aogonek=260;e.Aring=197;e.Aringacute=506;e.Aringbelow=7680;e.Aringsmall=63461;e.Asmall=63329;e.Atilde=195;e.Atildesmall=63459;e.Aybarmenian=1329;e.B=66;e.Bcircle=9399;e.Bdotaccent=7682;e.Bdotbelow=7684;e.Becyrillic=1041;e.Benarmenian=1330;e.Beta=914;e.Bhook=385;e.Blinebelow=7686;e.Bmonospace=65314;e.Brevesmall=63220;e.Bsmall=63330;e.Btopbar=386;e.C=67;e.Caarmenian=1342;e.Cacute=262;e.Caron=63178;e.Caronsmall=63221;e.Ccaron=268;e.Ccedilla=199;e.Ccedillaacute=7688;e.Ccedillasmall=63463;e.Ccircle=9400;e.Ccircumflex=264;e.Cdot=266;e.Cdotaccent=266;e.Cedillasmall=63416;e.Chaarmenian=1353;e.Cheabkhasiancyrillic=1212;e.Checyrillic=1063;e.Chedescenderabkhasiancyrillic=1214;e.Chedescendercyrillic=1206;e.Chedieresiscyrillic=1268;e.Cheharmenian=1347;e.Chekhakassiancyrillic=1227;e.Cheverticalstrokecyrillic=1208;e.Chi=935;e.Chook=391;e.Circumflexsmall=63222;e.Cmonospace=65315;e.Coarmenian=1361;e.Csmall=63331;e.D=68;e.DZ=497;e.DZcaron=452;e.Daarmenian=1332;e.Dafrican=393;e.Dcaron=270;e.Dcedilla=7696;e.Dcircle=9401;e.Dcircumflexbelow=7698;e.Dcroat=272;e.Ddotaccent=7690;e.Ddotbelow=7692;e.Decyrillic=1044;e.Deicoptic=1006;e.Delta=8710;e.Deltagreek=916;e.Dhook=394;e.Dieresis=63179;e.DieresisAcute=63180;e.DieresisGrave=63181;e.Dieresissmall=63400;e.Digammagreek=988;e.Djecyrillic=1026;e.Dlinebelow=7694;e.Dmonospace=65316;e.Dotaccentsmall=63223;e.Dslash=272;e.Dsmall=63332;e.Dtopbar=395;e.Dz=498;e.Dzcaron=453;e.Dzeabkhasiancyrillic=1248;e.Dzecyrillic=1029;e.Dzhecyrillic=1039;e.E=69;e.Eacute=201;e.Eacutesmall=63465;e.Ebreve=276;e.Ecaron=282;e.Ecedillabreve=7708;e.Echarmenian=1333;e.Ecircle=9402;e.Ecircumflex=202;e.Ecircumflexacute=7870;e.Ecircumflexbelow=7704;e.Ecircumflexdotbelow=7878;e.Ecircumflexgrave=7872;e.Ecircumflexhookabove=7874;e.Ecircumflexsmall=63466;e.Ecircumflextilde=7876;e.Ecyrillic=1028;e.Edblgrave=516;e.Edieresis=203;e.Edieresissmall=63467;e.Edot=278;e.Edotaccent=278;e.Edotbelow=7864;e.Efcyrillic=1060;e.Egrave=200;e.Egravesmall=63464;e.Eharmenian=1335;e.Ehookabove=7866;e.Eightroman=8551;e.Einvertedbreve=518;e.Eiotifiedcyrillic=1124;e.Elcyrillic=1051;e.Elevenroman=8554;e.Emacron=274;e.Emacronacute=7702;e.Emacrongrave=7700;e.Emcyrillic=1052;e.Emonospace=65317;e.Encyrillic=1053;e.Endescendercyrillic=1186;e.Eng=330;e.Enghecyrillic=1188;e.Enhookcyrillic=1223;e.Eogonek=280;e.Eopen=400;e.Epsilon=917;e.Epsilontonos=904;e.Ercyrillic=1056;e.Ereversed=398;e.Ereversedcyrillic=1069;e.Escyrillic=1057;e.Esdescendercyrillic=1194;e.Esh=425;e.Esmall=63333;e.Eta=919;e.Etarmenian=1336;e.Etatonos=905;e.Eth=208;e.Ethsmall=63472;e.Etilde=7868;e.Etildebelow=7706;e.Euro=8364;e.Ezh=439;e.Ezhcaron=494;e.Ezhreversed=440;e.F=70;e.Fcircle=9403;e.Fdotaccent=7710;e.Feharmenian=1366;e.Feicoptic=996;e.Fhook=401;e.Fitacyrillic=1138;e.Fiveroman=8548;e.Fmonospace=65318;e.Fourroman=8547;e.Fsmall=63334;e.G=71;e.GBsquare=13191;e.Gacute=500;e.Gamma=915;e.Gammaafrican=404;e.Gangiacoptic=1002;e.Gbreve=286;e.Gcaron=486;e.Gcedilla=290;e.Gcircle=9404;e.Gcircumflex=284;e.Gcommaaccent=290;e.Gdot=288;e.Gdotaccent=288;e.Gecyrillic=1043;e.Ghadarmenian=1346;e.Ghemiddlehookcyrillic=1172;e.Ghestrokecyrillic=1170;e.Gheupturncyrillic=1168;e.Ghook=403;e.Gimarmenian=1331;e.Gjecyrillic=1027;e.Gmacron=7712;e.Gmonospace=65319;e.Grave=63182;e.Gravesmall=63328;e.Gsmall=63335;e.Gsmallhook=667;e.Gstroke=484;e.H=72;e.H18533=9679;e.H18543=9642;e.H18551=9643;e.H22073=9633;e.HPsquare=13259;e.Haabkhasiancyrillic=1192;e.Hadescendercyrillic=1202;e.Hardsigncyrillic=1066;e.Hbar=294;e.Hbrevebelow=7722;e.Hcedilla=7720;e.Hcircle=9405;e.Hcircumflex=292;e.Hdieresis=7718;e.Hdotaccent=7714;e.Hdotbelow=7716;e.Hmonospace=65320;e.Hoarmenian=1344;e.Horicoptic=1e3;e.Hsmall=63336;e.Hungarumlaut=63183;e.Hungarumlautsmall=63224;e.Hzsquare=13200;e.I=73;e.IAcyrillic=1071;e.IJ=306;e.IUcyrillic=1070;e.Iacute=205;e.Iacutesmall=63469;e.Ibreve=300;e.Icaron=463;e.Icircle=9406;e.Icircumflex=206;e.Icircumflexsmall=63470;e.Icyrillic=1030;e.Idblgrave=520;e.Idieresis=207;e.Idieresisacute=7726;e.Idieresiscyrillic=1252;e.Idieresissmall=63471;e.Idot=304;e.Idotaccent=304;e.Idotbelow=7882;e.Iebrevecyrillic=1238;e.Iecyrillic=1045;e.Ifraktur=8465;e.Igrave=204;e.Igravesmall=63468;e.Ihookabove=7880;e.Iicyrillic=1048;e.Iinvertedbreve=522;e.Iishortcyrillic=1049;e.Imacron=298;e.Imacroncyrillic=1250;e.Imonospace=65321;e.Iniarmenian=1339;e.Iocyrillic=1025;e.Iogonek=302;e.Iota=921;e.Iotaafrican=406;e.Iotadieresis=938;e.Iotatonos=906;e.Ismall=63337;e.Istroke=407;e.Itilde=296;e.Itildebelow=7724;e.Izhitsacyrillic=1140;e.Izhitsadblgravecyrillic=1142;e.J=74;e.Jaarmenian=1345;e.Jcircle=9407;e.Jcircumflex=308;e.Jecyrillic=1032;e.Jheharmenian=1355;e.Jmonospace=65322;e.Jsmall=63338;e.K=75;e.KBsquare=13189;e.KKsquare=13261;e.Kabashkircyrillic=1184;e.Kacute=7728;e.Kacyrillic=1050;e.Kadescendercyrillic=1178;e.Kahookcyrillic=1219;e.Kappa=922;e.Kastrokecyrillic=1182;e.Kaverticalstrokecyrillic=1180;e.Kcaron=488;e.Kcedilla=310;e.Kcircle=9408;e.Kcommaaccent=310;e.Kdotbelow=7730;e.Keharmenian=1364;e.Kenarmenian=1343;e.Khacyrillic=1061;e.Kheicoptic=998;e.Khook=408;e.Kjecyrillic=1036;e.Klinebelow=7732;e.Kmonospace=65323;e.Koppacyrillic=1152;e.Koppagreek=990;e.Ksicyrillic=1134;e.Ksmall=63339;e.L=76;e.LJ=455;e.LL=63167;e.Lacute=313;e.Lambda=923;e.Lcaron=317;e.Lcedilla=315;e.Lcircle=9409;e.Lcircumflexbelow=7740;e.Lcommaaccent=315;e.Ldot=319;e.Ldotaccent=319;e.Ldotbelow=7734;e.Ldotbelowmacron=7736;e.Liwnarmenian=1340;e.Lj=456;e.Ljecyrillic=1033;e.Llinebelow=7738;e.Lmonospace=65324;e.Lslash=321;e.Lslashsmall=63225;e.Lsmall=63340;e.M=77;e.MBsquare=13190;e.Macron=63184;e.Macronsmall=63407;e.Macute=7742;e.Mcircle=9410;e.Mdotaccent=7744;e.Mdotbelow=7746;e.Menarmenian=1348;e.Mmonospace=65325;e.Msmall=63341;e.Mturned=412;e.Mu=924;e.N=78;e.NJ=458;e.Nacute=323;e.Ncaron=327;e.Ncedilla=325;e.Ncircle=9411;e.Ncircumflexbelow=7754;e.Ncommaaccent=325;e.Ndotaccent=7748;e.Ndotbelow=7750;e.Nhookleft=413;e.Nineroman=8552;e.Nj=459;e.Njecyrillic=1034;e.Nlinebelow=7752;e.Nmonospace=65326;e.Nowarmenian=1350;e.Nsmall=63342;e.Ntilde=209;e.Ntildesmall=63473;e.Nu=925;e.O=79;e.OE=338;e.OEsmall=63226;e.Oacute=211;e.Oacutesmall=63475;e.Obarredcyrillic=1256;e.Obarreddieresiscyrillic=1258;e.Obreve=334;e.Ocaron=465;e.Ocenteredtilde=415;e.Ocircle=9412;e.Ocircumflex=212;e.Ocircumflexacute=7888;e.Ocircumflexdotbelow=7896;e.Ocircumflexgrave=7890;e.Ocircumflexhookabove=7892;e.Ocircumflexsmall=63476;e.Ocircumflextilde=7894;e.Ocyrillic=1054;e.Odblacute=336;e.Odblgrave=524;e.Odieresis=214;e.Odieresiscyrillic=1254;e.Odieresissmall=63478;e.Odotbelow=7884;e.Ogoneksmall=63227;e.Ograve=210;e.Ogravesmall=63474;e.Oharmenian=1365;e.Ohm=8486;e.Ohookabove=7886;e.Ohorn=416;e.Ohornacute=7898;e.Ohorndotbelow=7906;e.Ohorngrave=7900;e.Ohornhookabove=7902;e.Ohorntilde=7904;e.Ohungarumlaut=336;e.Oi=418;e.Oinvertedbreve=526;e.Omacron=332;e.Omacronacute=7762;e.Omacrongrave=7760;e.Omega=8486;e.Omegacyrillic=1120;e.Omegagreek=937;e.Omegaroundcyrillic=1146;e.Omegatitlocyrillic=1148;e.Omegatonos=911;e.Omicron=927;e.Omicrontonos=908;e.Omonospace=65327;e.Oneroman=8544;e.Oogonek=490;e.Oogonekmacron=492;e.Oopen=390;e.Oslash=216;e.Oslashacute=510;e.Oslashsmall=63480;e.Osmall=63343;e.Ostrokeacute=510;e.Otcyrillic=1150;e.Otilde=213;e.Otildeacute=7756;e.Otildedieresis=7758;e.Otildesmall=63477;e.P=80;e.Pacute=7764;e.Pcircle=9413;e.Pdotaccent=7766;e.Pecyrillic=1055;e.Peharmenian=1354;e.Pemiddlehookcyrillic=1190;e.Phi=934;e.Phook=420;e.Pi=928;e.Piwrarmenian=1363;e.Pmonospace=65328;e.Psi=936;e.Psicyrillic=1136;e.Psmall=63344;e.Q=81;e.Qcircle=9414;e.Qmonospace=65329;e.Qsmall=63345;e.R=82;e.Raarmenian=1356;e.Racute=340;e.Rcaron=344;e.Rcedilla=342;e.Rcircle=9415;e.Rcommaaccent=342;e.Rdblgrave=528;e.Rdotaccent=7768;e.Rdotbelow=7770;e.Rdotbelowmacron=7772;e.Reharmenian=1360;e.Rfraktur=8476;e.Rho=929;e.Ringsmall=63228;e.Rinvertedbreve=530;e.Rlinebelow=7774;e.Rmonospace=65330;e.Rsmall=63346;e.Rsmallinverted=641;e.Rsmallinvertedsuperior=694;e.S=83;e.SF010000=9484;e.SF020000=9492;e.SF030000=9488;e.SF040000=9496;e.SF050000=9532;e.SF060000=9516;e.SF070000=9524;e.SF080000=9500;e.SF090000=9508;e.SF100000=9472;e.SF110000=9474;e.SF190000=9569;e.SF200000=9570;e.SF210000=9558;e.SF220000=9557;e.SF230000=9571;e.SF240000=9553;e.SF250000=9559;e.SF260000=9565;e.SF270000=9564;e.SF280000=9563;e.SF360000=9566;e.SF370000=9567;e.SF380000=9562;e.SF390000=9556;e.SF400000=9577;e.SF410000=9574;e.SF420000=9568;e.SF430000=9552;e.SF440000=9580;e.SF450000=9575;e.SF460000=9576;e.SF470000=9572;e.SF480000=9573;e.SF490000=9561;e.SF500000=9560;e.SF510000=9554;e.SF520000=9555;e.SF530000=9579;e.SF540000=9578;e.Sacute=346;e.Sacutedotaccent=7780;e.Sampigreek=992;e.Scaron=352;e.Scarondotaccent=7782;e.Scaronsmall=63229;e.Scedilla=350;e.Schwa=399;e.Schwacyrillic=1240;e.Schwadieresiscyrillic=1242;e.Scircle=9416;e.Scircumflex=348;e.Scommaaccent=536;e.Sdotaccent=7776;e.Sdotbelow=7778;e.Sdotbelowdotaccent=7784;e.Seharmenian=1357;e.Sevenroman=8550;e.Shaarmenian=1351;e.Shacyrillic=1064;e.Shchacyrillic=1065;e.Sheicoptic=994;e.Shhacyrillic=1210;e.Shimacoptic=1004;e.Sigma=931;e.Sixroman=8549;e.Smonospace=65331;e.Softsigncyrillic=1068;e.Ssmall=63347;e.Stigmagreek=986;e.T=84;e.Tau=932;e.Tbar=358;e.Tcaron=356;e.Tcedilla=354;e.Tcircle=9417;e.Tcircumflexbelow=7792;e.Tcommaaccent=354;e.Tdotaccent=7786;e.Tdotbelow=7788;e.Tecyrillic=1058;e.Tedescendercyrillic=1196;e.Tenroman=8553;e.Tetsecyrillic=1204;e.Theta=920;e.Thook=428;e.Thorn=222;e.Thornsmall=63486;e.Threeroman=8546;e.Tildesmall=63230;e.Tiwnarmenian=1359;e.Tlinebelow=7790;e.Tmonospace=65332;e.Toarmenian=1337;e.Tonefive=444;e.Tonesix=388;e.Tonetwo=423;e.Tretroflexhook=430;e.Tsecyrillic=1062;e.Tshecyrillic=1035;e.Tsmall=63348;e.Twelveroman=8555;e.Tworoman=8545;e.U=85;e.Uacute=218;e.Uacutesmall=63482;e.Ubreve=364;e.Ucaron=467;e.Ucircle=9418;e.Ucircumflex=219;e.Ucircumflexbelow=7798;e.Ucircumflexsmall=63483;e.Ucyrillic=1059;e.Udblacute=368;e.Udblgrave=532;e.Udieresis=220;e.Udieresisacute=471;e.Udieresisbelow=7794;e.Udieresiscaron=473;e.Udieresiscyrillic=1264;e.Udieresisgrave=475;e.Udieresismacron=469;e.Udieresissmall=63484;e.Udotbelow=7908;e.Ugrave=217;e.Ugravesmall=63481;e.Uhookabove=7910;e.Uhorn=431;e.Uhornacute=7912;e.Uhorndotbelow=7920;e.Uhorngrave=7914;e.Uhornhookabove=7916;e.Uhorntilde=7918;e.Uhungarumlaut=368;e.Uhungarumlautcyrillic=1266;e.Uinvertedbreve=534;e.Ukcyrillic=1144;e.Umacron=362;e.Umacroncyrillic=1262;e.Umacrondieresis=7802;e.Umonospace=65333;e.Uogonek=370;e.Upsilon=933;e.Upsilon1=978;e.Upsilonacutehooksymbolgreek=979;e.Upsilonafrican=433;e.Upsilondieresis=939;e.Upsilondieresishooksymbolgreek=980;e.Upsilonhooksymbol=978;e.Upsilontonos=910;e.Uring=366;e.Ushortcyrillic=1038;e.Usmall=63349;e.Ustraightcyrillic=1198;e.Ustraightstrokecyrillic=1200;e.Utilde=360;e.Utildeacute=7800;e.Utildebelow=7796;e.V=86;e.Vcircle=9419;e.Vdotbelow=7806;e.Vecyrillic=1042;e.Vewarmenian=1358;e.Vhook=434;e.Vmonospace=65334;e.Voarmenian=1352;e.Vsmall=63350;e.Vtilde=7804;e.W=87;e.Wacute=7810;e.Wcircle=9420;e.Wcircumflex=372;e.Wdieresis=7812;e.Wdotaccent=7814;e.Wdotbelow=7816;e.Wgrave=7808;e.Wmonospace=65335;e.Wsmall=63351;e.X=88;e.Xcircle=9421;e.Xdieresis=7820;e.Xdotaccent=7818;e.Xeharmenian=1341;e.Xi=926;e.Xmonospace=65336;e.Xsmall=63352;e.Y=89;e.Yacute=221;e.Yacutesmall=63485;e.Yatcyrillic=1122;e.Ycircle=9422;e.Ycircumflex=374;e.Ydieresis=376;e.Ydieresissmall=63487;e.Ydotaccent=7822;e.Ydotbelow=7924;e.Yericyrillic=1067;e.Yerudieresiscyrillic=1272;e.Ygrave=7922;e.Yhook=435;e.Yhookabove=7926;e.Yiarmenian=1349;e.Yicyrillic=1031;e.Yiwnarmenian=1362;e.Ymonospace=65337;e.Ysmall=63353;e.Ytilde=7928;e.Yusbigcyrillic=1130;e.Yusbigiotifiedcyrillic=1132;e.Yuslittlecyrillic=1126;e.Yuslittleiotifiedcyrillic=1128;e.Z=90;e.Zaarmenian=1334;e.Zacute=377;e.Zcaron=381;e.Zcaronsmall=63231;e.Zcircle=9423;e.Zcircumflex=7824;e.Zdot=379;e.Zdotaccent=379;e.Zdotbelow=7826;e.Zecyrillic=1047;e.Zedescendercyrillic=1176;e.Zedieresiscyrillic=1246;e.Zeta=918;e.Zhearmenian=1338;e.Zhebrevecyrillic=1217;e.Zhecyrillic=1046;e.Zhedescendercyrillic=1174;e.Zhedieresiscyrillic=1244;e.Zlinebelow=7828;e.Zmonospace=65338;e.Zsmall=63354;e.Zstroke=437;e.a=97;e.aabengali=2438;e.aacute=225;e.aadeva=2310;e.aagujarati=2694;e.aagurmukhi=2566;e.aamatragurmukhi=2622;e.aarusquare=13059;e.aavowelsignbengali=2494;e.aavowelsigndeva=2366;e.aavowelsigngujarati=2750;e.abbreviationmarkarmenian=1375;e.abbreviationsigndeva=2416;e.abengali=2437;e.abopomofo=12570;e.abreve=259;e.abreveacute=7855;e.abrevecyrillic=1233;e.abrevedotbelow=7863;e.abrevegrave=7857;e.abrevehookabove=7859;e.abrevetilde=7861;e.acaron=462;e.acircle=9424;e.acircumflex=226;e.acircumflexacute=7845;e.acircumflexdotbelow=7853;e.acircumflexgrave=7847;e.acircumflexhookabove=7849;e.acircumflextilde=7851;e.acute=180;e.acutebelowcmb=791;e.acutecmb=769;e.acutecomb=769;e.acutedeva=2388;e.acutelowmod=719;e.acutetonecmb=833;e.acyrillic=1072;e.adblgrave=513;e.addakgurmukhi=2673;e.adeva=2309;e.adieresis=228;e.adieresiscyrillic=1235;e.adieresismacron=479;e.adotbelow=7841;e.adotmacron=481;e.ae=230;e.aeacute=509;e.aekorean=12624;e.aemacron=483;e.afii00208=8213;e.afii08941=8356;e.afii10017=1040;e.afii10018=1041;e.afii10019=1042;e.afii10020=1043;e.afii10021=1044;e.afii10022=1045;e.afii10023=1025;e.afii10024=1046;e.afii10025=1047;e.afii10026=1048;e.afii10027=1049;e.afii10028=1050;e.afii10029=1051;e.afii10030=1052;e.afii10031=1053;e.afii10032=1054;e.afii10033=1055;e.afii10034=1056;e.afii10035=1057;e.afii10036=1058;e.afii10037=1059;e.afii10038=1060;e.afii10039=1061;e.afii10040=1062;e.afii10041=1063;e.afii10042=1064;e.afii10043=1065;e.afii10044=1066;e.afii10045=1067;e.afii10046=1068;e.afii10047=1069;e.afii10048=1070;e.afii10049=1071;e.afii10050=1168;e.afii10051=1026;e.afii10052=1027;e.afii10053=1028;e.afii10054=1029;e.afii10055=1030;e.afii10056=1031;e.afii10057=1032;e.afii10058=1033;e.afii10059=1034;e.afii10060=1035;e.afii10061=1036;e.afii10062=1038;e.afii10063=63172;e.afii10064=63173;e.afii10065=1072;e.afii10066=1073;e.afii10067=1074;e.afii10068=1075;e.afii10069=1076;e.afii10070=1077;e.afii10071=1105;e.afii10072=1078;e.afii10073=1079;e.afii10074=1080;e.afii10075=1081;e.afii10076=1082;e.afii10077=1083;e.afii10078=1084;e.afii10079=1085;e.afii10080=1086;e.afii10081=1087;e.afii10082=1088;e.afii10083=1089;e.afii10084=1090;e.afii10085=1091;e.afii10086=1092;e.afii10087=1093;e.afii10088=1094;e.afii10089=1095;e.afii10090=1096;e.afii10091=1097;e.afii10092=1098;e.afii10093=1099;e.afii10094=1100;e.afii10095=1101;e.afii10096=1102;e.afii10097=1103;e.afii10098=1169;e.afii10099=1106;e.afii10100=1107;e.afii10101=1108;e.afii10102=1109;e.afii10103=1110;e.afii10104=1111;e.afii10105=1112;e.afii10106=1113;e.afii10107=1114;e.afii10108=1115;e.afii10109=1116;e.afii10110=1118;e.afii10145=1039;e.afii10146=1122;e.afii10147=1138;e.afii10148=1140;e.afii10192=63174;e.afii10193=1119;e.afii10194=1123;e.afii10195=1139;e.afii10196=1141;e.afii10831=63175;e.afii10832=63176;e.afii10846=1241;e.afii299=8206;e.afii300=8207;e.afii301=8205;e.afii57381=1642;e.afii57388=1548;e.afii57392=1632;e.afii57393=1633;e.afii57394=1634;e.afii57395=1635;e.afii57396=1636;e.afii57397=1637;e.afii57398=1638;e.afii57399=1639;e.afii57400=1640;e.afii57401=1641;e.afii57403=1563;e.afii57407=1567;e.afii57409=1569;e.afii57410=1570;e.afii57411=1571;e.afii57412=1572;e.afii57413=1573;e.afii57414=1574;e.afii57415=1575;e.afii57416=1576;e.afii57417=1577;e.afii57418=1578;e.afii57419=1579;e.afii57420=1580;e.afii57421=1581;e.afii57422=1582;e.afii57423=1583;e.afii57424=1584;e.afii57425=1585;e.afii57426=1586;e.afii57427=1587;e.afii57428=1588;e.afii57429=1589;e.afii57430=1590;e.afii57431=1591;e.afii57432=1592;e.afii57433=1593;e.afii57434=1594;e.afii57440=1600;e.afii57441=1601;e.afii57442=1602;e.afii57443=1603;e.afii57444=1604;e.afii57445=1605;e.afii57446=1606;e.afii57448=1608;e.afii57449=1609;e.afii57450=1610;e.afii57451=1611;e.afii57452=1612;e.afii57453=1613;e.afii57454=1614;e.afii57455=1615;e.afii57456=1616;e.afii57457=1617;e.afii57458=1618;e.afii57470=1607;e.afii57505=1700;e.afii57506=1662;e.afii57507=1670;e.afii57508=1688;e.afii57509=1711;e.afii57511=1657;e.afii57512=1672;e.afii57513=1681;e.afii57514=1722;e.afii57519=1746;e.afii57534=1749;e.afii57636=8362;e.afii57645=1470;e.afii57658=1475;e.afii57664=1488;e.afii57665=1489;e.afii57666=1490;e.afii57667=1491;e.afii57668=1492;e.afii57669=1493;e.afii57670=1494;e.afii57671=1495;e.afii57672=1496;e.afii57673=1497;e.afii57674=1498;e.afii57675=1499;e.afii57676=1500;e.afii57677=1501;e.afii57678=1502;e.afii57679=1503;e.afii57680=1504;e.afii57681=1505;e.afii57682=1506;e.afii57683=1507;e.afii57684=1508;e.afii57685=1509;e.afii57686=1510;e.afii57687=1511;e.afii57688=1512;e.afii57689=1513;e.afii57690=1514;e.afii57694=64298;e.afii57695=64299;e.afii57700=64331;e.afii57705=64287;e.afii57716=1520;e.afii57717=1521;e.afii57718=1522;e.afii57723=64309;e.afii57793=1460;e.afii57794=1461;e.afii57795=1462;e.afii57796=1467;e.afii57797=1464;e.afii57798=1463;e.afii57799=1456;e.afii57800=1458;e.afii57801=1457;e.afii57802=1459;e.afii57803=1474;e.afii57804=1473;e.afii57806=1465;e.afii57807=1468;e.afii57839=1469;e.afii57841=1471;e.afii57842=1472;e.afii57929=700;e.afii61248=8453;e.afii61289=8467;e.afii61352=8470;e.afii61573=8236;e.afii61574=8237;e.afii61575=8238;e.afii61664=8204;e.afii63167=1645;e.afii64937=701;e.agrave=224;e.agujarati=2693;e.agurmukhi=2565;e.ahiragana=12354;e.ahookabove=7843;e.aibengali=2448;e.aibopomofo=12574;e.aideva=2320;e.aiecyrillic=1237;e.aigujarati=2704;e.aigurmukhi=2576;e.aimatragurmukhi=2632;e.ainarabic=1593;e.ainfinalarabic=65226;e.aininitialarabic=65227;e.ainmedialarabic=65228;e.ainvertedbreve=515;e.aivowelsignbengali=2504;e.aivowelsigndeva=2376;e.aivowelsigngujarati=2760;e.akatakana=12450;e.akatakanahalfwidth=65393;e.akorean=12623;e.alef=1488;e.alefarabic=1575;e.alefdageshhebrew=64304;e.aleffinalarabic=65166;e.alefhamzaabovearabic=1571;e.alefhamzaabovefinalarabic=65156;e.alefhamzabelowarabic=1573;e.alefhamzabelowfinalarabic=65160;e.alefhebrew=1488;e.aleflamedhebrew=64335;e.alefmaddaabovearabic=1570;e.alefmaddaabovefinalarabic=65154;e.alefmaksuraarabic=1609;e.alefmaksurafinalarabic=65264;e.alefmaksurainitialarabic=65267;e.alefmaksuramedialarabic=65268;e.alefpatahhebrew=64302;e.alefqamatshebrew=64303;e.aleph=8501;e.allequal=8780;e.alpha=945;e.alphatonos=940;e.amacron=257;e.amonospace=65345;e.ampersand=38;e.ampersandmonospace=65286;e.ampersandsmall=63270;e.amsquare=13250;e.anbopomofo=12578;e.angbopomofo=12580;e.angbracketleft=12296;e.angbracketright=12297;e.angkhankhuthai=3674;e.angle=8736;e.anglebracketleft=12296;e.anglebracketleftvertical=65087;e.anglebracketright=12297;e.anglebracketrightvertical=65088;e.angleleft=9001;e.angleright=9002;e.angstrom=8491;e.anoteleia=903;e.anudattadeva=2386;e.anusvarabengali=2434;e.anusvaradeva=2306;e.anusvaragujarati=2690;e.aogonek=261;e.apaatosquare=13056;e.aparen=9372;e.apostrophearmenian=1370;e.apostrophemod=700;e.apple=63743;e.approaches=8784;e.approxequal=8776;e.approxequalorimage=8786;e.approximatelyequal=8773;e.araeaekorean=12686;e.araeakorean=12685;e.arc=8978;e.arighthalfring=7834;e.aring=229;e.aringacute=507;e.aringbelow=7681;e.arrowboth=8596;e.arrowdashdown=8675;e.arrowdashleft=8672;e.arrowdashright=8674;e.arrowdashup=8673;e.arrowdblboth=8660;e.arrowdbldown=8659;e.arrowdblleft=8656;e.arrowdblright=8658;e.arrowdblup=8657;e.arrowdown=8595;e.arrowdownleft=8601;e.arrowdownright=8600;e.arrowdownwhite=8681;e.arrowheaddownmod=709;e.arrowheadleftmod=706;e.arrowheadrightmod=707;e.arrowheadupmod=708;e.arrowhorizex=63719;e.arrowleft=8592;e.arrowleftdbl=8656;e.arrowleftdblstroke=8653;e.arrowleftoverright=8646;e.arrowleftwhite=8678;e.arrowright=8594;e.arrowrightdblstroke=8655;e.arrowrightheavy=10142;e.arrowrightoverleft=8644;e.arrowrightwhite=8680;e.arrowtableft=8676;e.arrowtabright=8677;e.arrowup=8593;e.arrowupdn=8597;e.arrowupdnbse=8616;e.arrowupdownbase=8616;e.arrowupleft=8598;e.arrowupleftofdown=8645;e.arrowupright=8599;e.arrowupwhite=8679;e.arrowvertex=63718;e.asciicircum=94;e.asciicircummonospace=65342;e.asciitilde=126;e.asciitildemonospace=65374;e.ascript=593;e.ascriptturned=594;e.asmallhiragana=12353;e.asmallkatakana=12449;e.asmallkatakanahalfwidth=65383;e.asterisk=42;e.asteriskaltonearabic=1645;e.asteriskarabic=1645;e.asteriskmath=8727;e.asteriskmonospace=65290;e.asterisksmall=65121;e.asterism=8258;e.asuperior=63209;e.asymptoticallyequal=8771;e.at=64;e.atilde=227;e.atmonospace=65312;e.atsmall=65131;e.aturned=592;e.aubengali=2452;e.aubopomofo=12576;e.audeva=2324;e.augujarati=2708;e.augurmukhi=2580;e.aulengthmarkbengali=2519;e.aumatragurmukhi=2636;e.auvowelsignbengali=2508;e.auvowelsigndeva=2380;e.auvowelsigngujarati=2764;e.avagrahadeva=2365;e.aybarmenian=1377;e.ayin=1506;e.ayinaltonehebrew=64288;e.ayinhebrew=1506;e.b=98;e.babengali=2476;e.backslash=92;e.backslashmonospace=65340;e.badeva=2348;e.bagujarati=2732;e.bagurmukhi=2604;e.bahiragana=12400;e.bahtthai=3647;e.bakatakana=12496;e.bar=124;e.barmonospace=65372;e.bbopomofo=12549;e.bcircle=9425;e.bdotaccent=7683;e.bdotbelow=7685;e.beamedsixteenthnotes=9836;e.because=8757;e.becyrillic=1073;e.beharabic=1576;e.behfinalarabic=65168;e.behinitialarabic=65169;e.behiragana=12409;e.behmedialarabic=65170;e.behmeeminitialarabic=64671;e.behmeemisolatedarabic=64520;e.behnoonfinalarabic=64621;e.bekatakana=12505;e.benarmenian=1378;e.bet=1489;e.beta=946;e.betasymbolgreek=976;e.betdagesh=64305;e.betdageshhebrew=64305;e.bethebrew=1489;e.betrafehebrew=64332;e.bhabengali=2477;e.bhadeva=2349;e.bhagujarati=2733;e.bhagurmukhi=2605;e.bhook=595;e.bihiragana=12403;e.bikatakana=12499;e.bilabialclick=664;e.bindigurmukhi=2562;e.birusquare=13105;e.blackcircle=9679;e.blackdiamond=9670;e.blackdownpointingtriangle=9660;e.blackleftpointingpointer=9668;e.blackleftpointingtriangle=9664;e.blacklenticularbracketleft=12304;e.blacklenticularbracketleftvertical=65083;e.blacklenticularbracketright=12305;e.blacklenticularbracketrightvertical=65084;e.blacklowerlefttriangle=9699;e.blacklowerrighttriangle=9698;e.blackrectangle=9644;e.blackrightpointingpointer=9658;e.blackrightpointingtriangle=9654;e.blacksmallsquare=9642;e.blacksmilingface=9787;e.blacksquare=9632;e.blackstar=9733;e.blackupperlefttriangle=9700;e.blackupperrighttriangle=9701;e.blackuppointingsmalltriangle=9652;e.blackuppointingtriangle=9650;e.blank=9251;e.blinebelow=7687;e.block=9608;e.bmonospace=65346;e.bobaimaithai=3610;e.bohiragana=12412;e.bokatakana=12508;e.bparen=9373;e.bqsquare=13251;e.braceex=63732;e.braceleft=123;e.braceleftbt=63731;e.braceleftmid=63730;e.braceleftmonospace=65371;e.braceleftsmall=65115;e.bracelefttp=63729;e.braceleftvertical=65079;e.braceright=125;e.bracerightbt=63742;e.bracerightmid=63741;e.bracerightmonospace=65373;e.bracerightsmall=65116;e.bracerighttp=63740;e.bracerightvertical=65080;e.bracketleft=91;e.bracketleftbt=63728;e.bracketleftex=63727;e.bracketleftmonospace=65339;e.bracketlefttp=63726;e.bracketright=93;e.bracketrightbt=63739;e.bracketrightex=63738;e.bracketrightmonospace=65341;e.bracketrighttp=63737;e.breve=728;e.brevebelowcmb=814;e.brevecmb=774;e.breveinvertedbelowcmb=815;e.breveinvertedcmb=785;e.breveinverteddoublecmb=865;e.bridgebelowcmb=810;e.bridgeinvertedbelowcmb=826;e.brokenbar=166;e.bstroke=384;e.bsuperior=63210;e.btopbar=387;e.buhiragana=12406;e.bukatakana=12502;e.bullet=8226;e.bulletinverse=9688;e.bulletoperator=8729;e.bullseye=9678;e.c=99;e.caarmenian=1390;e.cabengali=2458;e.cacute=263;e.cadeva=2330;e.cagujarati=2714;e.cagurmukhi=2586;e.calsquare=13192;e.candrabindubengali=2433;e.candrabinducmb=784;e.candrabindudeva=2305;e.candrabindugujarati=2689;e.capslock=8682;e.careof=8453;e.caron=711;e.caronbelowcmb=812;e.caroncmb=780;e.carriagereturn=8629;e.cbopomofo=12568;e.ccaron=269;e.ccedilla=231;e.ccedillaacute=7689;e.ccircle=9426;e.ccircumflex=265;e.ccurl=597;e.cdot=267;e.cdotaccent=267;e.cdsquare=13253;e.cedilla=184;e.cedillacmb=807;e.cent=162;e.centigrade=8451;e.centinferior=63199;e.centmonospace=65504;e.centoldstyle=63394;e.centsuperior=63200;e.chaarmenian=1401;e.chabengali=2459;e.chadeva=2331;e.chagujarati=2715;e.chagurmukhi=2587;e.chbopomofo=12564;e.cheabkhasiancyrillic=1213;e.checkmark=10003;e.checyrillic=1095;e.chedescenderabkhasiancyrillic=1215;e.chedescendercyrillic=1207;e.chedieresiscyrillic=1269;e.cheharmenian=1395;e.chekhakassiancyrillic=1228;e.cheverticalstrokecyrillic=1209;e.chi=967;e.chieuchacirclekorean=12919;e.chieuchaparenkorean=12823;e.chieuchcirclekorean=12905;e.chieuchkorean=12618;e.chieuchparenkorean=12809;e.chochangthai=3594;e.chochanthai=3592;e.chochingthai=3593;e.chochoethai=3596;e.chook=392;e.cieucacirclekorean=12918;e.cieucaparenkorean=12822;e.cieuccirclekorean=12904;e.cieuckorean=12616;e.cieucparenkorean=12808;e.cieucuparenkorean=12828;e.circle=9675;e.circlecopyrt=169;e.circlemultiply=8855;e.circleot=8857;e.circleplus=8853;e.circlepostalmark=12342;e.circlewithlefthalfblack=9680;e.circlewithrighthalfblack=9681;e.circumflex=710;e.circumflexbelowcmb=813;e.circumflexcmb=770;e.clear=8999;e.clickalveolar=450;e.clickdental=448;e.clicklateral=449;e.clickretroflex=451;e.club=9827;e.clubsuitblack=9827;e.clubsuitwhite=9831;e.cmcubedsquare=13220;e.cmonospace=65347;e.cmsquaredsquare=13216;e.coarmenian=1409;e.colon=58;e.colonmonetary=8353;e.colonmonospace=65306;e.colonsign=8353;e.colonsmall=65109;e.colontriangularhalfmod=721;e.colontriangularmod=720;e.comma=44;e.commaabovecmb=787;e.commaaboverightcmb=789;e.commaaccent=63171;e.commaarabic=1548;e.commaarmenian=1373;e.commainferior=63201;e.commamonospace=65292;e.commareversedabovecmb=788;e.commareversedmod=701;e.commasmall=65104;e.commasuperior=63202;e.commaturnedabovecmb=786;e.commaturnedmod=699;e.compass=9788;e.congruent=8773;e.contourintegral=8750;e.control=8963;e.controlACK=6;e.controlBEL=7;e.controlBS=8;e.controlCAN=24;e.controlCR=13;e.controlDC1=17;e.controlDC2=18;e.controlDC3=19;e.controlDC4=20;e.controlDEL=127;e.controlDLE=16;e.controlEM=25;e.controlENQ=5;e.controlEOT=4;e.controlESC=27;e.controlETB=23;e.controlETX=3;e.controlFF=12;e.controlFS=28;e.controlGS=29;e.controlHT=9;e.controlLF=10;e.controlNAK=21;e.controlNULL=0;e.controlRS=30;e.controlSI=15;e.controlSO=14;e.controlSOT=2;e.controlSTX=1;e.controlSUB=26;e.controlSYN=22;e.controlUS=31;e.controlVT=11;e.copyright=169;e.copyrightsans=63721;e.copyrightserif=63193;e.cornerbracketleft=12300;e.cornerbracketlefthalfwidth=65378;e.cornerbracketleftvertical=65089;e.cornerbracketright=12301;e.cornerbracketrighthalfwidth=65379;e.cornerbracketrightvertical=65090;e.corporationsquare=13183;e.cosquare=13255;e.coverkgsquare=13254;e.cparen=9374;e.cruzeiro=8354;e.cstretched=663;e.curlyand=8911;e.curlyor=8910;e.currency=164;e.cyrBreve=63185;e.cyrFlex=63186;e.cyrbreve=63188;e.cyrflex=63189;e.d=100;e.daarmenian=1380;e.dabengali=2470;e.dadarabic=1590;e.dadeva=2342;e.dadfinalarabic=65214;e.dadinitialarabic=65215;e.dadmedialarabic=65216;e.dagesh=1468;e.dageshhebrew=1468;e.dagger=8224;e.daggerdbl=8225;e.dagujarati=2726;e.dagurmukhi=2598;e.dahiragana=12384;e.dakatakana=12480;e.dalarabic=1583;e.dalet=1491;e.daletdagesh=64307;e.daletdageshhebrew=64307;e.dalethebrew=1491;e.dalfinalarabic=65194;e.dammaarabic=1615;e.dammalowarabic=1615;e.dammatanaltonearabic=1612;e.dammatanarabic=1612;e.danda=2404;e.dargahebrew=1447;e.dargalefthebrew=1447;e.dasiapneumatacyrilliccmb=1157;e.dblGrave=63187;e.dblanglebracketleft=12298;e.dblanglebracketleftvertical=65085;e.dblanglebracketright=12299;e.dblanglebracketrightvertical=65086;e.dblarchinvertedbelowcmb=811;e.dblarrowleft=8660;e.dblarrowright=8658;e.dbldanda=2405;e.dblgrave=63190;e.dblgravecmb=783;e.dblintegral=8748;e.dbllowline=8215;e.dbllowlinecmb=819;e.dbloverlinecmb=831;e.dblprimemod=698;e.dblverticalbar=8214;e.dblverticallineabovecmb=782;e.dbopomofo=12553;e.dbsquare=13256;e.dcaron=271;e.dcedilla=7697;e.dcircle=9427;e.dcircumflexbelow=7699;e.dcroat=273;e.ddabengali=2465;e.ddadeva=2337;e.ddagujarati=2721;e.ddagurmukhi=2593;e.ddalarabic=1672;e.ddalfinalarabic=64393;e.dddhadeva=2396;e.ddhabengali=2466;e.ddhadeva=2338;e.ddhagujarati=2722;e.ddhagurmukhi=2594;e.ddotaccent=7691;e.ddotbelow=7693;e.decimalseparatorarabic=1643;e.decimalseparatorpersian=1643;e.decyrillic=1076;e.degree=176;e.dehihebrew=1453;e.dehiragana=12391;e.deicoptic=1007;e.dekatakana=12487;e.deleteleft=9003;e.deleteright=8998;e.delta=948;e.deltaturned=397;e.denominatorminusonenumeratorbengali=2552;e.dezh=676;e.dhabengali=2471;e.dhadeva=2343;e.dhagujarati=2727;e.dhagurmukhi=2599;e.dhook=599;e.dialytikatonos=901;e.dialytikatonoscmb=836;e.diamond=9830;e.diamondsuitwhite=9826;e.dieresis=168;e.dieresisacute=63191;e.dieresisbelowcmb=804;e.dieresiscmb=776;e.dieresisgrave=63192;e.dieresistonos=901;e.dihiragana=12386;e.dikatakana=12482;e.dittomark=12291;e.divide=247;e.divides=8739;e.divisionslash=8725;e.djecyrillic=1106;e.dkshade=9619;e.dlinebelow=7695;e.dlsquare=13207;e.dmacron=273;e.dmonospace=65348;e.dnblock=9604;e.dochadathai=3598;e.dodekthai=3604;e.dohiragana=12393;e.dokatakana=12489;e.dollar=36;e.dollarinferior=63203;e.dollarmonospace=65284;e.dollaroldstyle=63268;e.dollarsmall=65129;e.dollarsuperior=63204;e.dong=8363;e.dorusquare=13094;e.dotaccent=729;e.dotaccentcmb=775;e.dotbelowcmb=803;e.dotbelowcomb=803;e.dotkatakana=12539;e.dotlessi=305;e.dotlessj=63166;e.dotlessjstrokehook=644;e.dotmath=8901;e.dottedcircle=9676;e.doubleyodpatah=64287;e.doubleyodpatahhebrew=64287;e.downtackbelowcmb=798;e.downtackmod=725;e.dparen=9375;e.dsuperior=63211;e.dtail=598;e.dtopbar=396;e.duhiragana=12389;e.dukatakana=12485;e.dz=499;e.dzaltone=675;e.dzcaron=454;e.dzcurl=677;e.dzeabkhasiancyrillic=1249;e.dzecyrillic=1109;e.dzhecyrillic=1119;e.e=101;e.eacute=233;e.earth=9793;e.ebengali=2447;e.ebopomofo=12572;e.ebreve=277;e.ecandradeva=2317;e.ecandragujarati=2701;e.ecandravowelsigndeva=2373;e.ecandravowelsigngujarati=2757;e.ecaron=283;e.ecedillabreve=7709;e.echarmenian=1381;e.echyiwnarmenian=1415;e.ecircle=9428;e.ecircumflex=234;e.ecircumflexacute=7871;e.ecircumflexbelow=7705;e.ecircumflexdotbelow=7879;e.ecircumflexgrave=7873;e.ecircumflexhookabove=7875;e.ecircumflextilde=7877;e.ecyrillic=1108;e.edblgrave=517;e.edeva=2319;e.edieresis=235;e.edot=279;e.edotaccent=279;e.edotbelow=7865;e.eegurmukhi=2575;e.eematragurmukhi=2631;e.efcyrillic=1092;e.egrave=232;e.egujarati=2703;e.eharmenian=1383;e.ehbopomofo=12573;e.ehiragana=12360;e.ehookabove=7867;e.eibopomofo=12575;e.eight=56;e.eightarabic=1640;e.eightbengali=2542;e.eightcircle=9319;e.eightcircleinversesansserif=10129;e.eightdeva=2414;e.eighteencircle=9329;e.eighteenparen=9349;e.eighteenperiod=9369;e.eightgujarati=2798;e.eightgurmukhi=2670;e.eighthackarabic=1640;e.eighthangzhou=12328;e.eighthnotebeamed=9835;e.eightideographicparen=12839;e.eightinferior=8328;e.eightmonospace=65304;e.eightoldstyle=63288;e.eightparen=9339;e.eightperiod=9359;e.eightpersian=1784;e.eightroman=8567;e.eightsuperior=8312;e.eightthai=3672;e.einvertedbreve=519;e.eiotifiedcyrillic=1125;e.ekatakana=12456;e.ekatakanahalfwidth=65396;e.ekonkargurmukhi=2676;e.ekorean=12628;e.elcyrillic=1083;e.element=8712;e.elevencircle=9322;e.elevenparen=9342;e.elevenperiod=9362;e.elevenroman=8570;e.ellipsis=8230;e.ellipsisvertical=8942;e.emacron=275;e.emacronacute=7703;e.emacrongrave=7701;e.emcyrillic=1084;e.emdash=8212;e.emdashvertical=65073;e.emonospace=65349;e.emphasismarkarmenian=1371;e.emptyset=8709;e.enbopomofo=12579;e.encyrillic=1085;e.endash=8211;e.endashvertical=65074;e.endescendercyrillic=1187;e.eng=331;e.engbopomofo=12581;e.enghecyrillic=1189;e.enhookcyrillic=1224;e.enspace=8194;e.eogonek=281;e.eokorean=12627;e.eopen=603;e.eopenclosed=666;e.eopenreversed=604;e.eopenreversedclosed=606;e.eopenreversedhook=605;e.eparen=9376;e.epsilon=949;e.epsilontonos=941;e.equal=61;e.equalmonospace=65309;e.equalsmall=65126;e.equalsuperior=8316;e.equivalence=8801;e.erbopomofo=12582;e.ercyrillic=1088;e.ereversed=600;e.ereversedcyrillic=1101;e.escyrillic=1089;e.esdescendercyrillic=1195;e.esh=643;e.eshcurl=646;e.eshortdeva=2318;e.eshortvowelsigndeva=2374;e.eshreversedloop=426;e.eshsquatreversed=645;e.esmallhiragana=12359;e.esmallkatakana=12455;e.esmallkatakanahalfwidth=65386;e.estimated=8494;e.esuperior=63212;e.eta=951;e.etarmenian=1384;e.etatonos=942;e.eth=240;e.etilde=7869;e.etildebelow=7707;e.etnahtafoukhhebrew=1425;e.etnahtafoukhlefthebrew=1425;e.etnahtahebrew=1425;e.etnahtalefthebrew=1425;e.eturned=477;e.eukorean=12641;e.euro=8364;e.evowelsignbengali=2503;e.evowelsigndeva=2375;e.evowelsigngujarati=2759;e.exclam=33;e.exclamarmenian=1372;e.exclamdbl=8252;e.exclamdown=161;e.exclamdownsmall=63393;e.exclammonospace=65281;e.exclamsmall=63265;e.existential=8707;e.ezh=658;e.ezhcaron=495;e.ezhcurl=659;e.ezhreversed=441;e.ezhtail=442;e.f=102;e.fadeva=2398;e.fagurmukhi=2654;e.fahrenheit=8457;e.fathaarabic=1614;e.fathalowarabic=1614;e.fathatanarabic=1611;e.fbopomofo=12552;e.fcircle=9429;e.fdotaccent=7711;e.feharabic=1601;e.feharmenian=1414;e.fehfinalarabic=65234;e.fehinitialarabic=65235;e.fehmedialarabic=65236;e.feicoptic=997;e.female=9792;e.ff=64256;e.f_f=64256;e.ffi=64259;e.f_f_i=64259;e.ffl=64260;e.f_f_l=64260;e.fi=64257;e.f_i=64257;e.fifteencircle=9326;e.fifteenparen=9346;e.fifteenperiod=9366;e.figuredash=8210;e.filledbox=9632;e.filledrect=9644;e.finalkaf=1498;e.finalkafdagesh=64314;e.finalkafdageshhebrew=64314;e.finalkafhebrew=1498;e.finalmem=1501;e.finalmemhebrew=1501;e.finalnun=1503;e.finalnunhebrew=1503;e.finalpe=1507;e.finalpehebrew=1507;e.finaltsadi=1509;e.finaltsadihebrew=1509;e.firsttonechinese=713;e.fisheye=9673;e.fitacyrillic=1139;e.five=53;e.fivearabic=1637;e.fivebengali=2539;e.fivecircle=9316;e.fivecircleinversesansserif=10126;e.fivedeva=2411;e.fiveeighths=8541;e.fivegujarati=2795;e.fivegurmukhi=2667;e.fivehackarabic=1637;e.fivehangzhou=12325;e.fiveideographicparen=12836;e.fiveinferior=8325;e.fivemonospace=65301;e.fiveoldstyle=63285;e.fiveparen=9336;e.fiveperiod=9356;e.fivepersian=1781;e.fiveroman=8564;e.fivesuperior=8309;e.fivethai=3669;e.fl=64258;e.f_l=64258;e.florin=402;e.fmonospace=65350;e.fmsquare=13209;e.fofanthai=3615;e.fofathai=3613;e.fongmanthai=3663;e.forall=8704;e.four=52;e.fourarabic=1636;e.fourbengali=2538;e.fourcircle=9315;e.fourcircleinversesansserif=10125;e.fourdeva=2410;e.fourgujarati=2794;e.fourgurmukhi=2666;e.fourhackarabic=1636;e.fourhangzhou=12324;e.fourideographicparen=12835;e.fourinferior=8324;e.fourmonospace=65300;e.fournumeratorbengali=2551;e.fouroldstyle=63284;e.fourparen=9335;e.fourperiod=9355;e.fourpersian=1780;e.fourroman=8563;e.foursuperior=8308;e.fourteencircle=9325;e.fourteenparen=9345;e.fourteenperiod=9365;e.fourthai=3668;e.fourthtonechinese=715;e.fparen=9377;e.fraction=8260;e.franc=8355;e.g=103;e.gabengali=2455;e.gacute=501;e.gadeva=2327;e.gafarabic=1711;e.gaffinalarabic=64403;e.gafinitialarabic=64404;e.gafmedialarabic=64405;e.gagujarati=2711;e.gagurmukhi=2583;e.gahiragana=12364;e.gakatakana=12460;e.gamma=947;e.gammalatinsmall=611;e.gammasuperior=736;e.gangiacoptic=1003;e.gbopomofo=12557;e.gbreve=287;e.gcaron=487;e.gcedilla=291;e.gcircle=9430;e.gcircumflex=285;e.gcommaaccent=291;e.gdot=289;e.gdotaccent=289;e.gecyrillic=1075;e.gehiragana=12370;e.gekatakana=12466;e.geometricallyequal=8785;e.gereshaccenthebrew=1436;e.gereshhebrew=1523;e.gereshmuqdamhebrew=1437;e.germandbls=223;e.gershayimaccenthebrew=1438;e.gershayimhebrew=1524;e.getamark=12307;e.ghabengali=2456;e.ghadarmenian=1394;e.ghadeva=2328;e.ghagujarati=2712;e.ghagurmukhi=2584;e.ghainarabic=1594;e.ghainfinalarabic=65230;e.ghaininitialarabic=65231;e.ghainmedialarabic=65232;e.ghemiddlehookcyrillic=1173;e.ghestrokecyrillic=1171;e.gheupturncyrillic=1169;e.ghhadeva=2394;e.ghhagurmukhi=2650;e.ghook=608;e.ghzsquare=13203;e.gihiragana=12366;e.gikatakana=12462;e.gimarmenian=1379;e.gimel=1490;e.gimeldagesh=64306;e.gimeldageshhebrew=64306;e.gimelhebrew=1490;e.gjecyrillic=1107;e.glottalinvertedstroke=446;e.glottalstop=660;e.glottalstopinverted=662;e.glottalstopmod=704;e.glottalstopreversed=661;e.glottalstopreversedmod=705;e.glottalstopreversedsuperior=740;e.glottalstopstroke=673;e.glottalstopstrokereversed=674;e.gmacron=7713;e.gmonospace=65351;e.gohiragana=12372;e.gokatakana=12468;e.gparen=9378;e.gpasquare=13228;e.gradient=8711;e.grave=96;e.gravebelowcmb=790;e.gravecmb=768;e.gravecomb=768;e.gravedeva=2387;e.gravelowmod=718;e.gravemonospace=65344;e.gravetonecmb=832;e.greater=62;e.greaterequal=8805;e.greaterequalorless=8923;e.greatermonospace=65310;e.greaterorequivalent=8819;e.greaterorless=8823;e.greateroverequal=8807;e.greatersmall=65125;e.gscript=609;e.gstroke=485;e.guhiragana=12368;e.guillemotleft=171;e.guillemotright=187;e.guilsinglleft=8249;e.guilsinglright=8250;e.gukatakana=12464;e.guramusquare=13080;e.gysquare=13257;e.h=104;e.haabkhasiancyrillic=1193;e.haaltonearabic=1729;e.habengali=2489;e.hadescendercyrillic=1203;e.hadeva=2361;e.hagujarati=2745;e.hagurmukhi=2617;e.haharabic=1581;e.hahfinalarabic=65186;e.hahinitialarabic=65187;e.hahiragana=12399;e.hahmedialarabic=65188;e.haitusquare=13098;e.hakatakana=12495;e.hakatakanahalfwidth=65418;e.halantgurmukhi=2637;e.hamzaarabic=1569;e.hamzalowarabic=1569;e.hangulfiller=12644;e.hardsigncyrillic=1098;e.harpoonleftbarbup=8636;e.harpoonrightbarbup=8640;e.hasquare=13258;e.hatafpatah=1458;e.hatafpatah16=1458;e.hatafpatah23=1458;e.hatafpatah2f=1458;e.hatafpatahhebrew=1458;e.hatafpatahnarrowhebrew=1458;e.hatafpatahquarterhebrew=1458;e.hatafpatahwidehebrew=1458;e.hatafqamats=1459;e.hatafqamats1b=1459;e.hatafqamats28=1459;e.hatafqamats34=1459;e.hatafqamatshebrew=1459;e.hatafqamatsnarrowhebrew=1459;e.hatafqamatsquarterhebrew=1459;e.hatafqamatswidehebrew=1459;e.hatafsegol=1457;e.hatafsegol17=1457;e.hatafsegol24=1457;e.hatafsegol30=1457;e.hatafsegolhebrew=1457;e.hatafsegolnarrowhebrew=1457;e.hatafsegolquarterhebrew=1457;e.hatafsegolwidehebrew=1457;e.hbar=295;e.hbopomofo=12559;e.hbrevebelow=7723;e.hcedilla=7721;e.hcircle=9431;e.hcircumflex=293;e.hdieresis=7719;e.hdotaccent=7715;e.hdotbelow=7717;e.he=1492;e.heart=9829;e.heartsuitblack=9829;e.heartsuitwhite=9825;e.hedagesh=64308;e.hedageshhebrew=64308;e.hehaltonearabic=1729;e.heharabic=1607;e.hehebrew=1492;e.hehfinalaltonearabic=64423;e.hehfinalalttwoarabic=65258;e.hehfinalarabic=65258;e.hehhamzaabovefinalarabic=64421;e.hehhamzaaboveisolatedarabic=64420;e.hehinitialaltonearabic=64424;e.hehinitialarabic=65259;e.hehiragana=12408;e.hehmedialaltonearabic=64425;e.hehmedialarabic=65260;e.heiseierasquare=13179;e.hekatakana=12504;e.hekatakanahalfwidth=65421;e.hekutaarusquare=13110;e.henghook=615;e.herutusquare=13113;e.het=1495;e.hethebrew=1495;e.hhook=614;e.hhooksuperior=689;e.hieuhacirclekorean=12923;e.hieuhaparenkorean=12827;e.hieuhcirclekorean=12909;e.hieuhkorean=12622;e.hieuhparenkorean=12813;e.hihiragana=12402;e.hikatakana=12498;e.hikatakanahalfwidth=65419;e.hiriq=1460;e.hiriq14=1460;e.hiriq21=1460;e.hiriq2d=1460;e.hiriqhebrew=1460;e.hiriqnarrowhebrew=1460;e.hiriqquarterhebrew=1460;e.hiriqwidehebrew=1460;e.hlinebelow=7830;e.hmonospace=65352;e.hoarmenian=1392;e.hohipthai=3627;e.hohiragana=12411;e.hokatakana=12507;e.hokatakanahalfwidth=65422;e.holam=1465;e.holam19=1465;e.holam26=1465;e.holam32=1465;e.holamhebrew=1465;e.holamnarrowhebrew=1465;e.holamquarterhebrew=1465;e.holamwidehebrew=1465;e.honokhukthai=3630;e.hookabovecomb=777;e.hookcmb=777;e.hookpalatalizedbelowcmb=801;e.hookretroflexbelowcmb=802;e.hoonsquare=13122;e.horicoptic=1001;e.horizontalbar=8213;e.horncmb=795;e.hotsprings=9832;e.house=8962;e.hparen=9379;e.hsuperior=688;e.hturned=613;e.huhiragana=12405;e.huiitosquare=13107;e.hukatakana=12501;e.hukatakanahalfwidth=65420;e.hungarumlaut=733;e.hungarumlautcmb=779;e.hv=405;e.hyphen=45;e.hypheninferior=63205;e.hyphenmonospace=65293;e.hyphensmall=65123;e.hyphensuperior=63206;e.hyphentwo=8208;e.i=105;e.iacute=237;e.iacyrillic=1103;e.ibengali=2439;e.ibopomofo=12583;e.ibreve=301;e.icaron=464;e.icircle=9432;e.icircumflex=238;e.icyrillic=1110;e.idblgrave=521;e.ideographearthcircle=12943;e.ideographfirecircle=12939;e.ideographicallianceparen=12863;e.ideographiccallparen=12858;e.ideographiccentrecircle=12965;e.ideographicclose=12294;e.ideographiccomma=12289;e.ideographiccommaleft=65380;e.ideographiccongratulationparen=12855;e.ideographiccorrectcircle=12963;e.ideographicearthparen=12847;e.ideographicenterpriseparen=12861;e.ideographicexcellentcircle=12957;e.ideographicfestivalparen=12864;e.ideographicfinancialcircle=12950;e.ideographicfinancialparen=12854;e.ideographicfireparen=12843;e.ideographichaveparen=12850;e.ideographichighcircle=12964;e.ideographiciterationmark=12293;e.ideographiclaborcircle=12952;e.ideographiclaborparen=12856;e.ideographicleftcircle=12967;e.ideographiclowcircle=12966;e.ideographicmedicinecircle=12969;e.ideographicmetalparen=12846;e.ideographicmoonparen=12842;e.ideographicnameparen=12852;e.ideographicperiod=12290;e.ideographicprintcircle=12958;e.ideographicreachparen=12867;e.ideographicrepresentparen=12857;e.ideographicresourceparen=12862;e.ideographicrightcircle=12968;e.ideographicsecretcircle=12953;e.ideographicselfparen=12866;e.ideographicsocietyparen=12851;e.ideographicspace=12288;e.ideographicspecialparen=12853;e.ideographicstockparen=12849;e.ideographicstudyparen=12859;e.ideographicsunparen=12848;e.ideographicsuperviseparen=12860;e.ideographicwaterparen=12844;e.ideographicwoodparen=12845;e.ideographiczero=12295;e.ideographmetalcircle=12942;e.ideographmooncircle=12938;e.ideographnamecircle=12948;e.ideographsuncircle=12944;e.ideographwatercircle=12940;e.ideographwoodcircle=12941;e.ideva=2311;e.idieresis=239;e.idieresisacute=7727;e.idieresiscyrillic=1253;e.idotbelow=7883;e.iebrevecyrillic=1239;e.iecyrillic=1077;e.ieungacirclekorean=12917;e.ieungaparenkorean=12821;e.ieungcirclekorean=12903;e.ieungkorean=12615;e.ieungparenkorean=12807;e.igrave=236;e.igujarati=2695;e.igurmukhi=2567;e.ihiragana=12356;e.ihookabove=7881;e.iibengali=2440;e.iicyrillic=1080;e.iideva=2312;e.iigujarati=2696;e.iigurmukhi=2568;e.iimatragurmukhi=2624;e.iinvertedbreve=523;e.iishortcyrillic=1081;e.iivowelsignbengali=2496;e.iivowelsigndeva=2368;e.iivowelsigngujarati=2752;e.ij=307;e.ikatakana=12452;e.ikatakanahalfwidth=65394;e.ikorean=12643;e.ilde=732;e.iluyhebrew=1452;e.imacron=299;e.imacroncyrillic=1251;e.imageorapproximatelyequal=8787;e.imatragurmukhi=2623;e.imonospace=65353;e.increment=8710;e.infinity=8734;e.iniarmenian=1387;e.integral=8747;e.integralbottom=8993;e.integralbt=8993;e.integralex=63733;e.integraltop=8992;e.integraltp=8992;e.intersection=8745;e.intisquare=13061;e.invbullet=9688;e.invcircle=9689;e.invsmileface=9787;e.iocyrillic=1105;e.iogonek=303;e.iota=953;e.iotadieresis=970;e.iotadieresistonos=912;e.iotalatin=617;e.iotatonos=943;e.iparen=9380;e.irigurmukhi=2674;e.ismallhiragana=12355;e.ismallkatakana=12451;e.ismallkatakanahalfwidth=65384;e.issharbengali=2554;e.istroke=616;e.isuperior=63213;e.iterationhiragana=12445;e.iterationkatakana=12541;e.itilde=297;e.itildebelow=7725;e.iubopomofo=12585;e.iucyrillic=1102;e.ivowelsignbengali=2495;e.ivowelsigndeva=2367;e.ivowelsigngujarati=2751;e.izhitsacyrillic=1141;e.izhitsadblgravecyrillic=1143;e.j=106;e.jaarmenian=1393;e.jabengali=2460;e.jadeva=2332;e.jagujarati=2716;e.jagurmukhi=2588;e.jbopomofo=12560;e.jcaron=496;e.jcircle=9433;e.jcircumflex=309;e.jcrossedtail=669;e.jdotlessstroke=607;e.jecyrillic=1112;e.jeemarabic=1580;e.jeemfinalarabic=65182;e.jeeminitialarabic=65183;e.jeemmedialarabic=65184;e.jeharabic=1688;e.jehfinalarabic=64395;e.jhabengali=2461;e.jhadeva=2333;e.jhagujarati=2717;e.jhagurmukhi=2589;e.jheharmenian=1403;e.jis=12292;e.jmonospace=65354;e.jparen=9381;e.jsuperior=690;e.k=107;e.kabashkircyrillic=1185;e.kabengali=2453;e.kacute=7729;e.kacyrillic=1082;e.kadescendercyrillic=1179;e.kadeva=2325;e.kaf=1499;e.kafarabic=1603;e.kafdagesh=64315;e.kafdageshhebrew=64315;e.kaffinalarabic=65242;e.kafhebrew=1499;e.kafinitialarabic=65243;e.kafmedialarabic=65244;e.kafrafehebrew=64333;e.kagujarati=2709;e.kagurmukhi=2581;e.kahiragana=12363;e.kahookcyrillic=1220;e.kakatakana=12459;e.kakatakanahalfwidth=65398;e.kappa=954;e.kappasymbolgreek=1008;e.kapyeounmieumkorean=12657;e.kapyeounphieuphkorean=12676;e.kapyeounpieupkorean=12664;e.kapyeounssangpieupkorean=12665;e.karoriisquare=13069;e.kashidaautoarabic=1600;e.kashidaautonosidebearingarabic=1600;e.kasmallkatakana=12533;e.kasquare=13188;e.kasraarabic=1616;e.kasratanarabic=1613;e.kastrokecyrillic=1183;e.katahiraprolongmarkhalfwidth=65392;e.kaverticalstrokecyrillic=1181;e.kbopomofo=12558;e.kcalsquare=13193;e.kcaron=489;e.kcedilla=311;e.kcircle=9434;e.kcommaaccent=311;e.kdotbelow=7731;e.keharmenian=1412;e.kehiragana=12369;e.kekatakana=12465;e.kekatakanahalfwidth=65401;e.kenarmenian=1391;e.kesmallkatakana=12534;e.kgreenlandic=312;e.khabengali=2454;e.khacyrillic=1093;e.khadeva=2326;e.khagujarati=2710;e.khagurmukhi=2582;e.khaharabic=1582;e.khahfinalarabic=65190;e.khahinitialarabic=65191;e.khahmedialarabic=65192;e.kheicoptic=999;e.khhadeva=2393;e.khhagurmukhi=2649;e.khieukhacirclekorean=12920;e.khieukhaparenkorean=12824;e.khieukhcirclekorean=12906;e.khieukhkorean=12619;e.khieukhparenkorean=12810;e.khokhaithai=3586;e.khokhonthai=3589;e.khokhuatthai=3587;e.khokhwaithai=3588;e.khomutthai=3675;e.khook=409;e.khorakhangthai=3590;e.khzsquare=13201;e.kihiragana=12365;e.kikatakana=12461;e.kikatakanahalfwidth=65399;e.kiroguramusquare=13077;e.kiromeetorusquare=13078;e.kirosquare=13076;e.kiyeokacirclekorean=12910;e.kiyeokaparenkorean=12814;e.kiyeokcirclekorean=12896;e.kiyeokkorean=12593;e.kiyeokparenkorean=12800;e.kiyeoksioskorean=12595;e.kjecyrillic=1116;e.klinebelow=7733;e.klsquare=13208;e.kmcubedsquare=13222;e.kmonospace=65355;e.kmsquaredsquare=13218;e.kohiragana=12371;e.kohmsquare=13248;e.kokaithai=3585;e.kokatakana=12467;e.kokatakanahalfwidth=65402;e.kooposquare=13086;e.koppacyrillic=1153;e.koreanstandardsymbol=12927;e.koroniscmb=835;e.kparen=9382;e.kpasquare=13226;e.ksicyrillic=1135;e.ktsquare=13263;e.kturned=670;e.kuhiragana=12367;e.kukatakana=12463;e.kukatakanahalfwidth=65400;e.kvsquare=13240;e.kwsquare=13246;e.l=108;e.labengali=2482;e.lacute=314;e.ladeva=2354;e.lagujarati=2738;e.lagurmukhi=2610;e.lakkhangyaothai=3653;e.lamaleffinalarabic=65276;e.lamalefhamzaabovefinalarabic=65272;e.lamalefhamzaaboveisolatedarabic=65271;e.lamalefhamzabelowfinalarabic=65274;e.lamalefhamzabelowisolatedarabic=65273;e.lamalefisolatedarabic=65275;e.lamalefmaddaabovefinalarabic=65270;e.lamalefmaddaaboveisolatedarabic=65269;e.lamarabic=1604;e.lambda=955;e.lambdastroke=411;e.lamed=1500;e.lameddagesh=64316;e.lameddageshhebrew=64316;e.lamedhebrew=1500;e.lamfinalarabic=65246;e.lamhahinitialarabic=64714;e.laminitialarabic=65247;e.lamjeeminitialarabic=64713;e.lamkhahinitialarabic=64715;e.lamlamhehisolatedarabic=65010;e.lammedialarabic=65248;e.lammeemhahinitialarabic=64904;e.lammeeminitialarabic=64716;e.largecircle=9711;e.lbar=410;e.lbelt=620;e.lbopomofo=12556;e.lcaron=318;e.lcedilla=316;e.lcircle=9435;e.lcircumflexbelow=7741;e.lcommaaccent=316;e.ldot=320;e.ldotaccent=320;e.ldotbelow=7735;e.ldotbelowmacron=7737;e.leftangleabovecmb=794;e.lefttackbelowcmb=792;e.less=60;e.lessequal=8804;e.lessequalorgreater=8922;e.lessmonospace=65308;e.lessorequivalent=8818;e.lessorgreater=8822;e.lessoverequal=8806;e.lesssmall=65124;e.lezh=622;e.lfblock=9612;e.lhookretroflex=621;e.lira=8356;e.liwnarmenian=1388;e.lj=457;e.ljecyrillic=1113;e.ll=63168;e.lladeva=2355;e.llagujarati=2739;e.llinebelow=7739;e.llladeva=2356;e.llvocalicbengali=2529;e.llvocalicdeva=2401;e.llvocalicvowelsignbengali=2531;e.llvocalicvowelsigndeva=2403;e.lmiddletilde=619;e.lmonospace=65356;e.lmsquare=13264;e.lochulathai=3628;e.logicaland=8743;e.logicalnot=172;e.logicalnotreversed=8976;e.logicalor=8744;e.lolingthai=3621;e.longs=383;e.lowlinecenterline=65102;e.lowlinecmb=818;e.lowlinedashed=65101;e.lozenge=9674;e.lparen=9383;e.lslash=322;e.lsquare=8467;e.lsuperior=63214;e.ltshade=9617;e.luthai=3622;e.lvocalicbengali=2444;e.lvocalicdeva=2316;e.lvocalicvowelsignbengali=2530;e.lvocalicvowelsigndeva=2402;e.lxsquare=13267;e.m=109;e.mabengali=2478;e.macron=175;e.macronbelowcmb=817;e.macroncmb=772;e.macronlowmod=717;e.macronmonospace=65507;e.macute=7743;e.madeva=2350;e.magujarati=2734;e.magurmukhi=2606;e.mahapakhhebrew=1444;e.mahapakhlefthebrew=1444;e.mahiragana=12414;e.maichattawalowleftthai=63637;e.maichattawalowrightthai=63636;e.maichattawathai=3659;e.maichattawaupperleftthai=63635;e.maieklowleftthai=63628;e.maieklowrightthai=63627;e.maiekthai=3656;e.maiekupperleftthai=63626;e.maihanakatleftthai=63620;e.maihanakatthai=3633;e.maitaikhuleftthai=63625;e.maitaikhuthai=3655;e.maitholowleftthai=63631;e.maitholowrightthai=63630;e.maithothai=3657;e.maithoupperleftthai=63629;e.maitrilowleftthai=63634;e.maitrilowrightthai=63633;e.maitrithai=3658;e.maitriupperleftthai=63632;e.maiyamokthai=3654;e.makatakana=12510;e.makatakanahalfwidth=65423;e.male=9794;e.mansyonsquare=13127;e.maqafhebrew=1470;e.mars=9794;e.masoracirclehebrew=1455;e.masquare=13187;e.mbopomofo=12551;e.mbsquare=13268;e.mcircle=9436;e.mcubedsquare=13221;e.mdotaccent=7745;e.mdotbelow=7747;e.meemarabic=1605;e.meemfinalarabic=65250;e.meeminitialarabic=65251;e.meemmedialarabic=65252;e.meemmeeminitialarabic=64721;e.meemmeemisolatedarabic=64584;e.meetorusquare=13133;e.mehiragana=12417;e.meizierasquare=13182;e.mekatakana=12513;e.mekatakanahalfwidth=65426;e.mem=1502;e.memdagesh=64318;e.memdageshhebrew=64318;e.memhebrew=1502;e.menarmenian=1396;e.merkhahebrew=1445;e.merkhakefulahebrew=1446;e.merkhakefulalefthebrew=1446;e.merkhalefthebrew=1445;e.mhook=625;e.mhzsquare=13202;e.middledotkatakanahalfwidth=65381;e.middot=183;e.mieumacirclekorean=12914;e.mieumaparenkorean=12818;e.mieumcirclekorean=12900;e.mieumkorean=12609;e.mieumpansioskorean=12656;e.mieumparenkorean=12804;e.mieumpieupkorean=12654;e.mieumsioskorean=12655;e.mihiragana=12415;e.mikatakana=12511;e.mikatakanahalfwidth=65424;e.minus=8722;e.minusbelowcmb=800;e.minuscircle=8854;e.minusmod=727;e.minusplus=8723;e.minute=8242;e.miribaarusquare=13130;e.mirisquare=13129;e.mlonglegturned=624;e.mlsquare=13206;e.mmcubedsquare=13219;e.mmonospace=65357;e.mmsquaredsquare=13215;e.mohiragana=12418;e.mohmsquare=13249;e.mokatakana=12514;e.mokatakanahalfwidth=65427;e.molsquare=13270;e.momathai=3617;e.moverssquare=13223;e.moverssquaredsquare=13224;e.mparen=9384;e.mpasquare=13227;e.mssquare=13235;e.msuperior=63215;e.mturned=623;e.mu=181;e.mu1=181;e.muasquare=13186;e.muchgreater=8811;e.muchless=8810;e.mufsquare=13196;e.mugreek=956;e.mugsquare=13197;e.muhiragana=12416;e.mukatakana=12512;e.mukatakanahalfwidth=65425;e.mulsquare=13205;e.multiply=215;e.mumsquare=13211;e.munahhebrew=1443;e.munahlefthebrew=1443;e.musicalnote=9834;e.musicalnotedbl=9835;e.musicflatsign=9837;e.musicsharpsign=9839;e.mussquare=13234;e.muvsquare=13238;e.muwsquare=13244;e.mvmegasquare=13241;e.mvsquare=13239;e.mwmegasquare=13247;e.mwsquare=13245;e.n=110;e.nabengali=2472;e.nabla=8711;e.nacute=324;e.nadeva=2344;e.nagujarati=2728;e.nagurmukhi=2600;e.nahiragana=12394;e.nakatakana=12490;e.nakatakanahalfwidth=65413;e.napostrophe=329;e.nasquare=13185;e.nbopomofo=12555;e.nbspace=160;e.ncaron=328;e.ncedilla=326;e.ncircle=9437;e.ncircumflexbelow=7755;e.ncommaaccent=326;e.ndotaccent=7749;e.ndotbelow=7751;e.nehiragana=12397;e.nekatakana=12493;e.nekatakanahalfwidth=65416;e.newsheqelsign=8362;e.nfsquare=13195;e.ngabengali=2457;e.ngadeva=2329;e.ngagujarati=2713;e.ngagurmukhi=2585;e.ngonguthai=3591;e.nhiragana=12435;e.nhookleft=626;e.nhookretroflex=627;e.nieunacirclekorean=12911;e.nieunaparenkorean=12815;e.nieuncieuckorean=12597;e.nieuncirclekorean=12897;e.nieunhieuhkorean=12598;e.nieunkorean=12596;e.nieunpansioskorean=12648;e.nieunparenkorean=12801;e.nieunsioskorean=12647;e.nieuntikeutkorean=12646;e.nihiragana=12395;e.nikatakana=12491;e.nikatakanahalfwidth=65414;e.nikhahitleftthai=63641;e.nikhahitthai=3661;e.nine=57;e.ninearabic=1641;e.ninebengali=2543;e.ninecircle=9320;e.ninecircleinversesansserif=10130;e.ninedeva=2415;e.ninegujarati=2799;e.ninegurmukhi=2671;e.ninehackarabic=1641;e.ninehangzhou=12329;e.nineideographicparen=12840;e.nineinferior=8329;e.ninemonospace=65305;e.nineoldstyle=63289;e.nineparen=9340;e.nineperiod=9360;e.ninepersian=1785;e.nineroman=8568;e.ninesuperior=8313;e.nineteencircle=9330;e.nineteenparen=9350;e.nineteenperiod=9370;e.ninethai=3673;e.nj=460;e.njecyrillic=1114;e.nkatakana=12531;e.nkatakanahalfwidth=65437;e.nlegrightlong=414;e.nlinebelow=7753;e.nmonospace=65358;e.nmsquare=13210;e.nnabengali=2467;e.nnadeva=2339;e.nnagujarati=2723;e.nnagurmukhi=2595;e.nnnadeva=2345;e.nohiragana=12398;e.nokatakana=12494;e.nokatakanahalfwidth=65417;e.nonbreakingspace=160;e.nonenthai=3603;e.nonuthai=3609;e.noonarabic=1606;e.noonfinalarabic=65254;e.noonghunnaarabic=1722;e.noonghunnafinalarabic=64415;e.nooninitialarabic=65255;e.noonjeeminitialarabic=64722;e.noonjeemisolatedarabic=64587;e.noonmedialarabic=65256;e.noonmeeminitialarabic=64725;e.noonmeemisolatedarabic=64590;e.noonnoonfinalarabic=64653;e.notcontains=8716;e.notelement=8713;e.notelementof=8713;e.notequal=8800;e.notgreater=8815;e.notgreaternorequal=8817;e.notgreaternorless=8825;e.notidentical=8802;e.notless=8814;e.notlessnorequal=8816;e.notparallel=8742;e.notprecedes=8832;e.notsubset=8836;e.notsucceeds=8833;e.notsuperset=8837;e.nowarmenian=1398;e.nparen=9385;e.nssquare=13233;e.nsuperior=8319;e.ntilde=241;e.nu=957;e.nuhiragana=12396;e.nukatakana=12492;e.nukatakanahalfwidth=65415;e.nuktabengali=2492;e.nuktadeva=2364;e.nuktagujarati=2748;e.nuktagurmukhi=2620;e.numbersign=35;e.numbersignmonospace=65283;e.numbersignsmall=65119;e.numeralsigngreek=884;e.numeralsignlowergreek=885;e.numero=8470;e.nun=1504;e.nundagesh=64320;e.nundageshhebrew=64320;e.nunhebrew=1504;e.nvsquare=13237;e.nwsquare=13243;e.nyabengali=2462;e.nyadeva=2334;e.nyagujarati=2718;e.nyagurmukhi=2590;e.o=111;e.oacute=243;e.oangthai=3629;e.obarred=629;e.obarredcyrillic=1257;e.obarreddieresiscyrillic=1259;e.obengali=2451;e.obopomofo=12571;e.obreve=335;e.ocandradeva=2321;e.ocandragujarati=2705;e.ocandravowelsigndeva=2377;e.ocandravowelsigngujarati=2761;e.ocaron=466;e.ocircle=9438;e.ocircumflex=244;e.ocircumflexacute=7889;e.ocircumflexdotbelow=7897;e.ocircumflexgrave=7891;e.ocircumflexhookabove=7893;e.ocircumflextilde=7895;e.ocyrillic=1086;e.odblacute=337;e.odblgrave=525;e.odeva=2323;e.odieresis=246;e.odieresiscyrillic=1255;e.odotbelow=7885;e.oe=339;e.oekorean=12634;e.ogonek=731;e.ogonekcmb=808;e.ograve=242;e.ogujarati=2707;e.oharmenian=1413;e.ohiragana=12362;e.ohookabove=7887;e.ohorn=417;e.ohornacute=7899;e.ohorndotbelow=7907;e.ohorngrave=7901;e.ohornhookabove=7903;e.ohorntilde=7905;e.ohungarumlaut=337;e.oi=419;e.oinvertedbreve=527;e.okatakana=12458;e.okatakanahalfwidth=65397;e.okorean=12631;e.olehebrew=1451;e.omacron=333;e.omacronacute=7763;e.omacrongrave=7761;e.omdeva=2384;e.omega=969;e.omega1=982;e.omegacyrillic=1121;e.omegalatinclosed=631;e.omegaroundcyrillic=1147;e.omegatitlocyrillic=1149;e.omegatonos=974;e.omgujarati=2768;e.omicron=959;e.omicrontonos=972;e.omonospace=65359;e.one=49;e.onearabic=1633;e.onebengali=2535;e.onecircle=9312;e.onecircleinversesansserif=10122;e.onedeva=2407;e.onedotenleader=8228;e.oneeighth=8539;e.onefitted=63196;e.onegujarati=2791;e.onegurmukhi=2663;e.onehackarabic=1633;e.onehalf=189;e.onehangzhou=12321;e.oneideographicparen=12832;e.oneinferior=8321;e.onemonospace=65297;e.onenumeratorbengali=2548;e.oneoldstyle=63281;e.oneparen=9332;e.oneperiod=9352;e.onepersian=1777;e.onequarter=188;e.oneroman=8560;e.onesuperior=185;e.onethai=3665;e.onethird=8531;e.oogonek=491;e.oogonekmacron=493;e.oogurmukhi=2579;e.oomatragurmukhi=2635;e.oopen=596;e.oparen=9386;e.openbullet=9702;e.option=8997;e.ordfeminine=170;e.ordmasculine=186;e.orthogonal=8735;e.oshortdeva=2322;e.oshortvowelsigndeva=2378;e.oslash=248;e.oslashacute=511;e.osmallhiragana=12361;e.osmallkatakana=12457;e.osmallkatakanahalfwidth=65387;e.ostrokeacute=511;e.osuperior=63216;e.otcyrillic=1151;e.otilde=245;e.otildeacute=7757;e.otildedieresis=7759;e.oubopomofo=12577;e.overline=8254;e.overlinecenterline=65098;e.overlinecmb=773;e.overlinedashed=65097;e.overlinedblwavy=65100;e.overlinewavy=65099;e.overscore=175;e.ovowelsignbengali=2507;e.ovowelsigndeva=2379;e.ovowelsigngujarati=2763;e.p=112;e.paampssquare=13184;e.paasentosquare=13099;e.pabengali=2474;e.pacute=7765;e.padeva=2346;e.pagedown=8671;e.pageup=8670;e.pagujarati=2730;e.pagurmukhi=2602;e.pahiragana=12401;e.paiyannoithai=3631;e.pakatakana=12497;e.palatalizationcyrilliccmb=1156;e.palochkacyrillic=1216;e.pansioskorean=12671;e.paragraph=182;e.parallel=8741;e.parenleft=40;e.parenleftaltonearabic=64830;e.parenleftbt=63725;e.parenleftex=63724;e.parenleftinferior=8333;e.parenleftmonospace=65288;e.parenleftsmall=65113;e.parenleftsuperior=8317;e.parenlefttp=63723;e.parenleftvertical=65077;e.parenright=41;e.parenrightaltonearabic=64831;e.parenrightbt=63736;e.parenrightex=63735;e.parenrightinferior=8334;e.parenrightmonospace=65289;e.parenrightsmall=65114;e.parenrightsuperior=8318;e.parenrighttp=63734;e.parenrightvertical=65078;e.partialdiff=8706;e.paseqhebrew=1472;e.pashtahebrew=1433;e.pasquare=13225;e.patah=1463;e.patah11=1463;e.patah1d=1463;e.patah2a=1463;e.patahhebrew=1463;e.patahnarrowhebrew=1463;e.patahquarterhebrew=1463;e.patahwidehebrew=1463;e.pazerhebrew=1441;e.pbopomofo=12550;e.pcircle=9439;e.pdotaccent=7767;e.pe=1508;e.pecyrillic=1087;e.pedagesh=64324;e.pedageshhebrew=64324;e.peezisquare=13115;e.pefinaldageshhebrew=64323;e.peharabic=1662;e.peharmenian=1402;e.pehebrew=1508;e.pehfinalarabic=64343;e.pehinitialarabic=64344;e.pehiragana=12410;e.pehmedialarabic=64345;e.pekatakana=12506;e.pemiddlehookcyrillic=1191;e.perafehebrew=64334;e.percent=37;e.percentarabic=1642;e.percentmonospace=65285;e.percentsmall=65130;e.period=46;e.periodarmenian=1417;e.periodcentered=183;e.periodhalfwidth=65377;e.periodinferior=63207;e.periodmonospace=65294;e.periodsmall=65106;e.periodsuperior=63208;e.perispomenigreekcmb=834;e.perpendicular=8869;e.perthousand=8240;e.peseta=8359;e.pfsquare=13194;e.phabengali=2475;e.phadeva=2347;e.phagujarati=2731;e.phagurmukhi=2603;e.phi=966;e.phi1=981;e.phieuphacirclekorean=12922;e.phieuphaparenkorean=12826;e.phieuphcirclekorean=12908;e.phieuphkorean=12621;e.phieuphparenkorean=12812;e.philatin=632;e.phinthuthai=3642;e.phisymbolgreek=981;e.phook=421;e.phophanthai=3614;e.phophungthai=3612;e.phosamphaothai=3616;e.pi=960;e.pieupacirclekorean=12915;e.pieupaparenkorean=12819;e.pieupcieuckorean=12662;e.pieupcirclekorean=12901;e.pieupkiyeokkorean=12658;e.pieupkorean=12610;e.pieupparenkorean=12805;e.pieupsioskiyeokkorean=12660;e.pieupsioskorean=12612;e.pieupsiostikeutkorean=12661;e.pieupthieuthkorean=12663;e.pieuptikeutkorean=12659;e.pihiragana=12404;e.pikatakana=12500;e.pisymbolgreek=982;e.piwrarmenian=1411;e.planckover2pi=8463;e.planckover2pi1=8463;e.plus=43;e.plusbelowcmb=799;e.pluscircle=8853;e.plusminus=177;e.plusmod=726;e.plusmonospace=65291;e.plussmall=65122;e.plussuperior=8314;e.pmonospace=65360;e.pmsquare=13272;e.pohiragana=12413;e.pointingindexdownwhite=9759;e.pointingindexleftwhite=9756;e.pointingindexrightwhite=9758;e.pointingindexupwhite=9757;e.pokatakana=12509;e.poplathai=3611;e.postalmark=12306;e.postalmarkface=12320;e.pparen=9387;e.precedes=8826;e.prescription=8478;e.primemod=697;e.primereversed=8245;e.product=8719;e.projective=8965;e.prolongedkana=12540;e.propellor=8984;e.propersubset=8834;e.propersuperset=8835;e.proportion=8759;e.proportional=8733;e.psi=968;e.psicyrillic=1137;e.psilipneumatacyrilliccmb=1158;e.pssquare=13232;e.puhiragana=12407;e.pukatakana=12503;e.pvsquare=13236;e.pwsquare=13242;e.q=113;e.qadeva=2392;e.qadmahebrew=1448;e.qafarabic=1602;e.qaffinalarabic=65238;e.qafinitialarabic=65239;e.qafmedialarabic=65240;e.qamats=1464;e.qamats10=1464;e.qamats1a=1464;e.qamats1c=1464;e.qamats27=1464;e.qamats29=1464;e.qamats33=1464;e.qamatsde=1464;e.qamatshebrew=1464;e.qamatsnarrowhebrew=1464;e.qamatsqatanhebrew=1464;e.qamatsqatannarrowhebrew=1464;e.qamatsqatanquarterhebrew=1464;e.qamatsqatanwidehebrew=1464;e.qamatsquarterhebrew=1464;e.qamatswidehebrew=1464;e.qarneyparahebrew=1439;e.qbopomofo=12561;e.qcircle=9440;e.qhook=672;e.qmonospace=65361;e.qof=1511;e.qofdagesh=64327;e.qofdageshhebrew=64327;e.qofhebrew=1511;e.qparen=9388;e.quarternote=9833;e.qubuts=1467;e.qubuts18=1467;e.qubuts25=1467;e.qubuts31=1467;e.qubutshebrew=1467;e.qubutsnarrowhebrew=1467;e.qubutsquarterhebrew=1467;e.qubutswidehebrew=1467;e.question=63;e.questionarabic=1567;e.questionarmenian=1374;e.questiondown=191;e.questiondownsmall=63423;e.questiongreek=894;e.questionmonospace=65311;e.questionsmall=63295;e.quotedbl=34;e.quotedblbase=8222;e.quotedblleft=8220;e.quotedblmonospace=65282;e.quotedblprime=12318;e.quotedblprimereversed=12317;e.quotedblright=8221;e.quoteleft=8216;e.quoteleftreversed=8219;e.quotereversed=8219;e.quoteright=8217;e.quoterightn=329;e.quotesinglbase=8218;e.quotesingle=39;e.quotesinglemonospace=65287;e.r=114;e.raarmenian=1404;e.rabengali=2480;e.racute=341;e.radeva=2352;e.radical=8730;e.radicalex=63717;e.radoverssquare=13230;e.radoverssquaredsquare=13231;e.radsquare=13229;e.rafe=1471;e.rafehebrew=1471;e.ragujarati=2736;e.ragurmukhi=2608;e.rahiragana=12425;e.rakatakana=12521;e.rakatakanahalfwidth=65431;e.ralowerdiagonalbengali=2545;e.ramiddlediagonalbengali=2544;e.ramshorn=612;e.ratio=8758;e.rbopomofo=12566;e.rcaron=345;e.rcedilla=343;e.rcircle=9441;e.rcommaaccent=343;e.rdblgrave=529;e.rdotaccent=7769;e.rdotbelow=7771;e.rdotbelowmacron=7773;e.referencemark=8251;e.reflexsubset=8838;e.reflexsuperset=8839;e.registered=174;e.registersans=63720;e.registerserif=63194;e.reharabic=1585;e.reharmenian=1408;e.rehfinalarabic=65198;e.rehiragana=12428;e.rekatakana=12524;e.rekatakanahalfwidth=65434;e.resh=1512;e.reshdageshhebrew=64328;e.reshhebrew=1512;e.reversedtilde=8765;e.reviahebrew=1431;e.reviamugrashhebrew=1431;e.revlogicalnot=8976;e.rfishhook=638;e.rfishhookreversed=639;e.rhabengali=2525;e.rhadeva=2397;e.rho=961;e.rhook=637;e.rhookturned=635;e.rhookturnedsuperior=693;e.rhosymbolgreek=1009;e.rhotichookmod=734;e.rieulacirclekorean=12913;e.rieulaparenkorean=12817;e.rieulcirclekorean=12899;e.rieulhieuhkorean=12608;e.rieulkiyeokkorean=12602;e.rieulkiyeoksioskorean=12649;e.rieulkorean=12601;e.rieulmieumkorean=12603;e.rieulpansioskorean=12652;e.rieulparenkorean=12803;e.rieulphieuphkorean=12607;e.rieulpieupkorean=12604;e.rieulpieupsioskorean=12651;e.rieulsioskorean=12605;e.rieulthieuthkorean=12606;e.rieultikeutkorean=12650;e.rieulyeorinhieuhkorean=12653;e.rightangle=8735;e.righttackbelowcmb=793;e.righttriangle=8895;e.rihiragana=12426;e.rikatakana=12522;e.rikatakanahalfwidth=65432;e.ring=730;e.ringbelowcmb=805;e.ringcmb=778;e.ringhalfleft=703;e.ringhalfleftarmenian=1369;e.ringhalfleftbelowcmb=796;e.ringhalfleftcentered=723;e.ringhalfright=702;e.ringhalfrightbelowcmb=825;e.ringhalfrightcentered=722;e.rinvertedbreve=531;e.rittorusquare=13137;e.rlinebelow=7775;e.rlongleg=636;e.rlonglegturned=634;e.rmonospace=65362;e.rohiragana=12429;e.rokatakana=12525;e.rokatakanahalfwidth=65435;e.roruathai=3619;e.rparen=9389;e.rrabengali=2524;e.rradeva=2353;e.rragurmukhi=2652;e.rreharabic=1681;e.rrehfinalarabic=64397;e.rrvocalicbengali=2528;e.rrvocalicdeva=2400;e.rrvocalicgujarati=2784;e.rrvocalicvowelsignbengali=2500;e.rrvocalicvowelsigndeva=2372;e.rrvocalicvowelsigngujarati=2756;e.rsuperior=63217;e.rtblock=9616;e.rturned=633;e.rturnedsuperior=692;e.ruhiragana=12427;e.rukatakana=12523;e.rukatakanahalfwidth=65433;e.rupeemarkbengali=2546;e.rupeesignbengali=2547;e.rupiah=63197;e.ruthai=3620;e.rvocalicbengali=2443;e.rvocalicdeva=2315;e.rvocalicgujarati=2699;e.rvocalicvowelsignbengali=2499;e.rvocalicvowelsigndeva=2371;e.rvocalicvowelsigngujarati=2755;e.s=115;e.sabengali=2488;e.sacute=347;e.sacutedotaccent=7781;e.sadarabic=1589;e.sadeva=2360;e.sadfinalarabic=65210;e.sadinitialarabic=65211;e.sadmedialarabic=65212;e.sagujarati=2744;e.sagurmukhi=2616;e.sahiragana=12373;e.sakatakana=12469;e.sakatakanahalfwidth=65403;e.sallallahoualayhewasallamarabic=65018;e.samekh=1505;e.samekhdagesh=64321;e.samekhdageshhebrew=64321;e.samekhhebrew=1505;e.saraaathai=3634;e.saraaethai=3649;e.saraaimaimalaithai=3652;e.saraaimaimuanthai=3651;e.saraamthai=3635;e.saraathai=3632;e.saraethai=3648;e.saraiileftthai=63622;e.saraiithai=3637;e.saraileftthai=63621;e.saraithai=3636;e.saraothai=3650;e.saraueeleftthai=63624;e.saraueethai=3639;e.saraueleftthai=63623;e.sarauethai=3638;e.sarauthai=3640;e.sarauuthai=3641;e.sbopomofo=12569;e.scaron=353;e.scarondotaccent=7783;e.scedilla=351;e.schwa=601;e.schwacyrillic=1241;e.schwadieresiscyrillic=1243;e.schwahook=602;e.scircle=9442;e.scircumflex=349;e.scommaaccent=537;e.sdotaccent=7777;e.sdotbelow=7779;e.sdotbelowdotaccent=7785;e.seagullbelowcmb=828;e.second=8243;e.secondtonechinese=714;e.section=167;e.seenarabic=1587;e.seenfinalarabic=65202;e.seeninitialarabic=65203;e.seenmedialarabic=65204;e.segol=1462;e.segol13=1462;e.segol1f=1462;e.segol2c=1462;e.segolhebrew=1462;e.segolnarrowhebrew=1462;e.segolquarterhebrew=1462;e.segoltahebrew=1426;e.segolwidehebrew=1462;e.seharmenian=1405;e.sehiragana=12379;e.sekatakana=12475;e.sekatakanahalfwidth=65406;e.semicolon=59;e.semicolonarabic=1563;e.semicolonmonospace=65307;e.semicolonsmall=65108;e.semivoicedmarkkana=12444;e.semivoicedmarkkanahalfwidth=65439;e.sentisquare=13090;e.sentosquare=13091;e.seven=55;e.sevenarabic=1639;e.sevenbengali=2541;e.sevencircle=9318;e.sevencircleinversesansserif=10128;e.sevendeva=2413;e.seveneighths=8542;e.sevengujarati=2797;e.sevengurmukhi=2669;e.sevenhackarabic=1639;e.sevenhangzhou=12327;e.sevenideographicparen=12838;e.seveninferior=8327;e.sevenmonospace=65303;e.sevenoldstyle=63287;e.sevenparen=9338;e.sevenperiod=9358;e.sevenpersian=1783;e.sevenroman=8566;e.sevensuperior=8311;e.seventeencircle=9328;e.seventeenparen=9348;e.seventeenperiod=9368;e.seventhai=3671;e.sfthyphen=173;e.shaarmenian=1399;e.shabengali=2486;e.shacyrillic=1096;e.shaddaarabic=1617;e.shaddadammaarabic=64609;e.shaddadammatanarabic=64606;e.shaddafathaarabic=64608;e.shaddakasraarabic=64610;e.shaddakasratanarabic=64607;e.shade=9618;e.shadedark=9619;e.shadelight=9617;e.shademedium=9618;e.shadeva=2358;e.shagujarati=2742;e.shagurmukhi=2614;e.shalshelethebrew=1427;e.shbopomofo=12565;e.shchacyrillic=1097;e.sheenarabic=1588;e.sheenfinalarabic=65206;e.sheeninitialarabic=65207;e.sheenmedialarabic=65208;e.sheicoptic=995;e.sheqel=8362;e.sheqelhebrew=8362;e.sheva=1456;e.sheva115=1456;e.sheva15=1456;e.sheva22=1456;e.sheva2e=1456;e.shevahebrew=1456;e.shevanarrowhebrew=1456;e.shevaquarterhebrew=1456;e.shevawidehebrew=1456;e.shhacyrillic=1211;e.shimacoptic=1005;e.shin=1513;e.shindagesh=64329;e.shindageshhebrew=64329;e.shindageshshindot=64300;e.shindageshshindothebrew=64300;e.shindageshsindot=64301;e.shindageshsindothebrew=64301;e.shindothebrew=1473;e.shinhebrew=1513;e.shinshindot=64298;e.shinshindothebrew=64298;e.shinsindot=64299;e.shinsindothebrew=64299;e.shook=642;e.sigma=963;e.sigma1=962;e.sigmafinal=962;e.sigmalunatesymbolgreek=1010;e.sihiragana=12375;e.sikatakana=12471;e.sikatakanahalfwidth=65404;e.siluqhebrew=1469;e.siluqlefthebrew=1469;e.similar=8764;e.sindothebrew=1474;e.siosacirclekorean=12916;e.siosaparenkorean=12820;e.sioscieuckorean=12670;e.sioscirclekorean=12902;e.sioskiyeokkorean=12666;e.sioskorean=12613;e.siosnieunkorean=12667;e.siosparenkorean=12806;e.siospieupkorean=12669;e.siostikeutkorean=12668;e.six=54;e.sixarabic=1638;e.sixbengali=2540;e.sixcircle=9317;e.sixcircleinversesansserif=10127;e.sixdeva=2412;e.sixgujarati=2796;e.sixgurmukhi=2668;e.sixhackarabic=1638;e.sixhangzhou=12326;e.sixideographicparen=12837;e.sixinferior=8326;e.sixmonospace=65302;e.sixoldstyle=63286;e.sixparen=9337;e.sixperiod=9357;e.sixpersian=1782;e.sixroman=8565;e.sixsuperior=8310;e.sixteencircle=9327;e.sixteencurrencydenominatorbengali=2553;e.sixteenparen=9347;e.sixteenperiod=9367;e.sixthai=3670;e.slash=47;e.slashmonospace=65295;e.slong=383;e.slongdotaccent=7835;e.smileface=9786;e.smonospace=65363;e.sofpasuqhebrew=1475;e.softhyphen=173;e.softsigncyrillic=1100;e.sohiragana=12381;e.sokatakana=12477;e.sokatakanahalfwidth=65407;e.soliduslongoverlaycmb=824;e.solidusshortoverlaycmb=823;e.sorusithai=3625;e.sosalathai=3624;e.sosothai=3595;e.sosuathai=3626;e.space=32;e.spacehackarabic=32;e.spade=9824;e.spadesuitblack=9824;e.spadesuitwhite=9828;e.sparen=9390;e.squarebelowcmb=827;e.squarecc=13252;e.squarecm=13213;e.squarediagonalcrosshatchfill=9641;e.squarehorizontalfill=9636;e.squarekg=13199;e.squarekm=13214;e.squarekmcapital=13262;e.squareln=13265;e.squarelog=13266;e.squaremg=13198;e.squaremil=13269;e.squaremm=13212;e.squaremsquared=13217;e.squareorthogonalcrosshatchfill=9638;e.squareupperlefttolowerrightfill=9639;e.squareupperrighttolowerleftfill=9640;e.squareverticalfill=9637;e.squarewhitewithsmallblack=9635;e.srsquare=13275;e.ssabengali=2487;e.ssadeva=2359;e.ssagujarati=2743;e.ssangcieuckorean=12617;e.ssanghieuhkorean=12677;e.ssangieungkorean=12672;e.ssangkiyeokkorean=12594;e.ssangnieunkorean=12645;e.ssangpieupkorean=12611;e.ssangsioskorean=12614;e.ssangtikeutkorean=12600;e.ssuperior=63218;e.sterling=163;e.sterlingmonospace=65505;e.strokelongoverlaycmb=822;e.strokeshortoverlaycmb=821;e.subset=8834;e.subsetnotequal=8842;e.subsetorequal=8838;e.succeeds=8827;e.suchthat=8715;e.suhiragana=12377;e.sukatakana=12473;e.sukatakanahalfwidth=65405;e.sukunarabic=1618;e.summation=8721;e.sun=9788;e.superset=8835;e.supersetnotequal=8843;e.supersetorequal=8839;e.svsquare=13276;e.syouwaerasquare=13180;e.t=116;e.tabengali=2468;e.tackdown=8868;e.tackleft=8867;e.tadeva=2340;e.tagujarati=2724;e.tagurmukhi=2596;e.taharabic=1591;e.tahfinalarabic=65218;e.tahinitialarabic=65219;e.tahiragana=12383;e.tahmedialarabic=65220;e.taisyouerasquare=13181;e.takatakana=12479;e.takatakanahalfwidth=65408;e.tatweelarabic=1600;e.tau=964;e.tav=1514;e.tavdages=64330;e.tavdagesh=64330;e.tavdageshhebrew=64330;e.tavhebrew=1514;e.tbar=359;e.tbopomofo=12554;e.tcaron=357;e.tccurl=680;e.tcedilla=355;e.tcheharabic=1670;e.tchehfinalarabic=64379;e.tchehinitialarabic=64380;e.tchehmedialarabic=64381;e.tcircle=9443;e.tcircumflexbelow=7793;e.tcommaaccent=355;e.tdieresis=7831;e.tdotaccent=7787;e.tdotbelow=7789;e.tecyrillic=1090;e.tedescendercyrillic=1197;e.teharabic=1578;e.tehfinalarabic=65174;e.tehhahinitialarabic=64674;e.tehhahisolatedarabic=64524;e.tehinitialarabic=65175;e.tehiragana=12390;e.tehjeeminitialarabic=64673;e.tehjeemisolatedarabic=64523;e.tehmarbutaarabic=1577;e.tehmarbutafinalarabic=65172;e.tehmedialarabic=65176;e.tehmeeminitialarabic=64676;e.tehmeemisolatedarabic=64526;e.tehnoonfinalarabic=64627;e.tekatakana=12486;e.tekatakanahalfwidth=65411;e.telephone=8481;e.telephoneblack=9742;e.telishagedolahebrew=1440;e.telishaqetanahebrew=1449;e.tencircle=9321;e.tenideographicparen=12841;e.tenparen=9341;e.tenperiod=9361;e.tenroman=8569;e.tesh=679;e.tet=1496;e.tetdagesh=64312;e.tetdageshhebrew=64312;e.tethebrew=1496;e.tetsecyrillic=1205;e.tevirhebrew=1435;e.tevirlefthebrew=1435;e.thabengali=2469;e.thadeva=2341;e.thagujarati=2725;e.thagurmukhi=2597;e.thalarabic=1584;e.thalfinalarabic=65196;e.thanthakhatlowleftthai=63640;e.thanthakhatlowrightthai=63639;e.thanthakhatthai=3660;e.thanthakhatupperleftthai=63638;e.theharabic=1579;e.thehfinalarabic=65178;e.thehinitialarabic=65179;e.thehmedialarabic=65180;e.thereexists=8707;e.therefore=8756;e.theta=952;e.theta1=977;e.thetasymbolgreek=977;e.thieuthacirclekorean=12921;e.thieuthaparenkorean=12825;e.thieuthcirclekorean=12907;e.thieuthkorean=12620;e.thieuthparenkorean=12811;e.thirteencircle=9324;e.thirteenparen=9344;e.thirteenperiod=9364;e.thonangmonthothai=3601;e.thook=429;e.thophuthaothai=3602;e.thorn=254;e.thothahanthai=3607;e.thothanthai=3600;e.thothongthai=3608;e.thothungthai=3606;e.thousandcyrillic=1154;e.thousandsseparatorarabic=1644;e.thousandsseparatorpersian=1644;e.three=51;e.threearabic=1635;e.threebengali=2537;e.threecircle=9314;e.threecircleinversesansserif=10124;e.threedeva=2409;e.threeeighths=8540;e.threegujarati=2793;e.threegurmukhi=2665;e.threehackarabic=1635;e.threehangzhou=12323;e.threeideographicparen=12834;e.threeinferior=8323;e.threemonospace=65299;e.threenumeratorbengali=2550;e.threeoldstyle=63283;e.threeparen=9334;e.threeperiod=9354;e.threepersian=1779;e.threequarters=190;e.threequartersemdash=63198;e.threeroman=8562;e.threesuperior=179;e.threethai=3667;e.thzsquare=13204;e.tihiragana=12385;e.tikatakana=12481;e.tikatakanahalfwidth=65409;e.tikeutacirclekorean=12912;e.tikeutaparenkorean=12816;e.tikeutcirclekorean=12898;e.tikeutkorean=12599;e.tikeutparenkorean=12802;e.tilde=732;e.tildebelowcmb=816;e.tildecmb=771;e.tildecomb=771;e.tildedoublecmb=864;e.tildeoperator=8764;e.tildeoverlaycmb=820;e.tildeverticalcmb=830;e.timescircle=8855;e.tipehahebrew=1430;e.tipehalefthebrew=1430;e.tippigurmukhi=2672;e.titlocyrilliccmb=1155;e.tiwnarmenian=1407;e.tlinebelow=7791;e.tmonospace=65364;e.toarmenian=1385;e.tohiragana=12392;e.tokatakana=12488;e.tokatakanahalfwidth=65412;e.tonebarextrahighmod=741;e.tonebarextralowmod=745;e.tonebarhighmod=742;e.tonebarlowmod=744;e.tonebarmidmod=743;e.tonefive=445;e.tonesix=389;e.tonetwo=424;e.tonos=900;e.tonsquare=13095;e.topatakthai=3599;e.tortoiseshellbracketleft=12308;e.tortoiseshellbracketleftsmall=65117;e.tortoiseshellbracketleftvertical=65081;e.tortoiseshellbracketright=12309;e.tortoiseshellbracketrightsmall=65118;e.tortoiseshellbracketrightvertical=65082;e.totaothai=3605;e.tpalatalhook=427;e.tparen=9391;e.trademark=8482;e.trademarksans=63722;e.trademarkserif=63195;e.tretroflexhook=648;e.triagdn=9660;e.triaglf=9668;e.triagrt=9658;e.triagup=9650;e.ts=678;e.tsadi=1510;e.tsadidagesh=64326;e.tsadidageshhebrew=64326;e.tsadihebrew=1510;e.tsecyrillic=1094;e.tsere=1461;e.tsere12=1461;e.tsere1e=1461;e.tsere2b=1461;e.tserehebrew=1461;e.tserenarrowhebrew=1461;e.tserequarterhebrew=1461;e.tserewidehebrew=1461;e.tshecyrillic=1115;e.tsuperior=63219;e.ttabengali=2463;e.ttadeva=2335;e.ttagujarati=2719;e.ttagurmukhi=2591;e.tteharabic=1657;e.ttehfinalarabic=64359;e.ttehinitialarabic=64360;e.ttehmedialarabic=64361;e.tthabengali=2464;e.tthadeva=2336;e.tthagujarati=2720;e.tthagurmukhi=2592;e.tturned=647;e.tuhiragana=12388;e.tukatakana=12484;e.tukatakanahalfwidth=65410;e.tusmallhiragana=12387;e.tusmallkatakana=12483;e.tusmallkatakanahalfwidth=65391;e.twelvecircle=9323;e.twelveparen=9343;e.twelveperiod=9363;e.twelveroman=8571;e.twentycircle=9331;e.twentyhangzhou=21316;e.twentyparen=9351;e.twentyperiod=9371;e.two=50;e.twoarabic=1634;e.twobengali=2536;e.twocircle=9313;e.twocircleinversesansserif=10123;e.twodeva=2408;e.twodotenleader=8229;e.twodotleader=8229;e.twodotleadervertical=65072;e.twogujarati=2792;e.twogurmukhi=2664;e.twohackarabic=1634;e.twohangzhou=12322;e.twoideographicparen=12833;e.twoinferior=8322;e.twomonospace=65298;e.twonumeratorbengali=2549;e.twooldstyle=63282;e.twoparen=9333;e.twoperiod=9353;e.twopersian=1778;e.tworoman=8561;e.twostroke=443;e.twosuperior=178;e.twothai=3666;e.twothirds=8532;e.u=117;e.uacute=250;e.ubar=649;e.ubengali=2441;e.ubopomofo=12584;e.ubreve=365;e.ucaron=468;e.ucircle=9444;e.ucircumflex=251;e.ucircumflexbelow=7799;e.ucyrillic=1091;e.udattadeva=2385;e.udblacute=369;e.udblgrave=533;e.udeva=2313;e.udieresis=252;e.udieresisacute=472;e.udieresisbelow=7795;e.udieresiscaron=474;e.udieresiscyrillic=1265;e.udieresisgrave=476;e.udieresismacron=470;e.udotbelow=7909;e.ugrave=249;e.ugujarati=2697;e.ugurmukhi=2569;e.uhiragana=12358;e.uhookabove=7911;e.uhorn=432;e.uhornacute=7913;e.uhorndotbelow=7921;e.uhorngrave=7915;e.uhornhookabove=7917;e.uhorntilde=7919;e.uhungarumlaut=369;e.uhungarumlautcyrillic=1267;e.uinvertedbreve=535;e.ukatakana=12454;e.ukatakanahalfwidth=65395;e.ukcyrillic=1145;e.ukorean=12636;e.umacron=363;e.umacroncyrillic=1263;e.umacrondieresis=7803;e.umatragurmukhi=2625;e.umonospace=65365;e.underscore=95;e.underscoredbl=8215;e.underscoremonospace=65343;e.underscorevertical=65075;e.underscorewavy=65103;e.union=8746;e.universal=8704;e.uogonek=371;e.uparen=9392;e.upblock=9600;e.upperdothebrew=1476;e.upsilon=965;e.upsilondieresis=971;e.upsilondieresistonos=944;e.upsilonlatin=650;e.upsilontonos=973;e.uptackbelowcmb=797;e.uptackmod=724;e.uragurmukhi=2675;e.uring=367;e.ushortcyrillic=1118;e.usmallhiragana=12357;e.usmallkatakana=12453;e.usmallkatakanahalfwidth=65385;e.ustraightcyrillic=1199;e.ustraightstrokecyrillic=1201;e.utilde=361;e.utildeacute=7801;e.utildebelow=7797;e.uubengali=2442;e.uudeva=2314;e.uugujarati=2698;e.uugurmukhi=2570;e.uumatragurmukhi=2626;e.uuvowelsignbengali=2498;e.uuvowelsigndeva=2370;e.uuvowelsigngujarati=2754;e.uvowelsignbengali=2497;e.uvowelsigndeva=2369;e.uvowelsigngujarati=2753;e.v=118;e.vadeva=2357;e.vagujarati=2741;e.vagurmukhi=2613;e.vakatakana=12535;e.vav=1493;e.vavdagesh=64309;e.vavdagesh65=64309;e.vavdageshhebrew=64309;e.vavhebrew=1493;e.vavholam=64331;e.vavholamhebrew=64331;e.vavvavhebrew=1520;e.vavyodhebrew=1521;e.vcircle=9445;e.vdotbelow=7807;e.vecyrillic=1074;e.veharabic=1700;e.vehfinalarabic=64363;e.vehinitialarabic=64364;e.vehmedialarabic=64365;e.vekatakana=12537;e.venus=9792;e.verticalbar=124;e.verticallineabovecmb=781;e.verticallinebelowcmb=809;e.verticallinelowmod=716;e.verticallinemod=712;e.vewarmenian=1406;e.vhook=651;e.vikatakana=12536;e.viramabengali=2509;e.viramadeva=2381;e.viramagujarati=2765;e.visargabengali=2435;e.visargadeva=2307;e.visargagujarati=2691;e.vmonospace=65366;e.voarmenian=1400;e.voicediterationhiragana=12446;e.voicediterationkatakana=12542;e.voicedmarkkana=12443;e.voicedmarkkanahalfwidth=65438;e.vokatakana=12538;e.vparen=9393;e.vtilde=7805;e.vturned=652;e.vuhiragana=12436;e.vukatakana=12532;e.w=119;e.wacute=7811;e.waekorean=12633;e.wahiragana=12431;e.wakatakana=12527;e.wakatakanahalfwidth=65436;e.wakorean=12632;e.wasmallhiragana=12430;e.wasmallkatakana=12526;e.wattosquare=13143;e.wavedash=12316;e.wavyunderscorevertical=65076;e.wawarabic=1608;e.wawfinalarabic=65262;e.wawhamzaabovearabic=1572;e.wawhamzaabovefinalarabic=65158;e.wbsquare=13277;e.wcircle=9446;e.wcircumflex=373;e.wdieresis=7813;e.wdotaccent=7815;e.wdotbelow=7817;e.wehiragana=12433;e.weierstrass=8472;e.wekatakana=12529;e.wekorean=12638;e.weokorean=12637;e.wgrave=7809;e.whitebullet=9702;e.whitecircle=9675;e.whitecircleinverse=9689;e.whitecornerbracketleft=12302;e.whitecornerbracketleftvertical=65091;e.whitecornerbracketright=12303;e.whitecornerbracketrightvertical=65092;e.whitediamond=9671;e.whitediamondcontainingblacksmalldiamond=9672;e.whitedownpointingsmalltriangle=9663;e.whitedownpointingtriangle=9661;e.whiteleftpointingsmalltriangle=9667;e.whiteleftpointingtriangle=9665;e.whitelenticularbracketleft=12310;e.whitelenticularbracketright=12311;e.whiterightpointingsmalltriangle=9657;e.whiterightpointingtriangle=9655;e.whitesmallsquare=9643;e.whitesmilingface=9786;e.whitesquare=9633;e.whitestar=9734;e.whitetelephone=9743;e.whitetortoiseshellbracketleft=12312;e.whitetortoiseshellbracketright=12313;e.whiteuppointingsmalltriangle=9653;e.whiteuppointingtriangle=9651;e.wihiragana=12432;e.wikatakana=12528;e.wikorean=12639;e.wmonospace=65367;e.wohiragana=12434;e.wokatakana=12530;e.wokatakanahalfwidth=65382;e.won=8361;e.wonmonospace=65510;e.wowaenthai=3623;e.wparen=9394;e.wring=7832;e.wsuperior=695;e.wturned=653;e.wynn=447;e.x=120;e.xabovecmb=829;e.xbopomofo=12562;e.xcircle=9447;e.xdieresis=7821;e.xdotaccent=7819;e.xeharmenian=1389;e.xi=958;e.xmonospace=65368;e.xparen=9395;e.xsuperior=739;e.y=121;e.yaadosquare=13134;e.yabengali=2479;e.yacute=253;e.yadeva=2351;e.yaekorean=12626;e.yagujarati=2735;e.yagurmukhi=2607;e.yahiragana=12420;e.yakatakana=12516;e.yakatakanahalfwidth=65428;e.yakorean=12625;e.yamakkanthai=3662;e.yasmallhiragana=12419;e.yasmallkatakana=12515;e.yasmallkatakanahalfwidth=65388;e.yatcyrillic=1123;e.ycircle=9448;e.ycircumflex=375;e.ydieresis=255;e.ydotaccent=7823;e.ydotbelow=7925;e.yeharabic=1610;e.yehbarreearabic=1746;e.yehbarreefinalarabic=64431;e.yehfinalarabic=65266;e.yehhamzaabovearabic=1574;e.yehhamzaabovefinalarabic=65162;e.yehhamzaaboveinitialarabic=65163;e.yehhamzaabovemedialarabic=65164;e.yehinitialarabic=65267;e.yehmedialarabic=65268;e.yehmeeminitialarabic=64733;e.yehmeemisolatedarabic=64600;e.yehnoonfinalarabic=64660;e.yehthreedotsbelowarabic=1745;e.yekorean=12630;e.yen=165;e.yenmonospace=65509;e.yeokorean=12629;e.yeorinhieuhkorean=12678;e.yerahbenyomohebrew=1450;e.yerahbenyomolefthebrew=1450;e.yericyrillic=1099;e.yerudieresiscyrillic=1273;e.yesieungkorean=12673;e.yesieungpansioskorean=12675;e.yesieungsioskorean=12674;e.yetivhebrew=1434;e.ygrave=7923;e.yhook=436;e.yhookabove=7927;e.yiarmenian=1397;e.yicyrillic=1111;e.yikorean=12642;e.yinyang=9775;e.yiwnarmenian=1410;e.ymonospace=65369;e.yod=1497;e.yoddagesh=64313;e.yoddageshhebrew=64313;e.yodhebrew=1497;e.yodyodhebrew=1522;e.yodyodpatahhebrew=64287;e.yohiragana=12424;e.yoikorean=12681;e.yokatakana=12520;e.yokatakanahalfwidth=65430;e.yokorean=12635;e.yosmallhiragana=12423;e.yosmallkatakana=12519;e.yosmallkatakanahalfwidth=65390;e.yotgreek=1011;e.yoyaekorean=12680;e.yoyakorean=12679;e.yoyakthai=3618;e.yoyingthai=3597;e.yparen=9396;e.ypogegrammeni=890;e.ypogegrammenigreekcmb=837;e.yr=422;e.yring=7833;e.ysuperior=696;e.ytilde=7929;e.yturned=654;e.yuhiragana=12422;e.yuikorean=12684;e.yukatakana=12518;e.yukatakanahalfwidth=65429;e.yukorean=12640;e.yusbigcyrillic=1131;e.yusbigiotifiedcyrillic=1133;e.yuslittlecyrillic=1127;e.yuslittleiotifiedcyrillic=1129;e.yusmallhiragana=12421;e.yusmallkatakana=12517;e.yusmallkatakanahalfwidth=65389;e.yuyekorean=12683;e.yuyeokorean=12682;e.yyabengali=2527;e.yyadeva=2399;e.z=122;e.zaarmenian=1382;e.zacute=378;e.zadeva=2395;e.zagurmukhi=2651;e.zaharabic=1592;e.zahfinalarabic=65222;e.zahinitialarabic=65223;e.zahiragana=12374;e.zahmedialarabic=65224;e.zainarabic=1586;e.zainfinalarabic=65200;e.zakatakana=12470;e.zaqefgadolhebrew=1429;e.zaqefqatanhebrew=1428;e.zarqahebrew=1432;e.zayin=1494;e.zayindagesh=64310;e.zayindageshhebrew=64310;e.zayinhebrew=1494;e.zbopomofo=12567;e.zcaron=382;e.zcircle=9449;e.zcircumflex=7825;e.zcurl=657;e.zdot=380;e.zdotaccent=380;e.zdotbelow=7827;e.zecyrillic=1079;e.zedescendercyrillic=1177;e.zedieresiscyrillic=1247;e.zehiragana=12380;e.zekatakana=12476;e.zero=48;e.zeroarabic=1632;e.zerobengali=2534;e.zerodeva=2406;e.zerogujarati=2790;e.zerogurmukhi=2662;e.zerohackarabic=1632;e.zeroinferior=8320;e.zeromonospace=65296;e.zerooldstyle=63280;e.zeropersian=1776;e.zerosuperior=8304;e.zerothai=3664;e.zerowidthjoiner=65279;e.zerowidthnonjoiner=8204;e.zerowidthspace=8203;e.zeta=950;e.zhbopomofo=12563;e.zhearmenian=1386;e.zhebrevecyrillic=1218;e.zhecyrillic=1078;e.zhedescendercyrillic=1175;e.zhedieresiscyrillic=1245;e.zihiragana=12376;e.zikatakana=12472;e.zinorhebrew=1454;e.zlinebelow=7829;e.zmonospace=65370;e.zohiragana=12382;e.zokatakana=12478;e.zparen=9397;e.zretroflexhook=656;e.zstroke=438;e.zuhiragana=12378;e.zukatakana=12474;e[".notdef"]=0;e.angbracketleftbig=9001;e.angbracketleftBig=9001;e.angbracketleftbigg=9001;e.angbracketleftBigg=9001;e.angbracketrightBig=9002;e.angbracketrightbig=9002;e.angbracketrightBigg=9002;e.angbracketrightbigg=9002;e.arrowhookleft=8618;e.arrowhookright=8617;e.arrowlefttophalf=8636;e.arrowleftbothalf=8637;e.arrownortheast=8599;e.arrownorthwest=8598;e.arrowrighttophalf=8640;e.arrowrightbothalf=8641;e.arrowsoutheast=8600;e.arrowsouthwest=8601;e.backslashbig=8726;e.backslashBig=8726;e.backslashBigg=8726;e.backslashbigg=8726;e.bardbl=8214;e.bracehtipdownleft=65079;e.bracehtipdownright=65079;e.bracehtipupleft=65080;e.bracehtipupright=65080;e.braceleftBig=123;e.braceleftbig=123;e.braceleftbigg=123;e.braceleftBigg=123;e.bracerightBig=125;e.bracerightbig=125;e.bracerightbigg=125;e.bracerightBigg=125;e.bracketleftbig=91;e.bracketleftBig=91;e.bracketleftbigg=91;e.bracketleftBigg=91;e.bracketrightBig=93;e.bracketrightbig=93;e.bracketrightbigg=93;e.bracketrightBigg=93;e.ceilingleftbig=8968;e.ceilingleftBig=8968;e.ceilingleftBigg=8968;e.ceilingleftbigg=8968;e.ceilingrightbig=8969;e.ceilingrightBig=8969;e.ceilingrightbigg=8969;e.ceilingrightBigg=8969;e.circledotdisplay=8857;e.circledottext=8857;e.circlemultiplydisplay=8855;e.circlemultiplytext=8855;e.circleplusdisplay=8853;e.circleplustext=8853;e.contintegraldisplay=8750;e.contintegraltext=8750;e.coproductdisplay=8720;e.coproducttext=8720;e.floorleftBig=8970;e.floorleftbig=8970;e.floorleftbigg=8970;e.floorleftBigg=8970;e.floorrightbig=8971;e.floorrightBig=8971;e.floorrightBigg=8971;e.floorrightbigg=8971;e.hatwide=770;e.hatwider=770;e.hatwidest=770;e.intercal=7488;e.integraldisplay=8747;e.integraltext=8747;e.intersectiondisplay=8898;e.intersectiontext=8898;e.logicalanddisplay=8743;e.logicalandtext=8743;e.logicalordisplay=8744;e.logicalortext=8744;e.parenleftBig=40;e.parenleftbig=40;e.parenleftBigg=40;e.parenleftbigg=40;e.parenrightBig=41;e.parenrightbig=41;e.parenrightBigg=41;e.parenrightbigg=41;e.prime=8242;e.productdisplay=8719;e.producttext=8719;e.radicalbig=8730;e.radicalBig=8730;e.radicalBigg=8730;e.radicalbigg=8730;e.radicalbt=8730;e.radicaltp=8730;e.radicalvertex=8730;e.slashbig=47;e.slashBig=47;e.slashBigg=47;e.slashbigg=47;e.summationdisplay=8721;e.summationtext=8721;e.tildewide=732;e.tildewider=732;e.tildewidest=732;e.uniondisplay=8899;e.unionmultidisplay=8846;e.unionmultitext=8846;e.unionsqdisplay=8852;e.unionsqtext=8852;e.uniontext=8899;e.vextenddouble=8741;e.vextendsingle=8739})),Ir=getLookupTableFactory((function(e){e.space=32;e.a1=9985;e.a2=9986;e.a202=9987;e.a3=9988;e.a4=9742;e.a5=9990;e.a119=9991;e.a118=9992;e.a117=9993;e.a11=9755;e.a12=9758;e.a13=9996;e.a14=9997;e.a15=9998;e.a16=9999;e.a105=1e4;e.a17=10001;e.a18=10002;e.a19=10003;e.a20=10004;e.a21=10005;e.a22=10006;e.a23=10007;e.a24=10008;e.a25=10009;e.a26=10010;e.a27=10011;e.a28=10012;e.a6=10013;e.a7=10014;e.a8=10015;e.a9=10016;e.a10=10017;e.a29=10018;e.a30=10019;e.a31=10020;e.a32=10021;e.a33=10022;e.a34=10023;e.a35=9733;e.a36=10025;e.a37=10026;e.a38=10027;e.a39=10028;e.a40=10029;e.a41=10030;e.a42=10031;e.a43=10032;e.a44=10033;e.a45=10034;e.a46=10035;e.a47=10036;e.a48=10037;e.a49=10038;e.a50=10039;e.a51=10040;e.a52=10041;e.a53=10042;e.a54=10043;e.a55=10044;e.a56=10045;e.a57=10046;e.a58=10047;e.a59=10048;e.a60=10049;e.a61=10050;e.a62=10051;e.a63=10052;e.a64=10053;e.a65=10054;e.a66=10055;e.a67=10056;e.a68=10057;e.a69=10058;e.a70=10059;e.a71=9679;e.a72=10061;e.a73=9632;e.a74=10063;e.a203=10064;e.a75=10065;e.a204=10066;e.a76=9650;e.a77=9660;e.a78=9670;e.a79=10070;e.a81=9687;e.a82=10072;e.a83=10073;e.a84=10074;e.a97=10075;e.a98=10076;e.a99=10077;e.a100=10078;e.a101=10081;e.a102=10082;e.a103=10083;e.a104=10084;e.a106=10085;e.a107=10086;e.a108=10087;e.a112=9827;e.a111=9830;e.a110=9829;e.a109=9824;e.a120=9312;e.a121=9313;e.a122=9314;e.a123=9315;e.a124=9316;e.a125=9317;e.a126=9318;e.a127=9319;e.a128=9320;e.a129=9321;e.a130=10102;e.a131=10103;e.a132=10104;e.a133=10105;e.a134=10106;e.a135=10107;e.a136=10108;e.a137=10109;e.a138=10110;e.a139=10111;e.a140=10112;e.a141=10113;e.a142=10114;e.a143=10115;e.a144=10116;e.a145=10117;e.a146=10118;e.a147=10119;e.a148=10120;e.a149=10121;e.a150=10122;e.a151=10123;e.a152=10124;e.a153=10125;e.a154=10126;e.a155=10127;e.a156=10128;e.a157=10129;e.a158=10130;e.a159=10131;e.a160=10132;e.a161=8594;e.a163=8596;e.a164=8597;e.a196=10136;e.a165=10137;e.a192=10138;e.a166=10139;e.a167=10140;e.a168=10141;e.a169=10142;e.a170=10143;e.a171=10144;e.a172=10145;e.a173=10146;e.a162=10147;e.a174=10148;e.a175=10149;e.a176=10150;e.a177=10151;e.a178=10152;e.a179=10153;e.a193=10154;e.a180=10155;e.a199=10156;e.a181=10157;e.a200=10158;e.a182=10159;e.a201=10161;e.a183=10162;e.a184=10163;e.a197=10164;e.a185=10165;e.a194=10166;e.a198=10167;e.a186=10168;e.a195=10169;e.a187=10170;e.a188=10171;e.a189=10172;e.a190=10173;e.a191=10174;e.a89=10088;e.a90=10089;e.a93=10090;e.a94=10091;e.a91=10092;e.a92=10093;e.a205=10094;e.a85=10095;e.a206=10096;e.a86=10097;e.a87=10098;e.a88=10099;e.a95=10100;e.a96=10101;e[".notdef"]=0})),Or=getLookupTableFactory((function(e){e[63721]=169;e[63193]=169;e[63720]=174;e[63194]=174;e[63722]=8482;e[63195]=8482;e[63729]=9127;e[63730]=9128;e[63731]=9129;e[63740]=9131;e[63741]=9132;e[63742]=9133;e[63726]=9121;e[63727]=9122;e[63728]=9123;e[63737]=9124;e[63738]=9125;e[63739]=9126;e[63723]=9115;e[63724]=9116;e[63725]=9117;e[63734]=9118;e[63735]=9119;e[63736]=9120}));function getUnicodeForGlyph(e,t){let a=t[e];if(void 0!==a)return a;if(!e)return-1;if("u"===e[0]){const t=e.length;let r;if(7===t&&"n"===e[1]&&"i"===e[2])r=e.substring(3);else{if(!(t>=5&&t<=7))return-1;r=e.substring(1)}if(r===r.toUpperCase()){a=parseInt(r,16);if(a>=0)return a}}return-1}const Tr=[[0,127],[128,255],[256,383],[384,591],[592,687,7424,7551,7552,7615],[688,767,42752,42783],[768,879,7616,7679],[880,1023],[11392,11519],[1024,1279,1280,1327,11744,11775,42560,42655],[1328,1423],[1424,1535],[42240,42559],[1536,1791,1872,1919],[1984,2047],[2304,2431],[2432,2559],[2560,2687],[2688,2815],[2816,2943],[2944,3071],[3072,3199],[3200,3327],[3328,3455],[3584,3711],[3712,3839],[4256,4351,11520,11567],[6912,7039],[4352,4607],[7680,7935,11360,11391,42784,43007],[7936,8191],[8192,8303,11776,11903],[8304,8351],[8352,8399],[8400,8447],[8448,8527],[8528,8591],[8592,8703,10224,10239,10496,10623,11008,11263],[8704,8959,10752,11007,10176,10223,10624,10751],[8960,9215],[9216,9279],[9280,9311],[9312,9471],[9472,9599],[9600,9631],[9632,9727],[9728,9983],[9984,10175],[12288,12351],[12352,12447],[12448,12543,12784,12799],[12544,12591,12704,12735],[12592,12687],[43072,43135],[12800,13055],[13056,13311],[44032,55215],[55296,57343],[67840,67871],[19968,40959,11904,12031,12032,12255,12272,12287,13312,19903,131072,173791,12688,12703],[57344,63743],[12736,12783,63744,64255,194560,195103],[64256,64335],[64336,65023],[65056,65071],[65040,65055],[65104,65135],[65136,65279],[65280,65519],[65520,65535],[3840,4095],[1792,1871],[1920,1983],[3456,3583],[4096,4255],[4608,4991,4992,5023,11648,11743],[5024,5119],[5120,5759],[5760,5791],[5792,5887],[6016,6143],[6144,6319],[10240,10495],[40960,42127],[5888,5919,5920,5951,5952,5983,5984,6015],[66304,66351],[66352,66383],[66560,66639],[118784,119039,119040,119295,119296,119375],[119808,120831],[1044480,1048573],[65024,65039,917760,917999],[917504,917631],[6400,6479],[6480,6527],[6528,6623],[6656,6687],[11264,11359],[11568,11647],[19904,19967],[43008,43055],[65536,65663,65664,65791,65792,65855],[65856,65935],[66432,66463],[66464,66527],[66640,66687],[66688,66735],[67584,67647],[68096,68191],[119552,119647],[73728,74751,74752,74879],[119648,119679],[7040,7103],[7168,7247],[7248,7295],[43136,43231],[43264,43311],[43312,43359],[43520,43615],[65936,65999],[66e3,66047],[66208,66271,66176,66207,67872,67903],[127024,127135,126976,127023]];function getUnicodeRangeFor(e,t=-1){if(-1!==t){const a=Tr[t];for(let r=0,i=a.length;r=a[r]&&e<=a[r+1])return t}for(let t=0,a=Tr.length;t=a[r]&&e<=a[r+1])return t}return-1}const Mr=new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$","u"),Dr=new Map;const Rr=!0,Nr=1,Er=2,Pr=4,Lr=32,jr=[".notdef",".null","nonmarkingreturn","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","nonbreakingspace","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron","Lslash","lslash","Scaron","scaron","Zcaron","zcaron","brokenbar","Eth","eth","Yacute","yacute","Thorn","thorn","minus","multiply","onesuperior","twosuperior","threesuperior","onehalf","onequarter","threequarters","franc","Gbreve","gbreve","Idotaccent","Scedilla","scedilla","Cacute","cacute","Ccaron","ccaron","dcroat"];function recoverGlyphName(e,t){if(void 0!==t[e])return e;const a=getUnicodeForGlyph(e,t);if(-1!==a)for(const e in t)if(t[e]===a)return e;info("Unable to recover a standard glyph name for: "+e);return e}function type1FontGlyphMapping(e,t,a){const r=Object.create(null);let i,n,s;const o=!!(e.flags&Pr);if(e.isInternalFont){s=t;for(n=0;n=0?i:0}}else if(e.baseEncodingName){s=getEncoding(e.baseEncodingName);for(n=0;n=0?i:0}}else if(o)for(n in t)r[n]=t[n];else{s=Ar;for(n=0;n=0?i:0}}const c=e.differences;let l;if(c)for(n in c){const e=c[n];i=a.indexOf(e);if(-1===i){l||(l=Fr());const t=recoverGlyphName(e,l);t!==e&&(i=a.indexOf(t))}r[n]=i>=0?i:0}return r}function normalizeFontName(e){return e.replaceAll(/[,_]/g,"-").replaceAll(/\s/g,"")}const _r=getLookupTableFactory((e=>{e[8211]=65074;e[8212]=65073;e[8229]=65072;e[8230]=65049;e[12289]=65041;e[12290]=65042;e[12296]=65087;e[12297]=65088;e[12298]=65085;e[12299]=65086;e[12300]=65089;e[12301]=65090;e[12302]=65091;e[12303]=65092;e[12304]=65083;e[12305]=65084;e[12308]=65081;e[12309]=65082;e[12310]=65047;e[12311]=65048;e[65103]=65076;e[65281]=65045;e[65288]=65077;e[65289]=65078;e[65292]=65040;e[65306]=65043;e[65307]=65044;e[65311]=65046;e[65339]=65095;e[65341]=65096;e[65343]=65075;e[65371]=65079;e[65373]=65080}));const Ur=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron"],Xr=[".notdef","space","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],qr=[".notdef","space","dollaroldstyle","dollarsuperior","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","hyphensuperior","colonmonetary","onefitted","rupiah","centoldstyle","figuredash","hypheninferior","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior"],Hr=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall","001.000","001.001","001.002","001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"],Wr=391,zr=[null,{id:"hstem",min:2,stackClearing:!0,stem:!0},null,{id:"vstem",min:2,stackClearing:!0,stem:!0},{id:"vmoveto",min:1,stackClearing:!0},{id:"rlineto",min:2,resetStack:!0},{id:"hlineto",min:1,resetStack:!0},{id:"vlineto",min:1,resetStack:!0},{id:"rrcurveto",min:6,resetStack:!0},null,{id:"callsubr",min:1,undefStack:!0},{id:"return",min:0,undefStack:!0},null,null,{id:"endchar",min:0,stackClearing:!0},null,null,null,{id:"hstemhm",min:2,stackClearing:!0,stem:!0},{id:"hintmask",min:0,stackClearing:!0},{id:"cntrmask",min:0,stackClearing:!0},{id:"rmoveto",min:2,stackClearing:!0},{id:"hmoveto",min:1,stackClearing:!0},{id:"vstemhm",min:2,stackClearing:!0,stem:!0},{id:"rcurveline",min:8,resetStack:!0},{id:"rlinecurve",min:8,resetStack:!0},{id:"vvcurveto",min:4,resetStack:!0},{id:"hhcurveto",min:4,resetStack:!0},null,{id:"callgsubr",min:1,undefStack:!0},{id:"vhcurveto",min:4,resetStack:!0},{id:"hvcurveto",min:4,resetStack:!0}],$r=[null,null,null,{id:"and",min:2,stackDelta:-1},{id:"or",min:2,stackDelta:-1},{id:"not",min:1,stackDelta:0},null,null,null,{id:"abs",min:1,stackDelta:0},{id:"add",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]+e[t-1]}},{id:"sub",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]-e[t-1]}},{id:"div",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]/e[t-1]}},null,{id:"neg",min:1,stackDelta:0,stackFn(e,t){e[t-1]=-e[t-1]}},{id:"eq",min:2,stackDelta:-1},null,null,{id:"drop",min:1,stackDelta:-1},null,{id:"put",min:2,stackDelta:-2},{id:"get",min:1,stackDelta:0},{id:"ifelse",min:4,stackDelta:-3},{id:"random",min:0,stackDelta:1},{id:"mul",min:2,stackDelta:-1,stackFn(e,t){e[t-2]=e[t-2]*e[t-1]}},null,{id:"sqrt",min:1,stackDelta:0},{id:"dup",min:1,stackDelta:1},{id:"exch",min:2,stackDelta:0},{id:"index",min:2,stackDelta:0},{id:"roll",min:3,stackDelta:-2},null,null,null,{id:"hflex",min:7,resetStack:!0},{id:"flex",min:13,resetStack:!0},{id:"hflex1",min:9,resetStack:!0},{id:"flex1",min:11,resetStack:!0}];class CFFParser{constructor(e,t,a){this.bytes=e.getBytes();this.properties=t;this.seacAnalysisEnabled=!!a}parse(){const e=this.properties,t=new CFF;this.cff=t;const a=this.parseHeader(),r=this.parseIndex(a.endPos),i=this.parseIndex(r.endPos),n=this.parseIndex(i.endPos),s=this.parseIndex(n.endPos),o=this.parseDict(i.obj.get(0)),c=this.createDict(CFFTopDict,o,t.strings);t.header=a.obj;t.names=this.parseNameIndex(r.obj);t.strings=this.parseStringIndex(n.obj);t.topDict=c;t.globalSubrIndex=s.obj;this.parsePrivateDict(t.topDict);t.isCIDFont=c.hasName("ROS");const l=c.getByName("CharStrings"),h=this.parseIndex(l).obj,u=c.getByName("FontMatrix");u&&(e.fontMatrix=u);const d=c.getByName("FontBBox");if(d){e.ascent=Math.max(d[3],d[1]);e.descent=Math.min(d[1],d[3]);e.ascentScaled=!0}let f,g;if(t.isCIDFont){const e=this.parseIndex(c.getByName("FDArray")).obj;for(let a=0,r=e.count;a=t)throw new FormatError("Invalid CFF header");if(0!==a){info("cff data is shifted");e=e.subarray(a);this.bytes=e}const r=e[0],i=e[1],n=e[2],s=e[3];return{obj:new CFFHeader(r,i,n,s),endPos:n}}parseDict(e){let t=0;function parseOperand(){let a=e[t++];if(30===a)return function parseFloatOperand(){let a="";const r=15,i=["0","1","2","3","4","5","6","7","8","9",".","E","E-",null,"-"],n=e.length;for(;t>4,o=15&n;if(s===r)break;a+=i[s];if(o===r)break;a+=i[o]}return parseFloat(a)}();if(28===a){a=readInt16(e,t);t+=2;return a}if(29===a){a=e[t++];a=a<<8|e[t++];a=a<<8|e[t++];a=a<<8|e[t++];return a}if(a>=32&&a<=246)return a-139;if(a>=247&&a<=250)return 256*(a-247)+e[t++]+108;if(a>=251&&a<=254)return-256*(a-251)-e[t++]-108;warn('CFFParser_parseDict: "'+a+'" is a reserved command.');return NaN}let a=[];const r=[];t=0;const i=e.length;for(;t10)return!1;let i=e.stackSize;const n=e.stack;let s=t.length;for(let o=0;o=4){i-=4;if(this.seacAnalysisEnabled){e.seac=n.slice(i,i+4);return!1}}l=zr[c]}else if(c>=32&&c<=246){n[i]=c-139;i++}else if(c>=247&&c<=254){n[i]=c<251?(c-247<<8)+t[o]+108:-(c-251<<8)-t[o]-108;o++;i++}else if(255===c){n[i]=(t[o]<<24|t[o+1]<<16|t[o+2]<<8|t[o+3])/65536;o+=4;i++}else if(19===c||20===c){e.hints+=i>>1;if(0===e.hints){t.copyWithin(o-1,o,-1);o-=1;s-=1;continue}o+=e.hints+7>>3;i%=2;l=zr[c]}else{if(10===c||29===c){const t=10===c?a:r;if(!t){l=zr[c];warn("Missing subrsIndex for "+l.id);return!1}let s=32768;t.count<1240?s=107:t.count<33900&&(s=1131);const o=n[--i]+s;if(o<0||o>=t.count||isNaN(o)){l=zr[c];warn("Out of bounds subrIndex for "+l.id);return!1}e.stackSize=i;e.callDepth++;if(!this.parseCharString(e,t.get(o),a,r))return!1;e.callDepth--;i=e.stackSize;continue}if(11===c){e.stackSize=i;return!0}if(0===c&&o===t.length){t[o-1]=14;l=zr[14]}else{if(9===c){t.copyWithin(o-1,o,-1);o-=1;s-=1;continue}l=zr[c]}}if(l){if(l.stem){e.hints+=i>>1;if(3===c||23===c)e.hasVStems=!0;else if(e.hasVStems&&(1===c||18===c)){warn("CFF stem hints are in wrong order");t[o-1]=1===c?3:23}}if("min"in l&&!e.undefStack&&i=2&&l.stem?i%=2:i>1&&warn("Found too many parameters for stack-clearing command");i>0&&(e.width=n[i-1])}if("stackDelta"in l){"stackFn"in l&&l.stackFn(n,i);i+=l.stackDelta}else if(l.stackClearing)i=0;else if(l.resetStack){i=0;e.undefStack=!1}else if(l.undefStack){i=0;e.undefStack=!0;e.firstStackClearing=!1}}}s=i.length){warn("Invalid fd index for glyph index.");u=!1}if(u){f=i[e].privateDict;d=f.subrsIndex}}else t&&(d=t);u&&(u=this.parseCharString(h,c,d,a));if(null!==h.width){const e=f.getByName("nominalWidthX");o[l]=e+h.width}else{const e=f.getByName("defaultWidthX");o[l]=e}null!==h.seac&&(s[l]=h.seac);u||e.set(l,new Uint8Array([14]))}return{charStrings:e,seacs:s,widths:o}}emptyPrivateDictionary(e){const t=this.createDict(CFFPrivateDict,[],e.strings);e.setByKey(18,[0,0]);e.privateDict=t}parsePrivateDict(e){if(!e.hasName("Private")){this.emptyPrivateDictionary(e);return}const t=e.getByName("Private");if(!Array.isArray(t)||2!==t.length){e.removeByName("Private");return}const a=t[0],r=t[1];if(0===a||r>=this.bytes.length){this.emptyPrivateDictionary(e);return}const i=r+a,n=this.bytes.subarray(r,i),s=this.parseDict(n),o=this.createDict(CFFPrivateDict,s,e.strings);e.privateDict=o;0===o.getByName("ExpansionFactor")&&o.setByName("ExpansionFactor",.06);if(!o.getByName("Subrs"))return;const c=o.getByName("Subrs"),l=r+c;if(0===c||l>=this.bytes.length){this.emptyPrivateDictionary(e);return}const h=this.parseIndex(l);o.subrsIndex=h.obj}parseCharsets(e,t,a,r){if(0===e)return new CFFCharset(!0,Kr.ISO_ADOBE,Ur);if(1===e)return new CFFCharset(!0,Kr.EXPERT,Xr);if(2===e)return new CFFCharset(!0,Kr.EXPERT_SUBSET,qr);const i=this.bytes,n=e,s=i[e++],o=[r?0:".notdef"];let c,l,h;t-=1;switch(s){case 0:for(h=0;h=65535){warn("Not enough space in charstrings to duplicate first glyph.");return}const e=this.charStrings.get(0);this.charStrings.add(e);this.isCIDFont&&this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0])}hasGlyphId(e){if(e<0||e>=this.charStrings.count)return!1;return this.charStrings.get(e).length>0}}class CFFHeader{constructor(e,t,a,r){this.major=e;this.minor=t;this.hdrSize=a;this.offSize=r}}class CFFStrings{constructor(){this.strings=[]}get(e){return e>=0&&e<=390?Hr[e]:e-Wr<=this.strings.length?this.strings[e-Wr]:Hr[0]}getSID(e){let t=Hr.indexOf(e);if(-1!==t)return t;t=this.strings.indexOf(e);return-1!==t?t+Wr:-1}add(e){this.strings.push(e)}get count(){return this.strings.length}}class CFFIndex{constructor(){this.objects=[];this.length=0}add(e){this.length+=e.length;this.objects.push(e)}set(e,t){this.length+=t.length-this.objects[e].length;this.objects[e]=t}get(e){return this.objects[e]}get count(){return this.objects.length}}class CFFDict{constructor(e,t){this.keyToNameMap=e.keyToNameMap;this.nameToKeyMap=e.nameToKeyMap;this.defaults=e.defaults;this.types=e.types;this.opcodes=e.opcodes;this.order=e.order;this.strings=t;this.values=Object.create(null)}setByKey(e,t){if(!(e in this.keyToNameMap))return!1;if(0===t.length)return!0;for(const a of t)if(isNaN(a)){warn(`Invalid CFFDict value: "${t}" for key "${e}".`);return!0}const a=this.types[e];"num"!==a&&"sid"!==a&&"offset"!==a||(t=t[0]);this.values[e]=t;return!0}setByName(e,t){if(!(e in this.nameToKeyMap))throw new FormatError(`Invalid dictionary name "${e}"`);this.values[this.nameToKeyMap[e]]=t}hasName(e){return this.nameToKeyMap[e]in this.values}getByName(e){if(!(e in this.nameToKeyMap))throw new FormatError(`Invalid dictionary name ${e}"`);const t=this.nameToKeyMap[e];return t in this.values?this.values[t]:this.defaults[t]}removeByName(e){delete this.values[this.nameToKeyMap[e]]}static createTables(e){const t={keyToNameMap:{},nameToKeyMap:{},defaults:{},types:{},opcodes:{},order:[]};for(const a of e){const e=Array.isArray(a[0])?(a[0][0]<<8)+a[0][1]:a[0];t.keyToNameMap[e]=a[1];t.nameToKeyMap[a[1]]=e;t.types[e]=a[2];t.defaults[e]=a[3];t.opcodes[e]=Array.isArray(a[0])?a[0]:[a[0]];t.order.push(e)}return t}}const Gr=[[[12,30],"ROS",["sid","sid","num"],null],[[12,20],"SyntheticBase","num",null],[0,"version","sid",null],[1,"Notice","sid",null],[[12,0],"Copyright","sid",null],[2,"FullName","sid",null],[3,"FamilyName","sid",null],[4,"Weight","sid",null],[[12,1],"isFixedPitch","num",0],[[12,2],"ItalicAngle","num",0],[[12,3],"UnderlinePosition","num",-100],[[12,4],"UnderlineThickness","num",50],[[12,5],"PaintType","num",0],[[12,6],"CharstringType","num",2],[[12,7],"FontMatrix",["num","num","num","num","num","num"],[.001,0,0,.001,0,0]],[13,"UniqueID","num",null],[5,"FontBBox",["num","num","num","num"],[0,0,0,0]],[[12,8],"StrokeWidth","num",0],[14,"XUID","array",null],[15,"charset","offset",0],[16,"Encoding","offset",0],[17,"CharStrings","offset",0],[18,"Private",["offset","offset"],null],[[12,21],"PostScript","sid",null],[[12,22],"BaseFontName","sid",null],[[12,23],"BaseFontBlend","delta",null],[[12,31],"CIDFontVersion","num",0],[[12,32],"CIDFontRevision","num",0],[[12,33],"CIDFontType","num",0],[[12,34],"CIDCount","num",8720],[[12,35],"UIDBase","num",null],[[12,37],"FDSelect","offset",null],[[12,36],"FDArray","offset",null],[[12,38],"FontName","sid",null]];class CFFTopDict extends CFFDict{static get tables(){return shadow(this,"tables",this.createTables(Gr))}constructor(e){super(CFFTopDict.tables,e);this.privateDict=null}}const Vr=[[6,"BlueValues","delta",null],[7,"OtherBlues","delta",null],[8,"FamilyBlues","delta",null],[9,"FamilyOtherBlues","delta",null],[[12,9],"BlueScale","num",.039625],[[12,10],"BlueShift","num",7],[[12,11],"BlueFuzz","num",1],[10,"StdHW","num",null],[11,"StdVW","num",null],[[12,12],"StemSnapH","delta",null],[[12,13],"StemSnapV","delta",null],[[12,14],"ForceBold","num",0],[[12,17],"LanguageGroup","num",0],[[12,18],"ExpansionFactor","num",.06],[[12,19],"initialRandomSeed","num",0],[20,"defaultWidthX","num",0],[21,"nominalWidthX","num",0],[19,"Subrs","offset",null]];class CFFPrivateDict extends CFFDict{static get tables(){return shadow(this,"tables",this.createTables(Vr))}constructor(e){super(CFFPrivateDict.tables,e);this.subrsIndex=null}}const Kr={ISO_ADOBE:0,EXPERT:1,EXPERT_SUBSET:2};class CFFCharset{constructor(e,t,a,r){this.predefined=e;this.format=t;this.charset=a;this.raw=r}}class CFFEncoding{constructor(e,t,a,r){this.predefined=e;this.format=t;this.encoding=a;this.raw=r}}class CFFFDSelect{constructor(e,t){this.format=e;this.fdSelect=t}getFDIndex(e){return e<0||e>=this.fdSelect.length?-1:this.fdSelect[e]}}class CFFOffsetTracker{constructor(){this.offsets=Object.create(null)}isTracking(e){return e in this.offsets}track(e,t){if(e in this.offsets)throw new FormatError(`Already tracking location of ${e}`);this.offsets[e]=t}offset(e){for(const t in this.offsets)this.offsets[t]+=e}setEntryLocation(e,t,a){if(!(e in this.offsets))throw new FormatError(`Not tracking location of ${e}`);const r=a.data,i=this.offsets[e];for(let e=0,a=t.length;e>24&255;r[s]=l>>16&255;r[o]=l>>8&255;r[c]=255&l}}}class CFFCompiler{constructor(e){this.cff=e}compile(){const e=this.cff,t={data:[],length:0,add(e){try{this.data.push(...e)}catch{this.data=this.data.concat(e)}this.length=this.data.length}},a=this.compileHeader(e.header);t.add(a);const r=this.compileNameIndex(e.names);t.add(r);if(e.isCIDFont&&e.topDict.hasName("FontMatrix")){const t=e.topDict.getByName("FontMatrix");e.topDict.removeByName("FontMatrix");for(const a of e.fdArray){let e=t.slice(0);a.hasName("FontMatrix")&&(e=Util.transform(e,a.getByName("FontMatrix")));a.setByName("FontMatrix",e)}}const i=e.topDict.getByName("XUID");i?.length>16&&e.topDict.removeByName("XUID");e.topDict.setByName("charset",0);let n=this.compileTopDicts([e.topDict],t.length,e.isCIDFont);t.add(n.output);const s=n.trackers[0],o=this.compileStringIndex(e.strings.strings);t.add(o);const c=this.compileIndex(e.globalSubrIndex);t.add(c);if(e.encoding&&e.topDict.hasName("Encoding"))if(e.encoding.predefined)s.setEntryLocation("Encoding",[e.encoding.format],t);else{const a=this.compileEncoding(e.encoding);s.setEntryLocation("Encoding",[t.length],t);t.add(a)}const l=this.compileCharset(e.charset,e.charStrings.count,e.strings,e.isCIDFont);s.setEntryLocation("charset",[t.length],t);t.add(l);const h=this.compileCharStrings(e.charStrings);s.setEntryLocation("CharStrings",[t.length],t);t.add(h);if(e.isCIDFont){s.setEntryLocation("FDSelect",[t.length],t);const a=this.compileFDSelect(e.fdSelect);t.add(a);n=this.compileTopDicts(e.fdArray,t.length,!0);s.setEntryLocation("FDArray",[t.length],t);t.add(n.output);const r=n.trackers;this.compilePrivateDicts(e.fdArray,r,t)}this.compilePrivateDicts([e.topDict],[s],t);t.add([0]);return t.data}encodeNumber(e){return Number.isInteger(e)?this.encodeInteger(e):this.encodeFloat(e)}static get EncodeFloatRegExp(){return shadow(this,"EncodeFloatRegExp",/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/)}encodeFloat(e){let t=e.toString();const a=CFFCompiler.EncodeFloatRegExp.exec(t);if(a){const r=parseFloat("1e"+((a[2]?+a[2]:0)+a[1].length));t=(Math.round(e*r)/r).toString()}let r,i,n="";for(r=0,i=t.length;r=-107&&e<=107?[e+139]:e>=108&&e<=1131?[247+((e-=108)>>8),255&e]:e>=-1131&&e<=-108?[251+((e=-e-108)>>8),255&e]:e>=-32768&&e<=32767?[28,e>>8&255,255&e]:[29,e>>24&255,e>>16&255,e>>8&255,255&e];return t}compileHeader(e){return[e.major,e.minor,4,e.offSize]}compileNameIndex(e){const t=new CFFIndex;for(const a of e){const e=Math.min(a.length,127);let r=new Array(e);for(let t=0;t"~"||"["===e||"]"===e||"("===e||")"===e||"{"===e||"}"===e||"<"===e||">"===e||"/"===e||"%"===e)&&(e="_");r[t]=e}r=r.join("");""===r&&(r="Bad_Font_Name");t.add(stringToBytes(r))}return this.compileIndex(t)}compileTopDicts(e,t,a){const r=[];let i=new CFFIndex;for(const n of e){if(a){n.removeByName("CIDFontVersion");n.removeByName("CIDFontRevision");n.removeByName("CIDFontType");n.removeByName("CIDCount");n.removeByName("UIDBase")}const e=new CFFOffsetTracker,s=this.compileDict(n,e);r.push(e);i.add(s);e.offset(t)}i=this.compileIndex(i,r);return{trackers:r,output:i}}compilePrivateDicts(e,t,a){for(let r=0,i=e.length;r>8&255,255&e])}else{i=new Uint8Array(1+2*n);i[0]=0;let t=0;const r=e.charset.length;let s=!1;for(let n=1;n>8&255;i[n+1]=255&o}}return this.compileTypedArray(i)}compileEncoding(e){return this.compileTypedArray(e.raw)}compileFDSelect(e){const t=e.format;let a,r;switch(t){case 0:a=new Uint8Array(1+e.fdSelect.length);a[0]=t;for(r=0;r>8&255,255&i,n];for(r=1;r>8&255,255&r,t);n=t}}const o=(s.length-3)/3;s[1]=o>>8&255;s[2]=255&o;s.push(r>>8&255,255&r);a=new Uint8Array(s)}return this.compileTypedArray(a)}compileTypedArray(e){return Array.from(e)}compileIndex(e,t=[]){const a=e.objects,r=a.length;if(0===r)return[0,0];const i=[r>>8&255,255&r];let n,s,o=1;for(n=0;n>8&255,255&c):3===s?i.push(c>>16&255,c>>8&255,255&c):i.push(c>>>24&255,c>>16&255,c>>8&255,255&c);a[n]&&(c+=a[n].length)}for(n=0;n=this.firstChar&&e<=this.lastChar?e:-1}amend(e){unreachable("Should not call amend()")}}class CFFFont{constructor(e,t){this.properties=t;const a=new CFFParser(e,t,Rr);this.cff=a.parse();this.cff.duplicateFirstGlyph();const r=new CFFCompiler(this.cff);this.seacs=this.cff.seacs;try{this.data=r.compile()}catch{warn("Failed to compile font "+t.loadedName);this.data=e}this._createBuiltInEncoding()}get numGlyphs(){return this.cff.charStrings.count}getCharset(){return this.cff.charset.charset}getGlyphMapping(){const e=this.cff,t=this.properties,{cidToGidMap:a,cMap:r}=t,i=e.charset.charset;let n,s;if(t.composite){let t,o;if(a?.length>0){t=Object.create(null);for(let e=0,r=a.length;e=0){const r=a[t];r&&(i[e]=r)}}i.length>0&&(this.properties.builtInEncoding=i)}}function getFloat214(e,t){return readInt16(e,t)/16384}function getSubroutineBias(e){const t=e.length;let a=32768;t<1240?a=107:t<33900&&(a=1131);return a}function parseCmap(e,t,a){const r=1===readUint16(e,t+2)?readUint32(e,t+8):readUint32(e,t+16),i=readUint16(e,t+r);let n,s,o;if(4===i){readUint16(e,t+r+2);const a=readUint16(e,t+r+6)>>1;s=t+r+14;n=[];for(o=0;o>1;a0;)h.push({flags:n})}for(a=0;a>1;y=!0;break;case 4:s+=i.pop();moveTo(n,s);y=!0;break;case 5:for(;i.length>0;){n+=i.shift();s+=i.shift();lineTo(n,s)}break;case 6:for(;i.length>0;){n+=i.shift();lineTo(n,s);if(0===i.length)break;s+=i.shift();lineTo(n,s)}break;case 7:for(;i.length>0;){s+=i.shift();lineTo(n,s);if(0===i.length)break;n+=i.shift();lineTo(n,s)}break;case 8:for(;i.length>0;){l=n+i.shift();u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+i.shift();bezierCurveTo(l,u,h,d,n,s)}break;case 10:m=i.pop();b=null;if(a.isCFFCIDFont){const e=a.fdSelect.getFDIndex(r);if(e>=0&&eMath.abs(s-t)?n+=i.shift():s+=i.shift();bezierCurveTo(l,u,h,d,n,s);break;default:throw new FormatError(`unknown operator: 12 ${w}`)}break;case 14:if(i.length>=4){const e=i.pop(),r=i.pop();s=i.pop();n=i.pop();t.save();t.translate(n,s);let o=lookupCmap(a.cmap,String.fromCharCode(a.glyphNameMap[Ar[e]]));compileCharString(a.glyphs[o.glyphId],t,a,o.glyphId);t.restore();o=lookupCmap(a.cmap,String.fromCharCode(a.glyphNameMap[Ar[r]]));compileCharString(a.glyphs[o.glyphId],t,a,o.glyphId)}return;case 19:case 20:o+=i.length>>1;c+=o+7>>3;y=!0;break;case 21:s+=i.pop();n+=i.pop();moveTo(n,s);y=!0;break;case 22:n+=i.pop();moveTo(n,s);y=!0;break;case 24:for(;i.length>2;){l=n+i.shift();u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+i.shift();bezierCurveTo(l,u,h,d,n,s)}n+=i.shift();s+=i.shift();lineTo(n,s);break;case 25:for(;i.length>6;){n+=i.shift();s+=i.shift();lineTo(n,s)}l=n+i.shift();u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+i.shift();bezierCurveTo(l,u,h,d,n,s);break;case 26:i.length%2&&(n+=i.shift());for(;i.length>0;){l=n;u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h;s=d+i.shift();bezierCurveTo(l,u,h,d,n,s)}break;case 27:i.length%2&&(s+=i.shift());for(;i.length>0;){l=n+i.shift();u=s;h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d;bezierCurveTo(l,u,h,d,n,s)}break;case 28:i.push(readInt16(e,c));c+=2;break;case 29:m=i.pop()+a.gsubrsBias;b=a.gsubrs[m];b&&parse(b);break;case 30:for(;i.length>0;){l=n;u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s);if(0===i.length)break;l=n+i.shift();u=s;h=l+i.shift();d=u+i.shift();s=d+i.shift();n=h+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s)}break;case 31:for(;i.length>0;){l=n+i.shift();u=s;h=l+i.shift();d=u+i.shift();s=d+i.shift();n=h+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s);if(0===i.length)break;l=n;u=s+i.shift();h=l+i.shift();d=u+i.shift();n=h+i.shift();s=d+(1===i.length?i.shift():0);bezierCurveTo(l,u,h,d,n,s)}break;default:if(w<32)throw new FormatError(`unknown operator: ${w}`);if(w<247)i.push(w-139);else if(w<251)i.push(256*(w-247)+e[c++]+108);else if(w<255)i.push(256*-(w-251)-e[c++]-108);else{i.push((e[c]<<24|e[c+1]<<16|e[c+2]<<8|e[c+3])/65536);c+=4}}y&&(i.length=0)}}(e)}class Commands{cmds=[];transformStack=[];currentTransform=[1,0,0,1,0,0];add(e,t){if(t){const{currentTransform:a}=this;for(let e=0,r=t.length;e=0&&e2*readUint16(e,t)}const n=[];let s=i(t,0);for(let a=r;ae.getSize()+3&-4)))}write(){const e=this.getSize(),t=new DataView(new ArrayBuffer(e)),a=e>131070,r=a?4:2,i=new DataView(new ArrayBuffer((this.glyphs.length+1)*r));a?i.setUint32(0,0):i.setUint16(0,0);let n=0,s=0;for(const e of this.glyphs){n+=e.write(n,t);n=n+3&-4;s+=r;a?i.setUint32(s,n):i.setUint16(s,n>>1)}return{isLocationLong:a,loca:new Uint8Array(i.buffer),glyf:new Uint8Array(t.buffer)}}scale(e){for(let t=0,a=this.glyphs.length;te.getSize())));return this.header.getSize()+e}write(e,t){if(!this.header)return 0;const a=e;e+=this.header.write(e,t);if(this.simple)e+=this.simple.write(e,t);else for(const a of this.composites)e+=a.write(e,t);return e-a}scale(e){if(!this.header)return;const t=(this.header.xMin+this.header.xMax)/2;this.header.scale(t,e);if(this.simple)this.simple.scale(t,e);else for(const a of this.composites)a.scale(t,e)}}class GlyphHeader{constructor({numberOfContours:e,xMin:t,yMin:a,xMax:r,yMax:i}){this.numberOfContours=e;this.xMin=t;this.yMin=a;this.xMax=r;this.yMax=i}static parse(e,t){return[10,new GlyphHeader({numberOfContours:t.getInt16(e),xMin:t.getInt16(e+2),yMin:t.getInt16(e+4),xMax:t.getInt16(e+6),yMax:t.getInt16(e+8)})]}getSize(){return 10}write(e,t){t.setInt16(e,this.numberOfContours);t.setInt16(e+2,this.xMin);t.setInt16(e+4,this.yMin);t.setInt16(e+6,this.xMax);t.setInt16(e+8,this.yMax);return 10}scale(e,t){this.xMin=Math.round(e+(this.xMin-e)*t);this.xMax=Math.round(e+(this.xMax-e)*t)}}class Contour{constructor({flags:e,xCoordinates:t,yCoordinates:a}){this.xCoordinates=t;this.yCoordinates=a;this.flags=e}}class SimpleGlyph{constructor({contours:e,instructions:t}){this.contours=e;this.instructions=t}static parse(e,t,a){const r=[];for(let i=0;i255?e+=2:o>0&&(e+=1);t=n;o=Math.abs(s-a);o>255?e+=2:o>0&&(e+=1);a=s}}return e}write(e,t){const a=e,r=[],i=[],n=[];let s=0,o=0;for(const a of this.contours){for(let e=0,t=a.xCoordinates.length;e=0?18:2;r.push(e)}else r.push(l)}s=c;const h=a.yCoordinates[e];l=h-o;if(0===l){t|=32;i.push(0)}else{const e=Math.abs(l);if(e<=255){t|=l>=0?36:4;i.push(e)}else i.push(l)}o=h;n.push(t)}t.setUint16(e,r.length-1);e+=2}t.setUint16(e,this.instructions.length);e+=2;if(this.instructions.length){new Uint8Array(t.buffer,0,t.buffer.byteLength).set(this.instructions,e);e+=this.instructions.length}for(const a of n)t.setUint8(e++,a);for(let a=0,i=r.length;a=-128&&this.argument1<=127&&this.argument2>=-128&&this.argument2<=127||(e+=2):this.argument1>=0&&this.argument1<=255&&this.argument2>=0&&this.argument2<=255||(e+=2);return e}write(e,t){const a=e;2&this.flags?this.argument1>=-128&&this.argument1<=127&&this.argument2>=-128&&this.argument2<=127||(this.flags|=1):this.argument1>=0&&this.argument1<=255&&this.argument2>=0&&this.argument2<=255||(this.flags|=1);t.setUint16(e,this.flags);t.setUint16(e+2,this.glyphIndex);e+=4;if(1&this.flags){if(2&this.flags){t.setInt16(e,this.argument1);t.setInt16(e+2,this.argument2)}else{t.setUint16(e,this.argument1);t.setUint16(e+2,this.argument2)}e+=4}else{t.setUint8(e,this.argument1);t.setUint8(e+1,this.argument2);e+=2}if(256&this.flags){t.setUint16(e,this.instructions.length);e+=2;if(this.instructions.length){new Uint8Array(t.buffer,0,t.buffer.byteLength).set(this.instructions,e);e+=this.instructions.length}}return e-a}scale(e,t){}}function writeInt16(e,t,a){e[t]=a>>8&255;e[t+1]=255&a}function writeInt32(e,t,a){e[t]=a>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}function writeData(e,t,a){if(a instanceof Uint8Array)e.set(a,t);else if("string"==typeof a)for(let r=0,i=a.length;ra;){a<<=1;r++}const i=a*t;return{range:i,entry:r,rangeShift:t*e-i}}toArray(){let e=this.sfnt;const t=this.tables,a=Object.keys(t);a.sort();const r=a.length;let i,n,s,o,c,l=12+16*r;const h=[l];for(i=0;i>>0;h.push(l)}const u=new Uint8Array(l);for(i=0;i>>0}writeInt32(u,l+4,e);writeInt32(u,l+8,h[i]);writeInt32(u,l+12,t[c].length);l+=16}return u}addTable(e,t){if(e in this.tables)throw new Error("Table "+e+" already exists");this.tables[e]=t}}const si=[4],oi=[5],ci=[6],li=[7],hi=[8],ui=[12,35],di=[14],fi=[21],gi=[22],pi=[30],mi=[31];class Type1CharString{constructor(){this.width=0;this.lsb=0;this.flexing=!1;this.output=[];this.stack=[]}convert(e,t,a){const r=e.length;let i,n,s,o=!1;for(let c=0;cr)return!0;const i=r-e;for(let e=i;e>8&255,255&t);else{t=65536*t|0;this.output.push(255,t>>24&255,t>>16&255,t>>8&255,255&t)}}this.output.push(...t);a?this.stack.splice(i,e):this.stack.length=0;return!1}}function isHexDigit(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function decrypt(e,t,a){if(a>=e.length)return new Uint8Array(0);let r,i,n=0|t;for(r=0;r>8;n=52845*(t+n)+22719&65535}return o}function isSpecial(e){return 47===e||91===e||93===e||123===e||125===e||40===e||41===e}class Type1Parser{constructor(e,t,a){if(t){const t=e.getBytes(),a=!((isHexDigit(t[0])||isWhiteSpace(t[0]))&&isHexDigit(t[1])&&isHexDigit(t[2])&&isHexDigit(t[3])&&isHexDigit(t[4])&&isHexDigit(t[5])&&isHexDigit(t[6])&&isHexDigit(t[7]));e=new Stream(a?decrypt(t,55665,4):function decryptAscii(e,t,a){let r=0|t;const i=e.length,n=new Uint8Array(i>>>1);let s,o;for(s=0,o=0;s>8;r=52845*(e+r)+22719&65535}}return n.slice(a,o)}(t,55665,4))}this.seacAnalysisEnabled=!!a;this.stream=e;this.nextChar()}readNumberArray(){this.getToken();const e=[];for(;;){const t=this.getToken();if(null===t||"]"===t||"}"===t)break;e.push(parseFloat(t||0))}return e}readNumber(){const e=this.getToken();return parseFloat(e||0)}readInt(){const e=this.getToken();return 0|parseInt(e||0,10)}readBoolean(){return"true"===this.getToken()?1:0}nextChar(){return this.currentChar=this.stream.getByte()}prevChar(){this.stream.skip(-2);return this.currentChar=this.stream.getByte()}getToken(){let e=!1,t=this.currentChar;for(;;){if(-1===t)return null;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!isWhiteSpace(t))break;t=this.nextChar()}if(isSpecial(t)){this.nextChar();return String.fromCharCode(t)}let a="";do{a+=String.fromCharCode(t);t=this.nextChar()}while(t>=0&&!isWhiteSpace(t)&&!isSpecial(t));return a}readCharStrings(e,t){return-1===t?e:decrypt(e,4330,t)}extractFontProgram(e){const t=this.stream,a=[],r=[],i=Object.create(null);i.lenIV=4;const n={subrs:[],charstrings:[],properties:{privateData:i}};let s,o,c,l;for(;null!==(s=this.getToken());)if("/"===s){s=this.getToken();switch(s){case"CharStrings":this.getToken();this.getToken();this.getToken();this.getToken();for(;;){s=this.getToken();if(null===s||"end"===s)break;if("/"!==s)continue;const e=this.getToken();o=this.readInt();this.getToken();c=o>0?t.getBytes(o):new Uint8Array(0);l=n.properties.privateData.lenIV;const a=this.readCharStrings(c,l);this.nextChar();s=this.getToken();"noaccess"===s?this.getToken():"/"===s&&this.prevChar();r.push({glyph:e,encoded:a})}break;case"Subrs":this.readInt();this.getToken();for(;"dup"===this.getToken();){const e=this.readInt();o=this.readInt();this.getToken();c=o>0?t.getBytes(o):new Uint8Array(0);l=n.properties.privateData.lenIV;const r=this.readCharStrings(c,l);this.nextChar();s=this.getToken();"noaccess"===s&&this.getToken();a[e]=r}break;case"BlueValues":case"OtherBlues":case"FamilyBlues":case"FamilyOtherBlues":const e=this.readNumberArray();e.length>0&&e.length,0;break;case"StemSnapH":case"StemSnapV":n.properties.privateData[s]=this.readNumberArray();break;case"StdHW":case"StdVW":n.properties.privateData[s]=this.readNumberArray()[0];break;case"BlueShift":case"lenIV":case"BlueFuzz":case"BlueScale":case"LanguageGroup":n.properties.privateData[s]=this.readNumber();break;case"ExpansionFactor":n.properties.privateData[s]=this.readNumber()||.06;break;case"ForceBold":n.properties.privateData[s]=this.readBoolean()}}for(const{encoded:t,glyph:i}of r){const r=new Type1CharString,s=r.convert(t,a,this.seacAnalysisEnabled);let o=r.output;s&&(o=[14]);const c={glyphName:i,charstring:o,width:r.width,lsb:r.lsb,seac:r.seac};".notdef"===i?n.charstrings.unshift(c):n.charstrings.push(c);if(e.builtInEncoding){const t=e.builtInEncoding.indexOf(i);t>-1&&void 0===e.widths[t]&&t>=e.firstChar&&t<=e.lastChar&&(e.widths[t]=r.width)}}return n}extractFontHeader(e){let t;for(;null!==(t=this.getToken());)if("/"===t){t=this.getToken();switch(t){case"FontMatrix":const a=this.readNumberArray();e.fontMatrix=a;break;case"Encoding":const r=this.getToken();let i;if(/^\d+$/.test(r)){i=[];const e=0|parseInt(r,10);this.getToken();for(let a=0;a=i){s+=a;for(;s=0&&(r[e]=i)}}return type1FontGlyphMapping(e,r,a)}hasGlyphId(e){if(e<0||e>=this.numGlyphs)return!1;if(0===e)return!0;return this.charstrings[e-1].charstring.length>0}getSeacs(e){const t=[];for(let a=0,r=e.length;a0;e--)t[e]-=t[e-1];f.setByName(e,t)}n.topDict.privateDict=f;const p=new CFFIndex;for(h=0,u=r.length;h0&&e.toUnicode.amend(t)}class fonts_Glyph{constructor(e,t,a,r,i,n,s,o,c){this.originalCharCode=e;this.fontChar=t;this.unicode=a;this.accent=r;this.width=i;this.vmetric=n;this.operatorListId=s;this.isSpace=o;this.isInFont=c}get category(){return shadow(this,"category",function getCharUnicodeCategory(e){const t=Dr.get(e);if(t)return t;const a=e.match(Mr),r={isWhitespace:!!a?.[1],isZeroWidthDiacritic:!!a?.[2],isInvisibleFormatMark:!!a?.[3]};Dr.set(e,r);return r}(this.unicode),!0)}}function int16(e,t){return(e<<8)+t}function writeSignedInt16(e,t,a){e[t+1]=a;e[t]=a>>>8}function signedInt16(e,t){const a=(e<<8)+t;return 32768&a?a-65536:a}function string16(e){return String.fromCharCode(e>>8&255,255&e)}function safeString16(e){e>32767?e=32767:e<-32768&&(e=-32768);return String.fromCharCode(e>>8&255,255&e)}function isTrueTypeCollectionFile(e){return"ttcf"===bytesToString(e.peekBytes(4))}function getFontFileType(e,{type:t,subtype:a,composite:r}){let i,n;if(function isTrueTypeFile(e){const t=e.peekBytes(4);return 65536===readUint32(t,0)||"true"===bytesToString(t)}(e)||isTrueTypeCollectionFile(e))i=r?"CIDFontType2":"TrueType";else if(function isOpenTypeFile(e){return"OTTO"===bytesToString(e.peekBytes(4))}(e))i=r?"CIDFontType2":"OpenType";else if(function isType1File(e){const t=e.peekBytes(2);return 37===t[0]&&33===t[1]||128===t[0]&&1===t[1]}(e))i=r?"CIDFontType0":"MMType1"===t?"MMType1":"Type1";else if(function isCFFFile(e){const t=e.peekBytes(4);return t[0]>=1&&t[3]>=1&&t[3]<=4}(e))if(r){i="CIDFontType0";n="CIDFontType0C"}else{i="MMType1"===t?"MMType1":"Type1";n="Type1C"}else{warn("getFontFileType: Unable to detect correct font file Type/Subtype.");i=t;n=a}return[i,n]}function applyStandardFontGlyphMap(e,t){for(const a in t)e[+a]=t[a]}function buildToFontChar(e,t,a){const r=[];let i;for(let a=0,n=e.length;ah){c++;if(c>=bi.length){warn("Ran out of space in font private use area.");break}l=bi[c][0];h=bi[c][1]}const p=l++;0===g&&(g=a);let m=r.get(f);if("string"==typeof m)if(1===m.length)m=m.codePointAt(0);else{if(!u){u=new Map;for(let e=64256;e<=64335;e++){const t=String.fromCharCode(e).normalize("NFKD");t.length>1&&u.set(t,e)}}m=u.get(m)||m.codePointAt(0)}if(m&&!(d=m,bi[0][0]<=d&&d<=bi[0][1]||bi[1][0]<=d&&d<=bi[1][1])&&!o.has(g)){n.set(m,g);o.add(g)}i[p]=g;s[f]=p}var d;return{toFontChar:s,charCodeToGlyphId:i,toUnicodeExtraMap:n,nextAvailableFontCharCode:l}}function createCmapTable(e,t,a){const r=function getRanges(e,t,a){const r=[];for(const t in e)e[t]>=a||r.push({fontCharCode:0|t,glyphId:e[t]});if(t)for(const[e,i]of t)i>=a||r.push({fontCharCode:e,glyphId:i});0===r.length&&r.push({fontCharCode:0,glyphId:0});r.sort(((e,t)=>e.fontCharCode-t.fontCharCode));const i=[],n=r.length;for(let e=0;e65535?2:1;let n,s,o,c,l="\0\0"+string16(i)+"\0\0"+string32(4+8*i);for(n=r.length-1;n>=0&&!(r[n][0]<=65535);--n);const h=n+1;r[n][0]<65535&&65535===r[n][1]&&(r[n][1]=65534);const u=r[n][1]<65535?1:0,d=h+u,f=OpenTypeFileBuilder.getSearchParams(d,2);let g,p,m,b,y="",w="",S="",x="",k="",C=0;for(n=0,s=h;n0){w+="ÿÿ";y+="ÿÿ";S+="\0";x+="\0\0"}const v="\0\0"+string16(2*d)+string16(f.range)+string16(f.entry)+string16(f.rangeShift)+w+"\0\0"+y+S+x+k;let F="",O="";if(i>1){l+="\0\0\n"+string32(4+8*i+4+v.length);F="";for(n=0,s=r.length;ne||!o)&&(o=e);c 123 are reserved for internal usage");s|=1<65535&&(c=65535)}else{o=0;c=255}const h=e.bbox||[0,0,0,0],u=a.unitsPerEm||(e.fontMatrix?1/Math.max(...e.fontMatrix.slice(0,4).map(Math.abs)):1e3),d=e.ascentScaled?1:u/yi,f=a.ascent||Math.round(d*(e.ascent||h[3]));let g=a.descent||Math.round(d*(e.descent||h[1]));g>0&&e.descent>0&&h[1]<0&&(g=-g);const p=a.yMax||f,m=-a.yMin||-g;return"\0$ô\0\0\0Š»\0\0\0ŒŠ»\0\0ß\x001\0\0\0\0"+String.fromCharCode(e.fixedPitch?9:0)+"\0\0\0\0\0\0"+string32(r)+string32(i)+string32(n)+string32(s)+"*21*"+string16(e.italicAngle?1:0)+string16(o||e.firstChar)+string16(c||e.lastChar)+string16(f)+string16(g)+"\0d"+string16(p)+string16(m)+"\0\0\0\0\0\0\0\0"+string16(e.xHeight)+string16(e.capHeight)+string16(0)+string16(o||e.firstChar)+"\0"}function createPostTable(e){return"\0\0\0"+string32(Math.floor(65536*e.italicAngle))+"\0\0\0\0"+string32(e.fixedPitch?1:0)+"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}function createPostscriptName(e){return e.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g,"").slice(0,63)}function createNameTable(e,t){t||(t=[[],[]]);const a=[t[0][0]||"Original licence",t[0][1]||e,t[0][2]||"Unknown",t[0][3]||"uniqueID",t[0][4]||e,t[0][5]||"Version 0.11",t[0][6]||createPostscriptName(e),t[0][7]||"Unknown",t[0][8]||"Unknown",t[0][9]||"Unknown"],r=[];let i,n,s,o,c;for(i=0,n=a.length;i0;if((s||o)&&"CIDFontType2"===a&&this.cidEncoding.startsWith("Identity-")){const a=e.cidToGidMap,r=[];applyStandardFontGlyphMap(r,ti());/Arial-?Black/i.test(t)?applyStandardFontGlyphMap(r,ai()):/Calibri/i.test(t)&&applyStandardFontGlyphMap(r,ri());if(a){for(const e in r){const t=r[e];void 0!==a[t]&&(r[+e]=a[t])}a.length!==this.toUnicode.length&&e.hasIncludedToUnicodeMap&&this.toUnicode instanceof IdentityToUnicodeMap&&this.toUnicode.forEach((function(e,t){const i=r[e];void 0===a[i]&&(r[+e]=t)}))}this.toUnicode instanceof IdentityToUnicodeMap||this.toUnicode.forEach((function(e,t){r[+e]=t}));this.toFontChar=r;this.toUnicode=new ToUnicodeMap(r)}else if(/Symbol/i.test(r))this.toFontChar=buildToFontChar(Cr,Fr(),this.differences);else if(/Dingbats/i.test(r))this.toFontChar=buildToFontChar(vr,Ir(),this.differences);else if(s||o){const e=buildToFontChar(this.defaultEncoding,Fr(),this.differences);"CIDFontType2"!==a||this.cidEncoding.startsWith("Identity-")||this.toUnicode instanceof IdentityToUnicodeMap||this.toUnicode.forEach((function(t,a){e[+t]=a}));this.toFontChar=e}else{const e=Fr(),a=[];this.toUnicode.forEach(((t,r)=>{if(!this.composite){const a=getUnicodeForGlyph(this.differences[t]||this.defaultEncoding[t],e);-1!==a&&(r=a)}a[+t]=r}));this.composite&&this.toUnicode instanceof IdentityToUnicodeMap&&/Tahoma|Verdana/i.test(t)&&applyStandardFontGlyphMap(a,ti());this.toFontChar=a}amendFallbackToUnicode(e);this.loadedName=r.split("-",1)[0]}checkAndRepair(e,t,a){const r=["OS/2","cmap","head","hhea","hmtx","maxp","name","post","loca","glyf","fpgm","prep","cvt ","CFF "];function readTables(e,t){const a=Object.create(null);a["OS/2"]=null;a.cmap=null;a.head=null;a.hhea=null;a.hmtx=null;a.maxp=null;a.name=null;a.post=null;for(let i=0;i>>0,r=e.getInt32()>>>0,i=e.getInt32()>>>0,n=e.pos;e.pos=e.start||0;e.skip(r);const s=e.getBytes(i);e.pos=n;if("head"===t){s[8]=s[9]=s[10]=s[11]=0;s[17]|=32}return{tag:t,checksum:a,length:i,offset:r,data:s}}function readOpenTypeHeader(e){return{version:e.getString(4),numTables:e.getUint16(),searchRange:e.getUint16(),entrySelector:e.getUint16(),rangeShift:e.getUint16()}}function sanitizeGlyph(e,t,a,r,i,n){const s={length:0,sizeOfInstructions:0};if(t<0||t>=e.length||a>e.length||a-t<=12)return s;const o=e.subarray(t,a),c=signedInt16(o[2],o[3]),l=signedInt16(o[4],o[5]),h=signedInt16(o[6],o[7]),u=signedInt16(o[8],o[9]);if(c>h){writeSignedInt16(o,2,h);writeSignedInt16(o,6,c)}if(l>u){writeSignedInt16(o,4,u);writeSignedInt16(o,8,l)}const d=signedInt16(o[0],o[1]);if(d<0){if(d<-1)return s;r.set(o,i);s.length=o.length;return s}let f,g=10,p=0;for(f=0;fo.length)return s;if(!n&&b>0){r.set(o.subarray(0,m),i);r.set([0,0],i+m);r.set(o.subarray(y,S),i+m+2);S-=b;o.length-S>3&&(S=S+3&-4);s.length=S;return s}if(o.length-S>3){S=S+3&-4;r.set(o.subarray(0,S),i);s.length=S;return s}r.set(o,i);s.length=o.length;return s}function readNameTable(e){const a=(t.start||0)+e.offset;t.pos=a;const r=[[],[]],i=[],n=e.length,s=a+n;if(0!==t.getUint16()||n<6)return[r,i];const o=t.getUint16(),c=t.getUint16();let l,h;for(l=0;ls)continue;t.pos=n;const o=e.name;if(e.encoding){let a="";for(let r=0,i=e.length;r0&&(l+=e-1)}}else{if(m||y){warn("TT: nested FDEFs not allowed");p=!0}m=!0;u=l;s=d.pop();t.functionsDefined[s]={data:c,i:l}}else if(!m&&!y){s=d.at(-1);if(isNaN(s))info("TT: CALL empty stack (or invalid entry).");else{t.functionsUsed[s]=!0;if(s in t.functionsStackDeltas){const e=d.length+t.functionsStackDeltas[s];if(e<0){warn("TT: CALL invalid functions stack delta.");t.hintsValid=!1;return}d.length=e}else if(s in t.functionsDefined&&!g.includes(s)){f.push({data:c,i:l,stackTop:d.length-1});g.push(s);o=t.functionsDefined[s];if(!o){warn("TT: CALL non-existent function");t.hintsValid=!1;return}c=o.data;l=o.i}}}if(!m&&!y){let t=0;e<=142?t=i[e]:e>=192&&e<=223?t=-1:e>=224&&(t=-2);if(e>=113&&e<=117){r=d.pop();isNaN(r)||(t=2*-r)}for(;t<0&&d.length>0;){d.pop();t++}for(;t>0;){d.push(NaN);t--}}}t.tooComplexToFollowFunctions=p;const w=[c];l>c.length&&w.push(new Uint8Array(l-c.length));if(u>h){warn("TT: complementing a missing function tail");w.push(new Uint8Array([34,45]))}!function foldTTTable(e,t){if(t.length>1){let a,r,i=0;for(a=0,r=t.length;a>>0,n=[];for(let t=0;t>>0);const s={ttcTag:t,majorVersion:a,minorVersion:r,numFonts:i,offsetTable:n};switch(a){case 1:return s;case 2:s.dsigTag=e.getInt32()>>>0;s.dsigLength=e.getInt32()>>>0;s.dsigOffset=e.getInt32()>>>0;return s}throw new FormatError(`Invalid TrueType Collection majorVersion: ${a}.`)}(e),i=t.split("+");let n;for(let s=0;s0||!(a.cMap instanceof IdentityCMap));if("OTTO"===n.version&&!t||!s.head||!s.hhea||!s.maxp||!s.post){c=new Stream(s["CFF "].data);o=new CFFFont(c,a);return this.convert(e,o,a)}delete s.glyf;delete s.loca;delete s.fpgm;delete s.prep;delete s["cvt "];this.isOpenType=!0}if(!s.maxp)throw new FormatError('Required "maxp" table is not found');t.pos=(t.start||0)+s.maxp.offset;let h=t.getInt32();const u=t.getUint16();if(65536!==h&&20480!==h){if(6===s.maxp.length)h=20480;else{if(!(s.maxp.length>=32))throw new FormatError('"maxp" table has a wrong version number');h=65536}!function writeUint32(e,t,a){e[t+3]=255&a;e[t+2]=a>>>8;e[t+1]=a>>>16;e[t]=a>>>24}(s.maxp.data,0,h)}if(a.scaleFactors?.length===u&&l){const{scaleFactors:e}=a,t=int16(s.head.data[50],s.head.data[51]),r=new GlyfTable({glyfTable:s.glyf.data,isGlyphLocationsLong:t,locaTable:s.loca.data,numGlyphs:u});r.scale(e);const{glyf:i,loca:n,isLocationLong:o}=r.write();s.glyf.data=i;s.loca.data=n;if(o!==!!t){s.head.data[50]=0;s.head.data[51]=o?1:0}const c=s.hmtx.data;for(let t=0;t>8&255;c[a+1]=255&r;writeSignedInt16(c,a+2,Math.round(e[t]*signedInt16(c[a+2],c[a+3])))}}let d=u+1,f=!0;if(d>65535){f=!1;d=u;warn("Not enough space in glyfs to duplicate first glyph.")}let g=0,p=0;if(h>=65536&&s.maxp.length>=32){t.pos+=8;if(t.getUint16()>2){s.maxp.data[14]=0;s.maxp.data[15]=2}t.pos+=4;g=t.getUint16();t.pos+=4;p=t.getUint16()}s.maxp.data[4]=d>>8;s.maxp.data[5]=255&d;const m=function sanitizeTTPrograms(e,t,a,r){const i={functionsDefined:[],functionsUsed:[],functionsStackDeltas:[],tooComplexToFollowFunctions:!1,hintsValid:!0};e&&sanitizeTTProgram(e,i);t&&sanitizeTTProgram(t,i);e&&function checkInvalidFunctions(e,t){if(!e.tooComplexToFollowFunctions)if(e.functionsDefined.length>t){warn("TT: more functions defined than expected");e.hintsValid=!1}else for(let a=0,r=e.functionsUsed.length;at){warn("TT: invalid function id: "+a);e.hintsValid=!1;return}if(e.functionsUsed[a]&&!e.functionsDefined[a]){warn("TT: undefined function: "+a);e.hintsValid=!1;return}}}(i,r);if(a&&1&a.length){const e=new Uint8Array(a.length+1);e.set(a.data);a.data=e}return i.hintsValid}(s.fpgm,s.prep,s["cvt "],g);if(!m){delete s.fpgm;delete s.prep;delete s["cvt "]}!function sanitizeMetrics(e,t,a,r,i,n){if(!t){a&&(a.data=null);return}e.pos=(e.start||0)+t.offset;e.pos+=4;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;e.pos+=2;const s=e.getUint16();e.pos+=8;e.pos+=2;let o=e.getUint16();if(0!==s){if(!(2&int16(r.data[44],r.data[45]))){t.data[22]=0;t.data[23]=0}}if(o>i){info(`The numOfMetrics (${o}) should not be greater than the numGlyphs (${i}).`);o=i;t.data[34]=(65280&o)>>8;t.data[35]=255&o}const c=i-o-(a.length-4*o>>1);if(c>0){const e=new Uint8Array(a.length+2*c);e.set(a.data);if(n){e[a.length]=a.data[2];e[a.length+1]=a.data[3]}a.data=e}}(t,s.hhea,s.hmtx,s.head,d,f);if(!s.head)throw new FormatError('Required "head" table is not found');!function sanitizeHead(e,t,a){const r=e.data,i=function int32(e,t,a,r){return(e<<24)+(t<<16)+(a<<8)+r}(r[0],r[1],r[2],r[3]);if(i>>16!=1){info("Attempting to fix invalid version in head table: "+i);r[0]=0;r[1]=1;r[2]=0;r[3]=0}const n=int16(r[50],r[51]);if(n<0||n>1){info("Attempting to fix invalid indexToLocFormat in head table: "+n);const e=t+1;if(a===e<<1){r[50]=0;r[51]=0}else{if(a!==e<<2)throw new FormatError("Could not fix indexToLocFormat: "+n);r[50]=0;r[51]=1}}}(s.head,u,l?s.loca.length:0);let b=Object.create(null);if(l){const e=int16(s.head.data[50],s.head.data[51]),t=function sanitizeGlyphLocations(e,t,a,r,i,n,s){let o,c,l;if(r){o=4;c=function fontItemDecodeLong(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]};l=function fontItemEncodeLong(e,t,a){e[t]=a>>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}}else{o=2;c=function fontItemDecode(e,t){return e[t]<<9|e[t+1]<<1};l=function fontItemEncode(e,t,a){e[t]=a>>9&255;e[t+1]=a>>1&255}}const h=n?a+1:a,u=o*(1+h),d=new Uint8Array(u);d.set(e.data.subarray(0,u));e.data=d;const f=t.data,g=f.length,p=new Uint8Array(g);let m,b;const y=[];for(m=0,b=0;mg&&(e=g);y.push({index:m,offset:e,endOffset:0})}y.sort(((e,t)=>e.offset-t.offset));for(m=0;me.index-t.index));for(m=0;ms&&(s=e.sizeOfInstructions);x+=t;l(d,b,x)}if(0===x){const e=new Uint8Array([0,1,0,0,0,0,0,0,0,0,0,0,0,0,49,0]);for(m=0,b=o;ma+x)t.data=p.subarray(0,a+x);else{t.data=new Uint8Array(a+x);t.data.set(p.subarray(0,x))}t.data.set(p.subarray(0,a),x);l(e.data,d.length-o,x+a)}else t.data=p.subarray(0,x);return{missingGlyphs:S,maxSizeOfInstructions:s}}(s.loca,s.glyf,u,e,m,f,p);b=t.missingGlyphs;if(h>=65536&&s.maxp.length>=32){s.maxp.data[26]=t.maxSizeOfInstructions>>8;s.maxp.data[27]=255&t.maxSizeOfInstructions}}if(!s.hhea)throw new FormatError('Required "hhea" table is not found');if(0===s.hhea.data[10]&&0===s.hhea.data[11]){s.hhea.data[10]=255;s.hhea.data[11]=255}const y={unitsPerEm:int16(s.head.data[18],s.head.data[19]),yMax:signedInt16(s.head.data[42],s.head.data[43]),yMin:signedInt16(s.head.data[38],s.head.data[39]),ascent:signedInt16(s.hhea.data[4],s.hhea.data[5]),descent:signedInt16(s.hhea.data[6],s.hhea.data[7]),lineGap:signedInt16(s.hhea.data[8],s.hhea.data[9])};this.ascent=y.ascent/y.unitsPerEm;this.descent=y.descent/y.unitsPerEm;this.lineGap=y.lineGap/y.unitsPerEm;if(this.cssFontInfo?.lineHeight){this.lineHeight=this.cssFontInfo.metrics.lineHeight;this.lineGap=this.cssFontInfo.metrics.lineGap}else this.lineHeight=this.ascent-this.descent+this.lineGap;s.post&&function readPostScriptTable(e,a,r){const i=(t.start||0)+e.offset;t.pos=i;const n=i+e.length,s=t.getInt32();t.skip(28);let o,c,l=!0;switch(s){case 65536:o=jr;break;case 131072:const e=t.getUint16();if(e!==r){l=!1;break}const i=[];for(c=0;c=32768){l=!1;break}i.push(e)}if(!l)break;const h=[],u=[];for(;t.pos65535)throw new FormatError("Max size of CID is 65,535");let i=-1;t?i=r:void 0!==e[r]&&(i=e[r]);i>=0&&i>>0;let h=!1;if(o?.platformId!==i||o?.encodingId!==n){if(0!==i||0!==n&&1!==n&&3!==n)if(1===i&&0===n)h=!0;else if(3!==i||1!==n||!r&&o){if(a&&3===i&&0===n){h=!0;let a=!0;if(e>3;e.push(r);a=Math.max(r,a)}const r=[];for(let e=0;e<=a;e++)r.push({firstCode:t.getUint16(),entryCount:t.getUint16(),idDelta:signedInt16(t.getByte(),t.getByte()),idRangePos:t.pos+t.getUint16()});for(let a=0;a<256;a++)if(0===e[a]){t.pos=r[0].idRangePos+2*a;f=t.getUint16();u.push({charCode:a,glyphId:f})}else{const i=r[e[a]];for(d=0;d>1;t.skip(6);const a=[];let r;for(r=0;r>1)-(e-r);i.offsetIndex=s;o=Math.max(o,s+i.end-i.start+1)}else i.offsetIndex=-1}const c=[];for(d=0;d>>0;for(d=0;d>>0,a=t.getInt32()>>>0;let r=t.getInt32()>>>0;for(let t=e;t<=a;t++)u.push({charCode:t,glyphId:r++})}}}u.sort(((e,t)=>e.charCode-t.charCode));const g=[],p=new Set;for(const e of u){const{charCode:t}=e;if(!p.has(t)){p.add(t);g.push(e)}}return{platformId:o.platformId,encodingId:o.encodingId,mappings:g,hasShortCmap:h}}(s.cmap,t,this.isSymbolicFont,a.hasEncoding),r=e.platformId,i=e.encodingId,n=e.mappings;let o=[],c=!1;!a.hasEncoding||"MacRomanEncoding"!==a.baseEncodingName&&"WinAnsiEncoding"!==a.baseEncodingName||(o=getEncoding(a.baseEncodingName));if(a.hasEncoding&&!this.isSymbolicFont&&(3===r&&1===i||1===r&&0===i)){const e=Fr();for(let t=0;t<256;t++){let s;s=void 0!==this.differences[t]?this.differences[t]:o.length&&""!==o[t]?o[t]:Ar[t];if(!s)continue;const c=recoverGlyphName(s,e);let l;3===r&&1===i?l=e[c]:1===r&&0===i&&(l=xr.indexOf(c));if(void 0===l){if(!a.glyphNames&&a.hasIncludedToUnicodeMap&&!(this.toUnicode instanceof IdentityToUnicodeMap)){const e=this.toUnicode.get(t);e&&(l=e.codePointAt(0))}if(void 0===l)continue}for(const e of n)if(e.charCode===l){w[t]=e.glyphId;break}}}else if(0===r){for(const e of n)w[e.charCode]=e.glyphId;c=!0}else if(3===r&&0===i)for(const e of n){let t=e.charCode;t>=61440&&t<=61695&&(t&=255);w[t]=e.glyphId}else for(const e of n)w[e.charCode]=e.glyphId;if(a.glyphNames&&(o.length||this.differences.length))for(let e=0;e<256;++e){if(!c&&void 0!==w[e])continue;const t=this.differences[e]||o[e];if(!t)continue;const r=a.glyphNames.indexOf(t);r>0&&hasGlyph(r)&&(w[e]=r)}}0===w.length&&(w[0]=0);let S=d-1;f||(S=0);if(!a.cssFontInfo){const e=adjustMapping(w,hasGlyph,S,this.toUnicode);this.toFontChar=e.toFontChar;s.cmap={tag:"cmap",data:createCmapTable(e.charCodeToGlyphId,e.toUnicodeExtraMap,d)};s["OS/2"]&&function validateOS2Table(e,t){t.pos=(t.start||0)+e.offset;const a=t.getUint16();t.skip(60);const r=t.getUint16();if(a<4&&768&r)return!1;if(t.getUint16()>t.getUint16())return!1;t.skip(6);if(0===t.getUint16())return!1;e.data[8]=e.data[9]=0;return!0}(s["OS/2"],t)||(s["OS/2"]={tag:"OS/2",data:createOS2Table(a,e.charCodeToGlyphId,y)})}if(!l)try{c=new Stream(s["CFF "].data);o=new CFFParser(c,a,Rr).parse();o.duplicateFirstGlyph();const e=new CFFCompiler(o);s["CFF "].data=e.compile()}catch{warn("Failed to compile font "+a.loadedName)}if(s.name){const[t,r]=readNameTable(s.name);s.name.data=createNameTable(e,t);this.psName=t[0][6]||null;a.composite||function adjustTrueTypeToUnicode(e,t,a){if(e.isInternalFont)return;if(e.hasIncludedToUnicodeMap)return;if(e.hasEncoding)return;if(e.toUnicode instanceof IdentityToUnicodeMap)return;if(!t)return;if(0===a.length)return;if(e.defaultEncoding===kr)return;for(const e of a)if(!isWinNameRecord(e))return;const r=kr,i=[],n=Fr();for(const e in r){const t=r[e];if(""===t)continue;const a=n[t];void 0!==a&&(i[e]=String.fromCharCode(a))}i.length>0&&e.toUnicode.amend(i)}(a,this.isSymbolicFont,r)}else s.name={tag:"name",data:createNameTable(this.name)};const x=new OpenTypeFileBuilder(n.version);for(const e in s)x.addTable(e,s[e].data);return x.toArray()}convert(e,a,r){r.fixedPitch=!1;r.builtInEncoding&&function adjustType1ToUnicode(e,t){if(e.isInternalFont)return;if(e.hasIncludedToUnicodeMap)return;if(t===e.defaultEncoding)return;if(e.toUnicode instanceof IdentityToUnicodeMap)return;const a=[],r=Fr();for(const i in t){if(e.hasEncoding&&(e.baseEncodingName||void 0!==e.differences[i]))continue;const n=getUnicodeForGlyph(t[i],r);-1!==n&&(a[i]=String.fromCharCode(n))}a.length>0&&e.toUnicode.amend(a)}(r,r.builtInEncoding);let i=1;a instanceof CFFFont&&(i=a.numGlyphs-1);const n=a.getGlyphMapping(r);let s=null,o=n,c=null;if(!r.cssFontInfo){s=adjustMapping(n,a.hasGlyphId.bind(a),i,this.toUnicode);this.toFontChar=s.toFontChar;o=s.charCodeToGlyphId;c=s.toUnicodeExtraMap}const l=a.numGlyphs;function getCharCodes(e,t){let a=null;for(const r in e)t===e[r]&&(a||=[]).push(0|r);return a}function createCharCode(e,t){for(const a in e)if(t===e[a])return 0|a;s.charCodeToGlyphId[s.nextAvailableFontCharCode]=t;return s.nextAvailableFontCharCode++}const h=a.seacs;if(s&&h?.length){const e=r.fontMatrix||t,i=a.getCharset(),o=Object.create(null);for(let t in h){t|=0;const a=h[t],r=Ar[a[2]],c=Ar[a[3]],l=i.indexOf(r),u=i.indexOf(c);if(l<0||u<0)continue;const d={x:a[0]*e[0]+a[1]*e[2]+e[4],y:a[0]*e[1]+a[1]*e[3]+e[5]},f=getCharCodes(n,t);if(f)for(const e of f){const t=s.charCodeToGlyphId,a=createCharCode(t,l),r=createCharCode(t,u);o[e]={baseFontCharCode:a,accentFontCharCode:r,accentOffset:d}}}r.seacMap=o}const u=r.fontMatrix?1/Math.max(...r.fontMatrix.slice(0,4).map(Math.abs)):1e3,d=new OpenTypeFileBuilder("OTTO");d.addTable("CFF ",a.data);d.addTable("OS/2",createOS2Table(r,o));d.addTable("cmap",createCmapTable(o,c,l));d.addTable("head","\0\0\0\0\0\0\0\0\0\0_<õ\0\0"+safeString16(u)+"\0\0\0\0ž\v~'\0\0\0\0ž\v~'\0\0"+safeString16(r.descent)+"ÿ"+safeString16(r.ascent)+string16(r.italicAngle?2:0)+"\0\0\0\0\0\0\0");d.addTable("hhea","\0\0\0"+safeString16(r.ascent)+safeString16(r.descent)+"\0\0ÿÿ\0\0\0\0\0\0"+safeString16(r.capHeight)+safeString16(Math.tan(r.italicAngle)*r.xHeight)+"\0\0\0\0\0\0\0\0\0\0\0\0"+string16(l));d.addTable("hmtx",function fontFieldsHmtx(){const e=a.charstrings,t=a.cff?a.cff.widths:null;let r="\0\0\0\0";for(let a=1,i=l;a=65520&&e<=65535?0:e>=62976&&e<=63743?Or()[e]||e:173===e?45:e}(a)}this.isType3Font&&(i=a);let h=null;if(this.seacMap?.[e]){l=!0;const t=this.seacMap[e];a=t.baseFontCharCode;h={fontChar:String.fromCodePoint(t.accentFontCharCode),offset:t.accentOffset}}let u="";"number"==typeof a&&(a<=1114111?u=String.fromCodePoint(a):warn(`charToGlyph - invalid fontCharCode: ${a}`));if(this.missingFile&&this.vertical&&1===u.length){const e=_r()[u.charCodeAt(0)];e&&(u=c=String.fromCharCode(e))}n=new fonts_Glyph(e,u,c,h,r,o,i,t,l);return this._glyphCache[e]=n}charsToGlyphs(e){let t=this._charsCache[e];if(t)return t;t=[];if(this.cMap){const a=Object.create(null),r=e.length;let i=0;for(;it.length%2==1,r=this.toUnicode instanceof IdentityToUnicodeMap?e=>this.toUnicode.charCodeOf(e):e=>this.toUnicode.charCodeOf(String.fromCodePoint(e));for(let i=0,n=e.length;i55295&&(n<57344||n>65533)&&i++;if(this.toUnicode){const e=r(n);if(-1!==e){if(hasCurrentBufErrors()){t.push(a.join(""));a.length=0}for(let t=(this.cMap?this.cMap.getCharCodeLength(e):1)-1;t>=0;t--)a.push(String.fromCharCode(e>>8*t&255));continue}}if(!hasCurrentBufErrors()){t.push(a.join(""));a.length=0}a.push(String.fromCodePoint(n))}t.push(a.join(""));return t}}class ErrorFont{constructor(e){this.error=e;this.loadedName="g_font_error";this.missingFile=!0}charsToGlyphs(){return[]}encodeString(e){return[e]}exportData(){return{error:this.error}}}const xi=2,Ai=3,ki=4,Ci=5,vi=6,Fi=7;class Pattern{constructor(){unreachable("Cannot initialize Pattern.")}static parseShading(e,t,a,r,i,n){const s=e instanceof BaseStream?e.dict:e,o=s.get("ShadingType");try{switch(o){case xi:case Ai:return new RadialAxialShading(s,t,a,r,i,n);case ki:case Ci:case vi:case Fi:return new MeshShading(e,t,a,r,i,n);default:throw new FormatError("Unsupported ShadingType: "+o)}}catch(e){if(e instanceof MissingDataException)throw e;warn(e);return new DummyShading}}}class BaseShading{static SMALL_NUMBER=1e-6;getIR(){unreachable("Abstract method `getIR` called.")}}class RadialAxialShading extends BaseShading{constructor(e,t,a,r,i,n){super();this.shadingType=e.get("ShadingType");let s=0;this.shadingType===xi?s=4:this.shadingType===Ai&&(s=6);this.coordsArr=e.getArray("Coords");if(!isNumberArray(this.coordsArr,s))throw new FormatError("RadialAxialShading: Invalid /Coords array.");const o=ColorSpaceUtils.parse({cs:e.getRaw("CS")||e.getRaw("ColorSpace"),xref:t,resources:a,pdfFunctionFactory:r,globalColorSpaceCache:i,localColorSpaceCache:n});this.bbox=lookupNormalRect(e.getArray("BBox"),null);let c=0,l=1;const h=e.getArray("Domain");isNumberArray(h,2)&&([c,l]=h);let u=!1,d=!1;const f=e.getArray("Extend");(function isBooleanArray(e,t){return Array.isArray(e)&&(null===t||e.length===t)&&e.every((e=>"boolean"==typeof e))})(f,2)&&([u,d]=f);if(!(this.shadingType!==Ai||u&&d)){const[e,t,a,r,i,n]=this.coordsArr,s=Math.hypot(e-r,t-i);a<=n+s&&n<=a+s&&warn("Unsupported radial gradient.")}this.extendStart=u;this.extendEnd=d;const g=e.getRaw("Function"),p=r.create(g,!0),m=(l-c)/840,b=this.colorStops=[];if(c>=l||m<=0){info("Bad shading domain.");return}const y=new Float32Array(o.numComps),w=new Float32Array(1);let S=0;w[0]=c;p(w,0,y,0);const x=new Uint8ClampedArray(3);o.getRgb(y,0,x);let[k,C,v]=x;b.push([0,Util.makeHexColor(k,C,v)]);let F=1;w[0]=c+m;p(w,0,y,0);o.getRgb(y,0,x);let[O,T,M]=x,D=O-k+1,R=T-C+1,N=M-v+1,E=O-k-1,L=T-C-1,j=M-v-1;for(let e=2;e<840;e++){w[0]=c+e*m;p(w,0,y,0);o.getRgb(y,0,x);const[t,a,r]=x,i=e-S;D=Math.min(D,(t-k+1)/i);R=Math.min(R,(a-C+1)/i);N=Math.min(N,(r-v+1)/i);E=Math.max(E,(t-k-1)/i);L=Math.max(L,(a-C-1)/i);j=Math.max(j,(r-v-1)/i);if(!(E<=D&&L<=R&&j<=N)){const e=Util.makeHexColor(O,T,M);b.push([F/840,e]);D=t-O+1;R=a-T+1;N=r-M+1;E=t-O-1;L=a-T-1;j=r-M-1;S=F;k=O;C=T;v=M}F=e;O=t;T=a;M=r}b.push([1,Util.makeHexColor(O,T,M)]);let _="transparent";e.has("Background")&&(_=o.getRgbHex(e.get("Background"),0));if(!u){b.unshift([0,_]);b[1][0]+=BaseShading.SMALL_NUMBER}if(!d){b.at(-1)[0]-=BaseShading.SMALL_NUMBER;b.push([1,_])}this.colorStops=b}getIR(){const{coordsArr:e,shadingType:t}=this;let a,r,i,n,s;if(t===xi){r=[e[0],e[1]];i=[e[2],e[3]];n=null;s=null;a="axial"}else if(t===Ai){r=[e[0],e[1]];i=[e[3],e[4]];n=e[2];s=e[5];a="radial"}else unreachable(`getPattern type unknown: ${t}`);return["RadialAxial",a,this.bbox,this.colorStops,r,i,n,s]}}class MeshStreamReader{constructor(e,t){this.stream=e;this.context=t;this.buffer=0;this.bufferLength=0;const a=t.numComps;this.tmpCompsBuf=new Float32Array(a);const r=t.colorSpace.numComps;this.tmpCsCompsBuf=t.colorFn?new Float32Array(r):this.tmpCompsBuf}get hasData(){if(this.stream.end)return this.stream.pos0)return!0;const e=this.stream.getByte();if(e<0)return!1;this.buffer=e;this.bufferLength=8;return!0}readBits(e){const{stream:t}=this;let{buffer:a,bufferLength:r}=this;if(32===e){if(0===r)return t.getInt32()>>>0;a=a<<24|t.getByte()<<16|t.getByte()<<8|t.getByte();const e=t.getByte();this.buffer=e&(1<>r)>>>0}if(8===e&&0===r)return t.getByte();for(;r>r}align(){this.buffer=0;this.bufferLength=0}readFlag(){return this.readBits(this.context.bitsPerFlag)}readCoordinate(){const{bitsPerCoordinate:e,decode:t}=this.context,a=this.readBits(e),r=this.readBits(e),i=e<32?1/((1<n?n:e;t=t>s?s:t;a=ae*i[t])):a;let s,o=-2;const c=[];for(const[e,t]of r.map(((e,t)=>[e,t])).sort((([e],[t])=>e-t)))if(-1!==e)if(e===o+1){s.push(n[t]);o+=1}else{o=e;s=[n[t]];c.push(e,s)}return c}(e),a=new Dict(null);a.set("BaseFont",Name.get(e));a.set("Type",Name.get("Font"));a.set("Subtype",Name.get("CIDFontType2"));a.set("Encoding",Name.get("Identity-H"));a.set("CIDToGIDMap",Name.get("Identity"));a.set("W",t);a.set("FirstChar",t[0]);a.set("LastChar",t.at(-2)+t.at(-1).length-1);const r=new Dict(null);a.set("FontDescriptor",r);const i=new Dict(null);i.set("Ordering","Identity");i.set("Registry","Adobe");i.set("Supplement",0);a.set("CIDSystemInfo",i);return a}class PostScriptParser{constructor(e){this.lexer=e;this.operators=[];this.token=null;this.prev=null}nextToken(){this.prev=this.token;this.token=this.lexer.getToken()}accept(e){if(this.token.type===e){this.nextToken();return!0}return!1}expect(e){if(this.accept(e))return!0;throw new FormatError(`Unexpected symbol: found ${this.token.type} expected ${e}.`)}parse(){this.nextToken();this.expect(yn.LBRACE);this.parseBlock();this.expect(yn.RBRACE);return this.operators}parseBlock(){for(;;)if(this.accept(yn.NUMBER))this.operators.push(this.prev.value);else if(this.accept(yn.OPERATOR))this.operators.push(this.prev.value);else{if(!this.accept(yn.LBRACE))return;this.parseCondition()}}parseCondition(){const e=this.operators.length;this.operators.push(null,null);this.parseBlock();this.expect(yn.RBRACE);if(this.accept(yn.IF)){this.operators[e]=this.operators.length;this.operators[e+1]="jz"}else{if(!this.accept(yn.LBRACE))throw new FormatError("PS Function: error parsing conditional.");{const t=this.operators.length;this.operators.push(null,null);const a=this.operators.length;this.parseBlock();this.expect(yn.RBRACE);this.expect(yn.IFELSE);this.operators[t]=this.operators.length;this.operators[t+1]="j";this.operators[e]=a;this.operators[e+1]="jz"}}}}const yn={LBRACE:0,RBRACE:1,NUMBER:2,OPERATOR:3,IF:4,IFELSE:5};class PostScriptToken{static get opCache(){return shadow(this,"opCache",Object.create(null))}constructor(e,t){this.type=e;this.value=t}static getOperator(e){return PostScriptToken.opCache[e]||=new PostScriptToken(yn.OPERATOR,e)}static get LBRACE(){return shadow(this,"LBRACE",new PostScriptToken(yn.LBRACE,"{"))}static get RBRACE(){return shadow(this,"RBRACE",new PostScriptToken(yn.RBRACE,"}"))}static get IF(){return shadow(this,"IF",new PostScriptToken(yn.IF,"IF"))}static get IFELSE(){return shadow(this,"IFELSE",new PostScriptToken(yn.IFELSE,"IFELSE"))}}class PostScriptLexer{constructor(e){this.stream=e;this.nextChar();this.strBuf=[]}nextChar(){return this.currentChar=this.stream.getByte()}getToken(){let e=!1,t=this.currentChar;for(;;){if(t<0)return wa;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!isWhiteSpace(t))break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return new PostScriptToken(yn.NUMBER,this.getNumber());case 123:this.nextChar();return PostScriptToken.LBRACE;case 125:this.nextChar();return PostScriptToken.RBRACE}const a=this.strBuf;a.length=0;a[0]=String.fromCharCode(t);for(;(t=this.nextChar())>=0&&(t>=65&&t<=90||t>=97&&t<=122);)a.push(String.fromCharCode(t));const r=a.join("");switch(r.toLowerCase()){case"if":return PostScriptToken.IF;case"ifelse":return PostScriptToken.IFELSE;default:return PostScriptToken.getOperator(r)}}getNumber(){let e=this.currentChar;const t=this.strBuf;t.length=0;t[0]=String.fromCharCode(e);for(;(e=this.nextChar())>=0&&(e>=48&&e<=57||45===e||46===e);)t.push(String.fromCharCode(e));const a=parseFloat(t.join(""));if(isNaN(a))throw new FormatError(`Invalid floating point number: ${a}`);return a}}class BaseLocalCache{constructor(e){this._onlyRefs=!0===e?.onlyRefs;if(!this._onlyRefs){this._nameRefMap=new Map;this._imageMap=new Map}this._imageCache=new RefSetCache}getByName(e){this._onlyRefs&&unreachable("Should not call `getByName` method.");const t=this._nameRefMap.get(e);return t?this.getByRef(t):this._imageMap.get(e)||null}getByRef(e){return this._imageCache.get(e)||null}set(e,t,a){unreachable("Abstract method `set` called.")}}class LocalImageCache extends BaseLocalCache{set(e,t=null,a){if("string"!=typeof e)throw new Error('LocalImageCache.set - expected "name" argument.');if(t){if(this._imageCache.has(t))return;this._nameRefMap.set(e,t);this._imageCache.put(t,a)}else this._imageMap.has(e)||this._imageMap.set(e,a)}}class LocalColorSpaceCache extends BaseLocalCache{set(e=null,t=null,a){if("string"!=typeof e&&!t)throw new Error('LocalColorSpaceCache.set - expected "name" and/or "ref" argument.');if(t){if(this._imageCache.has(t))return;null!==e&&this._nameRefMap.set(e,t);this._imageCache.put(t,a)}else this._imageMap.has(e)||this._imageMap.set(e,a)}}class LocalFunctionCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('LocalFunctionCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}}class LocalGStateCache extends BaseLocalCache{set(e,t=null,a){if("string"!=typeof e)throw new Error('LocalGStateCache.set - expected "name" argument.');if(t){if(this._imageCache.has(t))return;this._nameRefMap.set(e,t);this._imageCache.put(t,a)}else this._imageMap.has(e)||this._imageMap.set(e,a)}}class LocalTilingPatternCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('LocalTilingPatternCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}}class RegionalImageCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('RegionalImageCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}}class GlobalColorSpaceCache extends BaseLocalCache{constructor(e){super({onlyRefs:!0})}set(e=null,t,a){if(!t)throw new Error('GlobalColorSpaceCache.set - expected "ref" argument.');this._imageCache.has(t)||this._imageCache.put(t,a)}clear(){this._imageCache.clear()}}class GlobalImageCache{static NUM_PAGES_THRESHOLD=2;static MIN_IMAGES_TO_CACHE=10;static MAX_BYTE_SIZE=5e7;#H=new RefSet;constructor(){this._refCache=new RefSetCache;this._imageCache=new RefSetCache}get#W(){let e=0;for(const t of this._imageCache)e+=t.byteSize;return e}get#z(){return!(this._imageCache.size+e)):null}class PDFFunction{static getSampleArray(e,t,a,r){let i,n,s=1;for(i=0,n=e.length;i>c)*h;l&=(1<0&&(d=n[u-1]);let f=a[1];u>1,c=r.length>>1,l=new PostScriptEvaluator(s),h=Object.create(null);let u=8192;const d=new Float32Array(c);return function constructPostScriptFn(e,t,a,r){let n,s,f="";const g=d;for(n=0;ne&&(s=e)}m[n]=s}if(u>0){u--;h[f]=m}a.set(m,r)}}}function isPDFFunction(e){let t;if(e instanceof Dict)t=e;else{if(!(e instanceof BaseStream))return!1;t=e.dict}return t.has("FunctionType")}class PostScriptStack{static MAX_STACK_SIZE=100;constructor(e){this.stack=e?Array.from(e):[]}push(e){if(this.stack.length>=PostScriptStack.MAX_STACK_SIZE)throw new Error("PostScript function stack overflow.");this.stack.push(e)}pop(){if(this.stack.length<=0)throw new Error("PostScript function stack underflow.");return this.stack.pop()}copy(e){if(this.stack.length+e>=PostScriptStack.MAX_STACK_SIZE)throw new Error("PostScript function stack overflow.");const t=this.stack;for(let a=t.length-e,r=e-1;r>=0;r--,a++)t.push(t[a])}index(e){this.push(this.stack[this.stack.length-e-1])}roll(e,t){const a=this.stack,r=a.length-e,i=a.length-1,n=r+(t-Math.floor(t/e)*e);for(let e=r,t=i;e0?t.push(s<>o);break;case"ceiling":s=t.pop();t.push(Math.ceil(s));break;case"copy":s=t.pop();t.copy(s);break;case"cos":s=t.pop();t.push(Math.cos(s%360/180*Math.PI));break;case"cvi":s=0|t.pop();t.push(s);break;case"cvr":break;case"div":o=t.pop();s=t.pop();t.push(s/o);break;case"dup":t.copy(1);break;case"eq":o=t.pop();s=t.pop();t.push(s===o);break;case"exch":t.roll(2,1);break;case"exp":o=t.pop();s=t.pop();t.push(s**o);break;case"false":t.push(!1);break;case"floor":s=t.pop();t.push(Math.floor(s));break;case"ge":o=t.pop();s=t.pop();t.push(s>=o);break;case"gt":o=t.pop();s=t.pop();t.push(s>o);break;case"idiv":o=t.pop();s=t.pop();t.push(s/o|0);break;case"index":s=t.pop();t.index(s);break;case"le":o=t.pop();s=t.pop();t.push(s<=o);break;case"ln":s=t.pop();t.push(Math.log(s));break;case"log":s=t.pop();t.push(Math.log10(s));break;case"lt":o=t.pop();s=t.pop();t.push(s=t?new AstLiteral(t):e.max<=t?e:new AstMin(e,t)}class PostScriptCompiler{compile(e,t,a){const r=[],i=[],n=t.length>>1,s=a.length>>1;let o,c,l,h,u,d,f,g,p=0;for(let e=0;et.min){o.unshift("Math.max(",n,", ");o.push(")")}if(s4){r=!0;t=0}else{r=!1;t=1}const c=[];for(n=0;n=0&&"ET"===An[e];--e)An[e]="EN";for(let e=n+1;e0&&(t=An[n-1]);let a=u;e+1g&&isOdd(g)&&(m=g)}for(g=p;g>=m;--g){let e=-1;for(n=0,s=c.length;n=0){reverseValues(xn,e,n);e=-1}}else e<0&&(e=n);e>=0&&reverseValues(xn,e,c.length)}for(n=0,s=xn.length;n"!==e||(xn[n]="")}return createBidiText(xn.join(""),r)}const kn={style:"normal",weight:"normal"},Cn={style:"normal",weight:"bold"},vn={style:"italic",weight:"normal"},Fn={style:"italic",weight:"bold"},In=new Map([["Times-Roman",{local:["Times New Roman","Times-Roman","Times","Liberation Serif","Nimbus Roman","Nimbus Roman L","Tinos","Thorndale","TeX Gyre Termes","FreeSerif","Linux Libertine O","Libertinus Serif","DejaVu Serif","Bitstream Vera Serif","Ubuntu"],style:kn,ultimate:"serif"}],["Times-Bold",{alias:"Times-Roman",style:Cn,ultimate:"serif"}],["Times-Italic",{alias:"Times-Roman",style:vn,ultimate:"serif"}],["Times-BoldItalic",{alias:"Times-Roman",style:Fn,ultimate:"serif"}],["Helvetica",{local:["Helvetica","Helvetica Neue","Arial","Arial Nova","Liberation Sans","Arimo","Nimbus Sans","Nimbus Sans L","A030","TeX Gyre Heros","FreeSans","DejaVu Sans","Albany","Bitstream Vera Sans","Arial Unicode MS","Microsoft Sans Serif","Apple Symbols","Cantarell"],path:"LiberationSans-Regular.ttf",style:kn,ultimate:"sans-serif"}],["Helvetica-Bold",{alias:"Helvetica",path:"LiberationSans-Bold.ttf",style:Cn,ultimate:"sans-serif"}],["Helvetica-Oblique",{alias:"Helvetica",path:"LiberationSans-Italic.ttf",style:vn,ultimate:"sans-serif"}],["Helvetica-BoldOblique",{alias:"Helvetica",path:"LiberationSans-BoldItalic.ttf",style:Fn,ultimate:"sans-serif"}],["Courier",{local:["Courier","Courier New","Liberation Mono","Nimbus Mono","Nimbus Mono L","Cousine","Cumberland","TeX Gyre Cursor","FreeMono","Linux Libertine Mono O","Libertinus Mono"],style:kn,ultimate:"monospace"}],["Courier-Bold",{alias:"Courier",style:Cn,ultimate:"monospace"}],["Courier-Oblique",{alias:"Courier",style:vn,ultimate:"monospace"}],["Courier-BoldOblique",{alias:"Courier",style:Fn,ultimate:"monospace"}],["ArialBlack",{local:["Arial Black"],style:{style:"normal",weight:"900"},fallback:"Helvetica-Bold"}],["ArialBlack-Bold",{alias:"ArialBlack"}],["ArialBlack-Italic",{alias:"ArialBlack",style:{style:"italic",weight:"900"},fallback:"Helvetica-BoldOblique"}],["ArialBlack-BoldItalic",{alias:"ArialBlack-Italic"}],["ArialNarrow",{local:["Arial Narrow","Liberation Sans Narrow","Helvetica Condensed","Nimbus Sans Narrow","TeX Gyre Heros Cn"],style:kn,fallback:"Helvetica"}],["ArialNarrow-Bold",{alias:"ArialNarrow",style:Cn,fallback:"Helvetica-Bold"}],["ArialNarrow-Italic",{alias:"ArialNarrow",style:vn,fallback:"Helvetica-Oblique"}],["ArialNarrow-BoldItalic",{alias:"ArialNarrow",style:Fn,fallback:"Helvetica-BoldOblique"}],["Calibri",{local:["Calibri","Carlito"],style:kn,fallback:"Helvetica"}],["Calibri-Bold",{alias:"Calibri",style:Cn,fallback:"Helvetica-Bold"}],["Calibri-Italic",{alias:"Calibri",style:vn,fallback:"Helvetica-Oblique"}],["Calibri-BoldItalic",{alias:"Calibri",style:Fn,fallback:"Helvetica-BoldOblique"}],["Wingdings",{local:["Wingdings","URW Dingbats"],style:kn}],["Wingdings-Regular",{alias:"Wingdings"}],["Wingdings-Bold",{alias:"Wingdings"}]]),On=new Map([["Arial-Black","ArialBlack"]]);function getFamilyName(e){const t=new Set(["thin","extralight","ultralight","demilight","semilight","light","book","regular","normal","medium","demibold","semibold","bold","extrabold","ultrabold","black","heavy","extrablack","ultrablack","roman","italic","oblique","ultracondensed","extracondensed","condensed","semicondensed","normal","semiexpanded","expanded","extraexpanded","ultraexpanded","bolditalic"]);return e.split(/[- ,+]+/g).filter((e=>!t.has(e.toLowerCase()))).join(" ")}function generateFont({alias:e,local:t,path:a,fallback:r,style:i,ultimate:n},s,o,c=!0,l=!0,h=""){const u={style:null,ultimate:null};if(t){const e=h?` ${h}`:"";for(const a of t)s.push(`local(${a}${e})`)}if(e){const t=In.get(e),n=h||function getStyleToAppend(e){switch(e){case Cn:return"Bold";case vn:return"Italic";case Fn:return"Bold Italic";default:if("bold"===e?.weight)return"Bold";if("italic"===e?.style)return"Italic"}return""}(i);Object.assign(u,generateFont(t,s,o,c&&!r,l&&!a,n))}i&&(u.style=i);n&&(u.ultimate=n);if(c&&r){const e=In.get(r),{ultimate:t}=generateFont(e,s,o,c,l&&!a,h);u.ultimate||=t}l&&a&&o&&s.push(`url(${o}${a})`);return u}function getFontSubstitution(e,t,a,r,i,n){if(r.startsWith("InvalidPDFjsFont_"))return null;"TrueType"!==n&&"Type1"!==n||!/^[A-Z]{6}\+/.test(r)||(r=r.slice(7));const s=r=normalizeFontName(r);let o=e.get(s);if(o)return o;let c=In.get(r);if(!c)for(const[e,t]of On)if(r.startsWith(e)){r=`${t}${r.substring(e.length)}`;c=In.get(r);break}let l=!1;if(!c){c=In.get(i);l=!0}const h=`${t.getDocId()}_s${t.createFontId()}`;if(!c){if(!validateFontName(r)){warn(`Cannot substitute the font because of its name: ${r}`);e.set(s,null);return null}const t=/bold/gi.test(r),a=/oblique|italic/gi.test(r),i=t&&a&&Fn||t&&Cn||a&&vn||kn;o={css:`"${getFamilyName(r)}",${h}`,guessFallback:!0,loadedName:h,baseFontName:r,src:`local(${r})`,style:i};e.set(s,o);return o}const u=[];l&&validateFontName(r)&&u.push(`local(${r})`);const{style:d,ultimate:f}=generateFont(c,u,a),g=null===f,p=g?"":`,${f}`;o={css:`"${getFamilyName(r)}",${h}${p}`,guessFallback:g,loadedName:h,baseFontName:r,src:u.join(","),style:d};e.set(s,o);return o}const Tn=3285377520,Mn=4294901760,Dn=65535;class MurmurHash3_64{constructor(e){this.h1=e?4294967295&e:Tn;this.h2=e?4294967295&e:Tn}update(e){let t,a;if("string"==typeof e){t=new Uint8Array(2*e.length);a=0;for(let r=0,i=e.length;r>>8;t[a++]=255&i}}}else{if(!ArrayBuffer.isView(e))throw new Error("Invalid data format, must be a string or TypedArray.");t=e.slice();a=t.byteLength}const r=a>>2,i=a-4*r,n=new Uint32Array(t.buffer,0,r);let s=0,o=0,c=this.h1,l=this.h2;const h=3432918353,u=461845907,d=11601,f=13715;for(let e=0;e>>17;s=s*u&Mn|s*f&Dn;c^=s;c=c<<13|c>>>19;c=5*c+3864292196}else{o=n[e];o=o*h&Mn|o*d&Dn;o=o<<15|o>>>17;o=o*u&Mn|o*f&Dn;l^=o;l=l<<13|l>>>19;l=5*l+3864292196}s=0;switch(i){case 3:s^=t[4*r+2]<<16;case 2:s^=t[4*r+1]<<8;case 1:s^=t[4*r];s=s*h&Mn|s*d&Dn;s=s<<15|s>>>17;s=s*u&Mn|s*f&Dn;1&r?c^=s:l^=s}this.h1=c;this.h2=l}hexdigest(){let e=this.h1,t=this.h2;e^=t>>>1;e=3981806797*e&Mn|36045*e&Dn;t=4283543511*t&Mn|(2950163797*(t<<16|e>>>16)&Mn)>>>16;e^=t>>>1;e=444984403*e&Mn|60499*e&Dn;t=3301882366*t&Mn|(3120437893*(t<<16|e>>>16)&Mn)>>>16;e^=t>>>1;return(e>>>0).toString(16).padStart(8,"0")+(t>>>0).toString(16).padStart(8,"0")}}function resizeImageMask(e,t,a,r,i,n){const s=i*n;let o;o=t<=8?new Uint8Array(s):t<=16?new Uint16Array(s):new Uint32Array(s);const c=a/i,l=r/n;let h,u,d,f,g=0;const p=new Uint16Array(i),m=a;for(h=0;h0&&Number.isInteger(a.height)&&a.height>0&&(a.width!==f||a.height!==g)){warn("PDFImage - using the Width/Height of the image data, rather than the image dictionary.");f=a.width;g=a.height}else{const e="number"==typeof f&&f>0,t="number"==typeof g&&g>0;if(!e||!t){if(!a.fallbackDims)throw new FormatError(`Invalid image width: ${f} or height: ${g}`);warn("PDFImage - using the Width/Height of the parent image, for SMask/Mask data.");e||(f=a.fallbackDims.width);t||(g=a.fallbackDims.height)}}this.width=f;this.height=g;this.interpolate=h.get("I","Interpolate");this.imageMask=h.get("IM","ImageMask")||!1;this.matte=h.get("Matte")||!1;let p=a.bitsPerComponent;if(!p){p=h.get("BPC","BitsPerComponent");if(!p){if(!this.imageMask)throw new FormatError(`Bits per component missing in image: ${this.imageMask}`);p=1}}this.bpc=p;if(!this.imageMask){let i=h.getRaw("CS")||h.getRaw("ColorSpace");const n=!!i;if(n)this.jpxDecoderOptions?.smaskInData&&(i=Name.get("DeviceRGBA"));else if(this.jpxDecoderOptions)i=Name.get("DeviceRGBA");else switch(a.numComps){case 1:i=Name.get("DeviceGray");break;case 3:i=Name.get("DeviceRGB");break;case 4:i=Name.get("DeviceCMYK");break;default:throw new Error(`Images with ${a.numComps} color components not supported.`)}this.colorSpace=ColorSpaceUtils.parse({cs:i,xref:e,resources:r?t:null,pdfFunctionFactory:o,globalColorSpaceCache:c,localColorSpaceCache:l});this.numComps=this.colorSpace.numComps;if(this.jpxDecoderOptions){this.jpxDecoderOptions.numComponents=n?this.numComps:0;this.jpxDecoderOptions.isIndexedColormap="Indexed"===this.colorSpace.name}}this.decode=h.getArray("D","Decode");this.needsDecode=!1;if(this.decode&&(this.colorSpace&&!this.colorSpace.isDefaultDecode(this.decode,p)||s&&!ColorSpace.isDefaultDecode(this.decode,1))){this.needsDecode=!0;const e=(1<0,c=(r+7>>3)*i,l=e.getBytes(c),h=1===r&&1===i&&o===(0===l.length||!!(128&l[0]));if(h)return{isSingleOpaquePixel:h};if(t){if(ImageResizer.needsToBeResized(r,i)){const e=new Uint8ClampedArray(r*i*4);convertBlackAndWhiteToRGBA({src:l,dest:e,width:r,height:i,nonBlackColor:0,inverseDecode:o});return ImageResizer.createImage({kind:v,data:e,width:r,height:i,interpolate:n})}const e=new OffscreenCanvas(r,i),t=e.getContext("2d"),a=t.createImageData(r,i);convertBlackAndWhiteToRGBA({src:l,dest:a.data,width:r,height:i,nonBlackColor:0,inverseDecode:o});t.putImageData(a,0,0);return{data:null,width:r,height:i,interpolate:n,bitmap:e.transferToImageBitmap()}}const u=l.byteLength;let d;if(e instanceof DecodeStream&&(!o||c===u))d=l;else if(o){d=new Uint8Array(c);d.set(l);d.fill(255,u)}else d=new Uint8Array(l);if(o)for(let e=0;e>7&1;s[d+1]=u>>6&1;s[d+2]=u>>5&1;s[d+3]=u>>4&1;s[d+4]=u>>3&1;s[d+5]=u>>2&1;s[d+6]=u>>1&1;s[d+7]=1&u;d+=8}if(d>=1}}}}else{let a=0;u=0;for(d=0,h=n;d>r;i<0?i=0:i>l&&(i=l);s[d]=i;u&=(1<s[r+1]){t=255;break}}o[h]=t}}}if(o)for(h=0,d=3,u=t*r;h>3,h=t&&ImageResizer.needsToBeResized(a,r);if(!this.smask&&!this.mask&&"DeviceRGBA"===this.colorSpace.name){i.kind=v;const e=i.data=await this.getImageBytes(o*s*4,{});return t?h?ImageResizer.createImage(i,!1):this.createBitmap(v,a,r,e):i}if(!e){let e;"DeviceGray"===this.colorSpace.name&&1===c?e=k:"DeviceRGB"!==this.colorSpace.name||8!==c||this.needsDecode||(e=C);if(e&&!this.smask&&!this.mask&&a===s&&r===o){const n=await this.#$(s,o);if(n)return n;const c=await this.getImageBytes(o*l,{});if(t)return h?ImageResizer.createImage({data:c,kind:e,width:a,height:r,interpolate:this.interpolate},this.needsDecode):this.createBitmap(e,s,o,c);i.kind=e;i.data=c;if(this.needsDecode){assert(e===k,"PDFImage.createImageData: The image must be grayscale.");const t=i.data;for(let e=0,a=t.length;e>3,s=await this.getImageBytes(r*n,{internal:!0}),o=this.getComponents(s);let c,l;if(1===i){l=a*r;if(this.needsDecode)for(c=0;c0&&r[0].count++}class TimeSlotManager{static TIME_SLOT_DURATION_MS=20;static CHECK_TIME_EVERY=100;constructor(){this.reset()}check(){if(++this.checkedo){const e="Image exceeded maximum allowed size and was removed.";if(!c)throw new Error(e);warn(e);return}let g;h.has("OC")&&(g=await this.parseMarkedContentProps(h.get("OC"),e));let p,m,b;if(h.get("IM","ImageMask")||!1){p=await PDFImage.createMask({image:t,isOffscreenCanvasSupported:l&&!this.parsingType3Font});if(p.isSingleOpaquePixel){m=ta;b=[];r.addImageOps(m,b,g);if(i){const e={fn:m,args:b,optionalContent:g};n.set(i,u,e);u&&this._regionalImageCache.set(null,u,e)}return}if(this.parsingType3Font){b=function compileType3Glyph({data:e,width:t,height:a}){if(t>1e3||a>1e3)return null;const r=new Uint8Array([0,2,4,0,1,0,5,4,8,10,0,8,0,2,1,0]),i=t+1,n=new Uint8Array(i*(a+1));let s,o,c;const l=t+7&-8,h=new Uint8Array(l*a);let u=0;for(const t of e){let e=128;for(;e>0;){h[u++]=t&e?0:255;e>>=1}}let d=0;u=0;if(0!==h[u]){n[0]=1;++d}for(o=1;o>2)+(h[u+1]?4:0)+(h[u-l+1]?8:0);if(r[e]){n[c+o]=r[e];++d}u++}if(h[u-l]!==h[u]){n[c+o]=h[u]?2:4;++d}if(d>1e3)return null}u=l*(a-1);c=s*i;if(0!==h[u]){n[c]=8;++d}for(o=1;o1e3)return null;const f=new Int32Array([0,i,-1,0,-i,0,0,0,1]),g=[],{a:p,b:m,c:b,d:y,e:w,f:S}=(new DOMMatrix).scaleSelf(1/t,-1/a).translateSelf(0,-a);for(s=0;d&&s<=a;s++){let e=s*i;const a=e+t;for(;e>4;n[e]&=l>>2|l<<2}r=e%i;o=e/i|0;g.push(oa,p*r+b*o+w,m*r+y*o+S);n[e]||--d}while(c!==e);--s}return[na,[new Float32Array(g)],new Float32Array([0,0,t,a])]}(p);if(b){r.addImageOps(aa,b,g);return}warn("Cannot compile Type3 glyph.");r.addImageOps(Vt,[p],g);return}const e=`mask_${this.idFactory.createObjId()}`;r.addDependency(e);p.dataLen=p.bitmap?p.width*p.height*4:p.data.length;this._sendImgData(e,p);m=Vt;b=[{data:e,width:p.width,height:p.height,interpolate:p.interpolate,count:1}];r.addImageOps(m,b,g);if(i){const t={objId:e,fn:m,args:b,optionalContent:g};n.set(i,u,t);u&&this._regionalImageCache.set(null,u,t)}return}const y=h.has("SMask")||h.has("Mask");if(a&&d+f<200&&!y){try{const i=new PDFImage({xref:this.xref,res:e,image:t,isInline:a,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:s});p=await i.createImageData(!0,!1);r.addImageOps(Yt,[p],g)}catch(e){const t=`Unable to decode inline image: "${e}".`;if(!c)throw new Error(t);warn(t)}return}let w=`img_${this.idFactory.createObjId()}`,S=!1,x=null;if(this.parsingType3Font)w=`${this.idFactory.getDocId()}_type3_${w}`;else if(i&&u){S=this.globalImageCache.shouldCache(u,this.pageIndex);if(S){assert(!a,"Cannot cache an inline image globally.");w=`${this.idFactory.getDocId()}_${w}`}}r.addDependency(w);m=Jt;b=[w,d,f];r.addImageOps(m,b,g,y);if(S){x={objId:w,fn:m,args:b,optionalContent:g,hasMask:y,byteSize:0};if(this.globalImageCache.hasDecodeFailed(u)){this.globalImageCache.setData(u,x);this._sendImgData(w,null,S);return}if(d*f>25e4||y){const e=await this.handler.sendWithPromise("commonobj",[w,"CopyLocalImage",{imageRef:u}]);if(e){this.globalImageCache.setData(u,x);this.globalImageCache.addByteSize(u,e);return}}}PDFImage.buildImage({xref:this.xref,res:e,image:t,isInline:a,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:s}).then((async e=>{p=await e.createImageData(!1,l);p.dataLen=p.bitmap?p.width*p.height*4:p.data.length;p.ref=u;S&&this.globalImageCache.addByteSize(u,p.dataLen);return this._sendImgData(w,p,S)})).catch((e=>{warn(`Unable to decode image "${w}": "${e}".`);u&&this.globalImageCache.addDecodeFailed(u);return this._sendImgData(w,null,S)}));if(i){const e={objId:w,fn:m,args:b,optionalContent:g,hasMask:y};n.set(i,u,e);if(u){this._regionalImageCache.set(null,u,e);if(S){assert(x,"The global cache-data must be available.");this.globalImageCache.setData(u,x)}}}}handleSMask(e,t,a,r,i,n,s){const o=e.get("G"),c={subtype:e.get("S").name,backdrop:e.get("BC")},l=e.get("TR");if(isPDFFunction(l)){const e=this._pdfFunctionFactory.create(l),t=new Uint8Array(256),a=new Float32Array(1);for(let r=0;r<256;r++){a[0]=r/255;e(a,0,a,0);t[r]=255*a[0]|0}c.transferMap=t}return this.buildFormXObject(t,o,c,a,r,i.state.clone({newPath:!0}),n,s)}handleTransferFunction(e){let t;if(Array.isArray(e))t=e;else{if(!isPDFFunction(e))return null;t=[e]}const a=[];let r=0,i=0;for(const e of t){const t=this.xref.fetchIfRef(e);r++;if(isName(t,"Identity")){a.push(null);continue}if(!isPDFFunction(t))return null;const n=this._pdfFunctionFactory.create(t),s=new Uint8Array(256),o=new Float32Array(1);for(let e=0;e<256;e++){o[0]=e/255;n(o,0,o,0);s[e]=255*o[0]|0}a.push(s);i++}return 1!==r&&4!==r||0===i?null:a}handleTilingType(e,t,a,r,i,n,s,o){const c=new OperatorList,l=Dict.merge({xref:this.xref,dictArray:[i.get("Resources"),a]});return this.getOperatorList({stream:r,task:s,resources:l,operatorList:c}).then((function(){const a=c.getIR(),r=getTilingPatternIR(a,i,t);n.addDependencies(c.dependencies);n.addOp(e,r);i.objId&&o.set(null,i.objId,{operatorListIR:a,dict:i})})).catch((e=>{if(!(e instanceof AbortException)){if(!this.options.ignoreErrors)throw e;warn(`handleTilingType - ignoring pattern: "${e}".`)}}))}async handleSetFont(e,t,a,r,i,n,s=null,o=null){const c=t?.[0]instanceof Name?t[0].name:null,l=await this.loadFont(c,a,e,i,s,o);l.font.isType3Font&&r.addDependencies(l.type3Dependencies);n.font=l.font;l.send(this.handler);return l.loadedName}handleText(e,t){const a=t.font,r=a.charsToGlyphs(e);if(a.data){(!!(t.textRenderingMode&x)||"Pattern"===t.fillColorSpace.name||a.disableFontFace)&&PartialEvaluator.buildFontPaths(a,r,this.handler,this.options)}return r}ensureStateFont(e){if(e.font)return;const t=new FormatError("Missing setFont (Tf) operator before text rendering operator.");if(!this.options.ignoreErrors)throw t;warn(`ensureStateFont: "${t}".`)}async setGState({resources:e,gState:t,operatorList:a,cacheKey:r,task:i,stateManager:n,localGStateCache:s,localColorSpaceCache:o,seenRefs:c}){const l=t.objId;let h=!0;const u=[];let d=Promise.resolve();for(const[r,s]of t)switch(r){case"Type":break;case"LW":if("number"!=typeof s){warn(`Invalid LW (line width): ${s}`);break}u.push([r,Math.abs(s)]);break;case"LC":case"LJ":case"ML":case"D":case"RI":case"FL":case"CA":case"ca":u.push([r,s]);break;case"Font":h=!1;d=d.then((()=>this.handleSetFont(e,null,s[0],a,i,n.state).then((function(e){a.addDependency(e);u.push([r,[e,s[1]]])}))));break;case"BM":u.push([r,normalizeBlendMode(s)]);break;case"SMask":if(isName(s,"None")){u.push([r,!1]);break}if(s instanceof Dict){h=!1;d=d.then((()=>this.handleSMask(s,e,a,i,n,o,c)));u.push([r,!0])}else warn("Unsupported SMask type");break;case"TR":const t=this.handleTransferFunction(s);u.push([r,t]);break;case"OP":case"op":case"OPM":case"BG":case"BG2":case"UCR":case"UCR2":case"TR2":case"HT":case"SM":case"SA":case"AIS":case"TK":info("graphic state operator "+r);break;default:info("Unknown graphic state operator "+r)}await d;u.length>0&&a.addOp(De,[u]);h&&s.set(r,l,u)}loadFont(e,t,a,r,i=null,n=null){const errorFont=async()=>new TranslatedFont({loadedName:"g_font_error",font:new ErrorFont(`Font "${e}" is not available.`),dict:t});let s;if(t)t instanceof Ref&&(s=t);else{const t=a.get("Font");t&&(s=t.getRaw(e))}if(s){if(this.type3FontRefs?.has(s))return errorFont();if(this.fontCache.has(s))return this.fontCache.get(s);try{t=this.xref.fetchIfRef(s)}catch(e){warn(`loadFont - lookup failed: "${e}".`)}}if(!(t instanceof Dict)){if(!this.options.ignoreErrors&&!this.parsingType3Font){warn(`Font "${e}" is not available.`);return errorFont()}warn(`Font "${e}" is not available -- attempting to fallback to a default font.`);t=i||PartialEvaluator.fallbackFontDict}if(t.cacheKey&&this.fontCache.has(t.cacheKey))return this.fontCache.get(t.cacheKey);const{promise:o,resolve:c}=Promise.withResolvers();let l;try{l=this.preEvaluateFont(t);l.cssFontInfo=n}catch(e){warn(`loadFont - preEvaluateFont failed: "${e}".`);return errorFont()}const{descriptor:h,hash:u}=l,d=s instanceof Ref;let f;if(u&&h instanceof Dict){const e=h.fontAliases||=Object.create(null);if(e[u]){const t=e[u].aliasRef;if(d&&t&&this.fontCache.has(t)){this.fontCache.putAlias(s,t);return this.fontCache.get(s)}}else e[u]={fontID:this.idFactory.createFontId()};d&&(e[u].aliasRef=s);f=e[u].fontID}else f=this.idFactory.createFontId();assert(f?.startsWith("f"),'The "fontID" must be (correctly) defined.');if(d)this.fontCache.put(s,o);else{t.cacheKey=`cacheKey_${f}`;this.fontCache.put(t.cacheKey,o)}t.loadedName=`${this.idFactory.getDocId()}_${f}`;this.translateFont(l).then((async e=>{const i=new TranslatedFont({loadedName:t.loadedName,font:e,dict:t});if(e.isType3Font)try{await i.loadType3Data(this,a,r)}catch(e){throw new Error(`Type3 font load error: ${e}`)}c(i)})).catch((e=>{warn(`loadFont - translateFont failed: "${e}".`);c(new TranslatedFont({loadedName:t.loadedName,font:new ErrorFont(e?.message),dict:t}))}));return o}buildPath(e,t,a){const{pathMinMax:r,pathBuffer:i}=a;switch(0|e){case Xe:{const e=a.currentPointX=t[0],n=a.currentPointY=t[1],s=t[2],o=t[3],c=e+s,l=n+o;0===s||0===o?i.push(sa,e,n,oa,c,l,la):i.push(sa,e,n,oa,c,n,oa,c,l,oa,e,l,la);Util.rectBoundingBox(e,n,c,l,r);break}case Ee:{const e=a.currentPointX=t[0],n=a.currentPointY=t[1];i.push(sa,e,n);Util.pointBoundingBox(e,n,r);break}case Pe:{const e=a.currentPointX=t[0],n=a.currentPointY=t[1];i.push(oa,e,n);Util.pointBoundingBox(e,n,r);break}case Le:{const e=a.currentPointX,n=a.currentPointY,[s,o,c,l,h,u]=t;a.currentPointX=h;a.currentPointY=u;i.push(ca,s,o,c,l,h,u);Util.bezierBoundingBox(e,n,s,o,c,l,h,u,r);break}case je:{const e=a.currentPointX,n=a.currentPointY,[s,o,c,l]=t;a.currentPointX=c;a.currentPointY=l;i.push(ca,e,n,s,o,c,l);Util.bezierBoundingBox(e,n,e,n,s,o,c,l,r);break}case _e:{const e=a.currentPointX,n=a.currentPointY,[s,o,c,l]=t;a.currentPointX=c;a.currentPointY=l;i.push(ca,s,o,c,l,c,l);Util.bezierBoundingBox(e,n,s,o,c,l,c,l,r);break}case Ue:i.push(la)}}_getColorSpace(e,t,a){return ColorSpaceUtils.parse({cs:e,xref:this.xref,resources:t,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:a,asyncIfNotCached:!0})}async _handleColorSpace(e){try{return await e}catch(e){if(e instanceof AbortException)return null;if(this.options.ignoreErrors){warn(`_handleColorSpace - ignoring ColorSpace: "${e}".`);return null}throw e}}parseShading({shading:e,resources:t,localColorSpaceCache:a,localShadingPatternCache:r}){let i,n=r.get(e);if(n)return n;try{i=Pattern.parseShading(e,this.xref,t,this._pdfFunctionFactory,this.globalColorSpaceCache,a).getIR()}catch(t){if(t instanceof AbortException)return null;if(this.options.ignoreErrors){warn(`parseShading - ignoring shading: "${t}".`);r.set(e,null);return null}throw t}n=`pattern_${this.idFactory.createObjId()}`;this.parsingType3Font&&(n=`${this.idFactory.getDocId()}_type3_${n}`);r.set(e,n);this.parsingType3Font?this.handler.send("commonobj",[n,"Pattern",i]):this.handler.send("obj",[n,this.pageIndex,"Pattern",i]);return n}handleColorN(e,t,a,r,i,n,s,o,c,l){const h=a.pop();if(h instanceof Name){const u=i.getRaw(h.name),d=u instanceof Ref&&c.getByRef(u);if(d)try{const i=r.base?r.base.getRgbHex(a,0):null,n=getTilingPatternIR(d.operatorListIR,d.dict,i);e.addOp(t,n);return}catch{}const f=this.xref.fetchIfRef(u);if(f){const i=f instanceof BaseStream?f.dict:f,h=i.get("PatternType");if(h===Rn){const o=r.base?r.base.getRgbHex(a,0):null;return this.handleTilingType(t,o,n,f,i,e,s,c)}if(h===Nn){const a=i.get("Shading"),r=this.parseShading({shading:a,resources:n,localColorSpaceCache:o,localShadingPatternCache:l});if(r){const a=lookupMatrix(i.getArray("Matrix"),null);e.addOp(t,["Shading",r,a])}return}throw new FormatError(`Unknown PatternType: ${h}`)}}throw new FormatError(`Unknown PatternName: ${h}`)}_parseVisibilityExpression(e,t,a){if(++t>10){warn("Visibility expression is too deeply nested");return}const r=e.length,i=this.xref.fetchIfRef(e[0]);if(!(r<2)&&i instanceof Name){switch(i.name){case"And":case"Or":case"Not":a.push(i.name);break;default:warn(`Invalid operator ${i.name} in visibility expression`);return}for(let i=1;i0)return{type:"OCMD",expression:t}}const t=a.get("OCGs");if(Array.isArray(t)||t instanceof Dict){const e=[];if(Array.isArray(t))for(const a of t)e.push(a.toString());else e.push(t.objId);return{type:r,ids:e,policy:a.get("P")instanceof Name?a.get("P").name:null,expression:null}}if(t instanceof Ref)return{type:r,id:t.toString()}}return null}getOperatorList({stream:e,task:t,resources:a,operatorList:r,initialState:i=null,fallbackFontDict:n=null,prevRefs:s=null}){const o=e.dict?.objId,c=new RefSet(s);if(o){if(s?.has(o))throw new Error(`getOperatorList - ignoring circular reference: ${o}`);c.put(o)}a||=Dict.empty;i||=new EvalState;if(!r)throw new Error('getOperatorList: missing "operatorList" parameter');const l=this,h=this.xref,u=new LocalImageCache,d=new LocalColorSpaceCache,f=new LocalGStateCache,g=new LocalTilingPatternCache,p=new Map,m=a.get("XObject")||Dict.empty,b=a.get("Pattern")||Dict.empty,y=new StateManager(i),w=new EvaluatorPreprocessor(e,h,y),S=new TimeSlotManager;function closePendingRestoreOPS(e){for(let e=0,t=w.savedStatesDepth;e{y.state.fillColorSpace=e||ColorSpaceUtils.gray})));return}case yt:{const t=l._getColorSpace(e[0],a,d);if(t instanceof ColorSpace){y.state.strokeColorSpace=t;continue}next(l._handleColorSpace(t).then((e=>{y.state.strokeColorSpace=e||ColorSpaceUtils.gray})));return}case At:C=y.state.fillColorSpace;e=[C.getRgbHex(e,0)];i=It;break;case St:C=y.state.strokeColorSpace;e=[C.getRgbHex(e,0)];i=Ft;break;case vt:y.state.fillColorSpace=ColorSpaceUtils.gray;e=[ColorSpaceUtils.gray.getRgbHex(e,0)];i=It;break;case Ct:y.state.strokeColorSpace=ColorSpaceUtils.gray;e=[ColorSpaceUtils.gray.getRgbHex(e,0)];i=Ft;break;case Tt:y.state.fillColorSpace=ColorSpaceUtils.cmyk;e=[ColorSpaceUtils.cmyk.getRgbHex(e,0)];i=It;break;case Ot:y.state.strokeColorSpace=ColorSpaceUtils.cmyk;e=[ColorSpaceUtils.cmyk.getRgbHex(e,0)];i=Ft;break;case It:y.state.fillColorSpace=ColorSpaceUtils.rgb;e=[ColorSpaceUtils.rgb.getRgbHex(e,0)];break;case Ft:y.state.strokeColorSpace=ColorSpaceUtils.rgb;e=[ColorSpaceUtils.rgb.getRgbHex(e,0)];break;case kt:C=y.state.patternFillColorSpace;if(!C){if(isNumberArray(e,null)){e=[ColorSpaceUtils.gray.getRgbHex(e,0)];i=It;break}e=[];i=ia;break}if("Pattern"===C.name){next(l.handleColorN(r,kt,e,C,b,a,t,d,g,p));return}e=[C.getRgbHex(e,0)];i=It;break;case xt:C=y.state.patternStrokeColorSpace;if(!C){if(isNumberArray(e,null)){e=[ColorSpaceUtils.gray.getRgbHex(e,0)];i=Ft;break}e=[];i=ra;break}if("Pattern"===C.name){next(l.handleColorN(r,xt,e,C,b,a,t,d,g,p));return}e=[C.getRgbHex(e,0)];i=Ft;break;case Mt:let O;try{const t=a.get("Shading");if(!t)throw new FormatError("No shading resource found");O=t.get(e[0].name);if(!O)throw new FormatError("No shading object found")}catch(e){if(e instanceof AbortException)continue;if(l.options.ignoreErrors){warn(`getOperatorList - ignoring Shading: "${e}".`);continue}throw e}const T=l.parseShading({shading:O,resources:a,localColorSpaceCache:d,localShadingPatternCache:p});if(!T)continue;e=[T];i=Mt;break;case De:F=e[0]instanceof Name;v=e[0].name;if(F){const t=f.getByName(v);if(t){t.length>0&&r.addOp(De,[t]);e=null;continue}}next(new Promise((function(e,i){if(!F)throw new FormatError("GState must be referred to by name.");const n=a.get("ExtGState");if(!(n instanceof Dict))throw new FormatError("ExtGState should be a dictionary.");const s=n.get(v);if(!(s instanceof Dict))throw new FormatError("GState should be a dictionary.");l.setGState({resources:a,gState:s,operatorList:r,cacheKey:v,task:t,stateManager:y,localGStateCache:f,localColorSpaceCache:d,seenRefs:c}).then(e,i)})).catch((function(e){if(!(e instanceof AbortException)){if(!l.options.ignoreErrors)throw e;warn(`getOperatorList - ignoring ExtGState: "${e}".`)}})));return;case Ce:{const[t]=e;if("number"!=typeof t){warn(`Invalid setLineWidth: ${t}`);continue}e[0]=Math.abs(t);break}case Ee:case Pe:case Le:case je:case _e:case Ue:case Xe:l.buildPath(i,e,y.state);continue;case qe:case He:case We:case ze:case $e:case Ge:case Ve:case Ke:case Je:{const{state:{pathBuffer:e,pathMinMax:t}}=y;i!==He&&i!==Ve&&i!==Ke||e.push(la);if(0===e.length)r.addOp(aa,[i,[null],null]);else{r.addOp(aa,[i,[new Float32Array(e)],t.slice()]);e.length=0;t.set([1/0,1/0,-1/0,-1/0],0)}continue}case ht:r.addOp(i,[new Float32Array(e)]);continue;case Et:case Pt:case Ut:case Xt:continue;case jt:if(!(e[0]instanceof Name)){warn(`Expected name for beginMarkedContentProps arg0=${e[0]}`);r.addOp(jt,["OC",null]);continue}if("OC"===e[0].name){next(l.parseMarkedContentProps(e[1],a).then((e=>{r.addOp(jt,["OC",e])})).catch((e=>{if(!(e instanceof AbortException)){if(!l.options.ignoreErrors)throw e;warn(`getOperatorList - ignoring beginMarkedContentProps: "${e}".`);r.addOp(jt,["OC",null])}})));return}e=[e[0].name,e[1]instanceof Dict?e[1].get("MCID"):null];break;default:if(null!==e){for(x=0,k=e.length;x{if(!(e instanceof AbortException)){if(!this.options.ignoreErrors)throw e;warn(`getOperatorList - ignoring errors during "${t.name}" task: "${e}".`);closePendingRestoreOPS()}}))}getTextContent({stream:e,task:a,resources:r,stateManager:i=null,includeMarkedContent:n=!1,sink:s,seenStyles:o=new Set,viewBox:c,lang:l=null,markedContentData:h=null,disableNormalization:u=!1,keepWhiteSpace:d=!1,prevRefs:f=null}){const g=e.dict?.objId,p=new RefSet(f);if(g){if(f?.has(g))throw new Error(`getTextContent - ignoring circular reference: ${g}`);p.put(g)}r||=Dict.empty;i||=new StateManager(new TextState);n&&(h||={level:0});const m={items:[],styles:Object.create(null),lang:l},b={initialized:!1,str:[],totalWidth:0,totalHeight:0,width:0,height:0,vertical:!1,prevTransform:null,textAdvanceScale:0,spaceInFlowMin:0,spaceInFlowMax:0,trackingSpaceMin:1/0,negativeSpaceMax:-1/0,notASpace:-1/0,transform:null,fontName:null,hasEOL:!1},y=[" "," "];let w=0;function saveLastChar(e){const t=(w+1)%2,a=" "!==y[w]&&" "===y[t];y[w]=e;w=t;return!d&&a}function shouldAddWhitepsace(){return!d&&" "!==y[w]&&" "===y[(w+1)%2]}function resetLastChars(){y[0]=y[1]=" ";w=0}const S=this,x=this.xref,k=[];let C=null;const v=new LocalImageCache,F=new LocalGStateCache,O=new EvaluatorPreprocessor(e,x,i);let T;function pushWhitespace({width:e=0,height:t=0,transform:a=b.prevTransform,fontName:r=b.fontName}){m.items.push({str:" ",dir:"ltr",width:e,height:t,transform:a,fontName:r,hasEOL:!1})}function getCurrentTextTransform(){const e=T.font,a=[T.fontSize*T.textHScale,0,0,T.fontSize,0,T.textRise];if(e.isType3Font&&(T.fontSize<=1||e.isCharBBox)&&!isArrayEqual(T.fontMatrix,t)){const t=e.bbox[3]-e.bbox[1];t>0&&(a[3]*=t*T.fontMatrix[3])}return Util.transform(T.ctm,Util.transform(T.textMatrix,a))}function ensureTextContentItem(){if(b.initialized)return b;const{font:e,loadedName:t}=T;if(!o.has(t)){o.add(t);m.styles[t]={fontFamily:e.fallbackName,ascent:e.ascent,descent:e.descent,vertical:e.vertical};if(S.options.fontExtraProperties&&e.systemFontInfo){const a=m.styles[t];a.fontSubstitution=e.systemFontInfo.css;a.fontSubstitutionLoadedName=e.systemFontInfo.loadedName}}b.fontName=t;const a=b.transform=getCurrentTextTransform();if(e.vertical){b.width=b.totalWidth=Math.hypot(a[0],a[1]);b.height=b.totalHeight=0;b.vertical=!0}else{b.width=b.totalWidth=0;b.height=b.totalHeight=Math.hypot(a[2],a[3]);b.vertical=!1}const r=Math.hypot(T.textLineMatrix[0],T.textLineMatrix[1]),i=Math.hypot(T.ctm[0],T.ctm[1]);b.textAdvanceScale=i*r;const{fontSize:n}=T;b.trackingSpaceMin=.102*n;b.notASpace=.03*n;b.negativeSpaceMax=-.2*n;b.spaceInFlowMin=.102*n;b.spaceInFlowMax=.6*n;b.hasEOL=!1;b.initialized=!0;return b}function updateAdvanceScale(){if(!b.initialized)return;const e=Math.hypot(T.textLineMatrix[0],T.textLineMatrix[1]),t=Math.hypot(T.ctm[0],T.ctm[1])*e;if(t!==b.textAdvanceScale){if(b.vertical){b.totalHeight+=b.height*b.textAdvanceScale;b.height=0}else{b.totalWidth+=b.width*b.textAdvanceScale;b.width=0}b.textAdvanceScale=t}}function runBidiTransform(e){let t=e.str.join("");u||(t=function normalizeUnicode(e){if(!ma){ma=/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;ba=new Map([["ſt","ſt"]])}return e.replaceAll(ma,((e,t,a)=>t?t.normalize("NFKC"):ba.get(a)))}(t));const a=bidi(t,-1,e.vertical);return{str:a.str,dir:a.dir,width:Math.abs(e.totalWidth),height:Math.abs(e.totalHeight),transform:e.transform,fontName:e.fontName,hasEOL:e.hasEOL}}async function handleSetFont(e,i){const n=await S.loadFont(e,i,r,a);T.loadedName=n.loadedName;T.font=n.font;T.fontMatrix=n.font.fontMatrix||t}function applyInverseRotation(e,t,a){const r=Math.hypot(a[0],a[1]);return[(a[0]*e+a[1]*t)/r,(a[2]*e+a[3]*t)/r]}function compareWithLastPosition(e){const t=getCurrentTextTransform();let a=t[4],r=t[5];if(T.font?.vertical){if(ac[2]||r+ec[3])return!1}else if(a+ec[2]||rc[3])return!1;if(!T.font||!b.prevTransform)return!0;let i=b.prevTransform[4],n=b.prevTransform[5];if(i===a&&n===r)return!0;let s=-1;t[0]&&0===t[1]&&0===t[2]?s=t[0]>0?0:180:t[1]&&0===t[0]&&0===t[3]&&(s=t[1]>0?90:270);switch(s){case 0:break;case 90:[a,r]=[r,a];[i,n]=[n,i];break;case 180:[a,r,i,n]=[-a,-r,-i,-n];break;case 270:[a,r]=[-r,-a];[i,n]=[-n,-i];break;default:[a,r]=applyInverseRotation(a,r,t);[i,n]=applyInverseRotation(i,n,b.prevTransform)}if(T.font.vertical){const e=(n-r)/b.textAdvanceScale,t=a-i,s=Math.sign(b.height);if(e.5*b.width){appendEOL();return!0}resetLastChars();flushTextContentItem();return!0}if(Math.abs(t)>b.width){appendEOL();return!0}e<=s*b.notASpace&&resetLastChars();if(e<=s*b.trackingSpaceMin)if(shouldAddWhitepsace()){resetLastChars();flushTextContentItem();pushWhitespace({height:Math.abs(e)})}else b.height+=e;else if(!addFakeSpaces(e,b.prevTransform,s))if(0===b.str.length){resetLastChars();pushWhitespace({height:Math.abs(e)})}else b.height+=e;Math.abs(t)>.25*b.width&&flushTextContentItem();return!0}const o=(a-i)/b.textAdvanceScale,l=r-n,h=Math.sign(b.width);if(o.5*b.height){appendEOL();return!0}resetLastChars();flushTextContentItem();return!0}if(Math.abs(l)>b.height){appendEOL();return!0}o<=h*b.notASpace&&resetLastChars();if(o<=h*b.trackingSpaceMin)if(shouldAddWhitepsace()){resetLastChars();flushTextContentItem();pushWhitespace({width:Math.abs(o)})}else b.width+=o;else if(!addFakeSpaces(o,b.prevTransform,h))if(0===b.str.length){resetLastChars();pushWhitespace({width:Math.abs(o)})}else b.width+=o;Math.abs(l)>.25*b.height&&flushTextContentItem();return!0}function buildTextContentItem({chars:e,extraSpacing:t}){const a=T.font;if(!e){const e=T.charSpacing+t;e&&(a.vertical?T.translateTextMatrix(0,-e):T.translateTextMatrix(e*T.textHScale,0));d&&compareWithLastPosition(0);return}const r=a.charsToGlyphs(e),i=T.fontMatrix[0]*T.fontSize;for(let e=0,n=r.length;e0){const e=k.join("");k.length=0;buildTextContentItem({chars:e,extraSpacing:0})}break;case dt:if(!i.state.font){S.ensureStateFont(i.state);continue}buildTextContentItem({chars:w[0],extraSpacing:0});break;case gt:if(!i.state.font){S.ensureStateFont(i.state);continue}T.carriageReturn();buildTextContentItem({chars:w[0],extraSpacing:0});break;case pt:if(!i.state.font){S.ensureStateFont(i.state);continue}T.wordSpacing=w[0];T.charSpacing=w[1];T.carriageReturn();buildTextContentItem({chars:w[2],extraSpacing:0});break;case Nt:flushTextContentItem();C??=r.get("XObject")||Dict.empty;y=w[0]instanceof Name;b=w[0].name;if(y&&v.getByName(b))break;next(new Promise((function(e,t){if(!y)throw new FormatError("XObject must be referred to by name.");let f=C.getRaw(b);if(f instanceof Ref){if(v.getByRef(f)){e();return}if(S.globalImageCache.getData(f,S.pageIndex)){e();return}f=x.fetch(f)}if(!(f instanceof BaseStream))throw new FormatError("XObject should be a stream");const{dict:g}=f,m=g.get("Subtype");if(!(m instanceof Name))throw new FormatError("XObject should have a Name subtype");if("Form"!==m.name){v.set(b,g.objId,!0);e();return}const w=i.state.clone(),k=new StateManager(w),F=lookupMatrix(g.getArray("Matrix"),null);F&&k.transform(F);const O=g.get("Resources");enqueueChunk();const T={enqueueInvoked:!1,enqueue(e,t){this.enqueueInvoked=!0;s.enqueue(e,t)},get desiredSize(){return s.desiredSize},get ready(){return s.ready}};S.getTextContent({stream:f,task:a,resources:O instanceof Dict?O:r,stateManager:k,includeMarkedContent:n,sink:T,seenStyles:o,viewBox:c,lang:l,markedContentData:h,disableNormalization:u,keepWhiteSpace:d,prevRefs:p}).then((function(){T.enqueueInvoked||v.set(b,g.objId,!0);e()}),t)})).catch((function(e){if(!(e instanceof AbortException)){if(!S.options.ignoreErrors)throw e;warn(`getTextContent - ignoring XObject: "${e}".`)}})));return;case De:y=w[0]instanceof Name;b=w[0].name;if(y&&F.getByName(b))break;next(new Promise((function(e,t){if(!y)throw new FormatError("GState must be referred to by name.");const a=r.get("ExtGState");if(!(a instanceof Dict))throw new FormatError("ExtGState should be a dictionary.");const i=a.get(b);if(!(i instanceof Dict))throw new FormatError("GState should be a dictionary.");const n=i.get("Font");if(n){flushTextContentItem();T.fontName=null;T.fontSize=n[1];handleSetFont(null,n[0]).then(e,t)}else{F.set(b,i.objId,!0);e()}})).catch((function(e){if(!(e instanceof AbortException)){if(!S.options.ignoreErrors)throw e;warn(`getTextContent - ignoring ExtGState: "${e}".`)}})));return;case Lt:flushTextContentItem();if(n){h.level++;m.items.push({type:"beginMarkedContent",tag:w[0]instanceof Name?w[0].name:null})}break;case jt:flushTextContentItem();if(n){h.level++;let e=null;w[1]instanceof Dict&&(e=w[1].get("MCID"));m.items.push({type:"beginMarkedContentProps",id:Number.isInteger(e)?`${S.idFactory.getPageObjId()}_mc${e}`:null,tag:w[0]instanceof Name?w[0].name:null})}break;case _t:flushTextContentItem();if(n){if(0===h.level)break;h.level--;m.items.push({type:"endMarkedContent"})}break;case Re:!e||e.font===T.font&&e.fontSize===T.fontSize&&e.fontName===T.fontName||flushTextContentItem()}if(m.items.length>=s.desiredSize){g=!0;break}}if(g)next(En);else{flushTextContentItem();enqueueChunk();e()}})).catch((e=>{if(!(e instanceof AbortException)){if(!this.options.ignoreErrors)throw e;warn(`getTextContent - ignoring errors during "${a.name}" task: "${e}".`);flushTextContentItem();enqueueChunk()}}))}async extractDataStructures(e,t){const a=this.xref;let r;const i=this.readToUnicode(t.toUnicode);if(t.composite){const a=e.get("CIDSystemInfo");a instanceof Dict&&(t.cidSystemInfo={registry:stringToPDFString(a.get("Registry")),ordering:stringToPDFString(a.get("Ordering")),supplement:a.get("Supplement")});try{const t=e.get("CIDToGIDMap");t instanceof BaseStream&&(r=t.getBytes())}catch(e){if(!this.options.ignoreErrors)throw e;warn(`extractDataStructures - ignoring CIDToGIDMap data: "${e}".`)}}const n=[];let s,o=null;if(e.has("Encoding")){s=e.get("Encoding");if(s instanceof Dict){o=s.get("BaseEncoding");o=o instanceof Name?o.name:null;if(s.has("Differences")){const e=s.get("Differences");let t=0;for(const r of e){const e=a.fetchIfRef(r);if("number"==typeof e)t=e;else{if(!(e instanceof Name))throw new FormatError(`Invalid entry in 'Differences' array: ${e}`);n[t++]=e.name}}}}else if(s instanceof Name)o=s.name;else{const e="Encoding is not a Name nor a Dict";if(!this.options.ignoreErrors)throw new FormatError(e);warn(e)}"MacRomanEncoding"!==o&&"MacExpertEncoding"!==o&&"WinAnsiEncoding"!==o&&(o=null)}const c=!t.file||t.isInternalFont,l=ei()[t.name];o&&c&&l&&(o=null);if(o)t.defaultEncoding=getEncoding(o);else{const e=!!(t.flags&Pr),a=!!(t.flags&Lr);s=Ar;"TrueType"!==t.type||a||(s=kr);if(e||l){s=xr;c&&(/Symbol/i.test(t.name)?s=Cr:/Dingbats/i.test(t.name)?s=vr:/Wingdings/i.test(t.name)&&(s=kr))}t.defaultEncoding=s}t.differences=n;t.baseEncodingName=o;t.hasEncoding=!!o||n.length>0;t.dict=e;t.toUnicode=await i;const h=await this.buildToUnicode(t);t.toUnicode=h;r&&(t.cidToGidMap=this.readCidToGidMap(r,h));return t}_simpleFontToUnicode(e,t=!1){assert(!e.composite,"Must be a simple font.");const a=[],r=e.defaultEncoding.slice(),i=e.baseEncodingName,n=e.differences;for(const e in n){const t=n[e];".notdef"!==t&&(r[e]=t)}const s=Fr();for(const n in r){let o=r[n];if(""===o)continue;let c=s[o];if(void 0!==c){a[n]=String.fromCharCode(c);continue}let l=0;switch(o[0]){case"G":3===o.length&&(l=parseInt(o.substring(1),16));break;case"g":5===o.length&&(l=parseInt(o.substring(1),16));break;case"C":case"c":if(o.length>=3&&o.length<=4){const a=o.substring(1);if(t){l=parseInt(a,16);break}l=+a;if(Number.isNaN(l)&&Number.isInteger(parseInt(a,16)))return this._simpleFontToUnicode(e,!0)}break;case"u":c=getUnicodeForGlyph(o,s);-1!==c&&(l=c);break;default:switch(o){case"f_h":case"f_t":case"T_h":a[n]=o.replaceAll("_","");continue}}if(l>0&&l<=1114111&&Number.isInteger(l)){if(i&&l===+n){const e=getEncoding(i);if(e&&(o=e[n])){a[n]=String.fromCharCode(s[o]);continue}}a[n]=String.fromCodePoint(l)}}return a}async buildToUnicode(e){e.hasIncludedToUnicodeMap=e.toUnicode?.length>0;if(e.hasIncludedToUnicodeMap){!e.composite&&e.hasEncoding&&(e.fallbackToUnicode=this._simpleFontToUnicode(e));return e.toUnicode}if(!e.composite)return new ToUnicodeMap(this._simpleFontToUnicode(e));if(e.composite&&(e.cMap.builtInCMap&&!(e.cMap instanceof IdentityCMap)||"Adobe"===e.cidSystemInfo?.registry&&("GB1"===e.cidSystemInfo.ordering||"CNS1"===e.cidSystemInfo.ordering||"Japan1"===e.cidSystemInfo.ordering||"Korea1"===e.cidSystemInfo.ordering))){const{registry:t,ordering:a}=e.cidSystemInfo,r=Name.get(`${t}-${a}-UCS2`),i=await CMapFactory.create({encoding:r,fetchBuiltInCMap:this._fetchBuiltInCMapBound,useCMap:null}),n=[],s=[];e.cMap.forEach((function(e,t){if(t>65535)throw new FormatError("Max size of CID is 65,535");const a=i.lookup(t);if(a){s.length=0;for(let e=0,t=a.length;e>1;(0!==i||t.has(n))&&(a[n]=i)}return a}extractWidths(e,t,a){const r=this.xref;let i=[],n=0;const s=[];let o;if(a.composite){const t=e.get("DW");n="number"==typeof t?Math.ceil(t):1e3;const c=e.get("W");if(Array.isArray(c))for(let e=0,t=c.length;e{const t=c.get(e),r=new OperatorList;return n.getOperatorList({stream:t,task:a,resources:l,operatorList:r}).then((()=>{switch(r.fnArray[0]){case bt:this.#K(r,b);break;case mt:b||this.#J(r)}h[e]=r.getIR();for(const e of r.dependencies)i.add(e)})).catch((function(t){warn(`Type3 font resource "${e}" is not available.`);const a=new OperatorList;h[e]=a.getIR()}))}));this.#V=o.then((()=>{r.charProcOperatorList=h;if(this._bbox){r.isCharBBox=!0;r.bbox=this._bbox}}));return this.#V}#K(e,t=NaN){const a=Util.normalizeRect(e.argsArray[0].slice(2)),r=a[2]-a[0],i=a[3]-a[1],n=Math.hypot(r,i);if(0===r||0===i){e.fnArray.splice(0,1);e.argsArray.splice(0,1)}else if(0===t||Math.round(n/t)>=10){this._bbox??=[1/0,1/0,-1/0,-1/0];Util.rectBoundingBox(...a,this._bbox)}let s=0,o=e.length;for(;s=Ee&&n<=Je;if(i.variableArgs)o>s&&info(`Command ${r}: expected [0, ${s}] args, but received ${o} args.`);else{if(o!==s){const e=this.nonProcessedArgs;for(;o>s;){e.push(t.shift());o--}for(;oEvaluatorPreprocessor.MAX_INVALID_PATH_OPS)throw new FormatError(`Invalid ${e}`);warn(`Skipping ${e}`);null!==t&&(t.length=0);continue}}this.preprocessCommand(n,t);e.fn=n;e.args=t;return!0}if(a===wa)return!1;if(null!==a){null===t&&(t=[]);t.push(a);if(t.length>33)throw new FormatError("Too many arguments")}}}preprocessCommand(e,t){switch(0|e){case Be:this.stateManager.save();break;case Re:this.stateManager.restore();break;case Ne:this.stateManager.transform(t)}}}class DefaultAppearanceEvaluator extends EvaluatorPreprocessor{constructor(e){super(new StringStream(e))}parse(){const e={fn:0,args:[]},t={fontSize:0,fontName:"",fontColor:new Uint8ClampedArray(3)};try{for(;;){e.args.length=0;if(!this.read(e))break;if(0!==this.savedStatesDepth)continue;const{fn:a,args:r}=e;switch(0|a){case nt:const[e,a]=r;e instanceof Name&&(t.fontName=e.name);"number"==typeof a&&a>0&&(t.fontSize=a);break;case It:ColorSpaceUtils.rgb.getRgbItem(r,0,t.fontColor,0);break;case vt:ColorSpaceUtils.gray.getRgbItem(r,0,t.fontColor,0);break;case Tt:ColorSpaceUtils.cmyk.getRgbItem(r,0,t.fontColor,0)}}}catch(e){warn(`parseDefaultAppearance - ignoring errors: "${e}".`)}return t}}function parseDefaultAppearance(e){return new DefaultAppearanceEvaluator(e).parse()}class AppearanceStreamEvaluator extends EvaluatorPreprocessor{constructor(e,t,a,r){super(e);this.stream=e;this.evaluatorOptions=t;this.xref=a;this.globalColorSpaceCache=r;this.resources=e.dict?.get("Resources")}parse(){const e={fn:0,args:[]};let t={scaleFactor:1,fontSize:0,fontName:"",fontColor:new Uint8ClampedArray(3),fillColorSpace:ColorSpaceUtils.gray},a=!1;const r=[];try{for(;;){e.args.length=0;if(a||!this.read(e))break;const{fn:i,args:n}=e;switch(0|i){case Be:r.push({scaleFactor:t.scaleFactor,fontSize:t.fontSize,fontName:t.fontName,fontColor:t.fontColor.slice(),fillColorSpace:t.fillColorSpace});break;case Re:t=r.pop()||t;break;case ht:t.scaleFactor*=Math.hypot(n[0],n[1]);break;case nt:const[e,i]=n;e instanceof Name&&(t.fontName=e.name);"number"==typeof i&&i>0&&(t.fontSize=i*t.scaleFactor);break;case wt:t.fillColorSpace=ColorSpaceUtils.parse({cs:n[0],xref:this.xref,resources:this.resources,pdfFunctionFactory:this._pdfFunctionFactory,globalColorSpaceCache:this.globalColorSpaceCache,localColorSpaceCache:this._localColorSpaceCache});break;case At:t.fillColorSpace.getRgbItem(n,0,t.fontColor,0);break;case It:ColorSpaceUtils.rgb.getRgbItem(n,0,t.fontColor,0);break;case vt:ColorSpaceUtils.gray.getRgbItem(n,0,t.fontColor,0);break;case Tt:ColorSpaceUtils.cmyk.getRgbItem(n,0,t.fontColor,0);break;case dt:case ft:case gt:case pt:a=!0}}}catch(e){warn(`parseAppearanceStream - ignoring errors: "${e}".`)}this.stream.reset();delete t.scaleFactor;delete t.fillColorSpace;return t}get _localColorSpaceCache(){return shadow(this,"_localColorSpaceCache",new LocalColorSpaceCache)}get _pdfFunctionFactory(){return shadow(this,"_pdfFunctionFactory",new PDFFunctionFactory({xref:this.xref,isEvalSupported:this.evaluatorOptions.isEvalSupported}))}}function getPdfColor(e,t){if(e[0]===e[1]&&e[1]===e[2]){return`${numberToString(e[0]/255)} ${t?"g":"G"}`}return Array.from(e,(e=>numberToString(e/255))).join(" ")+" "+(t?"rg":"RG")}class FakeUnicodeFont{constructor(e,t){this.xref=e;this.widths=null;this.firstChar=1/0;this.lastChar=-1/0;this.fontFamily=t;const a=new OffscreenCanvas(1,1);this.ctxMeasure=a.getContext("2d",{willReadFrequently:!0});FakeUnicodeFont._fontNameId||(FakeUnicodeFont._fontNameId=1);this.fontName=Name.get(`InvalidPDFjsFont_${t}_${FakeUnicodeFont._fontNameId++}`)}get fontDescriptorRef(){if(!FakeUnicodeFont._fontDescriptorRef){const e=new Dict(this.xref);e.set("Type",Name.get("FontDescriptor"));e.set("FontName",this.fontName);e.set("FontFamily","MyriadPro Regular");e.set("FontBBox",[0,0,0,0]);e.set("FontStretch",Name.get("Normal"));e.set("FontWeight",400);e.set("ItalicAngle",0);FakeUnicodeFont._fontDescriptorRef=this.xref.getNewPersistentRef(e)}return FakeUnicodeFont._fontDescriptorRef}get descendantFontRef(){const e=new Dict(this.xref);e.set("BaseFont",this.fontName);e.set("Type",Name.get("Font"));e.set("Subtype",Name.get("CIDFontType0"));e.set("CIDToGIDMap",Name.get("Identity"));e.set("FirstChar",this.firstChar);e.set("LastChar",this.lastChar);e.set("FontDescriptor",this.fontDescriptorRef);e.set("DW",1e3);const t=[],a=[...this.widths.entries()].sort();let r=null,i=null;for(const[e,n]of a)if(r)if(e===r+i.length)i.push(n);else{t.push(r,i);r=e;i=[n]}else{r=e;i=[n]}r&&t.push(r,i);e.set("W",t);const n=new Dict(this.xref);n.set("Ordering","Identity");n.set("Registry","Adobe");n.set("Supplement",0);e.set("CIDSystemInfo",n);return this.xref.getNewPersistentRef(e)}get baseFontRef(){const e=new Dict(this.xref);e.set("BaseFont",this.fontName);e.set("Type",Name.get("Font"));e.set("Subtype",Name.get("Type0"));e.set("Encoding",Name.get("Identity-H"));e.set("DescendantFonts",[this.descendantFontRef]);e.set("ToUnicode",Name.get("Identity-H"));return this.xref.getNewPersistentRef(e)}get resources(){const e=new Dict(this.xref),t=new Dict(this.xref);t.set(this.fontName.name,this.baseFontRef);e.set("Font",t);return e}_createContext(){this.widths=new Map;this.ctxMeasure.font=`1000px ${this.fontFamily}`;return this.ctxMeasure}createFontResources(e){const t=this._createContext();for(const a of e.split(/\r\n?|\n/))for(const e of a.split("")){const a=e.charCodeAt(0);if(this.widths.has(a))continue;const r=t.measureText(e),i=Math.ceil(r.width);this.widths.set(a,i);this.firstChar=Math.min(a,this.firstChar);this.lastChar=Math.max(a,this.lastChar)}return this.resources}static getFirstPositionInfo(e,t,i){const[n,s,o,c]=e;let l=o-n,h=c-s;t%180!=0&&([l,h]=[h,l]);const u=a*i;return{coords:[0,h+r*i-u],bbox:[0,0,l,h],matrix:0!==t?getRotationMatrix(t,h,u):void 0}}createAppearance(e,t,i,n,s,o){const c=this._createContext(),l=[];let h=-1/0;for(const t of e.split(/\r\n?|\n/)){l.push(t);const e=c.measureText(t).width;h=Math.max(h,e);for(const e of codePointIter(t)){const t=String.fromCodePoint(e);let a=this.widths.get(e);if(void 0===a){const r=c.measureText(t);a=Math.ceil(r.width);this.widths.set(e,a);this.firstChar=Math.min(e,this.firstChar);this.lastChar=Math.max(e,this.lastChar)}}}h*=n/1e3;const[u,d,f,g]=t;let p=f-u,m=g-d;i%180!=0&&([p,m]=[m,p]);let b=1;h>p&&(b=p/h);let y=1;const w=a*n,S=r*n,x=w*l.length;x>m&&(y=m/x);const k=n*Math.min(b,y),C=["q",`0 0 ${numberToString(p)} ${numberToString(m)} re W n`,"BT",`1 0 0 1 0 ${numberToString(m+S)} Tm 0 Tc ${getPdfColor(s,!0)}`,`/${this.fontName.name} ${numberToString(k)} Tf`],{resources:v}=this;if(1!==(o="number"==typeof o&&o>=0&&o<=1?o:1)){C.push("/R0 gs");const e=new Dict(this.xref),t=new Dict(this.xref);t.set("ca",o);t.set("CA",o);t.set("Type",Name.get("ExtGState"));e.set("R0",t);v.set("ExtGState",e)}const F=numberToString(w);for(const e of l)C.push(`0 -${F} Td <${stringToUTF16HexString(e)}> Tj`);C.push("ET","Q");const O=C.join("\n"),T=new Dict(this.xref);T.set("Subtype",Name.get("Form"));T.set("Type",Name.get("XObject"));T.set("BBox",[0,0,p,m]);T.set("Length",O.length);T.set("Resources",v);if(i){const e=getRotationMatrix(i,p,m);T.set("Matrix",e)}const M=new StringStream(O);M.dict=T;return M}}class NameOrNumberTree{constructor(e,t,a){this.root=e;this.xref=t;this._type=a}getAll(){const e=new Map;if(!this.root)return e;const t=this.xref,a=new RefSet;a.put(this.root);const r=[this.root];for(;r.length>0;){const i=t.fetchIfRef(r.shift());if(!(i instanceof Dict))continue;if(i.has("Kids")){const e=i.get("Kids");if(!Array.isArray(e))continue;for(const t of e){if(a.has(t))throw new FormatError(`Duplicate entry in "${this._type}" tree.`);r.push(t);a.put(t)}continue}const n=i.get(this._type);if(Array.isArray(n))for(let a=0,r=n.length;a10){warn(`Search depth limit reached for "${this._type}" tree.`);return null}const i=a.get("Kids");if(!Array.isArray(i))return null;let n=0,s=i.length-1;for(;n<=s;){const r=n+s>>1,o=t.fetchIfRef(i[r]),c=o.get("Limits");if(et.fetchIfRef(c[1]))){a=o;break}n=r+1}}if(n>s)return null}const i=a.get(this._type);if(Array.isArray(i)){let a=0,r=i.length-2;for(;a<=r;){const n=a+r>>1,s=n+(1&n),o=t.fetchIfRef(i[s]);if(eo))return i[s+1];a=s+2}}}return null}get(e){return this.xref.fetchIfRef(this.getRaw(e))}}class NameTree extends NameOrNumberTree{constructor(e,t){super(e,t,"Names")}}class NumberTree extends NameOrNumberTree{constructor(e,t){super(e,t,"Nums")}}function clearGlobalCaches(){!function clearPatternCaches(){Ii=Object.create(null)}();!function clearPrimitiveCaches(){Sa=Object.create(null);xa=Object.create(null);Aa=Object.create(null)}();!function clearUnicodeCaches(){Dr.clear()}();JpxImage.cleanup()}function pickPlatformItem(e){return e instanceof Dict?e.has("UF")?e.get("UF"):e.has("F")?e.get("F"):e.has("Unix")?e.get("Unix"):e.has("Mac")?e.get("Mac"):e.has("DOS")?e.get("DOS"):null:null}class FileSpec{#Y=!1;constructor(e,t,a=!1){if(e instanceof Dict){this.xref=t;this.root=e;e.has("FS")&&(this.fs=e.get("FS"));e.has("RF")&&warn("Related file specifications are not supported");a||(e.has("EF")?this.#Y=!0:warn("Non-embedded file specifications are not supported"))}}get filename(){let e="";const t=pickPlatformItem(this.root);t&&"string"==typeof t&&(e=stringToPDFString(t,!0).replaceAll("\\\\","\\").replaceAll("\\/","/").replaceAll("\\","/"));return shadow(this,"filename",e||"unnamed")}get content(){if(!this.#Y)return null;this._contentRef||=pickPlatformItem(this.root?.get("EF"));let e=null;if(this._contentRef){const t=this.xref.fetchIfRef(this._contentRef);t instanceof BaseStream?e=t.getBytes():warn("Embedded file specification points to non-existing/invalid content")}else warn("Embedded file specification does not have any content");return e}get description(){let e="";const t=this.root?.get("Desc");t&&"string"==typeof t&&(e=stringToPDFString(t));return shadow(this,"description",e)}get serializable(){return{rawFilename:this.filename,filename:(e=this.filename,e.substring(e.lastIndexOf("/")+1)),content:this.content,description:this.description};var e}}const Pn=0,Ln=-2,jn=-3,_n=-4,Un=-5,Xn=-6,qn=-9;function isWhitespace(e,t){const a=e[t];return" "===a||"\n"===a||"\r"===a||"\t"===a}class XMLParserBase{_resolveEntities(e){return e.replaceAll(/&([^;]+);/g,((e,t)=>{if("#x"===t.substring(0,2))return String.fromCodePoint(parseInt(t.substring(2),16));if("#"===t.substring(0,1))return String.fromCodePoint(parseInt(t.substring(1),10));switch(t){case"lt":return"<";case"gt":return">";case"amp":return"&";case"quot":return'"';case"apos":return"'"}return this.onResolveEntity(t)}))}_parseContent(e,t){const a=[];let r=t;function skipWs(){for(;r"!==e[r]&&"/"!==e[r];)++r;const i=e.substring(t,r);skipWs();for(;r"!==e[r]&&"/"!==e[r]&&"?"!==e[r];){skipWs();let t="",i="";for(;r"!==e[a]&&"?"!==e[a]&&"/"!==e[a];)++a;const r=e.substring(t,a);!function skipWs(){for(;a"!==e[a+1]);)++a;return{name:r,value:e.substring(i,a),parsed:a-t}}parseXml(e){let t=0;for(;t",a);if(t<0){this.onError(qn);return}this.onEndElement(e.substring(a,t));a=t+1;break;case"?":++a;const r=this._parseProcessingInstruction(e,a);if("?>"!==e.substring(a+r.parsed,a+r.parsed+2)){this.onError(jn);return}this.onPi(r.name,r.value);a+=r.parsed+2;break;case"!":if("--"===e.substring(a+1,a+3)){t=e.indexOf("--\x3e",a+3);if(t<0){this.onError(Un);return}this.onComment(e.substring(a+3,t));a=t+3}else if("[CDATA["===e.substring(a+1,a+8)){t=e.indexOf("]]>",a+8);if(t<0){this.onError(Ln);return}this.onCdata(e.substring(a+8,t));a=t+3}else{if("DOCTYPE"!==e.substring(a+1,a+8)){this.onError(Xn);return}{const r=e.indexOf("[",a+8);let i=!1;t=e.indexOf(">",a+8);if(t<0){this.onError(_n);return}if(r>0&&t>r){t=e.indexOf("]>",a+8);if(t<0){this.onError(_n);return}i=!0}const n=e.substring(a+8,t+(i?1:0));this.onDoctype(n);a=t+(i?2:1)}}break;default:const i=this._parseContent(e,a);if(null===i){this.onError(Xn);return}let n=!1;if("/>"===e.substring(a+i.parsed,a+i.parsed+2))n=!0;else if(">"!==e.substring(a+i.parsed,a+i.parsed+1)){this.onError(qn);return}this.onBeginElement(i.name,i.attributes,n);a+=i.parsed+(n?2:1)}}else{for(;ae.textContent)).join(""):this.nodeValue||""}get children(){return this.childNodes||[]}hasChildNodes(){return this.childNodes?.length>0}searchNode(e,t){if(t>=e.length)return this;const a=e[t];if(a.name.startsWith("#")&&t0){r.push([i,0]);i=i.childNodes[0]}else{if(0===r.length)return null;for(;0!==r.length;){const[e,t]=r.pop(),a=t+1;if(a");for(const t of this.childNodes)t.dump(e);e.push(``)}else this.nodeValue?e.push(`>${encodeToXmlString(this.nodeValue)}`):e.push("/>")}else e.push(encodeToXmlString(this.nodeValue))}}class SimpleXMLParser extends XMLParserBase{constructor({hasAttributes:e=!1,lowerCaseName:t=!1}){super();this._currentFragment=null;this._stack=null;this._errorCode=Pn;this._hasAttributes=e;this._lowerCaseName=t}parseFromString(e){this._currentFragment=[];this._stack=[];this._errorCode=Pn;this.parseXml(e);if(this._errorCode!==Pn)return;const[t]=this._currentFragment;return t?{documentElement:t}:void 0}onText(e){if(function isWhitespaceString(e){for(let t=0,a=e.length;t\\376\\377([^<]+)/g,(function(e,t){const a=t.replaceAll(/\\([0-3])([0-7])([0-7])/g,(function(e,t,a,r){return String.fromCharCode(64*t+8*a+1*r)})).replaceAll(/&(amp|apos|gt|lt|quot);/g,(function(e,t){switch(t){case"amp":return"&";case"apos":return"'";case"gt":return">";case"lt":return"<";case"quot":return'"'}throw new Error(`_repair: ${t} isn't defined.`)})),r=[">"];for(let e=0,t=a.length;e=32&&t<127&&60!==t&&62!==t&&38!==t?r.push(String.fromCharCode(t)):r.push("&#x"+(65536+t).toString(16).substring(1)+";")}return r.join("")}))}_getSequence(e){const t=e.nodeName;return"rdf:bag"!==t&&"rdf:seq"!==t&&"rdf:alt"!==t?null:e.childNodes.filter((e=>"rdf:li"===e.nodeName))}_parseArray(e){if(!e.hasChildNodes())return;const[t]=e.childNodes,a=this._getSequence(t)||[];this._metadataMap.set(e.nodeName,a.map((e=>e.textContent.trim())))}_parse(e){let t=e.documentElement;if("rdf:rdf"!==t.nodeName){t=t.firstChild;for(;t&&"rdf:rdf"!==t.nodeName;)t=t.nextSibling}if(t&&"rdf:rdf"===t.nodeName&&t.hasChildNodes())for(const e of t.childNodes)if("rdf:description"===e.nodeName)for(const t of e.childNodes){const e=t.nodeName;switch(e){case"#text":continue;case"dc:creator":case"dc:subject":this._parseArray(t);continue}this._metadataMap.set(e,t.textContent.trim())}}get serializable(){return{parsedData:this._metadataMap,rawData:this._data}}}const Hn=1,Wn=2,zn=3,$n=4,Gn=5;class StructTreeRoot{constructor(e,t,a){this.xref=e;this.dict=t;this.ref=a instanceof Ref?a:null;this.roleMap=new Map;this.structParentIds=null}init(){this.readRoleMap()}#Z(e,t,a){if(!(e instanceof Ref)||t<0)return;this.structParentIds||=new RefSetCache;let r=this.structParentIds.get(e);if(!r){r=[];this.structParentIds.put(e,r)}r.push([t,a])}addAnnotationIdToPage(e,t){this.#Z(e,t,$n)}readRoleMap(){const e=this.dict.get("RoleMap");if(e instanceof Dict)for(const[t,a]of e)a instanceof Name&&this.roleMap.set(t,a.name)}static async canCreateStructureTree({catalogRef:e,pdfManager:t,newAnnotationsByPage:a}){if(!(e instanceof Ref)){warn("Cannot save the struct tree: no catalog reference.");return!1}let r=0,i=!0;for(const[e,n]of a){const{ref:a}=await t.getPage(e);if(!(a instanceof Ref)){warn(`Cannot save the struct tree: page ${e} has no ref.`);i=!0;break}for(const e of n)if(e.accessibilityData?.type){e.parentTreeId=r++;i=!1}}if(i){for(const e of a.values())for(const t of e)delete t.parentTreeId;return!1}return!0}static async createStructureTree({newAnnotationsByPage:e,xref:t,catalogRef:a,pdfManager:r,changes:i}){const n=await r.ensureCatalog("cloneDict"),s=new RefSetCache;s.put(a,n);const o=t.getNewTemporaryRef();n.set("StructTreeRoot",o);const c=new Dict(t);c.set("Type",Name.get("StructTreeRoot"));const l=t.getNewTemporaryRef();c.set("ParentTree",l);const h=[];c.set("K",h);s.put(o,c);const u=new Dict(t),d=[];u.set("Nums",d);const f=await this.#Q({newAnnotationsByPage:e,structTreeRootRef:o,structTreeRoot:null,kids:h,nums:d,xref:t,pdfManager:r,changes:i,cache:s});c.set("ParentTreeNextKey",f);s.put(l,u);for(const[e,t]of s.items())i.put(e,{data:t})}async canUpdateStructTree({pdfManager:e,newAnnotationsByPage:t}){if(!this.ref){warn("Cannot update the struct tree: no root reference.");return!1}let a=this.dict.get("ParentTreeNextKey");if(!Number.isInteger(a)||a<0){warn("Cannot update the struct tree: invalid next key.");return!1}const r=this.dict.get("ParentTree");if(!(r instanceof Dict)){warn("Cannot update the struct tree: ParentTree isn't a dict.");return!1}const i=r.get("Nums");if(!Array.isArray(i)){warn("Cannot update the struct tree: nums isn't an array.");return!1}const n=new NumberTree(r,this.xref);for(const a of t.keys()){const{pageDict:t}=await e.getPage(a);if(!t.has("StructParents"))continue;const r=t.get("StructParents");if(!Number.isInteger(r)||!Array.isArray(n.get(r))){warn(`Cannot save the struct tree: page ${a} has a wrong id.`);return!1}}let s=!0;for(const[r,i]of t){const{pageDict:t}=await e.getPage(r);StructTreeRoot.#ee({elements:i,xref:this.xref,pageDict:t,numberTree:n});for(const e of i)if(e.accessibilityData?.type){e.accessibilityData.structParent>=0||(e.parentTreeId=a++);s=!1}}if(s){for(const e of t.values())for(const t of e){delete t.parentTreeId;delete t.structTreeParent}return!1}return!0}async updateStructureTree({newAnnotationsByPage:e,pdfManager:t,changes:a}){const{ref:r,xref:i}=this,n=this.dict.clone(),s=new RefSetCache;s.put(r,n);let o,c=n.getRaw("ParentTree");if(c instanceof Ref)o=i.fetch(c);else{o=c;c=i.getNewTemporaryRef();n.set("ParentTree",c)}o=o.clone();s.put(c,o);let l=o.getRaw("Nums"),h=null;if(l instanceof Ref){h=l;l=i.fetch(h)}l=l.slice();h||o.set("Nums",l);const u=await StructTreeRoot.#Q({newAnnotationsByPage:e,structTreeRootRef:r,structTreeRoot:this,kids:null,nums:l,xref:i,pdfManager:t,changes:a,cache:s});if(-1!==u){n.set("ParentTreeNextKey",u);h&&s.put(h,l);for(const[e,t]of s.items())a.put(e,{data:t})}}static async#Q({newAnnotationsByPage:e,structTreeRootRef:t,structTreeRoot:a,kids:r,nums:i,xref:n,pdfManager:s,changes:o,cache:c}){const l=Name.get("OBJR");let h,u=-1;for(const[d,f]of e){const e=await s.getPage(d),{ref:g}=e,p=g instanceof Ref;for(const{accessibilityData:s,ref:m,parentTreeId:b,structTreeParent:y}of f){if(!s?.type)continue;const{structParent:f}=s;if(a&&Number.isInteger(f)&&f>=0){let t=(h||=new Map).get(d);if(void 0===t){t=new StructTreePage(a,e.pageDict).collectObjects(g);h.set(d,t)}const r=t?.get(f);if(r){const e=n.fetch(r).clone();StructTreeRoot.#te(e,s);o.put(r,{data:e});continue}}u=Math.max(u,b);const w=n.getNewTemporaryRef(),S=new Dict(n);StructTreeRoot.#te(S,s);await this.#ae({structTreeParent:y,tagDict:S,newTagRef:w,structTreeRootRef:t,fallbackKids:r,xref:n,cache:c});const x=new Dict(n);S.set("K",x);x.set("Type",l);p&&x.set("Pg",g);x.set("Obj",m);c.put(w,S);i.push(b,w)}}return u+1}static#te(e,{type:t,title:a,lang:r,alt:i,expanded:n,actualText:s}){e.set("S",Name.get(t));a&&e.set("T",stringToAsciiOrUTF16BE(a));r&&e.set("Lang",stringToAsciiOrUTF16BE(r));i&&e.set("Alt",stringToAsciiOrUTF16BE(i));n&&e.set("E",stringToAsciiOrUTF16BE(n));s&&e.set("ActualText",stringToAsciiOrUTF16BE(s))}static#ee({elements:e,xref:t,pageDict:a,numberTree:r}){const i=new Map;for(const t of e)if(t.structTreeParentId){const e=parseInt(t.structTreeParentId.split("_mc")[1],10);let a=i.get(e);if(!a){a=[];i.set(e,a)}a.push(t)}const n=a.get("StructParents");if(!Number.isInteger(n))return;const s=r.get(n),updateElement=(e,a,r)=>{const n=i.get(e);if(n){const e=a.getRaw("P"),i=t.fetchIfRef(e);if(e instanceof Ref&&i instanceof Dict){const e={ref:r,dict:a};for(const t of n)t.structTreeParent=e}return!0}return!1};for(const e of s){if(!(e instanceof Ref))continue;const a=t.fetch(e),r=a.get("K");if(Number.isInteger(r))updateElement(r,a,e);else if(Array.isArray(r))for(let i of r){i=t.fetchIfRef(i);if(Number.isInteger(i)&&updateElement(i,a,e))break;if(!(i instanceof Dict))continue;if(!isName(i.get("Type"),"MCR"))break;const r=i.get("MCID");if(Number.isInteger(r)&&updateElement(r,a,e))break}}}static async#ae({structTreeParent:e,tagDict:t,newTagRef:a,structTreeRootRef:r,fallbackKids:i,xref:n,cache:s}){let o,c=null;if(e){({ref:c}=e);o=e.dict.getRaw("P")||r}else o=r;t.set("P",o);const l=n.fetchIfRef(o);if(!l){i.push(a);return}let h=s.get(o);if(!h){h=l.clone();s.put(o,h)}const u=h.getRaw("K");let d=u instanceof Ref?s.get(u):null;if(!d){d=n.fetchIfRef(u);d=Array.isArray(d)?d.slice():[u];const e=n.getNewTemporaryRef();h.set("K",e);s.put(e,d)}const f=d.indexOf(c);d.splice(f>=0?f+1:d.length,0,a)}}class StructElementNode{constructor(e,t){this.tree=e;this.xref=e.xref;this.dict=t;this.kids=[];this.parseKids()}get role(){const e=this.dict.get("S"),t=e instanceof Name?e.name:"",{root:a}=this.tree;return a.roleMap.get(t)??t}parseKids(){let e=null;const t=this.dict.getRaw("Pg");t instanceof Ref&&(e=t.toString());const a=this.dict.get("K");if(Array.isArray(a))for(const t of a){const a=this.parseKid(e,this.xref.fetchIfRef(t));a&&this.kids.push(a)}else{const t=this.parseKid(e,a);t&&this.kids.push(t)}}parseKid(e,t){if(Number.isInteger(t))return this.tree.pageDict.objId!==e?null:new StructElement({type:Hn,mcid:t,pageObjId:e});if(!(t instanceof Dict))return null;const a=t.getRaw("Pg");a instanceof Ref&&(e=a.toString());const r=t.get("Type")instanceof Name?t.get("Type").name:null;if("MCR"===r){if(this.tree.pageDict.objId!==e)return null;const a=t.getRaw("Stm");return new StructElement({type:Wn,refObjId:a instanceof Ref?a.toString():null,pageObjId:e,mcid:t.get("MCID")})}if("OBJR"===r){if(this.tree.pageDict.objId!==e)return null;const a=t.getRaw("Obj");return new StructElement({type:zn,refObjId:a instanceof Ref?a.toString():null,pageObjId:e})}return new StructElement({type:Gn,dict:t})}}class StructElement{constructor({type:e,dict:t=null,mcid:a=null,pageObjId:r=null,refObjId:i=null}){this.type=e;this.dict=t;this.mcid=a;this.pageObjId=r;this.refObjId=i;this.parentNode=null}}class StructTreePage{constructor(e,t){this.root=e;this.xref=e?.xref??null;this.rootDict=e?.dict??null;this.pageDict=t;this.nodes=[]}collectObjects(e){if(!(this.root&&this.rootDict&&e instanceof Ref))return null;const t=this.rootDict.get("ParentTree");if(!t)return null;const a=this.root.structParentIds?.get(e);if(!a)return null;const r=new Map,i=new NumberTree(t,this.xref);for(const[e]of a){const t=i.getRaw(e);t instanceof Ref&&r.set(e,t)}return r}parse(e){if(!(this.root&&this.rootDict&&e instanceof Ref))return;const t=this.rootDict.get("ParentTree");if(!t)return;const a=this.pageDict.get("StructParents"),r=this.root.structParentIds?.get(e);if(!Number.isInteger(a)&&!r)return;const i=new Map,n=new NumberTree(t,this.xref);if(Number.isInteger(a)){const e=n.get(a);if(Array.isArray(e))for(const t of e)t instanceof Ref&&this.addNode(this.xref.fetch(t),i)}if(r)for(const[e,t]of r){const a=n.get(e);if(a){const e=this.addNode(this.xref.fetchIfRef(a),i);1===e?.kids?.length&&e.kids[0].type===zn&&(e.kids[0].type=t)}}}addNode(e,t,a=0){if(a>40){warn("StructTree MAX_DEPTH reached.");return null}if(!(e instanceof Dict))return null;if(t.has(e))return t.get(e);const r=new StructElementNode(this,e);t.set(e,r);const i=e.get("P");if(!(i instanceof Dict)||isName(i.get("Type"),"StructTreeRoot")){this.addTopLevelNode(e,r)||t.delete(e);return r}const n=this.addNode(i,t,a+1);if(!n)return r;let s=!1;for(const t of n.kids)if(t.type===Gn&&t.dict===e){t.parentNode=r;s=!0}s||t.delete(e);return r}addTopLevelNode(e,t){const a=this.rootDict.get("K");if(!a)return!1;if(a instanceof Dict){if(a.objId!==e.objId)return!1;this.nodes[0]=t;return!0}if(!Array.isArray(a))return!0;let r=!1;for(let i=0;i40){warn("StructTree too deep to be fully serialized.");return}const r=Object.create(null);r.role=e.role;r.children=[];t.children.push(r);let i=e.dict.get("Alt");"string"!=typeof i&&(i=e.dict.get("ActualText"));"string"==typeof i&&(r.alt=stringToPDFString(i));const n=e.dict.get("A");if(n instanceof Dict){const e=lookupNormalRect(n.getArray("BBox"),null);if(e)r.bbox=e;else{const e=n.get("Width"),t=n.get("Height");"number"==typeof e&&e>0&&"number"==typeof t&&t>0&&(r.bbox=[0,0,e,t])}}const s=e.dict.get("Lang");"string"==typeof s&&(r.lang=stringToPDFString(s));for(const t of e.kids){const e=t.type===Gn?t.parentNode:null;e?nodeToSerializable(e,r,a+1):t.type===Hn||t.type===Wn?r.children.push({type:"content",id:`p${t.pageObjId}_mc${t.mcid}`}):t.type===zn?r.children.push({type:"object",id:t.refObjId}):t.type===$n&&r.children.push({type:"annotation",id:`pdfjs_internal_id_${t.refObjId}`})}}const e=Object.create(null);e.children=[];e.role="Root";for(const t of this.nodes)t&&nodeToSerializable(t,e);return e}}const Vn=function _isValidExplicitDest(e,t,a){if(!Array.isArray(a)||a.length<2)return!1;const[r,i,...n]=a;if(!e(r)&&!Number.isInteger(r))return!1;if(!t(i))return!1;const s=n.length;let o=!0;switch(i.name){case"XYZ":if(s<2||s>3)return!1;break;case"Fit":case"FitB":return 0===s;case"FitH":case"FitBH":case"FitV":case"FitBV":if(s>1)return!1;break;case"FitR":if(4!==s)return!1;o=!1;break;default:return!1}for(const e of n)if(!("number"==typeof e||o&&null===e))return!1;return!0}.bind(null,(e=>e instanceof Ref),isName);function fetchDest(e){e instanceof Dict&&(e=e.get("D"));return Vn(e)?e:null}function fetchRemoteDest(e){let t=e.get("D");if(t){t instanceof Name&&(t=t.name);if("string"==typeof t)return stringToPDFString(t,!0);if(Vn(t))return JSON.stringify(t)}return null}class Catalog{#re=null;#ie=null;builtInCMapCache=new Map;fontCache=new RefSetCache;globalColorSpaceCache=new GlobalColorSpaceCache;globalImageCache=new GlobalImageCache;nonBlendModesSet=new RefSet;pageDictCache=new RefSetCache;pageIndexCache=new RefSetCache;pageKidsCountCache=new RefSetCache;standardFontDataCache=new Map;systemFontCache=new Map;constructor(e,t){this.pdfManager=e;this.xref=t;this.#ie=t.getCatalogObj();if(!(this.#ie instanceof Dict))throw new FormatError("Catalog object is not a dictionary.");this.toplevelPagesDict}cloneDict(){return this.#ie.clone()}get version(){const e=this.#ie.get("Version");if(e instanceof Name){if(Ca.test(e.name))return shadow(this,"version",e.name);warn(`Invalid PDF catalog version: ${e.name}`)}return shadow(this,"version",null)}get lang(){const e=this.#ie.get("Lang");return shadow(this,"lang",e&&"string"==typeof e?stringToPDFString(e):null)}get needsRendering(){const e=this.#ie.get("NeedsRendering");return shadow(this,"needsRendering","boolean"==typeof e&&e)}get collection(){let e=null;try{const t=this.#ie.get("Collection");t instanceof Dict&&t.size>0&&(e=t)}catch(e){if(e instanceof MissingDataException)throw e;info("Cannot fetch Collection entry; assuming no collection is present.")}return shadow(this,"collection",e)}get acroForm(){let e=null;try{const t=this.#ie.get("AcroForm");t instanceof Dict&&t.size>0&&(e=t)}catch(e){if(e instanceof MissingDataException)throw e;info("Cannot fetch AcroForm entry; assuming no forms are present.")}return shadow(this,"acroForm",e)}get acroFormRef(){const e=this.#ie.getRaw("AcroForm");return shadow(this,"acroFormRef",e instanceof Ref?e:null)}get metadata(){const e=this.#ie.getRaw("Metadata");if(!(e instanceof Ref))return shadow(this,"metadata",null);let t=null;try{const a=this.xref.fetch(e,!this.xref.encrypt?.encryptMetadata);if(a instanceof BaseStream&&a.dict instanceof Dict){const e=a.dict.get("Type"),r=a.dict.get("Subtype");if(isName(e,"Metadata")&&isName(r,"XML")){const e=stringToUTF8String(a.getString());e&&(t=new MetadataParser(e).serializable)}}}catch(e){if(e instanceof MissingDataException)throw e;info(`Skipping invalid Metadata: "${e}".`)}return shadow(this,"metadata",t)}get markInfo(){let e=null;try{e=this.#ne()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read mark info.")}return shadow(this,"markInfo",e)}#ne(){const e=this.#ie.get("MarkInfo");if(!(e instanceof Dict))return null;const t={Marked:!1,UserProperties:!1,Suspects:!1};for(const a in t){const r=e.get(a);"boolean"==typeof r&&(t[a]=r)}return t}get structTreeRoot(){let e=null;try{e=this.#se()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable read to structTreeRoot info.")}return shadow(this,"structTreeRoot",e)}#se(){const e=this.#ie.getRaw("StructTreeRoot"),t=this.xref.fetchIfRef(e);if(!(t instanceof Dict))return null;const a=new StructTreeRoot(this.xref,t,e);a.init();return a}get toplevelPagesDict(){const e=this.#ie.get("Pages");if(!(e instanceof Dict))throw new FormatError("Invalid top-level pages dictionary.");return shadow(this,"toplevelPagesDict",e)}get documentOutline(){let e=null;try{e=this.#oe()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read document outline.")}return shadow(this,"documentOutline",e)}#oe(){let e=this.#ie.get("Outlines");if(!(e instanceof Dict))return null;e=e.getRaw("First");if(!(e instanceof Ref))return null;const t={items:[]},a=[{obj:e,parent:t}],r=new RefSet;r.put(e);const i=this.xref,n=new Uint8ClampedArray(3);for(;a.length>0;){const t=a.shift(),s=i.fetchIfRef(t.obj);if(null===s)continue;s.has("Title")||warn("Invalid outline item encountered.");const o={url:null,dest:null,action:null};Catalog.parseDestDictionary({destDict:s,resultObj:o,docBaseUrl:this.baseUrl,docAttachments:this.attachments});const c=s.get("Title"),l=s.get("F")||0,h=s.getArray("C"),u=s.get("Count");let d=n;!isNumberArray(h,3)||0===h[0]&&0===h[1]&&0===h[2]||(d=ColorSpaceUtils.rgb.getRgb(h,0));const f={action:o.action,attachment:o.attachment,dest:o.dest,url:o.url,unsafeUrl:o.unsafeUrl,newWindow:o.newWindow,setOCGState:o.setOCGState,title:"string"==typeof c?stringToPDFString(c):"",color:d,count:Number.isInteger(u)?u:void 0,bold:!!(2&l),italic:!!(1&l),items:[]};t.parent.items.push(f);e=s.getRaw("First");if(e instanceof Ref&&!r.has(e)){a.push({obj:e,parent:f});r.put(e)}e=s.getRaw("Next");if(e instanceof Ref&&!r.has(e)){a.push({obj:e,parent:t.parent});r.put(e)}}return t.items.length>0?t.items:null}get permissions(){let e=null;try{e=this.#ce()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read permissions.")}return shadow(this,"permissions",e)}#ce(){const e=this.xref.trailer.get("Encrypt");if(!(e instanceof Dict))return null;let t=e.get("P");if("number"!=typeof t)return null;t+=2**32;const a=[];for(const e in w){const r=w[e];t&r&&a.push(r)}return a}get optionalContentConfig(){let e=null;try{const t=this.#ie.get("OCProperties");if(!t)return shadow(this,"optionalContentConfig",null);const a=t.get("D");if(!a)return shadow(this,"optionalContentConfig",null);const r=t.get("OCGs");if(!Array.isArray(r))return shadow(this,"optionalContentConfig",null);const i=new RefSetCache;for(const e of r)e instanceof Ref&&!i.has(e)&&i.put(e,this.#le(e));e=this.#he(a,i)}catch(e){if(e instanceof MissingDataException)throw e;warn(`Unable to read optional content config: ${e}`)}return shadow(this,"optionalContentConfig",e)}#le(e){const t=this.xref.fetch(e),a={id:e.toString(),name:null,intent:null,usage:{print:null,view:null},rbGroups:[]},r=t.get("Name");"string"==typeof r&&(a.name=stringToPDFString(r));let i=t.getArray("Intent");Array.isArray(i)||(i=[i]);i.every((e=>e instanceof Name))&&(a.intent=i.map((e=>e.name)));const n=t.get("Usage");if(!(n instanceof Dict))return a;const s=a.usage,o=n.get("Print");if(o instanceof Dict){const e=o.get("PrintState");if(e instanceof Name)switch(e.name){case"ON":case"OFF":s.print={printState:e.name}}}const c=n.get("View");if(c instanceof Dict){const e=c.get("ViewState");if(e instanceof Name)switch(e.name){case"ON":case"OFF":s.view={viewState:e.name}}}return a}#he(e,t){function parseOnOff(e){const a=[];if(Array.isArray(e))for(const r of e)r instanceof Ref&&t.has(r)&&a.push(r.toString());return a}function parseOrder(e,a=0){if(!Array.isArray(e))return null;const i=[];for(const n of e){if(n instanceof Ref&&t.has(n)){r.put(n);i.push(n.toString());continue}const e=parseNestedOrder(n,a);e&&i.push(e)}if(a>0)return i;const n=[];for(const[e]of t.items())r.has(e)||n.push(e.toString());n.length&&i.push({name:null,order:n});return i}function parseNestedOrder(e,t){if(++t>i){warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");return null}const r=a.fetchIfRef(e);if(!Array.isArray(r))return null;const n=a.fetchIfRef(r[0]);if("string"!=typeof n)return null;const s=parseOrder(r.slice(1),t);return s?.length?{name:stringToPDFString(n),order:s}:null}const a=this.xref,r=new RefSet,i=10;!function parseRBGroups(e){if(Array.isArray(e))for(const r of e){const e=a.fetchIfRef(r);if(!Array.isArray(e)||!e.length)continue;const i=new Set;for(const a of e)if(a instanceof Ref&&t.has(a)&&!i.has(a.toString())){i.add(a.toString());t.get(a).rbGroups.push(i)}}}(e.get("RBGroups"));return{name:"string"==typeof e.get("Name")?stringToPDFString(e.get("Name")):null,creator:"string"==typeof e.get("Creator")?stringToPDFString(e.get("Creator")):null,baseState:e.get("BaseState")instanceof Name?e.get("BaseState").name:null,on:parseOnOff(e.get("ON")),off:parseOnOff(e.get("OFF")),order:parseOrder(e.get("Order")),groups:[...t]}}setActualNumPages(e=null){this.#re=e}get hasActualNumPages(){return null!==this.#re}get _pagesCount(){const e=this.toplevelPagesDict.get("Count");if(!Number.isInteger(e))throw new FormatError("Page count in top-level pages dictionary is not an integer.");return shadow(this,"_pagesCount",e)}get numPages(){return this.#re??this._pagesCount}get destinations(){const e=this.#ue(),t=Object.create(null);for(const a of e)if(a instanceof NameTree)for(const[e,r]of a.getAll()){const a=fetchDest(r);a&&(t[stringToPDFString(e,!0)]=a)}else if(a instanceof Dict)for(const[e,r]of a){const a=fetchDest(r);a&&(t[stringToPDFString(e,!0)]||=a)}return shadow(this,"destinations",t)}getDestination(e){if(this.hasOwnProperty("destinations"))return this.destinations[e]??null;const t=this.#ue();for(const a of t)if(a instanceof NameTree||a instanceof Dict){const t=fetchDest(a.get(e));if(t)return t}if(t.length){const t=this.destinations[e];if(t)return t}return null}#ue(){const e=this.#ie.get("Names"),t=[];e?.has("Dests")&&t.push(new NameTree(e.getRaw("Dests"),this.xref));this.#ie.has("Dests")&&t.push(this.#ie.get("Dests"));return t}get pageLabels(){let e=null;try{e=this.#de()}catch(e){if(e instanceof MissingDataException)throw e;warn("Unable to read page labels.")}return shadow(this,"pageLabels",e)}#de(){const e=this.#ie.getRaw("PageLabels");if(!e)return null;const t=new Array(this.numPages);let a=null,r="";const i=new NumberTree(e,this.xref).getAll();let n="",s=1;for(let e=0,o=this.numPages;e=1))throw new FormatError("Invalid start in PageLabel dictionary.");s=e}else s=1}switch(a){case"D":n=s;break;case"R":case"r":n=toRomanNumerals(s,"r"===a);break;case"A":case"a":const e=26,t="a"===a?97:65,r=s-1;n=String.fromCharCode(t+r%e).repeat(Math.floor(r/e)+1);break;default:if(a)throw new FormatError(`Invalid style "${a}" in PageLabel dictionary.`);n=""}t[e]=r+n;s++}return t}get pageLayout(){const e=this.#ie.get("PageLayout");let t="";if(e instanceof Name)switch(e.name){case"SinglePage":case"OneColumn":case"TwoColumnLeft":case"TwoColumnRight":case"TwoPageLeft":case"TwoPageRight":t=e.name}return shadow(this,"pageLayout",t)}get pageMode(){const e=this.#ie.get("PageMode");let t="UseNone";if(e instanceof Name)switch(e.name){case"UseNone":case"UseOutlines":case"UseThumbs":case"FullScreen":case"UseOC":case"UseAttachments":t=e.name}return shadow(this,"pageMode",t)}get viewerPreferences(){const e=this.#ie.get("ViewerPreferences");if(!(e instanceof Dict))return shadow(this,"viewerPreferences",null);let t=null;for(const[a,r]of e){let e;switch(a){case"HideToolbar":case"HideMenubar":case"HideWindowUI":case"FitWindow":case"CenterWindow":case"DisplayDocTitle":case"PickTrayByPDFSize":"boolean"==typeof r&&(e=r);break;case"NonFullScreenPageMode":if(r instanceof Name)switch(r.name){case"UseNone":case"UseOutlines":case"UseThumbs":case"UseOC":e=r.name;break;default:e="UseNone"}break;case"Direction":if(r instanceof Name)switch(r.name){case"L2R":case"R2L":e=r.name;break;default:e="L2R"}break;case"ViewArea":case"ViewClip":case"PrintArea":case"PrintClip":if(r instanceof Name)switch(r.name){case"MediaBox":case"CropBox":case"BleedBox":case"TrimBox":case"ArtBox":e=r.name;break;default:e="CropBox"}break;case"PrintScaling":if(r instanceof Name)switch(r.name){case"None":case"AppDefault":e=r.name;break;default:e="AppDefault"}break;case"Duplex":if(r instanceof Name)switch(r.name){case"Simplex":case"DuplexFlipShortEdge":case"DuplexFlipLongEdge":e=r.name;break;default:e="None"}break;case"PrintPageRange":if(Array.isArray(r)&&r.length%2==0){r.every(((e,t,a)=>Number.isInteger(e)&&e>0&&(0===t||e>=a[t-1])&&e<=this.numPages))&&(e=r)}break;case"NumCopies":Number.isInteger(r)&&r>0&&(e=r);break;default:warn(`Ignoring non-standard key in ViewerPreferences: ${a}.`);continue}if(void 0!==e){t??=Object.create(null);t[a]=e}else warn(`Bad value, for key "${a}", in ViewerPreferences: ${r}.`)}return shadow(this,"viewerPreferences",t)}get openAction(){const e=this.#ie.get("OpenAction"),t=Object.create(null);if(e instanceof Dict){const a=new Dict(this.xref);a.set("A",e);const r={url:null,dest:null,action:null};Catalog.parseDestDictionary({destDict:a,resultObj:r});Array.isArray(r.dest)?t.dest=r.dest:r.action&&(t.action=r.action)}else Vn(e)&&(t.dest=e);return shadow(this,"openAction",objectSize(t)>0?t:null)}get attachments(){const e=this.#ie.get("Names");let t=null;if(e instanceof Dict&&e.has("EmbeddedFiles")){const a=new NameTree(e.getRaw("EmbeddedFiles"),this.xref);for(const[e,r]of a.getAll()){const a=new FileSpec(r,this.xref);t??=Object.create(null);t[stringToPDFString(e,!0)]=a.serializable}}return shadow(this,"attachments",t)}get xfaImages(){const e=this.#ie.get("Names");let t=null;if(e instanceof Dict&&e.has("XFAImages")){const a=new NameTree(e.getRaw("XFAImages"),this.xref);for(const[e,r]of a.getAll())if(r instanceof BaseStream){t??=new Map;t.set(stringToPDFString(e,!0),r.getBytes())}}return shadow(this,"xfaImages",t)}#fe(){const e=this.#ie.get("Names");let t=null;function appendIfJavaScriptDict(e,a){if(!(a instanceof Dict))return;if(!isName(a.get("S"),"JavaScript"))return;let r=a.get("JS");if(r instanceof BaseStream)r=r.getString();else if("string"!=typeof r)return;r=stringToPDFString(r,!0).replaceAll("\0","");r&&(t||=new Map).set(e,r)}if(e instanceof Dict&&e.has("JavaScript")){const t=new NameTree(e.getRaw("JavaScript"),this.xref);for(const[e,a]of t.getAll())appendIfJavaScriptDict(stringToPDFString(e,!0),a)}const a=this.#ie.get("OpenAction");a&&appendIfJavaScriptDict("OpenAction",a);return t}get jsActions(){const e=this.#fe();let t=collectActions(this.xref,this.#ie,we);if(e){t||=Object.create(null);for(const[a,r]of e)a in t?t[a].push(r):t[a]=[r]}return shadow(this,"jsActions",t)}async cleanup(e=!1){clearGlobalCaches();this.globalColorSpaceCache.clear();this.globalImageCache.clear(e);this.pageKidsCountCache.clear();this.pageIndexCache.clear();this.pageDictCache.clear();this.nonBlendModesSet.clear();for(const{dict:e}of await Promise.all(this.fontCache))delete e.cacheKey;this.fontCache.clear();this.builtInCMapCache.clear();this.standardFontDataCache.clear();this.systemFontCache.clear()}async getPageDict(e){const t=[this.toplevelPagesDict],a=new RefSet,r=this.#ie.getRaw("Pages");r instanceof Ref&&a.put(r);const i=this.xref,n=this.pageKidsCountCache,s=this.pageIndexCache,o=this.pageDictCache;let c=0;for(;t.length;){const r=t.pop();if(r instanceof Ref){const l=n.get(r);if(l>=0&&c+l<=e){c+=l;continue}if(a.has(r))throw new FormatError("Pages tree contains circular reference.");a.put(r);const h=await(o.get(r)||i.fetchAsync(r));if(h instanceof Dict){let t=h.getRaw("Type");t instanceof Ref&&(t=await i.fetchAsync(t));if(isName(t,"Page")||!h.has("Kids")){n.has(r)||n.put(r,1);s.has(r)||s.put(r,c);if(c===e)return[h,r];c++;continue}}t.push(h);continue}if(!(r instanceof Dict))throw new FormatError("Page dictionary kid reference points to wrong type of object.");const{objId:l}=r;let h=r.getRaw("Count");h instanceof Ref&&(h=await i.fetchAsync(h));if(Number.isInteger(h)&&h>=0){l&&!n.has(l)&&n.put(l,h);if(c+h<=e){c+=h;continue}}let u=r.getRaw("Kids");u instanceof Ref&&(u=await i.fetchAsync(u));if(!Array.isArray(u)){let t=r.getRaw("Type");t instanceof Ref&&(t=await i.fetchAsync(t));if(isName(t,"Page")||!r.has("Kids")){if(c===e)return[r,null];c++;continue}throw new FormatError("Page dictionary kids object is not an array.")}for(let e=u.length-1;e>=0;e--){const a=u[e];t.push(a);r===this.toplevelPagesDict&&a instanceof Ref&&!o.has(a)&&o.put(a,i.fetchAsync(a))}}throw new Error(`Page index ${e} not found.`)}async getAllPageDicts(e=!1){const{ignoreErrors:t}=this.pdfManager.evaluatorOptions,a=[{currentNode:this.toplevelPagesDict,posInKids:0}],r=new RefSet,i=this.#ie.getRaw("Pages");i instanceof Ref&&r.put(i);const n=new Map,s=this.xref,o=this.pageIndexCache;let c=0;function addPageDict(e,t){t&&!o.has(t)&&o.put(t,c);n.set(c++,[e,t])}function addPageError(a){if(a instanceof XRefEntryException&&!e)throw a;if(e&&t&&0===c){warn(`getAllPageDicts - Skipping invalid first page: "${a}".`);a=Dict.empty}n.set(c++,[a,null])}for(;a.length>0;){const e=a.at(-1),{currentNode:t,posInKids:i}=e;let n=t.getRaw("Kids");if(n instanceof Ref)try{n=await s.fetchAsync(n)}catch(e){addPageError(e);break}if(!Array.isArray(n)){addPageError(new FormatError("Page dictionary kids object is not an array."));break}if(i>=n.length){a.pop();continue}const o=n[i];let c;if(o instanceof Ref){if(r.has(o)){addPageError(new FormatError("Pages tree contains circular reference."));break}r.put(o);try{c=await s.fetchAsync(o)}catch(e){addPageError(e);break}}else c=o;if(!(c instanceof Dict)){addPageError(new FormatError("Page dictionary kid reference points to wrong type of object."));break}let l=c.getRaw("Type");if(l instanceof Ref)try{l=await s.fetchAsync(l)}catch(e){addPageError(e);break}isName(l,"Page")||!c.has("Kids")?addPageDict(c,o instanceof Ref?o:null):a.push({currentNode:c,posInKids:0});e.posInKids++}return n}getPageIndex(e){const t=this.pageIndexCache.get(e);if(void 0!==t)return Promise.resolve(t);const a=this.xref;let r=0;const next=t=>function pagesBeforeRef(t){let r,i=0;return a.fetchAsync(t).then((function(a){if(isRefsEqual(t,e)&&!isDict(a,"Page")&&!(a instanceof Dict&&!a.has("Type")&&a.has("Contents")))throw new FormatError("The reference does not point to a /Page dictionary.");if(!a)return null;if(!(a instanceof Dict))throw new FormatError("Node must be a dictionary.");r=a.getRaw("Parent");return a.getAsync("Parent")})).then((function(e){if(!e)return null;if(!(e instanceof Dict))throw new FormatError("Parent must be a dictionary.");return e.getAsync("Kids")})).then((function(e){if(!e)return null;const n=[];let s=!1;for(const r of e){if(!(r instanceof Ref))throw new FormatError("Kid must be a reference.");if(isRefsEqual(r,t)){s=!0;break}n.push(a.fetchAsync(r).then((function(e){if(!(e instanceof Dict))throw new FormatError("Kid node must be a dictionary.");e.has("Count")?i+=e.get("Count"):i++})))}if(!s)throw new FormatError("Kid reference not found in parent's kids.");return Promise.all(n).then((()=>[i,r]))}))}(t).then((t=>{if(!t){this.pageIndexCache.put(e,r);return r}const[a,i]=t;r+=a;return next(i)}));return next(e)}get baseUrl(){const e=this.#ie.get("URI");if(e instanceof Dict){const t=e.get("Base");if("string"==typeof t){const e=createValidAbsoluteUrl(t,null,{tryConvertEncoding:!0});if(e)return shadow(this,"baseUrl",e.href)}}return shadow(this,"baseUrl",this.pdfManager.docBaseUrl)}static parseDestDictionary({destDict:e,resultObj:t,docBaseUrl:a=null,docAttachments:r=null}){if(!(e instanceof Dict)){warn("parseDestDictionary: `destDict` must be a dictionary.");return}let i,n,s=e.get("A");if(!(s instanceof Dict))if(e.has("Dest"))s=e.get("Dest");else{s=e.get("AA");s instanceof Dict&&(s.has("D")?s=s.get("D"):s.has("U")&&(s=s.get("U")))}if(s instanceof Dict){const e=s.get("S");if(!(e instanceof Name)){warn("parseDestDictionary: Invalid type in Action dictionary.");return}const a=e.name;switch(a){case"ResetForm":const e=s.get("Flags"),o=!(1&("number"==typeof e?e:0)),c=[],l=[];for(const e of s.get("Fields")||[])e instanceof Ref?l.push(e.toString()):"string"==typeof e&&c.push(stringToPDFString(e));t.resetForm={fields:c,refs:l,include:o};break;case"URI":i=s.get("URI");i instanceof Name&&(i="/"+i.name);break;case"GoTo":n=s.get("D");break;case"Launch":case"GoToR":const h=s.get("F");if(h instanceof Dict){const e=new FileSpec(h,null,!0),{rawFilename:t}=e.serializable;i=t}else"string"==typeof h&&(i=h);const u=fetchRemoteDest(s);u&&"string"==typeof i&&(i=i.split("#",1)[0]+"#"+u);const d=s.get("NewWindow");"boolean"==typeof d&&(t.newWindow=d);break;case"GoToE":const f=s.get("T");let g;if(r&&f instanceof Dict){const e=f.get("R"),t=f.get("N");isName(e,"C")&&"string"==typeof t&&(g=r[stringToPDFString(t,!0)])}if(g){t.attachment=g;const e=fetchRemoteDest(s);e&&(t.attachmentDest=e)}else warn('parseDestDictionary - unimplemented "GoToE" action.');break;case"Named":const p=s.get("N");p instanceof Name&&(t.action=p.name);break;case"SetOCGState":const m=s.get("State"),b=s.get("PreserveRB");if(!Array.isArray(m)||0===m.length)break;const y=[];for(const e of m)if(e instanceof Name)switch(e.name){case"ON":case"OFF":case"Toggle":y.push(e.name)}else e instanceof Ref&&y.push(e.toString());if(y.length!==m.length)break;t.setOCGState={state:y,preserveRB:"boolean"!=typeof b||b};break;case"JavaScript":const w=s.get("JS");let S;w instanceof BaseStream?S=w.getString():"string"==typeof w&&(S=w);const x=S&&recoverJsURL(stringToPDFString(S,!0));if(x){i=x.url;t.newWindow=x.newWindow;break}default:if("JavaScript"===a||"SubmitForm"===a)break;warn(`parseDestDictionary - unsupported action: "${a}".`)}}else e.has("Dest")&&(n=e.get("Dest"));if("string"==typeof i){const e=createValidAbsoluteUrl(i,a,{addDefaultProtocol:!0,tryConvertEncoding:!0});e&&(t.url=e.href);t.unsafeUrl=i}if(n){n instanceof Name&&(n=n.name);"string"==typeof n?t.dest=stringToPDFString(n,!0):Vn(n)&&(t.dest=n)}}}function addChildren(e,t){if(e instanceof Dict)e=e.getRawValues();else if(e instanceof BaseStream)e=e.dict.getRawValues();else if(!Array.isArray(e))return;for(const r of e)((a=r)instanceof Ref||a instanceof Dict||a instanceof BaseStream||Array.isArray(a))&&t.push(r);var a}class ObjectLoader{refSet=new RefSet;constructor(e,t,a){this.dict=e;this.keys=t;this.xref=a}async load(){const{keys:e,dict:t}=this,a=[];for(const r of e){const e=t.getRaw(r);void 0!==e&&a.push(e)}await this.#ge(a);this.refSet=null}async#ge(e){const t=[],a=[];for(;e.length;){let r=e.pop();if(r instanceof Ref){if(this.refSet.has(r))continue;try{this.refSet.put(r);r=this.xref.fetch(r)}catch(e){if(!(e instanceof MissingDataException)){warn(`ObjectLoader.#walk - requesting all data: "${e}".`);await this.xref.stream.manager.requestAllChunks();return}t.push(r);a.push({begin:e.begin,end:e.end})}}if(r instanceof BaseStream){const e=r.getBaseStreams();if(e){let i=!1;for(const t of e)if(!t.isDataLoaded){i=!0;a.push({begin:t.start,end:t.end})}i&&t.push(r)}}addChildren(r,e)}if(a.length){await this.xref.stream.manager.requestRanges(a);for(const e of t)e instanceof Ref&&this.refSet.remove(e);await this.#ge(t)}}static async load(e,t,a){if(a.stream.isDataLoaded)return;const r=new ObjectLoader(e,t,a);await r.load()}}const Kn=Symbol(),Jn=Symbol(),Yn=Symbol(),Zn=Symbol(),Qn=Symbol(),es=Symbol(),ts=Symbol(),as=Symbol(),rs=Symbol(),is=Symbol("content"),ns=Symbol("data"),ss=Symbol(),os=Symbol("extra"),cs=Symbol(),ls=Symbol(),hs=Symbol(),us=Symbol(),ds=Symbol(),fs=Symbol(),gs=Symbol(),ps=Symbol(),ms=Symbol(),bs=Symbol(),ys=Symbol(),ws=Symbol(),Ss=Symbol(),xs=Symbol(),As=Symbol(),ks=Symbol(),Cs=Symbol(),vs=Symbol(),Fs=Symbol(),Is=Symbol(),Os=Symbol(),Ts=Symbol(),Ms=Symbol(),Ds=Symbol(),Bs=Symbol(),Rs=Symbol(),Ns=Symbol(),Es=Symbol(),Ls=Symbol(),js=Symbol(),_s=Symbol(),Us=Symbol(),Xs=Symbol("namespaceId"),qs=Symbol("nodeName"),Hs=Symbol(),Ws=Symbol(),zs=Symbol(),$s=Symbol(),Gs=Symbol(),Vs=Symbol(),Ks=Symbol(),Js=Symbol(),Ys=Symbol("root"),Zs=Symbol(),Qs=Symbol(),eo=Symbol(),to=Symbol(),ao=Symbol(),ro=Symbol(),io=Symbol(),no=Symbol(),so=Symbol(),oo=Symbol(),co=Symbol(),lo=Symbol("uid"),ho=Symbol(),uo={config:{id:0,check:e=>e.startsWith("http://www.xfa.org/schema/xci/")},connectionSet:{id:1,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-connection-set/")},datasets:{id:2,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-data/")},form:{id:3,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-form/")},localeSet:{id:4,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-locale-set/")},pdf:{id:5,check:e=>"http://ns.adobe.com/xdp/pdf/"===e},signature:{id:6,check:e=>"http://www.w3.org/2000/09/xmldsig#"===e},sourceSet:{id:7,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-source-set/")},stylesheet:{id:8,check:e=>"http://www.w3.org/1999/XSL/Transform"===e},template:{id:9,check:e=>e.startsWith("http://www.xfa.org/schema/xfa-template/")},xdc:{id:10,check:e=>e.startsWith("http://www.xfa.org/schema/xdc/")},xdp:{id:11,check:e=>"http://ns.adobe.com/xdp/"===e},xfdf:{id:12,check:e=>"http://ns.adobe.com/xfdf/"===e},xhtml:{id:13,check:e=>"http://www.w3.org/1999/xhtml"===e},xmpmeta:{id:14,check:e=>"http://ns.adobe.com/xmpmeta/"===e}},fo={pt:e=>e,cm:e=>e/2.54*72,mm:e=>e/25.4*72,in:e=>72*e,px:e=>e},go=/([+-]?\d+\.?\d*)(.*)/;function stripQuotes(e){return e.startsWith("'")||e.startsWith('"')?e.slice(1,-1):e}function getInteger({data:e,defaultValue:t,validate:a}){if(!e)return t;e=e.trim();const r=parseInt(e,10);return!isNaN(r)&&a(r)?r:t}function getFloat({data:e,defaultValue:t,validate:a}){if(!e)return t;e=e.trim();const r=parseFloat(e);return!isNaN(r)&&a(r)?r:t}function getKeyword({data:e,defaultValue:t,validate:a}){return e&&a(e=e.trim())?e:t}function getStringOption(e,t){return getKeyword({data:e,defaultValue:t[0],validate:e=>t.includes(e)})}function getMeasurement(e,t="0"){t||="0";if(!e)return getMeasurement(t);const a=e.trim().match(go);if(!a)return getMeasurement(t);const[,r,i]=a,n=parseFloat(r);if(isNaN(n))return getMeasurement(t);if(0===n)return 0;const s=fo[i];return s?s(n):n}function getRatio(e){if(!e)return{num:1,den:1};const t=e.split(":",2).map((e=>parseFloat(e.trim()))).filter((e=>!isNaN(e)));1===t.length&&t.push(1);if(0===t.length)return{num:1,den:1};const[a,r]=t;return{num:a,den:r}}function getRelevant(e){return e?e.trim().split(/\s+/).map((e=>({excluded:"-"===e[0],viewname:e.substring(1)}))):[]}class HTMLResult{static get FAILURE(){return shadow(this,"FAILURE",new HTMLResult(!1,null,null,null))}static get EMPTY(){return shadow(this,"EMPTY",new HTMLResult(!0,null,null,null))}constructor(e,t,a,r){this.success=e;this.html=t;this.bbox=a;this.breakNode=r}isBreak(){return!!this.breakNode}static breakNode(e){return new HTMLResult(!1,null,null,e)}static success(e,t=null){return new HTMLResult(!0,e,t,null)}}class FontFinder{constructor(e){this.fonts=new Map;this.cache=new Map;this.warned=new Set;this.defaultFont=null;this.add(e)}add(e,t=null){for(const t of e)this.addPdfFont(t);for(const e of this.fonts.values())e.regular||(e.regular=e.italic||e.bold||e.bolditalic);if(!t||0===t.size)return;const a=this.fonts.get("PdfJS-Fallback-PdfJS-XFA");for(const e of t)this.fonts.set(e,a)}addPdfFont(e){const t=e.cssFontInfo,a=t.fontFamily;let r=this.fonts.get(a);if(!r){r=Object.create(null);this.fonts.set(a,r);this.defaultFont||(this.defaultFont=r)}let i="";const n=parseFloat(t.fontWeight);0!==parseFloat(t.italicAngle)?i=n>=700?"bolditalic":"italic":n>=700&&(i="bold");if(!i){(e.name.includes("Bold")||e.psName?.includes("Bold"))&&(i="bold");(e.name.includes("Italic")||e.name.endsWith("It")||e.psName?.includes("Italic")||e.psName?.endsWith("It"))&&(i+="italic")}i||(i="regular");r[i]=e}getDefault(){return this.defaultFont}find(e,t=!0){let a=this.fonts.get(e)||this.cache.get(e);if(a)return a;const r=/,|-|_| |bolditalic|bold|italic|regular|it/gi;let i=e.replaceAll(r,"");a=this.fonts.get(i);if(a){this.cache.set(e,a);return a}i=i.toLowerCase();const n=[];for(const[e,t]of this.fonts.entries())e.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(t);if(0===n.length)for(const[,e]of this.fonts.entries())e.regular.name?.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(e);if(0===n.length){i=i.replaceAll(/psmt|mt/gi,"");for(const[e,t]of this.fonts.entries())e.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(t)}if(0===n.length)for(const e of this.fonts.values())e.regular.name?.replaceAll(r,"").toLowerCase().startsWith(i)&&n.push(e);if(n.length>=1){1!==n.length&&t&&warn(`XFA - Too many choices to guess the correct font: ${e}`);this.cache.set(e,n[0]);return n[0]}if(t&&!this.warned.has(e)){this.warned.add(e);warn(`XFA - Cannot find the font: ${e}`)}return null}}function selectFont(e,t){return"italic"===e.posture?"bold"===e.weight?t.bolditalic:t.italic:"bold"===e.weight?t.bold:t.regular}class FontInfo{constructor(e,t,a,r){this.lineHeight=a;this.paraMargin=t||{top:0,bottom:0,left:0,right:0};if(!e){[this.pdfFont,this.xfaFont]=this.defaultFont(r);return}this.xfaFont={typeface:e.typeface,posture:e.posture,weight:e.weight,size:e.size,letterSpacing:e.letterSpacing};const i=r.find(e.typeface);if(i){this.pdfFont=selectFont(e,i);this.pdfFont||([this.pdfFont,this.xfaFont]=this.defaultFont(r))}else[this.pdfFont,this.xfaFont]=this.defaultFont(r)}defaultFont(e){const t=e.find("Helvetica",!1)||e.find("Myriad Pro",!1)||e.find("Arial",!1)||e.getDefault();if(t?.regular){const e=t.regular;return[e,{typeface:e.cssFontInfo.fontFamily,posture:"normal",weight:"normal",size:10,letterSpacing:0}]}return[null,{typeface:"Courier",posture:"normal",weight:"normal",size:10,letterSpacing:0}]}}class FontSelector{constructor(e,t,a,r){this.fontFinder=r;this.stack=[new FontInfo(e,t,a,r)]}pushData(e,t,a){const r=this.stack.at(-1);for(const t of["typeface","posture","weight","size","letterSpacing"])e[t]||(e[t]=r.xfaFont[t]);for(const e of["top","bottom","left","right"])isNaN(t[e])&&(t[e]=r.paraMargin[e]);const i=new FontInfo(e,t,a||r.lineHeight,this.fontFinder);i.pdfFont||(i.pdfFont=r.pdfFont);this.stack.push(i)}popFont(){this.stack.pop()}topFont(){return this.stack.at(-1)}}class TextMeasure{constructor(e,t,a,r){this.glyphs=[];this.fontSelector=new FontSelector(e,t,a,r);this.extraHeight=0}pushData(e,t,a){this.fontSelector.pushData(e,t,a)}popFont(e){return this.fontSelector.popFont()}addPara(){const e=this.fontSelector.topFont();this.extraHeight+=e.paraMargin.top+e.paraMargin.bottom}addString(e){if(!e)return;const t=this.fontSelector.topFont(),a=t.xfaFont.size;if(t.pdfFont){const r=t.xfaFont.letterSpacing,i=t.pdfFont,n=i.lineHeight||1.2,s=t.lineHeight||Math.max(1.2,n)*a,o=n-(void 0===i.lineGap?.2:i.lineGap),c=Math.max(1,o)*a,l=a/1e3,h=i.defaultWidth||i.charsToGlyphs(" ")[0].width;for(const t of e.split(/[\u2029\n]/)){const e=i.encodeString(t).join(""),a=i.charsToGlyphs(e);for(const e of a){const t=e.width||h;this.glyphs.push([t*l+r,s,c,e.unicode,!1])}this.glyphs.push([0,0,0,"\n",!0])}this.glyphs.pop()}else{for(const t of e.split(/[\u2029\n]/)){for(const e of t.split(""))this.glyphs.push([a,1.2*a,a,e,!1]);this.glyphs.push([0,0,0,"\n",!0])}this.glyphs.pop()}}compute(e){let t=-1,a=0,r=0,i=0,n=0,s=0,o=!1,c=!0;for(let l=0,h=this.glyphs.length;le){r=Math.max(r,n);n=0;i+=s;s=m;t=-1;a=0;o=!0;c=!1}else{s=Math.max(m,s);a=n;n+=h;t=l}else if(n+h>e){i+=s;s=m;if(-1!==t){l=t;r=Math.max(r,a);n=0;t=-1;a=0}else{r=Math.max(r,n);n=h}o=!0;c=!1}else{n+=h;s=Math.max(m,s)}}r=Math.max(r,n);i+=s+this.extraHeight;return{width:1.02*r,height:i,isBroken:o}}}const po=/^[^.[]+/,mo=/^[^\]]+/,bo=0,yo=1,wo=2,So=3,xo=4,Ao=new Map([["$data",(e,t)=>e.datasets?e.datasets.data:e],["$record",(e,t)=>(e.datasets?e.datasets.data:e)[ws]()[0]],["$template",(e,t)=>e.template],["$connectionSet",(e,t)=>e.connectionSet],["$form",(e,t)=>e.form],["$layout",(e,t)=>e.layout],["$host",(e,t)=>e.host],["$dataWindow",(e,t)=>e.dataWindow],["$event",(e,t)=>e.event],["!",(e,t)=>e.datasets],["$xfa",(e,t)=>e],["xfa",(e,t)=>e],["$",(e,t)=>t]]),ko=new WeakMap;function parseExpression(e,t,a=!0){let r=e.match(po);if(!r)return null;let[i]=r;const n=[{name:i,cacheName:"."+i,index:0,js:null,formCalc:null,operator:bo}];let s=i.length;for(;s0&&h.push(e)}if(0!==h.length||o||0!==c)e=isFinite(l)?h.filter((e=>le[l])):h.flat();else{const a=t[ks]();if(!(t=a))return null;c=-1;e=[t]}}return 0===e.length?null:e}function createDataNode(e,t,a){const r=parseExpression(a);if(!r)return null;if(r.some((e=>e.operator===yo)))return null;const i=Ao.get(r[0].name);let n=0;if(i){e=i(e,t);n=1}else e=t||e;for(let t=r.length;ne[io]())).join("")}get[Fo](){const e=Object.getPrototypeOf(this);if(!e._attributes){const t=e._attributes=new Set;for(const e of Object.getOwnPropertyNames(this)){if(null===this[e]||this[e]instanceof XFAObject||this[e]instanceof XFAObjectArray)break;t.add(e)}}return shadow(this,Fo,e._attributes)}[Rs](e){let t=this;for(;t;){if(t===e)return!0;t=t[ks]()}return!1}[ks](){return this[jo]}[As](){return this[ks]()}[ws](e=null){return e?this[e]:this[Io]}[ss](){const e=Object.create(null);this[is]&&(e.$content=this[is]);for(const t of Object.getOwnPropertyNames(this)){const a=this[t];null!==a&&(a instanceof XFAObject?e[t]=a[ss]():a instanceof XFAObjectArray?a.isEmpty()||(e[t]=a.dump()):e[t]=a)}return e}[co](){return null}[so](){return HTMLResult.EMPTY}*[Ss](){for(const e of this[ws]())yield e}*[Bo](e,t){for(const a of this[Ss]())if(!e||t===e.has(a[qs])){const e=this[ds](),t=a[so](e);t.success||(this[os].failingNode=a);yield t}}[ls](){return null}[Jn](e,t){this[os].children.push(e)}[ds](){}[Zn]({filter:e=null,include:t=!0}){if(this[os].generator){const e=this[ds](),t=this[os].failingNode[so](e);if(!t.success)return t;t.html&&this[Jn](t.html,t.bbox);delete this[os].failingNode}else this[os].generator=this[Bo](e,t);for(;;){const e=this[os].generator.next();if(e.done)break;const t=e.value;if(!t.success)return t;t.html&&this[Jn](t.html,t.bbox)}this[os].generator=null;return HTMLResult.EMPTY}[to](e){this[Uo]=new Set(Object.keys(e))}[No](e){const t=this[Fo],a=this[Uo];return[...e].filter((e=>t.has(e)&&!a.has(e)))}[Zs](e,t=new Set){for(const a of this[Io])a[_o](e,t)}[_o](e,t){const a=this[Ro](e,t);a?this[Co](a,e,t):this[Zs](e,t)}[Ro](e,t){const{use:a,usehref:r}=this;if(!a&&!r)return null;let i=null,n=null,s=null,o=a;if(r){o=r;r.startsWith("#som(")&&r.endsWith(")")?n=r.slice(5,-1):r.startsWith(".#som(")&&r.endsWith(")")?n=r.slice(6,-1):r.startsWith("#")?s=r.slice(1):r.startsWith(".#")&&(s=r.slice(2))}else a.startsWith("#")?s=a.slice(1):n=a;this.use=this.usehref="";if(s)i=e.get(s);else{i=searchNode(e.get(Ys),this,n,!0,!1);i&&(i=i[0])}if(!i){warn(`XFA - Invalid prototype reference: ${o}.`);return null}if(i[qs]!==this[qs]){warn(`XFA - Incompatible prototype: ${i[qs]} !== ${this[qs]}.`);return null}if(t.has(i)){warn("XFA - Cycle detected in prototypes use.");return null}t.add(i);const c=i[Ro](e,t);c&&i[Co](c,e,t);i[Zs](e,t);t.delete(i);return i}[Co](e,t,a){if(a.has(e)){warn("XFA - Cycle detected in prototypes use.");return}!this[is]&&e[is]&&(this[is]=e[is]);new Set(a).add(e);for(const t of this[No](e[Uo])){this[t]=e[t];this[Uo]&&this[Uo].add(t)}for(const r of Object.getOwnPropertyNames(this)){if(this[Fo].has(r))continue;const i=this[r],n=e[r];if(i instanceof XFAObjectArray){for(const e of i[Io])e[_o](t,a);for(let r=i[Io].length,s=n[Io].length;rXFAObject[Oo](e))):"object"==typeof e&&null!==e?Object.assign({},e):e}[as](){const e=Object.create(Object.getPrototypeOf(this));for(const t of Object.getOwnPropertySymbols(this))try{e[t]=this[t]}catch{shadow(e,t,this[t])}e[lo]=`${e[qs]}${qo++}`;e[Io]=[];for(const t of Object.getOwnPropertyNames(this)){if(this[Fo].has(t)){e[t]=XFAObject[Oo](this[t]);continue}const a=this[t];e[t]=a instanceof XFAObjectArray?new XFAObjectArray(a[Po]):null}for(const t of this[Io]){const a=t[qs],r=t[as]();e[Io].push(r);r[jo]=e;null===e[a]?e[a]=r:e[a][Io].push(r)}return e}[ws](e=null){return e?this[Io].filter((t=>t[qs]===e)):this[Io]}[fs](e){return this[e]}[gs](e,t,a=!0){return Array.from(this[ps](e,t,a))}*[ps](e,t,a=!0){if("parent"!==e){for(const a of this[Io]){a[qs]===e&&(yield a);a.name===e&&(yield a);(t||a[js]())&&(yield*a[ps](e,t,!1))}a&&this[Fo].has(e)&&(yield new XFAAttribute(this,e,this[e]))}else yield this[jo]}}class XFAObjectArray{constructor(e=1/0){this[Po]=e;this[Io]=[]}get isXFAObject(){return!1}get isXFAObjectArray(){return!0}push(e){if(this[Io].length<=this[Po]){this[Io].push(e);return!0}warn(`XFA - node "${e[qs]}" accepts no more than ${this[Po]} children`);return!1}isEmpty(){return 0===this[Io].length}dump(){return 1===this[Io].length?this[Io][0][ss]():this[Io].map((e=>e[ss]()))}[as](){const e=new XFAObjectArray(this[Po]);e[Io]=this[Io].map((e=>e[as]()));return e}get children(){return this[Io]}clear(){this[Io].length=0}}class XFAAttribute{constructor(e,t,a){this[jo]=e;this[qs]=t;this[is]=a;this[rs]=!1;this[lo]="attribute"+qo++}[ks](){return this[jo]}[Bs](){return!0}[ms](){return this[is].trim()}[ao](e){e=e.value||"";this[is]=e.toString()}[io](){return this[is]}[Rs](e){return this[jo]===e||this[jo][Rs](e)}}class XmlObject extends XFAObject{constructor(e,t,a={}){super(e,t);this[is]="";this[Mo]=null;if("#text"!==t){const e=new Map;this[vo]=e;for(const[t,r]of Object.entries(a))e.set(t,new XFAAttribute(this,t,r));if(a.hasOwnProperty(Hs)){const e=a[Hs].xfa.dataNode;void 0!==e&&("dataGroup"===e?this[Mo]=!1:"dataValue"===e&&(this[Mo]=!0))}}this[rs]=!1}[oo](e){const t=this[qs];if("#text"===t){e.push(encodeToXmlString(this[is]));return}const a=utf8StringToString(t),r=this[Xs]===Ho?"xfa:":"";e.push(`<${r}${a}`);for(const[t,a]of this[vo].entries()){const r=utf8StringToString(t);e.push(` ${r}="${encodeToXmlString(a[is])}"`)}null!==this[Mo]&&(this[Mo]?e.push(' xfa:dataNode="dataValue"'):e.push(' xfa:dataNode="dataGroup"'));if(this[is]||0!==this[Io].length){e.push(">");if(this[is])"string"==typeof this[is]?e.push(encodeToXmlString(this[is])):this[is][oo](e);else for(const t of this[Io])t[oo](e);e.push(``)}else e.push("/>")}[Ws](e){if(this[is]){const e=new XmlObject(this[Xs],"#text");this[Yn](e);e[is]=this[is];this[is]=""}this[Yn](e);return!0}[$s](e){this[is]+=e}[cs](){if(this[is]&&this[Io].length>0){const e=new XmlObject(this[Xs],"#text");this[Yn](e);e[is]=this[is];delete this[is]}}[so](){return"#text"===this[qs]?HTMLResult.success({name:"#text",value:this[is]}):HTMLResult.EMPTY}[ws](e=null){return e?this[Io].filter((t=>t[qs]===e)):this[Io]}[us](){return this[vo]}[fs](e){const t=this[vo].get(e);return void 0!==t?t:this[ws](e)}*[ps](e,t){const a=this[vo].get(e);a&&(yield a);for(const a of this[Io]){a[qs]===e&&(yield a);t&&(yield*a[ps](e,t))}}*[hs](e,t){const a=this[vo].get(e);!a||t&&a[rs]||(yield a);for(const a of this[Io])yield*a[hs](e,t)}*[ys](e,t,a){for(const r of this[Io]){r[qs]!==e||a&&r[rs]||(yield r);t&&(yield*r[ys](e,t,a))}}[Bs](){return null===this[Mo]?0===this[Io].length||this[Io][0][Xs]===uo.xhtml.id:this[Mo]}[ms](){return null===this[Mo]?0===this[Io].length?this[is].trim():this[Io][0][Xs]===uo.xhtml.id?this[Io][0][io]().trim():null:this[is].trim()}[ao](e){e=e.value||"";this[is]=e.toString()}[ss](e=!1){const t=Object.create(null);e&&(t.$ns=this[Xs]);this[is]&&(t.$content=this[is]);t.$name=this[qs];t.children=[];for(const a of this[Io])t.children.push(a[ss](e));t.attributes=Object.create(null);for(const[e,a]of this[vo])t.attributes[e]=a[is];return t}}class ContentObject extends XFAObject{constructor(e,t){super(e,t);this[is]=""}[$s](e){this[is]+=e}[cs](){}}class OptionObject extends ContentObject{constructor(e,t,a){super(e,t);this[Lo]=a}[cs](){this[is]=getKeyword({data:this[is],defaultValue:this[Lo][0],validate:e=>this[Lo].includes(e)})}[Qn](e){super[Qn](e);delete this[Lo]}}class StringObject extends ContentObject{[cs](){this[is]=this[is].trim()}}class IntegerObject extends ContentObject{constructor(e,t,a,r){super(e,t);this[Do]=a;this[Xo]=r}[cs](){this[is]=getInteger({data:this[is],defaultValue:this[Do],validate:this[Xo]})}[Qn](e){super[Qn](e);delete this[Do];delete this[Xo]}}class Option01 extends IntegerObject{constructor(e,t){super(e,t,0,(e=>1===e))}}class Option10 extends IntegerObject{constructor(e,t){super(e,t,1,(e=>0===e))}}function measureToString(e){return"string"==typeof e?"0px":Number.isInteger(e)?`${e}px`:`${e.toFixed(2)}px`}const Wo={anchorType(e,t){const a=e[As]();if(a&&(!a.layout||"position"===a.layout)){"transform"in t||(t.transform="");switch(e.anchorType){case"bottomCenter":t.transform+="translate(-50%, -100%)";break;case"bottomLeft":t.transform+="translate(0,-100%)";break;case"bottomRight":t.transform+="translate(-100%,-100%)";break;case"middleCenter":t.transform+="translate(-50%,-50%)";break;case"middleLeft":t.transform+="translate(0,-50%)";break;case"middleRight":t.transform+="translate(-100%,-50%)";break;case"topCenter":t.transform+="translate(-50%,0)";break;case"topRight":t.transform+="translate(-100%,0)"}}},dimensions(e,t){const a=e[As]();let r=e.w;const i=e.h;if(a.layout?.includes("row")){const t=a[os],i=e.colSpan;let n;if(-1===i){n=Math.sumPrecise(t.columnWidths.slice(t.currentColumn));t.currentColumn=0}else{n=Math.sumPrecise(t.columnWidths.slice(t.currentColumn,t.currentColumn+i));t.currentColumn=(t.currentColumn+e.colSpan)%t.columnWidths.length}isNaN(n)||(r=e.w=n)}t.width=""!==r?measureToString(r):"auto";t.height=""!==i?measureToString(i):"auto"},position(e,t){const a=e[As]();if(!a?.layout||"position"===a.layout){t.position="absolute";t.left=measureToString(e.x);t.top=measureToString(e.y)}},rotate(e,t){if(e.rotate){"transform"in t||(t.transform="");t.transform+=`rotate(-${e.rotate}deg)`;t.transformOrigin="top left"}},presence(e,t){switch(e.presence){case"invisible":t.visibility="hidden";break;case"hidden":case"inactive":t.display="none"}},hAlign(e,t){if("para"===e[qs])switch(e.hAlign){case"justifyAll":t.textAlign="justify-all";break;case"radix":t.textAlign="left";break;default:t.textAlign=e.hAlign}else switch(e.hAlign){case"left":t.alignSelf="start";break;case"center":t.alignSelf="center";break;case"right":t.alignSelf="end"}},margin(e,t){e.margin&&(t.margin=e.margin[co]().margin)}};function setMinMaxDimensions(e,t){if("position"===e[As]().layout){e.minW>0&&(t.minWidth=measureToString(e.minW));e.maxW>0&&(t.maxWidth=measureToString(e.maxW));e.minH>0&&(t.minHeight=measureToString(e.minH));e.maxH>0&&(t.maxHeight=measureToString(e.maxH))}}function layoutText(e,t,a,r,i,n){const s=new TextMeasure(t,a,r,i);"string"==typeof e?s.addString(e):e[Gs](s);return s.compute(n)}function layoutNode(e,t){let a=null,r=null,i=!1;if((!e.w||!e.h)&&e.value){let n=0,s=0;if(e.margin){n=e.margin.leftInset+e.margin.rightInset;s=e.margin.topInset+e.margin.bottomInset}let o=null,c=null;if(e.para){c=Object.create(null);o=""===e.para.lineHeight?null:e.para.lineHeight;c.top=""===e.para.spaceAbove?0:e.para.spaceAbove;c.bottom=""===e.para.spaceBelow?0:e.para.spaceBelow;c.left=""===e.para.marginLeft?0:e.para.marginLeft;c.right=""===e.para.marginRight?0:e.para.marginRight}let l=e.font;if(!l){const t=e[Cs]();let a=e[ks]();for(;a&&a!==t;){if(a.font){l=a.font;break}a=a[ks]()}}const h=(e.w||t.width)-n,u=e[vs].fontFinder;if(e.value.exData&&e.value.exData[is]&&"text/html"===e.value.exData.contentType){const t=layoutText(e.value.exData[is],l,c,o,u,h);r=t.width;a=t.height;i=t.isBroken}else{const t=e.value[io]();if(t){const e=layoutText(t,l,c,o,u,h);r=e.width;a=e.height;i=e.isBroken}}null===r||e.w||(r+=n);null===a||e.h||(a+=s)}return{w:r,h:a,isBroken:i}}function computeBbox(e,t,a){let r;if(""!==e.w&&""!==e.h)r=[e.x,e.y,e.w,e.h];else{if(!a)return null;let i=e.w;if(""===i){if(0===e.maxW){const t=e[As]();i="position"===t.layout&&""!==t.w?0:e.minW}else i=Math.min(e.maxW,a.width);t.attributes.style.width=measureToString(i)}let n=e.h;if(""===n){if(0===e.maxH){const t=e[As]();n="position"===t.layout&&""!==t.h?0:e.minH}else n=Math.min(e.maxH,a.height);t.attributes.style.height=measureToString(n)}r=[e.x,e.y,i,n]}return r}function fixDimensions(e){const t=e[As]();if(t.layout?.includes("row")){const a=t[os],r=e.colSpan;let i;i=-1===r?Math.sumPrecise(a.columnWidths.slice(a.currentColumn)):Math.sumPrecise(a.columnWidths.slice(a.currentColumn,a.currentColumn+r));isNaN(i)||(e.w=i)}t.layout&&"position"!==t.layout&&(e.x=e.y=0);"table"===e.layout&&""===e.w&&Array.isArray(e.columnWidths)&&(e.w=Math.sumPrecise(e.columnWidths))}function layoutClass(e){switch(e.layout){case"position":default:return"xfaPosition";case"lr-tb":return"xfaLrTb";case"rl-row":return"xfaRlRow";case"rl-tb":return"xfaRlTb";case"row":return"xfaRow";case"table":return"xfaTable";case"tb":return"xfaTb"}}function toStyle(e,...t){const a=Object.create(null);for(const r of t){const t=e[r];if(null!==t)if(Wo.hasOwnProperty(r))Wo[r](e,a);else if(t instanceof XFAObject){const e=t[co]();e?Object.assign(a,e):warn(`(DEBUG) - XFA - style for ${r} not implemented yet`)}}return a}function createWrapper(e,t){const{attributes:a}=t,{style:r}=a,i={name:"div",attributes:{class:["xfaWrapper"],style:Object.create(null)},children:[]};a.class.push("xfaWrapped");if(e.border){const{widths:a,insets:n}=e.border[os];let s,o,c=n[0],l=n[3];const h=n[0]+n[2],u=n[1]+n[3];switch(e.border.hand){case"even":c-=a[0]/2;l-=a[3]/2;s=`calc(100% + ${(a[1]+a[3])/2-u}px)`;o=`calc(100% + ${(a[0]+a[2])/2-h}px)`;break;case"left":c-=a[0];l-=a[3];s=`calc(100% + ${a[1]+a[3]-u}px)`;o=`calc(100% + ${a[0]+a[2]-h}px)`;break;case"right":s=u?`calc(100% - ${u}px)`:"100%";o=h?`calc(100% - ${h}px)`:"100%"}const d=["xfaBorder"];isPrintOnly(e.border)&&d.push("xfaPrintOnly");const f={name:"div",attributes:{class:d,style:{top:`${c}px`,left:`${l}px`,width:s,height:o}},children:[]};for(const e of["border","borderWidth","borderColor","borderRadius","borderStyle"])if(void 0!==r[e]){f.attributes.style[e]=r[e];delete r[e]}i.children.push(f,t)}else i.children.push(t);for(const e of["background","backgroundClip","top","left","width","height","minWidth","minHeight","maxWidth","maxHeight","transform","transformOrigin","visibility"])if(void 0!==r[e]){i.attributes.style[e]=r[e];delete r[e]}i.attributes.style.position="absolute"===r.position?"absolute":"relative";delete r.position;if(r.alignSelf){i.attributes.style.alignSelf=r.alignSelf;delete r.alignSelf}return i}function fixTextIndent(e){const t=getMeasurement(e.textIndent,"0px");if(t>=0)return;const a="padding"+("left"===("right"===e.textAlign?"right":"left")?"Left":"Right"),r=getMeasurement(e[a],"0px");e[a]=r-t+"px"}function setAccess(e,t){switch(e.access){case"nonInteractive":t.push("xfaNonInteractive");break;case"readOnly":t.push("xfaReadOnly");break;case"protected":t.push("xfaDisabled")}}function isPrintOnly(e){return e.relevant.length>0&&!e.relevant[0].excluded&&"print"===e.relevant[0].viewname}function getCurrentPara(e){const t=e[Cs]()[os].paraStack;return t.length?t.at(-1):null}function setPara(e,t,a){if(a.attributes.class?.includes("xfaRich")){if(t){""===e.h&&(t.height="auto");""===e.w&&(t.width="auto")}const r=getCurrentPara(e);if(r){const e=a.attributes.style;e.display="flex";e.flexDirection="column";switch(r.vAlign){case"top":e.justifyContent="start";break;case"bottom":e.justifyContent="end";break;case"middle":e.justifyContent="center"}const t=r[co]();for(const[a,r]of Object.entries(t))a in e||(e[a]=r)}}}function setFontFamily(e,t,a,r){if(!a){delete r.fontFamily;return}const i=stripQuotes(e.typeface);r.fontFamily=`"${i}"`;const n=a.find(i);if(n){const{fontFamily:a}=n.regular.cssFontInfo;a!==i&&(r.fontFamily=`"${a}"`);const s=getCurrentPara(t);if(s&&""!==s.lineHeight)return;if(r.lineHeight)return;const o=selectFont(e,n);o&&(r.lineHeight=Math.max(1.2,o.lineHeight))}}function fixURL(e){const t=createValidAbsoluteUrl(e,null,{addDefaultProtocol:!0,tryConvertEncoding:!0});return t?t.href:null}function createLine(e,t){return{name:"div",attributes:{class:["lr-tb"===e.layout?"xfaLr":"xfaRl"]},children:t}}function flushHTML(e){if(!e[os])return null;const t={name:"div",attributes:e[os].attributes,children:e[os].children};if(e[os].failingNode){const a=e[os].failingNode[ls]();a&&(e.layout.endsWith("-tb")?t.children.push(createLine(e,[a])):t.children.push(a))}return 0===t.children.length?null:t}function addHTML(e,t,a){const r=e[os],i=r.availableSpace,[n,s,o,c]=a;switch(e.layout){case"position":r.width=Math.max(r.width,n+o);r.height=Math.max(r.height,s+c);r.children.push(t);break;case"lr-tb":case"rl-tb":if(!r.line||1===r.attempt){r.line=createLine(e,[]);r.children.push(r.line);r.numberInLine=0}r.numberInLine+=1;r.line.children.push(t);if(0===r.attempt){r.currentWidth+=o;r.height=Math.max(r.height,r.prevHeight+c)}else{r.currentWidth=o;r.prevHeight=r.height;r.height+=c;r.attempt=0}r.width=Math.max(r.width,r.currentWidth);break;case"rl-row":case"row":{r.children.push(t);r.width+=o;r.height=Math.max(r.height,c);const e=measureToString(r.height);for(const t of r.children)t.attributes.style.height=e;break}case"table":case"tb":r.width=MathClamp(o,r.width,i.width);r.height+=c;r.children.push(t)}}function getAvailableSpace(e){const t=e[os].availableSpace,a=e.margin?e.margin.topInset+e.margin.bottomInset:0,r=e.margin?e.margin.leftInset+e.margin.rightInset:0;switch(e.layout){case"lr-tb":case"rl-tb":return 0===e[os].attempt?{width:t.width-r-e[os].currentWidth,height:t.height-a-e[os].prevHeight}:{width:t.width-r,height:t.height-a-e[os].height};case"rl-row":case"row":return{width:Math.sumPrecise(e[os].columnWidths.slice(e[os].currentColumn)),height:t.height-r};case"table":case"tb":return{width:t.width-r,height:t.height-a-e[os].height};default:return t}}function checkDimensions(e,t){if(null===e[Cs]()[os].firstUnsplittable)return!0;if(0===e.w||0===e.h)return!0;const a=e[As](),r=a[os]?.attempt||0,[,i,n,s]=function getTransformedBBox(e){let t,a,r=""===e.w?NaN:e.w,i=""===e.h?NaN:e.h,[n,s]=[0,0];switch(e.anchorType||""){case"bottomCenter":[n,s]=[r/2,i];break;case"bottomLeft":[n,s]=[0,i];break;case"bottomRight":[n,s]=[r,i];break;case"middleCenter":[n,s]=[r/2,i/2];break;case"middleLeft":[n,s]=[0,i/2];break;case"middleRight":[n,s]=[r,i/2];break;case"topCenter":[n,s]=[r/2,0];break;case"topRight":[n,s]=[r,0]}switch(e.rotate||0){case 0:[t,a]=[-n,-s];break;case 90:[t,a]=[-s,n];[r,i]=[i,-r];break;case 180:[t,a]=[n,s];[r,i]=[-r,-i];break;case 270:[t,a]=[s,-n];[r,i]=[-i,r]}return[e.x+t+Math.min(0,r),e.y+a+Math.min(0,i),Math.abs(r),Math.abs(i)]}(e);switch(a.layout){case"lr-tb":case"rl-tb":return 0===r?e[Cs]()[os].noLayoutFailure?""!==e.w?Math.round(n-t.width)<=2:t.width>2:!(""!==e.h&&Math.round(s-t.height)>2)&&(""!==e.w?Math.round(n-t.width)<=2||0===a[os].numberInLine&&t.height>2:t.width>2):!!e[Cs]()[os].noLayoutFailure||!(""!==e.h&&Math.round(s-t.height)>2)&&((""===e.w||Math.round(n-t.width)<=2||!a[Ls]())&&t.height>2);case"table":case"tb":return!!e[Cs]()[os].noLayoutFailure||(""===e.h||e[Es]()?(""===e.w||Math.round(n-t.width)<=2||!a[Ls]())&&t.height>2:Math.round(s-t.height)<=2);case"position":if(e[Cs]()[os].noLayoutFailure)return!0;if(""===e.h||Math.round(s+i-t.height)<=2)return!0;return s+i>e[Cs]()[os].currentContentArea.h;case"rl-row":case"row":return!!e[Cs]()[os].noLayoutFailure||(""===e.h||Math.round(s-t.height)<=2);default:return!0}}const zo=uo.template.id,$o="http://www.w3.org/2000/svg",Go=/^H(\d+)$/,Vo=new Set(["image/gif","image/jpeg","image/jpg","image/pjpeg","image/png","image/apng","image/x-png","image/bmp","image/x-ms-bmp","image/tiff","image/tif","application/octet-stream"]),Ko=[[[66,77],"image/bmp"],[[255,216,255],"image/jpeg"],[[73,73,42,0],"image/tiff"],[[77,77,0,42],"image/tiff"],[[71,73,70,56,57,97],"image/gif"],[[137,80,78,71,13,10,26,10],"image/png"]];function getBorderDims(e){if(!e||!e.border)return{w:0,h:0};const t=e.border[bs]();return t?{w:t.widths[0]+t.widths[2]+t.insets[0]+t.insets[2],h:t.widths[1]+t.widths[3]+t.insets[1]+t.insets[3]}:{w:0,h:0}}function hasMargin(e){return e.margin&&(e.margin.topInset||e.margin.rightInset||e.margin.bottomInset||e.margin.leftInset)}function _setValue(e,t){if(!e.value){const t=new Value({});e[Yn](t);e.value=t}e.value[ao](t)}function*getContainedChildren(e){for(const t of e[ws]())t instanceof SubformSet?yield*t[Ss]():yield t}function isRequired(e){return"error"===e.validate?.nullTest}function setTabIndex(e){for(;e;){if(!e.traversal){e[ro]=e[ks]()[ro];return}if(e[ro])return;let t=null;for(const a of e.traversal[ws]())if("next"===a.operation){t=a;break}if(!t||!t.ref){e[ro]=e[ks]()[ro];return}const a=e[Cs]();e[ro]=++a[ro];const r=a[Qs](t.ref,e);if(!r)return;e=r[0]}}function applyAssist(e,t){const a=e.assist;if(a){const e=a[so]();e&&(t.title=e);const r=a.role.match(Go);if(r){const e="heading",a=r[1];t.role=e;t["aria-level"]=a}}if("table"===e.layout)t.role="table";else if("row"===e.layout)t.role="row";else{const a=e[ks]();"row"===a.layout&&(t.role="TH"===a.assist?.role?"columnheader":"cell")}}function ariaLabel(e){if(!e.assist)return null;const t=e.assist;return t.speak&&""!==t.speak[is]?t.speak[is]:t.toolTip?t.toolTip[is]:null}function valueToHtml(e){return HTMLResult.success({name:"div",attributes:{class:["xfaRich"],style:Object.create(null)},children:[{name:"span",attributes:{style:Object.create(null)},value:e}]})}function setFirstUnsplittable(e){const t=e[Cs]();if(null===t[os].firstUnsplittable){t[os].firstUnsplittable=e;t[os].noLayoutFailure=!0}}function unsetFirstUnsplittable(e){const t=e[Cs]();t[os].firstUnsplittable===e&&(t[os].noLayoutFailure=!1)}function handleBreak(e){if(e[os])return!1;e[os]=Object.create(null);if("auto"===e.targetType)return!1;const t=e[Cs]();let a=null;if(e.target){a=t[Qs](e.target,e[ks]());if(!a)return!1;a=a[0]}const{currentPageArea:r,currentContentArea:i}=t[os];if("pageArea"===e.targetType){a instanceof PageArea||(a=null);if(e.startNew){e[os].target=a||r;return!0}if(a&&a!==r){e[os].target=a;return!0}return!1}a instanceof ContentArea||(a=null);const n=a&&a[ks]();let s,o=n;if(e.startNew)if(a){const e=n.contentArea.children,t=e.indexOf(i),r=e.indexOf(a);-1!==t&&te;r[os].noLayoutFailure=!0;const s=t[so](a);e[Jn](s.html,s.bbox);r[os].noLayoutFailure=i;t[As]=n}class AppearanceFilter extends StringObject{constructor(e){super(zo,"appearanceFilter");this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Arc extends XFAObject{constructor(e){super(zo,"arc",!0);this.circular=getInteger({data:e.circular,defaultValue:0,validate:e=>1===e});this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.startAngle=getFloat({data:e.startAngle,defaultValue:0,validate:e=>!0});this.sweepAngle=getFloat({data:e.sweepAngle,defaultValue:360,validate:e=>!0});this.use=e.use||"";this.usehref=e.usehref||"";this.edge=null;this.fill=null}[so](){const e=this.edge||new Edge({}),t=e[co](),a=Object.create(null);"visible"===this.fill?.presence?Object.assign(a,this.fill[co]()):a.fill="transparent";a.strokeWidth=measureToString("visible"===e.presence?e.thickness:0);a.stroke=t.color;let r;const i={xmlns:$o,style:{width:"100%",height:"100%",overflow:"visible"}};if(360===this.sweepAngle)r={name:"ellipse",attributes:{xmlns:$o,cx:"50%",cy:"50%",rx:"50%",ry:"50%",style:a}};else{const e=this.startAngle*Math.PI/180,t=this.sweepAngle*Math.PI/180,n=this.sweepAngle>180?1:0,[s,o,c,l]=[50*(1+Math.cos(e)),50*(1-Math.sin(e)),50*(1+Math.cos(e+t)),50*(1-Math.sin(e+t))];r={name:"path",attributes:{xmlns:$o,d:`M ${s} ${o} A 50 50 0 ${n} 0 ${c} ${l}`,vectorEffect:"non-scaling-stroke",style:a}};Object.assign(i,{viewBox:"0 0 100 100",preserveAspectRatio:"none"})}const n={name:"svg",children:[r],attributes:i};if(hasMargin(this[ks]()[ks]()))return HTMLResult.success({name:"div",attributes:{style:{display:"inline",width:"100%",height:"100%"}},children:[n]});n.attributes.style.position="absolute";return HTMLResult.success(n)}}class Area extends XFAObject{constructor(e){super(zo,"area",!0);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.id=e.id||"";this.name=e.name||"";this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.desc=null;this.extras=null;this.area=new XFAObjectArray;this.draw=new XFAObjectArray;this.exObject=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.field=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray}*[Ss](){yield*getContainedChildren(this)}[js](){return!0}[Ds](){return!0}[Jn](e,t){const[a,r,i,n]=t;this[os].width=Math.max(this[os].width,a+i);this[os].height=Math.max(this[os].height,r+n);this[os].children.push(e)}[ds](){return this[os].availableSpace}[so](e){const t=toStyle(this,"position"),a={style:t,id:this[lo],class:["xfaArea"]};isPrintOnly(this)&&a.class.push("xfaPrintOnly");this.name&&(a.xfaName=this.name);const r=[];this[os]={children:r,width:0,height:0,availableSpace:e};const i=this[Zn]({filter:new Set(["area","draw","field","exclGroup","subform","subformSet"]),include:!0});if(!i.success){if(i.isBreak())return i;delete this[os];return HTMLResult.FAILURE}t.width=measureToString(this[os].width);t.height=measureToString(this[os].height);const n={name:"div",attributes:a,children:r},s=[this.x,this.y,this[os].width,this[os].height];delete this[os];return HTMLResult.success(n,s)}}class Assist extends XFAObject{constructor(e){super(zo,"assist",!0);this.id=e.id||"";this.role=e.role||"";this.use=e.use||"";this.usehref=e.usehref||"";this.speak=null;this.toolTip=null}[so](){return this.toolTip?.[is]||null}}class Barcode extends XFAObject{constructor(e){super(zo,"barcode",!0);this.charEncoding=getKeyword({data:e.charEncoding?e.charEncoding.toLowerCase():"",defaultValue:"",validate:e=>["utf-8","big-five","fontspecific","gbk","gb-18030","gb-2312","ksc-5601","none","shift-jis","ucs-2","utf-16"].includes(e)||e.match(/iso-8859-\d{2}/)});this.checksum=getStringOption(e.checksum,["none","1mod10","1mod10_1mod11","2mod10","auto"]);this.dataColumnCount=getInteger({data:e.dataColumnCount,defaultValue:-1,validate:e=>e>=0});this.dataLength=getInteger({data:e.dataLength,defaultValue:-1,validate:e=>e>=0});this.dataPrep=getStringOption(e.dataPrep,["none","flateCompress"]);this.dataRowCount=getInteger({data:e.dataRowCount,defaultValue:-1,validate:e=>e>=0});this.endChar=e.endChar||"";this.errorCorrectionLevel=getInteger({data:e.errorCorrectionLevel,defaultValue:-1,validate:e=>e>=0&&e<=8});this.id=e.id||"";this.moduleHeight=getMeasurement(e.moduleHeight,"5mm");this.moduleWidth=getMeasurement(e.moduleWidth,"0.25mm");this.printCheckDigit=getInteger({data:e.printCheckDigit,defaultValue:0,validate:e=>1===e});this.rowColumnRatio=getRatio(e.rowColumnRatio);this.startChar=e.startChar||"";this.textLocation=getStringOption(e.textLocation,["below","above","aboveEmbedded","belowEmbedded","none"]);this.truncate=getInteger({data:e.truncate,defaultValue:0,validate:e=>1===e});this.type=getStringOption(e.type?e.type.toLowerCase():"",["aztec","codabar","code2of5industrial","code2of5interleaved","code2of5matrix","code2of5standard","code3of9","code3of9extended","code11","code49","code93","code128","code128a","code128b","code128c","code128sscc","datamatrix","ean8","ean8add2","ean8add5","ean13","ean13add2","ean13add5","ean13pwcd","fim","logmars","maxicode","msi","pdf417","pdf417macro","plessey","postauscust2","postauscust3","postausreplypaid","postausstandard","postukrm4scc","postusdpbc","postusimb","postusstandard","postus5zip","qrcode","rfid","rss14","rss14expanded","rss14limited","rss14stacked","rss14stackedomni","rss14truncated","telepen","ucc128","ucc128random","ucc128sscc","upca","upcaadd2","upcaadd5","upcapwcd","upce","upceadd2","upceadd5","upcean2","upcean5","upsmaxicode"]);this.upsMode=getStringOption(e.upsMode,["usCarrier","internationalCarrier","secureSymbol","standardSymbol"]);this.use=e.use||"";this.usehref=e.usehref||"";this.wideNarrowRatio=getRatio(e.wideNarrowRatio);this.encrypt=null;this.extras=null}}class Bind extends XFAObject{constructor(e){super(zo,"bind",!0);this.match=getStringOption(e.match,["once","dataRef","global","none"]);this.ref=e.ref||"";this.picture=null}}class BindItems extends XFAObject{constructor(e){super(zo,"bindItems");this.connection=e.connection||"";this.labelRef=e.labelRef||"";this.ref=e.ref||"";this.valueRef=e.valueRef||""}}class Bookend extends XFAObject{constructor(e){super(zo,"bookend");this.id=e.id||"";this.leader=e.leader||"";this.trailer=e.trailer||"";this.use=e.use||"";this.usehref=e.usehref||""}}class BooleanElement extends Option01{constructor(e){super(zo,"boolean");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[so](e){return valueToHtml(1===this[is]?"1":"0")}}class Border extends XFAObject{constructor(e){super(zo,"border",!0);this.break=getStringOption(e.break,["close","open"]);this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.corner=new XFAObjectArray(4);this.edge=new XFAObjectArray(4);this.extras=null;this.fill=null;this.margin=null}[bs](){if(!this[os]){const e=this.edge.children.slice();if(e.length<4){const t=e.at(-1)||new Edge({});for(let a=e.length;a<4;a++)e.push(t)}const t=e.map((e=>e.thickness)),a=[0,0,0,0];if(this.margin){a[0]=this.margin.topInset;a[1]=this.margin.rightInset;a[2]=this.margin.bottomInset;a[3]=this.margin.leftInset}this[os]={widths:t,insets:a,edges:e}}return this[os]}[co](){const{edges:e}=this[bs](),t=e.map((e=>{const t=e[co]();t.color||="#000000";return t})),a=Object.create(null);this.margin&&Object.assign(a,this.margin[co]());"visible"===this.fill?.presence&&Object.assign(a,this.fill[co]());if(this.corner.children.some((e=>0!==e.radius))){const e=this.corner.children.map((e=>e[co]()));if(2===e.length||3===e.length){const t=e.at(-1);for(let a=e.length;a<4;a++)e.push(t)}a.borderRadius=e.map((e=>e.radius)).join(" ")}switch(this.presence){case"invisible":case"hidden":a.borderStyle="";break;case"inactive":a.borderStyle="none";break;default:a.borderStyle=t.map((e=>e.style)).join(" ")}a.borderWidth=t.map((e=>e.width)).join(" ");a.borderColor=t.map((e=>e.color)).join(" ");return a}}class Break extends XFAObject{constructor(e){super(zo,"break",!0);this.after=getStringOption(e.after,["auto","contentArea","pageArea","pageEven","pageOdd"]);this.afterTarget=e.afterTarget||"";this.before=getStringOption(e.before,["auto","contentArea","pageArea","pageEven","pageOdd"]);this.beforeTarget=e.beforeTarget||"";this.bookendLeader=e.bookendLeader||"";this.bookendTrailer=e.bookendTrailer||"";this.id=e.id||"";this.overflowLeader=e.overflowLeader||"";this.overflowTarget=e.overflowTarget||"";this.overflowTrailer=e.overflowTrailer||"";this.startNew=getInteger({data:e.startNew,defaultValue:0,validate:e=>1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}}class BreakAfter extends XFAObject{constructor(e){super(zo,"breakAfter",!0);this.id=e.id||"";this.leader=e.leader||"";this.startNew=getInteger({data:e.startNew,defaultValue:0,validate:e=>1===e});this.target=e.target||"";this.targetType=getStringOption(e.targetType,["auto","contentArea","pageArea"]);this.trailer=e.trailer||"";this.use=e.use||"";this.usehref=e.usehref||"";this.script=null}}class BreakBefore extends XFAObject{constructor(e){super(zo,"breakBefore",!0);this.id=e.id||"";this.leader=e.leader||"";this.startNew=getInteger({data:e.startNew,defaultValue:0,validate:e=>1===e});this.target=e.target||"";this.targetType=getStringOption(e.targetType,["auto","contentArea","pageArea"]);this.trailer=e.trailer||"";this.use=e.use||"";this.usehref=e.usehref||"";this.script=null}[so](e){this[os]={};return HTMLResult.FAILURE}}class Button extends XFAObject{constructor(e){super(zo,"button",!0);this.highlight=getStringOption(e.highlight,["inverted","none","outline","push"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[so](e){const t=this[ks]()[ks](),a={name:"button",attributes:{id:this[lo],class:["xfaButton"],style:{}},children:[]};for(const e of t.event.children){if("click"!==e.activity||!e.script)continue;const t=recoverJsURL(e.script[is]);if(!t)continue;const r=fixURL(t.url);r&&a.children.push({name:"a",attributes:{id:"link"+this[lo],href:r,newWindow:t.newWindow,class:["xfaLink"],style:{}},children:[]})}return HTMLResult.success(a)}}class Calculate extends XFAObject{constructor(e){super(zo,"calculate",!0);this.id=e.id||"";this.override=getStringOption(e.override,["disabled","error","ignore","warning"]);this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.message=null;this.script=null}}class Caption extends XFAObject{constructor(e){super(zo,"caption",!0);this.id=e.id||"";this.placement=getStringOption(e.placement,["left","bottom","inline","right","top"]);this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.reserve=Math.ceil(getMeasurement(e.reserve));this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.font=null;this.margin=null;this.para=null;this.value=null}[ao](e){_setValue(this,e)}[bs](e){if(!this[os]){let{width:t,height:a}=e;switch(this.placement){case"left":case"right":case"inline":t=this.reserve<=0?t:this.reserve;break;case"top":case"bottom":a=this.reserve<=0?a:this.reserve}this[os]=layoutNode(this,{width:t,height:a})}return this[os]}[so](e){if(!this.value)return HTMLResult.EMPTY;this[Ks]();const t=this.value[so](e).html;if(!t){this[Vs]();return HTMLResult.EMPTY}const a=this.reserve;if(this.reserve<=0){const{w:t,h:a}=this[bs](e);switch(this.placement){case"left":case"right":case"inline":this.reserve=t;break;case"top":case"bottom":this.reserve=a}}const r=[];"string"==typeof t?r.push({name:"#text",value:t}):r.push(t);const i=toStyle(this,"font","margin","visibility");switch(this.placement){case"left":case"right":this.reserve>0&&(i.width=measureToString(this.reserve));break;case"top":case"bottom":this.reserve>0&&(i.height=measureToString(this.reserve))}setPara(this,null,t);this[Vs]();this.reserve=a;return HTMLResult.success({name:"div",attributes:{style:i,class:["xfaCaption"]},children:r})}}class Certificate extends StringObject{constructor(e){super(zo,"certificate");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Certificates extends XFAObject{constructor(e){super(zo,"certificates",!0);this.credentialServerPolicy=getStringOption(e.credentialServerPolicy,["optional","required"]);this.id=e.id||"";this.url=e.url||"";this.urlPolicy=e.urlPolicy||"";this.use=e.use||"";this.usehref=e.usehref||"";this.encryption=null;this.issuers=null;this.keyUsage=null;this.oids=null;this.signing=null;this.subjectDNs=null}}class CheckButton extends XFAObject{constructor(e){super(zo,"checkButton",!0);this.id=e.id||"";this.mark=getStringOption(e.mark,["default","check","circle","cross","diamond","square","star"]);this.shape=getStringOption(e.shape,["square","round"]);this.size=getMeasurement(e.size,"10pt");this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}[so](e){const t=toStyle(this,"margin"),a=measureToString(this.size);t.width=t.height=a;let r,i,n;const s=this[ks]()[ks](),o=s.items.children.length&&s.items.children[0][so]().html||[],c={on:(void 0!==o[0]?o[0]:"on").toString(),off:(void 0!==o[1]?o[1]:"off").toString()},l=(s.value?.[io]()||"off")===c.on||void 0,h=s[As](),u=s[lo];let d;if(h instanceof ExclGroup){n=h[lo];r="radio";i="xfaRadio";d=h[ns]?.[lo]||h[lo]}else{r="checkbox";i="xfaCheckbox";d=s[ns]?.[lo]||s[lo]}const f={name:"input",attributes:{class:[i],style:t,fieldId:u,dataId:d,type:r,checked:l,xfaOn:c.on,xfaOff:c.off,"aria-label":ariaLabel(s),"aria-required":!1}};n&&(f.attributes.name=n);if(isRequired(s)){f.attributes["aria-required"]=!0;f.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[f]})}}class ChoiceList extends XFAObject{constructor(e){super(zo,"choiceList",!0);this.commitOn=getStringOption(e.commitOn,["select","exit"]);this.id=e.id||"";this.open=getStringOption(e.open,["userControl","always","multiSelect","onEntry"]);this.textEntry=getInteger({data:e.textEntry,defaultValue:0,validate:e=>1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}[so](e){const t=toStyle(this,"border","margin"),a=this[ks]()[ks](),r={fontSize:`calc(${a.font?.size||10}px * var(--total-scale-factor))`},i=[];if(a.items.children.length>0){const e=a.items;let t=0,n=0;if(2===e.children.length){t=e.children[0].save;n=1-t}const s=e.children[t][so]().html,o=e.children[n][so]().html;let c=!1;const l=a.value?.[io]()||"";for(let e=0,t=s.length;eMathClamp(parseInt(e.trim(),10),0,255))).map((e=>isNaN(e)?0:e));if(n.length<3)return{r:a,g:r,b:i};[a,r,i]=n;return{r:a,g:r,b:i}}(e.value):"";this.extras=null}[Fs](){return!1}[co](){return this.value?Util.makeHexColor(this.value.r,this.value.g,this.value.b):null}}class Comb extends XFAObject{constructor(e){super(zo,"comb");this.id=e.id||"";this.numberOfCells=getInteger({data:e.numberOfCells,defaultValue:0,validate:e=>e>=0});this.use=e.use||"";this.usehref=e.usehref||""}}class Connect extends XFAObject{constructor(e){super(zo,"connect",!0);this.connection=e.connection||"";this.id=e.id||"";this.ref=e.ref||"";this.usage=getStringOption(e.usage,["exportAndImport","exportOnly","importOnly"]);this.use=e.use||"";this.usehref=e.usehref||"";this.picture=null}}class ContentArea extends XFAObject{constructor(e){super(zo,"contentArea",!0);this.h=getMeasurement(e.h);this.id=e.id||"";this.name=e.name||"";this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.w=getMeasurement(e.w);this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.desc=null;this.extras=null}[so](e){const t={left:measureToString(this.x),top:measureToString(this.y),width:measureToString(this.w),height:measureToString(this.h)},a=["xfaContentarea"];isPrintOnly(this)&&a.push("xfaPrintOnly");return HTMLResult.success({name:"div",children:[],attributes:{style:t,class:a,id:this[lo]}})}}class Corner extends XFAObject{constructor(e){super(zo,"corner",!0);this.id=e.id||"";this.inverted=getInteger({data:e.inverted,defaultValue:0,validate:e=>1===e});this.join=getStringOption(e.join,["square","round"]);this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.radius=getMeasurement(e.radius);this.stroke=getStringOption(e.stroke,["solid","dashDot","dashDotDot","dashed","dotted","embossed","etched","lowered","raised"]);this.thickness=getMeasurement(e.thickness,"0.5pt");this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[co](){const e=toStyle(this,"visibility");e.radius=measureToString("square"===this.join?0:this.radius);return e}}class DateElement extends ContentObject{constructor(e){super(zo,"date");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){const e=this[is].trim();this[is]=e?new Date(e):null}[so](e){return valueToHtml(this[is]?this[is].toString():"")}}class DateTime extends ContentObject{constructor(e){super(zo,"dateTime");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){const e=this[is].trim();this[is]=e?new Date(e):null}[so](e){return valueToHtml(this[is]?this[is].toString():"")}}class DateTimeEdit extends XFAObject{constructor(e){super(zo,"dateTimeEdit",!0);this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.picker=getStringOption(e.picker,["host","none"]);this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.comb=null;this.extras=null;this.margin=null}[so](e){const t=toStyle(this,"border","font","margin"),a=this[ks]()[ks](),r={name:"input",attributes:{type:"text",fieldId:a[lo],dataId:a[ns]?.[lo]||a[lo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(a),"aria-required":!1}};if(isRequired(a)){r.attributes["aria-required"]=!0;r.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[r]})}}class Decimal extends ContentObject{constructor(e){super(zo,"decimal");this.fracDigits=getInteger({data:e.fracDigits,defaultValue:2,validate:e=>!0});this.id=e.id||"";this.leadDigits=getInteger({data:e.leadDigits,defaultValue:-1,validate:e=>!0});this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){const e=parseFloat(this[is].trim());this[is]=isNaN(e)?null:e}[so](e){return valueToHtml(null!==this[is]?this[is].toString():"")}}class DefaultUi extends XFAObject{constructor(e){super(zo,"defaultUi",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}}class Desc extends XFAObject{constructor(e){super(zo,"desc",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}}class DigestMethod extends OptionObject{constructor(e){super(zo,"digestMethod",["","SHA1","SHA256","SHA512","RIPEMD160"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class DigestMethods extends XFAObject{constructor(e){super(zo,"digestMethods",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.digestMethod=new XFAObjectArray}}class Draw extends XFAObject{constructor(e){super(zo,"draw",!0);this.anchorType=getStringOption(e.anchorType,["topLeft","bottomCenter","bottomLeft","bottomRight","middleCenter","middleLeft","middleRight","topCenter","topRight"]);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.locale=e.locale||"";this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.rotate=getInteger({data:e.rotate,defaultValue:0,validate:e=>e%90==0});this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.border=null;this.caption=null;this.desc=null;this.extras=null;this.font=null;this.keep=null;this.margin=null;this.para=null;this.traversal=null;this.ui=null;this.value=null;this.setProperty=new XFAObjectArray}[ao](e){_setValue(this,e)}[so](e){setTabIndex(this);if("hidden"===this.presence||"inactive"===this.presence)return HTMLResult.EMPTY;fixDimensions(this);this[Ks]();const t=this.w,a=this.h,{w:r,h:i,isBroken:n}=layoutNode(this,e);if(r&&""===this.w){if(n&&this[As]()[Ls]()){this[Vs]();return HTMLResult.FAILURE}this.w=r}i&&""===this.h&&(this.h=i);setFirstUnsplittable(this);if(!checkDimensions(this,e)){this.w=t;this.h=a;this[Vs]();return HTMLResult.FAILURE}unsetFirstUnsplittable(this);const s=toStyle(this,"font","hAlign","dimensions","position","presence","rotate","anchorType","border","margin");setMinMaxDimensions(this,s);if(s.margin){s.padding=s.margin;delete s.margin}const o=["xfaDraw"];this.font&&o.push("xfaFont");isPrintOnly(this)&&o.push("xfaPrintOnly");const c={style:s,id:this[lo],class:o};this.name&&(c.xfaName=this.name);const l={name:"div",attributes:c,children:[]};applyAssist(this,c);const h=computeBbox(this,l,e),u=this.value?this.value[so](e).html:null;if(null===u){this.w=t;this.h=a;this[Vs]();return HTMLResult.success(createWrapper(this,l),h)}l.children.push(u);setPara(this,s,u);this.w=t;this.h=a;this[Vs]();return HTMLResult.success(createWrapper(this,l),h)}}class Edge extends XFAObject{constructor(e){super(zo,"edge",!0);this.cap=getStringOption(e.cap,["square","butt","round"]);this.id=e.id||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.stroke=getStringOption(e.stroke,["solid","dashDot","dashDotDot","dashed","dotted","embossed","etched","lowered","raised"]);this.thickness=getMeasurement(e.thickness,"0.5pt");this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[co](){const e=toStyle(this,"visibility");Object.assign(e,{linecap:this.cap,width:measureToString(this.thickness),color:this.color?this.color[co]():"#000000",style:""});if("visible"!==this.presence)e.style="none";else switch(this.stroke){case"solid":e.style="solid";break;case"dashDot":case"dashDotDot":case"dashed":e.style="dashed";break;case"dotted":e.style="dotted";break;case"embossed":e.style="ridge";break;case"etched":e.style="groove";break;case"lowered":e.style="inset";break;case"raised":e.style="outset"}return e}}class Encoding extends OptionObject{constructor(e){super(zo,"encoding",["adbe.x509.rsa_sha1","adbe.pkcs7.detached","adbe.pkcs7.sha1"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Encodings extends XFAObject{constructor(e){super(zo,"encodings",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.encoding=new XFAObjectArray}}class Encrypt extends XFAObject{constructor(e){super(zo,"encrypt",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=null}}class EncryptData extends XFAObject{constructor(e){super(zo,"encryptData",!0);this.id=e.id||"";this.operation=getStringOption(e.operation,["encrypt","decrypt"]);this.target=e.target||"";this.use=e.use||"";this.usehref=e.usehref||"";this.filter=null;this.manifest=null}}class Encryption extends XFAObject{constructor(e){super(zo,"encryption",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=new XFAObjectArray}}class EncryptionMethod extends OptionObject{constructor(e){super(zo,"encryptionMethod",["","AES256-CBC","TRIPLEDES-CBC","AES128-CBC","AES192-CBC"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class EncryptionMethods extends XFAObject{constructor(e){super(zo,"encryptionMethods",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.encryptionMethod=new XFAObjectArray}}class Event extends XFAObject{constructor(e){super(zo,"event",!0);this.activity=getStringOption(e.activity,["click","change","docClose","docReady","enter","exit","full","indexChange","initialize","mouseDown","mouseEnter","mouseExit","mouseUp","postExecute","postOpen","postPrint","postSave","postSign","postSubmit","preExecute","preOpen","prePrint","preSave","preSign","preSubmit","ready","validationState"]);this.id=e.id||"";this.listen=getStringOption(e.listen,["refOnly","refAndDescendents"]);this.name=e.name||"";this.ref=e.ref||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.encryptData=null;this.execute=null;this.script=null;this.signData=null;this.submit=null}}class ExData extends ContentObject{constructor(e){super(zo,"exData");this.contentType=e.contentType||"";this.href=e.href||"";this.id=e.id||"";this.maxLength=getInteger({data:e.maxLength,defaultValue:-1,validate:e=>e>=-1});this.name=e.name||"";this.rid=e.rid||"";this.transferEncoding=getStringOption(e.transferEncoding,["none","base64","package"]);this.use=e.use||"";this.usehref=e.usehref||""}[Ms](){return"text/html"===this.contentType}[Ws](e){if("text/html"===this.contentType&&e[Xs]===uo.xhtml.id){this[is]=e;return!0}if("text/xml"===this.contentType){this[is]=e;return!0}return!1}[so](e){return"text/html"===this.contentType&&this[is]?this[is][so](e):HTMLResult.EMPTY}}class ExObject extends XFAObject{constructor(e){super(zo,"exObject",!0);this.archive=e.archive||"";this.classId=e.classId||"";this.codeBase=e.codeBase||"";this.codeType=e.codeType||"";this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.exObject=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}}class ExclGroup extends XFAObject{constructor(e){super(zo,"exclGroup",!0);this.access=getStringOption(e.access,["open","nonInteractive","protected","readOnly"]);this.accessKey=e.accessKey||"";this.anchorType=getStringOption(e.anchorType,["topLeft","bottomCenter","bottomLeft","bottomRight","middleCenter","middleLeft","middleRight","topCenter","topRight"]);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.layout=getStringOption(e.layout,["position","lr-tb","rl-row","rl-tb","row","table","tb"]);this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.bind=null;this.border=null;this.calculate=null;this.caption=null;this.desc=null;this.extras=null;this.margin=null;this.para=null;this.traversal=null;this.validate=null;this.connect=new XFAObjectArray;this.event=new XFAObjectArray;this.field=new XFAObjectArray;this.setProperty=new XFAObjectArray}[Ds](){return!0}[Fs](){return!0}[ao](e){for(const t of this.field.children){if(!t.value){const e=new Value({});t[Yn](e);t.value=e}t.value[ao](e)}}[Ls](){return this.layout.endsWith("-tb")&&0===this[os].attempt&&this[os].numberInLine>0||this[ks]()[Ls]()}[Es](){const e=this[As]();if(!e[Es]())return!1;if(void 0!==this[os]._isSplittable)return this[os]._isSplittable;if("position"===this.layout||this.layout.includes("row")){this[os]._isSplittable=!1;return!1}if(e.layout?.endsWith("-tb")&&0!==e[os].numberInLine)return!1;this[os]._isSplittable=!0;return!0}[ls](){return flushHTML(this)}[Jn](e,t){addHTML(this,e,t)}[ds](){return getAvailableSpace(this)}[so](e){setTabIndex(this);if("hidden"===this.presence||"inactive"===this.presence||0===this.h||0===this.w)return HTMLResult.EMPTY;fixDimensions(this);const t=[],a={id:this[lo],class:[]};setAccess(this,a.class);this[os]||=Object.create(null);Object.assign(this[os],{children:t,attributes:a,attempt:0,line:null,numberInLine:0,availableSpace:{width:Math.min(this.w||1/0,e.width),height:Math.min(this.h||1/0,e.height)},width:0,height:0,prevHeight:0,currentWidth:0});const r=this[Es]();r||setFirstUnsplittable(this);if(!checkDimensions(this,e))return HTMLResult.FAILURE;const i=new Set(["field"]);if(this.layout.includes("row")){const e=this[As]().columnWidths;if(Array.isArray(e)&&e.length>0){this[os].columnWidths=e;this[os].currentColumn=0}}const n=toStyle(this,"anchorType","dimensions","position","presence","border","margin","hAlign"),s=["xfaExclgroup"],o=layoutClass(this);o&&s.push(o);isPrintOnly(this)&&s.push("xfaPrintOnly");a.style=n;a.class=s;this.name&&(a.xfaName=this.name);this[Ks]();const c="lr-tb"===this.layout||"rl-tb"===this.layout,l=c?2:1;for(;this[os].attempte>=1||-1===e});this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.locale=e.locale||"";this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.rotate=getInteger({data:e.rotate,defaultValue:0,validate:e=>e%90==0});this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.bind=null;this.border=null;this.calculate=null;this.caption=null;this.desc=null;this.extras=null;this.font=null;this.format=null;this.items=new XFAObjectArray(2);this.keep=null;this.margin=null;this.para=null;this.traversal=null;this.ui=null;this.validate=null;this.value=null;this.bindItems=new XFAObjectArray;this.connect=new XFAObjectArray;this.event=new XFAObjectArray;this.setProperty=new XFAObjectArray}[Ds](){return!0}[ao](e){_setValue(this,e)}[so](e){setTabIndex(this);if(!this.ui){this.ui=new Ui({});this.ui[vs]=this[vs];this[Yn](this.ui);let e;switch(this.items.children.length){case 0:e=new TextEdit({});this.ui.textEdit=e;break;case 1:e=new CheckButton({});this.ui.checkButton=e;break;case 2:e=new ChoiceList({});this.ui.choiceList=e}this.ui[Yn](e)}if(!this.ui||"hidden"===this.presence||"inactive"===this.presence||0===this.h||0===this.w)return HTMLResult.EMPTY;this.caption&&delete this.caption[os];this[Ks]();const t=this.caption?this.caption[so](e).html:null,a=this.w,r=this.h;let i=0,n=0;if(this.margin){i=this.margin.leftInset+this.margin.rightInset;n=this.margin.topInset+this.margin.bottomInset}let s=null;if(""===this.w||""===this.h){let t=null,a=null,r=0,o=0;if(this.ui.checkButton)r=o=this.ui.checkButton.size;else{const{w:t,h:a}=layoutNode(this,e);if(null!==t){r=t;o=a}else o=function fonts_getMetrics(e,t=!1){let a=null;if(e){const t=stripQuotes(e.typeface),r=e[vs].fontFinder.find(t);a=selectFont(e,r)}if(!a)return{lineHeight:12,lineGap:2,lineNoGap:10};const r=e.size||10,i=a.lineHeight?Math.max(t?0:1.2,a.lineHeight):1.2,n=void 0===a.lineGap?.2:a.lineGap;return{lineHeight:i*r,lineGap:n*r,lineNoGap:Math.max(1,i-n)*r}}(this.font,!0).lineNoGap}s=getBorderDims(this.ui[bs]());r+=s.w;o+=s.h;if(this.caption){const{w:i,h:n,isBroken:s}=this.caption[bs](e);if(s&&this[As]()[Ls]()){this[Vs]();return HTMLResult.FAILURE}t=i;a=n;switch(this.caption.placement){case"left":case"right":case"inline":t+=r;break;case"top":case"bottom":a+=o}}else{t=r;a=o}if(t&&""===this.w){t+=i;this.w=Math.min(this.maxW<=0?1/0:this.maxW,this.minW+1e>=1&&e<=5});this.appearanceFilter=null;this.certificates=null;this.digestMethods=null;this.encodings=null;this.encryptionMethods=null;this.handler=null;this.lockDocument=null;this.mdp=null;this.reasons=null;this.timeStamp=null}}class Float extends ContentObject{constructor(e){super(zo,"float");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){const e=parseFloat(this[is].trim());this[is]=isNaN(e)?null:e}[so](e){return valueToHtml(null!==this[is]?this[is].toString():"")}}class template_Font extends XFAObject{constructor(e){super(zo,"font",!0);this.baselineShift=getMeasurement(e.baselineShift);this.fontHorizontalScale=getFloat({data:e.fontHorizontalScale,defaultValue:100,validate:e=>e>=0});this.fontVerticalScale=getFloat({data:e.fontVerticalScale,defaultValue:100,validate:e=>e>=0});this.id=e.id||"";this.kerningMode=getStringOption(e.kerningMode,["none","pair"]);this.letterSpacing=getMeasurement(e.letterSpacing,"0");this.lineThrough=getInteger({data:e.lineThrough,defaultValue:0,validate:e=>1===e||2===e});this.lineThroughPeriod=getStringOption(e.lineThroughPeriod,["all","word"]);this.overline=getInteger({data:e.overline,defaultValue:0,validate:e=>1===e||2===e});this.overlinePeriod=getStringOption(e.overlinePeriod,["all","word"]);this.posture=getStringOption(e.posture,["normal","italic"]);this.size=getMeasurement(e.size,"10pt");this.typeface=e.typeface||"Courier";this.underline=getInteger({data:e.underline,defaultValue:0,validate:e=>1===e||2===e});this.underlinePeriod=getStringOption(e.underlinePeriod,["all","word"]);this.use=e.use||"";this.usehref=e.usehref||"";this.weight=getStringOption(e.weight,["normal","bold"]);this.extras=null;this.fill=null}[Qn](e){super[Qn](e);this[vs].usedTypefaces.add(this.typeface)}[co](){const e=toStyle(this,"fill"),t=e.color;if(t)if("#000000"===t)delete e.color;else if(!t.startsWith("#")){e.background=t;e.backgroundClip="text";e.color="transparent"}this.baselineShift&&(e.verticalAlign=measureToString(this.baselineShift));e.fontKerning="none"===this.kerningMode?"none":"normal";e.letterSpacing=measureToString(this.letterSpacing);if(0!==this.lineThrough){e.textDecoration="line-through";2===this.lineThrough&&(e.textDecorationStyle="double")}if(0!==this.overline){e.textDecoration="overline";2===this.overline&&(e.textDecorationStyle="double")}e.fontStyle=this.posture;e.fontSize=measureToString(.99*this.size);setFontFamily(this,this,this[vs].fontFinder,e);if(0!==this.underline){e.textDecoration="underline";2===this.underline&&(e.textDecorationStyle="double")}e.fontWeight=this.weight;return e}}class Format extends XFAObject{constructor(e){super(zo,"format",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.picture=null}}class Handler extends StringObject{constructor(e){super(zo,"handler");this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Hyphenation extends XFAObject{constructor(e){super(zo,"hyphenation");this.excludeAllCaps=getInteger({data:e.excludeAllCaps,defaultValue:0,validate:e=>1===e});this.excludeInitialCap=getInteger({data:e.excludeInitialCap,defaultValue:0,validate:e=>1===e});this.hyphenate=getInteger({data:e.hyphenate,defaultValue:0,validate:e=>1===e});this.id=e.id||"";this.pushCharacterCount=getInteger({data:e.pushCharacterCount,defaultValue:3,validate:e=>e>=0});this.remainCharacterCount=getInteger({data:e.remainCharacterCount,defaultValue:3,validate:e=>e>=0});this.use=e.use||"";this.usehref=e.usehref||"";this.wordCharacterCount=getInteger({data:e.wordCharacterCount,defaultValue:7,validate:e=>e>=0})}}class Image extends StringObject{constructor(e){super(zo,"image");this.aspect=getStringOption(e.aspect,["fit","actual","height","none","width"]);this.contentType=e.contentType||"";this.href=e.href||"";this.id=e.id||"";this.name=e.name||"";this.transferEncoding=getStringOption(e.transferEncoding,["base64","none","package"]);this.use=e.use||"";this.usehref=e.usehref||""}[so](){if(this.contentType&&!Vo.has(this.contentType.toLowerCase()))return HTMLResult.EMPTY;let e=this[vs].images?.get(this.href);if(!e&&(this.href||!this[is]))return HTMLResult.EMPTY;e||"base64"!==this.transferEncoding||(e=function fromBase64Util(e){return Uint8Array.fromBase64?Uint8Array.fromBase64(e):stringToBytes(atob(e))}(this[is]));if(!e)return HTMLResult.EMPTY;if(!this.contentType){for(const[t,a]of Ko)if(e.length>t.length&&t.every(((t,a)=>t===e[a]))){this.contentType=a;break}if(!this.contentType)return HTMLResult.EMPTY}const t=new Blob([e],{type:this.contentType});let a;switch(this.aspect){case"fit":case"actual":break;case"height":a={height:"100%",objectFit:"fill"};break;case"none":a={width:"100%",height:"100%",objectFit:"fill"};break;case"width":a={width:"100%",objectFit:"fill"}}const r=this[ks]();return HTMLResult.success({name:"img",attributes:{class:["xfaImage"],style:a,src:URL.createObjectURL(t),alt:r?ariaLabel(r[ks]()):null}})}}class ImageEdit extends XFAObject{constructor(e){super(zo,"imageEdit",!0);this.data=getStringOption(e.data,["link","embed"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}[so](e){return"embed"===this.data?HTMLResult.success({name:"div",children:[],attributes:{}}):HTMLResult.EMPTY}}class Integer extends ContentObject{constructor(e){super(zo,"integer");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){const e=parseInt(this[is].trim(),10);this[is]=isNaN(e)?null:e}[so](e){return valueToHtml(null!==this[is]?this[is].toString():"")}}class Issuers extends XFAObject{constructor(e){super(zo,"issuers",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=new XFAObjectArray}}class Items extends XFAObject{constructor(e){super(zo,"items",!0);this.id=e.id||"";this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.ref=e.ref||"";this.save=getInteger({data:e.save,defaultValue:0,validate:e=>1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}[so](){const e=[];for(const t of this[ws]())e.push(t[io]());return HTMLResult.success(e)}}class Keep extends XFAObject{constructor(e){super(zo,"keep",!0);this.id=e.id||"";const t=["none","contentArea","pageArea"];this.intact=getStringOption(e.intact,t);this.next=getStringOption(e.next,t);this.previous=getStringOption(e.previous,t);this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}}class KeyUsage extends XFAObject{constructor(e){super(zo,"keyUsage");const t=["","yes","no"];this.crlSign=getStringOption(e.crlSign,t);this.dataEncipherment=getStringOption(e.dataEncipherment,t);this.decipherOnly=getStringOption(e.decipherOnly,t);this.digitalSignature=getStringOption(e.digitalSignature,t);this.encipherOnly=getStringOption(e.encipherOnly,t);this.id=e.id||"";this.keyAgreement=getStringOption(e.keyAgreement,t);this.keyCertSign=getStringOption(e.keyCertSign,t);this.keyEncipherment=getStringOption(e.keyEncipherment,t);this.nonRepudiation=getStringOption(e.nonRepudiation,t);this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Line extends XFAObject{constructor(e){super(zo,"line",!0);this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.slope=getStringOption(e.slope,["\\","/"]);this.use=e.use||"";this.usehref=e.usehref||"";this.edge=null}[so](){const e=this[ks]()[ks](),t=this.edge||new Edge({}),a=t[co](),r=Object.create(null),i="visible"===t.presence?t.thickness:0;r.strokeWidth=measureToString(i);r.stroke=a.color;let n,s,o,c,l="100%",h="100%";if(e.w<=i){[n,s,o,c]=["50%",0,"50%","100%"];l=r.strokeWidth}else if(e.h<=i){[n,s,o,c]=[0,"50%","100%","50%"];h=r.strokeWidth}else"\\"===this.slope?[n,s,o,c]=[0,0,"100%","100%"]:[n,s,o,c]=[0,"100%","100%",0];const u={name:"svg",children:[{name:"line",attributes:{xmlns:$o,x1:n,y1:s,x2:o,y2:c,style:r}}],attributes:{xmlns:$o,width:l,height:h,style:{overflow:"visible"}}};if(hasMargin(e))return HTMLResult.success({name:"div",attributes:{style:{display:"inline",width:"100%",height:"100%"}},children:[u]});u.attributes.style.position="absolute";return HTMLResult.success(u)}}class Linear extends XFAObject{constructor(e){super(zo,"linear",!0);this.id=e.id||"";this.type=getStringOption(e.type,["toRight","toBottom","toLeft","toTop"]);this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[co](e){e=e?e[co]():"#FFFFFF";return`linear-gradient(${this.type.replace(/([RBLT])/," $1").toLowerCase()}, ${e}, ${this.color?this.color[co]():"#000000"})`}}class LockDocument extends ContentObject{constructor(e){super(zo,"lockDocument");this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}[cs](){this[is]=getStringOption(this[is],["auto","0","1"])}}class Manifest extends XFAObject{constructor(e){super(zo,"manifest",!0);this.action=getStringOption(e.action,["include","all","exclude"]);this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.ref=new XFAObjectArray}}class Margin extends XFAObject{constructor(e){super(zo,"margin",!0);this.bottomInset=getMeasurement(e.bottomInset,"0");this.id=e.id||"";this.leftInset=getMeasurement(e.leftInset,"0");this.rightInset=getMeasurement(e.rightInset,"0");this.topInset=getMeasurement(e.topInset,"0");this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[co](){return{margin:measureToString(this.topInset)+" "+measureToString(this.rightInset)+" "+measureToString(this.bottomInset)+" "+measureToString(this.leftInset)}}}class Mdp extends XFAObject{constructor(e){super(zo,"mdp");this.id=e.id||"";this.permissions=getInteger({data:e.permissions,defaultValue:2,validate:e=>1===e||3===e});this.signatureType=getStringOption(e.signatureType,["filler","author"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Medium extends XFAObject{constructor(e){super(zo,"medium");this.id=e.id||"";this.imagingBBox=function getBBox(e){const t=-1;if(!e)return{x:t,y:t,width:t,height:t};const a=e.split(",",4).map((e=>getMeasurement(e.trim(),"-1")));if(a.length<4||a[2]<0||a[3]<0)return{x:t,y:t,width:t,height:t};const[r,i,n,s]=a;return{x:r,y:i,width:n,height:s}}(e.imagingBBox);this.long=getMeasurement(e.long);this.orientation=getStringOption(e.orientation,["portrait","landscape"]);this.short=getMeasurement(e.short);this.stock=e.stock||"";this.trayIn=getStringOption(e.trayIn,["auto","delegate","pageFront"]);this.trayOut=getStringOption(e.trayOut,["auto","delegate"]);this.use=e.use||"";this.usehref=e.usehref||""}}class Message extends XFAObject{constructor(e){super(zo,"message",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.text=new XFAObjectArray}}class NumericEdit extends XFAObject{constructor(e){super(zo,"numericEdit",!0);this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.comb=null;this.extras=null;this.margin=null}[so](e){const t=toStyle(this,"border","font","margin"),a=this[ks]()[ks](),r={name:"input",attributes:{type:"text",fieldId:a[lo],dataId:a[ns]?.[lo]||a[lo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(a),"aria-required":!1}};if(isRequired(a)){r.attributes["aria-required"]=!0;r.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[r]})}}class Occur extends XFAObject{constructor(e){super(zo,"occur",!0);this.id=e.id||"";this.initial=""!==e.initial?getInteger({data:e.initial,defaultValue:"",validate:e=>!0}):"";this.max=""!==e.max?getInteger({data:e.max,defaultValue:1,validate:e=>!0}):"";this.min=""!==e.min?getInteger({data:e.min,defaultValue:1,validate:e=>!0}):"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[Qn](){const e=this[ks](),t=this.min;""===this.min&&(this.min=e instanceof PageArea||e instanceof PageSet?0:1);""===this.max&&(this.max=""===t?e instanceof PageArea||e instanceof PageSet?-1:1:this.min);-1!==this.max&&this.max!0});this.name=e.name||"";this.numbered=getInteger({data:e.numbered,defaultValue:1,validate:e=>!0});this.oddOrEven=getStringOption(e.oddOrEven,["any","even","odd"]);this.pagePosition=getStringOption(e.pagePosition,["any","first","last","only","rest"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.desc=null;this.extras=null;this.medium=null;this.occur=null;this.area=new XFAObjectArray;this.contentArea=new XFAObjectArray;this.draw=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.field=new XFAObjectArray;this.subform=new XFAObjectArray}[_s](){if(!this[os]){this[os]={numberOfUse:0};return!0}return!this.occur||-1===this.occur.max||this[os].numberOfUsee.oddOrEven===t&&e.pagePosition===a));if(r)return r;r=this.pageArea.children.find((e=>"any"===e.oddOrEven&&e.pagePosition===a));if(r)return r;r=this.pageArea.children.find((e=>"any"===e.oddOrEven&&"any"===e.pagePosition));return r||this.pageArea.children[0]}}class Para extends XFAObject{constructor(e){super(zo,"para",!0);this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.lineHeight=e.lineHeight?getMeasurement(e.lineHeight,"0pt"):"";this.marginLeft=e.marginLeft?getMeasurement(e.marginLeft,"0pt"):"";this.marginRight=e.marginRight?getMeasurement(e.marginRight,"0pt"):"";this.orphans=getInteger({data:e.orphans,defaultValue:0,validate:e=>e>=0});this.preserve=e.preserve||"";this.radixOffset=e.radixOffset?getMeasurement(e.radixOffset,"0pt"):"";this.spaceAbove=e.spaceAbove?getMeasurement(e.spaceAbove,"0pt"):"";this.spaceBelow=e.spaceBelow?getMeasurement(e.spaceBelow,"0pt"):"";this.tabDefault=e.tabDefault?getMeasurement(this.tabDefault):"";this.tabStops=(e.tabStops||"").trim().split(/\s+/).map(((e,t)=>t%2==1?getMeasurement(e):e));this.textIndent=e.textIndent?getMeasurement(e.textIndent,"0pt"):"";this.use=e.use||"";this.usehref=e.usehref||"";this.vAlign=getStringOption(e.vAlign,["top","bottom","middle"]);this.widows=getInteger({data:e.widows,defaultValue:0,validate:e=>e>=0});this.hyphenation=null}[co](){const e=toStyle(this,"hAlign");""!==this.marginLeft&&(e.paddingLeft=measureToString(this.marginLeft));""!==this.marginRight&&(e.paddingRight=measureToString(this.marginRight));""!==this.spaceAbove&&(e.paddingTop=measureToString(this.spaceAbove));""!==this.spaceBelow&&(e.paddingBottom=measureToString(this.spaceBelow));if(""!==this.textIndent){e.textIndent=measureToString(this.textIndent);fixTextIndent(e)}this.lineHeight>0&&(e.lineHeight=measureToString(this.lineHeight));""!==this.tabDefault&&(e.tabSize=measureToString(this.tabDefault));this.tabStops.length;this.hyphenatation&&Object.assign(e,this.hyphenatation[co]());return e}}class PasswordEdit extends XFAObject{constructor(e){super(zo,"passwordEdit",!0);this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.passwordChar=e.passwordChar||"*";this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.margin=null}}class template_Pattern extends XFAObject{constructor(e){super(zo,"pattern",!0);this.id=e.id||"";this.type=getStringOption(e.type,["crossHatch","crossDiagonal","diagonalLeft","diagonalRight","horizontal","vertical"]);this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[co](e){e=e?e[co]():"#FFFFFF";const t=this.color?this.color[co]():"#000000",a="repeating-linear-gradient",r=`${e},${e} 5px,${t} 5px,${t} 10px`;switch(this.type){case"crossHatch":return`${a}(to top,${r}) ${a}(to right,${r})`;case"crossDiagonal":return`${a}(45deg,${r}) ${a}(-45deg,${r})`;case"diagonalLeft":return`${a}(45deg,${r})`;case"diagonalRight":return`${a}(-45deg,${r})`;case"horizontal":return`${a}(to top,${r})`;case"vertical":return`${a}(to right,${r})`}return""}}class Picture extends StringObject{constructor(e){super(zo,"picture");this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Proto extends XFAObject{constructor(e){super(zo,"proto",!0);this.appearanceFilter=new XFAObjectArray;this.arc=new XFAObjectArray;this.area=new XFAObjectArray;this.assist=new XFAObjectArray;this.barcode=new XFAObjectArray;this.bindItems=new XFAObjectArray;this.bookend=new XFAObjectArray;this.boolean=new XFAObjectArray;this.border=new XFAObjectArray;this.break=new XFAObjectArray;this.breakAfter=new XFAObjectArray;this.breakBefore=new XFAObjectArray;this.button=new XFAObjectArray;this.calculate=new XFAObjectArray;this.caption=new XFAObjectArray;this.certificate=new XFAObjectArray;this.certificates=new XFAObjectArray;this.checkButton=new XFAObjectArray;this.choiceList=new XFAObjectArray;this.color=new XFAObjectArray;this.comb=new XFAObjectArray;this.connect=new XFAObjectArray;this.contentArea=new XFAObjectArray;this.corner=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.dateTimeEdit=new XFAObjectArray;this.decimal=new XFAObjectArray;this.defaultUi=new XFAObjectArray;this.desc=new XFAObjectArray;this.digestMethod=new XFAObjectArray;this.digestMethods=new XFAObjectArray;this.draw=new XFAObjectArray;this.edge=new XFAObjectArray;this.encoding=new XFAObjectArray;this.encodings=new XFAObjectArray;this.encrypt=new XFAObjectArray;this.encryptData=new XFAObjectArray;this.encryption=new XFAObjectArray;this.encryptionMethod=new XFAObjectArray;this.encryptionMethods=new XFAObjectArray;this.event=new XFAObjectArray;this.exData=new XFAObjectArray;this.exObject=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.execute=new XFAObjectArray;this.extras=new XFAObjectArray;this.field=new XFAObjectArray;this.fill=new XFAObjectArray;this.filter=new XFAObjectArray;this.float=new XFAObjectArray;this.font=new XFAObjectArray;this.format=new XFAObjectArray;this.handler=new XFAObjectArray;this.hyphenation=new XFAObjectArray;this.image=new XFAObjectArray;this.imageEdit=new XFAObjectArray;this.integer=new XFAObjectArray;this.issuers=new XFAObjectArray;this.items=new XFAObjectArray;this.keep=new XFAObjectArray;this.keyUsage=new XFAObjectArray;this.line=new XFAObjectArray;this.linear=new XFAObjectArray;this.lockDocument=new XFAObjectArray;this.manifest=new XFAObjectArray;this.margin=new XFAObjectArray;this.mdp=new XFAObjectArray;this.medium=new XFAObjectArray;this.message=new XFAObjectArray;this.numericEdit=new XFAObjectArray;this.occur=new XFAObjectArray;this.oid=new XFAObjectArray;this.oids=new XFAObjectArray;this.overflow=new XFAObjectArray;this.pageArea=new XFAObjectArray;this.pageSet=new XFAObjectArray;this.para=new XFAObjectArray;this.passwordEdit=new XFAObjectArray;this.pattern=new XFAObjectArray;this.picture=new XFAObjectArray;this.radial=new XFAObjectArray;this.reason=new XFAObjectArray;this.reasons=new XFAObjectArray;this.rectangle=new XFAObjectArray;this.ref=new XFAObjectArray;this.script=new XFAObjectArray;this.setProperty=new XFAObjectArray;this.signData=new XFAObjectArray;this.signature=new XFAObjectArray;this.signing=new XFAObjectArray;this.solid=new XFAObjectArray;this.speak=new XFAObjectArray;this.stipple=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray;this.subjectDN=new XFAObjectArray;this.subjectDNs=new XFAObjectArray;this.submit=new XFAObjectArray;this.text=new XFAObjectArray;this.textEdit=new XFAObjectArray;this.time=new XFAObjectArray;this.timeStamp=new XFAObjectArray;this.toolTip=new XFAObjectArray;this.traversal=new XFAObjectArray;this.traverse=new XFAObjectArray;this.ui=new XFAObjectArray;this.validate=new XFAObjectArray;this.value=new XFAObjectArray;this.variables=new XFAObjectArray}}class Radial extends XFAObject{constructor(e){super(zo,"radial",!0);this.id=e.id||"";this.type=getStringOption(e.type,["toEdge","toCenter"]);this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[co](e){e=e?e[co]():"#FFFFFF";const t=this.color?this.color[co]():"#000000";return`radial-gradient(circle at center, ${"toEdge"===this.type?`${e},${t}`:`${t},${e}`})`}}class Reason extends StringObject{constructor(e){super(zo,"reason");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Reasons extends XFAObject{constructor(e){super(zo,"reasons",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.reason=new XFAObjectArray}}class Rectangle extends XFAObject{constructor(e){super(zo,"rectangle",!0);this.hand=getStringOption(e.hand,["even","left","right"]);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.corner=new XFAObjectArray(4);this.edge=new XFAObjectArray(4);this.fill=null}[so](){const e=this.edge.children.length?this.edge.children[0]:new Edge({}),t=e[co](),a=Object.create(null);"visible"===this.fill?.presence?Object.assign(a,this.fill[co]()):a.fill="transparent";a.strokeWidth=measureToString("visible"===e.presence?e.thickness:0);a.stroke=t.color;const r=(this.corner.children.length?this.corner.children[0]:new Corner({}))[co](),i={name:"svg",children:[{name:"rect",attributes:{xmlns:$o,width:"100%",height:"100%",x:0,y:0,rx:r.radius,ry:r.radius,style:a}}],attributes:{xmlns:$o,style:{overflow:"visible"},width:"100%",height:"100%"}};if(hasMargin(this[ks]()[ks]()))return HTMLResult.success({name:"div",attributes:{style:{display:"inline",width:"100%",height:"100%"}},children:[i]});i.attributes.style.position="absolute";return HTMLResult.success(i)}}class RefElement extends StringObject{constructor(e){super(zo,"ref");this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Script extends StringObject{constructor(e){super(zo,"script");this.binding=e.binding||"";this.contentType=e.contentType||"";this.id=e.id||"";this.name=e.name||"";this.runAt=getStringOption(e.runAt,["client","both","server"]);this.use=e.use||"";this.usehref=e.usehref||""}}class SetProperty extends XFAObject{constructor(e){super(zo,"setProperty");this.connection=e.connection||"";this.ref=e.ref||"";this.target=e.target||""}}class SignData extends XFAObject{constructor(e){super(zo,"signData",!0);this.id=e.id||"";this.operation=getStringOption(e.operation,["sign","clear","verify"]);this.ref=e.ref||"";this.target=e.target||"";this.use=e.use||"";this.usehref=e.usehref||"";this.filter=null;this.manifest=null}}class Signature extends XFAObject{constructor(e){super(zo,"signature",!0);this.id=e.id||"";this.type=getStringOption(e.type,["PDF1.3","PDF1.6"]);this.use=e.use||"";this.usehref=e.usehref||"";this.border=null;this.extras=null;this.filter=null;this.manifest=null;this.margin=null}}class Signing extends XFAObject{constructor(e){super(zo,"signing",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.certificate=new XFAObjectArray}}class Solid extends XFAObject{constructor(e){super(zo,"solid",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null}[co](e){return e?e[co]():"#FFFFFF"}}class Speak extends StringObject{constructor(e){super(zo,"speak");this.disable=getInteger({data:e.disable,defaultValue:0,validate:e=>1===e});this.id=e.id||"";this.priority=getStringOption(e.priority,["custom","caption","name","toolTip"]);this.rid=e.rid||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Stipple extends XFAObject{constructor(e){super(zo,"stipple",!0);this.id=e.id||"";this.rate=getInteger({data:e.rate,defaultValue:50,validate:e=>e>=0&&e<=100});this.use=e.use||"";this.usehref=e.usehref||"";this.color=null;this.extras=null}[co](e){const t=this.rate/100;return Util.makeHexColor(Math.round(e.value.r*(1-t)+this.value.r*t),Math.round(e.value.g*(1-t)+this.value.g*t),Math.round(e.value.b*(1-t)+this.value.b*t))}}class Subform extends XFAObject{constructor(e){super(zo,"subform",!0);this.access=getStringOption(e.access,["open","nonInteractive","protected","readOnly"]);this.allowMacro=getInteger({data:e.allowMacro,defaultValue:0,validate:e=>1===e});this.anchorType=getStringOption(e.anchorType,["topLeft","bottomCenter","bottomLeft","bottomRight","middleCenter","middleLeft","middleRight","topCenter","topRight"]);this.colSpan=getInteger({data:e.colSpan,defaultValue:1,validate:e=>e>=1||-1===e});this.columnWidths=(e.columnWidths||"").trim().split(/\s+/).map((e=>"-1"===e?-1:getMeasurement(e)));this.h=e.h?getMeasurement(e.h):"";this.hAlign=getStringOption(e.hAlign,["left","center","justify","justifyAll","radix","right"]);this.id=e.id||"";this.layout=getStringOption(e.layout,["position","lr-tb","rl-row","rl-tb","row","table","tb"]);this.locale=e.locale||"";this.maxH=getMeasurement(e.maxH,"0pt");this.maxW=getMeasurement(e.maxW,"0pt");this.mergeMode=getStringOption(e.mergeMode,["consumeData","matchTemplate"]);this.minH=getMeasurement(e.minH,"0pt");this.minW=getMeasurement(e.minW,"0pt");this.name=e.name||"";this.presence=getStringOption(e.presence,["visible","hidden","inactive","invisible"]);this.relevant=getRelevant(e.relevant);this.restoreState=getStringOption(e.restoreState,["manual","auto"]);this.scope=getStringOption(e.scope,["name","none"]);this.use=e.use||"";this.usehref=e.usehref||"";this.w=e.w?getMeasurement(e.w):"";this.x=getMeasurement(e.x,"0pt");this.y=getMeasurement(e.y,"0pt");this.assist=null;this.bind=null;this.bookend=null;this.border=null;this.break=null;this.calculate=null;this.desc=null;this.extras=null;this.keep=null;this.margin=null;this.occur=null;this.overflow=null;this.pageSet=null;this.para=null;this.traversal=null;this.validate=null;this.variables=null;this.area=new XFAObjectArray;this.breakAfter=new XFAObjectArray;this.breakBefore=new XFAObjectArray;this.connect=new XFAObjectArray;this.draw=new XFAObjectArray;this.event=new XFAObjectArray;this.exObject=new XFAObjectArray;this.exclGroup=new XFAObjectArray;this.field=new XFAObjectArray;this.proto=new XFAObjectArray;this.setProperty=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray}[As](){const e=this[ks]();return e instanceof SubformSet?e[As]():e}[Ds](){return!0}[Ls](){return this.layout.endsWith("-tb")&&0===this[os].attempt&&this[os].numberInLine>0||this[ks]()[Ls]()}*[Ss](){yield*getContainedChildren(this)}[ls](){return flushHTML(this)}[Jn](e,t){addHTML(this,e,t)}[ds](){return getAvailableSpace(this)}[Es](){const e=this[As]();if(!e[Es]())return!1;if(void 0!==this[os]._isSplittable)return this[os]._isSplittable;if("position"===this.layout||this.layout.includes("row")){this[os]._isSplittable=!1;return!1}if(this.keep&&"none"!==this.keep.intact){this[os]._isSplittable=!1;return!1}if(e.layout?.endsWith("-tb")&&0!==e[os].numberInLine)return!1;this[os]._isSplittable=!0;return!0}[so](e){setTabIndex(this);if(this.break){if("auto"!==this.break.after||""!==this.break.afterTarget){const e=new BreakAfter({targetType:this.break.after,target:this.break.afterTarget,startNew:this.break.startNew.toString()});e[vs]=this[vs];this[Yn](e);this.breakAfter.push(e)}if("auto"!==this.break.before||""!==this.break.beforeTarget){const e=new BreakBefore({targetType:this.break.before,target:this.break.beforeTarget,startNew:this.break.startNew.toString()});e[vs]=this[vs];this[Yn](e);this.breakBefore.push(e)}if(""!==this.break.overflowTarget){const e=new Overflow({target:this.break.overflowTarget,leader:this.break.overflowLeader,trailer:this.break.overflowTrailer});e[vs]=this[vs];this[Yn](e);this.overflow.push(e)}this[Js](this.break);this.break=null}if("hidden"===this.presence||"inactive"===this.presence)return HTMLResult.EMPTY;(this.breakBefore.children.length>1||this.breakAfter.children.length>1)&&warn("XFA - Several breakBefore or breakAfter in subforms: please file a bug.");if(this.breakBefore.children.length>=1){const e=this.breakBefore.children[0];if(handleBreak(e))return HTMLResult.breakNode(e)}if(this[os]?.afterBreakAfter)return HTMLResult.EMPTY;fixDimensions(this);const t=[],a={id:this[lo],class:[]};setAccess(this,a.class);this[os]||=Object.create(null);Object.assign(this[os],{children:t,line:null,attributes:a,attempt:0,numberInLine:0,availableSpace:{width:Math.min(this.w||1/0,e.width),height:Math.min(this.h||1/0,e.height)},width:0,height:0,prevHeight:0,currentWidth:0});const r=this[Cs](),i=r[os].noLayoutFailure,n=this[Es]();n||setFirstUnsplittable(this);if(!checkDimensions(this,e))return HTMLResult.FAILURE;const s=new Set(["area","draw","exclGroup","field","subform","subformSet"]);if(this.layout.includes("row")){const e=this[As]().columnWidths;if(Array.isArray(e)&&e.length>0){this[os].columnWidths=e;this[os].currentColumn=0}}const o=toStyle(this,"anchorType","dimensions","position","presence","border","margin","hAlign"),c=["xfaSubform"],l=layoutClass(this);l&&c.push(l);a.style=o;a.class=c;this.name&&(a.xfaName=this.name);if(this.overflow){const t=this.overflow[bs]();if(t.addLeader){t.addLeader=!1;handleOverflow(this,t.leader,e)}}this[Ks]();const h="lr-tb"===this.layout||"rl-tb"===this.layout,u=h?2:1;for(;this[os].attempt=1){const e=this.breakAfter.children[0];if(handleBreak(e)){this[os].afterBreakAfter=y;return HTMLResult.breakNode(e)}}delete this[os];return y}}class SubformSet extends XFAObject{constructor(e){super(zo,"subformSet",!0);this.id=e.id||"";this.name=e.name||"";this.relation=getStringOption(e.relation,["ordered","choice","unordered"]);this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.bookend=null;this.break=null;this.desc=null;this.extras=null;this.occur=null;this.overflow=null;this.breakAfter=new XFAObjectArray;this.breakBefore=new XFAObjectArray;this.subform=new XFAObjectArray;this.subformSet=new XFAObjectArray}*[Ss](){yield*getContainedChildren(this)}[As](){let e=this[ks]();for(;!(e instanceof Subform);)e=e[ks]();return e}[Ds](){return!0}}class SubjectDN extends ContentObject{constructor(e){super(zo,"subjectDN");this.delimiter=e.delimiter||",";this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){this[is]=new Map(this[is].split(this.delimiter).map((e=>{(e=e.split("=",2))[0]=e[0].trim();return e})))}}class SubjectDNs extends XFAObject{constructor(e){super(zo,"subjectDNs",!0);this.id=e.id||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||"";this.subjectDN=new XFAObjectArray}}class Submit extends XFAObject{constructor(e){super(zo,"submit",!0);this.embedPDF=getInteger({data:e.embedPDF,defaultValue:0,validate:e=>1===e});this.format=getStringOption(e.format,["xdp","formdata","pdf","urlencoded","xfd","xml"]);this.id=e.id||"";this.target=e.target||"";this.textEncoding=getKeyword({data:e.textEncoding?e.textEncoding.toLowerCase():"",defaultValue:"",validate:e=>["utf-8","big-five","fontspecific","gbk","gb-18030","gb-2312","ksc-5601","none","shift-jis","ucs-2","utf-16"].includes(e)||e.match(/iso-8859-\d{2}/)});this.use=e.use||"";this.usehref=e.usehref||"";this.xdpContent=e.xdpContent||"";this.encrypt=null;this.encryptData=new XFAObjectArray;this.signData=new XFAObjectArray}}class Template extends XFAObject{constructor(e){super(zo,"template",!0);this.baseProfile=getStringOption(e.baseProfile,["full","interactiveForms"]);this.extras=null;this.subform=new XFAObjectArray}[cs](){0===this.subform.children.length&&warn("XFA - No subforms in template node.");this.subform.children.length>=2&&warn("XFA - Several subforms in template node: please file a bug.");this[ro]=5e3}[Es](){return!0}[Qs](e,t){return e.startsWith("#")?[this[Is].get(e.slice(1))]:searchNode(this,t,e,!0,!0)}*[no](){if(!this.subform.children.length)return HTMLResult.success({name:"div",children:[]});this[os]={overflowNode:null,firstUnsplittable:null,currentContentArea:null,currentPageArea:null,noLayoutFailure:!1,pageNumber:1,pagePosition:"first",oddOrEven:"odd",blankOrNotBlank:"nonBlank",paraStack:[]};const e=this.subform.children[0];e.pageSet[es]();const t=e.pageSet.pageArea.children,a={name:"div",children:[]};let r=null,i=null,n=null;if(e.breakBefore.children.length>=1){i=e.breakBefore.children[0];n=i.target}else if(e.subform.children.length>=1&&e.subform.children[0].breakBefore.children.length>=1){i=e.subform.children[0].breakBefore.children[0];n=i.target}else if(e.break?.beforeTarget){i=e.break;n=i.beforeTarget}else if(e.subform.children.length>=1&&e.subform.children[0].break?.beforeTarget){i=e.subform.children[0].break;n=i.beforeTarget}if(i){const e=this[Qs](n,i[ks]());if(e instanceof PageArea){r=e;i[os]={}}}r||=t[0];r[os]={numberOfUse:1};const s=r[ks]();s[os]={numberOfUse:1,pageIndex:s.pageArea.children.indexOf(r),pageSetIndex:0};let o,c=null,l=null,h=!0,u=0,d=0;for(;;){if(h)u=0;else{a.children.pop();if(3==++u){warn("XFA - Something goes wrong: please file a bug.");return a}}o=null;this[os].currentPageArea=r;const t=r[so]().html;a.children.push(t);if(c){this[os].noLayoutFailure=!0;t.children.push(c[so](r[os].space).html);c=null}if(l){this[os].noLayoutFailure=!0;t.children.push(l[so](r[os].space).html);l=null}const i=r.contentArea.children,n=t.children.filter((e=>e.attributes.class.includes("xfaContentarea")));h=!1;this[os].firstUnsplittable=null;this[os].noLayoutFailure=!1;const flush=t=>{const a=e[ls]();if(a){h||=a.children?.length>0;n[t].children.push(a)}};for(let t=d,r=i.length;t0;n[t].children.push(u.html)}else!h&&a.children.length>1&&a.children.pop();return a}if(u.isBreak()){const e=u.breakNode;flush(t);if("auto"===e.targetType)continue;if(e.leader){c=this[Qs](e.leader,e[ks]());c=c?c[0]:null}if(e.trailer){l=this[Qs](e.trailer,e[ks]());l=l?l[0]:null}if("pageArea"===e.targetType){o=e[os].target;t=1/0}else if(e[os].target){o=e[os].target;d=e[os].index+1;t=1/0}else t=e[os].index}else if(this[os].overflowNode){const e=this[os].overflowNode;this[os].overflowNode=null;const a=e[bs](),r=a.target;a.addLeader=null!==a.leader;a.addTrailer=null!==a.trailer;flush(t);const n=t;t=1/0;if(r instanceof PageArea)o=r;else if(r instanceof ContentArea){const e=i.indexOf(r);if(-1!==e)e>n?t=e-1:d=e;else{o=r[ks]();d=o.contentArea.children.indexOf(r)}}}else flush(t)}this[os].pageNumber+=1;o&&(o[_s]()?o[os].numberOfUse+=1:o=null);r=o||r[xs]();yield null}}}class Text extends ContentObject{constructor(e){super(zo,"text");this.id=e.id||"";this.maxChars=getInteger({data:e.maxChars,defaultValue:0,validate:e=>e>=0});this.name=e.name||"";this.rid=e.rid||"";this.use=e.use||"";this.usehref=e.usehref||""}[Kn](){return!0}[Ws](e){if(e[Xs]===uo.xhtml.id){this[is]=e;return!0}warn(`XFA - Invalid content in Text: ${e[qs]}.`);return!1}[$s](e){this[is]instanceof XFAObject||super[$s](e)}[cs](){"string"==typeof this[is]&&(this[is]=this[is].replaceAll("\r\n","\n"))}[bs](){return"string"==typeof this[is]?this[is].split(/[\u2029\u2028\n]/).filter((e=>!!e)).join("\n"):this[is][io]()}[so](e){if("string"==typeof this[is]){const e=valueToHtml(this[is]).html;if(this[is].includes("\u2029")){e.name="div";e.children=[];this[is].split("\u2029").map((e=>e.split(/[\u2028\n]/).flatMap((e=>[{name:"span",value:e},{name:"br"}])))).forEach((t=>{e.children.push({name:"p",children:t})}))}else if(/[\u2028\n]/.test(this[is])){e.name="div";e.children=[];this[is].split(/[\u2028\n]/).forEach((t=>{e.children.push({name:"span",value:t},{name:"br"})}))}return HTMLResult.success(e)}return this[is][so](e)}}class TextEdit extends XFAObject{constructor(e){super(zo,"textEdit",!0);this.allowRichText=getInteger({data:e.allowRichText,defaultValue:0,validate:e=>1===e});this.hScrollPolicy=getStringOption(e.hScrollPolicy,["auto","off","on"]);this.id=e.id||"";this.multiLine=getInteger({data:e.multiLine,defaultValue:"",validate:e=>0===e||1===e});this.use=e.use||"";this.usehref=e.usehref||"";this.vScrollPolicy=getStringOption(e.vScrollPolicy,["auto","off","on"]);this.border=null;this.comb=null;this.extras=null;this.margin=null}[so](e){const t=toStyle(this,"border","font","margin");let a;const r=this[ks]()[ks]();""===this.multiLine&&(this.multiLine=r instanceof Draw?1:0);a=1===this.multiLine?{name:"textarea",attributes:{dataId:r[ns]?.[lo]||r[lo],fieldId:r[lo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(r),"aria-required":!1}}:{name:"input",attributes:{type:"text",dataId:r[ns]?.[lo]||r[lo],fieldId:r[lo],class:["xfaTextfield"],style:t,"aria-label":ariaLabel(r),"aria-required":!1}};if(isRequired(r)){a.attributes["aria-required"]=!0;a.attributes.required=!0}return HTMLResult.success({name:"label",attributes:{class:["xfaLabel"]},children:[a]})}}class Time extends StringObject{constructor(e){super(zo,"time");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}[cs](){const e=this[is].trim();this[is]=e?new Date(e):null}[so](e){return valueToHtml(this[is]?this[is].toString():"")}}class TimeStamp extends XFAObject{constructor(e){super(zo,"timeStamp");this.id=e.id||"";this.server=e.server||"";this.type=getStringOption(e.type,["optional","required"]);this.use=e.use||"";this.usehref=e.usehref||""}}class ToolTip extends StringObject{constructor(e){super(zo,"toolTip");this.id=e.id||"";this.rid=e.rid||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Traversal extends XFAObject{constructor(e){super(zo,"traversal",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.traverse=new XFAObjectArray}}class Traverse extends XFAObject{constructor(e){super(zo,"traverse",!0);this.id=e.id||"";this.operation=getStringOption(e.operation,["next","back","down","first","left","right","up"]);this.ref=e.ref||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.script=null}get name(){return this.operation}[js](){return!1}}class Ui extends XFAObject{constructor(e){super(zo,"ui",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.picture=null;this.barcode=null;this.button=null;this.checkButton=null;this.choiceList=null;this.dateTimeEdit=null;this.defaultUi=null;this.imageEdit=null;this.numericEdit=null;this.passwordEdit=null;this.signature=null;this.textEdit=null}[bs](){if(void 0===this[os]){for(const e of Object.getOwnPropertyNames(this)){if("extras"===e||"picture"===e)continue;const t=this[e];if(t instanceof XFAObject){this[os]=t;return t}}this[os]=null}return this[os]}[so](e){const t=this[bs]();return t?t[so](e):HTMLResult.EMPTY}}class Validate extends XFAObject{constructor(e){super(zo,"validate",!0);this.formatTest=getStringOption(e.formatTest,["warning","disabled","error"]);this.id=e.id||"";this.nullTest=getStringOption(e.nullTest,["disabled","error","warning"]);this.scriptTest=getStringOption(e.scriptTest,["error","disabled","warning"]);this.use=e.use||"";this.usehref=e.usehref||"";this.extras=null;this.message=null;this.picture=null;this.script=null}}class Value extends XFAObject{constructor(e){super(zo,"value",!0);this.id=e.id||"";this.override=getInteger({data:e.override,defaultValue:0,validate:e=>1===e});this.relevant=getRelevant(e.relevant);this.use=e.use||"";this.usehref=e.usehref||"";this.arc=null;this.boolean=null;this.date=null;this.dateTime=null;this.decimal=null;this.exData=null;this.float=null;this.image=null;this.integer=null;this.line=null;this.rectangle=null;this.text=null;this.time=null}[ao](e){const t=this[ks]();if(t instanceof Field&&t.ui?.imageEdit){if(!this.image){this.image=new Image({});this[Yn](this.image)}this.image[is]=e[is];return}const a=e[qs];if(null===this[a]){for(const e of Object.getOwnPropertyNames(this)){const t=this[e];if(t instanceof XFAObject){this[e]=null;this[Js](t)}}this[e[qs]]=e;this[Yn](e)}else this[a][is]=e[is]}[io](){if(this.exData)return"string"==typeof this.exData[is]?this.exData[is].trim():this.exData[is][io]().trim();for(const e of Object.getOwnPropertyNames(this)){if("image"===e)continue;const t=this[e];if(t instanceof XFAObject)return(t[is]||"").toString().trim()}return null}[so](e){for(const t of Object.getOwnPropertyNames(this)){const a=this[t];if(a instanceof XFAObject)return a[so](e)}return HTMLResult.EMPTY}}class Variables extends XFAObject{constructor(e){super(zo,"variables",!0);this.id=e.id||"";this.use=e.use||"";this.usehref=e.usehref||"";this.boolean=new XFAObjectArray;this.date=new XFAObjectArray;this.dateTime=new XFAObjectArray;this.decimal=new XFAObjectArray;this.exData=new XFAObjectArray;this.float=new XFAObjectArray;this.image=new XFAObjectArray;this.integer=new XFAObjectArray;this.manifest=new XFAObjectArray;this.script=new XFAObjectArray;this.text=new XFAObjectArray;this.time=new XFAObjectArray}[js](){return!0}}class TemplateNamespace{static[ho](e,t){if(TemplateNamespace.hasOwnProperty(e)){const a=TemplateNamespace[e](t);a[to](t);return a}}static appearanceFilter(e){return new AppearanceFilter(e)}static arc(e){return new Arc(e)}static area(e){return new Area(e)}static assist(e){return new Assist(e)}static barcode(e){return new Barcode(e)}static bind(e){return new Bind(e)}static bindItems(e){return new BindItems(e)}static bookend(e){return new Bookend(e)}static boolean(e){return new BooleanElement(e)}static border(e){return new Border(e)}static break(e){return new Break(e)}static breakAfter(e){return new BreakAfter(e)}static breakBefore(e){return new BreakBefore(e)}static button(e){return new Button(e)}static calculate(e){return new Calculate(e)}static caption(e){return new Caption(e)}static certificate(e){return new Certificate(e)}static certificates(e){return new Certificates(e)}static checkButton(e){return new CheckButton(e)}static choiceList(e){return new ChoiceList(e)}static color(e){return new Color(e)}static comb(e){return new Comb(e)}static connect(e){return new Connect(e)}static contentArea(e){return new ContentArea(e)}static corner(e){return new Corner(e)}static date(e){return new DateElement(e)}static dateTime(e){return new DateTime(e)}static dateTimeEdit(e){return new DateTimeEdit(e)}static decimal(e){return new Decimal(e)}static defaultUi(e){return new DefaultUi(e)}static desc(e){return new Desc(e)}static digestMethod(e){return new DigestMethod(e)}static digestMethods(e){return new DigestMethods(e)}static draw(e){return new Draw(e)}static edge(e){return new Edge(e)}static encoding(e){return new Encoding(e)}static encodings(e){return new Encodings(e)}static encrypt(e){return new Encrypt(e)}static encryptData(e){return new EncryptData(e)}static encryption(e){return new Encryption(e)}static encryptionMethod(e){return new EncryptionMethod(e)}static encryptionMethods(e){return new EncryptionMethods(e)}static event(e){return new Event(e)}static exData(e){return new ExData(e)}static exObject(e){return new ExObject(e)}static exclGroup(e){return new ExclGroup(e)}static execute(e){return new Execute(e)}static extras(e){return new Extras(e)}static field(e){return new Field(e)}static fill(e){return new Fill(e)}static filter(e){return new Filter(e)}static float(e){return new Float(e)}static font(e){return new template_Font(e)}static format(e){return new Format(e)}static handler(e){return new Handler(e)}static hyphenation(e){return new Hyphenation(e)}static image(e){return new Image(e)}static imageEdit(e){return new ImageEdit(e)}static integer(e){return new Integer(e)}static issuers(e){return new Issuers(e)}static items(e){return new Items(e)}static keep(e){return new Keep(e)}static keyUsage(e){return new KeyUsage(e)}static line(e){return new Line(e)}static linear(e){return new Linear(e)}static lockDocument(e){return new LockDocument(e)}static manifest(e){return new Manifest(e)}static margin(e){return new Margin(e)}static mdp(e){return new Mdp(e)}static medium(e){return new Medium(e)}static message(e){return new Message(e)}static numericEdit(e){return new NumericEdit(e)}static occur(e){return new Occur(e)}static oid(e){return new Oid(e)}static oids(e){return new Oids(e)}static overflow(e){return new Overflow(e)}static pageArea(e){return new PageArea(e)}static pageSet(e){return new PageSet(e)}static para(e){return new Para(e)}static passwordEdit(e){return new PasswordEdit(e)}static pattern(e){return new template_Pattern(e)}static picture(e){return new Picture(e)}static proto(e){return new Proto(e)}static radial(e){return new Radial(e)}static reason(e){return new Reason(e)}static reasons(e){return new Reasons(e)}static rectangle(e){return new Rectangle(e)}static ref(e){return new RefElement(e)}static script(e){return new Script(e)}static setProperty(e){return new SetProperty(e)}static signData(e){return new SignData(e)}static signature(e){return new Signature(e)}static signing(e){return new Signing(e)}static solid(e){return new Solid(e)}static speak(e){return new Speak(e)}static stipple(e){return new Stipple(e)}static subform(e){return new Subform(e)}static subformSet(e){return new SubformSet(e)}static subjectDN(e){return new SubjectDN(e)}static subjectDNs(e){return new SubjectDNs(e)}static submit(e){return new Submit(e)}static template(e){return new Template(e)}static text(e){return new Text(e)}static textEdit(e){return new TextEdit(e)}static time(e){return new Time(e)}static timeStamp(e){return new TimeStamp(e)}static toolTip(e){return new ToolTip(e)}static traversal(e){return new Traversal(e)}static traverse(e){return new Traverse(e)}static ui(e){return new Ui(e)}static validate(e){return new Validate(e)}static value(e){return new Value(e)}static variables(e){return new Variables(e)}}const Jo=uo.datasets.id;function createText(e){const t=new Text({});t[is]=e;return t}class Binder{constructor(e){this.root=e;this.datasets=e.datasets;this.data=e.datasets?.data||new XmlObject(uo.datasets.id,"data");this.emptyMerge=0===this.data[ws]().length;this.root.form=this.form=e.template[as]()}_isConsumeData(){return!this.emptyMerge&&this._mergeMode}_isMatchTemplate(){return!this._isConsumeData()}bind(){this._bindElement(this.form,this.data);return this.form}getData(){return this.data}_bindValue(e,t,a){e[ns]=t;if(e[Fs]())if(t[Bs]()){const a=t[ms]();e[ao](createText(a))}else if(e instanceof Field&&"multiSelect"===e.ui?.choiceList?.open){const a=t[ws]().map((e=>e[is].trim())).join("\n");e[ao](createText(a))}else this._isConsumeData()&&warn("XFA - Nodes haven't the same type.");else!t[Bs]()||this._isMatchTemplate()?this._bindElement(e,t):warn("XFA - Nodes haven't the same type.")}_findDataByNameToConsume(e,t,a,r){if(!e)return null;let i,n;for(let r=0;r<3;r++){i=a[ys](e,!1,!0);for(;;){n=i.next().value;if(!n)break;if(t===n[Bs]())return n}if(a[Xs]===uo.datasets.id&&"data"===a[qs])break;a=a[ks]()}if(!r)return null;i=this.data[ys](e,!0,!1);n=i.next().value;if(n)return n;i=this.data[hs](e,!0);n=i.next().value;return n?.[Bs]()?n:null}_setProperties(e,t){if(e.hasOwnProperty("setProperty"))for(const{ref:a,target:r,connection:i}of e.setProperty.children){if(i)continue;if(!a)continue;const n=searchNode(this.root,t,a,!1,!1);if(!n){warn(`XFA - Invalid reference: ${a}.`);continue}const[s]=n;if(!s[Rs](this.data)){warn("XFA - Invalid node: must be a data node.");continue}const o=searchNode(this.root,e,r,!1,!1);if(!o){warn(`XFA - Invalid target: ${r}.`);continue}const[c]=o;if(!c[Rs](e)){warn("XFA - Invalid target: must be a property or subproperty.");continue}const l=c[ks]();if(c instanceof SetProperty||l instanceof SetProperty){warn("XFA - Invalid target: cannot be a setProperty or one of its properties.");continue}if(c instanceof BindItems||l instanceof BindItems){warn("XFA - Invalid target: cannot be a bindItems or one of its properties.");continue}const h=s[io](),u=c[qs];if(c instanceof XFAAttribute){const e=Object.create(null);e[u]=h;const t=Reflect.construct(Object.getPrototypeOf(l).constructor,[e]);l[u]=t[u]}else if(c.hasOwnProperty(is)){c[ns]=s;c[is]=h;c[cs]()}else warn("XFA - Invalid node to use in setProperty")}}_bindItems(e,t){if(!e.hasOwnProperty("items")||!e.hasOwnProperty("bindItems")||e.bindItems.isEmpty())return;for(const t of e.items.children)e[Js](t);e.items.clear();const a=new Items({}),r=new Items({});e[Yn](a);e.items.push(a);e[Yn](r);e.items.push(r);for(const{ref:i,labelRef:n,valueRef:s,connection:o}of e.bindItems.children){if(o)continue;if(!i)continue;const e=searchNode(this.root,t,i,!1,!1);if(e)for(const t of e){if(!t[Rs](this.datasets)){warn(`XFA - Invalid ref (${i}): must be a datasets child.`);continue}const e=searchNode(this.root,t,n,!0,!1);if(!e){warn(`XFA - Invalid label: ${n}.`);continue}const[o]=e;if(!o[Rs](this.datasets)){warn("XFA - Invalid label: must be a datasets child.");continue}const c=searchNode(this.root,t,s,!0,!1);if(!c){warn(`XFA - Invalid value: ${s}.`);continue}const[l]=c;if(!l[Rs](this.datasets)){warn("XFA - Invalid value: must be a datasets child.");continue}const h=createText(o[io]()),u=createText(l[io]());a[Yn](h);a.text.push(h);r[Yn](u);r.text.push(u)}else warn(`XFA - Invalid reference: ${i}.`)}}_bindOccurrences(e,t,a){let r;if(t.length>1){r=e[as]();r[Js](r.occur);r.occur=null}this._bindValue(e,t[0],a);this._setProperties(e,t[0]);this._bindItems(e,t[0]);if(1===t.length)return;const i=e[ks](),n=e[qs],s=i[Os](e);for(let e=1,o=t.length;et.name===e.name)).length:a[r].children.length;const n=a[Os](e)+1,s=t.initial-i;if(s){const t=e[as]();t[Js](t.occur);t.occur=null;a[r].push(t);a[Ts](n,t);for(let e=1;e0)this._bindOccurrences(r,[e[0]],null);else if(this.emptyMerge){const e=t[Xs]===Jo?-1:t[Xs],a=r[ns]=new XmlObject(e,r.name||"root");t[Yn](a);this._bindElement(r,a)}continue}if(!r[Ds]())continue;let e=!1,i=null,n=null,s=null;if(r.bind){switch(r.bind.match){case"none":this._setAndBind(r,t);continue;case"global":e=!0;break;case"dataRef":if(!r.bind.ref){warn(`XFA - ref is empty in node ${r[qs]}.`);this._setAndBind(r,t);continue}n=r.bind.ref}r.bind.picture&&(i=r.bind.picture[is])}const[o,c]=this._getOccurInfo(r);if(n){s=searchNode(this.root,t,n,!0,!1);if(null===s){s=createDataNode(this.data,t,n);if(!s)continue;this._isConsumeData()&&(s[rs]=!0);this._setAndBind(r,s);continue}this._isConsumeData()&&(s=s.filter((e=>!e[rs])));s.length>c?s=s.slice(0,c):0===s.length&&(s=null);s&&this._isConsumeData()&&s.forEach((e=>{e[rs]=!0}))}else{if(!r.name){this._setAndBind(r,t);continue}if(this._isConsumeData()){const a=[];for(;a.length0?a:null}else{s=t[ys](r.name,!1,this.emptyMerge).next().value;if(!s){if(0===o){a.push(r);continue}const e=t[Xs]===Jo?-1:t[Xs];s=r[ns]=new XmlObject(e,r.name);this.emptyMerge&&(s[rs]=!0);t[Yn](s);this._setAndBind(r,s);continue}this.emptyMerge&&(s[rs]=!0);s=[s]}}s?this._bindOccurrences(r,s,i):o>0?this._setAndBind(r,t):a.push(r)}a.forEach((e=>e[ks]()[Js](e)))}}class DataHandler{constructor(e,t){this.data=t;this.dataset=e.datasets||null}serialize(e){const t=[[-1,this.data[ws]()]];for(;t.length>0;){const a=t.at(-1),[r,i]=a;if(r+1===i.length){t.pop();continue}const n=i[++a[0]],s=e.get(n[lo]);if(s)n[ao](s);else{const t=n[us]();for(const a of t.values()){const t=e.get(a[lo]);if(t){a[ao](t);break}}}const o=n[ws]();o.length>0&&t.push([-1,o])}const a=[''];if(this.dataset)for(const e of this.dataset[ws]())"data"!==e[qs]&&e[oo](a);this.data[oo](a);a.push("");return a.join("")}}const Yo=uo.config.id;class Acrobat extends XFAObject{constructor(e){super(Yo,"acrobat",!0);this.acrobat7=null;this.autoSave=null;this.common=null;this.validate=null;this.validateApprovalSignatures=null;this.submitUrl=new XFAObjectArray}}class Acrobat7 extends XFAObject{constructor(e){super(Yo,"acrobat7",!0);this.dynamicRender=null}}class ADBE_JSConsole extends OptionObject{constructor(e){super(Yo,"ADBE_JSConsole",["delegate","Enable","Disable"])}}class ADBE_JSDebugger extends OptionObject{constructor(e){super(Yo,"ADBE_JSDebugger",["delegate","Enable","Disable"])}}class AddSilentPrint extends Option01{constructor(e){super(Yo,"addSilentPrint")}}class AddViewerPreferences extends Option01{constructor(e){super(Yo,"addViewerPreferences")}}class AdjustData extends Option10{constructor(e){super(Yo,"adjustData")}}class AdobeExtensionLevel extends IntegerObject{constructor(e){super(Yo,"adobeExtensionLevel",0,(e=>e>=1&&e<=8))}}class Agent extends XFAObject{constructor(e){super(Yo,"agent",!0);this.name=e.name?e.name.trim():"";this.common=new XFAObjectArray}}class AlwaysEmbed extends ContentObject{constructor(e){super(Yo,"alwaysEmbed")}}class Amd extends StringObject{constructor(e){super(Yo,"amd")}}class config_Area extends XFAObject{constructor(e){super(Yo,"area");this.level=getInteger({data:e.level,defaultValue:0,validate:e=>e>=1&&e<=3});this.name=getStringOption(e.name,["","barcode","coreinit","deviceDriver","font","general","layout","merge","script","signature","sourceSet","templateCache"])}}class Attributes extends OptionObject{constructor(e){super(Yo,"attributes",["preserve","delegate","ignore"])}}class AutoSave extends OptionObject{constructor(e){super(Yo,"autoSave",["disabled","enabled"])}}class Base extends StringObject{constructor(e){super(Yo,"base")}}class BatchOutput extends XFAObject{constructor(e){super(Yo,"batchOutput");this.format=getStringOption(e.format,["none","concat","zip","zipCompress"])}}class BehaviorOverride extends ContentObject{constructor(e){super(Yo,"behaviorOverride")}[cs](){this[is]=new Map(this[is].trim().split(/\s+/).filter((e=>e.includes(":"))).map((e=>e.split(":",2))))}}class Cache extends XFAObject{constructor(e){super(Yo,"cache",!0);this.templateCache=null}}class Change extends Option01{constructor(e){super(Yo,"change")}}class Common extends XFAObject{constructor(e){super(Yo,"common",!0);this.data=null;this.locale=null;this.localeSet=null;this.messaging=null;this.suppressBanner=null;this.template=null;this.validationMessaging=null;this.versionControl=null;this.log=new XFAObjectArray}}class Compress extends XFAObject{constructor(e){super(Yo,"compress");this.scope=getStringOption(e.scope,["imageOnly","document"])}}class CompressLogicalStructure extends Option01{constructor(e){super(Yo,"compressLogicalStructure")}}class CompressObjectStream extends Option10{constructor(e){super(Yo,"compressObjectStream")}}class Compression extends XFAObject{constructor(e){super(Yo,"compression",!0);this.compressLogicalStructure=null;this.compressObjectStream=null;this.level=null;this.type=null}}class Config extends XFAObject{constructor(e){super(Yo,"config",!0);this.acrobat=null;this.present=null;this.trace=null;this.agent=new XFAObjectArray}}class Conformance extends OptionObject{constructor(e){super(Yo,"conformance",["A","B"])}}class ContentCopy extends Option01{constructor(e){super(Yo,"contentCopy")}}class Copies extends IntegerObject{constructor(e){super(Yo,"copies",1,(e=>e>=1))}}class Creator extends StringObject{constructor(e){super(Yo,"creator")}}class CurrentPage extends IntegerObject{constructor(e){super(Yo,"currentPage",0,(e=>e>=0))}}class Data extends XFAObject{constructor(e){super(Yo,"data",!0);this.adjustData=null;this.attributes=null;this.incrementalLoad=null;this.outputXSL=null;this.range=null;this.record=null;this.startNode=null;this.uri=null;this.window=null;this.xsl=null;this.excludeNS=new XFAObjectArray;this.transform=new XFAObjectArray}}class Debug extends XFAObject{constructor(e){super(Yo,"debug",!0);this.uri=null}}class DefaultTypeface extends ContentObject{constructor(e){super(Yo,"defaultTypeface");this.writingScript=getStringOption(e.writingScript,["*","Arabic","Cyrillic","EastEuropeanRoman","Greek","Hebrew","Japanese","Korean","Roman","SimplifiedChinese","Thai","TraditionalChinese","Vietnamese"])}}class Destination extends OptionObject{constructor(e){super(Yo,"destination",["pdf","pcl","ps","webClient","zpl"])}}class DocumentAssembly extends Option01{constructor(e){super(Yo,"documentAssembly")}}class Driver extends XFAObject{constructor(e){super(Yo,"driver",!0);this.name=e.name?e.name.trim():"";this.fontInfo=null;this.xdc=null}}class DuplexOption extends OptionObject{constructor(e){super(Yo,"duplexOption",["simplex","duplexFlipLongEdge","duplexFlipShortEdge"])}}class DynamicRender extends OptionObject{constructor(e){super(Yo,"dynamicRender",["forbidden","required"])}}class Embed extends Option01{constructor(e){super(Yo,"embed")}}class config_Encrypt extends Option01{constructor(e){super(Yo,"encrypt")}}class config_Encryption extends XFAObject{constructor(e){super(Yo,"encryption",!0);this.encrypt=null;this.encryptionLevel=null;this.permissions=null}}class EncryptionLevel extends OptionObject{constructor(e){super(Yo,"encryptionLevel",["40bit","128bit"])}}class Enforce extends StringObject{constructor(e){super(Yo,"enforce")}}class Equate extends XFAObject{constructor(e){super(Yo,"equate");this.force=getInteger({data:e.force,defaultValue:1,validate:e=>0===e});this.from=e.from||"";this.to=e.to||""}}class EquateRange extends XFAObject{constructor(e){super(Yo,"equateRange");this.from=e.from||"";this.to=e.to||"";this._unicodeRange=e.unicodeRange||""}get unicodeRange(){const e=[],t=/U\+([0-9a-fA-F]+)/,a=this._unicodeRange;for(let r of a.split(",").map((e=>e.trim())).filter((e=>!!e))){r=r.split("-",2).map((e=>{const a=e.match(t);return a?parseInt(a[1],16):0}));1===r.length&&r.push(r[0]);e.push(r)}return shadow(this,"unicodeRange",e)}}class Exclude extends ContentObject{constructor(e){super(Yo,"exclude")}[cs](){this[is]=this[is].trim().split(/\s+/).filter((e=>e&&["calculate","close","enter","exit","initialize","ready","validate"].includes(e)))}}class ExcludeNS extends StringObject{constructor(e){super(Yo,"excludeNS")}}class FlipLabel extends OptionObject{constructor(e){super(Yo,"flipLabel",["usePrinterSetting","on","off"])}}class config_FontInfo extends XFAObject{constructor(e){super(Yo,"fontInfo",!0);this.embed=null;this.map=null;this.subsetBelow=null;this.alwaysEmbed=new XFAObjectArray;this.defaultTypeface=new XFAObjectArray;this.neverEmbed=new XFAObjectArray}}class FormFieldFilling extends Option01{constructor(e){super(Yo,"formFieldFilling")}}class GroupParent extends StringObject{constructor(e){super(Yo,"groupParent")}}class IfEmpty extends OptionObject{constructor(e){super(Yo,"ifEmpty",["dataValue","dataGroup","ignore","remove"])}}class IncludeXDPContent extends StringObject{constructor(e){super(Yo,"includeXDPContent")}}class IncrementalLoad extends OptionObject{constructor(e){super(Yo,"incrementalLoad",["none","forwardOnly"])}}class IncrementalMerge extends Option01{constructor(e){super(Yo,"incrementalMerge")}}class Interactive extends Option01{constructor(e){super(Yo,"interactive")}}class Jog extends OptionObject{constructor(e){super(Yo,"jog",["usePrinterSetting","none","pageSet"])}}class LabelPrinter extends XFAObject{constructor(e){super(Yo,"labelPrinter",!0);this.name=getStringOption(e.name,["zpl","dpl","ipl","tcpl"]);this.batchOutput=null;this.flipLabel=null;this.fontInfo=null;this.xdc=null}}class Layout extends OptionObject{constructor(e){super(Yo,"layout",["paginate","panel"])}}class Level extends IntegerObject{constructor(e){super(Yo,"level",0,(e=>e>0))}}class Linearized extends Option01{constructor(e){super(Yo,"linearized")}}class Locale extends StringObject{constructor(e){super(Yo,"locale")}}class LocaleSet extends StringObject{constructor(e){super(Yo,"localeSet")}}class Log extends XFAObject{constructor(e){super(Yo,"log",!0);this.mode=null;this.threshold=null;this.to=null;this.uri=null}}class MapElement extends XFAObject{constructor(e){super(Yo,"map",!0);this.equate=new XFAObjectArray;this.equateRange=new XFAObjectArray}}class MediumInfo extends XFAObject{constructor(e){super(Yo,"mediumInfo",!0);this.map=null}}class config_Message extends XFAObject{constructor(e){super(Yo,"message",!0);this.msgId=null;this.severity=null}}class Messaging extends XFAObject{constructor(e){super(Yo,"messaging",!0);this.message=new XFAObjectArray}}class Mode extends OptionObject{constructor(e){super(Yo,"mode",["append","overwrite"])}}class ModifyAnnots extends Option01{constructor(e){super(Yo,"modifyAnnots")}}class MsgId extends IntegerObject{constructor(e){super(Yo,"msgId",1,(e=>e>=1))}}class NameAttr extends StringObject{constructor(e){super(Yo,"nameAttr")}}class NeverEmbed extends ContentObject{constructor(e){super(Yo,"neverEmbed")}}class NumberOfCopies extends IntegerObject{constructor(e){super(Yo,"numberOfCopies",null,(e=>e>=2&&e<=5))}}class OpenAction extends XFAObject{constructor(e){super(Yo,"openAction",!0);this.destination=null}}class Output extends XFAObject{constructor(e){super(Yo,"output",!0);this.to=null;this.type=null;this.uri=null}}class OutputBin extends StringObject{constructor(e){super(Yo,"outputBin")}}class OutputXSL extends XFAObject{constructor(e){super(Yo,"outputXSL",!0);this.uri=null}}class Overprint extends OptionObject{constructor(e){super(Yo,"overprint",["none","both","draw","field"])}}class Packets extends StringObject{constructor(e){super(Yo,"packets")}[cs](){"*"!==this[is]&&(this[is]=this[is].trim().split(/\s+/).filter((e=>["config","datasets","template","xfdf","xslt"].includes(e))))}}class PageOffset extends XFAObject{constructor(e){super(Yo,"pageOffset");this.x=getInteger({data:e.x,defaultValue:"useXDCSetting",validate:e=>!0});this.y=getInteger({data:e.y,defaultValue:"useXDCSetting",validate:e=>!0})}}class PageRange extends StringObject{constructor(e){super(Yo,"pageRange")}[cs](){const e=this[is].trim().split(/\s+/).map((e=>parseInt(e,10))),t=[];for(let a=0,r=e.length;a!1))}}class Pcl extends XFAObject{constructor(e){super(Yo,"pcl",!0);this.name=e.name||"";this.batchOutput=null;this.fontInfo=null;this.jog=null;this.mediumInfo=null;this.outputBin=null;this.pageOffset=null;this.staple=null;this.xdc=null}}class Pdf extends XFAObject{constructor(e){super(Yo,"pdf",!0);this.name=e.name||"";this.adobeExtensionLevel=null;this.batchOutput=null;this.compression=null;this.creator=null;this.encryption=null;this.fontInfo=null;this.interactive=null;this.linearized=null;this.openAction=null;this.pdfa=null;this.producer=null;this.renderPolicy=null;this.scriptModel=null;this.silentPrint=null;this.submitFormat=null;this.tagged=null;this.version=null;this.viewerPreferences=null;this.xdc=null}}class Pdfa extends XFAObject{constructor(e){super(Yo,"pdfa",!0);this.amd=null;this.conformance=null;this.includeXDPContent=null;this.part=null}}class Permissions extends XFAObject{constructor(e){super(Yo,"permissions",!0);this.accessibleContent=null;this.change=null;this.contentCopy=null;this.documentAssembly=null;this.formFieldFilling=null;this.modifyAnnots=null;this.plaintextMetadata=null;this.print=null;this.printHighQuality=null}}class PickTrayByPDFSize extends Option01{constructor(e){super(Yo,"pickTrayByPDFSize")}}class config_Picture extends StringObject{constructor(e){super(Yo,"picture")}}class PlaintextMetadata extends Option01{constructor(e){super(Yo,"plaintextMetadata")}}class Presence extends OptionObject{constructor(e){super(Yo,"presence",["preserve","dissolve","dissolveStructure","ignore","remove"])}}class Present extends XFAObject{constructor(e){super(Yo,"present",!0);this.behaviorOverride=null;this.cache=null;this.common=null;this.copies=null;this.destination=null;this.incrementalMerge=null;this.layout=null;this.output=null;this.overprint=null;this.pagination=null;this.paginationOverride=null;this.script=null;this.validate=null;this.xdp=null;this.driver=new XFAObjectArray;this.labelPrinter=new XFAObjectArray;this.pcl=new XFAObjectArray;this.pdf=new XFAObjectArray;this.ps=new XFAObjectArray;this.submitUrl=new XFAObjectArray;this.webClient=new XFAObjectArray;this.zpl=new XFAObjectArray}}class Print extends Option01{constructor(e){super(Yo,"print")}}class PrintHighQuality extends Option01{constructor(e){super(Yo,"printHighQuality")}}class PrintScaling extends OptionObject{constructor(e){super(Yo,"printScaling",["appdefault","noScaling"])}}class PrinterName extends StringObject{constructor(e){super(Yo,"printerName")}}class Producer extends StringObject{constructor(e){super(Yo,"producer")}}class Ps extends XFAObject{constructor(e){super(Yo,"ps",!0);this.name=e.name||"";this.batchOutput=null;this.fontInfo=null;this.jog=null;this.mediumInfo=null;this.outputBin=null;this.staple=null;this.xdc=null}}class Range extends ContentObject{constructor(e){super(Yo,"range")}[cs](){this[is]=this[is].split(",",2).map((e=>e.split("-").map((e=>parseInt(e.trim(),10))))).filter((e=>e.every((e=>!isNaN(e))))).map((e=>{1===e.length&&e.push(e[0]);return e}))}}class Record extends ContentObject{constructor(e){super(Yo,"record")}[cs](){this[is]=this[is].trim();const e=parseInt(this[is],10);!isNaN(e)&&e>=0&&(this[is]=e)}}class Relevant extends ContentObject{constructor(e){super(Yo,"relevant")}[cs](){this[is]=this[is].trim().split(/\s+/)}}class Rename extends ContentObject{constructor(e){super(Yo,"rename")}[cs](){this[is]=this[is].trim();(this[is].toLowerCase().startsWith("xml")||new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*","u").test(this[is]))&&warn("XFA - Rename: invalid XFA name")}}class RenderPolicy extends OptionObject{constructor(e){super(Yo,"renderPolicy",["server","client"])}}class RunScripts extends OptionObject{constructor(e){super(Yo,"runScripts",["both","client","none","server"])}}class config_Script extends XFAObject{constructor(e){super(Yo,"script",!0);this.currentPage=null;this.exclude=null;this.runScripts=null}}class ScriptModel extends OptionObject{constructor(e){super(Yo,"scriptModel",["XFA","none"])}}class Severity extends OptionObject{constructor(e){super(Yo,"severity",["ignore","error","information","trace","warning"])}}class SilentPrint extends XFAObject{constructor(e){super(Yo,"silentPrint",!0);this.addSilentPrint=null;this.printerName=null}}class Staple extends XFAObject{constructor(e){super(Yo,"staple");this.mode=getStringOption(e.mode,["usePrinterSetting","on","off"])}}class StartNode extends StringObject{constructor(e){super(Yo,"startNode")}}class StartPage extends IntegerObject{constructor(e){super(Yo,"startPage",0,(e=>!0))}}class SubmitFormat extends OptionObject{constructor(e){super(Yo,"submitFormat",["html","delegate","fdf","xml","pdf"])}}class SubmitUrl extends StringObject{constructor(e){super(Yo,"submitUrl")}}class SubsetBelow extends IntegerObject{constructor(e){super(Yo,"subsetBelow",100,(e=>e>=0&&e<=100))}}class SuppressBanner extends Option01{constructor(e){super(Yo,"suppressBanner")}}class Tagged extends Option01{constructor(e){super(Yo,"tagged")}}class config_Template extends XFAObject{constructor(e){super(Yo,"template",!0);this.base=null;this.relevant=null;this.startPage=null;this.uri=null;this.xsl=null}}class Threshold extends OptionObject{constructor(e){super(Yo,"threshold",["trace","error","information","warning"])}}class To extends OptionObject{constructor(e){super(Yo,"to",["null","memory","stderr","stdout","system","uri"])}}class TemplateCache extends XFAObject{constructor(e){super(Yo,"templateCache");this.maxEntries=getInteger({data:e.maxEntries,defaultValue:5,validate:e=>e>=0})}}class Trace extends XFAObject{constructor(e){super(Yo,"trace",!0);this.area=new XFAObjectArray}}class Transform extends XFAObject{constructor(e){super(Yo,"transform",!0);this.groupParent=null;this.ifEmpty=null;this.nameAttr=null;this.picture=null;this.presence=null;this.rename=null;this.whitespace=null}}class Type extends OptionObject{constructor(e){super(Yo,"type",["none","ascii85","asciiHex","ccittfax","flate","lzw","runLength","native","xdp","mergedXDP"])}}class Uri extends StringObject{constructor(e){super(Yo,"uri")}}class config_Validate extends OptionObject{constructor(e){super(Yo,"validate",["preSubmit","prePrint","preExecute","preSave"])}}class ValidateApprovalSignatures extends ContentObject{constructor(e){super(Yo,"validateApprovalSignatures")}[cs](){this[is]=this[is].trim().split(/\s+/).filter((e=>["docReady","postSign"].includes(e)))}}class ValidationMessaging extends OptionObject{constructor(e){super(Yo,"validationMessaging",["allMessagesIndividually","allMessagesTogether","firstMessageOnly","noMessages"])}}class Version extends OptionObject{constructor(e){super(Yo,"version",["1.7","1.6","1.5","1.4","1.3","1.2"])}}class VersionControl extends XFAObject{constructor(e){super(Yo,"VersionControl");this.outputBelow=getStringOption(e.outputBelow,["warn","error","update"]);this.sourceAbove=getStringOption(e.sourceAbove,["warn","error"]);this.sourceBelow=getStringOption(e.sourceBelow,["update","maintain"])}}class ViewerPreferences extends XFAObject{constructor(e){super(Yo,"viewerPreferences",!0);this.ADBE_JSConsole=null;this.ADBE_JSDebugger=null;this.addViewerPreferences=null;this.duplexOption=null;this.enforce=null;this.numberOfCopies=null;this.pageRange=null;this.pickTrayByPDFSize=null;this.printScaling=null}}class WebClient extends XFAObject{constructor(e){super(Yo,"webClient",!0);this.name=e.name?e.name.trim():"";this.fontInfo=null;this.xdc=null}}class Whitespace extends OptionObject{constructor(e){super(Yo,"whitespace",["preserve","ltrim","normalize","rtrim","trim"])}}class Window extends ContentObject{constructor(e){super(Yo,"window")}[cs](){const e=this[is].split(",",2).map((e=>parseInt(e.trim(),10)));if(e.some((e=>isNaN(e))))this[is]=[0,0];else{1===e.length&&e.push(e[0]);this[is]=e}}}class Xdc extends XFAObject{constructor(e){super(Yo,"xdc",!0);this.uri=new XFAObjectArray;this.xsl=new XFAObjectArray}}class Xdp extends XFAObject{constructor(e){super(Yo,"xdp",!0);this.packets=null}}class Xsl extends XFAObject{constructor(e){super(Yo,"xsl",!0);this.debug=null;this.uri=null}}class Zpl extends XFAObject{constructor(e){super(Yo,"zpl",!0);this.name=e.name?e.name.trim():"";this.batchOutput=null;this.flipLabel=null;this.fontInfo=null;this.xdc=null}}class ConfigNamespace{static[ho](e,t){if(ConfigNamespace.hasOwnProperty(e))return ConfigNamespace[e](t)}static acrobat(e){return new Acrobat(e)}static acrobat7(e){return new Acrobat7(e)}static ADBE_JSConsole(e){return new ADBE_JSConsole(e)}static ADBE_JSDebugger(e){return new ADBE_JSDebugger(e)}static addSilentPrint(e){return new AddSilentPrint(e)}static addViewerPreferences(e){return new AddViewerPreferences(e)}static adjustData(e){return new AdjustData(e)}static adobeExtensionLevel(e){return new AdobeExtensionLevel(e)}static agent(e){return new Agent(e)}static alwaysEmbed(e){return new AlwaysEmbed(e)}static amd(e){return new Amd(e)}static area(e){return new config_Area(e)}static attributes(e){return new Attributes(e)}static autoSave(e){return new AutoSave(e)}static base(e){return new Base(e)}static batchOutput(e){return new BatchOutput(e)}static behaviorOverride(e){return new BehaviorOverride(e)}static cache(e){return new Cache(e)}static change(e){return new Change(e)}static common(e){return new Common(e)}static compress(e){return new Compress(e)}static compressLogicalStructure(e){return new CompressLogicalStructure(e)}static compressObjectStream(e){return new CompressObjectStream(e)}static compression(e){return new Compression(e)}static config(e){return new Config(e)}static conformance(e){return new Conformance(e)}static contentCopy(e){return new ContentCopy(e)}static copies(e){return new Copies(e)}static creator(e){return new Creator(e)}static currentPage(e){return new CurrentPage(e)}static data(e){return new Data(e)}static debug(e){return new Debug(e)}static defaultTypeface(e){return new DefaultTypeface(e)}static destination(e){return new Destination(e)}static documentAssembly(e){return new DocumentAssembly(e)}static driver(e){return new Driver(e)}static duplexOption(e){return new DuplexOption(e)}static dynamicRender(e){return new DynamicRender(e)}static embed(e){return new Embed(e)}static encrypt(e){return new config_Encrypt(e)}static encryption(e){return new config_Encryption(e)}static encryptionLevel(e){return new EncryptionLevel(e)}static enforce(e){return new Enforce(e)}static equate(e){return new Equate(e)}static equateRange(e){return new EquateRange(e)}static exclude(e){return new Exclude(e)}static excludeNS(e){return new ExcludeNS(e)}static flipLabel(e){return new FlipLabel(e)}static fontInfo(e){return new config_FontInfo(e)}static formFieldFilling(e){return new FormFieldFilling(e)}static groupParent(e){return new GroupParent(e)}static ifEmpty(e){return new IfEmpty(e)}static includeXDPContent(e){return new IncludeXDPContent(e)}static incrementalLoad(e){return new IncrementalLoad(e)}static incrementalMerge(e){return new IncrementalMerge(e)}static interactive(e){return new Interactive(e)}static jog(e){return new Jog(e)}static labelPrinter(e){return new LabelPrinter(e)}static layout(e){return new Layout(e)}static level(e){return new Level(e)}static linearized(e){return new Linearized(e)}static locale(e){return new Locale(e)}static localeSet(e){return new LocaleSet(e)}static log(e){return new Log(e)}static map(e){return new MapElement(e)}static mediumInfo(e){return new MediumInfo(e)}static message(e){return new config_Message(e)}static messaging(e){return new Messaging(e)}static mode(e){return new Mode(e)}static modifyAnnots(e){return new ModifyAnnots(e)}static msgId(e){return new MsgId(e)}static nameAttr(e){return new NameAttr(e)}static neverEmbed(e){return new NeverEmbed(e)}static numberOfCopies(e){return new NumberOfCopies(e)}static openAction(e){return new OpenAction(e)}static output(e){return new Output(e)}static outputBin(e){return new OutputBin(e)}static outputXSL(e){return new OutputXSL(e)}static overprint(e){return new Overprint(e)}static packets(e){return new Packets(e)}static pageOffset(e){return new PageOffset(e)}static pageRange(e){return new PageRange(e)}static pagination(e){return new Pagination(e)}static paginationOverride(e){return new PaginationOverride(e)}static part(e){return new Part(e)}static pcl(e){return new Pcl(e)}static pdf(e){return new Pdf(e)}static pdfa(e){return new Pdfa(e)}static permissions(e){return new Permissions(e)}static pickTrayByPDFSize(e){return new PickTrayByPDFSize(e)}static picture(e){return new config_Picture(e)}static plaintextMetadata(e){return new PlaintextMetadata(e)}static presence(e){return new Presence(e)}static present(e){return new Present(e)}static print(e){return new Print(e)}static printHighQuality(e){return new PrintHighQuality(e)}static printScaling(e){return new PrintScaling(e)}static printerName(e){return new PrinterName(e)}static producer(e){return new Producer(e)}static ps(e){return new Ps(e)}static range(e){return new Range(e)}static record(e){return new Record(e)}static relevant(e){return new Relevant(e)}static rename(e){return new Rename(e)}static renderPolicy(e){return new RenderPolicy(e)}static runScripts(e){return new RunScripts(e)}static script(e){return new config_Script(e)}static scriptModel(e){return new ScriptModel(e)}static severity(e){return new Severity(e)}static silentPrint(e){return new SilentPrint(e)}static staple(e){return new Staple(e)}static startNode(e){return new StartNode(e)}static startPage(e){return new StartPage(e)}static submitFormat(e){return new SubmitFormat(e)}static submitUrl(e){return new SubmitUrl(e)}static subsetBelow(e){return new SubsetBelow(e)}static suppressBanner(e){return new SuppressBanner(e)}static tagged(e){return new Tagged(e)}static template(e){return new config_Template(e)}static templateCache(e){return new TemplateCache(e)}static threshold(e){return new Threshold(e)}static to(e){return new To(e)}static trace(e){return new Trace(e)}static transform(e){return new Transform(e)}static type(e){return new Type(e)}static uri(e){return new Uri(e)}static validate(e){return new config_Validate(e)}static validateApprovalSignatures(e){return new ValidateApprovalSignatures(e)}static validationMessaging(e){return new ValidationMessaging(e)}static version(e){return new Version(e)}static versionControl(e){return new VersionControl(e)}static viewerPreferences(e){return new ViewerPreferences(e)}static webClient(e){return new WebClient(e)}static whitespace(e){return new Whitespace(e)}static window(e){return new Window(e)}static xdc(e){return new Xdc(e)}static xdp(e){return new Xdp(e)}static xsl(e){return new Xsl(e)}static zpl(e){return new Zpl(e)}}const Zo=uo.connectionSet.id;class ConnectionSet extends XFAObject{constructor(e){super(Zo,"connectionSet",!0);this.wsdlConnection=new XFAObjectArray;this.xmlConnection=new XFAObjectArray;this.xsdConnection=new XFAObjectArray}}class EffectiveInputPolicy extends XFAObject{constructor(e){super(Zo,"effectiveInputPolicy");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class EffectiveOutputPolicy extends XFAObject{constructor(e){super(Zo,"effectiveOutputPolicy");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class Operation extends StringObject{constructor(e){super(Zo,"operation");this.id=e.id||"";this.input=e.input||"";this.name=e.name||"";this.output=e.output||"";this.use=e.use||"";this.usehref=e.usehref||""}}class RootElement extends StringObject{constructor(e){super(Zo,"rootElement");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class SoapAction extends StringObject{constructor(e){super(Zo,"soapAction");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class SoapAddress extends StringObject{constructor(e){super(Zo,"soapAddress");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class connection_set_Uri extends StringObject{constructor(e){super(Zo,"uri");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class WsdlAddress extends StringObject{constructor(e){super(Zo,"wsdlAddress");this.id=e.id||"";this.name=e.name||"";this.use=e.use||"";this.usehref=e.usehref||""}}class WsdlConnection extends XFAObject{constructor(e){super(Zo,"wsdlConnection",!0);this.dataDescription=e.dataDescription||"";this.name=e.name||"";this.effectiveInputPolicy=null;this.effectiveOutputPolicy=null;this.operation=null;this.soapAction=null;this.soapAddress=null;this.wsdlAddress=null}}class XmlConnection extends XFAObject{constructor(e){super(Zo,"xmlConnection",!0);this.dataDescription=e.dataDescription||"";this.name=e.name||"";this.uri=null}}class XsdConnection extends XFAObject{constructor(e){super(Zo,"xsdConnection",!0);this.dataDescription=e.dataDescription||"";this.name=e.name||"";this.rootElement=null;this.uri=null}}class ConnectionSetNamespace{static[ho](e,t){if(ConnectionSetNamespace.hasOwnProperty(e))return ConnectionSetNamespace[e](t)}static connectionSet(e){return new ConnectionSet(e)}static effectiveInputPolicy(e){return new EffectiveInputPolicy(e)}static effectiveOutputPolicy(e){return new EffectiveOutputPolicy(e)}static operation(e){return new Operation(e)}static rootElement(e){return new RootElement(e)}static soapAction(e){return new SoapAction(e)}static soapAddress(e){return new SoapAddress(e)}static uri(e){return new connection_set_Uri(e)}static wsdlAddress(e){return new WsdlAddress(e)}static wsdlConnection(e){return new WsdlConnection(e)}static xmlConnection(e){return new XmlConnection(e)}static xsdConnection(e){return new XsdConnection(e)}}const Qo=uo.datasets.id;class datasets_Data extends XmlObject{constructor(e){super(Qo,"data",e)}[Ns](){return!0}}class Datasets extends XFAObject{constructor(e){super(Qo,"datasets",!0);this.data=null;this.Signature=null}[Ws](e){const t=e[qs];("data"===t&&e[Xs]===Qo||"Signature"===t&&e[Xs]===uo.signature.id)&&(this[t]=e);this[Yn](e)}}class DatasetsNamespace{static[ho](e,t){if(DatasetsNamespace.hasOwnProperty(e))return DatasetsNamespace[e](t)}static datasets(e){return new Datasets(e)}static data(e){return new datasets_Data(e)}}const ec=uo.localeSet.id;class CalendarSymbols extends XFAObject{constructor(e){super(ec,"calendarSymbols",!0);this.name="gregorian";this.dayNames=new XFAObjectArray(2);this.eraNames=null;this.meridiemNames=null;this.monthNames=new XFAObjectArray(2)}}class CurrencySymbol extends StringObject{constructor(e){super(ec,"currencySymbol");this.name=getStringOption(e.name,["symbol","isoname","decimal"])}}class CurrencySymbols extends XFAObject{constructor(e){super(ec,"currencySymbols",!0);this.currencySymbol=new XFAObjectArray(3)}}class DatePattern extends StringObject{constructor(e){super(ec,"datePattern");this.name=getStringOption(e.name,["full","long","med","short"])}}class DatePatterns extends XFAObject{constructor(e){super(ec,"datePatterns",!0);this.datePattern=new XFAObjectArray(4)}}class DateTimeSymbols extends ContentObject{constructor(e){super(ec,"dateTimeSymbols")}}class Day extends StringObject{constructor(e){super(ec,"day")}}class DayNames extends XFAObject{constructor(e){super(ec,"dayNames",!0);this.abbr=getInteger({data:e.abbr,defaultValue:0,validate:e=>1===e});this.day=new XFAObjectArray(7)}}class Era extends StringObject{constructor(e){super(ec,"era")}}class EraNames extends XFAObject{constructor(e){super(ec,"eraNames",!0);this.era=new XFAObjectArray(2)}}class locale_set_Locale extends XFAObject{constructor(e){super(ec,"locale",!0);this.desc=e.desc||"";this.name="isoname";this.calendarSymbols=null;this.currencySymbols=null;this.datePatterns=null;this.dateTimeSymbols=null;this.numberPatterns=null;this.numberSymbols=null;this.timePatterns=null;this.typeFaces=null}}class locale_set_LocaleSet extends XFAObject{constructor(e){super(ec,"localeSet",!0);this.locale=new XFAObjectArray}}class Meridiem extends StringObject{constructor(e){super(ec,"meridiem")}}class MeridiemNames extends XFAObject{constructor(e){super(ec,"meridiemNames",!0);this.meridiem=new XFAObjectArray(2)}}class Month extends StringObject{constructor(e){super(ec,"month")}}class MonthNames extends XFAObject{constructor(e){super(ec,"monthNames",!0);this.abbr=getInteger({data:e.abbr,defaultValue:0,validate:e=>1===e});this.month=new XFAObjectArray(12)}}class NumberPattern extends StringObject{constructor(e){super(ec,"numberPattern");this.name=getStringOption(e.name,["full","long","med","short"])}}class NumberPatterns extends XFAObject{constructor(e){super(ec,"numberPatterns",!0);this.numberPattern=new XFAObjectArray(4)}}class NumberSymbol extends StringObject{constructor(e){super(ec,"numberSymbol");this.name=getStringOption(e.name,["decimal","grouping","percent","minus","zero"])}}class NumberSymbols extends XFAObject{constructor(e){super(ec,"numberSymbols",!0);this.numberSymbol=new XFAObjectArray(5)}}class TimePattern extends StringObject{constructor(e){super(ec,"timePattern");this.name=getStringOption(e.name,["full","long","med","short"])}}class TimePatterns extends XFAObject{constructor(e){super(ec,"timePatterns",!0);this.timePattern=new XFAObjectArray(4)}}class TypeFace extends XFAObject{constructor(e){super(ec,"typeFace",!0);this.name=""|e.name}}class TypeFaces extends XFAObject{constructor(e){super(ec,"typeFaces",!0);this.typeFace=new XFAObjectArray}}class LocaleSetNamespace{static[ho](e,t){if(LocaleSetNamespace.hasOwnProperty(e))return LocaleSetNamespace[e](t)}static calendarSymbols(e){return new CalendarSymbols(e)}static currencySymbol(e){return new CurrencySymbol(e)}static currencySymbols(e){return new CurrencySymbols(e)}static datePattern(e){return new DatePattern(e)}static datePatterns(e){return new DatePatterns(e)}static dateTimeSymbols(e){return new DateTimeSymbols(e)}static day(e){return new Day(e)}static dayNames(e){return new DayNames(e)}static era(e){return new Era(e)}static eraNames(e){return new EraNames(e)}static locale(e){return new locale_set_Locale(e)}static localeSet(e){return new locale_set_LocaleSet(e)}static meridiem(e){return new Meridiem(e)}static meridiemNames(e){return new MeridiemNames(e)}static month(e){return new Month(e)}static monthNames(e){return new MonthNames(e)}static numberPattern(e){return new NumberPattern(e)}static numberPatterns(e){return new NumberPatterns(e)}static numberSymbol(e){return new NumberSymbol(e)}static numberSymbols(e){return new NumberSymbols(e)}static timePattern(e){return new TimePattern(e)}static timePatterns(e){return new TimePatterns(e)}static typeFace(e){return new TypeFace(e)}static typeFaces(e){return new TypeFaces(e)}}const tc=uo.signature.id;class signature_Signature extends XFAObject{constructor(e){super(tc,"signature",!0)}}class SignatureNamespace{static[ho](e,t){if(SignatureNamespace.hasOwnProperty(e))return SignatureNamespace[e](t)}static signature(e){return new signature_Signature(e)}}const ac=uo.stylesheet.id;class Stylesheet extends XFAObject{constructor(e){super(ac,"stylesheet",!0)}}class StylesheetNamespace{static[ho](e,t){if(StylesheetNamespace.hasOwnProperty(e))return StylesheetNamespace[e](t)}static stylesheet(e){return new Stylesheet(e)}}const rc=uo.xdp.id;class xdp_Xdp extends XFAObject{constructor(e){super(rc,"xdp",!0);this.uuid=e.uuid||"";this.timeStamp=e.timeStamp||"";this.config=null;this.connectionSet=null;this.datasets=null;this.localeSet=null;this.stylesheet=new XFAObjectArray;this.template=null}[zs](e){const t=uo[e[qs]];return t&&e[Xs]===t.id}}class XdpNamespace{static[ho](e,t){if(XdpNamespace.hasOwnProperty(e))return XdpNamespace[e](t)}static xdp(e){return new xdp_Xdp(e)}}const ic=uo.xhtml.id,nc=Symbol(),sc=new Set(["color","font","font-family","font-size","font-stretch","font-style","font-weight","margin","margin-bottom","margin-left","margin-right","margin-top","letter-spacing","line-height","orphans","page-break-after","page-break-before","page-break-inside","tab-interval","tab-stop","text-align","text-decoration","text-indent","vertical-align","widows","kerning-mode","xfa-font-horizontal-scale","xfa-font-vertical-scale","xfa-spacerun","xfa-tab-stops"]),oc=new Map([["page-break-after","breakAfter"],["page-break-before","breakBefore"],["page-break-inside","breakInside"],["kerning-mode",e=>"none"===e?"none":"normal"],["xfa-font-horizontal-scale",e=>`scaleX(${Math.max(0,parseInt(e)/100).toFixed(2)})`],["xfa-font-vertical-scale",e=>`scaleY(${Math.max(0,parseInt(e)/100).toFixed(2)})`],["xfa-spacerun",""],["xfa-tab-stops",""],["font-size",(e,t)=>measureToString(.99*(e=t.fontSize=Math.abs(getMeasurement(e))))],["letter-spacing",e=>measureToString(getMeasurement(e))],["line-height",e=>measureToString(getMeasurement(e))],["margin",e=>measureToString(getMeasurement(e))],["margin-bottom",e=>measureToString(getMeasurement(e))],["margin-left",e=>measureToString(getMeasurement(e))],["margin-right",e=>measureToString(getMeasurement(e))],["margin-top",e=>measureToString(getMeasurement(e))],["text-indent",e=>measureToString(getMeasurement(e))],["font-family",e=>e],["vertical-align",e=>measureToString(getMeasurement(e))]]),cc=/\s+/g,lc=/[\r\n]+/g,hc=/\r\n?/g;function mapStyle(e,t,a){const r=Object.create(null);if(!e)return r;const i=Object.create(null);for(const[t,a]of e.split(";").map((e=>e.split(":",2)))){const e=oc.get(t);if(""===e)continue;let n=a;e&&(n="string"==typeof e?e:e(a,i));t.endsWith("scale")?r.transform=r.transform?`${r[t]} ${n}`:n:r[t.replaceAll(/-([a-zA-Z])/g,((e,t)=>t.toUpperCase()))]=n}r.fontFamily&&setFontFamily({typeface:r.fontFamily,weight:r.fontWeight||"normal",posture:r.fontStyle||"normal",size:i.fontSize||0},t,t[vs].fontFinder,r);if(a&&r.verticalAlign&&"0px"!==r.verticalAlign&&r.fontSize){const e=.583,t=.333,a=getMeasurement(r.fontSize);r.fontSize=measureToString(a*e);r.verticalAlign=measureToString(Math.sign(getMeasurement(r.verticalAlign))*a*t)}a&&r.fontSize&&(r.fontSize=`calc(${r.fontSize} * var(--total-scale-factor))`);fixTextIndent(r);return r}const uc=new Set(["body","html"]);class XhtmlObject extends XmlObject{constructor(e,t){super(ic,t);this[nc]=!1;this.style=e.style||""}[Qn](e){super[Qn](e);this.style=function checkStyle(e){return e.style?e.style.split(";").filter((e=>!!e.trim())).map((e=>e.split(":",2).map((e=>e.trim())))).filter((([t,a])=>{"font-family"===t&&e[vs].usedTypefaces.add(a);return sc.has(t)})).map((e=>e.join(":"))).join(";"):""}(this)}[Kn](){return!uc.has(this[qs])}[$s](e,t=!1){if(t)this[nc]=!0;else{e=e.replaceAll(lc,"");this.style.includes("xfa-spacerun:yes")||(e=e.replaceAll(cc," "))}e&&(this[is]+=e)}[Gs](e,t=!0){const a=Object.create(null),r={top:NaN,bottom:NaN,left:NaN,right:NaN};let i=null;for(const[e,t]of this.style.split(";").map((e=>e.split(":",2))))switch(e){case"font-family":a.typeface=stripQuotes(t);break;case"font-size":a.size=getMeasurement(t);break;case"font-weight":a.weight=t;break;case"font-style":a.posture=t;break;case"letter-spacing":a.letterSpacing=getMeasurement(t);break;case"margin":const e=t.split(/ \t/).map((e=>getMeasurement(e)));switch(e.length){case 1:r.top=r.bottom=r.left=r.right=e[0];break;case 2:r.top=r.bottom=e[0];r.left=r.right=e[1];break;case 3:r.top=e[0];r.bottom=e[2];r.left=r.right=e[1];break;case 4:r.top=e[0];r.left=e[1];r.bottom=e[2];r.right=e[3]}break;case"margin-top":r.top=getMeasurement(t);break;case"margin-bottom":r.bottom=getMeasurement(t);break;case"margin-left":r.left=getMeasurement(t);break;case"margin-right":r.right=getMeasurement(t);break;case"line-height":i=getMeasurement(t)}e.pushData(a,r,i);if(this[is])e.addString(this[is]);else for(const t of this[ws]())"#text"!==t[qs]?t[Gs](e):e.addString(t[is]);t&&e.popFont()}[so](e){const t=[];this[os]={children:t};this[Zn]({});if(0===t.length&&!this[is])return HTMLResult.EMPTY;let a;a=this[nc]?this[is]?this[is].replaceAll(hc,"\n"):void 0:this[is]||void 0;return HTMLResult.success({name:this[qs],attributes:{href:this.href,style:mapStyle(this.style,this,this[nc])},children:t,value:a})}}class A extends XhtmlObject{constructor(e){super(e,"a");this.href=fixURL(e.href)||""}}class B extends XhtmlObject{constructor(e){super(e,"b")}[Gs](e){e.pushFont({weight:"bold"});super[Gs](e);e.popFont()}}class Body extends XhtmlObject{constructor(e){super(e,"body")}[so](e){const t=super[so](e),{html:a}=t;if(!a)return HTMLResult.EMPTY;a.name="div";a.attributes.class=["xfaRich"];return t}}class Br extends XhtmlObject{constructor(e){super(e,"br")}[io](){return"\n"}[Gs](e){e.addString("\n")}[so](e){return HTMLResult.success({name:"br"})}}class Html extends XhtmlObject{constructor(e){super(e,"html")}[so](e){const t=[];this[os]={children:t};this[Zn]({});if(0===t.length)return HTMLResult.success({name:"div",attributes:{class:["xfaRich"],style:{}},value:this[is]||""});if(1===t.length){const e=t[0];if(e.attributes?.class.includes("xfaRich"))return HTMLResult.success(e)}return HTMLResult.success({name:"div",attributes:{class:["xfaRich"],style:{}},children:t})}}class I extends XhtmlObject{constructor(e){super(e,"i")}[Gs](e){e.pushFont({posture:"italic"});super[Gs](e);e.popFont()}}class Li extends XhtmlObject{constructor(e){super(e,"li")}}class Ol extends XhtmlObject{constructor(e){super(e,"ol")}}class P extends XhtmlObject{constructor(e){super(e,"p")}[Gs](e){super[Gs](e,!1);e.addString("\n");e.addPara();e.popFont()}[io](){return this[ks]()[ws]().at(-1)===this?super[io]():super[io]()+"\n"}}class Span extends XhtmlObject{constructor(e){super(e,"span")}}class Sub extends XhtmlObject{constructor(e){super(e,"sub")}}class Sup extends XhtmlObject{constructor(e){super(e,"sup")}}class Ul extends XhtmlObject{constructor(e){super(e,"ul")}}class XhtmlNamespace{static[ho](e,t){if(XhtmlNamespace.hasOwnProperty(e))return XhtmlNamespace[e](t)}static a(e){return new A(e)}static b(e){return new B(e)}static body(e){return new Body(e)}static br(e){return new Br(e)}static html(e){return new Html(e)}static i(e){return new I(e)}static li(e){return new Li(e)}static ol(e){return new Ol(e)}static p(e){return new P(e)}static span(e){return new Span(e)}static sub(e){return new Sub(e)}static sup(e){return new Sup(e)}static ul(e){return new Ul(e)}}const dc={config:ConfigNamespace,connection:ConnectionSetNamespace,datasets:DatasetsNamespace,localeSet:LocaleSetNamespace,signature:SignatureNamespace,stylesheet:StylesheetNamespace,template:TemplateNamespace,xdp:XdpNamespace,xhtml:XhtmlNamespace};class UnknownNamespace{constructor(e){this.namespaceId=e}[ho](e,t){return new XmlObject(this.namespaceId,e,t)}}class Root extends XFAObject{constructor(e){super(-1,"root",Object.create(null));this.element=null;this[Is]=e}[Ws](e){this.element=e;return!0}[cs](){super[cs]();if(this.element.template instanceof Template){this[Is].set(Ys,this.element);this.element.template[Zs](this[Is]);this.element.template[Is]=this[Is]}}}class Empty extends XFAObject{constructor(){super(-1,"",Object.create(null))}[Ws](e){return!1}}class Builder{constructor(e=null){this._namespaceStack=[];this._nsAgnosticLevel=0;this._namespacePrefixes=new Map;this._namespaces=new Map;this._nextNsId=Math.max(...Object.values(uo).map((({id:e})=>e)));this._currentNamespace=e||new UnknownNamespace(++this._nextNsId)}buildRoot(e){return new Root(e)}build({nsPrefix:e,name:t,attributes:a,namespace:r,prefixes:i}){const n=null!==r;if(n){this._namespaceStack.push(this._currentNamespace);this._currentNamespace=this._searchNamespace(r)}i&&this._addNamespacePrefix(i);if(a.hasOwnProperty(Hs)){const e=dc.datasets,t=a[Hs];let r=null;for(const[a,i]of Object.entries(t)){if(this._getNamespaceToUse(a)===e){r={xfa:i};break}}r?a[Hs]=r:delete a[Hs]}const s=this._getNamespaceToUse(e),o=s?.[ho](t,a)||new Empty;o[Ns]()&&this._nsAgnosticLevel++;(n||i||o[Ns]())&&(o[ts]={hasNamespace:n,prefixes:i,nsAgnostic:o[Ns]()});return o}isNsAgnostic(){return this._nsAgnosticLevel>0}_searchNamespace(e){let t=this._namespaces.get(e);if(t)return t;for(const[a,{check:r}]of Object.entries(uo))if(r(e)){t=dc[a];if(t){this._namespaces.set(e,t);return t}break}t=new UnknownNamespace(++this._nextNsId);this._namespaces.set(e,t);return t}_addNamespacePrefix(e){for(const{prefix:t,value:a}of e){const e=this._searchNamespace(a);let r=this._namespacePrefixes.get(t);if(!r){r=[];this._namespacePrefixes.set(t,r)}r.push(e)}}_getNamespaceToUse(e){if(!e)return this._currentNamespace;const t=this._namespacePrefixes.get(e);if(t?.length>0)return t.at(-1);warn(`Unknown namespace prefix: ${e}.`);return null}clean(e){const{hasNamespace:t,prefixes:a,nsAgnostic:r}=e;t&&(this._currentNamespace=this._namespaceStack.pop());a&&a.forEach((({prefix:e})=>{this._namespacePrefixes.get(e).pop()}));r&&this._nsAgnosticLevel--}}class XFAParser extends XMLParserBase{constructor(e=null,t=!1){super();this._builder=new Builder(e);this._stack=[];this._globalData={usedTypefaces:new Set};this._ids=new Map;this._current=this._builder.buildRoot(this._ids);this._errorCode=Pn;this._whiteRegex=/^\s+$/;this._nbsps=/\xa0+/g;this._richText=t}parse(e){this.parseXml(e);if(this._errorCode===Pn){this._current[cs]();return this._current.element}}onText(e){e=e.replace(this._nbsps,(e=>e.slice(1)+" "));this._richText||this._current[Kn]()?this._current[$s](e,this._richText):this._whiteRegex.test(e)||this._current[$s](e.trim())}onCdata(e){this._current[$s](e)}_mkAttributes(e,t){let a=null,r=null;const i=Object.create({});for(const{name:n,value:s}of e)if("xmlns"===n)a?warn(`XFA - multiple namespace definition in <${t}>`):a=s;else if(n.startsWith("xmlns:")){const e=n.substring(6);r??=[];r.push({prefix:e,value:s})}else{const e=n.indexOf(":");if(-1===e)i[n]=s;else{const t=i[Hs]??=Object.create(null),[a,r]=[n.slice(0,e),n.slice(e+1)];(t[a]||=Object.create(null))[r]=s}}return[a,r,i]}_getNameAndPrefix(e,t){const a=e.indexOf(":");return-1===a?[e,null]:[e.substring(a+1),t?"":e.substring(0,a)]}onBeginElement(e,t,a){const[r,i,n]=this._mkAttributes(t,e),[s,o]=this._getNameAndPrefix(e,this._builder.isNsAgnostic()),c=this._builder.build({nsPrefix:o,name:s,attributes:n,namespace:r,prefixes:i});c[vs]=this._globalData;if(a){c[cs]();this._current[Ws](c)&&c[eo](this._ids);c[Qn](this._builder)}else{this._stack.push(this._current);this._current=c}}onEndElement(e){const t=this._current;if(t[Ms]()&&"string"==typeof t[is]){const e=new XFAParser;e._globalData=this._globalData;const a=e.parse(t[is]);t[is]=null;t[Ws](a)}t[cs]();this._current=this._stack.pop();this._current[Ws](t)&&t[eo](this._ids);t[Qn](this._builder)}onError(e){this._errorCode=e}}class XFAFactory{constructor(e){try{this.root=(new XFAParser).parse(XFAFactory._createDocument(e));const t=new Binder(this.root);this.form=t.bind();this.dataHandler=new DataHandler(this.root,t.getData());this.form[vs].template=this.form}catch(e){warn(`XFA - an error occurred during parsing and binding: ${e}`)}}isValid(){return!(!this.root||!this.form)}_createPagesHelper(){const e=this.form[no]();return new Promise(((t,a)=>{const nextIteration=()=>{try{const a=e.next();a.done?t(a.value):setTimeout(nextIteration,0)}catch(e){a(e)}};setTimeout(nextIteration,0)}))}async _createPages(){try{this.pages=await this._createPagesHelper();this.dims=this.pages.children.map((e=>{const{width:t,height:a}=e.attributes.style;return[0,0,parseInt(t),parseInt(a)]}))}catch(e){warn(`XFA - an error occurred during layout: ${e}`)}}getBoundingBox(e){return this.dims[e]}async getNumPages(){this.pages||await this._createPages();return this.dims.length}setImages(e){this.form[vs].images=e}setFonts(e){this.form[vs].fontFinder=new FontFinder(e);const t=[];for(let e of this.form[vs].usedTypefaces){e=stripQuotes(e);this.form[vs].fontFinder.find(e)||t.push(e)}return t.length>0?t:null}appendFonts(e,t){this.form[vs].fontFinder.add(e,t)}async getPages(){this.pages||await this._createPages();const e=this.pages;this.pages=null;return e}serializeData(e){return this.dataHandler.serialize(e)}static _createDocument(e){return e["/xdp:xdp"]?Object.values(e).join(""):e["xdp:xdp"]}static getRichTextAsHtml(e){if(!e||"string"!=typeof e)return null;try{let t=new XFAParser(XhtmlNamespace,!0).parse(e);if(!["body","xhtml"].includes(t[qs])){const e=XhtmlNamespace.body({});e[Yn](t);t=e}const a=t[so]();if(!a.success)return null;const{html:r}=a,{attributes:i}=r;if(i){i.class&&(i.class=i.class.filter((e=>!e.startsWith("xfa"))));i.dir="auto"}return{html:r,str:t[io]()}}catch(e){warn(`XFA - an error occurred during parsing of rich text: ${e}`)}return null}}class AnnotationFactory{static createGlobals(e){return Promise.all([e.ensureCatalog("acroForm"),e.ensureDoc("xfaDatasets"),e.ensureCatalog("structTreeRoot"),e.ensureCatalog("baseUrl"),e.ensureCatalog("attachments"),e.ensureCatalog("globalColorSpaceCache")]).then((([t,a,r,i,n,s])=>({pdfManager:e,acroForm:t instanceof Dict?t:Dict.empty,xfaDatasets:a,structTreeRoot:r,baseUrl:i,attachments:n,globalColorSpaceCache:s})),(e=>{warn(`createGlobals: "${e}".`);return null}))}static async create(e,t,a,r,i,n,s){const o=i?await this._getPageIndex(e,t,a.pdfManager):null;return a.pdfManager.ensure(this,"_create",[e,t,a,r,i,n,o,s])}static _create(e,t,a,r,i=!1,n=null,s=null,o=null){const c=e.fetchIfRef(t);if(!(c instanceof Dict))return;const{acroForm:l,pdfManager:h}=a,u=t instanceof Ref?t.toString():`annot_${r.createObjId()}`;let d=c.get("Subtype");d=d instanceof Name?d.name:null;const f={xref:e,ref:t,dict:c,subtype:d,id:u,annotationGlobals:a,collectFields:i,orphanFields:n,needAppearances:!i&&!0===l.get("NeedAppearances"),pageIndex:s,evaluatorOptions:h.evaluatorOptions,pageRef:o};switch(d){case"Link":return new LinkAnnotation(f);case"Text":return new TextAnnotation(f);case"Widget":let e=getInheritableProperty({dict:c,key:"FT"});e=e instanceof Name?e.name:null;switch(e){case"Tx":return new TextWidgetAnnotation(f);case"Btn":return new ButtonWidgetAnnotation(f);case"Ch":return new ChoiceWidgetAnnotation(f);case"Sig":return new SignatureWidgetAnnotation(f)}warn(`Unimplemented widget field type "${e}", falling back to base field type.`);return new WidgetAnnotation(f);case"Popup":return new PopupAnnotation(f);case"FreeText":return new FreeTextAnnotation(f);case"Line":return new LineAnnotation(f);case"Square":return new SquareAnnotation(f);case"Circle":return new CircleAnnotation(f);case"PolyLine":return new PolylineAnnotation(f);case"Polygon":return new PolygonAnnotation(f);case"Caret":return new CaretAnnotation(f);case"Ink":return new InkAnnotation(f);case"Highlight":return new HighlightAnnotation(f);case"Underline":return new UnderlineAnnotation(f);case"Squiggly":return new SquigglyAnnotation(f);case"StrikeOut":return new StrikeOutAnnotation(f);case"Stamp":return new StampAnnotation(f);case"FileAttachment":return new FileAttachmentAnnotation(f);default:i||warn(d?`Unimplemented annotation type "${d}", falling back to base annotation.`:"Annotation is missing the required /Subtype.");return new Annotation(f)}}static async _getPageIndex(e,t,a){try{const r=await e.fetchIfRefAsync(t);if(!(r instanceof Dict))return-1;const i=r.getRaw("P");if(i instanceof Ref)try{return await a.ensureCatalog("getPageIndex",[i])}catch(e){info(`_getPageIndex -- not a valid page reference: "${e}".`)}if(r.has("Kids"))return-1;const n=await a.ensureDoc("numPages");for(let e=0;ee/255))}function getQuadPoints(e,t){const a=e.getArray("QuadPoints");if(!isNumberArray(a,null)||0===a.length||a.length%8>0)return null;const r=new Float32Array(a.length);for(let e=0,i=a.length;et[2]||gt[3]))return null;r.set([d,p,f,p,d,g,f,g],e)}return r}function getTransformMatrix(e,t,a){const r=new Float32Array([1/0,1/0,-1/0,-1/0]);Util.axialAlignedBoundingBox(t,a,r);const[i,n,s,o]=r;if(i===s||n===o)return[1,0,0,1,e[0],e[1]];const c=(e[2]-e[0])/(s-i),l=(e[3]-e[1])/(o-n);return[c,0,0,l,e[0]-i*c,e[1]-n*l]}class Annotation{constructor(e){const{dict:t,xref:a,annotationGlobals:r,ref:i,orphanFields:n}=e,s=n?.get(i);s&&t.set("Parent",s);this.setTitle(t.get("T"));this.setContents(t.get("Contents"));this.setModificationDate(t.get("M"));this.setFlags(t.get("F"));this.setRectangle(t.getArray("Rect"));this.setColor(t.getArray("C"));this.setBorderStyle(t);this.setAppearance(t);this.setOptionalContent(t);const o=t.get("MK");this.setBorderAndBackgroundColors(o);this.setRotation(o,t);this.ref=e.ref instanceof Ref?e.ref:null;this._streams=[];this.appearance&&this._streams.push(this.appearance);const c=!!(this.flags&ee),l=!!(this.flags&te);this.data={annotationFlags:this.flags,borderStyle:this.borderStyle,color:this.color,backgroundColor:this.backgroundColor,borderColor:this.borderColor,rotation:this.rotation,contentsObj:this._contents,hasAppearance:!!this.appearance,id:e.id,modificationDate:this.modificationDate,rect:this.rectangle,subtype:e.subtype,hasOwnCanvas:!1,noRotate:!!(this.flags&Z),noHTML:c&&l,isEditable:!1,structParent:-1};if(r.structTreeRoot){let a=t.get("StructParent");this.data.structParent=a=Number.isInteger(a)&&a>=0?a:-1;r.structTreeRoot.addAnnotationIdToPage(e.pageRef,a)}if(e.collectFields){const r=t.get("Kids");if(Array.isArray(r)){const e=[];for(const t of r)t instanceof Ref&&e.push(t.toString());0!==e.length&&(this.data.kidIds=e)}this.data.actions=collectActions(a,t,ye);this.data.fieldName=this._constructFieldName(t);this.data.pageIndex=e.pageIndex}const h=t.get("IT");h instanceof Name&&(this.data.it=h.name);this._isOffscreenCanvasSupported=e.evaluatorOptions.isOffscreenCanvasSupported;this._fallbackFontDict=null;this._needAppearances=!1}_hasFlag(e,t){return!!(e&t)}_buildFlags(e,t){let{flags:a}=this;if(void 0===e){if(void 0===t)return;return t?a&~Y:a&~J|Y}if(e){a|=Y;return t?a&~Q|J:a&~J|Q}a&=~(J|Q);return t?a&~Y:a|Y}_isViewable(e){return!this._hasFlag(e,K)&&!this._hasFlag(e,Q)}_isPrintable(e){return this._hasFlag(e,Y)&&!this._hasFlag(e,J)&&!this._hasFlag(e,K)}mustBeViewed(e,t){const a=e?.get(this.data.id)?.noView;return void 0!==a?!a:this.viewable&&!this._hasFlag(this.flags,J)}mustBePrinted(e){const t=e?.get(this.data.id)?.noPrint;return void 0!==t?!t:this.printable}mustBeViewedWhenEditing(e,t=null){return e?!this.data.isEditable:!t?.has(this.data.id)}get viewable(){return null!==this.data.quadPoints&&(0===this.flags||this._isViewable(this.flags))}get printable(){return null!==this.data.quadPoints&&(0!==this.flags&&this._isPrintable(this.flags))}_parseStringHelper(e){const t="string"==typeof e?stringToPDFString(e):"";return{str:t,dir:t&&"rtl"===bidi(t).dir?"rtl":"ltr"}}setDefaultAppearance(e){const{dict:t,annotationGlobals:a}=e,r=getInheritableProperty({dict:t,key:"DA"})||a.acroForm.get("DA");this._defaultAppearance="string"==typeof r?r:"";this.data.defaultAppearanceData=parseDefaultAppearance(this._defaultAppearance)}setTitle(e){this._title=this._parseStringHelper(e)}setContents(e){this._contents=this._parseStringHelper(e)}setModificationDate(e){this.modificationDate="string"==typeof e?e:null}setFlags(e){this.flags=Number.isInteger(e)&&e>0?e:0;this.flags&K&&"Annotation"!==this.constructor.name&&(this.flags^=K)}hasFlag(e){return this._hasFlag(this.flags,e)}setRectangle(e){this.rectangle=lookupNormalRect(e,[0,0,0,0])}setColor(e){this.color=getRgbColor(e)}setLineEndings(e){this.lineEndings=["None","None"];if(Array.isArray(e)&&2===e.length)for(let t=0;t<2;t++){const a=e[t];if(a instanceof Name)switch(a.name){case"None":continue;case"Square":case"Circle":case"Diamond":case"OpenArrow":case"ClosedArrow":case"Butt":case"ROpenArrow":case"RClosedArrow":case"Slash":this.lineEndings[t]=a.name;continue}warn(`Ignoring invalid lineEnding: ${a}`)}}setRotation(e,t){this.rotation=0;let a=e instanceof Dict?e.get("R")||0:t.get("Rotate")||0;if(Number.isInteger(a)&&0!==a){a%=360;a<0&&(a+=360);a%90==0&&(this.rotation=a)}}setBorderAndBackgroundColors(e){if(e instanceof Dict){this.borderColor=getRgbColor(e.getArray("BC"),null);this.backgroundColor=getRgbColor(e.getArray("BG"),null)}else this.borderColor=this.backgroundColor=null}setBorderStyle(e){this.borderStyle=new AnnotationBorderStyle;if(e instanceof Dict)if(e.has("BS")){const t=e.get("BS");if(t instanceof Dict){const e=t.get("Type");if(!e||isName(e,"Border")){this.borderStyle.setWidth(t.get("W"),this.rectangle);this.borderStyle.setStyle(t.get("S"));this.borderStyle.setDashArray(t.getArray("D"))}}}else if(e.has("Border")){const t=e.getArray("Border");if(Array.isArray(t)&&t.length>=3){this.borderStyle.setHorizontalCornerRadius(t[0]);this.borderStyle.setVerticalCornerRadius(t[1]);this.borderStyle.setWidth(t[2],this.rectangle);4===t.length&&this.borderStyle.setDashArray(t[3],!0)}}else this.borderStyle.setWidth(0)}setAppearance(e){this.appearance=null;const t=e.get("AP");if(!(t instanceof Dict))return;const a=t.get("N");if(a instanceof BaseStream){this.appearance=a;return}if(!(a instanceof Dict))return;const r=e.get("AS");if(!(r instanceof Name&&a.has(r.name)))return;const i=a.get(r.name);i instanceof BaseStream&&(this.appearance=i)}setOptionalContent(e){this.oc=null;const t=e.get("OC");t instanceof Name?warn("setOptionalContent: Support for /Name-entry is not implemented."):t instanceof Dict&&(this.oc=t)}async loadResources(e,t){const a=await t.dict.getAsync("Resources");a&&await ObjectLoader.load(a,e,a.xref);return a}async getOperatorList(e,t,a,r){const{hasOwnCanvas:i,id:n,rect:o}=this.data;let c=this.appearance;const l=!!(i&&a&s);if(l&&(0===this.width||0===this.height)){this.data.hasOwnCanvas=!1;return{opList:new OperatorList,separateForm:!1,separateCanvas:!1}}if(!c){if(!l)return{opList:new OperatorList,separateForm:!1,separateCanvas:!1};c=new StringStream("");c.dict=new Dict}const h=c.dict,u=await this.loadResources(Ia,c),d=lookupRect(h.getArray("BBox"),[0,0,1,1]),f=lookupMatrix(h.getArray("Matrix"),Fa),g=getTransformMatrix(o,d,f),p=new OperatorList;let m;this.oc&&(m=await e.parseMarkedContentProps(this.oc,null));void 0!==m&&p.addOp(jt,["OC",m]);p.addOp($t,[n,o,g,f,l]);await e.getOperatorList({stream:c,task:t,resources:u,operatorList:p,fallbackFontDict:this._fallbackFontDict});p.addOp(Gt,[]);void 0!==m&&p.addOp(_t,[]);this.reset();return{opList:p,separateForm:!1,separateCanvas:l}}async save(e,t,a,r){return null}get hasTextContent(){return!1}async extractTextContent(e,t,a){if(!this.appearance)return;const r=await this.loadResources(Oa,this.appearance),i=[],n=[];let s=null;const o={desiredSize:Math.Infinity,ready:!0,enqueue(e,t){for(const t of e.items)if(void 0!==t.str){s||=t.transform.slice(-2);n.push(t.str);if(t.hasEOL){i.push(n.join("").trimEnd());n.length=0}}}};await e.getTextContent({stream:this.appearance,task:t,resources:r,includeMarkedContent:!0,keepWhiteSpace:!0,sink:o,viewBox:a});this.reset();n.length&&i.push(n.join("").trimEnd());if(i.length>1||i[0]){const e=this.appearance.dict,t=lookupRect(e.getArray("BBox"),null),a=lookupMatrix(e.getArray("Matrix"),null);this.data.textPosition=this._transformPoint(s,t,a);this.data.textContent=i}}_transformPoint(e,t,a){const{rect:r}=this.data;t||=[0,0,1,1];a||=[1,0,0,1,0,0];const i=getTransformMatrix(r,t,a);i[4]-=r[0];i[5]-=r[1];const n=e.slice();Util.applyTransform(n,i);Util.applyTransform(n,a);return n}getFieldObject(){return this.data.kidIds?{id:this.data.id,actions:this.data.actions,name:this.data.fieldName,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,type:"",kidIds:this.data.kidIds,page:this.data.pageIndex,rotation:this.rotation}:null}reset(){for(const e of this._streams)e.reset()}_constructFieldName(e){if(!e.has("T")&&!e.has("Parent")){warn("Unknown field name, falling back to empty field name.");return""}if(!e.has("Parent"))return stringToPDFString(e.get("T"));const t=[];e.has("T")&&t.unshift(stringToPDFString(e.get("T")));let a=e;const r=new RefSet;e.objId&&r.put(e.objId);for(;a.has("Parent");){a=a.get("Parent");if(!(a instanceof Dict)||a.objId&&r.has(a.objId))break;a.objId&&r.put(a.objId);a.has("T")&&t.unshift(stringToPDFString(a.get("T")))}return t.join(".")}get width(){return this.data.rect[2]-this.data.rect[0]}get height(){return this.data.rect[3]-this.data.rect[1]}}class AnnotationBorderStyle{constructor(){this.width=1;this.rawWidth=1;this.style=fe;this.dashArray=[3];this.horizontalCornerRadius=0;this.verticalCornerRadius=0}setWidth(e,t=[0,0,0,0]){if(e instanceof Name)this.width=0;else if("number"==typeof e){if(e>0){this.rawWidth=e;const a=(t[2]-t[0])/2,r=(t[3]-t[1])/2;if(a>0&&r>0&&(e>a||e>r)){warn(`AnnotationBorderStyle.setWidth - ignoring width: ${e}`);e=1}}this.width=e}}setStyle(e){if(e instanceof Name)switch(e.name){case"S":this.style=fe;break;case"D":this.style=ge;break;case"B":this.style=pe;break;case"I":this.style=me;break;case"U":this.style=be}}setDashArray(e,t=!1){if(Array.isArray(e)){let a=!0,r=!0;for(const t of e){if(!(+t>=0)){a=!1;break}t>0&&(r=!1)}if(0===e.length||a&&!r){this.dashArray=e;t&&this.setStyle(Name.get("D"))}else this.width=0}else e&&(this.width=0)}setHorizontalCornerRadius(e){Number.isInteger(e)&&(this.horizontalCornerRadius=e)}setVerticalCornerRadius(e){Number.isInteger(e)&&(this.verticalCornerRadius=e)}}class MarkupAnnotation extends Annotation{constructor(e){super(e);const{dict:t}=e;if(t.has("IRT")){const e=t.getRaw("IRT");this.data.inReplyTo=e instanceof Ref?e.toString():null;const a=t.get("RT");this.data.replyType=a instanceof Name?a.name:V}let a=null;if(this.data.replyType===G){const e=t.get("IRT");this.setTitle(e.get("T"));this.data.titleObj=this._title;this.setContents(e.get("Contents"));this.data.contentsObj=this._contents;if(e.has("CreationDate")){this.setCreationDate(e.get("CreationDate"));this.data.creationDate=this.creationDate}else this.data.creationDate=null;if(e.has("M")){this.setModificationDate(e.get("M"));this.data.modificationDate=this.modificationDate}else this.data.modificationDate=null;a=e.getRaw("Popup");if(e.has("C")){this.setColor(e.getArray("C"));this.data.color=this.color}else this.data.color=null}else{this.data.titleObj=this._title;this.setCreationDate(t.get("CreationDate"));this.data.creationDate=this.creationDate;a=t.getRaw("Popup");t.has("C")||(this.data.color=null)}this.data.popupRef=a instanceof Ref?a.toString():null;t.has("RC")&&(this.data.richText=XFAFactory.getRichTextAsHtml(t.get("RC")))}setCreationDate(e){this.creationDate="string"==typeof e?e:null}_setDefaultAppearance({xref:e,extra:t,strokeColor:a,fillColor:r,blendMode:i,strokeAlpha:n,fillAlpha:s,pointsCallback:o}){const c=this.data.rect=[1/0,1/0,-1/0,-1/0],l=["q"];t&&l.push(t);a&&l.push(`${a[0]} ${a[1]} ${a[2]} RG`);r&&l.push(`${r[0]} ${r[1]} ${r[2]} rg`);const h=this.data.quadPoints||Float32Array.from([this.rectangle[0],this.rectangle[3],this.rectangle[2],this.rectangle[3],this.rectangle[0],this.rectangle[1],this.rectangle[2],this.rectangle[1]]);for(let e=0,t=h.length;e"string"==typeof e)).map((e=>stringToPDFString(e))):e instanceof Name?stringToPDFString(e.name):"string"==typeof e?stringToPDFString(e):null}hasFieldFlag(e){return!!(this.data.fieldFlags&e)}_isViewable(e){return!0}mustBeViewed(e,t){return t?this.viewable:super.mustBeViewed(e,t)&&!this._hasFlag(this.flags,Q)}getRotationMatrix(e){let t=e?.get(this.data.id)?.rotation;void 0===t&&(t=this.rotation);return 0===t?Fa:getRotationMatrix(t,this.width,this.height)}getBorderAndBackgroundAppearances(e){let t=e?.get(this.data.id)?.rotation;void 0===t&&(t=this.rotation);if(!this.backgroundColor&&!this.borderColor)return"";const a=0===t||180===t?`0 0 ${this.width} ${this.height} re`:`0 0 ${this.height} ${this.width} re`;let r="";this.backgroundColor&&(r=`${getPdfColor(this.backgroundColor,!0)} ${a} f `);if(this.borderColor){r+=`${this.borderStyle.width||1} w ${getPdfColor(this.borderColor,!1)} ${a} S `}return r}async getOperatorList(e,t,a,r){if(a&l&&!(this instanceof SignatureWidgetAnnotation)&&!this.data.noHTML&&!this.data.hasOwnCanvas)return{opList:new OperatorList,separateForm:!0,separateCanvas:!1};if(!this._hasText)return super.getOperatorList(e,t,a,r);const i=await this._getAppearance(e,t,a,r);if(this.appearance&&null===i)return super.getOperatorList(e,t,a,r);const n=new OperatorList;if(!this._defaultAppearance||null===i)return{opList:n,separateForm:!1,separateCanvas:!1};const o=!!(this.data.hasOwnCanvas&&a&s),c=[0,0,this.width,this.height],h=getTransformMatrix(this.data.rect,c,[1,0,0,1,0,0]);let u;this.oc&&(u=await e.parseMarkedContentProps(this.oc,null));void 0!==u&&n.addOp(jt,["OC",u]);n.addOp($t,[this.data.id,this.data.rect,h,this.getRotationMatrix(r),o]);const d=new StringStream(i);await e.getOperatorList({stream:d,task:t,resources:this._fieldResources.mergedResources,operatorList:n});n.addOp(Gt,[]);void 0!==u&&n.addOp(_t,[]);return{opList:n,separateForm:!1,separateCanvas:o}}_getMKDict(e){const t=new Dict(null);e&&t.set("R",e);this.borderColor&&t.set("BC",getPdfColorArray(this.borderColor));this.backgroundColor&&t.set("BG",getPdfColorArray(this.backgroundColor));return t.size>0?t:null}amendSavedDict(e,t){}setValue(e,t,a,r){const{dict:i,ref:n}=function getParentToUpdate(e,t,a){const r=new RefSet,i=e,n={dict:null,ref:null};for(;e instanceof Dict&&!r.has(t);){r.put(t);if(e.has("T"))break;if(!((t=e.getRaw("Parent"))instanceof Ref))return n;e=a.fetch(t)}if(e instanceof Dict&&e!==i){n.dict=e;n.ref=t}return n}(e,this.ref,a);if(i){if(!r.has(n)){const e=i.clone();e.set("V",t);r.put(n,{data:e});return e}}else e.set("V",t);return null}async save(e,t,a,r){const i=a?.get(this.data.id),n=this._buildFlags(i?.noView,i?.noPrint);let s=i?.value,o=i?.rotation;if(s===this.data.fieldValue||void 0===s){if(!this._hasValueFromXFA&&void 0===o&&void 0===n)return;s||=this.data.fieldValue}if(void 0===o&&!this._hasValueFromXFA&&Array.isArray(s)&&Array.isArray(this.data.fieldValue)&&isArrayEqual(s,this.data.fieldValue)&&void 0===n)return;void 0===o&&(o=this.rotation);let l=null;if(!this._needAppearances){l=await this._getAppearance(e,t,c,a);if(null===l&&void 0===n)return}let h=!1;if(l?.needAppearances){h=!0;l=null}const{xref:u}=e,d=u.fetchIfRef(this.ref);if(!(d instanceof Dict))return;const f=new Dict(u);for(const e of d.getKeys())"AP"!==e&&f.set(e,d.getRaw(e));if(void 0!==n){f.set("F",n);if(null===l&&!h){const e=d.getRaw("AP");e&&f.set("AP",e)}}const g={path:this.data.fieldName,value:s},p=this.setValue(f,Array.isArray(s)?s.map(stringToAsciiOrUTF16BE):stringToAsciiOrUTF16BE(s),u,r);this.amendSavedDict(a,p||f);const m=this._getMKDict(o);m&&f.set("MK",m);r.put(this.ref,{data:f,xfa:g,needAppearances:h});if(null!==l){const e=u.getNewTemporaryRef(),t=new Dict(u);f.set("AP",t);t.set("N",e);const i=this._getSaveFieldResources(u),n=new StringStream(l),s=n.dict=new Dict(u);s.set("Subtype",Name.get("Form"));s.set("Resources",i);const c=o%180==0?[0,0,this.width,this.height]:[0,0,this.height,this.width];s.set("BBox",c);const h=this.getRotationMatrix(a);h!==Fa&&s.set("Matrix",h);r.put(e,{data:n,xfa:null,needAppearances:!1})}f.set("M",`D:${getModificationDate()}`)}async _getAppearance(e,t,a,r){if(this.data.password)return null;const n=r?.get(this.data.id);let s,o;if(n){s=n.formattedValue||n.value;o=n.rotation}if(void 0===o&&void 0===s&&!this._needAppearances&&(!this._hasValueFromXFA||this.appearance))return null;const l=this.getBorderAndBackgroundAppearances(r);if(void 0===s){s=this.data.fieldValue;if(!s)return`/Tx BMC q ${l}Q EMC`}Array.isArray(s)&&1===s.length&&(s=s[0]);assert("string"==typeof s,"Expected `value` to be a string.");s=s.trimEnd();if(this.data.combo){const e=this.data.options.find((({exportValue:e})=>s===e));s=e?.displayValue||s}if(""===s)return`/Tx BMC q ${l}Q EMC`;void 0===o&&(o=this.rotation);let h,u=-1;if(this.data.multiLine){h=s.split(/\r\n?|\n/).map((e=>e.normalize("NFC")));u=h.length}else h=[s.replace(/\r\n?|\n/,"").normalize("NFC")];let{width:d,height:f}=this;90!==o&&270!==o||([d,f]=[f,d]);this._defaultAppearance||(this.data.defaultAppearanceData=parseDefaultAppearance(this._defaultAppearance="/Helvetica 0 Tf 0 g"));let g,p,m,b=await WidgetAnnotation._getFontData(e,t,this.data.defaultAppearanceData,this._fieldResources.mergedResources);const y=[];let w=!1;for(const e of h){const t=b.encodeString(e);t.length>1&&(w=!0);y.push(t.join(""))}if(w&&a&c)return{needAppearances:!0};if(w&&this._isOffscreenCanvasSupported){const a=this.data.comb?"monospace":"sans-serif",r=new FakeUnicodeFont(e.xref,a),i=r.createFontResources(h.join("")),n=i.getRaw("Font");if(this._fieldResources.mergedResources.has("Font")){const e=this._fieldResources.mergedResources.get("Font");for(const t of n.getKeys())e.set(t,n.getRaw(t))}else this._fieldResources.mergedResources.set("Font",n);const o=r.fontName.name;b=await WidgetAnnotation._getFontData(e,t,{fontName:o,fontSize:0},i);for(let e=0,t=y.length;e2)return`/Tx BMC q ${l}BT `+g+` 1 0 0 1 ${numberToString(2)} ${numberToString(C)} Tm (${escapeString(y[0])}) Tj ET Q EMC`;return`/Tx BMC q ${l}BT `+g+` 1 0 0 1 0 0 Tm ${this._renderText(y[0],b,p,d,k,{shift:0},2,C)} ET Q EMC`}static async _getFontData(e,t,a,r){const i=new OperatorList,n={font:null,clone(){return this}},{fontName:s,fontSize:o}=a;await e.handleSetFont(r,[s&&Name.get(s),o],null,i,t,n,null);return n.font}_getTextWidth(e,t){return Math.sumPrecise(t.charsToGlyphs(e).map((e=>e.width)))/1e3}_computeFontSize(e,t,r,i,n){let{fontSize:s}=this.data.defaultAppearanceData,o=(s||12)*a,c=Math.round(e/o);if(!s){const roundWithTwoDigits=e=>Math.floor(100*e)/100;if(-1===n){const n=this._getTextWidth(r,i);s=roundWithTwoDigits(Math.min(e/a,t/n));c=1}else{const l=r.split(/\r\n?|\n/),h=[];for(const e of l){const t=i.encodeString(e).join(""),a=i.charsToGlyphs(t),r=i.getCharPositions(t);h.push({line:t,glyphs:a,positions:r})}const isTooBig=a=>{let r=0;for(const n of h){r+=this._splitLine(null,i,a,t,n).length*a;if(r>e)return!0}return!1};c=Math.max(c,n);for(;;){o=e/c;s=roundWithTwoDigits(o/a);if(!isTooBig(s))break;c++}}const{fontName:l,fontColor:h}=this.data.defaultAppearanceData;this._defaultAppearance=function createDefaultAppearance({fontSize:e,fontName:t,fontColor:a}){return`/${escapePDFName(t)} ${e} Tf ${getPdfColor(a,!0)}`}({fontSize:s,fontName:l,fontColor:h})}return[this._defaultAppearance,s,e/c]}_renderText(e,t,a,r,i,n,s,o){let c;if(1===i){c=(r-this._getTextWidth(e,t)*a)/2}else if(2===i){c=r-this._getTextWidth(e,t)*a-s}else c=s;const l=numberToString(c-n.shift);n.shift=c;return`${l} ${o=numberToString(o)} Td (${escapeString(e)}) Tj`}_getSaveFieldResources(e){const{localResources:t,appearanceResources:a,acroFormResources:r}=this._fieldResources,i=this.data.defaultAppearanceData?.fontName;if(!i)return t||Dict.empty;for(const e of[t,a])if(e instanceof Dict){const t=e.get("Font");if(t instanceof Dict&&t.has(i))return e}if(r instanceof Dict){const a=r.get("Font");if(a instanceof Dict&&a.has(i)){const r=new Dict(e);r.set(i,a.getRaw(i));const n=new Dict(e);n.set("Font",r);return Dict.merge({xref:e,dictArray:[n,t],mergeSubDicts:!0})}}return t||Dict.empty}getFieldObject(){return null}}class TextWidgetAnnotation extends WidgetAnnotation{constructor(e){super(e);const{dict:t}=e;if(t.has("PMD")){this.flags|=J;this.data.hidden=!0;warn("Barcodes are not supported")}this.data.hasOwnCanvas=this.data.readOnly&&!this.data.noHTML;this._hasText=!0;"string"!=typeof this.data.fieldValue&&(this.data.fieldValue="");let a=getInheritableProperty({dict:t,key:"Q"});(!Number.isInteger(a)||a<0||a>2)&&(a=null);this.data.textAlignment=a;let r=getInheritableProperty({dict:t,key:"MaxLen"});(!Number.isInteger(r)||r<0)&&(r=0);this.data.maxLen=r;this.data.multiLine=this.hasFieldFlag(ie);this.data.comb=this.hasFieldFlag(de)&&!this.data.multiLine&&!this.data.password&&!this.hasFieldFlag(le)&&0!==this.data.maxLen;this.data.doNotScroll=this.hasFieldFlag(ue)}get hasTextContent(){return!!this.appearance&&!this._needAppearances}_getCombAppearance(e,t,a,r,i,n,s,o,c,l,h){const u=i/this.data.maxLen,d=this.getBorderAndBackgroundAppearances(h),f=[],g=t.getCharPositions(a);for(const[e,t]of g)f.push(`(${escapeString(a.substring(e,t))}) Tj`);const p=f.join(` ${numberToString(u)} 0 Td `);return`/Tx BMC q ${d}BT `+e+` 1 0 0 1 ${numberToString(s)} ${numberToString(o+c)} Tm ${p} ET Q EMC`}_getMultilineAppearance(e,t,a,r,i,n,s,o,c,l,h,u){const d=[],f=i-2*o,g={shift:0};for(let e=0,n=t.length;er){c.push(e.substring(d,a));d=a;f=p;l=-1;u=-1}else{f+=p;l=a;h=i;u=t}else if(f+p>r)if(-1!==l){c.push(e.substring(d,h));d=h;t=u+1;l=-1;f=0}else{c.push(e.substring(d,a));d=a;f=p}else f+=p}dt?`\\${t}`:"\\s+"));new RegExp(`^\\s*${n}\\s*$`).test(this.data.fieldValue)&&(this.data.textContent=this.data.fieldValue.split("\n"))}getFieldObject(){return{id:this.data.id,value:this.data.fieldValue,defaultValue:this.data.defaultFieldValue||"",multiline:this.data.multiLine,password:this.data.password,charLimit:this.data.maxLen,comb:this.data.comb,editable:!this.data.readOnly,hidden:this.data.hidden,name:this.data.fieldName,rect:this.data.rect,actions:this.data.actions,page:this.data.pageIndex,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,rotation:this.rotation,type:"text"}}}class ButtonWidgetAnnotation extends WidgetAnnotation{constructor(e){super(e);this.checkedAppearance=null;this.uncheckedAppearance=null;const t=this.hasFieldFlag(se),a=this.hasFieldFlag(oe);this.data.checkBox=!t&&!a;this.data.radioButton=t&&!a;this.data.pushButton=a;this.data.isTooltipOnly=!1;if(this.data.checkBox)this._processCheckBox(e);else if(this.data.radioButton)this._processRadioButton(e);else if(this.data.pushButton){this.data.hasOwnCanvas=!0;this.data.noHTML=!1;this._processPushButton(e)}else warn("Invalid field flags for button widget annotation")}async getOperatorList(e,t,a,r){if(this.data.pushButton)return super.getOperatorList(e,t,a,!1,r);let i=null,n=null;if(r){const e=r.get(this.data.id);i=e?e.value:null;n=e?e.rotation:null}if(null===i&&this.appearance)return super.getOperatorList(e,t,a,r);null==i&&(i=this.data.checkBox?this.data.fieldValue===this.data.exportValue:this.data.fieldValue===this.data.buttonValue);const s=i?this.checkedAppearance:this.uncheckedAppearance;if(s){const i=this.appearance,o=lookupMatrix(s.dict.getArray("Matrix"),Fa);n&&s.dict.set("Matrix",this.getRotationMatrix(r));this.appearance=s;const c=super.getOperatorList(e,t,a,r);this.appearance=i;s.dict.set("Matrix",o);return c}return{opList:new OperatorList,separateForm:!1,separateCanvas:!1}}async save(e,t,a,r){this.data.checkBox?this._saveCheckbox(e,t,a,r):this.data.radioButton&&this._saveRadioButton(e,t,a,r)}async _saveCheckbox(e,t,a,r){if(!a)return;const i=a.get(this.data.id),n=this._buildFlags(i?.noView,i?.noPrint);let s=i?.rotation,o=i?.value;if(void 0===s&&void 0===n){if(void 0===o)return;if(this.data.fieldValue===this.data.exportValue===o)return}let c=e.xref.fetchIfRef(this.ref);if(!(c instanceof Dict))return;c=c.clone();void 0===s&&(s=this.rotation);void 0===o&&(o=this.data.fieldValue===this.data.exportValue);const l={path:this.data.fieldName,value:o?this.data.exportValue:""},h=Name.get(o?this.data.exportValue:"Off");this.setValue(c,h,e.xref,r);c.set("AS",h);c.set("M",`D:${getModificationDate()}`);void 0!==n&&c.set("F",n);const u=this._getMKDict(s);u&&c.set("MK",u);r.put(this.ref,{data:c,xfa:l,needAppearances:!1})}async _saveRadioButton(e,t,a,r){if(!a)return;const i=a.get(this.data.id),n=this._buildFlags(i?.noView,i?.noPrint);let s=i?.rotation,o=i?.value;if(void 0===s&&void 0===n){if(void 0===o)return;if(this.data.fieldValue===this.data.buttonValue===o)return}let c=e.xref.fetchIfRef(this.ref);if(!(c instanceof Dict))return;c=c.clone();void 0===o&&(o=this.data.fieldValue===this.data.buttonValue);void 0===s&&(s=this.rotation);const l={path:this.data.fieldName,value:o?this.data.buttonValue:""},h=Name.get(o?this.data.buttonValue:"Off");o&&this.setValue(c,h,e.xref,r);c.set("AS",h);c.set("M",`D:${getModificationDate()}`);void 0!==n&&c.set("F",n);const u=this._getMKDict(s);u&&c.set("MK",u);r.put(this.ref,{data:c,xfa:l,needAppearances:!1})}_getDefaultCheckedAppearance(e,t){const{width:a,height:r}=this,i=[0,0,a,r],n=.8*Math.min(a,r);let s,o;if("check"===t){s={width:.755*n,height:.705*n};o="3"}else if("disc"===t){s={width:.791*n,height:.705*n};o="l"}else unreachable(`_getDefaultCheckedAppearance - unsupported type: ${t}`);const c=`q BT /PdfJsZaDb ${n} Tf 0 g ${numberToString((a-s.width)/2)} ${numberToString((r-s.height)/2)} Td (${o}) Tj ET Q`,l=new Dict(e.xref);l.set("FormType",1);l.set("Subtype",Name.get("Form"));l.set("Type",Name.get("XObject"));l.set("BBox",i);l.set("Matrix",[1,0,0,1,0,0]);l.set("Length",c.length);const h=new Dict(e.xref),u=new Dict(e.xref);u.set("PdfJsZaDb",this.fallbackFontDict);h.set("Font",u);l.set("Resources",h);this.checkedAppearance=new StringStream(c);this.checkedAppearance.dict=l;this._streams.push(this.checkedAppearance)}_processCheckBox(e){const t=e.dict.get("AP");if(!(t instanceof Dict))return;const a=t.get("N");if(!(a instanceof Dict))return;const r=this._decodeFormValue(e.dict.get("AS"));"string"==typeof r&&(this.data.fieldValue=r);const i=null!==this.data.fieldValue&&"Off"!==this.data.fieldValue?this.data.fieldValue:"Yes",n=this._decodeFormValue(a.getKeys());if(0===n.length)n.push("Off",i);else if(1===n.length)"Off"===n[0]?n.push(i):n.unshift("Off");else if(n.includes(i)){n.length=0;n.push("Off",i)}else{const e=n.find((e=>"Off"!==e));n.length=0;n.push("Off",e)}n.includes(this.data.fieldValue)||(this.data.fieldValue="Off");this.data.exportValue=n[1];const s=a.get(this.data.exportValue);this.checkedAppearance=s instanceof BaseStream?s:null;const o=a.get("Off");this.uncheckedAppearance=o instanceof BaseStream?o:null;this.checkedAppearance?this._streams.push(this.checkedAppearance):this._getDefaultCheckedAppearance(e,"check");this.uncheckedAppearance&&this._streams.push(this.uncheckedAppearance);this._fallbackFontDict=this.fallbackFontDict;null===this.data.defaultFieldValue&&(this.data.defaultFieldValue="Off")}_processRadioButton(e){this.data.buttonValue=null;const t=e.dict.get("Parent");if(t instanceof Dict){this.parent=e.dict.getRaw("Parent");const a=t.get("V");a instanceof Name&&(this.data.fieldValue=this._decodeFormValue(a))}const a=e.dict.get("AP");if(!(a instanceof Dict))return;const r=a.get("N");if(!(r instanceof Dict))return;for(const e of r.getKeys())if("Off"!==e){this.data.buttonValue=this._decodeFormValue(e);break}const i=r.get(this.data.buttonValue);this.checkedAppearance=i instanceof BaseStream?i:null;const n=r.get("Off");this.uncheckedAppearance=n instanceof BaseStream?n:null;this.checkedAppearance?this._streams.push(this.checkedAppearance):this._getDefaultCheckedAppearance(e,"disc");this.uncheckedAppearance&&this._streams.push(this.uncheckedAppearance);this._fallbackFontDict=this.fallbackFontDict;null===this.data.defaultFieldValue&&(this.data.defaultFieldValue="Off")}_processPushButton(e){const{dict:t,annotationGlobals:a}=e;if(t.has("A")||t.has("AA")||this.data.alternativeText){this.data.isTooltipOnly=!t.has("A")&&!t.has("AA");Catalog.parseDestDictionary({destDict:t,resultObj:this.data,docBaseUrl:a.baseUrl,docAttachments:a.attachments})}else warn("Push buttons without action dictionaries are not supported")}getFieldObject(){let e,t="button";if(this.data.checkBox){t="checkbox";e=this.data.exportValue}else if(this.data.radioButton){t="radiobutton";e=this.data.buttonValue}return{id:this.data.id,value:this.data.fieldValue||"Off",defaultValue:this.data.defaultFieldValue,exportValues:e,editable:!this.data.readOnly,name:this.data.fieldName,rect:this.data.rect,hidden:this.data.hidden,actions:this.data.actions,page:this.data.pageIndex,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,rotation:this.rotation,type:t}}get fallbackFontDict(){const e=new Dict;e.set("BaseFont",Name.get("ZapfDingbats"));e.set("Type",Name.get("FallbackType"));e.set("Subtype",Name.get("FallbackType"));e.set("Encoding",Name.get("ZapfDingbatsEncoding"));return shadow(this,"fallbackFontDict",e)}}class ChoiceWidgetAnnotation extends WidgetAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.indices=t.getArray("I");this.hasIndices=Array.isArray(this.indices)&&this.indices.length>0;this.data.options=[];const r=getInheritableProperty({dict:t,key:"Opt"});if(Array.isArray(r))for(let e=0,t=r.length;e=0&&t0&&(this.data.options=this.data.fieldValue.map((e=>({exportValue:e,displayValue:e}))));this.data.combo=this.hasFieldFlag(ce);this.data.multiSelect=this.hasFieldFlag(he);this._hasText=!0}getFieldObject(){const e=this.data.combo?"combobox":"listbox",t=this.data.fieldValue.length>0?this.data.fieldValue[0]:null;return{id:this.data.id,value:t,defaultValue:this.data.defaultFieldValue,editable:!this.data.readOnly,name:this.data.fieldName,rect:this.data.rect,numItems:this.data.fieldValue.length,multipleSelection:this.data.multiSelect,hidden:this.data.hidden,actions:this.data.actions,items:this.data.options,page:this.data.pageIndex,strokeColor:this.data.borderColor,fillColor:this.data.backgroundColor,rotation:this.rotation,type:e}}amendSavedDict(e,t){if(!this.hasIndices)return;let a=e?.get(this.data.id)?.value;Array.isArray(a)||(a=[a]);const r=[],{options:i}=this.data;for(let e=0,t=0,n=i.length;ea){a=r;t=e}}[f,g]=this._computeFontSize(e,c-4,t,d,-1)}const p=g*a,m=(p-g)/2,b=Math.floor(l/p);let y=0;if(u.length>0){const e=Math.min(...u),t=Math.max(...u);y=Math.max(0,t-b+1);y>e&&(y=e)}const w=Math.min(y+b+1,h),S=["/Tx BMC q",`1 1 ${c} ${l} re W n`];if(u.length){S.push("0.600006 0.756866 0.854904 rg");for(const e of u)y<=e&&ee.trimEnd()));const{coords:e,bbox:t,matrix:r}=FakeUnicodeFont.getFirstPositionInfo(this.rectangle,this.rotation,a);this.data.textPosition=this._transformPoint(e,t,r)}if(this._isOffscreenCanvasSupported){const i=e.dict.get("CA"),n=new FakeUnicodeFont(r,"sans-serif");this.appearance=n.createAppearance(this._contents.str,this.rectangle,this.rotation,a,t,i);this._streams.push(this.appearance)}else warn("FreeTextAnnotation: OffscreenCanvas is not supported, annotation may not render correctly.")}}get hasTextContent(){return this._hasAppearance}static createNewDict(e,t,{apRef:a,ap:r}){const{color:i,fontSize:n,oldAnnotation:s,rect:o,rotation:c,user:l,value:h}=e,u=s||new Dict(t);u.set("Type",Name.get("Annot"));u.set("Subtype",Name.get("FreeText"));if(s){u.set("M",`D:${getModificationDate()}`);u.delete("RC")}else u.set("CreationDate",`D:${getModificationDate()}`);u.set("Rect",o);const d=`/Helv ${n} Tf ${getPdfColor(i,!0)}`;u.set("DA",d);u.set("Contents",stringToAsciiOrUTF16BE(h));u.set("F",4);u.set("Border",[0,0,0]);u.set("Rotate",c);l&&u.set("T",stringToAsciiOrUTF16BE(l));if(a||r){const e=new Dict(t);u.set("AP",e);a?e.set("N",a):e.set("N",r)}return u}static async createNewAppearanceStream(e,t,r){const{baseFontRef:i,evaluator:n,task:s}=r,{color:o,fontSize:c,rect:l,rotation:h,value:u}=e,d=new Dict(t),f=new Dict(t);if(i)f.set("Helv",i);else{const e=new Dict(t);e.set("BaseFont",Name.get("Helvetica"));e.set("Type",Name.get("Font"));e.set("Subtype",Name.get("Type1"));e.set("Encoding",Name.get("WinAnsiEncoding"));f.set("Helv",e)}d.set("Font",f);const g=await WidgetAnnotation._getFontData(n,s,{fontName:"Helv",fontSize:c},d),[p,m,b,y]=l;let w=b-p,S=y-m;h%180!=0&&([w,S]=[S,w]);const x=u.split("\n"),k=c/1e3;let C=-1/0;const v=[];for(let e of x){const t=g.encodeString(e);if(t.length>1)return null;e=t.join("");v.push(e);let a=0;const r=g.charsToGlyphs(e);for(const e of r)a+=e.width*k;C=Math.max(C,a)}let F=1;C>w&&(F=w/C);let O=1;const T=a*c,M=1*c,D=T*x.length;D>S&&(O=S/D);const R=c*Math.min(F,O);let N,E,L;switch(h){case 0:L=[1,0,0,1];E=[l[0],l[1],w,S];N=[l[0],l[3]-M];break;case 90:L=[0,1,-1,0];E=[l[1],-l[2],w,S];N=[l[1],-l[0]-M];break;case 180:L=[-1,0,0,-1];E=[-l[2],-l[3],w,S];N=[-l[2],-l[1]-M];break;case 270:L=[0,-1,1,0];E=[-l[3],l[0],w,S];N=[-l[3],l[2]-M]}const j=["q",`${L.join(" ")} 0 0 cm`,`${E.join(" ")} re W n`,"BT",`${getPdfColor(o,!0)}`,`0 Tc /Helv ${numberToString(R)} Tf`];j.push(`${N.join(" ")} Td (${escapeString(v[0])}) Tj`);const _=numberToString(T);for(let e=1,t=v.length;e{e.push(`${r[0]} ${r[1]} m`,`${r[2]} ${r[3]} l`,"S");return[t[0]-c,t[7]-c,t[2]+c,t[3]+c]}})}}}class SquareAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=D;this.data.hasOwnCanvas=this.data.noRotate;this.data.noHTML=!1;if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA"),i=getRgbColor(t.getArray("IC"),null),n=i?getPdfColorArray(i):null,s=n?r:null;if(0===this.borderStyle.width&&!n)return;this._setDefaultAppearance({xref:a,extra:`${this.borderStyle.width} w`,strokeColor:e,fillColor:n,strokeAlpha:r,fillAlpha:s,pointsCallback:(e,t)=>{const a=t[4]+this.borderStyle.width/2,r=t[5]+this.borderStyle.width/2,i=t[6]-t[4]-this.borderStyle.width,s=t[3]-t[7]-this.borderStyle.width;e.push(`${a} ${r} ${i} ${s} re`);n?e.push("B"):e.push("S");return[t[0],t[7],t[2],t[3]]}})}}}class CircleAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=R;if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA"),i=getRgbColor(t.getArray("IC"),null),n=i?getPdfColorArray(i):null,s=n?r:null;if(0===this.borderStyle.width&&!n)return;const o=4/3*Math.tan(Math.PI/8);this._setDefaultAppearance({xref:a,extra:`${this.borderStyle.width} w`,strokeColor:e,fillColor:n,strokeAlpha:r,fillAlpha:s,pointsCallback:(e,t)=>{const a=t[0]+this.borderStyle.width/2,r=t[1]-this.borderStyle.width/2,i=t[6]-this.borderStyle.width/2,s=t[7]+this.borderStyle.width/2,c=a+(i-a)/2,l=r+(s-r)/2,h=(i-a)/2*o,u=(s-r)/2*o;e.push(`${c} ${s} m`,`${c+h} ${s} ${i} ${l+u} ${i} ${l} c`,`${i} ${l-u} ${c+h} ${r} ${c} ${r} c`,`${c-h} ${r} ${a} ${l-u} ${a} ${l} c`,`${a} ${l+u} ${c-h} ${s} ${c} ${s} c`,"h");n?e.push("B"):e.push("S");return[t[0],t[7],t[2],t[3]]}})}}}class PolylineAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=E;this.data.hasOwnCanvas=this.data.noRotate;this.data.noHTML=!1;this.data.vertices=null;if(!(this instanceof PolygonAnnotation)){this.setLineEndings(t.getArray("LE"));this.data.lineEndings=this.lineEndings}const r=t.getArray("Vertices");if(!isNumberArray(r,null))return;const i=this.data.vertices=Float32Array.from(r);if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA"),n=this.borderStyle.width||1,s=2*n,o=[1/0,1/0,-1/0,-1/0];for(let e=0,t=i.length;e{for(let t=0,a=i.length;t{for(const t of this.data.inkLists){for(let a=0,r=t.length;a{e.push(`${t[0]} ${t[1]} m`,`${t[2]} ${t[3]} l`,`${t[6]} ${t[7]} l`,`${t[4]} ${t[5]} l`,"f");return[t[0],t[7],t[2],t[3]]}})}}else this.data.popupRef=null}static createNewDict(e,t,{apRef:a,ap:r}){const{color:i,oldAnnotation:n,opacity:s,rect:o,rotation:c,user:l,quadPoints:h}=e,u=n||new Dict(t);u.set("Type",Name.get("Annot"));u.set("Subtype",Name.get("Highlight"));u.set(n?"M":"CreationDate",`D:${getModificationDate()}`);u.set("CreationDate",`D:${getModificationDate()}`);u.set("Rect",o);u.set("F",4);u.set("Border",[0,0,0]);u.set("Rotate",c);u.set("QuadPoints",h);u.set("C",getPdfColorArray(i));u.set("CA",s);l&&u.set("T",stringToAsciiOrUTF16BE(l));if(a||r){const e=new Dict(t);u.set("AP",e);e.set("N",a||r)}return u}static async createNewAppearanceStream(e,t,a){const{color:r,rect:i,outlines:n,opacity:s}=e,o=[`${getPdfColor(r,!0)}`,"/R0 gs"],c=[];for(const e of n){c.length=0;c.push(`${numberToString(e[0])} ${numberToString(e[1])} m`);for(let t=2,a=e.length;t{e.push(`${t[4]} ${t[5]+1.3} m`,`${t[6]} ${t[7]+1.3} l`,"S");return[t[0],t[7],t[2],t[3]]}})}}else this.data.popupRef=null}}class SquigglyAnnotation extends MarkupAnnotation{constructor(e){super(e);const{dict:t,xref:a}=e;this.data.annotationType=_;if(this.data.quadPoints=getQuadPoints(t,null)){if(!this.appearance){const e=this.color?getPdfColorArray(this.color):[0,0,0],r=t.get("CA");this._setDefaultAppearance({xref:a,extra:"[] 0 d 1 w",strokeColor:e,strokeAlpha:r,pointsCallback:(e,t)=>{const a=(t[1]-t[5])/6;let r=a,i=t[4];const n=t[5],s=t[6];e.push(`${i} ${n+r} m`);do{i+=2;r=0===r?a:0;e.push(`${i} ${n+r} l`)}while(i{e.push((t[0]+t[4])/2+" "+(t[1]+t[5])/2+" m",(t[2]+t[6])/2+" "+(t[3]+t[7])/2+" l","S");return[t[0],t[7],t[2],t[3]]}})}}else this.data.popupRef=null}}class StampAnnotation extends MarkupAnnotation{#pe=null;constructor(e){super(e);this.data.annotationType=X;this.data.hasOwnCanvas=this.data.noRotate;this.data.isEditable=!this.data.noHTML;this.data.noHTML=!1}mustBeViewedWhenEditing(e,t=null){if(e){if(!this.data.isEditable)return!0;this.#pe??=this.data.hasOwnCanvas;this.data.hasOwnCanvas=!0;return!0}if(null!==this.#pe){this.data.hasOwnCanvas=this.#pe;this.#pe=null}return!t?.has(this.data.id)}static async createImage(e,t){const{width:a,height:r}=e,i=new OffscreenCanvas(a,r),n=i.getContext("2d",{alpha:!0});n.drawImage(e,0,0);const s=n.getImageData(0,0,a,r).data,o=new Uint32Array(s.buffer),c=o.some(FeatureTest.isLittleEndian?e=>e>>>24!=255:e=>!!(255&~e));if(c){n.fillStyle="white";n.fillRect(0,0,a,r);n.drawImage(e,0,0)}const l=i.convertToBlob({type:"image/jpeg",quality:1}).then((e=>e.arrayBuffer())),h=Name.get("XObject"),u=Name.get("Image"),d=new Dict(t);d.set("Type",h);d.set("Subtype",u);d.set("BitsPerComponent",8);d.set("ColorSpace",Name.get("DeviceRGB"));d.set("Filter",Name.get("DCTDecode"));d.set("BBox",[0,0,a,r]);d.set("Width",a);d.set("Height",r);let f=null;if(c){const e=new Uint8Array(o.length);if(FeatureTest.isLittleEndian)for(let t=0,a=o.length;t>>24;else for(let t=0,a=o.length;t=0&&n<=1?n:null}}const fc={get r(){return shadow(this,"r",new Uint8Array([7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21]))},get k(){return shadow(this,"k",new Int32Array([-680876936,-389564586,606105819,-1044525330,-176418897,1200080426,-1473231341,-45705983,1770035416,-1958414417,-42063,-1990404162,1804603682,-40341101,-1502002290,1236535329,-165796510,-1069501632,643717713,-373897302,-701558691,38016083,-660478335,-405537848,568446438,-1019803690,-187363961,1163531501,-1444681467,-51403784,1735328473,-1926607734,-378558,-2022574463,1839030562,-35309556,-1530992060,1272893353,-155497632,-1094730640,681279174,-358537222,-722521979,76029189,-640364487,-421815835,530742520,-995338651,-198630844,1126891415,-1416354905,-57434055,1700485571,-1894986606,-1051523,-2054922799,1873313359,-30611744,-1560198380,1309151649,-145523070,-1120210379,718787259,-343485551]))}};function calculateMD5(e,t,a){let r=1732584193,i=-271733879,n=-1732584194,s=271733878;const o=a+72&-64,c=new Uint8Array(o);let l,h;for(l=0;l>5&255;c[l++]=a>>13&255;c[l++]=a>>21&255;c[l++]=a>>>29&255;l+=3;const d=new Int32Array(16),{k:f,r:g}=fc;for(l=0;l>>32-n)|0;a=r}r=r+a|0;i=i+o|0;n=n+u|0;s=s+p|0}return new Uint8Array([255&r,r>>8&255,r>>16&255,r>>>24&255,255&i,i>>8&255,i>>16&255,i>>>24&255,255&n,n>>8&255,n>>16&255,n>>>24&255,255&s,s>>8&255,s>>16&255,s>>>24&255])}function decodeString(e){try{return stringToUTF8String(e)}catch(t){warn(`UTF-8 decoding failed: "${t}".`);return e}}class DatasetXMLParser extends SimpleXMLParser{constructor(e){super(e);this.node=null}onEndElement(e){const t=super.onEndElement(e);if(t&&"xfa:datasets"===e){this.node=t;throw new Error("Aborting DatasetXMLParser.")}}}class DatasetReader{constructor(e){if(e.datasets)this.node=new SimpleXMLParser({hasAttributes:!0}).parseFromString(e.datasets).documentElement;else{const t=new DatasetXMLParser({hasAttributes:!0});try{t.parseFromString(e["xdp:xdp"])}catch{}this.node=t.node}}getValue(e){if(!this.node||!e)return"";const t=this.node.searchNode(parseXFAPath(e),0);if(!t)return"";const a=t.firstChild;return"value"===a?.nodeName?t.children.map((e=>decodeString(e.textContent))):decodeString(t.textContent)}}class Word64{constructor(e,t){this.high=0|e;this.low=0|t}and(e){this.high&=e.high;this.low&=e.low}xor(e){this.high^=e.high;this.low^=e.low}shiftRight(e){if(e>=32){this.low=this.high>>>e-32|0;this.high=0}else{this.low=this.low>>>e|this.high<<32-e;this.high=this.high>>>e|0}}rotateRight(e){let t,a;if(32&e){a=this.low;t=this.high}else{t=this.low;a=this.high}e&=31;this.low=t>>>e|a<<32-e;this.high=a>>>e|t<<32-e}not(){this.high=~this.high;this.low=~this.low}add(e){const t=(this.low>>>0)+(e.low>>>0);let a=(this.high>>>0)+(e.high>>>0);t>4294967295&&(a+=1);this.low=0|t;this.high=0|a}copyTo(e,t){e[t]=this.high>>>24&255;e[t+1]=this.high>>16&255;e[t+2]=this.high>>8&255;e[t+3]=255&this.high;e[t+4]=this.low>>>24&255;e[t+5]=this.low>>16&255;e[t+6]=this.low>>8&255;e[t+7]=255&this.low}assign(e){this.high=e.high;this.low=e.low}}const gc={get k(){return shadow(this,"k",[new Word64(1116352408,3609767458),new Word64(1899447441,602891725),new Word64(3049323471,3964484399),new Word64(3921009573,2173295548),new Word64(961987163,4081628472),new Word64(1508970993,3053834265),new Word64(2453635748,2937671579),new Word64(2870763221,3664609560),new Word64(3624381080,2734883394),new Word64(310598401,1164996542),new Word64(607225278,1323610764),new Word64(1426881987,3590304994),new Word64(1925078388,4068182383),new Word64(2162078206,991336113),new Word64(2614888103,633803317),new Word64(3248222580,3479774868),new Word64(3835390401,2666613458),new Word64(4022224774,944711139),new Word64(264347078,2341262773),new Word64(604807628,2007800933),new Word64(770255983,1495990901),new Word64(1249150122,1856431235),new Word64(1555081692,3175218132),new Word64(1996064986,2198950837),new Word64(2554220882,3999719339),new Word64(2821834349,766784016),new Word64(2952996808,2566594879),new Word64(3210313671,3203337956),new Word64(3336571891,1034457026),new Word64(3584528711,2466948901),new Word64(113926993,3758326383),new Word64(338241895,168717936),new Word64(666307205,1188179964),new Word64(773529912,1546045734),new Word64(1294757372,1522805485),new Word64(1396182291,2643833823),new Word64(1695183700,2343527390),new Word64(1986661051,1014477480),new Word64(2177026350,1206759142),new Word64(2456956037,344077627),new Word64(2730485921,1290863460),new Word64(2820302411,3158454273),new Word64(3259730800,3505952657),new Word64(3345764771,106217008),new Word64(3516065817,3606008344),new Word64(3600352804,1432725776),new Word64(4094571909,1467031594),new Word64(275423344,851169720),new Word64(430227734,3100823752),new Word64(506948616,1363258195),new Word64(659060556,3750685593),new Word64(883997877,3785050280),new Word64(958139571,3318307427),new Word64(1322822218,3812723403),new Word64(1537002063,2003034995),new Word64(1747873779,3602036899),new Word64(1955562222,1575990012),new Word64(2024104815,1125592928),new Word64(2227730452,2716904306),new Word64(2361852424,442776044),new Word64(2428436474,593698344),new Word64(2756734187,3733110249),new Word64(3204031479,2999351573),new Word64(3329325298,3815920427),new Word64(3391569614,3928383900),new Word64(3515267271,566280711),new Word64(3940187606,3454069534),new Word64(4118630271,4000239992),new Word64(116418474,1914138554),new Word64(174292421,2731055270),new Word64(289380356,3203993006),new Word64(460393269,320620315),new Word64(685471733,587496836),new Word64(852142971,1086792851),new Word64(1017036298,365543100),new Word64(1126000580,2618297676),new Word64(1288033470,3409855158),new Word64(1501505948,4234509866),new Word64(1607167915,987167468),new Word64(1816402316,1246189591)])}};function ch(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.not();i.and(r);e.xor(i)}function maj(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.and(r);e.xor(i);i.assign(a);i.and(r);e.xor(i)}function sigma(e,t,a){e.assign(t);e.rotateRight(28);a.assign(t);a.rotateRight(34);e.xor(a);a.assign(t);a.rotateRight(39);e.xor(a)}function sigmaPrime(e,t,a){e.assign(t);e.rotateRight(14);a.assign(t);a.rotateRight(18);e.xor(a);a.assign(t);a.rotateRight(41);e.xor(a)}function littleSigma(e,t,a){e.assign(t);e.rotateRight(1);a.assign(t);a.rotateRight(8);e.xor(a);a.assign(t);a.shiftRight(7);e.xor(a)}function littleSigmaPrime(e,t,a){e.assign(t);e.rotateRight(19);a.assign(t);a.rotateRight(61);e.xor(a);a.assign(t);a.shiftRight(6);e.xor(a)}function calculateSHA512(e,t,a,r=!1){let i,n,s,o,c,l,h,u;if(r){i=new Word64(3418070365,3238371032);n=new Word64(1654270250,914150663);s=new Word64(2438529370,812702999);o=new Word64(355462360,4144912697);c=new Word64(1731405415,4290775857);l=new Word64(2394180231,1750603025);h=new Word64(3675008525,1694076839);u=new Word64(1203062813,3204075428)}else{i=new Word64(1779033703,4089235720);n=new Word64(3144134277,2227873595);s=new Word64(1013904242,4271175723);o=new Word64(2773480762,1595750129);c=new Word64(1359893119,2917565137);l=new Word64(2600822924,725511199);h=new Word64(528734635,4215389547);u=new Word64(1541459225,327033209)}const d=128*Math.ceil((a+17)/128),f=new Uint8Array(d);let g,p;for(g=0;g>>29&255;f[g++]=a>>21&255;f[g++]=a>>13&255;f[g++]=a>>5&255;f[g++]=a<<3&255;const b=new Array(80);for(g=0;g<80;g++)b[g]=new Word64(0,0);const{k:y}=gc;let w=new Word64(0,0),S=new Word64(0,0),x=new Word64(0,0),k=new Word64(0,0),C=new Word64(0,0),v=new Word64(0,0),F=new Word64(0,0),O=new Word64(0,0);const T=new Word64(0,0),M=new Word64(0,0),D=new Word64(0,0),R=new Word64(0,0);let N,E;for(g=0;g>>t|e<<32-t}function calculate_sha256_ch(e,t,a){return e&t^~e&a}function calculate_sha256_maj(e,t,a){return e&t^e&a^t&a}function calculate_sha256_sigma(e){return rotr(e,2)^rotr(e,13)^rotr(e,22)}function calculate_sha256_sigmaPrime(e){return rotr(e,6)^rotr(e,11)^rotr(e,25)}function calculate_sha256_littleSigma(e){return rotr(e,7)^rotr(e,18)^e>>>3}function calculateSHA256(e,t,a){let r=1779033703,i=3144134277,n=1013904242,s=2773480762,o=1359893119,c=2600822924,l=528734635,h=1541459225;const u=64*Math.ceil((a+9)/64),d=new Uint8Array(u);let f,g;for(f=0;f>>29&255;d[f++]=a>>21&255;d[f++]=a>>13&255;d[f++]=a>>5&255;d[f++]=a<<3&255;const m=new Uint32Array(64),{k:b}=pc;for(f=0;f>>10)+m[g-7]+calculate_sha256_littleSigma(m[g-15])+m[g-16]|0;let e,t,a=r,u=i,p=n,w=s,S=o,x=c,k=l,C=h;for(g=0;g<64;++g){e=C+calculate_sha256_sigmaPrime(S)+calculate_sha256_ch(S,x,k)+b[g]+m[g];t=calculate_sha256_sigma(a)+calculate_sha256_maj(a,u,p);C=k;k=x;x=S;S=w+e|0;w=p;p=u;u=a;a=e+t|0}r=r+a|0;i=i+u|0;n=n+p|0;s=s+w|0;o=o+S|0;c=c+x|0;l=l+k|0;h=h+C|0}var y;return new Uint8Array([r>>24&255,r>>16&255,r>>8&255,255&r,i>>24&255,i>>16&255,i>>8&255,255&i,n>>24&255,n>>16&255,n>>8&255,255&n,s>>24&255,s>>16&255,s>>8&255,255&s,o>>24&255,o>>16&255,o>>8&255,255&o,c>>24&255,c>>16&255,c>>8&255,255&c,l>>24&255,l>>16&255,l>>8&255,255&l,h>>24&255,h>>16&255,h>>8&255,255&h])}class DecryptStream extends DecodeStream{constructor(e,t,a){super(t);this.str=e;this.dict=e.dict;this.decrypt=a;this.nextChunk=null;this.initialized=!1}readBlock(){let e;if(this.initialized)e=this.nextChunk;else{e=this.str.getBytes(512);this.initialized=!0}if(!e?.length){this.eof=!0;return}this.nextChunk=this.str.getBytes(512);const t=this.nextChunk?.length>0;e=(0,this.decrypt)(e,!t);const a=this.bufferLength,r=a+e.length;this.ensureBuffer(r).set(e,a);this.bufferLength=r}}class ARCFourCipher{constructor(e){this.a=0;this.b=0;const t=new Uint8Array(256),a=e.length;for(let e=0;e<256;++e)t[e]=e;for(let r=0,i=0;r<256;++r){const n=t[r];i=i+n+e[r%a]&255;t[r]=t[i];t[i]=n}this.s=t}encryptBlock(e){let t=this.a,a=this.b;const r=this.s,i=e.length,n=new Uint8Array(i);for(let s=0;st<128?t<<1:t<<1^27));constructor(){this.buffer=new Uint8Array(16);this.bufferPosition=0}_expandKey(e){unreachable("Cannot call `_expandKey` on the base class")}_decrypt(e,t){let a,r,i;const n=new Uint8Array(16);n.set(e);for(let e=0,a=this._keySize;e<16;++e,++a)n[e]^=t[a];for(let e=this._cyclesOfRepetition-1;e>=1;--e){a=n[13];n[13]=n[9];n[9]=n[5];n[5]=n[1];n[1]=a;a=n[14];r=n[10];n[14]=n[6];n[10]=n[2];n[6]=a;n[2]=r;a=n[15];r=n[11];i=n[7];n[15]=n[3];n[11]=a;n[7]=r;n[3]=i;for(let e=0;e<16;++e)n[e]=this._inv_s[n[e]];for(let a=0,r=16*e;a<16;++a,++r)n[a]^=t[r];for(let e=0;e<16;e+=4){const t=this._mix[n[e]],r=this._mix[n[e+1]],i=this._mix[n[e+2]],s=this._mix[n[e+3]];a=t^r>>>8^r<<24^i>>>16^i<<16^s>>>24^s<<8;n[e]=a>>>24&255;n[e+1]=a>>16&255;n[e+2]=a>>8&255;n[e+3]=255&a}}a=n[13];n[13]=n[9];n[9]=n[5];n[5]=n[1];n[1]=a;a=n[14];r=n[10];n[14]=n[6];n[10]=n[2];n[6]=a;n[2]=r;a=n[15];r=n[11];i=n[7];n[15]=n[3];n[11]=a;n[7]=r;n[3]=i;for(let e=0;e<16;++e){n[e]=this._inv_s[n[e]];n[e]^=t[e]}return n}_encrypt(e,t){const a=this._s;let r,i,n;const s=new Uint8Array(16);s.set(e);for(let e=0;e<16;++e)s[e]^=t[e];for(let e=1;e=r;--a)if(e[a]!==t){t=0;break}o-=t;n[n.length-1]=e.subarray(0,16-t)}}const c=new Uint8Array(o);for(let e=0,t=0,a=n.length;e=256&&(o=255&(27^o))}for(let t=0;t<4;++t){a[e]=r^=a[e-32];e++;a[e]=i^=a[e-32];e++;a[e]=n^=a[e-32];e++;a[e]=s^=a[e-32];e++}}return a}}class PDFBase{_hash(e,t,a){unreachable("Abstract method `_hash` called")}checkOwnerPassword(e,t,a,r){const i=new Uint8Array(e.length+56);i.set(e,0);i.set(t,e.length);i.set(a,e.length+t.length);return isArrayEqual(this._hash(e,i,a),r)}checkUserPassword(e,t,a){const r=new Uint8Array(e.length+8);r.set(e,0);r.set(t,e.length);return isArrayEqual(this._hash(e,r,[]),a)}getOwnerKey(e,t,a,r){const i=new Uint8Array(e.length+56);i.set(e,0);i.set(t,e.length);i.set(a,e.length+t.length);const n=this._hash(e,i,a);return new AES256Cipher(n).decryptBlock(r,!1,new Uint8Array(16))}getUserKey(e,t,a){const r=new Uint8Array(e.length+8);r.set(e,0);r.set(t,e.length);const i=this._hash(e,r,[]);return new AES256Cipher(i).decryptBlock(a,!1,new Uint8Array(16))}}class PDF17 extends PDFBase{_hash(e,t,a){return calculateSHA256(t,0,t.length)}}class PDF20 extends PDFBase{_hash(e,t,a){let r=calculateSHA256(t,0,t.length).subarray(0,32),i=[0],n=0;for(;n<64||i.at(-1)>n-32;){const t=e.length+r.length+a.length,l=new Uint8Array(t);let h=0;l.set(e,h);h+=e.length;l.set(r,h);h+=r.length;l.set(a,h);const u=new Uint8Array(64*t);for(let e=0,a=0;e<64;e++,a+=t)u.set(l,a);i=new AES128Cipher(r.subarray(0,16)).encrypt(u,r.subarray(16,32));const d=Math.sumPrecise(i.slice(0,16))%3;0===d?r=calculateSHA256(i,0,i.length):1===d?r=(s=i,o=0,c=i.length,calculateSHA512(s,o,c,!0)):2===d&&(r=calculateSHA512(i,0,i.length));n++}var s,o,c;return r.subarray(0,32)}}class CipherTransform{constructor(e,t){this.StringCipherConstructor=e;this.StreamCipherConstructor=t}createStream(e,t){const a=new this.StreamCipherConstructor;return new DecryptStream(e,t,(function cipherTransformDecryptStream(e,t){return a.decryptBlock(e,t)}))}decryptString(e){const t=new this.StringCipherConstructor;let a=stringToBytes(e);a=t.decryptBlock(a,!0);return bytesToString(a)}encryptString(e){const t=new this.StringCipherConstructor;if(t instanceof AESBaseCipher){const a=16-e.length%16;e+=String.fromCharCode(a).repeat(a);const r=new Uint8Array(16);crypto.getRandomValues(r);let i=stringToBytes(e);i=t.encrypt(i,r);const n=new Uint8Array(16+i.length);n.set(r);n.set(i,16);return bytesToString(n)}let a=stringToBytes(e);a=t.encrypt(a);return bytesToString(a)}}class CipherTransformFactory{static get _defaultPasswordBytes(){return shadow(this,"_defaultPasswordBytes",new Uint8Array([40,191,78,94,78,117,138,65,100,0,78,86,255,250,1,8,46,46,0,182,208,104,62,128,47,12,169,254,100,83,105,122]))}#be(e,t,a,r,i,n,s,o,c,l,h,u){if(t){const e=Math.min(127,t.length);t=t.subarray(0,e)}else t=[];const d=6===e?new PDF20:new PDF17;return d.checkUserPassword(t,o,s)?d.getUserKey(t,c,h):t.length&&d.checkOwnerPassword(t,r,n,a)?d.getOwnerKey(t,i,n,l):null}#ye(e,t,a,r,i,n,s,o){const c=40+a.length+e.length,l=new Uint8Array(c);let h,u,d=0;if(t){u=Math.min(32,t.length);for(;d>8&255;l[d++]=i>>16&255;l[d++]=i>>>24&255;l.set(e,d);d+=e.length;if(n>=4&&!o){l.fill(255,d,d+4);d+=4}let f=calculateMD5(l,0,d);const g=s>>3;if(n>=3)for(h=0;h<50;++h)f=calculateMD5(f,0,g);const p=f.subarray(0,g);let m,b;if(n>=3){d=0;l.set(CipherTransformFactory._defaultPasswordBytes,d);d+=32;l.set(e,d);d+=e.length;m=new ARCFourCipher(p);b=m.encryptBlock(calculateMD5(l,0,d));u=p.length;const t=new Uint8Array(u);for(h=1;h<=19;++h){for(let e=0;er[t]===e))?p:null}#we(e,t,a,r){const i=new Uint8Array(32);let n=0;const s=Math.min(32,e.length);for(;n>3;if(a>=3)for(o=0;o<50;++o)c=calculateMD5(c,0,c.length);let h,u;if(a>=3){u=t;const e=new Uint8Array(l);for(o=19;o>=0;o--){for(let t=0;t>8&255;n[s++]=e>>16&255;n[s++]=255&t;n[s++]=t>>8&255;if(r){n[s++]=115;n[s++]=65;n[s++]=108;n[s++]=84}return calculateMD5(n,0,s).subarray(0,Math.min(i+5,16))}#xe(e,t,a,r,i){if(!(t instanceof Name))throw new FormatError("Invalid crypt filter name.");const n=this,s=e.get(t.name),o=s?.get("CFM");if(!o||"None"===o.name)return function(){return new NullCipher};if("V2"===o.name)return function(){return new ARCFourCipher(n.#Se(a,r,i,!1))};if("AESV2"===o.name)return function(){return new AES128Cipher(n.#Se(a,r,i,!0))};if("AESV3"===o.name)return function(){return new AES256Cipher(i)};throw new FormatError("Unknown crypto method")}constructor(e,t,a){const r=e.get("Filter");if(!isName(r,"Standard"))throw new FormatError("unknown encryption method");this.filterName=r.name;this.dict=e;const i=e.get("V");if(!Number.isInteger(i)||1!==i&&2!==i&&4!==i&&5!==i)throw new FormatError("unsupported encryption algorithm");this.algorithm=i;let n=e.get("Length");if(!n)if(i<=3)n=40;else{const t=e.get("CF"),a=e.get("StmF");if(t instanceof Dict&&a instanceof Name){t.suppressEncryption=!0;const e=t.get(a.name);n=e?.get("Length")||128;n<40&&(n<<=3)}}if(!Number.isInteger(n)||n<40||n%8!=0)throw new FormatError("invalid key length");const s=stringToBytes(e.get("O")),o=stringToBytes(e.get("U")),c=s.subarray(0,32),l=o.subarray(0,32),h=e.get("P"),u=e.get("R"),d=(4===i||5===i)&&!1!==e.get("EncryptMetadata");this.encryptMetadata=d;const f=stringToBytes(t);let g,p;if(a){if(6===u)try{a=utf8StringToString(a)}catch{warn("CipherTransformFactory: Unable to convert UTF8 encoded password.")}g=stringToBytes(a)}if(5!==i)p=this.#ye(f,g,c,l,h,u,n,d);else{const t=s.subarray(32,40),a=s.subarray(40,48),r=o.subarray(0,48),i=o.subarray(32,40),n=o.subarray(40,48),h=stringToBytes(e.get("OE")),d=stringToBytes(e.get("UE")),f=stringToBytes(e.get("Perms"));p=this.#be(u,g,c,t,a,r,l,i,n,h,d,f)}if(!p){if(!a)throw new PasswordException("No password given",ha);const e=this.#we(g,c,u,n);p=this.#ye(f,e,c,l,h,u,n,d)}if(!p)throw new PasswordException("Incorrect Password",ua);if(4===i&&p.length<16){this.encryptionKey=new Uint8Array(16);this.encryptionKey.set(p)}else this.encryptionKey=p;if(i>=4){const t=e.get("CF");t instanceof Dict&&(t.suppressEncryption=!0);this.cf=t;this.stmf=e.get("StmF")||Name.get("Identity");this.strf=e.get("StrF")||Name.get("Identity");this.eff=e.get("EFF")||this.stmf}}createCipherTransform(e,t){if(4===this.algorithm||5===this.algorithm)return new CipherTransform(this.#xe(this.cf,this.strf,e,t,this.encryptionKey),this.#xe(this.cf,this.stmf,e,t,this.encryptionKey));const a=this.#Se(e,t,this.encryptionKey,!1),cipherConstructor=function(){return new ARCFourCipher(a)};return new CipherTransform(cipherConstructor,cipherConstructor)}}class XRef{#Ae=null;constructor(e,t){this.stream=e;this.pdfManager=t;this.entries=[];this._xrefStms=new Set;this._cacheMap=new Map;this._pendingRefs=new RefSet;this._newPersistentRefNum=null;this._newTemporaryRefNum=null;this._persistentRefsCache=null}getNewPersistentRef(e){null===this._newPersistentRefNum&&(this._newPersistentRefNum=this.entries.length||1);const t=this._newPersistentRefNum++;this._cacheMap.set(t,e);return Ref.get(t,0)}getNewTemporaryRef(){if(null===this._newTemporaryRefNum){this._newTemporaryRefNum=this.entries.length||1;if(this._newPersistentRefNum){this._persistentRefsCache=new Map;for(let e=this._newTemporaryRefNum;e0;){const[s,o]=n;if(!Number.isInteger(s)||!Number.isInteger(o))throw new FormatError(`Invalid XRef range fields: ${s}, ${o}`);if(!Number.isInteger(a)||!Number.isInteger(r)||!Number.isInteger(i))throw new FormatError(`Invalid XRef entry fields length: ${s}, ${o}`);for(let n=t.entryNum;n=e.length);){a+=String.fromCharCode(r);r=e[t]}return a}function skipUntil(e,t,a){const r=a.length,i=e.length;let n=0;for(;t=r)break;t++;n++}return n}const e=/\b(endobj|\d+\s+\d+\s+obj|xref|trailer\s*<<)\b/g,t=/\b(startxref|\d+\s+\d+\s+obj)\b/g,a=/^(\d+)\s+(\d+)\s+obj\b/,r=new Uint8Array([116,114,97,105,108,101,114]),i=new Uint8Array([115,116,97,114,116,120,114,101,102]),n=new Uint8Array([47,88,82,101,102]);this.entries.length=0;this._cacheMap.clear();const s=this.stream;s.pos=0;const o=s.getBytes(),c=bytesToString(o),l=o.length;let h=s.start;const u=[],d=[];for(;h=l)break;f=o[h]}while(10!==f&&13!==f);continue}const g=readToken(o,h);let p;if(g.startsWith("xref")&&(4===g.length||/\s/.test(g[4]))){h+=skipUntil(o,h,r);u.push(h);h+=skipUntil(o,h,i)}else if(p=a.exec(g)){const t=0|p[1],a=0|p[2],r=h+g.length;let i,u=!1;if(this.entries[t]){if(this.entries[t].gen===a)try{new Parser({lexer:new Lexer(s.makeSubStream(r))}).getObj();u=!0}catch(e){e instanceof ParserEOFException?warn(`indexObjects -- checking object (${g}): "${e}".`):u=!0}}else u=!0;u&&(this.entries[t]={offset:h-s.start,gen:a,uncompressed:!0});e.lastIndex=r;const f=e.exec(c);if(f){i=e.lastIndex+1-h;if("endobj"!==f[1]){warn(`indexObjects: Found "${f[1]}" inside of another "obj", caused by missing "endobj" -- trying to recover.`);i-=f[1].length+1}}else i=l-h;const m=o.subarray(h,h+i),b=skipUntil(m,0,n);if(b0?Math.max(...this._xrefStms):null)}getEntry(e){const t=this.entries[e];return t&&!t.free&&t.offset?t:null}fetchIfRef(e,t=!1){return e instanceof Ref?this.fetch(e,t):e}fetch(e,t=!1){if(!(e instanceof Ref))throw new Error("ref object is not a reference");const a=e.num,r=this._cacheMap.get(a);if(void 0!==r){r instanceof Dict&&!r.objId&&(r.objId=e.toString());return r}let i=this.getEntry(a);if(null===i)return i;if(this._pendingRefs.has(e)){this._pendingRefs.remove(e);warn(`Ignoring circular reference: ${e}.`);return ya}this._pendingRefs.put(e);try{i=i.uncompressed?this.fetchUncompressed(e,i,t):this.fetchCompressed(e,i,t);this._pendingRefs.remove(e)}catch(t){this._pendingRefs.remove(e);throw t}i instanceof Dict?i.objId=e.toString():i instanceof BaseStream&&(i.dict.objId=e.toString());return i}fetchUncompressed(e,t,a=!1){const r=e.gen;let i=e.num;if(t.gen!==r){const n=`Inconsistent generation in XRef: ${e}`;if(this._generationFallback&&t.gen0&&t[3]-t[1]>0)return t;warn(`Empty, or invalid, /${e} entry.`)}return null}get mediaBox(){return shadow(this,"mediaBox",this.#Fe("MediaBox")||mc)}get cropBox(){return shadow(this,"cropBox",this.#Fe("CropBox")||this.mediaBox)}get userUnit(){const e=this.pageDict.get("UserUnit");return shadow(this,"userUnit","number"==typeof e&&e>0?e:1)}get view(){const{cropBox:e,mediaBox:t}=this;if(e!==t&&!isArrayEqual(e,t)){const a=Util.intersect(e,t);if(a&&a[2]-a[0]>0&&a[3]-a[1]>0)return shadow(this,"view",a);warn("Empty /CropBox and /MediaBox intersection.")}return shadow(this,"view",t)}get rotate(){let e=this.#ve("Rotate")||0;e%90!=0?e=0:e>=360?e%=360:e<0&&(e=(e%360+360)%360);return shadow(this,"rotate",e)}#Ie(e,t){if(!this.evaluatorOptions.ignoreErrors)throw e;warn(`getContentStream - ignoring sub-stream (${t}): "${e}".`)}async getContentStream(){const e=await this.pdfManager.ensure(this,"content");return e instanceof BaseStream?e:Array.isArray(e)?new StreamsSequenceStream(e,this.#Ie.bind(this)):new NullStream}get xfaData(){return shadow(this,"xfaData",this.xfaFactory?{bbox:this.xfaFactory.getBoundingBox(this.pageIndex)}:null)}async#Oe(e,t,a){const r=[];for(const i of e)if(i.id){const e=Ref.fromString(i.id);if(!e){warn(`A non-linked annotation cannot be modified: ${i.id}`);continue}if(i.deleted){t.put(e,e);if(i.popupRef){const e=Ref.fromString(i.popupRef);e&&t.put(e,e)}continue}a?.put(e);i.ref=e;r.push(this.xref.fetchAsync(e).then((e=>{e instanceof Dict&&(i.oldAnnotation=e.clone())}),(()=>{warn(`Cannot fetch \`oldAnnotation\` for: ${e}.`)})));delete i.id}await Promise.all(r)}async saveNewAnnotations(e,t,a,r,i){if(this.xfaFactory)throw new Error("XFA: Cannot save new annotations.");const n=this.#Ce(e),s=new RefSetCache,o=new RefSet;await this.#Oe(a,s,o);const c=this.pageDict,l=this.annotations.filter((e=>!(e instanceof Ref&&s.has(e)))),h=await AnnotationFactory.saveNewAnnotations(n,t,a,r,i);for(const{ref:e}of h.annotations)e instanceof Ref&&!o.has(e)&&l.push(e);const u=c.clone();u.set("Annots",l);i.put(this.ref,{data:u});for(const e of s)i.put(e,{data:null})}async save(e,t,a,r){const i=this.#Ce(e),n=await this._parsedAnnotations,s=[];for(const e of n)s.push(e.save(i,t,a,r).catch((function(e){warn(`save - ignoring annotation data during "${t.name}" task: "${e}".`);return null})));return Promise.all(s)}async loadResources(e){await(this.#ke??=this.pdfManager.ensure(this,"resources"));await ObjectLoader.load(this.resources,e,this.xref)}async#Te(e,t){const a=e?.get("Resources");if(!(a instanceof Dict&&a.size))return this.resources;await ObjectLoader.load(a,t,this.xref);return Dict.merge({xref:this.xref,dictArray:[a,this.resources],mergeSubDicts:!0})}async getOperatorList({handler:e,sink:t,task:a,intent:r,cacheKey:i,annotationStorage:c=null,modifiedIds:d=null}){const g=this.getContentStream(),p=this.loadResources(Ia),m=this.#Ce(e),b=this.xfaFactory?null:getNewAnnotationsMap(c),y=b?.get(this.pageIndex);let w=Promise.resolve(null),S=null;if(y){const e=this.pdfManager.ensureDoc("annotationGlobals");let t;const r=new Set;for(const{bitmapId:e,bitmap:t}of y)!e||t||r.has(e)||r.add(e);const{isOffscreenCanvasSupported:i}=this.evaluatorOptions;if(r.size>0){const e=y.slice();for(const[t,a]of c)t.startsWith(f)&&a.bitmap&&r.has(a.bitmapId)&&e.push(a);t=AnnotationFactory.generateImages(e,this.xref,i)}else t=AnnotationFactory.generateImages(y,this.xref,i);S=new RefSet;w=Promise.all([e,this.#Oe(y,S,null)]).then((([e])=>e?AnnotationFactory.printNewAnnotations(e,m,a,y,t):null))}const x=Promise.all([g,p]).then((async([n])=>{const s=await this.#Te(n.dict,Ia),o=new OperatorList(r,t);e.send("StartRenderPage",{transparency:m.hasBlendModes(s,this.nonBlendModesSet),pageIndex:this.pageIndex,cacheKey:i});await m.getOperatorList({stream:n,task:a,resources:s,operatorList:o});return o}));let[k,C,v]=await Promise.all([x,this._parsedAnnotations,w]);if(v){C=C.filter((e=>!(e.ref&&S.has(e.ref))));for(let e=0,t=v.length;ee.ref&&isRefsEqual(e.ref,a.refToReplace)));if(r>=0){C.splice(r,1,a);v.splice(e--,1);t--}}}C=C.concat(v)}if(0===C.length||r&h){k.flush(!0);return{length:k.totalLength}}const F=!!(r&l),O=!!(r&u),T=!!(r&n),M=!!(r&s),D=!!(r&o),R=[];for(const e of C)(T||M&&e.mustBeViewed(c,F)&&e.mustBeViewedWhenEditing(O,d)||D&&e.mustBePrinted(c))&&R.push(e.getOperatorList(m,a,r,c).catch((function(e){warn(`getOperatorList - ignoring annotation data during "${a.name}" task: "${e}".`);return{opList:null,separateForm:!1,separateCanvas:!1}})));const N=await Promise.all(R);let E=!1,L=!1;for(const{opList:e,separateForm:t,separateCanvas:a}of N){k.addOpList(e);E||=t;L||=a}k.flush(!0,{form:E,canvas:L});return{length:k.totalLength}}async extractTextContent({handler:e,task:t,includeMarkedContent:a,disableNormalization:r,sink:i}){const n=this.getContentStream(),s=this.loadResources(Oa),o=this.pdfManager.ensureCatalog("lang"),[c,,l]=await Promise.all([n,s,o]),h=await this.#Te(c.dict,Oa);return this.#Ce(e).getTextContent({stream:c,task:t,resources:h,includeMarkedContent:a,disableNormalization:r,sink:i,viewBox:this.view,lang:l})}async getStructTree(){const e=await this.pdfManager.ensureCatalog("structTreeRoot");if(!e)return null;await this._parsedAnnotations;try{const t=await this.pdfManager.ensure(this,"_parseStructTree",[e]);return await this.pdfManager.ensure(t,"serializable")}catch(e){warn(`getStructTree: "${e}".`);return null}}_parseStructTree(e){const t=new StructTreePage(e,this.pageDict);t.parse(this.ref);return t}async getAnnotationsData(e,t,a){const r=await this._parsedAnnotations;if(0===r.length)return r;const i=[],c=[];let l;const h=!!(a&n),u=!!(a&s),d=!!(a&o);for(const a of r){const r=h||u&&a.viewable;(r||d&&a.printable)&&i.push(a.data);if(a.hasTextContent&&r){l??=this.#Ce(e);c.push(a.extractTextContent(l,t,[-1/0,-1/0,1/0,1/0]).catch((function(e){warn(`getAnnotationsData - ignoring textContent during "${t.name}" task: "${e}".`)})))}}await Promise.all(c);return i}get annotations(){const e=this.#ve("Annots");return shadow(this,"annotations",Array.isArray(e)?e:[])}get _parsedAnnotations(){return shadow(this,"_parsedAnnotations",this.pdfManager.ensure(this,"annotations").then((async e=>{if(0===e.length)return e;const[t,a]=await Promise.all([this.pdfManager.ensureDoc("annotationGlobals"),this.pdfManager.ensureDoc("fieldObjects")]);if(!t)return[];const r=a?.orphanFields,i=[];for(const a of e)i.push(AnnotationFactory.create(this.xref,a,t,this._localIdFactory,!1,r,this.ref).catch((function(e){warn(`_parsedAnnotations: "${e}".`);return null})));const n=[];let s,o;for(const e of await Promise.all(i))e&&(e instanceof WidgetAnnotation?(o||=[]).push(e):e instanceof PopupAnnotation?(s||=[]).push(e):n.push(e));o&&n.push(...o);s&&n.push(...s);return n})))}get jsActions(){return shadow(this,"jsActions",collectActions(this.xref,this.pageDict,Se))}}const bc=new Uint8Array([37,80,68,70,45]),yc=new Uint8Array([115,116,97,114,116,120,114,101,102]),wc=new Uint8Array([101,110,100,111,98,106]);function find(e,t,a=1024,r=!1){const i=t.length,n=e.peekBytes(a),s=n.length-i;if(s<=0)return!1;if(r){const a=i-1;let r=n.length-1;for(;r>=a;){let s=0;for(;s=i){e.pos+=r-a;return!0}r--}}else{let a=0;for(;a<=s;){let r=0;for(;r=i){e.pos+=a;return!0}a++}}return!1}class PDFDocument{#Me=new Map;#De=null;constructor(e,t){if(t.length<=0)throw new InvalidPDFException("The PDF file is empty, i.e. its size is zero bytes.");this.pdfManager=e;this.stream=t;this.xref=new XRef(t,e);const a={font:0};this._globalIdFactory=class{static getDocId(){return`g_${e.docId}`}static createFontId(){return"f"+ ++a.font}static createObjId(){unreachable("Abstract method `createObjId` called.")}static getPageObjId(){unreachable("Abstract method `getPageObjId` called.")}}}parse(e){this.xref.parse(e);this.catalog=new Catalog(this.pdfManager,this.xref)}get linearization(){let e=null;try{e=Linearization.create(this.stream)}catch(e){if(e instanceof MissingDataException)throw e;info(e)}return shadow(this,"linearization",e)}get startXRef(){const e=this.stream;let t=0;if(this.linearization){e.reset();if(find(e,wc)){e.skip(6);let a=e.peekByte();for(;isWhiteSpace(a);){e.pos++;a=e.peekByte()}t=e.pos-e.start}}else{const a=1024,r=yc.length;let i=!1,n=e.end;for(;!i&&n>0;){n-=a-r;n<0&&(n=0);e.pos=n;i=find(e,yc,a,!0)}if(i){e.skip(9);let a;do{a=e.getByte()}while(isWhiteSpace(a));let r="";for(;a>=32&&a<=57;){r+=String.fromCharCode(a);a=e.getByte()}t=parseInt(r,10);isNaN(t)&&(t=0)}}return shadow(this,"startXRef",t)}checkHeader(){const e=this.stream;e.reset();if(!find(e,bc))return;e.moveStart();e.skip(bc.length);let t,a="";for(;(t=e.getByte())>32&&a.length<7;)a+=String.fromCharCode(t);Ca.test(a)?this.#De=a:warn(`Invalid PDF header version: ${a}`)}parseStartXRef(){this.xref.setStartXRef(this.startXRef)}get numPages(){let e=0;e=this.catalog.hasActualNumPages?this.catalog.numPages:this.xfaFactory?this.xfaFactory.getNumPages():this.linearization?this.linearization.numPages:this.catalog.numPages;return shadow(this,"numPages",e)}#Be(e,t=0){return!!Array.isArray(e)&&e.every((e=>{if(!((e=this.xref.fetchIfRef(e))instanceof Dict))return!1;if(e.has("Kids")){if(++t>10){warn("#hasOnlyDocumentSignatures: maximum recursion depth reached");return!1}return this.#Be(e.get("Kids"),t)}const a=isName(e.get("FT"),"Sig"),r=e.get("Rect"),i=Array.isArray(r)&&r.every((e=>0===e));return a&&i}))}get _xfaStreams(){const{acroForm:e}=this.catalog;if(!e)return null;const t=e.get("XFA"),a=new Map(["xdp:xdp","template","datasets","config","connectionSet","localeSet","stylesheet","/xdp:xdp"].map((e=>[e,null])));if(t instanceof BaseStream&&!t.isEmpty){a.set("xdp:xdp",t);return a}if(!Array.isArray(t)||0===t.length)return null;for(let e=0,r=t.length;el.handleSetFont(r,[Name.get(e),1],null,h,t,d,a,i).catch((e=>{warn(`loadXfaFonts: "${e}".`);return null})),f=[];for(const[e,t]of i){const a=t.get("FontDescriptor");if(!(a instanceof Dict))continue;let r=a.get("FontFamily");r=r.replaceAll(/[ ]+(\d)/g,"$1");const i={fontFamily:r,fontWeight:a.get("FontWeight"),italicAngle:-a.get("ItalicAngle")};validateCSSFont(i)&&f.push(parseFont(e,null,i))}await Promise.all(f);const g=this.xfaFactory.setFonts(u);if(!g)return;n.ignoreErrors=!0;f.length=0;u.length=0;const p=new Set;for(const e of g)getXfaFontName(`${e}-Regular`)||p.add(e);p.size&&g.push("PdfJS-Fallback");for(const e of g)if(!p.has(e))for(const t of[{name:"Regular",fontWeight:400,italicAngle:0},{name:"Bold",fontWeight:700,italicAngle:0},{name:"Italic",fontWeight:400,italicAngle:12},{name:"BoldItalic",fontWeight:700,italicAngle:12}]){const a=`${e}-${t.name}`;f.push(parseFont(a,getXfaFontDict(a),{fontFamily:e,fontWeight:t.fontWeight,italicAngle:t.italicAngle}))}await Promise.all(f);this.xfaFactory.appendFonts(u,p)}loadXfaResources(e,t){return Promise.all([this.#Ne(e,t).catch((()=>{})),this.#Re()])}serializeXfaData(e){return this.xfaFactory?this.xfaFactory.serializeData(e):null}get version(){return this.catalog.version||this.#De}get formInfo(){const e={hasFields:!1,hasAcroForm:!1,hasXfa:!1,hasSignatures:!1},{acroForm:t}=this.catalog;if(!t)return shadow(this,"formInfo",e);try{const a=t.get("Fields"),r=Array.isArray(a)&&a.length>0;e.hasFields=r;const i=t.get("XFA");e.hasXfa=Array.isArray(i)&&i.length>0||i instanceof BaseStream&&!i.isEmpty;const n=!!(1&t.get("SigFlags")),s=n&&this.#Be(a);e.hasAcroForm=r&&!s;e.hasSignatures=n}catch(e){if(e instanceof MissingDataException)throw e;warn(`Cannot fetch form information: "${e}".`)}return shadow(this,"formInfo",e)}get documentInfo(){const{catalog:e,formInfo:t,xref:a}=this,r={PDFFormatVersion:this.version,Language:e.lang,EncryptFilterName:a.encrypt?.filterName??null,IsLinearized:!!this.linearization,IsAcroFormPresent:t.hasAcroForm,IsXFAPresent:t.hasXfa,IsCollectionPresent:!!e.collection,IsSignaturesPresent:t.hasSignatures};let i;try{i=a.trailer.get("Info")}catch(e){if(e instanceof MissingDataException)throw e;info("The document information dictionary is invalid.")}if(!(i instanceof Dict))return shadow(this,"documentInfo",r);for(const[e,t]of i){switch(e){case"Title":case"Author":case"Subject":case"Keywords":case"Creator":case"Producer":case"CreationDate":case"ModDate":if("string"==typeof t){r[e]=stringToPDFString(t);continue}break;case"Trapped":if(t instanceof Name){r[e]=t;continue}break;default:let a;switch(typeof t){case"string":a=stringToPDFString(t);break;case"number":case"boolean":a=t;break;default:t instanceof Name&&(a=t)}if(void 0===a){warn(`Bad value, for custom key "${e}", in Info: ${t}.`);continue}r.Custom??=Object.create(null);r.Custom[e]=a;continue}warn(`Bad value, for key "${e}", in Info: ${t}.`)}return shadow(this,"documentInfo",r)}get fingerprints(){const e="\0".repeat(16);function validate(t){return"string"==typeof t&&16===t.length&&t!==e}const t=this.xref.trailer.get("ID");let a,r;if(Array.isArray(t)&&validate(t[0])){a=stringToBytes(t[0]);t[1]!==t[0]&&validate(t[1])&&(r=stringToBytes(t[1]))}else a=calculateMD5(this.stream.getByteRange(0,1024),0,1024);return shadow(this,"fingerprints",[toHexUtil(a),r?toHexUtil(r):null])}async#Ee(e){const{catalog:t,linearization:a,xref:r}=this,i=Ref.get(a.objectNumberFirst,0);try{const e=await r.fetchAsync(i);if(e instanceof Dict){let a=e.getRaw("Type");a instanceof Ref&&(a=await r.fetchAsync(a));if(isName(a,"Page")||!e.has("Type")&&!e.has("Kids")&&e.has("Contents")){t.pageKidsCountCache.has(i)||t.pageKidsCountCache.put(i,1);t.pageIndexCache.has(i)||t.pageIndexCache.put(i,0);return[e,i]}}throw new FormatError("The Linearization dictionary doesn't point to a valid Page dictionary.")}catch(a){warn(`_getLinearizationPage: "${a.message}".`);return t.getPageDict(e)}}getPage(e){const t=this.#Me.get(e);if(t)return t;const{catalog:a,linearization:r,xfaFactory:i}=this;let n;n=i?Promise.resolve([Dict.empty,null]):r?.pageFirst===e?this.#Ee(e):a.getPageDict(e);n=n.then((([t,r])=>new Page({pdfManager:this.pdfManager,xref:this.xref,pageIndex:e,pageDict:t,ref:r,globalIdFactory:this._globalIdFactory,fontCache:a.fontCache,builtInCMapCache:a.builtInCMapCache,standardFontDataCache:a.standardFontDataCache,globalColorSpaceCache:a.globalColorSpaceCache,globalImageCache:a.globalImageCache,systemFontCache:a.systemFontCache,nonBlendModesSet:a.nonBlendModesSet,xfaFactory:i})));this.#Me.set(e,n);return n}async checkFirstPage(e=!1){if(!e)try{await this.getPage(0)}catch(e){if(e instanceof XRefEntryException){this.#Me.delete(0);await this.cleanup();throw new XRefParseException}}}async checkLastPage(e=!1){const{catalog:t,pdfManager:a}=this;t.setActualNumPages();let r;try{await Promise.all([a.ensureDoc("xfaFactory"),a.ensureDoc("linearization"),a.ensureCatalog("numPages")]);if(this.xfaFactory)return;r=this.linearization?this.linearization.numPages:t.numPages;if(!Number.isInteger(r))throw new FormatError("Page count is not an integer.");if(r<=1)return;await this.getPage(r-1)}catch(i){this.#Me.delete(r-1);await this.cleanup();if(i instanceof XRefEntryException&&!e)throw new XRefParseException;warn(`checkLastPage - invalid /Pages tree /Count: ${r}.`);let n;try{n=await t.getAllPageDicts(e)}catch(a){if(a instanceof XRefEntryException&&!e)throw new XRefParseException;t.setActualNumPages(1);return}for(const[e,[r,i]]of n){let n;if(r instanceof Error){n=Promise.reject(r);n.catch((()=>{}))}else n=Promise.resolve(new Page({pdfManager:a,xref:this.xref,pageIndex:e,pageDict:r,ref:i,globalIdFactory:this._globalIdFactory,fontCache:t.fontCache,builtInCMapCache:t.builtInCMapCache,standardFontDataCache:t.standardFontDataCache,globalColorSpaceCache:this.globalColorSpaceCache,globalImageCache:t.globalImageCache,systemFontCache:t.systemFontCache,nonBlendModesSet:t.nonBlendModesSet,xfaFactory:null}));this.#Me.set(e,n)}t.setActualNumPages(n.size)}}async fontFallback(e,t){const{catalog:a,pdfManager:r}=this;for(const i of await Promise.all(a.fontCache))if(i.loadedName===e){i.fallback(t,r.evaluatorOptions);return}}async cleanup(e=!1){return this.catalog?this.catalog.cleanup(e):clearGlobalCaches()}async#Pe(e,t,a,r,i,n,s){const{xref:o}=this;if(!(a instanceof Ref)||n.has(a))return;n.put(a);const c=await o.fetchAsync(a);if(!(c instanceof Dict))return;let l=await c.getAsync("Subtype");l=l instanceof Name?l.name:null;if("Link"===l)return;if(c.has("T")){const t=stringToPDFString(await c.getAsync("T"));e=""===e?t:`${e}.${t}`}else{let a=c;for(;;){a=a.getRaw("Parent")||t;if(a instanceof Ref){if(n.has(a))break;a=await o.fetchAsync(a)}if(!(a instanceof Dict))break;if(a.has("T")){const t=stringToPDFString(await a.getAsync("T"));e=""===e?t:`${e}.${t}`;break}}}t&&!c.has("Parent")&&isName(c.get("Subtype"),"Widget")&&s.put(a,t);r.has(e)||r.set(e,[]);r.get(e).push(AnnotationFactory.create(o,a,i,null,!0,s,null).then((e=>e?.getFieldObject())).catch((function(e){warn(`#collectFieldObjects: "${e}".`);return null})));if(!c.has("Kids"))return;const h=await c.getAsync("Kids");if(Array.isArray(h))for(const t of h)await this.#Pe(e,a,t,r,i,n,s)}get fieldObjects(){return shadow(this,"fieldObjects",this.pdfManager.ensureDoc("formInfo").then((async e=>{if(!e.hasFields)return null;const t=await this.annotationGlobals;if(!t)return null;const{acroForm:a}=t,r=new RefSet,i=Object.create(null),n=new Map,s=new RefSetCache;for(const e of a.get("Fields"))await this.#Pe("",null,e,n,t,r,s);const o=[];for(const[e,t]of n)o.push(Promise.all(t).then((t=>{(t=t.filter((e=>!!e))).length>0&&(i[e]=t)})));await Promise.all(o);return{allFields:objectSize(i)>0?i:null,orphanFields:s}})))}get hasJSActions(){return shadow(this,"hasJSActions",this.pdfManager.ensureDoc("_parseHasJSActions"))}async _parseHasJSActions(){const[e,t]=await Promise.all([this.pdfManager.ensureCatalog("jsActions"),this.pdfManager.ensureDoc("fieldObjects")]);return!!e||!!t?.allFields&&Object.values(t.allFields).some((e=>e.some((e=>null!==e.actions))))}get calculationOrderIds(){const e=this.catalog.acroForm?.get("CO");if(!Array.isArray(e)||0===e.length)return shadow(this,"calculationOrderIds",null);const t=[];for(const a of e)a instanceof Ref&&t.push(a.toString());return shadow(this,"calculationOrderIds",t.length?t:null)}get annotationGlobals(){return shadow(this,"annotationGlobals",AnnotationFactory.createGlobals(this.pdfManager))}}class BasePdfManager{constructor({docBaseUrl:e,docId:t,enableXfa:a,evaluatorOptions:r,handler:i,password:n}){this._docBaseUrl=function parseDocBaseUrl(e){if(e){const t=createValidAbsoluteUrl(e);if(t)return t.href;warn(`Invalid absolute docBaseUrl: "${e}".`)}return null}(e);this._docId=t;this._password=n;this.enableXfa=a;r.isOffscreenCanvasSupported&&=FeatureTest.isOffscreenCanvasSupported;r.isImageDecoderSupported&&=FeatureTest.isImageDecoderSupported;this.evaluatorOptions=Object.freeze(r);ImageResizer.setOptions(r);JpegStream.setOptions(r);OperatorList.setOptions(r);const s={...r,handler:i};JpxImage.setOptions(s);IccColorSpace.setOptions(s);CmykICCBasedCS.setOptions(s)}get docId(){return this._docId}get password(){return this._password}get docBaseUrl(){return this._docBaseUrl}ensureDoc(e,t){return this.ensure(this.pdfDocument,e,t)}ensureXRef(e,t){return this.ensure(this.pdfDocument.xref,e,t)}ensureCatalog(e,t){return this.ensure(this.pdfDocument.catalog,e,t)}getPage(e){return this.pdfDocument.getPage(e)}fontFallback(e,t){return this.pdfDocument.fontFallback(e,t)}cleanup(e=!1){return this.pdfDocument.cleanup(e)}async ensure(e,t,a){unreachable("Abstract method `ensure` called")}requestRange(e,t){unreachable("Abstract method `requestRange` called")}requestLoadedStream(e=!1){unreachable("Abstract method `requestLoadedStream` called")}sendProgressiveData(e){unreachable("Abstract method `sendProgressiveData` called")}updatePassword(e){this._password=e}terminate(e){unreachable("Abstract method `terminate` called")}}class LocalPdfManager extends BasePdfManager{constructor(e){super(e);const t=new Stream(e.source);this.pdfDocument=new PDFDocument(this,t);this._loadedStreamPromise=Promise.resolve(t)}async ensure(e,t,a){const r=e[t];return"function"==typeof r?r.apply(e,a):r}requestRange(e,t){return Promise.resolve()}requestLoadedStream(e=!1){return this._loadedStreamPromise}terminate(e){}}class NetworkPdfManager extends BasePdfManager{constructor(e){super(e);this.streamManager=new ChunkedStreamManager(e.source,{msgHandler:e.handler,length:e.length,disableAutoFetch:e.disableAutoFetch,rangeChunkSize:e.rangeChunkSize});this.pdfDocument=new PDFDocument(this,this.streamManager.getStream())}async ensure(e,t,a){try{const r=e[t];return"function"==typeof r?r.apply(e,a):r}catch(r){if(!(r instanceof MissingDataException))throw r;await this.requestRange(r.begin,r.end);return this.ensure(e,t,a)}}requestRange(e,t){return this.streamManager.requestRange(e,t)}requestLoadedStream(e=!1){return this.streamManager.requestAllChunks(e)}sendProgressiveData(e){this.streamManager.onReceiveData({chunk:e})}terminate(e){this.streamManager.abort(e)}}const Sc=1,xc=2,Ac=1,kc=2,Cc=3,vc=4,Fc=5,Ic=6,Oc=7,Tc=8;function onFn(){}function wrapReason(e){if(e instanceof AbortException||e instanceof InvalidPDFException||e instanceof PasswordException||e instanceof ResponseException||e instanceof UnknownErrorException)return e;e instanceof Error||"object"==typeof e&&null!==e||unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');switch(e.name){case"AbortException":return new AbortException(e.message);case"InvalidPDFException":return new InvalidPDFException(e.message);case"PasswordException":return new PasswordException(e.message,e.code);case"ResponseException":return new ResponseException(e.message,e.status,e.missing);case"UnknownErrorException":return new UnknownErrorException(e.message,e.details)}return new UnknownErrorException(e.message,e.toString())}class MessageHandler{#Le=new AbortController;constructor(e,t,a){this.sourceName=e;this.targetName=t;this.comObj=a;this.callbackId=1;this.streamId=1;this.streamSinks=Object.create(null);this.streamControllers=Object.create(null);this.callbackCapabilities=Object.create(null);this.actionHandler=Object.create(null);a.addEventListener("message",this.#je.bind(this),{signal:this.#Le.signal})}#je({data:e}){if(e.targetName!==this.sourceName)return;if(e.stream){this.#_e(e);return}if(e.callback){const t=e.callbackId,a=this.callbackCapabilities[t];if(!a)throw new Error(`Cannot resolve callback ${t}`);delete this.callbackCapabilities[t];if(e.callback===Sc)a.resolve(e.data);else{if(e.callback!==xc)throw new Error("Unexpected callback case");a.reject(wrapReason(e.reason))}return}const t=this.actionHandler[e.action];if(!t)throw new Error(`Unknown action from worker: ${e.action}`);if(e.callbackId){const a=this.sourceName,r=e.sourceName,i=this.comObj;Promise.try(t,e.data).then((function(t){i.postMessage({sourceName:a,targetName:r,callback:Sc,callbackId:e.callbackId,data:t})}),(function(t){i.postMessage({sourceName:a,targetName:r,callback:xc,callbackId:e.callbackId,reason:wrapReason(t)})}))}else e.streamId?this.#Ue(e):t(e.data)}on(e,t){const a=this.actionHandler;if(a[e])throw new Error(`There is already an actionName called "${e}"`);a[e]=t}send(e,t,a){this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:e,data:t},a)}sendWithPromise(e,t,a){const r=this.callbackId++,i=Promise.withResolvers();this.callbackCapabilities[r]=i;try{this.comObj.postMessage({sourceName:this.sourceName,targetName:this.targetName,action:e,callbackId:r,data:t},a)}catch(e){i.reject(e)}return i.promise}sendWithStream(e,t,a,r){const i=this.streamId++,n=this.sourceName,s=this.targetName,o=this.comObj;return new ReadableStream({start:a=>{const c=Promise.withResolvers();this.streamControllers[i]={controller:a,startCall:c,pullCall:null,cancelCall:null,isClosed:!1};o.postMessage({sourceName:n,targetName:s,action:e,streamId:i,data:t,desiredSize:a.desiredSize},r);return c.promise},pull:e=>{const t=Promise.withResolvers();this.streamControllers[i].pullCall=t;o.postMessage({sourceName:n,targetName:s,stream:Ic,streamId:i,desiredSize:e.desiredSize});return t.promise},cancel:e=>{assert(e instanceof Error,"cancel must have a valid reason");const t=Promise.withResolvers();this.streamControllers[i].cancelCall=t;this.streamControllers[i].isClosed=!0;o.postMessage({sourceName:n,targetName:s,stream:Ac,streamId:i,reason:wrapReason(e)});return t.promise}},a)}#Ue(e){const t=e.streamId,a=this.sourceName,r=e.sourceName,i=this.comObj,n=this,s=this.actionHandler[e.action],o={enqueue(e,n=1,s){if(this.isCancelled)return;const o=this.desiredSize;this.desiredSize-=n;if(o>0&&this.desiredSize<=0){this.sinkCapability=Promise.withResolvers();this.ready=this.sinkCapability.promise}i.postMessage({sourceName:a,targetName:r,stream:vc,streamId:t,chunk:e},s)},close(){if(!this.isCancelled){this.isCancelled=!0;i.postMessage({sourceName:a,targetName:r,stream:Cc,streamId:t});delete n.streamSinks[t]}},error(e){assert(e instanceof Error,"error must have a valid reason");if(!this.isCancelled){this.isCancelled=!0;i.postMessage({sourceName:a,targetName:r,stream:Fc,streamId:t,reason:wrapReason(e)})}},sinkCapability:Promise.withResolvers(),onPull:null,onCancel:null,isCancelled:!1,desiredSize:e.desiredSize,ready:null};o.sinkCapability.resolve();o.ready=o.sinkCapability.promise;this.streamSinks[t]=o;Promise.try(s,e.data,o).then((function(){i.postMessage({sourceName:a,targetName:r,stream:Tc,streamId:t,success:!0})}),(function(e){i.postMessage({sourceName:a,targetName:r,stream:Tc,streamId:t,reason:wrapReason(e)})}))}#_e(e){const t=e.streamId,a=this.sourceName,r=e.sourceName,i=this.comObj,n=this.streamControllers[t],s=this.streamSinks[t];switch(e.stream){case Tc:e.success?n.startCall.resolve():n.startCall.reject(wrapReason(e.reason));break;case Oc:e.success?n.pullCall.resolve():n.pullCall.reject(wrapReason(e.reason));break;case Ic:if(!s){i.postMessage({sourceName:a,targetName:r,stream:Oc,streamId:t,success:!0});break}s.desiredSize<=0&&e.desiredSize>0&&s.sinkCapability.resolve();s.desiredSize=e.desiredSize;Promise.try(s.onPull||onFn).then((function(){i.postMessage({sourceName:a,targetName:r,stream:Oc,streamId:t,success:!0})}),(function(e){i.postMessage({sourceName:a,targetName:r,stream:Oc,streamId:t,reason:wrapReason(e)})}));break;case vc:assert(n,"enqueue should have stream controller");if(n.isClosed)break;n.controller.enqueue(e.chunk);break;case Cc:assert(n,"close should have stream controller");if(n.isClosed)break;n.isClosed=!0;n.controller.close();this.#Xe(n,t);break;case Fc:assert(n,"error should have stream controller");n.controller.error(wrapReason(e.reason));this.#Xe(n,t);break;case kc:e.success?n.cancelCall.resolve():n.cancelCall.reject(wrapReason(e.reason));this.#Xe(n,t);break;case Ac:if(!s)break;const o=wrapReason(e.reason);Promise.try(s.onCancel||onFn,o).then((function(){i.postMessage({sourceName:a,targetName:r,stream:kc,streamId:t,success:!0})}),(function(e){i.postMessage({sourceName:a,targetName:r,stream:kc,streamId:t,reason:wrapReason(e)})}));s.sinkCapability.reject(o);s.isCancelled=!0;delete this.streamSinks[t];break;default:throw new Error("Unexpected stream case")}}async#Xe(e,t){await Promise.allSettled([e.startCall?.promise,e.pullCall?.promise,e.cancelCall?.promise]);delete this.streamControllers[t]}destroy(){this.#Le?.abort();this.#Le=null}}async function writeObject(e,t,a,{encrypt:r=null}){const i=r?.createCipherTransform(e.num,e.gen);a.push(`${e.num} ${e.gen} obj\n`);t instanceof Dict?await writeDict(t,a,i):t instanceof BaseStream?await writeStream(t,a,i):(Array.isArray(t)||ArrayBuffer.isView(t))&&await writeArray(t,a,i);a.push("\nendobj\n")}async function writeDict(e,t,a){t.push("<<");for(const r of e.getKeys()){t.push(` /${escapePDFName(r)} `);await writeValue(e.getRaw(r),t,a)}t.push(">>")}async function writeStream(e,t,a){let r=e.getBytes();const{dict:i}=e,[n,s]=await Promise.all([i.getAsync("Filter"),i.getAsync("DecodeParms")]),o=isName(Array.isArray(n)?await i.xref.fetchIfRefAsync(n[0]):n,"FlateDecode");if(r.length>=256||o)try{const e=new CompressionStream("deflate"),t=e.writable.getWriter();await t.ready;t.write(r).then((async()=>{await t.ready;await t.close()})).catch((()=>{}));const a=await new Response(e.readable).arrayBuffer();r=new Uint8Array(a);let c,l;if(n){if(!o){c=Array.isArray(n)?[Name.get("FlateDecode"),...n]:[Name.get("FlateDecode"),n];s&&(l=Array.isArray(s)?[null,...s]:[null,s])}}else c=Name.get("FlateDecode");c&&i.set("Filter",c);l&&i.set("DecodeParms",l)}catch(e){info(`writeStream - cannot compress data: "${e}".`)}let c=bytesToString(r);a&&(c=a.encryptString(c));i.set("Length",c.length);await writeDict(i,t,a);t.push(" stream\n",c,"\nendstream")}async function writeArray(e,t,a){t.push("[");let r=!0;for(const i of e){r?r=!1:t.push(" ");await writeValue(i,t,a)}t.push("]")}async function writeValue(e,t,a){if(e instanceof Name)t.push(`/${escapePDFName(e.name)}`);else if(e instanceof Ref)t.push(`${e.num} ${e.gen} R`);else if(Array.isArray(e)||ArrayBuffer.isView(e))await writeArray(e,t,a);else if("string"==typeof e){a&&(e=a.encryptString(e));t.push(`(${escapeString(e)})`)}else"number"==typeof e?t.push(numberToString(e)):"boolean"==typeof e?t.push(e.toString()):e instanceof Dict?await writeDict(e,t,a):e instanceof BaseStream?await writeStream(e,t,a):null===e?t.push("null"):warn(`Unhandled value in writer: ${typeof e}, please file a bug.`)}function writeInt(e,t,a,r){for(let i=t+a-1;i>a-1;i--){r[i]=255&e;e>>=8}return a+t}function writeString(e,t,a){const r=e.length;for(let i=0;i1&&(n=a.documentElement.searchNode([i.at(-1)],0));n?n.childNodes=Array.isArray(r)?r.map((e=>new SimpleDOMNode("value",e))):[new SimpleDOMNode("#text",r)]:warn(`Node not found for path: ${t}`)}const r=[];a.documentElement.dump(r);return r.join("")}(r.fetchIfRef(t).getString(),a)}const i=new StringStream(e);i.dict=new Dict(r);i.dict.set("Type",Name.get("EmbeddedFile"));a.put(t,{data:i})}function getIndexes(e){const t=[];for(const{ref:a}of e)a.num===t.at(-2)+t.at(-1)?t[t.length-1]+=1:t.push(a.num,1);return t}function computeIDs(e,t,a){if(Array.isArray(t.fileIds)&&t.fileIds.length>0){const r=function computeMD5(e,t){const a=Math.floor(Date.now()/1e3),r=t.filename||"",i=[a.toString(),r,e.toString(),...t.infoMap.values()],n=Math.sumPrecise(i.map((e=>e.length))),s=new Uint8Array(n);let o=0;for(const e of i)o=writeString(e,o,s);return bytesToString(calculateMD5(s,0,s.length))}(e,t);a.set("ID",[t.fileIds[0],r])}}async function incrementalUpdate({originalData:e,xrefInfo:t,changes:a,xref:r=null,hasXfa:i=!1,xfaDatasetsRef:n=null,hasXfaDatasetsEntry:s=!1,needAppearances:o,acroFormRef:c=null,acroForm:l=null,xfaData:h=null,useXrefStream:u=!1}){await async function updateAcroform({xref:e,acroForm:t,acroFormRef:a,hasXfa:r,hasXfaDatasetsEntry:i,xfaDatasetsRef:n,needAppearances:s,changes:o}){!r||i||n||warn("XFA - Cannot save it");if(!s&&(!r||!n||i))return;const c=t.clone();if(r&&!i){const e=t.get("XFA").slice();e.splice(2,0,"datasets");e.splice(3,0,n);c.set("XFA",e)}s&&c.set("NeedAppearances",!0);o.put(a,{data:c})}({xref:r,acroForm:l,acroFormRef:c,hasXfa:i,hasXfaDatasetsEntry:s,xfaDatasetsRef:n,needAppearances:o,changes:a});i&&updateXFA({xfaData:h,xfaDatasetsRef:n,changes:a,xref:r});const d=function getTrailerDict(e,t,a){const r=new Dict(null);r.set("Prev",e.startXRef);const i=e.newRef;if(a){t.put(i,{data:""});r.set("Size",i.num+1);r.set("Type",Name.get("XRef"))}else r.set("Size",i.num);null!==e.rootRef&&r.set("Root",e.rootRef);null!==e.infoRef&&r.set("Info",e.infoRef);null!==e.encryptRef&&r.set("Encrypt",e.encryptRef);return r}(t,a,u),f=[],g=await async function writeChanges(e,t,a=[]){const r=[];for(const[i,{data:n}]of e.items())if(null!==n&&"string"!=typeof n){await writeObject(i,n,a,t);r.push({ref:i,data:a.join("")});a.length=0}else r.push({ref:i,data:n});return r.sort(((e,t)=>e.ref.num-t.ref.num))}(a,r,f);let p=e.length;const m=e.at(-1);if(10!==m&&13!==m){f.push("\n");p+=1}for(const{data:e}of g)null!==e&&f.push(e);await(u?async function getXRefStreamTable(e,t,a,r,i){const n=[];let s=0,o=0;for(const{ref:e,data:r}of a){let a;s=Math.max(s,t);if(null!==r){a=Math.min(e.gen,65535);n.push([1,t,a]);t+=r.length}else{a=Math.min(e.gen+1,65535);n.push([0,0,a])}o=Math.max(o,a)}r.set("Index",getIndexes(a));const c=[1,getSizeInBytes(s),getSizeInBytes(o)];r.set("W",c);computeIDs(t,e,r);const l=Math.sumPrecise(c),h=new Uint8Array(l*n.length),u=new Stream(h);u.dict=r;let d=0;for(const[e,t,a]of n){d=writeInt(e,c[0],d,h);d=writeInt(t,c[1],d,h);d=writeInt(a,c[2],d,h)}await writeObject(e.newRef,u,i,{});i.push("startxref\n",t.toString(),"\n%%EOF\n")}(t,p,g,d,f):async function getXRefTable(e,t,a,r,i){i.push("xref\n");const n=getIndexes(a);let s=0;for(const{ref:e,data:r}of a){if(e.num===n[s]){i.push(`${n[s]} ${n[s+1]}\n`);s+=2}if(null!==r){i.push(`${t.toString().padStart(10,"0")} ${Math.min(e.gen,65535).toString().padStart(5,"0")} n\r\n`);t+=r.length}else i.push(`0000000000 ${Math.min(e.gen+1,65535).toString().padStart(5,"0")} f\r\n`)}computeIDs(t,e,r);i.push("trailer\n");await writeDict(r,i);i.push("\nstartxref\n",t.toString(),"\n%%EOF\n")}(t,p,g,d,f));const b=e.length+Math.sumPrecise(f.map((e=>e.length))),y=new Uint8Array(b);y.set(e);let w=e.length;for(const e of f)w=writeString(e,w,y);return y}class PDFWorkerStream{constructor(e){this._msgHandler=e;this._contentLength=null;this._fullRequestReader=null;this._rangeRequestReaders=[]}getFullReader(){assert(!this._fullRequestReader,"PDFWorkerStream.getFullReader can only be called once.");this._fullRequestReader=new PDFWorkerStreamReader(this._msgHandler);return this._fullRequestReader}getRangeReader(e,t){const a=new PDFWorkerStreamRangeReader(e,t,this._msgHandler);this._rangeRequestReaders.push(a);return a}cancelAllRequests(e){this._fullRequestReader?.cancel(e);for(const t of this._rangeRequestReaders.slice(0))t.cancel(e)}}class PDFWorkerStreamReader{constructor(e){this._msgHandler=e;this.onProgress=null;this._contentLength=null;this._isRangeSupported=!1;this._isStreamingSupported=!1;const t=this._msgHandler.sendWithStream("GetReader");this._reader=t.getReader();this._headersReady=this._msgHandler.sendWithPromise("ReaderHeadersReady").then((e=>{this._isStreamingSupported=e.isStreamingSupported;this._isRangeSupported=e.isRangeSupported;this._contentLength=e.contentLength}))}get headersReady(){return this._headersReady}get contentLength(){return this._contentLength}get isStreamingSupported(){return this._isStreamingSupported}get isRangeSupported(){return this._isRangeSupported}async read(){const{value:e,done:t}=await this._reader.read();return t?{value:void 0,done:!0}:{value:e.buffer,done:!1}}cancel(e){this._reader.cancel(e)}}class PDFWorkerStreamRangeReader{constructor(e,t,a){this._msgHandler=a;this.onProgress=null;const r=this._msgHandler.sendWithStream("GetRangeReader",{begin:e,end:t});this._reader=r.getReader()}get isStreamingSupported(){return!1}async read(){const{value:e,done:t}=await this._reader.read();return t?{value:void 0,done:!0}:{value:e.buffer,done:!1}}cancel(e){this._reader.cancel(e)}}class WorkerTask{constructor(e){this.name=e;this.terminated=!1;this._capability=Promise.withResolvers()}get finished(){return this._capability.promise}finish(){this._capability.resolve()}terminate(){this.terminated=!0}ensureNotTerminated(){if(this.terminated)throw new Error("Worker task was terminated")}}class WorkerMessageHandler{static{"undefined"==typeof window&&!e&&"undefined"!=typeof self&&"function"==typeof self.postMessage&&"onmessage"in self&&this.initializeFromPort(self)}static setup(e,t){let a=!1;e.on("test",(t=>{if(!a){a=!0;e.send("test",t instanceof Uint8Array)}}));e.on("configure",(e=>{!function setVerbosityLevel(e){Number.isInteger(e)&&(da=e)}(e.verbosity)}));e.on("GetDocRequest",(e=>this.createDocumentHandler(e,t)))}static createDocumentHandler(e,t){let a,r=!1,i=null;const n=new Set,s=getVerbosityLevel(),{docId:o,apiVersion:c}=e,l="5.3.31";if(c!==l)throw new Error(`The API version "${c}" does not match the Worker version "${l}".`);const buildMsg=(e,t)=>`The \`${e}.prototype\` contains unexpected enumerable property "${t}", thus breaking e.g. \`for...in\` iteration of ${e}s.`;for(const e in{})throw new Error(buildMsg("Object",e));for(const e in[])throw new Error(buildMsg("Array",e));const h=o+"_worker";let u=new MessageHandler(h,o,t);function ensureNotTerminated(){if(r)throw new Error("Worker was terminated")}function startWorkerTask(e){n.add(e)}function finishWorkerTask(e){e.finish();n.delete(e)}async function loadDocument(e){await a.ensureDoc("checkHeader");await a.ensureDoc("parseStartXRef");await a.ensureDoc("parse",[e]);await a.ensureDoc("checkFirstPage",[e]);await a.ensureDoc("checkLastPage",[e]);const t=await a.ensureDoc("isPureXfa");if(t){const e=new WorkerTask("loadXfaResources");startWorkerTask(e);await a.ensureDoc("loadXfaResources",[u,e]);finishWorkerTask(e)}const[r,i]=await Promise.all([a.ensureDoc("numPages"),a.ensureDoc("fingerprints")]);return{numPages:r,fingerprints:i,htmlForXfa:t?await a.ensureDoc("htmlForXfa"):null}}function setupDoc(e){function onSuccess(e){ensureNotTerminated();u.send("GetDoc",{pdfInfo:e})}function onFailure(e){ensureNotTerminated();if(e instanceof PasswordException){const t=new WorkerTask(`PasswordException: response ${e.code}`);startWorkerTask(t);u.sendWithPromise("PasswordRequest",e).then((function({password:e}){finishWorkerTask(t);a.updatePassword(e);pdfManagerReady()})).catch((function(){finishWorkerTask(t);u.send("DocException",e)}))}else u.send("DocException",wrapReason(e))}function pdfManagerReady(){ensureNotTerminated();loadDocument(!1).then(onSuccess,(function(e){ensureNotTerminated();e instanceof XRefParseException?a.requestLoadedStream().then((function(){ensureNotTerminated();loadDocument(!0).then(onSuccess,onFailure)})):onFailure(e)}))}ensureNotTerminated();(async function getPdfManager({data:e,password:t,disableAutoFetch:a,rangeChunkSize:r,length:n,docBaseUrl:s,enableXfa:c,evaluatorOptions:l}){const h={source:null,disableAutoFetch:a,docBaseUrl:s,docId:o,enableXfa:c,evaluatorOptions:l,handler:u,length:n,password:t,rangeChunkSize:r};if(e){h.source=e;return new LocalPdfManager(h)}const d=new PDFWorkerStream(u),f=d.getFullReader(),g=Promise.withResolvers();let p,m=[],b=0;f.headersReady.then((function(){if(f.isRangeSupported){h.source=d;h.length=f.contentLength;h.disableAutoFetch||=f.isStreamingSupported;p=new NetworkPdfManager(h);for(const e of m)p.sendProgressiveData(e);m=[];g.resolve(p);i=null}})).catch((function(e){g.reject(e);i=null}));new Promise((function(e,t){const readChunk=function({value:e,done:a}){try{ensureNotTerminated();if(a){if(!p){const e=arrayBuffersToBytes(m);m=[];n&&e.length!==n&&warn("reported HTTP length is different from actual");h.source=e;p=new LocalPdfManager(h);g.resolve(p)}i=null;return}b+=e.byteLength;f.isStreamingSupported||u.send("DocProgress",{loaded:b,total:Math.max(b,f.contentLength||0)});p?p.sendProgressiveData(e):m.push(e);f.read().then(readChunk,t)}catch(e){t(e)}};f.read().then(readChunk,t)})).catch((function(e){g.reject(e);i=null}));i=e=>{d.cancelAllRequests(e)};return g.promise})(e).then((function(e){if(r){e.terminate(new AbortException("Worker was terminated."));throw new Error("Worker was terminated")}a=e;a.requestLoadedStream(!0).then((e=>{u.send("DataLoaded",{length:e.bytes.byteLength})}))})).then(pdfManagerReady,onFailure)}u.on("GetPage",(function(e){return a.getPage(e.pageIndex).then((function(e){return Promise.all([a.ensure(e,"rotate"),a.ensure(e,"ref"),a.ensure(e,"userUnit"),a.ensure(e,"view")]).then((function([e,t,a,r]){return{rotate:e,ref:t,refStr:t?.toString()??null,userUnit:a,view:r}}))}))}));u.on("GetPageIndex",(function(e){const t=Ref.get(e.num,e.gen);return a.ensureCatalog("getPageIndex",[t])}));u.on("GetDestinations",(function(e){return a.ensureCatalog("destinations")}));u.on("GetDestination",(function(e){return a.ensureCatalog("getDestination",[e.id])}));u.on("GetPageLabels",(function(e){return a.ensureCatalog("pageLabels")}));u.on("GetPageLayout",(function(e){return a.ensureCatalog("pageLayout")}));u.on("GetPageMode",(function(e){return a.ensureCatalog("pageMode")}));u.on("GetViewerPreferences",(function(e){return a.ensureCatalog("viewerPreferences")}));u.on("GetOpenAction",(function(e){return a.ensureCatalog("openAction")}));u.on("GetAttachments",(function(e){return a.ensureCatalog("attachments")}));u.on("GetDocJSActions",(function(e){return a.ensureCatalog("jsActions")}));u.on("GetPageJSActions",(function({pageIndex:e}){return a.getPage(e).then((e=>a.ensure(e,"jsActions")))}));u.on("GetOutline",(function(e){return a.ensureCatalog("documentOutline")}));u.on("GetOptionalContentConfig",(function(e){return a.ensureCatalog("optionalContentConfig")}));u.on("GetPermissions",(function(e){return a.ensureCatalog("permissions")}));u.on("GetMetadata",(function(e){return Promise.all([a.ensureDoc("documentInfo"),a.ensureCatalog("metadata")])}));u.on("GetMarkInfo",(function(e){return a.ensureCatalog("markInfo")}));u.on("GetData",(function(e){return a.requestLoadedStream().then((e=>e.bytes))}));u.on("GetAnnotations",(function({pageIndex:e,intent:t}){return a.getPage(e).then((function(a){const r=new WorkerTask(`GetAnnotations: page ${e}`);startWorkerTask(r);return a.getAnnotationsData(u,r,t).then((e=>{finishWorkerTask(r);return e}),(e=>{finishWorkerTask(r);throw e}))}))}));u.on("GetFieldObjects",(function(e){return a.ensureDoc("fieldObjects").then((e=>e?.allFields||null))}));u.on("HasJSActions",(function(e){return a.ensureDoc("hasJSActions")}));u.on("GetCalculationOrderIds",(function(e){return a.ensureDoc("calculationOrderIds")}));u.on("SaveDocument",(async function({isPureXfa:e,numPages:t,annotationStorage:r,filename:i}){const n=[a.requestLoadedStream(),a.ensureCatalog("acroForm"),a.ensureCatalog("acroFormRef"),a.ensureDoc("startXRef"),a.ensureDoc("xref"),a.ensureDoc("linearization"),a.ensureCatalog("structTreeRoot")],s=new RefSetCache,o=[],c=e?null:getNewAnnotationsMap(r),[l,h,d,f,g,p,m]=await Promise.all(n),b=g.trailer.getRaw("Root")||null;let y;if(c){m?await m.canUpdateStructTree({pdfManager:a,newAnnotationsByPage:c})&&(y=m):await StructTreeRoot.canCreateStructureTree({catalogRef:b,pdfManager:a,newAnnotationsByPage:c})&&(y=null);const e=AnnotationFactory.generateImages(r.values(),g,a.evaluatorOptions.isOffscreenCanvasSupported),t=void 0===y?o:[];for(const[r,i]of c)t.push(a.getPage(r).then((t=>{const a=new WorkerTask(`Save (editor): page ${r}`);startWorkerTask(a);return t.saveNewAnnotations(u,a,i,e,s).finally((function(){finishWorkerTask(a)}))})));null===y?o.push(Promise.all(t).then((async()=>{await StructTreeRoot.createStructureTree({newAnnotationsByPage:c,xref:g,catalogRef:b,pdfManager:a,changes:s})}))):y&&o.push(Promise.all(t).then((async()=>{await y.updateStructureTree({newAnnotationsByPage:c,pdfManager:a,changes:s})})))}if(e)o.push(a.ensureDoc("serializeXfaData",[r]));else for(let e=0;ee.needAppearances)),k=h instanceof Dict&&h.get("XFA")||null;let C=null,v=!1;if(Array.isArray(k)){for(let e=0,t=k.length;e{g.resetNewTemporaryRef()}))}));u.on("GetOperatorList",(function(e,t){const r=e.pageIndex;a.getPage(r).then((function(a){const i=new WorkerTask(`GetOperatorList: page ${r}`);startWorkerTask(i);const n=s>=Ae?Date.now():0;a.getOperatorList({handler:u,sink:t,task:i,intent:e.intent,cacheKey:e.cacheKey,annotationStorage:e.annotationStorage,modifiedIds:e.modifiedIds}).then((function(e){finishWorkerTask(i);n&&info(`page=${r+1} - getOperatorList: time=${Date.now()-n}ms, len=${e.length}`);t.close()}),(function(e){finishWorkerTask(i);i.terminated||t.error(e)}))}))}));u.on("GetTextContent",(function(e,t){const{pageIndex:r,includeMarkedContent:i,disableNormalization:n}=e;a.getPage(r).then((function(e){const a=new WorkerTask("GetTextContent: page "+r);startWorkerTask(a);const o=s>=Ae?Date.now():0;e.extractTextContent({handler:u,task:a,sink:t,includeMarkedContent:i,disableNormalization:n}).then((function(){finishWorkerTask(a);o&&info(`page=${r+1} - getTextContent: time=`+(Date.now()-o)+"ms");t.close()}),(function(e){finishWorkerTask(a);a.terminated||t.error(e)}))}))}));u.on("GetStructTree",(function(e){return a.getPage(e.pageIndex).then((e=>a.ensure(e,"getStructTree")))}));u.on("FontFallback",(function(e){return a.fontFallback(e.id,u)}));u.on("Cleanup",(function(e){return a.cleanup(!0)}));u.on("Terminate",(function(e){r=!0;const t=[];if(a){a.terminate(new AbortException("Worker was terminated."));const e=a.cleanup();t.push(e);a=null}else clearGlobalCaches();i?.(new AbortException("Worker was terminated."));for(const e of n){t.push(e.finished);e.terminate()}return Promise.all(t).then((function(){u.destroy();u=null}))}));u.on("Ready",(function(t){setupDoc(e);e=null}));return h}static initializeFromPort(e){const t=new MessageHandler("worker","main",e);this.setup(t,e);t.send("ready",null)}}globalThis.pdfjsWorker={WorkerMessageHandler};export{WorkerMessageHandler}; \ No newline at end of file diff --git a/src/configs/AI-history.json b/src/configs/AI-history.json deleted file mode 100644 index 0637a08..0000000 --- a/src/configs/AI-history.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/src/configs/agenda-config.json b/src/configs/agenda-config.json deleted file mode 100644 index 960b0ac..0000000 --- a/src/configs/agenda-config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "discardedItems": [] -} diff --git a/src/configs/banking-allocations.json b/src/configs/banking-allocations.json deleted file mode 100644 index 0637a08..0000000 --- a/src/configs/banking-allocations.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/src/configs/banking-epochs.json b/src/configs/banking-epochs.json deleted file mode 100644 index fe51488..0000000 --- a/src/configs/banking-epochs.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/src/configs/config-manager.js b/src/configs/config-manager.js index b76bad5..8615fcb 100644 --- a/src/configs/config-manager.js +++ b/src/configs/config-manager.js @@ -1,7 +1,58 @@ const fs = require('fs'); const path = require('path'); -const configFilePath = path.join(__dirname, 'oasis-config.json'); +const os = require('os'); +const legacyConfigPath = path.join(__dirname, 'oasis-config.json'); +const stateDir = process.env.OASIS_STATE_DIR || process.env.ssb_path || path.join(os.homedir(), '.ssb'); +const oasisDir = path.join(stateDir, 'oasis'); +const configFilePath = path.join(oasisDir, 'oasis-config.json'); +const defaultServerConfigPath = path.join(__dirname, 'server-config.json'); +const serverConfigFilePath = path.join(oasisDir, 'oasis-server-config.json'); +for (const [from, to] of [[path.join(stateDir, 'oasis-config.json'), configFilePath], [path.join(stateDir, 'oasis-server-config.json'), serverConfigFilePath]]) { + try { + if (!fs.existsSync(to) && fs.existsSync(from)) { + fs.mkdirSync(oasisDir, { recursive: true, mode: 0o700 }); + fs.renameSync(from, to); + } + } catch (_) {} +} +const writeStateFile = (file, data) => { + fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); + const tmp = `${file}.tmp-${process.pid}-${Date.now()}`; + fs.writeFileSync(tmp, data, { encoding: 'utf8', mode: 0o600 }); + fs.renameSync(tmp, file); + try { fs.chmodSync(file, 0o600); } catch (_) {} +}; +let configCache = { at: 0, cfg: null }; +const CONFIG_TTL_MS = 2000; +const writeConfigFile = (data) => { configCache = { at: 0, cfg: null }; return writeStateFile(configFilePath, data); }; +const readJsonFile = (file) => { try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (_) { return null; } }; +const isPlainObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v); +const mergeDeep = (base, over) => { + if (!isPlainObject(base) || !isPlainObject(over)) return over === undefined ? base : over; + const out = { ...base }; + for (const [k, v] of Object.entries(over)) out[k] = isPlainObject(v) && isPlainObject(base[k]) ? mergeDeep(base[k], v) : v; + return out; +}; +if (!fs.existsSync(serverConfigFilePath)) { + const shipped = readJsonFile(defaultServerConfigPath); + const hops = shipped && shipped.friends ? shipped.friends.hops : undefined; + try { + if (shipped && shipped.pub === true) writeStateFile(serverConfigFilePath, JSON.stringify(shipped, null, 2)); + else if (Number.isFinite(hops) && hops !== 2) writeStateFile(serverConfigFilePath, JSON.stringify({ friends: { hops } }, null, 2)); + } catch (_) {} +} +const readServerConfig = () => mergeDeep(readJsonFile(defaultServerConfigPath) || {}, readJsonFile(serverConfigFilePath) || {}); +const saveServerConfig = (patch) => writeStateFile(serverConfigFilePath, JSON.stringify(mergeDeep(readJsonFile(serverConfigFilePath) || {}, patch), null, 2)); +const inviteCodesPath = path.join(__dirname, 'invite-codes.json'); +const readInviteCodes = () => { + const raw = readJsonFile(inviteCodesPath); + return (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((e) => e && typeof e.code === 'string' && e.code.trim()); +}; +const defaultInvite = () => readInviteCodes()[0] || null; +if (!fs.existsSync(configFilePath) && fs.existsSync(legacyConfigPath)) { + try { writeConfigFile(fs.readFileSync(legacyConfigPath, 'utf8')); } catch (_) {} +} if (!fs.existsSync(configFilePath)) { const defaultConfig = { @@ -22,10 +73,10 @@ if (!fs.existsSync(configFilePath)) { "threadsMod": "on", "multiverseMod": "on", "pollsMod": "on", - "fediverseMod": "off", + "fediverseMod": "on", "invitesMod": "on", "walletMod": "on", - "legacyMod": "on", + "backupMod": "on", "devMod": "on", "cipherMod": "on", "bookmarksMod": "on", @@ -43,6 +94,14 @@ if (!fs.existsSync(configFilePath)) { "reportsMod": "on", "opinionsMod": "on", "padsMod": "on", + "wikiMod": "on", + "emergenciesMod": "on", + "mailingMod": "on", + "roomsMod": "on", + "phoneMod": "on", + "logisticsMod": "on", + "podcastsMod": "on", + "campaignsMod": "on", "calendarsMod": "on", "transfersMod": "on", "feedMod": "on", @@ -58,6 +117,7 @@ if (!fs.existsSync(configFilePath)) { "projectsMod": "on", "industryMod": "on", "bankingMod": "on", + "schoolMod": "on", "parliamentMod": "on", "courtsMod": "on", "favoritesMod": "on", @@ -65,6 +125,7 @@ if (!fs.existsSync(configFilePath)) { "mapsMod": "on", "chatsMod": "on", "torrentsMod": "on", + "filesMod": "on", "graphosMod": "on", "larpMod": "on" }, @@ -74,9 +135,6 @@ if (!fs.existsSync(configFilePath)) { "pass": "", "fee": "5" }, - "walletPub": { - "pubId": "" - }, "ai": { "prompt": "Provide an informative and precise response." }, @@ -87,20 +145,29 @@ if (!fs.existsSync(configFilePath)) { "language": "en", "wish": "whole", "pmVisibility": "whole", + "phone": { "visibility": "mutuals", "dnd": false, "relay": true }, "lanBroadcasting": true }; - fs.writeFileSync(configFilePath, JSON.stringify(defaultConfig, null, 2)); + writeConfigFile(JSON.stringify(defaultConfig, null, 2)); } const getConfig = () => { + const now = Date.now(); + if (!configCache.cfg || now - configCache.at > CONFIG_TTL_MS) configCache = { at: now, cfg: readConfig() }; + return structuredClone(configCache.cfg); +}; + +const readConfig = () => { const configData = fs.readFileSync(configFilePath); const cfg = JSON.parse(configData); - if (!['whole', 'mutuals', 'only-lan'].includes(cfg.wish)) cfg.wish = 'whole'; + if (!['whole', 'mutuals', 'only-lan', 'local'].includes(cfg.wish)) cfg.wish = 'whole'; if (cfg.pmVisibility !== 'whole' && cfg.pmVisibility !== 'mutuals') cfg.pmVisibility = 'whole'; + if (!cfg.phone || typeof cfg.phone !== 'object') cfg.phone = {}; + cfg.phone = { visibility: cfg.phone.visibility === 'whole' ? 'whole' : 'mutuals', dnd: cfg.phone.dnd === true, relay: cfg.phone.relay !== false, video: cfg.phone.video !== false, ...(typeof cfg.phone.videoDevice === 'string' && cfg.phone.videoDevice.trim() ? { videoDevice: cfg.phone.videoDevice.trim() } : {}) }; if (typeof cfg.ux === 'string') cfg.ux = { current: cfg.ux }; if (!cfg.ux || typeof cfg.ux !== 'object') cfg.ux = { current: 'blocks' }; if (cfg.ux.current === 'menus') cfg.ux.current = 'blocks'; - if (cfg.ux.current !== 'blocks' && cfg.ux.current !== 'ainav' && cfg.ux.current !== 'chats') cfg.ux.current = 'blocks'; + if (cfg.ux.current !== 'blocks' && cfg.ux.current !== 'ainav' && cfg.ux.current !== 'chats' && cfg.ux.current !== 'feed' && cfg.ux.current !== 'phone') cfg.ux.current = 'blocks'; if (cfg.ux.current === 'ainav' && cfg.modules && cfg.modules.aiNavMod !== 'on') cfg.ux.current = 'blocks'; if (cfg.ux.current === 'chats' && cfg.modules && cfg.modules.chatsMod !== 'on') cfg.ux.current = 'blocks'; @@ -113,14 +180,30 @@ const getConfig = () => { pins = pins.filter((k, i) => pins.indexOf(k) === i).slice(0, 4); cfg.bottomBarPins = pins; delete cfg.bottomBarPin; + if (cfg.ux.current === 'phone' && cfg.modules && cfg.modules.phoneMod === 'off') cfg.ux.current = 'blocks'; + if (cfg.ux.current === 'feed' && cfg.modules && cfg.modules.feedMod === 'off') cfg.ux.current = 'blocks'; + if (!cfg.blobCache || typeof cfg.blobCache !== 'object' || !Number.isFinite(Number(cfg.blobCache.maxMB))) cfg.blobCache = { maxMB: 0 }; + if (!Number.isFinite(Number(cfg.blobCache.pubMaxMB))) cfg.blobCache.pubMaxMB = 0; + if (cfg.modules && typeof cfg.modules === 'object') { + if (cfg.modules.backupMod === undefined) cfg.modules.backupMod = cfg.modules.legacyMod === 'off' ? 'off' : 'on'; + for (const mod of ['wikiMod', 'emergenciesMod', 'mailingMod', 'logisticsMod', 'podcastsMod', 'campaignsMod', 'filesMod', 'phoneMod', 'roomsMod']) { + if (cfg.modules[mod] === undefined) cfg.modules[mod] = 'on'; + } + } return cfg; }; const saveConfig = (newConfig) => { - fs.writeFileSync(configFilePath, JSON.stringify(newConfig, null, 2)); + writeConfigFile(JSON.stringify(newConfig, null, 2)); }; module.exports = { + configFilePath, + serverConfigFilePath, getConfig, saveConfig, + readServerConfig, + saveServerConfig, + readInviteCodes, + defaultInvite, }; diff --git a/src/configs/content_favorites.json b/src/configs/content_favorites.json deleted file mode 100644 index c036334..0000000 --- a/src/configs/content_favorites.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "audios": [], - "blogs": [], - "bookmarks": [], - "calendars": [], - "chats": [], - "documents": [], - "events": [], - "forum": [], - "housing": [], - "images": [], - "jobs": [], - "logs": [], - "maps": [], - "market": [], - "pads": [], - "polls": [], - "projects": [], - "reports": [], - "shopProducts": [], - "shops": [], - "tasks": [], - "torrents": [], - "transfers": [], - "videos": [], - "votes": [] -} diff --git a/src/configs/follow_state.json b/src/configs/follow_state.json deleted file mode 100644 index e66a46e..0000000 --- a/src/configs/follow_state.json +++ /dev/null @@ -1 +0,0 @@ -{"pending":[],"accepted":[],"lastAcceptMs":0} diff --git a/src/configs/invite-codes.json b/src/configs/invite-codes.json new file mode 100644 index 0000000..c5691fe --- /dev/null +++ b/src/configs/invite-codes.json @@ -0,0 +1,23 @@ +[ + { + "name": "SNH \"La Plaza\"", + "description": "A shared place to begin an experimental utopia ...", + "url": "https://solarnethub.com", + "code": "pub.solarnethub.com:8008:@0qSCyK3xyL71X4qKkmf84Cb2riP6OeUqxCvbP2Z6HWs=.ed25519~7mC1c1gBJe8pmnWI9eIUYpFtEPUOiuHAxbzfxP/f0Ak=", + "createdAt": "2026-05-24T00:00:00.000Z" + }, + { + "name": "t0k10 h4x0rs", + "description": "n0 l0gin. n0 m41l. n0 cl0ud...", + "url": "https://pub.elhack.net", + "code": "pub.elhack.net:8008:@R8WWz72Pz66Jw0SObjxe9Boio5WzskGpTK7wsPzeUEc=.ed25519~HaBhhmtYnYYfBA2j+JoYnckWXhPSzFGn0TCY1I9rP98=", + "createdAt": "2026-08-11T00:00:00.000Z" + }, + { + "name": "Acampada 26s", + "description": "Red de la acampada en Madrid del #26s", + "url": "https://acampada26s.net", + "code": "madrid.acampada26s.net:8008:@Wt9tL9XtJCb7sZjX0+FtDlzziFLumWo1DAbou4Q5m3o=.ed25519~tkAoj1gE6dX8AcAOLQXs22VWFWKfOeJdT+7pBPfgbDA=", + "createdAt": "2026-09-26T00:00:00.000Z" + } +] diff --git a/src/configs/oasis-config.json b/src/configs/oasis-config.json index ad8f220..e91dcac 100644 --- a/src/configs/oasis-config.json +++ b/src/configs/oasis-config.json @@ -11,9 +11,7 @@ "fediverseMod": "on", "invitesMod": "on", "walletMod": "off", - "legacyMod": "on", "devMod": "off", - "industryMod": "on", "cipherMod": "on", "bookmarksMod": "on", "videosMod": "on", @@ -34,17 +32,20 @@ "calendarsMod": "on", "transfersMod": "off", "feedMod": "on", - "pixeliaMod": "on", + "pixeliaMod": "off", "melodyMod": "on", "agendaMod": "on", "aiMod": "off", "aiNavMod": "off", "forumMod": "on", "gamesMod": "off", + "housingMod": "on", "jobsMod": "on", "shopsMod": "on", "projectsMod": "on", + "industryMod": "on", "bankingMod": "off", + "schoolMod": "on", "parliamentMod": "on", "courtsMod": "off", "favoritesMod": "on", @@ -52,15 +53,24 @@ "mapsMod": "off", "chatsMod": "on", "torrentsMod": "off", + "filesMod": "on", + "phoneMod": "on", + "roomsMod": "on", "graphosMod": "on", "larpMod": "on", - "housingMod": "off" + "backupMod": "on", + "campaignsMod": "on", + "emergenciesMod": "on", + "logisticsMod": "on", + "mailingMod": "on", + "podcastsMod": "on", + "wikiMod": "on" }, "wallet": { - "url": "http://localhost:7474", + "url": "http://127.0.0.1:7474", "user": "", "pass": "", - "fee": "5" + "fee": "0.01" }, "walletPub": { "pubId": "" @@ -76,6 +86,15 @@ "wish": "whole", "pmVisibility": "whole", "lanBroadcasting": true, + "phone": { + "visibility": "whole", + "dnd": false, + "relay": true + }, + "blobCache": { + "maxMB": 0, + "pubMaxMB": 0 + }, "bottomBarPins": [ "search", "inbox", @@ -91,4 +110,4 @@ }, "relayOnly": false } -} \ No newline at end of file +} diff --git a/src/configs/server-config.json b/src/configs/server-config.json index 7779f6f..5b3060b 100644 --- a/src/configs/server-config.json +++ b/src/configs/server-config.json @@ -11,13 +11,6 @@ "dunbar": 300, "hops": 2 }, - "gossip": { - "connections": 20, - "local": true, - "friends": true, - "seed": true, - "global": true - }, "replicationScheduler": { "autostart": true, "partialReplication": null @@ -54,7 +47,11 @@ } ], "tunnel": [], - "onion": [], + "onion": [ + { + "transform": "shs" + } + ], "ws": [] } } diff --git a/src/configs/shared-state.js b/src/configs/shared-state.js index 75ef3b3..ad758e3 100644 --- a/src/configs/shared-state.js +++ b/src/configs/shared-state.js @@ -1,28 +1,65 @@ let _inboxCount = 0; +let _phoneCount = 0; +let _phoneCall = null; +let _phoneRoom = null; +let _pubIds = new Set(); +let _inboxPmCount = 0; +let _inboxNotifCount = 0; let _carbonHcT = 0; +let _walletReady = false; +let _donatableAuthors = new Set(); let _carbonHcH = 0; let _lastRefresh = 0; let _onlinePeers = null; +let _syncedPeers = null; let _inboxUnread = null; let _lastSyncTs = null; let _ecoValue = null; let _lastActivity = null; let _maxBlockBytes = 0; let _inhabitantCount = 0; +let _tribesCount = 0; let _mentionsCount = 0; +let _mentionsTotal = 0; let _bestMatch = null; -let _dismissedSuggestion = null; +let _matchPool = []; +let _matchIdx = -1; +let _sectionMatches = new Map(); +const _dismissedSuggestions = new Set(); +let _suggestionsQuiet = false; +let _featuredEmergency = null; +let _dismissedEmergency = null; + module.exports = { getInboxCount: () => _inboxCount, setInboxCount: (n) => { _inboxCount = n; }, + getPhoneCount: () => _phoneCount, + setPhoneCount: (n) => { _phoneCount = Number(n) || 0; }, + getPhoneCall: () => _phoneCall, + setPhoneCall: (st) => { _phoneCall = st || null; }, + getPhoneRoom: () => _phoneRoom, + setPhoneRoom: (st) => { _phoneRoom = st || null; }, + getPubIds: () => _pubIds, + setPubIds: (set) => { _pubIds = set instanceof Set ? set : new Set(); }, + isPubId: (id) => _pubIds.has(String(id || '')), + getInboxPmCount: () => _inboxPmCount, + setInboxPmCount: (n) => { _inboxPmCount = n; }, + getInboxNotifCount: () => _inboxNotifCount, + setInboxNotifCount: (n) => { _inboxNotifCount = n; }, getCarbonHcT: () => _carbonHcT, setCarbonHcT: (n) => { _carbonHcT = n; }, + getDonatableAuthors: () => _donatableAuthors, + setDonatableAuthors: (set) => { _donatableAuthors = set instanceof Set ? set : new Set(); }, + getWalletReady: () => _walletReady, + setWalletReady: (v) => { _walletReady = !!v; }, getCarbonHcH: () => _carbonHcH, setCarbonHcH: (n) => { _carbonHcH = n; }, getLastRefresh: () => _lastRefresh, setLastRefresh: (t) => { _lastRefresh = t; }, getOnlinePeerCount: () => _onlinePeers, setOnlinePeerCount: (n) => { _onlinePeers = n; }, + getSyncedPeerCount: () => _syncedPeers, + setSyncedPeerCount: (n) => { _syncedPeers = Math.max(0, Number(n) || 0); }, getInboxUnreadCount: () => _inboxUnread, setInboxUnreadCount: (n) => { _inboxUnread = n; }, getLastSyncTs: () => _lastSyncTs, @@ -33,12 +70,36 @@ module.exports = { setLastActivity: (a) => { _lastActivity = a; }, getMaxBlockBytes: () => _maxBlockBytes, setMaxBlockBytes: (n) => { if (Number(n) > _maxBlockBytes) _maxBlockBytes = Number(n); }, + getMentionsTotal: () => _mentionsTotal, + setMentionsTotal: (n) => { _mentionsTotal = Math.max(0, Number(n) || 0); }, getMentionsCount: () => _mentionsCount, setMentionsCount: (n) => { _mentionsCount = Math.max(0, Number(n) || 0); }, getInhabitantCount: () => _inhabitantCount, setInhabitantCount: (n) => { _inhabitantCount = Math.max(0, Number(n) || 0); }, + getTribesCount: () => _tribesCount, + setTribesCount: (n) => { _tribesCount = Math.max(0, Number(n) || 0); }, + getFeaturedEmergency: () => _featuredEmergency, + setFeaturedEmergency: (a) => { _featuredEmergency = a || null; }, + getDismissedEmergency: () => _dismissedEmergency, + setDismissedEmergency: (id) => { _dismissedEmergency = id || null; }, getBestMatch: () => _bestMatch, setBestMatch: (m) => { _bestMatch = m || null; }, - getDismissedSuggestion: () => _dismissedSuggestion, - setDismissedSuggestion: (href) => { _dismissedSuggestion = href || null; } + setMatchPool: (pool) => { + const before = new Set(_matchPool.map(m => m.href)); + _matchPool = Array.isArray(pool) ? pool.filter(m => m && m.href) : []; + if (_matchPool.some(m => !before.has(m.href) && !_dismissedSuggestions.has(m.href))) _suggestionsQuiet = false; + }, + nextBestMatch: () => { + const avail = _matchPool.filter(m => !_dismissedSuggestions.has(m.href)); + if (!avail.length) { _bestMatch = null; return null; } + _matchIdx = (_matchIdx + 1) % avail.length; + _bestMatch = avail[_matchIdx]; + return _bestMatch; + }, + setSectionMatches: (map) => { _sectionMatches = map instanceof Map ? map : new Map(); }, + getSectionMatch: (section) => (_sectionMatches.get(String(section || '')) || []).find(m => !_dismissedSuggestions.has(m.href)) || null, + isKnownSuggestion: (href) => !!href && (_matchPool.some(m => m.href === href) || [..._sectionMatches.values()].some(list => list.some(m => m.href === href))), + dismissSuggestion: (href) => { if (href) { _dismissedSuggestions.add(href); _suggestionsQuiet = true; } }, + areSuggestionsQuiet: () => _suggestionsQuiet, + isSuggestionDismissed: (href) => _dismissedSuggestions.has(href) }; diff --git a/src/configs/snh-invite-code.json b/src/configs/snh-invite-code.json deleted file mode 100644 index 545d3b1..0000000 --- a/src/configs/snh-invite-code.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "SNH \"La Plaza\"", - "description": "A shared place to begin a utopia ...", - "url": "https://pub.solarnethub.com", - "code": "pub.solarnethub.com:8008:@0qSCyK3xyL71X4qKkmf84Cb2riP6OeUqxCvbP2Z6HWs=.ed25519~7mC1c1gBJe8pmnWI9eIUYpFtEPUOiuHAxbzfxP/f0Ak=", - "createdAt": "2026-05-24T00:00:00.000Z" -} diff --git a/src/configs/state-manager.js b/src/configs/state-manager.js new file mode 100644 index 0000000..c9f0773 --- /dev/null +++ b/src/configs/state-manager.js @@ -0,0 +1,112 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const LEGACY_CONFIG_DIR = __dirname; +const ROOT = 'oasis'; + +const FOLDERS = { + 'wallet-addresses.json': 'banking', + 'banking-address-book.json': 'banking', + 'banking-allocations.json': 'banking', + 'banking-eco-history.json': 'banking', + 'banking-epochs.json': 'banking', + 'banking-ubi-notice.json': 'banking', + 'banking-confirm-notice.json': 'banking', + 'banking-ubi-paid.json': 'banking', + 'banking-funds-history.json': 'banking', + 'content_favorites.json': 'content', + 'blob-access.json': 'content', + 'follow_state.json': 'content', + 'invites-used.json': 'content', + 'agenda-config.json': 'content', + 'AI-history.json': 'ai', + 'AI-vectors.json': 'ai', + 'AI-search-vectors.json': 'ai', + 'fediverse-accounts.json': 'multiverse', + 'nextcloud-account.json': 'multiverse', + 'oasis-backup.json': 'backup', + 'oasis-first-contact': 'flags', + 'oasis-mentions-seen': 'flags', + 'oasis-inbox-read': 'flags', + 'oasis-inbox-archived': 'flags', + 'oasis-political-seen': 'flags', + 'gossip_unfollowed.json': 'peers', + 'lan-peers.json': 'peers', + 'peer-health.json': 'peers', + 'phone-history.json': 'phone', + 'phone-seen.json': 'phone', + 'snapshot.oasissn': 'content', + 'snapshot-recent.oasissn': 'content' +}; + +const ALIASES = { + 'oasis-backup.json': ['backup.json'] +}; + +const ssbDir = () => { + try { + const resolved = require('../server/ssb_config').statePath('.'); + if (resolved) return path.resolve(resolved); + } catch (_) {} + if (process.env.OASIS_STATE_DIR) return process.env.OASIS_STATE_DIR; + return path.join(os.homedir(), '.ssb'); +}; + +const stateDir = (name) => path.join(ssbDir(), ROOT, FOLDERS[name] || ''); + +const statePath = (name) => { + const dir = stateDir(name); + const target = path.join(dir, name); + try { + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + if (!fs.existsSync(target)) { + const candidates = [name, ...(ALIASES[name] || [])] + .flatMap(n => [path.join(ssbDir(), n), path.join(LEGACY_CONFIG_DIR, n)]); + const legacy = candidates.find(p => fs.existsSync(p)); + if (legacy) { + try { fs.renameSync(legacy, target); } + catch (_) { fs.copyFileSync(legacy, target); try { fs.unlinkSync(legacy); } catch (_) {} } + } + } + } catch (_) {} + return target; +}; + +const keysDir = (base = null) => { + const root = ssbDir(); + if (base && path.resolve(base) !== root) return path.join(base, 'keys'); + const target = path.join(root, ROOT, 'keys'); + try { + if (!fs.existsSync(target)) { + fs.mkdirSync(path.dirname(target), { recursive: true }); + const legacy = path.join(root, 'keys'); + if (fs.existsSync(legacy)) fs.renameSync(legacy, target); + else fs.mkdirSync(target, { recursive: true, mode: 0o700 }); + } + } catch (_) {} + return target; +}; + +const migrateAll = () => { + for (const name of Object.keys(FOLDERS)) { + try { + const legacy = [name, ...(ALIASES[name] || [])] + .flatMap(n => [path.join(ssbDir(), n), path.join(LEGACY_CONFIG_DIR, n)]) + .find(p => fs.existsSync(p)); + if (legacy) statePath(name); + } catch (_) {} + } + for (const name of Object.keys(FOLDERS)) { + if (FOLDERS[name] !== 'flags' || name.endsWith('.json')) continue; + const legacy = path.join(ssbDir(), name); + const target = path.join(stateDir(name), name); + try { if (fs.existsSync(legacy) && fs.existsSync(target)) fs.unlinkSync(legacy); } catch (_) {} + } + for (const leftover of ['keys', 'node_modules']) { + const dir = path.join(ssbDir(), leftover); + try { if (fs.statSync(dir).isDirectory() && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir); } catch (_) {} + } +}; + +module.exports = { statePath, stateDir, ssbDir, keysDir, migrateAll }; diff --git a/src/configs/wallet-addresses.json b/src/configs/wallet-addresses.json deleted file mode 100644 index 0967ef4..0000000 --- a/src/configs/wallet-addresses.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/games/minesweeper/index.html b/src/games/minesweeper/index.html new file mode 100644 index 0000000..a415d6b --- /dev/null +++ b/src/games/minesweeper/index.html @@ -0,0 +1,241 @@ + + + + + +Minesweeper + + + +
+ ← Back to Games + MINESWEEPER +
+
+ LEVEL 1 + MINES 0 + TIME 0 + SCORE 0 + BEST - +
+
+
Click any cell to start
+
+ + +
+
Click — reveal  |  Right click — flag  |  Clear the board to level up
+ + + + diff --git a/src/games/minesweeper/thumbnail.svg b/src/games/minesweeper/thumbnail.svg new file mode 100644 index 0000000..97f31cd --- /dev/null +++ b/src/games/minesweeper/thumbnail.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 1 + 2 + 3 + 2 + 1 + 1 + 1 + 1 + + + + + + + + + + + + + MINESWEEPER + diff --git a/src/games/visualdj/index.html b/src/games/visualdj/index.html new file mode 100644 index 0000000..f2a2664 --- /dev/null +++ b/src/games/visualdj/index.html @@ -0,0 +1,945 @@ + + + + + + Visual DJ + + + +
+ ← Back to Games + VISUAL DJ +
+
+
+
Status: Paused | FPS: -- | BPM: 120 | Time: 0.0 s
+
Deck A: Plasma | Deck B: Tunnel | Mix: 50% A/B | Blend: Screen | Mic: Off
+
+ + + + + + +
+
+ +
+
+ Live controls:
+ Drag on the stage to move the crossfader, click to flash.
+ 1-8 pattern on deck A · Q-I pattern on deck B · Z-/ effects · ← → crossfade · Space flash · T tap tempo · P play/pause · F fullscreen · Shift+1-4 scenes +
+
+ +
+
+
+

Deck A

+
+
+ + + + + + + + + + +
+
+ +
+

Deck B

+
+
+ + + + + + + + + + +
+
+
+ +
+

Mixer

+
+ + + + + + + + + + + + + + + + +
+
+ +
+

Effects

+
+
+ + + + +
+
+ +
+ Scenes + +
+
+
+
+
+ + + + diff --git a/src/games/visualdj/thumbnail.svg b/src/games/visualdj/thumbnail.svg new file mode 100644 index 0000000..677c916 --- /dev/null +++ b/src/games/visualdj/thumbnail.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + VISUALDJ + diff --git a/src/maps/data/coast_50m.json b/src/maps/data/coast_50m.json new file mode 100644 index 0000000..25d07e1 --- /dev/null +++ b/src/maps/data/coast_50m.json @@ -0,0 +1 @@ +{"f":[{"b":[178.497,-16.976,180,-16.153],"l":[180,-16.153,179.848,-16.214,179.789,-16.221,179.715,-16.208,179.635,-16.223,179.552,-16.25,179.475,-16.294,179.359,-16.38,179.294,-16.399,179.225,-16.405,179.091,-16.437,178.961,-16.483,178.866,-16.54,178.805,-16.631,178.745,-16.634,178.686,-16.666,178.634,-16.649,178.584,-16.622,178.568,-16.664,178.542,-16.7,178.514,-16.726,178.497,-16.788,178.604,-16.801,178.638,-16.851,178.665,-16.92,178.707,-16.976,178.803,-16.952,178.884,-16.886,178.95,-16.904,179.007,-16.9,179.055,-16.814,179.202,-16.713,179.3,-16.71,179.323,-16.718,179.346,-16.77,179.375,-16.792,179.419,-16.807,179.465,-16.806,179.589,-16.787,179.715,-16.744,179.821,-16.737,179.928,-16.744,179.89,-16.667,179.906,-16.584,179.927,-16.552,179.93,-16.519,179.885,-16.518,179.841,-16.537,179.697,-16.632,179.568,-16.747,179.564,-16.637,179.619,-16.528,179.748,-16.446,179.794,-16.37,179.848,-16.302,179.999,-16.169]},{"b":[177.183,-17.164,177.287,-17.049],"l":[177.258,-17.054,177.287,-17.049,177.276,-17.105,177.234,-17.147,177.183,-17.164,177.21,-17.084,177.239,-17.059,177.258,-17.054]},{"b":[127.286,0.758,127.373,0.862],"l":[127.373,0.791,127.354,0.847,127.32,0.862,127.293,0.842,127.286,0.812,127.306,0.769,127.338,0.758,127.373,0.791]},{"b":[-81.422,24.63,-81.32,24.75],"l":[-81.322,24.685,-81.42,24.75,-81.422,24.733,-81.379,24.666,-81.379,24.636,-81.365,24.63,-81.335,24.65,-81.32,24.668,-81.322,24.685]},{"b":[-80.848,24.804,-80.785,24.846],"l":[-80.799,24.846,-80.839,24.818,-80.848,24.804,-80.829,24.804,-80.787,24.821,-80.785,24.835,-80.799,24.846]},{"b":[-80.665,24.898,-80.615,24.941],"l":[-80.626,24.941,-80.665,24.898,-80.638,24.903,-80.615,24.938,-80.626,24.941]},{"b":[113.185,-39.146,153.617,-10.707],"l":[129.569,-31.627,129.188,-31.66,128.946,-31.703,128.546,-31.888,128.068,-32.067,127.678,-32.151,127.32,-32.264,127.084,-32.297,126.779,-32.311,126.137,-32.257,125.917,-32.297,125.567,-32.506,125.464,-32.557,125.267,-32.614,124.759,-32.883,124.525,-32.94,124.373,-32.958,124.244,-33.015,124.126,-33.129,123.967,-33.446,123.868,-33.596,123.65,-33.836,123.507,-33.916,123.365,-33.905,123.208,-33.988,123.068,-33.901,122.956,-33.884,122.778,-33.891,122.151,-33.992,122.061,-33.874,121.946,-33.857,121.73,-33.862,121.405,-33.827,120.815,-33.871,120.531,-33.92,120.418,-33.963,120.209,-33.935,119.854,-33.975,119.729,-34.042,119.635,-34.101,119.451,-34.368,119.248,-34.456,119.081,-34.459,118.895,-34.48,118.52,-34.737,118.136,-34.987,118.006,-35.013,117.863,-35.055,117.675,-35.075,117.582,-35.098,117.144,-35.034,116.865,-35.027,116.517,-34.988,116.217,-34.866,115.987,-34.795,115.726,-34.526,115.565,-34.426,115.278,-34.304,115.195,-34.308,115.128,-34.342,115.009,-34.256,115.006,-34.145,114.973,-34.051,114.976,-33.804,114.994,-33.515,115.099,-33.58,115.182,-33.643,115.359,-33.64,115.515,-33.531,115.604,-33.372,115.683,-33.193,115.671,-33.002,115.619,-32.667,115.654,-32.597,115.708,-32.568,115.725,-32.401,115.738,-31.888,115.698,-31.695,115.455,-31.303,115.294,-30.962,115.177,-30.808,115.078,-30.56,114.995,-30.216,114.969,-30.042,114.942,-29.722,114.971,-29.54,114.959,-29.434,114.857,-29.143,114.628,-28.872,114.591,-28.772,114.592,-28.666,114.537,-28.543,114.354,-28.295,114.165,-28.081,114.133,-27.976,114.098,-27.544,114.028,-27.347,113.709,-26.848,113.333,-26.417,113.231,-26.241,113.185,-26.182,113.211,-26.174,113.253,-26.197,113.3,-26.24,113.323,-26.244,113.345,-26.208,113.343,-26.126,113.356,-26.08,113.389,-26.106,113.427,-26.198,113.547,-26.437,113.582,-26.558,113.734,-26.595,113.78,-26.563,113.836,-26.501,113.853,-26.332,113.776,-26.256,113.706,-26.224,113.589,-26.099,113.513,-25.898,113.395,-25.713,113.397,-25.647,113.451,-25.599,113.539,-25.625,113.621,-25.732,113.713,-25.831,113.698,-26.004,113.684,-26.052,113.692,-26.092,113.724,-26.13,113.766,-26.16,113.812,-26.116,113.854,-26.014,113.88,-26.028,113.942,-26.259,113.992,-26.321,114.09,-26.394,114.176,-26.337,114.216,-26.289,114.203,-26.126,114.229,-25.969,114.214,-25.852,113.993,-25.545,113.792,-25.166,113.671,-24.977,113.569,-24.693,113.504,-24.595,113.418,-24.436,113.413,-24.254,113.421,-24.132,113.49,-23.87,113.553,-23.733,113.757,-23.418,113.767,-23.283,113.765,-23.18,113.795,-23.024,113.795,-22.915,113.768,-22.813,113.683,-22.638,113.795,-22.332,113.958,-21.939,114.023,-21.881,114.124,-21.829,114.143,-21.91,114.093,-22.181,114.164,-22.323,114.142,-22.483,114.205,-22.456,114.304,-22.425,114.378,-22.342,114.417,-22.261,114.603,-21.942,114.709,-21.823,114.859,-21.736,115.162,-21.631,115.456,-21.492,115.596,-21.358,115.771,-21.242,115.894,-21.117,116.011,-21.03,116.606,-20.713,116.707,-20.654,116.836,-20.647,116.995,-20.658,117.139,-20.641,117.293,-20.713,117.406,-20.721,117.684,-20.643,117.832,-20.573,118.087,-20.419,118.199,-20.375,118.458,-20.327,118.751,-20.262,119.104,-19.995,119.359,-20.012,119.586,-20.038,119.768,-19.958,120.196,-19.909,120.434,-19.842,120.878,-19.665,120.998,-19.604,121.18,-19.478,121.338,-19.32,121.494,-19.106,121.589,-18.915,121.631,-18.817,121.722,-18.66,121.785,-18.536,121.834,-18.477,122.006,-18.394,122.262,-18.159,122.345,-18.112,122.361,-18.037,122.306,-17.995,122.237,-17.969,122.191,-17.72,122.147,-17.549,122.143,-17.428,122.16,-17.314,122.261,-17.136,122.333,-17.059,122.432,-16.97,122.523,-16.943,122.598,-16.865,122.72,-16.788,122.772,-16.71,122.848,-16.552,122.917,-16.433,122.971,-16.437,123.074,-16.715,123.142,-16.863,123.266,-17.037,123.383,-17.293,123.479,-17.41,123.525,-17.486,123.563,-17.521,123.571,-17.472,123.562,-17.415,123.608,-17.22,123.586,-17.083,123.594,-17.03,123.618,-17.008,123.664,-17.023,123.754,-17.1,123.799,-17.127,123.831,-17.121,123.829,-16.997,123.874,-16.919,123.856,-16.865,123.778,-16.868,123.745,-16.801,123.68,-16.724,123.607,-16.668,123.518,-16.541,123.49,-16.491,123.525,-16.468,123.581,-16.471,123.626,-16.416,123.646,-16.343,123.607,-16.224,123.647,-16.18,123.729,-16.192,123.859,-16.382,123.915,-16.364,123.961,-16.287,124.044,-16.265,124.13,-16.279,124.186,-16.334,124.3,-16.388,124.453,-16.382,124.53,-16.395,124.692,-16.386,124.772,-16.403,124.757,-16.373,124.669,-16.339,124.57,-16.332,124.454,-16.335,124.405,-16.299,124.388,-16.203,124.416,-16.133,124.435,-16.104,124.51,-16.116,124.577,-16.114,124.585,-16.02,124.609,-15.937,124.649,-15.87,124.648,-15.805,124.607,-15.823,124.504,-15.972,124.455,-15.851,124.382,-15.758,124.397,-15.626,124.44,-15.494,124.506,-15.475,124.562,-15.496,124.644,-15.419,124.691,-15.36,124.68,-15.311,124.693,-15.274,124.75,-15.285,124.972,-15.404,125.016,-15.467,125.063,-15.442,125.078,-15.375,125.073,-15.307,125.024,-15.317,124.909,-15.31,124.883,-15.272,124.893,-15.241,124.839,-15.161,124.914,-15.11,124.979,-15.107,125.023,-15.072,125.024,-15.024,125.038,-15.004,125.073,-15.032,125.189,-15.045,125.302,-15.107,125.356,-15.12,125.376,-15.087,125.384,-15.016,125.243,-14.945,125.239,-14.875,125.18,-14.794,125.179,-14.715,125.267,-14.648,125.285,-14.584,125.335,-14.558,125.436,-14.557,125.504,-14.502,125.58,-14.483,125.598,-14.362,125.597,-14.278,125.628,-14.257,125.705,-14.291,125.681,-14.388,125.681,-14.48,125.662,-14.529,125.691,-14.525,125.708,-14.505,125.738,-14.444,125.82,-14.469,125.84,-14.534,125.85,-14.597,125.891,-14.618,125.946,-14.52,126.021,-14.495,126.017,-14.371,126.045,-14.283,126.054,-14.217,126.101,-14.184,126.111,-14.114,126.073,-14.066,126.054,-13.977,126.119,-13.958,126.184,-14.002,126.228,-14.113,126.258,-14.164,126.299,-14.136,126.323,-14.062,126.403,-14.019,126.482,-14.079,126.57,-14.161,126.679,-14.089,126.781,-13.955,126.764,-13.873,126.776,-13.788,126.903,-13.744,127.006,-13.777,127.099,-13.867,127.293,-13.935,127.458,-14.031,127.531,-14.095,127.673,-14.195,127.763,-14.299,127.888,-14.485,128.18,-14.712,128.199,-14.752,128.16,-14.827,128.124,-14.924,128.08,-15.088,128.069,-15.329,128.112,-15.312,128.155,-15.226,128.202,-15.243,128.255,-15.299,128.259,-15.246,128.227,-15.214,128.173,-15.102,128.175,-15.043,128.218,-14.996,128.285,-14.939,128.358,-14.902,128.403,-14.869,128.41,-14.829,128.477,-14.788,128.576,-14.775,128.636,-14.781,129.058,-14.884,129.165,-14.988,129.175,-15.115,129.216,-15.16,129.238,-15.08,129.234,-14.906,129.268,-14.871,129.381,-14.898,129.459,-14.933,129.567,-15.047,129.588,-15.103,129.635,-15.14,129.65,-15.087,129.628,-15.012,129.613,-14.926,129.637,-14.851,129.763,-14.845,129.849,-14.829,129.808,-14.8,129.754,-14.79,129.663,-14.721,129.605,-14.647,129.699,-14.575,129.698,-14.557,129.608,-14.56,129.484,-14.49,129.379,-14.392,129.459,-14.213,129.62,-14.038,129.71,-13.98,129.718,-13.921,129.762,-13.812,129.789,-13.72,129.797,-13.648,129.839,-13.573,129.938,-13.502,130.073,-13.476,130.136,-13.448,130.199,-13.383,130.26,-13.302,130.135,-13.146,130.145,-13.059,130.168,-12.957,130.318,-12.883,130.4,-12.688,130.454,-12.659,130.572,-12.664,130.617,-12.647,130.61,-12.491,130.623,-12.431,130.672,-12.407,130.736,-12.428,130.777,-12.495,130.867,-12.558,130.898,-12.524,130.883,-12.455,130.874,-12.367,130.957,-12.348,131.023,-12.343,131.03,-12.271,131.02,-12.214,131.046,-12.19,131.22,-12.178,131.265,-12.119,131.292,-12.068,131.314,-12.096,131.342,-12.21,131.438,-12.277,131.726,-12.278,131.888,-12.232,131.957,-12.259,132.064,-12.281,132.182,-12.227,132.253,-12.186,132.372,-12.239,132.411,-12.295,132.442,-12.176,132.511,-12.135,132.584,-12.11,132.676,-12.13,132.713,-12.123,132.63,-12.035,132.635,-11.955,132.63,-11.836,132.645,-11.727,132.674,-11.649,132.475,-11.492,132.278,-11.468,132.134,-11.501,132.073,-11.475,131.945,-11.349,131.822,-11.302,131.812,-11.271,131.962,-11.181,132.019,-11.196,132.106,-11.281,132.155,-11.311,132.198,-11.305,132.225,-11.239,132.263,-11.204,132.334,-11.224,132.557,-11.367,132.683,-11.506,132.747,-11.469,132.857,-11.391,132.961,-11.407,133.025,-11.453,133.114,-11.622,133.185,-11.706,133.356,-11.728,133.443,-11.76,133.533,-11.816,133.654,-11.811,133.904,-11.832,134.139,-11.94,134.237,-12.008,134.351,-12.026,134.417,-12.053,134.538,-12.061,134.73,-11.984,134.816,-12.055,134.855,-12.103,135.03,-12.194,135.218,-12.222,135.352,-12.129,135.549,-12.061,135.686,-11.956,135.788,-11.907,135.885,-11.822,135.922,-11.826,135.844,-11.905,135.834,-11.951,135.896,-11.97,135.889,-11.993,135.804,-12.055,135.703,-12.152,135.704,-12.21,135.744,-12.242,135.791,-12.228,135.857,-12.179,135.938,-12.152,136.008,-12.191,136.031,-12.331,136.082,-12.422,136.193,-12.435,136.261,-12.434,136.329,-12.306,136.292,-12.196,136.25,-12.173,136.27,-12.132,136.443,-11.951,136.54,-11.958,136.61,-12.134,136.719,-12.226,136.836,-12.219,136.897,-12.244,136.947,-12.35,136.537,-12.784,136.518,-12.833,136.573,-12.912,136.594,-13.004,136.461,-13.225,136.412,-13.236,136.365,-13.176,136.294,-13.138,136.232,-13.165,136.166,-13.181,135.927,-13.304,135.929,-13.622,135.99,-13.81,135.954,-13.935,135.883,-14.153,135.806,-14.234,135.745,-14.287,135.539,-14.585,135.473,-14.657,135.405,-14.758,135.428,-14.856,135.453,-14.923,135.531,-15,135.833,-15.16,135.97,-15.27,136.205,-15.403,136.259,-15.495,136.291,-15.57,136.462,-15.655,136.584,-15.707,136.619,-15.693,136.644,-15.676,136.675,-15.675,136.705,-15.685,136.7,-15.752,136.687,-15.788,136.698,-15.835,136.785,-15.894,136.923,-15.892,137.002,-15.878,137.09,-15.941,137.169,-15.982,137.299,-16.066,137.526,-16.167,137.704,-16.233,137.913,-16.477,138.072,-16.617,138.245,-16.718,138.506,-16.79,138.626,-16.778,138.82,-16.861,139.01,-16.899,139.11,-17.014,139.145,-17.101,139.154,-17.168,139.248,-17.329,139.441,-17.381,139.69,-17.541,139.895,-17.611,139.946,-17.654,140.036,-17.703,140.21,-17.704,140.511,-17.625,140.648,-17.544,140.83,-17.414,140.916,-17.193,140.966,-17.015,141.219,-16.646,141.291,-16.463,141.356,-16.221,141.412,-16.07,141.393,-15.905,141.452,-15.605,141.581,-15.195,141.625,-15.057,141.604,-14.853,141.523,-14.47,141.559,-14.338,141.594,-14.153,141.535,-14.019,141.481,-13.927,141.473,-13.798,141.534,-13.554,141.589,-13.425,141.645,-13.259,141.614,-12.943,141.735,-12.833,141.782,-12.779,141.876,-12.778,141.92,-12.803,141.93,-12.74,141.893,-12.681,141.878,-12.613,141.852,-12.579,141.795,-12.567,141.747,-12.529,141.678,-12.491,141.689,-12.351,141.806,-12.08,141.871,-11.976,141.913,-12.019,141.961,-12.054,141.968,-11.976,141.952,-11.896,142.041,-11.632,142.139,-11.273,142.168,-10.947,142.326,-10.884,142.407,-10.802,142.456,-10.707,142.545,-10.707,142.605,-10.748,142.565,-10.819,142.553,-10.874,142.723,-11.01,142.78,-11.115,142.803,-11.214,142.837,-11.307,142.853,-11.432,142.851,-11.632,142.873,-11.821,142.934,-11.881,142.988,-11.919,143.066,-11.924,143.179,-11.954,143.153,-12.076,143.105,-12.17,143.099,-12.226,143.11,-12.304,143.191,-12.361,143.254,-12.398,143.29,-12.499,143.402,-12.64,143.398,-12.736,143.458,-12.856,143.512,-13.095,143.529,-13.304,143.587,-13.444,143.548,-13.741,143.589,-13.863,143.643,-13.964,143.707,-14.165,143.756,-14.349,143.822,-14.401,143.962,-14.463,144.106,-14.395,144.21,-14.302,144.322,-14.279,144.473,-14.232,144.586,-14.355,144.648,-14.492,144.916,-14.674,145.064,-14.791,145.18,-14.857,145.288,-14.943,145.277,-15.029,145.252,-15.097,145.276,-15.204,145.293,-15.327,145.272,-15.477,145.35,-15.702,145.375,-15.881,145.458,-16.056,145.452,-16.237,145.436,-16.305,145.426,-16.406,145.49,-16.532,145.55,-16.625,145.638,-16.726,145.755,-16.879,145.838,-16.91,145.912,-16.912,145.902,-17.07,146.05,-17.381,146.126,-17.635,146.074,-17.977,146.023,-18.176,146.032,-18.273,146.223,-18.51,146.333,-18.554,146.312,-18.667,146.297,-18.841,146.383,-18.977,146.481,-19.079,146.587,-19.139,146.692,-19.187,146.829,-19.236,147.003,-19.256,147.093,-19.333,147.139,-19.393,147.278,-19.414,147.342,-19.403,147.419,-19.378,147.471,-19.419,147.51,-19.474,147.586,-19.623,147.742,-19.77,147.853,-19.795,147.916,-19.869,148.004,-19.89,148.081,-19.899,148.19,-19.956,148.367,-20.087,148.527,-20.109,148.6,-20.145,148.759,-20.29,148.821,-20.366,148.885,-20.481,148.805,-20.492,148.73,-20.468,148.684,-20.58,148.789,-20.736,148.912,-20.845,149.061,-20.961,149.205,-21.125,149.241,-21.25,149.28,-21.3,149.329,-21.476,149.454,-21.579,149.46,-21.765,149.524,-22.024,149.596,-22.258,149.645,-22.328,149.704,-22.441,149.772,-22.426,149.822,-22.39,149.92,-22.501,149.974,-22.551,150.006,-22.522,149.942,-22.308,149.981,-22.184,150.021,-22.168,150.076,-22.164,150.143,-22.265,150.235,-22.373,150.405,-22.469,150.541,-22.559,150.58,-22.556,150.564,-22.486,150.569,-22.384,150.623,-22.367,150.672,-22.418,150.764,-22.576,150.783,-22.903,150.783,-23.177,150.843,-23.458,150.931,-23.532,150.989,-23.602,151.088,-23.696,151.154,-23.784,151.236,-23.825,151.501,-24.012,151.575,-24.034,151.691,-24.038,151.832,-24.123,151.903,-24.201,152.055,-24.494,152.13,-24.598,152.282,-24.699,152.353,-24.733,152.456,-24.802,152.493,-24.904,152.502,-24.964,152.563,-25.072,152.654,-25.202,152.789,-25.274,152.913,-25.432,152.921,-25.689,152.985,-25.816,153.028,-25.87,153.125,-25.923,153.165,-25.964,153.084,-26.304,153.162,-26.983,153.117,-27.194,153.198,-27.405,153.386,-27.769,153.428,-27.898,153.455,-28.048,153.576,-28.241,153.569,-28.533,153.617,-28.673,153.605,-28.854,153.462,-29.05,153.348,-29.29,153.347,-29.497,153.272,-29.892,153.224,-29.999,153.188,-30.164,153.031,-30.563,153.024,-30.72,153.048,-30.907,153.022,-31.087,152.982,-31.209,152.944,-31.435,152.786,-31.786,152.559,-32.046,152.545,-32.243,152.517,-32.33,152.47,-32.439,152.331,-32.558,152.247,-32.609,152.216,-32.678,152.137,-32.678,152.135,-32.7,152.188,-32.722,152.164,-32.757,151.954,-32.82,151.813,-32.901,151.668,-33.099,151.608,-33.202,151.53,-33.301,151.484,-33.347,151.463,-33.397,151.432,-33.522,151.358,-33.544,151.292,-33.581,151.323,-33.699,151.288,-33.835,151.28,-33.927,151.245,-33.985,151.202,-33.964,151.168,-33.973,151.125,-34.005,151.191,-34.015,151.232,-34.03,151.09,-34.162,150.96,-34.297,150.927,-34.387,150.871,-34.499,150.822,-34.749,150.781,-34.892,150.809,-34.994,150.805,-35.013,150.775,-35.02,150.756,-35.007,150.697,-35.042,150.681,-35.077,150.706,-35.12,150.722,-35.135,150.715,-35.155,150.69,-35.178,150.634,-35.178,150.567,-35.214,150.374,-35.584,150.292,-35.682,150.195,-35.834,150.158,-35.971,150.129,-36.12,150.095,-36.373,150.063,-36.55,149.988,-36.723,149.96,-36.846,149.951,-37.08,149.986,-37.258,149.963,-37.353,149.962,-37.444,149.933,-37.529,149.809,-37.548,149.709,-37.617,149.565,-37.73,149.481,-37.771,149.298,-37.802,148.944,-37.788,148.263,-37.831,148.131,-37.856,147.877,-37.934,147.631,-38.056,147.396,-38.219,146.857,-38.663,146.436,-38.712,146.356,-38.712,146.293,-38.7,146.217,-38.727,146.216,-38.783,146.286,-38.84,146.337,-38.894,146.427,-38.82,146.467,-38.84,146.482,-38.978,146.484,-39.065,146.457,-39.112,146.4,-39.146,146.34,-39.124,146.332,-39.077,146.254,-38.964,146.158,-38.866,146.07,-38.834,146.018,-38.867,145.935,-38.902,145.866,-38.776,145.791,-38.667,145.692,-38.656,145.606,-38.657,145.535,-38.61,145.397,-38.535,145.424,-38.477,145.463,-38.416,145.542,-38.394,145.518,-38.311,145.476,-38.244,145.366,-38.226,145.293,-38.238,145.249,-38.291,145.191,-38.384,144.96,-38.501,144.847,-38.436,144.718,-38.34,144.78,-38.347,144.911,-38.344,145.02,-38.258,145.067,-38.205,145.12,-38.091,145.05,-38.011,144.985,-37.952,144.891,-37.9,144.538,-38.077,144.465,-38.103,144.396,-38.137,144.518,-38.166,144.589,-38.158,144.665,-38.21,144.544,-38.284,144.448,-38.304,144.329,-38.348,144.102,-38.462,143.812,-38.699,143.687,-38.767,143.539,-38.821,143.338,-38.758,143.226,-38.743,143.083,-38.646,142.84,-38.581,142.612,-38.452,142.456,-38.386,142.345,-38.372,142.188,-38.399,141.925,-38.284,141.725,-38.271,141.594,-38.388,141.492,-38.38,141.424,-38.363,141.214,-38.172,141.011,-38.077,140.627,-38.028,140.39,-37.897,140.212,-37.642,139.875,-37.352,139.784,-37.246,139.742,-37.142,139.738,-37.06,139.784,-36.903,139.847,-36.748,139.857,-36.662,139.729,-36.371,139.549,-36.097,139.466,-36.01,139.245,-35.827,139.038,-35.689,138.985,-35.618,138.969,-35.581,139.067,-35.598,139.113,-35.542,139.178,-35.523,139.231,-35.598,139.289,-35.611,139.292,-35.486,139.325,-35.427,139.303,-35.399,139.283,-35.375,139.193,-35.347,139.094,-35.39,139.018,-35.443,138.915,-35.489,138.875,-35.537,138.771,-35.538,138.73,-35.551,138.522,-35.642,138.389,-35.645,138.184,-35.613,138.252,-35.487,138.333,-35.412,138.4,-35.326,138.511,-35.024,138.49,-34.764,138.436,-34.656,138.264,-34.44,138.186,-34.307,138.089,-34.17,138.041,-34.25,138.012,-34.334,137.919,-34.456,137.874,-34.727,137.692,-35.143,137.566,-35.148,137.46,-35.131,137.272,-35.179,137.144,-35.236,137.03,-35.237,136.967,-35.255,136.884,-35.24,137.014,-34.916,137.128,-34.925,137.252,-34.912,137.308,-34.917,137.391,-34.913,137.454,-34.764,137.493,-34.598,137.469,-34.49,137.459,-34.379,137.484,-34.252,137.494,-34.161,137.65,-33.859,137.781,-33.703,137.932,-33.579,137.914,-33.461,137.866,-33.314,137.852,-33.201,137.924,-33.165,137.993,-33.094,137.913,-32.771,137.863,-32.674,137.783,-32.578,137.782,-32.702,137.791,-32.823,137.68,-32.978,137.536,-33.089,137.442,-33.194,137.354,-33.43,137.237,-33.629,137.13,-33.703,137.034,-33.72,136.937,-33.75,136.783,-33.83,136.636,-33.897,136.526,-33.984,136.431,-34.03,136.121,-34.429,135.98,-34.562,135.951,-34.616,135.891,-34.661,135.903,-34.724,135.951,-34.767,135.999,-34.944,135.97,-34.982,135.919,-34.962,135.792,-34.863,135.713,-34.899,135.648,-34.94,135.481,-34.758,135.412,-34.716,135.324,-34.643,135.231,-34.58,135.191,-34.573,135.123,-34.586,135.13,-34.537,135.176,-34.497,135.217,-34.487,135.292,-34.546,135.379,-34.598,135.427,-34.602,135.45,-34.581,135.368,-34.376,135.312,-34.196,135.286,-34.142,135.219,-33.96,135.185,-33.907,135.042,-33.778,134.889,-33.626,134.847,-33.445,134.791,-33.328,134.719,-33.255,134.608,-33.19,134.301,-33.165,134.174,-32.979,134.1,-32.749,134.158,-32.733,134.227,-32.731,134.249,-32.659,134.234,-32.549,133.93,-32.412,133.787,-32.269,133.665,-32.207,133.551,-32.183,133.401,-32.188,133.212,-32.184,132.757,-31.956,132.649,-31.949,132.324,-32.02,132.215,-32.007,131.721,-31.696,131.393,-31.549,131.285,-31.521,131.144,-31.496,131.029,-31.532,130.948,-31.566,130.783,-31.604,130.13,-31.579,129.569,-31.627]},{"b":[177.255,-18.264,178.668,-17.313],"l":[178.575,-17.749,178.596,-17.699,178.592,-17.651,178.523,-17.596,178.411,-17.523,178.339,-17.438,178.309,-17.435,178.28,-17.416,178.28,-17.372,178.247,-17.329,178.188,-17.313,178.128,-17.339,177.94,-17.395,177.818,-17.388,177.618,-17.461,177.504,-17.54,177.401,-17.632,177.406,-17.682,177.423,-17.737,177.411,-17.754,177.386,-17.762,177.366,-17.786,177.36,-17.82,177.316,-17.846,177.264,-17.863,177.255,-17.915,177.263,-17.969,177.321,-18.078,177.383,-18.121,177.457,-18.148,177.636,-18.181,177.771,-18.22,177.847,-18.255,177.955,-18.264,178.064,-18.25,178.16,-18.25,178.244,-18.184,178.332,-18.135,178.423,-18.124,178.461,-18.139,178.487,-18.112,178.597,-18.109,178.668,-18.081,178.618,-17.933,178.604,-17.839,178.575,-17.749]},{"b":[164.037,-22.376,167.004,-20.142],"l":[166.942,-22.09,166.82,-22.017,166.69,-21.953,166.588,-21.873,166.493,-21.783,166.303,-21.637,166.058,-21.484,165.95,-21.442,165.885,-21.389,165.823,-21.364,165.775,-21.312,165.663,-21.267,165.582,-21.18,165.447,-21.081,165.421,-21.043,165.413,-20.981,165.381,-20.936,165.307,-20.887,165.252,-20.818,165.192,-20.769,165.112,-20.745,164.976,-20.681,164.588,-20.381,164.436,-20.282,164.315,-20.309,164.202,-20.246,164.06,-20.142,164.041,-20.173,164.037,-20.234,164.065,-20.279,164.124,-20.305,164.158,-20.348,164.152,-20.415,164.17,-20.48,164.313,-20.633,164.375,-20.739,164.455,-20.829,164.559,-20.906,164.656,-20.992,164.855,-21.202,164.927,-21.29,165.01,-21.327,165.242,-21.525,165.329,-21.58,165.428,-21.615,165.62,-21.724,165.744,-21.777,165.823,-21.854,165.933,-21.908,166.096,-21.957,166.124,-21.989,166.143,-22.044,166.177,-22.089,166.292,-22.155,166.416,-22.196,166.438,-22.232,166.468,-22.256,166.522,-22.249,166.571,-22.266,166.774,-22.376,166.835,-22.355,166.9,-22.353,166.97,-22.323,167.004,-22.262,166.942,-22.09]},{"b":[127.42,-0.893,128.9,2.199],"l":[127.687,-0.08,127.681,0.035,127.685,0.149,127.709,0.288,127.669,0.337,127.616,0.383,127.555,0.49,127.537,0.611,127.542,0.681,127.567,0.743,127.601,0.796,127.608,0.848,127.52,0.924,127.429,1.14,127.42,1.252,127.537,1.467,127.535,1.572,127.558,1.634,127.571,1.7,127.632,1.844,127.731,1.966,127.9,2.137,127.964,2.175,128.036,2.199,128.043,2.157,128.031,2.12,127.907,1.946,127.89,1.906,127.887,1.833,127.946,1.79,128.011,1.701,128.024,1.583,128.026,1.458,128.012,1.332,127.988,1.29,127.885,1.163,127.653,1.014,127.633,0.977,127.634,0.936,127.677,0.887,127.733,0.848,127.805,0.826,127.881,0.832,127.919,0.877,127.929,0.935,127.967,1.043,128.055,1.116,128.117,1.127,128.161,1.158,128.153,1.238,128.157,1.317,128.222,1.401,128.424,1.518,128.539,1.559,128.688,1.573,128.705,1.528,128.688,1.464,128.717,1.367,128.703,1.106,128.669,1.069,128.515,0.979,128.346,0.907,128.299,0.877,128.257,0.805,128.261,0.734,128.398,0.639,128.611,0.55,128.655,0.508,128.684,0.438,128.692,0.36,128.743,0.323,128.815,0.305,128.863,0.268,128.9,0.216,128.54,0.338,128.446,0.392,128.333,0.398,128.221,0.414,128.106,0.461,127.983,0.472,127.924,0.438,127.901,0.372,127.887,0.298,127.915,0.206,127.912,0.151,127.889,0.05,127.978,-0.248,128.089,-0.485,128.254,-0.732,128.335,-0.816,128.425,-0.893,128.278,-0.87,128.233,-0.788,128.046,-0.706,128.011,-0.657,127.889,-0.424,127.853,-0.38,127.741,-0.3,127.692,-0.242,127.675,-0.163,127.687,-0.08]},{"b":[68.77,-49.71,70.555,-48.656],"l":[70.32,-49.059,70.285,-49.076,70.208,-49.135,70.061,-49.136,69.984,-49.16,69.854,-49.222,69.771,-49.248,69.667,-49.265,69.611,-49.266,69.542,-49.256,69.405,-49.182,69.436,-49.124,69.572,-49.129,69.644,-49.117,69.587,-49.072,69.593,-48.971,69.535,-48.974,69.314,-49.106,69.265,-49.115,69.185,-49.11,69.052,-49.082,69.033,-49.018,69.099,-48.938,69.104,-48.9,69.136,-48.861,69.123,-48.766,69.072,-48.753,69.093,-48.724,69.081,-48.679,69.057,-48.656,69.002,-48.661,68.959,-48.694,68.9,-48.776,68.832,-48.849,68.837,-48.926,68.797,-48.995,68.77,-49.066,68.79,-49.104,68.817,-49.135,68.854,-49.141,68.883,-49.165,68.814,-49.192,68.799,-49.232,68.841,-49.285,68.818,-49.354,68.862,-49.392,68.872,-49.444,68.848,-49.5,68.811,-49.55,68.791,-49.6,68.783,-49.651,68.815,-49.7,68.873,-49.71,68.993,-49.705,69.086,-49.653,69.153,-49.53,69.275,-49.543,69.353,-49.563,69.478,-49.617,69.613,-49.651,69.682,-49.642,69.747,-49.602,69.804,-49.614,69.826,-49.645,69.919,-49.689,70.075,-49.709,70.124,-49.704,70.207,-49.665,70.216,-49.629,70.259,-49.601,70.307,-49.583,70.248,-49.531,70.166,-49.509,70.073,-49.518,70.063,-49.589,69.986,-49.582,69.856,-49.544,69.78,-49.49,69.749,-49.448,69.76,-49.43,69.818,-49.438,69.861,-49.421,69.902,-49.389,69.916,-49.349,69.993,-49.345,70.166,-49.343,70.238,-49.372,70.298,-49.425,70.338,-49.435,70.386,-49.434,70.411,-49.411,70.39,-49.366,70.485,-49.328,70.537,-49.266,70.555,-49.201,70.531,-49.137,70.484,-49.084,70.406,-49.061,70.32,-49.059]},{"b":[62.52,80.698,65.437,81.198],"l":[63.116,80.967,63.615,80.981,63.856,80.981,64.096,80.998,64.166,81.036,64.21,81.106,64.256,81.144,64.31,81.175,64.575,81.198,64.802,81.197,65.028,81.169,65.172,81.144,65.31,81.096,65.382,81.057,65.36,81.008,65.372,80.968,65.437,80.931,64.997,80.819,64.548,80.755,63.374,80.7,63.188,80.698,63.002,80.713,62.76,80.763,62.52,80.822,62.593,80.853,62.819,80.894,63.116,80.967]},{"b":[57.011,80.104,59.255,80.494],"l":[57.392,80.139,57.332,80.158,57.281,80.194,57.214,80.328,57.212,80.368,57.186,80.396,57.083,80.445,57.011,80.468,57.075,80.494,57.522,80.475,58.48,80.465,58.972,80.416,59.116,80.388,59.255,80.343,58.398,80.319,58.284,80.298,58.286,80.248,58.255,80.202,58.163,80.197,57.956,80.123,57.8,80.104,57.392,80.139]},{"b":[-28.037,70.454,-25.346,71.044],"l":[-25.468,70.78,-25.421,70.795,-25.401,70.811,-25.394,70.835,-25.397,70.862,-25.432,70.921,-25.458,70.943,-25.612,70.976,-25.661,70.998,-25.727,71.042,-25.819,71.044,-26.337,70.919,-26.622,70.876,-26.976,70.863,-27.239,70.868,-27.387,70.876,-27.617,70.914,-27.709,70.897,-27.744,70.79,-27.714,70.713,-27.805,70.642,-27.94,70.615,-27.968,70.595,-28.037,70.514,-28.035,70.487,-28.003,70.467,-27.898,70.454,-27.69,70.479,-27.105,70.531,-26.605,70.553,-26.339,70.511,-26.274,70.454,-26.218,70.454,-26.05,70.509,-25.911,70.573,-25.801,70.599,-25.402,70.653,-25.346,70.693,-25.352,70.714,-25.38,70.741,-25.468,70.78]},{"b":[159.607,-9.929,160.819,-9.269],"l":[160.682,-9.692,160.625,-9.589,160.525,-9.536,160.355,-9.422,160.065,-9.419,159.971,-9.433,159.75,-9.273,159.686,-9.269,159.626,-9.311,159.607,-9.354,159.612,-9.471,159.622,-9.532,159.68,-9.637,159.755,-9.726,159.803,-9.763,159.854,-9.792,160.002,-9.812,160.321,-9.821,160.482,-9.895,160.649,-9.929,160.713,-9.914,160.802,-9.878,160.819,-9.863,160.794,-9.767,160.751,-9.715,160.682,-9.692]},{"b":[117.219,8.367,119.687,11.346],"l":[119.297,10.751,119.313,10.687,119.287,10.574,119.224,10.477,119.186,10.439,119.143,10.409,119.08,10.386,119.024,10.354,118.845,10.131,118.82,10.105,118.728,10.035,118.533,9.794,118.344,9.603,118.115,9.347,118.024,9.27,117.983,9.253,117.932,9.251,117.885,9.241,117.745,9.098,117.593,8.968,117.53,8.903,117.418,8.767,117.35,8.714,117.256,8.541,117.229,8.457,117.219,8.367,117.311,8.44,117.413,8.496,117.469,8.511,117.517,8.538,117.54,8.596,117.572,8.642,117.68,8.678,117.78,8.729,117.889,8.798,117.99,8.877,118.069,8.984,118.134,9.101,118.229,9.168,118.35,9.201,118.435,9.256,118.504,9.333,118.57,9.423,118.774,9.767,118.755,9.862,118.782,9.916,118.835,9.949,118.949,9.993,119.192,10.061,119.219,10.101,119.232,10.152,119.285,10.252,119.369,10.327,119.422,10.354,119.541,10.379,119.595,10.407,119.687,10.5,119.684,10.552,119.616,10.707,119.527,10.953,119.562,11.046,119.533,11.102,119.535,11.157,119.56,11.267,119.553,11.314,119.501,11.346,119.465,11.294,119.341,11.033,119.306,10.974,119.261,10.845,119.297,10.751]},{"b":[121.891,10.444,123.158,11.897],"l":[123.103,11.541,122.895,11.441,122.901,11.487,122.931,11.529,122.838,11.596,122.726,11.608,122.613,11.564,122.496,11.615,122.399,11.702,122.291,11.772,122.087,11.855,122.029,11.895,121.964,11.897,121.916,11.854,121.891,11.791,121.941,11.758,122.067,11.724,122.101,11.681,122.104,11.643,122.06,11.326,122.051,11.097,122.021,10.979,121.964,10.872,121.95,10.757,121.972,10.699,121.98,10.639,121.934,10.494,121.938,10.471,121.954,10.444,121.988,10.458,122.052,10.514,122.109,10.576,122.198,10.623,122.522,10.692,122.673,10.801,122.77,10.824,122.791,10.88,122.789,10.941,122.803,10.99,122.847,11.022,122.939,11.058,123.017,11.117,123.075,11.197,123.12,11.287,123.144,11.364,123.156,11.443,123.158,11.536,123.103,11.541]},{"b":[122.4,9.053,123.568,10.994],"l":[123.511,10.923,123.257,10.994,123.222,10.989,123.024,10.912,122.983,10.887,122.97,10.836,122.969,10.766,122.958,10.698,122.906,10.603,122.856,10.553,122.817,10.504,122.852,10.395,122.867,10.284,122.866,10.125,122.856,10.087,122.713,9.99,122.648,9.982,122.523,9.979,122.471,9.962,122.426,9.896,122.4,9.823,122.411,9.694,122.563,9.483,122.61,9.443,122.665,9.41,122.772,9.371,122.867,9.32,122.948,9.108,122.995,9.059,123.065,9.053,123.131,9.064,123.192,9.088,123.229,9.121,123.293,9.217,123.321,9.273,123.322,9.317,123.308,9.357,123.15,9.606,123.149,9.659,123.163,9.715,123.162,9.864,123.187,9.933,123.266,10.059,123.296,10.125,123.344,10.325,123.407,10.459,123.493,10.582,123.528,10.662,123.568,10.781,123.563,10.816,123.511,10.923]},{"b":[124.308,10.033,125.268,11.535],"l":[125.197,10.457,125.188,10.585,125.164,10.637,125.128,10.685,125.084,10.722,125.034,10.751,125.013,10.786,125.04,10.952,125.044,11.135,125.027,11.212,124.994,11.256,124.93,11.373,124.821,11.401,124.724,11.322,124.644,11.308,124.575,11.343,124.548,11.395,124.511,11.424,124.436,11.457,124.374,11.515,124.331,11.535,124.308,11.486,124.331,11.427,124.366,11.371,124.412,11.15,124.446,10.924,124.503,10.904,124.616,10.962,124.663,10.962,124.739,10.88,124.787,10.781,124.797,10.732,124.799,10.682,124.738,10.44,124.79,10.328,124.792,10.275,124.781,10.168,124.813,10.135,124.929,10.096,125.027,10.033,125.024,10.115,125.005,10.197,124.988,10.368,125.044,10.323,125.105,10.218,125.143,10.189,125.14,10.235,125.148,10.272,125.253,10.264,125.268,10.308,125.26,10.35,125.197,10.457]},{"b":[124.295,11.05,125.749,12.573],"l":[125.627,11.234,125.574,11.238,125.582,11.279,125.609,11.323,125.593,11.378,125.506,11.544,125.492,11.594,125.5,11.655,125.497,11.714,125.464,11.772,125.457,11.953,125.513,12.055,125.503,12.136,125.536,12.191,125.481,12.252,125.409,12.285,125.352,12.293,125.32,12.322,125.328,12.387,125.31,12.446,125.24,12.528,125.15,12.573,124.84,12.535,124.566,12.526,124.295,12.569,124.326,12.404,124.385,12.244,124.446,12.153,124.529,12.079,124.572,12.055,124.677,12.021,124.75,11.933,124.796,11.896,124.821,11.852,124.884,11.775,124.936,11.755,124.995,11.765,124.998,11.702,124.979,11.638,124.917,11.558,124.945,11.479,125.034,11.341,125.088,11.287,125.156,11.267,125.233,11.145,125.312,11.142,125.432,11.113,125.628,11.132,125.674,11.121,125.736,11.05,125.749,11.074,125.704,11.165,125.627,11.234]},{"b":[120.338,12.219,121.541,13.522],"l":[121.48,12.837,121.475,12.932,121.49,13.02,121.539,13.089,121.523,13.131,121.442,13.188,121.357,13.265,121.284,13.374,121.203,13.432,121.122,13.381,121.079,13.411,121.025,13.429,120.981,13.486,120.915,13.501,120.755,13.471,120.704,13.479,120.653,13.498,120.468,13.522,120.401,13.517,120.353,13.473,120.338,13.412,120.388,13.402,120.438,13.405,120.455,13.394,120.481,13.311,120.508,13.26,120.573,13.209,120.651,13.169,120.68,13.131,120.764,12.97,120.769,12.841,120.776,12.791,120.796,12.748,120.855,12.704,120.899,12.646,120.921,12.581,120.922,12.512,120.963,12.447,121.049,12.36,121.083,12.339,121.108,12.304,121.117,12.253,121.155,12.236,121.237,12.219,121.289,12.277,121.322,12.304,121.357,12.313,121.394,12.301,121.4,12.361,121.418,12.389,121.412,12.423,121.458,12.508,121.519,12.584,121.541,12.638,121.48,12.837]},{"b":[154.709,-6.863,155.958,-5.444],"l":[155.823,-6.38,155.734,-6.296,155.638,-6.221,155.581,-6.196,155.519,-6.182,155.467,-6.145,155.373,-5.974,155.323,-5.932,155.228,-5.865,155.198,-5.828,155.187,-5.777,155.094,-5.62,154.997,-5.54,154.871,-5.521,154.818,-5.494,154.773,-5.454,154.729,-5.444,154.741,-5.545,154.709,-5.747,154.721,-5.817,154.759,-5.931,154.782,-5.971,154.87,-6.061,154.94,-6.106,155.01,-6.21,155.045,-6.234,155.202,-6.308,155.234,-6.412,155.209,-6.527,155.261,-6.626,155.344,-6.722,155.427,-6.783,155.521,-6.83,155.617,-6.856,155.719,-6.863,155.763,-6.834,155.805,-6.796,155.892,-6.762,155.915,-6.797,155.933,-6.78,155.958,-6.687,155.928,-6.565,155.882,-6.47,155.823,-6.38]},{"b":[150.746,-4.832,153.133,-2.573],"l":[153.017,-4.106,152.38,-3.582,152.329,-3.521,152.179,-3.41,152.065,-3.28,152.033,-3.251,151.807,-3.173,151.69,-3.073,151.586,-3.003,151.475,-2.942,151.315,-2.875,151.226,-2.87,151.175,-2.789,150.995,-2.688,150.825,-2.573,150.843,-2.644,150.826,-2.713,150.746,-2.739,150.848,-2.78,150.968,-2.78,151.067,-2.829,151.405,-3.037,151.465,-3.101,151.579,-3.154,151.793,-3.338,151.88,-3.4,151.973,-3.453,152.023,-3.469,152.136,-3.487,152.192,-3.506,152.279,-3.582,152.356,-3.668,152.598,-3.995,152.668,-4.132,152.697,-4.282,152.693,-4.356,152.678,-4.429,152.681,-4.498,152.74,-4.636,152.787,-4.699,152.846,-4.762,152.892,-4.832,152.966,-4.756,153.023,-4.666,153.046,-4.576,153.044,-4.476,153.112,-4.392,153.133,-4.352,153.124,-4.252,153.017,-4.106]},{"b":[137.65,-8.412,138.989,-7.38],"l":[138.77,-7.39,138.544,-7.38,138.296,-7.438,138.185,-7.495,138.082,-7.566,138.008,-7.642,137.833,-7.932,137.685,-8.262,137.65,-8.386,137.688,-8.412,137.872,-8.38,137.983,-8.382,138.296,-8.405,138.535,-8.274,138.612,-8.198,138.786,-8.059,138.893,-7.882,138.989,-7.696,138.963,-7.588,138.899,-7.512,138.802,-7.415,138.77,-7.39]},{"b":[127.878,-3.858,130.86,-2.786],"l":[130.773,-3.419,130.718,-3.411,130.671,-3.392,130.642,-3.312,130.626,-3.228,130.57,-3.131,130.379,-2.989,130.304,-2.979,130.103,-2.993,129.984,-2.977,129.755,-2.866,129.6,-2.806,129.543,-2.79,129.484,-2.786,129.427,-2.791,129.371,-2.821,129.28,-2.889,129.174,-2.933,129.116,-2.937,129.074,-2.895,129.058,-2.838,128.991,-2.829,128.911,-2.85,128.791,-2.857,128.57,-2.842,128.199,-2.866,128.113,-2.935,127.878,-3.222,127.897,-3.282,127.928,-3.341,127.928,-3.397,127.902,-3.496,127.92,-3.506,127.97,-3.444,128.03,-3.341,128.044,-3.303,128.056,-3.239,128.082,-3.184,128.132,-3.157,128.181,-3.172,128.233,-3.203,128.28,-3.241,128.419,-3.416,128.466,-3.44,128.517,-3.449,128.639,-3.433,128.677,-3.397,128.751,-3.3,128.802,-3.266,128.863,-3.235,128.925,-3.229,128.958,-3.241,128.964,-3.272,128.952,-3.304,128.967,-3.326,129.108,-3.349,129.212,-3.393,129.333,-3.409,129.468,-3.453,129.522,-3.434,129.52,-3.363,129.512,-3.329,129.545,-3.319,129.627,-3.317,129.844,-3.327,129.953,-3.392,129.981,-3.439,130.02,-3.475,130.27,-3.579,130.363,-3.625,130.58,-3.749,130.805,-3.858,130.86,-3.57,130.846,-3.533,130.773,-3.419]},{"b":[126.026,-3.824,127.244,-3.065],"l":[127.163,-3.338,127.125,-3.311,127.092,-3.278,127.063,-3.217,127.025,-3.166,126.861,-3.088,126.808,-3.069,126.555,-3.065,126.306,-3.103,126.22,-3.148,126.088,-3.105,126.05,-3.128,126.026,-3.171,126.034,-3.356,126.057,-3.421,126.147,-3.523,126.178,-3.579,126.215,-3.605,126.411,-3.711,126.547,-3.772,126.686,-3.824,126.74,-3.814,126.794,-3.789,126.87,-3.783,126.941,-3.765,127.085,-3.671,127.155,-3.647,127.23,-3.633,127.244,-3.471,127.227,-3.391,127.163,-3.338]},{"b":[119.807,-8.935,123.006,-8.093],"l":[122.978,-8.152,122.917,-8.106,122.846,-8.093,122.792,-8.127,122.759,-8.186,122.919,-8.222,122.85,-8.304,122.75,-8.353,122.604,-8.402,122.556,-8.432,122.514,-8.47,122.484,-8.514,122.467,-8.566,122.433,-8.601,122.323,-8.628,122.263,-8.625,122.067,-8.497,122.02,-8.472,121.967,-8.455,121.912,-8.482,121.863,-8.494,121.747,-8.507,121.683,-8.506,121.61,-8.526,121.548,-8.575,121.498,-8.585,121.445,-8.578,121.372,-8.551,121.277,-8.478,121.118,-8.424,121.009,-8.366,120.886,-8.327,120.751,-8.321,120.71,-8.308,120.61,-8.24,120.547,-8.26,120.486,-8.266,120.425,-8.249,120.354,-8.258,120.231,-8.29,120.099,-8.378,119.964,-8.436,119.918,-8.445,119.875,-8.42,119.866,-8.473,119.848,-8.523,119.818,-8.571,119.807,-8.623,119.808,-8.698,119.841,-8.764,119.879,-8.808,119.909,-8.858,120.012,-8.81,120.121,-8.777,120.32,-8.82,120.55,-8.802,120.781,-8.849,120.982,-8.928,121.035,-8.935,121.086,-8.926,121.138,-8.904,121.191,-8.896,121.328,-8.917,121.415,-8.815,121.5,-8.812,121.585,-8.821,121.621,-8.854,121.651,-8.899,121.738,-8.87,121.839,-8.86,122.094,-8.745,122.186,-8.73,122.321,-8.738,122.417,-8.735,122.47,-8.725,122.554,-8.681,122.642,-8.647,122.783,-8.612,122.82,-8.596,122.847,-8.562,122.811,-8.481,122.902,-8.416,122.924,-8.381,122.955,-8.354,123.006,-8.329,122.978,-8.152]},{"b":[118.959,-10.294,120.833,-9.301],"l":[120.7,-9.903,120.633,-9.806,120.556,-9.719,120.504,-9.674,120.444,-9.646,120.365,-9.655,120.291,-9.648,120.258,-9.603,120.248,-9.543,120.221,-9.506,120.058,-9.42,120.013,-9.375,119.974,-9.322,119.942,-9.301,119.851,-9.36,119.795,-9.38,119.615,-9.352,119.424,-9.37,119.296,-9.367,119.186,-9.384,119.031,-9.44,118.994,-9.472,118.959,-9.519,118.977,-9.573,119.008,-9.621,119.042,-9.669,119.085,-9.707,119.363,-9.772,119.417,-9.771,119.47,-9.761,119.601,-9.774,119.813,-9.917,119.931,-9.967,119.998,-10.04,120.052,-10.123,120.145,-10.2,120.255,-10.242,120.395,-10.263,120.439,-10.294,120.562,-10.236,120.64,-10.228,120.698,-10.207,120.804,-10.108,120.833,-10.037,120.784,-9.957,120.7,-9.903]},{"b":[116.768,-9.099,119.13,-8.089],"l":[119.044,-8.457,118.988,-8.338,118.926,-8.298,118.846,-8.293,118.794,-8.306,118.748,-8.331,118.714,-8.415,118.692,-8.393,118.671,-8.323,118.612,-8.281,118.552,-8.27,118.491,-8.271,118.433,-8.293,118.338,-8.354,118.292,-8.357,118.242,-8.318,118.203,-8.267,118.151,-8.15,118.117,-8.122,117.921,-8.089,117.868,-8.101,117.755,-8.15,117.738,-8.205,117.766,-8.279,117.815,-8.342,117.979,-8.459,118.018,-8.467,118.061,-8.464,118.1,-8.475,118.174,-8.528,118.235,-8.592,118.206,-8.652,118.104,-8.65,117.97,-8.728,117.893,-8.704,117.806,-8.711,117.712,-8.583,117.673,-8.563,117.643,-8.536,117.622,-8.46,117.567,-8.426,117.435,-8.435,117.357,-8.429,117.224,-8.375,117.165,-8.367,117.064,-8.444,116.953,-8.503,116.886,-8.508,116.835,-8.532,116.801,-8.598,116.783,-8.665,116.807,-8.811,116.772,-8.894,116.768,-8.955,116.788,-9.006,116.871,-9.046,116.958,-9.076,117.061,-9.099,117.161,-9.069,117.21,-9.034,117.265,-9.026,117.326,-9.034,117.388,-9.032,117.508,-9.008,117.732,-8.92,117.795,-8.92,117.861,-8.931,118.071,-8.851,118.132,-8.856,118.19,-8.841,118.234,-8.808,118.379,-8.675,118.4,-8.704,118.398,-8.813,118.427,-8.855,118.479,-8.856,118.674,-8.812,118.728,-8.805,118.808,-8.838,118.833,-8.833,118.837,-8.809,118.818,-8.791,118.756,-8.774,118.746,-8.735,118.821,-8.712,118.903,-8.703,118.939,-8.713,118.971,-8.741,119.006,-8.75,119.079,-8.73,119.104,-8.71,119.13,-8.668,119.101,-8.628,119.063,-8.6,119.042,-8.561,119.044,-8.457]},{"b":[115.857,-8.929,116.734,-8.204],"l":[116.402,-8.204,116.304,-8.238,116.22,-8.295,116.061,-8.437,116.078,-8.611,116.076,-8.745,116.032,-8.765,115.914,-8.758,115.869,-8.743,115.857,-8.788,115.875,-8.826,116.027,-8.873,116.239,-8.912,116.29,-8.906,116.377,-8.929,116.587,-8.886,116.559,-8.854,116.514,-8.821,116.641,-8.614,116.734,-8.387,116.719,-8.336,116.688,-8.304,116.647,-8.283,116.402,-8.204]},{"b":[114.468,-8.849,115.704,-8.066],"l":[115.549,-8.208,115.448,-8.155,115.34,-8.115,115.191,-8.067,115.154,-8.066,114.998,-8.174,114.938,-8.187,114.833,-8.183,114.62,-8.128,114.504,-8.117,114.475,-8.119,114.468,-8.166,114.479,-8.215,114.502,-8.261,114.571,-8.345,114.613,-8.378,114.731,-8.394,114.842,-8.429,114.952,-8.496,115.055,-8.573,115.106,-8.629,115.142,-8.697,115.14,-8.769,115.092,-8.829,115.145,-8.849,115.194,-8.835,115.22,-8.82,115.236,-8.798,115.247,-8.758,115.295,-8.664,115.334,-8.616,115.56,-8.514,115.661,-8.448,115.704,-8.407,115.691,-8.364,115.549,-8.208]},{"b":[105.133,-3.072,106.818,-1.505],"l":[106.366,-2.465,106.209,-2.189,106.162,-1.867,106.127,-1.8,106.08,-1.738,106.046,-1.669,106.027,-1.593,105.981,-1.539,105.91,-1.505,105.816,-1.506,105.72,-1.534,105.754,-1.659,105.701,-1.731,105.668,-1.68,105.64,-1.61,105.585,-1.527,105.46,-1.575,105.413,-1.611,105.373,-1.657,105.364,-1.705,105.387,-1.751,105.375,-1.813,105.316,-1.861,105.191,-1.917,105.138,-1.973,105.133,-2.043,105.248,-2.079,105.293,-2.114,105.343,-2.125,105.553,-2.079,105.599,-2.103,105.705,-2.133,105.786,-2.181,105.807,-2.307,105.862,-2.415,105.908,-2.452,105.939,-2.493,105.908,-2.643,105.937,-2.744,105.999,-2.825,106.126,-2.855,106.25,-2.894,106.342,-2.949,106.397,-2.967,106.449,-2.994,106.496,-3.029,106.547,-3.056,106.611,-3.071,106.667,-3.072,106.658,-3.001,106.619,-2.936,106.612,-2.896,106.679,-2.704,106.707,-2.658,106.744,-2.618,106.818,-2.573,106.366,-2.465]},{"b":[21.854,57.931,23.323,58.627],"l":[22.964,58.606,22.82,58.622,22.754,58.605,22.688,58.597,22.617,58.621,22.547,58.627,22.474,58.605,22.328,58.581,22.267,58.508,22.206,58.521,22.169,58.516,22.081,58.478,22.002,58.51,21.924,58.514,21.862,58.497,21.984,58.387,21.965,58.349,21.924,58.316,21.891,58.305,21.854,58.302,21.882,58.262,22.035,58.213,22.104,58.172,22.188,58.154,22.153,58.115,21.986,57.995,21.978,57.963,21.997,57.931,22.076,57.936,22.152,57.967,22.227,58.052,22.269,58.161,22.372,58.217,22.498,58.236,22.73,58.231,22.757,58.261,22.885,58.311,22.98,58.364,23.035,58.372,23.083,58.398,23.127,58.436,23.323,58.451,23.293,58.483,22.964,58.606]},{"b":[20.228,77.267,24.902,78.598],"l":[24.238,77.899,23.883,77.865,23.684,77.875,23.331,77.958,23.117,77.992,23.152,78.088,23.365,78.121,23.452,78.149,23.352,78.186,23.119,78.239,22.989,78.252,22.735,78.24,22.449,78.215,22.3,78.228,22.207,78.408,22.043,78.577,21.746,78.572,21.608,78.596,21.455,78.598,21.047,78.557,20.363,78.515,20.228,78.478,20.373,78.412,20.56,78.419,20.528,78.326,20.786,78.252,20.845,78.166,21.035,78.059,21.21,78.006,21.653,77.924,21.608,77.916,21.431,77.812,21.334,77.772,21.251,77.711,21.201,77.619,20.873,77.565,20.928,77.46,21.05,77.441,21.856,77.494,22.057,77.501,22.255,77.529,22.397,77.57,22.448,77.571,22.62,77.55,22.685,77.554,22.733,77.539,22.679,77.5,22.442,77.429,22.487,77.36,22.469,77.331,22.427,77.316,22.554,77.267,22.802,77.276,22.9,77.311,22.997,77.361,23.101,77.385,23.381,77.38,23.505,77.401,23.736,77.462,23.841,77.498,23.955,77.558,24.062,77.631,24.13,77.658,24.902,77.757,24.571,77.834,24.238,77.899]},{"b":[59.288,80.419,62.228,80.893],"l":[62.103,80.867,61.851,80.886,61.597,80.893,61.313,80.863,60.82,80.827,60.482,80.804,60.278,80.801,60.235,80.838,60.095,80.849,59.716,80.836,59.592,80.817,59.549,80.784,59.495,80.767,59.387,80.713,59.306,80.618,59.288,80.573,59.304,80.522,59.346,80.505,59.65,80.431,59.9,80.446,60.278,80.494,60.722,80.435,61.051,80.419,61.285,80.505,61.597,80.535,61.681,80.586,61.769,80.601,62.076,80.617,62.115,80.684,62.192,80.73,62.228,80.794,62.168,80.835,62.103,80.867]},{"b":[44.905,80.447,48.687,80.854],"l":[47.011,80.562,47.145,80.609,47.198,80.615,47.304,80.606,47.414,80.675,47.512,80.688,47.6,80.742,47.705,80.765,47.777,80.756,48.044,80.668,48.625,80.629,48.684,80.633,48.687,80.718,48.547,80.779,48.446,80.806,48.345,80.819,48.243,80.823,47.9,80.813,47.442,80.854,47.352,80.853,47.021,80.814,46.799,80.755,46.327,80.735,45.125,80.652,44.905,80.611,45.149,80.599,45.389,80.56,45.641,80.537,45.969,80.569,46.024,80.541,46.06,80.484,46.141,80.447,46.378,80.457,46.514,80.476,46.624,80.541,46.678,80.561,47.011,80.562]},{"b":[46.644,80.082,51.704,80.927],"l":[51.455,80.745,50.918,80.89,50.801,80.914,50.431,80.911,50.278,80.927,50.124,80.924,49.508,80.865,49.244,80.821,49.147,80.712,49.193,80.656,49.185,80.559,49.088,80.516,48.625,80.508,48.465,80.558,48.403,80.569,48.306,80.562,48.208,80.544,47.896,80.529,47.656,80.501,47.403,80.445,46.644,80.3,46.738,80.258,46.846,80.237,46.991,80.183,47.249,80.18,47.343,80.189,47.444,80.23,47.642,80.245,47.893,80.239,47.978,80.213,47.723,80.151,47.632,80.112,47.737,80.082,47.94,80.089,48.026,80.099,48.096,80.122,48.167,80.133,48.386,80.096,48.467,80.11,48.533,80.158,48.555,80.183,48.582,80.195,48.797,80.161,48.892,80.155,48.978,80.163,49.011,80.207,48.991,80.242,48.96,80.266,48.922,80.277,48.689,80.29,48.677,80.3,48.811,80.354,48.896,80.369,49.586,80.377,49.794,80.425,49.75,80.472,49.846,80.498,50.28,80.527,50.961,80.54,51.146,80.604,51.704,80.688,51.591,80.741,51.455,80.745]},{"b":[48.279,68.733,50.283,69.511],"l":[49.225,69.511,48.953,69.509,48.845,69.495,48.631,69.436,48.414,69.346,48.32,69.269,48.296,69.184,48.28,69.097,48.279,69.04,48.294,68.984,48.316,68.942,48.439,68.805,48.667,68.733,48.91,68.743,49.18,68.778,49.626,68.86,49.84,68.974,49.921,69.053,50.094,69.126,50.141,69.098,50.164,69.038,50.221,69.049,50.283,69.089,50.265,69.186,50.167,69.257,49.996,69.309,49.225,69.511]},{"b":[58.473,69.688,60.481,70.465],"l":[60.393,69.962,60.172,70.023,59.956,70.108,59.636,70.197,59.529,70.249,59.426,70.311,59.31,70.362,59.088,70.437,59.048,70.46,59.005,70.465,58.794,70.433,58.678,70.36,58.615,70.351,58.52,70.318,58.473,70.267,58.568,70.156,58.606,70.129,58.634,70.088,58.68,70.051,58.953,69.893,59.004,69.883,59.083,69.911,59.144,69.922,59.268,69.898,59.382,69.89,59.503,69.866,59.581,69.791,59.578,69.739,59.637,69.721,59.725,69.706,59.813,69.696,59.92,69.697,60.026,69.717,60.216,69.688,60.327,69.715,60.44,69.726,60.477,69.794,60.481,69.885,60.45,69.935,60.393,69.962]},{"b":[69.92,73.037,71.63,73.514],"l":[71.445,73.342,71.351,73.372,71.232,73.448,71.141,73.478,71.023,73.504,70.94,73.514,70.35,73.478,70.15,73.445,69.996,73.359,70.019,73.224,69.986,73.169,69.93,73.127,69.92,73.085,70.041,73.037,70.119,73.056,70.298,73.044,70.38,73.048,70.674,73.095,70.887,73.12,71.356,73.162,71.626,73.174,71.63,73.225,71.59,73.283,71.445,73.342]},{"b":[91.07,79.675,93.803,80.052],"l":[92.593,79.997,92.173,80.045,91.752,80.052,91.426,80.049,91.229,80.031,91.07,79.981,91.126,79.905,91.376,79.835,91.684,79.791,92.154,79.685,92.441,79.675,92.683,79.685,92.926,79.704,93.155,79.738,93.382,79.784,93.604,79.817,93.803,79.905,93.482,79.941,92.593,79.997]},{"b":[111.503,74.095,113.387,74.549],"l":[112.084,74.549,112.008,74.527,111.989,74.496,111.983,74.456,111.949,74.389,111.88,74.364,111.638,74.374,111.57,74.368,111.503,74.353,111.643,74.273,111.912,74.219,112.105,74.163,112.196,74.146,112.782,74.095,112.811,74.103,112.978,74.197,113.19,74.239,113.259,74.273,113.299,74.317,113.353,74.353,113.387,74.4,113.286,74.441,112.952,74.48,112.084,74.549]},{"b":[140.102,73.919,141.097,74.274],"l":[141.039,74.243,140.944,74.265,140.849,74.274,140.407,74.266,140.3,74.257,140.194,74.237,140.102,74.184,140.183,74.005,140.409,73.922,140.507,73.919,141.01,73.999,141.047,74.05,141.097,74.168,141.079,74.209,141.039,74.243]},{"b":[23.548,34.934,26.32,35.656],"l":[26.168,35.215,26.028,35.215,25.893,35.179,25.837,35.133,25.791,35.123,25.745,35.143,25.735,35.184,25.756,35.326,25.73,35.349,25.57,35.328,25.476,35.306,25.297,35.339,25.104,35.347,25.003,35.41,24.721,35.425,24.627,35.41,24.535,35.381,24.445,35.366,24.354,35.359,24.313,35.364,24.275,35.386,24.258,35.423,24.255,35.469,24.179,35.46,24.123,35.484,24.109,35.496,24.124,35.511,24.198,35.537,24.166,35.595,24.093,35.594,24.034,35.535,24.013,35.529,23.921,35.528,23.852,35.535,23.793,35.556,23.771,35.634,23.737,35.656,23.715,35.605,23.715,35.55,23.673,35.514,23.627,35.53,23.609,35.566,23.57,35.535,23.548,35.416,23.562,35.295,23.593,35.257,23.638,35.235,23.704,35.233,23.884,35.246,23.994,35.222,24.464,35.16,24.583,35.115,24.709,35.089,24.735,35.058,24.744,35.014,24.745,34.951,24.8,34.934,25.206,34.959,25.611,35.007,25.83,35.025,26.047,35.014,26.166,35.019,26.244,35.045,26.256,35.095,26.281,35.159,26.299,35.269,26.32,35.315,26.286,35.31,26.168,35.215]},{"b":[22.87,37.97,24.588,39.035],"l":[24.464,38.145,24.36,38.163,24.276,38.22,24.22,38.339,24.188,38.463,24.211,38.504,24.2,38.541,24.155,38.588,24.128,38.648,24.099,38.671,23.878,38.687,23.688,38.765,23.636,38.77,23.525,38.813,23.472,38.85,23.415,38.959,23.313,39.035,23.258,39.031,23.146,39.003,22.986,38.916,22.87,38.871,22.881,38.848,22.936,38.84,23.029,38.873,23.144,38.845,23.252,38.801,23.364,38.735,23.465,38.656,23.505,38.613,23.553,38.582,23.617,38.553,23.651,38.443,23.759,38.401,23.886,38.401,24.04,38.39,24.042,38.374,24.064,38.337,24.103,38.317,24.144,38.243,24.189,38.204,24.193,38.152,24.212,38.118,24.318,38.06,24.359,38.019,24.417,38.017,24.446,38.005,24.473,37.981,24.502,37.97,24.537,37.98,24.579,38.02,24.588,38.124,24.563,38.148,24.464,38.145]},{"b":[32.301,34.57,34.556,35.662],"l":[34.272,35.57,34.192,35.546,34.063,35.474,33.608,35.354,33.459,35.336,33.308,35.342,33.123,35.358,32.942,35.39,32.926,35.278,32.88,35.181,32.772,35.16,32.713,35.171,32.652,35.183,32.556,35.156,32.475,35.09,32.391,35.05,32.301,35.083,32.317,34.953,32.414,34.778,32.449,34.729,32.506,34.706,32.693,34.649,32.75,34.648,32.867,34.661,32.914,34.635,32.942,34.576,33.008,34.57,33.024,34.6,33.025,34.637,33.062,34.675,33.116,34.696,33.176,34.698,33.297,34.718,33.415,34.751,33.514,34.806,33.699,34.97,33.759,34.973,33.822,34.966,33.937,34.971,34.05,34.988,34.024,35.046,34.004,35.065,33.931,35.14,33.908,35.202,33.942,35.292,34.463,35.594,34.556,35.662,34.411,35.629,34.272,35.57]},{"b":[18.105,56.921,19.076,57.915],"l":[18.841,57.9,18.805,57.833,18.722,57.864,18.537,57.831,18.405,57.757,18.283,57.655,18.205,57.611,18.137,57.557,18.129,57.449,18.152,57.339,18.105,57.272,18.164,57.212,18.21,57.133,18.285,57.083,18.207,57.01,18.146,56.921,18.249,56.932,18.34,56.978,18.387,57.088,18.477,57.163,18.538,57.197,18.7,57.243,18.743,57.324,18.785,57.361,18.844,57.386,18.908,57.398,18.791,57.483,18.814,57.706,18.878,57.73,18.945,57.742,18.994,57.812,19.076,57.836,18.956,57.9,18.901,57.915,18.841,57.9]},{"b":[10.979,54.773,12.608,56.122],"l":[12.073,54.977,12.066,55.07,12.09,55.188,12.322,55.237,12.413,55.286,12.385,55.386,12.275,55.414,12.215,55.467,12.243,55.538,12.321,55.588,12.407,55.616,12.507,55.637,12.545,55.656,12.571,55.685,12.569,55.785,12.525,55.918,12.543,55.959,12.608,56.033,12.579,56.064,12.526,56.083,12.428,56.106,12.323,56.122,12.219,56.119,12.04,56.052,11.866,55.968,11.913,55.937,11.935,55.896,11.922,55.828,11.885,55.808,11.858,55.772,11.82,55.698,11.784,55.702,11.691,55.729,11.682,55.829,11.696,55.908,11.628,55.957,11.475,55.943,11.46,55.907,11.464,55.879,11.322,55.753,11.275,55.736,11.224,55.731,11.05,55.74,10.979,55.722,11.009,55.644,11.07,55.629,11.121,55.601,11.12,55.566,11.128,55.535,11.19,55.466,11.171,55.329,11.286,55.204,11.31,55.198,11.407,55.215,11.476,55.212,11.654,55.187,11.697,55.096,11.704,55.039,11.74,54.972,11.741,54.915,11.862,54.773,12.05,54.815,12.069,54.909,12.073,54.977]},{"b":[9.859,55.049,10.819,55.613],"l":[10.785,55.133,10.808,55.203,10.785,55.27,10.819,55.322,10.738,55.446,10.687,55.558,10.645,55.61,10.623,55.613,10.505,55.558,10.424,55.56,10.354,55.599,10.286,55.611,9.994,55.535,9.861,55.515,9.859,55.357,9.93,55.229,9.967,55.205,9.989,55.163,10.255,55.088,10.443,55.049,10.624,55.052,10.785,55.133]},{"b":[2.37,39.301,3.462,39.971],"l":[3.241,39.757,3.145,39.79,3.159,39.837,3.191,39.861,3.199,39.89,3.167,39.908,3.164,39.924,3.198,39.961,3.159,39.971,2.905,39.908,2.785,39.855,2.371,39.613,2.37,39.572,2.394,39.54,2.459,39.53,2.5,39.478,2.576,39.531,2.634,39.556,2.701,39.542,2.746,39.51,2.77,39.41,2.8,39.385,2.9,39.368,3.073,39.301,3.155,39.333,3.245,39.387,3.293,39.477,3.349,39.556,3.415,39.627,3.462,39.698,3.449,39.761,3.396,39.777,3.342,39.787,3.241,39.757]},{"b":[-123.346,-74.403,-120.272,-73.673],"l":[-120.722,-73.752,-121.497,-73.733,-121.967,-73.712,-122.436,-73.682,-122.91,-73.68,-123.012,-73.673,-123.112,-73.682,-123.249,-73.739,-123.292,-73.803,-123.346,-73.843,-123.191,-73.849,-123.035,-73.838,-122.992,-73.844,-122.951,-73.867,-122.625,-73.966,-122.71,-73.994,-122.881,-74.099,-122.875,-74.141,-122.794,-74.19,-122.765,-74.219,-122.891,-74.227,-122.956,-74.24,-122.938,-74.302,-122.859,-74.343,-122.287,-74.403,-121.062,-74.337,-121.002,-74.326,-121.005,-74.293,-121.036,-74.279,-121.054,-74.26,-121.019,-74.173,-120.99,-74.157,-120.272,-73.989,-120.312,-73.922,-120.378,-73.856,-120.556,-73.756,-120.722,-73.752]},{"b":[-127.429,-74.256,-123.8,-73.279],"l":[-127.233,-73.586,-127.232,-73.713,-127.212,-73.724,-127.122,-73.734,-127.006,-73.726,-126.902,-73.677,-126.838,-73.657,-126.711,-73.654,-126.583,-73.67,-126.538,-73.68,-126.496,-73.7,-126.466,-73.746,-126.472,-73.812,-126.244,-73.891,-125.887,-73.955,-125.683,-74.035,-125.549,-74.063,-125.421,-74.07,-125.09,-74.182,-124.873,-74.208,-124.199,-74.226,-123.982,-74.256,-123.937,-74.256,-123.837,-74.226,-123.839,-74.168,-123.811,-74.117,-123.8,-74.076,-123.851,-74.057,-123.932,-74.008,-124.129,-73.971,-124.152,-73.944,-124.101,-73.907,-124.042,-73.88,-124.129,-73.834,-124.54,-73.74,-124.617,-73.735,-124.694,-73.75,-124.993,-73.83,-125.109,-73.826,-125.224,-73.801,-125.327,-73.796,-125.552,-73.82,-125.674,-73.822,-125.799,-73.802,-125.857,-73.78,-125.86,-73.749,-125.828,-73.718,-125.723,-73.703,-125.612,-73.711,-125.276,-73.691,-125.264,-73.666,-125.326,-73.618,-125.504,-73.562,-125.561,-73.536,-125.627,-73.453,-125.736,-73.406,-125.857,-73.388,-125.976,-73.357,-126.065,-73.315,-126.33,-73.286,-126.597,-73.279,-126.83,-73.291,-126.978,-73.308,-127.124,-73.294,-127.268,-73.304,-127.394,-73.382,-127.429,-73.447,-127.414,-73.516,-127.332,-73.567,-127.233,-73.586]},{"b":[-67.809,-80.081,-65.504,-79.528],"l":[-66.785,-79.608,-66.881,-79.582,-66.979,-79.569,-67.438,-79.56,-67.688,-79.528,-67.809,-79.546,-67.771,-79.589,-67.719,-79.62,-67.077,-79.762,-66.994,-79.793,-66.962,-79.873,-66.904,-79.909,-66.41,-79.973,-66.367,-80.054,-66.319,-80.075,-66.267,-80.081,-66.174,-80.078,-65.989,-80.054,-65.899,-80.041,-65.504,-79.954,-65.54,-79.837,-65.579,-79.771,-65.87,-79.738,-66.014,-79.624,-66.274,-79.612,-66.785,-79.608]},{"b":[-58.438,-64.41,-57.222,-63.804],"l":[-57.295,-64.367,-57.338,-64.318,-57.414,-64.296,-57.328,-64.238,-57.222,-64.221,-57.273,-64.166,-57.249,-64.097,-57.517,-64.011,-57.48,-63.962,-57.593,-63.967,-57.71,-64.015,-57.741,-64.048,-57.774,-64.062,-57.809,-64.068,-57.846,-64.054,-57.827,-63.949,-57.781,-63.907,-57.779,-63.868,-57.831,-63.804,-57.926,-63.806,-57.971,-63.835,-58.07,-63.847,-58.146,-63.878,-58.275,-63.916,-58.342,-63.994,-58.425,-64.068,-58.438,-64.113,-58.398,-64.135,-58.352,-64.131,-58.25,-64.107,-58.147,-64.097,-58.162,-64.161,-58.138,-64.206,-58.02,-64.242,-58.304,-64.315,-58.214,-64.37,-58.169,-64.369,-58.022,-64.322,-57.971,-64.32,-57.921,-64.331,-57.952,-64.394,-57.91,-64.41,-57.871,-64.401,-57.823,-64.302,-57.703,-64.293,-57.671,-64.311,-57.683,-64.357,-57.581,-64.35,-57.388,-64.379,-57.295,-64.367]},{"b":[-64.272,-64.834,-62.837,-64.261],"l":[-63.131,-64.572,-63.277,-64.573,-63.181,-64.47,-63.271,-64.381,-63.23,-64.324,-63.334,-64.266,-63.486,-64.261,-63.534,-64.273,-63.606,-64.314,-63.683,-64.343,-63.668,-64.384,-63.674,-64.421,-63.916,-64.457,-63.897,-64.487,-63.867,-64.51,-64.171,-64.582,-64.226,-64.635,-64.272,-64.698,-64.184,-64.71,-64.099,-64.733,-64.007,-64.769,-63.804,-64.792,-63.77,-64.808,-63.74,-64.834,-63.647,-64.803,-63.558,-64.734,-63.458,-64.727,-63.355,-64.734,-63.275,-64.717,-63.203,-64.68,-63.026,-64.611,-62.837,-64.572,-62.928,-64.519,-63.032,-64.535,-63.131,-64.572]},{"b":[-76.512,-71.133,-73.55,-70.553],"l":[-74.205,-70.924,-73.695,-70.794,-73.55,-70.723,-73.707,-70.635,-73.879,-70.578,-73.958,-70.561,-74.038,-70.553,-74.113,-70.577,-74.115,-70.655,-74.225,-70.615,-74.401,-70.576,-74.456,-70.587,-74.469,-70.727,-74.527,-70.77,-74.59,-70.792,-74.79,-70.631,-74.898,-70.591,-74.954,-70.59,-75.007,-70.609,-75.038,-70.651,-75.06,-70.706,-75.127,-70.752,-75.21,-70.773,-76.035,-70.836,-76.249,-70.864,-76.378,-70.894,-76.5,-70.941,-76.512,-70.991,-76.421,-71.09,-76.364,-71.117,-76.271,-71.133,-76.176,-71.132,-74.806,-71.012,-74.505,-70.973,-74.205,-70.924]},{"b":[-75.813,-70.179,-74.438,-69.728],"l":[-74.849,-70.179,-74.672,-70.132,-74.578,-69.998,-74.46,-69.972,-74.438,-69.95,-74.465,-69.917,-74.55,-69.861,-74.81,-69.752,-74.987,-69.728,-75.136,-69.741,-75.179,-69.735,-75.265,-69.749,-75.314,-69.817,-75.34,-69.84,-75.681,-69.882,-75.76,-69.916,-75.813,-69.984,-75.804,-70.038,-75.764,-70.085,-75.727,-70.096,-75.268,-70.149,-74.849,-70.179]},{"b":[-102.314,-72.578,-95.531,-71.764],"l":[-99.735,-72.033,-99.784,-72.044,-99.833,-72.046,-99.985,-71.939,-100.085,-71.837,-100.219,-71.833,-100.401,-71.866,-102.128,-71.985,-102.237,-72.009,-102.288,-72.032,-102.314,-72.081,-102.265,-72.135,-102.022,-72.185,-101.903,-72.19,-101.785,-72.178,-101.602,-72.176,-100.357,-72.278,-100.195,-72.273,-100.104,-72.287,-100.014,-72.312,-99.672,-72.38,-99.434,-72.407,-99.149,-72.472,-98.882,-72.473,-98.641,-72.49,-98.408,-72.548,-98.163,-72.556,-97.828,-72.557,-97.596,-72.548,-97.366,-72.522,-97.25,-72.521,-97.028,-72.574,-96.915,-72.578,-96.804,-72.558,-96.693,-72.548,-96.052,-72.577,-96.03,-72.554,-96.014,-72.525,-96.078,-72.454,-95.826,-72.439,-95.575,-72.41,-95.531,-72.249,-95.61,-72.175,-95.609,-72.068,-95.685,-72.057,-95.906,-72.122,-96.482,-72.208,-96.718,-72.255,-96.799,-72.259,-96.89,-72.247,-96.979,-72.222,-96.715,-72.132,-96.298,-72.045,-96.125,-71.896,-96.383,-71.836,-96.869,-71.851,-97.089,-71.944,-97.155,-72.045,-97.196,-72.091,-97.242,-72.132,-97.345,-72.189,-97.46,-72.188,-97.526,-72.149,-97.582,-72.095,-97.473,-72,-97.585,-71.883,-97.816,-71.919,-97.923,-72.117,-98.168,-72.123,-98.176,-72.018,-98.091,-71.912,-98.189,-71.82,-98.394,-71.782,-98.615,-71.764,-98.965,-71.854,-99.082,-71.933,-99.254,-71.972,-99.563,-71.945,-99.735,-72.033]},{"b":[-76.096,-73.611,-74.224,-72.82],"l":[-75.377,-72.82,-75.731,-72.879,-75.702,-72.911,-75.6,-72.953,-75.439,-72.994,-75.244,-73.009,-75.276,-73.05,-75.417,-73.052,-75.468,-73.101,-75.506,-73.109,-75.775,-73.054,-75.898,-73.056,-76.018,-73.085,-76.062,-73.109,-76.096,-73.15,-76.09,-73.203,-76.053,-73.255,-76.003,-73.288,-75.901,-73.333,-74.575,-73.611,-74.452,-73.565,-74.366,-73.464,-74.467,-73.427,-74.551,-73.369,-74.575,-73.328,-74.615,-73.311,-74.668,-73.275,-74.522,-73.244,-74.499,-73.229,-74.354,-73.098,-74.224,-72.997,-74.276,-72.951,-74.336,-72.919,-74.474,-72.894,-75.377,-72.82]},{"b":[-69.138,-67.753,-67.681,-66.624],"l":[-68.901,-67.744,-68.818,-67.753,-68.734,-67.746,-68.664,-67.723,-68.622,-67.723,-68.58,-67.733,-68.507,-67.707,-68.439,-67.656,-68.381,-67.555,-68.325,-67.532,-68.25,-67.54,-68.175,-67.558,-68.093,-67.539,-67.988,-67.474,-67.969,-67.45,-68.007,-67.418,-68.144,-67.382,-68.235,-67.372,-68.175,-67.344,-68.03,-67.3,-67.956,-67.255,-67.848,-67.219,-67.688,-67.147,-67.761,-67.123,-67.828,-67.082,-67.876,-67.062,-67.949,-67.045,-67.968,-67.032,-67.969,-66.982,-67.932,-66.845,-67.741,-66.746,-67.681,-66.709,-67.711,-66.633,-67.831,-66.624,-67.938,-66.657,-68.336,-66.802,-68.417,-66.853,-68.575,-66.993,-68.656,-67.07,-68.734,-67.157,-68.82,-67.234,-69.082,-67.403,-69.133,-67.453,-69.138,-67.515,-69.12,-67.578,-69.098,-67.603,-68.982,-67.68,-68.901,-67.744]},{"b":[-180,70.919,-177.498,71.596],"l":[-180,71.538,-179.845,71.551,-179.691,71.578,-179.546,71.582,-179.402,71.567,-179.256,71.572,-179.112,71.596,-178.994,71.593,-178.876,71.577,-178.439,71.541,-178.354,71.529,-178.215,71.482,-178.134,71.465,-178.057,71.438,-177.975,71.391,-177.817,71.34,-177.584,71.282,-177.532,71.263,-177.498,71.219,-177.524,71.167,-177.822,71.068,-178.063,71.042,-178.528,71.015,-179.157,70.94,-179.416,70.919,-179.507,70.923,-179.734,70.972,-180,70.993]},{"b":[-59.649,-52.308,-57.792,-51.27],"l":[-57.792,-51.636,-57.866,-51.605,-57.96,-51.583,-57.915,-51.534,-57.808,-51.518,-57.923,-51.404,-57.977,-51.384,-58.206,-51.405,-58.259,-51.417,-58.289,-51.458,-58.276,-51.506,-58.241,-51.551,-58.235,-51.579,-58.272,-51.575,-58.474,-51.509,-58.509,-51.484,-58.519,-51.424,-58.467,-51.412,-58.407,-51.418,-58.379,-51.373,-58.426,-51.324,-58.506,-51.308,-58.698,-51.329,-58.85,-51.27,-58.917,-51.272,-58.887,-51.358,-59.097,-51.491,-59.099,-51.59,-59.065,-51.65,-59.06,-51.685,-59.095,-51.704,-59.18,-51.712,-59.262,-51.737,-59.309,-51.78,-59.571,-51.925,-59.537,-51.971,-59.649,-52.077,-59.649,-52.134,-59.532,-52.236,-59.396,-52.308,-59.342,-52.196,-59.256,-52.183,-59.163,-52.202,-59.068,-52.173,-59.196,-52.018,-59.131,-52.008,-58.653,-52.099,-58.638,-52.023,-58.643,-51.995,-58.683,-51.936,-58.336,-51.864,-58.218,-51.822,-58.151,-51.765,-58.004,-51.743,-57.838,-51.709,-57.831,-51.685,-57.792,-51.636]},{"b":[-38.017,-54.867,-35.799,-53.984],"l":[-35.895,-54.555,-35.965,-54.568,-36.033,-54.568,-36.073,-54.554,-36.117,-54.458,-36.173,-54.382,-36.236,-54.36,-36.285,-54.289,-36.326,-54.251,-36.386,-54.279,-36.407,-54.303,-36.449,-54.308,-36.541,-54.248,-36.647,-54.262,-36.607,-54.19,-36.704,-54.108,-36.76,-54.108,-36.805,-54.101,-36.849,-54.085,-36.929,-54.081,-37.006,-54.114,-37.103,-54.066,-37.233,-54.061,-37.369,-54.009,-37.382,-53.984,-37.536,-53.994,-37.946,-53.996,-38.017,-54.008,-37.913,-54.029,-37.619,-54.042,-37.689,-54.077,-37.692,-54.135,-37.631,-54.167,-37.498,-54.156,-37.158,-54.271,-37.083,-54.312,-37.007,-54.341,-36.886,-54.339,-36.852,-54.366,-36.824,-54.404,-36.735,-54.467,-36.628,-54.496,-36.507,-54.511,-36.472,-54.534,-36.446,-54.571,-36.311,-54.694,-36.252,-54.78,-36.124,-54.853,-36.085,-54.867,-35.939,-54.834,-35.867,-54.792,-35.799,-54.763,-35.913,-54.711,-35.922,-54.637,-35.895,-54.555]},{"b":[-74.387,-43.372,-73.423,-41.796],"l":[-74.114,-43.358,-73.99,-43.357,-73.919,-43.372,-73.849,-43.367,-73.773,-43.346,-73.738,-43.291,-73.75,-43.159,-73.65,-43.127,-73.541,-43.074,-73.473,-42.993,-73.436,-42.937,-73.511,-42.847,-73.568,-42.762,-73.673,-42.704,-73.767,-42.622,-73.789,-42.586,-73.715,-42.545,-73.653,-42.529,-73.634,-42.508,-73.549,-42.493,-73.471,-42.466,-73.525,-42.393,-73.533,-42.314,-73.439,-42.278,-73.423,-42.193,-73.454,-42.166,-73.478,-42.047,-73.517,-41.981,-73.528,-41.896,-73.731,-41.877,-74.037,-41.796,-74.063,-41.823,-74.031,-41.854,-74.019,-41.891,-74.057,-42.002,-74.059,-42.056,-74.072,-42.106,-74.16,-42.216,-74.17,-42.269,-74.164,-42.325,-74.174,-42.382,-74.194,-42.436,-74.199,-42.481,-74.156,-42.591,-74.209,-42.879,-74.289,-43.079,-74.373,-43.186,-74.387,-43.232,-74.355,-43.264,-74.239,-43.319,-74.114,-43.358]},{"b":[-60.961,-52.195,-59.268,-51.354],"l":[-59.573,-51.681,-59.514,-51.627,-59.437,-51.593,-59.392,-51.556,-59.354,-51.511,-59.294,-51.479,-59.268,-51.428,-59.321,-51.384,-59.388,-51.36,-59.465,-51.411,-59.493,-51.396,-59.711,-51.439,-59.788,-51.446,-59.842,-51.403,-59.917,-51.388,-60.009,-51.411,-60.142,-51.481,-60.286,-51.462,-60.445,-51.399,-60.516,-51.354,-60.568,-51.358,-60.518,-51.428,-60.523,-51.463,-60.506,-51.485,-60.415,-51.545,-60.303,-51.58,-60.245,-51.639,-60.281,-51.656,-60.467,-51.697,-60.528,-51.696,-60.583,-51.713,-60.5,-51.757,-60.38,-51.735,-60.328,-51.718,-60.277,-51.717,-60.238,-51.734,-60.238,-51.772,-60.289,-51.801,-60.334,-51.84,-60.45,-51.877,-60.591,-51.952,-60.762,-51.946,-60.961,-52.057,-60.812,-52.148,-60.686,-52.188,-60.508,-52.195,-60.484,-52.17,-60.452,-52.16,-60.384,-52.154,-60.353,-52.14,-60.288,-52.074,-60.246,-51.986,-60.194,-51.983,-60.132,-51.994,-59.99,-51.984,-59.921,-51.97,-59.715,-51.808,-59.573,-51.681]},{"b":[-75.57,-50.019,-74.459,-48.705],"l":[-75.032,-49.836,-74.994,-49.752,-74.991,-49.606,-74.981,-49.564,-74.96,-49.533,-74.744,-49.422,-74.724,-49.424,-74.719,-49.437,-74.727,-49.452,-74.781,-49.489,-74.805,-49.516,-74.812,-49.605,-74.859,-49.634,-74.882,-49.692,-74.88,-49.726,-74.822,-49.814,-74.825,-49.879,-74.811,-49.93,-74.763,-50.011,-74.703,-50.019,-74.595,-50.007,-74.57,-49.991,-74.543,-49.919,-74.496,-49.859,-74.472,-49.786,-74.459,-49.691,-74.471,-49.669,-74.516,-49.66,-74.522,-49.623,-74.484,-49.442,-74.467,-49.295,-74.476,-49.148,-74.531,-48.813,-74.546,-48.767,-74.567,-48.755,-74.652,-48.75,-74.747,-48.709,-74.793,-48.705,-74.896,-48.733,-74.97,-48.791,-74.981,-48.819,-74.945,-48.889,-74.949,-48.96,-75.037,-49.022,-75.184,-49.084,-75.21,-49.148,-75.094,-49.185,-75.086,-49.27,-75.217,-49.293,-75.27,-49.263,-75.327,-49.269,-75.433,-49.322,-75.467,-49.359,-75.429,-49.408,-75.364,-49.462,-75.306,-49.494,-75.337,-49.628,-75.521,-49.622,-75.57,-49.697,-75.55,-49.791,-75.451,-49.77,-75.3,-49.847,-75.167,-49.856,-75.066,-49.852,-75.032,-49.836]},{"b":[-73.445,-44.86,-72.764,-44.335],"l":[-73.397,-44.774,-73.35,-44.833,-73.228,-44.86,-72.986,-44.78,-72.897,-44.712,-72.845,-44.638,-72.764,-44.549,-72.776,-44.509,-72.842,-44.458,-73.028,-44.384,-73.208,-44.335,-73.26,-44.35,-73.272,-44.394,-73.266,-44.44,-73.282,-44.49,-73.315,-44.531,-73.404,-44.596,-73.445,-44.641,-73.42,-44.725,-73.397,-44.774]},{"b":[-64.485,49.079,-61.696,49.944],"l":[-63.885,49.658,-63.777,49.602,-63.676,49.534,-63.626,49.46,-63.566,49.399,-63.042,49.225,-62.8,49.171,-62.553,49.141,-62.22,49.079,-61.801,49.094,-61.746,49.106,-61.696,49.139,-61.736,49.204,-61.817,49.284,-62.043,49.39,-62.133,49.407,-62.633,49.624,-62.859,49.705,-63.089,49.773,-63.292,49.817,-63.76,49.875,-64.131,49.942,-64.244,49.944,-64.373,49.926,-64.485,49.887,-64.44,49.828,-63.885,49.658]},{"b":[-61.495,45.573,-59.828,47.01],"l":[-59.842,45.942,-59.828,45.965,-59.934,46.019,-59.881,46.062,-59.849,46.113,-59.85,46.141,-59.865,46.16,-59.961,46.191,-60.092,46.206,-60.226,46.196,-60.244,46.27,-60.298,46.311,-60.377,46.285,-60.431,46.256,-60.505,46.204,-60.586,46.117,-60.573,46.061,-60.733,45.957,-60.705,45.933,-60.461,45.969,-60.472,45.947,-60.699,45.773,-60.738,45.751,-60.806,45.738,-60.878,45.748,-60.93,45.748,-61.059,45.703,-61.092,45.748,-61.052,45.795,-60.972,45.838,-60.971,45.856,-61.038,45.882,-60.984,45.911,-60.868,45.949,-60.865,45.983,-60.937,45.986,-61.071,45.937,-61.105,45.945,-60.912,46.045,-60.831,46.074,-60.745,46.093,-60.577,46.172,-60.534,46.215,-60.495,46.27,-60.508,46.303,-60.482,46.414,-60.384,46.613,-60.333,46.737,-60.332,46.768,-60.425,46.923,-60.431,46.963,-60.408,47.004,-60.489,47.01,-60.571,46.999,-60.617,46.976,-60.76,46.863,-60.87,46.797,-60.932,46.729,-60.983,46.65,-61.241,46.303,-61.302,46.244,-61.409,46.17,-61.481,46.06,-61.495,45.941,-61.45,45.716,-61.408,45.669,-61.323,45.598,-61.284,45.574,-61.236,45.573,-61.186,45.585,-61.084,45.582,-60.979,45.606,-60.872,45.611,-60.764,45.591,-60.673,45.591,-60.386,45.655,-60.205,45.743,-60.114,45.819,-60.016,45.88,-59.842,45.942]},{"b":[-171.819,62.956,-168.716,63.727],"l":[-168.996,63.347,-169.221,63.349,-169.295,63.358,-169.428,63.348,-169.555,63.373,-169.587,63.407,-169.624,63.431,-169.777,63.448,-170.017,63.492,-170.056,63.527,-170.082,63.577,-170.122,63.618,-170.171,63.641,-170.299,63.681,-170.43,63.699,-170.552,63.688,-170.673,63.669,-170.875,63.594,-171.035,63.585,-171.197,63.609,-171.343,63.62,-171.448,63.616,-171.463,63.64,-171.646,63.727,-171.746,63.703,-171.804,63.581,-171.818,63.53,-171.819,63.477,-171.791,63.425,-171.738,63.394,-171.632,63.351,-171.519,63.332,-171.401,63.339,-171.287,63.372,-171.176,63.416,-171.061,63.446,-170.954,63.453,-170.848,63.444,-170.527,63.379,-170.424,63.349,-170.324,63.311,-170.273,63.284,-170.243,63.232,-170.19,63.196,-170.115,63.194,-169.988,63.173,-169.863,63.14,-169.819,63.122,-169.778,63.094,-169.72,62.99,-169.676,62.956,-169.623,62.969,-169.571,62.997,-169.559,63.058,-169.471,63.121,-169.365,63.171,-169.109,63.185,-168.852,63.171,-168.761,63.214,-168.716,63.311,-168.996,63.347]},{"b":[-167.436,59.764,-165.592,60.397],"l":[-165.631,60.028,-165.712,60.069,-165.707,60.101,-165.714,60.173,-165.689,60.224,-165.696,60.282,-165.73,60.314,-165.784,60.336,-165.841,60.346,-165.995,60.331,-166.044,60.334,-166.135,60.384,-166.185,60.397,-166.247,60.391,-166.364,60.365,-166.42,60.382,-166.476,60.383,-166.599,60.339,-166.731,60.316,-166.784,60.296,-166.836,60.217,-167.252,60.234,-167.344,60.224,-167.436,60.207,-167.295,60.096,-167.139,60.009,-166.985,59.984,-166.628,59.865,-166.343,59.834,-166.262,59.815,-166.188,59.774,-166.149,59.764,-166.107,59.775,-166.131,59.82,-166.1,59.85,-165.947,59.89,-165.769,59.893,-165.592,59.913,-165.605,59.973,-165.631,60.028]},{"b":[-164.904,54.404,-163.083,55.051],"l":[-163.275,54.766,-163.337,54.783,-163.379,54.816,-163.476,54.981,-163.511,55.014,-163.553,55.038,-163.607,55.051,-163.807,55.049,-163.868,55.039,-164.145,54.955,-164.274,54.9,-164.424,54.913,-164.479,54.907,-164.53,54.881,-164.706,54.692,-164.751,54.663,-164.888,54.608,-164.904,54.568,-164.904,54.545,-164.866,54.461,-164.823,54.419,-164.744,54.407,-164.591,54.404,-164.463,54.427,-164.404,54.448,-164.347,54.482,-164.235,54.571,-164.171,54.603,-164.073,54.621,-163.583,54.626,-163.531,54.638,-163.358,54.736,-163.083,54.669,-163.089,54.686,-163.135,54.723,-163.187,54.748,-163.275,54.766]},{"b":[-78.34,17.715,-76.211,18.522],"l":[-76.35,18.152,-76.701,18.257,-76.793,18.304,-76.908,18.39,-76.959,18.402,-77.014,18.403,-77.14,18.421,-77.261,18.457,-77.354,18.466,-77.452,18.467,-77.873,18.522,-77.927,18.501,-77.978,18.468,-78.095,18.445,-78.217,18.448,-78.252,18.426,-78.326,18.35,-78.34,18.287,-78.294,18.218,-78.074,18.191,-78.044,18.174,-77.963,18.048,-77.881,18.019,-77.849,17.988,-77.768,17.877,-77.671,17.86,-77.464,17.856,-77.361,17.834,-77.28,17.78,-77.205,17.715,-77.158,17.845,-77.119,17.88,-77.071,17.901,-77.036,17.854,-76.944,17.849,-76.896,17.904,-76.853,17.974,-76.795,17.976,-76.748,17.965,-76.774,17.94,-76.669,17.928,-76.625,17.901,-76.525,17.866,-76.416,17.868,-76.301,17.88,-76.211,17.914,-76.233,17.97,-76.35,18.152]},{"b":[-67.264,17.947,-65.621,18.522],"l":[-65.629,18.381,-65.756,18.402,-65.879,18.444,-66.07,18.469,-66.093,18.469,-66.068,18.428,-66.098,18.425,-66.129,18.445,-66.153,18.471,-66.189,18.476,-66.813,18.493,-67.06,18.522,-67.113,18.515,-67.159,18.499,-67.172,18.436,-67.213,18.394,-67.264,18.365,-67.239,18.321,-67.204,18.283,-67.172,18.224,-67.174,18.153,-67.197,17.994,-67.142,17.967,-67.013,17.968,-66.961,17.954,-66.9,17.948,-66.838,17.955,-66.772,17.987,-66.598,17.978,-66.511,17.987,-66.409,17.951,-66.326,17.964,-66.286,17.95,-66.245,17.947,-66.135,17.949,-65.971,17.974,-65.834,18.057,-65.782,18.129,-65.718,18.187,-65.621,18.242,-65.629,18.381]},{"b":[-154.712,56.774,-152.215,57.972],"l":[-152.411,57.646,-152.483,57.703,-152.485,57.734,-152.419,57.782,-152.412,57.806,-152.429,57.826,-152.512,57.851,-152.616,57.849,-152.696,57.832,-152.85,57.776,-152.891,57.769,-152.898,57.824,-152.85,57.897,-152.943,57.936,-153.16,57.972,-153.226,57.958,-153.169,57.911,-153.175,57.879,-153.201,57.863,-153.2,57.82,-153.217,57.796,-153.252,57.79,-153.357,57.805,-153.39,57.798,-153.423,57.779,-153.454,57.747,-153.488,57.731,-153.524,57.731,-153.569,57.761,-153.663,57.858,-153.696,57.871,-153.769,57.88,-153.806,57.875,-153.842,57.863,-153.904,57.82,-153.906,57.791,-153.879,57.757,-153.808,57.715,-153.693,57.663,-153.69,57.641,-153.799,57.647,-153.838,57.636,-153.818,57.596,-153.798,57.443,-153.757,57.367,-153.688,57.305,-153.755,57.325,-153.805,57.358,-153.882,57.439,-153.947,57.53,-154.008,57.556,-154.016,57.567,-153.995,57.587,-154.03,57.631,-154.116,57.651,-154.179,57.652,-154.281,57.638,-154.387,57.59,-154.535,57.559,-154.673,57.446,-154.712,57.366,-154.706,57.335,-154.569,57.206,-154.499,57.037,-154.339,56.921,-154.261,56.912,-154.208,56.964,-154.184,57.005,-154.191,57.036,-154.209,57.063,-154.239,57.087,-154.27,57.099,-154.381,57.097,-154.377,57.107,-154.324,57.132,-154.244,57.143,-154.135,57.141,-154.065,57.134,-154.035,57.122,-154.025,57.108,-154.08,57.061,-154.103,57.021,-154.084,57.02,-153.999,57.05,-153.88,57.004,-153.804,56.998,-153.793,56.99,-154.071,56.821,-154.07,56.805,-154.051,56.788,-154.027,56.778,-153.973,56.774,-153.757,56.858,-153.643,56.961,-153.631,56.984,-153.633,57.01,-153.647,57.03,-153.733,57.052,-153.588,57.078,-153.524,57.103,-153.504,57.138,-153.444,57.167,-153.274,57.226,-153.052,57.238,-152.99,57.282,-152.879,57.321,-152.789,57.321,-152.714,57.331,-152.679,57.345,-152.693,57.38,-152.72,57.411,-152.781,57.453,-152.957,57.46,-152.997,57.469,-152.941,57.498,-152.912,57.508,-152.831,57.503,-152.631,57.472,-152.412,57.455,-152.381,57.46,-152.337,57.482,-152.216,57.577,-152.215,57.598,-152.237,57.615,-152.411,57.646]},{"b":[-153.381,57.994,-151.974,58.451],"l":[-152.165,58.178,-152.109,58.161,-152.069,58.178,-151.983,58.244,-151.974,58.31,-151.998,58.314,-152.037,58.307,-152.079,58.312,-152.125,58.374,-152.198,58.363,-152.316,58.413,-152.343,58.412,-152.381,58.352,-152.417,58.36,-152.478,58.4,-152.544,58.428,-152.612,58.446,-152.675,58.451,-152.841,58.416,-152.844,58.396,-152.769,58.346,-152.772,58.279,-152.815,58.276,-152.895,58.294,-152.976,58.297,-153.116,58.239,-153.381,58.087,-153.305,58.063,-152.983,57.997,-152.928,57.994,-152.841,58.014,-152.782,58.016,-152.764,58.031,-152.683,58.063,-152.639,58.102,-152.598,58.163,-152.571,58.168,-152.558,58.119,-152.538,58.101,-152.452,58.129,-152.381,58.124,-152.309,58.134,-152.305,58.154,-152.333,58.187,-152.334,58.208,-152.268,58.252,-152.252,58.251,-152.224,58.214,-152.187,58.185,-152.165,58.178]},{"b":[-131.846,55.166,-130.965,55.955],"l":[-130.998,55.728,-131.121,55.857,-131.236,55.949,-131.269,55.955,-131.625,55.832,-131.648,55.586,-131.759,55.503,-131.846,55.416,-131.842,55.359,-131.811,55.223,-131.762,55.166,-131.724,55.218,-131.641,55.299,-131.522,55.341,-131.475,55.373,-131.448,55.409,-131.422,55.368,-131.451,55.316,-131.421,55.276,-131.367,55.266,-131.316,55.269,-131.262,55.22,-131.188,55.206,-131.083,55.267,-131.014,55.379,-130.979,55.489,-130.965,55.568,-130.966,55.67,-130.998,55.728]},{"b":[-133.755,54.713,-131.976,56.339],"l":[-132.469,54.938,-132.37,54.922,-132.341,54.907,-132.266,54.802,-132.189,54.735,-132.134,54.713,-132.065,54.713,-132.022,54.726,-131.981,54.805,-131.983,54.835,-131.997,54.869,-131.997,54.901,-131.978,54.94,-131.978,54.969,-132,55.034,-131.976,55.209,-132.005,55.231,-132.166,55.218,-132.207,55.224,-132.215,55.237,-132.19,55.255,-132.158,55.3,-132.16,55.323,-132.215,55.384,-132.272,55.399,-132.418,55.483,-132.592,55.464,-132.631,55.473,-132.582,55.503,-132.548,55.544,-132.529,55.59,-132.511,55.594,-132.296,55.507,-132.196,55.479,-132.173,55.481,-132.215,55.519,-132.289,55.558,-132.43,55.687,-132.497,55.798,-132.534,55.842,-132.598,55.895,-132.758,55.995,-133.097,56.09,-133.101,56.1,-133.08,56.129,-133.075,56.156,-133.082,56.194,-133.104,56.235,-133.144,56.279,-133.203,56.32,-133.377,56.318,-133.566,56.339,-133.599,56.316,-133.594,56.216,-133.544,56.177,-133.531,56.146,-133.599,56.094,-133.755,55.999,-133.743,55.965,-133.684,55.943,-133.539,55.999,-133.371,56.036,-133.289,56.019,-133.252,55.957,-133.242,55.921,-133.308,55.886,-133.322,55.845,-133.412,55.798,-133.447,55.797,-133.537,55.832,-133.584,55.837,-133.664,55.804,-133.68,55.785,-133.64,55.749,-133.553,55.691,-133.503,55.696,-133.369,55.689,-133.343,55.651,-133.298,55.607,-133.244,55.595,-133.09,55.613,-133.033,55.59,-133.078,55.535,-133.082,55.504,-132.959,55.396,-132.971,55.376,-133.03,55.378,-133.103,55.36,-133.119,55.328,-133.061,55.301,-132.913,55.188,-132.782,55.048,-132.704,55.03,-132.683,55.074,-132.702,55.131,-132.665,55.147,-132.622,55.136,-132.627,55.11,-132.588,55.052,-132.594,54.996,-132.549,54.953,-132.486,54.95,-132.469,54.938]},{"b":[-133.979,56.452,-132.951,57.069],"l":[-133.979,57.01,-133.917,56.967,-133.823,56.924,-133.758,56.877,-133.681,56.798,-133.688,56.71,-133.658,56.596,-133.649,56.517,-133.631,56.484,-133.603,56.464,-133.484,56.452,-133.383,56.474,-133.213,56.465,-133.181,56.474,-133.158,56.495,-133.145,56.528,-133.144,56.567,-133.157,56.611,-133.178,56.645,-133.227,56.689,-133.24,56.726,-133.309,56.786,-133.332,56.819,-133.329,56.83,-133.244,56.796,-133.132,56.683,-133.035,56.621,-133.004,56.624,-132.976,56.647,-132.959,56.677,-132.954,56.713,-132.963,56.783,-132.951,56.85,-132.954,56.88,-132.996,56.93,-133.071,56.974,-133.196,57.003,-133.264,57.005,-133.3,56.972,-133.366,57.004,-133.708,57.063,-133.866,57.069,-133.962,57.043,-133.979,57.01]},{"b":[-135.823,56.216,-134.611,57.534],"l":[-134.885,57.242,-134.97,57.351,-135.065,57.417,-135.131,57.432,-135.346,57.533,-135.449,57.534,-135.487,57.517,-135.57,57.425,-135.581,57.39,-135.625,57.354,-135.681,57.333,-135.787,57.317,-135.823,57.28,-135.821,57.23,-135.768,57.1,-135.782,57.058,-135.812,57.01,-135.662,57.034,-135.609,57.071,-135.502,57.244,-135.455,57.249,-135.375,57.188,-135.341,57.082,-135.267,57.049,-135.211,57.045,-135.2,57.027,-135.315,56.932,-135.338,56.894,-135.341,56.851,-135.331,56.822,-135.285,56.8,-135.163,56.824,-135.147,56.802,-135.159,56.725,-135.097,56.703,-135.018,56.66,-134.928,56.667,-134.883,56.679,-134.875,56.67,-134.933,56.616,-134.967,56.596,-134.982,56.564,-134.981,56.519,-134.95,56.457,-134.848,56.323,-134.806,56.281,-134.75,56.241,-134.682,56.216,-134.654,56.227,-134.63,56.302,-134.632,56.436,-134.657,56.523,-134.652,56.556,-134.624,56.579,-134.611,56.603,-134.621,56.718,-134.634,56.762,-134.677,56.842,-134.769,57.054,-134.823,57.157,-134.885,57.242]},{"b":[-134.933,57.043,-133.823,58.355],"l":[-134.1,57.3,-133.925,57.337,-133.911,57.353,-133.909,57.369,-133.974,57.451,-133.921,57.492,-133.937,57.582,-133.961,57.614,-134.084,57.712,-134.267,57.885,-134.3,57.963,-134.307,58.034,-134.292,58.045,-134.25,58.049,-134.213,58.038,-134.18,58.011,-134.178,57.982,-134.105,57.879,-134.067,57.84,-134.032,57.821,-133.996,57.778,-133.925,57.671,-133.827,57.618,-133.823,57.629,-133.869,57.708,-133.904,57.789,-133.966,57.874,-134.07,57.995,-134.24,58.144,-134.426,58.139,-134.68,58.162,-134.733,58.225,-134.837,58.32,-134.923,58.355,-134.933,58.329,-134.908,58.263,-134.87,58.202,-134.82,58.147,-134.781,58.078,-134.754,57.995,-134.695,57.736,-134.66,57.638,-134.595,57.568,-134.487,57.482,-134.489,57.42,-134.576,57.232,-134.62,57.196,-134.613,57.138,-134.592,57.092,-134.555,57.058,-134.516,57.043,-134.435,57.057,-134.26,57.147,-134.1,57.3]},{"b":[-136.569,57.42,-134.873,58.269],"l":[-135.692,57.42,-135.618,57.48,-135.621,57.597,-135.609,57.651,-135.564,57.666,-135.498,57.662,-135.22,57.574,-135.085,57.511,-134.931,57.481,-134.873,57.589,-134.897,57.648,-134.979,57.724,-135.25,57.733,-135.338,57.769,-135.231,57.816,-135.165,57.796,-135.103,57.794,-134.971,57.817,-134.928,57.953,-134.955,58.015,-135.002,58.051,-135.163,58.096,-135.347,58.124,-135.375,58.122,-135.421,58.102,-135.572,58.009,-135.613,57.992,-135.671,58.012,-135.693,58.039,-135.615,58.057,-135.586,58.124,-135.587,58.147,-135.73,58.244,-135.787,58.269,-135.882,58.247,-135.947,58.206,-135.994,58.197,-136.094,58.198,-136.142,58.154,-136.144,58.098,-136.246,58.157,-136.322,58.219,-136.37,58.143,-136.454,58.108,-136.512,58.096,-136.525,58.051,-136.569,57.972,-136.46,57.873,-136.378,57.84,-136.077,57.675,-135.997,57.535,-135.911,57.447,-135.692,57.42]},{"b":[-129.184,52.29,-128.507,53.28],"l":[-128.766,52.598,-128.749,52.556,-128.736,52.468,-128.731,52.357,-128.679,52.29,-128.624,52.34,-128.577,52.452,-128.51,52.519,-128.507,52.621,-128.552,52.94,-128.633,53.113,-128.74,53.179,-128.858,53.229,-128.97,53.274,-129.033,53.28,-129.06,53.241,-129.084,53.14,-129.111,53.091,-129.178,53.018,-129.184,52.991,-129.176,52.965,-129.095,52.892,-129.085,52.822,-129.023,52.756,-128.9,52.674,-128.831,52.679,-128.77,52.751,-128.746,52.763,-128.766,52.598]},{"b":[-133.098,53.189,-131.668,54.159],"l":[-132.347,53.189,-132.011,53.265,-131.957,53.309,-131.928,53.379,-131.922,53.588,-131.889,53.714,-131.821,53.842,-131.703,53.986,-131.685,54.023,-131.668,54.141,-131.696,54.143,-131.82,54.077,-131.941,54.042,-132.134,54.034,-132.136,53.996,-132.111,53.9,-132.114,53.86,-132.152,53.807,-132.172,53.707,-132.187,53.685,-132.464,53.653,-132.535,53.652,-132.567,53.664,-132.574,53.675,-132.565,53.688,-132.215,53.815,-132.175,53.847,-132.155,53.875,-132.166,53.955,-132.216,54.028,-132.262,54.076,-132.303,54.099,-132.344,54.106,-132.564,54.069,-132.656,54.127,-132.893,54.141,-132.991,54.158,-133.048,54.159,-133.064,54.144,-133.098,54.006,-133.098,53.92,-133.079,53.837,-133.052,53.778,-132.913,53.629,-132.9,53.605,-132.898,53.563,-132.845,53.508,-132.67,53.459,-132.431,53.35,-132.425,53.337,-132.462,53.338,-132.546,53.359,-132.655,53.371,-132.693,53.368,-132.748,53.31,-132.675,53.263,-132.52,53.194,-132.347,53.189]},{"b":[-132.547,52.154,-131.116,53.231],"l":[-131.116,52.219,-131.143,52.291,-131.259,52.292,-131.32,52.303,-131.327,52.318,-131.26,52.39,-131.26,52.416,-131.274,52.426,-131.383,52.416,-131.43,52.422,-131.444,52.453,-131.591,52.578,-131.573,52.623,-131.455,52.702,-131.611,52.745,-131.727,52.756,-131.81,52.819,-131.904,52.867,-131.972,52.88,-131.916,52.909,-131.88,52.915,-131.795,52.885,-131.635,52.922,-131.622,53.02,-131.652,53.103,-131.754,53.196,-131.853,53.23,-131.893,53.231,-131.99,53.202,-132.036,53.179,-132.154,53.16,-132.345,53.136,-132.524,53.145,-132.547,53.138,-132.505,53.087,-132.469,53.072,-132.144,52.999,-132.145,52.957,-132.23,52.948,-132.258,52.934,-132.26,52.907,-132.239,52.867,-132.165,52.783,-132.092,52.753,-131.81,52.542,-131.624,52.444,-131.562,52.4,-131.511,52.322,-131.422,52.238,-131.222,52.154,-131.116,52.219]},{"b":[-64.403,45.967,-62.024,47.062],"l":[-62.164,46.487,-62.423,46.478,-62.682,46.459,-62.712,46.45,-62.964,46.428,-63.129,46.422,-63.286,46.46,-63.369,46.508,-63.413,46.512,-63.456,46.504,-63.534,46.541,-63.681,46.562,-63.737,46.481,-63.784,46.455,-63.811,46.469,-63.834,46.494,-63.906,46.509,-63.876,46.539,-63.864,46.572,-63.879,46.609,-63.903,46.639,-64.088,46.775,-63.981,46.913,-63.997,46.982,-63.994,47.062,-64.157,46.955,-64.223,46.901,-64.28,46.836,-64.355,46.769,-64.403,46.692,-64.388,46.641,-64.236,46.631,-64.136,46.6,-64.107,46.562,-64.111,46.425,-64.02,46.405,-63.861,46.408,-63.759,46.398,-63.751,46.384,-63.763,46.37,-63.801,46.367,-63.732,46.289,-63.641,46.23,-63.569,46.209,-63.277,46.153,-63.213,46.16,-63.153,46.188,-63.271,46.2,-63.195,46.237,-63.117,46.253,-63.057,46.295,-62.978,46.316,-62.995,46.292,-63.053,46.27,-63.056,46.224,-63.015,46.19,-62.953,46.195,-62.895,46.124,-63.022,46.067,-62.995,46.058,-62.904,46.068,-62.878,46.001,-62.805,45.973,-62.743,45.967,-62.531,45.977,-62.478,46,-62.503,46.023,-62.543,46.029,-62.539,46.098,-62.552,46.166,-62.526,46.203,-62.32,46.278,-62.172,46.355,-62.024,46.422,-62.041,46.446,-62.074,46.466,-62.164,46.487]},{"b":[-74.032,40.571,-71.903,41.153],"l":[-72.102,41.015,-72.151,41.051,-72.184,41.047,-72.287,41.024,-72.409,40.972,-72.461,40.934,-72.517,40.915,-72.581,40.921,-72.51,40.986,-72.427,41.039,-72.274,41.153,-72.373,41.126,-72.544,41.027,-72.625,40.992,-72.829,40.972,-73.034,40.966,-73.111,40.957,-73.186,40.93,-73.278,40.924,-73.373,40.944,-73.407,40.941,-73.441,40.927,-73.487,40.92,-73.574,40.92,-73.61,40.906,-73.643,40.881,-73.652,40.838,-73.695,40.87,-73.757,40.834,-73.879,40.792,-73.965,40.725,-74.003,40.683,-74.032,40.639,-74.015,40.581,-73.929,40.599,-73.875,40.652,-73.823,40.656,-73.799,40.641,-73.801,40.622,-73.9,40.571,-73.767,40.593,-73.621,40.6,-73.266,40.664,-73.229,40.652,-73.194,40.654,-72.763,40.778,-72.676,40.791,-72.556,40.826,-72.428,40.875,-72.339,40.894,-71.903,41.061,-72.004,41.044,-72.102,41.015]},{"b":[148.337,-6.301,152.406,-4.2],"l":[152.197,-4.285,152.117,-4.212,151.968,-4.317,151.916,-4.297,151.865,-4.261,151.819,-4.217,151.704,-4.2,151.593,-4.201,151.561,-4.247,151.544,-4.299,151.552,-4.346,151.665,-4.637,151.679,-4.761,151.671,-4.883,151.573,-4.937,151.44,-4.931,151.381,-4.941,151.327,-4.96,151.138,-5.113,151.069,-5.204,151.022,-5.321,150.953,-5.424,150.9,-5.447,150.843,-5.454,150.784,-5.471,150.734,-5.51,150.626,-5.521,150.519,-5.46,150.404,-5.473,150.299,-5.536,150.183,-5.524,150.106,-5.429,150.072,-5.31,150.082,-5.186,150.109,-5.136,150.17,-5.071,150.122,-5.018,150.09,-5.012,150.045,-5.035,150.012,-5.14,149.963,-5.448,149.831,-5.524,149.681,-5.524,149.632,-5.516,149.475,-5.573,149.359,-5.584,149.245,-5.573,149.124,-5.523,148.999,-5.485,148.783,-5.512,148.724,-5.493,148.666,-5.487,148.616,-5.507,148.565,-5.508,148.432,-5.472,148.345,-5.545,148.337,-5.669,148.401,-5.765,148.51,-5.805,148.625,-5.831,148.719,-5.867,148.808,-5.916,149.099,-6.117,149.127,-6.128,149.273,-6.079,149.382,-6.078,149.483,-6.125,149.598,-6.261,149.653,-6.29,149.75,-6.301,149.851,-6.293,150.191,-6.289,150.428,-6.276,150.474,-6.263,150.588,-6.188,150.706,-6.149,150.76,-6.114,150.809,-6.071,150.92,-6.027,151.043,-6.015,151.09,-5.997,151.229,-5.92,151.331,-5.839,151.422,-5.747,151.455,-5.703,151.48,-5.655,151.481,-5.591,151.515,-5.552,151.695,-5.544,151.865,-5.565,151.968,-5.529,152.077,-5.458,152.143,-5.357,152.077,-5.247,151.994,-5.149,151.984,-5.074,152.013,-5.004,152.167,-4.993,152.216,-4.979,152.258,-4.955,152.351,-4.822,152.4,-4.731,152.404,-4.629,152.376,-4.56,152.364,-4.491,152.406,-4.341,152.299,-4.321,152.197,-4.285]},{"b":[123.589,-10.347,127.296,-8.316],"l":[124.198,-9.256,123.977,-9.373,123.877,-9.453,123.709,-9.615,123.666,-9.705,123.636,-9.838,123.589,-9.967,123.599,-10.015,123.716,-10.079,123.69,-10.129,123.648,-10.168,123.614,-10.215,123.605,-10.27,123.644,-10.311,123.747,-10.347,123.858,-10.344,123.971,-10.295,124.176,-10.183,124.327,-10.17,124.428,-10.149,124.508,-10.086,124.602,-9.993,124.708,-9.914,124.842,-9.76,124.963,-9.666,124.998,-9.565,125.21,-9.404,125.408,-9.276,125.735,-9.161,125.84,-9.13,125.895,-9.132,125.946,-9.124,126.073,-9.044,126.164,-8.997,126.265,-8.973,126.383,-8.958,126.487,-8.913,126.569,-8.833,126.665,-8.782,126.792,-8.755,126.915,-8.715,127.115,-8.584,127.296,-8.425,127.257,-8.395,127.215,-8.373,127.058,-8.348,126.966,-8.316,126.905,-8.342,126.846,-8.377,126.735,-8.423,126.62,-8.459,126.531,-8.471,126.173,-8.489,125.905,-8.487,125.804,-8.492,125.382,-8.575,125.323,-8.591,125.178,-8.648,125.116,-8.708,125.027,-8.859,124.89,-8.968,124.708,-9.062,124.646,-9.117,124.575,-9.155,124.198,-9.256]},{"b":[144.646,-43.619,148.343,-40.672],"l":[147.786,-43.22,147.699,-43.123,147.648,-43.021,147.687,-42.98,147.774,-43.003,147.8,-42.98,147.807,-42.954,147.8,-42.928,147.693,-42.872,147.574,-42.846,147.536,-42.878,147.549,-42.975,147.537,-42.996,147.452,-43.033,147.408,-42.894,147.298,-42.791,147.302,-42.841,147.348,-42.927,147.343,-42.964,147.325,-43.013,147.281,-43.032,147.26,-43.071,147.26,-43.126,147.245,-43.216,147.173,-43.256,146.997,-43.156,146.985,-43.19,146.988,-43.219,147.077,-43.276,147.036,-43.319,147.005,-43.37,146.955,-43.502,146.874,-43.612,146.834,-43.619,146.699,-43.602,146.549,-43.509,146.413,-43.52,146.187,-43.513,146.043,-43.547,146.013,-43.445,145.982,-43.408,145.994,-43.376,146.109,-43.354,146.226,-43.355,146.208,-43.316,146.176,-43.302,146.125,-43.311,145.975,-43.277,145.873,-43.292,145.803,-43.244,145.682,-43.076,145.61,-42.998,145.567,-42.968,145.518,-42.951,145.488,-42.927,145.268,-42.544,145.237,-42.456,145.199,-42.231,145.373,-42.338,145.435,-42.407,145.468,-42.493,145.527,-42.388,145.517,-42.354,145.36,-42.228,145.34,-42.191,145.331,-42.147,145.294,-42.191,145.235,-42.197,145.259,-42.107,145.238,-42.02,145.055,-41.827,144.916,-41.644,144.778,-41.419,144.766,-41.39,144.764,-41.342,144.698,-41.191,144.662,-41.079,144.646,-40.981,144.71,-40.783,144.719,-40.672,144.819,-40.722,145.043,-40.787,145.159,-40.791,145.224,-40.765,145.283,-40.77,145.349,-40.826,145.429,-40.858,145.485,-40.852,145.533,-40.864,145.576,-40.904,145.686,-40.939,145.734,-40.962,145.775,-40.997,145.821,-41.025,146.111,-41.118,146.317,-41.163,146.574,-41.142,146.651,-41.116,146.723,-41.078,146.786,-41.114,146.848,-41.168,146.836,-41.109,146.857,-41.058,146.919,-41.018,146.99,-40.992,147.106,-40.994,147.219,-40.983,147.269,-40.96,147.321,-40.956,147.388,-40.986,147.455,-41.002,147.501,-40.964,147.579,-40.876,147.622,-40.845,147.818,-40.872,147.873,-40.873,147.969,-40.78,148.033,-40.781,148.215,-40.855,148.293,-40.947,148.285,-41.115,148.292,-41.175,148.306,-41.233,148.312,-41.35,148.29,-41.465,148.287,-41.555,148.297,-41.646,148.288,-41.816,148.316,-41.928,148.302,-42.004,148.301,-42.04,148.328,-42.074,148.341,-42.111,148.331,-42.159,148.343,-42.215,148.331,-42.262,148.29,-42.255,148.277,-42.219,148.285,-42.173,148.277,-42.136,148.256,-42.103,148.183,-42.065,148.204,-42.042,148.242,-42.022,148.214,-41.97,148.167,-42.012,148.141,-42.07,148.156,-42.088,148.128,-42.104,148.067,-42.17,148.023,-42.259,148.005,-42.345,148.009,-42.436,147.974,-42.506,147.924,-42.572,147.912,-42.658,147.915,-42.816,147.958,-42.96,147.981,-43.157,147.945,-43.182,147.839,-43.195,147.786,-43.22]},{"b":[91.524,80.01,97.87,81.28],"l":[97.87,80.763,97.832,80.798,97.703,80.827,97.414,80.842,96.755,80.958,96.693,80.994,96.563,81.03,96.527,81.076,96.471,81.099,96.187,81.184,96.075,81.193,95.984,81.211,95.902,81.261,95.801,81.28,95.16,81.271,95.061,81.188,94.838,81.139,94.612,81.115,94.375,81.107,94.14,81.089,93.889,81.058,93.637,81.038,93.497,81.039,93.359,81.032,93.065,80.988,92.939,80.926,92.765,80.893,92.71,80.872,92.61,80.81,92.593,80.781,92.773,80.769,93.263,80.791,92.981,80.703,92.827,80.619,92.578,80.533,92.247,80.499,91.897,80.478,91.688,80.419,91.524,80.359,91.637,80.27,91.892,80.249,92.092,80.223,92.202,80.179,93.002,80.102,93.655,80.01,93.872,80.01,94.328,80.076,94.565,80.126,94.661,80.123,94.961,80.15,95.856,80.177,97.175,80.241,97.298,80.273,97.417,80.323,97.287,80.343,97.25,80.363,97.115,80.497,97.073,80.52,97.025,80.536,97.113,80.614,97.221,80.652,97.665,80.678,97.747,80.699,97.856,80.698,97.87,80.763]},{"b":[93.071,78.788,100.061,80.168],"l":[99.517,79.13,99.317,79.227,99.042,79.293,99.104,79.305,99.167,79.306,99.388,79.275,99.537,79.277,99.681,79.323,99.722,79.385,99.706,79.463,99.721,79.492,99.749,79.515,99.771,79.568,99.782,79.628,99.805,79.653,99.839,79.669,99.916,79.738,100.061,79.777,99.947,79.849,99.818,79.898,99.727,79.92,99.536,79.941,99.473,79.97,99.371,79.986,99.295,80.016,98.866,80.045,98.596,80.052,98.532,80.044,98.472,80.009,98.499,79.953,98.353,79.884,98.273,79.874,98.065,79.901,97.871,79.853,97.725,79.781,97.652,79.761,97.591,79.775,97.627,79.85,97.76,79.896,97.808,79.956,97.907,80.004,98.018,80.023,97.904,80.095,97.675,80.158,97.587,80.168,97.121,80.153,96.417,80.104,96.277,80.11,96.163,80.097,95.858,80.11,95.498,80.106,95.391,80.073,95.338,80.042,95.281,80.031,94.987,80.097,94.947,80.089,94.815,80.035,94.719,80.011,94.347,79.942,94.257,79.83,94.038,79.756,93.847,79.702,93.405,79.632,93.071,79.495,93.272,79.458,93.479,79.463,93.759,79.451,94.219,79.402,94.314,79.308,94.482,79.219,94.62,79.192,94.632,79.141,94.652,79.127,94.791,79.087,95.02,79.053,95.133,79.05,95.437,79.099,95.531,79.098,95.703,79.012,95.796,79.001,96.347,79.016,96.43,79.003,96.808,78.985,96.871,78.964,96.933,78.934,97.248,78.868,97.555,78.827,97.689,78.827,97.905,78.81,98.054,78.821,98.283,78.795,98.411,78.788,98.82,78.818,99.44,78.834,99.541,78.853,99.929,78.961,99.9,79.006,99.815,79.096,99.751,79.108,99.517,79.13]},{"b":[99.287,77.957,105.343,79.433],"l":[105.146,78.819,105.015,78.843,104.881,78.855,104.633,78.835,104.452,78.88,104.477,78.923,104.449,78.964,104.404,78.977,104.091,79.013,104.004,79.063,103.926,79.123,103.801,79.149,103.673,79.15,103.433,79.126,103.199,79.071,103.076,79.056,102.95,79.056,102.845,79.014,102.748,78.95,102.587,78.871,102.412,78.835,102.448,78.877,102.746,79.106,102.787,79.176,102.885,79.254,102.94,79.271,103.052,79.283,103.098,79.299,103.042,79.332,102.79,79.392,102.405,79.433,102.282,79.43,102.225,79.413,102.181,79.373,102.177,79.313,102.251,79.256,102.129,79.252,102.005,79.264,101.912,79.312,101.824,79.37,101.761,79.372,101.643,79.361,101.591,79.35,101.555,79.313,101.543,79.254,101.31,79.233,101.196,79.204,101.149,79.157,101.052,79.123,101.068,79.096,101.031,79.023,100.965,79.007,100.901,78.98,100.865,78.926,100.856,78.898,100.898,78.812,100.956,78.788,100.876,78.784,100.62,78.797,100.516,78.788,100.416,78.753,100.284,78.679,100.263,78.631,100.257,78.574,100.215,78.536,100.163,78.504,100.124,78.47,100.058,78.38,100.019,78.339,99.678,78.233,99.546,78.179,99.439,78.084,99.287,78.038,99.392,78.001,99.5,77.976,99.845,77.957,100.082,77.975,100.541,78.048,101.04,78.143,101.204,78.192,101.692,78.194,102.18,78.205,102.617,78.225,102.673,78.202,102.734,78.19,102.797,78.188,103.003,78.256,103.719,78.258,104.297,78.335,104.519,78.349,104.742,78.34,104.833,78.353,105.313,78.5,105.343,78.594,105.31,78.666,105.256,78.733,105.205,78.78,105.146,78.819]},{"b":[136.948,74.657,145.36,76.2],"l":[143.686,75.864,143.56,75.86,143.311,75.822,143.185,75.814,142.927,75.827,142.67,75.863,142.46,75.904,142.001,76.044,141.742,76.108,141.485,76.137,141.299,76.064,141.033,75.989,140.985,75.965,140.95,75.927,140.926,75.867,140.927,75.799,140.94,75.75,140.944,75.7,140.889,75.652,140.816,75.631,140.657,75.634,140.602,75.644,140.547,75.663,140.496,75.69,140.389,75.796,140.274,75.822,140.152,75.81,140.049,75.829,139.743,75.953,139.529,76.013,139.211,76.081,139.109,76.108,139.018,76.16,138.92,76.197,138.814,76.2,138.431,76.13,138.208,76.115,138.096,76.081,138.039,76.047,137.977,76.028,137.774,76.016,137.625,75.988,137.561,75.955,137.501,75.91,137.594,75.823,137.707,75.76,137.358,75.782,137.269,75.749,137.215,75.554,137.29,75.349,137.166,75.347,136.982,75.365,136.948,75.326,136.962,75.27,137.006,75.235,137.218,75.124,137.447,75.054,137.568,75.041,137.683,75.009,137.915,74.871,138.001,74.827,138.092,74.797,138.866,74.701,138.982,74.674,139.099,74.657,139.215,74.66,139.326,74.687,139.43,74.749,139.512,74.838,139.548,74.904,139.606,74.946,139.681,74.964,139.758,74.964,140.011,74.895,140.268,74.847,140.464,74.856,140.661,74.882,141.31,74.923,141.53,74.947,141.748,74.983,141.987,74.991,142.1,74.951,142.184,74.9,142.287,74.85,142.378,74.829,142.473,74.82,142.626,74.837,142.778,74.868,143.128,74.97,142.93,75.062,142.697,75.103,142.617,75.133,142.265,75.346,142.199,75.393,142.151,75.458,142.086,75.661,142.308,75.692,142.552,75.721,142.942,75.713,143.002,75.66,142.986,75.633,142.868,75.572,142.734,75.545,142.7,75.449,142.729,75.338,142.82,75.268,142.922,75.217,143.17,75.117,143.396,75.083,143.626,75.084,144.02,75.045,144.216,75.059,144.408,75.102,144.883,75.269,144.814,75.325,144.727,75.366,144.803,75.416,145.023,75.49,145.36,75.53,145.31,75.564,145.255,75.586,143.686,75.864]},{"b":[139.686,73.221,143.491,73.915],"l":[143.344,73.569,142.639,73.803,142.435,73.852,142.185,73.896,141.932,73.915,141.682,73.904,141.312,73.872,141.19,73.876,141.085,73.866,140.984,73.832,140.884,73.778,140.697,73.629,140.594,73.565,140.381,73.483,140.155,73.458,139.92,73.449,139.686,73.426,139.786,73.355,139.925,73.355,140.027,73.361,140.392,73.435,140.663,73.452,140.754,73.446,141.183,73.389,141.597,73.311,142.126,73.282,142.342,73.253,142.587,73.253,142.842,73.245,143.193,73.221,143.451,73.231,143.491,73.246,143.464,73.459,143.411,73.521,143.344,73.569]},{"b":[146.149,74.772,150.822,75.582],"l":[150.69,75.155,150.613,75.12,150.531,75.1,150.417,75.134,150.281,75.164,150.104,75.219,149.645,75.245,149.083,75.262,148.892,75.228,148.59,75.236,148.475,75.272,148.489,75.309,148.519,75.336,148.509,75.387,148.432,75.414,147.497,75.441,147.444,75.438,147.06,75.364,146.795,75.371,146.748,75.429,146.751,75.51,146.538,75.582,146.438,75.558,146.343,75.481,146.258,75.394,146.186,75.296,146.149,75.198,146.703,75.114,146.925,75.063,147.144,74.998,147.257,74.984,147.627,74.959,147.741,74.932,147.972,74.857,148.092,74.826,148.297,74.8,149.05,74.772,149.597,74.773,149.838,74.795,150.331,74.867,150.58,74.919,150.646,74.945,150.822,75.157,150.757,75.162,150.69,75.155]},{"b":[172.706,-41.611,178.536,-34.429],"l":[175.781,-36.805,175.842,-36.875,175.876,-36.958,175.921,-37.205,175.99,-37.437,176.115,-37.538,176.129,-37.587,176.053,-37.562,176.03,-37.576,176.038,-37.601,176.108,-37.645,176.191,-37.667,176.243,-37.664,176.292,-37.68,176.615,-37.831,176.77,-37.89,177.162,-37.986,177.274,-37.993,177.336,-37.991,177.453,-37.957,177.558,-37.897,177.649,-37.808,177.727,-37.706,177.813,-37.656,177.909,-37.617,177.958,-37.581,178.009,-37.555,178.272,-37.567,178.361,-37.618,178.476,-37.66,178.536,-37.692,178.516,-37.758,178.447,-37.854,178.394,-37.96,178.347,-38.201,178.315,-38.444,178.268,-38.551,178.181,-38.634,178.085,-38.694,177.976,-38.722,177.932,-38.86,177.91,-39.022,177.917,-39.062,177.951,-39.095,177.966,-39.142,177.909,-39.24,177.875,-39.225,177.829,-39.145,177.786,-39.111,177.656,-39.086,177.523,-39.074,177.408,-39.081,177.297,-39.116,177.129,-39.186,177.077,-39.222,177.031,-39.267,176.954,-39.368,176.936,-39.491,176.939,-39.555,176.967,-39.605,177.11,-39.673,176.968,-39.911,176.842,-40.158,176.771,-40.228,176.689,-40.293,176.612,-40.442,176.476,-40.57,176.385,-40.668,176.314,-40.769,176.252,-40.877,176.119,-41.029,176.06,-41.13,175.983,-41.213,175.84,-41.32,175.687,-41.412,175.447,-41.538,175.38,-41.58,175.31,-41.611,175.222,-41.574,175.204,-41.535,175.185,-41.449,175.166,-41.417,175.054,-41.391,174.906,-41.433,174.881,-41.424,174.875,-41.404,174.875,-41.278,174.9,-41.243,174.866,-41.223,174.832,-41.231,174.82,-41.263,174.841,-41.291,174.757,-41.325,174.67,-41.326,174.643,-41.313,174.635,-41.289,174.657,-41.251,174.685,-41.218,174.848,-41.059,175.017,-40.848,175.163,-40.622,175.2,-40.505,175.254,-40.289,175.21,-40.199,175.156,-40.115,175.009,-39.952,174.814,-39.86,174.687,-39.847,174.567,-39.813,174.455,-39.735,174.352,-39.643,174.149,-39.568,173.934,-39.509,173.812,-39.426,173.783,-39.376,173.764,-39.319,173.766,-39.265,173.782,-39.211,173.806,-39.17,173.844,-39.139,174.071,-39.031,174.312,-38.971,174.356,-38.972,174.398,-38.963,174.458,-38.926,174.566,-38.842,174.597,-38.785,174.619,-38.605,174.653,-38.428,174.715,-38.226,174.809,-38.1,174.84,-38.023,174.802,-37.896,174.837,-37.849,174.88,-37.821,174.928,-37.804,174.846,-37.685,174.749,-37.505,174.729,-37.449,174.744,-37.393,174.768,-37.339,174.707,-37.325,174.673,-37.273,174.586,-37.098,174.61,-37.07,174.66,-37.089,174.734,-37.215,174.746,-37.15,174.804,-37.11,174.864,-37.089,174.929,-37.085,174.782,-36.944,174.733,-36.949,174.668,-36.972,174.601,-36.986,174.537,-36.973,174.476,-36.942,174.445,-36.883,174.406,-36.768,174.382,-36.726,174.189,-36.492,174.246,-36.485,174.402,-36.602,174.432,-36.565,174.454,-36.511,174.447,-36.451,174.41,-36.406,174.354,-36.376,174.353,-36.323,174.395,-36.274,174.393,-36.24,174.304,-36.171,174.268,-36.163,174.252,-36.196,174.278,-36.244,174.254,-36.249,174.036,-36.122,173.969,-36.021,173.914,-35.909,173.909,-35.954,173.917,-36.018,174.003,-36.146,174.142,-36.289,174.166,-36.328,174.146,-36.377,174.097,-36.391,174.055,-36.36,173.991,-36.237,173.945,-36.176,173.412,-35.543,173.48,-35.459,173.586,-35.389,173.61,-35.357,173.626,-35.319,173.582,-35.313,173.542,-35.33,173.496,-35.362,173.454,-35.399,173.402,-35.481,173.376,-35.5,173.314,-35.443,173.29,-35.408,173.291,-35.366,173.275,-35.34,173.228,-35.331,173.16,-35.248,173.117,-35.205,173.189,-35.124,173.191,-35.016,173.117,-34.903,173.03,-34.8,172.861,-34.632,172.706,-34.455,172.874,-34.433,173.044,-34.429,172.964,-34.535,173,-34.596,173.054,-34.648,173.171,-34.807,173.181,-34.853,173.241,-34.899,173.269,-34.935,173.285,-34.981,173.34,-34.948,173.381,-34.896,173.448,-34.844,173.439,-34.929,173.473,-34.947,173.694,-35.006,173.739,-35.055,173.786,-35.069,173.813,-35.041,173.844,-35.026,173.924,-35.057,174.104,-35.143,174.119,-35.172,174.11,-35.216,174.119,-35.263,174.143,-35.3,174.203,-35.309,174.283,-35.254,174.32,-35.247,174.373,-35.325,174.393,-35.369,174.385,-35.367,174.419,-35.411,174.465,-35.454,174.543,-35.582,174.532,-35.627,174.509,-35.667,174.581,-35.786,174.533,-35.794,174.391,-35.774,174.396,-35.797,174.479,-35.884,174.549,-36.007,174.605,-36.081,174.802,-36.309,174.772,-36.391,174.777,-36.445,174.752,-36.491,174.819,-36.612,174.777,-36.65,174.749,-36.774,174.719,-36.796,174.722,-36.841,174.802,-36.853,174.85,-36.873,174.891,-36.909,174.917,-36.865,174.952,-36.853,175.047,-36.912,175.245,-36.971,175.3,-36.993,175.326,-37.041,175.347,-37.156,175.385,-37.207,175.461,-37.217,175.542,-37.201,175.568,-37.159,175.552,-37.046,175.493,-36.866,175.493,-36.807,175.501,-36.748,175.487,-36.69,175.458,-36.634,175.426,-36.592,175.386,-36.556,175.4,-36.501,175.461,-36.476,175.498,-36.523,175.528,-36.579,175.681,-36.747,175.772,-36.735,175.781,-36.805]},{"b":[166.478,-46.631,174.37,-40.49],"l":[172.889,-43.124,173.072,-43.06,173.149,-43.023,173.221,-42.977,173.348,-42.841,173.545,-42.518,173.589,-42.474,173.84,-42.271,173.889,-42.212,173.888,-42.13,173.974,-42.081,174.047,-42.003,174.215,-41.85,174.243,-41.813,174.283,-41.741,174.217,-41.678,174.17,-41.657,174.084,-41.671,174.161,-41.562,174.092,-41.505,174.073,-41.472,174.069,-41.429,174.102,-41.366,174.17,-41.327,174.237,-41.312,174.297,-41.264,174.368,-41.188,174.37,-41.104,174.284,-41.172,174.138,-41.248,174.039,-41.242,174.103,-41.217,174.2,-41.16,174.214,-41.126,174.274,-41.069,174.303,-41.02,174.224,-41.024,174.212,-40.985,174.153,-40.991,174.121,-41.005,174.081,-41.006,173.999,-40.993,173.998,-41.028,174.024,-41.072,173.958,-41.1,173.915,-41.158,173.933,-41.187,173.897,-41.239,173.798,-41.272,173.862,-41.192,173.86,-41.124,173.915,-41.07,173.88,-41.031,173.89,-41.007,173.953,-40.985,174.002,-40.918,173.947,-40.924,173.898,-40.951,173.784,-40.972,173.738,-40.989,173.563,-41.102,173.447,-41.151,173.338,-41.211,173.231,-41.284,173.115,-41.279,173.069,-41.186,173.052,-41.079,173.042,-40.954,172.989,-40.848,172.869,-40.82,172.767,-40.773,172.729,-40.724,172.704,-40.668,172.711,-40.605,172.733,-40.544,172.944,-40.519,172.83,-40.49,172.711,-40.497,172.641,-40.518,172.468,-40.622,172.273,-40.759,172.139,-40.947,172.093,-41.202,172.011,-41.445,171.948,-41.539,171.831,-41.655,171.732,-41.72,171.672,-41.745,171.536,-41.758,171.486,-41.795,171.421,-41.973,171.36,-42.08,171.323,-42.189,171.296,-42.303,171.252,-42.402,171.296,-42.431,171.313,-42.46,171.257,-42.465,171.221,-42.479,171.19,-42.5,171.028,-42.696,171.048,-42.802,171.038,-42.862,171.012,-42.885,171.018,-42.819,171.011,-42.764,170.97,-42.718,170.84,-42.849,170.742,-42.927,170.725,-42.975,170.735,-43.03,170.665,-42.961,170.616,-42.972,170.524,-43.009,170.536,-43.041,170.612,-43.092,170.536,-43.058,170.459,-43.038,170.379,-43.066,170.303,-43.108,170.374,-43.135,170.396,-43.182,170.356,-43.154,170.3,-43.145,170.24,-43.164,170.19,-43.222,170.149,-43.248,170.104,-43.265,170.018,-43.349,169.859,-43.426,169.908,-43.447,169.891,-43.462,169.835,-43.459,169.824,-43.497,169.834,-43.537,169.769,-43.538,169.662,-43.591,169.515,-43.624,169.323,-43.702,169.17,-43.777,169.136,-43.82,169.179,-43.913,169.136,-43.9,169.067,-43.863,168.99,-43.89,168.806,-43.992,168.775,-43.996,168.651,-43.972,168.457,-44.031,168.367,-44.082,168.196,-44.224,168.018,-44.359,167.857,-44.501,167.866,-44.592,167.902,-44.625,167.909,-44.665,167.859,-44.625,167.787,-44.595,167.698,-44.641,167.578,-44.741,167.485,-44.771,167.46,-44.802,167.456,-44.838,167.482,-44.874,167.479,-44.915,167.466,-44.958,167.411,-44.828,167.195,-44.963,167.172,-44.997,167.205,-45.048,167.259,-45.082,167.188,-45.094,167.128,-45.051,167.026,-45.124,167.023,-45.177,167.033,-45.222,167.127,-45.266,167.207,-45.28,167.23,-45.29,167.145,-45.302,167.118,-45.318,167.112,-45.354,167.156,-45.411,167.052,-45.383,166.909,-45.307,166.869,-45.311,166.876,-45.368,166.92,-45.408,166.778,-45.41,166.743,-45.468,166.734,-45.544,166.869,-45.55,166.991,-45.532,166.826,-45.603,166.798,-45.646,166.81,-45.699,167.003,-45.712,166.953,-45.75,166.836,-45.775,166.513,-45.812,166.488,-45.832,166.478,-45.903,166.493,-45.964,166.613,-45.955,166.718,-45.889,166.734,-45.928,166.727,-45.963,166.65,-46.042,166.73,-46.053,166.856,-45.981,166.917,-45.957,166.712,-46.134,166.732,-46.198,166.831,-46.225,167.1,-46.249,167.369,-46.242,167.414,-46.229,167.491,-46.155,167.539,-46.149,167.682,-46.193,167.722,-46.227,167.842,-46.366,167.9,-46.368,168.077,-46.353,168.189,-46.362,168.23,-46.386,168.266,-46.419,168.32,-46.447,168.343,-46.489,168.326,-46.546,168.357,-46.588,168.382,-46.605,168.466,-46.588,168.572,-46.611,168.631,-46.588,168.767,-46.566,168.838,-46.578,168.966,-46.613,169.099,-46.631,169.342,-46.621,169.687,-46.552,169.729,-46.521,169.761,-46.48,169.918,-46.334,170.186,-46.161,170.267,-46.083,170.335,-45.992,170.419,-45.941,170.674,-45.896,170.722,-45.878,170.776,-45.871,170.791,-45.844,170.788,-45.792,170.74,-45.756,170.7,-45.714,170.701,-45.684,170.815,-45.519,170.89,-45.374,170.94,-45.216,170.991,-45.151,171.113,-45.039,171.134,-44.978,171.023,-44.937,170.999,-44.911,171.146,-44.912,171.198,-44.768,171.213,-44.612,171.231,-44.521,171.313,-44.302,171.285,-44.279,171.241,-44.264,171.365,-44.255,171.417,-44.209,171.443,-44.136,171.518,-44.118,171.659,-44.117,171.712,-44.097,171.808,-44.042,171.891,-44.007,171.978,-43.984,172.081,-43.946,172.18,-43.896,172.137,-43.834,172.052,-43.74,172.036,-43.702,172.146,-43.764,172.221,-43.825,172.297,-43.868,172.35,-43.859,172.385,-43.83,172.396,-43.778,172.43,-43.746,172.48,-43.727,172.527,-43.739,172.584,-43.774,172.476,-43.833,172.503,-43.844,172.555,-43.831,172.749,-43.813,172.818,-43.87,172.921,-43.891,173.023,-43.885,173.066,-43.875,173.094,-43.844,173.117,-43.798,173.098,-43.704,173.073,-43.676,172.947,-43.659,172.807,-43.621,172.767,-43.562,172.749,-43.517,172.74,-43.468,172.693,-43.444,172.527,-43.465,172.562,-43.436,172.632,-43.428,172.7,-43.4,172.735,-43.355,172.688,-43.315,172.627,-43.3,172.624,-43.272,172.719,-43.259,172.808,-43.198,172.889,-43.124]},{"b":[121.904,5.599,126.593,9.759],"l":[126.593,7.547,126.57,7.677,126.544,7.725,126.494,7.757,126.435,7.833,126.425,7.927,126.457,8.149,126.459,8.203,126.38,8.327,126.365,8.484,126.282,8.539,126.173,8.56,126.14,8.596,126.142,8.627,126.22,8.696,126.263,8.744,126.32,8.845,126.305,8.952,126.209,9.081,126.192,9.125,126.193,9.277,126.088,9.261,126.006,9.321,125.955,9.427,125.877,9.513,125.642,9.654,125.521,9.759,125.471,9.757,125.414,9.669,125.51,9.276,125.533,9.141,125.499,9.015,125.376,8.992,125.248,9.027,125.21,9.027,125.176,8.922,125.141,8.869,125.046,8.891,124.944,8.957,124.869,8.972,124.806,8.924,124.787,8.874,124.762,8.69,124.731,8.563,124.622,8.523,124.451,8.606,124.405,8.6,124.358,8.559,124.325,8.508,124.283,8.386,124.226,8.271,124.198,8.23,124.159,8.201,123.997,8.159,123.931,8.128,123.799,8.049,123.753,8.058,123.853,8.145,123.877,8.189,123.861,8.376,123.849,8.433,123.783,8.548,123.68,8.621,123.564,8.647,123.499,8.682,123.435,8.703,123.38,8.616,123.341,8.57,123.293,8.541,123.147,8.516,123.096,8.481,123.051,8.434,123.018,8.398,122.999,8.356,123.003,8.287,122.996,8.221,122.911,8.156,122.804,8.134,122.673,8.133,122.589,8.093,122.387,8.046,122.337,8.028,122.243,7.945,122.132,7.81,122.12,7.765,122.115,7.66,122.047,7.364,121.991,7.279,121.925,7.2,121.904,7.075,121.964,6.968,122.028,6.929,122.098,6.914,122.142,6.95,122.176,7.004,122.251,7.17,122.32,7.34,122.449,7.561,122.474,7.639,122.498,7.673,122.616,7.763,122.714,7.774,122.792,7.722,122.819,7.558,122.843,7.529,122.917,7.531,122.99,7.546,123.049,7.614,123.097,7.7,123.121,7.667,123.139,7.63,123.151,7.575,123.178,7.529,123.282,7.464,123.391,7.408,123.476,7.665,123.482,7.71,123.477,7.756,123.493,7.808,123.553,7.832,123.609,7.832,123.666,7.818,123.717,7.785,123.765,7.743,123.968,7.665,124.068,7.578,124.182,7.437,124.207,7.396,124.213,7.332,124.191,7.267,124.158,7.219,124.118,7.175,124.045,7.114,123.985,6.994,123.981,6.93,123.988,6.863,124.048,6.667,124.05,6.533,124.078,6.404,124.213,6.233,124.399,6.12,124.636,5.998,124.927,5.875,124.975,5.866,125.035,5.871,125.076,5.906,125.174,6.047,125.191,6.063,125.232,6.07,125.268,6.033,125.265,5.926,125.233,5.808,125.241,5.757,125.288,5.632,125.346,5.599,125.456,5.664,125.608,5.87,125.668,5.979,125.671,6.225,125.588,6.466,125.565,6.5,125.487,6.574,125.433,6.607,125.381,6.69,125.401,6.796,125.465,6.911,125.542,7.017,125.641,7.105,125.66,7.161,125.67,7.222,125.689,7.263,125.774,7.322,125.824,7.333,125.901,7.117,125.962,7.033,125.985,6.944,126.043,6.843,126.08,6.733,126.11,6.49,126.142,6.398,126.189,6.31,126.221,6.483,126.24,6.734,126.192,6.853,126.217,6.891,126.294,6.882,126.439,7.012,126.547,7.176,126.582,7.248,126.589,7.325,126.593,7.547]},{"b":[119.762,12.567,124.143,18.615],"l":[122.294,18.234,122.223,18.157,122.179,18.064,122.151,17.756,122.152,17.664,122.175,17.576,122.237,17.435,122.269,17.395,122.362,17.345,122.388,17.307,122.393,17.238,122.408,17.178,122.468,17.155,122.519,17.125,122.5,17.058,122.468,16.99,122.426,16.823,122.226,16.435,122.214,16.352,122.135,16.185,121.975,16.158,121.789,16.077,121.685,16.015,121.595,15.933,121.561,15.827,121.59,15.778,121.609,15.726,121.607,15.67,121.579,15.623,121.49,15.51,121.452,15.417,121.412,15.375,121.392,15.324,121.399,15.267,121.435,15.216,121.544,14.999,121.661,14.79,121.686,14.765,121.695,14.737,121.627,14.682,121.628,14.581,121.649,14.481,121.752,14.234,121.767,14.168,121.8,14.114,121.853,14.063,121.912,14.02,122.08,13.947,122.144,13.933,122.212,13.93,122.228,13.979,122.288,13.996,122.274,14.045,122.203,14.112,122.2,14.148,122.238,14.175,122.283,14.191,122.384,14.264,122.491,14.322,122.627,14.318,122.761,14.285,122.856,14.251,122.934,14.188,123.015,14.08,123.071,13.96,123.071,13.903,123.057,13.845,123.06,13.789,123.102,13.75,123.231,13.747,123.297,13.836,123.305,13.937,123.259,13.975,123.28,14.025,123.32,14.062,123.377,14.029,123.432,13.966,123.633,13.898,123.684,13.897,123.726,13.884,123.816,13.837,123.858,13.8,123.806,13.722,123.607,13.704,123.55,13.646,123.608,13.528,123.704,13.432,123.765,13.354,123.819,13.269,123.817,13.192,123.785,13.111,123.873,13.117,123.955,13.1,124.069,13.032,124.105,13.025,124.143,13.036,124.137,12.791,124.06,12.567,123.962,12.595,123.878,12.69,123.895,12.805,123.949,12.916,123.918,12.94,123.864,12.931,123.802,12.906,123.736,12.897,123.627,12.912,123.402,13.033,123.311,13.044,123.29,13.099,123.296,13.216,123.206,13.354,123.192,13.403,123.163,13.442,122.896,13.592,122.863,13.617,122.781,13.737,122.595,13.908,122.543,13.925,122.486,13.93,122.468,13.887,122.494,13.82,122.504,13.763,122.5,13.703,122.508,13.657,122.596,13.562,122.609,13.517,122.668,13.395,122.675,13.253,122.6,13.194,122.515,13.26,122.513,13.314,122.498,13.364,122.407,13.493,122.377,13.521,122.205,13.648,122.073,13.788,121.778,13.938,121.743,13.946,121.692,13.935,121.643,13.916,121.501,13.842,121.451,13.791,121.446,13.712,121.344,13.649,121.204,13.64,121.096,13.679,121.006,13.758,120.932,13.762,120.841,13.885,120.729,13.901,120.637,13.804,120.617,13.995,120.617,14.188,120.643,14.244,120.688,14.291,120.922,14.493,120.952,14.558,120.941,14.645,120.888,14.716,120.804,14.759,120.708,14.777,120.638,14.816,120.584,14.881,120.547,14.766,120.583,14.595,120.589,14.483,120.556,14.441,120.496,14.44,120.439,14.453,120.396,14.493,120.365,14.608,120.284,14.684,120.251,14.793,120.214,14.809,120.138,14.8,120.082,14.851,120.045,14.978,120.037,15.115,120.005,15.229,119.959,15.34,119.933,15.431,119.892,15.838,119.881,15.875,119.86,15.906,119.808,15.952,119.769,16.008,119.762,16.055,119.773,16.255,119.79,16.303,119.831,16.327,119.886,16.287,119.93,16.239,119.985,16.215,120.033,16.185,120.124,16.066,120.16,16.048,120.271,16.051,120.337,16.066,120.369,16.11,120.389,16.161,120.389,16.222,120.325,16.4,120.305,16.529,120.304,16.645,120.321,16.762,120.409,16.956,120.42,17.09,120.412,17.27,120.427,17.377,120.425,17.438,120.372,17.535,120.358,17.638,120.505,18.163,120.551,18.264,120.584,18.369,120.6,18.508,120.709,18.546,120.814,18.603,120.868,18.599,120.925,18.585,121.051,18.614,121.102,18.615,121.254,18.563,121.593,18.376,121.717,18.33,121.846,18.295,121.948,18.285,122.038,18.328,122.077,18.372,122.147,18.487,122.221,18.501,122.266,18.459,122.3,18.403,122.315,18.32,122.294,18.234]},{"b":[130.996,-10.655,150.85,-0.347],"l":[140.662,-8.847,140.581,-8.728,140.49,-8.62,140.102,-8.301,140.003,-8.196,139.983,-8.167,139.993,-8.139,140.037,-8.084,140.117,-7.924,140.034,-8.023,139.935,-8.101,139.791,-8.106,139.649,-8.125,139.519,-8.173,139.386,-8.189,139.319,-8.166,139.279,-8.107,139.258,-8.047,139.249,-7.982,139.193,-8.086,139.083,-8.143,138.933,-8.262,138.891,-8.238,138.865,-8.192,138.856,-8.145,138.885,-8.095,138.905,-8.041,138.936,-7.913,139.003,-7.838,139.046,-7.691,139.074,-7.639,139.088,-7.587,139.049,-7.528,138.983,-7.508,138.938,-7.472,138.886,-7.373,138.853,-7.34,138.794,-7.299,138.748,-7.251,138.798,-7.216,138.865,-7.201,138.919,-7.204,139.018,-7.226,139.063,-7.227,139.177,-7.19,139.113,-7.202,139.049,-7.201,138.846,-7.136,138.72,-7.07,138.601,-6.937,138.6,-6.911,138.684,-6.887,138.865,-6.858,138.808,-6.79,138.727,-6.731,138.698,-6.626,138.642,-6.56,138.522,-6.454,138.439,-6.343,138.368,-6.119,138.296,-5.949,138.314,-5.887,138.375,-5.844,138.283,-5.839,138.2,-5.807,138.244,-5.724,138.34,-5.676,138.252,-5.688,138.167,-5.712,138.127,-5.717,138.087,-5.709,138.066,-5.676,138.063,-5.629,138.076,-5.546,138.061,-5.465,137.985,-5.428,137.922,-5.37,137.887,-5.349,137.84,-5.35,137.795,-5.312,137.759,-5.256,137.307,-5.014,137.28,-4.945,137.238,-4.976,137.196,-4.99,137.144,-4.951,137.089,-4.924,137.03,-4.929,136.975,-4.907,136.917,-4.895,136.857,-4.893,136.619,-4.819,136.394,-4.701,136.211,-4.651,136.097,-4.585,135.98,-4.531,135.717,-4.478,135.45,-4.443,135.354,-4.442,135.273,-4.453,135.196,-4.451,134.754,-4.195,134.68,-4.079,134.687,-4.011,134.707,-3.955,134.887,-3.938,134.76,-3.922,134.708,-3.93,134.603,-3.976,134.547,-3.979,134.467,-3.949,134.391,-3.91,134.266,-3.946,134.202,-3.887,134.18,-3.825,134.147,-3.797,134.1,-3.8,134.037,-3.822,133.974,-3.818,133.933,-3.776,133.904,-3.72,133.861,-3.68,133.808,-3.65,133.723,-3.578,133.678,-3.479,133.683,-3.309,133.697,-3.248,133.782,-3.149,133.842,-3.055,133.767,-3.044,133.7,-3.087,133.672,-3.132,133.661,-3.186,133.653,-3.364,133.599,-3.416,133.518,-3.412,133.542,-3.516,133.509,-3.616,133.415,-3.732,133.407,-3.785,133.422,-3.843,133.401,-3.899,133.249,-4.062,133.198,-4.07,133.085,-4.069,132.969,-4.095,132.914,-4.057,132.87,-4.007,132.837,-3.949,132.791,-3.828,132.754,-3.704,132.87,-3.551,132.83,-3.413,132.751,-3.295,132.554,-3.131,132.348,-2.975,132.255,-2.943,132.102,-2.93,132.054,-2.915,132.006,-2.856,131.971,-2.789,132.067,-2.76,132.231,-2.68,132.323,-2.684,132.575,-2.727,132.653,-2.766,132.725,-2.789,132.897,-2.658,133.034,-2.487,133.119,-2.45,133.191,-2.438,133.265,-2.454,133.411,-2.514,133.527,-2.542,133.609,-2.547,133.652,-2.601,133.7,-2.625,133.711,-2.544,133.753,-2.451,133.835,-2.422,133.878,-2.415,133.905,-2.391,133.899,-2.304,133.791,-2.294,133.85,-2.22,133.902,-2.184,133.921,-2.147,133.922,-2.102,133.71,-2.189,133.488,-2.226,133.356,-2.216,133.225,-2.214,132.963,-2.273,132.863,-2.27,132.631,-2.247,132.503,-2.218,132.403,-2.24,132.308,-2.242,132.207,-2.176,132.122,-2.092,132.08,-2.033,132.023,-1.99,131.998,-1.933,131.936,-1.715,131.93,-1.56,131.83,-1.557,131.731,-1.541,131.294,-1.393,131.241,-1.43,131.179,-1.448,131.118,-1.455,131.057,-1.448,130.996,-1.425,131.001,-1.384,131.046,-1.284,131.091,-1.247,131.152,-1.219,131.191,-1.166,131.254,-1.007,131.259,-0.953,131.252,-0.897,131.257,-0.855,131.296,-0.834,131.462,-0.782,131.804,-0.704,131.891,-0.657,131.962,-0.582,132.046,-0.537,132.084,-0.491,132.128,-0.454,132.394,-0.355,132.508,-0.347,132.625,-0.359,132.856,-0.417,133.077,-0.512,133.268,-0.636,133.473,-0.726,133.724,-0.741,133.85,-0.731,133.975,-0.744,134.025,-0.77,134.112,-0.847,134.087,-0.897,134.072,-1.002,134.116,-1.102,134.188,-1.203,134.247,-1.311,134.26,-1.363,134.237,-1.474,134.217,-1.529,134.145,-1.621,134.106,-1.721,134.131,-1.845,134.145,-1.969,134.143,-2.083,134.156,-2.195,134.195,-2.309,134.362,-2.621,134.46,-2.832,134.491,-2.714,134.483,-2.583,134.518,-2.536,134.567,-2.51,134.627,-2.537,134.645,-2.59,134.649,-2.706,134.702,-2.934,134.77,-2.944,134.843,-2.909,134.855,-2.979,134.853,-3.108,134.887,-3.21,134.917,-3.25,135.037,-3.333,135.092,-3.349,135.252,-3.369,135.372,-3.375,135.487,-3.345,135.561,-3.269,135.628,-3.186,135.859,-2.995,135.926,-2.904,135.991,-2.764,136.013,-2.734,136.243,-2.583,136.27,-2.529,136.303,-2.426,136.352,-2.325,136.39,-2.273,136.612,-2.224,136.843,-2.198,137.072,-2.105,137.171,-2.025,137.176,-1.973,137.125,-1.881,137.123,-1.841,137.176,-1.802,137.381,-1.686,137.617,-1.566,137.806,-1.483,137.911,-1.484,138.008,-1.557,138.111,-1.616,138.65,-1.791,138.736,-1.846,138.811,-1.918,138.919,-1.968,139.039,-1.992,139.149,-2.039,139.253,-2.099,139.482,-2.212,139.79,-2.348,139.868,-2.356,140.155,-2.35,140.204,-2.376,140.251,-2.412,140.295,-2.42,140.623,-2.446,140.673,-2.472,140.721,-2.508,140.747,-2.607,141.105,-2.611,141.186,-2.628,141.687,-2.845,141.837,-2.932,141.888,-2.953,141.938,-2.953,141.986,-2.964,142.212,-3.083,142.549,-3.205,142.905,-3.321,143.016,-3.345,143.13,-3.355,143.378,-3.395,143.509,-3.431,143.701,-3.573,143.797,-3.617,143.888,-3.697,144.016,-3.784,144.066,-3.805,144.122,-3.815,144.248,-3.818,144.374,-3.803,144.427,-3.81,144.478,-3.825,144.525,-3.855,144.548,-3.913,144.627,-3.993,144.738,-4.029,144.843,-4.101,144.938,-4.188,145.008,-4.275,145.088,-4.349,145.208,-4.38,145.335,-4.385,145.767,-4.823,145.788,-4.891,145.793,-5.178,145.745,-5.402,145.853,-5.471,145.999,-5.497,146.205,-5.545,146.403,-5.617,147.034,-5.919,147.121,-5.945,147.248,-5.955,147.377,-5.951,147.423,-5.966,147.519,-6.021,147.567,-6.057,147.653,-6.155,147.73,-6.261,147.763,-6.292,147.802,-6.315,147.825,-6.373,147.854,-6.551,147.846,-6.662,147.81,-6.704,147.71,-6.724,147.356,-6.742,147.119,-6.722,146.954,-6.834,146.949,-6.883,146.961,-6.929,147.105,-7.167,147.19,-7.378,147.26,-7.464,147.365,-7.534,147.459,-7.616,147.545,-7.711,147.724,-7.876,147.822,-7.937,147.936,-7.975,148.127,-8.104,148.152,-8.16,148.206,-8.339,148.23,-8.46,148.234,-8.51,148.247,-8.554,148.414,-8.664,148.451,-8.695,148.526,-8.939,148.583,-9.052,148.679,-9.092,148.792,-9.089,149.097,-9.017,149.142,-9.015,149.198,-9.031,149.248,-9.071,149.264,-9.181,149.216,-9.296,149.203,-9.407,149.263,-9.498,149.419,-9.569,149.476,-9.588,149.756,-9.611,149.866,-9.63,149.974,-9.661,150.011,-9.688,149.985,-9.737,149.928,-9.761,149.864,-9.771,149.761,-9.806,149.763,-9.869,149.821,-9.934,149.874,-10.013,149.919,-10.042,149.968,-10.061,150.089,-10.088,150.206,-10.126,150.284,-10.163,150.364,-10.19,150.539,-10.207,150.667,-10.257,150.85,-10.236,150.691,-10.318,150.637,-10.338,150.446,-10.307,150.41,-10.339,150.489,-10.426,150.605,-10.484,150.647,-10.518,150.618,-10.558,150.482,-10.637,150.426,-10.649,150.32,-10.655,150.142,-10.621,150.017,-10.577,149.982,-10.518,149.948,-10.483,149.835,-10.399,149.754,-10.353,149.651,-10.337,149.544,-10.338,149.353,-10.29,148.937,-10.255,148.838,-10.234,148.713,-10.167,148.654,-10.157,148.591,-10.178,148.431,-10.191,148.383,-10.185,148.269,-10.128,148.15,-10.107,148.101,-10.125,148.051,-10.128,147.89,-10.087,147.769,-10.07,147.669,-10.013,147.614,-9.96,147.553,-9.912,147.496,-9.79,147.408,-9.675,147.299,-9.58,147.064,-9.426,147.017,-9.388,146.925,-9.247,146.93,-9.154,146.964,-9.06,146.913,-9.092,146.856,-9.088,146.697,-9.025,146.631,-8.951,146.524,-8.75,146.456,-8.644,146.296,-8.456,146.251,-8.344,146.184,-8.246,146.143,-8.21,146.109,-8.168,146.079,-8.114,146.033,-8.076,145.811,-7.993,145.772,-7.966,145.729,-7.952,145.563,-7.944,145.468,-7.93,145.288,-7.862,145.194,-7.841,145.082,-7.828,144.974,-7.802,144.921,-7.777,144.885,-7.734,144.864,-7.632,144.773,-7.642,144.684,-7.625,144.598,-7.589,144.51,-7.567,144.45,-7.598,144.431,-7.679,144.403,-7.684,144.352,-7.667,144.326,-7.677,144.27,-7.714,144.225,-7.765,144.143,-7.757,143.974,-7.706,143.898,-7.674,143.834,-7.616,143.779,-7.55,143.723,-7.498,143.655,-7.46,143.742,-7.55,143.942,-7.944,143.892,-7.952,143.841,-7.942,143.888,-8.018,143.833,-8.029,143.779,-8.028,143.665,-7.996,143.552,-7.985,143.518,-8.001,143.542,-8.029,143.582,-8.113,143.614,-8.2,143.45,-8.24,143.282,-8.264,143.095,-8.311,142.905,-8.314,142.808,-8.287,142.709,-8.272,142.615,-8.287,142.524,-8.322,142.448,-8.316,142.399,-8.255,142.376,-8.208,142.347,-8.167,142.276,-8.174,142.207,-8.196,142.325,-8.198,142.361,-8.25,142.391,-8.313,142.475,-8.369,142.576,-8.336,142.798,-8.345,143.014,-8.444,143.065,-8.455,143.112,-8.475,143.223,-8.572,143.307,-8.661,143.377,-8.762,143.392,-8.802,143.388,-8.908,143.366,-8.961,143.227,-9.036,143.078,-9.092,142.859,-9.203,142.647,-9.328,142.536,-9.303,142.435,-9.237,142.396,-9.219,142.293,-9.183,142.23,-9.17,141.979,-9.198,141.727,-9.213,141.622,-9.211,141.519,-9.19,141.406,-9.151,141.294,-9.168,141.217,-9.214,141.133,-9.221,140.925,-9.085,140.787,-8.974,140.662,-8.847]},{"b":[118.783,-5.693,125.234,1.701],"l":[119.772,-0.484,119.722,-0.088,119.736,-0.051,119.787,-0.057,119.838,-0.022,119.866,0.04,119.812,0.187,119.809,0.239,119.913,0.445,119.998,0.52,120.035,0.567,120.056,0.693,120.101,0.74,120.157,0.774,120.23,0.861,120.27,0.971,120.294,0.979,120.322,0.983,120.367,0.888,120.416,0.849,120.517,0.818,120.603,0.854,120.626,0.902,120.659,0.944,120.711,0.987,120.755,1.036,120.804,1.149,120.868,1.253,120.912,1.289,120.965,1.312,121.025,1.326,121.082,1.328,121.208,1.263,121.282,1.25,121.357,1.255,121.404,1.244,121.44,1.214,121.473,1.156,121.513,1.105,121.551,1.08,121.592,1.068,121.867,1.089,122.108,1.031,122.437,1.018,122.549,0.984,122.657,0.941,122.79,0.863,122.838,0.846,122.892,0.85,122.96,0.923,123.013,0.939,123.067,0.942,123.278,0.928,123.847,0.838,123.931,0.85,124.274,1.022,124.411,1.185,124.534,1.23,124.575,1.304,124.6,1.392,124.644,1.416,124.747,1.441,124.788,1.468,124.861,1.576,124.947,1.672,124.989,1.701,125.111,1.686,125.165,1.644,125.234,1.502,125.222,1.479,125.141,1.408,125.117,1.379,125.028,1.18,124.967,1.083,124.889,0.995,124.698,0.826,124.64,0.744,124.589,0.655,124.514,0.557,124.428,0.471,124.384,0.445,124.278,0.398,124.217,0.38,124.101,0.375,123.754,0.306,123.64,0.297,123.526,0.3,123.31,0.318,123.265,0.327,123.179,0.416,123.083,0.486,122.997,0.494,122.91,0.486,122.281,0.481,122.061,0.468,121.842,0.437,121.723,0.451,121.605,0.486,121.516,0.498,121.426,0.495,121.013,0.442,120.909,0.447,120.7,0.515,120.579,0.528,120.46,0.51,120.349,0.449,120.307,0.408,120.192,0.269,120.127,0.167,120.078,0.04,120.036,-0.09,120.013,-0.196,120.012,-0.307,120.032,-0.432,120.063,-0.556,120.097,-0.65,120.241,-0.868,120.27,-0.899,120.425,-0.961,120.518,-1.039,120.605,-1.258,120.667,-1.37,120.729,-1.371,120.797,-1.364,120.916,-1.378,121.034,-1.407,121.149,-1.339,121.213,-1.212,121.277,-1.118,121.431,-0.939,121.519,-0.856,121.576,-0.829,121.633,-0.84,121.681,-0.888,121.738,-0.926,121.853,-0.946,121.97,-0.933,122.094,-0.875,122.138,-0.839,122.175,-0.794,122.28,-0.757,122.53,-0.757,122.659,-0.77,122.889,-0.755,122.886,-0.722,122.841,-0.687,122.829,-0.659,122.872,-0.641,123.02,-0.6,123.171,-0.571,123.281,-0.592,123.38,-0.649,123.417,-0.707,123.434,-0.778,123.396,-0.962,123.378,-1.004,123.3,-1.026,123.226,-1.002,123.153,-0.907,123.049,-0.872,122.903,-0.901,122.853,-0.928,122.807,-0.966,122.725,-1.064,122.656,-1.175,122.507,-1.348,122.334,-1.498,122.251,-1.555,122.158,-1.594,121.859,-1.693,121.78,-1.767,121.719,-1.863,121.651,-1.895,121.573,-1.906,121.514,-1.888,121.395,-1.834,121.355,-1.878,121.349,-1.946,121.408,-1.97,121.502,-2.045,121.575,-2.151,121.622,-2.174,121.726,-2.208,121.77,-2.241,121.848,-2.332,121.972,-2.542,122.014,-2.656,122.083,-2.75,122.292,-2.908,122.303,-2.952,122.29,-3.004,122.307,-3.052,122.381,-3.142,122.399,-3.201,122.317,-3.275,122.313,-3.383,122.263,-3.527,122.251,-3.576,122.253,-3.62,122.288,-3.662,122.329,-3.694,122.385,-3.711,122.435,-3.74,122.529,-3.853,122.579,-3.882,122.61,-3.923,122.607,-3.985,122.65,-4.021,122.69,-4.084,122.75,-4.1,122.779,-4.082,122.798,-4.054,122.848,-4.065,122.877,-4.109,122.894,-4.166,122.9,-4.229,122.897,-4.349,122.872,-4.392,122.818,-4.39,122.72,-4.341,122.715,-4.376,122.722,-4.411,122.672,-4.422,122.615,-4.417,122.471,-4.422,122.207,-4.496,122.114,-4.54,122.054,-4.62,122.05,-4.675,122.073,-4.792,122.038,-4.832,121.917,-4.848,121.748,-4.817,121.646,-4.786,121.589,-4.76,121.514,-4.681,121.487,-4.581,121.541,-4.283,121.557,-4.245,121.583,-4.211,121.612,-4.156,121.618,-4.093,121.537,-4.015,121.416,-3.984,121.313,-3.919,120.914,-3.556,120.892,-3.521,120.891,-3.46,120.907,-3.404,121.038,-3.205,121.054,-3.167,121.07,-3.01,121.067,-2.881,121.052,-2.752,120.99,-2.67,120.879,-2.646,120.765,-2.642,120.654,-2.668,120.544,-2.733,120.341,-2.87,120.261,-2.949,120.254,-3.053,120.3,-3.154,120.36,-3.247,120.392,-3.348,120.437,-3.707,120.435,-3.748,120.383,-3.852,120.363,-4.086,120.385,-4.415,120.42,-4.617,120.405,-4.727,120.31,-4.963,120.281,-5.093,120.279,-5.146,120.391,-5.393,120.417,-5.49,120.43,-5.591,120.312,-5.542,120.256,-5.544,120.201,-5.559,120.077,-5.575,119.952,-5.578,119.908,-5.596,119.818,-5.662,119.764,-5.688,119.717,-5.693,119.557,-5.611,119.463,-5.522,119.376,-5.425,119.36,-5.314,119.391,-5.201,119.434,-5.079,119.52,-4.877,119.516,-4.742,119.545,-4.631,119.594,-4.523,119.612,-4.424,119.624,-4.034,119.611,-4,119.494,-3.769,119.48,-3.73,119.479,-3.667,119.492,-3.608,119.495,-3.554,119.467,-3.513,119.42,-3.475,119.362,-3.459,119.24,-3.475,118.995,-3.538,118.922,-3.483,118.868,-3.398,118.833,-3.28,118.813,-3.157,118.822,-3.041,118.858,-2.929,118.829,-2.85,118.784,-2.765,118.783,-2.721,118.809,-2.682,118.853,-2.65,118.908,-2.631,118.958,-2.597,119.092,-2.483,119.135,-2.382,119.138,-2.258,119.172,-2.14,119.241,-2.031,119.322,-1.93,119.348,-1.825,119.308,-1.66,119.324,-1.584,119.31,-1.496,119.309,-1.408,119.359,-1.243,119.508,-0.907,119.654,-0.728,119.711,-0.681,119.787,-0.764,119.844,-0.862,119.845,-0.773,119.83,-0.686,119.772,-0.484]},{"b":[105.243,-8.77,114.599,-5.904],"l":[110.607,-8.149,110.83,-8.202,111.055,-8.24,111.339,-8.262,111.51,-8.305,112.115,-8.324,112.352,-8.354,112.586,-8.4,112.679,-8.409,112.772,-8.396,112.898,-8.361,113.019,-8.313,113.134,-8.288,113.253,-8.287,113.693,-8.478,113.94,-8.568,114.16,-8.626,114.277,-8.615,114.339,-8.647,114.383,-8.705,114.459,-8.741,114.584,-8.77,114.599,-8.727,114.595,-8.685,114.482,-8.604,114.449,-8.559,114.387,-8.405,114.381,-8.334,114.385,-8.263,114.443,-8.005,114.444,-7.896,114.409,-7.792,114.383,-7.771,114.071,-7.633,114.037,-7.632,113.876,-7.677,113.747,-7.703,113.498,-7.724,113.248,-7.718,113.014,-7.658,112.795,-7.552,112.783,-7.432,112.794,-7.304,112.752,-7.265,112.649,-7.221,112.626,-7.178,112.587,-7.051,112.539,-6.926,112.434,-6.903,112.312,-6.894,112.137,-6.905,112.087,-6.893,111.99,-6.806,111.738,-6.773,111.688,-6.742,111.644,-6.699,111.54,-6.648,111.484,-6.652,111.387,-6.693,111.342,-6.7,111.182,-6.687,111.154,-6.669,111.001,-6.465,110.972,-6.436,110.835,-6.424,110.784,-6.443,110.737,-6.472,110.701,-6.518,110.674,-6.57,110.634,-6.69,110.584,-6.806,110.521,-6.897,110.426,-6.947,110.373,-6.948,110.321,-6.938,110.261,-6.912,110.198,-6.895,110.067,-6.899,109.936,-6.916,109.821,-6.902,109.587,-6.843,109.501,-6.81,109.404,-6.86,109.294,-6.867,109.018,-6.817,108.899,-6.808,108.78,-6.808,108.678,-6.791,108.604,-6.729,108.538,-6.516,108.516,-6.471,108.419,-6.383,108.33,-6.286,108.295,-6.265,108.254,-6.267,108.197,-6.289,108.138,-6.297,108.009,-6.277,107.884,-6.233,107.776,-6.219,107.667,-6.216,107.563,-6.183,107.475,-6.122,107.374,-6.008,107.332,-5.978,107.162,-5.957,107.046,-5.904,107.012,-6.008,106.932,-6.073,106.878,-6.092,106.825,-6.098,106.676,-6.038,106.569,-6.022,106.459,-6.018,106.35,-5.984,106.166,-5.965,106.075,-5.914,106.029,-5.934,105.936,-6.017,105.868,-6.116,105.787,-6.457,105.757,-6.48,105.706,-6.498,105.655,-6.47,105.608,-6.617,105.581,-6.671,105.484,-6.782,105.46,-6.787,105.405,-6.768,105.387,-6.751,105.371,-6.664,105.336,-6.674,105.273,-6.729,105.243,-6.778,105.255,-6.835,105.303,-6.841,105.362,-6.826,105.421,-6.833,105.478,-6.854,105.601,-6.86,105.725,-6.846,105.835,-6.846,105.944,-6.859,106.198,-6.928,106.52,-7.054,106.492,-7.114,106.448,-7.177,106.417,-7.239,106.411,-7.312,106.455,-7.369,106.535,-7.394,106.631,-7.416,107.071,-7.447,107.285,-7.472,107.547,-7.542,107.598,-7.567,107.696,-7.636,107.804,-7.688,107.917,-7.724,108.221,-7.782,108.336,-7.794,108.452,-7.797,108.518,-7.736,108.571,-7.707,108.741,-7.667,108.856,-7.668,108.987,-7.704,109.194,-7.695,109.282,-7.705,109.853,-7.828,110.039,-7.891,110.607,-8.149]},{"b":[95.207,-5.908,106.058,5.629],"l":[100.817,2.14,100.818,2.194,100.828,2.243,100.877,2.283,100.936,2.295,101.046,2.257,101.225,2.102,101.301,2.012,101.358,1.887,101.405,1.757,101.477,1.693,101.575,1.671,101.684,1.661,101.785,1.621,102.02,1.442,102.098,1.358,102.157,1.259,102.198,1.142,102.223,1.019,102.239,0.99,102.39,0.842,102.469,0.779,102.566,0.749,102.849,0.715,102.949,0.664,103.032,0.579,103.067,0.492,103.008,0.415,102.786,0.298,102.55,0.216,102.78,0.244,102.896,0.279,103.003,0.332,103.109,0.4,103.277,0.495,103.339,0.514,103.412,0.507,103.479,0.48,103.579,0.387,103.673,0.289,103.743,0.174,103.787,0.047,103.706,-0.02,103.589,-0.069,103.429,-0.192,103.412,-0.24,103.444,-0.272,103.405,-0.362,103.495,-0.418,103.509,-0.466,103.431,-0.534,103.439,-0.576,103.533,-0.755,103.578,-0.796,103.721,-0.887,103.94,-0.979,104.061,-1.021,104.199,-1.054,104.258,-1.053,104.361,-1.038,104.381,-1.074,104.426,-1.251,104.447,-1.362,104.478,-1.6,104.519,-1.699,104.516,-1.819,104.569,-1.922,104.676,-1.987,104.791,-2.041,104.845,-2.093,104.845,-2.172,104.826,-2.234,104.787,-2.283,104.668,-2.386,104.647,-2.43,104.631,-2.543,104.651,-2.595,104.698,-2.598,104.736,-2.571,104.878,-2.419,104.917,-2.392,104.971,-2.371,105.026,-2.358,105.287,-2.356,105.397,-2.38,105.495,-2.43,105.582,-2.492,105.899,-2.888,106.044,-3.106,106.056,-3.161,106.058,-3.217,106.034,-3.261,105.901,-3.41,105.885,-3.451,105.844,-3.614,105.852,-3.731,105.896,-3.78,105.93,-3.833,105.928,-3.881,105.841,-4.122,105.831,-4.163,105.887,-4.554,105.891,-4.66,105.879,-4.794,105.887,-5.01,105.816,-5.677,105.803,-5.716,105.748,-5.818,105.677,-5.818,105.619,-5.8,105.578,-5.761,105.556,-5.712,105.523,-5.673,105.349,-5.55,105.304,-5.57,105.128,-5.723,105.081,-5.746,105.023,-5.727,104.93,-5.681,104.64,-5.52,104.622,-5.572,104.618,-5.642,104.676,-5.816,104.684,-5.893,104.631,-5.908,104.602,-5.905,104.481,-5.803,104.37,-5.691,104.243,-5.539,104.15,-5.467,104.067,-5.386,103.831,-5.08,103.77,-5.033,103.406,-4.816,103.332,-4.765,103.239,-4.676,103.139,-4.596,102.919,-4.471,102.538,-4.152,102.372,-3.969,102.188,-3.675,102.128,-3.599,101.818,-3.378,101.649,-3.244,101.579,-3.167,101.414,-2.899,101.366,-2.808,101.306,-2.729,101.206,-2.664,101.119,-2.588,100.944,-2.345,100.89,-2.249,100.848,-2.144,100.855,-1.934,100.487,-1.299,100.394,-1.101,100.308,-0.827,100.289,-0.799,100.088,-0.553,100.017,-0.474,99.931,-0.4,99.86,-0.314,99.721,-0.033,99.67,0.045,99.598,0.102,99.335,0.209,99.236,0.268,99.159,0.352,99.112,0.459,99.06,0.686,98.936,1.032,98.796,1.495,98.703,1.702,98.595,1.865,98.564,1.902,98.087,2.195,98.005,2.238,97.919,2.264,97.795,2.283,97.701,2.359,97.662,2.494,97.641,2.676,97.617,2.785,97.591,2.847,97.391,2.975,97.313,3.077,97.248,3.189,97.188,3.276,96.969,3.575,96.894,3.654,96.801,3.709,96.525,3.767,96.445,3.816,96.311,3.986,96.23,4.073,95.988,4.263,95.579,4.662,95.495,4.761,95.432,4.865,95.381,4.976,95.207,5.284,95.221,5.346,95.247,5.411,95.243,5.464,95.224,5.517,95.228,5.565,95.28,5.593,95.396,5.629,95.517,5.625,95.629,5.609,95.737,5.579,95.841,5.515,96.027,5.351,96.133,5.294,96.251,5.267,96.493,5.229,96.615,5.22,96.843,5.274,96.968,5.269,97.086,5.23,97.19,5.207,97.451,5.236,97.5,5.228,97.547,5.206,97.588,5.17,97.707,5.04,97.908,4.88,97.967,4.777,98,4.662,98.021,4.635,98.248,4.415,98.273,4.322,98.241,4.195,98.307,4.093,98.528,3.998,98.659,3.928,98.687,3.886,98.706,3.835,98.778,3.759,98.869,3.71,99.151,3.581,99.521,3.311,99.732,3.183,99.907,2.988,99.969,2.895,100.021,2.794,100.127,2.648,100.307,2.467,100.353,2.411,100.401,2.332,100.457,2.257,100.524,2.189,100.604,2.137,100.685,2.12,100.817,1.989,100.888,1.948,100.877,2.051,100.817,2.14]},{"b":[108.906,-4.17,119.266,6.99],"l":[112.119,2.915,111.728,2.854,111.623,2.818,111.513,2.743,111.443,2.634,111.44,2.498,111.451,2.424,111.444,2.382,111.406,2.368,111.351,2.364,111.296,2.399,111.242,2.436,111.209,2.38,111.196,2.297,111.209,2.198,111.268,2.14,111.251,2.064,111.198,1.985,111.17,1.902,111.154,1.739,111.11,1.684,111.047,1.634,111.029,1.558,111.058,1.487,111.123,1.449,111.223,1.396,111.145,1.387,111.098,1.401,110.94,1.517,110.895,1.532,110.782,1.521,110.675,1.548,110.4,1.7,110.349,1.72,110.298,1.701,110.246,1.695,110.114,1.699,109.985,1.718,109.865,1.764,109.72,1.858,109.694,1.889,109.629,2.028,109.379,1.923,109.318,1.821,109.273,1.705,109.167,1.607,109.076,1.496,109.055,1.438,109.01,1.24,109.065,1.247,109.096,1.258,109.132,1.254,109.088,1.224,109.031,1.204,108.959,1.135,108.917,0.913,108.906,0.794,108.923,0.533,108.945,0.356,109.075,0.253,109.149,0.168,109.181,0.117,109.22,0.074,109.247,0.056,109.258,0.031,109.195,-0.009,109.165,-0.142,109.15,-0.186,109.122,-0.265,109.121,-0.391,109.13,-0.445,109.161,-0.495,109.257,-0.577,109.373,-0.638,109.366,-0.667,109.312,-0.68,109.271,-0.732,109.259,-0.807,109.289,-0.846,109.333,-0.875,109.454,-0.869,109.682,-0.944,109.787,-1.011,109.873,-1.101,109.938,-1.181,109.983,-1.275,110.019,-1.399,110.036,-1.526,110.023,-1.643,109.964,-1.743,109.96,-1.863,110.075,-1.946,110.097,-2.001,110.124,-2.234,110.224,-2.689,110.233,-2.925,110.256,-2.966,110.303,-2.985,110.351,-2.947,110.378,-2.934,110.574,-2.891,110.668,-3.005,110.703,-3.021,110.736,-2.989,110.811,-2.938,110.899,-2.909,110.852,-2.946,110.83,-2.995,110.869,-3.049,110.93,-3.071,111.044,-3.056,111.259,-2.956,111.368,-2.934,111.495,-2.973,111.625,-2.975,111.658,-2.926,111.695,-2.889,111.76,-2.939,111.809,-3.008,111.823,-3.057,111.836,-3.308,111.834,-3.42,111.822,-3.533,111.858,-3.552,111.907,-3.553,111.955,-3.53,112.127,-3.381,112.285,-3.321,112.444,-3.371,112.6,-3.4,112.758,-3.322,112.971,-3.187,113.034,-2.933,113.343,-3.246,113.367,-3.224,113.409,-3.229,113.526,-3.184,113.566,-3.178,113.61,-3.196,113.63,-3.246,113.637,-3.332,113.634,-3.42,113.705,-3.455,113.796,-3.456,113.959,-3.394,114.082,-3.279,114.109,-3.285,114.128,-3.327,114.178,-3.354,114.236,-3.361,114.293,-3.306,114.344,-3.235,114.302,-3.365,114.305,-3.41,114.344,-3.444,114.397,-3.471,114.446,-3.482,114.526,-3.377,114.536,-3.494,114.606,-3.703,114.625,-4.112,114.653,-4.152,114.694,-4.17,115.258,-3.907,115.956,-3.595,115.999,-3.523,116.017,-3.433,116.058,-3.348,116.15,-3.233,116.167,-3.183,116.205,-3.149,116.257,-3.126,116.172,-3.025,116.154,-2.984,116.166,-2.935,116.226,-2.977,116.289,-2.959,116.331,-2.902,116.353,-2.833,116.372,-2.707,116.375,-2.578,116.307,-2.603,116.317,-2.552,116.353,-2.522,116.401,-2.52,116.45,-2.538,116.529,-2.511,116.549,-2.411,116.565,-2.3,116.528,-2.208,116.418,-2.187,116.369,-2.158,116.314,-2.14,116.424,-2.053,116.452,-1.923,116.43,-1.864,116.424,-1.785,116.353,-1.779,116.275,-1.785,116.3,-1.744,116.332,-1.712,116.478,-1.633,116.518,-1.598,116.545,-1.553,116.554,-1.474,116.612,-1.429,116.715,-1.376,116.753,-1.327,116.771,-1.267,116.759,-1.207,116.729,-1.151,116.726,-1.098,116.74,-1.044,116.761,-1.117,116.797,-1.184,116.849,-1.218,116.914,-1.224,117.003,-1.188,117.07,-1.113,117.146,-1.009,117.241,-0.926,117.357,-0.867,117.522,-0.797,117.563,-0.771,117.574,-0.728,117.557,-0.675,117.549,-0.554,117.463,-0.324,117.464,-0.2,117.522,0.236,117.553,0.341,117.745,0.73,117.777,0.754,117.853,0.789,117.923,0.831,117.964,0.89,117.977,0.964,117.952,1.032,117.912,1.099,118.016,1.039,118.096,0.929,118.196,0.874,118.312,0.847,118.535,0.814,118.757,0.839,118.892,0.887,118.985,0.982,118.963,1.044,118.853,1.096,118.639,1.319,118.472,1.416,118.157,1.64,118.08,1.702,117.928,1.867,117.865,1.968,117.831,2.002,117.789,2.027,117.881,2.061,117.889,2.087,117.957,2.16,118.042,2.215,118.066,2.263,118.067,2.318,118.034,2.378,117.886,2.542,117.805,2.669,117.786,2.747,117.75,2.776,117.667,2.807,117.639,2.825,117.665,2.859,117.698,2.887,117.637,2.915,117.569,2.929,117.638,2.951,117.612,3.005,117.611,3.064,117.567,3.098,117.507,3.105,117.422,3.165,117.352,3.194,117.322,3.244,117.385,3.365,117.346,3.427,117.166,3.592,117.114,3.613,117.056,3.623,117.172,3.639,117.288,3.639,117.45,3.629,117.495,3.666,117.51,3.73,117.567,3.678,117.63,3.636,117.714,3.645,117.777,3.689,117.762,3.734,117.732,3.77,117.728,3.797,117.639,3.878,117.566,3.93,117.559,3.988,117.465,4.076,117.497,4.133,117.566,4.162,117.604,4.2,117.65,4.304,117.696,4.343,117.741,4.338,117.896,4.263,118.008,4.25,118.117,4.288,118.229,4.316,118.364,4.336,118.498,4.362,118.548,4.379,118.586,4.41,118.595,4.461,118.563,4.502,118.324,4.669,118.185,4.829,118.261,4.989,118.32,5.012,118.382,5.019,118.551,4.968,118.672,4.964,118.913,5.023,119.132,5.1,119.22,5.16,119.25,5.199,119.263,5.246,119.266,5.308,119.256,5.366,119.223,5.413,119.178,5.431,119.05,5.415,119.003,5.418,118.957,5.429,118.714,5.559,118.595,5.592,118.563,5.685,118.514,5.729,118.456,5.763,118.353,5.806,118.3,5.82,118.249,5.821,118.145,5.754,118.031,5.712,117.974,5.706,117.928,5.769,117.935,5.788,118.072,5.832,118.116,5.863,118.062,5.983,118.004,6.053,117.896,5.972,117.818,5.94,117.501,5.885,117.617,5.941,117.645,6.002,117.65,6.074,117.616,6.197,117.696,6.272,117.694,6.35,117.67,6.427,117.646,6.474,117.61,6.513,117.499,6.571,117.38,6.612,117.294,6.677,117.255,6.783,117.245,6.833,117.252,6.919,117.23,6.94,117.129,6.969,117.078,6.917,117.019,6.797,116.913,6.66,116.812,6.608,116.788,6.606,116.808,6.691,116.842,6.772,116.85,6.827,116.833,6.952,116.776,6.99,116.75,6.977,116.538,6.583,116.495,6.522,116.138,6.13,116.11,6.003,116.06,5.882,115.918,5.725,115.877,5.614,115.797,5.536,115.741,5.533,115.685,5.535,115.625,5.549,115.604,5.603,115.556,5.567,115.419,5.413,115.422,5.331,115.467,5.254,115.582,5.194,115.554,5.094,115.52,5.049,115.428,4.969,115.375,4.933,115.14,4.9,115.027,4.9,115.047,4.962,115.048,5.016,114.995,5.022,114.841,4.946,114.741,4.881,114.646,4.798,114.545,4.725,114.424,4.66,114.299,4.607,114.178,4.591,114.054,4.593,114.013,4.575,113.984,4.546,113.99,4.483,113.988,4.421,113.953,4.289,113.924,4.243,113.712,4.001,113.446,3.741,113.32,3.561,113.14,3.344,113.045,3.205,112.988,3.162,112.921,3.131,112.737,3.07,112.119,2.915]},{"b":[53.763,73.297,68.942,77.012],"l":[58.618,74.227,58.665,74.289,58.646,74.328,58.562,74.422,58.502,74.464,58.535,74.499,58.928,74.463,59.04,74.486,59.101,74.508,59.146,74.552,59.157,74.611,59.182,74.666,59.24,74.693,59.596,74.614,59.674,74.61,59.753,74.637,59.771,74.664,59.735,74.695,59.747,74.746,59.982,74.745,60.08,74.756,60.222,74.797,60.301,74.837,60.439,74.875,60.501,74.905,60.455,74.946,60.241,74.971,60.277,75.008,60.476,75.055,60.534,75.059,60.655,75.055,60.719,75.069,60.829,75.111,60.936,75.164,61.147,75.223,61.249,75.281,61.356,75.315,61.487,75.311,61.616,75.32,62.066,75.428,63.046,75.576,63.317,75.603,63.659,75.669,63.779,75.673,64.263,75.72,64.745,75.788,65.202,75.839,65.619,75.905,66.282,75.984,66.657,76.047,66.893,76.072,67.127,76.108,67.365,76.161,67.765,76.238,68.165,76.285,68.222,76.313,68.559,76.449,68.9,76.573,68.858,76.61,68.891,76.66,68.942,76.708,68.912,76.761,68.873,76.79,68.699,76.871,68.486,76.934,68.017,76.991,67.652,77.012,67.535,77.008,67.264,76.964,66.829,76.924,66.345,76.821,66.063,76.746,65.959,76.688,65.863,76.613,65.755,76.579,65.637,76.579,65.528,76.568,65.31,76.518,65.197,76.5,65.073,76.497,64.95,76.484,64.708,76.426,64.463,76.378,63.526,76.31,62.971,76.237,62.782,76.245,62.471,76.23,62.237,76.242,61.787,76.291,61.569,76.298,61.202,76.282,61.157,76.274,61.034,76.233,61.037,76.169,61.054,76.12,60.998,76.089,60.942,76.071,60.801,76.069,60.731,76.104,60.606,76.109,60.279,76.096,60.118,76.067,60.036,75.984,59.782,75.946,59.347,75.907,59.11,75.874,58.995,75.872,58.881,75.855,58.653,75.777,58.418,75.72,58.058,75.663,58.073,75.619,58.094,75.593,57.783,75.507,57.708,75.454,57.632,75.356,57.607,75.341,57.302,75.373,57.088,75.384,56.989,75.375,56.844,75.351,56.809,75.328,56.829,75.278,56.876,75.244,56.57,75.098,56.485,75.096,56.389,75.138,56.289,75.164,56.162,75.187,56.036,75.194,55.921,75.168,55.81,75.125,55.821,75.091,55.863,75.059,55.998,75.003,56.34,75.013,56.429,74.973,56.499,74.957,56.218,74.898,55.914,74.796,55.66,74.656,55.582,74.628,55.61,74.591,55.662,74.556,55.752,74.541,55.947,74.542,56.137,74.496,56.078,74.481,55.416,74.436,55.341,74.42,55.023,74.187,54.92,74.129,54.831,74.096,54.733,74.034,54.643,73.96,54.606,73.951,54.386,73.936,54.174,73.886,53.963,73.822,53.851,73.801,53.763,73.766,53.839,73.697,54.205,73.542,54.132,73.481,54.3,73.351,54.566,73.419,54.769,73.449,55.007,73.454,55.28,73.392,55.549,73.357,56.035,73.346,56.228,73.314,56.43,73.297,56.634,73.304,56.964,73.367,57.134,73.504,57.46,73.61,57.543,73.658,57.464,73.746,57.291,73.815,57.313,73.838,57.449,73.826,57.604,73.775,57.657,73.768,57.756,73.769,57.845,73.805,57.872,73.85,57.853,73.898,57.778,73.974,57.767,74.014,58.441,74.129,58.618,74.227]},{"b":[51.429,70.563,57.625,73.388],"l":[51.583,72.071,51.653,72.099,51.805,72.142,51.885,72.153,52.069,72.131,52.252,72.13,52.332,72.154,52.407,72.197,52.462,72.252,52.586,72.284,52.622,72.301,52.662,72.337,52.706,72.391,52.714,72.437,52.749,72.483,52.864,72.55,52.823,72.591,52.839,72.619,52.917,72.669,52.683,72.682,52.605,72.704,52.529,72.737,52.551,72.769,52.579,72.791,52.812,72.875,52.913,72.9,53.024,72.914,53.135,72.913,53.254,72.904,53.37,72.917,53.247,72.973,53.237,73.011,53.189,73.104,53.198,73.148,53.251,73.183,53.358,73.225,53.512,73.238,53.634,73.26,53.753,73.293,53.866,73.299,54.091,73.276,54.202,73.281,54.328,73.299,54.676,73.37,54.804,73.388,54.941,73.383,55.121,73.357,55.32,73.308,55.787,73.269,56.138,73.256,56.35,73.226,56.43,73.201,56.397,73.139,56.335,73.114,56.189,73.033,56.167,72.983,56.193,72.905,56.171,72.848,56.122,72.807,56.084,72.789,55.82,72.79,55.723,72.766,55.718,72.722,55.701,72.672,55.616,72.599,55.441,72.575,55.403,72.549,55.417,72.501,55.356,72.465,55.36,72.409,55.39,72.378,55.399,72.314,55.518,72.221,55.495,72.182,55.403,72.107,55.375,72.015,55.298,71.935,55.471,71.869,55.547,71.783,55.614,71.69,55.819,71.508,56.043,71.346,56.454,71.107,56.895,70.927,57.066,70.876,57.484,70.792,57.556,70.766,57.625,70.729,57.447,70.661,57.264,70.636,57.247,70.605,57.146,70.589,56.649,70.647,56.622,70.655,56.569,70.697,56.51,70.729,56.386,70.734,56.26,70.715,56.335,70.677,56.417,70.665,56.561,70.594,56.5,70.566,56.435,70.563,56.142,70.658,56.115,70.646,56.087,70.618,55.942,70.649,55.907,70.626,55.797,70.616,55.707,70.642,55.706,70.675,55.687,70.692,55.237,70.666,55.052,70.667,54.867,70.678,54.645,70.742,54.608,70.713,54.601,70.68,54.517,70.693,54.333,70.745,54.199,70.765,53.722,70.814,53.384,70.874,53.468,70.901,53.614,70.915,53.616,70.951,53.593,71.001,53.588,71.052,53.671,71.087,53.857,71.07,53.834,71.127,53.922,71.138,54.094,71.105,54.156,71.125,53.886,71.196,53.591,71.297,53.622,71.333,53.515,71.343,53.41,71.34,53.319,71.399,53.333,71.477,53.412,71.53,53.364,71.542,52.909,71.495,52.679,71.506,52.419,71.537,52.18,71.49,51.938,71.475,51.813,71.491,51.692,71.525,51.59,71.571,51.511,71.648,51.439,71.777,51.429,71.826,51.444,71.934,51.482,71.98,51.583,72.071]},{"b":[141.661,45.917,144.714,54.416],"l":[142.335,54.281,142.467,54.149,142.67,53.968,142.706,53.896,142.683,53.816,142.643,53.737,142.689,53.73,142.68,53.674,142.553,53.653,142.509,53.588,142.583,53.537,142.526,53.447,142.424,53.411,142.371,53.403,142.319,53.405,142.18,53.484,142.142,53.496,141.964,53.456,141.852,53.389,141.824,53.34,141.839,53.138,141.874,53.039,141.856,52.794,141.803,52.556,141.748,52.455,141.682,52.359,141.661,52.273,141.668,51.933,141.721,51.847,141.808,51.789,141.772,51.752,141.722,51.736,141.772,51.69,141.873,51.63,142.006,51.521,142.091,51.429,142.207,51.223,142.208,50.998,142.147,50.89,142.1,50.776,142.066,50.63,142.071,50.515,142.143,50.312,142.153,50.217,142.142,49.569,142.109,49.44,142.067,49.312,142.02,49.078,141.98,48.972,141.866,48.75,141.873,48.702,141.897,48.655,142.029,48.477,142.135,48.29,142.182,48.013,142.149,47.902,142.076,47.808,142.016,47.701,141.964,47.587,141.963,47.544,141.984,47.348,142.017,47.245,142.039,47.14,142.011,47.03,141.867,46.694,141.83,46.451,141.916,46.171,141.93,46.088,141.962,46.013,142.016,45.962,142.077,45.917,142.15,45.999,142.209,46.089,142.304,46.358,142.35,46.459,142.406,46.555,142.479,46.644,142.578,46.701,142.636,46.716,142.692,46.711,142.747,46.671,142.796,46.62,142.829,46.605,143.048,46.593,143.282,46.559,143.352,46.476,143.37,46.358,143.419,46.222,143.432,46.029,143.463,46.069,143.482,46.116,143.491,46.175,143.509,46.23,143.581,46.361,143.579,46.406,143.54,46.575,143.486,46.752,143.447,46.792,143.384,46.806,143.319,46.807,143.218,46.795,143.178,46.844,143.089,47.001,143.006,47.223,142.94,47.323,142.905,47.362,142.864,47.392,142.801,47.416,142.745,47.452,142.67,47.537,142.579,47.684,142.557,47.738,142.546,47.885,142.574,48.072,142.651,48.247,142.972,48.918,143.027,49.105,143.105,49.199,143.236,49.263,143.382,49.291,143.732,49.312,143.819,49.309,143.968,49.276,144.049,49.249,144.125,49.209,144.284,49.07,144.412,48.986,144.536,48.894,144.621,48.815,144.673,48.679,144.714,48.64,144.707,48.82,144.686,48.871,144.607,48.936,144.432,49.051,144.341,49.181,144.272,49.311,144.24,49.432,144.2,49.55,144.141,49.661,144.048,49.896,143.816,50.283,143.736,50.507,143.534,51.246,143.489,51.277,143.473,51.299,143.467,51.402,143.455,51.471,143.418,51.521,143.321,51.583,143.3,51.632,143.295,51.744,143.251,51.848,143.191,51.944,143.156,52.084,143.172,52.349,143.201,52.443,143.264,52.479,143.295,52.529,143.324,52.614,143.333,52.7,143.325,52.963,143.288,53.134,143.26,53.217,143.224,53.296,143.096,53.489,142.918,53.794,142.936,53.811,142.911,53.878,142.927,53.956,142.967,54.029,142.986,54.086,142.976,54.141,142.761,54.394,142.693,54.416,142.666,54.358,142.616,54.304,142.552,54.279,142.335,54.281]},{"b":[139.821,41.423,145.833,45.51],"l":[145.214,43.578,145.14,43.663,145.101,43.765,145.126,43.869,145.245,44.076,145.352,44.23,145.37,44.281,145.37,44.327,145.343,44.334,145.102,44.166,144.872,43.982,144.799,43.94,144.715,43.928,144.597,43.93,144.482,43.95,144.101,44.102,144.005,44.117,143.95,44.112,143.824,44.117,143.759,44.132,143.655,44.221,143.512,44.278,143.289,44.397,143.075,44.535,142.885,44.67,142.704,44.819,142.416,45.125,142.172,45.326,142.016,45.438,141.981,45.483,141.938,45.51,141.879,45.483,141.829,45.439,141.778,45.419,141.668,45.401,141.654,45.377,141.653,45.349,141.594,45.256,141.583,45.156,141.656,45.051,141.719,44.941,141.782,44.716,141.761,44.483,141.716,44.371,141.661,44.264,141.645,44.019,141.601,43.919,141.447,43.749,141.398,43.643,141.398,43.513,141.412,43.381,141.374,43.28,141.296,43.2,141.245,43.185,141.138,43.18,140.954,43.201,140.819,43.205,140.781,43.215,140.585,43.312,140.487,43.338,140.392,43.303,140.379,43.237,140.397,43.167,140.486,43.05,140.432,42.954,140.329,42.867,140.224,42.796,140.115,42.733,140.015,42.685,139.951,42.671,139.891,42.649,139.86,42.582,139.829,42.448,139.821,42.388,139.835,42.278,139.895,42.19,140.024,42.1,140.057,42.067,140.108,41.913,140.084,41.803,140.021,41.696,139.995,41.576,140.009,41.521,140.037,41.474,140.085,41.434,140.149,41.423,140.27,41.456,140.385,41.519,140.432,41.567,140.489,41.672,140.593,41.769,140.66,41.816,140.816,41.76,140.908,41.743,141,41.737,141.079,41.76,141.151,41.805,141.108,41.848,140.912,41.978,140.734,42.116,140.684,42.123,140.578,42.119,140.528,42.132,140.417,42.201,140.327,42.293,140.315,42.334,140.324,42.376,140.351,42.435,140.385,42.487,140.48,42.559,140.548,42.57,140.617,42.571,140.71,42.556,140.788,42.5,140.948,42.36,140.986,42.342,141.407,42.547,141.851,42.579,142.088,42.472,142.508,42.258,142.906,42.118,143.112,42.022,143.237,42,143.279,42.038,143.314,42.084,143.327,42.151,143.332,42.22,143.369,42.325,143.429,42.419,143.581,42.599,143.762,42.748,143.969,42.881,144.197,42.974,144.302,42.984,144.516,42.944,144.631,42.947,144.807,42.994,144.921,43.001,145.029,43.032,145.127,43.089,145.23,43.135,145.347,43.177,145.405,43.18,145.505,43.174,145.537,43.193,145.624,43.291,145.726,43.343,145.833,43.386,145.751,43.396,145.674,43.389,145.583,43.328,145.488,43.28,145.436,43.282,145.341,43.303,145.273,43.463,145.214,43.578]},{"b":[130.889,33.487,141.993,41.506],"l":[133.142,34.302,133.019,34.33,132.775,34.255,132.657,34.246,132.534,34.287,132.421,34.353,132.313,34.325,132.238,34.227,132.202,34.032,132.159,33.944,132.146,33.839,132.09,33.855,131.763,34.045,131.741,34.052,131.476,34.019,131.408,34.004,131.323,33.965,131.233,33.948,131.15,33.976,131.072,34.021,130.996,34.007,130.919,33.976,130.889,34.262,130.904,34.3,130.952,34.35,131.004,34.393,131.132,34.407,131.262,34.393,131.354,34.413,131.433,34.47,131.515,34.55,131.608,34.615,131.734,34.667,131.856,34.726,131.963,34.809,132.065,34.9,132.158,34.967,132.26,35.022,132.414,35.156,132.619,35.307,132.698,35.418,132.747,35.449,132.923,35.511,133.157,35.559,133.267,35.557,133.376,35.459,133.435,35.472,133.495,35.497,133.615,35.511,133.739,35.495,133.86,35.495,133.981,35.507,134.214,35.539,134.337,35.578,134.456,35.628,134.882,35.663,135.174,35.747,135.221,35.741,135.265,35.722,135.269,35.66,135.232,35.592,135.268,35.551,135.327,35.526,135.602,35.518,135.68,35.503,135.795,35.55,135.903,35.607,136.016,35.683,136.095,35.768,136.022,35.874,136.006,35.991,136.067,36.117,136.156,36.223,136.262,36.288,136.359,36.362,136.556,36.572,136.698,36.742,136.749,36.951,136.719,37.198,136.843,37.382,136.962,37.414,137.199,37.497,137.323,37.522,137.341,37.485,137.338,37.437,137.152,37.283,137.046,37.22,136.982,37.2,136.924,37.172,136.9,37.118,136.994,37.027,137.019,36.96,137.013,36.895,137.017,36.837,137.124,36.774,137.246,36.753,137.298,36.754,137.343,36.77,137.483,36.925,137.514,36.952,137.913,37.065,138.11,37.151,138.218,37.173,138.32,37.218,138.548,37.392,138.633,37.472,138.709,37.561,138.771,37.663,138.819,37.775,138.885,37.844,139.247,38.009,139.364,38.099,139.401,38.143,139.446,38.269,139.477,38.4,139.521,38.503,139.58,38.599,139.66,38.697,139.749,38.788,139.802,38.882,139.879,39.105,139.912,39.229,139.939,39.273,139.977,39.311,140.011,39.358,140.037,39.411,140.048,39.464,140.065,39.624,140.055,39.749,139.995,39.855,139.945,39.885,139.891,39.887,139.81,39.878,139.742,39.921,139.755,39.959,139.826,39.966,139.874,39.986,139.908,40.022,139.972,40.137,140.011,40.26,140.014,40.315,139.964,40.414,139.924,40.534,139.923,40.598,139.967,40.673,140.029,40.733,140.085,40.747,140.146,40.752,140.201,40.775,140.252,40.809,140.281,40.846,140.326,40.948,140.344,41.006,140.315,41.161,140.344,41.203,140.386,41.23,140.441,41.21,140.498,41.206,140.564,41.212,140.628,41.195,140.64,41.156,140.679,40.893,140.702,40.858,140.749,40.83,140.801,40.834,140.846,40.875,140.876,40.93,140.936,40.941,141.119,40.882,141.183,40.924,141.225,40.988,141.262,41.103,141.244,41.206,141.2,41.244,141.155,41.237,141.115,41.208,141.07,41.193,140.801,41.139,140.802,41.254,140.86,41.425,140.892,41.48,140.937,41.506,141.05,41.476,141.106,41.456,141.229,41.373,141.269,41.354,141.455,41.405,141.42,41.251,141.4,41.096,141.414,40.839,141.43,40.723,141.463,40.611,141.542,40.531,141.646,40.474,141.797,40.291,141.878,40.067,141.935,39.958,141.978,39.844,141.991,39.792,141.992,39.74,141.979,39.668,141.993,39.611,141.977,39.429,141.909,39.219,141.901,39.111,141.842,39.09,141.807,39.04,141.776,39.017,141.742,39,141.694,38.995,141.659,38.975,141.645,38.918,141.622,38.865,141.58,38.817,141.546,38.763,141.519,38.632,141.509,38.498,141.467,38.404,141.368,38.38,141.254,38.381,141.108,38.338,141.077,38.313,140.962,38.149,140.929,38.053,140.928,37.95,140.96,37.823,141.003,37.698,141.036,37.467,141.002,37.115,140.968,37.002,140.895,36.926,140.84,36.89,140.792,36.847,140.73,36.732,140.627,36.503,140.619,36.445,140.619,36.386,140.592,36.308,140.574,36.231,140.59,36.142,140.622,36.059,140.76,35.846,140.813,35.783,140.874,35.725,140.639,35.661,140.597,35.632,140.457,35.51,140.413,35.395,140.417,35.267,140.393,35.221,140.355,35.181,140.315,35.155,140.159,35.096,140.059,35.038,139.96,34.947,139.92,34.9,139.844,34.915,139.799,34.957,139.843,35.01,139.83,35.072,139.851,35.232,139.826,35.297,139.906,35.345,139.944,35.423,140.027,35.485,140.086,35.54,140.097,35.585,140.044,35.633,139.988,35.668,139.91,35.668,139.835,35.658,139.786,35.612,139.77,35.55,139.774,35.52,139.768,35.495,139.65,35.409,139.666,35.319,139.7,35.274,139.744,35.252,139.731,35.222,139.675,35.149,139.636,35.142,139.564,35.243,139.474,35.299,139.363,35.298,139.249,35.278,139.163,35.211,139.134,35.155,139.116,35.097,139.122,34.956,139.086,34.839,139.016,34.736,138.983,34.698,138.897,34.628,138.838,34.619,138.795,34.651,138.761,34.699,138.804,34.876,138.803,34.975,138.904,35.025,138.821,35.096,138.72,35.124,138.577,35.086,138.537,35.044,138.51,34.987,138.433,34.915,138.349,34.848,138.253,34.733,138.189,34.596,137.979,34.641,137.864,34.651,137.749,34.647,137.543,34.664,137.318,34.636,137.062,34.583,137.077,34.621,137.288,34.704,137.295,34.728,137.275,34.773,137.223,34.775,137.097,34.759,137.032,34.766,137.006,34.814,136.963,34.835,136.935,34.815,136.944,34.722,136.913,34.709,136.871,34.733,136.885,34.806,136.856,34.913,136.853,34.979,136.897,35.036,136.852,35.06,136.804,35.05,136.744,35.023,136.69,34.984,136.577,34.79,136.533,34.678,136.616,34.589,136.842,34.464,136.88,34.434,136.881,34.38,136.854,34.324,136.792,34.299,136.544,34.258,136.33,34.177,136.268,34.095,136.073,33.778,135.916,33.562,135.695,33.487,135.453,33.553,135.394,33.629,135.347,33.722,135.257,33.806,135.175,33.898,135.128,34.007,135.135,34.183,135.1,34.288,135.132,34.317,135.266,34.381,135.309,34.417,135.385,34.5,135.412,34.547,135.416,34.617,135.355,34.654,135.198,34.653,135.042,34.631,134.93,34.662,134.785,34.747,134.74,34.765,134.584,34.771,134.472,34.755,134.363,34.724,134.247,34.714,134.208,34.698,134.075,34.593,133.968,34.527,133.876,34.495,133.678,34.486,133.579,34.465,133.474,34.43,133.445,34.433,133.336,34.385,133.21,34.344,133.142,34.302]},{"b":[132.033,32.752,134.739,34.358],"l":[134.695,33.928,134.655,33.983,134.635,34.044,134.638,34.227,134.496,34.215,134.357,34.256,134.219,34.319,134.076,34.358,133.948,34.348,133.826,34.307,133.706,34.237,133.656,34.233,133.603,34.244,133.643,34.135,133.627,34.069,133.582,34.017,133.472,33.973,133.35,33.977,133.299,33.969,133.193,33.933,133.134,33.927,133.051,33.997,132.99,34.088,132.935,34.095,132.839,34.021,132.784,33.992,132.752,33.906,132.716,33.852,132.699,33.791,132.643,33.69,132.536,33.633,132.366,33.512,132.288,33.47,132.114,33.395,132.033,33.34,132.086,33.34,132.281,33.417,132.375,33.434,132.413,33.43,132.405,33.331,132.445,33.305,132.511,33.293,132.515,33.255,132.505,33.211,132.477,33.181,132.476,33.126,132.428,33.059,132.493,33.008,132.495,32.917,132.602,32.92,132.709,32.902,132.642,32.762,132.692,32.776,132.804,32.752,132.87,32.755,132.977,32.842,133.016,32.984,133.051,33.012,133.101,33.028,133.146,33.083,133.24,33.25,133.286,33.36,133.632,33.511,133.686,33.516,133.854,33.493,133.959,33.448,134.124,33.287,134.182,33.247,134.206,33.347,134.243,33.439,134.307,33.527,134.377,33.608,134.549,33.729,134.739,33.821,134.675,33.848,134.695,33.928]},{"b":[129.58,31.015,132.009,33.928],"l":[131.977,32.844,132.002,32.882,132.009,32.919,131.91,32.974,131.937,33.01,131.949,33.047,131.903,33.088,131.848,33.118,131.855,33.182,131.897,33.255,131.717,33.252,131.537,33.274,131.616,33.392,131.711,33.502,131.724,33.554,131.696,33.603,131.643,33.638,131.583,33.652,131.499,33.624,131.419,33.584,131.366,33.571,131.309,33.573,131.175,33.603,131.058,33.673,131.009,33.776,130.953,33.872,130.84,33.918,130.716,33.928,130.67,33.915,130.484,33.835,130.457,33.789,130.439,33.734,130.365,33.634,130.275,33.598,130.168,33.598,130.131,33.578,130.103,33.54,130.072,33.522,129.919,33.483,129.826,33.437,129.837,33.404,129.858,33.375,129.844,33.322,129.702,33.36,129.66,33.365,129.61,33.344,129.58,33.236,129.665,33.187,129.799,33.084,129.897,33.022,129.922,32.988,129.992,32.852,129.901,32.852,129.828,32.893,129.778,32.986,129.679,33.06,129.662,32.995,129.668,32.929,129.69,32.875,129.786,32.782,129.827,32.725,129.808,32.645,129.769,32.571,129.853,32.622,129.951,32.722,130.054,32.771,130.152,32.748,130.193,32.706,130.246,32.677,130.298,32.675,130.34,32.702,130.361,32.756,130.354,32.81,130.326,32.853,130.28,32.867,130.222,32.847,130.175,32.851,130.168,32.932,130.173,33.013,130.127,33.105,130.177,33.145,130.238,33.178,130.287,33.155,130.382,33.093,130.44,32.951,130.547,32.832,130.569,32.734,130.498,32.657,130.563,32.626,130.641,32.619,130.56,32.456,130.462,32.305,130.395,32.219,130.319,32.144,130.214,32.115,130.194,32.091,130.196,31.95,130.211,31.849,130.188,31.769,130.224,31.73,130.269,31.696,130.322,31.601,130.307,31.488,130.294,31.451,130.261,31.437,130.147,31.408,130.201,31.292,130.251,31.273,130.311,31.267,130.589,31.179,130.621,31.218,130.645,31.267,130.566,31.352,130.54,31.403,130.528,31.46,130.556,31.563,130.613,31.665,130.655,31.718,130.715,31.718,130.776,31.706,130.797,31.671,130.796,31.624,130.78,31.604,130.749,31.598,130.704,31.577,130.709,31.526,130.774,31.383,130.79,31.269,130.758,31.156,130.736,31.122,130.704,31.094,130.684,31.059,130.686,31.015,130.902,31.112,131.098,31.256,131.035,31.378,131.071,31.437,131.14,31.442,131.25,31.41,131.337,31.405,131.46,31.671,131.475,31.778,131.46,31.883,131.506,32.002,131.531,32.117,131.565,32.223,131.61,32.325,131.66,32.466,131.732,32.593,131.977,32.844]},{"b":[120.072,21.925,121.929,25.277],"l":[121.905,25.056,121.853,25.104,121.733,25.154,121.687,25.182,121.643,25.232,121.594,25.275,121.517,25.277,121.45,25.249,121.365,25.159,121.095,25.065,121.041,25.033,120.964,24.928,120.902,24.813,120.836,24.723,120.757,24.642,120.63,24.479,120.159,23.709,120.132,23.653,120.125,23.527,120.143,23.399,120.121,23.305,120.086,23.212,120.072,23.15,120.083,23.094,120.122,23.037,120.15,22.975,120.233,22.718,120.273,22.627,120.326,22.542,120.316,22.548,120.388,22.485,120.48,22.442,120.581,22.356,120.608,22.313,120.678,22.16,120.69,22.033,120.743,21.956,120.84,21.925,120.864,22.033,120.878,22.142,120.877,22.262,120.897,22.379,120.947,22.503,121.009,22.62,121.161,22.776,121.296,22.967,121.352,23.067,121.397,23.173,121.477,23.424,121.526,23.668,121.583,23.861,121.613,24.053,121.639,24.13,121.737,24.285,121.828,24.534,121.826,24.641,121.813,24.746,121.82,24.825,121.856,24.895,121.929,24.974,121.905,25.056]},{"b":[108.636,18.218,111.014,20.138],"l":[110.971,19.883,110.938,19.948,110.889,19.992,110.809,20.014,110.745,20.059,110.679,20.137,110.652,20.138,110.598,20.098,110.589,20.072,110.588,19.976,110.418,20.055,110.394,20.059,110.388,20.018,110.392,19.976,110.344,20.039,110.213,20.056,110.172,20.054,110.083,19.993,109.906,19.963,109.651,19.984,109.584,19.97,109.514,19.904,109.418,19.889,109.315,19.904,109.263,19.883,109.219,19.843,109.177,19.768,109.22,19.757,109.277,19.761,109.179,19.674,109.063,19.614,108.903,19.481,108.791,19.418,108.694,19.338,108.666,19.304,108.65,19.265,108.636,18.908,108.638,18.866,108.676,18.75,108.702,18.535,108.922,18.416,109.03,18.368,109.183,18.325,109.341,18.3,109.4,18.281,109.519,18.218,109.59,18.226,109.681,18.247,109.703,18.259,109.76,18.348,109.816,18.397,109.968,18.422,110.02,18.416,110.067,18.448,110.066,18.476,110.049,18.505,110.156,18.57,110.252,18.656,110.291,18.67,110.334,18.673,110.4,18.698,110.451,18.748,110.478,18.813,110.519,18.97,110.566,19.099,110.563,19.135,110.572,19.172,110.603,19.207,110.641,19.291,110.822,19.558,110.913,19.586,111.014,19.655,110.998,19.765,110.971,19.883]},{"b":[79.708,5.949,81.877,9.813],"l":[81.832,7.428,81.797,7.465,81.727,7.625,81.683,7.684,81.676,7.711,81.679,7.742,81.665,7.782,81.436,8.119,81.422,8.148,81.422,8.215,81.373,8.431,81.334,8.472,81.275,8.484,81.227,8.506,81.216,8.549,81.219,8.608,81.198,8.662,81.016,8.933,80.979,8.957,80.935,8.971,80.91,9.025,80.893,9.086,80.711,9.366,80.376,9.642,80.253,9.796,80.181,9.81,80.078,9.807,79.982,9.813,79.967,9.793,79.954,9.742,79.979,9.699,80.046,9.65,80.258,9.611,80.385,9.549,80.428,9.481,80.368,9.48,80.318,9.465,80.256,9.495,80.196,9.538,80.086,9.578,80.111,9.453,80.118,9.327,80.1,9.21,80.065,9.096,79.929,8.899,79.928,8.846,79.944,8.741,79.942,8.692,79.851,8.412,79.832,8.304,79.809,8.05,79.783,8.018,79.75,8.049,79.75,8.294,79.713,8.182,79.708,8.066,79.76,7.796,79.792,7.585,79.859,6.829,79.947,6.585,80.007,6.364,80.095,6.153,80.267,6.01,80.496,5.949,80.724,5.979,80.971,6.088,81.306,6.204,81.38,6.241,81.637,6.425,81.713,6.512,81.768,6.614,81.819,6.756,81.861,6.901,81.877,7.02,81.874,7.288,81.832,7.428]},{"b":[43.257,-25.571,50.483,-12.08],"l":[44.405,-19.922,44.432,-19.674,44.453,-19.551,44.449,-19.429,44.387,-19.303,44.239,-19.075,44.234,-19.033,44.246,-18.863,44.233,-18.741,44.179,-18.619,44.109,-18.504,44.04,-18.288,44.007,-17.933,44.014,-17.804,43.994,-17.69,43.944,-17.581,43.979,-17.392,44.421,-16.703,44.436,-16.621,44.418,-16.411,44.427,-16.289,44.442,-16.244,44.476,-16.217,44.552,-16.204,44.909,-16.175,44.955,-16.153,45.044,-16.095,45.167,-15.983,45.223,-15.95,45.271,-15.962,45.302,-16.01,45.342,-16.037,45.486,-15.986,45.542,-15.984,45.598,-15.993,45.625,-15.946,45.641,-15.883,45.662,-15.839,45.7,-15.814,45.886,-15.8,46.004,-15.782,46.158,-15.738,46.191,-15.747,46.314,-15.905,46.352,-15.918,46.4,-15.925,46.442,-15.896,46.341,-15.813,46.326,-15.767,46.331,-15.714,46.385,-15.6,46.475,-15.513,46.675,-15.382,46.882,-15.23,46.942,-15.219,46.993,-15.243,47.032,-15.423,47.027,-15.452,47.061,-15.456,47.099,-15.434,47.133,-15.362,47.135,-15.302,47.107,-15.244,47.094,-15.195,47.093,-15.15,47.198,-15.044,47.28,-14.943,47.319,-14.822,47.352,-14.766,47.439,-14.703,47.465,-14.713,47.485,-14.764,47.496,-14.818,47.474,-14.872,47.442,-14.925,47.429,-14.996,47.478,-15.009,47.525,-14.992,47.593,-14.864,47.67,-14.743,47.716,-14.68,47.774,-14.637,47.87,-14.646,47.964,-14.673,47.812,-14.545,47.773,-14.37,47.955,-14.067,47.957,-14.004,47.983,-13.985,47.996,-13.96,47.901,-13.858,47.884,-13.808,47.896,-13.731,47.941,-13.662,47.982,-13.615,48.04,-13.596,48.086,-13.623,48.187,-13.707,48.255,-13.719,48.338,-13.639,48.405,-13.538,48.506,-13.469,48.621,-13.426,48.796,-13.267,48.91,-12.936,48.919,-12.839,48.894,-12.722,48.854,-12.61,48.786,-12.471,48.804,-12.44,48.9,-12.458,48.932,-12.439,49.036,-12.316,49.207,-12.08,49.263,-12.08,49.312,-12.124,49.33,-12.189,49.364,-12.236,49.48,-12.348,49.538,-12.432,49.584,-12.537,49.638,-12.637,49.805,-12.88,49.876,-12.973,49.938,-13.072,49.967,-13.27,50.073,-13.578,50.174,-14.04,50.205,-14.514,50.235,-14.732,50.313,-14.937,50.441,-15.149,50.483,-15.386,50.405,-15.629,50.292,-15.858,50.262,-15.902,50.209,-15.96,50.185,-15.958,50.094,-15.899,50.02,-15.802,49.927,-15.574,49.893,-15.458,49.853,-15.439,49.744,-15.45,49.664,-15.522,49.65,-15.567,49.667,-15.696,49.697,-15.811,49.71,-15.929,49.713,-16.077,49.742,-16.121,49.786,-16.159,49.831,-16.256,49.839,-16.487,49.811,-16.603,49.734,-16.703,49.739,-16.758,49.767,-16.815,49.74,-16.849,49.637,-16.893,49.595,-16.931,49.54,-17.033,49.449,-17.241,49.437,-17.347,49.494,-17.67,49.478,-17.899,49.363,-18.336,49.297,-18.544,49.203,-18.792,49.06,-19.12,48.918,-19.53,48.797,-19.953,48.708,-20.207,48.607,-20.458,48.469,-20.9,48.351,-21.349,48.176,-21.843,47.934,-22.394,47.908,-22.466,47.858,-22.747,47.804,-22.992,47.739,-23.233,47.604,-23.633,47.589,-23.756,47.558,-23.875,47.428,-24.125,47.373,-24.218,47.334,-24.318,47.312,-24.443,47.273,-24.564,47.177,-24.787,47.035,-24.979,46.938,-25.049,46.729,-25.15,46.622,-25.17,46.387,-25.173,46.159,-25.23,45.921,-25.341,45.692,-25.468,45.605,-25.529,45.508,-25.563,45.206,-25.571,45.115,-25.543,44.813,-25.334,44.696,-25.3,44.474,-25.271,44.407,-25.253,44.346,-25.226,44.256,-25.117,44.078,-25.025,44.035,-24.996,44.008,-24.932,43.99,-24.863,43.944,-24.787,43.91,-24.641,43.852,-24.538,43.688,-24.358,43.67,-24.3,43.657,-24.109,43.662,-23.979,43.646,-23.742,43.665,-23.63,43.722,-23.53,43.699,-23.421,43.638,-23.307,43.615,-23.188,43.57,-23.08,43.398,-22.886,43.358,-22.791,43.33,-22.692,43.265,-22.384,43.257,-22.276,43.267,-22.049,43.291,-21.933,43.332,-21.851,43.343,-21.79,43.37,-21.738,43.411,-21.696,43.438,-21.647,43.502,-21.356,43.583,-21.292,43.704,-21.255,43.8,-21.179,43.856,-21.077,43.911,-20.866,44.063,-20.656,44.117,-20.546,44.24,-20.38,44.348,-20.146,44.381,-20.035,44.405,-19.922]},{"b":[53.019,38.756,53.11,39.097],"l":[53.055,39.038,53.11,38.803,53.1,38.756,53.046,38.897,53.019,39.053,53.053,39.097,53.092,39.094,53.055,39.038]},{"b":[49.995,44.826,50.184,45.082],"l":[50.095,44.831,49.995,44.937,50.023,45.045,50.059,45.067,50.11,45.082,50.117,45.058,50.045,45.01,50.039,44.949,50.098,44.882,50.184,44.855,50.149,44.826,50.095,44.831]},{"b":[50.256,44.959,50.35,45.083],"l":[50.295,45.076,50.35,45.083,50.331,44.998,50.312,44.972,50.277,44.959,50.256,45.022,50.295,45.076]},{"b":[-164.282,-79.845,-158.997,-78.719],"l":[-160.25,-79.271,-160.764,-79.132,-161.283,-79.007,-161.643,-78.901,-162.161,-78.793,-162.39,-78.76,-162.622,-78.742,-162.873,-78.725,-163.124,-78.719,-163.256,-78.722,-163.345,-78.78,-163.66,-78.856,-163.815,-78.929,-164.126,-78.995,-164.2,-79.051,-164.245,-79.138,-164.282,-79.246,-164.226,-79.321,-163.971,-79.389,-163.712,-79.442,-163.317,-79.505,-161.866,-79.704,-160.807,-79.812,-160.302,-79.845,-159.053,-79.807,-159.01,-79.78,-158.997,-79.735,-159.051,-79.695,-159.119,-79.675,-159.19,-79.637,-159.256,-79.591,-159.366,-79.545,-159.419,-79.508,-159.684,-79.402,-159.964,-79.324,-160.25,-79.271]},{"b":[-71.784,-79.683,-66.728,-78.316],"l":[-66.728,-78.384,-66.84,-78.35,-67.038,-78.316,-67.479,-78.362,-69.398,-78.686,-69.748,-78.769,-69.972,-78.809,-70.544,-78.884,-70.626,-78.902,-71.254,-79.06,-71.454,-79.129,-71.667,-79.246,-71.784,-79.444,-71.778,-79.501,-71.735,-79.539,-71.687,-79.568,-71.526,-79.624,-71.414,-79.64,-70.984,-79.674,-70.553,-79.683,-70.334,-79.68,-70.116,-79.666,-69.732,-79.618,-69.634,-79.518,-69.686,-79.443,-69.394,-79.28,-69.251,-79.21,-68.638,-79.013,-68.157,-78.871,-67.481,-78.682,-67.166,-78.57,-67.046,-78.514,-66.787,-78.422,-66.728,-78.384]},{"b":[-54.372,-80.87,-42.945,-77.785],"l":[-43.947,-78.598,-43.852,-78.53,-43.788,-78.433,-43.777,-78.385,-43.785,-78.336,-43.809,-78.287,-43.854,-78.258,-44.094,-78.167,-44.34,-78.093,-44.594,-78.035,-44.852,-77.988,-45.53,-77.881,-45.993,-77.827,-46.258,-77.805,-46.826,-77.785,-47.03,-77.791,-47.463,-77.819,-47.692,-77.84,-49.081,-78.047,-49.144,-78.094,-49.354,-78.222,-49.94,-78.462,-50.142,-78.557,-50.22,-78.605,-50.294,-78.696,-50.377,-78.78,-50.335,-78.818,-50.242,-78.833,-50.298,-78.882,-50.38,-78.923,-50.502,-78.95,-50.514,-78.98,-50.502,-79.022,-50.52,-79.104,-50.573,-79.172,-50.649,-79.233,-50.733,-79.283,-50.464,-79.313,-50.42,-79.321,-50.379,-79.338,-50.331,-79.381,-50.295,-79.43,-50.339,-79.479,-50.402,-79.512,-50.664,-79.627,-51.184,-79.82,-51.711,-79.99,-52.297,-80.141,-52.338,-80.126,-52.357,-80.078,-52.461,-80.067,-52.567,-80.099,-52.807,-80.156,-53.053,-80.175,-53.176,-80.161,-53.346,-80.114,-53.394,-80.109,-53.482,-80.189,-53.676,-80.284,-54.045,-80.487,-54.13,-80.517,-54.347,-80.569,-54.372,-80.624,-54.351,-80.76,-54.241,-80.847,-54.202,-80.864,-54.162,-80.87,-49.773,-80.784,-49.701,-80.753,-49.63,-80.712,-49.41,-80.667,-49.187,-80.643,-43.528,-80.191,-43.489,-80.178,-43.454,-80.155,-43.459,-80.123,-43.49,-80.096,-43.758,-80.021,-43.742,-80.003,-43.704,-79.99,-43.6,-79.974,-43.496,-79.969,-43.267,-79.979,-43.066,-79.891,-42.99,-79.801,-42.945,-79.579,-42.965,-79.477,-43.119,-79.35,-43.211,-79.3,-43.267,-79.163,-43.364,-79.085,-43.451,-78.99,-43.544,-78.902,-43.627,-78.846,-43.722,-78.818,-44.041,-78.807,-44.566,-78.804,-45.092,-78.814,-45.223,-78.811,-45.352,-78.791,-45.295,-78.74,-45.213,-78.687,-45.068,-78.661,-43.947,-78.598]},{"b":[-66.771,-80.948,-59.322,-79.741],"l":[-59.498,-80.115,-59.788,-80.101,-59.786,-80.001,-59.752,-79.938,-59.706,-79.875,-59.873,-79.777,-60.579,-79.741,-61.026,-79.809,-61.115,-79.862,-61.343,-79.887,-61.346,-79.951,-61.246,-79.978,-61.302,-79.996,-61.684,-80.02,-61.717,-80.069,-61.694,-80.134,-61.597,-80.206,-61.485,-80.244,-61.194,-80.257,-61.313,-80.307,-61.633,-80.344,-62.232,-80.369,-62.519,-80.373,-65.98,-80.384,-66.115,-80.361,-66.168,-80.346,-66.217,-80.258,-66.296,-80.235,-66.377,-80.222,-66.483,-80.224,-66.588,-80.239,-66.681,-80.26,-66.771,-80.294,-66.734,-80.319,-66.591,-80.358,-66.184,-80.442,-65.203,-80.607,-64.268,-80.749,-64.22,-80.734,-64.126,-80.668,-64.065,-80.65,-63.715,-80.617,-63.144,-80.595,-63.068,-80.627,-62.986,-80.735,-62.94,-80.766,-62.671,-80.834,-62.023,-80.889,-60.583,-80.948,-60.268,-80.881,-60.125,-80.841,-59.926,-80.774,-59.825,-80.733,-59.771,-80.657,-59.774,-80.558,-59.734,-80.344,-59.683,-80.315,-59.612,-80.255,-59.53,-80.208,-59.427,-80.198,-59.322,-80.196,-59.408,-80.151,-59.498,-80.115]},{"b":[-75.383,-72.67,-68.228,-68.789],"l":[-70.543,-72.664,-70.063,-72.626,-69.209,-72.534,-69.149,-72.427,-68.64,-72.21,-68.543,-72.158,-68.461,-72.085,-68.394,-71.975,-68.241,-71.822,-68.228,-71.725,-68.252,-71.313,-68.278,-71.097,-68.314,-70.912,-68.336,-70.856,-68.451,-70.818,-68.459,-70.683,-68.554,-70.581,-68.731,-70.408,-69.091,-70.09,-69.234,-69.909,-69.353,-69.666,-69.417,-69.583,-69.708,-69.321,-69.855,-69.277,-69.913,-69.267,-70.079,-69.311,-70.051,-69.189,-70.053,-69.14,-70.105,-68.959,-70.154,-68.923,-70.312,-68.832,-70.417,-68.789,-71.392,-68.874,-71.869,-68.941,-71.99,-68.971,-72.058,-69.001,-72.109,-69.06,-72.138,-69.115,-72.135,-69.177,-72.114,-69.225,-72.081,-69.267,-71.963,-69.329,-71.834,-69.367,-71.743,-69.423,-71.718,-69.524,-71.767,-69.649,-71.852,-69.807,-71.868,-69.847,-71.879,-69.909,-71.854,-69.969,-71.81,-70.005,-71.729,-70.054,-71.696,-70.068,-71.121,-70.196,-71.024,-70.201,-70.926,-70.192,-70.72,-70.139,-70.328,-70.16,-70.234,-70.18,-70.118,-70.234,-69.883,-70.305,-69.618,-70.398,-69.66,-70.412,-69.702,-70.415,-69.975,-70.36,-70.09,-70.351,-70.328,-70.361,-70.562,-70.404,-71.061,-70.537,-71.19,-70.66,-71.173,-70.713,-71.049,-70.762,-70.917,-70.786,-70.661,-70.818,-70.299,-70.836,-70.196,-70.851,-70.094,-70.883,-69.993,-70.897,-69.933,-70.88,-69.876,-70.876,-69.83,-70.914,-69.823,-70.973,-69.823,-71.034,-69.835,-71.093,-69.87,-71.126,-69.916,-71.134,-70.268,-70.965,-70.323,-70.951,-70.381,-70.946,-70.741,-70.993,-71.194,-70.985,-71.308,-71.011,-71.504,-71.112,-71.719,-71.145,-72.356,-71.075,-72.71,-71.073,-73.06,-71.127,-72.995,-71.187,-72.905,-71.223,-72.43,-71.275,-72.212,-71.335,-72.622,-71.388,-72.821,-71.384,-73.02,-71.369,-73.397,-71.321,-73.474,-71.325,-73.604,-71.351,-73.617,-71.397,-73.592,-71.448,-73.38,-71.528,-73.427,-71.559,-73.479,-71.579,-73.545,-71.573,-73.724,-71.517,-73.937,-71.438,-74.041,-71.41,-74.187,-71.383,-74.236,-71.388,-74.308,-71.4,-74.375,-71.415,-74.425,-71.457,-74.42,-71.507,-74.38,-71.579,-74.373,-71.618,-74.392,-71.638,-74.419,-71.643,-74.487,-71.642,-74.636,-71.617,-74.863,-71.543,-75.1,-71.555,-75.293,-71.615,-75.335,-71.645,-75.353,-71.68,-75.325,-71.726,-75.331,-71.752,-75.373,-71.78,-75.383,-71.828,-75.353,-71.878,-75.259,-71.914,-75.13,-71.964,-75.024,-71.988,-74.908,-72.033,-74.786,-72.064,-74.663,-72.07,-74.429,-72.056,-74.322,-72.073,-74.209,-72.142,-74.153,-72.159,-73.996,-72.17,-73.899,-72.152,-73.537,-72.022,-73.572,-71.981,-73.691,-71.929,-73.83,-71.87,-73.776,-71.849,-73.633,-71.835,-73.41,-71.853,-73.167,-71.905,-72.972,-71.924,-72.928,-71.922,-72.412,-71.662,-72.337,-71.632,-72.259,-71.641,-72.046,-71.74,-71.816,-71.822,-71.574,-71.851,-71.465,-71.838,-71.355,-71.836,-70.821,-71.907,-70.845,-71.946,-70.891,-71.987,-71.034,-72.035,-71.107,-72.047,-71.898,-72.121,-71.892,-72.153,-71.661,-72.25,-71.413,-72.284,-71.178,-72.264,-70.945,-72.229,-70.641,-72.17,-70.533,-72.163,-70.424,-72.168,-70.314,-72.191,-70.206,-72.228,-70.428,-72.323,-70.671,-72.356,-70.873,-72.366,-71.605,-72.359,-72.135,-72.331,-72.376,-72.296,-72.618,-72.275,-72.737,-72.281,-72.855,-72.304,-73.086,-72.408,-73.057,-72.448,-73.007,-72.484,-72.888,-72.547,-72.78,-72.581,-72.67,-72.596,-72.531,-72.59,-72.48,-72.617,-72.433,-72.658,-72.367,-72.67,-71.846,-72.639,-71.159,-72.627,-70.923,-72.613,-70.731,-72.623,-70.543,-72.664]},{"b":[-71.928,-55.032,-65.179,-52.486],"l":[-65.747,-54.653,-65.993,-54.599,-66.236,-54.533,-66.462,-54.441,-66.67,-54.314,-66.865,-54.223,-67.069,-54.148,-67.294,-54.05,-67.503,-53.922,-67.678,-53.787,-67.861,-53.662,-67.94,-53.619,-68.008,-53.564,-68.144,-53.319,-68.161,-53.306,-68.393,-53.295,-68.489,-53.261,-68.521,-53.222,-68.521,-53.177,-68.479,-53.114,-68.431,-53.055,-68.333,-53.02,-68.24,-53.082,-68.278,-52.984,-68.339,-52.9,-68.571,-52.695,-68.63,-52.653,-68.659,-52.632,-68.758,-52.582,-68.79,-52.577,-69.08,-52.674,-69.167,-52.668,-69.414,-52.486,-69.498,-52.491,-69.572,-52.549,-69.663,-52.646,-69.764,-52.731,-69.883,-52.799,-69.935,-52.821,-69.994,-52.821,-70.088,-52.769,-70.19,-52.724,-70.335,-52.734,-70.38,-52.752,-70.297,-52.817,-70.259,-52.857,-70.163,-52.899,-70.14,-52.919,-70.131,-52.943,-70.161,-52.97,-70.196,-52.99,-70.256,-53.004,-70.32,-53.001,-70.391,-53.026,-70.443,-53.086,-70.46,-53.143,-70.46,-53.206,-70.416,-53.305,-70.329,-53.378,-70.213,-53.414,-70.09,-53.418,-69.874,-53.35,-69.756,-53.337,-69.637,-53.334,-69.513,-53.342,-69.394,-53.373,-69.356,-53.416,-69.352,-53.48,-69.39,-53.499,-69.69,-53.601,-69.95,-53.672,-70.091,-53.722,-70.149,-53.761,-70.151,-53.888,-70.086,-54.011,-69.988,-54.109,-69.196,-54.354,-69.044,-54.407,-69.045,-54.428,-69.077,-54.445,-69.128,-54.458,-69.169,-54.483,-69.253,-54.557,-69.312,-54.571,-69.322,-54.543,-69.325,-54.488,-69.365,-54.438,-69.419,-54.407,-69.622,-54.364,-69.742,-54.306,-69.809,-54.321,-69.867,-54.367,-69.99,-54.381,-70.169,-54.379,-70.243,-54.348,-70.246,-54.277,-70.38,-54.181,-70.535,-54.136,-70.63,-54.006,-70.461,-54.006,-70.38,-53.987,-70.443,-53.893,-70.531,-53.627,-70.619,-53.655,-70.696,-53.727,-70.644,-53.823,-70.868,-53.884,-70.857,-53.996,-70.863,-54.11,-70.76,-54.241,-70.636,-54.262,-70.54,-54.303,-70.468,-54.373,-70.298,-54.486,-70.311,-54.529,-70.418,-54.502,-70.573,-54.504,-70.701,-54.485,-70.688,-54.415,-70.699,-54.349,-70.797,-54.327,-70.898,-54.338,-70.928,-54.36,-70.946,-54.398,-70.966,-54.42,-71.08,-54.444,-71.159,-54.451,-71.355,-54.395,-71.393,-54.4,-71.5,-54.445,-71.573,-54.495,-71.606,-54.497,-71.716,-54.444,-71.8,-54.434,-71.823,-54.474,-71.907,-54.495,-71.928,-54.529,-71.902,-54.602,-71.832,-54.626,-71.441,-54.62,-71.229,-54.694,-70.925,-54.714,-70.735,-54.751,-70.497,-54.81,-70.282,-54.752,-70.259,-54.756,-70.238,-54.778,-70.138,-54.819,-70.031,-54.816,-69.899,-54.782,-69.772,-54.739,-69.723,-54.712,-69.588,-54.813,-69.486,-54.859,-69.082,-54.91,-68.844,-54.877,-68.804,-54.854,-68.653,-54.854,-68.619,-54.834,-68.491,-54.836,-68.332,-54.816,-68.22,-54.818,-68.007,-54.848,-67.793,-54.869,-67.127,-54.904,-66.93,-54.925,-66.628,-55.013,-66.511,-55.032,-66.399,-55.009,-66.287,-54.978,-66.172,-54.975,-66.061,-54.957,-65.954,-54.919,-65.842,-54.91,-65.723,-54.926,-65.603,-54.928,-65.471,-54.915,-65.346,-54.878,-65.252,-54.789,-65.179,-54.678,-65.252,-54.638,-65.369,-54.632,-65.747,-54.653]},{"b":[-84.887,19.855,-74.137,23.19],"l":[-74.513,20.385,-74.662,20.522,-74.732,20.573,-74.883,20.651,-74.96,20.673,-75.213,20.714,-75.338,20.702,-75.525,20.717,-75.643,20.733,-75.725,20.715,-75.753,20.736,-75.76,20.776,-75.74,20.812,-75.597,20.838,-75.663,20.898,-75.639,20.947,-75.596,20.995,-75.634,21.061,-75.723,21.111,-75.899,21.114,-76.074,21.133,-76.259,21.227,-76.455,21.274,-76.552,21.272,-76.647,21.285,-76.689,21.34,-76.726,21.359,-76.765,21.362,-76.867,21.33,-76.86,21.365,-76.836,21.4,-76.928,21.459,-77.099,21.589,-77.141,21.539,-77.208,21.479,-77.253,21.483,-77.27,21.538,-77.366,21.613,-77.245,21.594,-77.181,21.598,-77.144,21.644,-77.222,21.672,-77.3,21.712,-77.342,21.755,-77.497,21.872,-77.583,21.889,-77.573,21.868,-77.507,21.811,-77.497,21.788,-77.545,21.775,-77.637,21.797,-77.865,21.901,-77.971,21.972,-78.143,22.109,-78.686,22.367,-78.719,22.358,-78.776,22.367,-78.835,22.391,-78.902,22.396,-79.183,22.388,-79.276,22.408,-79.358,22.449,-79.456,22.51,-79.549,22.578,-79.677,22.743,-79.851,22.827,-79.82,22.887,-79.924,22.869,-79.96,22.877,-80.075,22.942,-80.168,22.949,-80.266,22.935,-80.365,22.943,-80.459,22.975,-80.55,23.017,-80.613,23.084,-80.65,23.103,-81.008,23.09,-81.145,23.055,-81.179,23.06,-81.272,23.129,-81.262,23.157,-81.364,23.13,-81.575,23.117,-81.837,23.163,-82.101,23.19,-82.351,23.154,-82.588,23.065,-82.666,23.044,-83.177,22.983,-83.258,22.968,-84.045,22.666,-84.122,22.619,-84.281,22.474,-84.361,22.379,-84.383,22.256,-84.326,22.074,-84.373,22.036,-84.433,22.031,-84.494,22.042,-84.533,22.031,-84.877,21.894,-84.887,21.857,-84.838,21.828,-84.786,21.842,-84.683,21.899,-84.627,21.92,-84.56,21.933,-84.501,21.93,-84.491,21.854,-84.503,21.776,-84.449,21.792,-84.241,21.898,-84.138,21.929,-84.031,21.943,-83.998,21.98,-83.963,22.092,-83.933,22.15,-83.901,22.17,-83.687,22.18,-83.643,22.189,-83.602,22.209,-83.544,22.209,-83.486,22.187,-83.38,22.223,-83.292,22.303,-83.189,22.355,-83.144,22.386,-83.107,22.43,-83.009,22.514,-82.861,22.595,-82.786,22.658,-82.738,22.689,-81.903,22.679,-81.839,22.672,-81.79,22.657,-81.746,22.633,-81.703,22.592,-81.683,22.535,-81.71,22.497,-81.757,22.467,-81.973,22.422,-82.078,22.388,-81.973,22.291,-81.849,22.214,-81.816,22.2,-81.441,22.184,-81.355,22.104,-81.284,22.109,-81.222,22.143,-81.2,22.203,-81.185,22.268,-81.141,22.207,-81.117,22.134,-81.083,22.098,-81.036,22.074,-80.962,22.053,-80.499,22.064,-80.485,22.087,-80.485,22.123,-80.393,22.034,-80.311,21.933,-80.231,21.872,-80.138,21.829,-79.91,21.743,-79.357,21.585,-79.274,21.563,-79.189,21.553,-78.823,21.619,-78.728,21.593,-78.636,21.516,-78.577,21.414,-78.537,21.297,-78.491,21.054,-78.454,21.011,-78.406,20.974,-78.314,20.927,-78.116,20.762,-77.997,20.715,-77.857,20.714,-77.593,20.69,-77.467,20.69,-77.348,20.672,-77.23,20.644,-77.205,20.611,-77.189,20.56,-77.108,20.492,-77.093,20.453,-77.104,20.408,-77.149,20.347,-77.213,20.3,-77.554,20.082,-77.715,19.855,-77.463,19.861,-77.212,19.894,-76.999,19.893,-76.89,19.921,-76.78,19.94,-76.516,19.957,-76.253,19.987,-76.158,19.99,-75.765,19.96,-75.657,19.932,-75.552,19.891,-75.29,19.893,-75.219,19.924,-75.177,19.959,-75.152,20.008,-75.122,19.954,-75.124,19.925,-75.116,19.901,-75.003,19.929,-74.955,19.958,-74.85,20.002,-74.635,20.058,-74.412,20.075,-74.253,20.08,-74.217,20.117,-74.154,20.169,-74.137,20.232,-74.167,20.292,-74.198,20.311,-74.234,20.326,-74.273,20.317,-74.384,20.33,-74.513,20.385]},{"b":[17.917,79.194,27.199,80.478],"l":[26.861,80.16,26.437,80.175,25.836,80.175,25.751,80.188,25.667,80.21,25.471,80.233,24.907,80.277,24.786,80.301,24.736,80.301,24.614,80.286,24.547,80.295,24.403,80.355,24.298,80.36,24.28,80.329,24.234,80.303,24.143,80.295,23.953,80.305,23.773,80.244,23.688,80.207,23.353,80.179,23.115,80.187,23.225,80.318,23.25,80.381,23.315,80.425,23.251,80.447,23.008,80.474,22.897,80.469,22.793,80.433,22.672,80.413,22.549,80.416,22.451,80.402,22.418,80.366,22.446,80.308,22.443,80.19,22.376,80.09,22.29,80.049,22.19,80.06,21.898,80.132,21.781,80.139,21.697,80.159,21.655,80.218,21.549,80.243,20.998,80.239,20.898,80.25,20.693,80.299,20.476,80.372,20.359,80.401,20.104,80.43,19.851,80.471,19.733,80.478,19.614,80.463,19.691,80.402,19.777,80.353,19.81,80.327,19.802,80.295,19.751,80.227,19.568,80.25,19.327,80.323,19.264,80.336,19.178,80.332,19.157,80.302,19.191,80.263,19.355,80.185,19.537,80.163,19.343,80.116,19.143,80.139,18.962,80.175,18.779,80.194,18.089,80.171,17.917,80.143,18.129,80.093,18.344,80.06,18.856,80.037,18.726,79.996,18.595,79.967,18.255,79.929,18.285,79.887,18.325,79.86,18.428,79.825,18.725,79.761,18.942,79.736,19.4,79.727,19.638,79.729,19.899,79.744,20.123,79.779,20.461,79.775,20.784,79.749,20.687,79.707,20.565,79.691,20.493,79.633,20.187,79.632,20.015,79.64,19.821,79.634,19.747,79.618,19.675,79.591,19.9,79.534,20.128,79.49,20.4,79.463,20.761,79.442,20.806,79.41,20.861,79.398,21.911,79.381,22.866,79.412,22.696,79.329,22.789,79.264,22.904,79.231,23.759,79.206,23.948,79.194,24.133,79.215,24.257,79.263,24.383,79.302,24.751,79.365,24.843,79.367,25.145,79.339,25.239,79.345,25.641,79.403,25.726,79.44,25.902,79.561,26.006,79.617,26.221,79.677,27.08,79.865,27.199,79.907,27.148,80.059,27.017,80.125,26.861,80.16]},{"b":[10.682,76.579,21.389,80.053],"l":[20.725,78.672,21.096,78.676,21.244,78.699,21.389,78.74,21.353,78.772,21.312,78.796,21.09,78.853,20.72,78.907,20.501,78.981,20.767,79.059,20.611,79.107,20.458,79.129,20.163,79.146,20.114,79.125,20.114,79.077,19.894,79.056,19.751,79.147,19.49,79.176,19.089,79.157,18.979,79.179,18.88,79.234,18.772,79.26,18.678,79.262,18.72,79.281,18.807,79.303,18.823,79.337,18.832,79.385,18.815,79.427,18.785,79.461,18.748,79.488,18.581,79.572,18.397,79.605,18.333,79.611,18.272,79.601,17.861,79.437,17.669,79.386,17.715,79.431,17.734,79.481,17.688,79.533,17.733,79.57,17.86,79.635,17.956,79.704,17.835,79.8,17.685,79.857,17.578,79.885,17.219,79.941,16.966,79.959,16.926,79.943,16.889,79.915,16.838,79.905,16.787,79.907,16.524,80.021,16.387,80.053,16.246,80.049,16.094,80.007,16.057,79.954,16.1,79.884,15.956,79.835,15.845,79.734,15.826,79.709,15.816,79.682,15.841,79.587,15.875,79.519,16.028,79.342,16.254,79.112,16.344,78.976,16.295,78.981,15.858,79.16,15.764,79.174,15.66,79.235,15.444,79.407,15.251,79.545,15.052,79.675,14.832,79.766,14.594,79.799,14.38,79.726,14.178,79.619,14.04,79.586,14.02,79.539,14.011,79.482,14.026,79.429,14.056,79.383,14.03,79.344,13.957,79.34,13.834,79.376,13.716,79.429,13.601,79.457,13.432,79.471,13.384,79.481,13.334,79.575,13.215,79.588,12.555,79.569,13.039,79.685,13.778,79.715,13.907,79.752,13.921,79.762,13.926,79.793,13.914,79.817,13.693,79.861,13.108,79.832,12.754,79.776,12.602,79.773,12.28,79.816,12.219,79.798,12.245,79.75,12.288,79.713,12.205,79.719,12.102,79.738,11.702,79.821,11.344,79.799,11.251,79.785,11.185,79.72,11.15,79.717,11.05,79.76,10.866,79.797,10.804,79.799,10.746,79.789,10.682,79.758,10.686,79.734,10.755,79.69,10.811,79.641,10.737,79.582,10.725,79.556,10.738,79.52,10.834,79.463,10.888,79.415,10.926,79.35,10.975,79.305,11.107,79.233,11.208,79.13,11.339,79.109,11.521,79.151,11.616,79.205,11.58,79.283,11.679,79.291,11.978,79.293,12.084,79.268,12.016,79.213,11.893,79.152,11.902,79.112,11.926,79.077,11.982,79.025,12.046,78.983,12.087,78.975,12.253,78.975,12.375,78.966,12.403,78.953,12.323,78.914,12.275,78.904,11.548,78.983,11.456,78.973,11.365,78.95,11.611,78.883,11.861,78.832,11.755,78.812,11.746,78.766,11.774,78.716,11.866,78.674,11.962,78.642,12.138,78.606,12.258,78.595,12.435,78.483,12.665,78.385,12.822,78.351,12.87,78.331,12.913,78.301,13.15,78.238,13.655,78.245,13.908,78.267,14.11,78.271,14.238,78.31,14.363,78.36,14.5,78.392,14.638,78.415,14.546,78.462,14.432,78.492,14.468,78.541,14.515,78.581,14.505,78.631,14.467,78.675,14.578,78.705,14.689,78.721,14.744,78.721,14.792,78.706,14.839,78.666,14.892,78.639,15.016,78.63,15.137,78.664,15.225,78.732,15.323,78.781,15.384,78.771,15.392,78.721,15.348,78.663,15.265,78.608,15.254,78.589,15.279,78.554,15.36,78.488,15.417,78.473,15.681,78.471,15.944,78.493,16.158,78.538,16.446,78.639,16.53,78.656,16.783,78.664,16.697,78.613,16.449,78.504,16.535,78.449,16.727,78.407,16.811,78.397,16.992,78.4,17.172,78.417,17.003,78.369,16.777,78.35,16.15,78.353,15.875,78.339,15.784,78.327,15.657,78.299,15.659,78.265,15.698,78.228,15.519,78.233,15.341,78.221,14.995,78.151,14.248,78.071,14.307,78.005,14.048,78.067,13.937,78.086,13.824,78.085,13.77,78.075,13.718,78.058,13.681,78.028,13.714,77.919,13.75,77.883,13.791,77.854,13.963,77.796,14.09,77.771,14.604,77.766,14.847,77.779,15.097,77.809,15.345,77.857,15.585,77.869,15.826,77.847,16.06,77.847,16.54,77.88,16.853,77.912,16.914,77.898,16.969,77.842,17.033,77.798,16.619,77.799,16.206,77.782,14.921,77.689,14.695,77.525,14.596,77.538,14.488,77.571,14.378,77.58,14.071,77.564,14.026,77.545,13.996,77.508,14.004,77.445,14.05,77.403,14.145,77.336,14.248,77.282,14.366,77.234,14.487,77.199,14.738,77.162,15.124,77.085,15.547,76.886,16.004,76.761,16.124,76.739,16.238,76.702,16.346,76.645,16.462,76.609,16.7,76.579,16.935,76.606,17.063,76.659,17.036,76.72,16.98,76.779,16.977,76.812,17.142,76.895,17.249,76.969,17.188,77.011,17.153,77.049,17.349,77.157,17.442,77.225,17.623,77.399,17.847,77.497,18.137,77.507,18.228,77.523,18.299,77.579,18.362,77.682,18.404,77.794,18.439,77.942,18.431,77.991,18.439,78.025,18.575,78.048,18.712,78.04,18.822,78.042,18.995,78.081,19.009,78.132,18.958,78.182,18.984,78.234,19.056,78.319,19.15,78.379,19.381,78.48,19.619,78.562,19.655,78.598,19.677,78.61,19.769,78.623,20.387,78.643,20.725,78.672]},{"b":[8.181,38.91,9.805,41.257],"l":[9.682,40.818,9.632,40.882,9.574,40.915,9.554,40.932,9.59,40.992,9.621,41.005,9.615,41.017,9.576,41.031,9.539,41.054,9.5,41.106,9.455,41.15,9.351,41.196,9.283,41.202,9.228,41.257,9.182,41.242,9.163,41.185,9.107,41.143,8.998,41.11,8.821,40.95,8.699,40.895,8.572,40.85,8.468,40.834,8.363,40.846,8.31,40.858,8.245,40.907,8.224,40.913,8.204,40.871,8.181,40.771,8.19,40.652,8.23,40.606,8.296,40.559,8.353,40.501,8.385,40.443,8.409,40.352,8.471,40.293,8.471,40.131,8.455,40.078,8.409,40.038,8.399,39.978,8.408,39.917,8.496,39.897,8.548,39.839,8.539,39.77,8.541,39.732,8.511,39.722,8.471,39.748,8.451,39.722,8.461,39.648,8.447,39.563,8.419,39.523,8.399,39.482,8.411,39.292,8.418,39.206,8.486,39.11,8.553,39.03,8.595,38.964,8.649,38.927,8.719,38.927,8.801,38.91,8.881,38.913,8.967,38.964,9.023,39.043,9.056,39.239,9.102,39.211,9.149,39.197,9.207,39.214,9.264,39.217,9.388,39.168,9.486,39.14,9.563,39.166,9.584,39.254,9.617,39.354,9.686,39.924,9.707,40.017,9.701,40.092,9.659,40.159,9.643,40.268,9.754,40.4,9.783,40.442,9.805,40.5,9.794,40.556,9.682,40.818]},{"b":[8.566,41.385,9.556,43.021],"l":[8.642,42.118,8.701,42.096,8.703,42.043,8.653,41.996,8.615,41.959,8.622,41.931,8.674,41.922,8.74,41.925,8.759,41.87,8.719,41.804,8.718,41.761,8.771,41.737,8.887,41.701,8.879,41.669,8.83,41.628,8.808,41.588,8.842,41.559,8.895,41.516,9.003,41.477,9.186,41.385,9.253,41.46,9.331,41.627,9.374,41.679,9.395,41.731,9.401,41.926,9.428,41.972,9.551,42.13,9.556,42.161,9.526,42.553,9.509,42.586,9.473,42.616,9.454,42.659,9.48,42.805,9.479,42.86,9.461,42.945,9.463,42.981,9.415,43.021,9.363,43.017,9.331,42.944,9.323,42.814,9.338,42.767,9.313,42.713,9.288,42.695,9.254,42.712,9.198,42.729,9.138,42.733,9.088,42.705,9.044,42.662,8.995,42.645,8.815,42.608,8.713,42.55,8.64,42.427,8.588,42.385,8.566,42.358,8.592,42.345,8.626,42.343,8.675,42.284,8.608,42.258,8.566,42.219,8.588,42.161,8.642,42.118]},{"b":[12.436,36.688,15.635,38.296],"l":[15.341,38.217,15.28,38.23,15.224,38.211,15.176,38.168,15.119,38.153,14.982,38.168,14.846,38.172,14.79,38.167,14.737,38.151,14.637,38.085,14.506,38.046,14.416,38.043,14.288,38.017,14.05,38.041,13.937,38.024,13.789,37.981,13.735,37.984,13.682,38.001,13.491,38.103,13.433,38.11,13.383,38.127,13.352,38.181,13.291,38.191,13.16,38.19,13.057,38.131,13.049,38.084,12.955,38.041,12.903,38.035,12.851,38.064,12.734,38.183,12.702,38.142,12.664,38.108,12.602,38.085,12.548,38.053,12.487,37.939,12.436,37.82,12.454,37.774,12.527,37.67,12.64,37.594,12.699,37.572,12.757,37.567,12.871,37.575,12.924,37.571,13.04,37.507,13.17,37.479,13.221,37.452,13.265,37.41,13.361,37.349,13.587,37.254,13.801,37.136,13.905,37.101,14.024,37.107,14.143,37.104,14.259,37.046,14.367,36.973,14.502,36.799,14.555,36.777,14.614,36.767,14.776,36.71,14.89,36.724,15.002,36.694,15.113,36.688,15.116,36.736,15.104,36.785,15.116,36.839,15.142,36.892,15.185,36.935,15.295,37.013,15.296,37.055,15.289,37.097,15.236,37.139,15.174,37.209,15.23,37.244,15.194,37.283,15.146,37.308,15.117,37.335,15.106,37.375,15.1,37.459,15.131,37.532,15.165,37.59,15.19,37.651,15.207,37.721,15.234,37.785,15.476,38.063,15.509,38.107,15.577,38.22,15.635,38.268,15.568,38.296,15.499,38.291,15.341,38.217]},{"b":[-54.919,69.257,-51.9,70.317],"l":[-52.113,69.489,-51.988,69.55,-51.9,69.605,-51.908,69.637,-51.982,69.664,-52.007,69.686,-51.985,69.704,-51.977,69.722,-51.983,69.743,-52.011,69.782,-52.045,69.807,-52.398,69.863,-52.731,69.945,-53.103,70.141,-53.297,70.205,-53.375,70.221,-54.007,70.296,-54.372,70.317,-54.706,70.256,-54.786,70.218,-54.816,70.189,-54.83,70.161,-54.831,70.133,-54.809,70.085,-54.774,70.053,-54.652,70.011,-54.323,69.942,-54.363,69.924,-54.665,69.966,-54.788,69.95,-54.841,69.902,-54.919,69.714,-54.866,69.665,-54.804,69.631,-54.734,69.611,-54.497,69.577,-54.133,69.565,-53.994,69.553,-53.921,69.534,-53.825,69.54,-53.783,69.506,-53.722,69.491,-53.658,69.465,-53.89,69.437,-54.047,69.437,-54.158,69.428,-54.183,69.404,-54.121,69.364,-54.051,69.337,-53.902,69.302,-53.793,69.264,-53.754,69.26,-53.578,69.257,-53.003,69.343,-52.77,69.364,-52.113,69.489]},{"b":[-128.35,48.323,-123.284,50.861],"l":[-123.415,48.698,-123.443,48.69,-123.458,48.674,-123.473,48.602,-123.497,48.582,-123.627,48.824,-123.752,48.951,-123.82,49.083,-123.854,49.119,-123.937,49.171,-123.996,49.224,-124.186,49.301,-124.496,49.38,-124.643,49.429,-124.831,49.53,-124.931,49.643,-124.932,49.67,-124.905,49.685,-124.935,49.732,-125.066,49.848,-125.233,50.012,-125.314,50.107,-125.42,50.255,-125.482,50.317,-125.534,50.342,-125.615,50.359,-125.839,50.381,-126.204,50.454,-126.701,50.516,-127.197,50.64,-127.713,50.821,-127.918,50.861,-128.101,50.858,-128.242,50.828,-128.301,50.794,-128.346,50.744,-128.35,50.697,-128.267,50.609,-128.136,50.521,-128.058,50.498,-127.964,50.493,-127.865,50.499,-127.731,50.536,-127.75,50.578,-127.751,50.607,-127.526,50.597,-127.466,50.583,-127.529,50.537,-127.524,50.496,-127.489,50.427,-127.487,50.405,-127.578,50.465,-127.641,50.479,-127.832,50.471,-127.874,50.464,-127.906,50.445,-127.963,50.346,-127.947,50.326,-127.851,50.314,-127.839,50.293,-127.828,50.211,-127.873,50.15,-127.864,50.128,-127.816,50.118,-127.77,50.121,-127.675,50.163,-127.467,50.163,-127.43,50.131,-127.398,50.085,-127.349,50.052,-127.29,50.071,-127.272,50.096,-127.268,50.129,-127.25,50.138,-127.216,50.121,-127.192,50.1,-127.18,50.073,-127.179,50.05,-127.208,49.992,-127.196,49.949,-127.166,49.91,-127.114,49.88,-127.049,49.872,-126.977,49.883,-126.948,49.903,-126.926,49.935,-126.903,49.944,-126.849,49.923,-126.745,49.905,-126.683,49.876,-126.593,49.764,-126.558,49.733,-126.525,49.72,-126.463,49.72,-126.403,49.678,-126.348,49.661,-126.134,49.672,-126.158,49.65,-126.443,49.619,-126.542,49.59,-126.557,49.579,-126.564,49.543,-126.549,49.419,-126.519,49.397,-126.5,49.4,-126.445,49.451,-126.419,49.449,-126.304,49.382,-126.28,49.392,-126.27,49.432,-126.244,49.443,-126.169,49.415,-126.1,49.421,-126.075,49.409,-126.048,49.379,-126.02,49.368,-125.935,49.401,-125.938,49.38,-125.984,49.288,-125.952,49.248,-125.918,49.25,-125.835,49.277,-125.796,49.26,-125.728,49.2,-125.694,49.19,-125.655,49.193,-125.644,49.186,-125.702,49.139,-125.812,49.107,-125.829,49.092,-125.66,49.029,-125.543,48.953,-125.489,48.934,-125.46,48.941,-125.363,48.998,-125.168,48.991,-124.927,49.014,-124.904,49.031,-124.868,49.079,-124.839,49.139,-124.821,49.207,-124.813,49.213,-124.8,49.142,-124.817,49.083,-124.85,49.028,-124.935,48.956,-125.136,48.822,-125.14,48.803,-125.121,48.761,-125.017,48.711,-124.868,48.654,-124.689,48.597,-124.376,48.515,-124.115,48.436,-123.917,48.387,-123.595,48.334,-123.573,48.323,-123.536,48.345,-123.485,48.4,-123.446,48.427,-123.335,48.406,-123.311,48.411,-123.284,48.455,-123.366,48.606,-123.39,48.67,-123.415,48.698]},{"b":[-74.478,17.636,-68.339,19.928],"l":[-68.685,18.905,-68.901,18.988,-69.031,19.013,-69.163,19.028,-69.28,19.052,-69.395,19.086,-69.508,19.108,-69.624,19.118,-69.623,19.16,-69.606,19.206,-69.52,19.212,-69.323,19.201,-69.264,19.226,-69.232,19.272,-69.325,19.328,-69.739,19.299,-69.823,19.367,-69.878,19.473,-69.891,19.59,-69.957,19.672,-70.014,19.673,-70.129,19.636,-70.194,19.638,-70.305,19.676,-70.436,19.771,-70.479,19.777,-70.636,19.776,-70.686,19.793,-70.785,19.851,-70.834,19.887,-70.954,19.914,-71.082,19.89,-71.236,19.848,-71.281,19.847,-71.442,19.894,-71.558,19.895,-71.616,19.877,-71.667,19.849,-71.706,19.795,-71.735,19.735,-71.779,19.718,-71.835,19.697,-71.954,19.722,-72.22,19.745,-72.43,19.813,-72.637,19.901,-72.877,19.928,-73.118,19.904,-73.218,19.884,-73.315,19.855,-73.401,19.807,-73.438,19.722,-73.396,19.659,-73.316,19.637,-73.053,19.611,-72.863,19.526,-72.703,19.441,-72.742,19.342,-72.768,19.241,-72.741,19.131,-72.811,19.072,-72.649,18.894,-72.465,18.744,-72.348,18.675,-72.347,18.624,-72.376,18.574,-72.418,18.559,-72.618,18.551,-72.66,18.515,-72.696,18.468,-72.739,18.442,-72.789,18.435,-72.917,18.456,-73.592,18.522,-73.687,18.565,-73.862,18.575,-73.976,18.601,-74.1,18.641,-74.228,18.663,-74.284,18.657,-74.387,18.625,-74.478,18.45,-74.46,18.393,-74.419,18.346,-74.195,18.269,-74.085,18.215,-73.989,18.143,-73.885,18.042,-73.839,18.058,-73.825,18.122,-73.747,18.19,-73.644,18.229,-73.515,18.245,-73.385,18.251,-73.272,18.234,-73.16,18.206,-72.877,18.152,-72.755,18.156,-72.633,18.176,-72.592,18.187,-72.553,18.208,-72.504,18.22,-72.06,18.229,-72.002,18.212,-71.946,18.186,-71.853,18.119,-71.768,18.039,-71.712,18.005,-71.674,17.954,-71.657,17.889,-71.658,17.821,-71.632,17.774,-71.569,17.757,-71.518,17.725,-71.439,17.636,-71.396,17.646,-71.358,17.694,-71.267,17.85,-71.106,18.07,-71.083,18.128,-71.082,18.224,-71.07,18.25,-71.028,18.273,-70.924,18.292,-70.759,18.346,-70.645,18.336,-70.565,18.268,-70.48,18.217,-70.183,18.252,-70.142,18.277,-70.063,18.346,-70.018,18.374,-69.896,18.418,-69.771,18.444,-69.645,18.436,-69.519,18.416,-69.397,18.42,-69.275,18.44,-69.072,18.399,-68.935,18.408,-68.82,18.339,-68.778,18.266,-68.721,18.218,-68.687,18.215,-68.659,18.222,-68.612,18.306,-68.564,18.355,-68.493,18.379,-68.445,18.418,-68.359,18.538,-68.339,18.612,-68.381,18.671,-68.445,18.714,-68.685,18.905]},{"b":[-50.796,-1.801,-48.38,-0.116],"l":[-48.38,-0.353,-48.393,-0.297,-48.444,-0.272,-48.515,-0.248,-48.588,-0.232,-48.787,-0.216,-49.117,-0.164,-49.215,-0.159,-49.314,-0.168,-49.403,-0.215,-49.535,-0.234,-49.629,-0.229,-50.248,-0.116,-50.462,-0.157,-50.646,-0.273,-50.694,-0.364,-50.716,-0.47,-50.703,-0.529,-50.72,-0.583,-50.771,-0.645,-50.781,-0.69,-50.796,-0.906,-50.783,-1.01,-50.71,-1.078,-50.593,-1.073,-50.577,-1.103,-50.581,-1.139,-50.596,-1.147,-50.668,-1.131,-50.729,-1.127,-50.76,-1.24,-50.724,-1.371,-50.673,-1.516,-50.617,-1.638,-50.602,-1.698,-50.508,-1.788,-50.443,-1.801,-50.338,-1.756,-50.109,-1.748,-50.066,-1.704,-50.01,-1.708,-49.911,-1.763,-49.805,-1.79,-49.749,-1.755,-49.651,-1.738,-49.588,-1.712,-49.526,-1.63,-49.507,-1.512,-49.407,-1.556,-49.345,-1.595,-49.234,-1.6,-49.205,-1.559,-49.182,-1.485,-49.173,-1.413,-49.087,-1.505,-49.038,-1.514,-48.986,-1.505,-48.929,-1.482,-48.834,-1.39,-48.804,-1.327,-48.829,-1.277,-48.84,-1.227,-48.79,-1.173,-48.729,-1.132,-48.705,-1.107,-48.624,-0.987,-48.571,-0.893,-48.55,-0.848,-48.54,-0.801,-48.567,-0.684,-48.523,-0.691,-48.497,-0.665,-48.464,-0.535,-48.428,-0.442,-48.38,-0.353]},{"b":[-6.134,50.021,1.747,58.65],"l":[-3.988,57.581,-3.868,57.6,-3.628,57.662,-3.403,57.708,-3.295,57.71,-3.084,57.673,-3.036,57.672,-2.947,57.689,-2.856,57.692,-2.244,57.681,-2.074,57.702,-1.962,57.677,-1.867,57.612,-1.778,57.494,-1.781,57.474,-1.835,57.42,-1.934,57.352,-2.02,57.259,-2.046,57.209,-2.062,57.153,-2.09,57.103,-2.26,56.863,-2.427,56.731,-2.501,56.637,-2.593,56.562,-2.681,56.514,-2.775,56.483,-3.047,56.449,-3.124,56.425,-3.214,56.384,-3.31,56.363,-3.198,56.366,-3.087,56.389,-2.885,56.398,-2.653,56.318,-2.674,56.253,-2.768,56.202,-2.98,56.194,-3.178,56.08,-3.268,56.045,-3.362,56.028,-3.48,56.033,-3.695,56.063,-3.789,56.095,-3.704,56.043,-3.608,56.016,-3.049,55.952,-3.015,55.959,-2.837,56.026,-2.599,56.027,-2.147,55.903,-2.017,55.808,-1.83,55.672,-1.729,55.619,-1.655,55.57,-1.61,55.498,-1.523,55.26,-1.423,55.026,-1.292,54.774,-1.232,54.704,-1.154,54.654,-0.759,54.541,-0.671,54.504,-0.518,54.395,-0.37,54.279,-0.233,54.19,-0.084,54.118,-0.156,54.081,-0.206,54.022,-0.169,53.942,-0.108,53.865,0.011,53.743,0.115,53.609,0.077,53.629,0.036,53.641,-0.019,53.637,-0.074,53.644,-0.174,53.685,-0.27,53.737,-0.461,53.716,-0.568,53.725,-0.66,53.724,-0.485,53.694,-0.294,53.692,0.128,53.468,0.271,53.335,0.356,53.16,0.298,53.081,0.208,53.03,0.124,52.972,0.046,52.906,0.28,52.809,0.33,52.812,0.382,52.825,0.432,52.858,0.516,52.938,0.559,52.967,0.704,52.977,0.827,52.971,0.949,52.953,1.056,52.959,1.271,52.925,1.382,52.894,1.657,52.754,1.716,52.677,1.743,52.579,1.747,52.469,1.7,52.369,1.647,52.279,1.615,52.162,1.591,52.12,1.559,52.087,1.413,51.995,1.317,51.957,1.276,51.974,1.232,51.971,1.228,51.949,1.274,51.902,1.274,51.845,1.188,51.803,1.101,51.785,0.955,51.808,0.752,51.73,0.898,51.689,0.927,51.647,0.891,51.571,0.799,51.538,0.698,51.523,0.593,51.519,0.507,51.501,0.425,51.466,0.528,51.484,0.6,51.468,0.646,51.405,0.687,51.387,0.889,51.36,1.015,51.36,1.257,51.375,1.373,51.375,1.415,51.363,1.416,51.311,1.398,51.182,1.366,51.155,1.044,51.047,0.979,50.972,0.96,50.926,0.772,50.934,0.684,50.886,0.532,50.853,0.415,50.819,0.3,50.776,0.205,50.763,-0.204,50.814,-0.451,50.81,-0.785,50.765,-0.871,50.773,-1.001,50.816,-1.133,50.845,-1.285,50.857,-1.416,50.897,-1.334,50.821,-1.517,50.747,-1.601,50.733,-1.688,50.735,-1.866,50.715,-2.031,50.725,-2.006,50.673,-1.962,50.628,-1.998,50.608,-2.036,50.603,-2.35,50.637,-2.395,50.631,-2.433,50.599,-2.548,50.616,-2.659,50.67,-2.777,50.706,-2.901,50.722,-2.999,50.717,-3.405,50.632,-3.485,50.548,-3.526,50.428,-3.584,50.322,-3.68,50.24,-3.793,50.229,-3.9,50.286,-4.103,50.349,-4.173,50.391,-4.195,50.393,-4.217,50.378,-4.297,50.359,-4.379,50.358,-4.507,50.341,-4.728,50.29,-4.817,50.256,-5.01,50.161,-5.049,50.134,-5.119,50.038,-5.225,50.021,-5.323,50.083,-5.434,50.104,-5.551,50.083,-5.622,50.051,-5.655,50.077,-5.656,50.132,-5.571,50.197,-5.342,50.246,-5.142,50.374,-5.043,50.452,-5.004,50.495,-4.956,50.523,-4.894,50.534,-4.861,50.582,-4.583,50.776,-4.56,50.821,-4.546,50.901,-4.523,50.977,-4.296,51.027,-4.188,51.189,-4.158,51.201,-3.842,51.231,-3.608,51.229,-3.375,51.197,-3.256,51.194,-3.136,51.205,-3.042,51.249,-2.881,51.406,-2.791,51.475,-2.687,51.537,-2.59,51.609,-2.433,51.741,-2.539,51.695,-2.668,51.623,-2.742,51.581,-2.979,51.539,-3.08,51.496,-3.259,51.398,-3.293,51.39,-3.562,51.414,-3.763,51.54,-3.891,51.592,-3.944,51.598,-3.998,51.582,-4.115,51.566,-4.235,51.569,-4.174,51.627,-4.091,51.66,-4.276,51.683,-4.328,51.7,-4.386,51.741,-4.531,51.748,-4.601,51.738,-4.718,51.684,-4.902,51.626,-5.125,51.706,-5.168,51.741,-5.167,51.808,-5.201,51.861,-5.262,51.88,-5.183,51.95,-5.088,51.996,-4.879,52.042,-4.561,52.151,-4.383,52.197,-4.218,52.277,-4.149,52.326,-4.1,52.393,-4.051,52.475,-3.98,52.542,-4.048,52.558,-4.079,52.608,-4.071,52.659,-4.039,52.704,-4.067,52.761,-4.118,52.82,-4.115,52.866,-4.101,52.915,-4.229,52.913,-4.356,52.897,-4.472,52.862,-4.584,52.815,-4.683,52.806,-4.681,52.844,-4.638,52.891,-4.526,52.958,-4.405,53.014,-4.362,53.056,-4.328,53.105,-4.269,53.145,-4.111,53.219,-3.809,53.303,-3.764,53.308,-3.646,53.298,-3.53,53.311,-3.428,53.341,-3.326,53.347,-3.098,53.26,-3.166,53.395,-3.065,53.427,-2.919,53.305,-2.864,53.293,-2.75,53.31,-2.794,53.331,-2.845,53.332,-2.913,53.35,-2.97,53.389,-3.065,53.513,-3.059,53.586,-2.996,53.663,-2.925,53.733,-2.984,53.747,-3.032,53.774,-3.045,53.844,-3.027,53.906,-2.9,53.961,-2.862,54.044,-2.846,54.135,-2.868,54.177,-2.994,54.171,-3.055,54.153,-3.11,54.126,-3.166,54.128,-3.322,54.229,-3.41,54.306,-3.569,54.468,-3.592,54.564,-3.465,54.773,-3.268,54.907,-3.036,54.953,-3.081,54.962,-3.434,54.964,-3.55,54.947,-3.658,54.893,-3.719,54.876,-3.783,54.87,-3.842,54.843,-3.899,54.805,-3.958,54.781,-4.076,54.787,-4.133,54.779,-4.174,54.801,-4.208,54.837,-4.253,54.847,-4.304,54.836,-4.41,54.787,-4.517,54.758,-4.648,54.789,-4.818,54.846,-4.852,54.825,-4.89,54.772,-4.911,54.689,-5.032,54.761,-5.135,54.858,-5.17,54.918,-5.173,54.986,-5.117,55.012,-5.056,54.988,-4.965,55.149,-4.785,55.359,-4.721,55.421,-4.677,55.501,-4.684,55.554,-4.724,55.598,-4.892,55.699,-4.89,55.781,-4.872,55.874,-4.826,55.93,-4.807,55.94,-4.584,55.939,-4.671,55.967,-4.844,56.051,-4.841,56.081,-4.8,56.158,-4.819,56.15,-4.856,56.115,-4.927,56.028,-4.97,56.008,-5.093,55.987,-5.115,55.945,-5.135,55.933,-5.196,55.929,-5.215,55.889,-5.228,55.886,-5.246,55.929,-5.247,56,-5.223,56.066,-5.176,56.117,-4.997,56.233,-5.084,56.197,-5.282,56.09,-5.383,56.019,-5.41,55.995,-5.419,55.975,-5.418,55.952,-5.373,55.828,-5.386,55.77,-5.556,55.39,-5.589,55.351,-5.618,55.331,-5.647,55.327,-5.731,55.334,-5.768,55.363,-5.768,55.395,-5.752,55.443,-5.681,55.624,-5.651,55.674,-5.605,55.721,-5.504,55.802,-5.507,55.808,-5.574,55.792,-5.602,55.797,-5.623,55.813,-5.61,56.055,-5.555,56.135,-5.535,56.251,-5.488,56.35,-5.433,56.422,-5.392,56.515,-5.329,56.556,-5.313,56.619,-5.243,56.687,-5.188,56.758,-5.218,56.751,-5.564,56.566,-5.652,56.532,-5.773,56.541,-5.865,56.562,-5.937,56.606,-5.969,56.69,-6.058,56.692,-6.134,56.707,-6.133,56.718,-6.035,56.764,-5.878,56.78,-5.731,56.853,-5.861,56.903,-5.85,56.918,-5.736,56.961,-5.591,57.102,-5.562,57.233,-5.631,57.294,-5.656,57.334,-5.795,57.379,-5.818,57.436,-5.802,57.468,-5.757,57.499,-5.689,57.524,-5.582,57.547,-5.679,57.572,-5.715,57.601,-5.742,57.644,-5.745,57.668,-5.695,57.778,-5.665,57.824,-5.608,57.881,-5.349,57.878,-5.319,57.904,-5.29,57.905,-5.157,57.881,-5.177,57.906,-5.394,58.044,-5.413,58.07,-5.351,58.144,-5.347,58.177,-5.356,58.212,-5.338,58.239,-5.27,58.251,-5.06,58.25,-5.008,58.263,-5.032,58.298,-5.081,58.345,-5.09,58.385,-5.079,58.419,-5.076,58.489,-5.067,58.52,-5.017,58.567,-4.976,58.58,-4.925,58.588,-4.81,58.573,-4.766,58.554,-4.715,58.51,-4.678,58.514,-4.535,58.562,-4.492,58.568,-4.433,58.513,-4.189,58.557,-3.86,58.577,-3.662,58.606,-3.454,58.617,-3.259,58.65,-3.053,58.635,-3.046,58.616,-3.057,58.589,-3.11,58.515,-3.101,58.434,-3.113,58.409,-3.137,58.378,-3.212,58.321,-3.411,58.24,-3.775,58.052,-3.99,57.959,-4.02,57.914,-4.036,57.852,-3.907,57.84,-3.857,57.819,-3.888,57.787,-4.078,57.677,-4.135,57.578,-3.988,57.581]},{"b":[-10.39,51.474,-5.47,55.366],"l":[-9.542,51.664,-9.525,51.681,-9.58,51.689,-9.899,51.647,-10.01,51.611,-10.121,51.601,-10.069,51.656,-9.926,51.731,-9.85,51.766,-9.803,51.78,-9.75,51.824,-9.599,51.874,-10.084,51.771,-10.212,51.784,-10.242,51.812,-10.341,51.799,-10.379,51.869,-10.232,51.975,-10.146,52.02,-10.044,52.045,-9.946,52.08,-9.91,52.123,-9.956,52.137,-10.25,52.126,-10.39,52.135,-10.383,52.169,-10.357,52.207,-10.211,52.272,-10.132,52.282,-10.062,52.276,-9.993,52.259,-9.937,52.238,-9.772,52.25,-9.841,52.291,-9.853,52.375,-9.906,52.404,-9.838,52.443,-9.761,52.466,-9.632,52.547,-9.586,52.559,-9.331,52.579,-9.056,52.621,-8.783,52.68,-8.923,52.712,-8.99,52.755,-9.098,52.668,-9.175,52.635,-9.394,52.617,-9.463,52.627,-9.561,52.654,-9.591,52.644,-9.62,52.623,-9.764,52.58,-9.917,52.57,-9.74,52.648,-9.515,52.781,-9.465,52.823,-9.394,52.896,-9.416,52.929,-9.462,52.947,-9.299,53.098,-9.242,53.125,-9.138,53.129,-9.061,53.153,-9.027,53.153,-8.997,53.162,-8.93,53.207,-9.034,53.236,-9.14,53.25,-9.471,53.235,-9.514,53.238,-9.555,53.252,-9.582,53.272,-9.602,53.323,-9.626,53.334,-9.701,53.334,-9.774,53.319,-9.825,53.32,-9.876,53.343,-9.795,53.395,-9.899,53.407,-10.004,53.397,-10.091,53.413,-10.094,53.446,-10.054,53.478,-10.106,53.509,-10.117,53.549,-10.062,53.568,-10.001,53.561,-9.878,53.59,-9.721,53.604,-9.856,53.633,-9.91,53.658,-9.912,53.695,-9.902,53.727,-9.745,53.781,-9.578,53.805,-9.591,53.841,-9.579,53.88,-9.748,53.891,-9.914,53.864,-9.896,53.938,-9.856,54.004,-9.848,54.048,-9.856,54.095,-9.934,54.075,-9.944,54.142,-9.977,54.187,-10.093,54.156,-10.09,54.216,-10.056,54.258,-9.996,54.276,-9.936,54.268,-9.825,54.269,-9.717,54.3,-9.562,54.309,-9.316,54.299,-9.146,54.21,-9.102,54.226,-9.034,54.282,-9.002,54.288,-8.747,54.263,-8.588,54.231,-8.546,54.241,-8.568,54.304,-8.623,54.347,-8.554,54.404,-8.471,54.442,-8.415,54.461,-8.287,54.485,-8.23,54.507,-8.193,54.58,-8.133,54.641,-8.457,54.609,-8.764,54.681,-8.715,54.732,-8.65,54.761,-8.538,54.783,-8.528,54.809,-8.471,54.832,-8.377,54.889,-8.412,54.965,-8.393,55.02,-8.326,55.056,-8.305,55.108,-8.275,55.146,-8.138,55.16,-8.006,55.195,-7.959,55.192,-7.803,55.2,-7.751,55.186,-7.763,55.248,-7.667,55.256,-7.63,55.244,-7.613,55.2,-7.57,55.171,-7.557,55.122,-7.586,55.084,-7.634,55.055,-7.59,55.025,-7.659,54.971,-7.584,54.994,-7.478,55.047,-7.484,55.09,-7.502,55.145,-7.531,55.194,-7.518,55.248,-7.458,55.282,-7.302,55.299,-7.366,55.36,-7.309,55.366,-7.247,55.353,-7.155,55.305,-7.06,55.268,-6.962,55.238,-7.056,55.178,-7.173,55.137,-7.219,55.092,-7.179,55.057,-7.101,55.048,-7.031,55.081,-6.947,55.183,-6.889,55.189,-6.825,55.181,-6.699,55.193,-6.475,55.241,-6.375,55.242,-6.234,55.217,-6.129,55.217,-6.036,55.145,-5.986,55.03,-5.869,54.916,-5.717,54.817,-5.711,54.757,-5.765,54.725,-5.879,54.684,-5.879,54.641,-5.803,54.663,-5.739,54.673,-5.583,54.663,-5.528,54.62,-5.49,54.554,-5.47,54.5,-5.484,54.442,-5.526,54.46,-5.569,54.513,-5.616,54.537,-5.671,54.55,-5.646,54.478,-5.656,54.382,-5.632,54.373,-5.558,54.371,-5.607,54.273,-5.708,54.246,-5.826,54.236,-5.855,54.201,-5.876,54.156,-5.938,54.089,-6.019,54.051,-6.12,54.059,-6.218,54.089,-6.176,54.054,-6.157,54.017,-6.231,54.004,-6.308,54.011,-6.345,53.987,-6.348,53.941,-6.322,53.882,-6.27,53.84,-6.229,53.746,-6.195,53.641,-6.142,53.578,-6.131,53.499,-6.139,53.46,-6.129,53.391,-6.152,53.366,-6.135,53.301,-6.072,53.166,-6.045,53.091,-6.027,52.927,-6.071,52.866,-6.131,52.807,-6.169,52.738,-6.199,52.663,-6.217,52.543,-6.345,52.402,-6.4,52.367,-6.463,52.345,-6.325,52.247,-6.438,52.203,-6.561,52.189,-6.697,52.214,-6.782,52.21,-6.86,52.179,-6.89,52.159,-6.915,52.169,-6.966,52.25,-7.003,52.166,-7.082,52.139,-7.216,52.145,-7.441,52.123,-7.527,52.099,-7.563,52.062,-7.59,52.019,-7.625,51.993,-7.665,51.98,-7.838,51.948,-7.872,51.935,-7.952,51.866,-8.058,51.826,-8.145,51.814,-8.222,51.854,-8.254,51.878,-8.29,51.891,-8.409,51.889,-8.372,51.876,-8.347,51.848,-8.336,51.793,-8.349,51.739,-8.408,51.712,-8.478,51.707,-8.588,51.651,-8.734,51.636,-8.813,51.585,-9.296,51.498,-9.324,51.497,-9.391,51.519,-9.463,51.529,-9.535,51.522,-9.737,51.474,-9.835,51.483,-9.71,51.604,-9.542,51.664]},{"b":[-180,64.28,-169.729,68.983],"l":[-180,68.983,-179.799,68.94,-179.595,68.906,-179.515,68.917,-179.471,68.912,-179.356,68.853,-179.279,68.825,-178.874,68.754,-178.689,68.675,-178.539,68.586,-178.614,68.603,-178.751,68.66,-178.737,68.593,-178.693,68.546,-178.474,68.502,-178.244,68.467,-178.097,68.425,-178.049,68.388,-178.019,68.323,-178.056,68.265,-177.922,68.287,-177.797,68.338,-177.862,68.378,-178.285,68.519,-178.373,68.566,-178.25,68.541,-177.683,68.363,-177.527,68.294,-177.593,68.281,-177.639,68.241,-177.589,68.224,-177.521,68.237,-177.408,68.245,-177.297,68.223,-177.172,68.175,-176.907,68.119,-175.345,67.678,-175.31,67.602,-175.266,67.567,-175.24,67.521,-175.233,67.447,-175.375,67.357,-175.155,67.365,-175.123,67.377,-175.066,67.413,-175.003,67.438,-174.918,67.408,-174.85,67.349,-174.87,67.269,-174.93,67.203,-174.938,67.093,-174.885,67,-174.829,66.961,-174.784,66.917,-174.771,66.784,-174.87,66.725,-174.925,66.623,-174.864,66.613,-174.675,66.603,-174.612,66.585,-174.504,66.538,-174.478,66.492,-174.454,66.43,-174.419,66.372,-174.394,66.344,-174.366,66.348,-174.257,66.428,-174.206,66.452,-174.085,66.473,-174.018,66.383,-174.065,66.23,-174.025,66.23,-173.994,66.246,-173.955,66.287,-173.9,66.31,-173.832,66.366,-173.774,66.435,-173.843,66.488,-173.921,66.522,-174.102,66.541,-174.196,66.581,-174.232,66.632,-174.14,66.653,-174.061,66.69,-174.006,66.779,-174.019,66.827,-174.041,66.875,-174.086,66.943,-174.154,66.982,-174.284,67.002,-174.342,67.04,-174.431,67.038,-174.519,67.049,-174.554,67.063,-174.55,67.091,-174.448,67.103,-173.884,67.106,-173.68,67.145,-173.587,67.133,-173.494,67.105,-173.158,67.069,-173.168,67.052,-173.224,67.035,-173.324,66.955,-173.343,66.909,-173.347,66.851,-173.259,66.84,-173.175,66.865,-173.216,66.911,-173.228,66.969,-173.193,66.994,-173.147,66.999,-173.058,66.956,-172.963,66.942,-172.641,66.925,-172.549,66.931,-172.52,66.952,-172.583,66.978,-173.002,67.034,-173.008,67.065,-172.621,67.027,-172.447,66.992,-172.274,66.966,-172.031,66.973,-171.796,66.932,-171.57,66.819,-171.36,66.677,-171.149,66.593,-170.927,66.53,-170.556,66.357,-170.51,66.344,-170.473,66.32,-170.543,66.291,-170.604,66.249,-170.483,66.278,-170.361,66.298,-170.301,66.294,-170.247,66.272,-170.212,66.236,-170.192,66.201,-170.244,66.169,-169.889,66.163,-169.778,66.143,-169.729,66.058,-169.832,65.999,-169.892,66.006,-169.949,66.031,-170.004,66.033,-170.159,66.008,-170.401,65.929,-170.541,65.865,-170.563,65.824,-170.541,65.71,-170.561,65.656,-170.666,65.622,-170.897,65.643,-171.001,65.665,-171.119,65.695,-171.232,65.737,-171.377,65.804,-171.422,65.81,-171.451,65.794,-171.402,65.752,-171.303,65.698,-171.134,65.628,-171.054,65.55,-171.106,65.511,-171.17,65.502,-171.216,65.503,-171.364,65.527,-171.466,65.533,-171.79,65.51,-171.907,65.496,-171.947,65.508,-171.957,65.542,-172.131,65.567,-172.234,65.57,-172.282,65.582,-172.322,65.618,-172.436,65.67,-172.608,65.69,-172.719,65.692,-172.783,65.681,-172.557,65.612,-172.354,65.496,-172.392,65.475,-172.418,65.45,-172.306,65.448,-172.233,65.456,-172.212,65.425,-172.27,65.303,-172.309,65.276,-172.662,65.249,-172.573,65.228,-172.482,65.222,-172.379,65.227,-172.286,65.206,-172.224,65.129,-172.213,65.048,-172.304,65.002,-172.399,64.965,-172.593,64.908,-172.792,64.883,-172.897,64.889,-172.999,64.877,-173.066,64.847,-173.086,64.817,-172.998,64.837,-172.897,64.826,-172.801,64.791,-172.812,64.761,-172.903,64.729,-172.924,64.705,-172.889,64.664,-172.901,64.629,-172.854,64.61,-172.747,64.603,-172.616,64.578,-172.487,64.544,-172.437,64.515,-172.394,64.475,-172.379,64.432,-172.401,64.414,-172.695,64.407,-172.739,64.412,-172.756,64.46,-172.792,64.499,-172.903,64.526,-172.949,64.507,-172.916,64.369,-172.96,64.328,-173.009,64.297,-173.157,64.28,-173.275,64.29,-173.376,64.355,-173.376,64.41,-173.309,64.443,-173.309,64.487,-173.327,64.54,-173.396,64.479,-173.475,64.429,-173.604,64.365,-173.666,64.357,-173.73,64.365,-173.898,64.41,-174.001,64.449,-174.205,64.578,-174.318,64.638,-174.571,64.718,-174.83,64.776,-175.036,64.814,-175.146,64.809,-175.256,64.794,-175.395,64.802,-175.442,64.817,-175.483,64.849,-175.521,64.867,-175.716,64.946,-175.854,65.011,-175.859,65.054,-175.83,65.106,-175.856,65.233,-175.923,65.352,-176.093,65.471,-176.547,65.548,-176.922,65.601,-177.056,65.614,-177.175,65.602,-177.489,65.504,-177.699,65.49,-178.31,65.485,-178.412,65.496,-178.505,65.537,-178.526,65.593,-178.499,65.697,-178.502,65.74,-178.526,65.755,-178.559,65.754,-178.679,65.795,-178.791,65.865,-178.879,65.936,-178.939,66.033,-178.858,66.038,-178.747,66.014,-178.731,66.037,-178.694,66.124,-178.616,66.166,-178.587,66.198,-178.534,66.317,-178.527,66.402,-178.616,66.355,-178.753,66.237,-178.821,66.203,-178.868,66.187,-178.916,66.18,-179.026,66.204,-179.105,66.232,-179.107,66.346,-179.143,66.375,-179.178,66.353,-179.193,66.313,-179.293,66.305,-179.34,66.288,-179.316,66.22,-179.327,66.163,-179.423,66.141,-179.616,66.128,-179.683,66.184,-179.741,66.106,-179.784,66.018,-179.79,65.901,-179.728,65.804,-179.641,65.758,-179.449,65.688,-179.366,65.639,-179.344,65.575,-179.352,65.517,-179.452,65.445,-179.519,65.386,-179.635,65.244,-179.705,65.187,-180,65.067]},{"b":[-24.476,63.407,-13.556,66.526],"l":[-14.788,66.331,-14.912,66.284,-15.03,66.178,-15.117,66.126,-15.116,66.102,-15.01,66.061,-14.894,66.038,-14.787,66.059,-14.74,66.051,-14.698,66.02,-14.674,65.99,-14.669,65.96,-14.688,65.897,-14.753,65.834,-14.839,65.781,-14.827,65.764,-14.758,65.756,-14.426,65.79,-14.392,65.787,-14.373,65.77,-14.369,65.739,-14.351,65.71,-14.318,65.684,-14.328,65.658,-14.473,65.575,-14.302,65.628,-14.167,65.642,-13.935,65.616,-13.841,65.586,-13.785,65.533,-13.705,65.551,-13.67,65.55,-13.618,65.519,-13.616,65.487,-13.654,65.441,-13.668,65.399,-13.708,65.382,-13.783,65.369,-13.805,65.355,-13.772,65.323,-13.723,65.291,-13.653,65.29,-13.641,65.275,-13.64,65.257,-13.649,65.237,-13.672,65.223,-13.707,65.215,-13.755,65.193,-13.581,65.143,-13.559,65.125,-13.556,65.098,-13.57,65.068,-13.599,65.036,-13.652,65.017,-13.777,65.014,-13.854,64.993,-13.828,64.958,-13.83,64.914,-13.853,64.862,-13.952,64.784,-14.044,64.742,-14.135,64.715,-14.297,64.724,-14.385,64.745,-14.375,64.677,-14.465,64.636,-14.448,64.601,-14.417,64.583,-14.433,64.538,-14.475,64.494,-14.547,64.446,-14.628,64.416,-14.79,64.38,-14.927,64.32,-15.022,64.296,-15.256,64.297,-15.495,64.258,-15.833,64.177,-16.06,64.111,-16.236,64.037,-16.468,63.916,-16.64,63.865,-16.74,63.852,-16.933,63.841,-17.095,63.808,-17.633,63.747,-17.816,63.713,-17.839,63.682,-17.915,63.636,-17.92,63.62,-17.886,63.607,-17.88,63.59,-17.947,63.536,-18.08,63.496,-18.143,63.497,-18.219,63.531,-18.252,63.53,-18.265,63.525,-18.266,63.514,-18.222,63.473,-18.303,63.454,-18.654,63.407,-19.25,63.442,-19.487,63.479,-19.778,63.537,-19.952,63.552,-20.198,63.556,-20.4,63.637,-20.494,63.687,-20.502,63.708,-20.491,63.732,-20.47,63.748,-20.438,63.757,-20.372,63.758,-20.363,63.765,-20.414,63.805,-20.463,63.792,-20.593,63.735,-20.651,63.737,-20.727,63.766,-20.73,63.793,-20.879,63.804,-21.008,63.838,-21.137,63.888,-21.156,63.907,-21.094,63.934,-21.106,63.94,-21.152,63.945,-21.246,63.935,-21.388,63.873,-21.449,63.858,-22.373,63.844,-22.607,63.837,-22.652,63.828,-22.693,63.869,-22.729,63.959,-22.743,64.019,-22.734,64.048,-22.701,64.083,-22.651,64.077,-22.603,64.05,-22.56,64.01,-22.51,63.991,-22.188,64.039,-22.057,64.071,-22.001,64.102,-21.935,64.154,-21.866,64.18,-21.833,64.205,-21.768,64.285,-21.723,64.322,-21.669,64.349,-21.606,64.367,-21.463,64.379,-21.557,64.398,-21.647,64.398,-21.951,64.314,-22.053,64.314,-22.049,64.327,-22.006,64.351,-21.901,64.392,-21.973,64.395,-22.001,64.413,-22.004,64.452,-21.95,64.515,-21.702,64.598,-21.617,64.61,-21.591,64.626,-21.623,64.64,-21.675,64.648,-21.924,64.563,-22.106,64.533,-22.16,64.539,-22.254,64.572,-22.284,64.587,-22.325,64.624,-22.32,64.647,-22.234,64.714,-22.248,64.727,-22.307,64.733,-22.467,64.795,-22.72,64.789,-23.347,64.824,-23.476,64.809,-23.69,64.757,-23.819,64.739,-23.879,64.751,-23.933,64.779,-23.982,64.816,-24.026,64.863,-24.007,64.896,-23.924,64.915,-23.864,64.924,-23.693,64.913,-23.485,64.946,-23.353,64.953,-23.315,64.958,-23.237,64.993,-23.198,65.002,-23.138,64.99,-23.109,64.966,-22.9,65.003,-22.828,65.022,-22.82,65.033,-22.788,65.046,-22.684,65.026,-22.6,65.026,-22.494,65.04,-22.308,65.046,-21.892,65.049,-21.83,65.079,-21.8,65.106,-21.764,65.174,-21.78,65.188,-22.04,65.125,-22.099,65.126,-22.4,65.159,-22.509,65.197,-22.473,65.227,-22.314,65.292,-22.149,65.344,-21.907,65.4,-21.85,65.422,-21.844,65.447,-22.006,65.493,-22.311,65.481,-22.39,65.535,-22.644,65.568,-22.813,65.547,-22.902,65.58,-23.122,65.535,-23.605,65.469,-23.796,65.423,-23.9,65.408,-24.019,65.445,-24.224,65.487,-24.455,65.5,-24.476,65.525,-24.341,65.601,-24.249,65.615,-24.156,65.608,-23.979,65.555,-23.857,65.538,-24.01,65.616,-24.006,65.646,-24.018,65.691,-24.065,65.71,-24.112,65.76,-24.093,65.776,-24.032,65.782,-23.909,65.766,-23.616,65.68,-23.472,65.695,-23.393,65.727,-23.285,65.75,-23.316,65.762,-23.569,65.764,-23.705,65.781,-23.773,65.806,-23.833,65.849,-23.812,65.869,-23.741,65.885,-23.525,65.88,-23.667,65.954,-23.767,65.997,-23.777,66.018,-23.771,66.043,-23.757,66.061,-23.737,66.069,-23.489,66.026,-23.434,66.024,-23.485,66.052,-23.594,66.093,-23.599,66.109,-23.553,66.122,-23.53,66.145,-23.528,66.164,-23.453,66.181,-23.377,66.182,-23.3,66.167,-23.063,66.086,-23.029,66.064,-23.017,66.034,-23.029,65.997,-23.019,65.982,-22.926,65.995,-22.852,65.979,-22.815,65.983,-22.723,66.039,-22.66,66.026,-22.622,66,-22.61,65.976,-22.604,65.944,-22.62,65.877,-22.616,65.867,-22.552,65.905,-22.442,65.908,-22.428,65.927,-22.424,65.998,-22.433,66.058,-22.445,66.07,-22.806,66.153,-22.869,66.172,-22.948,66.213,-22.932,66.233,-22.862,66.251,-22.756,66.259,-22.509,66.258,-22.484,66.266,-22.532,66.288,-22.646,66.302,-22.673,66.314,-22.686,66.338,-22.821,66.325,-22.972,66.324,-23.117,66.339,-23.12,66.357,-23.063,66.384,-22.944,66.429,-22.889,66.441,-22.724,66.433,-22.559,66.445,-22.426,66.43,-22.32,66.385,-22.17,66.307,-21.967,66.257,-21.948,66.241,-21.84,66.2,-21.625,66.09,-21.407,66.026,-21.397,66.009,-21.433,65.99,-21.517,65.968,-21.497,65.955,-21.388,65.939,-21.309,65.895,-21.303,65.876,-21.375,65.742,-21.413,65.713,-21.457,65.698,-21.658,65.724,-21.61,65.681,-21.466,65.635,-21.434,65.61,-21.455,65.585,-21.439,65.579,-21.387,65.592,-21.365,65.578,-21.374,65.536,-21.396,65.502,-21.432,65.474,-21.422,65.462,-21.365,65.466,-21.313,65.459,-21.23,65.421,-21.163,65.304,-21.13,65.267,-21.106,65.3,-21.076,65.385,-21.047,65.428,-21.021,65.43,-20.998,65.445,-20.979,65.471,-20.94,65.565,-20.804,65.636,-20.74,65.658,-20.679,65.663,-20.649,65.654,-20.548,65.579,-20.487,65.567,-20.455,65.571,-20.412,65.622,-20.357,65.719,-20.344,65.828,-20.374,65.948,-20.357,66.033,-20.292,66.084,-20.208,66.1,-20.103,66.08,-20.026,66.049,-19.875,65.93,-19.753,65.868,-19.648,65.801,-19.594,65.779,-19.49,65.768,-19.462,65.772,-19.443,65.788,-19.434,65.814,-19.456,65.985,-19.427,66.038,-19.383,66.076,-19.195,66.098,-19.093,66.122,-18.994,66.16,-18.911,66.181,-18.846,66.184,-18.778,66.169,-18.706,66.136,-18.595,66.071,-18.455,65.965,-18.277,65.885,-18.184,65.758,-18.164,65.737,-18.142,65.734,-18.118,65.751,-18.103,65.774,-18.099,65.83,-18.149,65.905,-18.315,66.093,-18.318,66.129,-18.297,66.157,-18.18,66.161,-17.907,66.143,-17.82,66.114,-17.634,65.999,-17.582,65.971,-17.55,65.964,-17.539,65.978,-17.467,66,-17.417,66.026,-17.334,66.089,-17.153,66.203,-17.115,66.206,-17.062,66.197,-16.97,66.167,-16.925,66.143,-16.838,66.125,-16.748,66.132,-16.625,66.172,-16.485,66.196,-16.437,66.253,-16.428,66.278,-16.541,66.447,-16.493,66.481,-16.249,66.523,-16.036,66.526,-15.985,66.515,-15.851,66.433,-15.76,66.392,-15.714,66.359,-15.703,66.286,-15.647,66.259,-15.543,66.229,-15.428,66.225,-15.241,66.259,-15.162,66.282,-14.97,66.36,-14.856,66.381,-14.681,66.376,-14.596,66.382,-14.594,66.374,-14.702,66.342,-14.788,66.331]},{"b":[-65.024,18.321,-64.845,18.385],"l":[-64.845,18.33,-64.889,18.374,-64.942,18.385,-65.024,18.368,-64.92,18.321,-64.845,18.33]},{"b":[178.475,51.899,178.607,51.995],"l":[178.571,51.978,178.509,51.995,178.475,51.968,178.478,51.943,178.512,51.899,178.575,51.916,178.607,51.953,178.571,51.978]},{"b":[14.18,36.012,14.321,36.076],"l":[14.18,36.06,14.194,36.042,14.254,36.012,14.313,36.028,14.321,36.036,14.304,36.062,14.263,36.076,14.18,36.06]},{"b":[13.854,40.697,13.962,40.762],"l":[13.871,40.762,13.854,40.724,13.868,40.709,13.894,40.697,13.938,40.706,13.961,40.718,13.962,40.739,13.871,40.762]},{"b":[11.936,36.746,12.051,36.843],"l":[11.948,36.843,11.936,36.829,11.941,36.78,12.003,36.746,12.051,36.757,12.048,36.776,12.024,36.821,11.948,36.843]},{"b":[6.159,53.477,6.333,53.515],"l":[6.193,53.477,6.333,53.511,6.291,53.515,6.168,53.494,6.159,53.484,6.193,53.477]},{"b":[5.654,53.443,5.929,53.475],"l":[5.708,53.473,5.654,53.467,5.665,53.455,5.732,53.443,5.929,53.459,5.928,53.465,5.876,53.475,5.708,53.473]},{"b":[4.908,53.235,5.109,53.31],"l":[5.027,53.31,4.908,53.246,4.924,53.235,5.109,53.308,5.027,53.31]},{"b":[5.19,53.378,5.583,53.444],"l":[5.415,53.431,5.19,53.392,5.233,53.378,5.326,53.386,5.583,53.438,5.557,53.444,5.415,53.431]},{"b":[-179.079,-17.992,-178.988,-17.92],"l":[-179.039,-17.988,-179.018,-17.992,-178.988,-17.977,-178.999,-17.947,-179.048,-17.92,-179.079,-17.944,-179.064,-17.972,-179.039,-17.988]},{"b":[-178.848,-18.252,-178.762,-18.186],"l":[-178.848,-18.202,-178.827,-18.222,-178.774,-18.252,-178.762,-18.234,-178.763,-18.191,-178.791,-18.186,-178.848,-18.202]},{"b":[-178.357,-17.963,-178.251,-17.876],"l":[-178.325,-17.876,-178.357,-17.921,-178.307,-17.963,-178.251,-17.953,-178.255,-17.93,-178.28,-17.886,-178.325,-17.876]},{"b":[-151.512,-16.665,-151.438,-16.574],"l":[-151.506,-16.574,-151.512,-16.619,-151.504,-16.647,-151.485,-16.665,-151.467,-16.658,-151.438,-16.623,-151.457,-16.604,-151.506,-16.574]},{"b":[-149.912,-17.571,-149.782,-17.47],"l":[-149.814,-17.545,-149.782,-17.488,-149.809,-17.474,-149.902,-17.47,-149.912,-17.501,-149.905,-17.528,-149.887,-17.553,-149.845,-17.571,-149.814,-17.545]},{"b":[-120.442,34.022,-120.307,34.073],"l":[-120.353,34.061,-120.368,34.073,-120.413,34.056,-120.442,34.033,-120.36,34.022,-120.307,34.025,-120.353,34.061]},{"b":[-119.575,33.215,-119.438,33.282],"l":[-119.525,33.282,-119.575,33.278,-119.544,33.225,-119.483,33.215,-119.438,33.217,-119.442,33.232,-119.479,33.275,-119.525,33.282]},{"b":[-67.85,-55.892,-67.51,-55.816],"l":[-67.762,-55.816,-67.834,-55.828,-67.85,-55.843,-67.846,-55.857,-67.832,-55.865,-67.7,-55.873,-67.611,-55.892,-67.575,-55.89,-67.545,-55.877,-67.51,-55.844,-67.517,-55.833,-67.545,-55.826,-67.762,-55.816]},{"b":[-70.066,12.423,-69.896,12.614],"l":[-69.973,12.568,-70.035,12.614,-70.051,12.597,-70.066,12.547,-70.004,12.5,-69.942,12.439,-69.896,12.423,-69.899,12.452,-69.912,12.48,-69.973,12.568]},{"b":[-65.415,10.884,-65.213,10.974],"l":[-65.383,10.974,-65.415,10.938,-65.365,10.906,-65.266,10.884,-65.213,10.906,-65.227,10.93,-65.302,10.974,-65.383,10.974]},{"b":[-73.285,18.707,-72.805,18.967],"l":[-73.171,18.967,-73.276,18.954,-73.285,18.897,-73.078,18.791,-72.822,18.707,-72.805,18.778,-72.919,18.861,-73.069,18.932,-73.171,18.967]},{"b":[-25.198,36.942,-25.027,37.024],"l":[-25.164,37.019,-25.198,36.997,-25.16,36.943,-25.088,36.949,-25.032,36.942,-25.027,36.96,-25.044,37,-25.083,37.024,-25.164,37.019]},{"b":[16.651,42.896,17.188,42.997],"l":[16.978,42.928,17.089,42.915,17.188,42.917,17.17,42.933,17.094,42.964,16.971,42.981,16.836,42.969,16.651,42.997,16.666,42.96,16.696,42.934,16.739,42.913,16.851,42.896,16.978,42.928]},{"b":[16.376,43.115,17.194,43.229],"l":[17.124,43.115,17.194,43.126,17.061,43.144,16.697,43.175,16.656,43.214,16.521,43.229,16.376,43.214,16.406,43.197,16.55,43.144,16.679,43.123,17.124,43.115]},{"b":[16.423,43.268,16.891,43.387],"l":[16.627,43.268,16.49,43.286,16.423,43.317,16.428,43.343,16.449,43.387,16.602,43.382,16.834,43.351,16.891,43.315,16.874,43.298,16.785,43.271,16.627,43.268]},{"b":[-2.646,49.429,-2.512,49.507],"l":[-2.646,49.468,-2.639,49.451,-2.547,49.429,-2.512,49.495,-2.521,49.506,-2.542,49.507,-2.646,49.468]},{"b":[-2.236,49.17,-2.01,49.266],"l":[-2.221,49.266,-2.236,49.176,-2.166,49.187,-2.091,49.187,-2.054,49.17,-2.01,49.181,-2.019,49.231,-2.082,49.255,-2.221,49.266]},{"b":[145.646,18.725,145.708,18.807],"l":[145.678,18.725,145.708,18.763,145.707,18.79,145.69,18.802,145.646,18.807,145.653,18.753,145.678,18.725]},{"b":[145.631,16.332,145.72,16.38],"l":[145.631,16.378,145.636,16.352,145.658,16.336,145.69,16.332,145.712,16.339,145.72,16.36,145.696,16.38,145.631,16.378]},{"b":[145.152,14.111,145.265,14.189],"l":[145.152,14.164,145.157,14.137,145.18,14.121,145.215,14.111,145.265,14.158,145.265,14.18,145.232,14.189,145.152,14.164]},{"b":[-60.117,43.904,-59.727,44.003],"l":[-59.866,43.947,-59.936,43.94,-60.117,43.953,-60.114,43.939,-60.038,43.907,-59.922,43.904,-59.788,43.94,-59.727,44.003,-59.866,43.947]},{"b":[-97.036,27.899,-96.84,28.133],"l":[-96.84,28.089,-96.857,28.133,-96.899,28.117,-96.979,28.014,-96.988,27.981,-97.036,27.899,-97.014,27.902,-96.921,28.016,-96.84,28.089]},{"b":[-88.944,29.66,-88.873,29.753],"l":[-88.941,29.68,-88.944,29.66,-88.889,29.713,-88.873,29.753,-88.901,29.733,-88.941,29.68]},{"b":[-88.723,30.205,-88.558,30.264],"l":[-88.723,30.264,-88.713,30.245,-88.659,30.226,-88.571,30.205,-88.558,30.216,-88.574,30.229,-88.723,30.264]},{"b":[-88.316,30.231,-88.071,30.274],"l":[-88.109,30.274,-88.264,30.255,-88.316,30.24,-88.29,30.233,-88.159,30.231,-88.071,30.252,-88.109,30.274]},{"b":[-63.125,18.019,-63.009,18.115],"l":[-63.115,18.091,-63.125,18.064,-63.09,18.041,-63.023,18.019,-63.012,18.045,-63.009,18.104,-63.025,18.113,-63.063,18.115,-63.115,18.091]},{"b":[-65.572,18.1,-65.295,18.165],"l":[-65.572,18.137,-65.555,18.108,-65.504,18.1,-65.426,18.106,-65.295,18.133,-65.303,18.144,-65.366,18.161,-65.477,18.165,-65.572,18.137]},{"b":[-62.625,17.101,-62.532,17.201],"l":[-62.625,17.13,-62.582,17.101,-62.532,17.122,-62.534,17.17,-62.575,17.201,-62.615,17.199,-62.625,17.13]},{"b":[-64.444,18.457,-64.325,18.517],"l":[-64.426,18.513,-64.444,18.473,-64.438,18.459,-64.421,18.457,-64.395,18.465,-64.325,18.517,-64.426,18.513]},{"b":[-64.788,18.328,-64.66,18.372],"l":[-64.788,18.341,-64.771,18.332,-64.726,18.328,-64.66,18.354,-64.752,18.372,-64.788,18.341]},{"b":[-64.695,18.399,-64.545,18.446],"l":[-64.651,18.443,-64.695,18.412,-64.672,18.399,-64.594,18.403,-64.545,18.438,-64.569,18.446,-64.651,18.443]},{"b":[-64.889,17.702,-64.58,17.794],"l":[-64.889,17.702,-64.686,17.706,-64.58,17.75,-64.682,17.75,-64.766,17.794,-64.885,17.772,-64.889,17.702]},{"b":[-77.562,24.993,-77.269,25.083],"l":[-77.451,25.081,-77.527,25.058,-77.562,25.03,-77.541,25.014,-77.46,24.993,-77.348,25.014,-77.269,25.044,-77.276,25.056,-77.329,25.083,-77.451,25.081]},{"b":[-81.137,24.693,-80.93,24.759],"l":[-81.085,24.734,-81.137,24.71,-81.09,24.693,-81.044,24.717,-80.989,24.728,-80.93,24.759,-81.085,24.734]},{"b":[114.134,22.195,114.247,22.297],"l":[114.134,22.292,114.139,22.268,114.207,22.195,114.232,22.211,114.244,22.234,114.247,22.264,114.187,22.297,114.134,22.292]},{"b":[-76.674,34.653,-76.546,34.7],"l":[-76.622,34.695,-76.546,34.655,-76.569,34.653,-76.608,34.664,-76.662,34.685,-76.674,34.7,-76.622,34.695]},{"b":[-175.362,-21.264,-175.078,-21.068],"l":[-175.078,-21.129,-175.084,-21.161,-175.157,-21.264,-175.202,-21.223,-175.335,-21.158,-175.362,-21.107,-175.318,-21.068,-175.323,-21.099,-175.3,-21.113,-175.225,-21.119,-175.158,-21.146,-175.2,-21.156,-175.162,-21.169,-175.148,-21.169,-175.132,-21.14,-175.078,-21.129]},{"b":[52.554,45.401,52.693,45.528],"l":[52.66,45.518,52.609,45.528,52.554,45.474,52.598,45.428,52.665,45.401,52.682,45.412,52.693,45.461,52.66,45.518]},{"b":[47.918,45.47,47.987,45.647],"l":[47.987,45.554,47.947,45.647,47.918,45.618,47.92,45.562,47.968,45.47,47.983,45.488,47.987,45.554]},{"b":[-58.541,-52.099,-58.433,-51.999],"l":[-58.497,-51.999,-58.541,-52.028,-58.513,-52.071,-58.433,-52.099,-58.439,-52.011,-58.461,-52.002,-58.497,-51.999]},{"b":[42.549,15.281,42.625,15.433],"l":[42.57,15.407,42.549,15.32,42.559,15.281,42.59,15.303,42.61,15.332,42.625,15.368,42.602,15.433,42.57,15.407]},{"b":[39.996,15.986,40.11,16.104],"l":[40.048,16.104,40.039,16.081,39.996,16.043,40.012,16.023,40.11,15.986,40.076,16.082,40.048,16.104]},{"b":[36.504,25.383,36.955,25.689],"l":[36.924,25.426,36.748,25.559,36.589,25.62,36.554,25.645,36.534,25.689,36.504,25.645,36.53,25.602,36.722,25.534,36.764,25.501,36.805,25.451,36.875,25.383,36.902,25.383,36.955,25.415,36.924,25.426]},{"b":[36.544,25.699,36.596,25.856],"l":[36.586,25.699,36.596,25.713,36.596,25.735,36.58,25.795,36.583,25.856,36.546,25.812,36.544,25.734,36.586,25.699]},{"b":[101.265,2.97,101.328,3.067],"l":[101.268,2.97,101.319,2.988,101.328,3.048,101.311,3.067,101.274,3.033,101.265,2.996,101.268,2.97]},{"b":[134.634,-6.559,134.729,-6.456],"l":[134.679,-6.456,134.634,-6.477,134.661,-6.559,134.716,-6.549,134.729,-6.506,134.679,-6.456]},{"b":[123.483,-1.711,123.616,-1.503],"l":[123.549,-1.508,123.529,-1.503,123.487,-1.535,123.483,-1.681,123.529,-1.711,123.598,-1.704,123.616,-1.627,123.582,-1.591,123.561,-1.552,123.549,-1.508]},{"b":[126.646,34.296,126.77,34.396],"l":[126.7,34.396,126.652,34.39,126.646,34.351,126.689,34.305,126.77,34.296,126.753,34.344,126.7,34.396]},{"b":[152.559,76.086,152.886,76.195],"l":[152.799,76.195,152.643,76.175,152.559,76.144,152.786,76.086,152.886,76.122,152.864,76.163,152.835,76.185,152.799,76.195]},{"b":[67.026,69.443,67.345,69.575],"l":[67.216,69.575,67.026,69.483,67.047,69.467,67.098,69.447,67.264,69.443,67.345,69.53,67.329,69.572,67.216,69.575]},{"b":[66.395,70.501,66.569,70.773],"l":[66.458,70.699,66.463,70.769,66.44,70.773,66.418,70.757,66.395,70.727,66.408,70.616,66.449,70.561,66.516,70.515,66.569,70.501,66.561,70.542,66.458,70.699]},{"b":[54.98,80.227,55.48,80.325],"l":[55.24,80.325,55.092,80.296,54.98,80.256,55.048,80.228,55.195,80.227,55.48,80.274,55.435,80.302,55.353,80.318,55.24,80.325]},{"b":[18.35,59.02,18.486,59.108],"l":[18.398,59.089,18.377,59.069,18.35,59.023,18.372,59.02,18.416,59.029,18.486,59.105,18.465,59.108,18.398,59.089]},{"b":[18.545,59.437,18.698,59.548],"l":[18.621,59.548,18.572,59.526,18.555,59.486,18.545,59.478,18.57,59.437,18.595,59.47,18.624,59.492,18.698,59.525,18.698,59.535,18.621,59.548]},{"b":[-122.912,47.185,-122.849,47.275],"l":[-122.853,47.205,-122.863,47.185,-122.877,47.186,-122.908,47.226,-122.912,47.254,-122.885,47.275,-122.849,47.216,-122.853,47.205]},{"b":[105.584,-10.564,105.725,-10.431],"l":[105.646,-10.452,105.596,-10.46,105.584,-10.512,105.644,-10.525,105.697,-10.564,105.725,-10.493,105.705,-10.431,105.67,-10.449,105.646,-10.452]},{"b":[-86.63,16.3,-86.256,16.439],"l":[-86.338,16.439,-86.256,16.428,-86.42,16.378,-86.58,16.3,-86.63,16.302,-86.557,16.362,-86.438,16.414,-86.338,16.439]},{"b":[163.737,-74.833,164.208,-74.608],"l":[163.742,-74.712,163.737,-74.734,163.763,-74.803,163.845,-74.833,163.976,-74.833,164.059,-74.753,164.098,-74.732,164.208,-74.608,164.002,-74.629,163.742,-74.712]},{"b":[164.639,-67.569,164.919,-67.259],"l":[164.684,-67.259,164.675,-67.289,164.696,-67.408,164.639,-67.5,164.692,-67.56,164.746,-67.569,164.834,-67.54,164.86,-67.504,164.919,-67.447,164.907,-67.419,164.85,-67.364,164.825,-67.326,164.684,-67.259]},{"b":[166.013,-78.306,167.643,-77.992],"l":[167.594,-78.022,167.498,-77.992,167.422,-78.006,167.364,-78.046,167.138,-78.13,166.864,-78.196,166.76,-78.198,166.567,-78.148,166.111,-78.09,166.013,-78.102,166.013,-78.131,166.051,-78.213,166.122,-78.275,166.285,-78.306,166.626,-78.284,166.936,-78.222,167.377,-78.249,167.517,-78.216,167.643,-78.141,167.64,-78.112,167.594,-78.022]},{"b":[163.09,-66.882,163.302,-66.688],"l":[163.271,-66.768,163.238,-66.709,163.156,-66.688,163.09,-66.701,163.164,-66.819,163.235,-66.868,163.284,-66.882,163.302,-66.821,163.299,-66.798,163.271,-66.768]},{"b":[166.217,-77.851,169.353,-77.162],"l":[168.451,-77.386,167.461,-77.394,167.084,-77.322,167.085,-77.307,167.107,-77.271,166.987,-77.187,166.716,-77.162,166.506,-77.189,166.413,-77.252,166.469,-77.289,166.607,-77.336,166.626,-77.377,166.458,-77.444,166.378,-77.494,166.217,-77.525,166.237,-77.547,166.533,-77.7,166.65,-77.774,166.729,-77.851,167.025,-77.756,167.279,-77.703,167.392,-77.649,167.918,-77.644,168.323,-77.683,168.519,-77.681,168.755,-77.653,169.117,-77.561,169.353,-77.525,169.276,-77.455,168.451,-77.386]},{"b":[169.479,-73.625,169.96,-73.32],"l":[169.887,-73.459,169.783,-73.324,169.74,-73.32,169.672,-73.346,169.645,-73.379,169.659,-73.418,169.479,-73.539,169.522,-73.562,169.709,-73.625,169.844,-73.605,169.859,-73.568,169.96,-73.514,169.887,-73.459]},{"b":[162.591,-75.696,162.968,-75.557],"l":[162.72,-75.597,162.591,-75.669,162.662,-75.692,162.788,-75.696,162.968,-75.567,162.917,-75.557,162.842,-75.566,162.72,-75.597]},{"b":[100.27,-65.701,101.259,-65.378],"l":[100.409,-65.466,100.324,-65.521,100.27,-65.603,100.293,-65.651,100.351,-65.673,100.512,-65.675,100.547,-65.701,100.981,-65.678,101.238,-65.565,101.259,-65.528,101.221,-65.472,101.079,-65.403,100.883,-65.378,100.607,-65.396,100.545,-65.409,100.409,-65.466]},{"b":[85.314,-66.98,85.938,-66.723],"l":[85.34,-66.723,85.314,-66.776,85.359,-66.854,85.617,-66.951,85.622,-66.965,85.65,-66.98,85.822,-66.953,85.938,-66.894,85.806,-66.775,85.553,-66.729,85.34,-66.723]},{"b":[92.248,-65.822,92.67,-65.702],"l":[92.301,-65.707,92.248,-65.74,92.263,-65.76,92.333,-65.807,92.471,-65.822,92.601,-65.808,92.67,-65.775,92.665,-65.76,92.634,-65.731,92.496,-65.702,92.301,-65.707]},{"b":[86.232,-66.792,86.652,-66.675],"l":[86.383,-66.675,86.278,-66.697,86.232,-66.733,86.337,-66.788,86.427,-66.792,86.542,-66.767,86.652,-66.718,86.557,-66.706,86.521,-66.687,86.383,-66.675]},{"b":[85.069,-66.643,85.329,-66.519],"l":[85.165,-66.522,85.121,-66.519,85.069,-66.584,85.08,-66.604,85.136,-66.637,85.222,-66.643,85.329,-66.612,85.194,-66.556,85.165,-66.522]},{"b":[69.693,-72.047,69.918,-71.894],"l":[69.796,-71.894,69.737,-71.922,69.693,-71.968,69.744,-72.044,69.792,-72.047,69.918,-71.918,69.895,-71.908,69.796,-71.894]},{"b":[68.409,-72.3,68.84,-72.089],"l":[68.667,-72.103,68.566,-72.19,68.436,-72.26,68.409,-72.3,68.462,-72.3,68.67,-72.276,68.817,-72.229,68.84,-72.165,68.729,-72.089,68.667,-72.103]},{"b":[96.307,-66.225,97.019,-66.036],"l":[96.727,-66.061,96.613,-66.036,96.5,-66.046,96.399,-66.08,96.307,-66.186,96.395,-66.225,96.934,-66.201,97.016,-66.164,97.019,-66.139,97.006,-66.097,96.932,-66.058,96.727,-66.061]},{"b":[71.637,-70.633,72.097,-70.264],"l":[72.073,-70.525,72.056,-70.501,72,-70.457,71.88,-70.406,71.851,-70.368,71.838,-70.312,71.797,-70.264,71.705,-70.284,71.647,-70.336,71.637,-70.444,71.659,-70.497,71.726,-70.549,71.841,-70.622,71.929,-70.633,72.002,-70.633,72.078,-70.609,72.097,-70.575,72.073,-70.525]},{"b":[162.297,-66.525,162.611,-66.251],"l":[162.311,-66.251,162.302,-66.265,162.297,-66.304,162.326,-66.347,162.303,-66.4,162.511,-66.52,162.557,-66.525,162.611,-66.477,162.563,-66.433,162.311,-66.251]},{"b":[100.076,-66.229,100.291,-66.112],"l":[100.174,-66.131,100.076,-66.188,100.082,-66.203,100.133,-66.229,100.265,-66.217,100.282,-66.18,100.291,-66.112,100.174,-66.131]},{"b":[98.596,-66.482,98.95,-66.369],"l":[98.655,-66.453,98.752,-66.482,98.846,-66.47,98.95,-66.421,98.749,-66.369,98.596,-66.383,98.605,-66.4,98.655,-66.453]},{"b":[102.76,-65.469,103.397,-65.13],"l":[103.186,-65.331,103.182,-65.308,103.191,-65.237,103.137,-65.191,102.893,-65.13,102.796,-65.136,102.76,-65.168,102.789,-65.236,103.054,-65.285,103.113,-65.312,103.124,-65.338,103.138,-65.435,103.176,-65.455,103.337,-65.469,103.397,-65.445,103.379,-65.426,103.261,-65.377,103.186,-65.331]},{"b":[-148.001,-76.663,-147.579,-76.558],"l":[-147.579,-76.663,-147.588,-76.65,-147.649,-76.611,-147.77,-76.577,-147.9,-76.558,-148.001,-76.577,-147.954,-76.597,-147.73,-76.653,-147.579,-76.663]},{"b":[-146.151,-75.716,-145.238,-75.505],"l":[-145.761,-75.514,-145.896,-75.505,-146.076,-75.533,-146.151,-75.574,-146.043,-75.612,-145.541,-75.693,-145.348,-75.716,-145.238,-75.711,-145.252,-75.683,-145.315,-75.641,-145.417,-75.588,-145.761,-75.514]},{"b":[-147.116,-77.006,-146.164,-76.837],"l":[-146.867,-76.837,-147.087,-76.837,-147.116,-76.866,-147.101,-76.887,-147.044,-76.93,-147.079,-76.993,-146.981,-77.006,-146.607,-76.961,-146.164,-76.949,-146.244,-76.883,-146.867,-76.837]},{"b":[-132.857,-74.498,-132.362,-74.387],"l":[-132.832,-74.422,-132.857,-74.462,-132.546,-74.498,-132.391,-74.442,-132.362,-74.41,-132.552,-74.387,-132.832,-74.422]},{"b":[-149.303,-77.019,-148.44,-76.9],"l":[-149.238,-76.9,-149.303,-76.916,-149.245,-76.993,-149.015,-77.019,-148.596,-77.007,-148.474,-76.998,-148.44,-76.977,-148.509,-76.955,-148.704,-76.936,-149.238,-76.9]},{"b":[-149.469,-76.845,-148.321,-76.717],"l":[-148.371,-76.795,-148.321,-76.772,-148.384,-76.744,-148.663,-76.721,-148.928,-76.73,-149.333,-76.717,-149.469,-76.757,-149.238,-76.818,-148.984,-76.845,-148.815,-76.841,-148.67,-76.802,-148.371,-76.795]},{"b":[-132.163,-74.605,-130.957,-74.324],"l":[-131.234,-74.414,-131.56,-74.367,-131.594,-74.33,-131.763,-74.324,-131.938,-74.349,-132.163,-74.426,-132.049,-74.464,-132.025,-74.489,-131.952,-74.514,-131.841,-74.543,-131.598,-74.554,-131.179,-74.605,-131.067,-74.584,-130.967,-74.515,-130.957,-74.456,-130.981,-74.414,-131.234,-74.414]},{"b":[-119.905,-74.122,-118.877,-73.775],"l":[-119.549,-74.11,-119.449,-74.076,-119.059,-73.998,-118.99,-73.967,-118.877,-73.878,-118.91,-73.834,-118.959,-73.809,-119.216,-73.778,-119.516,-73.775,-119.669,-73.809,-119.803,-73.815,-119.662,-73.989,-119.694,-74.006,-119.797,-74.029,-119.905,-74.082,-119.887,-74.097,-119.821,-74.12,-119.758,-74.122,-119.549,-74.11]},{"b":[-117.398,-74.193,-116.155,-73.866],"l":[-116.381,-73.866,-117.376,-74.083,-117.398,-74.122,-117.363,-74.161,-117.23,-74.193,-116.739,-74.165,-116.571,-74.126,-116.514,-74.096,-116.534,-74.083,-116.609,-74.069,-116.585,-74.056,-116.451,-74.018,-116.155,-73.91,-116.203,-73.896,-116.381,-73.866]},{"b":[-128.133,-74.641,-127.145,-74.312],"l":[-127.486,-74.405,-127.853,-74.332,-128.043,-74.312,-128.133,-74.327,-128.096,-74.466,-128.07,-74.478,-128,-74.489,-127.915,-74.543,-127.817,-74.575,-127.518,-74.641,-127.366,-74.623,-127.232,-74.578,-127.145,-74.48,-127.23,-74.425,-127.486,-74.405]},{"b":[-149.662,-77.387,-148.929,-77.275],"l":[-149.218,-77.336,-149.249,-77.315,-149.375,-77.28,-149.519,-77.275,-149.662,-77.301,-149.439,-77.371,-148.929,-77.387,-149.218,-77.336]},{"b":[-163.796,-83.142,-161.635,-82.843],"l":[-162.798,-82.865,-163.796,-82.843,-163.735,-82.857,-163.704,-82.879,-163.634,-82.902,-163.602,-82.927,-163.602,-82.969,-163.552,-82.988,-163.348,-83.022,-163.242,-83.06,-163.047,-83.097,-162.305,-83.142,-161.994,-83.119,-161.828,-83.043,-161.635,-83.027,-162.34,-82.923,-162.411,-82.899,-162.798,-82.865]},{"b":[-150.874,-76.789,-150.085,-76.691],"l":[-150.085,-76.735,-150.104,-76.719,-150.177,-76.691,-150.838,-76.714,-150.874,-76.737,-150.83,-76.762,-150.655,-76.789,-150.233,-76.776,-150.085,-76.735]},{"b":[-147.421,-76.261,-146.69,-76.063],"l":[-147.151,-76.197,-146.894,-76.261,-146.69,-76.246,-146.949,-76.098,-147.361,-76.063,-147.418,-76.073,-147.421,-76.09,-147.408,-76.105,-147.345,-76.147,-147.151,-76.197]},{"b":[-147.355,-76.714,-146.79,-76.532],"l":[-146.947,-76.555,-147.135,-76.532,-147.279,-76.553,-147.355,-76.619,-147.221,-76.671,-146.908,-76.714,-146.79,-76.633,-146.878,-76.563,-146.947,-76.555]},{"b":[-163.951,-81.598,-160.467,-81.281],"l":[-160.467,-81.589,-160.485,-81.567,-160.617,-81.522,-160.938,-81.463,-161.559,-81.397,-162.456,-81.313,-163.201,-81.281,-163.869,-81.324,-163.93,-81.352,-163.951,-81.391,-163.939,-81.404,-163.89,-81.424,-163.766,-81.445,-163.253,-81.482,-160.571,-81.598,-160.467,-81.589]},{"b":[-151.512,-77.374,-150.356,-77.22],"l":[-151.022,-77.22,-151.218,-77.226,-151.512,-77.273,-151.344,-77.296,-150.475,-77.374,-150.397,-77.369,-150.356,-77.349,-150.499,-77.335,-151.022,-77.22]},{"b":[-155.751,-80.036,-153.93,-79.766],"l":[-153.93,-80.033,-154.025,-79.988,-154.535,-79.936,-155.162,-79.851,-155.674,-79.766,-155.751,-79.83,-155.525,-79.846,-155.045,-79.9,-154.942,-79.966,-154.529,-80,-154.349,-80.026,-154.114,-80.036,-153.93,-80.033]},{"b":[-150.789,-77.137,-149.231,-76.875],"l":[-149.293,-77.137,-149.231,-77.121,-149.288,-77.093,-149.416,-77.079,-149.442,-77.049,-149.506,-77.002,-149.743,-76.927,-149.79,-76.889,-149.871,-76.875,-150.393,-76.899,-150.476,-76.926,-150.68,-76.948,-150.789,-76.982,-150.736,-77.004,-150.462,-77.076,-149.856,-77.099,-149.817,-77.114,-149.729,-77.129,-149.293,-77.137]},{"b":[-158.989,-82.112,-157.835,-81.779],"l":[-158.261,-81.947,-158.347,-81.9,-158.914,-81.78,-158.989,-81.779,-158.926,-81.819,-158.773,-81.875,-158.545,-81.949,-158.154,-82.058,-158.076,-82.112,-157.987,-82.105,-157.835,-82.031,-158.261,-81.947]},{"b":[-6.438,-70.612,-5.894,-70.405],"l":[-6.068,-70.405,-6.244,-70.446,-6.438,-70.453,-6.266,-70.55,-6.181,-70.586,-6.156,-70.612,-5.894,-70.552,-5.95,-70.432,-5.972,-70.421,-6.068,-70.405]},{"b":[2.585,-70.624,3.26,-70.382],"l":[3.072,-70.382,2.631,-70.5,2.585,-70.535,2.623,-70.593,2.698,-70.624,3.037,-70.597,3.171,-70.554,3.221,-70.519,3.26,-70.449,3.231,-70.403,3.193,-70.393,3.072,-70.382]},{"b":[-3.404,-71.237,-2.955,-71.052],"l":[-3.399,-71.062,-3.392,-71.081,-3.404,-71.12,-3.386,-71.143,-3.309,-71.201,-3.201,-71.23,-3.061,-71.237,-2.955,-71.214,-3.191,-71.095,-3.213,-71.076,-3.263,-71.052,-3.399,-71.062]},{"b":[0.95,-70.381,1.461,-70.023],"l":[1.315,-70.023,1.027,-70.05,0.95,-70.094,0.953,-70.169,0.99,-70.224,1.105,-70.304,1.156,-70.378,1.212,-70.381,1.299,-70.255,1.461,-70.136,1.412,-70.041,1.315,-70.023]},{"b":[-3.575,-71.167,-2.092,-70.674],"l":[-2.533,-70.768,-2.75,-70.694,-3.04,-70.674,-3.537,-70.683,-3.575,-70.703,-3.489,-70.736,-3.007,-70.851,-2.975,-70.883,-2.963,-70.94,-2.801,-70.982,-2.805,-71.015,-2.822,-71.057,-2.825,-71.113,-2.783,-71.167,-2.607,-71.141,-2.369,-71.044,-2.293,-70.998,-2.213,-70.902,-2.119,-70.855,-2.092,-70.821,-2.256,-70.796,-2.423,-70.8,-2.533,-70.768]},{"b":[4.07,-70.503,4.618,-70.241],"l":[4.13,-70.417,4.18,-70.451,4.365,-70.503,4.526,-70.479,4.59,-70.433,4.618,-70.369,4.586,-70.294,4.495,-70.251,4.256,-70.241,4.112,-70.267,4.07,-70.29,4.076,-70.325,4.13,-70.417]},{"b":[48.294,-66.807,48.786,-66.701],"l":[48.638,-66.701,48.358,-66.704,48.301,-66.724,48.294,-66.75,48.296,-66.774,48.304,-66.798,48.378,-66.807,48.546,-66.784,48.775,-66.778,48.786,-66.768,48.782,-66.731,48.751,-66.72,48.638,-66.701]},{"b":[15.563,-70.072,16.625,-69.705],"l":[15.91,-69.728,15.699,-69.773,15.597,-69.828,15.563,-69.863,15.571,-69.885,15.614,-69.939,15.663,-69.955,15.845,-69.982,16.159,-70.072,16.223,-70.008,16.315,-69.844,16.625,-69.75,16.573,-69.723,16.247,-69.705,15.91,-69.728]},{"b":[25.954,-70.448,26.875,-70.061],"l":[26.686,-70.114,26.605,-70.078,26.426,-70.061,26.301,-70.072,25.983,-70.2,25.954,-70.261,25.964,-70.295,26.005,-70.373,26.358,-70.434,26.47,-70.448,26.609,-70.412,26.793,-70.419,26.857,-70.381,26.875,-70.33,26.737,-70.186,26.686,-70.114]},{"b":[-32,-80.011,-29.614,-79.634],"l":[-30.985,-79.818,-31.119,-79.798,-31.605,-79.645,-31.68,-79.634,-32,-79.732,-31.824,-79.85,-31.594,-79.888,-30.844,-79.938,-30.422,-80.011,-30.029,-79.936,-29.8,-79.926,-29.721,-79.93,-29.614,-79.91,-29.871,-79.823,-30.66,-79.733,-30.78,-79.647,-30.861,-79.726,-30.841,-79.771,-30.985,-79.818]},{"b":[-36.601,-79.357,-33.934,-79.09],"l":[-33.995,-79.279,-34.392,-79.223,-35.535,-79.09,-35.597,-79.092,-35.79,-79.149,-36.048,-79.181,-36.238,-79.196,-36.566,-79.209,-36.601,-79.283,-36.481,-79.294,-34.05,-79.357,-33.934,-79.32,-33.947,-79.305,-33.995,-79.279]},{"b":[-3.497,-70.535,-2.683,-70.28],"l":[-2.738,-70.507,-2.683,-70.462,-2.684,-70.376,-2.714,-70.32,-2.805,-70.288,-2.95,-70.28,-3.173,-70.307,-3.287,-70.344,-3.497,-70.488,-3.49,-70.508,-3.442,-70.535,-3.28,-70.534,-2.738,-70.507]},{"b":[-22.035,-74.493,-20.411,-73.619],"l":[-20.6,-74.197,-20.641,-74.151,-20.654,-74.105,-20.607,-73.887,-20.521,-73.798,-20.521,-73.712,-20.58,-73.619,-20.69,-73.625,-20.867,-73.677,-20.979,-73.79,-21.025,-73.88,-21.126,-73.94,-21.288,-73.989,-21.93,-74.057,-22.035,-74.107,-21.61,-74.092,-21.167,-74.133,-21.051,-74.176,-20.977,-74.225,-20.846,-74.438,-20.818,-74.455,-20.737,-74.481,-20.489,-74.493,-20.417,-74.443,-20.411,-74.408,-20.423,-74.317,-20.6,-74.197]},{"b":[-16.517,-72.703,-16.104,-72.459],"l":[-16.303,-72.478,-16.356,-72.459,-16.455,-72.474,-16.517,-72.531,-16.51,-72.582,-16.453,-72.652,-16.318,-72.702,-16.175,-72.703,-16.104,-72.679,-16.173,-72.6,-16.303,-72.478]},{"b":[-12.963,-72.196,-12.509,-72.001],"l":[-12.789,-72.007,-12.875,-72.001,-12.915,-72.015,-12.963,-72.064,-12.944,-72.099,-12.888,-72.137,-12.72,-72.188,-12.588,-72.196,-12.509,-72.173,-12.535,-72.14,-12.637,-72.071,-12.789,-72.007]},{"b":[-32.583,-79.683,-31.933,-79.53],"l":[-31.957,-79.604,-31.933,-79.568,-32.15,-79.53,-32.377,-79.535,-32.501,-79.592,-32.583,-79.658,-32.515,-79.683,-32.343,-79.674,-32.001,-79.607,-31.957,-79.604]},{"b":[-45.956,-60.733,-45.173,-60.521],"l":[-45.78,-60.586,-45.709,-60.645,-45.398,-60.65,-45.174,-60.733,-45.173,-60.699,-45.186,-60.672,-45.211,-60.648,-45.228,-60.64,-45.357,-60.624,-45.386,-60.583,-45.5,-60.546,-45.718,-60.521,-45.834,-60.543,-45.935,-60.527,-45.956,-60.568,-45.955,-60.597,-45.937,-60.62,-45.78,-60.586]},{"b":[-54.192,-61.308,-54.024,-61.135],"l":[-54.122,-61.202,-54.192,-61.247,-54.184,-61.27,-54.115,-61.308,-54.071,-61.299,-54.041,-61.255,-54.024,-61.135,-54.05,-61.142,-54.122,-61.202]},{"b":[-68.549,-79.501,-67.069,-79.214],"l":[-68.422,-79.333,-68.549,-79.437,-68.408,-79.464,-68.161,-79.479,-67.434,-79.501,-67.262,-79.453,-67.305,-79.394,-67.24,-79.328,-67.173,-79.312,-67.069,-79.268,-67.475,-79.223,-67.714,-79.214,-68.033,-79.227,-68.233,-79.285,-68.324,-79.298,-68.422,-79.333]},{"b":[-55.44,-61.249,-54.671,-61.073],"l":[-55.387,-61.073,-55.44,-61.106,-55.369,-61.146,-55.347,-61.212,-55.297,-61.249,-55.165,-61.22,-55.058,-61.169,-54.71,-61.14,-54.671,-61.117,-55.387,-61.073]},{"b":[-56.505,-63.437,-55.075,-63.128],"l":[-55.466,-63.2,-55.528,-63.174,-55.529,-63.157,-55.59,-63.128,-56.042,-63.157,-56.385,-63.234,-56.466,-63.283,-56.461,-63.302,-56.475,-63.318,-56.505,-63.334,-56.499,-63.358,-56.463,-63.418,-56.379,-63.437,-56.083,-63.383,-56.009,-63.342,-55.83,-63.298,-55.75,-63.297,-55.594,-63.336,-55.157,-63.353,-55.075,-63.324,-55.106,-63.249,-55.156,-63.205,-55.216,-63.199,-55.466,-63.2]},{"b":[-56.235,-63.58,-55.719,-63.407],"l":[-55.762,-63.422,-55.858,-63.407,-56.21,-63.437,-56.235,-63.469,-56.178,-63.513,-55.957,-63.58,-55.873,-63.536,-55.719,-63.492,-55.762,-63.422]},{"b":[-57.448,-64.571,-56.895,-64.333],"l":[-57.446,-64.46,-57.448,-64.488,-57.433,-64.54,-57.326,-64.571,-57.24,-64.567,-56.991,-64.468,-56.945,-64.427,-56.952,-64.382,-56.895,-64.333,-57.023,-64.352,-57.315,-64.435,-57.366,-64.439,-57.446,-64.46]},{"b":[-59.202,-62.348,-58.838,-62.239],"l":[-59.064,-62.239,-59.202,-62.283,-59.175,-62.302,-59.059,-62.348,-58.838,-62.303,-58.879,-62.268,-58.962,-62.264,-58.991,-62.249,-59.064,-62.239]},{"b":[-59.004,-62.248,-57.637,-61.912],"l":[-58.562,-62.244,-58.507,-62.226,-58.467,-62.137,-58.341,-62.119,-58.183,-62.17,-58.133,-62.146,-58.172,-62.118,-58.148,-62.063,-57.963,-62.078,-57.807,-62.012,-57.64,-62.02,-57.637,-61.998,-57.677,-61.942,-57.738,-61.921,-57.849,-61.94,-57.978,-61.912,-58.265,-61.953,-58.399,-61.938,-58.684,-62.008,-58.71,-62.045,-58.955,-62.164,-59.004,-62.21,-58.819,-62.171,-58.755,-62.206,-58.746,-62.218,-58.644,-62.225,-58.594,-62.248,-58.562,-62.244]},{"b":[-62.044,-64.08,-61.798,-63.967],"l":[-62.021,-64.027,-62.044,-64.08,-61.952,-64.077,-61.911,-64.054,-61.886,-64.027,-61.798,-63.967,-61.936,-63.99,-62.021,-64.027]},{"b":[-62.568,-69.729,-61.784,-69.146],"l":[-61.978,-69.3,-62.118,-69.215,-62.239,-69.176,-62.442,-69.146,-62.516,-69.155,-62.568,-69.18,-62.496,-69.288,-62.216,-69.495,-62.172,-69.637,-62.085,-69.729,-61.998,-69.722,-61.97,-69.691,-61.908,-69.588,-61.911,-69.533,-61.807,-69.515,-61.784,-69.442,-61.816,-69.376,-61.978,-69.3]},{"b":[-63.558,-64.907,-63.177,-64.73],"l":[-63.177,-64.739,-63.219,-64.73,-63.367,-64.792,-63.459,-64.796,-63.558,-64.906,-63.474,-64.907,-63.316,-64.861,-63.257,-64.791,-63.177,-64.739]},{"b":[-62.782,-64.514,-62.058,-64.012],"l":[-62.094,-64.235,-62.058,-64.138,-62.269,-64.09,-62.268,-64.04,-62.329,-64.013,-62.451,-64.012,-62.545,-64.046,-62.586,-64.076,-62.611,-64.116,-62.59,-64.14,-62.48,-64.211,-62.504,-64.253,-62.643,-64.392,-62.721,-64.445,-62.747,-64.472,-62.782,-64.479,-62.727,-64.496,-62.58,-64.514,-62.508,-64.454,-62.455,-64.472,-62.396,-64.465,-62.326,-64.424,-62.304,-64.401,-62.186,-64.369,-62.174,-64.296,-62.094,-64.235]},{"b":[-62.655,-63.074,-62.317,-62.874],"l":[-62.411,-62.972,-62.344,-62.918,-62.317,-62.874,-62.527,-62.924,-62.639,-63.032,-62.655,-63.074,-62.615,-63.069,-62.411,-62.972]},{"b":[-59.661,-62.451,-59.353,-62.352],"l":[-59.525,-62.451,-59.389,-62.444,-59.353,-62.413,-59.396,-62.367,-59.479,-62.352,-59.661,-62.354,-59.619,-62.395,-59.525,-62.451]},{"b":[-56.614,-63.173,-56.051,-62.982],"l":[-56.354,-63.169,-56.259,-63.173,-56.06,-63.079,-56.051,-63.055,-56.058,-63.019,-56.062,-63.013,-56.14,-63.005,-56.489,-62.982,-56.614,-63.045,-56.601,-63.062,-56.546,-63.098,-56.354,-63.169]},{"b":[-61.152,-62.745,-59.85,-62.475],"l":[-60.796,-62.662,-60.697,-62.621,-60.62,-62.633,-60.378,-62.617,-60.354,-62.679,-60.322,-62.708,-60.221,-62.745,-59.85,-62.615,-60.003,-62.618,-60.14,-62.549,-60.576,-62.573,-60.625,-62.56,-60.732,-62.491,-60.799,-62.475,-60.838,-62.534,-60.975,-62.592,-61.152,-62.589,-61.15,-62.634,-61.063,-62.679,-60.995,-62.679,-60.796,-62.662]},{"b":[-57.683,-63.879,-57.104,-63.791],"l":[-57.344,-63.879,-57.248,-63.868,-57.218,-63.876,-57.104,-63.841,-57.161,-63.816,-57.36,-63.825,-57.374,-63.807,-57.439,-63.791,-57.683,-63.813,-57.616,-63.854,-57.344,-63.879]},{"b":[-61.404,-69.978,-61.108,-69.856],"l":[-61.327,-69.856,-61.386,-69.893,-61.404,-69.933,-61.378,-69.95,-61.309,-69.978,-61.158,-69.976,-61.108,-69.955,-61.152,-69.883,-61.327,-69.856]},{"b":[-61.015,-68.795,-60.656,-68.681],"l":[-60.693,-68.795,-60.656,-68.768,-60.705,-68.722,-60.814,-68.688,-60.947,-68.681,-61.015,-68.71,-60.894,-68.759,-60.82,-68.778,-60.693,-68.795]},{"b":[-60.976,-70.711,-60.449,-70.509],"l":[-60.554,-70.509,-60.884,-70.518,-60.942,-70.533,-60.976,-70.599,-60.958,-70.629,-60.896,-70.69,-60.826,-70.711,-60.741,-70.711,-60.488,-70.647,-60.449,-70.603,-60.452,-70.544,-60.554,-70.509]},{"b":[-60.946,-71.059,-60.516,-70.914],"l":[-60.516,-71,-60.533,-70.962,-60.613,-70.92,-60.783,-70.914,-60.889,-70.934,-60.946,-70.967,-60.906,-71.007,-60.79,-71.041,-60.652,-71.059,-60.552,-71.053,-60.536,-71.041,-60.516,-71]},{"b":[-60.74,-63.018,-60.505,-62.895],"l":[-60.637,-62.895,-60.706,-62.906,-60.74,-62.949,-60.693,-62.996,-60.622,-63.018,-60.564,-63.009,-60.618,-62.987,-60.62,-62.969,-60.555,-62.978,-60.505,-62.967,-60.637,-62.895]},{"b":[-60.972,-63.902,-60.562,-63.669],"l":[-60.562,-63.696,-60.715,-63.669,-60.797,-63.717,-60.81,-63.837,-60.972,-63.849,-60.852,-63.891,-60.778,-63.902,-60.653,-63.867,-60.655,-63.85,-60.689,-63.808,-60.656,-63.759,-60.562,-63.696]},{"b":[-94.113,-72.92,-93.756,-72.82],"l":[-94.004,-72.82,-94.047,-72.823,-94.113,-72.86,-94.078,-72.884,-93.966,-72.92,-93.796,-72.92,-93.756,-72.908,-93.8,-72.882,-94.004,-72.82]},{"b":[-91.67,-73.207,-90.751,-72.547],"l":[-90.78,-72.732,-90.763,-72.681,-90.807,-72.611,-90.947,-72.556,-91.304,-72.547,-91.612,-72.594,-91.67,-72.624,-91.551,-72.754,-91.382,-72.868,-91.357,-72.909,-91.45,-72.968,-91.511,-73.196,-91.344,-73.207,-91.161,-73.182,-90.998,-73.137,-90.893,-73.084,-90.776,-72.993,-90.751,-72.917,-90.776,-72.854,-90.895,-72.824,-90.78,-72.732]},{"b":[-105.132,-73.212,-104.538,-72.941],"l":[-104.972,-72.941,-105.085,-72.966,-105.132,-72.992,-105.123,-73.026,-105.053,-73.126,-104.881,-73.201,-104.66,-73.212,-104.538,-73.166,-104.972,-72.941]},{"b":[-95.273,-72.67,-94.426,-72.468],"l":[-94.566,-72.468,-94.753,-72.517,-95.216,-72.599,-95.273,-72.647,-95.219,-72.67,-95.027,-72.665,-94.426,-72.613,-94.434,-72.589,-94.514,-72.492,-94.538,-72.476,-94.566,-72.468]},{"b":[-71.795,-70.442,-71.34,-70.265],"l":[-71.551,-70.439,-71.438,-70.392,-71.34,-70.317,-71.355,-70.298,-71.432,-70.267,-71.648,-70.295,-71.695,-70.265,-71.795,-70.288,-71.782,-70.319,-71.685,-70.442,-71.551,-70.439]},{"b":[-72.957,-69.74,-71.985,-69.413],"l":[-72.202,-69.74,-71.985,-69.698,-72.331,-69.492,-72.464,-69.452,-72.726,-69.413,-72.857,-69.433,-72.937,-69.469,-72.957,-69.529,-72.777,-69.645,-72.345,-69.707,-72.202,-69.74]},{"b":[-74.147,-73.376,-73.542,-73.1],"l":[-74.049,-73.22,-74.084,-73.249,-74.134,-73.277,-74.147,-73.315,-74.038,-73.366,-73.975,-73.376,-73.878,-73.357,-73.721,-73.296,-73.682,-73.225,-73.542,-73.124,-73.674,-73.1,-73.832,-73.113,-74.049,-73.22]},{"b":[-66.181,-65.881,-65.637,-65.527],"l":[-66.064,-65.881,-65.845,-65.842,-65.836,-65.814,-65.841,-65.738,-65.814,-65.687,-65.784,-65.674,-65.67,-65.653,-65.668,-65.626,-65.637,-65.548,-65.834,-65.527,-65.968,-65.571,-66,-65.633,-66.067,-65.666,-66.05,-65.745,-66.153,-65.774,-66.181,-65.826,-66.175,-65.867,-66.064,-65.881]},{"b":[-66.868,-66.313,-66.575,-66.067],"l":[-66.867,-66.275,-66.868,-66.294,-66.85,-66.305,-66.819,-66.313,-66.595,-66.201,-66.593,-66.179,-66.623,-66.134,-66.575,-66.082,-66.631,-66.067,-66.779,-66.111,-66.792,-66.234,-66.867,-66.275]},{"b":[-67.743,-67.785,-67.149,-67.591],"l":[-67.149,-67.65,-67.175,-67.625,-67.247,-67.599,-67.418,-67.591,-67.557,-67.604,-67.743,-67.661,-67.731,-67.679,-67.69,-67.688,-67.693,-67.763,-67.545,-67.785,-67.349,-67.766,-67.3,-67.737,-67.28,-67.712,-67.149,-67.65]},{"b":[-67.593,-66.902,-67.257,-66.737],"l":[-67.257,-66.841,-67.269,-66.815,-67.332,-66.754,-67.426,-66.737,-67.511,-66.756,-67.5,-66.804,-67.593,-66.876,-67.521,-66.897,-67.409,-66.902,-67.362,-66.895,-67.257,-66.841]},{"b":[-180,-16.963,-179.822,-16.688],"l":[-180,-16.963,-179.975,-16.925,-179.868,-16.85,-179.822,-16.765,-179.861,-16.688,-179.894,-16.7,-180,-16.786]},{"b":[-180,-16.54,-179.901,-16.432],"l":[-180,-16.54,-179.929,-16.503,-179.927,-16.479,-179.901,-16.432,-179.944,-16.441,-180,-16.489]},{"b":[179.897,-17,180,-16.786],"l":[179.999,-16.963,179.926,-17,179.897,-16.964,179.931,-16.876,180,-16.786]},{"b":[-78.965,8.232,-78.839,8.46],"l":[-78.839,8.348,-78.853,8.302,-78.898,8.274,-78.918,8.232,-78.965,8.326,-78.957,8.351,-78.961,8.436,-78.916,8.458,-78.883,8.46,-78.856,8.448,-78.839,8.348]},{"b":[154.611,49.268,154.9,49.647],"l":[154.613,49.381,154.611,49.294,154.715,49.268,154.81,49.312,154.83,49.348,154.802,49.468,154.883,49.566,154.9,49.63,154.825,49.647,154.613,49.381]},{"b":[58.61,81.292,59.375,81.398],"l":[58.61,81.337,58.634,81.36,58.881,81.392,59.075,81.398,59.281,81.366,59.375,81.325,59.313,81.305,59.097,81.292,58.719,81.314,58.61,81.337]},{"b":[53.812,80.421,54.437,80.605],"l":[53.902,80.542,53.859,80.563,53.877,80.605,54.177,80.574,54.205,80.562,54.407,80.54,54.437,80.499,54.415,80.473,54.276,80.421,53.812,80.476,53.85,80.504,53.9,80.515,53.902,80.542]},{"b":[60.058,80.93,61.567,81.104],"l":[60.587,81.088,61.457,81.104,61.567,81.05,61.472,81.011,61.141,80.95,60.827,80.93,60.321,80.956,60.058,80.985,60.078,80.999,60.148,81.017,60.587,81.088]},{"b":[52.213,80.173,53.852,80.413],"l":[52.344,80.213,52.213,80.264,52.27,80.276,52.577,80.297,52.681,80.319,52.716,80.348,52.854,80.402,53.186,80.413,53.329,80.402,53.346,80.366,53.486,80.323,53.852,80.268,53.778,80.228,53.653,80.223,53.521,80.185,52.856,80.173,52.636,80.179,52.607,80.191,52.55,80.202,52.344,80.213]},{"b":[55.724,80.071,57.123,80.366],"l":[55.942,80.163,56.012,80.204,55.99,80.32,56.024,80.341,56.655,80.33,56.707,80.363,56.945,80.366,57.079,80.351,57.123,80.317,57.119,80.194,57.073,80.139,57.08,80.095,56.987,80.071,56.201,80.076,55.812,80.087,55.724,80.105,55.942,80.163]},{"b":[58.919,79.923,59.911,80.119],"l":[58.946,80.042,59.001,80.054,59.545,80.119,59.802,80.083,59.911,79.994,59.689,79.956,59.331,79.923,59.203,79.933,59.169,79.948,59.1,79.964,58.919,79.985,58.946,80.042]},{"b":[-66.637,-55.273,-66.436,-55.166],"l":[-66.542,-55.169,-66.6,-55.194,-66.625,-55.213,-66.637,-55.234,-66.63,-55.254,-66.611,-55.27,-66.552,-55.273,-66.472,-55.229,-66.436,-55.19,-66.523,-55.166,-66.542,-55.169]},{"b":[-75.142,-44.907,-75.032,-44.795],"l":[-75.124,-44.87,-75.099,-44.902,-75.067,-44.907,-75.042,-44.89,-75.032,-44.871,-75.048,-44.824,-75.079,-44.795,-75.107,-44.795,-75.142,-44.816,-75.124,-44.87]},{"b":[-75.651,-48.765,-75.156,-48.02],"l":[-75.225,-48.671,-75.434,-48.721,-75.51,-48.763,-75.623,-48.765,-75.651,-48.586,-75.518,-48.329,-75.509,-48.231,-75.554,-48.157,-75.571,-48.096,-75.561,-48.071,-75.391,-48.02,-75.338,-48.074,-75.275,-48.218,-75.156,-48.425,-75.158,-48.623,-75.225,-48.671]},{"b":[-75.477,-50.772,-75.115,-50.481],"l":[-75.304,-50.484,-75.428,-50.481,-75.42,-50.53,-75.443,-50.596,-75.477,-50.654,-75.453,-50.683,-75.439,-50.741,-75.411,-50.764,-75.33,-50.772,-75.302,-50.68,-75.292,-50.597,-75.203,-50.581,-75.16,-50.554,-75.115,-50.51,-75.156,-50.497,-75.304,-50.484]},{"b":[-74.712,-53.358,-73.135,-52.734],"l":[-73.31,-53.248,-73.451,-53.144,-73.505,-53.14,-73.549,-53.126,-73.654,-53.07,-73.782,-53.056,-73.879,-53.012,-74.066,-52.965,-74.275,-52.946,-74.33,-52.929,-74.369,-52.931,-74.386,-52.922,-74.422,-52.86,-74.475,-52.836,-74.572,-52.771,-74.67,-52.734,-74.712,-52.749,-74.712,-52.768,-74.62,-52.835,-74.558,-52.922,-74.414,-52.995,-74.27,-53.082,-74.236,-53.076,-74.139,-53.091,-73.994,-53.076,-73.867,-53.097,-73.794,-53.121,-73.617,-53.23,-73.596,-53.253,-73.583,-53.3,-73.567,-53.307,-73.501,-53.318,-73.409,-53.321,-73.226,-53.358,-73.135,-53.354,-73.143,-53.341,-73.302,-53.259,-73.31,-53.248]},{"b":[-69.988,-55.651,-68.046,-54.882],"l":[-68.305,-55.357,-68.327,-55.333,-68.323,-55.308,-68.283,-55.255,-68.33,-55.219,-68.382,-55.192,-68.586,-55.178,-68.613,-55.156,-68.598,-55.128,-68.4,-55.042,-68.458,-54.96,-68.654,-54.958,-68.901,-55.018,-69.703,-54.919,-69.884,-54.882,-69.921,-55.061,-69.947,-55.111,-69.988,-55.131,-69.98,-55.147,-69.887,-55.174,-69.866,-55.191,-69.854,-55.22,-69.824,-55.237,-69.68,-55.219,-69.657,-55.229,-69.656,-55.298,-69.646,-55.321,-69.61,-55.34,-69.509,-55.371,-69.456,-55.424,-69.412,-55.444,-69.241,-55.477,-69.181,-55.475,-69.299,-55.369,-69.359,-55.301,-69.356,-55.274,-69.297,-55.166,-69.193,-55.172,-69.151,-55.183,-69.047,-55.244,-69.008,-55.256,-68.913,-55.239,-68.89,-55.241,-68.889,-55.263,-68.932,-55.347,-68.931,-55.371,-68.896,-55.424,-68.867,-55.45,-68.785,-55.436,-68.694,-55.452,-68.594,-55.45,-68.467,-55.489,-68.338,-55.505,-68.293,-55.521,-68.23,-55.602,-68.157,-55.634,-68.083,-55.651,-68.048,-55.643,-68.046,-55.587,-68.058,-55.518,-68.09,-55.478,-68.153,-55.437,-68.305,-55.357]},{"b":[-75.641,-49.231,-75.107,-48.773],"l":[-75.115,-48.916,-75.107,-48.837,-75.119,-48.773,-75.236,-48.779,-75.297,-48.811,-75.49,-48.85,-75.535,-48.838,-75.583,-48.859,-75.619,-48.886,-75.638,-48.943,-75.576,-48.981,-75.54,-48.988,-75.515,-49.01,-75.488,-49.082,-75.573,-49.139,-75.641,-49.195,-75.58,-49.23,-75.506,-49.231,-75.39,-49.159,-75.263,-49.069,-75.115,-48.916]},{"b":[-60.276,-51.396,-60.07,-51.273],"l":[-60.249,-51.396,-60.276,-51.363,-60.275,-51.281,-60.171,-51.273,-60.07,-51.308,-60.076,-51.343,-60.112,-51.396,-60.249,-51.396]},{"b":[-26.451,-58.492,-26.26,-58.382],"l":[-26.303,-58.382,-26.401,-58.383,-26.451,-58.415,-26.415,-58.44,-26.26,-58.492,-26.264,-58.435,-26.279,-58.402,-26.303,-58.382]},{"b":[53.316,12.319,54.511,12.716],"l":[54.187,12.664,53.919,12.659,53.825,12.625,53.763,12.637,53.638,12.707,53.535,12.716,53.431,12.664,53.404,12.633,53.388,12.602,53.316,12.533,53.499,12.425,53.598,12.342,53.719,12.319,54.129,12.361,54.271,12.447,54.414,12.483,54.45,12.523,54.511,12.553,54.187,12.664]},{"b":[53.191,24.253,53.445,24.411],"l":[53.445,24.371,53.412,24.411,53.371,24.364,53.333,24.342,53.191,24.291,53.258,24.253,53.332,24.259,53.383,24.281,53.409,24.308,53.445,24.371]},{"b":[58.641,20.204,58.951,20.681],"l":[58.788,20.497,58.641,20.337,58.641,20.211,58.659,20.204,58.722,20.219,58.772,20.267,58.835,20.424,58.951,20.516,58.884,20.681,58.788,20.497]},{"b":[52.582,24.281,52.658,24.377],"l":[52.6,24.281,52.617,24.289,52.658,24.333,52.629,24.377,52.584,24.352,52.582,24.335,52.6,24.281]},{"b":[55.295,26.583,56.279,27.003],"l":[56.074,26.983,55.907,26.91,55.758,26.948,55.747,26.931,55.785,26.857,55.763,26.812,55.532,26.71,55.347,26.648,55.296,26.658,55.295,26.639,55.312,26.593,55.34,26.586,55.424,26.583,55.543,26.618,55.675,26.686,55.747,26.692,55.848,26.731,55.894,26.732,55.954,26.701,56.095,26.801,56.188,26.921,56.279,26.952,56.214,27.003,56.074,26.983]},{"b":[53.634,24.136,53.928,24.259],"l":[53.799,24.136,53.826,24.153,53.928,24.143,53.928,24.177,53.894,24.215,53.834,24.259,53.69,24.211,53.634,24.17,53.716,24.145,53.799,24.136]},{"b":[41.776,16.571,42.17,17.003],"l":[42.06,16.804,42.026,16.758,42.033,16.742,42.065,16.71,41.988,16.716,41.947,16.749,41.963,16.779,41.954,16.806,41.948,16.936,41.917,16.994,41.86,17.003,41.885,16.947,41.858,16.893,41.816,16.86,41.776,16.847,41.802,16.779,41.897,16.684,41.964,16.653,42.072,16.671,42.102,16.644,42.108,16.618,42.128,16.595,42.158,16.571,42.167,16.596,42.17,16.709,42.06,16.804]},{"b":[39.94,15.577,40.408,15.889],"l":[40.25,15.703,40.234,15.666,40.211,15.648,40.183,15.643,40.141,15.696,40.132,15.795,40.097,15.838,40.043,15.875,39.957,15.889,40,15.828,39.979,15.807,39.945,15.789,39.94,15.745,40.016,15.733,40.071,15.677,40.063,15.666,40.024,15.656,39.947,15.696,39.975,15.612,40.095,15.591,40.196,15.598,40.305,15.577,40.399,15.58,40.408,15.629,40.25,15.703]},{"b":[42.69,13.674,42.794,13.769],"l":[42.69,13.674,42.756,13.704,42.794,13.766,42.781,13.769,42.735,13.753,42.69,13.674]},{"b":[50.452,25.807,50.617,26.246],"l":[50.47,26.229,50.452,26.191,50.489,26.058,50.466,25.966,50.544,25.833,50.575,25.807,50.607,25.883,50.617,26.002,50.61,26.124,50.558,26.198,50.586,26.241,50.564,26.246,50.47,26.229]},{"b":[42.694,13.95,42.798,14.067],"l":[42.694,14.008,42.756,13.955,42.774,13.95,42.787,13.971,42.798,14.012,42.762,14.067,42.694,14.008]},{"b":[48.081,29.602,48.348,29.979],"l":[48.348,29.783,48.228,29.936,48.185,29.979,48.159,29.96,48.139,29.897,48.12,29.886,48.113,29.87,48.115,29.849,48.081,29.799,48.143,29.665,48.18,29.611,48.218,29.602,48.275,29.624,48.34,29.695,48.347,29.72,48.348,29.783]},{"b":[179.373,-17.394,179.447,-17.256],"l":[179.373,-17.256,179.389,-17.394,179.422,-17.367,179.447,-17.306,179.433,-17.272,179.408,-17.257,179.373,-17.256]},{"b":[179.254,-18.11,179.362,-17.944],"l":[179.306,-17.944,179.272,-17.971,179.256,-17.999,179.254,-18.031,179.34,-18.11,179.349,-18.102,179.362,-18.065,179.338,-17.99,179.306,-17.944]},{"b":[178.748,-17.747,178.853,-17.619],"l":[178.748,-17.686,178.776,-17.747,178.828,-17.729,178.853,-17.681,178.831,-17.619,178.787,-17.624,178.748,-17.686]},{"b":[168.124,-16.835,168.477,-16.594],"l":[168.323,-16.788,168.46,-16.835,168.477,-16.794,168.447,-16.779,168.366,-16.759,168.296,-16.684,168.265,-16.671,168.234,-16.64,168.199,-16.594,168.182,-16.6,168.135,-16.637,168.124,-16.69,168.149,-16.766,168.181,-16.804,168.212,-16.806,168.323,-16.788]},{"b":[167.929,-16.347,168.298,-16.082],"l":[168.297,-16.337,168.298,-16.299,168.276,-16.265,168.235,-16.231,168.198,-16.12,168.164,-16.082,168.064,-16.181,167.985,-16.196,167.929,-16.229,167.957,-16.272,168.022,-16.316,168.182,-16.347,168.297,-16.337]},{"b":[168.158,-17.807,168.585,-17.542],"l":[168.446,-17.542,168.341,-17.552,168.32,-17.544,168.297,-17.545,168.273,-17.552,168.191,-17.645,168.158,-17.711,168.182,-17.717,168.233,-17.698,168.278,-17.706,168.306,-17.746,168.252,-17.781,168.399,-17.807,168.525,-17.798,168.585,-17.696,168.545,-17.685,168.446,-17.542]},{"b":[167.674,-15.482,168.003,-15.283],"l":[168.003,-15.283,167.826,-15.312,167.674,-15.452,167.72,-15.477,167.844,-15.482,167.911,-15.436,168.003,-15.283]},{"b":[166.526,-15.635,167.201,-14.637],"l":[167.182,-15.39,167.132,-15.135,167.106,-15.126,167.069,-15.072,167.054,-14.974,167.076,-14.936,167.027,-14.923,166.987,-14.94,166.968,-15.062,166.923,-15.139,166.885,-15.157,166.81,-15.157,166.746,-14.827,166.663,-14.735,166.608,-14.637,166.567,-14.642,166.526,-14.76,166.527,-14.85,166.648,-15.212,166.631,-15.406,166.699,-15.516,166.759,-15.567,166.758,-15.631,166.826,-15.635,166.937,-15.578,167.094,-15.581,167.2,-15.486,167.201,-15.443,167.182,-15.39]},{"b":[168.123,-15.972,168.268,-15.462],"l":[168.16,-15.462,168.123,-15.681,168.179,-15.926,168.196,-15.972,168.213,-15.97,168.256,-15.955,168.268,-15.892,168.183,-15.508,168.16,-15.462]},{"b":[168.104,-15.391,168.189,-14.986],"l":[168.13,-15.319,168.172,-15.391,168.189,-15.329,168.187,-15.197,168.136,-14.986,168.115,-14.989,168.104,-15.017,168.13,-15.319]},{"b":[167.151,-16.574,167.837,-15.877],"l":[167.793,-16.395,167.776,-16.341,167.714,-16.314,167.681,-16.261,167.642,-16.263,167.499,-16.166,167.484,-16.118,167.459,-16.118,167.413,-16.096,167.336,-15.917,167.254,-15.877,167.2,-15.885,167.183,-15.929,167.151,-16.08,167.218,-16.155,167.246,-16.15,167.316,-16.116,167.349,-16.154,167.38,-16.246,167.401,-16.401,167.447,-16.502,167.436,-16.515,167.449,-16.555,167.526,-16.574,167.611,-16.499,167.76,-16.516,167.837,-16.45,167.793,-16.395]},{"b":[167.095,-15.75,167.234,-15.623],"l":[167.234,-15.645,167.119,-15.623,167.095,-15.685,167.201,-15.75,167.219,-15.724,167.234,-15.645]},{"b":[158.457,-8.557,159.881,-7.545],"l":[159.794,-8.406,159.843,-8.327,159.431,-8.029,159.368,-7.994,159.287,-7.976,159.198,-7.91,159.109,-7.904,159.011,-7.837,158.972,-7.789,158.863,-7.722,158.734,-7.604,158.457,-7.545,158.479,-7.577,158.565,-7.651,158.597,-7.759,158.686,-7.818,158.778,-7.907,158.832,-7.927,158.855,-7.96,158.944,-8.041,159.09,-8.103,159.239,-8.196,159.292,-8.203,159.354,-8.26,159.645,-8.372,159.746,-8.474,159.881,-8.557,159.879,-8.534,159.849,-8.463,159.794,-8.406]},{"b":[156.453,-7.426,157.519,-6.609],"l":[157.412,-7.309,157.336,-7.28,157.193,-7.16,157.148,-7.114,157.103,-6.957,157.03,-6.892,156.765,-6.764,156.604,-6.641,156.479,-6.609,156.453,-6.638,156.457,-6.715,156.495,-6.762,156.696,-6.911,156.904,-7.18,157.102,-7.324,157.243,-7.353,157.315,-7.342,157.317,-7.359,157.339,-7.393,157.441,-7.426,157.519,-7.366,157.487,-7.33,157.452,-7.314,157.412,-7.309]},{"b":[153.592,-4.123,153.663,-4.041],"l":[153.592,-4.096,153.65,-4.123,153.659,-4.099,153.663,-4.041,153.64,-4.045,153.592,-4.096]},{"b":[120.876,5.87,121.415,6.096],"l":[121.283,6.022,121.214,6.004,121.159,6.076,121.038,6.096,120.898,6.007,120.876,5.953,120.931,5.896,121.019,5.923,121.083,5.893,121.218,5.943,121.294,5.87,121.411,5.94,121.415,5.965,121.392,6.002,121.283,6.022]},{"b":[114.299,-7.173,114.413,-7.073],"l":[114.299,-7.098,114.347,-7.163,114.398,-7.173,114.413,-7.133,114.384,-7.081,114.349,-7.073,114.322,-7.08,114.299,-7.098]},{"b":[134.182,34.423,134.372,34.534],"l":[134.182,34.519,134.188,34.496,134.238,34.467,134.252,34.423,134.315,34.469,134.333,34.464,134.352,34.484,134.372,34.522,134.326,34.534,134.182,34.519]},{"b":[-75.261,-47.851,-74.916,-47.691],"l":[-75.084,-47.825,-75.051,-47.8,-74.916,-47.757,-74.926,-47.723,-75.004,-47.695,-75.09,-47.691,-75.203,-47.728,-75.261,-47.764,-75.194,-47.818,-75.186,-47.851,-75.112,-47.838,-75.084,-47.825]},{"b":[-74.843,-43.625,-74.665,-43.536],"l":[-74.665,-43.6,-74.673,-43.577,-74.697,-43.553,-74.745,-43.536,-74.818,-43.549,-74.842,-43.57,-74.843,-43.596,-74.81,-43.625,-74.669,-43.608,-74.665,-43.6]},{"b":[-73.818,-44.833,-73.617,-44.544],"l":[-73.628,-44.681,-73.641,-44.611,-73.686,-44.546,-73.724,-44.544,-73.779,-44.559,-73.817,-44.614,-73.818,-44.652,-73.8,-44.684,-73.735,-44.752,-73.725,-44.797,-73.695,-44.831,-73.665,-44.833,-73.632,-44.821,-73.617,-44.753,-73.628,-44.681]},{"b":[-75.247,-48.626,-74.567,-47.839],"l":[-74.567,-48.592,-74.618,-48.425,-74.6,-48.393,-74.602,-48.37,-74.615,-48.343,-74.664,-48.299,-74.702,-48.206,-74.715,-48.146,-74.729,-48.126,-74.805,-48.078,-74.846,-48.021,-74.827,-47.85,-74.896,-47.839,-74.975,-47.923,-75.198,-47.975,-75.247,-48.027,-75.234,-48.053,-75.213,-48.142,-75.158,-48.225,-75.132,-48.279,-75.079,-48.362,-75.052,-48.391,-75.013,-48.536,-74.923,-48.626,-74.71,-48.601,-74.586,-48.616,-74.567,-48.592]},{"b":[-64.757,-54.903,-63.815,-54.716],"l":[-64.055,-54.73,-64.105,-54.722,-64.221,-54.722,-64.439,-54.739,-64.549,-54.716,-64.581,-54.753,-64.625,-54.774,-64.689,-54.775,-64.757,-54.827,-64.731,-54.863,-64.637,-54.903,-64.509,-54.84,-64.453,-54.84,-64.323,-54.796,-64.028,-54.793,-63.971,-54.811,-63.833,-54.768,-63.815,-54.725,-63.882,-54.723,-64.032,-54.742,-64.055,-54.73]},{"b":[-62.093,-39.235,-61.866,-39.086],"l":[-61.967,-39.112,-62.093,-39.086,-62.083,-39.11,-62.042,-39.167,-61.918,-39.227,-61.866,-39.235,-61.876,-39.172,-61.907,-39.136,-61.967,-39.112]},{"b":[-59.793,-52.251,-59.681,-52.134],"l":[-59.753,-52.141,-59.793,-52.134,-59.786,-52.156,-59.785,-52.205,-59.764,-52.242,-59.747,-52.251,-59.683,-52.232,-59.681,-52.18,-59.753,-52.141]},{"b":[-61.145,-51.946,-60.876,-51.786],"l":[-60.876,-51.794,-60.947,-51.8,-61.019,-51.786,-61.052,-51.814,-61.145,-51.839,-61.116,-51.875,-61.032,-51.942,-60.948,-51.946,-60.916,-51.897,-60.876,-51.794]},{"b":[-73.845,-54.127,-72.205,-53.394],"l":[-72.471,-54.028,-72.409,-54.004,-72.369,-53.981,-72.366,-53.941,-72.306,-53.862,-72.205,-53.807,-72.307,-53.725,-72.373,-53.687,-72.459,-53.599,-72.482,-53.588,-72.685,-53.558,-72.809,-53.565,-72.882,-53.578,-72.896,-53.563,-72.923,-53.482,-72.947,-53.442,-72.971,-53.423,-73.022,-53.415,-73.054,-53.394,-73.074,-53.397,-73.111,-53.425,-73.115,-53.448,-73.099,-53.512,-73.366,-53.47,-73.447,-53.41,-73.687,-53.427,-73.845,-53.546,-73.642,-53.57,-73.582,-53.655,-73.471,-53.736,-73.36,-53.724,-73.325,-53.723,-73.314,-53.729,-73.295,-53.792,-73.293,-53.836,-73.312,-53.92,-73.305,-53.944,-73.211,-53.986,-73.12,-54.009,-73.081,-53.998,-73.071,-53.978,-73.086,-53.916,-73.073,-53.875,-73.039,-53.833,-72.984,-53.861,-72.936,-53.861,-72.872,-53.849,-72.764,-53.865,-72.782,-53.955,-72.882,-54.042,-72.959,-54.066,-72.946,-54.092,-72.907,-54.115,-72.871,-54.127,-72.84,-54.125,-72.789,-54.103,-72.677,-54.079,-72.563,-54.074,-72.471,-54.028]},{"b":[-68.301,-55.293,-67.08,-54.929],"l":[-67.737,-55.256,-67.691,-55.243,-67.585,-55.192,-67.535,-55.179,-67.495,-55.177,-67.463,-55.182,-67.443,-55.201,-67.429,-55.237,-67.399,-55.272,-67.34,-55.293,-67.257,-55.282,-67.173,-55.243,-67.109,-55.192,-67.08,-55.154,-67.085,-55.115,-67.107,-55.064,-67.245,-54.978,-67.425,-54.969,-67.874,-54.93,-68.107,-54.929,-68.301,-54.981,-68.174,-55.071,-68.135,-55.173,-68.1,-55.207,-68.07,-55.221,-67.768,-55.26,-67.737,-55.256]},{"b":[-71.437,-55.177,-70.283,-54.835],"l":[-70.283,-55.066,-70.418,-54.909,-70.535,-54.921,-70.615,-54.946,-70.749,-54.953,-70.805,-54.968,-70.928,-54.943,-70.945,-54.931,-70.992,-54.868,-71.089,-54.867,-71.197,-54.844,-71.374,-54.835,-71.411,-54.839,-71.437,-54.889,-71.427,-54.914,-71.407,-54.931,-71.389,-54.934,-71.325,-54.914,-71.299,-54.892,-71.274,-54.887,-71.203,-54.893,-71.12,-54.938,-70.991,-54.99,-70.967,-55.007,-70.965,-55.04,-70.94,-55.062,-70.815,-55.08,-70.744,-55.104,-70.711,-55.107,-70.641,-55.085,-70.597,-55.082,-70.551,-55.112,-70.539,-55.135,-70.543,-55.161,-70.476,-55.177,-70.404,-55.166,-70.298,-55.114,-70.283,-55.066]},{"b":[-67.563,-55.785,-67.262,-55.585],"l":[-67.289,-55.777,-67.267,-55.763,-67.262,-55.744,-67.31,-55.689,-67.351,-55.612,-67.374,-55.589,-67.397,-55.585,-67.449,-55.641,-67.513,-55.662,-67.546,-55.684,-67.563,-55.708,-67.56,-55.725,-67.393,-55.753,-67.352,-55.766,-67.325,-55.785,-67.289,-55.777]},{"b":[-75.3,-51.625,-74.559,-51.207],"l":[-74.593,-51.387,-74.561,-51.361,-74.559,-51.277,-74.571,-51.245,-74.612,-51.207,-74.737,-51.208,-74.881,-51.279,-75.04,-51.318,-75.154,-51.279,-75.21,-51.383,-75.238,-51.454,-75.3,-51.556,-75.289,-51.625,-75.192,-51.567,-75.146,-51.524,-75.047,-51.398,-74.937,-51.428,-74.853,-51.434,-74.797,-51.412,-74.731,-51.367,-74.691,-51.37,-74.62,-51.396,-74.593,-51.387]},{"b":[-75.449,-50.376,-74.839,-50.012],"l":[-74.963,-50.237,-74.839,-50.197,-74.876,-50.11,-75.004,-50.089,-75.123,-50.055,-75.21,-50.045,-75.327,-50.012,-75.369,-50.113,-75.377,-50.168,-75.398,-50.193,-75.412,-50.257,-75.449,-50.343,-75.308,-50.343,-75.25,-50.376,-75.055,-50.296,-74.963,-50.237]},{"b":[-75.105,-52.279,-74.532,-51.63],"l":[-74.537,-51.965,-74.647,-51.866,-74.75,-51.852,-74.78,-51.825,-74.823,-51.63,-74.91,-51.65,-74.915,-51.738,-75.008,-51.724,-75.105,-51.789,-75.051,-51.904,-75.017,-52.038,-74.918,-52.152,-74.852,-52.271,-74.694,-52.279,-74.666,-52.16,-74.532,-51.992,-74.537,-51.965]},{"b":[-74.143,-43.922,-73.79,-43.784],"l":[-73.938,-43.914,-73.904,-43.875,-73.834,-43.883,-73.79,-43.876,-73.811,-43.827,-73.841,-43.789,-73.857,-43.784,-73.967,-43.817,-74.14,-43.821,-74.143,-43.872,-74.118,-43.887,-73.956,-43.922,-73.938,-43.914]},{"b":[-72.21,-54.374,-71.005,-53.885],"l":[-71.671,-54.225,-71.558,-54.246,-71.473,-54.231,-71.305,-54.314,-71.143,-54.374,-71.118,-54.366,-71.083,-54.316,-71.028,-54.281,-71.005,-54.247,-71.023,-54.162,-71.022,-54.112,-71.169,-54.113,-71.39,-54.033,-71.554,-53.956,-71.705,-53.923,-71.996,-53.885,-72.069,-53.921,-72.146,-53.939,-72.21,-54.048,-72.092,-54.119,-71.972,-54.207,-71.949,-54.301,-71.818,-54.276,-71.761,-54.23,-71.671,-54.225]},{"b":[-74.475,-51.942,-74.115,-51.725],"l":[-74.283,-51.919,-74.172,-51.942,-74.142,-51.931,-74.119,-51.911,-74.115,-51.888,-74.133,-51.871,-74.277,-51.812,-74.326,-51.77,-74.362,-51.751,-74.451,-51.725,-74.475,-51.726,-74.437,-51.791,-74.424,-51.845,-74.339,-51.898,-74.283,-51.919]},{"b":[-74.618,-45.345,-73.703,-44.135],"l":[-73.727,-45.119,-73.786,-45.034,-73.795,-44.979,-73.792,-44.946,-73.828,-44.84,-73.877,-44.729,-73.919,-44.655,-74.002,-44.591,-73.996,-44.538,-73.983,-44.495,-73.862,-44.445,-73.785,-44.437,-73.735,-44.395,-73.704,-44.325,-73.703,-44.274,-73.818,-44.235,-73.865,-44.185,-73.9,-44.135,-73.995,-44.14,-74.083,-44.186,-74.108,-44.276,-74.097,-44.389,-74.133,-44.416,-74.212,-44.427,-74.301,-44.396,-74.422,-44.435,-74.502,-44.474,-74.481,-44.585,-74.618,-44.648,-74.499,-44.748,-74.419,-44.865,-74.35,-44.911,-74.268,-45.059,-74.195,-45.145,-74.089,-45.196,-74.099,-45.325,-74.016,-45.345,-73.849,-45.341,-73.834,-45.327,-73.83,-45.283,-73.771,-45.277,-73.752,-45.267,-73.728,-45.196,-73.722,-45.158,-73.727,-45.119]},{"b":[-74.69,-45.757,-74.229,-45.173],"l":[-74.229,-45.611,-74.24,-45.575,-74.315,-45.464,-74.285,-45.277,-74.311,-45.173,-74.422,-45.203,-74.45,-45.253,-74.502,-45.285,-74.495,-45.426,-74.558,-45.526,-74.646,-45.6,-74.69,-45.663,-74.678,-45.739,-74.562,-45.722,-74.466,-45.757,-74.368,-45.736,-74.313,-45.692,-74.244,-45.654,-74.229,-45.611]},{"b":[-73.961,45.441,-73.476,45.705],"l":[-73.687,45.561,-73.853,45.516,-73.961,45.441,-73.92,45.442,-73.775,45.468,-73.644,45.449,-73.567,45.469,-73.552,45.49,-73.539,45.546,-73.476,45.705,-73.522,45.701,-73.687,45.561]},{"b":[-73.858,45.565,-73.572,45.694],"l":[-73.695,45.585,-73.572,45.694,-73.725,45.672,-73.858,45.574,-73.816,45.565,-73.695,45.585]},{"b":[-61.082,45.482,-60.912,45.577],"l":[-61.026,45.577,-61.082,45.558,-61.076,45.537,-61.012,45.496,-61.003,45.482,-60.962,45.49,-60.953,45.516,-60.912,45.567,-61.026,45.577]},{"b":[-62.008,47.219,-61.396,47.647],"l":[-61.476,47.564,-61.396,47.638,-61.474,47.647,-61.548,47.632,-61.628,47.594,-61.827,47.469,-61.925,47.425,-62.008,47.234,-61.951,47.219,-61.834,47.223,-61.773,47.26,-61.815,47.268,-61.879,47.266,-61.914,47.285,-61.887,47.345,-61.831,47.392,-61.751,47.431,-61.684,47.499,-61.582,47.56,-61.476,47.564]},{"b":[-64.685,47.748,-64.509,47.887],"l":[-64.565,47.866,-64.591,47.872,-64.663,47.863,-64.66,47.794,-64.685,47.754,-64.665,47.748,-64.621,47.752,-64.534,47.814,-64.509,47.887,-64.565,47.866]},{"b":[-64.591,47.907,-64.476,48.014],"l":[-64.591,47.907,-64.476,47.959,-64.481,48.007,-64.5,48.014,-64.52,48.005,-64.541,47.985,-64.591,47.907]},{"b":[-71.117,46.865,-70.826,46.996],"l":[-71.095,46.9,-71.117,46.865,-71.026,46.873,-70.913,46.92,-70.826,46.995,-70.88,46.996,-70.971,46.961,-71.095,46.9]},{"b":[-66.324,44.257,-66.21,44.392],"l":[-66.324,44.257,-66.274,44.292,-66.21,44.392,-66.25,44.379,-66.312,44.292,-66.324,44.257]},{"b":[-68.703,44.176,-68.623,44.256],"l":[-68.691,44.249,-68.703,44.232,-68.702,44.183,-68.661,44.176,-68.623,44.196,-68.656,44.242,-68.677,44.256,-68.691,44.249]},{"b":[-66.897,44.629,-66.745,44.805],"l":[-66.897,44.629,-66.762,44.682,-66.753,44.71,-66.745,44.791,-66.802,44.805,-66.845,44.764,-66.897,44.629]},{"b":[-68.412,44.25,-68.187,44.456],"l":[-68.299,44.456,-68.347,44.43,-68.409,44.364,-68.412,44.294,-68.386,44.277,-68.315,44.25,-68.308,44.269,-68.309,44.321,-68.245,44.313,-68.187,44.332,-68.191,44.364,-68.238,44.438,-68.299,44.456]},{"b":[112.726,-7.224,114.083,-6.873],"l":[113.067,-6.88,112.868,-6.9,112.769,-7.001,112.726,-7.073,112.764,-7.14,113.04,-7.212,113.127,-7.224,113.142,-7.208,113.166,-7.207,113.198,-7.218,113.471,-7.218,113.546,-7.193,113.656,-7.112,113.826,-7.12,113.845,-7.105,113.885,-7.049,114.083,-6.989,114.074,-6.96,113.975,-6.873,113.067,-6.88]},{"b":[158.836,-54.749,158.959,-54.472],"l":[158.836,-54.704,158.845,-54.749,158.879,-54.71,158.946,-54.575,158.959,-54.472,158.897,-54.506,158.836,-54.704]},{"b":[-174.069,-18.699,-173.922,-18.565],"l":[-174.069,-18.64,-174.053,-18.663,-174.009,-18.698,-173.991,-18.699,-173.954,-18.639,-173.924,-18.608,-173.922,-18.589,-173.968,-18.565,-174.002,-18.571,-174.069,-18.64]},{"b":[-151.485,-16.879,-151.365,-16.74],"l":[-151.485,-16.864,-151.449,-16.879,-151.41,-16.878,-151.365,-16.864,-151.411,-16.774,-151.467,-16.74,-151.476,-16.761,-151.485,-16.864]},{"b":[-157.579,1.732,-157.176,2.029],"l":[-157.342,1.856,-157.322,1.969,-157.442,2.025,-157.492,2.029,-157.365,1.946,-157.393,1.928,-157.436,1.847,-157.508,1.886,-157.531,1.927,-157.579,1.902,-157.42,1.788,-157.246,1.732,-157.176,1.74,-157.342,1.856]},{"b":[-139.166,-9.846,-138.827,-9.695],"l":[-139.024,-9.695,-139.166,-9.77,-139.134,-9.829,-139.074,-9.846,-139.024,-9.821,-138.875,-9.793,-138.827,-9.742,-138.874,-9.747,-139.024,-9.695]},{"b":[-139.134,-10.01,-139.06,-9.915],"l":[-139.134,-9.926,-139.134,-10.01,-139.06,-9.931,-139.083,-9.915,-139.107,-9.915,-139.134,-9.926]},{"b":[-138.69,-10.532,-138.624,-10.426],"l":[-138.688,-10.532,-138.651,-10.515,-138.632,-10.492,-138.624,-10.463,-138.643,-10.446,-138.69,-10.426,-138.688,-10.532]},{"b":[-149.635,-17.862,-149.151,-17.496],"l":[-149.322,-17.69,-149.33,-17.589,-149.379,-17.522,-149.508,-17.496,-149.611,-17.532,-149.635,-17.564,-149.633,-17.618,-149.579,-17.735,-149.482,-17.753,-149.341,-17.732,-149.29,-17.822,-149.254,-17.85,-149.182,-17.862,-149.151,-17.812,-149.178,-17.737,-149.322,-17.69]},{"b":[-140.253,-8.934,-140.044,-8.782],"l":[-140.224,-8.782,-140.24,-8.798,-140.253,-8.848,-140.217,-8.93,-140.171,-8.934,-140.073,-8.91,-140.046,-8.874,-140.044,-8.838,-140.058,-8.801,-140.224,-8.782]},{"b":[-140.144,-9.444,-140.031,-9.328],"l":[-140.144,-9.359,-140.138,-9.384,-140.097,-9.444,-140.076,-9.426,-140.031,-9.345,-140.071,-9.328,-140.144,-9.359]},{"b":[-172.779,-13.804,-172.177,-13.465],"l":[-172.333,-13.465,-172.511,-13.483,-172.67,-13.524,-172.779,-13.517,-172.744,-13.579,-172.659,-13.645,-172.536,-13.792,-172.485,-13.8,-172.331,-13.775,-172.225,-13.804,-172.177,-13.685,-172.222,-13.56,-172.333,-13.465]},{"b":[-179.018,-17.307,-178.915,-17.148],"l":[-179.015,-17.182,-178.971,-17.213,-178.976,-17.237,-179.004,-17.295,-178.982,-17.307,-178.956,-17.273,-178.925,-17.249,-178.915,-17.223,-178.921,-17.208,-178.953,-17.182,-179.005,-17.148,-179.018,-17.161,-179.015,-17.182]},{"b":[-159.409,3.797,-159.259,3.924],"l":[-159.409,3.873,-159.378,3.847,-159.373,3.881,-159.354,3.881,-159.327,3.863,-159.306,3.838,-159.314,3.823,-159.359,3.815,-159.332,3.8,-159.275,3.797,-159.259,3.839,-159.339,3.924,-159.369,3.917,-159.391,3.9,-159.409,3.873]},{"b":[-172.046,-14.047,-171.45,-13.807],"l":[-171.858,-13.807,-171.985,-13.824,-172.046,-13.857,-172.028,-13.907,-171.912,-14.002,-171.864,-14.002,-171.728,-14.047,-171.454,-14.046,-171.45,-14.022,-171.461,-13.978,-171.507,-13.95,-171.565,-13.943,-171.604,-13.879,-171.858,-13.807]},{"b":[-170.821,-14.36,-170.568,-14.257],"l":[-170.689,-14.257,-170.721,-14.276,-170.821,-14.312,-170.769,-14.36,-170.726,-14.351,-170.64,-14.282,-170.568,-14.267,-170.689,-14.257]},{"b":[-169.948,-19.138,-169.793,-18.966],"l":[-169.909,-18.99,-169.948,-19.073,-169.904,-19.138,-169.803,-19.083,-169.793,-19.043,-169.834,-18.966,-169.862,-18.969,-169.909,-18.99]},{"b":[-176.195,-13.341,-176.128,-13.222],"l":[-176.195,-13.302,-176.177,-13.341,-176.161,-13.333,-176.128,-13.268,-176.148,-13.222,-176.171,-13.243,-176.195,-13.302]},{"b":[-174.973,-21.451,-174.913,-21.3],"l":[-174.973,-21.35,-174.968,-21.382,-174.919,-21.451,-174.913,-21.3,-174.923,-21.303,-174.973,-21.35]},{"b":[134.506,7.361,134.66,7.712],"l":[134.6,7.616,134.556,7.594,134.516,7.526,134.506,7.437,134.535,7.361,134.595,7.382,134.598,7.438,134.633,7.501,134.66,7.663,134.651,7.712,134.609,7.624,134.6,7.616]},{"b":[145.729,18.057,145.835,18.173],"l":[145.729,18.057,145.778,18.079,145.835,18.137,145.807,18.173,145.789,18.155,145.729,18.057]},{"b":[145.684,15.107,145.822,15.265],"l":[145.713,15.215,145.684,15.125,145.698,15.114,145.749,15.107,145.752,15.133,145.782,15.175,145.789,15.223,145.822,15.265,145.786,15.257,145.713,15.215]},{"b":[144.649,13.258,144.941,13.622],"l":[144.79,13.527,144.649,13.429,144.65,13.313,144.663,13.291,144.7,13.258,144.742,13.259,144.78,13.411,144.941,13.57,144.91,13.599,144.875,13.615,144.837,13.622,144.79,13.527]},{"b":[145.587,14.92,145.662,15.06],"l":[145.587,15.031,145.592,14.999,145.621,14.92,145.662,14.971,145.647,15.059,145.625,15.06,145.587,15.031]},{"b":[138.062,9.419,138.214,9.593],"l":[138.085,9.495,138.062,9.446,138.067,9.419,138.143,9.501,138.183,9.507,138.214,9.547,138.186,9.593,138.147,9.584,138.117,9.55,138.085,9.495]},{"b":[177.959,-19.152,178.488,-18.934],"l":[178.334,-18.934,178.282,-18.957,178.208,-18.97,178.157,-19.028,178.104,-19.066,178.052,-19.06,178.001,-19.101,177.959,-19.122,178.021,-19.152,178.162,-19.121,178.182,-19.112,178.189,-19.092,178.211,-19.067,178.288,-19.004,178.316,-19.01,178.359,-19.046,178.488,-19.017,178.488,-18.974,178.42,-18.951,178.334,-18.934]},{"b":[167.404,-14.312,167.599,-14.142],"l":[167.439,-14.168,167.411,-14.197,167.404,-14.282,167.43,-14.295,167.543,-14.312,167.585,-14.261,167.599,-14.184,167.506,-14.142,167.439,-14.168]},{"b":[167.392,-13.917,167.554,-13.709],"l":[167.499,-13.885,167.543,-13.873,167.553,-13.846,167.554,-13.814,167.547,-13.777,167.481,-13.709,167.407,-13.748,167.392,-13.788,167.451,-13.909,167.474,-13.917,167.489,-13.907,167.499,-13.885]},{"b":[169.738,-20.242,169.896,-20.145],"l":[169.751,-20.153,169.738,-20.202,169.807,-20.241,169.861,-20.242,169.896,-20.187,169.852,-20.148,169.829,-20.145,169.751,-20.153]},{"b":[169.217,-19.649,169.491,-19.322],"l":[169.36,-19.458,169.337,-19.329,169.291,-19.322,169.247,-19.345,169.217,-19.476,169.262,-19.545,169.347,-19.624,169.438,-19.649,169.491,-19.54,169.36,-19.458]},{"b":[168.987,-18.989,169.334,-18.617],"l":[169.296,-18.867,169.201,-18.796,169.256,-18.763,169.178,-18.725,169.144,-18.631,169.088,-18.617,169.016,-18.644,168.987,-18.708,168.998,-18.825,168.987,-18.871,169.248,-18.983,169.288,-18.989,169.334,-18.94,169.296,-18.867]},{"b":[167.815,-21.643,168.139,-21.338],"l":[168.011,-21.43,167.995,-21.407,167.985,-21.37,167.988,-21.338,167.926,-21.373,167.815,-21.393,167.879,-21.524,167.876,-21.582,167.941,-21.606,167.967,-21.642,168.006,-21.643,168.121,-21.616,168.139,-21.445,168.058,-21.448,168.011,-21.43]},{"b":[166.494,-20.709,166.671,-20.413],"l":[166.56,-20.561,166.558,-20.617,166.494,-20.709,166.547,-20.699,166.589,-20.662,166.623,-20.596,166.602,-20.585,166.6,-20.525,166.618,-20.478,166.671,-20.45,166.625,-20.418,166.583,-20.413,166.585,-20.45,166.56,-20.561]},{"b":[167.033,-21.169,167.431,-20.674],"l":[167.293,-20.892,167.298,-20.733,167.269,-20.701,167.204,-20.674,167.056,-20.72,167.045,-20.759,167.136,-20.766,167.189,-20.804,167.112,-20.904,167.033,-20.923,167.073,-20.997,167.134,-21.061,167.273,-21.097,167.346,-21.169,167.401,-21.161,167.43,-21.087,167.431,-21.055,167.361,-20.942,167.293,-20.892]},{"b":[167.422,-22.661,167.544,-22.541],"l":[167.443,-22.541,167.422,-22.619,167.444,-22.639,167.473,-22.653,167.513,-22.661,167.544,-22.623,167.529,-22.579,167.443,-22.541]},{"b":[159.928,-19.333,159.975,-19.115],"l":[159.928,-19.174,159.936,-19.333,159.952,-19.312,159.975,-19.238,159.96,-19.115,159.928,-19.174]},{"b":[166.747,-11.681,166.929,-11.571],"l":[166.855,-11.579,166.791,-11.571,166.747,-11.591,166.806,-11.677,166.841,-11.681,166.929,-11.665,166.875,-11.63,166.855,-11.579]},{"b":[165.79,-10.851,166.162,-10.661],"l":[166.024,-10.661,165.909,-10.674,165.86,-10.703,165.836,-10.761,165.79,-10.756,165.791,-10.785,165.819,-10.844,165.857,-10.841,165.904,-10.851,165.968,-10.779,166.028,-10.77,166.053,-10.775,166.133,-10.758,166.13,-10.745,166.162,-10.693,166.126,-10.68,166.024,-10.661]},{"b":[159.979,-11.832,160.576,-11.472],"l":[160.077,-11.493,160,-11.472,159.986,-11.495,159.979,-11.538,160.004,-11.58,160.087,-11.594,160.1,-11.611,160.15,-11.644,160.27,-11.664,160.355,-11.712,160.395,-11.789,160.443,-11.815,160.507,-11.832,160.576,-11.798,160.537,-11.759,160.449,-11.696,160.077,-11.493]},{"b":[160.59,-9.611,161.378,-8.314],"l":[161.368,-9.49,161.258,-9.317,161.257,-9.192,161.209,-9.133,161.205,-9.092,161.159,-8.962,161.043,-8.855,160.976,-8.837,160.944,-8.799,160.954,-8.699,160.988,-8.665,160.998,-8.612,160.749,-8.314,160.702,-8.317,160.685,-8.336,160.649,-8.338,160.596,-8.328,160.59,-8.373,160.714,-8.539,160.663,-8.621,160.772,-8.964,160.873,-9.157,161.024,-9.271,161.042,-9.308,161.191,-9.393,161.322,-9.59,161.367,-9.611,161.378,-9.574,161.368,-9.49]},{"b":[161.286,-10.832,162.373,-10.204],"l":[162.288,-10.776,162.287,-10.71,162.157,-10.506,162.105,-10.454,162.023,-10.477,161.914,-10.436,161.841,-10.446,161.715,-10.387,161.698,-10.371,161.654,-10.352,161.476,-10.238,161.382,-10.206,161.305,-10.204,161.286,-10.282,161.294,-10.326,161.398,-10.332,161.487,-10.361,161.499,-10.455,161.539,-10.491,161.538,-10.566,161.787,-10.717,161.906,-10.764,162.043,-10.785,162.124,-10.824,162.201,-10.808,162.301,-10.832,162.373,-10.823,162.288,-10.776]},{"b":[161.364,-9.77,161.559,-9.353],"l":[161.412,-9.6,161.41,-9.682,161.442,-9.719,161.478,-9.691,161.554,-9.77,161.559,-9.733,161.548,-9.626,161.407,-9.368,161.364,-9.353,161.402,-9.448,161.417,-9.514,161.412,-9.6]},{"b":[159.036,-9.126,159.234,-8.993],"l":[159.175,-9.126,159.189,-9.124,159.234,-9.094,159.228,-9.03,159.176,-9.022,159.154,-9.001,159.13,-8.993,159.078,-9.025,159.036,-9.075,159.071,-9.11,159.128,-9.114,159.175,-9.126]},{"b":[160.096,-9.181,160.408,-8.996],"l":[160.319,-9.061,160.254,-9.007,160.226,-9.01,160.168,-8.996,160.096,-9.034,160.105,-9.081,160.175,-9.084,160.253,-9.123,160.268,-9.163,160.276,-9.169,160.3,-9.16,160.371,-9.181,160.408,-9.14,160.319,-9.061]},{"b":[159.538,-8.521,159.688,-8.379],"l":[159.646,-8.45,159.642,-8.414,159.595,-8.379,159.553,-8.399,159.538,-8.451,159.569,-8.485,159.64,-8.521,159.688,-8.508,159.646,-8.45]},{"b":[157.218,-8.612,157.898,-7.966],"l":[157.885,-8.569,157.898,-8.506,157.826,-8.324,157.763,-8.242,157.651,-8.217,157.612,-8.165,157.599,-8.006,157.491,-7.966,157.433,-7.985,157.341,-8.096,157.322,-8.161,157.229,-8.212,157.218,-8.263,157.232,-8.315,157.302,-8.333,157.351,-8.275,157.504,-8.258,157.558,-8.27,157.565,-8.338,157.588,-8.445,157.656,-8.5,157.749,-8.524,157.819,-8.612,157.834,-8.573,157.885,-8.569]},{"b":[158.155,-8.826,158.253,-8.678],"l":[158.21,-8.678,158.155,-8.786,158.179,-8.826,158.201,-8.822,158.253,-8.797,158.236,-8.765,158.21,-8.678]},{"b":[157.879,-8.763,158.132,-8.508],"l":[157.909,-8.566,157.898,-8.587,157.879,-8.669,157.938,-8.736,158.009,-8.763,158.108,-8.684,158.104,-8.646,158.09,-8.623,158.068,-8.607,158.132,-8.557,158.105,-8.537,157.998,-8.508,157.967,-8.544,157.938,-8.561,157.909,-8.566]},{"b":[157.212,-8.728,157.411,-8.421],"l":[157.411,-8.475,157.379,-8.421,157.345,-8.432,157.234,-8.52,157.212,-8.565,157.334,-8.7,157.389,-8.728,157.389,-8.713,157.332,-8.651,157.347,-8.575,157.383,-8.555,157.411,-8.475]},{"b":[156.502,-7.937,156.809,-7.574],"l":[156.718,-7.696,156.64,-7.613,156.561,-7.574,156.502,-7.64,156.511,-7.708,156.612,-7.806,156.611,-7.866,156.635,-7.883,156.669,-7.937,156.688,-7.923,156.708,-7.877,156.79,-7.778,156.809,-7.723,156.718,-7.696]},{"b":[156.54,-8.196,156.612,-7.959],"l":[156.57,-7.959,156.551,-7.971,156.542,-8.011,156.54,-8.073,156.592,-8.196,156.604,-8.172,156.612,-8.096,156.57,-7.959]},{"b":[156.958,-8.123,157.201,-7.855],"l":[157.192,-8.082,157.201,-8.016,157.186,-7.941,157.146,-7.883,157.103,-7.855,157.024,-7.868,156.959,-7.938,156.958,-8.014,157.041,-8.117,157.15,-8.123,157.172,-8.108,157.192,-8.082]},{"b":[155.678,-7.121,155.865,-6.973],"l":[155.739,-6.973,155.705,-7.013,155.678,-7.089,155.739,-7.121,155.84,-7.097,155.865,-7.043,155.739,-6.973]},{"b":[162.921,5.277,162.993,5.335],"l":[162.921,5.318,162.93,5.301,162.993,5.277,162.983,5.326,162.958,5.335,162.921,5.318]},{"b":[158.128,6.791,158.335,6.978],"l":[158.186,6.978,158.135,6.945,158.128,6.905,158.161,6.883,158.183,6.801,158.257,6.791,158.315,6.814,158.309,6.855,158.335,6.893,158.295,6.951,158.186,6.978]},{"b":[175.337,-36.334,175.551,-36.071],"l":[175.513,-36.177,175.444,-36.115,175.409,-36.071,175.39,-36.078,175.382,-36.095,175.337,-36.135,175.346,-36.218,175.359,-36.231,175.445,-36.273,175.475,-36.314,175.551,-36.334,175.543,-36.279,175.513,-36.177]},{"b":[173.781,-40.922,173.964,-40.713],"l":[173.873,-40.749,173.812,-40.794,173.786,-40.881,173.781,-40.922,173.915,-40.864,173.958,-40.787,173.964,-40.713,173.903,-40.746,173.873,-40.749]},{"b":[166.532,-45.73,166.746,-45.615],"l":[166.567,-45.644,166.532,-45.7,166.559,-45.708,166.592,-45.702,166.642,-45.724,166.695,-45.73,166.729,-45.73,166.741,-45.705,166.746,-45.656,166.731,-45.639,166.686,-45.615,166.567,-45.644]},{"b":[-176.848,-44.117,-176.178,-43.718],"l":[-176.275,-43.765,-176.214,-43.766,-176.178,-43.74,-176.566,-43.718,-176.667,-43.765,-176.761,-43.758,-176.848,-43.824,-176.808,-43.835,-176.635,-43.82,-176.555,-43.852,-176.524,-43.901,-176.563,-43.954,-176.632,-44.006,-176.629,-44.036,-176.598,-44.107,-176.572,-44.115,-176.516,-44.117,-176.453,-44.077,-176.334,-44.048,-176.334,-44.025,-176.385,-43.951,-176.439,-43.955,-176.5,-43.86,-176.441,-43.816,-176.455,-43.805,-176.517,-43.785,-176.499,-43.768,-176.407,-43.761,-176.375,-43.791,-176.382,-43.867,-176.275,-43.765]},{"b":[-176.229,-44.331,-176.123,-44.225],"l":[-176.229,-44.237,-176.215,-44.274,-176.221,-44.331,-176.176,-44.322,-176.123,-44.268,-176.155,-44.225,-176.229,-44.237]},{"b":[169.022,-52.57,169.233,-52.485],"l":[169.079,-52.499,169.022,-52.495,169.04,-52.529,169.076,-52.552,169.128,-52.57,169.233,-52.548,169.178,-52.497,169.129,-52.485,169.079,-52.499]},{"b":[165.889,-50.846,166.267,-50.531],"l":[166.21,-50.612,166.259,-50.577,166.267,-50.559,166.254,-50.544,166.225,-50.531,166.101,-50.539,166.103,-50.573,166.074,-50.679,165.916,-50.763,165.889,-50.808,165.904,-50.821,165.971,-50.82,166.013,-50.778,166.038,-50.787,166.073,-50.823,166.188,-50.846,166.243,-50.846,166.221,-50.762,166.201,-50.751,166.179,-50.715,166.22,-50.694,166.208,-50.652,166.21,-50.612]},{"b":[167.522,-47.264,168.261,-46.694],"l":[168.156,-46.988,168.125,-46.956,168.043,-46.933,168.041,-46.888,168.145,-46.902,168.145,-46.862,167.956,-46.694,167.784,-46.7,167.765,-46.798,167.801,-46.907,167.742,-46.957,167.741,-47.014,167.654,-47.044,167.631,-47.088,167.629,-47.142,167.539,-47.199,167.522,-47.259,167.555,-47.264,167.676,-47.243,167.785,-47.176,167.811,-47.17,167.906,-47.18,168.015,-47.117,168.184,-47.102,168.241,-47.07,168.261,-47.027,168.241,-46.979,168.156,-46.988]},{"b":[136.943,-15.824,137.094,-15.595],"l":[137.065,-15.663,137.01,-15.595,136.986,-15.652,136.963,-15.666,136.943,-15.712,136.985,-15.726,136.996,-15.776,137.051,-15.824,137.094,-15.778,137.071,-15.738,137.065,-15.663]},{"b":[136.846,-15.627,136.89,-15.503],"l":[136.877,-15.503,136.846,-15.544,136.847,-15.627,136.863,-15.62,136.89,-15.589,136.877,-15.503]},{"b":[136.503,-15.632,136.612,-15.534],"l":[136.523,-15.543,136.503,-15.583,136.514,-15.627,136.531,-15.632,136.591,-15.628,136.612,-15.544,136.586,-15.534,136.523,-15.543]},{"b":[132.484,-11.318,132.629,-10.969],"l":[132.574,-11.318,132.593,-11.303,132.629,-11.169,132.597,-11.106,132.593,-10.998,132.579,-10.969,132.538,-11.028,132.484,-11.037,132.516,-11.116,132.494,-11.164,132.574,-11.318]},{"b":[148.939,-20.303,149.045,-20.154],"l":[149.004,-20.221,148.981,-20.154,148.939,-20.284,148.987,-20.302,149.02,-20.303,149.044,-20.292,149.045,-20.278,149.004,-20.221]},{"b":[148.887,-20.154,148.968,-20.044],"l":[148.968,-20.044,148.932,-20.069,148.906,-20.102,148.887,-20.144,148.913,-20.154,148.936,-20.15,148.956,-20.135,148.968,-20.044]},{"b":[146.099,-18.485,146.342,-18.226],"l":[146.278,-18.231,146.249,-18.226,146.231,-18.241,146.187,-18.255,146.099,-18.252,146.116,-18.292,146.191,-18.363,146.236,-18.451,146.299,-18.485,146.327,-18.449,146.342,-18.4,146.299,-18.326,146.278,-18.231]},{"b":[142.125,-10.762,142.275,-10.592],"l":[142.198,-10.592,142.131,-10.641,142.125,-10.668,142.137,-10.732,142.191,-10.762,142.275,-10.705,142.198,-10.592]},{"b":[142.195,-10.254,142.339,-10.14],"l":[142.219,-10.149,142.195,-10.199,142.216,-10.236,142.279,-10.254,142.339,-10.192,142.299,-10.14,142.219,-10.149]},{"b":[142.098,-10.181,142.192,-10.052],"l":[142.149,-10.052,142.098,-10.122,142.142,-10.181,142.168,-10.154,142.192,-10.085,142.149,-10.052]},{"b":[139.148,-16.742,139.698,-16.395],"l":[139.588,-16.395,139.459,-16.438,139.293,-16.467,139.229,-16.528,139.163,-16.626,139.148,-16.714,139.16,-16.742,139.239,-16.719,139.283,-16.719,139.354,-16.697,139.392,-16.649,139.431,-16.661,139.508,-16.573,139.56,-16.529,139.698,-16.515,139.604,-16.403,139.588,-16.395]},{"b":[139.408,-17.132,139.571,-16.99],"l":[139.493,-16.99,139.459,-17.049,139.408,-17.091,139.422,-17.132,139.459,-17.115,139.571,-17.094,139.56,-17.042,139.493,-16.99]},{"b":[136.335,-14.293,136.951,-13.676],"l":[136.885,-14.197,136.787,-14.158,136.75,-14.115,136.745,-14.073,136.788,-13.946,136.815,-13.907,136.843,-13.897,136.906,-13.827,136.891,-13.787,136.871,-13.764,136.845,-13.751,136.804,-13.842,136.758,-13.845,136.715,-13.804,136.696,-13.726,136.702,-13.682,136.656,-13.676,136.583,-13.721,136.534,-13.794,136.425,-13.865,136.411,-14.011,136.428,-14.126,136.392,-14.175,136.335,-14.212,136.363,-14.229,136.461,-14.235,136.65,-14.28,136.763,-14.273,136.894,-14.293,136.931,-14.246,136.951,-14.184,136.934,-14.179,136.885,-14.197]},{"b":[136.122,-13.836,136.275,-13.665],"l":[136.16,-13.737,136.134,-13.753,136.122,-13.781,136.123,-13.817,136.214,-13.836,136.237,-13.825,136.275,-13.791,136.257,-13.707,136.215,-13.665,136.16,-13.737]},{"b":[136.522,-11.439,136.78,-11.012],"l":[136.688,-11.178,136.649,-11.212,136.56,-11.358,136.522,-11.394,136.527,-11.439,136.599,-11.379,136.741,-11.195,136.78,-11.012,136.732,-11.025,136.727,-11.105,136.711,-11.158,136.688,-11.178]},{"b":[136.18,-11.677,136.479,-11.466],"l":[136.339,-11.602,136.379,-11.583,136.471,-11.509,136.479,-11.466,136.449,-11.487,136.267,-11.576,136.18,-11.677,136.339,-11.602]},{"b":[130.043,-11.836,130.606,-11.337],"l":[130.459,-11.679,130.433,-11.592,130.386,-11.51,130.377,-11.42,130.339,-11.337,130.295,-11.337,130.251,-11.361,130.153,-11.478,130.187,-11.541,130.198,-11.658,130.139,-11.697,130.072,-11.681,130.043,-11.787,130.077,-11.825,130.131,-11.825,130.317,-11.772,130.503,-11.836,130.606,-11.817,130.603,-11.773,130.58,-11.737,130.542,-11.703,130.459,-11.679]},{"b":[130.369,-11.926,131.539,-11.18],"l":[131.437,-11.313,131.321,-11.247,131.268,-11.19,131.217,-11.243,131.141,-11.263,131.023,-11.334,130.987,-11.34,130.913,-11.309,130.752,-11.384,130.619,-11.376,130.56,-11.306,130.519,-11.279,130.427,-11.183,130.403,-11.18,130.385,-11.192,130.369,-11.215,130.405,-11.305,130.423,-11.446,130.512,-11.618,130.645,-11.742,130.951,-11.926,131.292,-11.711,131.383,-11.583,131.459,-11.588,131.468,-11.51,131.539,-11.437,131.522,-11.415,131.473,-11.383,131.437,-11.313]},{"b":[125.091,-14.642,125.199,-14.456],"l":[125.117,-14.492,125.091,-14.592,125.135,-14.642,125.199,-14.579,125.194,-14.553,125.198,-14.475,125.16,-14.456,125.117,-14.492]},{"b":[124.483,-15.43,124.605,-15.267],"l":[124.504,-15.292,124.483,-15.34,124.524,-15.382,124.524,-15.421,124.56,-15.43,124.597,-15.402,124.605,-15.357,124.565,-15.311,124.551,-15.27,124.519,-15.267,124.504,-15.292]},{"b":[115.309,-20.866,115.458,-20.668],"l":[115.435,-20.668,115.354,-20.746,115.309,-20.811,115.318,-20.851,115.388,-20.866,115.446,-20.788,115.458,-20.716,115.435,-20.668]},{"b":[112.908,-26.095,113.183,-25.52],"l":[113.132,-25.883,113.096,-25.815,112.982,-25.52,112.947,-25.532,112.908,-25.57,112.964,-25.783,113.156,-26.095,113.183,-26.053,113.148,-25.974,113.132,-25.952,113.132,-25.883]},{"b":[147.105,-43.501,147.356,-43.279],"l":[147.313,-43.28,147.284,-43.279,147.234,-43.33,147.22,-43.371,147.198,-43.379,147.185,-43.408,147.163,-43.43,147.105,-43.413,147.105,-43.431,147.154,-43.5,147.231,-43.483,147.309,-43.501,147.356,-43.397,147.342,-43.346,147.313,-43.28]},{"b":[148.023,-42.719,148.17,-42.593],"l":[148.023,-42.64,148.031,-42.663,148.03,-42.715,148.048,-42.719,148.104,-42.71,148.101,-42.681,148.17,-42.652,148.143,-42.616,148.073,-42.593,148.023,-42.64]},{"b":[147.296,-43.241,147.435,-43.08],"l":[147.353,-43.08,147.327,-43.115,147.319,-43.145,147.296,-43.162,147.338,-43.183,147.349,-43.232,147.372,-43.241,147.435,-43.241,147.397,-43.118,147.353,-43.08]},{"b":[144.71,-40.589,144.791,-40.435],"l":[144.751,-40.47,144.71,-40.485,144.748,-40.589,144.784,-40.507,144.791,-40.44,144.783,-40.435,144.751,-40.47]},{"b":[143.839,-40.12,144.141,-39.58],"l":[144.121,-39.785,144.091,-39.638,144.001,-39.58,143.949,-39.584,143.939,-39.658,143.879,-39.7,143.862,-39.738,143.865,-39.824,143.839,-39.904,143.888,-39.984,143.876,-40.064,143.899,-40.12,143.928,-40.116,144.035,-40.078,144.112,-40.022,144.141,-39.954,144.106,-39.874,144.121,-39.785]},{"b":[148.01,-40.497,148.474,-40.307],"l":[148.326,-40.307,148.198,-40.358,148.059,-40.357,148.01,-40.381,148.02,-40.404,148.103,-40.452,148.214,-40.458,148.319,-40.435,148.353,-40.497,148.404,-40.487,148.474,-40.432,148.421,-40.367,148.326,-40.307]},{"b":[147.767,-40.262,148.323,-39.726],"l":[148.106,-40.262,148.21,-40.234,148.299,-40.172,148.314,-40.174,148.323,-40.144,148.251,-40.1,148.29,-40.065,148.297,-39.986,148.27,-39.967,148.178,-39.938,148,-39.758,147.933,-39.726,147.839,-39.832,147.767,-39.87,147.812,-39.91,147.876,-39.905,147.906,-39.971,147.891,-40.015,148.025,-40.172,148.047,-40.213,148.074,-40.241,148.106,-40.262]},{"b":[148.117,-40.593,148.237,-40.503],"l":[148.188,-40.593,148.237,-40.515,148.218,-40.505,148.193,-40.503,148.117,-40.521,148.127,-40.544,148.188,-40.593]},{"b":[136.541,-36.075,138.123,-35.592],"l":[138.047,-35.755,137.929,-35.726,137.836,-35.762,137.596,-35.739,137.598,-35.722,137.635,-35.656,137.585,-35.62,137.53,-35.605,137.334,-35.592,137.092,-35.664,136.639,-35.749,136.579,-35.809,136.541,-35.89,136.589,-35.935,136.755,-36.033,136.913,-36.047,137.026,-36.024,137.148,-36.039,137.21,-35.982,137.382,-36.021,137.448,-36.075,137.59,-36.027,137.622,-35.938,137.671,-35.898,137.836,-35.868,138.012,-35.908,138.067,-35.901,138.123,-35.852,138.047,-35.755]},{"b":[145.28,-38.421,145.487,-38.314],"l":[145.295,-38.319,145.286,-38.341,145.28,-38.391,145.336,-38.421,145.487,-38.355,145.427,-38.314,145.295,-38.319]},{"b":[145.128,-38.557,145.355,-38.459],"l":[145.218,-38.459,145.128,-38.528,145.271,-38.52,145.355,-38.557,145.349,-38.538,145.314,-38.491,145.288,-38.472,145.218,-38.459]},{"b":[153.396,-27.712,153.539,-27.405],"l":[153.401,-27.506,153.396,-27.665,153.427,-27.706,153.453,-27.712,153.539,-27.436,153.522,-27.422,153.435,-27.405,153.401,-27.506]},{"b":[153.365,-27.331,153.467,-27.03],"l":[153.467,-27.038,153.432,-27.03,153.38,-27.049,153.365,-27.139,153.377,-27.235,153.421,-27.331,153.442,-27.316,153.426,-27.201,153.467,-27.038]},{"b":[152.977,-25.778,153.359,-24.729],"l":[153.298,-24.915,153.282,-24.738,153.257,-24.729,153.223,-24.74,153.181,-24.765,153.144,-24.815,153.186,-24.833,153.242,-24.923,153.228,-25.006,153.189,-25.071,153.038,-25.193,153.061,-25.302,153.052,-25.354,152.999,-25.448,152.977,-25.551,153.007,-25.729,153.052,-25.778,153.077,-25.751,153.084,-25.683,153.141,-25.513,153.35,-25.063,153.359,-24.978,153.298,-24.915]},{"b":[151.033,-23.776,151.296,-23.461],"l":[151.229,-23.595,151.24,-23.53,151.212,-23.513,151.181,-23.516,151.147,-23.491,151.06,-23.461,151.033,-23.53,151.184,-23.741,151.238,-23.776,151.262,-23.762,151.296,-23.72,151.274,-23.668,151.229,-23.595]},{"b":[150.462,-22.325,150.549,-22.211],"l":[150.485,-22.268,150.462,-22.308,150.488,-22.325,150.517,-22.323,150.549,-22.307,150.521,-22.228,150.488,-22.211,150.485,-22.268]},{"b":[149.87,-22.223,149.928,-22.049],"l":[149.87,-22.15,149.894,-22.223,149.928,-22.193,149.928,-22.149,149.912,-22.049,149.875,-22.074,149.87,-22.15]},{"b":[117.272,8.191,117.355,8.315],"l":[117.281,8.315,117.272,8.254,117.287,8.191,117.355,8.215,117.354,8.289,117.33,8.308,117.281,8.315]},{"b":[116.97,7.808,117.08,8.069],"l":[116.994,8.051,116.976,8.017,116.97,7.895,117.028,7.808,117.08,7.883,117.077,8.069,116.994,8.051]},{"b":[119.764,10.455,120.008,10.64],"l":[119.852,10.64,119.764,10.552,119.793,10.455,119.916,10.486,119.981,10.539,120.008,10.57,119.95,10.605,119.852,10.64]},{"b":[119.726,11.376,119.883,11.525],"l":[119.761,11.474,119.726,11.475,119.73,11.432,119.799,11.409,119.831,11.376,119.855,11.393,119.883,11.472,119.861,11.525,119.827,11.515,119.761,11.474]},{"b":[119.866,11.994,120.341,12.319],"l":[120.228,12.22,120.194,12.167,120.155,12.152,120.1,12.168,120.078,12.198,119.964,12.27,119.916,12.319,119.897,12.313,119.886,12.3,119.88,12.28,119.892,12.273,119.87,12.244,119.866,12.199,119.896,12.179,119.957,12.069,120.011,12.008,120.1,11.994,120.174,12.02,120.243,12.005,120.315,12.012,120.341,12.077,120.261,12.142,120.228,12.22]},{"b":[119.861,11.669,120.073,11.981],"l":[119.957,11.96,119.916,11.981,119.861,11.954,119.933,11.774,119.932,11.74,119.945,11.691,119.964,11.669,120.039,11.703,120.073,11.783,120.062,11.821,120.071,11.861,120.036,11.917,119.998,11.932,119.957,11.96]},{"b":[122.517,10.425,122.737,10.741],"l":[122.626,10.695,122.538,10.608,122.517,10.493,122.538,10.425,122.597,10.461,122.622,10.459,122.65,10.473,122.681,10.498,122.737,10.655,122.729,10.706,122.701,10.741,122.673,10.739,122.648,10.723,122.626,10.695]},{"b":[123.316,9.423,124.058,11.274],"l":[124.053,11.029,124.04,11.054,124.037,11.107,124.058,11.217,124.039,11.274,123.967,11.187,123.964,11.137,123.95,11.079,123.925,11.041,123.93,10.964,123.832,10.731,123.726,10.562,123.711,10.474,123.593,10.303,123.514,10.14,123.386,9.967,123.404,9.889,123.327,9.578,123.316,9.489,123.332,9.423,123.37,9.45,123.494,9.589,123.634,9.922,123.643,10.02,123.7,10.128,123.789,10.221,123.874,10.258,123.952,10.317,124.005,10.4,124.051,10.586,124.028,10.768,124.053,10.926,124.053,11.029]},{"b":[123.708,11.151,123.816,11.283],"l":[123.757,11.283,123.741,11.279,123.708,11.248,123.737,11.151,123.816,11.151,123.757,11.283]},{"b":[123.817,9.599,124.594,10.16],"l":[124.486,10.065,124.406,10.126,124.373,10.13,124.352,10.141,124.336,10.16,124.173,10.135,124.094,10.061,124.06,10,123.909,9.92,123.864,9.879,123.817,9.817,123.83,9.761,123.872,9.676,123.936,9.624,124.122,9.599,124.36,9.63,124.403,9.654,124.478,9.748,124.506,9.754,124.584,9.75,124.594,9.787,124.582,9.83,124.555,9.879,124.577,10.027,124.486,10.065]},{"b":[124.639,9.083,124.807,9.243],"l":[124.737,9.243,124.708,9.243,124.653,9.226,124.639,9.175,124.666,9.132,124.778,9.083,124.807,9.143,124.79,9.19,124.737,9.243]},{"b":[123.493,9.103,123.706,9.279],"l":[123.698,9.237,123.655,9.279,123.626,9.268,123.535,9.214,123.494,9.216,123.493,9.192,123.541,9.13,123.614,9.103,123.706,9.134,123.698,9.237]},{"b":[119.821,5.06,120.25,5.34],"l":[120.208,5.34,120.165,5.332,120.08,5.264,119.983,5.228,119.827,5.133,119.821,5.07,119.878,5.06,119.958,5.08,120.013,5.151,120.101,5.169,120.118,5.215,120.15,5.184,120.192,5.168,120.223,5.196,120.25,5.257,120.229,5.284,120.208,5.34]},{"b":[121.809,6.415,122.324,6.741],"l":[122.058,6.741,121.915,6.676,121.832,6.664,121.809,6.614,121.872,6.563,121.88,6.518,121.959,6.416,121.991,6.415,122.093,6.428,122.201,6.483,122.252,6.58,122.324,6.602,122.288,6.639,122.058,6.741]},{"b":[125.991,9.761,126.173,10.059],"l":[126.129,9.944,126.074,10.059,125.999,9.927,125.991,9.839,126.047,9.761,126.059,9.766,126.137,9.768,126.173,9.8,126.121,9.865,126.129,9.891,126.129,9.944]},{"b":[125.495,9.886,125.703,10.44],"l":[125.703,10.072,125.647,10.245,125.685,10.392,125.667,10.44,125.648,10.437,125.606,10.38,125.58,10.364,125.525,10.31,125.522,10.192,125.495,10.119,125.534,10.09,125.591,9.998,125.649,9.944,125.673,9.886,125.69,9.914,125.692,9.939,125.684,9.963,125.703,10.072]},{"b":[120.099,13.683,120.273,13.858],"l":[120.211,13.821,120.121,13.858,120.103,13.843,120.099,13.817,120.104,13.782,120.273,13.683,120.271,13.751,120.211,13.821]},{"b":[121.815,13.205,122.122,13.566],"l":[121.996,13.547,121.977,13.537,121.915,13.54,121.866,13.566,121.815,13.424,121.829,13.329,121.876,13.282,122.005,13.205,122.042,13.236,122.055,13.269,122.122,13.365,122.107,13.421,122.115,13.463,121.996,13.547]},{"b":[121.923,12.106,122.145,12.653],"l":[122.002,12.599,121.989,12.435,121.941,12.385,121.923,12.331,121.936,12.29,121.982,12.245,121.96,12.191,122.014,12.106,122.094,12.355,122.132,12.538,122.13,12.613,122.145,12.653,122.104,12.651,122.002,12.599]},{"b":[122.248,12.498,122.311,12.593],"l":[122.248,12.557,122.261,12.503,122.28,12.498,122.311,12.529,122.288,12.589,122.278,12.593,122.248,12.557]},{"b":[122.423,12.286,122.683,12.492],"l":[122.604,12.492,122.472,12.492,122.423,12.455,122.439,12.429,122.499,12.384,122.603,12.286,122.654,12.309,122.683,12.382,122.674,12.424,122.604,12.492]},{"b":[122.949,12.701,123.367,13.116],"l":[123.044,13.113,123.017,13.116,122.958,13.107,122.949,13.059,122.973,13.035,123.054,12.993,123.166,12.876,123.274,12.805,123.367,12.701,123.282,12.853,123.206,12.905,123.044,13.113]},{"b":[123.587,12.366,123.779,12.675],"l":[123.775,12.454,123.709,12.611,123.621,12.675,123.587,12.633,123.621,12.571,123.742,12.399,123.779,12.366,123.775,12.454]},{"b":[123.156,11.752,124.056,12.583],"l":[123.211,12.107,123.156,11.968,123.158,11.926,123.293,12.036,123.419,12.194,123.474,12.217,123.531,12.197,123.612,12.09,123.668,12.069,123.675,12.05,123.736,12.003,123.725,11.952,123.754,11.934,123.848,11.914,123.983,11.819,124.046,11.752,124.056,11.812,124.04,11.967,123.908,12.169,123.717,12.287,123.575,12.407,123.559,12.445,123.463,12.501,123.337,12.542,123.236,12.583,123.24,12.495,123.267,12.395,123.245,12.328,123.211,12.107]},{"b":[124.039,13.532,124.417,14.078],"l":[124.337,13.931,124.308,13.947,124.225,14.078,124.186,14.06,124.154,14.026,124.123,13.98,124.124,13.79,124.039,13.663,124.057,13.606,124.175,13.532,124.248,13.587,124.295,13.59,124.327,13.567,124.354,13.632,124.404,13.679,124.396,13.75,124.417,13.871,124.337,13.931]},{"b":[121.946,14.008,122.175,14.229],"l":[121.959,14.229,121.946,14.205,121.946,14.181,121.956,14.156,122.172,14.008,122.175,14.049,121.959,14.229]},{"b":[121.82,14.656,122.052,15.046],"l":[121.84,15.038,121.82,14.964,121.862,14.917,121.889,14.84,121.923,14.8,121.935,14.737,121.922,14.715,121.911,14.667,121.933,14.656,121.99,14.662,122.022,14.759,121.97,14.893,122.017,14.965,122.032,14.972,122.052,14.97,122.033,15.005,121.972,15.046,121.84,15.038]},{"b":[121.825,18.823,121.988,19.01],"l":[121.889,18.992,121.86,18.937,121.861,18.913,121.825,18.843,121.858,18.823,121.922,18.895,121.988,18.957,121.943,19.01,121.889,18.992]},{"b":[121.185,19.015,121.252,19.184],"l":[121.196,19.051,121.247,19.015,121.252,19.082,121.245,19.143,121.213,19.184,121.19,19.139,121.185,19.101,121.196,19.051]},{"b":[121.375,19.271,121.531,19.399],"l":[121.392,19.399,121.376,19.38,121.375,19.356,121.383,19.328,121.472,19.273,121.531,19.271,121.521,19.362,121.392,19.399]},{"b":[121.914,20.354,122.031,20.48],"l":[121.914,20.359,121.941,20.354,121.96,20.366,122.031,20.469,121.991,20.48,121.941,20.454,121.914,20.359]},{"b":[121.791,20.7,121.878,20.841],"l":[121.796,20.747,121.791,20.701,121.83,20.7,121.878,20.782,121.867,20.839,121.848,20.841,121.796,20.747]},{"b":[147.781,-5.834,148.076,-5.491],"l":[147.846,-5.491,147.795,-5.492,147.783,-5.522,147.781,-5.627,147.875,-5.749,147.968,-5.789,147.985,-5.834,148.026,-5.826,148.06,-5.765,148.076,-5.65,148.055,-5.612,147.846,-5.491]},{"b":[147.006,-5.437,147.222,-5.191],"l":[147.131,-5.191,147.015,-5.257,147.006,-5.307,147.029,-5.342,147.12,-5.437,147.176,-5.432,147.222,-5.382,147.206,-5.252,147.131,-5.191]},{"b":[145.884,-4.756,146.053,-4.539],"l":[145.9,-4.604,145.884,-4.667,145.904,-4.733,145.952,-4.756,146.019,-4.726,146.053,-4.64,146.037,-4.573,145.996,-4.539,145.959,-4.554,145.9,-4.604]},{"b":[147.736,-2.336,147.877,-2.247],"l":[147.79,-2.306,147.736,-2.316,147.769,-2.331,147.845,-2.336,147.877,-2.283,147.836,-2.247,147.812,-2.262,147.79,-2.306]},{"b":[146.531,-2.21,147.444,-1.949],"l":[147.401,-2.025,147.068,-1.96,146.857,-1.949,146.76,-1.978,146.656,-1.974,146.596,-2.017,146.607,-2.103,146.532,-2.126,146.531,-2.154,146.546,-2.209,146.572,-2.21,146.635,-2.173,146.699,-2.183,146.748,-2.149,146.926,-2.189,147.064,-2.187,147.142,-2.167,147.206,-2.182,147.301,-2.09,147.337,-2.066,147.385,-2.071,147.438,-2.059,147.444,-2.012,147.424,-1.995,147.419,-2.001,147.423,-2.024,147.401,-2.025]},{"b":[154.54,-5.441,154.727,-5.014],"l":[154.682,-5.054,154.633,-5.014,154.606,-5.035,154.54,-5.111,154.563,-5.152,154.576,-5.221,154.584,-5.314,154.627,-5.441,154.647,-5.433,154.698,-5.383,154.727,-5.218,154.689,-5.143,154.682,-5.054]},{"b":[152.543,-3.221,152.671,-3.043],"l":[152.639,-3.043,152.57,-3.062,152.543,-3.096,152.585,-3.17,152.646,-3.221,152.671,-3.133,152.639,-3.043]},{"b":[151.955,-2.998,152.099,-2.846],"l":[151.975,-2.846,151.955,-2.871,151.971,-2.896,152.057,-2.995,152.088,-2.998,152.099,-2.947,152.075,-2.918,151.975,-2.846]},{"b":[151.93,-2.83,152.011,-2.709],"l":[152.002,-2.738,151.946,-2.709,151.93,-2.751,151.933,-2.83,151.957,-2.83,152.011,-2.809,152.002,-2.738]},{"b":[149.546,-1.589,149.765,-1.353],"l":[149.633,-1.362,149.581,-1.353,149.548,-1.408,149.546,-1.472,149.671,-1.576,149.691,-1.571,149.763,-1.589,149.765,-1.553,149.725,-1.431,149.633,-1.362]},{"b":[149.962,-2.675,150.452,-2.384],"l":[150.429,-2.47,150.227,-2.384,150.103,-2.405,149.962,-2.474,149.985,-2.492,150.043,-2.512,150.102,-2.603,150.166,-2.66,150.238,-2.675,150.437,-2.662,150.446,-2.632,150.452,-2.541,150.45,-2.513,150.429,-2.47]},{"b":[150.776,-10.195,151.296,-9.709],"l":[151.123,-10.02,151.081,-10.02,151.044,-9.983,151.051,-9.939,150.862,-9.802,150.817,-9.736,150.776,-9.709,150.79,-9.774,150.861,-9.876,150.896,-9.968,150.952,-9.998,150.959,-10.093,151.175,-10.159,151.231,-10.195,151.296,-9.957,151.256,-9.923,151.194,-9.946,151.123,-10.02]},{"b":[150.11,-9.519,150.368,-9.206],"l":[150.32,-9.264,150.208,-9.206,150.135,-9.26,150.11,-9.362,150.273,-9.5,150.331,-9.519,150.345,-9.494,150.368,-9.396,150.357,-9.349,150.32,-9.264]},{"b":[150.431,-9.703,150.899,-9.346],"l":[150.848,-9.663,150.844,-9.703,150.894,-9.667,150.899,-9.641,150.884,-9.582,150.879,-9.513,150.789,-9.418,150.746,-9.404,150.669,-9.429,150.528,-9.347,150.499,-9.346,150.437,-9.36,150.431,-9.387,150.435,-9.435,150.508,-9.536,150.495,-9.562,150.436,-9.625,150.576,-9.631,150.678,-9.657,150.81,-9.655,150.848,-9.663]},{"b":[151.005,-8.805,151.139,-8.419],"l":[151.139,-8.568,151.116,-8.522,151.118,-8.419,151.09,-8.426,151.046,-8.451,151.005,-8.524,151.083,-8.569,151.087,-8.595,151.081,-8.642,151.046,-8.728,151.124,-8.805,151.107,-8.733,151.139,-8.568]},{"b":[152.515,-9.224,152.995,-8.959],"l":[152.85,-9.025,152.81,-8.967,152.689,-8.975,152.631,-8.959,152.577,-8.97,152.515,-9.01,152.638,-9.058,152.708,-9.126,152.72,-9.167,152.759,-9.177,152.867,-9.224,152.923,-9.203,152.967,-9.209,152.959,-9.169,152.985,-9.151,152.995,-9.131,152.995,-9.108,152.953,-9.07,152.905,-9.044,152.85,-9.025]},{"b":[150.786,-10.643,150.899,-10.552],"l":[150.799,-10.554,150.786,-10.603,150.802,-10.62,150.885,-10.643,150.899,-10.565,150.872,-10.552,150.799,-10.554]},{"b":[154.023,-11.426,154.281,-11.311],"l":[154.102,-11.311,154.118,-11.366,154.023,-11.348,154.031,-11.371,154.064,-11.419,154.121,-11.426,154.23,-11.397,154.266,-11.416,154.281,-11.361,154.238,-11.339,154.102,-11.311]},{"b":[153.204,-11.631,153.76,-11.324],"l":[153.703,-11.529,153.536,-11.476,153.307,-11.356,153.204,-11.324,153.207,-11.352,153.234,-11.42,153.322,-11.471,153.287,-11.517,153.357,-11.495,153.379,-11.56,153.519,-11.595,153.554,-11.631,153.7,-11.613,153.76,-11.586,153.703,-11.529]},{"b":[143.322,-8.485,143.593,-8.368],"l":[143.543,-8.485,143.587,-8.482,143.593,-8.46,143.581,-8.391,143.528,-8.379,143.322,-8.368,143.367,-8.417,143.543,-8.485]},{"b":[143.207,-8.677,143.608,-8.423],"l":[143.443,-8.519,143.293,-8.473,143.207,-8.423,143.254,-8.49,143.324,-8.517,143.463,-8.617,143.608,-8.677,143.59,-8.633,143.443,-8.519]},{"b":[135.383,-1.217,136.375,-0.651],"l":[136.283,-1.065,136.155,-0.978,136.069,-0.878,135.894,-0.726,135.841,-0.712,135.749,-0.733,135.673,-0.688,135.596,-0.69,135.383,-0.651,135.388,-0.705,135.432,-0.769,135.483,-0.801,135.491,-0.785,135.524,-0.787,135.646,-0.882,135.747,-0.823,135.826,-1.028,135.839,-1.119,135.915,-1.178,136.003,-1.17,136.11,-1.217,136.165,-1.215,136.305,-1.173,136.375,-1.094,136.283,-1.065]},{"b":[135.47,-1.894,136.893,-1.592],"l":[136.719,-1.734,136.39,-1.722,136.202,-1.655,135.976,-1.636,135.869,-1.642,135.474,-1.592,135.47,-1.616,135.488,-1.668,135.866,-1.752,136.049,-1.824,136.193,-1.859,136.228,-1.894,136.326,-1.872,136.461,-1.89,136.622,-1.873,136.709,-1.838,136.893,-1.8,136.817,-1.754,136.719,-1.734]},{"b":[134.809,-1.134,134.996,-0.938],"l":[134.957,-1.031,134.941,-0.979,134.889,-0.938,134.828,-0.979,134.809,-1.038,134.862,-1.114,134.917,-1.134,134.965,-1.116,134.996,-1.034,134.957,-1.031]},{"b":[134.335,-2.139,134.419,-2.028],"l":[134.351,-2.037,134.335,-2.095,134.345,-2.139,134.374,-2.124,134.419,-2.052,134.391,-2.031,134.37,-2.028,134.351,-2.037]},{"b":[138.563,-8.402,138.898,-8.173],"l":[138.796,-8.174,138.763,-8.173,138.677,-8.199,138.621,-8.268,138.563,-8.309,138.567,-8.33,138.594,-8.371,138.846,-8.402,138.895,-8.389,138.898,-8.337,138.796,-8.174]},{"b":[133.321,-4.299,133.622,-4.111],"l":[133.464,-4.2,133.321,-4.111,133.333,-4.17,133.503,-4.257,133.622,-4.299,133.571,-4.246,133.464,-4.2]},{"b":[129.738,-2.064,130.425,-1.691],"l":[130.425,-1.805,130.365,-1.75,130.353,-1.691,130.318,-1.692,130.2,-1.732,130.106,-1.73,129.994,-1.759,129.738,-1.867,129.754,-1.894,129.887,-1.986,130.093,-2.028,130.133,-2.064,130.248,-2.048,130.284,-2.009,130.339,-1.982,130.373,-1.992,130.419,-1.971,130.393,-1.942,130.381,-1.903,130.404,-1.89,130.425,-1.805]},{"b":[130.668,-1.343,131.074,-0.89],"l":[131.033,-0.918,130.939,-0.915,130.897,-0.89,130.673,-0.96,130.668,-0.984,130.704,-1.05,130.712,-1.104,130.739,-1.173,130.782,-1.255,130.845,-1.317,130.967,-1.343,131.002,-1.316,131.046,-1.188,131.074,-0.968,131.033,-0.918]},{"b":[130.402,-0.924,130.905,-0.765],"l":[130.807,-0.765,130.813,-0.814,130.723,-0.822,130.635,-0.812,130.594,-0.827,130.57,-0.822,130.548,-0.826,130.527,-0.837,130.484,-0.833,130.457,-0.851,130.439,-0.887,130.402,-0.924,130.832,-0.863,130.88,-0.828,130.905,-0.777,130.807,-0.765]},{"b":[130.465,-0.53,130.684,-0.417],"l":[130.616,-0.417,130.597,-0.418,130.564,-0.441,130.526,-0.449,130.465,-0.487,130.569,-0.53,130.627,-0.529,130.684,-0.469,130.657,-0.437,130.616,-0.417]},{"b":[130.237,-0.444,131.34,-0.004],"l":[131.277,-0.15,131.026,-0.04,130.987,-0.047,130.813,-0.004,130.722,-0.03,130.584,-0.045,130.548,-0.07,130.5,-0.06,130.431,-0.098,130.363,-0.073,130.295,-0.101,130.288,-0.155,130.237,-0.21,130.341,-0.262,130.496,-0.267,130.551,-0.366,130.575,-0.362,130.607,-0.329,130.689,-0.297,130.7,-0.392,130.75,-0.444,130.896,-0.416,130.899,-0.344,130.843,-0.298,130.802,-0.302,130.761,-0.291,130.691,-0.181,130.638,-0.143,130.622,-0.086,130.683,-0.081,130.808,-0.226,130.897,-0.268,130.946,-0.338,131.005,-0.361,131.098,-0.33,131.178,-0.346,131.218,-0.374,131.258,-0.366,131.34,-0.29,131.303,-0.241,131.317,-0.204,131.277,-0.15]},{"b":[129.309,-0.19,129.549,0.045],"l":[129.309,0.045,129.37,-0.066,129.469,-0.131,129.506,-0.19,129.549,-0.187,129.542,-0.139,129.309,0.045]},{"b":[128.218,2.035,128.688,2.598],"l":[128.602,2.598,128.569,2.596,128.472,2.571,128.33,2.469,128.218,2.297,128.26,2.083,128.296,2.035,128.454,2.052,128.548,2.097,128.623,2.224,128.688,2.474,128.602,2.598]},{"b":[127.397,-0.037,127.453,0.143],"l":[127.431,0.143,127.42,0.124,127.397,0.017,127.418,0.006,127.449,-0.037,127.453,-0.006,127.449,0.069,127.431,0.143]},{"b":[127.363,0.631,127.446,0.744],"l":[127.42,0.642,127.446,0.683,127.443,0.733,127.425,0.744,127.383,0.744,127.363,0.675,127.374,0.635,127.384,0.631,127.42,0.642]},{"b":[127.104,-0.521,127.29,-0.256],"l":[127.281,-0.391,127.253,-0.319,127.29,-0.284,127.19,-0.256,127.126,-0.279,127.104,-0.414,127.119,-0.521,127.187,-0.521,127.25,-0.495,127.281,-0.391]},{"b":[127.156,-0.802,127.301,-0.619],"l":[127.258,-0.623,127.209,-0.619,127.156,-0.761,127.185,-0.775,127.289,-0.802,127.3,-0.781,127.301,-0.758,127.258,-0.623]},{"b":[127.297,-0.884,127.88,-0.307],"l":[127.804,-0.694,127.659,-0.689,127.605,-0.61,127.682,-0.468,127.567,-0.319,127.527,-0.307,127.492,-0.336,127.455,-0.406,127.371,-0.332,127.325,-0.336,127.329,-0.391,127.297,-0.46,127.3,-0.5,127.381,-0.6,127.469,-0.643,127.438,-0.739,127.463,-0.806,127.498,-0.802,127.624,-0.766,127.643,-0.784,127.668,-0.832,127.761,-0.884,127.842,-0.848,127.88,-0.809,127.863,-0.76,127.838,-0.724,127.804,-0.694]},{"b":[127.392,-1.729,128.153,-1.332],"l":[127.905,-1.439,127.743,-1.36,127.647,-1.332,127.592,-1.351,127.457,-1.454,127.395,-1.59,127.392,-1.645,127.458,-1.697,127.562,-1.729,127.741,-1.691,127.914,-1.685,128.061,-1.712,128.092,-1.701,128.153,-1.661,128.149,-1.604,128.033,-1.532,127.905,-1.439]},{"b":[134.153,-6.365,134.758,-5.427],"l":[134.744,-6.202,134.756,-6.171,134.758,-6.1,134.752,-6.05,134.712,-5.95,134.755,-5.883,134.738,-5.817,134.739,-5.746,134.747,-5.707,134.701,-5.603,134.646,-5.581,134.658,-5.539,134.646,-5.492,134.617,-5.439,134.571,-5.427,134.506,-5.438,134.49,-5.525,134.456,-5.558,134.341,-5.713,134.247,-5.682,134.205,-5.707,134.226,-5.744,134.343,-5.833,134.299,-5.971,134.302,-6.01,134.225,-6.008,134.153,-6.02,134.155,-6.063,134.175,-6.09,134.264,-6.172,134.28,-6.201,134.356,-6.271,134.441,-6.335,134.638,-6.365,134.661,-6.337,134.684,-6.328,134.714,-6.295,134.744,-6.202]},{"b":[134.059,-6.909,134.537,-6.176],"l":[134.52,-6.513,134.537,-6.442,134.415,-6.387,134.318,-6.316,134.234,-6.226,134.168,-6.176,134.115,-6.191,134.111,-6.255,134.125,-6.426,134.195,-6.46,134.185,-6.479,134.154,-6.481,134.107,-6.472,134.059,-6.769,134.091,-6.834,134.2,-6.909,134.323,-6.849,134.356,-6.815,134.413,-6.68,134.504,-6.591,134.52,-6.513]},{"b":[132.845,-5.988,133.173,-5.311],"l":[133.009,-5.621,132.938,-5.683,132.922,-5.785,132.845,-5.988,132.926,-5.902,132.971,-5.736,133.12,-5.576,133.173,-5.348,133.138,-5.318,133.115,-5.311,133.009,-5.621]},{"b":[132.63,-5.947,132.807,-5.607],"l":[132.738,-5.662,132.717,-5.648,132.698,-5.609,132.63,-5.607,132.681,-5.739,132.667,-5.856,132.681,-5.913,132.705,-5.913,132.746,-5.947,132.807,-5.851,132.804,-5.789,132.738,-5.662]},{"b":[128.585,-3.603,128.722,-3.495],"l":[128.667,-3.517,128.595,-3.495,128.585,-3.512,128.62,-3.589,128.659,-3.588,128.713,-3.603,128.72,-3.589,128.722,-3.547,128.694,-3.525,128.667,-3.517]},{"b":[128.392,-3.638,128.563,-3.515],"l":[128.452,-3.515,128.428,-3.54,128.392,-3.638,128.563,-3.585,128.536,-3.541,128.452,-3.515]},{"b":[127.925,-3.771,128.329,-3.512],"l":[128.264,-3.512,128.119,-3.587,128.016,-3.601,127.928,-3.679,127.925,-3.699,127.934,-3.743,127.978,-3.771,128.052,-3.715,128.111,-3.686,128.147,-3.677,128.159,-3.698,128.143,-3.733,128.192,-3.735,128.25,-3.711,128.276,-3.675,128.277,-3.633,128.291,-3.598,128.314,-3.564,128.329,-3.516,128.264,-3.512]},{"b":[122.585,-5.671,123.203,-4.387],"l":[123.051,-5.156,123.025,-5.162,122.981,-5.186,122.972,-5.138,122.987,-4.963,123.015,-4.91,123.018,-4.832,123.055,-4.748,123.084,-4.749,123.104,-4.74,123.119,-4.723,123.139,-4.74,123.196,-4.823,123.203,-4.766,123.18,-4.551,123.069,-4.434,123.075,-4.387,123.038,-4.395,122.947,-4.443,122.853,-4.618,122.849,-4.831,122.821,-4.944,122.804,-5,122.794,-5.052,122.768,-5.177,122.767,-5.21,122.731,-5.262,122.67,-5.331,122.643,-5.381,122.642,-5.426,122.586,-5.489,122.585,-5.545,122.645,-5.663,122.684,-5.666,122.733,-5.635,122.812,-5.671,122.85,-5.638,122.916,-5.519,122.909,-5.477,122.935,-5.437,122.969,-5.406,122.986,-5.394,123.043,-5.419,123.121,-5.393,123.187,-5.333,123.202,-5.273,123.15,-5.224,123.051,-5.156]},{"b":[122.283,-5.391,122.76,-4.619],"l":[122.74,-4.675,122.702,-4.619,122.66,-4.634,122.524,-4.707,122.369,-4.767,122.334,-4.847,122.39,-4.999,122.396,-5.07,122.329,-5.138,122.283,-5.32,122.307,-5.381,122.371,-5.383,122.392,-5.335,122.474,-5.381,122.52,-5.391,122.564,-5.387,122.619,-5.336,122.645,-5.269,122.614,-5.139,122.76,-4.934,122.74,-4.675]},{"b":[121.808,-5.465,122.062,-5.072],"l":[122.041,-5.159,122,-5.141,121.966,-5.076,121.914,-5.072,121.866,-5.096,121.874,-5.145,121.857,-5.156,121.821,-5.203,121.808,-5.256,121.859,-5.35,121.98,-5.465,122.043,-5.438,122.062,-5.221,122.041,-5.159]},{"b":[122.969,-4.233,123.247,-3.981],"l":[122.969,-4.03,122.971,-4.061,122.995,-4.148,123.076,-4.227,123.145,-4.233,123.242,-4.113,123.247,-4.041,123.212,-3.998,123.025,-3.981,122.969,-4.03]},{"b":[125.863,-2.469,126.066,-1.975],"l":[125.976,-2.168,125.993,-2.012,125.963,-1.976,125.923,-1.975,125.873,-2.036,125.863,-2.077,125.903,-2.222,125.938,-2.263,125.978,-2.415,126.038,-2.469,126.055,-2.451,126.066,-2.366,125.976,-2.168]},{"b":[125.387,-1.94,126.332,-1.79],"l":[126.024,-1.79,125.72,-1.814,125.521,-1.801,125.445,-1.809,125.387,-1.843,125.426,-1.882,125.433,-1.938,125.479,-1.94,125.839,-1.906,125.956,-1.917,126.288,-1.859,126.332,-1.823,126.024,-1.79]},{"b":[124.33,-2.007,125.32,-1.636],"l":[125.188,-1.713,125.146,-1.693,125.127,-1.699,125.096,-1.741,125.063,-1.741,124.97,-1.705,124.664,-1.636,124.483,-1.644,124.418,-1.659,124.381,-1.687,124.33,-1.859,124.418,-2.005,124.521,-2.007,124.639,-1.978,124.834,-1.894,125.007,-1.943,125.135,-1.889,125.314,-1.877,125.32,-1.81,125.305,-1.794,125.258,-1.771,125.198,-1.78,125.188,-1.713]},{"b":[123.777,-1.996,123.866,-1.878],"l":[123.783,-1.878,123.777,-1.919,123.804,-1.994,123.866,-1.996,123.848,-1.955,123.783,-1.878]},{"b":[122.811,-1.616,123.547,-1.158],"l":[123.435,-1.237,123.339,-1.254,123.238,-1.389,123.198,-1.288,123.234,-1.234,123.212,-1.171,123.158,-1.158,122.972,-1.189,122.908,-1.182,122.832,-1.283,122.811,-1.432,122.858,-1.548,122.89,-1.587,122.984,-1.511,123.105,-1.34,123.15,-1.304,123.183,-1.493,123.123,-1.556,123.13,-1.577,123.173,-1.616,123.221,-1.598,123.237,-1.577,123.275,-1.437,123.329,-1.443,123.367,-1.507,123.449,-1.499,123.512,-1.447,123.547,-1.337,123.527,-1.286,123.489,-1.259,123.435,-1.237]},{"b":[123.071,-1.899,123.153,-1.773],"l":[123.071,-1.855,123.079,-1.899,123.153,-1.817,123.138,-1.773,123.106,-1.787,123.086,-1.815,123.071,-1.855]},{"b":[121.655,-0.526,121.907,-0.407],"l":[121.797,-0.418,121.749,-0.407,121.672,-0.479,121.655,-0.526,121.681,-0.525,121.722,-0.495,121.756,-0.491,121.847,-0.49,121.881,-0.503,121.907,-0.451,121.864,-0.407,121.797,-0.418]},{"b":[126.704,4.013,126.921,4.548],"l":[126.865,4.48,126.813,4.537,126.757,4.548,126.721,4.416,126.722,4.344,126.767,4.283,126.814,4.258,126.77,4.162,126.704,4.071,126.711,4.02,126.776,4.013,126.817,4.033,126.848,4.18,126.921,4.291,126.887,4.373,126.865,4.48]},{"b":[126.638,3.833,126.74,4.042],"l":[126.638,4.042,126.661,3.928,126.722,3.833,126.719,3.875,126.74,3.918,126.686,4.001,126.638,4.042]},{"b":[126.778,3.757,126.858,3.858],"l":[126.8,3.784,126.836,3.757,126.852,3.768,126.858,3.787,126.857,3.812,126.804,3.858,126.779,3.843,126.778,3.813,126.8,3.784]},{"b":[125.455,3.405,125.658,3.733],"l":[125.586,3.571,125.543,3.67,125.469,3.733,125.455,3.684,125.469,3.639,125.501,3.593,125.518,3.55,125.512,3.461,125.633,3.405,125.658,3.436,125.644,3.477,125.586,3.571]},{"b":[125.36,2.63,125.446,2.805],"l":[125.391,2.805,125.36,2.747,125.397,2.63,125.407,2.652,125.404,2.707,125.446,2.763,125.435,2.784,125.391,2.805]},{"b":[120.436,-6.465,120.549,-5.775],"l":[120.477,-5.775,120.446,-5.876,120.452,-6.095,120.436,-6.18,120.461,-6.254,120.468,-6.406,120.487,-6.465,120.528,-6.298,120.549,-5.969,120.534,-5.904,120.477,-5.775]},{"b":[131.751,-7.251,131.982,-7.104],"l":[131.922,-7.104,131.751,-7.117,131.778,-7.144,131.823,-7.159,131.884,-7.167,131.927,-7.225,131.97,-7.251,131.982,-7.202,131.922,-7.104]},{"b":[130.775,-8.35,131.176,-8.091],"l":[131.02,-8.091,130.833,-8.271,130.775,-8.35,130.862,-8.319,130.908,-8.246,131.044,-8.212,131.176,-8.131,131.087,-8.125,131.02,-8.091]},{"b":[131.087,-8.011,131.736,-7.113],"l":[131.701,-7.14,131.643,-7.113,131.561,-7.136,131.531,-7.165,131.535,-7.221,131.483,-7.251,131.446,-7.315,131.411,-7.34,131.349,-7.425,131.297,-7.438,131.26,-7.471,131.197,-7.617,131.19,-7.672,131.138,-7.685,131.137,-7.782,131.087,-7.865,131.123,-7.922,131.114,-7.997,131.185,-7.998,131.309,-8.011,131.326,-8,131.343,-7.981,131.348,-7.948,131.377,-7.869,131.474,-7.777,131.498,-7.731,131.58,-7.682,131.624,-7.626,131.691,-7.439,131.644,-7.267,131.736,-7.197,131.701,-7.14]},{"b":[129.592,-8.046,129.844,-7.795],"l":[129.655,-7.795,129.609,-7.803,129.599,-7.831,129.592,-7.917,129.713,-8.041,129.78,-8.046,129.839,-7.955,129.844,-7.889,129.813,-7.82,129.655,-7.795]},{"b":[128.53,-7.209,128.673,-7.069],"l":[128.628,-7.069,128.577,-7.083,128.53,-7.135,128.55,-7.156,128.625,-7.209,128.67,-7.183,128.667,-7.138,128.673,-7.113,128.658,-7.091,128.628,-7.069]},{"b":[127.786,-8.255,128.119,-8.099],"l":[127.998,-8.139,127.823,-8.099,127.786,-8.12,127.821,-8.19,128.024,-8.255,128.119,-8.171,128.099,-8.135,127.998,-8.139]},{"b":[127.355,-7.646,127.475,-7.513],"l":[127.371,-7.513,127.375,-7.572,127.355,-7.646,127.419,-7.623,127.464,-7.597,127.474,-7.579,127.475,-7.531,127.371,-7.513]},{"b":[125.798,-7.985,126.814,-7.572],"l":[126.726,-7.662,126.61,-7.572,126.463,-7.608,126.359,-7.677,126.214,-7.707,126.085,-7.697,125.975,-7.663,125.843,-7.817,125.808,-7.881,125.798,-7.985,125.826,-7.979,125.952,-7.911,126.04,-7.886,126.108,-7.884,126.171,-7.912,126.313,-7.918,126.472,-7.95,126.518,-7.87,126.577,-7.808,126.693,-7.754,126.813,-7.738,126.814,-7.717,126.801,-7.668,126.726,-7.662]},{"b":[125.507,-8.312,125.646,-8.14],"l":[125.507,-8.275,125.579,-8.312,125.646,-8.14,125.621,-8.15,125.584,-8.179,125.507,-8.275]},{"b":[123.326,-10.337,123.497,-10.14],"l":[123.395,-10.171,123.326,-10.264,123.326,-10.337,123.416,-10.303,123.405,-10.227,123.497,-10.194,123.494,-10.177,123.459,-10.14,123.395,-10.171]},{"b":[122.818,-10.91,123.418,-10.472],"l":[123.34,-10.486,123.265,-10.518,123.146,-10.64,123.061,-10.698,122.846,-10.762,122.818,-10.811,122.826,-10.899,122.856,-10.91,122.949,-10.909,123.005,-10.876,123.215,-10.806,123.311,-10.698,123.418,-10.651,123.413,-10.623,123.383,-10.568,123.371,-10.475,123.358,-10.472,123.34,-10.486]},{"b":[121.705,-10.602,121.998,-10.433],"l":[121.796,-10.507,121.705,-10.556,121.726,-10.573,121.833,-10.602,121.883,-10.59,121.981,-10.528,121.998,-10.447,121.95,-10.433,121.867,-10.439,121.796,-10.507]},{"b":[124.356,-8.445,125.132,-8.135],"l":[124.752,-8.16,124.677,-8.168,124.6,-8.202,124.576,-8.141,124.509,-8.135,124.431,-8.183,124.394,-8.253,124.426,-8.296,124.356,-8.386,124.381,-8.415,124.444,-8.445,125.097,-8.353,125.132,-8.326,125.125,-8.205,125.05,-8.18,124.924,-8.166,124.752,-8.16]},{"b":[123.928,-8.552,124.304,-8.202],"l":[124.24,-8.203,124.111,-8.364,124.096,-8.356,124.069,-8.318,124.014,-8.319,123.971,-8.354,123.928,-8.449,124.017,-8.444,124.066,-8.552,124.147,-8.531,124.184,-8.499,124.226,-8.391,124.287,-8.329,124.304,-8.229,124.287,-8.209,124.266,-8.202,124.24,-8.203]},{"b":[123.23,-8.587,123.925,-8.19],"l":[123.776,-8.19,123.601,-8.291,123.573,-8.292,123.53,-8.265,123.473,-8.267,123.391,-8.28,123.395,-8.301,123.425,-8.313,123.476,-8.322,123.455,-8.354,123.325,-8.439,123.23,-8.531,123.253,-8.539,123.33,-8.536,123.411,-8.587,123.434,-8.576,123.489,-8.532,123.553,-8.567,123.58,-8.545,123.588,-8.524,123.583,-8.502,123.592,-8.478,123.629,-8.422,123.698,-8.424,123.784,-8.3,123.925,-8.272,123.896,-8.239,123.846,-8.213,123.776,-8.19]},{"b":[123.025,-8.399,123.336,-8.235],"l":[123.217,-8.235,123.133,-8.254,123.108,-8.275,123.033,-8.338,123.025,-8.396,123.297,-8.399,123.317,-8.355,123.336,-8.269,123.217,-8.235]},{"b":[119.379,-8.75,119.557,-8.429],"l":[119.471,-8.456,119.446,-8.429,119.43,-8.455,119.42,-8.539,119.379,-8.587,119.402,-8.647,119.386,-8.736,119.425,-8.75,119.464,-8.741,119.444,-8.672,119.483,-8.628,119.536,-8.589,119.555,-8.553,119.557,-8.519,119.547,-8.483,119.502,-8.481,119.482,-8.473,119.471,-8.456]},{"b":[119.021,-8.24,119.135,-8.139],"l":[119.037,-8.158,119.021,-8.2,119.03,-8.24,119.074,-8.239,119.107,-8.223,119.135,-8.197,119.128,-8.177,119.098,-8.139,119.079,-8.14,119.037,-8.158]},{"b":[115.48,-8.804,115.613,-8.67],"l":[115.48,-8.715,115.501,-8.743,115.582,-8.804,115.61,-8.77,115.613,-8.713,115.561,-8.67,115.541,-8.675,115.48,-8.715]},{"b":[115.22,-6.988,115.546,-6.838],"l":[115.414,-6.84,115.354,-6.838,115.241,-6.861,115.222,-6.905,115.22,-6.953,115.296,-6.988,115.377,-6.971,115.424,-6.941,115.546,-6.939,115.524,-6.902,115.479,-6.87,115.414,-6.84]},{"b":[112.586,-5.846,112.727,-5.726],"l":[112.586,-5.804,112.602,-5.844,112.698,-5.846,112.719,-5.811,112.727,-5.753,112.69,-5.726,112.649,-5.731,112.586,-5.804]},{"b":[117.625,4.004,117.923,4.262],"l":[117.708,4.262,117.662,4.25,117.667,4.204,117.625,4.121,117.737,4.004,117.923,4.054,117.918,4.091,117.885,4.186,117.761,4.252,117.708,4.262]},{"b":[117.538,3.248,117.681,3.436],"l":[117.548,3.432,117.538,3.386,117.56,3.328,117.646,3.248,117.658,3.281,117.681,3.408,117.637,3.436,117.548,3.432]},{"b":[116.327,-3.637,116.427,-3.4],"l":[116.327,-3.539,116.388,-3.637,116.424,-3.464,116.427,-3.4,116.395,-3.423,116.327,-3.539]},{"b":[116.018,-4.054,116.319,-3.251],"l":[116.282,-3.535,116.295,-3.495,116.287,-3.449,116.262,-3.395,116.27,-3.251,116.239,-3.26,116.117,-3.34,116.064,-3.458,116.022,-3.612,116.018,-3.7,116.077,-3.817,116.059,-4.007,116.093,-4.054,116.303,-3.868,116.289,-3.821,116.319,-3.763,116.305,-3.719,116.282,-3.535]},{"b":[108.804,-1.662,108.957,-1.54],"l":[108.877,-1.54,108.804,-1.568,108.838,-1.662,108.953,-1.62,108.957,-1.564,108.877,-1.54]},{"b":[109.428,-1.283,109.761,-0.979],"l":[109.7,-1.007,109.615,-0.979,109.476,-0.985,109.45,-1.044,109.428,-1.241,109.464,-1.278,109.511,-1.283,109.71,-1.181,109.751,-1.145,109.761,-1.105,109.743,-1.039,109.7,-1.007]},{"b":[108.787,2.853,108.888,2.999],"l":[108.787,2.886,108.839,2.853,108.888,2.905,108.886,2.999,108.867,2.992,108.787,2.886]},{"b":[108.002,3.653,108.399,4.217],"l":[108.393,3.986,108.256,4.152,108.248,4.217,108.202,4.2,108.004,4.043,108.002,3.983,108.045,3.889,108.088,3.852,108.243,3.81,108.236,3.785,108.216,3.772,108.186,3.768,108.1,3.705,108.18,3.653,108.316,3.69,108.394,3.836,108.399,3.876,108.393,3.986]},{"b":[106.201,3.088,106.285,3.23],"l":[106.215,3.129,106.284,3.088,106.285,3.157,106.271,3.216,106.224,3.23,106.201,3.205,106.215,3.129]},{"b":[105.692,2.859,105.837,3.063],"l":[105.731,3.037,105.692,3.063,105.692,3.011,105.704,2.981,105.708,2.94,105.706,2.889,105.719,2.859,105.76,2.863,105.809,2.904,105.837,2.977,105.822,2.984,105.795,2.996,105.76,3.013,105.731,3.037]},{"b":[101.403,1.716,101.774,2.127],"l":[101.641,2.127,101.545,2.061,101.45,2.068,101.41,2.022,101.403,1.901,101.468,1.759,101.501,1.733,101.603,1.716,101.719,1.789,101.734,1.883,101.774,1.943,101.762,1.997,101.708,2.078,101.641,2.127]},{"b":[102.229,0.96,102.449,1.397],"l":[102.276,1.395,102.256,1.397,102.229,1.348,102.234,1.264,102.255,1.147,102.28,1.076,102.325,1.007,102.381,0.96,102.427,0.99,102.429,1.067,102.449,1.156,102.443,1.234,102.413,1.261,102.359,1.346,102.276,1.395]},{"b":[102.018,1.331,102.499,1.625],"l":[102.492,1.459,102.47,1.51,102.042,1.625,102.024,1.608,102.018,1.586,102.021,1.558,102.079,1.499,102.161,1.465,102.274,1.453,102.367,1.415,102.425,1.364,102.499,1.331,102.492,1.459]},{"b":[102.454,0.708,103.028,1.13],"l":[102.78,0.959,102.726,0.989,102.633,1.054,102.549,1.13,102.507,1.089,102.491,0.987,102.466,0.95,102.454,0.89,102.49,0.857,102.542,0.832,102.711,0.784,102.776,0.78,102.971,0.737,103.009,0.708,103.028,0.747,103.002,0.859,102.944,0.893,102.78,0.959]},{"b":[102.702,0.842,103.166,1.165],"l":[103.068,1.015,102.999,1.068,102.79,1.165,102.726,1.159,102.702,1.054,102.726,1.041,102.788,1.031,102.886,0.997,102.964,0.943,103.033,0.882,103.087,0.848,103.137,0.842,103.166,0.87,103.068,1.015]},{"b":[103.14,0.536,103.295,0.7],"l":[103.238,0.699,103.187,0.7,103.153,0.643,103.14,0.549,103.172,0.536,103.284,0.542,103.295,0.614,103.238,0.699]},{"b":[103.315,0.993,103.43,1.134],"l":[103.38,1.134,103.355,1.117,103.315,1.071,103.363,1.007,103.43,0.993,103.424,1.048,103.405,1.073,103.38,1.134]},{"b":[103.344,0.651,103.497,0.87],"l":[103.386,0.87,103.366,0.851,103.344,0.778,103.43,0.651,103.45,0.664,103.497,0.723,103.47,0.778,103.433,0.825,103.386,0.87]},{"b":[103.74,0.772,103.834,0.891],"l":[103.752,0.891,103.74,0.872,103.742,0.83,103.834,0.772,103.829,0.801,103.807,0.846,103.752,0.891]},{"b":[103.932,0.99,104.14,1.181],"l":[104.025,1.181,104,1.137,103.955,1.137,103.947,1.087,103.932,1.071,103.94,1.046,103.964,1.013,104.066,0.99,104.127,1.092,104.138,1.128,104.14,1.166,104.088,1.137,104.025,1.181]},{"b":[104.244,0.832,104.663,1.216],"l":[104.591,1.141,104.585,1.216,104.5,1.18,104.428,1.196,104.362,1.181,104.25,1.103,104.244,1.077,104.252,1.015,104.294,1.016,104.439,1.05,104.462,0.996,104.429,0.956,104.481,0.933,104.471,0.913,104.481,0.887,104.504,0.853,104.575,0.832,104.599,0.859,104.653,0.961,104.663,1.05,104.648,1.105,104.591,1.141]},{"b":[104.098,0.805,104.239,0.944],"l":[104.171,0.897,104.123,0.944,104.108,0.934,104.101,0.917,104.098,0.896,104.177,0.805,104.239,0.834,104.227,0.88,104.171,0.897]},{"b":[104.499,0.035,104.698,0.232],"l":[104.544,0.223,104.499,0.232,104.604,0.095,104.622,0.08,104.651,0.063,104.698,0.035,104.689,0.06,104.66,0.103,104.544,0.223]},{"b":[104.447,-0.323,105.005,0.018],"l":[104.843,-0.141,104.808,-0.192,104.779,-0.176,104.713,-0.103,104.653,-0.076,104.658,-0.063,104.636,-0.018,104.543,0.018,104.497,-0.126,104.447,-0.189,104.474,-0.212,104.567,-0.246,104.702,-0.209,104.914,-0.323,104.929,-0.317,104.951,-0.284,105.005,-0.283,104.95,-0.247,104.909,-0.212,104.843,-0.141]},{"b":[104.257,-0.659,104.59,-0.335],"l":[104.364,-0.403,104.341,-0.383,104.319,-0.38,104.302,-0.386,104.257,-0.463,104.33,-0.539,104.363,-0.659,104.414,-0.584,104.485,-0.613,104.507,-0.597,104.544,-0.521,104.59,-0.467,104.568,-0.432,104.474,-0.335,104.364,-0.403]},{"b":[103.461,-0.383,103.764,-0.228],"l":[103.611,-0.231,103.549,-0.228,103.479,-0.297,103.461,-0.358,103.606,-0.383,103.737,-0.348,103.764,-0.318,103.724,-0.277,103.611,-0.231]},{"b":[107.563,-3.227,108.291,-2.53],"l":[108.215,-2.697,108.074,-2.597,107.875,-2.56,107.838,-2.53,107.666,-2.566,107.642,-2.732,107.598,-2.8,107.605,-2.863,107.563,-2.92,107.584,-2.941,107.592,-2.977,107.595,-3.058,107.637,-3.125,107.614,-3.209,107.66,-3.206,107.822,-3.161,107.837,-3.097,107.858,-3.086,107.941,-3.129,107.967,-3.167,107.977,-3.222,108.055,-3.227,108.084,-3.195,108.167,-3.143,108.192,-3.103,108.207,-2.998,108.291,-2.83,108.215,-2.697]},{"b":[107.402,-2.925,107.5,-2.835],"l":[107.433,-2.925,107.473,-2.9,107.5,-2.845,107.474,-2.835,107.419,-2.838,107.402,-2.873,107.409,-2.901,107.433,-2.925]},{"b":[106.743,-3.025,106.911,-2.899],"l":[106.797,-2.899,106.743,-2.933,106.749,-2.96,106.774,-2.987,106.814,-3.014,106.87,-3.025,106.886,-3.005,106.911,-2.934,106.797,-2.899]},{"b":[105.121,-6.662,105.277,-6.524],"l":[105.121,-6.615,105.143,-6.643,105.19,-6.662,105.253,-6.64,105.277,-6.561,105.261,-6.524,105.226,-6.529,105.192,-6.546,105.121,-6.615]},{"b":[97.079,0.564,97.932,1.54],"l":[97.786,1.146,97.698,1.184,97.482,1.465,97.356,1.54,97.343,1.528,97.324,1.482,97.244,1.424,97.079,1.425,97.297,1.187,97.369,1.057,97.405,0.947,97.461,0.941,97.604,0.834,97.683,0.641,97.684,0.596,97.82,0.564,97.876,0.628,97.902,0.884,97.932,0.974,97.903,1.018,97.786,1.146]},{"b":[102.111,-5.483,102.405,-5.286],"l":[102.372,-5.366,102.198,-5.289,102.154,-5.286,102.111,-5.323,102.136,-5.361,102.286,-5.483,102.367,-5.479,102.405,-5.405,102.372,-5.366]},{"b":[100.179,-3.329,100.469,-2.783],"l":[100.464,-3.117,100.469,-3.039,100.455,-3.002,100.246,-2.783,100.199,-2.786,100.179,-2.82,100.204,-2.987,100.26,-3.057,100.332,-3.113,100.348,-3.159,100.346,-3.229,100.465,-3.329,100.425,-3.183,100.434,-3.141,100.464,-3.117]},{"b":[99.968,-2.821,100.204,-2.51],"l":[100.012,-2.51,99.988,-2.525,99.969,-2.594,99.968,-2.61,99.997,-2.649,99.992,-2.77,100.015,-2.82,100.133,-2.821,100.204,-2.741,100.202,-2.68,100.012,-2.51]},{"b":[99.537,-2.37,99.848,-2.017],"l":[99.735,-2.178,99.686,-2.063,99.622,-2.017,99.572,-2.026,99.562,-2.051,99.559,-2.115,99.537,-2.162,99.607,-2.258,99.685,-2.282,99.848,-2.37,99.843,-2.343,99.816,-2.284,99.735,-2.178]},{"b":[98.602,-1.783,99.271,-0.916],"l":[99.131,-1.442,99.14,-1.418,99.129,-1.384,99.101,-1.34,99.065,-1.241,98.955,-1.056,98.933,-0.954,98.869,-0.916,98.676,-0.971,98.602,-1.198,98.627,-1.261,98.816,-1.538,98.828,-1.61,98.874,-1.664,99.072,-1.783,99.164,-1.778,99.271,-1.738,99.267,-1.628,99.21,-1.559,99.131,-1.442]},{"b":[98.31,-0.577,98.544,0.007],"l":[98.415,-0.018,98.375,0.007,98.323,-0.001,98.427,-0.226,98.409,-0.309,98.355,-0.379,98.34,-0.468,98.31,-0.532,98.4,-0.577,98.459,-0.53,98.52,-0.38,98.544,-0.258,98.484,-0.168,98.415,-0.018]},{"b":[97.108,2.053,97.334,2.232],"l":[97.291,2.201,97.253,2.216,97.157,2.232,97.108,2.217,97.225,2.158,97.328,2.053,97.334,2.076,97.329,2.149,97.291,2.201]},{"b":[95.717,2.351,96.464,2.916],"l":[96.417,2.515,96.18,2.661,96.13,2.721,96.102,2.741,95.998,2.781,95.896,2.889,95.806,2.916,95.772,2.855,95.717,2.826,95.733,2.767,95.809,2.656,95.88,2.641,95.938,2.598,96.022,2.596,96.29,2.43,96.341,2.372,96.401,2.351,96.464,2.36,96.459,2.416,96.443,2.466,96.417,2.515]},{"b":[95.218,5.784,95.366,5.907],"l":[95.242,5.907,95.218,5.89,95.283,5.799,95.343,5.784,95.362,5.812,95.366,5.843,95.359,5.877,95.283,5.898,95.242,5.907]},{"b":[111.3,2.402,111.389,2.768],"l":[111.376,2.576,111.378,2.709,111.355,2.764,111.333,2.768,111.3,2.741,111.312,2.438,111.359,2.402,111.389,2.415,111.38,2.459,111.376,2.576]},{"b":[117.06,7.115,117.281,7.352],"l":[117.064,7.261,117.06,7.179,117.081,7.115,117.142,7.168,117.239,7.185,117.267,7.221,117.281,7.291,117.264,7.352,117.147,7.337,117.064,7.261]},{"b":[31.509,46.155,32.15,46.373],"l":[31.585,46.303,31.509,46.373,31.529,46.307,31.564,46.258,31.7,46.214,32.009,46.168,32.15,46.155,32.012,46.204,31.638,46.273,31.585,46.303]},{"b":[22.056,58.69,23.009,59.087],"l":[22.411,58.863,22.479,58.754,22.473,58.712,22.542,58.69,22.661,58.709,22.767,58.821,22.793,58.797,22.842,58.777,22.924,58.827,23.009,58.834,22.982,58.92,22.91,58.991,22.725,59.015,22.712,59.032,22.702,59.074,22.649,59.087,22.587,59.081,22.505,59.026,22.463,58.974,22.056,58.944,22.162,58.898,22.307,58.895,22.411,58.863]},{"b":[23.063,58.54,23.356,58.678],"l":[23.165,58.678,23.109,58.659,23.063,58.611,23.26,58.54,23.344,58.55,23.356,58.576,23.333,58.649,23.165,58.678]},{"b":[35.529,64.977,35.858,65.198],"l":[35.858,65.078,35.848,65.143,35.816,65.182,35.729,65.198,35.609,65.157,35.586,65.167,35.529,65.151,35.559,65.094,35.621,65.059,35.68,65.058,35.779,64.977,35.842,65.001,35.827,65.036,35.858,65.078]},{"b":[42.46,66.688,42.714,66.796],"l":[42.631,66.782,42.547,66.796,42.469,66.786,42.46,66.77,42.477,66.735,42.676,66.688,42.714,66.702,42.691,66.735,42.631,66.782]},{"b":[26.408,78.646,27.008,78.811],"l":[26.789,78.724,26.586,78.811,26.456,78.81,26.408,78.784,26.46,78.72,26.729,78.646,26.876,78.649,27.008,78.698,26.789,78.724]},{"b":[27.889,78.829,29.697,78.971],"l":[29.047,78.912,28.845,78.971,28.511,78.967,28.415,78.961,28.374,78.927,28.121,78.908,27.889,78.852,28.038,78.829,28.495,78.887,28.881,78.88,29.311,78.852,29.697,78.905,29.645,78.922,29.345,78.906,29.047,78.912]},{"b":[49.556,80.061,50.319,80.23],"l":[50.319,80.172,50.31,80.186,50.251,80.219,49.884,80.23,49.556,80.159,49.588,80.136,49.971,80.061,50.052,80.074,50.072,80.109,50.319,80.172]},{"b":[50.091,79.921,51.435,80.094],"l":[51.243,79.991,51.238,80.01,51.254,80.049,50.936,80.094,50.676,80.049,50.473,80.035,50.091,79.981,50.454,79.924,51.076,79.932,51.431,79.921,51.435,79.932,51.409,79.944,51.327,79.972,51.243,79.991]},{"b":[31.482,80.081,33.629,80.242],"l":[31.482,80.108,31.578,80.081,32.526,80.119,33.557,80.198,33.629,80.217,33.384,80.242,33.099,80.229,33.019,80.218,31.482,80.108]},{"b":[55.466,81.136,58.564,81.565],"l":[57.456,81.543,57.365,81.535,57.092,81.541,56.973,81.511,56.719,81.423,56.405,81.387,56.157,81.303,55.782,81.329,55.466,81.311,55.573,81.228,55.717,81.188,56.192,81.224,56.364,81.179,56.513,81.175,56.669,81.198,56.822,81.238,57.159,81.178,57.451,81.136,57.77,81.17,57.913,81.198,58.015,81.255,57.912,81.303,57.859,81.368,58.372,81.387,58.564,81.418,58.436,81.464,58.017,81.484,57.863,81.506,57.81,81.546,57.717,81.565,57.456,81.543]},{"b":[54.045,80.628,57.694,81.116],"l":[54.634,81.113,54.417,80.987,54.367,80.904,54.241,80.902,54.045,80.872,54.067,80.814,54.376,80.787,54.533,80.783,54.623,80.765,54.668,80.739,55.117,80.752,55.541,80.703,55.713,80.637,55.883,80.628,56.316,80.633,56.815,80.664,57.58,80.755,57.694,80.792,57.568,80.82,56.91,80.913,56.472,80.998,56.17,81.029,55.471,81.02,54.719,81.116,54.634,81.113]},{"b":[50.368,81.041,50.946,81.171],"l":[50.754,81.047,50.789,81.072,50.946,81.108,50.879,81.151,50.716,81.171,50.592,81.169,50.522,81.158,50.506,81.144,50.465,81.126,50.368,81.123,50.377,81.103,50.412,81.084,50.518,81.046,50.616,81.041,50.754,81.047]},{"b":[57.211,80.765,58.931,81.118],"l":[58.05,81.118,57.656,81.032,57.41,81.047,57.211,81.017,57.405,80.915,57.75,80.889,57.938,80.793,58.286,80.765,58.642,80.768,58.86,80.779,58.931,80.832,58.903,80.898,58.815,80.934,58.762,80.991,58.622,81.042,58.508,81.062,58.19,81.095,58.102,81.114,58.05,81.118]},{"b":[62.106,81.597,63.782,81.719],"l":[62.885,81.609,63.529,81.597,63.651,81.609,63.782,81.65,63.767,81.664,63.71,81.687,62.795,81.719,62.284,81.707,62.106,81.679,62.515,81.659,62.531,81.647,62.573,81.633,62.885,81.609]},{"b":[57.909,81.696,59.408,81.854],"l":[59.356,81.759,59.357,81.781,59.408,81.825,59.262,81.854,58.135,81.828,57.985,81.797,57.945,81.748,57.909,81.722,57.921,81.71,57.965,81.696,58.295,81.715,59.356,81.759]},{"b":[18.162,80.246,18.742,80.358],"l":[18.525,80.246,18.742,80.301,18.519,80.348,18.292,80.358,18.206,80.332,18.162,80.288,18.525,80.246]},{"b":[10.558,78.225,12.116,78.903],"l":[11.929,78.375,11.885,78.409,11.826,78.436,11.616,78.475,11.424,78.549,11.262,78.542,11.25,78.611,11.155,78.641,11.078,78.686,11.153,78.724,11.124,78.753,10.961,78.846,10.773,78.888,10.558,78.903,10.558,78.838,10.628,78.754,10.789,78.687,10.841,78.645,11.121,78.463,11.199,78.441,11.372,78.439,11.587,78.388,11.757,78.329,11.965,78.225,12.116,78.233,12.056,78.306,11.929,78.375]},{"b":[18.797,74.352,19.275,74.518],"l":[18.861,74.514,18.797,74.486,18.918,74.411,19.099,74.352,19.219,74.391,19.275,74.457,19.262,74.479,19.183,74.518,18.861,74.514]},{"b":[52.24,70.969,53.205,71.404],"l":[53.141,71.242,53.074,71.238,52.994,71.291,52.903,71.365,52.777,71.4,52.732,71.404,52.72,71.39,52.73,71.355,52.617,71.383,52.513,71.385,52.297,71.357,52.24,71.325,52.25,71.285,52.289,71.27,52.425,71.239,52.547,71.25,52.738,71.181,52.789,71.115,52.835,71.086,52.95,71.054,53.004,71.012,53.023,70.969,53.121,70.982,53.106,70.999,53.048,71.031,53.071,71.065,53.205,71.16,53.193,71.215,53.141,71.242]},{"b":[74.1,72.864,74.962,73.13],"l":[74.409,73.13,74.199,73.109,74.142,73.074,74.1,73.022,74.181,72.975,74.435,72.908,74.588,72.881,74.638,72.864,74.661,72.873,74.697,72.908,74.66,72.929,74.647,72.969,74.743,73.033,74.962,73.063,74.725,73.108,74.6,73.122,74.409,73.13]},{"b":[75.344,73.432,76.052,73.574],"l":[76.052,73.549,76.039,73.56,75.93,73.574,75.57,73.541,75.375,73.477,75.344,73.432,75.504,73.457,75.827,73.459,75.901,73.481,76.052,73.549]},{"b":[76.083,73.44,76.756,73.555],"l":[76.251,73.555,76.14,73.554,76.083,73.523,76.234,73.476,76.659,73.44,76.756,73.446,76.251,73.555]},{"b":[76.871,72.282,78.365,72.631],"l":[77.749,72.631,77.579,72.631,77.378,72.565,77.26,72.486,77.15,72.439,76.903,72.366,76.871,72.317,76.906,72.298,77.146,72.282,77.633,72.291,77.781,72.309,78.007,72.392,78.154,72.417,78.365,72.482,78.353,72.504,78.279,72.553,77.749,72.631]},{"b":[78.633,72.711,79.541,73.094],"l":[79.413,72.983,79.357,73.039,79.164,73.094,78.657,72.892,78.633,72.851,78.69,72.803,78.881,72.752,79.431,72.711,79.501,72.722,79.538,72.769,79.541,72.919,79.413,72.983]},{"b":[82.329,74.056,82.71,74.161],"l":[82.382,74.149,82.329,74.131,82.382,74.099,82.478,74.076,82.613,74.056,82.71,74.091,82.689,74.111,82.611,74.149,82.526,74.161,82.382,74.149]},{"b":[82.818,74.04,83.618,74.152],"l":[83.15,74.152,82.903,74.129,82.818,74.092,83.159,74.075,83.411,74.04,83.45,74.052,83.496,74.048,83.549,74.072,83.618,74.089,83.513,74.122,83.15,74.152]},{"b":[84.389,74.4,84.873,74.516],"l":[84.54,74.49,84.389,74.454,84.429,74.43,84.71,74.4,84.759,74.459,84.873,74.516,84.68,74.512,84.54,74.49]},{"b":[86.259,74.831,87.124,74.993],"l":[86.653,74.981,86.605,74.993,86.504,74.966,86.331,74.939,86.259,74.894,86.391,74.851,86.692,74.848,86.927,74.831,87.012,74.862,87.124,74.94,87.052,74.983,87.001,74.992,86.737,74.963,86.653,74.981]},{"b":[81.501,75.247,82.222,75.516],"l":[82.022,75.513,81.913,75.498,81.91,75.46,81.927,75.41,81.842,75.407,81.712,75.451,81.501,75.368,81.532,75.34,81.579,75.331,81.655,75.289,81.698,75.281,81.861,75.317,81.905,75.263,81.979,75.247,82.05,75.341,82.179,75.339,82.222,75.351,82.209,75.387,82.172,75.419,82.166,75.516,82.022,75.513]},{"b":[95.311,76.122,96.614,76.305],"l":[96.271,76.305,96.151,76.272,95.786,76.294,95.594,76.25,95.38,76.289,95.322,76.262,95.311,76.215,95.679,76.194,95.845,76.16,96.109,76.155,96.301,76.122,96.353,76.177,96.351,76.212,96.487,76.234,96.59,76.221,96.614,76.264,96.532,76.278,96.271,76.305]},{"b":[97.31,76.584,97.588,76.707],"l":[97.31,76.69,97.342,76.629,97.43,76.591,97.535,76.584,97.588,76.599,97.382,76.707,97.31,76.69]},{"b":[95.27,76.975,96.562,77.206],"l":[95.854,77.098,95.421,77.056,95.27,77.019,95.364,77.012,95.681,77.021,95.766,76.991,95.855,76.975,96.091,77.003,96.21,76.992,96.254,77.007,96.285,77.027,96.424,77.071,96.561,77.13,96.562,77.154,96.528,77.206,95.854,77.098]},{"b":[89.142,77.184,89.68,77.311],"l":[89.616,77.311,89.282,77.301,89.2,77.272,89.142,77.227,89.179,77.21,89.3,77.184,89.514,77.189,89.666,77.254,89.68,77.28,89.616,77.311]},{"b":[76.052,79.49,77.589,79.664],"l":[76.467,79.643,76.373,79.615,76.249,79.651,76.148,79.664,76.052,79.645,76.072,79.626,76.154,79.579,76.458,79.545,76.637,79.544,76.65,79.493,76.81,79.49,77.589,79.502,77.549,79.524,77.36,79.557,76.467,79.643]},{"b":[78.978,80.812,80.428,80.975],"l":[79.217,80.96,79.11,80.924,78.978,80.848,79.007,80.835,79.099,80.812,80.027,80.848,80.345,80.868,80.373,80.883,80.428,80.928,80.28,80.95,79.807,80.975,79.217,80.96]},{"b":[89.901,81.064,91.567,81.214],"l":[91.478,81.184,91.109,81.199,90.07,81.214,89.901,81.171,89.919,81.149,89.976,81.113,91.223,81.064,91.567,81.141,91.478,81.184]},{"b":[106.416,78.058,107.696,78.189],"l":[106.583,78.168,106.416,78.14,107.002,78.096,107.344,78.099,107.482,78.058,107.606,78.083,107.696,78.131,107.573,78.186,107.508,78.189,106.583,78.168]},{"b":[106.024,78.199,106.72,78.34],"l":[106.679,78.265,106.719,78.265,106.72,78.294,106.691,78.317,106.64,78.336,106.457,78.34,106.351,78.273,106.058,78.265,106.024,78.22,106.151,78.199,106.27,78.206,106.472,78.245,106.505,78.262,106.679,78.265]},{"b":[107.27,77.242,107.679,77.347],"l":[107.366,77.347,107.27,77.289,107.302,77.242,107.415,77.243,107.679,77.268,107.665,77.3,107.629,77.32,107.594,77.33,107.486,77.347,107.366,77.347]},{"b":[111.969,76.45,112.661,76.644],"l":[112.154,76.549,112.297,76.538,112.395,76.484,112.532,76.45,112.575,76.452,112.587,76.483,112.614,76.499,112.661,76.51,112.633,76.553,112.478,76.621,112.394,76.644,112.281,76.618,112.011,76.633,111.969,76.626,112.003,76.603,112.154,76.549]},{"b":[119.64,73.045,120.261,73.168],"l":[120.079,73.157,119.964,73.168,119.762,73.155,119.64,73.124,119.792,73.045,120.008,73.045,120.261,73.09,120.237,73.107,120.079,73.157]},{"b":[124.336,73.848,124.653,73.943],"l":[124.43,73.943,124.337,73.928,124.336,73.91,124.366,73.875,124.482,73.848,124.543,73.85,124.653,73.888,124.637,73.9,124.548,73.934,124.43,73.943]},{"b":[135.452,75.382,136.169,75.845],"l":[136.169,75.606,136.127,75.626,135.905,75.694,135.849,75.729,135.788,75.798,135.699,75.845,135.614,75.766,135.578,75.71,135.561,75.636,135.593,75.576,135.523,75.496,135.473,75.463,135.452,75.39,135.746,75.382,135.949,75.41,136.021,75.438,135.965,75.486,135.983,75.522,136.169,75.606]},{"b":[148.399,76.648,149.406,76.782],"l":[149.406,76.782,148.72,76.747,148.448,76.677,148.399,76.648,149.15,76.66,149.205,76.677,149.268,76.747,149.406,76.782]},{"b":[135.387,73.885,136.259,74.253],"l":[136.037,74.09,135.628,74.22,135.387,74.253,135.402,74.202,135.449,74.18,135.633,74.121,135.715,74.06,136.051,73.929,136.122,73.885,136.197,73.914,136.259,73.985,136.037,74.09]},{"b":[137.064,71.423,137.96,71.588],"l":[137.282,71.58,137.168,71.557,137.129,71.556,137.082,71.543,137.064,71.53,137.079,71.502,137.266,71.456,137.344,71.461,137.403,71.477,137.458,71.483,137.512,71.475,137.613,71.434,137.712,71.423,137.96,71.508,137.934,71.543,137.858,71.583,137.817,71.588,137.282,71.58]},{"b":[160.437,70.806,160.719,70.934],"l":[160.566,70.924,160.449,70.934,160.44,70.923,160.437,70.851,160.505,70.82,160.651,70.806,160.719,70.823,160.645,70.884,160.566,70.924]},{"b":[167.789,69.571,169.434,70.016],"l":[168.358,70.016,168.196,70.008,168.06,69.975,167.865,69.901,167.814,69.873,167.789,69.837,167.821,69.82,167.993,69.776,168.144,69.713,168.348,69.664,168.916,69.571,169.201,69.58,169.246,69.601,169.263,69.629,169.299,69.735,169.332,69.77,169.418,69.779,169.434,69.832,169.421,69.856,169.375,69.883,168.358,70.016]},{"b":[163.427,58.509,164.662,59.227],"l":[164.573,59.221,164.517,59.227,164.202,59.096,163.761,59.015,163.767,58.972,163.784,58.93,163.727,58.799,163.577,58.641,163.427,58.579,163.432,58.546,163.447,58.525,163.471,58.509,163.635,58.603,163.96,58.744,164.279,58.838,164.616,58.886,164.662,58.971,164.629,59.112,164.573,59.221]},{"b":[167.442,54.513,168.081,54.857],"l":[167.711,54.77,167.592,54.798,167.512,54.857,167.442,54.856,167.488,54.795,167.677,54.698,168.081,54.513,168.039,54.565,167.883,54.69,167.711,54.77]},{"b":[165.751,54.694,166.65,55.351],"l":[166.577,54.908,166.479,54.95,166.404,55.006,166.248,55.165,166.23,55.242,166.276,55.312,166.212,55.324,165.931,55.351,165.83,55.307,165.751,55.295,165.992,55.19,166.066,55.136,166.082,55.077,166.12,55.03,166.23,54.937,166.325,54.865,166.382,54.838,166.464,54.827,166.521,54.768,166.645,54.694,166.65,54.839,166.577,54.908]},{"b":[156.168,50.634,156.488,50.862],"l":[156.376,50.862,156.213,50.785,156.168,50.732,156.196,50.702,156.326,50.639,156.365,50.634,156.405,50.658,156.483,50.751,156.488,50.843,156.456,50.86,156.376,50.862]},{"b":[155.195,50.041,156.123,50.772],"l":[156.097,50.772,156.002,50.757,155.885,50.684,155.773,50.482,155.68,50.401,155.434,50.369,155.327,50.293,155.218,50.298,155.195,50.265,155.243,50.213,155.243,50.095,155.289,50.061,155.397,50.041,155.449,50.078,155.516,50.146,155.608,50.177,155.792,50.202,155.921,50.302,156.044,50.452,156.101,50.559,156.123,50.671,156.097,50.772]},{"b":[153.992,48.739,154.228,48.905],"l":[154.126,48.904,154.092,48.832,153.992,48.773,154.001,48.756,154.043,48.739,154.081,48.79,154.205,48.857,154.228,48.892,154.199,48.905,154.126,48.904]},{"b":[155.467,50.811,155.654,50.934],"l":[155.645,50.822,155.654,50.845,155.64,50.91,155.569,50.934,155.467,50.914,155.483,50.87,155.513,50.837,155.554,50.811,155.645,50.822]},{"b":[152.984,47.706,153.101,47.809],"l":[152.984,47.728,153.004,47.713,153.054,47.706,153.101,47.763,153.079,47.809,153.049,47.797,152.984,47.728]},{"b":[151.715,46.787,152.289,47.143],"l":[151.864,46.869,151.715,46.853,151.723,46.829,151.754,46.788,151.816,46.787,152.002,46.897,152.289,47.142,152.235,47.143,152.166,47.11,152.04,47.015,151.864,46.869]},{"b":[149.447,45.591,150.553,46.213],"l":[149.962,46.022,149.796,45.876,149.666,45.84,149.447,45.593,149.539,45.591,149.688,45.642,149.883,45.783,149.954,45.822,150.057,45.849,150.195,45.933,150.235,46.012,150.553,46.209,150.349,46.213,150.309,46.2,149.962,46.022]},{"b":[146.897,44.404,148.837,45.526],"l":[148.812,45.51,148.773,45.526,148.707,45.521,148.612,45.485,148.324,45.282,148.13,45.258,148.056,45.262,147.965,45.378,147.924,45.383,147.873,45.3,147.886,45.226,147.769,45.191,147.658,45.093,147.558,45.062,147.43,44.945,147.247,44.856,147.155,44.766,147.141,44.663,146.974,44.566,146.933,44.513,146.897,44.404,147.098,44.531,147.207,44.554,147.31,44.678,147.563,44.836,147.61,44.887,147.622,44.945,147.658,44.977,147.784,44.959,147.914,44.99,148.005,45.07,148.262,45.217,148.415,45.247,148.6,45.318,148.791,45.324,148.837,45.363,148.803,45.414,148.825,45.456,148.826,45.486,148.812,45.51]},{"b":[150.47,59.007,150.728,59.16],"l":[150.666,59.16,150.592,59.097,150.47,59.054,150.472,59.035,150.511,59.007,150.59,59.019,150.728,59.095,150.713,59.122,150.666,59.16]},{"b":[137.233,54.653,138.206,55.197],"l":[137.941,55.093,137.91,55.11,137.577,55.197,137.544,55.163,137.436,55.016,137.384,55.001,137.275,54.891,137.233,54.791,137.276,54.792,137.463,54.873,137.526,54.826,137.661,54.653,137.721,54.663,137.79,54.697,137.87,54.75,137.959,54.789,137.991,54.821,138.017,54.901,138.096,54.991,138.206,55.034,138.172,55.06,138.031,55.053,137.941,55.093]},{"b":[136.715,54.924,137.179,55.108],"l":[137.078,55.092,136.996,55.093,136.795,55.009,136.715,54.956,136.765,54.946,136.903,54.961,136.969,54.924,137.055,54.927,137.179,55.1,137.156,55.108,137.078,55.092]},{"b":[146.609,43.716,146.899,43.86],"l":[146.622,43.813,146.613,43.797,146.609,43.74,146.683,43.716,146.714,43.744,146.899,43.804,146.884,43.829,146.825,43.86,146.622,43.813]},{"b":[145.426,43.665,146.568,44.5],"l":[146.356,44.425,146.208,44.498,146.112,44.5,145.94,44.273,145.89,44.249,145.852,44.193,145.773,44.129,145.748,44.072,145.666,43.999,145.462,43.871,145.426,43.81,145.439,43.737,145.556,43.665,145.587,43.845,145.767,43.941,145.887,44.048,145.914,44.104,146.112,44.246,146.173,44.269,146.296,44.281,146.437,44.376,146.516,44.375,146.568,44.44,146.356,44.425]},{"b":[124.085,24.335,124.324,24.587],"l":[124.17,24.452,124.12,24.47,124.085,24.436,124.136,24.348,124.186,24.335,124.234,24.358,124.293,24.516,124.324,24.566,124.302,24.587,124.211,24.459,124.17,24.452]},{"b":[123.68,24.266,123.935,24.414],"l":[123.752,24.348,123.68,24.318,123.681,24.288,123.75,24.283,123.826,24.266,123.889,24.28,123.928,24.324,123.935,24.362,123.771,24.414,123.754,24.391,123.752,24.348]},{"b":[125.269,24.717,125.444,24.872],"l":[125.315,24.852,125.284,24.872,125.269,24.733,125.359,24.717,125.444,24.743,125.402,24.777,125.335,24.805,125.315,24.852]},{"b":[127.65,26.095,128.332,26.882],"l":[128.255,26.882,128.217,26.797,128.122,26.711,128.098,26.668,128.047,26.643,128.03,26.647,127.994,26.679,127.907,26.694,127.895,26.675,127.891,26.631,127.946,26.594,127.926,26.556,127.82,26.466,127.796,26.449,127.729,26.434,127.727,26.308,127.655,26.199,127.65,26.154,127.653,26.095,127.729,26.097,127.804,26.153,127.806,26.171,127.786,26.209,127.79,26.255,127.849,26.319,127.905,26.328,127.869,26.381,127.867,26.442,127.951,26.456,128.038,26.534,128.127,26.552,128.163,26.607,128.259,26.653,128.311,26.721,128.332,26.812,128.255,26.882]},{"b":[128.883,27.702,129.016,27.91],"l":[128.952,27.91,128.908,27.898,128.883,27.842,128.9,27.728,128.956,27.702,128.998,27.721,129.016,27.77,128.99,27.811,128.952,27.91]},{"b":[129.165,28.128,129.715,28.517],"l":[129.598,28.476,129.577,28.461,129.561,28.431,129.51,28.398,129.465,28.395,129.322,28.36,129.251,28.314,129.248,28.283,129.217,28.263,129.165,28.25,129.275,28.201,129.366,28.128,129.453,28.209,129.439,28.255,129.457,28.272,129.513,28.299,129.575,28.361,129.642,28.411,129.71,28.432,129.715,28.47,129.69,28.517,129.598,28.476]},{"b":[129.192,28.082,129.331,28.192],"l":[129.257,28.176,129.192,28.192,129.232,28.101,129.331,28.082,129.324,28.105,129.277,28.145,129.257,28.176]},{"b":[130.388,30.241,130.673,30.466],"l":[130.497,30.466,130.388,30.388,130.446,30.265,130.508,30.241,130.623,30.263,130.673,30.367,130.644,30.389,130.497,30.466]},{"b":[130.87,30.386,131.083,30.828],"l":[130.947,30.671,130.94,30.575,130.87,30.444,130.872,30.386,130.96,30.397,130.993,30.53,131.057,30.642,131.083,30.791,131.06,30.828,131.04,30.819,131.012,30.792,130.947,30.671]},{"b":[128.649,32.586,128.895,32.784],"l":[128.839,32.763,128.806,32.776,128.761,32.772,128.704,32.757,128.665,32.784,128.649,32.662,128.657,32.628,128.693,32.605,128.75,32.586,128.79,32.637,128.821,32.646,128.895,32.652,128.879,32.693,128.839,32.763]},{"b":[129.96,32.194,130.2,32.541],"l":[130.01,32.522,130.021,32.469,129.979,32.346,130.015,32.314,130.017,32.292,129.96,32.244,129.993,32.228,130.004,32.194,130.083,32.23,130.2,32.341,130.197,32.492,130.168,32.541,130.01,32.522]},{"b":[130.242,32.419,130.461,32.527],"l":[130.242,32.463,130.256,32.431,130.293,32.419,130.381,32.424,130.419,32.458,130.461,32.516,130.365,32.527,130.242,32.463]},{"b":[133.19,36.167,133.381,36.34],"l":[133.206,36.293,133.19,36.233,133.239,36.179,133.325,36.167,133.371,36.204,133.381,36.246,133.296,36.34,133.206,36.293]},{"b":[138.225,37.82,138.575,38.316],"l":[138.51,38.259,138.504,38.316,138.461,38.291,138.306,38.161,138.25,38.078,138.246,37.995,138.322,37.991,138.322,37.97,138.288,37.896,138.283,37.854,138.225,37.829,138.249,37.82,138.344,37.822,138.497,37.904,138.575,38.066,138.454,38.076,138.463,38.124,138.51,38.259]},{"b":[139.412,42.076,139.558,42.235],"l":[139.431,42.2,139.412,42.16,139.435,42.084,139.458,42.076,139.481,42.081,139.505,42.096,139.558,42.235,139.496,42.227,139.431,42.2]},{"b":[140.972,45.269,141.073,45.465],"l":[140.972,45.465,140.982,45.364,141.034,45.269,141.073,45.333,141.07,45.4,141.057,45.45,141.002,45.465,140.972,45.465]},{"b":[141.135,45.112,141.329,45.248],"l":[141.252,45.232,141.194,45.248,141.135,45.206,141.145,45.154,141.226,45.112,141.295,45.119,141.329,45.15,141.31,45.179,141.252,45.232]},{"b":[139.769,33.045,139.874,33.129],"l":[139.776,33.078,139.824,33.045,139.841,33.056,139.874,33.094,139.809,33.129,139.777,33.125,139.769,33.107,139.776,33.078]},{"b":[139.367,34.68,139.456,34.776],"l":[139.37,34.775,139.367,34.721,139.392,34.69,139.446,34.68,139.456,34.727,139.426,34.776,139.37,34.775]},{"b":[134.668,34.203,135.005,34.545],"l":[134.834,34.473,134.757,34.368,134.668,34.294,134.683,34.247,134.731,34.209,134.824,34.203,134.933,34.288,134.905,34.398,135.005,34.544,134.961,34.545,134.904,34.519,134.834,34.473]},{"b":[128.997,32.829,129.182,33.133],"l":[129.124,33.068,129.11,33.133,129.035,32.969,128.997,32.952,129.02,32.92,129.052,32.829,129.077,32.84,129.112,32.929,129.154,32.946,129.182,32.993,129.153,33.003,129.124,33.068]},{"b":[129.37,33.176,129.57,33.361],"l":[129.462,33.331,129.423,33.257,129.417,33.231,129.37,33.176,129.421,33.176,129.492,33.223,129.508,33.284,129.57,33.361,129.538,33.358,129.462,33.331]},{"b":[129.675,33.707,129.796,33.858],"l":[129.717,33.858,129.7,33.829,129.675,33.74,129.727,33.707,129.796,33.749,129.776,33.829,129.717,33.858]},{"b":[129.186,34.083,129.337,34.321],"l":[129.215,34.321,129.186,34.145,129.214,34.083,129.279,34.123,129.335,34.231,129.337,34.285,129.215,34.321]},{"b":[129.267,34.306,129.48,34.687],"l":[129.451,34.687,129.326,34.607,129.322,34.579,129.329,34.522,129.267,34.37,129.297,34.34,129.365,34.306,129.386,34.354,129.381,34.416,129.475,34.54,129.469,34.616,129.48,34.649,129.472,34.671,129.451,34.687]},{"b":[132.461,34.075,132.578,34.173],"l":[132.578,34.115,132.56,34.127,132.543,34.173,132.524,34.164,132.496,34.122,132.461,34.087,132.549,34.075,132.578,34.115]},{"b":[132.201,33.847,132.445,33.948],"l":[132.36,33.847,132.411,33.88,132.445,33.913,132.43,33.924,132.315,33.909,132.266,33.945,132.208,33.948,132.201,33.928,132.209,33.873,132.267,33.871,132.36,33.847]},{"b":[118.287,24.414,118.451,24.522],"l":[118.339,24.469,118.287,24.477,118.295,24.436,118.433,24.414,118.451,24.456,118.407,24.522,118.339,24.469]},{"b":[118.077,24.436,118.183,24.552],"l":[118.077,24.501,118.089,24.489,118.091,24.446,118.15,24.436,118.183,24.496,118.171,24.519,118.104,24.552,118.093,24.541,118.077,24.501]},{"b":[128.489,34.737,128.741,35.014],"l":[128.722,35.014,128.668,35.009,128.586,34.932,128.489,34.865,128.52,34.82,128.647,34.737,128.741,34.799,128.722,35.014]},{"b":[127.832,34.703,128.066,34.921],"l":[127.832,34.875,127.838,34.813,127.873,34.735,127.897,34.735,127.942,34.766,127.984,34.703,128.055,34.708,128.066,34.806,128.038,34.879,127.966,34.893,127.915,34.921,127.832,34.875]},{"b":[127.737,34.584,127.799,34.682],"l":[127.737,34.631,127.788,34.584,127.799,34.615,127.787,34.682,127.737,34.631]},{"b":[126.166,33.202,126.931,33.553],"l":[126.76,33.553,126.696,33.549,126.338,33.46,126.199,33.368,126.166,33.312,126.179,33.283,126.229,33.225,126.24,33.215,126.282,33.202,126.327,33.224,126.582,33.238,126.709,33.272,126.873,33.341,126.905,33.382,126.931,33.444,126.901,33.515,126.76,33.553]},{"b":[126.109,34.355,126.38,34.563],"l":[126.344,34.545,126.247,34.563,126.227,34.533,126.123,34.444,126.109,34.399,126.134,34.39,126.17,34.355,126.234,34.371,126.335,34.426,126.38,34.498,126.344,34.545]},{"b":[126.369,37.605,126.521,37.823],"l":[126.521,37.737,126.494,37.783,126.412,37.823,126.369,37.772,126.407,37.649,126.423,37.624,126.461,37.61,126.516,37.605,126.521,37.737]},{"b":[121.211,31.464,121.863,31.805],"l":[121.577,31.637,121.542,31.674,121.492,31.694,121.464,31.756,121.339,31.797,121.211,31.805,121.227,31.758,121.336,31.644,121.52,31.55,121.78,31.464,121.863,31.492,121.844,31.526,121.808,31.552,121.577,31.637]},{"b":[121.969,29.944,122.322,30.143],"l":[122.284,30.068,122.111,30.14,121.969,30.143,121.978,30.064,122.024,30.013,122.158,30.001,122.282,29.944,122.296,29.963,122.322,30.031,122.284,30.068]},{"b":[122.043,29.66,122.173,29.782],"l":[122.12,29.782,122.062,29.773,122.043,29.736,122.084,29.725,122.169,29.66,122.173,29.679,122.165,29.701,122.12,29.782]},{"b":[122.332,29.846,122.404,29.955],"l":[122.402,29.95,122.351,29.955,122.332,29.935,122.368,29.853,122.394,29.846,122.404,29.892,122.402,29.95]},{"b":[119.696,25.411,119.839,25.653],"l":[119.797,25.623,119.778,25.653,119.723,25.639,119.696,25.591,119.723,25.551,119.699,25.495,119.7,25.433,119.747,25.411,119.821,25.457,119.832,25.48,119.809,25.508,119.839,25.56,119.838,25.591,119.829,25.607,119.797,25.623]},{"b":[110.265,20.968,110.54,21.093],"l":[110.522,21.083,110.422,21.059,110.385,21.093,110.31,21.075,110.265,21.025,110.281,21.001,110.34,20.998,110.422,21.007,110.504,20.968,110.539,21.018,110.54,21.039,110.522,21.083]},{"b":[107.404,21.092,107.603,21.269],"l":[107.476,21.269,107.453,21.235,107.404,21.094,107.459,21.092,107.603,21.217,107.563,21.22,107.476,21.269]},{"b":[107.399,20.901,107.551,21.034],"l":[107.479,20.952,107.399,20.903,107.466,20.901,107.521,20.927,107.551,20.981,107.551,21.034,107.519,21.013,107.479,20.952]},{"b":[106.911,20.743,107.064,20.867],"l":[107.044,20.837,106.953,20.867,106.911,20.824,106.99,20.743,107.031,20.747,107.042,20.761,107.064,20.8,107.064,20.817,107.044,20.837]},{"b":[106.769,20.796,106.866,20.928],"l":[106.795,20.928,106.769,20.864,106.803,20.844,106.854,20.796,106.866,20.816,106.855,20.858,106.795,20.928]},{"b":[106.568,8.681,106.659,8.766],"l":[106.568,8.701,106.589,8.681,106.617,8.683,106.653,8.701,106.65,8.723,106.659,8.766,106.568,8.701]},{"b":[103.85,10.029,104.083,10.428],"l":[104.028,10.428,103.986,10.427,103.898,10.369,103.85,10.371,103.868,10.335,103.952,10.243,104.018,10.029,104.048,10.061,104.037,10.111,104.076,10.225,104.083,10.341,104.064,10.391,104.028,10.428]},{"b":[102.993,11.275,103.045,11.39],"l":[102.993,11.29,103.011,11.276,103.027,11.275,103.045,11.285,103.037,11.39,103.008,11.383,102.995,11.348,102.993,11.29]},{"b":[102.533,11.572,102.606,11.692],"l":[102.533,11.615,102.59,11.572,102.606,11.677,102.569,11.692,102.546,11.668,102.533,11.615]},{"b":[102.273,11.965,102.43,12.152],"l":[102.319,12.142,102.277,12.152,102.273,12.119,102.302,11.981,102.36,11.974,102.378,11.983,102.43,11.965,102.427,11.989,102.408,12.025,102.378,12.073,102.319,12.142]},{"b":[103.65,1.265,103.996,1.447],"l":[103.818,1.447,103.705,1.423,103.65,1.326,103.82,1.265,103.97,1.331,103.996,1.365,103.961,1.392,103.909,1.416,103.818,1.447]},{"b":[104.129,2.721,104.223,2.872],"l":[104.185,2.872,104.17,2.857,104.129,2.767,104.147,2.728,104.173,2.721,104.222,2.732,104.223,2.774,104.185,2.872]},{"b":[100.191,5.267,100.339,5.468],"l":[100.246,5.468,100.204,5.447,100.191,5.283,100.264,5.267,100.289,5.295,100.339,5.41,100.31,5.438,100.246,5.468]},{"b":[99.646,6.263,99.919,6.466],"l":[99.848,6.466,99.822,6.445,99.749,6.41,99.711,6.427,99.646,6.418,99.657,6.367,99.705,6.338,99.744,6.263,99.783,6.272,99.823,6.313,99.866,6.297,99.883,6.311,99.919,6.359,99.848,6.466]},{"b":[99.607,6.516,99.701,6.714],"l":[99.654,6.714,99.607,6.597,99.644,6.516,99.663,6.522,99.701,6.571,99.654,6.714]},{"b":[99.983,9.68,100.074,9.794],"l":[99.983,9.794,99.998,9.748,100.026,9.712,100.064,9.68,100.074,9.697,100.073,9.749,100.043,9.792,99.983,9.794]},{"b":[99.931,9.422,100.075,9.586],"l":[99.954,9.581,99.94,9.56,99.931,9.476,99.962,9.422,100.054,9.461,100.075,9.529,100.071,9.586,100.043,9.577,99.954,9.581]},{"b":[98.529,7.917,98.604,8.109],"l":[98.58,7.917,98.592,7.934,98.604,8.057,98.529,8.109,98.58,7.917]},{"b":[98.262,7.776,98.435,8.166],"l":[98.399,7.965,98.435,8.086,98.351,8.111,98.322,8.166,98.301,8.136,98.262,7.926,98.296,7.776,98.316,7.782,98.357,7.829,98.398,7.828,98.409,7.902,98.399,7.965]},{"b":[98.251,9.041,98.313,9.139],"l":[98.251,9.041,98.308,9.051,98.313,9.08,98.301,9.139,98.274,9.13,98.258,9.095,98.251,9.041]},{"b":[93.656,6.749,93.93,7.237],"l":[93.859,7.207,93.822,7.237,93.684,7.184,93.656,7.136,93.658,7.016,93.709,7.001,93.829,6.749,93.89,6.831,93.93,6.973,93.859,7.207]},{"b":[93.597,7.262,93.734,7.411],"l":[93.655,7.38,93.614,7.358,93.597,7.319,93.638,7.262,93.734,7.356,93.692,7.411,93.655,7.38]},{"b":[93.064,8.212,93.171,8.349],"l":[93.097,8.349,93.078,8.328,93.064,8.275,93.115,8.219,93.171,8.212,93.141,8.25,93.097,8.349]},{"b":[93.309,7.877,93.447,8.018],"l":[93.375,8.018,93.334,8.007,93.309,7.964,93.342,7.919,93.365,7.877,93.443,7.878,93.447,7.899,93.434,7.948,93.375,8.018]},{"b":[93.456,8.019,93.537,8.225],"l":[93.512,8.16,93.532,8.214,93.494,8.225,93.456,8.172,93.461,8.109,93.47,8.073,93.472,8.053,93.478,8.024,93.49,8.019,93.537,8.057,93.512,8.16]},{"b":[92.713,9.131,92.809,9.244],"l":[92.713,9.205,92.717,9.165,92.744,9.131,92.788,9.137,92.809,9.173,92.786,9.241,92.762,9.244,92.739,9.231,92.713,9.205]},{"b":[92.353,10.521,92.574,10.897],"l":[92.51,10.897,92.448,10.866,92.371,10.794,92.353,10.751,92.377,10.651,92.37,10.547,92.473,10.521,92.503,10.555,92.574,10.704,92.554,10.8,92.51,10.897]},{"b":[92.596,11.361,92.693,11.509],"l":[92.69,11.463,92.64,11.509,92.634,11.427,92.596,11.386,92.645,11.361,92.693,11.381,92.687,11.411,92.69,11.463]},{"b":[92.955,11.899,93.062,12.037],"l":[93.017,12.037,92.996,12.032,92.955,12.002,92.982,11.959,93.062,11.899,93.017,12.037]},{"b":[92.68,12.8,92.731,12.962],"l":[92.68,12.939,92.686,12.8,92.718,12.865,92.731,12.949,92.711,12.962,92.694,12.957,92.68,12.939]},{"b":[92.534,11.513,93.077,13.545],"l":[93.016,13.336,93.077,13.401,93.067,13.436,93.062,13.545,93.029,13.544,92.925,13.486,92.857,13.358,92.86,13.231,92.809,13.04,92.831,13.003,92.807,12.879,92.753,12.821,92.74,12.78,92.759,12.669,92.732,12.616,92.721,12.541,92.719,12.357,92.734,12.336,92.778,12.303,92.788,12.226,92.769,12.216,92.695,12.215,92.676,12.192,92.641,12.112,92.632,12.014,92.608,11.95,92.567,11.931,92.534,11.873,92.56,11.833,92.576,11.718,92.668,11.539,92.701,11.513,92.723,11.536,92.765,11.639,92.767,11.765,92.798,11.875,92.797,11.918,92.764,11.94,92.748,11.993,92.786,12.035,92.799,12.079,92.867,12.181,92.879,12.228,92.864,12.436,92.933,12.453,92.99,12.539,92.965,12.85,92.886,12.942,92.91,12.975,92.951,13.063,93.005,13.089,93.043,13.155,93.066,13.222,93.074,13.252,93.016,13.336]},{"b":[98.477,10.959,98.542,11.087],"l":[98.527,11.087,98.477,10.98,98.498,10.964,98.519,10.959,98.542,10.962,98.527,11.087]},{"b":[98.08,10.74,98.293,10.98],"l":[98.21,10.953,98.167,10.98,98.143,10.963,98.08,10.887,98.155,10.898,98.218,10.838,98.252,10.744,98.271,10.74,98.284,10.753,98.293,10.78,98.21,10.953]},{"b":[98.118,9.875,98.292,10.051],"l":[98.221,10.045,98.14,9.975,98.118,9.878,98.134,9.875,98.183,9.933,98.231,9.954,98.283,10.008,98.292,10.051,98.221,10.045]},{"b":[98.187,11.456,98.308,11.783],"l":[98.239,11.645,98.201,11.567,98.187,11.472,98.209,11.457,98.216,11.456,98.222,11.478,98.263,11.524,98.284,11.594,98.308,11.723,98.3,11.783,98.278,11.758,98.239,11.645]},{"b":[98.454,11.899,98.61,12.084],"l":[98.525,12.005,98.466,12.084,98.454,12.061,98.474,11.899,98.516,11.905,98.576,11.925,98.61,11.957,98.525,12.005]},{"b":[98.01,11.637,98.084,11.86],"l":[98.01,11.86,98.021,11.696,98.084,11.637,98.075,11.692,98.081,11.733,98.06,11.757,98.01,11.86]},{"b":[98.376,11.539,98.554,11.805],"l":[98.376,11.792,98.4,11.715,98.397,11.684,98.435,11.567,98.465,11.567,98.528,11.539,98.554,11.745,98.524,11.805,98.376,11.792]},{"b":[98.037,12.145,98.137,12.292],"l":[98.137,12.15,98.12,12.191,98.118,12.223,98.128,12.261,98.122,12.279,98.105,12.288,98.071,12.292,98.057,12.28,98.037,12.232,98.075,12.164,98.108,12.148,98.125,12.145,98.137,12.15]},{"b":[97.939,12.279,98.066,12.398],"l":[98.045,12.387,97.99,12.394,97.939,12.346,97.952,12.322,98.002,12.279,98.06,12.354,98.066,12.39,98.06,12.398,98.045,12.387]},{"b":[98.303,12.336,98.468,12.678],"l":[98.396,12.647,98.312,12.678,98.303,12.612,98.331,12.511,98.314,12.336,98.334,12.336,98.381,12.354,98.459,12.474,98.468,12.571,98.436,12.571,98.414,12.598,98.396,12.647]},{"b":[98.251,12.935,98.315,13.202],"l":[98.265,13.202,98.255,13.189,98.251,13.104,98.259,13.014,98.309,12.935,98.315,13.099,98.299,13.152,98.269,13.189,98.265,13.202]},{"b":[97.469,16.24,97.6,16.505],"l":[97.579,16.486,97.542,16.505,97.516,16.497,97.469,16.461,97.48,16.306,97.537,16.24,97.575,16.253,97.589,16.397,97.6,16.43,97.593,16.461,97.579,16.486]},{"b":[99.038,7.471,99.104,7.637],"l":[99.038,7.548,99.068,7.496,99.104,7.471,99.078,7.592,99.045,7.637,99.038,7.626,99.038,7.548]},{"b":[94.733,15.794,94.838,15.933],"l":[94.733,15.823,94.743,15.812,94.784,15.794,94.805,15.819,94.838,15.892,94.828,15.933,94.733,15.823]},{"b":[94.388,15.848,94.619,16.206],"l":[94.494,16.075,94.388,15.994,94.412,15.848,94.477,15.946,94.566,16.019,94.619,16.141,94.601,16.206,94.546,16.153,94.494,16.075]},{"b":[93.488,18.676,93.746,18.889],"l":[93.745,18.866,93.618,18.889,93.488,18.868,93.57,18.76,93.674,18.676,93.691,18.684,93.718,18.716,93.746,18.808,93.745,18.866]},{"b":[93.413,19.755,93.513,19.95],"l":[93.492,19.893,93.413,19.95,93.42,19.878,93.445,19.806,93.513,19.755,93.492,19.893]},{"b":[92.913,19.829,93.023,20.086],"l":[92.915,20.086,92.913,20,92.975,19.868,93.023,19.829,93.01,19.924,92.96,20.046,92.915,20.086]},{"b":[93.644,19.299,93.947,19.558],"l":[93.875,19.481,93.829,19.475,93.715,19.558,93.688,19.544,93.644,19.495,93.662,19.459,93.732,19.416,93.756,19.326,93.815,19.299,93.902,19.332,93.934,19.365,93.947,19.408,93.946,19.429,93.875,19.481]},{"b":[91.82,21.75,91.883,21.927],"l":[91.851,21.927,91.835,21.885,91.82,21.81,91.838,21.75,91.874,21.832,91.883,21.884,91.861,21.927,91.851,21.927]},{"b":[91.857,21.503,91.962,21.723],"l":[91.908,21.723,91.857,21.709,91.873,21.574,91.859,21.533,91.889,21.503,91.949,21.508,91.962,21.61,91.949,21.683,91.934,21.722,91.908,21.723]},{"b":[91.045,22.105,91.178,22.52],"l":[91.079,22.52,91.045,22.105,91.151,22.175,91.178,22.283,91.158,22.365,91.079,22.52]},{"b":[91.411,22.353,91.557,22.617],"l":[91.411,22.476,91.467,22.378,91.51,22.353,91.557,22.382,91.548,22.425,91.523,22.491,91.484,22.577,91.456,22.617,91.439,22.599,91.411,22.476]},{"b":[90.536,22.92,90.66,23.035],"l":[90.58,23.035,90.536,23.015,90.562,22.975,90.604,22.946,90.66,22.92,90.642,22.963,90.58,23.035]},{"b":[90.503,22.054,90.868,22.864],"l":[90.683,22.785,90.672,22.813,90.596,22.864,90.503,22.835,90.523,22.748,90.56,22.673,90.565,22.618,90.649,22.541,90.675,22.445,90.68,22.327,90.515,22.065,90.604,22.054,90.778,22.089,90.83,22.16,90.866,22.391,90.868,22.485,90.737,22.639,90.699,22.714,90.683,22.785]},{"b":[79.846,9.62,79.97,9.744],"l":[79.97,9.631,79.912,9.679,79.888,9.741,79.872,9.744,79.859,9.734,79.846,9.715,79.857,9.686,79.907,9.62,79.97,9.631]},{"b":[79.748,8.975,79.904,9.105],"l":[79.904,8.975,79.875,9.051,79.86,9.066,79.748,9.105,79.767,9.07,79.821,9.027,79.904,8.975]},{"b":[-9.099,70.833,-7.979,71.178],"l":[-8.344,71.14,-8.521,71.031,-8.965,70.916,-9.099,70.855,-9.046,70.833,-8.954,70.839,-8.635,70.94,-8.302,70.981,-8.002,71.041,-7.979,71.117,-8.001,71.178,-8.344,71.14]},{"b":[-156.049,18.964,-154.804,20.276],"l":[-154.953,19.645,-154.989,19.732,-155.066,19.748,-155.086,19.876,-155.199,19.994,-155.622,20.163,-155.832,20.276,-155.874,20.26,-155.893,20.167,-155.82,20.014,-155.909,19.895,-155.988,19.832,-156.049,19.75,-155.966,19.591,-155.891,19.383,-155.906,19.126,-155.881,19.071,-155.681,18.968,-155.626,18.964,-155.581,19.012,-155.535,19.109,-155.31,19.26,-155.053,19.319,-154.85,19.454,-154.804,19.524,-154.841,19.568,-154.953,19.645]},{"b":[-157.29,21.056,-156.712,21.23],"l":[-156.917,21.177,-156.952,21.2,-157.002,21.188,-157.214,21.215,-157.25,21.23,-157.254,21.181,-157.279,21.152,-157.29,21.113,-157.021,21.098,-156.86,21.056,-156.748,21.104,-156.712,21.155,-156.742,21.164,-156.917,21.177]},{"b":[-156.698,20.599,-155.99,21.034],"l":[-156.104,20.84,-156.148,20.885,-156.278,20.951,-156.354,20.941,-156.461,20.915,-156.487,20.933,-156.532,20.993,-156.585,21.034,-156.657,21.025,-156.698,20.949,-156.69,20.901,-156.615,20.822,-156.544,20.79,-156.48,20.801,-156.449,20.706,-156.438,20.618,-156.409,20.605,-156.31,20.599,-156.235,20.629,-156.107,20.645,-156.014,20.715,-155.99,20.757,-156.019,20.792,-156.104,20.84]},{"b":[-160.243,21.797,-160.049,22.015],"l":[-160.221,21.897,-160.243,21.843,-160.235,21.804,-160.2,21.797,-160.18,21.841,-160.153,21.879,-160.08,21.907,-160.077,21.958,-160.049,22.005,-160.101,22.015,-160.164,21.944,-160.221,21.897]},{"b":[-158.273,21.269,-157.635,21.701],"l":[-157.83,21.471,-157.854,21.512,-157.852,21.553,-157.962,21.701,-158.02,21.692,-158.123,21.6,-158.273,21.585,-158.239,21.533,-158.239,21.489,-158.138,21.377,-158.11,21.319,-158.079,21.312,-157.981,21.316,-158.017,21.368,-157.978,21.379,-157.968,21.367,-157.958,21.327,-157.902,21.341,-157.849,21.291,-157.799,21.269,-157.691,21.28,-157.635,21.308,-157.654,21.334,-157.706,21.378,-157.721,21.458,-157.765,21.451,-157.799,21.457,-157.83,21.471]},{"b":[-159.789,21.876,-159.301,22.223],"l":[-159.579,22.223,-159.727,22.14,-159.789,22.042,-159.748,21.99,-159.646,21.952,-159.609,21.91,-159.512,21.9,-159.461,21.876,-159.373,21.932,-159.344,21.974,-159.33,22.051,-159.301,22.105,-159.305,22.154,-159.352,22.22,-159.579,22.223]},{"b":[-157.051,20.744,-156.809,20.93],"l":[-156.942,20.93,-157.051,20.912,-156.988,20.826,-156.973,20.758,-156.909,20.744,-156.85,20.773,-156.809,20.831,-156.848,20.878,-156.881,20.905,-156.942,20.93]},{"b":[178.648,51.372,179.452,51.656],"l":[179.182,51.47,179.084,51.528,178.908,51.616,178.692,51.656,178.648,51.644,178.747,51.587,178.926,51.535,179.278,51.372,179.452,51.373,179.416,51.401,179.294,51.421,179.182,51.47]},{"b":[179.498,51.88,179.78,52.03],"l":[179.627,52.03,179.504,51.98,179.498,51.933,179.55,51.894,179.645,51.88,179.728,51.905,179.78,51.967,179.627,52.03]},{"b":[177.25,51.841,177.67,52.114],"l":[177.564,52.11,177.521,52.018,177.478,51.992,177.381,51.976,177.25,51.903,177.261,51.884,177.329,51.841,177.415,51.883,177.594,51.948,177.596,51.994,177.653,52.06,177.67,52.103,177.637,52.114,177.564,52.11]},{"b":[173.402,52.357,173.776,52.504],"l":[173.658,52.504,173.517,52.451,173.425,52.438,173.402,52.405,173.616,52.391,173.658,52.357,173.723,52.36,173.745,52.447,173.776,52.495,173.658,52.504]},{"b":[172.495,52.752,173.436,53.013],"l":[173.436,52.852,173.252,52.943,173.102,52.996,172.984,52.98,172.812,53.013,172.678,53.008,172.495,52.938,172.595,52.907,172.722,52.886,172.776,52.797,172.935,52.752,173.08,52.814,173.159,52.811,173.303,52.826,173.348,52.825,173.395,52.835,173.436,52.852]},{"b":[-173.074,60.299,-172.232,60.607],"l":[-172.387,60.398,-172.526,60.392,-172.742,60.457,-172.86,60.506,-172.924,60.607,-173.048,60.568,-173.074,60.493,-172.958,60.463,-172.636,60.329,-172.397,60.331,-172.232,60.299,-172.278,60.344,-172.387,60.398]},{"b":[-170.387,57.137,-170.116,57.242],"l":[-170.387,57.203,-170.386,57.189,-170.358,57.154,-170.264,57.137,-170.161,57.184,-170.116,57.242,-170.387,57.203]},{"b":[-160.517,55.259,-160.329,55.357],"l":[-160.493,55.352,-160.517,55.334,-160.481,55.309,-160.343,55.259,-160.329,55.338,-160.362,55.357,-160.493,55.352]},{"b":[-160.847,55.145,-160.483,55.405],"l":[-160.583,55.308,-160.552,55.363,-160.553,55.381,-160.574,55.378,-160.639,55.322,-160.67,55.314,-160.685,55.315,-160.666,55.359,-160.672,55.379,-160.696,55.398,-160.724,55.405,-160.789,55.383,-160.84,55.335,-160.847,55.311,-160.825,55.174,-160.795,55.145,-160.751,55.171,-160.702,55.178,-160.609,55.159,-160.488,55.185,-160.483,55.197,-160.531,55.233,-160.583,55.308]},{"b":[-162.434,54.842,-162.234,54.983],"l":[-162.434,54.932,-162.416,54.896,-162.391,54.873,-162.322,54.842,-162.298,54.847,-162.273,54.867,-162.234,54.932,-162.238,54.955,-162.265,54.983,-162.294,54.983,-162.434,54.932]},{"b":[-162.821,54.38,-162.554,54.495],"l":[-162.821,54.495,-162.812,54.444,-162.733,54.402,-162.641,54.38,-162.554,54.401,-162.608,54.447,-162.645,54.462,-162.821,54.495]},{"b":[-159.462,54.967,-159.362,55.059],"l":[-159.462,55.059,-159.458,55.035,-159.421,54.978,-159.394,54.967,-159.362,54.972,-159.363,55,-159.39,55.041,-159.462,55.059]},{"b":[-160.227,54.923,-159.839,55.273],"l":[-159.898,55.221,-159.871,55.264,-159.887,55.273,-159.92,55.268,-159.982,55.198,-160.039,55.193,-160.102,55.134,-160.134,55.12,-160.172,55.123,-160.152,55.057,-160.154,55.038,-160.164,55.01,-160.227,54.923,-160.17,54.942,-160.038,55.044,-159.999,55.067,-159.953,55.079,-159.934,55.107,-159.873,55.129,-159.854,55.145,-159.839,55.182,-159.898,55.221]},{"b":[-159.648,55.057,-159.515,55.226],"l":[-159.515,55.152,-159.545,55.226,-159.575,55.218,-159.595,55.182,-159.588,55.165,-159.598,55.126,-159.64,55.124,-159.635,55.102,-159.648,55.075,-159.618,55.057,-159.561,55.081,-159.535,55.06,-159.52,55.072,-159.515,55.152]},{"b":[-169.992,52.792,-169.692,52.884],"l":[-169.983,52.851,-169.992,52.83,-169.981,52.806,-169.877,52.814,-169.723,52.792,-169.708,52.807,-169.692,52.847,-169.711,52.867,-169.755,52.884,-169.821,52.883,-169.983,52.851]},{"b":[-170.827,52.55,-170.585,52.698],"l":[-170.682,52.698,-170.791,52.631,-170.827,52.601,-170.816,52.562,-170.797,52.55,-170.733,52.581,-170.692,52.593,-170.649,52.593,-170.614,52.61,-170.587,52.642,-170.585,52.668,-170.608,52.685,-170.682,52.698]},{"b":[-166.106,54.047,-165.693,54.211],"l":[-165.764,54.152,-165.893,54.207,-165.966,54.211,-166.041,54.191,-166.088,54.169,-166.106,54.145,-166.103,54.114,-166.057,54.054,-166.036,54.047,-165.933,54.059,-165.91,54.049,-165.879,54.053,-165.842,54.071,-165.738,54.081,-165.693,54.1,-165.704,54.12,-165.764,54.152]},{"b":[-165.654,54.129,-165.408,54.285],"l":[-165.442,54.208,-165.488,54.222,-165.534,54.274,-165.551,54.285,-165.59,54.279,-165.654,54.253,-165.621,54.184,-165.615,54.14,-165.605,54.129,-165.561,54.137,-165.468,54.181,-165.408,54.197,-165.442,54.208]},{"b":[-169.089,52.832,-167.805,53.558],"l":[-167.805,53.485,-167.828,53.508,-167.986,53.558,-168.073,53.557,-168.193,53.533,-168.288,53.5,-168.357,53.458,-168.396,53.409,-168.405,53.354,-168.397,53.322,-168.363,53.304,-168.38,53.283,-168.437,53.257,-168.572,53.266,-168.639,53.256,-168.69,53.227,-168.76,53.175,-168.778,53.149,-168.783,53.079,-168.796,53.045,-168.836,53.02,-168.909,52.951,-168.974,52.91,-169.073,52.864,-169.089,52.832,-169.066,52.834,-168.741,52.957,-168.699,52.963,-168.597,53.016,-168.549,53.036,-168.506,53.043,-168.446,53.084,-168.37,53.16,-168.271,53.238,-167.964,53.345,-167.865,53.387,-167.843,53.435,-167.805,53.485]},{"b":[-167.809,53.259,-166.231,54.006],"l":[-166.522,53.61,-166.444,53.652,-166.355,53.674,-166.309,53.698,-166.339,53.718,-166.385,53.721,-166.549,53.701,-166.546,53.726,-166.489,53.785,-166.319,53.874,-166.231,53.933,-166.336,53.971,-166.372,53.999,-166.4,53.978,-166.443,53.925,-166.497,53.884,-166.572,53.853,-166.615,53.901,-166.627,53.996,-166.673,54.006,-166.734,54.002,-166.849,53.978,-166.978,53.963,-167.038,53.942,-167.09,53.906,-167.118,53.873,-167.121,53.843,-167.106,53.813,-167.071,53.783,-167.027,53.769,-166.973,53.771,-166.89,53.759,-166.777,53.733,-166.741,53.713,-166.804,53.685,-166.809,53.646,-166.819,53.641,-166.838,53.648,-166.894,53.697,-167.016,53.698,-167.042,53.655,-167.092,53.636,-167.136,53.526,-167.204,53.495,-167.424,53.437,-167.53,53.394,-167.639,53.387,-167.71,53.371,-167.809,53.324,-167.781,53.3,-167.669,53.26,-167.629,53.259,-167.592,53.273,-167.522,53.276,-167.48,53.292,-167.429,53.326,-167.381,53.342,-167.337,53.341,-167.3,53.35,-167.271,53.371,-167.154,53.408,-166.961,53.447,-166.851,53.453,-166.77,53.476,-166.702,53.537,-166.522,53.61]},{"b":[-166.251,53.72,-166.103,53.843],"l":[-166.234,53.784,-166.251,53.768,-166.249,53.745,-166.224,53.72,-166.21,53.723,-166.184,53.757,-166.139,53.787,-166.103,53.833,-166.114,53.843,-166.155,53.836,-166.188,53.822,-166.234,53.784]},{"b":[-172.62,52.257,-172.314,52.388],"l":[-172.47,52.388,-172.544,52.354,-172.582,52.326,-172.62,52.273,-172.539,52.257,-172.465,52.272,-172.314,52.33,-172.383,52.373,-172.47,52.388]},{"b":[-173.992,52.042,-173.023,52.144],"l":[-173.357,52.096,-173.553,52.136,-173.657,52.144,-173.779,52.118,-173.794,52.104,-173.939,52.131,-173.992,52.123,-173.99,52.104,-173.93,52.072,-173.879,52.054,-173.836,52.048,-173.673,52.063,-173.461,52.042,-173.368,52.046,-173.232,52.068,-173.179,52.063,-173.023,52.079,-173.024,52.091,-173.113,52.1,-173.357,52.096]},{"b":[-175.296,51.994,-174.018,52.42],"l":[-174.169,52.42,-174.307,52.378,-174.365,52.342,-174.436,52.317,-174.406,52.296,-174.259,52.269,-174.306,52.216,-174.474,52.184,-174.668,52.135,-174.916,52.094,-175.118,52.047,-175.214,52.038,-175.296,52.022,-175.214,51.994,-174.677,52.035,-174.344,52.078,-174.121,52.135,-174.179,52.2,-174.163,52.223,-174.055,52.246,-174.03,52.29,-174.018,52.332,-174.046,52.367,-174.169,52.42]},{"b":[-176.204,51.79,-176.009,51.886],"l":[-176.194,51.886,-176.204,51.835,-176.093,51.79,-176.009,51.812,-176.072,51.843,-176.194,51.886]},{"b":[-176.414,51.733,-176.28,51.861],"l":[-176.287,51.792,-176.28,51.803,-176.379,51.861,-176.414,51.841,-176.396,51.76,-176.35,51.733,-176.287,51.792]},{"b":[-176.962,51.604,-176.437,51.986],"l":[-176.588,51.833,-176.593,51.867,-176.552,51.92,-176.55,51.944,-176.597,51.982,-176.698,51.986,-176.745,51.895,-176.736,51.84,-176.774,51.819,-176.874,51.79,-176.962,51.604,-176.837,51.676,-176.771,51.63,-176.558,51.712,-176.511,51.746,-176.47,51.731,-176.452,51.736,-176.437,51.754,-176.437,51.82,-176.473,51.837,-176.588,51.833]},{"b":[-176.185,51.973,-175.975,52.1],"l":[-176.045,51.973,-176.022,52.002,-175.975,52.029,-175.988,52.049,-176.031,52.082,-176.077,52.1,-176.156,52.099,-176.185,52.056,-176.178,52.03,-176.143,52.004,-176.045,51.973]},{"b":[-177.67,51.677,-177.063,51.93],"l":[-177.21,51.841,-177.257,51.805,-177.335,51.776,-177.668,51.721,-177.67,51.701,-177.655,51.677,-177.578,51.694,-177.475,51.701,-177.382,51.705,-177.23,51.694,-177.177,51.704,-177.148,51.717,-177.135,51.807,-177.121,51.836,-177.08,51.867,-177.063,51.902,-177.11,51.929,-177.131,51.93,-177.166,51.909,-177.21,51.841]},{"b":[-178.195,51.616,-177.644,51.918],"l":[-177.644,51.826,-177.8,51.84,-177.866,51.86,-177.954,51.918,-178.117,51.916,-178.168,51.903,-178.195,51.882,-178.153,51.848,-178.045,51.801,-177.986,51.764,-177.977,51.738,-178,51.717,-178.078,51.691,-178.059,51.673,-177.925,51.617,-177.901,51.616,-177.879,51.65,-177.827,51.686,-177.771,51.778,-177.725,51.802,-177.644,51.826]},{"b":[73.251,-53.185,73.838,-52.966],"l":[73.586,-53.027,73.388,-53,73.305,-52.966,73.251,-52.976,73.254,-52.989,73.285,-53.021,73.336,-53.03,73.413,-53.147,73.465,-53.184,73.588,-53.185,73.707,-53.137,73.795,-53.13,73.838,-53.113,73.731,-53.091,73.586,-53.027]},{"b":[69.15,-49.067,69.395,-48.879],"l":[69.221,-49.067,69.282,-49.059,69.321,-49.034,69.395,-48.951,69.369,-48.89,69.266,-48.879,69.167,-48.883,69.15,-48.919,69.17,-48.957,69.204,-48.991,69.202,-49.034,69.221,-49.067]},{"b":[51.659,-46.449,51.835,-46.327],"l":[51.742,-46.327,51.659,-46.374,51.697,-46.428,51.762,-46.449,51.835,-46.44,51.815,-46.395,51.784,-46.359,51.742,-46.327]},{"b":[37.59,-46.963,37.888,-46.824],"l":[37.59,-46.908,37.612,-46.946,37.814,-46.963,37.857,-46.944,37.888,-46.902,37.873,-46.885,37.79,-46.837,37.685,-46.824,37.65,-46.849,37.59,-46.908]},{"b":[27.716,35.908,28.232,36.434],"l":[27.914,36.345,27.815,36.277,27.774,36.214,27.716,36.172,27.719,36.141,27.757,36.069,27.716,35.957,27.746,35.911,27.771,35.908,27.843,35.929,27.966,36.048,28.088,36.065,28.068,36.13,28.144,36.21,28.23,36.37,28.232,36.434,28.171,36.426,27.914,36.345]},{"b":[27.071,35.409,27.234,35.82],"l":[27.157,35.629,27.207,35.714,27.223,35.82,27.158,35.789,27.156,35.726,27.071,35.598,27.116,35.511,27.099,35.456,27.138,35.409,27.176,35.465,27.234,35.479,27.209,35.559,27.157,35.629]},{"b":[27.786,36.538,27.87,36.641],"l":[27.863,36.641,27.837,36.635,27.786,36.608,27.788,36.584,27.838,36.538,27.86,36.554,27.869,36.583,27.87,36.623,27.863,36.641]},{"b":[26.889,36.945,27.04,37.087],"l":[27.04,37.002,27.016,37.01,26.967,37.052,26.889,37.087,26.938,37.025,26.92,36.945,27.02,36.959,27.035,36.976,27.04,37.002]},{"b":[26.918,36.726,27.352,36.905],"l":[27.061,36.84,26.956,36.774,26.918,36.726,26.95,36.727,27.034,36.771,27.151,36.778,27.193,36.809,27.352,36.869,27.266,36.905,27.215,36.899,27.061,36.84]},{"b":[25.362,36.93,25.588,37.196],"l":[25.362,37.07,25.396,36.984,25.457,36.93,25.546,36.968,25.584,37.039,25.588,37.153,25.564,37.185,25.525,37.196,25.362,37.07]},{"b":[25.105,36.991,25.279,37.149],"l":[25.275,37.138,25.235,37.149,25.146,37.107,25.105,37.035,25.133,37,25.199,36.991,25.279,37.068,25.271,37.084,25.275,37.138]},{"b":[25.371,36.34,25.482,36.474],"l":[25.415,36.442,25.413,36.405,25.397,36.379,25.371,36.359,25.436,36.34,25.482,36.393,25.467,36.435,25.409,36.474,25.397,36.465,25.415,36.442]},{"b":[25.26,36.658,25.407,36.789],"l":[25.364,36.658,25.382,36.674,25.407,36.717,25.296,36.789,25.26,36.758,25.289,36.722,25.364,36.658]},{"b":[26.581,37.645,27.055,37.811],"l":[26.982,37.782,26.947,37.778,26.824,37.811,26.743,37.81,26.639,37.781,26.581,37.724,26.613,37.71,26.721,37.705,26.788,37.657,26.845,37.645,26.978,37.7,27.055,37.709,27.04,37.77,26.982,37.782]},{"b":[25.982,37.526,26.351,37.674],"l":[26.212,37.638,26.086,37.635,25.997,37.566,25.982,37.526,26.029,37.529,26.205,37.569,26.297,37.62,26.351,37.674,26.326,37.673,26.212,37.638]},{"b":[25.743,36.782,26.064,36.937],"l":[25.942,36.887,25.852,36.848,25.834,36.825,25.797,36.807,25.743,36.79,25.771,36.782,25.859,36.79,25.985,36.88,26.064,36.903,26.001,36.937,25.942,36.887]},{"b":[26.27,36.511,26.461,36.639],"l":[26.337,36.581,26.27,36.595,26.27,36.547,26.331,36.511,26.382,36.562,26.461,36.585,26.421,36.624,26.37,36.639,26.384,36.608,26.337,36.581]},{"b":[24.277,37.528,24.401,37.683],"l":[24.277,37.601,24.289,37.528,24.356,37.577,24.401,37.649,24.379,37.683,24.32,37.678,24.277,37.601]},{"b":[24.37,37.314,24.481,37.475],"l":[24.398,37.383,24.379,37.314,24.436,37.344,24.481,37.408,24.449,37.45,24.431,37.475,24.395,37.45,24.37,37.42,24.398,37.383]},{"b":[24.326,36.656,24.538,36.764],"l":[24.425,36.713,24.357,36.744,24.345,36.723,24.326,36.656,24.531,36.684,24.538,36.705,24.536,36.764,24.46,36.747,24.45,36.729,24.425,36.713]},{"b":[24.895,37.39,24.943,37.509],"l":[24.943,37.494,24.907,37.509,24.895,37.446,24.896,37.407,24.912,37.391,24.938,37.39,24.943,37.494]},{"b":[24.7,37.692,24.992,37.99],"l":[24.948,37.858,24.956,37.905,24.855,37.914,24.79,37.99,24.763,37.988,24.7,37.962,24.714,37.899,24.767,37.871,24.799,37.824,24.884,37.771,24.962,37.692,24.992,37.76,24.98,37.797,24.948,37.858]},{"b":[24.996,37.535,25.256,37.681],"l":[25.156,37.545,25.219,37.535,25.256,37.6,25.225,37.631,25.092,37.648,25.039,37.681,24.996,37.677,25.016,37.646,25.052,37.614,25.156,37.545]},{"b":[24.671,36.917,24.763,37.024],"l":[24.676,36.959,24.703,36.917,24.721,36.921,24.763,36.949,24.716,37.024,24.681,37.022,24.671,36.999,24.676,36.959]},{"b":[25.846,38.162,26.16,38.602],"l":[26.013,38.602,25.846,38.574,25.851,38.508,25.96,38.416,25.991,38.354,25.953,38.303,25.874,38.27,25.892,38.243,25.999,38.162,26.094,38.218,26.103,38.234,26.111,38.28,26.157,38.303,26.15,38.468,26.141,38.486,26.16,38.541,26.11,38.545,26.013,38.602]},{"b":[25.844,38.973,26.596,39.383],"l":[26.531,39.172,26.393,39.27,26.41,39.329,26.348,39.383,26.165,39.374,26.165,39.332,26.088,39.304,26.026,39.285,25.91,39.288,25.844,39.2,25.855,39.179,25.906,39.139,26.072,39.096,26.176,39.194,26.273,39.198,26.245,39.164,26.108,39.081,26.161,39.026,26.39,38.974,26.469,38.973,26.547,38.994,26.503,39.031,26.489,39.075,26.532,39.064,26.584,39.031,26.596,39.049,26.578,39.11,26.531,39.172]},{"b":[23.594,39.081,23.78,39.209],"l":[23.735,39.081,23.78,39.114,23.594,39.209,23.666,39.095,23.735,39.081]},{"b":[23.841,39.147,23.971,39.268],"l":[23.971,39.268,23.888,39.226,23.841,39.147,23.888,39.158,23.94,39.201,23.971,39.268]},{"b":[24.461,38.785,24.675,38.98],"l":[24.581,38.879,24.564,38.942,24.486,38.98,24.473,38.962,24.461,38.889,24.566,38.832,24.565,38.819,24.541,38.789,24.569,38.785,24.675,38.809,24.581,38.879]},{"b":[25.448,40.4,25.686,40.516],"l":[25.569,40.516,25.448,40.483,25.573,40.4,25.686,40.427,25.664,40.464,25.624,40.492,25.569,40.516]},{"b":[25.052,39.806,25.449,40.035],"l":[25.374,40.016,25.348,39.985,25.286,39.956,25.234,40.005,25.058,40,25.052,39.976,25.065,39.91,25.062,39.852,25.126,39.826,25.185,39.83,25.203,39.849,25.224,39.893,25.249,39.894,25.252,39.854,25.263,39.823,25.299,39.806,25.357,39.808,25.372,39.891,25.4,39.95,25.438,39.983,25.449,40.035,25.374,40.016]},{"b":[24.516,40.579,24.786,40.793],"l":[24.623,40.793,24.586,40.769,24.517,40.687,24.516,40.647,24.646,40.579,24.774,40.615,24.769,40.659,24.786,40.704,24.774,40.73,24.719,40.786,24.623,40.793]},{"b":[25.669,40.105,25.977,40.238],"l":[25.741,40.196,25.669,40.136,25.74,40.105,25.97,40.136,25.977,40.178,25.918,40.238,25.875,40.234,25.741,40.196]},{"b":[-16.236,11.045,-16.053,11.199],"l":[-16.067,11.197,-16.087,11.199,-16.105,11.191,-16.144,11.167,-16.176,11.131,-16.195,11.13,-16.236,11.113,-16.231,11.094,-16.195,11.045,-16.115,11.059,-16.072,11.084,-16.053,11.118,-16.067,11.197]},{"b":[-16.477,19.609,-16.344,19.866],"l":[-16.42,19.802,-16.477,19.71,-16.466,19.646,-16.438,19.609,-16.373,19.706,-16.344,19.866,-16.393,19.849,-16.42,19.802]},{"b":[-17.241,32.648,-16.693,32.869],"l":[-16.693,32.758,-16.774,32.774,-16.929,32.841,-17.054,32.816,-17.191,32.869,-17.241,32.807,-17.226,32.767,-17.171,32.722,-17.018,32.663,-16.837,32.648,-16.765,32.71,-16.693,32.758]},{"b":[-15.964,11.055,-15.896,11.193],"l":[-15.963,11.095,-15.951,11.087,-15.964,11.059,-15.905,11.055,-15.896,11.082,-15.905,11.148,-15.909,11.161,-15.938,11.193,-15.946,11.18,-15.963,11.095]},{"b":[-16.905,28.007,-16.119,28.576],"l":[-16.334,28.38,-16.119,28.528,-16.124,28.576,-16.319,28.558,-16.517,28.413,-16.557,28.4,-16.752,28.37,-16.843,28.376,-16.905,28.34,-16.866,28.293,-16.795,28.149,-16.658,28.007,-16.543,28.032,-16.496,28.062,-16.418,28.151,-16.334,28.38]},{"b":[-13.86,28.845,-13.423,29.237],"l":[-13.535,29.144,-13.65,29.119,-13.788,29.056,-13.824,29.013,-13.86,28.869,-13.784,28.845,-13.716,28.911,-13.555,28.96,-13.478,29.007,-13.454,29.151,-13.423,29.198,-13.464,29.237,-13.501,29.211,-13.535,29.144]},{"b":[-14.492,28.056,-13.827,28.745],"l":[-14.028,28.617,-14.153,28.407,-14.232,28.216,-14.356,28.13,-14.492,28.101,-14.469,28.082,-14.333,28.056,-14.197,28.169,-13.928,28.253,-13.863,28.409,-13.828,28.585,-13.827,28.691,-13.857,28.738,-13.886,28.745,-13.954,28.741,-14.003,28.707,-14.028,28.617]},{"b":[-12.952,7.41,-12.501,7.637],"l":[-12.615,7.637,-12.854,7.622,-12.952,7.571,-12.607,7.475,-12.541,7.41,-12.526,7.436,-12.501,7.535,-12.512,7.582,-12.544,7.607,-12.615,7.637]},{"b":[-15.809,27.747,-15.383,28.159],"l":[-15.401,28.147,-15.415,28.159,-15.433,28.154,-15.453,28.137,-15.683,28.154,-15.721,28.064,-15.809,27.994,-15.807,27.888,-15.71,27.784,-15.656,27.758,-15.559,27.747,-15.437,27.811,-15.389,27.875,-15.383,27.993,-15.407,28.071,-15.401,28.147]},{"b":[-15.78,11.175,-15.658,11.302],"l":[-15.717,11.302,-15.755,11.269,-15.78,11.195,-15.767,11.182,-15.725,11.175,-15.725,11.215,-15.687,11.234,-15.667,11.258,-15.658,11.286,-15.672,11.296,-15.717,11.302]},{"b":[-15.62,11.514,-15.482,11.632],"l":[-15.537,11.618,-15.62,11.533,-15.563,11.514,-15.553,11.537,-15.526,11.554,-15.484,11.568,-15.482,11.632,-15.537,11.618]},{"b":[-25.342,16.926,-24.98,17.194],"l":[-24.98,17.095,-25.035,17.176,-25.113,17.194,-25.337,17.091,-25.342,17.068,-25.322,17.015,-25.308,16.936,-25.267,16.926,-25.17,16.946,-25.017,17.049,-24.98,17.095]},{"b":[-25.093,16.794,-24.887,16.922],"l":[-24.991,16.913,-25.07,16.871,-25.093,16.833,-25.02,16.797,-24.969,16.794,-24.887,16.818,-24.892,16.846,-24.936,16.922,-24.991,16.913]},{"b":[-17.325,28.014,-17.101,28.203],"l":[-17.325,28.118,-17.274,28.038,-17.225,28.014,-17.185,28.022,-17.101,28.083,-17.104,28.111,-17.13,28.156,-17.214,28.199,-17.259,28.203,-17.29,28.176,-17.325,28.118]},{"b":[-16.023,11.434,-15.902,11.598],"l":[-15.949,11.434,-15.902,11.466,-15.915,11.589,-15.965,11.598,-16.019,11.527,-16.023,11.477,-15.997,11.449,-15.949,11.434]},{"b":[-16.152,11.76,-15.986,11.887],"l":[-16.102,11.773,-16.038,11.76,-15.986,11.882,-16.022,11.887,-16.152,11.877,-16.147,11.846,-16.102,11.773]},{"b":[-18.161,27.646,-17.888,27.85],"l":[-18.043,27.768,-18.161,27.761,-18.136,27.728,-18.107,27.707,-17.985,27.646,-17.888,27.81,-17.925,27.85,-18.043,27.768]},{"b":[-22.959,15.986,-22.682,16.237],"l":[-22.693,16.169,-22.749,16.222,-22.803,16.226,-22.834,16.219,-22.918,16.237,-22.916,16.148,-22.959,16.045,-22.884,15.993,-22.821,15.986,-22.71,16.043,-22.682,16.113,-22.693,16.169]},{"b":[-24.517,14.818,-24.296,15.038],"l":[-24.329,15.019,-24.392,15.038,-24.497,14.98,-24.517,14.931,-24.492,14.874,-24.441,14.835,-24.386,14.818,-24.308,14.856,-24.296,14.93,-24.329,15.019]},{"b":[-18.001,28.486,-17.727,28.847],"l":[-17.929,28.845,-18.001,28.758,-17.882,28.565,-17.859,28.486,-17.834,28.493,-17.758,28.569,-17.744,28.616,-17.752,28.689,-17.727,28.724,-17.745,28.787,-17.798,28.847,-17.929,28.845]},{"b":[-24.398,16.493,-24.033,16.678],"l":[-24.271,16.645,-24.377,16.678,-24.393,16.664,-24.398,16.618,-24.322,16.493,-24.283,16.576,-24.243,16.599,-24.094,16.561,-24.033,16.572,-24.046,16.593,-24.088,16.623,-24.271,16.645]},{"b":[-22.991,16.608,-22.888,16.844],"l":[-22.904,16.732,-22.905,16.844,-22.933,16.841,-22.991,16.809,-22.981,16.701,-22.959,16.683,-22.92,16.608,-22.888,16.659,-22.904,16.732]},{"b":[-23.252,15.133,-23.116,15.324],"l":[-23.21,15.324,-23.247,15.257,-23.242,15.241,-23.252,15.178,-23.21,15.133,-23.182,15.137,-23.116,15.167,-23.119,15.268,-23.138,15.318,-23.21,15.324]},{"b":[-23.785,14.916,-23.444,15.329],"l":[-23.535,15.139,-23.58,15.161,-23.701,15.272,-23.707,15.317,-23.748,15.329,-23.759,15.311,-23.754,15.244,-23.783,15.166,-23.785,15.077,-23.705,14.961,-23.637,14.923,-23.505,14.916,-23.444,15.008,-23.535,15.139]},{"b":[44.22,-12.357,44.527,-12.071],"l":[44.476,-12.082,44.452,-12.071,44.413,-12.093,44.407,-12.12,44.379,-12.166,44.334,-12.173,44.292,-12.165,44.22,-12.171,44.377,-12.252,44.46,-12.335,44.505,-12.357,44.526,-12.324,44.527,-12.22,44.476,-12.082]},{"b":[43.631,-12.368,43.859,-12.247],"l":[43.704,-12.256,43.631,-12.247,43.633,-12.288,43.664,-12.343,43.859,-12.368,43.789,-12.307,43.704,-12.256]},{"b":[45.043,-12.985,45.223,-12.653],"l":[45.135,-12.709,45.092,-12.653,45.043,-12.701,45.094,-12.786,45.088,-12.835,45.069,-12.896,45.088,-12.958,45.118,-12.985,45.18,-12.977,45.179,-12.92,45.209,-12.848,45.204,-12.824,45.223,-12.752,45.159,-12.713,45.135,-12.709]},{"b":[43.227,-11.915,43.492,-11.368],"l":[43.379,-11.614,43.393,-11.409,43.342,-11.368,43.299,-11.375,43.281,-11.391,43.256,-11.432,43.227,-11.752,43.303,-11.844,43.355,-11.858,43.447,-11.915,43.466,-11.901,43.492,-11.862,43.448,-11.753,43.379,-11.614]},{"b":[39.603,-7.991,39.907,-7.649],"l":[39.847,-7.73,39.717,-7.832,39.661,-7.901,39.603,-7.936,39.636,-7.978,39.657,-7.991,39.711,-7.977,39.762,-7.912,39.824,-7.901,39.898,-7.728,39.907,-7.649,39.891,-7.663,39.847,-7.73]},{"b":[40.957,-2.167,41.139,-2.037],"l":[40.976,-2.11,40.957,-2.167,40.994,-2.158,41.118,-2.1,41.137,-2.085,41.139,-2.07,41.131,-2.053,41.086,-2.037,40.976,-2.11]},{"b":[39.182,-6.454,39.573,-5.722],"l":[39.488,-6.166,39.433,-6.115,39.368,-5.951,39.357,-5.812,39.309,-5.722,39.267,-5.853,39.192,-5.931,39.206,-6.083,39.182,-6.173,39.243,-6.275,39.313,-6.279,39.383,-6.365,39.424,-6.348,39.447,-6.42,39.481,-6.454,39.509,-6.452,39.563,-6.427,39.573,-6.387,39.496,-6.175,39.488,-6.166]},{"b":[57.318,-20.513,57.792,-19.99],"l":[57.737,-20.098,57.657,-19.99,57.576,-19.997,57.515,-20.056,57.486,-20.144,57.416,-20.184,57.386,-20.229,57.362,-20.338,57.365,-20.406,57.318,-20.428,57.328,-20.45,57.383,-20.504,57.525,-20.513,57.651,-20.485,57.707,-20.435,57.725,-20.369,57.781,-20.327,57.792,-20.213,57.737,-20.098]},{"b":[55.233,-21.369,55.839,-20.865],"l":[55.662,-20.906,55.596,-20.88,55.45,-20.865,55.311,-20.904,55.25,-21.002,55.233,-21.058,55.31,-21.217,55.363,-21.274,55.558,-21.358,55.656,-21.369,55.797,-21.339,55.822,-21.278,55.839,-21.139,55.739,-21.021,55.662,-20.906]},{"b":[48.191,-13.4,48.351,-13.198],"l":[48.351,-13.31,48.309,-13.198,48.27,-13.205,48.256,-13.256,48.191,-13.26,48.212,-13.385,48.344,-13.4,48.342,-13.364,48.351,-13.31]},{"b":[49.824,-17.087,50.023,-16.695],"l":[49.856,-16.933,49.824,-17.087,49.936,-16.903,50.023,-16.695,49.986,-16.712,49.856,-16.933]},{"b":[-5.783,-16.004,-5.66,-15.906],"l":[-5.775,-15.957,-5.783,-16.004,-5.692,-15.998,-5.66,-15.971,-5.662,-15.913,-5.708,-15.906,-5.775,-15.957]},{"b":[39.647,-5.444,39.871,-4.906],"l":[39.749,-5.444,39.796,-5.394,39.853,-5.255,39.859,-5.155,39.856,-5.004,39.871,-4.957,39.865,-4.906,39.781,-4.945,39.673,-4.927,39.701,-5.114,39.647,-5.369,39.673,-5.407,39.708,-5.429,39.749,-5.444]},{"b":[55.383,-4.786,55.543,-4.559],"l":[55.481,-4.695,55.495,-4.755,55.543,-4.786,55.54,-4.693,55.456,-4.559,55.383,-4.609,55.417,-4.65,55.481,-4.695]},{"b":[8.434,3.217,8.951,3.758],"l":[8.76,3.754,8.736,3.758,8.676,3.736,8.638,3.669,8.623,3.58,8.577,3.482,8.55,3.468,8.465,3.451,8.452,3.423,8.434,3.332,8.445,3.294,8.475,3.265,8.652,3.217,8.704,3.224,8.763,3.305,8.792,3.4,8.946,3.628,8.951,3.705,8.91,3.758,8.76,3.754]},{"b":[6.468,0.047,6.75,0.404],"l":[6.626,0.4,6.524,0.34,6.478,0.28,6.468,0.227,6.497,0.117,6.52,0.066,6.557,0.047,6.66,0.121,6.75,0.243,6.75,0.326,6.687,0.404,6.626,0.4]},{"b":[7.331,1.542,7.452,1.699],"l":[7.414,1.699,7.388,1.68,7.331,1.603,7.342,1.564,7.387,1.542,7.424,1.568,7.452,1.631,7.45,1.662,7.437,1.683,7.414,1.699]},{"b":[7.14,4.388,7.328,4.527],"l":[7.227,4.527,7.14,4.395,7.204,4.388,7.301,4.418,7.328,4.487,7.271,4.499,7.227,4.527]},{"b":[-44.36,-23.212,-44.098,-23.074],"l":[-44.22,-23.083,-44.243,-23.074,-44.274,-23.116,-44.36,-23.172,-44.32,-23.212,-44.221,-23.191,-44.156,-23.167,-44.098,-23.169,-44.129,-23.142,-44.192,-23.113,-44.22,-23.083]},{"b":[-48.666,-26.414,-48.498,-26.17],"l":[-48.498,-26.219,-48.54,-26.17,-48.666,-26.29,-48.603,-26.414,-48.584,-26.402,-48.568,-26.38,-48.531,-26.313,-48.498,-26.219]},{"b":[-48.555,-27.812,-48.378,-27.4],"l":[-48.542,-27.575,-48.555,-27.812,-48.486,-27.767,-48.497,-27.707,-48.41,-27.566,-48.378,-27.451,-48.415,-27.4,-48.465,-27.436,-48.505,-27.496,-48.542,-27.575]},{"b":[-45.451,-23.941,-45.233,-23.728],"l":[-45.233,-23.825,-45.249,-23.783,-45.272,-23.752,-45.302,-23.728,-45.451,-23.896,-45.413,-23.935,-45.303,-23.915,-45.261,-23.941,-45.26,-23.889,-45.25,-23.853,-45.233,-23.825]},{"b":[11.124,34.682,11.281,34.82],"l":[11.255,34.82,11.153,34.745,11.124,34.682,11.278,34.754,11.281,34.802,11.255,34.82]},{"b":[10.722,33.687,11.038,33.893],"l":[10.922,33.893,10.745,33.889,10.734,33.856,10.722,33.739,10.757,33.717,10.785,33.718,10.857,33.687,10.883,33.69,10.931,33.717,10.958,33.722,10.993,33.746,11.038,33.785,11.034,33.805,11.018,33.823,10.922,33.893]},{"b":[-4.785,54.059,-4.338,54.407],"l":[-4.412,54.185,-4.392,54.225,-4.338,54.269,-4.377,54.393,-4.396,54.403,-4.425,54.407,-4.509,54.377,-4.615,54.267,-4.699,54.225,-4.746,54.119,-4.785,54.073,-4.766,54.069,-4.696,54.081,-4.614,54.059,-4.412,54.185]},{"b":[-6.669,56.579,-6.484,56.672],"l":[-6.57,56.661,-6.669,56.594,-6.664,56.579,-6.608,56.585,-6.53,56.627,-6.484,56.666,-6.506,56.672,-6.57,56.661]},{"b":[-5.393,55.448,-5.095,55.717],"l":[-5.105,55.574,-5.16,55.667,-5.185,55.691,-5.252,55.717,-5.318,55.709,-5.346,55.691,-5.371,55.667,-5.393,55.618,-5.331,55.481,-5.277,55.457,-5.231,55.448,-5.105,55.449,-5.095,55.494,-5.105,55.574]},{"b":[-6.326,56.289,-5.761,56.65],"l":[-5.836,56.523,-5.947,56.535,-6.03,56.61,-6.103,56.646,-6.138,56.65,-6.182,56.643,-6.286,56.612,-6.306,56.599,-6.32,56.569,-6.311,56.552,-6.139,56.491,-6.185,56.357,-6.298,56.339,-6.326,56.321,-6.313,56.294,-6.176,56.289,-5.778,56.344,-5.761,56.491,-5.836,56.523]},{"b":[-6.496,55.607,-6.055,55.931],"l":[-6.129,55.931,-6.216,55.905,-6.311,55.856,-6.344,55.874,-6.375,55.871,-6.413,55.855,-6.445,55.832,-6.463,55.808,-6.466,55.769,-6.496,55.712,-6.491,55.697,-6.452,55.704,-6.334,55.774,-6.302,55.781,-6.286,55.773,-6.302,55.728,-6.27,55.67,-6.307,55.619,-6.305,55.607,-6.253,55.607,-6.088,55.658,-6.055,55.695,-6.058,55.723,-6.093,55.802,-6.129,55.931]},{"b":[-6.072,55.804,-5.725,56.12],"l":[-5.939,56.045,-5.973,56.004,-5.97,55.992,-5.912,55.975,-6.041,55.926,-6.072,55.893,-6.071,55.848,-6.06,55.823,-6.042,55.807,-5.991,55.804,-5.97,55.815,-5.797,56.006,-5.725,56.119,-5.762,56.12,-5.8,56.109,-5.939,56.045]},{"b":[-1.166,60.502,-0.992,60.72],"l":[-1.093,60.72,-1.166,60.604,-1.068,60.502,-1.043,60.514,-1.034,60.53,-1.035,60.593,-1.049,60.647,-1.045,60.656,-1,60.658,-0.992,60.686,-1.006,60.717,-1.093,60.72]},{"b":[-1.664,59.879,-1.052,60.61],"l":[-1.236,60.485,-1.287,60.467,-1.308,60.538,-1.302,60.608,-1.364,60.61,-1.414,60.599,-1.498,60.53,-1.553,60.517,-1.572,60.494,-1.549,60.481,-1.45,60.469,-1.375,60.333,-1.494,60.292,-1.577,60.298,-1.664,60.283,-1.66,60.262,-1.641,60.237,-1.613,60.229,-1.517,60.231,-1.499,60.222,-1.497,60.194,-1.481,60.173,-1.409,60.19,-1.323,60.188,-1.291,60.153,-1.276,60.115,-1.3,60.04,-1.356,59.911,-1.299,59.879,-1.284,59.887,-1.245,59.971,-1.199,60.007,-1.179,60.114,-1.166,60.124,-1.153,60.177,-1.134,60.207,-1.066,60.382,-1.052,60.444,-1.118,60.418,-1.158,60.418,-1.236,60.485]},{"b":[-0.938,60.684,-0.774,60.832],"l":[-0.825,60.684,-0.826,60.716,-0.774,60.8,-0.774,60.812,-0.802,60.831,-0.823,60.832,-0.865,60.806,-0.891,60.816,-0.916,60.81,-0.928,60.797,-0.938,60.746,-0.922,60.697,-0.909,60.687,-0.825,60.684]},{"b":[-3.401,58.781,-3.159,58.919],"l":[-3.272,58.905,-3.357,58.919,-3.395,58.91,-3.401,58.882,-3.367,58.84,-3.279,58.782,-3.222,58.781,-3.165,58.794,-3.159,58.801,-3.212,58.814,-3.222,58.826,-3.228,58.857,-3.272,58.905]},{"b":[-4.568,53.134,-4.049,53.419],"l":[-4.315,53.417,-4.462,53.419,-4.568,53.386,-4.553,53.26,-4.472,53.176,-4.419,53.178,-4.373,53.134,-4.279,53.172,-4.2,53.218,-4.084,53.264,-4.049,53.306,-4.155,53.303,-4.197,53.321,-4.315,53.417]},{"b":[-2.662,59.23,-2.407,59.304],"l":[-2.536,59.304,-2.604,59.289,-2.662,59.23,-2.549,59.231,-2.43,59.271,-2.407,59.298,-2.536,59.304]},{"b":[-3.035,58.739,-2.896,58.836],"l":[-3.035,58.823,-2.975,58.757,-2.939,58.739,-2.929,58.742,-2.913,58.8,-2.896,58.828,-2.941,58.836,-3.035,58.823]},{"b":[-3.354,58.891,-2.762,59.144],"l":[-2.818,58.982,-2.885,58.985,-2.995,59.006,-3.071,59.005,-3.057,59.03,-3.02,59.058,-3.019,59.076,-3.051,59.099,-3.156,59.136,-3.249,59.144,-3.31,59.131,-3.347,59.065,-3.354,59.019,-3.347,58.987,-3.332,58.971,-3.304,58.967,-3.242,59,-3.233,58.99,-3.233,58.956,-3.223,58.939,-3.201,58.925,-3.167,58.919,-2.995,58.939,-2.864,58.891,-2.826,58.893,-2.793,58.907,-2.762,58.956,-2.818,58.982]},{"b":[-6.885,61.768,-6.623,61.904],"l":[-6.662,61.862,-6.791,61.895,-6.842,61.904,-6.885,61.899,-6.864,61.862,-6.839,61.841,-6.764,61.815,-6.67,61.769,-6.643,61.768,-6.623,61.806,-6.626,61.827,-6.662,61.862]},{"b":[-6.935,61.414,-6.68,61.634],"l":[-6.704,61.496,-6.741,61.536,-6.741,61.571,-6.77,61.584,-6.882,61.603,-6.906,61.631,-6.935,61.634,-6.929,61.603,-6.888,61.535,-6.771,61.452,-6.703,61.418,-6.68,61.414,-6.699,61.445,-6.704,61.496]},{"b":[-7.423,62.04,-7.065,62.151],"l":[-7.379,62.075,-7.255,62.046,-7.179,62.04,-7.117,62.047,-7.065,62.073,-7.097,62.101,-7.187,62.139,-7.235,62.151,-7.337,62.139,-7.423,62.14,-7.379,62.075]},{"b":[-7.172,61.951,-6.631,62.316],"l":[-7.014,62.094,-6.81,61.977,-6.725,61.951,-6.714,61.964,-6.723,61.99,-6.809,62.08,-6.838,62.095,-6.841,62.119,-6.823,62.139,-6.769,62.131,-6.696,62.094,-6.656,62.094,-6.631,62.228,-6.804,62.266,-6.959,62.316,-7.172,62.286,-7.014,62.094]},{"b":[-3.052,59.162,-2.72,59.347],"l":[-2.964,59.274,-2.861,59.247,-2.852,59.182,-2.815,59.162,-2.729,59.187,-2.72,59.219,-2.731,59.227,-2.815,59.241,-2.862,59.288,-2.976,59.347,-3.042,59.334,-3.052,59.324,-3.013,59.291,-2.964,59.274]},{"b":[-6.559,62.187,-6.406,62.356],"l":[-6.454,62.187,-6.406,62.259,-6.473,62.292,-6.555,62.356,-6.552,62.278,-6.559,62.225,-6.544,62.206,-6.525,62.198,-6.454,62.187]},{"b":[-7.096,57.75,-6.194,58.503],"l":[-6.854,57.827,-6.797,57.828,-6.683,57.911,-6.578,57.941,-6.425,58.021,-6.402,58.041,-6.403,58.076,-6.437,58.092,-6.555,58.093,-6.419,58.141,-6.376,58.185,-6.326,58.189,-6.199,58.363,-6.194,58.435,-6.219,58.489,-6.237,58.503,-6.297,58.487,-6.544,58.383,-6.742,58.322,-6.776,58.302,-6.788,58.284,-6.725,58.198,-6.726,58.189,-6.812,58.196,-6.886,58.183,-6.95,58.218,-7.012,58.229,-7.028,58.222,-7.045,58.202,-7.085,58.182,-7.096,58.138,-7.088,58.095,-7.077,58.079,-7.038,58.072,-7.017,58.055,-6.985,58.05,-7.052,58.018,-7.057,58.003,-7.003,57.975,-6.864,57.933,-6.857,57.924,-6.944,57.894,-6.956,57.865,-7.083,57.814,-7.013,57.762,-6.983,57.75,-6.957,57.75,-6.91,57.773,-6.854,57.827]},{"b":[-6.433,56.952,-6.261,57.051],"l":[-6.433,57.018,-6.383,56.971,-6.346,56.954,-6.309,56.952,-6.279,56.965,-6.261,56.985,-6.261,57.01,-6.278,57.031,-6.322,57.051,-6.433,57.018]},{"b":[-6.761,57.044,-5.669,57.672],"l":[-6.362,57.238,-6.323,57.202,-6.266,57.184,-6.163,57.182,-6.034,57.201,-6.015,57.052,-5.987,57.044,-5.949,57.045,-5.914,57.063,-5.795,57.147,-5.696,57.198,-5.669,57.227,-5.672,57.253,-5.706,57.269,-5.88,57.263,-6.068,57.284,-6.093,57.302,-6.136,57.314,-6.141,57.354,-6.164,57.409,-6.146,57.461,-6.145,57.505,-6.166,57.585,-6.247,57.651,-6.306,57.672,-6.358,57.667,-6.379,57.603,-6.617,57.563,-6.615,57.553,-6.583,57.521,-6.583,57.507,-6.606,57.491,-6.643,57.483,-6.704,57.496,-6.753,57.459,-6.761,57.442,-6.741,57.412,-6.675,57.363,-6.442,57.327,-6.362,57.238]},{"b":[-7.516,57.533,-7.093,57.683],"l":[-7.206,57.683,-7.271,57.657,-7.325,57.663,-7.392,57.645,-7.44,57.656,-7.47,57.653,-7.499,57.636,-7.516,57.616,-7.515,57.602,-7.321,57.534,-7.183,57.533,-7.093,57.627,-7.206,57.683]},{"b":[-7.422,57.11,-7.248,57.384],"l":[-7.296,57.384,-7.411,57.381,-7.407,57.298,-7.422,57.229,-7.416,57.192,-7.381,57.131,-7.347,57.115,-7.292,57.11,-7.25,57.115,-7.248,57.126,-7.267,57.372,-7.296,57.384]},{"b":[-10.266,53.885,-9.948,54.017],"l":[-10.14,54.005,-10.181,54.017,-10.266,53.978,-10.062,53.96,-10.027,53.92,-9.953,53.885,-9.948,53.913,-9.956,53.987,-9.996,54.003,-10.14,54.005]},{"b":[-7.543,56.952,-7.399,57.019],"l":[-7.523,57.007,-7.543,56.972,-7.537,56.96,-7.505,56.952,-7.417,56.965,-7.399,56.983,-7.407,57,-7.455,57.019,-7.523,57.007]},{"b":[21.806,60.332,21.994,60.47],"l":[21.908,60.393,21.95,60.402,21.907,60.438,21.827,60.47,21.819,60.452,21.846,60.412,21.806,60.401,21.819,60.382,21.921,60.332,21.994,60.337,21.98,60.355,21.908,60.393]},{"b":[19.519,60.135,19.667,60.246],"l":[19.551,60.244,19.519,60.185,19.537,60.145,19.58,60.135,19.6,60.163,19.629,60.17,19.667,60.165,19.662,60.187,19.629,60.246,19.551,60.244]},{"b":[19.672,60.082,20.259,60.406],"l":[20.073,60.193,20.125,60.201,20.155,60.192,20.195,60.194,20.259,60.261,20.24,60.283,20.184,60.294,20.168,60.315,20.087,60.353,20.034,60.359,20.02,60.351,19.99,60.351,19.945,60.358,19.888,60.406,19.823,60.39,19.788,60.354,19.812,60.332,19.855,60.319,19.872,60.302,19.867,60.268,19.848,60.221,19.785,60.213,19.779,60.286,19.737,60.282,19.687,60.268,19.672,60.233,19.746,60.099,19.8,60.082,20.034,60.094,20.032,60.152,20.043,60.181,20.073,60.193]},{"b":[21.084,63.152,21.422,63.278],"l":[21.366,63.262,21.287,63.228,21.229,63.223,21.218,63.241,21.222,63.259,21.236,63.278,21.084,63.278,21.149,63.199,21.253,63.152,21.31,63.163,21.318,63.179,21.367,63.207,21.378,63.199,21.416,63.197,21.422,63.246,21.366,63.262]},{"b":[19.039,57.86,19.331,57.981],"l":[19.135,57.981,19.039,57.911,19.087,57.865,19.138,57.86,19.156,57.923,19.331,57.963,19.281,57.978,19.135,57.981]},{"b":[16.394,56.24,17.118,57.345],"l":[16.961,57.25,16.902,57.175,16.865,57.091,16.728,56.902,16.63,56.877,16.412,56.569,16.394,56.484,16.401,56.311,16.432,56.244,16.477,56.24,16.529,56.291,16.778,56.805,16.838,56.841,16.884,56.985,17.054,57.208,17.058,57.229,17.05,57.28,17.118,57.32,17.089,57.332,17.025,57.345,16.996,57.318,16.961,57.25]},{"b":[14.684,55.005,15.137,55.297],"l":[14.765,55.297,14.714,55.238,14.684,55.102,14.886,55.033,15.051,55.005,15.088,55.022,15.137,55.087,15.133,55.145,14.765,55.297]},{"b":[13.156,54.246,13.734,54.699],"l":[13.602,54.425,13.58,54.464,13.603,54.488,13.671,54.535,13.658,54.56,13.636,54.577,13.491,54.615,13.45,54.65,13.423,54.699,13.337,54.697,13.24,54.638,13.231,54.583,13.177,54.544,13.181,54.509,13.156,54.397,13.162,54.365,13.19,54.326,13.364,54.246,13.415,54.25,13.482,54.337,13.595,54.338,13.707,54.281,13.734,54.315,13.709,54.383,13.602,54.425]},{"b":[12.119,54.892,12.549,55.064],"l":[12.31,55.041,12.274,55.064,12.259,55.021,12.22,54.994,12.162,54.975,12.144,54.959,12.119,54.914,12.184,54.892,12.358,54.962,12.511,54.951,12.549,54.966,12.513,54.997,12.47,55.017,12.417,55.031,12.31,55.041]},{"b":[12.52,55.554,12.666,55.68],"l":[12.52,55.615,12.551,55.556,12.572,55.554,12.666,55.597,12.648,55.647,12.62,55.679,12.599,55.68,12.57,55.65,12.52,55.615]},{"b":[11.036,54.629,11.766,54.952],"l":[11.658,54.833,11.538,54.83,11.361,54.892,11.258,54.952,11.059,54.941,11.042,54.893,11.036,54.773,11.457,54.629,11.586,54.662,11.68,54.654,11.766,54.679,11.759,54.768,11.74,54.807,11.658,54.833]},{"b":[10.2,54.837,10.505,54.963],"l":[10.347,54.906,10.266,54.949,10.2,54.963,10.216,54.941,10.341,54.859,10.417,54.837,10.484,54.848,10.505,54.861,10.414,54.897,10.347,54.906]},{"b":[9.771,54.872,10.061,55.069],"l":[9.781,55.069,9.771,55.06,9.806,54.906,9.904,54.897,9.957,54.872,10.061,54.886,10.058,54.908,9.999,54.986,9.83,55.058,9.781,55.069]},{"b":[10.622,54.745,10.951,55.158],"l":[10.738,54.962,10.692,54.903,10.622,54.851,10.629,54.826,10.69,54.745,10.734,54.751,10.765,54.8,10.921,55.062,10.951,55.156,10.925,55.158,10.857,55.052,10.738,54.962]},{"b":[10.516,55.765,10.662,55.992],"l":[10.527,55.784,10.59,55.765,10.607,55.783,10.627,55.834,10.662,55.878,10.636,55.914,10.547,55.992,10.516,55.959,10.544,55.907,10.52,55.848,10.527,55.784]},{"b":[11.012,54.416,11.283,54.533],"l":[11.085,54.533,11.043,54.515,11.012,54.466,11.071,54.456,11.129,54.416,11.283,54.418,11.28,54.438,11.234,54.501,11.085,54.533]},{"b":[10.874,57.229,11.175,57.33],"l":[10.935,57.309,10.874,57.262,11.011,57.229,11.052,57.253,11.077,57.277,11.175,57.323,11.086,57.33,10.935,57.309]},{"b":[8.285,54.767,8.63,55.059],"l":[8.451,55.055,8.405,55.059,8.296,54.908,8.285,54.767,8.308,54.787,8.347,54.848,8.601,54.865,8.63,54.892,8.38,54.9,8.371,54.929,8.39,54.986,8.404,55.015,8.451,55.055]},{"b":[-1.389,45.817,-1.178,46.05],"l":[-1.389,46.05,-1.389,46.033,-1.369,45.968,-1.28,45.897,-1.214,45.817,-1.178,45.904,-1.285,46.003,-1.389,46.05]},{"b":[-1.563,50.589,-1.066,50.773],"l":[-1.313,50.773,-1.386,50.734,-1.516,50.703,-1.563,50.666,-1.515,50.67,-1.306,50.589,-1.251,50.589,-1.196,50.599,-1.176,50.615,-1.149,50.656,-1.066,50.69,-1.144,50.735,-1.313,50.773]},{"b":[4.709,53,4.886,53.183],"l":[4.74,53.091,4.709,53.036,4.727,53.02,4.787,53,4.886,53.071,4.886,53.183,4.74,53.091]},{"b":[10.11,42.713,10.432,42.858],"l":[10.359,42.822,10.286,42.828,10.248,42.816,10.128,42.81,10.11,42.785,10.131,42.742,10.209,42.737,10.336,42.761,10.419,42.713,10.41,42.771,10.432,42.797,10.428,42.819,10.395,42.858,10.359,42.822]},{"b":[8.359,38.969,8.479,39.116],"l":[8.367,39.116,8.359,39.099,8.361,39.039,8.421,38.969,8.479,39.068,8.441,39.091,8.367,39.116]},{"b":[8.206,40.994,8.344,41.122],"l":[8.344,41.102,8.32,41.122,8.267,41.099,8.224,41.031,8.206,40.997,8.253,40.994,8.286,41.04,8.319,41.063,8.344,41.102]},{"b":[3.843,39.83,4.322,40.075],"l":[4.059,40.075,3.853,40.063,3.845,40.036,3.843,39.976,3.867,39.959,3.968,39.946,4.275,39.83,4.294,39.842,4.322,39.898,4.315,39.917,4.226,40.032,4.059,40.075]},{"b":[1.402,38.659,1.594,38.768],"l":[1.417,38.74,1.402,38.711,1.406,38.671,1.505,38.671,1.571,38.659,1.594,38.672,1.593,38.701,1.497,38.712,1.436,38.768,1.417,38.74]},{"b":[1.223,38.857,1.624,39.121],"l":[1.349,39.081,1.303,39.031,1.3,38.982,1.256,38.973,1.223,38.904,1.257,38.879,1.409,38.857,1.445,38.919,1.495,38.933,1.624,39.039,1.613,39.087,1.564,39.121,1.349,39.081]},{"b":[-31.283,39.359,-31.137,39.521],"l":[-31.261,39.497,-31.283,39.394,-31.258,39.376,-31.181,39.359,-31.137,39.407,-31.139,39.479,-31.2,39.521,-31.261,39.497]},{"b":[-27.386,38.634,-27.042,38.803],"l":[-27.127,38.79,-27.26,38.803,-27.351,38.789,-27.386,38.766,-27.362,38.698,-27.303,38.661,-27.095,38.634,-27.075,38.643,-27.042,38.679,-27.042,38.741,-27.127,38.79]},{"b":[-28.311,38.544,-27.778,38.744],"l":[-28.311,38.744,-28.187,38.655,-28.092,38.621,-27.826,38.544,-27.778,38.556,-27.963,38.636,-28.311,38.744]},{"b":[-28.842,38.522,-28.606,38.638],"l":[-28.698,38.638,-28.842,38.598,-28.744,38.522,-28.641,38.525,-28.606,38.551,-28.624,38.586,-28.655,38.614,-28.698,38.638]},{"b":[-28.549,38.385,-28.065,38.553],"l":[-28.402,38.553,-28.51,38.553,-28.549,38.519,-28.531,38.463,-28.454,38.409,-28.332,38.413,-28.231,38.385,-28.19,38.404,-28.065,38.413,-28.147,38.453,-28.402,38.553]},{"b":[-25.848,37.715,-25.182,37.911],"l":[-25.585,37.834,-25.649,37.841,-25.784,37.911,-25.846,37.894,-25.848,37.872,-25.834,37.826,-25.734,37.763,-25.439,37.715,-25.251,37.735,-25.191,37.764,-25.182,37.838,-25.267,37.849,-25.585,37.834]},{"b":[14.351,35.82,14.566,35.978],"l":[14.448,35.957,14.351,35.978,14.352,35.872,14.436,35.822,14.533,35.82,14.566,35.853,14.537,35.886,14.448,35.957]},{"b":[15.066,44.027,15.247,44.158],"l":[15.231,44.062,15.066,44.158,15.075,44.138,15.122,44.093,15.247,44.027,15.231,44.062]},{"b":[14.438,44.956,14.81,45.225],"l":[14.739,45.065,14.701,45.09,14.63,45.178,14.571,45.225,14.525,45.147,14.438,45.099,14.45,45.079,14.512,45.035,14.613,45.025,14.628,44.994,14.687,44.956,14.81,44.977,14.739,45.065]},{"b":[14.66,44.715,14.857,44.848],"l":[14.857,44.715,14.831,44.759,14.764,44.821,14.754,44.845,14.691,44.848,14.672,44.824,14.66,44.8,14.678,44.77,14.763,44.755,14.857,44.715]},{"b":[14.742,44.309,15.24,44.697],"l":[14.913,44.486,14.996,44.434,15.039,44.393,15.098,44.358,15.163,44.309,15.188,44.336,15.214,44.348,15.24,44.35,15.113,44.436,15.006,44.534,14.898,44.611,14.855,44.618,14.804,44.649,14.742,44.697,14.76,44.665,14.885,44.545,14.913,44.486]},{"b":[14.865,43.898,15.203,44.168],"l":[14.953,44.117,14.865,44.168,14.891,44.126,15.136,43.907,15.15,43.912,15.202,43.898,15.203,43.908,15.189,43.922,14.953,44.117]},{"b":[15.27,43.9,15.437,44.011],"l":[15.374,43.915,15.437,43.9,15.371,43.974,15.27,44.011,15.309,43.961,15.374,43.915]},{"b":[14.286,44.621,14.488,45.167],"l":[14.468,44.725,14.453,44.869,14.467,44.97,14.394,45.031,14.369,45.081,14.358,45.167,14.331,45.165,14.286,45.145,14.34,45.02,14.342,44.98,14.303,44.94,14.312,44.9,14.389,44.758,14.42,44.67,14.48,44.621,14.488,44.66,14.483,44.693,14.468,44.725]},{"b":[17.344,42.7,17.744,42.8],"l":[17.39,42.799,17.344,42.79,17.744,42.7,17.608,42.769,17.432,42.8,17.39,42.799]},{"b":[20.352,38.071,20.789,38.475],"l":[20.789,38.142,20.695,38.246,20.625,38.268,20.612,38.383,20.563,38.475,20.55,38.457,20.548,38.395,20.52,38.332,20.481,38.318,20.435,38.356,20.409,38.337,20.352,38.222,20.353,38.18,20.391,38.188,20.452,38.234,20.473,38.219,20.499,38.184,20.496,38.164,20.524,38.107,20.569,38.098,20.606,38.12,20.761,38.071,20.781,38.089,20.789,38.142]},{"b":[20.62,37.665,20.994,37.93],"l":[20.84,37.841,20.759,37.853,20.692,37.93,20.62,37.855,20.635,37.823,20.704,37.743,20.819,37.665,20.909,37.732,20.994,37.708,20.888,37.805,20.84,37.841]},{"b":[20.555,38.583,20.72,38.844],"l":[20.648,38.601,20.687,38.609,20.715,38.638,20.72,38.799,20.694,38.844,20.635,38.818,20.592,38.76,20.558,38.662,20.555,38.583,20.584,38.602,20.614,38.606,20.648,38.601]},{"b":[20.624,38.319,20.759,38.484],"l":[20.701,38.451,20.675,38.476,20.65,38.484,20.624,38.48,20.646,38.414,20.709,38.319,20.759,38.329,20.739,38.366,20.712,38.399,20.701,38.426,20.701,38.451]},{"b":[19.646,39.377,20.1,39.82],"l":[19.955,39.47,19.927,39.506,19.903,39.6,19.904,39.619,19.847,39.668,19.862,39.693,19.937,39.747,19.926,39.774,19.892,39.797,19.839,39.82,19.707,39.798,19.646,39.767,19.649,39.726,19.809,39.585,19.884,39.462,19.975,39.411,20.1,39.377,20.078,39.433,20.028,39.442,19.955,39.47]},{"b":[23.419,37.901,23.551,37.991],"l":[23.511,37.901,23.551,37.926,23.535,37.97,23.516,37.986,23.484,37.991,23.462,37.98,23.439,37.941,23.419,37.931,23.435,37.911,23.467,37.902,23.511,37.901]},{"b":[22.906,36.146,23.097,36.384],"l":[22.95,36.384,22.933,36.369,22.906,36.32,22.911,36.221,22.939,36.176,23.042,36.146,23.054,36.19,23.097,36.247,22.998,36.328,22.95,36.384]},{"b":[3.699,51.627,4.075,51.746],"l":[3.949,51.739,3.789,51.746,3.699,51.73,3.699,51.71,3.732,51.678,3.819,51.694,3.951,51.627,4.075,51.649,4.068,51.667,4.047,51.685,3.949,51.739]},{"b":[-51.35,69.517,-50.679,69.924],"l":[-51.35,69.855,-51.319,69.804,-51.339,69.732,-51.315,69.674,-51.234,69.552,-51.202,69.525,-51.17,69.517,-51.014,69.552,-50.97,69.583,-50.978,69.618,-50.967,69.664,-50.912,69.757,-50.754,69.798,-50.698,69.829,-50.679,69.849,-50.94,69.909,-51.095,69.924,-51.209,69.914,-51.35,69.855]},{"b":[-37.238,65.531,-36.953,65.722],"l":[-37.048,65.722,-37.223,65.695,-37.238,65.61,-37.187,65.531,-37.031,65.532,-36.987,65.576,-36.953,65.663,-37.048,65.722]},{"b":[-53.958,71.034,-53.432,71.297],"l":[-53.629,71.034,-53.535,71.041,-53.488,71.056,-53.455,71.083,-53.437,71.115,-53.432,71.153,-53.441,71.186,-53.512,71.25,-53.584,71.297,-53.701,71.283,-53.862,71.207,-53.947,71.156,-53.958,71.128,-53.941,71.104,-53.898,71.085,-53.629,71.034]},{"b":[-56.215,72.564,-55.017,72.842],"l":[-55.524,72.568,-55.274,72.684,-55.158,72.723,-55.017,72.791,-55.033,72.821,-55.206,72.842,-55.235,72.825,-55.428,72.789,-55.516,72.781,-55.574,72.78,-55.666,72.794,-55.994,72.782,-56.078,72.753,-56.215,72.719,-56.141,72.668,-56.043,72.656,-55.936,72.668,-55.869,72.662,-55.827,72.652,-55.814,72.636,-55.781,72.617,-55.687,72.61,-55.634,72.579,-55.567,72.564,-55.524,72.568]},{"b":[-52.148,70.853,-51.607,70.976],"l":[-51.97,70.976,-52.107,70.968,-52.148,70.904,-52.144,70.882,-52.119,70.871,-51.809,70.853,-51.675,70.855,-51.607,70.869,-51.631,70.892,-51.807,70.942,-51.97,70.976]},{"b":[-46.79,60.66,-46.205,60.944],"l":[-46.394,60.909,-46.79,60.78,-46.788,60.758,-46.666,60.766,-46.553,60.741,-46.496,60.687,-46.382,60.66,-46.267,60.781,-46.254,60.842,-46.219,60.889,-46.205,60.944,-46.394,60.909]},{"b":[-126.231,49.264,-126.064,49.38],"l":[-126.115,49.365,-126.209,49.38,-126.231,49.339,-126.23,49.296,-126.187,49.278,-126.064,49.264,-126.092,49.354,-126.115,49.365]},{"b":[-122.51,47.355,-122.394,47.49],"l":[-122.507,47.422,-122.51,47.358,-122.469,47.39,-122.458,47.386,-122.457,47.359,-122.437,47.355,-122.399,47.373,-122.394,47.395,-122.442,47.446,-122.469,47.49,-122.486,47.489,-122.507,47.422]},{"b":[-122.576,47.575,-122.497,47.704],"l":[-122.517,47.691,-122.55,47.704,-122.56,47.698,-122.574,47.667,-122.576,47.619,-122.558,47.598,-122.503,47.575,-122.497,47.595,-122.508,47.683,-122.517,47.691]},{"b":[-123.003,48.594,-122.769,48.71],"l":[-122.918,48.707,-122.977,48.679,-123.003,48.652,-122.986,48.627,-122.893,48.594,-122.887,48.612,-122.903,48.665,-122.883,48.661,-122.838,48.627,-122.809,48.63,-122.769,48.651,-122.782,48.673,-122.898,48.71,-122.918,48.707]},{"b":[-122.932,48.422,-122.815,48.552],"l":[-122.869,48.549,-122.885,48.552,-122.912,48.538,-122.932,48.485,-122.922,48.457,-122.89,48.435,-122.837,48.422,-122.821,48.431,-122.815,48.452,-122.862,48.502,-122.869,48.549]},{"b":[-123.17,48.468,-122.987,48.613],"l":[-123.114,48.613,-123.162,48.606,-123.17,48.587,-123.153,48.526,-123.14,48.508,-123.094,48.489,-122.987,48.468,-123.013,48.501,-123.024,48.538,-123.114,48.613]},{"b":[-122.749,47.918,-122.367,48.384],"l":[-122.503,48.08,-122.524,48.025,-122.573,48.157,-122.598,48.2,-122.624,48.214,-122.697,48.229,-122.692,48.241,-122.542,48.294,-122.536,48.321,-122.572,48.36,-122.604,48.381,-122.629,48.384,-122.669,48.351,-122.725,48.281,-122.749,48.239,-122.742,48.225,-122.69,48.174,-122.657,48.156,-122.623,48.151,-122.606,48.129,-122.603,48.055,-122.591,48.03,-122.558,47.992,-122.492,47.981,-122.462,47.964,-122.438,47.931,-122.411,47.918,-122.383,47.923,-122.367,47.939,-122.367,47.985,-122.503,48.08]},{"b":[-124.65,49.51,-124.14,49.775],"l":[-124.494,49.667,-124.457,49.634,-124.362,49.588,-124.14,49.51,-124.154,49.531,-124.309,49.667,-124.421,49.728,-124.547,49.765,-124.623,49.775,-124.65,49.758,-124.631,49.736,-124.518,49.686,-124.494,49.667]},{"b":[-123.689,48.875,-123.372,49.095],"l":[-123.385,48.875,-123.372,48.886,-123.378,48.908,-123.482,48.955,-123.689,49.095,-123.646,49.039,-123.541,48.946,-123.385,48.875]},{"b":[-123.582,48.729,-123.407,48.926],"l":[-123.468,48.867,-123.555,48.922,-123.582,48.926,-123.518,48.75,-123.5,48.732,-123.477,48.729,-123.435,48.754,-123.407,48.756,-123.423,48.793,-123.488,48.846,-123.468,48.867]},{"b":[-125.026,50.021,-124.907,50.217],"l":[-124.978,50.03,-124.908,50.071,-124.907,50.084,-124.916,50.132,-124.938,50.166,-124.991,50.217,-124.987,50.196,-124.996,50.175,-125.026,50.134,-125.002,50.021,-124.978,50.03]},{"b":[-126.951,49.601,-126.626,49.872],"l":[-126.738,49.844,-126.826,49.872,-126.926,49.838,-126.897,49.783,-126.905,49.763,-126.94,49.75,-126.951,49.736,-126.939,49.718,-126.814,49.642,-126.743,49.613,-126.68,49.601,-126.641,49.606,-126.626,49.627,-126.628,49.675,-126.65,49.746,-126.698,49.808,-126.738,49.844]},{"b":[-125.358,50.044,-125.074,50.418],"l":[-125.126,50.32,-125.14,50.34,-125.196,50.39,-125.261,50.418,-125.301,50.414,-125.345,50.354,-125.358,50.312,-125.26,50.13,-125.195,50.044,-125.184,50.097,-125.113,50.163,-125.074,50.221,-125.091,50.268,-125.126,50.32]},{"b":[-118.59,32.819,-118.35,33.033],"l":[-118.507,32.96,-118.557,33.033,-118.59,33.011,-118.529,32.936,-118.473,32.839,-118.409,32.819,-118.35,32.828,-118.383,32.849,-118.507,32.96]},{"b":[-118.401,28.904,-118.243,29.188],"l":[-118.312,29.131,-118.312,29.183,-118.368,29.188,-118.401,29.163,-118.4,29.112,-118.285,28.904,-118.243,28.942,-118.247,29.043,-118.266,29.086,-118.312,29.131]},{"b":[-120.252,33.905,-119.984,34.027],"l":[-120.252,34.014,-120.167,33.918,-120.114,33.905,-120.044,33.919,-119.984,33.973,-119.994,33.985,-120.072,34.027,-120.252,34.014]},{"b":[-119.918,33.968,-119.549,34.08],"l":[-119.679,34.028,-119.882,34.08,-119.918,34.068,-119.892,34.032,-119.885,33.995,-119.81,33.968,-119.562,34.007,-119.549,34.028,-119.569,34.053,-119.679,34.028]},{"b":[-118.569,33.312,-118.297,33.477],"l":[-118.469,33.357,-118.446,33.317,-118.37,33.321,-118.297,33.312,-118.348,33.386,-118.392,33.415,-118.555,33.477,-118.569,33.464,-118.563,33.437,-118.559,33.432,-118.507,33.427,-118.492,33.413,-118.469,33.357]},{"b":[-71.403,41.467,-71.354,41.572],"l":[-71.393,41.467,-71.365,41.485,-71.354,41.542,-71.364,41.572,-71.384,41.571,-71.403,41.515,-71.393,41.467]},{"b":[-74.253,39.529,-74.107,39.746],"l":[-74.25,39.529,-74.133,39.681,-74.107,39.746,-74.253,39.558,-74.25,39.529]},{"b":[-74.236,40.519,-74.067,40.658],"l":[-74.188,40.615,-74.236,40.519,-74.188,40.523,-74.139,40.542,-74.08,40.586,-74.067,40.615,-74.069,40.649,-74.1,40.658,-74.188,40.615]},{"b":[-64.863,32.26,-64.668,32.387],"l":[-64.771,32.308,-64.863,32.274,-64.845,32.262,-64.82,32.26,-64.73,32.293,-64.668,32.382,-64.695,32.387,-64.771,32.308]},{"b":[-70.829,41.327,-70.51,41.457],"l":[-70.674,41.449,-70.76,41.374,-70.829,41.359,-70.785,41.327,-70.51,41.376,-70.525,41.415,-70.616,41.457,-70.674,41.449]},{"b":[-71.346,41.465,-71.232,41.654],"l":[-71.346,41.469,-71.291,41.465,-71.241,41.492,-71.232,41.654,-71.264,41.638,-71.28,41.62,-71.307,41.56,-71.318,41.506,-71.346,41.469]},{"b":[-70.233,41.249,-69.978,41.397],"l":[-70.063,41.328,-70.087,41.318,-70.233,41.286,-70.055,41.249,-69.978,41.266,-69.986,41.299,-70.041,41.397,-70.044,41.374,-70.063,41.328]},{"b":[-75.984,35.119,-75.782,35.19],"l":[-75.782,35.19,-75.865,35.174,-75.984,35.123,-75.964,35.119,-75.782,35.19]},{"b":[-75.717,35.836,-75.636,35.946],"l":[-75.651,35.836,-75.636,35.856,-75.637,35.881,-75.649,35.91,-75.717,35.946,-75.651,35.836]},{"b":[-75.379,37.872,-75.098,38.298],"l":[-75.226,38.072,-75.252,38.037,-75.379,37.872,-75.333,37.888,-75.226,38.04,-75.203,38.072,-75.136,38.181,-75.098,38.298,-75.137,38.24,-75.226,38.072]},{"b":[-75.69,35.213,-75.456,35.769],"l":[-75.504,35.769,-75.504,35.735,-75.481,35.572,-75.488,35.479,-75.536,35.279,-75.69,35.222,-75.678,35.213,-75.544,35.24,-75.509,35.28,-75.465,35.449,-75.456,35.564,-75.479,35.717,-75.504,35.769]},{"b":[-76.529,34.631,-76.207,34.939],"l":[-76.437,34.756,-76.529,34.631,-76.504,34.643,-76.358,34.804,-76.207,34.939,-76.256,34.915,-76.437,34.756]},{"b":[-96.801,28.148,-96.404,28.382],"l":[-96.519,28.333,-96.682,28.23,-96.756,28.202,-96.801,28.148,-96.764,28.153,-96.544,28.276,-96.413,28.338,-96.404,28.382,-96.419,28.376,-96.453,28.341,-96.519,28.333]},{"b":[-97.385,27.243,-97.061,27.822],"l":[-97.295,27.523,-97.376,27.328,-97.385,27.243,-97.354,27.3,-97.251,27.541,-97.061,27.822,-97.13,27.779,-97.295,27.523]},{"b":[-95.09,29.136,-94.768,29.341],"l":[-95.09,29.136,-95.04,29.146,-94.865,29.253,-94.768,29.339,-94.826,29.341,-94.872,29.29,-95.09,29.136]},{"b":[-97.407,26.113,-97.171,27.196],"l":[-97.386,27.196,-97.407,27.1,-97.402,26.821,-97.267,26.33,-97.185,26.113,-97.171,26.159,-97.202,26.3,-97.351,26.801,-97.386,27.196]},{"b":[-85.117,29.607,-84.737,29.732],"l":[-85.049,29.638,-85.117,29.633,-85.008,29.607,-84.908,29.643,-84.737,29.732,-84.812,29.718,-84.877,29.679,-85.001,29.627,-85.049,29.638]},{"b":[-81.485,30.728,-81.419,30.971],"l":[-81.463,30.728,-81.419,30.971,-81.451,30.947,-81.485,30.898,-81.483,30.814,-81.463,30.728]},{"b":[-89.342,30.038,-89.185,30.169],"l":[-89.185,30.169,-89.276,30.111,-89.288,30.094,-89.31,30.079,-89.342,30.063,-89.269,30.061,-89.22,30.038,-89.224,30.084,-89.211,30.126,-89.185,30.169]},{"b":[-92.007,29.486,-91.754,29.644],"l":[-91.796,29.597,-91.875,29.641,-91.925,29.644,-92.007,29.61,-91.996,29.573,-91.831,29.486,-91.794,29.501,-91.762,29.539,-91.754,29.567,-91.768,29.585,-91.796,29.597]},{"b":[-88.867,29.776,-88.813,30.057],"l":[-88.828,29.928,-88.856,29.776,-88.827,29.808,-88.813,29.933,-88.826,30,-88.867,30.057,-88.828,29.928]},{"b":[-78,23.739,-77.519,24.288],"l":[-77.532,23.939,-77.537,23.962,-77.532,23.988,-77.562,24.137,-77.615,24.216,-77.683,24.118,-77.755,24.163,-77.656,24.227,-77.658,24.249,-77.701,24.288,-77.757,24.27,-77.85,24.258,-77.884,24.242,-77.95,24.253,-78,24.22,-77.914,24.091,-77.852,24.04,-77.806,23.884,-77.776,23.862,-77.771,23.753,-77.574,23.739,-77.519,23.869,-77.521,23.911,-77.532,23.939]},{"b":[-77.919,21.921,-77.634,22.092],"l":[-77.774,22.083,-77.855,22.092,-77.919,22.088,-77.9,22.037,-77.823,21.988,-77.784,21.97,-77.755,21.966,-77.71,21.921,-77.669,21.952,-77.646,21.996,-77.634,22.054,-77.774,22.083]},{"b":[-78.274,22.269,-78.027,22.438],"l":[-78.027,22.285,-78.062,22.306,-78.094,22.387,-78.151,22.431,-78.201,22.438,-78.274,22.424,-78.27,22.402,-78.226,22.38,-78.18,22.322,-78.102,22.306,-78.048,22.269,-78.027,22.285]},{"b":[-78.696,22.445,-78.284,22.552],"l":[-78.284,22.455,-78.351,22.539,-78.4,22.547,-78.445,22.544,-78.493,22.531,-78.63,22.552,-78.696,22.534,-78.674,22.509,-78.629,22.488,-78.548,22.464,-78.425,22.46,-78.39,22.445,-78.343,22.445,-78.284,22.455]},{"b":[-77.944,25.895,-77.038,26.94],"l":[-77.511,26.846,-77.33,26.618,-77.239,26.561,-77.206,26.489,-77.23,26.425,-77.221,26.362,-77.248,26.289,-77.247,26.156,-77.294,26.096,-77.403,26.025,-77.333,25.996,-77.246,25.895,-77.226,25.904,-77.191,25.955,-77.167,26.24,-77.038,26.333,-77.066,26.53,-77.162,26.597,-77.257,26.639,-77.269,26.663,-77.266,26.689,-77.296,26.712,-77.369,26.748,-77.449,26.836,-77.534,26.903,-77.672,26.914,-77.788,26.936,-77.863,26.94,-77.944,26.904,-77.796,26.901,-77.511,26.846]},{"b":[-74.314,22.665,-74.035,22.84],"l":[-74.242,22.715,-74.099,22.665,-74.035,22.706,-74.058,22.723,-74.175,22.76,-74.221,22.812,-74.307,22.84,-74.314,22.804,-74.303,22.764,-74.275,22.712,-74.242,22.715]},{"b":[-74.551,23.96,-74.429,24.127],"l":[-74.429,24.068,-74.45,24.125,-74.472,24.127,-74.527,24.105,-74.551,23.969,-74.509,23.96,-74.429,24.068]},{"b":[-73.687,20.937,-73.012,21.313],"l":[-73.661,20.937,-73.401,20.944,-73.165,20.979,-73.059,21.119,-73.027,21.192,-73.012,21.3,-73.058,21.313,-73.137,21.205,-73.235,21.154,-73.302,21.156,-73.425,21.202,-73.523,21.191,-73.585,21.126,-73.68,21.103,-73.67,21.082,-73.668,21.062,-73.687,21.009,-73.681,20.976,-73.661,20.937]},{"b":[-74.277,22.184,-73.837,22.731],"l":[-73.915,22.568,-73.906,22.527,-73.936,22.478,-73.995,22.449,-74.01,22.428,-74.052,22.401,-74.127,22.323,-74.261,22.236,-74.277,22.184,-74.207,22.214,-74.093,22.306,-73.975,22.361,-73.837,22.538,-73.877,22.681,-73.85,22.731,-73.954,22.716,-73.975,22.682,-73.976,22.635,-73.915,22.568]},{"b":[-76.781,24.649,-76.115,25.565],"l":[-76.344,25.332,-76.484,25.375,-76.649,25.487,-76.711,25.565,-76.727,25.552,-76.749,25.481,-76.781,25.427,-76.693,25.443,-76.621,25.432,-76.5,25.342,-76.369,25.313,-76.284,25.222,-76.16,25.119,-76.153,25.026,-76.204,24.936,-76.214,24.822,-76.32,24.818,-76.3,24.796,-76.241,24.754,-76.205,24.682,-76.17,24.649,-76.175,24.76,-76.141,24.886,-76.115,25.095,-76.127,25.141,-76.192,25.191,-76.344,25.332]},{"b":[-76.037,23.444,-75.665,23.671],"l":[-75.949,23.647,-76.01,23.671,-76.037,23.603,-75.956,23.592,-75.781,23.471,-75.706,23.444,-75.665,23.45,-75.754,23.49,-75.808,23.543,-75.949,23.647]},{"b":[-75.316,22.869,-74.84,23.668],"l":[-75.131,23.268,-75.141,23.205,-75.204,23.193,-75.223,23.165,-75.132,23.117,-74.973,23.069,-74.847,22.869,-74.84,22.894,-74.846,23,-74.937,23.088,-75.064,23.15,-75.109,23.333,-75.175,23.439,-75.217,23.547,-75.316,23.668,-75.31,23.59,-75.288,23.568,-75.241,23.475,-75.158,23.336,-75.131,23.268]},{"b":[-75.744,24.139,-75.302,24.698],"l":[-75.526,24.45,-75.654,24.681,-75.71,24.698,-75.727,24.689,-75.744,24.655,-75.661,24.59,-75.639,24.529,-75.593,24.491,-75.494,24.33,-75.409,24.266,-75.412,24.221,-75.481,24.174,-75.503,24.139,-75.468,24.14,-75.369,24.159,-75.302,24.149,-75.308,24.2,-75.518,24.427,-75.526,24.45]},{"b":[-78.042,22.125,-77.842,22.302],"l":[-77.879,22.128,-77.842,22.149,-77.889,22.201,-77.894,22.215,-77.97,22.241,-77.986,22.302,-77.999,22.299,-78.007,22.248,-78.042,22.201,-78.012,22.166,-77.912,22.125,-77.879,22.128]},{"b":[-82.201,26.428,-82.037,26.548],"l":[-82.201,26.548,-82.184,26.481,-82.145,26.447,-82.073,26.428,-82.037,26.454,-82.116,26.461,-82.139,26.477,-82.201,26.548]},{"b":[-78.435,24.287,-77.735,25.202],"l":[-78.367,24.544,-78.258,24.483,-78.192,24.466,-78.146,24.493,-78.136,24.412,-78.076,24.365,-78.045,24.287,-77.983,24.335,-77.881,24.369,-77.853,24.403,-77.745,24.463,-77.735,24.496,-77.746,24.586,-77.744,24.707,-77.84,24.794,-77.919,24.943,-77.973,25.005,-77.975,25.085,-78.033,25.143,-78.163,25.202,-78.211,25.191,-78.159,25.022,-78.184,24.917,-78.299,24.754,-78.274,24.692,-78.26,24.687,-78.243,24.654,-78.319,24.59,-78.339,24.642,-78.435,24.628,-78.367,24.544]},{"b":[-81.631,24.59,-81.532,24.689],"l":[-81.562,24.689,-81.579,24.629,-81.631,24.59,-81.567,24.6,-81.532,24.614,-81.532,24.642,-81.562,24.689]},{"b":[-80.437,27.205,-80.171,27.851],"l":[-80.262,27.376,-80.171,27.205,-80.187,27.278,-80.356,27.679,-80.396,27.795,-80.437,27.851,-80.376,27.643,-80.262,27.376]},{"b":[-83.184,21.441,-82.562,21.943],"l":[-82.682,21.821,-82.715,21.89,-82.756,21.91,-82.991,21.943,-83.078,21.833,-83.083,21.791,-82.974,21.592,-83.007,21.566,-83.055,21.549,-83.113,21.574,-83.18,21.623,-83.184,21.593,-83.142,21.532,-83.067,21.469,-82.96,21.441,-82.853,21.444,-82.655,21.519,-82.562,21.572,-82.568,21.622,-82.629,21.767,-82.682,21.821]},{"b":[-82.169,26.494,-82.084,26.701],"l":[-82.085,26.494,-82.084,26.552,-82.121,26.666,-82.169,26.701,-82.136,26.592,-82.085,26.494]},{"b":[-79.628,22.638,-79.348,22.807],"l":[-79.382,22.681,-79.579,22.807,-79.628,22.805,-79.598,22.788,-79.523,22.711,-79.348,22.638,-79.35,22.664,-79.382,22.681]},{"b":[-78.986,26.501,-77.922,26.798],"l":[-78.234,26.637,-77.926,26.663,-77.922,26.691,-77.944,26.744,-78.089,26.714,-78.268,26.723,-78.307,26.702,-78.372,26.698,-78.493,26.729,-78.597,26.798,-78.633,26.726,-78.621,26.705,-78.633,26.659,-78.712,26.599,-78.798,26.582,-78.936,26.673,-78.986,26.69,-78.799,26.528,-78.744,26.501,-78.671,26.507,-78.516,26.559,-78.234,26.637]},{"b":[-80.581,24.954,-80.257,25.348],"l":[-80.257,25.348,-80.28,25.341,-80.351,25.297,-80.355,25.234,-80.404,25.179,-80.456,25.149,-80.481,25.102,-80.559,25.001,-80.581,24.954,-80.382,25.142,-80.257,25.348]},{"b":[-87.998,17.906,-87.849,18.155],"l":[-87.849,18.14,-87.859,18.154,-87.898,18.155,-87.953,18.001,-87.959,17.964,-87.998,17.906,-87.951,17.925,-87.849,18.14]},{"b":[-87.935,17.283,-87.789,17.546],"l":[-87.903,17.426,-87.935,17.323,-87.93,17.283,-87.853,17.423,-87.798,17.48,-87.789,17.524,-87.826,17.546,-87.833,17.501,-87.859,17.463,-87.903,17.426]},{"b":[-91.816,18.654,-91.537,18.778],"l":[-91.816,18.676,-91.796,18.654,-91.684,18.677,-91.654,18.711,-91.537,18.76,-91.55,18.774,-91.589,18.778,-91.816,18.676]},{"b":[-82.322,9.334,-82.233,9.432],"l":[-82.244,9.334,-82.233,9.381,-82.259,9.43,-82.276,9.432,-82.322,9.418,-82.244,9.334]},{"b":[-81.419,19.272,-81.107,19.385],"l":[-81.285,19.363,-81.296,19.341,-81.337,19.329,-81.37,19.349,-81.391,19.385,-81.419,19.375,-81.405,19.278,-81.304,19.272,-81.277,19.277,-81.225,19.304,-81.107,19.305,-81.13,19.347,-81.285,19.363]},{"b":[-87.019,20.272,-86.755,20.579],"l":[-86.928,20.552,-86.978,20.49,-87.019,20.382,-86.991,20.272,-86.94,20.303,-86.809,20.468,-86.755,20.552,-86.763,20.579,-86.829,20.559,-86.928,20.552]},{"b":[-62.84,17.224,-62.631,17.403],"l":[-62.714,17.353,-62.795,17.403,-62.827,17.386,-62.839,17.365,-62.84,17.347,-62.839,17.339,-62.776,17.303,-62.702,17.286,-62.656,17.224,-62.631,17.24,-62.641,17.262,-62.676,17.291,-62.714,17.353]},{"b":[-61.073,13.718,-60.887,14.093],"l":[-60.908,14.093,-60.945,14.073,-60.997,14.011,-61.064,13.916,-61.073,13.866,-61.061,13.783,-60.951,13.718,-60.895,13.822,-60.887,14.011,-60.908,14.093]},{"b":[-61.22,14.426,-60.826,14.875],"l":[-60.934,14.686,-60.919,14.735,-60.927,14.755,-60.953,14.756,-61.027,14.826,-61.127,14.875,-61.181,14.872,-61.213,14.849,-61.22,14.804,-61.141,14.652,-61.104,14.621,-61.011,14.602,-61.09,14.53,-61.089,14.51,-61.064,14.467,-60.899,14.474,-60.862,14.426,-60.837,14.437,-60.826,14.494,-60.87,14.614,-60.889,14.645,-60.934,14.686]},{"b":[-61.906,10.065,-60.918,10.84],"l":[-60.918,10.84,-61.079,10.832,-61.174,10.803,-61.37,10.797,-61.465,10.764,-61.592,10.748,-61.651,10.718,-61.635,10.699,-61.541,10.664,-61.499,10.639,-61.478,10.603,-61.465,10.539,-61.499,10.269,-61.529,10.253,-61.633,10.243,-61.661,10.192,-61.906,10.069,-61.772,10.085,-61.597,10.065,-61.174,10.078,-61.012,10.134,-61.004,10.168,-61,10.261,-60.968,10.323,-61.016,10.386,-61.037,10.482,-61.019,10.558,-61.034,10.67,-60.997,10.716,-60.918,10.84]},{"b":[-68.371,12.032,-68.206,12.302],"l":[-68.369,12.302,-68.371,12.258,-68.348,12.228,-68.307,12.207,-68.287,12.172,-68.282,12.082,-68.254,12.032,-68.206,12.145,-68.219,12.231,-68.369,12.302]},{"b":[-60.811,11.169,-60.525,11.325],"l":[-60.709,11.277,-60.804,11.208,-60.811,11.169,-60.756,11.179,-60.546,11.264,-60.525,11.325,-60.563,11.324,-60.709,11.277]},{"b":[-62.223,16.681,-62.148,16.81],"l":[-62.176,16.81,-62.191,16.804,-62.223,16.752,-62.222,16.7,-62.154,16.681,-62.148,16.74,-62.176,16.81]},{"b":[-59.647,13.062,-59.428,13.318],"l":[-59.592,13.318,-59.647,13.303,-59.643,13.15,-59.611,13.102,-59.522,13.062,-59.493,13.082,-59.428,13.153,-59.488,13.197,-59.592,13.318]},{"b":[-64.402,10.881,-63.817,11.167],"l":[-64.007,11.068,-64.028,11.002,-64.113,11.006,-64.185,11.043,-64.214,11.086,-64.25,11.08,-64.349,11.052,-64.402,10.982,-64.362,10.962,-64.219,10.942,-64.161,10.959,-64.101,10.901,-64.055,10.884,-63.994,10.881,-63.918,10.888,-63.827,10.976,-63.817,11,-63.849,11.131,-63.893,11.167,-64.007,11.068]},{"b":[-63.16,18.171,-62.98,18.27],"l":[-63.16,18.171,-63.001,18.222,-62.98,18.265,-63.026,18.27,-63.153,18.2,-63.16,18.171]},{"b":[-61.782,12.008,-61.607,12.237],"l":[-61.66,12.237,-61.715,12.185,-61.75,12.109,-61.756,12.046,-61.782,12.008,-61.715,12.013,-61.627,12.054,-61.607,12.223,-61.66,12.237]},{"b":[-61.869,17.549,-61.747,17.714],"l":[-61.762,17.549,-61.747,17.575,-61.75,17.661,-61.777,17.69,-61.82,17.697,-61.852,17.714,-61.866,17.704,-61.869,17.685,-61.844,17.596,-61.762,17.549]},{"b":[-61.318,15.886,-61.203,16.006],"l":[-61.25,16.006,-61.275,15.996,-61.318,15.955,-61.311,15.895,-61.286,15.886,-61.23,15.89,-61.203,15.921,-61.212,15.96,-61.25,16.006]},{"b":[-61.887,16.997,-61.686,17.169],"l":[-61.86,17.013,-61.748,16.997,-61.716,17.037,-61.695,17.049,-61.686,17.07,-61.686,17.098,-61.708,17.105,-61.739,17.138,-61.817,17.169,-61.887,17.098,-61.882,17.063,-61.86,17.013]},{"b":[-61.481,15.227,-61.251,15.633],"l":[-61.251,15.373,-61.277,15.527,-61.32,15.585,-61.458,15.633,-61.47,15.603,-61.481,15.525,-61.416,15.4,-61.375,15.227,-61.282,15.249,-61.251,15.373]},{"b":[-61.54,16.219,-61.173,16.507],"l":[-61.173,16.256,-61.355,16.363,-61.396,16.413,-61.406,16.468,-61.471,16.507,-61.511,16.478,-61.529,16.434,-61.501,16.36,-61.54,16.3,-61.522,16.228,-61.445,16.219,-61.327,16.23,-61.173,16.256]},{"b":[-61.794,15.962,-61.552,16.355],"l":[-61.642,16.326,-61.748,16.355,-61.767,16.34,-61.794,16.301,-61.759,16.062,-61.71,15.976,-61.67,15.962,-61.59,16.007,-61.564,16.048,-61.575,16.227,-61.552,16.271,-61.597,16.292,-61.642,16.326]},{"b":[-61.277,13.142,-61.124,13.359],"l":[-61.135,13.203,-61.124,13.294,-61.139,13.359,-61.182,13.356,-61.224,13.331,-61.268,13.288,-61.277,13.21,-61.204,13.142,-61.175,13.158,-61.135,13.203]},{"b":[-73.162,22.291,-72.747,22.455],"l":[-72.981,22.369,-72.889,22.36,-72.784,22.291,-72.747,22.327,-72.763,22.344,-72.831,22.386,-72.945,22.416,-72.979,22.415,-73.041,22.429,-73.127,22.455,-73.162,22.381,-73.11,22.368,-72.981,22.369]},{"b":[-69.159,12.045,-68.751,12.38],"l":[-68.827,12.159,-69.013,12.231,-69.077,12.342,-69.118,12.373,-69.159,12.38,-69.154,12.298,-68.995,12.142,-68.803,12.045,-68.751,12.06,-68.827,12.159]},{"b":[-71.848,21.752,-71.637,21.852],"l":[-71.722,21.79,-71.665,21.752,-71.661,21.765,-71.637,21.788,-71.668,21.833,-71.806,21.852,-71.848,21.843,-71.83,21.791,-71.722,21.79]},{"b":[-73.063,21.458,-72.916,21.562],"l":[-72.916,21.507,-72.995,21.562,-73.063,21.515,-73.049,21.458,-72.916,21.507]},{"b":[-72.019,21.83,-71.88,21.952],"l":[-71.955,21.864,-71.897,21.83,-71.88,21.84,-71.9,21.863,-71.932,21.952,-72.011,21.95,-72.019,21.918,-71.985,21.893,-71.964,21.892,-71.955,21.864]},{"b":[-72.342,21.755,-72.144,21.851],"l":[-72.191,21.77,-72.182,21.78,-72.144,21.793,-72.15,21.804,-72.219,21.796,-72.333,21.851,-72.342,21.795,-72.335,21.758,-72.301,21.755,-72.191,21.77]},{"b":[-161.131,58.561,-160.715,58.795],"l":[-161.085,58.671,-161.131,58.668,-161.07,58.569,-160.992,58.561,-160.919,58.577,-160.715,58.795,-160.769,58.789,-160.986,58.736,-161.085,58.671]},{"b":[-153.375,57,-152.908,57.185],"l":[-153.157,57.094,-153.134,57.093,-153.007,57.125,-152.933,57.129,-152.908,57.14,-152.908,57.152,-152.935,57.167,-153.285,57.185,-153.354,57.132,-153.375,57.052,-153.295,57,-153.235,57.029,-153.157,57.094]},{"b":[-152.069,60.358,-151.887,60.504],"l":[-151.96,60.504,-152.004,60.407,-152.069,60.358,-152.021,60.362,-151.987,60.374,-151.887,60.473,-151.899,60.49,-151.96,60.504]},{"b":[-152.637,58.479,-152.357,58.619],"l":[-152.516,58.479,-152.486,58.485,-152.393,58.541,-152.362,58.571,-152.357,58.595,-152.368,58.611,-152.396,58.619,-152.463,58.619,-152.605,58.566,-152.637,58.542,-152.589,58.509,-152.516,58.479]},{"b":[-153.52,57.822,-153.241,57.971],"l":[-153.241,57.85,-153.29,57.898,-153.347,57.933,-153.481,57.971,-153.52,57.956,-153.517,57.942,-153.465,57.909,-153.351,57.862,-153.295,57.829,-153.269,57.822,-153.241,57.85]},{"b":[-155.737,55.79,-155.564,55.924],"l":[-155.737,55.83,-155.723,55.802,-155.681,55.792,-155.605,55.79,-155.566,55.821,-155.564,55.887,-155.573,55.921,-155.594,55.924,-155.621,55.913,-155.737,55.83]},{"b":[-154.777,56.412,-154.445,56.601],"l":[-154.518,56.601,-154.624,56.561,-154.729,56.502,-154.761,56.471,-154.777,56.44,-154.774,56.42,-154.751,56.412,-154.683,56.436,-154.511,56.521,-154.445,56.573,-154.463,56.598,-154.518,56.601]},{"b":[-154.332,56.513,-154.102,56.609],"l":[-154.217,56.609,-154.322,56.571,-154.332,56.539,-154.258,56.513,-154.209,56.515,-154.15,56.53,-154.116,56.544,-154.107,56.558,-154.102,56.582,-154.11,56.603,-154.217,56.609]},{"b":[-146.703,60.268,-146.102,60.481],"l":[-146.372,60.422,-146.394,60.45,-146.56,60.481,-146.606,60.468,-146.67,60.433,-146.703,60.409,-146.703,60.396,-146.683,60.361,-146.65,60.336,-146.618,60.274,-146.595,60.268,-146.419,60.325,-146.202,60.368,-146.128,60.393,-146.102,60.411,-146.124,60.424,-146.18,60.429,-146.372,60.422]},{"b":[-145.284,60.313,-145.102,60.401],"l":[-145.284,60.337,-145.238,60.321,-145.15,60.313,-145.119,60.337,-145.102,60.388,-145.128,60.401,-145.284,60.337]},{"b":[-144.614,59.813,-144.236,60.015],"l":[-144.542,59.878,-144.614,59.813,-144.566,59.818,-144.403,59.921,-144.249,59.982,-144.236,60.015,-144.354,59.996,-144.445,59.951,-144.542,59.878]},{"b":[-147.872,59.799,-146.958,60.363],"l":[-147.02,60.332,-147.12,60.363,-147.181,60.358,-147.205,60.311,-147.337,60.185,-147.466,60.097,-147.607,60.037,-147.734,59.954,-147.768,59.944,-147.814,59.902,-147.872,59.828,-147.846,59.799,-147.736,59.813,-147.669,59.832,-147.645,59.854,-147.602,59.866,-147.54,59.868,-147.499,59.89,-147.479,59.934,-147.448,59.96,-147.404,59.97,-147.377,59.991,-147.346,60.052,-147.318,60.075,-146.987,60.254,-146.958,60.289,-147.02,60.332]},{"b":[-148.272,60.035,-147.914,60.152],"l":[-147.914,60.092,-148.08,60.152,-148.231,60.114,-148.272,60.053,-148.074,60.035,-148.022,60.065,-147.914,60.092]},{"b":[-147.891,60.178,-147.658,60.491],"l":[-147.842,60.351,-147.855,60.321,-147.891,60.299,-147.871,60.23,-147.805,60.231,-147.822,60.203,-147.816,60.185,-147.788,60.178,-147.76,60.19,-147.732,60.222,-147.712,60.273,-147.66,60.352,-147.69,60.399,-147.659,60.424,-147.658,60.45,-147.689,60.491,-147.703,60.487,-147.737,60.447,-147.76,60.439,-147.774,60.445,-147.779,60.466,-147.795,60.46,-147.838,60.371,-147.842,60.351]},{"b":[-148.124,60.818,-147.931,60.924],"l":[-147.931,60.826,-147.943,60.875,-147.964,60.9,-148.038,60.924,-148.102,60.916,-148.1,60.895,-148.124,60.844,-148.115,60.831,-148.057,60.818,-147.931,60.826]},{"b":[-133.454,54.684,-132.617,55.326],"l":[-132.677,54.726,-132.647,54.756,-132.634,54.84,-132.617,54.892,-132.649,54.907,-132.701,54.919,-132.772,54.926,-132.813,54.89,-132.838,54.881,-132.862,54.894,-132.946,55.003,-132.982,55.033,-132.996,55.111,-133.067,55.166,-133.097,55.214,-133.297,55.326,-133.429,55.304,-133.454,55.26,-133.418,55.211,-133.325,55.185,-133.251,55.175,-133.205,55.084,-133.123,54.97,-133.081,54.949,-133.075,54.921,-133.009,54.855,-132.89,54.763,-132.807,54.709,-132.706,54.684,-132.677,54.726]},{"b":[-134.391,56.077,-133.738,56.932],"l":[-133.855,56.582,-133.809,56.611,-133.767,56.6,-133.738,56.65,-133.778,56.729,-133.831,56.781,-133.925,56.776,-133.99,56.845,-134.001,56.869,-134.052,56.898,-134.143,56.932,-134.274,56.918,-134.374,56.839,-134.391,56.749,-134.384,56.724,-134.278,56.617,-134.29,56.58,-134.15,56.513,-134.084,56.456,-134.195,56.414,-134.245,56.203,-134.19,56.077,-134.122,56.077,-134.067,56.133,-134.024,56.119,-133.994,56.101,-133.971,56.108,-133.95,56.128,-133.939,56.194,-133.885,56.292,-133.87,56.389,-133.884,56.485,-133.855,56.582]},{"b":[-133.737,55.269,-133.282,55.559],"l":[-133.634,55.539,-133.737,55.497,-133.635,55.413,-133.65,55.269,-133.547,55.317,-133.493,55.362,-133.463,55.377,-133.429,55.418,-133.426,55.431,-133.282,55.498,-133.283,55.516,-133.305,55.544,-133.346,55.559,-133.455,55.522,-133.567,55.527,-133.634,55.539]},{"b":[-131.595,54.888,-131.232,55.264],"l":[-131.431,54.996,-131.456,54.931,-131.446,54.909,-131.406,54.894,-131.33,54.888,-131.232,54.904,-131.237,54.95,-131.34,55.08,-131.405,55.213,-131.513,55.263,-131.565,55.264,-131.578,55.249,-131.578,55.201,-131.556,55.137,-131.595,55.091,-131.592,55.026,-131.54,55.048,-131.482,55.035,-131.431,54.996]},{"b":[-132.948,56.511,-132.568,56.795],"l":[-132.907,56.637,-132.936,56.607,-132.948,56.567,-132.931,56.524,-132.885,56.512,-132.758,56.511,-132.747,56.526,-132.714,56.543,-132.634,56.553,-132.568,56.576,-132.599,56.636,-132.656,56.685,-132.843,56.795,-132.871,56.696,-132.907,56.637]},{"b":[-132.699,55.929,-132.067,56.499],"l":[-132.38,56.499,-132.506,56.335,-132.539,56.324,-132.599,56.242,-132.675,56.224,-132.699,56.198,-132.691,56.13,-132.66,56.078,-132.603,56.066,-132.451,56.056,-132.406,56.029,-132.421,55.98,-132.407,55.958,-132.369,55.94,-132.287,55.929,-132.21,55.953,-132.173,55.953,-132.133,55.943,-132.112,56.109,-132.067,56.244,-132.206,56.388,-132.317,56.488,-132.38,56.499]},{"b":[-133.038,56.244,-132.629,56.454],"l":[-132.935,56.442,-133.017,56.392,-133.038,56.365,-133.035,56.341,-132.891,56.259,-132.831,56.244,-132.78,56.247,-132.669,56.287,-132.647,56.313,-132.658,56.339,-132.653,56.364,-132.632,56.388,-132.629,56.412,-132.643,56.435,-132.706,56.448,-132.902,56.454,-132.935,56.442]},{"b":[-134.662,58.204,-134.313,58.333],"l":[-134.313,58.229,-134.399,58.287,-134.52,58.333,-134.648,58.312,-134.662,58.291,-134.594,58.243,-134.456,58.207,-134.32,58.204,-134.313,58.229]},{"b":[-129.268,52.464,-128.937,52.811],"l":[-129.215,52.804,-129.246,52.811,-129.264,52.801,-129.268,52.772,-129.25,52.722,-129.151,52.605,-129.102,52.574,-128.969,52.464,-128.937,52.51,-128.94,52.601,-128.994,52.662,-129.186,52.791,-129.215,52.804]},{"b":[-130.959,54.478,-130.753,54.632],"l":[-130.922,54.615,-130.953,54.542,-130.959,54.499,-130.95,54.478,-130.927,54.479,-130.805,54.544,-130.763,54.577,-130.753,54.6,-130.758,54.614,-130.777,54.619,-130.907,54.632,-130.922,54.615]},{"b":[-131.117,51.96,-131.011,52.151],"l":[-131.107,52.137,-131.117,52.101,-131.103,52.014,-131.081,51.98,-131.047,51.96,-131.029,51.962,-131.011,52.095,-131.098,52.151,-131.107,52.137]},{"b":[-130.707,53.844,-130.236,54.089],"l":[-130.316,54.047,-130.397,54.086,-130.448,54.089,-130.495,54.074,-130.647,53.991,-130.696,53.951,-130.707,53.921,-130.703,53.892,-130.683,53.863,-130.664,53.848,-130.644,53.845,-130.638,53.86,-130.646,53.894,-130.642,53.921,-130.625,53.941,-130.59,53.94,-130.537,53.918,-130.47,53.862,-130.407,53.856,-130.384,53.844,-130.338,53.866,-130.267,53.923,-130.236,53.959,-130.298,54.036,-130.316,54.047]},{"b":[-130.518,53.165,-129.755,53.631],"l":[-130.035,53.481,-130.195,53.55,-130.395,53.62,-130.452,53.631,-130.518,53.544,-130.411,53.491,-130.306,53.407,-130.151,53.346,-129.934,53.177,-129.869,53.165,-129.849,53.168,-129.769,53.217,-129.755,53.245,-129.945,53.436,-130.035,53.481]},{"b":[-128.149,51.457,-127.916,51.708],"l":[-127.933,51.605,-127.987,51.674,-127.999,51.704,-128.032,51.708,-128.123,51.667,-128.142,51.647,-128.149,51.627,-128.092,51.511,-128.045,51.474,-127.981,51.457,-127.941,51.457,-127.925,51.474,-127.916,51.506,-127.916,51.585,-127.933,51.605]},{"b":[-128.445,52.388,-128.247,52.784],"l":[-128.298,52.548,-128.248,52.741,-128.247,52.784,-128.365,52.782,-128.44,52.696,-128.436,52.56,-128.426,52.503,-128.412,52.473,-128.42,52.441,-128.445,52.388,-128.369,52.401,-128.344,52.426,-128.324,52.459,-128.298,52.548]},{"b":[-129.515,52.984,-129.314,53.188],"l":[-129.471,53.183,-129.501,53.188,-129.515,53.179,-129.5,53.129,-129.478,53.098,-129.41,53.024,-129.37,52.998,-129.329,52.984,-129.314,52.992,-129.344,53.053,-129.451,53.175,-129.471,53.183]},{"b":[-129.331,53.111,-129.168,53.33],"l":[-129.168,53.118,-129.177,53.259,-129.195,53.293,-129.238,53.33,-129.251,53.317,-129.253,53.285,-129.314,53.212,-129.331,53.174,-129.324,53.142,-129.306,53.121,-129.277,53.111,-129.173,53.111,-129.168,53.118]},{"b":[-139.291,69.539,-138.879,69.65],"l":[-139.291,69.598,-139.257,69.579,-139.126,69.539,-139.043,69.577,-138.879,69.59,-138.932,69.617,-139.073,69.648,-139.14,69.65,-139.291,69.598]},{"b":[-61.069,8.847,-60.849,9.07],"l":[-60.998,8.867,-60.916,8.899,-60.861,8.95,-60.849,8.996,-60.9,9.032,-60.895,9.053,-60.916,9.07,-60.945,9.055,-61.05,8.974,-61.069,8.947,-61.06,8.847,-60.998,8.867]},{"b":[-80.273,-3.012,-79.909,-2.669],"l":[-80.224,-2.753,-80.25,-2.812,-80.272,-2.952,-80.273,-2.996,-80.246,-3.008,-80.151,-3.012,-80.132,-2.973,-80.093,-2.846,-80.071,-2.834,-80.013,-2.82,-79.909,-2.726,-79.997,-2.674,-80.081,-2.669,-80.146,-2.696,-80.224,-2.753]},{"b":[-51.938,-1.453,-51.161,-0.541],"l":[-51.254,-0.541,-51.424,-0.566,-51.546,-0.65,-51.678,-0.855,-51.68,-1.086,-51.802,-1.203,-51.938,-1.453,-51.833,-1.434,-51.638,-1.342,-51.465,-1.211,-51.31,-1.024,-51.276,-1.022,-51.161,-0.667,-51.254,-0.541]},{"b":[-79.128,8.21,-79.065,8.296],"l":[-79.128,8.252,-79.11,8.21,-79.065,8.254,-79.085,8.296,-79.096,8.295,-79.128,8.252]},{"b":[-78.992,1.245,-78.9,1.36],"l":[-78.992,1.293,-78.966,1.245,-78.909,1.253,-78.9,1.36,-78.923,1.349,-78.992,1.293]},{"b":[-78.21,2.52,-78.114,2.646],"l":[-78.178,2.646,-78.21,2.609,-78.192,2.559,-78.141,2.52,-78.114,2.542,-78.119,2.604,-78.138,2.634,-78.178,2.646]},{"b":[-60.941,9.106,-60.736,9.218],"l":[-60.79,9.177,-60.736,9.203,-60.759,9.216,-60.782,9.218,-60.821,9.208,-60.845,9.192,-60.907,9.179,-60.939,9.132,-60.941,9.106,-60.821,9.138,-60.79,9.177]},{"b":[-44.598,-3.038,-44.481,-2.718],"l":[-44.481,-2.718,-44.502,-2.726,-44.569,-2.785,-44.582,-2.846,-44.565,-2.924,-44.598,-3.038,-44.499,-2.94,-44.483,-2.812,-44.487,-2.79,-44.481,-2.718]},{"b":[-38.787,-13.119,-38.6,-12.88],"l":[-38.668,-12.88,-38.685,-12.975,-38.787,-13.055,-38.783,-13.119,-38.744,-13.097,-38.601,-12.993,-38.6,-12.972,-38.615,-12.924,-38.668,-12.88]},{"b":[-39.022,-13.532,-38.904,-13.398],"l":[-39.007,-13.416,-39.022,-13.446,-38.993,-13.484,-38.978,-13.524,-38.938,-13.532,-38.904,-13.473,-38.907,-13.401,-38.98,-13.398,-39.007,-13.416]},{"b":[-45.021,-1.391,-44.883,-1.267],"l":[-44.979,-1.267,-44.996,-1.348,-45.011,-1.345,-45.021,-1.372,-44.968,-1.391,-44.947,-1.366,-44.883,-1.318,-44.888,-1.277,-44.979,-1.267]},{"b":[-50.345,-0.029,-49.697,0.305],"l":[-50.128,0.227,-50.273,0.232,-50.345,0.134,-50.339,0.043,-50.286,0.029,-50.113,0.033,-50.002,-0.029,-49.917,-0.023,-49.839,0.007,-49.697,0.216,-49.738,0.268,-49.879,0.305,-50.128,0.227]},{"b":[-50.509,1.893,-50.292,2.161],"l":[-50.491,2.129,-50.509,2.03,-50.456,1.91,-50.399,1.893,-50.299,1.939,-50.292,1.98,-50.342,2.142,-50.363,2.154,-50.419,2.161,-50.491,2.129]},{"b":[-51.038,-0.327,-50.651,-0.041],"l":[-50.653,-0.132,-50.651,-0.106,-50.667,-0.058,-50.765,-0.041,-50.842,-0.05,-50.995,-0.105,-51.026,-0.172,-51.022,-0.188,-51.038,-0.226,-51.019,-0.263,-50.926,-0.327,-50.653,-0.132]},{"b":[-49.83,-0.144,-49.372,0.084],"l":[-49.709,-0.144,-49.444,-0.112,-49.381,-0.055,-49.372,0.001,-49.4,0.057,-49.503,0.084,-49.602,0.063,-49.712,0.015,-49.803,-0.052,-49.83,-0.094,-49.709,-0.144]},{"b":[-50.624,-0.008,-50.332,0.591],"l":[-50.343,0.382,-50.351,0.582,-50.373,0.591,-50.397,0.581,-50.425,0.558,-50.426,0.425,-50.452,0.327,-50.526,0.247,-50.61,0.205,-50.624,0.054,-50.444,-0.008,-50.426,0.139,-50.332,0.259,-50.343,0.382]},{"b":[-50.282,0.359,-50.037,0.638],"l":[-50.261,0.359,-50.153,0.393,-50.04,0.523,-50.037,0.595,-50.059,0.638,-50.099,0.625,-50.113,0.605,-50.251,0.585,-50.282,0.517,-50.282,0.391,-50.261,0.359]},{"b":[-90.542,-0.773,-90.185,-0.485],"l":[-90.269,-0.485,-90.47,-0.517,-90.532,-0.581,-90.542,-0.676,-90.387,-0.773,-90.335,-0.772,-90.315,-0.757,-90.261,-0.742,-90.193,-0.659,-90.185,-0.545,-90.269,-0.485]},{"b":[-89.609,-0.952,-89.259,-0.68],"l":[-89.423,-0.722,-89.48,-0.793,-89.543,-0.827,-89.609,-0.889,-89.603,-0.913,-89.577,-0.934,-89.537,-0.952,-89.419,-0.911,-89.358,-0.826,-89.295,-0.786,-89.259,-0.728,-89.267,-0.705,-89.288,-0.69,-89.318,-0.68,-89.423,-0.722]},{"b":[-81.859,7.328,-81.603,7.622],"l":[-81.71,7.486,-81.671,7.523,-81.729,7.621,-81.752,7.622,-81.812,7.592,-81.857,7.508,-81.859,7.48,-81.852,7.453,-81.77,7.37,-81.658,7.328,-81.603,7.333,-81.613,7.38,-81.695,7.425,-81.71,7.486]},{"b":[-91.654,-0.478,-91.399,-0.256],"l":[-91.399,-0.322,-91.46,-0.256,-91.647,-0.284,-91.654,-0.311,-91.647,-0.391,-91.611,-0.444,-91.526,-0.478,-91.426,-0.461,-91.4,-0.421,-91.399,-0.322]},{"b":[-90.52,-1.342,-90.379,-1.221],"l":[-90.477,-1.221,-90.52,-1.299,-90.464,-1.342,-90.424,-1.34,-90.379,-1.292,-90.399,-1.262,-90.432,-1.24,-90.477,-1.221]},{"b":[-91.597,-1.02,-90.8,0.126],"l":[-90.959,-0.595,-90.968,-0.576,-90.951,-0.525,-90.976,-0.417,-91.176,-0.223,-91.21,-0.039,-91.272,0.025,-91.306,0.091,-91.361,0.126,-91.491,0.105,-91.509,0.062,-91.597,0.002,-91.59,-0.015,-91.55,-0.047,-91.469,-0.01,-91.429,-0.023,-91.369,-0.287,-91.25,-0.374,-91.197,-0.497,-91.121,-0.559,-91.145,-0.623,-91.334,-0.706,-91.458,-0.8,-91.495,-0.861,-91.484,-0.925,-91.419,-0.997,-91.372,-1.017,-91.131,-1.02,-90.906,-0.941,-90.8,-0.752,-90.863,-0.672,-90.959,-0.595]},{"b":[-90.868,-0.364,-90.553,-0.16],"l":[-90.668,-0.19,-90.78,-0.16,-90.82,-0.192,-90.868,-0.271,-90.809,-0.329,-90.62,-0.364,-90.574,-0.334,-90.553,-0.278,-90.668,-0.19]},{"b":[-109.434,-27.171,-109.223,-27.068],"l":[-109.39,-27.068,-109.429,-27.116,-109.434,-27.171,-109.28,-27.14,-109.223,-27.101,-109.276,-27.096,-109.39,-27.068]},{"b":[-106.639,21.529,-106.502,21.712],"l":[-106.524,21.652,-106.536,21.676,-106.597,21.712,-106.639,21.698,-106.634,21.613,-106.607,21.561,-106.531,21.529,-106.502,21.611,-106.524,21.652]},{"b":[-111.064,18.72,-110.914,18.863],"l":[-110.989,18.863,-111.04,18.83,-111.064,18.782,-110.975,18.72,-110.914,18.741,-110.942,18.802,-110.989,18.863]},{"b":[-110.703,24.892,-110.539,25.088],"l":[-110.703,25.047,-110.657,24.969,-110.59,24.908,-110.539,24.892,-110.567,25.003,-110.595,25.042,-110.69,25.088,-110.699,25.081,-110.703,25.047]},{"b":[-109.9,24.148,-109.794,24.345],"l":[-109.89,24.345,-109.9,24.331,-109.878,24.201,-109.827,24.148,-109.805,24.151,-109.796,24.164,-109.794,24.183,-109.89,24.345]},{"b":[-113.594,29.035,-113.156,29.573],"l":[-113.202,29.302,-113.374,29.339,-113.376,29.417,-113.416,29.486,-113.508,29.56,-113.587,29.573,-113.594,29.463,-113.581,29.413,-113.496,29.308,-113.265,29.097,-113.163,29.035,-113.156,29.052,-113.178,29.132,-113.202,29.302]},{"b":[-114.789,31.701,-114.688,31.794],"l":[-114.789,31.747,-114.727,31.701,-114.694,31.706,-114.688,31.724,-114.709,31.757,-114.771,31.794,-114.785,31.79,-114.789,31.747]},{"b":[-115.353,28.037,-115.149,28.368],"l":[-115.234,28.368,-115.274,28.343,-115.26,28.221,-115.353,28.104,-115.184,28.037,-115.171,28.069,-115.149,28.172,-115.197,28.328,-115.234,28.368]},{"b":[-112.013,24.346,-111.699,24.551],"l":[-111.857,24.538,-111.941,24.551,-112.013,24.533,-111.712,24.346,-111.699,24.394,-111.857,24.538]},{"b":[-111.225,25.836,-111.088,26.076],"l":[-111.091,26.076,-111.139,26.07,-111.183,26.041,-111.225,25.836,-111.204,25.85,-111.135,25.999,-111.094,25.974,-111.088,25.985,-111.1,26.021,-111.091,26.076]},{"b":[-112.531,28.769,-112.203,29.24],"l":[-112.203,29.005,-112.249,29.126,-112.263,29.207,-112.285,29.24,-112.424,29.204,-112.47,29.168,-112.531,28.894,-112.514,28.848,-112.355,28.773,-112.278,28.769,-112.203,29.005]},{"b":[-112.297,24.535,-112.057,25.286],"l":[-112.164,24.8,-112.195,24.841,-112.198,24.885,-112.132,25.224,-112.159,25.286,-112.222,24.951,-112.297,24.79,-112.21,24.763,-112.175,24.73,-112.163,24.65,-112.077,24.535,-112.057,24.546,-112.067,24.584,-112.126,24.654,-112.13,24.73,-112.164,24.8]},{"b":[166.893,-45.3,167.023,-45.18],"l":[166.893,-45.241,166.931,-45.277,167.023,-45.3,166.979,-45.18,166.963,-45.18,166.893,-45.241]},{"b":[124.288,10.601,124.382,10.707],"l":[124.335,10.707,124.288,10.601,124.316,10.606,124.381,10.633,124.382,10.68,124.371,10.691,124.335,10.707]},{"b":[125.133,9.933,125.288,10.155],"l":[125.231,10.116,125.176,10.151,125.133,10.155,125.159,10.063,125.288,9.933,125.281,9.982,125.231,10.116]},{"b":[122.797,7.315,122.948,7.433],"l":[122.915,7.433,122.871,7.397,122.822,7.428,122.797,7.393,122.805,7.316,122.84,7.315,122.944,7.361,122.948,7.386,122.937,7.409,122.915,7.433]},{"b":[125.683,6.906,125.785,7.186],"l":[125.783,7.131,125.714,7.186,125.683,7.073,125.708,7.04,125.769,6.906,125.785,6.963,125.783,7.131]},{"b":[125.922,9.568,125.993,9.759],"l":[125.968,9.759,125.949,9.739,125.922,9.621,125.952,9.568,125.971,9.594,125.993,9.685,125.968,9.759]},{"b":[124.36,11.486,124.622,11.695],"l":[124.565,11.64,124.511,11.687,124.437,11.695,124.36,11.666,124.429,11.532,124.483,11.486,124.608,11.492,124.622,11.55,124.565,11.64]},{"b":[124.731,11.543,124.854,11.715],"l":[124.788,11.683,124.731,11.715,124.744,11.659,124.781,11.581,124.807,11.558,124.836,11.543,124.854,11.595,124.821,11.627,124.788,11.683]},{"b":[134.795,-6.442,134.886,-6.324],"l":[134.795,-6.393,134.795,-6.442,134.82,-6.434,134.886,-6.324,134.852,-6.325,134.823,-6.35,134.795,-6.393]},{"b":[134.629,-6.765,134.736,-6.623],"l":[134.629,-6.713,134.631,-6.733,134.657,-6.765,134.674,-6.75,134.726,-6.669,134.736,-6.623,134.698,-6.626,134.663,-6.658,134.629,-6.713]},{"b":[127.834,-3.02,127.988,-2.937],"l":[127.834,-3.004,127.85,-3.016,127.938,-3.02,127.988,-2.937,127.938,-2.952,127.834,-3.004]},{"b":[127.488,-3.359,127.629,-3.254],"l":[127.554,-3.254,127.53,-3.262,127.488,-3.288,127.531,-3.331,127.629,-3.359,127.606,-3.315,127.554,-3.254]},{"b":[123.972,-6.022,124.051,-5.88],"l":[124.023,-5.902,123.976,-5.88,123.972,-5.939,124.006,-5.967,124.042,-6.022,124.051,-5.974,124.023,-5.902]},{"b":[123.541,-5.373,123.627,-5.25],"l":[123.55,-5.332,123.583,-5.367,123.623,-5.373,123.627,-5.272,123.561,-5.25,123.543,-5.271,123.541,-5.298,123.55,-5.332]},{"b":[120.633,-7.125,120.782,-7.018],"l":[120.633,-7.018,120.641,-7.116,120.672,-7.125,120.774,-7.119,120.782,-7.063,120.746,-7.06,120.633,-7.018]},{"b":[122.888,-8.604,123.153,-8.44],"l":[123.011,-8.448,122.933,-8.497,122.904,-8.531,122.888,-8.587,122.946,-8.604,122.977,-8.545,123.03,-8.495,123.153,-8.476,123.138,-8.457,123.089,-8.44,123.011,-8.448]},{"b":[117.482,-8.368,117.669,-8.148],"l":[117.546,-8.152,117.491,-8.183,117.482,-8.239,117.506,-8.307,117.49,-8.349,117.534,-8.368,117.556,-8.367,117.669,-8.189,117.665,-8.148,117.546,-8.152]},{"b":[69.469,66.49,70.11,66.751],"l":[70.057,66.599,70.058,66.627,70.077,66.696,69.918,66.712,69.8,66.736,69.616,66.739,69.503,66.751,69.469,66.716,69.651,66.565,69.845,66.49,70.021,66.502,70.059,66.518,70.11,66.569,70.057,66.599]},{"b":[96.739,76.188,97.053,76.355],"l":[96.854,76.199,96.974,76.237,97.053,76.303,97.045,76.315,96.99,76.343,96.878,76.355,96.835,76.345,96.833,76.324,96.74,76.258,96.739,76.207,96.754,76.196,96.798,76.188,96.854,76.199]},{"b":[99.915,79.602,100.3,79.701],"l":[100.068,79.701,99.956,79.69,99.942,79.671,99.915,79.602,100.136,79.614,100.3,79.67,100.142,79.684,100.068,79.701]},{"b":[161.083,68.9,161.618,69.639],"l":[161.521,69.634,161.505,69.639,161.41,69.596,161.323,69.541,161.111,69.47,161.083,69.406,161.165,69.334,161.125,69.197,161.137,69.11,161.183,69.082,161.364,69.044,161.461,68.996,161.456,68.966,161.422,68.9,161.467,68.901,161.507,68.928,161.517,68.97,161.495,69.016,161.394,69.106,161.378,69.194,161.373,69.293,161.351,69.369,161.374,69.414,161.54,69.437,161.609,69.501,161.618,69.592,161.521,69.634]},{"b":[129.687,31.64,129.794,31.787],"l":[129.707,31.718,129.687,31.64,129.718,31.657,129.794,31.742,129.787,31.787,129.707,31.718]},{"b":[126.319,36.428,126.418,36.613],"l":[126.387,36.571,126.319,36.613,126.338,36.471,126.404,36.428,126.418,36.493,126.387,36.571]},{"b":[124.846,39.508,124.935,39.608],"l":[124.89,39.602,124.846,39.559,124.849,39.508,124.905,39.536,124.935,39.608,124.89,39.602]},{"b":[121.132,28.063,121.251,28.204],"l":[121.205,28.204,121.134,28.135,121.132,28.063,121.164,28.063,121.251,28.086,121.251,28.145,121.234,28.181,121.205,28.204]},{"b":[113.404,22.758,113.564,22.905],"l":[113.465,22.905,113.404,22.903,113.426,22.859,113.463,22.832,113.486,22.828,113.564,22.758,113.555,22.804,113.521,22.852,113.465,22.905]},{"b":[112.713,21.582,112.863,21.772],"l":[112.782,21.772,112.761,21.733,112.713,21.698,112.733,21.67,112.742,21.618,112.771,21.582,112.79,21.602,112.801,21.695,112.813,21.712,112.863,21.753,112.839,21.765,112.782,21.772]},{"b":[112.525,21.619,112.648,21.71],"l":[112.559,21.675,112.525,21.623,112.546,21.619,112.644,21.64,112.648,21.71,112.559,21.675]},{"b":[113.839,22.21,114.044,22.333],"l":[113.998,22.21,114.003,22.278,114.044,22.333,113.882,22.28,113.839,22.242,113.852,22.22,113.877,22.21,113.998,22.21]},{"b":[107.078,10.337,107.195,10.446],"l":[107.078,10.388,107.084,10.337,107.168,10.397,107.195,10.446,107.177,10.446,107.151,10.42,107.078,10.388]},{"b":[103.223,10.68,103.318,10.782],"l":[103.223,10.782,103.223,10.76,103.281,10.68,103.318,10.719,103.223,10.782]},{"b":[8.4,54.688,8.588,54.76],"l":[8.51,54.76,8.468,54.757,8.418,54.739,8.4,54.714,8.454,54.691,8.549,54.688,8.588,54.713,8.573,54.749,8.51,54.76]},{"b":[6.642,53.579,6.801,53.625],"l":[6.755,53.625,6.669,53.606,6.642,53.579,6.735,53.583,6.801,53.625,6.755,53.625]},{"b":[-90.025,61.868,-61.3,73.855],"l":[-62.624,66.016,-62.497,65.974,-62.448,65.946,-62.41,65.906,-62.388,65.868,-62.382,65.833,-62.486,65.804,-62.61,65.724,-62.659,65.64,-62.772,65.632,-62.817,65.648,-62.969,65.622,-63.169,65.657,-63.241,65.696,-63.459,65.853,-63.464,65.835,-63.41,65.756,-63.421,65.709,-63.652,65.674,-63.651,65.661,-63.509,65.636,-63.337,65.617,-63.364,65.543,-63.363,65.23,-63.402,65.118,-63.486,65.021,-63.607,64.928,-63.737,64.989,-63.789,65.051,-63.833,65.083,-63.896,65.109,-63.976,65.121,-64.061,65.122,-64.152,65.066,-64.25,65.114,-64.346,65.172,-64.31,65.325,-64.27,65.401,-64.286,65.4,-64.34,65.364,-64.47,65.253,-64.555,65.117,-64.665,65.169,-64.765,65.234,-64.847,65.3,-64.98,65.375,-65.108,65.464,-65.176,65.568,-65.207,65.59,-65.282,65.677,-65.311,65.702,-65.337,65.71,-65.402,65.764,-65.378,65.822,-65.277,65.891,-65.185,65.94,-65.032,65.989,-64.854,66.016,-64.773,66.079,-64.673,66.193,-64.564,66.272,-64.445,66.317,-64.504,66.325,-64.655,66.287,-64.761,66.231,-64.887,66.137,-65.004,66.078,-65.305,66.008,-65.416,65.995,-65.544,65.987,-65.826,65.997,-65.891,66.02,-65.857,66.086,-65.656,66.205,-65.688,66.213,-65.759,66.171,-65.856,66.142,-65.94,66.127,-66.064,66.133,-66.209,66.206,-66.277,66.229,-66.419,66.254,-66.477,66.28,-66.712,66.46,-66.76,66.508,-66.787,66.556,-66.863,66.595,-66.986,66.627,-67.015,66.622,-66.97,66.582,-66.969,66.547,-67.077,66.525,-67.19,66.533,-67.307,66.57,-67.318,66.52,-67.192,66.433,-67.19,66.322,-67.225,66.31,-67.311,66.304,-67.369,66.317,-67.56,66.4,-67.741,66.458,-67.868,66.49,-67.883,66.467,-67.801,66.367,-67.704,66.269,-67.547,66.187,-67.297,66.09,-67.183,66.034,-67.273,65.956,-67.35,65.93,-67.399,65.922,-67.551,65.922,-67.828,65.965,-67.958,66.014,-68.147,66.13,-68.46,66.249,-68.528,66.249,-68.749,66.2,-68.714,66.192,-68.572,66.189,-68.467,66.173,-68.217,66.079,-68.198,66.039,-68.261,65.995,-68.257,65.939,-68.187,65.871,-68.115,65.828,-67.968,65.797,-67.894,65.793,-67.866,65.774,-67.954,65.623,-67.962,65.582,-67.937,65.565,-67.906,65.563,-67.717,65.625,-67.638,65.64,-67.57,65.644,-67.49,65.626,-67.4,65.588,-67.346,65.549,-67.33,65.509,-67.303,65.483,-67.118,65.44,-67.135,65.421,-67.326,65.357,-67.337,65.347,-67.298,65.342,-67.178,65.304,-67.067,65.244,-66.999,65.173,-66.985,65.138,-66.986,65.105,-66.97,65.085,-66.912,65.081,-66.887,65.094,-66.861,65.092,-66.831,65.074,-66.8,65.02,-66.733,64.86,-66.697,64.815,-66.677,64.814,-66.667,64.974,-66.635,65,-66.518,64.972,-66.345,64.91,-66.224,64.854,-66.21,64.828,-66.302,64.778,-66.282,64.755,-66.215,64.722,-66.152,64.735,-66.108,64.791,-66.03,64.847,-65.939,64.886,-65.768,64.854,-65.627,64.771,-65.605,64.742,-65.513,64.706,-65.432,64.726,-65.275,64.632,-65.349,64.589,-65.513,64.526,-65.529,64.505,-65.49,64.51,-65.179,64.51,-65.095,64.485,-65.075,64.437,-65.213,64.303,-65.34,64.315,-65.507,64.318,-65.594,64.311,-65.58,64.294,-65.348,64.232,-65.282,64.182,-65.193,64.13,-65.15,64.087,-65.151,64.068,-65.187,64.038,-65.17,64.028,-65.011,64.009,-64.912,64.026,-64.788,64.033,-64.678,64.028,-64.67,64.01,-64.686,63.961,-64.798,63.916,-64.768,63.905,-64.637,63.918,-64.576,63.897,-64.498,63.79,-64.411,63.706,-64.482,63.687,-64.562,63.68,-64.55,63.573,-64.499,63.463,-64.498,63.358,-64.514,63.264,-64.587,63.243,-64.665,63.245,-64.696,63.269,-64.886,63.549,-64.933,63.599,-64.99,63.643,-65.192,63.764,-65.184,63.745,-65.134,63.689,-65.089,63.606,-65.031,63.44,-65.005,63.333,-65.017,63.293,-65.058,63.283,-65.069,63.263,-65.049,63.235,-64.895,63.126,-64.82,63.06,-64.767,62.992,-64.718,62.946,-64.672,62.922,-64.684,62.902,-64.752,62.887,-64.869,62.88,-64.923,62.889,-65.133,62.952,-65.163,62.933,-65.047,62.701,-65.05,62.646,-65.108,62.626,-65.18,62.649,-65.266,62.715,-65.397,62.788,-65.572,62.869,-65.74,62.932,-65.78,62.93,-65.806,62.912,-65.834,62.909,-65.864,62.921,-65.92,62.969,-65.979,63.001,-66.224,63.107,-66.249,63.108,-66.226,63.076,-66.201,63.006,-66.229,62.991,-66.293,62.997,-66.414,63.027,-66.496,63.097,-66.6,63.219,-66.655,63.265,-66.66,63.235,-66.631,63.119,-66.636,63.08,-66.697,63.07,-66.723,63.08,-66.749,63.111,-66.773,63.162,-66.831,63.201,-66.923,63.228,-66.975,63.256,-67,63.305,-67.018,63.317,-67.18,63.305,-67.261,63.341,-67.495,63.481,-67.709,63.634,-67.844,63.715,-67.893,63.734,-67.821,63.635,-67.743,63.489,-67.723,63.423,-67.759,63.42,-67.838,63.449,-68.244,63.637,-68.494,63.725,-68.633,63.741,-68.859,63.752,-68.911,63.703,-68.789,63.595,-68.671,63.514,-68.555,63.459,-68.374,63.352,-68.208,63.215,-68.141,63.172,-67.915,63.114,-67.797,63.098,-67.676,63.094,-67.665,63.073,-67.724,63.034,-67.737,63.01,-67.468,62.948,-67.367,62.914,-67.269,62.858,-67.213,62.844,-66.98,62.701,-66.922,62.678,-66.714,62.632,-66.645,62.602,-66.531,62.51,-66.459,62.463,-66.357,62.352,-66.281,62.303,-66.095,62.246,-66.016,62.23,-65.98,62.209,-66.004,62.158,-66.027,62.137,-66.133,62.102,-66.116,62.054,-66.056,61.967,-66.059,61.914,-66.124,61.893,-66.257,61.868,-66.324,61.87,-66.425,61.891,-66.551,61.926,-66.803,62.013,-67.181,62.073,-67.322,62.105,-67.369,62.134,-67.44,62.151,-68.379,62.235,-68.536,62.256,-68.634,62.281,-68.724,62.319,-69.082,62.405,-69.126,62.424,-69.366,62.572,-69.545,62.745,-69.605,62.768,-69.8,62.79,-69.962,62.776,-70.071,62.757,-70.236,62.763,-70.344,62.792,-70.571,62.869,-70.801,62.91,-71.002,62.978,-71.106,63.002,-71.096,63.02,-70.946,63.121,-70.993,63.119,-71.254,63.043,-71.347,63.066,-71.501,63.126,-71.617,63.187,-71.855,63.355,-71.992,63.416,-71.973,63.43,-71.819,63.435,-71.697,63.43,-71.614,63.444,-71.456,63.512,-71.387,63.555,-71.381,63.58,-71.513,63.587,-71.542,63.599,-71.566,63.627,-71.627,63.663,-71.725,63.706,-71.838,63.725,-72.223,63.709,-72.29,63.728,-72.289,63.757,-72.213,63.839,-72.172,63.872,-72.159,63.89,-72.174,63.893,-72.226,63.891,-72.45,63.818,-72.498,63.823,-72.586,63.901,-72.639,63.989,-72.678,64.02,-72.73,64.03,-72.913,64.117,-73.174,64.282,-73.27,64.333,-73.377,64.38,-73.455,64.399,-73.444,64.423,-73.278,64.56,-73.271,64.583,-73.413,64.574,-73.627,64.603,-73.728,64.568,-73.793,64.566,-73.868,64.585,-73.91,64.578,-73.95,64.466,-73.981,64.438,-74.026,64.423,-74.065,64.425,-74.099,64.444,-74.098,64.47,-74.13,64.608,-74.205,64.628,-74.416,64.633,-74.461,64.645,-74.512,64.67,-74.556,64.717,-74.593,64.786,-74.634,64.824,-74.681,64.831,-74.719,64.825,-74.748,64.807,-74.813,64.796,-74.916,64.792,-74.919,64.766,-74.823,64.717,-74.73,64.647,-74.64,64.557,-74.695,64.497,-74.894,64.466,-75.067,64.457,-75.215,64.469,-75.328,64.49,-75.488,64.541,-75.715,64.524,-75.767,64.392,-75.815,64.385,-76.032,64.388,-76.118,64.376,-76.407,64.303,-76.495,64.293,-76.562,64.302,-76.627,64.284,-76.724,64.242,-76.856,64.238,-77.024,64.271,-77.166,64.285,-77.283,64.28,-77.403,64.3,-77.527,64.344,-77.628,64.363,-77.76,64.36,-77.791,64.367,-77.985,64.461,-78.045,64.499,-78.175,64.618,-78.198,64.665,-78.201,64.715,-78.19,64.752,-78.145,64.808,-78.096,64.939,-78.055,64.983,-77.995,65.023,-77.876,65.073,-77.447,65.162,-77.361,65.197,-77.364,65.22,-77.461,65.328,-77.46,65.356,-77.428,65.372,-77.358,65.435,-77.327,65.453,-77.251,65.463,-77.094,65.431,-76.959,65.418,-76.779,65.414,-76.482,65.37,-76.067,65.285,-75.828,65.227,-75.648,65.141,-75.52,65.056,-75.502,65.013,-75.561,64.947,-75.591,64.928,-75.589,64.905,-75.556,64.879,-75.452,64.842,-75.427,64.856,-75.435,64.901,-75.414,64.939,-75.363,64.969,-75.357,65.009,-75.397,65.058,-75.446,65.1,-75.505,65.135,-75.773,65.257,-75.799,65.298,-75.709,65.316,-75.317,65.275,-75.166,65.284,-75.109,65.331,-75.048,65.364,-74.982,65.381,-74.85,65.389,-74.665,65.367,-74.575,65.364,-74.495,65.372,-74.391,65.398,-74.237,65.484,-74.138,65.503,-73.99,65.517,-73.878,65.519,-73.675,65.484,-73.551,65.485,-73.561,65.543,-73.643,65.653,-73.746,65.767,-73.826,65.805,-74.033,65.877,-74.276,66.013,-74.401,66.097,-74.434,66.139,-74.416,66.167,-74.375,66.208,-73.934,66.358,-73.584,66.507,-73.431,66.583,-73.357,66.636,-73.281,66.675,-73.201,66.699,-73.033,66.728,-72.985,66.765,-72.975,66.829,-72.947,66.883,-72.789,67.031,-72.668,67.07,-72.485,67.098,-72.365,67.133,-72.22,67.254,-72.234,67.284,-72.301,67.307,-72.353,67.342,-72.576,67.659,-72.725,67.812,-72.904,67.945,-73.063,68.107,-73.328,68.267,-73.331,68.309,-73.284,68.357,-73.307,68.368,-73.58,68.298,-73.644,68.295,-73.749,68.325,-73.821,68.363,-73.879,68.429,-73.873,68.464,-73.834,68.497,-73.783,68.578,-73.781,68.619,-73.798,68.659,-73.822,68.686,-73.852,68.701,-73.935,68.711,-74.073,68.715,-74.118,68.701,-73.966,68.579,-73.989,68.549,-74.183,68.535,-74.27,68.541,-74.35,68.556,-74.422,68.58,-74.648,68.708,-74.696,68.756,-74.681,68.79,-74.7,68.808,-74.746,68.797,-74.808,68.796,-74.893,68.808,-74.91,68.823,-74.752,68.892,-74.743,68.913,-74.816,68.936,-74.925,68.941,-74.954,68.961,-74.917,68.983,-74.769,69.021,-74.717,69.046,-74.805,69.064,-74.855,69.066,-74.954,69.025,-75.104,68.941,-75.213,68.909,-75.363,68.948,-75.457,68.961,-75.523,68.953,-75.623,68.888,-75.842,68.84,-76.235,68.728,-76.403,68.692,-76.585,68.699,-76.619,68.721,-76.616,68.76,-76.604,68.792,-76.582,68.816,-76.575,68.847,-76.588,68.974,-76.557,69.009,-76.495,69.03,-76.381,69.052,-76.089,69.026,-75.954,69.031,-75.859,69.06,-75.763,69.103,-75.668,69.159,-75.648,69.213,-75.749,69.3,-75.787,69.319,-76.046,69.386,-76.19,69.411,-76.316,69.422,-76.408,69.441,-76.465,69.469,-76.52,69.517,-76.525,69.549,-76.516,69.591,-76.463,69.62,-76.231,69.653,-76.234,69.662,-76.424,69.687,-76.513,69.684,-76.59,69.656,-76.687,69.591,-76.742,69.573,-76.916,69.611,-77.02,69.617,-77.09,69.635,-77.129,69.653,-77.105,69.671,-77.019,69.689,-76.869,69.745,-76.859,69.775,-76.962,69.825,-77.016,69.836,-77.232,69.855,-77.494,69.836,-77.592,69.846,-77.635,69.9,-77.663,69.966,-77.675,70.042,-77.722,70.171,-77.774,70.239,-77.843,70.247,-78.157,70.219,-78.231,70.219,-78.283,70.229,-78.491,70.316,-78.575,70.346,-78.621,70.353,-78.773,70.445,-78.831,70.463,-78.9,70.509,-78.98,70.581,-79.066,70.604,-79.16,70.575,-79.253,70.535,-79.347,70.482,-79.397,70.437,-79.405,70.401,-79.347,70.372,-79.018,70.325,-78.934,70.294,-78.863,70.242,-78.81,70.179,-78.775,70.104,-78.778,70.048,-78.819,70.01,-78.89,69.977,-79.093,69.925,-79.303,69.895,-79.515,69.888,-79.616,69.895,-80.162,69.996,-80.26,69.997,-80.387,70.01,-80.67,70.052,-80.826,70.057,-81.098,70.091,-81.56,70.111,-81.652,70.095,-81.529,70.048,-81.422,70.025,-81.329,70.024,-81.197,69.983,-81.024,69.9,-80.925,69.851,-80.843,69.792,-80.84,69.771,-80.922,69.731,-81.565,69.943,-81.958,69.869,-82.139,69.841,-82.294,69.837,-82.488,69.866,-82.925,69.968,-83.091,70.004,-83.15,70.009,-83.531,69.965,-83.859,69.963,-84.522,70.005,-84.765,70.034,-84.829,70.063,-84.909,70.078,-85.053,70.078,-85.432,70.111,-85.78,70.037,-86.198,70.105,-86.322,70.145,-86.361,70.173,-86.483,70.289,-86.5,70.35,-86.465,70.406,-86.431,70.445,-86.397,70.465,-86.624,70.401,-86.704,70.391,-86.809,70.388,-87.122,70.412,-87.172,70.4,-87.156,70.377,-87.074,70.345,-87.063,70.325,-87.238,70.31,-87.502,70.326,-87.618,70.319,-87.67,70.31,-87.789,70.258,-87.838,70.247,-87.901,70.252,-88.178,70.369,-88.402,70.442,-88.663,70.471,-88.783,70.494,-88.848,70.523,-89.208,70.76,-89.258,70.811,-89.372,70.996,-89.41,71.036,-89.456,71.062,-89.366,71.067,-89.025,71.045,-88.696,71.046,-88.517,71.031,-88.309,70.984,-88.039,70.951,-87.845,70.944,-87.534,70.957,-87.182,70.988,-87.14,71.012,-87.369,71.053,-87.572,71.108,-87.76,71.179,-87.872,71.209,-88.061,71.227,-88.59,71.24,-89.079,71.288,-89.418,71.352,-89.693,71.423,-89.805,71.462,-89.846,71.492,-89.889,71.586,-89.934,71.743,-89.977,71.848,-90.02,71.902,-90.025,71.949,-89.931,72.049,-89.664,72.158,-89.657,72.175,-89.711,72.18,-89.823,72.208,-89.859,72.248,-89.873,72.313,-89.874,72.367,-89.862,72.412,-89.817,72.468,-89.702,72.568,-89.536,72.69,-89.358,72.804,-89.327,72.842,-89.311,72.943,-89.288,73.017,-89.263,73.069,-89.225,73.108,-89.115,73.182,-88.977,73.252,-88.761,73.312,-88.743,73.335,-88.74,73.365,-88.727,73.388,-88.705,73.403,-88.17,73.595,-87.926,73.673,-87.72,73.723,-87.472,73.759,-86.769,73.834,-86.406,73.855,-85.951,73.85,-85.11,73.808,-85.009,73.779,-84.984,73.764,-84.947,73.722,-84.975,73.695,-85.204,73.604,-85.494,73.528,-85.682,73.461,-86.001,73.313,-86.086,73.26,-86.481,72.96,-86.575,72.911,-86.629,72.871,-86.668,72.763,-86.656,72.724,-86.595,72.661,-86.38,72.525,-86.323,72.461,-86.324,72.402,-86.348,72.262,-86.351,72.191,-86.341,72.123,-86.297,72.026,-86.218,71.899,-86.036,71.771,-85.75,71.641,-85.537,71.555,-85.327,71.492,-85.079,71.398,-85.023,71.353,-85.138,71.303,-85.405,71.227,-85.757,71.194,-85.945,71.163,-86.179,71.096,-86.473,71.043,-86.589,71.011,-86.55,70.989,-86.321,71.017,-86.127,71.049,-85.825,71.126,-85.644,71.152,-85.095,71.152,-85.002,71.137,-85.043,71.092,-85.066,71.079,-85.048,71.059,-84.988,71.032,-84.87,71.002,-84.824,71.029,-84.79,71.093,-84.709,71.359,-84.674,71.439,-84.658,71.515,-84.66,71.586,-84.699,71.631,-84.84,71.659,-85.032,71.654,-85.131,71.661,-85.25,71.675,-85.339,71.697,-85.397,71.727,-85.512,71.817,-85.596,71.866,-85.813,71.956,-85.912,71.987,-85.862,72.022,-85.665,72.063,-85.546,72.102,-85.406,72.215,-85.322,72.233,-85.019,72.218,-84.608,72.129,-84.352,72.053,-84.284,72.044,-84.282,72.058,-84.347,72.094,-84.643,72.19,-84.778,72.259,-84.842,72.308,-84.811,72.33,-84.645,72.351,-84.623,72.377,-84.849,72.406,-84.964,72.406,-85.057,72.384,-85.156,72.383,-85.341,72.422,-85.391,72.444,-85.498,72.511,-85.554,72.569,-85.616,72.605,-85.638,72.633,-85.65,72.722,-85.645,72.774,-85.619,72.819,-85.575,72.856,-85.455,72.925,-85.388,72.945,-85.262,72.954,-84.99,72.92,-84.257,72.797,-84.274,72.836,-85.094,73.003,-85.384,73.045,-85.455,73.105,-85.018,73.335,-84.616,73.39,-84.416,73.456,-84.089,73.459,-83.782,73.417,-83.777,73.428,-83.915,73.508,-83.904,73.528,-83.73,73.576,-83.41,73.632,-83.02,73.676,-82.943,73.699,-82.843,73.715,-82.66,73.73,-82.203,73.736,-81.946,73.73,-81.605,73.696,-81.406,73.635,-81.344,73.598,-81.238,73.48,-81.152,73.314,-81.025,73.245,-80.822,73.207,-80.681,73.166,-80.603,73.121,-80.583,73.065,-80.619,72.997,-80.592,72.928,-80.501,72.857,-80.431,72.816,-80.277,72.77,-80.275,72.746,-80.323,72.717,-80.424,72.679,-80.675,72.559,-80.999,72.426,-81.229,72.312,-81.241,72.278,-80.761,72.457,-80.611,72.451,-80.605,72.426,-80.702,72.338,-80.821,72.26,-80.941,72.21,-80.919,72.191,-80.691,72.103,-80.733,72.089,-80.843,72.096,-80.888,72.088,-80.921,72.072,-80.941,72.048,-80.943,72.014,-80.925,71.971,-80.927,71.938,-80.948,71.917,-80.925,71.908,-80.858,71.911,-80.802,71.929,-80.705,71.988,-80.386,72.149,-80.182,72.209,-80.116,72.214,-79.928,72.175,-79.884,72.177,-80.091,72.301,-80.109,72.332,-80.067,72.378,-80.042,72.394,-79.927,72.428,-79.831,72.446,-79.778,72.439,-79.693,72.376,-79.654,72.332,-79.584,72.315,-79.427,72.337,-79.323,72.391,-79.194,72.356,-79,72.272,-79.018,72.188,-79.018,72.104,-79.008,72.043,-78.776,71.93,-78.614,71.881,-78.585,71.881,-78.589,71.898,-78.623,71.935,-78.711,71.972,-78.791,72.03,-78.863,72.101,-78.82,72.265,-78.699,72.351,-78.582,72.329,-78.429,72.28,-78.307,72.275,-78.116,72.28,-77.726,72.18,-77.517,72.178,-77.536,72.219,-77.694,72.238,-77.926,72.294,-78.287,72.36,-78.453,72.435,-78.484,72.471,-78.479,72.509,-78.459,72.542,-78.422,72.572,-78.35,72.6,-78.001,72.688,-77.753,72.725,-77.567,72.737,-77.255,72.736,-76.894,72.721,-76.698,72.695,-76.473,72.633,-76.189,72.572,-76.087,72.561,-75.969,72.563,-75.833,72.577,-75.704,72.572,-75.294,72.481,-75.186,72.434,-75.12,72.378,-75.071,72.323,-75.04,72.27,-75.053,72.226,-75.394,72.04,-75.543,72.008,-75.641,71.937,-75.787,71.803,-75.911,71.731,-75.923,71.717,-75.897,71.714,-75.822,71.746,-75.693,71.839,-75.6,71.918,-75.428,71.984,-75.148,72.063,-74.903,72.1,-74.695,72.097,-74.52,72.086,-74.377,72.067,-74.293,72.051,-74.266,72.038,-74.209,71.979,-74.213,71.939,-74.248,71.894,-74.316,71.843,-74.621,71.786,-74.789,71.742,-74.893,71.726,-75.205,71.709,-75.191,71.692,-74.959,71.667,-74.701,71.676,-74.707,71.647,-74.829,71.571,-74.868,71.505,-74.834,71.451,-74.841,71.407,-74.931,71.314,-75.035,71.231,-74.996,71.218,-74.759,71.338,-74.695,71.469,-74.6,71.585,-74.488,71.648,-74.404,71.673,-74.139,71.682,-73.992,71.75,-73.867,71.771,-73.814,71.771,-73.707,71.746,-73.714,71.72,-73.869,71.599,-74.197,71.404,-74.063,71.426,-73.973,71.473,-73.851,71.519,-73.713,71.588,-73.622,71.526,-73.482,71.479,-73.398,71.373,-73.262,71.322,-73.181,71.283,-73.192,71.35,-73.31,71.484,-73.278,71.538,-73.187,71.565,-72.902,71.678,-72.703,71.64,-72.581,71.607,-72.519,71.616,-72.365,71.611,-72.117,71.593,-71.875,71.561,-71.641,71.516,-71.46,71.464,-71.333,71.403,-71.256,71.362,-71.229,71.339,-71.187,71.279,-71.219,71.239,-71.397,71.147,-71.495,71.105,-71.593,71.086,-71.856,71.105,-71.938,71.094,-72.024,71.065,-72.298,70.939,-72.449,70.884,-72.598,70.849,-72.633,70.831,-72.313,70.833,-72.224,70.87,-72.15,70.941,-72.009,71.013,-71.743,71.047,-71.371,70.975,-71.186,70.978,-71.045,71.05,-70.888,71.099,-70.826,71.109,-70.792,71.103,-70.673,71.052,-70.636,71.007,-70.639,70.902,-70.655,70.871,-70.762,70.792,-71.022,70.674,-71.192,70.63,-71.38,70.606,-71.586,70.566,-71.658,70.534,-71.729,70.488,-71.8,70.457,-71.89,70.432,-71.772,70.394,-71.727,70.395,-71.684,70.418,-71.565,70.506,-71.477,70.544,-71.427,70.552,-71.375,70.548,-71.325,70.531,-71.276,70.5,-71.28,70.425,-71.429,70.128,-71.405,70.129,-71.313,70.209,-71.045,70.519,-70.98,70.581,-70.851,70.644,-70.561,70.738,-70.337,70.788,-70.085,70.83,-69.95,70.845,-69.796,70.835,-69.696,70.786,-69.56,70.777,-69.395,70.789,-69.289,70.783,-69.169,70.764,-69.066,70.728,-68.891,70.687,-68.496,70.61,-68.447,70.594,-68.401,70.565,-68.358,70.523,-68.364,70.481,-68.417,70.44,-68.483,70.415,-68.561,70.406,-68.643,70.383,-68.794,70.324,-68.843,70.314,-69.079,70.289,-69.299,70.277,-69.436,70.253,-69.699,70.189,-70.061,70.071,-70.058,70.043,-69.913,70.029,-69.796,70.047,-69.635,70.129,-69.483,70.16,-69.246,70.185,-68.919,70.207,-68.778,70.204,-68.753,70.199,-68.735,70.18,-68.723,70.146,-68.777,70.101,-68.839,70.08,-69.008,69.979,-68.897,69.953,-68.744,69.941,-68.657,69.968,-68.578,70.03,-68.489,70.065,-68.391,70.072,-68.305,70.087,-68.231,70.112,-68.21,70.128,-68.319,70.161,-68.327,70.18,-68.283,70.228,-68.204,70.281,-68.121,70.315,-68.059,70.317,-67.855,70.282,-67.716,70.22,-67.364,70.034,-67.318,69.998,-67.196,69.861,-67.173,69.799,-67.193,69.757,-67.222,69.731,-67.259,69.721,-67.337,69.721,-67.806,69.777,-68.02,69.77,-68.114,69.754,-68.189,69.731,-68.248,69.701,-68.29,69.665,-68.372,69.644,-68.67,69.644,-68.837,69.624,-69.125,69.575,-69.228,69.547,-69.251,69.512,-69.075,69.518,-68.785,69.564,-68.513,69.577,-68.058,69.476,-67.908,69.46,-67.825,69.475,-67.725,69.479,-67.361,69.473,-67.237,69.46,-67.053,69.421,-66.771,69.337,-66.717,69.312,-66.685,69.286,-66.676,69.258,-66.679,69.191,-66.707,69.168,-66.803,69.153,-67.208,69.171,-67.332,69.185,-67.484,69.167,-67.607,69.173,-67.765,69.2,-67.938,69.248,-68.198,69.203,-68.406,69.232,-68.619,69.206,-69.041,69.098,-68.993,69.079,-68.416,69.172,-68.304,69.166,-68.121,69.133,-67.833,69.066,-67.752,69.039,-67.751,68.934,-67.795,68.863,-67.883,68.784,-68.016,68.795,-68.324,68.844,-68.45,68.851,-68.543,68.843,-68.667,68.811,-68.725,68.81,-69.219,68.873,-69.33,68.876,-69.343,68.869,-69.319,68.857,-68.871,68.76,-68.541,68.749,-68.333,68.733,-68.21,68.703,-68.152,68.681,-68.148,68.616,-68.038,68.551,-67.938,68.524,-67.875,68.523,-67.766,68.547,-67.656,68.551,-67.567,68.534,-67.456,68.498,-67.321,68.488,-67.202,68.466,-67.111,68.461,-66.854,68.472,-66.743,68.458,-66.714,68.446,-66.762,68.425,-66.997,68.374,-67.033,68.326,-66.9,68.264,-66.831,68.216,-66.834,68.18,-66.905,68.098,-66.923,68.066,-66.9,68.063,-66.729,68.129,-66.702,68.121,-66.685,68.029,-66.663,68.034,-66.605,68.11,-66.631,68.211,-66.531,68.25,-66.212,68.28,-66.266,68.123,-66.275,68.041,-66.414,67.904,-66.53,67.86,-66.526,67.851,-66.444,67.834,-66.392,67.832,-66.343,67.853,-66.225,67.959,-65.986,68.069,-65.942,68.071,-65.944,68.031,-65.975,67.957,-65.864,67.923,-65.759,67.957,-65.702,67.987,-65.569,67.982,-65.509,67.968,-65.491,67.936,-65.552,67.799,-65.541,67.766,-65.401,67.675,-65.387,67.68,-65.413,67.724,-65.442,67.832,-65.415,67.879,-65.3,67.94,-65.064,68.026,-64.977,68.043,-64.922,68.032,-64.835,67.99,-64.863,67.965,-64.956,67.939,-65.026,67.892,-65.071,67.824,-65.021,67.788,-64.83,67.784,-64.638,67.84,-64.528,67.813,-64.396,67.74,-64.156,67.623,-64.019,67.655,-63.85,67.566,-64.077,67.496,-64.008,67.347,-64.303,67.353,-64.469,67.342,-64.58,67.355,-64.7,67.351,-64.589,67.316,-64.376,67.301,-64.356,67.256,-64.189,67.257,-64.063,67.266,-63.836,67.264,-63.824,67.316,-63.676,67.345,-63.592,67.378,-63.521,67.358,-63.316,67.336,-63.04,67.235,-63.162,67.174,-63.195,67.117,-63.236,67.069,-63.258,67.025,-63.307,66.994,-63.702,66.822,-63.636,66.821,-63.469,66.862,-63.144,66.924,-62.962,66.949,-62.833,66.933,-62.768,66.932,-62.71,66.954,-62.603,66.929,-62.38,66.905,-62.124,67.047,-61.969,67.019,-61.824,66.932,-61.515,66.778,-61.353,66.689,-61.3,66.649,-61.307,66.609,-61.453,66.567,-61.528,66.558,-61.724,66.638,-61.904,66.678,-62.014,66.674,-62.123,66.643,-62.089,66.626,-61.653,66.503,-61.576,66.413,-61.571,66.373,-61.863,66.313,-61.956,66.309,-62.158,66.338,-62.277,66.392,-62.375,66.411,-62.51,66.417,-62.553,66.407,-62.406,66.316,-62.42,66.289,-62.496,66.271,-62.534,66.227,-62.242,66.148,-62.024,66.068,-61.992,66.035,-62.139,66.011,-62.244,66.006,-62.468,66.017,-62.59,66.034,-62.624,66.016]},{"b":[-91.684,76.174,-61.207,83.116],"l":[-68.721,81.261,-68.689,81.293,-66.914,81.485,-66.861,81.499,-66.801,81.527,-66.765,81.563,-66.626,81.616,-66.005,81.629,-65.701,81.646,-65.495,81.668,-65.399,81.715,-65.226,81.744,-64.574,81.734,-64.436,81.743,-64.128,81.794,-63.592,81.846,-62.496,82.007,-62.177,82.043,-61.969,82.11,-61.615,82.184,-61.274,82.28,-61.207,82.341,-61.302,82.4,-61.392,82.442,-61.477,82.467,-61.697,82.489,-62.475,82.52,-63.247,82.45,-63.251,82.467,-63.087,82.533,-63.085,82.565,-63.385,82.653,-63.593,82.694,-63.643,82.713,-63.621,82.729,-63.564,82.749,-63.473,82.771,-63.499,82.793,-63.641,82.813,-63.984,82.829,-64.134,82.823,-64.433,82.778,-64.504,82.778,-64.635,82.819,-64.777,82.876,-64.905,82.901,-64.984,82.902,-65.113,82.889,-65.162,82.87,-65.247,82.819,-65.299,82.8,-65.4,82.802,-65.55,82.827,-65.727,82.842,-66.12,82.807,-66.612,82.742,-66.866,82.719,-66.998,82.716,-67.397,82.668,-67.736,82.652,-68.173,82.646,-68.469,82.653,-68.358,82.677,-66.836,82.818,-66.6,82.861,-66.425,82.906,-66.423,82.927,-66.592,82.944,-67.406,82.954,-67.624,82.964,-67.925,82.956,-68.107,82.961,-68.409,83.005,-68.673,82.999,-69.489,83.017,-69.569,83.025,-69.782,83.093,-69.868,83.11,-69.97,83.116,-70.871,83.098,-71.085,83.083,-71.424,83.021,-71.402,83.001,-71.198,82.97,-70.933,82.911,-70.94,82.902,-71.132,82.923,-71.406,82.975,-71.983,83.101,-72.069,83.106,-72.812,83.081,-73.331,82.999,-73.404,82.977,-73.441,82.946,-73.442,82.905,-73.235,82.844,-72.776,82.756,-72.659,82.722,-73.272,82.772,-73.703,82.852,-73.917,82.904,-74.056,82.955,-74.198,82.989,-74.414,83.013,-75.745,83.047,-77.125,83.009,-77.041,82.968,-76.908,82.919,-76.41,82.816,-76.188,82.758,-76.087,82.724,-75.643,82.644,-75.566,82.609,-75.744,82.572,-76.009,82.535,-76.146,82.55,-76.244,82.604,-76.336,82.644,-76.421,82.671,-77.226,82.837,-77.48,82.883,-77.618,82.896,-77.969,82.906,-78.525,82.891,-79.181,82.933,-79.886,82.939,-80.155,82.911,-80.141,82.894,-79.974,82.859,-79.834,82.817,-79.642,82.785,-79.207,82.733,-78.792,82.694,-78.749,82.679,-79.035,82.675,-80.076,82.706,-80.657,82.769,-81.01,82.779,-81.128,82.762,-81.178,82.745,-81.147,82.716,-80.81,82.586,-80.862,82.572,-81.189,82.594,-81.58,82.643,-81.785,82.649,-82.117,82.629,-82.123,82.602,-81.959,82.563,-81.681,82.519,-81.718,82.506,-82.023,82.494,-82.269,82.465,-82.451,82.427,-82.448,82.395,-82.254,82.336,-81.998,82.278,-81.468,82.192,-80.13,82.028,-79.629,81.932,-79.425,81.854,-79.466,81.851,-79.686,81.886,-79.909,81.936,-80.153,81.978,-80.55,82.005,-81.584,82.121,-82.277,82.218,-82.537,82.247,-82.638,82.246,-82.709,82.229,-82.747,82.196,-82.657,82.158,-82.327,82.092,-82.356,82.066,-82.634,82.077,-82.774,82.095,-83.01,82.142,-83.176,82.187,-83.591,82.326,-83.824,82.351,-84.368,82.374,-84.553,82.398,-84.745,82.437,-84.897,82.449,-85.276,82.405,-85.481,82.366,-85.794,82.292,-85.92,82.283,-86.188,82.248,-86.616,82.219,-86.581,82.187,-85.311,82.044,-85.169,82.023,-85.052,81.995,-85.045,81.983,-85.403,81.982,-85.538,81.955,-85.646,81.953,-85.875,81.976,-86.158,82.026,-86.378,82.045,-86.627,82.051,-86.834,82.033,-86.999,81.992,-87.018,81.959,-87.218,82,-87.404,82.054,-87.639,82.085,-88.063,82.096,-88.567,82.061,-88.875,82.018,-89.156,81.955,-89.381,81.917,-89.633,81.895,-90.163,81.894,-90.49,81.877,-90.942,81.827,-91.219,81.788,-91.424,81.744,-91.648,81.684,-91.684,81.636,-91.403,81.578,-91.292,81.571,-91.103,81.592,-90.834,81.64,-90.626,81.656,-90.48,81.639,-90.331,81.632,-89.822,81.635,-89.845,81.612,-90.554,81.464,-90.609,81.43,-90.416,81.405,-90.304,81.401,-88.978,81.542,-88.479,81.565,-88.101,81.559,-87.597,81.526,-87.617,81.509,-88.127,81.519,-88.622,81.501,-88.892,81.474,-89.427,81.387,-89.674,81.329,-89.636,81.302,-89.209,81.25,-89.263,81.239,-89.563,81.226,-89.947,81.173,-89.981,81.125,-89.792,81.065,-89.623,81.032,-89.398,81.025,-88.887,81.058,-87.275,81.081,-86.623,81.123,-85.875,81.241,-85.402,81.285,-85.206,81.295,-84.941,81.286,-85.083,81.247,-85.81,81.124,-86.477,81.036,-86.929,81,-87.389,80.988,-88.413,81,-89.167,80.941,-89.263,80.914,-89.212,80.882,-89.145,80.854,-89.062,80.83,-88.921,80.806,-88.625,80.77,-88.232,80.704,-88.004,80.675,-87.712,80.656,-87.33,80.67,-87.08,80.726,-86.233,80.95,-85.967,81.012,-85.781,81.035,-84.635,81.098,-83.289,81.148,-83.349,81.103,-84.68,81.042,-85.246,80.988,-85.639,80.925,-86.252,80.79,-86.44,80.728,-86.603,80.664,-86.615,80.63,-86.532,80.605,-86.25,80.566,-86.097,80.562,-85.726,80.581,-85.307,80.526,-85.146,80.521,-84.418,80.527,-84.22,80.538,-84.076,80.556,-83.885,80.602,-83.647,80.674,-83.401,80.714,-82.78,80.736,-82.498,80.763,-82.222,80.772,-82.337,80.729,-82.768,80.631,-82.884,80.578,-82.613,80.559,-82.368,80.561,-81.553,80.623,-81.301,80.627,-81.007,80.655,-80.134,80.764,-79.761,80.842,-79.607,80.882,-79.545,80.909,-79.477,80.961,-79.402,81.037,-79.309,81.089,-79.198,81.118,-79.072,81.128,-78.932,81.119,-78.734,81.151,-78.352,81.259,-77.972,81.331,-76.885,81.43,-77.031,81.386,-77.536,81.321,-78.287,81.168,-78.464,81.114,-78.629,81.043,-78.682,81.001,-78.716,80.952,-78.551,80.921,-78.004,80.905,-77.389,80.905,-77.119,80.896,-76.85,80.878,-76.863,80.865,-77.169,80.843,-77.507,80.835,-78.386,80.784,-79.629,80.648,-79.674,80.625,-80.051,80.529,-80.98,80.445,-82.536,80.376,-82.785,80.354,-82.987,80.323,-82.961,80.278,-82.681,80.175,-82.332,80.066,-81.86,79.957,-81.644,79.89,-81.359,79.788,-81.179,79.733,-81.01,79.693,-80.714,79.675,-80.287,79.679,-80.124,79.669,-80.271,79.635,-80.476,79.606,-80.668,79.601,-81.038,79.614,-81.463,79.654,-81.688,79.686,-81.856,79.723,-82.049,79.783,-82.377,79.908,-82.677,79.993,-83.004,80.055,-83.344,80.147,-83.724,80.229,-84.057,80.262,-84.675,80.279,-85.16,80.272,-86.307,80.319,-86.499,80.258,-86.615,80.124,-86.494,80.018,-86.421,79.845,-86.147,79.743,-86.031,79.722,-85.457,79.69,-85.269,79.664,-85.09,79.612,-84.836,79.495,-84.522,79.377,-84.381,79.301,-84.197,79.225,-83.978,79.163,-83.662,79.09,-83.576,79.054,-83.825,79.059,-84.053,79.099,-84.257,79.122,-84.384,79.119,-84.53,79.101,-84.568,79.071,-84.496,79.029,-84.412,78.997,-84.316,78.975,-84.146,78.96,-83.779,78.945,-83.059,78.94,-82.644,78.908,-82.439,78.904,-82.237,78.924,-82.028,78.962,-81.889,78.975,-81.75,78.976,-81.781,78.95,-81.981,78.898,-82.151,78.864,-82.291,78.847,-82.442,78.84,-82.99,78.844,-83.147,78.808,-83.271,78.77,-83.389,78.779,-83.547,78.804,-83.908,78.839,-84.787,78.885,-85.004,78.912,-85.23,78.902,-85.691,78.844,-86.242,78.824,-86.808,78.774,-86.953,78.664,-87.164,78.558,-87.361,78.479,-87.491,78.417,-87.491,78.284,-87.552,78.177,-87.339,78.133,-86.913,78.127,-86.694,78.151,-86.427,78.197,-86.071,78.285,-85.92,78.343,-86.063,78.187,-86.218,78.081,-85.586,78.11,-85.419,78.142,-85.27,78.2,-85.024,78.312,-84.783,78.528,-84.91,78.24,-84.55,78.251,-84.388,78.206,-84.223,78.176,-84.524,78.197,-84.615,78.196,-85.031,78.062,-85.265,78.011,-85.548,77.928,-85.292,77.764,-85.289,77.559,-85.088,77.515,-84.861,77.5,-84.486,77.562,-84.168,77.523,-83.928,77.518,-83.779,77.533,-83.428,77.621,-83.304,77.674,-82.704,77.962,-82.595,77.992,-82.626,77.936,-82.665,77.889,-82.71,77.85,-82.903,77.733,-83.25,77.585,-83.477,77.514,-83.55,77.483,-83.608,77.442,-83.721,77.414,-83.974,77.391,-84.487,77.368,-84.739,77.361,-84.951,77.375,-85.588,77.461,-85.731,77.509,-85.907,77.614,-86.173,77.746,-86.385,77.809,-86.755,77.864,-87.018,77.892,-87.236,77.892,-87.497,77.872,-87.757,77.836,-88.017,77.785,-88.095,77.719,-87.938,77.6,-87.78,77.493,-87.681,77.436,-87.589,77.395,-87.43,77.348,-87.265,77.343,-87.182,77.332,-87.101,77.308,-86.874,77.2,-86.812,77.185,-86.852,77.174,-87.064,77.166,-87.362,77.136,-87.61,77.127,-87.828,77.136,-88.148,77.124,-88.398,77.104,-88.556,77.072,-88.771,76.993,-89.5,76.827,-89.544,76.66,-89.57,76.492,-89.37,76.474,-88.804,76.457,-88.546,76.421,-88.563,76.547,-88.614,76.651,-88.496,76.773,-88.482,76.58,-88.396,76.405,-88.104,76.413,-87.498,76.386,-87.49,76.586,-87.354,76.448,-86.978,76.413,-86.68,76.377,-86.562,76.517,-86.454,76.585,-86.419,76.58,-86.367,76.549,-86.296,76.492,-86.116,76.435,-85.681,76.349,-85.344,76.313,-85.141,76.305,-84.275,76.357,-84.224,76.675,-83.986,76.495,-83.886,76.453,-83.389,76.439,-82.233,76.466,-82.208,76.514,-82.262,76.575,-82.357,76.636,-82.493,76.698,-82.53,76.723,-82.393,76.69,-82.311,76.655,-82.218,76.64,-82.114,76.643,-82.034,76.629,-81.823,76.521,-81.717,76.495,-81.592,76.484,-81.474,76.488,-81.365,76.504,-81.171,76.513,-81.074,76.498,-80.975,76.47,-80.832,76.409,-80.835,76.369,-80.901,76.322,-80.955,76.27,-80.997,76.215,-80.963,76.184,-80.8,76.174,-80.69,76.176,-80.187,76.24,-79.954,76.251,-79.511,76.31,-79.286,76.355,-79.131,76.404,-78.934,76.451,-78.284,76.571,-78.119,76.644,-77.983,76.755,-77.999,76.852,-78.165,76.935,-78.289,76.978,-78.37,76.981,-78.456,76.967,-78.659,76.908,-78.792,76.884,-78.979,76.893,-79.221,76.936,-79.319,76.98,-79.274,77.026,-79.281,77.085,-79.341,77.158,-79.497,77.196,-79.924,77.194,-80.219,77.147,-80.274,77.151,-80.673,77.244,-80.798,77.259,-81.117,77.27,-81.277,77.257,-81.534,77.214,-81.756,77.204,-81.84,77.214,-81.968,77.248,-82.066,77.284,-82.057,77.297,-81.767,77.296,-81.523,77.311,-81.301,77.344,-81.278,77.365,-81.378,77.385,-81.504,77.43,-81.654,77.499,-81.659,77.525,-81.519,77.51,-81.377,77.482,-80.875,77.359,-80.573,77.315,-80.282,77.301,-79.906,77.3,-79.138,77.331,-78.87,77.333,-78.708,77.342,-78.493,77.369,-78.284,77.413,-78.168,77.458,-78.076,77.519,-78.047,77.615,-78.081,77.747,-78.084,77.846,-78.056,77.912,-78.013,77.946,-77.456,77.947,-76.974,77.927,-76.708,77.938,-76.356,77.991,-76.078,77.987,-75.97,77.993,-75.866,78.01,-75.551,78.221,-75.193,78.328,-75.237,78.356,-75.488,78.404,-76.137,78.492,-76.416,78.512,-76.373,78.521,-75.966,78.53,-75.397,78.523,-74.879,78.545,-74.547,78.62,-74.535,78.659,-74.433,78.724,-74.486,78.75,-74.618,78.758,-75.099,78.858,-75.4,78.881,-75.795,78.89,-75.953,78.959,-76.077,78.985,-76.256,79.007,-76.524,79.024,-76.825,79.018,-77.51,78.978,-77.698,78.955,-77.883,78.942,-78.037,78.964,-78.222,79.015,-78.422,79.048,-78.559,79.055,-78.582,79.075,-78.258,79.082,-77.974,79.076,-77.729,79.057,-77.398,79.057,-76.771,79.087,-76.531,79.087,-76.38,79.104,-76.158,79.1,-75.912,79.118,-75.639,79.088,-75.515,79.061,-75.233,79.036,-74.641,79.036,-74.532,79.053,-74.481,79.229,-74.727,79.235,-75.094,79.204,-75.354,79.228,-75.603,79.24,-75.948,79.311,-76.116,79.326,-76.296,79.414,-76.671,79.478,-76.855,79.488,-76.899,79.512,-76.376,79.494,-76.067,79.473,-75.774,79.431,-75.503,79.414,-75.259,79.421,-74.798,79.459,-74.406,79.454,-74.189,79.465,-74.015,79.491,-73.866,79.501,-73.466,79.495,-73.362,79.504,-73.294,79.522,-73.24,79.552,-73.201,79.597,-73.229,79.644,-73.406,79.732,-73.472,79.756,-73.642,79.771,-74.051,79.778,-74.541,79.816,-74.66,79.835,-74.394,79.874,-74.144,79.88,-73.805,79.846,-73.448,79.827,-72.437,79.694,-72.216,79.687,-71.965,79.701,-71.388,79.762,-71.299,79.783,-71.11,79.848,-71.106,79.876,-71.278,79.906,-71.356,79.911,-70.758,79.998,-70.559,80.071,-70.568,80.094,-70.758,80.119,-70.877,80.122,-71.616,80.071,-71.949,80.086,-72.063,80.106,-72.056,80.123,-71.928,80.139,-71.796,80.143,-71.661,80.136,-71.47,80.146,-71.1,80.187,-70.265,80.234,-70.213,80.278,-70.668,80.506,-70.713,80.54,-70.639,80.528,-70.403,80.459,-70.144,80.398,-69.949,80.374,-69.734,80.367,-69.551,80.383,-69.4,80.423,-68.959,80.587,-68.63,80.679,-67.774,80.859,-66.727,81.041,-66.313,81.146,-65.484,81.285,-64.833,81.439,-64.78,81.493,-65.24,81.51,-65.736,81.494,-68.318,81.261,-68.543,81.248,-68.721,81.261]},{"b":[-136.444,68.684,-120.682,70.574],"l":[-120.682,69.567,-120.815,69.617,-120.962,69.66,-121.336,69.742,-121.531,69.776,-121.742,69.798,-122.07,69.816,-122.387,69.808,-122.705,69.817,-122.785,69.808,-122.957,69.819,-123.026,69.81,-123.077,69.782,-123.11,69.738,-123.144,69.632,-123.214,69.542,-123.249,69.52,-123.361,69.497,-123.46,69.42,-123.528,69.389,-123.609,69.377,-124.05,69.373,-124.112,69.359,-124.338,69.365,-124.426,69.379,-124.472,69.4,-124.481,69.425,-124.454,69.455,-124.398,69.494,-124.138,69.653,-124.125,69.69,-124.349,69.735,-124.407,69.767,-124.472,69.919,-124.467,69.983,-124.442,70.062,-124.444,70.111,-124.503,70.141,-124.555,70.151,-124.64,70.141,-124.706,70.117,-124.745,70.08,-124.952,70.042,-124.99,70.027,-124.963,70.013,-124.92,70.006,-124.863,70.006,-124.768,69.99,-124.794,69.969,-124.889,69.936,-124.968,69.894,-125.031,69.844,-125.08,69.818,-125.114,69.815,-125.201,69.829,-125.228,69.757,-125.219,69.732,-125.346,69.662,-125.357,69.626,-125.262,69.566,-125.167,69.48,-125.172,69.428,-125.387,69.349,-125.525,69.352,-125.728,69.38,-125.907,69.419,-126.064,69.467,-126.25,69.545,-126.612,69.73,-126.685,69.777,-126.759,69.853,-126.833,69.959,-126.927,70.062,-127.138,70.239,-127.226,70.296,-127.377,70.369,-127.753,70.517,-127.862,70.549,-127.991,70.574,-128.04,70.566,-128.127,70.524,-128.168,70.48,-128.17,70.418,-128.121,70.397,-127.989,70.363,-128.044,70.329,-128.034,70.315,-127.974,70.293,-127.684,70.26,-127.765,70.222,-128.096,70.161,-128.279,70.108,-128.359,69.988,-128.387,69.96,-128.706,69.81,-128.853,69.751,-128.971,69.712,-129.054,69.701,-129.102,69.717,-129.136,69.75,-129.158,69.8,-129.138,69.833,-128.939,69.875,-128.917,69.895,-128.884,69.963,-128.899,69.966,-128.984,69.933,-129.033,69.905,-129.109,69.882,-129.265,69.855,-129.572,69.827,-130.118,69.72,-130.354,69.656,-130.516,69.57,-130.661,69.481,-130.875,69.32,-130.914,69.285,-130.971,69.209,-130.982,69.253,-130.986,69.363,-131.013,69.429,-131.063,69.451,-131.113,69.459,-131.162,69.455,-131.209,69.432,-131.294,69.364,-131.325,69.361,-131.303,69.415,-131.343,69.435,-131.563,69.461,-131.632,69.459,-131.788,69.432,-131.82,69.402,-131.781,69.389,-131.787,69.371,-131.833,69.336,-131.92,69.291,-132.134,69.234,-132.214,69.202,-132.358,69.167,-132.545,69.141,-132.719,69.079,-132.755,69.042,-132.77,69.012,-132.765,68.972,-132.739,68.922,-132.704,68.896,-132.542,68.89,-132.533,68.876,-132.578,68.848,-132.706,68.815,-133.304,68.847,-133.337,68.835,-133.32,68.82,-133.192,68.777,-133.138,68.747,-133.197,68.74,-133.348,68.77,-133.373,68.788,-133.418,68.844,-133.379,68.887,-133.228,68.967,-133.089,69.029,-132.968,69.101,-132.817,69.206,-132.687,69.26,-132.481,69.273,-132.331,69.308,-132.197,69.365,-132.129,69.402,-131.989,69.518,-131.938,69.535,-131.863,69.549,-131.473,69.579,-131.306,69.597,-131.208,69.616,-130.96,69.632,-130.832,69.651,-130.709,69.686,-130.459,69.78,-129.648,69.998,-129.538,70.074,-129.538,70.105,-129.623,70.168,-129.676,70.193,-129.73,70.192,-129.898,70.106,-129.945,70.091,-130.043,70.095,-130.175,70.086,-130.275,70.098,-130.396,70.129,-130.498,70.143,-130.665,70.127,-130.926,70.052,-130.991,70.018,-131.032,69.979,-131.136,69.907,-131.216,69.901,-131.319,69.924,-131.441,69.918,-131.582,69.882,-131.934,69.753,-132.163,69.705,-132.232,69.708,-132.334,69.752,-132.488,69.738,-132.538,69.727,-132.571,69.707,-132.568,69.698,-132.479,69.693,-132.413,69.674,-132.404,69.659,-132.452,69.647,-132.527,69.643,-132.84,69.651,-132.915,69.63,-133.028,69.508,-133.084,69.471,-133.163,69.434,-133.294,69.412,-133.476,69.405,-133.694,69.368,-133.948,69.301,-134.174,69.253,-134.165,69.281,-134.018,69.388,-133.947,69.429,-133.88,69.508,-133.9,69.528,-134.077,69.558,-134.134,69.587,-134.19,69.639,-134.242,69.669,-134.409,69.682,-134.451,69.665,-134.474,69.633,-134.495,69.572,-134.491,69.545,-134.457,69.478,-134.494,69.468,-134.853,69.486,-135.141,69.468,-135.199,69.45,-135.23,69.425,-135.255,69.324,-135.293,69.308,-135.5,69.337,-135.615,69.291,-135.691,69.311,-135.91,69.111,-135.85,69.081,-135.743,69.049,-135.651,69.031,-135.576,69.027,-135.59,69.008,-135.695,69.001,-135.873,69.001,-135.925,68.993,-135.939,68.974,-135.895,68.927,-135.876,68.917,-135.638,68.892,-135.435,68.842,-135.407,68.829,-135.231,68.694,-135.259,68.684,-135.362,68.696,-135.867,68.833,-136.122,68.882,-136.444,68.895]},{"b":[-168.088,-53.883,-34.805,71.987],"l":[-136.444,68.895,-136.499,68.897,-136.717,68.889,-137.07,68.951,-137.26,68.964,-137.869,69.093,-138.128,69.152,-138.291,69.219,-138.69,69.317,-139.182,69.516,-139.977,69.622,-140.405,69.602,-140.86,69.635,-141.002,69.651,-141.081,69.659,-141.29,69.665,-141.339,69.647,-141.408,69.653,-141.526,69.715,-141.699,69.77,-142.297,69.87,-142.422,69.94,-142.708,70.034,-143.218,70.116,-143.276,70.095,-143.357,70.09,-143.566,70.101,-143.746,70.102,-144.064,70.054,-144.417,70.039,-144.619,69.982,-145.197,70.009,-145.237,70.034,-145.44,70.051,-145.823,70.16,-146.058,70.156,-146.281,70.186,-146.745,70.192,-147.063,70.17,-147.705,70.217,-147.791,70.24,-147.87,70.303,-148.039,70.315,-148.143,70.355,-148.249,70.357,-148.371,70.315,-148.479,70.318,-148.688,70.416,-148.845,70.425,-149.269,70.501,-149.411,70.491,-149.544,70.513,-149.87,70.51,-150.152,70.444,-150.274,70.434,-150.403,70.444,-150.544,70.49,-150.663,70.51,-150.979,70.465,-151.128,70.452,-151.225,70.419,-151.945,70.452,-151.82,70.511,-151.8,70.538,-151.769,70.56,-152.173,70.557,-152.253,70.568,-152.27,70.615,-152.399,70.62,-152.471,70.654,-152.437,70.733,-152.233,70.81,-152.3,70.847,-152.491,70.881,-152.671,70.891,-152.785,70.876,-153.233,70.933,-153.498,70.891,-153.701,70.894,-153.918,70.877,-154.195,70.801,-154.392,70.838,-154.599,70.848,-154.785,70.894,-154.726,70.928,-154.674,70.987,-154.818,71.048,-154.944,71.083,-155.167,71.099,-155.23,71.082,-155.313,71.015,-155.579,70.894,-155.708,70.857,-155.872,70.835,-155.974,70.842,-156.042,70.902,-156.147,70.928,-155.804,70.995,-155.635,71.062,-155.579,71.121,-155.646,71.183,-155.811,71.188,-156.47,71.292,-156.567,71.342,-156.497,71.379,-156.395,71.397,-156.47,71.408,-156.783,71.319,-156.973,71.23,-157.195,71.093,-157.325,71.04,-157.606,70.941,-157.909,70.86,-157.998,70.845,-158.484,70.841,-158.511,70.82,-158.621,70.799,-158.996,70.802,-159.075,70.772,-159.251,70.748,-159.304,70.753,-159.34,70.781,-159.262,70.814,-159.183,70.832,-159.192,70.86,-159.232,70.877,-159.315,70.879,-159.681,70.787,-160.082,70.635,-159.962,70.634,-159.746,70.53,-159.387,70.525,-159.683,70.477,-159.815,70.497,-159.843,70.453,-159.858,70.389,-159.855,70.324,-159.866,70.279,-159.908,70.331,-160.095,70.333,-160.006,70.448,-160.106,70.473,-159.963,70.568,-160.046,70.586,-160.117,70.591,-160.634,70.446,-160.648,70.421,-160.996,70.305,-161.639,70.235,-161.768,70.197,-161.997,70.165,-162.074,70.162,-162.042,70.177,-161.912,70.205,-161.818,70.248,-161.761,70.258,-161.78,70.277,-161.813,70.29,-161.881,70.332,-161.978,70.288,-162.071,70.227,-162.35,70.094,-162.952,69.758,-163.094,69.611,-163.131,69.454,-163.161,69.388,-163.187,69.38,-163.205,69.393,-163.251,69.345,-163.536,69.17,-163.868,69.037,-164.15,68.961,-164.302,68.936,-164.89,68.902,-165.044,68.882,-165.509,68.868,-166.209,68.885,-166.182,68.797,-166.283,68.573,-166.381,68.425,-166.447,68.39,-166.57,68.361,-166.648,68.374,-166.546,68.424,-166.644,68.408,-166.786,68.36,-166.574,68.32,-166.409,68.308,-166.236,68.278,-165.96,68.156,-165.386,68.046,-164.125,67.607,-163.943,67.478,-163.8,67.271,-163.721,67.196,-163.532,67.103,-163.002,67.027,-162.761,67.036,-162.583,67.019,-162.409,67.104,-162.412,67.06,-162.392,67.02,-161.965,67.05,-161.72,67.021,-161.622,66.979,-161.731,66.923,-161.879,66.804,-161.857,66.7,-161.681,66.646,-161.572,66.592,-161.398,66.552,-161.051,66.653,-160.864,66.671,-160.644,66.605,-160.361,66.613,-160.263,66.572,-160.227,66.509,-160.232,66.42,-160.651,66.373,-160.784,66.384,-161.048,66.474,-161.156,66.495,-161.336,66.496,-161.591,66.46,-161.91,66.56,-162.051,66.667,-162.018,66.784,-162.131,66.801,-162.254,66.919,-162.362,66.947,-162.478,66.931,-162.607,66.894,-162.544,66.805,-162.467,66.736,-162.318,66.734,-162.191,66.693,-161.888,66.493,-161.917,66.412,-161.828,66.371,-161.544,66.407,-161.12,66.334,-161.07,66.295,-161.034,66.189,-161.109,66.24,-161.201,66.219,-161.345,66.247,-161.455,66.281,-161.557,66.251,-161.816,66.054,-161.934,66.043,-162.214,66.071,-162.587,66.051,-162.722,66.06,-162.886,66.099,-163.171,66.075,-163.695,66.084,-164.034,66.216,-163.965,66.257,-163.894,66.287,-163.903,66.378,-163.794,66.493,-163.775,66.531,-163.838,66.562,-163.894,66.576,-163.816,66.583,-163.638,66.575,-163.728,66.616,-164.058,66.611,-164.46,66.588,-164.674,66.555,-165.064,66.438,-165.198,66.44,-165.449,66.41,-165.776,66.319,-165.812,66.288,-165.84,66.245,-165.56,66.167,-165.59,66.145,-165.63,66.131,-165.724,66.113,-166.009,66.121,-166.057,66.127,-166.215,66.17,-166.399,66.144,-166.54,66.101,-166.748,66.052,-166.894,65.959,-166.997,65.905,-167.074,65.877,-167.405,65.859,-167.58,65.758,-167.914,65.681,-167.927,65.714,-167.931,65.748,-168.01,65.719,-168.088,65.658,-168.035,65.596,-167.987,65.568,-167.404,65.422,-166.665,65.338,-166.609,65.353,-166.197,65.306,-166.157,65.286,-166.121,65.261,-166.28,65.274,-166.452,65.247,-166.531,65.155,-166.763,65.135,-166.857,65.147,-166.906,65.164,-166.928,65.157,-166.827,65.096,-166.551,64.953,-166.415,64.927,-166.409,64.827,-166.478,64.798,-166.481,64.728,-166.325,64.626,-166.143,64.583,-165.446,64.513,-165.138,64.465,-164.979,64.454,-164.9,64.461,-164.857,64.48,-164.83,64.511,-164.765,64.53,-164.727,64.523,-164.692,64.507,-164.304,64.584,-163.713,64.588,-163.486,64.55,-163.267,64.475,-163.144,64.424,-163.104,64.479,-163.052,64.52,-163.174,64.533,-163.248,64.563,-163.303,64.606,-163.204,64.652,-162.876,64.516,-162.807,64.374,-162.711,64.378,-162.636,64.451,-162.335,64.613,-162.172,64.678,-161.868,64.743,-161.759,64.816,-161.634,64.792,-161.466,64.795,-161.187,64.924,-161.13,64.925,-161.063,64.904,-160.967,64.84,-160.887,64.796,-160.856,64.756,-160.836,64.682,-160.894,64.613,-160.932,64.579,-161.049,64.534,-161.193,64.516,-161.415,64.526,-161.491,64.434,-161.386,64.44,-161.22,64.397,-160.988,64.251,-160.904,64.031,-160.84,63.935,-160.779,63.819,-160.827,63.729,-160.927,63.661,-161.1,63.558,-161.266,63.497,-161.505,63.468,-161.974,63.453,-162.056,63.471,-162.112,63.534,-162.193,63.541,-162.283,63.529,-162.36,63.453,-162.621,63.266,-162.808,63.207,-162.948,63.115,-163.062,63.08,-163.288,63.046,-163.359,63.046,-163.423,63.085,-163.504,63.106,-163.649,63.057,-163.738,63.016,-163.749,63.03,-163.726,63.048,-163.664,63.07,-163.634,63.09,-163.616,63.125,-163.736,63.193,-163.943,63.247,-164.108,63.262,-164.409,63.215,-164.463,63.185,-164.525,63.128,-164.375,63.054,-164.384,63.03,-164.428,63.04,-164.677,63.02,-164.764,62.971,-164.8,62.918,-164.845,62.801,-164.819,62.677,-164.793,62.623,-164.689,62.677,-164.589,62.709,-164.596,62.687,-164.688,62.608,-164.844,62.581,-164.796,62.512,-164.758,62.497,-164.779,62.481,-164.892,62.518,-165,62.534,-165.116,62.513,-165.195,62.474,-165.448,62.304,-165.707,62.1,-165.744,62.012,-165.725,61.959,-165.706,61.927,-165.613,61.869,-165.834,61.837,-165.991,61.834,-166.079,61.803,-166.02,61.748,-165.809,61.696,-165.835,61.679,-166.1,61.645,-166.131,61.657,-166.168,61.651,-166.164,61.589,-166.153,61.546,-166.094,61.507,-165.961,61.551,-165.845,61.536,-165.797,61.491,-165.906,61.404,-165.864,61.336,-165.691,61.3,-165.628,61.165,-165.566,61.102,-165.48,61.095,-165.381,61.106,-165.379,61.169,-165.392,61.212,-165.334,61.266,-165.274,61.275,-165.244,61.269,-165.311,61.228,-165.345,61.198,-165.28,61.17,-165.204,61.153,-165.15,61.187,-165.128,61.192,-165.138,61.13,-165.077,61.094,-164.941,61.115,-164.869,61.112,-164.876,61.087,-165,61.044,-165.175,60.966,-165.115,60.933,-165.066,60.921,-164.754,60.931,-164.442,60.87,-163.995,60.865,-163.749,60.97,-163.659,60.938,-163.587,60.903,-163.655,60.877,-163.837,60.88,-163.907,60.854,-163.623,60.822,-163.512,60.798,-163.421,60.757,-163.529,60.665,-163.73,60.59,-163.821,60.668,-163.895,60.745,-163.936,60.758,-164,60.766,-164.132,60.692,-164.31,60.607,-164.372,60.592,-164.321,60.647,-164.266,60.725,-164.319,60.771,-164.37,60.796,-164.513,60.819,-164.682,60.872,-164.805,60.892,-164.9,60.873,-165.016,60.74,-165.354,60.541,-165.225,60.524,-165.113,60.526,-165.027,60.501,-165.049,60.464,-165.061,60.413,-164.92,60.348,-164.8,60.307,-164.662,60.304,-164.471,60.149,-164.132,59.994,-164.141,59.949,-164.143,59.897,-163.907,59.807,-163.68,59.802,-163.219,59.846,-162.878,59.923,-162.733,59.994,-162.571,59.99,-162.536,60.038,-162.5,60.127,-162.527,60.199,-162.548,60.231,-162.685,60.269,-162.6,60.297,-162.469,60.395,-162.265,60.595,-162.2,60.634,-162.138,60.686,-162.068,60.695,-161.962,60.695,-161.947,60.685,-162.139,60.614,-162.288,60.457,-162.421,60.284,-162.242,60.178,-162.138,59.98,-161.909,59.714,-161.829,59.589,-161.832,59.515,-161.872,59.428,-161.92,59.365,-162.023,59.284,-161.981,59.146,-161.891,59.076,-161.794,59.109,-161.644,59.11,-161.789,59.016,-161.79,58.95,-161.781,58.897,-161.724,58.794,-161.856,58.717,-162.009,58.685,-162.145,58.644,-161.755,58.612,-161.361,58.67,-161.328,58.744,-161.288,58.761,-161.247,58.799,-161.216,58.801,-160.924,58.872,-160.817,58.872,-160.657,58.955,-160.52,59.007,-160.363,59.051,-160.261,58.972,-160.153,58.906,-159.92,58.82,-159.832,58.836,-159.741,58.894,-159.67,58.911,-159.454,58.793,-159.358,58.721,-159.083,58.47,-158.951,58.405,-158.789,58.441,-158.772,58.52,-158.861,58.719,-158.838,58.794,-158.776,58.903,-158.809,58.974,-158.761,58.95,-158.678,58.929,-158.584,58.988,-158.514,59.073,-158.423,59.09,-158.221,59.038,-158.081,58.977,-158.189,58.98,-158.315,59.009,-158.426,58.999,-158.476,58.938,-158.503,58.85,-158.439,58.783,-158.39,58.746,-158.303,58.642,-158.191,58.614,-158.022,58.64,-157.666,58.748,-157.142,58.878,-156.963,58.989,-156.809,59.134,-156.923,58.964,-157.04,58.773,-157.009,58.744,-156.975,58.736,-157.229,58.641,-157.461,58.503,-157.524,58.421,-157.524,58.351,-157.488,58.254,-157.394,58.235,-157.339,58.235,-157.194,58.194,-157.443,58.172,-157.555,58.14,-157.611,58.051,-157.621,57.895,-157.684,57.744,-157.697,57.679,-157.681,57.638,-157.608,57.601,-157.572,57.541,-157.533,57.526,-157.474,57.518,-157.462,57.506,-157.535,57.483,-157.646,57.498,-157.674,57.514,-157.697,57.539,-157.737,57.548,-157.846,57.528,-157.894,57.511,-158.046,57.409,-158.134,57.366,-158.225,57.343,-158.321,57.298,-158.474,57.199,-158.586,57.114,-158.661,57.039,-158.677,56.997,-158.685,56.944,-158.681,56.888,-158.666,56.828,-158.675,56.795,-158.709,56.789,-158.782,56.796,-158.895,56.816,-158.918,56.847,-158.918,56.882,-158.99,56.86,-159.159,56.77,-159.283,56.689,-159.785,56.562,-160.046,56.437,-160.149,56.396,-160.302,56.314,-160.377,56.241,-160.461,56.138,-160.515,56.059,-160.539,56.006,-160.527,55.965,-160.48,55.935,-160.308,55.864,-160.27,55.832,-160.292,55.805,-160.347,55.8,-160.437,55.817,-160.498,55.838,-160.53,55.863,-160.6,55.874,-160.706,55.87,-160.758,55.855,-160.746,55.771,-160.763,55.757,-160.803,55.754,-160.851,55.772,-161.005,55.887,-161.008,55.912,-160.902,55.941,-160.878,55.971,-160.899,55.994,-160.969,55.97,-161.145,55.951,-161.193,55.954,-161.223,55.977,-161.179,56.014,-161.216,56.021,-161.697,55.907,-161.937,55.824,-162.157,55.719,-162.349,55.595,-162.513,55.45,-162.659,55.351,-162.786,55.297,-162.857,55.253,-162.872,55.219,-162.907,55.196,-162.962,55.184,-163.008,55.187,-163.045,55.205,-163.115,55.194,-163.279,55.122,-163.304,55.096,-163.306,55.059,-163.286,55.01,-163.296,54.949,-163.338,54.876,-163.335,54.839,-163.289,54.838,-163.221,54.863,-163.131,54.917,-163.1,54.974,-163.128,55.035,-163.12,55.065,-162.996,55.046,-162.865,54.955,-162.82,54.95,-162.674,54.997,-162.619,55.038,-162.614,55.071,-162.644,55.218,-162.63,55.247,-162.542,55.243,-162.495,55.208,-162.427,55.145,-162.413,55.107,-162.452,55.093,-162.457,55.078,-162.428,55.061,-162.386,55.052,-162.333,55.05,-162.275,55.073,-162.211,55.121,-162.167,55.144,-162.074,55.139,-161.98,55.199,-161.742,55.391,-161.72,55.421,-161.684,55.53,-161.654,55.563,-161.599,55.593,-161.517,55.618,-161.459,55.629,-161.357,55.612,-161.255,55.579,-161.215,55.56,-161.202,55.544,-161.313,55.559,-161.373,55.556,-161.413,55.536,-161.444,55.513,-161.477,55.465,-161.481,55.398,-161.464,55.383,-161.382,55.371,-161.178,55.389,-161.1,55.406,-161.024,55.44,-160.952,55.493,-160.897,55.514,-160.771,55.484,-160.727,55.5,-160.683,55.54,-160.625,55.552,-160.554,55.535,-160.499,55.537,-160.463,55.558,-160.407,55.614,-160.373,55.635,-160.244,55.661,-160.046,55.763,-159.962,55.795,-159.914,55.792,-159.874,55.8,-159.81,55.833,-159.771,55.841,-159.743,55.844,-159.679,55.825,-159.665,55.795,-159.67,55.645,-159.66,55.626,-159.61,55.653,-159.568,55.695,-159.541,55.748,-159.523,55.81,-159.429,55.843,-158.79,55.987,-158.705,56.043,-158.627,56.155,-158.591,56.185,-158.554,56.183,-158.543,56.167,-158.523,56.072,-158.505,56.062,-158.476,56.075,-158.432,56.111,-158.276,56.196,-158.291,56.204,-158.317,56.254,-158.344,56.28,-158.386,56.302,-158.467,56.318,-158.536,56.308,-158.552,56.313,-158.537,56.335,-158.414,56.436,-158.352,56.454,-158.189,56.478,-158.124,56.501,-158.071,56.51,-157.982,56.51,-157.93,56.52,-157.929,56.532,-157.978,56.543,-158.078,56.552,-158.028,56.592,-157.869,56.645,-157.771,56.652,-157.674,56.633,-157.61,56.628,-157.578,56.634,-157.529,56.673,-157.49,56.76,-157.441,56.79,-157.39,56.81,-157.334,56.816,-157.271,56.808,-157.206,56.812,-157.139,56.827,-157.067,56.86,-156.988,56.913,-156.923,56.942,-156.872,56.948,-156.824,56.969,-156.78,57.006,-156.713,57.016,-156.629,57.01,-156.592,57.065,-156.501,57.09,-156.475,57.105,-156.4,57.205,-156.398,57.241,-156.444,57.294,-156.474,57.311,-156.478,57.328,-156.436,57.36,-156.242,57.449,-156.156,57.463,-156.09,57.445,-156.055,57.448,-156.037,57.527,-156,57.545,-155.814,57.559,-155.778,57.568,-155.729,57.627,-155.629,57.673,-155.596,57.701,-155.59,57.734,-155.53,57.759,-155.414,57.777,-155.313,57.807,-155.147,57.882,-155.099,57.913,-155.007,58.016,-154.585,58.056,-154.582,58.11,-154.571,58.118,-154.409,58.147,-154.282,58.147,-154.247,58.159,-154.235,58.235,-154.208,58.289,-154.282,58.293,-154.289,58.304,-154.086,58.366,-154.056,58.397,-154.062,58.442,-154.02,58.493,-153.862,58.588,-153.821,58.604,-153.699,58.626,-153.617,58.655,-153.438,58.755,-153.363,58.822,-153.334,58.858,-153.327,58.884,-153.339,58.909,-153.418,58.96,-153.656,59.039,-153.788,59.068,-153.9,59.078,-154.13,59.12,-154.178,59.156,-154.139,59.24,-154.067,59.336,-154.088,59.363,-153.814,59.474,-153.753,59.51,-153.714,59.545,-153.622,59.598,-153.609,59.615,-153.671,59.635,-153.653,59.647,-153.483,59.721,-153.414,59.74,-153.366,59.73,-153.36,59.717,-153.383,59.667,-153.364,59.66,-153.236,59.671,-153.094,59.709,-153.048,59.73,-153.025,59.794,-153.04,59.81,-153.211,59.843,-153.186,59.857,-153.106,59.875,-152.857,59.898,-152.759,59.921,-152.66,59.997,-152.629,60.041,-152.63,60.084,-152.665,60.125,-152.752,60.177,-152.893,60.24,-153.031,60.289,-153.025,60.296,-152.923,60.293,-152.798,60.247,-152.727,60.237,-152.654,60.238,-152.541,60.265,-152.369,60.336,-152.292,60.381,-152.26,60.409,-152.305,60.453,-152.307,60.472,-152.271,60.528,-151.996,60.682,-151.866,60.734,-151.785,60.74,-151.75,60.755,-151.784,60.833,-151.782,60.858,-151.734,60.911,-151.594,60.98,-151.46,61.014,-151.282,61.042,-151.15,61.086,-151.065,61.146,-150.946,61.198,-150.612,61.301,-150.567,61.307,-150.533,61.3,-150.472,61.26,-150.109,61.268,-149.976,61.279,-149.945,61.294,-149.874,61.373,-149.824,61.413,-149.695,61.471,-149.625,61.486,-149.434,61.501,-149.329,61.497,-149.596,61.417,-149.737,61.363,-149.829,61.308,-149.882,61.264,-149.895,61.232,-149.927,61.213,-150.019,61.194,-150.053,61.171,-149.968,61.122,-149.592,60.994,-149.459,60.965,-149.142,60.936,-149.071,60.886,-149.075,60.876,-149.173,60.88,-149.632,60.952,-149.856,60.962,-149.998,60.935,-150.113,60.933,-150.203,60.955,-150.281,60.985,-150.349,61.023,-150.441,61.024,-150.779,60.915,-150.954,60.841,-151.322,60.743,-151.356,60.723,-151.355,60.66,-151.318,60.554,-151.313,60.466,-151.396,60.274,-151.451,60.203,-151.612,60.092,-151.735,59.988,-151.783,59.921,-151.853,59.782,-151.817,59.721,-151.764,59.7,-151.513,59.651,-151.45,59.65,-151.404,59.662,-151.089,59.789,-151.057,59.782,-151.046,59.772,-151.189,59.638,-151.262,59.586,-151.4,59.516,-151.513,59.483,-151.693,59.462,-151.85,59.406,-151.885,59.386,-151.932,59.343,-151.964,59.285,-151.95,59.265,-151.904,59.26,-151.738,59.189,-151.619,59.187,-151.477,59.231,-151.366,59.246,-151.287,59.232,-151.222,59.229,-151.171,59.237,-151.163,59.257,-151.199,59.29,-151.183,59.301,-151.064,59.278,-150.961,59.244,-150.935,59.249,-150.899,59.303,-150.853,59.342,-150.677,59.427,-150.623,59.48,-150.621,59.535,-150.607,59.563,-150.582,59.565,-150.526,59.537,-150.485,59.535,-150.338,59.581,-150.296,59.583,-150.258,59.571,-150.198,59.567,-149.967,59.69,-149.96,59.713,-150.016,59.777,-150.005,59.784,-149.965,59.782,-149.801,59.738,-149.782,59.75,-149.804,59.833,-149.795,59.856,-149.714,59.92,-149.685,59.895,-149.63,59.785,-149.613,59.767,-149.598,59.77,-149.549,59.894,-149.46,59.966,-149.432,60.001,-149.415,60.1,-149.395,60.106,-149.305,60.014,-149.267,59.998,-149.122,60.033,-149.07,60,-149.004,59.98,-148.843,59.951,-148.751,59.948,-148.644,59.957,-148.574,59.97,-148.542,59.987,-148.506,59.989,-148.465,59.975,-148.431,59.989,-148.333,60.122,-148.291,60.145,-148.245,60.147,-148.214,60.154,-148.198,60.168,-148.219,60.268,-148.216,60.323,-148.204,60.365,-148.182,60.393,-148.129,60.414,-148.046,60.428,-147.991,60.452,-147.964,60.485,-147.984,60.523,-148.051,60.567,-148.119,60.575,-148.189,60.547,-148.296,60.532,-148.44,60.53,-148.549,60.515,-148.624,60.486,-148.64,60.489,-148.597,60.524,-148.51,60.565,-148.468,60.572,-148.338,60.57,-148.305,60.583,-148.284,60.609,-148.257,60.675,-148.268,60.7,-148.341,60.724,-148.399,60.734,-148.557,60.803,-148.556,60.827,-148.471,60.835,-148.393,60.832,-148.344,60.854,-148.293,60.94,-148.209,61.03,-148.226,61.044,-148.287,61.036,-148.396,61.007,-148.411,61.011,-148.389,61.037,-148.342,61.06,-148.27,61.082,-148.209,61.088,-148.158,61.08,-148.049,61.083,-147.986,61.106,-147.845,61.186,-147.774,61.218,-147.752,61.219,-147.971,61.019,-148.005,60.969,-147.991,60.948,-147.891,60.89,-147.808,60.885,-147.656,60.91,-147.623,60.933,-147.593,60.979,-147.567,60.995,-147.523,60.97,-147.433,60.95,-147.391,60.918,-147.361,60.915,-147.321,60.925,-147.286,60.947,-147.255,60.978,-147.195,60.997,-147.106,61.003,-147.034,60.996,-146.98,60.978,-146.874,61.005,-146.716,61.078,-146.583,61.128,-146.384,61.136,-146.285,61.113,-146.599,61.054,-146.636,60.993,-146.638,60.897,-146.604,60.871,-146.532,60.839,-146.392,60.811,-146.496,60.757,-146.546,60.745,-146.57,60.729,-146.503,60.701,-146.347,60.738,-146.251,60.749,-146.182,60.735,-146.167,60.716,-146.166,60.692,-146.149,60.661,-145.675,60.651,-145.69,60.622,-145.76,60.562,-145.811,60.525,-145.899,60.478,-145.848,60.469,-145.718,60.468,-145.563,60.441,-145.382,60.389,-145.248,60.38,-145.163,60.415,-145.096,60.454,-144.984,60.537,-144.863,60.601,-144.724,60.663,-144.691,60.669,-144.787,60.585,-144.824,60.534,-144.862,60.459,-144.901,60.335,-144.852,60.295,-144.741,60.273,-144.672,60.249,-144.643,60.225,-144.53,60.205,-144.333,60.191,-144.185,60.151,-144.089,60.084,-144.084,60.063,-144.161,60.046,-144.147,60.016,-143.979,60.009,-143.805,60.013,-143.506,60.055,-142.946,60.097,-142.549,60.086,-142.104,60.033,-141.67,59.97,-141.53,59.995,-141.447,60.019,-141.41,60.042,-141.422,60.085,-141.422,60.109,-141.409,60.118,-141.362,60.105,-141.33,60.083,-141.29,60.004,-141.295,59.98,-141.408,59.903,-141.332,59.874,-140.843,59.749,-140.648,59.723,-140.42,59.711,-140.217,59.727,-139.917,59.806,-139.85,59.831,-139.612,59.973,-139.519,60.017,-139.431,60.012,-139.402,60.001,-139.242,59.893,-138.988,59.835,-139.048,59.828,-139.179,59.84,-139.221,59.82,-139.246,59.782,-139.259,59.743,-139.266,59.663,-139.276,59.62,-139.287,59.611,-139.32,59.739,-139.315,59.848,-139.331,59.877,-139.447,59.957,-139.483,59.964,-139.512,59.954,-139.554,59.933,-139.569,59.912,-139.581,59.881,-139.582,59.848,-139.558,59.79,-139.506,59.726,-139.513,59.698,-139.612,59.61,-139.674,59.587,-139.766,59.566,-139.799,59.546,-139.773,59.527,-139.714,59.504,-139.577,59.462,-139.341,59.376,-138.884,59.237,-138.704,59.188,-138.515,59.166,-138.521,59.152,-138.56,59.129,-138.537,59.115,-138.451,59.11,-138.352,59.087,-138.241,59.047,-138.027,58.941,-137.961,58.891,-137.934,58.847,-137.864,58.786,-137.75,58.707,-137.661,58.66,-137.597,58.644,-137.565,58.626,-137.557,58.59,-137.544,58.581,-137.072,58.395,-136.865,58.332,-136.699,58.266,-136.607,58.244,-136.583,58.245,-136.462,58.328,-136.13,58.35,-136.081,58.364,-136.056,58.384,-136.061,58.453,-136.103,58.506,-136.225,58.602,-136.32,58.624,-136.484,58.618,-136.404,58.68,-136.41,58.701,-136.533,58.74,-136.549,58.752,-136.568,58.786,-136.614,58.809,-136.74,58.85,-136.879,58.882,-136.963,58.884,-137.038,58.867,-137.059,58.874,-136.988,58.925,-136.948,58.935,-136.953,58.967,-137.002,59.021,-136.989,59.034,-136.831,58.984,-136.566,58.941,-136.511,58.907,-136.478,58.863,-136.451,58.846,-136.38,58.827,-136.299,58.787,-136.226,58.765,-136.186,58.77,-136.147,58.789,-136.124,58.82,-136.118,58.863,-136.124,58.893,-136.159,58.947,-136.15,59.048,-136.134,59.04,-136.101,59,-136.049,58.893,-136.017,58.874,-135.932,58.904,-135.826,58.898,-136.043,58.822,-136.046,58.789,-135.89,58.623,-135.862,58.577,-135.896,58.464,-135.898,58.4,-135.873,58.394,-135.572,58.412,-135.45,58.376,-135.363,58.298,-135.303,58.256,-135.142,58.233,-135.09,58.246,-135.06,58.279,-135.05,58.307,-135.062,58.341,-135.152,58.512,-135.185,58.59,-135.207,58.671,-135.257,58.778,-135.334,58.91,-135.386,59.088,-135.502,59.202,-135.434,59.211,-135.4,59.208,-135.417,59.242,-135.484,59.309,-135.413,59.318,-135.403,59.353,-135.364,59.419,-135.349,59.41,-135.358,59.325,-135.33,59.239,-135.217,59.077,-135.132,58.843,-135.076,58.797,-134.986,58.766,-134.965,58.742,-134.943,58.646,-134.776,58.454,-134.664,58.385,-134.485,58.367,-134.331,58.3,-134.258,58.244,-134.209,58.233,-134.131,58.279,-134.036,58.415,-133.944,58.498,-133.911,58.515,-133.877,58.518,-133.889,58.499,-133.934,58.468,-134.045,58.289,-134.063,58.211,-134.057,58.128,-134.031,58.072,-133.894,57.993,-133.821,57.936,-133.744,57.855,-133.722,57.844,-133.657,57.841,-133.626,57.857,-133.559,57.924,-133.497,57.925,-133.194,57.878,-133.212,57.866,-133.511,57.88,-133.536,57.864,-133.535,57.833,-133.515,57.775,-133.436,57.727,-133.117,57.566,-133.143,57.555,-133.342,57.631,-133.554,57.695,-133.603,57.695,-133.627,57.677,-133.649,57.642,-133.539,57.554,-133.437,57.337,-133.466,57.172,-132.913,57.047,-132.825,57.056,-132.814,57.041,-132.839,56.96,-132.83,56.931,-132.802,56.895,-132.702,56.822,-132.64,56.796,-132.487,56.766,-132.476,56.65,-132.434,56.634,-132.358,56.626,-132.337,56.603,-132.332,56.558,-132.305,56.52,-132.256,56.489,-132.182,56.421,-132.133,56.4,-132.022,56.38,-131.962,56.324,-131.927,56.273,-131.888,56.242,-131.844,56.23,-131.551,56.207,-131.738,56.161,-131.844,56.16,-132.006,55.93,-132.091,55.84,-132.158,55.781,-132.208,55.753,-132.223,55.721,-132.155,55.6,-132.119,55.57,-131.983,55.535,-131.945,55.554,-131.869,55.647,-131.834,55.735,-131.803,55.766,-131.799,55.783,-131.826,55.835,-131.815,55.854,-131.784,55.877,-131.635,55.932,-131.288,56.012,-131.033,56.088,-131.074,56.044,-131.14,55.998,-131.128,55.96,-130.977,55.812,-130.919,55.736,-130.88,55.612,-130.873,55.551,-130.88,55.46,-130.856,55.355,-130.835,55.332,-130.748,55.318,-130.75,55.297,-130.984,55.244,-131.046,55.18,-131.048,55.158,-130.98,55.061,-130.935,54.95,-130.85,54.808,-130.616,54.791,-130.575,54.77,-130.535,54.749,-130.349,54.815,-130.204,54.947,-130.214,55.026,-130.219,55.06,-130.172,55.137,-130.037,55.298,-130.039,55.344,-130.059,55.412,-130.12,55.524,-130.14,55.585,-130.147,55.654,-130.137,55.719,-130.112,55.78,-130.075,55.836,-130.025,55.888,-130.02,55.881,-130.06,55.814,-130.085,55.752,-130.095,55.695,-130.093,55.632,-130.08,55.563,-130.044,55.472,-129.985,55.359,-129.996,55.264,-130.058,55.195,-130.092,55.108,-130.048,55.057,-129.985,55.111,-129.877,55.251,-129.838,55.319,-129.816,55.418,-129.812,55.533,-129.795,55.56,-129.765,55.498,-129.734,55.458,-129.701,55.439,-129.667,55.437,-129.63,55.452,-129.561,55.463,-129.781,55.28,-129.89,55.165,-129.949,55.081,-130.109,54.887,-130.141,54.823,-130.219,54.73,-130.307,54.7,-130.35,54.655,-130.37,54.62,-130.389,54.539,-130.393,54.48,-130.43,54.421,-130.397,54.352,-130.29,54.27,-130.084,54.181,-129.898,54.226,-129.795,54.236,-129.626,54.23,-129.791,54.166,-130.043,54.134,-130.064,54.106,-130.086,53.976,-130.233,53.867,-130.335,53.724,-130.263,53.654,-130.074,53.576,-129.912,53.551,-129.822,53.413,-129.687,53.334,-129.564,53.251,-129.462,53.347,-129.284,53.393,-129.258,53.418,-129.24,53.479,-129.232,53.576,-129.208,53.642,-129.056,53.778,-129.014,53.797,-128.959,53.841,-128.944,53.84,-128.928,53.823,-128.89,53.83,-128.705,53.919,-128.651,53.919,-128.532,53.858,-128.56,53.845,-128.653,53.832,-128.715,53.81,-128.746,53.78,-128.764,53.747,-128.768,53.71,-128.751,53.661,-128.676,53.555,-128.6,53.506,-128.512,53.477,-128.47,53.471,-128.369,53.49,-128.207,53.483,-128.115,53.446,-127.95,53.33,-127.928,53.275,-128.079,53.369,-128.133,53.418,-128.291,53.458,-128.358,53.46,-128.479,53.41,-128.542,53.421,-128.833,53.549,-128.906,53.559,-128.85,53.665,-128.855,53.705,-128.936,53.715,-129.021,53.692,-129.114,53.641,-129.172,53.534,-129.13,53.442,-129.081,53.367,-128.869,53.328,-128.652,53.244,-128.525,53.141,-128.452,52.877,-128.365,52.826,-128.106,52.907,-128.053,52.911,-128.109,52.858,-128.132,52.806,-128.197,52.623,-128.275,52.435,-128.272,52.363,-128.241,52.368,-128.184,52.408,-128.038,52.531,-127.943,52.551,-127.94,52.545,-128.021,52.491,-128.052,52.453,-128.06,52.428,-128.029,52.342,-128.037,52.318,-128.358,52.159,-128.194,51.998,-128.102,51.788,-127.995,51.951,-127.902,52.151,-127.834,52.251,-127.792,52.289,-127.713,52.319,-127.56,52.343,-127.519,52.359,-127.107,52.633,-127.066,52.653,-127.035,52.682,-127.013,52.72,-127.006,52.755,-127.019,52.842,-127.008,52.843,-126.966,52.785,-126.951,52.751,-126.951,52.721,-126.995,52.658,-127.187,52.538,-127.208,52.498,-127.194,52.458,-127.161,52.395,-127.127,52.371,-126.938,52.309,-126.901,52.265,-126.895,52.225,-126.753,52.112,-126.714,52.061,-126.739,52.065,-126.826,52.125,-126.9,52.188,-126.959,52.255,-127.008,52.291,-127.176,52.315,-127.242,52.395,-127.438,52.356,-127.55,52.298,-127.673,52.253,-127.795,52.191,-127.843,52.086,-127.859,51.99,-127.728,51.993,-127.83,51.879,-127.863,51.821,-127.869,51.775,-127.851,51.673,-127.819,51.604,-127.748,51.544,-127.729,51.506,-127.714,51.49,-127.669,51.478,-127.645,51.478,-127.61,51.514,-127.576,51.563,-127.443,51.679,-127.339,51.707,-127.034,51.717,-126.691,51.703,-126.735,51.693,-126.968,51.67,-127.281,51.654,-127.347,51.642,-127.42,51.608,-127.633,51.427,-127.689,51.343,-127.714,51.269,-127.708,51.151,-127.591,51.088,-127.441,50.989,-127.357,50.946,-127.267,50.916,-127.058,50.868,-127.014,50.867,-126.96,50.894,-126.632,50.915,-126.563,50.965,-126.517,51.057,-126.485,50.96,-126.522,50.866,-126.488,50.842,-126.418,50.85,-126.375,50.837,-126.397,50.807,-126.472,50.767,-126.517,50.724,-126.514,50.679,-126.493,50.672,-126.37,50.667,-125.981,50.711,-125.904,50.705,-125.898,50.684,-126.067,50.664,-126.239,50.624,-126.416,50.607,-126.447,50.588,-126.45,50.55,-126.404,50.53,-126.237,50.523,-126.094,50.498,-126.024,50.497,-125.965,50.487,-125.84,50.511,-125.772,50.508,-125.741,50.479,-125.698,50.465,-125.641,50.466,-125.61,50.486,-125.586,50.574,-125.556,50.635,-125.539,50.649,-125.526,50.534,-125.507,50.507,-125.476,50.497,-125.21,50.476,-125.059,50.514,-124.985,50.592,-124.943,50.666,-124.931,50.718,-124.949,50.765,-124.934,50.811,-124.86,50.872,-124.875,50.826,-124.858,50.717,-124.854,50.669,-124.863,50.637,-124.937,50.537,-125.057,50.419,-125.044,50.364,-124.986,50.356,-124.933,50.298,-124.934,50.258,-124.784,50.073,-124.782,50.02,-124.702,49.958,-124.483,49.808,-124.413,49.778,-124.281,49.772,-124.142,49.793,-124.059,49.854,-123.985,49.876,-123.972,49.892,-123.971,49.97,-123.957,49.993,-123.866,50.072,-123.863,50.103,-123.946,50.184,-123.934,50.188,-123.88,50.174,-123.825,50.144,-123.788,50.107,-123.785,50.088,-123.824,50.044,-123.885,50.017,-123.904,49.981,-123.904,49.795,-123.874,49.737,-123.818,49.685,-123.763,49.658,-123.708,49.657,-123.582,49.681,-123.613,49.658,-123.739,49.594,-123.817,49.587,-123.847,49.637,-123.923,49.718,-123.96,49.736,-123.993,49.736,-124.024,49.711,-124.054,49.662,-124.029,49.603,-123.948,49.535,-123.892,49.495,-123.859,49.483,-123.531,49.397,-123.508,49.402,-123.437,49.451,-123.399,49.442,-123.336,49.459,-123.322,49.517,-123.337,49.545,-123.325,49.578,-123.187,49.68,-123.18,49.674,-123.191,49.644,-123.223,49.59,-123.248,49.443,-123.264,49.39,-123.286,49.375,-123.291,49.359,-123.277,49.344,-123.174,49.348,-123.016,49.322,-122.964,49.329,-122.879,49.399,-122.913,49.323,-122.948,49.293,-123.067,49.292,-123.184,49.278,-123.229,49.26,-123.191,49.22,-123.196,49.148,-123.182,49.129,-123.15,49.121,-123.08,49.131,-123.077,49.118,-123.109,49.085,-123.118,49.056,-123.086,48.993,-123.077,48.98,-123.063,48.978,-123.049,48.993,-123.027,49.039,-123.002,49.061,-122.963,49.075,-122.924,49.075,-122.827,49.028,-122.789,48.993,-122.722,48.853,-122.686,48.794,-122.653,48.764,-122.599,48.767,-122.58,48.78,-122.562,48.778,-122.545,48.762,-122.513,48.669,-122.515,48.555,-122.501,48.538,-122.497,48.506,-122.543,48.488,-122.628,48.498,-122.657,48.49,-122.669,48.465,-122.662,48.446,-122.638,48.433,-122.583,48.429,-122.542,48.411,-122.488,48.374,-122.409,48.294,-122.403,48.269,-122.467,48.258,-122.52,48.229,-122.529,48.199,-122.517,48.16,-122.494,48.13,-122.395,48.084,-122.387,48.09,-122.425,48.176,-122.416,48.184,-122.389,48.166,-122.353,48.114,-122.317,48.08,-122.261,48.042,-122.242,48.011,-122.318,47.933,-122.33,47.899,-122.393,47.821,-122.402,47.784,-122.382,47.752,-122.384,47.716,-122.407,47.677,-122.41,47.653,-122.381,47.628,-122.368,47.604,-122.375,47.528,-122.351,47.395,-122.354,47.372,-122.42,47.312,-122.465,47.296,-122.511,47.295,-122.542,47.276,-122.604,47.167,-122.627,47.144,-122.702,47.111,-122.73,47.112,-122.812,47.146,-122.914,47.131,-123.028,47.139,-122.988,47.173,-122.956,47.245,-122.92,47.29,-122.828,47.337,-122.813,47.329,-122.783,47.226,-122.768,47.218,-122.721,47.305,-122.708,47.316,-122.649,47.281,-122.604,47.275,-122.578,47.293,-122.554,47.405,-122.557,47.463,-122.586,47.528,-122.675,47.612,-122.664,47.617,-122.628,47.608,-122.614,47.616,-122.63,47.693,-122.618,47.713,-122.524,47.769,-122.511,47.816,-122.533,47.92,-122.586,47.928,-122.593,47.916,-122.588,47.856,-122.608,47.835,-122.718,47.762,-122.757,47.701,-122.814,47.659,-122.913,47.607,-122.982,47.559,-123.049,47.479,-123.06,47.454,-123.067,47.4,-123.018,47.401,-122.917,47.418,-122.922,47.408,-123.031,47.36,-123.104,47.348,-123.136,47.356,-123.139,47.386,-123.131,47.438,-123.051,47.552,-122.821,47.793,-122.805,47.784,-122.802,47.735,-122.778,47.738,-122.657,47.881,-122.679,47.932,-122.74,48.013,-122.769,48.076,-122.768,48.12,-122.779,48.138,-122.861,48.09,-122.909,48.077,-122.974,48.073,-123.024,48.082,-123.124,48.151,-123.162,48.155,-123.25,48.124,-123.294,48.12,-123.976,48.168,-124.099,48.2,-124.175,48.242,-124.429,48.301,-124.633,48.375,-124.71,48.38,-124.68,48.286,-124.702,48.152,-124.663,47.974,-124.621,47.904,-124.46,47.784,-124.376,47.659,-124.309,47.405,-124.199,47.209,-124.171,47.087,-124.164,47.015,-124.139,46.955,-124.117,47,-124.112,47.035,-124.042,47.03,-123.986,46.984,-123.843,46.963,-124.113,46.863,-124.072,46.745,-123.958,46.709,-123.889,46.66,-123.912,46.533,-123.946,46.433,-124.016,46.521,-124.044,46.605,-124.05,46.491,-124.045,46.373,-124.073,46.279,-123.96,46.301,-123.896,46.268,-123.688,46.3,-123.65,46.268,-123.465,46.271,-123.405,46.221,-123.299,46.171,-123.251,46.167,-123.221,46.154,-123.322,46.144,-123.402,46.155,-123.466,46.209,-123.522,46.223,-123.674,46.183,-123.912,46.182,-123.963,46.225,-123.989,46.219,-123.975,46.178,-123.947,46.141,-123.961,45.843,-123.929,45.577,-123.963,45.476,-123.949,45.401,-124.059,44.778,-124.045,44.648,-124.065,44.52,-124.047,44.425,-124.099,44.334,-124.131,44.056,-124.149,43.692,-124.184,43.652,-124.239,43.54,-124.288,43.41,-124.233,43.436,-124.197,43.423,-124.275,43.367,-124.321,43.368,-124.347,43.342,-124.454,43.012,-124.499,42.937,-124.54,42.813,-124.444,42.67,-124.406,42.584,-124.421,42.381,-124.41,42.304,-124.355,42.123,-124.228,42.001,-124.212,41.985,-124.209,41.889,-124.245,41.788,-124.163,41.719,-124.118,41.622,-124.072,41.46,-124.069,41.384,-124.14,41.156,-124.133,40.97,-124.2,40.822,-124.219,40.791,-124.223,40.775,-124.19,40.772,-124.208,40.746,-124.22,40.696,-124.251,40.704,-124.242,40.728,-124.254,40.74,-124.284,40.711,-124.325,40.598,-124.372,40.491,-124.357,40.371,-124.324,40.252,-124.108,40.095,-123.884,39.861,-123.833,39.775,-123.783,39.619,-123.778,39.515,-123.82,39.368,-123.72,39.111,-123.701,38.907,-123.425,38.676,-123.29,38.536,-123.121,38.449,-123.046,38.305,-122.987,38.277,-122.908,38.197,-122.877,38.123,-122.978,38.227,-122.968,38.097,-123.001,38.019,-122.999,37.989,-122.932,38.055,-122.873,38.026,-122.76,37.946,-122.681,37.902,-122.584,37.874,-122.521,37.826,-122.467,37.838,-122.495,37.954,-122.484,38.109,-122.393,38.145,-122.337,38.136,-122.208,38.073,-122.154,38.066,-122.031,38.124,-121.993,38.12,-121.934,38.087,-121.881,38.075,-121.749,38.08,-121.682,38.075,-121.626,38.084,-121.525,38.056,-121.573,38.052,-121.638,38.061,-121.717,38.034,-122.087,38.05,-122.217,38.041,-122.314,38.007,-122.385,37.961,-122.365,37.921,-122.333,37.897,-122.296,37.79,-122.222,37.732,-122.158,37.626,-122.124,37.544,-122.097,37.518,-122.071,37.478,-122.119,37.483,-122.166,37.502,-122.229,37.564,-122.298,37.592,-122.37,37.656,-122.39,37.741,-122.384,37.789,-122.446,37.798,-122.514,37.772,-122.5,37.653,-122.499,37.543,-122.408,37.373,-122.395,37.208,-122.164,36.991,-121.881,36.939,-121.807,36.851,-121.795,36.801,-121.79,36.732,-121.835,36.657,-121.919,36.572,-121.91,36.433,-121.877,36.331,-121.664,36.154,-121.465,35.927,-121.434,35.864,-121.344,35.792,-121.284,35.676,-121.138,35.607,-121.023,35.481,-120.9,35.425,-120.86,35.365,-120.885,35.275,-120.857,35.21,-120.707,35.158,-120.659,35.122,-120.634,35.076,-120.663,34.949,-120.625,34.812,-120.638,34.749,-120.627,34.669,-120.645,34.58,-120.56,34.544,-120.481,34.472,-120.396,34.46,-120.17,34.476,-120.053,34.469,-119.853,34.412,-119.713,34.4,-119.606,34.418,-119.414,34.339,-119.268,34.257,-119.236,34.164,-119.144,34.112,-118.832,34.024,-118.599,34.035,-118.506,34.017,-118.393,33.858,-118.41,33.744,-118.294,33.712,-118.264,33.759,-118.162,33.751,-118.081,33.722,-117.952,33.62,-117.789,33.538,-117.467,33.296,-117.319,33.1,-117.263,32.939,-117.256,32.873,-117.271,32.806,-117.243,32.664,-117.184,32.688,-117.137,32.649,-117.13,32.54,-117.128,32.533,-117.063,32.344,-117.035,32.305,-116.914,32.199,-116.848,31.997,-116.621,31.851,-116.624,31.758,-116.652,31.74,-116.702,31.744,-116.722,31.735,-116.668,31.699,-116.662,31.565,-116.61,31.499,-116.458,31.361,-116.333,31.203,-116.31,31.127,-116.31,31.051,-116.296,30.971,-116.062,30.804,-116.035,30.705,-116.029,30.564,-115.996,30.414,-115.858,30.36,-115.816,30.304,-115.79,30.084,-115.808,29.96,-115.749,29.936,-115.674,29.756,-115.565,29.68,-115.311,29.532,-115.166,29.427,-114.994,29.384,-114.937,29.352,-114.876,29.282,-114.664,29.095,-114.309,28.73,-114.146,28.605,-114.048,28.426,-114.093,28.221,-114.185,28.013,-114.266,27.934,-114.253,27.908,-114.158,27.92,-114.157,27.868,-114.175,27.831,-114.135,27.727,-114.069,27.676,-114.137,27.671,-114.233,27.718,-114.302,27.776,-114.289,27.839,-114.301,27.873,-114.373,27.841,-114.448,27.797,-114.57,27.784,-114.824,27.83,-115.036,27.842,-115.033,27.799,-114.994,27.736,-114.859,27.659,-114.716,27.54,-114.54,27.431,-114.498,27.376,-114.48,27.284,-114.445,27.218,-114.333,27.158,-114.202,27.144,-114.11,27.106,-113.996,26.988,-113.936,26.985,-113.841,26.967,-113.757,26.871,-113.701,26.791,-113.599,26.721,-113.426,26.796,-113.272,26.791,-113.206,26.857,-113.156,26.946,-113.143,26.792,-113.119,26.717,-113.021,26.583,-112.658,26.317,-112.526,26.273,-112.377,26.214,-112.174,25.913,-112.12,25.766,-112.115,25.63,-112.093,25.584,-112.07,25.573,-112.056,25.488,-112.078,25.324,-112.129,25.043,-112.119,24.934,-112.073,24.84,-111.848,24.67,-111.825,24.632,-111.822,24.573,-111.802,24.543,-111.75,24.554,-111.683,24.556,-111.578,24.443,-111.419,24.329,-111.036,24.105,-110.896,23.97,-110.765,23.877,-110.63,23.737,-110.363,23.605,-110.289,23.518,-110.244,23.412,-110.181,23.342,-110.086,23.005,-110.006,22.894,-109.923,22.886,-109.823,22.922,-109.728,22.982,-109.63,23.079,-109.496,23.16,-109.458,23.215,-109.415,23.406,-109.421,23.48,-109.51,23.598,-109.677,23.662,-109.711,23.804,-109.776,23.865,-109.811,23.939,-109.893,24.033,-109.983,24.109,-110.023,24.175,-110.263,24.345,-110.304,24.339,-110.325,24.306,-110.321,24.259,-110.297,24.195,-110.32,24.139,-110.367,24.1,-110.41,24.131,-110.4,24.165,-110.421,24.183,-110.547,24.214,-110.659,24.341,-110.735,24.59,-110.729,24.672,-110.677,24.789,-110.687,24.868,-110.756,24.995,-110.894,25.144,-111.014,25.42,-111.034,25.527,-111.15,25.573,-111.292,25.79,-111.33,25.931,-111.332,26.125,-111.405,26.265,-111.419,26.35,-111.465,26.408,-111.47,26.507,-111.546,26.579,-111.57,26.708,-111.795,26.88,-111.822,26.865,-111.817,26.756,-111.779,26.687,-111.699,26.581,-111.723,26.564,-111.754,26.573,-111.863,26.679,-111.883,26.84,-112.009,26.967,-112.016,27.01,-112.004,27.079,-112.098,27.146,-112.191,27.187,-112.283,27.347,-112.329,27.523,-112.553,27.657,-112.734,27.826,-112.758,27.901,-112.749,27.995,-112.808,28.092,-112.796,28.207,-112.868,28.292,-112.865,28.351,-112.871,28.424,-112.957,28.456,-113.034,28.473,-113.094,28.512,-113.206,28.799,-113.259,28.819,-113.321,28.813,-113.335,28.839,-113.329,28.873,-113.382,28.947,-113.5,28.927,-113.538,29.023,-113.545,29.102,-113.755,29.367,-113.829,29.439,-114.062,29.61,-114.179,29.734,-114.373,29.83,-114.403,29.896,-114.55,30.022,-114.63,30.156,-114.65,30.238,-114.633,30.507,-114.685,30.621,-114.703,30.765,-114.761,30.959,-114.845,31.08,-114.882,31.156,-114.848,31.538,-114.79,31.647,-114.84,31.799,-114.895,31.851,-114.934,31.901,-114.741,31.806,-114.698,31.777,-114.609,31.762,-114.549,31.734,-114.264,31.554,-114.149,31.507,-114.081,31.51,-114.003,31.525,-113.977,31.593,-113.948,31.629,-113.759,31.558,-113.7,31.523,-113.633,31.468,-113.623,31.346,-113.481,31.294,-113.231,31.256,-113.186,31.236,-113.084,31.207,-113.047,31.179,-113.043,31.087,-113.073,31.061,-113.108,31.077,-113.119,31.048,-113.105,31.027,-113.087,30.938,-113.11,30.793,-113.058,30.651,-112.952,30.51,-112.825,30.3,-112.759,30.126,-112.738,29.985,-112.697,29.917,-112.653,29.87,-112.573,29.72,-112.415,29.536,-112.389,29.46,-112.393,29.42,-112.378,29.348,-112.301,29.323,-112.223,29.269,-112.192,29.118,-112.162,29.019,-112.045,28.896,-111.941,28.823,-111.919,28.798,-111.907,28.752,-111.832,28.648,-111.747,28.564,-111.68,28.471,-111.472,28.384,-111.282,28.115,-111.121,27.967,-110.986,27.926,-110.921,27.889,-110.849,27.918,-110.759,27.915,-110.53,27.864,-110.578,27.796,-110.615,27.654,-110.593,27.544,-110.561,27.45,-110.519,27.396,-110.478,27.323,-110.377,27.233,-110.277,27.162,-109.944,27.079,-109.926,27.029,-109.922,26.978,-109.891,26.883,-109.828,26.77,-109.755,26.703,-109.676,26.697,-109.483,26.71,-109.276,26.534,-109.243,26.45,-109.241,26.405,-109.216,26.355,-109.146,26.306,-109.117,26.253,-109.159,26.258,-109.2,26.305,-109.271,26.243,-109.354,26.138,-109.426,26.033,-109.385,25.727,-109.304,25.633,-109.254,25.609,-109.196,25.593,-109.084,25.615,-109.008,25.642,-108.935,25.69,-108.887,25.733,-108.885,25.696,-108.973,25.588,-109.068,25.552,-109.063,25.517,-109.029,25.48,-108.893,25.512,-108.844,25.543,-108.787,25.538,-108.751,25.424,-108.696,25.383,-108.466,25.265,-108.374,25.194,-108.093,25.094,-108.051,25.067,-108.036,25.035,-108.08,25.018,-108.14,25.018,-108.192,25.031,-108.243,25.074,-108.281,25.082,-108.208,24.975,-108.015,24.783,-108.009,24.694,-107.951,24.615,-107.817,24.539,-107.71,24.525,-107.674,24.504,-107.602,24.49,-107.549,24.505,-107.512,24.489,-107.489,24.424,-107.494,24.369,-107.527,24.36,-107.727,24.472,-107.765,24.472,-107.085,24.016,-106.935,23.881,-106.729,23.611,-106.567,23.449,-106.402,23.196,-106.235,23.061,-106.022,22.829,-105.943,22.777,-105.792,22.627,-105.646,22.327,-105.649,21.988,-105.527,21.818,-105.457,21.672,-105.431,21.618,-105.209,21.491,-105.233,21.38,-105.225,21.25,-105.237,21.119,-105.302,21.027,-105.394,20.926,-105.456,20.844,-105.511,20.809,-105.492,20.777,-105.42,20.775,-105.327,20.753,-105.252,20.669,-105.245,20.634,-105.26,20.579,-105.377,20.512,-105.543,20.498,-105.642,20.436,-105.669,20.386,-105.616,20.316,-105.57,20.228,-105.532,20.075,-105.482,19.976,-105.286,19.706,-105.108,19.562,-105.045,19.443,-104.938,19.309,-104.603,19.153,-104.405,19.091,-104.277,19.011,-104.046,18.912,-103.912,18.828,-103.699,18.633,-103.58,18.484,-103.442,18.325,-103.019,18.187,-102.7,18.063,-102.547,18.041,-102.217,17.957,-101.996,17.973,-101.919,17.96,-101.847,17.922,-101.762,17.842,-101.6,17.652,-101.487,17.615,-101.385,17.514,-101.148,17.393,-101.002,17.276,-100.848,17.2,-100.432,17.064,-100.243,16.984,-100.025,16.921,-99.691,16.72,-99.348,16.665,-99.002,16.581,-98.908,16.545,-98.762,16.535,-98.52,16.305,-98.139,16.206,-97.755,15.967,-97.185,15.909,-96.808,15.726,-96.511,15.652,-96.409,15.683,-96.214,15.693,-95.772,15.888,-95.464,15.975,-95.134,16.177,-94.949,16.21,-94.799,16.21,-94.786,16.229,-94.846,16.247,-95.021,16.278,-95.024,16.306,-94.935,16.379,-94.9,16.417,-94.859,16.42,-94.793,16.365,-94.797,16.327,-94.791,16.287,-94.753,16.291,-94.651,16.352,-94.617,16.348,-94.587,16.316,-94.682,16.228,-94.662,16.202,-94.471,16.187,-94.001,16.019,-94.028,16.062,-94.193,16.146,-94.25,16.168,-94.303,16.169,-94.37,16.195,-94.426,16.226,-94.409,16.287,-94.374,16.285,-94.311,16.239,-94.24,16.205,-94.079,16.145,-93.916,16.054,-93.734,15.888,-93.541,15.75,-93.167,15.448,-93.024,15.31,-92.918,15.236,-92.809,15.139,-92.531,14.84,-92.265,14.568,-92.235,14.545,-91.819,14.228,-91.641,14.115,-91.377,13.99,-91.146,13.926,-90.607,13.929,-90.479,13.901,-90.095,13.737,-89.97,13.683,-89.804,13.56,-89.523,13.509,-89.278,13.478,-88.867,13.283,-88.512,13.184,-88.484,13.197,-88.582,13.245,-88.656,13.259,-88.686,13.281,-88.592,13.281,-88.417,13.214,-88.181,13.164,-88.023,13.169,-87.931,13.181,-87.878,13.224,-87.821,13.285,-87.838,13.386,-87.814,13.399,-87.769,13.377,-87.708,13.36,-87.602,13.386,-87.489,13.353,-87.485,13.311,-87.498,13.275,-87.458,13.215,-87.413,13.127,-87.333,13.085,-87.337,12.979,-87.339,12.95,-87.39,12.921,-87.424,12.921,-87.498,12.984,-87.543,13.04,-87.585,13.043,-87.67,12.966,-87.668,12.904,-87.46,12.758,-87.188,12.508,-87.125,12.434,-86.851,12.248,-86.756,12.157,-86.656,11.982,-86.469,11.738,-85.961,11.331,-85.829,11.199,-85.745,11.089,-85.744,11.062,-85.744,11.043,-85.752,10.985,-85.887,10.921,-85.908,10.898,-85.833,10.85,-85.715,10.791,-85.667,10.745,-85.671,10.68,-85.663,10.635,-85.703,10.563,-85.831,10.398,-85.85,10.292,-85.796,10.133,-85.681,9.959,-85.625,9.902,-85.315,9.811,-85.154,9.62,-85.115,9.582,-85.077,9.602,-85.06,9.668,-85.001,9.699,-84.886,9.821,-84.908,9.885,-84.963,9.933,-85.161,10.017,-85.237,10.107,-85.263,10.257,-85.236,10.242,-85.198,10.195,-85.025,10.116,-84.715,9.899,-84.643,9.789,-84.67,9.703,-84.659,9.647,-84.582,9.568,-84.483,9.526,-84.222,9.463,-84.118,9.379,-83.896,9.276,-83.737,9.15,-83.637,9.035,-83.616,8.96,-83.614,8.804,-83.642,8.729,-83.734,8.614,-83.605,8.48,-83.544,8.446,-83.452,8.438,-83.377,8.415,-83.292,8.406,-83.29,8.464,-83.298,8.507,-83.422,8.619,-83.47,8.707,-83.391,8.718,-83.286,8.664,-83.162,8.588,-83.13,8.505,-83.123,8.353,-83.041,8.288,-82.947,8.182,-82.879,8.071,-82.854,8.1,-82.866,8.246,-82.781,8.304,-82.68,8.322,-82.531,8.287,-82.365,8.275,-82.235,8.311,-82.224,8.23,-82.16,8.195,-82.097,8.223,-81.973,8.215,-81.86,8.165,-81.728,8.138,-81.694,8.071,-81.676,8.016,-81.504,7.721,-81.37,7.675,-81.268,7.625,-81.219,7.621,-81.195,7.668,-81.179,7.808,-81.158,7.854,-81.094,7.876,-81.064,7.9,-81.035,7.711,-80.915,7.438,-80.901,7.277,-80.846,7.22,-80.667,7.226,-80.439,7.275,-80.373,7.325,-80.348,7.386,-80.287,7.426,-80.111,7.433,-80.067,7.453,-80.011,7.5,-80.04,7.6,-80.075,7.667,-80.261,7.852,-80.366,7.998,-80.409,8.029,-80.458,8.077,-80.466,8.14,-80.459,8.214,-80.408,8.262,-80.369,8.289,-80.2,8.314,-80.126,8.35,-79.75,8.596,-79.816,8.639,-79.759,8.712,-79.731,8.775,-79.687,8.851,-79.572,8.903,-79.552,8.924,-79.507,8.97,-79.442,9.006,-79.247,9.02,-79.086,8.997,-78.955,8.933,-78.848,8.842,-78.77,8.811,-78.71,8.753,-78.67,8.742,-78.621,8.714,-78.514,8.628,-78.469,8.447,-78.436,8.403,-78.41,8.355,-78.379,8.359,-78.369,8.405,-78.388,8.443,-78.399,8.506,-78.374,8.489,-78.35,8.46,-78.256,8.454,-78.251,8.421,-78.223,8.397,-78.191,8.417,-78.162,8.454,-78.099,8.497,-78.057,8.397,-78.012,8.325,-77.853,8.216,-77.761,8.133,-77.834,8.151,-77.952,8.23,-78.048,8.285,-78.114,8.38,-78.142,8.386,-78.18,8.33,-78.281,8.248,-78.255,8.139,-78.287,8.092,-78.315,8.067,-78.368,8.071,-78.422,8.061,-78.378,7.9,-78.17,7.544,-77.93,7.256,-77.901,7.229,-77.804,7.137,-77.681,6.96,-77.646,6.87,-77.602,6.837,-77.526,6.693,-77.439,6.69,-77.369,6.576,-77.36,6.504,-77.398,6.275,-77.44,6.272,-77.473,6.286,-77.469,6.177,-77.345,5.995,-77.249,5.78,-77.325,5.676,-77.534,5.537,-77.402,5.416,-77.373,5.324,-77.359,5.215,-77.367,5.077,-77.339,4.839,-77.307,4.785,-77.286,4.722,-77.314,4.594,-77.328,4.475,-77.354,4.398,-77.414,4.348,-77.446,4.301,-77.516,4.256,-77.521,4.213,-77.409,4.248,-77.404,4.201,-77.434,4.131,-77.427,4.06,-77.358,3.945,-77.278,4.058,-77.248,4.041,-77.264,3.893,-77.212,3.867,-77.167,3.862,-77.127,3.906,-77.077,3.913,-77.243,3.585,-77.324,3.475,-77.357,3.349,-77.417,3.342,-77.472,3.234,-77.52,3.16,-77.559,3.076,-77.632,3.051,-77.694,3.04,-77.701,3.008,-77.671,2.919,-77.67,2.879,-77.777,2.787,-77.808,2.746,-77.814,2.716,-77.875,2.726,-77.901,2.699,-77.932,2.629,-77.987,2.569,-78.03,2.543,-78.067,2.509,-78.12,2.488,-78.296,2.51,-78.343,2.461,-78.417,2.483,-78.46,2.47,-78.535,2.424,-78.592,2.357,-78.617,2.307,-78.629,2.056,-78.55,1.924,-78.577,1.774,-78.793,1.849,-78.958,1.752,-79.025,1.624,-78.888,1.524,-78.86,1.455,-78.827,1.296,-78.9,1.206,-79.229,1.105,-79.465,1.06,-79.613,0.971,-79.741,0.98,-79.796,0.922,-79.904,0.86,-80.036,0.835,-80.088,0.785,-80.061,0.592,-80.025,0.41,-80.046,0.155,-80.133,-0.006,-80.237,-0.113,-80.321,-0.166,-80.482,-0.368,-80.468,-0.436,-80.385,-0.584,-80.282,-0.621,-80.358,-0.625,-80.455,-0.585,-80.505,-0.684,-80.554,-0.848,-80.624,-0.899,-80.841,-0.975,-80.902,-1.079,-80.82,-1.286,-80.801,-1.383,-80.835,-1.632,-80.763,-1.823,-80.761,-1.935,-80.77,-2.077,-80.868,-2.141,-80.963,-2.189,-80.952,-2.235,-80.932,-2.269,-80.839,-2.349,-80.685,-2.397,-80.45,-2.626,-80.379,-2.668,-80.285,-2.707,-80.255,-2.665,-80.249,-2.631,-80.127,-2.528,-80.053,-2.391,-80.007,-2.354,-80.03,-2.557,-79.989,-2.579,-79.926,-2.549,-79.88,-2.424,-79.893,-2.146,-79.842,-2.067,-79.833,-2.111,-79.84,-2.168,-79.822,-2.357,-79.746,-2.485,-79.73,-2.579,-79.823,-2.777,-79.922,-3.09,-79.963,-3.158,-80.027,-3.228,-80.1,-3.274,-80.16,-3.324,-80.303,-3.375,-80.325,-3.388,-80.504,-3.496,-80.653,-3.638,-80.799,-3.731,-80.892,-3.882,-81.232,-4.234,-81.283,-4.322,-81.337,-4.67,-81.289,-4.761,-81.195,-4.879,-81.108,-5.028,-81.151,-5.102,-81.168,-5.167,-80.943,-5.475,-80.882,-5.635,-80.883,-5.759,-80.931,-5.841,-80.992,-5.861,-81.092,-5.812,-81.164,-5.875,-81.181,-5.942,-81.142,-6.057,-81.058,-6.129,-80.812,-6.282,-80.11,-6.65,-79.995,-6.769,-79.905,-6.902,-79.762,-7.067,-79.618,-7.296,-79.589,-7.419,-79.377,-7.836,-79.313,-7.923,-79.164,-8.047,-79.012,-8.21,-78.925,-8.405,-78.762,-8.617,-78.755,-8.74,-78.665,-8.971,-78.58,-9.157,-78.446,-9.371,-78.356,-9.652,-78.276,-9.81,-78.186,-10.089,-78.095,-10.261,-77.736,-10.837,-77.664,-11.022,-77.639,-11.194,-77.633,-11.288,-77.31,-11.532,-77.22,-11.663,-77.158,-11.923,-77.153,-12.06,-77.063,-12.107,-77.038,-12.173,-76.994,-12.219,-76.832,-12.349,-76.758,-12.527,-76.637,-12.728,-76.555,-12.823,-76.502,-12.984,-76.427,-13.11,-76.224,-13.371,-76.184,-13.515,-76.259,-13.803,-76.319,-13.821,-76.376,-13.863,-76.297,-13.948,-76.289,-14.133,-76.175,-14.227,-76.136,-14.32,-76.006,-14.496,-75.934,-14.634,-75.738,-14.785,-75.534,-14.899,-75.397,-15.094,-75.275,-15.178,-75.191,-15.32,-75.104,-15.412,-74.555,-15.699,-74.373,-15.834,-74.147,-15.912,-73.825,-16.153,-73.728,-16.202,-73.4,-16.304,-73.264,-16.389,-72.958,-16.521,-72.794,-16.615,-72.468,-16.708,-72.362,-16.775,-72.269,-16.876,-72.111,-17.003,-71.967,-17.064,-71.868,-17.151,-71.774,-17.199,-71.532,-17.294,-71.436,-17.366,-71.399,-17.422,-71.365,-17.621,-71.337,-17.683,-71.057,-17.876,-70.942,-17.932,-70.817,-18.053,-70.492,-18.278,-70.418,-18.346,-70.362,-18.398,-70.336,-18.595,-70.335,-18.828,-70.276,-19.268,-70.21,-19.487,-70.198,-19.613,-70.157,-19.706,-70.148,-19.805,-70.147,-20.23,-70.194,-20.531,-70.197,-20.725,-70.088,-21.253,-70.08,-21.357,-70.088,-21.493,-70.13,-21.641,-70.155,-21.867,-70.185,-21.975,-70.229,-22.193,-70.26,-22.556,-70.332,-22.849,-70.389,-22.97,-70.45,-23.034,-70.563,-23.057,-70.569,-23.173,-70.593,-23.255,-70.588,-23.368,-70.512,-23.483,-70.42,-23.529,-70.392,-23.566,-70.41,-23.656,-70.488,-23.782,-70.507,-23.886,-70.52,-23.969,-70.507,-24.13,-70.546,-24.332,-70.574,-24.644,-70.559,-24.779,-70.445,-25.173,-70.452,-25.252,-70.49,-25.376,-70.578,-25.487,-70.633,-25.546,-70.714,-25.784,-70.7,-25.861,-70.635,-25.993,-70.662,-26.225,-70.647,-26.329,-70.687,-26.422,-70.708,-26.597,-70.803,-26.841,-70.813,-26.951,-70.898,-27.187,-70.914,-27.308,-70.909,-27.505,-70.926,-27.589,-70.946,-27.618,-71.053,-27.727,-71.087,-27.814,-71.154,-28.064,-71.186,-28.378,-71.267,-28.508,-71.307,-28.672,-71.384,-28.779,-71.494,-28.855,-71.519,-28.926,-71.486,-29.198,-71.353,-29.35,-71.327,-29.443,-71.316,-29.65,-71.348,-29.933,-71.4,-30.143,-71.669,-30.33,-71.709,-30.628,-71.706,-30.759,-71.654,-30.987,-71.662,-31.17,-71.577,-31.496,-71.526,-31.806,-71.513,-32.208,-71.421,-32.387,-71.461,-32.538,-71.452,-32.66,-71.592,-32.97,-71.636,-33.023,-71.743,-33.095,-71.697,-33.289,-71.696,-33.429,-71.636,-33.519,-71.664,-33.653,-71.831,-33.82,-71.854,-33.89,-71.927,-34.016,-72.003,-34.165,-71.992,-34.288,-72.031,-34.421,-72.056,-34.616,-72.182,-34.92,-72.224,-35.096,-72.387,-35.24,-72.455,-35.341,-72.505,-35.447,-72.562,-35.505,-72.624,-35.586,-72.587,-35.76,-72.683,-35.877,-72.778,-35.979,-72.875,-36.39,-72.968,-36.538,-73.007,-36.643,-73.118,-36.688,-73.138,-36.8,-73.151,-36.876,-73.173,-37.054,-73.216,-37.167,-73.271,-37.207,-73.375,-37.224,-73.602,-37.188,-73.634,-37.255,-73.662,-37.341,-73.603,-37.479,-73.665,-37.59,-73.662,-37.699,-73.517,-37.911,-73.465,-38.04,-73.472,-38.13,-73.533,-38.367,-73.52,-38.509,-73.481,-38.624,-73.226,-39.224,-73.25,-39.422,-73.41,-39.789,-73.482,-39.854,-73.671,-39.963,-73.669,-40.082,-73.742,-40.263,-73.784,-40.468,-73.92,-40.872,-73.984,-40.974,-73.966,-41.118,-73.876,-41.319,-73.855,-41.446,-73.811,-41.517,-73.711,-41.574,-73.624,-41.581,-73.625,-41.612,-73.688,-41.639,-73.722,-41.692,-73.735,-41.742,-73.624,-41.774,-73.521,-41.797,-73.242,-41.781,-73.174,-41.747,-73.015,-41.544,-72.953,-41.515,-72.88,-41.518,-72.805,-41.544,-72.67,-41.659,-72.601,-41.684,-72.542,-41.691,-72.428,-41.646,-72.359,-41.514,-72.318,-41.499,-72.36,-41.649,-72.486,-41.722,-72.66,-41.742,-72.744,-41.801,-72.784,-41.847,-72.824,-41.909,-72.781,-41.96,-72.738,-41.995,-72.624,-42.011,-72.499,-41.981,-72.46,-42.207,-72.412,-42.388,-72.43,-42.434,-72.548,-42.256,-72.631,-42.2,-72.707,-42.221,-72.773,-42.258,-72.785,-42.301,-72.716,-42.41,-72.632,-42.51,-72.655,-42.517,-72.774,-42.505,-72.848,-42.669,-72.845,-42.808,-72.766,-42.908,-72.755,-42.993,-72.758,-43.039,-72.878,-43.048,-72.915,-43.134,-72.94,-43.211,-73.076,-43.324,-73.101,-43.455,-72.997,-43.632,-73.069,-43.862,-73.224,-43.898,-73.241,-44.066,-73.265,-44.169,-73.141,-44.237,-73.001,-44.292,-72.828,-44.395,-72.664,-44.436,-72.68,-44.594,-72.739,-44.734,-73.078,-44.92,-73.256,-44.961,-73.362,-44.978,-73.405,-45.102,-73.445,-45.238,-73.226,-45.255,-73.064,-45.36,-72.976,-45.393,-72.941,-45.417,-72.934,-45.452,-72.978,-45.451,-73.202,-45.354,-73.266,-45.346,-73.379,-45.383,-73.55,-45.484,-73.731,-45.48,-73.78,-45.628,-73.757,-45.703,-73.662,-45.731,-73.594,-45.777,-73.592,-45.899,-73.629,-45.987,-73.652,-46.159,-73.668,-46.212,-73.662,-46.297,-73.716,-46.415,-73.77,-46.5,-73.845,-46.566,-73.944,-46.572,-73.949,-46.533,-73.935,-46.501,-73.811,-46.377,-73.708,-46.07,-73.708,-45.967,-73.695,-45.86,-73.735,-45.812,-73.813,-45.818,-73.879,-45.847,-73.929,-46.05,-73.968,-46.154,-74.09,-46.222,-74.213,-46.239,-74.372,-46.246,-74.393,-46.217,-74.357,-46.213,-74.082,-46.132,-74.02,-46.056,-74.061,-45.947,-74,-45.895,-73.96,-45.835,-73.882,-45.569,-73.844,-45.502,-73.825,-45.447,-73.92,-45.408,-73.957,-45.404,-74.038,-45.418,-74.099,-45.46,-74.123,-45.496,-74.099,-45.603,-74.083,-45.645,-74.082,-45.678,-74.096,-45.717,-74.158,-45.767,-74.301,-45.803,-74.369,-45.81,-74.463,-45.841,-74.631,-45.845,-74.763,-45.824,-75.067,-45.875,-75.075,-46.004,-74.998,-46.098,-74.924,-46.16,-75.075,-46.235,-75.247,-46.369,-75.376,-46.429,-75.437,-46.483,-75.657,-46.61,-75.706,-46.705,-75.708,-46.775,-75.635,-46.863,-75.497,-46.94,-75.43,-46.935,-75.401,-46.906,-75.386,-46.863,-75.446,-46.751,-75.528,-46.746,-75.565,-46.729,-75.54,-46.699,-75.478,-46.662,-75.337,-46.647,-75.146,-46.6,-75.019,-46.511,-74.984,-46.512,-75.053,-46.628,-75.031,-46.695,-75.006,-46.741,-74.811,-46.8,-74.691,-46.864,-74.512,-46.885,-74.48,-46.886,-74.467,-46.864,-74.489,-46.835,-74.484,-46.795,-74.454,-46.767,-74.314,-46.788,-74.209,-46.885,-74.152,-46.974,-74.208,-47.083,-74.158,-47.183,-74.216,-47.21,-74.403,-47.328,-74.483,-47.43,-74.324,-47.531,-74.243,-47.56,-74.191,-47.568,-74.134,-47.591,-74.152,-47.627,-74.243,-47.679,-74.323,-47.667,-74.404,-47.6,-74.467,-47.578,-74.534,-47.568,-74.588,-47.618,-74.655,-47.702,-74.609,-47.758,-74.569,-47.773,-74.43,-47.8,-74.376,-47.83,-74.379,-47.891,-74.351,-47.944,-74.227,-47.969,-74.085,-47.955,-73.941,-47.929,-73.847,-47.867,-73.779,-47.738,-73.748,-47.661,-73.716,-47.655,-73.635,-47.88,-73.629,-47.942,-73.61,-47.994,-73.57,-48.019,-73.501,-48.107,-73.391,-48.146,-73.384,-48.177,-73.528,-48.198,-73.854,-48.042,-74.25,-48.045,-74.4,-48.013,-74.585,-47.999,-74.591,-48.162,-74.577,-48.274,-74.499,-48.362,-74.474,-48.464,-74.343,-48.493,-74.27,-48.455,-74.172,-48.427,-74.009,-48.475,-74.057,-48.504,-74.129,-48.504,-74.176,-48.494,-74.228,-48.517,-74.341,-48.596,-74.382,-48.794,-74.38,-49.048,-74.358,-49.351,-74.367,-49.4,-74.349,-49.426,-74.302,-49.464,-74.221,-49.501,-74.185,-49.404,-74.168,-49.321,-74.14,-49.25,-74.074,-49.188,-74.061,-49.111,-74.028,-49.026,-73.935,-49.021,-73.938,-49.046,-73.985,-49.06,-74.015,-49.091,-74.006,-49.158,-74.023,-49.244,-74.049,-49.306,-74.083,-49.362,-74.094,-49.43,-73.988,-49.491,-73.892,-49.523,-73.836,-49.609,-73.892,-49.624,-73.956,-49.593,-74.102,-49.555,-74.23,-49.579,-74.291,-49.604,-74.319,-49.72,-74.324,-49.783,-74.171,-49.907,-74.073,-49.949,-74.011,-49.929,-73.959,-49.995,-74.019,-50.023,-74.334,-49.975,-74.434,-50.065,-74.63,-50.194,-74.516,-50.266,-74.425,-50.35,-74.374,-50.363,-74.306,-50.398,-74.031,-50.47,-73.95,-50.511,-74.186,-50.485,-74.197,-50.61,-74.164,-50.638,-74.097,-50.717,-73.978,-50.827,-73.892,-50.783,-73.75,-50.54,-73.68,-50.49,-73.654,-50.493,-73.693,-50.57,-73.614,-50.628,-73.618,-50.651,-73.659,-50.651,-73.741,-50.697,-73.825,-50.836,-73.807,-50.938,-73.847,-50.94,-74.139,-50.818,-74.156,-50.797,-74.19,-50.778,-74.331,-50.56,-74.366,-50.488,-74.564,-50.382,-74.644,-50.361,-74.722,-50.408,-74.776,-50.47,-74.702,-50.535,-74.649,-50.618,-74.686,-50.662,-74.837,-50.679,-75.095,-50.681,-75.055,-50.786,-74.983,-50.881,-74.815,-51.063,-74.69,-51.087,-74.587,-51.131,-74.508,-51.15,-74.414,-51.162,-74.332,-51.195,-74.21,-51.205,-74.121,-51.195,-73.94,-51.266,-73.896,-51.331,-73.93,-51.618,-74.07,-51.579,-74.197,-51.681,-74.146,-51.712,-73.973,-51.784,-73.894,-51.758,-73.858,-51.79,-73.811,-51.801,-73.753,-51.796,-73.65,-51.856,-73.582,-51.96,-73.518,-52.041,-73.383,-52.07,-73.189,-51.991,-72.928,-51.86,-72.6,-51.799,-72.583,-51.737,-72.649,-51.695,-72.705,-51.678,-72.789,-51.614,-73.115,-51.504,-73.163,-51.496,-73.197,-51.478,-73.169,-51.454,-73.127,-51.44,-72.761,-51.573,-72.543,-51.706,-72.49,-51.764,-72.494,-51.848,-72.523,-51.891,-72.625,-51.946,-72.625,-52.007,-72.614,-52.037,-72.524,-52.17,-72.519,-52.217,-72.523,-52.255,-72.533,-52.282,-72.569,-52.334,-72.631,-52.372,-72.677,-52.385,-72.714,-52.357,-72.694,-52.33,-72.583,-52.254,-72.571,-52.2,-72.588,-52.145,-72.65,-52.1,-72.695,-52.045,-72.695,-51.985,-72.735,-51.961,-72.795,-51.95,-72.843,-51.961,-72.944,-52.047,-73.137,-52.13,-73.26,-52.158,-73.327,-52.166,-73.458,-52.146,-73.532,-52.153,-73.649,-52.078,-73.684,-52.078,-73.685,-52.137,-73.703,-52.199,-73.749,-52.216,-73.834,-52.234,-74.04,-52.159,-74.134,-52.155,-74.195,-52.12,-74.265,-52.105,-74.296,-52.118,-74.266,-52.171,-74.238,-52.202,-74.177,-52.317,-74.151,-52.383,-74.094,-52.376,-74.037,-52.403,-74,-52.513,-74.036,-52.577,-74.014,-52.639,-73.915,-52.688,-73.711,-52.662,-73.586,-52.686,-73.382,-52.595,-73.244,-52.624,-73.178,-52.563,-73.184,-52.488,-73.124,-52.488,-73.073,-52.535,-73.145,-52.602,-73.241,-52.707,-73.346,-52.754,-73.645,-52.837,-73.508,-52.904,-73.46,-52.965,-73.338,-53.055,-73.122,-53.074,-73.055,-53.046,-73.023,-53.022,-73.016,-52.977,-73.02,-52.892,-72.932,-52.782,-72.802,-52.712,-72.766,-52.643,-72.777,-52.577,-72.712,-52.536,-72.645,-52.529,-72.504,-52.56,-72.478,-52.604,-72.438,-52.626,-72.315,-52.539,-72.226,-52.521,-71.812,-52.537,-71.665,-52.56,-71.511,-52.605,-71.554,-52.644,-71.591,-52.661,-71.797,-52.683,-71.979,-52.646,-72.118,-52.65,-72.453,-52.814,-72.627,-52.818,-72.632,-52.774,-72.676,-52.749,-72.728,-52.762,-72.832,-52.82,-72.889,-52.872,-72.91,-52.937,-72.916,-53.122,-72.998,-53.177,-73.053,-53.243,-72.998,-53.291,-72.727,-53.42,-72.549,-53.461,-72.531,-53.372,-72.493,-53.291,-72.458,-53.254,-72.278,-53.132,-72.129,-53.064,-71.898,-53.002,-71.388,-52.764,-71.227,-52.811,-71.155,-52.846,-71.163,-52.888,-71.18,-52.921,-71.289,-53.034,-71.4,-53.107,-71.741,-53.233,-71.791,-53.485,-71.892,-53.524,-71.903,-53.496,-71.867,-53.458,-71.828,-53.398,-71.853,-53.286,-71.942,-53.234,-72.081,-53.25,-72.249,-53.247,-72.306,-53.254,-72.413,-53.35,-72.398,-53.418,-72.377,-53.471,-72.174,-53.632,-72.101,-53.666,-71.872,-53.723,-71.694,-53.803,-71.444,-53.841,-71.298,-53.883,-71.083,-53.825,-70.996,-53.779,-70.948,-53.57,-70.985,-53.448,-70.984,-53.374,-70.952,-53.227,-70.821,-52.963,-70.839,-52.89,-70.795,-52.769,-70.68,-52.712,-70.563,-52.673,-70.391,-52.661,-69.907,-52.514,-69.763,-52.506,-69.62,-52.465,-69.561,-52.422,-69.447,-52.269,-69.241,-52.205,-69.134,-52.211,-69.007,-52.263,-68.443,-52.357,-68.394,-52.307,-68.494,-52.198,-68.691,-52.013,-68.917,-51.715,-68.965,-51.677,-69.033,-51.636,-69.18,-51.662,-69.313,-51.601,-69.409,-51.61,-69.465,-51.584,-69.361,-51.559,-69.218,-51.561,-69.058,-51.547,-69.035,-51.489,-69.03,-51.446,-69.066,-51.304,-69.144,-51.097,-69.201,-50.994,-69.268,-51.006,-69.352,-51.046,-69.359,-51.028,-69.235,-50.951,-69.155,-50.864,-69.141,-50.753,-69.09,-50.583,-69.045,-50.499,-68.939,-50.382,-68.75,-50.281,-68.589,-50.225,-68.465,-50.195,-68.422,-50.158,-68.474,-50.091,-68.533,-50.036,-68.598,-50.009,-68.753,-49.988,-68.98,-50.003,-68.913,-49.969,-68.662,-49.936,-68.638,-49.863,-68.673,-49.793,-68.668,-49.753,-68.569,-49.867,-68.488,-49.978,-68.405,-50.043,-68.257,-50.105,-68.146,-50.091,-67.914,-49.984,-67.826,-49.92,-67.783,-49.859,-67.662,-49.342,-67.694,-49.304,-67.685,-49.247,-67.466,-48.952,-67.263,-48.814,-67.131,-48.688,-67.033,-48.628,-66.783,-48.523,-66.596,-48.42,-66.393,-48.342,-66.017,-48.084,-65.943,-48.019,-65.912,-47.977,-65.81,-47.941,-65.864,-47.853,-65.934,-47.827,-66.097,-47.853,-66.172,-47.858,-66.225,-47.827,-66.041,-47.783,-65.886,-47.702,-65.814,-47.638,-65.775,-47.568,-65.738,-47.345,-65.769,-47.257,-65.854,-47.157,-65.999,-47.094,-66.65,-47.045,-66.777,-47.006,-67.387,-46.554,-67.506,-46.443,-67.563,-46.345,-67.586,-46.27,-67.609,-46.167,-67.6,-46.053,-67.557,-45.97,-67.393,-45.776,-67.258,-45.577,-66.941,-45.257,-66.882,-45.228,-66.585,-45.183,-66.533,-45.158,-66.494,-45.118,-66.348,-45.034,-66.19,-44.965,-65.758,-45.007,-65.639,-45.008,-65.606,-44.945,-65.599,-44.876,-65.698,-44.796,-65.648,-44.661,-65.361,-44.477,-65.29,-44.361,-65.266,-44.28,-65.308,-44.158,-65.239,-44.049,-65.305,-43.787,-65.284,-43.63,-65.252,-43.572,-65.19,-43.522,-64.986,-43.294,-64.84,-43.189,-64.715,-43.136,-64.432,-43.059,-64.376,-43.025,-64.319,-42.969,-64.38,-42.949,-64.442,-42.951,-64.629,-42.909,-65.027,-42.759,-64.971,-42.666,-64.812,-42.633,-64.65,-42.531,-64.488,-42.513,-64.324,-42.572,-64.248,-42.646,-64.22,-42.756,-64.131,-42.861,-64.035,-42.881,-63.692,-42.805,-63.644,-42.746,-63.617,-42.696,-63.594,-42.556,-63.596,-42.407,-63.63,-42.283,-63.685,-42.189,-63.729,-42.153,-63.796,-42.114,-63.893,-42.125,-64.083,-42.183,-64.229,-42.218,-64.253,-42.251,-64.061,-42.266,-64.062,-42.353,-64.101,-42.395,-64.265,-42.422,-64.42,-42.434,-64.571,-42.416,-64.574,-42.356,-64.524,-42.299,-64.512,-42.27,-64.538,-42.255,-64.622,-42.261,-64.7,-42.221,-64.898,-42.162,-64.986,-42.102,-65.059,-41.97,-65.007,-41.745,-65.018,-41.567,-65.128,-41.239,-65.155,-41.106,-65.152,-40.947,-65.133,-40.881,-65.069,-40.805,-64.917,-40.731,-64.869,-40.736,-64.804,-40.757,-64.82,-40.793,-64.853,-40.814,-64.621,-40.854,-64.383,-40.922,-64.123,-41.008,-63.773,-41.15,-63.622,-41.16,-63.213,-41.152,-62.959,-41.11,-62.798,-41.047,-62.395,-40.891,-62.302,-40.815,-62.246,-40.675,-62.394,-40.459,-62.427,-40.356,-62.402,-40.197,-62.324,-39.951,-62.287,-39.895,-62.254,-39.88,-62.132,-39.825,-62.083,-39.568,-62.077,-39.462,-62.131,-39.432,-62.179,-39.38,-62.054,-39.374,-62.126,-39.31,-62.209,-39.262,-62.295,-39.243,-62.338,-39.151,-62.304,-38.988,-62.374,-38.853,-62.335,-38.8,-62.189,-38.813,-62.067,-38.919,-61.848,-38.962,-61.603,-38.999,-61.383,-38.981,-61.112,-38.993,-60.904,-38.974,-59.828,-38.838,-59.676,-38.797,-59.007,-38.673,-58.179,-38.436,-57.646,-38.17,-57.547,-38.086,-57.507,-37.909,-57.396,-37.745,-57.088,-37.446,-56.727,-36.958,-56.672,-36.851,-56.668,-36.735,-56.698,-36.426,-56.717,-36.389,-56.749,-36.346,-56.937,-36.353,-57.076,-36.297,-57.265,-36.144,-57.335,-36.027,-57.375,-35.9,-57.354,-35.72,-57.159,-35.506,-57.171,-35.362,-57.304,-35.188,-57.548,-35.019,-57.764,-34.895,-58.283,-34.683,-58.419,-34.532,-58.466,-34.457,-58.525,-34.296,-58.475,-34.263,-58.435,-34.253,-58.392,-34.193,-58.409,-34.061,-58.429,-33.991,-58.457,-33.898,-58.531,-33.753,-58.547,-33.663,-58.455,-33.286,-58.424,-33.112,-58.376,-33.072,-58.309,-33.083,-58.25,-33.078,-58.201,-33.015,-58.171,-32.959,-58.22,-32.564,-58.201,-32.472,-58.162,-32.567,-58.13,-32.757,-58.082,-32.894,-58.093,-32.967,-58.154,-33.065,-58.222,-33.129,-58.292,-33.138,-58.364,-33.182,-58.353,-33.26,-58.411,-33.509,-58.438,-33.719,-58.4,-33.912,-58.207,-34.109,-57.961,-34.307,-57.902,-34.39,-57.873,-34.448,-57.829,-34.477,-57.543,-34.448,-57.171,-34.452,-56.855,-34.677,-56.463,-34.775,-56.388,-34.861,-56.25,-34.901,-56.195,-34.906,-56.118,-34.908,-55.863,-34.811,-55.673,-34.776,-55.371,-34.808,-55.238,-34.896,-55.095,-34.895,-54.902,-34.933,-54.365,-34.733,-54.272,-34.667,-54.169,-34.671,-54.01,-34.517,-53.785,-34.38,-53.743,-34.25,-53.535,-34.017,-53.472,-33.849,-53.42,-33.779,-53.371,-33.742,-52.921,-33.402,-52.763,-33.266,-52.652,-33.138,-52.508,-32.875,-52.342,-32.44,-52.275,-32.324,-52.19,-32.221,-52.127,-32.168,-52.167,-32.088,-52.192,-31.968,-52.194,-31.886,-52.12,-31.695,-52.027,-31.599,-51.995,-31.49,-51.972,-31.384,-51.927,-31.339,-51.717,-31.244,-51.506,-31.104,-51.464,-31.053,-51.485,-30.978,-51.459,-30.913,-51.376,-30.847,-51.359,-30.675,-51.316,-30.703,-51.283,-30.752,-51.288,-30.591,-51.247,-30.468,-51.188,-30.412,-51.157,-30.364,-51.282,-30.244,-51.295,-30.141,-51.298,-30.035,-51.25,-30.06,-51.234,-30.121,-51.179,-30.211,-51.04,-30.261,-51.025,-30.369,-50.932,-30.374,-50.646,-30.237,-50.564,-30.254,-50.547,-30.317,-50.582,-30.439,-50.615,-30.457,-50.685,-30.413,-50.716,-30.426,-50.689,-30.704,-50.77,-30.813,-50.941,-30.904,-50.965,-31.005,-50.954,-31.052,-50.98,-31.094,-51.106,-31.081,-51.161,-31.119,-51.158,-31.267,-51.174,-31.34,-51.272,-31.477,-51.446,-31.557,-51.681,-31.775,-51.803,-31.797,-51.841,-31.832,-51.893,-31.868,-51.995,-31.815,-52.063,-31.83,-52.06,-31.913,-52.043,-31.978,-52.069,-32.063,-52.039,-32.115,-51.92,-31.99,-51.798,-31.9,-51.46,-31.702,-51.152,-31.48,-50.921,-31.258,-50.748,-31.068,-50.62,-30.898,-50.3,-30.426,-50.033,-29.801,-49.746,-29.363,-49.5,-29.075,-49.271,-28.871,-49.024,-28.699,-48.8,-28.575,-48.797,-28.443,-48.693,-28.31,-48.648,-28.207,-48.621,-28.076,-48.606,-27.825,-48.643,-27.558,-48.572,-27.373,-48.596,-27.264,-48.554,-27.196,-48.568,-27.123,-48.593,-27.058,-48.616,-26.878,-48.678,-26.703,-48.677,-26.612,-48.658,-26.519,-48.652,-26.406,-48.701,-26.348,-48.748,-26.269,-48.714,-26.227,-48.679,-26.226,-48.619,-26.179,-48.576,-25.935,-48.613,-25.875,-48.679,-25.875,-48.666,-25.844,-48.545,-25.816,-48.401,-25.597,-48.43,-25.55,-48.507,-25.521,-48.692,-25.492,-48.732,-25.369,-48.644,-25.437,-48.564,-25.447,-48.476,-25.443,-48.428,-25.403,-48.458,-25.311,-48.402,-25.272,-48.273,-25.306,-48.186,-25.31,-48.242,-25.403,-48.203,-25.417,-48.024,-25.237,-47.929,-25.168,-47.908,-25.068,-47.959,-25.065,-47.989,-25.036,-47.914,-25,-47.877,-24.997,-47.831,-24.953,-47.592,-24.781,-47.137,-24.493,-46.867,-24.236,-46.631,-24.11,-45.972,-23.796,-45.843,-23.764,-45.665,-23.765,-45.527,-23.805,-45.464,-23.803,-45.433,-23.758,-45.423,-23.685,-45.325,-23.6,-45.215,-23.576,-44.952,-23.381,-44.667,-23.335,-44.619,-23.316,-44.57,-23.274,-44.621,-23.229,-44.674,-23.207,-44.681,-23.107,-44.637,-23.055,-44.368,-23.005,-44.148,-23.011,-44.047,-22.945,-43.866,-22.911,-43.703,-22.966,-43.676,-23.009,-43.791,-23.046,-43.899,-23.035,-43.974,-23.057,-43.899,-23.101,-43.737,-23.067,-43.533,-23.046,-43.369,-22.998,-43.224,-22.991,-43.194,-22.939,-43.209,-22.878,-43.237,-22.829,-43.242,-22.795,-43.229,-22.748,-43.154,-22.725,-43.086,-22.723,-43.065,-22.771,-43.101,-22.85,-43.081,-22.903,-43.016,-22.943,-42.958,-22.967,-42.829,-22.973,-42.581,-22.941,-42.122,-22.941,-42.042,-22.947,-41.987,-22.845,-41.941,-22.788,-41.986,-22.736,-41.998,-22.645,-41.98,-22.581,-41.706,-22.31,-41.583,-22.244,-41.123,-22.084,-41,-21.999,-40.988,-21.92,-41.022,-21.611,-41.023,-21.597,-41.047,-21.506,-40.955,-21.238,-40.829,-21.031,-40.789,-20.906,-40.727,-20.846,-40.597,-20.784,-40.396,-20.569,-40.319,-20.426,-40.299,-20.293,-40.203,-20.206,-40.142,-19.968,-40.001,-19.742,-39.845,-19.649,-39.783,-19.572,-39.731,-19.454,-39.7,-19.278,-39.742,-18.846,-39.74,-18.64,-39.651,-18.252,-39.487,-17.99,-39.413,-17.92,-39.278,-17.849,-39.154,-17.704,-39.171,-17.642,-39.215,-17.316,-39.203,-17.178,-39.164,-17.044,-39.125,-16.764,-39.063,-16.504,-38.961,-16.187,-38.881,-15.864,-38.885,-15.842,-38.943,-15.564,-38.996,-15.254,-39.013,-14.936,-39.06,-14.655,-38.942,-14.031,-38.967,-14.003,-39.008,-14.101,-39.048,-14.044,-39.035,-13.991,-39.041,-13.758,-39.001,-13.665,-38.989,-13.615,-39.009,-13.581,-39.035,-13.559,-39.089,-13.588,-39.067,-13.48,-39.031,-13.365,-38.959,-13.273,-38.835,-13.147,-38.833,-13.033,-38.764,-12.907,-38.784,-12.844,-38.852,-12.79,-38.788,-12.783,-38.744,-12.749,-38.691,-12.624,-38.654,-12.645,-38.525,-12.762,-38.499,-12.957,-38.447,-12.967,-38.402,-12.966,-38.24,-12.844,-38.019,-12.591,-37.957,-12.475,-37.689,-12.1,-37.469,-11.654,-37.412,-11.497,-37.438,-11.394,-37.359,-11.253,-37.322,-11.215,-37.321,-11.267,-37.332,-11.31,-37.355,-11.35,-37.356,-11.404,-37.315,-11.376,-37.181,-11.187,-37.183,-11.068,-37.125,-11.085,-37.093,-11.055,-36.938,-10.82,-36.768,-10.672,-36.636,-10.59,-36.412,-10.49,-36.398,-10.484,-36.224,-10.225,-36.055,-10.076,-35.885,-9.848,-35.848,-9.772,-35.891,-9.687,-35.83,-9.719,-35.764,-9.703,-35.597,-9.541,-35.341,-9.231,-35.158,-8.931,-34.967,-8.408,-34.891,-8.092,-34.835,-7.971,-34.837,-7.872,-34.879,-7.747,-34.873,-7.692,-34.855,-7.634,-34.861,-7.595,-34.858,-7.533,-34.817,-7.395,-34.805,-7.288,-34.834,-7.024,-34.876,-7.003,-34.88,-6.908,-34.93,-6.785,-34.988,-6.394,-35.095,-6.185,-35.142,-5.917,-35.235,-5.567,-35.393,-5.251,-35.482,-5.166,-35.549,-5.129,-35.98,-5.054,-36.162,-5.094,-36.387,-5.084,-36.591,-5.098,-36.747,-5.051,-36.861,-4.967,-36.955,-4.937,-37.175,-4.912,-37.301,-4.713,-37.626,-4.592,-37.796,-4.404,-38.049,-4.216,-38.272,-3.948,-38.362,-3.876,-38.476,-3.717,-38.686,-3.654,-38.896,-3.502,-39.014,-3.39,-39.353,-3.197,-39.511,-3.126,-39.609,-3.056,-39.772,-2.986,-39.965,-2.862,-40.235,-2.813,-40.475,-2.796,-40.876,-2.87,-41.195,-2.886,-41.318,-2.936,-41.48,-2.917,-41.64,-2.879,-41.722,-2.809,-41.876,-2.747,-42,-2.806,-42.25,-2.792,-42.594,-2.661,-42.676,-2.59,-42.832,-2.53,-42.937,-2.465,-43.23,-2.386,-43.38,-2.376,-43.435,-2.414,-43.455,-2.502,-43.729,-2.518,-43.864,-2.595,-43.933,-2.583,-44.013,-2.642,-44.193,-2.81,-44.225,-2.755,-44.192,-2.7,-44.113,-2.599,-44.101,-2.56,-44.106,-2.493,-44.179,-2.471,-44.229,-2.471,-44.308,-2.535,-44.381,-2.738,-44.438,-2.944,-44.623,-3.138,-44.723,-3.205,-44.721,-3.142,-44.639,-2.762,-44.611,-2.677,-44.589,-2.573,-44.562,-2.524,-44.521,-2.481,-44.52,-2.405,-44.382,-2.366,-44.391,-2.27,-44.435,-2.168,-44.52,-2.19,-44.579,-2.23,-44.662,-2.373,-44.701,-2.32,-44.756,-2.266,-44.708,-2.241,-44.659,-2.228,-44.617,-2.152,-44.58,-2.114,-44.538,-2.053,-44.547,-1.946,-44.592,-1.842,-44.651,-1.746,-44.721,-1.792,-44.779,-1.799,-44.721,-1.733,-44.79,-1.725,-44.828,-1.672,-44.92,-1.589,-45.026,-1.513,-45.076,-1.466,-45.182,-1.507,-45.239,-1.629,-45.282,-1.697,-45.329,-1.717,-45.353,-1.567,-45.459,-1.356,-45.557,-1.331,-45.645,-1.348,-45.779,-1.251,-45.972,-1.187,-46.045,-1.103,-46.14,-1.118,-46.215,-1.1,-46.219,-1.031,-46.321,-1.039,-46.422,-1.03,-46.516,-0.997,-46.617,-0.971,-46.644,-0.916,-46.77,-0.837,-46.811,-0.78,-46.894,-0.78,-46.944,-0.743,-47.025,-0.75,-47.127,-0.745,-47.201,-0.68,-47.269,-0.645,-47.398,-0.627,-47.439,-0.648,-47.46,-0.681,-47.433,-0.722,-47.419,-0.766,-47.471,-0.749,-47.557,-0.67,-47.651,-0.719,-47.687,-0.725,-47.731,-0.71,-47.774,-0.677,-47.808,-0.663,-47.883,-0.693,-47.961,-0.77,-48.033,-0.705,-48.069,-0.714,-48.115,-0.737,-48.128,-0.795,-48.202,-0.828,-48.266,-0.895,-48.318,-0.961,-48.306,-1.04,-48.45,-1.146,-48.409,-1.229,-48.478,-1.324,-48.468,-1.394,-48.451,-1.436,-48.35,-1.482,-48.446,-1.52,-48.463,-1.614,-48.53,-1.567,-48.6,-1.489,-48.71,-1.488,-48.991,-1.83,-49.155,-1.879,-49.211,-1.917,-49.408,-2.344,-49.458,-2.505,-49.524,-2.597,-49.576,-2.631,-49.637,-2.657,-49.599,-2.584,-49.553,-2.52,-49.507,-2.28,-49.46,-2.192,-49.399,-1.972,-49.314,-1.732,-49.585,-1.867,-49.72,-1.926,-49.903,-1.871,-49.999,-1.832,-50.117,-1.858,-50.173,-1.896,-50.26,-1.923,-50.403,-2.016,-50.586,-1.85,-50.639,-1.817,-50.69,-1.762,-50.675,-1.695,-50.679,-1.644,-50.786,-1.49,-50.819,-1.376,-50.826,-1.311,-50.845,-1.226,-50.897,-1.164,-50.918,-1.115,-50.838,-1.039,-50.842,-1,-50.895,-0.938,-50.992,-0.986,-51.029,-1.032,-51.202,-1.137,-51.297,-1.224,-51.531,-1.354,-51.646,-1.394,-51.948,-1.587,-52.197,-1.64,-52.31,-1.56,-52.664,-1.552,-52.553,-1.514,-52.229,-1.362,-52.02,-1.399,-51.981,-1.368,-51.934,-1.32,-51.922,-1.181,-51.819,-1.118,-51.721,-1.018,-51.722,-0.855,-51.703,-0.762,-51.555,-0.549,-51.496,-0.509,-51.404,-0.393,-51.3,-0.179,-51.283,-0.085,-51.102,-0.031,-50.967,0.13,-50.91,0.161,-50.816,0.173,-50.755,0.223,-50.582,0.421,-50.463,0.637,-50.343,0.751,-50.294,0.836,-50.071,1.015,-50.047,1.052,-49.938,1.121,-49.899,1.163,-49.906,1.269,-49.882,1.42,-49.957,1.66,-50.055,1.731,-50.188,1.786,-50.304,1.798,-50.459,1.83,-50.534,1.927,-50.576,1.999,-50.609,2.104,-50.659,2.131,-50.714,2.134,-50.677,2.179,-50.679,2.21,-50.737,2.377,-50.79,2.478,-50.817,2.573,-50.827,2.652,-50.994,3.078,-51.052,3.282,-51.076,3.672,-51.22,4.094,-51.327,4.225,-51.462,4.314,-51.547,4.311,-51.558,4.234,-51.653,4.061,-51.658,4.098,-51.653,4.139,-51.666,4.229,-51.699,4.287,-51.786,4.571,-51.828,4.636,-51.88,4.634,-51.92,4.524,-51.928,4.436,-51.955,4.399,-52.003,4.352,-52.002,4.386,-51.979,4.43,-51.962,4.514,-52.012,4.646,-52.058,4.717,-52.22,4.863,-52.325,4.771,-52.289,4.876,-52.291,4.942,-52.454,5.021,-52.765,5.273,-52.899,5.425,-53.27,5.543,-53.454,5.563,-53.847,5.782,-53.92,5.769,-53.99,5.676,-54.085,5.412,-54.156,5.359,-54.08,5.502,-54.046,5.609,-54.037,5.721,-54.054,5.808,-54.142,5.856,-54.356,5.91,-54.834,5.988,-55.148,5.993,-55.379,5.953,-55.648,5.986,-55.828,5.962,-55.91,5.893,-55.896,5.795,-55.898,5.699,-55.94,5.795,-56.236,5.885,-56.466,5.938,-56.97,5.993,-57.057,5.939,-57.105,5.829,-57.136,5.737,-57.141,5.644,-57.182,5.529,-57.195,5.548,-57.205,5.565,-57.167,5.885,-57.19,6.097,-57.228,6.178,-57.344,6.272,-57.54,6.332,-57.608,6.45,-57.793,6.599,-57.983,6.786,-58.072,6.821,-58.173,6.829,-58.298,6.879,-58.415,6.851,-58.502,6.734,-58.569,6.627,-58.594,6.452,-58.673,6.391,-58.613,6.503,-58.608,6.697,-58.583,6.844,-58.481,7.038,-58.477,7.326,-58.511,7.398,-58.627,7.546,-58.701,7.607,-58.812,7.736,-59.2,8.075,-59.477,8.254,-59.666,8.363,-59.739,8.38,-59.74,8.339,-59.757,8.34,-59.837,8.374,-59.981,8.533,-60.018,8.549,-60.167,8.617,-60.34,8.629,-60.404,8.61,-60.481,8.547,-60.801,8.592,-60.865,8.579,-61.036,8.493,-61.194,8.488,-61.304,8.41,-61.443,8.509,-61.527,8.546,-61.619,8.597,-61.247,8.6,-61.176,8.725,-61.122,8.843,-61.099,8.941,-61.093,8.966,-61.054,9.035,-61.053,9.095,-61.023,9.155,-60.971,9.215,-60.841,9.264,-60.792,9.361,-60.874,9.453,-61.013,9.556,-61.234,9.598,-61.309,9.633,-61.512,9.848,-61.589,9.895,-61.625,9.816,-61.766,9.814,-61.759,9.754,-61.732,9.702,-61.736,9.631,-61.759,9.677,-61.805,9.706,-61.831,9.733,-61.837,9.782,-61.909,9.87,-62.017,9.955,-62.077,9.975,-62.12,9.985,-62.155,9.979,-62.147,9.953,-62.17,9.879,-62.153,9.822,-62.172,9.827,-62.19,9.842,-62.221,9.883,-62.257,9.819,-62.281,9.793,-62.3,9.788,-62.32,9.783,-62.401,9.918,-62.515,10.176,-62.55,10.2,-62.6,10.217,-62.608,10.163,-62.6,10.117,-62.651,10.071,-62.741,10.056,-62.695,10.1,-62.662,10.199,-62.686,10.29,-62.706,10.333,-62.781,10.399,-62.813,10.4,-62.843,10.418,-62.843,10.507,-62.914,10.531,-62.694,10.563,-62.38,10.547,-62.233,10.634,-62.04,10.645,-61.921,10.681,-61.879,10.741,-62.242,10.7,-62.702,10.75,-62.947,10.707,-63.035,10.72,-63.19,10.709,-63.497,10.643,-63.873,10.664,-64.202,10.633,-64.298,10.635,-64.248,10.543,-64.158,10.579,-63.863,10.558,-63.732,10.503,-63.779,10.472,-63.834,10.449,-64.188,10.458,-64.85,10.098,-64.944,10.095,-65.023,10.077,-65.129,10.07,-65.317,10.122,-65.489,10.159,-65.656,10.228,-65.852,10.258,-66.09,10.473,-66.092,10.517,-66.106,10.575,-66.247,10.632,-66.989,10.611,-67.133,10.57,-67.581,10.524,-67.872,10.472,-68.14,10.493,-68.234,10.569,-68.296,10.689,-68.325,10.809,-68.272,10.88,-68.325,10.949,-68.343,11.053,-68.399,11.161,-68.616,11.309,-68.828,11.432,-69.055,11.461,-69.233,11.518,-69.526,11.5,-69.57,11.485,-69.632,11.48,-69.712,11.564,-69.762,11.676,-69.811,11.837,-69.831,11.996,-69.86,12.054,-69.914,12.115,-70.004,12.178,-70.122,12.137,-70.203,12.098,-70.245,12.004,-70.287,11.886,-70.22,11.73,-70.22,11.681,-70.193,11.625,-69.911,11.672,-69.817,11.672,-69.773,11.541,-69.805,11.474,-69.885,11.444,-70.049,11.53,-70.097,11.52,-70.16,11.428,-70.233,11.373,-70.546,11.261,-70.821,11.208,-71.264,11,-71.47,10.964,-71.461,10.836,-71.545,10.779,-71.518,10.622,-71.494,10.533,-71.463,10.469,-71.387,10.264,-71.262,10.144,-71.207,10.015,-71.082,9.833,-71.053,9.706,-71.078,9.511,-71.086,9.348,-71.205,9.222,-71.241,9.16,-71.298,9.126,-71.537,9.048,-71.62,9.048,-71.687,9.073,-71.74,9.134,-71.781,9.25,-71.761,9.336,-71.806,9.386,-71.873,9.428,-71.976,9.553,-71.993,9.642,-72.113,9.816,-71.956,10.108,-71.885,10.167,-71.794,10.316,-71.665,10.444,-71.594,10.657,-71.598,10.726,-71.69,10.835,-71.731,10.995,-71.676,10.997,-71.642,11.014,-71.791,11.135,-71.835,11.19,-71.947,11.414,-71.957,11.483,-71.957,11.57,-71.908,11.608,-71.869,11.627,-71.488,11.719,-71.415,11.755,-71.349,11.815,-71.32,11.862,-71.284,11.918,-71.137,12.046,-71.155,12.164,-71.262,12.335,-71.494,12.432,-71.597,12.434,-71.715,12.42,-71.919,12.309,-71.931,12.27,-71.97,12.238,-72.055,12.238,-72.136,12.189,-72.165,12.06,-72.275,11.889,-72.447,11.802,-72.722,11.712,-73.313,11.296,-73.677,11.271,-73.796,11.276,-73.91,11.309,-74.059,11.341,-74.143,11.321,-74.2,11.266,-74.219,11.105,-74.3,10.952,-74.35,10.814,-74.401,10.765,-74.46,10.787,-74.516,10.863,-74.492,10.934,-74.41,10.967,-74.352,10.975,-74.33,10.997,-74.454,10.989,-74.845,11.11,-74.922,11.058,-75.123,10.87,-75.248,10.783,-75.281,10.727,-75.446,10.611,-75.493,10.528,-75.554,10.328,-75.671,10.196,-75.708,10.143,-75.642,10.172,-75.558,10.236,-75.539,10.205,-75.596,10.126,-75.593,9.993,-75.637,9.834,-75.68,9.73,-75.635,9.658,-75.604,9.538,-75.639,9.45,-75.756,9.416,-75.905,9.431,-76.027,9.366,-76.135,9.266,-76.277,8.989,-76.689,8.695,-76.802,8.641,-76.888,8.62,-76.92,8.574,-76.872,8.513,-76.819,8.465,-76.772,8.311,-76.742,8.002,-76.787,7.932,-76.867,7.918,-76.897,7.939,-76.925,7.973,-76.912,8.033,-76.869,8.063,-76.852,8.09,-76.891,8.128,-76.936,8.147,-76.992,8.25,-77.13,8.401,-77.262,8.494,-77.344,8.637,-77.374,8.658,-77.697,8.889,-77.831,9.068,-78.083,9.236,-78.504,9.406,-78.697,9.435,-78.932,9.428,-78.975,9.453,-79.017,9.51,-79.112,9.537,-79.212,9.532,-79.355,9.569,-79.577,9.598,-79.652,9.558,-79.723,9.479,-79.855,9.378,-79.915,9.361,-79.978,9.344,-80.127,9.21,-80.547,9.082,-80.676,9.022,-80.839,8.887,-81.063,8.813,-81.204,8.787,-81.355,8.781,-81.546,8.827,-81.712,9.019,-81.803,9.074,-81.842,9.119,-81.894,9.14,-81.9,9.111,-81.831,9.046,-81.78,8.957,-81.826,8.944,-81.894,8.956,-82.078,8.935,-82.133,8.98,-82.244,9.031,-82.235,9.142,-82.201,9.168,-82.188,9.192,-82.205,9.215,-82.272,9.191,-82.34,9.209,-82.375,9.337,-82.363,9.382,-82.371,9.429,-82.5,9.523,-82.564,9.577,-82.61,9.616,-82.778,9.67,-82.81,9.735,-82.866,9.771,-83.029,9.991,-83.125,10.042,-83.347,10.315,-83.448,10.466,-83.575,10.735,-83.588,10.815,-83.617,10.877,-83.642,10.917,-83.714,10.934,-83.768,11.01,-83.832,11.131,-83.868,11.3,-83.859,11.354,-83.829,11.428,-83.777,11.504,-83.745,11.567,-83.652,11.642,-83.664,11.724,-83.705,11.825,-83.753,11.821,-83.793,11.836,-83.829,11.861,-83.813,11.896,-83.769,11.932,-83.773,11.977,-83.767,12.059,-83.716,12.057,-83.698,12.03,-83.68,12.024,-83.669,12.228,-83.651,12.287,-83.667,12.337,-83.716,12.407,-83.754,12.502,-83.718,12.553,-83.682,12.568,-83.625,12.613,-83.593,12.713,-83.578,12.667,-83.591,12.579,-83.624,12.515,-83.627,12.459,-83.596,12.396,-83.565,12.393,-83.511,12.412,-83.518,12.514,-83.541,12.596,-83.514,12.944,-83.567,13.32,-83.494,13.739,-83.412,13.996,-83.347,14.057,-83.281,14.154,-83.212,14.267,-83.188,14.34,-83.299,14.749,-83.341,14.765,-83.375,14.766,-83.414,14.825,-83.389,14.871,-83.344,14.902,-83.306,14.891,-83.302,14.802,-83.28,14.813,-83.216,14.932,-83.185,14.956,-83.158,14.993,-83.226,15.042,-83.291,15.079,-83.369,15.24,-83.646,15.368,-83.676,15.365,-83.551,15.294,-83.498,15.222,-83.536,15.219,-83.59,15.266,-83.672,15.261,-83.716,15.219,-83.76,15.22,-83.802,15.289,-83.871,15.353,-83.927,15.394,-84.013,15.414,-84.048,15.398,-84.095,15.401,-84.105,15.43,-84.111,15.492,-84.083,15.511,-83.973,15.52,-83.765,15.405,-83.775,15.437,-84.261,15.823,-84.52,15.873,-84.49,15.847,-84.426,15.829,-84.44,15.813,-84.492,15.794,-84.56,15.802,-84.646,15.884,-84.974,15.99,-85.048,15.974,-85.164,15.918,-85.484,15.9,-85.784,16.003,-85.986,16.024,-85.954,16.002,-85.936,15.953,-86.069,15.906,-86.181,15.885,-86.357,15.783,-86.481,15.801,-86.757,15.794,-86.907,15.762,-87.286,15.834,-87.377,15.826,-87.487,15.79,-87.545,15.832,-87.618,15.91,-87.702,15.911,-87.875,15.879,-87.907,15.863,-88.01,15.786,-88.055,15.765,-88.131,15.701,-88.228,15.729,-88.594,15.95,-88.598,15.927,-88.572,15.901,-88.536,15.85,-88.603,15.764,-88.709,15.807,-88.798,15.863,-88.84,15.869,-88.894,15.891,-88.912,15.956,-88.879,16.017,-88.695,16.248,-88.562,16.29,-88.461,16.434,-88.405,16.489,-88.313,16.633,-88.262,16.963,-88.294,17.192,-88.289,17.313,-88.267,17.393,-88.203,17.517,-88.272,17.61,-88.221,17.751,-88.207,17.846,-88.097,18.122,-88.085,18.226,-88.13,18.351,-88.247,18.355,-88.296,18.344,-88.349,18.359,-88.296,18.472,-88.276,18.515,-88.195,18.643,-88.197,18.72,-88.127,18.773,-88.074,18.834,-88.032,18.839,-88.011,18.727,-88.056,18.524,-88.039,18.484,-87.96,18.441,-87.882,18.274,-87.853,18.269,-87.804,18.357,-87.762,18.446,-87.734,18.655,-87.653,18.799,-87.594,19.046,-87.501,19.288,-87.509,19.317,-87.551,19.321,-87.622,19.25,-87.656,19.258,-87.659,19.352,-87.628,19.383,-87.567,19.416,-87.513,19.426,-87.483,19.444,-87.435,19.502,-87.425,19.583,-87.469,19.586,-87.512,19.575,-87.587,19.573,-87.645,19.554,-87.69,19.594,-87.688,19.637,-87.586,19.779,-87.507,19.827,-87.466,19.824,-87.442,19.862,-87.432,19.898,-87.466,19.999,-87.467,20.102,-87.421,20.231,-87.221,20.507,-87.06,20.631,-86.926,20.786,-86.865,20.885,-86.816,21.005,-86.772,21.151,-86.804,21.2,-86.817,21.234,-86.824,21.422,-86.912,21.463,-87.035,21.592,-87.128,21.621,-87.216,21.582,-87.276,21.572,-87.369,21.574,-87.387,21.551,-87.296,21.525,-87.249,21.527,-87.211,21.544,-87.188,21.546,-87.164,21.514,-87.188,21.477,-87.218,21.458,-87.251,21.447,-87.48,21.472,-87.689,21.536,-87.774,21.55,-88.007,21.604,-88.132,21.616,-88.171,21.604,-88.172,21.591,-88.185,21.579,-88.251,21.567,-88.467,21.569,-88.585,21.539,-88.747,21.448,-88.879,21.414,-89.82,21.275,-89.888,21.253,-90.183,21.121,-90.353,21.009,-90.435,20.758,-90.458,20.714,-90.484,20.556,-90.478,20.38,-90.486,20.224,-90.482,20.026,-90.492,19.947,-90.507,19.912,-90.65,19.796,-90.693,19.73,-90.739,19.352,-90.955,19.152,-91.059,19.098,-91.136,19.038,-91.437,18.89,-91.458,18.865,-91.469,18.833,-91.446,18.833,-91.343,18.901,-91.334,18.877,-91.368,18.806,-91.356,18.777,-91.308,18.773,-91.279,18.721,-91.275,18.624,-91.44,18.542,-91.534,18.457,-91.6,18.447,-91.803,18.471,-91.914,18.529,-91.943,18.563,-91.88,18.6,-91.88,18.638,-91.974,18.716,-92.103,18.704,-92.213,18.685,-92.441,18.675,-92.485,18.665,-92.71,18.612,-92.729,18.575,-92.769,18.524,-92.885,18.469,-93.127,18.423,-93.228,18.444,-93.552,18.43,-93.764,18.358,-93.873,18.304,-94.189,18.195,-94.392,18.166,-94.46,18.167,-94.546,18.175,-94.682,18.348,-94.798,18.515,-95.015,18.571,-95.182,18.701,-95.561,18.719,-95.697,18.775,-95.72,18.768,-95.655,18.724,-95.578,18.69,-95.627,18.691,-95.821,18.755,-95.92,18.82,-95.928,18.85,-95.81,18.804,-95.778,18.806,-95.913,18.897,-95.985,19.054,-96.073,19.106,-96.124,19.199,-96.29,19.344,-96.315,19.473,-96.368,19.567,-96.456,19.87,-96.709,20.188,-97.121,20.615,-97.186,20.717,-97.195,20.8,-97.357,21.104,-97.501,21.398,-97.501,21.432,-97.515,21.478,-97.567,21.508,-97.598,21.536,-97.638,21.604,-97.754,22.027,-97.59,21.762,-97.457,21.612,-97.383,21.567,-97.385,21.524,-97.424,21.465,-97.434,21.356,-97.409,21.273,-97.388,21.374,-97.337,21.438,-97.315,21.564,-97.36,21.615,-97.485,21.705,-97.585,21.809,-97.763,22.106,-97.782,22.279,-97.842,22.51,-97.842,22.557,-97.858,22.625,-97.817,22.776,-97.758,22.886,-97.745,22.942,-97.766,23.306,-97.727,23.732,-97.743,23.761,-97.729,23.788,-97.717,23.981,-97.668,24.39,-97.507,25.015,-97.424,25.233,-97.225,25.585,-97.164,25.755,-97.146,25.961,-97.14,26.03,-97.15,26.065,-97.214,26.068,-97.402,26.397,-97.435,26.486,-97.466,26.692,-97.494,26.76,-97.527,26.908,-97.555,26.967,-97.517,27.053,-97.476,27.118,-97.475,27.173,-97.485,27.237,-97.692,27.287,-97.768,27.458,-97.682,27.395,-97.524,27.314,-97.48,27.317,-97.439,27.328,-97.38,27.419,-97.289,27.671,-97.431,27.837,-97.404,27.859,-97.374,27.87,-97.252,27.854,-97.171,27.88,-97.073,27.986,-97.034,28.094,-97.141,28.061,-97.155,28.103,-97.156,28.144,-97.096,28.158,-97.015,28.163,-96.967,28.19,-96.933,28.224,-96.92,28.185,-96.892,28.158,-96.84,28.194,-96.807,28.22,-96.795,28.321,-96.774,28.422,-96.676,28.341,-96.562,28.367,-96.489,28.406,-96.421,28.457,-96.475,28.479,-96.525,28.489,-96.64,28.709,-96.608,28.723,-96.576,28.716,-96.56,28.684,-96.526,28.648,-96.449,28.594,-96.374,28.631,-96.373,28.657,-96.275,28.655,-96.115,28.622,-96.011,28.632,-96.132,28.561,-96.235,28.489,-96.181,28.502,-96.02,28.587,-95.853,28.64,-95.732,28.712,-95.656,28.745,-95.388,28.898,-95.273,28.964,-95.152,29.079,-95.139,29.168,-95.018,29.259,-94.888,29.371,-94.936,29.46,-94.993,29.531,-95.023,29.702,-94.982,29.713,-94.93,29.68,-94.89,29.677,-94.832,29.753,-94.742,29.75,-94.724,29.655,-94.778,29.548,-94.733,29.535,-94.605,29.568,-94.526,29.548,-94.75,29.418,-94.76,29.384,-94.574,29.485,-94.1,29.67,-93.89,29.689,-93.886,29.723,-93.946,29.815,-93.841,29.98,-93.794,29.977,-93.769,29.952,-93.773,29.914,-93.809,29.851,-93.848,29.819,-93.884,29.81,-93.866,29.756,-93.826,29.725,-93.766,29.753,-93.695,29.77,-93.388,29.777,-93.283,29.789,-93.176,29.779,-92.952,29.714,-92.791,29.635,-92.671,29.597,-92.261,29.557,-92.084,29.593,-92.059,29.617,-92.114,29.668,-92.135,29.699,-92.08,29.761,-92.017,29.8,-91.893,29.836,-91.824,29.751,-91.672,29.746,-91.565,29.605,-91.514,29.555,-91.331,29.514,-91.278,29.563,-91.249,29.564,-91.26,29.505,-91.244,29.457,-91.155,29.351,-91.151,29.318,-91.237,29.331,-91.283,29.321,-91.29,29.289,-91.003,29.194,-90.751,29.131,-90.677,29.151,-90.586,29.272,-90.502,29.3,-90.379,29.295,-90.302,29.256,-90.247,29.131,-90.213,29.105,-90.136,29.136,-90.101,29.182,-90.083,29.24,-90.074,29.297,-90.053,29.337,-90.052,29.431,-90.101,29.463,-90.141,29.48,-90.161,29.504,-90.159,29.537,-89.877,29.458,-89.818,29.416,-89.797,29.381,-89.792,29.333,-89.717,29.313,-89.672,29.317,-89.62,29.302,-89.577,29.268,-89.522,29.249,-89.443,29.194,-89.389,29.105,-89.354,29.07,-89.376,28.981,-89.331,28.999,-89.236,29.081,-89.195,29.054,-89.156,29.017,-89.133,29.046,-89.11,29.099,-89.021,29.143,-89.016,29.203,-89.065,29.218,-89.117,29.248,-89.181,29.336,-89.246,29.333,-89.514,29.42,-89.58,29.486,-89.675,29.539,-89.721,29.619,-89.689,29.646,-89.683,29.675,-89.662,29.684,-89.621,29.674,-89.559,29.698,-89.591,29.725,-89.531,29.772,-89.455,29.784,-89.354,29.82,-89.363,29.84,-89.358,29.921,-89.401,29.978,-89.414,30.011,-89.401,30.046,-89.494,30.058,-89.563,30.002,-89.59,29.915,-89.632,29.904,-89.744,29.93,-89.815,30.007,-89.777,30.046,-89.715,30.078,-89.665,30.117,-89.668,30.145,-89.737,30.172,-89.773,30.137,-89.812,30.124,-89.894,30.126,-89.994,30.059,-90.175,30.029,-90.285,30.065,-90.413,30.14,-90.332,30.278,-90.225,30.379,-90.126,30.369,-90.045,30.351,-89.954,30.269,-89.588,30.166,-89.521,30.193,-89.444,30.223,-89.321,30.345,-89.264,30.344,-89.224,30.332,-89.054,30.368,-88.905,30.415,-88.873,30.416,-88.82,30.406,-88.692,30.355,-88.4,30.371,-88.35,30.373,-88.249,30.363,-88.135,30.367,-88.117,30.415,-88.078,30.566,-88.032,30.681,-88.011,30.694,-87.949,30.627,-87.923,30.562,-87.924,30.45,-87.898,30.414,-87.857,30.407,-87.813,30.347,-87.79,30.292,-87.904,30.259,-87.985,30.254,-88.006,30.231,-87.622,30.265,-87.513,30.368,-87.49,30.378,-87.448,30.394,-87.444,30.364,-87.501,30.309,-87.476,30.294,-87.281,30.339,-87.251,30.397,-87.185,30.454,-87.171,30.539,-87.119,30.539,-87.072,30.5,-87.034,30.554,-86.998,30.57,-86.965,30.502,-86.986,30.431,-87.124,30.397,-87.164,30.374,-87.201,30.339,-86.968,30.372,-86.68,30.403,-86.606,30.425,-86.523,30.467,-86.448,30.496,-86.374,30.482,-86.257,30.493,-86.166,30.464,-86.138,30.442,-86.124,30.406,-86.24,30.429,-86.454,30.399,-86.175,30.333,-85.856,30.214,-85.791,30.172,-85.756,30.167,-85.743,30.201,-85.741,30.244,-85.676,30.279,-85.604,30.287,-85.641,30.237,-85.663,30.189,-85.61,30.148,-85.623,30.117,-85.676,30.122,-85.504,29.976,-85.354,29.876,-85.307,29.798,-85.315,29.758,-85.336,29.74,-85.383,29.785,-85.414,29.842,-85.414,29.768,-85.376,29.695,-85.319,29.68,-85.186,29.708,-85.029,29.721,-84.969,29.745,-84.889,29.778,-84.801,29.773,-84.55,29.898,-84.454,29.91,-84.383,29.907,-84.359,29.929,-84.375,29.982,-84.356,30.029,-84.31,30.065,-84.044,30.104,-83.694,29.926,-83.29,29.452,-82.769,29.052,-82.651,28.888,-82.644,28.812,-82.651,28.77,-82.661,28.486,-82.749,28.237,-82.844,27.846,-82.808,27.777,-82.775,27.734,-82.743,27.709,-82.715,27.733,-82.661,27.718,-82.626,27.746,-82.611,27.777,-82.597,27.873,-82.634,27.898,-82.675,27.964,-82.636,27.981,-82.58,27.958,-82.521,27.878,-82.498,27.868,-82.446,27.903,-82.406,27.863,-82.401,27.835,-82.431,27.771,-82.521,27.678,-82.636,27.525,-82.687,27.515,-82.715,27.5,-82.655,27.449,-82.62,27.401,-82.441,27.06,-82.354,26.936,-82.29,26.871,-82.243,26.849,-82.181,26.84,-82.169,26.874,-82.181,26.937,-82.096,26.963,-82.013,26.962,-82.067,26.892,-82.078,26.704,-82.04,26.552,-82.006,26.54,-81.97,26.552,-81.921,26.631,-81.882,26.665,-81.829,26.687,-81.896,26.597,-81.959,26.49,-81.931,26.467,-81.867,26.435,-81.811,26.146,-81.715,25.983,-81.568,25.892,-81.365,25.831,-81.345,25.732,-81.227,25.583,-81.113,25.367,-81.057,25.338,-80.98,25.312,-80.94,25.264,-80.965,25.224,-81.098,25.319,-81.136,25.31,-81.159,25.269,-81.167,25.229,-81.11,25.138,-81.012,25.133,-80.862,25.176,-80.737,25.156,-80.558,25.232,-80.485,25.23,-80.367,25.331,-80.328,25.427,-80.301,25.619,-80.219,25.742,-80.159,25.878,-80.143,25.874,-80.136,25.843,-80.126,25.833,-80.111,26.132,-80.041,26.569,-80.05,26.808,-80.089,26.994,-80.126,27.083,-80.226,27.207,-80.65,28.181,-80.686,28.272,-80.749,28.381,-80.787,28.561,-80.818,28.636,-80.838,28.758,-80.809,28.759,-80.771,28.732,-80.78,28.683,-80.766,28.633,-80.7,28.601,-80.688,28.579,-80.729,28.516,-80.732,28.463,-80.694,28.345,-80.665,28.375,-80.654,28.452,-80.633,28.518,-80.607,28.523,-80.623,28.32,-80.61,28.178,-80.5,27.934,-80.457,27.901,-80.533,28.07,-80.573,28.181,-80.585,28.272,-80.581,28.365,-80.568,28.426,-80.524,28.486,-80.564,28.556,-80.9,29.05,-81.105,29.457,-81.25,29.794,-81.337,30.141,-81.386,30.27,-81.457,30.641,-81.504,30.731,-81.516,30.802,-81.52,30.875,-81.501,30.914,-81.471,31.009,-81.453,31.088,-81.46,31.127,-81.442,31.2,-81.413,31.179,-81.365,31.172,-81.288,31.264,-81.329,31.314,-81.378,31.332,-81.381,31.353,-81.295,31.371,-81.258,31.436,-81.219,31.472,-81.175,31.531,-81.196,31.539,-81.223,31.528,-81.259,31.539,-81.242,31.574,-81.17,31.61,-81.166,31.646,-81.187,31.667,-81.198,31.704,-81.162,31.744,-81.098,31.753,-81.066,31.788,-81.065,31.813,-81.096,31.841,-81.113,31.879,-81.083,31.894,-81.046,31.892,-80.923,31.945,-80.872,32.03,-80.882,32.069,-80.849,32.114,-80.791,32.126,-80.758,32.142,-80.694,32.216,-80.702,32.246,-80.734,32.265,-80.765,32.298,-80.798,32.363,-80.803,32.448,-80.709,32.337,-80.683,32.349,-80.678,32.381,-80.647,32.396,-80.626,32.326,-80.608,32.293,-80.579,32.287,-80.514,32.324,-80.486,32.352,-80.474,32.423,-80.53,32.475,-80.634,32.512,-80.572,32.534,-80.461,32.521,-80.363,32.501,-80.268,32.537,-80.23,32.577,-80.18,32.593,-80.123,32.589,-80.022,32.62,-79.941,32.667,-79.894,32.729,-79.933,32.81,-79.805,32.787,-79.735,32.825,-79.615,32.909,-79.587,33.001,-79.499,33.027,-79.42,33.043,-79.276,33.135,-79.229,33.185,-79.281,33.315,-79.226,33.405,-79.227,33.363,-79.238,33.312,-79.194,33.244,-79.138,33.406,-78.92,33.659,-78.841,33.724,-78.578,33.873,-78.564,33.877,-78.406,33.918,-78.013,33.912,-77.971,33.993,-77.953,34.169,-77.926,34.073,-77.933,33.989,-77.928,33.94,-77.888,34.05,-77.861,34.149,-77.751,34.285,-77.697,34.332,-77.65,34.358,-77.518,34.451,-77.38,34.527,-77.402,34.555,-77.413,34.592,-77.412,34.731,-77.384,34.694,-77.358,34.62,-77.296,34.603,-77.252,34.616,-77.134,34.708,-77.05,34.697,-76.896,34.701,-76.797,34.704,-76.733,34.707,-76.707,34.752,-76.618,34.77,-76.517,34.777,-76.44,34.843,-76.362,34.937,-76.457,34.989,-76.745,34.941,-76.899,34.97,-76.975,35.025,-77.07,35.155,-76.861,35.005,-76.779,34.99,-76.628,35.073,-76.613,35.104,-76.608,35.153,-76.566,35.215,-76.513,35.27,-76.553,35.306,-76.595,35.33,-76.974,35.458,-77.04,35.527,-76.887,35.463,-76.741,35.431,-76.634,35.453,-76.611,35.53,-76.577,35.532,-76.532,35.508,-76.516,35.436,-76.49,35.397,-76.447,35.408,-76.39,35.401,-76.275,35.369,-76.174,35.354,-76.104,35.38,-75.966,35.508,-75.774,35.647,-75.745,35.765,-75.759,35.843,-75.772,35.9,-75.812,35.956,-75.854,35.96,-75.979,35.896,-76.001,35.722,-76.046,35.691,-76.084,35.691,-76.076,35.788,-76.06,35.879,-76.07,35.97,-76.207,35.991,-76.264,35.967,-76.358,35.953,-76.504,35.956,-76.611,35.944,-76.726,35.958,-76.719,36.033,-76.74,36.133,-76.734,36.229,-76.718,36.148,-76.679,36.075,-76.559,36.015,-76.479,36.028,-76.424,36.068,-76.384,36.134,-76.321,36.138,-76.227,36.116,-76.271,36.19,-76.222,36.167,-76.15,36.146,-76.141,36.215,-76.148,36.279,-76.055,36.235,-75.95,36.209,-75.883,36.176,-75.82,36.113,-75.867,36.268,-75.925,36.383,-75.978,36.429,-75.993,36.474,-75.966,36.551,-75.96,36.571,-75.973,36.6,-75.965,36.638,-75.946,36.659,-75.918,36.633,-75.894,36.567,-75.889,36.55,-75.81,36.271,-75.728,36.104,-75.58,35.872,-75.534,35.819,-75.559,35.879,-75.758,36.229,-75.857,36.551,-75.89,36.657,-75.942,36.766,-75.966,36.862,-75.999,36.913,-76.144,36.931,-76.244,36.953,-76.4,36.89,-76.488,36.897,-76.505,36.961,-76.634,37.047,-76.672,37.173,-76.765,37.184,-76.925,37.225,-77.002,37.271,-77.196,37.296,-77.227,37.309,-77.251,37.329,-77.007,37.318,-76.704,37.218,-76.631,37.222,-76.602,37.143,-76.507,37.072,-76.462,37.031,-76.401,36.991,-76.338,37.013,-76.283,37.053,-76.301,37.111,-76.327,37.149,-76.401,37.213,-76.497,37.247,-76.611,37.323,-76.738,37.449,-76.756,37.479,-76.758,37.505,-76.538,37.309,-76.454,37.274,-76.393,37.3,-76.405,37.332,-76.401,37.386,-76.263,37.357,-76.254,37.431,-76.269,37.495,-76.368,37.53,-76.306,37.571,-76.484,37.629,-76.549,37.669,-76.62,37.755,-76.715,37.81,-76.849,37.94,-76.925,38.033,-77.111,38.166,-77.071,38.167,-76.94,38.095,-76.829,37.962,-76.793,37.938,-76.492,37.682,-76.437,37.67,-76.344,37.676,-76.306,37.722,-76.293,37.794,-76.262,37.848,-76.264,37.894,-76.355,37.963,-76.472,38.011,-76.55,38.094,-76.645,38.134,-76.906,38.197,-77.047,38.357,-77.11,38.37,-77.232,38.34,-77.273,38.352,-77.314,38.397,-77.284,38.529,-77.26,38.6,-77.165,38.677,-77.092,38.72,-77.046,38.776,-77.03,38.889,-77.018,38.778,-77.054,38.706,-77.135,38.65,-77.221,38.541,-77.242,38.495,-77.233,38.408,-77.156,38.397,-77.077,38.442,-77.001,38.445,-76.988,38.394,-76.95,38.347,-76.89,38.292,-76.868,38.337,-76.868,38.39,-76.769,38.263,-76.594,38.228,-76.454,38.174,-76.402,38.125,-76.341,38.087,-76.333,38.141,-76.366,38.197,-76.409,38.268,-76.642,38.454,-76.669,38.538,-76.677,38.612,-76.659,38.58,-76.647,38.539,-76.572,38.436,-76.51,38.404,-76.439,38.362,-76.394,38.369,-76.416,38.42,-76.458,38.475,-76.501,38.532,-76.537,38.743,-76.521,38.788,-76.516,38.841,-76.52,38.898,-76.494,38.945,-76.519,39.001,-76.559,39.065,-76.546,39.068,-76.473,39.031,-76.42,39.074,-76.428,39.126,-76.489,39.159,-76.574,39.254,-76.57,39.269,-76.421,39.225,-76.403,39.253,-76.406,39.304,-76.359,39.325,-76.345,39.365,-76.347,39.388,-76.331,39.404,-76.276,39.323,-76.257,39.352,-76.248,39.439,-76.223,39.42,-76.216,39.38,-76.141,39.403,-76.097,39.433,-76.081,39.47,-76.085,39.527,-76.063,39.561,-76.006,39.569,-75.959,39.585,-75.97,39.505,-75.873,39.511,-75.913,39.468,-75.955,39.46,-76.003,39.411,-75.939,39.399,-75.876,39.376,-75.976,39.367,-76.074,39.369,-76.153,39.315,-76.236,39.192,-76.217,39.064,-76.133,39.123,-76.135,39.082,-76.186,38.991,-76.245,39.009,-76.313,39.009,-76.33,38.953,-76.331,38.909,-76.241,38.943,-76.191,38.916,-76.168,38.853,-76.247,38.823,-76.3,38.818,-76.341,38.71,-76.308,38.723,-76.278,38.772,-76.213,38.758,-76.175,38.707,-76.057,38.621,-76.017,38.625,-76.001,38.602,-76.113,38.602,-76.198,38.619,-76.264,38.6,-76.295,38.495,-76.265,38.436,-76.212,38.361,-76.117,38.318,-76.051,38.28,-76.02,38.295,-76.007,38.323,-75.986,38.332,-75.967,38.291,-75.937,38.31,-75.889,38.356,-75.859,38.362,-75.877,38.319,-75.864,38.261,-75.885,38.214,-75.928,38.169,-75.891,38.147,-75.856,38.14,-75.795,38.087,-75.829,38.033,-75.851,37.972,-75.735,37.974,-75.659,37.954,-75.719,37.821,-75.792,37.756,-75.888,37.619,-75.975,37.398,-75.997,37.264,-75.985,37.212,-75.934,37.152,-75.854,37.297,-75.812,37.425,-75.767,37.473,-75.699,37.516,-75.632,37.535,-75.587,37.559,-75.596,37.631,-75.376,38.025,-75.354,38.065,-75.292,38.129,-75.225,38.242,-75.16,38.255,-75.142,38.298,-75.134,38.384,-75.117,38.406,-75.09,38.425,-75.073,38.41,-75.074,38.366,-75.051,38.383,-75.039,38.426,-75.038,38.456,-75.036,38.503,-75.073,38.579,-75.111,38.599,-75.187,38.591,-75.128,38.632,-75.084,38.723,-75.089,38.778,-75.185,38.819,-75.31,38.967,-75.392,39.093,-75.413,39.281,-75.52,39.403,-75.574,39.477,-75.567,39.553,-75.582,39.589,-75.588,39.641,-75.502,39.717,-75.464,39.781,-75.421,39.815,-75.401,39.832,-75.321,39.865,-75.173,39.895,-75.074,39.983,-75.104,39.932,-75.154,39.871,-75.353,39.83,-75.422,39.79,-75.472,39.712,-75.524,39.602,-75.519,39.532,-75.524,39.49,-75.353,39.34,-75.231,39.284,-75.136,39.208,-75.05,39.211,-74.975,39.188,-74.897,39.145,-74.92,39.047,-74.954,38.95,-74.923,38.941,-74.794,39.002,-74.646,39.208,-74.605,39.248,-74.603,39.293,-74.579,39.316,-74.517,39.347,-74.474,39.343,-74.429,39.387,-74.411,39.455,-74.39,39.487,-74.407,39.549,-74.331,39.536,-74.257,39.614,-74.176,39.727,-74.118,39.938,-74.096,39.976,-74.065,39.993,-74.084,39.829,-74.08,39.788,-74.049,39.923,-74.028,40.073,-74.004,40.171,-73.972,40.251,-73.958,40.328,-73.972,40.4,-73.998,40.452,-74.05,40.43,-74.242,40.456,-74.264,40.529,-74.227,40.608,-74.187,40.648,-74.153,40.673,-74.116,40.687,-74.067,40.72,-74.025,40.756,-73.927,40.914,-73.91,40.992,-73.909,40.996,-73.918,41.136,-73.97,41.25,-73.925,41.218,-73.882,41.171,-73.872,41.055,-73.907,40.912,-73.949,40.839,-73.987,40.751,-73.947,40.777,-73.911,40.816,-73.851,40.831,-73.779,40.878,-73.671,40.966,-73.63,40.992,-73.583,41.022,-73.182,41.176,-73.024,41.216,-72.925,41.285,-72.847,41.266,-72.479,41.276,-72.371,41.312,-72.265,41.292,-72.074,41.326,-71.93,41.341,-71.842,41.335,-71.769,41.331,-71.523,41.379,-71.444,41.454,-71.427,41.633,-71.364,41.703,-71.39,41.795,-71.359,41.786,-71.331,41.762,-71.311,41.72,-71.271,41.681,-71.234,41.707,-71.178,41.744,-71.149,41.746,-71.204,41.641,-71.188,41.516,-71.169,41.489,-71.08,41.538,-70.974,41.549,-70.701,41.715,-70.666,41.71,-70.655,41.608,-70.668,41.558,-70.657,41.534,-70.481,41.582,-70.405,41.627,-70.06,41.677,-69.987,41.684,-69.949,41.677,-69.934,41.71,-69.942,41.808,-69.978,41.961,-70.109,42.078,-70.16,42.097,-70.204,42.101,-70.241,42.091,-70.237,42.071,-70.196,42.035,-70.173,42.063,-70.11,42.03,-70.09,41.98,-70.006,41.872,-70.001,41.826,-70.135,41.77,-70.295,41.729,-70.427,41.757,-70.515,41.803,-70.549,41.939,-70.656,41.987,-70.645,42.022,-70.618,42.04,-70.738,42.229,-70.818,42.265,-70.997,42.3,-71.046,42.331,-70.93,42.432,-70.871,42.497,-70.831,42.553,-70.752,42.57,-70.661,42.617,-70.613,42.623,-70.604,42.65,-70.624,42.672,-70.655,42.674,-70.697,42.665,-70.736,42.669,-70.781,42.721,-70.8,42.774,-70.829,42.825,-70.806,42.877,-70.778,42.941,-70.733,43.07,-70.691,43.109,-70.642,43.134,-70.521,43.349,-70.36,43.48,-70.203,43.626,-70.238,43.656,-70.269,43.672,-70.179,43.766,-70.062,43.835,-69.965,43.855,-69.975,43.818,-69.974,43.788,-69.926,43.797,-69.873,43.82,-69.84,43.79,-69.808,43.772,-69.792,43.805,-69.803,43.867,-69.795,43.911,-69.772,43.899,-69.762,43.861,-69.73,43.852,-69.699,43.955,-69.653,43.994,-69.637,43.949,-69.624,43.881,-69.59,43.887,-69.557,43.983,-69.542,43.963,-69.521,43.897,-69.481,43.905,-69.435,43.956,-69.345,44.001,-69.226,43.986,-69.137,44.038,-69.068,44.098,-69.064,44.172,-68.956,44.348,-68.961,44.434,-68.847,44.485,-68.8,44.549,-68.763,44.571,-68.766,44.51,-68.795,44.454,-68.777,44.446,-68.736,44.454,-68.71,44.443,-68.794,44.382,-68.812,44.339,-68.723,44.342,-68.612,44.311,-68.572,44.271,-68.533,44.259,-68.514,44.304,-68.521,44.38,-68.479,44.446,-68.451,44.508,-68.417,44.469,-68.374,44.445,-68.317,44.474,-68.277,44.507,-68.246,44.515,-68.198,44.515,-68.152,44.502,-68.117,44.491,-68.094,44.439,-68.057,44.384,-68.014,44.401,-67.985,44.42,-67.963,44.464,-67.907,44.454,-67.839,44.576,-67.79,44.586,-67.727,44.567,-67.653,44.562,-67.599,44.577,-67.556,44.645,-67.458,44.657,-67.364,44.697,-67.191,44.676,-66.987,44.828,-66.991,44.85,-67.014,44.868,-67.107,44.885,-67.114,44.944,-67.08,44.989,-67.102,45.088,-67.13,45.139,-67.125,45.169,-67.084,45.144,-66.977,45.157,-66.919,45.146,-66.908,45.098,-66.872,45.067,-66.707,45.083,-66.511,45.143,-66.44,45.096,-66.352,45.133,-66.252,45.189,-66.144,45.228,-66.107,45.257,-66.183,45.335,-66.09,45.376,-66.065,45.401,-66.027,45.418,-66.067,45.359,-66.11,45.317,-65.956,45.222,-65.884,45.223,-65.545,45.337,-65.282,45.473,-65.057,45.544,-64.898,45.626,-64.779,45.638,-64.594,45.814,-64.642,45.913,-64.633,45.947,-64.536,45.867,-64.482,45.806,-64.404,45.827,-64.315,45.836,-64.351,45.783,-64.397,45.756,-64.56,45.625,-64.827,45.476,-64.913,45.375,-64.873,45.355,-64.832,45.35,-64.747,45.324,-64.681,45.383,-64.6,45.41,-64.336,45.39,-64.087,45.411,-63.906,45.378,-63.614,45.394,-63.368,45.365,-63.46,45.321,-63.748,45.311,-64.093,45.217,-64.183,45.147,-64.135,45.023,-64.235,45.114,-64.354,45.138,-64.366,45.187,-64.359,45.238,-64.34,45.268,-64.331,45.309,-64.448,45.337,-64.407,45.306,-64.449,45.256,-64.751,45.18,-64.903,45.121,-65.657,44.76,-65.692,44.738,-65.728,44.697,-65.587,44.729,-65.502,44.76,-65.52,44.733,-65.616,44.68,-65.682,44.651,-65.778,44.646,-65.917,44.615,-66.022,44.562,-66.091,44.505,-66.125,44.47,-66.146,44.436,-65.942,44.576,-65.868,44.569,-66.1,44.367,-66.193,44.144,-66.193,44.08,-66.126,43.814,-66.038,43.742,-66.002,43.778,-65.978,43.815,-65.887,43.795,-65.835,43.734,-65.738,43.561,-65.662,43.534,-65.564,43.553,-65.482,43.518,-65.45,43.524,-65.429,43.561,-65.386,43.565,-65.344,43.55,-65.33,43.668,-65.235,43.727,-65.172,43.731,-65.087,43.727,-64.862,43.868,-64.826,43.929,-64.692,44.021,-64.578,44.142,-64.469,44.185,-64.378,44.304,-64.335,44.292,-64.276,44.334,-64.312,44.415,-64.339,44.445,-64.286,44.55,-64.167,44.587,-64.101,44.487,-64.045,44.545,-64.045,44.588,-64,44.645,-63.924,44.604,-63.891,44.546,-63.821,44.511,-63.761,44.486,-63.61,44.48,-63.568,44.514,-63.545,44.544,-63.558,44.611,-63.604,44.683,-63.544,44.655,-63.457,44.64,-63.381,44.652,-63.306,44.643,-63.156,44.711,-63.089,44.709,-63.032,44.715,-62.768,44.785,-62.514,44.844,-62.265,44.936,-62.027,44.994,-61.794,45.084,-61.719,45.094,-61.647,45.131,-61.569,45.154,-61.498,45.157,-61.387,45.185,-61.284,45.235,-61.165,45.256,-61.101,45.233,-61.068,45.253,-61.032,45.292,-61.071,45.33,-61.107,45.349,-61.461,45.367,-61.376,45.411,-61.282,45.441,-61.277,45.476,-61.35,45.574,-61.428,45.648,-61.492,45.687,-61.657,45.642,-61.777,45.656,-61.877,45.714,-61.912,45.799,-61.924,45.851,-61.956,45.868,-62.218,45.731,-62.422,45.665,-62.447,45.641,-62.483,45.622,-62.586,45.661,-62.75,45.648,-62.718,45.686,-62.701,45.741,-62.911,45.776,-63.108,45.782,-63.217,45.758,-63.293,45.752,-63.316,45.78,-63.358,45.811,-63.509,45.875,-63.568,45.878,-63.703,45.858,-63.875,45.959,-64.031,46.013,-64.056,46.021,-63.832,46.107,-63.873,46.146,-63.916,46.166,-64.145,46.193,-64.212,46.22,-64.542,46.24,-64.557,46.311,-64.648,46.356,-64.641,46.426,-64.69,46.512,-64.726,46.671,-64.817,46.699,-64.883,46.823,-64.906,46.888,-64.866,46.958,-64.831,47.061,-64.942,47.086,-65.042,47.089,-65.192,47.05,-65.26,47.069,-65.319,47.101,-65.086,47.234,-64.912,47.369,-64.852,47.57,-64.766,47.673,-64.703,47.725,-64.874,47.797,-65.046,47.793,-65.002,47.847,-65.228,47.811,-65.344,47.768,-65.483,47.687,-65.607,47.67,-65.666,47.696,-65.756,47.86,-65.849,47.911,-66.21,47.989,-66.36,48.061,-66.429,48.067,-66.632,48.011,-66.704,48.022,-66.449,48.12,-66.324,48.098,-66.249,48.117,-66.083,48.103,-66.013,48.147,-65.927,48.189,-65.755,48.112,-65.476,48.031,-65.36,48.011,-65.259,48.021,-65.036,48.106,-64.96,48.16,-64.822,48.196,-64.765,48.228,-64.706,48.311,-64.633,48.36,-64.349,48.423,-64.254,48.55,-64.246,48.691,-64.415,48.804,-64.514,48.841,-64.371,48.839,-64.209,48.806,-64.216,48.874,-64.262,48.922,-64.568,49.105,-64.836,49.192,-65.396,49.262,-65.523,49.266,-65.883,49.226,-66.178,49.213,-66.598,49.126,-67.117,48.964,-67.561,48.856,-67.889,48.731,-68.238,48.626,-68.431,48.542,-68.552,48.457,-68.746,48.376,-68.816,48.366,-68.987,48.275,-69.306,48.047,-69.471,47.967,-69.581,47.824,-69.802,47.623,-70.017,47.471,-70.07,47.378,-70.218,47.29,-70.388,47.117,-70.519,47.033,-70.993,46.852,-71.152,46.819,-71.261,46.756,-71.439,46.721,-71.671,46.654,-71.901,46.632,-72.109,46.551,-72.187,46.512,-72.24,46.442,-72.366,46.405,-72.496,46.353,-72.733,46.182,-72.99,46.104,-73.16,46.01,-73.253,45.864,-73.369,45.758,-73.465,45.632,-73.484,45.587,-73.519,45.459,-73.558,45.425,-73.765,45.395,-74.05,45.241,-74.269,45.188,-74.566,45.042,-74.709,45.004,-74.358,45.206,-74.34,45.214,-74.098,45.324,-73.974,45.345,-74,45.433,-74.248,45.493,-74.315,45.531,-74.038,45.502,-73.897,45.564,-73.798,45.655,-73.712,45.711,-73.477,45.738,-73.284,45.9,-73.18,46.025,-73.145,46.066,-73.022,46.12,-72.981,46.21,-72.843,46.262,-72.68,46.287,-72.257,46.485,-72.205,46.559,-72.028,46.607,-71.88,46.687,-71.757,46.674,-71.625,46.698,-71.268,46.796,-71.116,46.925,-70.973,47.007,-70.706,47.14,-70.448,47.423,-70.3,47.503,-69.994,47.74,-69.906,47.832,-69.84,47.953,-69.775,48.098,-69.866,48.172,-69.971,48.206,-70.145,48.244,-70.501,48.354,-70.671,48.353,-70.839,48.367,-70.923,48.422,-71.018,48.456,-70.384,48.367,-70.111,48.278,-70.001,48.271,-69.852,48.207,-69.762,48.191,-69.674,48.199,-69.55,48.251,-69.375,48.386,-69.231,48.574,-68.929,48.829,-68.669,48.94,-68.628,49.007,-68.544,49.056,-68.414,49.1,-68.295,49.114,-68.221,49.15,-68.282,49.197,-68.056,49.257,-67.549,49.332,-67.469,49.335,-67.372,49.348,-67.262,49.451,-67.234,49.602,-66.941,49.994,-66.741,50.066,-66.622,50.155,-66.55,50.161,-66.496,50.212,-66.411,50.224,-66.369,50.207,-66.242,50.22,-66.126,50.201,-65.955,50.294,-65.762,50.259,-65.269,50.32,-65.181,50.298,-64.868,50.275,-64.509,50.309,-64.17,50.269,-64.016,50.304,-63.854,50.314,-63.734,50.305,-63.587,50.258,-63.239,50.243,-63.136,50.294,-62.95,50.291,-62.83,50.301,-62.715,50.302,-62.541,50.285,-62.362,50.277,-62.165,50.239,-61.92,50.233,-61.835,50.197,-61.725,50.104,-61.29,50.202,-61.181,50.192,-60.956,50.205,-60.807,50.25,-60.608,50.221,-60.438,50.239,-60.08,50.255,-59.886,50.316,-59.815,50.418,-59.612,50.492,-59.378,50.675,-59.165,50.78,-59.055,50.879,-58.638,51.172,-58.615,51.237,-58.593,51.257,-58.51,51.295,-58.442,51.306,-58.27,51.295,-58.089,51.311,-58.023,51.322,-57.854,51.4,-57.77,51.426,-57.462,51.469,-57.299,51.478,-57.1,51.443,-57.096,51.443,-57.018,51.447,-56.976,51.458,-56.549,51.681,-56.283,51.797,-56.017,51.929,-55.695,52.138,-55.673,52.19,-55.691,52.242,-55.783,52.28,-55.834,52.31,-56.005,52.37,-56.012,52.394,-55.897,52.37,-55.777,52.364,-55.716,52.392,-55.706,52.428,-55.746,52.475,-55.84,52.508,-56.053,52.537,-56.228,52.536,-56.325,52.545,-56.292,52.574,-56.167,52.575,-55.848,52.623,-55.803,52.643,-55.819,52.677,-55.873,52.736,-55.858,52.823,-55.83,52.878,-55.892,53,-55.808,53.135,-55.798,53.212,-55.817,53.246,-55.855,53.286,-55.863,53.312,-55.859,53.344,-55.911,53.391,-55.966,53.471,-56.11,53.588,-56.27,53.6,-56.354,53.624,-56.444,53.718,-56.465,53.765,-56.524,53.766,-56.697,53.758,-56.841,53.739,-57.012,53.673,-57.221,53.529,-57.332,53.469,-57.386,53.561,-57.42,53.583,-57.527,53.6,-57.524,53.611,-57.489,53.633,-57.244,53.715,-57.157,53.757,-57.135,53.792,-57.149,53.848,-57.199,53.924,-57.416,54.163,-57.615,54.191,-58.192,54.228,-58.31,54.202,-58.356,54.172,-58.361,54.154,-58.317,54.114,-58.177,54.131,-58.065,54.127,-57.928,54.104,-57.936,54.091,-58.088,54.09,-58.327,54.052,-58.652,53.978,-58.92,53.875,-59.129,53.744,-59.322,53.644,-59.498,53.575,-59.621,53.537,-59.829,53.505,-59.882,53.48,-59.987,53.393,-60.148,53.307,-60.329,53.266,-60.337,53.277,-60.346,53.289,-60.273,53.317,-60.251,53.344,-60.306,53.36,-60.29,53.391,-60.157,53.45,-60.1,53.487,-60.16,53.53,-60.37,53.607,-60.395,53.653,-60.263,53.61,-60.145,53.596,-60.117,53.61,-60.1,53.634,-60.081,53.701,-60.057,53.733,-60.014,53.762,-59.873,53.808,-59.823,53.834,-59.749,53.842,-59.653,53.831,-59.497,53.834,-59.201,53.929,-59.039,53.964,-59.013,53.976,-58.978,54.01,-58.92,54.033,-58.841,54.044,-58.719,54.039,-58.633,54.05,-58.558,54.103,-58.435,54.228,-58.359,54.253,-58.22,54.286,-58.162,54.32,-58.151,54.35,-57.889,54.384,-57.699,54.387,-57.563,54.44,-57.485,54.517,-57.404,54.57,-57.404,54.591,-57.483,54.64,-57.627,54.65,-57.725,54.674,-57.827,54.719,-57.929,54.773,-57.962,54.876,-58.058,54.882,-58.195,54.866,-58.223,54.813,-58.398,54.774,-58.5,54.783,-58.78,54.838,-58.886,54.952,-58.956,55.055,-58.997,55.149,-59.086,55.183,-59.26,55.2,-59.324,55.153,-59.394,55.081,-59.429,55.056,-59.75,54.887,-59.838,54.814,-59.816,54.867,-59.742,54.943,-59.486,55.13,-59.438,55.176,-59.518,55.197,-59.605,55.173,-59.689,55.196,-59.696,55.269,-59.759,55.31,-59.862,55.295,-59.93,55.259,-60.213,55.236,-60.341,55.194,-60.557,55.067,-60.617,55.06,-60.521,55.129,-60.45,55.2,-60.433,55.243,-60.361,55.366,-60.224,55.444,-60.192,55.481,-60.308,55.557,-60.352,55.612,-60.408,55.65,-60.365,55.709,-60.341,55.785,-60.413,55.789,-60.476,55.805,-60.562,55.727,-60.593,55.815,-60.631,55.825,-60.737,55.887,-60.743,55.941,-60.832,55.958,-60.893,55.914,-60.996,55.862,-61.089,55.866,-61.123,55.889,-61.134,55.93,-61.188,55.955,-61.351,55.974,-61.45,55.996,-61.449,56.022,-61.301,56.047,-61.324,56.076,-61.365,56.216,-61.421,56.222,-61.559,56.208,-61.713,56.231,-61.707,56.289,-61.499,56.328,-61.425,56.361,-61.515,56.39,-61.692,56.397,-61.94,56.424,-62.01,56.454,-61.899,56.505,-61.76,56.511,-61.738,56.526,-61.813,56.571,-61.855,56.584,-61.992,56.591,-62.117,56.667,-62.396,56.73,-62.497,56.802,-62.46,56.818,-62.372,56.836,-62.296,56.833,-62.382,56.788,-62.366,56.767,-62.062,56.699,-61.532,56.655,-61.372,56.681,-61.373,56.776,-61.39,56.853,-61.346,56.922,-61.334,57.011,-61.629,57.183,-61.716,57.196,-61.798,57.186,-61.861,57.198,-61.945,57.228,-61.977,57.248,-61.939,57.274,-61.886,57.348,-61.85,57.37,-61.851,57.381,-61.921,57.421,-62.088,57.453,-62.194,57.455,-62.303,57.441,-62.396,57.448,-62.455,57.462,-62.496,57.489,-62.377,57.478,-62.339,57.485,-62.254,57.529,-62.167,57.537,-62.084,57.562,-61.968,57.612,-61.931,57.669,-61.995,57.769,-61.968,57.803,-61.914,57.825,-61.899,57.861,-61.959,57.912,-62.117,57.964,-62.202,57.955,-62.306,57.972,-62.486,58.154,-62.588,58.158,-62.818,58.129,-62.981,58.093,-63.22,58.002,-63.262,58.015,-63.152,58.084,-63.063,58.127,-62.812,58.2,-62.674,58.319,-62.594,58.474,-62.608,58.496,-62.737,58.492,-62.837,58.479,-63.076,58.415,-63.132,58.411,-63.12,58.442,-63.146,58.46,-63.21,58.467,-63.296,58.441,-63.474,58.331,-63.537,58.33,-63.438,58.399,-63.39,58.453,-63.219,58.52,-63.102,58.546,-62.874,58.672,-62.926,58.765,-63.008,58.855,-63.05,58.878,-63.185,58.858,-63.282,58.867,-63.222,58.911,-63.216,58.928,-63.279,59.003,-63.31,59.026,-63.304,59.034,-63.223,59.057,-63.248,59.068,-63.326,59.082,-63.399,59.08,-63.568,59.047,-63.794,59.027,-63.941,59.027,-63.971,59.054,-63.91,59.066,-63.756,59.063,-63.646,59.079,-63.506,59.115,-63.415,59.194,-63.54,59.333,-63.637,59.341,-63.752,59.277,-63.776,59.277,-63.759,59.319,-63.781,59.349,-63.945,59.38,-63.971,59.409,-63.85,59.448,-63.75,59.513,-63.841,59.574,-63.929,59.645,-63.969,59.698,-63.979,59.754,-64.056,59.823,-64.151,59.794,-64.226,59.741,-64.169,59.847,-64.183,59.973,-64.283,60.064,-64.408,60.065,-64.559,60.043,-64.733,59.998,-64.768,60.012,-64.713,60.037,-64.528,60.095,-64.42,60.171,-64.436,60.228,-64.499,60.268,-64.614,60.306,-64.706,60.336,-64.817,60.331,-64.845,60.308,-64.89,60.287,-64.931,60.252,-65.073,60.062,-65.105,59.993,-65.172,59.908,-65.181,59.867,-65.159,59.83,-65.113,59.802,-65.028,59.771,-65.054,59.753,-65.212,59.81,-65.289,59.818,-65.358,59.809,-65.406,59.795,-65.433,59.777,-65.481,59.69,-65.486,59.649,-65.475,59.617,-65.407,59.539,-65.346,59.511,-65.263,59.495,-65.171,59.462,-65.069,59.411,-65.038,59.388,-65.074,59.378,-65.274,59.464,-65.35,59.479,-65.475,59.47,-65.489,59.448,-65.407,59.33,-65.412,59.315,-65.513,59.35,-65.545,59.32,-65.578,59.245,-65.607,59.213,-65.661,59.23,-65.692,59.229,-65.7,59.213,-65.666,59.153,-65.64,59.128,-65.606,59.111,-65.496,59.091,-65.384,59.06,-65.396,59.038,-65.526,59.036,-65.544,59.012,-65.695,59.032,-65.721,59.024,-65.722,59.003,-65.704,58.971,-65.795,58.98,-65.841,58.977,-65.921,58.915,-65.918,58.896,-65.836,58.86,-65.855,58.847,-65.967,58.839,-66.043,58.821,-66.049,58.788,-66.021,58.735,-65.95,58.65,-65.928,58.611,-65.923,58.572,-65.931,58.535,-66.002,58.431,-66.017,58.431,-66.03,58.567,-66.045,58.606,-66.091,58.659,-66.168,58.727,-66.237,58.772,-66.299,58.795,-66.362,58.791,-66.48,58.731,-66.515,58.697,-66.558,58.637,-66.608,58.549,-66.722,58.491,-66.9,58.463,-67.019,58.433,-67.163,58.37,-67.382,58.3,-67.57,58.213,-67.596,58.186,-67.617,58.14,-67.632,58.076,-67.678,57.991,-67.698,58.009,-67.681,58.107,-67.688,58.14,-67.69,58.244,-67.737,58.385,-67.756,58.405,-67.805,58.365,-67.823,58.31,-67.856,58.273,-68.009,58.152,-68.064,58.139,-67.911,58.267,-67.888,58.296,-67.888,58.329,-67.981,58.461,-68.021,58.485,-68.111,58.473,-68.176,58.403,-68.289,58.178,-68.352,58.091,-68.414,58.052,-68.495,58.012,-68.781,57.976,-69.041,57.902,-69.035,57.926,-68.945,57.969,-68.826,58,-68.597,58.037,-68.468,58.076,-68.357,58.163,-68.315,58.227,-68.234,58.399,-68.229,58.485,-68.235,58.528,-68.253,58.557,-68.326,58.595,-68.381,58.744,-68.414,58.783,-68.475,58.823,-68.563,58.866,-68.637,58.893,-68.698,58.905,-68.942,58.889,-69.064,58.898,-69.173,58.897,-69.271,58.884,-69.382,58.851,-69.651,58.728,-69.79,58.689,-69.879,58.697,-70.033,58.745,-70.154,58.761,-70.16,58.789,-69.979,58.816,-69.868,58.856,-69.842,58.881,-69.829,58.905,-69.829,58.929,-69.814,58.946,-69.784,58.956,-69.753,58.94,-69.677,58.831,-69.648,58.821,-69.608,58.829,-69.532,58.869,-69.5,58.921,-69.475,59,-69.448,59.049,-69.42,59.068,-69.414,59.087,-69.46,59.152,-69.45,59.18,-69.35,59.277,-69.344,59.303,-69.4,59.338,-69.682,59.342,-69.711,59.393,-69.692,59.488,-69.656,59.565,-69.602,59.623,-69.579,59.675,-69.587,59.722,-69.63,59.822,-69.673,59.871,-69.734,59.918,-69.806,59.945,-70.327,59.971,-70.467,59.971,-70.62,59.984,-70.655,60.026,-70.509,60.015,-69.963,60.018,-69.796,60.03,-69.674,60.076,-69.63,60.122,-69.623,60.145,-69.629,60.199,-69.633,60.22,-69.708,60.286,-69.741,60.332,-69.756,60.389,-69.759,60.44,-69.751,60.487,-69.721,60.567,-69.64,60.69,-69.574,60.743,-69.49,60.78,-69.433,60.814,-69.405,60.847,-69.398,60.883,-69.414,60.922,-69.472,61.011,-69.503,61.04,-69.557,61.06,-69.624,61.05,-69.65,61.014,-69.678,60.95,-69.708,60.915,-69.8,60.907,-69.909,60.86,-69.992,60.856,-70.095,60.88,-70.146,60.922,-70.144,60.981,-70.158,61.021,-70.187,61.041,-70.279,61.069,-70.384,61.064,-70.541,61.042,-70.723,61.055,-71.035,61.126,-71.175,61.147,-71.348,61.149,-71.423,61.159,-71.552,61.213,-71.743,61.337,-71.732,61.372,-71.646,61.399,-71.645,61.413,-71.794,61.421,-71.854,61.44,-71.841,61.466,-71.756,61.527,-71.656,61.551,-71.619,61.573,-71.605,61.592,-71.638,61.617,-71.866,61.689,-71.922,61.677,-71.964,61.636,-72.023,61.612,-72.216,61.587,-72.247,61.602,-72.082,61.641,-72.043,61.665,-72.04,61.68,-72.081,61.728,-72.126,61.753,-72.178,61.802,-72.226,61.832,-72.361,61.888,-72.506,61.923,-72.574,61.907,-72.661,61.863,-72.727,61.839,-72.772,61.84,-72.666,61.955,-72.632,62.027,-72.633,62.053,-72.646,62.077,-72.671,62.114,-72.687,62.125,-72.735,62.131,-72.882,62.125,-72.992,62.18,-73.049,62.198,-73.195,62.279,-73.299,62.325,-73.428,62.369,-73.63,62.454,-73.705,62.473,-73.764,62.469,-73.878,62.434,-74.046,62.37,-74.205,62.321,-74.429,62.272,-74.646,62.211,-74.69,62.183,-74.613,62.125,-74.633,62.116,-74.908,62.23,-75.023,62.264,-75.114,62.271,-75.341,62.312,-75.409,62.307,-75.489,62.286,-75.79,62.18,-75.809,62.193,-75.676,62.25,-75.817,62.316,-76.616,62.466,-76.879,62.525,-77.205,62.55,-77.372,62.573,-77.604,62.531,-77.9,62.427,-78.068,62.355,-78.109,62.318,-78.133,62.282,-78.147,62.209,-78.137,62.107,-78.077,61.923,-78.021,61.832,-77.948,61.762,-77.89,61.729,-77.814,61.695,-77.698,61.626,-77.514,61.556,-77.649,61.479,-77.736,61.437,-77.75,61.393,-77.727,61.231,-77.731,61.206,-77.765,61.158,-77.83,61.084,-77.934,61.003,-78.16,60.852,-78.181,60.819,-78.122,60.81,-77.998,60.818,-77.872,60.786,-77.603,60.825,-77.59,60.809,-77.661,60.79,-77.734,60.697,-77.761,60.679,-77.791,60.64,-77.715,60.578,-77.639,60.567,-77.516,60.563,-77.504,60.543,-77.598,60.507,-77.681,60.427,-77.649,60.363,-77.453,60.146,-77.461,60.133,-77.572,60.101,-77.586,60.088,-77.547,60.061,-77.312,60.042,-77.289,60.022,-77.373,59.925,-77.368,59.884,-77.328,59.833,-77.332,59.797,-77.475,59.716,-77.485,59.685,-77.411,59.61,-77.349,59.579,-77.397,59.569,-77.59,59.681,-77.726,59.676,-77.748,59.658,-77.733,59.581,-77.749,59.558,-77.859,59.476,-77.845,59.444,-77.779,59.41,-77.761,59.38,-77.843,59.305,-77.988,59.246,-78.068,59.2,-78.14,59.142,-78.244,59.035,-78.431,58.902,-78.459,58.873,-78.483,58.829,-78.502,58.769,-78.515,58.682,-78.506,58.649,-78.463,58.602,-78.352,58.581,-78.014,58.399,-77.884,58.351,-77.684,58.291,-77.552,58.24,-77.489,58.195,-77.157,58.019,-76.891,57.758,-76.81,57.658,-76.786,57.599,-76.655,57.381,-76.601,57.272,-76.573,57.181,-76.526,56.892,-76.52,56.707,-76.53,56.5,-76.546,56.359,-76.604,56.2,-76.65,56.107,-76.762,55.996,-76.938,55.867,-77.073,55.756,-77.165,55.664,-77.325,55.556,-77.702,55.344,-77.775,55.291,-77.891,55.236,-78.129,55.151,-78.304,55.069,-78.475,55.011,-78.846,54.908,-78.909,54.881,-79.666,54.697,-79.712,54.672,-79.714,54.655,-79.67,54.647,-79.632,54.629,-79.598,54.602,-79.521,54.492,-79.476,54.395,-79.431,54.337,-79.356,54.263,-79.296,54.217,-79.216,54.186,-79.147,54.169,-79.139,54.157,-79.179,54.117,-79.242,54.099,-79.067,54.052,-79.01,54.024,-78.996,54.002,-79.073,53.951,-79.075,53.932,-79.032,53.881,-78.944,53.84,-78.946,53.832,-79.003,53.837,-79.04,53.818,-79.081,53.742,-79.113,53.717,-79.1,53.657,-79.043,53.56,-78.992,53.41,-78.947,53.206,-78.898,53.043,-78.854,52.976,-78.74,52.899,-78.722,52.856,-78.754,52.812,-78.766,52.76,-78.744,52.655,-78.724,52.628,-78.601,52.535,-78.557,52.492,-78.529,52.399,-78.526,52.311,-78.513,52.291,-78.448,52.261,-78.492,52.252,-78.537,52.213,-78.593,52.14,-78.702,52.033,-78.828,51.963,-78.871,51.913,-78.891,51.845,-78.928,51.799,-78.982,51.775,-78.978,51.734,-78.776,51.566,-78.736,51.527,-78.731,51.497,-78.827,51.43,-78.858,51.384,-78.898,51.272,-78.903,51.2,-78.937,51.259,-78.984,51.386,-79.005,51.425,-79.041,51.464,-79.091,51.502,-79.153,51.526,-79.226,51.537,-79.264,51.552,-79.297,51.623,-79.339,51.628,-79.498,51.57,-79.521,51.545,-79.528,51.538,-79.547,51.494,-79.586,51.452,-79.643,51.414,-79.689,51.347,-79.723,51.252,-79.737,51.186,-79.731,51.15,-79.714,51.118,-79.636,51.049,-79.52,50.966,-79.453,50.917,-79.381,50.835,-79.348,50.763,-79.456,50.876,-79.52,50.919,-79.652,51.008,-79.836,51.173,-79.96,51.235,-80.104,51.283,-80.266,51.316,-80.368,51.33,-80.478,51.307,-80.677,51.191,-80.795,51.132,-80.851,51.125,-80.673,51.265,-80.496,51.345,-80.443,51.389,-80.448,51.432,-80.496,51.525,-80.588,51.667,-80.658,51.758,-80.706,51.798,-80.969,51.972,-81.127,52.045,-81.285,52.089,-81.398,52.142,-81.466,52.204,-81.55,52.237,-81.648,52.239,-81.815,52.217,-81.828,52.224,-81.776,52.254,-81.661,52.294,-81.612,52.324,-81.572,52.367,-81.599,52.433,-81.742,52.564,-81.859,52.651,-82.02,52.812,-82.108,52.877,-82.203,52.922,-82.26,52.961,-82.292,53.031,-82.292,53.066,-82.26,53.16,-82.219,53.211,-82.159,53.264,-82.146,53.365,-82.18,53.513,-82.191,53.611,-82.15,53.74,-82.141,53.818,-82.163,53.886,-82.24,54.045,-82.264,54.073,-82.394,54.18,-82.424,54.245,-82.418,54.356,-82.371,54.483,-82.219,54.813,-82.227,54.856,-82.308,54.998,-82.393,55.068,-82.577,55.149,-82.687,55.166,-82.801,55.156,-82.868,55.161,-82.947,55.222,-82.986,55.231,-83.214,55.215,-83.569,55.262,-83.668,55.265,-83.911,55.315,-83.972,55.314,-84.023,55.298,-84.105,55.291,-84.219,55.293,-84.356,55.283,-84.518,55.259,-84.706,55.259,-84.92,55.283,-85.061,55.286,-85.129,55.266,-85.214,55.224,-85.365,55.079,-85.362,55.095,-85.212,55.297,-85.218,55.349,-85.283,55.383,-85.407,55.431,-85.478,55.474,-85.559,55.54,-85.677,55.601,-85.831,55.657,-85.984,55.696,-86.139,55.718,-86.377,55.773,-86.919,55.915,-87.287,55.975,-87.482,56.021,-87.561,56.056,-87.878,56.342,-88.075,56.467,-88.271,56.536,-88.447,56.609,-88.68,56.725,-88.826,56.814,-88.948,56.851,-89.212,56.884,-89.342,56.915,-89.791,56.981,-90.075,57.052,-90.345,57.149,-90.592,57.224,-90.897,57.257,-91.111,57.241,-92.018,57.064,-92.249,57.009,-92.356,56.971,-92.372,56.975,-92.298,57.023,-92.303,57.046,-92.456,57.037,-92.51,57.022,-92.651,56.958,-92.725,56.934,-92.798,56.922,-92.802,56.928,-92.738,56.953,-92.675,56.99,-92.614,57.039,-92.548,57.111,-92.478,57.205,-92.44,57.275,-92.433,57.32,-92.449,57.385,-92.49,57.469,-92.702,57.778,-92.74,57.844,-92.842,58.076,-92.925,58.225,-93.1,58.49,-93.127,58.564,-93.155,58.695,-93.179,58.726,-93.278,58.756,-93.375,58.741,-93.486,58.744,-93.78,58.773,-94.056,58.76,-94.123,58.737,-94.209,58.626,-94.272,58.378,-94.332,58.297,-94.333,58.339,-94.281,58.659,-94.287,58.716,-94.419,58.746,-94.54,58.848,-94.579,58.868,-94.624,58.876,-94.673,58.87,-94.713,58.903,-94.744,58.975,-94.776,59.021,-94.847,59.05,-94.957,59.069,-94.87,59.088,-94.82,59.151,-94.788,59.268,-94.777,59.478,-94.786,59.953,-94.772,60.002,-94.742,60.107,-94.67,60.301,-94.647,60.416,-94.671,60.453,-94.705,60.478,-94.762,60.498,-94.679,60.538,-94.569,60.542,-94.509,60.605,-94.427,60.731,-94.309,60.871,-94.154,61.025,-94.068,61.139,-94.05,61.211,-94.055,61.266,-94.083,61.304,-94.061,61.318,-93.942,61.308,-93.889,61.344,-93.889,61.364,-93.941,61.444,-93.913,61.481,-93.71,61.603,-93.421,61.706,-93.352,61.74,-93.312,61.767,-93.314,61.78,-93.43,61.812,-93.494,61.847,-93.527,61.872,-93.582,61.942,-93.372,61.929,-93.333,61.933,-93.273,61.961,-93.297,61.982,-93.366,62.015,-93.35,62.03,-93.167,62.034,-93.073,62.061,-93.016,62.093,-93.028,62.109,-93.07,62.128,-93.066,62.15,-92.906,62.215,-92.914,62.245,-92.988,62.286,-93.179,62.35,-93.205,62.365,-93.154,62.367,-92.866,62.306,-92.735,62.26,-92.648,62.208,-92.579,62.177,-92.528,62.168,-92.544,62.202,-92.627,62.279,-92.701,62.328,-92.766,62.35,-92.768,62.38,-92.707,62.418,-92.595,62.47,-92.562,62.503,-92.551,62.547,-92.497,62.565,-92.4,62.557,-92.324,62.565,-92.27,62.587,-92.207,62.585,-92.081,62.544,-92.008,62.541,-91.944,62.575,-91.936,62.592,-91.956,62.645,-92.149,62.665,-92.243,62.684,-92.305,62.712,-92.345,62.734,-92.378,62.772,-92.388,62.801,-92.361,62.819,-92.196,62.829,-92.152,62.839,-92.11,62.862,-92.034,62.863,-91.87,62.835,-91.449,62.804,-91.349,62.819,-91.115,62.922,-91.008,62.94,-90.871,62.946,-90.778,62.972,-90.728,63.017,-90.699,63.064,-90.691,63.111,-90.711,63.304,-90.747,63.352,-90.97,63.443,-91.103,63.476,-91.33,63.507,-91.489,63.562,-91.686,63.66,-91.842,63.698,-91.957,63.676,-92.077,63.64,-92.29,63.563,-92.465,63.555,-92.461,63.569,-92.205,63.657,-92.157,63.692,-92.196,63.708,-92.339,63.735,-92.529,63.761,-93.166,63.902,-93.25,63.927,-93.379,63.948,-93.406,63.941,-93.38,63.9,-93.327,63.872,-93.266,63.853,-93.27,63.841,-93.416,63.838,-93.56,63.865,-93.664,63.941,-93.656,63.973,-93.605,64.004,-93.597,64.041,-93.696,64.147,-93.43,64.029,-92.97,63.938,-92.55,63.83,-92.338,63.788,-92.195,63.776,-92.095,63.784,-92.038,63.813,-91.982,63.822,-91.929,63.812,-91.919,63.801,-91.954,63.787,-91.956,63.772,-91.926,63.757,-91.675,63.742,-91.539,63.726,-91.108,63.618,-90.946,63.588,-90.812,63.581,-90.707,63.597,-90.635,63.624,-90.596,63.661,-90.533,63.665,-90.446,63.636,-90.369,63.624,-90.245,63.642,-90.155,63.69,-90.013,63.804,-90.018,63.829,-90.06,63.877,-90.168,63.979,-90.142,63.982,-89.922,63.944,-89.856,63.957,-89.861,63.979,-89.954,64.081,-89.986,64.1,-90.08,64.128,-90.042,64.141,-89.811,64.181,-89.792,64.168,-89.764,64.1,-89.733,64.077,-89.616,64.031,-89.551,64.015,-89.501,64.015,-89.465,64.03,-89.404,64.04,-89.215,63.984,-89.132,63.969,-89.108,63.981,-89.209,64.105,-89.201,64.114,-89.06,64.034,-88.964,64.011,-88.818,63.992,-88.653,64.009,-88.379,64.089,-88.106,64.183,-87.998,64.244,-87.964,64.302,-87.885,64.4,-87.281,64.826,-87.183,64.927,-87.029,65.064,-87.003,65.109,-87.028,65.198,-87.108,65.225,-87.392,65.261,-87.93,65.28,-88.198,65.28,-88.974,65.348,-89.127,65.396,-89.242,65.446,-89.6,65.648,-89.788,65.737,-89.924,65.78,-90.048,65.806,-90.159,65.813,-90.597,65.885,-90.983,65.919,-91.065,65.9,-91.074,65.886,-91.041,65.83,-91.285,65.894,-91.427,65.948,-91.412,65.959,-91.305,65.965,-91.01,65.966,-90.826,65.954,-90.655,65.929,-90.513,65.921,-90.316,65.926,-90.117,65.882,-90.004,65.883,-89.89,65.869,-89.848,65.872,-89.944,65.934,-89.89,65.941,-89.749,65.936,-89.593,65.909,-89.42,65.861,-89.088,65.739,-88.946,65.703,-88.808,65.692,-88.744,65.679,-88.672,65.612,-88.587,65.588,-88.395,65.516,-88.121,65.395,-87.97,65.349,-87.678,65.335,-87.453,65.339,-87.291,65.355,-87.194,65.383,-87.081,65.441,-86.953,65.528,-86.702,65.671,-86.043,66.023,-86.012,66.048,-85.959,66.119,-85.964,66.154,-86.001,66.187,-86.113,66.225,-86.301,66.27,-86.585,66.322,-86.685,66.36,-86.747,66.417,-86.738,66.433,-86.7,66.443,-86.689,66.457,-86.737,66.511,-86.708,66.523,-86.633,66.531,-86.063,66.52,-85.792,66.533,-85.604,66.568,-85.442,66.537,-85.307,66.44,-85.192,66.37,-85.096,66.325,-84.908,66.271,-84.628,66.208,-84.478,66.179,-84.459,66.186,-84.398,66.259,-84.324,66.291,-84.293,66.292,-84.012,66.231,-83.905,66.212,-83.869,66.214,-83.798,66.238,-83.826,66.29,-83.964,66.421,-84.094,66.526,-84.153,66.59,-84.183,66.648,-84.223,66.682,-84.319,66.712,-84.59,66.857,-84.603,66.875,-84.639,66.902,-84.738,66.934,-84.857,66.941,-84.899,66.927,-84.978,66.881,-85.018,66.872,-85.111,66.891,-85.114,66.907,-85.04,66.956,-84.846,67.029,-84.693,67.017,-84.538,66.973,-84.531,66.961,-84.466,66.927,-84.31,66.863,-84.273,66.839,-84.361,66.823,-84.366,66.811,-84.324,66.782,-84.208,66.736,-84.154,66.732,-84.05,66.74,-83.998,66.729,-83.92,66.679,-83.739,66.534,-83.651,66.485,-83.628,66.461,-83.59,66.388,-83.523,66.369,-83.406,66.371,-83.298,66.392,-83.199,66.431,-82.949,66.551,-82.642,66.588,-82.554,66.621,-82.375,66.709,-82.261,66.739,-82.198,66.765,-82.113,66.825,-82.005,66.92,-81.926,66.975,-81.874,66.988,-81.722,66.986,-81.63,67.002,-81.468,67.07,-81.443,67.093,-81.387,67.189,-81.301,67.357,-81.27,67.46,-81.294,67.497,-81.412,67.595,-81.493,67.637,-81.709,67.722,-81.869,67.802,-81.976,67.862,-82.063,67.928,-82.1,67.99,-82.102,68.019,-82.092,68.051,-82.013,68.173,-82.012,68.194,-82.034,68.196,-82.078,68.18,-82.151,68.14,-82.187,68.134,-82.222,68.145,-82.393,68.285,-82.423,68.297,-82.431,68.307,-82.393,68.338,-82.413,68.357,-82.464,68.382,-82.553,68.446,-82.549,68.469,-82.499,68.479,-82.397,68.478,-82.21,68.506,-82.106,68.499,-82.006,68.463,-81.915,68.459,-81.831,68.487,-81.64,68.524,-81.527,68.556,-81.282,68.657,-81.259,68.692,-81.252,68.743,-81.264,68.781,-81.331,68.828,-81.381,68.85,-81.476,68.866,-81.687,68.879,-81.958,68.884,-81.952,68.909,-81.758,68.957,-81.611,69.003,-81.329,69.12,-81.322,69.139,-81.378,69.186,-81.412,69.198,-81.732,69.258,-81.952,69.276,-82.151,69.249,-82.228,69.249,-82.247,69.265,-82.208,69.297,-82.232,69.333,-82.31,69.41,-82.642,69.458,-82.755,69.494,-82.633,69.518,-82.496,69.532,-82.39,69.601,-82.374,69.642,-82.618,69.691,-82.746,69.695,-82.991,69.686,-83.552,69.704,-83.665,69.7,-83.917,69.745,-84.242,69.835,-84.319,69.844,-84.645,69.85,-84.834,69.835,-85.02,69.805,-85.177,69.805,-85.305,69.836,-85.415,69.85,-85.507,69.845,-85.535,69.835,-85.497,69.819,-85.446,69.778,-85.448,69.748,-85.502,69.652,-85.482,69.619,-85.43,69.581,-85.416,69.548,-85.44,69.52,-85.437,69.488,-85.409,69.452,-85.402,69.427,-85.416,69.411,-85.432,69.354,-85.428,69.318,-85.387,69.232,-85.275,69.172,-85.243,69.163,-85.114,69.166,-84.89,69.093,-84.862,69.074,-84.893,69.021,-84.895,68.989,-84.916,68.962,-85.008,68.949,-85.083,68.908,-85.104,68.871,-85.107,68.844,-84.867,68.79,-84.868,68.773,-85.275,68.741,-85.338,68.746,-85.425,68.774,-85.491,68.774,-85.518,68.77,-85.562,68.729,-85.643,68.7,-85.69,68.671,-85.734,68.63,-85.745,68.578,-85.723,68.515,-85.731,68.445,-85.789,68.328,-85.953,68.072,-85.984,68.045,-86.37,67.825,-86.398,67.8,-86.476,67.713,-86.504,67.649,-86.536,67.516,-86.561,67.482,-86.609,67.451,-86.682,67.422,-86.75,67.406,-86.813,67.402,-86.924,67.356,-87.083,67.268,-87.266,67.184,-87.32,67.173,-87.359,67.177,-87.392,67.191,-87.418,67.214,-87.471,67.325,-87.499,67.355,-87.997,67.626,-88.196,67.766,-88.314,67.95,-88.325,67.989,-88.32,68.166,-88.361,68.26,-88.347,68.288,-88.235,68.339,-88.209,68.335,-88.146,68.266,-88.111,68.251,-87.991,68.242,-87.893,68.248,-87.853,68.267,-87.828,68.3,-87.814,68.346,-87.81,68.404,-87.828,68.448,-87.866,68.478,-87.912,68.565,-87.964,68.709,-88.041,68.812,-88.224,68.915,-88.316,68.954,-88.638,69.059,-88.815,69.136,-88.954,69.22,-89.057,69.266,-89.198,69.269,-89.28,69.255,-89.352,69.227,-89.552,69.085,-89.667,69.015,-89.72,68.932,-89.751,68.812,-89.783,68.736,-89.884,68.626,-89.897,68.594,-89.879,68.522,-89.898,68.491,-90.005,68.398,-90.116,68.339,-90.157,68.306,-90.174,68.27,-90.205,68.257,-90.248,68.267,-90.285,68.292,-90.317,68.33,-90.36,68.347,-90.423,68.395,-90.528,68.432,-90.574,68.475,-90.525,68.611,-90.51,68.689,-90.543,68.786,-90.539,68.82,-90.468,68.864,-90.479,68.881,-90.587,68.947,-90.745,69.106,-91.237,69.286,-91.218,69.302,-91.148,69.303,-91.058,69.318,-91.025,69.315,-91.049,69.293,-91.005,69.277,-90.892,69.267,-90.822,69.29,-90.795,69.347,-90.749,69.392,-90.684,69.428,-90.606,69.445,-90.513,69.445,-90.416,69.457,-90.451,69.475,-90.555,69.504,-90.667,69.516,-90.786,69.509,-90.95,69.515,-91.288,69.543,-91.44,69.526,-91.427,69.538,-91.305,69.581,-91.17,69.62,-91.151,69.637,-91.202,69.645,-91.384,69.649,-91.532,69.615,-91.724,69.546,-91.912,69.531,-92.209,69.603,-92.258,69.634,-92.231,69.653,-92.312,69.673,-92.493,69.683,-92.643,69.659,-92.803,69.651,-92.855,69.655,-92.888,69.668,-92.751,69.714,-92.285,69.892,-92.069,69.984,-91.977,70.039,-92.058,70.071,-92.127,70.085,-92.446,70.083,-92.512,70.104,-92.455,70.15,-92.363,70.201,-92.321,70.235,-92.29,70.24,-92.209,70.198,-92.121,70.17,-91.995,70.143,-91.859,70.133,-91.616,70.148,-91.572,70.162,-91.564,70.178,-91.654,70.233,-91.716,70.299,-91.761,70.326,-91.82,70.342,-91.876,70.331,-91.926,70.295,-91.984,70.286,-92.047,70.303,-92.073,70.319,-92.037,70.367,-92.049,70.39,-92.214,70.493,-92.315,70.608,-92.356,70.634,-92.388,70.65,-92.567,70.693,-92.642,70.719,-92.783,70.798,-92.961,70.838,-92.981,70.852,-92.922,70.887,-92.904,70.916,-92.883,71.069,-92.89,71.122,-92.949,71.262,-92.983,71.3,-93.031,71.336,-93.256,71.461,-93.407,71.521,-93.576,71.569,-93.763,71.638,-93.782,71.674,-93.751,71.717,-93.746,71.743,-93.81,71.766,-94.086,71.771,-94.171,71.758,-94.308,71.765,-94.479,71.849,-94.491,71.916,-94.557,71.979,-94.611,71.987,-94.735,71.983,-94.887,71.963,-95.201,71.904,-95.512,71.777,-95.616,71.685,-95.838,71.598,-95.872,71.573,-95.83,71.526,-95.773,71.514,-95.674,71.504,-95.445,71.505,-95.406,71.492,-95.448,71.46,-95.564,71.337,-95.633,71.319,-95.725,71.328,-95.851,71.361,-95.924,71.393,-95.994,71.411,-96.062,71.414,-96.14,71.396,-96.271,71.339,-96.406,71.274,-96.447,71.24,-96.421,71.176,-96.445,71.159,-96.504,71.143,-96.525,71.127,-96.47,71.07,-96.491,71.002,-96.551,70.89,-96.549,70.809,-96.359,70.679,-96.258,70.642,-96.186,70.638,-95.906,70.698,-95.886,70.694,-95.988,70.617,-95.98,70.593,-95.879,70.549,-96.048,70.567,-96.123,70.561,-96.226,70.542,-96.298,70.511,-96.337,70.47,-96.546,70.327,-96.56,70.243,-96.551,70.21,-96.492,70.125,-96.269,69.992,-96.172,69.925,-96.119,69.872,-96.05,69.831,-95.965,69.803,-95.851,69.785,-95.707,69.778,-95.588,69.756,-95.491,69.718,-95.292,69.667,-94.823,69.578,-94.789,69.585,-94.713,69.649,-94.676,69.657,-94.634,69.65,-94.514,69.583,-94.419,69.517,-94.338,69.474,-94.271,69.455,-94.163,69.446,-94.015,69.447,-93.915,69.458,-93.794,69.498,-93.65,69.519,-93.532,69.481,-93.522,69.451,-93.543,69.406,-93.537,69.382,-93.431,69.375,-93.451,69.355,-93.567,69.297,-93.749,69.226,-93.82,69.253,-93.801,69.281,-93.613,69.403,-93.619,69.417,-93.854,69.376,-94.156,69.342,-94.255,69.314,-94.277,69.275,-94.285,69.242,-94.255,69.151,-94.222,69.136,-94.081,69.136,-94.084,69.123,-94.237,69.05,-94.476,68.958,-94.563,68.912,-94.6,68.803,-94.587,68.776,-94.478,68.743,-94.217,68.761,-94.065,68.785,-93.992,68.821,-93.938,68.889,-93.896,68.982,-93.881,68.997,-93.852,69,-93.811,68.993,-93.766,68.97,-93.716,68.931,-93.681,68.887,-93.663,68.838,-93.66,68.784,-93.676,68.686,-93.644,68.633,-93.606,68.624,-93.449,68.619,-93.483,68.599,-93.652,68.543,-93.928,68.474,-94.098,68.399,-94.255,68.297,-94.384,68.227,-94.485,68.19,-94.744,68.071,-94.861,68.042,-94.955,68.05,-95.126,68.083,-95.235,68.06,-95.384,68.056,-95.427,68.045,-95.461,68.021,-95.65,67.737,-95.634,67.704,-95.463,67.61,-95.39,67.518,-95.296,67.361,-95.259,67.263,-95.321,67.152,-95.354,66.981,-95.4,66.949,-95.49,66.924,-95.625,66.916,-95.972,66.952,-96.037,66.938,-96.045,66.923,-96.016,66.87,-95.772,66.726,-95.743,66.69,-95.788,66.617,-95.797,66.617,-95.813,66.69,-95.885,66.741,-96.36,66.989,-96.42,67.036,-96.423,67.052,-96.404,67.063,-96.35,67.07,-96.216,66.998,-96.095,66.994,-96.02,67.019,-95.954,67.011,-95.862,66.978,-95.769,66.967,-95.611,66.976,-95.559,66.973,-95.502,66.98,-95.457,66.989,-95.419,67.013,-95.407,67.056,-95.405,67.116,-95.416,67.156,-95.529,67.209,-95.557,67.215,-95.626,67.212,-95.778,67.185,-95.783,67.194,-95.695,67.299,-95.72,67.317,-95.879,67.298,-96.013,67.271,-96.141,67.272,-96.169,67.289,-96.185,67.376,-96.213,67.404,-96.369,67.51,-96.371,67.554,-96.228,67.679,-96.199,67.718,-96.171,67.832,-96.036,68.158,-95.97,68.249,-96.076,68.237,-96.439,68.151,-96.461,68.136,-96.494,68.085,-96.531,68.063,-96.592,68.048,-96.722,68.039,-96.725,68.061,-96.48,68.243,-96.435,68.29,-96.431,68.311,-96.628,68.25,-96.977,68.255,-97,68.265,-97.072,68.333,-97.136,68.378,-97.266,68.453,-97.336,68.479,-97.41,68.497,-97.481,68.495,-97.548,68.475,-97.64,68.482,-97.829,68.533,-97.925,68.524,-97.939,68.51,-97.911,68.45,-97.794,68.388,-98.091,68.346,-98.219,68.317,-98.469,68.382,-98.522,68.383,-98.562,68.371,-98.65,68.364,-98.633,68.331,-98.491,68.224,-98.449,68.201,-98.381,68.132,-98.386,68.115,-98.5,68.132,-98.5,68.118,-98.438,68.065,-98.193,67.923,-98.11,67.903,-97.913,67.954,-97.739,67.978,-97.547,67.961,-97.336,67.901,-97.207,67.855,-97.158,67.822,-97.14,67.796,-97.157,67.755,-97.155,67.726,-97.194,67.697,-97.274,67.666,-97.455,67.617,-97.607,67.631,-97.931,67.711,-97.978,67.739,-98.063,67.77,-98.415,67.988,-98.54,68.047,-98.632,68.073,-98.69,68.066,-98.72,68.042,-98.722,68,-98.704,67.966,-98.606,67.911,-98.468,67.856,-98.417,67.826,-98.412,67.807,-98.453,67.798,-98.697,67.78,-98.812,67.744,-98.92,67.726,-99.032,67.719,-99.147,67.724,-99.294,67.745,-99.472,67.784,-99.773,67.815,-100.213,67.839,-100.456,67.839,-100.52,67.818,-100.616,67.808,-100.746,67.809,-100.856,67.799,-101.026,67.766,-101.096,67.762,-101.555,67.693,-101.689,67.709,-101.884,67.745,-102.057,67.753,-102.21,67.733,-102.32,67.736,-102.389,67.762,-102.692,67.812,-102.842,67.853,-103.022,67.94,-103.323,68.064,-103.474,68.115,-103.657,68.069,-103.902,68.041,-104.194,68.031,-104.351,68.041,-104.487,68.063,-104.628,68.121,-104.661,68.149,-104.636,68.214,-104.653,68.23,-104.77,68.252,-104.879,68.245,-104.912,68.25,-104.937,68.303,-104.96,68.311,-104.994,68.307,-105.044,68.288,-105.101,68.298,-105.195,68.33,-105.377,68.414,-105.429,68.458,-105.457,68.578,-105.54,68.719,-105.606,68.782,-105.686,68.828,-105.798,68.865,-106.016,68.906,-106.164,68.92,-106.324,68.899,-106.713,68.819,-106.831,68.809,-107.436,68.689,-107.766,68.649,-108.313,68.611,-108.346,68.598,-108.641,68.379,-108.718,68.297,-108.687,68.277,-108.368,68.178,-108.323,68.154,-108.261,68.15,-108.105,68.169,-108.027,68.163,-107.734,68.174,-107.678,68.203,-107.734,68.252,-107.742,68.286,-107.619,68.331,-107.499,68.324,-107.298,68.296,-107.146,68.304,-107.043,68.347,-106.946,68.374,-106.854,68.387,-106.78,68.387,-106.608,68.357,-106.567,68.389,-106.543,68.461,-106.458,68.516,-106.237,68.577,-106.027,68.623,-105.932,68.637,-105.774,68.611,-105.75,68.592,-105.781,68.527,-105.857,68.475,-105.933,68.443,-106.039,68.407,-106.132,68.39,-106.271,68.383,-106.404,68.319,-106.429,68.288,-106.424,68.201,-106.459,68.196,-106.535,68.209,-106.668,68.216,-106.711,68.207,-106.791,68.145,-106.836,68.129,-106.923,68.114,-106.994,68.106,-107.125,68.108,-107.224,68.094,-107.351,68.061,-107.446,68.05,-107.509,68.059,-107.761,68.032,-107.798,68.037,-107.787,68.013,-107.729,67.959,-107.763,67.907,-107.891,67.856,-107.958,67.819,-107.972,67.732,-107.954,67.7,-107.865,67.639,-107.753,67.587,-107.65,67.511,-107.638,67.474,-107.651,67.428,-107.644,67.385,-107.567,67.273,-107.482,67.199,-107.318,67.128,-107.283,67.103,-107.348,67.055,-107.323,67.023,-107.254,66.976,-107.156,66.882,-107.2,66.883,-107.329,66.932,-107.402,66.948,-107.419,66.931,-107.451,66.927,-107.499,66.936,-107.626,67.003,-107.725,66.984,-107.746,66.961,-107.74,66.814,-107.731,66.769,-107.71,66.74,-107.564,66.619,-107.278,66.425,-107.259,66.399,-107.291,66.402,-107.374,66.435,-107.48,66.492,-107.705,66.637,-107.761,66.684,-107.958,66.781,-108.002,66.818,-108.05,66.844,-108.101,66.86,-108.158,66.893,-108.218,66.941,-108.455,67.063,-108.496,67.092,-108.459,67.09,-108.344,67.058,-108.221,67.051,-108.088,67.07,-107.991,67.095,-107.929,67.127,-107.909,67.163,-107.931,67.202,-107.989,67.256,-108.347,67.403,-108.492,67.483,-108.593,67.591,-108.613,67.598,-108.68,67.606,-108.715,67.583,-108.815,67.438,-108.852,67.422,-108.891,67.438,-108.95,67.494,-108.968,67.532,-108.994,67.637,-109.038,67.691,-109.081,67.711,-109.224,67.73,-109.63,67.733,-109.686,67.752,-109.76,67.82,-109.831,67.866,-109.904,67.874,-109.937,67.888,-110.042,67.977,-110.074,67.993,-110.102,67.992,-110.216,67.954,-110.372,67.954,-110.805,67.832,-110.99,67.791,-111.087,67.788,-111.155,67.798,-111.192,67.823,-111.291,67.815,-111.451,67.776,-111.576,67.757,-111.711,67.757,-112.101,67.732,-112.237,67.731,-112.315,67.72,-112.435,67.685,-112.503,67.682,-112.879,67.68,-113.075,67.687,-113.215,67.702,-113.682,67.7,-113.893,67.707,-114.051,67.727,-114.176,67.735,-114.267,67.731,-114.429,67.751,-114.663,67.795,-114.857,67.814,-115.011,67.806,-115.133,67.819,-115.288,67.872,-115.434,67.902,-115.427,67.924,-115.202,67.998,-115.167,68.019,-115.187,68.084,-115.176,68.104,-115.127,68.132,-114.852,68.195,-114.765,68.27,-114.275,68.248,-114.096,68.267,-114.053,68.283,-114.021,68.306,-113.964,68.399,-113.988,68.415,-114.051,68.415,-114.092,68.435,-114.111,68.477,-114.218,68.552,-114.414,68.66,-114.62,68.746,-114.994,68.85,-115.24,68.892,-115.442,68.941,-115.631,68.973,-115.806,68.987,-115.883,68.987,-115.936,68.958,-116.167,68.975,-116.243,68.974,-116.252,68.958,-116.065,68.855,-116.059,68.837,-116.223,68.847,-116.334,68.874,-116.425,68.881,-116.55,68.879,-117.026,68.916,-117.132,68.907,-117.227,68.913,-117.311,68.935,-117.83,69,-118.095,69.043,-118.307,69.093,-118.486,69.145,-118.745,69.234,-118.869,69.257,-119.853,69.342,-120.14,69.381,-120.293,69.421,-120.682,69.567]},{"b":[-82.039,52.734,-80.71,53.224],"l":[-81.136,53.206,-81.335,53.224,-81.847,53.186,-81.901,53.166,-81.951,53.132,-82.039,53.05,-82.005,53.01,-81.839,52.958,-81.352,52.852,-81.097,52.78,-81.01,52.761,-80.802,52.734,-80.732,52.747,-80.71,52.787,-80.71,52.832,-80.765,52.923,-80.9,53.037,-81.136,53.206]},{"b":[-74.749,67.783,-73.398,68.093],"l":[-73.584,68.015,-73.881,68.022,-74.111,68.061,-74.379,68.093,-74.707,68.067,-74.731,68.049,-74.749,68.018,-74.746,67.985,-74.679,67.906,-74.573,67.829,-74.481,67.805,-74.374,67.79,-74.109,67.783,-73.622,67.784,-73.407,67.793,-73.398,67.83,-73.402,67.879,-73.435,67.97,-73.459,67.99,-73.494,68.001,-73.584,68.015]},{"b":[-78.537,63.114,-77.527,63.49],"l":[-77.792,63.428,-77.877,63.471,-77.934,63.479,-78.235,63.49,-78.417,63.47,-78.507,63.451,-78.537,63.424,-78.469,63.358,-78.256,63.24,-78.024,63.139,-77.942,63.114,-77.791,63.13,-77.658,63.165,-77.594,63.188,-77.533,63.234,-77.527,63.269,-77.538,63.287,-77.655,63.396,-77.704,63.431,-77.792,63.428]},{"b":[-83.912,62.16,-81.949,62.978],"l":[-82.706,62.945,-82.966,62.874,-83.026,62.872,-83.111,62.884,-83.289,62.922,-83.376,62.905,-83.739,62.569,-83.899,62.476,-83.91,62.454,-83.912,62.426,-83.903,62.402,-83.761,62.304,-83.729,62.257,-83.714,62.174,-83.699,62.16,-83.377,62.238,-83.252,62.233,-83.13,62.204,-83.071,62.2,-83.016,62.21,-82.568,62.403,-82.491,62.447,-82.388,62.519,-82.114,62.652,-82.026,62.73,-81.99,62.776,-81.964,62.828,-81.949,62.884,-81.961,62.926,-82,62.954,-82.048,62.971,-82.129,62.978,-82.235,62.977,-82.46,62.936,-82.706,62.945]},{"b":[-80.28,61.595,-79.272,62.413],"l":[-79.466,62.385,-79.545,62.412,-79.598,62.413,-79.65,62.398,-79.713,62.395,-79.868,62.404,-79.927,62.393,-80.022,62.343,-80.179,62.213,-80.235,62.153,-80.26,62.109,-80.275,62.055,-80.28,61.99,-80.276,61.859,-80.265,61.818,-80.205,61.777,-80.092,61.747,-80.004,61.703,-79.896,61.63,-79.816,61.595,-79.763,61.596,-79.714,61.613,-79.669,61.644,-79.611,61.71,-79.542,61.808,-79.462,61.894,-79.372,61.968,-79.324,62.026,-79.306,62.104,-79.272,62.186,-79.286,62.248,-79.336,62.294,-79.466,62.385]},{"b":[-106.066,77.124,-104.454,77.754],"l":[-104.77,77.413,-104.955,77.419,-104.994,77.45,-105.007,77.507,-105.074,77.548,-105.29,77.642,-105.456,77.701,-105.588,77.736,-105.863,77.754,-106.036,77.74,-106.066,77.725,-105.883,77.627,-105.848,77.563,-105.747,77.525,-105.695,77.461,-105.556,77.353,-105.38,77.254,-105.215,77.182,-105.016,77.165,-104.711,77.124,-104.558,77.142,-104.493,77.162,-104.457,77.221,-104.454,77.249,-104.501,77.309,-104.542,77.338,-104.77,77.413]},{"b":[-96.277,77.452,-93.129,77.792],"l":[-93.129,77.66,-93.211,77.71,-93.301,77.74,-93.471,77.764,-93.583,77.771,-94.015,77.76,-94.667,77.776,-94.96,77.774,-95.233,77.754,-95.484,77.792,-95.684,77.782,-96.143,77.714,-96.195,77.701,-96.239,77.673,-96.277,77.631,-96.264,77.595,-96.056,77.503,-95.987,77.484,-94.409,77.474,-93.836,77.452,-93.74,77.465,-93.544,77.467,-93.52,77.474,-93.339,77.63,-93.129,77.66]},{"b":[-100.126,79.724,-98.769,80.144],"l":[-99.516,79.887,-99.333,79.84,-99.306,79.803,-99.302,79.784,-99.218,79.762,-98.945,79.724,-98.885,79.726,-98.841,79.737,-98.79,79.785,-98.769,79.851,-98.792,79.981,-98.823,80.037,-98.895,80.082,-99.017,80.111,-99.153,80.124,-99.425,80.126,-99.731,80.144,-99.803,80.14,-100.053,80.093,-100.079,80.081,-100.12,80.03,-100.126,80.001,-100.092,79.919,-100.057,79.898,-100,79.884,-99.857,79.879,-99.516,79.887]},{"b":[-91.185,77.21,-89.694,77.655],"l":[-90.172,77.595,-90.423,77.628,-90.675,77.649,-90.843,77.655,-91.019,77.644,-91.109,77.626,-91.149,77.608,-91.183,77.557,-91.185,77.482,-91.177,77.426,-91.147,77.387,-90.993,77.329,-90.228,77.212,-90.095,77.21,-89.833,77.268,-89.747,77.293,-89.712,77.31,-89.695,77.339,-89.694,77.378,-89.719,77.442,-89.839,77.491,-90.172,77.595]},{"b":[-96.6,74.637,-93.463,75.63],"l":[-93.498,75.137,-93.591,75.23,-93.667,75.274,-93.751,75.349,-93.909,75.423,-94.257,75.544,-94.427,75.593,-94.649,75.623,-94.878,75.63,-95.05,75.622,-95.671,75.529,-95.853,75.469,-95.955,75.444,-96.125,75.358,-96.118,75.301,-96.18,75.24,-96.292,75.219,-96.383,75.211,-96.566,75.099,-96.597,75.058,-96.6,75.032,-96.591,75.002,-96.56,74.99,-96.386,74.999,-96.343,74.982,-96.319,74.948,-96.294,74.927,-96.27,74.92,-96.182,74.951,-96.094,74.933,-95.865,74.83,-95.451,74.797,-95.286,74.794,-94.959,74.7,-94.804,74.66,-94.697,74.642,-94.535,74.637,-94.206,74.647,-93.985,74.644,-93.626,74.661,-93.573,74.669,-93.547,74.691,-93.548,74.728,-93.536,74.749,-93.509,74.756,-93.491,74.772,-93.463,74.856,-93.467,74.921,-93.478,74.952,-93.543,75.028,-93.552,75.051,-93.532,75.1,-93.498,75.137]},{"b":[-113.293,78.275,-110.004,78.758],"l":[-110.004,78.687,-110.14,78.704,-110.408,78.757,-110.618,78.758,-110.878,78.735,-111.071,78.708,-111.4,78.644,-111.52,78.603,-111.709,78.575,-112.214,78.548,-112.641,78.5,-112.856,78.467,-113.15,78.408,-113.282,78.353,-113.293,78.334,-113.223,78.298,-113.173,78.284,-113,78.293,-112.558,78.342,-112.131,78.366,-111.76,78.283,-111.517,78.275,-111.435,78.287,-111.3,78.337,-111.229,78.376,-111.169,78.386,-111.027,78.368,-110.84,78.322,-110.755,78.311,-110.418,78.295,-110.293,78.298,-110.022,78.323,-110.004,78.322]},{"b":[-110.004,78.304,-109.336,78.687],"l":[-110.004,78.322,-109.709,78.304,-109.484,78.316,-109.391,78.337,-109.352,78.369,-109.336,78.408,-109.342,78.456,-109.362,78.493,-109.467,78.567,-109.504,78.582,-109.581,78.593,-109.641,78.592,-109.816,78.65,-109.941,78.678,-110.004,78.687]},{"b":[-119.395,75.515,-117.499,76.115],"l":[-117.626,75.966,-117.499,76.077,-117.513,76.099,-117.634,76.115,-117.752,76.112,-117.889,76.076,-118.137,75.994,-118.379,75.958,-118.626,75.906,-119.003,75.77,-119.227,75.699,-119.32,75.663,-119.395,75.617,-119.383,75.601,-119.306,75.585,-119.087,75.569,-118.817,75.522,-118.614,75.515,-118.328,75.58,-118.227,75.611,-117.891,75.805,-117.716,75.921,-117.626,75.966]},{"b":[-106.95,72.915,-104.552,73.767],"l":[-104.835,73.647,-105.114,73.744,-105.318,73.767,-105.512,73.766,-106.362,73.719,-106.614,73.696,-106.695,73.67,-106.831,73.599,-106.95,73.51,-106.922,73.48,-106.75,73.458,-106.526,73.413,-106.18,73.304,-106.113,73.258,-106.071,73.196,-105.8,73.093,-105.573,72.989,-105.434,72.938,-105.339,72.915,-105.289,72.92,-105.201,72.947,-105.075,72.997,-105.003,73.038,-104.969,73.089,-104.791,73.168,-104.622,73.311,-104.583,73.354,-104.552,73.466,-104.555,73.541,-104.587,73.578,-104.649,73.614,-104.718,73.636,-104.835,73.647]},{"b":[-102.714,71.302,-96.44,73.946],"l":[-96.869,72.687,-97.052,72.637,-97.128,72.628,-97.159,72.643,-97.14,72.673,-97.073,72.718,-97.083,72.763,-97.238,72.837,-97.378,72.865,-97.371,72.878,-97.31,72.898,-97.296,72.918,-97.329,72.938,-97.476,72.992,-97.636,73.028,-97.725,73.037,-97.939,73.036,-98.061,73.021,-98.181,72.993,-98.367,72.934,-98.422,72.941,-98.431,72.958,-98.437,73,-98.417,73.023,-98.376,73.045,-98.176,73.116,-97.796,73.285,-97.484,73.339,-97.273,73.387,-97.23,73.421,-97.287,73.458,-97.35,73.481,-97.47,73.488,-97.532,73.474,-97.586,73.471,-97.615,73.481,-97.626,73.502,-97.597,73.537,-97.49,73.527,-97.395,73.564,-97.284,73.571,-97.156,73.592,-97.095,73.615,-97.002,73.667,-96.997,73.675,-97.011,73.706,-97.112,73.79,-97.171,73.825,-97.225,73.844,-97.327,73.862,-97.582,73.888,-97.67,73.888,-97.832,73.879,-97.928,73.866,-98.152,73.818,-98.519,73.792,-98.785,73.761,-99.04,73.749,-99.158,73.732,-100.002,73.946,-100.138,73.929,-100.227,73.889,-100.225,73.873,-100.154,73.844,-100.04,73.844,-99.94,73.857,-99.912,73.847,-99.991,73.795,-100.182,73.801,-100.484,73.844,-100.915,73.805,-100.963,73.791,-100.985,73.765,-100.982,73.727,-100.953,73.691,-100.898,73.658,-100.783,73.613,-100.658,73.593,-100.607,73.575,-100.536,73.51,-100.509,73.465,-100.522,73.449,-100.677,73.494,-100.854,73.571,-100.976,73.6,-101.115,73.596,-101.323,73.572,-101.463,73.534,-101.518,73.505,-101.523,73.486,-101.482,73.446,-101.451,73.431,-100.889,73.275,-100.755,73.278,-100.587,73.3,-100.498,73.316,-100.366,73.359,-100.258,73.34,-100.006,73.24,-99.825,73.214,-99.966,73.201,-100.067,73.211,-100.226,73.255,-100.341,73.265,-100.439,73.255,-100.489,73.234,-100.536,73.198,-100.55,73.164,-100.531,73.138,-100.446,73.121,-100.334,73.128,-100.283,73.12,-100.236,73.095,-100.184,73.055,-100.097,72.963,-100.092,72.945,-100.128,72.907,-100.188,72.89,-100.228,72.899,-100.368,72.978,-100.396,72.977,-100.443,72.807,-100.468,72.779,-100.485,72.773,-100.896,72.726,-101.088,72.713,-101.273,72.722,-101.351,72.746,-101.435,72.821,-101.544,72.883,-101.618,72.91,-101.755,72.943,-101.798,72.973,-101.835,73.018,-101.922,73.057,-102.02,73.07,-102.204,73.077,-102.336,73.064,-102.504,73.006,-102.551,72.978,-102.628,72.911,-102.687,72.843,-102.714,72.783,-102.709,72.765,-102.657,72.719,-102.402,72.595,-101.974,72.486,-101.909,72.431,-101.833,72.409,-101.804,72.385,-101.775,72.341,-101.724,72.315,-101.498,72.278,-101.319,72.313,-101.251,72.322,-101.209,72.317,-101.093,72.279,-101.026,72.229,-100.984,72.21,-100.8,72.199,-100.707,72.186,-100.594,72.152,-100.326,72.004,-100.124,71.912,-99.735,71.757,-99.581,71.652,-99.404,71.557,-99.276,71.424,-99.224,71.387,-99.167,71.367,-98.986,71.369,-98.899,71.352,-98.784,71.314,-98.663,71.302,-98.536,71.318,-98.412,71.349,-98.199,71.441,-98.19,71.462,-98.195,71.491,-98.231,71.559,-98.421,71.717,-98.459,71.773,-98.389,71.824,-98.323,71.852,-98.306,71.848,-98.303,71.831,-98.313,71.803,-98.307,71.765,-98.284,71.716,-98.242,71.681,-98.181,71.662,-97.582,71.63,-97.461,71.634,-97.222,71.673,-97.117,71.711,-97.025,71.761,-96.946,71.792,-96.613,71.834,-96.624,71.968,-96.717,72.025,-96.758,72.032,-96.766,72.046,-96.618,72.146,-96.601,72.173,-96.593,72.204,-96.616,72.237,-96.669,72.271,-96.796,72.314,-96.801,72.322,-96.746,72.323,-96.638,72.342,-96.52,72.393,-96.473,72.434,-96.44,72.487,-96.446,72.552,-96.489,72.63,-96.542,72.699,-96.592,72.71,-96.671,72.713,-96.869,72.687]},{"b":[-87.194,63.12,-80.261,65.919],"l":[-80.668,63.901,-80.579,63.909,-80.569,63.932,-80.608,63.972,-80.694,64.025,-80.829,64.09,-80.921,64.1,-81.005,64.033,-81.024,64.031,-81.104,64.037,-81.336,64.076,-81.716,64.022,-81.887,64.016,-81.903,64.031,-81.721,64.119,-81.681,64.146,-81.667,64.171,-81.676,64.213,-81.787,64.426,-81.929,64.559,-82.05,64.644,-82.159,64.691,-82.272,64.721,-82.586,64.762,-82.668,64.78,-82.991,64.904,-83.201,64.96,-83.222,64.968,-83.407,65.104,-83.491,65.132,-83.723,65.169,-83.9,65.181,-84.085,65.218,-84.133,65.245,-84.18,65.316,-84.266,65.367,-84.501,65.458,-84.568,65.461,-84.612,65.447,-84.771,65.305,-84.842,65.256,-84.885,65.249,-84.92,65.261,-85.056,65.437,-85.24,65.51,-85.243,65.526,-85.226,65.546,-85.13,65.592,-85.105,65.623,-85.13,65.693,-85.176,65.747,-85.241,65.796,-85.442,65.846,-85.496,65.9,-85.523,65.915,-85.555,65.919,-85.699,65.883,-85.814,65.832,-85.962,65.704,-86.017,65.64,-86.075,65.534,-86.114,65.417,-86.188,65.01,-86.228,64.896,-86.344,64.662,-86.374,64.566,-86.375,64.503,-86.354,64.377,-86.274,64.238,-86.252,64.181,-86.252,64.137,-86.309,64.094,-86.422,64.052,-86.886,63.924,-86.932,63.902,-87.032,63.83,-87.154,63.715,-87.189,63.672,-87.194,63.633,-87.177,63.595,-87.152,63.586,-87.053,63.572,-86.915,63.569,-86.847,63.575,-86.576,63.662,-86.302,63.657,-85.805,63.707,-85.769,63.7,-85.739,63.684,-85.714,63.658,-85.566,63.271,-85.496,63.139,-85.393,63.12,-85.238,63.139,-84.962,63.197,-84.796,63.247,-84.633,63.309,-84.555,63.35,-84.506,63.39,-84.387,63.529,-84.308,63.586,-84.26,63.6,-84.142,63.614,-84.022,63.66,-83.728,63.813,-83.662,63.873,-83.638,63.918,-83.617,64.013,-83.584,64.058,-83.494,64.099,-83.304,64.144,-83.186,64.158,-83.065,64.159,-83.016,64.127,-83.039,64.061,-83.034,64.023,-82.93,64,-82.571,63.961,-82.467,63.927,-82.412,63.737,-82.378,63.707,-82.146,63.691,-81.963,63.664,-81.372,63.538,-81.18,63.483,-81.046,63.462,-81.014,63.463,-80.954,63.48,-80.712,63.596,-80.504,63.674,-80.302,63.762,-80.261,63.802,-80.451,63.863,-80.668,63.901]},{"b":[-77.306,67.236,-75.062,68.337],"l":[-75.676,68.323,-75.867,68.337,-75.983,68.332,-76.088,68.314,-76.173,68.309,-76.364,68.319,-76.596,68.279,-76.688,68.254,-76.74,68.231,-76.945,68.091,-77.126,67.947,-77.229,67.85,-77.306,67.706,-77.304,67.685,-77.224,67.508,-77.157,67.408,-77.076,67.32,-77.005,67.267,-76.944,67.25,-76.859,67.24,-76.694,67.236,-76.333,67.258,-76.049,67.262,-75.78,67.284,-75.4,67.367,-75.315,67.4,-75.202,67.459,-75.127,67.537,-75.091,67.635,-75.086,67.751,-75.127,67.965,-75.124,67.985,-75.073,68.049,-75.062,68.075,-75.063,68.141,-75.078,68.173,-75.103,68.202,-75.154,68.234,-75.676,68.323]},{"b":[-80.861,72.756,-76.09,73.767],"l":[-80.736,73.483,-80.777,73.482,-80.798,73.472,-80.823,73.429,-80.824,73.381,-80.776,73.334,-80.727,73.305,-80.618,73.271,-80.293,73.246,-80.183,73.225,-80.146,73.161,-80.114,73.078,-80.052,72.977,-79.975,72.892,-79.937,72.864,-79.821,72.826,-79.501,72.756,-79.319,72.758,-79.134,72.772,-78.554,72.858,-78.314,72.882,-77.836,72.897,-77.014,72.844,-76.401,72.821,-76.183,72.843,-76.09,72.881,-76.135,72.912,-76.255,72.959,-76.31,72.998,-76.29,73.081,-76.331,73.1,-76.458,73.122,-76.57,73.159,-76.622,73.225,-76.657,73.254,-76.759,73.31,-77.005,73.356,-77.042,73.373,-77.12,73.45,-77.207,73.5,-77.382,73.537,-78.063,73.648,-78.287,73.666,-79.367,73.641,-79.537,73.654,-79.889,73.702,-80.12,73.707,-80.412,73.765,-80.621,73.767,-80.763,73.758,-80.823,73.743,-80.849,73.721,-80.861,73.671,-80.858,73.591,-80.827,73.535,-80.736,73.483]},{"b":[-102.798,74.984,-97.336,76.694],"l":[-97.863,75.738,-97.891,75.76,-97.694,75.803,-97.602,75.851,-97.603,75.879,-97.652,75.94,-97.65,75.979,-97.613,76.053,-97.531,76.109,-97.524,76.139,-97.531,76.182,-97.573,76.224,-97.707,76.304,-97.739,76.335,-97.737,76.363,-97.702,76.387,-97.69,76.422,-97.701,76.467,-97.726,76.496,-97.808,76.519,-97.967,76.533,-98.236,76.575,-98.289,76.599,-98.528,76.667,-98.711,76.694,-98.941,76.643,-99.024,76.615,-98.971,76.537,-98.89,76.466,-99.17,76.454,-99.329,76.521,-99.669,76.624,-99.814,76.632,-100.069,76.635,-100.388,76.614,-100.574,76.585,-100.83,76.524,-100.891,76.475,-100.874,76.457,-100.82,76.437,-100.651,76.396,-100.175,76.359,-100.082,76.343,-99.978,76.312,-99.983,76.3,-100.043,76.291,-100.358,76.271,-100.414,76.257,-100.414,76.243,-100.183,76.197,-99.998,76.196,-99.817,76.168,-99.541,76.146,-99.79,76.133,-100.002,76.139,-100.086,76.134,-100.113,76.117,-100.051,76.067,-99.978,76.029,-99.689,75.96,-99.701,75.941,-99.775,75.927,-99.865,75.924,-100.02,75.94,-100.106,75.96,-100.231,76.008,-100.9,76.207,-101.056,76.246,-101.094,76.272,-101.088,76.308,-101.139,76.345,-101.34,76.41,-101.415,76.425,-101.677,76.451,-101.788,76.451,-101.858,76.439,-101.964,76.399,-102.105,76.331,-102.138,76.285,-101.987,76.243,-101.91,76.234,-101.557,76.236,-101.529,76.217,-101.771,76.15,-101.861,76.101,-101.872,76.083,-101.823,76.041,-101.717,76.008,-101.431,75.992,-101.508,75.944,-101.506,75.918,-101.47,75.882,-101.415,75.846,-101.288,75.789,-101.259,75.784,-101.01,75.802,-100.973,75.798,-101.119,75.763,-101.261,75.758,-101.421,75.782,-101.6,75.833,-101.943,75.884,-102.145,75.875,-102.271,75.813,-102.252,75.778,-102.411,75.713,-102.728,75.639,-102.798,75.6,-102.7,75.544,-102.587,75.514,-102.541,75.514,-101.461,75.608,-101.207,75.59,-100.902,75.62,-99.915,75.681,-99.195,75.698,-99.209,75.669,-99.591,75.655,-99.756,75.633,-99.77,75.612,-99.965,75.569,-100.28,75.461,-100.712,75.406,-100.704,75.394,-100.731,75.347,-100.615,75.321,-100.364,75.29,-100.146,75.246,-100.152,75.236,-100.459,75.219,-100.483,75.188,-100.357,75.067,-100.292,75.028,-100.234,75.008,-99.947,75.003,-99.627,74.984,-99.421,75.044,-99.326,75.049,-99.245,75.026,-99.156,75.016,-99.01,75.021,-98.835,75.018,-98.704,75.006,-98.569,75.009,-98.295,75.032,-98.121,75.033,-97.992,75.046,-97.953,75.06,-97.99,75.111,-98.077,75.153,-98.092,75.176,-98.069,75.199,-98.045,75.201,-97.971,75.153,-97.843,75.122,-97.799,75.117,-97.674,75.127,-97.66,75.151,-97.705,75.191,-97.853,75.26,-97.878,75.416,-97.653,75.508,-97.465,75.459,-97.363,75.417,-97.336,75.42,-97.41,75.552,-97.408,75.673,-97.44,75.685,-97.863,75.738]},{"b":[-105.58,77.794,-99,79.352],"l":[-104.901,79.051,-104.747,79.027,-104.735,78.991,-104.893,78.89,-104.97,78.856,-104.97,78.829,-104.896,78.808,-104.817,78.807,-104.736,78.826,-104.395,78.956,-104.152,78.99,-104.113,78.986,-104.007,78.948,-103.887,78.919,-103.876,78.903,-104.155,78.814,-104.195,78.796,-104.185,78.781,-104.009,78.764,-103.518,78.769,-103.408,78.752,-103.372,78.736,-103.563,78.693,-103.929,78.663,-104.021,78.635,-103.588,78.623,-103.483,78.594,-103.571,78.54,-103.764,78.52,-104.214,78.54,-104.727,78.579,-104.82,78.573,-104.91,78.553,-104.996,78.519,-104.985,78.468,-104.879,78.401,-104.764,78.352,-104.513,78.295,-104.324,78.269,-103.947,78.26,-103.677,78.32,-102.731,78.371,-102.784,78.33,-102.772,78.307,-102.723,78.275,-102.668,78.256,-102.607,78.249,-102.284,78.275,-102.057,78.28,-101.829,78.264,-101.298,78.199,-101.074,78.194,-100.958,78.13,-100.826,78.088,-100.81,78.072,-100.778,77.996,-100.758,77.978,-100.68,77.931,-100.586,77.892,-100.275,77.833,-99.956,77.794,-99.659,77.824,-99.341,77.84,-99.166,77.857,-99.128,77.877,-99.061,77.966,-99,77.997,-99.005,78.016,-99.053,78.072,-99.132,78.118,-99.562,78.279,-99.751,78.303,-99.778,78.325,-99.769,78.365,-99.774,78.393,-99.848,78.438,-99.818,78.455,-99.68,78.494,-99.631,78.545,-99.582,78.563,-99.609,78.583,-99.782,78.62,-100.015,78.729,-100.435,78.82,-100.917,78.783,-101.128,78.802,-101.147,78.824,-101.116,78.858,-101.034,78.915,-101.037,78.939,-101.088,78.962,-101.145,78.973,-101.299,78.982,-101.704,79.079,-101.873,79.088,-101.974,79.079,-102.189,79.038,-102.393,79.01,-102.407,78.954,-102.425,78.933,-102.495,78.901,-102.576,78.879,-102.593,78.901,-102.581,78.93,-102.596,78.943,-102.731,78.969,-102.683,78.991,-102.648,79.027,-102.638,79.055,-102.639,79.078,-102.652,79.095,-102.914,79.231,-103.192,79.295,-103.426,79.316,-103.706,79.352,-103.965,79.348,-104.847,79.311,-105.388,79.324,-105.436,79.302,-105.515,79.242,-105.571,79.164,-105.58,79.114,-105.571,79.061,-105.536,79.033,-105.309,79.033,-104.901,79.051]},{"b":[-96.773,78.15,-85.042,81.364],"l":[-91.755,81.049,-91.886,81.133,-91.998,81.185,-92.212,81.244,-92.413,81.278,-93.035,81.346,-93.333,81.364,-93.605,81.351,-94.06,81.349,-94.179,81.339,-94.22,81.331,-94.233,81.315,-94.231,81.29,-94.219,81.265,-94.194,81.241,-94.11,81.225,-93.894,81.213,-93.407,81.209,-93.286,81.179,-93.235,81.155,-93.236,81.129,-93.287,81.1,-93.345,81.085,-93.444,81.083,-93.826,81.106,-94.216,81.057,-94.519,81.031,-94.981,81.05,-95.27,81.001,-95.509,80.863,-95.515,80.838,-95.196,80.808,-94.788,80.751,-94.596,80.641,-94.202,80.61,-94.029,80.586,-93.928,80.559,-94.485,80.558,-94.734,80.572,-94.893,80.571,-95.026,80.646,-95.226,80.686,-95.505,80.691,-95.714,80.725,-95.927,80.721,-96.133,80.691,-96.152,80.553,-95.901,80.471,-95.614,80.396,-95.549,80.367,-95.747,80.365,-96.012,80.383,-96.112,80.38,-96.334,80.353,-96.385,80.333,-96.394,80.315,-96.368,80.293,-96.308,80.267,-96.215,80.246,-96.026,80.222,-95.904,80.214,-95.646,80.231,-95.405,80.135,-95.192,80.134,-94.59,80.202,-94.263,80.195,-94.304,80.182,-94.583,80.141,-94.607,80.126,-94.613,80.103,-94.6,80.074,-94.611,80.056,-94.646,80.049,-95.394,80.053,-95.782,80.066,-96.773,80.136,-96.639,80.024,-96.607,79.978,-96.589,79.917,-96.463,79.848,-96,79.705,-95.858,79.674,-95.739,79.66,-95.552,79.653,-95.297,79.653,-94.973,79.677,-94.581,79.726,-94.402,79.736,-94.476,79.686,-94.52,79.667,-95.302,79.568,-95.563,79.55,-95.663,79.527,-95.733,79.418,-95.657,79.39,-95.317,79.355,-95.103,79.29,-95.044,79.294,-94.846,79.335,-94.405,79.391,-94.284,79.4,-94.11,79.402,-93.96,79.396,-93.94,79.386,-94.04,79.357,-94.109,79.315,-94.093,79.303,-94.04,79.295,-93.933,79.291,-93.55,79.354,-93.381,79.368,-93.028,79.429,-92.822,79.45,-92.645,79.45,-92.485,79.439,-92.248,79.373,-91.693,79.365,-91.3,79.373,-91.344,79.361,-91.868,79.317,-92.547,79.283,-92.684,79.186,-92.842,79.156,-93.068,79.155,-93.294,79.14,-93.95,79.037,-94.163,78.994,-94.17,78.973,-94.154,78.951,-94.115,78.929,-93.902,78.872,-93.336,78.808,-93.16,78.776,-93.208,78.769,-93.561,78.777,-93.623,78.768,-93.634,78.751,-93.552,78.708,-93.389,78.643,-93.267,78.608,-93.109,78.602,-92.973,78.613,-92.716,78.605,-91.935,78.562,-91.867,78.543,-92.297,78.521,-92.726,78.487,-92.848,78.46,-92.808,78.43,-92.678,78.389,-92.351,78.313,-91.899,78.237,-91.41,78.188,-90.918,78.158,-90.614,78.15,-90.387,78.163,-90.327,78.185,-90.358,78.219,-90.405,78.247,-90.469,78.269,-90.652,78.308,-90.622,78.322,-90.459,78.331,-90.297,78.328,-90.136,78.313,-90.025,78.291,-89.965,78.262,-89.873,78.238,-89.651,78.193,-89.579,78.167,-89.526,78.16,-89.49,78.172,-89.507,78.203,-89.612,78.279,-89.757,78.37,-90.001,78.496,-90.076,78.549,-90.037,78.607,-89.995,78.601,-89.926,78.573,-89.655,78.439,-89.47,78.37,-89.096,78.209,-88.97,78.184,-88.822,78.186,-88.791,78.192,-88.733,78.242,-88.648,78.334,-88.606,78.392,-88.623,78.462,-88.714,78.546,-88.742,78.584,-88.709,78.596,-88.581,78.602,-88.285,78.497,-88.148,78.477,-88.04,78.494,-87.983,78.537,-87.974,78.565,-87.982,78.595,-88.003,78.616,-88.037,78.627,-88.228,78.653,-88.254,78.672,-88.19,78.696,-88.167,78.746,-88.19,78.867,-88.164,78.933,-88.104,78.973,-88.04,78.995,-87.878,79.038,-87.829,79.045,-87.817,79.036,-87.923,78.951,-87.953,78.915,-87.961,78.893,-87.956,78.852,-87.922,78.751,-87.861,78.707,-87.617,78.676,-87.479,78.718,-87.246,78.813,-87.08,78.866,-87.016,78.899,-86.957,78.975,-86.913,78.983,-86.721,78.975,-86.629,78.991,-86.451,79.039,-86.092,79.1,-85.29,79.208,-85.181,79.234,-85.042,79.285,-85.064,79.328,-85.176,79.387,-85.501,79.53,-85.648,79.611,-85.679,79.615,-85.751,79.595,-85.804,79.573,-85.949,79.486,-86.007,79.479,-86.086,79.551,-86.18,79.605,-86.232,79.622,-86.337,79.635,-86.649,79.646,-86.861,79.598,-86.925,79.591,-87.243,79.571,-87.295,79.58,-87.22,79.63,-87.144,79.663,-87.05,79.805,-86.977,79.894,-87.076,79.967,-87.202,80.043,-87.329,80.047,-87.651,80.079,-87.861,80.088,-87.922,80.098,-87.869,80.134,-87.625,80.187,-87.618,80.207,-87.63,80.302,-87.646,80.348,-87.675,80.372,-87.96,80.416,-88.125,80.429,-88.424,80.428,-88.525,80.418,-88.644,80.387,-88.663,80.348,-88.646,80.29,-88.612,80.255,-88.381,80.225,-88.255,80.167,-88.197,80.125,-88.2,80.111,-88.329,80.134,-88.538,80.131,-88.857,80.166,-89.019,80.198,-89.198,80.263,-89.218,80.289,-89.147,80.36,-89.155,80.379,-89.197,80.394,-89.204,80.407,-89.134,80.44,-89.138,80.457,-89.167,80.48,-89.236,80.511,-89.329,80.532,-89.525,80.539,-89.674,80.531,-89.798,80.501,-89.862,80.498,-90.218,80.548,-90.537,80.576,-90.643,80.594,-90.632,80.642,-90.637,80.655,-90.683,80.688,-91.054,80.778,-91.272,80.85,-91.755,81.049]},{"b":[-96.898,74.47,-79.401,77.066],"l":[-87.593,74.47,-87.364,74.502,-86.995,74.48,-86.77,74.479,-86.666,74.489,-86.731,74.557,-86.655,74.555,-86.341,74.513,-86.211,74.536,-86.11,74.54,-85.956,74.499,-85.808,74.499,-85.544,74.535,-85.512,74.545,-85.489,74.567,-85.474,74.6,-85.442,74.601,-85.339,74.543,-85.214,74.519,-85.133,74.517,-85.087,74.528,-85.061,74.607,-85.012,74.604,-84.916,74.568,-84.818,74.542,-84.667,74.52,-84.426,74.508,-84.245,74.515,-83.868,74.564,-83.622,74.566,-83.532,74.586,-83.412,74.655,-83.394,74.67,-83.341,74.765,-83.364,74.802,-83.487,74.834,-83.51,74.848,-83.544,74.892,-83.522,74.901,-83.407,74.885,-83.22,74.828,-83.158,74.817,-83.103,74.817,-83.087,74.788,-83.112,74.732,-83.117,74.693,-83.058,74.63,-82.978,74.583,-82.931,74.566,-82.736,74.53,-82.645,74.525,-82.415,74.535,-82.069,74.482,-81.94,74.473,-81.809,74.477,-81.607,74.502,-81.34,74.554,-81.226,74.567,-80.278,74.582,-80.263,74.584,-80.221,74.657,-80.21,74.704,-80.213,74.749,-80.192,74.78,-80.149,74.796,-80.19,74.828,-80.315,74.876,-80.348,74.903,-80.289,74.908,-80.202,74.895,-79.944,74.834,-79.508,74.88,-79.401,74.918,-79.46,74.959,-79.525,74.99,-79.664,75.021,-79.733,75.021,-80.036,74.991,-80.135,74.988,-80.261,75.002,-80.382,75.034,-80.358,75.052,-79.977,75.119,-79.634,75.199,-79.509,75.26,-79.508,75.295,-79.586,75.385,-79.66,75.45,-79.738,75.461,-80.1,75.467,-80.26,75.479,-80.287,75.49,-80.126,75.542,-80.119,75.562,-80.158,75.581,-80.322,75.629,-80.528,75.642,-81.001,75.643,-81.124,75.658,-81.174,75.669,-81.193,75.684,-81.151,75.736,-81.269,75.756,-81.647,75.795,-82.154,75.831,-82.354,75.833,-82.553,75.818,-83.093,75.756,-83.237,75.751,-83.745,75.813,-83.932,75.819,-84.014,75.78,-84.128,75.763,-84.605,75.653,-84.75,75.655,-84.987,75.645,-85.372,75.573,-85.581,75.58,-85.973,75.529,-86.069,75.502,-85.905,75.442,-85.951,75.395,-86.236,75.406,-86.437,75.436,-86.545,75.463,-86.814,75.491,-87.257,75.618,-87.365,75.591,-87.539,75.485,-87.572,75.494,-87.644,75.547,-87.73,75.576,-88.201,75.512,-88.569,75.645,-88.645,75.658,-88.715,75.659,-88.784,75.647,-88.852,75.625,-88.864,75.589,-88.82,75.539,-88.804,75.502,-88.839,75.463,-88.869,75.452,-88.917,75.454,-89.28,75.564,-89.337,75.572,-89.646,75.565,-89.625,75.584,-89.361,75.646,-89.257,75.698,-89.205,75.737,-89.205,75.762,-89.278,75.795,-89.511,75.857,-89.65,75.844,-89.695,75.854,-89.793,75.925,-89.913,75.966,-90.033,75.971,-90.176,76.03,-90.251,76.053,-90.712,76.076,-90.802,76.106,-91.02,76.142,-91.279,76.16,-91.407,76.22,-91.26,76.23,-90.827,76.186,-90.312,76.158,-89.407,76.189,-89.292,76.218,-89.237,76.239,-89.219,76.258,-89.285,76.302,-90.855,76.437,-91.334,76.446,-91.415,76.456,-91.443,76.499,-91.398,76.51,-91.336,76.511,-91.263,76.5,-90.864,76.484,-90.622,76.465,-90.543,76.496,-90.555,76.516,-90.605,76.543,-90.738,76.581,-91.124,76.662,-91.305,76.681,-91.548,76.685,-91.789,76.676,-92.297,76.616,-92.716,76.603,-92.995,76.62,-93.422,76.474,-93.535,76.448,-93.485,76.492,-93.426,76.527,-93.317,76.574,-93.264,76.626,-93.201,76.669,-93.19,76.686,-93.189,76.708,-93.212,76.755,-93.23,76.77,-93.277,76.784,-93.421,76.812,-93.608,76.874,-93.811,76.914,-93.948,76.917,-94.108,76.904,-94.295,76.912,-94.616,76.958,-95.126,77.017,-95.638,77.064,-95.85,77.066,-96.061,77.05,-96.377,77.005,-96.55,76.988,-96.685,76.985,-96.758,76.972,-96.77,76.948,-96.814,76.913,-96.771,76.889,-96.661,76.855,-96.433,76.811,-96.402,76.797,-96.451,76.774,-96.59,76.763,-96.679,76.766,-96.878,76.803,-96.898,76.773,-96.898,76.754,-96.881,76.738,-96.846,76.726,-96.64,76.703,-95.971,76.57,-95.873,76.566,-95.651,76.585,-95.696,76.563,-95.789,76.537,-96.013,76.513,-96.04,76.487,-95.959,76.446,-95.842,76.416,-95.447,76.363,-95.274,76.264,-94.997,76.258,-94.737,76.293,-94.585,76.297,-94.383,76.282,-93.852,76.27,-93.665,76.273,-93.644,76.289,-93.56,76.311,-93.309,76.36,-93.192,76.366,-93.092,76.354,-92.883,76.214,-92.709,76.114,-92.474,75.986,-92.307,75.915,-92.185,75.847,-92.142,75.797,-92.099,75.727,-92.069,75.658,-92.081,75.634,-92.11,75.611,-92.331,75.479,-92.411,75.406,-92.428,75.383,-92.427,75.346,-92.408,75.297,-92.389,75.263,-92.347,75.23,-92.207,75.181,-92.076,75.124,-92.06,75.101,-92.165,75.072,-92.174,75.051,-92.103,74.948,-91.962,74.793,-91.871,74.744,-91.666,74.699,-91.549,74.656,-91.508,74.651,-91.339,74.667,-91.168,74.646,-91.135,74.65,-91.164,74.71,-91.13,74.736,-90.88,74.818,-90.878,74.801,-90.958,74.745,-90.967,74.715,-90.784,74.696,-90.553,74.613,-90.362,74.61,-90.015,74.561,-89.844,74.549,-89.559,74.555,-89.45,74.568,-89.262,74.609,-89.196,74.638,-89.189,74.667,-89.197,74.698,-89.219,74.732,-89.192,74.745,-89.115,74.738,-89.058,74.747,-89.02,74.774,-88.98,74.788,-88.94,74.79,-88.908,74.764,-88.883,74.711,-88.851,74.69,-88.778,74.715,-88.682,74.802,-88.535,74.832,-88.488,74.829,-88.431,74.804,-88.34,74.785,-88.375,74.744,-88.477,74.667,-88.538,74.609,-88.558,74.57,-88.556,74.541,-88.501,74.51,-88.423,74.494,-88.006,74.489,-87.593,74.47]},{"b":[-98.341,77.806,-94.887,78.805],"l":[-96.603,77.849,-96.477,77.872,-96.012,77.887,-95.671,77.924,-95.452,77.963,-95.371,77.971,-95.199,77.968,-95.087,77.993,-94.934,78.076,-94.936,78.106,-94.988,78.136,-95.103,78.178,-95.329,78.225,-95.268,78.263,-95.014,78.313,-94.887,78.345,-94.888,78.36,-94.915,78.391,-95.031,78.43,-95.413,78.498,-95.561,78.517,-95.968,78.505,-96.204,78.531,-96.256,78.551,-96.243,78.573,-96.265,78.595,-96.475,78.665,-96.587,78.687,-96.936,78.72,-97.169,78.758,-97.382,78.783,-97.596,78.796,-98.043,78.805,-98.212,78.805,-98.333,78.774,-98.341,78.751,-98.29,78.692,-98.096,78.587,-98.06,78.558,-98.316,78.517,-98.324,78.498,-98.317,78.477,-98.276,78.438,-98.255,78.429,-98.114,78.403,-98.069,78.386,-98.05,78.326,-97.843,78.262,-97.819,78.231,-97.323,78.203,-97.027,78.157,-96.945,78.152,-96.958,78.139,-97.041,78.117,-97.227,78.103,-97.658,78.091,-97.648,78.072,-97.621,78.05,-97.427,77.982,-97.093,77.933,-97.019,77.908,-97.052,77.881,-97.064,77.859,-97.04,77.827,-96.99,77.806,-96.834,77.812,-96.603,77.849]},{"b":[-113.283,77.344,-110.004,78.103],"l":[-110.004,78.09,-110.458,78.103,-110.727,78.097,-110.873,78.081,-111.207,78.088,-112.305,78.007,-112.805,77.942,-113.022,77.919,-113.187,77.912,-113.215,77.904,-113.269,77.86,-113.283,77.836,-113.283,77.813,-113.271,77.778,-113.19,77.718,-113.168,77.676,-113.121,77.633,-113.137,77.618,-113.189,77.6,-113.209,77.58,-113.197,77.559,-113.164,77.53,-113.046,77.511,-112.926,77.475,-112.644,77.444,-112.373,77.364,-112.177,77.344,-111.952,77.344,-111.226,77.429,-111.06,77.433,-110.894,77.426,-110.683,77.446,-110.372,77.491,-110.198,77.525,-110.139,77.572,-110.117,77.625,-110.118,77.716,-110.131,77.742,-110.153,77.763,-110.189,77.777,-110.292,77.786,-110.719,77.781,-110.812,77.803,-110.857,77.82,-110.866,77.834,-110.751,77.857,-110.2,77.905,-110.004,77.929]},{"b":[-110.004,77.929,-109.619,78.09],"l":[-110.004,77.929,-109.772,77.957,-109.679,77.999,-109.619,78.057,-109.622,78.075,-109.657,78.079,-110.004,78.09]},{"b":[-122.903,75.825,-115.47,77.548],"l":[-118.644,76.418,-118.8,76.464,-118.821,76.486,-118.791,76.513,-118.732,76.526,-118.574,76.525,-118.468,76.547,-118.431,76.588,-118.409,76.662,-118.37,76.701,-118.301,76.737,-118.203,76.76,-118.076,76.772,-117.881,76.805,-117.818,76.804,-117.78,76.784,-117.808,76.734,-117.9,76.653,-117.965,76.574,-118.005,76.497,-118.02,76.447,-117.993,76.406,-117.841,76.345,-117.732,76.317,-117.492,76.273,-117.347,76.273,-117.234,76.282,-117.154,76.298,-117.108,76.322,-117.044,76.373,-117.026,76.404,-117.013,76.469,-117.017,76.496,-116.999,76.532,-116.468,76.577,-116.22,76.611,-116.076,76.654,-115.985,76.687,-115.946,76.711,-115.945,76.736,-116.016,76.785,-116.234,76.874,-116.253,76.901,-116.183,76.916,-116.11,76.918,-115.913,76.908,-115.81,76.939,-115.857,76.969,-116.073,77.03,-116.286,77.102,-116.329,77.137,-116.214,77.178,-115.624,77.266,-115.507,77.292,-115.47,77.309,-115.476,77.324,-115.551,77.363,-116.008,77.461,-116.209,77.516,-116.363,77.543,-116.511,77.548,-116.835,77.529,-116.947,77.504,-117.04,77.465,-117.046,77.449,-117.03,77.432,-116.766,77.398,-116.704,77.38,-116.795,77.347,-116.844,77.34,-117.061,77.348,-117.149,77.361,-117.211,77.331,-117.279,77.313,-117.419,77.317,-118.005,77.381,-118.82,77.333,-119.09,77.305,-119.324,77.241,-119.495,77.177,-119.831,77.074,-120.2,76.931,-120.311,76.905,-120.358,76.887,-120.438,76.816,-120.485,76.793,-120.998,76.691,-121.102,76.661,-121.204,76.622,-121.561,76.453,-121.614,76.441,-122.365,76.401,-122.423,76.39,-122.519,76.353,-122.774,76.228,-122.878,76.165,-122.903,76.135,-122.685,76.162,-122.624,76.167,-122.593,76.162,-122.588,76.153,-122.609,76.14,-122.609,76.121,-122.548,76.097,-122.546,76.081,-122.607,76.039,-122.646,76.031,-122.64,76.009,-122.591,75.973,-122.533,75.951,-122.4,75.944,-122.303,75.96,-122.057,76.018,-121.908,76.035,-121.695,76.02,-121.428,75.981,-121.32,75.977,-121.213,75.984,-121.019,76.02,-120.9,76.163,-120.848,76.183,-120.813,76.179,-120.772,76.166,-120.729,76.134,-120.637,76.034,-120.563,76.008,-120.514,75.958,-120.458,75.87,-120.409,75.826,-120.365,75.825,-120.161,75.852,-119.913,75.859,-119.735,75.915,-119.667,75.946,-119.608,75.985,-119.538,75.982,-119.526,75.997,-119.527,76.031,-119.55,76.052,-119.725,76.1,-119.74,76.118,-119.64,76.157,-119.636,76.19,-119.651,76.244,-119.649,76.28,-119.58,76.327,-119.524,76.34,-119.489,76.32,-119.448,76.275,-119.368,76.222,-119.249,76.159,-119.168,76.127,-119.081,76.124,-118.994,76.145,-118.955,76.168,-118.851,76.258,-118.812,76.277,-118.643,76.335,-118.625,76.366,-118.644,76.418]},{"b":[-117.6,74.402,-110.003,76.506],"l":[-110.003,75.539,-110.459,75.555,-110.726,75.56,-110.89,75.547,-111.053,75.549,-111.163,75.57,-111.276,75.613,-111.373,75.676,-111.454,75.762,-111.513,75.811,-111.549,75.822,-111.709,75.832,-111.877,75.826,-112.057,75.834,-112.081,75.847,-112.047,75.866,-111.868,75.911,-111.865,75.939,-112.334,76.072,-112.698,76.202,-112.978,76.245,-113.171,76.258,-113.363,76.248,-113.823,76.207,-113.923,76.229,-114.059,76.301,-114.101,76.331,-114.113,76.349,-114.116,76.396,-114.141,76.423,-114.194,76.451,-114.299,76.475,-114.535,76.502,-114.767,76.506,-114.998,76.497,-115.581,76.438,-115.779,76.365,-115.826,76.33,-115.832,76.296,-115.822,76.27,-115.797,76.253,-115.664,76.24,-115.025,76.211,-114.88,76.195,-114.779,76.173,-114.939,76.166,-115.768,76.184,-116.059,76.202,-116.21,76.194,-116.454,76.143,-116.591,76.096,-116.61,76.074,-116.55,76.017,-116.58,75.992,-116.665,75.958,-116.654,75.929,-116.444,75.891,-116.338,75.881,-115.602,75.895,-114.992,75.896,-115.174,75.867,-115.477,75.841,-115.838,75.841,-116.39,75.808,-116.802,75.772,-116.973,75.746,-117.039,75.718,-117.164,75.645,-117.138,75.617,-117.025,75.602,-116.426,75.585,-116.034,75.607,-115.122,75.706,-115.117,75.695,-115.142,75.679,-115.251,75.639,-115.335,75.618,-116.077,75.493,-116.213,75.483,-116.891,75.481,-117.154,75.473,-117.258,75.46,-117.336,75.442,-117.388,75.421,-117.513,75.357,-117.576,75.314,-117.597,75.293,-117.6,75.272,-117.565,75.233,-117.502,75.204,-117.005,75.156,-116.841,75.152,-116.476,75.172,-116.143,75.042,-115.729,74.968,-115.683,74.974,-115.609,75.01,-115.574,75.056,-115.537,75.081,-115.478,75.104,-115.413,75.115,-115.343,75.113,-115.28,75.102,-115.174,75.049,-115.128,75.009,-115.077,74.985,-115.02,74.976,-114.859,75,-114.452,75.088,-114.356,75.141,-114.358,75.171,-114.504,75.258,-114.514,75.275,-114.483,75.285,-114.429,75.281,-114.285,75.25,-114.168,75.24,-114.125,75.291,-114.075,75.392,-114.053,75.417,-114.017,75.434,-113.984,75.43,-113.916,75.388,-113.879,75.375,-113.589,75.412,-113.467,75.416,-113.503,75.397,-113.759,75.322,-113.811,75.296,-113.853,75.259,-113.886,75.211,-113.861,75.188,-113.855,75.129,-113.845,75.112,-113.795,75.084,-113.712,75.069,-113.34,75.093,-112.951,75.108,-112.855,75.121,-112.8,75.138,-112.703,75.187,-112.652,75.205,-112.597,75.212,-112.478,75.2,-112.256,75.134,-112.214,75.133,-112,75.142,-111.781,75.166,-111.621,75.168,-111.474,75.191,-111.181,75.26,-111.093,75.256,-111.033,75.227,-111.079,75.195,-111.258,75.128,-111.503,75.056,-111.671,75.019,-111.784,75.006,-111.956,75,-112.193,75.01,-112.663,74.994,-112.836,74.976,-113.324,74.875,-113.863,74.813,-114.132,74.766,-114.313,74.715,-114.377,74.671,-114.268,74.604,-114.175,74.574,-113.837,74.489,-113.672,74.453,-113.514,74.43,-113.017,74.402,-112.519,74.417,-111.729,74.502,-111.288,74.585,-110.941,74.639,-110.749,74.688,-110.625,74.753,-110.543,74.78,-110.387,74.814,-110.176,74.84,-110.003,74.851]},{"b":[-110.003,75.506,-108.9,76.244],"l":[-110.003,76.244,-109.908,76.223,-109.71,76.212,-109.487,76.145,-109.43,76.109,-109.417,76.072,-109.425,76.043,-109.455,76.021,-109.871,75.929,-109.796,75.863,-108.945,75.699,-108.918,75.675,-108.9,75.624,-108.913,75.587,-108.947,75.542,-109.005,75.515,-109.086,75.506,-110.003,75.539]},{"b":[-110.314,76.244,-110.003,76.48],"l":[-110.003,76.48,-110.27,76.417,-110.309,76.397,-110.314,76.369,-110.285,76.333,-110.247,76.306,-110.201,76.289,-110.003,76.244]},{"b":[-110.003,74.851,-105.481,76.821],"l":[-110.003,74.851,-109.503,74.883,-109.003,75.01,-108.831,75.065,-108.666,75.04,-108.633,75.023,-108.67,75.007,-108.751,74.992,-108.594,74.96,-108.475,74.947,-108.354,74.943,-108.227,74.952,-108.024,74.986,-107.82,75,-107.462,74.952,-107.153,74.927,-107.056,74.928,-106.961,74.94,-106.588,75.015,-106.093,75.089,-105.972,75.131,-105.863,75.192,-105.703,75.413,-105.678,75.501,-105.519,75.632,-105.481,75.702,-105.481,75.746,-105.563,75.881,-105.604,75.93,-105.633,75.945,-105.711,75.967,-105.905,76.009,-106.397,76.06,-106.529,76.053,-106.677,76.024,-106.804,75.975,-106.846,75.952,-106.862,75.93,-106.82,75.872,-106.759,75.842,-106.688,75.819,-106.693,75.81,-106.892,75.782,-106.903,75.742,-106.904,75.689,-106.914,75.68,-106.971,75.773,-107.05,75.845,-107.08,75.863,-107.136,75.879,-107.216,75.892,-107.418,75.907,-107.541,75.901,-107.703,75.878,-107.918,75.802,-107.951,75.796,-108.021,75.805,-107.97,75.84,-107.755,75.94,-107.732,75.956,-107.721,75.974,-107.723,75.995,-107.777,76.035,-107.852,76.058,-108.019,76.065,-108.166,76.054,-108.292,76.057,-108.387,76.067,-108.406,76.085,-108.382,76.116,-108.306,76.154,-108.178,76.2,-108.123,76.233,-108.194,76.33,-108.345,76.392,-108.512,76.439,-108.524,76.447,-108.539,76.503,-108.56,76.536,-108.628,76.587,-108.635,76.609,-108.612,76.63,-108.512,76.68,-108.478,76.708,-108.467,76.738,-108.492,76.754,-108.554,76.758,-108.832,76.821,-109.098,76.812,-109.22,76.792,-109.339,76.76,-109.505,76.692,-109.865,76.522,-109.982,76.485,-110.003,76.48]},{"b":[-95.648,72.001,-90.355,74.179],"l":[-91.088,74.009,-91.63,74.028,-91.874,74.013,-92.223,73.972,-92.314,73.992,-92.493,74.062,-92.587,74.083,-92.778,74.114,-93.171,74.161,-93.41,74.179,-93.549,74.167,-93.785,74.118,-93.939,74.132,-94.483,74.113,-94.729,74.086,-94.974,74.041,-95.04,74.024,-95.121,73.985,-95.145,73.96,-95.153,73.933,-95.149,73.906,-95.134,73.881,-95.059,73.805,-94.897,73.716,-94.797,73.686,-94.691,73.671,-94.698,73.664,-94.817,73.663,-94.996,73.686,-95.386,73.755,-95.447,73.752,-95.569,73.728,-95.633,73.695,-95.645,73.671,-95.648,73.639,-95.644,73.557,-95.604,73.328,-95.589,73.174,-95.592,73.115,-95.612,72.999,-95.613,72.942,-95.602,72.884,-95.58,72.831,-95.548,72.782,-95.251,72.502,-95.193,72.345,-95.167,72.18,-95.193,72.027,-95.008,72.013,-94.611,72.042,-94.497,72.044,-94.144,72.001,-94.038,72.029,-93.973,72.13,-93.871,72.253,-93.555,72.421,-93.542,72.437,-93.534,72.499,-93.546,72.531,-93.572,72.559,-93.771,72.668,-93.92,72.703,-94.152,72.736,-94.211,72.757,-93.579,72.801,-93.341,72.802,-92.392,72.718,-92.235,72.727,-92.118,72.754,-91.905,72.849,-91.788,72.915,-91.621,73.026,-91.46,73.145,-91.426,73.195,-91.466,73.214,-91.554,73.236,-91.298,73.285,-91.249,73.304,-91.068,73.416,-91.002,73.467,-90.975,73.502,-90.934,73.528,-90.765,73.581,-90.566,73.686,-90.466,73.754,-90.381,73.825,-90.355,73.869,-90.458,73.908,-90.627,73.952,-91.088,74.009]},{"b":[-99.564,68.461,-95.268,69.862],"l":[-95.585,68.835,-95.751,68.898,-95.855,68.954,-95.951,69.024,-96.061,69.125,-96.184,69.259,-96.3,69.344,-96.695,69.471,-96.875,69.51,-96.989,69.554,-97.096,69.615,-97.236,69.673,-97.278,69.68,-97.306,69.673,-97.351,69.641,-97.409,69.631,-97.439,69.643,-97.469,69.667,-97.46,69.683,-97.386,69.7,-97.383,69.712,-97.411,69.738,-97.604,69.802,-97.691,69.841,-97.791,69.862,-97.889,69.858,-98.081,69.833,-98.2,69.797,-98.239,69.78,-98.268,69.754,-98.301,69.692,-98.304,69.669,-98.289,69.629,-98.163,69.512,-98.041,69.457,-98.156,69.469,-98.222,69.485,-98.389,69.565,-98.476,69.579,-98.546,69.573,-98.548,69.545,-98.534,69.527,-98.495,69.499,-98.448,69.48,-98.537,69.478,-98.559,69.461,-98.535,69.426,-98.467,69.375,-98.45,69.354,-98.456,69.335,-98.504,69.308,-98.724,69.219,-98.912,69.168,-99.085,69.15,-99.456,69.131,-99.513,69.1,-99.557,69.054,-99.564,69.034,-99.495,68.96,-99.441,68.918,-99.318,68.876,-99.254,68.863,-99.091,68.863,-99.073,68.877,-99.094,68.899,-99.057,68.918,-98.964,68.933,-98.904,68.932,-98.879,68.916,-98.864,68.894,-98.859,68.864,-98.83,68.839,-98.775,68.817,-98.704,68.803,-98.54,68.798,-98.432,68.818,-98.376,68.842,-98.321,68.843,-98.296,68.831,-98.28,68.807,-98.273,68.772,-98.258,68.749,-98.235,68.739,-97.885,68.672,-97.705,68.626,-97.472,68.544,-97.264,68.528,-97.008,68.539,-96.599,68.461,-96.402,68.471,-96.268,68.508,-96.024,68.607,-95.895,68.627,-95.802,68.686,-95.686,68.736,-95.614,68.745,-95.466,68.747,-95.359,68.778,-95.295,68.805,-95.268,68.826,-95.32,68.873,-95.374,68.892,-95.438,68.881,-95.585,68.835]},{"b":[-118.994,69.65,-110.002,73.373],"l":[-110.002,72.982,-110.008,72.984,-110.095,72.992,-110.509,72.999,-110.661,73.008,-110.689,72.945,-110.554,72.861,-110.279,72.792,-110.197,72.759,-110.208,72.681,-110.205,72.661,-110.439,72.633,-110.513,72.6,-110.782,72.534,-110.959,72.432,-111.14,72.365,-111.185,72.357,-111.268,72.364,-111.284,72.378,-111.287,72.401,-111.277,72.425,-111.253,72.449,-111.265,72.459,-111.311,72.455,-111.447,72.408,-111.544,72.351,-111.675,72.3,-111.762,72.335,-111.895,72.356,-111.816,72.386,-111.611,72.436,-111.356,72.572,-111.25,72.669,-111.27,72.714,-111.455,72.766,-112.048,72.888,-112.454,72.937,-112.754,72.986,-113.074,72.995,-113.208,72.981,-113.292,72.95,-113.45,72.863,-113.491,72.822,-113.496,72.754,-113.486,72.722,-113.5,72.694,-113.578,72.652,-113.622,72.647,-113.692,72.673,-113.958,72.651,-114.174,72.624,-114.342,72.591,-114.458,72.58,-114.522,72.593,-114.498,72.626,-114.28,72.739,-114.178,72.805,-114.109,72.861,-114.075,72.907,-114.054,72.958,-114.046,73.015,-114.052,73.071,-114.095,73.18,-114.127,73.231,-114.164,73.27,-114.206,73.298,-114.302,73.331,-114.638,73.373,-115.552,73.213,-116.573,73.055,-116.972,72.959,-117.256,72.914,-117.552,72.831,-118.133,72.633,-118.375,72.534,-118.457,72.473,-118.481,72.428,-118.449,72.399,-118.39,72.37,-118.246,72.311,-118.207,72.287,-118.213,72.263,-118.369,72.205,-118.59,72.167,-118.945,71.986,-118.96,71.972,-118.984,71.913,-118.994,71.803,-118.988,71.764,-118.952,71.732,-118.868,71.687,-118.583,71.649,-118.372,71.64,-117.888,71.661,-117.742,71.659,-117.878,71.561,-118.148,71.526,-118.226,71.467,-118.222,71.449,-118.188,71.436,-117.936,71.392,-117.723,71.391,-117.337,71.435,-116.78,71.444,-115.587,71.546,-115.338,71.511,-115.303,71.494,-115.341,71.472,-115.472,71.466,-115.734,71.485,-115.98,71.469,-116.044,71.454,-116.045,71.423,-115.922,71.401,-115.892,71.382,-116.042,71.362,-116.228,71.359,-116.422,71.338,-116.815,71.277,-117.314,71.212,-117.814,71.158,-117.934,71.135,-118.269,71.035,-118.353,71,-118.377,70.968,-118.264,70.888,-117.587,70.63,-116.993,70.604,-116.327,70.624,-116.226,70.616,-116.086,70.591,-115.991,70.586,-115.311,70.601,-114.841,70.621,-114.593,70.642,-114.331,70.675,-114.232,70.674,-113.966,70.696,-113.757,70.691,-113.397,70.652,-113.146,70.616,-112.114,70.447,-111.726,70.352,-111.633,70.309,-111.705,70.286,-111.784,70.273,-112.19,70.276,-112.266,70.255,-112.523,70.229,-112.638,70.225,-113.211,70.264,-113.666,70.27,-113.917,70.282,-114.167,70.307,-114.592,70.312,-115.529,70.257,-116.554,70.175,-117.135,70.1,-117.163,70.092,-117.195,70.054,-117.184,69.991,-117.149,69.888,-117.122,69.826,-117.104,69.804,-116.993,69.719,-116.856,69.65]},{"b":[-116.856,68.477,-100.906,73.349],"l":[-116.856,69.65,-116.712,69.576,-116.609,69.512,-116.569,69.463,-116.537,69.434,-116.513,69.425,-116.102,69.337,-115.861,69.304,-115.618,69.283,-115.159,69.265,-114.699,69.273,-114.323,69.269,-114.073,69.251,-113.694,69.195,-113.681,69.182,-113.609,69.03,-113.593,68.96,-113.617,68.838,-113.555,68.768,-113.338,68.599,-113.231,68.535,-113.128,68.494,-113.02,68.481,-112.864,68.477,-112.666,68.485,-112.305,68.516,-111.518,68.533,-111.311,68.542,-111.128,68.588,-110.957,68.594,-110.848,68.578,-110.468,68.61,-109.959,68.63,-109.472,68.677,-108.946,68.76,-108.73,68.827,-108.553,68.897,-108.365,68.935,-107.863,68.954,-107.44,69.002,-107.353,69.032,-107.123,69.152,-107.033,69.181,-106.856,69.347,-106.76,69.407,-106.659,69.44,-106.54,69.443,-106.42,69.414,-106.361,69.381,-106.344,69.34,-106.356,69.281,-106.354,69.251,-106.341,69.224,-106.27,69.195,-106.141,69.162,-106.008,69.148,-105.805,69.153,-105.533,69.134,-105.262,69.094,-105.02,69.081,-105.014,69.068,-105.022,69.052,-105.148,68.978,-105.169,68.955,-105.106,68.92,-104.571,68.872,-104.46,68.912,-104.353,68.928,-104.067,68.866,-103.82,68.848,-103.468,68.809,-103.162,68.829,-102.895,68.824,-102.835,68.833,-102.738,68.865,-102.488,68.889,-102.359,68.923,-101.981,68.989,-101.857,69.024,-101.788,69.132,-101.789,69.182,-101.823,69.217,-101.873,69.24,-101.899,69.246,-101.993,69.236,-102.046,69.258,-102.064,69.281,-102.071,69.308,-102.067,69.337,-102.053,69.36,-101.976,69.407,-101.978,69.425,-102.046,69.465,-102.151,69.488,-102.447,69.476,-102.546,69.434,-102.777,69.378,-102.884,69.341,-103.09,69.212,-103.12,69.205,-103.113,69.236,-103.04,69.368,-103.032,69.433,-103.049,69.472,-103.063,69.485,-103.102,69.483,-103.142,69.497,-103.294,69.568,-103.418,69.611,-103.465,69.644,-103.435,69.668,-103.359,69.685,-103.303,69.674,-103.059,69.595,-102.92,69.565,-102.744,69.548,-102.621,69.552,-102.563,69.574,-102.541,69.592,-102.535,69.621,-102.545,69.66,-102.565,69.692,-102.596,69.718,-102.523,69.758,-102.348,69.813,-102.234,69.842,-102.182,69.846,-102.098,69.825,-101.86,69.738,-101.734,69.704,-101.648,69.699,-101.602,69.721,-101.565,69.756,-101.508,69.833,-101.484,69.85,-101.457,69.834,-101.4,69.749,-101.337,69.71,-101.216,69.68,-101.044,69.669,-100.982,69.68,-100.935,69.715,-100.906,69.812,-100.909,69.869,-100.973,70.029,-101.043,70.111,-101.091,70.136,-101.149,70.148,-101.239,70.151,-101.562,70.135,-101.618,70.172,-101.627,70.248,-101.641,70.266,-101.676,70.278,-101.732,70.286,-101.937,70.275,-101.99,70.285,-102.369,70.413,-102.589,70.469,-102.75,70.522,-103.05,70.655,-103.089,70.65,-103.083,70.619,-103.001,70.541,-103.005,70.526,-103.021,70.516,-103.077,70.509,-103.105,70.51,-103.197,70.547,-103.295,70.572,-103.585,70.631,-103.853,70.734,-103.953,70.763,-104.167,70.927,-104.515,71.064,-104.57,71.104,-104.563,71.132,-104.487,71.248,-104.437,71.3,-104.385,71.338,-104.356,71.382,-104.35,71.434,-104.355,71.472,-104.373,71.495,-104.386,71.577,-104.518,71.699,-104.767,71.868,-104.81,71.903,-104.878,71.98,-105.234,72.415,-105.247,72.464,-105.298,72.56,-105.323,72.635,-105.355,72.67,-105.411,72.709,-105.43,72.74,-105.412,72.765,-105.415,72.788,-105.496,72.849,-105.624,72.927,-105.813,73.011,-106.082,73.072,-106.482,73.196,-106.828,73.266,-106.951,73.276,-107.033,73.245,-107.074,73.197,-107.113,73.192,-107.496,73.288,-107.72,73.329,-108.029,73.349,-108.089,73.304,-108.077,73.281,-107.987,73.233,-107.936,73.217,-107.98,73.207,-108.118,73.202,-108.204,73.183,-108.237,73.15,-108.238,73.106,-107.997,72.653,-107.934,72.588,-107.924,72.557,-107.933,72.52,-107.91,72.491,-107.856,72.468,-107.824,72.443,-107.809,72.347,-107.794,72.303,-107.696,72.149,-107.543,72.025,-107.306,71.895,-107.377,71.886,-107.382,71.875,-107.369,71.859,-107.329,71.835,-107.347,71.819,-107.687,71.716,-107.757,71.663,-107.785,71.63,-107.813,71.626,-107.925,71.639,-108.021,71.677,-108.145,71.705,-108.188,71.724,-108.21,71.751,-108.276,71.9,-108.47,72.139,-108.566,72.317,-108.628,72.412,-108.698,72.499,-108.755,72.551,-108.798,72.568,-108.951,72.583,-108.994,72.596,-108.985,72.637,-108.968,72.654,-108.987,72.671,-109.043,72.687,-109.122,72.726,-109.357,72.775,-109.469,72.808,-109.61,72.876,-110.002,72.982]},{"b":[-125.845,71.088,-115.393,74.545],"l":[-122.623,74.464,-123.468,74.436,-124.696,74.348,-124.709,74.327,-124.645,74.304,-124.629,74.27,-124.575,74.248,-124.261,73.953,-124.192,73.902,-124.088,73.857,-123.873,73.828,-123.798,73.785,-123.797,73.768,-124.03,73.644,-124.114,73.527,-124.424,73.419,-124.594,73.243,-124.647,73.204,-124.804,73.126,-124.836,73.076,-124.817,73.059,-124.736,73.023,-124.643,73.019,-124.588,73.005,-124.57,72.989,-124.561,72.965,-124.565,72.944,-124.583,72.926,-124.931,72.863,-124.97,72.843,-125,72.813,-125.015,72.776,-125.015,72.731,-125.03,72.645,-125.019,72.617,-124.985,72.604,-124.987,72.588,-125.07,72.552,-125.168,72.523,-125.306,72.451,-125.383,72.424,-125.438,72.382,-125.512,72.308,-125.575,72.265,-125.627,72.255,-125.647,72.237,-125.634,72.21,-125.613,72.193,-125.584,72.183,-125.763,72.138,-125.769,72.129,-125.76,72.083,-125.768,72.054,-125.79,72.025,-125.845,71.979,-125.829,71.966,-125.767,71.961,-125.297,71.973,-125.215,71.955,-125.126,71.924,-124.76,71.835,-124.008,71.677,-123.953,71.652,-123.756,71.528,-123.682,71.493,-123.595,71.423,-123.393,71.219,-123.315,71.169,-123.211,71.123,-123.096,71.094,-122.937,71.088,-122.84,71.097,-122.72,71.128,-122.55,71.194,-122.157,71.266,-121.749,71.445,-121.701,71.451,-121.622,71.448,-121.547,71.407,-121.472,71.389,-121.16,71.415,-120.93,71.446,-120.619,71.506,-120.52,71.557,-120.461,71.605,-120.443,71.631,-120.366,71.888,-120.31,71.984,-120.194,72.127,-120.18,72.213,-120.09,72.229,-119.767,72.244,-119.513,72.303,-119.408,72.36,-119.132,72.609,-119.078,72.64,-118.962,72.684,-117.983,72.902,-117.464,73.038,-117.065,73.107,-116.483,73.253,-116.239,73.295,-115.992,73.323,-115.524,73.417,-115.447,73.439,-115.412,73.465,-115.393,73.502,-115.408,73.542,-115.456,73.585,-115.511,73.619,-115.634,73.666,-115.958,73.748,-116.722,74.027,-116.95,74.101,-117.199,74.171,-117.515,74.232,-117.707,74.252,-117.966,74.266,-118.2,74.267,-118.544,74.245,-118.625,74.233,-118.744,74.192,-119.026,74.045,-119.083,74.021,-119.118,74.016,-119.132,74.028,-119.139,74.128,-119.15,74.168,-119.171,74.186,-119.206,74.198,-119.315,74.206,-119.471,74.201,-119.729,74.108,-119.736,74.113,-119.737,74.13,-119.715,74.154,-119.563,74.233,-119.944,74.254,-120.554,74.353,-120.882,74.421,-121.129,74.49,-121.315,74.53,-121.504,74.545,-121.748,74.541,-122.623,74.464]},{"b":[-56.243,46.753,-56.137,46.811],"l":[-56.243,46.767,-56.172,46.753,-56.151,46.762,-56.139,46.779,-56.137,46.802,-56.153,46.811,-56.185,46.807,-56.209,46.798,-56.243,46.767]},{"b":[-94.901,75.749,-94.296,75.996],"l":[-94.745,75.957,-94.84,75.954,-94.881,75.946,-94.901,75.931,-94.894,75.912,-94.86,75.889,-94.834,75.859,-94.815,75.821,-94.787,75.791,-94.751,75.77,-94.624,75.749,-94.527,75.749,-94.33,75.766,-94.304,75.776,-94.296,75.788,-94.332,75.826,-94.414,75.885,-94.443,75.917,-94.471,75.971,-94.499,75.992,-94.538,75.996,-94.745,75.957]},{"b":[-79.644,51.945,-79.27,52.098],"l":[-79.644,52.01,-79.597,51.978,-79.521,51.953,-79.426,51.945,-79.384,51.952,-79.352,51.968,-79.329,51.992,-79.317,52.024,-79.27,52.071,-79.271,52.087,-79.335,52.098,-79.644,52.01]},{"b":[-78.914,56.131,-78.657,56.439],"l":[-78.669,56.439,-78.725,56.439,-78.762,56.421,-78.799,56.383,-78.822,56.34,-78.828,56.29,-78.857,56.232,-78.907,56.166,-78.914,56.133,-78.877,56.131,-78.827,56.145,-78.761,56.175,-78.71,56.213,-78.673,56.26,-78.657,56.317,-78.669,56.439]},{"b":[-80.008,55.807,-78.907,56.6],"l":[-79.765,55.807,-79.606,55.876,-79.527,55.871,-79.495,55.875,-79.455,55.896,-79.407,55.935,-79.222,56.176,-79.182,56.212,-79.142,56.181,-79.136,56.16,-79.142,56.136,-79.274,55.922,-79.228,55.879,-79.175,55.885,-79.084,56.068,-79.018,56.165,-78.936,56.266,-78.907,56.32,-78.931,56.328,-78.942,56.345,-78.94,56.371,-78.963,56.422,-78.995,56.436,-79.078,56.454,-79.1,56.474,-79.124,56.52,-79.155,56.538,-79.21,56.549,-79.246,56.568,-79.261,56.596,-79.272,56.6,-79.305,56.463,-79.339,56.376,-79.393,56.276,-79.458,56.211,-79.536,56.18,-79.554,56.192,-79.512,56.247,-79.476,56.313,-79.432,56.447,-79.435,56.513,-79.448,56.537,-79.459,56.54,-79.469,56.523,-79.468,56.46,-79.482,56.404,-79.515,56.327,-79.596,56.244,-79.79,56.114,-80.001,55.932,-80.008,55.911,-79.987,55.892,-79.905,55.871,-79.781,55.941,-79.565,56.121,-79.545,56.128,-79.495,56.115,-79.497,56.093,-79.765,55.807]},{"b":[-79.946,56.757,-79.827,56.865],"l":[-79.946,56.827,-79.944,56.777,-79.894,56.757,-79.867,56.775,-79.835,56.816,-79.827,56.843,-79.861,56.864,-79.898,56.865,-79.946,56.827]},{"b":[-79.838,57.442,-79.713,57.608],"l":[-79.775,57.514,-79.732,57.508,-79.717,57.516,-79.713,57.555,-79.727,57.605,-79.743,57.608,-79.768,57.599,-79.811,57.559,-79.819,57.542,-79.816,57.518,-79.838,57.483,-79.808,57.442,-79.792,57.449,-79.775,57.514]},{"b":[-80.089,56.199,-79.58,56.466],"l":[-79.688,56.327,-79.633,56.387,-79.58,56.466,-79.606,56.459,-79.681,56.404,-79.749,56.377,-79.81,56.377,-79.852,56.367,-79.874,56.348,-80.005,56.318,-80.057,56.287,-80.089,56.214,-80.029,56.199,-79.978,56.207,-79.688,56.327]},{"b":[-80.325,59.621,-80.167,59.725],"l":[-80.299,59.674,-80.325,59.633,-80.317,59.621,-80.285,59.624,-80.241,59.645,-80.183,59.683,-80.167,59.709,-80.21,59.725,-80.257,59.679,-80.299,59.674]},{"b":[-80.074,53.274,-79.938,53.364],"l":[-80.05,53.364,-80.074,53.344,-80.068,53.324,-80.039,53.297,-80.004,53.28,-79.939,53.274,-79.938,53.304,-79.975,53.352,-80.05,53.364]},{"b":[-80.167,59.764,-79.899,59.877],"l":[-79.95,59.81,-79.899,59.853,-79.956,59.877,-80.041,59.87,-80.084,59.852,-80.122,59.823,-80.167,59.764,-80.064,59.771,-79.95,59.81]},{"b":[-79.584,56.644,-79.482,56.799],"l":[-79.551,56.733,-79.577,56.645,-79.553,56.644,-79.518,56.657,-79.497,56.667,-79.485,56.687,-79.482,56.714,-79.491,56.743,-79.511,56.771,-79.553,56.799,-79.57,56.796,-79.584,56.781,-79.582,56.765,-79.551,56.733]},{"b":[-97.098,72.927,-96.598,73.189],"l":[-96.944,72.927,-96.782,72.937,-96.709,72.947,-96.671,72.961,-96.635,72.992,-96.604,73.042,-96.598,73.074,-96.646,73.102,-96.744,73.126,-96.768,73.137,-96.793,73.165,-96.862,73.189,-97.015,73.157,-97.069,73.13,-97.088,73.098,-97.098,73.062,-97.093,72.997,-96.944,72.927]},{"b":[-97.75,74.466,-97.291,74.627],"l":[-97.356,74.526,-97.304,74.56,-97.291,74.576,-97.318,74.598,-97.417,74.627,-97.516,74.602,-97.75,74.511,-97.722,74.489,-97.656,74.466,-97.356,74.526]},{"b":[-99.417,73.812,-97.659,74.115],"l":[-98.974,73.812,-98.817,73.817,-98.761,73.829,-98.691,73.856,-98.558,73.847,-98.27,73.869,-98.147,73.889,-97.861,73.968,-97.755,74.006,-97.673,74.053,-97.659,74.072,-97.667,74.09,-97.698,74.109,-97.8,74.115,-98.061,74.105,-98.585,74.035,-98.818,74.021,-98.904,74.007,-98.967,73.988,-99.005,73.965,-99.097,73.948,-99.346,73.926,-99.404,73.911,-99.417,73.895,-99.385,73.879,-99.298,73.862,-98.974,73.812]},{"b":[-90.492,69.252,-90.177,69.436],"l":[-90.177,69.357,-90.2,69.419,-90.229,69.436,-90.253,69.418,-90.322,69.429,-90.377,69.416,-90.455,69.39,-90.492,69.37,-90.465,69.329,-90.364,69.263,-90.33,69.252,-90.295,69.258,-90.267,69.273,-90.177,69.357]},{"b":[-90.772,69.209,-90.485,69.374],"l":[-90.6,69.368,-90.663,69.374,-90.742,69.357,-90.766,69.336,-90.772,69.293,-90.686,69.287,-90.667,69.259,-90.626,69.251,-90.574,69.209,-90.493,69.221,-90.485,69.247,-90.511,69.29,-90.54,69.325,-90.6,69.368]},{"b":[-74.626,62.61,-73.988,62.733],"l":[-74.395,62.696,-74.501,62.727,-74.564,62.733,-74.62,62.726,-74.626,62.713,-74.5,62.669,-74.254,62.622,-74.054,62.61,-74,62.618,-73.988,62.636,-74.017,62.663,-74.109,62.68,-74.395,62.696]},{"b":[-75.403,68.342,-74.798,68.696],"l":[-74.798,68.458,-74.819,68.494,-74.885,68.545,-74.984,68.648,-75.075,68.685,-75.2,68.696,-75.287,68.688,-75.37,68.636,-75.396,68.589,-75.403,68.55,-75.4,68.525,-75.31,68.474,-75.073,68.404,-74.959,68.342,-74.881,68.349,-74.845,68.366,-74.819,68.394,-74.813,68.413,-74.828,68.424,-74.831,68.441,-74.798,68.458]},{"b":[-78.669,60.717,-78.242,60.819],"l":[-78.612,60.772,-78.669,60.731,-78.669,60.717,-78.532,60.729,-78.372,60.756,-78.279,60.784,-78.242,60.819,-78.4,60.808,-78.612,60.772]},{"b":[-79.175,68.182,-78.829,68.353],"l":[-79.153,68.335,-79.175,68.264,-79.174,68.235,-79.064,68.182,-78.983,68.193,-78.829,68.268,-78.869,68.31,-78.953,68.353,-79.153,68.335]},{"b":[-77.365,63.384,-76.652,63.682],"l":[-76.652,63.504,-76.764,63.574,-77.134,63.682,-77.365,63.588,-77.057,63.45,-76.922,63.406,-76.783,63.384,-76.678,63.394,-76.652,63.504]},{"b":[-64.956,62.384,-64.418,62.559],"l":[-64.465,62.536,-64.515,62.552,-64.632,62.548,-64.824,62.559,-64.849,62.543,-64.85,62.525,-64.827,62.505,-64.842,62.494,-64.931,62.485,-64.956,62.458,-64.901,62.421,-64.837,62.406,-64.657,62.384,-64.546,62.391,-64.478,62.418,-64.418,62.487,-64.465,62.536]},{"b":[-62.872,67.029,-62.396,67.19],"l":[-62.625,67.177,-62.664,67.148,-62.757,67.113,-62.825,67.072,-62.872,67.063,-62.805,67.029,-62.682,67.056,-62.485,67.134,-62.396,67.178,-62.417,67.188,-62.47,67.19,-62.625,67.177]},{"b":[-68.368,60.233,-67.819,60.588],"l":[-68.088,60.588,-68.142,60.562,-68.235,60.456,-68.338,60.361,-68.368,60.315,-68.365,60.254,-68.324,60.233,-68.234,60.241,-68.012,60.305,-67.922,60.34,-67.844,60.392,-67.819,60.45,-67.848,60.489,-67.914,60.54,-67.978,60.57,-68.088,60.588]},{"b":[-71.22,62.545,-70.269,62.878],"l":[-70.367,62.666,-70.443,62.734,-70.674,62.807,-70.835,62.84,-71.014,62.865,-71.135,62.878,-71.22,62.874,-71.137,62.816,-70.986,62.788,-70.851,62.704,-70.838,62.648,-70.766,62.597,-70.687,62.573,-70.542,62.552,-70.406,62.545,-70.337,62.549,-70.289,62.562,-70.269,62.578,-70.292,62.616,-70.367,62.666]},{"b":[-65.432,61.354,-64.67,61.686],"l":[-64.788,61.413,-64.732,61.438,-64.696,61.471,-64.691,61.539,-64.67,61.593,-64.756,61.638,-64.79,61.662,-64.954,61.685,-65.13,61.686,-65.332,61.668,-65.432,61.65,-65.427,61.611,-65.394,61.563,-65.092,61.453,-65.054,61.432,-64.954,61.41,-64.88,61.357,-64.857,61.354,-64.833,61.366,-64.788,61.413]},{"b":[-68.221,69.535,-67.755,69.682],"l":[-67.755,69.631,-67.829,69.675,-67.909,69.682,-67.989,69.679,-68.093,69.657,-68.221,69.617,-68.202,69.58,-67.94,69.535,-67.915,69.541,-67.845,69.592,-67.755,69.631]},{"b":[-80.795,69.51,-79.365,69.81],"l":[-79.553,69.631,-79.402,69.685,-79.365,69.712,-79.39,69.73,-79.431,69.788,-79.594,69.81,-79.715,69.796,-79.87,69.756,-79.971,69.739,-80.062,69.746,-80.125,69.737,-80.169,69.782,-80.214,69.802,-80.295,69.794,-80.424,69.798,-80.438,69.783,-80.451,69.745,-80.466,69.737,-80.653,69.751,-80.727,69.74,-80.778,69.71,-80.795,69.689,-80.778,69.677,-80.448,69.65,-80.398,69.633,-80.33,69.587,-80.3,69.583,-80.269,69.6,-80.244,69.593,-80.227,69.562,-80.161,69.536,-80.047,69.514,-79.978,69.51,-79.954,69.523,-79.971,69.556,-80.048,69.634,-79.882,69.609,-79.553,69.631]},{"b":[-78.848,69.48,-77.969,69.74],"l":[-78.579,69.639,-78.789,69.523,-78.848,69.483,-78.795,69.48,-78.662,69.503,-78.552,69.492,-78.47,69.503,-78.307,69.552,-78.04,69.608,-77.969,69.639,-77.978,69.665,-78.029,69.715,-78.145,69.739,-78.201,69.74,-78.262,69.717,-78.267,69.687,-78.296,69.667,-78.344,69.675,-78.402,69.651,-78.579,69.639]},{"b":[-77.966,63.946,-77.564,64.037],"l":[-77.714,63.946,-77.642,63.992,-77.564,64.022,-77.569,64.03,-77.617,64.037,-77.711,64.036,-77.931,64.015,-77.966,63.993,-77.958,63.976,-77.929,63.962,-77.714,63.946]},{"b":[-83.238,66.199,-82.931,66.336],"l":[-83.06,66.199,-83.011,66.208,-82.931,66.257,-82.948,66.272,-83.024,66.271,-83.123,66.283,-83.222,66.336,-83.238,66.332,-83.233,66.303,-83.214,66.277,-83.148,66.234,-83.06,66.199]},{"b":[-79.406,68.839,-78.229,69.39],"l":[-78.853,68.916,-78.779,68.95,-78.705,69.014,-78.596,69.079,-78.56,69.106,-78.552,69.129,-78.533,69.146,-78.439,69.199,-78.287,69.263,-78.229,69.304,-78.234,69.315,-78.272,69.361,-78.3,69.379,-78.333,69.386,-78.458,69.39,-78.597,69.371,-78.65,69.351,-78.689,69.325,-78.689,69.3,-78.65,69.275,-78.662,69.262,-78.772,69.252,-78.804,69.235,-78.9,69.135,-78.93,69.123,-79.145,69.087,-79.243,69.049,-79.305,68.992,-79.355,68.956,-79.391,68.94,-79.406,68.923,-79.39,68.89,-79.361,68.858,-79.28,68.839,-79.211,68.845,-79.054,68.883,-78.853,68.916]},{"b":[-77.379,69.132,-76.669,69.44],"l":[-76.994,69.412,-77.109,69.437,-77.188,69.44,-77.319,69.416,-77.341,69.404,-77.352,69.379,-77.358,69.312,-77.379,69.274,-77.322,69.194,-77.276,69.162,-77.215,69.138,-77.122,69.132,-76.995,69.144,-76.911,69.175,-76.869,69.225,-76.81,69.267,-76.687,69.328,-76.67,69.349,-76.669,69.366,-76.684,69.38,-76.746,69.404,-76.994,69.412]},{"b":[-65.235,61.733,-64.846,61.943],"l":[-65.166,61.798,-64.928,61.733,-64.897,61.733,-64.847,61.762,-64.846,61.78,-64.865,61.798,-64.924,61.824,-64.947,61.843,-64.961,61.872,-64.981,61.881,-65.008,61.87,-65.031,61.879,-65.068,61.926,-65.126,61.942,-65.174,61.943,-65.211,61.928,-65.235,61.898,-65.23,61.864,-65.166,61.798]},{"b":[-87.323,69.976,-86.515,70.147],"l":[-86.984,70.011,-86.855,69.986,-86.734,69.976,-86.558,69.995,-86.515,70.017,-86.531,70.048,-86.563,70.077,-86.613,70.106,-86.691,70.115,-86.799,70.105,-86.913,70.113,-87.107,70.147,-87.168,70.127,-87.323,70.102,-87.323,70.08,-87.264,70.044,-87.191,70.019,-87.044,70,-86.984,70.011]},{"b":[-84.467,65.63,-83.234,66.131],"l":[-84.37,66.012,-84.271,65.991,-84.223,65.97,-84.193,65.942,-84.143,65.916,-84.13,65.877,-84.118,65.772,-84.008,65.752,-83.939,65.758,-83.814,65.788,-83.787,65.77,-83.702,65.756,-83.798,65.71,-83.809,65.678,-83.788,65.669,-83.65,65.658,-83.631,65.662,-83.644,65.679,-83.636,65.692,-83.607,65.701,-83.583,65.699,-83.537,65.669,-83.495,65.656,-83.381,65.63,-83.332,65.631,-83.264,65.668,-83.234,65.697,-83.234,65.715,-83.263,65.723,-83.469,65.735,-83.598,65.757,-83.726,65.797,-83.765,65.831,-83.715,65.861,-83.694,65.89,-83.701,65.92,-83.787,65.966,-83.95,66.027,-84.122,66.078,-84.407,66.131,-84.456,66.106,-84.467,66.088,-84.451,66.064,-84.37,66.012]},{"b":[-86.96,67.735,-86.382,68.306],"l":[-86.702,68.306,-86.834,68.23,-86.885,68.191,-86.899,68.163,-86.949,68.119,-86.96,68.1,-86.938,68.068,-86.847,68.01,-86.895,67.938,-86.908,67.902,-86.908,67.867,-86.893,67.837,-86.861,67.81,-86.706,67.75,-86.638,67.735,-86.596,67.736,-86.546,67.752,-86.49,67.784,-86.447,67.817,-86.396,67.889,-86.382,67.927,-86.39,67.989,-86.42,68.074,-86.43,68.139,-86.421,68.183,-86.452,68.225,-86.57,68.288,-86.702,68.306]},{"b":[-85.176,65.564,-84.602,66.025],"l":[-85.15,66.015,-85.176,65.972,-85.174,65.944,-85.144,65.885,-85.136,65.821,-85.096,65.756,-85.072,65.737,-84.931,65.689,-84.869,65.651,-84.83,65.599,-84.783,65.57,-84.727,65.564,-84.675,65.575,-84.626,65.604,-84.602,65.631,-84.603,65.657,-84.692,65.793,-84.757,65.859,-84.87,65.942,-84.889,65.972,-84.92,65.997,-84.939,66.009,-85.031,66.025,-85.15,66.015]},{"b":[-93.197,61.842,-92.993,61.935],"l":[-93.076,61.935,-93.197,61.919,-93.177,61.893,-93.085,61.842,-93.044,61.844,-93,61.867,-92.993,61.89,-93.076,61.935]},{"b":[-104.406,75.763,-102.008,76.311],"l":[-102.58,75.78,-102.511,75.808,-102.423,75.869,-102.318,75.895,-102.047,75.928,-102.008,75.939,-102.018,75.954,-102.227,76.015,-102.426,76.086,-102.49,76.095,-102.536,76.196,-102.584,76.282,-102.728,76.307,-103.098,76.311,-103.571,76.258,-104.012,76.223,-104.351,76.182,-104.406,76.108,-104.242,76.047,-103.985,76.047,-103.801,76.037,-103.985,75.933,-103.77,75.892,-103.202,75.958,-103.042,75.919,-103.245,75.823,-103.315,75.764,-102.944,75.763,-102.58,75.78]},{"b":[-101.613,76.575,-100.269,76.752],"l":[-100.269,76.734,-100.467,76.75,-100.622,76.752,-100.886,76.743,-100.962,76.734,-101.165,76.665,-101.509,76.628,-101.613,76.605,-101.605,76.587,-101.485,76.575,-101.226,76.579,-100.747,76.649,-100.269,76.734]},{"b":[-104.603,76.326,-103.034,76.666],"l":[-103.472,76.329,-103.311,76.348,-103.2,76.371,-103.083,76.405,-103.034,76.431,-103.051,76.45,-103.19,76.477,-103.585,76.539,-103.613,76.563,-103.723,76.601,-103.821,76.598,-103.973,76.578,-104.023,76.583,-103.969,76.614,-103.959,76.639,-103.992,76.657,-104.075,76.666,-104.205,76.666,-104.5,76.63,-104.586,76.606,-104.603,76.583,-104.577,76.54,-104.506,76.479,-104.408,76.365,-104.358,76.335,-104.271,76.326,-103.472,76.329]},{"b":[-103.274,78.126,-102.788,78.271],"l":[-103.11,78.246,-103.26,78.183,-103.274,78.166,-103.271,78.151,-103.252,78.138,-103.118,78.126,-103.003,78.146,-102.788,78.218,-102.826,78.25,-102.892,78.271,-102.973,78.267,-103.11,78.246]},{"b":[-102.475,77.687,-101.002,77.9],"l":[-102.08,77.692,-101.831,77.687,-101.694,77.697,-101.585,77.718,-101.398,77.729,-101.002,77.735,-101.02,77.762,-101.046,77.778,-101.128,77.813,-101.193,77.83,-101.322,77.854,-101.639,77.892,-101.918,77.9,-102.263,77.889,-102.448,77.881,-102.472,77.873,-102.475,77.837,-102.458,77.77,-102.378,77.728,-102.08,77.692]},{"b":[-90.562,76.488,-89.694,76.837],"l":[-89.949,76.836,-90.136,76.837,-90.41,76.81,-90.525,76.788,-90.562,76.754,-90.556,76.735,-90.441,76.663,-90.294,76.579,-90.165,76.524,-90.054,76.495,-89.974,76.488,-89.924,76.501,-89.773,76.494,-89.726,76.507,-89.726,76.521,-89.773,76.534,-89.805,76.561,-89.822,76.602,-89.822,76.631,-89.788,76.66,-89.709,76.701,-89.694,76.72,-89.695,76.741,-89.725,76.763,-89.775,76.782,-89.949,76.836]},{"b":[-97.021,75.369,-95.96,75.655],"l":[-96.428,75.606,-96.523,75.584,-96.856,75.538,-96.983,75.51,-97.021,75.468,-96.97,75.413,-96.915,75.38,-96.857,75.369,-96.723,75.381,-96.679,75.394,-96.622,75.431,-96.462,75.494,-96.344,75.506,-96.237,75.475,-96.156,75.477,-96.079,75.51,-95.969,75.542,-95.96,75.554,-96.04,75.586,-96.145,75.614,-96.368,75.655,-96.397,75.647,-96.417,75.631,-96.428,75.606]},{"b":[-95.851,74.5,-95.274,74.637],"l":[-95.51,74.637,-95.66,74.637,-95.746,74.616,-95.774,74.599,-95.851,74.582,-95.834,74.569,-95.777,74.551,-95.442,74.506,-95.352,74.5,-95.307,74.505,-95.274,74.519,-95.278,74.54,-95.353,74.586,-95.51,74.637]},{"b":[-115.029,77.721,-113.618,78.078],"l":[-113.898,77.916,-114.087,77.978,-114.181,77.998,-114.28,78.004,-114.303,78.033,-114.297,78.063,-114.33,78.078,-114.607,78.04,-114.726,78.016,-114.79,77.993,-114.89,77.977,-115.029,77.968,-114.98,77.915,-114.608,77.769,-114.287,77.721,-114.106,77.721,-113.832,77.755,-113.726,77.776,-113.619,77.813,-113.618,77.832,-113.697,77.869,-113.721,77.89,-113.768,77.904,-113.898,77.916]},{"b":[-121.241,75.741,-120.879,75.938],"l":[-121.042,75.903,-121.018,75.884,-121.015,75.868,-121.026,75.848,-121.221,75.777,-121.241,75.752,-121.154,75.741,-121.076,75.745,-121.007,75.766,-120.955,75.789,-120.921,75.814,-120.897,75.845,-120.879,75.907,-120.888,75.928,-120.914,75.938,-120.993,75.927,-121.042,75.903]},{"b":[-114.835,76.711,-113.488,76.895],"l":[-113.712,76.711,-113.561,76.743,-113.488,76.783,-113.517,76.825,-113.585,76.847,-113.708,76.873,-113.892,76.895,-114.42,76.875,-114.647,76.851,-114.835,76.795,-114.808,76.774,-114.751,76.759,-113.712,76.711]},{"b":[-104.887,75.031,-103.642,75.43],"l":[-103.746,75.252,-103.758,75.289,-103.804,75.346,-103.851,75.371,-103.917,75.392,-104.075,75.425,-104.346,75.43,-104.474,75.413,-104.649,75.35,-104.69,75.321,-104.801,75.211,-104.848,75.173,-104.882,75.16,-104.887,75.148,-104.828,75.12,-104.634,75.061,-104.309,75.031,-104.12,75.036,-103.814,75.08,-103.71,75.115,-103.664,75.139,-103.642,75.163,-103.644,75.187,-103.667,75.211,-103.746,75.252]},{"b":[-59.362,46.628,-52.654,51.596],"l":[-52.672,47.622,-52.703,47.693,-52.711,47.745,-52.745,47.769,-52.782,47.769,-52.817,47.728,-52.873,47.619,-52.945,47.553,-53.057,47.483,-53.122,47.455,-53.158,47.488,-53.17,47.512,-53.176,47.653,-53.154,47.735,-53.111,47.812,-52.998,47.976,-52.955,48.029,-52.872,48.094,-52.866,48.113,-52.883,48.131,-52.921,48.147,-53.085,48.069,-53.283,47.998,-53.504,47.744,-53.604,47.662,-53.672,47.648,-53.765,47.65,-53.805,47.682,-53.838,47.727,-53.864,47.787,-53.862,47.799,-53.695,47.921,-53.658,47.969,-53.638,48.015,-53.653,48.026,-53.794,48.01,-53.87,48.02,-53.758,48.042,-53.71,48.057,-53.704,48.068,-53.569,48.088,-53.542,48.108,-53.56,48.174,-53.61,48.208,-53.531,48.232,-53.406,48.294,-53.334,48.356,-53.301,48.368,-53.225,48.364,-53.182,48.374,-53.136,48.402,-53.06,48.48,-53.037,48.516,-53.021,48.572,-53.028,48.635,-53.043,48.657,-53.057,48.659,-53.127,48.633,-53.22,48.578,-53.275,48.563,-53.361,48.573,-53.411,48.562,-53.552,48.482,-53.644,48.511,-53.739,48.496,-53.799,48.449,-53.853,48.449,-53.937,48.437,-54.104,48.388,-54.114,48.394,-54.068,48.419,-53.886,48.485,-53.795,48.526,-53.775,48.576,-53.706,48.656,-53.698,48.68,-53.784,48.695,-53.886,48.685,-53.966,48.707,-53.97,48.725,-53.962,48.739,-53.887,48.768,-53.848,48.797,-53.853,48.811,-53.951,48.807,-54.1,48.785,-54.161,48.788,-53.903,48.889,-53.845,48.925,-53.825,48.951,-53.809,48.993,-53.758,49.035,-53.671,49.078,-53.573,49.141,-53.56,49.192,-53.57,49.264,-53.619,49.322,-53.755,49.385,-53.862,49.426,-53.958,49.442,-54.271,49.419,-54.317,49.424,-54.356,49.415,-54.389,49.392,-54.448,49.329,-54.463,49.342,-54.465,49.401,-54.481,49.469,-54.469,49.53,-54.502,49.527,-54.579,49.491,-54.651,49.445,-54.718,49.389,-54.782,49.355,-54.844,49.345,-54.911,49.316,-54.983,49.268,-55.016,49.26,-55.01,49.293,-55.026,49.305,-55.063,49.297,-55.176,49.244,-55.245,49.2,-55.254,49.18,-55.247,49.139,-55.252,49.121,-55.335,49.078,-55.353,49.079,-55.332,49.126,-55.342,49.168,-55.259,49.267,-55.225,49.335,-55.2,49.408,-55.207,49.482,-55.23,49.508,-55.266,49.524,-55.283,49.514,-55.28,49.413,-55.29,49.392,-55.344,49.373,-55.355,49.381,-55.354,49.438,-55.379,49.473,-55.376,49.49,-55.49,49.463,-55.678,49.435,-55.815,49.515,-56.041,49.457,-56.087,49.452,-55.892,49.58,-55.882,49.646,-55.87,49.67,-55.902,49.681,-55.979,49.678,-56.052,49.658,-56.121,49.622,-56.14,49.619,-56.04,49.705,-55.718,49.829,-55.584,49.892,-55.527,49.937,-55.503,49.983,-55.53,49.997,-55.674,49.967,-55.765,49.96,-55.873,50.013,-55.927,50.018,-56.075,49.983,-56.161,49.94,-56.164,49.957,-56.127,50.015,-56.122,50.063,-56.148,50.1,-56.179,50.115,-56.247,50.09,-56.322,50.014,-56.376,49.934,-56.428,49.897,-56.501,49.87,-56.611,49.788,-56.757,49.652,-56.822,49.613,-56.807,49.673,-56.809,49.71,-56.829,49.725,-56.849,49.765,-56.839,49.788,-56.79,49.834,-56.754,49.883,-56.747,49.908,-56.75,49.967,-56.732,50.008,-56.694,50.06,-56.539,50.207,-56.484,50.271,-56.455,50.35,-56.454,50.38,-56.382,50.417,-56.196,50.585,-56.136,50.651,-56.121,50.734,-56.107,50.759,-56.078,50.781,-55.962,50.838,-55.871,50.907,-55.8,51.033,-55.785,51.087,-55.785,51.131,-55.796,51.166,-55.815,51.191,-55.841,51.205,-55.874,51.208,-55.961,51.191,-56,51.199,-56.031,51.227,-56.044,51.262,-56.031,51.328,-55.941,51.343,-55.731,51.359,-55.631,51.373,-55.583,51.389,-55.532,51.437,-55.459,51.537,-55.453,51.562,-55.496,51.59,-55.522,51.596,-55.666,51.579,-55.701,51.559,-55.66,51.511,-55.69,51.471,-55.866,51.508,-55.902,51.564,-56.026,51.568,-56.207,51.489,-56.518,51.399,-56.619,51.362,-56.682,51.333,-56.75,51.275,-56.805,51.144,-56.825,51.126,-56.976,51.028,-57.036,51.011,-57.037,50.996,-57.013,50.968,-57.006,50.94,-57.053,50.857,-57.132,50.787,-57.242,50.745,-57.275,50.725,-57.298,50.699,-57.294,50.673,-57.264,50.649,-57.18,50.615,-57.237,50.605,-57.331,50.605,-57.36,50.584,-57.433,50.506,-57.466,50.464,-57.608,50.199,-57.712,50.025,-57.926,49.701,-57.929,49.668,-57.897,49.6,-57.799,49.509,-57.791,49.49,-57.856,49.474,-57.961,49.532,-58.016,49.542,-58.107,49.5,-58.183,49.435,-58.213,49.387,-58.219,49.305,-58.191,49.259,-58.097,49.23,-57.98,49.23,-57.991,49.209,-58.049,49.18,-58.099,49.077,-58.082,49.045,-58.041,49.01,-57.991,48.988,-58.006,48.981,-58.05,48.988,-58.186,49.062,-58.319,49.081,-58.359,49.097,-58.404,49.084,-58.494,49.003,-58.546,48.897,-58.642,48.749,-58.687,48.622,-58.716,48.598,-58.843,48.605,-58.877,48.623,-58.906,48.65,-58.887,48.692,-58.819,48.747,-58.842,48.746,-59.063,48.628,-59.168,48.558,-59.167,48.522,-58.944,48.522,-58.723,48.541,-58.606,48.533,-58.492,48.513,-58.33,48.522,-58.336,48.514,-58.503,48.442,-58.605,48.411,-58.711,48.325,-58.961,48.159,-59.272,47.996,-59.341,47.934,-59.362,47.889,-59.362,47.866,-59.321,47.737,-59.26,47.634,-59.219,47.603,-59.117,47.571,-58.941,47.58,-58.613,47.626,-58.509,47.653,-58.428,47.683,-58.337,47.731,-58.327,47.72,-58.333,47.677,-58.239,47.669,-57.926,47.675,-57.884,47.66,-57.66,47.625,-57.473,47.631,-56.952,47.574,-56.774,47.565,-56.722,47.592,-56.46,47.617,-56.326,47.654,-56.263,47.658,-56.221,47.671,-56.151,47.775,-56.121,47.789,-56.09,47.772,-56.02,47.764,-55.918,47.792,-55.858,47.819,-55.844,47.788,-55.867,47.592,-56.084,47.525,-56.127,47.503,-56.081,47.5,-55.862,47.53,-55.811,47.516,-55.775,47.498,-55.576,47.465,-55.499,47.475,-55.461,47.485,-55.435,47.501,-55.413,47.55,-55.391,47.643,-55.366,47.661,-55.197,47.65,-55.075,47.658,-55.035,47.634,-54.946,47.621,-54.891,47.629,-54.785,47.665,-54.795,47.64,-54.87,47.571,-54.976,47.516,-55.191,47.449,-55.361,47.259,-55.401,47.221,-55.492,47.161,-55.61,47.12,-55.772,47.092,-55.838,47.072,-55.919,47.017,-55.954,46.973,-55.958,46.956,-55.95,46.928,-55.881,46.887,-55.845,46.874,-55.789,46.867,-55.652,46.881,-55.531,46.914,-55.479,46.917,-55.401,46.899,-55.316,46.906,-55.255,46.942,-55.14,47.046,-55.099,47.104,-55.09,47.174,-54.857,47.385,-54.802,47.399,-54.745,47.395,-54.651,47.408,-54.575,47.458,-54.474,47.547,-54.463,47.536,-54.542,47.425,-54.563,47.375,-54.488,47.404,-54.456,47.428,-54.434,47.462,-54.405,47.556,-54.234,47.772,-54.218,47.847,-54.192,47.86,-54.047,47.806,-53.989,47.756,-53.94,47.645,-53.901,47.509,-53.878,47.464,-53.85,47.44,-53.869,47.387,-53.971,47.262,-54.093,47.086,-54.155,46.967,-54.173,46.917,-54.174,46.88,-54.133,46.839,-54.102,46.825,-54.076,46.82,-54.01,46.84,-53.86,46.939,-53.774,47.012,-53.695,47.093,-53.636,47.138,-53.597,47.146,-53.578,47.133,-53.58,47.099,-53.612,47.01,-53.581,46.957,-53.595,46.888,-53.616,46.68,-53.59,46.639,-53.568,46.628,-53.536,46.633,-53.382,46.711,-53.323,46.718,-53.291,46.717,-53.255,46.698,-53.214,46.66,-53.167,46.646,-53.115,46.656,-53.07,46.681,-53.032,46.723,-52.962,46.819,-52.889,46.974,-52.882,47.011,-52.888,47.046,-52.912,47.103,-52.684,47.426,-52.669,47.47,-52.654,47.549,-52.672,47.622]},{"b":[-55.634,50.709,-55.469,50.801],"l":[-55.604,50.781,-55.634,50.74,-55.629,50.721,-55.601,50.709,-55.57,50.709,-55.536,50.72,-55.504,50.742,-55.473,50.776,-55.469,50.796,-55.527,50.801,-55.604,50.781]},{"b":[-54.864,49.496,-54.538,49.631],"l":[-54.819,49.514,-54.787,49.496,-54.744,49.508,-54.709,49.531,-54.554,49.589,-54.538,49.62,-54.559,49.631,-54.619,49.622,-54.733,49.562,-54.764,49.562,-54.783,49.572,-54.789,49.591,-54.813,49.599,-54.855,49.597,-54.864,49.576,-54.819,49.514]},{"b":[-55.42,51.889,-55.274,51.995],"l":[-55.274,51.995,-55.346,51.983,-55.4,51.938,-55.42,51.9,-55.409,51.889,-55.361,51.89,-55.294,51.93,-55.274,51.995]},{"b":[-54.289,49.587,-53.981,49.751],"l":[-53.981,49.662,-54.02,49.679,-54.094,49.744,-54.138,49.751,-54.199,49.689,-54.259,49.719,-54.278,49.711,-54.289,49.661,-54.286,49.595,-54.269,49.587,-54.238,49.592,-53.981,49.662]},{"b":[-56.387,46.795,-56.255,47.099],"l":[-56.315,46.954,-56.278,47.035,-56.287,47.071,-56.365,47.099,-56.378,47.09,-56.387,47.068,-56.334,46.936,-56.333,46.916,-56.377,46.848,-56.385,46.819,-56.354,46.795,-56.267,46.838,-56.255,46.861,-56.29,46.9,-56.315,46.954]},{"b":[-54.326,47.407,-54.128,47.647],"l":[-54.168,47.607,-54.215,47.585,-54.226,47.566,-54.227,47.54,-54.259,47.498,-54.32,47.439,-54.326,47.408,-54.276,47.407,-54.227,47.441,-54.148,47.573,-54.128,47.647,-54.168,47.607]},{"b":[-100.625,68.724,-100.178,69.035],"l":[-100.289,68.958,-100.33,68.998,-100.414,69.028,-100.52,69.035,-100.565,69.027,-100.601,69.009,-100.612,68.99,-100.598,68.969,-100.6,68.941,-100.625,68.865,-100.625,68.816,-100.616,68.783,-100.597,68.766,-100.573,68.766,-100.521,68.791,-100.497,68.792,-100.481,68.786,-100.443,68.748,-100.397,68.724,-100.366,68.729,-100.288,68.766,-100.249,68.775,-100.217,68.807,-100.178,68.904,-100.207,68.926,-100.289,68.958]},{"b":[-100.247,68.954,-99.995,69.129],"l":[-100.153,69.129,-100.187,69.114,-100.237,69.071,-100.247,69.053,-100.242,69.04,-100.196,68.991,-100.141,68.97,-100.018,68.954,-99.995,69.014,-100.006,69.047,-100.035,69.087,-100.073,69.111,-100.153,69.129]},{"b":[-100.678,70.488,-100.276,70.67],"l":[-100.321,70.578,-100.276,70.595,-100.434,70.649,-100.538,70.669,-100.635,70.67,-100.678,70.646,-100.667,70.596,-100.648,70.563,-100.621,70.547,-100.537,70.525,-100.321,70.488,-100.308,70.496,-100.306,70.508,-100.323,70.542,-100.321,70.578]},{"b":[-95.995,69.336,-95.381,69.626],"l":[-95.986,69.392,-95.956,69.367,-95.893,69.352,-95.858,69.382,-95.812,69.447,-95.798,69.5,-95.818,69.541,-95.806,69.56,-95.764,69.56,-95.704,69.538,-95.683,69.5,-95.666,69.439,-95.67,69.402,-95.696,69.39,-95.73,69.348,-95.684,69.336,-95.579,69.336,-95.496,69.35,-95.437,69.378,-95.399,69.42,-95.382,69.474,-95.381,69.507,-95.514,69.574,-95.602,69.602,-95.707,69.624,-95.798,69.626,-95.876,69.606,-95.936,69.567,-95.979,69.509,-95.995,69.47,-95.978,69.433,-95.986,69.392]},{"b":[-101.356,69.388,-101.001,69.576],"l":[-101.098,69.541,-101.245,69.574,-101.313,69.576,-101.351,69.559,-101.356,69.54,-101.328,69.517,-101.23,69.493,-101.207,69.48,-101.218,69.463,-101.29,69.441,-101.268,69.431,-101.262,69.418,-101.269,69.391,-101.254,69.388,-101.172,69.397,-101.127,69.415,-101.087,69.443,-101.049,69.457,-101.001,69.462,-101.031,69.495,-101.098,69.541]},{"b":[-102.308,68.585,-101.722,68.825],"l":[-102.013,68.825,-102.074,68.774,-102.153,68.74,-102.271,68.708,-102.308,68.682,-102.266,68.664,-101.945,68.603,-101.887,68.585,-101.846,68.586,-101.794,68.637,-101.732,68.652,-101.722,68.724,-101.733,68.753,-101.759,68.775,-101.828,68.799,-102.013,68.825]},{"b":[-105.051,68.402,-104.44,68.582],"l":[-104.602,68.562,-104.7,68.577,-104.907,68.582,-104.994,68.574,-105.051,68.559,-105.042,68.532,-104.965,68.492,-104.851,68.454,-104.699,68.418,-104.596,68.402,-104.541,68.406,-104.457,68.431,-104.44,68.45,-104.445,68.471,-104.472,68.504,-104.602,68.562]},{"b":[-108.152,67.318,-107.891,67.665],"l":[-107.97,67.326,-107.95,67.318,-107.9,67.402,-107.891,67.437,-107.905,67.467,-107.932,67.476,-107.989,67.514,-107.975,67.549,-107.991,67.622,-108.049,67.665,-108.128,67.629,-108.121,67.568,-108.151,67.525,-108.152,67.429,-108.073,67.385,-108.004,67.366,-107.97,67.326]},{"b":[-109.184,67.879,-108.886,67.982],"l":[-108.971,67.98,-109.054,67.972,-109.166,67.982,-109.184,67.975,-109.162,67.952,-109.096,67.924,-108.92,67.879,-108.894,67.884,-108.886,67.899,-108.91,67.939,-108.971,67.98]},{"b":[-108.093,66.858,-107.806,67.005],"l":[-107.944,66.858,-107.895,66.872,-107.833,66.921,-107.806,66.999,-107.966,66.997,-108.093,67.005,-108.06,66.947,-107.965,66.885,-107.944,66.858]},{"b":[-109.498,67.988,-109.323,68.098],"l":[-109.469,68.098,-109.498,68.047,-109.361,67.988,-109.323,67.991,-109.324,68.013,-109.342,68.046,-109.469,68.098]},{"b":[-61.196,56.015,-60.955,56.141],"l":[-61.049,56.129,-61.087,56.141,-61.158,56.118,-61.188,56.089,-61.196,56.064,-61.191,56.048,-61.137,56.033,-60.983,56.015,-60.994,56.039,-60.955,56.08,-60.966,56.099,-61.049,56.129]},{"b":[-62.011,57.416,-61.637,57.579],"l":[-61.937,57.554,-61.983,57.567,-62.007,57.558,-62.011,57.548,-61.975,57.495,-61.795,57.422,-61.637,57.416,-61.66,57.525,-61.744,57.555,-61.848,57.579,-61.893,57.573,-61.937,57.554]},{"b":[-69.353,58.961,-69.155,59.146],"l":[-69.195,59.146,-69.303,59.145,-69.33,59.121,-69.312,59.075,-69.316,59.029,-69.353,58.961,-69.331,58.962,-69.302,58.977,-69.221,58.968,-69.16,59.04,-69.155,59.064,-69.181,59.073,-69.194,59.093,-69.195,59.146]},{"b":[22.077,60.166,22.416,60.371],"l":[22.306,60.229,22.313,60.27,22.416,60.303,22.355,60.356,22.302,60.348,22.175,60.371,22.126,60.356,22.108,60.315,22.077,60.286,22.141,60.265,22.188,60.237,22.209,60.197,22.258,60.166,22.361,60.166,22.346,60.203,22.306,60.229]},{"b":[21.215,60.48,21.451,60.638],"l":[21.215,60.604,21.244,60.526,21.3,60.48,21.369,60.488,21.437,60.483,21.451,60.53,21.301,60.596,21.268,60.638,21.225,60.621,21.215,60.604]},{"b":[21.695,60.106,21.864,60.202],"l":[21.764,60.199,21.705,60.172,21.695,60.114,21.733,60.106,21.833,60.141,21.864,60.202,21.764,60.199]},{"b":[-64.836,60.298,-64.407,60.515],"l":[-64.5,60.43,-64.533,60.441,-64.646,60.515,-64.783,60.51,-64.836,60.501,-64.834,60.448,-64.809,60.41,-64.738,60.376,-64.558,60.323,-64.442,60.298,-64.407,60.367,-64.5,60.43]},{"b":[126.008,34.707,126.172,34.915],"l":[126.169,34.83,126.078,34.915,126.008,34.867,126.052,34.838,126.071,34.783,126.115,34.714,126.159,34.707,126.172,34.731,126.169,34.83]},{"b":[72.35,-7.435,72.499,-7.22],"l":[72.494,-7.262,72.446,-7.22,72.436,-7.23,72.465,-7.278,72.474,-7.31,72.462,-7.338,72.467,-7.368,72.447,-7.396,72.427,-7.3,72.373,-7.263,72.35,-7.263,72.408,-7.334,72.429,-7.435,72.469,-7.417,72.492,-7.377,72.499,-7.295,72.494,-7.262]},{"b":[-139.632,-8.948,-139.508,-8.86],"l":[-139.535,-8.875,-139.584,-8.86,-139.612,-8.872,-139.632,-8.899,-139.621,-8.948,-139.556,-8.94,-139.51,-8.916,-139.508,-8.897,-139.535,-8.875]},{"b":[157.454,-8.794,157.645,-8.697],"l":[157.623,-8.735,157.579,-8.704,157.526,-8.697,157.454,-8.706,157.458,-8.73,157.586,-8.783,157.643,-8.794,157.645,-8.759,157.623,-8.735]},{"b":[20.398,60.012,20.611,60.075],"l":[20.569,60.07,20.49,60.075,20.43,60.062,20.398,60.041,20.411,60.03,20.488,60.033,20.522,60.012,20.603,60.017,20.611,60.041,20.569,60.07]},{"b":[21.486,60.098,21.648,60.172],"l":[21.634,60.169,21.568,60.172,21.507,60.148,21.486,60.127,21.541,60.098,21.628,60.108,21.648,60.141,21.634,60.169]},{"b":[24.577,64.958,24.998,65.086],"l":[24.786,65.086,24.651,65.074,24.577,65.043,24.579,64.979,24.699,64.958,24.848,64.991,24.892,65.026,24.998,65.039,24.971,65.055,24.786,65.086]},{"b":[29.744,69.768,30.055,69.906],"l":[29.836,69.906,29.786,69.829,29.744,69.792,29.766,69.768,29.956,69.797,30.055,69.838,29.993,69.873,29.914,69.902,29.836,69.906]},{"b":[4.799,61.072,4.973,61.199],"l":[4.862,61.194,4.824,61.178,4.799,61.083,4.87,61.072,4.959,61.085,4.973,61.148,4.915,61.199,4.862,61.194]},{"b":[7.804,63.337,8.141,63.471],"l":[8.103,63.338,8.141,63.366,8.136,63.431,8.074,63.471,7.938,63.45,7.804,63.414,7.815,63.385,7.888,63.352,8.005,63.337,8.103,63.338]},{"b":[8.287,63.665,8.815,63.805],"l":[8.471,63.667,8.787,63.703,8.815,63.726,8.809,63.771,8.765,63.805,8.733,63.801,8.709,63.774,8.451,63.732,8.287,63.687,8.356,63.665,8.471,63.667]},{"b":[12.429,65.899,12.779,66.011],"l":[12.476,65.977,12.43,65.94,12.429,65.899,12.51,65.902,12.719,65.964,12.779,65.992,12.747,66.011,12.642,66.009,12.549,66.002,12.476,65.977]},{"b":[12.327,66.037,12.623,66.211],"l":[12.623,66.122,12.621,66.178,12.527,66.211,12.461,66.185,12.446,66.151,12.418,66.123,12.343,66.081,12.327,66.037,12.42,66.043,12.576,66.072,12.623,66.122]},{"b":[19.599,70.068,20.088,70.274],"l":[19.869,70.212,19.818,70.205,19.767,70.217,19.684,70.274,19.599,70.266,19.613,70.219,19.711,70.165,19.747,70.11,19.781,70.077,19.897,70.068,20.006,70.076,20.088,70.102,20.084,70.129,19.994,70.149,19.91,70.202,19.869,70.212]},{"b":[21.995,70.509,23.441,70.843],"l":[22.605,70.533,22.656,70.559,22.829,70.542,22.885,70.554,22.929,70.574,23.068,70.594,23.305,70.722,23.387,70.754,23.421,70.784,23.441,70.816,23.396,70.843,23.28,70.813,23.205,70.815,22.964,70.711,22.858,70.728,22.571,70.697,22.421,70.703,22.35,70.658,22.233,70.667,22.17,70.656,21.995,70.657,22.056,70.613,22.169,70.562,22.359,70.515,22.432,70.509,22.558,70.516,22.605,70.533]},{"b":[20.405,70.057,20.819,70.231],"l":[20.819,70.205,20.786,70.22,20.655,70.231,20.493,70.203,20.412,70.155,20.405,70.119,20.464,70.077,20.535,70.081,20.598,70.071,20.643,70.057,20.725,70.067,20.779,70.09,20.819,70.205]},{"b":[25.315,70.954,26.147,71.142],"l":[26,70.975,26.134,70.996,26.147,71.04,26.078,71.033,25.945,71.105,25.854,71.104,25.586,71.142,25.423,71.097,25.315,71.053,25.315,71.034,25.482,71.02,25.582,70.961,25.76,70.954,25.791,70.963,26,70.975]},{"b":[22.918,70.283,23.641,70.617],"l":[23.547,70.617,23.248,70.505,23.158,70.516,23.022,70.487,22.941,70.445,22.918,70.417,22.918,70.385,23.006,70.353,23.091,70.378,23.108,70.359,23.1,70.296,23.159,70.283,23.271,70.296,23.345,70.315,23.333,70.335,23.548,70.408,23.641,70.464,23.634,70.503,23.615,70.549,23.579,70.594,23.547,70.617]},{"b":[23.663,70.527,24.078,70.747],"l":[23.852,70.714,23.837,70.729,23.778,70.747,23.689,70.723,23.663,70.675,23.67,70.597,23.717,70.562,23.827,70.527,24.018,70.567,24.078,70.651,23.956,70.7,23.852,70.714]},{"b":[13.2,67.995,14.119,68.276],"l":[13.932,68.248,13.873,68.265,13.784,68.276,13.688,68.273,13.538,68.249,13.429,68.163,13.368,68.167,13.3,68.16,13.256,68.121,13.2,68.087,13.229,67.995,13.352,68.01,13.392,68.021,13.404,68.061,13.424,68.083,13.495,68.052,13.584,68.094,13.656,68.105,13.778,68.105,13.824,68.121,13.888,68.169,14.029,68.188,14.097,68.219,14.119,68.247,14.088,68.253,13.932,68.248]},{"b":[12.824,67.821,13.123,68.071],"l":[12.958,68.015,12.878,67.918,12.824,67.821,12.972,67.874,13.075,67.935,13.098,67.956,13.098,68.003,13.123,68.049,13.068,68.071,12.958,68.015]},{"b":[16.81,69.014,18.077,69.596],"l":[18.004,69.505,17.927,69.507,17.863,69.543,17.784,69.563,17.677,69.557,17.623,69.539,17.503,69.596,17.488,69.587,17.483,69.57,17.454,69.53,17.356,69.527,17.252,69.504,17.23,69.478,17.373,69.439,17.395,69.417,17.361,69.381,17.083,69.399,17.002,69.362,16.997,69.33,16.974,69.285,16.998,69.191,16.972,69.138,16.843,69.112,16.815,69.095,16.81,69.071,16.96,69.069,17.077,69.047,17.083,69.014,17.161,69.026,17.324,69.13,17.488,69.197,17.568,69.16,17.774,69.172,17.951,69.198,17.921,69.274,17.942,69.329,18.021,69.35,18.077,69.396,18.052,69.458,18.004,69.505]},{"b":[18.062,69.535,19.608,70.247],"l":[19.5,70.048,19.446,70.038,19.422,70.017,19.344,70.012,19.255,70.066,19.249,70.179,19.213,70.247,19.133,70.244,19.06,70.167,19.051,70.135,19.075,70.086,19.051,70.038,18.969,70.043,18.883,70.011,18.824,69.96,18.687,69.891,18.674,69.864,18.698,69.825,18.674,69.782,18.624,69.813,18.584,69.807,18.512,69.769,18.406,69.782,18.349,69.768,18.315,69.715,18.268,69.702,18.232,69.677,18.227,69.636,18.083,69.626,18.062,69.602,18.13,69.558,18.274,69.535,18.41,69.553,18.785,69.579,18.801,69.605,18.807,69.64,18.909,69.707,19.008,69.76,19.131,69.81,19.197,69.8,19.335,69.82,19.442,69.908,19.592,69.97,19.608,70.019,19.5,70.048]},{"b":[14.257,68.178,16.547,69.302],"l":[15.095,68.441,14.586,68.4,14.438,68.342,14.257,68.257,14.258,68.191,14.35,68.178,14.629,68.198,14.927,68.307,15.038,68.283,15.098,68.289,15.188,68.31,15.28,68.374,15.337,68.378,15.341,68.325,15.438,68.313,15.683,68.356,15.764,68.409,15.837,68.409,15.873,68.394,15.913,68.389,15.975,68.402,16.048,68.464,16.194,68.538,16.338,68.568,16.519,68.633,16.547,68.717,16.48,68.803,16.425,68.842,16.329,68.876,16.276,68.868,16.228,68.854,16.151,68.842,16.121,68.799,16.127,68.746,16.069,68.714,16.06,68.681,15.909,68.65,15.772,68.554,15.76,68.561,15.791,68.617,15.928,68.733,15.924,68.819,15.906,68.908,15.834,68.961,15.812,69.024,15.993,69.113,16.115,69.216,16.129,69.274,16.048,69.302,15.965,69.302,15.893,69.278,15.742,69.171,15.65,69.133,15.483,69.043,15.438,68.979,15.444,68.919,15.529,68.912,15.564,68.874,15.489,68.805,15.413,68.616,15.095,68.441]},{"b":[14.373,68.606,15.397,69.008],"l":[15.102,69.008,15.038,69.001,15.038,68.894,14.872,68.914,14.838,68.887,14.849,68.848,14.802,68.791,14.725,68.8,14.69,68.815,14.554,68.819,14.497,68.772,14.373,68.711,14.405,68.663,14.521,68.633,14.612,68.638,14.743,68.677,14.793,68.668,14.804,68.638,14.89,68.611,15.027,68.606,15.222,68.616,15.348,68.672,15.397,68.784,15.337,68.842,15.207,68.943,15.176,68.982,15.128,69.004,15.102,69.008]},{"b":[10.74,64.838,11.246,64.979],"l":[11.133,64.976,11.021,64.979,10.813,64.923,10.74,64.87,10.833,64.843,11.063,64.86,11.179,64.838,11.231,64.866,11.246,64.908,11.133,64.976]},{"b":[4.93,60.189,5.089,60.452],"l":[4.956,60.243,4.997,60.198,5.089,60.189,5.086,60.308,5.05,60.389,4.991,60.452,4.957,60.447,4.93,60.412,4.951,60.341,4.944,60.272,4.956,60.243]},{"b":[11.765,65.596,12.003,65.706],"l":[11.972,65.702,11.875,65.706,11.8,65.684,11.765,65.631,11.778,65.605,11.902,65.596,11.968,65.627,12.003,65.679,11.972,65.702]},{"b":[-79.699,75.826,-78.845,76.146],"l":[-79.178,76.092,-79.382,76.011,-79.551,75.958,-79.699,75.883,-79.639,75.843,-79.545,75.826,-79.356,75.831,-79.124,75.87,-79.052,75.867,-79.063,75.926,-79.057,75.985,-78.946,76.025,-78.845,76.106,-78.926,76.135,-79.009,76.146,-79.178,76.092]},{"b":[-72.495,77.316,-71.433,77.467],"l":[-72.089,77.467,-72.247,77.464,-72.436,77.448,-72.49,77.432,-72.495,77.386,-72.374,77.355,-72.024,77.316,-71.667,77.325,-71.467,77.354,-71.433,77.394,-71.552,77.403,-71.733,77.432,-71.983,77.46,-72.089,77.467]},{"b":[-47.351,82.066,-44.75,82.692],"l":[-46.399,82.692,-46.787,82.666,-47.272,82.657,-47.351,82.599,-47.308,82.533,-46.752,82.348,-46.161,82.278,-45.491,82.172,-45.067,82.066,-44.865,82.084,-44.776,82.242,-44.75,82.401,-44.917,82.481,-45.411,82.578,-46.399,82.692]},{"b":[-19.611,81.814,-18.665,82.123],"l":[-19.315,82.123,-19.495,82.117,-19.611,82.078,-19.594,81.991,-19.369,81.917,-19.031,81.827,-18.768,81.814,-18.665,81.846,-18.813,81.949,-19.067,82.049,-19.315,82.123]},{"b":[-19.138,79.711,-17.401,80.055],"l":[-18.036,79.711,-17.612,79.826,-17.401,79.94,-17.471,80.029,-17.983,80.055,-18.547,80.011,-18.997,79.94,-19.138,79.852,-19.032,79.773,-18.662,79.72,-18.036,79.711]},{"b":[-19.315,77.939,-18.882,78.441],"l":[-19.112,78.424,-19.315,78.344,-19.297,78.185,-19.218,78.044,-19.129,77.939,-18.997,77.974,-18.882,78.115,-18.953,78.212,-18.953,78.353,-18.935,78.424,-19.006,78.441,-19.112,78.424]},{"b":[-19.085,76.016,-18.583,76.704],"l":[-18.662,76.404,-18.583,76.042,-18.697,76.016,-19.085,76.43,-19.085,76.58,-19.059,76.695,-18.882,76.704,-18.733,76.642,-18.662,76.404]},{"b":[-18.891,74.993,-17.392,75.407],"l":[-18.636,75.39,-18.856,75.319,-18.882,75.196,-18.891,75.072,-18.671,75.002,-18.353,75.01,-17.586,74.993,-17.392,75.037,-17.498,75.152,-17.762,75.143,-17.886,75.204,-17.921,75.302,-18.001,75.407,-18.23,75.372,-18.45,75.328,-18.636,75.39]},{"b":[-18.22,77.642,-17.641,77.875],"l":[-17.904,77.863,-18.174,77.714,-18.22,77.668,-18.148,77.642,-17.954,77.642,-17.729,77.706,-17.641,77.782,-17.681,77.859,-17.814,77.875,-17.904,77.863]},{"b":[-72.818,59.815,-11.426,83.6],"l":[-51.21,68.42,-51.293,68.416,-51.456,68.394,-51.478,68.384,-51.475,68.365,-51.632,68.273,-51.804,68.252,-52.199,68.221,-52.379,68.219,-52.698,68.262,-52.747,68.278,-52.78,68.31,-53.173,68.303,-53.29,68.293,-53.383,68.297,-53.337,68.352,-53.213,68.413,-53.039,68.611,-52.894,68.662,-52.605,68.709,-52.303,68.701,-51.781,68.548,-51.623,68.535,-51.479,68.547,-51.133,68.598,-51.07,68.619,-50.946,68.683,-50.801,68.791,-50.808,68.817,-51.03,68.756,-51.149,68.74,-51.249,68.74,-51.156,68.938,-51.12,69.091,-51.085,69.128,-50.792,69.117,-50.393,69.137,-50.297,69.171,-50.299,69.185,-50.459,69.206,-50.537,69.248,-50.671,69.234,-50.851,69.206,-51.077,69.209,-51.058,69.275,-50.892,69.412,-50.875,69.474,-50.811,69.599,-50.804,69.663,-50.72,69.725,-50.459,69.77,-50.349,69.796,-50.343,69.825,-50.5,69.936,-50.46,69.966,-50.337,69.994,-50.292,70.014,-50.323,70.027,-50.436,70.039,-50.61,70.015,-50.802,70.003,-50.973,70.04,-51.106,70.057,-51.19,70.052,-51.419,69.989,-51.499,69.987,-51.598,70.005,-52.255,70.059,-52.336,70.078,-52.571,70.172,-52.765,70.234,-53.023,70.302,-53.358,70.353,-53.769,70.389,-54.014,70.422,-54.136,70.468,-54.343,70.571,-54.501,70.657,-54.531,70.699,-54.438,70.752,-54.344,70.789,-54.166,70.82,-53.859,70.81,-53.694,70.796,-53.513,70.767,-53.376,70.761,-53.091,70.769,-52.802,70.751,-52.63,70.73,-52.405,70.687,-51.784,70.503,-51.524,70.439,-51.412,70.432,-50.947,70.364,-50.872,70.365,-50.682,70.397,-50.663,70.418,-50.728,70.438,-50.933,70.454,-51.173,70.529,-51.323,70.589,-51.34,70.688,-51.32,70.743,-51.283,70.768,-51.257,70.853,-51.396,70.903,-51.494,70.919,-51.753,70.992,-51.774,71.01,-51.65,71.019,-51.528,71.014,-51.267,70.977,-51.13,70.972,-51.03,70.986,-51.019,71.001,-51.178,71.043,-51.377,71.119,-51.792,71.13,-52.061,71.122,-52.234,71.148,-52.417,71.19,-52.535,71.2,-52.775,71.174,-52.897,71.171,-53.008,71.18,-53.117,71.313,-53.088,71.353,-53.002,71.37,-52.937,71.413,-52.892,71.458,-52.749,71.502,-51.967,71.599,-51.77,71.672,-51.779,71.683,-51.912,71.669,-52.082,71.637,-52.196,71.63,-52.656,71.672,-52.728,71.663,-52.915,71.602,-53.168,71.536,-53.284,71.54,-53.44,71.579,-53.465,71.607,-53.476,71.64,-53.305,71.686,-53.25,71.71,-53.139,71.775,-53.145,71.807,-53.334,71.79,-53.358,71.82,-53.355,71.871,-53.374,71.936,-53.42,72,-53.575,72.098,-53.64,72.123,-53.693,72.16,-53.81,72.293,-53.776,72.326,-53.672,72.351,-53.652,72.363,-53.901,72.342,-53.928,72.319,-53.881,72.285,-53.847,72.24,-53.828,72.183,-53.793,72.134,-53.703,72.08,-53.631,72.052,-53.514,71.976,-53.462,71.894,-53.478,71.85,-53.569,71.806,-53.715,71.758,-53.76,71.718,-53.78,71.679,-53.894,71.642,-53.964,71.656,-54.02,71.658,-53.954,71.593,-53.912,71.526,-53.963,71.459,-54.099,71.419,-54.173,71.417,-54.318,71.384,-54.689,71.367,-54.818,71.375,-55.055,71.409,-55.336,71.427,-55.448,71.472,-55.594,71.554,-55.668,71.627,-55.669,71.692,-55.63,71.739,-55.549,71.768,-55.452,71.958,-55.316,72.111,-54.971,72.268,-54.873,72.325,-54.84,72.356,-54.841,72.379,-54.896,72.394,-55.32,72.2,-55.581,72.179,-55.659,72.223,-55.636,72.3,-55.589,72.319,-55.378,72.311,-55.296,72.354,-55.428,72.42,-55.569,72.437,-55.602,72.453,-55.457,72.503,-55.122,72.5,-55.046,72.534,-54.925,72.572,-54.79,72.642,-54.74,72.7,-54.729,72.75,-54.758,72.791,-54.761,72.832,-54.738,72.873,-54.773,72.918,-54.866,72.967,-55.073,73.015,-55.134,72.961,-55.198,72.938,-55.289,72.933,-55.372,72.956,-55.46,72.964,-55.545,72.985,-55.634,72.991,-55.669,73.008,-55.691,73.054,-55.693,73.113,-55.592,73.14,-55.452,73.162,-55.359,73.203,-55.297,73.262,-55.288,73.327,-55.332,73.397,-55.446,73.46,-55.656,73.399,-55.739,73.384,-55.758,73.428,-55.787,73.46,-55.876,73.505,-55.992,73.537,-56.104,73.558,-56.109,73.591,-56.083,73.627,-56.033,73.67,-55.968,73.76,-55.897,73.752,-55.838,73.76,-55.872,73.833,-55.929,73.895,-55.997,73.931,-55.999,73.946,-56.014,73.964,-56.066,74.007,-56.124,74.039,-56.225,74.129,-56.298,74.163,-56.392,74.181,-56.493,74.182,-56.655,74.159,-56.954,74.131,-57.191,74.118,-57.231,74.125,-57.112,74.159,-56.937,74.195,-56.706,74.219,-56.639,74.278,-56.664,74.33,-56.654,74.378,-56.718,74.429,-56.656,74.458,-56.446,74.486,-56.35,74.49,-56.255,74.527,-56.522,74.614,-56.801,74.672,-56.871,74.695,-56.933,74.733,-56.986,74.787,-57.072,74.84,-57.191,74.894,-57.365,74.945,-57.813,75.04,-57.967,75.105,-58.109,75.205,-58.18,75.247,-58.253,75.279,-58.566,75.353,-58.603,75.385,-58.281,75.472,-58.25,75.507,-58.381,75.612,-58.516,75.689,-58.663,75.716,-58.881,75.73,-59.082,75.765,-59.264,75.819,-59.445,75.859,-59.717,75.896,-60.173,75.993,-60.875,76.097,-61.188,76.158,-61.375,76.18,-61.621,76.186,-62.097,76.242,-62.496,76.26,-62.743,76.252,-62.823,76.262,-63.006,76.319,-63.291,76.352,-63.439,76.339,-63.622,76.278,-63.843,76.217,-63.96,76.209,-64.135,76.265,-64.223,76.303,-64.307,76.317,-64.387,76.304,-64.543,76.253,-64.692,76.216,-64.912,76.173,-65.088,76.152,-65.313,76.146,-65.37,76.131,-65.457,76.13,-65.574,76.144,-65.683,76.173,-65.785,76.215,-65.876,76.238,-65.954,76.242,-66.134,76.22,-66.362,76.155,-66.466,76.139,-66.553,76.146,-66.66,76.166,-66.874,76.218,-66.993,76.213,-67.079,76.195,-67.055,76.152,-66.854,76.05,-66.675,75.977,-66.826,75.969,-68.149,76.067,-68.317,76.091,-68.561,76.15,-68.763,76.187,-69.108,76.281,-69.373,76.332,-69.461,76.372,-69.484,76.399,-69.4,76.436,-68.865,76.561,-68.661,76.587,-68.245,76.617,-68.147,76.636,-68.114,76.651,-68.223,76.678,-68.767,76.668,-69.252,76.686,-69.674,76.736,-69.747,76.752,-69.819,76.783,-69.888,76.827,-69.872,76.877,-69.771,76.931,-69.712,76.969,-69.694,76.989,-70.224,76.855,-70.441,76.807,-70.613,76.822,-70.734,76.844,-70.793,76.869,-70.791,76.896,-70.771,76.917,-70.735,76.929,-71.015,76.985,-71.141,77.029,-71.155,77.074,-71.055,77.121,-70.958,77.154,-70.863,77.175,-70.604,77.194,-69.657,77.229,-68.978,77.195,-68.747,77.307,-68.592,77.343,-68.136,77.38,-67.434,77.385,-66.938,77.364,-66.706,77.338,-66.389,77.28,-66.371,77.298,-66.448,77.35,-66.453,77.393,-66.325,77.468,-66.266,77.515,-66.306,77.565,-66.445,77.616,-66.691,77.681,-66.824,77.687,-66.971,77.671,-67.147,77.635,-67.515,77.543,-67.688,77.524,-67.977,77.519,-68.137,77.53,-68.292,77.544,-68.533,77.593,-68.622,77.602,-68.728,77.581,-68.854,77.529,-68.975,77.493,-69.091,77.472,-69.2,77.463,-69.351,77.467,-69.977,77.548,-70.118,77.583,-70.126,77.638,-70.318,77.69,-70.535,77.7,-70.562,77.717,-70.287,77.798,-70.081,77.831,-70.114,77.841,-70.412,77.843,-70.614,77.8,-70.729,77.793,-70.994,77.792,-71.272,77.813,-71.39,77.832,-71.512,77.875,-71.65,77.9,-72.065,77.937,-72.159,77.957,-72.247,77.99,-72.586,78.085,-72.792,78.155,-72.818,78.194,-72.581,78.279,-72.571,78.299,-72.672,78.335,-72.715,78.362,-72.68,78.4,-72.473,78.482,-72.396,78.504,-72.024,78.553,-71.651,78.623,-71.516,78.639,-71.395,78.643,-70.906,78.638,-70.754,78.656,-70.625,78.69,-70.414,78.725,-69.974,78.778,-68.993,78.857,-68.93,78.867,-68.924,78.882,-69.012,78.923,-69.031,78.943,-68.83,78.98,-68.377,79.038,-68.068,79.066,-67.868,79.068,-67.708,79.08,-67.482,79.117,-67.355,79.123,-66.584,79.138,-66.243,79.118,-66.075,79.118,-65.968,79.132,-65.826,79.174,-65.56,79.276,-65.42,79.34,-65.288,79.437,-65.117,79.589,-64.989,79.737,-64.905,79.881,-64.839,79.969,-64.792,80.001,-64.632,80.041,-64.466,80.072,-64.179,80.099,-64.205,80.112,-64.327,80.134,-64.44,80.142,-64.545,80.137,-64.735,80.104,-64.982,80.082,-65.222,80.086,-65.395,80.078,-65.553,80.048,-65.81,80.024,-65.982,80.029,-66.292,80.072,-66.448,80.08,-66.844,80.076,-66.959,80.092,-67.061,80.123,-67.141,80.166,-67.201,80.222,-67.193,80.28,-67.051,80.385,-66.996,80.413,-66.61,80.53,-66.372,80.584,-66.136,80.625,-65.963,80.649,-65.801,80.66,-65.645,80.685,-65.358,80.767,-65.062,80.836,-64.694,80.966,-64.516,81,-63.892,81.056,-63.722,81.057,-63.578,81.043,-63.442,81.014,-63.059,80.886,-63.029,80.89,-63.095,80.938,-63.235,81.083,-63.212,81.143,-62.993,81.207,-62.903,81.218,-62.672,81.214,-62.299,81.194,-62.049,81.173,-61.86,81.138,-61.636,81.116,-61.519,81.117,-61.436,81.134,-61.317,81.188,-61.162,81.281,-61.1,81.396,-61.131,81.532,-61.176,81.632,-61.236,81.695,-61.203,81.747,-61.015,81.81,-60.843,81.855,-60.432,81.92,-60.099,81.937,-59.902,81.933,-59.642,81.903,-59.282,81.884,-58.957,81.825,-58.43,81.69,-58.08,81.622,-57.79,81.592,-57.505,81.54,-57.083,81.43,-56.862,81.383,-56.731,81.366,-56.615,81.363,-56.658,81.394,-56.86,81.46,-57.168,81.532,-57.853,81.662,-58.23,81.754,-58.568,81.858,-58.817,81.92,-59.268,81.982,-59.262,82.007,-58.717,82.093,-57.717,82.168,-56.589,82.227,-56.212,82.221,-55.549,82.246,-55.486,82.283,-55.344,82.3,-54.726,82.351,-54.549,82.351,-54.277,82.326,-53.987,82.279,-53.853,82.237,-53.671,82.164,-53.582,82.062,-53.596,81.738,-53.591,81.677,-53.556,81.653,-53.43,81.688,-53.28,81.754,-53.145,81.8,-53.041,81.871,-52.969,81.967,-52.926,82.038,-53.102,82.119,-53.111,82.251,-53.023,82.322,-52.776,82.322,-51.754,82.078,-51.352,82.026,-50.894,81.895,-50.36,81.909,-49.867,81.893,-49.649,81.898,-49.541,81.918,-49.694,81.972,-50.395,82.121,-50.713,82.237,-50.936,82.383,-50.99,82.46,-50.82,82.474,-50.037,82.472,-48.861,82.405,-47.357,82.174,-46.617,82.097,-45.291,81.829,-44.891,81.788,-44.729,81.78,-44.608,81.813,-44.532,81.849,-44.527,81.897,-44.591,81.957,-44.628,82.026,-44.637,82.104,-44.547,82.26,-44.333,82.311,-44.239,82.368,-44.327,82.472,-44.577,82.543,-45.552,82.725,-45.557,82.747,-45.36,82.771,-45.067,82.785,-42.651,82.741,-42.233,82.725,-42.055,82.71,-41.977,82.689,-41.876,82.68,-41.357,82.705,-41.37,82.75,-41.434,82.779,-44.239,82.857,-44.762,82.884,-45.028,82.886,-45.303,82.865,-45.873,82.855,-46.137,82.859,-46.478,82.952,-46.169,83.064,-45.909,83.061,-45.415,83.018,-45.122,83.079,-44.657,83.129,-44.197,83.147,-43.195,83.255,-43.009,83.265,-42.776,83.259,-42.26,83.232,-42.055,83.205,-41.82,83.148,-41.683,83.13,-41.522,83.127,-41.3,83.101,-40.979,83.185,-40.689,83.275,-40.357,83.332,-39.886,83.299,-39.588,83.256,-39.316,83.204,-38.931,83.175,-38.278,82.999,-38.156,82.999,-38.099,83.014,-38.037,83.046,-38.015,83.095,-37.935,83.161,-37.993,83.185,-38.54,83.258,-38.643,83.286,-38.748,83.333,-38.75,83.371,-38.648,83.401,-38.541,83.415,-38.188,83.402,-38.071,83.412,-37.961,83.438,-37.828,83.486,-37.723,83.498,-37.487,83.499,-37.123,83.468,-36.804,83.466,-36.69,83.48,-36.672,83.51,-36.644,83.529,-36.606,83.537,-35.452,83.539,-35.166,83.546,-34.942,83.568,-34.668,83.571,-34.428,83.558,-34.132,83.529,-33.837,83.53,-33.398,83.577,-32.984,83.6,-30.703,83.593,-29.953,83.565,-28.992,83.505,-28.484,83.435,-28.377,83.437,-27.688,83.41,-27.034,83.377,-25.947,83.29,-25.795,83.261,-25.912,83.238,-26.183,83.221,-27.572,83.193,-30.092,83.157,-31.534,83.089,-31.993,83.085,-32.033,82.983,-31.837,82.978,-31.516,82.992,-30.386,83.094,-29.964,83.11,-29.175,83.102,-28.151,83.064,-27.739,83.077,-27.002,83.067,-26.141,83.096,-25.123,83.16,-24.845,83.019,-24.47,82.877,-24.174,82.893,-23.92,82.885,-23.834,82.839,-23.695,82.819,-23.407,82.83,-22.525,82.789,-21.92,82.716,-21.692,82.683,-21.583,82.634,-21.521,82.595,-21.616,82.548,-21.994,82.463,-22.473,82.385,-23.118,82.325,-23.862,82.287,-29.579,82.161,-29.773,82.131,-29.887,82.055,-29.811,81.955,-29.544,81.94,-28.919,81.996,-27.84,82.049,-27.046,82.046,-25.149,82.001,-24.589,81.883,-24.293,81.701,-23.637,81.742,-23.496,81.773,-23.393,81.827,-23.311,81.885,-23.249,81.947,-23.18,81.989,-23.104,82.012,-22.94,82.031,-22.563,82.053,-21.576,82.075,-21.338,82.069,-21.167,81.984,-21.13,81.934,-21.118,81.87,-21.123,81.79,-21.147,81.695,-21.231,81.601,-21.504,81.438,-21.724,81.348,-21.961,81.284,-22.415,81.137,-22.573,81.098,-23.072,80.927,-23.196,80.847,-23.204,80.789,-23.118,80.778,-22.973,80.833,-22.919,80.872,-22.826,80.913,-22.089,81.02,-21.931,81.05,-21.45,81.178,-21.142,81.226,-20.89,81.276,-20.756,81.312,-20.016,81.564,-19.63,81.64,-19.225,81.64,-19.153,81.512,-18.667,81.492,-18.457,81.498,-18.118,81.467,-17.969,81.441,-17.717,81.428,-17.456,81.398,-17.226,81.43,-17.159,81.451,-16.937,81.544,-16.637,81.626,-16.359,81.729,-16.267,81.754,-16.121,81.777,-15.969,81.785,-15.556,81.834,-15.451,81.837,-15.227,81.822,-14.242,81.814,-13.704,81.789,-12.956,81.72,-12.434,81.683,-12.193,81.649,-11.841,81.578,-11.557,81.503,-11.426,81.481,-11.431,81.457,-11.529,81.424,-12.231,81.309,-12.461,81.233,-13.126,81.088,-13.451,81.038,-13.804,81.019,-14.197,81.014,-14.452,80.993,-14.49,80.973,-14.308,80.913,-14.229,80.87,-14.24,80.832,-14.431,80.776,-14.504,80.763,-15.194,80.721,-15.543,80.65,-15.998,80.642,-16.319,80.65,-16.761,80.573,-16.588,80.511,-16.429,80.484,-15.937,80.428,-15.933,80.395,-16.168,80.329,-16.489,80.252,-16.868,80.198,-17.011,80.19,-17.191,80.204,-17.357,80.201,-17.723,80.176,-18.071,80.172,-18.693,80.207,-19.029,80.248,-19.206,80.262,-19.429,80.258,-19.515,80.241,-19.867,80.145,-20.04,80.079,-20.15,80.011,-20.197,79.938,-20.181,79.858,-20.138,79.803,-20.069,79.774,-19.985,79.756,-19.839,79.746,-19.518,79.755,-19.392,79.75,-19.353,79.734,-19.284,79.683,-19.296,79.635,-19.354,79.567,-19.399,79.488,-19.431,79.398,-19.414,79.349,-19.284,79.338,-19.223,79.342,-19.152,79.325,-19.072,79.289,-19.011,79.251,-18.971,79.211,-18.992,79.178,-19.075,79.152,-19.262,79.123,-19.723,79.065,-19.77,79.047,-19.806,79.012,-19.832,78.959,-19.887,78.911,-19.973,78.868,-20.05,78.842,-20.2,78.83,-20.396,78.829,-20.616,78.804,-21.134,78.659,-21.141,78.643,-20.947,78.596,-20.956,78.555,-21.195,78.38,-21.26,78.293,-21.312,78.174,-21.397,78.074,-21.516,77.992,-21.633,77.897,-21.748,77.791,-21.73,77.709,-21.579,77.651,-21.38,77.698,-21.132,77.847,-20.863,77.912,-20.572,77.892,-20.319,77.862,-19.995,77.803,-19.724,77.767,-19.49,77.719,-19.394,77.678,-19.297,77.621,-19.296,77.585,-19.468,77.566,-19.524,77.572,-19.953,77.666,-20.162,77.69,-20.439,77.662,-20.681,77.619,-20.464,77.447,-20.232,77.368,-19.809,77.332,-19.588,77.294,-19.426,77.246,-19.3,77.222,-19.131,77.233,-18.903,77.28,-18.586,77.283,-18.443,77.259,-18.339,77.215,-18.292,77.133,-18.303,77.012,-18.337,76.921,-18.396,76.86,-18.51,76.778,-18.606,76.763,-18.74,76.768,-18.865,76.785,-18.981,76.814,-19.156,76.837,-19.509,76.861,-19.865,76.914,-20.064,76.928,-20.487,76.921,-20.942,76.887,-20.96,76.843,-21.615,76.688,-21.749,76.69,-21.931,76.743,-22.185,76.794,-22.334,76.794,-22.555,76.729,-22.609,76.704,-22.607,76.681,-22.444,76.625,-22.379,76.612,-22.295,76.601,-22.004,76.588,-21.877,76.573,-21.758,76.401,-21.569,76.294,-21.488,76.272,-21.417,76.264,-21.185,76.268,-20.887,76.304,-20.783,76.275,-20.564,76.24,-20.435,76.231,-20.279,76.232,-20.104,76.219,-19.863,76.121,-19.958,75.997,-19.807,75.897,-19.566,75.795,-19.509,75.758,-19.486,75.69,-19.48,75.645,-19.462,75.604,-19.431,75.567,-19.4,75.494,-19.366,75.386,-19.375,75.298,-19.426,75.23,-19.526,75.18,-19.676,75.149,-19.798,75.157,-19.893,75.205,-20.027,75.255,-20.199,75.308,-20.485,75.314,-20.906,75.157,-21.094,75.149,-21.247,75.133,-21.409,75.065,-21.649,75.023,-21.861,75.04,-22.233,75.12,-22.098,75.066,-21.904,75.004,-21.784,74.971,-21.695,74.964,-21.598,74.972,-21.457,74.998,-21.141,75.069,-21.057,75.079,-20.986,75.074,-20.928,75.053,-20.861,74.993,-20.786,74.892,-20.795,74.806,-20.89,74.735,-20.971,74.69,-21.038,74.67,-21.038,74.654,-20.862,74.636,-20.611,74.728,-20.532,74.843,-20.417,74.975,-20.214,75.019,-19.985,74.975,-19.8,74.852,-19.538,74.625,-19.427,74.601,-19.287,74.546,-19.225,74.479,-19.242,74.4,-19.272,74.343,-19.315,74.307,-19.369,74.284,-19.467,74.269,-19.646,74.258,-20.048,74.282,-20.256,74.283,-20.231,74.205,-20.653,74.137,-21.129,74.111,-21.581,74.163,-21.955,74.244,-21.832,74.357,-21.762,74.483,-21.943,74.566,-21.983,74.567,-21.92,74.439,-21.973,74.39,-22.177,74.33,-22.322,74.303,-22.334,74.286,-22.264,74.272,-22.217,74.246,-22.196,74.206,-22.22,74.166,-22.291,74.125,-22.329,74.091,-22.335,74.063,-22.271,74.03,-22.135,73.99,-21.988,73.971,-21.298,73.962,-21.022,73.941,-20.367,73.848,-20.338,73.82,-20.449,73.653,-20.51,73.493,-20.637,73.464,-21.326,73.457,-21.548,73.432,-21.873,73.358,-22.185,73.27,-22.347,73.269,-22.987,73.346,-23.233,73.398,-23.761,73.543,-24.158,73.764,-24.34,73.672,-24.451,73.629,-24.566,73.606,-24.677,73.602,-24.784,73.618,-24.905,73.653,-25.109,73.734,-25.351,73.814,-25.521,73.852,-25.528,73.841,-25.427,73.794,-25.281,73.74,-24.909,73.58,-24.778,73.54,-24.791,73.511,-25.026,73.486,-25.311,73.431,-25.45,73.391,-25.665,73.293,-25.74,73.278,-26.062,73.253,-26.169,73.259,-26.407,73.313,-26.765,73.348,-26.977,73.38,-27.27,73.436,-27.169,73.374,-26.604,73.279,-26.542,73.249,-26.658,73.192,-26.729,73.171,-26.863,73.167,-27.062,73.179,-27.265,73.176,-27.472,73.16,-27.562,73.138,-27.533,73.113,-27.483,73.089,-27.413,73.068,-27.348,73.068,-27.19,73.132,-27.07,73.137,-26.753,73.121,-26.433,73.171,-26.202,73.193,-26.029,73.199,-25.399,73.276,-25.268,73.362,-25.057,73.396,-24.587,73.423,-24.133,73.409,-23.899,73.398,-23.71,73.317,-23.456,73.259,-23.244,73.193,-22.996,73.172,-22.852,73.084,-22.45,72.986,-22.194,72.965,-22.036,72.918,-22.023,72.721,-22.007,72.635,-22.075,72.399,-22.28,72.345,-22.239,72.22,-22.293,72.12,-22.498,72.158,-22.707,72.224,-23.208,72.327,-23.674,72.393,-23.856,72.452,-24.069,72.499,-24.359,72.687,-24.547,72.922,-24.63,73.038,-24.789,73.044,-24.992,73.013,-25.171,72.98,-25.256,72.924,-25.861,72.847,-26.08,72.794,-26.206,72.796,-26.658,72.716,-26.477,72.678,-26.392,72.673,-26.209,72.694,-26.1,72.722,-25.688,72.797,-25.357,72.81,-25.237,72.843,-24.985,72.889,-24.813,72.902,-24.789,72.89,-24.771,72.869,-24.65,72.583,-24.701,72.506,-24.837,72.473,-25.128,72.419,-25.204,72.393,-25.118,72.347,-24.844,72.39,-24.667,72.437,-24.572,72.42,-24.417,72.348,-24.242,72.311,-23.798,72.201,-23.587,72.14,-23.291,72.081,-22.956,71.999,-22.869,71.971,-22.562,71.928,-22.497,71.914,-22.37,71.77,-22.265,71.754,-21.96,71.745,-22.013,71.689,-22.311,71.565,-22.465,71.525,-22.503,71.5,-22.489,71.457,-22.48,71.383,-22.418,71.249,-22.348,71.373,-22.299,71.432,-22.234,71.45,-22.17,71.453,-21.961,71.508,-21.752,71.478,-21.698,71.337,-21.671,71.206,-21.69,71.092,-21.667,70.916,-21.675,70.856,-21.625,70.805,-21.574,70.59,-21.523,70.526,-21.626,70.469,-21.944,70.443,-22.069,70.472,-22.384,70.462,-22.385,70.513,-22.4,70.571,-22.401,70.612,-22.422,70.649,-22.437,70.86,-22.526,70.808,-22.531,70.765,-22.555,70.721,-22.61,70.493,-22.691,70.437,-22.943,70.451,-23.191,70.442,-23.328,70.451,-23.792,70.555,-23.971,70.649,-24.13,70.791,-24.229,70.923,-24.266,71.046,-24.377,71.146,-24.562,71.224,-24.781,71.286,-25.033,71.334,-25.255,71.396,-25.446,71.471,-25.656,71.53,-25.885,71.572,-26.211,71.59,-26.689,71.583,-27.011,71.631,-27.087,71.627,-27.162,71.602,-27.107,71.533,-26.737,71.501,-26.452,71.494,-26.074,71.498,-25.843,71.48,-25.758,71.44,-25.699,71.368,-25.668,71.265,-25.742,71.184,-26.014,71.093,-26.158,71.05,-26.576,70.969,-26.718,70.95,-27.067,70.945,-27.336,70.953,-27.689,70.993,-27.889,71.002,-28.303,71.007,-28.398,70.993,-28.292,70.949,-28.116,70.925,-27.992,70.895,-27.979,70.84,-28.024,70.757,-28.07,70.699,-28.146,70.656,-28.417,70.574,-28.53,70.548,-29.037,70.462,-29.072,70.445,-28.953,70.447,-28.633,70.478,-28.541,70.477,-28.015,70.402,-27.596,70.407,-26.747,70.476,-26.677,70.474,-26.622,70.463,-26.565,70.438,-26.508,70.397,-26.577,70.357,-26.771,70.319,-27.073,70.281,-27.203,70.256,-27.328,70.217,-27.561,70.124,-27.629,70.028,-27.384,69.992,-27.274,70.038,-27.144,70.141,-27.028,70.201,-26.752,70.242,-26.416,70.221,-26.156,70.246,-25.625,70.347,-25.53,70.353,-24.749,70.295,-24.041,70.181,-23.667,70.139,-23.173,70.115,-22.284,70.126,-22.207,70.108,-22.235,70.068,-22.287,70.033,-22.435,69.986,-22.615,69.954,-22.726,69.945,-22.821,69.923,-23.034,69.901,-23.088,69.883,-23.015,69.805,-23.05,69.793,-23.237,69.791,-23.553,69.741,-23.812,69.744,-23.866,69.737,-23.817,69.718,-23.764,69.681,-23.709,69.627,-23.739,69.589,-23.856,69.566,-23.944,69.558,-24.248,69.59,-24.297,69.586,-24.252,69.562,-24.227,69.527,-24.221,69.479,-24.296,69.439,-24.451,69.407,-24.741,69.318,-24.867,69.293,-25.133,69.272,-25.189,69.261,-25.08,69.192,-25.092,69.165,-25.272,69.092,-25.544,69.046,-25.581,69.021,-25.606,68.954,-25.626,68.928,-25.698,68.89,-25.956,68.817,-26.139,68.781,-26.229,68.752,-26.341,68.702,-26.483,68.676,-26.654,68.673,-26.815,68.654,-27.081,68.602,-27.266,68.584,-27.851,68.494,-28.126,68.479,-28.365,68.447,-28.854,68.36,-29.088,68.332,-29.25,68.299,-29.426,68.289,-29.714,68.311,-29.869,68.312,-29.964,68.299,-30.051,68.272,-30.196,68.199,-30.318,68.193,-30.72,68.251,-30.712,68.225,-30.606,68.162,-30.611,68.118,-30.85,68.073,-30.979,68.061,-31.168,68.08,-31.419,68.128,-31.742,68.23,-32.137,68.385,-32.327,68.437,-32.314,68.388,-32.27,68.339,-32.195,68.292,-32.18,68.257,-32.224,68.236,-32.282,68.225,-32.355,68.225,-32.367,68.213,-32.249,68.139,-32.156,68.063,-32.165,67.991,-32.275,67.923,-32.37,67.883,-32.449,67.871,-32.918,67.701,-33.049,67.679,-33.108,67.658,-33.157,67.627,-33.294,67.486,-33.349,67.443,-33.458,67.387,-33.504,67.377,-33.518,67.354,-33.498,67.318,-33.528,67.258,-33.608,67.174,-33.881,66.942,-34.102,66.726,-34.198,66.655,-34.269,66.625,-34.314,66.636,-34.423,66.63,-34.476,66.592,-34.524,66.523,-34.576,66.471,-34.633,66.435,-35.075,66.279,-35.189,66.25,-35.291,66.269,-35.412,66.262,-35.662,66.344,-35.705,66.374,-35.867,66.441,-35.862,66.406,-35.835,66.387,-35.812,66.358,-35.756,66.324,-35.63,66.14,-35.729,66.102,-35.818,66.059,-36.044,65.987,-36.289,65.865,-36.379,65.831,-36.399,65.93,-36.389,65.96,-36.527,66.008,-36.523,65.973,-36.537,65.941,-36.637,65.812,-36.665,65.79,-36.715,65.795,-36.822,65.771,-36.932,65.783,-37.026,65.841,-37.063,65.871,-37.233,65.788,-37.316,65.79,-37.33,65.72,-37.41,65.656,-37.516,65.629,-37.664,65.631,-37.754,65.593,-37.955,65.634,-38.001,65.71,-37.842,65.814,-37.797,65.857,-37.827,65.91,-37.788,65.978,-37.484,66.195,-37.279,66.304,-37.291,66.324,-37.57,66.348,-37.814,66.385,-38.052,66.398,-38.157,66.386,-37.989,66.323,-37.752,66.262,-37.869,66.203,-37.969,66.141,-38.073,65.973,-38.14,65.904,-38.398,65.983,-38.52,66.01,-38.443,65.922,-38.216,65.838,-38.202,65.811,-38.203,65.712,-38.637,65.624,-39.089,65.611,-39.413,65.586,-39.961,65.556,-40.174,65.556,-40.192,65.523,-39.656,65.369,-39.578,65.341,-39.653,65.288,-39.763,65.255,-39.937,65.142,-40.028,65.103,-40.253,65.049,-40.668,65.109,-40.881,65.082,-41.084,65.101,-41.089,65.035,-41.028,64.988,-40.966,64.869,-40.829,64.878,-40.655,64.915,-40.521,64.825,-40.433,64.673,-40.278,64.596,-40.21,64.536,-40.182,64.48,-40.278,64.424,-40.478,64.344,-40.699,64.33,-40.686,64.267,-40.782,64.222,-40.985,64.235,-41.079,64.267,-41.178,64.281,-41.581,64.298,-41.175,64.177,-41.031,64.121,-40.966,64.154,-40.826,64.163,-40.618,64.132,-40.652,63.928,-40.561,63.762,-40.55,63.725,-40.772,63.626,-40.775,63.534,-40.907,63.508,-41.049,63.514,-41.056,63.412,-41.152,63.349,-41.135,63.309,-41.108,63.274,-41.195,63.209,-41.275,63.131,-41.388,63.062,-41.448,63.069,-41.628,63.065,-41.844,63.07,-42.02,63.16,-42.092,63.189,-42.175,63.209,-42.143,63.151,-42.094,63.117,-41.932,63.052,-41.634,62.972,-41.644,62.916,-41.723,62.891,-41.909,62.737,-41.975,62.734,-42.058,62.694,-42.316,62.707,-42.424,62.723,-42.741,62.713,-42.849,62.727,-42.942,62.72,-42.855,62.677,-42.674,62.638,-42.467,62.598,-42.153,62.568,-42.164,62.512,-42.243,62.466,-42.198,62.397,-42.233,62.348,-42.248,62.289,-42.321,62.153,-42.236,62.059,-42.143,62.014,-42.154,61.953,-42.11,61.857,-42.25,61.771,-42.365,61.775,-42.53,61.755,-42.585,61.717,-42.324,61.682,-42.347,61.617,-42.419,61.537,-42.494,61.363,-42.646,61.064,-42.717,60.767,-43.044,60.524,-43.16,60.517,-43.189,60.507,-43.348,60.52,-43.598,60.576,-43.792,60.595,-43.923,60.595,-43.94,60.567,-43.831,60.522,-43.665,60.503,-43.533,60.473,-43.296,60.445,-43.213,60.391,-43.156,60.333,-43.165,60.301,-43.165,60.263,-43.123,60.061,-43.235,59.991,-43.32,59.928,-43.617,59.937,-43.669,59.959,-43.955,60.025,-43.937,59.994,-43.73,59.904,-43.658,59.859,-43.706,59.849,-43.79,59.846,-43.907,59.815,-44.117,59.832,-44.105,59.878,-44.065,59.925,-44.162,59.917,-44.269,59.893,-44.331,59.902,-44.384,59.899,-44.413,59.923,-44.453,60.015,-44.405,60.061,-44.231,60.18,-44.176,60.244,-44.224,60.274,-44.348,60.205,-44.476,60.096,-44.533,60.029,-44.613,60.017,-44.812,60.05,-45.379,60.203,-45.362,60.296,-45.368,60.373,-45.202,60.383,-45.082,60.416,-44.975,60.457,-44.854,60.532,-44.742,60.655,-44.757,60.665,-45.083,60.507,-45.283,60.455,-45.381,60.445,-45.429,60.468,-45.59,60.519,-45.695,60.542,-45.934,60.579,-45.977,60.6,-46.047,60.616,-46.142,60.777,-46.019,60.972,-45.934,61.028,-45.88,61.094,-45.849,61.181,-45.87,61.218,-45.942,61.206,-45.976,61.176,-45.97,61.129,-46.012,61.097,-46.297,61.022,-46.582,60.962,-46.718,60.905,-46.806,60.86,-46.874,60.816,-46.98,60.82,-47.125,60.811,-47.224,60.783,-47.37,60.8,-47.465,60.843,-47.579,60.847,-47.707,60.827,-47.796,60.829,-47.789,60.8,-47.73,60.729,-47.828,60.725,-48.014,60.722,-48.108,60.742,-48.181,60.769,-48.242,60.807,-48.205,60.856,-47.906,60.946,-47.77,60.998,-47.859,61.016,-48.146,60.999,-48.194,61.013,-48.386,61.005,-48.378,61.138,-48.425,61.172,-48.428,61.187,-48.495,61.225,-48.558,61.234,-48.597,61.247,-48.922,61.277,-48.965,61.352,-48.987,61.429,-49.049,61.524,-49.205,61.549,-49.289,61.59,-49.222,61.632,-49.193,61.686,-49.265,61.71,-49.311,61.748,-49.304,61.772,-49.363,61.839,-49.38,61.89,-49.313,61.939,-49.13,61.993,-49.071,62.015,-49.04,62.039,-48.829,62.08,-49.008,62.108,-49.12,62.113,-49.202,62.099,-49.278,62.046,-49.349,62.01,-49.624,61.999,-49.664,62.017,-49.683,62.093,-49.668,62.151,-49.553,62.233,-49.685,62.273,-49.806,62.287,-49.943,62.324,-50.07,62.365,-50.179,62.411,-50.285,62.466,-50.319,62.473,-50.281,62.531,-50.259,62.578,-50.256,62.68,-50.299,62.722,-50.204,62.809,-50.076,62.904,-49.793,63.045,-50.092,62.977,-50.338,62.829,-50.39,62.822,-50.408,62.849,-50.502,62.945,-50.572,62.971,-50.604,63,-50.744,63.051,-50.804,63.091,-50.89,63.167,-51.013,63.258,-51.188,63.436,-51.469,63.642,-51.538,63.758,-51.451,63.905,-51.548,64.006,-51.28,64.053,-50.898,64.106,-50.699,64.149,-50.585,64.162,-50.342,64.17,-50.261,64.214,-50.396,64.203,-50.487,64.209,-50.492,64.229,-50.459,64.266,-50.437,64.313,-50.483,64.304,-50.721,64.223,-51.072,64.159,-51.347,64.123,-51.391,64.125,-51.487,64.103,-51.542,64.097,-51.585,64.103,-51.682,64.165,-51.708,64.205,-51.534,64.314,-51.404,64.463,-51.232,64.561,-51.11,64.573,-50.907,64.568,-50.835,64.559,-50.858,64.617,-50.849,64.645,-50.684,64.678,-50.492,64.693,-50.355,64.683,-50.269,64.615,-50.158,64.49,-50.009,64.447,-50.016,64.507,-50.093,64.585,-50.122,64.704,-50.22,64.754,-50.299,64.779,-50.517,64.767,-50.648,64.853,-50.678,64.885,-50.681,64.928,-50.812,65.052,-50.854,65.114,-50.924,65.197,-50.961,65.201,-50.914,65.097,-50.852,65.024,-50.765,64.863,-50.722,64.798,-50.78,64.746,-50.891,64.695,-50.989,64.665,-51.221,64.628,-51.171,64.708,-51.139,64.786,-51.255,64.758,-51.364,64.702,-51.401,64.623,-51.47,64.552,-51.677,64.377,-51.758,64.28,-51.835,64.232,-51.923,64.219,-51.999,64.257,-52.063,64.346,-52.093,64.416,-52.097,64.597,-52.089,64.682,-52.124,64.795,-52.235,65.061,-52.259,65.155,-52.448,65.205,-52.45,65.221,-52.5,65.275,-52.538,65.329,-52.506,65.348,-52.461,65.363,-52.18,65.442,-51.971,65.531,-51.721,65.67,-51.619,65.713,-51.253,65.746,-51.09,65.751,-51.092,65.776,-51.146,65.786,-51.394,65.779,-51.723,65.723,-51.78,65.703,-51.924,65.617,-52.035,65.569,-52.348,65.461,-52.551,65.461,-52.761,65.591,-52.995,65.566,-53.153,65.575,-53.199,65.594,-53.234,65.771,-53.106,65.977,-53.244,65.979,-53.272,65.987,-53.345,66.034,-53.392,66.048,-53.357,66.073,-53.018,66.171,-52.511,66.362,-52.293,66.438,-52.158,66.47,-52.056,66.507,-51.932,66.588,-51.891,66.623,-51.822,66.652,-51.676,66.684,-51.517,66.732,-51.259,66.841,-51.225,66.882,-51.281,66.891,-51.402,66.854,-51.648,66.754,-51.823,66.698,-52.421,66.447,-52.676,66.355,-52.814,66.297,-52.922,66.241,-53.036,66.201,-53.156,66.178,-53.413,66.16,-53.539,66.139,-53.615,66.154,-53.648,66.274,-53.623,66.344,-53.635,66.414,-53.571,66.513,-53.476,66.584,-53.436,66.622,-53.419,66.649,-53.223,66.721,-53.115,66.754,-53.038,66.827,-52.603,66.853,-52.491,66.85,-52.431,66.86,-52.387,66.881,-52.43,66.898,-52.56,66.909,-52.907,66.907,-53.227,66.919,-53.373,66.932,-53.444,66.925,-53.56,66.946,-53.687,66.986,-53.884,67.136,-53.805,67.327,-53.799,67.418,-53.548,67.498,-53.414,67.525,-53.224,67.585,-52.97,67.687,-52.666,67.75,-52.512,67.761,-52.384,67.752,-51.909,67.664,-51.665,67.646,-51.451,67.668,-51.181,67.637,-50.705,67.509,-50.613,67.528,-50.64,67.559,-51.171,67.694,-51.168,67.734,-51.032,67.744,-50.887,67.784,-50.969,67.807,-51.321,67.787,-51.423,67.754,-51.765,67.738,-51.944,67.765,-52.104,67.779,-52.345,67.837,-52.546,67.818,-52.673,67.795,-52.898,67.773,-52.98,67.758,-53.419,67.575,-53.604,67.536,-53.735,67.549,-53.643,67.668,-53.616,67.715,-53.616,67.767,-53.578,67.837,-53.353,67.971,-53.211,68.117,-53.152,68.208,-53.041,68.218,-52.89,68.205,-52.436,68.146,-52.058,68.075,-51.78,68.057,-51.597,68.055,-51.518,68.077,-51.456,68.116,-51.433,68.143,-51.415,68.198,-51.394,68.218,-51.333,68.242,-51.207,68.326,-51.169,68.385,-51.21,68.42]},{"b":[179.985,-16.541,180,-16.489],"l":[180,-16.54,179.987,-16.541,179.985,-16.522,180,-16.489]},{"b":[-180,-16.168,-179.945,-16.126],"l":[-180,-16.153,-179.969,-16.126,-179.945,-16.126,-179.956,-16.149,-180,-16.168]},{"b":[-85.961,16.404,-85.834,16.514],"l":[-85.961,16.43,-85.947,16.404,-85.871,16.462,-85.844,16.488,-85.834,16.511,-85.878,16.514,-85.924,16.483,-85.961,16.43]},{"b":[25.307,37.413,25.463,37.509],"l":[25.403,37.419,25.457,37.447,25.463,37.471,25.348,37.509,25.313,37.489,25.307,37.413,25.403,37.419]},{"b":[166.907,-0.551,166.958,-0.489],"l":[166.939,-0.489,166.914,-0.499,166.907,-0.524,166.916,-0.546,166.939,-0.551,166.958,-0.517,166.956,-0.497,166.939,-0.489]},{"b":[169.591,5.8,169.735,6.014],"l":[169.591,5.802,169.615,5.8,169.635,5.83,169.673,5.935,169.726,5.976,169.735,6.014,169.7,5.977,169.651,5.945,169.627,5.856,169.612,5.824,169.591,5.802]},{"b":[172.887,3.096,172.97,3.149],"l":[172.963,3.149,172.887,3.101,172.906,3.096,172.97,3.129,172.962,3.143,172.963,3.149]},{"b":[172.75,3.013,172.887,3.074],"l":[172.75,3.033,172.77,3.013,172.844,3.051,172.88,3.054,172.887,3.074,172.827,3.071,172.777,3.034,172.75,3.033]},{"b":[172.933,1.823,173.024,1.944],"l":[172.982,1.897,172.969,1.913,172.95,1.933,172.935,1.944,172.933,1.926,172.967,1.885,173.024,1.823,173.019,1.846,172.982,1.897]},{"b":[172.993,1.713,173.045,1.809],"l":[173.038,1.804,173.024,1.809,173.028,1.747,173.02,1.727,172.993,1.713,173.029,1.717,173.045,1.742,173.043,1.779,173.038,1.804]},{"b":[172.97,0.835,173.087,1.025],"l":[173.033,1.013,173.004,1.025,172.99,1.025,173.004,0.991,173.01,0.991,173.026,0.999,173.065,0.963,173.039,0.915,172.97,0.843,172.991,0.835,173.061,0.915,173.079,0.946,173.087,0.973,173.033,1.013]},{"b":[174.381,-0.829,174.509,-0.592],"l":[174.453,-0.647,174.408,-0.63,174.381,-0.592,174.394,-0.592,174.439,-0.627,174.475,-0.642,174.495,-0.726,174.509,-0.802,174.476,-0.829,174.464,-0.804,174.48,-0.774,174.453,-0.647]},{"b":[174.717,-1.263,174.779,-1.134],"l":[174.767,-1.187,174.744,-1.147,174.717,-1.134,174.741,-1.185,174.748,-1.236,174.756,-1.256,174.779,-1.263,174.773,-1.212,174.767,-1.187]},{"b":[168.675,7.294,168.83,7.336],"l":[168.679,7.336,168.675,7.322,168.719,7.303,168.815,7.294,168.83,7.309,168.755,7.322,168.679,7.336]},{"b":[171.577,6.973,171.757,7.048],"l":[171.757,6.973,171.73,6.977,171.693,7,171.659,7.01,171.614,7.007,171.593,7.016,171.577,7.048,171.615,7.027,171.688,7.028,171.757,6.973]},{"b":[171.036,7.069,171.394,7.172],"l":[171.394,7.111,171.367,7.096,171.305,7.081,171.263,7.069,171.235,7.069,171.202,7.074,171.096,7.109,171.036,7.156,171.05,7.172,171.102,7.138,171.227,7.087,171.394,7.111]},{"b":[-178.194,-14.325,-178.044,-14.232],"l":[-178.142,-14.243,-178.178,-14.232,-178.194,-14.255,-178.159,-14.312,-178.103,-14.325,-178.047,-14.318,-178.044,-14.303,-178.105,-14.284,-178.142,-14.243]},{"b":[-140.852,-17.876,-140.638,-17.67],"l":[-140.809,-17.857,-140.829,-17.849,-140.833,-17.838,-140.815,-17.804,-140.776,-17.754,-140.652,-17.683,-140.638,-17.678,-140.65,-17.67,-140.686,-17.684,-140.761,-17.718,-140.804,-17.752,-140.824,-17.788,-140.851,-17.831,-140.852,-17.867,-140.841,-17.873,-140.804,-17.876,-140.809,-17.857]},{"b":[-136.479,-18.566,-136.294,-18.463],"l":[-136.314,-18.566,-136.294,-18.544,-136.328,-18.519,-136.38,-18.497,-136.426,-18.474,-136.459,-18.463,-136.479,-18.471,-136.464,-18.485,-136.436,-18.489,-136.383,-18.514,-136.344,-18.535,-136.316,-18.545,-136.314,-18.566]},{"b":[-137.068,-18.361,-136.971,-18.265],"l":[-136.971,-18.361,-136.972,-18.342,-137.03,-18.273,-137.068,-18.265,-136.971,-18.361]},{"b":[-138.568,-20.876,-138.506,-20.771],"l":[-138.535,-20.876,-138.506,-20.857,-138.515,-20.813,-138.546,-20.771,-138.568,-20.787,-138.546,-20.795,-138.524,-20.851,-138.535,-20.876]},{"b":[-140.782,-18.416,-140.672,-18.334],"l":[-140.672,-18.416,-140.685,-18.38,-140.782,-18.334,-140.773,-18.364,-140.696,-18.399,-140.672,-18.416]},{"b":[-140.974,-18.217,-140.823,-18.059],"l":[-140.823,-18.217,-140.83,-18.189,-140.893,-18.121,-140.925,-18.084,-140.974,-18.059,-140.959,-18.085,-140.895,-18.148,-140.86,-18.199,-140.823,-18.217]},{"b":[-142.53,-16.107,-142.481,-16.018],"l":[-142.53,-16.107,-142.507,-16.028,-142.481,-16.018,-142.512,-16.096,-142.53,-16.107]},{"b":[-143.551,-16.669,-143.386,-16.612],"l":[-143.386,-16.669,-143.441,-16.62,-143.465,-16.614,-143.516,-16.612,-143.551,-16.621,-143.459,-16.635,-143.386,-16.669]},{"b":[-143.707,-16.64,-143.571,-16.581],"l":[-143.571,-16.635,-143.615,-16.618,-143.67,-16.581,-143.707,-16.581,-143.611,-16.64,-143.571,-16.635]},{"b":[-145.161,-15.901,-145.051,-15.757],"l":[-145.058,-15.901,-145.051,-15.856,-145.134,-15.762,-145.161,-15.757,-145.138,-15.788,-145.076,-15.858,-145.058,-15.901]},{"b":[-145.614,-16.347,-145.482,-16.079],"l":[-145.482,-16.347,-145.487,-16.33,-145.517,-16.278,-145.542,-16.225,-145.577,-16.16,-145.614,-16.079,-145.613,-16.132,-145.609,-16.165,-145.577,-16.201,-145.553,-16.251,-145.54,-16.295,-145.503,-16.346,-145.482,-16.347]},{"b":[173.011,1.338,173.172,1.388],"l":[173.038,1.342,173.011,1.338,173.029,1.359,173.143,1.381,173.153,1.388,173.172,1.375,173.171,1.363,173.163,1.358,173.106,1.357,173.062,1.346,173.038,1.342]},{"b":[-169.766,56.542,-169.474,56.635],"l":[-169.624,56.615,-169.755,56.635,-169.766,56.608,-169.633,56.546,-169.587,56.542,-169.474,56.594,-169.486,56.618,-169.55,56.628,-169.624,56.615]},{"b":[-72.96,19.986,-72.623,20.094],"l":[-72.851,20.094,-72.907,20.086,-72.96,20.062,-72.899,20.031,-72.878,20.027,-72.844,20.035,-72.74,20.003,-72.639,19.986,-72.623,20.014,-72.664,20.038,-72.791,20.092,-72.851,20.094]},{"b":[24.425,37.11,24.536,37.21],"l":[24.484,37.21,24.441,37.187,24.425,37.132,24.487,37.11,24.524,37.125,24.536,37.168,24.529,37.192,24.484,37.21]},{"b":[142.107,26.616,142.202,26.726],"l":[142.162,26.71,142.125,26.726,142.107,26.722,142.17,26.616,142.188,26.617,142.202,26.649,142.162,26.71]},{"b":[130.81,37.449,130.934,37.554],"l":[130.838,37.537,130.81,37.51,130.817,37.478,130.871,37.449,130.916,37.478,130.934,37.53,130.904,37.554,130.838,37.537]},{"b":[-180,-85.192,180,-63.226],"l":[-180,-84.352,-178.39,-84.337,-178.069,-84.352,-177.73,-84.395,-176.986,-84.399,-176.289,-84.418,-176.107,-84.475,-175.875,-84.51,-175.381,-84.48,-174.987,-84.465,-174.663,-84.463,-171.704,-84.542,-168.668,-84.684,-168.049,-84.729,-167.492,-84.834,-166.911,-84.819,-163.464,-84.901,-162.933,-84.901,-160.821,-84.987,-157.127,-85.186,-156.81,-85.192,-156.459,-85.186,-156.643,-85.079,-156.988,-84.982,-157.454,-84.912,-157.15,-84.891,-156.49,-84.889,-156.621,-84.84,-156.986,-84.811,-158.303,-84.778,-163.569,-84.529,-163.685,-84.513,-163.759,-84.493,-163.897,-84.47,-164.114,-84.445,-164.917,-84.431,-165.135,-84.41,-165.24,-84.381,-165.185,-84.37,-165.125,-84.375,-163.899,-84.353,-163.765,-84.324,-163.758,-84.305,-163.821,-84.291,-164.032,-84.274,-164.528,-84.191,-164.685,-84.155,-164.603,-84.097,-164.503,-84.072,-164.124,-84.054,-164.011,-84.016,-164.083,-83.946,-164.951,-83.806,-165.536,-83.757,-165.922,-83.79,-166.649,-83.792,-167.553,-83.811,-167.801,-83.791,-168.053,-83.735,-168.347,-83.637,-168.497,-83.611,-168.785,-83.529,-169.168,-83.45,-171.188,-83.256,-171.539,-83.204,-174.066,-82.9,-174.172,-82.848,-174.236,-82.793,-173.071,-82.916,-172.852,-82.917,-172.593,-82.884,-172.392,-82.893,-172.124,-82.862,-171.821,-82.847,-171.031,-82.943,-169.441,-83.096,-169.016,-83.15,-168.79,-83.188,-168.604,-83.202,-168.418,-83.229,-168.191,-83.213,-168.055,-83.227,-167.724,-83.217,-166.217,-83.201,-165.619,-83.216,-164.916,-83.29,-164.644,-83.412,-164.446,-83.468,-164.058,-83.425,-163.733,-83.373,-163.111,-83.329,-162.912,-83.347,-162.574,-83.411,-162.197,-83.519,-160.595,-83.49,-159.924,-83.495,-159.444,-83.543,-157.699,-83.381,-157.428,-83.346,-157.028,-83.234,-157.356,-83.198,-157.589,-83.187,-157.679,-83.129,-157.522,-83.107,-157.018,-83.075,-156.037,-83.027,-155.459,-82.981,-155.15,-82.858,-153.822,-82.669,-153.399,-82.586,-153.01,-82.45,-153.883,-82.177,-154.717,-81.941,-154.451,-81.868,-154.188,-81.811,-154.061,-81.765,-153.957,-81.7,-154.232,-81.623,-154.485,-81.566,-154.908,-81.51,-156.493,-81.377,-157.033,-81.319,-156.815,-81.231,-156.528,-81.162,-155.921,-81.133,-152.035,-81.029,-148.123,-80.901,-148.023,-80.836,-148.543,-80.76,-148.984,-80.742,-149.147,-80.719,-149.207,-80.67,-149.214,-80.604,-149.264,-80.593,-149.429,-80.586,-150.133,-80.51,-150.282,-80.48,-150.516,-80.409,-150.575,-80.354,-150.435,-80.211,-150.221,-80.15,-149.845,-80.118,-149.578,-80.106,-148.766,-80.108,-148.448,-80.091,-148.317,-80.071,-148.34,-80.003,-148.43,-79.971,-148.433,-79.929,-148.296,-79.907,-148.129,-79.908,-148.083,-79.857,-148.177,-79.776,-148.417,-79.731,-149.051,-79.657,-150.491,-79.546,-151.048,-79.46,-151.368,-79.393,-151.636,-79.318,-151.904,-79.281,-152.091,-79.242,-152.053,-79.193,-152.138,-79.116,-152.244,-79.103,-152.701,-79.135,-153.518,-79.117,-154.518,-79.047,-155.21,-78.965,-156.115,-78.745,-156.469,-78.635,-156.208,-78.559,-155.92,-78.51,-154.716,-78.398,-154.538,-78.359,-154.293,-78.259,-154.695,-78.217,-155.037,-78.221,-155.342,-78.192,-156.569,-78.186,-157.267,-78.2,-157.848,-78.074,-158.286,-77.951,-158.407,-77.888,-158.5,-77.778,-158.351,-77.615,-158.23,-77.498,-158.246,-77.354,-158.214,-77.157,-158.003,-77.091,-157.842,-77.079,-157.465,-77.231,-157.139,-77.242,-156.668,-77.213,-156.368,-77.135,-156.211,-77.106,-155.92,-77.098,-155.359,-77.133,-155.102,-77.12,-154.815,-77.127,-153.91,-77.227,-153.713,-77.274,-153.606,-77.31,-153.573,-77.363,-153.461,-77.416,-153.077,-77.442,-151.998,-77.413,-151.719,-77.426,-150.956,-77.574,-150.306,-77.731,-150.084,-77.771,-149.718,-77.797,-149.588,-77.774,-149.474,-77.715,-149.126,-77.643,-148.34,-77.551,-148.156,-77.462,-148.26,-77.413,-148.559,-77.361,-148.744,-77.343,-148.844,-77.284,-148.839,-77.202,-148.777,-77.125,-148.572,-77.105,-148.196,-77.211,-147.73,-77.31,-147.566,-77.325,-147.442,-77.321,-147.207,-77.286,-146.928,-77.26,-146.391,-77.472,-146.074,-77.487,-145.677,-77.488,-145.601,-77.455,-145.65,-77.398,-145.714,-77.338,-145.794,-77.33,-145.808,-77.273,-145.635,-77.221,-145.516,-77.199,-145.563,-77.162,-145.753,-77.103,-145.864,-77.094,-145.967,-77.069,-145.934,-77.029,-145.806,-77.012,-145.629,-76.954,-145.686,-76.884,-145.676,-76.797,-145.75,-76.749,-146.166,-76.658,-146.777,-76.507,-147.34,-76.438,-148.601,-76.493,-149.046,-76.458,-149.34,-76.419,-149.654,-76.365,-149.285,-76.311,-148.895,-76.272,-148.78,-76.238,-148.632,-76.168,-148.459,-76.118,-148.32,-76.104,-147.86,-76.131,-146.817,-76.318,-146.597,-76.338,-145.886,-76.424,-145.687,-76.429,-145.442,-76.409,-145.642,-76.326,-145.86,-76.267,-146.383,-76.1,-146.323,-76.02,-145.988,-75.889,-145.106,-75.879,-144.721,-75.832,-144.221,-75.731,-143.574,-75.564,-143.022,-75.543,-142.33,-75.491,-142.094,-75.53,-141.506,-75.69,-141.135,-75.746,-141.009,-75.751,-140.874,-75.746,-141.223,-75.546,-140.999,-75.52,-140.709,-75.498,-140.471,-75.447,-140.294,-75.406,-139.691,-75.213,-139.149,-75.16,-137.618,-75.076,-137.09,-75.153,-136.65,-75.162,-136.55,-75.139,-136.462,-75.036,-136.228,-74.836,-136.03,-74.765,-135.362,-74.69,-134.84,-74.694,-134.465,-74.776,-134.117,-74.83,-133.796,-74.855,-133.475,-74.852,-132.992,-74.806,-132.351,-74.789,-132.049,-74.766,-131.707,-74.811,-130.857,-74.826,-130.196,-74.891,-129.791,-74.891,-129.238,-74.829,-128.941,-74.82,-127.863,-74.719,-127.02,-74.698,-126.384,-74.743,-125.353,-74.715,-124.312,-74.736,-123.889,-74.773,-121.544,-74.75,-119.677,-74.655,-119.422,-74.622,-119.022,-74.518,-118.803,-74.422,-118.656,-74.393,-118.342,-74.382,-117.806,-74.403,-117.068,-74.473,-116.433,-74.447,-115.223,-74.487,-115.105,-74.455,-114.991,-74.275,-114.791,-73.989,-114.624,-73.903,-114.346,-73.925,-113.508,-74.089,-113.489,-74.158,-113.575,-74.208,-113.714,-74.228,-113.753,-74.367,-113.641,-74.406,-113.454,-74.394,-113.333,-74.454,-113.597,-74.559,-113.783,-74.618,-113.903,-74.644,-113.985,-74.843,-114.097,-74.909,-114.11,-74.982,-113.932,-74.982,-113.753,-74.952,-113.593,-74.944,-113.092,-74.892,-112.17,-74.832,-111.868,-74.801,-111.696,-74.792,-111.584,-74.751,-111.739,-74.653,-111.789,-74.572,-111.696,-74.504,-111.722,-74.387,-111.806,-74.27,-111.63,-74.181,-111.467,-74.201,-111.18,-74.188,-111.02,-74.23,-110.77,-74.269,-110.534,-74.289,-110.307,-74.367,-110.23,-74.536,-110.3,-74.711,-110.532,-74.836,-110.968,-74.951,-111.463,-75.133,-111.359,-75.22,-111.104,-75.191,-109.99,-75.199,-109.272,-75.185,-108.822,-75.207,-108.254,-75.253,-107.805,-75.322,-107.267,-75.334,-106.932,-75.309,-106.619,-75.344,-105.399,-75.198,-104.902,-75.115,-104.618,-75.156,-104.16,-75.121,-103.901,-75.153,-103.425,-75.101,-103.121,-75.095,-102.771,-75.117,-101.708,-75.127,-101.628,-75.222,-101.304,-75.366,-101.039,-75.422,-100.706,-75.398,-100.463,-75.353,-100.083,-75.37,-99.531,-75.309,-98.98,-75.327,-98.752,-75.317,-98.646,-75.277,-98.558,-75.19,-98.727,-75.141,-99.208,-75.079,-99.652,-74.949,-99.849,-74.922,-100.164,-74.938,-100.313,-74.914,-100.473,-74.872,-100.265,-74.823,-100.013,-74.662,-100.119,-74.515,-100.238,-74.484,-100.531,-74.489,-100.882,-74.541,-101.023,-74.505,-101.252,-74.486,-101.343,-74.35,-101.587,-74.096,-101.715,-74.024,-102.105,-73.958,-102.441,-73.926,-102.766,-73.884,-102.863,-73.784,-102.8,-73.646,-102.411,-73.616,-102.037,-73.631,-101.828,-73.655,-101.587,-73.667,-101.311,-73.695,-101.13,-73.735,-100.985,-73.757,-100.718,-73.758,-99.781,-73.72,-99.656,-73.694,-99.541,-73.645,-99.343,-73.634,-99.162,-73.641,-98.896,-73.611,-99.2,-73.571,-99.528,-73.495,-100.021,-73.403,-100.436,-73.353,-101.189,-73.318,-101.574,-73.33,-101.816,-73.311,-102.675,-73.321,-102.909,-73.285,-103.076,-73.185,-103.308,-72.945,-103.375,-72.819,-103.217,-72.772,-103.11,-72.721,-102.856,-72.716,-102.485,-72.736,-102.363,-72.76,-102.272,-72.835,-102.363,-72.911,-102.482,-72.951,-102.409,-72.987,-102.029,-72.998,-101.842,-73.021,-101.681,-73.03,-101.332,-72.995,-100.821,-72.981,-100.564,-73.016,-100.259,-73.041,-99.811,-73,-98.209,-73.022,-98.012,-73.033,-97.819,-73.102,-97.651,-73.144,-97.476,-73.126,-96.956,-73.206,-96.676,-73.269,-96.394,-73.301,-96.152,-73.309,-95.881,-73.294,-95.529,-73.241,-95.237,-73.22,-95.03,-73.239,-94.586,-73.25,-94.246,-73.313,-93.985,-73.287,-93.706,-73.215,-92.828,-73.165,-92.241,-73.178,-91.169,-73.307,-90.921,-73.319,-90.431,-73.243,-90.274,-73.119,-90.295,-72.978,-90.152,-72.945,-90.035,-72.96,-89.818,-72.863,-89.522,-72.871,-89.341,-72.89,-89.229,-72.826,-89.127,-72.693,-88.78,-72.683,-88.527,-72.702,-88.194,-72.787,-88.195,-72.859,-88.332,-72.934,-88.561,-73.121,-88.419,-73.229,-88.205,-73.22,-87.936,-73.241,-87.608,-73.195,-87.401,-73.192,-87.038,-73.354,-86.791,-73.364,-86.602,-73.354,-85.981,-73.208,-85.801,-73.192,-85.582,-73.259,-85.261,-73.413,-84.981,-73.502,-84.571,-73.557,-84.214,-73.573,-83.796,-73.645,-83.565,-73.706,-83.042,-73.707,-82.815,-73.732,-82.183,-73.857,-81.606,-73.796,-81.309,-73.738,-81.163,-73.632,-81.236,-73.474,-81.262,-73.315,-81.176,-73.249,-81.024,-73.236,-80.336,-73.414,-80.38,-73.308,-80.439,-73.225,-80.614,-73.083,-80.588,-72.978,-80.442,-72.945,-80.152,-73,-79.808,-73.028,-79.522,-73.09,-78.964,-73.312,-78.786,-73.507,-78.408,-73.556,-78.144,-73.547,-77.846,-73.515,-77.444,-73.488,-77.136,-73.496,-76.85,-73.46,-76.765,-73.566,-77.033,-73.718,-77.135,-73.818,-77.049,-73.844,-76.887,-73.821,-76.755,-73.789,-76.291,-73.805,-75.916,-73.736,-75.595,-73.711,-75.293,-73.639,-75.044,-73.645,-74.855,-73.658,-74.594,-73.715,-74.345,-73.684,-74.197,-73.696,-73.996,-73.7,-72.929,-73.448,-72.687,-73.452,-72.381,-73.438,-71.994,-73.379,-71.698,-73.353,-71.453,-73.354,-71.017,-73.263,-70.323,-73.274,-69.969,-73.226,-69.282,-73.17,-68.821,-73.105,-68,-72.936,-67.667,-72.835,-67.307,-72.611,-67.08,-72.388,-66.828,-72.09,-66.952,-71.897,-67.084,-71.812,-67.196,-71.719,-67.46,-71.527,-67.53,-71.285,-67.505,-71.058,-67.598,-70.845,-67.692,-70.686,-67.888,-70.422,-68.126,-70.25,-68.403,-70.02,-68.404,-69.809,-68.47,-69.644,-68.638,-69.526,-68.708,-69.432,-68.58,-69.413,-68.462,-69.384,-68.141,-69.348,-67.372,-69.412,-67.304,-69.318,-67.11,-69.248,-66.975,-69.161,-67.021,-69.029,-67.188,-68.974,-67.391,-68.861,-67.299,-68.771,-67.134,-68.771,-67.054,-68.671,-67.117,-68.575,-67.041,-68.453,-66.894,-68.298,-66.793,-68.24,-66.978,-68.147,-67.15,-68.025,-67.107,-67.93,-67.021,-67.831,-66.915,-67.693,-66.77,-67.593,-66.677,-67.56,-66.705,-67.527,-66.923,-67.492,-67.124,-67.485,-67.487,-67.547,-67.545,-67.535,-67.565,-67.503,-67.586,-67.435,-67.55,-67.269,-67.493,-67.113,-67.44,-67.091,-67.299,-67.071,-67.16,-66.952,-67.034,-66.945,-66.955,-66.985,-66.929,-67.144,-66.886,-67.18,-66.902,-67.256,-66.836,-67.282,-66.757,-67.233,-66.61,-67.209,-66.552,-67.263,-66.499,-67.289,-66.472,-67.243,-66.491,-67.114,-66.515,-67.062,-66.533,-66.979,-66.502,-66.94,-66.465,-66.875,-66.527,-66.741,-66.504,-66.69,-66.371,-66.609,-66.307,-66.592,-66.182,-66.592,-65.954,-66.646,-65.847,-66.65,-65.766,-66.625,-65.718,-66.573,-65.678,-66.403,-65.776,-66.343,-65.775,-66.288,-65.717,-66.254,-65.617,-66.135,-65.465,-66.129,-65.316,-66.14,-65.172,-66.117,-65.222,-66.068,-65.267,-65.994,-65.105,-65.958,-64.999,-65.946,-64.722,-65.993,-64.614,-66.019,-64.514,-65.96,-64.547,-65.9,-64.653,-65.867,-64.673,-65.814,-64.647,-65.748,-64.475,-65.781,-64.435,-65.768,-64.39,-65.708,-64.417,-65.68,-64.439,-65.641,-64.213,-65.633,-64.18,-65.617,-64.132,-65.571,-64.066,-65.554,-63.862,-65.556,-63.818,-65.532,-63.798,-65.48,-63.908,-65.467,-64.051,-65.417,-64.071,-65.278,-64.038,-65.179,-63.912,-65.093,-63.76,-65.033,-63.482,-65.085,-63.264,-65.073,-63.178,-65.126,-63.059,-65.139,-63.033,-65.08,-63.086,-65.028,-63.12,-64.942,-62.775,-64.842,-62.665,-64.858,-62.528,-64.833,-62.576,-64.756,-62.503,-64.656,-62.404,-64.643,-62.338,-64.729,-62.243,-64.747,-62.14,-64.727,-61.883,-64.625,-61.756,-64.61,-61.632,-64.605,-61.5,-64.546,-61.47,-64.476,-61.396,-64.427,-61.174,-64.362,-61.082,-64.315,-60.887,-64.15,-60.922,-64.108,-60.864,-64.073,-60.277,-63.924,-59.99,-63.91,-59.51,-63.821,-59.218,-63.714,-59.036,-63.67,-58.872,-63.552,-58.674,-63.534,-58.216,-63.451,-57.868,-63.319,-57.39,-63.226,-57.168,-63.235,-57.077,-63.262,-57.021,-63.373,-56.927,-63.506,-56.782,-63.572,-56.835,-63.631,-56.974,-63.625,-57.119,-63.638,-57.152,-63.572,-57.098,-63.524,-57.152,-63.479,-57.284,-63.491,-57.461,-63.514,-57.581,-63.547,-57.737,-63.617,-57.857,-63.657,-58.263,-63.763,-58.532,-63.915,-58.723,-64.077,-58.839,-64.187,-59.005,-64.195,-59.047,-64.234,-58.977,-64.266,-58.923,-64.279,-58.799,-64.293,-58.819,-64.339,-58.905,-64.352,-58.895,-64.389,-58.806,-64.445,-58.786,-64.524,-58.892,-64.537,-59.051,-64.451,-59.229,-64.444,-59.37,-64.404,-59.461,-64.346,-59.547,-64.359,-59.612,-64.44,-59.573,-64.531,-59.646,-64.584,-59.734,-64.559,-59.765,-64.451,-59.85,-64.434,-59.963,-64.431,-60.242,-64.547,-60.341,-64.551,-60.394,-64.609,-60.556,-64.677,-60.66,-64.729,-60.915,-64.907,-61.06,-64.981,-61.332,-65.024,-61.439,-65.018,-61.503,-65,-61.603,-64.988,-61.703,-64.987,-61.736,-65.033,-61.577,-65.186,-61.663,-65.239,-61.856,-65.235,-61.948,-65.192,-62.025,-65.233,-62.085,-65.273,-62.145,-65.332,-62.054,-65.457,-61.903,-65.513,-61.796,-65.523,-61.756,-65.569,-61.991,-65.589,-62.151,-65.699,-62.222,-65.775,-62.305,-65.84,-62.293,-65.916,-62.169,-66.031,-62.005,-66.113,-61.839,-66.12,-61.625,-66.095,-61.575,-66.071,-61.359,-66.059,-61.266,-65.98,-61.198,-65.975,-61.138,-65.989,-61.039,-65.992,-60.988,-65.94,-60.913,-65.921,-60.813,-65.934,-60.618,-65.933,-60.565,-65.979,-60.625,-66.032,-60.744,-66.105,-60.856,-66.065,-60.956,-66.072,-61.009,-66.111,-60.903,-66.191,-60.942,-66.264,-61.028,-66.337,-61.134,-66.29,-61.15,-66.212,-61.293,-66.165,-61.432,-66.145,-61.526,-66.226,-61.676,-66.25,-61.696,-66.343,-61.756,-66.429,-61.842,-66.403,-61.875,-66.296,-62.117,-66.209,-62.241,-66.197,-62.494,-66.219,-62.583,-66.217,-62.682,-66.237,-62.755,-66.31,-62.65,-66.364,-62.615,-66.436,-62.618,-66.49,-62.638,-66.511,-62.655,-66.556,-62.543,-66.621,-62.537,-66.707,-62.629,-66.706,-62.705,-66.68,-62.997,-66.453,-63.18,-66.353,-63.258,-66.264,-63.449,-66.244,-63.587,-66.242,-63.753,-66.278,-63.688,-66.32,-63.654,-66.383,-63.756,-66.409,-63.88,-66.506,-63.964,-66.589,-64.015,-66.607,-64.078,-66.654,-63.809,-66.761,-63.769,-66.803,-63.755,-66.873,-63.84,-66.912,-64.043,-66.927,-64.401,-66.853,-64.554,-66.852,-64.607,-66.8,-64.686,-66.806,-64.735,-66.894,-64.793,-66.972,-64.878,-67.025,-64.854,-67.105,-64.785,-67.124,-64.839,-67.156,-64.951,-67.183,-65.027,-67.214,-64.858,-67.243,-64.826,-67.269,-64.819,-67.307,-65.08,-67.335,-65.249,-67.342,-65.35,-67.311,-65.443,-67.326,-65.503,-67.377,-65.523,-67.445,-65.504,-67.528,-65.471,-67.588,-65.447,-67.61,-65.418,-67.66,-65.574,-67.788,-65.589,-67.816,-65.6,-67.876,-65.528,-67.93,-65.469,-68.009,-65.551,-68.048,-65.64,-68.131,-65.546,-68.147,-65.387,-68.15,-65.218,-68.14,-64.959,-68.068,-64.885,-68.056,-64.853,-68.083,-64.829,-68.127,-64.896,-68.168,-65.365,-68.287,-65.452,-68.337,-65.331,-68.364,-65.09,-68.37,-64.996,-68.408,-65.055,-68.449,-65.14,-68.489,-65.242,-68.583,-65.158,-68.618,-64.898,-68.673,-64.429,-68.746,-64.078,-68.771,-64.157,-68.687,-64.169,-68.583,-63.924,-68.498,-63.796,-68.47,-63.217,-68.419,-63.057,-68.421,-62.933,-68.443,-62.98,-68.486,-63.115,-68.471,-63.348,-68.499,-63.707,-68.592,-63.773,-68.632,-63.747,-68.705,-63.443,-68.764,-63.344,-68.81,-63.478,-68.951,-63.456,-69.042,-63.301,-69.141,-63.094,-69.253,-62.994,-69.329,-62.84,-69.372,-62.587,-69.477,-62.451,-69.584,-62.407,-69.827,-62.202,-70.028,-61.961,-70.12,-61.935,-70.2,-62.014,-70.279,-62.218,-70.233,-62.331,-70.279,-62.378,-70.365,-62.232,-70.424,-62.001,-70.497,-61.505,-70.491,-61.491,-70.57,-61.605,-70.617,-61.696,-70.676,-61.809,-70.709,-61.994,-70.729,-62.04,-70.801,-61.961,-70.901,-61.702,-70.857,-61.513,-70.851,-61.313,-70.868,-61.252,-71.002,-61.017,-71.167,-60.962,-71.245,-61.003,-71.319,-61.148,-71.342,-61.237,-71.401,-61.369,-71.452,-61.516,-71.479,-61.79,-71.616,-61.91,-71.631,-61.959,-71.658,-61.725,-71.673,-61.563,-71.675,-61.214,-71.564,-61.081,-71.589,-60.995,-71.661,-60.949,-71.747,-61.035,-71.82,-61.645,-71.863,-61.939,-71.904,-62.257,-72.018,-61.894,-72.071,-61.628,-72.053,-61.493,-72.073,-61.31,-72.113,-61.107,-72.092,-60.952,-72.05,-60.833,-72.052,-60.719,-72.073,-60.704,-72.144,-60.691,-72.27,-60.665,-72.362,-60.73,-72.426,-61.048,-72.471,-61.28,-72.468,-61.286,-72.601,-60.939,-72.7,-60.724,-72.647,-60.532,-72.673,-60.532,-72.832,-60.385,-73.007,-60.254,-73.017,-60.149,-72.938,-60.01,-72.938,-59.957,-73.031,-60.016,-73.189,-60.122,-73.275,-60.404,-73.24,-60.561,-73.211,-60.687,-73.271,-60.896,-73.32,-61.081,-73.328,-61.242,-73.25,-61.428,-73.191,-61.726,-73.161,-62.008,-73.148,-61.915,-73.216,-61.788,-73.255,-61.738,-73.375,-61.637,-73.5,-61.405,-73.467,-61.08,-73.539,-60.879,-73.612,-60.79,-73.712,-60.903,-73.871,-61.088,-73.929,-61.203,-73.957,-61.404,-73.896,-61.545,-73.896,-61.692,-73.924,-61.742,-73.996,-61.838,-74.032,-61.319,-74.036,-61.161,-74.056,-61.042,-74.122,-61.227,-74.208,-61.571,-74.195,-61.718,-74.228,-61.843,-74.29,-61.332,-74.329,-61.121,-74.307,-60.784,-74.241,-60.704,-74.307,-60.838,-74.373,-61.011,-74.478,-61.37,-74.512,-61.64,-74.514,-61.995,-74.476,-62.089,-74.453,-62.235,-74.441,-62.226,-74.506,-62.133,-74.55,-61.894,-74.713,-61.855,-74.777,-61.928,-74.863,-62.138,-74.926,-62.372,-74.952,-62.567,-74.896,-62.708,-74.737,-62.887,-74.691,-63.072,-74.678,-63.178,-74.684,-63.168,-74.765,-63.125,-74.85,-63.198,-74.909,-63.357,-74.878,-63.559,-74.906,-63.751,-74.952,-63.925,-75.004,-63.571,-75.03,-63.337,-75.035,-63.173,-75.115,-63.231,-75.154,-63.551,-75.171,-63.858,-75.206,-64.28,-75.293,-63.972,-75.329,-63.678,-75.328,-63.475,-75.336,-63.304,-75.352,-63.258,-75.399,-63.363,-75.451,-64.053,-75.58,-64.778,-75.738,-65.044,-75.787,-65.322,-75.815,-65.966,-75.952,-66.37,-76.013,-67.518,-76.11,-69.304,-76.351,-69.915,-76.522,-70.096,-76.654,-70.21,-76.674,-70.551,-76.718,-70.895,-76.739,-71.799,-76.753,-72.722,-76.689,-73.472,-76.675,-73.88,-76.697,-75.268,-76.581,-75.444,-76.587,-75.659,-76.608,-75.831,-76.608,-75.963,-76.592,-76.244,-76.585,-77.19,-76.63,-77.287,-76.702,-77.168,-76.834,-76.824,-76.993,-76.249,-77.275,-75.937,-77.334,-75.748,-77.398,-75.387,-77.474,-74.581,-77.478,-73.478,-77.536,-72.852,-77.59,-72.875,-77.694,-73.252,-77.894,-73.485,-77.971,-74.042,-78.109,-74.812,-78.178,-75.398,-78.158,-76.438,-78.044,-77.742,-77.94,-79.679,-77.843,-80.104,-77.797,-80.602,-77.752,-80.889,-77.798,-81.103,-77.842,-81.581,-77.846,-81.441,-77.886,-79.51,-78.154,-77.858,-78.351,-77.665,-78.401,-77.433,-78.435,-77.452,-78.56,-77.545,-78.66,-77.869,-78.746,-78.712,-78.752,-79.767,-78.821,-80.292,-78.823,-80.816,-78.754,-81.929,-78.559,-82.608,-78.412,-83.083,-78.247,-83.412,-78.115,-83.779,-77.984,-83.752,-78.066,-83.688,-78.148,-83.508,-78.248,-83.246,-78.357,-83.227,-78.402,-83.355,-78.408,-83.544,-78.355,-83.706,-78.404,-83.763,-78.461,-83.697,-78.537,-83.595,-78.611,-83.26,-78.774,-82.971,-78.817,-82.589,-78.916,-81.661,-79.1,-81.503,-79.163,-81.222,-79.298,-81.163,-79.4,-80.892,-79.502,-80.705,-79.517,-80.535,-79.513,-80.479,-79.426,-80.489,-79.321,-80.416,-79.295,-80.151,-79.268,-79.456,-79.304,-76.499,-79.326,-76.218,-79.387,-76.105,-79.465,-76.032,-79.627,-76.344,-79.821,-76.558,-79.904,-76.904,-79.955,-77.222,-79.994,-77.702,-80.01,-78.692,-79.995,-79.66,-79.997,-78.907,-80.09,-78.176,-80.167,-77.16,-80.153,-76.757,-80.131,-76.407,-80.095,-76.26,-80.16,-75.986,-80.295,-75.822,-80.338,-75.709,-80.383,-75.555,-80.531,-75.495,-80.617,-75.345,-80.719,-75.237,-80.803,-75.076,-80.86,-74.807,-80.887,-74.511,-80.838,-73.938,-80.816,-73.383,-80.894,-73.029,-80.917,-72.553,-80.853,-72.174,-80.764,-71.38,-80.682,-71.231,-80.647,-71.018,-80.619,-70.688,-80.626,-70.56,-80.647,-70.392,-80.735,-70.239,-80.857,-70.012,-80.918,-69.772,-80.962,-69.634,-80.966,-69.182,-81.005,-68.59,-80.968,-68.327,-81.004,-68.285,-81.074,-68.144,-81.13,-67.965,-81.148,-65.574,-81.461,-64.75,-81.522,-63.478,-81.553,-62.49,-81.557,-62.354,-81.577,-62.165,-81.636,-62.542,-81.678,-62.946,-81.684,-63.554,-81.667,-63.769,-81.676,-64.233,-81.66,-64.476,-81.672,-64.696,-81.652,-65.022,-81.696,-65.62,-81.729,-65.487,-81.775,-65.264,-81.786,-64.811,-81.803,-64.19,-81.795,-64.137,-81.869,-64.706,-81.887,-65.916,-81.902,-66.042,-81.914,-66.134,-81.953,-65.953,-81.971,-65.844,-81.993,-65.787,-82.046,-65.913,-82.183,-65.714,-82.279,-65.572,-82.294,-65.424,-82.28,-65.17,-82.318,-64.92,-82.371,-64.397,-82.374,-63.773,-82.304,-63.466,-82.307,-62.645,-82.263,-61.902,-82.271,-60.859,-82.187,-60.687,-82.189,-60.528,-82.2,-60.817,-82.276,-62.095,-82.467,-62.553,-82.503,-62.736,-82.527,-62.631,-82.621,-62.466,-82.718,-62.129,-82.822,-61.917,-82.977,-61.709,-83.01,-61.313,-82.939,-61.218,-82.992,-61.2,-83.098,-61.303,-83.184,-61.436,-83.232,-61.531,-83.279,-61.59,-83.341,-61.425,-83.396,-60.983,-83.428,-60.397,-83.441,-59.854,-83.442,-59.516,-83.458,-58.29,-83.121,-57.798,-82.959,-57.557,-82.89,-57.354,-82.84,-56.318,-82.633,-56.075,-82.57,-55.801,-82.478,-55.295,-82.465,-54.601,-82.316,-53.986,-82.201,-53.74,-82.178,-53.558,-82.169,-53.339,-82.145,-52.799,-82.154,-52.415,-82.135,-51.731,-82.062,-51.21,-82.015,-50.653,-81.975,-50.029,-81.968,-48.361,-81.892,-47.887,-81.925,-47.36,-82.004,-47.02,-82.003,-46.567,-81.979,-46.258,-81.947,-46.119,-82.04,-46.046,-82.159,-46.199,-82.271,-46.448,-82.34,-46.517,-82.455,-46.175,-82.512,-45.789,-82.495,-45.044,-82.438,-44.455,-82.366,-44.292,-82.318,-44.064,-82.331,-43.669,-82.27,-43.18,-82.017,-42.565,-81.762,-42.046,-81.598,-41.712,-81.408,-41.434,-81.298,-41.126,-81.215,-40.915,-81.172,-40.441,-81.165,-39.762,-81.032,-38.772,-80.882,-38.011,-80.954,-37.209,-81.064,-36.812,-80.975,-36.5,-80.96,-36.234,-80.921,-35.966,-80.891,-35.776,-80.813,-35.521,-80.746,-35.327,-80.651,-34.35,-80.603,-33.329,-80.54,-33.191,-80.519,-33.057,-80.532,-32.706,-80.514,-32.256,-80.461,-31.634,-80.445,-31.312,-80.45,-31.015,-80.308,-30.425,-80.28,-29.797,-80.223,-29.531,-80.182,-29.329,-80.172,-24.24,-80.062,-24.02,-80.009,-23.574,-79.965,-23.407,-79.859,-24.088,-79.815,-24.3,-79.771,-24.534,-79.758,-24.67,-79.775,-25.259,-79.762,-29.949,-79.599,-30.049,-79.585,-30.211,-79.485,-30.178,-79.304,-30.316,-79.163,-30.645,-79.124,-30.985,-79.128,-31.413,-79.145,-32.542,-79.222,-32.994,-79.229,-34.197,-79.11,-34.995,-78.978,-35.516,-78.933,-35.89,-78.844,-36.239,-78.774,-36.266,-78.616,-36.181,-78.468,-35.509,-78.041,-35.088,-77.837,-34.808,-77.821,-34.551,-77.729,-34.29,-77.522,-34.076,-77.425,-33.591,-77.311,-33.377,-77.282,-32.614,-77.141,-32.405,-77.136,-32.063,-77.16,-31.676,-77.033,-30.489,-76.762,-30.222,-76.66,-29.892,-76.598,-28.934,-76.37,-28.079,-76.258,-27.653,-76.226,-27.135,-76.157,-26.56,-76.055,-26.059,-75.957,-24.27,-75.767,-23.197,-75.718,-22.465,-75.661,-21.948,-75.694,-21.434,-75.683,-20.989,-75.634,-20.783,-75.594,-20.487,-75.492,-19.493,-75.54,-18.851,-75.47,-18.585,-75.463,-18.305,-75.431,-18.415,-75.396,-18.517,-75.39,-18.617,-75.342,-18.749,-75.242,-18.617,-75.115,-18.517,-75.052,-18.221,-74.975,-18.068,-74.863,-17.923,-74.699,-17.436,-74.379,-17.299,-74.334,-16.989,-74.32,-16.727,-74.328,-16.43,-74.324,-15.673,-74.407,-15.531,-74.376,-15.29,-74.281,-15.089,-74.163,-14.659,-73.989,-14.574,-73.937,-14.611,-73.852,-15.26,-73.889,-15.749,-73.946,-16.22,-73.916,-16.282,-73.867,-16.181,-73.83,-16.003,-73.816,-15.936,-73.758,-16.097,-73.709,-16.388,-73.681,-16.519,-73.644,-16.507,-73.556,-16.435,-73.426,-16.279,-73.388,-16.149,-73.334,-15.803,-73.152,-15.596,-73.097,-15.007,-73.047,-14.321,-73.123,-14.165,-73.102,-14,-73.001,-14.168,-72.843,-14.298,-72.785,-14.298,-72.733,-13.939,-72.756,-13.603,-72.792,-13.209,-72.785,-12.747,-72.629,-12.095,-72.498,-11.777,-72.444,-11.497,-72.413,-11.346,-72.282,-11.121,-72.032,-10.958,-71.902,-10.961,-71.822,-11.009,-71.758,-11.179,-71.777,-11.333,-71.786,-11.697,-71.719,-12.148,-71.614,-12.285,-71.495,-12.351,-71.39,-12.208,-71.332,-12.074,-71.297,-11.926,-71.289,-11.663,-71.331,-11.328,-71.44,-11.16,-71.481,-10.97,-71.56,-10.825,-71.553,-10.659,-71.443,-10.52,-71.296,-10.407,-71.25,-10.231,-71.201,-10.033,-71.131,-10.122,-71.061,-10.331,-71.024,-10.36,-70.982,-10.271,-70.936,-10.099,-70.926,-9.888,-71.027,-9.599,-71.095,-9.402,-71.118,-9.231,-71.174,-8.966,-71.361,-8.646,-71.673,-8.498,-71.675,-8.216,-71.647,-7.916,-71.635,-7.714,-71.546,-7.669,-71.324,-7.59,-71.224,-7.618,-71.121,-7.757,-71.017,-7.873,-70.94,-7.855,-70.885,-7.753,-70.843,-7.62,-70.829,-7.388,-70.787,-7.032,-70.835,-6.838,-70.845,-6.548,-70.817,-6.245,-70.756,-5.936,-70.713,-5.695,-70.745,-5.588,-70.857,-5.709,-70.968,-5.904,-71.052,-6.08,-71.154,-6.127,-71.266,-6.117,-71.326,-5.95,-71.342,-4.45,-71.328,-4.253,-71.338,-3.995,-71.339,-3.713,-71.375,-3.24,-71.36,-2.812,-71.321,-2.61,-71.321,-2.261,-71.357,-2.015,-71.433,-1.501,-71.412,-1.354,-71.387,-1.216,-71.284,-1.068,-71.266,-0.896,-71.349,-0.84,-71.54,-0.76,-71.63,-0.543,-71.713,-0.327,-71.642,-0.185,-71.559,0.154,-71.398,0.538,-71.274,0.835,-71.202,1.552,-71.08,1.909,-71.004,2.609,-70.9,3.507,-70.844,5.113,-70.656,5.644,-70.636,6.508,-70.586,6.951,-70.535,7.401,-70.494,7.677,-70.356,8.307,-70.462,8.523,-70.474,8.817,-70.391,9.142,-70.184,9.613,-70.269,9.886,-70.403,10.218,-70.508,10.969,-70.688,11.204,-70.729,11.701,-70.767,11.834,-70.737,12.068,-70.617,12.309,-70.443,12.462,-70.37,12.682,-70.309,12.929,-70.213,12.865,-70.162,12.723,-70.144,12.595,-70.117,12.626,-70.066,13.066,-70.054,13.298,-70.23,13.533,-70.287,13.823,-70.343,14.492,-70.3,15.064,-70.295,15.563,-70.331,15.807,-70.324,16.025,-70.193,16.381,-70.145,16.585,-70.204,16.709,-70.397,17.167,-70.451,18.125,-70.54,18.232,-70.518,18.351,-70.416,18.433,-70.29,18.627,-70.269,18.877,-70.201,19.009,-70.212,19.196,-70.293,19.132,-70.492,19.027,-70.674,19.153,-70.821,19.265,-70.902,19.409,-70.917,19.652,-70.921,19.944,-70.91,20.128,-70.918,21.071,-70.843,21.186,-70.681,21.337,-70.495,21.705,-70.258,21.849,-70.277,21.962,-70.3,22.216,-70.417,22.366,-70.475,22.396,-70.561,22.234,-70.643,22.278,-70.696,22.445,-70.74,22.979,-70.81,23.15,-70.796,23.407,-70.723,23.665,-70.575,23.804,-70.405,24.024,-70.413,24.236,-70.449,24.386,-70.537,24.386,-70.704,24.588,-70.82,24.757,-70.892,25.187,-70.971,25.65,-70.991,25.974,-71.037,26.499,-71.02,26.754,-70.967,26.918,-70.954,27.207,-70.911,27.509,-70.813,27.698,-70.772,28.386,-70.682,28.912,-70.583,29.464,-70.406,30.003,-70.3,30.834,-70.246,31.063,-70.225,31.379,-70.226,32.16,-70.1,32.457,-70.026,32.621,-70.001,32.81,-69.909,32.912,-69.734,32.989,-69.624,32.976,-69.517,32.903,-69.379,32.738,-69.255,32.568,-69.074,32.642,-68.869,32.776,-68.783,33.121,-68.689,33.466,-68.671,33.854,-68.683,34.193,-68.702,34.219,-68.791,34.074,-68.885,33.885,-68.979,33.772,-69.02,33.814,-69.099,34.059,-69.111,34.596,-69.095,34.75,-69.168,35.131,-69.487,35.225,-69.637,35.357,-69.681,35.568,-69.66,36.018,-69.662,36.331,-69.639,36.586,-69.638,36.719,-69.652,36.856,-69.726,37.115,-69.81,37.375,-69.748,37.56,-69.718,37.787,-69.726,38.144,-69.824,38.499,-70.056,38.886,-70.172,38.912,-70.098,38.859,-70.006,39.019,-69.924,39.211,-69.786,39.487,-69.608,39.705,-69.426,39.762,-69.173,39.864,-68.967,40.042,-68.868,40.216,-68.805,40.484,-68.739,40.817,-68.724,41.133,-68.575,41.356,-68.515,41.825,-68.433,42.409,-68.352,42.82,-68.123,42.961,-68.095,43.171,-68.06,43.554,-68.046,44.178,-67.972,44.373,-67.961,44.7,-67.904,44.99,-67.769,45.197,-67.731,45.569,-67.736,45.888,-67.66,46.154,-67.657,46.399,-67.618,46.437,-67.533,46.32,-67.477,46.317,-67.402,46.454,-67.304,46.56,-67.268,46.884,-67.275,47.154,-67.357,47.352,-67.362,47.403,-67.409,47.231,-67.468,47.117,-67.573,47.314,-67.665,47.49,-67.728,47.704,-67.716,47.959,-67.66,48.21,-67.699,48.322,-67.785,48.322,-67.917,48.375,-67.988,48.551,-67.926,48.648,-67.794,48.62,-67.625,48.63,-67.521,49.053,-67.352,49.219,-67.227,48.923,-67.2,48.714,-67.217,48.598,-67.171,48.465,-67.043,48.83,-66.938,49.247,-66.942,49.489,-67.031,50.006,-67.175,50.293,-67.172,50.553,-67.194,50.606,-67.15,50.509,-66.939,50.521,-66.82,50.306,-66.753,50.244,-66.603,50.332,-66.445,50.588,-66.356,50.937,-66.315,51.688,-66.072,51.885,-66.02,52.378,-65.969,52.955,-65.946,53.672,-65.859,54.948,-65.916,55.29,-65.954,55.504,-66.003,55.71,-66.08,55.974,-66.209,56.362,-66.373,56.859,-66.423,57,-66.475,57.185,-66.613,56.987,-66.704,56.824,-66.713,56.51,-66.659,56.295,-66.603,56.146,-66.626,56.292,-66.721,56.453,-66.78,56.48,-66.859,56.391,-66.974,55.803,-67.199,56.155,-67.265,56.366,-67.212,56.562,-67.116,56.76,-67.073,56.892,-67.056,57.361,-67.053,57.627,-67.014,57.828,-67.041,58.027,-67.103,58.317,-67.163,58.737,-67.23,59.251,-67.485,59.65,-67.459,59.868,-67.403,60.482,-67.385,61.012,-67.5,61.309,-67.54,62.174,-67.575,62.688,-67.648,63.018,-67.562,63.238,-67.527,63.699,-67.508,63.931,-67.526,64.574,-67.62,65.708,-67.716,66.488,-67.766,67.175,-67.768,67.502,-67.81,68.099,-67.854,68.328,-67.89,68.9,-67.862,69.167,-67.825,69.416,-67.743,69.559,-67.763,69.656,-67.865,69.603,-68.041,69.704,-68.161,69.789,-68.279,69.907,-68.379,69.982,-68.464,69.928,-68.535,69.762,-68.599,69.534,-68.737,69.547,-68.857,69.646,-68.932,69.531,-69.024,69.615,-69.154,69.629,-69.232,69.549,-69.294,69.372,-69.331,69.065,-69.337,68.906,-69.373,68.88,-69.47,68.959,-69.54,69.136,-69.578,69.188,-69.655,69.162,-69.77,69.083,-69.867,68.921,-69.912,68.744,-69.921,68.415,-69.902,68.178,-69.837,68.027,-69.894,67.917,-69.953,67.575,-70.088,67.417,-70.177,67.268,-70.273,67.659,-70.326,67.941,-70.423,68.559,-70.412,68.757,-70.37,69.021,-70.325,69.162,-70.334,69.25,-70.431,69.197,-70.585,69.188,-70.705,68.873,-71.035,68.768,-71.091,68.624,-71.181,68.448,-71.252,68.311,-71.287,68.037,-71.391,67.873,-71.58,67.694,-71.737,67.432,-72.003,67.281,-72.291,67.215,-72.461,67.113,-72.641,66.892,-72.949,66.746,-73,66.498,-73.125,66.569,-73.209,66.765,-73.217,67.003,-73.236,67.322,-73.3,67.749,-73.168,67.971,-73.086,68.016,-72.918,67.971,-72.751,68.107,-72.651,68.42,-72.515,69.157,-72.419,69.309,-72.409,69.555,-72.375,69.77,-72.254,69.962,-72.133,70.294,-72.055,70.573,-71.931,70.616,-71.842,70.732,-71.822,71.079,-71.737,71.168,-71.672,71.277,-71.624,71.349,-71.514,71.379,-71.309,71.465,-71.155,71.634,-70.949,71.771,-70.801,71.905,-70.707,72.263,-70.657,72.418,-70.599,72.622,-70.472,72.76,-70.396,72.744,-70.239,72.822,-70.096,73.041,-70.01,73.325,-69.849,73.676,-69.826,73.942,-69.743,74.227,-69.8,74.571,-69.88,75.148,-69.855,75.424,-69.893,75.636,-69.849,75.821,-69.725,75.891,-69.576,76.112,-69.487,76.36,-69.49,76.77,-69.34,77.192,-69.206,77.541,-69.174,77.817,-69.069,78.015,-68.892,78.229,-68.756,78.489,-68.626,78.563,-68.394,78.726,-68.278,79.035,-68.175,79.288,-68.119,80.363,-67.947,81.187,-67.831,82.017,-67.69,82.273,-67.692,82.607,-67.613,83.158,-67.611,83.304,-67.603,83.494,-67.441,83.904,-67.292,84.161,-67.244,84.485,-67.114,84.748,-67.102,85.117,-67.126,85.429,-67.161,85.711,-67.161,86.118,-67.055,86.75,-67.037,86.947,-66.986,87.085,-66.94,87.98,-66.788,88.314,-66.817,88.789,-66.792,89.077,-66.799,89.352,-66.818,89.698,-66.823,90.293,-66.77,90.547,-66.734,91.022,-66.603,91.546,-66.572,91.777,-66.537,92.073,-66.508,92.312,-66.559,92.486,-66.604,92.592,-66.614,92.731,-66.624,93.075,-66.571,93.358,-66.585,93.722,-66.643,93.964,-66.69,94.089,-66.689,94.313,-66.647,94.587,-66.544,94.84,-66.501,95.084,-66.527,95.248,-66.571,95.541,-66.631,95.991,-66.621,96.424,-66.6,96.789,-66.551,97.101,-66.499,97.388,-66.579,97.72,-66.607,98.258,-66.467,98.462,-66.499,98.603,-66.535,98.72,-66.553,98.859,-66.671,99.37,-66.648,99.824,-66.549,100.212,-66.474,100.591,-66.425,100.889,-66.358,101.327,-66.1,101.321,-66.021,101.381,-65.973,101.474,-65.951,102.174,-65.954,102.392,-65.933,102.674,-65.865,103.167,-65.917,103.639,-65.999,103.763,-65.99,103.951,-65.988,104.289,-66.039,104.667,-66.137,105,-66.164,106.387,-66.411,107.171,-66.47,107.566,-66.552,107.667,-66.58,107.785,-66.664,107.992,-66.672,108.158,-66.639,108.376,-66.766,108.91,-66.862,109.463,-66.909,109.824,-66.834,110.437,-66.621,110.622,-66.524,110.587,-66.312,110.907,-66.077,111.453,-65.961,112.13,-65.9,112.548,-65.848,113.099,-65.8,113.368,-65.849,113.502,-65.886,113.71,-65.93,113.954,-66.06,114.337,-66.36,114.619,-66.468,114.87,-66.477,115.082,-66.493,115.31,-66.561,115.635,-66.771,115.442,-66.958,115.274,-67.028,114.571,-67.108,114.26,-67.172,113.991,-67.212,113.913,-67.368,114.027,-67.441,114.319,-67.406,114.658,-67.388,114.926,-67.357,115.172,-67.308,115.384,-67.238,115.885,-67.202,116.215,-67.143,116.509,-67.108,116.713,-67.047,116.924,-67.055,117.132,-67.114,117.298,-67.109,117.745,-67.129,117.952,-67.085,118.139,-67.082,118.326,-67.115,118.519,-67.161,118.714,-67.172,118.965,-67.145,119.318,-67.071,119.768,-66.992,120.187,-66.966,120.289,-66.967,120.375,-66.984,119.954,-67.076,119.281,-67.199,118.922,-67.32,119.133,-67.371,120.4,-67.236,120.979,-67.136,121.488,-67.091,121.613,-67.057,122.033,-66.902,122.183,-66.859,122.633,-66.805,123.222,-66.745,123.667,-66.677,123.969,-66.608,124.196,-66.601,124.371,-66.652,124.598,-66.708,124.822,-66.695,125.095,-66.641,125.286,-66.516,125.398,-66.424,125.603,-66.393,125.866,-66.364,126.077,-66.396,126.424,-66.462,126.665,-66.498,126.874,-66.759,127.365,-66.99,127.541,-67.051,127.968,-67.028,128.431,-67.119,128.628,-67.107,128.816,-67.08,128.982,-67.098,129.237,-67.042,129.5,-66.753,129.741,-66.469,129.976,-66.345,130.121,-66.292,130.301,-66.268,130.579,-66.209,130.952,-66.191,131.232,-66.216,131.831,-66.236,132.32,-66.165,132.874,-66.178,133.148,-66.095,133.445,-66.081,133.843,-66.154,133.958,-66.204,134.179,-66.277,134.232,-66.348,134.289,-66.477,134.398,-66.48,134.77,-66.353,134.971,-66.33,135.352,-66.127,135.555,-66.18,136.009,-66.267,136.194,-66.292,136.553,-66.439,136.74,-66.408,136.889,-66.34,137.336,-66.346,137.754,-66.406,137.926,-66.457,138.14,-66.544,138.271,-66.564,138.376,-66.54,139.242,-66.574,139.613,-66.638,139.9,-66.715,140.902,-66.752,141.286,-66.832,141.517,-66.794,141.973,-66.807,142.159,-66.874,142.327,-66.948,142.688,-67.013,142.888,-67,143.169,-66.949,143.448,-66.877,143.73,-66.877,143.863,-66.939,143.911,-67.091,144.118,-67.088,144.348,-67.018,144.551,-67.036,144.621,-67.141,144.515,-67.283,144.26,-67.479,144.154,-67.644,143.942,-67.794,143.977,-67.865,144.189,-67.9,144.404,-67.794,144.879,-67.721,145.128,-67.626,145.556,-67.591,145.975,-67.624,146.276,-67.751,146.828,-67.965,146.852,-68.041,146.897,-68.12,146.878,-68.191,146.798,-68.274,147.094,-68.369,147.354,-68.384,147.569,-68.375,148.456,-68.467,148.881,-68.431,149.263,-68.431,149.717,-68.418,150.066,-68.42,150.342,-68.436,150.672,-68.403,150.936,-68.358,151.068,-68.385,151.121,-68.623,151.139,-68.764,151.289,-68.817,151.448,-68.764,151.562,-68.694,152.265,-68.726,152.546,-68.73,152.814,-68.768,153.082,-68.857,153.34,-68.818,153.496,-68.764,153.705,-68.729,153.767,-68.641,153.792,-68.493,153.785,-68.35,153.908,-68.323,154.032,-68.35,154.2,-68.418,154.576,-68.634,154.866,-68.774,154.988,-68.841,155.164,-68.895,155.52,-69.024,156.011,-69.078,156.489,-69.183,157.046,-69.176,157.481,-69.309,157.776,-69.205,157.933,-69.181,158.158,-69.209,158.433,-69.299,158.647,-69.32,159.386,-69.468,159.784,-69.522,159.931,-69.631,160.126,-69.734,160.126,-69.84,160.21,-69.975,160.652,-70.081,160.827,-70.182,161.037,-70.317,161.425,-70.827,161.625,-70.916,161.916,-70.907,162.189,-71.04,162.278,-71.022,162.287,-70.969,162.04,-70.625,162.022,-70.44,162.216,-70.334,162.675,-70.305,163.026,-70.501,163.349,-70.621,163.567,-70.642,163.998,-70.637,164.403,-70.51,164.716,-70.557,165.209,-70.571,165.854,-70.645,166.132,-70.633,166.627,-70.664,167.229,-70.771,167.569,-70.81,167.64,-70.854,167.799,-70.925,167.878,-71.013,167.966,-71.092,168.173,-71.183,168.383,-71.197,168.798,-71.275,169.664,-71.511,169.977,-71.581,170.162,-71.63,170.25,-71.569,170.277,-71.444,170.436,-71.419,170.603,-71.604,170.78,-71.745,170.859,-71.869,170.675,-71.969,170.409,-71.948,170.224,-71.948,170.03,-72.116,169.954,-72.403,170.127,-72.398,170.259,-72.371,170.286,-72.477,170.206,-72.565,170.048,-72.601,169.775,-72.534,169.44,-72.487,169.072,-72.469,168.719,-72.384,168.576,-72.379,168.428,-72.383,168.622,-72.473,168.82,-72.552,169.27,-72.621,169.829,-72.729,169.845,-72.795,169.712,-72.877,169.545,-73.05,169.033,-73.2,168.736,-73.091,168.381,-73.066,168.204,-73.13,167.853,-73.122,167.156,-73.147,166.883,-73.011,166.453,-72.936,166.467,-72.997,166.834,-73.224,167.226,-73.276,167.616,-73.337,167.709,-73.394,167.534,-73.447,167.296,-73.44,166.996,-73.544,166.429,-73.527,166.159,-73.534,166.001,-73.577,165.86,-73.593,165.971,-73.631,166.106,-73.735,165.913,-73.823,165.734,-73.867,165.549,-73.846,165.347,-73.879,165.25,-73.782,165.245,-73.571,165.129,-73.383,165.005,-73.375,164.813,-73.397,164.75,-73.559,164.888,-73.838,164.98,-73.926,164.906,-74.003,164.776,-74.029,165.037,-74.263,165.263,-74.426,165.4,-74.479,165.409,-74.559,165.303,-74.594,165.001,-74.563,164.853,-74.578,164.689,-74.568,164.411,-74.533,164.174,-74.523,163.936,-74.567,163.735,-74.564,163.557,-74.417,163.398,-74.382,163.266,-74.426,163.167,-74.602,162.961,-74.656,162.752,-74.736,162.604,-74.823,162.534,-75.167,162.41,-75.238,162.226,-75.235,162.088,-75.262,161.91,-75.234,161.68,-75.218,160.911,-75.335,161.033,-75.396,161.227,-75.386,161.904,-75.404,162.19,-75.467,162.239,-75.622,162.352,-75.687,162.578,-75.758,162.754,-75.793,162.816,-75.846,162.745,-75.952,162.648,-76.049,162.437,-76.155,162.498,-76.208,162.728,-76.225,162.825,-76.464,162.675,-76.569,162.745,-76.658,162.763,-76.746,162.61,-76.829,162.489,-76.869,162.45,-76.956,162.679,-77.007,162.85,-77.024,163.087,-77.032,163.25,-77.126,163.458,-77.269,163.608,-77.388,163.619,-77.582,163.766,-77.7,164.045,-77.775,164.036,-77.853,164.232,-77.877,164.421,-77.883,164.492,-77.954,164.43,-78.042,164.108,-78.147,163.978,-78.224,164.297,-78.236,164.628,-78.316,165.051,-78.226,165.274,-78.129,165.418,-78.042,165.524,-78.064,165.534,-78.154,165.663,-78.306,166.209,-78.452,166.51,-78.497,166.801,-78.522,167.058,-78.518,167.13,-78.606,167.049,-78.686,166.85,-78.68,166.525,-78.695,166.287,-78.628,166.117,-78.571,164.635,-78.603,164.301,-78.63,163.902,-78.717,163.503,-78.759,162.895,-78.845,162.639,-78.898,161.975,-78.694,161.757,-78.545,161.669,-78.536,161.51,-78.571,161.502,-78.677,161.813,-78.907,161.951,-78.968,161.951,-79.03,161.864,-79.061,161.737,-79.06,161.546,-79.015,161.191,-78.979,160.874,-79.05,160.483,-79.201,160.567,-79.302,160.67,-79.359,160.646,-79.427,160.209,-79.554,160.068,-79.561,159.976,-79.586,160.084,-79.633,160.323,-79.636,160.346,-79.692,160.265,-79.737,159.872,-79.79,159.896,-79.859,160.111,-79.892,160.387,-79.879,160.558,-79.93,160.559,-80.011,160.382,-80.054,160.179,-80.088,158.767,-80.293,158.56,-80.349,158.574,-80.423,159.065,-80.443,160.542,-80.425,160.637,-80.45,160.603,-80.508,160.521,-80.583,160.601,-80.637,160.823,-80.674,160.83,-80.73,160.72,-80.753,160.503,-80.779,160.26,-80.787,160.276,-80.847,160.607,-80.901,160.717,-80.907,160.728,-81.113,160.717,-81.2,160.54,-81.242,160.479,-81.27,160.47,-81.341,160.908,-81.39,161.582,-81.61,161.73,-81.61,161.996,-81.653,162.425,-81.765,162.577,-81.832,162.821,-81.866,162.854,-81.921,163.004,-81.969,163.602,-82.121,163.68,-82.187,162.427,-82.314,161.167,-82.408,161.283,-82.49,162.644,-82.482,163.012,-82.535,163.175,-82.519,163.269,-82.463,164.001,-82.397,164.747,-82.354,164.98,-82.385,165.982,-82.63,166.446,-82.722,166.742,-82.757,166.957,-82.765,167.116,-82.801,167.271,-82.879,167.233,-82.952,167.297,-82.986,167.404,-82.999,167.602,-83.047,167.828,-83.031,168.092,-82.975,168.276,-82.987,168.607,-83.065,168.482,-83.127,168.408,-83.155,168.32,-83.211,168.24,-83.23,167.973,-83.243,167.825,-83.243,167.674,-83.231,167.658,-83.272,167.843,-83.316,168.11,-83.362,169.838,-83.399,170.332,-83.479,170.817,-83.436,171.036,-83.448,171.221,-83.475,171.289,-83.555,171.537,-83.581,171.917,-83.644,172.45,-83.675,172.874,-83.673,173.397,-83.759,173.662,-83.761,173.822,-83.81,175.011,-83.839,175.187,-83.877,175.323,-83.94,175.606,-83.968,175.911,-83.973,177.581,-84.075,178.209,-84.13,178.353,-84.127,178.496,-84.136,178.944,-84.181,179.403,-84.206,179.62,-84.268,180,-84.352]},{"b":[178.628,70.822,180,71.538],"l":[180,71.538,179.886,71.523,179.716,71.466,179.548,71.448,179.235,71.325,178.891,71.231,178.829,71.178,178.684,71.106,178.628,71.047,178.648,71.001,178.793,70.822,178.862,70.826,179.153,70.88,179.648,70.899,179.881,70.976,180,70.993]},{"b":[46.707,36.615,54.723,47.11],"l":[54.71,40.891,54.685,40.873,54.657,40.858,54.547,40.832,54.374,40.871,54.319,40.835,54.336,40.765,54.377,40.693,54.33,40.689,54.283,40.694,54.193,40.72,54.089,40.707,53.87,40.649,53.764,40.666,53.694,40.746,53.615,40.819,53.52,40.831,53.424,40.793,53.333,40.783,53.192,40.809,53.145,40.825,53.06,40.89,52.998,40.96,52.943,41.038,52.889,40.863,52.85,40.686,52.785,40.547,52.734,40.399,52.744,40.22,52.805,40.054,52.898,39.913,52.965,39.834,53.036,39.774,52.952,39.895,52.988,39.988,53.139,39.979,53.289,39.958,53.404,39.96,53.454,39.941,53.487,39.909,53.458,39.831,53.45,39.749,53.472,39.669,53.533,39.642,53.582,39.607,53.603,39.547,53.497,39.533,53.39,39.536,53.305,39.557,53.236,39.609,53.125,39.432,53.124,39.347,53.157,39.265,53.203,39.317,53.267,39.343,53.336,39.341,53.475,39.306,53.539,39.274,53.618,39.216,53.705,39.21,53.71,39.153,53.724,39.103,53.815,39.018,53.869,38.949,53.885,38.864,53.874,38.742,53.852,38.622,53.84,38.515,53.852,38.406,53.854,38.286,53.825,38.047,53.824,37.928,53.848,37.67,53.898,37.414,53.914,37.344,53.952,37.182,54.017,36.952,54.024,36.901,54.016,36.85,53.97,36.818,53.906,36.813,53.769,36.818,53.679,36.853,53.827,36.881,53.915,36.93,53.768,36.93,53.374,36.869,52.19,36.622,51.762,36.615,51.119,36.743,50.927,36.81,50.533,37.014,50.338,37.149,50.214,37.34,50.176,37.381,50.13,37.407,49.981,37.445,49.727,37.481,49.47,37.497,49.372,37.52,49.171,37.601,49.081,37.668,49.015,37.776,48.96,37.89,48.925,38.015,48.901,38.144,48.871,38.393,48.869,38.435,48.851,38.815,48.854,38.839,48.926,38.962,48.962,39.079,49.013,39.134,49.111,39.085,49.109,39.029,49.121,39.004,49.165,39.03,49.2,39.073,49.269,39.285,49.321,39.329,49.363,39.35,49.367,39.398,49.328,39.501,49.324,39.608,49.415,39.84,49.477,40.087,49.551,40.194,49.669,40.249,49.792,40.288,49.919,40.316,50.143,40.323,50.366,40.279,50.307,40.412,50.248,40.462,50.183,40.505,50.119,40.535,49.991,40.577,49.852,40.577,49.776,40.584,49.718,40.608,49.556,40.716,49.457,40.8,49.226,41.026,49.175,41.116,49.143,41.218,49.107,41.302,49.051,41.374,48.824,41.63,48.665,41.787,48.573,41.844,48.477,41.905,48.426,41.924,48.384,41.953,48.303,42.08,48.229,42.181,48.08,42.354,47.822,42.613,47.77,42.645,47.728,42.681,47.709,42.811,47.635,42.903,47.529,42.967,47.489,43,47.463,43.035,47.513,43.219,47.512,43.271,47.49,43.382,47.509,43.51,47.568,43.685,47.628,43.806,47.646,43.885,47.563,43.835,47.463,43.555,47.429,43.78,47.362,43.993,47.307,44.103,47.23,44.192,47.123,44.262,47.024,44.343,46.916,44.387,46.753,44.421,46.721,44.452,46.707,44.503,46.716,44.561,46.755,44.657,46.841,44.718,46.957,44.783,46.984,44.826,47.003,44.876,47.039,44.838,47.084,44.817,47.115,44.906,47.162,44.97,47.221,45.024,47.296,45.149,47.351,45.218,47.391,45.295,47.413,45.421,47.454,45.433,47.489,45.455,47.515,45.491,47.529,45.53,47.524,45.602,47.463,45.68,47.479,45.688,47.508,45.674,47.574,45.634,47.633,45.584,47.65,45.657,47.701,45.686,47.764,45.666,47.83,45.663,48.053,45.721,48.159,45.737,48.258,45.778,48.487,45.935,48.537,45.942,48.589,45.935,48.637,45.906,48.69,45.889,48.73,45.897,48.75,45.921,48.703,45.976,48.687,46.029,48.684,46.086,48.743,46.101,48.81,46.1,49.08,46.189,49.111,46.228,49.125,46.282,49.246,46.292,49.232,46.337,49.206,46.386,49.286,46.437,49.362,46.41,49.344,46.486,49.347,46.519,49.437,46.537,49.584,46.545,49.632,46.568,49.761,46.571,49.886,46.596,50,46.634,50.102,46.696,50.306,46.795,50.419,46.879,50.472,46.883,50.528,46.873,50.583,46.882,50.68,46.939,50.733,46.952,50.92,47.041,51.178,47.11,51.291,47.097,51.615,47.03,51.65,47.018,51.745,46.934,51.945,46.895,52.011,46.902,52.086,46.84,52.138,46.829,52.189,46.84,52.34,46.895,52.385,46.922,52.42,46.964,52.483,46.991,52.678,46.957,52.916,46.954,53.035,46.893,53.069,46.856,53.138,46.742,53.17,46.669,53.132,46.608,53.079,46.547,53.064,46.475,53.109,46.414,53.135,46.192,53.042,45.968,52.888,45.78,52.774,45.573,52.838,45.497,53.086,45.407,53.2,45.332,53.079,45.308,52.911,45.32,52.772,45.344,52.531,45.399,52.427,45.405,52.049,45.388,51.733,45.399,51.54,45.343,51.416,45.358,51.333,45.28,51.294,45.23,51.25,45.122,51.154,45.04,51.04,44.98,51.009,44.922,51.021,44.854,51.058,44.812,51.218,44.709,51.31,44.619,51.366,44.6,51.431,44.602,51.494,44.578,51.544,44.531,51.377,44.541,51.311,44.532,51.177,44.501,51.111,44.508,51.049,44.53,50.86,44.629,50.652,44.633,50.409,44.624,50.297,44.582,50.265,44.527,50.253,44.462,50.253,44.406,50.276,44.355,50.331,44.325,50.472,44.295,50.685,44.265,50.783,44.228,50.831,44.193,50.94,43.959,51.065,43.75,51.14,43.649,51.239,43.577,51.274,43.533,51.302,43.482,51.314,43.421,51.313,43.356,51.292,43.231,51.295,43.174,51.348,43.167,51.514,43.171,51.616,43.158,51.7,43.104,51.785,43.004,51.811,42.954,51.844,42.91,51.898,42.87,51.961,42.851,52.019,42.861,52.076,42.88,52.184,42.869,52.273,42.8,52.324,42.816,52.434,42.824,52.494,42.82,52.55,42.805,52.597,42.76,52.638,42.556,52.618,42.428,52.573,42.331,52.517,42.237,52.462,42.101,52.459,42.048,52.468,41.886,52.494,41.78,52.609,41.529,52.747,41.365,52.826,41.231,52.85,41.2,52.862,41.21,52.83,41.342,52.882,41.614,52.883,41.653,52.815,41.712,52.905,41.896,52.97,41.976,53.108,42.07,53.164,42.094,53.285,42.082,53.496,42.12,53.625,42.136,53.752,42.129,53.805,42.118,53.846,42.091,53.954,41.868,53.995,41.773,54.04,41.643,54.095,41.519,54.181,41.432,54.285,41.364,54.592,41.194,54.671,41.122,54.704,41.071,54.718,41.013,54.723,40.951,54.71,40.891]},{"b":[-17.536,-34.786,180,77.73],"l":[180,68.983,179.868,69.013,179.273,69.26,178.951,69.296,178.925,69.326,178.907,69.362,178.848,69.387,178.443,69.453,177.934,69.496,177.395,69.612,176.924,69.646,176.41,69.769,176.108,69.86,175.921,69.895,175.751,69.904,175.296,69.86,174.786,69.856,174.319,69.882,173.948,69.874,173.733,69.891,173.439,69.947,173.353,69.924,173.277,69.824,173.056,69.865,172.869,69.92,172.56,69.968,171.971,70,171.247,70.076,170.868,70.096,170.487,70.108,170.525,69.938,170.503,69.857,170.36,69.751,170.201,69.683,170.161,69.627,170.582,69.583,170.714,69.388,170.884,69.264,170.997,69.135,170.995,69.045,170.538,68.825,170.066,68.799,169.61,68.786,169.415,68.92,169.311,69.08,168.946,69.163,168.588,69.228,168.423,69.24,168.303,69.271,168.23,69.447,168.15,69.577,168.048,69.626,167.95,69.699,167.857,69.728,167.628,69.74,167.073,69.554,166.884,69.5,166.82,69.5,165.98,69.546,165.761,69.584,164.513,69.609,164.16,69.719,163.946,69.735,163.705,69.702,163.498,69.693,163.201,69.715,162.945,69.683,162.376,69.649,162.166,69.612,161.945,69.545,161.537,69.38,161.48,69.3,161.48,69.202,161.566,69.064,161.566,68.905,161.495,68.85,161.365,68.823,161.23,68.654,161.104,68.563,160.856,68.538,160.997,68.608,161.129,68.654,161.341,68.905,161.31,68.982,161.141,69.039,161.036,69.098,160.982,69.334,160.929,69.459,160.911,69.606,160.739,69.655,160.119,69.73,159.833,69.785,159.729,69.87,159.839,69.99,159.831,70.081,159.89,70.159,159.983,70.221,160.006,70.31,159.959,70.424,159.912,70.506,159.805,70.605,159.728,70.65,159.351,70.791,158.702,70.935,158.037,71.039,157.447,71.075,156.685,71.094,155.895,71.096,155.596,71.039,155.029,71.034,154.414,70.974,153.794,70.88,153.461,70.879,152.798,70.836,152.509,70.834,151.762,70.982,152,71.002,152.093,71.023,151.76,71.218,151.582,71.287,151.145,71.374,150.968,71.38,150.822,71.363,150.243,71.267,150.098,71.227,150.385,71.339,150.525,71.386,150.668,71.455,150.635,71.499,150.6,71.52,150.061,71.511,150.026,71.521,149.913,71.581,149.857,71.601,149.498,71.664,149.238,71.688,148.968,71.69,148.923,71.715,148.955,71.744,148.965,71.763,149.049,71.796,149.28,71.826,149.881,71.843,150.017,71.896,149.998,71.95,149.963,71.992,149.766,72.091,149.502,72.164,148.965,72.252,148.402,72.312,147.434,72.341,147.262,72.328,147.127,72.292,146.895,72.198,146.368,71.922,146.073,71.808,145.805,71.746,145.189,71.696,145.076,71.707,144.99,71.753,145.018,71.794,145.074,71.831,145.078,71.855,145.047,71.901,145.064,71.926,145.126,71.927,145.271,71.895,145.407,71.89,145.757,71.941,145.757,72.021,145.664,72.067,145.71,72.178,145.71,72.206,145.759,72.226,145.799,72.222,146.051,72.142,146.137,72.146,146.23,72.138,146.006,71.945,146.113,71.945,146.402,72.035,146.599,72.124,146.807,72.237,146.832,72.295,146.594,72.302,145.039,72.26,144.471,72.175,144.295,72.193,144.169,72.259,144.361,72.265,144.588,72.306,144.776,72.382,144.897,72.396,145.213,72.393,145.467,72.362,146.235,72.35,146.253,72.442,146.083,72.471,145.714,72.497,145.486,72.542,145.199,72.57,144.569,72.61,144.304,72.643,143.681,72.673,143.516,72.698,142.061,72.721,141.518,72.789,141.31,72.858,140.808,72.891,140.708,72.89,140.676,72.872,140.652,72.843,140.973,72.717,140.983,72.63,141.079,72.587,140.705,72.519,140.451,72.493,139.601,72.496,139.474,72.467,139.141,72.33,139.145,72.264,139.176,72.163,139.43,72.163,139.505,72.208,139.617,72.226,140.134,72.21,140.188,72.191,140.014,72.162,139.847,72.149,139.64,71.998,139.359,71.951,139.552,71.927,139.723,71.885,139.695,71.7,139.939,71.558,139.984,71.492,139.632,71.489,139.32,71.445,139.209,71.445,139.005,71.556,138.78,71.629,138.67,71.635,138.525,71.563,138.318,71.603,138.234,71.596,138.118,71.566,138.048,71.526,137.996,71.464,137.927,71.43,137.918,71.384,138.022,71.363,138.097,71.359,138.314,71.326,138.091,71.307,138.033,71.286,138.013,71.261,137.844,71.227,137.902,71.194,137.974,71.169,137.992,71.143,137.94,71.133,137.798,71.164,137.651,71.208,137.417,71.299,137.315,71.359,137.116,71.416,136.406,71.571,136.09,71.62,135.885,71.631,135.559,71.61,135.359,71.544,135.022,71.515,134.814,71.461,134.703,71.387,134.103,71.379,133.689,71.434,133.426,71.491,133.131,71.607,132.839,71.755,132.804,71.768,132.769,71.799,132.716,71.871,132.654,71.926,132.562,71.895,132.326,71.726,132.228,71.643,132.099,71.484,132.004,71.35,131.991,71.293,132.035,71.244,131.906,71.203,131.769,71.101,131.562,70.901,131.432,70.828,131.268,70.766,131.157,70.742,131.022,70.746,130.898,70.804,130.832,70.936,130.757,70.962,130.668,70.888,130.537,70.893,130.281,70.947,130.026,71.065,129.762,71.12,129.39,71.405,129.225,71.509,129.134,71.593,128.923,71.602,128.843,71.663,128.949,71.708,129.234,71.745,129.461,71.739,129.292,71.85,129.21,71.917,129.122,71.953,129.154,71.879,129.117,71.825,129.04,71.782,128.911,71.755,128.359,72.088,128.027,72.25,127.841,72.308,127.726,72.413,127.803,72.434,128.197,72.31,128.475,72.246,128.935,72.079,129.283,72.092,129.411,72.166,129.412,72.315,129.281,72.438,129.117,72.486,128.549,72.496,128.418,72.535,128.508,72.547,128.633,72.55,128.815,72.586,129.118,72.677,129.25,72.705,129.229,72.776,129.017,72.872,128.674,72.886,128.599,72.895,128.735,72.943,128.854,72.973,129.054,73.045,129.101,73.112,129.059,73.108,128.913,73.111,128.872,73.139,128.889,73.19,128.73,73.233,128.587,73.262,128.258,73.267,128.264,73.301,128.281,73.331,128.142,73.352,128.026,73.391,127.997,73.426,127.955,73.446,127.74,73.482,127.031,73.547,126.955,73.528,126.838,73.434,126.553,73.335,126.335,73.389,126.292,73.394,126.257,73.42,126.309,73.464,126.345,73.506,126.296,73.537,126.254,73.548,126.107,73.517,125.888,73.498,125.794,73.468,125.599,73.447,125.617,73.521,124.796,73.712,124.541,73.751,124.388,73.755,124.019,73.712,123.934,73.689,123.797,73.627,123.491,73.666,123.416,73.637,123.305,73.533,123.323,73.431,123.355,73.402,123.384,73.347,123.501,73.262,123.622,73.193,123.572,73.177,123.522,73.173,123.462,73.144,123.405,73.086,123.301,73.002,123.16,72.955,122.999,72.965,122.615,73.028,122.527,73.017,122.502,72.971,122.731,72.931,122.752,72.906,122.692,72.891,122.538,72.878,122.26,72.881,122.03,72.897,121.886,72.961,121.748,72.97,121.354,72.971,120.997,72.937,120.598,72.981,119.922,72.971,119.75,72.979,119.425,73.064,118.96,73.117,118.43,73.247,118.377,73.367,118.457,73.464,118.754,73.465,118.936,73.481,118.911,73.518,118.871,73.538,118.45,73.59,117.309,73.599,116.496,73.676,115.338,73.703,114.816,73.607,114.061,73.585,113.857,73.533,113.51,73.505,113.539,73.434,113.711,73.379,113.795,73.367,113.886,73.346,113.829,73.327,113.765,73.318,113.639,73.274,113.559,73.233,113.581,73.142,113.543,73.054,113.417,72.932,113.312,72.878,113.216,72.806,113.298,72.739,113.391,72.711,113.63,72.677,113.712,72.654,113.665,72.635,113.312,72.657,113.186,72.73,113.158,72.769,113.128,72.831,113.247,72.897,113.369,72.942,113.475,73.048,113.488,73.145,113.491,73.346,113.277,73.392,113.157,73.46,113.364,73.583,113.416,73.648,113.327,73.707,113.182,73.837,113.033,73.914,112.935,73.946,112.836,73.962,112.94,73.836,112.856,73.771,112.795,73.746,112.4,73.711,112.147,73.709,111.804,73.745,111.4,73.828,111.3,73.885,111.228,73.969,111.46,74.005,111.551,74.029,111.341,74.047,111.131,74.053,111.056,73.939,110.92,73.948,110.261,74.017,110.084,73.994,109.869,73.931,109.774,73.881,109.666,73.8,109.707,73.744,109.753,73.723,110.091,73.709,110.388,73.726,110.722,73.78,110.799,73.76,110.868,73.731,110.773,73.689,110.429,73.629,109.855,73.472,109.637,73.454,109.331,73.487,109.166,73.4,109.09,73.378,108.575,73.319,108.351,73.31,108.285,73.266,108.151,73.258,108.001,73.236,107.75,73.173,107.369,73.163,107.109,73.177,106.478,73.139,106.315,73.106,106.209,73.061,106.16,73.002,106.067,72.95,105.708,72.837,105.403,72.79,105.144,72.777,105.393,72.841,105.677,72.959,106.189,73.308,106.679,73.331,106.794,73.377,107.167,73.589,107.271,73.621,107.765,73.625,108.2,73.694,109.075,74.032,109.511,74.089,109.81,74.169,109.864,74.209,109.911,74.261,109.866,74.293,109.84,74.322,110.226,74.379,110.374,74.466,110.893,74.548,111.299,74.658,111.868,74.74,112.192,74.853,112.925,75.015,113.614,75.293,113.726,75.451,113.559,75.502,113.356,75.534,113.243,75.611,113.162,75.621,112.956,75.572,112.73,75.738,112.453,75.83,112.466,75.844,112.497,75.85,112.629,75.835,113.126,75.699,113.392,75.678,113.469,75.657,113.517,75.622,113.486,75.564,113.568,75.568,113.62,75.593,113.749,75.705,113.871,75.856,113.857,75.921,113.564,75.892,113.428,76.112,113.366,76.179,113.273,76.252,113.086,76.258,112.988,76.24,113.066,76.215,113.15,76.175,113.094,76.133,113.047,76.114,112.819,76.059,112.656,76.054,112.722,76.077,112.798,76.13,112.743,76.187,112.684,76.219,112.62,76.384,112.413,76.408,112.297,76.435,112.143,76.424,111.943,76.38,112.017,76.421,112.094,76.48,111.939,76.553,111.786,76.604,111.601,76.622,111.392,76.687,111.115,76.723,110.471,76.758,109.981,76.712,109.369,76.749,108.638,76.72,108.352,76.72,108.182,76.738,108.028,76.718,107.95,76.661,107.902,76.57,107.722,76.522,107.624,76.51,107.158,76.524,106.825,76.48,106.683,76.515,106.414,76.512,106.385,76.589,106.546,76.586,106.639,76.573,106.941,76.73,107.19,76.822,107.43,76.927,107.279,76.991,106.942,77.034,106.784,77.032,106.705,77.014,106.339,77.048,106.145,77.045,105.822,76.998,105.712,77.001,105.646,77.101,105.32,77.092,104.202,77.102,104.324,77.133,104.912,77.175,105.385,77.238,105.734,77.352,106.06,77.391,105.983,77.448,105.895,77.489,105.71,77.525,105.309,77.549,104.965,77.595,104.814,77.652,104.185,77.73,104.015,77.73,103.561,77.632,103.331,77.641,103.131,77.626,102.61,77.509,101.518,77.198,101.293,77.102,101.186,77.029,100.99,76.99,100.906,76.901,100.92,76.823,101.008,76.781,101.099,76.704,101.006,76.615,100.928,76.557,101.003,76.531,101.213,76.536,101.684,76.485,101.598,76.439,101.311,76.479,101.061,76.477,100.844,76.525,100.322,76.479,99.936,76.49,99.576,76.471,98.869,76.51,98.806,76.481,98.97,76.431,99.094,76.384,99.461,76.275,99.617,76.24,99.825,76.136,99.851,75.93,99.738,75.881,99.609,75.811,99.541,75.799,99.442,75.803,99.602,75.852,99.689,75.956,99.77,76.029,99.663,76.078,99.616,76.082,99.563,76.109,99.187,76.178,98.985,76.208,98.771,76.224,98.662,76.243,98.342,76.181,98.195,76.166,98.02,76.134,97.918,76.089,97.67,76.078,97.638,76.029,97.499,75.98,97.351,76.033,97.205,76.019,96.879,75.931,96.497,75.891,96.538,75.922,96.601,75.99,96.509,76.006,95.935,75.926,95.744,75.872,95.653,75.892,95.986,76.01,96.075,76.082,95.92,76.113,95.579,76.137,95.359,76.14,95.038,76.114,94.576,76.152,94.507,76.108,94.388,76.103,94.102,76.124,93.843,76.101,93.648,76.054,93.36,76.101,93.259,76.099,93.105,76.026,92.972,76.075,92.859,75.979,92.89,75.91,92.987,75.903,93.069,75.913,93.116,75.945,93.178,75.959,93.406,75.901,93.475,75.933,93.574,75.956,93.687,75.922,94.156,75.959,94.075,75.913,93.55,75.854,92.603,75.779,92.408,75.75,91.845,75.724,91.479,75.65,91.005,75.65,90.185,75.591,89.595,75.458,89.31,75.47,88.733,75.369,88.504,75.29,87.671,75.13,87.171,75.192,87.006,75.17,86.922,75.113,86.939,75.068,87.141,75.072,87.287,75.053,87.468,75.013,87.419,74.941,87.042,74.779,86.863,74.718,86.651,74.682,86.201,74.816,86.12,74.757,86.059,74.728,85.881,74.74,85.791,74.645,86.116,74.629,86.426,74.585,86.7,74.522,86.894,74.45,87.106,74.404,87.23,74.364,86.898,74.325,86.665,74.414,86.538,74.444,86.396,74.45,86.183,74.423,86.001,74.316,86.178,74.279,86.571,74.244,86.697,74.195,87.21,73.879,87.338,73.846,87.503,73.832,87.571,73.811,87.37,73.756,87.294,73.705,87.12,73.615,86.376,73.569,86.155,73.535,86.092,73.519,85.999,73.486,85.939,73.456,85.91,73.39,85.971,73.347,86.122,73.307,86.715,73.126,86.677,73.107,86.514,73.14,86.308,73.196,86.098,73.273,85.818,73.327,85.802,73.372,85.793,73.438,85.8,73.459,85.827,73.493,86.094,73.578,86.366,73.62,86.698,73.717,87.029,73.824,86.961,73.861,86.893,73.887,86.591,73.894,85.979,73.857,85.611,73.822,85.448,73.735,85.201,73.722,85.077,73.72,84.738,73.763,84.417,73.722,83.667,73.686,83.545,73.667,81.817,73.659,81.469,73.64,80.583,73.568,80.562,73.515,80.596,73.474,80.458,73.414,80.398,73.357,80.419,73.29,80.425,73.231,80.455,73.155,80.51,73.086,80.639,73.049,80.757,73.025,80.842,72.949,80.774,72.861,80.675,72.759,80.656,72.712,80.72,72.648,80.798,72.52,80.827,72.488,81.098,72.39,81.283,72.359,81.586,72.352,81.793,72.327,82.094,72.265,82.184,72.238,82.209,72.211,82.281,72.105,82.319,72.072,82.645,71.925,82.755,71.903,83.2,71.875,83.34,71.828,83.534,71.684,83.554,71.65,83.571,71.594,83.55,71.544,83.531,71.514,83.458,71.468,83.266,71.276,83.151,71.104,83.334,70.989,83.579,70.766,83.651,70.672,83.736,70.546,83.7,70.466,83.66,70.418,83.497,70.345,83.293,70.321,83.074,70.277,83.094,70.221,83.132,70.157,83.11,70.11,83.081,70.093,82.961,70.088,82.857,70.105,82.767,70.154,82.682,70.218,82.742,70.286,82.92,70.407,83.03,70.581,83.058,70.695,83.051,70.815,83.01,70.895,82.869,70.955,82.738,70.942,82.592,70.89,82.452,70.69,82.258,70.544,82.231,70.483,82.236,70.43,82.221,70.396,82.182,70.511,82.163,70.598,82.271,70.707,82.336,70.807,82.316,70.879,82.239,70.998,82.254,71.056,82.277,71.093,82.323,71.26,82.493,71.293,82.918,71.42,82.977,71.451,83.106,71.562,83.166,71.602,83.234,71.668,83.107,71.721,82.986,71.749,82.758,71.764,82.547,71.759,82.08,71.707,81.662,71.716,81.511,71.746,80.856,71.97,80.815,72.054,80.763,72.089,80.699,72.098,80.474,72.153,79.954,72.223,79.422,72.381,78.483,72.395,78.225,72.377,77.968,72.329,77.733,72.229,77.625,72.201,77.472,72.192,77.44,72.157,77.411,72.108,77.493,72.072,77.781,72.114,78.016,72.092,78.141,72.045,78.232,71.952,78.187,71.907,77.778,71.836,77.551,71.842,77.061,72.004,76.871,72.033,76.422,72.006,76.124,71.927,76.032,71.91,76.104,71.829,76.216,71.683,76.312,71.595,76.433,71.552,76.871,71.447,77.114,71.409,77.481,71.312,77.707,71.301,77.908,71.324,78.213,71.266,78.387,71.087,78.491,71.025,78.588,70.994,78.724,70.976,78.804,70.974,78.889,70.997,79.084,71.002,79.015,70.95,78.942,70.934,78.526,70.912,78.321,70.93,78.068,70.986,77.59,71.168,76.995,71.181,76.929,71.128,76.742,71.202,76.11,71.219,75.734,71.266,75.332,71.342,75.298,71.378,75.28,71.43,75.417,71.495,75.469,71.534,75.503,71.655,75.247,71.813,75.274,71.959,75.395,71.983,75.55,72.171,75.644,72.232,75.694,72.254,75.741,72.296,75.691,72.35,75.644,72.382,75.591,72.457,75.603,72.512,75.604,72.581,75.475,72.685,75.369,72.797,75.152,72.853,74.942,72.854,74.865,72.838,74.787,72.812,74.897,72.71,75.008,72.619,75.06,72.549,75.097,72.421,75.09,72.263,75.053,72.199,74.992,72.145,74.804,72.077,74.489,71.997,74.311,71.958,73.939,71.915,73.672,71.845,73.086,71.445,73.15,71.385,73.365,71.32,73.507,71.264,73.577,71.217,73.732,71.069,74.311,70.654,74.343,70.579,74.207,70.445,73.937,70.273,73.83,70.176,73.578,69.803,73.56,69.707,73.663,69.617,73.833,69.504,73.891,69.418,73.776,69.198,73.836,69.143,73.977,69.115,74.363,69.145,74.815,69.091,75.054,69.116,75.42,69.239,75.561,69.252,76.001,69.235,76.645,69.117,77.328,68.959,77.466,68.905,77.651,68.903,77.785,68.63,77.907,68.482,77.959,68.377,77.995,68.259,77.868,68.235,77.757,68.222,77.665,68.19,77.536,68.008,77.52,67.91,77.588,67.752,78.161,67.678,78.559,67.639,78.839,67.631,78.888,67.613,78.922,67.589,78.59,67.578,77.986,67.559,77.772,67.57,77.675,67.59,77.579,67.644,77.396,67.699,77.325,67.736,77.174,67.779,77.248,67.941,77.261,68.316,77.238,68.47,77.112,68.596,76.735,68.777,76.606,68.898,76.459,68.978,76.316,68.992,76.108,68.976,75.59,68.901,75.125,68.862,74.58,68.751,74.481,68.659,74.391,68.421,74.511,68.303,74.632,68.218,74.743,68.074,74.778,67.986,74.787,67.898,74.77,67.766,74.676,67.695,74.075,67.414,73.986,67.328,73.883,67.085,73.792,66.995,73.514,66.861,73.342,66.807,72.417,66.561,72.384,66.507,72.322,66.332,72.068,66.253,71.917,66.247,71.566,66.334,71.358,66.359,71.146,66.367,70.339,66.342,69.982,66.401,69.701,66.485,69.412,66.511,69.194,66.579,69.144,66.641,69.091,66.724,69.051,66.766,69.013,66.788,69.079,66.816,69.218,66.829,69.74,66.815,69.877,66.845,69.949,66.83,70.094,66.754,70.283,66.686,70.444,66.697,70.579,66.754,70.631,66.754,70.691,66.745,70.568,66.701,70.443,66.668,70.409,66.648,70.383,66.602,70.561,66.549,70.725,66.519,70.939,66.548,71.066,66.604,71.342,66.687,71.54,66.683,71.551,66.76,71.449,66.879,71.365,66.962,71.668,66.94,71.847,67.008,72.594,67.587,72.949,67.696,73.067,67.767,73.152,67.865,73.173,67.973,73.129,68.091,73.139,68.181,73.266,68.294,73.465,68.431,73.592,68.482,73.573,68.533,73.548,68.575,73.191,68.707,72.812,68.815,72.678,68.875,72.577,68.969,72.527,69.081,72.527,69.154,72.557,69.378,72.616,69.484,72.599,69.793,72.53,70.173,72.469,70.275,72.562,70.346,72.653,70.403,72.7,70.457,72.732,70.823,72.704,70.963,72.581,71.151,72.079,71.307,71.867,71.457,71.884,71.511,71.912,71.548,72.13,71.609,72.279,71.696,72.375,71.822,72.574,72.013,72.624,72.079,72.753,72.343,72.787,72.483,72.812,72.691,72.634,72.744,72.446,72.79,72.101,72.829,71.93,72.82,71.617,72.902,71.5,72.914,70.655,72.89,70.172,72.901,69.888,72.883,69.738,72.885,69.645,72.898,69.659,72.932,69.709,72.956,69.694,72.978,69.612,72.982,69.391,72.956,69.039,72.67,68.83,72.392,68.607,72.013,68.469,71.853,68.269,71.683,67.959,71.548,67.542,71.412,67.274,71.348,66.918,71.282,66.768,71.14,66.64,71.081,66.693,71.042,66.847,71.064,66.759,70.962,66.666,70.901,66.675,70.865,66.702,70.819,66.822,70.797,67.143,70.838,67.212,70.798,67.285,70.739,67.247,70.5,67.157,70.295,67.146,70.22,67.197,70.172,67.239,70.108,67.144,70.031,67.069,70.006,66.926,70.014,66.832,69.842,66.803,69.74,66.804,69.659,66.84,69.609,66.897,69.554,66.934,69.597,66.964,69.656,67.064,69.694,67.624,69.584,67.774,69.53,68.006,69.48,68.073,69.421,68.117,69.236,68.355,69.068,68.543,68.967,68.66,68.927,68.763,68.917,68.924,68.956,69.141,68.951,69.024,68.818,68.829,68.567,68.504,68.348,68.371,68.314,68.157,68.404,67.731,68.514,67.64,68.579,67.149,68.754,67.002,68.874,66.756,68.892,66.416,68.948,66.085,69.036,65.813,69.077,65.736,69.132,65.528,69.173,65.327,69.201,65.032,69.27,64.896,69.248,64.929,69.325,64.592,69.436,64.19,69.535,63.361,69.675,62.631,69.743,61.771,69.763,61.016,69.851,60.909,69.847,60.813,69.821,60.559,69.692,60.276,69.653,60.171,69.591,60.337,69.457,60.665,69.11,60.859,69.146,60.934,68.987,60.815,68.895,60.638,68.787,60.489,68.729,60.16,68.7,59.896,68.706,59.865,68.605,59.941,68.51,59.923,68.471,59.897,68.422,59.859,68.396,59.828,68.38,59.726,68.352,59.604,68.351,59.311,68.4,59.099,68.444,59.112,68.616,59.223,68.691,59.298,68.708,59.371,68.738,59.22,68.85,59.11,68.896,59.06,68.973,59.057,69.006,58.919,69.004,58.354,68.916,58.237,68.834,58.173,68.89,57.444,68.642,57.127,68.554,56.909,68.567,56.62,68.619,56.276,68.624,56.044,68.649,55.925,68.637,55.675,68.576,55.418,68.568,55.151,68.48,54.923,68.374,54.861,68.202,54.718,68.184,54.561,68.273,54.476,68.294,54.394,68.275,54.233,68.266,54.099,68.259,53.968,68.227,53.914,68.231,53.515,68.26,53.403,68.257,53.261,68.267,53.293,68.312,53.343,68.343,53.567,68.367,53.69,68.403,53.829,68.383,53.931,68.436,53.918,68.537,53.759,68.634,53.834,68.709,53.891,68.802,53.929,68.812,53.971,68.844,53.92,68.871,53.798,68.885,53.798,68.907,53.874,68.927,54.376,68.965,54.491,68.992,54.186,69.003,53.802,68.996,53.413,68.913,52.684,68.731,52.344,68.608,52.435,68.61,52.55,68.592,52.648,68.506,52.723,68.484,52.67,68.427,52.475,68.382,52.397,68.352,52.322,68.34,52.259,68.351,52.183,68.374,52.227,68.419,52.285,68.459,52.129,68.532,52.056,68.541,51.995,68.539,51.617,68.476,51.336,68.402,51.079,68.363,50.839,68.35,50.699,68.318,50.414,68.218,50.233,68.175,49.931,68.065,49.155,67.87,48.954,67.854,48.841,67.87,48.754,67.896,48.696,67.874,48.763,67.827,48.878,67.731,48.833,67.681,48.654,67.695,48.279,67.65,47.875,67.584,47.883,67.515,47.908,67.455,47.839,67.356,47.768,67.276,47.709,67.045,47.656,66.976,47.496,66.93,46.691,66.826,46.552,66.819,46.492,66.8,46.449,66.819,46.298,66.843,46.084,66.844,45.986,66.853,45.885,66.891,45.753,66.989,45.562,67.186,45.139,67.285,44.939,67.351,44.902,67.413,44.939,67.477,45.374,67.689,45.529,67.758,46.174,67.818,46.429,67.824,46.69,67.849,46.684,67.97,46.43,68.119,46.158,68.291,45.892,68.48,45.519,68.547,45.078,68.578,44.175,68.542,44.048,68.549,43.472,68.68,43.413,68.682,43.333,68.673,43.358,68.636,43.404,68.609,44.169,68.327,44.205,68.254,44.226,68.154,44.214,68.113,44.232,68.071,44.225,67.996,44.036,67.671,43.856,67.439,43.796,67.33,43.782,67.254,43.855,67.189,44.074,67.167,44.292,67.1,44.404,67.004,44.429,66.938,44.437,66.795,44.489,66.672,44.316,66.482,44.221,66.407,44.097,66.235,44.145,66.113,44.132,66.065,44.104,66.009,44.017,66.05,43.944,66.099,43.844,66.142,43.737,66.158,43.624,66.147,43.542,66.123,43.55,66.173,43.653,66.251,43.603,66.291,43.551,66.321,43.233,66.416,43.006,66.421,42.807,66.411,42.602,66.423,42.451,66.482,42.314,66.515,42.211,66.52,42.084,66.466,41.781,66.259,41.476,66.123,41.076,66.021,40.774,65.988,40.692,65.963,40.513,65.844,40.328,65.752,39.817,65.598,39.781,65.535,39.749,65.448,39.798,65.35,39.896,65.255,40.143,65.063,40.281,64.998,40.375,64.896,40.445,64.779,40.408,64.755,40.204,64.784,40.058,64.771,39.849,64.691,39.833,64.656,39.758,64.577,39.567,64.571,39.054,64.714,38.613,64.787,38.541,64.791,38.442,64.827,38.412,64.857,38.228,64.851,38.116,64.855,38.009,64.879,37.528,65.108,37.141,65.194,37.05,65.196,36.883,65.172,36.786,64.987,36.653,64.935,36.535,64.939,36.528,64.847,36.579,64.791,36.624,64.751,36.769,64.685,37.04,64.489,37.184,64.408,37.29,64.378,37.43,64.374,37.741,64.397,37.844,64.366,37.954,64.32,37.977,64.207,38.062,64.091,38.071,64.026,37.968,63.949,37.635,63.893,37.442,63.813,37.373,63.817,36.975,63.91,36.714,63.945,36.365,64.003,36.302,64.034,36.146,64.189,35.802,64.335,35.647,64.378,35.432,64.347,35.284,64.363,35.035,64.44,34.87,64.56,34.858,64.707,34.905,64.739,34.952,64.756,34.833,64.8,34.827,64.913,34.804,64.986,34.671,65.168,34.536,65.278,34.406,65.396,34.544,65.457,34.616,65.51,34.716,65.664,34.735,65.716,34.777,65.768,34.793,65.816,34.786,65.865,34.692,65.952,34.4,66.128,34.113,66.225,33.567,66.321,33.416,66.316,33.361,66.33,33.477,66.347,33.593,66.385,33.656,66.443,33.518,66.471,33.405,66.484,33.217,66.532,33.183,66.574,33.224,66.604,33.181,66.68,32.929,66.704,32.862,66.721,32.857,66.747,32.686,66.83,32.464,66.916,32.501,67.004,32.341,67.068,32.202,67.113,31.983,67.13,31.895,67.161,32.4,67.153,32.93,67.087,32.885,67.061,32.848,67.022,33.002,66.908,33.15,66.844,33.482,66.765,33.523,66.764,33.595,66.785,33.76,66.751,33.894,66.707,34.146,66.703,34.452,66.651,34.431,66.63,34.396,66.613,34.483,66.55,34.61,66.56,34.825,66.611,35.364,66.429,35.513,66.396,36.373,66.302,36.77,66.294,36.984,66.273,37.295,66.225,37.628,66.13,37.901,66.096,38.398,66.064,38.654,66.069,39.289,66.132,40.103,66.3,40.522,66.447,41.189,66.826,41.276,66.914,41.354,67.121,41.359,67.21,41.262,67.218,41.134,67.267,41.134,67.386,41.061,67.444,40.966,67.713,40.766,67.743,40.657,67.774,40.526,67.79,40.381,67.832,40.207,67.942,40.036,68.015,39.896,68.115,39.809,68.151,39.746,68.162,39.79,68.112,39.823,68.059,39.569,68.072,38.832,68.325,38.706,68.345,38.657,68.322,38.43,68.356,38.358,68.415,37.731,68.692,36.618,69.003,35.858,69.192,35.29,69.275,35.233,69.266,35.176,69.231,35.01,69.221,34.864,69.228,34.353,69.303,34.229,69.313,33.684,69.31,33.627,69.289,33.436,69.13,33.333,69.098,33.141,69.069,33.196,69.117,33.328,69.152,33.413,69.267,33.418,69.315,33.464,69.378,33.454,69.428,33.385,69.444,33.256,69.428,32.979,69.367,32.942,69.383,33.021,69.446,33,69.47,32.884,69.461,32.637,69.489,32.378,69.479,32.331,69.554,32.161,69.597,32.092,69.633,32.177,69.674,32.754,69.606,32.915,69.602,32.995,69.626,33.013,69.671,33.008,69.722,32.942,69.752,32.565,69.806,32.392,69.869,31.985,69.954,31.969,69.914,32.031,69.835,31.998,69.81,31.879,69.832,31.789,69.816,31.666,69.721,31.547,69.697,31.453,69.69,31.05,69.769,30.87,69.783,30.714,69.796,30.595,69.79,30.484,69.795,30.428,69.722,30.397,69.733,30.349,69.835,30.238,69.862,30.18,69.841,30.155,69.746,30.088,69.718,29.99,69.737,29.792,69.728,29.695,69.745,29.636,69.78,29.621,69.818,29.621,69.874,29.647,69.944,29.601,69.977,28.804,70.093,28.781,70.145,29.926,70.096,30.263,70.125,30.469,70.198,30.944,70.274,30.961,70.344,30.926,70.401,30.596,70.524,30.422,70.547,30.213,70.543,30.203,70.562,30.238,70.622,30.065,70.703,29.959,70.694,29.796,70.643,29.738,70.647,29.722,70.669,29.639,70.705,29.398,70.734,29.321,70.761,29.219,70.83,29.102,70.861,28.832,70.864,28.75,70.842,28.609,70.76,28.485,70.619,28.437,70.501,28.31,70.443,28.28,70.403,28.193,70.249,28.166,70.288,28.166,70.36,28.191,70.44,28.203,70.577,28.272,70.668,28.216,70.704,27.999,70.664,27.898,70.678,27.951,70.718,28.272,70.798,28.327,70.825,28.383,70.869,28.392,70.975,28.142,71.043,27.815,71.059,27.733,71.081,27.597,71.091,27.332,70.997,27.235,70.947,27.269,70.91,27.556,70.827,27.546,70.804,27.309,70.804,27.184,70.744,27.147,70.681,27.071,70.608,26.989,70.511,26.666,70.422,26.585,70.41,26.584,70.454,26.601,70.503,26.628,70.551,26.645,70.636,26.558,70.669,26.675,70.741,26.734,70.854,26.661,70.94,26.507,70.913,26.231,70.783,25.988,70.625,25.471,70.341,25.419,70.235,25.212,70.136,25.044,70.109,24.983,70.144,24.994,70.218,25.146,70.324,25.209,70.489,25.274,70.552,25.468,70.672,25.666,70.777,25.781,70.817,25.768,70.853,25.712,70.87,25.65,70.873,25.57,70.901,25.436,70.912,25.376,70.892,25.325,70.849,25.265,70.844,25.171,70.872,25.042,70.929,24.832,70.978,24.765,71.008,24.658,71.001,24.442,70.892,24.263,70.826,24.268,70.773,24.404,70.745,24.42,70.702,24.356,70.695,24.286,70.662,24.038,70.485,23.897,70.479,23.661,70.4,23.379,70.247,23.329,70.207,23.286,70.105,23.31,70.064,23.4,70.02,23.354,69.983,23.258,69.993,23.177,70.029,23.046,70.102,22.983,70.237,22.941,70.305,22.852,70.34,22.685,70.375,22.421,70.338,22.385,70.278,22.322,70.265,22.219,70.309,22.054,70.276,21.996,70.293,21.78,70.23,21.539,70.258,21.356,70.233,21.346,70.208,21.4,70.174,21.608,70.098,21.803,70.066,21.893,70.004,21.975,69.835,21.932,69.815,21.78,69.887,21.59,69.938,21.433,70.013,21.254,70.003,21.163,69.89,21.032,69.887,20.971,69.916,20.84,69.907,20.622,69.914,20.546,69.851,20.533,69.692,20.563,69.633,20.662,69.585,20.743,69.535,20.739,69.521,20.487,69.542,20.198,69.371,20.107,69.341,20.054,69.333,20.044,69.356,20.277,69.536,20.338,69.617,20.333,69.677,20.387,69.868,20.355,69.922,20.324,69.945,20.223,69.927,20.146,69.897,20.069,69.883,19.961,69.825,19.865,69.722,19.737,69.504,19.642,69.424,19.64,69.504,19.696,69.613,19.722,69.782,19.687,69.805,19.197,69.748,19.038,69.66,19.011,69.624,19.007,69.588,18.991,69.561,18.883,69.523,18.767,69.517,18.674,69.52,18.614,69.491,18.624,69.434,18.75,69.378,18.916,69.336,18.859,69.314,18.646,69.322,18.483,69.365,18.379,69.44,18.293,69.475,18.26,69.471,18.188,69.433,18.079,69.325,18.075,69.233,18.117,69.181,18.101,69.156,17.705,69.1,17.546,69.001,17.49,68.879,17.391,68.799,17.131,68.693,16.885,68.685,16.652,68.626,16.58,68.593,16.514,68.533,16.525,68.491,16.585,68.466,17.202,68.459,17.426,68.482,17.48,68.474,17.502,68.461,17.571,68.447,17.553,68.426,17.479,68.426,17.336,68.41,17.094,68.368,16.951,68.355,16.865,68.355,16.619,68.406,16.388,68.39,16.204,68.317,16.175,68.281,16.26,68.145,16.319,68.102,16.392,68.092,16.372,68.062,16.309,68.036,16.259,68.001,16.312,67.881,16.261,67.887,16.121,68.027,16.065,68.2,16.038,68.218,16.008,68.229,15.851,68.182,15.657,68.164,15.487,68.103,15.316,68.069,15.293,68.036,15.357,68.004,15.606,67.988,15.621,67.948,15.507,67.926,15.401,67.92,15.274,67.961,15.134,67.973,15.048,67.956,14.799,67.809,14.821,67.75,14.781,67.675,14.855,67.663,15.041,67.683,15.304,67.765,15.346,67.734,15.284,67.708,15.219,67.655,15.249,67.602,15.354,67.544,15.487,67.515,15.661,67.543,15.692,67.521,15.576,67.444,15.594,67.349,15.553,67.352,15.465,67.451,15.409,67.474,15.289,67.483,15.121,67.555,14.962,67.574,14.755,67.499,14.579,67.386,14.537,67.34,14.448,67.298,14.442,67.271,14.479,67.256,14.582,67.267,14.824,67.268,15.3,67.257,15.435,67.247,15.416,67.202,14.776,67.194,14.601,67.174,14.473,67.143,14.34,67.159,14.206,67.111,14.109,67.119,14.022,67.073,13.88,66.965,13.808,66.961,13.727,66.938,13.652,66.907,13.704,66.852,13.917,66.819,13.959,66.794,13.788,66.782,13.621,66.795,13.52,66.742,13.45,66.716,13.312,66.702,13.211,66.641,13.192,66.537,13.105,66.539,13.068,66.431,13.119,66.231,13.352,66.237,13.416,66.253,13.499,66.252,13.681,66.274,13.973,66.32,14.034,66.298,13.916,66.247,13.76,66.221,13.674,66.18,13.387,66.183,12.784,66.1,12.795,66.069,12.976,66.019,13.033,65.956,12.983,65.942,12.817,65.953,12.689,65.902,12.628,65.806,12.345,65.63,12.273,65.568,12.206,65.486,12.122,65.362,12.134,65.279,12.2,65.245,12.263,65.256,12.334,65.241,12.364,65.193,12.418,65.184,12.512,65.195,12.715,65.266,12.82,65.317,12.916,65.339,12.738,65.214,12.508,65.099,12.307,65.086,12.227,65.145,12.16,65.179,11.489,64.976,11.35,64.906,11.304,64.829,11.297,64.755,11.392,64.773,11.562,64.818,11.633,64.814,11.524,64.744,11.331,64.686,11.225,64.679,11.09,64.615,10.932,64.578,10.834,64.494,10.566,64.418,10.236,64.18,10.01,64.083,9.939,63.982,9.864,63.918,9.708,63.865,9.615,63.795,9.567,63.706,9.595,63.679,9.657,63.697,9.767,63.7,9.832,63.617,9.893,63.576,9.924,63.522,10.055,63.513,10.339,63.571,10.935,63.77,11.047,63.845,10.967,63.902,10.914,63.921,11.075,63.988,11.214,64.031,11.307,64.049,11.429,64.025,11.458,64.003,11.295,63.948,11.176,63.899,11.214,63.878,11.308,63.876,11.348,63.838,11.371,63.805,11.226,63.764,11.118,63.719,10.953,63.698,10.779,63.651,10.725,63.625,10.674,63.558,10.707,63.536,10.76,63.461,10.704,63.464,10.591,63.447,10.34,63.469,10.189,63.455,10.081,63.433,10.021,63.391,9.979,63.395,9.936,63.479,9.892,63.492,9.832,63.524,9.697,63.625,9.602,63.61,9.521,63.586,9.324,63.57,9.156,63.459,9.084,63.463,9.076,63.5,9.158,63.566,9.136,63.594,8.842,63.646,8.674,63.623,8.576,63.601,8.398,63.535,8.361,63.499,8.387,63.445,8.48,63.424,8.594,63.426,8.641,63.392,8.636,63.342,8.58,63.313,8.271,63.287,8.184,63.237,8.158,63.162,8.235,63.082,8.339,63.042,8.609,62.881,8.623,62.846,8.311,62.966,8.211,62.996,8.101,63.091,7.86,63.113,7.736,63.104,7.654,63.109,7.572,63.1,7.389,63.023,7.008,62.958,6.94,62.93,6.928,62.903,6.782,62.79,6.735,62.721,6.78,62.701,7.025,62.729,7.111,62.752,7.242,62.752,7.408,62.712,8.046,62.771,8.096,62.732,7.805,62.721,7.538,62.672,7.518,62.646,7.527,62.61,7.691,62.586,7.653,62.564,7.57,62.548,7.492,62.543,7.284,62.602,6.961,62.627,6.745,62.638,6.618,62.621,6.439,62.61,6.353,62.611,6.273,62.584,6.238,62.52,6.165,62.482,6.118,62.447,6.136,62.407,6.262,62.416,6.457,62.448,6.692,62.468,6.62,62.423,6.58,62.407,6.209,62.353,6.083,62.35,6.026,62.376,5.98,62.407,5.908,62.416,5.796,62.385,5.718,62.379,5.533,62.311,5.484,62.239,5.422,62.207,5.358,62.152,5.294,62.154,5.241,62.189,5.143,62.16,5.096,62.027,5.16,61.957,5.267,61.936,5.473,61.946,5.664,61.923,6.131,61.852,6.396,61.851,6.682,61.887,6.731,61.87,6.467,61.807,6.016,61.788,5.793,61.827,5.465,61.897,5.117,61.885,4.985,61.9,4.93,61.878,4.91,61.81,4.928,61.711,4.997,61.645,5.099,61.62,5.168,61.543,5.268,61.505,5.339,61.485,5.258,61.455,5.172,61.457,5.003,61.434,4.99,61.378,5.022,61.251,5.107,61.188,5.325,61.108,5.451,61.102,5.647,61.148,6.083,61.167,6.383,61.134,6.493,61.155,6.543,61.245,6.6,61.29,6.626,61.279,6.61,61.229,6.657,61.207,6.794,61.19,6.943,61.161,7.174,61.166,7.276,61.181,7.298,61.214,7.276,61.284,7.331,61.372,7.443,61.435,7.453,61.419,7.347,61.301,7.404,61.222,7.604,61.211,7.545,61.177,7.04,61.091,7.047,61.015,7.078,60.966,7.039,60.953,6.981,60.994,6.972,61.056,6.903,61.102,6.778,61.142,6.61,61.137,6.418,61.084,6.293,61.081,5.984,61.117,5.505,61.056,5.288,61.047,5.192,61.054,5.095,61.071,5.009,61.038,5.025,60.936,5.011,60.859,5.049,60.708,5.116,60.636,5.244,60.57,5.447,60.617,5.589,60.694,5.648,60.688,5.546,60.625,5.168,60.485,5.137,60.446,5.184,60.308,5.286,60.206,5.417,60.154,5.574,60.158,5.658,60.154,5.689,60.123,5.495,60.07,5.376,60.067,5.265,60.086,5.206,60.088,5.174,60.046,5.22,59.979,5.187,59.907,5.119,59.834,5.105,59.732,5.111,59.668,5.146,59.639,5.186,59.642,5.234,59.692,5.264,59.71,5.353,59.76,5.495,59.826,5.557,59.908,5.699,60.01,5.801,60.026,5.877,60.07,5.904,60.151,5.967,60.206,6.102,60.29,6.153,60.346,6.347,60.419,6.806,60.501,6.996,60.512,6.95,60.478,6.787,60.454,6.72,60.418,6.661,60.367,6.527,60.153,6.526,60.214,6.574,60.361,6.518,60.408,6.349,60.353,6.141,60.233,6.105,60.165,6.112,60.132,6.07,60.083,5.996,60.031,5.784,59.913,5.73,59.863,5.763,59.808,5.834,59.795,5.967,59.813,6.059,59.816,6.212,59.832,6.217,59.818,5.991,59.745,5.867,59.734,5.772,59.661,5.579,59.687,5.53,59.713,5.472,59.713,5.404,59.656,5.305,59.643,5.242,59.564,5.185,59.454,5.132,59.226,5.173,59.163,5.362,59.166,5.468,59.204,5.564,59.291,5.657,59.31,5.718,59.33,5.845,59.353,6.017,59.414,6.159,59.49,6.279,59.535,6.404,59.561,6.415,59.547,6.306,59.506,6.199,59.438,6.051,59.368,5.952,59.299,5.937,59.234,5.969,59.186,5.949,59.135,5.889,59.098,5.889,59.06,6.017,58.988,6.099,58.952,6.321,59.016,6.363,59.001,6.214,58.945,6.137,58.875,6.099,58.87,5.854,58.959,5.612,59.013,5.556,58.975,5.522,58.823,5.517,58.727,5.586,58.62,5.707,58.524,5.977,58.432,6.055,58.375,6.389,58.268,6.492,58.259,6.618,58.266,6.66,58.263,6.677,58.234,6.692,58.224,6.606,58.176,6.555,58.123,6.591,58.097,6.731,58.068,6.767,58.082,6.771,58.132,6.803,58.155,6.877,58.151,6.912,58.143,6.895,58.121,6.89,58.102,6.903,58.071,7.005,58.024,7.194,58.048,7.466,58.021,7.876,58.08,8.037,58.147,8.166,58.145,8.312,58.224,8.521,58.301,8.928,58.57,9.178,58.675,9.194,58.712,9.239,58.739,9.323,58.748,9.396,58.806,9.31,58.857,9.551,58.933,9.618,58.946,9.657,58.971,9.696,59.01,9.627,59.068,9.557,59.113,9.635,59.118,9.8,59.027,9.843,58.958,9.96,58.968,10.083,59.029,10.179,59.009,10.205,59.039,10.243,59.062,10.431,59.28,10.455,59.377,10.446,59.444,10.407,59.456,10.398,59.519,10.494,59.542,10.57,59.587,10.534,59.696,10.595,59.765,10.604,59.68,10.634,59.602,10.631,59.428,10.645,59.389,10.743,59.296,10.834,59.184,10.945,59.171,10.999,59.164,11.091,59.142,11.132,59.143,11.366,59.105,11.386,59.066,11.388,59.037,11.295,59.087,11.196,59.078,11.167,59.046,11.147,58.989,11.169,58.923,11.208,58.866,11.224,58.68,11.272,58.476,11.252,58.424,11.248,58.369,11.33,58.38,11.432,58.34,11.449,58.118,11.549,58.001,11.703,57.973,11.729,57.764,11.735,57.718,11.879,57.679,11.885,57.613,11.917,57.522,11.962,57.426,12.053,57.447,12.152,57.227,12.421,56.906,12.573,56.823,12.718,56.663,12.793,56.649,12.884,56.618,12.92,56.516,12.857,56.452,12.773,56.456,12.656,56.441,12.691,56.384,12.742,56.347,12.802,56.264,12.753,56.242,12.706,56.235,12.507,56.293,12.471,56.291,12.521,56.246,12.593,56.138,12.835,55.882,12.942,55.806,12.974,55.748,12.978,55.694,12.963,55.613,12.939,55.533,12.941,55.482,12.886,55.411,13.321,55.346,13.806,55.429,14.08,55.392,14.174,55.397,14.342,55.528,14.276,55.636,14.203,55.729,14.215,55.833,14.262,55.888,14.402,55.977,14.473,56.014,14.559,56.049,14.656,56.02,14.755,56.033,14.714,56.134,14.782,56.162,15.051,56.172,15.327,56.151,15.51,56.183,15.627,56.186,15.722,56.164,15.827,56.125,15.92,56.167,15.997,56.223,16.151,56.501,16.217,56.59,16.349,56.709,16.408,56.809,16.458,56.927,16.528,57.068,16.507,57.142,16.479,57.188,16.476,57.265,16.631,57.43,16.652,57.501,16.604,57.568,16.584,57.642,16.586,57.761,16.555,57.812,16.597,57.913,16.695,57.918,16.7,58.161,16.77,58.214,16.717,58.303,16.652,58.434,16.824,58.46,16.924,58.493,16.788,58.585,16.683,58.6,16.478,58.613,16.391,58.602,16.318,58.628,16.214,58.637,16.316,58.664,16.639,58.651,16.978,58.654,17.103,58.711,17.348,58.781,17.457,58.858,17.67,58.916,17.765,58.965,17.829,58.955,17.975,59.003,18.098,59.062,18.285,59.109,18.322,59.132,18.373,59.18,18.414,59.29,18.499,59.292,18.618,59.327,18.56,59.394,18.509,59.408,18.459,59.397,18.396,59.369,18.336,59.375,18.271,59.367,18.211,59.331,18.133,59.316,17.98,59.329,17.964,59.359,18.164,59.43,18.217,59.421,18.276,59.438,18.338,59.477,18.402,59.49,18.578,59.566,18.64,59.601,18.719,59.657,18.896,59.733,18.971,59.757,18.99,59.828,18.933,59.942,18.884,59.98,18.853,60.026,18.787,60.079,18.601,60.119,18.535,60.153,18.558,60.254,18.4,60.337,18.25,60.362,18.163,60.408,18.011,60.511,17.956,60.59,17.872,60.58,17.742,60.539,17.661,60.535,17.631,60.585,17.593,60.628,17.555,60.643,17.457,60.642,17.36,60.641,17.251,60.701,17.261,60.763,17.279,60.812,17.203,60.952,17.213,60.986,17.186,61.147,17.18,61.249,17.164,61.278,17.2,61.312,17.178,61.358,17.138,61.382,17.164,61.458,17.147,61.505,17.131,61.576,17.216,61.656,17.196,61.725,17.335,61.692,17.465,61.684,17.417,61.741,17.398,61.782,17.375,61.866,17.412,61.966,17.447,62.023,17.51,62.166,17.563,62.212,17.634,62.233,17.535,62.264,17.429,62.335,17.373,62.427,17.378,62.463,17.41,62.508,17.509,62.483,17.571,62.451,17.646,62.451,17.718,62.501,17.834,62.503,17.947,62.578,18.037,62.601,18.007,62.626,17.93,62.641,17.903,62.659,17.941,62.68,17.974,62.721,17.933,62.786,17.896,62.831,17.88,62.873,17.907,62.887,17.951,62.834,18.094,62.836,18.078,62.812,18.074,62.791,18.17,62.789,18.215,62.812,18.248,62.849,18.463,62.896,18.483,62.928,18.487,62.959,18.502,62.989,18.313,62.996,18.344,63.032,18.408,63.038,18.531,63.064,18.578,63.126,18.606,63.178,18.667,63.177,18.76,63.198,18.819,63.197,18.859,63.207,18.85,63.224,18.792,63.238,18.817,63.257,19.034,63.238,19.236,63.347,19.288,63.429,19.354,63.477,19.495,63.424,19.491,63.46,19.502,63.509,19.59,63.487,19.656,63.458,19.722,63.463,19.782,63.538,19.914,63.611,20.205,63.662,20.371,63.723,20.454,63.774,20.678,63.826,20.763,63.868,21.018,64.178,21.256,64.299,21.465,64.38,21.494,64.416,21.52,64.463,21.394,64.544,21.332,64.629,21.279,64.725,21.205,64.774,21.138,64.809,21.196,64.877,21.294,64.941,21.425,65.013,21.574,65.126,21.581,65.161,21.546,65.207,21.506,65.245,21.438,65.283,21.41,65.317,21.447,65.321,21.567,65.255,21.609,65.261,21.613,65.299,21.596,65.317,21.545,65.331,21.523,65.359,21.533,65.387,21.566,65.408,21.681,65.403,21.88,65.424,21.913,65.437,21.95,65.47,21.903,65.508,21.92,65.532,22.087,65.53,22.148,65.553,22.133,65.57,22.096,65.584,22.086,65.611,22.254,65.598,22.267,65.622,22.275,65.725,22.288,65.751,22.336,65.791,22.366,65.843,22.401,65.862,22.465,65.853,22.539,65.794,22.62,65.807,22.747,65.871,22.919,65.786,23.102,65.735,23.155,65.75,23.221,65.786,23.418,65.804,23.592,65.805,23.691,65.829,23.891,65.782,24.155,65.805,24.238,65.812,24.404,65.78,24.533,65.822,24.592,65.858,24.628,65.859,24.623,65.832,24.582,65.757,24.675,65.671,24.764,65.656,24.839,65.66,25.242,65.546,25.348,65.479,25.308,65.353,25.298,65.243,25.256,65.143,25.34,65.099,25.362,65.065,25.373,65.009,25.271,64.984,25.228,64.951,25.281,64.916,25.288,64.86,25.214,64.853,25.134,64.875,24.942,64.884,24.748,64.852,24.658,64.806,24.558,64.801,24.53,64.739,24.441,64.68,24.278,64.515,24.022,64.386,23.925,64.274,23.861,64.258,23.653,64.134,23.599,64.041,23.494,64.034,23.249,63.896,23.134,63.865,23.014,63.822,22.756,63.683,22.532,63.648,22.528,63.58,22.398,63.491,22.319,63.504,22.313,63.473,22.346,63.442,22.273,63.455,22.243,63.438,22.286,63.377,22.316,63.346,22.32,63.31,22.12,63.244,21.896,63.21,21.8,63.238,21.545,63.204,21.549,63.156,21.569,63.114,21.651,63.039,21.474,63.033,21.458,62.95,21.196,62.791,21.144,62.74,21.118,62.689,21.104,62.623,21.142,62.515,21.166,62.414,21.323,62.343,21.343,62.277,21.354,62.224,21.302,62.113,21.256,61.99,21.385,61.915,21.471,61.812,21.546,61.703,21.552,61.667,21.606,61.592,21.598,61.578,21.592,61.568,21.522,61.567,21.498,61.552,21.527,61.523,21.552,61.51,21.565,61.484,21.507,61.484,21.502,61.455,21.521,61.411,21.513,61.281,21.479,61.171,21.451,61.127,21.378,61.059,21.361,60.967,21.379,60.85,21.404,60.767,21.412,60.697,21.411,60.637,21.436,60.596,21.528,60.57,21.613,60.531,21.727,60.583,21.805,60.594,21.854,60.505,21.934,60.5,22.258,60.401,22.521,60.377,22.56,60.385,22.585,60.381,22.576,60.359,22.512,60.281,22.517,60.263,22.588,60.256,22.59,60.228,22.564,60.206,22.513,60.199,22.47,60.201,22.443,60.157,22.471,60.147,22.439,60.09,22.439,60.072,22.463,60.029,22.646,60.028,22.697,60.038,22.75,60.057,22.793,60.077,22.819,60.101,22.844,60.187,22.867,60.216,22.912,60.21,22.994,60.099,23.08,60.047,23.148,60.041,23.198,60.022,23.188,59.972,23.116,59.913,23.01,59.869,22.964,59.826,23.021,59.816,23.181,59.845,23.327,59.926,23.464,59.986,23.593,59.968,23.722,59.966,24.025,60.009,24.343,60.042,24.446,60.021,24.518,60.046,24.6,60.114,24.849,60.158,24.958,60.157,25.156,60.194,25.268,60.248,25.456,60.261,25.548,60.302,25.656,60.333,25.715,60.267,25.758,60.268,25.846,60.315,25.946,60.347,26.036,60.342,26.04,60.372,26.006,60.425,25.956,60.474,26.036,60.475,26.205,60.407,26.378,60.424,26.456,60.467,26.496,60.552,26.569,60.625,26.606,60.628,26.602,60.596,26.551,60.546,26.52,60.472,26.535,60.413,26.607,60.438,26.721,60.455,26.951,60.471,27.076,60.525,27.205,60.543,27.242,60.539,27.462,60.465,27.525,60.491,27.669,60.499,27.762,60.533,27.798,60.536,28.179,60.571,28.513,60.677,28.578,60.653,28.651,60.611,28.64,60.543,28.622,60.492,28.492,60.54,28.522,60.483,28.643,60.375,28.813,60.332,29.069,60.191,29.37,60.176,29.569,60.202,29.721,60.195,29.872,60.121,29.977,60.026,30.06,60.003,30.173,59.957,30.157,59.904,30.123,59.874,29.67,59.956,29.147,60,29.079,59.961,29.013,59.902,28.982,59.855,28.947,59.829,28.867,59.812,28.748,59.807,28.604,59.818,28.518,59.85,28.454,59.814,28.424,59.734,28.335,59.693,28.213,59.725,28.131,59.787,28.058,59.782,28.014,59.725,28.046,59.647,28.064,59.554,28.013,59.484,28.002,59.47,27.893,59.414,27.336,59.45,26.975,59.451,26.852,59.472,26.625,59.554,26.461,59.554,25.794,59.635,25.616,59.628,25.509,59.639,25.507,59.598,25.521,59.559,25.444,59.521,24.878,59.522,24.584,59.456,24.38,59.473,24.175,59.376,24.054,59.372,24.083,59.292,23.783,59.275,23.641,59.242,23.494,59.196,23.517,59.108,23.48,59.07,23.468,59.032,23.515,58.999,23.49,58.96,23.432,58.921,23.497,58.82,23.504,58.79,23.534,58.782,23.681,58.787,23.647,58.754,23.531,58.716,23.509,58.659,23.563,58.576,23.692,58.506,23.706,58.433,23.768,58.361,24.011,58.307,24.115,58.266,24.236,58.29,24.287,58.328,24.337,58.381,24.392,58.386,24.529,58.354,24.55,58.305,24.536,58.283,24.488,58.262,24.464,58.106,24.332,57.91,24.323,57.871,24.302,57.784,24.363,57.645,24.403,57.325,24.383,57.25,24.281,57.172,24.054,57.066,23.931,57.008,23.648,56.971,23.287,57.09,23.137,57.324,23.038,57.392,22.649,57.595,22.617,57.651,22.555,57.724,22.231,57.667,21.942,57.598,21.729,57.571,21.459,57.322,21.421,57.236,21.405,57.131,21.351,57.018,21.257,56.933,21.071,56.824,21.031,56.637,21.015,56.259,21.046,56.07,21.054,56.023,21.062,55.813,21.171,55.618,21.238,55.455,21.201,55.344,21.236,55.271,21.223,55.108,21.189,54.935,20.996,54.903,20.888,54.909,20.774,54.947,20.678,54.956,20.595,54.982,20.859,55.184,21.032,55.35,21.104,55.488,21.116,55.568,21.115,55.617,21.088,55.583,21.058,55.477,21.014,55.402,20.9,55.287,20.846,55.232,20.679,55.103,20.52,54.995,20.397,54.951,20.108,54.956,19.975,54.921,19.953,54.83,19.944,54.75,19.859,54.634,19.758,54.545,19.604,54.459,19.56,54.435,19.407,54.386,18.976,54.349,18.836,54.37,18.67,54.431,18.587,54.513,18.436,54.745,18.502,54.742,18.678,54.665,18.8,54.633,18.759,54.685,18.535,54.769,18.323,54.838,18.086,54.836,17.843,54.817,17.262,54.73,17.007,54.652,16.885,54.596,16.56,54.554,16.376,54.437,16.292,54.362,16.239,54.333,16.186,54.29,16.043,54.266,15.9,54.254,15.288,54.14,14.716,54.018,14.384,53.925,14.249,53.932,14.211,53.95,14.039,54.035,13.828,54.127,13.82,54.093,13.827,54.06,13.872,54.036,13.922,53.997,13.902,53.939,13.926,53.879,14.048,53.863,14.172,53.874,14.351,53.859,14.558,53.823,14.565,53.754,14.552,53.707,14.572,53.676,14.583,53.639,14.488,53.672,14.25,53.732,14.025,53.767,13.95,53.801,13.866,53.853,13.822,54.019,13.724,54.153,13.448,54.141,13.147,54.283,13.029,54.411,12.898,54.423,12.779,54.446,12.575,54.467,12.379,54.347,12.296,54.284,12.169,54.226,12.111,54.168,11.796,54.145,11.701,54.114,11.461,53.965,11.4,53.945,11.104,54.009,10.918,53.995,10.855,54.01,10.811,54.075,11.009,54.181,11.064,54.281,11.013,54.379,10.956,54.376,10.732,54.316,10.36,54.438,10.212,54.409,10.171,54.45,10.143,54.488,9.869,54.472,9.941,54.515,10.029,54.581,10.022,54.674,9.954,54.738,9.892,54.781,9.746,54.807,9.705,54.928,9.732,54.968,9.688,55,9.645,55.023,9.572,55.041,9.454,55.04,9.505,55.116,9.643,55.205,9.671,55.266,9.64,55.344,9.626,55.414,9.591,55.493,9.661,55.557,9.773,55.608,9.81,55.651,9.899,55.708,9.999,55.736,10.024,55.761,9.962,55.813,9.904,55.843,10.017,55.876,10.107,55.874,10.159,55.854,10.183,55.865,10.227,56.005,10.319,56.213,10.374,56.252,10.427,56.276,10.539,56.2,10.621,56.202,10.753,56.242,10.856,56.296,10.894,56.359,10.926,56.443,10.883,56.493,10.846,56.522,10.49,56.521,10.384,56.555,10.283,56.621,10.297,56.781,10.287,56.823,10.296,56.999,10.338,57.021,10.437,57.172,10.524,57.243,10.518,57.379,10.537,57.449,10.445,57.562,10.46,57.615,10.481,57.649,10.61,57.737,10.533,57.735,10.259,57.617,9.962,57.581,9.815,57.478,9.554,57.232,9.434,57.174,9.299,57.147,9.036,57.155,8.952,57.151,8.812,57.11,8.619,57.111,8.427,56.984,8.284,56.852,8.266,56.815,8.268,56.754,8.347,56.712,8.468,56.665,8.603,56.71,8.772,56.725,8.876,56.887,8.993,57.016,9.11,57.044,9.255,57.012,9.21,56.808,9.196,56.702,9.14,56.75,9.067,56.794,8.995,56.775,8.888,56.735,8.736,56.627,8.718,56.544,8.672,56.496,8.608,56.515,8.553,56.56,8.473,56.565,8.281,56.617,8.232,56.618,8.164,56.607,8.13,56.321,8.121,56.14,8.202,55.982,8.181,55.901,8.132,55.6,8.345,55.51,8.616,55.418,8.651,55.329,8.67,55.156,8.573,55.134,8.638,55.046,8.661,54.986,8.682,54.792,8.79,54.696,8.881,54.594,8.957,54.538,8.952,54.468,8.831,54.428,8.648,54.398,8.626,54.354,8.645,54.295,8.736,54.295,8.78,54.313,8.852,54.3,8.907,54.261,8.904,54,8.92,53.965,8.978,53.926,9.07,53.901,9.216,53.891,9.312,53.859,9.631,53.6,9.784,53.555,9.673,53.566,9.585,53.6,9.322,53.813,9.206,53.856,8.898,53.836,8.619,53.875,8.576,53.838,8.528,53.781,8.506,53.671,8.538,53.557,8.495,53.394,8.493,53.514,8.451,53.552,8.334,53.606,8.302,53.584,8.279,53.511,8.245,53.445,8.201,53.432,8.108,53.468,8.167,53.543,8.009,53.691,7.629,53.697,7.285,53.681,7.206,53.655,7.107,53.557,7.074,53.478,7.053,53.376,7.152,53.327,7.197,53.282,7.058,53.301,6.968,53.327,6.912,53.375,6.816,53.441,6.564,53.434,6.353,53.415,6.062,53.407,5.874,53.375,5.532,53.269,5.446,53.214,5.358,53.096,5.061,52.961,4.888,52.908,4.839,52.928,4.769,52.941,4.713,52.872,4.678,52.81,4.562,52.443,4.483,52.309,4.376,52.197,4.209,52.059,4.132,52.012,4.085,51.994,4.026,51.928,3.979,51.848,3.947,51.811,4.135,51.673,4.158,51.633,4.183,51.61,4.005,51.596,4.08,51.551,4.175,51.519,4.239,51.504,4.274,51.472,4.206,51.457,4.141,51.456,3.886,51.574,3.744,51.596,3.549,51.589,3.5,51.577,3.449,51.541,3.521,51.486,3.587,51.454,3.694,51.45,3.822,51.409,4.007,51.443,4.139,51.402,4.226,51.386,4.112,51.361,4.011,51.396,3.883,51.354,3.717,51.369,3.589,51.399,3.426,51.394,3.35,51.378,3.225,51.352,2.96,51.265,2.525,51.097,2.446,51.067,1.913,50.991,1.768,50.936,1.672,50.885,1.61,50.819,1.579,50.739,1.552,50.294,1.593,50.252,1.548,50.231,1.514,50.205,1.407,50.089,1.246,49.998,0.924,49.91,0.616,49.863,0.187,49.703,0.127,49.602,0.109,49.558,0.129,49.508,0.278,49.463,0.439,49.473,0.417,49.448,0.136,49.402,-0.011,49.33,-0.163,49.297,-0.521,49.355,-0.766,49.36,-0.959,49.393,-1.139,49.388,-1.195,49.445,-1.232,49.495,-1.265,49.598,-1.259,49.68,-1.366,49.707,-1.588,49.668,-1.705,49.681,-1.856,49.684,-1.875,49.631,-1.87,49.595,-1.813,49.49,-1.69,49.313,-1.583,49.202,-1.565,48.806,-1.48,48.698,-1.376,48.653,-1.438,48.641,-1.825,48.631,-1.852,48.669,-1.906,48.697,-1.973,48.635,-2.004,48.582,-2.079,48.645,-2.446,48.648,-2.692,48.537,-2.793,48.601,-3.003,48.791,-3.231,48.841,-3.471,48.813,-3.546,48.766,-3.715,48.71,-3.856,48.695,-4.059,48.708,-4.531,48.62,-4.721,48.54,-4.762,48.45,-4.749,48.41,-4.719,48.363,-4.585,48.358,-4.525,48.372,-4.393,48.368,-4.364,48.357,-4.302,48.347,-4.241,48.304,-4.403,48.293,-4.498,48.299,-4.531,48.31,-4.577,48.29,-4.544,48.247,-4.512,48.23,-4.435,48.218,-4.329,48.17,-4.378,48.129,-4.512,48.097,-4.629,48.086,-4.679,48.04,-4.428,47.969,-4.375,47.877,-4.312,47.823,-4.226,47.81,-4.071,47.848,-3.901,47.838,-3.508,47.753,-3.444,47.711,-3.396,47.72,-3.329,47.713,-3.265,47.685,-3.222,47.694,-3.159,47.695,-3.064,47.621,-2.964,47.601,-2.859,47.614,-2.787,47.626,-2.733,47.602,-2.797,47.537,-2.77,47.514,-2.666,47.526,-2.554,47.527,-2.483,47.512,-2.428,47.471,-2.476,47.413,-2.53,47.382,-2.503,47.312,-2.434,47.291,-2.353,47.279,-1.975,47.311,-1.743,47.216,-1.821,47.225,-1.922,47.261,-2.028,47.274,-2.108,47.263,-2.149,47.224,-2.197,47.163,-2.144,47.126,-2.082,47.112,-2.019,47.038,-2.09,46.921,-2.092,46.865,-2.059,46.81,-1.921,46.685,-1.787,46.515,-1.392,46.35,-1.313,46.327,-1.239,46.325,-1.146,46.311,-1.132,46.253,-1.136,46.205,-1.104,45.925,-1.066,45.806,-1.042,45.773,-1.032,45.741,-1.114,45.769,-1.21,45.771,-1.196,45.714,-1.17,45.686,-0.881,45.538,-0.791,45.468,-0.733,45.385,-0.641,45.09,-0.582,45.051,-0.548,45.001,-0.634,45.047,-0.691,45.093,-0.767,45.314,-0.826,45.381,-0.942,45.457,-1.081,45.532,-1.149,45.343,-1.189,45.161,-1.245,44.667,-1.22,44.687,-1.2,44.726,-1.153,44.764,-1.077,44.69,-1.171,44.662,-1.246,44.56,-1.346,44.02,-1.485,43.564,-1.631,43.438,-1.794,43.407,-1.829,43.401,-1.991,43.345,-2.197,43.322,-2.337,43.328,-2.607,43.413,-2.875,43.454,-2.948,43.44,-3.046,43.372,-3.418,43.452,-3.524,43.511,-3.605,43.519,-3.774,43.478,-3.889,43.499,-4.015,43.463,-4.313,43.415,-4.523,43.416,-5.105,43.502,-5.316,43.553,-5.666,43.582,-5.847,43.645,-6.08,43.595,-6.224,43.604,-6.476,43.579,-6.617,43.592,-6.901,43.586,-7.061,43.554,-7.262,43.595,-7.399,43.696,-7.504,43.74,-7.595,43.727,-7.698,43.765,-7.853,43.707,-8.005,43.694,-8.137,43.629,-8.257,43.58,-8.289,43.54,-8.252,43.497,-8.249,43.439,-8.355,43.397,-8.422,43.386,-8.537,43.337,-8.666,43.317,-8.874,43.334,-9.025,43.239,-9.096,43.214,-9.178,43.174,-9.236,43.036,-9.235,42.977,-9.179,42.911,-9.127,42.865,-9.042,42.814,-8.927,42.799,-8.937,42.767,-9.035,42.662,-9.033,42.594,-8.988,42.586,-8.812,42.64,-8.8,42.6,-8.81,42.562,-8.812,42.47,-8.776,42.435,-8.73,42.412,-8.769,42.358,-8.81,42.334,-8.816,42.285,-8.729,42.287,-8.691,42.274,-8.772,42.211,-8.887,42.105,-8.878,41.947,-8.852,41.927,-8.777,41.941,-8.878,41.832,-8.888,41.765,-8.846,41.705,-8.755,41.698,-8.811,41.652,-8.806,41.56,-8.738,41.285,-8.675,41.154,-8.66,41.086,-8.656,41.029,-8.674,40.917,-8.685,40.753,-8.732,40.651,-8.772,40.606,-8.873,40.259,-8.887,40.179,-8.851,40.152,-8.838,40.116,-9.004,39.821,-9.148,39.543,-9.251,39.426,-9.32,39.391,-9.375,39.338,-9.357,39.284,-9.353,39.248,-9.414,39.112,-9.431,38.96,-9.475,38.853,-9.48,38.799,-9.474,38.731,-9.41,38.708,-9.357,38.698,-9.252,38.713,-9.136,38.743,-9.091,38.835,-8.954,39.016,-8.867,39.066,-8.792,39.078,-8.938,38.998,-9,38.903,-8.977,38.803,-9.021,38.747,-9.093,38.697,-9.178,38.688,-9.25,38.657,-9.203,38.539,-9.213,38.448,-9.187,38.438,-9.096,38.455,-8.915,38.512,-8.862,38.51,-8.799,38.518,-8.734,38.482,-8.668,38.424,-8.881,38.447,-8.811,38.3,-8.802,38.184,-8.879,37.959,-8.823,37.872,-8.792,37.733,-8.819,37.592,-8.814,37.431,-8.926,37.166,-8.998,37.032,-8.935,37.016,-8.848,37.076,-8.739,37.075,-8.598,37.121,-8.484,37.1,-8.137,37.077,-7.94,37.005,-7.834,37.006,-7.494,37.168,-7.406,37.179,-7.175,37.209,-6.975,37.198,-6.929,37.215,-6.864,37.279,-6.859,37.249,-6.885,37.194,-6.492,36.955,-6.396,36.832,-6.321,36.908,-6.217,36.914,-6.259,36.899,-6.328,36.848,-6.412,36.729,-6.384,36.637,-6.269,36.597,-6.258,36.565,-6.266,36.527,-6.226,36.426,-6.17,36.334,-6.041,36.188,-5.961,36.182,-5.808,36.088,-5.625,36.026,-5.551,36.039,-5.462,36.074,-5.444,36.151,-5.407,36.159,-5.382,36.134,-5.361,36.135,-5.33,36.236,-5.231,36.374,-5.171,36.424,-4.935,36.502,-4.674,36.506,-4.502,36.629,-4.435,36.7,-4.367,36.718,-3.828,36.756,-3.579,36.74,-3.431,36.708,-3.259,36.756,-3.149,36.758,-2.902,36.743,-2.788,36.715,-2.671,36.748,-2.596,36.806,-2.453,36.831,-2.306,36.82,-2.188,36.745,-2.112,36.777,-1.939,36.946,-1.798,37.233,-1.641,37.387,-1.328,37.561,-0.938,37.571,-0.822,37.581,-0.772,37.596,-0.722,37.631,-0.823,37.712,-0.815,37.77,-0.753,37.85,-0.742,37.886,-0.683,37.992,-0.647,38.152,-0.551,38.203,-0.521,38.317,-0.381,38.436,-0.053,38.586,0.136,38.697,0.202,38.759,0.155,38.825,-0.034,38.891,-0.134,38.969,-0.205,39.063,-0.329,39.417,-0.327,39.52,-0.075,39.876,0.043,40.014,0.158,40.107,0.364,40.319,0.596,40.615,0.627,40.622,0.66,40.613,0.721,40.63,0.859,40.686,0.891,40.722,0.796,40.804,0.715,40.823,0.817,40.892,1.033,41.062,1.206,41.098,1.567,41.196,2.083,41.287,2.146,41.321,2.311,41.467,3.005,41.767,3.147,41.861,3.248,41.944,3.238,42.082,3.225,42.111,3.175,42.136,3.15,42.162,3.166,42.256,3.219,42.26,3.307,42.289,3.288,42.344,3.24,42.368,3.211,42.431,3.198,42.461,3.091,42.591,3.043,42.838,3.052,42.915,3.163,43.081,3.259,43.193,3.785,43.462,3.862,43.516,3.911,43.563,4.053,43.593,4.075,43.582,4.113,43.563,4.163,43.504,4.224,43.48,4.376,43.456,4.41,43.447,4.629,43.387,4.712,43.373,4.789,43.379,4.787,43.401,4.808,43.405,4.844,43.394,4.874,43.412,4.912,43.427,4.976,43.427,5.06,43.445,5.061,43.406,5.073,43.367,5.12,43.349,5.2,43.352,5.32,43.345,5.407,43.229,5.672,43.178,5.809,43.098,6.031,43.101,6.116,43.072,6.305,43.139,6.494,43.169,6.57,43.199,6.657,43.262,6.687,43.335,6.717,43.374,6.865,43.438,7.181,43.659,7.262,43.696,7.378,43.732,7.439,43.75,7.493,43.767,7.733,43.803,8.005,43.877,8.082,43.919,8.292,44.137,8.552,44.346,8.766,44.422,8.93,44.408,9.196,44.323,9.289,44.319,9.731,44.101,10.048,44.02,10.188,43.948,10.246,43.852,10.321,43.513,10.448,43.371,10.521,43.204,10.532,43.14,10.517,43.065,10.515,42.968,10.59,42.954,10.645,42.957,10.708,42.936,10.737,42.9,10.765,42.845,10.803,42.804,10.938,42.739,11.168,42.535,11.185,42.457,11.142,42.444,11.103,42.417,11.141,42.39,11.189,42.393,11.25,42.416,11.296,42.423,11.498,42.363,11.637,42.288,11.807,42.082,12.075,41.941,12.206,41.813,12.631,41.47,12.849,41.409,13.024,41.301,13.041,41.266,13.089,41.244,13.183,41.278,13.247,41.289,13.362,41.279,13.555,41.232,13.67,41.254,13.733,41.236,13.86,41.13,14.048,40.87,14.044,40.812,14.076,40.794,14.102,40.827,14.147,40.821,14.309,40.813,14.428,40.759,14.461,40.729,14.34,40.599,14.383,40.6,14.459,40.633,14.557,40.626,14.611,40.645,14.766,40.668,14.84,40.63,14.907,40.556,14.948,40.469,14.986,40.377,14.929,40.31,14.927,40.265,14.951,40.239,15.295,40.07,15.391,40.052,15.585,40.053,15.693,39.99,15.764,39.87,15.854,39.627,16.024,39.354,16.071,39.139,16.107,39.024,16.21,38.941,16.197,38.759,16.066,38.736,15.972,38.713,15.927,38.672,15.879,38.614,15.905,38.483,15.822,38.303,15.7,38.262,15.643,38.175,15.646,38.034,15.725,37.939,16.057,37.942,16.11,38.019,16.144,38.086,16.282,38.25,16.546,38.409,16.574,38.494,16.559,38.715,16.617,38.8,16.755,38.89,16.951,38.94,17.099,38.919,17.175,38.998,17.123,39.137,17.115,39.381,16.999,39.482,16.824,39.578,16.598,39.639,16.522,39.748,16.53,39.86,16.67,40.137,16.807,40.326,16.928,40.458,17.031,40.513,17.18,40.503,17.215,40.486,17.249,40.438,17.258,40.399,17.396,40.34,17.476,40.315,17.865,40.28,18.078,39.937,18.219,39.853,18.344,39.821,18.393,39.904,18.423,39.987,18.486,40.105,18.461,40.221,18.328,40.371,18.036,40.565,17.955,40.655,17.474,40.841,17.275,40.975,17.103,41.062,16.552,41.232,16.013,41.435,15.9,41.512,15.914,41.621,16.034,41.701,16.151,41.758,16.189,41.814,16.165,41.896,16.062,41.928,15.964,41.939,15.405,41.913,15.169,41.934,14.866,42.053,14.541,42.244,14.183,42.506,14.01,42.69,13.925,42.852,13.805,43.18,13.693,43.39,13.564,43.571,13.508,43.612,13.295,43.686,12.907,43.921,12.691,43.995,12.487,44.134,12.396,44.224,12.305,44.429,12.248,44.723,12.279,44.832,12.319,44.833,12.384,44.798,12.464,44.845,12.498,44.899,12.523,44.968,12.392,45.04,12.286,45.208,12.226,45.242,12.249,45.369,12.274,45.446,12.354,45.492,12.492,45.546,12.536,45.545,12.432,45.468,12.498,45.462,12.612,45.497,12.761,45.544,12.903,45.611,13.03,45.638,13.12,45.698,13.157,45.747,13.206,45.771,13.465,45.71,13.558,45.771,13.628,45.771,13.783,45.627,13.72,45.588,13.637,45.536,13.578,45.517,13.517,45.482,13.603,45.231,13.613,45.163,13.629,45.108,13.742,44.992,13.861,44.837,13.9,44.829,13.966,44.836,14.042,44.927,14.091,44.998,14.236,45.16,14.269,45.283,14.313,45.338,14.386,45.342,14.55,45.298,14.632,45.223,14.855,45.081,14.907,44.971,14.885,44.818,14.895,44.707,14.981,44.603,15.27,44.383,15.381,44.328,15.471,44.272,15.37,44.289,15.284,44.289,15.231,44.271,15.185,44.273,15.123,44.257,15.186,44.172,15.499,43.909,15.656,43.811,15.821,43.736,15.942,43.657,15.949,43.607,15.943,43.569,15.986,43.52,16.046,43.506,16.131,43.506,16.269,43.531,16.394,43.543,16.6,43.464,16.903,43.392,17.129,43.211,17.33,43.115,17.537,42.962,17.585,42.938,17.668,42.897,17.724,42.851,17.22,43.026,17.126,43.026,17.045,43.015,17.258,42.968,17.585,42.837,17.824,42.797,18.161,42.634,18.333,42.528,18.517,42.433,18.554,42.429,18.592,42.444,18.646,42.443,18.633,42.423,18.619,42.398,18.633,42.378,18.894,42.249,19.122,42.06,19.186,41.949,19.342,41.869,19.468,41.856,19.578,41.788,19.576,41.64,19.546,41.597,19.497,41.563,19.441,41.425,19.453,41.321,19.48,41.236,19.456,41.106,19.461,40.933,19.384,40.791,19.338,40.664,19.345,40.622,19.439,40.47,19.459,40.405,19.441,40.376,19.395,40.394,19.359,40.409,19.322,40.407,19.36,40.348,19.398,40.285,19.485,40.21,19.852,40.044,19.965,39.872,19.996,39.801,20.001,39.709,20.099,39.641,20.191,39.546,20.301,39.327,20.468,39.255,20.572,39.148,20.691,39.067,20.713,39.035,20.78,39.009,20.923,39.037,21.034,39.026,21.069,39.032,21.118,39.03,21.145,38.979,21.152,38.922,21.112,38.896,21.074,38.885,20.893,38.941,20.777,38.928,20.769,38.874,20.777,38.808,20.873,38.776,20.992,38.654,21.06,38.503,21.113,38.385,21.183,38.346,21.303,38.374,21.33,38.424,21.331,38.487,21.355,38.475,21.39,38.408,21.473,38.321,21.568,38.334,21.65,38.354,21.717,38.355,21.805,38.367,21.965,38.412,22.227,38.353,22.32,38.357,22.385,38.386,22.422,38.439,22.583,38.345,22.754,38.29,22.784,38.262,22.834,38.235,22.933,38.202,22.995,38.216,23.034,38.202,23.094,38.196,23.149,38.176,23.183,38.134,23.153,38.096,23.122,38.073,22.955,38.075,22.893,38.051,22.917,38.007,22.92,37.958,22.846,37.968,22.8,37.981,22.712,38.047,22.556,38.113,22.244,38.189,21.953,38.321,21.825,38.328,21.748,38.274,21.658,38.175,21.549,38.165,21.451,38.205,21.404,38.197,21.308,38.027,21.145,37.919,21.125,37.892,21.138,37.854,21.205,37.829,21.288,37.775,21.329,37.669,21.416,37.64,21.571,37.541,21.679,37.387,21.692,37.309,21.579,37.2,21.583,37.081,21.738,36.863,21.892,36.737,21.934,36.804,21.94,36.892,21.956,36.99,22.012,37.017,22.08,37.029,22.134,36.964,22.165,36.903,22.231,36.883,22.376,36.702,22.381,36.646,22.375,36.514,22.428,36.476,22.489,36.447,22.489,36.568,22.608,36.78,22.717,36.794,22.78,36.786,22.832,36.687,22.983,36.528,23.061,36.487,23.107,36.452,23.16,36.448,23.112,36.548,23.041,36.645,23.074,36.775,23.06,36.854,22.995,37.016,22.851,37.291,22.765,37.393,22.725,37.542,22.775,37.585,22.851,37.532,22.941,37.517,23.015,37.482,23.096,37.441,23.1,37.364,23.162,37.334,23.203,37.349,23.253,37.377,23.489,37.44,23.491,37.464,23.458,37.497,23.409,37.542,23.396,37.58,23.348,37.598,23.263,37.595,23.198,37.62,23.147,37.716,23.147,37.795,23.086,37.853,23.036,37.878,23.047,37.903,23.087,37.913,23.194,37.959,23.42,37.992,23.502,38.035,23.537,38.033,23.58,38.011,23.733,37.884,23.877,37.778,23.972,37.677,24.02,37.678,24.055,37.71,24.062,37.775,24.061,37.818,24.033,37.955,24.025,38.14,24.005,38.227,23.967,38.275,23.836,38.325,23.684,38.352,23.57,38.489,23.369,38.526,23.253,38.661,23.138,38.668,23.02,38.742,22.774,38.8,22.688,38.849,22.634,38.851,22.569,38.867,22.597,38.891,22.677,38.899,22.803,38.902,22.93,38.948,23.067,39.038,22.966,39.031,22.939,39.112,22.886,39.17,22.839,39.259,22.921,39.306,22.993,39.331,23.162,39.258,23.169,39.21,23.119,39.133,23.155,39.101,23.218,39.104,23.328,39.175,23.288,39.289,23.233,39.358,23.103,39.492,22.979,39.564,22.919,39.629,22.836,39.801,22.592,40.037,22.569,40.119,22.605,40.276,22.643,40.367,22.625,40.429,22.629,40.496,22.742,40.536,22.811,40.579,22.922,40.591,22.893,40.524,22.851,40.491,22.896,40.4,23.098,40.304,23.312,40.216,23.328,40.09,23.396,39.99,23.627,39.924,23.658,39.934,23.674,39.959,23.467,40.074,23.433,40.115,23.386,40.222,23.426,40.264,23.665,40.224,23.835,40.022,23.947,39.966,23.982,39.994,24.001,40.025,23.967,40.115,23.918,40.155,23.823,40.205,23.721,40.286,23.728,40.33,23.823,40.368,23.913,40.359,24.056,40.304,24.159,40.28,24.232,40.215,24.343,40.148,24.292,40.242,24.213,40.328,24.031,40.409,23.932,40.406,23.867,40.419,23.832,40.482,23.879,40.544,23.779,40.628,23.743,40.677,23.763,40.748,23.946,40.748,24.082,40.724,24.234,40.786,24.384,40.913,24.477,40.948,24.557,40.936,24.679,40.869,24.793,40.858,25.005,40.968,25.104,40.995,25.25,40.933,25.325,40.943,25.497,40.888,25.856,40.844,26.011,40.769,26.039,40.727,26.068,40.683,26.105,40.611,26.224,40.618,26.361,40.606,26.578,40.625,26.792,40.627,26.72,40.544,26.447,40.445,26.355,40.39,26.254,40.315,26.252,40.248,26.26,40.202,26.226,40.142,26.203,40.075,26.272,40.097,26.33,40.123,26.468,40.261,26.772,40.498,26.975,40.564,27.258,40.687,27.43,40.84,27.499,40.973,27.747,41.013,27.925,40.991,28.086,41.061,28.172,41.081,28.295,41.071,28.78,40.974,28.956,41.008,28.996,41.061,29.032,41.14,29.057,41.23,28.947,41.248,28.346,41.466,28.198,41.554,28.05,41.729,27.987,41.855,28.014,41.969,27.983,42.047,27.821,42.208,27.708,42.35,27.64,42.401,27.485,42.468,27.754,42.707,27.818,42.717,27.889,42.75,27.896,43.021,27.929,43.186,27.979,43.231,28.035,43.268,28.134,43.396,28.32,43.427,28.465,43.389,28.562,43.501,28.585,43.742,28.591,43.797,28.659,43.984,28.645,44.296,28.699,44.374,28.852,44.506,28.888,44.575,28.807,44.565,28.814,44.602,28.846,44.637,28.849,44.716,28.87,44.75,28.926,44.81,28.892,44.919,28.931,44.966,28.981,44.993,29.095,44.975,29.048,44.926,29.069,44.871,29.081,44.799,29.048,44.758,29.558,44.843,29.605,44.915,29.635,44.98,29.679,45.152,29.689,45.193,29.706,45.26,29.727,45.343,29.67,45.541,29.601,45.6,29.602,45.683,29.628,45.722,29.685,45.755,29.821,45.732,29.902,45.752,30.007,45.798,30.184,45.85,30.219,45.867,30.493,46.09,30.512,46.105,30.657,46.267,30.672,46.304,30.722,46.366,30.773,46.473,30.796,46.552,31.137,46.624,31.32,46.613,31.403,46.629,31.497,46.738,31.563,46.777,31.532,46.665,31.657,46.642,31.78,46.632,31.873,46.65,31.902,46.722,31.913,46.926,31.866,47.003,31.837,47.087,31.759,47.213,31.838,47.157,31.865,47.095,31.94,46.982,31.964,46.855,31.945,46.784,31.974,46.709,32.044,46.642,32.127,46.597,32.354,46.565,32.578,46.616,32.553,46.592,32.419,46.518,32.361,46.475,32.131,46.509,31.878,46.522,31.716,46.555,31.555,46.554,31.624,46.51,31.714,46.472,31.856,46.462,32.008,46.43,32.013,46.387,31.992,46.364,31.916,46.349,31.843,46.346,31.78,46.325,31.831,46.282,31.925,46.287,32.036,46.261,32.33,46.13,32.477,46.084,32.797,46.131,32.942,46.124,33.202,46.176,33.263,46.126,33.43,46.058,33.499,46.079,33.594,46.096,33.637,46.033,33.665,45.947,33.466,45.838,33.28,45.765,33.142,45.749,32.828,45.593,32.508,45.404,32.552,45.35,32.611,45.328,32.773,45.359,32.919,45.348,33.187,45.195,33.262,45.171,33.392,45.188,33.555,45.098,33.601,44.981,33.612,44.908,33.53,44.681,33.491,44.619,33.463,44.597,33.451,44.554,33.656,44.433,33.756,44.399,33.91,44.388,34.074,44.424,34.282,44.538,34.47,44.722,34.717,44.807,34.888,44.824,35.088,44.803,35.155,44.896,35.358,44.978,35.473,45.098,35.57,45.119,35.678,45.102,35.759,45.071,35.804,45.04,35.87,45.005,36.055,45.031,36.23,45.026,36.393,45.065,36.428,45.153,36.451,45.232,36.514,45.304,36.575,45.394,36.427,45.433,36.29,45.457,36.171,45.453,36.077,45.424,36.013,45.372,35.833,45.402,35.751,45.389,35.558,45.311,35.458,45.316,35.374,45.354,35.26,45.447,35.023,45.701,34.907,45.879,34.857,45.987,34.844,46.074,34.85,46.19,34.97,46.242,35.064,46.267,35.23,46.441,35.292,46.371,35.291,46.314,35.28,46.279,35.218,46.232,35.015,46.106,35.055,46.104,35.132,46.126,35.204,46.169,35.257,46.204,35.4,46.381,35.827,46.624,36.025,46.667,36.195,46.646,36.279,46.659,36.432,46.733,36.559,46.763,36.689,46.764,36.795,46.714,36.932,46.825,37.048,46.876,37.219,46.917,37.34,46.917,37.543,47.075,37.829,47.096,38.178,47.08,38.214,47.091,38.485,47.176,38.577,47.239,38.762,47.262,38.736,47.236,38.644,47.212,38.552,47.15,38.668,47.144,38.928,47.176,39.024,47.272,39.196,47.269,39.245,47.2,39.293,47.106,39.289,47.071,39.271,47.044,39.127,47.023,38.801,46.906,38.631,46.873,38.439,46.813,38.488,46.732,38.501,46.664,38.343,46.678,38.23,46.701,38.159,46.691,37.968,46.618,37.867,46.634,37.767,46.636,37.81,46.532,37.914,46.406,37.978,46.383,38.078,46.394,38.315,46.242,38.492,46.091,38.4,46.08,38.312,46.095,38.184,46.095,38.133,46.003,38.08,45.935,38.07,45.97,38.074,46.017,38.014,46.048,37.933,46.002,37.841,45.8,37.669,45.654,37.612,45.565,37.61,45.5,37.634,45.486,37.672,45.488,37.673,45.43,37.647,45.377,37.264,45.311,37.214,45.272,37.104,45.303,36.978,45.384,36.866,45.427,36.794,45.41,36.72,45.372,36.762,45.348,36.811,45.34,36.941,45.29,36.873,45.252,36.619,45.185,36.628,45.151,36.651,45.126,36.944,45.07,37.205,44.972,37.284,44.905,37.352,44.788,37.411,44.735,37.495,44.695,37.572,44.671,37.705,44.661,37.851,44.699,38.181,44.42,38.312,44.374,38.636,44.318,38.717,44.288,39.329,43.897,39.517,43.728,39.874,43.473,39.978,43.42,40.191,43.312,40.462,43.146,40.524,43.121,40.837,43.063,41.062,42.931,41.129,42.828,41.419,42.738,41.489,42.659,41.578,42.398,41.663,42.147,41.763,41.97,41.761,41.885,41.759,41.817,41.702,41.705,41.51,41.517,41.414,41.424,41.084,41.261,40.959,41.212,40.82,41.19,40.688,41.107,40.265,40.961,40.128,40.943,40,40.977,39.911,40.966,39.808,40.983,39.426,41.106,38.852,41.018,38.557,40.937,38.381,40.925,37.91,41.002,37.766,41.079,37.431,41.114,37.066,41.184,36.992,41.275,36.778,41.363,36.647,41.353,36.587,41.327,36.51,41.263,36.405,41.275,36.278,41.336,36.179,41.427,36.052,41.683,35.978,41.705,35.92,41.714,35.558,41.634,35.298,41.729,35.209,41.794,35.122,41.891,35.114,41.957,35.141,41.99,35.155,42.028,35.006,42.063,34.75,41.957,34.193,41.964,33.381,42.018,33.285,42.005,32.947,41.892,32.542,41.806,32.306,41.73,32.086,41.589,31.458,41.32,31.347,41.158,31.255,41.108,30.81,41.085,30.345,41.197,29.919,41.151,29.322,41.228,29.148,41.221,29.094,41.177,29.067,41.102,29.046,41.008,29.082,40.963,29.114,40.938,29.26,40.847,29.365,40.809,29.801,40.76,29.849,40.76,29.845,40.738,29.508,40.708,29.054,40.649,28.958,40.631,28.788,40.534,28.841,40.503,28.895,40.482,28.974,40.467,29.055,40.424,29.007,40.39,28.739,40.391,28.63,40.376,28.289,40.403,27.963,40.37,27.929,40.38,27.964,40.435,27.995,40.467,27.99,40.489,27.875,40.513,27.769,40.51,27.732,40.481,27.849,40.382,27.789,40.351,27.728,40.329,27.476,40.32,27.333,40.376,27.314,40.415,27.285,40.456,27.122,40.452,27.012,40.396,26.738,40.4,26.475,40.197,26.313,40.025,26.181,39.99,26.15,39.873,26.155,39.657,26.101,39.569,26.096,39.521,26.113,39.467,26.351,39.484,26.484,39.521,26.827,39.563,26.899,39.55,26.911,39.517,26.813,39.419,26.711,39.34,26.682,39.292,26.719,39.261,26.854,39.116,26.849,39.057,26.808,39.014,26.815,38.961,26.866,38.923,26.92,38.934,26.97,38.919,27.014,38.887,26.909,38.776,26.79,38.736,26.764,38.71,26.788,38.66,26.795,38.626,26.838,38.558,26.907,38.482,26.97,38.448,27.144,38.452,27.099,38.416,26.861,38.373,26.77,38.388,26.727,38.419,26.696,38.405,26.674,38.336,26.641,38.352,26.595,38.419,26.61,38.487,26.587,38.557,26.514,38.629,26.441,38.641,26.378,38.624,26.372,38.562,26.43,38.441,26.416,38.368,26.344,38.37,26.291,38.277,26.333,38.242,26.428,38.214,26.525,38.162,26.582,38.149,26.621,38.176,26.683,38.198,26.807,38.138,26.879,38.055,26.944,38.063,27.159,37.987,27.232,37.979,27.255,37.882,27.224,37.725,27.078,37.688,27.068,37.658,27.148,37.604,27.204,37.491,27.219,37.389,27.29,37.349,27.376,37.341,27.401,37.307,27.52,37.249,27.535,37.164,27.368,37.122,27.3,37.127,27.25,37.079,27.263,36.977,27.311,36.982,27.349,37.02,27.668,37.007,28.134,37.029,28.242,37.029,28.224,36.996,28.083,36.92,28.005,36.832,27.934,36.809,27.631,36.787,27.555,36.759,27.467,36.746,27.454,36.712,27.54,36.684,27.656,36.675,27.804,36.736,28.084,36.751,28.014,36.67,28.019,36.634,28.112,36.646,28.196,36.686,28.304,36.812,28.484,36.804,28.718,36.701,28.817,36.675,28.896,36.674,28.97,36.715,29.038,36.693,29.058,36.638,29.066,36.59,29.116,36.52,29.143,36.397,29.224,36.324,29.348,36.259,29.689,36.157,29.789,36.168,30.083,36.249,30.232,36.307,30.295,36.288,30.387,36.243,30.446,36.27,30.484,36.31,30.506,36.451,30.558,36.526,30.582,36.797,30.644,36.866,30.95,36.849,31.241,36.822,31.353,36.801,31.778,36.613,32.022,36.535,32.131,36.449,32.284,36.268,32.378,36.184,32.534,36.101,32.795,36.036,32.929,36.096,33.1,36.103,33.442,36.153,33.523,36.144,33.695,36.182,33.955,36.295,34.023,36.341,34.3,36.604,34.601,36.784,34.704,36.817,34.811,36.799,34.943,36.726,35.176,36.635,35.393,36.575,35.537,36.597,35.626,36.653,35.661,36.724,35.734,36.764,35.802,36.778,35.905,36.848,36.049,36.911,36.135,36.852,36.18,36.807,36.188,36.743,36.188,36.659,36.032,36.523,35.883,36.406,35.811,36.31,35.887,36.159,35.957,35.998,35.893,35.917,35.84,35.849,35.764,35.572,35.902,35.421,35.916,35.351,35.918,35.3,35.943,35.224,35.89,35.06,35.888,34.949,35.899,34.852,35.976,34.629,35.978,34.547,35.921,34.493,35.804,34.437,35.648,34.248,35.612,34.032,35.511,33.88,35.336,33.503,35.251,33.393,35.204,33.259,35.155,33.16,35.109,33.084,35.077,32.967,35.006,32.827,34.922,32.614,34.804,32.196,34.678,31.896,34.484,31.592,34.477,31.585,34.387,31.484,34.198,31.323,34.176,31.304,33.903,31.181,33.667,31.13,33.378,31.131,33.194,31.085,33.157,31.126,33.13,31.168,32.902,31.111,32.854,31.118,32.685,31.074,32.603,31.069,32.533,31.101,32.324,31.256,32.251,31.295,32.216,31.294,32.243,31.247,32.282,31.201,32.207,31.119,32.102,31.093,32.066,31.153,32.008,31.221,31.902,31.24,31.771,31.293,31.876,31.414,31.892,31.482,32.076,31.344,32.136,31.341,31.964,31.502,31.889,31.541,31.839,31.526,31.607,31.456,31.524,31.458,31.194,31.588,31.083,31.603,31.052,31.592,31.031,31.508,31.002,31.463,30.841,31.44,30.7,31.404,30.563,31.417,30.884,31.522,30.924,31.567,30.571,31.473,30.395,31.458,30.344,31.403,30.312,31.357,30.262,31.317,30.223,31.258,30.128,31.256,30.049,31.265,29.93,31.227,29.592,31.012,29.429,30.927,29.279,30.867,29.16,30.835,29.072,30.83,28.973,30.857,28.807,30.943,28.515,31.05,27.968,31.097,27.83,31.195,27.62,31.192,27.54,31.213,27.248,31.378,26.769,31.47,26.457,31.512,25.893,31.621,25.382,31.513,25.225,31.534,25.15,31.655,25.115,31.712,25.025,31.883,24.951,31.954,24.879,31.984,24.684,32.016,24.48,31.997,24.13,32.009,24.039,32.037,23.898,32.127,23.798,32.159,23.286,32.214,23.106,32.331,23.11,32.397,23.13,32.448,23.091,32.619,22.917,32.687,22.754,32.741,22.523,32.794,22.341,32.88,22.187,32.918,21.839,32.909,21.721,32.942,21.636,32.937,21.425,32.799,21.319,32.778,21.062,32.776,20.621,32.58,20.371,32.431,20.121,32.219,20.031,32.108,19.973,31.999,19.926,31.818,19.961,31.556,20.02,31.411,20.104,31.301,20.141,31.196,20.151,31.079,20.112,30.964,20.013,30.801,19.713,30.488,19.59,30.414,19.292,30.288,19.124,30.266,18.936,30.29,18.67,30.416,18.19,30.777,17.949,30.852,17.83,30.928,17.349,31.081,16.782,31.215,16.451,31.227,16.123,31.264,15.832,31.361,15.706,31.426,15.596,31.531,15.496,31.657,15.414,31.834,15.363,31.971,15.359,32.16,15.267,32.312,15.177,32.391,14.513,32.511,14.424,32.55,14.237,32.681,14.156,32.71,13.835,32.792,13.648,32.799,13.536,32.824,13.283,32.915,13.138,32.897,12.754,32.801,12.427,32.829,12.28,32.859,11.813,33.094,11.657,33.119,11.505,33.182,11.401,33.225,11.338,33.209,11.234,33.234,11.203,33.249,11.232,33.272,11.27,33.286,11.257,33.309,11.15,33.369,11.085,33.563,10.958,33.626,10.898,33.534,10.828,33.519,10.723,33.514,10.704,33.61,10.713,33.689,10.454,33.663,10.305,33.728,10.159,33.85,10.049,34.056,10.04,34.14,10.065,34.212,10.118,34.28,10.2,34.346,10.535,34.545,10.691,34.678,10.866,34.884,10.956,35.034,11.12,35.24,11.043,35.335,11.032,35.454,11.001,35.552,11.004,35.634,10.784,35.772,10.689,35.8,10.591,35.887,10.506,36.032,10.477,36.175,10.488,36.255,10.526,36.323,10.642,36.42,10.798,36.493,10.967,36.743,11.057,36.841,11.127,36.874,11.077,36.967,11.054,37.073,10.951,37.059,10.766,36.93,10.571,36.879,10.518,36.791,10.412,36.732,10.293,36.781,10.334,36.865,10.189,37.034,10.196,37.206,10.087,37.251,9.988,37.258,9.876,37.254,9.879,37.213,9.896,37.182,9.83,37.135,9.784,37.211,9.816,37.255,9.838,37.309,9.759,37.33,9.688,37.34,9.142,37.195,9.059,37.156,8.824,36.998,8.577,36.937,8.127,36.91,7.91,36.856,7.792,36.88,7.608,37,7.432,37.059,7.204,37.092,7.238,36.969,7.143,36.943,6.928,36.919,6.576,37.003,6.487,37.086,6.328,37.046,6.249,36.938,6.065,36.864,5.725,36.8,5.425,36.675,5.295,36.648,5.196,36.677,4.995,36.808,4.878,36.862,4.758,36.896,3.779,36.896,3.521,36.795,2.973,36.784,2.846,36.739,2.593,36.601,2.343,36.61,1.975,36.568,1.257,36.52,0.972,36.444,0.791,36.357,0.515,36.262,0.312,36.162,0.152,36.063,0.048,35.901,-0.048,35.833,-0.189,35.819,-0.351,35.863,-0.426,35.862,-0.917,35.668,-1.088,35.579,-1.205,35.496,-1.336,35.364,-1.484,35.303,-1.674,35.183,-1.913,35.094,-2.018,35.085,-2.22,35.104,-2.424,35.123,-2.637,35.113,-2.731,35.135,-2.84,35.128,-2.87,35.173,-2.926,35.287,-2.954,35.315,-2.958,35.363,-2.972,35.407,-3.063,35.317,-3.206,35.239,-3.395,35.212,-3.591,35.228,-3.693,35.28,-3.788,35.245,-3.982,35.243,-4.33,35.161,-4.628,35.206,-4.837,35.281,-5.105,35.468,-5.253,35.615,-5.338,35.745,-5.338,35.857,-5.278,35.903,-5.397,35.93,-5.522,35.862,-5.623,35.829,-5.748,35.816,-5.925,35.786,-5.958,35.681,-6.353,34.776,-6.756,34.133,-6.901,33.969,-7.145,33.83,-7.562,33.64,-8.301,33.374,-8.513,33.252,-8.596,33.187,-8.836,32.92,-9.246,32.572,-9.249,32.486,-9.287,32.241,-9.347,32.086,-9.675,31.711,-9.809,31.425,-9.833,31.07,-9.832,30.847,-9.875,30.718,-9.854,30.645,-9.773,30.603,-9.653,30.448,-9.624,30.353,-9.667,30.109,-9.743,29.958,-9.853,29.809,-10.01,29.641,-10.201,29.38,-10.486,29.065,-10.674,28.939,-11.081,28.714,-11.299,28.526,-11.43,28.382,-11.553,28.31,-11.986,28.129,-12.469,28.009,-12.794,27.978,-12.949,27.914,-13.041,27.77,-13.176,27.656,-13.256,27.435,-13.41,27.147,-13.496,26.873,-13.576,26.735,-13.696,26.643,-13.952,26.489,-14.168,26.415,-14.312,26.297,-14.414,26.254,-14.471,26.163,-14.523,25.925,-14.602,25.809,-14.707,25.548,-14.795,25.404,-14.843,25.22,-14.856,24.872,-14.904,24.72,-15.039,24.549,-15.189,24.479,-15.586,24.073,-15.778,23.953,-15.899,23.844,-15.953,23.741,-15.981,23.67,-15.913,23.728,-15.855,23.8,-15.802,23.842,-15.789,23.793,-15.806,23.75,-15.943,23.553,-15.997,23.425,-16.114,23.228,-16.21,23.098,-16.17,23.032,-16.202,22.945,-16.304,22.835,-16.359,22.595,-16.514,22.333,-16.684,22.274,-16.793,22.16,-16.931,21.9,-17.01,21.377,-17.099,20.857,-17.048,20.806,-16.998,21.04,-16.971,21.076,-16.928,21.115,-16.876,21.086,-16.728,20.806,-16.623,20.634,-16.563,20.604,-16.535,20.654,-16.53,20.71,-16.479,20.69,-16.43,20.652,-16.334,20.416,-16.21,20.228,-16.241,20.141,-16.233,20.001,-16.283,19.787,-16.445,19.473,-16.305,19.513,-16.371,19.41,-16.475,19.391,-16.514,19.362,-16.476,19.285,-16.306,19.154,-16.213,19.003,-16.15,18.718,-16.085,18.521,-16.047,18.223,-16.03,17.888,-16.079,17.546,-16.207,17.193,-16.347,16.926,-16.464,16.602,-16.481,16.454,-16.536,16.287,-16.535,15.838,-16.571,15.734,-16.843,15.294,-17.147,14.922,-17.412,14.792,-17.536,14.755,-17.445,14.652,-17.418,14.723,-17.346,14.729,-17.261,14.701,-17.168,14.641,-17.079,14.483,-16.974,14.403,-16.881,14.208,-16.798,14.093,-16.792,14.004,-16.742,14.006,-16.667,14.036,-16.618,14.041,-16.64,14.007,-16.734,13.961,-16.767,13.905,-16.745,13.84,-16.648,13.771,-16.588,13.69,-16.562,13.587,-16.53,13.458,-16.441,13.353,-16.352,13.343,-16.135,13.448,-15.85,13.46,-15.57,13.5,-15.438,13.483,-15.427,13.468,-15.471,13.459,-15.618,13.46,-15.804,13.425,-15.986,13.409,-16.158,13.384,-16.188,13.326,-16.185,13.283,-16.272,13.294,-16.413,13.27,-16.556,13.303,-16.598,13.357,-16.615,13.435,-16.669,13.475,-16.75,13.425,-16.825,13.341,-16.769,13.148,-16.763,13.064,-16.757,12.98,-16.769,12.883,-16.759,12.702,-16.778,12.67,-16.768,12.628,-16.744,12.585,-16.701,12.603,-16.673,12.622,-16.638,12.685,-16.598,12.715,-16.549,12.664,-16.455,12.625,-16.443,12.609,-16.45,12.581,-16.488,12.582,-16.553,12.605,-16.678,12.56,-16.76,12.526,-16.785,12.473,-16.746,12.4,-16.712,12.355,-16.437,12.204,-16.312,12.243,-16.245,12.237,-16.255,12.206,-16.319,12.144,-16.328,12.052,-16.274,11.978,-16.138,11.917,-15.959,11.96,-15.92,11.938,-15.903,11.92,-15.942,11.787,-15.819,11.763,-15.651,11.818,-15.513,11.918,-15.435,11.944,-15.188,11.927,-15.112,11.97,-15.078,11.969,-15.072,11.947,-15.102,11.914,-15.133,11.907,-15.211,11.871,-15.416,11.872,-15.467,11.843,-15.5,11.778,-15.502,11.724,-15.413,11.615,-15.36,11.623,-15.317,11.669,-15.23,11.687,-15.122,11.662,-15.073,11.598,-15.164,11.581,-15.253,11.573,-15.429,11.499,-15.479,11.41,-15.449,11.39,-15.399,11.401,-15.355,11.396,-15.348,11.378,-15.395,11.334,-15.401,11.266,-15.393,11.217,-15.317,11.152,-15.263,11.161,-15.217,11.156,-15.222,11.031,-15.181,11.034,-15.097,11.14,-15.055,11.142,-15.094,11.011,-15.043,10.94,-15.051,10.835,-15.012,10.804,-14.975,10.803,-14.925,10.945,-14.887,10.968,-14.837,10.963,-14.776,10.932,-14.757,10.862,-14.693,10.741,-14.677,10.689,-14.594,10.767,-14.587,10.735,-14.614,10.618,-14.61,10.55,-14.427,10.248,-14.17,10.129,-14.086,10.127,-14.045,10.141,-14.03,10.115,-14.022,10.048,-13.955,9.969,-13.82,9.887,-13.754,9.87,-13.713,9.923,-13.69,9.928,-13.7,9.851,-13.659,9.776,-13.657,9.639,-13.691,9.536,-13.568,9.543,-13.436,9.42,-13.406,9.361,-13.396,9.314,-13.296,9.219,-13.269,9.171,-13.303,9.078,-13.293,9.049,-13.272,8.987,-13.154,8.898,-13.059,8.881,-13.071,8.856,-13.207,8.843,-13.226,8.766,-13.228,8.696,-13.182,8.577,-13.122,8.589,-13.088,8.626,-12.953,8.615,-12.904,8.656,-12.894,8.63,-12.913,8.582,-12.994,8.526,-13.085,8.425,-13.158,8.442,-13.203,8.484,-13.261,8.488,-13.273,8.43,-13.202,8.336,-13.149,8.215,-13.021,8.201,-12.957,8.145,-12.925,8.055,-12.881,7.857,-12.851,7.819,-12.782,7.791,-12.698,7.716,-12.57,7.701,-12.51,7.753,-12.48,7.753,-12.51,7.666,-12.433,7.545,-12.481,7.442,-12.486,7.386,-12.347,7.342,-11.929,7.184,-11.733,7.089,-11.548,6.947,-11.508,6.907,-11.292,6.688,-11.005,6.557,-10.849,6.465,-10.786,6.31,-10.708,6.258,-10.597,6.211,-10.418,6.167,-10.276,6.078,-9.654,5.519,-9.375,5.241,-9.132,5.055,-8.259,4.59,-7.998,4.509,-7.66,4.367,-7.545,4.351,-7.426,4.376,-7.231,4.486,-7.058,4.545,-6.923,4.638,-6.845,4.671,-6.548,4.762,-6.062,4.953,-5.914,5.011,-5.565,5.089,-5.062,5.131,-5.105,5.162,-5.266,5.16,-5.368,5.151,-5.335,5.192,-5.282,5.21,-5.024,5.204,-4.97,5.148,-4.9,5.138,-4.662,5.173,-4.037,5.23,-4.062,5.257,-4.115,5.262,-4.609,5.236,-4.553,5.28,-4.357,5.301,-4.12,5.31,-3.984,5.293,-3.871,5.221,-3.348,5.131,-3.312,5.161,-3.238,5.335,-3.2,5.354,-3.151,5.348,-3.169,5.203,-3.064,5.158,-3.026,5.151,-3.019,5.131,-3.087,5.128,-3.215,5.147,-3.246,5.114,-3.114,5.089,-3.082,5.082,-2.965,5.046,-2.723,5.014,-2.399,4.929,-2.266,4.874,-2.09,4.764,-2.002,4.762,-1.777,4.88,-1.638,4.981,-1.502,5.038,-1.064,5.183,-0.798,5.227,-0.669,5.319,-0.485,5.394,-0.349,5.501,-0.127,5.568,0.26,5.757,0.672,5.76,0.749,5.76,0.95,5.81,1.008,5.906,1.05,5.994,1.106,6.051,1.187,6.089,1.311,6.147,1.623,6.217,1.818,6.261,2.287,6.328,2.706,6.369,2.772,6.376,3.336,6.397,3.43,6.525,3.503,6.531,3.717,6.598,3.752,6.584,3.546,6.477,3.49,6.457,3.451,6.427,3.487,6.409,4.126,6.411,4.431,6.349,4.634,6.217,4.861,6.026,5.042,5.798,5.093,5.767,5.106,5.728,5.112,5.642,5.173,5.603,5.276,5.642,5.305,5.694,5.327,5.708,5.325,5.648,5.35,5.623,5.418,5.625,5.457,5.612,5.394,5.575,5.289,5.577,5.216,5.572,5.199,5.534,5.232,5.484,5.386,5.402,5.55,5.474,5.532,5.426,5.501,5.379,5.439,5.365,5.368,5.338,5.364,5.259,5.37,5.195,5.388,5.174,5.476,5.154,5.452,5.127,5.403,5.142,5.383,5.129,5.448,4.946,5.493,4.839,5.554,4.733,5.588,4.647,5.799,4.456,5.906,4.388,5.971,4.339,6.077,4.291,6.173,4.277,6.206,4.292,6.215,4.385,6.271,4.432,6.275,4.372,6.256,4.334,6.264,4.309,6.3,4.304,6.462,4.333,6.5,4.332,6.555,4.341,6.58,4.476,6.602,4.455,6.617,4.376,6.633,4.34,6.715,4.342,6.757,4.344,6.86,4.373,6.793,4.469,6.792,4.593,6.786,4.652,6.768,4.725,6.788,4.725,6.825,4.645,6.839,4.523,6.868,4.441,6.923,4.391,7.013,4.397,7.155,4.514,7.164,4.616,7.087,4.686,7.077,4.716,7.144,4.684,7.207,4.612,7.284,4.548,7.46,4.555,7.509,4.595,7.517,4.645,7.531,4.655,7.566,4.561,7.644,4.525,7.801,4.522,8.029,4.555,8.293,4.558,8.328,4.656,8.234,4.907,8.253,4.924,8.342,4.825,8.394,4.814,8.431,4.746,8.515,4.725,8.544,4.758,8.556,4.755,8.571,4.752,8.533,4.606,8.54,4.572,8.574,4.526,8.656,4.516,8.69,4.55,8.66,4.671,8.708,4.646,8.762,4.58,8.807,4.573,8.856,4.579,8.889,4.573,8.918,4.554,8.903,4.435,8.914,4.358,8.932,4.29,8.977,4.23,9,4.092,9.114,4.041,9.249,3.998,9.297,3.973,9.311,3.94,9.362,3.926,9.425,3.922,9.462,3.943,9.501,4.001,9.484,4.066,9.512,4.061,9.551,4.028,9.6,4.027,9.67,4.077,9.689,4.056,9.649,4.008,9.64,3.965,9.736,3.88,9.74,3.853,9.628,3.87,9.593,3.814,9.556,3.798,9.616,3.696,9.642,3.612,9.766,3.624,9.672,3.538,9.876,3.31,9.915,3.24,9.948,3.079,9.885,2.917,9.868,2.735,9.822,2.539,9.801,2.304,9.78,2.068,9.807,1.927,9.719,1.789,9.648,1.618,9.632,1.566,9.584,1.54,9.494,1.435,9.434,1.296,9.386,1.139,9.445,1.121,9.51,1.115,9.599,1.054,9.591,1.032,9.575,0.991,9.626,0.779,9.625,0.632,9.618,0.577,9.601,0.568,9.557,0.594,9.539,0.659,9.495,0.665,9.33,0.611,9.325,0.552,9.399,0.487,9.47,0.362,9.546,0.296,9.777,0.192,9.944,0.22,10.001,0.195,9.813,0.126,9.797,0.044,9.738,0.085,9.574,0.149,9.468,0.16,9.411,0.2,9.386,0.246,9.376,0.307,9.355,0.344,9.302,0.289,9.325,0.116,9.339,-0.058,9.297,-0.351,9.137,-0.573,9.082,-0.624,9.038,-0.637,8.995,-0.635,8.946,-0.689,8.821,-0.708,8.757,-0.615,8.703,-0.591,8.844,-0.914,8.877,-0.946,8.909,-1.025,8.942,-1.071,9.065,-1.298,9.204,-1.382,9.26,-1.374,9.297,-1.361,9.318,-1.333,9.347,-1.325,9.28,-1.482,9.296,-1.515,9.331,-1.535,9.397,-1.53,9.448,-1.509,9.501,-1.555,9.523,-1.598,9.406,-1.635,9.357,-1.638,9.319,-1.632,9.036,-1.309,9.053,-1.379,9.158,-1.528,9.258,-1.726,9.248,-1.779,9.266,-1.825,9.342,-1.829,9.483,-1.895,9.495,-1.935,9.483,-1.962,9.342,-1.894,9.299,-1.903,9.371,-1.975,9.402,-2.028,9.533,-2.164,9.574,-2.23,9.591,-2.293,9.625,-2.367,9.676,-2.416,9.769,-2.413,9.861,-2.443,9.959,-2.49,10.062,-2.55,10.034,-2.576,10.002,-2.588,9.764,-2.474,9.722,-2.468,9.759,-2.519,10.006,-2.748,10.348,-3.013,10.585,-3.278,10.641,-3.398,10.849,-3.561,10.947,-3.662,11.032,-3.826,11.13,-3.916,11.364,-4.131,11.394,-4.2,11.668,-4.434,11.778,-4.566,11.781,-4.677,11.801,-4.705,11.821,-4.755,11.893,-4.866,11.967,-4.954,12.003,-4.982,12.018,-5.004,12.04,-5.035,12.111,-5.197,12.177,-5.325,12.207,-5.468,12.18,-5.539,12.155,-5.633,12.199,-5.732,12.214,-5.759,12.24,-5.807,12.315,-5.895,12.412,-5.986,12.453,-6,12.515,-6.004,12.681,-5.961,12.792,-5.878,12.861,-5.854,13.003,-5.836,13.068,-5.865,13.01,-5.908,12.791,-6.004,12.554,-6.046,12.38,-6.084,12.303,-6.093,12.283,-6.124,12.334,-6.187,12.402,-6.353,12.521,-6.59,12.823,-6.955,12.862,-7.232,13.091,-7.78,13.379,-8.37,13.366,-8.469,13.368,-8.555,13.378,-8.625,13.359,-8.687,13.054,-9.007,13.047,-8.975,13.077,-8.934,13.093,-8.9,13.047,-8.922,12.999,-8.991,12.999,-9.048,13.076,-9.23,13.156,-9.39,13.197,-9.551,13.209,-9.703,13.288,-9.827,13.332,-9.999,13.495,-10.257,13.539,-10.421,13.633,-10.512,13.721,-10.634,13.739,-10.757,13.834,-10.93,13.847,-11.054,13.784,-11.488,13.785,-11.813,13.686,-12.124,13.598,-12.286,13.417,-12.52,13.163,-12.652,12.983,-12.776,12.898,-13.028,12.55,-13.438,12.504,-13.755,12.379,-14.039,12.28,-14.637,12.073,-15.248,12.016,-15.514,11.968,-15.634,11.9,-15.72,11.85,-15.768,11.751,-15.832,11.769,-15.915,11.797,-15.986,11.82,-16.504,11.819,-16.704,11.78,-16.871,11.743,-17.249,11.722,-17.467,11.733,-17.751,11.776,-18.002,11.951,-18.271,12.041,-18.471,12.096,-18.541,12.329,-18.751,12.458,-18.927,13.042,-20.028,13.168,-20.185,13.284,-20.524,13.451,-20.917,13.839,-21.473,13.888,-21.607,13.973,-21.768,14.322,-22.19,14.463,-22.449,14.526,-22.703,14.52,-22.805,14.496,-22.921,14.459,-22.908,14.438,-22.881,14.403,-22.968,14.424,-23.079,14.474,-23.281,14.472,-23.477,14.497,-23.643,14.483,-24.05,14.502,-24.202,14.628,-24.548,14.768,-24.788,14.837,-25.033,14.819,-25.246,14.823,-25.359,14.864,-25.534,14.845,-25.726,14.931,-25.958,14.968,-26.318,15.097,-26.426,15.139,-26.508,15.163,-26.6,15.124,-26.668,15.133,-26.788,15.216,-26.995,15.288,-27.275,15.342,-27.387,15.719,-27.966,15.891,-28.153,16.007,-28.232,16.335,-28.537,16.448,-28.618,16.481,-28.642,16.739,-29.009,16.95,-29.403,17.189,-30.1,17.347,-30.445,17.677,-31.019,17.939,-31.383,18.164,-31.655,18.211,-31.742,18.311,-32.122,18.33,-32.27,18.325,-32.505,18.251,-32.652,18.125,-32.749,18.037,-32.775,17.965,-32.709,17.895,-32.75,17.851,-32.827,17.878,-32.962,17.958,-33.046,17.993,-33.152,18.075,-33.207,18.156,-33.359,18.261,-33.422,18.309,-33.514,18.433,-33.717,18.456,-33.796,18.465,-33.888,18.354,-33.939,18.333,-34.074,18.352,-34.188,18.41,-34.296,18.462,-34.347,18.462,-34.168,18.5,-34.109,18.534,-34.086,18.605,-34.077,18.709,-34.072,18.752,-34.083,18.809,-34.108,18.826,-34.188,18.831,-34.254,18.825,-34.296,18.831,-34.364,18.902,-34.361,18.952,-34.344,19.098,-34.35,19.149,-34.417,19.245,-34.412,19.279,-34.437,19.331,-34.492,19.323,-34.571,19.298,-34.615,19.392,-34.606,19.635,-34.753,19.85,-34.757,19.926,-34.775,20.021,-34.786,20.435,-34.509,20.53,-34.463,20.775,-34.44,20.882,-34.387,20.99,-34.367,21.06,-34.365,21.249,-34.407,21.35,-34.408,21.553,-34.373,21.789,-34.373,22.246,-34.069,22.414,-34.054,22.554,-34.01,22.736,-34.01,22.926,-34.063,23.268,-34.081,23.35,-34.069,23.586,-33.985,23.698,-33.993,24.183,-34.062,24.596,-34.175,24.827,-34.169,24.906,-34.06,25.003,-33.974,25.17,-33.961,25.477,-34.028,25.574,-34.035,25.638,-34.011,25.652,-33.85,25.806,-33.737,25.99,-33.711,26.429,-33.76,26.614,-33.707,27.077,-33.521,27.364,-33.361,27.762,-33.096,27.861,-33.054,28.214,-32.769,28.449,-32.625,28.856,-32.294,29.128,-32.003,29.483,-31.675,29.735,-31.47,29.83,-31.424,29.971,-31.322,30.289,-30.97,30.472,-30.715,30.664,-30.434,30.878,-30.071,31.023,-29.901,31.17,-29.591,31.335,-29.378,31.778,-28.937,31.892,-28.912,31.955,-28.884,32.027,-28.84,32.286,-28.621,32.375,-28.498,32.535,-28.2,32.657,-27.607,32.706,-27.442,32.849,-27.08,32.886,-26.849,32.889,-26.83,32.934,-26.252,32.955,-26.084,32.916,-26.087,32.894,-26.13,32.849,-26.268,32.804,-26.241,32.77,-26.203,32.704,-26.158,32.647,-26.092,32.59,-26.004,32.656,-25.902,32.723,-25.821,32.792,-25.644,32.961,-25.49,33.347,-25.261,33.53,-25.189,33.836,-25.068,34.607,-24.821,34.992,-24.651,35.156,-24.541,35.255,-24.43,35.438,-24.171,35.49,-24.066,35.542,-23.824,35.522,-23.785,35.485,-23.784,35.462,-23.851,35.399,-23.838,35.37,-23.798,35.377,-23.708,35.494,-23.185,35.575,-22.963,35.506,-22.772,35.49,-22.658,35.542,-22.377,35.54,-22.303,35.53,-22.248,35.505,-22.19,35.494,-22.125,35.456,-22.116,35.419,-22.178,35.401,-22.316,35.408,-22.403,35.383,-22.455,35.316,-22.397,35.326,-22.26,35.329,-22.037,35.273,-21.762,35.268,-21.651,35.128,-21.395,35.118,-21.195,34.982,-20.806,34.877,-20.671,34.765,-20.562,34.705,-20.473,34.698,-20.404,34.75,-20.091,34.745,-19.929,34.756,-19.822,34.713,-19.767,34.649,-19.701,34.721,-19.71,34.852,-19.821,34.891,-19.822,34.948,-19.813,35.365,-19.494,35.651,-19.164,35.854,-18.993,35.98,-18.912,36.125,-18.842,36.183,-18.871,36.236,-18.861,36.263,-18.72,36.327,-18.793,36.404,-18.77,36.412,-18.693,36.498,-18.576,36.54,-18.518,36.756,-18.307,36.9,-18.129,36.919,-18.08,36.939,-17.993,37,-17.935,37.051,-17.909,37.245,-17.74,37.512,-17.571,37.839,-17.393,38.048,-17.321,38.087,-17.276,38.145,-17.243,38.381,-17.17,38.633,-17.078,38.67,-17.05,38.713,-17.046,38.758,-17.055,38.885,-17.042,38.956,-17.005,39.084,-16.973,39.182,-16.842,39.242,-16.793,39.625,-16.579,39.765,-16.468,39.845,-16.436,39.791,-16.295,39.86,-16.252,39.984,-16.225,40.099,-16.065,40.109,-16.025,40.109,-15.979,40.208,-15.867,40.314,-15.764,40.559,-15.473,40.651,-15.261,40.653,-15.193,40.618,-15.116,40.642,-15.082,40.694,-15.065,40.687,-15.012,40.701,-14.93,40.776,-14.842,40.835,-14.792,40.845,-14.719,40.821,-14.635,40.827,-14.569,40.812,-14.536,40.818,-14.468,40.775,-14.421,40.727,-14.421,40.646,-14.539,40.636,-14.452,40.64,-14.39,40.713,-14.291,40.716,-14.214,40.65,-14.199,40.603,-14.167,40.596,-14.123,40.591,-13.845,40.56,-13.62,40.558,-13.531,40.545,-13.463,40.583,-13.374,40.552,-13.294,40.57,-13.223,40.564,-13.115,40.573,-13.058,40.569,-12.985,40.437,-12.983,40.435,-12.936,40.448,-12.905,40.553,-12.825,40.572,-12.758,40.581,-12.636,40.548,-12.527,40.487,-12.492,40.523,-12.393,40.509,-12.313,40.501,-12.119,40.532,-12.005,40.51,-11.94,40.494,-11.844,40.433,-11.657,40.465,-11.449,40.403,-11.332,40.421,-11.266,40.491,-11.179,40.545,-11.066,40.527,-11.025,40.506,-10.998,40.516,-10.93,40.597,-10.831,40.487,-10.765,40.555,-10.716,40.612,-10.662,40.517,-10.567,40.464,-10.464,40.453,-10.443,40.436,-10.41,40.389,-10.354,40.216,-10.241,40.138,-10.203,40.084,-10.157,39.984,-10.16,39.945,-10.092,39.864,-10.022,39.725,-10,39.784,-9.915,39.775,-9.837,39.728,-9.725,39.697,-9.578,39.625,-9.409,39.641,-9.192,39.451,-8.943,39.48,-8.906,39.488,-8.862,39.377,-8.721,39.304,-8.444,39.309,-8.351,39.34,-8.243,39.441,-8.012,39.428,-7.813,39.33,-7.747,39.287,-7.788,39.288,-7.518,39.353,-7.341,39.433,-7.207,39.519,-7.124,39.546,-7.024,39.472,-6.879,39.287,-6.815,39.228,-6.685,39.125,-6.556,39.067,-6.499,38.981,-6.455,38.874,-6.331,38.855,-6.205,38.805,-6.07,38.819,-5.878,38.911,-5.626,38.978,-5.519,39.058,-5.232,39.088,-5.165,39.119,-5.065,39.123,-4.98,39.202,-4.776,39.222,-4.692,39.228,-4.666,39.288,-4.609,39.377,-4.625,39.491,-4.478,39.637,-4.153,39.658,-4.119,39.687,-4.068,39.732,-3.993,39.746,-3.955,39.761,-3.913,39.819,-3.786,39.861,-3.577,39.896,-3.536,39.937,-3.442,39.992,-3.351,40.115,-3.251,40.128,-3.173,40.195,-3.019,40.18,-2.819,40.222,-2.688,40.279,-2.629,40.404,-2.556,40.644,-2.539,40.813,-2.392,40.82,-2.336,40.898,-2.27,40.922,-2.194,40.906,-2.137,40.89,-2.024,40.917,-2.042,40.952,-2.056,40.971,-1.992,40.996,-1.951,41.059,-1.975,41.107,-1.982,41.267,-1.945,41.387,-1.867,41.533,-1.695,41.632,-1.579,41.732,-1.43,41.846,-1.203,41.888,-1.151,41.926,-1.056,41.98,-0.973,42.106,-0.856,42.219,-0.738,42.399,-0.51,42.466,-0.457,42.561,-0.321,42.634,-0.251,42.712,-0.176,43.468,0.622,43.718,0.858,44.033,1.106,44.333,1.391,44.92,1.81,45.826,2.31,46.051,2.475,46.879,3.286,47.511,3.968,47.975,4.497,48.234,4.953,48.649,5.494,49.049,6.174,49.093,6.408,49.235,6.777,49.349,6.991,49.57,7.297,49.671,7.47,49.761,7.66,49.852,7.963,50.103,8.2,50.286,8.509,50.321,8.62,50.43,8.845,50.638,9.109,50.685,9.241,50.825,9.428,50.833,9.71,50.874,9.924,50.898,10.253,50.93,10.336,51.036,10.385,51.209,10.431,51.268,10.403,51.385,10.387,51.39,10.423,51.369,10.475,51.296,10.499,51.193,10.555,51.186,10.53,51.188,10.48,51.063,10.434,51.032,10.445,51.051,10.472,51.094,10.489,51.105,10.536,51.131,10.596,51.141,10.657,51.122,11.077,51.084,11.336,51.136,11.505,51.218,11.658,51.232,11.745,51.255,11.831,51.191,11.842,50.792,11.984,50.636,11.944,50.528,11.823,50.466,11.728,50.11,11.529,49.642,11.451,49.388,11.343,49.062,11.271,48.939,11.258,48.903,11.255,48.674,11.323,48.573,11.321,48.439,11.29,48.019,11.139,47.713,11.112,47.474,11.175,47.405,11.174,47.23,11.1,46.973,10.925,46.565,10.746,46.46,10.734,46.254,10.781,46.025,10.794,45.817,10.836,45.696,10.804,45.338,10.65,44.943,10.437,44.387,10.43,44.279,10.472,44.158,10.551,43.853,10.784,43.631,11.035,43.441,11.346,43.246,11.5,43.162,11.566,43.043,11.588,42.912,11.587,42.79,11.562,42.653,11.51,42.584,11.497,42.54,11.504,42.522,11.572,42.64,11.56,42.799,11.739,43.048,11.829,43.272,11.97,43.337,12.027,43.38,12.091,43.41,12.19,43.354,12.367,43.299,12.464,43.131,12.66,43.117,12.709,43.083,12.825,42.999,12.9,42.97,12.808,42.796,12.864,42.734,13.019,42.523,13.221,42.399,13.213,42.346,13.398,42.245,13.588,41.658,13.983,41.48,14.244,41.176,14.62,40.799,14.743,40.634,14.883,40.546,14.934,40.437,14.964,40.305,14.974,40.204,15.014,40.084,15.152,40.058,15.217,40.041,15.335,39.978,15.393,39.864,15.47,39.813,15.414,39.79,15.319,39.816,15.245,39.819,15.201,39.786,15.125,39.721,15.214,39.631,15.453,39.579,15.523,39.507,15.532,39.422,15.787,39.299,15.921,39.223,16.194,39.143,16.729,39.034,17.086,38.912,17.427,38.609,18.005,38.574,18.073,38.333,18.219,38.283,18.287,38.252,18.264,38.202,18.249,38.128,18.333,38.074,18.41,37.922,18.556,37.73,18.694,37.599,18.717,37.532,18.753,37.471,18.82,37.362,19.092,37.248,19.582,37.263,19.792,37.193,20.121,37.188,20.395,37.228,20.557,37.173,20.732,37.157,20.895,37.141,20.982,37.151,21.104,37.217,21.078,37.257,21.039,37.263,21.073,37.259,21.109,37.212,21.186,37.081,21.326,36.927,21.587,36.883,21.769,36.871,21.997,36.87,22.016,36.83,22.098,36.415,22.394,36.23,22.629,35.913,22.74,35.846,22.786,35.797,22.849,35.698,22.946,35.564,23.271,35.523,23.443,35.504,23.779,35.515,23.843,35.541,23.921,35.594,23.943,35.632,23.95,35.784,23.938,35.625,24.066,35.478,24.155,35.397,24.27,35.194,24.475,34.853,25.14,34.679,25.443,34.565,25.691,34.329,26.024,34.05,26.551,33.959,26.649,33.893,27.049,33.849,27.185,33.802,27.268,33.697,27.341,33.657,27.431,33.55,27.607,33.559,27.701,33.547,27.898,33.495,27.974,33.372,28.051,33.202,28.208,33.023,28.442,32.898,28.565,32.857,28.631,32.829,28.703,32.784,28.787,32.659,28.928,32.632,28.992,32.638,29.182,32.599,29.322,32.565,29.386,32.397,29.534,32.36,29.631,32.409,29.749,32.489,29.852,32.473,29.925,32.566,29.974,32.647,29.798,32.721,29.522,32.767,29.45,32.812,29.4,32.871,29.286,33.076,29.073,33.13,28.978,33.204,28.778,33.202,28.696,33.248,28.568,33.416,28.39,33.594,28.256,33.76,28.048,34.045,27.829,34.22,27.764,34.319,27.889,34.4,28.016,34.427,28.106,34.446,28.357,34.617,28.758,34.736,29.271,34.849,29.432,34.904,29.477,34.973,29.555,34.982,29.484,34.951,29.354,34.799,28.721,34.78,28.507,34.683,28.264,34.616,28.148,34.625,28.065,34.722,28.131,34.828,28.109,35.078,28.087,35.18,28.035,35.424,27.734,35.581,27.432,35.763,27.259,35.852,27.07,36.032,26.881,36.094,26.766,36.25,26.595,36.519,26.105,36.675,26.039,36.703,25.903,36.763,25.751,36.86,25.692,36.921,25.641,37.149,25.291,37.218,25.151,37.243,25.073,37.266,24.96,37.22,24.873,37.181,24.82,37.338,24.616,37.431,24.459,37.543,24.292,37.638,24.278,37.713,24.274,37.821,24.188,37.92,24.185,37.978,24.125,38.099,24.058,38.289,23.911,38.464,23.712,38.542,23.558,38.706,23.306,38.757,23.194,38.797,23.049,38.836,22.989,38.941,22.882,38.883,22.882,38.939,22.805,39.007,22.77,39.001,22.699,39.062,22.592,39.096,22.393,39.07,22.294,39.034,22.203,39.021,22.033,38.988,21.882,39.03,21.776,39.091,21.664,39.147,21.519,39.151,21.433,39.094,21.31,39.276,20.974,39.491,20.737,39.614,20.518,39.728,20.39,39.884,20.293,40.081,20.266,40.482,19.993,40.616,19.822,40.759,19.755,40.777,19.717,40.792,19.646,40.848,19.555,40.913,19.49,41.116,19.082,41.144,18.989,41.191,18.871,41.221,18.765,41.229,18.678,41.432,18.452,41.508,18.256,41.658,18.008,41.75,17.886,42.052,17.669,42.294,17.435,42.332,17.257,42.383,17.122,42.475,17.05,42.544,16.96,42.553,16.868,42.647,16.801,42.699,16.737,42.726,16.653,42.731,16.57,42.79,16.452,42.799,16.372,42.84,16.032,42.717,15.655,42.8,15.372,42.799,15.326,42.788,15.266,42.736,15.294,42.698,15.326,42.658,15.233,42.856,15.133,42.897,15.006,42.936,14.939,42.937,14.898,42.913,14.863,42.922,14.817,42.947,14.773,43.021,14.555,43.019,14.521,43.006,14.483,43.045,14.342,43.093,14.204,43.089,14.011,43.234,13.859,43.282,13.693,43.283,13.64,43.232,13.267,43.475,12.839,43.488,12.699,43.634,12.744,43.835,12.674,43.93,12.617,44.006,12.608,44.112,12.639,44.26,12.645,44.358,12.669,44.618,12.817,44.755,12.764,44.89,12.784,45.039,12.816,45.11,12.939,45.164,12.998,45.394,13.067,45.534,13.233,45.657,13.339,45.92,13.394,46.203,13.424,46.502,13.416,46.663,13.433,46.789,13.466,46.976,13.547,47.243,13.609,47.408,13.662,47.633,13.858,47.855,13.957,47.916,14.013,47.99,14.048,48.278,13.998,48.449,14.006,48.594,14.046,48.668,14.05,48.78,14.124,48.929,14.267,49.005,14.355,49.048,14.456,49.103,14.5,49.35,14.638,49.549,14.722,49.906,14.828,50.167,14.851,50.339,14.927,50.527,15.038,51.015,15.141,51.322,15.226,51.604,15.337,51.682,15.379,51.749,15.44,51.831,15.459,51.966,15.536,52.087,15.586,52.217,15.656,52.222,15.761,52.174,15.957,52.237,16.171,52.328,16.294,52.448,16.391,52.581,16.47,53.086,16.648,53.298,16.723,53.61,16.76,53.775,16.856,53.954,16.918,54.068,17.006,54.377,17.034,54.567,17.031,54.665,17.009,54.772,16.965,55.064,17.039,55.174,17.158,55.275,17.321,55.296,17.382,55.281,17.446,55.238,17.505,55.255,17.586,55.479,17.843,55.614,17.886,55.998,17.935,56.27,17.951,56.383,17.988,56.551,18.166,56.655,18.587,56.826,18.754,56.957,18.828,57.177,18.903,57.428,18.944,57.676,18.958,57.738,18.977,57.812,19.017,57.79,19.146,57.764,19.253,57.761,19.432,57.715,19.607,57.714,19.678,57.741,19.804,57.802,19.955,57.844,20.118,57.862,20.244,57.947,20.344,58.103,20.57,58.169,20.59,58.245,20.599,58.232,20.507,58.209,20.424,58.266,20.395,58.349,20.387,58.474,20.407,58.534,20.504,58.69,20.807,58.896,21.113,59.069,21.289,59.304,21.435,59.371,21.499,59.518,21.782,59.653,21.951,59.681,22.054,59.8,22.22,59.824,22.305,59.838,22.421,59.823,22.509,59.696,22.546,59.535,22.579,59.429,22.661,59.311,22.793,59.195,22.972,59.03,23.131,58.983,23.235,58.912,23.334,58.83,23.397,58.773,23.517,58.578,23.643,58.5,23.646,58.393,23.618,58.325,23.624,58.12,23.717,57.825,23.759,57.611,23.804,57.22,23.923,57.123,23.981,56.913,24.15,56.774,24.335,56.641,24.47,56.49,24.716,56.388,24.979,56.373,25.018,56.363,25.569,56.298,25.651,56.307,25.709,56.329,25.752,56.374,25.805,56.416,26.109,56.418,26.208,56.43,26.327,56.413,26.351,56.379,26.356,56.346,26.314,56.306,26.235,56.228,26.22,56.197,26.229,56.164,26.207,56.08,26.063,56.075,26.053,56.025,25.916,55.941,25.794,55.523,25.498,55.433,25.394,55.322,25.3,55.304,25.237,55.098,25.042,54.747,24.81,54.659,24.716,54.624,24.621,54.58,24.564,54.535,24.531,54.499,24.463,54.458,24.358,54.397,24.278,54.304,24.254,54.148,24.171,53.893,24.077,53.802,24.069,53.33,24.098,53.026,24.147,52.648,24.155,52.511,24.113,52.251,23.995,52.119,23.971,51.906,23.985,51.843,24.011,51.792,24.075,51.768,24.254,51.735,24.263,51.665,24.25,51.623,24.301,51.605,24.338,51.568,24.286,51.535,24.286,51.477,24.308,51.395,24.319,51.31,24.34,51.37,24.477,51.418,24.531,51.411,24.571,51.338,24.564,51.268,24.607,51.396,24.645,51.428,24.668,51.533,24.891,51.587,24.965,51.609,25.053,51.602,25.148,51.561,25.284,51.52,25.39,51.51,25.452,51.485,25.525,51.527,25.682,51.572,25.781,51.543,25.902,51.389,26.011,51.262,26.153,51.108,26.081,51.003,25.981,50.904,25.724,50.869,25.613,50.803,25.497,50.763,25.445,50.755,25.399,50.777,25.177,50.847,24.889,50.836,24.85,50.804,24.789,50.726,24.869,50.667,24.964,50.558,25.087,50.508,25.307,50.455,25.425,50.281,25.566,50.239,25.623,50.19,25.756,50.13,25.847,50.081,25.961,50.032,26.111,50.054,26.123,50.096,26.119,50.135,26.101,50.155,26.101,50.214,26.308,50.185,26.405,50.111,26.456,50.027,26.527,50.011,26.609,50.008,26.679,50.026,26.699,50.087,26.676,50.135,26.66,50.15,26.663,49.986,26.829,49.717,26.956,49.538,27.152,49.405,27.181,49.282,27.31,49.175,27.438,49.238,27.493,49.158,27.528,49.087,27.549,48.906,27.629,48.797,27.724,48.807,27.765,48.833,27.801,48.809,27.896,48.774,27.959,48.626,28.133,48.523,28.355,48.499,28.449,48.442,28.543,48.39,28.632,48.371,28.692,48.339,28.763,48.253,28.901,48.184,28.979,48.1,29.211,48.086,29.275,48.051,29.355,47.998,29.386,47.935,29.367,47.845,29.366,47.723,29.393,47.725,29.417,47.817,29.487,47.97,29.617,48.048,29.598,48.089,29.579,48.143,29.572,48.136,29.618,48.077,29.716,48.006,29.836,47.974,29.946,47.979,29.983,47.983,30.011,48.073,30.043,48.142,30.041,48.355,29.957,48.454,29.938,48.546,29.962,48.596,29.975,48.671,30.028,48.832,30.035,48.87,30.062,48.919,30.121,48.909,30.241,48.891,30.328,48.917,30.397,49.037,30.45,49.002,30.507,49.13,30.509,49.225,30.472,49.247,30.413,49.19,30.375,49.096,30.407,49.049,30.397,49.002,30.374,49.028,30.333,49.054,30.307,49.43,30.13,49.555,30.029,49.983,30.209,50.072,30.199,50.129,30.048,50.169,29.921,50.23,29.873,50.387,29.679,50.544,29.548,50.65,29.42,50.668,29.34,50.646,29.212,50.675,29.147,50.796,29.117,50.876,29.063,50.876,29.004,50.843,28.928,50.867,28.87,51.021,28.782,51.062,28.726,51.094,28.512,51.128,28.435,51.276,28.219,51.279,28.131,51.519,27.91,51.589,27.864,51.666,27.845,51.842,27.848,52.031,27.824,52.192,27.717,52.476,27.617,52.603,27.493,52.638,27.392,52.692,27.323,52.983,27.142,53.342,27.004,53.455,26.943,53.507,26.852,53.706,26.726,53.823,26.708,54.069,26.732,54.247,26.697,54.522,26.589,54.645,26.509,54.759,26.505,54.896,26.557,55.155,26.725,55.294,26.786,55.424,26.771,55.519,26.83,55.592,26.932,55.65,26.978,55.941,27.038,56.118,27.143,56.284,27.191,56.356,27.2,56.728,27.128,56.813,27.09,56.91,26.995,56.982,26.905,57.036,26.801,57.072,26.68,57.104,26.371,57.201,26.159,57.206,26.037,57.261,25.919,57.335,25.792,57.733,25.725,57.796,25.653,57.937,25.692,58.022,25.641,58.203,25.592,58.314,25.581,58.531,25.592,58.798,25.555,59.046,25.417,59.227,25.428,59.456,25.481,59.616,25.403,59.818,25.401,59.897,25.362,60.025,25.384,60.4,25.312,60.511,25.437,60.588,25.414,60.615,25.33,60.664,25.282,61.109,25.184,61.243,25.142,61.412,25.102,61.49,25.154,61.533,25.196,61.744,25.138,61.908,25.131,62.089,25.155,62.152,25.207,62.199,25.225,62.249,25.197,62.315,25.135,62.391,25.153,62.445,25.197,62.572,25.255,62.665,25.265,63.015,25.225,63.17,25.255,63.286,25.228,63.491,25.211,63.496,25.298,63.557,25.353,63.721,25.386,63.936,25.343,63.987,25.351,64.059,25.403,64.125,25.374,64.152,25.333,64.544,25.237,64.594,25.206,64.659,25.184,64.777,25.307,65.061,25.311,65.406,25.374,65.68,25.355,65.884,25.42,66.235,25.464,66.328,25.466,66.403,25.447,66.468,25.445,66.407,25.485,66.356,25.507,66.131,25.493,66.162,25.554,66.219,25.59,66.324,25.602,66.429,25.575,66.534,25.484,66.57,25.379,66.699,25.226,66.71,25.111,66.682,24.929,66.703,24.861,67.101,24.792,67.171,24.756,67.289,24.368,67.304,24.263,67.309,24.175,67.365,24.092,67.428,24.065,67.454,24.04,67.477,24.018,67.504,23.94,67.563,23.882,67.646,23.92,67.65,23.867,67.668,23.811,67.819,23.828,67.86,23.903,67.951,23.829,68.001,23.826,68.037,23.848,68.068,23.818,68.116,23.753,68.149,23.797,68.165,23.857,68.192,23.729,68.235,23.597,68.343,23.617,68.425,23.706,68.497,23.748,68.642,23.808,68.777,23.852,68.627,23.754,68.454,23.629,68.417,23.571,68.529,23.364,68.641,23.19,68.817,23.054,69.236,22.849,69.665,22.759,69.74,22.775,69.85,22.856,70.118,22.947,70.192,22.966,70.251,22.971,70.339,22.94,70.368,22.973,70.396,23.03,70.435,23.077,70.489,23.09,70.509,23.04,70.513,23.002,70.44,22.97,70.328,22.816,70.177,22.573,70.084,22.554,70.006,22.548,69.819,22.452,69.728,22.465,69.655,22.404,69.549,22.408,69.277,22.285,69.239,22.3,69.194,22.336,69.131,22.416,69.052,22.437,68.983,22.385,68.97,22.29,69.009,22.197,69.192,21.992,69.385,21.84,69.542,21.679,69.748,21.506,70.034,21.179,70.127,21.095,70.485,20.84,70.719,20.74,70.88,20.715,71.025,20.739,71.396,20.87,71.571,20.971,72.015,21.156,72.077,21.224,72.254,21.531,72.257,21.661,72.21,21.728,72.171,21.774,72.103,21.795,72.037,21.823,72.076,21.863,72.094,21.92,72.162,21.985,72.244,22.028,72.274,22.09,72.306,22.189,72.243,22.245,72.183,22.27,72.333,22.27,72.456,22.248,72.59,22.278,72.702,22.264,72.809,22.233,72.709,22.207,72.628,22.2,72.553,22.16,72.522,21.976,72.617,21.962,72.7,21.972,72.644,21.938,72.592,21.878,72.543,21.697,72.84,21.687,72.979,21.705,73.113,21.75,73.022,21.7,72.811,21.62,72.718,21.551,72.613,21.462,72.668,21.456,72.735,21.471,72.687,21.436,72.624,21.372,72.692,21.178,72.752,21.129,72.814,21.117,72.824,21.084,72.841,20.952,72.879,20.829,72.894,20.673,72.881,20.563,72.709,20.078,72.668,19.831,72.676,19.798,72.697,19.757,72.727,19.578,72.799,19.52,72.756,19.451,72.764,19.413,72.788,19.363,72.987,19.277,72.812,19.299,72.795,19.252,72.803,19.219,72.803,19.079,72.835,18.976,72.901,19.015,72.972,19.153,73.006,19.021,72.977,18.927,72.899,18.779,72.871,18.683,72.875,18.643,72.917,18.576,72.943,18.366,72.972,18.259,72.994,18.098,73.047,17.907,73.156,17.622,73.149,17.527,73.239,17.199,73.338,16.46,73.454,16.152,73.476,16.054,73.608,15.871,73.68,15.709,73.733,15.657,73.833,15.659,73.772,15.573,73.814,15.539,73.852,15.482,73.932,15.397,73.801,15.397,73.884,15.306,73.949,15.075,74.041,14.949,74.089,14.902,74.223,14.709,74.28,14.65,74.335,14.575,74.382,14.495,74.397,14.407,74.467,14.217,74.467,14.169,74.499,14.046,74.608,13.85,74.671,13.668,74.682,13.584,74.682,13.507,74.771,13.077,74.803,12.977,74.868,12.845,74.946,12.565,75.197,12.058,75.23,12.023,75.315,11.958,75.423,11.812,75.525,11.703,75.646,11.468,75.724,11.362,75.845,11.058,75.923,10.784,76.096,10.402,76.123,10.327,76.201,10.201,76.193,10.164,76.196,10.086,76.223,10.024,76.249,10.018,76.346,9.922,76.459,9.536,76.42,9.52,76.376,9.54,76.372,9.707,76.343,9.827,76.285,9.91,76.242,9.927,76.292,9.676,76.325,9.452,76.403,9.237,76.419,9.208,76.452,9.189,76.472,9.161,76.483,9.091,76.553,8.903,76.617,8.847,76.967,8.407,77.066,8.316,77.301,8.145,77.518,8.078,77.587,8.13,77.77,8.19,78.06,8.385,78.126,8.511,78.136,8.663,78.192,8.891,78.275,8.99,78.421,9.105,78.98,9.269,79.213,9.256,79.411,9.192,79.356,9.252,79.275,9.285,79.107,9.309,79.02,9.333,78.953,9.394,78.919,9.453,78.94,9.566,78.996,9.683,79.258,10.035,79.254,10.175,79.315,10.257,79.391,10.306,79.532,10.33,79.589,10.312,79.667,10.3,79.757,10.304,79.838,10.323,79.85,10.769,79.849,11.197,79.835,11.269,79.799,11.339,79.693,11.313,79.749,11.371,79.793,11.447,79.754,11.575,79.771,11.69,79.858,11.989,79.982,12.235,80.038,12.296,80.143,12.452,80.229,12.69,80.342,13.361,80.29,13.437,80.114,13.529,80.062,13.606,80.156,13.714,80.233,13.606,80.266,13.521,80.307,13.485,80.246,13.686,80.244,13.773,80.224,13.858,80.144,14.059,80.112,14.212,80.136,14.287,80.17,14.349,80.179,14.478,80.165,14.578,80.099,14.798,80.053,15.074,80.101,15.324,80.293,15.711,80.385,15.793,80.647,15.895,80.708,15.888,80.782,15.867,80.826,15.766,80.865,15.782,80.918,15.76,80.979,15.758,80.993,15.809,81.03,15.881,81.132,15.962,81.239,16.264,81.286,16.337,81.402,16.365,81.712,16.334,81.762,16.329,82.142,16.485,82.259,16.56,82.327,16.664,82.339,16.707,82.36,16.783,82.35,16.825,82.307,16.879,82.282,16.936,82.287,16.978,82.36,17.096,82.593,17.274,82.977,17.462,83.198,17.609,83.388,17.787,83.572,18.004,83.654,18.07,84.104,18.293,84.182,18.401,84.463,18.69,84.609,18.884,84.691,18.965,84.75,19.05,84.771,19.125,85.226,19.508,85.442,19.627,85.437,19.657,85.371,19.679,85.229,19.601,85.181,19.595,85.163,19.621,85.249,19.758,85.46,19.896,85.504,19.888,85.555,19.867,85.56,19.753,85.511,19.727,85.497,19.697,85.575,19.693,85.853,19.792,86.216,19.896,86.279,19.919,86.303,19.945,86.312,19.988,86.245,20.053,86.294,20.054,86.377,20.007,86.446,20.089,86.499,20.172,86.75,20.313,86.769,20.356,86.763,20.419,86.836,20.534,86.925,20.62,86.975,20.7,86.939,20.745,86.896,20.966,86.842,21.106,86.86,21.237,86.954,21.365,87.101,21.501,87.201,21.545,87.678,21.654,87.824,21.727,87.948,21.825,88.051,22.001,88.104,22.047,88.159,22.122,88.083,22.183,88.011,22.213,87.962,22.255,87.941,22.374,87.994,22.266,88.087,22.218,88.196,22.14,88.181,22.033,88.099,21.794,88.057,21.694,88.122,21.636,88.254,21.622,88.279,21.697,88.288,21.758,88.305,21.723,88.446,21.614,88.585,21.66,88.6,21.714,88.567,21.832,88.642,22.122,88.66,22.067,88.708,22.056,88.73,22.036,88.74,22.005,88.691,21.733,88.695,21.662,88.713,21.622,88.745,21.584,88.834,21.661,88.858,21.745,88.907,21.653,88.967,21.641,89.052,21.654,89.042,21.759,89.02,21.834,88.949,21.938,89.028,21.937,89.051,22.093,89.082,22.015,89.094,21.873,89.167,21.784,89.234,21.722,89.279,21.707,89.354,21.721,89.452,21.821,89.501,21.914,89.503,22.032,89.469,22.213,89.483,22.276,89.547,21.984,89.567,21.861,89.569,21.767,89.628,21.814,89.668,21.878,89.757,21.919,89.812,21.983,89.853,22.091,89.866,22.173,89.853,22.289,89.882,22.388,89.985,22.466,89.894,22.308,89.894,22.203,89.918,22.116,89.954,22.023,90.069,22.098,90.143,22.138,90.21,22.157,90.088,22.017,90.07,21.96,90.071,21.887,90.131,21.847,90.159,21.817,90.231,21.83,90.288,21.899,90.356,22.048,90.494,22.179,90.553,22.218,90.589,22.258,90.616,22.362,90.596,22.436,90.532,22.539,90.487,22.589,90.498,22.635,90.481,22.685,90.435,22.752,90.437,22.828,90.462,22.882,90.552,22.905,90.478,22.987,90.466,23.054,90.528,23.085,90.595,23.134,90.599,23.204,90.591,23.266,90.523,23.346,90.392,23.367,90.269,23.456,90.408,23.432,90.556,23.422,90.568,23.474,90.562,23.537,90.573,23.578,90.604,23.591,90.616,23.532,90.616,23.442,90.656,23.273,90.634,23.094,90.656,23.025,90.827,22.721,90.946,22.597,91.151,22.614,91.216,22.642,91.314,22.735,91.41,22.797,91.48,22.885,91.482,22.797,91.53,22.708,91.693,22.505,91.734,22.407,91.797,22.297,91.845,22.343,91.863,22.35,91.858,22.317,91.825,22.229,91.85,22.157,91.913,21.883,92.008,21.685,92.011,21.516,92.056,21.175,92.195,20.984,92.248,20.884,92.308,20.79,92.324,20.792,92.378,20.718,92.608,20.47,92.723,20.296,92.733,20.453,92.709,20.564,92.736,20.563,92.787,20.469,92.851,20.415,92.891,20.34,92.872,20.302,92.844,20.283,92.791,20.211,92.828,20.178,92.882,20.152,92.991,20.288,93.019,20.346,93.035,20.406,93.067,20.378,93.015,20.185,93.068,20.189,93.096,20.181,93.04,20.13,93.002,20.075,93.129,19.858,93.191,19.851,93.199,19.898,93.157,20.041,93.25,20.07,93.362,20.058,93.41,20.038,93.439,20.009,93.582,19.91,93.707,19.912,93.66,19.854,93.612,19.776,93.669,19.732,93.74,19.697,93.761,19.648,93.77,19.61,93.84,19.534,93.888,19.504,93.961,19.482,93.998,19.441,93.962,19.329,93.886,19.272,93.825,19.238,93.728,19.267,93.579,19.401,93.531,19.398,93.493,19.369,93.598,19.188,93.705,19.027,93.8,18.961,93.929,18.9,93.961,18.958,93.968,18.995,93.941,19.146,94.002,19.182,94.022,19.269,94.045,19.287,94.039,19.146,94.07,18.893,94.091,18.849,94.246,18.741,94.171,18.732,94.252,18.609,94.266,18.507,94.431,18.202,94.494,17.825,94.56,17.699,94.589,17.569,94.564,17.309,94.494,17.167,94.473,17.135,94.452,16.954,94.4,16.868,94.353,16.64,94.327,16.572,94.271,16.517,94.214,16.127,94.224,16.016,94.299,16.008,94.442,16.094,94.496,16.186,94.588,16.289,94.638,16.309,94.665,16.336,94.679,16.426,94.703,16.512,94.717,16.452,94.72,16.399,94.677,16.242,94.681,16.133,94.651,16.065,94.656,15.989,94.662,15.904,94.798,15.971,94.848,16.033,94.86,16.102,94.893,16.183,94.898,16.141,94.882,16.088,94.892,16.038,94.891,15.979,94.943,15.818,95.078,15.839,95.177,15.826,95.226,15.877,95.333,16.033,95.347,16.098,95.365,15.985,95.308,15.88,95.301,15.756,95.348,15.729,95.39,15.723,95.556,15.838,95.679,15.977,95.711,16.073,95.763,16.169,96.012,16.254,96.032,16.285,96.043,16.34,96.081,16.353,96.135,16.343,96.293,16.41,96.324,16.444,96.237,16.567,96.238,16.631,96.189,16.768,96.22,16.781,96.249,16.765,96.262,16.659,96.282,16.596,96.364,16.521,96.431,16.505,96.507,16.514,96.622,16.564,96.765,16.71,96.811,16.778,96.858,16.921,96.909,17.031,96.851,17.203,96.91,17.305,96.878,17.342,96.851,17.401,96.97,17.317,97.075,17.207,97.1,17.165,97.2,17.095,97.178,17.062,97.212,16.893,97.267,16.743,97.331,16.672,97.376,16.523,97.505,16.525,97.62,16.537,97.668,16.552,97.726,16.569,97.665,16.52,97.634,16.458,97.641,16.254,97.609,16.144,97.584,16.02,97.71,15.876,97.774,15.431,97.744,15.307,97.8,15.185,97.812,14.859,97.869,14.739,97.937,14.764,98.019,14.653,97.929,14.696,97.91,14.653,97.977,14.461,97.998,14.335,98.1,14.162,98.073,13.986,98.098,13.898,98.111,13.713,98.15,13.648,98.2,13.98,98.239,13.934,98.248,13.84,98.245,13.733,98.421,13.484,98.487,13.293,98.576,13.162,98.595,12.986,98.637,12.848,98.636,12.771,98.663,12.662,98.665,12.54,98.624,12.441,98.679,12.348,98.619,12.3,98.6,12.245,98.631,12.225,98.696,12.225,98.664,12.127,98.686,12.047,98.689,11.957,98.645,11.91,98.639,11.869,98.625,11.801,98.636,11.738,98.694,11.718,98.805,11.779,98.84,11.739,98.876,11.72,98.791,11.665,98.741,11.592,98.746,11.521,98.733,11.435,98.73,11.33,98.745,11.24,98.683,11.133,98.676,10.987,98.599,10.864,98.536,10.741,98.501,10.719,98.465,10.676,98.523,10.353,98.497,10.183,98.521,10.107,98.563,10.035,98.658,10.179,98.703,10.19,98.562,9.838,98.493,9.561,98.443,9.493,98.371,9.291,98.326,8.969,98.242,8.768,98.227,8.544,98.238,8.423,98.305,8.226,98.361,8.187,98.421,8.178,98.474,8.247,98.5,8.318,98.579,8.344,98.636,8.305,98.704,8.257,98.789,8.06,98.872,8.024,98.974,7.963,99.051,7.888,99.043,7.766,99.078,7.718,99.183,7.718,99.264,7.619,99.3,7.561,99.359,7.372,99.435,7.334,99.529,7.329,99.597,7.356,99.553,7.219,99.602,7.155,99.668,7.151,99.72,7.106,99.696,6.877,99.869,6.75,100.119,6.442,100.158,6.324,100.263,6.183,100.343,5.984,100.374,5.778,100.353,5.588,100.473,5.044,100.615,4.652,100.615,4.373,100.661,4.226,100.76,4.097,100.796,4.023,100.757,4.002,100.715,3.966,100.782,3.864,100.851,3.777,101.025,3.625,101.115,3.472,101.3,3.253,101.33,3.142,101.354,3.011,101.296,2.885,101.351,2.839,101.407,2.813,101.52,2.684,101.781,2.574,101.89,2.449,102.146,2.248,102.548,2.042,102.727,1.856,102.897,1.792,103.357,1.546,103.4,1.498,103.427,1.43,103.48,1.329,103.55,1.333,103.695,1.45,103.817,1.477,103.915,1.447,103.992,1.455,103.991,1.55,103.981,1.624,104.016,1.579,104.076,1.53,104.101,1.488,104.094,1.446,104.115,1.412,104.176,1.365,104.25,1.389,104.28,1.416,104.288,1.481,104.219,1.723,103.968,2.261,103.832,2.508,103.812,2.58,103.537,2.775,103.485,2.837,103.439,2.933,103.445,3.261,103.429,3.379,103.454,3.521,103.373,3.671,103.362,3.769,103.421,3.977,103.469,4.393,103.454,4.669,103.416,4.85,103.197,5.262,103.097,5.408,102.982,5.525,102.899,5.564,102.79,5.645,102.534,5.863,102.34,6.172,102.274,6.203,102.101,6.242,101.799,6.475,101.614,6.754,101.498,6.865,101.401,6.9,101.302,6.908,101.154,6.875,101.018,6.861,100.793,6.995,100.702,7.082,100.586,7.176,100.49,7.161,100.424,7.188,100.371,7.28,100.205,7.501,100.161,7.599,100.158,7.728,100.257,7.775,100.317,7.716,100.324,7.644,100.28,7.584,100.284,7.552,100.343,7.553,100.38,7.542,100.411,7.464,100.439,7.281,100.545,7.227,100.504,7.337,100.454,7.442,100.279,8.269,100.229,8.425,100.163,8.508,100.159,8.474,100.154,8.443,100.129,8.428,100.056,8.511,99.99,8.589,99.961,8.671,99.905,9.113,99.878,9.195,99.836,9.288,99.724,9.314,99.394,9.214,99.335,9.225,99.254,9.265,99.265,9.353,99.288,9.415,99.191,9.627,99.161,9.734,99.169,9.934,99.195,10.175,99.19,10.266,99.165,10.32,99.237,10.388,99.285,10.569,99.487,10.89,99.514,11.101,99.561,11.215,99.627,11.463,99.725,11.662,99.799,11.749,99.837,11.937,99.93,12.047,99.989,12.171,100.006,12.355,99.964,12.69,99.982,12.771,100.09,13.046,100.051,13.171,99.991,13.243,100.017,13.353,100.122,13.44,100.236,13.484,100.536,13.514,100.603,13.568,100.656,13.521,100.907,13.462,100.963,13.432,100.947,13.358,100.926,13.303,100.946,13.187,100.904,13.035,100.896,12.818,100.863,12.715,100.898,12.654,100.954,12.621,101.09,12.674,101.445,12.619,101.724,12.689,101.836,12.64,101.889,12.593,101.945,12.564,102.034,12.532,102.134,12.443,102.23,12.332,102.248,12.361,102.259,12.394,102.343,12.253,102.434,12.179,102.54,12.109,102.575,12.158,102.594,12.203,102.655,12.149,102.763,12.012,102.792,11.889,102.884,11.773,102.912,11.704,102.934,11.707,102.932,11.742,102.949,11.773,103.004,11.711,103.011,11.589,103.125,11.461,103.107,11.368,103.091,11.211,103.106,11.074,103.153,10.914,103.272,10.909,103.354,10.922,103.411,10.977,103.467,11.084,103.532,11.147,103.595,11.108,103.654,11.059,103.722,10.89,103.681,10.759,103.592,10.721,103.54,10.669,103.532,10.605,103.587,10.552,103.662,10.509,103.841,10.581,103.871,10.655,103.902,10.644,103.937,10.587,104.262,10.541,104.426,10.411,104.516,10.34,104.594,10.267,104.613,10.208,104.663,10.17,104.748,10.199,104.802,10.203,104.873,10.115,104.966,10.101,105.028,10.067,105.084,9.996,105.095,9.945,105.093,9.901,104.987,9.869,104.903,9.816,104.845,9.606,104.815,9.185,104.819,8.802,104.896,8.747,104.77,8.598,104.892,8.583,105.114,8.629,105.191,8.711,105.322,8.801,105.401,8.962,105.501,9.093,106.168,9.397,106.193,9.448,106.206,9.502,106.159,9.594,106.113,9.674,105.831,10.001,105.926,9.962,106.204,9.675,106.378,9.556,106.484,9.559,106.539,9.604,106.572,9.641,106.564,9.716,106.507,9.821,106.184,10.142,106.136,10.222,106.449,9.94,106.557,9.868,106.596,9.86,106.657,9.901,106.658,9.949,106.659,9.991,106.714,10.06,106.785,10.116,106.785,10.151,106.729,10.193,106.602,10.232,106.464,10.298,106.492,10.304,106.644,10.289,106.757,10.296,106.776,10.339,106.778,10.376,106.741,10.444,106.698,10.462,106.643,10.456,106.606,10.465,106.727,10.536,106.813,10.433,106.902,10.383,106.947,10.4,106.966,10.441,106.984,10.618,107.007,10.661,107.021,10.631,107.036,10.556,107.088,10.498,107.194,10.472,107.235,10.42,107.262,10.398,107.384,10.459,107.47,10.486,107.564,10.555,107.845,10.7,108.001,10.72,108.095,10.897,108.176,10.92,108.272,10.934,108.419,11.041,108.551,11.156,108.7,11.199,108.821,11.315,108.987,11.336,109.018,11.468,109.04,11.593,109.133,11.601,109.173,11.665,109.199,11.725,109.193,11.773,109.158,11.837,109.167,11.912,109.2,11.972,109.199,11.999,109.215,12.01,109.22,11.959,109.247,11.909,109.259,11.955,109.256,11.993,109.216,12.073,109.207,12.415,109.305,12.391,109.219,12.646,109.274,12.709,109.336,12.752,109.381,12.671,109.445,12.6,109.42,12.719,109.424,12.956,109.377,13.025,109.31,13.219,109.272,13.279,109.288,13.451,109.252,13.591,109.247,13.855,109.288,13.765,109.303,13.856,109.245,14.053,109.224,14.097,109.207,14.154,109.191,14.27,109.137,14.384,109.087,14.553,109.085,14.716,109.022,14.803,108.94,15.001,108.898,15.181,108.821,15.378,108.743,15.427,108.674,15.484,108.578,15.585,108.447,15.763,108.395,15.872,108.286,15.989,108.274,16.029,108.267,16.09,108.241,16.101,108.209,16.091,108.17,16.164,108.088,16.243,108.029,16.331,107.991,16.337,107.936,16.329,107.882,16.31,107.834,16.322,107.803,16.403,107.724,16.488,107.593,16.568,107.541,16.609,107.549,16.643,107.355,16.794,107.18,16.898,107.12,17.056,106.926,17.221,106.736,17.367,106.517,17.663,106.371,17.747,106.356,17.765,106.479,17.72,106.459,17.874,106.499,17.946,106.412,18.053,106.24,18.221,106.145,18.259,106.066,18.316,105.888,18.502,105.839,18.574,105.808,18.646,105.744,18.746,105.732,18.779,105.622,18.966,105.639,19.057,105.716,19.128,105.791,19.294,105.785,19.379,105.812,19.467,105.814,19.587,105.984,19.939,106.062,19.987,106.166,19.992,106.396,20.206,106.518,20.289,106.573,20.392,106.551,20.527,106.753,20.735,106.737,20.806,106.675,20.96,106.683,21,106.725,21,106.76,20.991,106.821,20.958,106.886,20.95,106.936,20.974,106.981,20.971,107.019,20.991,107.075,20.999,107.112,20.96,107.165,20.949,107.354,21.055,107.373,21.128,107.376,21.194,107.41,21.285,107.527,21.336,107.637,21.368,107.707,21.406,107.809,21.497,107.926,21.499,107.973,21.508,108.067,21.526,108.146,21.565,108.246,21.558,108.302,21.622,108.325,21.694,108.355,21.697,108.383,21.679,108.444,21.607,108.502,21.633,108.526,21.671,108.493,21.739,108.481,21.829,108.48,21.905,108.594,21.901,108.616,21.869,108.589,21.816,108.616,21.77,108.675,21.725,108.744,21.651,108.772,21.63,108.846,21.634,108.922,21.624,109.031,21.627,109.102,21.59,109.133,21.544,109.098,21.487,109.082,21.44,109.149,21.426,109.22,21.443,109.347,21.454,109.436,21.479,109.544,21.538,109.521,21.693,109.566,21.691,109.594,21.672,109.687,21.525,109.743,21.528,109.759,21.56,109.83,21.484,109.931,21.481,109.921,21.376,109.78,21.337,109.76,21.228,109.681,21.132,109.704,21.053,109.663,20.917,109.685,20.874,109.726,20.839,109.767,20.781,109.805,20.711,109.792,20.622,109.861,20.514,109.946,20.474,109.968,20.448,109.984,20.403,109.932,20.399,109.886,20.413,109.883,20.364,109.938,20.295,110.123,20.264,110.345,20.295,110.45,20.355,110.487,20.427,110.518,20.46,110.512,20.518,110.313,20.672,110.326,20.72,110.371,20.752,110.388,20.791,110.365,20.838,110.18,20.859,110.154,20.945,110.194,21.038,110.331,21.131,110.375,21.172,110.397,21.248,110.411,21.338,110.435,21.327,110.458,21.231,110.504,21.207,110.567,21.214,110.652,21.279,110.771,21.387,110.878,21.396,110.997,21.43,111.017,21.512,111.061,21.511,111.101,21.485,111.144,21.482,111.221,21.494,111.319,21.486,111.392,21.535,111.603,21.559,111.682,21.608,111.712,21.655,111.776,21.719,111.825,21.71,111.873,21.717,111.926,21.776,111.944,21.85,112.025,21.843,112.117,21.806,112.193,21.763,112.305,21.742,112.356,21.768,112.39,21.801,112.377,21.917,112.36,21.978,112.396,21.981,112.429,21.958,112.439,21.927,112.421,21.881,112.495,21.818,112.586,21.777,112.634,21.82,112.661,21.859,112.725,21.902,112.809,21.945,112.904,21.881,112.954,21.907,112.984,21.938,113.008,22.119,113.089,22.208,113.149,22.075,113.266,22.089,113.328,22.145,113.367,22.165,113.416,22.178,113.499,22.202,113.548,22.223,113.576,22.297,113.589,22.35,113.551,22.404,113.553,22.594,113.485,22.692,113.45,22.726,113.432,22.789,113.345,22.865,113.338,22.889,113.331,22.912,113.442,22.941,113.46,22.996,113.445,23.055,113.52,23.102,113.621,23.127,113.592,23.077,113.586,23.02,113.603,22.969,113.62,22.861,113.661,22.802,113.754,22.734,113.828,22.607,113.931,22.531,114.015,22.512,114.007,22.484,113.896,22.428,113.903,22.396,113.937,22.365,114.033,22.376,114.139,22.348,114.268,22.296,114.288,22.325,114.291,22.374,114.335,22.396,114.325,22.437,114.285,22.458,114.291,22.499,114.266,22.541,114.341,22.593,114.42,22.583,114.496,22.527,114.554,22.529,114.544,22.621,114.572,22.654,114.593,22.698,114.652,22.755,114.711,22.739,114.75,22.626,114.854,22.617,114.896,22.64,114.914,22.685,115.012,22.709,115.092,22.782,115.196,22.817,115.29,22.776,115.383,22.719,115.498,22.719,115.535,22.765,115.561,22.825,115.64,22.853,115.756,22.824,115.852,22.802,116.063,22.879,116.157,22.887,116.206,22.919,116.222,22.95,116.252,22.981,116.346,22.941,116.471,22.946,116.52,23.007,116.538,23.18,116.586,23.218,116.669,23.228,116.699,23.278,116.682,23.327,116.629,23.354,116.712,23.36,116.76,23.383,116.861,23.453,116.911,23.647,117.033,23.623,117.083,23.579,117.148,23.599,117.225,23.647,117.291,23.714,117.331,23.709,117.347,23.636,117.368,23.589,117.417,23.621,117.462,23.736,117.46,23.771,117.433,23.792,117.466,23.841,117.579,23.857,117.628,23.837,117.668,23.939,117.742,24.015,117.839,24.012,117.904,24.106,118.056,24.246,118.051,24.327,118.024,24.38,117.879,24.396,117.848,24.432,117.843,24.474,117.897,24.48,117.935,24.474,118.006,24.482,118.014,24.56,118.087,24.627,118.195,24.626,118.295,24.573,118.412,24.601,118.56,24.58,118.657,24.621,118.719,24.746,118.692,24.782,118.64,24.809,118.637,24.836,118.708,24.85,118.822,24.911,118.909,24.929,118.956,25.005,118.914,25.127,118.978,25.209,119.025,25.223,119.236,25.206,119.286,25.232,119.244,25.307,119.169,25.356,119.146,25.414,119.18,25.45,119.263,25.468,119.344,25.446,119.422,25.46,119.499,25.409,119.593,25.368,119.622,25.391,119.619,25.437,119.539,25.591,119.553,25.699,119.617,25.823,119.648,25.919,119.619,26.004,119.501,26.009,119.418,25.954,119.332,25.949,119.264,25.975,119.139,26.122,119.232,26.104,119.313,26.063,119.37,26.054,119.463,26.055,119.567,26.127,119.693,26.236,119.797,26.3,119.881,26.334,119.876,26.371,119.84,26.414,119.831,26.45,119.785,26.547,119.726,26.609,119.638,26.621,119.624,26.676,119.59,26.73,119.588,26.785,119.652,26.747,119.71,26.729,119.767,26.775,119.789,26.831,119.824,26.846,119.815,26.798,119.821,26.737,119.842,26.689,119.879,26.683,119.882,26.61,119.968,26.586,120.043,26.634,120.087,26.672,120.097,26.781,120.139,26.886,120.279,27.097,120.385,27.156,120.469,27.256,120.54,27.318,120.608,27.412,120.629,27.482,120.588,27.581,120.665,27.639,120.661,27.688,120.685,27.745,120.833,27.891,120.833,27.938,120.763,27.977,120.748,28.01,120.813,28.013,120.892,28.004,120.959,28.037,121.035,28.157,121.098,28.291,121.146,28.327,121.217,28.346,121.272,28.222,121.355,28.23,121.51,28.324,121.61,28.292,121.602,28.367,121.538,28.521,121.475,28.641,121.519,28.714,121.59,28.735,121.63,28.768,121.663,28.851,121.54,28.932,121.641,28.916,121.68,28.953,121.665,29.011,121.521,29.118,121.448,29.131,121.487,29.193,121.534,29.237,121.656,29.236,121.717,29.256,121.791,29.226,121.854,29.129,121.918,29.135,121.968,29.491,121.941,29.606,121.888,29.628,121.822,29.605,121.69,29.511,121.506,29.485,121.575,29.537,121.677,29.584,121.906,29.78,122.083,29.87,122.017,29.888,121.944,29.894,121.812,29.952,121.678,29.979,121.433,30.227,121.341,30.282,121.258,30.304,121.159,30.302,120.904,30.161,120.633,30.133,120.495,30.303,120.353,30.247,120.261,30.263,120.229,30.25,120.195,30.241,120.246,30.284,120.45,30.388,120.63,30.391,120.821,30.355,120.897,30.393,120.938,30.47,120.998,30.558,121.31,30.7,121.419,30.79,121.528,30.841,121.675,30.864,121.769,30.87,121.878,30.917,121.834,31.062,121.786,31.163,121.661,31.32,121.351,31.485,121.205,31.628,121.055,31.719,120.938,31.75,120.788,31.82,120.752,31.923,120.716,31.984,120.497,32.02,120.347,31.952,120.192,31.906,120.036,31.936,120.074,31.96,120.099,31.976,120.184,31.966,120.52,32.106,120.661,32.081,120.792,32.032,120.974,31.869,121.146,31.842,121.266,31.863,121.352,31.859,121.681,31.712,121.764,31.7,121.866,31.704,121.856,31.816,121.832,31.9,121.751,31.993,121.674,32.051,121.491,32.121,121.451,32.153,121.404,32.206,121.401,32.372,121.342,32.425,121.293,32.457,120.99,32.567,120.853,32.661,120.853,32.764,120.897,32.843,120.871,33.017,120.734,33.237,120.616,33.491,120.505,33.638,120.5,33.716,120.426,33.866,120.323,34.169,120.267,34.274,120.201,34.326,119.964,34.448,119.77,34.496,119.583,34.582,119.427,34.714,119.351,34.749,119.201,34.748,119.165,34.849,119.216,35.012,119.353,35.114,119.43,35.301,119.526,35.359,119.608,35.47,119.72,35.589,119.811,35.618,119.866,35.644,119.912,35.693,119.979,35.74,120.027,35.799,120.055,35.861,120.219,35.935,120.285,35.984,120.265,36.007,120.181,36.017,120.094,36.119,120.117,36.15,120.183,36.202,120.27,36.226,120.328,36.228,120.343,36.189,120.33,36.11,120.348,36.079,120.393,36.054,120.519,36.109,120.638,36.13,120.681,36.168,120.682,36.341,120.712,36.413,120.776,36.456,120.847,36.426,120.896,36.444,120.905,36.485,120.883,36.539,120.797,36.607,120.811,36.633,120.879,36.635,120.99,36.598,121.054,36.611,121.144,36.66,121.413,36.738,121.67,36.836,121.933,36.959,122.05,36.971,122.162,36.959,122.203,36.927,122.22,36.88,122.242,36.85,122.274,36.834,122.341,36.832,122.457,36.915,122.52,36.947,122.523,37.003,122.487,37.022,122.447,37.068,122.516,37.138,122.587,37.181,122.573,37.318,122.667,37.403,122.602,37.426,122.493,37.408,122.338,37.405,122.169,37.456,122.11,37.522,122.057,37.529,122.01,37.496,121.965,37.445,121.816,37.457,121.64,37.46,121.505,37.515,121.388,37.579,121.22,37.6,121.049,37.725,120.75,37.834,120.37,37.701,120.285,37.692,120.257,37.679,120.287,37.656,120.312,37.623,120.156,37.495,119.883,37.351,119.88,37.296,119.888,37.253,119.761,37.155,119.45,37.125,119.287,37.138,119.112,37.201,118.998,37.277,118.953,37.331,118.955,37.494,118.991,37.641,119.033,37.661,119.089,37.701,119.07,37.749,119.038,37.777,119.036,37.809,119.028,37.904,118.94,38.043,118.8,38.127,118.667,38.126,118.543,38.095,118.015,38.183,117.767,38.312,117.656,38.424,117.558,38.625,117.554,38.691,117.617,38.853,117.785,39.134,117.866,39.191,118.041,39.227,118.148,39.195,118.298,39.067,118.472,39.118,118.626,39.177,118.752,39.16,118.826,39.172,118.912,39.166,118.977,39.183,119.04,39.222,119.225,39.408,119.261,39.561,119.322,39.662,119.391,39.752,119.591,39.903,119.85,39.987,120.369,40.204,120.479,40.231,120.771,40.589,120.841,40.649,120.922,40.683,121.003,40.749,121.086,40.842,121.175,40.901,121.537,40.878,121.599,40.843,121.729,40.846,121.766,40.876,121.809,40.969,121.835,40.974,121.859,40.842,122.14,40.688,122.179,40.603,122.275,40.542,122.264,40.5,122.203,40.396,122.191,40.358,121.983,40.136,121.869,40.046,121.801,39.951,121.517,39.845,121.474,39.755,121.514,39.685,121.518,39.639,121.47,39.64,121.406,39.621,121.267,39.545,121.286,39.519,121.3,39.452,121.275,39.385,121.356,39.377,121.513,39.375,121.785,39.401,121.818,39.387,121.758,39.348,121.665,39.269,121.628,39.22,121.68,39.109,121.263,38.96,121.188,38.947,121.107,38.921,121.122,38.813,121.164,38.732,121.207,38.744,121.236,38.767,121.32,38.808,121.517,38.831,121.65,38.865,121.67,38.892,121.633,38.955,121.677,39.003,121.745,39.01,121.805,38.991,121.864,38.996,121.923,39.037,121.982,39.053,122.048,39.094,122.121,39.152,122.225,39.267,122.335,39.366,122.84,39.601,122.961,39.62,123.032,39.674,123.227,39.687,123.269,39.727,123.348,39.763,123.49,39.768,123.581,39.786,123.611,39.841,123.651,39.882,123.76,39.822,124.106,39.841,124.267,39.924,124.35,40.012,124.362,40.004,124.375,39.996,124.349,39.907,124.404,39.866,124.557,39.791,124.608,39.717,124.638,39.615,124.699,39.632,124.732,39.652,124.739,39.742,124.775,39.758,124.868,39.702,125.1,39.59,125.18,39.583,125.361,39.527,125.374,39.428,125.413,39.326,125.41,39.288,125.157,38.872,125.169,38.806,125.299,38.743,125.424,38.747,125.489,38.728,125.554,38.686,125.492,38.676,125.415,38.68,125.31,38.665,125.067,38.557,124.974,38.48,124.881,38.342,124.883,38.295,124.875,38.233,124.691,38.129,124.779,38.102,124.907,38.113,124.995,38.078,125.163,38.094,125.207,38.082,125.247,38.057,125.193,38.038,124.989,37.931,125.027,37.923,125.102,37.882,125.311,37.844,125.365,37.748,125.358,37.725,125.407,37.719,125.449,37.73,125.582,37.815,125.676,37.918,125.695,37.963,125.769,37.985,125.942,37.874,126.05,37.87,126.117,37.743,126.161,37.764,126.203,37.829,126.37,37.878,126.573,37.797,126.623,37.79,126.634,37.782,126.621,37.755,126.578,37.745,126.563,37.717,126.58,37.654,126.608,37.617,126.657,37.551,126.65,37.447,126.696,37.411,126.791,37.295,126.746,37.194,126.776,37.158,126.787,37.103,126.869,36.976,126.96,36.958,126.977,36.939,126.958,36.906,126.879,36.862,126.872,36.824,126.839,36.846,126.784,36.948,126.687,36.96,126.578,37.02,126.487,37.007,126.429,36.969,126.352,36.958,126.217,36.871,126.161,36.772,126.181,36.692,126.231,36.689,126.389,36.651,126.433,36.678,126.488,36.694,126.507,36.586,126.548,36.478,126.552,36.43,126.544,36.341,126.557,36.236,126.54,36.166,126.597,36.105,126.682,36.038,126.693,36.014,126.664,35.975,126.647,35.922,126.72,35.898,126.753,35.872,126.717,35.769,126.602,35.714,126.542,35.669,126.488,35.647,126.487,35.606,126.565,35.59,126.614,35.571,126.582,35.534,126.493,35.501,126.46,35.456,126.396,35.314,126.361,35.217,126.291,35.154,126.327,35.045,126.398,34.933,126.421,34.823,126.479,34.81,126.548,34.837,126.593,34.824,126.539,34.779,126.473,34.756,126.505,34.738,126.525,34.698,126.426,34.695,126.301,34.72,126.264,34.673,126.333,34.59,126.482,34.494,126.506,34.428,126.508,34.351,126.531,34.314,126.584,34.318,126.611,34.404,126.755,34.512,126.796,34.494,126.826,34.451,126.897,34.439,127.031,34.607,127.247,34.755,127.269,34.72,127.261,34.662,127.195,34.605,127.173,34.546,127.325,34.463,127.381,34.501,127.401,34.553,127.479,34.625,127.423,34.688,127.39,34.743,127.404,34.823,127.477,34.844,127.524,34.84,127.565,34.766,127.632,34.69,127.715,34.721,127.742,34.783,127.663,34.843,127.639,34.89,127.659,34.926,127.715,34.955,127.873,34.966,127.977,35.019,128.036,35.022,128.095,34.934,128.152,34.916,128.276,34.911,128.388,34.875,128.444,34.87,128.448,34.932,128.419,35.016,128.458,35.069,128.511,35.101,128.643,35.12,128.796,35.094,128.98,35.102,129.077,35.123,129.214,35.182,129.329,35.333,129.419,35.498,129.485,35.687,129.562,35.948,129.573,36.051,129.51,36.038,129.458,36.006,129.426,36.019,129.404,36.052,129.402,36.138,129.391,36.202,129.393,36.323,129.427,36.385,129.445,36.471,129.433,36.637,129.473,36.742,129.426,36.926,129.418,37.059,129.335,37.275,129.052,37.678,128.852,37.887,128.619,38.176,128.375,38.623,128.329,38.681,128.249,38.745,128.163,38.786,128.123,38.816,127.972,38.898,127.786,39.084,127.699,39.125,127.581,39.143,127.497,39.179,127.395,39.208,127.383,39.296,127.422,39.374,127.457,39.401,127.523,39.377,127.549,39.461,127.547,39.563,127.527,39.696,127.568,39.782,127.867,39.896,127.967,39.996,128.106,40.033,128.304,40.036,128.393,40.089,128.511,40.13,128.611,40.198,128.701,40.318,128.843,40.358,128.945,40.428,129.11,40.491,129.245,40.661,129.341,40.726,129.709,40.857,129.742,40.932,129.712,41.124,129.766,41.304,129.759,41.392,129.682,41.494,129.686,41.595,129.756,41.712,129.876,41.806,129.928,41.897,130.007,41.991,130.068,42.046,130.18,42.097,130.236,42.183,130.315,42.214,130.458,42.302,130.569,42.292,130.637,42.275,130.687,42.303,130.73,42.326,130.834,42.523,130.709,42.656,130.756,42.673,130.946,42.634,131.025,42.645,131.158,42.626,131.245,42.697,131.292,42.772,131.393,42.822,131.516,42.996,131.722,43.203,131.795,43.255,131.939,43.302,131.976,43.296,132.013,43.28,131.898,43.171,131.867,43.095,131.947,43.095,132.029,43.119,132.233,43.245,132.31,43.314,132.334,43.239,132.304,42.883,132.481,42.91,132.576,42.872,132.709,42.876,132.864,42.794,132.924,42.805,132.997,42.808,133.059,42.723,133.16,42.697,133.329,42.764,133.587,42.828,133.709,42.83,134.01,42.947,134.156,43.042,134.692,43.291,134.917,43.427,135.131,43.526,135.26,43.685,135.483,43.835,135.489,43.899,135.533,43.971,135.875,44.374,135.987,44.44,136.142,44.489,136.209,44.562,136.251,44.667,136.46,44.822,136.604,44.978,136.737,45.08,136.804,45.171,137.147,45.394,137.425,45.64,137.685,45.818,137.769,45.929,138.106,46.251,138.21,46.463,138.337,46.543,138.392,46.745,138.5,46.89,138.53,46.976,138.587,47.057,139.001,47.383,139.167,47.635,139.373,47.887,139.521,47.975,139.676,48.09,139.761,48.181,139.998,48.324,140.113,48.423,140.171,48.524,140.224,48.773,140.378,48.964,140.334,48.995,140.309,49.054,140.326,49.12,140.349,49.159,140.364,49.221,140.399,49.29,140.431,49.331,140.517,49.596,140.511,49.762,140.465,49.826,140.463,49.911,140.585,50.033,140.613,50.054,140.625,50.082,140.564,50.107,140.535,50.131,140.476,50.546,140.521,50.8,140.646,50.987,140.671,51.051,140.688,51.232,140.839,51.414,140.933,51.62,141.129,51.728,141.258,51.861,141.367,51.921,141.386,52.057,141.485,52.179,141.409,52.234,141.33,52.271,141.17,52.368,141.132,52.436,141.245,52.55,141.266,52.653,141.256,52.84,141.087,52.898,140.875,53.04,140.84,53.088,140.887,53.092,141.181,53.015,141.328,53.097,141.402,53.184,141.374,53.293,141.218,53.334,141.015,53.454,141.006,53.495,140.688,53.596,140.347,53.813,140.242,54.001,140.179,54.052,139.858,54.205,139.796,54.256,139.707,54.277,139.32,54.193,139.105,54.218,138.696,54.32,138.657,54.298,138.716,54.223,138.705,54.148,138.722,54.044,138.699,53.87,138.661,53.745,138.511,53.57,138.451,53.537,138.32,53.523,138.25,53.524,138.292,53.592,138.407,53.674,138.569,53.819,138.568,53.947,138.528,53.96,138.494,53.96,138.379,53.909,138.253,53.726,137.95,53.604,137.738,53.56,137.328,53.539,137.254,53.546,137.221,53.579,137.314,53.632,137.517,53.707,137.645,53.866,137.786,53.903,137.835,53.947,137.623,53.97,137.476,54.028,137.339,54.101,137.403,54.124,137.451,54.13,137.513,54.156,137.666,54.283,137.525,54.291,137.378,54.282,137.142,54.182,137.096,54.129,137.172,54.057,137.258,54.025,137.155,53.822,137.019,53.848,136.886,53.839,136.803,53.782,136.719,53.804,136.683,53.931,136.729,54.061,136.77,54.353,136.82,54.452,136.824,54.561,136.797,54.621,136.715,54.624,136.58,54.614,136.238,54.614,135.852,54.584,135.438,54.692,135.325,54.707,135.258,54.731,135.212,54.841,135.235,54.903,135.263,54.943,135.541,55.114,135.751,55.161,136.175,55.352,136.351,55.51,136.46,55.577,136.794,55.694,137.012,55.795,137.19,55.892,137.384,55.975,137.573,56.112,137.692,56.139,138.074,56.433,138.141,56.499,138.18,56.589,138.218,56.629,138.662,56.966,138.966,57.088,139.182,57.262,139.444,57.33,139.507,57.358,139.619,57.456,139.803,57.514,139.862,57.549,140.002,57.688,140.447,57.814,140.495,57.865,140.685,58.212,140.79,58.303,140.988,58.417,141.347,58.528,141.603,58.649,141.755,58.745,142.025,59,142.33,59.153,142.58,59.24,143.192,59.37,143.524,59.344,143.869,59.411,144.123,59.408,144.483,59.376,145.555,59.414,145.756,59.374,145.829,59.33,145.932,59.198,146.05,59.171,146.273,59.221,146.444,59.43,146.537,59.457,146.804,59.373,147.04,59.366,147.514,59.269,147.688,59.291,147.875,59.388,148.257,59.414,148.491,59.262,148.727,59.258,148.914,59.283,148.965,59.369,148.89,59.4,148.744,59.374,148.709,59.449,148.797,59.532,148.925,59.475,149.133,59.481,149.205,59.488,149.175,59.527,149.128,59.559,149.065,59.631,149.29,59.728,149.425,59.761,149.643,59.77,150.203,59.651,150.326,59.639,150.457,59.591,150.667,59.556,150.54,59.525,150.484,59.494,150.615,59.507,150.729,59.469,150.823,59.461,150.863,59.475,150.912,59.523,150.983,59.571,151.034,59.586,151.17,59.583,151.348,59.561,151.486,59.524,151.798,59.323,151.942,59.284,152.104,59.291,152.17,59.278,152.261,59.224,151.99,59.16,151.733,59.147,151.505,59.164,151.121,59.083,151.327,58.875,151.705,58.867,152.088,58.91,152.165,58.997,152.32,59.031,152.401,59.026,152.576,58.954,152.818,58.926,152.882,58.939,153.078,59.082,153.196,59.094,153.273,59.091,153.361,59.215,153.695,59.225,153.892,59.114,154.011,59.076,154.247,59.109,154.376,59.188,154.458,59.217,154.704,59.141,154.824,59.188,155.017,59.196,155.16,59.19,155.153,59.27,155.167,59.36,154.971,59.45,154.583,59.54,154.358,59.481,154.272,59.475,154.213,59.483,154.15,59.529,154.209,59.6,154.269,59.658,154.267,59.73,154.293,59.833,154.39,59.877,154.441,59.884,154.578,60.095,154.971,60.377,155.428,60.55,155.716,60.682,155.853,60.777,156.056,60.996,156.344,61.155,156.483,61.206,156.63,61.272,156.68,61.481,156.791,61.53,156.892,61.565,157.084,61.676,157.371,61.747,157.469,61.799,157.799,61.795,158.07,61.754,158.152,61.765,158.334,61.826,158.547,61.811,158.824,61.85,159.077,61.922,159.189,61.929,159.295,61.914,159.423,61.808,159.496,61.781,159.552,61.719,159.722,61.758,160.183,61.903,160.238,61.904,160.309,61.894,160.321,61.839,160.317,61.793,160.247,61.648,160.163,61.538,159.931,61.324,159.883,61.292,159.914,61.234,159.949,61.129,159.835,61.014,159.79,60.957,159.883,60.943,160.004,61.007,160.184,61.048,160.281,61.045,160.379,61.025,160.226,60.832,160.201,60.73,160.177,60.691,160.174,60.638,160.287,60.667,160.368,60.709,160.482,60.74,160.767,60.753,160.915,60.893,161.037,60.963,162.188,61.541,162.393,61.662,162.506,61.67,162.608,61.65,162.699,61.653,162.718,61.695,162.752,61.711,162.856,61.705,162.922,61.598,162.994,61.544,163.047,61.554,163.085,61.571,163.139,61.611,163.198,61.645,163.258,61.699,163.208,61.737,163.009,61.792,163.018,61.891,163.131,62.05,163.118,62.153,163.163,62.26,163.213,62.313,163.258,62.337,163.302,62.373,163.244,62.455,163.287,62.511,163.332,62.551,164.256,62.697,164.418,62.705,164.567,62.675,164.792,62.571,165.044,62.517,165.397,62.494,165.417,62.447,165.28,62.463,165.214,62.448,165.226,62.406,165.208,62.374,165.124,62.412,164.888,62.432,164.671,62.474,164.598,62.471,164.288,62.347,164.207,62.292,164.074,62.045,164.068,61.874,164.02,61.711,163.883,61.64,163.837,61.558,163.804,61.461,163.973,61.42,163.992,61.388,164.005,61.344,163.893,61.24,163.62,61.111,163.589,61.084,163.554,61.026,163.71,60.917,163.585,60.877,163.466,60.85,163.352,60.8,162.973,60.783,162.713,60.659,162.266,60.537,162.068,60.466,162.004,60.42,161.846,60.232,161.754,60.152,161.449,60.027,161.219,59.846,160.855,59.627,160.711,59.602,160.547,59.547,160.35,59.394,159.847,59.127,159.592,58.804,159.453,58.696,159.308,58.611,159.211,58.519,159.037,58.424,158.687,58.281,158.449,58.163,158.321,58.083,158.275,58.009,158.21,58.025,157.975,57.986,157.666,58.02,157.45,57.799,157.217,57.777,156.986,57.83,156.872,57.804,156.83,57.78,156.792,57.748,156.9,57.677,156.948,57.616,156.964,57.561,156.977,57.466,156.849,57.29,156.728,57.152,156.529,57.021,156.067,56.782,156.025,56.752,155.983,56.695,155.717,56.072,155.643,55.794,155.555,55.348,155.564,55.199,155.62,54.865,155.706,54.521,155.905,53.928,155.95,53.744,156.099,53.006,156.11,52.866,156.154,52.747,156.229,52.626,156.365,52.509,156.377,52.367,156.49,51.913,156.5,51.475,156.521,51.38,156.543,51.312,156.671,51.227,156.713,51.124,156.724,51.047,156.748,50.969,156.847,51.007,157.202,51.213,157.49,51.409,157.531,51.48,157.629,51.535,157.823,51.605,158.104,51.81,158.332,52.091,158.463,52.305,158.493,52.383,158.5,52.46,158.481,52.627,158.534,52.688,158.609,52.874,158.56,52.922,158.432,52.957,158.472,53.032,158.565,53.05,158.64,53.015,158.684,52.935,158.745,52.909,158.952,53.048,159.136,53.117,159.586,53.238,159.772,53.23,159.947,53.125,160.025,53.13,160.002,53.275,159.898,53.381,159.899,53.448,159.956,53.552,159.914,53.621,159.871,53.673,159.844,53.784,159.922,54.008,160.01,54.131,160.074,54.189,160.289,54.288,160.517,54.431,160.773,54.541,160.936,54.578,161.13,54.598,161.294,54.521,161.625,54.516,161.726,54.533,161.967,54.689,162.106,54.752,162.08,54.886,161.996,54.998,161.824,55.139,161.785,55.205,161.729,55.358,161.724,55.496,161.776,55.655,161.924,55.84,162.085,56.09,162.146,56.128,162.334,56.188,162.461,56.235,162.528,56.261,162.489,56.399,162.589,56.455,162.671,56.49,162.878,56.476,162.944,56.508,163.038,56.522,162.975,56.449,162.893,56.399,162.713,56.331,162.628,56.232,162.84,56.066,162.972,56.034,163.047,56.045,163.189,56.137,163.261,56.174,163.336,56.233,163.294,56.448,163.243,56.565,163.257,56.688,163.165,56.725,163.046,56.741,162.922,56.723,162.85,56.757,162.803,56.811,162.791,56.875,162.815,57.023,162.808,57.103,162.762,57.244,162.762,57.284,162.779,57.358,162.957,57.477,163.109,57.565,163.188,57.637,163.214,57.687,163.226,57.79,163.145,57.837,162.718,57.946,162.654,57.948,162.522,57.904,162.467,57.766,162.391,57.717,162.392,57.745,162.411,57.778,162.197,57.829,162.098,57.875,162.04,57.918,162.002,57.981,161.96,58.077,162.049,58.273,162.142,58.447,162.453,58.709,162.643,58.8,162.847,58.939,162.935,58.964,162.97,58.986,163.004,59.02,162.94,59.114,162.975,59.137,163.01,59.148,163.085,59.131,163.273,59.303,163.269,59.52,163.321,59.705,163.365,59.781,163.41,59.835,163.494,59.887,163.574,59.914,163.69,59.978,163.744,60.028,163.78,60.041,163.913,60.037,164.018,60.017,164.135,59.984,164.113,59.898,164.252,59.974,164.377,60.058,164.44,60.073,164.525,60.061,164.67,59.997,164.779,59.874,164.854,59.841,164.954,59.844,165.019,59.861,165.074,59.946,165.085,60.099,165.193,60.125,165.285,60.135,165.416,60.205,165.583,60.236,165.942,60.357,166.18,60.48,166.352,60.485,166.308,60.414,166.292,60.346,166.23,60.178,166.168,60.089,166.136,59.979,166.149,59.922,166.187,59.849,166.273,59.856,166.332,59.872,166.453,59.947,166.964,60.307,167.227,60.406,167.626,60.469,167.746,60.509,168.138,60.574,168.463,60.592,168.67,60.563,168.788,60.564,169.227,60.596,169.276,60.557,169.618,60.438,169.815,60.265,169.854,60.25,169.887,60.218,169.898,60.148,169.927,60.104,169.983,60.067,170.154,59.986,170.351,59.966,170.396,60.01,170.423,60.048,170.512,60.26,170.589,60.343,170.59,60.394,170.608,60.435,170.799,60.496,170.949,60.523,171.49,60.726,171.729,60.843,171.831,60.837,171.918,60.864,171.998,60.901,172.067,60.916,172.213,60.998,172.393,61.062,172.362,61.117,172.396,61.167,172.497,61.186,172.585,61.19,172.697,61.249,172.69,61.295,172.731,61.314,172.789,61.311,172.868,61.293,172.908,61.312,172.838,61.376,172.807,61.436,172.857,61.469,173.055,61.406,173.132,61.407,173.391,61.557,173.623,61.716,173.822,61.679,174.139,61.795,174.285,61.818,174.514,61.824,174.611,61.868,174.715,61.948,174.798,61.939,175.192,62.034,175.268,62.102,175.366,62.121,175.442,62.128,175.614,62.184,176.328,62.346,176.437,62.411,176.703,62.506,176.907,62.536,177.159,62.561,177.19,62.592,177.008,62.627,176.965,62.659,176.963,62.693,176.99,62.722,177.024,62.777,177.091,62.79,177.173,62.75,177.259,62.75,177.298,62.784,177.339,62.781,177.36,62.737,177.316,62.685,177.296,62.644,177.293,62.599,177.351,62.587,177.663,62.583,178.019,62.547,178.964,62.355,179.045,62.324,179.121,62.32,179.134,62.396,179.177,62.469,179.289,62.51,179.477,62.613,179.571,62.688,179.571,62.773,179.511,62.863,179.381,62.884,179.302,62.94,179.26,63.008,179.329,63.058,179.405,63.078,179.389,63.147,179.332,63.19,179.028,63.282,178.921,63.345,178.919,63.4,178.793,63.54,178.775,63.51,178.787,63.442,178.744,63.395,178.679,63.402,178.669,63.44,178.706,63.522,178.654,63.557,178.466,63.574,178.44,63.606,178.626,63.651,178.681,63.651,178.731,63.667,178.692,63.842,178.65,63.965,178.536,63.976,178.451,64.011,178.475,64.089,178.477,64.128,178.381,64.261,178.313,64.314,178.229,64.364,178.164,64.309,178.131,64.235,178.045,64.22,177.953,64.222,177.688,64.305,177.433,64.444,177.41,64.573,177.467,64.737,177.427,64.763,177.388,64.774,177.05,64.719,176.843,64.634,176.731,64.625,176.508,64.682,176.141,64.586,176.219,64.642,176.283,64.664,176.351,64.705,176.301,64.784,176.247,64.843,176.169,64.885,176.057,64.905,175.946,64.865,175.859,64.825,175.678,64.782,175.331,64.747,175.097,64.747,174.699,64.681,174.549,64.684,175.098,64.777,175.396,64.784,175.781,64.844,176.061,64.961,176.429,64.855,176.557,64.84,176.831,64.849,177.069,64.787,177.148,64.805,177.223,64.862,177.123,64.947,177.037,65,176.94,65.016,176.646,65.007,176.452,65.025,176.341,65.047,176.413,65.071,176.625,65.038,176.881,65.082,177.179,65.014,177.252,64.954,177.337,64.931,177.582,64.778,177.749,64.717,178.285,64.672,178.52,64.603,178.698,64.631,179.15,64.782,179.448,64.822,179.651,64.921,179.827,65.034,180,65.067]},{"b":[145.869,43.42,145.944,43.46],"l":[145.944,43.426,145.941,43.445,145.931,43.457,145.914,43.455,145.896,43.455,145.882,43.46,145.869,43.458,145.869,43.45,145.882,43.444,145.887,43.433,145.894,43.42,145.907,43.422,145.931,43.426,145.944,43.426]},{"b":[146.274,43.62,146.359,43.652],"l":[146.333,43.647,146.31,43.652,146.284,43.639,146.274,43.63,146.288,43.625,146.332,43.62,146.359,43.625,146.35,43.644,146.333,43.647]},{"b":[146.028,43.407,146.101,43.449],"l":[146.089,43.449,146.049,43.434,146.028,43.42,146.032,43.407,146.046,43.409,146.07,43.421,146.086,43.429,146.101,43.44,146.089,43.449]},{"b":[-159.842,-21.25,-159.737,-21.186],"l":[-159.811,-21.186,-159.832,-21.2,-159.842,-21.229,-159.84,-21.238,-159.813,-21.242,-159.773,-21.25,-159.741,-21.249,-159.737,-21.241,-159.74,-21.208,-159.768,-21.188,-159.811,-21.186]},{"b":[-172.499,-8.583,-172.479,-8.546],"l":[-172.494,-8.559,-172.499,-8.548,-172.497,-8.546,-172.487,-8.556,-172.481,-8.567,-172.479,-8.581,-172.484,-8.583,-172.488,-8.572,-172.494,-8.559]},{"b":[-171.204,-9.358,-171.186,-9.333],"l":[-171.2,-9.345,-171.204,-9.333,-171.202,-9.333,-171.194,-9.339,-171.189,-9.347,-171.186,-9.355,-171.189,-9.358,-171.193,-9.352,-171.2,-9.345]},{"b":[-172.228,-4.524,-172.181,-4.492],"l":[-172.215,-4.503,-172.204,-4.5,-172.197,-4.5,-172.196,-4.495,-172.198,-4.492,-172.212,-4.494,-172.228,-4.507,-172.215,-4.524,-172.189,-4.522,-172.181,-4.515,-172.194,-4.516,-172.208,-4.518,-172.215,-4.511,-172.215,-4.503]},{"b":[-171.728,-2.856,-171.628,-2.761],"l":[-171.647,-2.856,-171.628,-2.856,-171.628,-2.847,-171.64,-2.811,-171.665,-2.786,-171.698,-2.766,-171.719,-2.761,-171.725,-2.768,-171.728,-2.774,-171.725,-2.781,-171.718,-2.779,-171.706,-2.773,-171.688,-2.779,-171.673,-2.788,-171.66,-2.799,-171.64,-2.829,-171.639,-2.847,-171.655,-2.84,-171.678,-2.825,-171.698,-2.822,-171.696,-2.826,-171.687,-2.83,-171.671,-2.844,-171.647,-2.856]},{"b":[-171.097,-3.143,-171.081,-3.115],"l":[-171.092,-3.125,-171.088,-3.115,-171.081,-3.12,-171.085,-3.135,-171.09,-3.143,-171.097,-3.137,-171.092,-3.125]},{"b":[-171.262,-4.468,-171.232,-4.442],"l":[-171.252,-4.442,-171.239,-4.444,-171.232,-4.454,-171.233,-4.463,-171.243,-4.468,-171.255,-4.467,-171.262,-4.46,-171.262,-4.449,-171.252,-4.442]},{"b":[-174.541,-4.695,-174.501,-4.657],"l":[-174.501,-4.688,-174.513,-4.675,-174.531,-4.659,-174.541,-4.657,-174.541,-4.662,-174.533,-4.665,-174.523,-4.674,-174.511,-4.686,-174.517,-4.687,-174.529,-4.682,-174.524,-4.69,-174.507,-4.694,-174.501,-4.695,-174.501,-4.688]},{"b":[-155.015,-4.094,-154.943,-4.031],"l":[-155.015,-4.048,-155.015,-4.055,-154.995,-4.071,-154.971,-4.086,-154.959,-4.094,-154.956,-4.088,-154.95,-4.056,-154.943,-4.042,-154.951,-4.031,-154.987,-4.039,-155.015,-4.048]},{"b":[-151.819,-11.457,-151.783,-11.392],"l":[-151.813,-11.392,-151.819,-11.409,-151.816,-11.431,-151.807,-11.451,-151.791,-11.457,-151.783,-11.441,-151.791,-11.414,-151.803,-11.393,-151.813,-11.392]},{"b":[-155.929,-5.632,-155.862,-5.608],"l":[-155.911,-5.609,-155.919,-5.608,-155.929,-5.608,-155.928,-5.619,-155.914,-5.632,-155.887,-5.632,-155.864,-5.627,-155.862,-5.619,-155.872,-5.611,-155.911,-5.609]},{"b":[-178.599,-19.175,-178.535,-19.109],"l":[-178.576,-19.125,-178.563,-19.119,-178.557,-19.113,-178.568,-19.109,-178.589,-19.119,-178.599,-19.137,-178.596,-19.151,-178.574,-19.165,-178.546,-19.175,-178.535,-19.166,-178.541,-19.157,-178.557,-19.154,-178.574,-19.143,-178.576,-19.125]},{"b":[-179.867,-19.003,-179.798,-18.924],"l":[-179.867,-18.978,-179.865,-18.999,-179.851,-19.003,-179.849,-18.991,-179.846,-18.971,-179.839,-18.962,-179.83,-18.956,-179.812,-18.968,-179.798,-18.97,-179.8,-18.94,-179.831,-18.924,-179.856,-18.943,-179.863,-18.964,-179.867,-18.978]},{"b":[-178.731,-20.671,-178.71,-20.645],"l":[-178.723,-20.667,-178.729,-20.66,-178.731,-20.653,-178.728,-20.645,-178.725,-20.646,-178.719,-20.652,-178.714,-20.66,-178.712,-20.668,-178.71,-20.671,-178.715,-20.67,-178.723,-20.667]},{"b":[-78.989,-33.668,-78.769,-33.575],"l":[-78.838,-33.585,-78.859,-33.578,-78.877,-33.575,-78.888,-33.576,-78.938,-33.614,-78.979,-33.644,-78.989,-33.662,-78.983,-33.668,-78.804,-33.646,-78.775,-33.642,-78.769,-33.627,-78.785,-33.61,-78.838,-33.585]},{"b":[-14.415,-7.976,-14.303,-7.883],"l":[-14.384,-7.883,-14.399,-7.906,-14.415,-7.944,-14.409,-7.967,-14.399,-7.976,-14.364,-7.974,-14.317,-7.956,-14.303,-7.935,-14.329,-7.913,-14.36,-7.886,-14.384,-7.883]},{"b":[72.772,11.196,72.796,11.263],"l":[72.788,11.216,72.78,11.202,72.773,11.196,72.772,11.214,72.782,11.243,72.793,11.263,72.796,11.26,72.793,11.242,72.788,11.216]},{"b":[73.023,8.252,73.084,8.317],"l":[73.053,8.257,73.039,8.252,73.029,8.254,73.023,8.266,73.026,8.275,73.039,8.265,73.056,8.275,73.075,8.306,73.079,8.317,73.084,8.311,73.08,8.293,73.067,8.269,73.053,8.257]},{"b":[73.382,3.229,73.443,3.29],"l":[73.417,3.231,73.395,3.229,73.382,3.246,73.385,3.271,73.402,3.289,73.428,3.29,73.443,3.274,73.435,3.25,73.417,3.231]},{"b":[73.473,4.155,73.528,4.248],"l":[73.519,4.187,73.512,4.165,73.495,4.155,73.479,4.159,73.473,4.171,73.481,4.188,73.495,4.21,73.504,4.235,73.518,4.248,73.528,4.243,73.527,4.23,73.522,4.211,73.519,4.187]},{"b":[131.135,3.022,131.188,3.061],"l":[131.137,3.039,131.135,3.025,131.15,3.022,131.172,3.026,131.186,3.042,131.188,3.056,131.172,3.061,131.152,3.054,131.137,3.039]},{"b":[151.57,7.333,151.65,7.39],"l":[151.593,7.379,151.608,7.375,151.604,7.357,151.575,7.351,151.57,7.346,151.578,7.338,151.639,7.333,151.648,7.346,151.65,7.363,151.643,7.379,151.629,7.39,151.606,7.389,151.593,7.379]},{"b":[151.856,7.427,151.913,7.467],"l":[151.882,7.467,151.865,7.466,151.86,7.457,151.856,7.432,151.864,7.427,151.881,7.432,151.913,7.454,151.911,7.46,151.882,7.467]},{"b":[166.845,11.146,166.899,11.169],"l":[166.888,11.169,166.859,11.166,166.845,11.153,166.864,11.146,166.89,11.153,166.899,11.165,166.888,11.169]},{"b":[177.006,-12.516,177.127,-12.477],"l":[177.118,-12.482,177.068,-12.477,177.026,-12.487,177.006,-12.491,177.019,-12.507,177.082,-12.516,177.121,-12.505,177.127,-12.493,177.118,-12.482]},{"b":[174.587,-21.706,174.63,-21.667],"l":[174.593,-21.702,174.587,-21.68,174.604,-21.667,174.628,-21.676,174.63,-21.695,174.622,-21.706,174.593,-21.702]},{"b":[179.196,-8.535,179.217,-8.466],"l":[179.201,-8.535,179.214,-8.524,179.217,-8.515,179.212,-8.488,179.203,-8.466,179.199,-8.47,179.198,-8.489,179.201,-8.512,179.196,-8.535,179.201,-8.535]},{"b":[178.667,-7.495,178.695,-7.458],"l":[178.667,-7.458,178.677,-7.463,178.69,-7.474,178.695,-7.483,178.693,-7.495,178.687,-7.494,178.674,-7.48,178.667,-7.458]},{"b":[178.371,-8.068,178.39,-8.032],"l":[178.387,-8.032,178.379,-8.047,178.371,-8.061,178.377,-8.068,178.39,-8.046,178.387,-8.032]},{"b":[169.523,-0.876,169.555,-0.847],"l":[169.523,-0.866,169.526,-0.853,169.539,-0.847,169.555,-0.857,169.551,-0.874,169.542,-0.876,169.523,-0.866]},{"b":[-128.35,-24.413,-128.29,-24.323],"l":[-128.33,-24.323,-128.35,-24.34,-128.342,-24.371,-128.321,-24.4,-128.3,-24.413,-128.29,-24.397,-128.291,-24.365,-128.304,-24.334,-128.33,-24.323]},{"b":[-177.39,28.196,-177.354,28.221],"l":[-177.354,28.217,-177.371,28.221,-177.389,28.209,-177.39,28.196,-177.371,28.2,-177.362,28.207,-177.354,28.217]},{"b":[-81.811,24.542,-81.739,24.577],"l":[-81.784,24.545,-81.74,24.554,-81.739,24.575,-81.768,24.577,-81.811,24.558,-81.809,24.542,-81.784,24.545]},{"b":[-86.753,21.191,-86.696,21.28],"l":[-86.727,21.264,-86.739,21.28,-86.753,21.279,-86.736,21.233,-86.714,21.197,-86.696,21.191,-86.714,21.239,-86.727,21.264]},{"b":[-166.149,66.222,-165.822,66.328],"l":[-165.822,66.328,-166.033,66.278,-166.146,66.237,-166.149,66.222,-166.11,66.227,-165.942,66.278,-165.83,66.317,-165.822,66.328]},{"b":[54.335,24.423,54.465,24.506],"l":[54.427,24.471,54.398,24.506,54.379,24.505,54.335,24.471,54.358,24.443,54.428,24.425,54.457,24.423,54.465,24.443,54.427,24.471]},{"b":[124.625,37.92,124.755,37.989],"l":[124.74,37.947,124.754,37.955,124.755,37.97,124.745,37.981,124.717,37.989,124.661,37.973,124.636,37.981,124.625,37.945,124.656,37.925,124.697,37.92,124.717,37.933]}]} \ No newline at end of file diff --git a/src/maps/data/countries_110m.json b/src/maps/data/countries_110m.json new file mode 100644 index 0000000..0779ab3 --- /dev/null +++ b/src/maps/data/countries_110m.json @@ -0,0 +1 @@ +{"f":[{"n":"Fiji","c":"FJ","r":6,"ml":3,"lx":177.98,"ly":-17.83,"b":[-180,-18.29,180,-16.02],"p":[[[180,-16.07,180,-16.56,179.36,-16.8,178.73,-17.01,178.6,-16.64,179.1,-16.43,179.41,-16.38,180,-16.07]],[[178.13,-17.5,178.37,-17.34,178.72,-17.63,178.55,-18.15,177.93,-18.29,177.38,-18.16,177.29,-17.72,177.67,-17.38,178.13,-17.5]],[[-179.79,-16.02,-179.92,-16.5,-180,-16.56,-180,-16.07,-179.79,-16.02]]]},{"n":"Tanzania","c":"TZ","r":3,"ml":3,"lx":34.96,"ly":-6.05,"b":[29.34,-11.72,40.32,-0.95],"p":[[[33.9,-0.95,34.07,-1.06,37.7,-3.1,37.77,-3.68,39.2,-4.68,38.74,-5.91,38.8,-6.48,39.44,-6.84,39.47,-7.1,39.19,-7.7,39.25,-8.01,39.19,-8.49,39.54,-9.11,39.95,-10.1,40.32,-10.32,39.52,-10.9,38.43,-11.29,37.83,-11.27,37.47,-11.57,36.78,-11.59,36.51,-11.72,35.31,-11.44,34.56,-11.52,34.28,-10.16,33.94,-9.69,33.74,-9.42,32.76,-9.23,32.19,-8.93,31.56,-8.76,31.16,-8.59,30.74,-8.34,30.2,-7.08,29.62,-6.52,29.42,-5.94,29.52,-5.42,29.34,-4.5,29.75,-4.45,30.12,-4.09,30.51,-3.57,30.75,-3.36,30.74,-3.03,30.53,-2.81,30.47,-2.41,30.76,-2.29,30.82,-1.7,30.42,-1.13,30.77,-1.01,31.87,-1.03,33.9,-0.95]]]},{"n":"Western Sahara","c":"EH","r":7,"ml":6,"lx":-12.63,"ly":23.97,"b":[-17.06,21,-8.67,27.66],"p":[[[-8.67,27.66,-8.67,27.59,-8.68,27.4,-8.69,25.88,-11.97,25.93,-11.94,23.37,-12.87,23.28,-13.12,22.77,-12.93,21.33,-16.85,21.33,-17.06,21,-17.02,21.42,-17,21.42,-14.75,21.5,-14.63,21.86,-14.22,22.31,-13.89,23.69,-12.5,24.77,-12.03,26.03,-11.72,26.1,-11.39,26.88,-10.55,26.99,-10.19,26.86,-9.74,26.86,-9.41,27.09,-8.79,27.12,-8.82,27.66,-8.67,27.66]]]},{"n":"Canada","c":"CA","r":2,"ml":1.7,"lx":-101.91,"ly":60.32,"b":[-141,41.68,-52.65,83.23],"p":[[[-122.84,49,-122.97,49,-124.91,49.98,-125.62,50.42,-127.44,50.83,-127.99,51.72,-127.85,52.33,-129.13,52.76,-129.31,53.56,-130.51,54.29,-130.54,54.8,-129.98,55.29,-130.01,55.92,-131.71,56.55,-132.73,57.69,-133.36,58.41,-134.27,58.86,-134.94,59.27,-135.48,59.79,-136.48,59.46,-137.45,58.91,-138.34,59.56,-139.04,60,-140.01,60.28,-141,60.31,-140.99,66,-140.99,69.71,-139.12,69.47,-137.55,68.99,-136.5,68.9,-135.63,69.32,-134.41,69.63,-132.93,69.51,-131.43,69.94,-129.79,70.19,-129.11,69.78,-128.36,70.01,-128.14,70.48,-127.45,70.38,-125.76,69.48,-124.42,70.16,-124.29,69.4,-123.06,69.56,-122.68,69.86,-121.47,69.8,-119.94,69.38,-117.6,69.01,-116.23,68.84,-115.25,68.91,-113.9,68.4,-115.3,67.9,-113.5,67.69,-110.8,67.81,-109.95,67.98,-108.88,67.38,-107.79,67.89,-108.81,68.31,-108.17,68.65,-106.95,68.7,-106.15,68.8,-105.34,68.56,-104.34,68.02,-103.22,68.1,-101.45,67.65,-99.9,67.81,-98.44,67.78,-98.56,68.4,-97.67,68.58,-96.12,68.24,-96.13,67.29,-95.49,68.09,-94.68,68.06,-94.23,69.07,-95.3,69.69,-96.47,70.09,-96.39,71.19,-95.21,71.92,-93.89,71.76,-92.88,71.32,-91.52,70.19,-92.41,69.7,-90.55,69.5,-90.55,68.47,-89.22,69.26,-88.02,68.62,-88.32,67.87,-87.35,67.2,-86.31,67.92,-85.58,68.78,-85.52,69.88,-84.1,69.81,-82.62,69.66,-81.28,69.16,-81.22,68.67,-81.96,68.13,-81.26,67.6,-81.39,67.11,-83.34,66.41,-84.74,66.26,-85.77,66.56,-86.07,66.06,-87.03,65.21,-87.32,64.78,-88.48,64.1,-89.91,64.03,-90.7,63.61,-90.77,62.96,-91.93,62.84,-93.16,62.02,-94.24,60.9,-94.63,60.11,-94.68,58.95,-93.22,58.78,-92.76,57.85,-92.3,57.09,-90.9,57.28,-89.04,56.85,-88.04,56.47,-87.32,56,-86.07,55.72,-85.01,55.3,-83.36,55.24,-82.27,55.15,-82.44,54.28,-82.13,53.28,-81.4,52.16,-79.91,51.21,-79.14,51.53,-78.6,52.56,-79.12,54.14,-79.83,54.67,-78.23,55.14,-77.1,55.84,-76.54,56.53,-76.62,57.2,-77.3,58.05,-78.52,58.8,-77.34,59.85,-77.77,60.76,-78.11,62.32,-77.41,62.55,-75.7,62.28,-74.67,62.18,-73.84,62.44,-72.91,62.11,-71.68,61.53,-71.37,61.14,-69.59,61.06,-69.62,60.22,-69.29,58.96,-68.37,58.8,-67.65,58.21,-66.2,58.77,-65.25,59.87,-64.58,60.34,-63.8,59.44,-62.5,58.17,-61.4,56.97,-61.8,56.34,-60.47,55.78,-59.57,55.2,-57.98,54.95,-57.33,54.63,-56.94,53.78,-56.16,53.65,-55.76,53.27,-55.68,52.15,-56.41,51.77,-57.13,51.42,-58.77,51.06,-60.03,50.24,-61.72,50.08,-63.86,50.29,-65.36,50.3,-66.4,50.23,-67.24,49.51,-68.51,49.07,-69.95,47.74,-71.1,46.82,-70.26,46.99,-68.65,48.3,-66.55,49.13,-65.06,49.23,-64.17,48.74,-65.12,48.07,-64.8,46.99,-64.47,46.24,-63.17,45.74,-61.52,45.88,-60.52,47.01,-60.45,46.28,-59.8,45.92,-61.04,45.27,-63.25,44.67,-64.25,44.27,-65.36,43.55,-66.12,43.62,-66.16,44.47,-64.43,45.29,-66.03,45.26,-67.14,45.14,-67.79,45.7,-67.79,47.07,-68.23,47.35,-68.9,47.19,-69.24,47.45,-70,46.69,-70.31,45.92,-70.66,45.46,-71.08,45.31,-71.4,45.26,-71.51,45.01,-73.35,45.01,-74.87,45,-75.32,44.82,-76.37,44.1,-76.5,44.02,-76.82,43.63,-77.74,43.63,-78.72,43.63,-79.17,43.47,-79.01,43.27,-78.92,42.97,-78.94,42.86,-80.25,42.37,-81.28,42.21,-82.44,41.68,-82.69,41.68,-83.03,41.83,-83.14,41.98,-83.12,42.08,-82.9,42.43,-82.43,42.98,-82.14,43.57,-82.34,44.44,-82.55,45.35,-83.59,45.82,-83.47,45.99,-83.62,46.12,-83.89,46.12,-84.09,46.28,-84.14,46.51,-84.34,46.41,-84.6,46.44,-84.54,46.54,-84.78,46.64,-84.88,46.9,-85.65,47.22,-86.46,47.55,-87.44,47.94,-88.38,48.3,-89.27,48.02,-89.6,48.01,-90.83,48.27,-91.64,48.14,-92.61,48.45,-93.63,48.61,-94.33,48.67,-94.64,48.84,-94.82,49.39,-95.16,49.38,-95.16,49,-97.23,49,-100.65,49,-104.05,49,-107.05,49,-110.05,49,-113,49,-116.05,49,-117.03,49,-120,49,-122.84,49]],[[-83.99,62.45,-83.25,62.91,-81.88,62.9,-81.9,62.71,-83.07,62.16,-83.77,62.18,-83.99,62.45]],[[-79.78,72.8,-80.88,73.33,-80.83,73.69,-80.35,73.76,-78.06,73.65,-76.34,73.1,-76.25,72.83,-77.31,72.86,-78.39,72.88,-79.49,72.74,-79.78,72.8]],[[-80.32,62.09,-79.93,62.39,-79.52,62.36,-79.27,62.16,-79.66,61.63,-80.1,61.72,-80.36,62.02,-80.32,62.09]],[[-93.61,74.98,-94.16,74.59,-95.61,74.67,-96.82,74.93,-96.29,75.38,-94.85,75.65,-93.98,75.3,-93.61,74.98]],[[-93.84,77.52,-94.3,77.49,-96.17,77.56,-96.44,77.83,-94.42,77.82,-93.72,77.63,-93.84,77.52]],[[-96.75,78.77,-95.56,78.42,-95.83,78.06,-97.31,77.85,-98.12,78.08,-98.55,78.46,-98.63,78.87,-97.34,78.83,-96.75,78.77]],[[-88.15,74.39,-89.76,74.52,-92.42,74.84,-92.77,75.39,-92.89,75.88,-93.89,76.32,-95.96,76.44,-97.12,76.75,-96.75,77.16,-94.68,77.1,-93.57,76.78,-91.61,76.78,-90.74,76.45,-90.97,76.07,-89.82,75.85,-89.19,75.61,-87.84,75.57,-86.38,75.48,-84.79,75.7,-82.75,75.78,-81.13,75.71,-80.06,75.34,-79.83,74.92,-80.46,74.66,-81.95,74.44,-83.23,74.56,-86.1,74.41,-88.15,74.39]],[[-111.26,78.15,-109.85,78,-110.19,77.7,-112.05,77.41,-113.53,77.73,-112.72,78.05,-111.26,78.15]],[[-110.96,78.8,-109.66,78.6,-110.88,78.41,-112.54,78.41,-112.53,78.55,-111.5,78.85,-110.96,78.8]],[[-55.6,51.32,-56.13,50.69,-56.8,49.81,-56.14,50.15,-55.47,49.94,-55.82,49.59,-54.94,49.31,-54.47,49.56,-53.48,49.25,-53.79,48.52,-53.09,48.69,-52.96,48.16,-52.65,47.54,-53.07,46.66,-53.52,46.62,-54.18,46.81,-53.96,47.63,-54.24,47.75,-55.4,46.88,-56,46.92,-55.29,47.39,-56.25,47.63,-57.33,47.57,-59.27,47.6,-59.42,47.9,-58.8,48.25,-59.23,48.52,-58.39,49.13,-57.36,50.72,-56.74,51.29,-55.87,51.63,-55.41,51.59,-55.6,51.32]],[[-83.88,65.11,-82.79,64.77,-81.64,64.46,-81.55,63.98,-80.82,64.06,-80.1,63.73,-80.99,63.41,-82.55,63.65,-83.11,64.1,-84.1,63.57,-85.52,63.05,-85.87,63.64,-87.22,63.54,-86.35,64.04,-86.22,64.82,-85.88,65.74,-85.16,65.66,-84.98,65.22,-84.46,65.37,-83.88,65.11]],[[-78.77,72.35,-77.82,72.75,-75.61,72.24,-74.23,71.77,-74.1,71.33,-72.24,71.56,-71.2,70.92,-68.79,70.53,-67.91,70.12,-66.97,69.19,-68.81,68.72,-66.45,68.07,-64.86,67.85,-63.42,66.93,-61.85,66.86,-62.16,66.16,-63.92,65,-65.15,65.43,-66.72,66.39,-68.02,66.26,-68.14,65.69,-67.09,65.11,-65.73,64.65,-65.32,64.38,-64.67,63.39,-65.01,62.67,-66.28,62.95,-68.78,63.75,-67.37,62.88,-66.33,62.28,-66.17,61.93,-68.88,62.33,-71.02,62.91,-72.24,63.4,-71.89,63.68,-73.38,64.19,-74.83,64.68,-74.82,64.39,-77.71,64.23,-78.56,64.57,-77.9,65.31,-76.02,65.33,-73.96,65.45,-74.29,65.81,-73.94,66.31,-72.65,67.28,-72.93,67.73,-73.31,68.07,-74.84,68.55,-76.87,68.89,-76.23,69.15,-77.29,69.77,-78.17,69.83,-78.96,70.17,-79.49,69.87,-81.31,69.74,-84.94,69.97,-87.06,70.26,-88.68,70.41,-89.51,70.76,-88.47,71.22,-89.89,71.22,-90.21,72.24,-89.44,73.13,-88.41,73.54,-85.83,73.8,-86.56,73.16,-85.77,72.53,-84.85,73.34,-82.32,73.75,-80.6,72.72,-80.75,72.06,-78.77,72.35]],[[-94.5,74.13,-92.42,74.1,-90.51,73.86,-92,72.97,-93.2,72.77,-94.27,72.02,-95.41,72.06,-96.03,72.94,-96.02,73.44,-95.5,73.86,-94.5,74.13]],[[-122.85,76.12,-121.16,76.86,-119.1,77.51,-117.57,77.5,-116.2,77.65,-116.34,76.88,-117.11,76.53,-118.04,76.48,-119.9,76.05,-121.5,75.9,-122.85,76.12]],[[-132.71,54.04,-131.75,54.12,-132.05,52.98,-131.18,52.18,-131.58,52.18,-132.18,52.64,-132.55,53.1,-133.05,53.41,-133.24,53.85,-133.18,54.17,-132.71,54.04]],[[-105.49,79.3,-103.53,79.17,-100.83,78.8,-100.06,78.32,-99.67,77.91,-101.3,78.02,-102.95,78.34,-105.18,78.38,-104.21,78.68,-105.42,78.92,-105.49,79.3]],[[-123.51,48.51,-124.01,48.37,-125.66,48.83,-125.95,49.18,-126.85,49.53,-127.03,49.81,-128.06,49.99,-128.44,50.54,-128.36,50.77,-127.31,50.55,-126.7,50.4,-125.76,50.3,-125.42,49.95,-124.92,49.48,-123.92,49.06,-123.51,48.51]],[[-121.54,74.45,-120.11,74.24,-117.56,74.19,-116.58,73.9,-115.51,73.48,-116.77,73.22,-119.22,72.52,-120.46,71.82,-120.46,71.38,-123.09,70.9,-123.62,71.34,-125.93,71.87,-125.5,72.29,-124.81,73.02,-123.94,73.68,-124.92,74.29,-121.54,74.45]],[[-107.82,75.85,-106.93,76.01,-105.88,75.97,-105.7,75.48,-106.31,75.01,-109.7,74.85,-112.22,74.42,-113.74,74.39,-113.87,74.72,-111.79,75.16,-116.31,75.04,-117.71,75.22,-116.35,76.2,-115.4,76.48,-112.59,76.14,-110.81,75.55,-109.07,75.47,-110.5,76.43,-109.58,76.79,-108.55,76.68,-108.21,76.2,-107.82,75.85]],[[-106.52,73.08,-105.4,72.67,-104.77,71.7,-104.46,70.99,-102.79,70.5,-100.98,70.02,-101.09,69.58,-102.73,69.5,-102.09,69.12,-102.43,68.75,-104.24,68.91,-105.96,69.18,-107.12,69.12,-109,68.78,-111.53,68.63,-113.31,68.54,-113.85,69.01,-115.22,69.28,-116.11,69.17,-117.34,69.96,-116.67,70.07,-115.13,70.24,-113.72,70.19,-112.42,70.37,-114.35,70.6,-116.49,70.52,-117.9,70.54,-118.43,70.91,-116.11,71.31,-117.66,71.3,-119.4,71.56,-118.56,72.31,-117.87,72.71,-115.19,73.31,-114.17,73.12,-114.67,72.65,-112.44,72.96,-111.05,72.45,-109.92,72.96,-109.01,72.63,-108.19,71.65,-107.69,72.07,-108.4,73.09,-107.52,73.24,-106.52,73.08]],[[-100.44,72.71,-101.54,73.36,-100.36,73.84,-99.16,73.63,-97.38,73.76,-97.12,73.47,-98.05,72.99,-96.54,72.56,-96.72,71.66,-98.36,71.27,-99.32,71.36,-100.01,71.74,-102.5,72.51,-102.48,72.83,-100.44,72.71]],[[-106.6,73.6,-105.26,73.64,-104.5,73.42,-105.38,72.76,-106.94,73.46,-106.6,73.6]],[[-98.5,76.72,-97.74,76.26,-97.7,75.74,-98.16,75,-99.81,74.9,-100.88,75.06,-100.86,75.64,-102.5,75.56,-102.57,76.34,-101.49,76.31,-99.98,76.65,-98.58,76.59,-98.5,76.72]],[[-96.02,80.6,-95.32,80.91,-94.3,80.98,-94.74,81.21,-92.41,81.26,-91.13,80.72,-89.45,80.51,-87.81,80.32,-87.02,79.66,-85.81,79.34,-87.19,79.04,-89.04,78.29,-90.8,78.22,-92.88,78.34,-93.95,78.75,-93.94,79.11,-93.15,79.38,-94.97,79.37,-96.08,79.71,-96.71,80.16,-96.02,80.6]],[[-91.59,81.89,-90.1,82.09,-88.93,82.12,-86.97,82.28,-85.5,82.65,-84.26,82.6,-83.18,82.32,-82.42,82.86,-81.1,83.02,-79.31,83.13,-76.25,83.17,-75.72,83.06,-72.83,83.23,-70.67,83.17,-68.5,83.11,-65.83,83.03,-63.68,82.9,-61.85,82.63,-61.89,82.36,-64.33,81.93,-66.75,81.73,-67.66,81.5,-65.48,81.51,-67.84,80.9,-69.47,80.62,-71.18,79.8,-73.24,79.63,-73.88,79.43,-76.91,79.32,-75.53,79.2,-76.22,79.02,-75.39,78.53,-76.34,78.18,-77.89,77.9,-78.36,77.51,-79.76,77.21,-79.62,76.98,-77.91,77.02,-77.89,76.78,-80.56,76.18,-83.17,76.45,-86.11,76.3,-87.6,76.42,-89.49,76.47,-89.62,76.95,-87.77,77.18,-88.26,77.9,-87.65,77.97,-84.98,77.54,-86.34,78.18,-87.96,78.37,-87.15,78.76,-85.38,79,-85.09,79.35,-86.51,79.74,-86.93,80.25,-84.2,80.21,-83.41,80.1,-81.85,80.46,-84.1,80.58,-87.6,80.52,-89.37,80.86,-90.2,81.26,-91.37,81.55,-91.59,81.89]],[[-75.22,67.44,-75.87,67.15,-76.99,67.1,-77.24,67.59,-76.81,68.15,-75.9,68.29,-75.11,68.01,-75.1,67.58,-75.22,67.44]],[[-96.26,69.49,-95.65,69.11,-96.27,68.76,-97.62,69.06,-98.43,68.95,-99.8,69.4,-98.92,69.71,-98.22,70.14,-97.16,69.86,-96.56,69.68,-96.26,69.49]],[[-64.52,49.87,-64.17,49.96,-62.86,49.71,-61.84,49.29,-61.81,49.11,-62.29,49.09,-63.59,49.4,-64.52,49.87]],[[-64.01,47.04,-63.66,46.55,-62.94,46.42,-62.01,46.44,-62.5,46.03,-62.87,45.97,-64.14,46.39,-64.39,46.73,-64.01,47.04]]]},{"n":"United States of America","c":"US","r":2,"ml":1.7,"lx":-97.48,"ly":39.54,"b":[-171.79,18.92,-66.96,71.36],"p":[[[-122.84,49,-120,49,-117.03,49,-116.05,49,-113,49,-110.05,49,-107.05,49,-104.05,49,-100.65,49,-97.23,49,-95.16,49,-95.16,49.38,-94.82,49.39,-94.64,48.84,-94.33,48.67,-93.63,48.61,-92.61,48.45,-91.64,48.14,-90.83,48.27,-89.6,48.01,-89.27,48.02,-88.38,48.3,-87.44,47.94,-86.46,47.55,-85.65,47.22,-84.88,46.9,-84.78,46.64,-84.54,46.54,-84.6,46.44,-84.34,46.41,-84.14,46.51,-84.09,46.28,-83.89,46.12,-83.62,46.12,-83.47,45.99,-83.59,45.82,-82.55,45.35,-82.34,44.44,-82.14,43.57,-82.43,42.98,-82.9,42.43,-83.12,42.08,-83.14,41.98,-83.03,41.83,-82.69,41.68,-82.44,41.68,-81.28,42.21,-80.25,42.37,-78.94,42.86,-78.92,42.97,-79.01,43.27,-79.17,43.47,-78.72,43.63,-77.74,43.63,-76.82,43.63,-76.5,44.02,-76.37,44.1,-75.32,44.82,-74.87,45,-73.35,45.01,-71.51,45.01,-71.4,45.26,-71.08,45.31,-70.66,45.46,-70.31,45.92,-70,46.69,-69.24,47.45,-68.9,47.19,-68.23,47.35,-67.79,47.07,-67.79,45.7,-67.14,45.14,-66.96,44.81,-68.03,44.33,-69.06,43.98,-70.12,43.68,-70.65,43.09,-70.81,42.87,-70.82,42.34,-70.49,41.81,-70.08,41.78,-70.18,42.15,-69.88,41.92,-69.97,41.64,-70.64,41.48,-71.12,41.49,-71.86,41.32,-72.29,41.27,-72.88,41.22,-73.71,40.93,-72.24,41.12,-71.94,40.93,-73.34,40.63,-73.98,40.63,-73.95,40.75,-74.26,40.47,-73.96,40.43,-74.18,39.71,-74.91,38.94,-74.98,39.2,-75.2,39.25,-75.53,39.5,-75.32,38.96,-75.07,38.78,-75.06,38.4,-75.38,38.02,-75.94,37.22,-76.03,37.26,-75.72,37.94,-76.23,38.32,-76.35,39.15,-76.54,38.72,-76.33,38.08,-76.99,38.24,-76.3,37.92,-76.26,36.97,-75.97,36.9,-75.87,36.55,-75.73,35.55,-76.36,34.81,-77.4,34.51,-78.05,33.93,-78.55,33.86,-79.06,33.49,-79.2,33.16,-80.3,32.51,-80.86,32.03,-81.34,31.44,-81.49,30.73,-81.31,30.04,-80.98,29.18,-80.54,28.47,-80.53,28.04,-80.06,26.88,-80.09,26.21,-80.13,25.82,-80.38,25.21,-80.68,25.08,-81.17,25.2,-81.33,25.64,-81.71,25.87,-82.24,26.73,-82.71,27.5,-82.86,27.89,-82.65,28.55,-82.93,29.1,-83.71,29.94,-84.1,30.09,-85.11,29.64,-85.29,29.69,-85.77,30.15,-86.4,30.4,-87.53,30.27,-88.42,30.38,-89.18,30.32,-89.59,30.16,-89.41,29.89,-89.43,29.49,-89.22,29.29,-89.41,29.16,-89.78,29.31,-90.15,29.12,-90.88,29.15,-91.63,29.68,-92.5,29.55,-93.23,29.78,-93.85,29.71,-94.69,29.48,-95.6,28.74,-96.59,28.31,-97.14,27.83,-97.37,27.38,-97.38,26.69,-97.33,26.21,-97.14,25.87,-97.53,25.84,-98.24,26.06,-99.02,26.37,-99.3,26.84,-99.52,27.54,-100.11,28.11,-100.46,28.7,-100.96,29.38,-101.66,29.78,-102.48,29.76,-103.11,28.97,-103.94,29.27,-104.46,29.57,-104.71,30.12,-105.04,30.64,-105.63,31.08,-106.14,31.4,-106.51,31.75,-108.24,31.75,-108.24,31.34,-109.03,31.34,-111.02,31.33,-113.3,32.04,-114.81,32.53,-114.72,32.72,-115.99,32.61,-117.13,32.54,-117.3,33.05,-117.94,33.62,-118.41,33.74,-118.52,34.03,-119.08,34.08,-119.44,34.35,-120.37,34.45,-120.62,34.61,-120.74,35.16,-121.71,36.16,-122.55,37.55,-122.51,37.78,-122.95,38.11,-123.73,38.95,-123.87,39.77,-124.4,40.31,-124.18,41.14,-124.21,42,-124.53,42.77,-124.14,43.71,-124.02,44.62,-123.9,45.52,-124.08,46.86,-124.4,47.72,-124.69,48.18,-124.57,48.38,-123.12,48.04,-122.59,47.1,-122.34,47.36,-122.5,48.18,-122.84,49]],[[-155.4,20.08,-155.22,19.99,-155.06,19.86,-154.81,19.51,-154.83,19.45,-155.22,19.24,-155.54,19.08,-155.69,18.92,-155.94,19.06,-155.91,19.34,-156.07,19.7,-156.02,19.81,-155.85,19.98,-155.92,20.17,-155.86,20.27,-155.79,20.25,-155.4,20.08]],[[-156,20.76,-156.08,20.64,-156.41,20.57,-156.59,20.78,-156.7,20.86,-156.71,20.93,-156.61,21.01,-156.26,20.92,-156,20.76]],[[-156.76,21.18,-156.79,21.07,-157.33,21.1,-157.25,21.22,-156.76,21.18]],[[-158.03,21.72,-157.94,21.65,-157.65,21.32,-157.71,21.26,-157.78,21.28,-158.13,21.31,-158.25,21.54,-158.29,21.58,-158.03,21.72]],[[-159.37,22.21,-159.35,21.98,-159.46,21.88,-159.8,22.07,-159.75,22.14,-159.6,22.24,-159.37,22.21]],[[-166.47,60.38,-165.67,60.29,-165.58,59.91,-166.19,59.75,-166.85,59.94,-167.46,60.21,-166.47,60.38]],[[-153.23,57.97,-152.56,57.9,-152.14,57.59,-153.01,57.12,-154.01,56.73,-154.52,56.99,-154.67,57.46,-153.76,57.82,-153.23,57.97]],[[-140.99,69.71,-140.99,66,-141,60.31,-140.01,60.28,-139.04,60,-138.34,59.56,-137.45,58.91,-136.48,59.46,-135.48,59.79,-134.94,59.27,-134.27,58.86,-133.36,58.41,-132.73,57.69,-131.71,56.55,-130.01,55.92,-129.98,55.29,-130.54,54.8,-131.09,55.18,-131.97,55.5,-132.25,56.37,-133.54,57.18,-134.08,58.12,-135.04,58.19,-136.63,58.21,-137.8,58.5,-139.87,59.54,-140.83,59.73,-142.57,60.08,-143.96,60,-145.93,60.46,-147.11,60.88,-148.22,60.67,-148.02,59.98,-148.57,59.91,-149.73,59.71,-150.61,59.37,-151.72,59.16,-151.86,59.74,-151.41,60.73,-150.35,61.03,-150.62,61.28,-151.9,60.73,-152.58,60.06,-154.02,59.35,-153.29,58.86,-154.23,58.15,-155.31,57.73,-156.31,57.42,-156.56,56.98,-158.12,56.46,-158.43,55.99,-159.6,55.57,-160.29,55.64,-161.22,55.36,-162.24,55.02,-163.07,54.69,-164.79,54.4,-164.94,54.57,-163.85,55.04,-162.87,55.35,-161.8,55.89,-160.56,56.01,-160.07,56.42,-158.68,57.02,-158.46,57.22,-157.72,57.57,-157.55,58.33,-157.04,58.92,-158.19,58.62,-158.52,58.79,-159.06,58.42,-159.71,58.93,-159.98,58.57,-160.36,59.07,-161.36,58.67,-161.97,58.67,-162.05,59.27,-161.87,59.63,-162.52,59.99,-163.82,59.8,-164.66,60.27,-165.35,60.51,-165.35,61.07,-166.12,61.5,-165.73,62.07,-164.92,62.63,-164.56,63.15,-163.75,63.22,-163.07,63.06,-162.26,63.54,-161.53,63.46,-160.77,63.77,-160.96,64.22,-161.52,64.4,-160.78,64.79,-161.39,64.78,-162.45,64.56,-162.76,64.34,-163.55,64.56,-164.96,64.45,-166.43,64.69,-166.85,65.09,-168.11,65.67,-166.71,66.09,-164.47,66.58,-163.65,66.58,-163.79,66.08,-161.68,66.12,-162.49,66.74,-163.72,67.12,-164.43,67.62,-165.39,68.04,-166.76,68.36,-166.2,68.88,-164.43,68.92,-163.17,69.37,-162.93,69.86,-161.91,70.33,-160.93,70.45,-159.04,70.89,-158.12,70.82,-156.58,71.36,-155.07,71.15,-154.34,70.7,-153.9,70.89,-152.21,70.83,-152.27,70.6,-150.74,70.43,-149.72,70.53,-147.61,70.21,-145.69,70.12,-144.92,69.99,-143.59,70.15,-142.07,69.85,-140.99,69.71]],[[-171.73,63.78,-171.11,63.59,-170.49,63.69,-169.68,63.43,-168.69,63.3,-168.77,63.19,-169.53,62.98,-170.29,63.19,-170.67,63.38,-171.55,63.32,-171.79,63.41,-171.73,63.78]]]},{"n":"Kazakhstan","c":"KZ","r":3,"ml":2.7,"lx":68.69,"ly":49.05,"b":[46.47,40.66,87.36,55.39],"p":[[[87.36,49.21,86.6,48.55,85.77,48.46,85.72,47.45,85.16,47,83.18,47.33,82.46,45.54,81.95,45.32,79.97,44.92,80.87,43.18,80.18,42.92,80.26,42.35,79.64,42.5,79.14,42.86,77.66,42.96,76,42.99,75.64,42.88,74.21,43.3,73.65,43.09,73.49,42.5,71.84,42.85,71.19,42.7,70.96,42.27,70.39,42.08,69.07,41.38,68.63,40.67,68.26,40.66,67.99,41.14,66.71,41.17,66.51,41.99,66.02,41.99,66.1,43,64.9,43.73,63.19,43.65,62.01,43.5,61.06,44.41,60.24,44.78,58.69,45.5,58.5,45.59,55.93,45,55.97,41.31,55.46,41.26,54.76,42.04,54.08,42.32,52.94,42.12,52.5,41.78,52.45,42.03,52.69,42.44,52.5,42.79,51.34,43.13,50.89,44.03,50.34,44.28,50.31,44.61,51.28,44.51,51.32,45.25,52.17,45.41,53.04,45.26,53.22,46.23,53.04,46.85,52.04,46.8,51.19,47.05,50.03,46.61,49.1,46.4,48.59,46.56,48.69,47.08,48.06,47.74,47.32,47.72,46.47,48.39,47.04,49.15,46.75,49.36,47.55,50.45,48.58,49.87,48.7,50.61,50.77,51.69,52.33,51.72,54.53,51.03,55.72,50.62,56.78,51.04,58.36,51.06,59.64,50.55,59.93,50.84,61.34,50.8,61.59,51.27,59.97,51.96,60.93,52.45,60.74,52.72,61.7,52.98,60.98,53.66,61.44,54.01,65.18,54.35,65.67,54.6,68.17,54.97,69.07,55.39,70.87,55.17,71.18,54.13,72.22,54.38,73.51,54.04,73.43,53.49,74.38,53.55,76.89,54.49,76.53,54.18,77.8,53.4,80.04,50.86,80.57,51.39,81.95,50.81,83.38,51.07,83.94,50.89,84.42,50.31,85.12,50.12,85.54,49.69,86.83,49.83,87.36,49.21]]]},{"n":"Uzbekistan","c":"UZ","r":3,"ml":3,"lx":64.01,"ly":41.69,"b":[55.93,37.14,73.06,45.59],"p":[[[55.97,41.31,55.93,45,58.5,45.59,58.69,45.5,60.24,44.78,61.06,44.41,62.01,43.5,63.19,43.65,64.9,43.73,66.1,43,66.02,41.99,66.51,41.99,66.71,41.17,67.99,41.14,68.26,40.66,68.63,40.67,69.07,41.38,70.39,42.08,70.96,42.27,71.26,42.17,70.42,41.52,71.16,41.14,71.87,41.39,73.06,40.87,71.77,40.15,71.01,40.24,70.6,40.22,70.46,40.5,70.67,40.96,69.33,40.73,69.01,40.09,68.54,39.53,67.7,39.58,67.44,39.14,68.18,38.9,68.39,38.16,67.83,37.14,67.08,37.36,66.52,37.36,66.55,37.97,65.22,38.4,64.17,38.89,63.52,39.36,62.37,40.05,61.88,41.08,61.55,41.27,60.47,41.22,60.08,41.43,59.98,42.22,58.63,42.75,57.79,42.17,56.93,41.83,57.1,41.32,55.97,41.31]]]},{"n":"Papua New Guinea","c":"PG","r":2,"ml":2.5,"lx":143.91,"ly":-5.7,"b":[141,-10.65,156.02,-2.5],"p":[[[141,-2.6,142.74,-3.29,144.58,-3.86,145.27,-4.37,145.83,-4.88,145.98,-5.47,147.65,-6.08,147.89,-6.61,146.97,-6.72,147.19,-7.39,148.08,-8.04,148.73,-9.1,149.31,-9.07,149.27,-9.51,150.04,-9.68,149.74,-9.87,150.8,-10.29,150.69,-10.58,150.03,-10.65,149.78,-10.39,148.92,-10.28,147.91,-10.13,147.14,-9.49,146.57,-8.94,146.05,-8.07,144.74,-7.63,143.9,-7.92,143.29,-8.25,143.41,-8.98,142.63,-9.33,142.07,-9.16,141.03,-9.12,141.02,-5.86,141,-2.6]],[[152.64,-3.66,153.02,-3.98,153.14,-4.5,152.83,-4.77,152.64,-4.18,152.41,-3.79,151.95,-3.46,151.38,-3.04,150.66,-2.74,150.94,-2.5,151.48,-2.78,151.82,-3,152.24,-3.24,152.64,-3.66]],[[151.3,-5.84,150.75,-6.08,150.24,-6.32,149.71,-6.32,148.89,-6.03,148.32,-5.75,148.4,-5.44,149.3,-5.58,149.85,-5.51,150,-5.03,150.14,-5,150.24,-5.53,150.81,-5.46,151.09,-5.11,151.65,-4.76,151.54,-4.17,152.14,-4.15,152.34,-4.31,152.32,-4.87,151.98,-5.48,151.46,-5.56,151.3,-5.84]],[[154.76,-5.34,155.06,-5.57,155.55,-6.2,156.02,-6.54,155.88,-6.82,155.6,-6.92,155.17,-6.54,154.73,-5.9,154.51,-5.14,154.65,-5.04,154.76,-5.34]]]},{"n":"Indonesia","c":"ID","r":2,"ml":1.7,"lx":101.89,"ly":-0.95,"b":[95.29,-10.36,141.03,5.48],"p":[[[141,-2.6,141.02,-5.86,141.03,-9.12,140.14,-8.3,139.13,-8.1,138.88,-8.38,137.61,-8.41,138.04,-7.6,138.67,-7.32,138.41,-6.23,137.93,-5.39,135.99,-4.55,135.16,-4.46,133.66,-3.54,133.37,-4.02,132.98,-4.11,132.76,-3.75,132.75,-3.31,131.99,-2.82,133.07,-2.46,133.78,-2.48,133.7,-2.21,132.23,-2.21,131.84,-1.62,130.94,-1.43,130.52,-0.94,131.87,-0.7,132.38,-0.37,133.99,-0.78,134.14,-1.15,134.42,-2.77,135.46,-3.37,136.29,-2.31,137.44,-1.7,138.33,-1.7,139.18,-2.05,139.93,-2.41,141,-2.6]],[[124.97,-8.89,125.07,-9.09,125.09,-9.39,124.44,-10.14,123.58,-10.36,123.46,-10.24,123.55,-9.9,123.98,-9.29,124.97,-8.89]],[[134.21,-6.9,134.11,-6.14,134.29,-5.78,134.5,-5.45,134.73,-5.74,134.72,-6.21,134.21,-6.9]],[[117.88,4.14,117.31,3.23,118.05,2.29,117.88,1.83,119,0.9,117.81,0.78,117.48,0.1,117.52,-0.8,116.56,-1.49,116.53,-2.48,116.15,-4.01,116,-3.66,114.86,-4.11,114.47,-3.5,113.76,-3.44,113.26,-3.12,112.07,-3.48,111.7,-2.99,111.05,-3.05,110.22,-2.93,110.07,-1.59,109.57,-1.31,109.09,-0.46,108.95,0.42,109.07,1.34,109.66,2.01,109.83,1.34,110.51,0.77,111.16,0.98,111.8,0.9,112.38,1.41,112.86,1.5,113.81,1.22,114.62,1.43,115.13,2.82,115.52,3.17,115.87,4.31,117.02,4.31,117.88,4.14]],[[129.37,-2.8,130.47,-3.09,130.83,-3.86,129.99,-3.45,129.16,-3.36,128.59,-3.43,127.9,-3.39,128.14,-2.84,129.37,-2.8]],[[126.87,-3.79,126.18,-3.61,125.99,-3.18,127,-3.13,127.25,-3.46,126.87,-3.79]],[[127.93,2.17,128,1.63,128.59,1.54,128.69,1.13,128.64,0.26,128.12,0.36,127.97,-0.25,128.38,-0.78,128.1,-0.9,127.7,-0.27,127.4,1.01,127.6,1.81,127.93,2.17]],[[122.93,0.88,124.08,0.92,125.07,1.64,125.24,1.42,124.44,0.43,123.69,0.24,122.72,0.43,121.06,0.38,120.18,0.24,120.04,-0.52,120.94,-1.41,121.48,-0.96,123.34,-0.62,123.26,-1.08,122.82,-0.93,122.39,-1.52,121.51,-1.9,122.45,-3.19,122.27,-3.53,123.17,-4.68,123.16,-5.34,122.63,-5.63,122.24,-5.28,122.72,-4.46,121.74,-4.85,121.49,-4.57,121.62,-4.19,120.9,-3.6,120.97,-2.63,120.31,-2.93,120.39,-4.1,120.43,-5.53,119.8,-5.67,119.37,-5.38,119.65,-4.46,119.5,-3.49,119.08,-3.49,118.77,-2.8,119.18,-2.15,119.32,-1.35,119.83,0.15,120.04,0.57,120.89,1.31,121.67,1.01,122.93,0.88]],[[120.3,-10.26,118.97,-9.56,119.9,-9.36,120.43,-9.67,120.78,-9.97,120.72,-10.24,120.3,-10.26]],[[121.34,-8.54,122.01,-8.46,122.9,-8.09,122.76,-8.65,121.25,-8.93,119.92,-8.81,119.92,-8.44,120.72,-8.24,121.34,-8.54]],[[118.26,-8.36,118.88,-8.28,119.13,-8.71,117.97,-8.91,117.28,-9.04,116.74,-9.03,117.08,-8.46,117.63,-8.45,117.9,-8.1,118.26,-8.36]],[[108.49,-6.42,108.62,-6.78,110.54,-6.88,110.76,-6.47,112.61,-6.95,112.98,-7.59,114.48,-7.78,115.71,-8.37,114.56,-8.75,113.46,-8.35,112.56,-8.38,111.52,-8.3,110.59,-8.12,109.43,-7.74,108.69,-7.64,108.28,-7.77,106.45,-7.35,106.28,-6.92,105.37,-6.85,106.05,-5.9,107.27,-5.95,108.07,-6.35,108.49,-6.42]],[[104.37,-1.08,104.54,-1.78,104.89,-2.34,105.62,-2.43,106.11,-3.06,105.86,-4.31,105.82,-5.85,104.71,-5.87,103.87,-5.04,102.58,-4.22,102.16,-3.61,101.4,-2.8,100.9,-2.05,100.14,-0.65,99.26,0.18,98.97,1.04,98.6,1.82,97.7,2.45,97.18,3.31,96.42,3.87,95.38,4.97,95.29,5.48,95.94,5.44,97.48,5.25,98.37,4.27,99.14,3.59,99.69,3.17,100.64,2.1,101.66,2.08,102.5,1.4,103.08,0.56,103.84,0.1,103.44,-0.71,104.01,-1.06,104.37,-1.08]]]},{"n":"Argentina","c":"AR","r":2,"ml":2,"lx":-64.17,"ly":-33.5,"b":[-73.42,-55.25,-53.63,-21.83],"p":[[[-68.63,-52.64,-68.25,-53.1,-67.75,-53.85,-66.45,-54.45,-65.05,-54.7,-65.5,-55.2,-66.45,-55.25,-66.96,-54.9,-67.56,-54.87,-68.63,-54.87,-68.63,-52.64]],[[-57.63,-30.22,-57.87,-31.02,-58.14,-32.04,-58.13,-33.04,-58.35,-33.26,-58.43,-33.91,-58.5,-34.43,-57.23,-35.29,-57.36,-35.98,-56.74,-36.41,-56.79,-36.9,-57.75,-38.18,-59.23,-38.72,-61.24,-38.93,-62.34,-38.83,-62.13,-39.42,-62.33,-40.17,-62.15,-40.68,-62.75,-41.03,-63.77,-41.17,-64.73,-40.8,-65.12,-41.06,-64.98,-42.06,-64.3,-42.36,-63.76,-42.04,-63.46,-42.56,-64.38,-42.87,-65.18,-43.5,-65.33,-44.5,-65.57,-45.04,-66.51,-45.04,-67.29,-45.55,-67.58,-46.3,-66.6,-47.03,-65.64,-47.24,-65.99,-48.13,-67.17,-48.7,-67.82,-49.87,-68.73,-50.26,-69.14,-50.73,-68.82,-51.77,-68.15,-52.35,-68.57,-52.3,-69.5,-52.14,-71.91,-52.01,-72.33,-51.43,-72.31,-50.68,-72.98,-50.74,-73.33,-50.38,-73.42,-49.32,-72.65,-48.88,-72.33,-48.24,-72.45,-47.74,-71.92,-46.88,-71.55,-45.56,-71.66,-44.97,-71.22,-44.78,-71.33,-44.41,-71.79,-44.21,-71.46,-43.79,-71.92,-43.41,-72.15,-42.25,-71.75,-42.05,-71.92,-40.83,-71.68,-39.81,-71.41,-38.92,-70.81,-38.55,-71.12,-37.58,-71.12,-36.66,-70.36,-36.01,-70.39,-35.17,-69.82,-34.19,-69.81,-33.27,-70.07,-33.09,-70.54,-31.37,-69.92,-30.34,-70.01,-29.37,-69.66,-28.46,-69,-27.52,-68.3,-26.9,-68.59,-26.51,-68.39,-26.19,-68.42,-24.52,-67.33,-24.03,-66.99,-22.99,-67.11,-22.74,-66.27,-21.83,-64.96,-22.08,-64.38,-22.8,-63.99,-21.99,-62.85,-22.03,-62.69,-22.25,-60.85,-23.88,-60.03,-24.03,-58.81,-24.77,-57.78,-25.16,-57.63,-25.6,-58.62,-27.12,-57.61,-27.4,-56.49,-27.55,-55.7,-27.39,-54.79,-26.62,-54.63,-25.74,-54.13,-25.55,-53.63,-26.12,-53.65,-26.92,-54.49,-27.47,-55.16,-27.88,-56.29,-28.85,-57.63,-30.22]]]},{"n":"Chile","c":"CL","r":2,"ml":1.7,"lx":-72.32,"ly":-38.15,"b":[-75.64,-55.61,-66.96,-17.58],"p":[[[-68.63,-52.64,-68.63,-54.87,-67.56,-54.87,-66.96,-54.9,-67.29,-55.3,-68.15,-55.61,-68.64,-55.58,-69.23,-55.5,-69.96,-55.2,-71.01,-55.05,-72.26,-54.5,-73.29,-53.96,-74.66,-52.84,-73.84,-53.05,-72.43,-53.72,-71.11,-54.07,-70.59,-53.62,-70.27,-52.93,-69.35,-52.52,-68.63,-52.64]],[[-69.59,-17.58,-69.1,-18.26,-68.97,-18.98,-68.44,-19.41,-68.76,-20.37,-68.22,-21.49,-67.83,-22.87,-67.11,-22.74,-66.99,-22.99,-67.33,-24.03,-68.42,-24.52,-68.39,-26.19,-68.59,-26.51,-68.3,-26.9,-69,-27.52,-69.66,-28.46,-70.01,-29.37,-69.92,-30.34,-70.54,-31.37,-70.07,-33.09,-69.81,-33.27,-69.82,-34.19,-70.39,-35.17,-70.36,-36.01,-71.12,-36.66,-71.12,-37.58,-70.81,-38.55,-71.41,-38.92,-71.68,-39.81,-71.92,-40.83,-71.75,-42.05,-72.15,-42.25,-71.92,-43.41,-71.46,-43.79,-71.79,-44.21,-71.33,-44.41,-71.22,-44.78,-71.66,-44.97,-71.55,-45.56,-71.92,-46.88,-72.45,-47.74,-72.33,-48.24,-72.65,-48.88,-73.42,-49.32,-73.33,-50.38,-72.98,-50.74,-72.31,-50.68,-72.33,-51.43,-71.91,-52.01,-69.5,-52.14,-68.57,-52.3,-69.46,-52.29,-69.94,-52.54,-70.85,-52.9,-71.01,-53.83,-71.43,-53.86,-72.56,-53.53,-73.7,-52.84,-74.95,-52.26,-75.26,-51.63,-74.98,-51.04,-75.48,-50.38,-75.61,-48.67,-75.18,-47.71,-74.13,-46.94,-75.64,-46.65,-74.69,-45.76,-74.35,-44.1,-73.24,-44.45,-72.72,-42.38,-73.39,-42.12,-73.7,-43.37,-74.33,-43.22,-74.02,-41.79,-73.68,-39.94,-73.22,-39.26,-73.51,-38.28,-73.59,-37.16,-73.17,-37.12,-72.55,-35.51,-71.86,-33.91,-71.44,-32.42,-71.67,-30.92,-71.37,-30.1,-71.49,-28.86,-70.91,-27.64,-70.72,-25.71,-70.4,-23.63,-70.09,-21.39,-70.16,-19.76,-70.37,-18.35,-69.86,-18.09,-69.59,-17.58]]]},{"n":"Democratic Republic of the Congo","c":"CD","r":2,"ml":2,"lx":23.46,"ly":-1.86,"b":[12.18,-13.26,31.17,5.26],"p":[[[29.34,-4.5,29.52,-5.42,29.42,-5.94,29.62,-6.52,30.2,-7.08,30.74,-8.34,30.35,-8.24,29,-8.41,28.73,-8.53,28.45,-9.16,28.67,-9.61,28.5,-10.79,28.37,-11.79,28.64,-11.97,29.34,-12.36,29.62,-12.18,29.7,-13.26,28.93,-13.25,28.52,-12.7,28.16,-12.27,27.39,-12.13,27.16,-11.61,26.55,-11.92,25.75,-11.78,25.42,-11.33,24.78,-11.24,24.31,-11.26,24.26,-10.95,23.91,-10.93,23.46,-10.87,22.84,-11.02,22.4,-10.99,22.16,-11.08,22.21,-9.89,21.88,-9.52,21.8,-8.91,21.95,-8.31,21.75,-7.92,21.73,-7.29,20.51,-7.3,20.6,-6.94,20.09,-6.94,20.04,-7.12,19.42,-7.16,19.17,-7.74,19.02,-7.99,18.46,-7.85,18.13,-7.99,17.47,-8.07,17.09,-7.55,16.86,-7.22,16.57,-6.62,16.33,-5.88,13.38,-5.86,13.02,-5.98,12.74,-5.97,12.32,-6.1,12.18,-5.79,12.44,-5.68,12.47,-5.25,12.63,-4.99,13,-4.78,13.26,-4.88,13.6,-4.5,14.14,-4.51,14.21,-4.79,14.58,-4.97,15.17,-4.34,15.75,-3.86,16.01,-3.54,15.97,-2.71,16.41,-1.74,16.87,-1.23,17.52,-0.74,17.64,-0.42,17.66,-0.06,17.83,0.29,17.77,0.86,17.9,1.74,18.09,2.37,18.39,2.9,18.45,3.5,18.54,4.2,18.93,4.71,19.47,5.03,20.29,4.69,20.93,4.32,21.66,4.22,22.41,4.03,22.7,4.63,22.84,4.71,23.3,4.61,24.41,5.11,24.81,4.9,25.13,4.93,25.28,5.17,25.65,5.26,26.4,5.15,27.04,5.13,27.37,5.23,27.98,4.41,28.43,4.29,28.7,4.46,29.16,4.39,29.72,4.6,29.95,4.17,30.83,3.51,30.77,2.34,31.17,2.2,30.85,1.85,30.47,1.58,30.09,1.06,29.88,0.6,29.82,-0.21,29.59,-0.59,29.58,-1.34,29.29,-1.62,29.25,-2.22,29.12,-2.29,29.02,-2.84,29.28,-3.29,29.34,-4.5]]]},{"n":"Somalia","c":"SO","r":6,"ml":4,"lx":45.19,"ly":3.57,"b":[40.98,-1.68,51.13,12.02],"p":[[[41.59,-1.68,40.99,-0.86,40.98,2.78,41.86,3.92,42.13,4.23,42.77,4.25,43.66,4.96,44.96,5,47.79,8,48.49,8.84,48.94,9.45,48.94,9.97,48.94,10.98,48.94,11.39,48.95,11.41,49.27,11.43,49.73,11.58,50.26,11.68,50.73,12.02,51.11,12.02,51.13,11.75,51.04,11.17,51.05,10.64,50.83,10.28,50.55,9.2,50.07,8.08,49.45,6.8,48.59,5.34,47.74,4.22,46.56,2.86,45.56,2.05,44.07,1.05,43.14,0.29,42.04,-0.92,41.81,-1.45,41.59,-1.68]]]},{"n":"Kenya","c":"KE","r":2,"ml":1.7,"lx":37.91,"ly":0.55,"b":[33.89,-4.68,41.86,5.51],"p":[[[39.2,-4.68,37.77,-3.68,37.7,-3.1,34.07,-1.06,33.9,-0.95,33.89,0.11,34.18,0.52,34.67,1.18,35.04,1.91,34.6,3.05,34.48,3.56,34.01,4.25,34.62,4.85,35.3,5.51,35.82,5.34,35.82,4.78,36.16,4.45,36.86,4.45,38.12,3.6,38.44,3.59,38.67,3.62,38.89,3.5,39.56,3.42,39.85,3.84,40.77,4.26,41.17,3.92,41.86,3.92,40.98,2.78,40.99,-0.86,41.59,-1.68,40.88,-2.08,40.64,-2.5,40.26,-2.57,40.12,-3.28,39.8,-3.68,39.6,-4.35,39.2,-4.68]]]},{"n":"Sudan","c":"SD","r":3,"ml":2.5,"lx":29.26,"ly":16.33,"b":[21.94,8.23,38.41,22],"p":[[[24.57,8.23,23.81,8.67,23.46,8.95,23.39,9.27,23.56,9.68,23.55,10.09,22.98,10.71,22.86,11.14,22.88,11.38,22.51,11.68,22.5,12.26,22.29,12.65,21.94,12.59,22.04,12.96,22.3,13.37,22.18,13.79,22.51,14.09,22.3,14.33,22.57,14.94,23.02,15.68,23.89,15.61,23.84,19.58,23.85,20,25,20,25,22,29.02,22,32.9,22,36.87,22,37.19,21.02,36.97,20.84,37.11,19.81,37.48,18.61,37.86,18.37,38.41,18,37.9,17.43,37.17,17.26,36.85,16.96,36.75,16.29,36.32,14.82,36.43,14.42,36.27,13.56,35.86,12.58,35.26,12.08,34.83,11.32,34.73,10.91,34.26,10.63,33.96,9.58,33.97,8.68,33.96,9.46,33.82,9.48,33.84,9.98,33.72,10.33,33.21,10.72,33.09,11.44,33.21,12.18,32.74,12.25,32.67,12.02,32.07,11.97,32.31,11.68,32.4,11.08,31.85,10.53,31.35,9.81,30.84,9.71,30,10.29,29.62,10.08,29.52,9.79,29,9.6,28.97,9.4,27.97,9.4,27.83,9.6,27.11,9.64,26.75,9.47,26.48,9.55,25.96,10.14,25.79,10.41,25.07,10.27,24.79,9.81,24.54,8.92,24.19,8.73,23.89,8.62,24.57,8.23]]]},{"n":"Chad","c":"TD","r":3,"ml":3,"lx":18.65,"ly":15.14,"b":[13.54,7.42,23.89,23.41],"p":[[[23.84,19.58,23.89,15.61,23.02,15.68,22.57,14.94,22.3,14.33,22.51,14.09,22.18,13.79,22.3,13.37,22.04,12.96,21.94,12.59,22.29,12.65,22.5,12.26,22.51,11.68,22.88,11.38,22.86,11.14,22.23,10.97,21.72,10.57,21,9.48,20.06,9.01,19.09,9.07,18.81,8.98,18.91,8.63,18.39,8.28,17.96,7.89,16.71,7.51,16.46,7.73,16.29,7.75,16.11,7.5,15.28,7.42,15.44,7.69,15.12,8.38,14.98,8.8,14.54,8.97,13.95,9.55,14.17,10.02,14.63,9.92,14.91,9.99,15.47,9.98,14.92,10.89,14.96,11.56,14.89,12.22,14.5,12.86,14.6,13.33,13.95,13.35,13.96,14,13.54,14.37,13.97,15.68,15.25,16.63,15.3,17.93,15.69,19.96,15.9,20.39,15.49,20.73,15.47,21.05,15.1,21.31,14.85,22.86,15.86,23.41,19.85,21.5,23.84,19.58]]]},{"n":"Haiti","c":"HT","r":5,"ml":4,"lx":-72.22,"ly":19.26,"b":[-74.46,18.03,-71.62,19.92],"p":[[[-71.71,19.71,-71.62,19.17,-71.7,18.79,-71.95,18.62,-71.69,18.32,-71.71,18.04,-72.37,18.21,-72.84,18.15,-73.45,18.22,-73.92,18.03,-74.46,18.34,-74.37,18.66,-73.45,18.53,-72.69,18.45,-72.33,18.67,-72.79,19.1,-72.78,19.48,-73.42,19.64,-73.19,19.92,-72.58,19.87,-71.71,19.71]]]},{"n":"Dominican Republic","c":"DO","r":5,"ml":4.5,"lx":-70.65,"ly":19.1,"b":[-71.95,17.6,-68.32,19.88],"p":[[[-71.71,18.04,-71.69,18.32,-71.95,18.62,-71.7,18.79,-71.62,19.17,-71.71,19.71,-71.59,19.88,-70.81,19.88,-70.21,19.62,-69.95,19.65,-69.77,19.29,-69.22,19.31,-69.25,19.02,-68.81,18.98,-68.32,18.61,-68.69,18.21,-69.16,18.42,-69.62,18.38,-69.95,18.43,-70.13,18.25,-70.52,18.18,-70.67,18.43,-71,18.28,-71.4,17.6,-71.66,17.76,-71.71,18.04]]]},{"n":"Russia","c":"RU","r":2,"ml":1.7,"lx":44.69,"ly":58.25,"b":[-180,41.15,180,81.25],"p":[[[178.73,71.1,180,71.52,180,70.83,178.9,70.78,178.73,71.1]],[[49.1,46.4,48.65,45.81,47.68,45.64,46.68,44.61,47.59,43.66,47.49,42.99,48.58,41.81,47.99,41.41,47.82,41.15,47.37,41.22,46.69,41.83,46.4,41.86,45.78,42.09,45.47,42.5,44.54,42.71,43.93,42.55,43.76,42.74,42.39,43.22,40.92,43.38,40.08,43.55,39.96,43.43,38.68,44.28,37.54,44.66,36.68,45.24,37.4,45.4,38.23,46.24,37.67,46.64,39.15,47.04,39.12,47.26,38.22,47.1,38.26,47.55,38.77,47.83,39.74,47.9,39.9,48.23,39.67,48.78,40.08,49.31,40.07,49.6,38.59,49.93,38.01,49.92,37.39,50.38,36.63,50.23,35.36,50.58,35.38,50.77,35.02,51.21,34.22,51.26,34.14,51.57,34.39,51.77,33.75,52.34,32.72,52.24,32.41,52.29,32.16,52.06,31.79,52.1,31.54,52.74,31.31,53.07,31.5,53.17,32.3,53.13,32.69,53.35,32.41,53.62,31.73,53.79,31.79,53.97,31.38,54.16,30.76,54.81,30.97,55.08,30.87,55.55,29.9,55.79,29.37,55.67,29.23,55.92,28.18,56.17,27.86,56.76,27.77,57.24,27.29,57.47,27.72,57.79,27.42,58.72,28.13,59.3,27.98,59.48,29.12,60.03,28.07,60.5,30.21,61.78,31.14,62.36,31.52,62.87,30.04,63.55,30.44,64.2,29.54,64.95,30.22,65.81,29.05,66.94,29.98,67.7,28.45,68.36,28.59,69.06,29.4,69.16,31.1,69.56,32.13,69.91,33.78,69.3,36.51,69.06,40.29,67.93,41.06,67.46,41.13,66.79,40.02,66.27,38.38,66,33.92,66.76,33.18,66.63,34.81,65.9,34.88,65.44,34.94,64.41,36.23,64.11,37.01,63.85,37.14,64.33,36.54,64.76,37.18,65.14,39.59,64.52,40.44,64.76,39.76,65.5,42.09,66.48,43.02,66.42,43.95,66.07,44.53,66.76,43.7,67.35,44.19,67.95,43.45,68.57,46.25,68.25,46.82,67.69,45.56,67.57,45.56,67.01,46.35,66.67,47.89,66.88,48.14,67.52,50.23,68,53.72,68.86,54.47,68.81,53.49,68.2,54.73,68.1,55.44,68.44,57.32,68.47,58.8,68.88,59.94,68.28,61.08,68.94,60.03,69.52,60.55,69.85,63.5,69.55,64.89,69.23,68.51,68.09,69.18,68.62,68.16,69.14,68.14,69.36,66.93,69.45,67.26,69.93,66.72,70.71,66.69,71.03,68.54,71.93,69.2,72.84,69.94,73.04,72.59,72.78,72.8,72.22,71.85,71.41,72.47,71.09,72.79,70.39,72.56,69.02,73.67,68.41,73.24,67.74,71.28,66.32,72.42,66.17,72.82,66.53,73.92,66.79,74.19,67.28,75.05,67.76,74.47,68.33,74.94,68.99,73.84,69.07,73.6,69.63,74.4,70.63,73.1,71.45,74.89,72.12,74.66,72.83,75.16,72.85,75.68,72.3,75.29,71.34,76.36,71.15,75.9,71.87,77.58,72.27,79.65,72.32,81.5,71.75,80.61,72.58,80.51,73.65,82.25,73.85,84.66,73.81,86.82,73.94,86.01,74.46,87.17,75.12,88.32,75.14,90.26,75.64,92.9,75.77,93.23,76.05,95.86,76.14,96.68,75.92,98.92,76.45,100.76,76.43,101.04,76.86,101.99,77.29,104.35,77.7,106.07,77.37,104.71,77.13,106.97,76.97,107.24,76.48,108.15,76.72,111.08,76.71,113.33,76.22,114.13,75.85,113.89,75.33,112.78,75.03,110.15,74.48,109.4,74.18,110.64,74.04,112.12,73.79,113.02,73.98,113.53,73.34,113.97,73.59,115.57,73.75,118.78,73.59,119.02,73.12,123.2,72.97,123.26,73.74,125.38,73.56,126.98,73.57,128.59,73.04,129.05,72.4,128.46,71.98,129.72,71.19,131.29,70.79,132.25,71.84,133.86,71.39,135.56,71.66,137.5,71.35,138.23,71.63,139.87,71.49,139.15,72.42,140.47,72.85,149.5,72.2,150.35,71.61,152.97,70.84,157.01,71.03,159,70.87,159.83,70.45,159.71,69.72,160.94,69.44,162.28,69.64,164.05,69.67,165.94,69.47,167.84,69.58,169.58,68.69,170.82,69.01,170.01,69.65,170.45,70.1,173.64,69.82,175.72,69.88,178.6,69.4,180,68.96,180,64.98,179.99,64.97,178.71,64.53,177.41,64.61,178.31,64.08,178.91,63.25,179.37,62.98,179.49,62.57,179.23,62.3,177.36,62.52,174.57,61.77,173.68,61.65,172.15,60.95,170.7,60.34,170.33,59.88,168.9,60.57,166.29,59.79,165.84,60.16,164.88,59.73,163.54,59.87,163.22,59.21,162.02,58.24,162.05,57.84,163.19,57.62,163.06,56.16,162.13,56.12,161.7,55.29,162.12,54.86,160.37,54.34,160.02,53.2,158.53,52.96,158.23,51.94,156.79,51.01,156.42,51.7,155.99,53.16,155.43,55.38,155.91,56.77,156.76,57.36,156.81,57.83,158.36,58.06,160.15,59.31,161.87,60.34,163.67,61.14,164.47,62.55,163.26,62.47,162.66,61.64,160.12,60.54,159.3,61.77,156.72,61.43,154.22,59.76,155.04,59.14,152.81,58.88,151.27,58.78,151.34,59.5,149.78,59.66,148.54,59.16,145.49,59.34,142.2,59.04,138.96,57.09,135.13,54.73,136.7,54.6,137.19,53.98,138.16,53.76,138.8,54.25,139.9,54.19,141.35,53.09,141.38,52.24,140.6,51.24,140.51,50.05,140.06,48.45,138.55,47,138.22,46.31,136.86,45.14,135.52,43.99,134.87,43.4,133.54,42.81,132.91,42.8,132.28,43.28,130.94,42.55,130.78,42.22,130.64,42.4,130.63,42.9,131.14,42.93,131.29,44.11,131.03,44.97,131.88,45.32,133.1,45.14,133.77,46.12,134.11,47.21,134.5,47.58,135.03,48.48,133.37,48.18,132.51,47.79,130.99,47.79,130.58,48.73,129.4,49.44,127.66,49.76,127.29,50.74,126.94,51.35,126.56,51.78,125.95,52.79,125.07,53.16,123.57,53.46,122.25,53.43,121,53.25,120.18,52.75,120.73,52.52,120.74,51.96,120.18,51.64,119.28,50.58,119.29,50.14,117.88,49.51,116.68,49.89,115.49,49.81,114.96,50.14,114.36,50.25,112.9,49.54,111.58,49.38,110.66,49.13,109.4,49.29,108.48,49.28,107.87,49.79,106.89,50.27,105.89,50.41,104.62,50.28,103.68,50.09,102.26,50.51,102.07,51.26,100.89,51.52,99.98,51.63,98.86,52.05,97.83,51.01,98.23,50.42,97.26,49.73,95.81,49.98,94.82,50.01,94.15,50.48,93.1,50.5,92.23,50.8,90.71,50.33,88.81,49.47,87.75,49.3,87.36,49.21,86.83,49.83,85.54,49.69,85.12,50.12,84.42,50.31,83.94,50.89,83.38,51.07,81.95,50.81,80.57,51.39,80.04,50.86,77.8,53.4,76.53,54.18,76.89,54.49,74.38,53.55,73.43,53.49,73.51,54.04,72.22,54.38,71.18,54.13,70.87,55.17,69.07,55.39,68.17,54.97,65.67,54.6,65.18,54.35,61.44,54.01,60.98,53.66,61.7,52.98,60.74,52.72,60.93,52.45,59.97,51.96,61.59,51.27,61.34,50.8,59.93,50.84,59.64,50.55,58.36,51.06,56.78,51.04,55.72,50.62,54.53,51.03,52.33,51.72,50.77,51.69,48.7,50.61,48.58,49.87,47.55,50.45,46.75,49.36,47.04,49.15,46.47,48.39,47.32,47.72,48.06,47.74,48.69,47.08,48.59,46.56,49.1,46.4]],[[93.78,81.02,95.94,81.25,97.88,80.75,100.19,79.78,99.94,78.88,97.76,78.76,94.97,79.04,93.31,79.43,92.55,80.14,91.18,80.34,93.78,81.02]],[[102.84,79.28,105.37,78.71,105.08,78.31,99.44,77.92,101.26,79.23,102.09,79.35,102.84,79.28]],[[138.83,76.14,141.47,76.09,145.09,75.56,144.3,74.82,140.61,74.85,138.96,74.61,136.97,75.26,137.51,75.95,138.83,76.14]],[[148.22,75.35,150.73,75.08,149.58,74.69,147.98,74.78,146.12,75.17,146.36,75.5,148.22,75.35]],[[139.86,73.37,140.81,73.77,142.06,73.86,143.48,73.48,143.6,73.21,142.09,73.21,140.04,73.32,139.86,73.37]],[[44.85,80.59,46.8,80.77,48.32,80.78,48.52,80.51,49.1,80.75,50.04,80.92,51.52,80.7,51.14,80.55,49.79,80.42,48.89,80.34,48.75,80.18,47.59,80.01,46.5,80.25,47.07,80.56,44.85,80.59]],[[22.73,54.33,20.89,54.31,19.66,54.43,19.89,54.87,21.27,55.19,22.32,55.02,22.76,54.86,22.65,54.58,22.73,54.33]],[[53.51,73.75,55.9,74.63,55.63,75.08,57.87,75.61,61.17,76.25,64.5,76.44,66.21,76.81,68.16,76.94,68.85,76.54,68.18,76.23,64.64,75.74,61.58,75.26,58.48,74.31,56.99,73.33,55.42,72.37,55.62,71.54,57.54,70.72,56.94,70.63,53.68,70.76,53.41,71.21,51.6,71.47,51.46,72.01,52.48,72.23,52.44,72.77,54.43,73.63,53.51,73.75]],[[142.91,53.7,143.26,52.74,143.24,51.76,143.65,50.75,144.65,48.98,143.17,49.31,142.56,47.86,143.53,46.84,143.51,46.14,142.75,46.74,142.09,45.97,141.91,46.81,142.02,47.78,141.9,48.86,142.14,49.62,142.18,50.95,141.59,51.94,141.68,53.3,142.61,53.76,142.21,54.23,142.65,54.37,142.91,53.7]],[[-174.93,67.21,-175.01,66.58,-174.34,66.34,-174.57,67.06,-171.86,66.91,-169.9,65.98,-170.89,65.54,-172.53,65.44,-172.55,64.46,-172.96,64.25,-173.89,64.28,-174.65,64.63,-175.98,64.92,-176.21,65.36,-177.22,65.52,-178.36,65.39,-178.9,65.74,-178.69,66.11,-179.88,65.87,-179.43,65.4,-180,64.98,-180,68.96,-177.55,68.2,-174.93,67.21]],[[-178.69,70.89,-180,70.83,-180,71.52,-179.87,71.56,-179.02,71.56,-177.58,71.27,-177.66,71.13,-178.69,70.89]],[[33.44,45.97,33.7,46.22,34.41,46.01,34.73,45.97,34.86,45.77,35.01,45.74,35.02,45.65,35.51,45.41,36.53,45.47,36.33,45.11,35.24,44.94,33.88,44.36,33.33,44.56,33.55,45.03,32.45,45.33,32.63,45.52,33.59,45.85,33.44,45.97]]]},{"n":"The Bahamas","c":"BS","r":4,"ml":4,"lx":-77.15,"ly":26.4,"b":[-78.98,23.71,-77,27.04],"p":[[[-78.98,26.79,-78.51,26.87,-77.85,26.84,-77.82,26.58,-78.91,26.42,-78.98,26.79]],[[-77.79,27.04,-77,26.59,-77.17,25.88,-77.36,26.01,-77.34,26.53,-77.79,26.93,-77.79,27.04]],[[-78.19,25.21,-77.89,25.17,-77.54,24.34,-77.53,23.76,-77.78,23.71,-78.03,24.29,-78.41,24.58,-78.19,25.21]]]},{"n":"Falkland Islands","c":"FK","r":5,"ml":4.5,"lx":-58.74,"ly":-51.61,"b":[-61.2,-52.3,-57.75,-51.1],"p":[[[-61.2,-51.85,-60,-51.25,-59.15,-51.5,-58.55,-51.1,-57.75,-51.55,-58.05,-51.9,-59.4,-52.2,-59.85,-51.85,-60.7,-52.3,-61.2,-51.85]]]},{"n":"Norway","c":"NO","r":3,"ml":3,"lx":9.68,"ly":61.36,"b":[4.99,58.08,31.29,80.66],"p":[[[15.14,79.67,15.52,80.02,16.99,80.05,18.25,79.7,21.54,78.96,19.03,78.56,18.47,77.83,17.59,77.64,17.12,76.81,15.91,76.77,13.76,77.38,14.67,77.74,13.17,78.02,11.22,78.87,10.44,79.65,13.17,80.01,13.72,79.66,15.14,79.67]],[[31.1,69.56,29.4,69.16,28.59,69.06,29.02,69.77,27.73,70.16,26.18,69.83,25.69,69.09,24.74,68.65,23.66,68.89,22.36,68.84,21.24,69.37,20.65,69.11,20.03,69.07,19.88,68.41,17.99,68.57,17.73,68.01,16.77,68.01,16.11,67.3,15.11,66.19,13.56,64.79,13.92,64.45,13.57,64.05,12.58,64.07,11.93,63.13,11.99,61.8,12.63,61.29,12.3,60.12,11.47,59.43,11.03,58.86,10.36,59.47,8.38,58.31,7.05,58.08,5.67,58.59,5.31,59.66,4.99,61.97,5.91,62.61,8.55,63.45,10.53,64.49,12.36,65.88,14.76,67.81,16.44,68.56,19.18,69.82,21.38,70.26,23.02,70.2,24.55,71.03,26.37,70.99,28.17,71.19,31.29,70.45,30.01,70.19,31.1,69.56]],[[27.41,80.06,25.92,79.52,23.02,79.4,20.08,79.57,19.9,79.84,18.46,79.86,17.37,80.32,20.46,80.6,21.91,80.36,22.92,80.66,25.45,80.41,27.41,80.06]],[[24.72,77.85,22.49,77.44,20.73,77.68,21.42,77.94,20.81,78.25,22.88,78.45,23.28,78.08,24.72,77.85]]]},{"n":"Greenland","c":"GL","r":3,"ml":1.7,"lx":-39.34,"ly":74.32,"b":[-73.3,60.04,-12.21,83.65],"p":[[[-46.76,82.63,-43.41,83.23,-39.9,83.18,-38.62,83.55,-35.09,83.65,-27.1,83.52,-20.85,82.73,-22.69,82.34,-26.52,82.3,-31.9,82.2,-31.4,82.02,-27.86,82.13,-24.84,81.79,-22.9,82.09,-22.07,81.73,-23.17,81.15,-20.62,81.52,-15.77,81.91,-12.77,81.72,-12.21,81.29,-16.29,80.58,-16.85,80.35,-20.05,80.18,-17.73,80.13,-18.9,79.4,-19.7,78.75,-19.67,77.64,-18.47,76.99,-20.04,76.94,-21.68,76.63,-19.83,76.1,-19.6,75.25,-20.67,75.16,-19.37,74.3,-21.59,74.22,-20.43,73.82,-20.76,73.46,-22.17,73.31,-23.57,73.31,-22.31,72.63,-22.3,72.18,-24.28,72.6,-24.79,72.33,-23.44,72.08,-22.13,71.47,-21.75,70.66,-23.54,70.47,-24.31,70.86,-25.54,71.43,-25.2,70.75,-26.36,70.23,-23.73,70.18,-22.35,70.13,-25.03,69.26,-27.75,68.47,-30.67,68.13,-31.78,68.12,-32.81,67.74,-34.2,66.68,-36.35,65.98,-37.04,65.94,-38.38,65.69,-39.81,65.46,-40.67,64.84,-40.68,64.14,-41.19,63.48,-42.82,62.68,-42.42,61.9,-42.87,61.07,-43.38,60.1,-44.79,60.04,-46.26,60.85,-48.26,60.86,-49.23,61.41,-49.9,62.38,-51.63,63.63,-52.14,64.28,-52.28,65.18,-53.66,66.1,-53.3,66.84,-53.97,67.19,-52.98,68.36,-51.48,68.73,-51.08,69.15,-50.87,69.93,-52.01,69.57,-52.56,69.43,-53.46,69.28,-54.68,69.61,-54.75,70.29,-54.36,70.82,-53.43,70.84,-51.39,70.57,-53.11,71.2,-54,71.55,-55,71.41,-55.83,71.65,-54.72,72.59,-55.33,72.96,-56.12,73.65,-57.32,74.71,-58.6,75.1,-58.59,75.52,-61.27,76.1,-63.39,76.18,-66.06,76.13,-68.5,76.06,-69.66,76.38,-71.4,77.01,-68.78,77.32,-66.76,77.38,-71.04,77.64,-73.3,78.04,-73.16,78.43,-69.37,78.91,-65.71,79.39,-65.32,79.76,-68.02,80.12,-67.15,80.52,-63.69,81.21,-62.23,81.32,-62.65,81.77,-60.28,82.03,-57.21,82.19,-54.13,82.2,-53.04,81.89,-50.39,82.44,-48,82.06,-46.6,81.99,-44.52,81.66,-46.9,82.2,-46.76,82.63]]]},{"n":"French Southern and Antarctic Lands","c":"TF","r":6,"ml":4,"lx":69.12,"ly":-49.3,"b":[68.72,-49.77,70.56,-48.62],"p":[[[68.94,-48.62,69.58,-48.94,70.53,-49.06,70.56,-49.25,70.28,-49.71,68.75,-49.77,68.72,-49.24,68.87,-48.83,68.94,-48.62]]]},{"n":"East Timor","c":"TL","r":5,"ml":4,"lx":125.85,"ly":-8.8,"b":[124.97,-9.39,127.34,-8.27],"p":[[[124.97,-8.89,125.09,-8.66,125.95,-8.43,126.64,-8.4,126.96,-8.27,127.34,-8.4,126.97,-8.67,125.93,-9.11,125.09,-9.39,125.07,-9.09,124.97,-8.89]]]},{"n":"South Africa","c":"ZA","r":2,"ml":1.7,"lx":23.67,"ly":-29.71,"b":[16.34,-34.82,32.83,-22.09],"p":[[[16.34,-28.58,16.82,-28.08,17.22,-28.36,17.39,-28.78,17.84,-28.86,18.46,-29.05,19,-28.97,19.89,-28.46,19.9,-24.77,20.17,-24.92,20.76,-25.87,20.67,-26.48,20.89,-26.83,21.61,-26.73,22.11,-26.28,22.58,-25.98,22.82,-25.5,23.31,-25.27,23.73,-25.39,24.21,-25.67,25.03,-25.72,25.66,-25.49,25.77,-25.17,25.94,-24.7,26.49,-24.62,26.79,-24.24,27.12,-23.57,28.02,-22.83,29.43,-22.09,29.84,-22.1,30.32,-22.27,30.66,-22.15,31.19,-22.25,31.67,-23.66,31.93,-24.37,31.75,-25.48,31.84,-25.84,31.33,-25.66,31.04,-25.73,30.95,-26.02,30.68,-26.4,30.69,-26.74,31.28,-27.29,31.87,-27.18,32.07,-26.73,32.83,-26.74,32.58,-27.47,32.46,-28.3,32.2,-28.75,31.52,-29.26,31.33,-29.4,30.9,-29.91,30.62,-30.42,30.06,-31.14,28.93,-32.17,28.22,-32.77,27.46,-33.23,26.42,-33.61,25.91,-33.67,25.78,-33.94,25.17,-33.8,24.68,-33.99,23.59,-33.79,22.99,-33.92,22.57,-33.86,21.54,-34.26,20.69,-34.42,20.07,-34.8,19.62,-34.82,19.19,-34.46,18.86,-34.44,18.42,-34,18.38,-34.14,18.24,-33.87,18.25,-33.28,17.93,-32.61,18.25,-32.43,18.22,-31.66,17.57,-30.73,17.06,-29.88,16.34,-28.58],[28.98,-28.96,28.54,-28.65,28.07,-28.85,27.53,-29.24,27,-29.88,27.75,-30.65,28.11,-30.55,28.29,-30.23,28.85,-30.07,29.02,-29.74,29.33,-29.26,28.98,-28.96]]]},{"n":"Lesotho","c":"LS","r":6,"ml":4,"lx":28.25,"ly":-29.48,"b":[27,-30.65,29.33,-28.65],"p":[[[28.98,-28.96,29.33,-29.26,29.02,-29.74,28.85,-30.07,28.29,-30.23,28.11,-30.55,27.75,-30.65,27,-29.88,27.53,-29.24,28.07,-28.85,28.54,-28.65,28.98,-28.96]]]},{"n":"Mexico","c":"MX","r":2,"ml":2,"lx":-102.29,"ly":23.92,"b":[-117.13,14.54,-86.81,32.72],"p":[[[-117.13,32.54,-115.99,32.61,-114.72,32.72,-114.81,32.53,-113.3,32.04,-111.02,31.33,-109.03,31.34,-108.24,31.34,-108.24,31.75,-106.51,31.75,-106.14,31.4,-105.63,31.08,-105.04,30.64,-104.71,30.12,-104.46,29.57,-103.94,29.27,-103.11,28.97,-102.48,29.76,-101.66,29.78,-100.96,29.38,-100.46,28.7,-100.11,28.11,-99.52,27.54,-99.3,26.84,-99.02,26.37,-98.24,26.06,-97.53,25.84,-97.14,25.87,-97.53,24.99,-97.7,24.27,-97.78,22.93,-97.87,22.44,-97.7,21.9,-97.39,21.41,-97.19,20.64,-96.53,19.89,-96.29,19.32,-95.9,18.83,-94.84,18.56,-94.43,18.14,-93.55,18.42,-92.79,18.52,-92.04,18.7,-91.41,18.88,-90.77,19.28,-90.53,19.87,-90.45,20.71,-90.28,21,-89.6,21.26,-88.54,21.49,-87.66,21.46,-87.05,21.54,-86.81,21.33,-86.85,20.85,-87.38,20.26,-87.62,19.65,-87.44,19.47,-87.59,19.04,-87.84,18.26,-88.09,18.52,-88.3,18.5,-88.49,18.49,-88.85,17.88,-89.03,18,-89.15,17.96,-89.14,17.81,-90.07,17.82,-91,17.82,-91,17.25,-91.45,17.25,-91.08,16.92,-90.71,16.69,-90.6,16.47,-90.44,16.41,-90.46,16.07,-91.75,16.07,-92.23,15.25,-92.09,15.06,-92.2,14.83,-92.23,14.54,-93.36,15.62,-93.88,15.94,-94.69,16.2,-95.25,16.13,-96.05,15.75,-96.56,15.65,-97.26,15.92,-98.01,16.11,-98.95,16.57,-99.7,16.71,-100.83,17.17,-101.67,17.65,-101.92,17.92,-102.48,17.98,-103.5,18.29,-103.92,18.75,-104.99,19.32,-105.49,19.95,-105.73,20.43,-105.4,20.53,-105.5,20.82,-105.27,21.08,-105.27,21.42,-105.6,21.87,-105.69,22.27,-106.03,22.77,-106.91,23.77,-107.92,24.55,-108.4,25.17,-109.26,25.58,-109.44,25.82,-109.29,26.44,-109.8,26.68,-110.39,27.16,-110.64,27.86,-111.18,27.94,-111.76,28.47,-112.23,28.95,-112.27,29.27,-112.81,30.02,-113.16,30.79,-113.15,31.17,-113.87,31.57,-114.21,31.52,-114.78,31.8,-114.94,31.39,-114.77,30.91,-114.67,30.16,-114.33,29.75,-113.59,29.06,-113.42,28.83,-113.27,28.75,-113.14,28.41,-112.96,28.43,-112.76,27.78,-112.46,27.53,-112.24,27.17,-111.62,26.66,-111.28,25.73,-110.99,25.29,-110.71,24.83,-110.66,24.3,-110.17,24.27,-109.77,23.81,-109.41,23.36,-109.43,23.19,-109.85,22.82,-110.03,22.82,-110.3,23.43,-110.95,24,-111.67,24.48,-112.18,24.74,-112.15,25.47,-112.3,26.01,-112.78,26.32,-113.46,26.77,-113.6,26.64,-113.85,26.9,-114.47,27.14,-115.06,27.72,-114.98,27.8,-114.57,27.74,-114.2,28.12,-114.16,28.57,-114.93,29.28,-115.52,29.56,-115.89,30.18,-116.26,30.84,-116.72,31.64,-117.13,32.54]]]},{"n":"Uruguay","c":"UY","r":4,"ml":3,"lx":-55.97,"ly":-32.96,"b":[-58.43,-34.95,-53.21,-30.11],"p":[[[-57.63,-30.22,-56.98,-30.11,-55.97,-30.88,-55.6,-30.85,-54.57,-31.49,-53.79,-32.05,-53.21,-32.73,-53.65,-33.2,-53.37,-33.77,-53.81,-34.4,-54.94,-34.95,-55.67,-34.75,-56.22,-34.86,-57.14,-34.43,-57.82,-34.46,-58.43,-33.91,-58.35,-33.26,-58.13,-33.04,-58.14,-32.04,-57.87,-31.02,-57.63,-30.22]]]},{"n":"Brazil","c":"BR","r":2,"ml":1.7,"lx":-49.56,"ly":-12.1,"b":[-73.99,-33.77,-34.73,5.24],"p":[[[-53.37,-33.77,-53.65,-33.2,-53.21,-32.73,-53.79,-32.05,-54.57,-31.49,-55.6,-30.85,-55.97,-30.88,-56.98,-30.11,-57.63,-30.22,-56.29,-28.85,-55.16,-27.88,-54.49,-27.47,-53.65,-26.92,-53.63,-26.12,-54.13,-25.55,-54.63,-25.74,-54.43,-25.16,-54.29,-24.57,-54.29,-24.02,-54.65,-23.84,-55.03,-24,-55.4,-23.96,-55.52,-23.57,-55.61,-22.66,-55.8,-22.36,-56.47,-22.09,-56.88,-22.28,-57.94,-22.09,-57.87,-20.73,-58.17,-20.18,-57.85,-19.97,-57.95,-19.4,-57.68,-18.96,-57.5,-18.17,-57.73,-17.55,-58.28,-17.27,-58.39,-16.88,-58.24,-16.3,-60.16,-16.26,-60.54,-15.09,-60.25,-15.08,-60.26,-14.65,-60.46,-14.35,-60.5,-13.78,-61.08,-13.48,-61.71,-13.49,-62.13,-13.2,-62.8,-13,-63.2,-12.63,-64.32,-12.46,-65.4,-11.57,-65.32,-10.9,-65.44,-10.51,-65.34,-9.76,-66.65,-9.93,-67.17,-10.31,-68.05,-10.71,-68.27,-11.01,-68.79,-11.04,-69.53,-10.95,-70.09,-11.12,-70.55,-11.01,-70.48,-9.49,-71.3,-10.08,-72.18,-10.05,-72.56,-9.52,-73.23,-9.46,-73.02,-9.03,-73.57,-8.42,-73.99,-7.52,-73.72,-7.34,-73.72,-6.92,-73.12,-6.63,-73.22,-6.09,-72.96,-5.74,-72.89,-5.27,-71.75,-4.59,-70.93,-4.4,-70.79,-4.25,-69.89,-4.3,-69.44,-1.56,-69.42,-1.12,-69.58,-0.55,-70.02,-0.19,-70.02,0.54,-69.45,0.71,-69.25,0.6,-69.22,0.99,-69.8,1.09,-69.82,1.71,-67.87,1.69,-67.54,2.04,-67.26,1.72,-67.07,1.13,-66.88,1.25,-66.33,0.72,-65.55,0.79,-65.35,1.1,-64.61,1.33,-64.2,1.49,-64.08,1.92,-63.37,2.2,-63.42,2.41,-64.27,2.5,-64.41,3.13,-64.37,3.8,-64.82,4.06,-64.63,4.15,-63.89,4.02,-63.09,3.77,-62.8,4.01,-62.09,4.16,-60.97,4.54,-60.6,4.92,-60.73,5.2,-60.21,5.24,-59.98,5.01,-60.11,4.57,-59.77,4.42,-59.54,3.96,-59.82,3.61,-59.97,2.76,-59.72,2.25,-59.65,1.79,-59.03,1.32,-58.54,1.27,-58.43,1.46,-58.11,1.51,-57.66,1.68,-57.34,1.95,-56.78,1.86,-56.54,1.9,-56,1.82,-55.91,2.02,-56.07,2.22,-55.97,2.51,-55.57,2.42,-55.1,2.52,-54.52,2.31,-54.09,2.11,-53.78,2.38,-53.55,2.33,-53.42,2.05,-52.94,2.12,-52.56,2.5,-52.25,3.24,-51.66,4.16,-51.32,4.2,-51.07,3.65,-50.51,1.9,-49.97,1.74,-49.95,1.05,-50.7,0.22,-50.39,-0.08,-48.62,-0.24,-48.58,-1.24,-47.82,-0.58,-46.57,-0.94,-44.91,-1.55,-44.42,-2.14,-44.58,-2.69,-43.42,-2.38,-41.47,-2.91,-39.98,-2.87,-38.5,-3.7,-37.22,-4.82,-36.45,-5.11,-35.6,-5.15,-35.24,-5.46,-34.9,-6.74,-34.73,-7.34,-35.13,-9,-35.64,-9.65,-37.05,-11.04,-37.68,-12.17,-38.42,-13.04,-38.67,-13.06,-38.95,-13.79,-38.88,-15.67,-39.16,-17.21,-39.27,-17.87,-39.58,-18.26,-39.76,-19.6,-40.77,-20.9,-40.94,-21.94,-41.75,-22.37,-41.99,-22.97,-43.07,-22.97,-44.65,-23.35,-45.35,-23.8,-46.47,-24.09,-47.65,-24.89,-48.5,-25.88,-48.64,-26.62,-48.47,-27.18,-48.66,-28.19,-48.89,-28.67,-49.59,-29.22,-50.7,-30.98,-51.58,-31.78,-52.26,-32.25,-52.71,-33.2,-53.37,-33.77]]]},{"n":"Bolivia","c":"BO","r":3,"ml":3,"lx":-64.59,"ly":-16.67,"b":[-69.59,-22.87,-57.5,-9.76],"p":[[[-69.53,-10.95,-68.79,-11.04,-68.27,-11.01,-68.05,-10.71,-67.17,-10.31,-66.65,-9.93,-65.34,-9.76,-65.44,-10.51,-65.32,-10.9,-65.4,-11.57,-64.32,-12.46,-63.2,-12.63,-62.8,-13,-62.13,-13.2,-61.71,-13.49,-61.08,-13.48,-60.5,-13.78,-60.46,-14.35,-60.26,-14.65,-60.25,-15.08,-60.54,-15.09,-60.16,-16.26,-58.24,-16.3,-58.39,-16.88,-58.28,-17.27,-57.73,-17.55,-57.5,-18.17,-57.68,-18.96,-57.95,-19.4,-57.85,-19.97,-58.17,-20.18,-58.18,-19.87,-59.12,-19.36,-60.04,-19.34,-61.79,-19.63,-62.27,-20.51,-62.29,-21.05,-62.69,-22.25,-62.85,-22.03,-63.99,-21.99,-64.38,-22.8,-64.96,-22.08,-66.27,-21.83,-67.11,-22.74,-67.83,-22.87,-68.22,-21.49,-68.76,-20.37,-68.44,-19.41,-68.97,-18.98,-69.1,-18.26,-69.59,-17.58,-68.96,-16.5,-69.39,-15.66,-69.16,-15.32,-69.34,-14.95,-68.95,-14.45,-68.93,-13.6,-68.88,-12.9,-68.67,-12.56,-69.53,-10.95]]]},{"n":"Peru","c":"PE","r":2,"ml":2,"lx":-72.9,"ly":-12.98,"b":[-81.41,-18.35,-68.67,-0.06],"p":[[[-69.89,-4.3,-70.79,-4.25,-70.93,-4.4,-71.75,-4.59,-72.89,-5.27,-72.96,-5.74,-73.22,-6.09,-73.12,-6.63,-73.72,-6.92,-73.72,-7.34,-73.99,-7.52,-73.57,-8.42,-73.02,-9.03,-73.23,-9.46,-72.56,-9.52,-72.18,-10.05,-71.3,-10.08,-70.48,-9.49,-70.55,-11.01,-70.09,-11.12,-69.53,-10.95,-68.67,-12.56,-68.88,-12.9,-68.93,-13.6,-68.95,-14.45,-69.34,-14.95,-69.16,-15.32,-69.39,-15.66,-68.96,-16.5,-69.59,-17.58,-69.86,-18.09,-70.37,-18.35,-71.38,-17.77,-71.46,-17.36,-73.44,-16.36,-75.24,-15.27,-76.01,-14.65,-76.42,-13.82,-76.26,-13.54,-77.11,-12.22,-78.09,-10.38,-79.04,-8.39,-79.45,-7.93,-79.76,-7.19,-80.54,-6.54,-81.25,-6.14,-80.93,-5.69,-81.41,-4.74,-81.1,-4.04,-80.3,-3.4,-80.18,-3.82,-80.47,-4.06,-80.44,-4.43,-80.03,-4.35,-79.62,-4.45,-79.21,-4.96,-78.64,-4.55,-78.45,-3.87,-77.84,-3,-76.64,-2.61,-75.54,-1.56,-75.23,-0.91,-75.37,-0.15,-75.11,-0.06,-74.44,-0.53,-74.12,-1,-73.66,-1.26,-73.07,-2.31,-72.33,-2.43,-71.77,-2.17,-71.41,-2.34,-70.81,-2.26,-70.05,-2.73,-70.69,-3.74,-70.39,-3.77,-69.89,-4.3]]]},{"n":"Colombia","c":"CO","r":2,"ml":3,"lx":-73.17,"ly":3.37,"b":[-78.99,-4.3,-66.88,12.44],"p":[[[-66.88,1.25,-67.07,1.13,-67.26,1.72,-67.54,2.04,-67.87,1.69,-69.82,1.71,-69.8,1.09,-69.22,0.99,-69.25,0.6,-69.45,0.71,-70.02,0.54,-70.02,-0.19,-69.58,-0.55,-69.42,-1.12,-69.44,-1.56,-69.89,-4.3,-70.39,-3.77,-70.69,-3.74,-70.05,-2.73,-70.81,-2.26,-71.41,-2.34,-71.77,-2.17,-72.33,-2.43,-73.07,-2.31,-73.66,-1.26,-74.12,-1,-74.44,-0.53,-75.11,-0.06,-75.37,-0.15,-75.8,0.08,-76.29,0.42,-76.58,0.26,-77.42,0.4,-77.67,0.83,-77.86,0.81,-78.86,1.38,-78.99,1.69,-78.62,1.77,-78.66,2.27,-78.43,2.63,-77.93,2.7,-77.51,3.33,-77.13,3.85,-77.5,4.09,-77.31,4.67,-77.53,5.58,-77.32,5.85,-77.48,6.69,-77.88,7.22,-77.75,7.71,-77.43,7.64,-77.24,7.94,-77.47,8.52,-77.35,8.67,-76.84,8.64,-76.09,9.34,-75.67,9.44,-75.66,9.77,-75.48,10.62,-74.91,11.08,-74.28,11.1,-74.2,11.31,-73.41,11.23,-72.63,11.73,-72.24,11.96,-71.75,12.44,-71.4,12.38,-71.14,12.11,-71.33,11.78,-71.97,11.61,-72.23,11.11,-72.61,10.82,-72.91,10.45,-73.03,9.74,-73.3,9.15,-72.79,9.09,-72.66,8.63,-72.44,8.41,-72.36,8,-72.48,7.63,-72.44,7.42,-72.2,7.34,-71.96,6.99,-70.67,7.09,-70.09,6.96,-69.39,6.1,-68.99,6.21,-68.27,6.15,-67.7,6.27,-67.34,6.1,-67.52,5.56,-67.74,5.22,-67.82,4.5,-67.62,3.84,-67.34,3.54,-67.3,3.32,-67.81,2.82,-67.45,2.6,-67.18,2.25,-66.88,1.25]]]},{"n":"Panama","c":"PA","r":4,"ml":4,"lx":-80.35,"ly":8.72,"b":[-82.97,7.22,-77.24,9.61],"p":[[[-77.35,8.67,-77.47,8.52,-77.24,7.94,-77.43,7.64,-77.75,7.71,-77.88,7.22,-78.21,7.51,-78.43,8.05,-78.18,8.32,-78.44,8.39,-78.62,8.72,-79.12,9,-79.56,8.93,-79.76,8.58,-80.16,8.33,-80.38,8.3,-80.48,8.09,-80,7.55,-80.28,7.42,-80.42,7.27,-80.89,7.22,-81.06,7.82,-81.19,7.65,-81.52,7.71,-81.72,8.11,-82.13,8.18,-82.39,8.29,-82.82,8.29,-82.85,8.07,-82.97,8.23,-82.91,8.42,-82.83,8.63,-82.87,8.81,-82.72,8.93,-82.93,9.07,-82.93,9.48,-82.55,9.57,-82.19,9.21,-82.21,9,-81.81,8.95,-81.71,9.03,-81.44,8.79,-80.95,8.86,-80.52,9.11,-79.91,9.31,-79.57,9.61,-79.02,9.55,-79.06,9.45,-78.5,9.42,-78.06,9.25,-77.73,8.95,-77.35,8.67]]]},{"n":"Costa Rica","c":"CR","r":5,"ml":2.5,"lx":-84.08,"ly":10.07,"b":[-85.94,8.23,-82.55,11.22],"p":[[[-82.55,9.57,-82.93,9.48,-82.93,9.07,-82.72,8.93,-82.87,8.81,-82.83,8.63,-82.91,8.42,-82.97,8.23,-83.51,8.45,-83.71,8.66,-83.6,8.83,-83.63,9.05,-83.91,9.29,-84.3,9.49,-84.65,9.62,-84.71,9.91,-84.98,10.09,-84.91,9.8,-85.11,9.56,-85.34,9.83,-85.66,9.93,-85.8,10.13,-85.79,10.44,-85.66,10.75,-85.94,10.9,-85.71,11.09,-85.56,11.22,-84.9,10.95,-84.67,11.08,-84.36,11,-84.19,10.79,-83.9,10.73,-83.66,10.94,-83.4,10.4,-83.02,9.99,-82.55,9.57]]]},{"n":"Nicaragua","c":"NI","r":5,"ml":4,"lx":-85.07,"ly":12.67,"b":[-87.67,10.73,-83.15,15.02],"p":[[[-83.66,10.94,-83.9,10.73,-84.19,10.79,-84.36,11,-84.67,11.08,-84.9,10.95,-85.56,11.22,-85.71,11.09,-86.06,11.4,-86.53,11.81,-86.75,12.14,-87.17,12.46,-87.67,12.91,-87.56,13.06,-87.39,12.91,-87.32,12.98,-87.01,13.03,-86.88,13.25,-86.73,13.26,-86.76,13.75,-86.52,13.78,-86.31,13.77,-86.1,14.04,-85.8,13.84,-85.7,13.96,-85.51,14.08,-85.17,14.35,-85.15,14.56,-85.05,14.55,-84.92,14.79,-84.82,14.82,-84.65,14.67,-84.45,14.62,-84.23,14.75,-83.98,14.75,-83.63,14.88,-83.49,15.02,-83.15,15,-83.23,14.9,-83.28,14.68,-83.18,14.31,-83.41,13.97,-83.52,13.57,-83.55,13.13,-83.5,12.87,-83.47,12.42,-83.63,12.32,-83.72,11.89,-83.65,11.63,-83.86,11.37,-83.81,11.1,-83.66,10.94]]]},{"n":"Honduras","c":"HN","r":5,"ml":4.5,"lx":-86.89,"ly":14.79,"b":[-89.35,12.98,-83.15,16.01],"p":[[[-83.15,15,-83.49,15.02,-83.63,14.88,-83.98,14.75,-84.23,14.75,-84.45,14.62,-84.65,14.67,-84.82,14.82,-84.92,14.79,-85.05,14.55,-85.15,14.56,-85.17,14.35,-85.51,14.08,-85.7,13.96,-85.8,13.84,-86.1,14.04,-86.31,13.77,-86.52,13.78,-86.76,13.75,-86.73,13.26,-86.88,13.25,-87.01,13.03,-87.32,12.98,-87.49,13.3,-87.79,13.38,-87.72,13.79,-87.86,13.89,-88.07,13.96,-88.5,13.85,-88.54,13.98,-88.84,14.14,-89.06,14.34,-89.35,14.42,-89.15,14.68,-89.23,14.87,-89.15,15.07,-88.68,15.35,-88.23,15.73,-88.12,15.69,-87.9,15.86,-87.62,15.88,-87.52,15.8,-87.37,15.85,-86.9,15.76,-86.44,15.78,-86.12,15.89,-86,16.01,-85.68,15.95,-85.44,15.89,-85.18,15.91,-84.98,16,-84.53,15.86,-84.37,15.84,-84.06,15.65,-83.77,15.42,-83.41,15.27,-83.15,15]]]},{"n":"El Salvador","c":"SV","r":6,"ml":5,"lx":-88.89,"ly":13.69,"b":[-90.1,13.15,-87.72,14.42],"p":[[[-89.35,14.42,-89.06,14.34,-88.84,14.14,-88.54,13.98,-88.5,13.85,-88.07,13.96,-87.86,13.89,-87.72,13.79,-87.79,13.38,-87.9,13.15,-88.48,13.16,-88.84,13.26,-89.26,13.46,-89.81,13.52,-90.1,13.74,-90.06,13.88,-89.72,14.13,-89.53,14.24,-89.59,14.36,-89.35,14.42]]]},{"n":"Guatemala","c":"GT","r":3,"ml":3,"lx":-90.5,"ly":14.98,"b":[-92.23,13.74,-88.23,17.82],"p":[[[-92.23,14.54,-92.2,14.83,-92.09,15.06,-92.23,15.25,-91.75,16.07,-90.46,16.07,-90.44,16.41,-90.6,16.47,-90.71,16.69,-91.08,16.92,-91.45,17.25,-91,17.25,-91,17.82,-90.07,17.82,-89.14,17.81,-89.15,17.02,-89.23,15.89,-88.93,15.89,-88.6,15.71,-88.52,15.86,-88.23,15.73,-88.68,15.35,-89.15,15.07,-89.23,14.87,-89.15,14.68,-89.35,14.42,-89.59,14.36,-89.53,14.24,-89.72,14.13,-90.06,13.88,-90.1,13.74,-90.61,13.91,-91.23,13.93,-91.69,14.13,-92.23,14.54]]]},{"n":"Belize","c":"BZ","r":6,"ml":5,"lx":-88.71,"ly":17.2,"b":[-89.23,15.89,-88.11,18.5],"p":[[[-89.14,17.81,-89.15,17.96,-89.03,18,-88.85,17.88,-88.49,18.49,-88.3,18.5,-88.3,18.35,-88.11,18.35,-88.12,18.08,-88.29,17.64,-88.2,17.49,-88.3,17.13,-88.24,17.04,-88.36,16.53,-88.55,16.27,-88.73,16.23,-88.93,15.89,-89.23,15.89,-89.15,17.02,-89.14,17.81]]]},{"n":"Venezuela","c":"VE","r":3,"ml":2.5,"lx":-64.6,"ly":7.18,"b":[-73.3,0.72,-59.76,12.16],"p":[[[-60.73,5.2,-60.6,4.92,-60.97,4.54,-62.09,4.16,-62.8,4.01,-63.09,3.77,-63.89,4.02,-64.63,4.15,-64.82,4.06,-64.37,3.8,-64.41,3.13,-64.27,2.5,-63.42,2.41,-63.37,2.2,-64.08,1.92,-64.2,1.49,-64.61,1.33,-65.35,1.1,-65.55,0.79,-66.33,0.72,-66.88,1.25,-67.18,2.25,-67.45,2.6,-67.81,2.82,-67.3,3.32,-67.34,3.54,-67.62,3.84,-67.82,4.5,-67.74,5.22,-67.52,5.56,-67.34,6.1,-67.7,6.27,-68.27,6.15,-68.99,6.21,-69.39,6.1,-70.09,6.96,-70.67,7.09,-71.96,6.99,-72.2,7.34,-72.44,7.42,-72.48,7.63,-72.36,8,-72.44,8.41,-72.66,8.63,-72.79,9.09,-73.3,9.15,-73.03,9.74,-72.91,10.45,-72.61,10.82,-72.23,11.11,-71.97,11.61,-71.33,11.78,-71.36,11.54,-71.95,11.42,-71.62,10.97,-71.63,10.45,-72.07,9.87,-71.7,9.07,-71.26,9.14,-71.04,9.86,-71.35,10.21,-71.4,10.97,-70.16,11.38,-70.29,11.85,-69.94,12.16,-69.58,11.46,-68.88,11.44,-68.23,10.89,-68.19,10.55,-67.3,10.55,-66.23,10.65,-65.66,10.2,-64.89,10.08,-64.33,10.39,-64.32,10.64,-63.08,10.7,-61.88,10.72,-62.73,10.42,-62.39,9.95,-61.59,9.87,-60.83,9.38,-60.67,8.58,-60.15,8.6,-59.76,8.37,-60.55,7.78,-60.64,7.42,-60.3,7.04,-60.54,6.86,-61.16,6.7,-61.14,6.23,-61.41,5.96,-60.73,5.2]]]},{"n":"Guyana","c":"GY","r":4,"ml":4,"lx":-58.94,"ly":5.12,"b":[-61.41,1.27,-56.54,8.37],"p":[[[-56.54,1.9,-56.78,1.86,-57.34,1.95,-57.66,1.68,-58.11,1.51,-58.43,1.46,-58.54,1.27,-59.03,1.32,-59.65,1.79,-59.72,2.25,-59.97,2.76,-59.82,3.61,-59.54,3.96,-59.77,4.42,-60.11,4.57,-59.98,5.01,-60.21,5.24,-60.73,5.2,-61.41,5.96,-61.14,6.23,-61.16,6.7,-60.54,6.86,-60.3,7.04,-60.64,7.42,-60.55,7.78,-59.76,8.37,-59.1,8,-58.48,7.35,-58.45,6.83,-58.08,6.81,-57.54,6.32,-57.15,5.97,-57.31,5.07,-57.91,4.81,-57.86,4.58,-58.04,4.06,-57.6,3.33,-57.28,3.33,-57.15,2.77,-56.54,1.9]]]},{"n":"Suriname","c":"SR","r":4,"ml":4,"lx":-55.91,"ly":4.14,"b":[-58.04,1.82,-53.96,6.03],"p":[[[-54.52,2.31,-55.1,2.52,-55.57,2.42,-55.97,2.51,-56.07,2.22,-55.91,2.02,-56,1.82,-56.54,1.9,-57.15,2.77,-57.28,3.33,-57.6,3.33,-58.04,4.06,-57.86,4.58,-57.91,4.81,-57.31,5.07,-57.15,5.97,-55.95,5.77,-55.84,5.95,-55.03,6.03,-53.96,5.76,-54.48,4.9,-54.4,4.21,-54.01,3.62,-54.18,3.19,-54.27,2.73,-54.52,2.31]]]},{"n":"France","c":"FR","r":2,"ml":1.7,"lx":2.55,"ly":46.7,"b":[-54.52,2.05,9.56,51.15],"p":[[[-51.66,4.16,-52.25,3.24,-52.56,2.5,-52.94,2.12,-53.42,2.05,-53.55,2.33,-53.78,2.38,-54.09,2.11,-54.52,2.31,-54.27,2.73,-54.18,3.19,-54.01,3.62,-54.4,4.21,-54.48,4.9,-53.96,5.76,-53.62,5.65,-52.88,5.41,-51.82,4.57,-51.66,4.16]],[[6.19,49.46,6.66,49.2,8.1,49.02,7.59,48.33,7.47,47.62,7.19,47.45,6.74,47.54,6.77,47.29,6.04,46.73,6.02,46.27,6.5,46.43,6.84,45.99,6.8,45.71,7.1,45.33,6.75,45.03,7.01,44.25,7.55,44.13,7.44,43.69,6.53,43.13,4.56,43.4,3.1,43.08,2.99,42.47,1.83,42.34,0.7,42.8,0.34,42.58,-1.5,43.03,-1.9,43.42,-1.38,44.02,-1.19,46.01,-2.23,47.06,-2.96,47.57,-4.49,47.95,-4.59,48.68,-3.3,48.9,-1.62,48.64,-1.93,49.78,-0.99,49.35,1.34,50.13,1.64,50.95,2.51,51.15,2.66,50.8,3.12,50.78,3.59,50.38,4.29,49.91,4.8,49.99,5.67,49.53,5.9,49.44,6.19,49.46]],[[8.75,42.63,9.39,43.01,9.56,42.15,9.23,41.38,8.78,41.58,8.54,42.26,8.75,42.63]]]},{"n":"Ecuador","c":"EC","r":3,"ml":3,"lx":-78.19,"ly":-1.26,"b":[-80.97,-4.96,-75.23,1.38],"p":[[[-75.37,-0.15,-75.23,-0.91,-75.54,-1.56,-76.64,-2.61,-77.84,-3,-78.45,-3.87,-78.64,-4.55,-79.21,-4.96,-79.62,-4.45,-80.03,-4.35,-80.44,-4.43,-80.47,-4.06,-80.18,-3.82,-80.3,-3.4,-79.77,-2.66,-79.99,-2.22,-80.37,-2.69,-80.97,-2.25,-80.76,-1.97,-80.93,-1.06,-80.58,-0.91,-80.4,-0.28,-80.02,0.36,-80.09,0.77,-79.54,0.98,-78.86,1.38,-77.86,0.81,-77.67,0.83,-77.42,0.4,-76.58,0.26,-76.29,0.42,-75.8,0.08,-75.37,-0.15]]]},{"n":"Puerto Rico","c":"PR","r":5,"ml":3,"lx":-66.48,"ly":18.23,"b":[-67.24,17.95,-65.59,18.52],"p":[[[-66.28,18.51,-65.77,18.43,-65.59,18.23,-65.85,17.98,-66.6,17.98,-67.18,17.95,-67.24,18.37,-67.1,18.52,-66.28,18.51]]]},{"n":"Jamaica","c":"JM","r":4,"ml":4,"lx":-77.32,"ly":18.14,"b":[-78.34,17.7,-76.2,18.52],"p":[[[-77.57,18.49,-76.9,18.4,-76.37,18.16,-76.2,17.89,-76.9,17.87,-77.21,17.7,-77.77,17.86,-78.34,18.23,-78.22,18.45,-77.8,18.52,-77.57,18.49]]]},{"n":"Cuba","c":"CU","r":3,"ml":2.7,"lx":-77.98,"ly":21.33,"b":[-84.97,19.86,-74.18,23.19],"p":[[[-82.27,23.19,-81.4,23.12,-80.62,23.11,-79.68,22.77,-79.28,22.4,-78.35,22.51,-77.99,22.28,-77.15,21.66,-76.52,21.21,-76.19,21.22,-75.6,21.02,-75.67,20.74,-74.93,20.69,-74.18,20.28,-74.3,20.05,-74.96,19.92,-75.63,19.87,-76.32,19.95,-77.76,19.86,-77.09,20.41,-77.49,20.67,-78.14,20.74,-78.48,21.03,-78.72,21.6,-79.28,21.56,-80.22,21.83,-80.52,22.04,-81.82,22.19,-82.17,22.39,-81.8,22.64,-82.78,22.69,-83.49,22.17,-83.91,22.15,-84.05,21.91,-84.55,21.8,-84.97,21.9,-84.45,22.2,-84.23,22.57,-83.78,22.79,-83.27,22.98,-82.51,23.08,-82.27,23.19]]]},{"n":"Zimbabwe","c":"ZW","r":3,"ml":2.5,"lx":29.93,"ly":-18.91,"b":[25.26,-22.27,32.85,-15.51],"p":[[[31.19,-22.25,30.66,-22.15,30.32,-22.27,29.84,-22.1,29.43,-22.09,28.79,-21.64,28.02,-21.49,27.73,-20.85,27.72,-20.5,27.3,-20.39,26.16,-19.29,25.85,-18.71,25.65,-18.54,25.26,-17.74,26.38,-17.85,26.71,-17.96,27.04,-17.94,27.6,-17.29,28.47,-16.47,28.83,-16.39,28.95,-16.04,29.52,-15.64,30.27,-15.51,30.34,-15.88,31.17,-15.86,31.64,-16.07,31.85,-16.32,32.33,-16.39,32.85,-16.71,32.85,-17.98,32.65,-18.67,32.61,-19.42,32.77,-19.72,32.66,-20.3,32.51,-20.4,32.24,-21.12,31.19,-22.25]]]},{"n":"Botswana","c":"BW","r":4,"ml":4,"lx":24.18,"ly":-22.1,"b":[19.9,-26.83,29.43,-17.66],"p":[[[29.43,-22.09,28.02,-22.83,27.12,-23.57,26.79,-24.24,26.49,-24.62,25.94,-24.7,25.77,-25.17,25.66,-25.49,25.03,-25.72,24.21,-25.67,23.73,-25.39,23.31,-25.27,22.82,-25.5,22.58,-25.98,22.11,-26.28,21.61,-26.73,20.89,-26.83,20.67,-26.48,20.76,-25.87,20.17,-24.92,19.9,-24.77,19.9,-21.85,20.88,-21.81,20.91,-18.25,21.66,-18.22,23.2,-17.87,23.58,-18.28,24.22,-17.89,24.52,-17.89,25.08,-17.66,25.26,-17.74,25.65,-18.54,25.85,-18.71,26.16,-19.29,27.3,-20.39,27.72,-20.5,27.73,-20.85,28.02,-21.49,28.79,-21.64,29.43,-22.09]]]},{"n":"Namibia","c":"NA","r":3,"ml":3,"lx":17.11,"ly":-20.58,"b":[11.73,-29.05,25.08,-16.94],"p":[[[19.9,-24.77,19.89,-28.46,19,-28.97,18.46,-29.05,17.84,-28.86,17.39,-28.78,17.22,-28.36,16.82,-28.08,16.34,-28.58,15.6,-27.82,15.21,-27.09,14.99,-26.12,14.74,-25.39,14.41,-23.85,14.39,-22.66,14.26,-22.11,13.87,-21.7,13.35,-20.87,12.83,-19.67,12.61,-19.05,11.79,-18.07,11.73,-17.3,12.22,-17.11,12.81,-16.94,13.46,-16.97,14.06,-17.42,14.21,-17.35,18.26,-17.31,18.96,-17.79,21.38,-17.93,23.22,-17.52,24.03,-17.3,24.68,-17.35,25.08,-17.58,25.08,-17.66,24.52,-17.89,24.22,-17.89,23.58,-18.28,23.2,-17.87,21.66,-18.22,20.91,-18.25,20.88,-21.81,19.9,-21.85,19.9,-24.77]]]},{"n":"Senegal","c":"SN","r":3,"ml":2.7,"lx":-14.78,"ly":15.14,"b":[-17.63,12.33,-11.47,16.6],"p":[[[-16.71,13.59,-17.13,14.37,-17.63,14.73,-17.19,14.92,-16.7,15.62,-16.46,16.14,-16.12,16.46,-15.62,16.37,-15.14,16.59,-14.58,16.6,-14.1,16.3,-13.44,16.04,-12.83,15.3,-12.17,14.62,-12.12,13.99,-11.93,13.42,-11.55,13.14,-11.47,12.75,-11.51,12.44,-11.66,12.39,-12.2,12.47,-12.28,12.35,-12.5,12.33,-13.22,12.58,-13.7,12.59,-15.55,12.63,-15.82,12.52,-16.15,12.55,-16.68,12.38,-16.84,13.15,-15.93,13.13,-15.69,13.27,-15.51,13.28,-15.14,13.51,-14.71,13.3,-14.28,13.28,-13.84,13.51,-14.05,13.79,-14.38,13.63,-14.69,13.63,-15.08,13.88,-15.4,13.86,-15.62,13.62,-16.71,13.59]]]},{"n":"Mali","c":"ML","r":3,"ml":3,"lx":-2.04,"ly":18.69,"b":[-12.17,10.1,4.27,24.97],"p":[[[-11.51,12.44,-11.47,12.75,-11.55,13.14,-11.93,13.42,-12.12,13.99,-12.17,14.62,-11.83,14.8,-11.67,15.39,-11.35,15.41,-10.65,15.13,-10.09,15.33,-9.7,15.26,-9.55,15.49,-5.54,15.5,-5.32,16.2,-5.49,16.33,-5.97,20.64,-6.45,24.96,-4.92,24.97,-1.55,22.79,1.82,20.61,2.06,20.14,2.68,19.86,3.15,19.69,3.16,19.06,4.27,19.16,4.27,16.85,3.72,16.18,3.64,15.57,2.75,15.41,1.39,15.32,1.02,14.97,0.37,14.93,-0.27,14.92,-0.52,15.12,-1.07,14.97,-2,14.56,-2.19,14.25,-2.97,13.8,-3.1,13.54,-3.52,13.34,-4.01,13.47,-4.28,13.23,-4.43,12.54,-5.22,11.71,-5.2,11.38,-5.47,10.95,-5.4,10.37,-5.82,10.22,-6.05,10.1,-6.21,10.52,-6.49,10.41,-6.67,10.43,-6.85,10.14,-7.62,10.15,-7.9,10.3,-8.03,10.21,-8.34,10.49,-8.28,10.79,-8.41,10.91,-8.62,10.81,-8.58,11.14,-8.38,11.39,-8.79,11.81,-8.91,12.09,-9.13,12.31,-9.33,12.33,-9.57,12.19,-9.89,12.06,-10.17,11.84,-10.59,11.92,-10.87,12.18,-11.04,12.21,-11.3,12.08,-11.46,12.08,-11.51,12.44]]]},{"n":"Mauritania","c":"MR","r":3,"ml":3,"lx":-9.74,"ly":19.59,"b":[-17.06,14.62,-4.92,27.4],"p":[[[-17.06,21,-16.85,21.33,-12.93,21.33,-13.12,22.77,-12.87,23.28,-11.94,23.37,-11.97,25.93,-8.69,25.88,-8.68,27.4,-4.92,24.97,-6.45,24.96,-5.97,20.64,-5.49,16.33,-5.32,16.2,-5.54,15.5,-9.55,15.49,-9.7,15.26,-10.09,15.33,-10.65,15.13,-11.35,15.41,-11.67,15.39,-11.83,14.8,-12.17,14.62,-12.83,15.3,-13.44,16.04,-14.1,16.3,-14.58,16.6,-15.14,16.59,-15.62,16.37,-16.12,16.46,-16.46,16.14,-16.55,16.67,-16.27,17.17,-16.15,18.11,-16.26,19.1,-16.38,19.59,-16.28,20.09,-16.54,20.57,-17.06,21]]]},{"n":"Benin","c":"BJ","r":5,"ml":4,"lx":2.35,"ly":10.32,"b":[0.77,6.14,3.8,12.24],"p":[[[2.69,6.26,1.87,6.14,1.62,6.83,1.66,9.13,1.46,9.33,1.43,9.83,1.08,10.18,0.77,10.47,0.9,11,1.24,11.11,1.45,11.55,1.94,11.64,2.15,11.94,2.49,12.23,2.85,12.24,3.61,11.66,3.57,11.33,3.8,10.73,3.6,10.33,3.71,10.06,3.22,9.44,2.91,9.14,2.72,8.51,2.75,7.87,2.69,6.26]]]},{"n":"Niger","c":"NE","r":3,"ml":3,"lx":9.5,"ly":17.45,"b":[0.3,11.66,15.9,23.47],"p":[[[14.85,22.86,15.1,21.31,15.47,21.05,15.49,20.73,15.9,20.39,15.69,19.96,15.3,17.93,15.25,16.63,13.97,15.68,13.54,14.37,13.96,14,13.95,13.35,14.6,13.33,14.5,12.86,14.21,12.8,14.18,12.48,14,12.46,13.32,13.56,13.08,13.6,12.3,13.04,11.53,13.33,10.99,13.39,10.7,13.25,10.11,13.28,9.52,12.85,9.01,12.83,7.8,13.34,7.33,13.1,6.82,13.12,6.45,13.49,5.44,13.87,4.37,13.75,4.11,13.53,3.97,12.96,3.68,12.55,3.61,11.66,2.85,12.24,2.49,12.23,2.15,11.94,2.18,12.63,1.02,12.85,0.99,13.34,0.43,13.99,0.3,14.44,0.37,14.93,1.02,14.97,1.39,15.32,2.75,15.41,3.64,15.57,3.72,16.18,4.27,16.85,4.27,19.16,5.68,19.6,8.57,21.57,12,23.47,13.58,23.04,14.14,22.49,14.85,22.86]]]},{"n":"Nigeria","c":"NG","r":2,"ml":1.7,"lx":7.5,"ly":9.44,"b":[2.69,4.24,14.58,13.87],"p":[[[2.69,6.26,2.75,7.87,2.72,8.51,2.91,9.14,3.22,9.44,3.71,10.06,3.6,10.33,3.8,10.73,3.57,11.33,3.61,11.66,3.68,12.55,3.97,12.96,4.11,13.53,4.37,13.75,5.44,13.87,6.45,13.49,6.82,13.12,7.33,13.1,7.8,13.34,9.01,12.83,9.52,12.85,10.11,13.28,10.7,13.25,10.99,13.39,11.53,13.33,12.3,13.04,13.08,13.6,13.32,13.56,14,12.46,14.18,12.48,14.58,12.09,14.47,11.9,14.42,11.57,13.57,10.8,13.31,10.16,13.17,9.64,12.96,9.42,12.75,8.72,12.22,8.31,12.06,7.8,11.84,7.4,11.75,6.98,11.06,6.64,10.5,7.06,10.12,7.04,9.52,6.45,9.23,6.44,8.76,5.48,8.5,4.77,7.46,4.41,7.08,4.46,6.7,4.24,5.9,4.26,5.36,4.89,5.03,5.61,4.33,6.27,3.57,6.26,2.69,6.26]]]},{"n":"Cameroon","c":"CM","r":3,"ml":3,"lx":12.47,"ly":4.59,"b":[8.49,1.73,16.01,12.86],"p":[[[14.5,12.86,14.89,12.22,14.96,11.56,14.92,10.89,15.47,9.98,14.91,9.99,14.63,9.92,14.17,10.02,13.95,9.55,14.54,8.97,14.98,8.8,15.12,8.38,15.44,7.69,15.28,7.42,14.78,6.41,14.54,6.23,14.46,5.45,14.56,5.03,14.48,4.73,14.95,4.21,15.04,3.85,15.41,3.34,15.86,3.01,15.91,2.56,16.01,2.27,15.94,1.73,15.15,1.96,14.34,2.23,13.08,2.27,12.95,2.32,12.36,2.19,11.75,2.33,11.28,2.26,9.65,2.28,9.8,3.07,9.4,3.73,8.95,3.9,8.74,4.35,8.49,4.5,8.5,4.77,8.76,5.48,9.23,6.44,9.52,6.45,10.12,7.04,10.5,7.06,11.06,6.64,11.75,6.98,11.84,7.4,12.06,7.8,12.22,8.31,12.75,8.72,12.96,9.42,13.17,9.64,13.31,10.16,13.57,10.8,14.42,11.57,14.47,11.9,14.58,12.09,14.18,12.48,14.21,12.8,14.5,12.86]]]},{"n":"Togo","c":"TG","r":6,"ml":5,"lx":1.06,"ly":8.81,"b":[-0.05,5.93,1.87,11.02],"p":[[[0.9,11,0.77,10.47,1.08,10.18,1.43,9.83,1.46,9.33,1.66,9.13,1.62,6.83,1.87,6.14,1.06,5.93,0.84,6.28,0.57,6.91,0.49,7.41,0.71,8.31,0.46,8.68,0.37,9.47,0.37,10.19,-0.05,10.71,0.02,11.02,0.9,11]]]},{"n":"Ghana","c":"GH","r":3,"ml":2.7,"lx":-1.04,"ly":7.72,"b":[-3.24,4.71,1.06,11.1],"p":[[[0.02,11.02,-0.05,10.71,0.37,10.19,0.37,9.47,0.46,8.68,0.71,8.31,0.49,7.41,0.57,6.91,0.84,6.28,1.06,5.93,-0.51,5.34,-1.06,5,-1.96,4.71,-2.86,4.99,-2.81,5.39,-3.24,6.25,-2.98,7.38,-2.56,8.22,-2.83,9.64,-2.96,10.4,-2.94,10.96,-1.2,11.01,-0.76,10.94,-0.44,11.1,0.02,11.02]]]},{"n":"Ivory Coast","c":"CI","r":3,"ml":2.5,"lx":-5.57,"ly":7.49,"b":[-8.6,4.34,-2.56,10.52],"p":[[[-8.03,10.21,-7.9,10.3,-7.62,10.15,-6.85,10.14,-6.67,10.43,-6.49,10.41,-6.21,10.52,-6.05,10.1,-5.82,10.22,-5.4,10.37,-4.95,10.15,-4.78,9.82,-4.33,9.61,-3.98,9.86,-3.51,9.9,-2.83,9.64,-2.56,8.22,-2.98,7.38,-3.24,6.25,-2.81,5.39,-2.86,4.99,-3.31,4.98,-4.01,5.18,-4.65,5.17,-5.83,4.99,-6.53,4.71,-7.52,4.34,-7.71,4.36,-7.64,5.19,-7.54,5.31,-7.57,5.71,-7.99,6.13,-8.31,6.19,-8.6,6.47,-8.39,6.91,-8.49,7.4,-8.44,7.69,-8.28,7.69,-8.22,8.12,-8.3,8.32,-8.2,8.46,-7.83,8.58,-8.08,9.38,-8.31,9.79,-8.23,10.13,-8.03,10.21]]]},{"n":"Guinea","c":"GN","r":3,"ml":3,"lx":-10.02,"ly":10.62,"b":[-15.13,7.31,-7.83,12.59],"p":[[[-13.7,12.59,-13.22,12.58,-12.5,12.33,-12.28,12.35,-12.2,12.47,-11.66,12.39,-11.51,12.44,-11.46,12.08,-11.3,12.08,-11.04,12.21,-10.87,12.18,-10.59,11.92,-10.17,11.84,-9.89,12.06,-9.57,12.19,-9.33,12.33,-9.13,12.31,-8.91,12.09,-8.79,11.81,-8.38,11.39,-8.58,11.14,-8.62,10.81,-8.41,10.91,-8.28,10.79,-8.34,10.49,-8.03,10.21,-8.23,10.13,-8.31,9.79,-8.08,9.38,-7.83,8.58,-8.2,8.46,-8.3,8.32,-8.22,8.12,-8.28,7.69,-8.44,7.69,-8.72,7.71,-8.93,7.31,-9.21,7.31,-9.4,7.53,-9.34,7.93,-9.76,8.54,-10.02,8.43,-10.23,8.41,-10.51,8.35,-10.49,8.72,-10.65,8.98,-10.62,9.27,-10.84,9.69,-11.12,10.05,-11.92,10.05,-12.15,9.86,-12.43,9.84,-12.6,9.62,-12.71,9.34,-13.25,8.9,-13.69,9.49,-14.07,9.89,-14.33,10.02,-14.58,10.21,-14.69,10.66,-14.84,10.88,-15.13,11.04,-14.69,11.53,-14.38,11.51,-14.12,11.68,-13.9,11.68,-13.74,11.81,-13.83,12.14,-13.72,12.25,-13.7,12.59]]]},{"n":"Guinea-Bissau","c":"GW","r":6,"ml":5,"lx":-14.52,"ly":12.16,"b":[-16.68,11.04,-13.7,12.63],"p":[[[-16.68,12.38,-16.15,12.55,-15.82,12.52,-15.55,12.63,-13.7,12.59,-13.72,12.25,-13.83,12.14,-13.74,11.81,-13.9,11.68,-14.12,11.68,-14.38,11.51,-14.69,11.53,-15.13,11.04,-15.66,11.46,-16.09,11.52,-16.31,11.81,-16.31,11.96,-16.61,12.17,-16.68,12.38]]]},{"n":"Liberia","c":"LR","r":4,"ml":4,"lx":-9.46,"ly":6.45,"b":[-11.44,4.36,-7.54,8.54],"p":[[[-8.44,7.69,-8.49,7.4,-8.39,6.91,-8.6,6.47,-8.31,6.19,-7.99,6.13,-7.57,5.71,-7.54,5.31,-7.64,5.19,-7.71,4.36,-7.97,4.36,-9,4.83,-9.91,5.59,-10.77,6.14,-11.44,6.79,-11.2,7.11,-11.15,7.4,-10.7,7.94,-10.23,8.41,-10.02,8.43,-9.76,8.54,-9.34,7.93,-9.4,7.53,-9.21,7.31,-8.93,7.31,-8.72,7.71,-8.44,7.69]]]},{"n":"Sierra Leone","c":"SL","r":4,"ml":4,"lx":-11.76,"ly":8.62,"b":[-13.25,6.79,-10.23,10.05],"p":[[[-13.25,8.9,-12.71,9.34,-12.6,9.62,-12.43,9.84,-12.15,9.86,-11.92,10.05,-11.12,10.05,-10.84,9.69,-10.62,9.27,-10.65,8.98,-10.49,8.72,-10.51,8.35,-10.23,8.41,-10.7,7.94,-11.15,7.4,-11.2,7.11,-11.44,6.79,-11.71,6.86,-12.43,7.26,-12.95,7.8,-13.12,8.16,-13.25,8.9]]]},{"n":"Burkina Faso","c":"BF","r":3,"ml":3,"lx":-1.36,"ly":12.67,"b":[-5.47,9.61,2.18,15.12],"p":[[[-5.4,10.37,-5.47,10.95,-5.2,11.38,-5.22,11.71,-4.43,12.54,-4.28,13.23,-4.01,13.47,-3.52,13.34,-3.1,13.54,-2.97,13.8,-2.19,14.25,-2,14.56,-1.07,14.97,-0.52,15.12,-0.27,14.92,0.37,14.93,0.3,14.44,0.43,13.99,0.99,13.34,1.02,12.85,2.18,12.63,2.15,11.94,1.94,11.64,1.45,11.55,1.24,11.11,0.9,11,0.02,11.02,-0.44,11.1,-0.76,10.94,-1.2,11.01,-2.94,10.96,-2.96,10.4,-2.83,9.64,-3.51,9.9,-3.98,9.86,-4.33,9.61,-4.78,9.82,-4.95,10.15,-5.4,10.37]]]},{"n":"Central African Republic","c":"CF","r":4,"ml":4,"lx":20.91,"ly":6.99,"b":[14.46,2.27,27.37,11.14],"p":[[[27.37,5.23,27.04,5.13,26.4,5.15,25.65,5.26,25.28,5.17,25.13,4.93,24.81,4.9,24.41,5.11,23.3,4.61,22.84,4.71,22.7,4.63,22.41,4.03,21.66,4.22,20.93,4.32,20.29,4.69,19.47,5.03,18.93,4.71,18.54,4.2,18.45,3.5,17.81,3.56,17.13,3.73,16.54,3.2,16.01,2.27,15.91,2.56,15.86,3.01,15.41,3.34,15.04,3.85,14.95,4.21,14.48,4.73,14.56,5.03,14.46,5.45,14.54,6.23,14.78,6.41,15.28,7.42,16.11,7.5,16.29,7.75,16.46,7.73,16.71,7.51,17.96,7.89,18.39,8.28,18.91,8.63,18.81,8.98,19.09,9.07,20.06,9.01,21,9.48,21.72,10.57,22.23,10.97,22.86,11.14,22.98,10.71,23.55,10.09,23.56,9.68,23.39,9.27,23.46,8.95,23.81,8.67,24.57,8.23,25.11,7.83,25.12,7.5,25.8,6.98,26.21,6.55,26.47,5.95,27.21,5.55,27.37,5.23]]]},{"n":"Republic of the Congo","c":"CG","r":4,"ml":4,"lx":15.9,"ly":0.14,"b":[11.09,-5.04,18.45,3.73],"p":[[[18.45,3.5,18.39,2.9,18.09,2.37,17.9,1.74,17.77,0.86,17.83,0.29,17.66,-0.06,17.64,-0.42,17.52,-0.74,16.87,-1.23,16.41,-1.74,15.97,-2.71,16.01,-3.54,15.75,-3.86,15.17,-4.34,14.58,-4.97,14.21,-4.79,14.14,-4.51,13.6,-4.5,13.26,-4.88,13,-4.78,12.62,-4.44,12.32,-4.61,11.91,-5.04,11.09,-3.98,11.86,-3.43,11.48,-2.77,11.82,-2.51,12.5,-2.39,12.58,-1.95,13.11,-2.43,13.99,-2.47,14.3,-2,14.43,-1.33,14.32,-0.55,13.84,0.04,14.28,1.2,14.03,1.4,13.28,1.31,13,1.83,13.08,2.27,14.34,2.23,15.15,1.96,15.94,1.73,16.01,2.27,16.54,3.2,17.13,3.73,17.81,3.56,18.45,3.5]]]},{"n":"Gabon","c":"GA","r":4,"ml":3,"lx":11.84,"ly":-0.44,"b":[8.8,-3.98,14.43,2.33],"p":[[[11.28,2.26,11.75,2.33,12.36,2.19,12.95,2.32,13.08,2.27,13,1.83,13.28,1.31,14.03,1.4,14.28,1.2,13.84,0.04,14.32,-0.55,14.43,-1.33,14.3,-2,13.99,-2.47,13.11,-2.43,12.58,-1.95,12.5,-2.39,11.82,-2.51,11.48,-2.77,11.86,-3.43,11.09,-3.98,10.07,-2.97,9.41,-2.14,8.8,-1.11,8.83,-0.78,9.05,-0.46,9.29,0.27,9.49,1.01,9.83,1.07,11.29,1.06,11.28,2.26]]]},{"n":"Equatorial Guinea","c":"GQ","r":4,"ml":4,"lx":8.99,"ly":2.33,"b":[9.31,1.01,11.29,2.28],"p":[[[9.65,2.28,11.28,2.26,11.29,1.06,9.83,1.07,9.49,1.01,9.31,1.16,9.65,2.28]]]},{"n":"Zambia","c":"ZM","r":3,"ml":3,"lx":26.4,"ly":-14.66,"b":[21.89,-17.96,33.49,-8.24],"p":[[[30.74,-8.34,31.16,-8.59,31.56,-8.76,32.19,-8.93,32.76,-9.23,33.23,-9.68,33.49,-10.53,33.32,-10.8,33.11,-11.61,33.31,-12.44,32.99,-12.78,32.69,-13.71,33.21,-13.97,30.18,-14.8,30.27,-15.51,29.52,-15.64,28.95,-16.04,28.83,-16.39,28.47,-16.47,27.6,-17.29,27.04,-17.94,26.71,-17.96,26.38,-17.85,25.26,-17.74,25.08,-17.66,25.08,-17.58,24.68,-17.35,24.03,-17.3,23.22,-17.52,22.56,-16.9,21.89,-16.08,21.93,-12.9,24.02,-12.91,23.93,-12.57,24.08,-12.19,23.9,-11.72,24.02,-11.24,23.91,-10.93,24.26,-10.95,24.31,-11.26,24.78,-11.24,25.42,-11.33,25.75,-11.78,26.55,-11.92,27.16,-11.61,27.39,-12.13,28.16,-12.27,28.52,-12.7,28.93,-13.25,29.7,-13.26,29.62,-12.18,29.34,-12.36,28.64,-11.97,28.37,-11.79,28.5,-10.79,28.67,-9.61,28.45,-9.16,28.73,-8.53,29,-8.41,30.35,-8.24,30.74,-8.34]]]},{"n":"Malawi","c":"MW","r":6,"ml":4,"lx":33.61,"ly":-13.39,"b":[32.69,-16.8,35.77,-9.23],"p":[[[32.76,-9.23,33.74,-9.42,33.94,-9.69,34.28,-10.16,34.56,-11.52,34.28,-12.28,34.56,-13.58,34.91,-13.57,35.27,-13.89,35.69,-14.61,35.77,-15.9,35.34,-16.11,35.03,-16.8,34.38,-16.18,34.31,-15.48,34.52,-15.01,34.46,-14.61,34.06,-14.36,33.79,-14.45,33.21,-13.97,32.69,-13.71,32.99,-12.78,33.31,-12.44,33.11,-11.61,33.32,-10.8,33.49,-10.53,33.23,-9.68,32.76,-9.23]]]},{"n":"Mozambique","c":"MZ","r":3,"ml":3,"lx":37.84,"ly":-13.94,"b":[30.18,-26.74,40.78,-10.32],"p":[[[34.56,-11.52,35.31,-11.44,36.51,-11.72,36.78,-11.59,37.47,-11.57,37.83,-11.27,38.43,-11.29,39.52,-10.9,40.32,-10.32,40.48,-10.77,40.44,-11.76,40.56,-12.64,40.6,-14.2,40.78,-14.69,40.48,-15.41,40.09,-16.1,39.45,-16.72,38.54,-17.1,37.41,-17.59,36.28,-18.66,35.9,-18.84,35.2,-19.55,34.79,-19.78,34.7,-20.5,35.18,-21.25,35.37,-21.84,35.39,-22.14,35.56,-22.09,35.53,-23.07,35.37,-23.54,35.61,-23.71,35.46,-24.12,35.04,-24.48,34.22,-24.82,33.01,-25.36,32.57,-25.73,32.66,-26.15,32.92,-26.22,32.83,-26.74,32.07,-26.73,31.99,-26.29,31.84,-25.84,31.75,-25.48,31.93,-24.37,31.67,-23.66,31.19,-22.25,32.24,-21.12,32.51,-20.4,32.66,-20.3,32.77,-19.72,32.61,-19.42,32.65,-18.67,32.85,-17.98,32.85,-16.71,32.33,-16.39,31.85,-16.32,31.64,-16.07,31.17,-15.86,30.34,-15.88,30.27,-15.51,30.18,-14.8,33.21,-13.97,33.79,-14.45,34.06,-14.36,34.46,-14.61,34.52,-15.01,34.31,-15.48,34.38,-16.18,35.03,-16.8,35.34,-16.11,35.77,-15.9,35.69,-14.61,35.27,-13.89,34.91,-13.57,34.56,-13.58,34.28,-12.28,34.56,-11.52]]]},{"n":"Eswatini","c":"SZ","r":4,"ml":4,"lx":31.47,"ly":-26.53,"b":[30.68,-27.29,32.07,-25.66],"p":[[[32.07,-26.73,31.87,-27.18,31.28,-27.29,30.69,-26.74,30.68,-26.4,30.95,-26.02,31.04,-25.73,31.33,-25.66,31.84,-25.84,31.99,-26.29,32.07,-26.73]]]},{"n":"Angola","c":"AO","r":3,"ml":3,"lx":17.98,"ly":-12.18,"b":[11.64,-17.93,24.08,-4.44],"p":[[[13,-4.78,12.63,-4.99,12.47,-5.25,12.44,-5.68,12.18,-5.79,11.91,-5.04,12.32,-4.61,12.62,-4.44,13,-4.78]],[[12.32,-6.1,12.74,-5.97,13.02,-5.98,13.38,-5.86,16.33,-5.88,16.57,-6.62,16.86,-7.22,17.09,-7.55,17.47,-8.07,18.13,-7.99,18.46,-7.85,19.02,-7.99,19.17,-7.74,19.42,-7.16,20.04,-7.12,20.09,-6.94,20.6,-6.94,20.51,-7.3,21.73,-7.29,21.75,-7.92,21.95,-8.31,21.8,-8.91,21.88,-9.52,22.21,-9.89,22.16,-11.08,22.4,-10.99,22.84,-11.02,23.46,-10.87,23.91,-10.93,24.02,-11.24,23.9,-11.72,24.08,-12.19,23.93,-12.57,24.02,-12.91,21.93,-12.9,21.89,-16.08,22.56,-16.9,23.22,-17.52,21.38,-17.93,18.96,-17.79,18.26,-17.31,14.21,-17.35,14.06,-17.42,13.46,-16.97,12.81,-16.94,12.22,-17.11,11.73,-17.3,11.64,-16.67,11.78,-15.79,12.12,-14.88,12.18,-14.45,12.5,-13.55,12.74,-13.14,13.31,-12.48,13.63,-12.04,13.74,-11.3,13.69,-10.73,13.39,-10.37,13.12,-9.77,12.88,-9.17,12.93,-8.96,13.24,-8.56,12.93,-7.6,12.73,-6.93,12.23,-6.29,12.32,-6.1]]]},{"n":"Burundi","c":"BI","r":6,"ml":4,"lx":29.92,"ly":-3.33,"b":[29.02,-4.5,30.75,-2.35],"p":[[[30.47,-2.41,30.53,-2.81,30.74,-3.03,30.75,-3.36,30.51,-3.57,30.12,-4.09,29.75,-4.45,29.34,-4.5,29.28,-3.29,29.02,-2.84,29.63,-2.92,29.94,-2.35,30.47,-2.41]]]},{"n":"Israel","c":"IL","r":4,"ml":3,"lx":34.85,"ly":30.91,"b":[34.27,29.5,35.84,33.28],"p":[[[35.72,32.71,35.55,32.39,35.18,32.53,34.97,31.87,35.23,31.75,34.97,31.62,34.93,31.35,35.4,31.49,35.42,31.1,34.92,29.5,34.82,29.76,34.27,31.22,34.56,31.55,34.49,31.61,34.75,32.07,34.96,32.83,35.1,33.08,35.13,33.09,35.46,33.09,35.55,33.26,35.82,33.28,35.84,32.87,35.7,32.72,35.72,32.71]]]},{"n":"Lebanon","c":"LB","r":5,"ml":4,"lx":35.99,"ly":34.13,"b":[35.13,33.09,36.61,34.64],"p":[[[35.82,33.28,35.55,33.26,35.46,33.09,35.13,33.09,35.48,33.91,35.98,34.61,36,34.64,36.45,34.59,36.61,34.2,36.07,33.82,35.82,33.28]]]},{"n":"Madagascar","c":"MG","r":3,"ml":2.7,"lx":46.7,"ly":-18.63,"b":[43.25,-25.6,50.48,-12.04],"p":[[[49.54,-12.47,49.81,-12.9,50.06,-13.56,50.22,-14.76,50.48,-15.23,50.38,-15.71,50.2,-16,49.86,-15.41,49.67,-15.71,49.86,-16.45,49.77,-16.88,49.5,-17.11,49.44,-17.95,49.04,-19.12,48.55,-20.5,47.93,-22.39,47.55,-23.78,47.1,-24.94,46.28,-25.18,45.41,-25.6,44.83,-25.35,44.04,-24.99,43.76,-24.46,43.7,-23.57,43.35,-22.78,43.25,-22.06,43.43,-21.34,43.89,-21.16,43.9,-20.83,44.37,-20.07,44.46,-19.44,44.23,-18.96,44.04,-18.33,43.96,-17.41,44.31,-16.85,44.45,-16.22,44.94,-16.18,45.5,-15.97,45.87,-15.79,46.31,-15.78,46.88,-15.21,47.71,-14.59,48.01,-14.09,47.87,-13.66,48.29,-13.78,48.85,-13.09,48.86,-12.49,49.19,-12.04,49.54,-12.47]]]},{"n":"Palestine","c":"PS","r":5,"ml":4.5,"lx":35.29,"ly":32.05,"b":[34.93,31.35,35.55,32.53],"p":[[[35.4,31.49,34.93,31.35,34.97,31.62,35.23,31.75,34.97,31.87,35.18,32.53,35.55,32.39,35.55,31.78,35.4,31.49]]]},{"n":"The Gambia","c":"GM","r":6,"ml":5,"lx":-15,"ly":13.64,"b":[-16.84,13.13,-13.84,13.88],"p":[[[-16.71,13.59,-15.62,13.62,-15.4,13.86,-15.08,13.88,-14.69,13.63,-14.38,13.63,-14.05,13.79,-13.84,13.51,-14.28,13.28,-14.71,13.3,-15.14,13.51,-15.51,13.28,-15.69,13.27,-15.93,13.13,-16.84,13.15,-16.71,13.59]]]},{"n":"Tunisia","c":"TN","r":3,"ml":3,"lx":9.01,"ly":33.69,"b":[7.52,30.31,11.49,37.35],"p":[[[9.48,30.31,9.06,32.1,8.44,32.51,8.43,32.75,7.61,33.34,7.52,34.1,8.14,34.66,8.38,35.48,8.22,36.43,8.42,36.95,9.51,37.35,10.21,37.23,10.18,36.72,11.03,37.09,11.1,36.9,10.6,36.41,10.59,35.95,10.94,35.7,10.81,34.83,10.15,34.33,10.34,33.79,10.86,33.77,11.11,33.29,11.49,33.14,11.43,32.37,10.94,32.08,10.64,31.76,9.95,31.38,10.06,30.96,9.97,30.54,9.48,30.31]]]},{"n":"Algeria","c":"DZ","r":3,"ml":2.5,"lx":2.81,"ly":27.4,"b":[-8.68,19.06,12,37.12],"p":[[[-8.68,27.4,-8.67,27.59,-8.67,27.66,-8.67,28.84,-7.06,29.58,-6.06,29.73,-5.24,30,-4.86,30.5,-3.69,30.9,-3.65,31.64,-3.07,31.72,-2.62,32.09,-1.31,32.26,-1.12,32.65,-1.39,32.86,-1.73,33.92,-1.79,34.53,-2.17,35.17,-1.21,35.71,-0.13,35.89,0.5,36.3,1.47,36.61,3.16,36.78,4.82,36.87,5.32,36.72,6.26,37.11,7.33,37.12,7.74,36.89,8.42,36.95,8.22,36.43,8.38,35.48,8.14,34.66,7.52,34.1,7.61,33.34,8.43,32.75,8.44,32.51,9.06,32.1,9.48,30.31,9.81,29.42,9.86,28.96,9.68,28.14,9.76,27.69,9.63,27.14,9.72,26.51,9.32,26.09,9.91,25.37,9.95,24.94,10.3,24.38,10.77,24.56,11.56,24.1,12,23.47,8.57,21.57,5.68,19.6,4.27,19.16,3.16,19.06,3.15,19.69,2.68,19.86,2.06,20.14,1.82,20.61,-1.55,22.79,-4.92,24.97,-8.68,27.4]]]},{"n":"Jordan","c":"JO","r":4,"ml":4,"lx":36.38,"ly":30.81,"b":[34.92,29.2,39.2,33.38],"p":[[[35.55,32.39,35.72,32.71,36.83,32.31,38.79,33.38,39.2,32.16,39,32.01,37,31.51,38,30.51,37.67,30.34,37.5,30,36.74,29.87,36.5,29.51,36.07,29.2,34.96,29.36,34.92,29.5,35.42,31.1,35.4,31.49,35.55,31.78,35.55,32.39]]]},{"n":"United Arab Emirates","c":"AE","r":4,"ml":4,"lx":54.55,"ly":23.47,"b":[51.58,22.5,56.4,26.06],"p":[[[51.58,24.25,51.76,24.29,51.79,24.02,52.58,24.18,53.4,24.15,54.01,24.12,54.69,24.8,55.44,25.44,56.07,26.06,56.26,25.71,56.4,24.92,55.89,24.92,55.8,24.27,55.98,24.13,55.53,23.93,55.53,23.52,55.23,23.11,55.21,22.71,55.01,22.5,52,23,51.62,24.01,51.58,24.25]]]},{"n":"Qatar","c":"QA","r":5,"ml":4,"lx":51.14,"ly":25.24,"b":[50.74,24.56,51.61,26.11],"p":[[[50.81,24.75,50.74,25.48,51.01,26.01,51.29,26.11,51.59,25.8,51.61,25.22,51.39,24.63,51.11,24.56,50.81,24.75]]]},{"n":"Kuwait","c":"KW","r":6,"ml":5,"lx":47.31,"ly":29.41,"b":[46.57,28.53,48.42,30.06],"p":[[[47.97,29.98,48.18,29.53,48.09,29.31,48.42,28.55,47.71,28.53,47.46,29,46.57,29.1,47.3,30.06,47.97,29.98]]]},{"n":"Iraq","c":"IQ","r":3,"ml":3,"lx":43.26,"ly":33.09,"b":[38.79,29.1,48.57,37.39],"p":[[[39.2,32.16,38.79,33.38,41.01,34.42,41.38,35.63,41.29,36.36,41.84,36.61,42.35,37.23,42.78,37.39,43.94,37.26,44.29,37,44.77,37.17,45.42,35.98,46.08,35.68,46.15,35.09,45.65,34.75,45.42,33.97,46.11,33.02,47.33,32.47,47.85,31.71,47.69,30.98,48,30.99,48.01,30.45,48.57,29.93,47.97,29.98,47.3,30.06,46.57,29.1,44.71,29.18,41.89,31.19,40.4,31.89,39.2,32.16]]]},{"n":"Oman","c":"OM","r":4,"ml":4,"lx":57.34,"ly":22.12,"b":[52,16.65,59.81,26.4],"p":[[[55.21,22.71,55.23,23.11,55.53,23.52,55.53,23.93,55.98,24.13,55.8,24.27,55.89,24.92,56.4,24.92,56.85,24.24,57.4,23.88,58.14,23.75,58.73,23.57,59.18,22.99,59.45,22.66,59.81,22.53,59.81,22.31,59.44,21.71,59.28,21.43,58.86,21.11,58.49,20.43,58.03,20.48,57.83,20.24,57.67,19.74,57.79,19.07,57.69,18.94,57.23,18.95,56.61,18.57,56.51,18.09,56.28,17.88,55.66,17.88,55.27,17.63,55.27,17.23,54.79,16.95,54.24,17.04,53.57,16.71,53.11,16.65,52.78,17.35,52,19,55,20,55.67,22,55.21,22.71]],[[56.26,25.71,56.07,26.06,56.36,26.4,56.49,26.31,56.39,25.9,56.26,25.71]]]},{"n":"Vanuatu","c":"VU","r":4,"ml":4,"lx":166.91,"ly":-15.37,"b":[166.63,-16.6,167.84,-14.63],"p":[[[167.22,-15.89,167.84,-16.47,167.52,-16.6,167.18,-16.16,167.22,-15.89]],[[166.79,-15.67,166.65,-15.39,166.63,-14.63,167.11,-14.93,167.27,-15.74,167,-15.61,166.79,-15.67]]]},{"n":"Cambodia","c":"KH","r":3,"ml":3,"lx":104.5,"ly":12.65,"b":[102.35,10.49,107.61,14.57],"p":[[[102.58,12.19,102.35,13.39,102.99,14.23,104.28,14.42,105.22,14.27,106.04,13.88,106.5,14.57,107.38,14.2,107.61,13.54,107.49,12.34,105.81,11.57,106.25,10.96,105.2,10.89,104.33,10.49,103.5,10.63,103.09,11.15,102.58,12.19]]]},{"n":"Thailand","c":"TH","r":3,"ml":2.7,"lx":101.07,"ly":15.46,"b":[97.38,5.69,105.59,20.42],"p":[[[105.22,14.27,104.28,14.42,102.99,14.23,102.35,13.39,102.58,12.19,101.69,12.65,100.83,12.63,100.98,13.41,100.1,13.41,100.02,12.31,99.48,10.85,99.15,9.96,99.22,9.24,99.87,9.21,100.28,8.3,100.46,7.43,101.02,6.86,101.62,6.74,102.14,6.22,101.81,5.81,101.15,5.69,101.08,6.2,100.26,6.64,100.09,6.46,99.69,6.85,99.52,7.34,98.99,7.91,98.5,8.38,98.34,7.79,98.15,8.35,98.26,8.97,98.55,9.93,99.04,10.96,99.59,11.89,99.2,12.8,99.21,13.27,99.1,13.83,98.43,14.62,98.19,15.12,98.54,15.31,98.9,16.18,98.49,16.84,97.86,17.57,97.38,18.45,97.8,18.63,98.25,19.71,98.96,19.75,99.54,20.19,100.12,20.42,100.55,20.11,100.61,19.51,101.28,19.46,101.04,18.41,101.06,17.51,102.11,18.11,102.41,17.93,103,17.96,103.2,18.31,103.96,18.24,104.72,17.43,104.78,16.44,105.59,15.57,105.54,14.72,105.22,14.27]]]},{"n":"Laos","c":"LA","r":4,"ml":4,"lx":102.53,"ly":19.43,"b":[100.12,13.88,107.56,22.46],"p":[[[107.38,14.2,106.5,14.57,106.04,13.88,105.22,14.27,105.54,14.72,105.59,15.57,104.78,16.44,104.72,17.43,103.96,18.24,103.2,18.31,103,17.96,102.41,17.93,102.11,18.11,101.06,17.51,101.04,18.41,101.28,19.46,100.61,19.51,100.55,20.11,100.12,20.42,100.33,20.79,101.18,21.44,101.27,21.2,101.8,21.17,101.65,22.32,102.17,22.46,102.75,21.68,103.2,20.77,104.44,20.76,104.82,19.89,104.18,19.62,103.9,19.27,105.09,18.67,105.93,17.49,106.56,16.6,107.31,15.91,107.56,15.2,107.38,14.2]]]},{"n":"Myanmar","c":"MM","r":3,"ml":3,"lx":95.8,"ly":21.57,"b":[92.3,9.93,101.18,28.34],"p":[[[100.12,20.42,99.54,20.19,98.96,19.75,98.25,19.71,97.8,18.63,97.38,18.45,97.86,17.57,98.49,16.84,98.9,16.18,98.54,15.31,98.19,15.12,98.43,14.62,99.1,13.83,99.21,13.27,99.2,12.8,99.59,11.89,99.04,10.96,98.55,9.93,98.46,10.68,98.76,11.44,98.43,12.03,98.51,13.12,98.1,13.64,97.78,14.84,97.6,16.1,97.16,16.93,96.51,16.43,95.37,15.71,94.81,15.8,94.19,16.04,94.53,17.28,94.32,18.21,93.54,19.37,93.66,19.73,93.08,19.86,92.37,20.67,92.3,21.48,92.65,21.32,92.67,22.04,93.17,22.28,93.06,22.7,93.29,23.04,93.33,24.08,94.11,23.85,94.55,24.68,94.6,25.16,95.16,26,95.12,26.57,96.42,27.26,97.13,27.08,97.05,27.7,97.4,27.88,97.33,28.26,97.91,28.34,98.25,27.75,98.68,27.51,98.71,26.74,98.67,25.92,97.72,25.08,97.6,23.9,98.66,24.06,98.9,23.14,99.53,22.95,99.24,22.12,99.98,21.74,100.42,21.56,101.15,21.85,101.18,21.44,100.33,20.79,100.12,20.42]]]},{"n":"Vietnam","c":"VN","r":2,"ml":2,"lx":105.39,"ly":21.72,"b":[102.17,8.6,109.34,23.35],"p":[[[104.33,10.49,105.2,10.89,106.25,10.96,105.81,11.57,107.49,12.34,107.61,13.54,107.38,14.2,107.56,15.2,107.31,15.91,106.56,16.6,105.93,17.49,105.09,18.67,103.9,19.27,104.18,19.62,104.82,19.89,104.44,20.76,103.2,20.77,102.75,21.68,102.17,22.46,102.71,22.71,103.5,22.7,104.48,22.82,105.33,23.35,105.81,22.98,106.73,22.79,106.57,22.22,107.04,21.81,108.05,21.55,106.72,20.7,105.88,19.75,105.66,19.06,106.43,18,107.36,16.7,108.27,16.08,108.88,15.28,109.34,13.43,109.2,11.67,108.37,11.01,107.22,10.36,106.41,9.53,105.16,8.6,104.8,9.24,105.08,9.92,104.33,10.49]]]},{"n":"North Korea","c":"KP","r":3,"ml":3,"lx":126.44,"ly":39.89,"b":[124.27,37.67,130.78,42.99],"p":[[[130.64,42.4,130.78,42.22,130.4,42.28,129.97,41.94,129.67,41.6,129.71,40.88,129.19,40.66,129.01,40.49,128.63,40.19,127.97,40.03,127.53,39.76,127.5,39.32,127.39,39.21,127.78,39.05,128.35,38.61,128.21,38.37,127.78,38.3,127.07,38.26,126.68,37.8,126.24,37.84,126.17,37.75,125.69,37.94,125.57,37.75,125.28,37.67,125.24,37.86,124.98,37.95,124.71,38.11,124.99,38.55,125.22,38.67,125.13,38.85,125.39,39.39,125.32,39.55,124.74,39.66,124.27,39.93,125.08,40.57,126.18,41.11,126.87,41.82,127.34,41.5,128.21,41.47,128.05,41.99,129.6,42.42,129.99,42.99,130.64,42.4]]]},{"n":"South Korea","c":"KR","r":2,"ml":2.5,"lx":128.13,"ly":36.38,"b":[126.12,34.39,129.47,38.61],"p":[[[126.17,37.75,126.24,37.84,126.68,37.8,127.07,38.26,127.78,38.3,128.21,38.37,128.35,38.61,129.21,37.43,129.46,36.78,129.47,35.63,129.09,35.08,128.19,34.89,127.39,34.48,126.49,34.39,126.37,34.93,126.56,35.68,126.12,36.73,126.86,36.89,126.17,37.75]]]},{"n":"Mongolia","c":"MN","r":3,"ml":3,"lx":104.15,"ly":46,"b":[87.75,41.6,119.77,52.05],"p":[[[87.75,49.3,88.81,49.47,90.71,50.33,92.23,50.8,93.1,50.5,94.15,50.48,94.82,50.01,95.81,49.98,97.26,49.73,98.23,50.42,97.83,51.01,98.86,52.05,99.98,51.63,100.89,51.52,102.07,51.26,102.26,50.51,103.68,50.09,104.62,50.28,105.89,50.41,106.89,50.27,107.87,49.79,108.48,49.28,109.4,49.29,110.66,49.13,111.58,49.38,112.9,49.54,114.36,50.25,114.96,50.14,115.49,49.81,116.68,49.89,116.19,49.13,115.49,48.14,115.74,47.73,116.31,47.85,117.3,47.7,118.06,48.07,118.87,47.75,119.77,47.05,119.66,46.69,118.87,46.81,117.42,46.67,116.72,46.39,115.99,45.73,114.46,45.34,113.46,44.81,112.44,45.01,111.87,45.1,111.35,44.46,111.67,44.07,111.83,43.74,111.13,43.41,110.41,42.87,109.24,42.52,107.74,42.48,106.13,42.13,104.96,41.6,104.52,41.91,103.31,41.91,101.83,42.51,100.85,42.66,99.52,42.52,97.45,42.75,96.35,42.73,95.76,43.32,95.31,44.24,94.69,44.35,93.48,44.98,92.13,45.12,90.95,45.29,90.59,45.72,90.97,46.89,90.28,47.69,88.85,48.07,88.01,48.6,87.75,49.3]]]},{"n":"India","c":"IN","r":2,"ml":1.7,"lx":79.36,"ly":22.69,"b":[68.18,7.97,97.4,35.49],"p":[[[97.33,28.26,97.4,27.88,97.05,27.7,97.13,27.08,96.42,27.26,95.12,26.57,95.16,26,94.6,25.16,94.55,24.68,94.11,23.85,93.33,24.08,93.29,23.04,93.06,22.7,93.17,22.28,92.67,22.04,92.15,23.63,91.87,23.62,91.71,22.99,91.16,23.5,91.47,24.07,91.92,24.13,92.38,24.98,91.8,25.15,90.87,25.13,89.92,25.27,89.83,25.97,89.36,26.01,88.56,26.45,88.21,25.77,88.93,25.24,88.31,24.87,88.08,24.5,88.7,24.23,88.53,23.63,88.88,22.88,89.03,22.06,88.89,21.69,88.21,21.7,86.98,21.5,87.03,20.74,86.5,20.15,85.06,19.48,83.94,18.3,83.19,17.67,82.19,17.02,82.19,16.56,81.69,16.31,80.79,15.95,80.32,15.9,80.03,15.14,80.23,13.84,80.29,13.01,79.86,12.06,79.86,10.36,79.34,10.31,78.89,9.55,79.19,9.22,78.28,8.93,77.94,8.25,77.54,7.97,76.59,8.9,76.13,10.3,75.75,11.31,75.4,11.78,74.86,12.74,74.62,13.99,74.44,14.62,73.53,15.99,73.12,17.93,72.82,19.21,72.82,20.42,72.63,21.36,71.18,20.76,70.47,20.88,69.16,22.09,69.64,22.45,69.35,22.84,68.18,23.69,68.84,24.36,71.04,24.36,70.84,25.22,70.28,25.72,70.17,26.49,69.51,26.94,70.62,27.99,71.78,27.91,72.82,28.96,73.45,29.98,74.42,30.98,74.41,31.69,75.26,32.27,74.45,32.76,74.1,33.44,73.75,34.32,74.24,34.75,75.76,34.5,76.87,34.65,77.84,35.49,78.91,34.32,78.81,33.51,79.21,32.99,79.18,32.48,78.46,32.62,78.74,31.52,79.72,30.88,81.11,30.18,80.48,29.73,80.09,28.79,81.06,28.42,82,27.93,83.3,27.36,84.68,27.23,85.25,26.73,86.02,26.63,87.23,26.4,88.06,26.41,88.17,26.81,88.04,27.45,88.12,27.88,88.73,28.09,88.81,27.3,88.84,27.1,89.74,26.72,90.37,26.88,91.22,26.81,92.03,26.84,92.1,27.45,91.7,27.77,92.5,27.9,93.41,28.64,94.57,29.28,95.4,29.03,96.12,29.45,96.59,28.83,96.25,28.41,97.33,28.26]]]},{"n":"Bangladesh","c":"BD","r":3,"ml":3,"lx":89.68,"ly":24.21,"b":[88.08,20.67,92.67,26.45],"p":[[[92.67,22.04,92.65,21.32,92.3,21.48,92.37,20.67,92.08,21.19,92.03,21.7,91.83,22.18,91.42,22.77,90.5,22.81,90.59,22.39,90.27,21.84,89.85,22.04,89.7,21.86,89.42,21.97,89.03,22.06,88.88,22.88,88.53,23.63,88.7,24.23,88.08,24.5,88.31,24.87,88.93,25.24,88.21,25.77,88.56,26.45,89.36,26.01,89.83,25.97,89.92,25.27,90.87,25.13,91.8,25.15,92.38,24.98,91.92,24.13,91.47,24.07,91.16,23.5,91.71,22.99,91.87,23.62,92.15,23.63,92.67,22.04]]]},{"n":"Bhutan","c":"BT","r":5,"ml":4,"lx":90.04,"ly":27.54,"b":[88.81,26.72,92.1,28.3],"p":[[[91.7,27.77,92.1,27.45,92.03,26.84,91.22,26.81,90.37,26.88,89.74,26.72,88.84,27.1,88.81,27.3,89.48,28.04,90.02,28.3,90.73,28.06,91.26,28.04,91.7,27.77]]]},{"n":"Nepal","c":"NP","r":3,"ml":3,"lx":83.64,"ly":28.3,"b":[80.09,26.4,88.17,30.42],"p":[[[88.12,27.88,88.04,27.45,88.17,26.81,88.06,26.41,87.23,26.4,86.02,26.63,85.25,26.73,84.68,27.23,83.3,27.36,82,27.93,81.06,28.42,80.09,28.79,80.48,29.73,81.11,30.18,81.53,30.42,82.33,30.12,83.34,29.46,83.9,29.32,84.23,28.84,85.01,28.64,85.82,28.2,86.95,27.97,88.12,27.88]]]},{"n":"Pakistan","c":"PK","r":2,"ml":2.7,"lx":68.55,"ly":29.33,"b":[60.87,23.69,77.84,37.13],"p":[[[77.84,35.49,76.87,34.65,75.76,34.5,74.24,34.75,73.75,34.32,74.1,33.44,74.45,32.76,75.26,32.27,74.41,31.69,74.42,30.98,73.45,29.98,72.82,28.96,71.78,27.91,70.62,27.99,69.51,26.94,70.17,26.49,70.28,25.72,70.84,25.22,71.04,24.36,68.84,24.36,68.18,23.69,67.44,23.94,67.15,24.66,66.37,25.43,64.53,25.24,62.91,25.22,61.5,25.08,61.87,26.24,63.32,26.76,63.23,27.22,62.76,27.38,62.73,28.26,61.77,28.7,61.37,29.3,60.87,29.83,62.55,29.32,63.55,29.47,64.15,29.34,64.35,29.56,65.05,29.47,66.35,29.89,66.38,30.74,66.94,31.3,67.68,31.3,67.79,31.58,68.56,31.71,68.93,31.62,69.32,31.9,69.26,32.5,69.69,33.11,70.32,33.36,69.93,34.02,70.88,33.99,71.16,34.35,71.12,34.73,71.61,35.15,71.5,35.65,71.26,36.07,71.85,36.51,72.92,36.72,74.07,36.84,74.58,37.02,75.16,37.13,75.9,36.67,76.19,35.9,77.84,35.49]]]},{"n":"Afghanistan","c":"AF","r":3,"ml":3,"lx":66.5,"ly":34.16,"b":[60.53,29.32,75.16,38.49],"p":[[[66.52,37.36,67.08,37.36,67.83,37.14,68.14,37.02,68.86,37.34,69.2,37.15,69.52,37.61,70.12,37.59,70.27,37.74,70.38,38.14,70.81,38.49,71.35,38.26,71.24,37.95,71.54,37.91,71.45,37.07,71.84,36.74,72.19,36.95,72.64,37.05,73.26,37.5,73.95,37.42,74.98,37.42,75.16,37.13,74.58,37.02,74.07,36.84,72.92,36.72,71.85,36.51,71.26,36.07,71.5,35.65,71.61,35.15,71.12,34.73,71.16,34.35,70.88,33.99,69.93,34.02,70.32,33.36,69.69,33.11,69.26,32.5,69.32,31.9,68.93,31.62,68.56,31.71,67.79,31.58,67.68,31.3,66.94,31.3,66.38,30.74,66.35,29.89,65.05,29.47,64.35,29.56,64.15,29.34,63.55,29.47,62.55,29.32,60.87,29.83,61.78,30.74,61.7,31.38,60.94,31.55,60.86,32.18,60.54,32.98,60.96,33.53,60.53,33.68,60.8,34.4,61.21,35.65,62.23,35.27,62.98,35.4,63.19,35.86,63.98,36.01,64.55,36.31,64.75,37.11,65.59,37.31,65.75,37.66,66.22,37.39,66.52,37.36]]]},{"n":"Tajikistan","c":"TJ","r":4,"ml":4,"lx":72.59,"ly":38.2,"b":[67.44,36.74,74.98,40.96],"p":[[[67.83,37.14,68.39,38.16,68.18,38.9,67.44,39.14,67.7,39.58,68.54,39.53,69.01,40.09,69.33,40.73,70.67,40.96,70.46,40.5,70.6,40.22,71.01,40.24,70.65,39.94,69.56,40.1,69.46,39.53,70.55,39.6,71.78,39.28,73.68,39.43,73.93,38.51,74.26,38.61,74.86,38.38,74.83,37.99,74.98,37.42,73.95,37.42,73.26,37.5,72.64,37.05,72.19,36.95,71.84,36.74,71.45,37.07,71.54,37.91,71.24,37.95,71.35,38.26,70.81,38.49,70.38,38.14,70.27,37.74,70.12,37.59,69.52,37.61,69.2,37.15,68.86,37.34,68.14,37.02,67.83,37.14]]]},{"n":"Kyrgyzstan","c":"KG","r":4,"ml":3,"lx":74.53,"ly":41.67,"b":[69.46,39.28,80.26,43.3],"p":[[[70.96,42.27,71.19,42.7,71.84,42.85,73.49,42.5,73.65,43.09,74.21,43.3,75.64,42.88,76,42.99,77.66,42.96,79.14,42.86,79.64,42.5,80.26,42.35,80.12,42.12,78.54,41.58,78.19,41.19,76.9,41.07,76.53,40.43,75.47,40.56,74.78,40.37,73.82,39.89,73.96,39.66,73.68,39.43,71.78,39.28,70.55,39.6,69.46,39.53,69.56,40.1,70.65,39.94,71.01,40.24,71.77,40.15,73.06,40.87,71.87,41.39,71.16,41.14,70.42,41.52,71.26,42.17,70.96,42.27]]]},{"n":"Turkmenistan","c":"TM","r":4,"ml":3,"lx":58.68,"ly":39.86,"b":[52.5,35.27,66.55,42.75],"p":[[[52.5,41.78,52.94,42.12,54.08,42.32,54.76,42.04,55.46,41.26,55.97,41.31,57.1,41.32,56.93,41.83,57.79,42.17,58.63,42.75,59.98,42.22,60.08,41.43,60.47,41.22,61.55,41.27,61.88,41.08,62.37,40.05,63.52,39.36,64.17,38.89,65.22,38.4,66.55,37.97,66.52,37.36,66.22,37.39,65.75,37.66,65.59,37.31,64.75,37.11,64.55,36.31,63.98,36.01,63.19,35.86,62.98,35.4,62.23,35.27,61.21,35.65,61.12,36.49,60.38,36.53,59.23,37.41,58.44,37.52,57.33,38.03,56.62,38.12,56.18,37.94,55.51,37.96,54.8,37.39,53.92,37.2,53.74,37.91,53.88,38.95,53.1,39.29,53.36,39.98,52.69,40.03,52.92,40.88,53.86,40.63,54.74,40.95,54.01,41.55,53.72,42.12,52.92,41.87,52.81,41.14,52.5,41.78]]]},{"n":"Iran","c":"IR","r":2,"ml":2.5,"lx":54.93,"ly":32.17,"b":[44.11,25.08,63.32,39.71],"p":[[[48.57,29.93,48.01,30.45,48,30.99,47.69,30.98,47.85,31.71,47.33,32.47,46.11,33.02,45.42,33.97,45.65,34.75,46.15,35.09,46.08,35.68,45.42,35.98,44.77,37.17,44.23,37.97,44.42,38.28,44.11,39.43,44.79,39.71,44.95,39.34,45.46,38.87,46.14,38.74,46.51,38.77,47.69,39.51,48.06,39.58,48.36,39.29,48.01,38.79,48.63,38.27,48.88,38.32,49.2,37.58,50.15,37.37,50.84,36.87,52.26,36.7,53.83,36.97,53.92,37.2,54.8,37.39,55.51,37.96,56.18,37.94,56.62,38.12,57.33,38.03,58.44,37.52,59.23,37.41,60.38,36.53,61.12,36.49,61.21,35.65,60.8,34.4,60.53,33.68,60.96,33.53,60.54,32.98,60.86,32.18,60.94,31.55,61.7,31.38,61.78,30.74,60.87,29.83,61.37,29.3,61.77,28.7,62.73,28.26,62.76,27.38,63.23,27.22,63.32,26.76,61.87,26.24,61.5,25.08,59.62,25.38,58.53,25.61,57.4,25.74,56.97,26.97,56.49,27.14,55.72,26.96,54.72,26.48,53.49,26.81,52.48,27.58,51.52,27.87,50.85,28.81,50.12,30.15,49.58,29.99,48.94,30.32,48.57,29.93]]]},{"n":"Syria","c":"SY","r":3,"ml":3,"lx":38.28,"ly":35.01,"b":[35.7,32.31,42.35,37.23],"p":[[[35.72,32.71,35.7,32.72,35.84,32.87,35.82,33.28,36.07,33.82,36.61,34.2,36.45,34.59,36,34.64,35.91,35.41,36.15,35.82,36.42,36.04,36.69,36.26,36.74,36.82,37.07,36.62,38.17,36.9,38.7,36.71,39.52,36.72,40.67,37.09,41.21,37.07,42.35,37.23,41.84,36.61,41.29,36.36,41.38,35.63,41.01,34.42,38.79,33.38,36.83,32.31,35.72,32.71]]]},{"n":"Armenia","c":"AM","r":6,"ml":5,"lx":44.8,"ly":40.46,"b":[43.58,38.74,46.51,41.25],"p":[[[46.51,38.77,46.14,38.74,45.74,39.32,45.74,39.47,45.3,39.47,45,39.74,44.79,39.71,44.4,40.01,43.66,40.25,43.75,40.74,43.58,41.09,44.97,41.25,45.18,40.99,45.56,40.81,45.36,40.56,45.89,40.22,45.61,39.9,46.03,39.63,46.48,39.46,46.51,38.77]]]},{"n":"Sweden","c":"SE","r":3,"ml":2,"lx":19.02,"ly":65.86,"b":[11.03,55.36,23.9,69.11],"p":[[[11.03,58.86,11.47,59.43,12.3,60.12,12.63,61.29,11.99,61.8,11.93,63.13,12.58,64.07,13.57,64.05,13.92,64.45,13.56,64.79,15.11,66.19,16.11,67.3,16.77,68.01,17.73,68.01,17.99,68.57,19.88,68.41,20.03,69.07,20.65,69.11,21.98,68.62,23.54,67.94,23.57,66.4,23.9,66.01,22.18,65.72,21.21,65.03,21.37,64.41,19.78,63.61,17.85,62.75,17.12,61.34,17.83,60.64,18.79,60.08,17.87,58.95,16.83,58.72,16.45,57.04,15.88,56.1,14.67,56.2,14.1,55.41,12.94,55.36,12.63,56.31,11.79,57.44,11.03,58.86]]]},{"n":"Belarus","c":"BY","r":4,"ml":3,"lx":28.42,"ly":53.82,"b":[23.2,51.32,32.69,56.17],"p":[[[28.18,56.17,29.23,55.92,29.37,55.67,29.9,55.79,30.87,55.55,30.97,55.08,30.76,54.81,31.38,54.16,31.79,53.97,31.73,53.79,32.41,53.62,32.69,53.35,32.3,53.13,31.5,53.17,31.31,53.07,31.54,52.74,31.79,52.1,30.93,52.04,30.62,51.82,30.56,51.32,30.16,51.42,29.25,51.37,28.99,51.6,28.62,51.43,28.24,51.57,27.45,51.59,26.34,51.83,25.33,51.91,24.55,51.89,24.01,51.62,23.53,51.58,23.51,52.02,23.2,52.49,23.8,52.69,23.8,53.09,23.53,53.47,23.48,53.91,24.45,53.91,25.54,54.28,25.77,54.85,26.59,55.17,26.49,55.62,27.1,55.78,28.18,56.17]]]},{"n":"Ukraine","c":"UA","r":3,"ml":2.7,"lx":32.14,"ly":49.72,"b":[22.09,45.29,40.08,52.34],"p":[[[31.79,52.1,32.16,52.06,32.41,52.29,32.72,52.24,33.75,52.34,34.39,51.77,34.14,51.57,34.22,51.26,35.02,51.21,35.38,50.77,35.36,50.58,36.63,50.23,37.39,50.38,38.01,49.92,38.59,49.93,40.07,49.6,40.08,49.31,39.67,48.78,39.9,48.23,39.74,47.9,38.77,47.83,38.26,47.55,38.22,47.1,37.43,47.02,36.76,46.7,35.82,46.65,34.96,46.27,35.01,45.74,34.86,45.77,34.73,45.97,34.41,46.01,33.7,46.22,33.44,45.97,33.3,46.08,31.74,46.33,31.68,46.71,30.75,46.58,30.38,46.03,29.6,45.29,29.15,45.46,28.68,45.3,28.23,45.49,28.49,45.6,28.66,45.94,28.93,46.26,28.86,46.44,29.07,46.52,29.17,46.38,29.76,46.35,30.02,46.42,29.84,46.53,29.91,46.67,29.56,46.93,29.42,47.35,29.05,47.51,29.12,47.85,28.67,48.12,28.26,48.16,27.52,48.47,26.86,48.37,26.62,48.22,26.2,48.22,25.95,47.99,25.21,47.89,24.87,47.74,24.4,47.98,23.76,47.99,23.14,48.1,22.71,47.88,22.64,48.15,22.09,48.42,22.28,48.83,22.56,49.09,22.78,49.03,22.52,49.48,23.43,50.31,23.92,50.42,24.03,50.71,23.53,51.58,24.01,51.62,24.55,51.89,25.33,51.91,26.34,51.83,27.45,51.59,28.24,51.57,28.62,51.43,28.99,51.6,29.25,51.37,30.16,51.42,30.56,51.32,30.62,51.82,30.93,52.04,31.79,52.1]]]},{"n":"Poland","c":"PL","r":3,"ml":2.5,"lx":19.49,"ly":51.99,"b":[14.07,49.03,24.03,54.85],"p":[[[23.48,53.91,23.53,53.47,23.8,53.09,23.8,52.69,23.2,52.49,23.51,52.02,23.53,51.58,24.03,50.71,23.92,50.42,23.43,50.31,22.52,49.48,22.78,49.03,22.56,49.09,21.61,49.47,20.89,49.33,20.42,49.43,19.83,49.22,19.32,49.57,18.91,49.44,18.85,49.5,18.39,49.99,17.65,50.05,17.55,50.36,16.87,50.47,16.72,50.22,16.18,50.42,16.24,50.7,15.49,50.78,15.02,51.11,14.61,51.75,14.69,52.09,14.44,52.62,14.07,52.98,14.35,53.25,14.12,53.76,14.8,54.05,16.36,54.51,17.62,54.85,18.62,54.68,18.7,54.44,19.66,54.43,20.89,54.31,22.73,54.33,23.24,54.22,23.48,53.91]]]},{"n":"Austria","c":"AT","r":4,"ml":3,"lx":14.13,"ly":47.52,"b":[9.48,46.43,16.98,49.04],"p":[[[16.98,48.12,16.9,47.71,16.34,47.71,16.53,47.5,16.2,46.85,16.01,46.68,15.14,46.66,14.63,46.43,13.81,46.51,12.38,46.77,12.15,47.12,11.16,46.94,11.05,46.75,10.44,46.89,9.93,46.92,9.48,47.1,9.63,47.35,9.59,47.53,9.9,47.58,10.4,47.3,10.54,47.57,11.43,47.52,12.14,47.7,12.62,47.67,12.93,47.47,13.03,47.64,12.88,48.29,13.24,48.42,13.6,48.88,14.34,48.56,14.9,48.96,15.25,49.04,16.03,48.73,16.5,48.79,16.96,48.6,16.88,48.47,16.98,48.12]]]},{"n":"Hungary","c":"HU","r":5,"ml":4,"lx":19.45,"ly":47.09,"b":[16.2,45.76,22.71,48.62],"p":[[[22.09,48.42,22.64,48.15,22.71,47.88,22.1,47.67,21.63,46.99,21.02,46.32,20.22,46.13,19.6,46.17,18.83,45.91,18.46,45.76,17.63,45.95,16.88,46.38,16.56,46.5,16.37,46.84,16.2,46.85,16.53,47.5,16.34,47.71,16.9,47.71,16.98,48.12,17.49,47.87,17.86,47.76,18.7,47.88,18.78,48.08,19.17,48.11,19.66,48.27,19.77,48.2,20.24,48.33,20.47,48.56,20.8,48.62,21.87,48.32,22.09,48.42]]]},{"n":"Moldova","c":"MD","r":6,"ml":5,"lx":28.49,"ly":47.43,"b":[26.62,45.49,30.02,48.47],"p":[[[26.62,48.22,26.86,48.37,27.52,48.47,28.26,48.16,28.67,48.12,29.12,47.85,29.05,47.51,29.42,47.35,29.56,46.93,29.91,46.67,29.84,46.53,30.02,46.42,29.76,46.35,29.17,46.38,29.07,46.52,28.86,46.44,28.93,46.26,28.66,45.94,28.49,45.6,28.23,45.49,28.05,45.94,28.16,46.37,28.13,46.81,27.55,47.41,27.23,47.83,26.92,48.12,26.62,48.22]]]},{"n":"Romania","c":"RO","r":3,"ml":3,"lx":24.97,"ly":45.73,"b":[20.22,43.69,29.63,48.22],"p":[[[28.23,45.49,28.68,45.3,29.15,45.46,29.6,45.29,29.63,45.04,29.14,44.82,28.84,44.91,28.56,43.71,27.97,43.81,27.24,44.18,26.07,43.94,25.57,43.69,24.1,43.74,23.33,43.9,22.94,43.82,22.66,44.23,22.47,44.41,22.71,44.58,22.46,44.7,22.15,44.48,21.56,44.77,21.48,45.18,20.87,45.42,20.76,45.73,20.22,46.13,21.02,46.32,21.63,46.99,22.1,47.67,22.71,47.88,23.14,48.1,23.76,47.99,24.4,47.98,24.87,47.74,25.21,47.89,25.95,47.99,26.2,48.22,26.62,48.22,26.92,48.12,27.23,47.83,27.55,47.41,28.13,46.81,28.16,46.37,28.05,45.94,28.23,45.49]]]},{"n":"Lithuania","c":"LT","r":5,"ml":4,"lx":24.09,"ly":55.1,"b":[21.06,53.91,26.59,56.37],"p":[[[26.49,55.62,26.59,55.17,25.77,54.85,25.54,54.28,24.45,53.91,23.48,53.91,23.24,54.22,22.73,54.33,22.65,54.58,22.76,54.86,22.32,55.02,21.27,55.19,21.06,56.03,22.2,56.34,23.88,56.27,24.86,56.37,25,56.16,25.53,56.1,26.49,55.62]]]},{"n":"Latvia","c":"LV","r":5,"ml":4,"lx":25.46,"ly":57.07,"b":[21.06,55.62,28.18,57.97],"p":[[[27.29,57.47,27.77,57.24,27.86,56.76,28.18,56.17,27.1,55.78,26.49,55.62,25.53,56.1,25,56.16,24.86,56.37,23.88,56.27,22.2,56.34,21.06,56.03,21.09,56.78,21.58,57.41,22.52,57.75,23.32,57.01,24.12,57.03,24.31,57.79,25.16,57.97,25.6,57.85,26.46,57.48,27.29,57.47]]]},{"n":"Estonia","c":"EE","r":6,"ml":3,"lx":25.87,"ly":58.72,"b":[23.34,57.47,28.13,59.61],"p":[[[27.98,59.48,28.13,59.3,27.42,58.72,27.72,57.79,27.29,57.47,26.46,57.48,25.6,57.85,25.16,57.97,24.31,57.79,24.43,58.38,24.06,58.26,23.43,58.61,23.34,59.19,24.6,59.47,25.86,59.61,26.95,59.45,27.98,59.48]]]},{"n":"Germany","c":"DE","r":2,"ml":1.7,"lx":9.68,"ly":50.96,"b":[5.99,47.3,15.02,54.98],"p":[[[14.12,53.76,14.35,53.25,14.07,52.98,14.44,52.62,14.69,52.09,14.61,51.75,15.02,51.11,14.57,51,14.31,51.12,14.06,50.93,13.34,50.73,12.97,50.48,12.24,50.27,12.42,49.97,12.52,49.55,13.03,49.31,13.6,48.88,13.24,48.42,12.88,48.29,13.03,47.64,12.93,47.47,12.62,47.67,12.14,47.7,11.43,47.52,10.54,47.57,10.4,47.3,9.9,47.58,9.59,47.53,8.52,47.83,8.32,47.61,7.47,47.62,7.59,48.33,8.1,49.02,6.66,49.2,6.19,49.46,6.24,49.9,6.04,50.13,6.16,50.8,5.99,51.85,6.59,51.85,6.84,52.23,7.09,53.14,6.91,53.48,7.1,53.69,7.94,53.75,8.12,53.53,8.8,54.02,8.57,54.4,8.53,54.96,9.28,54.83,9.92,54.98,9.94,54.6,10.95,54.36,10.94,54.01,11.96,54.2,12.52,54.47,13.65,54.08,14.12,53.76]]]},{"n":"Bulgaria","c":"BG","r":4,"ml":4,"lx":25.16,"ly":42.51,"b":[22.38,41.23,28.56,44.23],"p":[[[22.66,44.23,22.94,43.82,23.33,43.9,24.1,43.74,25.57,43.69,26.07,43.94,27.24,44.18,27.97,43.81,28.56,43.71,28.04,43.29,27.67,42.58,28,42.01,27.14,42.14,26.12,41.83,26.11,41.33,25.2,41.23,24.49,41.58,23.69,41.31,22.95,41.34,22.88,42,22.38,42.32,22.55,42.46,22.44,42.58,22.6,42.9,22.99,43.21,22.5,43.64,22.41,44.01,22.66,44.23]]]},{"n":"Greece","c":"GR","r":3,"ml":2.7,"lx":21.73,"ly":39.49,"b":[20.15,34.92,26.6,41.83],"p":[[[26.29,35.3,26.16,35,24.72,34.92,24.74,35.08,23.51,35.28,23.7,35.71,24.25,35.37,25.03,35.42,25.77,35.35,25.75,35.18,26.29,35.3]],[[22.95,41.34,23.69,41.31,24.49,41.58,25.2,41.23,26.11,41.33,26.12,41.83,26.6,41.56,26.29,40.94,26.06,40.82,25.45,40.85,24.93,40.95,23.71,40.69,24.41,40.12,23.9,39.96,23.34,39.96,22.81,40.48,22.63,40.26,22.85,39.66,23.35,39.19,22.97,38.97,23.53,38.51,24.03,38.22,24.04,37.66,23.12,37.92,23.41,37.41,22.77,37.31,23.15,36.42,22.49,36.41,21.67,36.84,21.3,37.64,21.12,38.31,20.73,38.77,20.22,39.34,20.15,39.62,20.62,40.11,20.67,40.44,21,40.58,21.02,40.84,21.67,40.93,22.06,41.15,22.6,41.13,22.76,41.3,22.95,41.34]]]},{"n":"Turkey","c":"TR","r":2,"ml":2,"lx":34.51,"ly":39.35,"b":[26.04,35.82,44.79,42.14],"p":[[[44.77,37.17,44.29,37,43.94,37.26,42.78,37.39,42.35,37.23,41.21,37.07,40.67,37.09,39.52,36.72,38.7,36.71,38.17,36.9,37.07,36.62,36.74,36.82,36.69,36.26,36.42,36.04,36.15,35.82,35.78,36.27,36.16,36.65,35.55,36.57,34.71,36.8,34.03,36.22,32.51,36.11,31.7,36.64,30.62,36.68,30.39,36.26,29.7,36.14,28.73,36.68,27.64,36.66,27.05,37.65,26.32,38.21,26.8,38.99,26.17,39.46,27.28,40.42,28.82,40.46,29.24,41.22,31.15,41.09,32.35,41.74,33.51,42.02,35.17,42.04,36.91,41.34,38.35,40.95,39.51,41.1,40.37,41.01,41.55,41.54,42.62,41.58,43.58,41.09,43.75,40.74,43.66,40.25,44.4,40.01,44.79,39.71,44.11,39.43,44.42,38.28,44.23,37.97,44.77,37.17]],[[26.12,41.83,27.14,42.14,28,42.01,28.12,41.62,28.99,41.3,28.81,41.05,27.62,41,27.19,40.69,26.36,40.15,26.04,40.62,26.06,40.82,26.29,40.94,26.6,41.56,26.12,41.83]]]},{"n":"Albania","c":"AL","r":6,"ml":5,"lx":20.11,"ly":40.65,"b":[19.3,39.62,21.02,42.69],"p":[[[21.02,40.84,21,40.58,20.67,40.44,20.62,40.11,20.15,39.62,19.98,39.69,19.96,39.92,19.41,40.25,19.32,40.73,19.4,41.41,19.54,41.72,19.37,41.88,19.3,42.2,19.74,42.69,19.8,42.5,20.07,42.59,20.28,42.32,20.52,42.22,20.59,41.86,20.46,41.52,20.61,41.09,21.02,40.84]]]},{"n":"Croatia","c":"HR","r":6,"ml":4,"lx":16.37,"ly":45.81,"b":[13.66,42.48,19.39,46.5],"p":[[[16.56,46.5,16.88,46.38,17.63,45.95,18.46,45.76,18.83,45.91,19.07,45.52,19.39,45.24,19.01,44.86,18.55,45.08,17.86,45.07,17,45.23,16.53,45.21,16.32,45,15.96,45.23,15.75,44.82,16.24,44.35,16.46,44.04,16.92,43.67,17.3,43.45,17.67,43.03,18.56,42.65,18.45,42.48,17.51,42.85,16.93,43.21,16.02,43.51,15.17,44.24,15.38,44.32,14.92,44.74,14.9,45.08,14.26,45.23,13.95,44.8,13.66,45.14,13.68,45.48,13.72,45.5,14.41,45.47,14.6,45.63,14.94,45.47,15.33,45.45,15.32,45.73,15.67,45.83,15.77,46.24,16.56,46.5]]]},{"n":"Switzerland","c":"CH","r":4,"ml":4,"lx":7.46,"ly":46.72,"b":[6.02,45.78,10.44,47.83],"p":[[[9.59,47.53,9.63,47.35,9.48,47.1,9.93,46.92,10.44,46.89,10.36,46.48,9.92,46.31,9.18,46.44,8.97,46.04,8.49,46.01,8.32,46.16,7.76,45.82,7.27,45.78,6.84,45.99,6.5,46.43,6.02,46.27,6.04,46.73,6.77,47.29,6.74,47.54,7.19,47.45,7.47,47.62,8.32,47.61,8.52,47.83,9.59,47.53]]]},{"n":"Luxembourg","c":"LU","r":6,"ml":5.7,"lx":6.08,"ly":49.73,"b":[5.67,49.44,6.24,50.13],"p":[[[6.04,50.13,6.24,49.9,6.19,49.46,5.9,49.44,5.67,49.53,5.78,50.09,6.04,50.13]]]},{"n":"Belgium","c":"BE","r":2,"ml":4,"lx":4.8,"ly":50.79,"b":[2.51,49.53,6.16,51.48],"p":[[[6.16,50.8,6.04,50.13,5.78,50.09,5.67,49.53,4.8,49.99,4.29,49.91,3.59,50.38,3.12,50.78,2.66,50.8,2.51,51.15,3.31,51.35,3.32,51.35,3.31,51.35,4.05,51.27,4.97,51.48,5.61,51.04,6.16,50.8]]]},{"n":"Netherlands","c":"NL","r":5,"ml":4,"lx":5.61,"ly":52.42,"b":[3.31,50.8,7.09,53.51],"p":[[[6.91,53.48,7.09,53.14,6.84,52.23,6.59,51.85,5.99,51.85,6.16,50.8,5.61,51.04,4.97,51.48,4.05,51.27,3.31,51.35,3.32,51.35,3.83,51.62,4.71,53.09,6.07,53.51,6.91,53.48]]]},{"n":"Portugal","c":"PT","r":2,"ml":3,"lx":-8.27,"ly":39.61,"b":[-9.53,36.84,-6.39,42.28],"p":[[[-9.03,41.88,-8.67,42.13,-8.26,42.28,-8.01,41.79,-7.42,41.79,-7.25,41.92,-6.67,41.88,-6.39,41.38,-6.85,41.11,-6.86,40.33,-7.03,40.18,-7.07,39.71,-7.5,39.63,-7.1,39.03,-7.37,38.37,-7.03,38.08,-7.17,37.8,-7.54,37.43,-7.45,37.1,-7.86,36.84,-8.38,36.98,-8.9,36.87,-8.75,37.65,-8.84,38.27,-9.29,38.36,-9.53,38.74,-9.45,39.39,-9.05,39.76,-8.98,40.16,-8.77,40.76,-8.79,41.18,-8.99,41.54,-9.03,41.88]]]},{"n":"Spain","c":"ES","r":2,"ml":2,"lx":-3.46,"ly":40.09,"b":[-9.39,35.95,3.04,43.75],"p":[[[-7.45,37.1,-7.54,37.43,-7.17,37.8,-7.03,38.08,-7.37,38.37,-7.1,39.03,-7.5,39.63,-7.07,39.71,-7.03,40.18,-6.86,40.33,-6.85,41.11,-6.39,41.38,-6.67,41.88,-7.25,41.92,-7.42,41.79,-8.01,41.79,-8.26,42.28,-8.67,42.13,-9.03,41.88,-8.98,42.59,-9.39,43.03,-7.98,43.75,-6.75,43.57,-5.41,43.57,-4.35,43.4,-3.52,43.46,-1.9,43.42,-1.5,43.03,0.34,42.58,0.7,42.8,1.83,42.34,2.99,42.47,3.04,41.89,2.09,41.23,0.81,41.01,0.72,40.68,0.11,40.12,-0.28,39.31,0.11,38.74,-0.47,38.29,-0.68,37.64,-1.44,37.44,-2.15,36.67,-3.42,36.66,-4.37,36.68,-5,36.32,-5.38,35.95,-5.87,36.03,-6.24,36.37,-6.52,36.94,-7.45,37.1]]]},{"n":"Ireland","c":"IE","r":3,"ml":3,"lx":-7.8,"ly":53.08,"b":[-9.98,51.67,-6.03,55.13],"p":[[[-6.2,53.87,-6.03,53.15,-6.79,52.26,-8.56,51.67,-9.98,51.82,-9.17,52.86,-9.69,53.88,-8.33,54.66,-7.57,55.13,-7.37,54.6,-7.57,54.06,-6.95,54.07,-6.2,53.87]]]},{"n":"New Caledonia","c":"NC","r":3,"ml":4.6,"lx":165.08,"ly":-21.06,"b":[164.03,-22.4,167.12,-20.11],"p":[[[165.78,-21.08,166.6,-21.7,167.12,-22.16,166.74,-22.4,166.19,-22.13,165.47,-21.68,164.83,-21.15,164.17,-20.44,164.03,-20.11,164.46,-20.12,165.02,-20.46,165.46,-20.8,165.78,-21.08]]]},{"n":"Solomon Islands","c":"SB","r":3,"ml":3,"lx":159.17,"ly":-8.03,"b":[156.49,-10.83,162.4,-6.6],"p":[[[162.12,-10.48,162.4,-10.83,161.7,-10.82,161.32,-10.2,161.92,-10.45,162.12,-10.48]],[[161.68,-9.6,161.53,-9.78,160.79,-8.92,160.58,-8.32,160.92,-8.32,161.28,-9.12,161.68,-9.6]],[[160.85,-9.87,160.46,-9.9,159.85,-9.79,159.64,-9.64,159.7,-9.24,160.36,-9.4,160.69,-9.61,160.85,-9.87]],[[159.64,-8.02,159.88,-8.34,159.92,-8.54,159.13,-8.11,158.59,-7.75,158.21,-7.42,158.36,-7.32,158.82,-7.56,159.64,-8.02]],[[157.14,-7.02,157.54,-7.35,157.34,-7.4,156.9,-7.18,156.49,-6.77,156.54,-6.6,157.14,-7.02]]]},{"n":"New Zealand","c":"NZ","r":2,"ml":2,"lx":172.79,"ly":-39.76,"b":[166.51,-46.64,178.52,-34.45],"p":[[[176.89,-40.07,176.51,-40.6,176.01,-41.29,175.24,-41.69,175.07,-41.43,174.65,-41.28,175.23,-40.46,174.9,-39.91,173.82,-39.51,173.85,-39.15,174.57,-38.8,174.74,-38.03,174.7,-37.38,174.29,-36.71,174.32,-36.53,173.84,-36.12,173.05,-35.24,172.64,-34.53,173.01,-34.45,173.55,-35.01,174.33,-35.27,174.61,-36.16,175.34,-37.21,175.36,-36.53,175.81,-36.8,175.96,-37.56,176.76,-37.88,177.44,-37.96,178.01,-37.58,178.52,-37.7,178.27,-38.58,177.97,-39.17,177.21,-39.15,176.94,-39.45,177.03,-39.88,176.89,-40.07]],[[169.67,-43.56,170.52,-43.03,171.13,-42.51,171.57,-41.77,171.95,-41.51,172.1,-40.96,172.8,-40.49,173.02,-40.92,173.25,-41.33,173.96,-40.93,174.25,-41.35,174.25,-41.77,173.88,-42.23,173.22,-42.97,172.71,-43.37,173.08,-43.85,172.31,-43.87,171.45,-44.24,171.19,-44.9,170.62,-45.91,169.83,-46.36,169.33,-46.64,168.41,-46.62,167.76,-46.29,166.68,-46.22,166.51,-45.85,167.05,-45.11,168.3,-44.12,168.95,-43.94,169.67,-43.56]]]},{"n":"Australia","c":"AU","r":2,"ml":1.7,"lx":134.05,"ly":-24.13,"b":[113.34,-43.63,153.57,-10.67],"p":[[[147.69,-40.81,148.29,-40.88,148.36,-42.06,148.02,-42.41,147.91,-43.21,147.56,-42.94,146.87,-43.63,146.66,-43.58,146.05,-43.55,145.43,-42.69,145.3,-42.03,144.72,-41.16,144.74,-40.7,145.4,-40.79,146.36,-41.14,146.91,-41,147.69,-40.81]],[[126.15,-32.22,125.09,-32.73,124.22,-32.96,124.03,-33.48,123.66,-33.89,122.81,-33.91,122.18,-34,121.3,-33.82,120.58,-33.93,119.89,-33.98,119.3,-34.51,119.01,-34.46,118.51,-34.75,118.02,-35.06,117.3,-35.03,116.63,-35.03,115.56,-34.39,115.03,-34.2,115.05,-33.62,115.55,-33.49,115.71,-33.26,115.68,-32.9,115.8,-32.21,115.69,-31.61,115.16,-30.6,115,-30.03,115.04,-29.46,114.64,-28.81,114.62,-28.52,114.17,-28.12,114.05,-27.33,113.48,-26.54,113.34,-26.12,113.78,-26.55,113.44,-25.62,113.94,-25.91,114.23,-26.3,114.22,-25.79,113.72,-25,113.63,-24.68,113.39,-24.38,113.5,-23.81,113.71,-23.56,113.84,-23.06,113.74,-22.48,114.15,-21.76,114.23,-22.52,114.65,-21.83,115.46,-21.5,115.95,-21.07,116.71,-20.7,117.17,-20.62,117.44,-20.75,118.23,-20.37,118.84,-20.26,118.99,-20.04,119.25,-19.95,119.81,-19.98,120.86,-19.68,121.4,-19.24,121.66,-18.71,122.24,-18.2,122.29,-17.8,122.31,-17.25,123.01,-16.41,123.43,-17.27,123.86,-17.07,123.5,-16.6,123.82,-16.11,124.26,-16.33,124.38,-15.57,124.93,-15.08,125.17,-14.68,125.67,-14.51,125.69,-14.23,126.13,-14.35,126.14,-14.1,126.58,-13.95,127.07,-13.82,127.8,-14.28,128.36,-14.87,128.99,-14.88,129.62,-14.97,129.41,-14.42,129.89,-13.62,130.34,-13.36,130.18,-13.11,130.62,-12.54,131.22,-12.18,131.74,-12.3,132.58,-12.11,132.56,-11.6,131.82,-11.27,132.36,-11.13,133.02,-11.38,133.55,-11.79,134.39,-12.04,134.68,-11.94,135.3,-12.25,135.88,-11.96,136.26,-12.05,136.49,-11.86,136.95,-12.35,136.69,-12.89,136.31,-13.29,135.96,-13.32,136.08,-13.72,135.78,-14.22,135.43,-14.72,135.5,-15,136.3,-15.55,137.07,-15.87,137.58,-16.22,138.3,-16.81,138.59,-16.81,139.11,-17.06,139.26,-17.37,140.22,-17.71,140.88,-17.37,141.07,-16.83,141.27,-16.39,141.4,-15.84,141.7,-15.04,141.56,-14.56,141.64,-14.27,141.52,-13.7,141.65,-12.94,141.84,-12.74,141.69,-12.41,141.93,-11.88,142.12,-11.33,142.14,-11.04,142.52,-10.67,142.8,-11.16,142.87,-11.78,143.12,-11.91,143.16,-12.33,143.52,-12.83,143.6,-13.4,143.56,-13.76,143.92,-14.55,144.56,-14.17,144.89,-14.59,145.37,-14.98,145.27,-15.43,145.49,-16.29,145.64,-16.78,145.89,-16.91,146.16,-17.76,146.06,-18.28,146.39,-18.96,147.47,-19.48,148.18,-19.96,148.85,-20.39,148.72,-20.63,149.29,-21.26,149.68,-22.34,150.08,-22.12,150.48,-22.56,150.73,-22.4,150.9,-23.46,151.61,-24.08,152.07,-24.46,152.86,-25.27,153.14,-26.07,153.16,-26.64,153.09,-27.26,153.57,-28.11,153.51,-29,153.34,-29.46,153.07,-30.35,153.09,-30.92,152.89,-31.64,152.45,-32.55,151.71,-33.04,151.34,-33.82,151.01,-34.31,150.71,-35.17,150.33,-35.67,150.08,-36.42,149.95,-37.11,150,-37.43,149.42,-37.77,148.3,-37.81,147.38,-38.22,146.92,-38.61,146.32,-39.04,145.49,-38.59,144.88,-38.42,145.03,-37.9,144.49,-38.09,143.61,-38.81,142.75,-38.54,142.18,-38.38,141.61,-38.31,140.64,-38.02,139.99,-37.4,139.81,-36.64,139.57,-36.14,139.08,-35.73,138.12,-35.61,138.45,-35.13,138.21,-34.38,137.72,-35.08,136.83,-35.26,137.35,-34.71,137.5,-34.13,137.89,-33.64,137.81,-32.9,137,-33.75,136.37,-34.09,135.99,-34.89,135.21,-34.48,135.24,-33.95,134.61,-33.22,134.09,-32.85,134.27,-32.62,132.99,-32.01,132.29,-31.98,131.33,-31.5,129.54,-31.59,128.24,-31.95,127.1,-32.28,126.15,-32.22]]]},{"n":"Sri Lanka","c":"LK","r":3,"ml":3,"lx":80.7,"ly":7.58,"b":[79.7,5.97,81.79,9.82],"p":[[[81.79,7.52,81.64,6.48,81.22,6.2,80.35,5.97,79.87,6.76,79.7,8.2,80.15,9.82,80.84,9.27,81.3,8.56,81.79,7.52]]]},{"n":"People's Republic of China","c":"CN","r":2,"ml":1.7,"lx":106.34,"ly":32.5,"b":[73.68,18.2,135.03,53.46],"p":[[[109.48,18.2,108.66,18.51,108.63,19.37,109.12,19.82,110.21,20.1,110.79,20.08,111.01,19.7,110.57,19.26,110.34,18.68,109.48,18.2]],[[80.26,42.35,80.18,42.92,80.87,43.18,79.97,44.92,81.95,45.32,82.46,45.54,83.18,47.33,85.16,47,85.72,47.45,85.77,48.46,86.6,48.55,87.36,49.21,87.75,49.3,88.01,48.6,88.85,48.07,90.28,47.69,90.97,46.89,90.59,45.72,90.95,45.29,92.13,45.12,93.48,44.98,94.69,44.35,95.31,44.24,95.76,43.32,96.35,42.73,97.45,42.75,99.52,42.52,100.85,42.66,101.83,42.51,103.31,41.91,104.52,41.91,104.96,41.6,106.13,42.13,107.74,42.48,109.24,42.52,110.41,42.87,111.13,43.41,111.83,43.74,111.67,44.07,111.35,44.46,111.87,45.1,112.44,45.01,113.46,44.81,114.46,45.34,115.99,45.73,116.72,46.39,117.42,46.67,118.87,46.81,119.66,46.69,119.77,47.05,118.87,47.75,118.06,48.07,117.3,47.7,116.31,47.85,115.74,47.73,115.49,48.14,116.19,49.13,116.68,49.89,117.88,49.51,119.29,50.14,119.28,50.58,120.18,51.64,120.74,51.96,120.73,52.52,120.18,52.75,121,53.25,122.25,53.43,123.57,53.46,125.07,53.16,125.95,52.79,126.56,51.78,126.94,51.35,127.29,50.74,127.66,49.76,129.4,49.44,130.58,48.73,130.99,47.79,132.51,47.79,133.37,48.18,135.03,48.48,134.5,47.58,134.11,47.21,133.77,46.12,133.1,45.14,131.88,45.32,131.03,44.97,131.29,44.11,131.14,42.93,130.63,42.9,130.64,42.4,129.99,42.99,129.6,42.42,128.05,41.99,128.21,41.47,127.34,41.5,126.87,41.82,126.18,41.11,125.08,40.57,124.27,39.93,122.87,39.64,122.13,39.17,121.05,38.9,121.59,39.36,121.38,39.75,122.17,40.42,121.64,40.95,120.77,40.59,119.64,39.9,119.02,39.25,118.04,39.2,117.53,38.74,118.06,38.06,118.88,37.9,118.91,37.45,119.7,37.16,120.82,37.87,121.71,37.48,122.36,37.45,122.52,36.93,121.1,36.65,120.64,36.11,119.66,35.61,119.15,34.91,120.23,34.36,120.62,33.38,121.23,32.46,121.91,31.69,121.89,30.95,121.26,30.68,121.5,30.14,122.09,29.83,121.94,29.02,121.68,28.23,121.13,28.14,120.4,27.05,119.59,25.74,118.66,24.55,117.28,23.62,115.89,22.78,114.76,22.67,114.15,22.22,113.81,22.55,113.24,22.05,111.84,21.55,110.79,21.4,110.44,20.34,109.89,20.28,109.63,21.01,109.86,21.4,108.52,21.72,108.05,21.55,107.04,21.81,106.57,22.22,106.73,22.79,105.81,22.98,105.33,23.35,104.48,22.82,103.5,22.7,102.71,22.71,102.17,22.46,101.65,22.32,101.8,21.17,101.27,21.2,101.18,21.44,101.15,21.85,100.42,21.56,99.98,21.74,99.24,22.12,99.53,22.95,98.9,23.14,98.66,24.06,97.6,23.9,97.72,25.08,98.67,25.92,98.71,26.74,98.68,27.51,98.25,27.75,97.91,28.34,97.33,28.26,96.25,28.41,96.59,28.83,96.12,29.45,95.4,29.03,94.57,29.28,93.41,28.64,92.5,27.9,91.7,27.77,91.26,28.04,90.73,28.06,90.02,28.3,89.48,28.04,88.81,27.3,88.73,28.09,88.12,27.88,86.95,27.97,85.82,28.2,85.01,28.64,84.23,28.84,83.9,29.32,83.34,29.46,82.33,30.12,81.53,30.42,81.11,30.18,79.72,30.88,78.74,31.52,78.46,32.62,79.18,32.48,79.21,32.99,78.81,33.51,78.91,34.32,77.84,35.49,76.19,35.9,75.9,36.67,75.16,37.13,74.98,37.42,74.83,37.99,74.86,38.38,74.26,38.61,73.93,38.51,73.68,39.43,73.96,39.66,73.82,39.89,74.78,40.37,75.47,40.56,76.53,40.43,76.9,41.07,78.19,41.19,78.54,41.58,80.12,42.12,80.26,42.35]]]},{"n":"Taiwan","c":"TW","r":3,"ml":4.5,"lx":120.87,"ly":23.65,"b":[120.11,21.97,121.95,25.3],"p":[[[121.78,24.39,121.18,22.79,120.75,21.97,120.22,22.81,120.11,23.56,120.69,24.54,121.5,25.3,121.95,25,121.78,24.39]]]},{"n":"Italy","c":"IT","r":2,"ml":2,"lx":11.08,"ly":44.73,"b":[6.75,36.62,18.48,47.12],"p":[[[10.44,46.89,11.05,46.75,11.16,46.94,12.15,47.12,12.38,46.77,13.81,46.51,13.7,46.02,13.94,45.59,13.14,45.74,12.33,45.38,12.38,44.89,12.26,44.6,12.59,44.09,13.53,43.59,14.03,42.76,15.14,41.96,15.93,41.96,16.17,41.74,15.89,41.54,16.79,41.18,17.52,40.88,18.38,40.36,18.48,40.17,18.29,39.81,17.74,40.28,16.87,40.44,16.45,39.8,17.17,39.42,17.05,38.9,16.64,38.84,16.1,37.99,15.68,37.91,15.69,38.21,15.89,38.75,16.11,38.96,15.72,39.54,15.41,40.05,15,40.17,14.7,40.6,14.06,40.79,13.63,41.19,12.89,41.25,12.11,41.7,11.19,42.36,10.51,42.93,10.2,43.92,9.7,44.04,8.89,44.37,8.43,44.23,7.85,43.77,7.44,43.69,7.55,44.13,7.01,44.25,6.75,45.03,7.1,45.33,6.8,45.71,6.84,45.99,7.27,45.78,7.76,45.82,8.32,46.16,8.49,46.01,8.97,46.04,9.18,46.44,9.92,46.31,10.36,46.48,10.44,46.89]],[[14.76,38.14,15.52,38.23,15.16,37.44,15.31,37.13,15.1,36.62,14.34,37,13.83,37.1,12.43,37.61,12.57,38.13,13.74,38.03,14.76,38.14]],[[8.71,40.9,9.21,41.21,9.81,40.5,9.67,39.18,9.21,39.24,8.81,38.91,8.43,39.17,8.39,40.38,8.16,40.95,8.71,40.9]]]},{"n":"Denmark","c":"DK","r":4,"ml":3,"lx":9.02,"ly":55.97,"b":[8.09,54.8,12.69,57.73],"p":[[[9.92,54.98,9.28,54.83,8.53,54.96,8.12,55.52,8.09,56.54,8.26,56.81,8.54,57.11,9.42,57.17,9.78,57.45,10.58,57.73,10.55,57.22,10.25,56.89,10.37,56.61,10.91,56.46,10.67,56.08,10.37,56.19,9.65,55.47,9.92,54.98]],[[12.37,56.11,12.69,55.61,12.09,54.8,11.04,55.36,10.9,55.78,12.37,56.11]]]},{"n":"United Kingdom","c":"GB","r":2,"ml":1.7,"lx":-2.12,"ly":54.4,"b":[-7.57,49.96,1.68,58.64],"p":[[[-6.2,53.87,-6.95,54.07,-7.57,54.06,-7.37,54.6,-7.57,55.13,-6.73,55.17,-5.66,54.55,-6.2,53.87]],[[-3.09,53.4,-2.95,53.99,-3.61,54.6,-3.63,54.62,-4.84,54.79,-5.08,55.06,-4.72,55.51,-5.05,55.78,-5.59,55.31,-5.64,56.28,-6.15,56.79,-5.79,57.82,-5.01,58.63,-4.21,58.55,-3.01,58.64,-4.07,57.55,-3.06,57.69,-1.96,57.68,-2.22,56.87,-3.12,55.97,-2.09,55.91,-2.01,55.8,-1.11,54.62,-0.43,54.46,0.18,53.33,0.47,52.93,1.68,52.74,1.56,52.1,1.05,51.81,1.45,51.29,0.55,50.77,-0.79,50.77,-2.49,50.5,-2.96,50.7,-3.62,50.23,-4.54,50.34,-5.25,49.96,-5.78,50.16,-4.31,51.21,-3.41,51.43,-3.42,51.43,-4.98,51.59,-5.27,51.99,-4.22,52.3,-4.77,52.84,-4.58,53.5,-3.09,53.4]]]},{"n":"Iceland","c":"IS","r":3,"ml":2,"lx":-18.67,"ly":64.78,"b":[-24.33,63.5,-13.61,66.53],"p":[[[-14.51,66.46,-14.74,65.81,-13.61,65.13,-14.91,64.36,-17.79,63.68,-18.66,63.5,-19.97,63.64,-22.76,63.96,-21.78,64.4,-23.96,64.89,-22.18,65.08,-22.23,65.38,-24.33,65.61,-23.65,66.26,-22.13,66.41,-20.58,65.73,-19.06,66.28,-17.8,65.99,-16.17,66.53,-14.51,66.46]]]},{"n":"Azerbaijan","c":"AZ","r":5,"ml":4,"lx":47.21,"ly":40.4,"b":[44.79,38.27,50.39,41.86],"p":[[[46.4,41.86,46.69,41.83,47.37,41.22,47.82,41.15,47.99,41.41,48.58,41.81,49.11,41.28,49.62,40.57,50.08,40.53,50.39,40.26,49.57,40.18,49.4,39.4,49.22,39.05,48.86,38.82,48.88,38.32,48.63,38.27,48.01,38.79,48.36,39.29,48.06,39.58,47.69,39.51,46.51,38.77,46.48,39.46,46.03,39.63,45.61,39.9,45.89,40.22,45.36,40.56,45.56,40.81,45.18,40.99,44.97,41.25,45.22,41.41,45.96,41.12,46.5,41.06,46.64,41.18,46.15,41.72,46.4,41.86]],[[46.14,38.74,45.46,38.87,44.95,39.34,44.79,39.71,45,39.74,45.3,39.47,45.74,39.47,45.74,39.32,46.14,38.74]]]},{"n":"Georgia","c":"GE","r":5,"ml":4,"lx":43.74,"ly":41.87,"b":[39.96,41.06,46.64,43.55],"p":[[[39.96,43.43,40.08,43.55,40.92,43.38,42.39,43.22,43.76,42.74,43.93,42.55,44.54,42.71,45.47,42.5,45.78,42.09,46.4,41.86,46.15,41.72,46.64,41.18,46.5,41.06,45.96,41.12,45.22,41.41,44.97,41.25,43.58,41.09,42.62,41.58,41.55,41.54,41.7,41.96,41.45,42.65,40.88,43.01,40.32,43.13,39.96,43.43]]]},{"n":"Philippines","c":"PH","r":2,"ml":2.5,"lx":122.47,"ly":11.2,"b":[117.17,5.58,126.54,18.51],"p":[[[120.83,12.7,120.32,13.47,121.18,13.43,121.53,13.07,121.26,12.21,120.83,12.7]],[[122.59,9.98,122.84,10.26,122.95,10.88,123.5,10.94,123.34,10.27,124.08,11.23,123.98,10.28,123.62,9.95,123.31,9.32,123,9.02,122.38,9.71,122.59,9.98]],[[126.38,8.41,126.48,7.75,126.54,7.19,126.2,6.27,125.83,7.29,125.36,6.79,125.68,6.05,125.4,5.58,124.22,6.16,123.94,6.89,124.24,7.36,123.61,7.83,123.3,7.42,122.83,7.46,122.09,6.9,121.92,7.19,122.31,8.03,122.94,8.32,123.49,8.69,123.84,8.24,124.6,8.51,124.76,8.96,125.47,8.99,125.41,9.76,126.22,9.29,126.31,8.78,126.38,8.41]],[[118.5,9.32,117.17,8.37,117.66,9.07,118.39,9.68,118.99,10.38,119.51,11.37,119.69,10.55,119.03,10,118.5,9.32]],[[122.34,18.22,122.17,17.81,122.52,17.09,122.25,16.26,121.66,15.93,121.51,15.12,121.73,14.33,122.26,14.22,122.7,14.34,123.95,13.78,123.86,13.24,124.18,13,124.08,12.54,123.3,13.03,122.93,13.55,122.67,13.19,122.03,13.78,121.13,13.64,120.63,13.86,120.68,14.27,120.99,14.53,120.69,14.76,120.56,14.4,120.07,14.97,119.92,15.41,119.88,16.36,120.29,16.03,120.39,17.6,120.72,18.51,121.32,18.5,121.94,18.22,122.25,18.48,122.34,18.22]],[[122.04,11.42,121.88,11.89,122.48,11.58,123.12,11.58,123.1,11.17,122.64,10.74,122,10.44,121.97,10.91,122.04,11.42]],[[125.5,12.16,125.78,11.05,125.01,11.31,125.03,10.98,125.28,10.36,124.8,10.13,124.76,10.84,124.46,10.89,124.3,11.5,124.89,11.42,124.88,11.79,124.27,12.56,125.23,12.54,125.5,12.16]]]},{"n":"Malaysia","c":"MY","r":3,"ml":3,"lx":113.84,"ly":2.53,"b":[100.09,0.77,119.18,6.93],"p":[[[100.09,6.46,100.26,6.64,101.08,6.2,101.15,5.69,101.81,5.81,102.14,6.22,102.37,6.13,102.96,5.52,103.38,4.86,103.44,4.18,103.33,3.73,103.43,3.38,103.5,2.79,103.85,2.52,104.25,1.63,104.23,1.29,103.52,1.23,102.57,1.97,101.39,2.76,101.27,3.27,100.7,3.94,100.56,4.77,100.2,5.31,100.31,6.04,100.09,6.46]],[[117.88,4.14,117.02,4.31,115.87,4.31,115.52,3.17,115.13,2.82,114.62,1.43,113.81,1.22,112.86,1.5,112.38,1.41,111.8,0.9,111.16,0.98,110.51,0.77,109.83,1.34,109.66,2.01,110.4,1.66,111.17,1.85,111.37,2.7,111.8,2.89,113,3.1,113.71,3.89,114.2,4.53,114.66,4.01,114.87,4.35,115.35,4.32,115.41,4.96,115.45,5.45,116.22,6.14,116.73,6.92,117.13,6.93,117.64,6.42,117.69,5.99,118.35,5.71,119.18,5.41,119.11,5.02,118.44,4.97,118.62,4.48,117.88,4.14]]]},{"n":"Brunei","c":"BN","r":6,"ml":4,"lx":114.55,"ly":4.45,"b":[114.2,4.01,115.45,5.45],"p":[[[115.45,5.45,115.41,4.96,115.35,4.32,114.87,4.35,114.66,4.01,114.2,4.53,114.6,4.9,115.45,5.45]]]},{"n":"Slovenia","c":"SI","r":6,"ml":5,"lx":14.92,"ly":46.06,"b":[13.7,45.45,16.56,46.85],"p":[[[13.81,46.51,14.63,46.43,15.14,46.66,16.01,46.68,16.2,46.85,16.37,46.84,16.56,46.5,15.77,46.24,15.67,45.83,15.32,45.73,15.33,45.45,14.94,45.47,14.6,45.63,14.41,45.47,13.72,45.5,13.94,45.59,13.7,46.02,13.81,46.51]]]},{"n":"Finland","c":"FI","r":3,"ml":3,"lx":27.28,"ly":63.25,"b":[20.65,59.85,31.52,70.16],"p":[[[28.59,69.06,28.45,68.36,29.98,67.7,29.05,66.94,30.22,65.81,29.54,64.95,30.44,64.2,30.04,63.55,31.52,62.87,31.14,62.36,30.21,61.78,28.07,60.5,26.26,60.42,24.5,60.06,22.87,59.85,22.29,60.39,21.32,60.72,21.54,61.71,21.06,62.61,21.54,63.19,22.44,63.82,24.73,64.9,25.4,65.11,25.29,65.53,23.9,66.01,23.57,66.4,23.54,67.94,21.98,68.62,20.65,69.11,21.24,69.37,22.36,68.84,23.66,68.89,24.74,68.65,25.69,69.09,26.18,69.83,27.73,70.16,29.02,69.77,28.59,69.06]]]},{"n":"Slovakia","c":"SK","r":6,"ml":4,"lx":19.05,"ly":48.73,"b":[16.88,47.76,22.56,49.57],"p":[[[22.56,49.09,22.28,48.83,22.09,48.42,21.87,48.32,20.8,48.62,20.47,48.56,20.24,48.33,19.77,48.2,19.66,48.27,19.17,48.11,18.78,48.08,18.7,47.88,17.86,47.76,17.49,47.87,16.98,48.12,16.88,48.47,16.96,48.6,17.1,48.82,17.55,48.8,17.89,48.9,17.91,49,18.1,49.04,18.17,49.27,18.4,49.32,18.55,49.5,18.85,49.5,18.91,49.44,19.32,49.57,19.83,49.22,20.42,49.43,20.89,49.33,21.61,49.47,22.56,49.09]]]},{"n":"Czech Republic","c":"CZ","r":5,"ml":4,"lx":15.38,"ly":49.88,"b":[12.24,48.56,18.85,51.12],"p":[[[15.02,51.11,15.49,50.78,16.24,50.7,16.18,50.42,16.72,50.22,16.87,50.47,17.55,50.36,17.65,50.05,18.39,49.99,18.85,49.5,18.55,49.5,18.4,49.32,18.17,49.27,18.1,49.04,17.91,49,17.89,48.9,17.55,48.8,17.1,48.82,16.96,48.6,16.5,48.79,16.03,48.73,15.25,49.04,14.9,48.96,14.34,48.56,13.6,48.88,13.03,49.31,12.52,49.55,12.42,49.97,12.24,50.27,12.97,50.48,13.34,50.73,14.06,50.93,14.31,51.12,14.57,51,15.02,51.11]]]},{"n":"Eritrea","c":"ER","r":4,"ml":4,"lx":38.29,"ly":15.79,"b":[36.32,12.46,43.08,18],"p":[[[36.43,14.42,36.32,14.82,36.75,16.29,36.85,16.96,37.17,17.26,37.9,17.43,38.41,18,38.99,16.84,39.27,15.92,39.81,15.44,41.18,14.49,41.73,13.92,42.28,13.34,42.59,13,43.08,12.7,42.78,12.46,42.35,12.54,42.01,12.87,41.6,13.45,41.16,13.77,40.9,14.12,40.03,14.52,39.34,14.53,39.1,14.74,38.51,14.51,37.91,14.96,37.59,14.21,36.43,14.42]]]},{"n":"Japan","c":"JP","r":2,"ml":1.7,"lx":138.44,"ly":36.14,"b":[129.41,31.03,145.54,45.55],"p":[[[141.88,39.18,140.96,38.17,140.98,37.14,140.6,36.34,140.77,35.84,140.25,35.14,138.98,34.67,137.22,34.61,135.79,33.46,135.12,33.85,135.08,34.6,133.34,34.38,132.16,33.9,130.99,33.89,132,33.15,131.33,31.45,130.69,31.03,130.2,31.42,130.45,32.32,129.81,32.61,129.41,33.3,130.35,33.6,130.88,34.23,131.88,34.75,132.62,35.43,134.61,35.73,135.68,35.53,136.72,37.3,137.39,36.83,138.86,37.83,139.43,38.22,140.05,39.44,139.88,40.56,140.31,41.2,141.37,41.38,141.91,39.99,141.88,39.18]],[[144.61,43.96,145.32,44.38,145.54,43.26,144.06,42.99,143.18,42,141.61,42.68,141.07,41.58,139.96,41.57,139.82,42.56,140.31,43.33,141.38,43.39,141.67,44.77,141.97,45.55,143.14,44.51,143.91,44.17,144.61,43.96]],[[132.37,33.46,132.92,34.06,133.49,33.94,133.9,34.36,134.64,34.15,134.77,33.81,134.2,33.2,133.79,33.52,133.28,33.29,133.01,32.7,132.36,32.99,132.37,33.46]]]},{"n":"Paraguay","c":"PY","r":4,"ml":3,"lx":-60.15,"ly":-21.67,"b":[-62.69,-27.55,-54.29,-19.34],"p":[[[-58.17,-20.18,-57.87,-20.73,-57.94,-22.09,-56.88,-22.28,-56.47,-22.09,-55.8,-22.36,-55.61,-22.66,-55.52,-23.57,-55.4,-23.96,-55.03,-24,-54.65,-23.84,-54.29,-24.02,-54.29,-24.57,-54.43,-25.16,-54.63,-25.74,-54.79,-26.62,-55.7,-27.39,-56.49,-27.55,-57.61,-27.4,-58.62,-27.12,-57.63,-25.6,-57.78,-25.16,-58.81,-24.77,-60.03,-24.03,-60.85,-23.88,-62.69,-22.25,-62.29,-21.05,-62.27,-20.51,-61.79,-19.63,-60.04,-19.34,-59.12,-19.36,-58.18,-19.87,-58.17,-20.18]]]},{"n":"Yemen","c":"YE","r":3,"ml":3,"lx":45.87,"ly":15.33,"b":[42.6,12.59,53.11,19],"p":[[[52,19,52.78,17.35,53.11,16.65,52.39,16.38,52.19,15.94,52.17,15.6,51.17,15.18,49.57,14.71,48.68,14,48.24,13.95,47.94,14.01,47.35,13.59,46.72,13.4,45.88,13.35,45.63,13.29,45.41,13.03,45.14,12.95,44.99,12.7,44.49,12.72,44.18,12.59,43.48,12.64,43.22,13.22,43.25,13.77,43.09,14.06,42.89,14.8,42.6,15.21,42.81,15.26,42.7,15.72,42.82,15.91,42.78,16.35,43.22,16.67,43.12,17.09,43.38,17.58,43.79,17.32,44.06,17.41,45.22,17.43,45.4,17.33,46.37,17.23,46.75,17.28,47,16.95,47.47,17.12,48.18,18.17,49.12,18.62,52,19]]]},{"n":"Saudi Arabia","c":"SA","r":2,"ml":1.7,"lx":44.7,"ly":23.81,"b":[34.63,16.35,55.67,32.16],"p":[[[34.96,29.36,36.07,29.2,36.5,29.51,36.74,29.87,37.5,30,37.67,30.34,38,30.51,37,31.51,39,32.01,39.2,32.16,40.4,31.89,41.89,31.19,44.71,29.18,46.57,29.1,47.46,29,47.71,28.53,48.42,28.55,48.81,27.69,49.3,27.46,49.47,27.11,50.15,26.69,50.21,26.28,50.11,25.94,50.24,25.61,50.53,25.33,50.66,25,50.81,24.75,51.11,24.56,51.39,24.63,51.58,24.25,51.62,24.01,52,23,55.01,22.5,55.21,22.71,55.67,22,55,20,52,19,49.12,18.62,48.18,18.17,47.47,17.12,47,16.95,46.75,17.28,46.37,17.23,45.4,17.33,45.22,17.43,44.06,17.41,43.79,17.32,43.38,17.58,43.12,17.09,43.22,16.67,42.78,16.35,42.65,16.77,42.35,17.08,42.27,17.47,41.75,17.83,41.22,18.67,40.94,19.49,40.25,20.17,39.8,20.34,39.14,21.29,39.02,21.99,39.07,22.58,38.49,23.69,38.02,24.08,37.48,24.29,37.15,24.86,37.21,25.08,36.93,25.6,36.64,25.83,36.25,26.57,35.64,27.38,35.13,28.06,34.63,28.06,34.79,28.61,34.83,28.96,34.96,29.36]]]},{"n":"Antarctica","c":"AQ","r":4,"ml":4,"lx":35.89,"ly":-79.84,"b":[-180,-90,180,-63.27],"p":[[[-48.66,-78.05,-48.15,-78.05,-46.66,-77.83,-45.15,-78.05,-43.92,-78.48,-43.49,-79.09,-43.37,-79.52,-43.33,-80.03,-44.88,-80.34,-46.51,-80.59,-48.39,-80.83,-50.48,-81.03,-52.85,-80.97,-54.16,-80.63,-53.99,-80.22,-51.85,-79.95,-50.99,-79.61,-50.36,-79.18,-49.91,-78.81,-49.31,-78.46,-48.66,-78.05]],[[-66.29,-80.26,-64.04,-80.29,-61.88,-80.39,-61.14,-79.98,-60.61,-79.63,-59.57,-80.04,-59.87,-80.55,-60.16,-81,-62.26,-80.86,-64.49,-80.92,-65.74,-80.59,-65.74,-80.55,-66.29,-80.26]],[[-73.92,-71.27,-73.23,-71.15,-72.07,-71.19,-71.78,-70.68,-71.72,-70.31,-71.74,-69.51,-71.17,-69.04,-70.25,-68.88,-69.72,-69.25,-69.49,-69.62,-69.06,-70.07,-68.73,-70.51,-68.45,-70.96,-68.33,-71.41,-68.51,-71.8,-68.78,-72.17,-69.96,-72.31,-71.08,-72.5,-72.39,-72.48,-71.9,-72.09,-73.07,-72.23,-74.19,-72.37,-74.95,-72.07,-75.01,-71.66,-73.92,-71.27]],[[-102.33,-71.89,-101.7,-71.72,-100.43,-71.85,-98.98,-71.93,-97.88,-72.07,-96.79,-71.95,-96.2,-72.52,-96.98,-72.44,-98.2,-72.48,-99.43,-72.44,-100.78,-72.5,-101.8,-72.31,-102.33,-71.89]],[[-122.62,-73.66,-122.41,-73.32,-121.21,-73.5,-119.92,-73.66,-118.72,-73.48,-119.29,-73.83,-120.23,-74.09,-121.62,-74.01,-122.62,-73.66]],[[-127.28,-73.46,-126.56,-73.25,-125.56,-73.48,-124.03,-73.87,-124.62,-73.83,-125.91,-73.74,-127.28,-73.46]],[[-163.71,-78.6,-163.11,-78.22,-161.25,-78.38,-160.25,-78.69,-159.48,-79.05,-159.21,-79.5,-161.13,-79.63,-162.44,-79.28,-163.03,-78.93,-163.07,-78.87,-163.71,-78.6]],[[180,-84.71,180,-90,-180,-90,-180,-84.71,-179.94,-84.72,-179.06,-84.14,-177.26,-84.45,-177.14,-84.42,-176.08,-84.1,-175.95,-84.11,-175.83,-84.12,-174.38,-84.53,-173.12,-84.12,-172.89,-84.06,-169.95,-83.88,-169,-84.12,-168.53,-84.24,-167.02,-84.57,-164.18,-84.83,-161.93,-85.14,-158.07,-85.37,-155.19,-85.1,-150.94,-85.3,-148.53,-85.61,-145.89,-85.32,-143.11,-85.04,-142.89,-84.57,-146.83,-84.53,-150.06,-84.3,-150.9,-83.9,-153.59,-83.69,-153.41,-83.24,-153.04,-82.83,-152.67,-82.45,-152.86,-82.04,-154.53,-81.77,-155.29,-81.42,-156.84,-81.1,-154.41,-81.16,-152.1,-81,-150.65,-81.34,-148.87,-81.04,-147.22,-80.67,-146.42,-80.34,-146.77,-79.93,-148.06,-79.65,-149.53,-79.36,-151.59,-79.3,-153.39,-79.16,-155.33,-79.06,-155.98,-78.69,-157.27,-78.38,-158.05,-78.03,-158.37,-76.89,-157.88,-76.99,-156.97,-77.3,-155.33,-77.2,-153.74,-77.07,-152.92,-77.5,-151.33,-77.4,-150,-77.18,-148.75,-76.91,-147.61,-76.58,-146.1,-76.48,-146.14,-76.11,-146.5,-75.73,-146.2,-75.38,-144.91,-75.2,-144.32,-75.54,-142.79,-75.34,-141.64,-75.09,-140.21,-75.07,-138.86,-74.97,-137.51,-74.73,-136.43,-74.52,-135.21,-74.3,-134.43,-74.36,-133.75,-74.44,-132.26,-74.3,-130.93,-74.48,-129.55,-74.46,-128.24,-74.32,-126.89,-74.42,-125.4,-74.52,-124.01,-74.48,-122.56,-74.5,-121.07,-74.52,-119.7,-74.48,-118.68,-74.19,-117.47,-74.03,-116.22,-74.24,-115.02,-74.07,-113.94,-73.71,-113.3,-74.03,-112.95,-74.38,-112.3,-74.71,-111.26,-74.42,-110.07,-74.79,-108.71,-74.91,-107.56,-75.18,-106.15,-75.13,-104.88,-74.95,-103.37,-74.99,-102.02,-75.13,-100.65,-75.3,-100.12,-74.87,-100.76,-74.54,-101.25,-74.19,-102.55,-74.11,-103.11,-73.73,-103.33,-73.36,-103.68,-72.62,-102.92,-72.75,-101.61,-72.81,-100.31,-72.75,-99.14,-72.91,-98.12,-73.21,-97.69,-73.56,-96.34,-73.62,-95.04,-73.48,-93.67,-73.28,-92.44,-73.17,-91.42,-73.4,-90.09,-73.32,-89.23,-72.56,-88.42,-73.01,-87.27,-73.19,-86.01,-73.09,-85.19,-73.48,-83.88,-73.52,-82.67,-73.64,-81.47,-73.85,-80.69,-73.48,-80.3,-73.13,-79.3,-73.52,-77.93,-73.42,-76.91,-73.64,-76.22,-73.97,-74.89,-73.87,-73.85,-73.66,-72.83,-73.4,-71.62,-73.26,-70.21,-73.15,-68.94,-73.01,-67.96,-72.79,-67.37,-72.48,-67.13,-72.05,-67.25,-71.64,-67.56,-71.25,-67.92,-70.85,-68.23,-70.46,-68.49,-70.11,-68.54,-69.72,-68.45,-69.33,-67.98,-68.95,-67.58,-68.54,-67.43,-68.15,-67.62,-67.72,-67.74,-67.33,-67.25,-66.88,-66.7,-66.58,-66.06,-66.21,-65.37,-65.9,-64.57,-65.6,-64.18,-65.17,-63.63,-64.9,-63,-64.64,-62.04,-64.58,-61.41,-64.27,-60.71,-64.07,-59.89,-63.96,-59.16,-63.7,-58.59,-63.39,-57.81,-63.27,-57.22,-63.53,-57.6,-63.86,-58.61,-64.15,-59.05,-64.37,-59.79,-64.21,-60.61,-64.31,-61.3,-64.54,-62.02,-64.8,-62.51,-65.09,-62.65,-65.48,-62.59,-65.86,-62.12,-66.19,-62.81,-66.43,-63.75,-66.5,-64.29,-66.84,-64.88,-67.15,-65.51,-67.58,-65.67,-67.95,-65.31,-68.37,-64.78,-68.68,-63.96,-68.91,-63.2,-69.23,-62.79,-69.62,-62.57,-69.99,-62.28,-70.38,-61.81,-70.72,-61.51,-71.09,-61.38,-72.01,-61.08,-72.38,-61,-72.77,-60.69,-73.17,-60.83,-73.7,-61.38,-74.11,-61.96,-74.44,-63.3,-74.58,-63.75,-74.93,-64.35,-75.26,-65.86,-75.64,-67.19,-75.79,-68.45,-76.01,-69.8,-76.22,-70.6,-76.63,-72.21,-76.67,-73.97,-76.63,-75.56,-76.71,-77.24,-76.71,-76.93,-77.1,-75.4,-77.28,-74.28,-77.56,-73.66,-77.91,-74.77,-78.22,-76.5,-78.12,-77.93,-78.38,-77.98,-78.79,-78.02,-79.18,-76.85,-79.51,-76.63,-79.89,-75.36,-80.26,-73.24,-80.42,-71.44,-80.69,-70.01,-81,-68.19,-81.32,-65.7,-81.47,-63.26,-81.75,-61.55,-82.04,-59.69,-82.38,-58.71,-82.85,-58.22,-83.22,-57.01,-82.87,-55.36,-82.57,-53.62,-82.26,-51.54,-82,-49.76,-81.73,-47.27,-81.71,-44.83,-81.85,-42.81,-82.08,-42.16,-81.65,-40.77,-81.36,-38.24,-81.34,-36.27,-81.12,-34.39,-80.91,-32.31,-80.77,-30.1,-80.59,-28.55,-80.34,-29.25,-79.99,-29.69,-79.63,-29.69,-79.26,-31.62,-79.3,-33.68,-79.46,-35.64,-79.46,-35.91,-79.08,-35.78,-78.34,-35.33,-78.12,-33.9,-77.89,-32.21,-77.65,-31,-77.36,-29.78,-77.07,-28.88,-76.67,-27.51,-76.5,-26.16,-76.36,-25.47,-76.28,-23.93,-76.24,-22.46,-76.11,-21.22,-75.91,-20.01,-75.67,-18.91,-75.44,-17.52,-75.13,-16.64,-74.79,-15.7,-74.5,-15.41,-74.11,-16.47,-73.87,-16.11,-73.46,-15.45,-73.15,-14.41,-72.95,-13.31,-72.72,-12.29,-72.4,-11.51,-72.01,-11.02,-71.54,-10.3,-71.27,-9.1,-71.32,-8.61,-71.66,-7.42,-71.7,-7.38,-71.32,-6.87,-70.93,-5.79,-71.03,-5.54,-71.4,-4.34,-71.46,-3.05,-71.29,-1.8,-71.17,-0.66,-71.23,-0.23,-71.64,0.87,-71.3,1.89,-71.13,3.02,-70.99,4.14,-70.85,5.16,-70.62,6.27,-70.46,7.14,-70.25,7.74,-69.89,8.49,-70.15,9.53,-70.01,10.25,-70.48,10.82,-70.83,11.95,-70.64,12.4,-70.25,13.42,-69.97,14.73,-70.03,15.13,-70.4,15.95,-70.03,17.03,-69.91,18.2,-69.87,19.26,-69.89,20.38,-70.01,21.45,-70.07,21.92,-70.4,22.57,-70.7,23.67,-70.52,24.84,-70.48,25.98,-70.48,27.09,-70.46,28.09,-70.32,29.15,-70.21,30.03,-69.93,30.97,-69.76,31.99,-69.66,32.75,-69.38,33.3,-68.84,33.87,-68.5,34.91,-68.66,35.3,-69.01,36.16,-69.25,37.2,-69.17,37.91,-69.52,38.65,-69.78,39.67,-69.54,40.02,-69.11,40.92,-68.93,41.96,-68.6,42.94,-68.46,44.11,-68.27,44.9,-68.05,45.72,-67.82,46.5,-67.6,47.44,-67.72,48.34,-67.37,48.99,-67.09,49.93,-67.11,50.75,-66.88,50.95,-66.52,51.79,-66.25,52.61,-66.05,53.61,-65.9,54.53,-65.82,55.41,-65.88,56.36,-65.97,57.16,-66.25,57.26,-66.68,58.14,-67.01,58.74,-67.29,59.94,-67.41,60.61,-67.68,61.43,-67.95,62.39,-68.01,63.19,-67.82,64.05,-67.41,64.99,-67.62,65.97,-67.74,66.91,-67.86,67.89,-67.93,68.89,-67.93,69.71,-68.97,69.67,-69.23,69.56,-69.68,68.6,-69.93,67.81,-70.31,67.95,-70.7,69.07,-70.68,68.93,-71.07,68.42,-71.44,67.95,-71.85,68.71,-72.17,69.87,-72.26,71.02,-72.09,71.57,-71.7,71.91,-71.32,72.45,-71.01,73.08,-70.72,73.34,-70.36,73.86,-69.87,74.49,-69.78,75.63,-69.74,76.63,-69.62,77.64,-69.46,78.13,-69.07,78.43,-68.7,79.11,-68.33,80.09,-68.07,80.94,-67.88,81.48,-67.54,82.05,-67.37,82.78,-67.21,83.78,-67.31,84.68,-67.21,85.66,-67.09,86.75,-67.15,87.48,-66.88,87.99,-66.21,88.36,-66.48,88.83,-66.95,89.67,-67.15,90.63,-67.23,91.59,-67.11,92.61,-67.19,93.55,-67.21,94.18,-67.11,95.02,-67.17,95.78,-67.39,96.68,-67.25,97.76,-67.25,98.68,-67.11,99.72,-67.25,100.38,-66.92,100.89,-66.58,101.58,-66.31,102.83,-65.56,103.48,-65.7,104.24,-65.97,104.91,-66.33,106.18,-66.93,107.16,-66.95,108.08,-66.95,109.16,-66.84,110.24,-66.7,111.06,-66.43,111.74,-66.13,112.86,-66.09,113.6,-65.88,114.39,-66.07,114.9,-66.39,115.6,-66.7,116.7,-66.66,117.38,-66.92,118.58,-67.17,119.83,-67.27,120.87,-67.19,121.65,-66.88,122.32,-66.56,123.22,-66.48,124.12,-66.62,125.16,-66.72,126.1,-66.56,127,-66.56,127.88,-66.66,128.8,-66.76,129.7,-66.58,130.78,-66.43,131.8,-66.39,132.94,-66.39,133.86,-66.29,134.76,-66.21,135.03,-65.72,135.07,-65.31,135.7,-65.58,135.87,-66.03,136.21,-66.45,136.62,-66.78,137.46,-66.95,138.6,-66.9,139.91,-66.88,140.81,-66.82,142.12,-66.82,143.06,-66.8,144.37,-66.84,145.49,-66.92,146.2,-67.23,146,-67.6,146.65,-67.9,147.72,-68.13,148.84,-68.39,150.13,-68.56,151.48,-68.72,152.5,-68.87,153.64,-68.89,154.28,-68.56,155.17,-68.84,155.93,-69.15,156.81,-69.38,158.03,-69.48,159.18,-69.6,159.67,-69.99,160.81,-70.23,161.57,-70.58,162.69,-70.74,163.84,-70.72,164.92,-70.78,166.11,-70.76,167.31,-70.83,168.43,-70.97,169.46,-71.21,170.5,-71.4,171.21,-71.7,171.09,-72.09,170.56,-72.44,170.11,-72.89,169.76,-73.24,169.29,-73.66,167.98,-73.81,167.39,-74.17,166.09,-74.38,165.64,-74.77,164.96,-75.15,164.23,-75.46,163.82,-75.87,163.57,-76.24,163.47,-76.69,163.49,-77.07,164.06,-77.46,164.27,-77.83,164.74,-78.18,166.6,-78.32,167,-78.75,165.19,-78.91,163.67,-79.12,161.77,-79.16,160.92,-79.73,160.75,-80.2,160.32,-80.57,159.79,-80.95,161.12,-81.28,161.63,-81.69,162.49,-82.06,163.71,-82.4,165.1,-82.71,166.6,-83.02,168.9,-83.34,169.4,-83.83,172.28,-84.04,172.48,-84.12,173.22,-84.41,175.99,-84.16,178.28,-84.47,180,-84.71]]]},{"n":"Turkish Republic of Northern Cyprus","c":"","r":6,"ml":6,"lx":33.69,"ly":35.22,"b":[32.73,35,34.58,35.67],"p":[[[32.73,35.14,32.8,35.15,32.95,35.39,33.67,35.37,34.58,35.67,33.9,35.25,33.97,35.06,33.87,35.09,33.68,35.02,33.53,35.04,33.48,35,33.46,35.1,33.38,35.16,33.19,35.17,32.92,35.09,32.73,35.14]]]},{"n":"Cyprus","c":"CY","r":5,"ml":4.5,"lx":33.08,"ly":34.91,"b":[32.26,34.57,34,35.17],"p":[[[32.73,35.14,32.92,35.09,33.19,35.17,33.38,35.16,33.46,35.1,33.48,35,33.53,35.04,33.68,35.02,33.87,35.09,33.97,35.06,34,34.98,32.98,34.57,32.49,34.7,32.26,35.1,32.73,35.14]]]},{"n":"Morocco","c":"MA","r":3,"ml":2.7,"lx":-7.19,"ly":31.65,"b":[-17.02,21.42,-1.12,35.76],"p":[[[-2.17,35.17,-1.79,34.53,-1.73,33.92,-1.39,32.86,-1.12,32.65,-1.31,32.26,-2.62,32.09,-3.07,31.72,-3.65,31.64,-3.69,30.9,-4.86,30.5,-5.24,30,-6.06,29.73,-7.06,29.58,-8.67,28.84,-8.67,27.66,-8.82,27.66,-8.79,27.12,-9.41,27.09,-9.74,26.86,-10.19,26.86,-10.55,26.99,-11.39,26.88,-11.72,26.1,-12.03,26.03,-12.5,24.77,-13.89,23.69,-14.22,22.31,-14.63,21.86,-14.75,21.5,-17,21.42,-17.02,21.42,-16.97,21.89,-16.59,22.16,-16.26,22.68,-16.33,23.02,-15.98,23.72,-15.43,24.36,-15.09,24.52,-14.82,25.1,-14.8,25.64,-14.44,26.25,-13.77,26.62,-13.14,27.64,-13.12,27.65,-12.62,28.04,-11.69,28.15,-10.9,28.83,-10.4,29.1,-9.56,29.93,-9.81,31.18,-9.43,32.04,-9.3,32.56,-8.66,33.24,-7.65,33.7,-6.91,34.11,-6.24,35.15,-5.93,35.76,-5.19,35.76,-4.59,35.33,-3.64,35.4,-2.6,35.18,-2.17,35.17]]]},{"n":"Egypt","c":"EG","r":2,"ml":1.7,"lx":29.45,"ly":26.19,"b":[24.7,22,36.87,31.59],"p":[[[36.87,22,32.9,22,29.02,22,25,22,25,25.68,25,29.24,24.7,30.04,24.96,30.66,24.8,31.09,25.16,31.57,26.5,31.59,27.46,31.32,28.45,31.03,28.91,30.87,29.68,31.19,30.1,31.47,30.98,31.56,31.69,31.43,31.96,30.93,32.19,31.26,32.99,31.02,33.77,30.97,34.27,31.22,34.82,29.76,34.92,29.5,34.64,29.1,34.43,28.34,34.15,27.82,33.92,27.65,33.59,27.97,33.14,28.42,32.42,29.85,32.32,29.76,32.73,28.71,33.35,27.7,34.1,26.14,34.47,25.6,34.8,25.03,35.69,23.93,35.49,23.75,35.53,23.1,36.69,22.2,36.87,22]]]},{"n":"Libya","c":"LY","r":3,"ml":3,"lx":18.01,"ly":26.64,"b":[9.32,19.58,25.16,33.14],"p":[[[25,22,25,20,23.85,20,23.84,19.58,19.85,21.5,15.86,23.41,14.85,22.86,14.14,22.49,13.58,23.04,12,23.47,11.56,24.1,10.77,24.56,10.3,24.38,9.95,24.94,9.91,25.37,9.32,26.09,9.72,26.51,9.63,27.14,9.76,27.69,9.68,28.14,9.86,28.96,9.81,29.42,9.48,30.31,9.97,30.54,10.06,30.96,9.95,31.38,10.64,31.76,10.94,32.08,11.43,32.37,11.49,33.14,12.66,32.79,13.08,32.88,13.92,32.71,15.25,32.27,15.71,31.38,16.61,31.18,18.02,30.76,19.09,30.27,19.57,30.53,20.05,30.99,19.82,31.75,20.13,32.24,20.85,32.71,21.54,32.84,22.9,32.64,23.24,32.19,23.61,32.19,23.93,32.02,24.92,31.9,25.16,31.57,24.8,31.09,24.96,30.66,24.7,30.04,25,29.24,25,25.68,25,22]]]},{"n":"Ethiopia","c":"ET","r":2,"ml":2,"lx":39.09,"ly":8.03,"b":[32.95,3.42,47.79,14.96],"p":[[[47.79,8,44.96,5,43.66,4.96,42.77,4.25,42.13,4.23,41.86,3.92,41.17,3.92,40.77,4.26,39.85,3.84,39.56,3.42,38.89,3.5,38.67,3.62,38.44,3.59,38.12,3.6,36.86,4.45,36.16,4.45,35.82,4.78,35.82,5.34,35.3,5.51,34.71,6.59,34.25,6.83,34.08,7.23,33.57,7.71,32.95,7.78,33.29,8.35,33.83,8.38,33.97,8.68,33.96,9.58,34.26,10.63,34.73,10.91,34.83,11.32,35.26,12.08,35.86,12.58,36.27,13.56,36.43,14.42,37.59,14.21,37.91,14.96,38.51,14.51,39.1,14.74,39.34,14.53,40.03,14.52,40.9,14.12,41.16,13.77,41.6,13.45,42.01,12.87,42.35,12.54,42,12.1,41.66,11.63,41.74,11.36,41.76,11.05,42.31,11.03,42.55,11.11,42.78,10.93,42.56,10.57,42.93,10.02,43.3,9.54,43.68,9.18,46.95,8,47.79,8]]]},{"n":"Djibouti","c":"DJ","r":5,"ml":4,"lx":42.5,"ly":11.98,"b":[41.66,10.93,43.32,12.7],"p":[[[42.35,12.54,42.78,12.46,43.08,12.7,43.32,12.39,43.29,11.97,42.72,11.74,43.15,11.46,42.78,10.93,42.55,11.11,42.31,11.03,41.76,11.05,41.74,11.36,41.66,11.63,42,12.1,42.35,12.54]]]},{"n":"Somaliland","c":"","r":5,"ml":4.5,"lx":46.73,"ly":9.44,"b":[42.56,8,48.95,11.46],"p":[[[48.95,11.41,48.94,11.39,48.94,10.98,48.94,9.97,48.94,9.45,48.49,8.84,47.79,8,46.95,8,43.68,9.18,43.3,9.54,42.93,10.02,42.56,10.57,42.78,10.93,43.15,11.46,43.47,11.28,43.67,10.86,44.12,10.45,44.61,10.44,45.56,10.7,46.65,10.82,47.53,11.13,48.02,11.19,48.38,11.38,48.95,11.41]]]},{"n":"Uganda","c":"UG","r":3,"ml":3,"lx":32.95,"ly":1.97,"b":[29.58,-1.44,35.04,4.25],"p":[[[33.9,-0.95,31.87,-1.03,30.77,-1.01,30.42,-1.13,29.82,-1.44,29.58,-1.34,29.59,-0.59,29.82,-0.21,29.88,0.6,30.09,1.06,30.47,1.58,30.85,1.85,31.17,2.2,30.77,2.34,30.83,3.51,31.25,3.78,31.88,3.56,32.69,3.79,33.39,3.79,34.01,4.25,34.48,3.56,34.6,3.05,35.04,1.91,34.67,1.18,34.18,0.52,33.89,0.11,33.9,-0.95]]]},{"n":"Rwanda","c":"RW","r":3,"ml":3,"lx":30.1,"ly":-1.9,"b":[29.02,-2.92,30.82,-1.13],"p":[[[30.42,-1.13,30.82,-1.7,30.76,-2.29,30.47,-2.41,29.94,-2.35,29.63,-2.92,29.02,-2.84,29.12,-2.29,29.25,-2.22,29.29,-1.62,29.58,-1.34,29.82,-1.44,30.42,-1.13]]]},{"n":"Bosnia and Herzegovina","c":"BA","r":5,"ml":4.5,"lx":18.07,"ly":44.09,"b":[15.75,42.65,19.6,45.23],"p":[[[18.56,42.65,17.67,43.03,17.3,43.45,16.92,43.67,16.46,44.04,16.24,44.35,15.75,44.82,15.96,45.23,16.32,45,16.53,45.21,17,45.23,17.86,45.07,18.55,45.08,19.01,44.86,19.37,44.86,19.12,44.42,19.6,44.04,19.45,43.57,19.22,43.52,19.03,43.43,18.71,43.2,18.56,42.65]]]},{"n":"North Macedonia","c":"MK","r":6,"ml":5,"lx":21.56,"ly":41.56,"b":[20.46,40.84,22.95,42.32],"p":[[[22.38,42.32,22.88,42,22.95,41.34,22.76,41.3,22.6,41.13,22.06,41.15,21.67,40.93,21.02,40.84,20.61,41.09,20.46,41.52,20.59,41.86,20.72,41.85,20.76,42.05,21.35,42.21,21.58,42.25,21.92,42.3,22.38,42.32]]]},{"n":"Serbia","c":"RS","r":5,"ml":4,"lx":20.79,"ly":44.19,"b":[18.83,42.25,22.99,46.17],"p":[[[18.83,45.91,19.6,46.17,20.22,46.13,20.76,45.73,20.87,45.42,21.48,45.18,21.56,44.77,22.15,44.48,22.46,44.7,22.71,44.58,22.47,44.41,22.66,44.23,22.41,44.01,22.5,43.64,22.99,43.21,22.6,42.9,22.44,42.58,22.55,42.46,22.38,42.32,21.92,42.3,21.58,42.25,21.54,42.32,21.66,42.44,21.78,42.68,21.63,42.68,21.44,42.86,21.27,42.91,21.14,43.07,20.96,43.13,20.81,43.27,20.64,43.22,20.5,42.88,20.26,42.81,20.34,42.9,19.96,43.11,19.63,43.21,19.48,43.35,19.22,43.52,19.45,43.57,19.6,44.04,19.12,44.42,19.37,44.86,19.01,44.86,19.39,45.24,19.07,45.52,18.83,45.91]]]},{"n":"Montenegro","c":"ME","r":6,"ml":5,"lx":19.14,"ly":42.8,"b":[18.45,41.88,20.34,43.52],"p":[[[20.07,42.59,19.8,42.5,19.74,42.69,19.3,42.2,19.37,41.88,19.16,41.96,18.88,42.28,18.45,42.48,18.56,42.65,18.71,43.2,19.03,43.43,19.22,43.52,19.48,43.35,19.63,43.21,19.96,43.11,20.34,42.9,20.26,42.81,20.07,42.59]]]},{"n":"Kosovo","c":"XK","r":6,"ml":5,"lx":20.86,"ly":42.59,"b":[20.07,41.85,21.78,43.27],"p":[[[20.59,41.86,20.52,42.22,20.28,42.32,20.07,42.59,20.26,42.81,20.5,42.88,20.64,43.22,20.81,43.27,20.96,43.13,21.14,43.07,21.27,42.91,21.44,42.86,21.63,42.68,21.78,42.68,21.66,42.44,21.54,42.32,21.58,42.25,21.35,42.21,20.76,42.05,20.72,41.85,20.59,41.86]]]},{"n":"Trinidad and Tobago","c":"TT","r":5,"ml":4.5,"lx":-60.92,"ly":11,"b":[-61.95,10,-60.89,10.89],"p":[[[-61.68,10.76,-61.1,10.89,-60.89,10.86,-60.93,10.11,-61.77,10,-61.95,10.09,-61.66,10.37,-61.68,10.76]]]},{"n":"South Sudan","c":"SS","r":3,"ml":3,"lx":30.39,"ly":7.23,"b":[23.89,3.51,35.3,12.25],"p":[[[30.83,3.51,29.95,4.17,29.72,4.6,29.16,4.39,28.7,4.46,28.43,4.29,27.98,4.41,27.37,5.23,27.21,5.55,26.47,5.95,26.21,6.55,25.8,6.98,25.12,7.5,25.11,7.83,24.57,8.23,23.89,8.62,24.19,8.73,24.54,8.92,24.79,9.81,25.07,10.27,25.79,10.41,25.96,10.14,26.48,9.55,26.75,9.47,27.11,9.64,27.83,9.6,27.97,9.4,28.97,9.4,29,9.6,29.52,9.79,29.62,10.08,30,10.29,30.84,9.71,31.35,9.81,31.85,10.53,32.4,11.08,32.31,11.68,32.07,11.97,32.67,12.02,32.74,12.25,33.21,12.18,33.09,11.44,33.21,10.72,33.72,10.33,33.84,9.98,33.82,9.48,33.96,9.46,33.97,8.68,33.83,8.38,33.29,8.35,32.95,7.78,33.57,7.71,34.08,7.23,34.25,6.83,34.71,6.59,35.3,5.51,34.62,4.85,34.01,4.25,33.39,3.79,32.69,3.79,31.88,3.56,31.25,3.78,30.83,3.51]]]}]} \ No newline at end of file diff --git a/src/maps/data/countries_50m.json b/src/maps/data/countries_50m.json new file mode 100644 index 0000000..9681f4f --- /dev/null +++ b/src/maps/data/countries_50m.json @@ -0,0 +1 @@ +{"f":[{"n":"Zimbabwe","c":"ZW","r":3,"ml":2.5,"lx":29.93,"ly":-18.91,"b":[25.224,-22.402,33.007,-15.643],"p":[[[31.288,-22.402,31.197,-22.345,31.073,-22.308,30.916,-22.291,30.712,-22.298,30.46,-22.329,30.19,-22.291,29.902,-22.184,29.663,-22.146,29.377,-22.193,29.365,-22.194,29.315,-22.158,29.237,-22.079,29.107,-22.066,29.071,-22.047,29.042,-22.018,29.023,-21.981,29.016,-21.94,29.037,-21.811,29.026,-21.797,28.991,-21.781,28.919,-21.766,28.748,-21.708,28.532,-21.651,28.182,-21.589,28.046,-21.573,28.014,-21.554,27.975,-21.507,27.907,-21.359,27.844,-21.262,27.693,-21.111,27.669,-21.064,27.677,-20.945,27.688,-20.848,27.704,-20.766,27.697,-20.69,27.695,-20.595,27.7,-20.531,27.679,-20.503,27.625,-20.484,27.469,-20.475,27.281,-20.479,27.275,-20.382,27.257,-20.232,27.221,-20.146,27.178,-20.101,27.092,-20.054,26.917,-19.99,26.678,-19.893,26.475,-19.749,26.241,-19.569,26.168,-19.538,26.082,-19.37,25.951,-19.082,25.959,-18.986,25.939,-18.939,25.812,-18.797,25.784,-18.724,25.761,-18.649,25.558,-18.442,25.489,-18.351,25.437,-18.235,25.384,-18.142,25.34,-18.104,25.282,-18.041,25.242,-17.969,25.224,-17.915,25.239,-17.843,25.259,-17.794,25.452,-17.845,25.557,-17.85,25.64,-17.824,25.742,-17.858,25.863,-17.952,25.996,-17.97,26.14,-17.912,26.333,-17.929,26.578,-18.023,26.78,-18.042,27.021,-17.958,27.236,-17.728,27.438,-17.512,27.637,-17.262,27.757,-17.06,27.932,-16.896,28.164,-16.77,28.4,-16.663,28.761,-16.532,28.833,-16.424,28.857,-16.306,28.857,-16.142,28.876,-16.036,28.913,-15.988,28.973,-15.95,29.051,-15.901,29.288,-15.776,29.487,-15.697,29.73,-15.645,29.995,-15.644,30.251,-15.643,30.396,-15.643,30.398,-15.801,30.409,-15.978,30.438,-15.995,30.63,-15.999,30.939,-16.012,31.236,-16.024,31.426,-16.152,31.49,-16.18,31.688,-16.214,31.94,-16.429,32.243,-16.449,32.452,-16.516,32.636,-16.589,32.742,-16.678,32.81,-16.698,32.903,-16.704,32.948,-16.712,32.938,-16.776,32.876,-16.884,32.884,-17.038,32.969,-17.252,32.981,-17.437,32.955,-17.765,32.956,-18.083,32.965,-18.196,32.979,-18.271,32.996,-18.313,32.993,-18.36,32.942,-18.493,32.902,-18.633,32.9,-18.689,32.885,-18.729,32.854,-18.764,32.722,-18.828,32.699,-18.868,32.7,-18.941,32.717,-19.002,32.766,-19.024,32.826,-19.059,32.85,-19.104,32.85,-19.152,32.831,-19.241,32.778,-19.389,32.831,-19.558,32.89,-19.668,32.973,-19.795,33.007,-19.874,33.005,-19.93,32.993,-19.985,32.87,-20.217,32.781,-20.362,32.673,-20.516,32.529,-20.613,32.492,-20.66,32.478,-20.713,32.483,-20.829,32.476,-20.95,32.354,-21.137,32.43,-21.297,32.412,-21.312,32.371,-21.335,32.195,-21.515,32.016,-21.698,31.886,-21.832,31.738,-21.983,31.571,-22.154,31.429,-22.299,31.288,-22.402]]]},{"n":"Zambia","c":"ZM","r":3,"ml":3,"lx":26.4,"ly":-14.66,"b":[21.979,-18.042,33.662,-8.194],"p":[[[30.396,-15.643,30.251,-15.643,29.995,-15.644,29.73,-15.645,29.487,-15.697,29.288,-15.776,29.051,-15.901,28.973,-15.95,28.913,-15.988,28.876,-16.036,28.857,-16.142,28.857,-16.306,28.833,-16.424,28.761,-16.532,28.4,-16.663,28.164,-16.77,27.932,-16.896,27.757,-17.06,27.637,-17.262,27.438,-17.512,27.236,-17.728,27.021,-17.958,26.78,-18.042,26.578,-18.023,26.333,-17.929,26.14,-17.912,25.996,-17.97,25.863,-17.952,25.742,-17.858,25.64,-17.824,25.557,-17.85,25.452,-17.845,25.259,-17.794,25.092,-17.634,25.002,-17.569,24.932,-17.543,24.733,-17.518,24.275,-17.481,24.227,-17.49,24.037,-17.521,23.799,-17.56,23.595,-17.599,23.381,-17.641,23.182,-17.474,22.956,-17.286,22.722,-17.075,22.546,-16.91,22.459,-16.815,22.305,-16.69,22.194,-16.628,22.151,-16.597,22.04,-16.263,21.98,-15.956,21.98,-15.724,21.98,-15.403,21.98,-15.082,21.979,-14.761,21.979,-14.441,21.979,-14.12,21.979,-13.799,21.979,-13.478,21.979,-13.157,21.979,-13.001,22.21,-13.001,22.471,-13.001,22.744,-13.001,23.042,-13.001,23.339,-13.001,23.636,-13.001,23.843,-13.001,23.897,-12.998,23.963,-12.988,23.968,-12.957,23.882,-12.799,23.887,-12.743,23.909,-12.636,23.945,-12.544,23.991,-12.422,23.996,-12.351,23.959,-12.118,23.962,-11.988,23.973,-11.853,23.984,-11.725,23.971,-11.636,23.987,-11.587,24.015,-11.518,24.029,-11.439,24.047,-11.405,24.041,-11.374,24.026,-11.316,24.01,-11.185,23.988,-11.003,23.967,-10.872,24.003,-10.879,24.078,-10.892,24.115,-10.956,24.137,-11.026,24.187,-11.03,24.32,-11.072,24.366,-11.13,24.396,-11.255,24.378,-11.319,24.335,-11.371,24.378,-11.417,24.467,-11.448,24.519,-11.438,24.668,-11.353,24.728,-11.338,24.806,-11.321,24.877,-11.299,25.076,-11.26,25.185,-11.243,25.246,-11.212,25.289,-11.212,25.319,-11.237,25.292,-11.325,25.283,-11.405,25.321,-11.554,25.349,-11.623,25.413,-11.674,25.46,-11.7,25.512,-11.753,25.619,-11.744,25.855,-11.82,25.927,-11.855,26.026,-11.89,26.096,-11.903,26.34,-11.93,26.43,-11.948,26.596,-11.972,26.73,-11.976,26.824,-11.965,26.89,-11.944,26.931,-11.919,26.95,-11.899,26.977,-11.825,27.027,-11.664,27.046,-11.616,27.095,-11.594,27.159,-11.579,27.196,-11.605,27.238,-11.783,27.424,-11.945,27.487,-12.08,27.533,-12.195,27.574,-12.227,27.644,-12.267,27.757,-12.281,27.857,-12.285,28.069,-12.368,28.237,-12.435,28.358,-12.482,28.413,-12.518,28.451,-12.577,28.474,-12.623,28.511,-12.742,28.551,-12.836,28.615,-12.854,28.673,-12.861,28.73,-12.925,28.773,-12.982,28.859,-13.119,28.922,-13.215,28.942,-13.307,29.014,-13.369,29.112,-13.395,29.202,-13.398,29.254,-13.371,29.382,-13.323,29.481,-13.268,29.554,-13.249,29.597,-13.261,29.63,-13.299,29.648,-13.373,29.652,-13.414,29.723,-13.454,29.775,-13.438,29.795,-13.393,29.796,-13.37,29.796,-13.167,29.796,-12.992,29.796,-12.827,29.796,-12.626,29.796,-12.451,29.795,-12.306,29.795,-12.155,29.75,-12.164,29.692,-12.198,29.56,-12.202,29.508,-12.228,29.492,-12.267,29.502,-12.318,29.505,-12.386,29.486,-12.418,29.428,-12.431,29.344,-12.405,29.191,-12.37,29.064,-12.349,28.973,-12.258,28.85,-12.121,28.769,-12.051,28.575,-11.908,28.542,-11.879,28.483,-11.812,28.432,-11.698,28.407,-11.623,28.383,-11.567,28.357,-11.483,28.404,-11.354,28.47,-11.11,28.518,-10.933,28.544,-10.802,28.639,-10.669,28.646,-10.55,28.607,-10.397,28.617,-10.313,28.624,-10.099,28.629,-9.919,28.63,-9.831,28.604,-9.679,28.541,-9.51,28.4,-9.275,28.401,-9.225,28.484,-9.169,28.617,-9.072,28.681,-9.015,28.759,-8.933,28.794,-8.891,28.87,-8.786,28.918,-8.701,28.934,-8.59,28.898,-8.485,28.972,-8.465,29.216,-8.428,29.484,-8.387,29.766,-8.344,30.051,-8.3,30.328,-8.258,30.578,-8.22,30.751,-8.194,30.777,-8.266,30.831,-8.386,30.892,-8.474,30.968,-8.551,31.033,-8.598,31.076,-8.612,31.351,-8.607,31.449,-8.654,31.535,-8.713,31.556,-8.805,31.613,-8.863,31.674,-8.909,31.7,-8.914,31.745,-8.903,31.818,-8.902,31.886,-8.922,31.919,-8.942,31.922,-9.019,31.943,-9.054,32.035,-9.067,32.13,-9.073,32.221,-9.126,32.319,-9.135,32.433,-9.156,32.487,-9.213,32.608,-9.271,32.757,-9.322,32.863,-9.381,32.92,-9.407,32.923,-9.434,32.951,-9.484,32.98,-9.52,32.982,-9.574,32.996,-9.623,33.038,-9.635,33.072,-9.638,33.104,-9.603,33.148,-9.604,33.196,-9.626,33.213,-9.683,33.25,-9.76,33.31,-9.812,33.351,-9.862,33.337,-9.954,33.312,-10.038,33.394,-10.121,33.5,-10.2,33.529,-10.235,33.538,-10.352,33.554,-10.391,33.626,-10.489,33.662,-10.553,33.659,-10.591,33.465,-10.783,33.403,-10.802,33.345,-10.813,33.293,-10.852,33.261,-10.893,33.273,-10.915,33.293,-10.981,33.339,-11.085,33.38,-11.158,33.346,-11.249,33.268,-11.404,33.233,-11.418,33.226,-11.535,33.25,-11.578,33.288,-11.611,33.304,-11.691,33.305,-11.8,33.301,-11.888,33.252,-12.113,33.34,-12.308,33.37,-12.33,33.491,-12.331,33.512,-12.348,33.483,-12.403,33.431,-12.46,33.398,-12.49,33.243,-12.557,33.022,-12.63,32.975,-12.701,32.946,-12.804,32.971,-12.865,33,-12.9,32.99,-12.989,32.971,-13.084,32.978,-13.159,32.968,-13.225,32.939,-13.257,32.9,-13.357,32.852,-13.457,32.814,-13.503,32.758,-13.55,32.67,-13.59,32.672,-13.61,32.772,-13.657,32.797,-13.688,32.807,-13.71,32.785,-13.731,32.765,-13.761,32.811,-13.792,32.867,-13.817,32.92,-13.884,32.968,-13.977,32.981,-14.009,32.992,-14.022,33.009,-14.024,33.042,-14.01,33.104,-13.959,33.148,-13.941,33.202,-14.013,32.987,-14.085,32.875,-14.122,32.553,-14.23,32.273,-14.323,32.2,-14.341,32.054,-14.387,31.982,-14.414,31.729,-14.496,31.623,-14.537,31.538,-14.577,31.329,-14.638,31.131,-14.695,30.915,-14.753,30.673,-14.819,30.538,-14.867,30.446,-14.908,30.232,-14.99,30.222,-15.011,30.225,-15.067,30.252,-15.183,30.306,-15.289,30.351,-15.35,30.38,-15.506,30.396,-15.643]]]},{"n":"Yemen","c":"YE","r":3,"ml":3,"lx":45.87,"ly":15.33,"b":[42.549,12.319,54.511,18.996],"p":[[[53.086,16.648,52.581,16.47,52.448,16.391,52.328,16.294,52.237,16.171,52.174,15.957,52.222,15.761,52.217,15.656,52.087,15.586,51.966,15.536,51.831,15.459,51.749,15.44,51.682,15.379,51.604,15.337,51.322,15.226,51.015,15.141,50.527,15.038,50.339,14.927,50.167,14.851,49.906,14.828,49.549,14.722,49.35,14.638,49.103,14.5,49.048,14.456,49.005,14.355,48.929,14.267,48.78,14.124,48.668,14.05,48.594,14.046,48.449,14.006,48.278,13.998,47.99,14.048,47.916,14.013,47.855,13.957,47.633,13.858,47.408,13.662,47.243,13.609,46.976,13.547,46.789,13.466,46.663,13.433,46.502,13.416,46.203,13.424,45.92,13.394,45.657,13.339,45.534,13.233,45.394,13.067,45.164,12.998,45.11,12.939,45.039,12.816,44.89,12.784,44.755,12.764,44.618,12.817,44.358,12.669,44.26,12.645,44.112,12.639,44.006,12.608,43.93,12.617,43.835,12.674,43.634,12.744,43.488,12.699,43.475,12.839,43.232,13.267,43.283,13.64,43.282,13.693,43.234,13.859,43.089,14.011,43.093,14.204,43.045,14.342,43.006,14.483,43.019,14.521,43.021,14.555,42.947,14.773,42.922,14.817,42.913,14.863,42.937,14.898,42.936,14.939,42.897,15.006,42.856,15.133,42.658,15.233,42.698,15.326,42.736,15.294,42.788,15.266,42.799,15.326,42.8,15.372,42.717,15.655,42.84,16.032,42.799,16.372,42.986,16.509,43.034,16.55,43.061,16.587,43.105,16.664,43.165,16.689,43.186,16.771,43.184,16.812,43.146,16.847,43.117,16.942,43.126,17.062,43.136,17.113,43.156,17.205,43.221,17.239,43.237,17.266,43.186,17.325,43.191,17.359,43.302,17.457,43.346,17.486,43.418,17.516,43.474,17.516,43.539,17.499,43.597,17.471,43.653,17.422,43.713,17.366,43.804,17.344,43.866,17.35,43.917,17.325,43.96,17.338,44.008,17.367,44.086,17.366,44.156,17.399,44.355,17.414,44.546,17.404,44.747,17.432,44.946,17.43,45.148,17.427,45.193,17.423,45.237,17.406,45.407,17.32,45.535,17.302,45.794,17.278,46.071,17.253,46.31,17.231,46.513,17.252,46.682,17.269,46.728,17.266,46.779,17.212,46.88,17.079,46.976,16.953,47.144,16.947,47.251,16.994,47.37,17.06,47.442,17.112,47.525,17.316,47.58,17.448,47.704,17.597,47.808,17.721,47.946,17.886,48.022,17.977,48.172,18.157,48.316,18.227,48.593,18.362,48.865,18.495,49.042,18.582,49.192,18.621,49.445,18.655,49.742,18.695,50.039,18.735,50.355,18.778,50.708,18.825,50.95,18.858,51.258,18.899,51.515,18.934,51.743,18.965,51.978,18.996,52.022,18.896,52.066,18.796,52.11,18.696,52.155,18.597,52.199,18.497,52.243,18.397,52.288,18.297,52.332,18.197,52.376,18.097,52.42,17.997,52.465,17.897,52.509,17.798,52.553,17.698,52.597,17.598,52.642,17.498,52.686,17.398,52.729,17.3,52.801,17.268,52.843,17.176,52.904,17.044,52.964,16.912,53.025,16.78,53.086,16.648]],[[42.59,15.303,42.559,15.281,42.549,15.32,42.57,15.407,42.602,15.433,42.625,15.368,42.61,15.332,42.59,15.303]],[[42.756,13.704,42.69,13.674,42.735,13.753,42.781,13.769,42.794,13.766,42.756,13.704]],[[42.787,13.971,42.774,13.95,42.756,13.955,42.694,14.008,42.762,14.067,42.798,14.012,42.787,13.971]],[[53.763,12.637,53.825,12.625,53.919,12.659,54.187,12.664,54.511,12.553,54.45,12.523,54.414,12.483,54.271,12.447,54.129,12.361,53.719,12.319,53.598,12.342,53.499,12.425,53.316,12.533,53.388,12.602,53.404,12.633,53.431,12.664,53.535,12.716,53.638,12.707,53.763,12.637]]]},{"n":"Vietnam","c":"VN","r":2,"ml":2,"lx":105.39,"ly":21.72,"b":[102.127,8.583,109.445,23.345],"p":[[[104.064,10.391,104.083,10.341,104.076,10.225,104.037,10.111,104.048,10.061,104.018,10.029,103.952,10.243,103.868,10.335,103.85,10.371,103.898,10.369,103.986,10.427,104.028,10.428,104.064,10.391]],[[107.973,21.508,107.926,21.499,107.809,21.497,107.707,21.406,107.637,21.368,107.527,21.336,107.41,21.285,107.376,21.194,107.373,21.128,107.354,21.055,107.165,20.949,107.112,20.96,107.075,20.999,107.019,20.991,106.981,20.971,106.936,20.974,106.886,20.95,106.821,20.958,106.76,20.991,106.725,21,106.683,21,106.675,20.96,106.737,20.806,106.753,20.735,106.551,20.527,106.573,20.392,106.518,20.289,106.396,20.206,106.166,19.992,106.062,19.987,105.984,19.939,105.814,19.587,105.812,19.467,105.785,19.379,105.791,19.294,105.716,19.128,105.639,19.057,105.622,18.966,105.732,18.779,105.744,18.746,105.808,18.646,105.839,18.574,105.888,18.502,106.066,18.316,106.145,18.259,106.24,18.221,106.412,18.053,106.499,17.946,106.459,17.874,106.479,17.72,106.356,17.765,106.371,17.747,106.517,17.663,106.736,17.367,106.926,17.221,107.12,17.056,107.18,16.898,107.355,16.794,107.549,16.643,107.541,16.609,107.593,16.568,107.724,16.488,107.803,16.403,107.834,16.322,107.882,16.31,107.936,16.329,107.991,16.337,108.029,16.331,108.088,16.243,108.17,16.164,108.209,16.091,108.241,16.101,108.267,16.09,108.274,16.029,108.286,15.989,108.395,15.872,108.447,15.763,108.578,15.585,108.674,15.484,108.743,15.427,108.821,15.378,108.898,15.181,108.94,15.001,109.022,14.803,109.085,14.716,109.087,14.553,109.137,14.384,109.191,14.27,109.207,14.154,109.224,14.097,109.245,14.053,109.303,13.856,109.288,13.765,109.247,13.855,109.252,13.591,109.288,13.451,109.272,13.279,109.31,13.219,109.377,13.025,109.424,12.956,109.42,12.719,109.445,12.6,109.381,12.671,109.336,12.752,109.274,12.709,109.219,12.646,109.305,12.391,109.207,12.415,109.216,12.073,109.256,11.993,109.259,11.955,109.247,11.909,109.22,11.959,109.215,12.01,109.199,11.999,109.2,11.972,109.167,11.912,109.158,11.837,109.193,11.773,109.199,11.725,109.173,11.665,109.133,11.601,109.04,11.593,109.018,11.468,108.987,11.336,108.821,11.315,108.7,11.199,108.551,11.156,108.419,11.041,108.272,10.934,108.176,10.92,108.095,10.897,108.001,10.72,107.845,10.7,107.564,10.555,107.47,10.486,107.384,10.459,107.262,10.398,107.235,10.42,107.194,10.472,107.088,10.498,107.036,10.556,107.021,10.631,107.007,10.661,106.984,10.618,106.966,10.441,106.947,10.4,106.902,10.383,106.813,10.433,106.727,10.536,106.606,10.465,106.643,10.456,106.698,10.462,106.741,10.444,106.778,10.376,106.776,10.339,106.757,10.296,106.644,10.289,106.492,10.304,106.464,10.298,106.602,10.232,106.729,10.193,106.785,10.151,106.785,10.116,106.714,10.06,106.659,9.991,106.658,9.949,106.657,9.901,106.596,9.86,106.557,9.868,106.449,9.94,106.136,10.222,106.184,10.142,106.507,9.821,106.564,9.716,106.572,9.641,106.539,9.604,106.484,9.559,106.378,9.556,106.204,9.675,105.926,9.962,105.831,10.001,106.113,9.674,106.159,9.594,106.206,9.502,106.193,9.448,106.168,9.397,105.501,9.093,105.401,8.962,105.322,8.801,105.191,8.711,105.114,8.629,104.892,8.583,104.77,8.598,104.896,8.747,104.819,8.802,104.815,9.185,104.845,9.606,104.903,9.816,104.987,9.869,105.093,9.901,105.095,9.945,105.084,9.996,105.028,10.067,104.966,10.101,104.873,10.115,104.802,10.203,104.748,10.199,104.663,10.17,104.613,10.208,104.594,10.267,104.516,10.34,104.426,10.411,104.467,10.422,104.514,10.463,104.564,10.516,104.69,10.523,104.815,10.521,104.851,10.534,104.901,10.59,104.984,10.662,105.046,10.702,105.061,10.734,105.036,10.809,105.022,10.887,105.046,10.911,105.159,10.898,105.284,10.861,105.315,10.845,105.387,10.94,105.406,10.952,105.453,10.951,105.577,10.969,105.698,10.994,105.755,10.99,105.811,10.926,105.853,10.864,105.875,10.858,105.938,10.885,105.99,10.852,106.099,10.797,106.164,10.795,106.132,10.922,106.168,11.012,106.161,11.037,106.1,11.079,105.892,11.245,105.856,11.294,105.861,11.372,105.854,11.487,105.835,11.559,105.838,11.601,105.851,11.635,105.89,11.648,105.927,11.653,105.956,11.682,106.006,11.758,106.103,11.751,106.239,11.708,106.34,11.682,106.399,11.687,106.413,11.698,106.411,11.738,106.418,11.912,106.414,11.948,106.5,11.966,106.631,11.969,106.7,11.979,106.765,12.052,106.931,12.077,107.051,12.176,107.159,12.277,107.212,12.304,107.28,12.322,107.33,12.319,107.393,12.26,107.446,12.296,107.506,12.365,107.538,12.432,107.555,12.54,107.544,12.706,107.512,12.836,107.482,12.933,107.475,13.03,107.546,13.225,107.605,13.438,107.594,13.522,107.529,13.654,107.462,13.816,107.389,13.993,107.362,14.019,107.343,14.069,107.331,14.127,107.36,14.308,107.364,14.369,107.448,14.451,107.493,14.546,107.535,14.65,107.519,14.705,107.514,14.817,107.525,14.872,107.505,14.916,107.48,14.98,107.496,15.021,107.555,15.057,107.59,15.118,107.634,15.19,107.653,15.255,107.622,15.31,107.564,15.392,107.46,15.466,107.339,15.56,107.279,15.619,107.233,15.678,107.19,15.747,107.166,15.802,107.189,15.839,107.361,15.922,107.392,15.952,107.41,15.998,107.396,16.043,107.35,16.067,107.296,16.084,107.217,16.136,107.07,16.28,107.002,16.312,106.931,16.353,106.893,16.397,106.851,16.516,106.832,16.526,106.792,16.49,106.74,16.453,106.696,16.459,106.656,16.493,106.638,16.538,106.594,16.6,106.546,16.651,106.534,16.821,106.526,16.877,106.502,16.954,106.465,16.982,106.426,17.003,106.333,17.144,106.27,17.217,106.006,17.415,105.974,17.447,105.903,17.529,105.779,17.644,105.691,17.738,105.627,17.834,105.598,17.918,105.588,17.984,105.519,18.077,105.458,18.154,105.4,18.179,105.333,18.19,105.273,18.235,105.163,18.339,105.115,18.405,105.086,18.45,105.087,18.496,105.113,18.573,105.145,18.617,105.146,18.651,105.115,18.679,104.993,18.728,104.717,18.803,104.613,18.861,104.518,18.934,104.446,18.984,104.109,19.196,104.006,19.231,103.918,19.269,103.892,19.305,103.896,19.34,103.932,19.366,104.028,19.42,104.063,19.483,104.052,19.564,104.013,19.646,104.032,19.675,104.063,19.678,104.127,19.681,104.26,19.685,104.546,19.611,104.588,19.619,104.743,19.755,104.802,19.836,104.815,19.904,104.846,19.947,104.928,20.018,104.929,20.083,104.889,20.169,104.848,20.202,104.813,20.217,104.699,20.205,104.677,20.225,104.662,20.289,104.656,20.329,104.619,20.375,104.496,20.414,104.392,20.425,104.368,20.441,104.408,20.486,104.479,20.53,104.533,20.555,104.575,20.6,104.583,20.647,104.53,20.688,104.461,20.734,104.35,20.821,104.195,20.914,104.101,20.946,104.052,20.941,103.882,20.861,103.791,20.81,103.714,20.717,103.635,20.697,103.555,20.738,103.464,20.78,103.211,20.841,103.104,20.892,102.884,21.203,102.851,21.266,102.872,21.338,102.888,21.44,102.91,21.506,102.949,21.57,102.959,21.626,102.95,21.681,102.918,21.713,102.876,21.722,102.845,21.735,102.816,21.807,102.798,21.798,102.771,21.71,102.739,21.678,102.695,21.662,102.662,21.676,102.641,21.711,102.631,21.771,102.61,21.852,102.583,21.904,102.488,21.958,102.443,22.027,102.301,22.178,102.183,22.284,102.127,22.379,102.176,22.415,102.237,22.466,102.302,22.546,102.376,22.647,102.406,22.708,102.428,22.733,102.471,22.751,102.517,22.741,102.599,22.7,102.721,22.648,102.83,22.587,102.874,22.525,102.935,22.466,102.982,22.448,103.005,22.453,103.076,22.498,103.136,22.542,103.138,22.593,103.193,22.639,103.266,22.714,103.301,22.764,103.327,22.77,103.356,22.755,103.471,22.597,103.493,22.588,103.525,22.612,103.571,22.734,103.62,22.782,103.637,22.77,103.915,22.538,103.942,22.54,103.971,22.55,103.991,22.586,104.013,22.666,104.054,22.752,104.143,22.8,104.213,22.809,104.238,22.769,104.298,22.712,104.372,22.704,104.527,22.804,104.578,22.82,104.632,22.818,104.687,22.822,104.74,22.86,104.796,22.911,104.815,23.011,104.827,23.1,104.865,23.136,104.91,23.161,104.996,23.194,105.189,23.281,105.239,23.322,105.275,23.345,105.35,23.308,105.44,23.235,105.495,23.181,105.531,23.122,105.548,23.073,105.691,23.03,105.782,22.969,105.843,22.923,105.903,22.925,105.962,22.937,106.001,22.975,106.068,22.976,106.148,22.97,106.184,22.955,106.249,22.869,106.279,22.857,106.338,22.863,106.451,22.894,106.542,22.908,106.624,22.874,106.78,22.779,106.736,22.711,106.702,22.638,106.633,22.586,106.582,22.573,106.55,22.501,106.536,22.395,106.554,22.342,106.593,22.325,106.637,22.289,106.654,22.241,106.66,22.136,106.658,22.018,106.664,21.979,106.698,21.986,106.729,22,106.794,21.982,106.875,21.951,106.925,21.92,106.971,21.924,107.006,21.893,107.02,21.835,107.062,21.794,107.179,21.717,107.272,21.711,107.351,21.609,107.433,21.642,107.471,21.598,107.641,21.614,107.759,21.655,107.802,21.645,107.908,21.56,107.973,21.508]],[[106.617,8.683,106.589,8.681,106.568,8.701,106.659,8.766,106.65,8.723,106.653,8.701,106.617,8.683]],[[107.168,10.397,107.084,10.337,107.078,10.388,107.151,10.42,107.177,10.446,107.195,10.446,107.168,10.397]],[[106.866,20.816,106.854,20.796,106.803,20.844,106.769,20.864,106.795,20.928,106.855,20.858,106.866,20.816]],[[107.603,21.217,107.459,21.092,107.404,21.094,107.453,21.235,107.476,21.269,107.563,21.22,107.603,21.217]],[[107.521,20.927,107.466,20.901,107.399,20.903,107.479,20.952,107.519,21.013,107.551,21.034,107.551,20.981,107.521,20.927]],[[107.031,20.747,106.99,20.743,106.911,20.824,106.953,20.867,107.044,20.837,107.064,20.817,107.064,20.8,107.042,20.761,107.031,20.747]]]},{"n":"Venezuela","c":"VE","r":3,"ml":2.5,"lx":-64.6,"ly":7.18,"b":[-73.366,0.688,-59.829,12.178],"p":[[[-60.821,9.138,-60.941,9.106,-60.939,9.132,-60.907,9.179,-60.845,9.192,-60.821,9.208,-60.782,9.218,-60.759,9.216,-60.736,9.203,-60.79,9.177,-60.821,9.138]],[[-63.849,11.131,-63.817,11,-63.827,10.976,-63.918,10.888,-63.994,10.881,-64.055,10.884,-64.101,10.901,-64.161,10.959,-64.219,10.942,-64.362,10.962,-64.402,10.982,-64.349,11.052,-64.25,11.08,-64.214,11.086,-64.185,11.043,-64.113,11.006,-64.028,11.002,-64.007,11.068,-63.893,11.167,-63.849,11.131]],[[-65.213,10.906,-65.266,10.884,-65.365,10.906,-65.415,10.938,-65.383,10.974,-65.302,10.974,-65.227,10.93,-65.213,10.906]],[[-60.998,8.867,-61.06,8.847,-61.069,8.947,-61.05,8.974,-60.945,9.055,-60.916,9.07,-60.895,9.053,-60.9,9.032,-60.849,8.996,-60.861,8.95,-60.916,8.899,-60.998,8.867]],[[-60.018,8.549,-59.832,8.306,-59.829,8.279,-59.849,8.249,-59.965,8.192,-59.991,8.162,-60.032,8.054,-60.178,7.994,-60.279,7.919,-60.347,7.854,-60.381,7.828,-60.514,7.813,-60.556,7.772,-60.61,7.648,-60.649,7.597,-60.719,7.536,-60.719,7.499,-60.624,7.363,-60.607,7.321,-60.636,7.257,-60.633,7.211,-60.583,7.156,-60.523,7.144,-60.465,7.167,-60.392,7.165,-60.345,7.15,-60.325,7.134,-60.322,7.092,-60.352,7.003,-60.395,6.945,-60.586,6.857,-60.671,6.806,-60.718,6.768,-60.821,6.788,-60.873,6.787,-60.914,6.758,-60.938,6.733,-61.007,6.727,-61.105,6.711,-61.146,6.695,-61.177,6.651,-61.204,6.588,-61.182,6.513,-61.151,6.447,-61.152,6.385,-61.129,6.214,-61.159,6.174,-61.225,6.129,-61.303,6.05,-61.391,5.939,-61.377,5.907,-61.167,5.674,-60.954,5.437,-60.742,5.202,-60.712,5.192,-60.672,5.164,-60.635,5.082,-60.604,4.995,-60.604,4.949,-60.628,4.893,-60.679,4.827,-60.742,4.774,-60.833,4.729,-60.906,4.687,-60.966,4.575,-61.003,4.535,-61.036,4.519,-61.102,4.505,-61.209,4.508,-61.28,4.517,-61.368,4.433,-61.479,4.402,-61.554,4.288,-61.821,4.197,-62.082,4.126,-62.153,4.098,-62.411,4.157,-62.473,4.139,-62.544,4.084,-62.61,4.042,-62.665,4.04,-62.712,4.018,-62.74,3.94,-62.765,3.673,-62.857,3.593,-62.969,3.594,-63.045,3.686,-63.136,3.756,-63.295,3.922,-63.339,3.944,-63.38,3.943,-63.527,3.894,-63.597,3.915,-63.653,3.941,-63.747,3.933,-63.915,3.931,-64.021,3.929,-64.073,3.974,-64.122,4.067,-64.154,4.1,-64.192,4.127,-64.256,4.14,-64.526,4.14,-64.576,4.14,-64.614,4.158,-64.666,4.237,-64.722,4.274,-64.789,4.276,-64.818,4.232,-64.703,4.089,-64.669,4.012,-64.568,3.9,-64.275,3.663,-64.221,3.587,-64.227,3.491,-64.229,3.344,-64.219,3.205,-64.144,3.005,-64.038,2.802,-64.009,2.672,-64.029,2.576,-64.049,2.525,-64.047,2.502,-64.025,2.482,-63.924,2.452,-63.713,2.434,-63.585,2.434,-63.389,2.412,-63.375,2.34,-63.394,2.223,-63.433,2.156,-63.464,2.136,-63.57,2.121,-63.682,2.048,-63.844,1.977,-63.937,1.967,-63.976,1.953,-64.008,1.932,-64.035,1.904,-64.067,1.771,-64.115,1.619,-64.205,1.529,-64.304,1.455,-64.405,1.447,-64.486,1.453,-64.526,1.431,-64.584,1.37,-64.667,1.294,-64.732,1.253,-64.818,1.257,-64.91,1.22,-65.027,1.158,-65.104,1.108,-65.17,1.022,-65.264,0.932,-65.361,0.869,-65.407,0.79,-65.473,0.691,-65.556,0.688,-65.563,0.748,-65.523,0.843,-65.566,0.926,-65.645,0.97,-65.681,0.983,-65.718,0.978,-65.811,0.937,-65.926,0.863,-65.996,0.81,-66.06,0.785,-66.191,0.763,-66.302,0.752,-66.347,0.767,-66.429,0.822,-66.619,0.992,-66.876,1.223,-66.896,1.29,-66.884,1.358,-66.931,1.458,-66.958,1.564,-66.982,1.601,-66.988,1.68,-67.044,1.823,-67.09,1.94,-67.131,2,-67.114,2.051,-67.131,2.101,-67.165,2.143,-67.215,2.275,-67.198,2.333,-67.211,2.39,-67.253,2.429,-67.312,2.472,-67.392,2.56,-67.486,2.644,-67.535,2.677,-67.568,2.69,-67.597,2.769,-67.619,2.794,-67.667,2.8,-67.766,2.833,-67.859,2.794,-67.861,2.855,-67.835,2.893,-67.515,3.187,-67.354,3.323,-67.336,3.343,-67.322,3.374,-67.311,3.416,-67.348,3.464,-67.499,3.691,-67.551,3.734,-67.603,3.769,-67.662,3.864,-67.732,4.087,-67.783,4.198,-67.799,4.284,-67.795,4.381,-67.814,4.455,-67.855,4.507,-67.855,4.665,-67.814,4.931,-67.804,5.133,-67.825,5.27,-67.788,5.375,-67.695,5.448,-67.642,5.559,-67.631,5.709,-67.575,5.833,-67.474,5.93,-67.439,6.026,-67.472,6.12,-67.482,6.18,-67.568,6.242,-67.727,6.285,-67.859,6.29,-67.939,6.242,-68.143,6.198,-68.472,6.157,-68.736,6.157,-68.937,6.198,-69.09,6.184,-69.195,6.115,-69.268,6.1,-69.311,6.138,-69.357,6.148,-69.427,6.124,-69.439,6.135,-69.595,6.321,-69.739,6.494,-69.904,6.7,-70.095,6.938,-70.129,6.954,-70.188,6.952,-70.266,6.948,-70.387,6.973,-70.471,7.007,-70.536,7.041,-70.655,7.083,-70.737,7.09,-70.811,7.078,-71.013,6.994,-71.129,6.987,-71.218,6.985,-71.457,7.026,-71.621,7.033,-71.811,7.006,-71.893,6.99,-72.007,7.033,-72.084,7.097,-72.157,7.25,-72.208,7.37,-72.296,7.395,-72.395,7.415,-72.443,7.455,-72.472,7.524,-72.479,7.613,-72.469,7.758,-72.46,7.81,-72.446,7.966,-72.392,8.048,-72.358,8.087,-72.364,8.153,-72.39,8.287,-72.417,8.382,-72.526,8.49,-72.665,8.628,-72.726,8.848,-72.796,9.109,-72.852,9.135,-72.904,9.122,-72.96,9.135,-73.009,9.24,-73.058,9.26,-73.137,9.223,-73.193,9.194,-73.337,9.168,-73.366,9.194,-73.356,9.227,-73.296,9.322,-73.224,9.444,-73.141,9.555,-73.064,9.668,-73.007,9.789,-72.967,10.03,-72.94,10.196,-72.869,10.491,-72.739,10.727,-72.69,10.836,-72.572,10.977,-72.518,11.054,-72.446,11.114,-72.248,11.196,-72.012,11.602,-71.958,11.666,-71.719,11.727,-71.536,11.774,-71.4,11.824,-71.356,11.85,-71.32,11.862,-71.349,11.815,-71.415,11.755,-71.488,11.719,-71.869,11.627,-71.908,11.608,-71.957,11.57,-71.957,11.483,-71.947,11.414,-71.835,11.19,-71.791,11.135,-71.642,11.014,-71.676,10.997,-71.731,10.995,-71.69,10.835,-71.598,10.726,-71.594,10.657,-71.665,10.444,-71.794,10.316,-71.885,10.167,-71.956,10.108,-72.113,9.816,-71.993,9.642,-71.976,9.553,-71.873,9.428,-71.806,9.386,-71.761,9.336,-71.781,9.25,-71.74,9.134,-71.687,9.073,-71.62,9.048,-71.537,9.048,-71.298,9.126,-71.241,9.16,-71.205,9.222,-71.086,9.348,-71.078,9.511,-71.053,9.706,-71.082,9.833,-71.207,10.015,-71.262,10.144,-71.387,10.264,-71.463,10.469,-71.494,10.533,-71.518,10.622,-71.545,10.779,-71.461,10.836,-71.47,10.964,-71.264,11,-70.821,11.208,-70.546,11.261,-70.233,11.373,-70.16,11.428,-70.097,11.52,-70.049,11.53,-69.885,11.444,-69.805,11.474,-69.773,11.541,-69.817,11.672,-69.911,11.672,-70.193,11.625,-70.22,11.681,-70.22,11.73,-70.287,11.886,-70.245,12.004,-70.203,12.098,-70.122,12.137,-70.004,12.178,-69.914,12.115,-69.86,12.054,-69.831,11.996,-69.811,11.837,-69.762,11.676,-69.712,11.564,-69.632,11.48,-69.57,11.485,-69.526,11.5,-69.233,11.518,-69.055,11.461,-68.828,11.432,-68.616,11.309,-68.399,11.161,-68.343,11.053,-68.325,10.949,-68.272,10.88,-68.325,10.809,-68.296,10.689,-68.234,10.569,-68.14,10.493,-67.872,10.472,-67.581,10.524,-67.133,10.57,-66.989,10.611,-66.247,10.632,-66.106,10.575,-66.092,10.517,-66.09,10.473,-65.852,10.258,-65.656,10.228,-65.489,10.159,-65.317,10.122,-65.129,10.07,-65.023,10.077,-64.944,10.095,-64.85,10.098,-64.188,10.458,-63.834,10.449,-63.779,10.472,-63.732,10.503,-63.863,10.558,-64.158,10.579,-64.248,10.543,-64.298,10.635,-64.202,10.633,-63.873,10.664,-63.497,10.643,-63.19,10.709,-63.035,10.72,-62.947,10.707,-62.702,10.75,-62.242,10.7,-61.879,10.741,-61.921,10.681,-62.04,10.645,-62.233,10.634,-62.38,10.547,-62.694,10.563,-62.914,10.531,-62.843,10.507,-62.843,10.418,-62.813,10.4,-62.781,10.399,-62.706,10.333,-62.686,10.29,-62.662,10.199,-62.695,10.1,-62.741,10.056,-62.651,10.071,-62.6,10.117,-62.608,10.163,-62.6,10.217,-62.55,10.2,-62.515,10.176,-62.401,9.918,-62.32,9.783,-62.3,9.788,-62.281,9.793,-62.257,9.819,-62.221,9.883,-62.19,9.842,-62.172,9.827,-62.153,9.822,-62.17,9.879,-62.147,9.953,-62.155,9.979,-62.12,9.985,-62.077,9.975,-62.017,9.955,-61.909,9.87,-61.837,9.782,-61.831,9.733,-61.805,9.706,-61.759,9.677,-61.736,9.631,-61.732,9.702,-61.759,9.754,-61.766,9.814,-61.625,9.816,-61.589,9.895,-61.512,9.848,-61.309,9.633,-61.234,9.598,-61.013,9.556,-60.874,9.453,-60.792,9.361,-60.841,9.264,-60.971,9.215,-61.023,9.155,-61.053,9.095,-61.054,9.035,-61.093,8.966,-61.099,8.941,-61.122,8.843,-61.176,8.725,-61.247,8.6,-61.619,8.597,-61.527,8.546,-61.443,8.509,-61.304,8.41,-61.194,8.488,-61.036,8.493,-60.865,8.579,-60.801,8.592,-60.481,8.547,-60.404,8.61,-60.34,8.629,-60.167,8.617,-60.018,8.549]]]},{"n":"Vatican City","c":"VA","r":6,"ml":5,"lx":12.45,"ly":41.9,"b":[12.428,41.898,12.439,41.906],"p":[[[12.439,41.898,12.431,41.898,12.428,41.901,12.431,41.905,12.438,41.906,12.439,41.898]]]},{"n":"Vanuatu","c":"VU","r":4,"ml":4,"lx":166.91,"ly":-15.37,"b":[166.526,-20.242,169.896,-13.709],"p":[[[166.746,-14.827,166.81,-15.157,166.885,-15.157,166.923,-15.139,166.968,-15.062,166.987,-14.94,167.027,-14.923,167.076,-14.936,167.054,-14.974,167.069,-15.072,167.106,-15.126,167.132,-15.135,167.182,-15.39,167.201,-15.443,167.2,-15.486,167.094,-15.581,166.937,-15.578,166.826,-15.635,166.758,-15.631,166.759,-15.567,166.699,-15.516,166.631,-15.406,166.648,-15.212,166.527,-14.85,166.526,-14.76,166.567,-14.642,166.608,-14.637,166.663,-14.735,166.746,-14.827]],[[167.413,-16.096,167.459,-16.118,167.484,-16.118,167.499,-16.166,167.642,-16.263,167.681,-16.261,167.714,-16.314,167.776,-16.341,167.793,-16.395,167.837,-16.45,167.76,-16.516,167.611,-16.499,167.526,-16.574,167.449,-16.555,167.436,-16.515,167.447,-16.502,167.401,-16.401,167.38,-16.246,167.349,-16.154,167.316,-16.116,167.246,-16.15,167.218,-16.155,167.151,-16.08,167.183,-15.929,167.2,-15.885,167.254,-15.877,167.336,-15.917,167.413,-16.096]],[[168.447,-16.779,168.477,-16.794,168.46,-16.835,168.323,-16.788,168.212,-16.806,168.181,-16.804,168.149,-16.766,168.124,-16.69,168.135,-16.637,168.182,-16.6,168.199,-16.594,168.234,-16.64,168.265,-16.671,168.296,-16.684,168.366,-16.759,168.447,-16.779]],[[168.297,-16.337,168.182,-16.347,168.022,-16.316,167.957,-16.272,167.929,-16.229,167.985,-16.196,168.064,-16.181,168.164,-16.082,168.198,-16.12,168.235,-16.231,168.276,-16.265,168.298,-16.299,168.297,-16.337]],[[168.446,-17.542,168.545,-17.685,168.585,-17.696,168.525,-17.798,168.399,-17.807,168.252,-17.781,168.306,-17.746,168.278,-17.706,168.233,-17.698,168.182,-17.717,168.158,-17.711,168.191,-17.645,168.273,-17.552,168.297,-17.545,168.32,-17.544,168.341,-17.552,168.446,-17.542]],[[167.911,-15.436,167.844,-15.482,167.72,-15.477,167.674,-15.452,167.826,-15.312,168.003,-15.283,167.911,-15.436]],[[168.213,-15.97,168.196,-15.972,168.179,-15.926,168.123,-15.681,168.16,-15.462,168.183,-15.508,168.268,-15.892,168.256,-15.955,168.213,-15.97]],[[168.189,-15.329,168.172,-15.391,168.13,-15.319,168.104,-15.017,168.115,-14.989,168.136,-14.986,168.187,-15.197,168.189,-15.329]],[[167.219,-15.724,167.201,-15.75,167.095,-15.685,167.119,-15.623,167.234,-15.645,167.219,-15.724]],[[167.585,-14.261,167.543,-14.312,167.43,-14.295,167.404,-14.282,167.411,-14.197,167.439,-14.168,167.506,-14.142,167.599,-14.184,167.585,-14.261]],[[167.489,-13.907,167.474,-13.917,167.451,-13.909,167.392,-13.788,167.407,-13.748,167.481,-13.709,167.547,-13.777,167.554,-13.814,167.553,-13.846,167.543,-13.873,167.499,-13.885,167.489,-13.907]],[[169.896,-20.187,169.861,-20.242,169.807,-20.241,169.738,-20.202,169.751,-20.153,169.829,-20.145,169.852,-20.148,169.896,-20.187]],[[169.491,-19.54,169.438,-19.649,169.347,-19.624,169.262,-19.545,169.217,-19.476,169.247,-19.345,169.291,-19.322,169.337,-19.329,169.36,-19.458,169.491,-19.54]],[[169.334,-18.94,169.288,-18.989,169.248,-18.983,168.987,-18.871,168.998,-18.825,168.987,-18.708,169.016,-18.644,169.088,-18.617,169.144,-18.631,169.178,-18.725,169.256,-18.763,169.201,-18.796,169.296,-18.867,169.334,-18.94]]]},{"n":"Uzbekistan","c":"UZ","r":3,"ml":3,"lx":64.01,"ly":41.69,"b":[55.976,37.172,73.137,45.555],"p":[[[70.947,42.249,70.979,42.267,71.036,42.285,71.13,42.25,71.213,42.206,71.232,42.186,71.229,42.163,71.032,42.078,70.91,42.038,70.857,42.031,70.842,42.02,70.803,41.923,70.728,41.905,70.631,41.875,70.563,41.831,70.455,41.725,70.181,41.571,70.177,41.54,70.201,41.514,70.29,41.497,70.408,41.45,70.471,41.413,70.646,41.46,70.689,41.45,70.734,41.401,70.782,41.263,70.86,41.225,70.963,41.196,71.026,41.187,71.111,41.153,71.223,41.14,71.299,41.152,71.393,41.123,71.408,41.136,71.421,41.342,71.5,41.307,71.546,41.308,71.586,41.333,71.606,41.367,71.62,41.435,71.602,41.503,71.638,41.534,71.665,41.541,71.685,41.533,71.697,41.516,71.701,41.454,71.758,41.428,71.792,41.413,71.826,41.361,71.858,41.311,71.879,41.195,71.958,41.187,72.052,41.165,72.115,41.187,72.164,41.174,72.181,41.118,72.181,41.067,72.187,41.026,72.213,41.014,72.295,41.04,72.364,41.043,72.427,41.019,72.506,40.982,72.62,40.884,72.658,40.87,72.831,40.862,72.867,40.842,72.926,40.842,72.99,40.86,73.132,40.829,73.137,40.811,73.113,40.786,72.774,40.65,72.749,40.609,72.68,40.556,72.604,40.525,72.567,40.524,72.402,40.578,72.383,40.565,72.369,40.543,72.37,40.52,72.406,40.463,72.389,40.427,72.358,40.402,72.254,40.424,72.235,40.439,72.233,40.454,72.193,40.454,72.131,40.439,72.013,40.341,71.971,40.29,71.956,40.259,71.903,40.241,71.845,40.234,71.773,40.188,71.692,40.152,71.667,40.179,71.651,40.208,71.63,40.217,71.58,40.21,71.52,40.209,71.457,40.242,71.376,40.275,71.305,40.287,71.095,40.271,70.991,40.255,70.958,40.239,70.899,40.235,70.653,40.201,70.603,40.214,70.566,40.267,70.534,40.325,70.47,40.345,70.398,40.361,70.372,40.384,70.37,40.412,70.377,40.439,70.383,40.454,70.549,40.563,70.698,40.661,70.712,40.669,70.726,40.688,70.751,40.722,70.751,40.74,70.639,40.779,70.635,40.797,70.657,40.815,70.657,40.84,70.578,40.911,70.442,41.023,70.402,41.035,70.373,41.028,70.319,40.919,70.292,40.892,70.136,40.82,70.006,40.771,69.773,40.684,69.713,40.657,69.671,40.662,69.628,40.679,69.498,40.767,69.414,40.797,69.357,40.767,69.309,40.724,69.314,40.635,69.26,40.588,69.206,40.567,69.304,40.327,69.294,40.297,69.22,40.288,69.275,40.198,69.228,40.188,69.11,40.209,68.952,40.223,68.653,40.183,68.631,40.167,68.622,40.147,68.64,40.129,68.785,40.127,68.927,40.136,68.966,40.12,68.972,40.09,68.956,40.071,68.908,40.068,68.805,40.05,68.793,40.031,68.789,40.013,68.824,39.961,68.864,39.927,68.869,39.907,68.852,39.891,68.832,39.884,68.798,39.909,68.778,39.904,68.768,39.882,68.758,39.856,68.735,39.836,68.687,39.846,68.639,39.839,68.61,39.743,68.586,39.635,68.507,39.563,68.463,39.537,68.399,39.529,68.303,39.538,68.245,39.548,68.077,39.564,67.909,39.594,67.719,39.621,67.542,39.558,67.492,39.519,67.46,39.482,67.426,39.466,67.35,39.242,67.358,39.217,67.4,39.197,67.617,39.15,67.648,39.131,67.667,39.109,67.677,39.008,67.694,38.995,67.769,38.982,67.876,38.983,67.96,38.993,68.044,38.984,68.104,38.962,68.133,38.928,68.149,38.891,68.048,38.669,68.056,38.589,68.087,38.474,68.144,38.383,68.251,38.295,68.333,38.238,68.35,38.211,68.354,38.17,68.341,38.117,68.294,38.033,68.237,37.96,68.174,37.928,68.088,37.835,68.011,37.721,67.864,37.571,67.814,37.487,67.798,37.245,67.759,37.172,67.753,37.2,67.7,37.227,67.607,37.223,67.546,37.236,67.517,37.267,67.442,37.258,67.32,37.21,67.196,37.235,67.069,37.335,66.828,37.371,66.522,37.348,66.511,37.459,66.511,37.599,66.526,37.786,66.629,37.932,66.626,37.96,66.606,37.987,66.575,38.011,66.39,38.051,66.335,38.072,66.264,38.118,66.173,38.167,66.095,38.2,65.971,38.244,65.857,38.269,65.79,38.25,65.729,38.226,65.671,38.226,65.613,38.239,65.4,38.349,65.077,38.539,64.821,38.672,64.66,38.736,64.622,38.756,64.532,38.816,64.31,38.977,64.163,38.954,63.953,39.058,63.764,39.161,63.721,39.188,63.506,39.377,63.292,39.5,63.058,39.633,62.907,39.717,62.651,39.858,62.525,39.944,62.483,39.976,62.442,40.036,62.375,40.332,62.298,40.467,62.188,40.541,62.095,40.683,62.018,40.894,61.954,41.031,61.903,41.094,61.8,41.163,61.645,41.24,61.497,41.276,61.444,41.275,61.417,41.265,61.388,41.252,61.329,41.195,61.242,41.189,61.179,41.191,61.12,41.211,60.933,41.229,60.867,41.249,60.755,41.246,60.514,41.216,60.455,41.222,60.2,41.349,60.09,41.399,60.067,41.427,60.069,41.476,60.106,41.545,60.138,41.594,60.124,41.645,60.076,41.701,60.076,41.76,60.109,41.793,60.176,41.782,60.201,41.803,60.192,41.834,60.156,41.857,60.107,41.907,59.963,41.954,59.942,41.974,59.949,41.995,59.974,42.019,59.979,42.068,59.982,42.132,60.001,42.165,60.006,42.191,59.985,42.212,59.937,42.236,59.858,42.295,59.763,42.302,59.451,42.3,59.354,42.323,59.277,42.356,59.199,42.482,59.16,42.511,59.123,42.524,59.036,42.528,58.931,42.54,58.877,42.561,58.73,42.676,58.589,42.778,58.532,42.682,58.477,42.663,58.353,42.672,58.26,42.688,58.206,42.666,58.152,42.628,58.162,42.603,58.204,42.576,58.289,42.527,58.418,42.407,58.477,42.34,58.486,42.317,58.474,42.299,58.457,42.292,58.431,42.292,58.397,42.292,58.377,42.312,58.371,42.347,58.327,42.399,58.283,42.429,58.234,42.448,58.166,42.462,58.075,42.487,58.029,42.488,57.983,42.459,57.946,42.42,57.923,42.335,57.856,42.231,57.814,42.19,57.686,42.165,57.382,42.156,57.291,42.124,57.229,42.084,57.114,41.957,57.034,41.915,56.964,41.857,56.985,41.669,57.018,41.451,57.077,41.39,57.114,41.372,57.119,41.35,57.095,41.331,57.064,41.307,57.018,41.263,56.966,41.265,56.861,41.276,56.774,41.288,56.48,41.301,56.242,41.311,55.977,41.322,55.977,41.552,55.977,41.781,55.977,42.011,55.977,42.24,55.977,42.47,55.977,42.7,55.977,42.929,55.977,43.159,55.976,43.388,55.976,43.618,55.976,43.847,55.976,44.077,55.976,44.306,55.976,44.536,55.976,44.765,55.976,44.995,56.1,45.023,56.258,45.059,56.409,45.094,56.589,45.135,56.792,45.181,56.965,45.221,57.172,45.268,57.329,45.304,57.477,45.337,57.667,45.377,57.961,45.44,58.125,45.474,58.291,45.509,58.449,45.543,58.555,45.555,58.669,45.508,58.807,45.442,58.945,45.376,59.083,45.31,59.221,45.244,59.36,45.179,59.498,45.113,59.636,45.047,59.774,44.981,59.912,44.916,60.05,44.85,60.188,44.784,60.327,44.718,60.465,44.652,60.603,44.587,60.741,44.521,60.879,44.455,61.008,44.394,61.065,44.348,61.097,44.248,61.161,44.169,61.271,44.082,61.385,43.994,61.526,43.877,61.624,43.796,61.723,43.714,61.888,43.577,61.99,43.492,62.072,43.489,62.238,43.51,62.459,43.537,62.634,43.558,62.846,43.584,63.048,43.608,63.207,43.628,63.445,43.613,63.68,43.599,63.848,43.588,64.013,43.578,64.209,43.566,64.318,43.559,64.443,43.551,64.496,43.572,64.604,43.613,64.706,43.653,64.812,43.694,64.905,43.715,65.003,43.649,65.085,43.574,65.171,43.494,65.271,43.418,65.367,43.372,65.496,43.311,65.571,43.205,65.67,43.065,65.736,42.972,65.803,42.877,65.901,42.915,66.006,42.955,66.1,42.991,66.089,42.873,66.079,42.767,66.063,42.605,66.05,42.473,66.016,42.315,66.013,42.194,66.011,42.089,66.01,42.005,66.193,42.001,66.329,41.998,66.499,41.995,66.515,41.889,66.538,41.741,66.573,41.607,66.602,41.494,66.645,41.349,66.669,41.271,66.71,41.179,66.75,41.157,66.814,41.142,67.039,41.153,67.225,41.162,67.372,41.17,67.528,41.177,67.735,41.187,67.805,41.164,67.866,41.18,67.936,41.197,67.991,41.13,68.02,41.096,68.059,41.061,68.113,41.029,68.09,40.96,68.057,40.861,68.048,40.809,68.112,40.754,68.16,40.722,68.292,40.656,68.415,40.619,68.496,40.609,68.573,40.623,68.601,40.66,68.594,40.711,68.557,40.765,68.559,40.829,68.584,40.876,68.663,40.962,68.737,41.042,68.851,41.124,68.987,41.205,69.043,41.264,69.065,41.367,69.154,41.425,69.249,41.46,69.368,41.491,69.401,41.542,69.565,41.629,69.664,41.672,69.788,41.697,69.96,41.754,70.096,41.821,70.226,41.946,70.329,42.028,70.416,42.079,70.489,42.08,70.54,42.039,70.584,42.036,70.613,42.055,70.663,42.107,70.715,42.169,70.765,42.194,70.86,42.207,70.947,42.249],[70.653,40.937,70.649,40.961,70.618,41.002,70.572,41.025,70.55,41.015,70.569,40.982,70.623,40.934,70.653,40.937]],[[71.206,39.893,71.156,39.883,71.064,39.885,71.012,39.895,71.044,39.976,71.045,39.993,71.041,39.995,71.014,40.006,70.974,40.039,70.961,40.088,70.976,40.133,71.005,40.152,71.024,40.149,71.08,40.08,71.13,40.06,71.229,40.048,71.179,39.98,71.216,39.907,71.206,39.893]],[[71.78,39.95,71.753,39.907,71.706,39.917,71.669,39.946,71.681,39.969,71.737,39.981,71.765,39.993,71.79,39.995,71.78,39.95]]]},{"n":"Uruguay","c":"UY","r":4,"ml":3,"lx":-55.97,"ly":-32.96,"b":[-58.438,-34.933,-53.126,-30.101],"p":[[[-53.371,-33.742,-53.42,-33.779,-53.472,-33.849,-53.535,-34.017,-53.743,-34.25,-53.785,-34.38,-54.01,-34.517,-54.169,-34.671,-54.272,-34.667,-54.365,-34.733,-54.902,-34.933,-55.095,-34.895,-55.238,-34.896,-55.371,-34.808,-55.673,-34.776,-55.863,-34.811,-56.118,-34.908,-56.195,-34.906,-56.25,-34.901,-56.388,-34.861,-56.463,-34.775,-56.855,-34.677,-57.171,-34.452,-57.543,-34.448,-57.829,-34.477,-57.873,-34.448,-57.902,-34.39,-57.961,-34.307,-58.207,-34.109,-58.4,-33.912,-58.438,-33.719,-58.411,-33.509,-58.353,-33.26,-58.364,-33.182,-58.292,-33.138,-58.222,-33.129,-58.154,-33.065,-58.093,-32.967,-58.082,-32.894,-58.13,-32.757,-58.162,-32.567,-58.201,-32.472,-58.123,-32.322,-58.12,-32.249,-58.165,-32.185,-58.177,-32.119,-58.156,-32.052,-58.16,-31.987,-58.189,-31.924,-58.167,-31.873,-58.096,-31.832,-58.042,-31.769,-58.007,-31.685,-57.989,-31.621,-57.988,-31.576,-58.01,-31.534,-58.054,-31.495,-58.033,-31.417,-57.948,-31.299,-57.893,-31.195,-57.868,-31.104,-57.87,-31.031,-57.898,-30.975,-57.886,-30.937,-57.834,-30.917,-57.811,-30.859,-57.819,-30.712,-57.873,-30.591,-57.831,-30.495,-57.713,-30.384,-57.651,-30.295,-57.646,-30.227,-57.609,-30.188,-57.552,-30.261,-57.384,-30.281,-57.214,-30.283,-57.187,-30.265,-57.121,-30.144,-57.033,-30.11,-56.937,-30.101,-56.833,-30.107,-56.722,-30.187,-56.407,-30.447,-56.176,-30.628,-56.106,-30.714,-56.045,-30.778,-55.999,-30.837,-56.018,-30.992,-56.016,-31.06,-56.005,-31.079,-55.952,-31.081,-55.874,-31.07,-55.808,-31.037,-55.756,-30.987,-55.706,-30.947,-55.665,-30.925,-55.65,-30.892,-55.627,-30.858,-55.603,-30.851,-55.557,-30.876,-55.45,-30.964,-55.366,-31.046,-55.346,-31.093,-55.313,-31.142,-55.279,-31.184,-55.255,-31.226,-55.174,-31.28,-55.091,-31.314,-55.036,-31.279,-54.896,-31.391,-54.588,-31.485,-54.531,-31.542,-54.478,-31.623,-54.37,-31.745,-54.221,-31.855,-54.1,-31.902,-53.985,-31.928,-53.921,-31.952,-53.877,-31.995,-53.806,-32.04,-53.762,-32.057,-53.747,-32.097,-53.701,-32.186,-53.654,-32.299,-53.602,-32.403,-53.489,-32.503,-53.363,-32.581,-53.231,-32.625,-53.157,-32.68,-53.126,-32.737,-53.214,-32.821,-53.31,-32.927,-53.395,-33.01,-53.483,-33.069,-53.512,-33.109,-53.531,-33.171,-53.53,-33.5,-53.538,-33.623,-53.531,-33.655,-53.519,-33.677,-53.464,-33.71,-53.398,-33.737,-53.371,-33.742]]]},{"n":"Federated States of Micronesia","c":"FM","r":6,"ml":5,"lx":158.23,"ly":6.89,"b":[138.062,5.277,162.993,9.593],"p":[[[162.983,5.326,162.993,5.277,162.93,5.301,162.921,5.318,162.958,5.335,162.983,5.326]],[[138.143,9.501,138.067,9.419,138.062,9.446,138.085,9.495,138.117,9.55,138.147,9.584,138.186,9.593,138.214,9.547,138.183,9.507,138.143,9.501]],[[151.648,7.346,151.639,7.333,151.578,7.338,151.57,7.346,151.575,7.351,151.604,7.357,151.608,7.375,151.593,7.379,151.606,7.389,151.629,7.39,151.643,7.379,151.65,7.363,151.648,7.346]],[[151.881,7.432,151.864,7.427,151.856,7.432,151.86,7.457,151.865,7.466,151.882,7.467,151.911,7.46,151.913,7.454,151.881,7.432]],[[158.315,6.814,158.257,6.791,158.183,6.801,158.161,6.883,158.128,6.905,158.135,6.945,158.186,6.978,158.295,6.951,158.335,6.893,158.309,6.855,158.315,6.814]]]},{"n":"Marshall Islands","c":"MH","r":6,"ml":5,"lx":171.19,"ly":7.08,"b":[166.845,5.8,171.757,11.169],"p":[[[169.635,5.83,169.615,5.8,169.591,5.802,169.612,5.824,169.627,5.856,169.651,5.945,169.7,5.977,169.735,6.014,169.726,5.976,169.673,5.935,169.635,5.83]],[[168.83,7.309,168.815,7.294,168.719,7.303,168.675,7.322,168.679,7.336,168.755,7.322,168.83,7.309]],[[171.577,7.048,171.615,7.027,171.688,7.028,171.757,6.973,171.73,6.977,171.693,7,171.659,7.01,171.614,7.007,171.593,7.016,171.577,7.048]],[[171.102,7.138,171.227,7.087,171.394,7.111,171.367,7.096,171.305,7.081,171.263,7.069,171.235,7.069,171.202,7.074,171.096,7.109,171.036,7.156,171.05,7.172,171.102,7.138]],[[166.89,11.153,166.864,11.146,166.845,11.153,166.859,11.166,166.888,11.169,166.899,11.165,166.89,11.153]]]},{"n":"Northern Mariana Islands","c":"MP","r":6,"ml":5,"lx":145.73,"ly":15.19,"b":[145.152,14.111,145.835,18.807],"p":[[[145.708,18.763,145.678,18.725,145.653,18.753,145.646,18.807,145.69,18.802,145.707,18.79,145.708,18.763]],[[145.712,16.339,145.69,16.332,145.658,16.336,145.636,16.352,145.631,16.378,145.696,16.38,145.72,16.36,145.712,16.339]],[[145.265,14.158,145.215,14.111,145.18,14.121,145.157,14.137,145.152,14.164,145.232,14.189,145.265,14.18,145.265,14.158]],[[145.778,18.079,145.729,18.057,145.789,18.155,145.807,18.173,145.835,18.137,145.778,18.079]],[[145.752,15.133,145.749,15.107,145.698,15.114,145.684,15.125,145.713,15.215,145.786,15.257,145.822,15.265,145.789,15.223,145.782,15.175,145.752,15.133]],[[145.662,14.971,145.621,14.92,145.592,14.999,145.587,15.031,145.625,15.06,145.647,15.059,145.662,14.971]]]},{"n":"United States Virgin Islands","c":"VI","r":6,"ml":5,"lx":-64.78,"ly":17.75,"b":[-65.024,17.702,-64.58,18.385],"p":[[[-64.845,18.33,-64.92,18.321,-65.024,18.368,-64.942,18.385,-64.889,18.374,-64.845,18.33]],[[-64.66,18.354,-64.726,18.328,-64.771,18.332,-64.788,18.341,-64.752,18.372,-64.66,18.354]],[[-64.766,17.794,-64.682,17.75,-64.58,17.75,-64.686,17.706,-64.889,17.702,-64.885,17.772,-64.766,17.794]]]},{"n":"Guam","c":"GU","r":6,"ml":3,"lx":144.7,"ly":13.35,"b":[144.649,13.258,144.941,13.622],"p":[[[144.742,13.259,144.7,13.258,144.663,13.291,144.65,13.313,144.649,13.429,144.79,13.527,144.837,13.622,144.875,13.615,144.91,13.599,144.941,13.57,144.78,13.411,144.742,13.259]]]},{"n":"American Samoa","c":"AS","r":4,"ml":4,"lx":-170.75,"ly":-14.33,"b":[-170.821,-14.36,-170.568,-14.257],"p":[[[-170.726,-14.351,-170.769,-14.36,-170.821,-14.312,-170.721,-14.276,-170.689,-14.257,-170.568,-14.267,-170.64,-14.282,-170.726,-14.351]]]},{"n":"Puerto Rico","c":"PR","r":5,"ml":3,"lx":-66.48,"ly":18.23,"b":[-67.937,17.947,-65.295,18.522],"p":[[[-66.129,18.445,-66.098,18.425,-66.068,18.428,-66.093,18.469,-66.07,18.469,-65.879,18.444,-65.756,18.402,-65.629,18.381,-65.621,18.242,-65.718,18.187,-65.782,18.129,-65.834,18.057,-65.971,17.974,-66.135,17.949,-66.245,17.947,-66.286,17.95,-66.326,17.964,-66.409,17.951,-66.511,17.987,-66.598,17.978,-66.772,17.987,-66.838,17.955,-66.9,17.948,-66.961,17.954,-67.013,17.968,-67.142,17.967,-67.197,17.994,-67.174,18.153,-67.172,18.224,-67.204,18.283,-67.239,18.321,-67.264,18.365,-67.213,18.394,-67.172,18.436,-67.159,18.499,-67.113,18.515,-67.06,18.522,-66.813,18.493,-66.189,18.476,-66.153,18.471,-66.129,18.445]],[[-65.426,18.106,-65.504,18.1,-65.555,18.108,-65.572,18.137,-65.477,18.165,-65.366,18.161,-65.303,18.144,-65.295,18.133,-65.426,18.106]],[[-67.872,18.06,-67.882,18.059,-67.891,18.06,-67.895,18.063,-67.902,18.072,-67.93,18.087,-67.937,18.101,-67.931,18.115,-67.919,18.121,-67.861,18.123,-67.855,18.121,-67.844,18.111,-67.843,18.104,-67.849,18.097,-67.859,18.08,-67.863,18.075,-67.867,18.071,-67.868,18.063,-67.872,18.06]]]},{"n":"United States of America","c":"US","r":2,"ml":1.7,"lx":-97.48,"ly":39.54,"b":[-178.195,18.964,179.78,71.408],"p":[[[-132.747,56.526,-132.758,56.511,-132.885,56.512,-132.931,56.524,-132.948,56.567,-132.936,56.607,-132.907,56.637,-132.871,56.696,-132.843,56.795,-132.656,56.685,-132.599,56.636,-132.568,56.576,-132.634,56.553,-132.714,56.543,-132.747,56.526]],[[-132.78,56.247,-132.831,56.244,-132.891,56.259,-133.035,56.341,-133.038,56.365,-133.017,56.392,-132.935,56.442,-132.902,56.454,-132.706,56.448,-132.643,56.435,-132.629,56.412,-132.632,56.388,-132.653,56.364,-132.658,56.339,-132.647,56.313,-132.669,56.287,-132.78,56.247]],[[-134.313,58.229,-134.32,58.204,-134.456,58.207,-134.594,58.243,-134.662,58.291,-134.648,58.312,-134.52,58.333,-134.399,58.287,-134.313,58.229]],[[-145.119,60.337,-145.15,60.313,-145.238,60.321,-145.284,60.337,-145.128,60.401,-145.102,60.388,-145.119,60.337]],[[-144.566,59.818,-144.614,59.813,-144.542,59.878,-144.445,59.951,-144.354,59.996,-144.236,60.015,-144.249,59.982,-144.403,59.921,-144.566,59.818]],[[-148.022,60.065,-148.074,60.035,-148.272,60.053,-148.231,60.114,-148.08,60.152,-147.914,60.092,-148.022,60.065]],[[-152.021,60.362,-152.069,60.358,-152.004,60.407,-151.96,60.504,-151.899,60.49,-151.887,60.473,-151.987,60.374,-152.021,60.362]],[[-160.329,55.338,-160.343,55.259,-160.481,55.309,-160.517,55.334,-160.493,55.352,-160.362,55.357,-160.329,55.338]],[[-159.362,54.972,-159.394,54.967,-159.421,54.978,-159.458,55.035,-159.462,55.059,-159.39,55.041,-159.363,55,-159.362,54.972]],[[-159.515,55.152,-159.52,55.072,-159.535,55.06,-159.561,55.081,-159.618,55.057,-159.648,55.075,-159.635,55.102,-159.64,55.124,-159.598,55.126,-159.588,55.165,-159.595,55.182,-159.575,55.218,-159.545,55.226,-159.515,55.152]],[[-166.21,53.723,-166.224,53.72,-166.249,53.745,-166.251,53.768,-166.234,53.784,-166.188,53.822,-166.155,53.836,-166.114,53.843,-166.103,53.833,-166.139,53.787,-166.184,53.757,-166.21,53.723]],[[-176.009,51.812,-176.093,51.79,-176.204,51.835,-176.194,51.886,-176.072,51.843,-176.009,51.812]],[[-166.11,66.227,-166.149,66.222,-166.146,66.237,-166.033,66.278,-165.822,66.328,-165.83,66.317,-165.942,66.278,-166.11,66.227]],[[-171.463,63.64,-171.448,63.616,-171.343,63.62,-171.197,63.609,-171.035,63.585,-170.875,63.594,-170.673,63.669,-170.552,63.688,-170.43,63.699,-170.299,63.681,-170.171,63.641,-170.122,63.618,-170.082,63.577,-170.056,63.527,-170.017,63.492,-169.777,63.448,-169.624,63.431,-169.587,63.407,-169.555,63.373,-169.428,63.348,-169.295,63.358,-169.221,63.349,-168.996,63.347,-168.716,63.311,-168.761,63.214,-168.852,63.171,-169.109,63.185,-169.365,63.171,-169.471,63.121,-169.559,63.058,-169.571,62.997,-169.623,62.969,-169.676,62.956,-169.72,62.99,-169.778,63.094,-169.819,63.122,-169.863,63.14,-169.988,63.173,-170.115,63.194,-170.19,63.196,-170.243,63.232,-170.273,63.284,-170.324,63.311,-170.424,63.349,-170.527,63.379,-170.848,63.444,-170.954,63.453,-171.061,63.446,-171.176,63.416,-171.287,63.372,-171.401,63.339,-171.519,63.332,-171.632,63.351,-171.738,63.394,-171.791,63.425,-171.819,63.477,-171.818,63.53,-171.804,63.581,-171.746,63.703,-171.646,63.727,-171.463,63.64]],[[-166.135,60.384,-166.044,60.334,-165.995,60.331,-165.841,60.346,-165.784,60.336,-165.73,60.314,-165.696,60.282,-165.689,60.224,-165.714,60.173,-165.707,60.101,-165.712,60.069,-165.631,60.028,-165.605,59.973,-165.592,59.913,-165.769,59.893,-165.947,59.89,-166.1,59.85,-166.131,59.82,-166.107,59.775,-166.149,59.764,-166.188,59.774,-166.262,59.815,-166.343,59.834,-166.628,59.865,-166.985,59.984,-167.139,60.009,-167.295,60.096,-167.436,60.207,-167.344,60.224,-167.252,60.234,-166.836,60.217,-166.784,60.296,-166.731,60.316,-166.599,60.339,-166.476,60.383,-166.42,60.382,-166.364,60.365,-166.247,60.391,-166.185,60.397,-166.135,60.384]],[[-152.898,57.824,-152.891,57.769,-152.85,57.776,-152.696,57.832,-152.616,57.849,-152.512,57.851,-152.429,57.826,-152.412,57.806,-152.419,57.782,-152.485,57.734,-152.483,57.703,-152.411,57.646,-152.237,57.615,-152.215,57.598,-152.216,57.577,-152.337,57.482,-152.381,57.46,-152.412,57.455,-152.631,57.472,-152.831,57.503,-152.912,57.508,-152.941,57.498,-152.997,57.469,-152.957,57.46,-152.781,57.453,-152.72,57.411,-152.693,57.38,-152.679,57.345,-152.714,57.331,-152.789,57.321,-152.879,57.321,-152.99,57.282,-153.052,57.238,-153.274,57.226,-153.444,57.167,-153.504,57.138,-153.524,57.103,-153.588,57.078,-153.733,57.052,-153.647,57.03,-153.633,57.01,-153.631,56.984,-153.643,56.961,-153.757,56.858,-153.973,56.774,-154.027,56.778,-154.051,56.788,-154.07,56.805,-154.071,56.821,-153.793,56.99,-153.804,56.998,-153.88,57.004,-153.999,57.05,-154.084,57.02,-154.103,57.021,-154.08,57.061,-154.025,57.108,-154.035,57.122,-154.065,57.134,-154.135,57.141,-154.244,57.143,-154.324,57.132,-154.377,57.107,-154.381,57.097,-154.27,57.099,-154.239,57.087,-154.209,57.063,-154.191,57.036,-154.184,57.005,-154.208,56.964,-154.261,56.912,-154.339,56.921,-154.499,57.037,-154.569,57.206,-154.706,57.335,-154.712,57.366,-154.673,57.446,-154.535,57.559,-154.387,57.59,-154.281,57.638,-154.179,57.652,-154.116,57.651,-154.03,57.631,-153.995,57.587,-154.016,57.567,-154.008,57.556,-153.947,57.53,-153.882,57.439,-153.805,57.358,-153.755,57.325,-153.688,57.305,-153.757,57.367,-153.798,57.443,-153.818,57.596,-153.838,57.636,-153.799,57.647,-153.69,57.641,-153.693,57.663,-153.808,57.715,-153.879,57.757,-153.906,57.791,-153.904,57.82,-153.842,57.863,-153.806,57.875,-153.769,57.88,-153.696,57.871,-153.663,57.858,-153.569,57.761,-153.524,57.731,-153.488,57.731,-153.454,57.747,-153.423,57.779,-153.39,57.798,-153.357,57.805,-153.252,57.79,-153.217,57.796,-153.2,57.82,-153.201,57.863,-153.175,57.879,-153.169,57.911,-153.226,57.958,-153.16,57.972,-152.943,57.936,-152.85,57.897,-152.898,57.824]],[[-130.025,55.888,-130.075,55.836,-130.112,55.78,-130.137,55.719,-130.147,55.654,-130.14,55.585,-130.12,55.524,-130.059,55.412,-130.039,55.344,-130.037,55.298,-130.172,55.137,-130.219,55.06,-130.214,55.026,-130.313,54.946,-130.493,54.834,-130.575,54.77,-130.616,54.791,-130.85,54.808,-130.935,54.95,-130.98,55.061,-131.048,55.158,-131.046,55.18,-130.984,55.244,-130.75,55.297,-130.748,55.318,-130.835,55.332,-130.856,55.355,-130.88,55.46,-130.873,55.551,-130.88,55.612,-130.919,55.736,-130.977,55.812,-131.128,55.96,-131.14,55.998,-131.074,56.044,-131.033,56.088,-131.288,56.012,-131.635,55.932,-131.784,55.877,-131.815,55.854,-131.826,55.835,-131.799,55.783,-131.803,55.766,-131.834,55.735,-131.869,55.647,-131.945,55.554,-131.983,55.535,-132.119,55.57,-132.155,55.6,-132.223,55.721,-132.208,55.753,-132.158,55.781,-132.091,55.84,-132.006,55.93,-131.844,56.16,-131.738,56.161,-131.551,56.207,-131.844,56.23,-131.888,56.242,-131.927,56.273,-131.962,56.324,-132.022,56.38,-132.133,56.4,-132.182,56.421,-132.256,56.489,-132.305,56.52,-132.332,56.558,-132.337,56.603,-132.358,56.626,-132.434,56.634,-132.476,56.65,-132.487,56.766,-132.64,56.796,-132.702,56.822,-132.802,56.895,-132.83,56.931,-132.839,56.96,-132.814,57.041,-132.825,57.056,-132.913,57.047,-133.466,57.172,-133.437,57.337,-133.539,57.554,-133.649,57.642,-133.627,57.677,-133.603,57.695,-133.554,57.695,-133.342,57.631,-133.143,57.555,-133.117,57.566,-133.436,57.727,-133.515,57.775,-133.535,57.833,-133.536,57.864,-133.511,57.88,-133.212,57.866,-133.194,57.878,-133.497,57.925,-133.559,57.924,-133.626,57.857,-133.657,57.841,-133.722,57.844,-133.744,57.855,-133.821,57.936,-133.894,57.993,-134.031,58.072,-134.057,58.128,-134.063,58.211,-134.045,58.289,-133.934,58.468,-133.889,58.499,-133.877,58.518,-133.911,58.515,-133.944,58.498,-134.036,58.415,-134.131,58.279,-134.209,58.233,-134.258,58.244,-134.331,58.3,-134.485,58.367,-134.664,58.385,-134.776,58.454,-134.943,58.646,-134.965,58.742,-134.986,58.766,-135.076,58.797,-135.132,58.843,-135.217,59.077,-135.33,59.239,-135.358,59.325,-135.349,59.41,-135.364,59.419,-135.403,59.353,-135.413,59.318,-135.484,59.309,-135.417,59.242,-135.4,59.208,-135.434,59.211,-135.502,59.202,-135.386,59.088,-135.334,58.91,-135.257,58.778,-135.207,58.671,-135.185,58.59,-135.152,58.512,-135.062,58.341,-135.05,58.307,-135.06,58.279,-135.09,58.246,-135.142,58.233,-135.303,58.256,-135.363,58.298,-135.45,58.376,-135.572,58.412,-135.873,58.394,-135.898,58.4,-135.896,58.464,-135.862,58.577,-135.89,58.623,-136.046,58.789,-136.043,58.822,-135.826,58.898,-135.932,58.904,-136.017,58.874,-136.049,58.893,-136.101,59,-136.134,59.04,-136.15,59.048,-136.159,58.947,-136.124,58.893,-136.118,58.863,-136.124,58.82,-136.147,58.789,-136.186,58.77,-136.226,58.765,-136.299,58.787,-136.38,58.827,-136.451,58.846,-136.478,58.863,-136.511,58.907,-136.566,58.941,-136.831,58.984,-136.989,59.034,-137.002,59.021,-136.953,58.967,-136.948,58.935,-136.988,58.925,-137.059,58.874,-137.038,58.867,-136.963,58.884,-136.879,58.882,-136.74,58.85,-136.614,58.809,-136.568,58.786,-136.549,58.752,-136.533,58.74,-136.41,58.701,-136.404,58.68,-136.484,58.618,-136.32,58.624,-136.225,58.602,-136.103,58.506,-136.061,58.453,-136.056,58.384,-136.081,58.364,-136.13,58.35,-136.462,58.328,-136.583,58.245,-136.607,58.244,-136.699,58.266,-136.865,58.332,-137.072,58.395,-137.544,58.581,-137.557,58.59,-137.565,58.626,-137.597,58.644,-137.661,58.66,-137.75,58.707,-137.864,58.786,-137.934,58.847,-137.961,58.891,-138.027,58.941,-138.241,59.047,-138.352,59.087,-138.451,59.11,-138.537,59.115,-138.56,59.129,-138.521,59.152,-138.515,59.166,-138.704,59.188,-138.884,59.237,-139.341,59.376,-139.577,59.462,-139.714,59.504,-139.773,59.527,-139.799,59.546,-139.766,59.566,-139.674,59.587,-139.612,59.61,-139.513,59.698,-139.506,59.726,-139.558,59.79,-139.582,59.848,-139.581,59.881,-139.569,59.912,-139.554,59.933,-139.512,59.954,-139.483,59.964,-139.447,59.957,-139.331,59.877,-139.315,59.848,-139.32,59.739,-139.287,59.611,-139.276,59.62,-139.266,59.663,-139.259,59.743,-139.246,59.782,-139.221,59.82,-139.179,59.84,-139.048,59.828,-138.988,59.835,-139.242,59.893,-139.402,60.001,-139.431,60.012,-139.519,60.017,-139.612,59.973,-139.85,59.831,-139.917,59.806,-140.217,59.727,-140.42,59.711,-140.648,59.723,-140.843,59.749,-141.332,59.874,-141.408,59.903,-141.295,59.98,-141.29,60.004,-141.33,60.083,-141.362,60.105,-141.409,60.118,-141.422,60.109,-141.422,60.085,-141.41,60.042,-141.447,60.019,-141.53,59.995,-141.67,59.97,-142.104,60.033,-142.549,60.086,-142.946,60.097,-143.506,60.055,-143.805,60.013,-143.979,60.009,-144.147,60.016,-144.161,60.046,-144.084,60.063,-144.089,60.084,-144.185,60.151,-144.333,60.191,-144.53,60.205,-144.643,60.225,-144.672,60.249,-144.741,60.273,-144.852,60.295,-144.901,60.335,-144.862,60.459,-144.824,60.534,-144.787,60.585,-144.691,60.669,-144.724,60.663,-144.863,60.601,-144.984,60.537,-145.096,60.454,-145.163,60.415,-145.248,60.38,-145.382,60.389,-145.563,60.441,-145.718,60.468,-145.848,60.469,-145.899,60.478,-145.811,60.525,-145.76,60.562,-145.69,60.622,-145.675,60.651,-146.149,60.661,-146.166,60.692,-146.167,60.716,-146.182,60.735,-146.251,60.749,-146.347,60.738,-146.503,60.701,-146.57,60.729,-146.546,60.745,-146.496,60.757,-146.392,60.811,-146.532,60.839,-146.604,60.871,-146.638,60.897,-146.636,60.993,-146.599,61.054,-146.285,61.113,-146.384,61.136,-146.583,61.128,-146.716,61.078,-146.874,61.005,-146.98,60.978,-147.034,60.996,-147.106,61.003,-147.195,60.997,-147.255,60.978,-147.286,60.947,-147.321,60.925,-147.361,60.915,-147.391,60.918,-147.433,60.95,-147.523,60.97,-147.567,60.995,-147.593,60.979,-147.623,60.933,-147.656,60.91,-147.808,60.885,-147.891,60.89,-147.991,60.948,-148.005,60.969,-147.971,61.019,-147.752,61.219,-147.774,61.218,-147.845,61.186,-147.986,61.106,-148.049,61.083,-148.158,61.08,-148.209,61.088,-148.27,61.082,-148.342,61.06,-148.389,61.037,-148.411,61.011,-148.396,61.007,-148.287,61.036,-148.226,61.044,-148.209,61.03,-148.293,60.94,-148.344,60.854,-148.393,60.832,-148.471,60.835,-148.556,60.827,-148.557,60.803,-148.399,60.734,-148.341,60.724,-148.268,60.7,-148.257,60.675,-148.284,60.609,-148.305,60.583,-148.338,60.57,-148.468,60.572,-148.51,60.565,-148.597,60.524,-148.64,60.489,-148.624,60.486,-148.549,60.515,-148.44,60.53,-148.296,60.532,-148.189,60.547,-148.119,60.575,-148.051,60.567,-147.984,60.523,-147.964,60.485,-147.991,60.452,-148.046,60.428,-148.129,60.414,-148.182,60.393,-148.204,60.365,-148.216,60.323,-148.219,60.268,-148.198,60.168,-148.214,60.154,-148.245,60.147,-148.291,60.145,-148.333,60.122,-148.431,59.989,-148.465,59.975,-148.506,59.989,-148.542,59.987,-148.574,59.97,-148.644,59.957,-148.751,59.948,-148.843,59.951,-149.004,59.98,-149.07,60,-149.122,60.033,-149.267,59.998,-149.305,60.014,-149.395,60.106,-149.415,60.1,-149.432,60.001,-149.46,59.966,-149.549,59.894,-149.598,59.77,-149.613,59.767,-149.63,59.785,-149.685,59.895,-149.714,59.92,-149.795,59.856,-149.804,59.833,-149.782,59.75,-149.801,59.738,-149.965,59.782,-150.005,59.784,-150.016,59.777,-149.96,59.713,-149.967,59.69,-150.198,59.567,-150.258,59.571,-150.296,59.583,-150.338,59.581,-150.485,59.535,-150.526,59.537,-150.582,59.565,-150.607,59.563,-150.621,59.535,-150.623,59.48,-150.677,59.427,-150.853,59.342,-150.899,59.303,-150.935,59.249,-150.961,59.244,-151.064,59.278,-151.183,59.301,-151.199,59.29,-151.163,59.257,-151.171,59.237,-151.222,59.229,-151.287,59.232,-151.366,59.246,-151.477,59.231,-151.619,59.187,-151.738,59.189,-151.904,59.26,-151.95,59.265,-151.964,59.285,-151.932,59.343,-151.885,59.386,-151.85,59.406,-151.693,59.462,-151.513,59.483,-151.4,59.516,-151.262,59.586,-151.189,59.638,-151.046,59.772,-151.057,59.782,-151.089,59.789,-151.404,59.662,-151.45,59.65,-151.513,59.651,-151.764,59.7,-151.817,59.721,-151.853,59.782,-151.783,59.921,-151.735,59.988,-151.612,60.092,-151.451,60.203,-151.396,60.274,-151.313,60.466,-151.318,60.554,-151.355,60.66,-151.356,60.723,-151.322,60.743,-150.954,60.841,-150.779,60.915,-150.441,61.024,-150.349,61.023,-150.281,60.985,-150.203,60.955,-150.113,60.933,-149.998,60.935,-149.856,60.962,-149.632,60.952,-149.173,60.88,-149.075,60.876,-149.071,60.886,-149.142,60.936,-149.459,60.965,-149.592,60.994,-149.968,61.122,-150.053,61.171,-150.019,61.194,-149.927,61.213,-149.895,61.232,-149.882,61.264,-149.829,61.308,-149.737,61.363,-149.596,61.417,-149.329,61.497,-149.434,61.501,-149.625,61.486,-149.695,61.471,-149.824,61.413,-149.874,61.373,-149.945,61.294,-149.976,61.279,-150.109,61.268,-150.472,61.26,-150.533,61.3,-150.567,61.307,-150.612,61.301,-150.946,61.198,-151.065,61.146,-151.15,61.086,-151.282,61.042,-151.46,61.014,-151.594,60.98,-151.734,60.911,-151.782,60.858,-151.784,60.833,-151.75,60.755,-151.785,60.74,-151.866,60.734,-151.996,60.682,-152.271,60.528,-152.307,60.472,-152.305,60.453,-152.26,60.409,-152.292,60.381,-152.369,60.336,-152.541,60.265,-152.654,60.238,-152.727,60.237,-152.798,60.247,-152.923,60.293,-153.025,60.296,-153.031,60.289,-152.893,60.24,-152.752,60.177,-152.665,60.125,-152.63,60.084,-152.629,60.041,-152.66,59.997,-152.759,59.921,-152.857,59.898,-153.106,59.875,-153.186,59.857,-153.211,59.843,-153.04,59.81,-153.025,59.794,-153.048,59.73,-153.094,59.709,-153.236,59.671,-153.364,59.66,-153.383,59.667,-153.36,59.717,-153.366,59.73,-153.414,59.74,-153.483,59.721,-153.653,59.647,-153.671,59.635,-153.609,59.615,-153.622,59.598,-153.714,59.545,-153.753,59.51,-153.814,59.474,-154.088,59.363,-154.067,59.336,-154.139,59.24,-154.178,59.156,-154.13,59.12,-153.9,59.078,-153.788,59.068,-153.656,59.039,-153.418,58.96,-153.339,58.909,-153.327,58.884,-153.334,58.858,-153.363,58.822,-153.438,58.755,-153.617,58.655,-153.699,58.626,-153.821,58.604,-153.862,58.588,-154.02,58.493,-154.062,58.442,-154.056,58.397,-154.086,58.366,-154.289,58.304,-154.282,58.293,-154.208,58.289,-154.235,58.235,-154.247,58.159,-154.282,58.147,-154.409,58.147,-154.571,58.118,-154.582,58.11,-154.585,58.056,-155.007,58.016,-155.099,57.913,-155.147,57.882,-155.313,57.807,-155.414,57.777,-155.53,57.759,-155.59,57.734,-155.596,57.701,-155.629,57.673,-155.729,57.627,-155.778,57.568,-155.814,57.559,-156,57.545,-156.037,57.527,-156.055,57.448,-156.09,57.445,-156.156,57.463,-156.242,57.449,-156.436,57.36,-156.478,57.328,-156.474,57.311,-156.444,57.294,-156.398,57.241,-156.4,57.205,-156.475,57.105,-156.501,57.09,-156.592,57.065,-156.629,57.01,-156.713,57.016,-156.78,57.006,-156.824,56.969,-156.872,56.948,-156.923,56.942,-156.988,56.913,-157.067,56.86,-157.139,56.827,-157.206,56.812,-157.271,56.808,-157.334,56.816,-157.39,56.81,-157.441,56.79,-157.49,56.76,-157.529,56.673,-157.578,56.634,-157.61,56.628,-157.674,56.633,-157.771,56.652,-157.869,56.645,-158.028,56.592,-158.078,56.552,-157.978,56.543,-157.929,56.532,-157.93,56.52,-157.982,56.51,-158.071,56.51,-158.124,56.501,-158.189,56.478,-158.352,56.454,-158.414,56.436,-158.537,56.335,-158.552,56.313,-158.536,56.308,-158.467,56.318,-158.386,56.302,-158.344,56.28,-158.317,56.254,-158.291,56.204,-158.276,56.196,-158.432,56.111,-158.476,56.075,-158.505,56.062,-158.523,56.072,-158.543,56.167,-158.554,56.183,-158.591,56.185,-158.627,56.155,-158.705,56.043,-158.79,55.987,-159.429,55.843,-159.523,55.81,-159.541,55.748,-159.568,55.695,-159.61,55.653,-159.66,55.626,-159.67,55.645,-159.665,55.795,-159.679,55.825,-159.743,55.844,-159.771,55.841,-159.81,55.833,-159.874,55.8,-159.914,55.792,-159.962,55.795,-160.046,55.763,-160.244,55.661,-160.373,55.635,-160.407,55.614,-160.463,55.558,-160.499,55.537,-160.554,55.535,-160.625,55.552,-160.683,55.54,-160.727,55.5,-160.771,55.484,-160.897,55.514,-160.952,55.493,-161.024,55.44,-161.1,55.406,-161.178,55.389,-161.382,55.371,-161.464,55.383,-161.481,55.398,-161.477,55.465,-161.444,55.513,-161.413,55.536,-161.373,55.556,-161.313,55.559,-161.202,55.544,-161.215,55.56,-161.255,55.579,-161.357,55.612,-161.459,55.629,-161.517,55.618,-161.599,55.593,-161.654,55.563,-161.684,55.53,-161.72,55.421,-161.742,55.391,-161.98,55.199,-162.074,55.139,-162.167,55.144,-162.211,55.121,-162.275,55.073,-162.333,55.05,-162.386,55.052,-162.428,55.061,-162.457,55.078,-162.452,55.093,-162.413,55.107,-162.427,55.145,-162.495,55.208,-162.542,55.243,-162.63,55.247,-162.644,55.218,-162.614,55.071,-162.619,55.038,-162.674,54.997,-162.82,54.95,-162.865,54.955,-162.996,55.046,-163.12,55.065,-163.128,55.035,-163.1,54.974,-163.131,54.917,-163.221,54.863,-163.289,54.838,-163.335,54.839,-163.338,54.876,-163.296,54.949,-163.286,55.01,-163.306,55.059,-163.304,55.096,-163.279,55.122,-163.115,55.194,-163.045,55.205,-163.008,55.187,-162.962,55.184,-162.907,55.196,-162.872,55.219,-162.857,55.253,-162.786,55.297,-162.659,55.351,-162.513,55.45,-162.349,55.595,-162.157,55.719,-161.937,55.824,-161.697,55.907,-161.216,56.021,-161.179,56.014,-161.223,55.977,-161.193,55.954,-161.145,55.951,-160.969,55.97,-160.899,55.994,-160.878,55.971,-160.902,55.941,-161.008,55.912,-161.005,55.887,-160.851,55.772,-160.803,55.754,-160.763,55.757,-160.746,55.771,-160.758,55.855,-160.706,55.87,-160.6,55.874,-160.53,55.863,-160.498,55.838,-160.437,55.817,-160.347,55.8,-160.292,55.805,-160.27,55.832,-160.308,55.864,-160.48,55.935,-160.527,55.965,-160.539,56.006,-160.515,56.059,-160.461,56.138,-160.377,56.241,-160.302,56.314,-160.149,56.396,-160.046,56.437,-159.785,56.562,-159.283,56.689,-159.159,56.77,-158.99,56.86,-158.918,56.882,-158.918,56.847,-158.895,56.816,-158.782,56.796,-158.709,56.789,-158.675,56.795,-158.666,56.828,-158.681,56.888,-158.685,56.944,-158.677,56.997,-158.661,57.039,-158.586,57.114,-158.474,57.199,-158.321,57.298,-158.225,57.343,-158.134,57.366,-158.046,57.409,-157.894,57.511,-157.846,57.528,-157.737,57.548,-157.697,57.539,-157.674,57.514,-157.646,57.498,-157.535,57.483,-157.462,57.506,-157.474,57.518,-157.533,57.526,-157.572,57.541,-157.608,57.601,-157.681,57.638,-157.697,57.679,-157.684,57.744,-157.621,57.895,-157.611,58.051,-157.555,58.14,-157.443,58.172,-157.194,58.194,-157.339,58.235,-157.394,58.235,-157.488,58.254,-157.524,58.351,-157.524,58.421,-157.461,58.503,-157.229,58.641,-156.975,58.736,-157.009,58.744,-157.04,58.773,-156.923,58.964,-156.809,59.134,-156.963,58.989,-157.142,58.878,-157.666,58.748,-158.022,58.64,-158.191,58.614,-158.303,58.642,-158.39,58.746,-158.439,58.783,-158.503,58.85,-158.476,58.938,-158.426,58.999,-158.315,59.009,-158.189,58.98,-158.081,58.977,-158.221,59.038,-158.423,59.09,-158.514,59.073,-158.584,58.988,-158.678,58.929,-158.761,58.95,-158.809,58.974,-158.776,58.903,-158.838,58.794,-158.861,58.719,-158.772,58.52,-158.789,58.441,-158.951,58.405,-159.083,58.47,-159.358,58.721,-159.454,58.793,-159.67,58.911,-159.741,58.894,-159.832,58.836,-159.92,58.82,-160.153,58.906,-160.261,58.972,-160.363,59.051,-160.52,59.007,-160.657,58.955,-160.817,58.872,-160.924,58.872,-161.216,58.801,-161.247,58.799,-161.288,58.761,-161.328,58.744,-161.361,58.67,-161.755,58.612,-162.145,58.644,-162.009,58.685,-161.856,58.717,-161.724,58.794,-161.781,58.897,-161.79,58.95,-161.789,59.016,-161.644,59.11,-161.794,59.109,-161.891,59.076,-161.981,59.146,-162.023,59.284,-161.92,59.365,-161.872,59.428,-161.832,59.515,-161.829,59.589,-161.909,59.714,-162.138,59.98,-162.242,60.178,-162.421,60.284,-162.288,60.457,-162.139,60.614,-161.947,60.685,-161.962,60.695,-162.068,60.695,-162.138,60.686,-162.2,60.634,-162.265,60.595,-162.469,60.395,-162.6,60.297,-162.685,60.269,-162.548,60.231,-162.527,60.199,-162.5,60.127,-162.536,60.038,-162.571,59.99,-162.733,59.994,-162.878,59.923,-163.219,59.846,-163.68,59.802,-163.907,59.807,-164.143,59.897,-164.141,59.949,-164.132,59.994,-164.471,60.149,-164.662,60.304,-164.8,60.307,-164.92,60.348,-165.061,60.413,-165.049,60.464,-165.027,60.501,-165.113,60.526,-165.225,60.524,-165.354,60.541,-165.016,60.74,-164.9,60.873,-164.805,60.892,-164.682,60.872,-164.513,60.819,-164.37,60.796,-164.319,60.771,-164.266,60.725,-164.321,60.647,-164.372,60.592,-164.31,60.607,-164.132,60.692,-164,60.766,-163.936,60.758,-163.895,60.745,-163.821,60.668,-163.73,60.59,-163.529,60.665,-163.421,60.757,-163.512,60.798,-163.623,60.822,-163.907,60.854,-163.837,60.88,-163.655,60.877,-163.587,60.903,-163.659,60.938,-163.749,60.97,-163.995,60.865,-164.442,60.87,-164.754,60.931,-165.066,60.921,-165.115,60.933,-165.175,60.966,-165,61.044,-164.876,61.087,-164.869,61.112,-164.941,61.115,-165.077,61.094,-165.138,61.13,-165.128,61.192,-165.15,61.187,-165.204,61.153,-165.28,61.17,-165.345,61.198,-165.311,61.228,-165.244,61.269,-165.274,61.275,-165.334,61.266,-165.392,61.212,-165.379,61.169,-165.381,61.106,-165.48,61.095,-165.566,61.102,-165.628,61.165,-165.691,61.3,-165.864,61.336,-165.906,61.404,-165.797,61.491,-165.845,61.536,-165.961,61.551,-166.094,61.507,-166.153,61.546,-166.164,61.589,-166.168,61.651,-166.131,61.657,-166.1,61.645,-165.835,61.679,-165.809,61.696,-166.02,61.748,-166.079,61.803,-165.991,61.834,-165.834,61.837,-165.613,61.869,-165.706,61.927,-165.725,61.959,-165.744,62.012,-165.707,62.1,-165.448,62.304,-165.195,62.474,-165.116,62.513,-165,62.534,-164.892,62.518,-164.779,62.481,-164.758,62.497,-164.796,62.512,-164.844,62.581,-164.688,62.608,-164.596,62.687,-164.589,62.709,-164.689,62.677,-164.793,62.623,-164.819,62.677,-164.845,62.801,-164.8,62.918,-164.764,62.971,-164.677,63.02,-164.428,63.04,-164.384,63.03,-164.375,63.054,-164.525,63.128,-164.463,63.185,-164.409,63.215,-164.108,63.262,-163.943,63.247,-163.736,63.193,-163.616,63.125,-163.634,63.09,-163.664,63.07,-163.726,63.048,-163.749,63.03,-163.738,63.016,-163.649,63.057,-163.504,63.106,-163.423,63.085,-163.359,63.046,-163.288,63.046,-163.062,63.08,-162.948,63.115,-162.808,63.207,-162.621,63.266,-162.36,63.453,-162.283,63.529,-162.193,63.541,-162.112,63.534,-162.056,63.471,-161.974,63.453,-161.505,63.468,-161.266,63.497,-161.1,63.558,-160.927,63.661,-160.827,63.729,-160.779,63.819,-160.84,63.935,-160.904,64.031,-160.988,64.251,-161.22,64.397,-161.386,64.44,-161.491,64.434,-161.415,64.526,-161.193,64.516,-161.049,64.534,-160.932,64.579,-160.894,64.613,-160.836,64.682,-160.856,64.756,-160.887,64.796,-160.967,64.84,-161.063,64.904,-161.13,64.925,-161.187,64.924,-161.466,64.795,-161.634,64.792,-161.759,64.816,-161.868,64.743,-162.172,64.678,-162.335,64.613,-162.636,64.451,-162.711,64.378,-162.807,64.374,-162.876,64.516,-163.204,64.652,-163.303,64.606,-163.248,64.563,-163.174,64.533,-163.052,64.52,-163.104,64.479,-163.144,64.424,-163.267,64.475,-163.486,64.55,-163.713,64.588,-164.304,64.584,-164.692,64.507,-164.727,64.523,-164.765,64.53,-164.83,64.511,-164.857,64.48,-164.9,64.461,-164.979,64.454,-165.138,64.465,-165.446,64.513,-166.143,64.583,-166.325,64.626,-166.481,64.728,-166.478,64.798,-166.409,64.827,-166.415,64.927,-166.551,64.953,-166.827,65.096,-166.928,65.157,-166.906,65.164,-166.857,65.147,-166.763,65.135,-166.531,65.155,-166.452,65.247,-166.28,65.274,-166.121,65.261,-166.157,65.286,-166.197,65.306,-166.609,65.353,-166.665,65.338,-167.404,65.422,-167.987,65.568,-168.035,65.596,-168.088,65.658,-168.01,65.719,-167.931,65.748,-167.927,65.714,-167.914,65.681,-167.58,65.758,-167.405,65.859,-167.074,65.877,-166.997,65.905,-166.894,65.959,-166.748,66.052,-166.54,66.101,-166.399,66.144,-166.215,66.17,-166.057,66.127,-166.009,66.121,-165.724,66.113,-165.63,66.131,-165.59,66.145,-165.56,66.167,-165.84,66.245,-165.812,66.288,-165.776,66.319,-165.449,66.41,-165.198,66.44,-165.064,66.438,-164.674,66.555,-164.46,66.588,-164.058,66.611,-163.728,66.616,-163.638,66.575,-163.816,66.583,-163.894,66.576,-163.838,66.562,-163.775,66.531,-163.794,66.493,-163.903,66.378,-163.894,66.287,-163.965,66.257,-164.034,66.216,-163.695,66.084,-163.171,66.075,-162.886,66.099,-162.722,66.06,-162.587,66.051,-162.214,66.071,-161.934,66.043,-161.816,66.054,-161.557,66.251,-161.455,66.281,-161.345,66.247,-161.201,66.219,-161.109,66.24,-161.034,66.189,-161.07,66.295,-161.12,66.334,-161.544,66.407,-161.828,66.371,-161.917,66.412,-161.888,66.493,-162.191,66.693,-162.318,66.734,-162.467,66.736,-162.544,66.805,-162.607,66.894,-162.478,66.931,-162.362,66.947,-162.254,66.919,-162.131,66.801,-162.018,66.784,-162.051,66.667,-161.91,66.56,-161.591,66.46,-161.336,66.496,-161.156,66.495,-161.048,66.474,-160.784,66.384,-160.651,66.373,-160.232,66.42,-160.227,66.509,-160.263,66.572,-160.361,66.613,-160.644,66.605,-160.864,66.671,-161.051,66.653,-161.398,66.552,-161.572,66.592,-161.681,66.646,-161.857,66.7,-161.879,66.804,-161.731,66.923,-161.622,66.979,-161.72,67.021,-161.965,67.05,-162.392,67.02,-162.412,67.06,-162.409,67.104,-162.583,67.019,-162.761,67.036,-163.002,67.027,-163.532,67.103,-163.721,67.196,-163.8,67.271,-163.943,67.478,-164.125,67.607,-165.386,68.046,-165.96,68.156,-166.236,68.278,-166.409,68.308,-166.574,68.32,-166.786,68.36,-166.644,68.408,-166.546,68.424,-166.648,68.374,-166.57,68.361,-166.447,68.39,-166.381,68.425,-166.283,68.573,-166.182,68.797,-166.209,68.885,-165.509,68.868,-165.044,68.882,-164.89,68.902,-164.302,68.936,-164.15,68.961,-163.868,69.037,-163.536,69.17,-163.251,69.345,-163.205,69.393,-163.187,69.38,-163.161,69.388,-163.131,69.454,-163.094,69.611,-162.952,69.758,-162.35,70.094,-162.071,70.227,-161.978,70.288,-161.881,70.332,-161.813,70.29,-161.78,70.277,-161.761,70.258,-161.818,70.248,-161.912,70.205,-162.042,70.177,-162.074,70.162,-161.997,70.165,-161.768,70.197,-161.639,70.235,-160.996,70.305,-160.648,70.421,-160.634,70.446,-160.117,70.591,-160.046,70.586,-159.963,70.568,-160.106,70.473,-160.006,70.448,-160.095,70.333,-159.908,70.331,-159.866,70.279,-159.855,70.324,-159.858,70.389,-159.843,70.453,-159.815,70.497,-159.683,70.477,-159.387,70.525,-159.746,70.53,-159.962,70.634,-160.082,70.635,-159.681,70.787,-159.315,70.879,-159.232,70.877,-159.192,70.86,-159.183,70.832,-159.262,70.814,-159.34,70.781,-159.304,70.753,-159.251,70.748,-159.075,70.772,-158.996,70.802,-158.621,70.799,-158.511,70.82,-158.484,70.841,-157.998,70.845,-157.909,70.86,-157.606,70.941,-157.325,71.04,-157.195,71.093,-156.973,71.23,-156.783,71.319,-156.47,71.408,-156.395,71.397,-156.497,71.379,-156.567,71.342,-156.47,71.292,-155.811,71.188,-155.646,71.183,-155.579,71.121,-155.635,71.062,-155.804,70.995,-156.147,70.928,-156.042,70.902,-155.974,70.842,-155.872,70.835,-155.708,70.857,-155.579,70.894,-155.313,71.015,-155.23,71.082,-155.167,71.099,-154.944,71.083,-154.818,71.048,-154.674,70.987,-154.726,70.928,-154.785,70.894,-154.599,70.848,-154.392,70.838,-154.195,70.801,-153.918,70.877,-153.701,70.894,-153.498,70.891,-153.233,70.933,-152.785,70.876,-152.671,70.891,-152.491,70.881,-152.3,70.847,-152.233,70.81,-152.437,70.733,-152.471,70.654,-152.399,70.62,-152.27,70.615,-152.253,70.568,-152.173,70.557,-151.769,70.56,-151.8,70.538,-151.82,70.511,-151.945,70.452,-151.225,70.419,-151.128,70.452,-150.979,70.465,-150.663,70.51,-150.544,70.49,-150.403,70.444,-150.274,70.434,-150.152,70.444,-149.87,70.51,-149.544,70.513,-149.411,70.491,-149.269,70.501,-148.845,70.425,-148.688,70.416,-148.479,70.318,-148.371,70.315,-148.249,70.357,-148.143,70.355,-148.039,70.315,-147.87,70.303,-147.791,70.24,-147.705,70.217,-147.063,70.17,-146.745,70.192,-146.281,70.186,-146.058,70.156,-145.823,70.16,-145.44,70.051,-145.237,70.034,-145.197,70.009,-144.619,69.982,-144.417,70.039,-144.064,70.054,-143.746,70.102,-143.566,70.101,-143.357,70.09,-143.276,70.095,-143.218,70.116,-142.708,70.034,-142.422,69.94,-142.297,69.87,-141.699,69.77,-141.526,69.715,-141.408,69.653,-141.339,69.647,-141.29,69.665,-141.081,69.659,-141.002,69.651,-141.002,69.359,-141.002,69.066,-141.002,68.774,-141.002,68.482,-141.002,68.19,-141.002,67.898,-141.002,67.605,-141.002,67.313,-141.002,67.021,-141.002,66.729,-141.002,66.437,-141.002,66.144,-141.002,65.852,-141.002,65.56,-141.002,65.268,-141.002,64.976,-141.002,64.683,-141.002,64.391,-141.002,64.099,-141.002,63.807,-141.002,63.514,-141.002,63.222,-141.002,62.93,-141.002,62.638,-141.002,62.346,-141.002,62.053,-141.002,61.761,-141.002,61.469,-141.002,61.177,-141.002,60.885,-141.002,60.592,-141.002,60.3,-140.763,60.259,-140.525,60.218,-140.453,60.3,-140.197,60.238,-139.973,60.183,-139.831,60.253,-139.676,60.328,-139.468,60.334,-139.235,60.34,-139.079,60.344,-139.079,60.279,-139.137,60.173,-139.185,60.084,-139.043,59.993,-138.869,59.946,-138.705,59.901,-138.632,59.778,-138.454,59.683,-138.318,59.611,-138.187,59.542,-138.001,59.443,-137.871,59.374,-137.697,59.281,-137.593,59.226,-137.544,59.119,-137.484,58.991,-137.521,58.915,-137.439,58.903,-137.278,58.988,-137.126,59.041,-136.939,59.106,-136.813,59.15,-136.579,59.152,-136.467,59.28,-136.466,59.459,-136.348,59.456,-136.278,59.48,-136.247,59.533,-136.322,59.605,-136.097,59.638,-135.935,59.663,-135.703,59.729,-135.476,59.793,-135.368,59.743,-135.261,59.695,-135.051,59.579,-135.037,59.551,-135.051,59.496,-135.071,59.441,-134.944,59.288,-134.907,59.271,-134.802,59.25,-134.677,59.199,-134.622,59.155,-134.441,59.085,-134.41,59.056,-134.393,59.009,-134.364,58.969,-134.33,58.94,-134.297,58.898,-134.219,58.85,-134.069,58.796,-133.966,58.758,-133.821,58.705,-133.674,58.597,-133.546,58.503,-133.401,58.411,-133.423,58.337,-133.275,58.223,-133.12,58.078,-133.001,57.949,-132.917,57.877,-132.816,57.773,-132.692,57.645,-132.55,57.5,-132.442,57.407,-132.302,57.276,-132.232,57.199,-132.279,57.145,-132.338,57.079,-132.157,57.048,-132.032,57.027,-132.063,56.953,-132.104,56.857,-131.962,56.819,-131.866,56.793,-131.886,56.742,-131.833,56.685,-131.824,56.59,-131.652,56.596,-131.575,56.599,-131.472,56.557,-131.336,56.501,-131.199,56.449,-131.083,56.405,-130.93,56.379,-130.742,56.341,-130.649,56.264,-130.477,56.231,-130.413,56.123,-130.215,56.083,-130.098,56.109,-130.056,56.065,-130.023,56.015,-130.014,55.951,-130.025,55.888]],[[-163.476,54.981,-163.379,54.816,-163.337,54.783,-163.275,54.766,-163.187,54.748,-163.135,54.723,-163.089,54.686,-163.083,54.669,-163.358,54.736,-163.531,54.638,-163.583,54.626,-164.073,54.621,-164.171,54.603,-164.235,54.571,-164.347,54.482,-164.404,54.448,-164.463,54.427,-164.591,54.404,-164.744,54.407,-164.823,54.419,-164.866,54.461,-164.904,54.545,-164.904,54.568,-164.888,54.608,-164.751,54.663,-164.706,54.692,-164.53,54.881,-164.479,54.907,-164.424,54.913,-164.274,54.9,-164.145,54.955,-163.868,55.039,-163.807,55.049,-163.607,55.051,-163.553,55.038,-163.511,55.014,-163.476,54.981]],[[-133.305,55.544,-133.283,55.516,-133.282,55.498,-133.426,55.431,-133.429,55.418,-133.463,55.377,-133.493,55.362,-133.547,55.317,-133.65,55.269,-133.635,55.413,-133.737,55.497,-133.634,55.539,-133.567,55.527,-133.455,55.522,-133.346,55.559,-133.305,55.544]],[[-131.34,55.08,-131.237,54.95,-131.232,54.904,-131.33,54.888,-131.406,54.894,-131.446,54.909,-131.456,54.931,-131.431,54.996,-131.482,55.035,-131.54,55.048,-131.592,55.026,-131.595,55.091,-131.556,55.137,-131.578,55.201,-131.578,55.249,-131.565,55.264,-131.513,55.263,-131.405,55.213,-131.34,55.08]],[[-132.112,56.109,-132.133,55.943,-132.173,55.953,-132.21,55.953,-132.287,55.929,-132.369,55.94,-132.407,55.958,-132.421,55.98,-132.406,56.029,-132.451,56.056,-132.603,56.066,-132.66,56.078,-132.691,56.13,-132.699,56.198,-132.675,56.224,-132.599,56.242,-132.539,56.324,-132.506,56.335,-132.38,56.499,-132.317,56.488,-132.206,56.388,-132.067,56.244,-132.112,56.109]],[[-130.979,55.489,-131.014,55.379,-131.083,55.267,-131.188,55.206,-131.262,55.22,-131.316,55.269,-131.367,55.266,-131.421,55.276,-131.451,55.316,-131.422,55.368,-131.448,55.409,-131.475,55.373,-131.522,55.341,-131.641,55.299,-131.724,55.218,-131.762,55.166,-131.811,55.223,-131.842,55.359,-131.846,55.416,-131.759,55.503,-131.648,55.586,-131.625,55.832,-131.269,55.955,-131.236,55.949,-131.121,55.857,-130.998,55.728,-130.966,55.67,-130.965,55.568,-130.979,55.489]],[[-133.366,57.004,-133.3,56.972,-133.264,57.005,-133.196,57.003,-133.071,56.974,-132.996,56.93,-132.954,56.88,-132.951,56.85,-132.963,56.783,-132.954,56.713,-132.959,56.677,-132.976,56.647,-133.004,56.624,-133.035,56.621,-133.132,56.683,-133.244,56.796,-133.329,56.83,-133.332,56.819,-133.309,56.786,-133.24,56.726,-133.227,56.689,-133.178,56.645,-133.157,56.611,-133.144,56.567,-133.145,56.528,-133.158,56.495,-133.181,56.474,-133.213,56.465,-133.383,56.474,-133.484,56.452,-133.603,56.464,-133.631,56.484,-133.649,56.517,-133.658,56.596,-133.688,56.71,-133.681,56.798,-133.758,56.877,-133.823,56.924,-133.917,56.967,-133.979,57.01,-133.962,57.043,-133.866,57.069,-133.708,57.063,-133.366,57.004]],[[-132.862,54.894,-132.838,54.881,-132.813,54.89,-132.772,54.926,-132.701,54.919,-132.649,54.907,-132.617,54.892,-132.634,54.84,-132.647,54.756,-132.677,54.726,-132.706,54.684,-132.807,54.709,-132.89,54.763,-133.009,54.855,-133.075,54.921,-133.081,54.949,-133.123,54.97,-133.205,55.084,-133.251,55.175,-133.325,55.185,-133.418,55.211,-133.454,55.26,-133.429,55.304,-133.297,55.326,-133.097,55.214,-133.067,55.166,-132.996,55.111,-132.982,55.033,-132.946,55.003,-132.862,54.894]],[[-146.394,60.45,-146.372,60.422,-146.18,60.429,-146.124,60.424,-146.102,60.411,-146.128,60.393,-146.202,60.368,-146.419,60.325,-146.595,60.268,-146.618,60.274,-146.65,60.336,-146.683,60.361,-146.703,60.396,-146.703,60.409,-146.67,60.433,-146.606,60.468,-146.56,60.481,-146.394,60.45]],[[-147.736,59.813,-147.846,59.799,-147.872,59.828,-147.814,59.902,-147.768,59.944,-147.734,59.954,-147.607,60.037,-147.466,60.097,-147.337,60.185,-147.205,60.311,-147.181,60.358,-147.12,60.363,-147.02,60.332,-146.958,60.289,-146.987,60.254,-147.318,60.075,-147.346,60.052,-147.377,59.991,-147.404,59.97,-147.448,59.96,-147.479,59.934,-147.499,59.89,-147.54,59.868,-147.602,59.866,-147.645,59.854,-147.669,59.832,-147.736,59.813]],[[-147.658,60.45,-147.659,60.424,-147.69,60.399,-147.66,60.352,-147.712,60.273,-147.732,60.222,-147.76,60.19,-147.788,60.178,-147.816,60.185,-147.822,60.203,-147.805,60.231,-147.871,60.23,-147.891,60.299,-147.855,60.321,-147.842,60.351,-147.838,60.371,-147.795,60.46,-147.779,60.466,-147.774,60.445,-147.76,60.439,-147.737,60.447,-147.703,60.487,-147.689,60.491,-147.658,60.45]],[[-147.931,60.826,-148.057,60.818,-148.115,60.831,-148.124,60.844,-148.1,60.895,-148.102,60.916,-148.038,60.924,-147.964,60.9,-147.943,60.875,-147.931,60.826]],[[-153.007,57.125,-153.134,57.093,-153.157,57.094,-153.235,57.029,-153.295,57,-153.375,57.052,-153.354,57.132,-153.285,57.185,-152.935,57.167,-152.908,57.152,-152.908,57.14,-152.933,57.129,-153.007,57.125]],[[-152.486,58.485,-152.516,58.479,-152.589,58.509,-152.637,58.542,-152.605,58.566,-152.463,58.619,-152.396,58.619,-152.368,58.611,-152.357,58.595,-152.362,58.571,-152.393,58.541,-152.486,58.485]],[[-153.241,57.85,-153.269,57.822,-153.295,57.829,-153.351,57.862,-153.465,57.909,-153.517,57.942,-153.52,57.956,-153.481,57.971,-153.347,57.933,-153.29,57.898,-153.241,57.85]],[[-155.566,55.821,-155.605,55.79,-155.681,55.792,-155.723,55.802,-155.737,55.83,-155.621,55.913,-155.594,55.924,-155.573,55.921,-155.564,55.887,-155.566,55.821]],[[-154.683,56.436,-154.751,56.412,-154.774,56.42,-154.777,56.44,-154.761,56.471,-154.729,56.502,-154.624,56.561,-154.518,56.601,-154.463,56.598,-154.445,56.573,-154.511,56.521,-154.683,56.436]],[[-154.209,56.515,-154.258,56.513,-154.332,56.539,-154.322,56.571,-154.217,56.609,-154.11,56.603,-154.102,56.582,-154.107,56.558,-154.116,56.544,-154.15,56.53,-154.209,56.515]],[[-160.685,55.315,-160.67,55.314,-160.639,55.322,-160.574,55.378,-160.553,55.381,-160.552,55.363,-160.583,55.308,-160.531,55.233,-160.483,55.197,-160.488,55.185,-160.609,55.159,-160.702,55.178,-160.751,55.171,-160.795,55.145,-160.825,55.174,-160.847,55.311,-160.84,55.335,-160.789,55.383,-160.724,55.405,-160.696,55.398,-160.672,55.379,-160.666,55.359,-160.685,55.315]],[[-162.298,54.847,-162.322,54.842,-162.391,54.873,-162.416,54.896,-162.434,54.932,-162.294,54.983,-162.265,54.983,-162.238,54.955,-162.234,54.932,-162.273,54.867,-162.298,54.847]],[[-162.554,54.401,-162.641,54.38,-162.733,54.402,-162.812,54.444,-162.821,54.495,-162.645,54.462,-162.608,54.447,-162.554,54.401]],[[-159.873,55.129,-159.934,55.107,-159.953,55.079,-159.999,55.067,-160.038,55.044,-160.17,54.942,-160.227,54.923,-160.164,55.01,-160.154,55.038,-160.152,55.057,-160.172,55.123,-160.134,55.12,-160.102,55.134,-160.039,55.193,-159.982,55.198,-159.92,55.268,-159.887,55.273,-159.871,55.264,-159.898,55.221,-159.839,55.182,-159.854,55.145,-159.873,55.129]],[[-165.842,54.071,-165.879,54.053,-165.91,54.049,-165.933,54.059,-166.036,54.047,-166.057,54.054,-166.103,54.114,-166.106,54.145,-166.088,54.169,-166.041,54.191,-165.966,54.211,-165.893,54.207,-165.764,54.152,-165.704,54.12,-165.693,54.1,-165.738,54.081,-165.842,54.071]],[[-165.561,54.137,-165.605,54.129,-165.615,54.14,-165.621,54.184,-165.654,54.253,-165.59,54.279,-165.551,54.285,-165.534,54.274,-165.488,54.222,-165.442,54.208,-165.408,54.197,-165.468,54.181,-165.561,54.137]],[[-160.919,58.577,-160.992,58.561,-161.07,58.569,-161.131,58.668,-161.085,58.671,-160.986,58.736,-160.769,58.789,-160.715,58.795,-160.919,58.577]],[[-172.742,60.457,-172.526,60.392,-172.387,60.398,-172.278,60.344,-172.232,60.299,-172.397,60.331,-172.636,60.329,-172.958,60.463,-173.074,60.493,-173.048,60.568,-172.924,60.607,-172.86,60.506,-172.742,60.457]],[[-170.161,57.184,-170.264,57.137,-170.358,57.154,-170.386,57.189,-170.387,57.203,-170.116,57.242,-170.161,57.184]],[[-169.692,52.847,-169.708,52.807,-169.723,52.792,-169.877,52.814,-169.981,52.806,-169.992,52.83,-169.983,52.851,-169.821,52.883,-169.755,52.884,-169.711,52.867,-169.692,52.847]],[[-170.733,52.581,-170.797,52.55,-170.816,52.562,-170.827,52.601,-170.791,52.631,-170.682,52.698,-170.608,52.685,-170.585,52.668,-170.587,52.642,-170.614,52.61,-170.649,52.593,-170.692,52.593,-170.733,52.581]],[[-172.465,52.272,-172.539,52.257,-172.62,52.273,-172.582,52.326,-172.544,52.354,-172.47,52.388,-172.383,52.373,-172.314,52.33,-172.465,52.272]],[[-169.755,56.635,-169.624,56.615,-169.55,56.628,-169.486,56.618,-169.474,56.594,-169.587,56.542,-169.633,56.546,-169.766,56.608,-169.755,56.635]],[[-176.287,51.792,-176.35,51.733,-176.396,51.76,-176.414,51.841,-176.379,51.861,-176.28,51.803,-176.287,51.792]],[[-176.022,52.002,-176.045,51.973,-176.143,52.004,-176.178,52.03,-176.185,52.056,-176.156,52.099,-176.077,52.1,-176.031,52.082,-175.988,52.049,-175.975,52.029,-176.022,52.002]],[[-177.148,51.717,-177.177,51.704,-177.23,51.694,-177.382,51.705,-177.475,51.701,-177.578,51.694,-177.655,51.677,-177.67,51.701,-177.668,51.721,-177.335,51.776,-177.257,51.805,-177.21,51.841,-177.166,51.909,-177.131,51.93,-177.11,51.929,-177.063,51.902,-177.08,51.867,-177.121,51.836,-177.135,51.807,-177.148,51.717]],[[178.575,51.916,178.512,51.899,178.478,51.943,178.475,51.968,178.509,51.995,178.571,51.978,178.607,51.953,178.575,51.916]],[[179.452,51.373,179.278,51.372,178.926,51.535,178.747,51.587,178.648,51.644,178.692,51.656,178.908,51.616,179.084,51.528,179.182,51.47,179.294,51.421,179.416,51.401,179.452,51.373]],[[173.723,52.36,173.658,52.357,173.616,52.391,173.402,52.405,173.425,52.438,173.517,52.451,173.658,52.504,173.776,52.495,173.745,52.447,173.723,52.36]],[[-134.97,57.351,-134.885,57.242,-134.823,57.157,-134.769,57.054,-134.677,56.842,-134.634,56.762,-134.621,56.718,-134.611,56.603,-134.624,56.579,-134.652,56.556,-134.657,56.523,-134.632,56.436,-134.63,56.302,-134.654,56.227,-134.682,56.216,-134.75,56.241,-134.806,56.281,-134.848,56.323,-134.95,56.457,-134.981,56.519,-134.982,56.564,-134.967,56.596,-134.933,56.616,-134.875,56.67,-134.883,56.679,-134.928,56.667,-135.018,56.66,-135.097,56.703,-135.159,56.725,-135.147,56.802,-135.163,56.824,-135.285,56.8,-135.331,56.822,-135.341,56.851,-135.338,56.894,-135.315,56.932,-135.2,57.027,-135.211,57.045,-135.267,57.049,-135.341,57.082,-135.375,57.188,-135.455,57.249,-135.502,57.244,-135.609,57.071,-135.662,57.034,-135.812,57.01,-135.782,57.058,-135.768,57.1,-135.821,57.23,-135.823,57.28,-135.787,57.317,-135.681,57.333,-135.625,57.354,-135.581,57.39,-135.57,57.425,-135.487,57.517,-135.449,57.534,-135.346,57.533,-135.131,57.432,-135.065,57.417,-134.97,57.351]],[[-134.68,58.162,-134.426,58.139,-134.24,58.144,-134.07,57.995,-133.966,57.874,-133.904,57.789,-133.869,57.708,-133.823,57.629,-133.827,57.618,-133.925,57.671,-133.996,57.778,-134.032,57.821,-134.067,57.84,-134.105,57.879,-134.178,57.982,-134.18,58.011,-134.213,58.038,-134.25,58.049,-134.292,58.045,-134.307,58.034,-134.3,57.963,-134.267,57.885,-134.084,57.712,-133.961,57.614,-133.937,57.582,-133.921,57.492,-133.974,57.451,-133.909,57.369,-133.911,57.353,-133.925,57.337,-134.1,57.3,-134.26,57.147,-134.435,57.057,-134.516,57.043,-134.555,57.058,-134.592,57.092,-134.613,57.138,-134.62,57.196,-134.576,57.232,-134.489,57.42,-134.487,57.482,-134.595,57.568,-134.66,57.638,-134.695,57.736,-134.754,57.995,-134.781,58.078,-134.82,58.147,-134.87,58.202,-134.908,58.263,-134.933,58.329,-134.923,58.355,-134.837,58.32,-134.733,58.225,-134.68,58.162]],[[-135.73,58.244,-135.587,58.147,-135.586,58.124,-135.615,58.057,-135.693,58.039,-135.671,58.012,-135.613,57.992,-135.572,58.009,-135.421,58.102,-135.375,58.122,-135.347,58.124,-135.163,58.096,-135.002,58.051,-134.955,58.015,-134.928,57.953,-134.971,57.817,-135.103,57.794,-135.165,57.796,-135.231,57.816,-135.338,57.769,-135.25,57.733,-134.979,57.724,-134.897,57.648,-134.873,57.589,-134.931,57.481,-135.085,57.511,-135.22,57.574,-135.498,57.662,-135.564,57.666,-135.609,57.651,-135.621,57.597,-135.618,57.48,-135.692,57.42,-135.911,57.447,-135.997,57.535,-136.077,57.675,-136.378,57.84,-136.46,57.873,-136.569,57.972,-136.525,58.051,-136.512,58.096,-136.454,58.108,-136.37,58.143,-136.322,58.219,-136.246,58.157,-136.144,58.098,-136.142,58.154,-136.094,58.198,-135.994,58.197,-135.947,58.206,-135.882,58.247,-135.787,58.269,-135.73,58.244]],[[-133.566,56.339,-133.377,56.318,-133.203,56.32,-133.144,56.279,-133.104,56.235,-133.082,56.194,-133.075,56.156,-133.08,56.129,-133.101,56.1,-133.097,56.09,-132.758,55.995,-132.598,55.895,-132.534,55.842,-132.497,55.798,-132.43,55.687,-132.289,55.558,-132.215,55.519,-132.173,55.481,-132.196,55.479,-132.296,55.507,-132.511,55.594,-132.529,55.59,-132.548,55.544,-132.582,55.503,-132.631,55.473,-132.592,55.464,-132.418,55.483,-132.272,55.399,-132.215,55.384,-132.16,55.323,-132.158,55.3,-132.19,55.255,-132.215,55.237,-132.207,55.224,-132.166,55.218,-132.005,55.231,-131.976,55.209,-132,55.034,-131.978,54.969,-131.978,54.94,-131.997,54.901,-131.997,54.869,-131.983,54.835,-131.981,54.805,-132.022,54.726,-132.065,54.713,-132.134,54.713,-132.189,54.735,-132.266,54.802,-132.341,54.907,-132.37,54.922,-132.469,54.938,-132.486,54.95,-132.549,54.953,-132.594,54.996,-132.588,55.052,-132.627,55.11,-132.622,55.136,-132.665,55.147,-132.702,55.131,-132.683,55.074,-132.704,55.03,-132.782,55.048,-132.913,55.188,-133.061,55.301,-133.119,55.328,-133.103,55.36,-133.03,55.378,-132.971,55.376,-132.959,55.396,-133.082,55.504,-133.078,55.535,-133.033,55.59,-133.09,55.613,-133.244,55.595,-133.298,55.607,-133.343,55.651,-133.369,55.689,-133.503,55.696,-133.553,55.691,-133.64,55.749,-133.68,55.785,-133.664,55.804,-133.584,55.837,-133.537,55.832,-133.447,55.797,-133.412,55.798,-133.322,55.845,-133.308,55.886,-133.242,55.921,-133.252,55.957,-133.289,56.019,-133.371,56.036,-133.539,55.999,-133.684,55.943,-133.743,55.965,-133.755,55.999,-133.599,56.094,-133.531,56.146,-133.544,56.177,-133.594,56.216,-133.599,56.316,-133.566,56.339]],[[-133.99,56.845,-133.925,56.776,-133.831,56.781,-133.778,56.729,-133.738,56.65,-133.767,56.6,-133.809,56.611,-133.855,56.582,-133.884,56.485,-133.87,56.389,-133.885,56.292,-133.939,56.194,-133.95,56.128,-133.971,56.108,-133.994,56.101,-134.024,56.119,-134.067,56.133,-134.122,56.077,-134.19,56.077,-134.245,56.203,-134.195,56.414,-134.084,56.456,-134.15,56.513,-134.29,56.58,-134.278,56.617,-134.384,56.724,-134.391,56.749,-134.374,56.839,-134.274,56.918,-134.143,56.932,-134.052,56.898,-134.001,56.869,-133.99,56.845]],[[-152.417,58.36,-152.381,58.352,-152.343,58.412,-152.316,58.413,-152.198,58.363,-152.125,58.374,-152.079,58.312,-152.037,58.307,-151.998,58.314,-151.974,58.31,-151.983,58.244,-152.069,58.178,-152.109,58.161,-152.165,58.178,-152.187,58.185,-152.224,58.214,-152.252,58.251,-152.268,58.252,-152.334,58.208,-152.333,58.187,-152.305,58.154,-152.309,58.134,-152.381,58.124,-152.452,58.129,-152.538,58.101,-152.558,58.119,-152.571,58.168,-152.598,58.163,-152.639,58.102,-152.683,58.063,-152.764,58.031,-152.782,58.016,-152.841,58.014,-152.928,57.994,-152.983,57.997,-153.305,58.063,-153.381,58.087,-153.116,58.239,-152.976,58.297,-152.895,58.294,-152.815,58.276,-152.772,58.279,-152.769,58.346,-152.844,58.396,-152.841,58.416,-152.675,58.451,-152.612,58.446,-152.544,58.428,-152.478,58.4,-152.417,58.36]],[[-167.964,53.345,-168.271,53.238,-168.37,53.16,-168.446,53.084,-168.506,53.043,-168.549,53.036,-168.597,53.016,-168.699,52.963,-168.741,52.957,-169.066,52.834,-169.089,52.832,-169.073,52.864,-168.974,52.91,-168.909,52.951,-168.836,53.02,-168.796,53.045,-168.783,53.079,-168.778,53.149,-168.76,53.175,-168.69,53.227,-168.639,53.256,-168.572,53.266,-168.437,53.257,-168.38,53.283,-168.363,53.304,-168.397,53.322,-168.405,53.354,-168.396,53.409,-168.357,53.458,-168.288,53.5,-168.193,53.533,-168.073,53.557,-167.986,53.558,-167.828,53.508,-167.805,53.485,-167.843,53.435,-167.865,53.387,-167.964,53.345]],[[-166.615,53.901,-166.572,53.853,-166.497,53.884,-166.443,53.925,-166.4,53.978,-166.372,53.999,-166.336,53.971,-166.231,53.933,-166.319,53.874,-166.489,53.785,-166.546,53.726,-166.549,53.701,-166.385,53.721,-166.339,53.718,-166.309,53.698,-166.355,53.674,-166.444,53.652,-166.522,53.61,-166.702,53.537,-166.77,53.476,-166.851,53.453,-166.961,53.447,-167.154,53.408,-167.271,53.371,-167.3,53.35,-167.337,53.341,-167.381,53.342,-167.429,53.326,-167.48,53.292,-167.522,53.276,-167.592,53.273,-167.629,53.259,-167.669,53.26,-167.781,53.3,-167.809,53.324,-167.71,53.371,-167.639,53.387,-167.53,53.394,-167.424,53.437,-167.204,53.495,-167.136,53.526,-167.092,53.636,-167.042,53.655,-167.016,53.698,-166.894,53.697,-166.838,53.648,-166.819,53.641,-166.809,53.646,-166.804,53.685,-166.741,53.713,-166.777,53.733,-166.89,53.759,-166.973,53.771,-167.027,53.769,-167.071,53.783,-167.106,53.813,-167.121,53.843,-167.118,53.873,-167.09,53.906,-167.038,53.942,-166.978,53.963,-166.849,53.978,-166.734,54.002,-166.673,54.006,-166.627,53.996,-166.615,53.901]],[[-173.553,52.136,-173.357,52.096,-173.113,52.1,-173.024,52.091,-173.023,52.079,-173.179,52.063,-173.232,52.068,-173.368,52.046,-173.461,52.042,-173.673,52.063,-173.836,52.048,-173.879,52.054,-173.93,52.072,-173.99,52.104,-173.992,52.123,-173.939,52.131,-173.794,52.104,-173.779,52.118,-173.657,52.144,-173.553,52.136]],[[-174.677,52.035,-175.214,51.994,-175.296,52.022,-175.214,52.038,-175.118,52.047,-174.916,52.094,-174.668,52.135,-174.474,52.184,-174.306,52.216,-174.259,52.269,-174.406,52.296,-174.436,52.317,-174.365,52.342,-174.307,52.378,-174.169,52.42,-174.046,52.367,-174.018,52.332,-174.03,52.29,-174.055,52.246,-174.163,52.223,-174.179,52.2,-174.121,52.135,-174.344,52.078,-174.677,52.035]],[[-176.593,51.867,-176.588,51.833,-176.473,51.837,-176.437,51.82,-176.437,51.754,-176.452,51.736,-176.47,51.731,-176.511,51.746,-176.558,51.712,-176.771,51.63,-176.837,51.676,-176.962,51.604,-176.874,51.79,-176.774,51.819,-176.736,51.84,-176.745,51.895,-176.698,51.986,-176.597,51.982,-176.55,51.944,-176.552,51.92,-176.593,51.867]],[[-177.879,51.65,-177.901,51.616,-177.925,51.617,-178.059,51.673,-178.078,51.691,-178,51.717,-177.977,51.738,-177.986,51.764,-178.045,51.801,-178.153,51.848,-178.195,51.882,-178.168,51.903,-178.117,51.916,-177.954,51.918,-177.866,51.86,-177.8,51.84,-177.644,51.826,-177.725,51.802,-177.771,51.778,-177.827,51.686,-177.879,51.65]],[[179.728,51.905,179.645,51.88,179.55,51.894,179.498,51.933,179.504,51.98,179.627,52.03,179.78,51.967,179.728,51.905]],[[177.415,51.883,177.329,51.841,177.261,51.884,177.25,51.903,177.381,51.976,177.478,51.992,177.521,52.018,177.564,52.11,177.637,52.114,177.67,52.103,177.653,52.06,177.596,51.994,177.594,51.948,177.415,51.883]],[[172.812,53.013,172.984,52.98,173.102,52.996,173.252,52.943,173.436,52.852,173.395,52.835,173.348,52.825,173.303,52.826,173.159,52.811,173.08,52.814,172.935,52.752,172.776,52.797,172.722,52.886,172.595,52.907,172.495,52.938,172.678,53.008,172.812,53.013]],[[-155.581,19.012,-155.626,18.964,-155.681,18.968,-155.881,19.071,-155.906,19.126,-155.891,19.383,-155.966,19.591,-156.049,19.75,-155.988,19.832,-155.909,19.895,-155.82,20.014,-155.893,20.167,-155.874,20.26,-155.832,20.276,-155.622,20.163,-155.199,19.994,-155.086,19.876,-155.066,19.748,-154.989,19.732,-154.953,19.645,-154.841,19.568,-154.804,19.524,-154.85,19.454,-155.053,19.319,-155.31,19.26,-155.535,19.109,-155.581,19.012]],[[-157.214,21.215,-157.002,21.188,-156.952,21.2,-156.917,21.177,-156.742,21.164,-156.712,21.155,-156.748,21.104,-156.86,21.056,-157.021,21.098,-157.29,21.113,-157.279,21.152,-157.254,21.181,-157.25,21.23,-157.214,21.215]],[[-156.487,20.933,-156.461,20.915,-156.354,20.941,-156.278,20.951,-156.148,20.885,-156.104,20.84,-156.019,20.792,-155.99,20.757,-156.014,20.715,-156.107,20.645,-156.235,20.629,-156.31,20.599,-156.409,20.605,-156.438,20.618,-156.449,20.706,-156.48,20.801,-156.544,20.79,-156.615,20.822,-156.69,20.901,-156.698,20.949,-156.657,21.025,-156.585,21.034,-156.532,20.993,-156.487,20.933]],[[-157.799,21.457,-157.765,21.451,-157.721,21.458,-157.706,21.378,-157.654,21.334,-157.635,21.308,-157.691,21.28,-157.799,21.269,-157.849,21.291,-157.902,21.341,-157.958,21.327,-157.968,21.367,-157.978,21.379,-158.017,21.368,-157.981,21.316,-158.079,21.312,-158.11,21.319,-158.138,21.377,-158.239,21.489,-158.239,21.533,-158.273,21.585,-158.123,21.6,-158.02,21.692,-157.962,21.701,-157.852,21.553,-157.854,21.512,-157.83,21.471,-157.799,21.457]],[[-159.373,21.932,-159.461,21.876,-159.512,21.9,-159.609,21.91,-159.646,21.952,-159.748,21.99,-159.789,22.042,-159.727,22.14,-159.579,22.223,-159.352,22.22,-159.305,22.154,-159.301,22.105,-159.33,22.051,-159.344,21.974,-159.373,21.932]],[[-160.18,21.841,-160.2,21.797,-160.235,21.804,-160.243,21.843,-160.221,21.897,-160.164,21.944,-160.101,22.015,-160.049,22.005,-160.077,21.958,-160.08,21.907,-160.153,21.879,-160.18,21.841]],[[-156.85,20.773,-156.909,20.744,-156.973,20.758,-156.988,20.826,-157.051,20.912,-156.942,20.93,-156.881,20.905,-156.848,20.878,-156.809,20.831,-156.85,20.773]],[[-74.709,45.004,-74.663,45.004,-74.43,45.004,-74.014,45.005,-73.598,45.005,-73.182,45.006,-72.766,45.006,-72.35,45.007,-71.934,45.007,-71.518,45.008,-71.419,45.2,-71.327,45.29,-71.202,45.26,-71.135,45.263,-71.06,45.309,-71,45.337,-70.96,45.333,-70.926,45.291,-70.898,45.262,-70.865,45.271,-70.837,45.311,-70.838,45.366,-70.799,45.405,-70.753,45.411,-70.711,45.409,-70.69,45.428,-70.692,45.455,-70.707,45.499,-70.702,45.551,-70.596,45.644,-70.467,45.707,-70.421,45.738,-70.408,45.802,-70.333,45.868,-70.296,45.906,-70.287,45.939,-70.306,45.98,-70.304,46.057,-70.279,46.15,-70.248,46.251,-70.18,46.342,-70.067,46.441,-70.038,46.571,-70.008,46.709,-69.872,46.843,-69.718,46.995,-69.63,47.081,-69.471,47.239,-69.359,47.351,-69.302,47.402,-69.243,47.463,-69.146,47.445,-69.05,47.427,-69.064,47.338,-69.049,47.274,-69.003,47.236,-68.937,47.211,-68.887,47.203,-68.829,47.203,-68.669,47.253,-68.48,47.286,-68.377,47.316,-68.358,47.345,-68.311,47.354,-68.235,47.346,-68.097,47.275,-67.935,47.168,-67.807,47.083,-67.803,46.936,-67.8,46.78,-67.798,46.616,-67.796,46.498,-67.793,46.337,-67.79,46.209,-67.786,46.042,-67.785,45.953,-67.767,45.927,-67.778,45.892,-67.782,45.874,-67.781,45.86,-67.774,45.843,-67.775,45.818,-67.792,45.796,-67.8,45.77,-67.802,45.728,-67.785,45.702,-67.755,45.686,-67.731,45.686,-67.699,45.671,-67.658,45.644,-67.596,45.621,-67.531,45.613,-67.487,45.618,-67.433,45.603,-67.414,45.566,-67.424,45.53,-67.455,45.514,-67.488,45.501,-67.494,45.474,-67.477,45.446,-67.454,45.421,-67.428,45.378,-67.439,45.34,-67.462,45.309,-67.473,45.276,-67.453,45.248,-67.4,45.21,-67.367,45.174,-67.315,45.154,-67.291,45.168,-67.271,45.187,-67.25,45.201,-67.213,45.193,-67.171,45.182,-67.125,45.169,-67.13,45.139,-67.102,45.088,-67.08,44.989,-67.114,44.944,-67.107,44.885,-67.014,44.868,-66.991,44.85,-66.987,44.828,-67.191,44.676,-67.364,44.697,-67.458,44.657,-67.556,44.645,-67.599,44.577,-67.653,44.562,-67.727,44.567,-67.79,44.586,-67.839,44.576,-67.907,44.454,-67.963,44.464,-67.985,44.42,-68.014,44.401,-68.057,44.384,-68.094,44.439,-68.117,44.491,-68.152,44.502,-68.198,44.515,-68.246,44.515,-68.277,44.507,-68.317,44.474,-68.374,44.445,-68.417,44.469,-68.451,44.508,-68.479,44.446,-68.521,44.38,-68.514,44.304,-68.533,44.259,-68.572,44.271,-68.612,44.311,-68.723,44.342,-68.812,44.339,-68.794,44.382,-68.71,44.443,-68.736,44.454,-68.777,44.446,-68.795,44.454,-68.766,44.51,-68.763,44.571,-68.8,44.549,-68.847,44.485,-68.961,44.434,-68.956,44.348,-69.064,44.172,-69.068,44.098,-69.137,44.038,-69.226,43.986,-69.345,44.001,-69.435,43.956,-69.481,43.905,-69.521,43.897,-69.542,43.963,-69.557,43.983,-69.59,43.887,-69.624,43.881,-69.637,43.949,-69.653,43.994,-69.699,43.955,-69.73,43.852,-69.762,43.861,-69.772,43.899,-69.795,43.911,-69.803,43.867,-69.792,43.805,-69.808,43.772,-69.84,43.79,-69.873,43.82,-69.926,43.797,-69.974,43.788,-69.975,43.818,-69.965,43.855,-70.062,43.835,-70.179,43.766,-70.269,43.672,-70.238,43.656,-70.203,43.626,-70.36,43.48,-70.521,43.349,-70.642,43.134,-70.691,43.109,-70.733,43.07,-70.778,42.941,-70.829,42.825,-70.8,42.774,-70.781,42.721,-70.736,42.669,-70.697,42.665,-70.655,42.674,-70.624,42.672,-70.604,42.65,-70.613,42.623,-70.661,42.617,-70.752,42.57,-70.831,42.553,-70.871,42.497,-70.93,42.432,-71.046,42.331,-70.997,42.3,-70.818,42.265,-70.738,42.229,-70.618,42.04,-70.645,42.022,-70.656,41.987,-70.549,41.939,-70.515,41.803,-70.427,41.757,-70.295,41.729,-70.135,41.77,-70.001,41.826,-70.006,41.872,-70.09,41.98,-70.11,42.03,-70.173,42.063,-70.196,42.035,-70.237,42.071,-70.241,42.091,-70.204,42.101,-70.16,42.097,-70.109,42.078,-69.978,41.961,-69.942,41.808,-69.934,41.71,-69.949,41.677,-69.987,41.684,-70.06,41.677,-70.405,41.627,-70.481,41.582,-70.657,41.534,-70.668,41.558,-70.655,41.608,-70.666,41.71,-70.701,41.715,-70.974,41.549,-71.08,41.538,-71.169,41.489,-71.188,41.516,-71.204,41.641,-71.149,41.746,-71.178,41.744,-71.271,41.681,-71.311,41.72,-71.331,41.762,-71.359,41.786,-71.39,41.795,-71.364,41.703,-71.427,41.633,-71.444,41.454,-71.523,41.379,-71.769,41.331,-71.93,41.341,-72.074,41.326,-72.265,41.292,-72.371,41.312,-72.479,41.276,-72.847,41.266,-72.925,41.285,-73.024,41.216,-73.182,41.176,-73.583,41.022,-73.671,40.966,-73.779,40.878,-73.851,40.831,-73.911,40.816,-73.947,40.777,-73.987,40.751,-73.949,40.839,-73.907,40.912,-73.872,41.055,-73.882,41.171,-73.925,41.218,-73.97,41.25,-73.918,41.136,-73.909,40.996,-73.927,40.914,-74.025,40.756,-74.067,40.72,-74.116,40.687,-74.153,40.673,-74.187,40.648,-74.227,40.608,-74.264,40.529,-74.242,40.456,-74.05,40.43,-73.998,40.452,-73.972,40.4,-73.958,40.328,-73.972,40.251,-74.004,40.171,-74.028,40.073,-74.049,39.923,-74.08,39.788,-74.084,39.829,-74.065,39.993,-74.096,39.976,-74.118,39.938,-74.176,39.727,-74.257,39.614,-74.331,39.536,-74.407,39.549,-74.39,39.487,-74.411,39.455,-74.429,39.387,-74.474,39.343,-74.517,39.347,-74.579,39.316,-74.603,39.293,-74.605,39.248,-74.646,39.208,-74.794,39.002,-74.923,38.941,-74.954,38.95,-74.92,39.047,-74.897,39.145,-74.975,39.188,-75.05,39.211,-75.136,39.208,-75.231,39.284,-75.353,39.34,-75.524,39.49,-75.519,39.532,-75.524,39.602,-75.472,39.712,-75.422,39.79,-75.353,39.83,-75.154,39.871,-75.104,39.932,-75.074,39.983,-75.173,39.895,-75.321,39.865,-75.401,39.832,-75.464,39.781,-75.502,39.717,-75.588,39.641,-75.582,39.589,-75.567,39.553,-75.574,39.477,-75.52,39.403,-75.413,39.281,-75.392,39.093,-75.31,38.967,-75.185,38.819,-75.089,38.778,-75.084,38.723,-75.128,38.632,-75.187,38.591,-75.111,38.599,-75.073,38.579,-75.036,38.503,-75.039,38.426,-75.051,38.383,-75.074,38.366,-75.073,38.41,-75.09,38.425,-75.117,38.406,-75.134,38.384,-75.142,38.298,-75.16,38.255,-75.225,38.242,-75.292,38.129,-75.354,38.065,-75.596,37.631,-75.587,37.559,-75.632,37.535,-75.699,37.516,-75.767,37.473,-75.812,37.425,-75.854,37.297,-75.934,37.152,-75.985,37.212,-75.997,37.264,-75.975,37.398,-75.888,37.619,-75.792,37.756,-75.719,37.821,-75.659,37.954,-75.735,37.974,-75.851,37.972,-75.829,38.033,-75.795,38.087,-75.856,38.14,-75.891,38.147,-75.928,38.169,-75.885,38.214,-75.864,38.261,-75.877,38.319,-75.859,38.362,-75.889,38.356,-75.937,38.31,-75.967,38.291,-75.986,38.332,-76.007,38.323,-76.02,38.295,-76.051,38.28,-76.117,38.318,-76.212,38.361,-76.265,38.436,-76.295,38.495,-76.264,38.6,-76.198,38.619,-76.113,38.602,-76.001,38.602,-76.017,38.625,-76.057,38.621,-76.175,38.707,-76.213,38.758,-76.278,38.772,-76.308,38.723,-76.341,38.71,-76.3,38.818,-76.247,38.823,-76.168,38.853,-76.191,38.916,-76.241,38.943,-76.331,38.909,-76.33,38.953,-76.313,39.009,-76.245,39.009,-76.186,38.991,-76.135,39.082,-76.133,39.123,-76.217,39.064,-76.236,39.192,-76.153,39.315,-76.074,39.369,-75.976,39.367,-75.876,39.376,-75.939,39.399,-76.003,39.411,-75.955,39.46,-75.913,39.468,-75.873,39.511,-75.97,39.505,-75.959,39.585,-76.006,39.569,-76.063,39.561,-76.085,39.527,-76.081,39.47,-76.097,39.433,-76.141,39.403,-76.216,39.38,-76.223,39.42,-76.248,39.439,-76.257,39.352,-76.276,39.323,-76.331,39.404,-76.347,39.388,-76.345,39.365,-76.359,39.325,-76.406,39.304,-76.403,39.253,-76.421,39.225,-76.57,39.269,-76.574,39.254,-76.489,39.159,-76.428,39.126,-76.42,39.074,-76.473,39.031,-76.546,39.068,-76.559,39.065,-76.519,39.001,-76.494,38.945,-76.52,38.898,-76.516,38.841,-76.521,38.788,-76.537,38.743,-76.501,38.532,-76.458,38.475,-76.416,38.42,-76.394,38.369,-76.439,38.362,-76.51,38.404,-76.572,38.436,-76.647,38.539,-76.659,38.58,-76.677,38.612,-76.669,38.538,-76.642,38.454,-76.409,38.268,-76.366,38.197,-76.333,38.141,-76.341,38.087,-76.402,38.125,-76.454,38.174,-76.594,38.228,-76.769,38.263,-76.868,38.39,-76.868,38.337,-76.89,38.292,-76.95,38.347,-76.988,38.394,-77.001,38.445,-77.077,38.442,-77.156,38.397,-77.233,38.408,-77.242,38.495,-77.221,38.541,-77.135,38.65,-77.054,38.706,-77.018,38.778,-77.03,38.889,-77.046,38.776,-77.092,38.72,-77.165,38.677,-77.26,38.6,-77.284,38.529,-77.314,38.397,-77.273,38.352,-77.232,38.34,-77.11,38.37,-77.047,38.357,-76.906,38.197,-76.645,38.134,-76.55,38.094,-76.472,38.011,-76.355,37.963,-76.264,37.894,-76.262,37.848,-76.293,37.794,-76.306,37.722,-76.344,37.676,-76.437,37.67,-76.492,37.682,-76.793,37.938,-76.829,37.962,-76.94,38.095,-77.071,38.167,-77.111,38.166,-76.925,38.033,-76.849,37.94,-76.715,37.81,-76.62,37.755,-76.549,37.669,-76.484,37.629,-76.306,37.571,-76.368,37.53,-76.269,37.495,-76.254,37.431,-76.263,37.357,-76.401,37.386,-76.405,37.332,-76.393,37.3,-76.454,37.274,-76.538,37.309,-76.758,37.505,-76.756,37.479,-76.738,37.449,-76.611,37.323,-76.497,37.247,-76.401,37.213,-76.327,37.149,-76.301,37.111,-76.283,37.053,-76.338,37.013,-76.401,36.991,-76.462,37.031,-76.507,37.072,-76.602,37.143,-76.631,37.222,-76.704,37.218,-77.007,37.318,-77.251,37.329,-77.227,37.309,-77.196,37.296,-77.002,37.271,-76.925,37.225,-76.765,37.184,-76.672,37.173,-76.634,37.047,-76.505,36.961,-76.488,36.897,-76.4,36.89,-76.244,36.953,-76.144,36.931,-75.999,36.913,-75.966,36.862,-75.942,36.766,-75.89,36.657,-75.758,36.229,-75.559,35.879,-75.534,35.819,-75.58,35.872,-75.728,36.104,-75.81,36.271,-75.894,36.567,-75.918,36.633,-75.946,36.659,-75.965,36.638,-75.973,36.6,-75.96,36.571,-75.993,36.474,-75.978,36.429,-75.925,36.383,-75.867,36.268,-75.82,36.113,-75.883,36.176,-75.95,36.209,-76.055,36.235,-76.148,36.279,-76.141,36.215,-76.15,36.146,-76.222,36.167,-76.271,36.19,-76.227,36.116,-76.321,36.138,-76.384,36.134,-76.424,36.068,-76.479,36.028,-76.559,36.015,-76.679,36.075,-76.718,36.148,-76.734,36.229,-76.74,36.133,-76.719,36.033,-76.726,35.958,-76.611,35.944,-76.504,35.956,-76.358,35.953,-76.264,35.967,-76.207,35.991,-76.07,35.97,-76.06,35.879,-76.076,35.788,-76.084,35.691,-76.046,35.691,-76.001,35.722,-75.979,35.896,-75.854,35.96,-75.812,35.956,-75.772,35.9,-75.759,35.843,-75.745,35.765,-75.774,35.647,-75.966,35.508,-76.104,35.38,-76.174,35.354,-76.275,35.369,-76.39,35.401,-76.447,35.408,-76.49,35.397,-76.516,35.436,-76.532,35.508,-76.577,35.532,-76.611,35.53,-76.634,35.453,-76.741,35.431,-76.887,35.463,-77.04,35.527,-76.974,35.458,-76.595,35.33,-76.553,35.306,-76.513,35.27,-76.566,35.215,-76.608,35.153,-76.613,35.104,-76.628,35.073,-76.779,34.99,-76.861,35.005,-77.07,35.155,-76.975,35.025,-76.899,34.97,-76.745,34.941,-76.457,34.989,-76.362,34.937,-76.44,34.843,-76.517,34.777,-76.618,34.77,-76.707,34.752,-76.733,34.707,-76.797,34.704,-76.896,34.701,-77.05,34.697,-77.134,34.708,-77.252,34.616,-77.296,34.603,-77.358,34.62,-77.384,34.694,-77.412,34.731,-77.413,34.592,-77.402,34.555,-77.38,34.527,-77.518,34.451,-77.65,34.358,-77.697,34.332,-77.751,34.285,-77.861,34.149,-77.888,34.05,-77.928,33.94,-77.933,33.989,-77.926,34.073,-77.953,34.169,-77.971,33.993,-78.013,33.912,-78.406,33.918,-78.578,33.873,-78.841,33.724,-78.92,33.659,-79.138,33.406,-79.194,33.244,-79.238,33.312,-79.227,33.363,-79.226,33.405,-79.281,33.315,-79.229,33.185,-79.276,33.135,-79.42,33.043,-79.499,33.027,-79.587,33.001,-79.615,32.909,-79.735,32.825,-79.805,32.787,-79.933,32.81,-79.894,32.729,-79.941,32.667,-80.022,32.62,-80.123,32.589,-80.18,32.593,-80.23,32.577,-80.268,32.537,-80.363,32.501,-80.461,32.521,-80.572,32.534,-80.634,32.512,-80.53,32.475,-80.474,32.423,-80.486,32.352,-80.514,32.324,-80.579,32.287,-80.608,32.293,-80.626,32.326,-80.647,32.396,-80.678,32.381,-80.683,32.349,-80.709,32.337,-80.803,32.448,-80.798,32.363,-80.765,32.298,-80.734,32.265,-80.702,32.246,-80.694,32.216,-80.758,32.142,-80.791,32.126,-80.849,32.114,-80.882,32.069,-80.872,32.03,-80.923,31.945,-81.046,31.892,-81.083,31.894,-81.113,31.879,-81.096,31.841,-81.065,31.813,-81.066,31.788,-81.098,31.753,-81.162,31.744,-81.198,31.704,-81.187,31.667,-81.166,31.646,-81.17,31.61,-81.242,31.574,-81.259,31.539,-81.223,31.528,-81.196,31.539,-81.175,31.531,-81.219,31.472,-81.258,31.436,-81.295,31.371,-81.381,31.353,-81.378,31.332,-81.329,31.314,-81.288,31.264,-81.365,31.172,-81.413,31.179,-81.442,31.2,-81.46,31.127,-81.453,31.088,-81.471,31.009,-81.501,30.914,-81.52,30.875,-81.516,30.802,-81.504,30.731,-81.457,30.641,-81.386,30.27,-81.337,30.141,-81.25,29.794,-81.105,29.457,-80.9,29.05,-80.564,28.556,-80.524,28.486,-80.568,28.426,-80.581,28.365,-80.585,28.272,-80.573,28.181,-80.533,28.07,-80.457,27.901,-80.5,27.934,-80.61,28.178,-80.623,28.32,-80.607,28.523,-80.633,28.518,-80.654,28.452,-80.665,28.375,-80.694,28.345,-80.732,28.463,-80.729,28.516,-80.688,28.579,-80.7,28.601,-80.766,28.633,-80.78,28.683,-80.771,28.732,-80.809,28.759,-80.838,28.758,-80.818,28.636,-80.787,28.561,-80.749,28.381,-80.686,28.272,-80.65,28.181,-80.226,27.207,-80.126,27.083,-80.089,26.994,-80.05,26.808,-80.041,26.569,-80.111,26.132,-80.126,25.833,-80.136,25.843,-80.143,25.874,-80.159,25.878,-80.219,25.742,-80.301,25.619,-80.328,25.427,-80.367,25.331,-80.485,25.23,-80.558,25.232,-80.737,25.156,-80.862,25.176,-81.012,25.133,-81.11,25.138,-81.167,25.229,-81.159,25.269,-81.136,25.31,-81.098,25.319,-80.965,25.224,-80.94,25.264,-80.98,25.312,-81.057,25.338,-81.113,25.367,-81.227,25.583,-81.345,25.732,-81.365,25.831,-81.568,25.892,-81.715,25.983,-81.811,26.146,-81.867,26.435,-81.931,26.467,-81.959,26.49,-81.896,26.597,-81.829,26.687,-81.882,26.665,-81.921,26.631,-81.97,26.552,-82.006,26.54,-82.04,26.552,-82.078,26.704,-82.067,26.892,-82.013,26.962,-82.096,26.963,-82.181,26.937,-82.169,26.874,-82.181,26.84,-82.243,26.849,-82.29,26.871,-82.354,26.936,-82.441,27.06,-82.62,27.401,-82.655,27.449,-82.715,27.5,-82.687,27.515,-82.636,27.525,-82.521,27.678,-82.431,27.771,-82.401,27.835,-82.406,27.863,-82.446,27.903,-82.498,27.868,-82.521,27.878,-82.58,27.958,-82.636,27.981,-82.675,27.964,-82.634,27.898,-82.597,27.873,-82.611,27.777,-82.626,27.746,-82.661,27.718,-82.715,27.733,-82.743,27.709,-82.775,27.734,-82.808,27.777,-82.844,27.846,-82.749,28.237,-82.661,28.486,-82.651,28.77,-82.644,28.812,-82.651,28.888,-82.769,29.052,-83.29,29.452,-83.694,29.926,-84.044,30.104,-84.31,30.065,-84.356,30.029,-84.375,29.982,-84.359,29.929,-84.383,29.907,-84.454,29.91,-84.55,29.898,-84.801,29.773,-84.889,29.778,-84.969,29.745,-85.029,29.721,-85.186,29.708,-85.319,29.68,-85.376,29.695,-85.414,29.768,-85.414,29.842,-85.383,29.785,-85.336,29.74,-85.315,29.758,-85.307,29.798,-85.354,29.876,-85.504,29.976,-85.676,30.122,-85.623,30.117,-85.61,30.148,-85.663,30.189,-85.641,30.237,-85.604,30.287,-85.676,30.279,-85.741,30.244,-85.743,30.201,-85.756,30.167,-85.791,30.172,-85.856,30.214,-86.175,30.333,-86.454,30.399,-86.24,30.429,-86.124,30.406,-86.138,30.442,-86.166,30.464,-86.257,30.493,-86.374,30.482,-86.448,30.496,-86.523,30.467,-86.606,30.425,-86.68,30.403,-86.968,30.372,-87.201,30.339,-87.164,30.374,-87.124,30.397,-86.986,30.431,-86.965,30.502,-86.998,30.57,-87.034,30.554,-87.072,30.5,-87.119,30.539,-87.171,30.539,-87.185,30.454,-87.251,30.397,-87.281,30.339,-87.476,30.294,-87.501,30.309,-87.444,30.364,-87.448,30.394,-87.513,30.368,-87.622,30.265,-88.006,30.231,-87.985,30.254,-87.904,30.259,-87.79,30.292,-87.813,30.347,-87.857,30.407,-87.898,30.414,-87.924,30.45,-87.923,30.562,-87.949,30.627,-88.011,30.694,-88.032,30.681,-88.078,30.566,-88.117,30.415,-88.135,30.367,-88.249,30.363,-88.35,30.373,-88.692,30.355,-88.82,30.406,-88.873,30.416,-88.905,30.415,-89.054,30.368,-89.224,30.332,-89.264,30.344,-89.321,30.345,-89.444,30.223,-89.588,30.166,-89.954,30.269,-90.045,30.351,-90.126,30.369,-90.225,30.379,-90.332,30.278,-90.413,30.14,-90.285,30.065,-90.175,30.029,-89.994,30.059,-89.894,30.126,-89.812,30.124,-89.773,30.137,-89.737,30.172,-89.668,30.145,-89.665,30.117,-89.715,30.078,-89.777,30.046,-89.815,30.007,-89.744,29.93,-89.632,29.904,-89.59,29.915,-89.563,30.002,-89.494,30.058,-89.401,30.046,-89.414,30.011,-89.401,29.978,-89.358,29.921,-89.363,29.84,-89.354,29.82,-89.455,29.784,-89.531,29.772,-89.591,29.725,-89.559,29.698,-89.621,29.674,-89.662,29.684,-89.683,29.675,-89.689,29.646,-89.721,29.619,-89.675,29.539,-89.58,29.486,-89.514,29.42,-89.246,29.333,-89.181,29.336,-89.117,29.248,-89.065,29.218,-89.016,29.203,-89.021,29.143,-89.11,29.099,-89.133,29.046,-89.156,29.017,-89.195,29.054,-89.236,29.081,-89.331,28.999,-89.376,28.981,-89.354,29.07,-89.389,29.105,-89.443,29.194,-89.522,29.249,-89.577,29.268,-89.62,29.302,-89.672,29.317,-89.717,29.313,-89.792,29.333,-89.797,29.381,-89.818,29.416,-89.877,29.458,-90.159,29.537,-90.161,29.504,-90.141,29.48,-90.101,29.463,-90.052,29.431,-90.053,29.337,-90.074,29.297,-90.083,29.24,-90.101,29.182,-90.136,29.136,-90.213,29.105,-90.247,29.131,-90.302,29.256,-90.379,29.295,-90.502,29.3,-90.586,29.272,-90.677,29.151,-90.751,29.131,-91.003,29.194,-91.29,29.289,-91.283,29.321,-91.237,29.331,-91.151,29.318,-91.155,29.351,-91.244,29.457,-91.26,29.505,-91.249,29.564,-91.278,29.563,-91.331,29.514,-91.514,29.555,-91.565,29.605,-91.672,29.746,-91.824,29.751,-91.893,29.836,-92.017,29.8,-92.08,29.761,-92.135,29.699,-92.114,29.668,-92.059,29.617,-92.084,29.593,-92.261,29.557,-92.671,29.597,-92.791,29.635,-92.952,29.714,-93.176,29.779,-93.283,29.789,-93.388,29.777,-93.695,29.77,-93.766,29.753,-93.826,29.725,-93.866,29.756,-93.884,29.81,-93.848,29.819,-93.809,29.851,-93.773,29.914,-93.769,29.952,-93.794,29.977,-93.841,29.98,-93.946,29.815,-93.886,29.723,-93.89,29.689,-94.1,29.67,-94.574,29.485,-94.76,29.384,-94.75,29.418,-94.526,29.548,-94.605,29.568,-94.733,29.535,-94.778,29.548,-94.724,29.655,-94.742,29.75,-94.832,29.753,-94.89,29.677,-94.93,29.68,-94.982,29.713,-95.023,29.702,-94.993,29.531,-94.936,29.46,-94.888,29.371,-95.018,29.259,-95.139,29.168,-95.152,29.079,-95.273,28.964,-95.388,28.898,-95.656,28.745,-95.732,28.712,-95.853,28.64,-96.02,28.587,-96.181,28.502,-96.235,28.489,-96.132,28.561,-96.011,28.632,-96.115,28.622,-96.275,28.655,-96.373,28.657,-96.374,28.631,-96.449,28.594,-96.526,28.648,-96.56,28.684,-96.576,28.716,-96.608,28.723,-96.64,28.709,-96.525,28.489,-96.475,28.479,-96.421,28.457,-96.489,28.406,-96.562,28.367,-96.676,28.341,-96.774,28.422,-96.795,28.321,-96.807,28.22,-96.84,28.194,-96.892,28.158,-96.92,28.185,-96.933,28.224,-96.967,28.19,-97.015,28.163,-97.096,28.158,-97.156,28.144,-97.155,28.103,-97.141,28.061,-97.034,28.094,-97.073,27.986,-97.171,27.88,-97.252,27.854,-97.374,27.87,-97.404,27.859,-97.431,27.837,-97.289,27.671,-97.38,27.419,-97.439,27.328,-97.48,27.317,-97.524,27.314,-97.682,27.395,-97.768,27.458,-97.692,27.287,-97.485,27.237,-97.475,27.173,-97.476,27.118,-97.517,27.053,-97.555,26.967,-97.527,26.908,-97.494,26.76,-97.466,26.692,-97.435,26.486,-97.402,26.397,-97.214,26.068,-97.15,26.065,-97.14,26.03,-97.146,25.961,-97.282,25.942,-97.339,25.911,-97.35,25.885,-97.358,25.871,-97.376,25.872,-97.44,25.891,-97.587,25.984,-97.801,26.042,-98.083,26.064,-98.275,26.111,-98.378,26.182,-98.486,26.225,-98.598,26.238,-98.691,26.276,-98.765,26.34,-98.873,26.381,-99.015,26.399,-99.108,26.447,-99.172,26.564,-99.172,26.566,-99.23,26.762,-99.302,26.885,-99.444,27.037,-99.456,27.057,-99.457,27.057,-99.458,27.082,-99.44,27.17,-99.455,27.234,-99.5,27.285,-99.51,27.34,-99.486,27.398,-99.484,27.467,-99.505,27.548,-99.595,27.636,-99.754,27.73,-99.89,27.867,-100.001,28.048,-100.112,28.173,-100.221,28.243,-100.296,28.328,-100.336,28.428,-100.348,28.486,-100.332,28.503,-100.399,28.614,-100.55,28.821,-100.636,28.973,-100.659,29.069,-100.755,29.183,-100.924,29.315,-101.016,29.401,-101.039,29.46,-101.304,29.634,-101.38,29.743,-101.44,29.777,-101.509,29.773,-101.545,29.784,-101.546,29.808,-101.569,29.809,-101.612,29.787,-101.752,29.782,-101.991,29.796,-102.163,29.825,-102.269,29.871,-102.343,29.865,-102.386,29.807,-102.476,29.769,-102.615,29.752,-102.734,29.644,-102.834,29.444,-102.878,29.315,-102.866,29.258,-102.892,29.216,-102.957,29.19,-103.023,29.132,-103.09,29.042,-103.168,28.998,-103.258,29.001,-103.423,29.071,-103.664,29.207,-103.853,29.291,-103.99,29.323,-104.111,29.386,-104.216,29.48,-104.312,29.542,-104.401,29.574,-104.504,29.678,-104.622,29.854,-104.681,29.991,-104.681,30.134,-104.836,30.448,-104.918,30.583,-104.979,30.646,-105.098,30.721,-105.276,30.807,-105.514,30.981,-105.813,31.241,-106.024,31.398,-106.148,31.451,-106.256,31.545,-106.347,31.679,-106.436,31.764,-106.445,31.768,-106.453,31.77,-106.673,31.771,-106.893,31.772,-107.113,31.774,-107.333,31.775,-107.552,31.776,-107.772,31.777,-107.992,31.778,-108.212,31.779,-108.212,31.667,-108.213,31.554,-108.214,31.442,-108.214,31.329,-108.568,31.329,-108.921,31.328,-109.275,31.327,-109.628,31.327,-109.982,31.326,-110.335,31.326,-110.689,31.325,-111.042,31.324,-111.516,31.472,-111.99,31.62,-112.465,31.768,-112.939,31.916,-113.413,32.064,-113.887,32.212,-114.362,32.36,-114.836,32.508,-114.788,32.565,-114.725,32.715,-114.839,32.705,-115.125,32.683,-115.411,32.662,-115.698,32.64,-115.984,32.619,-116.27,32.598,-116.556,32.576,-116.842,32.555,-117.128,32.533,-117.13,32.54,-117.137,32.649,-117.184,32.688,-117.243,32.664,-117.271,32.806,-117.256,32.873,-117.263,32.939,-117.319,33.1,-117.467,33.296,-117.789,33.538,-117.952,33.62,-118.081,33.722,-118.162,33.751,-118.264,33.759,-118.294,33.712,-118.41,33.744,-118.393,33.858,-118.506,34.017,-118.599,34.035,-118.832,34.024,-119.144,34.112,-119.236,34.164,-119.268,34.257,-119.414,34.339,-119.606,34.418,-119.713,34.4,-119.853,34.412,-120.053,34.469,-120.17,34.476,-120.396,34.46,-120.481,34.472,-120.56,34.544,-120.645,34.58,-120.627,34.669,-120.638,34.749,-120.625,34.812,-120.663,34.949,-120.634,35.076,-120.659,35.122,-120.707,35.158,-120.857,35.21,-120.885,35.275,-120.86,35.365,-120.9,35.425,-121.023,35.481,-121.138,35.607,-121.284,35.676,-121.344,35.792,-121.434,35.864,-121.465,35.927,-121.664,36.154,-121.877,36.331,-121.91,36.433,-121.919,36.572,-121.835,36.657,-121.79,36.732,-121.795,36.801,-121.807,36.851,-121.881,36.939,-122.164,36.991,-122.395,37.208,-122.408,37.373,-122.499,37.543,-122.5,37.653,-122.514,37.772,-122.446,37.798,-122.384,37.789,-122.39,37.741,-122.37,37.656,-122.298,37.592,-122.229,37.564,-122.166,37.502,-122.119,37.483,-122.071,37.478,-122.097,37.518,-122.124,37.544,-122.158,37.626,-122.222,37.732,-122.296,37.79,-122.333,37.897,-122.365,37.921,-122.385,37.961,-122.314,38.007,-122.217,38.041,-122.087,38.05,-121.717,38.034,-121.638,38.061,-121.573,38.052,-121.525,38.056,-121.626,38.084,-121.682,38.075,-121.749,38.08,-121.881,38.075,-121.934,38.087,-121.993,38.12,-122.031,38.124,-122.154,38.066,-122.208,38.073,-122.337,38.136,-122.393,38.145,-122.484,38.109,-122.495,37.954,-122.467,37.838,-122.521,37.826,-122.584,37.874,-122.681,37.902,-122.76,37.946,-122.873,38.026,-122.932,38.055,-122.999,37.989,-123.001,38.019,-122.968,38.097,-122.978,38.227,-122.877,38.123,-122.908,38.197,-122.987,38.277,-123.046,38.305,-123.121,38.449,-123.29,38.536,-123.425,38.676,-123.701,38.907,-123.72,39.111,-123.82,39.368,-123.778,39.515,-123.783,39.619,-123.833,39.775,-123.884,39.861,-124.108,40.095,-124.324,40.252,-124.357,40.371,-124.372,40.491,-124.325,40.598,-124.284,40.711,-124.254,40.74,-124.242,40.728,-124.251,40.704,-124.22,40.696,-124.208,40.746,-124.19,40.772,-124.223,40.775,-124.219,40.791,-124.2,40.822,-124.133,40.97,-124.14,41.156,-124.069,41.384,-124.072,41.46,-124.118,41.622,-124.163,41.719,-124.245,41.788,-124.209,41.889,-124.212,41.985,-124.355,42.123,-124.41,42.304,-124.421,42.381,-124.406,42.584,-124.444,42.67,-124.54,42.813,-124.499,42.937,-124.454,43.012,-124.347,43.342,-124.321,43.368,-124.275,43.367,-124.197,43.423,-124.233,43.436,-124.288,43.41,-124.239,43.54,-124.184,43.652,-124.149,43.692,-124.131,44.056,-124.099,44.334,-124.047,44.425,-124.065,44.52,-124.045,44.648,-124.059,44.778,-123.949,45.401,-123.963,45.476,-123.929,45.577,-123.961,45.843,-123.947,46.141,-123.975,46.178,-123.989,46.219,-123.963,46.225,-123.912,46.182,-123.674,46.183,-123.522,46.223,-123.466,46.209,-123.402,46.155,-123.322,46.144,-123.221,46.154,-123.251,46.167,-123.299,46.171,-123.405,46.221,-123.465,46.271,-123.65,46.268,-123.688,46.3,-123.896,46.268,-123.96,46.301,-124.073,46.279,-124.045,46.373,-124.05,46.491,-124.044,46.605,-124.016,46.521,-123.946,46.433,-123.912,46.533,-123.889,46.66,-123.958,46.709,-124.072,46.745,-124.113,46.863,-123.843,46.963,-123.986,46.984,-124.042,47.03,-124.112,47.035,-124.117,47,-124.139,46.955,-124.164,47.015,-124.171,47.087,-124.199,47.209,-124.309,47.405,-124.376,47.659,-124.46,47.784,-124.621,47.904,-124.663,47.974,-124.702,48.152,-124.68,48.286,-124.71,48.38,-124.633,48.375,-124.429,48.301,-124.175,48.242,-124.099,48.2,-123.976,48.168,-123.294,48.12,-123.25,48.124,-123.162,48.155,-123.124,48.151,-123.024,48.082,-122.974,48.073,-122.909,48.077,-122.861,48.09,-122.779,48.138,-122.768,48.12,-122.769,48.076,-122.74,48.013,-122.679,47.932,-122.657,47.881,-122.778,47.738,-122.802,47.735,-122.805,47.784,-122.821,47.793,-123.051,47.552,-123.131,47.438,-123.139,47.386,-123.136,47.356,-123.104,47.348,-123.031,47.36,-122.922,47.408,-122.917,47.418,-123.018,47.401,-123.067,47.4,-123.06,47.454,-123.049,47.479,-122.982,47.559,-122.913,47.607,-122.814,47.659,-122.757,47.701,-122.718,47.762,-122.608,47.835,-122.588,47.856,-122.593,47.916,-122.586,47.928,-122.533,47.92,-122.511,47.816,-122.524,47.769,-122.618,47.713,-122.63,47.693,-122.614,47.616,-122.628,47.608,-122.664,47.617,-122.675,47.612,-122.586,47.528,-122.557,47.463,-122.554,47.405,-122.578,47.293,-122.604,47.275,-122.649,47.281,-122.708,47.316,-122.721,47.305,-122.768,47.218,-122.783,47.226,-122.813,47.329,-122.828,47.337,-122.92,47.29,-122.956,47.245,-122.988,47.173,-123.028,47.139,-122.914,47.131,-122.812,47.146,-122.73,47.112,-122.702,47.111,-122.627,47.144,-122.604,47.167,-122.542,47.276,-122.511,47.295,-122.465,47.296,-122.42,47.312,-122.354,47.372,-122.351,47.395,-122.375,47.528,-122.368,47.604,-122.381,47.628,-122.41,47.653,-122.407,47.677,-122.384,47.716,-122.382,47.752,-122.402,47.784,-122.393,47.821,-122.33,47.899,-122.318,47.933,-122.242,48.011,-122.261,48.042,-122.317,48.08,-122.353,48.114,-122.389,48.166,-122.416,48.184,-122.425,48.176,-122.387,48.09,-122.395,48.084,-122.494,48.13,-122.517,48.16,-122.529,48.199,-122.52,48.229,-122.467,48.258,-122.403,48.269,-122.409,48.294,-122.488,48.374,-122.542,48.411,-122.583,48.429,-122.638,48.433,-122.662,48.446,-122.669,48.465,-122.657,48.49,-122.628,48.498,-122.543,48.488,-122.497,48.506,-122.501,48.538,-122.515,48.555,-122.513,48.669,-122.545,48.762,-122.562,48.778,-122.58,48.78,-122.599,48.767,-122.653,48.764,-122.686,48.794,-122.722,48.853,-122.789,48.993,-122.686,48.993,-122.26,48.993,-121.834,48.993,-121.407,48.993,-120.981,48.993,-120.554,48.993,-120.128,48.993,-119.702,48.993,-119.275,48.993,-118.849,48.993,-118.423,48.993,-117.996,48.993,-117.57,48.993,-117.143,48.993,-116.717,48.993,-116.291,48.993,-115.864,48.993,-115.438,48.993,-115.012,48.993,-114.585,48.993,-114.159,48.993,-113.732,48.993,-113.306,48.993,-112.88,48.993,-112.453,48.993,-112.027,48.993,-111.6,48.993,-111.174,48.993,-110.748,48.993,-110.321,48.993,-109.895,48.993,-109.469,48.993,-109.042,48.993,-108.616,48.993,-108.189,48.993,-107.763,48.993,-107.337,48.993,-106.91,48.993,-106.484,48.993,-106.057,48.993,-105.631,48.993,-105.205,48.993,-104.778,48.993,-104.352,48.993,-103.926,48.993,-103.499,48.993,-103.073,48.993,-102.646,48.993,-102.22,48.993,-101.794,48.993,-101.367,48.993,-100.941,48.993,-100.515,48.993,-100.088,48.993,-99.662,48.993,-99.235,48.993,-98.809,48.993,-98.383,48.993,-97.956,48.993,-97.53,48.993,-97.103,48.993,-96.677,48.993,-96.251,48.993,-95.824,48.993,-95.398,48.993,-95.162,48.992,-95.158,49.203,-95.155,49.37,-94.939,49.349,-94.875,49.319,-94.854,49.305,-94.86,49.259,-94.843,49.119,-94.803,49.003,-94.713,48.863,-94.705,48.808,-94.675,48.774,-94.621,48.743,-94.414,48.704,-94.055,48.659,-93.852,48.607,-93.804,48.549,-93.708,48.525,-93.564,48.537,-93.464,48.561,-93.378,48.617,-93.258,48.629,-93.155,48.625,-93.052,48.62,-92.996,48.612,-92.837,48.568,-92.733,48.532,-92.583,48.465,-92.501,48.435,-92.461,48.366,-92.415,48.277,-92.348,48.277,-92.299,48.329,-92.172,48.338,-92.005,48.302,-91.858,48.198,-91.647,48.105,-91.518,48.058,-91.387,48.059,-91.221,48.105,-91.043,48.194,-90.916,48.209,-90.84,48.201,-90.797,48.131,-90.744,48.105,-90.607,48.113,-90.32,48.099,-90.092,48.118,-90.04,48.078,-89.994,48.015,-89.901,47.995,-89.775,48.015,-89.551,48,-89.456,47.996,-89.273,48.02,-89.186,48.047,-89.063,48.094,-88.899,48.156,-88.612,48.264,-88.378,48.303,-88.161,48.225,-87.987,48.157,-87.921,48.13,-87.744,48.061,-87.494,47.962,-87.208,47.848,-86.922,47.735,-86.672,47.636,-86.496,47.567,-86.429,47.54,-86.234,47.46,-86.04,47.38,-85.846,47.3,-85.652,47.22,-85.458,47.14,-85.264,47.06,-85.07,46.98,-84.876,46.9,-84.827,46.767,-84.779,46.637,-84.666,46.543,-84.562,46.457,-84.502,46.462,-84.44,46.498,-84.402,46.516,-84.337,46.519,-84.192,46.55,-84.149,46.543,-84.125,46.527,-84.123,46.503,-84.128,46.484,-84.15,46.445,-84.115,46.371,-84.108,46.289,-84.088,46.227,-84.029,46.147,-83.978,46.085,-83.913,46.073,-83.763,46.109,-83.669,46.123,-83.616,46.117,-83.525,46.059,-83.48,46.024,-83.469,45.995,-83.593,45.817,-83.397,45.729,-83.179,45.633,-82.919,45.518,-82.76,45.448,-82.551,45.347,-82.515,45.204,-82.485,45.084,-82.447,44.916,-82.407,44.744,-82.368,44.573,-82.327,44.392,-82.281,44.192,-82.241,44.015,-82.197,43.822,-82.138,43.571,-82.19,43.474,-82.305,43.263,-82.408,43.073,-82.417,43.017,-82.488,42.74,-82.545,42.625,-82.645,42.558,-82.744,42.493,-82.868,42.385,-83.004,42.332,-83.073,42.3,-83.11,42.251,-83.15,42.142,-83.142,41.976,-83.03,41.833,-82.866,41.753,-82.69,41.675,-82.439,41.675,-82.213,41.779,-81.974,41.889,-81.761,41.987,-81.507,42.103,-81.278,42.209,-81.028,42.247,-80.683,42.3,-80.248,42.366,-80.036,42.441,-79.762,42.539,-79.446,42.651,-79.174,42.749,-79.037,42.802,-78.939,42.864,-78.915,42.909,-78.921,42.935,-78.946,42.961,-78.981,42.981,-79.012,42.997,-79.026,43.017,-79.029,43.062,-79.048,43.087,-79.066,43.106,-79.059,43.278,-79.083,43.331,-79.172,43.467,-79.002,43.527,-78.846,43.583,-78.72,43.625,-78.458,43.631,-78.215,43.631,-77.879,43.63,-77.597,43.629,-77.267,43.627,-77.073,43.627,-76.82,43.629,-76.696,43.785,-76.586,43.924,-76.465,44.058,-76.249,44.214,-76.186,44.242,-76.151,44.304,-76.02,44.363,-75.876,44.417,-75.819,44.468,-75.792,44.497,-75.401,44.772,-75.179,44.899,-74.996,44.97,-74.857,45.004,-74.762,44.999,-74.709,45.004]],[[-72.51,40.986,-72.581,40.921,-72.517,40.915,-72.461,40.934,-72.409,40.972,-72.287,41.024,-72.184,41.047,-72.151,41.051,-72.102,41.015,-72.004,41.044,-71.903,41.061,-72.339,40.894,-72.428,40.875,-72.556,40.826,-72.676,40.791,-72.763,40.778,-73.194,40.654,-73.229,40.652,-73.266,40.664,-73.621,40.6,-73.767,40.593,-73.9,40.571,-73.801,40.622,-73.799,40.641,-73.823,40.656,-73.875,40.652,-73.929,40.599,-74.015,40.581,-74.032,40.639,-74.003,40.683,-73.965,40.725,-73.879,40.792,-73.757,40.834,-73.695,40.87,-73.652,40.838,-73.643,40.881,-73.61,40.906,-73.574,40.92,-73.487,40.92,-73.441,40.927,-73.407,40.941,-73.373,40.944,-73.278,40.924,-73.186,40.93,-73.111,40.957,-73.034,40.966,-72.829,40.972,-72.625,40.992,-72.544,41.027,-72.373,41.126,-72.274,41.153,-72.427,41.039,-72.51,40.986]],[[-68.187,44.332,-68.245,44.313,-68.309,44.321,-68.308,44.269,-68.315,44.25,-68.386,44.277,-68.412,44.294,-68.409,44.364,-68.347,44.43,-68.299,44.456,-68.238,44.438,-68.191,44.364,-68.187,44.332]],[[-74.188,40.523,-74.236,40.519,-74.188,40.615,-74.1,40.658,-74.069,40.649,-74.067,40.615,-74.08,40.586,-74.139,40.542,-74.188,40.523]],[[-70.51,41.376,-70.785,41.327,-70.829,41.359,-70.76,41.374,-70.674,41.449,-70.616,41.457,-70.525,41.415,-70.51,41.376]],[[-71.241,41.492,-71.291,41.465,-71.346,41.469,-71.318,41.506,-71.307,41.56,-71.28,41.62,-71.264,41.638,-71.232,41.654,-71.241,41.492]],[[-69.978,41.266,-70.055,41.249,-70.233,41.286,-70.087,41.318,-70.063,41.328,-70.044,41.374,-70.041,41.397,-69.986,41.299,-69.978,41.266]],[[-75.636,35.856,-75.651,35.836,-75.717,35.946,-75.649,35.91,-75.637,35.881,-75.636,35.856]],[[-75.544,35.24,-75.678,35.213,-75.69,35.222,-75.536,35.279,-75.488,35.479,-75.481,35.572,-75.504,35.735,-75.504,35.769,-75.479,35.717,-75.456,35.564,-75.465,35.449,-75.509,35.28,-75.544,35.24]],[[-75.782,35.19,-75.964,35.119,-75.984,35.123,-75.865,35.174,-75.782,35.19]],[[-76.504,34.643,-76.529,34.631,-76.437,34.756,-76.256,34.915,-76.207,34.939,-76.358,34.804,-76.504,34.643]],[[-82.037,26.454,-82.073,26.428,-82.145,26.447,-82.184,26.481,-82.201,26.548,-82.139,26.477,-82.116,26.461,-82.037,26.454]],[[-82.084,26.552,-82.085,26.494,-82.136,26.592,-82.169,26.701,-82.121,26.666,-82.084,26.552]],[[-80.382,25.142,-80.581,24.954,-80.559,25.001,-80.481,25.102,-80.456,25.149,-80.404,25.179,-80.355,25.234,-80.351,25.297,-80.28,25.341,-80.257,25.348,-80.382,25.142]],[[-91.794,29.501,-91.831,29.486,-91.996,29.573,-92.007,29.61,-91.925,29.644,-91.875,29.641,-91.796,29.597,-91.768,29.585,-91.754,29.567,-91.762,29.539,-91.794,29.501]],[[-97.014,27.902,-97.036,27.899,-96.988,27.981,-96.979,28.014,-96.899,28.117,-96.857,28.133,-96.84,28.089,-96.921,28.016,-97.014,27.902]],[[-96.764,28.153,-96.801,28.148,-96.756,28.202,-96.682,28.23,-96.519,28.333,-96.453,28.341,-96.419,28.376,-96.404,28.382,-96.413,28.338,-96.544,28.276,-96.764,28.153]],[[-97.354,27.3,-97.385,27.243,-97.376,27.328,-97.295,27.523,-97.13,27.779,-97.061,27.822,-97.251,27.541,-97.354,27.3]],[[-95.04,29.146,-95.09,29.136,-94.872,29.29,-94.826,29.341,-94.768,29.339,-94.865,29.253,-95.04,29.146]],[[-97.171,26.159,-97.185,26.113,-97.267,26.33,-97.402,26.821,-97.407,27.1,-97.386,27.196,-97.351,26.801,-97.202,26.3,-97.171,26.159]],[[-120.307,34.025,-120.36,34.022,-120.442,34.033,-120.413,34.056,-120.368,34.073,-120.353,34.061,-120.307,34.025]],[[-119.438,33.217,-119.483,33.215,-119.544,33.225,-119.575,33.278,-119.525,33.282,-119.479,33.275,-119.442,33.232,-119.438,33.217]],[[-118.35,32.828,-118.409,32.819,-118.473,32.839,-118.529,32.936,-118.59,33.011,-118.557,33.033,-118.507,32.96,-118.383,32.849,-118.35,32.828]],[[-120.044,33.919,-120.114,33.905,-120.167,33.918,-120.252,34.014,-120.072,34.027,-119.994,33.985,-119.984,33.973,-120.044,33.919]],[[-119.882,34.08,-119.679,34.028,-119.569,34.053,-119.549,34.028,-119.562,34.007,-119.81,33.968,-119.885,33.995,-119.892,34.032,-119.918,34.068,-119.882,34.08]],[[-118.348,33.386,-118.297,33.312,-118.37,33.321,-118.446,33.317,-118.469,33.357,-118.492,33.413,-118.507,33.427,-118.559,33.432,-118.563,33.437,-118.569,33.464,-118.555,33.477,-118.392,33.415,-118.348,33.386]],[[-122.782,48.673,-122.769,48.651,-122.809,48.63,-122.838,48.627,-122.883,48.661,-122.903,48.665,-122.887,48.612,-122.893,48.594,-122.986,48.627,-123.003,48.652,-122.977,48.679,-122.918,48.707,-122.898,48.71,-122.782,48.673]],[[-123.013,48.501,-122.987,48.468,-123.094,48.489,-123.14,48.508,-123.153,48.526,-123.17,48.587,-123.162,48.606,-123.114,48.613,-123.024,48.538,-123.013,48.501]],[[-122.573,48.157,-122.524,48.025,-122.503,48.08,-122.367,47.985,-122.367,47.939,-122.383,47.923,-122.411,47.918,-122.438,47.931,-122.462,47.964,-122.492,47.981,-122.558,47.992,-122.591,48.03,-122.603,48.055,-122.606,48.129,-122.623,48.151,-122.657,48.156,-122.69,48.174,-122.742,48.225,-122.749,48.239,-122.725,48.281,-122.669,48.351,-122.629,48.384,-122.604,48.381,-122.572,48.36,-122.536,48.321,-122.542,48.294,-122.692,48.241,-122.697,48.229,-122.624,48.214,-122.598,48.2,-122.573,48.157]],[[-71.365,41.485,-71.393,41.467,-71.403,41.515,-71.384,41.571,-71.364,41.572,-71.354,41.542,-71.365,41.485]],[[-74.133,39.681,-74.25,39.529,-74.253,39.558,-74.107,39.746,-74.133,39.681]],[[-75.333,37.888,-75.379,37.872,-75.226,38.072,-75.137,38.24,-75.098,38.298,-75.136,38.181,-75.203,38.072,-75.333,37.888]],[[-76.546,34.655,-76.569,34.653,-76.608,34.664,-76.662,34.685,-76.674,34.7,-76.622,34.695,-76.546,34.655]],[[-81.335,24.65,-81.365,24.63,-81.379,24.636,-81.379,24.666,-81.422,24.733,-81.42,24.75,-81.322,24.685,-81.32,24.668,-81.335,24.65]],[[-80.829,24.804,-80.848,24.804,-80.839,24.818,-80.799,24.846,-80.785,24.835,-80.787,24.821,-80.829,24.804]],[[-80.638,24.903,-80.665,24.898,-80.626,24.941,-80.615,24.938,-80.638,24.903]],[[-81.044,24.717,-81.09,24.693,-81.137,24.71,-81.085,24.734,-80.93,24.759,-80.989,24.728,-81.044,24.717]],[[-81.419,30.971,-81.463,30.728,-81.483,30.814,-81.485,30.898,-81.451,30.947,-81.419,30.971]],[[-81.567,24.6,-81.631,24.59,-81.579,24.629,-81.562,24.689,-81.532,24.642,-81.532,24.614,-81.567,24.6]],[[-80.187,27.278,-80.171,27.205,-80.262,27.376,-80.376,27.643,-80.437,27.851,-80.396,27.795,-80.356,27.679,-80.187,27.278]],[[-81.784,24.545,-81.809,24.542,-81.811,24.558,-81.768,24.577,-81.739,24.575,-81.74,24.554,-81.784,24.545]],[[-88.889,29.713,-88.944,29.66,-88.941,29.68,-88.901,29.733,-88.873,29.753,-88.889,29.713]],[[-88.558,30.216,-88.571,30.205,-88.659,30.226,-88.713,30.245,-88.723,30.264,-88.574,30.229,-88.558,30.216]],[[-88.071,30.252,-88.159,30.231,-88.29,30.233,-88.316,30.24,-88.264,30.255,-88.109,30.274,-88.071,30.252]],[[-89.224,30.084,-89.22,30.038,-89.269,30.061,-89.342,30.063,-89.31,30.079,-89.288,30.094,-89.276,30.111,-89.185,30.169,-89.211,30.126,-89.224,30.084]],[[-88.827,29.808,-88.856,29.776,-88.828,29.928,-88.867,30.057,-88.826,30,-88.813,29.933,-88.827,29.808]],[[-84.908,29.643,-85.008,29.607,-85.117,29.633,-85.049,29.638,-85.001,29.627,-84.877,29.679,-84.812,29.718,-84.737,29.732,-84.908,29.643]],[[-122.853,47.205,-122.863,47.185,-122.877,47.186,-122.908,47.226,-122.912,47.254,-122.885,47.275,-122.849,47.216,-122.853,47.205]],[[-122.394,47.395,-122.399,47.373,-122.437,47.355,-122.457,47.359,-122.458,47.386,-122.469,47.39,-122.51,47.358,-122.507,47.422,-122.486,47.489,-122.469,47.49,-122.442,47.446,-122.394,47.395]],[[-122.497,47.595,-122.503,47.575,-122.558,47.598,-122.576,47.619,-122.574,47.667,-122.56,47.698,-122.55,47.704,-122.517,47.691,-122.508,47.683,-122.497,47.595]],[[-122.821,48.431,-122.837,48.422,-122.89,48.435,-122.922,48.457,-122.932,48.485,-122.912,48.538,-122.885,48.552,-122.869,48.549,-122.862,48.502,-122.815,48.452,-122.821,48.431]],[[-68.623,44.196,-68.661,44.176,-68.702,44.183,-68.703,44.232,-68.691,44.249,-68.677,44.256,-68.656,44.242,-68.623,44.196]]]},{"n":"South Georgia and the South Sandwich Islands","c":"GS","r":4,"ml":5,"lx":-31.06,"ly":-55.68,"b":[-38.017,-58.492,-26.26,-53.984],"p":[[[-26.264,-58.435,-26.26,-58.492,-26.415,-58.44,-26.451,-58.415,-26.401,-58.383,-26.303,-58.382,-26.279,-58.402,-26.264,-58.435]],[[-37.103,-54.066,-37.006,-54.114,-36.929,-54.081,-36.849,-54.085,-36.805,-54.101,-36.76,-54.108,-36.704,-54.108,-36.607,-54.19,-36.647,-54.262,-36.541,-54.248,-36.449,-54.308,-36.407,-54.303,-36.386,-54.279,-36.326,-54.251,-36.285,-54.289,-36.236,-54.36,-36.173,-54.382,-36.117,-54.458,-36.073,-54.554,-36.033,-54.568,-35.965,-54.568,-35.895,-54.555,-35.922,-54.637,-35.913,-54.711,-35.799,-54.763,-35.867,-54.792,-35.939,-54.834,-36.085,-54.867,-36.124,-54.853,-36.252,-54.78,-36.311,-54.694,-36.446,-54.571,-36.472,-54.534,-36.507,-54.511,-36.628,-54.496,-36.735,-54.467,-36.824,-54.404,-36.852,-54.366,-36.886,-54.339,-37.007,-54.341,-37.083,-54.312,-37.158,-54.271,-37.498,-54.156,-37.631,-54.167,-37.692,-54.135,-37.689,-54.077,-37.619,-54.042,-37.913,-54.029,-38.017,-54.008,-37.946,-53.996,-37.536,-53.994,-37.382,-53.984,-37.369,-54.009,-37.233,-54.061,-37.103,-54.066]]]},{"n":"British Indian Ocean Territory","c":"IO","r":5,"ml":5,"lx":71.35,"ly":-6.19,"b":[72.35,-7.435,72.499,-7.22],"p":[[[72.492,-7.377,72.469,-7.417,72.429,-7.435,72.408,-7.334,72.35,-7.263,72.373,-7.263,72.427,-7.3,72.447,-7.396,72.467,-7.368,72.462,-7.338,72.474,-7.31,72.465,-7.278,72.436,-7.23,72.446,-7.22,72.494,-7.262,72.499,-7.295,72.492,-7.377]]]},{"n":"Saint Helena","c":"SH","r":6,"ml":5,"lx":-5.71,"ly":-15.95,"b":[-14.415,-16.004,-5.66,-7.883],"p":[[[-5.692,-15.998,-5.783,-16.004,-5.775,-15.957,-5.708,-15.906,-5.662,-15.913,-5.66,-15.971,-5.692,-15.998]],[[-14.364,-7.974,-14.399,-7.976,-14.409,-7.967,-14.415,-7.944,-14.399,-7.906,-14.384,-7.883,-14.36,-7.886,-14.329,-7.913,-14.303,-7.935,-14.317,-7.956,-14.364,-7.974]]]},{"n":"Pitcairn Islands","c":"PN","r":4,"ml":5,"lx":-128.32,"ly":-24.36,"b":[-128.35,-24.413,-128.29,-24.323],"p":[[[-128.29,-24.397,-128.3,-24.413,-128.321,-24.4,-128.342,-24.371,-128.35,-24.34,-128.33,-24.323,-128.304,-24.334,-128.291,-24.365,-128.29,-24.397]]]},{"n":"Anguilla","c":"AI","r":6,"ml":5,"lx":-63.03,"ly":18.24,"b":[-63.16,18.171,-62.98,18.27],"p":[[[-63.001,18.222,-63.16,18.171,-63.153,18.2,-63.026,18.27,-62.98,18.265,-63.001,18.222]]]},{"n":"Falkland Islands","c":"FK","r":5,"ml":4.5,"lx":-58.74,"ly":-51.61,"b":[-61.145,-52.308,-57.792,-51.27],"p":[[[-58.85,-51.27,-58.698,-51.329,-58.506,-51.308,-58.426,-51.324,-58.379,-51.373,-58.407,-51.418,-58.467,-51.412,-58.519,-51.424,-58.509,-51.484,-58.474,-51.509,-58.272,-51.575,-58.235,-51.579,-58.241,-51.551,-58.276,-51.506,-58.289,-51.458,-58.259,-51.417,-58.206,-51.405,-57.977,-51.384,-57.923,-51.404,-57.808,-51.518,-57.915,-51.534,-57.96,-51.583,-57.866,-51.605,-57.792,-51.636,-57.831,-51.685,-57.838,-51.709,-58.004,-51.743,-58.151,-51.765,-58.218,-51.822,-58.336,-51.864,-58.683,-51.936,-58.643,-51.995,-58.638,-52.023,-58.653,-52.099,-59.131,-52.008,-59.196,-52.018,-59.068,-52.173,-59.163,-52.202,-59.256,-52.183,-59.342,-52.196,-59.396,-52.308,-59.532,-52.236,-59.649,-52.134,-59.649,-52.077,-59.537,-51.971,-59.571,-51.925,-59.309,-51.78,-59.262,-51.737,-59.18,-51.712,-59.095,-51.704,-59.06,-51.685,-59.065,-51.65,-59.099,-51.59,-59.097,-51.491,-58.887,-51.358,-58.917,-51.272,-58.85,-51.27]],[[-60.286,-51.462,-60.142,-51.481,-60.009,-51.411,-59.917,-51.388,-59.842,-51.403,-59.788,-51.446,-59.711,-51.439,-59.493,-51.396,-59.465,-51.411,-59.388,-51.36,-59.321,-51.384,-59.268,-51.428,-59.294,-51.479,-59.354,-51.511,-59.392,-51.556,-59.437,-51.593,-59.514,-51.627,-59.573,-51.681,-59.715,-51.808,-59.921,-51.97,-59.99,-51.984,-60.132,-51.994,-60.194,-51.983,-60.246,-51.986,-60.288,-52.074,-60.353,-52.14,-60.384,-52.154,-60.452,-52.16,-60.484,-52.17,-60.508,-52.195,-60.686,-52.188,-60.812,-52.148,-60.961,-52.057,-60.762,-51.946,-60.591,-51.952,-60.45,-51.877,-60.334,-51.84,-60.289,-51.801,-60.238,-51.772,-60.238,-51.734,-60.277,-51.717,-60.328,-51.718,-60.38,-51.735,-60.5,-51.757,-60.583,-51.713,-60.528,-51.696,-60.467,-51.697,-60.281,-51.656,-60.245,-51.639,-60.303,-51.58,-60.415,-51.545,-60.506,-51.485,-60.523,-51.463,-60.518,-51.428,-60.568,-51.358,-60.516,-51.354,-60.445,-51.399,-60.286,-51.462]],[[-61.019,-51.786,-60.947,-51.8,-60.876,-51.794,-60.916,-51.897,-60.948,-51.946,-61.032,-51.942,-61.116,-51.875,-61.145,-51.839,-61.052,-51.814,-61.019,-51.786]],[[-58.439,-52.011,-58.433,-52.099,-58.513,-52.071,-58.541,-52.028,-58.497,-51.999,-58.461,-52.002,-58.439,-52.011]],[[-60.112,-51.396,-60.249,-51.396,-60.276,-51.363,-60.275,-51.281,-60.171,-51.273,-60.07,-51.308,-60.076,-51.343,-60.112,-51.396]],[[-59.683,-52.232,-59.747,-52.251,-59.764,-52.242,-59.785,-52.205,-59.786,-52.156,-59.793,-52.134,-59.753,-52.141,-59.681,-52.18,-59.683,-52.232]]]},{"n":"Cayman Islands","c":"KY","r":5,"ml":5,"lx":-81.24,"ly":19.32,"b":[-81.419,19.272,-79.742,19.766],"p":[[[-81.37,19.349,-81.337,19.329,-81.296,19.341,-81.285,19.363,-81.13,19.347,-81.107,19.305,-81.225,19.304,-81.277,19.277,-81.304,19.272,-81.405,19.278,-81.419,19.375,-81.391,19.385,-81.37,19.349]],[[-79.979,19.708,-79.989,19.703,-80.021,19.707,-80.094,19.666,-80.126,19.668,-80.116,19.683,-80.101,19.696,-80.084,19.706,-80.068,19.71,-80.016,19.718,-79.992,19.719,-79.975,19.71,-79.979,19.708]],[[-79.823,19.712,-79.87,19.697,-79.906,19.703,-79.824,19.744,-79.803,19.758,-79.785,19.766,-79.766,19.766,-79.742,19.757,-79.742,19.751,-79.823,19.712]]]},{"n":"Bermuda","c":"BM","r":6,"ml":4,"lx":-64.76,"ly":32.3,"b":[-64.863,32.26,-64.668,32.387],"p":[[[-64.73,32.293,-64.82,32.26,-64.845,32.262,-64.863,32.274,-64.771,32.308,-64.695,32.387,-64.668,32.382,-64.73,32.293]]]},{"n":"British Virgin Islands","c":"VG","r":6,"ml":5,"lx":-64.64,"ly":18.43,"b":[-64.695,18.399,-64.274,18.753],"p":[[[-64.395,18.465,-64.421,18.457,-64.438,18.459,-64.444,18.473,-64.426,18.513,-64.325,18.517,-64.395,18.465]],[[-64.594,18.403,-64.672,18.399,-64.695,18.412,-64.651,18.443,-64.569,18.446,-64.545,18.438,-64.594,18.403]],[[-64.288,18.741,-64.274,18.707,-64.282,18.708,-64.339,18.731,-64.383,18.733,-64.401,18.739,-64.411,18.751,-64.323,18.753,-64.288,18.741]]]},{"n":"Turks and Caicos Islands","c":"TC","r":6,"ml":5,"lx":-71.75,"ly":21.82,"b":[-72.342,21.752,-71.637,21.952],"p":[[[-71.661,21.765,-71.665,21.752,-71.722,21.79,-71.83,21.791,-71.848,21.843,-71.806,21.852,-71.668,21.833,-71.637,21.788,-71.661,21.765]],[[-71.88,21.84,-71.897,21.83,-71.955,21.864,-71.964,21.892,-71.985,21.893,-72.019,21.918,-72.011,21.95,-71.932,21.952,-71.9,21.863,-71.88,21.84]],[[-72.333,21.851,-72.219,21.796,-72.15,21.804,-72.144,21.793,-72.182,21.78,-72.191,21.77,-72.301,21.755,-72.335,21.758,-72.342,21.795,-72.333,21.851]]]},{"n":"Montserrat","c":"MS","r":6,"ml":5,"lx":-62.19,"ly":16.74,"b":[-62.223,16.681,-62.148,16.81],"p":[[[-62.148,16.74,-62.154,16.681,-62.222,16.7,-62.223,16.752,-62.191,16.804,-62.176,16.81,-62.148,16.74]]]},{"n":"Jersey","c":"JE","r":6,"ml":5,"lx":-2.09,"ly":49.22,"b":[-2.236,49.17,-2.01,49.266],"p":[[[-2.019,49.231,-2.01,49.181,-2.054,49.17,-2.091,49.187,-2.166,49.187,-2.236,49.176,-2.221,49.266,-2.082,49.255,-2.019,49.231]]]},{"n":"Guernsey","c":"GG","r":6,"ml":5,"lx":-2.56,"ly":49.46,"b":[-2.646,49.429,-2.512,49.507],"p":[[[-2.512,49.495,-2.547,49.429,-2.639,49.451,-2.646,49.468,-2.542,49.507,-2.521,49.506,-2.512,49.495]]]},{"n":"Isle of Man","c":"IM","r":6,"ml":5,"lx":-4.53,"ly":54.22,"b":[-4.785,54.059,-4.338,54.407],"p":[[[-4.412,54.185,-4.614,54.059,-4.696,54.081,-4.766,54.069,-4.785,54.073,-4.746,54.119,-4.699,54.225,-4.615,54.267,-4.509,54.377,-4.425,54.407,-4.396,54.403,-4.377,54.393,-4.338,54.269,-4.392,54.225,-4.412,54.185]]]},{"n":"United Kingdom","c":"GB","r":2,"ml":1.7,"lx":-2.12,"ly":54.4,"b":[-8.145,50.021,1.747,60.832],"p":[[[-2.668,51.623,-2.742,51.581,-2.979,51.539,-3.08,51.496,-3.259,51.398,-3.293,51.39,-3.562,51.414,-3.763,51.54,-3.891,51.592,-3.944,51.598,-3.998,51.582,-4.115,51.566,-4.235,51.569,-4.174,51.627,-4.091,51.66,-4.276,51.683,-4.328,51.7,-4.386,51.741,-4.531,51.748,-4.601,51.738,-4.718,51.684,-4.902,51.626,-5.125,51.706,-5.168,51.741,-5.167,51.808,-5.201,51.861,-5.262,51.88,-5.183,51.95,-5.088,51.996,-4.879,52.042,-4.561,52.151,-4.383,52.197,-4.218,52.277,-4.149,52.326,-4.1,52.393,-4.051,52.475,-3.98,52.542,-4.048,52.558,-4.079,52.608,-4.071,52.659,-4.039,52.704,-4.067,52.761,-4.118,52.82,-4.115,52.866,-4.101,52.915,-4.229,52.913,-4.356,52.897,-4.472,52.862,-4.584,52.815,-4.683,52.806,-4.681,52.844,-4.638,52.891,-4.526,52.958,-4.405,53.014,-4.362,53.056,-4.328,53.105,-4.269,53.145,-4.111,53.219,-3.809,53.303,-3.764,53.308,-3.646,53.298,-3.53,53.311,-3.428,53.341,-3.326,53.347,-3.098,53.26,-3.166,53.395,-3.065,53.427,-2.919,53.305,-2.864,53.293,-2.75,53.31,-2.794,53.331,-2.845,53.332,-2.913,53.35,-2.97,53.389,-3.065,53.513,-3.059,53.586,-2.996,53.663,-2.925,53.733,-2.984,53.747,-3.032,53.774,-3.045,53.844,-3.027,53.906,-2.9,53.961,-2.862,54.044,-2.846,54.135,-2.868,54.177,-2.994,54.171,-3.055,54.153,-3.11,54.126,-3.166,54.128,-3.322,54.229,-3.41,54.306,-3.569,54.468,-3.592,54.564,-3.465,54.773,-3.268,54.907,-3.036,54.953,-3.081,54.962,-3.434,54.964,-3.55,54.947,-3.658,54.893,-3.719,54.876,-3.783,54.87,-3.842,54.843,-3.899,54.805,-3.958,54.781,-4.076,54.787,-4.133,54.779,-4.174,54.801,-4.208,54.837,-4.253,54.847,-4.304,54.836,-4.41,54.787,-4.517,54.758,-4.648,54.789,-4.818,54.846,-4.852,54.825,-4.89,54.772,-4.911,54.689,-5.032,54.761,-5.135,54.858,-5.17,54.918,-5.173,54.986,-5.117,55.012,-5.056,54.988,-4.965,55.149,-4.785,55.359,-4.721,55.421,-4.677,55.501,-4.684,55.554,-4.724,55.598,-4.892,55.699,-4.89,55.781,-4.872,55.874,-4.826,55.93,-4.807,55.94,-4.584,55.939,-4.671,55.967,-4.844,56.051,-4.841,56.081,-4.8,56.158,-4.819,56.15,-4.856,56.115,-4.927,56.028,-4.97,56.008,-5.093,55.987,-5.115,55.945,-5.135,55.933,-5.196,55.929,-5.215,55.889,-5.228,55.886,-5.246,55.929,-5.247,56,-5.223,56.066,-5.176,56.117,-4.997,56.233,-5.084,56.197,-5.282,56.09,-5.383,56.019,-5.41,55.995,-5.419,55.975,-5.418,55.952,-5.373,55.828,-5.386,55.77,-5.556,55.39,-5.589,55.351,-5.618,55.331,-5.647,55.327,-5.731,55.334,-5.768,55.363,-5.768,55.395,-5.752,55.443,-5.681,55.624,-5.651,55.674,-5.605,55.721,-5.504,55.802,-5.507,55.808,-5.574,55.792,-5.602,55.797,-5.623,55.813,-5.61,56.055,-5.555,56.135,-5.535,56.251,-5.488,56.35,-5.433,56.422,-5.392,56.515,-5.329,56.556,-5.313,56.619,-5.243,56.687,-5.188,56.758,-5.218,56.751,-5.564,56.566,-5.652,56.532,-5.773,56.541,-5.865,56.562,-5.937,56.606,-5.969,56.69,-6.058,56.692,-6.134,56.707,-6.133,56.718,-6.035,56.764,-5.878,56.78,-5.731,56.853,-5.861,56.903,-5.85,56.918,-5.736,56.961,-5.591,57.102,-5.562,57.233,-5.631,57.294,-5.656,57.334,-5.795,57.379,-5.818,57.436,-5.802,57.468,-5.757,57.499,-5.689,57.524,-5.582,57.547,-5.679,57.572,-5.715,57.601,-5.742,57.644,-5.745,57.668,-5.695,57.778,-5.665,57.824,-5.608,57.881,-5.349,57.878,-5.319,57.904,-5.29,57.905,-5.157,57.881,-5.177,57.906,-5.394,58.044,-5.413,58.07,-5.351,58.144,-5.347,58.177,-5.356,58.212,-5.338,58.239,-5.27,58.251,-5.06,58.25,-5.008,58.263,-5.032,58.298,-5.081,58.345,-5.09,58.385,-5.079,58.419,-5.076,58.489,-5.067,58.52,-5.017,58.567,-4.976,58.58,-4.925,58.588,-4.81,58.573,-4.766,58.554,-4.715,58.51,-4.678,58.514,-4.535,58.562,-4.492,58.568,-4.433,58.513,-4.189,58.557,-3.86,58.577,-3.662,58.606,-3.454,58.617,-3.259,58.65,-3.053,58.635,-3.046,58.616,-3.057,58.589,-3.11,58.515,-3.101,58.434,-3.113,58.409,-3.137,58.378,-3.212,58.321,-3.411,58.24,-3.775,58.052,-3.99,57.959,-4.02,57.914,-4.036,57.852,-3.907,57.84,-3.857,57.819,-3.888,57.787,-4.078,57.677,-4.135,57.578,-3.988,57.581,-3.868,57.6,-3.628,57.662,-3.403,57.708,-3.295,57.71,-3.084,57.673,-3.036,57.672,-2.947,57.689,-2.856,57.692,-2.244,57.681,-2.074,57.702,-1.962,57.677,-1.867,57.612,-1.778,57.494,-1.781,57.474,-1.835,57.42,-1.934,57.352,-2.02,57.259,-2.046,57.209,-2.062,57.153,-2.09,57.103,-2.26,56.863,-2.427,56.731,-2.501,56.637,-2.593,56.562,-2.681,56.514,-2.775,56.483,-3.047,56.449,-3.124,56.425,-3.214,56.384,-3.31,56.363,-3.198,56.366,-3.087,56.389,-2.885,56.398,-2.653,56.318,-2.674,56.253,-2.768,56.202,-2.98,56.194,-3.178,56.08,-3.268,56.045,-3.362,56.028,-3.48,56.033,-3.695,56.063,-3.789,56.095,-3.704,56.043,-3.608,56.016,-3.049,55.952,-3.015,55.959,-2.837,56.026,-2.599,56.027,-2.147,55.903,-2.017,55.808,-1.83,55.672,-1.729,55.619,-1.655,55.57,-1.61,55.498,-1.523,55.26,-1.423,55.026,-1.292,54.774,-1.232,54.704,-1.154,54.654,-0.759,54.541,-0.671,54.504,-0.518,54.395,-0.37,54.279,-0.233,54.19,-0.084,54.118,-0.156,54.081,-0.206,54.022,-0.169,53.942,-0.108,53.865,0.011,53.743,0.115,53.609,0.077,53.629,0.036,53.641,-0.019,53.637,-0.074,53.644,-0.174,53.685,-0.27,53.737,-0.461,53.716,-0.568,53.725,-0.66,53.724,-0.485,53.694,-0.294,53.692,0.128,53.468,0.271,53.335,0.356,53.16,0.298,53.081,0.208,53.03,0.124,52.972,0.046,52.906,0.28,52.809,0.33,52.812,0.382,52.825,0.432,52.858,0.516,52.938,0.559,52.967,0.704,52.977,0.827,52.971,0.949,52.953,1.056,52.959,1.271,52.925,1.382,52.894,1.657,52.754,1.716,52.677,1.743,52.579,1.747,52.469,1.7,52.369,1.647,52.279,1.615,52.162,1.591,52.12,1.559,52.087,1.413,51.995,1.317,51.957,1.276,51.974,1.232,51.971,1.228,51.949,1.274,51.902,1.274,51.845,1.188,51.803,1.101,51.785,0.955,51.808,0.752,51.73,0.898,51.689,0.927,51.647,0.891,51.571,0.799,51.538,0.698,51.523,0.593,51.519,0.507,51.501,0.425,51.466,0.528,51.484,0.6,51.468,0.646,51.405,0.687,51.387,0.889,51.36,1.015,51.36,1.257,51.375,1.373,51.375,1.415,51.363,1.416,51.311,1.398,51.182,1.366,51.155,1.044,51.047,0.979,50.972,0.96,50.926,0.772,50.934,0.684,50.886,0.532,50.853,0.415,50.819,0.3,50.776,0.205,50.763,-0.204,50.814,-0.451,50.81,-0.785,50.765,-0.871,50.773,-1.001,50.816,-1.133,50.845,-1.285,50.857,-1.416,50.897,-1.334,50.821,-1.517,50.747,-1.601,50.733,-1.688,50.735,-1.866,50.715,-2.031,50.725,-2.006,50.673,-1.962,50.628,-1.998,50.608,-2.036,50.603,-2.35,50.637,-2.395,50.631,-2.433,50.599,-2.548,50.616,-2.659,50.67,-2.777,50.706,-2.901,50.722,-2.999,50.717,-3.405,50.632,-3.485,50.548,-3.526,50.428,-3.584,50.322,-3.68,50.24,-3.793,50.229,-3.9,50.286,-4.103,50.349,-4.173,50.391,-4.195,50.393,-4.217,50.378,-4.297,50.359,-4.379,50.358,-4.507,50.341,-4.728,50.29,-4.817,50.256,-5.01,50.161,-5.049,50.134,-5.119,50.038,-5.225,50.021,-5.323,50.083,-5.434,50.104,-5.551,50.083,-5.622,50.051,-5.655,50.077,-5.656,50.132,-5.571,50.197,-5.342,50.246,-5.142,50.374,-5.043,50.452,-5.004,50.495,-4.956,50.523,-4.894,50.534,-4.861,50.582,-4.583,50.776,-4.56,50.821,-4.546,50.901,-4.523,50.977,-4.296,51.027,-4.188,51.189,-4.158,51.201,-3.842,51.231,-3.608,51.229,-3.375,51.197,-3.256,51.194,-3.136,51.205,-3.042,51.249,-2.881,51.406,-2.791,51.475,-2.687,51.537,-2.59,51.609,-2.433,51.741,-2.539,51.695,-2.668,51.623]],[[-4.197,53.321,-4.155,53.303,-4.049,53.306,-4.084,53.264,-4.2,53.218,-4.279,53.172,-4.373,53.134,-4.419,53.178,-4.472,53.176,-4.553,53.26,-4.568,53.386,-4.462,53.419,-4.315,53.417,-4.197,53.321]],[[-2.549,59.231,-2.662,59.23,-2.604,59.289,-2.536,59.304,-2.407,59.298,-2.43,59.271,-2.549,59.231]],[[-1.043,60.514,-1.068,60.502,-1.166,60.604,-1.093,60.72,-1.006,60.717,-0.992,60.686,-1,60.658,-1.045,60.656,-1.049,60.647,-1.035,60.593,-1.034,60.53,-1.043,60.514]],[[-1.308,60.538,-1.287,60.467,-1.236,60.485,-1.158,60.418,-1.118,60.418,-1.052,60.444,-1.066,60.382,-1.134,60.207,-1.153,60.177,-1.166,60.124,-1.179,60.114,-1.199,60.007,-1.245,59.971,-1.284,59.887,-1.299,59.879,-1.356,59.911,-1.3,60.04,-1.276,60.115,-1.291,60.153,-1.323,60.188,-1.409,60.19,-1.481,60.173,-1.497,60.194,-1.499,60.222,-1.517,60.231,-1.613,60.229,-1.641,60.237,-1.66,60.262,-1.664,60.283,-1.577,60.298,-1.494,60.292,-1.375,60.333,-1.45,60.469,-1.549,60.481,-1.572,60.494,-1.553,60.517,-1.498,60.53,-1.414,60.599,-1.364,60.61,-1.302,60.608,-1.308,60.538]],[[-0.774,60.812,-0.774,60.8,-0.826,60.716,-0.825,60.684,-0.909,60.687,-0.922,60.697,-0.938,60.746,-0.928,60.797,-0.916,60.81,-0.891,60.816,-0.865,60.806,-0.823,60.832,-0.802,60.831,-0.774,60.812]],[[-3.165,58.794,-3.222,58.781,-3.279,58.782,-3.367,58.84,-3.401,58.882,-3.395,58.91,-3.357,58.919,-3.272,58.905,-3.228,58.857,-3.222,58.826,-3.212,58.814,-3.159,58.801,-3.165,58.794]],[[-2.929,58.742,-2.939,58.739,-2.975,58.757,-3.035,58.823,-2.941,58.836,-2.896,58.828,-2.913,58.8,-2.929,58.742]],[[-3.057,59.03,-3.071,59.005,-2.995,59.006,-2.885,58.985,-2.818,58.982,-2.762,58.956,-2.793,58.907,-2.826,58.893,-2.864,58.891,-2.995,58.939,-3.167,58.919,-3.201,58.925,-3.223,58.939,-3.233,58.956,-3.233,58.99,-3.242,59,-3.304,58.967,-3.332,58.971,-3.347,58.987,-3.354,59.019,-3.347,59.065,-3.31,59.131,-3.249,59.144,-3.156,59.136,-3.051,59.099,-3.019,59.076,-3.02,59.058,-3.057,59.03]],[[-2.729,59.187,-2.815,59.162,-2.852,59.182,-2.861,59.247,-2.964,59.274,-3.013,59.291,-3.052,59.324,-3.042,59.334,-2.976,59.347,-2.862,59.288,-2.815,59.241,-2.731,59.227,-2.72,59.219,-2.729,59.187]],[[-6.608,56.585,-6.664,56.579,-6.669,56.594,-6.57,56.661,-6.506,56.672,-6.484,56.666,-6.53,56.627,-6.608,56.585]],[[-5.105,55.449,-5.231,55.448,-5.277,55.457,-5.331,55.481,-5.393,55.618,-5.371,55.667,-5.346,55.691,-5.318,55.709,-5.252,55.717,-5.185,55.691,-5.16,55.667,-5.105,55.574,-5.095,55.494,-5.105,55.449]],[[-5.778,56.344,-6.176,56.289,-6.313,56.294,-6.326,56.321,-6.298,56.339,-6.185,56.357,-6.139,56.491,-6.311,56.552,-6.32,56.569,-6.306,56.599,-6.286,56.612,-6.182,56.643,-6.138,56.65,-6.103,56.646,-6.03,56.61,-5.947,56.535,-5.836,56.523,-5.761,56.491,-5.778,56.344]],[[-6.129,55.931,-6.093,55.802,-6.058,55.723,-6.055,55.695,-6.088,55.658,-6.253,55.607,-6.305,55.607,-6.307,55.619,-6.27,55.67,-6.302,55.728,-6.286,55.773,-6.302,55.781,-6.334,55.774,-6.452,55.704,-6.491,55.697,-6.496,55.712,-6.466,55.769,-6.463,55.808,-6.445,55.832,-6.413,55.855,-6.375,55.871,-6.344,55.874,-6.311,55.856,-6.216,55.905,-6.129,55.931]],[[-5.97,55.815,-5.991,55.804,-6.042,55.807,-6.06,55.823,-6.071,55.848,-6.072,55.893,-6.041,55.926,-5.912,55.975,-5.97,55.992,-5.973,56.004,-5.939,56.045,-5.8,56.109,-5.762,56.12,-5.725,56.119,-5.797,56.006,-5.97,55.815]],[[-6.199,58.363,-6.326,58.189,-6.376,58.185,-6.419,58.141,-6.555,58.093,-6.437,58.092,-6.403,58.076,-6.402,58.041,-6.425,58.021,-6.578,57.941,-6.683,57.911,-6.797,57.828,-6.854,57.827,-6.91,57.773,-6.957,57.75,-6.983,57.75,-7.013,57.762,-7.083,57.814,-6.956,57.865,-6.944,57.894,-6.857,57.924,-6.864,57.933,-7.003,57.975,-7.057,58.003,-7.052,58.018,-6.985,58.05,-7.017,58.055,-7.038,58.072,-7.077,58.079,-7.088,58.095,-7.096,58.138,-7.085,58.182,-7.045,58.202,-7.028,58.222,-7.012,58.229,-6.95,58.218,-6.886,58.183,-6.812,58.196,-6.726,58.189,-6.725,58.198,-6.788,58.284,-6.776,58.302,-6.742,58.322,-6.544,58.383,-6.297,58.487,-6.237,58.503,-6.219,58.489,-6.194,58.435,-6.199,58.363]],[[-6.279,56.965,-6.309,56.952,-6.346,56.954,-6.383,56.971,-6.433,57.018,-6.322,57.051,-6.278,57.031,-6.261,57.01,-6.261,56.985,-6.279,56.965]],[[-6.145,57.505,-6.146,57.461,-6.164,57.409,-6.141,57.354,-6.136,57.314,-6.093,57.302,-6.068,57.284,-5.88,57.263,-5.706,57.269,-5.672,57.253,-5.669,57.227,-5.696,57.198,-5.795,57.147,-5.914,57.063,-5.949,57.045,-5.987,57.044,-6.015,57.052,-6.034,57.201,-6.163,57.182,-6.266,57.184,-6.323,57.202,-6.362,57.238,-6.442,57.327,-6.675,57.363,-6.741,57.412,-6.761,57.442,-6.753,57.459,-6.704,57.496,-6.643,57.483,-6.606,57.491,-6.583,57.507,-6.583,57.521,-6.615,57.553,-6.617,57.563,-6.379,57.603,-6.358,57.667,-6.306,57.672,-6.247,57.651,-6.166,57.585,-6.145,57.505]],[[-7.206,57.683,-7.093,57.627,-7.183,57.533,-7.321,57.534,-7.515,57.602,-7.516,57.616,-7.499,57.636,-7.47,57.653,-7.44,57.656,-7.392,57.645,-7.325,57.663,-7.271,57.657,-7.206,57.683]],[[-7.25,57.115,-7.292,57.11,-7.347,57.115,-7.381,57.131,-7.416,57.192,-7.422,57.229,-7.407,57.298,-7.411,57.381,-7.296,57.384,-7.267,57.372,-7.248,57.126,-7.25,57.115]],[[-7.417,56.965,-7.505,56.952,-7.537,56.96,-7.543,56.972,-7.523,57.007,-7.455,57.019,-7.407,57,-7.399,56.983,-7.417,56.965]],[[-6.218,54.089,-6.304,54.095,-6.364,54.077,-6.403,54.061,-6.44,54.064,-6.548,54.057,-6.65,54.059,-6.664,54.085,-6.647,54.163,-6.67,54.185,-6.767,54.196,-6.803,54.214,-6.858,54.269,-6.869,54.294,-6.877,54.329,-6.936,54.374,-7.008,54.407,-7.05,54.408,-7.133,54.355,-7.203,54.302,-7.178,54.275,-7.155,54.24,-7.193,54.214,-7.307,54.156,-7.325,54.133,-7.355,54.121,-7.409,54.137,-7.544,54.134,-7.607,54.144,-7.679,54.187,-7.855,54.215,-7.884,54.284,-7.918,54.297,-8.118,54.414,-8.145,54.454,-8.119,54.477,-8.044,54.512,-7.794,54.571,-7.754,54.595,-7.746,54.616,-7.82,54.64,-7.886,54.666,-7.909,54.683,-7.911,54.698,-7.873,54.718,-7.797,54.719,-7.737,54.71,-7.69,54.728,-7.606,54.746,-7.55,54.768,-7.502,54.825,-7.451,54.877,-7.446,54.905,-7.401,55.003,-7.377,55.028,-7.219,55.092,-7.179,55.057,-7.101,55.048,-7.031,55.081,-6.947,55.183,-6.889,55.189,-6.825,55.181,-6.699,55.193,-6.475,55.241,-6.375,55.242,-6.234,55.217,-6.129,55.217,-6.036,55.145,-5.986,55.03,-5.869,54.916,-5.717,54.817,-5.711,54.757,-5.765,54.725,-5.879,54.684,-5.879,54.641,-5.803,54.663,-5.739,54.673,-5.583,54.663,-5.528,54.62,-5.49,54.554,-5.47,54.5,-5.484,54.442,-5.526,54.46,-5.569,54.513,-5.616,54.537,-5.671,54.55,-5.646,54.478,-5.656,54.382,-5.632,54.373,-5.558,54.371,-5.607,54.273,-5.708,54.246,-5.826,54.236,-5.855,54.201,-5.876,54.156,-5.938,54.089,-6.019,54.051,-6.12,54.059,-6.218,54.089]],[[-1.066,50.69,-1.149,50.656,-1.176,50.615,-1.196,50.599,-1.251,50.589,-1.306,50.589,-1.515,50.67,-1.563,50.666,-1.516,50.703,-1.386,50.734,-1.313,50.773,-1.144,50.735,-1.066,50.69]]]},{"n":"United Arab Emirates","c":"AE","r":4,"ml":4,"lx":54.55,"ly":23.47,"b":[51.568,22.621,56.388,26.068],"p":[[[56.298,25.651,56.363,25.569,56.373,25.018,56.388,24.979,56.353,24.973,56.314,24.931,56.268,24.867,56.205,24.833,56.154,24.796,56.107,24.749,56.064,24.739,56.008,24.798,55.97,24.859,55.98,24.872,56.006,24.876,56.017,24.908,56.001,24.953,55.963,24.97,55.916,24.972,55.871,24.951,55.823,24.911,55.796,24.868,55.792,24.781,55.804,24.684,55.804,24.636,55.778,24.577,55.768,24.491,55.787,24.424,55.804,24.384,55.806,24.35,55.761,24.243,55.8,24.223,55.929,24.215,55.966,24.143,55.992,24.093,55.985,24.063,55.894,24.041,55.779,24.017,55.697,24.024,55.548,23.991,55.468,23.941,55.492,23.91,55.519,23.885,55.532,23.819,55.508,23.725,55.466,23.633,55.414,23.519,55.353,23.387,55.27,23.19,55.2,23.035,55.192,22.923,55.194,22.85,55.186,22.704,55.119,22.624,55.104,22.621,55.025,22.631,54.998,22.634,54.922,22.644,54.805,22.658,54.652,22.677,54.472,22.699,54.27,22.723,54.055,22.75,53.832,22.777,53.61,22.804,53.394,22.83,53.192,22.855,53.012,22.877,52.859,22.896,52.742,22.91,52.666,22.919,52.639,22.923,52.555,22.933,52.51,22.987,52.455,23.052,52.4,23.118,52.345,23.183,52.29,23.249,52.235,23.314,52.179,23.38,52.125,23.445,52.069,23.511,52.014,23.576,51.959,23.642,51.904,23.708,51.849,23.773,51.794,23.838,51.739,23.904,51.684,23.97,51.629,24.035,51.593,24.079,51.572,24.128,51.568,24.258,51.568,24.286,51.605,24.338,51.623,24.301,51.665,24.25,51.735,24.263,51.768,24.254,51.792,24.075,51.843,24.011,51.906,23.985,52.119,23.971,52.251,23.995,52.511,24.113,52.648,24.155,53.026,24.147,53.33,24.098,53.802,24.069,53.893,24.077,54.148,24.171,54.304,24.254,54.397,24.278,54.458,24.358,54.499,24.463,54.535,24.531,54.58,24.564,54.624,24.621,54.659,24.716,54.747,24.81,55.098,25.042,55.304,25.237,55.322,25.3,55.433,25.394,55.523,25.498,55.941,25.794,56.025,25.916,56.075,26.053,56.08,26.063,56.117,26.068,56.167,26.047,56.173,25.945,56.154,25.848,56.152,25.746,56.145,25.691,56.184,25.645,56.25,25.625,56.279,25.628,56.298,25.651],[56.282,25.236,56.288,25.279,56.277,25.301,56.234,25.304,56.217,25.267,56.211,25.213,56.24,25.209,56.282,25.236]],[[53.332,24.259,53.258,24.253,53.191,24.291,53.333,24.342,53.371,24.364,53.412,24.411,53.445,24.371,53.409,24.308,53.383,24.281,53.332,24.259]],[[52.617,24.289,52.6,24.281,52.582,24.335,52.584,24.352,52.629,24.377,52.658,24.333,52.617,24.289]],[[53.928,24.177,53.928,24.143,53.826,24.153,53.799,24.136,53.716,24.145,53.634,24.17,53.69,24.211,53.834,24.259,53.894,24.215,53.928,24.177]],[[54.465,24.443,54.457,24.423,54.428,24.425,54.358,24.443,54.335,24.471,54.379,24.505,54.398,24.506,54.427,24.471,54.465,24.443]]]},{"n":"Ukraine","c":"UA","r":3,"ml":2.7,"lx":32.14,"ly":49.72,"b":[22.132,45.234,40.128,52.354],"p":[[[38.214,47.091,38.178,47.08,37.829,47.096,37.761,47.091,37.543,47.075,37.34,46.917,37.219,46.917,37.048,46.876,36.932,46.825,36.795,46.714,36.689,46.764,36.559,46.763,36.432,46.733,36.279,46.659,36.195,46.646,36.025,46.667,35.827,46.624,35.4,46.381,35.257,46.204,35.204,46.169,35.132,46.126,35.055,46.104,35.015,46.106,35.218,46.232,35.28,46.279,35.291,46.314,35.292,46.371,35.23,46.441,35.064,46.267,34.97,46.242,34.85,46.19,34.844,46.074,34.857,45.987,34.907,45.879,35.002,45.733,34.946,45.729,34.8,45.791,34.794,45.892,34.687,45.977,34.523,45.977,34.45,45.966,34.354,46.062,34.224,46.101,34.128,46.09,34.027,46.107,33.807,46.208,33.66,46.22,33.654,46.146,33.594,46.096,33.499,46.079,33.43,46.058,33.263,46.126,33.202,46.176,32.942,46.124,32.797,46.131,32.477,46.084,32.33,46.13,32.036,46.261,31.925,46.287,31.831,46.282,31.78,46.325,31.843,46.346,31.916,46.349,31.992,46.364,32.013,46.387,32.008,46.43,31.856,46.462,31.714,46.472,31.624,46.51,31.555,46.554,31.716,46.555,31.878,46.522,32.131,46.509,32.361,46.475,32.419,46.518,32.553,46.592,32.578,46.616,32.354,46.565,32.127,46.597,32.044,46.642,31.974,46.709,31.945,46.784,31.964,46.855,31.94,46.982,31.865,47.095,31.838,47.157,31.759,47.213,31.837,47.087,31.866,47.003,31.913,46.926,31.902,46.722,31.873,46.65,31.78,46.632,31.657,46.642,31.532,46.665,31.563,46.777,31.497,46.738,31.403,46.629,31.32,46.613,31.137,46.624,30.796,46.552,30.773,46.473,30.722,46.366,30.672,46.304,30.657,46.267,30.512,46.105,30.493,46.09,30.219,45.867,30.184,45.85,30.007,45.798,29.902,45.752,29.821,45.732,29.685,45.755,29.628,45.722,29.602,45.683,29.601,45.6,29.67,45.541,29.727,45.343,29.706,45.26,29.652,45.314,29.568,45.371,29.404,45.42,29.224,45.403,29.027,45.321,28.894,45.29,28.824,45.311,28.782,45.31,28.767,45.286,28.77,45.267,28.791,45.252,28.788,45.241,28.761,45.234,28.451,45.292,28.318,45.347,28.213,45.45,28.265,45.484,28.31,45.499,28.471,45.507,28.499,45.518,28.502,45.542,28.514,45.572,28.509,45.618,28.492,45.666,28.562,45.736,28.668,45.794,28.729,45.852,28.739,45.937,28.85,45.979,28.948,46.05,28.972,46.128,29.006,46.176,28.944,46.288,28.931,46.362,28.927,46.424,28.958,46.458,29.05,46.497,29.146,46.527,29.186,46.524,29.201,46.505,29.205,46.379,29.224,46.377,29.255,46.393,29.305,46.467,29.34,46.445,29.393,46.437,29.433,46.456,29.459,46.454,29.491,46.435,29.555,46.408,29.615,46.399,29.665,46.417,29.707,46.449,29.752,46.438,29.838,46.351,29.878,46.36,30.076,46.378,30.108,46.402,30.131,46.423,29.924,46.539,29.935,46.625,29.942,46.724,29.918,46.782,29.878,46.829,29.72,46.883,29.598,46.939,29.572,46.964,29.569,46.997,29.563,47.048,29.515,47.091,29.511,47.128,29.542,47.186,29.549,47.247,29.539,47.271,29.511,47.291,29.456,47.293,29.383,47.328,29.334,47.376,29.201,47.444,29.16,47.456,29.135,47.49,29.123,47.53,29.151,47.581,29.186,47.659,29.211,47.732,29.211,47.775,29.195,47.882,29.125,47.965,29.093,47.975,29.037,47.952,28.973,47.933,28.923,47.951,28.866,47.996,28.774,48.12,28.602,48.144,28.53,48.15,28.463,48.091,28.442,48.109,28.423,48.147,28.388,48.162,28.341,48.144,28.327,48.161,28.347,48.213,28.291,48.239,28.159,48.238,28.088,48.257,28.08,48.296,28.038,48.321,27.963,48.334,27.891,48.365,27.82,48.416,27.714,48.45,27.574,48.465,27.562,48.47,27.549,48.478,27.458,48.443,27.404,48.416,27.337,48.433,27.229,48.371,27.008,48.368,26.901,48.372,26.847,48.387,26.64,48.294,26.619,48.26,26.572,48.248,26.442,48.23,26.306,48.204,26.277,48.113,26.236,48.064,26.163,47.993,25.909,47.968,25.689,47.932,25.464,47.911,25.17,47.823,25.074,47.746,24.979,47.724,24.893,47.718,24.838,47.761,24.651,47.877,24.579,47.931,24.484,47.947,24.381,47.938,24.282,47.911,24.178,47.906,24.06,47.945,24.047,47.941,24.002,47.936,23.709,47.983,23.682,47.99,23.669,47.992,23.629,47.996,23.408,47.99,23.203,48.085,23.139,48.087,23.091,48.049,23.055,48.007,22.913,47.964,22.877,47.947,22.856,47.96,22.846,47.997,22.857,48.03,22.836,48.06,22.782,48.095,22.769,48.11,22.702,48.107,22.683,48.104,22.676,48.104,22.582,48.134,22.52,48.205,22.424,48.243,22.35,48.256,22.317,48.287,22.295,48.327,22.272,48.358,22.269,48.361,22.254,48.407,22.231,48.412,22.227,48.413,22.132,48.405,22.143,48.569,22.295,48.686,22.333,48.745,22.389,48.873,22.432,48.934,22.483,48.983,22.524,49.031,22.539,49.073,22.58,49.077,22.701,49.04,22.81,49.021,22.84,49.039,22.852,49.063,22.847,49.081,22.76,49.136,22.706,49.171,22.702,49.193,22.722,49.241,22.732,49.295,22.72,49.354,22.661,49.484,22.649,49.539,22.706,49.606,22.891,49.766,22.952,49.826,23.036,49.899,23.264,50.073,23.409,50.174,23.506,50.23,23.649,50.327,23.712,50.377,23.973,50.41,24.005,50.457,24.053,50.508,24.09,50.53,24.095,50.617,24.046,50.723,24.007,50.76,23.978,50.786,23.997,50.809,24.026,50.816,24.062,50.82,24.106,50.845,24.096,50.873,23.986,50.94,23.938,50.993,23.863,51.126,23.712,51.265,23.664,51.31,23.658,51.352,23.68,51.395,23.659,51.449,23.605,51.518,23.614,51.525,23.609,51.61,23.647,51.629,23.707,51.641,23.792,51.637,23.864,51.624,23.951,51.585,23.978,51.591,24.127,51.665,24.28,51.775,24.324,51.838,24.362,51.868,24.495,51.883,24.611,51.89,24.685,51.888,24.866,51.899,24.974,51.911,25.067,51.931,25.267,51.938,25.58,51.925,25.786,51.924,25.925,51.914,26.267,51.855,26.394,51.844,26.453,51.813,26.567,51.802,26.773,51.771,26.953,51.754,27.074,51.761,27.142,51.752,27.27,51.614,27.296,51.597,27.348,51.594,27.452,51.606,27.601,51.602,27.69,51.572,27.677,51.49,27.7,51.478,27.741,51.483,27.789,51.529,27.829,51.577,27.859,51.592,28.011,51.56,28.08,51.565,28.144,51.602,28.184,51.608,28.292,51.582,28.425,51.564,28.532,51.562,28.599,51.543,28.648,51.457,28.69,51.439,28.731,51.433,28.793,51.51,28.85,51.54,28.928,51.562,28.978,51.572,29.013,51.599,29.061,51.625,29.102,51.628,29.136,51.617,29.174,51.581,29.23,51.497,29.299,51.413,29.346,51.383,29.47,51.408,29.553,51.435,29.706,51.44,29.909,51.458,30.064,51.482,30.161,51.478,30.22,51.451,30.309,51.4,30.333,51.326,30.45,51.274,30.545,51.265,30.577,51.318,30.633,51.355,30.612,51.406,30.602,51.471,30.561,51.531,30.533,51.596,30.584,51.689,30.639,51.77,30.667,51.814,30.755,51.895,30.846,51.953,30.981,52.046,31.079,52.077,31.168,52.063,31.218,52.05,31.346,52.105,31.574,52.108,31.763,52.101,31.782,52.099,31.876,52.071,31.974,52.047,32.042,52.045,32.122,52.051,32.217,52.083,32.283,52.114,32.363,52.272,32.391,52.295,32.435,52.307,32.508,52.309,32.645,52.279,32.806,52.253,32.9,52.256,33.148,52.34,33.287,52.354,33.452,52.334,33.613,52.333,33.735,52.345,33.819,52.316,33.922,52.251,34.015,52.156,34.113,51.98,34.398,51.78,34.403,51.742,34.379,51.717,34.239,51.692,34.121,51.679,34.115,51.645,34.147,51.608,34.201,51.554,34.209,51.484,34.207,51.42,34.23,51.363,34.275,51.34,34.281,51.312,34.228,51.277,34.214,51.255,34.234,51.244,34.491,51.237,34.617,51.203,34.712,51.172,34.76,51.169,34.869,51.189,34.99,51.202,35.064,51.203,35.093,51.181,35.115,51.121,35.158,51.061,35.198,51.044,35.269,51.047,35.312,51.044,35.335,51.021,35.309,50.987,35.315,50.95,35.346,50.904,35.383,50.799,35.417,50.768,35.44,50.728,35.44,50.682,35.412,50.642,35.392,50.611,35.412,50.54,35.488,50.46,35.546,50.44,35.591,50.369,35.674,50.346,35.796,50.406,35.89,50.437,36.008,50.42,36.116,50.409,36.189,50.368,36.243,50.312,36.306,50.28,36.369,50.297,36.5,50.28,36.56,50.235,36.619,50.209,36.696,50.246,36.759,50.292,36.988,50.34,37.131,50.352,37.171,50.361,37.255,50.395,37.343,50.418,37.423,50.411,37.501,50.341,37.582,50.292,37.605,50.215,37.704,50.109,37.95,49.964,38.047,49.92,38.113,49.928,38.147,49.939,38.163,49.955,38.178,50.025,38.209,50.051,38.259,50.052,38.451,49.964,38.552,49.955,38.648,49.953,38.777,49.884,38.918,49.825,39.028,49.818,39.115,49.842,39.175,49.856,39.212,49.833,39.246,49.782,39.303,49.742,39.368,49.731,39.463,49.728,39.627,49.651,39.781,49.572,39.877,49.568,39.958,49.591,40.031,49.597,40.081,49.577,40.095,49.543,40.058,49.497,40.058,49.432,40.126,49.369,40.128,49.307,40.109,49.252,40.07,49.2,39.976,49.13,39.89,49.064,39.759,49.037,39.687,49.008,39.706,48.96,39.753,48.914,39.864,48.878,39.989,48.851,40.004,48.822,39.984,48.807,39.904,48.794,39.793,48.808,39.756,48.782,39.705,48.739,39.67,48.662,39.645,48.591,39.765,48.572,39.836,48.543,39.858,48.484,39.883,48.419,39.89,48.36,39.85,48.332,39.847,48.303,39.866,48.288,39.918,48.282,39.958,48.269,39.961,48.238,39.885,48.168,39.814,48.035,39.776,47.964,39.779,47.888,39.736,47.845,39.658,47.841,39.391,47.834,39.158,47.837,39.058,47.848,38.9,47.855,38.822,47.837,38.719,47.714,38.641,47.666,38.511,47.622,38.369,47.61,38.287,47.559,38.259,47.48,38.257,47.409,38.243,47.374,38.212,47.343,38.201,47.321,38.208,47.297,38.241,47.288,38.281,47.277,38.281,47.259,38.265,47.237,38.221,47.213,38.201,47.175,38.206,47.136,38.214,47.091]],[[32.012,46.204,32.15,46.155,32.009,46.168,31.7,46.214,31.564,46.258,31.529,46.307,31.509,46.373,31.585,46.303,31.638,46.273,32.012,46.204]]]},{"n":"Uganda","c":"UG","r":3,"ml":3,"lx":32.95,"ly":1.97,"b":[29.562,-1.47,34.978,4.22],"p":[[[33.903,-1.002,33.794,-1.002,33.616,-1.002,33.438,-1.002,33.261,-1.002,33.083,-1.002,32.905,-1.002,32.727,-1.002,32.55,-1.002,32.372,-1.002,32.194,-1.002,32.016,-1.002,31.839,-1.002,31.661,-1.002,31.483,-1.002,31.305,-1.002,31.128,-1.002,30.95,-1.002,30.845,-1.002,30.824,-0.999,30.809,-0.995,30.742,-1.008,30.673,-1.051,30.599,-1.07,30.52,-1.062,30.51,-1.067,30.47,-1.066,30.412,-1.063,30.36,-1.075,30.321,-1.113,30.28,-1.179,30.207,-1.254,30.15,-1.321,30.102,-1.369,29.991,-1.447,29.93,-1.47,29.9,-1.466,29.882,-1.452,29.847,-1.352,29.825,-1.336,29.61,-1.387,29.577,-1.388,29.58,-1.357,29.564,-1.121,29.562,-0.977,29.59,-0.887,29.606,-0.783,29.608,-0.691,29.648,-0.535,29.633,-0.442,29.684,-0.114,29.698,-0.06,29.718,0.098,29.75,0.147,29.778,0.166,29.815,0.264,29.885,0.419,29.934,0.499,29.924,0.674,29.932,0.793,29.943,0.819,30.047,0.864,30.183,0.973,30.24,1.103,30.321,1.185,30.478,1.239,30.943,1.683,31.159,1.922,31.253,2.045,31.256,2.088,31.274,2.146,31.236,2.191,31.191,2.232,31.176,2.27,31.138,2.289,31.082,2.288,31.045,2.316,31.004,2.369,30.962,2.403,30.83,2.4,30.729,2.455,30.73,2.53,30.77,2.678,30.847,2.847,30.851,2.894,30.84,2.933,30.821,2.968,30.787,3.001,30.754,3.042,30.779,3.163,30.828,3.283,30.868,3.342,30.906,3.409,30.895,3.464,30.839,3.491,30.868,3.544,30.929,3.634,31.048,3.725,31.152,3.786,31.222,3.786,31.357,3.738,31.48,3.68,31.547,3.678,31.629,3.701,31.798,3.803,31.839,3.77,31.888,3.709,31.942,3.608,32.048,3.561,32.099,3.529,32.136,3.52,32.156,3.528,32.197,3.608,32.246,3.651,32.336,3.706,32.535,3.75,32.677,3.763,32.737,3.773,32.838,3.798,32.997,3.88,33.154,3.775,33.324,3.754,33.489,3.755,33.54,3.787,33.568,3.812,33.742,3.985,33.976,4.22,34.132,3.889,34.186,3.87,34.178,3.841,34.165,3.813,34.267,3.733,34.393,3.692,34.438,3.651,34.442,3.606,34.399,3.413,34.407,3.358,34.448,3.163,34.523,3.12,34.589,2.925,34.723,2.842,34.742,2.818,34.773,2.723,34.814,2.62,34.847,2.596,34.866,2.59,34.906,2.48,34.883,2.418,34.914,2.23,34.964,2.062,34.978,1.862,34.978,1.774,34.976,1.72,34.965,1.643,34.941,1.599,34.898,1.556,34.851,1.489,34.81,1.417,34.784,1.381,34.804,1.273,34.799,1.245,34.788,1.231,34.727,1.214,34.649,1.185,34.602,1.156,34.535,1.102,34.482,1.042,34.411,0.867,34.293,0.731,34.273,0.686,34.161,0.605,34.112,0.505,34.081,0.382,34.037,0.295,33.943,0.174,33.921,-0.017,33.924,-0.398,33.9,-0.832,33.903,-1.002]]]},{"n":"Turkmenistan","c":"TM","r":4,"ml":3,"lx":58.68,"ly":39.86,"b":[52.494,35.171,66.629,42.778],"p":[[[53.11,38.803,53.1,38.756,53.046,38.897,53.019,39.053,53.053,39.097,53.092,39.094,53.055,39.038,53.11,38.803]],[[55.977,41.322,56.242,41.311,56.48,41.301,56.774,41.288,56.861,41.276,56.966,41.265,57.018,41.263,57.064,41.307,57.095,41.331,57.119,41.35,57.114,41.372,57.077,41.39,57.018,41.451,56.985,41.669,56.964,41.857,57.034,41.915,57.114,41.957,57.229,42.084,57.291,42.124,57.382,42.156,57.686,42.165,57.814,42.19,57.856,42.231,57.923,42.335,57.946,42.42,57.983,42.459,58.029,42.488,58.075,42.487,58.166,42.462,58.234,42.448,58.283,42.429,58.327,42.399,58.371,42.347,58.377,42.312,58.397,42.292,58.431,42.292,58.457,42.292,58.474,42.299,58.486,42.317,58.477,42.34,58.418,42.407,58.289,42.527,58.204,42.576,58.162,42.603,58.152,42.628,58.206,42.666,58.26,42.688,58.353,42.672,58.477,42.663,58.532,42.682,58.589,42.778,58.73,42.676,58.877,42.561,58.931,42.54,59.036,42.528,59.123,42.524,59.16,42.511,59.199,42.482,59.277,42.356,59.354,42.323,59.451,42.3,59.763,42.302,59.858,42.295,59.937,42.236,59.985,42.212,60.006,42.191,60.001,42.165,59.982,42.132,59.979,42.068,59.974,42.019,59.949,41.995,59.942,41.974,59.963,41.954,60.107,41.907,60.156,41.857,60.192,41.834,60.201,41.803,60.176,41.782,60.109,41.793,60.076,41.76,60.076,41.701,60.124,41.645,60.138,41.594,60.106,41.545,60.069,41.476,60.067,41.427,60.09,41.399,60.2,41.349,60.455,41.222,60.514,41.216,60.755,41.246,60.867,41.249,60.933,41.229,61.12,41.211,61.179,41.191,61.242,41.189,61.329,41.195,61.388,41.252,61.417,41.265,61.444,41.275,61.497,41.276,61.645,41.24,61.8,41.163,61.903,41.094,61.954,41.031,62.018,40.894,62.095,40.683,62.188,40.541,62.298,40.467,62.375,40.332,62.442,40.036,62.483,39.976,62.525,39.944,62.651,39.858,62.907,39.717,63.058,39.633,63.292,39.5,63.506,39.377,63.721,39.188,63.764,39.161,63.953,39.058,64.163,38.954,64.31,38.977,64.532,38.816,64.622,38.756,64.66,38.736,64.821,38.672,65.077,38.539,65.4,38.349,65.613,38.239,65.671,38.226,65.729,38.226,65.79,38.25,65.857,38.269,65.971,38.244,66.095,38.2,66.173,38.167,66.264,38.118,66.335,38.072,66.39,38.051,66.575,38.011,66.606,37.987,66.626,37.96,66.629,37.932,66.526,37.786,66.511,37.599,66.511,37.459,66.522,37.348,66.472,37.345,66.35,37.368,66.108,37.415,65.901,37.508,65.765,37.569,65.744,37.561,65.683,37.519,65.641,37.468,65.608,37.368,65.555,37.251,65.304,37.247,65.09,37.238,64.952,37.194,64.816,37.132,64.782,37.059,64.753,36.965,64.674,36.75,64.603,36.555,64.566,36.428,64.511,36.341,64.358,36.226,64.184,36.149,64.092,36.113,64.051,36.068,64.042,36.025,64.01,36.012,63.938,36.02,63.863,36.012,63.697,35.968,63.517,35.913,63.302,35.858,63.179,35.858,63.13,35.846,63.109,35.819,63.13,35.767,63.151,35.728,63.17,35.678,63.119,35.638,63.084,35.568,63.057,35.446,62.98,35.409,62.858,35.35,62.723,35.271,62.688,35.255,62.611,35.233,62.533,35.24,62.463,35.251,62.387,35.231,62.308,35.171,62.271,35.189,62.253,35.25,62.213,35.29,62.09,35.38,61.984,35.444,61.938,35.448,61.841,35.431,61.72,35.419,61.621,35.432,61.543,35.458,61.422,35.546,61.378,35.593,61.345,35.629,61.262,35.62,61.239,35.659,61.236,35.706,61.259,35.762,61.252,35.868,61.206,35.944,61.153,35.977,61.159,36,61.183,36.053,61.212,36.099,61.212,36.191,61.175,36.29,61.16,36.433,61.17,36.572,61.12,36.643,60.708,36.643,60.341,36.638,60.321,36.654,60.178,36.829,60.063,36.963,59.949,37.042,59.687,37.138,59.562,37.179,59.455,37.253,59.367,37.334,59.345,37.445,59.327,37.481,59.302,37.511,59.274,37.524,59.241,37.521,58.937,37.65,58.815,37.683,58.701,37.656,58.65,37.652,58.55,37.688,58.436,37.639,58.387,37.635,58.318,37.647,58.262,37.666,58.109,37.783,57.981,37.83,57.888,37.861,57.711,37.905,57.521,37.928,57.424,37.948,57.354,37.973,57.336,37.99,57.337,38.033,57.331,38.089,57.308,38.13,57.26,38.18,57.194,38.216,57.079,38.21,56.907,38.213,56.775,38.25,56.67,38.257,56.544,38.25,56.441,38.249,56.367,38.223,56.324,38.191,56.297,38.095,56.272,38.08,56.229,38.073,56.171,38.078,56.05,38.078,55.841,38.095,55.578,38.1,55.381,38.051,55.225,37.981,55.076,37.902,54.9,37.778,54.849,37.723,54.745,37.502,54.699,37.47,54.64,37.445,54.579,37.44,54.459,37.408,54.3,37.354,54.192,37.332,53.914,37.344,53.898,37.414,53.848,37.67,53.824,37.928,53.825,38.047,53.854,38.286,53.852,38.406,53.84,38.515,53.852,38.622,53.874,38.742,53.885,38.864,53.869,38.949,53.815,39.018,53.724,39.103,53.71,39.153,53.705,39.21,53.618,39.216,53.539,39.274,53.475,39.306,53.336,39.341,53.267,39.343,53.203,39.317,53.157,39.265,53.124,39.347,53.125,39.432,53.236,39.609,53.305,39.557,53.39,39.536,53.497,39.533,53.603,39.547,53.582,39.607,53.533,39.642,53.472,39.669,53.45,39.749,53.458,39.831,53.487,39.909,53.454,39.941,53.404,39.96,53.289,39.958,53.139,39.979,52.988,39.988,52.952,39.895,53.036,39.774,52.965,39.834,52.898,39.913,52.805,40.054,52.744,40.22,52.734,40.399,52.785,40.547,52.85,40.686,52.889,40.863,52.943,41.038,52.998,40.96,53.06,40.89,53.145,40.825,53.192,40.809,53.333,40.783,53.424,40.793,53.52,40.831,53.615,40.819,53.694,40.746,53.764,40.666,53.87,40.649,54.089,40.707,54.193,40.72,54.283,40.694,54.33,40.689,54.377,40.693,54.336,40.765,54.319,40.835,54.374,40.871,54.547,40.832,54.657,40.858,54.685,40.873,54.71,40.891,54.723,40.951,54.718,41.013,54.704,41.071,54.671,41.122,54.592,41.194,54.285,41.364,54.181,41.432,54.095,41.519,54.04,41.643,53.995,41.773,53.954,41.868,53.846,42.091,53.805,42.118,53.752,42.129,53.625,42.136,53.496,42.12,53.285,42.082,53.164,42.094,53.108,42.07,52.97,41.976,52.905,41.896,52.815,41.712,52.883,41.653,52.882,41.614,52.83,41.342,52.862,41.21,52.85,41.2,52.826,41.231,52.747,41.365,52.609,41.529,52.494,41.78,52.697,41.944,52.871,42.061,53.013,42.131,53.056,42.148,53.25,42.206,53.501,42.258,53.685,42.297,53.926,42.33,54.005,42.336,54.121,42.335,54.215,42.304,54.272,42.28,54.473,42.18,54.678,42.078,54.854,41.965,54.904,41.919,54.932,41.864,54.952,41.81,55.102,41.639,55.162,41.56,55.25,41.458,55.32,41.408,55.388,41.347,55.434,41.296,55.487,41.272,55.545,41.263,55.679,41.279,55.839,41.311,55.935,41.324,55.977,41.322]]]},{"n":"Turkey","c":"TR","r":2,"ml":2,"lx":34.51,"ly":39.35,"b":[25.669,35.831,44.817,42.093],"p":[[[25.97,40.136,25.74,40.105,25.669,40.136,25.741,40.196,25.875,40.234,25.918,40.238,25.977,40.178,25.97,40.136]],[[41.51,41.517,41.577,41.497,41.702,41.472,41.779,41.441,41.824,41.432,41.926,41.496,42.078,41.494,42.211,41.487,42.28,41.475,42.364,41.454,42.466,41.44,42.508,41.47,42.567,41.559,42.59,41.571,42.607,41.579,42.682,41.586,42.754,41.579,42.788,41.564,42.822,41.492,42.907,41.467,43.057,41.353,43.149,41.307,43.171,41.288,43.141,41.265,43.153,41.236,43.205,41.199,43.279,41.185,43.359,41.19,43.402,41.177,43.433,41.156,43.442,41.126,43.439,41.107,43.455,41.065,43.517,41.005,43.592,40.968,43.632,40.929,43.696,40.794,43.723,40.72,43.713,40.648,43.668,40.574,43.569,40.482,43.594,40.444,43.616,40.393,43.608,40.357,43.678,40.239,43.71,40.167,43.683,40.15,43.666,40.126,43.792,40.07,43.942,40.023,44.005,40.014,44.178,40.036,44.289,40.04,44.4,39.996,44.56,39.888,44.734,39.746,44.768,39.704,44.783,39.685,44.817,39.65,44.782,39.651,44.725,39.682,44.587,39.769,44.517,39.731,44.456,39.667,44.389,39.422,44.335,39.396,44.24,39.397,44.124,39.405,44.044,39.393,44.023,39.377,44.034,39.351,44.058,39.311,44.074,39.26,44.079,39.218,44.121,39.181,44.178,39.145,44.181,39.108,44.172,39.056,44.159,39.017,44.145,38.994,44.171,38.934,44.232,38.863,44.272,38.836,44.257,38.701,44.28,38.641,44.298,38.558,44.291,38.42,44.299,38.386,44.32,38.375,44.376,38.37,44.431,38.357,44.45,38.334,44.45,38.318,44.381,38.255,44.373,38.21,44.349,38.146,44.329,38.109,44.268,38.039,44.229,37.967,44.211,37.908,44.223,37.88,44.336,37.872,44.398,37.829,44.561,37.745,44.59,37.71,44.545,37.658,44.546,37.636,44.567,37.609,44.577,37.56,44.573,37.506,44.574,37.435,44.604,37.424,44.715,37.357,44.794,37.29,44.797,37.27,44.758,37.217,44.767,37.156,44.765,37.142,44.731,37.165,44.669,37.174,44.606,37.176,44.566,37.158,44.496,37.111,44.402,37.058,44.326,37.011,44.282,36.978,44.246,36.983,44.217,37.012,44.202,37.052,44.208,37.203,44.192,37.25,44.156,37.283,44.114,37.302,44.065,37.312,44.013,37.314,43.94,37.269,43.836,37.224,43.676,37.227,43.568,37.236,43.516,37.245,43.307,37.315,43.263,37.317,43.185,37.345,43.092,37.367,42.937,37.325,42.869,37.335,42.775,37.372,42.741,37.362,42.635,37.249,42.456,37.129,42.359,37.109,42.313,37.23,42.269,37.277,42.248,37.282,42.203,37.297,42.168,37.289,42.06,37.206,41.887,37.156,41.744,37.126,41.516,37.089,41.34,37.071,41.265,37.069,41.102,37.086,40.959,37.109,40.816,37.108,40.706,37.098,40.45,37.009,40.016,36.826,39.687,36.739,39.501,36.702,39.357,36.682,39.108,36.681,38.906,36.695,38.767,36.693,38.689,36.715,38.578,36.789,38.444,36.862,38.384,36.879,38.306,36.893,38.192,36.902,37.907,36.795,37.818,36.766,37.72,36.744,37.524,36.678,37.436,36.643,37.327,36.647,37.187,36.656,37.066,36.653,36.985,36.702,36.942,36.758,36.777,36.793,36.659,36.803,36.628,36.778,36.597,36.701,36.547,36.506,36.538,36.457,36.641,36.264,36.637,36.234,36.562,36.224,36.477,36.221,36.421,36.203,36.375,36.171,36.348,36.004,36.249,35.973,36.202,35.938,36.154,35.834,36.127,35.831,35.968,35.91,35.893,35.917,35.957,35.998,35.887,36.159,35.811,36.31,35.883,36.406,36.032,36.523,36.188,36.659,36.188,36.743,36.18,36.807,36.135,36.852,36.049,36.911,35.905,36.848,35.802,36.778,35.734,36.764,35.661,36.724,35.626,36.653,35.537,36.597,35.393,36.575,35.176,36.635,34.943,36.726,34.811,36.799,34.704,36.817,34.601,36.784,34.3,36.604,34.023,36.341,33.955,36.295,33.695,36.182,33.523,36.144,33.442,36.153,33.1,36.103,32.929,36.096,32.795,36.036,32.534,36.101,32.378,36.184,32.284,36.268,32.131,36.449,32.022,36.535,31.778,36.613,31.353,36.801,31.241,36.822,30.95,36.849,30.644,36.866,30.582,36.797,30.558,36.526,30.506,36.451,30.484,36.31,30.446,36.27,30.387,36.243,30.295,36.288,30.232,36.307,30.083,36.249,29.789,36.168,29.689,36.157,29.348,36.259,29.224,36.324,29.143,36.397,29.116,36.52,29.066,36.59,29.058,36.638,29.038,36.693,28.97,36.715,28.896,36.674,28.817,36.675,28.718,36.701,28.484,36.804,28.304,36.812,28.196,36.686,28.112,36.646,28.019,36.634,28.014,36.67,28.084,36.751,27.804,36.736,27.656,36.675,27.54,36.684,27.454,36.712,27.467,36.746,27.555,36.759,27.631,36.787,27.934,36.809,28.005,36.832,28.083,36.92,28.224,36.996,28.242,37.029,28.134,37.029,27.668,37.007,27.349,37.02,27.311,36.982,27.263,36.977,27.25,37.079,27.3,37.127,27.368,37.122,27.535,37.164,27.52,37.249,27.401,37.307,27.376,37.341,27.29,37.349,27.219,37.389,27.204,37.491,27.148,37.604,27.068,37.658,27.078,37.688,27.224,37.725,27.255,37.882,27.232,37.979,27.159,37.987,26.944,38.063,26.879,38.055,26.807,38.138,26.683,38.198,26.621,38.176,26.582,38.149,26.525,38.162,26.428,38.214,26.333,38.242,26.291,38.277,26.344,38.37,26.416,38.368,26.43,38.441,26.372,38.562,26.378,38.624,26.441,38.641,26.514,38.629,26.587,38.557,26.61,38.487,26.595,38.419,26.641,38.352,26.674,38.336,26.696,38.405,26.727,38.419,26.77,38.388,26.861,38.373,27.099,38.416,27.144,38.452,26.97,38.448,26.907,38.482,26.838,38.558,26.795,38.626,26.788,38.66,26.764,38.71,26.79,38.736,26.909,38.776,27.014,38.887,26.97,38.919,26.92,38.934,26.866,38.923,26.815,38.961,26.808,39.014,26.849,39.057,26.854,39.116,26.719,39.261,26.682,39.292,26.711,39.34,26.813,39.419,26.911,39.517,26.899,39.55,26.827,39.563,26.484,39.521,26.351,39.484,26.113,39.467,26.096,39.521,26.101,39.569,26.155,39.657,26.15,39.873,26.181,39.99,26.313,40.025,26.475,40.197,26.738,40.4,27.012,40.396,27.122,40.452,27.285,40.456,27.314,40.415,27.333,40.376,27.476,40.32,27.728,40.329,27.789,40.351,27.849,40.382,27.732,40.481,27.769,40.51,27.875,40.513,27.99,40.489,27.995,40.467,27.964,40.435,27.929,40.38,27.963,40.37,28.289,40.403,28.63,40.376,28.739,40.391,29.007,40.39,29.055,40.424,28.974,40.467,28.895,40.482,28.841,40.503,28.788,40.534,28.958,40.631,29.054,40.649,29.508,40.708,29.845,40.738,29.849,40.76,29.801,40.76,29.365,40.809,29.26,40.847,29.114,40.938,29.082,40.963,29.046,41.008,29.067,41.102,29.094,41.177,29.148,41.221,29.322,41.228,29.919,41.151,30.345,41.197,30.81,41.085,31.255,41.108,31.347,41.158,31.458,41.32,32.086,41.589,32.306,41.73,32.542,41.806,32.947,41.892,33.285,42.005,33.381,42.018,34.193,41.964,34.75,41.957,35.006,42.063,35.155,42.028,35.141,41.99,35.114,41.957,35.122,41.891,35.209,41.794,35.298,41.729,35.558,41.634,35.92,41.714,35.978,41.705,36.052,41.683,36.179,41.427,36.278,41.336,36.405,41.275,36.51,41.263,36.587,41.327,36.647,41.353,36.778,41.363,36.992,41.275,37.066,41.184,37.431,41.114,37.766,41.079,37.91,41.002,38.381,40.925,38.557,40.937,38.852,41.018,39.426,41.106,39.808,40.983,39.911,40.966,40,40.977,40.128,40.943,40.265,40.961,40.688,41.107,40.82,41.19,40.959,41.212,41.084,41.261,41.414,41.424,41.51,41.517]],[[28.014,41.969,27.987,41.855,28.05,41.729,28.198,41.554,28.346,41.466,28.947,41.248,29.057,41.23,29.032,41.14,28.996,41.061,28.956,41.008,28.78,40.974,28.295,41.071,28.172,41.081,28.086,41.061,27.925,40.991,27.747,41.013,27.499,40.973,27.43,40.84,27.258,40.687,26.975,40.564,26.772,40.498,26.468,40.261,26.33,40.123,26.272,40.097,26.203,40.075,26.226,40.142,26.26,40.202,26.252,40.248,26.254,40.315,26.355,40.39,26.447,40.445,26.72,40.544,26.792,40.627,26.578,40.625,26.361,40.606,26.224,40.618,26.105,40.611,26.068,40.683,26.039,40.727,26.07,40.74,26.109,40.75,26.179,40.827,26.241,40.883,26.331,40.954,26.354,40.997,26.354,41.037,26.333,41.064,26.328,41.097,26.326,41.143,26.331,41.239,26.536,41.343,26.602,41.354,26.625,41.402,26.61,41.512,26.581,41.601,26.545,41.607,26.495,41.633,26.463,41.663,26.411,41.696,26.321,41.717,26.318,41.745,26.327,41.773,26.36,41.802,26.511,41.826,26.529,41.847,26.55,41.897,26.58,41.948,26.615,41.965,26.679,41.963,26.8,41.975,26.885,41.992,26.969,42.027,27.012,42.059,27.193,42.077,27.244,42.093,27.295,42.08,27.363,42.025,27.475,41.947,27.535,41.921,27.58,41.933,27.661,41.961,27.739,41.962,27.802,41.957,27.832,41.981,27.879,41.987,28.014,41.969]]]},{"n":"Tunisia","c":"TN","r":3,"ml":3,"lx":9.01,"ly":33.69,"b":[7.496,30.229,11.536,37.34],"p":[[[11.505,33.182,11.502,33.156,11.467,32.966,11.459,32.897,11.454,32.782,11.454,32.643,11.534,32.525,11.536,32.473,11.505,32.414,11.358,32.345,11.168,32.257,11.005,32.173,10.826,32.081,10.772,32.021,10.683,31.975,10.609,31.93,10.596,31.886,10.544,31.803,10.476,31.736,10.306,31.705,10.275,31.685,10.196,31.585,10.16,31.546,10.115,31.464,10.173,31.251,10.243,31.032,10.257,30.941,10.256,30.865,10.216,30.783,10.126,30.666,10.06,30.58,9.933,30.425,9.895,30.387,9.807,30.342,9.638,30.282,9.519,30.229,9.458,30.465,9.406,30.667,9.363,30.833,9.288,31.125,9.224,31.374,9.16,31.621,9.102,31.846,9.044,32.072,9.019,32.105,8.844,32.212,8.683,32.31,8.515,32.422,8.333,32.544,8.304,32.696,8.211,32.927,8.113,33.055,8.076,33.089,7.877,33.172,7.763,33.233,7.731,33.269,7.709,33.362,7.628,33.549,7.534,33.718,7.5,33.832,7.496,33.977,7.514,34.081,7.554,34.125,7.749,34.254,7.838,34.41,7.949,34.469,8.046,34.513,8.123,34.564,8.193,34.646,8.246,34.734,8.255,34.829,8.277,34.979,8.312,35.085,8.394,35.204,8.36,35.3,8.316,35.403,8.329,35.582,8.318,35.655,8.283,35.719,8.247,35.802,8.246,35.871,8.28,36.051,8.307,36.189,8.349,36.368,8.334,36.418,8.303,36.456,8.209,36.495,8.208,36.519,8.231,36.545,8.37,36.633,8.444,36.761,8.507,36.788,8.601,36.834,8.598,36.884,8.577,36.937,8.824,36.998,9.059,37.156,9.142,37.195,9.688,37.34,9.759,37.33,9.838,37.309,9.816,37.255,9.784,37.211,9.83,37.135,9.896,37.182,9.879,37.213,9.876,37.254,9.988,37.258,10.087,37.251,10.196,37.206,10.189,37.034,10.334,36.865,10.293,36.781,10.412,36.732,10.518,36.791,10.571,36.879,10.766,36.93,10.951,37.059,11.054,37.073,11.077,36.967,11.127,36.874,11.057,36.841,10.967,36.743,10.798,36.493,10.642,36.42,10.526,36.323,10.488,36.255,10.477,36.175,10.506,36.032,10.591,35.887,10.689,35.8,10.784,35.772,11.004,35.634,11.001,35.552,11.032,35.454,11.043,35.335,11.12,35.24,10.956,35.034,10.866,34.884,10.691,34.678,10.535,34.545,10.2,34.346,10.118,34.28,10.065,34.212,10.04,34.14,10.049,34.056,10.159,33.85,10.305,33.728,10.454,33.663,10.713,33.689,10.704,33.61,10.723,33.514,10.828,33.519,10.898,33.534,10.958,33.626,11.085,33.563,11.15,33.369,11.257,33.309,11.27,33.286,11.232,33.272,11.203,33.249,11.234,33.234,11.338,33.209,11.401,33.225,11.505,33.182]],[[11.278,34.754,11.124,34.682,11.153,34.745,11.255,34.82,11.281,34.802,11.278,34.754]],[[10.958,33.722,10.931,33.717,10.883,33.69,10.857,33.687,10.785,33.718,10.757,33.717,10.722,33.739,10.734,33.856,10.745,33.889,10.922,33.893,11.018,33.823,11.034,33.805,11.038,33.785,10.993,33.746,10.958,33.722]]]},{"n":"Trinidad and Tobago","c":"TT","r":5,"ml":4.5,"lx":-60.92,"ly":11,"b":[-61.906,10.065,-60.525,11.325],"p":[[[-60.756,11.179,-60.811,11.169,-60.804,11.208,-60.709,11.277,-60.563,11.324,-60.525,11.325,-60.546,11.264,-60.756,11.179]],[[-61.012,10.134,-61.174,10.078,-61.597,10.065,-61.772,10.085,-61.906,10.069,-61.661,10.192,-61.633,10.243,-61.529,10.253,-61.499,10.269,-61.465,10.539,-61.478,10.603,-61.499,10.639,-61.541,10.664,-61.635,10.699,-61.651,10.718,-61.592,10.748,-61.465,10.764,-61.37,10.797,-61.174,10.803,-61.079,10.832,-60.918,10.84,-60.997,10.716,-61.034,10.67,-61.019,10.558,-61.037,10.482,-61.016,10.386,-60.968,10.323,-61,10.261,-61.004,10.168,-61.012,10.134]]]},{"n":"Tonga","c":"TO","r":4,"ml":4,"lx":-175.16,"ly":-21.21,"b":[-175.362,-21.451,-173.922,-18.565],"p":[[[-175.162,-21.169,-175.148,-21.169,-175.132,-21.14,-175.078,-21.129,-175.084,-21.161,-175.157,-21.264,-175.202,-21.223,-175.335,-21.158,-175.362,-21.107,-175.318,-21.068,-175.323,-21.099,-175.3,-21.113,-175.225,-21.119,-175.158,-21.146,-175.2,-21.156,-175.162,-21.169]],[[-173.954,-18.639,-173.991,-18.699,-174.009,-18.698,-174.053,-18.663,-174.069,-18.64,-174.002,-18.571,-173.968,-18.565,-173.922,-18.589,-173.924,-18.608,-173.954,-18.639]],[[-174.913,-21.3,-174.919,-21.451,-174.968,-21.382,-174.973,-21.35,-174.923,-21.303,-174.913,-21.3]]]},{"n":"Togo","c":"TG","r":6,"ml":5,"lx":1.06,"ly":8.81,"b":[-0.09,6.089,1.778,11.116],"p":[[[0.9,10.993,0.875,10.886,0.822,10.753,0.788,10.71,0.763,10.387,0.78,10.36,0.792,10.352,0.958,10.242,1.176,10.098,1.33,9.997,1.343,9.963,1.345,9.75,1.347,9.568,1.379,9.463,1.386,9.362,1.424,9.285,1.566,9.137,1.6,9.05,1.604,8.771,1.607,8.559,1.625,8.271,1.625,8.03,1.625,7.726,1.625,7.369,1.625,6.997,1.531,6.992,1.582,6.877,1.591,6.772,1.603,6.738,1.578,6.687,1.599,6.61,1.639,6.582,1.743,6.426,1.778,6.295,1.611,6.251,1.623,6.217,1.311,6.147,1.187,6.089,1.185,6.145,1.14,6.155,1.084,6.174,1.05,6.203,1.002,6.269,0.985,6.32,0.912,6.329,0.822,6.386,0.737,6.453,0.707,6.519,0.715,6.549,0.702,6.581,0.673,6.593,0.596,6.742,0.548,6.802,0.526,6.851,0.533,6.888,0.523,6.939,0.538,6.98,0.579,7.004,0.592,7.034,0.596,7.097,0.62,7.227,0.635,7.354,0.591,7.389,0.537,7.399,0.51,7.435,0.499,7.495,0.5,7.547,0.605,7.728,0.584,8.146,0.599,8.21,0.647,8.253,0.688,8.304,0.686,8.355,0.616,8.48,0.483,8.575,0.415,8.653,0.379,8.722,0.373,8.759,0.453,8.814,0.489,8.851,0.493,8.895,0.46,8.974,0.466,9.115,0.497,9.221,0.529,9.358,0.526,9.398,0.448,9.48,0.405,9.491,0.371,9.486,0.289,9.432,0.26,9.426,0.242,9.442,0.233,9.464,0.262,9.496,0.252,9.536,0.275,9.571,0.327,9.587,0.343,9.604,0.273,9.621,0.265,9.645,0.27,9.668,0.29,9.672,0.312,9.671,0.324,9.688,0.335,9.804,0.343,9.845,0.352,9.925,0.363,10.236,0.379,10.269,0.381,10.292,0.332,10.307,0.216,10.391,0.148,10.455,0.089,10.521,0.039,10.564,-0.058,10.631,-0.086,10.673,-0.09,10.716,-0.061,10.801,-0.014,10.891,0.009,11.021,-0.005,11.056,-0.069,11.116,0.159,11.07,0.484,10.992,0.491,10.978,0.493,10.955,0.549,10.955,0.643,10.983,0.9,10.993]]]},{"n":"East Timor","c":"TL","r":5,"ml":4,"lx":125.85,"ly":-8.8,"b":[124.036,-9.512,127.296,-8.14],"p":[[[125.646,-8.14,125.579,-8.312,125.507,-8.275,125.584,-8.179,125.621,-8.15,125.646,-8.14]],[[125.068,-9.512,125.034,-9.382,124.997,-9.326,124.968,-9.294,124.959,-9.255,124.96,-9.214,124.978,-9.195,125.1,-9.19,125.149,-9.123,125.149,-9.043,125.124,-9.015,125.1,-9.004,124.973,-9.064,124.937,-9.053,124.915,-9.032,124.922,-8.942,125.027,-8.859,125.116,-8.708,125.178,-8.648,125.323,-8.591,125.382,-8.575,125.804,-8.492,125.905,-8.487,126.173,-8.489,126.531,-8.471,126.62,-8.459,126.735,-8.423,126.846,-8.377,126.905,-8.342,126.966,-8.316,127.058,-8.348,127.215,-8.373,127.257,-8.395,127.296,-8.425,127.115,-8.584,126.915,-8.715,126.792,-8.755,126.665,-8.782,126.569,-8.833,126.487,-8.913,126.383,-8.958,126.265,-8.973,126.164,-8.997,126.073,-9.044,125.946,-9.124,125.895,-9.132,125.84,-9.13,125.735,-9.161,125.408,-9.276,125.21,-9.404,125.068,-9.512]],[[124.036,-9.342,124.198,-9.256,124.444,-9.19,124.438,-9.239,124.413,-9.314,124.376,-9.35,124.319,-9.414,124.282,-9.428,124.135,-9.414,124.116,-9.423,124.09,-9.416,124.052,-9.375,124.036,-9.342]]]},{"n":"Thailand","c":"TH","r":3,"ml":2.7,"lx":101.07,"ly":15.46,"b":[97.374,5.637,105.641,20.424],"p":[[[102.606,11.677,102.59,11.572,102.533,11.615,102.546,11.668,102.569,11.692,102.606,11.677]],[[102.427,11.989,102.43,11.965,102.378,11.983,102.36,11.974,102.302,11.981,102.273,12.119,102.277,12.152,102.319,12.142,102.378,12.073,102.408,12.025,102.427,11.989]],[[98.409,7.902,98.398,7.828,98.357,7.829,98.316,7.782,98.296,7.776,98.262,7.926,98.301,8.136,98.322,8.166,98.351,8.111,98.435,8.086,98.399,7.965,98.409,7.902]],[[100.122,20.317,100.115,20.258,100.14,20.245,100.174,20.273,100.218,20.34,100.266,20.377,100.318,20.386,100.373,20.34,100.432,20.241,100.492,20.184,100.52,20.178,100.54,20.132,100.543,20.089,100.515,19.996,100.466,19.889,100.398,19.756,100.42,19.644,100.514,19.553,100.625,19.5,100.744,19.515,100.807,19.542,100.858,19.585,100.906,19.605,100.967,19.611,101.155,19.579,101.212,19.548,101.221,19.487,101.198,19.328,101.227,19.212,101.28,19.089,101.286,18.977,101.221,18.793,101.166,18.618,101.106,18.534,101.06,18.479,101.047,18.442,101.051,18.407,101.093,18.355,101.138,18.287,101.149,18.222,101.144,18.143,101.113,18.034,100.999,17.797,100.908,17.584,100.956,17.541,101.046,17.51,101.105,17.48,101.167,17.499,101.3,17.625,101.414,17.719,101.555,17.812,101.564,17.821,101.688,17.889,101.744,17.953,101.775,18.033,101.819,18.065,101.875,18.046,101.947,18.081,102.035,18.17,102.101,18.211,102.148,18.204,102.232,18.149,102.352,18.046,102.459,17.985,102.553,17.965,102.598,17.927,102.596,17.87,102.617,17.833,102.661,17.818,102.68,17.824,102.675,17.852,102.718,17.892,102.807,17.946,102.899,17.977,102.991,17.986,103.051,18.029,103.091,18.138,103.149,18.222,103.2,18.259,103.263,18.278,103.28,18.305,103.249,18.339,103.252,18.373,103.288,18.408,103.367,18.423,103.488,18.418,103.63,18.383,103.792,18.317,103.899,18.295,103.95,18.319,104.049,18.217,104.196,17.988,104.323,17.816,104.428,17.699,104.539,17.609,104.656,17.547,104.74,17.462,104.816,17.3,104.759,17.077,104.744,16.884,104.751,16.648,104.819,16.466,104.95,16.34,105.026,16.238,105.047,16.16,105.149,16.094,105.331,16.038,105.406,15.987,105.376,15.942,105.373,15.89,105.399,15.83,105.462,15.78,105.562,15.741,105.622,15.7,105.641,15.657,105.639,15.586,105.616,15.488,105.574,15.413,105.513,15.361,105.506,15.32,105.49,15.257,105.49,15.128,105.533,15.042,105.547,14.932,105.523,14.843,105.5,14.661,105.497,14.591,105.476,14.53,105.423,14.472,105.342,14.417,105.244,14.368,105.183,14.346,105.169,14.336,105.126,14.281,105.074,14.227,105.034,14.227,105.003,14.254,104.982,14.289,104.97,14.366,104.879,14.404,104.779,14.428,104.576,14.39,104.412,14.37,104.228,14.396,104.054,14.363,103.982,14.358,103.899,14.363,103.818,14.362,103.742,14.374,103.6,14.421,103.546,14.417,103.432,14.379,103.313,14.351,103.199,14.333,103.031,14.253,102.909,14.137,102.873,14.055,102.813,13.972,102.729,13.842,102.62,13.717,102.545,13.66,102.566,13.626,102.547,13.586,102.429,13.568,102.336,13.56,102.32,13.54,102.331,13.288,102.363,13.193,102.423,13.078,102.462,13.015,102.491,12.828,102.5,12.67,102.63,12.57,102.703,12.494,102.756,12.426,102.737,12.383,102.706,12.256,102.737,12.09,102.918,11.732,102.934,11.707,102.912,11.704,102.884,11.773,102.792,11.889,102.763,12.012,102.655,12.149,102.594,12.203,102.575,12.158,102.54,12.109,102.434,12.179,102.343,12.253,102.259,12.394,102.248,12.361,102.23,12.332,102.134,12.443,102.034,12.532,101.945,12.564,101.889,12.593,101.836,12.64,101.724,12.689,101.445,12.619,101.09,12.674,100.954,12.621,100.898,12.654,100.863,12.715,100.896,12.818,100.904,13.035,100.946,13.187,100.926,13.303,100.947,13.358,100.963,13.432,100.907,13.462,100.656,13.521,100.603,13.568,100.536,13.514,100.236,13.484,100.122,13.44,100.017,13.353,99.991,13.243,100.051,13.171,100.09,13.046,99.982,12.771,99.964,12.69,100.006,12.355,99.989,12.171,99.93,12.047,99.837,11.937,99.799,11.749,99.725,11.662,99.627,11.463,99.561,11.215,99.514,11.101,99.487,10.89,99.285,10.569,99.237,10.388,99.165,10.32,99.19,10.266,99.195,10.175,99.169,9.934,99.161,9.734,99.191,9.627,99.288,9.415,99.265,9.353,99.254,9.265,99.335,9.225,99.394,9.214,99.724,9.314,99.836,9.288,99.878,9.195,99.905,9.113,99.961,8.671,99.99,8.589,100.056,8.511,100.129,8.428,100.154,8.443,100.159,8.474,100.163,8.508,100.229,8.425,100.279,8.269,100.454,7.442,100.504,7.337,100.545,7.227,100.439,7.281,100.411,7.464,100.38,7.542,100.343,7.553,100.284,7.552,100.28,7.584,100.324,7.644,100.317,7.716,100.257,7.775,100.158,7.728,100.161,7.599,100.205,7.501,100.371,7.28,100.424,7.188,100.49,7.161,100.586,7.176,100.702,7.082,100.793,6.995,101.018,6.861,101.154,6.875,101.302,6.908,101.401,6.9,101.498,6.865,101.614,6.754,101.799,6.475,102.101,6.242,102.068,6.185,102.055,6.097,101.936,5.979,101.917,5.911,101.874,5.825,101.791,5.779,101.72,5.771,101.678,5.779,101.65,5.796,101.601,5.877,101.577,5.902,101.556,5.908,101.404,5.852,101.257,5.789,101.23,5.734,101.191,5.669,101.148,5.643,101.114,5.637,101.082,5.675,101.025,5.725,100.982,5.771,100.993,5.846,101.076,5.956,101.087,6.034,101.076,6.166,101.054,6.243,101.029,6.245,100.989,6.258,100.874,6.245,100.817,6.332,100.794,6.426,100.754,6.46,100.716,6.481,100.629,6.448,100.564,6.468,100.345,6.55,100.261,6.683,100.217,6.687,100.177,6.672,100.161,6.642,100.138,6.489,100.119,6.442,99.869,6.75,99.696,6.877,99.72,7.106,99.668,7.151,99.602,7.155,99.553,7.219,99.597,7.356,99.529,7.329,99.435,7.334,99.359,7.372,99.3,7.561,99.264,7.619,99.183,7.718,99.078,7.718,99.043,7.766,99.051,7.888,98.974,7.963,98.872,8.024,98.789,8.06,98.704,8.257,98.636,8.305,98.579,8.344,98.5,8.318,98.474,8.247,98.421,8.178,98.361,8.187,98.305,8.226,98.238,8.423,98.227,8.544,98.242,8.768,98.326,8.969,98.371,9.291,98.443,9.493,98.493,9.561,98.562,9.838,98.703,10.19,98.718,10.266,98.747,10.351,98.768,10.431,98.775,10.557,98.757,10.624,98.757,10.661,98.787,10.708,98.887,10.788,99.025,10.92,99.19,11.105,99.359,11.389,99.443,11.554,99.478,11.613,99.515,11.631,99.573,11.687,99.613,11.75,99.615,11.781,99.523,12.09,99.463,12.19,99.432,12.309,99.416,12.395,99.394,12.474,99.405,12.548,99.372,12.594,99.297,12.653,99.22,12.74,99.174,12.882,99.174,12.961,99.124,13.031,99.107,13.104,99.137,13.173,99.176,13.233,99.172,13.497,99.156,13.576,99.137,13.717,99.086,13.823,99.015,13.947,98.934,14.05,98.721,14.236,98.57,14.36,98.495,14.473,98.4,14.603,98.332,14.696,98.246,14.815,98.202,14.976,98.178,15.147,98.191,15.204,98.232,15.241,98.286,15.272,98.329,15.279,98.452,15.357,98.537,15.351,98.557,15.368,98.565,15.404,98.554,15.56,98.558,15.769,98.574,15.939,98.592,16.051,98.818,16.181,98.866,16.237,98.888,16.298,98.888,16.352,98.869,16.394,98.835,16.418,98.689,16.305,98.661,16.33,98.594,16.515,98.565,16.571,98.523,16.638,98.478,16.732,98.471,16.895,98.439,16.976,98.257,17.148,98.175,17.24,98.063,17.373,97.929,17.533,97.793,17.681,97.729,17.776,97.706,17.797,97.699,17.834,97.74,17.935,97.72,18.037,97.652,18.174,97.622,18.258,97.632,18.29,97.599,18.303,97.524,18.296,97.451,18.36,97.381,18.494,97.374,18.518,97.397,18.518,97.485,18.494,97.515,18.498,97.577,18.529,97.672,18.561,97.728,18.572,97.746,18.588,97.754,18.621,97.706,18.932,97.714,18.996,97.804,19.13,97.794,19.266,97.817,19.46,97.916,19.593,97.991,19.654,98.015,19.75,98.049,19.77,98.111,19.762,98.239,19.691,98.294,19.687,98.371,19.689,98.455,19.694,98.494,19.701,98.761,19.771,98.82,19.778,98.876,19.77,98.917,19.773,98.958,19.805,98.987,19.861,99.021,20.042,99.04,20.074,99.074,20.099,99.131,20.117,99.197,20.115,99.284,20.08,99.338,20.079,99.399,20.093,99.452,20.118,99.486,20.15,99.502,20.188,99.488,20.261,99.448,20.352,99.459,20.363,99.532,20.343,99.639,20.32,99.72,20.325,99.773,20.341,99.825,20.384,99.89,20.424,99.954,20.415,100.004,20.38,100.122,20.317]],[[100.074,9.697,100.064,9.68,100.026,9.712,99.998,9.748,99.983,9.794,100.043,9.792,100.073,9.749,100.074,9.697]],[[100.071,9.586,100.075,9.529,100.054,9.461,99.962,9.422,99.931,9.476,99.94,9.56,99.954,9.581,100.043,9.577,100.071,9.586]],[[99.663,6.522,99.644,6.516,99.607,6.597,99.654,6.714,99.701,6.571,99.663,6.522]],[[98.592,7.934,98.58,7.917,98.529,8.109,98.604,8.057,98.592,7.934]],[[98.308,9.051,98.251,9.041,98.258,9.095,98.274,9.13,98.301,9.139,98.313,9.08,98.308,9.051]],[[99.078,7.592,99.104,7.471,99.068,7.496,99.038,7.548,99.038,7.626,99.045,7.637,99.078,7.592]]]},{"n":"Tanzania","c":"TZ","r":3,"ml":3,"lx":34.96,"ly":-6.05,"b":[29.323,-11.716,40.464,-0.995],"p":[[[39.496,-6.175,39.573,-6.387,39.563,-6.427,39.509,-6.452,39.481,-6.454,39.447,-6.42,39.424,-6.348,39.383,-6.365,39.313,-6.279,39.243,-6.275,39.182,-6.173,39.206,-6.083,39.192,-5.931,39.267,-5.853,39.309,-5.722,39.357,-5.812,39.368,-5.951,39.433,-6.115,39.488,-6.166,39.496,-6.175]],[[39.865,-4.906,39.871,-4.957,39.856,-5.004,39.859,-5.155,39.853,-5.255,39.796,-5.394,39.749,-5.444,39.708,-5.429,39.673,-5.407,39.647,-5.369,39.701,-5.114,39.673,-4.927,39.781,-4.945,39.865,-4.906]],[[32.92,-9.407,32.863,-9.381,32.757,-9.322,32.608,-9.271,32.487,-9.213,32.433,-9.156,32.319,-9.135,32.221,-9.126,32.13,-9.073,32.035,-9.067,31.943,-9.054,31.922,-9.019,31.919,-8.942,31.886,-8.922,31.818,-8.902,31.745,-8.903,31.7,-8.914,31.674,-8.909,31.613,-8.863,31.556,-8.805,31.535,-8.713,31.449,-8.654,31.351,-8.607,31.076,-8.612,31.033,-8.598,30.968,-8.551,30.892,-8.474,30.831,-8.386,30.777,-8.266,30.751,-8.194,30.721,-8.104,30.654,-7.971,30.559,-7.782,30.486,-7.627,30.407,-7.461,30.375,-7.339,30.313,-7.204,30.213,-7.038,30.162,-6.973,30.106,-6.915,29.962,-6.803,29.798,-6.692,29.71,-6.617,29.591,-6.394,29.541,-6.314,29.506,-6.172,29.48,-6.025,29.491,-5.965,29.596,-5.776,29.607,-5.723,29.594,-5.651,29.542,-5.5,29.504,-5.401,29.476,-5.317,29.42,-5.176,29.343,-4.983,29.323,-4.899,29.326,-4.836,29.368,-4.669,29.404,-4.497,29.403,-4.449,29.718,-4.456,29.77,-4.418,29.947,-4.307,30.147,-4.085,30.187,-3.993,30.269,-3.85,30.348,-3.78,30.379,-3.731,30.4,-3.654,30.425,-3.589,30.53,-3.492,30.632,-3.419,30.625,-3.389,30.611,-3.366,30.626,-3.347,30.682,-3.309,30.79,-3.275,30.811,-3.201,30.811,-3.116,30.794,-3.069,30.797,-3.015,30.78,-2.985,30.709,-2.977,30.604,-2.935,30.515,-2.918,30.456,-2.893,30.433,-2.875,30.424,-2.824,30.441,-2.769,30.45,-2.753,30.473,-2.694,30.434,-2.659,30.424,-2.642,30.442,-2.613,30.534,-2.426,30.554,-2.4,30.593,-2.397,30.657,-2.374,30.715,-2.363,30.763,-2.372,30.798,-2.363,30.829,-2.338,30.855,-2.265,30.877,-2.143,30.865,-2.044,30.819,-1.967,30.807,-1.851,30.828,-1.694,30.813,-1.563,30.762,-1.459,30.711,-1.397,30.632,-1.367,30.508,-1.208,30.47,-1.131,30.477,-1.083,30.51,-1.067,30.52,-1.062,30.599,-1.07,30.673,-1.051,30.742,-1.008,30.809,-0.995,30.824,-0.999,30.845,-1.002,30.95,-1.002,31.128,-1.002,31.305,-1.002,31.483,-1.002,31.661,-1.002,31.839,-1.002,32.016,-1.002,32.194,-1.002,32.372,-1.002,32.55,-1.002,32.727,-1.002,32.905,-1.002,33.083,-1.002,33.261,-1.002,33.438,-1.002,33.616,-1.002,33.794,-1.002,33.903,-1.002,33.979,-1.002,34.052,-1.04,34.132,-1.085,34.345,-1.204,34.558,-1.323,34.771,-1.442,34.984,-1.561,35.197,-1.68,35.411,-1.799,35.624,-1.918,35.837,-2.037,36.05,-2.156,36.263,-2.275,36.476,-2.394,36.689,-2.513,36.903,-2.632,37.116,-2.751,37.329,-2.87,37.542,-2.989,37.644,-3.045,37.659,-3.07,37.677,-3.178,37.688,-3.246,37.682,-3.306,37.625,-3.407,37.609,-3.46,37.608,-3.497,37.622,-3.512,37.67,-3.517,37.711,-3.541,37.726,-3.56,37.757,-3.636,37.797,-3.674,37.887,-3.739,38.041,-3.85,38.194,-3.96,38.348,-4.071,38.501,-4.181,38.655,-4.292,38.808,-4.402,38.962,-4.513,39.115,-4.624,39.19,-4.677,39.222,-4.692,39.202,-4.776,39.123,-4.98,39.119,-5.065,39.088,-5.165,39.058,-5.232,38.978,-5.519,38.911,-5.626,38.819,-5.878,38.805,-6.07,38.855,-6.205,38.874,-6.331,38.981,-6.455,39.067,-6.499,39.125,-6.556,39.228,-6.685,39.287,-6.815,39.472,-6.879,39.546,-7.024,39.519,-7.124,39.433,-7.207,39.353,-7.341,39.288,-7.518,39.287,-7.788,39.33,-7.747,39.428,-7.813,39.441,-8.012,39.34,-8.243,39.309,-8.351,39.304,-8.444,39.377,-8.721,39.488,-8.862,39.48,-8.906,39.451,-8.943,39.641,-9.192,39.625,-9.409,39.697,-9.578,39.728,-9.725,39.775,-9.837,39.784,-9.915,39.725,-10,39.864,-10.022,39.945,-10.092,39.984,-10.16,40.084,-10.157,40.138,-10.203,40.216,-10.241,40.389,-10.354,40.436,-10.41,40.453,-10.443,40.464,-10.464,40.347,-10.552,40.166,-10.687,39.989,-10.821,39.817,-10.912,39.694,-10.955,39.563,-10.979,39.439,-11.035,39.322,-11.123,39.171,-11.167,38.988,-11.167,38.795,-11.229,38.603,-11.345,38.492,-11.413,38.315,-11.311,38.177,-11.279,38.017,-11.282,37.92,-11.295,37.885,-11.317,37.855,-11.379,37.829,-11.482,37.725,-11.581,37.542,-11.675,37.373,-11.71,37.218,-11.687,37.114,-11.647,37.059,-11.592,36.979,-11.567,36.873,-11.571,36.771,-11.61,36.674,-11.684,36.519,-11.716,36.306,-11.706,36.191,-11.671,36.175,-11.609,36.082,-11.537,35.911,-11.455,35.785,-11.453,35.705,-11.532,35.631,-11.582,35.564,-11.602,35.504,-11.605,35.451,-11.59,35.418,-11.583,35.183,-11.575,34.959,-11.578,34.953,-11.544,34.937,-11.463,34.891,-11.394,34.851,-11.352,34.801,-11.341,34.774,-11.342,34.752,-11.309,34.726,-11.238,34.688,-11.177,34.638,-11.127,34.608,-11.08,34.598,-11.037,34.606,-10.99,34.652,-10.873,34.667,-10.792,34.662,-10.71,34.637,-10.626,34.584,-10.525,34.59,-10.496,34.572,-10.428,34.57,-10.38,34.58,-10.32,34.57,-10.241,34.524,-10.073,34.524,-10.03,34.476,-9.949,34.328,-9.757,34.321,-9.732,34.089,-9.538,33.996,-9.495,33.962,-9.532,33.95,-9.565,33.959,-9.627,33.954,-9.658,33.944,-9.672,33.889,-9.67,33.854,-9.663,33.766,-9.611,33.698,-9.598,33.528,-9.608,33.468,-9.62,33.421,-9.608,33.331,-9.519,33.225,-9.5,33.13,-9.496,32.974,-9.395,32.937,-9.4,32.92,-9.407]],[[39.711,-7.977,39.657,-7.991,39.636,-7.978,39.603,-7.936,39.661,-7.901,39.717,-7.832,39.847,-7.73,39.891,-7.663,39.907,-7.649,39.898,-7.728,39.824,-7.901,39.762,-7.912,39.711,-7.977]]]},{"n":"Tajikistan","c":"TJ","r":4,"ml":4,"lx":72.59,"ly":38.2,"b":[67.35,36.684,75.119,41.035],"p":[[[67.759,37.172,67.798,37.245,67.814,37.487,67.864,37.571,68.011,37.721,68.088,37.835,68.174,37.928,68.237,37.96,68.294,38.033,68.341,38.117,68.354,38.17,68.35,38.211,68.333,38.238,68.251,38.295,68.144,38.383,68.087,38.474,68.056,38.589,68.048,38.669,68.149,38.891,68.133,38.928,68.104,38.962,68.044,38.984,67.96,38.993,67.876,38.983,67.769,38.982,67.694,38.995,67.677,39.008,67.667,39.109,67.648,39.131,67.617,39.15,67.4,39.197,67.358,39.217,67.35,39.242,67.426,39.466,67.46,39.482,67.492,39.519,67.542,39.558,67.719,39.621,67.909,39.594,68.077,39.564,68.245,39.548,68.303,39.538,68.399,39.529,68.463,39.537,68.507,39.563,68.586,39.635,68.61,39.743,68.639,39.839,68.687,39.846,68.735,39.836,68.758,39.856,68.768,39.882,68.778,39.904,68.798,39.909,68.832,39.884,68.852,39.891,68.869,39.907,68.864,39.927,68.824,39.961,68.789,40.013,68.793,40.031,68.805,40.05,68.908,40.068,68.956,40.071,68.972,40.09,68.966,40.12,68.927,40.136,68.785,40.127,68.64,40.129,68.622,40.147,68.631,40.167,68.653,40.183,68.952,40.223,69.11,40.209,69.228,40.188,69.275,40.198,69.22,40.288,69.294,40.297,69.304,40.327,69.206,40.567,69.26,40.588,69.314,40.635,69.309,40.724,69.357,40.767,69.414,40.797,69.498,40.767,69.628,40.679,69.671,40.662,69.713,40.657,69.773,40.684,70.006,40.771,70.136,40.82,70.292,40.892,70.319,40.919,70.373,41.028,70.402,41.035,70.442,41.023,70.578,40.911,70.657,40.84,70.657,40.815,70.635,40.797,70.639,40.779,70.751,40.74,70.751,40.722,70.726,40.688,70.712,40.669,70.698,40.661,70.549,40.563,70.383,40.454,70.377,40.439,70.37,40.412,70.372,40.384,70.398,40.361,70.47,40.345,70.534,40.325,70.566,40.267,70.603,40.214,70.653,40.201,70.899,40.235,70.958,40.239,70.961,40.221,70.946,40.188,70.739,40.131,70.644,40.083,70.624,39.999,70.599,39.975,70.557,39.954,70.515,39.95,70.451,40.049,70.379,40.07,70.274,40.105,70.071,40.173,69.967,40.202,69.765,40.158,69.53,40.097,69.494,40.06,69.469,40.021,69.471,39.991,69.488,39.95,69.476,39.92,69.432,39.91,69.365,39.947,69.307,39.969,69.279,39.918,69.245,39.827,69.229,39.761,69.28,39.666,69.298,39.525,69.392,39.532,69.463,39.532,69.599,39.574,69.667,39.575,69.772,39.557,69.956,39.553,70.102,39.561,70.137,39.558,70.171,39.584,70.209,39.575,70.245,39.543,70.392,39.582,70.501,39.587,70.568,39.576,70.608,39.564,70.679,39.471,70.733,39.413,70.799,39.395,71.005,39.412,71.065,39.493,71.118,39.514,71.203,39.52,71.273,39.535,71.329,39.569,71.404,39.598,71.47,39.604,71.503,39.582,71.517,39.554,71.506,39.517,71.503,39.479,71.546,39.453,71.673,39.447,71.732,39.423,71.735,39.378,71.726,39.307,71.779,39.278,71.806,39.276,71.991,39.351,72.043,39.352,72.084,39.311,72.147,39.261,72.23,39.208,72.25,39.216,72.287,39.274,72.358,39.337,72.49,39.357,72.563,39.377,72.64,39.386,72.872,39.36,72.949,39.357,73.109,39.362,73.235,39.375,73.336,39.412,73.387,39.443,73.47,39.461,73.576,39.458,73.632,39.449,73.636,39.397,73.623,39.298,73.607,39.229,73.69,39.105,73.744,39.045,73.796,39.002,73.805,38.969,73.795,38.941,73.73,38.915,73.707,38.886,73.696,38.854,73.717,38.817,73.754,38.699,73.802,38.607,73.869,38.563,73.97,38.534,74.026,38.54,74.065,38.608,74.131,38.661,74.187,38.658,74.277,38.66,74.514,38.6,74.745,38.51,74.812,38.46,74.836,38.404,74.772,38.275,74.775,38.192,74.79,38.104,74.842,38.038,74.891,37.926,74.9,37.833,74.921,37.805,74.938,37.773,74.912,37.687,74.894,37.601,74.916,37.573,74.986,37.53,75.097,37.451,75.119,37.386,75.079,37.344,75.008,37.294,74.918,37.25,74.891,37.232,74.875,37.242,74.83,37.286,74.731,37.357,74.659,37.394,74.524,37.382,74.445,37.396,74.349,37.419,74.26,37.415,74.204,37.372,74.167,37.329,74.078,37.316,73.949,37.283,73.75,37.232,73.654,37.239,73.628,37.262,73.649,37.291,73.717,37.329,73.734,37.376,73.721,37.419,73.657,37.43,73.633,37.437,73.605,37.446,73.481,37.472,73.383,37.462,73.211,37.408,72.896,37.268,72.757,37.173,72.657,37.029,72.359,36.983,72.154,36.901,71.942,36.766,71.802,36.694,71.734,36.684,71.666,36.697,71.597,36.733,71.531,36.845,71.472,37.015,71.433,37.128,71.455,37.272,71.48,37.436,71.505,37.603,71.546,37.796,71.58,37.864,71.582,37.91,71.552,37.933,71.488,37.932,71.39,37.906,71.32,37.902,71.279,37.918,71.283,38.008,71.333,38.17,71.256,38.307,71.052,38.418,70.879,38.456,70.736,38.423,70.616,38.334,70.519,38.192,70.418,38.075,70.313,37.985,70.239,37.941,70.215,37.924,70.199,37.886,70.255,37.765,70.251,37.664,70.189,37.582,70.12,37.544,70.045,37.547,69.985,37.566,69.941,37.6,69.821,37.61,69.626,37.594,69.492,37.553,69.42,37.487,69.399,37.399,69.43,37.291,69.414,37.208,69.354,37.15,69.304,37.117,69.265,37.108,69.18,37.158,69.05,37.267,68.96,37.325,68.912,37.334,68.885,37.328,68.855,37.317,68.838,37.303,68.824,37.271,68.782,37.258,68.723,37.268,68.669,37.258,68.637,37.224,68.546,37.183,68.387,37.138,68.3,37.088,68.285,37.036,68.261,37.013,68.212,37.022,68.068,36.95,67.958,36.972,67.834,37.064,67.766,37.14,67.759,37.172]],[[70.653,40.937,70.623,40.934,70.569,40.982,70.55,41.015,70.572,41.025,70.618,41.002,70.649,40.961,70.653,40.937]],[[70.702,39.825,70.612,39.787,70.56,39.791,70.519,39.828,70.489,39.863,70.483,39.883,70.498,39.882,70.567,39.867,70.664,39.855,70.698,39.846,70.702,39.825]]]},{"n":"Taiwan","c":"TW","r":3,"ml":4.5,"lx":120.87,"ly":23.65,"b":[118.287,21.925,121.929,25.277],"p":[[[121.009,22.62,120.947,22.503,120.897,22.379,120.877,22.262,120.878,22.142,120.864,22.033,120.84,21.925,120.743,21.956,120.69,22.033,120.678,22.16,120.608,22.313,120.581,22.356,120.48,22.442,120.388,22.485,120.316,22.548,120.326,22.542,120.273,22.627,120.233,22.718,120.15,22.975,120.122,23.037,120.083,23.094,120.072,23.15,120.086,23.212,120.121,23.305,120.143,23.399,120.125,23.527,120.132,23.653,120.159,23.709,120.63,24.479,120.757,24.642,120.836,24.723,120.902,24.813,120.964,24.928,121.041,25.033,121.095,25.065,121.365,25.159,121.45,25.249,121.517,25.277,121.594,25.275,121.643,25.232,121.687,25.182,121.733,25.154,121.853,25.104,121.905,25.056,121.929,24.974,121.856,24.895,121.82,24.825,121.813,24.746,121.826,24.641,121.828,24.534,121.737,24.285,121.639,24.13,121.613,24.053,121.583,23.861,121.526,23.668,121.477,23.424,121.397,23.173,121.352,23.067,121.296,22.967,121.161,22.776,121.009,22.62]],[[118.407,24.522,118.451,24.456,118.433,24.414,118.295,24.436,118.287,24.477,118.339,24.469,118.407,24.522]]]},{"n":"Syria","c":"SY","r":3,"ml":3,"lx":38.28,"ly":35.01,"b":[35.764,32.317,42.359,37.297],"p":[[[35.893,35.917,35.968,35.91,36.127,35.831,36.154,35.834,36.202,35.938,36.249,35.973,36.348,36.004,36.375,36.171,36.421,36.203,36.477,36.221,36.562,36.224,36.637,36.234,36.641,36.264,36.538,36.457,36.547,36.506,36.597,36.701,36.628,36.778,36.659,36.803,36.777,36.793,36.942,36.758,36.985,36.702,37.066,36.653,37.187,36.656,37.327,36.647,37.436,36.643,37.524,36.678,37.72,36.744,37.818,36.766,37.907,36.795,38.192,36.902,38.306,36.893,38.384,36.879,38.444,36.862,38.578,36.789,38.689,36.715,38.767,36.693,38.906,36.695,39.108,36.681,39.357,36.682,39.501,36.702,39.687,36.739,40.016,36.826,40.45,37.009,40.706,37.098,40.816,37.108,40.959,37.109,41.102,37.086,41.265,37.069,41.34,37.071,41.516,37.089,41.744,37.126,41.887,37.156,42.06,37.206,42.168,37.289,42.203,37.297,42.248,37.282,42.269,37.277,42.313,37.23,42.359,37.109,42.359,37.095,42.35,37.061,42.237,36.961,42.084,36.826,41.974,36.741,41.789,36.597,41.65,36.566,41.417,36.515,41.354,36.464,41.296,36.383,41.262,36.272,41.252,36.203,41.246,36.073,41.3,35.939,41.353,35.81,41.359,35.725,41.354,35.64,41.303,35.551,41.248,35.427,41.216,35.288,41.2,35.027,41.199,34.805,41.195,34.769,41.099,34.612,40.987,34.429,40.935,34.387,40.689,34.332,40.421,34.198,40.122,34.048,39.85,33.911,39.564,33.768,39.268,33.62,39.057,33.514,38.774,33.372,38.516,33.237,38.254,33.099,38.056,32.995,37.754,32.83,37.577,32.733,37.318,32.591,37.089,32.466,36.818,32.317,36.479,32.361,36.372,32.387,36.284,32.457,36.22,32.495,36.059,32.534,35.956,32.667,35.895,32.714,35.787,32.735,35.833,32.82,35.842,32.876,35.888,32.944,35.859,32.989,35.845,33.085,35.825,33.108,35.817,33.133,35.833,33.164,35.827,33.195,35.809,33.209,35.8,33.256,35.785,33.291,35.793,33.334,35.816,33.362,35.841,33.416,35.869,33.432,35.915,33.465,35.927,33.5,35.968,33.535,36.022,33.563,36.034,33.585,36.027,33.598,35.972,33.623,35.942,33.668,35.968,33.732,35.986,33.753,36.019,33.784,36.092,33.832,36.15,33.84,36.199,33.84,36.283,33.836,36.349,33.827,36.365,33.839,36.363,33.855,36.282,33.894,36.278,33.925,36.298,33.959,36.355,34.011,36.423,34.05,36.458,34.057,36.535,34.134,36.585,34.221,36.504,34.432,36.456,34.466,36.376,34.495,36.33,34.5,36.326,34.513,36.389,34.567,36.433,34.613,36.384,34.658,36.296,34.679,36.264,34.633,36.151,34.629,35.976,34.629,35.899,34.852,35.888,34.949,35.89,35.06,35.943,35.224,35.918,35.3,35.916,35.351,35.902,35.421,35.764,35.572,35.84,35.849,35.893,35.917]]]},{"n":"Switzerland","c":"CH","r":4,"ml":4,"lx":7.46,"ly":46.72,"b":[5.97,45.83,10.455,47.776],"p":[[[9.524,47.524,9.554,47.511,9.626,47.467,9.609,47.392,9.528,47.271,9.484,47.173,9.479,47.098,9.488,47.062,9.502,47.063,9.58,47.057,9.62,47.057,9.745,47.037,9.845,47.007,9.865,46.976,9.878,46.938,9.997,46.885,10.133,46.852,10.18,46.862,10.349,46.985,10.415,46.964,10.455,46.899,10.453,46.865,10.406,46.735,10.398,46.665,10.438,46.619,10.442,46.583,10.431,46.55,10.363,46.547,10.272,46.565,10.196,46.621,10.138,46.614,10.087,46.6,10.061,46.547,10.038,46.483,10.046,46.448,10.082,46.421,10.11,46.363,10.13,46.288,10.145,46.254,10.128,46.238,10.081,46.228,10.041,46.238,9.972,46.328,9.939,46.362,9.884,46.368,9.788,46.346,9.639,46.296,9.58,46.296,9.529,46.306,9.481,46.349,9.441,46.431,9.428,46.482,9.399,46.481,9.304,46.496,9.26,46.475,9.26,46.391,9.251,46.287,9.203,46.219,9.071,46.102,9.022,46.051,9.003,46.015,8.999,45.983,9.019,45.928,9.047,45.876,9.024,45.846,8.954,45.83,8.904,45.862,8.885,45.919,8.778,45.996,8.827,46.061,8.819,46.077,8.642,46.111,8.565,46.16,8.458,46.246,8.438,46.283,8.443,46.403,8.437,46.432,8.423,46.446,8.371,46.445,8.299,46.403,8.232,46.341,8.096,46.271,8.082,46.256,8.127,46.188,8.125,46.161,8.014,46.052,7.993,46.016,7.852,45.947,7.788,45.922,7.593,45.972,7.539,45.978,7.452,45.944,7.328,45.912,7.129,45.88,7.056,45.904,7.021,45.926,7.004,45.959,6.954,46.017,6.897,46.052,6.858,46.089,6.806,46.131,6.772,46.165,6.817,46.275,6.784,46.314,6.767,46.369,6.776,46.407,6.758,46.416,6.578,46.437,6.429,46.431,6.322,46.394,6.235,46.333,6.224,46.319,6.23,46.308,6.273,46.252,6.199,46.193,6.087,46.147,6.007,46.142,5.971,46.151,5.97,46.215,6.036,46.238,6.096,46.279,6.116,46.338,6.123,46.379,6.06,46.428,6.068,46.459,6.107,46.516,6.13,46.567,6.161,46.611,6.285,46.683,6.41,46.755,6.429,46.832,6.439,46.926,6.456,46.948,6.625,47.004,6.667,47.027,6.688,47.058,6.821,47.163,6.952,47.267,6.979,47.302,7.001,47.323,7.001,47.339,6.984,47.353,6.921,47.361,6.9,47.394,6.968,47.453,7.053,47.489,7.136,47.49,7.169,47.473,7.167,47.454,7.203,47.433,7.266,47.426,7.343,47.433,7.42,47.455,7.467,47.508,7.495,47.547,7.616,47.593,7.698,47.57,7.927,47.564,8.094,47.576,8.198,47.607,8.328,47.607,8.415,47.59,8.43,47.592,8.454,47.596,8.478,47.613,8.559,47.624,8.571,47.638,8.567,47.652,8.552,47.659,8.452,47.652,8.413,47.663,8.403,47.688,8.436,47.731,8.51,47.767,8.573,47.776,8.618,47.766,8.728,47.7,8.755,47.698,8.77,47.71,8.793,47.717,8.831,47.704,8.874,47.663,8.881,47.656,9.128,47.671,9.183,47.671,9.35,47.599,9.524,47.524]]]},{"n":"Sweden","c":"SE","r":3,"ml":2,"lx":19.02,"ly":65.86,"b":[11.147,55.346,24.155,69.037],"p":[[[19.076,57.836,18.994,57.812,18.945,57.742,18.878,57.73,18.814,57.706,18.791,57.483,18.908,57.398,18.844,57.386,18.785,57.361,18.743,57.324,18.7,57.243,18.538,57.197,18.477,57.163,18.387,57.088,18.34,56.978,18.249,56.932,18.146,56.921,18.207,57.01,18.285,57.083,18.21,57.133,18.164,57.212,18.105,57.272,18.152,57.339,18.129,57.449,18.137,57.557,18.205,57.611,18.283,57.655,18.405,57.757,18.537,57.831,18.722,57.864,18.805,57.833,18.841,57.9,18.901,57.915,18.956,57.9,19.076,57.836]],[[16.529,56.291,16.477,56.24,16.432,56.244,16.401,56.311,16.394,56.484,16.412,56.569,16.63,56.877,16.728,56.902,16.865,57.091,16.902,57.175,16.961,57.25,16.996,57.318,17.025,57.345,17.089,57.332,17.118,57.32,17.05,57.28,17.058,57.229,17.054,57.208,16.884,56.985,16.838,56.841,16.778,56.805,16.529,56.291]],[[11.388,59.037,11.471,58.91,11.544,58.893,11.643,58.926,11.712,59.019,11.752,59.158,11.798,59.29,11.743,59.431,11.685,59.556,11.681,59.592,11.834,59.697,11.881,59.782,11.932,59.864,11.988,59.891,12.072,59.898,12.169,59.913,12.292,59.967,12.402,60.04,12.486,60.107,12.515,60.239,12.516,60.305,12.553,60.354,12.589,60.451,12.554,60.546,12.445,60.69,12.315,60.892,12.294,61.003,12.354,61.023,12.468,61.042,12.683,61.047,12.706,61.06,12.728,61.108,12.776,61.174,12.828,61.222,12.864,61.29,12.881,61.352,12.758,61.446,12.596,61.541,12.487,61.573,12.292,61.653,12.155,61.721,12.234,61.977,12.292,62.167,12.301,62.214,12.304,62.286,12.115,62.592,12.122,62.66,12.14,62.721,12.12,62.826,12.109,62.919,12.141,62.948,12.218,63.001,12.145,63.083,12.139,63.089,12,63.292,12.212,63.492,12.175,63.596,12.302,63.671,12.533,63.844,12.663,63.94,12.69,63.957,12.793,64,12.988,64.05,13.204,64.075,13.3,64.075,13.671,64.041,13.961,64.014,14.003,64.041,14.063,64.096,14.141,64.174,14.148,64.26,14.12,64.388,14.078,64.464,13.874,64.514,13.65,64.582,13.925,64.797,14.115,64.946,14.352,65.171,14.426,65.264,14.48,65.301,14.55,65.646,14.596,65.743,14.635,65.793,14.635,65.845,14.61,65.932,14.543,66.129,14.918,66.154,15.04,66.168,15.153,66.191,15.375,66.252,15.484,66.306,15.423,66.49,15.557,66.552,15.884,66.769,16.238,66.976,16.404,67.055,16.421,67.093,16.434,67.155,16.361,67.252,16.282,67.312,16.127,67.426,16.194,67.505,16.307,67.521,16.457,67.552,16.574,67.62,16.586,67.628,16.784,67.895,17.171,68.03,17.325,68.104,17.565,68.048,17.917,67.965,18.073,68.088,18.125,68.133,18.177,68.201,18.156,68.317,18.147,68.468,18.163,68.528,18.303,68.555,18.379,68.562,18.77,68.5,18.868,68.501,19.053,68.493,19.259,68.465,19.691,68.392,19.87,68.362,19.97,68.356,20.056,68.39,20.24,68.478,19.969,68.542,20.147,68.607,20.24,68.673,20.319,68.754,20.348,68.849,20.337,68.9,20.282,68.934,20.117,69.021,20.492,69.033,20.622,69.037,20.895,68.98,20.907,68.967,20.909,68.938,20.919,68.907,21.183,68.829,21.26,68.787,21.422,68.725,21.465,68.691,21.616,68.651,21.724,68.609,21.85,68.574,21.997,68.521,22.195,68.478,22.362,68.464,22.782,68.391,22.854,68.367,22.975,68.316,23.098,68.258,23.183,68.137,23.319,68.13,23.355,68.089,23.474,68.017,23.639,67.954,23.633,67.933,23.502,67.875,23.488,67.797,23.5,67.696,23.541,67.614,23.537,67.59,23.504,67.562,23.465,67.518,23.451,67.479,23.455,67.46,23.468,67.45,23.537,67.449,23.661,67.44,23.734,67.423,23.775,67.329,23.761,67.31,23.657,67.268,23.626,67.234,23.623,67.184,23.642,67.129,23.677,67.068,23.759,67.003,23.869,66.934,23.942,66.878,23.976,66.838,23.989,66.811,23.939,66.776,23.894,66.707,23.886,66.628,23.866,66.577,23.768,66.506,23.701,66.481,23.682,66.443,23.674,66.381,23.694,66.304,23.7,66.253,23.721,66.215,23.751,66.191,23.907,66.148,23.995,66.06,24.049,65.99,24.155,65.805,23.891,65.782,23.691,65.829,23.592,65.805,23.418,65.804,23.221,65.786,23.155,65.75,23.102,65.735,22.919,65.786,22.747,65.871,22.62,65.807,22.539,65.794,22.465,65.853,22.401,65.862,22.366,65.843,22.336,65.791,22.288,65.751,22.275,65.725,22.267,65.622,22.254,65.598,22.086,65.611,22.096,65.584,22.133,65.57,22.148,65.553,22.087,65.53,21.92,65.532,21.903,65.508,21.95,65.47,21.913,65.437,21.88,65.424,21.681,65.403,21.566,65.408,21.533,65.387,21.523,65.359,21.545,65.331,21.596,65.317,21.613,65.299,21.609,65.261,21.567,65.255,21.447,65.321,21.41,65.317,21.438,65.283,21.506,65.245,21.546,65.207,21.581,65.161,21.574,65.126,21.425,65.013,21.294,64.941,21.196,64.877,21.138,64.809,21.205,64.774,21.279,64.725,21.332,64.629,21.394,64.544,21.52,64.463,21.494,64.416,21.465,64.38,21.256,64.299,21.018,64.178,20.763,63.868,20.678,63.826,20.454,63.774,20.371,63.723,20.205,63.662,19.914,63.611,19.782,63.538,19.722,63.463,19.656,63.458,19.59,63.487,19.502,63.509,19.491,63.46,19.495,63.424,19.354,63.477,19.288,63.429,19.236,63.347,19.034,63.238,18.817,63.257,18.792,63.238,18.85,63.224,18.859,63.207,18.819,63.197,18.76,63.198,18.667,63.177,18.606,63.178,18.578,63.126,18.531,63.064,18.408,63.038,18.344,63.032,18.313,62.996,18.502,62.989,18.487,62.959,18.483,62.928,18.463,62.896,18.248,62.849,18.215,62.812,18.17,62.789,18.074,62.791,18.078,62.812,18.094,62.836,17.951,62.834,17.907,62.887,17.88,62.873,17.896,62.831,17.933,62.786,17.974,62.721,17.941,62.68,17.903,62.659,17.93,62.641,18.007,62.626,18.037,62.601,17.947,62.578,17.834,62.503,17.718,62.501,17.646,62.451,17.571,62.451,17.509,62.483,17.41,62.508,17.378,62.463,17.373,62.427,17.429,62.335,17.535,62.264,17.634,62.233,17.563,62.212,17.51,62.166,17.447,62.023,17.412,61.966,17.375,61.866,17.398,61.782,17.417,61.741,17.465,61.684,17.335,61.692,17.196,61.725,17.216,61.656,17.131,61.576,17.147,61.505,17.164,61.458,17.138,61.382,17.178,61.358,17.2,61.312,17.164,61.278,17.18,61.249,17.186,61.147,17.213,60.986,17.203,60.952,17.279,60.812,17.261,60.763,17.251,60.701,17.36,60.641,17.457,60.642,17.555,60.643,17.593,60.628,17.631,60.585,17.661,60.535,17.742,60.539,17.872,60.58,17.956,60.59,18.011,60.511,18.163,60.408,18.25,60.362,18.4,60.337,18.558,60.254,18.535,60.153,18.601,60.119,18.787,60.079,18.853,60.026,18.884,59.98,18.933,59.942,18.99,59.828,18.971,59.757,18.896,59.733,18.719,59.657,18.64,59.601,18.578,59.566,18.402,59.49,18.338,59.477,18.276,59.438,18.217,59.421,18.164,59.43,17.964,59.359,17.98,59.329,18.133,59.316,18.211,59.331,18.271,59.367,18.336,59.375,18.396,59.369,18.459,59.397,18.509,59.408,18.56,59.394,18.618,59.327,18.499,59.292,18.414,59.29,18.373,59.18,18.322,59.132,18.285,59.109,18.098,59.062,17.975,59.003,17.829,58.955,17.765,58.965,17.67,58.916,17.457,58.858,17.348,58.781,17.103,58.711,16.978,58.654,16.639,58.651,16.316,58.664,16.214,58.637,16.318,58.628,16.391,58.602,16.478,58.613,16.683,58.6,16.788,58.585,16.924,58.493,16.824,58.46,16.652,58.434,16.717,58.303,16.77,58.214,16.7,58.161,16.695,57.918,16.597,57.913,16.555,57.812,16.586,57.761,16.584,57.642,16.604,57.568,16.652,57.501,16.631,57.43,16.476,57.265,16.479,57.188,16.507,57.142,16.528,57.068,16.458,56.927,16.408,56.809,16.349,56.709,16.217,56.59,16.151,56.501,15.997,56.223,15.92,56.167,15.827,56.125,15.722,56.164,15.627,56.186,15.51,56.183,15.327,56.151,15.051,56.172,14.782,56.162,14.714,56.134,14.755,56.033,14.656,56.02,14.559,56.049,14.473,56.014,14.402,55.977,14.262,55.888,14.215,55.833,14.203,55.729,14.276,55.636,14.342,55.528,14.174,55.397,14.08,55.392,13.806,55.429,13.321,55.346,12.886,55.411,12.941,55.482,12.939,55.533,12.963,55.613,12.978,55.694,12.974,55.748,12.942,55.806,12.835,55.882,12.593,56.138,12.521,56.246,12.471,56.291,12.507,56.293,12.706,56.235,12.753,56.242,12.802,56.264,12.742,56.347,12.691,56.384,12.656,56.441,12.773,56.456,12.857,56.452,12.92,56.516,12.884,56.618,12.793,56.649,12.718,56.663,12.573,56.823,12.421,56.906,12.152,57.227,12.053,57.447,11.962,57.426,11.917,57.522,11.885,57.613,11.879,57.679,11.735,57.718,11.729,57.764,11.703,57.973,11.549,58.001,11.449,58.118,11.432,58.34,11.33,58.38,11.248,58.369,11.252,58.424,11.272,58.476,11.224,58.68,11.208,58.866,11.169,58.923,11.147,58.989,11.167,59.046,11.196,59.078,11.295,59.087,11.388,59.037]],[[19.156,57.923,19.138,57.86,19.087,57.865,19.039,57.911,19.135,57.981,19.281,57.978,19.331,57.963,19.156,57.923]],[[18.416,59.029,18.372,59.02,18.35,59.023,18.377,59.069,18.398,59.089,18.465,59.108,18.486,59.105,18.416,59.029]],[[18.595,59.47,18.57,59.437,18.545,59.478,18.555,59.486,18.572,59.526,18.621,59.548,18.698,59.535,18.698,59.525,18.624,59.492,18.595,59.47]]]},{"n":"Eswatini","c":"SZ","r":4,"ml":4,"lx":31.47,"ly":-26.53,"b":[30.788,-27.31,32.113,-25.743],"p":[[[31.948,-25.958,31.968,-25.972,32.061,-26.018,32.069,-26.11,32.06,-26.215,32.041,-26.281,32.048,-26.347,32.078,-26.45,32.106,-26.52,32.113,-26.839,32.082,-26.825,32.025,-26.811,31.995,-26.817,31.967,-26.961,31.946,-27.174,31.958,-27.306,31.743,-27.31,31.47,-27.296,31.274,-27.238,31.063,-27.112,30.938,-26.916,30.883,-26.792,30.807,-26.785,30.794,-26.764,30.788,-26.614,30.789,-26.455,30.803,-26.413,30.945,-26.219,31.033,-26.098,31.088,-25.981,31.207,-25.843,31.335,-25.756,31.383,-25.743,31.415,-25.747,31.64,-25.867,31.871,-25.982,31.922,-25.969,31.948,-25.958]]]},{"n":"Suriname","c":"SR","r":4,"ml":4,"lx":-55.91,"ly":4.14,"b":[-58.054,1.842,-53.99,5.993],"p":[[[-54.156,5.359,-54.24,5.288,-54.332,5.187,-54.452,5.013,-54.447,4.959,-54.473,4.915,-54.48,4.837,-54.471,4.749,-54.44,4.692,-54.426,4.583,-54.45,4.485,-54.441,4.428,-54.416,4.338,-54.396,4.241,-54.398,4.202,-54.369,4.171,-54.342,4.14,-54.351,4.054,-54.256,3.901,-54.197,3.834,-54.113,3.769,-54.082,3.706,-54.034,3.629,-54.006,3.62,-53.99,3.59,-54.006,3.531,-54.01,3.449,-54.063,3.353,-54.188,3.179,-54.203,3.138,-54.171,2.994,-54.188,2.875,-54.196,2.818,-54.257,2.714,-54.402,2.462,-54.486,2.416,-54.536,2.343,-54.568,2.343,-54.605,2.336,-54.616,2.327,-54.662,2.328,-54.697,2.36,-54.703,2.398,-54.722,2.442,-54.767,2.455,-54.852,2.44,-54.876,2.45,-54.927,2.497,-54.968,2.548,-54.979,2.598,-55.006,2.593,-55.07,2.548,-55.114,2.539,-55.149,2.551,-55.188,2.548,-55.286,2.5,-55.344,2.489,-55.385,2.441,-55.659,2.419,-55.731,2.406,-55.894,2.49,-55.936,2.517,-55.957,2.52,-55.976,2.516,-55.994,2.498,-56.02,2.393,-56.045,2.364,-56.088,2.341,-56.129,2.3,-56.138,2.259,-56.074,2.237,-56.02,2.158,-55.962,2.095,-55.915,2.04,-55.922,1.977,-55.93,1.888,-55.963,1.857,-56.02,1.842,-56.227,1.885,-56.386,1.924,-56.453,1.932,-56.483,1.942,-56.522,1.975,-56.563,2.005,-56.627,2.016,-56.704,2.036,-56.761,2.115,-56.82,2.227,-56.841,2.277,-56.886,2.326,-56.931,2.395,-56.945,2.457,-56.979,2.513,-56.997,2.532,-57.023,2.609,-57.029,2.638,-57.042,2.641,-57.06,2.666,-57.097,2.748,-57.105,2.768,-57.121,2.776,-57.164,2.833,-57.197,2.853,-57.21,2.883,-57.207,2.963,-57.225,3.003,-57.231,3.079,-57.232,3.109,-57.249,3.142,-57.278,3.164,-57.283,3.219,-57.29,3.354,-57.304,3.377,-57.426,3.375,-57.438,3.362,-57.491,3.354,-57.55,3.353,-57.603,3.371,-57.647,3.395,-57.656,3.424,-57.649,3.517,-57.72,3.588,-57.833,3.676,-57.867,3.787,-57.908,3.857,-58.032,4.002,-58.054,4.102,-58.054,4.172,-58.011,4.236,-57.95,4.35,-57.925,4.453,-57.906,4.507,-57.875,4.577,-57.846,4.668,-57.868,4.724,-57.905,4.779,-57.917,4.82,-57.881,4.881,-57.845,4.923,-57.804,4.929,-57.752,4.954,-57.711,4.991,-57.649,5.001,-57.571,5.004,-57.412,5.005,-57.331,5.02,-57.306,5.05,-57.31,5.106,-57.269,5.157,-57.227,5.179,-57.21,5.195,-57.207,5.214,-57.218,5.232,-57.235,5.243,-57.28,5.247,-57.319,5.335,-57.292,5.374,-57.258,5.445,-57.248,5.485,-57.195,5.548,-57.182,5.529,-57.141,5.644,-57.136,5.737,-57.105,5.829,-57.057,5.939,-56.97,5.993,-56.466,5.938,-56.236,5.885,-55.94,5.795,-55.898,5.699,-55.896,5.795,-55.91,5.893,-55.828,5.962,-55.648,5.986,-55.379,5.953,-55.148,5.993,-54.834,5.988,-54.356,5.91,-54.142,5.856,-54.054,5.808,-54.037,5.721,-54.046,5.609,-54.08,5.502,-54.156,5.359]]]},{"n":"South Sudan","c":"SS","r":3,"ml":3,"lx":30.39,"ly":7.23,"b":[24.147,3.491,35.268,12.223],"p":[[[33.976,4.22,33.742,3.985,33.568,3.812,33.54,3.787,33.489,3.755,33.324,3.754,33.154,3.775,32.997,3.88,32.838,3.798,32.737,3.773,32.677,3.763,32.535,3.75,32.336,3.706,32.246,3.651,32.197,3.608,32.156,3.528,32.136,3.52,32.099,3.529,32.048,3.561,31.942,3.608,31.888,3.709,31.839,3.77,31.798,3.803,31.629,3.701,31.547,3.678,31.48,3.68,31.357,3.738,31.222,3.786,31.152,3.786,31.048,3.725,30.929,3.634,30.868,3.544,30.839,3.491,30.817,3.533,30.797,3.573,30.757,3.624,30.7,3.644,30.648,3.634,30.587,3.624,30.559,3.653,30.554,3.723,30.537,3.787,30.508,3.836,30.421,3.884,30.195,3.982,30.021,4.178,29.934,4.269,29.87,4.327,29.78,4.481,29.677,4.587,29.552,4.636,29.47,4.612,29.385,4.498,29.225,4.392,29.151,4.388,29.057,4.446,28.939,4.487,28.727,4.505,28.64,4.454,28.525,4.373,28.428,4.324,28.367,4.319,28.311,4.338,28.247,4.349,28.192,4.35,28.079,4.425,28.02,4.479,27.981,4.532,27.917,4.568,27.842,4.598,27.788,4.645,27.761,4.703,27.719,4.778,27.664,4.846,27.491,4.968,27.439,5.039,27.403,5.109,27.332,5.186,27.257,5.29,27.233,5.441,27.229,5.563,27.213,5.619,27.181,5.675,27.144,5.723,27.083,5.777,26.942,5.855,26.796,5.946,26.726,5.998,26.594,6.018,26.514,6.069,26.447,6.183,26.421,6.274,26.353,6.345,26.325,6.396,26.309,6.455,26.362,6.635,26.285,6.699,26.169,6.782,26.087,6.872,26.037,6.955,25.889,7.065,25.567,7.229,25.381,7.333,25.279,7.427,25.19,7.519,25.181,7.557,25.239,7.649,25.247,7.725,25.2,7.808,25.007,7.965,24.853,8.138,24.737,8.192,24.456,8.239,24.375,8.258,24.291,8.291,24.208,8.369,24.18,8.461,24.221,8.608,24.195,8.653,24.147,8.666,24.16,8.696,24.214,8.768,24.3,8.814,24.532,8.887,24.545,8.915,24.549,9.007,24.568,9.052,24.648,9.179,24.659,9.23,24.663,9.338,24.674,9.389,24.697,9.426,24.76,9.489,24.783,9.527,24.792,9.61,24.785,9.775,24.818,9.84,24.964,9.989,25.003,10.055,25.016,10.115,25.015,10.176,25.024,10.236,25.067,10.294,25.104,10.312,25.212,10.33,25.285,10.319,25.798,10.421,25.858,10.406,25.885,10.346,25.883,10.25,25.892,10.203,25.919,10.169,26.001,10.123,26.057,10.047,26.087,10.018,26.17,9.966,26.551,9.526,26.659,9.484,26.763,9.499,26.971,9.591,27.074,9.614,27.8,9.588,27.881,9.602,27.886,9.6,27.996,9.379,28.049,9.329,28.845,9.326,28.829,9.389,28.839,9.459,28.932,9.549,28.98,9.594,29,9.61,29.122,9.675,29.242,9.718,29.473,9.769,29.557,9.848,29.604,9.921,29.605,10.065,29.636,10.089,29.691,10.122,29.958,10.25,30.003,10.277,30.475,9.979,30.739,9.743,30.755,9.731,30.769,9.727,30.783,9.735,30.795,9.746,30.814,9.753,30.827,9.756,30.94,9.759,31.154,9.771,31.225,9.799,31.655,10.221,31.764,10.356,31.792,10.383,31.854,10.479,31.92,10.644,31.933,10.663,32.404,11.058,32.421,11.089,32.425,11.114,32.354,11.247,32.339,11.315,32.336,11.419,32.35,11.58,32.345,11.683,32.343,11.694,32.338,11.71,32.335,11.716,32.072,12.007,32.737,12.01,32.738,12.034,32.738,12.046,32.736,12.058,32.723,12.093,32.716,12.139,32.715,12.152,32.716,12.165,32.72,12.189,32.721,12.202,32.72,12.208,32.719,12.214,32.719,12.219,32.722,12.223,33.199,12.217,33.193,12.135,33.135,11.942,33.136,11.826,33.122,11.693,33.119,11.682,33.106,11.654,33.095,11.638,33.082,11.622,33.078,11.616,33.073,11.606,33.073,11.592,33.172,10.85,33.168,10.831,33.165,10.819,33.138,10.773,33.131,10.758,33.13,10.746,33.141,10.738,33.361,10.658,33.371,10.653,33.38,10.646,33.459,10.551,33.892,10.199,33.907,10.181,33.952,10.071,33.957,10.054,33.958,10.028,33.957,10.007,33.946,9.941,33.95,9.911,33.957,9.891,33.963,9.869,33.963,9.862,33.963,9.856,33.959,9.845,33.895,9.718,33.874,9.627,33.868,9.55,33.871,9.506,33.879,9.478,33.882,9.471,33.885,9.466,33.888,9.464,33.891,9.462,34.077,9.462,34.078,9.462,34.077,9.421,34.085,9.219,34.091,9.041,34.102,8.752,34.102,8.676,34.095,8.582,34.073,8.545,34.02,8.492,33.953,8.444,33.785,8.431,33.645,8.433,33.545,8.443,33.409,8.448,33.281,8.437,33.234,8.396,33.165,8.251,33.065,8.04,33.013,7.952,32.999,7.9,33.015,7.869,33.081,7.824,33.226,7.761,33.392,7.724,33.516,7.708,33.601,7.69,33.666,7.671,33.902,7.51,33.978,7.435,34.02,7.368,34.03,7.297,34.064,7.226,34.2,7.085,34.279,7.003,34.484,6.898,34.563,6.78,34.639,6.722,34.711,6.66,34.749,6.568,34.838,6.3,34.898,6.16,34.959,6.045,34.984,5.858,35.032,5.775,35.082,5.673,35.164,5.581,35.252,5.511,35.268,5.492,35.084,5.312,34.878,5.11,34.64,4.875,34.38,4.621,34.177,4.419,33.976,4.22]]]},{"n":"Sudan","c":"SD","r":3,"ml":2.5,"lx":29.26,"ly":16.33,"b":[21.825,8.666,38.609,22.202],"p":[[[34.078,9.462,34.077,9.462,33.891,9.462,33.888,9.464,33.885,9.466,33.882,9.471,33.879,9.478,33.871,9.506,33.868,9.55,33.874,9.627,33.895,9.718,33.959,9.845,33.963,9.856,33.963,9.862,33.963,9.869,33.957,9.891,33.95,9.911,33.946,9.941,33.957,10.007,33.958,10.028,33.957,10.054,33.952,10.071,33.907,10.181,33.892,10.199,33.459,10.551,33.38,10.646,33.371,10.653,33.361,10.658,33.141,10.738,33.13,10.746,33.131,10.758,33.138,10.773,33.165,10.819,33.168,10.831,33.172,10.85,33.073,11.592,33.073,11.606,33.078,11.616,33.082,11.622,33.095,11.638,33.106,11.654,33.119,11.682,33.122,11.693,33.136,11.826,33.135,11.942,33.193,12.135,33.199,12.217,32.722,12.223,32.719,12.219,32.719,12.214,32.72,12.208,32.721,12.202,32.72,12.189,32.716,12.165,32.715,12.152,32.716,12.139,32.723,12.093,32.736,12.058,32.738,12.046,32.738,12.034,32.737,12.01,32.072,12.007,32.335,11.716,32.338,11.71,32.343,11.694,32.345,11.683,32.35,11.58,32.336,11.419,32.339,11.315,32.354,11.247,32.425,11.114,32.421,11.089,32.404,11.058,31.933,10.663,31.92,10.644,31.854,10.479,31.792,10.383,31.764,10.356,31.655,10.221,31.225,9.799,31.154,9.771,30.94,9.759,30.827,9.756,30.814,9.753,30.795,9.746,30.783,9.735,30.769,9.727,30.755,9.731,30.739,9.743,30.475,9.979,30.003,10.277,29.958,10.25,29.691,10.122,29.636,10.089,29.605,10.065,29.604,9.921,29.557,9.848,29.473,9.769,29.242,9.718,29.122,9.675,29,9.61,28.98,9.594,28.932,9.549,28.839,9.459,28.829,9.389,28.845,9.326,28.049,9.329,27.996,9.379,27.886,9.6,27.881,9.602,27.8,9.588,27.074,9.614,26.971,9.591,26.763,9.499,26.659,9.484,26.551,9.526,26.17,9.966,26.087,10.018,26.057,10.047,26.001,10.123,25.919,10.169,25.892,10.203,25.883,10.25,25.885,10.346,25.858,10.406,25.798,10.421,25.285,10.319,25.212,10.33,25.104,10.312,25.067,10.294,25.024,10.236,25.015,10.176,25.016,10.115,25.003,10.055,24.964,9.989,24.818,9.84,24.785,9.775,24.792,9.61,24.783,9.527,24.76,9.489,24.697,9.426,24.674,9.389,24.663,9.338,24.659,9.23,24.648,9.179,24.568,9.052,24.549,9.007,24.545,8.915,24.532,8.887,24.3,8.814,24.214,8.768,24.16,8.696,24.147,8.666,24.048,8.691,23.922,8.71,23.679,8.732,23.583,8.766,23.537,8.816,23.552,8.943,23.528,8.971,23.489,8.993,23.463,9.048,23.468,9.115,23.596,9.262,23.623,9.341,23.643,9.614,23.656,9.71,23.646,9.823,23.545,10.03,23.457,10.174,23.312,10.388,23.256,10.458,22.964,10.752,22.931,10.795,22.86,10.92,22.895,11.029,22.938,11.192,22.943,11.267,22.923,11.345,22.849,11.403,22.783,11.41,22.754,11.44,22.697,11.483,22.641,11.516,22.591,11.58,22.556,11.67,22.581,11.99,22.564,12.033,22.49,12.045,22.472,12.068,22.475,12.129,22.435,12.312,22.39,12.463,22.414,12.546,22.352,12.66,22.233,12.709,22.121,12.695,22.001,12.672,21.928,12.678,21.878,12.699,21.843,12.741,21.825,12.791,21.842,12.865,21.908,13.001,21.99,13.113,22.158,13.215,22.203,13.269,22.228,13.33,22.233,13.399,22.221,13.472,22.202,13.538,22.153,13.626,22.108,13.73,22.106,13.8,22.128,13.85,22.173,13.911,22.262,13.979,22.283,13.992,22.339,14.029,22.388,14.056,22.51,14.127,22.539,14.162,22.528,14.203,22.498,14.237,22.449,14.284,22.439,14.342,22.425,14.441,22.4,14.504,22.382,14.55,22.416,14.585,22.468,14.633,22.532,14.663,22.632,14.688,22.671,14.722,22.682,14.789,22.679,14.851,22.715,14.898,22.763,14.999,22.802,15.044,22.867,15.097,22.932,15.162,22.961,15.238,22.97,15.311,22.934,15.533,23.009,15.626,23.105,15.703,23.243,15.697,23.458,15.714,23.604,15.746,23.708,15.745,23.946,15.704,23.965,15.713,23.971,15.722,23.983,15.78,23.983,15.928,23.983,16.374,23.983,16.82,23.982,17.266,23.982,17.712,23.981,18.158,23.981,18.605,23.981,19.051,23.98,19.497,23.98,19.621,23.98,19.746,23.98,19.871,23.98,19.996,24.227,19.996,24.474,19.996,24.72,19.996,24.967,19.995,24.97,19.997,24.973,19.999,24.976,20.001,24.979,20.003,24.98,20.501,24.98,20.999,24.98,21.498,24.98,21.996,25.362,21.996,25.744,21.996,26.126,21.996,26.508,21.996,26.89,21.996,27.272,21.996,27.654,21.995,28.036,21.995,28.419,21.995,28.801,21.995,29.183,21.995,29.565,21.995,29.947,21.995,30.329,21.995,30.711,21.995,31.093,21.995,31.209,21.995,31.261,22.002,31.358,22.189,31.4,22.202,31.464,22.192,31.486,22.148,31.466,22.085,31.434,21.996,31.622,21.996,31.95,21.996,32.278,21.996,32.606,21.996,32.934,21.996,33.262,21.996,33.59,21.996,33.918,21.996,34.246,21.996,34.575,21.996,34.903,21.996,35.231,21.996,35.559,21.996,35.887,21.997,36.215,21.997,36.543,21.997,36.871,21.997,36.883,21.769,36.927,21.587,37.081,21.326,37.212,21.186,37.259,21.109,37.263,21.073,37.257,21.039,37.217,21.078,37.151,21.104,37.141,20.982,37.157,20.895,37.173,20.732,37.228,20.557,37.188,20.395,37.193,20.121,37.263,19.792,37.248,19.582,37.362,19.092,37.471,18.82,37.532,18.753,37.599,18.717,37.73,18.694,37.922,18.556,38.074,18.41,38.128,18.333,38.202,18.249,38.252,18.264,38.283,18.287,38.333,18.219,38.574,18.073,38.609,18.005,38.523,17.939,38.422,17.824,38.397,17.778,38.386,17.751,38.374,17.717,38.347,17.684,38.29,17.637,38.267,17.617,38.254,17.585,38.219,17.564,38.182,17.563,38.149,17.549,38.099,17.526,38.025,17.538,37.95,17.518,37.923,17.492,37.863,17.47,37.803,17.466,37.782,17.458,37.726,17.421,37.657,17.368,37.576,17.335,37.547,17.324,37.51,17.288,37.453,17.109,37.411,17.062,37.34,17.057,37.249,17.057,37.17,17.041,37.062,17.061,37.009,17.059,36.995,17.021,36.976,16.867,36.979,16.801,36.936,16.722,36.888,16.625,36.905,16.46,36.914,16.296,36.826,16.05,36.813,15.994,36.725,15.799,36.679,15.726,36.566,15.362,36.522,15.25,36.427,15.132,36.448,14.94,36.471,14.736,36.492,14.544,36.524,14.257,36.444,13.988,36.447,13.842,36.391,13.626,36.346,13.526,36.307,13.467,36.274,13.406,36.212,13.271,36.16,13.093,36.137,12.911,36.135,12.805,36.125,12.757,36.108,12.726,35.988,12.706,35.821,12.685,35.731,12.661,35.67,12.624,35.596,12.537,35.45,12.301,35.373,12.156,35.252,11.957,35.112,11.817,35.083,11.748,35.06,11.621,35.008,11.42,34.961,11.277,34.969,11.162,34.925,10.962,34.931,10.865,34.882,10.811,34.816,10.759,34.771,10.746,34.675,10.805,34.602,10.865,34.572,10.88,34.508,10.843,34.431,10.788,34.344,10.659,34.276,10.528,34.315,10.252,34.311,10.191,34.292,10.125,34.185,9.919,34.159,9.853,34.12,9.73,34.079,9.513,34.078,9.462]]]},{"n":"Sri Lanka","c":"LK","r":3,"ml":3,"lx":80.7,"ly":7.58,"b":[79.708,5.949,81.877,9.813],"p":[[[79.97,9.631,79.907,9.62,79.857,9.686,79.846,9.715,79.859,9.734,79.872,9.744,79.888,9.741,79.912,9.679,79.97,9.631]],[[79.875,9.051,79.904,8.975,79.821,9.027,79.767,9.07,79.748,9.105,79.86,9.066,79.875,9.051]],[[79.982,9.813,80.078,9.807,80.181,9.81,80.253,9.796,80.376,9.642,80.711,9.366,80.893,9.086,80.91,9.025,80.935,8.971,80.979,8.957,81.016,8.933,81.198,8.662,81.219,8.608,81.216,8.549,81.227,8.506,81.275,8.484,81.334,8.472,81.373,8.431,81.422,8.215,81.422,8.148,81.436,8.119,81.665,7.782,81.679,7.742,81.676,7.711,81.683,7.684,81.727,7.625,81.797,7.465,81.832,7.428,81.874,7.288,81.877,7.02,81.861,6.901,81.819,6.756,81.768,6.614,81.713,6.512,81.637,6.425,81.38,6.241,81.306,6.204,80.971,6.088,80.724,5.979,80.496,5.949,80.267,6.01,80.095,6.153,80.007,6.364,79.947,6.585,79.859,6.829,79.792,7.585,79.76,7.796,79.708,8.066,79.713,8.182,79.75,8.294,79.75,8.049,79.783,8.018,79.809,8.05,79.832,8.304,79.851,8.412,79.942,8.692,79.944,8.741,79.928,8.846,79.929,8.899,80.065,9.096,80.1,9.21,80.118,9.327,80.111,9.453,80.086,9.578,80.196,9.538,80.256,9.495,80.318,9.465,80.368,9.48,80.428,9.481,80.385,9.549,80.258,9.611,80.046,9.65,79.979,9.699,79.954,9.742,79.967,9.793,79.982,9.813]]]},{"n":"Spain","c":"ES","r":2,"ml":2,"lx":-3.46,"ly":40.09,"b":[-18.161,27.646,4.322,43.765],"p":[[[1.594,38.672,1.571,38.659,1.505,38.671,1.406,38.671,1.402,38.711,1.417,38.74,1.436,38.768,1.497,38.712,1.593,38.701,1.594,38.672]],[[3.145,39.79,3.241,39.757,3.342,39.787,3.396,39.777,3.449,39.761,3.462,39.698,3.415,39.627,3.349,39.556,3.293,39.477,3.245,39.387,3.155,39.333,3.073,39.301,2.9,39.368,2.8,39.385,2.77,39.41,2.746,39.51,2.701,39.542,2.634,39.556,2.576,39.531,2.5,39.478,2.459,39.53,2.394,39.54,2.37,39.572,2.371,39.613,2.785,39.855,2.905,39.908,3.159,39.971,3.198,39.961,3.164,39.924,3.167,39.908,3.199,39.89,3.191,39.861,3.159,39.837,3.145,39.79]],[[4.294,39.842,4.275,39.83,3.968,39.946,3.867,39.959,3.843,39.976,3.845,40.036,3.853,40.063,4.059,40.075,4.226,40.032,4.315,39.917,4.322,39.898,4.294,39.842]],[[1.445,38.919,1.409,38.857,1.257,38.879,1.223,38.904,1.256,38.973,1.3,38.982,1.303,39.031,1.349,39.081,1.564,39.121,1.613,39.087,1.624,39.039,1.495,38.933,1.445,38.919]],[[-1.794,43.407,-1.793,43.373,-1.753,43.325,-1.713,43.307,-1.627,43.282,-1.561,43.279,-1.472,43.268,-1.411,43.24,-1.407,43.197,-1.423,43.149,-1.459,43.105,-1.48,43.071,-1.461,43.052,-1.429,43.037,-1.394,43.033,-1.371,43.038,-1.353,43.064,-1.319,43.097,-1.3,43.101,-1.302,43.082,-1.285,43.06,-1.175,43.021,-0.934,42.95,-0.839,42.948,-0.763,42.94,-0.74,42.91,-0.586,42.799,-0.55,42.802,-0.481,42.799,-0.398,42.808,-0.339,42.829,-0.299,42.825,-0.256,42.804,-0.205,42.785,-0.14,42.749,-0.081,42.704,-0.041,42.689,0.201,42.719,0.255,42.693,0.313,42.693,0.377,42.7,0.518,42.686,0.632,42.69,0.642,42.701,0.652,42.8,0.67,42.836,0.697,42.845,0.764,42.838,1.01,42.779,1.111,42.742,1.208,42.713,1.293,42.71,1.349,42.691,1.428,42.596,1.415,42.548,1.422,42.531,1.43,42.498,1.428,42.461,1.449,42.437,1.486,42.434,1.534,42.442,1.586,42.456,1.679,42.497,1.706,42.503,1.86,42.457,1.928,42.426,1.951,42.393,1.987,42.358,2.033,42.354,2.098,42.386,2.2,42.421,2.374,42.39,2.568,42.346,2.652,42.34,2.655,42.362,2.67,42.393,2.702,42.408,2.749,42.413,2.816,42.429,2.891,42.456,2.97,42.467,3.053,42.447,3.152,42.431,3.211,42.431,3.24,42.368,3.288,42.344,3.307,42.289,3.219,42.26,3.166,42.256,3.15,42.162,3.175,42.136,3.225,42.111,3.238,42.082,3.248,41.944,3.147,41.861,3.005,41.767,2.311,41.467,2.146,41.321,2.083,41.287,1.567,41.196,1.206,41.098,1.033,41.062,0.817,40.892,0.715,40.823,0.796,40.804,0.891,40.722,0.859,40.686,0.721,40.63,0.66,40.613,0.627,40.622,0.596,40.615,0.364,40.319,0.158,40.107,0.043,40.014,-0.075,39.876,-0.327,39.52,-0.329,39.417,-0.205,39.063,-0.134,38.969,-0.034,38.891,0.155,38.825,0.202,38.759,0.136,38.697,-0.053,38.586,-0.381,38.436,-0.521,38.317,-0.551,38.203,-0.647,38.152,-0.683,37.992,-0.742,37.886,-0.753,37.85,-0.815,37.77,-0.823,37.712,-0.722,37.631,-0.772,37.596,-0.822,37.581,-0.938,37.571,-1.328,37.561,-1.641,37.387,-1.798,37.233,-1.939,36.946,-2.112,36.777,-2.188,36.745,-2.306,36.82,-2.453,36.831,-2.596,36.806,-2.671,36.748,-2.788,36.715,-2.902,36.743,-3.149,36.758,-3.259,36.756,-3.431,36.708,-3.579,36.74,-3.828,36.756,-4.367,36.718,-4.435,36.7,-4.502,36.629,-4.674,36.506,-4.935,36.502,-5.171,36.424,-5.231,36.374,-5.33,36.236,-5.361,36.135,-5.382,36.134,-5.407,36.159,-5.444,36.151,-5.462,36.074,-5.551,36.039,-5.625,36.026,-5.808,36.088,-5.961,36.182,-6.041,36.188,-6.17,36.334,-6.226,36.426,-6.266,36.527,-6.258,36.565,-6.269,36.597,-6.384,36.637,-6.412,36.729,-6.328,36.848,-6.259,36.899,-6.217,36.914,-6.321,36.908,-6.396,36.832,-6.492,36.955,-6.885,37.194,-6.859,37.249,-6.864,37.279,-6.929,37.215,-6.975,37.198,-7.175,37.209,-7.406,37.179,-7.467,37.428,-7.496,37.524,-7.504,37.585,-7.444,37.728,-7.379,37.786,-7.292,37.906,-7.185,38.006,-7.073,38.03,-7.023,38.045,-6.981,38.122,-6.958,38.188,-6.975,38.194,-7.106,38.181,-7.343,38.457,-7.336,38.501,-7.306,38.567,-7.286,38.649,-7.282,38.715,-7.22,38.771,-7.125,38.827,-7.046,38.907,-7.006,38.985,-6.998,39.056,-7.043,39.107,-7.172,39.135,-7.306,39.338,-7.335,39.465,-7.363,39.478,-7.445,39.536,-7.524,39.645,-7.536,39.662,-7.454,39.681,-7.118,39.682,-7.047,39.706,-7.037,39.714,-6.975,39.798,-6.911,39.937,-6.896,40.022,-6.916,40.057,-7.028,40.143,-7.033,40.168,-7.015,40.208,-6.948,40.252,-6.859,40.301,-6.81,40.343,-6.822,40.376,-6.848,40.411,-6.852,40.443,-6.836,40.483,-6.83,40.619,-6.818,40.654,-6.836,40.777,-6.858,40.878,-6.928,41.009,-6.916,41.038,-6.883,41.062,-6.776,41.108,-6.69,41.215,-6.566,41.304,-6.403,41.375,-6.289,41.455,-6.244,41.516,-6.212,41.532,-6.222,41.56,-6.243,41.602,-6.308,41.642,-6.392,41.665,-6.485,41.664,-6.542,41.673,-6.559,41.704,-6.553,41.79,-6.558,41.874,-6.575,41.913,-6.618,41.942,-6.704,41.935,-6.777,41.958,-6.833,41.964,-6.866,41.945,-7.03,41.951,-7.099,41.964,-7.147,41.981,-7.178,41.972,-7.195,41.955,-7.198,41.929,-7.21,41.895,-7.269,41.864,-7.404,41.834,-7.513,41.836,-7.613,41.858,-7.645,41.874,-7.693,41.888,-7.896,41.871,-7.921,41.884,-7.991,41.852,-8.094,41.814,-8.152,41.812,-8.174,41.82,-8.181,41.837,-8.225,41.896,-8.213,41.927,-8.13,42.018,-8.139,42.04,-8.174,42.069,-8.204,42.112,-8.213,42.134,-8.266,42.137,-8.323,42.115,-8.538,42.069,-8.59,42.053,-8.683,42.008,-8.777,41.941,-8.852,41.927,-8.878,41.947,-8.887,42.105,-8.772,42.211,-8.691,42.274,-8.729,42.287,-8.816,42.285,-8.81,42.334,-8.769,42.358,-8.73,42.412,-8.776,42.435,-8.812,42.47,-8.81,42.562,-8.8,42.6,-8.812,42.64,-8.988,42.586,-9.033,42.594,-9.035,42.662,-8.937,42.767,-8.927,42.799,-9.042,42.814,-9.127,42.865,-9.179,42.911,-9.235,42.977,-9.236,43.036,-9.178,43.174,-9.096,43.214,-9.025,43.239,-8.874,43.334,-8.666,43.317,-8.537,43.337,-8.422,43.386,-8.355,43.397,-8.249,43.439,-8.252,43.497,-8.289,43.54,-8.257,43.58,-8.137,43.629,-8.005,43.694,-7.853,43.707,-7.698,43.765,-7.595,43.727,-7.504,43.74,-7.399,43.696,-7.262,43.595,-7.061,43.554,-6.901,43.586,-6.617,43.592,-6.476,43.579,-6.224,43.604,-6.08,43.595,-5.847,43.645,-5.666,43.582,-5.316,43.553,-5.105,43.502,-4.523,43.416,-4.313,43.415,-4.015,43.463,-3.889,43.499,-3.774,43.478,-3.605,43.519,-3.524,43.511,-3.418,43.452,-3.046,43.372,-2.948,43.44,-2.875,43.454,-2.607,43.413,-2.337,43.328,-2.197,43.322,-1.991,43.345,-1.829,43.401,-1.794,43.407]],[[-16.334,28.38,-16.418,28.151,-16.496,28.062,-16.543,28.032,-16.658,28.007,-16.795,28.149,-16.866,28.293,-16.905,28.34,-16.843,28.376,-16.752,28.37,-16.557,28.4,-16.517,28.413,-16.319,28.558,-16.124,28.576,-16.119,28.528,-16.334,28.38]],[[-13.716,28.911,-13.784,28.845,-13.86,28.869,-13.824,29.013,-13.788,29.056,-13.65,29.119,-13.535,29.144,-13.501,29.211,-13.464,29.237,-13.423,29.198,-13.454,29.151,-13.478,29.007,-13.555,28.96,-13.716,28.911]],[[-14.197,28.169,-14.333,28.056,-14.469,28.082,-14.492,28.101,-14.356,28.13,-14.232,28.216,-14.153,28.407,-14.028,28.617,-14.003,28.707,-13.954,28.741,-13.886,28.745,-13.857,28.738,-13.827,28.691,-13.828,28.585,-13.863,28.409,-13.928,28.253,-14.197,28.169]],[[-15.401,28.147,-15.407,28.071,-15.383,27.993,-15.389,27.875,-15.437,27.811,-15.559,27.747,-15.656,27.758,-15.71,27.784,-15.807,27.888,-15.809,27.994,-15.721,28.064,-15.683,28.154,-15.453,28.137,-15.433,28.154,-15.415,28.159,-15.401,28.147]],[[-17.185,28.022,-17.225,28.014,-17.274,28.038,-17.325,28.118,-17.29,28.176,-17.259,28.203,-17.214,28.199,-17.13,28.156,-17.104,28.111,-17.101,28.083,-17.185,28.022]],[[-17.888,27.81,-17.985,27.646,-18.107,27.707,-18.136,27.728,-18.161,27.761,-18.043,27.768,-17.925,27.85,-17.888,27.81]],[[-17.834,28.493,-17.859,28.486,-17.882,28.565,-18.001,28.758,-17.929,28.845,-17.798,28.847,-17.745,28.787,-17.727,28.724,-17.752,28.689,-17.744,28.616,-17.758,28.569,-17.834,28.493]]]},{"n":"South Korea","c":"KR","r":2,"ml":2.5,"lx":128.13,"ly":36.38,"b":[126.008,33.202,130.934,38.623],"p":[[[126.634,37.782,126.665,37.801,126.667,37.828,126.667,37.917,126.754,37.979,126.879,38.106,126.94,38.176,127.01,38.241,127.09,38.284,127.17,38.305,127.294,38.313,127.533,38.305,127.579,38.313,127.746,38.319,127.785,38.308,127.905,38.3,128.039,38.309,128.106,38.327,128.169,38.359,128.223,38.417,128.279,38.524,128.339,38.608,128.375,38.623,128.619,38.176,128.852,37.887,129.052,37.678,129.335,37.275,129.418,37.059,129.426,36.926,129.473,36.742,129.433,36.637,129.445,36.471,129.427,36.385,129.393,36.323,129.391,36.202,129.402,36.138,129.404,36.052,129.426,36.019,129.458,36.006,129.51,36.038,129.573,36.051,129.562,35.948,129.485,35.687,129.419,35.498,129.329,35.333,129.214,35.182,129.077,35.123,128.98,35.102,128.796,35.094,128.643,35.12,128.511,35.101,128.458,35.069,128.419,35.016,128.448,34.932,128.444,34.87,128.388,34.875,128.276,34.911,128.152,34.916,128.095,34.934,128.036,35.022,127.977,35.019,127.873,34.966,127.715,34.955,127.659,34.926,127.639,34.89,127.663,34.843,127.742,34.783,127.715,34.721,127.632,34.69,127.565,34.766,127.524,34.84,127.477,34.844,127.404,34.823,127.39,34.743,127.423,34.688,127.479,34.625,127.401,34.553,127.381,34.501,127.325,34.463,127.173,34.546,127.195,34.605,127.261,34.662,127.269,34.72,127.247,34.755,127.031,34.607,126.897,34.439,126.826,34.451,126.796,34.494,126.755,34.512,126.611,34.404,126.584,34.318,126.531,34.314,126.508,34.351,126.506,34.428,126.482,34.494,126.333,34.59,126.264,34.673,126.301,34.72,126.426,34.695,126.525,34.698,126.505,34.738,126.473,34.756,126.539,34.779,126.593,34.824,126.548,34.837,126.479,34.81,126.421,34.823,126.398,34.933,126.327,35.045,126.291,35.154,126.361,35.217,126.396,35.314,126.46,35.456,126.493,35.501,126.582,35.534,126.614,35.571,126.565,35.59,126.487,35.606,126.488,35.647,126.542,35.669,126.602,35.714,126.717,35.769,126.753,35.872,126.72,35.898,126.647,35.922,126.664,35.975,126.693,36.014,126.682,36.038,126.597,36.105,126.54,36.166,126.557,36.236,126.544,36.341,126.552,36.43,126.548,36.478,126.507,36.586,126.488,36.694,126.433,36.678,126.389,36.651,126.231,36.689,126.181,36.692,126.161,36.772,126.217,36.871,126.352,36.958,126.429,36.969,126.487,37.007,126.578,37.02,126.687,36.96,126.784,36.948,126.839,36.846,126.872,36.824,126.879,36.862,126.958,36.906,126.977,36.939,126.96,36.958,126.869,36.976,126.787,37.103,126.776,37.158,126.746,37.194,126.791,37.295,126.696,37.411,126.65,37.447,126.657,37.551,126.608,37.617,126.58,37.654,126.563,37.717,126.578,37.745,126.621,37.755,126.634,37.782]],[[128.741,34.799,128.647,34.737,128.52,34.82,128.489,34.865,128.586,34.932,128.668,35.009,128.722,35.014,128.741,34.799]],[[128.066,34.806,128.055,34.708,127.984,34.703,127.942,34.766,127.897,34.735,127.873,34.735,127.838,34.813,127.832,34.875,127.915,34.921,127.966,34.893,128.038,34.879,128.066,34.806]],[[126.234,34.371,126.17,34.355,126.134,34.39,126.109,34.399,126.123,34.444,126.227,34.533,126.247,34.563,126.344,34.545,126.38,34.498,126.335,34.426,126.234,34.371]],[[126.521,37.737,126.516,37.605,126.461,37.61,126.423,37.624,126.407,37.649,126.369,37.772,126.412,37.823,126.494,37.783,126.521,37.737]],[[126.753,34.344,126.77,34.296,126.689,34.305,126.646,34.351,126.652,34.39,126.7,34.396,126.753,34.344]],[[127.799,34.615,127.788,34.584,127.737,34.631,127.787,34.682,127.799,34.615]],[[126.418,36.493,126.404,36.428,126.338,36.471,126.319,36.613,126.387,36.571,126.418,36.493]],[[126.172,34.731,126.159,34.707,126.115,34.714,126.071,34.783,126.052,34.838,126.008,34.867,126.078,34.915,126.169,34.83,126.172,34.731]],[[130.916,37.478,130.871,37.449,130.817,37.478,130.81,37.51,130.838,37.537,130.904,37.554,130.934,37.53,130.916,37.478]],[[126.327,33.224,126.282,33.202,126.24,33.215,126.229,33.225,126.179,33.283,126.166,33.312,126.199,33.368,126.338,33.46,126.696,33.549,126.76,33.553,126.901,33.515,126.931,33.444,126.905,33.382,126.873,33.341,126.709,33.272,126.582,33.238,126.327,33.224]]]},{"n":"South Africa","c":"ZA","r":2,"ml":1.7,"lx":23.67,"ly":-29.71,"b":[16.448,-46.963,37.888,-22.146],"p":[[[29.365,-22.194,29.377,-22.193,29.663,-22.146,29.902,-22.184,30.19,-22.291,30.46,-22.329,30.712,-22.298,30.916,-22.291,31.073,-22.308,31.197,-22.345,31.288,-22.402,31.293,-22.455,31.3,-22.479,31.348,-22.618,31.419,-22.825,31.467,-23.017,31.532,-23.279,31.53,-23.426,31.546,-23.482,31.604,-23.553,31.676,-23.674,31.7,-23.743,31.724,-23.795,31.8,-23.892,31.858,-24.04,31.908,-24.236,31.951,-24.33,31.967,-24.376,31.986,-24.461,31.983,-24.638,31.984,-24.844,31.986,-25.074,31.987,-25.263,31.979,-25.359,31.985,-25.632,31.92,-25.774,31.928,-25.885,31.948,-25.958,31.922,-25.969,31.871,-25.982,31.64,-25.867,31.415,-25.747,31.383,-25.743,31.335,-25.756,31.207,-25.843,31.088,-25.981,31.033,-26.098,30.945,-26.219,30.803,-26.413,30.789,-26.455,30.788,-26.614,30.794,-26.764,30.807,-26.785,30.883,-26.792,30.938,-26.916,31.063,-27.112,31.274,-27.238,31.47,-27.296,31.743,-27.31,31.958,-27.306,31.946,-27.174,31.967,-26.961,31.995,-26.817,32.025,-26.811,32.082,-26.825,32.113,-26.839,32.2,-26.833,32.354,-26.862,32.478,-26.858,32.589,-26.856,32.777,-26.851,32.886,-26.849,32.849,-27.08,32.706,-27.442,32.657,-27.607,32.535,-28.2,32.375,-28.498,32.286,-28.621,32.027,-28.84,31.955,-28.884,31.892,-28.912,31.778,-28.937,31.335,-29.378,31.17,-29.591,31.023,-29.901,30.878,-30.071,30.664,-30.434,30.472,-30.715,30.289,-30.97,29.971,-31.322,29.83,-31.424,29.735,-31.47,29.483,-31.675,29.128,-32.003,28.856,-32.294,28.449,-32.625,28.214,-32.769,27.861,-33.054,27.762,-33.096,27.364,-33.361,27.077,-33.521,26.614,-33.707,26.429,-33.76,25.99,-33.711,25.806,-33.737,25.652,-33.85,25.638,-34.011,25.574,-34.035,25.477,-34.028,25.17,-33.961,25.003,-33.974,24.906,-34.06,24.827,-34.169,24.596,-34.175,24.183,-34.062,23.698,-33.993,23.586,-33.985,23.35,-34.069,23.268,-34.081,22.926,-34.063,22.736,-34.01,22.554,-34.01,22.414,-34.054,22.246,-34.069,21.789,-34.373,21.553,-34.373,21.35,-34.408,21.249,-34.407,21.06,-34.365,20.99,-34.367,20.882,-34.387,20.775,-34.44,20.53,-34.463,20.435,-34.509,20.021,-34.786,19.926,-34.775,19.85,-34.757,19.635,-34.753,19.392,-34.606,19.298,-34.615,19.323,-34.571,19.331,-34.492,19.279,-34.437,19.245,-34.412,19.149,-34.417,19.098,-34.35,18.952,-34.344,18.902,-34.361,18.831,-34.364,18.825,-34.296,18.831,-34.254,18.826,-34.188,18.809,-34.108,18.752,-34.083,18.709,-34.072,18.605,-34.077,18.534,-34.086,18.5,-34.109,18.462,-34.168,18.462,-34.347,18.41,-34.296,18.352,-34.188,18.333,-34.074,18.354,-33.939,18.465,-33.888,18.456,-33.796,18.433,-33.717,18.309,-33.514,18.261,-33.422,18.156,-33.359,18.075,-33.207,17.993,-33.152,17.958,-33.046,17.878,-32.962,17.851,-32.827,17.895,-32.75,17.965,-32.709,18.037,-32.775,18.125,-32.749,18.251,-32.652,18.325,-32.505,18.33,-32.27,18.311,-32.122,18.211,-31.742,18.164,-31.655,17.939,-31.383,17.677,-31.019,17.347,-30.445,17.189,-30.1,16.95,-29.403,16.739,-29.009,16.481,-28.642,16.448,-28.618,16.487,-28.573,16.626,-28.488,16.689,-28.465,16.723,-28.475,16.756,-28.452,16.788,-28.395,16.795,-28.341,16.81,-28.265,16.841,-28.219,16.875,-28.128,16.933,-28.07,17.056,-28.031,17.149,-28.082,17.188,-28.133,17.205,-28.199,17.246,-28.231,17.312,-28.229,17.359,-28.269,17.386,-28.353,17.38,-28.414,17.343,-28.452,17.348,-28.501,17.396,-28.563,17.416,-28.621,17.448,-28.698,17.617,-28.743,17.699,-28.768,17.842,-28.777,17.976,-28.811,18.103,-28.872,18.311,-28.886,18.6,-28.855,18.839,-28.869,19.026,-28.928,19.162,-28.939,19.246,-28.902,19.282,-28.848,19.271,-28.778,19.313,-28.733,19.407,-28.714,19.483,-28.662,19.54,-28.575,19.671,-28.504,19.878,-28.449,19.98,-28.451,19.98,-28.31,19.98,-27.866,19.98,-27.421,19.98,-26.976,19.98,-26.531,19.98,-26.086,19.98,-25.642,19.98,-25.197,19.98,-24.777,20.029,-24.807,20.345,-25.03,20.431,-25.147,20.473,-25.221,20.609,-25.491,20.711,-25.733,20.793,-25.916,20.799,-25.999,20.811,-26.081,20.823,-26.121,20.815,-26.165,20.757,-26.264,20.698,-26.34,20.627,-26.444,20.62,-26.581,20.641,-26.742,20.685,-26.822,20.74,-26.849,20.871,-26.809,20.954,-26.821,21.071,-26.852,21.455,-26.833,21.501,-26.843,21.646,-26.854,21.695,-26.841,21.738,-26.807,21.788,-26.71,21.833,-26.678,21.915,-26.662,22.011,-26.636,22.091,-26.58,22.218,-26.389,22.471,-26.219,22.549,-26.178,22.598,-26.133,22.64,-26.071,22.729,-25.857,22.796,-25.679,22.819,-25.595,22.879,-25.458,22.951,-25.37,23.022,-25.324,23.058,-25.312,23.149,-25.289,23.266,-25.267,23.389,-25.291,23.521,-25.344,23.671,-25.434,23.823,-25.545,23.894,-25.601,23.97,-25.626,24.104,-25.635,24.193,-25.633,24.331,-25.743,24.4,-25.75,24.556,-25.783,24.748,-25.817,24.869,-25.813,24.999,-25.754,25.092,-25.751,25.213,-25.756,25.346,-25.74,25.444,-25.714,25.518,-25.663,25.584,-25.606,25.659,-25.438,25.703,-25.302,25.77,-25.146,25.852,-24.935,25.882,-24.788,25.912,-24.747,26.032,-24.702,26.131,-24.671,26.397,-24.614,26.452,-24.583,26.502,-24.513,26.618,-24.396,26.761,-24.297,26.835,-24.241,26.971,-23.763,26.987,-23.705,27.086,-23.578,27.146,-23.524,27.186,-23.523,27.241,-23.49,27.313,-23.424,27.399,-23.384,27.499,-23.368,27.563,-23.325,27.593,-23.253,27.644,-23.218,27.717,-23.22,27.758,-23.197,27.769,-23.149,27.813,-23.108,27.891,-23.074,27.931,-23.034,27.935,-22.987,28.028,-22.874,28.21,-22.694,28.382,-22.593,28.543,-22.573,28.696,-22.535,28.84,-22.481,28.946,-22.395,29.013,-22.278,29.13,-22.213,29.365,-22.194],[28.737,-30.102,28.901,-30.038,28.975,-29.999,29.029,-29.968,29.098,-29.919,29.122,-29.801,29.142,-29.701,29.195,-29.652,29.249,-29.619,29.294,-29.567,29.349,-29.442,29.387,-29.32,29.391,-29.27,29.371,-29.218,29.336,-29.164,29.301,-29.09,29.26,-29.078,29.178,-29.037,29.058,-28.954,28.954,-28.881,28.856,-28.776,28.816,-28.759,28.722,-28.688,28.681,-28.647,28.653,-28.598,28.626,-28.582,28.583,-28.594,28.472,-28.616,28.233,-28.701,28.084,-28.78,27.96,-28.873,27.83,-28.909,27.736,-28.94,27.66,-29.047,27.59,-29.146,27.527,-29.236,27.491,-29.277,27.458,-29.303,27.425,-29.36,27.357,-29.455,27.295,-29.519,27.207,-29.554,27.095,-29.599,27.057,-29.626,27.052,-29.664,27.092,-29.754,27.13,-29.84,27.194,-29.941,27.24,-30.015,27.313,-30.106,27.355,-30.159,27.35,-30.247,27.364,-30.279,27.388,-30.316,27.409,-30.325,27.431,-30.338,27.492,-30.364,27.507,-30.381,27.549,-30.411,27.59,-30.466,27.667,-30.542,27.753,-30.6,27.902,-30.624,28.018,-30.642,28.057,-30.631,28.096,-30.585,28.129,-30.525,28.139,-30.45,28.176,-30.41,28.315,-30.218,28.392,-30.148,28.439,-30.142,28.5,-30.129,28.577,-30.123,28.634,-30.129,28.647,-30.127,28.737,-30.102]],[[37.857,-46.944,37.814,-46.963,37.612,-46.946,37.59,-46.908,37.65,-46.849,37.685,-46.824,37.79,-46.837,37.873,-46.885,37.888,-46.902,37.857,-46.944]]]},{"n":"Somalia","c":"SO","r":6,"ml":4,"lx":45.19,"ly":3.57,"b":[40.964,-1.695,51.39,11.984],"p":[[[41.533,-1.695,41.538,-1.613,41.522,-1.572,41.427,-1.45,41.25,-1.221,41.116,-1.047,40.979,-0.87,40.978,-0.729,40.977,-0.307,40.973,0.535,40.97,1.378,40.967,2.221,40.965,2.642,40.964,2.815,40.979,2.842,41.135,2.997,41.342,3.202,41.613,3.59,41.761,3.802,41.884,3.978,41.915,4.031,42.024,4.138,42.228,4.202,42.355,4.212,42.792,4.292,42.857,4.324,42.895,4.361,42.931,4.445,43.016,4.563,43.126,4.644,43.334,4.75,43.538,4.84,43.583,4.855,43.829,4.911,43.889,4.931,43.989,4.951,44.028,4.951,44.37,4.931,44.637,4.916,44.912,4.9,44.941,4.912,45.133,5.122,45.438,5.455,45.634,5.668,45.935,5.997,46.167,6.235,46.423,6.497,46.672,6.737,46.971,7.026,47.16,7.208,47.453,7.49,47.732,7.759,47.978,7.997,48.127,8.222,48.273,8.443,48.429,8.68,48.617,8.965,48.794,9.233,48.938,9.452,48.938,9.564,48.938,9.808,48.938,9.973,48.938,10.433,48.938,10.714,48.938,10.982,48.939,11.258,49.062,11.271,49.388,11.343,49.642,11.451,50.11,11.529,50.466,11.728,50.528,11.823,50.636,11.944,50.792,11.984,51.191,11.842,51.255,11.831,51.232,11.745,51.218,11.658,51.136,11.505,51.084,11.336,51.122,11.077,51.141,10.657,51.131,10.596,51.105,10.536,51.094,10.489,51.051,10.472,51.032,10.445,51.063,10.434,51.188,10.48,51.186,10.53,51.193,10.555,51.296,10.499,51.369,10.475,51.39,10.423,51.385,10.387,51.268,10.403,51.209,10.431,51.036,10.385,50.93,10.336,50.898,10.253,50.874,9.924,50.833,9.71,50.825,9.428,50.685,9.241,50.638,9.109,50.43,8.845,50.321,8.62,50.286,8.509,50.103,8.2,49.852,7.963,49.761,7.66,49.671,7.47,49.57,7.297,49.349,6.991,49.235,6.777,49.093,6.408,49.049,6.174,48.649,5.494,48.234,4.953,47.975,4.497,47.511,3.968,46.879,3.286,46.051,2.475,45.826,2.31,44.92,1.81,44.333,1.391,44.033,1.106,43.718,0.858,43.468,0.622,42.712,-0.176,42.634,-0.251,42.561,-0.321,42.466,-0.457,42.399,-0.51,42.219,-0.738,42.106,-0.856,41.98,-0.973,41.926,-1.056,41.888,-1.151,41.846,-1.203,41.732,-1.43,41.632,-1.579,41.533,-1.695]]]},{"n":"Somaliland","c":"","r":5,"ml":4.5,"lx":46.73,"ly":9.44,"b":[42.656,7.997,48.939,11.5],"p":[[[48.939,11.258,48.938,10.982,48.938,10.714,48.938,10.433,48.938,9.973,48.938,9.808,48.938,9.564,48.938,9.452,48.794,9.233,48.617,8.965,48.429,8.68,48.273,8.443,48.127,8.222,47.978,7.997,47.638,7.997,47.306,7.997,46.978,7.997,46.92,8.026,46.645,8.118,46.296,8.235,45.863,8.38,45.555,8.483,45.227,8.591,44.894,8.7,44.632,8.786,44.306,8.893,44.023,8.986,43.984,9.009,43.827,9.151,43.621,9.337,43.581,9.341,43.483,9.379,43.394,9.48,43.303,9.609,43.218,9.77,43.182,9.88,43.069,9.926,43.015,10.013,42.913,10.141,42.842,10.203,42.816,10.257,42.784,10.37,42.725,10.492,42.669,10.568,42.656,10.6,42.66,10.621,42.763,10.787,42.81,10.846,42.863,10.903,42.906,10.96,42.923,10.999,43.049,11.194,43.159,11.366,43.246,11.5,43.441,11.346,43.631,11.035,43.853,10.784,44.158,10.551,44.279,10.472,44.387,10.43,44.943,10.437,45.338,10.65,45.696,10.804,45.817,10.836,46.025,10.794,46.254,10.781,46.46,10.734,46.565,10.746,46.973,10.925,47.23,11.1,47.405,11.174,47.474,11.175,47.713,11.112,48.019,11.139,48.439,11.29,48.573,11.321,48.674,11.323,48.903,11.255,48.939,11.258]]]},{"n":"Solomon Islands","c":"SB","r":3,"ml":3,"lx":159.17,"ly":-8.03,"b":[155.678,-11.832,166.929,-6.609],"p":[[[159.688,-8.508,159.64,-8.521,159.569,-8.485,159.538,-8.451,159.553,-8.399,159.595,-8.379,159.642,-8.414,159.646,-8.45,159.688,-8.508]],[[166.929,-11.665,166.841,-11.681,166.806,-11.677,166.747,-11.591,166.791,-11.571,166.855,-11.579,166.875,-11.63,166.929,-11.665]],[[166.133,-10.758,166.053,-10.775,166.028,-10.77,165.968,-10.779,165.904,-10.851,165.857,-10.841,165.819,-10.844,165.791,-10.785,165.79,-10.756,165.836,-10.761,165.86,-10.703,165.909,-10.674,166.024,-10.661,166.126,-10.68,166.162,-10.693,166.13,-10.745,166.133,-10.758]],[[160.576,-11.798,160.507,-11.832,160.443,-11.815,160.395,-11.789,160.355,-11.712,160.27,-11.664,160.15,-11.644,160.1,-11.611,160.087,-11.594,160.004,-11.58,159.979,-11.538,159.986,-11.495,160,-11.472,160.077,-11.493,160.449,-11.696,160.537,-11.759,160.576,-11.798]],[[161.548,-9.626,161.559,-9.733,161.554,-9.77,161.478,-9.691,161.442,-9.719,161.41,-9.682,161.412,-9.6,161.417,-9.514,161.402,-9.448,161.364,-9.353,161.407,-9.368,161.548,-9.626]],[[159.189,-9.124,159.175,-9.126,159.128,-9.114,159.071,-9.11,159.036,-9.075,159.078,-9.025,159.13,-8.993,159.154,-9.001,159.176,-9.022,159.228,-9.03,159.234,-9.094,159.189,-9.124]],[[160.168,-8.996,160.226,-9.01,160.254,-9.007,160.319,-9.061,160.408,-9.14,160.371,-9.181,160.3,-9.16,160.276,-9.169,160.268,-9.163,160.253,-9.123,160.175,-9.084,160.105,-9.081,160.096,-9.034,160.168,-8.996]],[[157.763,-8.242,157.826,-8.324,157.898,-8.506,157.885,-8.569,157.834,-8.573,157.819,-8.612,157.749,-8.524,157.656,-8.5,157.588,-8.445,157.565,-8.338,157.558,-8.27,157.504,-8.258,157.351,-8.275,157.302,-8.333,157.232,-8.315,157.218,-8.263,157.229,-8.212,157.322,-8.161,157.341,-8.096,157.433,-7.985,157.491,-7.966,157.599,-8.006,157.612,-8.165,157.651,-8.217,157.763,-8.242]],[[158.201,-8.822,158.179,-8.826,158.155,-8.786,158.21,-8.678,158.236,-8.765,158.253,-8.797,158.201,-8.822]],[[158.108,-8.684,158.009,-8.763,157.938,-8.736,157.879,-8.669,157.898,-8.587,157.909,-8.566,157.938,-8.561,157.967,-8.544,157.998,-8.508,158.105,-8.537,158.132,-8.557,158.068,-8.607,158.09,-8.623,158.104,-8.646,158.108,-8.684]],[[157.389,-8.713,157.389,-8.728,157.334,-8.7,157.212,-8.565,157.234,-8.52,157.345,-8.432,157.379,-8.421,157.411,-8.475,157.383,-8.555,157.347,-8.575,157.332,-8.651,157.389,-8.713]],[[156.688,-7.923,156.669,-7.937,156.635,-7.883,156.611,-7.866,156.612,-7.806,156.511,-7.708,156.502,-7.64,156.561,-7.574,156.64,-7.613,156.718,-7.696,156.809,-7.723,156.79,-7.778,156.708,-7.877,156.688,-7.923]],[[156.604,-8.172,156.592,-8.196,156.54,-8.073,156.542,-8.011,156.551,-7.971,156.57,-7.959,156.612,-8.096,156.604,-8.172]],[[157.172,-8.108,157.15,-8.123,157.041,-8.117,156.958,-8.014,156.959,-7.938,157.024,-7.868,157.103,-7.855,157.146,-7.883,157.186,-7.941,157.201,-8.016,157.192,-8.082,157.172,-8.108]],[[155.84,-7.097,155.739,-7.121,155.678,-7.089,155.705,-7.013,155.739,-6.973,155.865,-7.043,155.84,-7.097]],[[157.645,-8.759,157.643,-8.794,157.586,-8.783,157.458,-8.73,157.454,-8.706,157.526,-8.697,157.579,-8.704,157.623,-8.735,157.645,-8.759]],[[159.75,-9.273,159.971,-9.433,160.065,-9.419,160.355,-9.422,160.525,-9.536,160.625,-9.589,160.682,-9.692,160.751,-9.715,160.794,-9.767,160.819,-9.863,160.802,-9.878,160.713,-9.914,160.649,-9.929,160.482,-9.895,160.321,-9.821,160.002,-9.812,159.854,-9.792,159.803,-9.763,159.755,-9.726,159.68,-9.637,159.622,-9.532,159.612,-9.471,159.607,-9.354,159.626,-9.311,159.686,-9.269,159.75,-9.273]],[[159.879,-8.534,159.881,-8.557,159.746,-8.474,159.645,-8.372,159.354,-8.26,159.292,-8.203,159.239,-8.196,159.09,-8.103,158.944,-8.041,158.855,-7.96,158.832,-7.927,158.778,-7.907,158.686,-7.818,158.597,-7.759,158.565,-7.651,158.479,-7.577,158.457,-7.545,158.734,-7.604,158.863,-7.722,158.972,-7.789,159.011,-7.837,159.109,-7.904,159.198,-7.91,159.287,-7.976,159.368,-7.994,159.431,-8.029,159.843,-8.327,159.794,-8.406,159.849,-8.463,159.879,-8.534]],[[157.487,-7.33,157.519,-7.366,157.441,-7.426,157.339,-7.393,157.317,-7.359,157.315,-7.342,157.243,-7.353,157.102,-7.324,156.904,-7.18,156.696,-6.911,156.495,-6.762,156.457,-6.715,156.453,-6.638,156.479,-6.609,156.604,-6.641,156.765,-6.764,157.03,-6.892,157.103,-6.957,157.148,-7.114,157.193,-7.16,157.336,-7.28,157.412,-7.309,157.452,-7.314,157.487,-7.33]],[[160.749,-8.314,160.998,-8.612,160.988,-8.665,160.954,-8.699,160.944,-8.799,160.976,-8.837,161.043,-8.855,161.159,-8.962,161.205,-9.092,161.209,-9.133,161.257,-9.192,161.258,-9.317,161.368,-9.49,161.378,-9.574,161.367,-9.611,161.322,-9.59,161.191,-9.393,161.042,-9.308,161.024,-9.271,160.873,-9.157,160.772,-8.964,160.663,-8.621,160.714,-8.539,160.59,-8.373,160.596,-8.328,160.649,-8.338,160.685,-8.336,160.702,-8.317,160.749,-8.314]],[[161.715,-10.387,161.841,-10.446,161.914,-10.436,162.023,-10.477,162.105,-10.454,162.157,-10.506,162.287,-10.71,162.288,-10.776,162.373,-10.823,162.301,-10.832,162.201,-10.808,162.124,-10.824,162.043,-10.785,161.906,-10.764,161.787,-10.717,161.538,-10.566,161.539,-10.491,161.499,-10.455,161.487,-10.361,161.398,-10.332,161.294,-10.326,161.286,-10.282,161.305,-10.204,161.382,-10.206,161.476,-10.238,161.654,-10.352,161.698,-10.371,161.715,-10.387]]]},{"n":"Slovakia","c":"SK","r":6,"ml":4,"lx":19.05,"ly":48.73,"b":[16.863,47.763,22.539,49.598],"p":[[[22.539,49.073,22.524,49.031,22.483,48.983,22.432,48.934,22.389,48.873,22.333,48.745,22.295,48.686,22.143,48.569,22.132,48.405,22.111,48.393,21.767,48.338,21.721,48.347,21.675,48.378,21.649,48.401,21.633,48.419,21.603,48.464,21.563,48.496,21.505,48.522,21.451,48.552,21.382,48.553,21.196,48.511,21.067,48.506,20.981,48.52,20.867,48.546,20.643,48.55,20.49,48.527,20.475,48.495,20.334,48.296,20.129,48.222,19.95,48.147,19.899,48.131,19.81,48.155,19.709,48.2,19.625,48.223,19.564,48.213,19.497,48.162,19.467,48.111,19.265,48.073,18.914,48.051,18.792,48,18.75,47.939,18.748,47.893,18.778,47.853,18.741,47.806,18.724,47.787,18.476,47.777,18.146,47.763,17.948,47.767,17.762,47.77,17.635,47.81,17.481,47.888,17.317,47.991,17.302,47.993,17.277,48.004,17.175,48.012,17.147,48.006,17.086,48.04,17.068,48.083,16.973,48.198,16.865,48.387,16.863,48.441,16.904,48.504,16.943,48.551,16.949,48.589,16.953,48.599,16.985,48.677,17.063,48.781,17.136,48.841,17.188,48.861,17.297,48.843,17.483,48.828,17.625,48.842,17.758,48.888,17.831,48.929,17.893,48.971,17.913,48.999,17.941,49.012,18.051,49.037,18.086,49.065,18.1,49.119,18.11,49.18,18.133,49.225,18.161,49.257,18.365,49.336,18.383,49.364,18.416,49.391,18.476,49.421,18.535,49.465,18.596,49.491,18.676,49.488,18.75,49.494,18.807,49.509,18.832,49.511,18.938,49.498,18.957,49.448,18.968,49.396,19.149,49.4,19.25,49.511,19.302,49.525,19.386,49.564,19.442,49.598,19.48,49.576,19.535,49.505,19.593,49.447,19.627,49.424,19.63,49.407,19.664,49.396,19.73,49.39,19.774,49.372,19.787,49.319,19.788,49.27,19.767,49.235,19.757,49.204,19.802,49.192,19.869,49.204,19.916,49.221,20.058,49.181,20.108,49.271,20.164,49.316,20.237,49.338,20.303,49.366,20.363,49.385,20.405,49.384,20.423,49.392,20.475,49.39,20.535,49.381,20.616,49.392,20.729,49.37,20.8,49.329,20.868,49.315,20.947,49.317,21.001,49.34,21.079,49.418,21.136,49.417,21.225,49.429,21.35,49.429,21.64,49.412,21.712,49.382,21.89,49.343,21.968,49.299,22.002,49.246,22.02,49.21,22.203,49.153,22.473,49.081,22.539,49.073]]]},{"n":"Slovenia","c":"SI","r":6,"ml":5,"lx":14.92,"ly":46.06,"b":[13.378,45.428,16.516,46.863],"p":[[[16.516,46.5,16.428,46.524,16.321,46.535,16.301,46.521,16.258,46.508,16.237,46.484,16.253,46.389,16.227,46.373,16.106,46.382,16.067,46.371,16.001,46.305,15.933,46.278,15.848,46.258,15.784,46.234,15.704,46.213,15.636,46.201,15.609,46.172,15.593,46.14,15.597,46.109,15.666,46.048,15.676,45.984,15.668,45.904,15.652,45.862,15.625,45.834,15.454,45.798,15.277,45.733,15.273,45.718,15.354,45.66,15.357,45.646,15.29,45.613,15.284,45.58,15.291,45.542,15.327,45.502,15.339,45.467,15.242,45.441,15.11,45.451,14.955,45.5,14.9,45.493,14.847,45.467,14.793,45.478,14.734,45.508,14.65,45.571,14.609,45.61,14.592,45.651,14.569,45.657,14.534,45.645,14.505,45.595,14.427,45.506,14.37,45.481,14.283,45.487,14.161,45.485,14.086,45.478,13.993,45.509,13.97,45.503,13.97,45.483,13.936,45.45,13.879,45.428,13.615,45.477,13.578,45.517,13.637,45.536,13.72,45.588,13.776,45.582,13.845,45.593,13.875,45.615,13.831,45.68,13.722,45.761,13.663,45.792,13.583,45.812,13.57,45.834,13.614,45.962,13.601,45.98,13.509,45.974,13.488,45.987,13.48,46.009,13.486,46.04,13.548,46.089,13.617,46.133,13.635,46.158,13.633,46.177,13.545,46.197,13.492,46.217,13.45,46.224,13.421,46.212,13.4,46.225,13.378,46.262,13.4,46.318,13.479,46.369,13.563,46.415,13.637,46.449,13.68,46.463,13.7,46.52,13.744,46.514,13.831,46.511,13.929,46.498,14.02,46.482,14.1,46.462,14.267,46.441,14.42,46.428,14.466,46.416,14.504,46.417,14.55,46.4,14.577,46.413,14.597,46.436,14.68,46.463,14.757,46.499,14.811,46.545,14.841,46.58,14.893,46.606,14.949,46.613,15.001,46.626,15.217,46.643,15.439,46.63,15.545,46.655,15.633,46.698,15.76,46.711,15.767,46.711,15.958,46.678,15.972,46.697,15.98,46.706,15.977,46.801,16.037,46.845,16.093,46.863,16.284,46.857,16.308,46.828,16.318,46.783,16.335,46.722,16.367,46.705,16.385,46.681,16.381,46.639,16.418,46.607,16.506,46.522,16.516,46.5]]]},{"n":"Singapore","c":"SG","r":6,"ml":4,"lx":103.82,"ly":1.37,"b":[103.65,1.265,103.996,1.447],"p":[[[103.97,1.331,103.82,1.265,103.65,1.326,103.705,1.423,103.818,1.447,103.909,1.416,103.961,1.392,103.996,1.365,103.97,1.331]]]},{"n":"Sierra Leone","c":"SL","r":4,"ml":4,"lx":-11.76,"ly":8.62,"b":[-13.293,6.907,-10.283,9.997],"p":[[[-10.283,8.485,-10.286,8.454,-10.315,8.311,-10.36,8.188,-10.39,8.158,-10.517,8.125,-10.571,8.071,-10.618,7.896,-10.647,7.759,-10.691,7.736,-10.878,7.538,-11,7.463,-11.085,7.399,-11.166,7.314,-11.268,7.233,-11.377,7.095,-11.455,6.951,-11.508,6.907,-11.548,6.947,-11.733,7.089,-11.929,7.184,-12.347,7.342,-12.486,7.386,-12.481,7.442,-12.433,7.545,-12.51,7.666,-12.48,7.753,-12.51,7.753,-12.57,7.701,-12.698,7.716,-12.782,7.791,-12.851,7.819,-12.881,7.857,-12.925,8.055,-12.957,8.145,-13.021,8.201,-13.149,8.215,-13.202,8.336,-13.273,8.43,-13.261,8.488,-13.203,8.484,-13.158,8.442,-13.085,8.425,-12.994,8.526,-12.913,8.582,-12.894,8.63,-12.904,8.656,-12.953,8.615,-13.088,8.626,-13.122,8.589,-13.182,8.577,-13.228,8.696,-13.226,8.766,-13.207,8.843,-13.071,8.856,-13.059,8.881,-13.154,8.898,-13.272,8.987,-13.293,9.049,-13.234,9.07,-13.178,9.061,-13.13,9.048,-13.077,9.07,-13.028,9.104,-12.999,9.147,-12.959,9.263,-12.831,9.302,-12.756,9.374,-12.684,9.484,-12.652,9.562,-12.622,9.601,-12.604,9.634,-12.59,9.671,-12.558,9.705,-12.524,9.787,-12.501,9.862,-12.428,9.898,-12.278,9.93,-12.142,9.875,-11.923,9.923,-11.911,9.993,-11.71,9.994,-11.472,9.995,-11.274,9.997,-11.206,9.978,-11.181,9.925,-11.116,9.843,-11.047,9.786,-10.963,9.662,-10.865,9.516,-10.759,9.385,-10.691,9.314,-10.683,9.289,-10.688,9.261,-10.721,9.194,-10.75,9.122,-10.747,9.095,-10.727,9.082,-10.616,9.059,-10.606,8.979,-10.606,8.868,-10.552,8.764,-10.501,8.688,-10.503,8.66,-10.628,8.53,-10.677,8.401,-10.702,8.364,-10.712,8.335,-10.687,8.322,-10.653,8.33,-10.604,8.319,-10.558,8.316,-10.496,8.362,-10.394,8.481,-10.36,8.496,-10.283,8.485]],[[-12.526,7.436,-12.541,7.41,-12.607,7.475,-12.952,7.571,-12.854,7.622,-12.615,7.637,-12.544,7.607,-12.512,7.582,-12.501,7.535,-12.526,7.436]]]},{"n":"Seychelles","c":"SC","r":6,"ml":5,"lx":55.48,"ly":-4.68,"b":[55.383,-4.786,55.543,-4.559],"p":[[[55.54,-4.693,55.543,-4.786,55.495,-4.755,55.481,-4.695,55.417,-4.65,55.383,-4.609,55.456,-4.559,55.54,-4.693]]]},{"n":"Serbia","c":"RS","r":5,"ml":4,"lx":20.79,"ly":44.19,"b":[18.839,42.242,22.977,46.169],"p":[[[21.36,44.827,21.523,44.79,21.597,44.755,21.636,44.71,21.74,44.681,21.909,44.666,22.027,44.62,22.093,44.542,22.201,44.561,22.351,44.676,22.498,44.706,22.642,44.651,22.721,44.606,22.734,44.57,22.701,44.556,22.62,44.562,22.554,44.54,22.502,44.49,22.495,44.435,22.531,44.378,22.582,44.338,22.648,44.316,22.683,44.286,22.688,44.248,22.705,44.238,22.667,44.22,22.627,44.194,22.603,44.149,22.597,44.075,22.469,44.018,22.421,44.007,22.399,43.97,22.365,43.862,22.37,43.781,22.387,43.74,22.395,43.707,22.436,43.665,22.474,43.602,22.499,43.519,22.555,43.454,22.697,43.391,22.768,43.354,22.82,43.301,22.86,43.252,22.977,43.188,22.968,43.142,22.942,43.097,22.915,43.076,22.857,43.018,22.8,42.986,22.706,42.884,22.558,42.878,22.523,42.87,22.467,42.842,22.439,42.792,22.466,42.751,22.463,42.709,22.436,42.629,22.472,42.543,22.524,42.504,22.532,42.481,22.524,42.441,22.446,42.359,22.422,42.329,22.344,42.314,22.317,42.322,22.277,42.35,22.24,42.358,22.147,42.325,22.052,42.305,21.978,42.32,21.904,42.322,21.853,42.308,21.815,42.303,21.739,42.268,21.618,42.242,21.563,42.248,21.542,42.281,21.519,42.328,21.53,42.35,21.61,42.387,21.619,42.423,21.731,42.595,21.752,42.652,21.753,42.67,21.724,42.682,21.663,42.681,21.391,42.751,21.403,42.832,21.323,42.875,21.237,42.913,21.223,42.956,21.127,43.043,21.057,43.092,20.968,43.116,20.891,43.152,20.844,43.173,20.824,43.214,20.824,43.238,20.801,43.261,20.763,43.259,20.701,43.226,20.623,43.199,20.61,43.178,20.638,43.13,20.658,43.1,20.649,43.071,20.624,43.034,20.475,42.953,20.458,42.925,20.487,42.879,20.469,42.858,20.344,42.828,20.348,42.853,20.34,42.893,20.268,42.935,20.168,42.969,19.944,43.082,19.858,43.097,19.781,43.11,19.671,43.164,19.614,43.173,19.552,43.212,19.415,43.343,19.298,43.414,19.219,43.45,19.196,43.485,19.192,43.521,19.194,43.533,19.254,43.584,19.301,43.592,19.36,43.593,19.4,43.568,19.451,43.562,19.48,43.595,19.495,43.643,19.488,43.704,19.364,43.845,19.257,43.943,19.245,43.965,19.268,43.983,19.305,43.993,19.345,43.985,19.449,43.978,19.55,43.987,19.584,44.011,19.584,44.043,19.547,44.073,19.43,44.154,19.339,44.226,19.232,44.281,19.152,44.303,19.128,44.33,19.118,44.36,19.127,44.415,19.132,44.484,19.151,44.527,19.223,44.61,19.292,44.697,19.334,44.781,19.357,44.859,19.349,44.881,19.313,44.897,19.237,44.914,19.132,44.9,19.042,44.871,19.007,44.869,18.996,44.904,19.01,44.919,19.038,44.918,19.061,44.911,19.085,44.927,19.1,44.974,19.063,45.137,19.13,45.152,19.131,45.175,19.137,45.196,19.206,45.168,19.303,45.167,19.388,45.173,19.401,45.189,19.4,45.213,19.382,45.231,19.352,45.245,19.33,45.268,19.273,45.278,19.093,45.337,19.005,45.4,19.008,45.466,19.033,45.502,19.064,45.515,19.055,45.527,18.954,45.558,18.918,45.601,18.947,45.656,18.895,45.767,18.839,45.836,18.894,45.866,18.901,45.908,18.905,45.932,18.928,45.931,19.016,45.96,19.048,45.983,19.066,46.01,19.087,46.016,19.146,45.987,19.208,45.984,19.278,46.003,19.33,46.029,19.393,46.05,19.421,46.064,19.458,46.087,19.531,46.155,19.613,46.169,19.725,46.152,19.844,46.146,19.934,46.161,20.161,46.142,20.21,46.126,20.242,46.109,20.301,46.051,20.359,45.975,20.438,45.941,20.533,45.9,20.581,45.869,20.653,45.779,20.709,45.735,20.728,45.737,20.747,45.749,20.76,45.758,20.775,45.75,20.776,45.723,20.779,45.662,20.766,45.597,20.786,45.536,20.787,45.517,20.772,45.5,20.774,45.484,20.794,45.468,20.871,45.428,20.942,45.365,21.024,45.322,21.1,45.294,21.148,45.292,21.226,45.241,21.382,45.205,21.431,45.193,21.465,45.172,21.49,45.148,21.492,45.122,21.468,45.11,21.434,45.075,21.421,45.033,21.396,45.022,21.371,45.021,21.353,45.009,21.357,44.991,21.378,44.973,21.41,44.958,21.472,44.942,21.533,44.919,21.532,44.901,21.52,44.881,21.442,44.873,21.384,44.87,21.358,44.862,21.36,44.827]]]},{"n":"Senegal","c":"SN","r":3,"ml":2.7,"lx":-14.78,"ly":15.14,"b":[-17.536,12.328,-11.382,16.679],"p":[[[-12.281,14.809,-12.187,14.648,-12.207,14.571,-12.228,14.459,-12.175,14.377,-12.113,14.323,-12.068,14.274,-12.019,14.206,-12.011,14.072,-12.02,13.975,-11.988,13.931,-11.961,13.875,-11.966,13.829,-11.984,13.788,-12.044,13.734,-12.054,13.633,-11.957,13.511,-11.895,13.444,-11.895,13.406,-11.878,13.365,-11.832,13.316,-11.803,13.327,-11.772,13.367,-11.758,13.395,-11.674,13.382,-11.635,13.37,-11.581,13.29,-11.562,13.237,-11.549,13.17,-11.493,13.087,-11.444,13.028,-11.434,12.992,-11.39,12.942,-11.417,12.832,-11.414,12.775,-11.444,12.628,-11.451,12.558,-11.449,12.532,-11.382,12.479,-11.389,12.404,-11.457,12.418,-11.574,12.426,-11.808,12.387,-11.889,12.403,-12.042,12.398,-12.152,12.377,-12.291,12.328,-12.399,12.34,-12.457,12.378,-12.534,12.376,-12.621,12.396,-12.713,12.433,-12.797,12.452,-12.888,12.52,-12.931,12.532,-12.961,12.514,-12.986,12.492,-13.012,12.478,-13.061,12.49,-13.08,12.536,-13.064,12.581,-13.06,12.615,-13.083,12.634,-13.138,12.64,-13.228,12.64,-13.373,12.654,-13.406,12.662,-13.729,12.674,-14.065,12.675,-14.349,12.676,-14.708,12.678,-14.961,12.679,-15.196,12.68,-15.378,12.589,-15.575,12.49,-15.84,12.438,-16.144,12.457,-16.242,12.443,-16.342,12.4,-16.416,12.368,-16.521,12.349,-16.657,12.364,-16.712,12.355,-16.746,12.4,-16.785,12.473,-16.76,12.526,-16.678,12.56,-16.553,12.605,-16.488,12.582,-16.45,12.581,-16.443,12.609,-16.455,12.625,-16.549,12.664,-16.598,12.715,-16.638,12.685,-16.673,12.622,-16.701,12.603,-16.744,12.585,-16.768,12.628,-16.778,12.67,-16.759,12.702,-16.769,12.883,-16.757,12.98,-16.763,13.064,-16.705,13.12,-16.649,13.154,-16.431,13.157,-16.228,13.16,-16.033,13.158,-15.834,13.156,-15.814,13.325,-15.752,13.338,-15.657,13.356,-15.482,13.376,-15.286,13.396,-15.245,13.429,-15.212,13.485,-15.192,13.535,-15.151,13.556,-15.096,13.54,-15.025,13.513,-14.95,13.473,-14.865,13.435,-14.808,13.411,-14.672,13.352,-14.439,13.269,-14.247,13.236,-14.015,13.296,-13.848,13.335,-13.827,13.408,-13.853,13.479,-13.977,13.543,-14.147,13.536,-14.199,13.519,-14.278,13.497,-14.326,13.489,-14.405,13.504,-14.507,13.56,-14.571,13.616,-14.66,13.643,-14.766,13.669,-14.936,13.785,-15.024,13.806,-15.108,13.812,-15.27,13.789,-15.427,13.727,-15.51,13.586,-15.667,13.588,-16.002,13.593,-16.309,13.597,-16.562,13.587,-16.588,13.69,-16.648,13.771,-16.745,13.84,-16.767,13.905,-16.734,13.961,-16.64,14.007,-16.618,14.041,-16.667,14.036,-16.742,14.006,-16.792,14.004,-16.798,14.093,-16.881,14.208,-16.974,14.403,-17.079,14.483,-17.168,14.641,-17.261,14.701,-17.346,14.729,-17.418,14.723,-17.445,14.652,-17.536,14.755,-17.412,14.792,-17.147,14.922,-16.843,15.294,-16.571,15.734,-16.535,15.838,-16.502,15.917,-16.48,16.097,-16.441,16.205,-16.404,16.225,-16.358,16.307,-16.302,16.451,-16.239,16.531,-16.168,16.547,-16.113,16.54,-16.074,16.51,-15.959,16.492,-15.768,16.485,-15.621,16.507,-15.517,16.557,-15.38,16.582,-15.211,16.583,-15.121,16.604,-15.113,16.645,-15.091,16.657,-15.055,16.641,-15.022,16.647,-14.991,16.677,-14.96,16.679,-14.929,16.654,-14.787,16.646,-14.534,16.656,-14.3,16.58,-14.086,16.419,-13.975,16.311,-13.968,16.257,-13.933,16.203,-13.868,16.148,-13.81,16.138,-13.757,16.173,-13.715,16.169,-13.685,16.127,-13.624,16.118,-13.556,16.144,-13.507,16.135,-13.498,16.11,-13.487,16.097,-13.454,16.091,-13.41,16.059,-13.348,15.973,-13.297,15.854,-13.258,15.7,-13.206,15.617,-13.142,15.603,-13.105,15.572,-13.098,15.535,-13.079,15.51,-13.049,15.497,-12.994,15.505,-12.931,15.453,-12.863,15.34,-12.852,15.29,-12.863,15.262,-12.858,15.243,-12.813,15.224,-12.77,15.187,-12.735,15.131,-12.66,15.082,-12.544,15.039,-12.46,14.975,-12.409,14.889,-12.303,14.817,-12.281,14.809]]]},{"n":"Saudi Arabia","c":"SA","r":2,"ml":1.7,"lx":44.7,"ly":23.81,"b":[34.616,16.372,55.641,32.125],"p":[[[36.902,25.383,36.875,25.383,36.805,25.451,36.764,25.501,36.722,25.534,36.53,25.602,36.504,25.645,36.534,25.689,36.554,25.645,36.589,25.62,36.748,25.559,36.924,25.426,36.955,25.415,36.902,25.383]],[[41.988,16.716,42.065,16.71,42.033,16.742,42.026,16.758,42.06,16.804,42.17,16.709,42.167,16.596,42.158,16.571,42.128,16.595,42.108,16.618,42.102,16.644,42.072,16.671,41.964,16.653,41.897,16.684,41.802,16.779,41.776,16.847,41.816,16.86,41.858,16.893,41.885,16.947,41.86,17.003,41.917,16.994,41.948,16.936,41.954,16.806,41.963,16.779,41.947,16.749,41.988,16.716]],[[51.978,18.996,51.743,18.965,51.515,18.934,51.258,18.899,50.95,18.858,50.708,18.825,50.355,18.778,50.039,18.735,49.742,18.695,49.445,18.655,49.192,18.621,49.042,18.582,48.865,18.495,48.593,18.362,48.316,18.227,48.172,18.157,48.022,17.977,47.946,17.886,47.808,17.721,47.704,17.597,47.58,17.448,47.525,17.316,47.442,17.112,47.37,17.06,47.251,16.994,47.144,16.947,46.976,16.953,46.88,17.079,46.779,17.212,46.728,17.266,46.682,17.269,46.513,17.252,46.31,17.231,46.071,17.253,45.794,17.278,45.535,17.302,45.407,17.32,45.237,17.406,45.193,17.423,45.148,17.427,44.946,17.43,44.747,17.432,44.546,17.404,44.355,17.414,44.156,17.399,44.086,17.366,44.008,17.367,43.96,17.338,43.917,17.325,43.866,17.35,43.804,17.344,43.713,17.366,43.653,17.422,43.597,17.471,43.539,17.499,43.474,17.516,43.418,17.516,43.346,17.486,43.302,17.457,43.191,17.359,43.186,17.325,43.237,17.266,43.221,17.239,43.156,17.205,43.136,17.113,43.126,17.062,43.117,16.942,43.146,16.847,43.184,16.812,43.186,16.771,43.165,16.689,43.105,16.664,43.061,16.587,43.034,16.55,42.986,16.509,42.799,16.372,42.79,16.452,42.731,16.57,42.726,16.653,42.699,16.737,42.647,16.801,42.553,16.868,42.544,16.96,42.475,17.05,42.383,17.122,42.332,17.257,42.294,17.435,42.052,17.669,41.75,17.886,41.658,18.008,41.508,18.256,41.432,18.452,41.229,18.678,41.221,18.765,41.191,18.871,41.144,18.989,41.116,19.082,40.913,19.49,40.848,19.555,40.792,19.646,40.777,19.717,40.759,19.755,40.616,19.822,40.482,19.993,40.081,20.266,39.884,20.293,39.728,20.39,39.614,20.518,39.491,20.737,39.276,20.974,39.094,21.31,39.151,21.433,39.147,21.519,39.091,21.664,39.03,21.776,38.988,21.882,39.021,22.033,39.034,22.203,39.07,22.294,39.096,22.393,39.062,22.592,39.001,22.699,39.007,22.77,38.939,22.805,38.883,22.882,38.941,22.882,38.836,22.989,38.797,23.049,38.757,23.194,38.706,23.306,38.542,23.558,38.464,23.712,38.289,23.911,38.099,24.058,37.978,24.125,37.92,24.185,37.821,24.188,37.713,24.274,37.638,24.278,37.543,24.292,37.431,24.459,37.338,24.616,37.181,24.82,37.22,24.873,37.266,24.96,37.243,25.073,37.218,25.151,37.149,25.291,36.921,25.641,36.86,25.692,36.763,25.751,36.703,25.903,36.675,26.039,36.519,26.105,36.25,26.595,36.094,26.766,36.032,26.881,35.852,27.07,35.763,27.259,35.581,27.432,35.424,27.734,35.18,28.035,35.078,28.087,34.828,28.109,34.722,28.131,34.625,28.065,34.616,28.148,34.683,28.264,34.78,28.507,34.799,28.721,34.951,29.354,35.164,29.321,35.339,29.294,35.595,29.255,35.86,29.214,36.015,29.19,36.068,29.201,36.283,29.355,36.476,29.495,36.592,29.666,36.704,29.832,36.755,29.866,36.927,29.897,37.199,29.946,37.469,29.995,37.491,30.012,37.554,30.145,37.634,30.313,37.65,30.331,37.67,30.348,37.863,30.443,37.98,30.5,37.813,30.669,37.655,30.829,37.479,31.008,37.329,31.147,37.105,31.355,36.959,31.492,37.216,31.556,37.493,31.626,37.774,31.696,38.111,31.781,38.375,31.847,38.77,31.946,38.962,31.995,38.997,32.007,39.145,32.125,39.369,32.092,39.704,32.043,40.028,31.995,40.369,31.939,40.479,31.893,40.808,31.725,41.022,31.616,41.272,31.489,41.585,31.33,41.8,31.22,42.074,31.08,42.289,30.92,42.56,30.718,42.858,30.495,43.103,30.322,43.441,30.084,43.774,29.849,44.1,29.619,44.361,29.435,44.691,29.202,44.717,29.194,45.05,29.167,45.499,29.132,45.95,29.096,46.356,29.064,46.531,29.096,46.725,29.075,46.982,29.046,47.139,29.026,47.433,28.99,47.521,28.838,47.553,28.732,47.583,28.628,47.671,28.533,47.872,28.535,48.05,28.538,48.269,28.541,48.442,28.543,48.499,28.449,48.523,28.355,48.626,28.133,48.774,27.959,48.809,27.896,48.833,27.801,48.807,27.765,48.797,27.724,48.906,27.629,49.087,27.549,49.158,27.528,49.238,27.493,49.175,27.438,49.282,27.31,49.405,27.181,49.538,27.152,49.717,26.956,49.986,26.829,50.15,26.663,50.135,26.66,50.087,26.676,50.026,26.699,50.008,26.679,50.011,26.609,50.027,26.527,50.111,26.456,50.185,26.405,50.214,26.308,50.155,26.101,50.135,26.101,50.096,26.119,50.054,26.123,50.032,26.111,50.081,25.961,50.13,25.847,50.19,25.756,50.239,25.623,50.281,25.566,50.455,25.425,50.508,25.307,50.558,25.087,50.667,24.964,50.726,24.869,50.804,24.789,50.856,24.68,50.928,24.595,50.966,24.574,51.023,24.565,51.093,24.565,51.178,24.587,51.268,24.607,51.338,24.564,51.411,24.571,51.418,24.531,51.37,24.477,51.31,24.34,51.395,24.319,51.477,24.308,51.535,24.286,51.568,24.286,51.568,24.258,51.572,24.128,51.593,24.079,51.629,24.035,51.684,23.97,51.739,23.904,51.794,23.838,51.849,23.773,51.904,23.708,51.959,23.642,52.014,23.576,52.069,23.511,52.125,23.445,52.179,23.38,52.235,23.314,52.29,23.249,52.345,23.183,52.4,23.118,52.455,23.052,52.51,22.987,52.555,22.933,52.639,22.923,52.666,22.919,52.742,22.91,52.859,22.896,53.012,22.877,53.192,22.855,53.394,22.83,53.61,22.804,53.832,22.777,54.055,22.75,54.27,22.723,54.472,22.699,54.652,22.677,54.805,22.658,54.922,22.644,54.998,22.634,55.025,22.631,55.104,22.621,55.119,22.624,55.186,22.704,55.259,22.591,55.32,22.497,55.404,22.368,55.493,22.231,55.578,22.1,55.641,22.002,55.607,21.9,55.571,21.79,55.534,21.679,55.498,21.568,55.461,21.458,55.424,21.347,55.388,21.236,55.351,21.126,55.314,21.015,55.278,20.904,55.241,20.793,55.205,20.683,55.168,20.572,55.131,20.461,55.095,20.351,55.058,20.24,55.021,20.129,54.977,19.996,54.871,19.96,54.699,19.903,54.527,19.846,54.355,19.788,54.183,19.731,54.011,19.674,53.839,19.617,53.667,19.559,53.495,19.502,53.323,19.444,53.151,19.387,52.979,19.33,52.807,19.272,52.635,19.215,52.463,19.158,52.291,19.1,52.119,19.043,51.978,18.996]],[[36.596,25.713,36.586,25.699,36.544,25.734,36.546,25.812,36.583,25.856,36.58,25.795,36.596,25.735,36.596,25.713]]]},{"n":"São Tomé and Príncipe","c":"ST","r":6,"ml":5,"lx":7.02,"ly":0.97,"b":[6.468,0.047,7.452,1.699],"p":[[[7.424,1.568,7.387,1.542,7.342,1.564,7.331,1.603,7.388,1.68,7.414,1.699,7.437,1.683,7.45,1.662,7.452,1.631,7.424,1.568]],[[6.66,0.121,6.557,0.047,6.52,0.066,6.497,0.117,6.468,0.227,6.478,0.28,6.524,0.34,6.626,0.4,6.687,0.404,6.75,0.326,6.75,0.243,6.66,0.121]]]},{"n":"San Marino","c":"SM","r":6,"ml":5,"lx":12.44,"ly":43.93,"b":[12.397,43.894,12.515,43.99],"p":[[[12.485,43.901,12.426,43.894,12.397,43.935,12.441,43.982,12.504,43.99,12.515,43.953,12.485,43.901]]]},{"n":"Samoa","c":"WS","r":4,"ml":3,"lx":-172.44,"ly":-13.64,"b":[-172.779,-14.047,-171.45,-13.465],"p":[[[-172.333,-13.465,-172.222,-13.56,-172.177,-13.685,-172.225,-13.804,-172.331,-13.775,-172.485,-13.8,-172.536,-13.792,-172.659,-13.645,-172.744,-13.579,-172.779,-13.517,-172.67,-13.524,-172.511,-13.483,-172.333,-13.465]],[[-171.454,-14.046,-171.728,-14.047,-171.864,-14.002,-171.912,-14.002,-172.028,-13.907,-172.046,-13.857,-171.985,-13.824,-171.858,-13.807,-171.604,-13.879,-171.565,-13.943,-171.507,-13.95,-171.461,-13.978,-171.45,-14.022,-171.454,-14.046]]]},{"n":"Saint Vincent and the Grenadines","c":"VC","r":6,"ml":5,"lx":-61.34,"ly":13.09,"b":[-61.354,12.695,-61.124,13.359],"p":[[[-61.175,13.158,-61.204,13.142,-61.277,13.21,-61.268,13.288,-61.224,13.331,-61.182,13.356,-61.139,13.359,-61.124,13.294,-61.135,13.203,-61.175,13.158]],[[-61.226,12.995,-61.242,12.984,-61.235,12.984,-61.242,12.976,-61.247,12.984,-61.255,12.988,-61.265,12.99,-61.277,12.99,-61.277,12.997,-61.262,12.993,-61.239,12.997,-61.239,13.005,-61.246,13.013,-61.249,13.018,-61.241,13.026,-61.213,13.043,-61.201,13.053,-61.2,13.049,-61.199,13.045,-61.198,13.042,-61.194,13.038,-61.208,13.025,-61.226,12.995]],[[-61.334,12.695,-61.345,12.695,-61.354,12.698,-61.351,12.701,-61.34,12.704,-61.334,12.71,-61.336,12.719,-61.336,12.729,-61.327,12.735,-61.32,12.735,-61.317,12.732,-61.315,12.723,-61.32,12.716,-61.326,12.71,-61.329,12.701,-61.334,12.695]]]},{"n":"Saint Lucia","c":"LC","r":6,"ml":5,"lx":-60.98,"ly":13.89,"b":[-61.073,13.718,-60.887,14.093],"p":[[[-60.895,13.822,-60.951,13.718,-61.061,13.783,-61.073,13.866,-61.064,13.916,-60.997,14.011,-60.945,14.073,-60.908,14.093,-60.887,14.011,-60.895,13.822]]]},{"n":"Saint Kitts and Nevis","c":"KN","r":6,"ml":5,"lx":-62.76,"ly":17.34,"b":[-62.84,17.101,-62.532,17.403],"p":[[[-62.532,17.122,-62.582,17.101,-62.625,17.13,-62.615,17.199,-62.575,17.201,-62.534,17.17,-62.532,17.122]],[[-62.631,17.24,-62.656,17.224,-62.702,17.286,-62.776,17.303,-62.839,17.339,-62.84,17.347,-62.839,17.365,-62.827,17.386,-62.795,17.403,-62.714,17.353,-62.676,17.291,-62.641,17.262,-62.631,17.24]]]},{"n":"Rwanda","c":"RW","r":3,"ml":3,"lx":30.1,"ly":-1.9,"b":[28.858,-2.809,30.877,-1.063],"p":[[[29.577,-1.388,29.61,-1.387,29.825,-1.336,29.847,-1.352,29.882,-1.452,29.9,-1.466,29.93,-1.47,29.991,-1.447,30.102,-1.369,30.15,-1.321,30.207,-1.254,30.28,-1.179,30.321,-1.113,30.36,-1.075,30.412,-1.063,30.47,-1.066,30.51,-1.067,30.477,-1.083,30.47,-1.131,30.508,-1.208,30.632,-1.367,30.711,-1.397,30.762,-1.459,30.813,-1.563,30.828,-1.694,30.807,-1.851,30.819,-1.967,30.865,-2.044,30.877,-2.143,30.855,-2.265,30.829,-2.338,30.798,-2.363,30.763,-2.372,30.715,-2.363,30.657,-2.374,30.593,-2.397,30.554,-2.4,30.529,-2.396,30.482,-2.376,30.408,-2.313,30.271,-2.348,30.234,-2.347,30.183,-2.377,30.142,-2.414,30.117,-2.417,30.092,-2.412,29.973,-2.337,29.93,-2.34,29.912,-2.549,29.893,-2.665,29.868,-2.716,29.783,-2.766,29.698,-2.795,29.651,-2.793,29.464,-2.808,29.39,-2.809,29.35,-2.792,29.297,-2.673,29.198,-2.62,29.102,-2.596,29.063,-2.603,29.029,-2.665,29.014,-2.72,28.922,-2.682,28.894,-2.635,28.891,-2.556,28.858,-2.447,28.876,-2.4,28.913,-2.37,28.99,-2.313,29.106,-2.233,29.132,-2.195,29.148,-2.132,29.141,-1.985,29.129,-1.86,29.143,-1.816,29.197,-1.72,29.268,-1.622,29.352,-1.518,29.402,-1.507,29.468,-1.468,29.538,-1.41,29.577,-1.388]]]},{"n":"Russia","c":"RU","r":2,"ml":1.7,"lx":44.69,"ly":58.25,"b":[-180,41.199,180,81.854],"p":[[[145.882,43.46,145.896,43.455,145.914,43.455,145.931,43.457,145.941,43.445,145.944,43.426,145.931,43.426,145.907,43.422,145.894,43.42,145.887,43.433,145.882,43.444,145.869,43.45,145.869,43.458,145.882,43.46]],[[146.359,43.625,146.332,43.62,146.288,43.625,146.274,43.63,146.284,43.639,146.31,43.652,146.333,43.647,146.35,43.644,146.359,43.625]],[[146.046,43.409,146.032,43.407,146.028,43.42,146.049,43.434,146.089,43.449,146.101,43.44,146.086,43.429,146.07,43.421,146.046,43.409]],[[137.179,55.1,137.055,54.927,136.969,54.924,136.903,54.961,136.765,54.946,136.715,54.956,136.795,55.009,136.996,55.093,137.078,55.092,137.156,55.108,137.179,55.1]],[[150.59,59.019,150.511,59.007,150.472,59.035,150.47,59.054,150.592,59.097,150.666,59.16,150.713,59.122,150.728,59.095,150.59,59.019]],[[120.261,73.09,120.008,73.045,119.792,73.045,119.64,73.124,119.762,73.155,119.964,73.168,120.079,73.157,120.237,73.107,120.261,73.09]],[[124.543,73.85,124.482,73.848,124.366,73.875,124.336,73.91,124.337,73.928,124.43,73.943,124.548,73.934,124.637,73.9,124.653,73.888,124.543,73.85]],[[106.27,78.206,106.151,78.199,106.024,78.22,106.058,78.265,106.351,78.273,106.457,78.34,106.64,78.336,106.691,78.317,106.72,78.294,106.719,78.265,106.679,78.265,106.505,78.262,106.472,78.245,106.27,78.206]],[[107.415,77.243,107.302,77.242,107.27,77.289,107.366,77.347,107.486,77.347,107.594,77.33,107.629,77.32,107.665,77.3,107.679,77.268,107.415,77.243]],[[97.588,76.599,97.535,76.584,97.43,76.591,97.342,76.629,97.31,76.69,97.382,76.707,97.588,76.599]],[[96.854,76.199,96.798,76.188,96.754,76.196,96.739,76.207,96.74,76.258,96.833,76.324,96.835,76.345,96.878,76.355,96.99,76.343,97.045,76.315,97.053,76.303,96.974,76.237,96.854,76.199]],[[100.136,79.614,99.915,79.602,99.942,79.671,99.956,79.69,100.068,79.701,100.142,79.684,100.3,79.67,100.136,79.614]],[[76.756,73.446,76.659,73.44,76.234,73.476,76.083,73.523,76.14,73.554,76.251,73.555,76.756,73.446]],[[82.71,74.091,82.613,74.056,82.478,74.076,82.382,74.099,82.329,74.131,82.382,74.149,82.526,74.161,82.611,74.149,82.689,74.111,82.71,74.091]],[[84.759,74.459,84.71,74.4,84.429,74.43,84.389,74.454,84.54,74.49,84.68,74.512,84.873,74.516,84.759,74.459]],[[86.653,74.981,86.737,74.963,87.001,74.992,87.052,74.983,87.124,74.94,87.012,74.862,86.927,74.831,86.692,74.848,86.391,74.851,86.259,74.894,86.331,74.939,86.504,74.966,86.605,74.993,86.653,74.981]],[[59.313,81.305,59.097,81.292,58.719,81.314,58.61,81.337,58.634,81.36,58.881,81.392,59.075,81.398,59.281,81.366,59.375,81.325,59.313,81.305]],[[-179.799,68.94,-179.595,68.906,-179.515,68.917,-179.471,68.912,-179.356,68.853,-179.279,68.825,-178.874,68.754,-178.689,68.675,-178.539,68.586,-178.614,68.603,-178.751,68.66,-178.737,68.593,-178.693,68.546,-178.474,68.502,-178.244,68.467,-178.097,68.425,-178.049,68.388,-178.019,68.323,-178.056,68.265,-177.922,68.287,-177.797,68.338,-177.862,68.378,-178.285,68.519,-178.373,68.566,-178.25,68.541,-177.683,68.363,-177.527,68.294,-177.593,68.281,-177.639,68.241,-177.589,68.224,-177.521,68.237,-177.408,68.245,-177.297,68.223,-177.172,68.175,-176.907,68.119,-175.345,67.678,-175.31,67.602,-175.266,67.567,-175.24,67.521,-175.233,67.447,-175.375,67.357,-175.155,67.365,-175.123,67.377,-175.066,67.413,-175.003,67.438,-174.918,67.408,-174.85,67.349,-174.87,67.269,-174.93,67.203,-174.938,67.093,-174.885,67,-174.829,66.961,-174.784,66.917,-174.771,66.784,-174.87,66.725,-174.925,66.623,-174.864,66.613,-174.675,66.603,-174.612,66.585,-174.504,66.538,-174.478,66.492,-174.454,66.43,-174.419,66.372,-174.394,66.344,-174.366,66.348,-174.257,66.428,-174.206,66.452,-174.085,66.473,-174.018,66.383,-174.065,66.23,-174.025,66.23,-173.994,66.246,-173.955,66.287,-173.9,66.31,-173.832,66.366,-173.774,66.435,-173.843,66.488,-173.921,66.522,-174.102,66.541,-174.196,66.581,-174.232,66.632,-174.14,66.653,-174.061,66.69,-174.006,66.779,-174.019,66.827,-174.041,66.875,-174.086,66.943,-174.154,66.982,-174.284,67.002,-174.342,67.04,-174.431,67.038,-174.519,67.049,-174.554,67.063,-174.55,67.091,-174.448,67.103,-173.884,67.106,-173.68,67.145,-173.587,67.133,-173.494,67.105,-173.158,67.069,-173.168,67.052,-173.224,67.035,-173.324,66.955,-173.343,66.909,-173.347,66.851,-173.259,66.84,-173.175,66.865,-173.216,66.911,-173.228,66.969,-173.193,66.994,-173.147,66.999,-173.058,66.956,-172.963,66.942,-172.641,66.925,-172.549,66.931,-172.52,66.952,-172.583,66.978,-173.002,67.034,-173.008,67.065,-172.621,67.027,-172.447,66.992,-172.274,66.966,-172.031,66.973,-171.796,66.932,-171.57,66.819,-171.36,66.677,-171.149,66.593,-170.927,66.53,-170.556,66.357,-170.51,66.344,-170.473,66.32,-170.543,66.291,-170.604,66.249,-170.483,66.278,-170.361,66.298,-170.301,66.294,-170.247,66.272,-170.212,66.236,-170.192,66.201,-170.244,66.169,-169.889,66.163,-169.778,66.143,-169.729,66.058,-169.832,65.999,-169.892,66.006,-169.949,66.031,-170.004,66.033,-170.159,66.008,-170.401,65.929,-170.541,65.865,-170.563,65.824,-170.541,65.71,-170.561,65.656,-170.666,65.622,-170.897,65.643,-171.001,65.665,-171.119,65.695,-171.232,65.737,-171.377,65.804,-171.422,65.81,-171.451,65.794,-171.402,65.752,-171.303,65.698,-171.134,65.628,-171.054,65.55,-171.106,65.511,-171.17,65.502,-171.216,65.503,-171.364,65.527,-171.466,65.533,-171.79,65.51,-171.907,65.496,-171.947,65.508,-171.957,65.542,-172.131,65.567,-172.234,65.57,-172.282,65.582,-172.322,65.618,-172.436,65.67,-172.608,65.69,-172.719,65.692,-172.783,65.681,-172.557,65.612,-172.354,65.496,-172.392,65.475,-172.418,65.45,-172.306,65.448,-172.233,65.456,-172.212,65.425,-172.27,65.303,-172.309,65.276,-172.662,65.249,-172.573,65.228,-172.482,65.222,-172.379,65.227,-172.286,65.206,-172.224,65.129,-172.213,65.048,-172.304,65.002,-172.399,64.965,-172.593,64.908,-172.792,64.883,-172.897,64.889,-172.999,64.877,-173.066,64.847,-173.086,64.817,-172.998,64.837,-172.897,64.826,-172.801,64.791,-172.812,64.761,-172.903,64.729,-172.924,64.705,-172.889,64.664,-172.901,64.629,-172.854,64.61,-172.747,64.603,-172.616,64.578,-172.487,64.544,-172.437,64.515,-172.394,64.475,-172.379,64.432,-172.401,64.414,-172.695,64.407,-172.739,64.412,-172.756,64.46,-172.792,64.499,-172.903,64.526,-172.949,64.507,-172.916,64.369,-172.96,64.328,-173.009,64.297,-173.157,64.28,-173.275,64.29,-173.376,64.355,-173.376,64.41,-173.309,64.443,-173.309,64.487,-173.327,64.54,-173.396,64.479,-173.475,64.429,-173.604,64.365,-173.666,64.357,-173.73,64.365,-173.898,64.41,-174.001,64.449,-174.205,64.578,-174.318,64.638,-174.571,64.718,-174.83,64.776,-175.036,64.814,-175.146,64.809,-175.256,64.794,-175.395,64.802,-175.442,64.817,-175.483,64.849,-175.521,64.867,-175.716,64.946,-175.854,65.011,-175.859,65.054,-175.83,65.106,-175.856,65.233,-175.923,65.352,-176.093,65.471,-176.547,65.548,-176.922,65.601,-177.056,65.614,-177.175,65.602,-177.489,65.504,-177.699,65.49,-178.31,65.485,-178.412,65.496,-178.505,65.537,-178.526,65.593,-178.499,65.697,-178.502,65.74,-178.526,65.755,-178.559,65.754,-178.679,65.795,-178.791,65.865,-178.879,65.936,-178.939,66.033,-178.858,66.038,-178.747,66.014,-178.731,66.037,-178.694,66.124,-178.616,66.166,-178.587,66.198,-178.534,66.317,-178.527,66.402,-178.616,66.355,-178.753,66.237,-178.821,66.203,-178.868,66.187,-178.916,66.18,-179.026,66.204,-179.105,66.232,-179.107,66.346,-179.143,66.375,-179.178,66.353,-179.193,66.313,-179.293,66.305,-179.34,66.288,-179.316,66.22,-179.327,66.163,-179.423,66.141,-179.616,66.128,-179.683,66.184,-179.741,66.106,-179.784,66.018,-179.79,65.901,-179.728,65.804,-179.641,65.758,-179.449,65.688,-179.366,65.639,-179.344,65.575,-179.352,65.517,-179.452,65.445,-179.519,65.386,-179.635,65.244,-179.705,65.187,-180,65.067,-180,65.312,-180,65.557,-180,65.802,-180,66.046,-180,66.291,-180,66.536,-180,66.781,-180,67.025,-180,67.27,-180,67.515,-180,67.76,-180,68.004,-180,68.249,-180,68.494,-180,68.739,-180,68.983,-179.799,68.94]],[[130.687,42.303,130.658,42.328,130.652,42.373,130.618,42.416,130.554,42.475,130.527,42.535,130.584,42.567,130.577,42.623,130.521,42.674,130.439,42.686,130.42,42.7,130.425,42.727,130.453,42.755,130.493,42.779,130.577,42.812,130.722,42.836,130.803,42.857,130.869,42.863,130.943,42.852,131.006,42.883,131.069,42.902,131.083,42.956,131.086,43.038,131.109,43.062,131.136,43.098,131.176,43.142,131.212,43.258,131.239,43.338,131.257,43.378,131.262,43.433,131.244,43.469,131.209,43.49,131.182,43.506,131.18,43.567,131.184,43.651,131.174,43.705,131.213,44.003,131.255,44.072,131.126,44.469,131.087,44.596,131.061,44.66,131.004,44.753,130.968,44.8,130.982,44.844,131.033,44.889,131.082,44.91,131.228,44.92,131.268,44.936,131.447,44.984,131.488,45.013,131.579,45.084,131.614,45.137,131.654,45.205,131.742,45.243,131.795,45.305,131.852,45.327,131.909,45.274,131.978,45.244,132.067,45.226,132.181,45.203,132.363,45.16,132.549,45.123,132.666,45.094,132.723,45.081,132.839,45.061,132.889,45.046,132.936,45.03,133.012,45.075,133.113,45.131,133.097,45.22,133.113,45.321,133.186,45.495,133.267,45.545,133.31,45.553,133.355,45.572,133.436,45.605,133.466,45.651,133.449,45.705,133.476,45.758,133.485,45.81,133.513,45.879,133.551,45.898,133.608,45.92,133.648,45.955,133.686,46.009,133.711,46.07,133.701,46.14,133.75,46.186,133.833,46.224,133.861,46.248,133.875,46.309,133.88,46.336,133.903,46.367,133.887,46.431,133.867,46.499,133.958,46.614,134.023,46.713,134.039,46.858,134.046,46.882,134.071,46.951,134.086,46.978,134.137,47.069,134.202,47.128,134.189,47.194,134.163,47.259,134.168,47.302,134.225,47.353,134.26,47.378,134.291,47.414,134.339,47.429,134.383,47.438,134.483,47.447,134.542,47.485,134.596,47.524,134.696,47.625,134.728,47.684,134.752,47.715,134.699,47.801,134.65,47.874,134.591,47.975,134.566,48.023,134.605,48.083,134.647,48.12,134.669,48.153,134.681,48.21,134.665,48.254,134.564,48.322,134.456,48.355,134.335,48.369,134.293,48.373,134.206,48.36,133.842,48.274,133.672,48.208,133.573,48.133,133.468,48.097,133.301,48.102,133.144,48.106,133.02,48.064,132.877,47.979,132.773,47.94,132.707,47.947,132.637,47.89,132.562,47.769,132.476,47.715,132.38,47.729,132.15,47.718,131.785,47.681,131.557,47.682,131.464,47.723,131.319,47.728,131.122,47.698,131.003,47.691,130.962,47.709,130.933,47.76,130.915,47.843,130.849,47.929,130.733,48.019,130.712,48.128,130.787,48.255,130.804,48.342,130.763,48.388,130.747,48.43,130.659,48.483,130.597,48.575,130.552,48.602,130.566,48.68,130.617,48.773,130.553,48.861,130.355,48.866,130.196,48.892,130.037,48.972,129.793,49.199,129.671,49.279,129.591,49.287,129.534,49.323,129.498,49.389,129.441,49.389,129.385,49.389,129.35,49.362,129.31,49.354,129.248,49.379,129.185,49.381,129.12,49.362,129.065,49.375,129.02,49.419,128.938,49.449,128.819,49.464,128.77,49.495,128.791,49.542,128.769,49.577,128.704,49.6,128.527,49.594,128.237,49.559,128,49.569,127.814,49.622,127.711,49.672,127.69,49.717,127.637,49.76,127.551,49.802,127.502,49.873,127.492,49.975,127.512,50.072,127.59,50.209,127.395,50.299,127.337,50.35,127.351,50.394,127.341,50.428,127.306,50.454,127.308,50.494,127.347,50.55,127.347,50.621,127.307,50.708,127.198,50.829,127.02,50.986,126.925,51.1,126.912,51.172,126.888,51.23,126.854,51.261,126.834,51.315,126.848,51.374,126.827,51.412,126.802,51.448,126.805,51.506,126.775,51.545,126.709,51.566,126.689,51.61,126.701,51.703,126.654,51.781,126.511,51.926,126.468,52.031,126.456,52.126,126.395,52.173,126.392,52.215,126.383,52.287,126.346,52.306,126.324,52.332,126.342,52.362,126.313,52.4,126.238,52.445,126.203,52.484,126.194,52.519,126.157,52.547,126.046,52.573,126.016,52.61,126.023,52.643,126.047,52.673,126.06,52.692,126.056,52.716,126.048,52.739,126.004,52.768,125.942,52.801,125.872,52.872,125.783,52.891,125.728,52.891,125.681,52.931,125.695,52.956,125.692,53.004,125.649,53.042,125.596,53.057,125.546,53.048,125.422,53.084,125.226,53.166,125.075,53.204,124.971,53.197,124.907,53.173,124.882,53.13,124.812,53.134,124.64,53.211,124.466,53.23,124.369,53.271,124.291,53.341,124.22,53.37,124.154,53.359,123.995,53.406,123.741,53.511,123.608,53.547,123.56,53.527,123.535,53.526,123.489,53.529,123.424,53.531,123.31,53.556,123.154,53.545,122.958,53.498,122.745,53.469,122.516,53.457,122.38,53.463,122.338,53.485,122.089,53.451,121.744,53.384,121.405,53.317,120.985,53.285,120.704,53.172,120.421,52.968,120.218,52.84,120.095,52.787,120.044,52.718,120.068,52.633,120.173,52.602,120.36,52.627,120.521,52.615,120.656,52.567,120.699,52.494,120.65,52.396,120.665,52.3,120.745,52.205,120.75,52.097,120.681,51.973,120.511,51.849,120.237,51.723,120.067,51.601,119.967,51.422,119.813,51.267,119.757,51.179,119.746,51.108,119.685,51.03,119.573,50.947,119.512,50.863,119.502,50.779,119.446,50.703,119.344,50.634,119.281,50.561,119.256,50.484,119.217,50.433,119.164,50.406,119.192,50.38,119.302,50.354,119.346,50.279,119.326,50.155,119.26,50.066,119.147,50.013,118.979,49.979,118.756,49.963,118.452,49.844,118.187,49.693,117.873,49.513,117.813,49.514,117.698,49.536,117.477,49.609,117.246,49.625,117.022,49.693,116.889,49.738,116.683,49.824,116.632,49.877,116.551,49.92,116.351,49.978,116.217,50.009,116.135,50.011,115.926,49.952,115.795,49.906,115.718,49.881,115.588,49.886,115.429,49.896,115.365,49.912,115.275,49.949,115.098,50.059,115.003,50.139,114.88,50.183,114.743,50.234,114.675,50.246,114.554,50.241,114.386,50.255,114.297,50.274,114.222,50.257,114.071,50.205,113.881,50.101,113.732,50.062,113.574,50.007,113.446,49.942,113.319,49.874,113.164,49.797,113.092,49.693,113.056,49.616,112.915,49.569,112.806,49.524,112.697,49.507,112.495,49.532,112.375,49.515,112.08,49.424,111.934,49.416,111.833,49.404,111.736,49.398,111.575,49.376,111.512,49.361,111.429,49.343,111.337,49.356,111.204,49.304,110.828,49.166,110.71,49.143,110.631,49.138,110.53,49.187,110.428,49.22,110.321,49.216,110.2,49.17,109.995,49.206,109.75,49.239,109.529,49.27,109.454,49.296,109.237,49.335,108.92,49.335,108.733,49.336,108.614,49.323,108.522,49.342,108.407,49.396,108.213,49.525,108.098,49.563,108.034,49.594,108.01,49.647,107.965,49.654,107.937,49.691,107.939,49.741,107.935,49.849,107.948,49.925,107.917,49.948,107.787,49.96,107.631,49.983,107.347,49.987,107.233,49.989,107.143,50.033,107.04,50.086,106.941,50.197,106.854,50.248,106.711,50.313,106.574,50.329,106.368,50.318,106.218,50.305,106.083,50.333,105.996,50.368,105.875,50.405,105.693,50.414,105.542,50.441,105.384,50.474,105.267,50.46,105.186,50.43,105.095,50.39,104.977,50.383,104.685,50.342,104.596,50.317,104.466,50.306,104.354,50.275,104.26,50.214,104.18,50.169,104.079,50.154,103.958,50.157,103.856,50.172,103.803,50.176,103.723,50.154,103.633,50.139,103.496,50.165,103.421,50.187,103.304,50.2,103.234,50.264,103.162,50.291,103.039,50.301,102.86,50.333,102.765,50.367,102.683,50.387,102.546,50.461,102.469,50.526,102.407,50.536,102.336,50.544,102.288,50.585,102.286,50.635,102.303,50.666,102.317,50.718,102.277,50.769,102.235,50.791,102.215,50.829,102.226,50.901,102.21,50.974,102.195,51.051,102.152,51.108,102.142,51.216,102.16,51.261,102.156,51.314,102.112,51.353,101.979,51.382,101.821,51.421,101.571,51.467,101.464,51.471,101.381,51.453,101.304,51.475,101.223,51.513,101.085,51.553,100.904,51.604,100.711,51.662,100.536,51.713,100.469,51.726,100.23,51.73,100.035,51.737,99.922,51.756,99.788,51.828,99.719,51.872,99.613,51.893,99.532,51.9,99.407,51.924,99.176,51.999,99.091,52.035,99.034,52.035,98.958,52.102,98.893,52.117,98.849,52.07,98.803,51.957,98.76,51.905,98.641,51.801,98.353,51.718,98.303,51.674,98.277,51.635,98.238,51.578,98.22,51.506,98.185,51.486,98.103,51.484,98.038,51.45,97.989,51.377,97.947,51.348,97.923,51.28,97.927,51.251,97.918,51.218,97.911,51.165,97.836,51.052,97.825,50.985,97.856,50.943,97.92,50.887,97.953,50.855,97.964,50.818,97.962,50.769,98.001,50.702,98.03,50.645,98.079,50.604,98.145,50.569,98.221,50.557,98.279,50.533,98.293,50.487,98.277,50.423,98.25,50.302,98.2,50.228,98.17,50.181,98.122,50.107,98.103,50.078,98.004,50.014,97.937,49.997,97.854,49.947,97.786,49.945,97.721,49.945,97.651,49.934,97.589,49.911,97.541,49.843,97.418,49.773,97.36,49.741,97.209,49.731,97.137,49.762,97.098,49.805,97.049,49.83,96.986,49.883,96.712,49.912,96.64,49.898,96.598,49.878,96.543,49.893,96.506,49.919,96.466,49.912,96.381,49.896,96.315,49.901,96.23,49.954,96.112,49.982,96.066,49.999,96.019,49.999,95.99,49.974,95.936,49.96,95.899,49.991,95.852,50.013,95.789,50.013,95.708,49.966,95.567,49.944,95.523,49.911,95.442,49.916,95.386,49.941,95.329,49.944,95.166,49.944,95.111,49.935,95.044,49.962,95.013,50.008,94.93,50.044,94.811,50.048,94.718,50.043,94.675,50.028,94.615,50.024,94.565,50.088,94.497,50.133,94.458,50.166,94.4,50.18,94.355,50.222,94.347,50.303,94.319,50.405,94.287,50.511,94.251,50.556,94.076,50.573,93.99,50.569,93.795,50.578,93.662,50.584,93.626,50.586,93.501,50.597,93.387,50.608,93.271,50.616,93.223,50.607,93.103,50.604,93.01,50.655,92.971,50.713,92.964,50.745,92.941,50.778,92.856,50.789,92.779,50.779,92.739,50.711,92.681,50.683,92.627,50.688,92.579,50.725,92.486,50.765,92.426,50.803,92.355,50.864,92.296,50.85,92.279,50.812,92.265,50.775,92.192,50.701,92.104,50.692,91.957,50.698,91.804,50.694,91.706,50.666,91.634,50.615,91.597,50.576,91.522,50.562,91.446,50.522,91.415,50.468,91.341,50.47,91.301,50.463,91.234,50.452,91.063,50.423,91.022,50.415,90.917,50.364,90.838,50.324,90.761,50.306,90.714,50.259,90.655,50.222,90.517,50.213,90.365,50.167,90.311,50.151,90.225,50.117,90.104,50.103,90.054,50.094,90.005,50.069,89.977,49.984,89.878,49.954,89.744,49.948,89.644,49.903,89.634,49.823,89.67,49.75,89.654,49.717,89.579,49.7,89.475,49.661,89.396,49.612,89.299,49.611,89.244,49.627,89.203,49.596,89.18,49.532,89.109,49.501,89.008,49.473,88.971,49.484,88.945,49.508,88.9,49.54,88.864,49.528,88.86,49.482,88.832,49.448,88.748,49.446,88.683,49.465,88.633,49.486,88.544,49.483,88.452,49.473,88.393,49.483,88.338,49.473,88.193,49.452,88.136,49.381,88.134,49.298,88.116,49.256,88.029,49.22,87.988,49.187,87.935,49.165,87.818,49.162,87.814,49.162,87.763,49.166,87.668,49.147,87.577,49.132,87.516,49.122,87.476,49.091,87.417,49.077,87.323,49.086,87.297,49.148,87.234,49.216,87.148,49.24,87.071,49.255,87.001,49.287,86.953,49.322,86.812,49.488,86.714,49.559,86.626,49.563,86.614,49.61,86.665,49.657,86.731,49.696,86.729,49.749,86.675,49.777,86.61,49.769,86.522,49.708,86.418,49.638,86.292,49.588,86.242,49.546,86.181,49.499,86.093,49.505,86.03,49.503,85.974,49.499,85.934,49.55,85.88,49.557,85.498,49.605,85.372,49.624,85.292,49.599,85.233,49.616,85.21,49.665,85.137,49.751,85.076,49.822,85.001,49.894,84.975,49.951,85,50.01,84.989,50.061,84.924,50.088,84.839,50.091,84.607,50.202,84.499,50.219,84.401,50.239,84.323,50.239,84.258,50.288,84.195,50.437,84.176,50.521,84.099,50.605,84.002,50.677,83.945,50.775,83.86,50.818,83.718,50.887,83.581,50.936,83.357,50.995,83.274,50.995,83.16,50.989,83.093,50.961,83.019,50.897,82.919,50.893,82.761,50.893,82.719,50.869,82.693,50.826,82.612,50.771,82.494,50.728,82.326,50.742,82.212,50.719,82.098,50.711,81.934,50.766,81.752,50.764,81.634,50.739,81.466,50.74,81.431,50.771,81.452,50.824,81.438,50.871,81.41,50.91,81.388,50.956,81.319,50.966,81.125,50.946,81.071,50.969,81.078,51.015,81.112,51.072,81.141,51.147,81.127,51.191,81.027,51.186,80.966,51.19,80.934,51.243,80.877,51.281,80.813,51.283,80.735,51.293,80.65,51.277,80.605,51.224,80.551,51.217,80.491,51.202,80.448,51.183,80.421,51.136,80.434,51.093,80.452,50.998,80.424,50.946,80.345,50.919,80.27,50.925,80.22,50.912,80.127,50.858,80.086,50.84,80.072,50.807,80.066,50.758,79.986,50.775,79.86,50.955,79.716,51.16,79.554,51.378,79.469,51.493,79.149,51.868,78.992,52.047,78.721,52.357,78.475,52.638,78.198,52.93,78.033,53.095,77.86,53.269,77.799,53.317,77.704,53.379,77.469,53.499,77.132,53.67,76.821,53.823,76.576,53.943,76.513,53.993,76.485,54.023,76.459,54.055,76.422,54.114,76.422,54.152,76.655,54.145,76.703,54.182,76.789,54.322,76.837,54.442,76.759,54.437,76.616,54.387,76.539,54.351,76.496,54.336,76.267,54.312,76.141,54.259,75.881,54.168,75.693,54.115,75.657,54.106,75.437,54.09,75.398,54.069,75.392,54.022,75.377,53.97,75.22,53.894,75.052,53.827,74.989,53.819,74.887,53.834,74.834,53.826,74.681,53.754,74.452,53.647,74.43,53.604,74.429,53.551,74.403,53.504,74.352,53.488,74.277,53.528,74.21,53.576,74.069,53.611,73.859,53.62,73.731,53.603,73.643,53.576,73.47,53.469,73.407,53.448,73.372,53.454,73.362,53.506,73.327,53.543,73.286,53.598,73.306,53.707,73.399,53.811,73.554,53.868,73.679,53.929,73.716,53.996,73.712,54.042,73.666,54.063,73.618,54.067,73.59,54.045,73.506,53.999,73.381,53.963,73.277,53.956,73.23,53.958,73.119,53.981,72.914,54.107,72.741,54.125,72.622,54.134,72.583,54.122,72.564,54.09,72.576,54.056,72.599,54.023,72.586,53.996,72.53,53.976,72.447,53.942,72.404,53.964,72.383,54.054,72.387,54.123,72.329,54.181,72.269,54.272,72.186,54.326,72.105,54.308,72.066,54.232,72.004,54.206,71.887,54.221,71.677,54.178,71.336,54.158,71.093,54.212,71.053,54.26,71.152,54.364,71.16,54.455,71.159,54.539,71.186,54.599,71.126,54.715,70.992,54.95,70.91,55.128,70.79,55.261,70.738,55.305,70.486,55.282,70.417,55.253,70.371,55.212,70.293,55.184,70.182,55.162,70.087,55.177,69.982,55.199,69.87,55.246,69.74,55.307,69.493,55.357,69.247,55.373,68.977,55.39,68.843,55.358,68.713,55.308,68.525,55.205,68.438,55.194,68.302,55.187,68.206,55.161,68.225,55.115,68.244,55.052,68.209,55.003,68.156,54.977,68.074,54.96,67.94,54.954,67.83,54.944,67.693,54.872,67.485,54.854,67.257,54.829,67.098,54.788,66.754,54.738,66.555,54.715,66.223,54.667,65.955,54.66,65.914,54.693,65.708,54.619,65.477,54.623,65.434,54.593,65.378,54.564,65.316,54.552,65.237,54.516,65.192,54.441,65.158,54.364,65.088,54.34,64.995,54.369,64.927,54.397,64.809,54.369,64.65,54.352,64.525,54.362,64.461,54.384,64.199,54.347,64.063,54.303,64.037,54.28,64.004,54.267,63.847,54.236,63.721,54.245,63.701,54.243,63.582,54.222,63.414,54.183,63.293,54.17,63.191,54.171,63.127,54.139,63.074,54.105,62.633,54.069,62.588,54.044,62.499,54.013,62.04,54.003,62.002,53.98,61.986,53.954,61.929,53.946,61.598,53.995,61.334,54.049,61.231,54.019,61.144,53.964,61.113,53.882,61.113,53.813,61.113,53.753,61.074,53.71,60.986,53.657,60.979,53.622,61.099,53.583,61.248,53.551,61.336,53.565,61.41,53.587,61.474,53.58,61.519,53.554,61.535,53.523,61.527,53.502,61.499,53.485,61.401,53.456,61.312,53.466,61.229,53.446,61.186,53.406,61.163,53.337,61.199,53.287,61.311,53.275,61.437,53.239,61.576,53.222,61.66,53.228,61.766,53.174,62.015,53.108,62.081,53.057,62.083,53.005,62.037,52.966,61.974,52.944,61.889,52.956,61.719,52.969,61.534,52.979,61.401,52.996,61.207,52.989,61.047,52.972,61.007,52.933,60.945,52.86,60.893,52.819,60.802,52.745,60.774,52.676,60.821,52.57,60.979,52.395,60.995,52.337,60.938,52.281,60.828,52.233,60.67,52.151,60.499,52.146,60.425,52.126,60.234,52.025,60.066,51.976,60.03,51.933,60.067,51.891,60.28,51.835,60.388,51.773,60.418,51.704,60.465,51.651,60.63,51.617,60.974,51.537,60.993,51.529,61.015,51.492,61.363,51.442,61.411,51.415,61.555,51.325,61.585,51.23,61.512,51.137,61.465,50.99,61.389,50.861,61.227,50.775,60.942,50.696,60.638,50.664,60.508,50.669,60.425,50.679,60.288,50.704,60.187,50.77,60.112,50.834,60.059,50.85,60.005,50.84,59.955,50.799,59.888,50.69,59.812,50.582,59.751,50.544,59.523,50.493,59.498,50.511,59.524,50.583,59.495,50.604,59.452,50.62,59.171,50.648,59.064,50.668,58.985,50.676,58.884,50.694,58.814,50.737,58.665,50.868,58.547,50.971,58.359,51.064,58.188,51.082,58.175,51.072,58.045,51.069,57.839,51.092,57.829,51.089,57.765,51.047,57.717,50.981,57.654,50.925,57.558,50.896,57.442,50.889,57.313,50.947,57.179,51.036,57.012,51.065,56.85,51.046,56.79,51.032,56.62,50.981,56.567,51.004,56.491,51.02,56.326,50.936,56.144,50.845,56.104,50.776,56.05,50.714,55.929,50.654,55.798,50.602,55.686,50.583,55.542,50.602,55.361,50.665,55.195,50.745,55.015,50.87,54.868,50.941,54.727,50.998,54.642,51.012,54.573,50.99,54.546,50.946,54.566,50.911,54.606,50.88,54.638,50.781,54.65,50.66,54.636,50.592,54.596,50.551,54.555,50.536,54.517,50.541,54.471,50.584,54.443,50.674,54.421,50.78,54.298,50.914,54.191,50.996,54.14,51.041,54.042,51.115,53.957,51.161,53.776,51.214,53.688,51.252,53.535,51.4,53.449,51.445,53.338,51.482,53.247,51.494,53.227,51.485,53.038,51.464,52.903,51.467,52.821,51.495,52.735,51.498,52.728,51.498,52.635,51.48,52.618,51.481,52.571,51.482,52.496,51.512,52.423,51.594,52.331,51.681,52.219,51.709,52.007,51.673,51.775,51.554,51.609,51.484,51.473,51.482,51.396,51.471,51.345,51.475,51.301,51.497,51.291,51.54,51.27,51.594,51.163,51.647,51.018,51.682,50.882,51.719,50.794,51.729,50.756,51.675,50.644,51.589,50.516,51.506,50.354,51.37,50.309,51.322,50.247,51.29,50.105,51.255,49.932,51.197,49.822,51.132,49.666,51.102,49.498,51.084,49.425,51.027,49.379,50.935,49.323,50.852,49.059,50.726,48.914,50.645,48.808,50.601,48.735,50.607,48.655,50.62,48.625,50.613,48.666,50.55,48.7,50.354,48.749,50.228,48.785,50.156,48.818,50.1,48.843,50.013,48.81,49.962,48.759,49.928,48.6,49.875,48.434,49.829,48.335,49.858,48.225,49.932,48.181,49.97,48.061,50.094,47.85,50.282,47.706,50.378,47.6,50.414,47.504,50.403,47.429,50.358,47.376,50.318,47.326,50.274,47.295,50.217,47.298,50.14,47.295,50.058,47.248,50.001,47.13,49.939,46.992,49.853,46.89,49.697,46.823,49.502,46.802,49.367,46.853,49.304,46.953,49.253,47.018,49.2,47.031,49.15,47.014,49.098,46.962,49.038,46.853,48.97,46.703,48.806,46.609,48.574,46.661,48.412,46.853,48.324,47.004,48.284,47.065,48.232,47.119,48.127,47.112,48.02,47.093,47.948,47.131,47.877,47.202,47.792,47.292,47.741,47.387,47.769,47.482,47.804,47.6,47.79,47.935,47.761,48.11,47.745,48.167,47.709,48.276,47.59,48.413,47.456,48.553,47.321,48.601,47.262,48.714,47.1,48.832,46.955,48.959,46.775,48.95,46.726,48.884,46.705,48.776,46.71,48.694,46.737,48.647,46.759,48.605,46.766,48.558,46.757,48.519,46.734,48.502,46.699,48.509,46.65,48.541,46.606,48.586,46.577,48.61,46.566,48.774,46.508,48.959,46.442,49.184,46.349,49.232,46.337,49.246,46.292,49.125,46.282,49.111,46.228,49.08,46.189,48.81,46.1,48.743,46.101,48.684,46.086,48.687,46.029,48.703,45.976,48.75,45.921,48.73,45.897,48.69,45.889,48.637,45.906,48.589,45.935,48.537,45.942,48.487,45.935,48.258,45.778,48.159,45.737,48.053,45.721,47.83,45.663,47.764,45.666,47.701,45.686,47.65,45.657,47.633,45.584,47.574,45.634,47.508,45.674,47.479,45.688,47.463,45.68,47.524,45.602,47.529,45.53,47.515,45.491,47.489,45.455,47.454,45.433,47.413,45.421,47.391,45.295,47.351,45.218,47.296,45.149,47.221,45.024,47.162,44.97,47.115,44.906,47.084,44.817,47.039,44.838,47.003,44.876,46.984,44.826,46.957,44.783,46.841,44.718,46.755,44.657,46.716,44.561,46.707,44.503,46.721,44.452,46.753,44.421,46.916,44.387,47.024,44.343,47.123,44.262,47.23,44.192,47.307,44.103,47.362,43.993,47.429,43.78,47.463,43.555,47.563,43.835,47.646,43.885,47.628,43.806,47.568,43.685,47.509,43.51,47.49,43.382,47.512,43.271,47.513,43.219,47.463,43.035,47.489,43,47.529,42.967,47.635,42.903,47.709,42.811,47.728,42.681,47.77,42.645,47.822,42.613,48.08,42.354,48.229,42.181,48.303,42.08,48.384,41.953,48.426,41.924,48.477,41.905,48.573,41.844,48.519,41.779,48.431,41.663,48.391,41.602,48.298,41.545,48.142,41.485,48.056,41.459,47.964,41.334,47.861,41.213,47.791,41.199,47.592,41.218,47.521,41.229,47.318,41.282,47.261,41.315,47.205,41.456,47.143,41.516,47.064,41.555,47.01,41.588,46.988,41.621,46.931,41.67,46.826,41.743,46.749,41.813,46.69,41.831,46.616,41.807,46.571,41.8,46.552,41.812,46.538,41.87,46.43,41.891,46.412,41.905,46.268,41.96,46.213,41.99,46.16,41.992,46.048,42.009,45.954,42.035,45.91,42.071,45.846,42.11,45.727,42.159,45.639,42.205,45.634,42.235,45.688,42.357,45.728,42.475,45.705,42.498,45.656,42.518,45.563,42.536,45.344,42.53,45.208,42.648,45.16,42.675,45.072,42.694,44.943,42.73,44.871,42.756,44.85,42.747,44.771,42.617,44.692,42.71,44.644,42.735,44.576,42.748,44.506,42.749,44.329,42.704,44.2,42.654,44.103,42.616,44.005,42.596,43.957,42.567,43.826,42.572,43.76,42.594,43.738,42.617,43.75,42.658,43.795,42.703,43.799,42.728,43.783,42.747,43.623,42.808,43.558,42.844,43.348,42.897,43.089,42.989,43,43.05,42.992,43.092,42.89,43.133,42.761,43.17,42.66,43.159,42.566,43.155,42.419,43.224,42.28,43.228,42.122,43.207,42.088,43.199,42.05,43.19,41.581,43.219,41.461,43.276,41.358,43.333,41.083,43.374,40.942,43.418,40.802,43.48,40.648,43.534,40.519,43.512,40.342,43.543,40.15,43.57,40.085,43.553,40.024,43.485,39.978,43.42,39.874,43.473,39.517,43.728,39.329,43.897,38.717,44.288,38.636,44.318,38.312,44.374,38.181,44.42,37.851,44.699,37.705,44.661,37.572,44.671,37.495,44.695,37.411,44.735,37.352,44.788,37.284,44.905,37.205,44.972,36.944,45.07,36.651,45.126,36.628,45.151,36.619,45.185,36.873,45.252,36.941,45.29,36.811,45.34,36.762,45.348,36.72,45.372,36.794,45.41,36.866,45.427,36.978,45.384,37.104,45.303,37.214,45.272,37.264,45.311,37.647,45.377,37.673,45.43,37.672,45.488,37.634,45.486,37.61,45.5,37.612,45.565,37.669,45.654,37.841,45.8,37.933,46.002,38.014,46.048,38.074,46.017,38.07,45.97,38.08,45.935,38.133,46.003,38.184,46.095,38.312,46.095,38.4,46.08,38.492,46.091,38.315,46.242,38.078,46.394,37.978,46.383,37.914,46.406,37.81,46.532,37.767,46.636,37.867,46.634,37.968,46.618,38.159,46.691,38.23,46.701,38.343,46.678,38.501,46.664,38.488,46.732,38.439,46.813,38.631,46.873,38.801,46.906,39.127,47.023,39.271,47.044,39.289,47.071,39.293,47.106,39.245,47.2,39.196,47.269,39.024,47.272,38.928,47.176,38.668,47.144,38.552,47.15,38.644,47.212,38.736,47.236,38.762,47.262,38.577,47.239,38.485,47.176,38.214,47.091,38.206,47.136,38.201,47.175,38.221,47.213,38.265,47.237,38.281,47.259,38.281,47.277,38.241,47.288,38.208,47.297,38.201,47.321,38.212,47.343,38.243,47.374,38.257,47.409,38.259,47.48,38.287,47.559,38.369,47.61,38.511,47.622,38.641,47.666,38.719,47.714,38.822,47.837,38.9,47.855,39.058,47.848,39.158,47.837,39.391,47.834,39.658,47.841,39.736,47.845,39.779,47.888,39.776,47.964,39.814,48.035,39.885,48.168,39.961,48.238,39.958,48.269,39.918,48.282,39.866,48.288,39.847,48.303,39.85,48.332,39.89,48.36,39.883,48.419,39.858,48.484,39.836,48.543,39.765,48.572,39.645,48.591,39.67,48.662,39.705,48.739,39.756,48.782,39.793,48.808,39.904,48.794,39.984,48.807,40.004,48.822,39.989,48.851,39.864,48.878,39.753,48.914,39.706,48.96,39.687,49.008,39.759,49.037,39.89,49.064,39.976,49.13,40.07,49.2,40.109,49.252,40.128,49.307,40.126,49.369,40.058,49.432,40.058,49.497,40.095,49.543,40.081,49.577,40.031,49.597,39.958,49.591,39.877,49.568,39.781,49.572,39.627,49.651,39.463,49.728,39.368,49.731,39.303,49.742,39.246,49.782,39.212,49.833,39.175,49.856,39.115,49.842,39.028,49.818,38.918,49.825,38.777,49.884,38.648,49.953,38.552,49.955,38.451,49.964,38.259,50.052,38.209,50.051,38.178,50.025,38.163,49.955,38.147,49.939,38.113,49.928,38.047,49.92,37.95,49.964,37.704,50.109,37.605,50.215,37.582,50.292,37.501,50.341,37.423,50.411,37.343,50.418,37.255,50.395,37.171,50.361,37.131,50.352,36.988,50.34,36.759,50.292,36.696,50.246,36.619,50.209,36.56,50.235,36.5,50.28,36.369,50.297,36.306,50.28,36.243,50.312,36.189,50.368,36.116,50.409,36.008,50.42,35.89,50.437,35.796,50.406,35.674,50.346,35.591,50.369,35.546,50.44,35.488,50.46,35.412,50.54,35.392,50.611,35.412,50.642,35.44,50.682,35.44,50.728,35.417,50.768,35.383,50.799,35.346,50.904,35.315,50.95,35.309,50.987,35.335,51.021,35.312,51.044,35.269,51.047,35.198,51.044,35.158,51.061,35.115,51.121,35.093,51.181,35.064,51.203,34.99,51.202,34.869,51.189,34.76,51.169,34.712,51.172,34.617,51.203,34.491,51.237,34.234,51.244,34.214,51.255,34.228,51.277,34.281,51.312,34.275,51.34,34.23,51.363,34.207,51.42,34.209,51.484,34.201,51.554,34.147,51.608,34.115,51.645,34.121,51.679,34.239,51.692,34.379,51.717,34.403,51.742,34.398,51.78,34.113,51.98,34.015,52.156,33.922,52.251,33.819,52.316,33.735,52.345,33.613,52.333,33.452,52.334,33.287,52.354,33.148,52.34,32.9,52.256,32.806,52.253,32.645,52.279,32.508,52.309,32.435,52.307,32.391,52.295,32.363,52.272,32.283,52.114,32.217,52.083,32.122,52.051,32.042,52.045,31.974,52.047,31.876,52.071,31.782,52.099,31.763,52.101,31.759,52.126,31.691,52.221,31.65,52.262,31.602,52.285,31.577,52.312,31.577,52.426,31.586,52.532,31.616,52.546,31.526,52.633,31.519,52.699,31.563,52.731,31.565,52.759,31.535,52.798,31.443,52.862,31.353,52.933,31.295,52.99,31.259,53.017,31.303,53.061,31.365,53.139,31.388,53.185,31.418,53.196,31.563,53.202,31.668,53.201,31.747,53.184,31.777,53.147,31.85,53.106,32.055,53.089,32.142,53.091,32.251,53.128,32.426,53.211,32.469,53.27,32.578,53.312,32.644,53.329,32.704,53.336,32.71,53.371,32.706,53.419,32.686,53.448,32.47,53.547,32.442,53.579,32.425,53.617,32.451,53.653,32.45,53.693,32.2,53.781,31.992,53.797,31.821,53.792,31.754,53.81,31.783,53.855,31.825,53.935,31.838,54.001,31.826,54.031,31.792,54.056,31.628,54.111,31.404,54.196,31.299,54.292,31.246,54.392,31.185,54.453,31.075,54.492,31.082,54.517,31.155,54.611,31.152,54.625,31.121,54.648,30.984,54.696,30.799,54.783,30.791,54.806,30.804,54.861,30.83,54.915,30.867,54.941,30.978,55.05,30.978,55.088,30.959,55.138,30.877,55.223,30.814,55.279,30.811,55.307,30.821,55.33,30.862,55.36,30.901,55.397,30.909,55.525,30.907,55.57,30.882,55.596,30.856,55.608,30.801,55.601,30.722,55.622,30.662,55.655,30.626,55.666,30.587,55.7,30.475,55.769,30.456,55.787,30.234,55.845,30.043,55.836,29.937,55.845,29.882,55.832,29.824,55.795,29.744,55.77,29.685,55.77,29.63,55.751,29.482,55.685,29.413,55.725,29.353,55.784,29.373,55.835,29.398,55.881,29.396,55.912,29.375,55.939,29.283,55.968,29.087,56.021,29.032,56.022,28.947,56.002,28.795,55.943,28.741,55.955,28.691,56.003,28.637,56.062,28.564,56.092,28.407,56.089,28.392,56.087,28.316,56.053,28.284,56.056,28.148,56.143,28.173,56.19,28.202,56.26,28.192,56.316,28.169,56.387,28.111,56.511,28.103,56.546,28.008,56.6,27.992,56.645,27.941,56.704,27.892,56.741,27.882,56.824,27.849,56.853,27.806,56.867,27.656,56.843,27.639,56.846,27.711,56.978,27.717,57.055,27.763,57.135,27.815,57.167,27.83,57.194,27.838,57.248,27.829,57.293,27.797,57.317,27.673,57.368,27.539,57.43,27.511,57.508,27.47,57.524,27.352,57.528,27.354,57.55,27.372,57.613,27.4,57.667,27.492,57.725,27.515,57.764,27.542,57.799,27.753,57.841,27.777,57.857,27.779,57.871,27.769,57.884,27.722,57.905,27.673,57.935,27.644,58.014,27.571,58.138,27.502,58.221,27.488,58.27,27.506,58.326,27.53,58.381,27.531,58.435,27.427,58.733,27.434,58.787,27.464,58.841,27.513,58.886,27.622,58.945,27.758,59.052,27.85,59.193,27.898,59.278,27.938,59.297,28.016,59.302,28.046,59.328,28.061,59.343,28.128,59.358,28.151,59.374,28.133,59.403,28.066,59.453,28.013,59.484,28.064,59.554,28.046,59.647,28.014,59.725,28.058,59.782,28.131,59.787,28.213,59.725,28.335,59.693,28.424,59.734,28.454,59.814,28.518,59.85,28.604,59.818,28.748,59.807,28.867,59.812,28.947,59.829,28.982,59.855,29.013,59.902,29.079,59.961,29.147,60,29.67,59.956,30.123,59.874,30.157,59.904,30.173,59.957,30.06,60.003,29.977,60.026,29.872,60.121,29.721,60.195,29.569,60.202,29.37,60.176,29.069,60.191,28.813,60.332,28.643,60.375,28.522,60.483,28.492,60.54,28.622,60.492,28.64,60.543,28.651,60.611,28.578,60.653,28.513,60.677,28.179,60.571,27.798,60.536,28.152,60.746,28.407,60.897,28.455,60.92,28.568,60.96,28.663,61.003,28.739,61.059,28.993,61.169,29.252,61.288,29.492,61.444,29.579,61.493,29.69,61.546,29.933,61.712,30.01,61.757,30.306,61.965,30.48,62.068,30.566,62.128,30.936,62.324,31.187,62.481,31.286,62.568,31.382,62.692,31.437,62.776,31.534,62.885,31.537,62.922,31.509,62.955,31.437,63.008,31.337,63.068,31.247,63.142,31.181,63.208,30.975,63.301,30.655,63.417,30.419,63.504,30.055,63.689,29.992,63.735,30.004,63.747,30.21,63.803,30.415,63.948,30.504,64.021,30.526,64.077,30.528,64.141,30.514,64.2,30.488,64.237,30.391,64.282,30.108,64.366,30.042,64.443,29.987,64.524,29.986,64.558,30.12,64.645,30.126,64.688,30.11,64.733,30.073,64.765,29.783,64.804,29.702,64.846,29.638,64.912,29.604,64.968,29.601,65.002,29.622,65.04,29.72,65.08,29.811,65.108,29.827,65.145,29.826,65.185,29.811,65.205,29.63,65.224,29.612,65.235,29.608,65.249,29.617,65.265,29.715,65.337,29.728,65.473,29.819,65.569,29.716,65.625,29.724,65.634,29.883,65.664,30.029,65.671,30.095,65.682,30.103,65.726,30.088,65.787,29.937,66.023,29.903,66.091,29.804,66.177,29.721,66.235,29.671,66.276,29.591,66.357,29.544,66.44,29.464,66.532,29.371,66.617,29.293,66.696,29.093,66.849,29.066,66.892,29.069,66.93,29.087,66.971,29.243,67.097,29.388,67.201,29.572,67.324,29.751,67.426,29.941,67.547,29.988,67.668,29.979,67.689,29.822,67.754,29.524,67.929,29.344,68.062,29.063,68.118,28.685,68.19,28.56,68.351,28.471,68.488,28.479,68.538,28.752,68.771,28.778,68.814,28.773,68.84,28.745,68.856,28.706,68.866,28.454,68.872,28.414,68.904,28.566,68.928,28.692,68.961,28.899,69.01,28.966,69.022,29.119,69.05,29.171,69.072,29.21,69.097,29.353,69.271,29.388,69.298,29.833,69.36,29.994,69.392,30.087,69.433,30.132,69.464,30.164,69.502,30.187,69.543,30.196,69.581,30.16,69.63,30.18,69.636,30.228,69.634,30.38,69.585,30.615,69.533,30.789,69.529,30.861,69.538,30.897,69.561,30.922,69.606,30.924,69.652,30.87,69.783,31.05,69.769,31.453,69.69,31.547,69.697,31.666,69.721,31.789,69.816,31.879,69.832,31.998,69.81,32.031,69.835,31.969,69.914,31.985,69.954,32.392,69.869,32.565,69.806,32.942,69.752,33.008,69.722,33.013,69.671,32.995,69.626,32.915,69.602,32.754,69.606,32.177,69.674,32.092,69.633,32.161,69.597,32.331,69.554,32.378,69.479,32.637,69.489,32.884,69.461,33,69.47,33.021,69.446,32.942,69.383,32.979,69.367,33.256,69.428,33.385,69.444,33.454,69.428,33.464,69.378,33.418,69.315,33.413,69.267,33.328,69.152,33.196,69.117,33.141,69.069,33.333,69.098,33.436,69.13,33.627,69.289,33.684,69.31,34.229,69.313,34.353,69.303,34.864,69.228,35.01,69.221,35.176,69.231,35.233,69.266,35.29,69.275,35.858,69.192,36.618,69.003,37.731,68.692,38.358,68.415,38.43,68.356,38.657,68.322,38.706,68.345,38.832,68.325,39.569,68.072,39.823,68.059,39.79,68.112,39.746,68.162,39.809,68.151,39.896,68.115,40.036,68.015,40.207,67.942,40.381,67.832,40.526,67.79,40.657,67.774,40.766,67.743,40.966,67.713,41.061,67.444,41.134,67.386,41.134,67.267,41.262,67.218,41.359,67.21,41.354,67.121,41.276,66.914,41.189,66.826,40.522,66.447,40.103,66.3,39.289,66.132,38.654,66.069,38.398,66.064,37.901,66.096,37.628,66.13,37.295,66.225,36.984,66.273,36.77,66.294,36.373,66.302,35.513,66.396,35.364,66.429,34.825,66.611,34.61,66.56,34.483,66.55,34.396,66.613,34.431,66.63,34.452,66.651,34.146,66.703,33.894,66.707,33.76,66.751,33.595,66.785,33.523,66.764,33.482,66.765,33.15,66.844,33.002,66.908,32.848,67.022,32.885,67.061,32.93,67.087,32.4,67.153,31.895,67.161,31.983,67.13,32.202,67.113,32.341,67.068,32.501,67.004,32.464,66.916,32.686,66.83,32.857,66.747,32.862,66.721,32.929,66.704,33.181,66.68,33.224,66.604,33.183,66.574,33.217,66.532,33.405,66.484,33.518,66.471,33.656,66.443,33.593,66.385,33.477,66.347,33.361,66.33,33.416,66.316,33.567,66.321,34.113,66.225,34.4,66.128,34.692,65.952,34.786,65.865,34.793,65.816,34.777,65.768,34.735,65.716,34.716,65.664,34.616,65.51,34.544,65.457,34.406,65.396,34.536,65.278,34.671,65.168,34.804,64.986,34.827,64.913,34.833,64.8,34.952,64.756,34.905,64.739,34.858,64.707,34.87,64.56,35.035,64.44,35.284,64.363,35.432,64.347,35.647,64.378,35.802,64.335,36.146,64.189,36.302,64.034,36.365,64.003,36.714,63.945,36.975,63.91,37.373,63.817,37.442,63.813,37.635,63.893,37.968,63.949,38.071,64.026,38.062,64.091,37.977,64.207,37.954,64.32,37.844,64.366,37.741,64.397,37.43,64.374,37.29,64.378,37.184,64.408,37.04,64.489,36.769,64.685,36.624,64.751,36.579,64.791,36.528,64.847,36.535,64.939,36.653,64.935,36.786,64.987,36.883,65.172,37.05,65.196,37.141,65.194,37.528,65.108,38.009,64.879,38.116,64.855,38.228,64.851,38.412,64.857,38.442,64.827,38.541,64.791,38.613,64.787,39.054,64.714,39.567,64.571,39.758,64.577,39.833,64.656,39.849,64.691,40.058,64.771,40.204,64.784,40.408,64.755,40.445,64.779,40.375,64.896,40.281,64.998,40.143,65.063,39.896,65.255,39.798,65.35,39.749,65.448,39.781,65.535,39.817,65.598,40.328,65.752,40.513,65.844,40.692,65.963,40.774,65.988,41.076,66.021,41.476,66.123,41.781,66.259,42.084,66.466,42.211,66.52,42.314,66.515,42.451,66.482,42.602,66.423,42.807,66.411,43.006,66.421,43.233,66.416,43.551,66.321,43.603,66.291,43.653,66.251,43.55,66.173,43.542,66.123,43.624,66.147,43.737,66.158,43.844,66.142,43.944,66.099,44.017,66.05,44.104,66.009,44.132,66.065,44.145,66.113,44.097,66.235,44.221,66.407,44.316,66.482,44.489,66.672,44.437,66.795,44.429,66.938,44.404,67.004,44.292,67.1,44.074,67.167,43.855,67.189,43.782,67.254,43.796,67.33,43.856,67.439,44.036,67.671,44.225,67.996,44.232,68.071,44.214,68.113,44.226,68.154,44.205,68.254,44.169,68.327,43.404,68.609,43.358,68.636,43.333,68.673,43.413,68.682,43.472,68.68,44.048,68.549,44.175,68.542,45.078,68.578,45.519,68.547,45.892,68.48,46.158,68.291,46.43,68.119,46.684,67.97,46.69,67.849,46.429,67.824,46.174,67.818,45.529,67.758,45.374,67.689,44.939,67.477,44.902,67.413,44.939,67.351,45.139,67.285,45.562,67.186,45.753,66.989,45.885,66.891,45.986,66.853,46.084,66.844,46.298,66.843,46.449,66.819,46.492,66.8,46.552,66.819,46.691,66.826,47.496,66.93,47.656,66.976,47.709,67.045,47.768,67.276,47.839,67.356,47.908,67.455,47.883,67.515,47.875,67.584,48.279,67.65,48.654,67.695,48.833,67.681,48.878,67.731,48.763,67.827,48.696,67.874,48.754,67.896,48.841,67.87,48.954,67.854,49.155,67.87,49.931,68.065,50.233,68.175,50.414,68.218,50.699,68.318,50.839,68.35,51.079,68.363,51.336,68.402,51.617,68.476,51.995,68.539,52.056,68.541,52.129,68.532,52.285,68.459,52.227,68.419,52.183,68.374,52.259,68.351,52.322,68.34,52.397,68.352,52.475,68.382,52.67,68.427,52.723,68.484,52.648,68.506,52.55,68.592,52.435,68.61,52.344,68.608,52.684,68.731,53.413,68.913,53.802,68.996,54.186,69.003,54.491,68.992,54.376,68.965,53.874,68.927,53.798,68.907,53.798,68.885,53.92,68.871,53.971,68.844,53.929,68.812,53.891,68.802,53.834,68.709,53.759,68.634,53.918,68.537,53.931,68.436,53.829,68.383,53.69,68.403,53.567,68.367,53.343,68.343,53.293,68.312,53.261,68.267,53.403,68.257,53.515,68.26,53.914,68.231,53.968,68.227,54.099,68.259,54.233,68.266,54.394,68.275,54.476,68.294,54.561,68.273,54.718,68.184,54.861,68.202,54.923,68.374,55.151,68.48,55.418,68.568,55.675,68.576,55.925,68.637,56.044,68.649,56.276,68.624,56.62,68.619,56.909,68.567,57.127,68.554,57.444,68.642,58.173,68.89,58.237,68.834,58.354,68.916,58.919,69.004,59.057,69.006,59.06,68.973,59.11,68.896,59.22,68.85,59.371,68.738,59.298,68.708,59.223,68.691,59.112,68.616,59.099,68.444,59.311,68.4,59.604,68.351,59.726,68.352,59.828,68.38,59.859,68.396,59.897,68.422,59.923,68.471,59.941,68.51,59.865,68.605,59.896,68.706,60.16,68.7,60.489,68.729,60.638,68.787,60.815,68.895,60.934,68.987,60.859,69.146,60.665,69.11,60.337,69.457,60.171,69.591,60.276,69.653,60.559,69.692,60.813,69.821,60.909,69.847,61.016,69.851,61.771,69.763,62.631,69.743,63.361,69.675,64.19,69.535,64.592,69.436,64.929,69.325,64.896,69.248,65.032,69.27,65.327,69.201,65.528,69.173,65.736,69.132,65.813,69.077,66.085,69.036,66.366,68.961,66.416,68.948,66.756,68.892,67.002,68.874,67.149,68.754,67.64,68.579,67.731,68.514,68.157,68.404,68.371,68.314,68.504,68.348,68.829,68.567,69.024,68.818,69.141,68.951,68.924,68.956,68.763,68.917,68.66,68.927,68.543,68.967,68.355,69.068,68.117,69.236,68.073,69.421,68.006,69.48,67.774,69.53,67.624,69.584,67.064,69.694,66.964,69.656,66.934,69.597,66.897,69.554,66.84,69.609,66.804,69.659,66.803,69.74,66.832,69.842,66.926,70.014,67.069,70.006,67.144,70.031,67.239,70.108,67.197,70.172,67.146,70.22,67.157,70.295,67.247,70.5,67.285,70.739,67.212,70.798,67.143,70.838,66.822,70.797,66.702,70.819,66.675,70.865,66.666,70.901,66.759,70.962,66.847,71.064,66.693,71.042,66.64,71.081,66.768,71.14,66.918,71.282,67.274,71.348,67.542,71.412,67.959,71.548,68.269,71.683,68.469,71.853,68.607,72.013,68.83,72.392,69.039,72.67,69.391,72.956,69.612,72.982,69.694,72.978,69.709,72.956,69.659,72.932,69.645,72.898,69.738,72.885,69.888,72.883,70.172,72.901,70.655,72.89,71.5,72.914,71.617,72.902,71.93,72.82,72.101,72.829,72.446,72.79,72.634,72.744,72.812,72.691,72.787,72.483,72.753,72.343,72.624,72.079,72.574,72.013,72.375,71.822,72.279,71.696,72.13,71.609,71.912,71.548,71.884,71.511,71.867,71.457,72.079,71.307,72.581,71.151,72.704,70.963,72.732,70.823,72.7,70.457,72.653,70.403,72.562,70.346,72.469,70.275,72.53,70.173,72.599,69.793,72.616,69.484,72.557,69.378,72.527,69.154,72.527,69.081,72.577,68.969,72.678,68.875,72.812,68.815,73.191,68.707,73.548,68.575,73.573,68.533,73.592,68.482,73.465,68.431,73.266,68.294,73.139,68.181,73.129,68.091,73.173,67.973,73.152,67.865,73.067,67.767,72.949,67.696,72.594,67.587,71.847,67.008,71.668,66.94,71.365,66.962,71.449,66.879,71.551,66.76,71.54,66.683,71.342,66.687,71.066,66.604,70.939,66.548,70.725,66.519,70.561,66.549,70.383,66.602,70.409,66.648,70.443,66.668,70.568,66.701,70.691,66.745,70.631,66.754,70.579,66.754,70.444,66.697,70.283,66.686,70.094,66.754,69.949,66.83,69.877,66.845,69.74,66.815,69.218,66.829,69.079,66.816,69.013,66.788,69.051,66.766,69.091,66.724,69.144,66.641,69.194,66.579,69.412,66.511,69.701,66.485,69.982,66.401,70.339,66.342,71.146,66.367,71.358,66.359,71.566,66.334,71.917,66.247,72.068,66.253,72.322,66.332,72.384,66.507,72.417,66.561,73.342,66.807,73.514,66.861,73.792,66.995,73.883,67.085,73.986,67.328,74.075,67.414,74.676,67.695,74.77,67.766,74.787,67.898,74.778,67.986,74.743,68.074,74.632,68.218,74.511,68.303,74.391,68.421,74.481,68.659,74.58,68.751,75.125,68.862,75.59,68.901,76.108,68.976,76.316,68.992,76.459,68.978,76.606,68.898,76.735,68.777,77.112,68.596,77.238,68.47,77.261,68.316,77.248,67.941,77.174,67.779,77.325,67.736,77.396,67.699,77.579,67.644,77.675,67.59,77.772,67.57,77.986,67.559,78.59,67.578,78.922,67.589,78.888,67.613,78.839,67.631,78.559,67.639,78.161,67.678,77.588,67.752,77.52,67.91,77.536,68.008,77.665,68.19,77.757,68.222,77.868,68.235,77.995,68.259,77.959,68.377,77.907,68.482,77.785,68.63,77.651,68.903,77.466,68.905,77.328,68.959,76.645,69.117,76.001,69.235,75.561,69.252,75.42,69.239,75.054,69.116,74.815,69.091,74.363,69.145,73.977,69.115,73.836,69.143,73.776,69.198,73.891,69.418,73.833,69.504,73.663,69.617,73.56,69.707,73.578,69.803,73.83,70.176,73.937,70.273,74.207,70.445,74.343,70.579,74.311,70.654,73.732,71.069,73.577,71.217,73.507,71.264,73.365,71.32,73.15,71.385,73.086,71.445,73.672,71.845,73.939,71.915,74.311,71.958,74.489,71.997,74.804,72.077,74.992,72.145,75.053,72.199,75.09,72.263,75.097,72.421,75.06,72.549,75.008,72.619,74.897,72.71,74.787,72.812,74.865,72.838,74.942,72.854,75.152,72.853,75.369,72.797,75.475,72.685,75.604,72.581,75.603,72.512,75.591,72.457,75.644,72.382,75.691,72.35,75.741,72.296,75.694,72.254,75.644,72.232,75.55,72.171,75.395,71.983,75.274,71.959,75.247,71.813,75.503,71.655,75.469,71.534,75.417,71.495,75.28,71.43,75.298,71.378,75.332,71.342,75.734,71.266,76.11,71.219,76.742,71.202,76.929,71.128,76.995,71.181,77.59,71.168,78.068,70.986,78.321,70.93,78.526,70.912,78.942,70.934,79.015,70.95,79.084,71.002,78.889,70.997,78.804,70.974,78.724,70.976,78.588,70.994,78.491,71.025,78.387,71.087,78.213,71.266,77.908,71.324,77.707,71.301,77.481,71.312,77.114,71.409,76.871,71.447,76.433,71.552,76.312,71.595,76.216,71.683,76.104,71.829,76.032,71.91,76.124,71.927,76.422,72.006,76.871,72.033,77.061,72.004,77.551,71.842,77.778,71.836,78.187,71.907,78.232,71.952,78.141,72.045,78.016,72.092,77.781,72.114,77.493,72.072,77.411,72.108,77.44,72.157,77.472,72.192,77.625,72.201,77.733,72.229,77.968,72.329,78.225,72.377,78.483,72.395,79.422,72.381,79.954,72.223,80.474,72.153,80.699,72.098,80.763,72.089,80.815,72.054,80.856,71.97,81.511,71.746,81.662,71.716,82.08,71.707,82.547,71.759,82.758,71.764,82.986,71.749,83.107,71.721,83.234,71.668,83.166,71.602,83.106,71.562,82.977,71.451,82.918,71.42,82.493,71.293,82.323,71.26,82.277,71.093,82.254,71.056,82.239,70.998,82.316,70.879,82.336,70.807,82.271,70.707,82.163,70.598,82.182,70.511,82.221,70.396,82.236,70.43,82.231,70.483,82.258,70.544,82.452,70.69,82.592,70.89,82.738,70.942,82.869,70.955,83.01,70.895,83.051,70.815,83.058,70.695,83.03,70.581,82.92,70.407,82.742,70.286,82.682,70.218,82.767,70.154,82.857,70.105,82.961,70.088,83.081,70.093,83.11,70.11,83.132,70.157,83.094,70.221,83.074,70.277,83.293,70.321,83.497,70.345,83.66,70.418,83.7,70.466,83.736,70.546,83.651,70.672,83.579,70.766,83.334,70.989,83.151,71.104,83.266,71.276,83.458,71.468,83.531,71.514,83.55,71.544,83.571,71.594,83.554,71.65,83.534,71.684,83.34,71.828,83.2,71.875,82.755,71.903,82.645,71.925,82.319,72.072,82.281,72.105,82.209,72.211,82.184,72.238,82.094,72.265,81.793,72.327,81.586,72.352,81.283,72.359,81.098,72.39,80.827,72.488,80.798,72.52,80.72,72.648,80.656,72.712,80.675,72.759,80.774,72.861,80.842,72.949,80.757,73.025,80.639,73.049,80.51,73.086,80.455,73.155,80.425,73.231,80.419,73.29,80.398,73.357,80.458,73.414,80.596,73.474,80.562,73.515,80.583,73.568,81.469,73.64,81.817,73.659,83.545,73.667,83.667,73.686,84.417,73.722,84.738,73.763,85.077,73.72,85.201,73.722,85.448,73.735,85.611,73.822,85.979,73.857,86.591,73.894,86.893,73.887,86.961,73.861,87.029,73.824,86.698,73.717,86.366,73.62,86.094,73.578,85.827,73.493,85.8,73.459,85.793,73.438,85.802,73.372,85.818,73.327,86.098,73.273,86.308,73.196,86.514,73.14,86.677,73.107,86.715,73.126,86.122,73.307,85.971,73.347,85.91,73.39,85.939,73.456,85.999,73.486,86.092,73.519,86.155,73.535,86.376,73.569,87.12,73.615,87.294,73.705,87.37,73.756,87.571,73.811,87.503,73.832,87.338,73.846,87.21,73.879,86.697,74.195,86.571,74.244,86.178,74.279,86.001,74.316,86.183,74.423,86.396,74.45,86.538,74.444,86.665,74.414,86.898,74.325,87.23,74.364,87.106,74.404,86.894,74.45,86.7,74.522,86.426,74.585,86.116,74.629,85.791,74.645,85.881,74.74,86.059,74.728,86.12,74.757,86.201,74.816,86.651,74.682,86.863,74.718,87.042,74.779,87.419,74.941,87.468,75.013,87.287,75.053,87.141,75.072,86.939,75.068,86.922,75.113,87.006,75.17,87.171,75.192,87.671,75.13,88.504,75.29,88.733,75.369,89.31,75.47,89.595,75.458,90.185,75.591,91.005,75.65,91.479,75.65,91.845,75.724,92.408,75.75,92.603,75.779,93.55,75.854,94.075,75.913,94.156,75.959,93.687,75.922,93.574,75.956,93.475,75.933,93.406,75.901,93.178,75.959,93.116,75.945,93.069,75.913,92.987,75.903,92.89,75.91,92.859,75.979,92.972,76.075,93.105,76.026,93.259,76.099,93.36,76.101,93.648,76.054,93.843,76.101,94.102,76.124,94.388,76.103,94.507,76.108,94.576,76.152,95.038,76.114,95.359,76.14,95.579,76.137,95.92,76.113,96.075,76.082,95.986,76.01,95.653,75.892,95.744,75.872,95.935,75.926,96.509,76.006,96.601,75.99,96.538,75.922,96.497,75.891,96.879,75.931,97.205,76.019,97.351,76.033,97.499,75.98,97.638,76.029,97.67,76.078,97.918,76.089,98.02,76.134,98.195,76.166,98.342,76.181,98.662,76.243,98.771,76.224,98.985,76.208,99.187,76.178,99.563,76.109,99.616,76.082,99.663,76.078,99.77,76.029,99.689,75.956,99.602,75.852,99.442,75.803,99.541,75.799,99.609,75.811,99.738,75.881,99.851,75.93,99.825,76.136,99.617,76.24,99.461,76.275,99.094,76.384,98.97,76.431,98.806,76.481,98.869,76.51,99.576,76.471,99.936,76.49,100.322,76.479,100.844,76.525,101.061,76.477,101.311,76.479,101.598,76.439,101.684,76.485,101.213,76.536,101.003,76.531,100.928,76.557,101.006,76.615,101.099,76.704,101.008,76.781,100.92,76.823,100.906,76.901,100.99,76.99,101.186,77.029,101.293,77.102,101.518,77.198,102.61,77.509,103.131,77.626,103.331,77.641,103.561,77.632,104.015,77.73,104.185,77.73,104.814,77.652,104.965,77.595,105.309,77.549,105.71,77.525,105.895,77.489,105.983,77.448,106.06,77.391,105.734,77.352,105.385,77.238,104.912,77.175,104.324,77.133,104.202,77.102,105.32,77.092,105.646,77.101,105.712,77.001,105.822,76.998,106.145,77.045,106.339,77.048,106.705,77.014,106.784,77.032,106.942,77.034,107.279,76.991,107.43,76.927,107.19,76.822,106.941,76.73,106.639,76.573,106.546,76.586,106.385,76.589,106.414,76.512,106.683,76.515,106.825,76.48,107.158,76.524,107.624,76.51,107.722,76.522,107.902,76.57,107.95,76.661,108.028,76.718,108.182,76.738,108.352,76.72,108.638,76.72,109.369,76.749,109.981,76.712,110.471,76.758,111.115,76.723,111.392,76.687,111.601,76.622,111.786,76.604,111.939,76.553,112.094,76.48,112.017,76.421,111.943,76.38,112.143,76.424,112.297,76.435,112.413,76.408,112.62,76.384,112.684,76.219,112.743,76.187,112.798,76.13,112.722,76.077,112.656,76.054,112.819,76.059,113.047,76.114,113.094,76.133,113.15,76.175,113.066,76.215,112.988,76.24,113.086,76.258,113.273,76.252,113.366,76.179,113.428,76.112,113.564,75.892,113.857,75.921,113.871,75.856,113.749,75.705,113.62,75.593,113.568,75.568,113.486,75.564,113.517,75.622,113.469,75.657,113.392,75.678,113.126,75.699,112.629,75.835,112.497,75.85,112.466,75.844,112.453,75.83,112.73,75.738,112.956,75.572,113.162,75.621,113.243,75.611,113.356,75.534,113.559,75.502,113.726,75.451,113.614,75.293,112.925,75.015,112.192,74.853,111.868,74.74,111.299,74.658,110.893,74.548,110.374,74.466,110.226,74.379,109.84,74.322,109.866,74.293,109.911,74.261,109.864,74.209,109.81,74.169,109.511,74.089,109.075,74.032,108.2,73.694,107.765,73.625,107.271,73.621,107.167,73.589,106.794,73.377,106.679,73.331,106.189,73.308,105.677,72.959,105.393,72.841,105.144,72.777,105.403,72.79,105.708,72.837,106.067,72.95,106.16,73.002,106.209,73.061,106.315,73.106,106.478,73.139,107.109,73.177,107.369,73.163,107.75,73.173,108.001,73.236,108.151,73.258,108.285,73.266,108.351,73.31,108.575,73.319,109.09,73.378,109.166,73.4,109.331,73.487,109.637,73.454,109.855,73.472,110.429,73.629,110.773,73.689,110.868,73.731,110.799,73.76,110.722,73.78,110.388,73.726,110.091,73.709,109.753,73.723,109.707,73.744,109.666,73.8,109.774,73.881,109.869,73.931,110.084,73.994,110.261,74.017,110.92,73.948,111.056,73.939,111.131,74.053,111.341,74.047,111.551,74.029,111.46,74.005,111.228,73.969,111.3,73.885,111.4,73.828,111.804,73.745,112.147,73.709,112.4,73.711,112.795,73.746,112.856,73.771,112.94,73.836,112.836,73.962,112.935,73.946,113.033,73.914,113.182,73.837,113.327,73.707,113.416,73.648,113.364,73.583,113.157,73.46,113.277,73.392,113.491,73.346,113.488,73.145,113.475,73.048,113.369,72.942,113.247,72.897,113.128,72.831,113.158,72.769,113.186,72.73,113.312,72.657,113.665,72.635,113.712,72.654,113.63,72.677,113.391,72.711,113.298,72.739,113.216,72.806,113.312,72.878,113.417,72.932,113.543,73.054,113.581,73.142,113.559,73.233,113.639,73.274,113.765,73.318,113.829,73.327,113.886,73.346,113.795,73.367,113.711,73.379,113.539,73.434,113.51,73.505,113.857,73.533,114.061,73.585,114.816,73.607,115.338,73.703,116.496,73.676,117.309,73.599,118.45,73.59,118.871,73.538,118.911,73.518,118.936,73.481,118.754,73.465,118.457,73.464,118.377,73.367,118.43,73.247,118.96,73.117,119.425,73.064,119.75,72.979,119.922,72.971,120.598,72.981,120.997,72.937,121.354,72.971,121.748,72.97,121.886,72.961,122.03,72.897,122.26,72.881,122.538,72.878,122.692,72.891,122.752,72.906,122.731,72.931,122.502,72.971,122.527,73.017,122.615,73.028,122.999,72.965,123.16,72.955,123.301,73.002,123.405,73.086,123.462,73.144,123.522,73.173,123.572,73.177,123.622,73.193,123.501,73.262,123.384,73.347,123.355,73.402,123.323,73.431,123.305,73.533,123.416,73.637,123.491,73.666,123.797,73.627,123.934,73.689,124.019,73.712,124.388,73.755,124.541,73.751,124.796,73.712,125.617,73.521,125.599,73.447,125.794,73.468,125.888,73.498,126.107,73.517,126.254,73.548,126.296,73.537,126.345,73.506,126.309,73.464,126.257,73.42,126.292,73.394,126.335,73.389,126.553,73.335,126.838,73.434,126.955,73.528,127.031,73.547,127.74,73.482,127.955,73.446,127.997,73.426,128.026,73.391,128.142,73.352,128.281,73.331,128.264,73.301,128.258,73.267,128.587,73.262,128.73,73.233,128.889,73.19,128.872,73.139,128.913,73.111,129.059,73.108,129.101,73.112,129.054,73.045,128.854,72.973,128.735,72.943,128.599,72.895,128.674,72.886,129.017,72.872,129.229,72.776,129.25,72.705,129.118,72.677,128.815,72.586,128.633,72.55,128.508,72.547,128.418,72.535,128.549,72.496,129.117,72.486,129.281,72.438,129.412,72.315,129.411,72.166,129.283,72.092,128.935,72.079,128.475,72.246,128.197,72.31,127.803,72.434,127.726,72.413,127.841,72.308,128.027,72.25,128.359,72.088,128.911,71.755,129.04,71.782,129.117,71.825,129.154,71.879,129.122,71.953,129.21,71.917,129.292,71.85,129.461,71.739,129.234,71.745,128.949,71.708,128.843,71.663,128.923,71.602,129.134,71.593,129.225,71.509,129.39,71.405,129.762,71.12,130.026,71.065,130.281,70.947,130.537,70.893,130.668,70.888,130.757,70.962,130.832,70.936,130.898,70.804,131.022,70.746,131.157,70.742,131.268,70.766,131.432,70.828,131.562,70.901,131.769,71.101,131.906,71.203,132.035,71.244,131.991,71.293,132.004,71.35,132.099,71.484,132.228,71.643,132.326,71.726,132.562,71.895,132.654,71.926,132.716,71.871,132.769,71.799,132.804,71.768,132.839,71.755,133.131,71.607,133.426,71.491,133.689,71.434,134.103,71.379,134.703,71.387,134.814,71.461,135.022,71.515,135.359,71.544,135.559,71.61,135.885,71.631,136.09,71.62,136.406,71.571,137.116,71.416,137.315,71.359,137.417,71.299,137.651,71.208,137.798,71.164,137.94,71.133,137.992,71.143,137.974,71.169,137.902,71.194,137.844,71.227,138.013,71.261,138.033,71.286,138.091,71.307,138.314,71.326,138.097,71.359,138.022,71.363,137.918,71.384,137.927,71.43,137.996,71.464,138.048,71.526,138.118,71.566,138.234,71.596,138.318,71.603,138.525,71.563,138.67,71.635,138.78,71.629,139.005,71.556,139.209,71.445,139.32,71.445,139.632,71.489,139.984,71.492,139.939,71.558,139.695,71.7,139.723,71.885,139.552,71.927,139.359,71.951,139.64,71.998,139.847,72.149,140.014,72.162,140.188,72.191,140.134,72.21,139.617,72.226,139.505,72.208,139.43,72.163,139.176,72.163,139.145,72.264,139.141,72.33,139.474,72.467,139.601,72.496,140.451,72.493,140.705,72.519,141.079,72.587,140.983,72.63,140.973,72.717,140.652,72.843,140.676,72.872,140.708,72.89,140.808,72.891,141.31,72.858,141.518,72.789,142.061,72.721,143.516,72.698,143.681,72.673,144.304,72.643,144.569,72.61,145.199,72.57,145.486,72.542,145.714,72.497,146.083,72.471,146.253,72.442,146.235,72.35,145.467,72.362,145.213,72.393,144.897,72.396,144.776,72.382,144.588,72.306,144.361,72.265,144.169,72.259,144.295,72.193,144.471,72.175,145.039,72.26,146.594,72.302,146.832,72.295,146.807,72.237,146.599,72.124,146.402,72.035,146.113,71.945,146.006,71.945,146.23,72.138,146.137,72.146,146.051,72.142,145.799,72.222,145.759,72.226,145.71,72.206,145.71,72.178,145.664,72.067,145.757,72.021,145.757,71.941,145.407,71.89,145.271,71.895,145.126,71.927,145.064,71.926,145.047,71.901,145.078,71.855,145.074,71.831,145.018,71.794,144.99,71.753,145.076,71.707,145.189,71.696,145.805,71.746,146.073,71.808,146.368,71.922,146.895,72.198,147.127,72.292,147.262,72.328,147.434,72.341,148.402,72.312,148.965,72.252,149.502,72.164,149.766,72.091,149.963,71.992,149.998,71.95,150.017,71.896,149.881,71.843,149.28,71.826,149.049,71.796,148.965,71.763,148.955,71.744,148.923,71.715,148.968,71.69,149.238,71.688,149.498,71.664,149.857,71.601,149.913,71.581,150.026,71.521,150.061,71.511,150.6,71.52,150.635,71.499,150.668,71.455,150.525,71.386,150.385,71.339,150.098,71.227,150.243,71.267,150.822,71.363,150.968,71.38,151.145,71.374,151.582,71.287,151.76,71.218,152.093,71.023,152,71.002,151.762,70.982,152.509,70.834,152.798,70.836,153.461,70.879,153.794,70.88,154.414,70.974,155.029,71.034,155.596,71.039,155.895,71.096,156.685,71.094,157.447,71.075,158.037,71.039,158.702,70.935,159.351,70.791,159.728,70.65,159.805,70.605,159.912,70.506,159.959,70.424,160.006,70.31,159.983,70.221,159.89,70.159,159.831,70.081,159.839,69.99,159.729,69.87,159.833,69.785,160.119,69.73,160.739,69.655,160.911,69.606,160.929,69.459,160.982,69.334,161.036,69.098,161.141,69.039,161.31,68.982,161.341,68.905,161.129,68.654,160.997,68.608,160.856,68.538,161.104,68.563,161.23,68.654,161.365,68.823,161.495,68.85,161.566,68.905,161.566,69.064,161.48,69.202,161.48,69.3,161.537,69.38,161.945,69.545,162.166,69.612,162.376,69.649,162.945,69.683,163.201,69.715,163.498,69.693,163.705,69.702,163.946,69.735,164.16,69.719,164.513,69.609,165.761,69.584,165.98,69.546,166.82,69.5,166.884,69.5,167.073,69.554,167.628,69.74,167.857,69.728,167.95,69.699,168.048,69.626,168.15,69.577,168.23,69.447,168.303,69.271,168.423,69.24,168.588,69.228,168.946,69.163,169.311,69.08,169.415,68.92,169.61,68.786,170.066,68.799,170.538,68.825,170.995,69.045,170.997,69.135,170.884,69.264,170.714,69.388,170.582,69.583,170.161,69.627,170.201,69.683,170.36,69.751,170.503,69.857,170.525,69.938,170.487,70.108,170.868,70.096,171.247,70.076,171.971,70,172.56,69.968,172.869,69.92,173.056,69.865,173.277,69.824,173.353,69.924,173.439,69.947,173.733,69.891,173.948,69.874,174.319,69.882,174.786,69.856,175.296,69.86,175.751,69.904,175.921,69.895,176.108,69.86,176.41,69.769,176.924,69.646,177.395,69.612,177.934,69.496,178.443,69.453,178.848,69.387,178.907,69.362,178.925,69.326,178.951,69.296,179.273,69.26,179.868,69.013,180,68.983,180,65.067,179.827,65.034,179.651,64.921,179.448,64.822,179.15,64.782,178.698,64.631,178.52,64.603,178.285,64.672,177.749,64.717,177.582,64.778,177.337,64.931,177.252,64.954,177.179,65.014,176.881,65.082,176.625,65.038,176.413,65.071,176.341,65.047,176.452,65.025,176.646,65.007,176.94,65.016,177.037,65,177.123,64.947,177.223,64.862,177.148,64.805,177.069,64.787,176.831,64.849,176.557,64.84,176.429,64.855,176.061,64.961,175.781,64.844,175.396,64.784,175.098,64.777,174.549,64.684,174.699,64.681,175.097,64.747,175.331,64.747,175.678,64.782,175.859,64.825,175.946,64.865,176.057,64.905,176.169,64.885,176.247,64.843,176.301,64.784,176.351,64.705,176.283,64.664,176.219,64.642,176.141,64.586,176.508,64.682,176.731,64.625,176.843,64.634,177.05,64.719,177.388,64.774,177.427,64.763,177.467,64.737,177.41,64.573,177.433,64.444,177.688,64.305,177.953,64.222,178.045,64.22,178.131,64.235,178.164,64.309,178.229,64.364,178.313,64.314,178.381,64.261,178.477,64.128,178.475,64.089,178.451,64.011,178.536,63.976,178.65,63.965,178.692,63.842,178.731,63.667,178.681,63.651,178.626,63.651,178.44,63.606,178.466,63.574,178.654,63.557,178.706,63.522,178.669,63.44,178.679,63.402,178.744,63.395,178.787,63.442,178.775,63.51,178.793,63.54,178.919,63.4,178.921,63.345,179.028,63.282,179.332,63.19,179.389,63.147,179.405,63.078,179.329,63.058,179.26,63.008,179.302,62.94,179.381,62.884,179.511,62.863,179.571,62.773,179.571,62.688,179.477,62.613,179.289,62.51,179.177,62.469,179.134,62.396,179.121,62.32,179.045,62.324,178.964,62.355,178.019,62.547,177.663,62.583,177.351,62.587,177.293,62.599,177.296,62.644,177.316,62.685,177.36,62.737,177.339,62.781,177.298,62.784,177.259,62.75,177.173,62.75,177.091,62.79,177.024,62.777,176.99,62.722,176.963,62.693,176.965,62.659,177.008,62.627,177.19,62.592,177.159,62.561,176.907,62.536,176.703,62.506,176.437,62.411,176.328,62.346,175.614,62.184,175.442,62.128,175.366,62.121,175.268,62.102,175.192,62.034,174.798,61.939,174.715,61.948,174.611,61.868,174.514,61.824,174.285,61.818,174.139,61.795,173.822,61.679,173.623,61.716,173.391,61.557,173.132,61.407,173.055,61.406,172.857,61.469,172.807,61.436,172.838,61.376,172.908,61.312,172.868,61.293,172.789,61.311,172.731,61.314,172.69,61.295,172.697,61.249,172.585,61.19,172.497,61.186,172.396,61.167,172.362,61.117,172.393,61.062,172.213,60.998,172.067,60.916,171.998,60.901,171.918,60.864,171.831,60.837,171.729,60.843,171.49,60.726,170.949,60.523,170.799,60.496,170.608,60.435,170.59,60.394,170.589,60.343,170.512,60.26,170.423,60.048,170.396,60.01,170.351,59.966,170.154,59.986,169.983,60.067,169.927,60.104,169.898,60.148,169.887,60.218,169.854,60.25,169.815,60.265,169.618,60.438,169.276,60.557,169.227,60.596,168.788,60.564,168.67,60.563,168.463,60.592,168.138,60.574,167.746,60.509,167.626,60.469,167.227,60.406,166.964,60.307,166.453,59.947,166.332,59.872,166.273,59.856,166.187,59.849,166.149,59.922,166.136,59.979,166.168,60.089,166.23,60.178,166.292,60.346,166.308,60.414,166.352,60.485,166.18,60.48,165.942,60.357,165.583,60.236,165.416,60.205,165.285,60.135,165.193,60.125,165.085,60.099,165.074,59.946,165.019,59.861,164.954,59.844,164.854,59.841,164.779,59.874,164.67,59.997,164.525,60.061,164.44,60.073,164.377,60.058,164.252,59.974,164.113,59.898,164.135,59.984,164.018,60.017,163.913,60.037,163.78,60.041,163.744,60.028,163.69,59.978,163.574,59.914,163.494,59.887,163.41,59.835,163.365,59.781,163.321,59.705,163.269,59.52,163.273,59.303,163.085,59.131,163.01,59.148,162.975,59.137,162.94,59.114,163.004,59.02,162.97,58.986,162.935,58.964,162.847,58.939,162.643,58.8,162.453,58.709,162.142,58.447,162.049,58.273,161.96,58.077,162.002,57.981,162.04,57.918,162.098,57.875,162.197,57.829,162.411,57.778,162.392,57.745,162.391,57.717,162.467,57.766,162.522,57.904,162.654,57.948,162.718,57.946,163.145,57.837,163.226,57.79,163.214,57.687,163.188,57.637,163.109,57.565,162.957,57.477,162.779,57.358,162.762,57.284,162.762,57.244,162.808,57.103,162.815,57.023,162.791,56.875,162.803,56.811,162.85,56.757,162.922,56.723,163.046,56.741,163.165,56.725,163.257,56.688,163.243,56.565,163.294,56.448,163.336,56.233,163.261,56.174,163.189,56.137,163.047,56.045,162.972,56.034,162.84,56.066,162.628,56.232,162.713,56.331,162.893,56.399,162.975,56.449,163.038,56.522,162.944,56.508,162.878,56.476,162.671,56.49,162.589,56.455,162.489,56.399,162.528,56.261,162.461,56.235,162.334,56.188,162.146,56.128,162.085,56.09,161.924,55.84,161.776,55.655,161.724,55.496,161.729,55.358,161.785,55.205,161.824,55.139,161.996,54.998,162.08,54.886,162.106,54.752,161.967,54.689,161.726,54.533,161.625,54.516,161.294,54.521,161.13,54.598,160.936,54.578,160.773,54.541,160.517,54.431,160.289,54.288,160.074,54.189,160.01,54.131,159.922,54.008,159.844,53.784,159.871,53.673,159.914,53.621,159.956,53.552,159.899,53.448,159.898,53.381,160.002,53.275,160.025,53.13,159.947,53.125,159.772,53.23,159.586,53.238,159.136,53.117,158.952,53.048,158.745,52.909,158.684,52.935,158.64,53.015,158.565,53.05,158.472,53.032,158.432,52.957,158.56,52.922,158.609,52.874,158.534,52.688,158.481,52.627,158.5,52.46,158.493,52.383,158.463,52.305,158.332,52.091,158.104,51.81,157.823,51.605,157.629,51.535,157.531,51.48,157.49,51.409,157.202,51.213,156.847,51.007,156.748,50.969,156.724,51.047,156.713,51.124,156.671,51.227,156.543,51.312,156.521,51.38,156.5,51.475,156.49,51.913,156.377,52.367,156.365,52.509,156.229,52.626,156.154,52.747,156.11,52.866,156.099,53.006,155.95,53.744,155.905,53.928,155.706,54.521,155.62,54.865,155.564,55.199,155.555,55.348,155.643,55.794,155.717,56.072,155.983,56.695,156.025,56.752,156.067,56.782,156.529,57.021,156.728,57.152,156.849,57.29,156.977,57.466,156.964,57.561,156.948,57.616,156.9,57.677,156.792,57.748,156.83,57.78,156.872,57.804,156.986,57.83,157.217,57.777,157.45,57.799,157.666,58.02,157.975,57.986,158.21,58.025,158.275,58.009,158.321,58.083,158.449,58.163,158.687,58.281,159.037,58.424,159.211,58.519,159.308,58.611,159.453,58.696,159.592,58.804,159.847,59.127,160.35,59.394,160.547,59.547,160.711,59.602,160.855,59.627,161.219,59.846,161.449,60.027,161.754,60.152,161.846,60.232,162.004,60.42,162.068,60.466,162.266,60.537,162.713,60.659,162.973,60.783,163.352,60.8,163.466,60.85,163.585,60.877,163.71,60.917,163.554,61.026,163.589,61.084,163.62,61.111,163.893,61.24,164.005,61.344,163.992,61.388,163.973,61.42,163.804,61.461,163.837,61.558,163.883,61.64,164.02,61.711,164.068,61.874,164.074,62.045,164.207,62.292,164.288,62.347,164.598,62.471,164.671,62.474,164.888,62.432,165.124,62.412,165.208,62.374,165.226,62.406,165.214,62.448,165.28,62.463,165.417,62.447,165.397,62.494,165.044,62.517,164.792,62.571,164.567,62.675,164.418,62.705,164.256,62.697,163.332,62.551,163.287,62.511,163.244,62.455,163.302,62.373,163.258,62.337,163.213,62.313,163.163,62.26,163.118,62.153,163.131,62.05,163.018,61.891,163.009,61.792,163.208,61.737,163.258,61.699,163.198,61.645,163.139,61.611,163.085,61.571,163.047,61.554,162.994,61.544,162.922,61.598,162.856,61.705,162.752,61.711,162.718,61.695,162.699,61.653,162.608,61.65,162.506,61.67,162.393,61.662,162.188,61.541,161.037,60.963,160.915,60.893,160.767,60.753,160.482,60.74,160.368,60.709,160.287,60.667,160.174,60.638,160.177,60.691,160.201,60.73,160.226,60.832,160.379,61.025,160.281,61.045,160.184,61.048,160.004,61.007,159.883,60.943,159.79,60.957,159.835,61.014,159.949,61.129,159.914,61.234,159.883,61.292,159.931,61.324,160.163,61.538,160.247,61.648,160.317,61.793,160.321,61.839,160.309,61.894,160.238,61.904,160.183,61.903,159.722,61.758,159.552,61.719,159.496,61.781,159.423,61.808,159.295,61.914,159.189,61.929,159.077,61.922,158.824,61.85,158.547,61.811,158.334,61.826,158.152,61.765,158.07,61.754,157.799,61.795,157.469,61.799,157.371,61.747,157.084,61.676,156.892,61.565,156.791,61.53,156.68,61.481,156.63,61.272,156.483,61.206,156.344,61.155,156.056,60.996,155.853,60.777,155.716,60.682,155.428,60.55,154.971,60.377,154.578,60.095,154.441,59.884,154.39,59.877,154.293,59.833,154.267,59.73,154.269,59.658,154.209,59.6,154.15,59.529,154.213,59.483,154.272,59.475,154.358,59.481,154.583,59.54,154.971,59.45,155.167,59.36,155.153,59.27,155.16,59.19,155.017,59.196,154.824,59.188,154.704,59.141,154.458,59.217,154.376,59.188,154.247,59.109,154.011,59.076,153.892,59.114,153.695,59.225,153.361,59.215,153.273,59.091,153.196,59.094,153.078,59.082,152.882,58.939,152.818,58.926,152.576,58.954,152.401,59.026,152.32,59.031,152.165,58.997,152.088,58.91,151.705,58.867,151.327,58.875,151.121,59.083,151.505,59.164,151.733,59.147,151.99,59.16,152.261,59.224,152.17,59.278,152.104,59.291,151.942,59.284,151.798,59.323,151.486,59.524,151.348,59.561,151.17,59.583,151.034,59.586,150.983,59.571,150.912,59.523,150.863,59.475,150.823,59.461,150.729,59.469,150.615,59.507,150.484,59.494,150.54,59.525,150.667,59.556,150.457,59.591,150.326,59.639,150.203,59.651,149.643,59.77,149.425,59.761,149.29,59.728,149.065,59.631,149.128,59.559,149.175,59.527,149.205,59.488,149.133,59.481,148.925,59.475,148.797,59.532,148.709,59.449,148.744,59.374,148.89,59.4,148.965,59.369,148.914,59.283,148.727,59.258,148.491,59.262,148.257,59.414,147.875,59.388,147.688,59.291,147.514,59.269,147.04,59.366,146.804,59.373,146.537,59.457,146.444,59.43,146.273,59.221,146.05,59.171,145.932,59.198,145.829,59.33,145.756,59.374,145.555,59.414,144.483,59.376,144.123,59.408,143.869,59.411,143.524,59.344,143.192,59.37,142.58,59.24,142.33,59.153,142.025,59,141.755,58.745,141.603,58.649,141.347,58.528,140.988,58.417,140.79,58.303,140.685,58.212,140.495,57.865,140.447,57.814,140.002,57.688,139.862,57.549,139.803,57.514,139.619,57.456,139.507,57.358,139.444,57.33,139.182,57.262,138.966,57.088,138.662,56.966,138.218,56.629,138.18,56.589,138.141,56.499,138.074,56.433,137.692,56.139,137.573,56.112,137.384,55.975,137.19,55.892,137.012,55.795,136.794,55.694,136.46,55.577,136.351,55.51,136.175,55.352,135.751,55.161,135.541,55.114,135.263,54.943,135.235,54.903,135.212,54.841,135.258,54.731,135.325,54.707,135.438,54.692,135.852,54.584,136.238,54.614,136.58,54.614,136.715,54.624,136.797,54.621,136.824,54.561,136.82,54.452,136.77,54.353,136.729,54.061,136.683,53.931,136.719,53.804,136.803,53.782,136.886,53.839,137.019,53.848,137.155,53.822,137.258,54.025,137.172,54.057,137.096,54.129,137.142,54.182,137.378,54.282,137.525,54.291,137.666,54.283,137.513,54.156,137.451,54.13,137.403,54.124,137.339,54.101,137.476,54.028,137.623,53.97,137.835,53.947,137.786,53.903,137.645,53.866,137.517,53.707,137.314,53.632,137.221,53.579,137.254,53.546,137.328,53.539,137.738,53.56,137.95,53.604,138.253,53.726,138.379,53.909,138.494,53.96,138.528,53.96,138.568,53.947,138.569,53.819,138.407,53.674,138.292,53.592,138.25,53.524,138.32,53.523,138.451,53.537,138.511,53.57,138.661,53.745,138.699,53.87,138.722,54.044,138.705,54.148,138.716,54.223,138.657,54.298,138.696,54.32,139.105,54.218,139.32,54.193,139.707,54.277,139.796,54.256,139.858,54.205,140.179,54.052,140.242,54.001,140.347,53.813,140.688,53.596,141.006,53.495,141.015,53.454,141.218,53.334,141.374,53.293,141.402,53.184,141.328,53.097,141.181,53.015,140.887,53.092,140.84,53.088,140.875,53.04,141.087,52.898,141.256,52.84,141.266,52.653,141.245,52.55,141.132,52.436,141.17,52.368,141.33,52.271,141.409,52.234,141.485,52.179,141.386,52.057,141.367,51.921,141.258,51.861,141.129,51.728,140.933,51.62,140.839,51.414,140.688,51.232,140.671,51.051,140.646,50.987,140.521,50.8,140.476,50.546,140.535,50.131,140.564,50.107,140.625,50.082,140.613,50.054,140.585,50.033,140.463,49.911,140.465,49.826,140.511,49.762,140.517,49.596,140.431,49.331,140.399,49.29,140.364,49.221,140.349,49.159,140.326,49.12,140.309,49.054,140.334,48.995,140.378,48.964,140.224,48.773,140.171,48.524,140.113,48.423,139.998,48.324,139.761,48.181,139.676,48.09,139.521,47.975,139.373,47.887,139.167,47.635,139.001,47.383,138.587,47.057,138.53,46.976,138.5,46.89,138.392,46.745,138.337,46.543,138.21,46.463,138.106,46.251,137.769,45.929,137.685,45.818,137.425,45.64,137.147,45.394,136.804,45.171,136.737,45.08,136.604,44.978,136.46,44.822,136.251,44.667,136.209,44.562,136.142,44.489,135.987,44.44,135.875,44.374,135.533,43.971,135.489,43.899,135.483,43.835,135.26,43.685,135.131,43.526,134.917,43.427,134.692,43.291,134.156,43.042,134.01,42.947,133.709,42.83,133.587,42.828,133.329,42.764,133.16,42.697,133.059,42.723,132.997,42.808,132.924,42.805,132.864,42.794,132.709,42.876,132.576,42.872,132.481,42.91,132.304,42.883,132.334,43.239,132.31,43.314,132.233,43.245,132.029,43.119,131.947,43.095,131.867,43.095,131.898,43.171,132.013,43.28,131.976,43.296,131.939,43.302,131.795,43.255,131.722,43.203,131.516,42.996,131.393,42.822,131.292,42.772,131.245,42.697,131.158,42.626,131.025,42.645,130.946,42.634,130.756,42.673,130.709,42.656,130.834,42.523,130.73,42.326,130.687,42.303]],[[47.442,80.854,47.9,80.813,48.243,80.823,48.345,80.819,48.446,80.806,48.547,80.779,48.687,80.718,48.684,80.633,48.625,80.629,48.044,80.668,47.777,80.756,47.705,80.765,47.6,80.742,47.512,80.688,47.414,80.675,47.304,80.606,47.198,80.615,47.145,80.609,47.011,80.562,46.678,80.561,46.624,80.541,46.514,80.476,46.378,80.457,46.141,80.447,46.06,80.484,46.024,80.541,45.969,80.569,45.641,80.537,45.389,80.56,45.149,80.599,44.905,80.611,45.125,80.652,46.327,80.735,46.799,80.755,47.021,80.814,47.352,80.853,47.442,80.854]],[[50.278,80.927,50.431,80.911,50.801,80.914,50.918,80.89,51.455,80.745,51.591,80.741,51.704,80.688,51.146,80.604,50.961,80.54,50.28,80.527,49.846,80.498,49.75,80.472,49.794,80.425,49.586,80.377,48.896,80.369,48.811,80.354,48.677,80.3,48.689,80.29,48.922,80.277,48.96,80.266,48.991,80.242,49.011,80.207,48.978,80.163,48.892,80.155,48.797,80.161,48.582,80.195,48.555,80.183,48.533,80.158,48.467,80.11,48.386,80.096,48.167,80.133,48.096,80.122,48.026,80.099,47.94,80.089,47.737,80.082,47.632,80.112,47.723,80.151,47.978,80.213,47.893,80.239,47.642,80.245,47.444,80.23,47.343,80.189,47.249,80.18,46.991,80.183,46.846,80.237,46.738,80.258,46.644,80.3,47.403,80.445,47.656,80.501,47.896,80.529,48.208,80.544,48.306,80.562,48.403,80.569,48.465,80.558,48.625,80.508,49.088,80.516,49.185,80.559,49.193,80.656,49.147,80.712,49.244,80.821,49.508,80.865,50.124,80.924,50.278,80.927]],[[67.765,76.238,67.365,76.161,67.127,76.108,66.893,76.072,66.657,76.047,66.282,75.984,65.619,75.905,65.202,75.839,64.745,75.788,64.263,75.72,63.779,75.673,63.659,75.669,63.317,75.603,63.046,75.576,62.066,75.428,61.616,75.32,61.487,75.311,61.356,75.315,61.249,75.281,61.147,75.223,60.936,75.164,60.829,75.111,60.719,75.069,60.655,75.055,60.534,75.059,60.476,75.055,60.277,75.008,60.241,74.971,60.455,74.946,60.501,74.905,60.439,74.875,60.301,74.837,60.222,74.797,60.08,74.756,59.982,74.745,59.747,74.746,59.735,74.695,59.771,74.664,59.753,74.637,59.674,74.61,59.596,74.614,59.24,74.693,59.182,74.666,59.157,74.611,59.146,74.552,59.101,74.508,59.04,74.486,58.928,74.463,58.535,74.499,58.502,74.464,58.562,74.422,58.646,74.328,58.665,74.289,58.618,74.227,58.441,74.129,57.767,74.014,57.778,73.974,57.853,73.898,57.872,73.85,57.845,73.805,57.756,73.769,57.657,73.768,57.604,73.775,57.449,73.826,57.313,73.838,57.291,73.815,57.464,73.746,57.543,73.658,57.46,73.61,57.134,73.504,56.964,73.367,56.634,73.304,56.43,73.297,56.228,73.314,56.035,73.346,55.549,73.357,55.28,73.392,55.007,73.454,54.769,73.449,54.566,73.419,54.3,73.351,54.132,73.481,54.205,73.542,53.839,73.697,53.763,73.766,53.851,73.801,53.963,73.822,54.174,73.886,54.386,73.936,54.606,73.951,54.643,73.96,54.733,74.034,54.831,74.096,54.92,74.129,55.023,74.187,55.341,74.42,55.416,74.436,56.078,74.481,56.137,74.496,55.947,74.542,55.752,74.541,55.662,74.556,55.61,74.591,55.582,74.628,55.66,74.656,55.914,74.796,56.218,74.898,56.499,74.957,56.429,74.973,56.34,75.013,55.998,75.003,55.863,75.059,55.821,75.091,55.81,75.125,55.921,75.168,56.036,75.194,56.162,75.187,56.289,75.164,56.389,75.138,56.485,75.096,56.57,75.098,56.876,75.244,56.829,75.278,56.809,75.328,56.844,75.351,56.989,75.375,57.088,75.384,57.302,75.373,57.607,75.341,57.632,75.356,57.708,75.454,57.783,75.507,58.094,75.593,58.073,75.619,58.058,75.663,58.418,75.72,58.653,75.777,58.881,75.855,58.995,75.872,59.11,75.874,59.347,75.907,59.782,75.946,60.036,75.984,60.118,76.067,60.279,76.096,60.606,76.109,60.731,76.104,60.801,76.069,60.942,76.071,60.998,76.089,61.054,76.12,61.037,76.169,61.034,76.233,61.157,76.274,61.202,76.282,61.569,76.298,61.787,76.291,62.237,76.242,62.471,76.23,62.782,76.245,62.971,76.237,63.526,76.31,64.463,76.378,64.708,76.426,64.95,76.484,65.073,76.497,65.197,76.5,65.31,76.518,65.528,76.568,65.637,76.579,65.755,76.579,65.863,76.613,65.959,76.688,66.063,76.746,66.345,76.821,66.829,76.924,67.264,76.964,67.535,77.008,67.652,77.012,68.017,76.991,68.486,76.934,68.699,76.871,68.873,76.79,68.912,76.761,68.942,76.708,68.891,76.66,68.858,76.61,68.9,76.573,68.559,76.449,68.222,76.313,68.165,76.285,67.765,76.238]],[[55.32,73.308,55.787,73.269,56.138,73.256,56.35,73.226,56.43,73.201,56.397,73.139,56.335,73.114,56.189,73.033,56.167,72.983,56.193,72.905,56.171,72.848,56.122,72.807,56.084,72.789,55.82,72.79,55.723,72.766,55.718,72.722,55.701,72.672,55.616,72.599,55.441,72.575,55.403,72.549,55.417,72.501,55.356,72.465,55.36,72.409,55.39,72.378,55.399,72.314,55.518,72.221,55.495,72.182,55.403,72.107,55.375,72.015,55.298,71.935,55.471,71.869,55.547,71.783,55.614,71.69,55.819,71.508,56.043,71.346,56.454,71.107,56.895,70.927,57.066,70.876,57.484,70.792,57.556,70.766,57.625,70.729,57.447,70.661,57.264,70.636,57.247,70.605,57.146,70.589,56.649,70.647,56.622,70.655,56.569,70.697,56.51,70.729,56.386,70.734,56.26,70.715,56.335,70.677,56.417,70.665,56.561,70.594,56.5,70.566,56.435,70.563,56.142,70.658,56.115,70.646,56.087,70.618,55.942,70.649,55.907,70.626,55.797,70.616,55.707,70.642,55.706,70.675,55.687,70.692,55.237,70.666,55.052,70.667,54.867,70.678,54.645,70.742,54.608,70.713,54.601,70.68,54.517,70.693,54.333,70.745,54.199,70.765,53.722,70.814,53.384,70.874,53.468,70.901,53.614,70.915,53.616,70.951,53.593,71.001,53.588,71.052,53.671,71.087,53.857,71.07,53.834,71.127,53.922,71.138,54.094,71.105,54.156,71.125,53.886,71.196,53.591,71.297,53.622,71.333,53.515,71.343,53.41,71.34,53.319,71.399,53.333,71.477,53.412,71.53,53.364,71.542,52.909,71.495,52.679,71.506,52.419,71.537,52.18,71.49,51.938,71.475,51.813,71.491,51.692,71.525,51.59,71.571,51.511,71.648,51.439,71.777,51.429,71.826,51.444,71.934,51.482,71.98,51.583,72.071,51.653,72.099,51.805,72.142,51.885,72.153,52.069,72.131,52.252,72.13,52.332,72.154,52.407,72.197,52.462,72.252,52.586,72.284,52.622,72.301,52.662,72.337,52.706,72.391,52.714,72.437,52.749,72.483,52.864,72.55,52.823,72.591,52.839,72.619,52.917,72.669,52.683,72.682,52.605,72.704,52.529,72.737,52.551,72.769,52.579,72.791,52.812,72.875,52.913,72.9,53.024,72.914,53.135,72.913,53.254,72.904,53.37,72.917,53.247,72.973,53.237,73.011,53.189,73.104,53.198,73.148,53.251,73.183,53.358,73.225,53.512,73.238,53.634,73.26,53.753,73.293,53.866,73.299,54.091,73.276,54.202,73.281,54.328,73.299,54.676,73.37,54.804,73.388,54.941,73.383,55.121,73.357,55.32,73.308]],[[96.527,81.076,96.563,81.03,96.693,80.994,96.755,80.958,97.414,80.842,97.703,80.827,97.832,80.798,97.87,80.763,97.856,80.698,97.747,80.699,97.665,80.678,97.221,80.652,97.113,80.614,97.025,80.536,97.073,80.52,97.115,80.497,97.25,80.363,97.287,80.343,97.417,80.323,97.298,80.273,97.175,80.241,95.856,80.177,94.961,80.15,94.661,80.123,94.565,80.126,94.328,80.076,93.872,80.01,93.655,80.01,93.002,80.102,92.202,80.179,92.092,80.223,91.892,80.249,91.637,80.27,91.524,80.359,91.688,80.419,91.897,80.478,92.247,80.499,92.578,80.533,92.827,80.619,92.981,80.703,93.263,80.791,92.773,80.769,92.593,80.781,92.61,80.81,92.71,80.872,92.765,80.893,92.939,80.926,93.065,80.988,93.359,81.032,93.497,81.039,93.637,81.038,93.889,81.058,94.14,81.089,94.375,81.107,94.612,81.115,94.838,81.139,95.061,81.188,95.16,81.271,95.801,81.28,95.902,81.261,95.984,81.211,96.075,81.193,96.187,81.184,96.471,81.099,96.527,81.076]],[[97.675,80.158,97.904,80.095,98.018,80.023,97.907,80.004,97.808,79.956,97.76,79.896,97.627,79.85,97.591,79.775,97.652,79.761,97.725,79.781,97.871,79.853,98.065,79.901,98.273,79.874,98.353,79.884,98.499,79.953,98.472,80.009,98.532,80.044,98.596,80.052,98.866,80.045,99.295,80.016,99.371,79.986,99.473,79.97,99.536,79.941,99.727,79.92,99.818,79.898,99.947,79.849,100.061,79.777,99.916,79.738,99.839,79.669,99.805,79.653,99.782,79.628,99.771,79.568,99.749,79.515,99.721,79.492,99.706,79.463,99.722,79.385,99.681,79.323,99.537,79.277,99.388,79.275,99.167,79.306,99.104,79.305,99.042,79.293,99.317,79.227,99.517,79.13,99.751,79.108,99.815,79.096,99.9,79.006,99.929,78.961,99.541,78.853,99.44,78.834,98.82,78.818,98.411,78.788,98.283,78.795,98.054,78.821,97.905,78.81,97.689,78.827,97.555,78.827,97.248,78.868,96.933,78.934,96.871,78.964,96.808,78.985,96.43,79.003,96.347,79.016,95.796,79.001,95.703,79.012,95.531,79.098,95.437,79.099,95.133,79.05,95.02,79.053,94.791,79.087,94.652,79.127,94.632,79.141,94.62,79.192,94.482,79.219,94.314,79.308,94.219,79.402,93.759,79.451,93.479,79.463,93.272,79.458,93.071,79.495,93.405,79.632,93.847,79.702,94.038,79.756,94.257,79.83,94.347,79.942,94.719,80.011,94.815,80.035,94.947,80.089,94.987,80.097,95.281,80.031,95.338,80.042,95.391,80.073,95.498,80.106,95.858,80.11,96.163,80.097,96.277,80.11,96.417,80.104,97.121,80.153,97.587,80.168,97.675,80.158]],[[102.885,79.254,102.787,79.176,102.746,79.106,102.448,78.877,102.412,78.835,102.587,78.871,102.748,78.95,102.845,79.014,102.95,79.056,103.076,79.056,103.199,79.071,103.433,79.126,103.673,79.15,103.801,79.149,103.926,79.123,104.004,79.063,104.091,79.013,104.404,78.977,104.449,78.964,104.477,78.923,104.452,78.88,104.633,78.835,104.881,78.855,105.015,78.843,105.146,78.819,105.205,78.78,105.256,78.733,105.31,78.666,105.343,78.594,105.313,78.5,104.833,78.353,104.742,78.34,104.519,78.349,104.297,78.335,103.719,78.258,103.003,78.256,102.797,78.188,102.734,78.19,102.673,78.202,102.617,78.225,102.18,78.205,101.692,78.194,101.204,78.192,101.04,78.143,100.541,78.048,100.082,77.975,99.845,77.957,99.5,77.976,99.392,78.001,99.287,78.038,99.439,78.084,99.546,78.179,99.678,78.233,100.019,78.339,100.058,78.38,100.124,78.47,100.163,78.504,100.215,78.536,100.257,78.574,100.263,78.631,100.284,78.679,100.416,78.753,100.516,78.788,100.62,78.797,100.876,78.784,100.956,78.788,100.898,78.812,100.856,78.898,100.865,78.926,100.901,78.98,100.965,79.007,101.031,79.023,101.068,79.096,101.052,79.123,101.149,79.157,101.196,79.204,101.31,79.233,101.543,79.254,101.555,79.313,101.591,79.35,101.643,79.361,101.761,79.372,101.824,79.37,101.912,79.312,102.005,79.264,102.129,79.252,102.251,79.256,102.177,79.313,102.181,79.373,102.225,79.413,102.282,79.43,102.405,79.433,102.79,79.392,103.042,79.332,103.098,79.299,103.052,79.283,102.94,79.271,102.885,79.254]],[[140.049,75.829,140.152,75.81,140.274,75.822,140.389,75.796,140.496,75.69,140.547,75.663,140.602,75.644,140.657,75.634,140.816,75.631,140.889,75.652,140.944,75.7,140.94,75.75,140.927,75.799,140.926,75.867,140.95,75.927,140.985,75.965,141.033,75.989,141.299,76.064,141.485,76.137,141.742,76.108,142.001,76.044,142.46,75.904,142.67,75.863,142.927,75.827,143.185,75.814,143.311,75.822,143.56,75.86,143.686,75.864,145.255,75.586,145.31,75.564,145.36,75.53,145.023,75.49,144.803,75.416,144.727,75.366,144.814,75.325,144.883,75.269,144.408,75.102,144.216,75.059,144.02,75.045,143.626,75.084,143.396,75.083,143.17,75.117,142.922,75.217,142.82,75.268,142.729,75.338,142.7,75.449,142.734,75.545,142.868,75.572,142.986,75.633,143.002,75.66,142.942,75.713,142.552,75.721,142.308,75.692,142.086,75.661,142.151,75.458,142.199,75.393,142.265,75.346,142.617,75.133,142.697,75.103,142.93,75.062,143.128,74.97,142.778,74.868,142.626,74.837,142.473,74.82,142.378,74.829,142.287,74.85,142.184,74.9,142.1,74.951,141.987,74.991,141.748,74.983,141.53,74.947,141.31,74.923,140.661,74.882,140.464,74.856,140.268,74.847,140.011,74.895,139.758,74.964,139.681,74.964,139.606,74.946,139.548,74.904,139.512,74.838,139.43,74.749,139.326,74.687,139.215,74.66,139.099,74.657,138.982,74.674,138.866,74.701,138.092,74.797,138.001,74.827,137.915,74.871,137.683,75.009,137.568,75.041,137.447,75.054,137.218,75.124,137.006,75.235,136.962,75.27,136.948,75.326,136.982,75.365,137.166,75.347,137.29,75.349,137.215,75.554,137.269,75.749,137.358,75.782,137.707,75.76,137.594,75.823,137.501,75.91,137.561,75.955,137.625,75.988,137.774,76.016,137.977,76.028,138.039,76.047,138.096,76.081,138.208,76.115,138.431,76.13,138.814,76.2,138.92,76.197,139.018,76.16,139.109,76.108,139.211,76.081,139.529,76.013,139.743,75.953,140.049,75.829]],[[146.795,75.371,147.06,75.364,147.444,75.438,147.497,75.441,148.432,75.414,148.509,75.387,148.519,75.336,148.489,75.309,148.475,75.272,148.59,75.236,148.892,75.228,149.083,75.262,149.645,75.245,150.104,75.219,150.281,75.164,150.417,75.134,150.531,75.1,150.613,75.12,150.69,75.155,150.757,75.162,150.822,75.157,150.646,74.945,150.58,74.919,150.331,74.867,149.838,74.795,149.597,74.773,149.05,74.772,148.297,74.8,148.092,74.826,147.972,74.857,147.741,74.932,147.627,74.959,147.257,74.984,147.144,74.998,146.925,75.063,146.703,75.114,146.149,75.198,146.186,75.296,146.258,75.394,146.343,75.481,146.438,75.558,146.538,75.582,146.751,75.51,146.748,75.429,146.795,75.371]],[[178.862,70.826,178.793,70.822,178.648,71.001,178.628,71.047,178.684,71.106,178.829,71.178,178.891,71.231,179.235,71.325,179.548,71.448,179.716,71.466,179.886,71.523,180,71.538,180,70.993,179.881,70.976,179.648,70.899,179.153,70.88,178.862,70.826]],[[142.761,54.394,142.976,54.141,142.986,54.086,142.967,54.029,142.927,53.956,142.911,53.878,142.936,53.811,142.918,53.794,143.096,53.489,143.224,53.296,143.26,53.217,143.288,53.134,143.325,52.963,143.333,52.7,143.324,52.614,143.295,52.529,143.264,52.479,143.201,52.443,143.172,52.349,143.156,52.084,143.191,51.944,143.251,51.848,143.295,51.744,143.3,51.632,143.321,51.583,143.418,51.521,143.455,51.471,143.467,51.402,143.473,51.299,143.489,51.277,143.534,51.246,143.736,50.507,143.816,50.283,144.048,49.896,144.141,49.661,144.2,49.55,144.24,49.432,144.272,49.311,144.341,49.181,144.432,49.051,144.607,48.936,144.686,48.871,144.707,48.82,144.714,48.64,144.673,48.679,144.621,48.815,144.536,48.894,144.412,48.986,144.284,49.07,144.125,49.209,144.049,49.249,143.968,49.276,143.819,49.309,143.732,49.312,143.382,49.291,143.236,49.263,143.105,49.199,143.027,49.105,142.972,48.918,142.651,48.247,142.574,48.072,142.546,47.885,142.557,47.738,142.579,47.684,142.67,47.537,142.745,47.452,142.801,47.416,142.864,47.392,142.905,47.362,142.94,47.323,143.006,47.223,143.089,47.001,143.178,46.844,143.218,46.795,143.319,46.807,143.384,46.806,143.447,46.792,143.486,46.752,143.54,46.575,143.579,46.406,143.581,46.361,143.509,46.23,143.491,46.175,143.482,46.116,143.463,46.069,143.432,46.029,143.419,46.222,143.37,46.358,143.352,46.476,143.282,46.559,143.048,46.593,142.829,46.605,142.796,46.62,142.747,46.671,142.692,46.711,142.636,46.716,142.578,46.701,142.479,46.644,142.406,46.555,142.35,46.459,142.304,46.358,142.209,46.089,142.15,45.999,142.077,45.917,142.016,45.962,141.962,46.013,141.93,46.088,141.916,46.171,141.83,46.451,141.867,46.694,142.011,47.03,142.039,47.14,142.017,47.245,141.984,47.348,141.963,47.544,141.964,47.587,142.016,47.701,142.076,47.808,142.149,47.902,142.182,48.013,142.135,48.29,142.029,48.477,141.897,48.655,141.873,48.702,141.866,48.75,141.98,48.972,142.02,49.078,142.067,49.312,142.109,49.44,142.142,49.569,142.153,50.217,142.143,50.312,142.071,50.515,142.066,50.63,142.1,50.776,142.147,50.89,142.208,50.998,142.207,51.223,142.091,51.429,142.006,51.521,141.873,51.63,141.772,51.69,141.722,51.736,141.772,51.752,141.808,51.789,141.721,51.847,141.668,51.933,141.661,52.273,141.682,52.359,141.748,52.455,141.803,52.556,141.856,52.794,141.874,53.039,141.839,53.138,141.824,53.34,141.852,53.389,141.964,53.456,142.142,53.496,142.18,53.484,142.319,53.405,142.371,53.403,142.424,53.411,142.526,53.447,142.583,53.537,142.509,53.588,142.553,53.653,142.68,53.674,142.689,53.73,142.643,53.737,142.683,53.816,142.706,53.896,142.67,53.968,142.467,54.149,142.335,54.281,142.552,54.279,142.616,54.304,142.666,54.358,142.693,54.416,142.761,54.394]],[[-178.876,71.577,-178.439,71.541,-178.354,71.529,-178.215,71.482,-178.134,71.465,-178.057,71.438,-177.975,71.391,-177.817,71.34,-177.584,71.282,-177.532,71.263,-177.498,71.219,-177.524,71.167,-177.822,71.068,-178.063,71.042,-178.528,71.015,-179.157,70.94,-179.416,70.919,-179.507,70.923,-179.734,70.972,-180,70.993,-180,71.184,-180,71.4,-180,71.538,-179.845,71.551,-179.691,71.578,-179.546,71.582,-179.402,71.567,-179.256,71.572,-179.112,71.596,-178.994,71.593,-178.876,71.577]],[[35.816,65.182,35.848,65.143,35.858,65.078,35.827,65.036,35.842,65.001,35.779,64.977,35.68,65.058,35.621,65.059,35.559,65.094,35.529,65.151,35.586,65.167,35.609,65.157,35.729,65.198,35.816,65.182]],[[42.714,66.702,42.676,66.688,42.477,66.735,42.46,66.77,42.469,66.786,42.547,66.796,42.631,66.782,42.691,66.735,42.714,66.702]],[[52.903,71.365,52.994,71.291,53.074,71.238,53.141,71.242,53.193,71.215,53.205,71.16,53.071,71.065,53.048,71.031,53.106,70.999,53.121,70.982,53.023,70.969,53.004,71.012,52.95,71.054,52.835,71.086,52.789,71.115,52.738,71.181,52.547,71.25,52.425,71.239,52.289,71.27,52.25,71.285,52.24,71.325,52.297,71.357,52.513,71.385,52.617,71.383,52.73,71.355,52.72,71.39,52.732,71.404,52.777,71.4,52.903,71.365]],[[168.039,54.565,168.081,54.513,167.677,54.698,167.488,54.795,167.442,54.856,167.512,54.857,167.592,54.798,167.711,54.77,167.883,54.69,168.039,54.565]],[[160.719,70.823,160.651,70.806,160.505,70.82,160.437,70.851,160.44,70.923,160.449,70.934,160.566,70.924,160.645,70.884,160.719,70.823]],[[161.467,68.901,161.422,68.9,161.456,68.966,161.461,68.996,161.364,69.044,161.183,69.082,161.137,69.11,161.125,69.197,161.165,69.334,161.083,69.406,161.111,69.47,161.323,69.541,161.41,69.596,161.505,69.639,161.521,69.634,161.618,69.592,161.609,69.501,161.54,69.437,161.374,69.414,161.351,69.369,161.373,69.293,161.378,69.194,161.394,69.106,161.495,69.016,161.517,68.97,161.507,68.928,161.467,68.901]],[[152.886,76.122,152.786,76.086,152.559,76.144,152.643,76.175,152.799,76.195,152.835,76.185,152.864,76.163,152.886,76.122]],[[149.15,76.66,148.399,76.648,148.448,76.677,148.72,76.747,149.406,76.782,149.268,76.747,149.205,76.677,149.15,76.66]],[[137.96,71.508,137.712,71.423,137.613,71.434,137.512,71.475,137.458,71.483,137.403,71.477,137.344,71.461,137.266,71.456,137.079,71.502,137.064,71.53,137.082,71.543,137.129,71.556,137.168,71.557,137.282,71.58,137.817,71.588,137.858,71.583,137.934,71.543,137.96,71.508]],[[107.696,78.131,107.606,78.083,107.482,78.058,107.344,78.099,107.002,78.096,106.416,78.14,106.583,78.168,107.508,78.189,107.573,78.186,107.696,78.131]],[[112.478,76.621,112.633,76.553,112.661,76.51,112.614,76.499,112.587,76.483,112.575,76.452,112.532,76.45,112.395,76.484,112.297,76.538,112.154,76.549,112.003,76.603,111.969,76.626,112.011,76.633,112.281,76.618,112.394,76.644,112.478,76.621]],[[96.532,76.278,96.614,76.264,96.59,76.221,96.487,76.234,96.351,76.212,96.353,76.177,96.301,76.122,96.109,76.155,95.845,76.16,95.679,76.194,95.311,76.215,95.322,76.262,95.38,76.289,95.594,76.25,95.786,76.294,96.151,76.272,96.271,76.305,96.532,76.278]],[[96.285,77.027,96.254,77.007,96.21,76.992,96.091,77.003,95.855,76.975,95.766,76.991,95.681,77.021,95.364,77.012,95.27,77.019,95.421,77.056,95.854,77.098,96.528,77.206,96.562,77.154,96.561,77.13,96.424,77.071,96.285,77.027]],[[89.514,77.189,89.3,77.184,89.179,77.21,89.142,77.227,89.2,77.272,89.282,77.301,89.616,77.311,89.68,77.28,89.666,77.254,89.514,77.189]],[[74.661,72.873,74.638,72.864,74.588,72.881,74.435,72.908,74.181,72.975,74.1,73.022,74.142,73.074,74.199,73.109,74.409,73.13,74.6,73.122,74.725,73.108,74.962,73.063,74.743,73.033,74.647,72.969,74.66,72.929,74.697,72.908,74.661,72.873]],[[75.504,73.457,75.344,73.432,75.375,73.477,75.57,73.541,75.93,73.574,76.039,73.56,76.052,73.549,75.901,73.481,75.827,73.459,75.504,73.457]],[[83.549,74.072,83.496,74.048,83.45,74.052,83.411,74.04,83.159,74.075,82.818,74.092,82.903,74.129,83.15,74.152,83.513,74.122,83.618,74.089,83.549,74.072]],[[67.345,69.53,67.264,69.443,67.098,69.447,67.047,69.467,67.026,69.483,67.216,69.575,67.329,69.572,67.345,69.53]],[[66.561,70.542,66.569,70.501,66.516,70.515,66.449,70.561,66.408,70.616,66.395,70.727,66.418,70.757,66.44,70.773,66.463,70.769,66.458,70.699,66.561,70.542]],[[70.021,66.502,69.845,66.49,69.651,66.565,69.469,66.716,69.503,66.751,69.616,66.739,69.8,66.736,69.918,66.712,70.077,66.696,70.058,66.627,70.057,66.599,70.11,66.569,70.059,66.518,70.021,66.502]],[[50.052,80.074,49.971,80.061,49.588,80.136,49.556,80.159,49.884,80.23,50.251,80.219,50.31,80.186,50.319,80.172,50.072,80.109,50.052,80.074]],[[51.409,79.944,51.435,79.932,51.431,79.921,51.076,79.932,50.454,79.924,50.091,79.981,50.473,80.035,50.676,80.049,50.936,80.094,51.254,80.049,51.238,80.01,51.243,79.991,51.327,79.972,51.409,79.944]],[[50.754,81.047,50.616,81.041,50.518,81.046,50.412,81.084,50.377,81.103,50.368,81.123,50.465,81.126,50.506,81.144,50.522,81.158,50.592,81.169,50.716,81.171,50.879,81.151,50.946,81.108,50.789,81.072,50.754,81.047]],[[55.48,80.274,55.195,80.227,55.048,80.228,54.98,80.256,55.092,80.296,55.24,80.325,55.353,80.318,55.435,80.302,55.48,80.274]],[[54.415,80.473,54.276,80.421,53.812,80.476,53.85,80.504,53.9,80.515,53.902,80.542,53.859,80.563,53.877,80.605,54.177,80.574,54.205,80.562,54.407,80.54,54.437,80.499,54.415,80.473]],[[59.689,79.956,59.331,79.923,59.203,79.933,59.169,79.948,59.1,79.964,58.919,79.985,58.946,80.042,59.001,80.054,59.545,80.119,59.802,80.083,59.911,79.994,59.689,79.956]],[[58.622,81.042,58.762,80.991,58.815,80.934,58.903,80.898,58.931,80.832,58.86,80.779,58.642,80.768,58.286,80.765,57.938,80.793,57.75,80.889,57.405,80.915,57.211,81.017,57.41,81.047,57.656,81.032,58.05,81.118,58.102,81.114,58.19,81.095,58.508,81.062,58.622,81.042]],[[47.983,45.488,47.968,45.47,47.92,45.562,47.918,45.618,47.947,45.647,47.987,45.554,47.983,45.488]],[[50.265,69.186,50.283,69.089,50.221,69.049,50.164,69.038,50.141,69.098,50.094,69.126,49.921,69.053,49.84,68.974,49.626,68.86,49.18,68.778,48.91,68.743,48.667,68.733,48.439,68.805,48.316,68.942,48.294,68.984,48.279,69.04,48.28,69.097,48.296,69.184,48.32,69.269,48.414,69.346,48.631,69.436,48.845,69.495,48.953,69.509,49.225,69.511,49.996,69.309,50.167,69.257,50.265,69.186]],[[63.374,80.7,63.188,80.698,63.002,80.713,62.76,80.763,62.52,80.822,62.593,80.853,62.819,80.894,63.116,80.967,63.615,80.981,63.856,80.981,64.096,80.998,64.166,81.036,64.21,81.106,64.256,81.144,64.31,81.175,64.575,81.198,64.802,81.197,65.028,81.169,65.172,81.144,65.31,81.096,65.382,81.057,65.36,81.008,65.372,80.968,65.437,80.931,64.997,80.819,64.548,80.755,63.374,80.7]],[[57.956,80.123,57.8,80.104,57.392,80.139,57.332,80.158,57.281,80.194,57.214,80.328,57.212,80.368,57.186,80.396,57.083,80.445,57.011,80.468,57.075,80.494,57.522,80.475,58.48,80.465,58.972,80.416,59.116,80.388,59.255,80.343,58.398,80.319,58.284,80.298,58.286,80.248,58.255,80.202,58.163,80.197,57.956,80.123]],[[62.168,80.835,62.228,80.794,62.192,80.73,62.115,80.684,62.076,80.617,61.769,80.601,61.681,80.586,61.597,80.535,61.285,80.505,61.051,80.419,60.722,80.435,60.278,80.494,59.9,80.446,59.65,80.431,59.346,80.505,59.304,80.522,59.288,80.573,59.306,80.618,59.387,80.713,59.495,80.767,59.549,80.784,59.592,80.817,59.716,80.836,60.095,80.849,60.235,80.838,60.278,80.801,60.482,80.804,60.82,80.827,61.313,80.863,61.597,80.893,61.851,80.886,62.103,80.867,62.168,80.835]],[[61.141,80.95,60.827,80.93,60.321,80.956,60.058,80.985,60.078,80.999,60.148,81.017,60.587,81.088,61.457,81.104,61.567,81.05,61.472,81.011,61.141,80.95]],[[53.521,80.185,52.856,80.173,52.636,80.179,52.607,80.191,52.55,80.202,52.344,80.213,52.213,80.264,52.27,80.276,52.577,80.297,52.681,80.319,52.716,80.348,52.854,80.402,53.186,80.413,53.329,80.402,53.346,80.366,53.486,80.323,53.852,80.268,53.778,80.228,53.653,80.223,53.521,80.185]],[[57.079,80.351,57.123,80.317,57.119,80.194,57.073,80.139,57.08,80.095,56.987,80.071,56.201,80.076,55.812,80.087,55.724,80.105,55.942,80.163,56.012,80.204,55.99,80.32,56.024,80.341,56.655,80.33,56.707,80.363,56.945,80.366,57.079,80.351]],[[57.81,81.546,57.863,81.506,58.017,81.484,58.436,81.464,58.564,81.418,58.372,81.387,57.859,81.368,57.912,81.303,58.015,81.255,57.913,81.198,57.77,81.17,57.451,81.136,57.159,81.178,56.822,81.238,56.669,81.198,56.513,81.175,56.364,81.179,56.192,81.224,55.717,81.188,55.573,81.228,55.466,81.311,55.782,81.329,56.157,81.303,56.405,81.387,56.719,81.423,56.973,81.511,57.092,81.541,57.365,81.535,57.456,81.543,57.717,81.565,57.81,81.546]],[[54.719,81.116,55.471,81.02,56.17,81.029,56.472,80.998,56.91,80.913,57.568,80.82,57.694,80.792,57.58,80.755,56.815,80.664,56.316,80.633,55.883,80.628,55.713,80.637,55.541,80.703,55.117,80.752,54.668,80.739,54.623,80.765,54.533,80.783,54.376,80.787,54.067,80.814,54.045,80.872,54.241,80.902,54.367,80.904,54.417,80.987,54.634,81.113,54.719,81.116]],[[63.651,81.609,63.529,81.597,62.885,81.609,62.573,81.633,62.531,81.647,62.515,81.659,62.106,81.679,62.284,81.707,62.795,81.719,63.71,81.687,63.767,81.664,63.782,81.65,63.651,81.609]],[[58.295,81.715,57.965,81.696,57.921,81.71,57.909,81.722,57.945,81.748,57.985,81.797,58.135,81.828,59.262,81.854,59.408,81.825,59.357,81.781,59.356,81.759,58.295,81.715]],[[92.683,79.685,92.441,79.675,92.154,79.685,91.684,79.791,91.376,79.835,91.126,79.905,91.07,79.981,91.229,80.031,91.426,80.049,91.752,80.052,92.173,80.045,92.593,79.997,93.482,79.941,93.803,79.905,93.604,79.817,93.382,79.784,93.155,79.738,92.926,79.704,92.683,79.685]],[[91.567,81.141,91.223,81.064,89.976,81.113,89.919,81.149,89.901,81.171,90.07,81.214,91.109,81.199,91.478,81.184,91.567,81.141]],[[141.01,73.999,140.507,73.919,140.409,73.922,140.183,74.005,140.102,74.184,140.194,74.237,140.3,74.257,140.407,74.266,140.849,74.274,140.944,74.265,141.039,74.243,141.079,74.209,141.097,74.168,141.047,74.05,141.01,73.999]],[[142.185,73.896,142.435,73.852,142.639,73.803,143.344,73.569,143.411,73.521,143.464,73.459,143.491,73.246,143.451,73.231,143.193,73.221,142.842,73.245,142.587,73.253,142.342,73.253,142.126,73.282,141.597,73.311,141.183,73.389,140.754,73.446,140.663,73.452,140.392,73.435,140.027,73.361,139.925,73.355,139.786,73.355,139.686,73.426,139.92,73.449,140.155,73.458,140.381,73.483,140.594,73.565,140.697,73.629,140.884,73.778,140.984,73.832,141.085,73.866,141.19,73.876,141.312,73.872,141.682,73.904,141.932,73.915,142.185,73.896]],[[135.949,75.41,135.746,75.382,135.452,75.39,135.473,75.463,135.523,75.496,135.593,75.576,135.561,75.636,135.578,75.71,135.614,75.766,135.699,75.845,135.788,75.798,135.849,75.729,135.905,75.694,136.127,75.626,136.169,75.606,135.983,75.522,135.965,75.486,136.021,75.438,135.949,75.41]],[[136.197,73.914,136.122,73.885,136.051,73.929,135.715,74.06,135.633,74.121,135.449,74.18,135.402,74.202,135.387,74.253,135.628,74.22,136.037,74.09,136.259,73.985,136.197,73.914]],[[137.941,55.093,138.031,55.053,138.172,55.06,138.206,55.034,138.096,54.991,138.017,54.901,137.991,54.821,137.959,54.789,137.87,54.75,137.79,54.697,137.721,54.663,137.661,54.653,137.526,54.826,137.463,54.873,137.276,54.792,137.233,54.791,137.275,54.891,137.384,55.001,137.436,55.016,137.544,55.163,137.577,55.197,137.91,55.11,137.941,55.093]],[[169.201,69.58,168.916,69.571,168.348,69.664,168.144,69.713,167.993,69.776,167.821,69.82,167.789,69.837,167.814,69.873,167.865,69.901,168.06,69.975,168.196,70.008,168.358,70.016,169.375,69.883,169.421,69.856,169.434,69.832,169.418,69.779,169.332,69.77,169.299,69.735,169.263,69.629,169.246,69.601,169.201,69.58]],[[163.635,58.603,163.471,58.509,163.447,58.525,163.432,58.546,163.427,58.579,163.577,58.641,163.727,58.799,163.784,58.93,163.767,58.972,163.761,59.015,164.202,59.096,164.517,59.227,164.573,59.221,164.629,59.112,164.662,58.971,164.616,58.886,164.279,58.838,163.96,58.744,163.635,58.603]],[[166.65,54.839,166.645,54.694,166.521,54.768,166.464,54.827,166.382,54.838,166.325,54.865,166.23,54.937,166.12,55.03,166.082,55.077,166.066,55.136,165.992,55.19,165.751,55.295,165.83,55.307,165.931,55.351,166.212,55.324,166.276,55.312,166.23,55.242,166.248,55.165,166.404,55.006,166.479,54.95,166.577,54.908,166.65,54.839]],[[154.81,49.312,154.715,49.268,154.611,49.294,154.613,49.381,154.825,49.647,154.9,49.63,154.883,49.566,154.802,49.468,154.83,49.348,154.81,49.312]],[[156.405,50.658,156.365,50.634,156.326,50.639,156.196,50.702,156.168,50.732,156.213,50.785,156.376,50.862,156.456,50.86,156.488,50.843,156.483,50.751,156.405,50.658]],[[155.921,50.302,155.792,50.202,155.608,50.177,155.516,50.146,155.449,50.078,155.397,50.041,155.289,50.061,155.243,50.095,155.243,50.213,155.195,50.265,155.218,50.298,155.327,50.293,155.434,50.369,155.68,50.401,155.773,50.482,155.885,50.684,156.002,50.757,156.097,50.772,156.123,50.671,156.101,50.559,156.044,50.452,155.921,50.302]],[[154.081,48.79,154.043,48.739,154.001,48.756,153.992,48.773,154.092,48.832,154.126,48.904,154.199,48.905,154.228,48.892,154.205,48.857,154.081,48.79]],[[155.645,50.822,155.554,50.811,155.513,50.837,155.483,50.87,155.467,50.914,155.569,50.934,155.64,50.91,155.654,50.845,155.645,50.822]],[[153.101,47.763,153.054,47.706,153.004,47.713,152.984,47.728,153.049,47.797,153.079,47.809,153.101,47.763]],[[152.002,46.897,151.816,46.787,151.754,46.788,151.723,46.829,151.715,46.853,151.864,46.869,152.04,47.015,152.166,47.11,152.235,47.143,152.289,47.142,152.002,46.897]],[[149.688,45.642,149.539,45.591,149.447,45.593,149.666,45.84,149.796,45.876,149.962,46.022,150.309,46.2,150.349,46.213,150.553,46.209,150.235,46.012,150.195,45.933,150.057,45.849,149.954,45.822,149.883,45.783,149.688,45.642]],[[148.6,45.318,148.415,45.247,148.262,45.217,148.005,45.07,147.914,44.99,147.784,44.959,147.658,44.977,147.622,44.945,147.61,44.887,147.563,44.836,147.31,44.678,147.207,44.554,147.098,44.531,146.897,44.404,146.933,44.513,146.974,44.566,147.141,44.663,147.155,44.766,147.247,44.856,147.43,44.945,147.558,45.062,147.658,45.093,147.769,45.191,147.886,45.226,147.873,45.3,147.924,45.383,147.965,45.378,148.056,45.262,148.13,45.258,148.324,45.282,148.612,45.485,148.707,45.521,148.773,45.526,148.812,45.51,148.826,45.486,148.825,45.456,148.803,45.414,148.837,45.363,148.791,45.324,148.6,45.318]],[[146.714,43.744,146.683,43.716,146.609,43.74,146.613,43.797,146.622,43.813,146.825,43.86,146.884,43.829,146.899,43.804,146.714,43.744]],[[146.208,44.498,146.356,44.425,146.568,44.44,146.516,44.375,146.437,44.376,146.296,44.281,146.173,44.269,146.112,44.246,145.914,44.104,145.887,44.048,145.767,43.941,145.587,43.845,145.556,43.665,145.439,43.737,145.426,43.81,145.462,43.871,145.666,43.999,145.748,44.072,145.773,44.129,145.852,44.193,145.89,44.249,145.94,44.273,146.112,44.5,146.208,44.498]],[[113.387,74.4,113.353,74.353,113.299,74.317,113.259,74.273,113.19,74.239,112.978,74.197,112.811,74.103,112.782,74.095,112.196,74.146,112.105,74.163,111.912,74.219,111.643,74.273,111.503,74.353,111.57,74.368,111.638,74.374,111.88,74.364,111.949,74.389,111.983,74.456,111.989,74.496,112.008,74.527,112.084,74.549,112.952,74.48,113.286,74.441,113.387,74.4]],[[76.249,79.651,76.373,79.615,76.467,79.643,77.36,79.557,77.549,79.524,77.589,79.502,76.81,79.49,76.65,79.493,76.637,79.544,76.458,79.545,76.154,79.579,76.072,79.626,76.052,79.645,76.148,79.664,76.249,79.651]],[[80.027,80.848,79.099,80.812,79.007,80.835,78.978,80.848,79.11,80.924,79.217,80.96,79.807,80.975,80.28,80.95,80.428,80.928,80.373,80.883,80.345,80.868,80.027,80.848]],[[70.674,73.095,70.38,73.048,70.298,73.044,70.119,73.056,70.041,73.037,69.92,73.085,69.93,73.127,69.986,73.169,70.019,73.224,69.996,73.359,70.15,73.445,70.35,73.478,70.94,73.514,71.023,73.504,71.141,73.478,71.232,73.448,71.351,73.372,71.445,73.342,71.59,73.283,71.63,73.225,71.626,73.174,71.356,73.162,70.887,73.12,70.674,73.095]],[[77.633,72.291,77.146,72.282,76.906,72.298,76.871,72.317,76.903,72.366,77.15,72.439,77.26,72.486,77.378,72.565,77.579,72.631,77.749,72.631,78.279,72.553,78.353,72.504,78.365,72.482,78.154,72.417,78.007,72.392,77.781,72.309,77.633,72.291]],[[79.501,72.722,79.431,72.711,78.881,72.752,78.69,72.803,78.633,72.851,78.657,72.892,79.164,73.094,79.357,73.039,79.413,72.983,79.541,72.919,79.538,72.769,79.501,72.722]],[[82.172,75.419,82.209,75.387,82.222,75.351,82.179,75.339,82.05,75.341,81.979,75.247,81.905,75.263,81.861,75.317,81.698,75.281,81.655,75.289,81.579,75.331,81.532,75.34,81.501,75.368,81.712,75.451,81.842,75.407,81.927,75.41,81.91,75.46,81.913,75.498,82.022,75.513,82.166,75.516,82.172,75.419]],[[60.45,69.935,60.481,69.885,60.477,69.794,60.44,69.726,60.327,69.715,60.216,69.688,60.026,69.717,59.92,69.697,59.813,69.696,59.725,69.706,59.637,69.721,59.578,69.739,59.581,69.791,59.503,69.866,59.382,69.89,59.268,69.898,59.144,69.922,59.083,69.911,59.004,69.883,58.953,69.893,58.68,70.051,58.634,70.088,58.606,70.129,58.568,70.156,58.473,70.267,58.52,70.318,58.615,70.351,58.678,70.36,58.794,70.433,59.005,70.465,59.048,70.46,59.088,70.437,59.31,70.362,59.426,70.311,59.529,70.249,59.636,70.197,59.956,70.108,60.172,70.023,60.393,69.962,60.45,69.935]],[[20.958,55.279,20.859,55.184,20.595,54.982,20.678,54.956,20.774,54.947,20.888,54.909,20.996,54.903,21.189,54.935,21.223,55.108,21.236,55.264,21.298,55.264,21.389,55.276,21.447,55.234,21.555,55.195,21.683,55.16,21.874,55.101,22.072,55.064,22.138,55.059,22.346,55.064,22.567,55.059,22.627,54.971,22.737,54.929,22.825,54.871,22.831,54.838,22.71,54.633,22.684,54.563,22.68,54.493,22.724,54.406,22.766,54.357,22.732,54.35,22.168,54.36,21.634,54.376,21.141,54.392,20.665,54.407,20.208,54.421,19.924,54.434,19.644,54.447,19.604,54.459,19.758,54.545,19.859,54.634,19.944,54.75,19.953,54.83,19.975,54.921,20.108,54.956,20.397,54.951,20.52,54.995,20.679,55.103,20.846,55.232,20.9,55.287,20.958,55.279]],[[33.594,46.096,33.654,46.146,33.66,46.22,33.807,46.208,34.027,46.107,34.128,46.09,34.224,46.101,34.354,46.062,34.45,45.966,34.523,45.977,34.687,45.977,34.794,45.892,34.8,45.791,34.946,45.729,35.002,45.733,35.023,45.701,35.26,45.447,35.374,45.354,35.458,45.316,35.558,45.311,35.751,45.389,35.833,45.402,36.013,45.372,36.077,45.424,36.171,45.453,36.29,45.457,36.427,45.433,36.575,45.394,36.514,45.304,36.451,45.232,36.428,45.153,36.393,45.065,36.23,45.026,36.055,45.031,35.87,45.005,35.804,45.04,35.759,45.071,35.678,45.102,35.57,45.119,35.473,45.098,35.358,44.978,35.155,44.896,35.088,44.803,34.888,44.824,34.717,44.807,34.47,44.722,34.282,44.538,34.074,44.424,33.91,44.388,33.756,44.399,33.656,44.433,33.451,44.554,33.463,44.597,33.491,44.619,33.53,44.681,33.612,44.908,33.601,44.981,33.555,45.098,33.392,45.188,33.262,45.171,33.187,45.195,32.919,45.348,32.773,45.359,32.611,45.328,32.552,45.35,32.508,45.404,32.828,45.593,33.142,45.749,33.28,45.765,33.466,45.838,33.665,45.947,33.637,46.033,33.594,46.096]]]},{"n":"Romania","c":"RO","r":3,"ml":3,"lx":24.97,"ly":45.73,"b":[20.242,43.671,29.706,48.263],"p":[[[28.213,45.45,28.318,45.347,28.451,45.292,28.761,45.234,28.788,45.241,28.791,45.252,28.77,45.267,28.767,45.286,28.782,45.31,28.824,45.311,28.894,45.29,29.027,45.321,29.224,45.403,29.404,45.42,29.568,45.371,29.652,45.314,29.706,45.26,29.689,45.193,29.679,45.152,29.635,44.98,29.605,44.915,29.558,44.843,29.048,44.758,29.081,44.799,29.069,44.871,29.048,44.926,29.095,44.975,28.981,44.993,28.931,44.966,28.892,44.919,28.926,44.81,28.87,44.75,28.849,44.716,28.846,44.637,28.814,44.602,28.807,44.565,28.888,44.575,28.852,44.506,28.699,44.374,28.645,44.296,28.659,43.984,28.591,43.797,28.585,43.742,28.423,43.74,28.375,43.745,28.222,43.773,28.05,43.822,27.949,43.919,27.884,43.987,27.739,43.956,27.711,43.965,27.671,43.998,27.561,44.02,27.425,44.021,27.121,44.146,27.087,44.167,26.848,44.146,26.489,44.084,26.216,44.007,25.933,43.871,25.819,43.767,25.686,43.712,25.497,43.671,25.16,43.686,24.808,43.738,24.431,43.794,24.227,43.763,23.951,43.787,23.535,43.854,23.225,43.874,22.919,43.834,22.868,43.865,22.856,43.899,22.868,43.948,22.911,43.987,22.985,44.017,23.024,44.047,23.029,44.078,22.945,44.127,22.775,44.195,22.705,44.238,22.688,44.248,22.683,44.286,22.648,44.316,22.582,44.338,22.531,44.378,22.495,44.435,22.502,44.49,22.554,44.54,22.62,44.562,22.701,44.556,22.734,44.57,22.721,44.606,22.642,44.651,22.498,44.706,22.351,44.676,22.201,44.561,22.093,44.542,22.027,44.62,21.909,44.666,21.74,44.681,21.636,44.71,21.597,44.755,21.523,44.79,21.36,44.827,21.358,44.862,21.384,44.87,21.442,44.873,21.52,44.881,21.532,44.901,21.533,44.919,21.472,44.942,21.41,44.958,21.378,44.973,21.357,44.991,21.353,45.009,21.371,45.021,21.396,45.022,21.421,45.033,21.434,45.075,21.468,45.11,21.492,45.122,21.49,45.148,21.465,45.172,21.431,45.193,21.382,45.205,21.226,45.241,21.148,45.292,21.1,45.294,21.024,45.322,20.942,45.365,20.871,45.428,20.794,45.468,20.774,45.484,20.772,45.5,20.787,45.517,20.786,45.536,20.766,45.597,20.779,45.662,20.776,45.723,20.775,45.75,20.76,45.758,20.747,45.749,20.728,45.737,20.709,45.735,20.653,45.779,20.581,45.869,20.533,45.9,20.438,45.941,20.359,45.975,20.301,46.051,20.242,46.109,20.281,46.133,20.508,46.167,20.614,46.133,20.661,46.146,20.707,46.173,20.733,46.194,20.737,46.217,20.76,46.246,20.837,46.26,21.04,46.242,21.122,46.282,21.152,46.304,21.17,46.353,21.192,46.392,21.265,46.412,21.263,46.448,21.252,46.486,21.295,46.572,21.32,46.608,21.361,46.621,21.411,46.648,21.497,46.704,21.477,46.753,21.494,46.79,21.584,46.878,21.653,46.964,21.651,47.007,21.661,47.044,21.722,47.085,21.785,47.138,21.869,47.305,21.899,47.333,21.954,47.364,21.995,47.396,22,47.505,22.038,47.537,22.112,47.572,22.185,47.629,22.245,47.696,22.291,47.728,22.351,47.736,22.417,47.763,22.491,47.773,22.563,47.76,22.608,47.766,22.677,47.799,22.852,47.923,22.877,47.947,22.913,47.964,23.055,48.007,23.091,48.049,23.139,48.087,23.203,48.085,23.408,47.99,23.629,47.996,23.669,47.992,23.682,47.99,23.709,47.983,24.002,47.936,24.047,47.941,24.06,47.945,24.178,47.906,24.282,47.911,24.381,47.938,24.484,47.947,24.579,47.931,24.651,47.877,24.838,47.761,24.893,47.718,24.979,47.724,25.074,47.746,25.17,47.823,25.464,47.911,25.689,47.932,25.909,47.968,26.163,47.993,26.236,48.064,26.277,48.113,26.306,48.204,26.442,48.23,26.572,48.248,26.619,48.26,26.714,48.263,26.787,48.256,26.901,48.211,26.981,48.155,27.012,48.11,27.08,48.048,27.152,47.959,27.231,47.842,27.248,47.782,27.278,47.718,27.337,47.64,27.449,47.553,27.465,47.537,27.516,47.476,27.614,47.341,27.696,47.286,27.768,47.228,27.802,47.168,27.854,47.115,27.974,47.043,28.072,46.978,28.15,46.792,28.205,46.706,28.239,46.641,28.223,46.508,28.244,46.451,28.2,46.348,28.119,46.139,28.1,45.973,28.114,45.883,28.116,45.826,28.135,45.789,28.156,45.713,28.16,45.647,28.131,45.628,28.09,45.613,28.075,45.599,28.112,45.569,28.163,45.514,28.213,45.45]]]},{"n":"Qatar","c":"QA","r":5,"ml":4,"lx":51.14,"ly":25.24,"b":[50.755,24.565,51.609,26.153],"p":[[[51.268,24.607,51.178,24.587,51.093,24.565,51.023,24.565,50.966,24.574,50.928,24.595,50.856,24.68,50.804,24.789,50.836,24.85,50.847,24.889,50.777,25.177,50.755,25.399,50.763,25.445,50.803,25.497,50.869,25.613,50.904,25.724,51.003,25.981,51.108,26.081,51.262,26.153,51.389,26.011,51.543,25.902,51.572,25.781,51.527,25.682,51.485,25.525,51.51,25.452,51.52,25.39,51.561,25.284,51.602,25.148,51.609,25.053,51.587,24.965,51.533,24.891,51.428,24.668,51.396,24.645,51.268,24.607]]]},{"n":"Portugal","c":"PT","r":2,"ml":3,"lx":-8.27,"ly":39.61,"b":[-31.283,32.648,-6.212,42.137],"p":[[[-17.191,32.869,-17.054,32.816,-16.929,32.841,-16.774,32.774,-16.693,32.758,-16.765,32.71,-16.837,32.648,-17.018,32.663,-17.171,32.722,-17.226,32.767,-17.241,32.807,-17.191,32.869]],[[-8.777,41.941,-8.683,42.008,-8.59,42.053,-8.538,42.069,-8.323,42.115,-8.266,42.137,-8.213,42.134,-8.204,42.112,-8.174,42.069,-8.139,42.04,-8.13,42.018,-8.213,41.927,-8.225,41.896,-8.181,41.837,-8.174,41.82,-8.152,41.812,-8.094,41.814,-7.991,41.852,-7.921,41.884,-7.896,41.871,-7.693,41.888,-7.645,41.874,-7.613,41.858,-7.513,41.836,-7.404,41.834,-7.269,41.864,-7.21,41.895,-7.198,41.929,-7.195,41.955,-7.178,41.972,-7.147,41.981,-7.099,41.964,-7.03,41.951,-6.866,41.945,-6.833,41.964,-6.777,41.958,-6.704,41.935,-6.618,41.942,-6.575,41.913,-6.558,41.874,-6.553,41.79,-6.559,41.704,-6.542,41.673,-6.485,41.664,-6.392,41.665,-6.308,41.642,-6.243,41.602,-6.222,41.56,-6.212,41.532,-6.244,41.516,-6.289,41.455,-6.403,41.375,-6.566,41.304,-6.69,41.215,-6.776,41.108,-6.883,41.062,-6.916,41.038,-6.928,41.009,-6.858,40.878,-6.836,40.777,-6.818,40.654,-6.83,40.619,-6.836,40.483,-6.852,40.443,-6.848,40.411,-6.822,40.376,-6.81,40.343,-6.859,40.301,-6.948,40.252,-7.015,40.208,-7.033,40.168,-7.028,40.143,-6.916,40.057,-6.896,40.022,-6.911,39.937,-6.975,39.798,-7.037,39.714,-7.047,39.706,-7.118,39.682,-7.454,39.681,-7.536,39.662,-7.524,39.645,-7.445,39.536,-7.363,39.478,-7.335,39.465,-7.306,39.338,-7.172,39.135,-7.043,39.107,-6.998,39.056,-7.006,38.985,-7.046,38.907,-7.125,38.827,-7.22,38.771,-7.282,38.715,-7.286,38.649,-7.306,38.567,-7.336,38.501,-7.343,38.457,-7.106,38.181,-6.975,38.194,-6.958,38.188,-6.981,38.122,-7.023,38.045,-7.073,38.03,-7.185,38.006,-7.292,37.906,-7.379,37.786,-7.444,37.728,-7.504,37.585,-7.496,37.524,-7.467,37.428,-7.406,37.179,-7.494,37.168,-7.834,37.006,-7.94,37.005,-8.137,37.077,-8.484,37.1,-8.598,37.121,-8.739,37.075,-8.848,37.076,-8.935,37.016,-8.998,37.032,-8.926,37.166,-8.814,37.431,-8.819,37.592,-8.792,37.733,-8.823,37.872,-8.879,37.959,-8.802,38.184,-8.811,38.3,-8.881,38.447,-8.668,38.424,-8.734,38.482,-8.799,38.518,-8.862,38.51,-8.915,38.512,-9.096,38.455,-9.187,38.438,-9.213,38.448,-9.203,38.539,-9.25,38.657,-9.178,38.688,-9.093,38.697,-9.021,38.747,-8.977,38.803,-9,38.903,-8.938,38.998,-8.792,39.078,-8.867,39.066,-8.954,39.016,-9.091,38.835,-9.136,38.743,-9.252,38.713,-9.357,38.698,-9.41,38.708,-9.474,38.731,-9.48,38.799,-9.475,38.853,-9.431,38.96,-9.414,39.112,-9.353,39.248,-9.357,39.284,-9.375,39.338,-9.32,39.391,-9.251,39.426,-9.148,39.543,-9.004,39.821,-8.838,40.116,-8.851,40.152,-8.887,40.179,-8.873,40.259,-8.772,40.606,-8.732,40.651,-8.685,40.753,-8.674,40.917,-8.656,41.029,-8.66,41.086,-8.675,41.154,-8.738,41.285,-8.806,41.56,-8.811,41.652,-8.755,41.698,-8.846,41.705,-8.888,41.765,-8.878,41.832,-8.777,41.941]],[[-25.027,36.96,-25.032,36.942,-25.088,36.949,-25.16,36.943,-25.198,36.997,-25.164,37.019,-25.083,37.024,-25.044,37,-25.027,36.96]],[[-31.137,39.407,-31.181,39.359,-31.258,39.376,-31.283,39.394,-31.261,39.497,-31.2,39.521,-31.139,39.479,-31.137,39.407]],[[-27.075,38.643,-27.095,38.634,-27.303,38.661,-27.362,38.698,-27.386,38.766,-27.351,38.789,-27.26,38.803,-27.127,38.79,-27.042,38.741,-27.042,38.679,-27.075,38.643]],[[-27.778,38.556,-27.826,38.544,-28.092,38.621,-28.187,38.655,-28.311,38.744,-27.963,38.636,-27.778,38.556]],[[-28.641,38.525,-28.744,38.522,-28.842,38.598,-28.698,38.638,-28.655,38.614,-28.624,38.586,-28.606,38.551,-28.641,38.525]],[[-28.147,38.453,-28.065,38.413,-28.19,38.404,-28.231,38.385,-28.332,38.413,-28.454,38.409,-28.531,38.463,-28.549,38.519,-28.51,38.553,-28.402,38.553,-28.147,38.453]],[[-25.649,37.841,-25.585,37.834,-25.267,37.849,-25.182,37.838,-25.191,37.764,-25.251,37.735,-25.439,37.715,-25.734,37.763,-25.834,37.826,-25.848,37.872,-25.846,37.894,-25.784,37.911,-25.649,37.841]]]},{"n":"Poland","c":"PL","r":3,"ml":2.5,"lx":19.49,"ly":51.99,"b":[14.129,49.021,24.106,54.838],"p":[[[23.605,51.518,23.659,51.449,23.68,51.395,23.658,51.352,23.664,51.31,23.712,51.265,23.863,51.126,23.938,50.993,23.986,50.94,24.096,50.873,24.106,50.845,24.062,50.82,24.026,50.816,23.997,50.809,23.978,50.786,24.007,50.76,24.046,50.723,24.095,50.617,24.09,50.53,24.053,50.508,24.005,50.457,23.973,50.41,23.712,50.377,23.649,50.327,23.506,50.23,23.409,50.174,23.264,50.073,23.036,49.899,22.952,49.826,22.891,49.766,22.706,49.606,22.649,49.539,22.661,49.484,22.72,49.354,22.732,49.295,22.722,49.241,22.702,49.193,22.706,49.171,22.76,49.136,22.847,49.081,22.852,49.063,22.84,49.039,22.81,49.021,22.701,49.04,22.58,49.077,22.539,49.073,22.473,49.081,22.203,49.153,22.02,49.21,22.002,49.246,21.968,49.299,21.89,49.343,21.712,49.382,21.64,49.412,21.35,49.429,21.225,49.429,21.136,49.417,21.079,49.418,21.001,49.34,20.947,49.317,20.868,49.315,20.8,49.329,20.729,49.37,20.616,49.392,20.535,49.381,20.475,49.39,20.423,49.392,20.405,49.384,20.363,49.385,20.303,49.366,20.237,49.338,20.164,49.316,20.108,49.271,20.058,49.181,19.916,49.221,19.869,49.204,19.802,49.192,19.757,49.204,19.767,49.235,19.788,49.27,19.787,49.319,19.774,49.372,19.73,49.39,19.664,49.396,19.63,49.407,19.627,49.424,19.593,49.447,19.535,49.505,19.48,49.576,19.442,49.598,19.386,49.564,19.302,49.525,19.25,49.511,19.149,49.4,18.968,49.396,18.957,49.448,18.938,49.498,18.832,49.511,18.829,49.54,18.807,49.614,18.595,49.758,18.569,49.818,18.577,49.841,18.562,49.879,18.516,49.902,18.348,49.93,18.305,49.914,18.266,49.93,18.205,49.965,18.099,49.993,18.088,50.007,18.05,50.032,18.028,50.035,18.015,50.02,17.984,49.999,17.875,49.972,17.831,49.983,17.792,50.007,17.747,50.057,17.681,50.101,17.627,50.116,17.596,50.14,17.589,50.157,17.709,50.194,17.735,50.231,17.72,50.299,17.702,50.307,17.655,50.284,17.555,50.264,17.462,50.255,17.415,50.255,17.152,50.378,16.981,50.416,16.88,50.427,16.869,50.415,16.915,50.345,16.993,50.26,16.99,50.237,16.895,50.202,16.842,50.187,16.779,50.157,16.725,50.116,16.679,50.097,16.639,50.102,16.597,50.122,16.488,50.248,16.35,50.345,16.334,50.367,16.291,50.372,16.231,50.394,16.21,50.424,16.241,50.455,16.283,50.483,16.357,50.5,16.379,50.517,16.392,50.542,16.42,50.574,16.413,50.585,16.36,50.621,16.282,50.656,16.066,50.63,16.007,50.612,15.974,50.635,15.949,50.67,15.894,50.677,15.819,50.709,15.731,50.74,15.644,50.749,15.464,50.794,15.395,50.796,15.354,50.812,15.313,50.846,15.277,50.883,15.259,50.959,15.126,50.993,14.994,51.014,14.984,51.003,14.99,50.927,14.983,50.887,14.896,50.861,14.809,50.859,14.814,50.872,14.917,51.009,14.964,51.095,15.017,51.253,14.953,51.377,14.936,51.435,14.906,51.463,14.725,51.524,14.711,51.545,14.739,51.627,14.725,51.662,14.681,51.698,14.624,51.771,14.602,51.832,14.675,51.905,14.693,51.958,14.725,52.031,14.748,52.071,14.753,52.082,14.705,52.11,14.692,52.15,14.705,52.207,14.68,52.25,14.616,52.278,14.574,52.314,14.555,52.36,14.57,52.431,14.619,52.529,14.514,52.646,14.254,52.783,14.129,52.878,14.139,52.933,14.194,52.982,14.293,53.027,14.369,53.106,14.411,53.199,14.412,53.217,14.415,53.283,14.299,53.556,14.28,53.625,14.266,53.707,14.259,53.73,14.488,53.672,14.583,53.639,14.572,53.676,14.552,53.707,14.565,53.754,14.558,53.823,14.351,53.859,14.214,53.871,14.198,53.919,14.211,53.95,14.249,53.932,14.384,53.925,14.716,54.018,15.288,54.14,15.9,54.254,16.043,54.266,16.186,54.29,16.239,54.333,16.292,54.362,16.376,54.437,16.56,54.554,16.885,54.596,17.007,54.652,17.262,54.73,17.843,54.817,18.086,54.836,18.323,54.838,18.535,54.769,18.759,54.685,18.8,54.633,18.678,54.665,18.502,54.742,18.436,54.745,18.587,54.513,18.67,54.431,18.836,54.37,18.976,54.349,19.407,54.386,19.56,54.435,19.604,54.459,19.644,54.447,19.924,54.434,20.208,54.421,20.665,54.407,21.141,54.392,21.634,54.376,22.168,54.36,22.732,54.35,22.766,54.357,22.824,54.396,22.894,54.391,22.977,54.366,23.016,54.348,23.032,54.328,23.042,54.304,23.088,54.299,23.17,54.281,23.282,54.24,23.37,54.2,23.454,54.143,23.481,54.079,23.483,54.006,23.478,53.959,23.485,53.94,23.599,53.599,23.789,53.271,23.859,53.112,23.887,53.028,23.909,52.905,23.916,52.819,23.915,52.77,23.901,52.704,23.845,52.664,23.48,52.552,23.411,52.516,23.303,52.428,23.204,52.338,23.181,52.307,23.175,52.287,23.197,52.257,23.327,52.208,23.458,52.17,23.501,52.14,23.598,52.103,23.633,52.07,23.652,52.04,23.651,51.973,23.607,51.88,23.626,51.809,23.581,51.762,23.545,51.71,23.54,51.619,23.605,51.518]]]},{"n":"Philippines","c":"PH","r":2,"ml":2.5,"lx":122.47,"ly":11.2,"b":[116.97,5.06,126.593,20.841],"p":[[[121.102,18.615,121.254,18.563,121.593,18.376,121.717,18.33,121.846,18.295,121.948,18.285,122.038,18.328,122.077,18.372,122.147,18.487,122.221,18.501,122.266,18.459,122.3,18.403,122.315,18.32,122.294,18.234,122.223,18.157,122.179,18.064,122.151,17.756,122.152,17.664,122.175,17.576,122.237,17.435,122.269,17.395,122.362,17.345,122.388,17.307,122.393,17.238,122.408,17.178,122.468,17.155,122.519,17.125,122.5,17.058,122.468,16.99,122.426,16.823,122.226,16.435,122.214,16.352,122.135,16.185,121.975,16.158,121.789,16.077,121.685,16.015,121.595,15.933,121.561,15.827,121.59,15.778,121.609,15.726,121.607,15.67,121.579,15.623,121.49,15.51,121.452,15.417,121.412,15.375,121.392,15.324,121.399,15.267,121.435,15.216,121.544,14.999,121.661,14.79,121.686,14.765,121.695,14.737,121.627,14.682,121.628,14.581,121.649,14.481,121.752,14.234,121.767,14.168,121.8,14.114,121.853,14.063,121.912,14.02,122.08,13.947,122.144,13.933,122.212,13.93,122.228,13.979,122.288,13.996,122.274,14.045,122.203,14.112,122.2,14.148,122.238,14.175,122.283,14.191,122.384,14.264,122.491,14.322,122.627,14.318,122.761,14.285,122.856,14.251,122.934,14.188,123.015,14.08,123.071,13.96,123.071,13.903,123.057,13.845,123.06,13.789,123.102,13.75,123.231,13.747,123.297,13.836,123.305,13.937,123.259,13.975,123.28,14.025,123.32,14.062,123.377,14.029,123.432,13.966,123.633,13.898,123.684,13.897,123.726,13.884,123.816,13.837,123.858,13.8,123.806,13.722,123.607,13.704,123.55,13.646,123.608,13.528,123.704,13.432,123.765,13.354,123.819,13.269,123.817,13.192,123.785,13.111,123.873,13.117,123.955,13.1,124.069,13.032,124.105,13.025,124.143,13.036,124.137,12.791,124.06,12.567,123.962,12.595,123.878,12.69,123.895,12.805,123.949,12.916,123.918,12.94,123.864,12.931,123.802,12.906,123.736,12.897,123.627,12.912,123.402,13.033,123.311,13.044,123.29,13.099,123.296,13.216,123.206,13.354,123.192,13.403,123.163,13.442,122.896,13.592,122.863,13.617,122.781,13.737,122.595,13.908,122.543,13.925,122.486,13.93,122.468,13.887,122.494,13.82,122.504,13.763,122.5,13.703,122.508,13.657,122.596,13.562,122.609,13.517,122.668,13.395,122.675,13.253,122.6,13.194,122.515,13.26,122.513,13.314,122.498,13.364,122.407,13.493,122.377,13.521,122.205,13.648,122.073,13.788,121.778,13.938,121.743,13.946,121.692,13.935,121.643,13.916,121.501,13.842,121.451,13.791,121.446,13.712,121.344,13.649,121.204,13.64,121.096,13.679,121.006,13.758,120.932,13.762,120.841,13.885,120.729,13.901,120.637,13.804,120.617,13.995,120.617,14.188,120.643,14.244,120.688,14.291,120.922,14.493,120.952,14.558,120.941,14.645,120.888,14.716,120.804,14.759,120.708,14.777,120.638,14.816,120.584,14.881,120.547,14.766,120.583,14.595,120.589,14.483,120.556,14.441,120.496,14.44,120.439,14.453,120.396,14.493,120.365,14.608,120.284,14.684,120.251,14.793,120.214,14.809,120.138,14.8,120.082,14.851,120.045,14.978,120.037,15.115,120.005,15.229,119.959,15.34,119.933,15.431,119.892,15.838,119.881,15.875,119.86,15.906,119.808,15.952,119.769,16.008,119.762,16.055,119.773,16.255,119.79,16.303,119.831,16.327,119.886,16.287,119.93,16.239,119.985,16.215,120.033,16.185,120.124,16.066,120.16,16.048,120.271,16.051,120.337,16.066,120.369,16.11,120.389,16.161,120.389,16.222,120.325,16.4,120.305,16.529,120.304,16.645,120.321,16.762,120.409,16.956,120.42,17.09,120.412,17.27,120.427,17.377,120.425,17.438,120.372,17.535,120.358,17.638,120.505,18.163,120.551,18.264,120.584,18.369,120.6,18.508,120.709,18.546,120.814,18.603,120.868,18.599,120.925,18.585,121.051,18.614,121.102,18.615]],[[117.311,8.44,117.219,8.367,117.229,8.457,117.256,8.541,117.35,8.714,117.418,8.767,117.53,8.903,117.593,8.968,117.745,9.098,117.885,9.241,117.932,9.251,117.983,9.253,118.024,9.27,118.115,9.347,118.344,9.603,118.533,9.794,118.728,10.035,118.82,10.105,118.845,10.131,119.024,10.354,119.08,10.386,119.143,10.409,119.186,10.439,119.224,10.477,119.287,10.574,119.313,10.687,119.297,10.751,119.261,10.845,119.306,10.974,119.341,11.033,119.465,11.294,119.501,11.346,119.553,11.314,119.56,11.267,119.535,11.157,119.533,11.102,119.562,11.046,119.527,10.953,119.616,10.707,119.684,10.552,119.687,10.5,119.595,10.407,119.541,10.379,119.422,10.354,119.369,10.327,119.285,10.252,119.232,10.152,119.219,10.101,119.192,10.061,118.949,9.993,118.835,9.949,118.782,9.916,118.755,9.862,118.774,9.767,118.57,9.423,118.504,9.333,118.435,9.256,118.35,9.201,118.229,9.168,118.134,9.101,118.069,8.984,117.99,8.877,117.889,8.798,117.78,8.729,117.68,8.678,117.572,8.642,117.54,8.596,117.517,8.538,117.469,8.511,117.413,8.496,117.311,8.44]],[[122.496,11.615,122.613,11.564,122.726,11.608,122.838,11.596,122.931,11.529,122.901,11.487,122.895,11.441,123.103,11.541,123.158,11.536,123.156,11.443,123.144,11.364,123.12,11.287,123.075,11.197,123.017,11.117,122.939,11.058,122.847,11.022,122.803,10.99,122.789,10.941,122.791,10.88,122.77,10.824,122.673,10.801,122.522,10.692,122.198,10.623,122.109,10.576,122.052,10.514,121.988,10.458,121.954,10.444,121.938,10.471,121.934,10.494,121.98,10.639,121.972,10.699,121.95,10.757,121.964,10.872,122.021,10.979,122.051,11.097,122.06,11.326,122.104,11.643,122.101,11.681,122.067,11.724,121.941,11.758,121.891,11.791,121.916,11.854,121.964,11.897,122.029,11.895,122.087,11.855,122.291,11.772,122.399,11.702,122.496,11.615]],[[123.131,9.064,123.065,9.053,122.995,9.059,122.948,9.108,122.867,9.32,122.772,9.371,122.665,9.41,122.61,9.443,122.563,9.483,122.411,9.694,122.4,9.823,122.426,9.896,122.471,9.962,122.523,9.979,122.648,9.982,122.713,9.99,122.856,10.087,122.866,10.125,122.867,10.284,122.852,10.395,122.817,10.504,122.856,10.553,122.906,10.603,122.958,10.698,122.969,10.766,122.97,10.836,122.983,10.887,123.024,10.912,123.222,10.989,123.257,10.994,123.511,10.923,123.563,10.816,123.568,10.781,123.528,10.662,123.493,10.582,123.407,10.459,123.344,10.325,123.296,10.125,123.266,10.059,123.187,9.933,123.162,9.864,123.163,9.715,123.149,9.659,123.15,9.606,123.308,9.357,123.322,9.317,123.321,9.273,123.293,9.217,123.229,9.121,123.192,9.088,123.131,9.064]],[[124.575,11.343,124.644,11.308,124.724,11.322,124.821,11.401,124.93,11.373,124.994,11.256,125.027,11.212,125.044,11.135,125.04,10.952,125.013,10.786,125.034,10.751,125.084,10.722,125.128,10.685,125.164,10.637,125.188,10.585,125.197,10.457,125.26,10.35,125.268,10.308,125.253,10.264,125.148,10.272,125.14,10.235,125.143,10.189,125.105,10.218,125.044,10.323,124.988,10.368,125.005,10.197,125.024,10.115,125.027,10.033,124.929,10.096,124.813,10.135,124.781,10.168,124.792,10.275,124.79,10.328,124.738,10.44,124.799,10.682,124.797,10.732,124.787,10.781,124.739,10.88,124.663,10.962,124.616,10.962,124.503,10.904,124.446,10.924,124.412,11.15,124.366,11.371,124.331,11.427,124.308,11.486,124.331,11.535,124.374,11.515,124.436,11.457,124.511,11.424,124.548,11.395,124.575,11.343]],[[125.24,12.528,125.31,12.446,125.328,12.387,125.32,12.322,125.352,12.293,125.409,12.285,125.481,12.252,125.536,12.191,125.503,12.136,125.513,12.055,125.457,11.953,125.464,11.772,125.497,11.714,125.5,11.655,125.492,11.594,125.506,11.544,125.593,11.378,125.609,11.323,125.582,11.279,125.574,11.238,125.627,11.234,125.704,11.165,125.749,11.074,125.736,11.05,125.674,11.121,125.628,11.132,125.432,11.113,125.312,11.142,125.233,11.145,125.156,11.267,125.088,11.287,125.034,11.341,124.945,11.479,124.917,11.558,124.979,11.638,124.998,11.702,124.995,11.765,124.936,11.755,124.884,11.775,124.821,11.852,124.796,11.896,124.75,11.933,124.677,12.021,124.572,12.055,124.529,12.079,124.446,12.153,124.385,12.244,124.326,12.404,124.295,12.569,124.566,12.526,124.84,12.535,125.15,12.573,125.24,12.528]],[[120.704,13.479,120.755,13.471,120.915,13.501,120.981,13.486,121.025,13.429,121.079,13.411,121.122,13.381,121.203,13.432,121.284,13.374,121.357,13.265,121.442,13.188,121.523,13.131,121.539,13.089,121.49,13.02,121.475,12.932,121.48,12.837,121.541,12.638,121.519,12.584,121.458,12.508,121.412,12.423,121.418,12.389,121.4,12.361,121.394,12.301,121.357,12.313,121.322,12.304,121.289,12.277,121.237,12.219,121.155,12.236,121.117,12.253,121.108,12.304,121.083,12.339,121.049,12.36,120.963,12.447,120.922,12.512,120.921,12.581,120.899,12.646,120.855,12.704,120.796,12.748,120.776,12.791,120.769,12.841,120.764,12.97,120.68,13.131,120.651,13.169,120.573,13.209,120.508,13.26,120.481,13.311,120.455,13.394,120.438,13.405,120.388,13.402,120.338,13.412,120.353,13.473,120.401,13.517,120.468,13.522,120.653,13.498,120.704,13.479]],[[126.006,9.321,126.088,9.261,126.193,9.277,126.192,9.125,126.209,9.081,126.305,8.952,126.32,8.845,126.263,8.744,126.22,8.696,126.142,8.627,126.14,8.596,126.173,8.56,126.282,8.539,126.365,8.484,126.38,8.327,126.459,8.203,126.457,8.149,126.425,7.927,126.435,7.833,126.494,7.757,126.544,7.725,126.57,7.677,126.593,7.547,126.589,7.325,126.582,7.248,126.547,7.176,126.439,7.012,126.294,6.882,126.217,6.891,126.192,6.853,126.24,6.734,126.221,6.483,126.189,6.31,126.142,6.398,126.11,6.49,126.08,6.733,126.043,6.843,125.985,6.944,125.962,7.033,125.901,7.117,125.824,7.333,125.774,7.322,125.689,7.263,125.67,7.222,125.66,7.161,125.641,7.105,125.542,7.017,125.465,6.911,125.401,6.796,125.381,6.69,125.433,6.607,125.487,6.574,125.565,6.5,125.588,6.466,125.671,6.225,125.668,5.979,125.608,5.87,125.456,5.664,125.346,5.599,125.288,5.632,125.241,5.757,125.233,5.808,125.265,5.926,125.268,6.033,125.232,6.07,125.191,6.063,125.174,6.047,125.076,5.906,125.035,5.871,124.975,5.866,124.927,5.875,124.636,5.998,124.399,6.12,124.213,6.233,124.078,6.404,124.05,6.533,124.048,6.667,123.988,6.863,123.981,6.93,123.985,6.994,124.045,7.114,124.118,7.175,124.158,7.219,124.191,7.267,124.213,7.332,124.207,7.396,124.182,7.437,124.068,7.578,123.968,7.665,123.765,7.743,123.717,7.785,123.666,7.818,123.609,7.832,123.553,7.832,123.493,7.808,123.477,7.756,123.482,7.71,123.476,7.665,123.391,7.408,123.282,7.464,123.178,7.529,123.151,7.575,123.139,7.63,123.121,7.667,123.097,7.7,123.049,7.614,122.99,7.546,122.917,7.531,122.843,7.529,122.819,7.558,122.792,7.722,122.714,7.774,122.616,7.763,122.498,7.673,122.474,7.639,122.449,7.561,122.32,7.34,122.251,7.17,122.176,7.004,122.142,6.95,122.098,6.914,122.028,6.929,121.964,6.968,121.904,7.075,121.925,7.2,121.991,7.279,122.047,7.364,122.115,7.66,122.12,7.765,122.132,7.81,122.243,7.945,122.337,8.028,122.387,8.046,122.589,8.093,122.673,8.133,122.804,8.134,122.911,8.156,122.996,8.221,123.003,8.287,122.999,8.356,123.018,8.398,123.051,8.434,123.096,8.481,123.147,8.516,123.293,8.541,123.341,8.57,123.38,8.616,123.435,8.703,123.499,8.682,123.564,8.647,123.68,8.621,123.783,8.548,123.849,8.433,123.861,8.376,123.877,8.189,123.853,8.145,123.753,8.058,123.799,8.049,123.931,8.128,123.997,8.159,124.159,8.201,124.198,8.23,124.226,8.271,124.283,8.386,124.325,8.508,124.358,8.559,124.405,8.6,124.451,8.606,124.622,8.523,124.731,8.563,124.762,8.69,124.787,8.874,124.806,8.924,124.869,8.972,124.944,8.957,125.046,8.891,125.141,8.869,125.176,8.922,125.21,9.027,125.248,9.027,125.376,8.992,125.499,9.015,125.533,9.141,125.51,9.276,125.414,9.669,125.471,9.757,125.521,9.759,125.642,9.654,125.877,9.513,125.955,9.427,126.006,9.321]],[[123.37,9.45,123.332,9.423,123.316,9.489,123.327,9.578,123.404,9.889,123.386,9.967,123.514,10.14,123.593,10.303,123.711,10.474,123.726,10.562,123.832,10.731,123.93,10.964,123.925,11.041,123.95,11.079,123.964,11.137,123.967,11.187,124.039,11.274,124.058,11.217,124.037,11.107,124.04,11.054,124.053,11.029,124.053,10.926,124.028,10.768,124.051,10.586,124.005,10.4,123.952,10.317,123.874,10.258,123.789,10.221,123.7,10.128,123.643,10.02,123.634,9.922,123.494,9.589,123.37,9.45]],[[121.922,18.895,121.858,18.823,121.825,18.843,121.861,18.913,121.86,18.937,121.889,18.992,121.943,19.01,121.988,18.957,121.922,18.895]],[[121.521,19.362,121.531,19.271,121.472,19.273,121.383,19.328,121.375,19.356,121.376,19.38,121.392,19.399,121.521,19.362]],[[121.96,20.366,121.941,20.354,121.914,20.359,121.941,20.454,121.991,20.48,122.031,20.469,121.96,20.366]],[[121.878,20.782,121.83,20.7,121.791,20.701,121.796,20.747,121.848,20.841,121.867,20.839,121.878,20.782]],[[121.159,6.076,121.214,6.004,121.283,6.022,121.392,6.002,121.415,5.965,121.411,5.94,121.294,5.87,121.218,5.943,121.083,5.893,121.019,5.923,120.931,5.896,120.876,5.953,120.898,6.007,121.038,6.096,121.159,6.076]],[[117.08,7.883,117.028,7.808,116.97,7.895,116.976,8.017,116.994,8.051,117.077,8.069,117.08,7.883]],[[119.916,10.486,119.793,10.455,119.764,10.552,119.852,10.64,119.95,10.605,120.008,10.57,119.981,10.539,119.916,10.486]],[[120.1,12.168,120.155,12.152,120.194,12.167,120.228,12.22,120.261,12.142,120.341,12.077,120.315,12.012,120.243,12.005,120.174,12.02,120.1,11.994,120.011,12.008,119.957,12.069,119.896,12.179,119.866,12.199,119.87,12.244,119.892,12.273,119.88,12.28,119.886,12.3,119.897,12.313,119.916,12.319,119.964,12.27,120.078,12.198,120.1,12.168]],[[120.039,11.703,119.964,11.669,119.945,11.691,119.932,11.74,119.933,11.774,119.861,11.954,119.916,11.981,119.957,11.96,119.998,11.932,120.036,11.917,120.071,11.861,120.062,11.821,120.073,11.783,120.039,11.703]],[[122.65,10.473,122.622,10.459,122.597,10.461,122.538,10.425,122.517,10.493,122.538,10.608,122.626,10.695,122.648,10.723,122.673,10.739,122.701,10.741,122.729,10.706,122.737,10.655,122.681,10.498,122.65,10.473]],[[124.594,9.787,124.584,9.75,124.506,9.754,124.478,9.748,124.403,9.654,124.36,9.63,124.122,9.599,123.936,9.624,123.872,9.676,123.83,9.761,123.817,9.817,123.864,9.879,123.909,9.92,124.06,10,124.094,10.061,124.173,10.135,124.336,10.16,124.352,10.141,124.373,10.13,124.406,10.126,124.486,10.065,124.577,10.027,124.555,9.879,124.582,9.83,124.594,9.787]],[[120.25,5.257,120.223,5.196,120.192,5.168,120.15,5.184,120.118,5.215,120.101,5.169,120.013,5.151,119.958,5.08,119.878,5.06,119.821,5.07,119.827,5.133,119.983,5.228,120.08,5.264,120.165,5.332,120.208,5.34,120.229,5.284,120.25,5.257]],[[122.093,6.428,121.991,6.415,121.959,6.416,121.88,6.518,121.872,6.563,121.809,6.614,121.832,6.664,121.915,6.676,122.058,6.741,122.288,6.639,122.324,6.602,122.252,6.58,122.201,6.483,122.093,6.428]],[[126.059,9.766,126.047,9.761,125.991,9.839,125.999,9.927,126.074,10.059,126.129,9.944,126.129,9.891,126.121,9.865,126.173,9.8,126.137,9.768,126.059,9.766]],[[125.69,9.914,125.673,9.886,125.649,9.944,125.591,9.998,125.534,10.09,125.495,10.119,125.522,10.192,125.525,10.31,125.58,10.364,125.606,10.38,125.648,10.437,125.667,10.44,125.685,10.392,125.647,10.245,125.703,10.072,125.684,9.963,125.692,9.939,125.69,9.914]],[[120.271,13.751,120.273,13.683,120.104,13.782,120.099,13.817,120.103,13.843,120.121,13.858,120.211,13.821,120.271,13.751]],[[121.915,13.54,121.977,13.537,121.996,13.547,122.115,13.463,122.107,13.421,122.122,13.365,122.055,13.269,122.042,13.236,122.005,13.205,121.876,13.282,121.829,13.329,121.815,13.424,121.866,13.566,121.915,13.54]],[[122.094,12.355,122.014,12.106,121.96,12.191,121.982,12.245,121.936,12.29,121.923,12.331,121.941,12.385,121.989,12.435,122.002,12.599,122.104,12.651,122.145,12.653,122.13,12.613,122.132,12.538,122.094,12.355]],[[122.654,12.309,122.603,12.286,122.499,12.384,122.439,12.429,122.423,12.455,122.472,12.492,122.604,12.492,122.674,12.424,122.683,12.382,122.654,12.309]],[[123.282,12.853,123.367,12.701,123.274,12.805,123.166,12.876,123.054,12.993,122.973,13.035,122.949,13.059,122.958,13.107,123.017,13.116,123.044,13.113,123.206,12.905,123.282,12.853]],[[123.775,12.454,123.779,12.366,123.742,12.399,123.621,12.571,123.587,12.633,123.621,12.675,123.709,12.611,123.775,12.454]],[[123.717,12.287,123.908,12.169,124.04,11.967,124.056,11.812,124.046,11.752,123.983,11.819,123.848,11.914,123.754,11.934,123.725,11.952,123.736,12.003,123.675,12.05,123.668,12.069,123.612,12.09,123.531,12.197,123.474,12.217,123.419,12.194,123.293,12.036,123.158,11.926,123.156,11.968,123.211,12.107,123.245,12.328,123.267,12.395,123.24,12.495,123.236,12.583,123.337,12.542,123.463,12.501,123.559,12.445,123.575,12.407,123.717,12.287]],[[124.354,13.632,124.327,13.567,124.295,13.59,124.248,13.587,124.175,13.532,124.057,13.606,124.039,13.663,124.124,13.79,124.123,13.98,124.154,14.026,124.186,14.06,124.225,14.078,124.308,13.947,124.337,13.931,124.417,13.871,124.396,13.75,124.404,13.679,124.354,13.632]],[[122.175,14.049,122.172,14.008,121.956,14.156,121.946,14.181,121.946,14.205,121.959,14.229,122.175,14.049]],[[122.033,15.005,122.052,14.97,122.032,14.972,122.017,14.965,121.97,14.893,122.022,14.759,121.99,14.662,121.933,14.656,121.911,14.667,121.922,14.715,121.935,14.737,121.923,14.8,121.889,14.84,121.862,14.917,121.82,14.964,121.84,15.038,121.972,15.046,122.033,15.005]],[[124.316,10.606,124.288,10.601,124.335,10.707,124.371,10.691,124.382,10.68,124.381,10.633,124.316,10.606]],[[125.281,9.982,125.288,9.933,125.159,10.063,125.133,10.155,125.176,10.151,125.231,10.116,125.281,9.982]],[[122.937,7.409,122.948,7.386,122.944,7.361,122.84,7.315,122.805,7.316,122.797,7.393,122.822,7.428,122.871,7.397,122.915,7.433,122.937,7.409]],[[125.785,6.963,125.769,6.906,125.708,7.04,125.683,7.073,125.714,7.186,125.783,7.131,125.785,6.963]],[[124.608,11.492,124.483,11.486,124.429,11.532,124.36,11.666,124.437,11.695,124.511,11.687,124.565,11.64,124.622,11.55,124.608,11.492]],[[124.807,9.143,124.778,9.083,124.666,9.132,124.639,9.175,124.653,9.226,124.708,9.243,124.737,9.243,124.79,9.19,124.807,9.143]],[[121.252,19.082,121.247,19.015,121.196,19.051,121.185,19.101,121.19,19.139,121.213,19.184,121.245,19.143,121.252,19.082]],[[119.861,11.525,119.883,11.472,119.855,11.393,119.831,11.376,119.799,11.409,119.73,11.432,119.726,11.475,119.761,11.474,119.827,11.515,119.861,11.525]],[[123.757,11.283,123.816,11.151,123.737,11.151,123.708,11.248,123.741,11.279,123.757,11.283]],[[122.311,12.529,122.28,12.498,122.261,12.503,122.248,12.557,122.278,12.593,122.288,12.589,122.311,12.529]],[[125.971,9.594,125.952,9.568,125.922,9.621,125.949,9.739,125.968,9.759,125.993,9.685,125.971,9.594]],[[124.854,11.595,124.836,11.543,124.807,11.558,124.781,11.581,124.744,11.659,124.731,11.715,124.788,11.683,124.821,11.627,124.854,11.595]],[[117.355,8.215,117.287,8.191,117.272,8.254,117.281,8.315,117.33,8.308,117.354,8.289,117.355,8.215]],[[123.698,9.237,123.706,9.134,123.614,9.103,123.541,9.13,123.493,9.192,123.494,9.216,123.535,9.214,123.626,9.268,123.655,9.279,123.698,9.237]]]},{"n":"Peru","c":"PE","r":2,"ml":2,"lx":-72.9,"ly":-12.98,"b":[-81.337,-18.346,-68.685,-0.042],"p":[[[-69.966,-4.236,-69.972,-4.301,-70.004,-4.327,-70.053,-4.333,-70.129,-4.287,-70.184,-4.298,-70.239,-4.301,-70.317,-4.247,-70.344,-4.194,-70.405,-4.15,-70.531,-4.168,-70.635,-4.169,-70.722,-4.159,-70.8,-4.173,-70.866,-4.23,-70.916,-4.295,-70.974,-4.35,-71.144,-4.387,-71.235,-4.388,-71.317,-4.424,-71.438,-4.438,-71.521,-4.47,-71.668,-4.487,-71.845,-4.504,-71.943,-4.553,-71.982,-4.575,-72.083,-4.642,-72.257,-4.749,-72.353,-4.786,-72.469,-4.901,-72.608,-5.01,-72.699,-5.067,-72.832,-5.094,-72.887,-5.123,-72.907,-5.158,-72.896,-5.198,-72.918,-5.303,-72.959,-5.495,-72.97,-5.59,-72.98,-5.635,-73.068,-5.79,-73.163,-5.933,-73.209,-6.029,-73.236,-6.098,-73.206,-6.156,-73.168,-6.261,-73.135,-6.344,-73.126,-6.401,-73.137,-6.466,-73.177,-6.525,-73.24,-6.564,-73.325,-6.575,-73.5,-6.679,-73.695,-6.834,-73.758,-6.906,-73.776,-6.974,-73.805,-7.08,-73.793,-7.135,-73.758,-7.173,-73.723,-7.263,-73.72,-7.309,-73.749,-7.335,-73.805,-7.341,-73.854,-7.35,-73.892,-7.373,-73.929,-7.367,-73.964,-7.379,-73.964,-7.417,-73.953,-7.46,-73.958,-7.507,-73.982,-7.536,-74.002,-7.556,-73.982,-7.585,-73.947,-7.611,-73.895,-7.655,-73.822,-7.739,-73.767,-7.754,-73.72,-7.783,-73.715,-7.829,-73.732,-7.875,-73.773,-7.896,-73.776,-7.936,-73.72,-7.986,-73.683,-8.021,-73.645,-8.073,-73.61,-8.145,-73.61,-8.192,-73.572,-8.25,-73.549,-8.299,-73.549,-8.346,-73.488,-8.392,-73.436,-8.427,-73.398,-8.459,-73.36,-8.479,-73.352,-8.514,-73.357,-8.567,-73.302,-8.654,-73.203,-8.719,-73.123,-8.814,-73.071,-8.883,-72.974,-8.993,-72.97,-9.12,-73.09,-9.266,-73.209,-9.411,-73.014,-9.407,-72.814,-9.41,-72.605,-9.452,-72.465,-9.492,-72.379,-9.51,-72.318,-9.557,-72.289,-9.629,-72.266,-9.688,-72.26,-9.774,-72.173,-9.844,-72.179,-9.91,-72.182,-10.004,-72.143,-10.005,-71.887,-10.006,-71.608,-10.006,-71.339,-9.989,-71.238,-9.966,-71.115,-9.852,-71.042,-9.819,-70.971,-9.766,-70.885,-9.669,-70.816,-9.625,-70.758,-9.572,-70.672,-9.518,-70.637,-9.478,-70.608,-9.464,-70.541,-9.437,-70.57,-9.49,-70.592,-9.543,-70.599,-9.621,-70.567,-9.705,-70.594,-9.767,-70.637,-9.824,-70.638,-9.972,-70.639,-10.182,-70.639,-10.361,-70.64,-10.586,-70.642,-10.841,-70.642,-11.01,-70.597,-10.977,-70.533,-10.947,-70.451,-11.025,-70.392,-11.059,-70.342,-11.067,-70.29,-11.064,-70.22,-11.048,-70.066,-10.982,-69.96,-10.93,-69.84,-10.933,-69.674,-10.954,-69.579,-10.952,-69.454,-11.169,-69.362,-11.328,-69.258,-11.509,-69.174,-11.654,-69.046,-11.876,-68.936,-12.067,-68.819,-12.27,-68.685,-12.502,-68.728,-12.561,-68.763,-12.608,-68.759,-12.687,-68.812,-12.73,-68.868,-12.755,-68.934,-12.822,-68.979,-12.88,-68.981,-12.963,-68.972,-13.382,-68.983,-13.496,-69.018,-13.594,-69.053,-13.644,-69.074,-13.683,-69.023,-13.78,-68.974,-13.976,-68.937,-14.015,-68.892,-14.094,-68.871,-14.17,-68.88,-14.199,-68.972,-14.234,-69.004,-14.265,-69.013,-14.377,-69.053,-14.418,-69.12,-14.47,-69.163,-14.531,-69.199,-14.573,-69.235,-14.597,-69.252,-14.671,-69.276,-14.746,-69.359,-14.795,-69.374,-14.887,-69.375,-14.963,-69.331,-15.039,-69.187,-15.199,-69.172,-15.237,-69.254,-15.333,-69.302,-15.399,-69.419,-15.603,-69.421,-15.641,-69.392,-15.737,-69.218,-16.149,-69.188,-16.183,-69.134,-16.222,-69.046,-16.218,-68.913,-16.262,-68.849,-16.313,-68.843,-16.338,-68.858,-16.355,-68.928,-16.389,-69.006,-16.434,-69.033,-16.476,-69.038,-16.543,-69.021,-16.642,-69.055,-16.674,-69.133,-16.713,-69.2,-16.768,-69.267,-16.861,-69.382,-17.001,-69.421,-17.04,-69.438,-17.088,-69.503,-17.105,-69.625,-17.2,-69.646,-17.249,-69.626,-17.294,-69.564,-17.333,-69.522,-17.389,-69.511,-17.46,-69.511,-17.505,-69.511,-17.506,-69.586,-17.573,-69.685,-17.65,-69.806,-17.665,-69.852,-17.704,-69.842,-17.785,-69.802,-17.9,-69.803,-17.99,-69.84,-18.093,-69.926,-18.206,-70.059,-18.283,-70.184,-18.325,-70.282,-18.325,-70.377,-18.334,-70.418,-18.346,-70.492,-18.278,-70.817,-18.053,-70.942,-17.932,-71.057,-17.876,-71.337,-17.683,-71.365,-17.621,-71.399,-17.422,-71.436,-17.366,-71.532,-17.294,-71.774,-17.199,-71.868,-17.151,-71.967,-17.064,-72.111,-17.003,-72.269,-16.876,-72.362,-16.775,-72.468,-16.708,-72.794,-16.615,-72.958,-16.521,-73.264,-16.389,-73.4,-16.304,-73.728,-16.202,-73.825,-16.153,-74.147,-15.912,-74.373,-15.834,-74.555,-15.699,-75.104,-15.412,-75.191,-15.32,-75.275,-15.178,-75.397,-15.094,-75.534,-14.899,-75.738,-14.785,-75.934,-14.634,-76.006,-14.496,-76.136,-14.32,-76.175,-14.227,-76.289,-14.133,-76.297,-13.948,-76.376,-13.863,-76.319,-13.821,-76.259,-13.803,-76.184,-13.515,-76.224,-13.371,-76.427,-13.11,-76.502,-12.984,-76.555,-12.823,-76.637,-12.728,-76.758,-12.527,-76.832,-12.349,-76.994,-12.219,-77.038,-12.173,-77.063,-12.107,-77.153,-12.06,-77.158,-11.923,-77.22,-11.663,-77.31,-11.532,-77.633,-11.288,-77.639,-11.194,-77.664,-11.022,-77.736,-10.837,-78.095,-10.261,-78.186,-10.089,-78.276,-9.81,-78.356,-9.652,-78.446,-9.371,-78.58,-9.157,-78.665,-8.971,-78.755,-8.74,-78.762,-8.617,-78.925,-8.405,-79.012,-8.21,-79.164,-8.047,-79.313,-7.923,-79.377,-7.836,-79.589,-7.419,-79.618,-7.296,-79.762,-7.067,-79.905,-6.902,-79.995,-6.769,-80.11,-6.65,-80.812,-6.282,-81.058,-6.129,-81.142,-6.057,-81.181,-5.942,-81.164,-5.875,-81.092,-5.812,-80.992,-5.861,-80.931,-5.841,-80.883,-5.759,-80.882,-5.635,-80.943,-5.475,-81.168,-5.167,-81.151,-5.102,-81.108,-5.028,-81.195,-4.879,-81.289,-4.761,-81.337,-4.67,-81.283,-4.322,-81.232,-4.234,-80.892,-3.882,-80.799,-3.731,-80.653,-3.638,-80.504,-3.496,-80.325,-3.388,-80.298,-3.406,-80.274,-3.425,-80.272,-3.461,-80.265,-3.492,-80.245,-3.522,-80.244,-3.577,-80.221,-3.613,-80.219,-3.654,-80.217,-3.711,-80.229,-3.739,-80.218,-3.788,-80.179,-3.878,-80.194,-3.906,-80.231,-3.924,-80.267,-3.949,-80.303,-4.005,-80.358,-4.003,-80.437,-3.979,-80.49,-4.01,-80.51,-4.07,-80.493,-4.119,-80.488,-4.166,-80.454,-4.205,-80.353,-4.208,-80.444,-4.336,-80.488,-4.394,-80.479,-4.43,-80.424,-4.461,-80.383,-4.464,-80.293,-4.417,-80.232,-4.349,-80.197,-4.311,-80.14,-4.296,-80.064,-4.328,-79.963,-4.39,-79.845,-4.446,-79.797,-4.476,-79.711,-4.468,-79.639,-4.455,-79.578,-4.501,-79.516,-4.539,-79.502,-4.671,-79.456,-4.766,-79.399,-4.84,-79.331,-4.928,-79.268,-4.958,-79.187,-4.958,-79.076,-4.991,-79.033,-4.969,-78.995,-4.908,-78.975,-4.873,-78.919,-4.858,-78.914,-4.819,-78.926,-4.771,-78.908,-4.714,-78.862,-4.665,-78.743,-4.593,-78.686,-4.562,-78.674,-4.518,-78.653,-4.458,-78.661,-4.425,-78.685,-4.384,-78.679,-4.326,-78.648,-4.248,-78.603,-4.157,-78.565,-4.042,-78.55,-3.987,-78.509,-3.952,-78.493,-3.902,-78.471,-3.843,-78.42,-3.777,-78.421,-3.706,-78.4,-3.674,-78.398,-3.595,-78.347,-3.431,-78.345,-3.397,-78.323,-3.388,-78.284,-3.399,-78.251,-3.436,-78.24,-3.473,-78.226,-3.489,-78.195,-3.486,-78.158,-3.465,-78.161,-3.432,-78.187,-3.4,-78.195,-3.38,-78.183,-3.35,-78.128,-3.284,-78.068,-3.207,-77.938,-3.047,-77.861,-2.982,-77.659,-2.912,-77.506,-2.86,-77.36,-2.81,-77.161,-2.738,-76.881,-2.636,-76.679,-2.563,-76.499,-2.432,-76.36,-2.331,-76.241,-2.244,-76.09,-2.133,-75.885,-1.893,-75.745,-1.728,-75.642,-1.607,-75.571,-1.531,-75.514,-1.316,-75.449,-1.071,-75.42,-0.962,-75.408,-0.924,-75.38,-0.94,-75.348,-0.967,-75.309,-0.968,-75.272,-0.967,-75.25,-0.952,-75.284,-0.707,-75.279,-0.654,-75.259,-0.59,-75.263,-0.555,-75.325,-0.507,-75.425,-0.409,-75.466,-0.322,-75.491,-0.248,-75.561,-0.2,-75.632,-0.158,-75.626,-0.123,-75.584,-0.123,-75.476,-0.157,-75.398,-0.146,-75.34,-0.142,-75.284,-0.107,-75.225,-0.042,-75.184,-0.042,-75.138,-0.05,-75.055,-0.117,-75.005,-0.156,-74.945,-0.188,-74.889,-0.199,-74.837,-0.203,-74.802,-0.2,-74.78,-0.245,-74.755,-0.299,-74.692,-0.335,-74.616,-0.37,-74.555,-0.43,-74.514,-0.47,-74.465,-0.518,-74.418,-0.581,-74.375,-0.691,-74.353,-0.767,-74.329,-0.808,-74.334,-0.851,-74.284,-0.928,-74.246,-0.971,-74.181,-0.998,-74.054,-1.029,-73.987,-1.098,-73.927,-1.125,-73.863,-1.197,-73.807,-1.218,-73.736,-1.214,-73.664,-1.249,-73.61,-1.316,-73.575,-1.401,-73.521,-1.45,-73.494,-1.537,-73.525,-1.639,-73.496,-1.693,-73.44,-1.737,-73.35,-1.784,-73.266,-1.772,-73.224,-1.788,-73.197,-1.83,-73.181,-1.88,-73.145,-2.003,-73.127,-2.081,-73.16,-2.156,-73.173,-2.208,-73.154,-2.278,-73.068,-2.312,-72.99,-2.34,-72.941,-2.394,-72.887,-2.408,-72.811,-2.405,-72.714,-2.392,-72.66,-2.361,-72.625,-2.352,-72.587,-2.365,-72.501,-2.395,-72.396,-2.429,-72.301,-2.409,-72.218,-2.4,-72.137,-2.381,-72.054,-2.325,-71.984,-2.327,-71.932,-2.289,-71.867,-2.228,-71.803,-2.166,-71.753,-2.153,-71.671,-2.182,-71.559,-2.224,-71.496,-2.279,-71.447,-2.294,-71.397,-2.334,-71.3,-2.335,-71.196,-2.313,-71.113,-2.245,-71.027,-2.226,-70.969,-2.207,-70.915,-2.219,-70.705,-2.342,-70.648,-2.406,-70.576,-2.418,-70.517,-2.453,-70.418,-2.491,-70.364,-2.529,-70.295,-2.553,-70.244,-2.607,-70.165,-2.64,-70.096,-2.658,-70.065,-2.702,-70.064,-2.731,-70.074,-2.75,-70.147,-2.864,-70.29,-3.087,-70.419,-3.288,-70.622,-3.605,-70.735,-3.782,-70.706,-3.789,-70.53,-3.866,-70.486,-3.869,-70.421,-3.85,-70.379,-3.819,-70.34,-3.814,-70.298,-3.844,-70.24,-3.883,-70.198,-3.995,-70.168,-4.05,-70.095,-4.092,-70.017,-4.162,-69.966,-4.236]]]},{"n":"Paraguay","c":"PY","r":4,"ml":3,"lx":-60.15,"ly":-21.67,"b":[-62.651,-27.554,-54.242,-19.286],"p":[[[-58.16,-20.165,-58.138,-20.237,-58.125,-20.293,-58.092,-20.333,-58.058,-20.386,-58.025,-20.416,-58.002,-20.465,-58.009,-20.522,-57.996,-20.594,-57.979,-20.657,-57.962,-20.674,-57.915,-20.69,-57.891,-20.747,-57.908,-20.776,-57.902,-20.809,-57.885,-20.842,-57.9,-20.873,-57.892,-20.897,-57.86,-20.919,-57.83,-20.998,-57.827,-21.134,-57.86,-21.206,-57.886,-21.266,-57.893,-21.302,-57.873,-21.355,-57.906,-21.418,-57.946,-21.494,-57.936,-21.547,-57.929,-21.597,-57.926,-21.65,-57.916,-21.699,-57.929,-21.752,-57.943,-21.798,-57.949,-21.851,-57.933,-21.911,-57.962,-21.967,-57.979,-22.007,-57.986,-22.046,-57.956,-22.109,-57.88,-22.136,-57.82,-22.142,-57.764,-22.109,-57.721,-22.099,-57.642,-22.129,-57.569,-22.182,-57.476,-22.189,-57.394,-22.198,-57.331,-22.215,-57.238,-22.195,-57.142,-22.215,-57.03,-22.245,-56.937,-22.271,-56.845,-22.265,-56.775,-22.261,-56.702,-22.232,-56.633,-22.235,-56.58,-22.182,-56.55,-22.136,-56.524,-22.103,-56.448,-22.076,-56.395,-22.093,-56.352,-22.179,-56.276,-22.228,-56.246,-22.265,-56.19,-22.281,-56.067,-22.284,-55.991,-22.281,-55.905,-22.308,-55.849,-22.308,-55.8,-22.354,-55.753,-22.41,-55.747,-22.513,-55.704,-22.592,-55.647,-22.622,-55.618,-22.671,-55.628,-22.741,-55.654,-22.81,-55.651,-22.886,-55.621,-22.956,-55.621,-23.025,-55.601,-23.095,-55.561,-23.154,-55.548,-23.25,-55.555,-23.32,-55.528,-23.359,-55.518,-23.416,-55.535,-23.462,-55.542,-23.525,-55.538,-23.581,-55.518,-23.627,-55.459,-23.687,-55.442,-23.793,-55.442,-23.865,-55.416,-23.951,-55.366,-23.991,-55.287,-24.004,-55.194,-24.017,-55.082,-23.998,-54.983,-23.975,-54.926,-23.951,-54.817,-23.888,-54.721,-23.852,-54.672,-23.829,-54.625,-23.812,-54.53,-23.852,-54.44,-23.902,-54.371,-23.971,-54.242,-24.047,-54.267,-24.066,-54.318,-24.128,-54.317,-24.201,-54.281,-24.306,-54.313,-24.528,-54.413,-24.867,-54.454,-25.065,-54.436,-25.121,-54.473,-25.22,-54.611,-25.433,-54.616,-25.576,-54.632,-26.006,-54.678,-26.309,-54.755,-26.533,-54.825,-26.652,-54.889,-26.667,-54.934,-26.703,-54.962,-26.759,-55.014,-26.807,-55.089,-26.845,-55.13,-26.886,-55.136,-26.931,-55.208,-26.96,-55.346,-26.973,-55.427,-27.009,-55.451,-27.068,-55.497,-27.115,-55.565,-27.15,-55.597,-27.208,-55.594,-27.288,-55.633,-27.357,-55.715,-27.415,-55.79,-27.416,-55.859,-27.362,-55.951,-27.326,-56.067,-27.308,-56.164,-27.321,-56.242,-27.367,-56.311,-27.439,-56.371,-27.537,-56.437,-27.554,-56.511,-27.488,-56.603,-27.468,-56.716,-27.494,-56.805,-27.485,-56.872,-27.441,-56.974,-27.436,-57.112,-27.47,-57.391,-27.43,-57.812,-27.317,-58.168,-27.273,-58.605,-27.314,-58.642,-27.196,-58.619,-27.132,-58.548,-27.084,-58.503,-27.029,-58.485,-26.968,-58.436,-26.922,-58.356,-26.89,-58.323,-26.858,-58.335,-26.825,-58.318,-26.796,-58.272,-26.771,-58.246,-26.731,-58.239,-26.677,-58.222,-26.65,-58.191,-26.63,-58.188,-26.593,-58.205,-26.477,-58.203,-26.381,-58.181,-26.307,-58.155,-26.263,-58.136,-26.251,-58.118,-26.225,-58.111,-26.18,-58.082,-26.139,-57.943,-26.053,-57.891,-26.007,-57.886,-25.964,-57.865,-25.907,-57.782,-25.784,-57.757,-25.726,-57.755,-25.697,-57.725,-25.667,-57.626,-25.599,-57.572,-25.534,-57.563,-25.474,-57.587,-25.405,-57.644,-25.328,-57.822,-25.136,-57.96,-25.049,-58.136,-24.977,-58.253,-24.954,-58.309,-24.979,-58.365,-24.959,-58.423,-24.894,-58.52,-24.843,-58.724,-24.787,-59.187,-24.562,-59.373,-24.454,-59.435,-24.387,-59.609,-24.267,-59.892,-24.094,-60.11,-24.009,-60.262,-24.014,-60.505,-23.964,-60.84,-23.858,-61.033,-23.756,-61.085,-23.656,-61.208,-23.557,-61.404,-23.458,-61.506,-23.392,-61.513,-23.36,-61.571,-23.319,-61.679,-23.269,-61.799,-23.182,-61.928,-23.059,-62.067,-22.869,-62.214,-22.612,-62.373,-22.439,-62.542,-22.35,-62.626,-22.29,-62.626,-22.262,-62.651,-22.234,-62.629,-22.184,-62.567,-21.989,-62.478,-21.705,-62.385,-21.412,-62.277,-21.066,-62.277,-20.821,-62.276,-20.562,-62.122,-20.35,-62.012,-20.199,-61.917,-20.055,-61.821,-19.809,-61.757,-19.645,-61.512,-19.606,-61.096,-19.521,-60.889,-19.479,-60.452,-19.389,-60.007,-19.298,-59.541,-19.292,-59.091,-19.286,-58.741,-19.49,-58.474,-19.646,-58.18,-19.818,-58.16,-19.855,-58.14,-19.999,-58.16,-20.165]]]},{"n":"Papua New Guinea","c":"PG","r":2,"ml":2.5,"lx":143.91,"ly":-5.7,"b":[140.862,-11.631,155.958,-1.353],"p":[[[152.966,-4.756,152.892,-4.832,152.846,-4.762,152.787,-4.699,152.74,-4.636,152.681,-4.498,152.678,-4.429,152.693,-4.356,152.697,-4.282,152.668,-4.132,152.598,-3.995,152.356,-3.668,152.279,-3.582,152.192,-3.506,152.136,-3.487,152.023,-3.469,151.973,-3.453,151.88,-3.4,151.793,-3.338,151.579,-3.154,151.465,-3.101,151.405,-3.037,151.067,-2.829,150.968,-2.78,150.848,-2.78,150.746,-2.739,150.826,-2.713,150.843,-2.644,150.825,-2.573,150.995,-2.688,151.175,-2.789,151.226,-2.87,151.315,-2.875,151.475,-2.942,151.586,-3.003,151.69,-3.073,151.807,-3.173,152.033,-3.251,152.065,-3.28,152.179,-3.41,152.329,-3.521,152.38,-3.582,153.017,-4.106,153.124,-4.252,153.133,-4.352,153.112,-4.392,153.044,-4.476,153.046,-4.576,153.023,-4.666,152.966,-4.756]],[[151.916,-4.297,151.968,-4.317,152.117,-4.212,152.197,-4.285,152.299,-4.321,152.406,-4.341,152.364,-4.491,152.376,-4.56,152.404,-4.629,152.4,-4.731,152.351,-4.822,152.258,-4.955,152.216,-4.979,152.167,-4.993,152.013,-5.004,151.984,-5.074,151.994,-5.149,152.077,-5.247,152.143,-5.357,152.077,-5.458,151.968,-5.529,151.865,-5.565,151.695,-5.544,151.515,-5.552,151.481,-5.591,151.48,-5.655,151.455,-5.703,151.422,-5.747,151.331,-5.839,151.229,-5.92,151.09,-5.997,151.043,-6.015,150.92,-6.027,150.809,-6.071,150.76,-6.114,150.706,-6.149,150.588,-6.188,150.474,-6.263,150.428,-6.276,150.191,-6.289,149.851,-6.293,149.75,-6.301,149.653,-6.29,149.598,-6.261,149.483,-6.125,149.382,-6.078,149.273,-6.079,149.127,-6.128,149.099,-6.117,148.808,-5.916,148.719,-5.867,148.625,-5.831,148.51,-5.805,148.401,-5.765,148.337,-5.669,148.345,-5.545,148.432,-5.472,148.565,-5.508,148.616,-5.507,148.666,-5.487,148.724,-5.493,148.783,-5.512,148.999,-5.485,149.124,-5.523,149.245,-5.573,149.359,-5.584,149.475,-5.573,149.632,-5.516,149.681,-5.524,149.831,-5.524,149.963,-5.448,150.012,-5.14,150.045,-5.035,150.09,-5.012,150.122,-5.018,150.17,-5.071,150.109,-5.136,150.082,-5.186,150.072,-5.31,150.106,-5.429,150.183,-5.524,150.299,-5.536,150.404,-5.473,150.519,-5.46,150.626,-5.521,150.734,-5.51,150.784,-5.471,150.843,-5.454,150.9,-5.447,150.953,-5.424,151.022,-5.321,151.069,-5.204,151.138,-5.113,151.327,-4.96,151.381,-4.941,151.44,-4.931,151.573,-4.937,151.671,-4.883,151.679,-4.761,151.665,-4.637,151.552,-4.346,151.544,-4.299,151.561,-4.247,151.593,-4.201,151.704,-4.2,151.819,-4.217,151.865,-4.261,151.916,-4.297]],[[140.976,-9.119,140.976,-9.106,140.976,-8.902,140.976,-8.699,140.976,-8.496,140.976,-8.292,140.976,-8.089,140.976,-7.886,140.975,-7.683,140.975,-7.479,140.975,-7.276,140.975,-7.073,140.975,-6.905,140.92,-6.84,140.862,-6.74,140.875,-6.612,140.944,-6.452,140.975,-6.346,140.975,-6.259,140.975,-6.056,140.975,-5.853,140.975,-5.65,140.975,-5.446,140.975,-5.243,140.975,-5.04,140.974,-4.836,140.974,-4.633,140.974,-4.43,140.974,-4.226,140.974,-4.023,140.974,-3.82,140.974,-3.617,140.974,-3.413,140.974,-3.21,140.974,-3.007,140.974,-2.803,140.973,-2.681,140.973,-2.614,140.973,-2.61,141.003,-2.61,141.105,-2.611,141.186,-2.628,141.687,-2.845,141.837,-2.932,141.888,-2.953,141.938,-2.953,141.986,-2.964,142.212,-3.083,142.549,-3.205,142.905,-3.321,143.016,-3.345,143.13,-3.355,143.378,-3.395,143.509,-3.431,143.701,-3.573,143.797,-3.617,143.888,-3.697,144.016,-3.784,144.066,-3.805,144.122,-3.815,144.248,-3.818,144.374,-3.803,144.427,-3.81,144.478,-3.825,144.525,-3.855,144.548,-3.913,144.627,-3.993,144.738,-4.029,144.843,-4.101,144.938,-4.188,145.008,-4.275,145.088,-4.349,145.208,-4.38,145.335,-4.385,145.767,-4.823,145.788,-4.891,145.793,-5.178,145.745,-5.402,145.853,-5.471,145.999,-5.497,146.205,-5.545,146.403,-5.617,147.034,-5.919,147.121,-5.945,147.248,-5.955,147.377,-5.951,147.423,-5.966,147.519,-6.021,147.567,-6.057,147.653,-6.155,147.73,-6.261,147.763,-6.292,147.802,-6.315,147.825,-6.373,147.854,-6.551,147.846,-6.662,147.81,-6.704,147.71,-6.724,147.356,-6.742,147.119,-6.722,146.954,-6.834,146.949,-6.883,146.961,-6.929,147.105,-7.167,147.19,-7.378,147.26,-7.464,147.365,-7.534,147.459,-7.616,147.545,-7.711,147.724,-7.876,147.822,-7.937,147.936,-7.975,148.127,-8.104,148.152,-8.16,148.206,-8.339,148.23,-8.46,148.234,-8.51,148.247,-8.554,148.414,-8.664,148.451,-8.695,148.526,-8.939,148.583,-9.052,148.679,-9.092,148.792,-9.089,149.097,-9.017,149.142,-9.015,149.198,-9.031,149.248,-9.071,149.264,-9.181,149.216,-9.296,149.203,-9.407,149.263,-9.498,149.419,-9.569,149.476,-9.588,149.756,-9.611,149.866,-9.63,149.974,-9.661,150.011,-9.688,149.985,-9.737,149.928,-9.761,149.864,-9.771,149.761,-9.806,149.763,-9.869,149.821,-9.934,149.874,-10.013,149.919,-10.042,149.968,-10.061,150.089,-10.088,150.206,-10.126,150.284,-10.163,150.364,-10.19,150.539,-10.207,150.667,-10.257,150.85,-10.236,150.691,-10.318,150.637,-10.338,150.446,-10.307,150.41,-10.339,150.489,-10.426,150.605,-10.484,150.647,-10.518,150.618,-10.558,150.482,-10.637,150.426,-10.649,150.32,-10.655,150.142,-10.621,150.017,-10.577,149.982,-10.518,149.948,-10.483,149.835,-10.399,149.754,-10.353,149.651,-10.337,149.544,-10.338,149.353,-10.29,148.937,-10.255,148.838,-10.234,148.713,-10.167,148.654,-10.157,148.591,-10.178,148.431,-10.191,148.383,-10.185,148.269,-10.128,148.15,-10.107,148.101,-10.125,148.051,-10.128,147.89,-10.087,147.769,-10.07,147.669,-10.013,147.614,-9.96,147.553,-9.912,147.496,-9.79,147.408,-9.675,147.299,-9.58,147.064,-9.426,147.017,-9.388,146.925,-9.247,146.93,-9.154,146.964,-9.06,146.913,-9.092,146.856,-9.088,146.697,-9.025,146.631,-8.951,146.524,-8.75,146.456,-8.644,146.296,-8.456,146.251,-8.344,146.184,-8.246,146.143,-8.21,146.109,-8.168,146.079,-8.114,146.033,-8.076,145.811,-7.993,145.772,-7.966,145.729,-7.952,145.563,-7.944,145.468,-7.93,145.288,-7.862,145.194,-7.841,145.082,-7.828,144.974,-7.802,144.921,-7.777,144.885,-7.734,144.864,-7.632,144.773,-7.642,144.684,-7.625,144.598,-7.589,144.51,-7.567,144.45,-7.598,144.431,-7.679,144.403,-7.684,144.352,-7.667,144.326,-7.677,144.27,-7.714,144.225,-7.765,144.143,-7.757,143.974,-7.706,143.898,-7.674,143.834,-7.616,143.779,-7.55,143.723,-7.498,143.655,-7.46,143.742,-7.55,143.942,-7.944,143.892,-7.952,143.841,-7.942,143.888,-8.018,143.833,-8.029,143.779,-8.028,143.665,-7.996,143.552,-7.985,143.518,-8.001,143.542,-8.029,143.582,-8.113,143.614,-8.2,143.45,-8.24,143.282,-8.264,143.095,-8.311,142.905,-8.314,142.808,-8.287,142.709,-8.272,142.615,-8.287,142.524,-8.322,142.448,-8.316,142.399,-8.255,142.376,-8.208,142.347,-8.167,142.276,-8.174,142.207,-8.196,142.325,-8.198,142.361,-8.25,142.391,-8.313,142.475,-8.369,142.576,-8.336,142.798,-8.345,143.014,-8.444,143.065,-8.455,143.112,-8.475,143.223,-8.572,143.307,-8.661,143.377,-8.762,143.392,-8.802,143.388,-8.908,143.366,-8.961,143.227,-9.036,143.078,-9.092,142.859,-9.203,142.647,-9.328,142.536,-9.303,142.435,-9.237,142.396,-9.219,142.293,-9.183,142.23,-9.17,141.979,-9.198,141.727,-9.213,141.622,-9.211,141.519,-9.19,141.406,-9.151,141.294,-9.168,141.217,-9.214,141.133,-9.221,140.976,-9.119]],[[148.026,-5.826,147.985,-5.834,147.968,-5.789,147.875,-5.749,147.781,-5.627,147.783,-5.522,147.795,-5.492,147.846,-5.491,148.055,-5.612,148.076,-5.65,148.06,-5.765,148.026,-5.826]],[[146.019,-4.726,145.952,-4.756,145.904,-4.733,145.884,-4.667,145.9,-4.604,145.959,-4.554,145.996,-4.539,146.037,-4.573,146.053,-4.64,146.019,-4.726]],[[152.671,-3.133,152.646,-3.221,152.585,-3.17,152.543,-3.096,152.57,-3.062,152.639,-3.043,152.671,-3.133]],[[152.099,-2.947,152.088,-2.998,152.057,-2.995,151.971,-2.896,151.955,-2.871,151.975,-2.846,152.075,-2.918,152.099,-2.947]],[[149.765,-1.553,149.763,-1.589,149.691,-1.571,149.671,-1.576,149.546,-1.472,149.548,-1.408,149.581,-1.353,149.633,-1.362,149.725,-1.431,149.765,-1.553]],[[151.081,-10.02,151.123,-10.02,151.194,-9.946,151.256,-9.923,151.296,-9.957,151.231,-10.195,151.175,-10.159,150.959,-10.093,150.952,-9.998,150.896,-9.968,150.861,-9.876,150.79,-9.774,150.776,-9.709,150.817,-9.736,150.862,-9.802,151.051,-9.939,151.044,-9.983,151.081,-10.02]],[[150.345,-9.494,150.331,-9.519,150.273,-9.5,150.11,-9.362,150.135,-9.26,150.208,-9.206,150.32,-9.264,150.357,-9.349,150.368,-9.396,150.345,-9.494]],[[151.107,-8.733,151.124,-8.805,151.046,-8.728,151.081,-8.642,151.087,-8.595,151.083,-8.569,151.005,-8.524,151.046,-8.451,151.09,-8.426,151.118,-8.419,151.116,-8.522,151.139,-8.568,151.107,-8.733]],[[154.281,-11.361,154.266,-11.416,154.23,-11.397,154.121,-11.426,154.064,-11.419,154.031,-11.371,154.023,-11.348,154.118,-11.366,154.102,-11.311,154.238,-11.339,154.281,-11.361]],[[143.587,-8.482,143.543,-8.485,143.367,-8.417,143.322,-8.368,143.528,-8.379,143.581,-8.391,143.593,-8.46,143.587,-8.482]],[[147.176,-5.432,147.12,-5.437,147.029,-5.342,147.006,-5.307,147.015,-5.257,147.131,-5.191,147.206,-5.252,147.222,-5.382,147.176,-5.432]],[[147.068,-1.96,147.401,-2.025,147.423,-2.024,147.419,-2.001,147.424,-1.995,147.444,-2.012,147.438,-2.059,147.385,-2.071,147.337,-2.066,147.301,-2.09,147.206,-2.182,147.142,-2.167,147.064,-2.187,146.926,-2.189,146.748,-2.149,146.699,-2.183,146.635,-2.173,146.572,-2.21,146.546,-2.209,146.531,-2.154,146.532,-2.126,146.607,-2.103,146.596,-2.017,146.656,-1.974,146.76,-1.978,146.857,-1.949,147.068,-1.96]],[[150.437,-2.662,150.238,-2.675,150.166,-2.66,150.102,-2.603,150.043,-2.512,149.985,-2.492,149.962,-2.474,150.103,-2.405,150.227,-2.384,150.429,-2.47,150.45,-2.513,150.452,-2.541,150.446,-2.632,150.437,-2.662]],[[150.528,-9.347,150.669,-9.429,150.746,-9.404,150.789,-9.418,150.879,-9.513,150.884,-9.582,150.899,-9.641,150.894,-9.667,150.844,-9.703,150.848,-9.663,150.81,-9.655,150.678,-9.657,150.576,-9.631,150.436,-9.625,150.495,-9.562,150.508,-9.536,150.435,-9.435,150.431,-9.387,150.437,-9.36,150.499,-9.346,150.528,-9.347]],[[152.631,-8.959,152.689,-8.975,152.81,-8.967,152.85,-9.025,152.905,-9.044,152.953,-9.07,152.995,-9.108,152.995,-9.131,152.985,-9.151,152.959,-9.169,152.967,-9.209,152.923,-9.203,152.867,-9.224,152.759,-9.177,152.72,-9.167,152.708,-9.126,152.638,-9.058,152.515,-9.01,152.577,-8.97,152.631,-8.959]],[[153.536,-11.476,153.703,-11.529,153.76,-11.586,153.7,-11.613,153.554,-11.631,153.519,-11.595,153.379,-11.56,153.357,-11.495,153.287,-11.517,153.322,-11.471,153.234,-11.42,153.207,-11.352,153.204,-11.324,153.307,-11.356,153.536,-11.476]],[[143.59,-8.633,143.608,-8.677,143.463,-8.617,143.324,-8.517,143.254,-8.49,143.207,-8.423,143.293,-8.473,143.443,-8.519,143.59,-8.633]],[[147.877,-2.283,147.845,-2.336,147.769,-2.331,147.736,-2.316,147.79,-2.306,147.812,-2.262,147.836,-2.247,147.877,-2.283]],[[151.957,-2.83,151.933,-2.83,151.93,-2.751,151.946,-2.709,152.002,-2.738,152.011,-2.809,151.957,-2.83]],[[150.899,-10.565,150.885,-10.643,150.802,-10.62,150.786,-10.603,150.799,-10.554,150.872,-10.552,150.899,-10.565]],[[153.659,-4.099,153.65,-4.123,153.592,-4.096,153.64,-4.045,153.663,-4.041,153.659,-4.099]],[[155.958,-6.687,155.933,-6.78,155.915,-6.797,155.892,-6.762,155.805,-6.796,155.763,-6.834,155.719,-6.863,155.617,-6.856,155.521,-6.83,155.427,-6.783,155.344,-6.722,155.261,-6.626,155.209,-6.527,155.234,-6.412,155.202,-6.308,155.045,-6.234,155.01,-6.21,154.94,-6.106,154.87,-6.061,154.782,-5.971,154.759,-5.931,154.721,-5.817,154.709,-5.747,154.741,-5.545,154.729,-5.444,154.773,-5.454,154.818,-5.494,154.871,-5.521,154.997,-5.54,155.094,-5.62,155.187,-5.777,155.198,-5.828,155.228,-5.865,155.323,-5.932,155.373,-5.974,155.467,-6.145,155.519,-6.182,155.581,-6.196,155.638,-6.221,155.734,-6.296,155.823,-6.38,155.882,-6.47,155.928,-6.565,155.958,-6.687]],[[154.647,-5.433,154.627,-5.441,154.584,-5.314,154.576,-5.221,154.563,-5.152,154.54,-5.111,154.606,-5.035,154.633,-5.014,154.682,-5.054,154.689,-5.143,154.727,-5.218,154.698,-5.383,154.647,-5.433]]]},{"n":"Panama","c":"PA","r":4,"ml":4,"lx":-80.35,"ly":8.72,"b":[-83.027,7.22,-77.196,9.598],"p":[[[-77.374,8.658,-77.393,8.645,-77.448,8.566,-77.479,8.498,-77.407,8.427,-77.386,8.352,-77.346,8.27,-77.283,8.187,-77.212,8.034,-77.196,7.972,-77.216,7.933,-77.283,7.908,-77.346,7.837,-77.363,7.749,-77.351,7.706,-77.538,7.566,-77.587,7.543,-77.619,7.565,-77.659,7.635,-77.706,7.691,-77.732,7.711,-77.747,7.712,-77.762,7.699,-77.769,7.668,-77.744,7.537,-77.765,7.484,-77.828,7.443,-77.901,7.229,-77.93,7.256,-78.17,7.544,-78.378,7.9,-78.422,8.061,-78.368,8.071,-78.315,8.067,-78.287,8.092,-78.255,8.139,-78.281,8.248,-78.18,8.33,-78.142,8.386,-78.114,8.38,-78.048,8.285,-77.952,8.23,-77.834,8.151,-77.761,8.133,-77.853,8.216,-78.012,8.325,-78.057,8.397,-78.099,8.497,-78.162,8.454,-78.191,8.417,-78.223,8.397,-78.251,8.421,-78.256,8.454,-78.35,8.46,-78.374,8.489,-78.399,8.506,-78.388,8.443,-78.369,8.405,-78.379,8.359,-78.41,8.355,-78.436,8.403,-78.469,8.447,-78.514,8.628,-78.621,8.714,-78.67,8.742,-78.71,8.753,-78.77,8.811,-78.848,8.842,-78.955,8.933,-79.086,8.997,-79.247,9.02,-79.442,9.006,-79.507,8.97,-79.552,8.924,-79.572,8.903,-79.687,8.851,-79.731,8.775,-79.759,8.712,-79.816,8.639,-79.75,8.596,-80.126,8.35,-80.2,8.314,-80.369,8.289,-80.408,8.262,-80.459,8.214,-80.466,8.14,-80.458,8.077,-80.409,8.029,-80.366,7.998,-80.261,7.852,-80.075,7.667,-80.04,7.6,-80.011,7.5,-80.067,7.453,-80.111,7.433,-80.287,7.426,-80.348,7.386,-80.373,7.325,-80.439,7.275,-80.667,7.226,-80.846,7.22,-80.901,7.277,-80.915,7.438,-81.035,7.711,-81.064,7.9,-81.094,7.876,-81.158,7.854,-81.179,7.808,-81.195,7.668,-81.219,7.621,-81.268,7.625,-81.37,7.675,-81.504,7.721,-81.676,8.016,-81.694,8.071,-81.728,8.138,-81.86,8.165,-81.973,8.215,-82.097,8.223,-82.16,8.195,-82.224,8.23,-82.235,8.311,-82.365,8.275,-82.531,8.287,-82.68,8.322,-82.781,8.304,-82.866,8.246,-82.854,8.1,-82.879,8.071,-82.883,8.131,-82.913,8.2,-82.948,8.257,-83.023,8.316,-83.027,8.338,-82.998,8.368,-82.862,8.454,-82.845,8.489,-82.843,8.564,-82.856,8.635,-82.917,8.74,-82.882,8.805,-82.812,8.857,-82.74,8.899,-82.728,8.916,-82.741,8.952,-82.783,8.99,-82.881,9.056,-82.94,9.06,-82.943,9.249,-82.94,9.449,-82.925,9.469,-82.889,9.481,-82.86,9.511,-82.844,9.571,-82.801,9.592,-82.723,9.546,-82.644,9.506,-82.611,9.519,-82.587,9.539,-82.569,9.558,-82.564,9.577,-82.5,9.523,-82.371,9.429,-82.363,9.382,-82.375,9.337,-82.34,9.209,-82.272,9.191,-82.205,9.215,-82.188,9.192,-82.201,9.168,-82.235,9.142,-82.244,9.031,-82.133,8.98,-82.078,8.935,-81.894,8.956,-81.826,8.944,-81.78,8.957,-81.831,9.046,-81.9,9.111,-81.894,9.14,-81.842,9.119,-81.803,9.074,-81.712,9.019,-81.546,8.827,-81.355,8.781,-81.204,8.787,-81.063,8.813,-80.839,8.887,-80.676,9.022,-80.547,9.082,-80.127,9.21,-79.978,9.344,-79.915,9.361,-79.855,9.378,-79.723,9.479,-79.652,9.558,-79.577,9.598,-79.355,9.569,-79.212,9.532,-79.112,9.537,-79.017,9.51,-78.975,9.453,-78.932,9.428,-78.697,9.435,-78.504,9.406,-78.083,9.236,-77.831,9.068,-77.697,8.889,-77.374,8.658]],[[-78.898,8.274,-78.918,8.232,-78.965,8.326,-78.957,8.351,-78.961,8.436,-78.916,8.458,-78.883,8.46,-78.856,8.448,-78.839,8.348,-78.853,8.302,-78.898,8.274]],[[-81.603,7.333,-81.658,7.328,-81.77,7.37,-81.852,7.453,-81.859,7.48,-81.857,7.508,-81.812,7.592,-81.752,7.622,-81.729,7.621,-81.671,7.523,-81.71,7.486,-81.695,7.425,-81.613,7.38,-81.603,7.333]],[[-82.233,9.381,-82.244,9.334,-82.322,9.418,-82.276,9.432,-82.259,9.43,-82.233,9.381]],[[-79.065,8.254,-79.11,8.21,-79.128,8.252,-79.096,8.295,-79.085,8.296,-79.065,8.254]]]},{"n":"Palau","c":"PW","r":6,"ml":5,"lx":134.58,"ly":7.52,"b":[131.135,3.022,134.66,7.712],"p":[[[131.172,3.026,131.15,3.022,131.135,3.025,131.137,3.039,131.152,3.054,131.172,3.061,131.188,3.056,131.186,3.042,131.172,3.026]],[[134.595,7.382,134.535,7.361,134.506,7.437,134.516,7.526,134.556,7.594,134.6,7.616,134.609,7.624,134.651,7.712,134.66,7.663,134.633,7.501,134.598,7.438,134.595,7.382]]]},{"n":"Pakistan","c":"PK","r":2,"ml":2.7,"lx":68.55,"ly":29.33,"b":[60.843,23.753,77.049,37.037],"p":[[[76.767,35.662,76.813,35.572,76.882,35.436,76.928,35.347,76.979,35.246,77.004,35.196,77.049,35.11,77.031,35.062,77.001,34.992,76.892,34.939,76.783,34.9,76.758,34.878,76.749,34.848,76.696,34.787,76.594,34.736,76.51,34.741,76.457,34.756,76.172,34.668,76.041,34.67,75.938,34.613,75.862,34.56,75.709,34.503,75.606,34.503,75.453,34.537,75.264,34.601,75.188,34.639,75.118,34.637,74.952,34.646,74.789,34.678,74.594,34.716,74.498,34.732,74.3,34.765,74.172,34.721,74.056,34.681,73.961,34.653,73.883,34.529,73.85,34.485,73.812,34.422,73.795,34.378,73.81,34.325,73.925,34.288,73.972,34.237,73.979,34.191,73.938,34.145,73.904,34.108,73.904,34.076,73.922,34.043,73.95,34.019,74.113,34.004,74.209,34.003,74.246,33.99,74.251,33.946,74.216,33.887,74.078,33.839,74.001,33.788,73.976,33.721,73.978,33.668,74.004,33.632,74.07,33.592,74.131,33.545,74.15,33.507,74.143,33.455,74.118,33.384,74.05,33.301,73.994,33.242,73.99,33.221,74.004,33.189,74.049,33.143,74.126,33.075,74.222,33.02,74.284,33.005,74.304,32.992,74.323,32.928,74.33,32.861,74.305,32.81,74.355,32.769,74.483,32.771,74.588,32.753,74.632,32.771,74.663,32.758,74.643,32.608,74.658,32.519,74.686,32.494,74.789,32.458,74.987,32.462,75.104,32.42,75.234,32.372,75.303,32.319,75.333,32.279,75.325,32.215,75.254,32.14,75.139,32.105,75.071,32.089,74.739,31.949,74.636,31.89,74.556,31.819,74.526,31.765,74.51,31.713,74.582,31.524,74.594,31.465,74.535,31.261,74.518,31.186,74.54,31.133,74.61,31.113,74.626,31.069,74.633,31.035,74.51,30.96,74.38,30.893,74.339,30.894,74.216,30.769,74.009,30.52,73.899,30.435,73.892,30.394,73.883,30.352,73.925,30.282,73.933,30.222,73.887,30.162,73.809,30.093,73.658,30.033,73.467,29.972,73.382,29.934,73.317,29.773,73.258,29.611,73.231,29.551,73.128,29.364,72.949,29.089,72.903,29.029,72.626,28.896,72.342,28.752,72.292,28.697,72.234,28.566,72.179,28.422,72.129,28.346,71.948,28.177,71.889,28.047,71.87,27.963,71.717,27.915,71.543,27.87,71.29,27.855,71.185,27.832,70.875,27.714,70.798,27.71,70.737,27.729,70.692,27.769,70.649,27.835,70.629,27.937,70.569,27.984,70.489,28.023,70.404,28.025,70.318,27.982,70.244,27.934,70.194,27.895,70.145,27.849,70.05,27.695,69.896,27.474,69.725,27.313,69.661,27.265,69.622,27.228,69.568,27.175,69.537,27.123,69.495,26.954,69.47,26.804,69.481,26.771,69.507,26.743,69.601,26.699,69.736,26.627,69.911,26.586,70.059,26.579,70.115,26.548,70.148,26.506,70.157,26.471,70.149,26.348,70.133,26.215,70.078,26.072,70.079,25.99,70.1,25.91,70.265,25.707,70.325,25.686,70.449,25.681,70.506,25.685,70.57,25.706,70.615,25.692,70.648,25.667,70.657,25.626,70.652,25.423,70.703,25.331,70.8,25.206,70.878,25.063,70.951,24.892,71.021,24.758,71.048,24.688,71.002,24.654,70.976,24.619,70.97,24.572,70.979,24.522,70.973,24.487,71.006,24.444,71.045,24.43,71.044,24.4,70.983,24.361,70.928,24.362,70.886,24.344,70.805,24.262,70.767,24.245,70.716,24.238,70.659,24.246,70.579,24.279,70.556,24.331,70.565,24.386,70.547,24.418,70.489,24.412,70.289,24.356,70.098,24.288,70.065,24.241,70.021,24.192,69.934,24.171,69.805,24.165,69.716,24.173,69.634,24.225,69.559,24.273,69.443,24.275,69.235,24.268,69.12,24.269,69.052,24.286,68.985,24.273,68.901,24.292,68.863,24.267,68.828,24.264,68.8,24.309,68.781,24.314,68.759,24.307,68.74,24.292,68.728,24.266,68.724,23.965,68.587,23.967,68.489,23.967,68.381,23.951,68.283,23.928,68.234,23.901,68.165,23.857,68.149,23.797,68.116,23.753,68.068,23.818,68.037,23.848,68.001,23.826,67.951,23.829,67.86,23.903,67.819,23.828,67.668,23.811,67.65,23.867,67.646,23.92,67.563,23.882,67.504,23.94,67.477,24.018,67.454,24.04,67.428,24.065,67.365,24.092,67.309,24.175,67.304,24.263,67.289,24.368,67.171,24.756,67.101,24.792,66.703,24.861,66.682,24.929,66.71,25.111,66.699,25.226,66.57,25.379,66.534,25.484,66.429,25.575,66.324,25.602,66.219,25.59,66.162,25.554,66.131,25.493,66.356,25.507,66.407,25.485,66.468,25.445,66.403,25.447,66.328,25.466,66.235,25.464,65.884,25.42,65.68,25.355,65.406,25.374,65.061,25.311,64.777,25.307,64.659,25.184,64.594,25.206,64.544,25.237,64.152,25.333,64.125,25.374,64.059,25.403,63.987,25.351,63.936,25.343,63.721,25.386,63.557,25.353,63.496,25.298,63.491,25.211,63.286,25.228,63.17,25.255,63.015,25.225,62.665,25.265,62.572,25.255,62.445,25.197,62.391,25.153,62.315,25.135,62.249,25.197,62.199,25.225,62.152,25.207,62.089,25.155,61.908,25.131,61.744,25.138,61.567,25.186,61.588,25.202,61.615,25.286,61.64,25.585,61.671,25.692,61.662,25.751,61.669,25.769,61.738,25.821,61.754,25.843,61.781,25.996,61.81,26.165,61.842,26.226,61.87,26.242,62.089,26.318,62.126,26.369,62.239,26.357,62.25,26.369,62.26,26.427,62.312,26.491,62.385,26.543,62.439,26.561,62.636,26.594,62.752,26.639,62.787,26.644,63.093,26.632,63.158,26.65,63.168,26.666,63.186,26.838,63.242,26.865,63.25,26.879,63.231,26.998,63.242,27.078,63.305,27.125,63.302,27.151,63.256,27.208,63.196,27.244,63.167,27.252,62.915,27.218,62.812,27.229,62.763,27.25,62.753,27.266,62.763,27.3,62.764,27.357,62.801,27.445,62.812,27.497,62.782,27.801,62.74,28.002,62.763,28.202,62.758,28.244,62.749,28.253,62.718,28.253,62.565,28.235,62.434,28.364,62.353,28.415,62.131,28.479,62.033,28.491,61.89,28.547,61.758,28.668,61.623,28.792,61.569,28.871,61.509,29.006,61.338,29.265,61.339,29.332,61.318,29.373,61.152,29.543,61.034,29.663,60.843,29.859,61.224,29.749,61.521,29.666,62.001,29.53,62.373,29.425,62.477,29.408,63.568,29.498,63.971,29.43,64.099,29.392,64.118,29.414,64.172,29.46,64.266,29.507,64.394,29.544,64.521,29.565,64.704,29.567,64.827,29.564,64.919,29.553,65.096,29.559,65.18,29.578,65.471,29.652,65.666,29.701,65.962,29.779,66.177,29.836,66.231,29.866,66.287,29.92,66.313,29.969,66.247,30.044,66.238,30.11,66.282,30.193,66.305,30.321,66.301,30.503,66.287,30.608,66.347,30.803,66.397,30.912,66.497,30.965,66.567,30.997,66.596,31.02,66.624,31.046,66.731,31.195,66.829,31.264,66.924,31.306,67.028,31.3,67.116,31.243,67.287,31.218,67.453,31.235,67.596,31.278,67.662,31.313,67.738,31.344,67.733,31.379,67.647,31.41,67.598,31.453,67.578,31.506,67.627,31.539,67.74,31.548,68.017,31.678,68.13,31.763,68.161,31.803,68.214,31.807,68.32,31.768,68.443,31.754,68.521,31.794,68.598,31.803,68.673,31.76,68.714,31.708,68.782,31.646,68.869,31.634,68.973,31.667,69.083,31.738,69.187,31.838,69.279,31.937,69.257,32.249,69.241,32.434,69.29,32.531,69.359,32.59,69.405,32.683,69.405,32.764,69.453,32.833,69.502,33.02,69.568,33.064,69.704,33.095,69.92,33.113,70.09,33.198,70.261,33.289,70.284,33.369,70.22,33.455,70.134,33.621,70.057,33.72,69.868,33.898,69.89,34.007,69.995,34.052,70.254,33.976,70.326,33.961,70.416,33.95,70.654,33.952,70.848,33.982,71.052,34.05,71.091,34.12,71.089,34.204,71.092,34.273,71.096,34.369,71.023,34.431,70.979,34.486,70.966,34.53,71.016,34.555,71.066,34.6,71.113,34.682,71.226,34.78,71.294,34.868,71.358,34.91,71.455,34.967,71.517,35.051,71.546,35.101,71.602,35.151,71.621,35.183,71.605,35.212,71.577,35.248,71.546,35.289,71.546,35.329,71.572,35.37,71.601,35.408,71.587,35.461,71.572,35.547,71.519,35.598,71.484,35.715,71.428,35.834,71.398,35.88,71.343,35.939,71.22,36.001,71.185,36.042,71.233,36.122,71.313,36.171,71.463,36.293,71.546,36.378,71.621,36.436,71.716,36.427,71.773,36.432,71.822,36.486,71.921,36.534,72.096,36.634,72.157,36.701,72.25,36.735,72.327,36.742,72.431,36.766,72.531,36.802,72.623,36.83,72.766,36.835,72.994,36.852,73.117,36.869,73.411,36.882,73.732,36.888,73.769,36.888,73.908,36.853,74.002,36.823,74.039,36.826,74.195,36.897,74.431,36.984,74.541,37.022,74.601,37.037,74.692,37.036,74.766,37.013,74.841,36.979,74.889,36.952,74.949,36.968,75.054,36.987,75.145,36.973,75.347,36.913,75.377,36.884,75.424,36.738,75.46,36.725,75.574,36.759,75.667,36.742,75.772,36.695,75.84,36.65,75.885,36.601,75.933,36.522,75.952,36.458,75.974,36.382,75.969,36.169,75.934,36.134,75.905,36.088,75.912,36.049,75.945,36.018,76.01,35.996,76.071,35.983,76.103,35.949,76.148,35.829,76.178,35.811,76.252,35.811,76.386,35.837,76.502,35.878,76.551,35.887,76.563,35.773,76.632,35.729,76.728,35.679,76.767,35.662]]]},{"n":"Oman","c":"OM","r":4,"ml":4,"lx":57.34,"ly":22.12,"b":[51.978,16.648,59.838,26.356],"p":[[[58.722,20.219,58.659,20.204,58.641,20.211,58.641,20.337,58.788,20.497,58.884,20.681,58.951,20.516,58.835,20.424,58.772,20.267,58.722,20.219]],[[53.086,16.648,53.025,16.78,52.964,16.912,52.904,17.044,52.843,17.176,52.801,17.268,52.729,17.3,52.686,17.398,52.642,17.498,52.597,17.598,52.553,17.698,52.509,17.798,52.465,17.897,52.42,17.997,52.376,18.097,52.332,18.197,52.288,18.297,52.243,18.397,52.199,18.497,52.155,18.597,52.11,18.696,52.066,18.796,52.022,18.896,51.978,18.996,52.119,19.043,52.291,19.1,52.463,19.158,52.635,19.215,52.807,19.272,52.979,19.33,53.151,19.387,53.323,19.444,53.495,19.502,53.667,19.559,53.839,19.617,54.011,19.674,54.183,19.731,54.355,19.788,54.527,19.846,54.699,19.903,54.871,19.96,54.977,19.996,55.021,20.129,55.058,20.24,55.095,20.351,55.131,20.461,55.168,20.572,55.205,20.683,55.241,20.793,55.278,20.904,55.314,21.015,55.351,21.126,55.388,21.236,55.424,21.347,55.461,21.458,55.498,21.568,55.534,21.679,55.571,21.79,55.607,21.9,55.641,22.002,55.578,22.1,55.493,22.231,55.404,22.368,55.32,22.497,55.259,22.591,55.186,22.704,55.194,22.85,55.192,22.923,55.2,23.035,55.27,23.19,55.353,23.387,55.414,23.519,55.466,23.633,55.508,23.725,55.532,23.819,55.519,23.885,55.492,23.91,55.468,23.941,55.548,23.991,55.697,24.024,55.779,24.017,55.894,24.041,55.985,24.063,55.992,24.093,55.966,24.143,55.929,24.215,55.8,24.223,55.761,24.243,55.806,24.35,55.804,24.384,55.787,24.424,55.768,24.491,55.778,24.577,55.804,24.636,55.804,24.684,55.792,24.781,55.796,24.868,55.823,24.911,55.871,24.951,55.916,24.972,55.963,24.97,56.001,24.953,56.017,24.908,56.006,24.876,55.98,24.872,55.97,24.859,56.008,24.798,56.064,24.739,56.107,24.749,56.154,24.796,56.205,24.833,56.268,24.867,56.314,24.931,56.353,24.973,56.388,24.979,56.49,24.716,56.641,24.47,56.774,24.335,56.913,24.15,57.123,23.981,57.22,23.923,57.611,23.804,57.825,23.759,58.12,23.717,58.325,23.624,58.393,23.618,58.5,23.646,58.578,23.643,58.773,23.517,58.83,23.397,58.912,23.334,58.983,23.235,59.03,23.131,59.195,22.972,59.311,22.793,59.429,22.661,59.535,22.579,59.696,22.546,59.823,22.509,59.838,22.421,59.824,22.305,59.8,22.22,59.681,22.054,59.653,21.951,59.518,21.782,59.371,21.499,59.304,21.435,59.069,21.289,58.896,21.113,58.69,20.807,58.534,20.504,58.474,20.407,58.349,20.387,58.266,20.395,58.209,20.424,58.232,20.507,58.245,20.599,58.169,20.59,58.103,20.57,57.947,20.344,57.862,20.244,57.844,20.118,57.802,19.955,57.741,19.804,57.714,19.678,57.715,19.607,57.761,19.432,57.764,19.253,57.79,19.146,57.812,19.017,57.738,18.977,57.676,18.958,57.428,18.944,57.177,18.903,56.957,18.828,56.826,18.754,56.655,18.587,56.551,18.166,56.383,17.988,56.27,17.951,55.998,17.935,55.614,17.886,55.479,17.843,55.255,17.586,55.238,17.505,55.281,17.446,55.296,17.382,55.275,17.321,55.174,17.158,55.064,17.039,54.772,16.965,54.665,17.009,54.567,17.031,54.377,17.034,54.068,17.006,53.954,16.918,53.775,16.856,53.61,16.76,53.298,16.723,53.086,16.648]],[[56.298,25.651,56.279,25.628,56.25,25.625,56.184,25.645,56.145,25.691,56.152,25.746,56.154,25.848,56.173,25.945,56.167,26.047,56.117,26.068,56.08,26.063,56.164,26.207,56.197,26.229,56.228,26.22,56.306,26.235,56.346,26.314,56.379,26.356,56.413,26.351,56.43,26.327,56.418,26.208,56.416,26.109,56.374,25.805,56.329,25.752,56.307,25.709,56.298,25.651]],[[56.282,25.236,56.24,25.209,56.211,25.213,56.217,25.267,56.234,25.304,56.277,25.301,56.288,25.279,56.282,25.236]]]},{"n":"Norway","c":"NO","r":3,"ml":3,"lx":9.68,"ly":61.36,"b":[-9.099,58.021,33.629,80.478],"p":[[[20.622,69.037,20.492,69.033,20.117,69.021,20.282,68.934,20.337,68.9,20.348,68.849,20.319,68.754,20.24,68.673,20.147,68.607,19.969,68.542,20.24,68.478,20.056,68.39,19.97,68.356,19.87,68.362,19.691,68.392,19.259,68.465,19.053,68.493,18.868,68.501,18.77,68.5,18.379,68.562,18.303,68.555,18.163,68.528,18.147,68.468,18.156,68.317,18.177,68.201,18.125,68.133,18.073,68.088,17.917,67.965,17.565,68.048,17.325,68.104,17.171,68.03,16.784,67.895,16.586,67.628,16.574,67.62,16.457,67.552,16.307,67.521,16.194,67.505,16.127,67.426,16.282,67.312,16.361,67.252,16.434,67.155,16.421,67.093,16.404,67.055,16.238,66.976,15.884,66.769,15.557,66.552,15.423,66.49,15.484,66.306,15.375,66.252,15.153,66.191,15.04,66.168,14.918,66.154,14.543,66.129,14.61,65.932,14.635,65.845,14.635,65.793,14.596,65.743,14.55,65.646,14.48,65.301,14.426,65.264,14.352,65.171,14.115,64.946,13.925,64.797,13.65,64.582,13.874,64.514,14.078,64.464,14.12,64.388,14.148,64.26,14.141,64.174,14.063,64.096,14.003,64.041,13.961,64.014,13.671,64.041,13.3,64.075,13.204,64.075,12.988,64.05,12.793,64,12.69,63.957,12.663,63.94,12.533,63.844,12.302,63.671,12.175,63.596,12.212,63.492,12,63.292,12.139,63.089,12.145,63.083,12.218,63.001,12.141,62.948,12.109,62.919,12.12,62.826,12.14,62.721,12.122,62.66,12.115,62.592,12.304,62.286,12.301,62.214,12.292,62.167,12.234,61.977,12.155,61.721,12.292,61.653,12.487,61.573,12.596,61.541,12.758,61.446,12.881,61.352,12.864,61.29,12.828,61.222,12.776,61.174,12.728,61.108,12.706,61.06,12.683,61.047,12.468,61.042,12.354,61.023,12.294,61.003,12.315,60.892,12.445,60.69,12.554,60.546,12.589,60.451,12.553,60.354,12.516,60.305,12.515,60.239,12.486,60.107,12.402,60.04,12.292,59.967,12.169,59.913,12.072,59.898,11.988,59.891,11.932,59.864,11.881,59.782,11.834,59.697,11.681,59.592,11.685,59.556,11.743,59.431,11.798,59.29,11.752,59.158,11.712,59.019,11.643,58.926,11.544,58.893,11.471,58.91,11.388,59.037,11.386,59.066,11.366,59.105,11.132,59.143,11.091,59.142,10.999,59.164,10.945,59.171,10.834,59.184,10.743,59.296,10.645,59.389,10.631,59.428,10.634,59.602,10.604,59.68,10.595,59.765,10.534,59.696,10.57,59.587,10.494,59.542,10.398,59.519,10.407,59.456,10.446,59.444,10.455,59.377,10.431,59.28,10.243,59.062,10.205,59.039,10.179,59.009,10.083,59.029,9.96,58.968,9.843,58.958,9.8,59.027,9.635,59.118,9.557,59.113,9.627,59.068,9.696,59.01,9.657,58.971,9.618,58.946,9.551,58.933,9.31,58.857,9.396,58.806,9.323,58.748,9.239,58.739,9.194,58.712,9.178,58.675,8.928,58.57,8.521,58.301,8.312,58.224,8.166,58.145,8.037,58.147,7.876,58.08,7.466,58.021,7.194,58.048,7.005,58.024,6.903,58.071,6.89,58.102,6.895,58.121,6.912,58.143,6.877,58.151,6.803,58.155,6.771,58.132,6.767,58.082,6.731,58.068,6.591,58.097,6.555,58.123,6.606,58.176,6.692,58.224,6.677,58.234,6.66,58.263,6.618,58.266,6.492,58.259,6.389,58.268,6.055,58.375,5.977,58.432,5.707,58.524,5.586,58.62,5.517,58.727,5.522,58.823,5.556,58.975,5.612,59.013,5.854,58.959,6.099,58.87,6.137,58.875,6.214,58.945,6.363,59.001,6.321,59.016,6.099,58.952,6.017,58.988,5.889,59.06,5.889,59.098,5.949,59.135,5.969,59.186,5.937,59.234,5.952,59.299,6.051,59.368,6.199,59.438,6.306,59.506,6.415,59.547,6.404,59.561,6.279,59.535,6.159,59.49,6.017,59.414,5.845,59.353,5.718,59.33,5.657,59.31,5.564,59.291,5.468,59.204,5.362,59.166,5.173,59.163,5.132,59.226,5.185,59.454,5.242,59.564,5.305,59.643,5.404,59.656,5.472,59.713,5.53,59.713,5.579,59.687,5.772,59.661,5.867,59.734,5.991,59.745,6.217,59.818,6.212,59.832,6.059,59.816,5.967,59.813,5.834,59.795,5.763,59.808,5.73,59.863,5.784,59.913,5.996,60.031,6.07,60.083,6.112,60.132,6.105,60.165,6.141,60.233,6.349,60.353,6.518,60.408,6.574,60.361,6.526,60.214,6.527,60.153,6.661,60.367,6.72,60.418,6.787,60.454,6.95,60.478,6.996,60.512,6.806,60.501,6.347,60.419,6.153,60.346,6.102,60.29,5.967,60.206,5.904,60.151,5.877,60.07,5.801,60.026,5.699,60.01,5.557,59.908,5.495,59.826,5.353,59.76,5.264,59.71,5.234,59.692,5.186,59.642,5.146,59.639,5.111,59.668,5.105,59.732,5.119,59.834,5.187,59.907,5.22,59.979,5.174,60.046,5.206,60.088,5.265,60.086,5.376,60.067,5.495,60.07,5.689,60.123,5.658,60.154,5.574,60.158,5.417,60.154,5.286,60.206,5.184,60.308,5.137,60.446,5.168,60.485,5.546,60.625,5.648,60.688,5.589,60.694,5.447,60.617,5.244,60.57,5.116,60.636,5.049,60.708,5.011,60.859,5.025,60.936,5.009,61.038,5.095,61.071,5.192,61.054,5.288,61.047,5.505,61.056,5.984,61.117,6.293,61.081,6.418,61.084,6.61,61.137,6.778,61.142,6.903,61.102,6.972,61.056,6.981,60.994,7.039,60.953,7.078,60.966,7.047,61.015,7.04,61.091,7.545,61.177,7.604,61.211,7.404,61.222,7.347,61.301,7.453,61.419,7.443,61.435,7.331,61.372,7.276,61.284,7.298,61.214,7.276,61.181,7.174,61.166,6.943,61.161,6.794,61.19,6.657,61.207,6.61,61.229,6.626,61.279,6.6,61.29,6.543,61.245,6.493,61.155,6.383,61.134,6.083,61.167,5.647,61.148,5.451,61.102,5.325,61.108,5.107,61.188,5.022,61.251,4.99,61.378,5.003,61.434,5.172,61.457,5.258,61.455,5.339,61.485,5.268,61.505,5.168,61.543,5.099,61.62,4.997,61.645,4.928,61.711,4.91,61.81,4.93,61.878,4.985,61.9,5.117,61.885,5.465,61.897,5.793,61.827,6.016,61.788,6.467,61.807,6.731,61.87,6.682,61.887,6.396,61.851,6.131,61.852,5.664,61.923,5.473,61.946,5.267,61.936,5.16,61.957,5.096,62.027,5.143,62.16,5.241,62.189,5.294,62.154,5.358,62.152,5.422,62.207,5.484,62.239,5.533,62.311,5.718,62.379,5.796,62.385,5.908,62.416,5.98,62.407,6.026,62.376,6.083,62.35,6.209,62.353,6.58,62.407,6.62,62.423,6.692,62.468,6.457,62.448,6.262,62.416,6.136,62.407,6.118,62.447,6.165,62.482,6.238,62.52,6.273,62.584,6.353,62.611,6.439,62.61,6.618,62.621,6.745,62.638,6.961,62.627,7.284,62.602,7.492,62.543,7.57,62.548,7.653,62.564,7.691,62.586,7.527,62.61,7.518,62.646,7.538,62.672,7.805,62.721,8.096,62.732,8.046,62.771,7.408,62.712,7.242,62.752,7.111,62.752,7.025,62.729,6.78,62.701,6.735,62.721,6.782,62.79,6.928,62.903,6.94,62.93,7.008,62.958,7.389,63.023,7.572,63.1,7.654,63.109,7.736,63.104,7.86,63.113,8.101,63.091,8.211,62.996,8.311,62.966,8.623,62.846,8.609,62.881,8.339,63.042,8.235,63.082,8.158,63.162,8.184,63.237,8.271,63.287,8.58,63.313,8.636,63.342,8.641,63.392,8.594,63.426,8.48,63.424,8.387,63.445,8.361,63.499,8.398,63.535,8.576,63.601,8.674,63.623,8.842,63.646,9.136,63.594,9.158,63.566,9.076,63.5,9.084,63.463,9.156,63.459,9.324,63.57,9.521,63.586,9.602,63.61,9.697,63.625,9.832,63.524,9.892,63.492,9.936,63.479,9.979,63.395,10.021,63.391,10.081,63.433,10.189,63.455,10.34,63.469,10.591,63.447,10.704,63.464,10.76,63.461,10.707,63.536,10.674,63.558,10.725,63.625,10.779,63.651,10.953,63.698,11.118,63.719,11.226,63.764,11.371,63.805,11.348,63.838,11.308,63.876,11.214,63.878,11.176,63.899,11.295,63.948,11.458,64.003,11.429,64.025,11.307,64.049,11.214,64.031,11.075,63.988,10.914,63.921,10.967,63.902,11.047,63.845,10.935,63.77,10.339,63.571,10.055,63.513,9.924,63.522,9.893,63.576,9.832,63.617,9.767,63.7,9.657,63.697,9.595,63.679,9.567,63.706,9.615,63.795,9.708,63.865,9.864,63.918,9.939,63.982,10.01,64.083,10.236,64.18,10.566,64.418,10.834,64.494,10.932,64.578,11.09,64.615,11.225,64.679,11.331,64.686,11.524,64.744,11.633,64.814,11.562,64.818,11.392,64.773,11.297,64.755,11.304,64.829,11.35,64.906,11.489,64.976,12.16,65.179,12.227,65.145,12.307,65.086,12.508,65.099,12.738,65.214,12.916,65.339,12.82,65.317,12.715,65.266,12.512,65.195,12.418,65.184,12.364,65.193,12.334,65.241,12.263,65.256,12.2,65.245,12.134,65.279,12.122,65.362,12.206,65.486,12.273,65.568,12.345,65.63,12.628,65.806,12.689,65.902,12.817,65.953,12.983,65.942,13.033,65.956,12.976,66.019,12.795,66.069,12.784,66.1,13.387,66.183,13.674,66.18,13.76,66.221,13.916,66.247,14.034,66.298,13.973,66.32,13.681,66.274,13.499,66.252,13.416,66.253,13.352,66.237,13.119,66.231,13.068,66.431,13.105,66.539,13.192,66.537,13.211,66.641,13.312,66.702,13.45,66.716,13.52,66.742,13.621,66.795,13.788,66.782,13.959,66.794,13.917,66.819,13.704,66.852,13.652,66.907,13.727,66.938,13.808,66.961,13.88,66.965,14.022,67.073,14.109,67.119,14.206,67.111,14.34,67.159,14.473,67.143,14.601,67.174,14.776,67.194,15.416,67.202,15.435,67.247,15.3,67.257,14.824,67.268,14.582,67.267,14.479,67.256,14.442,67.271,14.448,67.298,14.537,67.34,14.579,67.386,14.755,67.499,14.962,67.574,15.121,67.555,15.289,67.483,15.409,67.474,15.465,67.451,15.553,67.352,15.594,67.349,15.576,67.444,15.692,67.521,15.661,67.543,15.487,67.515,15.354,67.544,15.249,67.602,15.219,67.655,15.284,67.708,15.346,67.734,15.304,67.765,15.041,67.683,14.855,67.663,14.781,67.675,14.821,67.75,14.799,67.809,15.048,67.956,15.134,67.973,15.274,67.961,15.401,67.92,15.507,67.926,15.621,67.948,15.606,67.988,15.357,68.004,15.293,68.036,15.316,68.069,15.487,68.103,15.657,68.164,15.851,68.182,16.008,68.229,16.038,68.218,16.065,68.2,16.121,68.027,16.261,67.887,16.312,67.881,16.259,68.001,16.309,68.036,16.372,68.062,16.392,68.092,16.319,68.102,16.26,68.145,16.175,68.281,16.204,68.317,16.388,68.39,16.619,68.406,16.865,68.355,16.951,68.355,17.094,68.368,17.336,68.41,17.479,68.426,17.553,68.426,17.571,68.447,17.502,68.461,17.48,68.474,17.426,68.482,17.202,68.459,16.585,68.466,16.525,68.491,16.514,68.533,16.58,68.593,16.652,68.626,16.885,68.685,17.131,68.693,17.391,68.799,17.49,68.879,17.546,69.001,17.705,69.1,18.101,69.156,18.117,69.181,18.075,69.233,18.079,69.325,18.188,69.433,18.26,69.471,18.293,69.475,18.379,69.44,18.483,69.365,18.646,69.322,18.859,69.314,18.916,69.336,18.75,69.378,18.624,69.434,18.614,69.491,18.674,69.52,18.767,69.517,18.883,69.523,18.991,69.561,19.007,69.588,19.011,69.624,19.038,69.66,19.197,69.748,19.687,69.805,19.722,69.782,19.696,69.613,19.64,69.504,19.642,69.424,19.737,69.504,19.865,69.722,19.961,69.825,20.069,69.883,20.146,69.897,20.223,69.927,20.324,69.945,20.355,69.922,20.387,69.868,20.333,69.677,20.338,69.617,20.277,69.536,20.044,69.356,20.054,69.333,20.107,69.341,20.198,69.371,20.487,69.542,20.739,69.521,20.743,69.535,20.662,69.585,20.563,69.633,20.533,69.692,20.546,69.851,20.622,69.914,20.84,69.907,20.971,69.916,21.032,69.887,21.163,69.89,21.254,70.003,21.433,70.013,21.59,69.938,21.78,69.887,21.932,69.815,21.975,69.835,21.893,70.004,21.803,70.066,21.608,70.098,21.4,70.174,21.346,70.208,21.356,70.233,21.539,70.258,21.78,70.23,21.996,70.293,22.054,70.276,22.219,70.309,22.322,70.265,22.385,70.278,22.421,70.338,22.685,70.375,22.852,70.34,22.941,70.305,22.983,70.237,23.046,70.102,23.177,70.029,23.258,69.993,23.354,69.983,23.4,70.02,23.31,70.064,23.286,70.105,23.329,70.207,23.379,70.247,23.661,70.4,23.897,70.479,24.038,70.485,24.286,70.662,24.356,70.695,24.42,70.702,24.404,70.745,24.268,70.773,24.263,70.826,24.442,70.892,24.658,71.001,24.765,71.008,24.832,70.978,25.042,70.929,25.171,70.872,25.265,70.844,25.325,70.849,25.376,70.892,25.436,70.912,25.57,70.901,25.65,70.873,25.712,70.87,25.768,70.853,25.781,70.817,25.666,70.777,25.468,70.672,25.274,70.552,25.209,70.489,25.146,70.324,24.994,70.218,24.983,70.144,25.044,70.109,25.212,70.136,25.419,70.235,25.471,70.341,25.988,70.625,26.231,70.783,26.507,70.913,26.661,70.94,26.734,70.854,26.675,70.741,26.558,70.669,26.645,70.636,26.628,70.551,26.601,70.503,26.584,70.454,26.585,70.41,26.666,70.422,26.989,70.511,27.071,70.608,27.147,70.681,27.184,70.744,27.309,70.804,27.546,70.804,27.556,70.827,27.269,70.91,27.235,70.947,27.332,70.997,27.597,71.091,27.733,71.081,27.815,71.059,28.142,71.043,28.392,70.975,28.383,70.869,28.327,70.825,28.272,70.798,27.951,70.718,27.898,70.678,27.999,70.664,28.216,70.704,28.272,70.668,28.203,70.577,28.191,70.44,28.166,70.36,28.166,70.288,28.193,70.249,28.28,70.403,28.31,70.443,28.437,70.501,28.485,70.619,28.609,70.76,28.75,70.842,28.832,70.864,29.102,70.861,29.219,70.83,29.321,70.761,29.398,70.734,29.639,70.705,29.722,70.669,29.738,70.647,29.796,70.643,29.959,70.694,30.065,70.703,30.238,70.622,30.203,70.562,30.213,70.543,30.422,70.547,30.596,70.524,30.926,70.401,30.961,70.344,30.944,70.274,30.469,70.198,30.263,70.125,29.926,70.096,28.781,70.145,28.804,70.093,29.601,69.977,29.647,69.944,29.621,69.874,29.621,69.818,29.636,69.78,29.695,69.745,29.792,69.728,29.99,69.737,30.088,69.718,30.155,69.746,30.18,69.841,30.238,69.862,30.349,69.835,30.397,69.733,30.428,69.722,30.484,69.795,30.595,69.79,30.714,69.796,30.87,69.783,30.924,69.652,30.922,69.606,30.897,69.561,30.861,69.538,30.789,69.529,30.615,69.533,30.38,69.585,30.228,69.634,30.18,69.636,30.16,69.63,30.196,69.581,30.187,69.543,30.164,69.502,30.132,69.464,30.087,69.433,29.994,69.392,29.833,69.36,29.388,69.298,29.353,69.271,29.21,69.097,29.171,69.072,29.119,69.05,28.966,69.022,28.892,69.061,28.833,69.119,28.846,69.177,29.025,69.288,29.192,69.367,29.239,69.394,29.333,69.473,29.142,69.671,28.8,69.731,28.412,69.823,28.269,69.871,28.047,69.972,27.89,70.062,27.748,70.065,27.592,70.042,27.348,69.96,27.206,69.919,27.128,69.906,27.109,69.905,26.934,69.928,26.74,69.933,26.584,69.926,26.525,69.915,26.308,69.782,26.156,69.715,26.072,69.692,26.012,69.653,25.962,69.589,25.85,69.367,25.767,69.283,25.749,69.231,25.768,69.076,25.748,68.99,25.647,68.919,25.575,68.887,25.481,68.881,25.357,68.862,25.249,68.821,25.173,68.765,25.087,68.64,24.941,68.593,24.802,68.606,24.703,68.653,24.491,68.689,24.332,68.712,24.154,68.761,23.997,68.798,23.854,68.806,23.773,68.758,23.707,68.714,23.463,68.678,23.324,68.649,23.144,68.643,23.072,68.674,22.811,68.695,22.501,68.72,22.411,68.72,22.383,68.777,22.3,68.856,22.08,68.993,21.989,69.041,21.82,69.154,21.622,69.271,21.594,69.274,21.461,69.277,21.267,69.274,21.144,69.247,21.066,69.214,21.053,69.187,21.128,69.081,21.104,69.054,21.066,69.042,20.889,69.071,20.676,69.069,20.622,69.037]],[[4.959,61.085,4.87,61.072,4.799,61.083,4.824,61.178,4.862,61.194,4.915,61.199,4.973,61.148,4.959,61.085]],[[5.086,60.308,5.089,60.189,4.997,60.198,4.956,60.243,4.944,60.272,4.951,60.341,4.93,60.412,4.957,60.447,4.991,60.452,5.05,60.389,5.086,60.308]],[[29.956,69.797,29.766,69.768,29.744,69.792,29.786,69.829,29.836,69.906,29.914,69.902,29.993,69.873,30.055,69.838,29.956,69.797]],[[11.968,65.627,11.902,65.596,11.778,65.605,11.765,65.631,11.8,65.684,11.875,65.706,11.972,65.702,12.003,65.679,11.968,65.627]],[[8.471,63.667,8.356,63.665,8.287,63.687,8.451,63.732,8.709,63.774,8.733,63.801,8.765,63.805,8.809,63.771,8.815,63.726,8.787,63.703,8.471,63.667]],[[8.103,63.338,8.005,63.337,7.888,63.352,7.815,63.385,7.804,63.414,7.938,63.45,8.074,63.471,8.136,63.431,8.141,63.366,8.103,63.338]],[[23.441,70.816,23.421,70.784,23.387,70.754,23.305,70.722,23.068,70.594,22.929,70.574,22.885,70.554,22.829,70.542,22.656,70.559,22.605,70.533,22.558,70.516,22.432,70.509,22.359,70.515,22.169,70.562,22.056,70.613,21.995,70.657,22.17,70.656,22.233,70.667,22.35,70.658,22.421,70.703,22.571,70.697,22.858,70.728,22.964,70.711,23.205,70.815,23.28,70.813,23.396,70.843,23.441,70.816]],[[25.586,71.142,25.854,71.104,25.945,71.105,26.078,71.033,26.147,71.04,26.134,70.996,26,70.975,25.791,70.963,25.76,70.954,25.582,70.961,25.482,71.02,25.315,71.034,25.315,71.053,25.423,71.097,25.586,71.142]],[[23.615,70.549,23.634,70.503,23.641,70.464,23.548,70.408,23.333,70.335,23.345,70.315,23.271,70.296,23.159,70.283,23.1,70.296,23.108,70.359,23.091,70.378,23.006,70.353,22.918,70.385,22.918,70.417,22.941,70.445,23.022,70.487,23.158,70.516,23.248,70.505,23.547,70.617,23.579,70.594,23.615,70.549]],[[24.018,70.567,23.827,70.527,23.717,70.562,23.67,70.597,23.663,70.675,23.689,70.723,23.778,70.747,23.837,70.729,23.852,70.714,23.956,70.7,24.078,70.651,24.018,70.567]],[[13.873,68.265,13.932,68.248,14.088,68.253,14.119,68.247,14.097,68.219,14.029,68.188,13.888,68.169,13.824,68.121,13.778,68.105,13.656,68.105,13.584,68.094,13.495,68.052,13.424,68.083,13.404,68.061,13.392,68.021,13.352,68.01,13.229,67.995,13.2,68.087,13.256,68.121,13.3,68.16,13.368,68.167,13.429,68.163,13.538,68.249,13.688,68.273,13.784,68.276,13.873,68.265]],[[12.972,67.874,12.824,67.821,12.878,67.918,12.958,68.015,13.068,68.071,13.123,68.049,13.098,68.003,13.098,67.956,13.075,67.935,12.972,67.874]],[[15.207,68.943,15.337,68.842,15.397,68.784,15.348,68.672,15.222,68.616,15.027,68.606,14.89,68.611,14.804,68.638,14.793,68.668,14.743,68.677,14.612,68.638,14.521,68.633,14.405,68.663,14.373,68.711,14.497,68.772,14.554,68.819,14.69,68.815,14.725,68.8,14.802,68.791,14.849,68.848,14.838,68.887,14.872,68.914,15.038,68.894,15.038,69.001,15.102,69.008,15.128,69.004,15.176,68.982,15.207,68.943]],[[19.767,70.217,19.818,70.205,19.869,70.212,19.91,70.202,19.994,70.149,20.084,70.129,20.088,70.102,20.006,70.076,19.897,70.068,19.781,70.077,19.747,70.11,19.711,70.165,19.613,70.219,19.599,70.266,19.684,70.274,19.767,70.217]],[[20.779,70.09,20.725,70.067,20.643,70.057,20.598,70.071,20.535,70.081,20.464,70.077,20.405,70.119,20.412,70.155,20.493,70.203,20.655,70.231,20.786,70.22,20.819,70.205,20.779,70.09]],[[19.255,70.066,19.344,70.012,19.422,70.017,19.446,70.038,19.5,70.048,19.608,70.019,19.592,69.97,19.442,69.908,19.335,69.82,19.197,69.8,19.131,69.81,19.008,69.76,18.909,69.707,18.807,69.64,18.801,69.605,18.785,69.579,18.41,69.553,18.274,69.535,18.13,69.558,18.062,69.602,18.083,69.626,18.227,69.636,18.232,69.677,18.268,69.702,18.315,69.715,18.349,69.768,18.406,69.782,18.512,69.769,18.584,69.807,18.624,69.813,18.674,69.782,18.698,69.825,18.674,69.864,18.687,69.891,18.824,69.96,18.883,70.011,18.969,70.043,19.051,70.038,19.075,70.086,19.051,70.135,19.06,70.167,19.133,70.244,19.213,70.247,19.249,70.179,19.255,70.066]],[[12.51,65.902,12.429,65.899,12.43,65.94,12.476,65.977,12.549,66.002,12.642,66.009,12.747,66.011,12.779,65.992,12.719,65.964,12.51,65.902]],[[12.42,66.043,12.327,66.037,12.343,66.081,12.418,66.123,12.446,66.151,12.461,66.185,12.527,66.211,12.621,66.178,12.623,66.122,12.576,66.072,12.42,66.043]],[[11.231,64.866,11.179,64.838,11.063,64.86,10.833,64.843,10.74,64.87,10.813,64.923,11.021,64.979,11.133,64.976,11.246,64.908,11.231,64.866]],[[17.503,69.596,17.623,69.539,17.677,69.557,17.784,69.563,17.863,69.543,17.927,69.507,18.004,69.505,18.052,69.458,18.077,69.396,18.021,69.35,17.942,69.329,17.921,69.274,17.951,69.198,17.774,69.172,17.568,69.16,17.488,69.197,17.324,69.13,17.161,69.026,17.083,69.014,17.077,69.047,16.96,69.069,16.81,69.071,16.815,69.095,16.843,69.112,16.972,69.138,16.998,69.191,16.974,69.285,16.997,69.33,17.002,69.362,17.083,69.399,17.361,69.381,17.395,69.417,17.373,69.439,17.23,69.478,17.252,69.504,17.356,69.527,17.454,69.53,17.483,69.57,17.488,69.587,17.503,69.596]],[[15.76,68.561,15.772,68.554,15.909,68.65,16.06,68.681,16.069,68.714,16.127,68.746,16.121,68.799,16.151,68.842,16.228,68.854,16.276,68.868,16.329,68.876,16.425,68.842,16.48,68.803,16.547,68.717,16.519,68.633,16.338,68.568,16.194,68.538,16.048,68.464,15.975,68.402,15.913,68.389,15.873,68.394,15.837,68.409,15.764,68.409,15.683,68.356,15.438,68.313,15.341,68.325,15.337,68.378,15.28,68.374,15.188,68.31,15.098,68.289,15.038,68.283,14.927,68.307,14.629,68.198,14.35,68.178,14.258,68.191,14.257,68.257,14.438,68.342,14.586,68.4,15.095,68.441,15.413,68.616,15.489,68.805,15.564,68.874,15.529,68.912,15.444,68.919,15.438,68.979,15.483,69.043,15.65,69.133,15.742,69.171,15.893,69.278,15.965,69.302,16.048,69.302,16.129,69.274,16.115,69.216,15.993,69.113,15.812,69.024,15.834,68.961,15.906,68.908,15.924,68.819,15.928,68.733,15.791,68.617,15.76,68.561]],[[-8.954,70.839,-9.046,70.833,-9.099,70.855,-8.965,70.916,-8.521,71.031,-8.344,71.14,-8.001,71.178,-7.979,71.117,-8.002,71.041,-8.302,70.981,-8.635,70.94,-8.954,70.839]],[[19.219,74.391,19.099,74.352,18.918,74.411,18.797,74.486,18.861,74.514,19.183,74.518,19.262,74.479,19.275,74.457,19.219,74.391]],[[26.876,78.649,26.729,78.646,26.46,78.72,26.408,78.784,26.456,78.81,26.586,78.811,26.789,78.724,27.008,78.698,26.876,78.649]],[[32.526,80.119,31.578,80.081,31.482,80.108,33.019,80.218,33.099,80.229,33.384,80.242,33.629,80.217,33.557,80.198,32.526,80.119]],[[21.608,78.596,21.746,78.572,22.043,78.577,22.207,78.408,22.3,78.228,22.449,78.215,22.735,78.24,22.989,78.252,23.119,78.239,23.352,78.186,23.452,78.149,23.365,78.121,23.152,78.088,23.117,77.992,23.331,77.958,23.684,77.875,23.883,77.865,24.238,77.899,24.571,77.834,24.902,77.757,24.13,77.658,24.062,77.631,23.955,77.558,23.841,77.498,23.736,77.462,23.505,77.401,23.381,77.38,23.101,77.385,22.997,77.361,22.9,77.311,22.802,77.276,22.554,77.267,22.427,77.316,22.469,77.331,22.487,77.36,22.442,77.429,22.679,77.5,22.733,77.539,22.685,77.554,22.62,77.55,22.448,77.571,22.397,77.57,22.255,77.529,22.057,77.501,21.856,77.494,21.05,77.441,20.928,77.46,20.873,77.565,21.201,77.619,21.251,77.711,21.334,77.772,21.431,77.812,21.608,77.916,21.653,77.924,21.21,78.006,21.035,78.059,20.845,78.166,20.786,78.252,20.528,78.326,20.56,78.419,20.373,78.412,20.228,78.478,20.363,78.515,21.047,78.557,21.455,78.598,21.608,78.596]],[[20.898,80.25,20.998,80.239,21.549,80.243,21.655,80.218,21.697,80.159,21.781,80.139,21.898,80.132,22.19,80.06,22.29,80.049,22.376,80.09,22.443,80.19,22.446,80.308,22.418,80.366,22.451,80.402,22.549,80.416,22.672,80.413,22.793,80.433,22.897,80.469,23.008,80.474,23.251,80.447,23.315,80.425,23.25,80.381,23.225,80.318,23.115,80.187,23.353,80.179,23.688,80.207,23.773,80.244,23.953,80.305,24.143,80.295,24.234,80.303,24.28,80.329,24.298,80.36,24.403,80.355,24.547,80.295,24.614,80.286,24.736,80.301,24.786,80.301,24.907,80.277,25.471,80.233,25.667,80.21,25.751,80.188,25.836,80.175,26.437,80.175,26.861,80.16,27.017,80.125,27.148,80.059,27.199,79.907,27.08,79.865,26.221,79.677,26.006,79.617,25.902,79.561,25.726,79.44,25.641,79.403,25.239,79.345,25.145,79.339,24.843,79.367,24.751,79.365,24.383,79.302,24.257,79.263,24.133,79.215,23.948,79.194,23.759,79.206,22.904,79.231,22.789,79.264,22.696,79.329,22.866,79.412,21.911,79.381,20.861,79.398,20.806,79.41,20.761,79.442,20.4,79.463,20.128,79.49,19.9,79.534,19.675,79.591,19.747,79.618,19.821,79.634,20.015,79.64,20.187,79.632,20.493,79.633,20.565,79.691,20.687,79.707,20.784,79.749,20.461,79.775,20.123,79.779,19.899,79.744,19.638,79.729,19.4,79.727,18.942,79.736,18.725,79.761,18.428,79.825,18.325,79.86,18.285,79.887,18.255,79.929,18.595,79.967,18.726,79.996,18.856,80.037,18.344,80.06,18.129,80.093,17.917,80.143,18.089,80.171,18.779,80.194,18.962,80.175,19.143,80.139,19.343,80.116,19.537,80.163,19.355,80.185,19.191,80.263,19.157,80.302,19.178,80.332,19.264,80.336,19.327,80.323,19.568,80.25,19.751,80.227,19.802,80.295,19.81,80.327,19.777,80.353,19.691,80.402,19.614,80.463,19.733,80.478,19.851,80.471,20.104,80.43,20.359,80.401,20.476,80.372,20.693,80.299,20.898,80.25]],[[16.787,79.907,16.838,79.905,16.889,79.915,16.926,79.943,16.966,79.959,17.219,79.941,17.578,79.885,17.685,79.857,17.835,79.8,17.956,79.704,17.86,79.635,17.733,79.57,17.688,79.533,17.734,79.481,17.715,79.431,17.669,79.386,17.861,79.437,18.272,79.601,18.333,79.611,18.397,79.605,18.581,79.572,18.748,79.488,18.785,79.461,18.815,79.427,18.832,79.385,18.823,79.337,18.807,79.303,18.72,79.281,18.678,79.262,18.772,79.26,18.88,79.234,18.979,79.179,19.089,79.157,19.49,79.176,19.751,79.147,19.894,79.056,20.114,79.077,20.114,79.125,20.163,79.146,20.458,79.129,20.611,79.107,20.767,79.059,20.501,78.981,20.72,78.907,21.09,78.853,21.312,78.796,21.353,78.772,21.389,78.74,21.244,78.699,21.096,78.676,20.725,78.672,20.387,78.643,19.769,78.623,19.677,78.61,19.655,78.598,19.619,78.562,19.381,78.48,19.15,78.379,19.056,78.319,18.984,78.234,18.958,78.182,19.009,78.132,18.995,78.081,18.822,78.042,18.712,78.04,18.575,78.048,18.439,78.025,18.431,77.991,18.439,77.942,18.404,77.794,18.362,77.682,18.299,77.579,18.228,77.523,18.137,77.507,17.847,77.497,17.623,77.399,17.442,77.225,17.349,77.157,17.153,77.049,17.188,77.011,17.249,76.969,17.142,76.895,16.977,76.812,16.98,76.779,17.036,76.72,17.063,76.659,16.935,76.606,16.7,76.579,16.462,76.609,16.346,76.645,16.238,76.702,16.124,76.739,16.004,76.761,15.547,76.886,15.124,77.085,14.738,77.162,14.487,77.199,14.366,77.234,14.248,77.282,14.145,77.336,14.05,77.403,14.004,77.445,13.996,77.508,14.026,77.545,14.071,77.564,14.378,77.58,14.488,77.571,14.596,77.538,14.695,77.525,14.921,77.689,16.206,77.782,16.619,77.799,17.033,77.798,16.969,77.842,16.914,77.898,16.853,77.912,16.54,77.88,16.06,77.847,15.826,77.847,15.585,77.869,15.345,77.857,15.097,77.809,14.847,77.779,14.604,77.766,14.09,77.771,13.963,77.796,13.791,77.854,13.75,77.883,13.714,77.919,13.681,78.028,13.718,78.058,13.77,78.075,13.824,78.085,13.937,78.086,14.048,78.067,14.307,78.005,14.248,78.071,14.995,78.151,15.341,78.221,15.519,78.233,15.698,78.228,15.659,78.265,15.657,78.299,15.784,78.327,15.875,78.339,16.15,78.353,16.777,78.35,17.003,78.369,17.172,78.417,16.992,78.4,16.811,78.397,16.727,78.407,16.535,78.449,16.449,78.504,16.697,78.613,16.783,78.664,16.53,78.656,16.446,78.639,16.158,78.538,15.944,78.493,15.681,78.471,15.417,78.473,15.36,78.488,15.279,78.554,15.254,78.589,15.265,78.608,15.348,78.663,15.392,78.721,15.384,78.771,15.323,78.781,15.225,78.732,15.137,78.664,15.016,78.63,14.892,78.639,14.839,78.666,14.792,78.706,14.744,78.721,14.689,78.721,14.578,78.705,14.467,78.675,14.505,78.631,14.515,78.581,14.468,78.541,14.432,78.492,14.546,78.462,14.638,78.415,14.5,78.392,14.363,78.36,14.238,78.31,14.11,78.271,13.908,78.267,13.655,78.245,13.15,78.238,12.913,78.301,12.87,78.331,12.822,78.351,12.665,78.385,12.435,78.483,12.258,78.595,12.138,78.606,11.962,78.642,11.866,78.674,11.774,78.716,11.746,78.766,11.755,78.812,11.861,78.832,11.611,78.883,11.365,78.95,11.456,78.973,11.548,78.983,12.275,78.904,12.323,78.914,12.403,78.953,12.375,78.966,12.253,78.975,12.087,78.975,12.046,78.983,11.982,79.025,11.926,79.077,11.902,79.112,11.893,79.152,12.016,79.213,12.084,79.268,11.978,79.293,11.679,79.291,11.58,79.283,11.616,79.205,11.521,79.151,11.339,79.109,11.208,79.13,11.107,79.233,10.975,79.305,10.926,79.35,10.888,79.415,10.834,79.463,10.738,79.52,10.725,79.556,10.737,79.582,10.811,79.641,10.755,79.69,10.686,79.734,10.682,79.758,10.746,79.789,10.804,79.799,10.866,79.797,11.05,79.76,11.15,79.717,11.185,79.72,11.251,79.785,11.344,79.799,11.702,79.821,12.102,79.738,12.205,79.719,12.288,79.713,12.245,79.75,12.219,79.798,12.28,79.816,12.602,79.773,12.754,79.776,13.108,79.832,13.693,79.861,13.914,79.817,13.926,79.793,13.921,79.762,13.907,79.752,13.778,79.715,13.039,79.685,12.555,79.569,13.215,79.588,13.334,79.575,13.384,79.481,13.432,79.471,13.601,79.457,13.716,79.429,13.834,79.376,13.957,79.34,14.03,79.344,14.056,79.383,14.026,79.429,14.011,79.482,14.02,79.539,14.04,79.586,14.178,79.619,14.38,79.726,14.594,79.799,14.832,79.766,15.052,79.675,15.251,79.545,15.444,79.407,15.66,79.235,15.764,79.174,15.858,79.16,16.295,78.981,16.344,78.976,16.254,79.112,16.028,79.342,15.875,79.519,15.841,79.587,15.816,79.682,15.826,79.709,15.845,79.734,15.956,79.835,16.1,79.884,16.057,79.954,16.094,80.007,16.246,80.049,16.387,80.053,16.524,80.021,16.787,79.907]],[[11.25,78.611,11.262,78.542,11.424,78.549,11.616,78.475,11.826,78.436,11.885,78.409,11.929,78.375,12.056,78.306,12.116,78.233,11.965,78.225,11.757,78.329,11.587,78.388,11.372,78.439,11.199,78.441,11.121,78.463,10.841,78.645,10.789,78.687,10.628,78.754,10.558,78.838,10.558,78.903,10.773,78.888,10.961,78.846,11.124,78.753,11.153,78.724,11.078,78.686,11.155,78.641,11.25,78.611]],[[18.742,80.301,18.525,80.246,18.162,80.288,18.206,80.332,18.292,80.358,18.519,80.348,18.742,80.301]],[[29.047,78.912,29.345,78.906,29.645,78.922,29.697,78.905,29.311,78.852,28.881,78.88,28.495,78.887,28.038,78.829,27.889,78.852,28.121,78.908,28.374,78.927,28.415,78.961,28.511,78.967,28.845,78.971,29.047,78.912]]]},{"n":"North Korea","c":"KP","r":3,"ml":3,"lx":126.44,"ly":39.89,"b":[124.349,37.719,130.687,42.998],"p":[[[128.375,38.623,128.339,38.608,128.279,38.524,128.223,38.417,128.169,38.359,128.106,38.327,128.039,38.309,127.905,38.3,127.785,38.308,127.746,38.319,127.579,38.313,127.533,38.305,127.294,38.313,127.17,38.305,127.09,38.284,127.01,38.241,126.94,38.176,126.879,38.106,126.754,37.979,126.667,37.917,126.667,37.828,126.665,37.801,126.634,37.782,126.623,37.79,126.573,37.797,126.37,37.878,126.203,37.829,126.161,37.764,126.117,37.743,126.05,37.87,125.942,37.874,125.769,37.985,125.695,37.963,125.676,37.918,125.582,37.815,125.449,37.73,125.407,37.719,125.358,37.725,125.365,37.748,125.311,37.844,125.102,37.882,125.027,37.923,124.989,37.931,125.193,38.038,125.247,38.057,125.207,38.082,125.163,38.094,124.995,38.078,124.907,38.113,124.779,38.102,124.691,38.129,124.875,38.233,124.883,38.295,124.881,38.342,124.974,38.48,125.067,38.557,125.31,38.665,125.415,38.68,125.492,38.676,125.554,38.686,125.489,38.728,125.424,38.747,125.299,38.743,125.169,38.806,125.157,38.872,125.41,39.288,125.413,39.326,125.374,39.428,125.361,39.527,125.18,39.583,125.1,39.59,124.868,39.702,124.775,39.758,124.739,39.742,124.732,39.652,124.699,39.632,124.638,39.615,124.608,39.717,124.557,39.791,124.404,39.866,124.349,39.907,124.375,39.996,124.362,40.004,124.387,40.104,124.481,40.182,124.712,40.319,124.772,40.384,124.889,40.46,124.942,40.458,124.997,40.465,125.013,40.498,125.026,40.524,125.073,40.547,125.186,40.589,125.314,40.645,125.417,40.66,125.543,40.743,125.594,40.779,125.645,40.779,125.659,40.796,125.688,40.839,125.728,40.867,125.784,40.872,125.875,40.892,125.989,40.905,126.067,40.974,126.093,41.024,126.145,41.078,126.254,41.138,126.329,41.226,126.412,41.321,126.451,41.352,126.49,41.358,126.514,41.394,126.54,41.496,126.578,41.594,126.601,41.641,126.697,41.692,126.722,41.717,126.743,41.725,126.788,41.718,126.847,41.748,126.904,41.781,126.955,41.769,127.007,41.742,127.061,41.687,127.085,41.644,127.128,41.607,127.137,41.555,127.18,41.531,127.271,41.52,127.42,41.484,127.517,41.482,127.572,41.455,127.688,41.44,127.919,41.461,128.013,41.449,128.053,41.416,128.111,41.389,128.149,41.388,128.2,41.433,128.255,41.507,128.291,41.563,128.289,41.607,128.258,41.655,128.182,41.7,128.132,41.769,128.084,41.841,128.056,41.864,128.033,41.898,128.029,41.952,128.045,41.988,128.16,42.012,128.308,42.026,128.427,42.011,128.627,42.021,128.749,42.041,128.84,42.038,128.923,42.038,128.961,42.069,129.077,42.142,129.134,42.169,129.196,42.218,129.205,42.271,129.218,42.313,129.253,42.358,129.314,42.414,129.366,42.439,129.424,42.436,129.485,42.41,129.524,42.385,129.568,42.392,129.604,42.436,129.628,42.444,129.698,42.448,129.72,42.475,129.746,42.604,129.773,42.705,129.779,42.777,129.842,42.894,129.861,42.965,129.898,42.998,129.941,42.996,129.977,42.975,130.022,42.963,130.083,42.974,130.125,42.956,130.151,42.918,130.24,42.892,130.249,42.873,130.247,42.745,130.296,42.685,130.361,42.631,130.45,42.582,130.498,42.571,130.527,42.535,130.554,42.475,130.618,42.416,130.652,42.373,130.658,42.328,130.687,42.303,130.637,42.275,130.569,42.292,130.458,42.302,130.315,42.214,130.236,42.183,130.18,42.097,130.068,42.046,130.007,41.991,129.928,41.897,129.876,41.806,129.756,41.712,129.686,41.595,129.682,41.494,129.759,41.392,129.766,41.304,129.712,41.124,129.742,40.932,129.709,40.857,129.341,40.726,129.245,40.661,129.11,40.491,128.945,40.428,128.843,40.358,128.701,40.318,128.611,40.198,128.511,40.13,128.393,40.089,128.304,40.036,128.106,40.033,127.967,39.996,127.867,39.896,127.568,39.782,127.527,39.696,127.547,39.563,127.549,39.461,127.523,39.377,127.457,39.401,127.422,39.374,127.383,39.296,127.395,39.208,127.497,39.179,127.581,39.143,127.699,39.125,127.786,39.084,127.972,38.898,128.123,38.816,128.163,38.786,128.249,38.745,128.329,38.681,128.375,38.623]],[[124.905,39.536,124.849,39.508,124.846,39.559,124.89,39.602,124.935,39.608,124.905,39.536]]]},{"n":"Nigeria","c":"NG","r":2,"ml":1.7,"lx":7.5,"ly":9.44,"b":[2.686,4.277,14.627,13.873],"p":[[[7.301,4.418,7.204,4.388,7.14,4.395,7.227,4.527,7.271,4.499,7.328,4.487,7.301,4.418]],[[13.606,13.705,13.763,13.49,13.932,13.258,14.064,13.079,14.16,12.613,14.17,12.524,14.178,12.484,14.185,12.447,14.197,12.384,14.273,12.356,14.415,12.344,14.519,12.298,14.581,12.222,14.587,12.209,14.62,12.151,14.627,12.109,14.618,11.987,14.597,11.83,14.562,11.729,14.582,11.591,14.575,11.532,14.56,11.492,14.496,11.446,14.409,11.401,14.202,11.268,14.143,11.249,14.057,11.245,13.981,11.212,13.892,11.14,13.7,10.873,13.535,10.605,13.479,10.383,13.415,10.171,13.27,10.036,13.25,9.96,13.244,9.916,13.239,9.814,13.221,9.645,13.199,9.564,13.175,9.54,13.019,9.488,12.929,9.426,12.876,9.304,12.856,9.171,12.824,9.019,12.807,8.887,12.782,8.818,12.731,8.746,12.652,8.668,12.583,8.624,12.404,8.596,12.311,8.42,12.233,8.282,12.231,8.227,12.156,7.942,12.025,7.728,12.017,7.652,12.016,7.59,11.852,7.401,11.809,7.345,11.767,7.272,11.809,7.202,11.855,7.138,11.861,7.116,11.787,7.056,11.658,6.952,11.58,6.889,11.563,6.855,11.552,6.697,11.529,6.655,11.478,6.597,11.402,6.534,11.325,6.485,11.237,6.451,11.153,6.438,11.106,6.458,11.08,6.506,11.033,6.698,11.009,6.739,10.954,6.777,10.846,6.882,10.738,6.988,10.606,7.063,10.578,7.058,10.556,7.037,10.519,6.93,10.482,6.891,10.413,6.878,10.293,6.877,10.205,6.892,10.186,6.913,10.168,6.959,10.144,6.996,10.039,6.921,9.874,6.803,9.821,6.784,9.78,6.76,9.726,6.65,9.66,6.532,9.574,6.47,9.49,6.419,9.442,6.373,9.373,6.32,9.239,6.186,9.06,6.009,8.997,5.918,8.935,5.781,8.899,5.63,8.859,5.464,8.801,5.197,8.716,5.047,8.641,4.927,8.585,4.833,8.556,4.755,8.544,4.758,8.515,4.725,8.431,4.746,8.394,4.814,8.342,4.825,8.253,4.924,8.234,4.907,8.328,4.656,8.293,4.558,8.029,4.555,7.801,4.522,7.644,4.525,7.566,4.561,7.531,4.655,7.517,4.645,7.509,4.595,7.46,4.555,7.284,4.548,7.207,4.612,7.144,4.684,7.077,4.716,7.087,4.686,7.164,4.616,7.155,4.514,7.013,4.397,6.923,4.391,6.868,4.441,6.839,4.523,6.825,4.645,6.788,4.725,6.768,4.725,6.786,4.652,6.792,4.593,6.793,4.469,6.86,4.373,6.757,4.344,6.715,4.342,6.633,4.34,6.617,4.376,6.602,4.455,6.58,4.476,6.555,4.341,6.5,4.332,6.462,4.333,6.3,4.304,6.264,4.309,6.256,4.334,6.275,4.372,6.271,4.432,6.215,4.385,6.206,4.292,6.173,4.277,6.077,4.291,5.971,4.339,5.906,4.388,5.799,4.456,5.588,4.647,5.554,4.733,5.493,4.839,5.448,4.946,5.383,5.129,5.403,5.142,5.452,5.127,5.476,5.154,5.388,5.174,5.37,5.195,5.364,5.259,5.368,5.338,5.439,5.365,5.501,5.379,5.532,5.426,5.55,5.474,5.386,5.402,5.232,5.484,5.199,5.534,5.216,5.572,5.289,5.577,5.394,5.575,5.457,5.612,5.418,5.625,5.35,5.623,5.325,5.648,5.327,5.708,5.305,5.694,5.276,5.642,5.173,5.603,5.112,5.642,5.106,5.728,5.093,5.767,5.042,5.798,4.861,6.026,4.634,6.217,4.431,6.349,4.126,6.411,3.487,6.409,3.451,6.427,3.49,6.457,3.546,6.477,3.752,6.584,3.717,6.598,3.503,6.531,3.43,6.525,3.336,6.397,2.772,6.376,2.706,6.369,2.708,6.428,2.736,6.596,2.754,6.662,2.775,6.712,2.753,6.772,2.732,6.853,2.721,6.98,2.748,7.02,2.757,7.068,2.751,7.143,2.75,7.395,2.766,7.423,2.784,7.443,2.785,7.477,2.751,7.542,2.719,7.616,2.72,7.723,2.708,7.827,2.686,7.874,2.702,8.05,2.712,8.273,2.703,8.372,2.724,8.442,2.735,8.614,2.733,8.783,2.775,9.049,2.898,9.061,3.045,9.084,3.11,9.188,3.148,9.321,3.136,9.452,3.165,9.495,3.223,9.566,3.329,9.667,3.325,9.778,3.354,9.813,3.405,9.839,3.477,9.852,3.557,9.907,3.602,10.005,3.646,10.16,3.577,10.268,3.578,10.292,3.604,10.351,3.647,10.409,3.68,10.428,3.758,10.413,3.772,10.418,3.784,10.436,3.834,10.607,3.83,10.654,3.757,10.769,3.745,10.85,3.734,10.972,3.716,11.08,3.695,11.12,3.656,11.155,3.639,11.177,3.488,11.395,3.491,11.499,3.554,11.632,3.595,11.696,3.653,11.732,3.665,11.762,3.647,11.8,3.618,11.828,3.612,11.887,3.62,11.927,3.641,11.97,3.633,12.062,3.634,12.202,3.644,12.405,3.647,12.53,3.769,12.622,3.948,12.775,4.039,12.935,4.087,13.055,4.148,13.458,4.191,13.482,4.242,13.501,4.421,13.648,4.559,13.702,4.665,13.733,4.823,13.76,4.922,13.749,5.101,13.743,5.242,13.757,5.362,13.837,5.416,13.859,5.492,13.873,5.838,13.765,6.184,13.664,6.247,13.673,6.3,13.659,6.386,13.604,6.514,13.485,6.59,13.409,6.627,13.364,6.804,13.108,6.871,13.043,6.937,13.008,7.005,12.996,7.057,13,7.106,13.029,7.173,13.086,7.275,13.112,7.358,13.107,7.789,13.338,7.83,13.341,7.956,13.323,8.095,13.291,8.456,13.06,8.751,12.908,8.958,12.857,9.202,12.821,9.616,12.811,9.929,13.135,10.045,13.206,10.185,13.27,10.23,13.281,10.476,13.33,10.959,13.372,11.412,13.354,11.501,13.341,11.693,13.298,11.99,13.192,12.118,13.09,12.319,13.074,12.463,13.094,12.51,13.194,12.655,13.327,12.76,13.38,12.872,13.449,13.048,13.535,13.194,13.573,13.324,13.671,13.427,13.702,13.606,13.705]]]},{"n":"Niger","c":"NE","r":3,"ml":3,"lx":9.5,"ly":17.45,"b":[0.164,11.696,15.963,23.518],"p":[[[13.606,13.705,13.427,13.702,13.324,13.671,13.194,13.573,13.048,13.535,12.872,13.449,12.76,13.38,12.655,13.327,12.51,13.194,12.463,13.094,12.319,13.074,12.118,13.09,11.99,13.192,11.693,13.298,11.501,13.341,11.412,13.354,10.959,13.372,10.476,13.33,10.23,13.281,10.185,13.27,10.045,13.206,9.929,13.135,9.616,12.811,9.202,12.821,8.958,12.857,8.751,12.908,8.456,13.06,8.095,13.291,7.956,13.323,7.83,13.341,7.789,13.338,7.358,13.107,7.275,13.112,7.173,13.086,7.106,13.029,7.057,13,7.005,12.996,6.937,13.008,6.871,13.043,6.804,13.108,6.627,13.364,6.59,13.409,6.514,13.485,6.386,13.604,6.3,13.659,6.247,13.673,6.184,13.664,5.838,13.765,5.492,13.873,5.416,13.859,5.362,13.837,5.242,13.757,5.101,13.743,4.922,13.749,4.823,13.76,4.665,13.733,4.559,13.702,4.421,13.648,4.242,13.501,4.191,13.482,4.148,13.458,4.087,13.055,4.039,12.935,3.948,12.775,3.769,12.622,3.647,12.53,3.644,12.405,3.634,12.202,3.633,12.062,3.641,11.97,3.62,11.927,3.612,11.887,3.618,11.828,3.647,11.8,3.665,11.762,3.653,11.732,3.595,11.696,3.532,11.787,3.45,11.852,3.36,11.88,3.299,11.927,3.267,11.992,3.15,12.118,2.878,12.368,2.85,12.374,2.805,12.384,2.729,12.354,2.681,12.313,2.648,12.297,2.598,12.294,2.469,12.263,2.366,12.222,2.363,12.188,2.413,11.999,2.389,11.897,2.343,11.946,2.194,12.136,2.091,12.278,2.073,12.309,2.058,12.358,2.069,12.379,2.109,12.394,2.204,12.413,2.221,12.427,2.226,12.466,2.212,12.538,2.16,12.636,2.105,12.701,2.074,12.714,2.017,12.716,1.956,12.707,1.841,12.628,1.79,12.613,1.671,12.62,1.565,12.635,1.5,12.676,1.309,12.834,1.097,13.001,1.008,13.025,0.987,13.042,0.973,13.17,0.977,13.325,0.988,13.365,1.077,13.341,1.171,13.33,1.201,13.358,1.126,13.412,1.018,13.468,0.978,13.552,0.947,13.581,0.898,13.611,0.842,13.626,0.786,13.65,0.748,13.675,0.685,13.685,0.618,13.703,0.522,13.84,0.429,13.972,0.374,14.076,0.355,14.139,0.383,14.246,0.355,14.288,0.251,14.396,0.164,14.497,0.185,14.653,0.203,14.783,0.204,14.865,0.217,14.911,0.229,14.964,0.286,14.98,0.433,14.979,0.719,14.955,0.947,14.982,0.96,14.987,1.121,15.126,1.3,15.272,1.569,15.286,1.859,15.302,2.088,15.309,2.421,15.32,2.69,15.33,3.001,15.341,3.011,15.408,3.029,15.425,3.06,15.427,3.289,15.391,3.504,15.356,3.521,15.483,3.71,15.642,3.817,15.674,3.843,15.702,3.877,15.755,3.898,15.838,3.907,15.897,3.947,15.946,3.976,16.036,4.015,16.193,4.121,16.358,4.182,16.582,4.191,16.798,4.203,16.963,4.235,16.996,4.234,17.288,4.233,17.582,4.232,17.831,4.231,18.139,4.23,18.411,4.229,18.704,4.228,18.968,4.228,19.143,4.446,19.185,4.671,19.228,5.001,19.291,5.359,19.36,5.748,19.434,5.837,19.479,6.131,19.732,6.263,19.846,6.527,20.073,6.731,20.248,6.989,20.471,7.263,20.694,7.482,20.873,7.825,21.076,8.343,21.381,8.861,21.686,9.379,21.991,9.896,22.297,10.414,22.602,10.932,22.907,11.45,23.213,11.968,23.518,12.489,23.402,12.984,23.291,13.481,23.18,13.599,23.12,13.863,22.902,14.201,22.624,14.216,22.62,14.231,22.618,14.556,22.783,14.979,22.996,15.089,22.418,15.172,21.922,15.178,21.606,15.182,21.523,15.216,21.467,15.294,21.412,15.607,20.954,15.54,20.875,15.587,20.733,15.668,20.672,15.929,20.4,15.963,20.346,15.949,20.303,15.766,19.983,15.735,19.904,15.699,19.495,15.673,19.207,15.638,18.811,15.596,18.337,15.562,17.937,15.517,17.408,15.474,16.908,15.212,16.634,14.747,16.147,14.368,15.75,14.178,15.485,13.807,14.966,13.642,14.631,13.514,14.456,13.448,14.381,13.506,14.134,13.606,13.705]]]},{"n":"Nicaragua","c":"NI","r":5,"ml":4,"lx":-85.07,"ly":12.67,"b":[-87.67,10.735,-83.158,15.008],"p":[[[-83.158,14.993,-83.185,14.956,-83.216,14.932,-83.28,14.813,-83.302,14.802,-83.306,14.891,-83.344,14.902,-83.389,14.871,-83.414,14.825,-83.375,14.766,-83.341,14.765,-83.299,14.749,-83.188,14.34,-83.212,14.267,-83.281,14.154,-83.347,14.057,-83.412,13.996,-83.494,13.739,-83.567,13.32,-83.514,12.944,-83.541,12.596,-83.518,12.514,-83.511,12.412,-83.565,12.393,-83.596,12.396,-83.627,12.459,-83.624,12.515,-83.591,12.579,-83.578,12.667,-83.593,12.713,-83.625,12.613,-83.682,12.568,-83.718,12.553,-83.754,12.502,-83.716,12.407,-83.667,12.337,-83.651,12.287,-83.669,12.228,-83.68,12.024,-83.698,12.03,-83.716,12.057,-83.767,12.059,-83.773,11.977,-83.769,11.932,-83.813,11.896,-83.829,11.861,-83.793,11.836,-83.753,11.821,-83.705,11.825,-83.664,11.724,-83.652,11.642,-83.745,11.567,-83.777,11.504,-83.829,11.428,-83.859,11.354,-83.868,11.3,-83.832,11.131,-83.768,11.01,-83.714,10.934,-83.642,10.917,-83.659,10.837,-83.713,10.786,-83.811,10.743,-83.919,10.735,-84.096,10.776,-84.168,10.78,-84.197,10.802,-84.205,10.841,-84.256,10.901,-84.348,10.98,-84.402,10.974,-84.489,10.992,-84.634,11.046,-84.701,11.052,-84.797,11.006,-84.909,10.945,-85.179,11.04,-85.368,11.106,-85.539,11.166,-85.584,11.189,-85.621,11.184,-85.654,11.153,-85.691,11.097,-85.703,11.082,-85.722,11.066,-85.744,11.062,-85.745,11.089,-85.829,11.199,-85.961,11.331,-86.469,11.738,-86.656,11.982,-86.756,12.157,-86.851,12.248,-87.125,12.434,-87.188,12.508,-87.46,12.758,-87.668,12.904,-87.67,12.966,-87.585,13.043,-87.543,13.04,-87.498,12.984,-87.424,12.921,-87.39,12.921,-87.339,12.95,-87.337,12.979,-87.059,12.991,-87.009,13.008,-86.959,13.054,-86.933,13.118,-86.929,13.179,-86.918,13.224,-86.874,13.267,-86.792,13.28,-86.729,13.284,-86.711,13.313,-86.73,13.407,-86.764,13.635,-86.771,13.699,-86.759,13.746,-86.734,13.763,-86.61,13.775,-86.377,13.756,-86.332,13.77,-86.238,13.899,-86.151,13.995,-86.089,14.037,-86.04,14.05,-85.984,13.966,-85.787,13.844,-85.753,13.852,-85.734,13.859,-85.728,13.876,-85.731,13.932,-85.682,13.983,-85.58,14.028,-85.477,14.109,-85.374,14.224,-85.284,14.292,-85.208,14.312,-85.179,14.343,-85.198,14.386,-85.192,14.447,-85.161,14.525,-85.117,14.571,-85.059,14.583,-85.037,14.608,-85.049,14.645,-85.037,14.686,-84.985,14.752,-84.86,14.81,-84.789,14.79,-84.73,14.713,-84.646,14.661,-84.538,14.633,-84.454,14.644,-84.394,14.692,-84.34,14.706,-84.292,14.687,-84.267,14.698,-84.264,14.739,-84.239,14.748,-84.192,14.726,-84.151,14.72,-84.114,14.731,-84.1,14.751,-84.093,14.771,-84.066,14.786,-83.972,14.771,-83.867,14.794,-83.751,14.856,-83.674,14.884,-83.635,14.876,-83.59,14.908,-83.537,14.977,-83.415,15.008,-83.158,14.993]]]},{"n":"New Zealand","c":"NZ","r":2,"ml":2,"lx":172.79,"ly":-39.76,"b":[-176.848,-52.57,178.536,-8.546],"p":[[[173.115,-41.279,173.231,-41.284,173.338,-41.211,173.447,-41.151,173.563,-41.102,173.738,-40.989,173.784,-40.972,173.898,-40.951,173.947,-40.924,174.002,-40.918,173.953,-40.985,173.89,-41.007,173.88,-41.031,173.915,-41.07,173.86,-41.124,173.862,-41.192,173.798,-41.272,173.897,-41.239,173.933,-41.187,173.915,-41.158,173.958,-41.1,174.024,-41.072,173.998,-41.028,173.999,-40.993,174.081,-41.006,174.121,-41.005,174.153,-40.991,174.212,-40.985,174.224,-41.024,174.303,-41.02,174.274,-41.069,174.214,-41.126,174.2,-41.16,174.103,-41.217,174.039,-41.242,174.138,-41.248,174.284,-41.172,174.37,-41.104,174.368,-41.188,174.297,-41.264,174.237,-41.312,174.17,-41.327,174.102,-41.366,174.069,-41.429,174.073,-41.472,174.092,-41.505,174.161,-41.562,174.084,-41.671,174.17,-41.657,174.217,-41.678,174.283,-41.741,174.243,-41.813,174.215,-41.85,174.047,-42.003,173.974,-42.081,173.888,-42.13,173.889,-42.212,173.84,-42.271,173.589,-42.474,173.545,-42.518,173.348,-42.841,173.221,-42.977,173.149,-43.023,173.072,-43.06,172.889,-43.124,172.808,-43.198,172.719,-43.259,172.624,-43.272,172.627,-43.3,172.688,-43.315,172.735,-43.355,172.7,-43.4,172.632,-43.428,172.562,-43.436,172.527,-43.465,172.693,-43.444,172.74,-43.468,172.749,-43.517,172.767,-43.562,172.807,-43.621,172.947,-43.659,173.073,-43.676,173.098,-43.704,173.117,-43.798,173.094,-43.844,173.066,-43.875,173.023,-43.885,172.921,-43.891,172.818,-43.87,172.749,-43.813,172.555,-43.831,172.503,-43.844,172.476,-43.833,172.584,-43.774,172.527,-43.739,172.48,-43.727,172.43,-43.746,172.396,-43.778,172.385,-43.83,172.35,-43.859,172.297,-43.868,172.221,-43.825,172.146,-43.764,172.036,-43.702,172.052,-43.74,172.137,-43.834,172.18,-43.896,172.081,-43.946,171.978,-43.984,171.891,-44.007,171.808,-44.042,171.712,-44.097,171.659,-44.117,171.518,-44.118,171.443,-44.136,171.417,-44.209,171.365,-44.255,171.241,-44.264,171.285,-44.279,171.313,-44.302,171.231,-44.521,171.213,-44.612,171.198,-44.768,171.146,-44.912,170.999,-44.911,171.023,-44.937,171.134,-44.978,171.113,-45.039,170.991,-45.151,170.94,-45.216,170.89,-45.374,170.815,-45.519,170.701,-45.684,170.7,-45.714,170.74,-45.756,170.788,-45.792,170.791,-45.844,170.776,-45.871,170.722,-45.878,170.674,-45.896,170.419,-45.941,170.335,-45.992,170.267,-46.083,170.186,-46.161,169.918,-46.334,169.761,-46.48,169.729,-46.521,169.687,-46.552,169.342,-46.621,169.099,-46.631,168.966,-46.613,168.838,-46.578,168.767,-46.566,168.631,-46.588,168.572,-46.611,168.466,-46.588,168.382,-46.605,168.357,-46.588,168.326,-46.546,168.343,-46.489,168.32,-46.447,168.266,-46.419,168.23,-46.386,168.189,-46.362,168.077,-46.353,167.9,-46.368,167.842,-46.366,167.722,-46.227,167.682,-46.193,167.539,-46.149,167.491,-46.155,167.414,-46.229,167.369,-46.242,167.1,-46.249,166.831,-46.225,166.732,-46.198,166.712,-46.134,166.917,-45.957,166.856,-45.981,166.73,-46.053,166.65,-46.042,166.727,-45.963,166.734,-45.928,166.718,-45.889,166.613,-45.955,166.493,-45.964,166.478,-45.903,166.488,-45.832,166.513,-45.812,166.836,-45.775,166.953,-45.75,167.003,-45.712,166.81,-45.699,166.798,-45.646,166.826,-45.603,166.991,-45.532,166.869,-45.55,166.734,-45.544,166.743,-45.468,166.778,-45.41,166.92,-45.408,166.876,-45.368,166.869,-45.311,166.909,-45.307,167.052,-45.383,167.156,-45.411,167.112,-45.354,167.118,-45.318,167.145,-45.302,167.23,-45.29,167.207,-45.28,167.127,-45.266,167.033,-45.222,167.023,-45.177,167.026,-45.124,167.128,-45.051,167.188,-45.094,167.259,-45.082,167.205,-45.048,167.172,-44.997,167.195,-44.963,167.411,-44.828,167.466,-44.958,167.479,-44.915,167.482,-44.874,167.456,-44.838,167.46,-44.802,167.485,-44.771,167.578,-44.741,167.698,-44.641,167.787,-44.595,167.859,-44.625,167.909,-44.665,167.902,-44.625,167.866,-44.592,167.857,-44.501,168.018,-44.359,168.196,-44.224,168.367,-44.082,168.457,-44.031,168.651,-43.972,168.775,-43.996,168.806,-43.992,168.99,-43.89,169.067,-43.863,169.136,-43.9,169.179,-43.913,169.136,-43.82,169.17,-43.777,169.323,-43.702,169.515,-43.624,169.662,-43.591,169.769,-43.538,169.834,-43.537,169.824,-43.497,169.835,-43.459,169.891,-43.462,169.908,-43.447,169.859,-43.426,170.018,-43.349,170.104,-43.265,170.149,-43.248,170.19,-43.222,170.24,-43.164,170.3,-43.145,170.356,-43.154,170.396,-43.182,170.374,-43.135,170.303,-43.108,170.379,-43.066,170.459,-43.038,170.536,-43.058,170.612,-43.092,170.536,-43.041,170.524,-43.009,170.616,-42.972,170.665,-42.961,170.735,-43.03,170.725,-42.975,170.742,-42.927,170.84,-42.849,170.97,-42.718,171.011,-42.764,171.018,-42.819,171.012,-42.885,171.038,-42.862,171.048,-42.802,171.028,-42.696,171.19,-42.5,171.221,-42.479,171.257,-42.465,171.313,-42.46,171.296,-42.431,171.252,-42.402,171.296,-42.303,171.323,-42.189,171.36,-42.08,171.421,-41.973,171.486,-41.795,171.536,-41.758,171.672,-41.745,171.732,-41.72,171.831,-41.655,171.948,-41.539,172.011,-41.445,172.093,-41.202,172.139,-40.947,172.273,-40.759,172.468,-40.622,172.641,-40.518,172.711,-40.497,172.83,-40.49,172.944,-40.519,172.733,-40.544,172.711,-40.605,172.704,-40.668,172.729,-40.724,172.767,-40.773,172.869,-40.82,172.989,-40.848,173.042,-40.954,173.052,-41.079,173.069,-41.186,173.115,-41.279]],[[173.915,-40.864,173.781,-40.922,173.786,-40.881,173.812,-40.794,173.873,-40.749,173.903,-40.746,173.964,-40.713,173.958,-40.787,173.915,-40.864]],[[166.746,-45.656,166.741,-45.705,166.729,-45.73,166.695,-45.73,166.642,-45.724,166.592,-45.702,166.559,-45.708,166.532,-45.7,166.567,-45.644,166.686,-45.615,166.731,-45.639,166.746,-45.656]],[[166.979,-45.18,167.023,-45.3,166.931,-45.277,166.893,-45.241,166.963,-45.18,166.979,-45.18]],[[168.145,-46.862,168.145,-46.902,168.041,-46.888,168.043,-46.933,168.125,-46.956,168.156,-46.988,168.241,-46.979,168.261,-47.027,168.241,-47.07,168.184,-47.102,168.015,-47.117,167.906,-47.18,167.811,-47.17,167.785,-47.176,167.676,-47.243,167.555,-47.264,167.522,-47.259,167.539,-47.199,167.629,-47.142,167.631,-47.088,167.654,-47.044,167.741,-47.014,167.742,-46.957,167.801,-46.907,167.765,-46.798,167.784,-46.7,167.956,-46.694,168.145,-46.862]],[[175.543,-36.279,175.551,-36.334,175.475,-36.314,175.445,-36.273,175.359,-36.231,175.346,-36.218,175.337,-36.135,175.382,-36.095,175.39,-36.078,175.409,-36.071,175.444,-36.115,175.513,-36.177,175.543,-36.279]],[[173.269,-34.935,173.285,-34.981,173.34,-34.948,173.381,-34.896,173.448,-34.844,173.439,-34.929,173.473,-34.947,173.694,-35.006,173.739,-35.055,173.786,-35.069,173.813,-35.041,173.844,-35.026,173.924,-35.057,174.104,-35.143,174.119,-35.172,174.11,-35.216,174.119,-35.263,174.143,-35.3,174.203,-35.309,174.283,-35.254,174.32,-35.247,174.373,-35.325,174.393,-35.369,174.385,-35.367,174.419,-35.411,174.465,-35.454,174.543,-35.582,174.532,-35.627,174.509,-35.667,174.581,-35.786,174.533,-35.794,174.391,-35.774,174.396,-35.797,174.479,-35.884,174.549,-36.007,174.605,-36.081,174.802,-36.309,174.772,-36.391,174.777,-36.445,174.752,-36.491,174.819,-36.612,174.777,-36.65,174.749,-36.774,174.719,-36.796,174.722,-36.841,174.802,-36.853,174.85,-36.873,174.891,-36.909,174.917,-36.865,174.952,-36.853,175.047,-36.912,175.245,-36.971,175.3,-36.993,175.326,-37.041,175.347,-37.156,175.385,-37.207,175.461,-37.217,175.542,-37.201,175.568,-37.159,175.552,-37.046,175.493,-36.866,175.493,-36.807,175.501,-36.748,175.487,-36.69,175.458,-36.634,175.426,-36.592,175.386,-36.556,175.4,-36.501,175.461,-36.476,175.498,-36.523,175.528,-36.579,175.681,-36.747,175.772,-36.735,175.781,-36.805,175.842,-36.875,175.876,-36.958,175.921,-37.205,175.99,-37.437,176.115,-37.538,176.129,-37.587,176.053,-37.562,176.03,-37.576,176.038,-37.601,176.108,-37.645,176.191,-37.667,176.243,-37.664,176.292,-37.68,176.615,-37.831,176.77,-37.89,177.162,-37.986,177.274,-37.993,177.336,-37.991,177.453,-37.957,177.558,-37.897,177.649,-37.808,177.727,-37.706,177.813,-37.656,177.909,-37.617,177.958,-37.581,178.009,-37.555,178.272,-37.567,178.361,-37.618,178.476,-37.66,178.536,-37.692,178.516,-37.758,178.447,-37.854,178.394,-37.96,178.347,-38.201,178.315,-38.444,178.268,-38.551,178.181,-38.634,178.085,-38.694,177.976,-38.722,177.932,-38.86,177.91,-39.022,177.917,-39.062,177.951,-39.095,177.966,-39.142,177.909,-39.24,177.875,-39.225,177.829,-39.145,177.786,-39.111,177.656,-39.086,177.523,-39.074,177.408,-39.081,177.297,-39.116,177.129,-39.186,177.077,-39.222,177.031,-39.267,176.954,-39.368,176.936,-39.491,176.939,-39.555,176.967,-39.605,177.11,-39.673,176.968,-39.911,176.842,-40.158,176.771,-40.228,176.689,-40.293,176.612,-40.442,176.476,-40.57,176.385,-40.668,176.314,-40.769,176.252,-40.877,176.119,-41.029,176.06,-41.13,175.983,-41.213,175.84,-41.32,175.687,-41.412,175.447,-41.538,175.38,-41.58,175.31,-41.611,175.222,-41.574,175.204,-41.535,175.185,-41.449,175.166,-41.417,175.054,-41.391,174.906,-41.433,174.881,-41.424,174.875,-41.404,174.875,-41.278,174.9,-41.243,174.866,-41.223,174.832,-41.231,174.82,-41.263,174.841,-41.291,174.757,-41.325,174.67,-41.326,174.643,-41.313,174.635,-41.289,174.657,-41.251,174.685,-41.218,174.848,-41.059,175.017,-40.848,175.163,-40.622,175.2,-40.505,175.254,-40.289,175.21,-40.199,175.156,-40.115,175.009,-39.952,174.814,-39.86,174.687,-39.847,174.567,-39.813,174.455,-39.735,174.352,-39.643,174.149,-39.568,173.934,-39.509,173.812,-39.426,173.783,-39.376,173.764,-39.319,173.766,-39.265,173.782,-39.211,173.806,-39.17,173.844,-39.139,174.071,-39.031,174.312,-38.971,174.356,-38.972,174.398,-38.963,174.458,-38.926,174.566,-38.842,174.597,-38.785,174.619,-38.605,174.653,-38.428,174.715,-38.226,174.809,-38.1,174.84,-38.023,174.802,-37.896,174.837,-37.849,174.88,-37.821,174.928,-37.804,174.846,-37.685,174.749,-37.505,174.729,-37.449,174.744,-37.393,174.768,-37.339,174.707,-37.325,174.673,-37.273,174.586,-37.098,174.61,-37.07,174.66,-37.089,174.734,-37.215,174.746,-37.15,174.804,-37.11,174.864,-37.089,174.929,-37.085,174.782,-36.944,174.733,-36.949,174.668,-36.972,174.601,-36.986,174.537,-36.973,174.476,-36.942,174.445,-36.883,174.406,-36.768,174.382,-36.726,174.189,-36.492,174.246,-36.485,174.402,-36.602,174.432,-36.565,174.454,-36.511,174.447,-36.451,174.41,-36.406,174.354,-36.376,174.353,-36.323,174.395,-36.274,174.393,-36.24,174.304,-36.171,174.268,-36.163,174.252,-36.196,174.278,-36.244,174.254,-36.249,174.036,-36.122,173.969,-36.021,173.914,-35.909,173.909,-35.954,173.917,-36.018,174.003,-36.146,174.142,-36.289,174.166,-36.328,174.146,-36.377,174.097,-36.391,174.055,-36.36,173.991,-36.237,173.945,-36.176,173.412,-35.543,173.48,-35.459,173.586,-35.389,173.61,-35.357,173.626,-35.319,173.582,-35.313,173.542,-35.33,173.496,-35.362,173.454,-35.399,173.402,-35.481,173.376,-35.5,173.314,-35.443,173.29,-35.408,173.291,-35.366,173.275,-35.34,173.228,-35.331,173.16,-35.248,173.117,-35.205,173.189,-35.124,173.191,-35.016,173.117,-34.903,173.03,-34.8,172.861,-34.632,172.706,-34.455,172.874,-34.433,173.044,-34.429,172.964,-34.535,173,-34.596,173.054,-34.648,173.171,-34.807,173.181,-34.853,173.241,-34.899,173.269,-34.935]],[[169.178,-52.497,169.233,-52.548,169.128,-52.57,169.076,-52.552,169.04,-52.529,169.022,-52.495,169.079,-52.499,169.129,-52.485,169.178,-52.497]],[[166.221,-50.762,166.243,-50.846,166.188,-50.846,166.073,-50.823,166.038,-50.787,166.013,-50.778,165.971,-50.82,165.904,-50.821,165.889,-50.808,165.916,-50.763,166.074,-50.679,166.103,-50.573,166.101,-50.539,166.225,-50.531,166.254,-50.544,166.267,-50.559,166.259,-50.577,166.21,-50.612,166.208,-50.652,166.22,-50.694,166.179,-50.715,166.201,-50.751,166.221,-50.762]],[[-176.178,-43.74,-176.214,-43.766,-176.275,-43.765,-176.382,-43.867,-176.375,-43.791,-176.407,-43.761,-176.499,-43.768,-176.517,-43.785,-176.455,-43.805,-176.441,-43.816,-176.5,-43.86,-176.439,-43.955,-176.385,-43.951,-176.334,-44.025,-176.334,-44.048,-176.453,-44.077,-176.516,-44.117,-176.572,-44.115,-176.598,-44.107,-176.629,-44.036,-176.632,-44.006,-176.563,-43.954,-176.524,-43.901,-176.555,-43.852,-176.635,-43.82,-176.808,-43.835,-176.848,-43.824,-176.761,-43.758,-176.667,-43.765,-176.566,-43.718,-176.178,-43.74]],[[-176.176,-44.322,-176.221,-44.331,-176.215,-44.274,-176.229,-44.237,-176.155,-44.225,-176.123,-44.268,-176.176,-44.322]],[[-172.479,-8.581,-172.484,-8.583,-172.488,-8.572,-172.494,-8.559,-172.499,-8.548,-172.497,-8.546,-172.487,-8.556,-172.481,-8.567,-172.479,-8.581]],[[-171.186,-9.355,-171.189,-9.358,-171.193,-9.352,-171.2,-9.345,-171.204,-9.333,-171.202,-9.333,-171.194,-9.339,-171.189,-9.347,-171.186,-9.355]]]},{"n":"Niue","c":"NU","r":4,"ml":4,"lx":-169.86,"ly":-19.05,"b":[-169.948,-19.138,-169.793,-18.966],"p":[[[-169.803,-19.083,-169.904,-19.138,-169.948,-19.073,-169.909,-18.99,-169.862,-18.969,-169.834,-18.966,-169.793,-19.043,-169.803,-19.083]]]},{"n":"Cook Islands","c":"CK","r":4,"ml":4,"lx":-159.79,"ly":-21.22,"b":[-159.842,-21.25,-159.737,-21.186],"p":[[[-159.741,-21.249,-159.773,-21.25,-159.813,-21.242,-159.84,-21.238,-159.842,-21.229,-159.832,-21.2,-159.811,-21.186,-159.768,-21.188,-159.74,-21.208,-159.737,-21.241,-159.741,-21.249]]]},{"n":"Netherlands","c":"NL","r":5,"ml":4,"lx":5.61,"ly":52.42,"b":[-68.371,12.032,7.197,53.625],"p":[[[5.994,50.75,5.892,50.753,5.797,50.755,5.747,50.76,5.694,50.775,5.669,50.806,5.639,50.844,5.648,50.867,5.737,50.932,5.75,50.95,5.741,50.96,5.75,50.989,5.818,51.086,5.827,51.126,5.796,51.153,5.752,51.169,5.609,51.198,5.54,51.239,5.509,51.275,5.477,51.285,5.43,51.273,5.311,51.26,5.214,51.279,5.1,51.346,5.073,51.407,5.059,51.453,5.031,51.469,4.993,51.445,4.944,51.408,4.848,51.403,4.821,51.412,4.816,51.433,4.811,51.453,4.784,51.477,4.756,51.491,4.634,51.422,4.589,51.422,4.532,51.449,4.503,51.475,4.441,51.46,4.385,51.428,4.404,51.367,4.374,51.356,4.304,51.362,4.226,51.386,4.139,51.402,4.007,51.443,3.822,51.409,3.694,51.45,3.587,51.454,3.521,51.486,3.449,51.541,3.5,51.577,3.549,51.589,3.744,51.596,3.886,51.574,4.141,51.456,4.206,51.457,4.274,51.472,4.239,51.504,4.175,51.519,4.08,51.551,4.005,51.596,4.183,51.61,4.158,51.633,4.135,51.673,3.947,51.811,3.979,51.848,4.026,51.928,4.085,51.994,4.132,52.012,4.209,52.059,4.376,52.197,4.483,52.309,4.562,52.443,4.678,52.81,4.713,52.872,4.769,52.941,4.839,52.928,4.888,52.908,5.061,52.961,5.358,53.096,5.446,53.214,5.532,53.269,5.874,53.375,6.062,53.407,6.353,53.415,6.564,53.434,6.816,53.441,6.912,53.375,6.968,53.327,7.058,53.301,7.197,53.282,7.189,53.187,7.19,53,7.179,52.966,7.117,52.887,7.051,52.745,7.033,52.651,7.013,52.634,6.748,52.634,6.711,52.618,6.705,52.598,6.719,52.574,6.712,52.55,6.692,52.53,6.703,52.499,6.749,52.464,6.833,52.442,6.922,52.44,6.968,52.444,7.002,52.419,7.035,52.38,7.033,52.331,7.02,52.266,6.977,52.206,6.855,52.136,6.8,52.111,6.749,52.099,6.725,52.08,6.713,52.057,6.716,52.036,6.802,51.98,6.8,51.967,6.775,51.938,6.742,51.911,6.518,51.854,6.425,51.858,6.372,51.83,6.356,51.825,6.297,51.851,6.167,51.881,6.117,51.87,6.09,51.854,6.008,51.834,5.949,51.803,5.949,51.762,6.053,51.658,6.089,51.638,6.091,51.599,6.142,51.55,6.193,51.489,6.199,51.45,6.193,51.411,6.166,51.355,6.076,51.224,6.075,51.199,6.082,51.18,6.113,51.175,6.137,51.165,6.13,51.147,5.961,51.057,5.939,51.041,5.868,51.045,5.858,51.03,5.867,51.006,5.895,50.984,5.955,50.973,6.007,50.95,6.048,50.905,5.994,50.75]],[[4.226,51.386,4.211,51.349,4.173,51.307,4.04,51.247,3.902,51.208,3.831,51.213,3.782,51.233,3.756,51.255,3.682,51.276,3.58,51.286,3.517,51.264,3.472,51.242,3.433,51.246,3.403,51.264,3.38,51.291,3.35,51.378,3.426,51.394,3.589,51.399,3.717,51.369,3.883,51.354,4.011,51.396,4.112,51.361,4.226,51.386]],[[6.333,53.511,6.193,53.477,6.159,53.484,6.168,53.494,6.291,53.515,6.333,53.511]],[[5.929,53.459,5.732,53.443,5.665,53.455,5.654,53.467,5.708,53.473,5.876,53.475,5.928,53.465,5.929,53.459]],[[5.109,53.308,4.924,53.235,4.908,53.246,5.027,53.31,5.109,53.308]],[[5.326,53.386,5.233,53.378,5.19,53.392,5.415,53.431,5.557,53.444,5.583,53.438,5.326,53.386]],[[4.886,53.071,4.787,53,4.727,53.02,4.709,53.036,4.74,53.091,4.886,53.183,4.886,53.071]],[[3.949,51.739,4.047,51.685,4.068,51.667,4.075,51.649,3.951,51.627,3.819,51.694,3.732,51.678,3.699,51.71,3.699,51.73,3.789,51.746,3.949,51.739]],[[6.735,53.583,6.642,53.579,6.669,53.606,6.755,53.625,6.801,53.625,6.735,53.583]],[[-68.206,12.145,-68.254,12.032,-68.282,12.082,-68.287,12.172,-68.307,12.207,-68.348,12.228,-68.371,12.258,-68.369,12.302,-68.219,12.231,-68.206,12.145]],[[-62.937,17.496,-62.962,17.475,-62.983,17.477,-62.997,17.497,-63,17.53,-62.979,17.521,-62.972,17.516,-62.965,17.509,-62.937,17.496]],[[-63.233,17.623,-63.242,17.62,-63.254,17.629,-63.252,17.645,-63.242,17.652,-63.233,17.647,-63.227,17.634,-63.233,17.623]]]},{"n":"Aruba","c":"AW","r":5,"ml":5,"lx":-69.97,"ly":12.52,"b":[-70.066,12.423,-69.896,12.614],"p":[[[-69.899,12.452,-69.896,12.423,-69.942,12.439,-70.004,12.5,-70.066,12.547,-70.051,12.597,-70.035,12.614,-69.973,12.568,-69.912,12.48,-69.899,12.452]]]},{"n":"Curaçao","c":"CW","r":5,"ml":5,"lx":-68.92,"ly":12.15,"b":[-69.159,12.045,-68.751,12.38],"p":[[[-68.751,12.06,-68.803,12.045,-68.995,12.142,-69.154,12.298,-69.159,12.38,-69.118,12.373,-69.077,12.342,-69.013,12.231,-68.827,12.159,-68.751,12.06]]]},{"n":"Nepal","c":"NP","r":3,"ml":3,"lx":83.64,"ly":28.3,"b":[80.052,26.36,88.162,30.388],"p":[[[88.11,27.871,88.15,27.843,88.154,27.799,88.147,27.749,88.106,27.642,88.068,27.567,88.024,27.409,87.984,27.134,87.993,27.086,88.111,26.928,88.157,26.807,88.162,26.725,88.112,26.586,88.055,26.43,88.027,26.395,87.995,26.382,87.849,26.437,87.749,26.429,87.633,26.399,87.513,26.405,87.414,26.423,87.287,26.36,87.167,26.394,87.09,26.433,87.038,26.542,87.016,26.555,86.763,26.442,86.701,26.435,86.544,26.496,86.414,26.556,86.366,26.574,86.242,26.598,86.129,26.612,86.007,26.649,85.856,26.6,85.795,26.604,85.737,26.64,85.707,26.713,85.7,26.782,85.648,26.829,85.568,26.84,85.456,26.797,85.293,26.741,85.24,26.75,85.192,26.767,85.174,26.782,85.152,26.847,85.125,26.861,85.087,26.863,85.02,26.879,84.937,26.927,84.685,27.041,84.654,27.092,84.655,27.204,84.641,27.25,84.61,27.299,84.481,27.348,84.23,27.428,84.091,27.491,84.025,27.462,83.897,27.435,83.829,27.378,83.747,27.396,83.552,27.456,83.447,27.465,83.384,27.445,83.369,27.41,83.29,27.371,83.214,27.402,83.064,27.445,82.933,27.468,82.733,27.519,82.711,27.597,82.677,27.673,82.63,27.687,82.451,27.672,82.288,27.757,82.112,27.865,82.037,27.901,81.988,27.914,81.945,27.899,81.897,27.874,81.853,27.867,81.757,27.914,81.636,27.98,81.486,28.062,81.311,28.176,81.239,28.241,81.206,28.289,81.169,28.335,81.017,28.41,80.896,28.469,80.751,28.54,80.726,28.554,80.671,28.596,80.587,28.65,80.518,28.665,80.496,28.636,80.479,28.605,80.419,28.612,80.325,28.666,80.227,28.723,80.15,28.776,80.071,28.83,80.052,28.87,80.085,28.994,80.13,29.1,80.17,29.124,80.233,29.195,80.256,29.318,80.255,29.423,80.317,29.572,80.402,29.73,80.549,29.9,80.613,29.956,80.684,29.994,80.82,30.119,80.848,30.14,80.908,30.172,80.966,30.18,81.01,30.165,81.056,30.099,81.11,30.037,81.177,30.04,81.255,30.093,81.417,30.338,81.642,30.388,81.855,30.362,82.043,30.327,82.099,30.245,82.135,30.159,82.159,30.115,82.221,30.064,82.487,29.942,82.641,29.831,82.854,29.683,83.014,29.618,83.155,29.613,83.235,29.555,83.355,29.439,83.457,29.306,83.583,29.184,83.671,29.188,83.79,29.227,83.936,29.279,84.022,29.254,84.101,29.22,84.128,29.156,84.176,29.036,84.229,28.912,84.312,28.868,84.411,28.804,84.465,28.753,84.651,28.66,84.677,28.622,84.714,28.596,84.759,28.579,84.797,28.56,84.855,28.554,85.069,28.61,85.126,28.603,85.159,28.592,85.16,28.572,85.121,28.484,85.089,28.372,85.122,28.316,85.212,28.293,85.411,28.276,85.678,28.277,85.759,28.221,85.84,28.135,85.922,27.99,85.954,27.928,85.995,27.91,86.064,27.935,86.075,27.995,86.079,28.084,86.137,28.114,86.174,28.092,86.218,28.022,86.329,27.96,86.409,27.929,86.485,27.94,86.517,27.964,86.554,28.085,86.614,28.103,86.691,28.095,86.72,28.071,86.75,28.022,86.842,27.999,86.934,27.968,87.02,27.929,87.141,27.838,87.291,27.822,87.464,27.824,87.555,27.822,87.623,27.815,87.683,27.821,87.861,27.886,87.933,27.891,88.023,27.883,88.11,27.871]]]},{"n":"Nauru","c":"NR","r":6,"ml":5,"lx":166.93,"ly":-0.52,"b":[166.907,-0.551,166.958,-0.489],"p":[[[166.958,-0.517,166.939,-0.551,166.916,-0.546,166.907,-0.524,166.914,-0.499,166.939,-0.489,166.956,-0.497,166.958,-0.517]]]},{"n":"Namibia","c":"NA","r":3,"ml":3,"lx":17.11,"ly":-20.58,"b":[11.722,-28.939,25.259,-16.968],"p":[[[23.381,-17.641,23.595,-17.599,23.799,-17.56,24.037,-17.521,24.227,-17.49,24.275,-17.481,24.733,-17.518,24.932,-17.543,25.002,-17.569,25.092,-17.634,25.259,-17.794,25.216,-17.788,24.909,-17.821,24.792,-17.865,24.531,-18.053,24.475,-18.029,24.412,-17.989,24.359,-17.978,24.244,-18.023,24.129,-18.078,24.003,-18.154,23.898,-18.229,23.864,-18.27,23.7,-18.424,23.647,-18.449,23.6,-18.46,23.581,-18.453,23.56,-18.386,23.46,-18.231,23.299,-18.027,23.252,-18.008,23.219,-18,23.1,-18.01,22.753,-18.067,22.46,-18.116,22.011,-18.199,21.53,-18.266,21.233,-18.307,20.974,-18.319,20.974,-18.521,20.975,-18.929,20.976,-19.336,20.976,-19.744,20.977,-20.152,20.977,-20.56,20.978,-20.968,20.979,-21.376,20.979,-21.784,20.979,-21.962,20.971,-22,20.823,-22,20.488,-22,20.205,-22,19.977,-22,19.978,-22.243,19.978,-22.529,19.978,-22.816,19.979,-23.103,19.979,-23.389,19.979,-23.676,19.98,-23.962,19.98,-24.249,19.98,-24.536,19.98,-24.752,19.98,-24.777,19.98,-25.197,19.98,-25.642,19.98,-26.086,19.98,-26.531,19.98,-26.976,19.98,-27.421,19.98,-27.866,19.98,-28.31,19.98,-28.451,19.878,-28.449,19.671,-28.504,19.54,-28.575,19.483,-28.662,19.407,-28.714,19.313,-28.733,19.271,-28.778,19.282,-28.848,19.246,-28.902,19.162,-28.939,19.026,-28.928,18.839,-28.869,18.6,-28.855,18.311,-28.886,18.103,-28.872,17.976,-28.811,17.842,-28.777,17.699,-28.768,17.617,-28.743,17.448,-28.698,17.416,-28.621,17.396,-28.563,17.348,-28.501,17.343,-28.452,17.38,-28.414,17.386,-28.353,17.359,-28.269,17.312,-28.229,17.246,-28.231,17.205,-28.199,17.188,-28.133,17.149,-28.082,17.056,-28.031,16.933,-28.07,16.875,-28.128,16.841,-28.219,16.81,-28.265,16.795,-28.341,16.788,-28.395,16.756,-28.452,16.723,-28.475,16.689,-28.465,16.626,-28.488,16.487,-28.573,16.448,-28.618,16.335,-28.537,16.007,-28.232,15.891,-28.153,15.719,-27.966,15.342,-27.387,15.288,-27.275,15.216,-26.995,15.133,-26.788,15.124,-26.668,15.163,-26.6,15.139,-26.508,15.097,-26.426,14.968,-26.318,14.931,-25.958,14.845,-25.726,14.864,-25.534,14.823,-25.359,14.819,-25.246,14.837,-25.033,14.768,-24.788,14.628,-24.548,14.502,-24.202,14.483,-24.05,14.497,-23.643,14.472,-23.477,14.474,-23.281,14.424,-23.079,14.403,-22.968,14.438,-22.881,14.459,-22.908,14.496,-22.921,14.52,-22.805,14.526,-22.703,14.463,-22.449,14.322,-22.19,13.973,-21.768,13.888,-21.607,13.839,-21.473,13.451,-20.917,13.284,-20.524,13.168,-20.185,13.042,-20.028,12.458,-18.927,12.329,-18.751,12.096,-18.541,12.041,-18.471,11.951,-18.271,11.776,-18.002,11.733,-17.751,11.722,-17.467,11.743,-17.249,11.903,-17.227,12.014,-17.169,12.114,-17.165,12.213,-17.21,12.318,-17.213,12.359,-17.206,12.548,-17.213,12.657,-17.161,12.785,-17.108,12.859,-17.063,12.963,-17.015,13.101,-16.968,13.179,-16.972,13.276,-16.99,13.404,-17.008,13.476,-17.04,13.562,-17.141,13.694,-17.233,13.792,-17.288,13.904,-17.361,13.938,-17.389,13.987,-17.404,14.017,-17.409,14.226,-17.398,14.415,-17.388,14.618,-17.388,15.001,-17.389,15.383,-17.389,15.766,-17.39,16.148,-17.39,16.531,-17.391,16.914,-17.391,17.296,-17.392,17.679,-17.393,17.835,-17.393,18.109,-17.396,18.396,-17.399,18.428,-17.405,18.46,-17.425,18.487,-17.443,18.588,-17.57,18.718,-17.703,18.826,-17.766,18.955,-17.804,19.076,-17.818,19.189,-17.808,19.377,-17.825,19.639,-17.869,19.912,-17.881,20.194,-17.864,20.393,-17.887,20.508,-17.953,20.625,-17.997,20.746,-18.02,20.908,-18.006,21.113,-17.956,21.288,-17.963,21.369,-18,21.417,-18.001,21.718,-17.948,21.961,-17.905,22.324,-17.837,22.624,-17.782,23.068,-17.699,23.381,-17.641]]]},{"n":"Mozambique","c":"MZ","r":3,"ml":3,"lx":37.84,"ly":-13.94,"b":[30.222,-26.862,40.845,-10.464],"p":[[[31.288,-22.402,31.429,-22.299,31.571,-22.154,31.738,-21.983,31.886,-21.832,32.016,-21.698,32.195,-21.515,32.371,-21.335,32.412,-21.312,32.43,-21.297,32.354,-21.137,32.476,-20.95,32.483,-20.829,32.478,-20.713,32.492,-20.66,32.529,-20.613,32.673,-20.516,32.781,-20.362,32.87,-20.217,32.993,-19.985,33.005,-19.93,33.007,-19.874,32.973,-19.795,32.89,-19.668,32.831,-19.558,32.778,-19.389,32.831,-19.241,32.85,-19.152,32.85,-19.104,32.826,-19.059,32.766,-19.024,32.717,-19.002,32.7,-18.941,32.699,-18.868,32.722,-18.828,32.854,-18.764,32.885,-18.729,32.9,-18.689,32.902,-18.633,32.942,-18.493,32.993,-18.36,32.996,-18.313,32.979,-18.271,32.965,-18.196,32.956,-18.083,32.955,-17.765,32.981,-17.437,32.969,-17.252,32.884,-17.038,32.876,-16.884,32.938,-16.776,32.948,-16.712,32.903,-16.704,32.81,-16.698,32.742,-16.678,32.636,-16.589,32.452,-16.516,32.243,-16.449,31.94,-16.429,31.688,-16.214,31.49,-16.18,31.426,-16.152,31.236,-16.024,30.939,-16.012,30.63,-15.999,30.438,-15.995,30.409,-15.978,30.398,-15.801,30.396,-15.643,30.38,-15.506,30.351,-15.35,30.306,-15.289,30.252,-15.183,30.225,-15.067,30.222,-15.011,30.232,-14.99,30.446,-14.908,30.538,-14.867,30.673,-14.819,30.915,-14.753,31.131,-14.695,31.329,-14.638,31.538,-14.577,31.623,-14.537,31.729,-14.496,31.982,-14.414,32.054,-14.387,32.2,-14.341,32.273,-14.323,32.553,-14.23,32.875,-14.122,32.987,-14.085,33.202,-14.013,33.244,-14.043,33.39,-14.289,33.505,-14.434,33.636,-14.568,33.658,-14.562,33.696,-14.53,33.761,-14.517,33.97,-14.487,34.049,-14.485,34.102,-14.449,34.209,-14.424,34.333,-14.409,34.375,-14.425,34.505,-14.598,34.524,-14.731,34.551,-14.922,34.558,-15.016,34.555,-15.141,34.541,-15.297,34.435,-15.477,34.415,-15.567,34.358,-15.705,34.283,-15.773,34.246,-15.829,34.248,-15.887,34.288,-15.936,34.376,-16.024,34.403,-16.08,34.395,-16.131,34.396,-16.199,34.416,-16.247,34.441,-16.274,34.528,-16.319,34.613,-16.432,34.759,-16.567,34.933,-16.76,35.015,-16.82,35.08,-16.834,35.112,-16.899,35.094,-16.974,35.044,-17.017,35.065,-17.079,35.093,-17.111,35.125,-17.127,35.201,-17.131,35.273,-17.118,35.29,-17.097,35.281,-16.808,35.23,-16.639,35.178,-16.573,35.167,-16.56,35.185,-16.505,35.243,-16.375,35.292,-16.247,35.322,-16.193,35.358,-16.161,35.599,-16.126,35.709,-16.096,35.755,-16.058,35.791,-15.959,35.82,-15.68,35.83,-15.419,35.805,-15.266,35.84,-15.035,35.893,-14.892,35.867,-14.864,35.847,-14.671,35.69,-14.466,35.488,-14.201,35.376,-14.059,35.247,-13.897,35.014,-13.643,34.907,-13.552,34.85,-13.516,34.662,-13.487,34.612,-13.438,34.564,-13.36,34.546,-13.216,34.543,-13.109,34.521,-12.926,34.483,-12.667,34.466,-12.591,34.412,-12.396,34.361,-12.211,34.358,-12.165,34.376,-12.12,34.463,-11.984,34.525,-11.887,34.554,-11.834,34.606,-11.69,34.619,-11.62,34.66,-11.589,34.827,-11.576,34.959,-11.578,35.183,-11.575,35.418,-11.583,35.451,-11.59,35.504,-11.605,35.564,-11.602,35.631,-11.582,35.705,-11.532,35.785,-11.453,35.911,-11.455,36.082,-11.537,36.175,-11.609,36.191,-11.671,36.306,-11.706,36.519,-11.716,36.674,-11.684,36.771,-11.61,36.873,-11.571,36.979,-11.567,37.059,-11.592,37.114,-11.647,37.218,-11.687,37.373,-11.71,37.542,-11.675,37.725,-11.581,37.829,-11.482,37.855,-11.379,37.885,-11.317,37.92,-11.295,38.017,-11.282,38.177,-11.279,38.315,-11.311,38.492,-11.413,38.603,-11.345,38.795,-11.229,38.988,-11.167,39.171,-11.167,39.322,-11.123,39.439,-11.035,39.563,-10.979,39.694,-10.955,39.817,-10.912,39.989,-10.821,40.166,-10.687,40.347,-10.552,40.464,-10.464,40.517,-10.567,40.612,-10.662,40.555,-10.716,40.487,-10.765,40.597,-10.831,40.516,-10.93,40.506,-10.998,40.527,-11.025,40.545,-11.066,40.491,-11.179,40.421,-11.266,40.403,-11.332,40.465,-11.449,40.433,-11.657,40.494,-11.844,40.51,-11.94,40.532,-12.005,40.501,-12.119,40.509,-12.313,40.523,-12.393,40.487,-12.492,40.548,-12.527,40.581,-12.636,40.572,-12.758,40.553,-12.825,40.448,-12.905,40.435,-12.936,40.437,-12.983,40.569,-12.985,40.573,-13.058,40.564,-13.115,40.57,-13.223,40.552,-13.294,40.583,-13.374,40.545,-13.463,40.558,-13.531,40.56,-13.62,40.591,-13.845,40.596,-14.123,40.603,-14.167,40.65,-14.199,40.716,-14.214,40.713,-14.291,40.64,-14.39,40.636,-14.452,40.646,-14.539,40.727,-14.421,40.775,-14.421,40.818,-14.468,40.812,-14.536,40.827,-14.569,40.821,-14.635,40.845,-14.719,40.835,-14.792,40.776,-14.842,40.701,-14.93,40.687,-15.012,40.694,-15.065,40.642,-15.082,40.618,-15.116,40.653,-15.193,40.651,-15.261,40.559,-15.473,40.314,-15.764,40.208,-15.867,40.109,-15.979,40.109,-16.025,40.099,-16.065,39.984,-16.225,39.86,-16.252,39.791,-16.295,39.845,-16.436,39.765,-16.468,39.625,-16.579,39.242,-16.793,39.182,-16.842,39.084,-16.973,38.956,-17.005,38.885,-17.042,38.758,-17.055,38.713,-17.046,38.67,-17.05,38.633,-17.078,38.381,-17.17,38.145,-17.243,38.087,-17.276,38.048,-17.321,37.839,-17.393,37.512,-17.571,37.245,-17.74,37.051,-17.909,37,-17.935,36.939,-17.993,36.919,-18.08,36.9,-18.129,36.756,-18.307,36.54,-18.518,36.498,-18.576,36.412,-18.693,36.404,-18.77,36.327,-18.793,36.263,-18.72,36.236,-18.861,36.183,-18.871,36.125,-18.842,35.98,-18.912,35.854,-18.993,35.651,-19.164,35.365,-19.494,34.948,-19.813,34.891,-19.822,34.852,-19.821,34.721,-19.71,34.649,-19.701,34.713,-19.767,34.756,-19.822,34.745,-19.929,34.75,-20.091,34.698,-20.404,34.705,-20.473,34.765,-20.562,34.877,-20.671,34.982,-20.806,35.118,-21.195,35.128,-21.395,35.268,-21.651,35.273,-21.762,35.329,-22.037,35.326,-22.26,35.316,-22.397,35.383,-22.455,35.408,-22.403,35.401,-22.316,35.419,-22.178,35.456,-22.116,35.494,-22.125,35.505,-22.19,35.53,-22.248,35.54,-22.303,35.542,-22.377,35.49,-22.658,35.506,-22.772,35.575,-22.963,35.494,-23.185,35.377,-23.708,35.37,-23.798,35.399,-23.838,35.462,-23.851,35.485,-23.784,35.522,-23.785,35.542,-23.824,35.49,-24.066,35.438,-24.171,35.255,-24.43,35.156,-24.541,34.992,-24.651,34.607,-24.821,33.836,-25.068,33.53,-25.189,33.347,-25.261,32.961,-25.49,32.792,-25.644,32.723,-25.821,32.656,-25.902,32.59,-26.004,32.647,-26.092,32.704,-26.158,32.77,-26.203,32.804,-26.241,32.849,-26.268,32.894,-26.13,32.916,-26.087,32.955,-26.084,32.934,-26.252,32.889,-26.83,32.886,-26.849,32.777,-26.851,32.589,-26.856,32.478,-26.858,32.354,-26.862,32.2,-26.833,32.113,-26.839,32.106,-26.52,32.078,-26.45,32.048,-26.347,32.041,-26.281,32.06,-26.215,32.069,-26.11,32.061,-26.018,31.968,-25.972,31.948,-25.958,31.928,-25.885,31.92,-25.774,31.985,-25.632,31.979,-25.359,31.987,-25.263,31.986,-25.074,31.984,-24.844,31.983,-24.638,31.986,-24.461,31.967,-24.376,31.951,-24.33,31.908,-24.236,31.858,-24.04,31.8,-23.892,31.724,-23.795,31.7,-23.743,31.676,-23.674,31.604,-23.553,31.546,-23.482,31.53,-23.426,31.532,-23.279,31.467,-23.017,31.419,-22.825,31.348,-22.618,31.3,-22.479,31.293,-22.455,31.288,-22.402],[34.642,-12.014,34.624,-11.985,34.591,-11.971,34.554,-11.982,34.542,-12.019,34.58,-12.066,34.622,-12.067,34.642,-12.014],[34.719,-12.111,34.746,-12.088,34.756,-12.059,34.756,-12.031,34.739,-12.013,34.715,-12.003,34.68,-12.009,34.667,-12.048,34.662,-12.101,34.684,-12.119,34.719,-12.111]]]},{"n":"Morocco","c":"MA","r":3,"ml":2.7,"lx":-7.19,"ly":31.65,"b":[-17.003,21.421,-1.066,35.93],"p":[[[-2.22,35.104,-2.191,35.03,-2.132,34.971,-1.921,34.836,-1.796,34.752,-1.792,34.723,-1.832,34.655,-1.85,34.607,-1.817,34.557,-1.739,34.496,-1.733,34.467,-1.752,34.433,-1.792,34.368,-1.707,34.176,-1.693,33.99,-1.715,33.858,-1.714,33.782,-1.703,33.717,-1.631,33.567,-1.679,33.319,-1.625,33.183,-1.551,33.074,-1.51,32.878,-1.45,32.785,-1.352,32.703,-1.296,32.676,-1.188,32.608,-1.111,32.552,-1.066,32.468,-1.163,32.399,-1.24,32.338,-1.262,32.271,-1.226,32.165,-1.226,32.107,-1.275,32.089,-1.477,32.095,-1.635,32.1,-1.817,32.105,-2.073,32.115,-2.231,32.121,-2.448,32.13,-2.523,32.126,-2.723,32.096,-2.863,32.075,-2.887,32.069,-2.931,32.043,-2.961,31.964,-2.988,31.874,-3.017,31.834,-3.44,31.705,-3.605,31.687,-3.7,31.7,-3.768,31.69,-3.827,31.662,-3.847,31.62,-3.85,31.566,-3.837,31.512,-3.796,31.437,-3.789,31.362,-3.815,31.309,-3.821,31.255,-3.833,31.198,-3.812,31.167,-3.771,31.162,-3.73,31.135,-3.673,31.111,-3.625,31.066,-3.627,31.001,-3.667,30.964,-3.702,30.944,-3.86,30.927,-3.985,30.914,-4.149,30.81,-4.323,30.699,-4.529,30.626,-4.62,30.605,-4.779,30.552,-4.968,30.465,-5.062,30.326,-5.18,30.166,-5.294,30.059,-5.449,29.957,-5.593,29.918,-5.775,29.869,-6.004,29.831,-6.167,29.819,-6.215,29.811,-6.358,29.808,-6.428,29.816,-6.48,29.82,-6.501,29.809,-6.508,29.784,-6.511,29.726,-6.521,29.66,-6.566,29.604,-6.598,29.579,-6.635,29.569,-6.755,29.584,-6.856,29.602,-7.095,29.625,-7.142,29.62,-7.16,29.613,-7.235,29.575,-7.35,29.495,-7.428,29.425,-7.486,29.392,-7.625,29.375,-7.685,29.35,-7.944,29.175,-7.999,29.132,-8.265,28.981,-8.34,28.93,-8.399,28.88,-8.558,28.768,-8.66,28.719,-8.678,28.689,-8.683,28.621,-8.683,28.469,-8.683,28.324,-8.683,28.112,-8.683,27.9,-8.683,27.656,-8.818,27.656,-8.814,27.614,-8.785,27.531,-8.774,27.461,-8.789,27.417,-8.803,27.361,-8.797,27.308,-8.774,27.251,-8.754,27.191,-8.754,27.151,-8.795,27.121,-8.889,27.104,-9.002,27.09,-9.084,27.09,-9.208,27.1,-9.286,27.098,-9.353,27.098,-9.413,27.088,-9.487,27.05,-9.57,26.991,-9.673,26.911,-9.735,26.861,-9.818,26.85,-9.9,26.85,-9.981,26.89,-10.033,26.911,-10.067,26.909,-10.123,26.88,-10.189,26.861,-10.251,26.861,-10.355,26.901,-10.479,26.961,-10.551,26.991,-10.654,27.001,-10.758,27.02,-10.83,27.01,-10.923,27.01,-11.047,26.97,-11.15,26.941,-11.264,26.911,-11.393,26.883,-11.361,26.794,-11.317,26.745,-11.317,26.684,-11.338,26.633,-11.4,26.584,-11.471,26.52,-11.512,26.47,-11.553,26.401,-11.584,26.361,-11.637,26.296,-11.685,26.213,-11.699,26.163,-11.718,26.104,-11.755,26.087,-11.881,26.071,-11.961,26.05,-12.031,26.031,-12.057,25.996,-12.061,25.991,-12.081,25.921,-12.081,25.871,-12.101,25.831,-12.131,25.731,-12.171,25.64,-12.201,25.52,-12.231,25.421,-12.271,25.26,-12.311,25.111,-12.361,24.97,-12.401,24.88,-12.431,24.831,-12.501,24.77,-12.561,24.731,-12.631,24.68,-12.711,24.63,-12.821,24.571,-12.911,24.52,-12.948,24.497,-12.991,24.47,-13.061,24.401,-13.121,24.3,-13.161,24.22,-13.231,24.09,-13.281,24.02,-13.311,23.981,-13.391,23.941,-13.481,23.911,-13.581,23.871,-13.661,23.831,-13.771,23.791,-13.841,23.751,-13.891,23.691,-13.931,23.621,-13.981,23.52,-14.021,23.411,-14.041,23.34,-14.101,23.1,-14.121,22.961,-14.141,22.871,-14.171,22.76,-14.191,22.59,-14.191,22.451,-14.211,22.371,-14.221,22.31,-14.271,22.241,-14.311,22.191,-14.381,22.121,-14.441,22.081,-14.461,22.041,-14.521,21.991,-14.581,21.911,-14.631,21.861,-14.621,21.821,-14.611,21.751,-14.641,21.68,-14.671,21.6,-14.751,21.501,-14.841,21.451,-14.971,21.441,-15.151,21.441,-15.291,21.451,-15.461,21.451,-15.611,21.47,-15.751,21.491,-15.921,21.501,-16.041,21.501,-16.191,21.481,-16.581,21.481,-16.731,21.47,-16.951,21.43,-17.003,21.421,-16.931,21.9,-16.793,22.16,-16.684,22.274,-16.514,22.333,-16.359,22.595,-16.304,22.835,-16.202,22.945,-16.17,23.032,-16.21,23.098,-16.114,23.228,-15.997,23.425,-15.943,23.553,-15.806,23.75,-15.789,23.793,-15.802,23.842,-15.855,23.8,-15.913,23.728,-15.981,23.67,-15.953,23.741,-15.899,23.844,-15.778,23.953,-15.586,24.073,-15.189,24.479,-15.039,24.549,-14.904,24.72,-14.856,24.872,-14.843,25.22,-14.795,25.404,-14.707,25.548,-14.602,25.809,-14.523,25.925,-14.471,26.163,-14.414,26.254,-14.312,26.297,-14.168,26.415,-13.952,26.489,-13.696,26.643,-13.576,26.735,-13.496,26.873,-13.41,27.147,-13.256,27.435,-13.177,27.652,-13.176,27.656,-13.041,27.77,-12.949,27.914,-12.794,27.978,-12.469,28.009,-11.986,28.129,-11.553,28.31,-11.43,28.382,-11.299,28.526,-11.081,28.714,-10.674,28.939,-10.486,29.065,-10.201,29.38,-10.01,29.641,-9.853,29.809,-9.743,29.958,-9.667,30.109,-9.624,30.353,-9.653,30.448,-9.773,30.603,-9.854,30.645,-9.875,30.718,-9.832,30.847,-9.833,31.07,-9.809,31.425,-9.675,31.711,-9.347,32.086,-9.287,32.241,-9.249,32.486,-9.246,32.572,-8.836,32.92,-8.596,33.187,-8.513,33.252,-8.301,33.374,-7.562,33.64,-7.145,33.83,-6.901,33.969,-6.756,34.133,-6.353,34.776,-5.958,35.681,-5.925,35.786,-5.748,35.816,-5.623,35.829,-5.522,35.862,-5.397,35.93,-5.278,35.903,-5.338,35.857,-5.338,35.745,-5.253,35.615,-5.105,35.468,-4.837,35.281,-4.628,35.206,-4.33,35.161,-3.982,35.243,-3.788,35.245,-3.693,35.28,-3.591,35.228,-3.395,35.212,-3.206,35.239,-3.063,35.317,-2.972,35.407,-2.958,35.363,-2.954,35.315,-2.926,35.287,-2.87,35.173,-2.84,35.128,-2.731,35.135,-2.637,35.113,-2.424,35.123,-2.22,35.104]]]},{"n":"Western Sahara","c":"EH","r":7,"ml":6,"lx":-12.63,"ly":23.97,"b":[-17.099,20.806,-8.682,27.656],"p":[[[-8.818,27.656,-8.683,27.656,-8.683,27.49,-8.683,27.286,-8.683,27.119,-8.683,26.921,-8.683,26.723,-8.682,26.498,-8.682,26.273,-8.682,26.109,-8.682,25.996,-8.886,25.996,-9.072,25.996,-9.258,25.996,-9.445,25.996,-9.631,25.996,-9.817,25.995,-10.004,25.995,-10.19,25.995,-10.376,25.995,-10.562,25.995,-10.749,25.995,-10.935,25.995,-11.121,25.995,-11.308,25.995,-11.494,25.995,-11.68,25.995,-11.867,25.995,-12.016,25.995,-12.016,25.876,-12.016,25.74,-12.016,25.604,-12.016,25.468,-12.016,25.332,-12.016,25.196,-12.016,25.059,-12.016,24.923,-12.016,24.787,-12.016,24.651,-12.016,24.515,-12.016,24.379,-12.016,24.242,-12.016,24.106,-12.016,23.97,-12.016,23.834,-12.016,23.698,-12.016,23.576,-12.023,23.468,-12.083,23.435,-12.226,23.377,-12.373,23.318,-12.559,23.291,-12.62,23.271,-12.74,23.193,-12.896,23.09,-13.031,23,-13.121,22.884,-13.153,22.821,-13.167,22.753,-13.156,22.689,-13.107,22.561,-13.094,22.496,-13.087,22.383,-13.078,22.26,-13.07,22.128,-13.061,21.996,-13.051,21.855,-13.042,21.714,-13.032,21.572,-13.025,21.467,-13.016,21.334,-13.167,21.334,-13.397,21.334,-13.626,21.333,-13.855,21.333,-14.085,21.333,-14.314,21.332,-14.543,21.332,-14.773,21.332,-15.002,21.332,-15.231,21.331,-15.461,21.331,-15.69,21.331,-15.919,21.33,-16.148,21.33,-16.378,21.33,-16.607,21.33,-16.836,21.329,-16.965,21.329,-17.006,21.142,-17.042,21.008,-17.064,20.899,-17.048,20.806,-17.099,20.857,-17.01,21.377,-17.003,21.421,-16.951,21.43,-16.731,21.47,-16.581,21.481,-16.191,21.481,-16.041,21.501,-15.921,21.501,-15.751,21.491,-15.611,21.47,-15.461,21.451,-15.291,21.451,-15.151,21.441,-14.971,21.441,-14.841,21.451,-14.751,21.501,-14.671,21.6,-14.641,21.68,-14.611,21.751,-14.621,21.821,-14.631,21.861,-14.581,21.911,-14.521,21.991,-14.461,22.041,-14.441,22.081,-14.381,22.121,-14.311,22.191,-14.271,22.241,-14.221,22.31,-14.211,22.371,-14.191,22.451,-14.191,22.59,-14.171,22.76,-14.141,22.871,-14.121,22.961,-14.101,23.1,-14.041,23.34,-14.021,23.411,-13.981,23.52,-13.931,23.621,-13.891,23.691,-13.841,23.751,-13.771,23.791,-13.661,23.831,-13.581,23.871,-13.481,23.911,-13.391,23.941,-13.311,23.981,-13.281,24.02,-13.231,24.09,-13.161,24.22,-13.121,24.3,-13.061,24.401,-12.991,24.47,-12.948,24.497,-12.911,24.52,-12.821,24.571,-12.711,24.63,-12.631,24.68,-12.561,24.731,-12.501,24.77,-12.431,24.831,-12.401,24.88,-12.361,24.97,-12.311,25.111,-12.271,25.26,-12.231,25.421,-12.201,25.52,-12.171,25.64,-12.131,25.731,-12.101,25.831,-12.081,25.871,-12.081,25.921,-12.061,25.991,-12.057,25.996,-12.031,26.031,-11.961,26.05,-11.881,26.071,-11.755,26.087,-11.718,26.104,-11.699,26.163,-11.685,26.213,-11.637,26.296,-11.584,26.361,-11.553,26.401,-11.512,26.47,-11.471,26.52,-11.4,26.584,-11.338,26.633,-11.317,26.684,-11.317,26.745,-11.361,26.794,-11.393,26.883,-11.264,26.911,-11.15,26.941,-11.047,26.97,-10.923,27.01,-10.83,27.01,-10.758,27.02,-10.654,27.001,-10.551,26.991,-10.479,26.961,-10.355,26.901,-10.251,26.861,-10.189,26.861,-10.123,26.88,-10.067,26.909,-10.033,26.911,-9.981,26.89,-9.9,26.85,-9.818,26.85,-9.735,26.861,-9.673,26.911,-9.57,26.991,-9.487,27.05,-9.413,27.088,-9.353,27.098,-9.286,27.098,-9.208,27.1,-9.084,27.09,-9.002,27.09,-8.889,27.104,-8.795,27.121,-8.754,27.151,-8.754,27.191,-8.774,27.251,-8.797,27.308,-8.803,27.361,-8.789,27.417,-8.774,27.461,-8.785,27.531,-8.814,27.614,-8.818,27.656]]]},{"n":"Montenegro","c":"ME","r":6,"ml":5,"lx":19.14,"ly":42.8,"b":[18.436,41.869,20.348,43.542],"p":[[[19.194,43.533,19.192,43.521,19.196,43.485,19.219,43.45,19.298,43.414,19.415,43.343,19.552,43.212,19.614,43.173,19.671,43.164,19.781,43.11,19.858,43.097,19.944,43.082,20.168,42.969,20.268,42.935,20.34,42.893,20.348,42.853,20.344,42.828,20.215,42.799,20.193,42.755,20.13,42.76,20.054,42.76,20.029,42.732,20.066,42.686,20.089,42.632,20.07,42.557,20.064,42.547,20.046,42.55,19.939,42.507,19.86,42.486,19.788,42.476,19.754,42.497,19.738,42.525,19.741,42.607,19.728,42.635,19.703,42.648,19.654,42.629,19.597,42.565,19.545,42.492,19.465,42.415,19.4,42.342,19.329,42.249,19.281,42.173,19.331,42.129,19.361,42.069,19.352,42.024,19.361,41.998,19.346,41.919,19.342,41.869,19.186,41.949,19.122,42.06,18.894,42.249,18.633,42.378,18.619,42.398,18.633,42.423,18.646,42.443,18.592,42.444,18.554,42.429,18.517,42.433,18.477,42.481,18.438,42.523,18.436,42.56,18.454,42.565,18.48,42.579,18.535,42.62,18.546,42.642,18.543,42.674,18.466,42.777,18.455,42.844,18.444,42.968,18.46,42.998,18.488,43.012,18.624,43.028,18.622,43.125,18.63,43.154,18.657,43.194,18.674,43.231,18.749,43.284,18.851,43.346,18.896,43.348,18.935,43.339,18.979,43.285,19.027,43.292,19.037,43.357,18.974,43.442,18.94,43.497,18.951,43.527,18.974,43.542,19.028,43.533,19.08,43.518,19.113,43.528,19.164,43.535,19.194,43.533]]]},{"n":"Mongolia","c":"MN","r":3,"ml":3,"lx":104.15,"ly":46,"b":[87.743,41.596,119.898,52.117],"p":[[[87.814,49.162,87.818,49.162,87.935,49.165,87.988,49.187,88.029,49.22,88.116,49.256,88.134,49.298,88.136,49.381,88.193,49.452,88.338,49.473,88.393,49.483,88.452,49.473,88.544,49.483,88.633,49.486,88.683,49.465,88.748,49.446,88.832,49.448,88.86,49.482,88.864,49.528,88.9,49.54,88.945,49.508,88.971,49.484,89.008,49.473,89.109,49.501,89.18,49.532,89.203,49.596,89.244,49.627,89.299,49.611,89.396,49.612,89.475,49.661,89.579,49.7,89.654,49.717,89.67,49.75,89.634,49.823,89.644,49.903,89.744,49.948,89.878,49.954,89.977,49.984,90.005,50.069,90.054,50.094,90.104,50.103,90.225,50.117,90.311,50.151,90.365,50.167,90.517,50.213,90.655,50.222,90.714,50.259,90.761,50.306,90.838,50.324,90.917,50.364,91.022,50.415,91.063,50.423,91.234,50.452,91.301,50.463,91.341,50.47,91.415,50.468,91.446,50.522,91.522,50.562,91.597,50.576,91.634,50.615,91.706,50.666,91.804,50.694,91.957,50.698,92.104,50.692,92.192,50.701,92.265,50.775,92.279,50.812,92.296,50.85,92.355,50.864,92.426,50.803,92.486,50.765,92.579,50.725,92.627,50.688,92.681,50.683,92.739,50.711,92.779,50.779,92.856,50.789,92.941,50.778,92.964,50.745,92.971,50.713,93.01,50.655,93.103,50.604,93.223,50.607,93.271,50.616,93.387,50.608,93.501,50.597,93.626,50.586,93.662,50.584,93.795,50.578,93.99,50.569,94.076,50.573,94.251,50.556,94.287,50.511,94.319,50.405,94.347,50.303,94.355,50.222,94.4,50.18,94.458,50.166,94.497,50.133,94.565,50.088,94.615,50.024,94.675,50.028,94.718,50.043,94.811,50.048,94.93,50.044,95.013,50.008,95.044,49.962,95.111,49.935,95.166,49.944,95.329,49.944,95.386,49.941,95.442,49.916,95.523,49.911,95.567,49.944,95.708,49.966,95.789,50.013,95.852,50.013,95.899,49.991,95.936,49.96,95.99,49.974,96.019,49.999,96.066,49.999,96.112,49.982,96.23,49.954,96.315,49.901,96.381,49.896,96.466,49.912,96.506,49.919,96.543,49.893,96.598,49.878,96.64,49.898,96.712,49.912,96.986,49.883,97.049,49.83,97.098,49.805,97.137,49.762,97.209,49.731,97.36,49.741,97.418,49.773,97.541,49.843,97.589,49.911,97.651,49.934,97.721,49.945,97.786,49.945,97.854,49.947,97.937,49.997,98.004,50.014,98.103,50.078,98.122,50.107,98.17,50.181,98.2,50.228,98.25,50.302,98.277,50.423,98.293,50.487,98.279,50.533,98.221,50.557,98.145,50.569,98.079,50.604,98.03,50.645,98.001,50.702,97.962,50.769,97.964,50.818,97.953,50.855,97.92,50.887,97.856,50.943,97.825,50.985,97.836,51.052,97.911,51.165,97.918,51.218,97.927,51.251,97.923,51.28,97.947,51.348,97.989,51.377,98.038,51.45,98.103,51.484,98.185,51.486,98.22,51.506,98.238,51.578,98.277,51.635,98.303,51.674,98.353,51.718,98.641,51.801,98.76,51.905,98.803,51.957,98.849,52.07,98.893,52.117,98.958,52.102,99.034,52.035,99.091,52.035,99.176,51.999,99.407,51.924,99.532,51.9,99.613,51.893,99.719,51.872,99.788,51.828,99.922,51.756,100.035,51.737,100.23,51.73,100.469,51.726,100.536,51.713,100.711,51.662,100.904,51.604,101.085,51.553,101.223,51.513,101.304,51.475,101.381,51.453,101.464,51.471,101.571,51.467,101.821,51.421,101.979,51.382,102.112,51.353,102.156,51.314,102.16,51.261,102.142,51.216,102.152,51.108,102.195,51.051,102.21,50.974,102.226,50.901,102.215,50.829,102.235,50.791,102.277,50.769,102.317,50.718,102.303,50.666,102.286,50.635,102.288,50.585,102.336,50.544,102.407,50.536,102.469,50.526,102.546,50.461,102.683,50.387,102.765,50.367,102.86,50.333,103.039,50.301,103.162,50.291,103.234,50.264,103.304,50.2,103.421,50.187,103.496,50.165,103.633,50.139,103.723,50.154,103.803,50.176,103.856,50.172,103.958,50.157,104.079,50.154,104.18,50.169,104.26,50.214,104.354,50.275,104.466,50.306,104.596,50.317,104.685,50.342,104.977,50.383,105.095,50.39,105.186,50.43,105.267,50.46,105.384,50.474,105.542,50.441,105.693,50.414,105.875,50.405,105.996,50.368,106.083,50.333,106.218,50.305,106.368,50.318,106.574,50.329,106.711,50.313,106.854,50.248,106.941,50.197,107.04,50.086,107.143,50.033,107.233,49.989,107.347,49.987,107.631,49.983,107.787,49.96,107.917,49.948,107.948,49.925,107.935,49.849,107.939,49.741,107.937,49.691,107.965,49.654,108.01,49.647,108.034,49.594,108.098,49.563,108.213,49.525,108.407,49.396,108.522,49.342,108.614,49.323,108.733,49.336,108.92,49.335,109.237,49.335,109.454,49.296,109.529,49.27,109.75,49.239,109.995,49.206,110.2,49.17,110.321,49.216,110.428,49.22,110.53,49.187,110.631,49.138,110.71,49.143,110.828,49.166,111.204,49.304,111.337,49.356,111.429,49.343,111.512,49.361,111.575,49.376,111.736,49.398,111.833,49.404,111.934,49.416,112.08,49.424,112.375,49.515,112.495,49.532,112.697,49.507,112.806,49.524,112.915,49.569,113.056,49.616,113.092,49.693,113.164,49.797,113.319,49.874,113.446,49.942,113.574,50.007,113.732,50.062,113.881,50.101,114.071,50.205,114.222,50.257,114.297,50.274,114.386,50.255,114.554,50.241,114.675,50.246,114.743,50.234,114.88,50.183,115.003,50.139,115.098,50.059,115.275,49.949,115.365,49.912,115.429,49.896,115.588,49.886,115.718,49.881,115.795,49.906,115.926,49.952,116.135,50.011,116.217,50.009,116.351,49.978,116.551,49.92,116.632,49.877,116.683,49.824,116.59,49.685,116.402,49.406,116.243,49.17,116.16,49.037,116.098,48.936,116.034,48.84,116.025,48.782,115.954,48.689,115.821,48.577,115.792,48.456,115.797,48.346,115.786,48.248,115.639,48.186,115.525,48.131,115.558,47.945,115.616,47.875,115.712,47.799,115.812,47.738,115.898,47.687,115.994,47.711,116.075,47.79,116.231,47.858,116.317,47.86,116.378,47.844,116.513,47.84,116.652,47.865,116.761,47.87,116.901,47.853,116.952,47.837,117.07,47.806,117.197,47.74,117.286,47.666,117.351,47.652,117.384,47.676,117.455,47.741,117.555,47.805,117.677,47.908,117.768,47.988,117.84,48,117.979,48,118.042,48.019,118.147,48.029,118.24,48,118.498,47.984,118.568,47.943,118.691,47.822,118.76,47.758,118.88,47.725,118.953,47.703,119.018,47.685,119.082,47.654,119.097,47.616,119.123,47.558,119.162,47.525,119.235,47.493,119.291,47.473,119.309,47.431,119.326,47.41,119.377,47.381,119.527,47.256,119.6,47.222,119.711,47.15,119.757,47.09,119.76,47.027,119.788,46.979,119.863,46.907,119.898,46.858,119.884,46.791,119.896,46.733,119.867,46.672,119.747,46.627,119.707,46.606,119.62,46.604,119.474,46.627,119.332,46.614,119.162,46.639,119.029,46.692,118.957,46.735,118.844,46.76,118.79,46.747,118.723,46.692,118.649,46.702,118.58,46.692,118.404,46.703,118.309,46.717,118.157,46.679,118.071,46.667,117.91,46.619,117.813,46.538,117.741,46.518,117.671,46.522,117.621,46.552,117.547,46.588,117.438,46.586,117.406,46.571,117.392,46.538,117.356,46.437,117.357,46.391,117.333,46.362,117.269,46.352,117.156,46.355,116.979,46.362,116.859,46.388,116.787,46.377,116.689,46.322,116.619,46.313,116.563,46.29,116.517,46.209,116.445,46.159,116.358,46.097,116.265,45.963,116.213,45.887,116.229,45.846,116.241,45.796,116.198,45.739,116.11,45.687,116.04,45.677,115.934,45.626,115.789,45.535,115.681,45.458,115.539,45.44,115.439,45.42,115.217,45.396,115.163,45.39,114.919,45.378,114.739,45.42,114.644,45.413,114.56,45.39,114.517,45.365,114.502,45.316,114.487,45.272,114.419,45.203,114.281,45.111,114.167,45.05,114.08,44.971,114.03,44.943,113.931,44.912,113.877,44.896,113.752,44.826,113.653,44.763,113.587,44.746,113.508,44.762,113.456,44.767,113.301,44.792,113.196,44.795,113.049,44.81,112.707,44.883,112.597,44.918,112.499,45.011,112.411,45.058,112.292,45.063,112.113,45.063,112.033,45.082,111.898,45.064,111.751,44.97,111.681,44.899,111.621,44.827,111.547,44.673,111.515,44.57,111.489,44.512,111.411,44.419,111.402,44.367,111.43,44.322,111.486,44.272,111.52,44.192,111.603,44.107,111.684,44.041,111.837,43.935,111.88,43.879,111.932,43.815,111.943,43.752,111.933,43.711,111.878,43.68,111.771,43.665,111.72,43.621,111.641,43.563,111.547,43.496,111.504,43.493,111.451,43.475,111.187,43.392,111.087,43.369,111.007,43.341,110.913,43.257,110.84,43.194,110.749,43.111,110.709,43.074,110.628,42.991,110.521,42.895,110.462,42.844,110.43,42.814,110.4,42.774,110.289,42.743,110.197,42.71,110.058,42.661,109.859,42.606,109.698,42.554,109.596,42.511,109.443,42.456,109.34,42.438,109.132,42.441,108.875,42.426,108.687,42.416,108.546,42.429,108.334,42.437,108.171,42.447,108.062,42.427,107.806,42.406,107.749,42.401,107.292,42.349,107.091,42.322,106.906,42.309,106.77,42.289,106.693,42.264,106.579,42.227,106.519,42.212,106.317,42.141,105.868,41.994,105.566,41.875,105.517,41.855,105.314,41.771,105.197,41.738,105.115,41.663,105.051,41.616,104.982,41.596,104.86,41.644,104.774,41.641,104.498,41.659,104.498,41.877,104.305,41.846,103.997,41.797,103.711,41.751,103.45,41.856,103.248,41.937,103.073,42.006,102.807,42.052,102.575,42.092,102.157,42.158,101.973,42.216,101.88,42.292,101.714,42.466,101.66,42.5,101.579,42.524,101.495,42.539,101.314,42.538,101.092,42.551,100.773,42.588,100.519,42.617,100.086,42.671,99.984,42.677,99.757,42.629,99.468,42.568,98.947,42.616,98.716,42.639,98.248,42.685,97.719,42.736,97.206,42.79,96.833,42.76,96.625,42.744,96.385,42.72,96.352,42.747,96.342,42.849,96.3,42.929,96.168,43.015,96.08,43.096,95.913,43.206,95.86,43.276,95.842,43.384,95.687,43.664,95.591,43.854,95.567,43.892,95.526,43.954,95.471,43.986,95.356,44.006,95.326,44.039,95.326,44.105,95.344,44.195,95.367,44.262,95.35,44.278,95.05,44.259,94.866,44.303,94.712,44.351,94.494,44.473,94.365,44.519,94.199,44.645,93.958,44.675,93.868,44.724,93.755,44.832,93.656,44.901,93.516,44.944,93.294,44.983,92.916,45.02,92.788,45.036,92.579,45.011,92.424,45.009,92.173,45.035,92.03,45.069,91.853,45.069,91.738,45.069,91.584,45.077,91.51,45.098,91.441,45.125,91.312,45.118,91.222,45.145,91.138,45.194,91.05,45.217,90.954,45.216,90.914,45.194,90.877,45.196,90.853,45.263,90.763,45.371,90.75,45.419,90.694,45.475,90.662,45.525,90.671,45.595,90.71,45.731,90.796,45.854,90.852,45.885,90.887,45.922,90.96,45.985,91.002,46.036,90.997,46.105,90.948,46.177,90.912,46.271,90.918,46.324,90.971,46.388,91.034,46.529,91.029,46.566,91.004,46.596,90.998,46.661,90.986,46.749,90.911,46.883,90.87,46.954,90.799,46.985,90.716,47.004,90.643,47.1,90.553,47.214,90.496,47.285,90.476,47.329,90.467,47.408,90.425,47.504,90.381,47.557,90.347,47.597,90.331,47.655,90.313,47.676,90.191,47.702,90.103,47.745,90.067,47.804,90.054,47.85,90.028,47.878,89.959,47.886,89.91,47.844,89.831,47.823,89.778,47.827,89.726,47.852,89.693,47.879,89.638,47.909,89.561,48.004,89.479,48.029,89.33,48.025,89.196,47.981,89.116,47.988,89.048,48.003,88.971,48.05,88.918,48.089,88.838,48.102,88.682,48.171,88.576,48.22,88.567,48.317,88.517,48.384,88.414,48.403,88.31,48.472,88.158,48.509,88.063,48.538,87.98,48.555,87.967,48.581,87.972,48.603,88.011,48.64,88.05,48.675,88.06,48.707,88.028,48.736,87.942,48.765,87.832,48.792,87.809,48.836,87.743,48.882,87.755,48.919,87.807,48.946,87.86,48.966,87.872,49,87.835,49.032,87.816,49.08,87.825,49.116,87.814,49.162]]]},{"n":"Moldova","c":"MD","r":6,"ml":5,"lx":28.49,"ly":47.43,"b":[26.619,45.45,30.131,48.478],"p":[[[26.619,48.26,26.64,48.294,26.847,48.387,26.901,48.372,27.008,48.368,27.229,48.371,27.337,48.433,27.404,48.416,27.458,48.443,27.549,48.478,27.562,48.47,27.574,48.465,27.714,48.45,27.82,48.416,27.891,48.365,27.963,48.334,28.038,48.321,28.08,48.296,28.088,48.257,28.159,48.238,28.291,48.239,28.347,48.213,28.327,48.161,28.341,48.144,28.388,48.162,28.423,48.147,28.442,48.109,28.463,48.091,28.53,48.15,28.602,48.144,28.774,48.12,28.866,47.996,28.923,47.951,28.973,47.933,29.037,47.952,29.093,47.975,29.125,47.965,29.195,47.882,29.211,47.775,29.211,47.732,29.186,47.659,29.151,47.581,29.123,47.53,29.135,47.49,29.16,47.456,29.201,47.444,29.334,47.376,29.383,47.328,29.456,47.293,29.511,47.291,29.539,47.271,29.549,47.247,29.542,47.186,29.511,47.128,29.515,47.091,29.563,47.048,29.569,46.997,29.572,46.964,29.598,46.939,29.72,46.883,29.878,46.829,29.918,46.782,29.942,46.724,29.935,46.625,29.924,46.539,30.131,46.423,30.108,46.402,30.076,46.378,29.878,46.36,29.838,46.351,29.752,46.438,29.707,46.449,29.665,46.417,29.615,46.399,29.555,46.408,29.491,46.435,29.459,46.454,29.433,46.456,29.393,46.437,29.34,46.445,29.305,46.467,29.255,46.393,29.224,46.377,29.205,46.379,29.201,46.505,29.186,46.524,29.146,46.527,29.05,46.497,28.958,46.458,28.927,46.424,28.931,46.362,28.944,46.288,29.006,46.176,28.972,46.128,28.948,46.05,28.85,45.979,28.739,45.937,28.729,45.852,28.668,45.794,28.562,45.736,28.492,45.666,28.509,45.618,28.514,45.572,28.502,45.542,28.499,45.518,28.471,45.507,28.31,45.499,28.265,45.484,28.213,45.45,28.163,45.514,28.112,45.569,28.075,45.599,28.09,45.613,28.131,45.628,28.16,45.647,28.156,45.713,28.135,45.789,28.116,45.826,28.114,45.883,28.1,45.973,28.119,46.139,28.2,46.348,28.244,46.451,28.223,46.508,28.239,46.641,28.205,46.706,28.15,46.792,28.072,46.978,27.974,47.043,27.854,47.115,27.802,47.168,27.768,47.228,27.696,47.286,27.614,47.341,27.516,47.476,27.465,47.537,27.449,47.553,27.337,47.64,27.278,47.718,27.248,47.782,27.231,47.842,27.152,47.959,27.08,48.048,27.012,48.11,26.981,48.155,26.901,48.211,26.787,48.256,26.714,48.263,26.619,48.26]]]},{"n":"Monaco","c":"MC","r":6,"ml":5,"lx":7.4,"ly":43.74,"b":[7.378,43.732,7.439,43.771],"p":[[[7.439,43.75,7.378,43.732,7.38,43.753,7.395,43.765,7.414,43.771,7.437,43.761,7.439,43.75]]]},{"n":"Mexico","c":"MX","r":2,"ml":2,"lx":-102.29,"ly":23.92,"b":[-118.401,14.545,-86.696,32.715],"p":[[[-117.128,32.533,-116.842,32.555,-116.556,32.576,-116.27,32.598,-115.984,32.619,-115.698,32.64,-115.411,32.662,-115.125,32.683,-114.839,32.705,-114.725,32.715,-114.788,32.565,-114.836,32.508,-114.362,32.36,-113.887,32.212,-113.413,32.064,-112.939,31.916,-112.465,31.768,-111.99,31.62,-111.516,31.472,-111.042,31.324,-110.689,31.325,-110.335,31.326,-109.982,31.326,-109.628,31.327,-109.275,31.327,-108.921,31.328,-108.568,31.329,-108.214,31.329,-108.214,31.442,-108.213,31.554,-108.212,31.667,-108.212,31.779,-107.992,31.778,-107.772,31.777,-107.552,31.776,-107.333,31.775,-107.113,31.774,-106.893,31.772,-106.673,31.771,-106.453,31.77,-106.445,31.768,-106.436,31.764,-106.347,31.679,-106.256,31.545,-106.148,31.451,-106.024,31.398,-105.813,31.241,-105.514,30.981,-105.276,30.807,-105.098,30.721,-104.979,30.646,-104.918,30.583,-104.836,30.448,-104.681,30.134,-104.681,29.991,-104.622,29.854,-104.504,29.678,-104.401,29.574,-104.312,29.542,-104.216,29.48,-104.111,29.386,-103.99,29.323,-103.853,29.291,-103.664,29.207,-103.423,29.071,-103.258,29.001,-103.168,28.998,-103.09,29.042,-103.023,29.132,-102.957,29.19,-102.892,29.216,-102.866,29.258,-102.878,29.315,-102.834,29.444,-102.734,29.644,-102.615,29.752,-102.476,29.769,-102.386,29.807,-102.343,29.865,-102.269,29.871,-102.163,29.825,-101.991,29.796,-101.752,29.782,-101.612,29.787,-101.569,29.809,-101.546,29.808,-101.545,29.784,-101.509,29.773,-101.44,29.777,-101.38,29.743,-101.304,29.634,-101.039,29.46,-101.016,29.401,-100.924,29.315,-100.755,29.183,-100.659,29.069,-100.636,28.973,-100.55,28.821,-100.399,28.614,-100.332,28.503,-100.348,28.486,-100.336,28.428,-100.296,28.328,-100.221,28.243,-100.112,28.173,-100.001,28.048,-99.89,27.867,-99.754,27.73,-99.595,27.636,-99.505,27.548,-99.484,27.467,-99.486,27.398,-99.51,27.34,-99.5,27.285,-99.455,27.234,-99.44,27.17,-99.458,27.082,-99.457,27.057,-99.456,27.057,-99.444,27.037,-99.302,26.885,-99.23,26.762,-99.172,26.566,-99.172,26.564,-99.108,26.447,-99.015,26.399,-98.873,26.381,-98.765,26.34,-98.691,26.276,-98.598,26.238,-98.486,26.225,-98.378,26.182,-98.275,26.111,-98.083,26.064,-97.801,26.042,-97.587,25.984,-97.44,25.891,-97.376,25.872,-97.358,25.871,-97.35,25.885,-97.339,25.911,-97.282,25.942,-97.146,25.961,-97.164,25.755,-97.225,25.585,-97.424,25.233,-97.507,25.015,-97.668,24.39,-97.717,23.981,-97.729,23.788,-97.743,23.761,-97.727,23.732,-97.766,23.306,-97.745,22.942,-97.758,22.886,-97.817,22.776,-97.858,22.625,-97.842,22.557,-97.842,22.51,-97.782,22.279,-97.763,22.106,-97.585,21.809,-97.485,21.705,-97.36,21.615,-97.315,21.564,-97.337,21.438,-97.388,21.374,-97.409,21.273,-97.434,21.356,-97.424,21.465,-97.385,21.524,-97.383,21.567,-97.457,21.612,-97.59,21.762,-97.754,22.027,-97.638,21.604,-97.598,21.536,-97.567,21.508,-97.515,21.478,-97.501,21.432,-97.501,21.398,-97.357,21.104,-97.195,20.8,-97.186,20.717,-97.121,20.615,-96.709,20.188,-96.456,19.87,-96.368,19.567,-96.315,19.473,-96.29,19.344,-96.124,19.199,-96.073,19.106,-95.985,19.054,-95.913,18.897,-95.778,18.806,-95.81,18.804,-95.928,18.85,-95.92,18.82,-95.821,18.755,-95.627,18.691,-95.578,18.69,-95.655,18.724,-95.72,18.768,-95.697,18.775,-95.561,18.719,-95.182,18.701,-95.015,18.571,-94.798,18.515,-94.682,18.348,-94.546,18.175,-94.46,18.167,-94.392,18.166,-94.189,18.195,-93.873,18.304,-93.764,18.358,-93.552,18.43,-93.228,18.444,-93.127,18.423,-92.885,18.469,-92.769,18.524,-92.729,18.575,-92.71,18.612,-92.485,18.665,-92.441,18.675,-92.213,18.685,-92.103,18.704,-91.974,18.716,-91.88,18.638,-91.88,18.6,-91.943,18.563,-91.914,18.529,-91.803,18.471,-91.6,18.447,-91.534,18.457,-91.44,18.542,-91.275,18.624,-91.279,18.721,-91.308,18.773,-91.356,18.777,-91.368,18.806,-91.334,18.877,-91.343,18.901,-91.446,18.833,-91.469,18.833,-91.458,18.865,-91.437,18.89,-91.136,19.038,-91.059,19.098,-90.955,19.152,-90.739,19.352,-90.693,19.73,-90.65,19.796,-90.507,19.912,-90.492,19.947,-90.482,20.026,-90.486,20.224,-90.478,20.38,-90.484,20.556,-90.458,20.714,-90.435,20.758,-90.353,21.009,-90.183,21.121,-89.888,21.253,-89.82,21.275,-88.879,21.414,-88.747,21.448,-88.585,21.539,-88.467,21.569,-88.251,21.567,-88.185,21.579,-88.172,21.591,-88.171,21.604,-88.132,21.616,-88.007,21.604,-87.774,21.55,-87.689,21.536,-87.48,21.472,-87.251,21.447,-87.218,21.458,-87.188,21.477,-87.164,21.514,-87.188,21.546,-87.211,21.544,-87.249,21.527,-87.296,21.525,-87.387,21.551,-87.369,21.574,-87.276,21.572,-87.216,21.582,-87.128,21.621,-87.035,21.592,-86.912,21.463,-86.824,21.422,-86.817,21.234,-86.804,21.2,-86.772,21.151,-86.816,21.005,-86.865,20.885,-86.926,20.786,-87.06,20.631,-87.221,20.507,-87.421,20.231,-87.467,20.102,-87.466,19.999,-87.432,19.898,-87.442,19.862,-87.466,19.824,-87.507,19.827,-87.586,19.779,-87.688,19.637,-87.69,19.594,-87.645,19.554,-87.587,19.573,-87.512,19.575,-87.469,19.586,-87.425,19.583,-87.435,19.502,-87.483,19.444,-87.513,19.426,-87.567,19.416,-87.628,19.383,-87.659,19.352,-87.656,19.258,-87.622,19.25,-87.551,19.321,-87.509,19.317,-87.501,19.288,-87.594,19.046,-87.653,18.799,-87.734,18.655,-87.762,18.446,-87.804,18.357,-87.853,18.269,-87.882,18.274,-87.96,18.441,-88.039,18.484,-88.056,18.524,-88.011,18.727,-88.032,18.839,-88.074,18.834,-88.127,18.773,-88.197,18.72,-88.195,18.643,-88.276,18.515,-88.296,18.472,-88.372,18.482,-88.461,18.477,-88.523,18.446,-88.586,18.291,-88.744,18.072,-88.806,17.966,-88.857,17.929,-88.898,17.915,-88.943,17.94,-89.05,18,-89.134,17.971,-89.162,17.902,-89.161,17.815,-89.372,17.815,-89.729,17.815,-90.184,17.816,-90.622,17.816,-90.989,17.816,-90.99,17.621,-90.992,17.447,-90.993,17.252,-91.196,17.254,-91.41,17.256,-91.392,17.236,-91.319,17.2,-91.224,17.112,-91.112,16.976,-90.976,16.868,-90.816,16.787,-90.711,16.708,-90.66,16.631,-90.634,16.565,-90.634,16.511,-90.576,16.468,-90.471,16.44,-90.417,16.391,-90.417,16.351,-90.45,16.261,-90.46,16.162,-90.447,16.073,-90.522,16.071,-90.703,16.071,-90.98,16.071,-91.234,16.071,-91.434,16.07,-91.737,16.07,-91.819,15.932,-91.957,15.703,-92.082,15.496,-92.187,15.321,-92.204,15.275,-92.204,15.238,-92.075,15.074,-92.099,15.027,-92.144,15.002,-92.159,14.964,-92.156,14.901,-92.186,14.818,-92.176,14.761,-92.16,14.691,-92.187,14.63,-92.209,14.571,-92.235,14.545,-92.265,14.568,-92.531,14.84,-92.809,15.139,-92.918,15.236,-93.024,15.31,-93.167,15.448,-93.541,15.75,-93.734,15.888,-93.916,16.054,-94.079,16.145,-94.24,16.205,-94.311,16.239,-94.374,16.285,-94.409,16.287,-94.426,16.226,-94.37,16.195,-94.303,16.169,-94.25,16.168,-94.193,16.146,-94.028,16.062,-94.001,16.019,-94.471,16.187,-94.662,16.202,-94.682,16.228,-94.587,16.316,-94.617,16.348,-94.651,16.352,-94.753,16.291,-94.791,16.287,-94.797,16.327,-94.793,16.365,-94.859,16.42,-94.9,16.417,-94.935,16.379,-95.024,16.306,-95.021,16.278,-94.846,16.247,-94.786,16.229,-94.799,16.21,-94.949,16.21,-95.134,16.177,-95.464,15.975,-95.772,15.888,-96.214,15.693,-96.409,15.683,-96.511,15.652,-96.808,15.726,-97.185,15.909,-97.755,15.967,-98.139,16.206,-98.52,16.305,-98.762,16.535,-98.908,16.545,-99.002,16.581,-99.348,16.665,-99.691,16.72,-100.025,16.921,-100.243,16.984,-100.432,17.064,-100.848,17.2,-101.002,17.276,-101.148,17.393,-101.385,17.514,-101.487,17.615,-101.6,17.652,-101.762,17.842,-101.847,17.922,-101.919,17.96,-101.996,17.973,-102.217,17.957,-102.547,18.041,-102.7,18.063,-103.019,18.187,-103.442,18.325,-103.58,18.484,-103.699,18.633,-103.912,18.828,-104.046,18.912,-104.277,19.011,-104.405,19.091,-104.603,19.153,-104.938,19.309,-105.045,19.443,-105.108,19.562,-105.286,19.706,-105.482,19.976,-105.532,20.075,-105.57,20.228,-105.616,20.316,-105.669,20.386,-105.642,20.436,-105.543,20.498,-105.377,20.512,-105.26,20.579,-105.245,20.634,-105.252,20.669,-105.327,20.753,-105.42,20.775,-105.492,20.777,-105.511,20.809,-105.456,20.844,-105.394,20.926,-105.302,21.027,-105.237,21.119,-105.225,21.25,-105.233,21.38,-105.209,21.491,-105.431,21.618,-105.457,21.672,-105.527,21.818,-105.649,21.988,-105.646,22.327,-105.792,22.627,-105.943,22.777,-106.022,22.829,-106.235,23.061,-106.402,23.196,-106.567,23.449,-106.729,23.611,-106.935,23.881,-107.085,24.016,-107.765,24.472,-107.727,24.472,-107.527,24.36,-107.494,24.369,-107.489,24.424,-107.512,24.489,-107.549,24.505,-107.602,24.49,-107.674,24.504,-107.71,24.525,-107.817,24.539,-107.951,24.615,-108.009,24.694,-108.015,24.783,-108.208,24.975,-108.281,25.082,-108.243,25.074,-108.192,25.031,-108.14,25.018,-108.08,25.018,-108.036,25.035,-108.051,25.067,-108.093,25.094,-108.374,25.194,-108.466,25.265,-108.696,25.383,-108.751,25.424,-108.787,25.538,-108.844,25.543,-108.893,25.512,-109.029,25.48,-109.063,25.517,-109.068,25.552,-108.973,25.588,-108.885,25.696,-108.887,25.733,-108.935,25.69,-109.008,25.642,-109.084,25.615,-109.196,25.593,-109.254,25.609,-109.304,25.633,-109.385,25.727,-109.426,26.033,-109.354,26.138,-109.271,26.243,-109.2,26.305,-109.159,26.258,-109.117,26.253,-109.146,26.306,-109.216,26.355,-109.241,26.405,-109.243,26.45,-109.276,26.534,-109.483,26.71,-109.676,26.697,-109.755,26.703,-109.828,26.77,-109.891,26.883,-109.922,26.978,-109.926,27.029,-109.944,27.079,-110.277,27.162,-110.377,27.233,-110.478,27.323,-110.519,27.396,-110.561,27.45,-110.593,27.544,-110.615,27.654,-110.578,27.796,-110.53,27.864,-110.759,27.915,-110.849,27.918,-110.921,27.889,-110.986,27.926,-111.121,27.967,-111.282,28.115,-111.472,28.384,-111.68,28.471,-111.747,28.564,-111.832,28.648,-111.907,28.752,-111.919,28.798,-111.941,28.823,-112.045,28.896,-112.162,29.019,-112.192,29.118,-112.223,29.269,-112.301,29.323,-112.378,29.348,-112.393,29.42,-112.389,29.46,-112.415,29.536,-112.573,29.72,-112.653,29.87,-112.697,29.917,-112.738,29.985,-112.759,30.126,-112.825,30.3,-112.952,30.51,-113.058,30.651,-113.11,30.793,-113.087,30.938,-113.105,31.027,-113.119,31.048,-113.108,31.077,-113.073,31.061,-113.043,31.087,-113.047,31.179,-113.084,31.207,-113.186,31.236,-113.231,31.256,-113.481,31.294,-113.623,31.346,-113.633,31.468,-113.7,31.523,-113.759,31.558,-113.948,31.629,-113.977,31.593,-114.003,31.525,-114.081,31.51,-114.149,31.507,-114.264,31.554,-114.549,31.734,-114.609,31.762,-114.698,31.777,-114.741,31.806,-114.934,31.901,-114.895,31.851,-114.84,31.799,-114.79,31.647,-114.848,31.538,-114.882,31.156,-114.845,31.08,-114.761,30.959,-114.703,30.765,-114.685,30.621,-114.633,30.507,-114.65,30.238,-114.63,30.156,-114.55,30.022,-114.403,29.896,-114.373,29.83,-114.179,29.734,-114.062,29.61,-113.829,29.439,-113.755,29.367,-113.545,29.102,-113.538,29.023,-113.5,28.927,-113.382,28.947,-113.329,28.873,-113.335,28.839,-113.321,28.813,-113.259,28.819,-113.206,28.799,-113.094,28.512,-113.034,28.473,-112.957,28.456,-112.871,28.424,-112.865,28.351,-112.868,28.292,-112.796,28.207,-112.808,28.092,-112.749,27.995,-112.758,27.901,-112.734,27.826,-112.553,27.657,-112.329,27.523,-112.283,27.347,-112.191,27.187,-112.098,27.146,-112.004,27.079,-112.016,27.01,-112.009,26.967,-111.883,26.84,-111.863,26.679,-111.754,26.573,-111.723,26.564,-111.699,26.581,-111.779,26.687,-111.817,26.756,-111.822,26.865,-111.795,26.88,-111.57,26.708,-111.546,26.579,-111.47,26.507,-111.465,26.408,-111.419,26.35,-111.405,26.265,-111.332,26.125,-111.33,25.931,-111.292,25.79,-111.15,25.573,-111.034,25.527,-111.014,25.42,-110.894,25.144,-110.756,24.995,-110.687,24.868,-110.677,24.789,-110.729,24.672,-110.735,24.59,-110.659,24.341,-110.547,24.214,-110.421,24.183,-110.4,24.165,-110.41,24.131,-110.367,24.1,-110.32,24.139,-110.297,24.195,-110.321,24.259,-110.325,24.306,-110.304,24.339,-110.263,24.345,-110.023,24.175,-109.983,24.109,-109.893,24.033,-109.811,23.939,-109.776,23.865,-109.711,23.804,-109.677,23.662,-109.51,23.598,-109.421,23.48,-109.415,23.406,-109.458,23.215,-109.496,23.16,-109.63,23.079,-109.728,22.982,-109.823,22.922,-109.923,22.886,-110.006,22.894,-110.086,23.005,-110.181,23.342,-110.244,23.412,-110.289,23.518,-110.363,23.605,-110.63,23.737,-110.765,23.877,-110.896,23.97,-111.036,24.105,-111.419,24.329,-111.578,24.443,-111.683,24.556,-111.75,24.554,-111.802,24.543,-111.822,24.573,-111.825,24.632,-111.848,24.67,-112.073,24.84,-112.119,24.934,-112.129,25.043,-112.078,25.324,-112.056,25.488,-112.07,25.573,-112.093,25.584,-112.115,25.63,-112.12,25.766,-112.174,25.913,-112.377,26.214,-112.526,26.273,-112.658,26.317,-113.021,26.583,-113.119,26.717,-113.143,26.792,-113.156,26.946,-113.206,26.857,-113.272,26.791,-113.426,26.796,-113.599,26.721,-113.701,26.791,-113.757,26.871,-113.841,26.967,-113.936,26.985,-113.996,26.988,-114.11,27.106,-114.202,27.144,-114.333,27.158,-114.445,27.218,-114.48,27.284,-114.498,27.376,-114.54,27.431,-114.716,27.54,-114.859,27.659,-114.994,27.736,-115.033,27.799,-115.036,27.842,-114.824,27.83,-114.57,27.784,-114.448,27.797,-114.373,27.841,-114.301,27.873,-114.289,27.839,-114.302,27.776,-114.233,27.718,-114.137,27.671,-114.069,27.676,-114.135,27.727,-114.175,27.831,-114.157,27.868,-114.158,27.92,-114.253,27.908,-114.266,27.934,-114.185,28.013,-114.093,28.221,-114.048,28.426,-114.146,28.605,-114.309,28.73,-114.664,29.095,-114.876,29.282,-114.937,29.352,-114.994,29.384,-115.166,29.427,-115.311,29.532,-115.565,29.68,-115.674,29.756,-115.749,29.936,-115.808,29.96,-115.79,30.084,-115.816,30.304,-115.858,30.36,-115.996,30.414,-116.029,30.564,-116.035,30.705,-116.062,30.804,-116.296,30.971,-116.31,31.051,-116.31,31.127,-116.333,31.203,-116.458,31.361,-116.61,31.499,-116.662,31.565,-116.668,31.699,-116.722,31.735,-116.702,31.744,-116.652,31.74,-116.624,31.758,-116.621,31.851,-116.848,31.997,-116.914,32.199,-117.035,32.305,-117.063,32.344,-117.128,32.533]],[[-86.94,20.303,-86.991,20.272,-87.019,20.382,-86.978,20.49,-86.928,20.552,-86.829,20.559,-86.763,20.579,-86.755,20.552,-86.809,20.468,-86.94,20.303]],[[-106.502,21.611,-106.531,21.529,-106.607,21.561,-106.634,21.613,-106.639,21.698,-106.597,21.712,-106.536,21.676,-106.524,21.652,-106.502,21.611]],[[-110.914,18.741,-110.975,18.72,-111.064,18.782,-111.04,18.83,-110.989,18.863,-110.942,18.802,-110.914,18.741]],[[-110.567,25.003,-110.539,24.892,-110.59,24.908,-110.657,24.969,-110.703,25.047,-110.699,25.081,-110.69,25.088,-110.595,25.042,-110.567,25.003]],[[-113.156,29.052,-113.163,29.035,-113.265,29.097,-113.496,29.308,-113.581,29.413,-113.594,29.463,-113.587,29.573,-113.508,29.56,-113.416,29.486,-113.376,29.417,-113.374,29.339,-113.202,29.302,-113.178,29.132,-113.156,29.052]],[[-115.171,28.069,-115.184,28.037,-115.353,28.104,-115.26,28.221,-115.274,28.343,-115.234,28.368,-115.197,28.328,-115.149,28.172,-115.171,28.069]],[[-112.203,29.005,-112.278,28.769,-112.355,28.773,-112.514,28.848,-112.531,28.894,-112.47,29.168,-112.424,29.204,-112.285,29.24,-112.263,29.207,-112.249,29.126,-112.203,29.005]],[[-118.243,28.942,-118.285,28.904,-118.4,29.112,-118.401,29.163,-118.368,29.188,-118.312,29.183,-118.312,29.131,-118.266,29.086,-118.247,29.043,-118.243,28.942]],[[-86.714,21.239,-86.696,21.191,-86.714,21.197,-86.736,21.233,-86.753,21.279,-86.739,21.28,-86.727,21.264,-86.714,21.239]],[[-91.684,18.677,-91.796,18.654,-91.816,18.676,-91.589,18.778,-91.55,18.774,-91.537,18.76,-91.654,18.711,-91.684,18.677]],[[-109.805,24.151,-109.827,24.148,-109.878,24.201,-109.9,24.331,-109.89,24.345,-109.794,24.183,-109.796,24.164,-109.805,24.151]],[[-114.694,31.706,-114.727,31.701,-114.789,31.747,-114.785,31.79,-114.771,31.794,-114.709,31.757,-114.688,31.724,-114.694,31.706]],[[-111.1,26.021,-111.088,25.985,-111.094,25.974,-111.135,25.999,-111.204,25.85,-111.225,25.836,-111.183,26.041,-111.139,26.07,-111.091,26.076,-111.1,26.021]],[[-111.699,24.394,-111.712,24.346,-112.013,24.533,-111.941,24.551,-111.857,24.538,-111.699,24.394]],[[-112.057,24.546,-112.077,24.535,-112.163,24.65,-112.175,24.73,-112.21,24.763,-112.297,24.79,-112.222,24.951,-112.159,25.286,-112.132,25.224,-112.198,24.885,-112.195,24.841,-112.164,24.8,-112.13,24.73,-112.126,24.654,-112.067,24.584,-112.057,24.546]]]},{"n":"Mauritius","c":"MU","r":5,"ml":4,"lx":57.57,"ly":-20.3,"b":[57.318,-20.513,57.792,-19.99],"p":[[[57.651,-20.485,57.525,-20.513,57.383,-20.504,57.328,-20.45,57.318,-20.428,57.365,-20.406,57.362,-20.338,57.386,-20.229,57.416,-20.184,57.486,-20.144,57.515,-20.056,57.576,-19.997,57.657,-19.99,57.737,-20.098,57.792,-20.213,57.781,-20.327,57.725,-20.369,57.707,-20.435,57.651,-20.485]]]},{"n":"Mauritania","c":"MR","r":3,"ml":3,"lx":-9.74,"ly":19.59,"b":[-17.064,14.745,-4.823,27.286],"p":[[[-16.373,19.706,-16.438,19.609,-16.466,19.646,-16.477,19.71,-16.42,19.802,-16.393,19.849,-16.344,19.866,-16.373,19.706]],[[-12.281,14.809,-12.303,14.817,-12.409,14.889,-12.46,14.975,-12.544,15.039,-12.66,15.082,-12.735,15.131,-12.77,15.187,-12.813,15.224,-12.858,15.243,-12.863,15.262,-12.852,15.29,-12.863,15.34,-12.931,15.453,-12.994,15.505,-13.049,15.497,-13.079,15.51,-13.098,15.535,-13.105,15.572,-13.142,15.603,-13.206,15.617,-13.258,15.7,-13.297,15.854,-13.348,15.973,-13.41,16.059,-13.454,16.091,-13.487,16.097,-13.498,16.11,-13.507,16.135,-13.556,16.144,-13.624,16.118,-13.685,16.127,-13.715,16.169,-13.757,16.173,-13.81,16.138,-13.868,16.148,-13.933,16.203,-13.968,16.257,-13.975,16.311,-14.086,16.419,-14.3,16.58,-14.534,16.656,-14.787,16.646,-14.929,16.654,-14.96,16.679,-14.991,16.677,-15.022,16.647,-15.055,16.641,-15.091,16.657,-15.113,16.645,-15.121,16.604,-15.211,16.583,-15.38,16.582,-15.517,16.557,-15.621,16.507,-15.768,16.485,-15.959,16.492,-16.074,16.51,-16.113,16.54,-16.168,16.547,-16.239,16.531,-16.302,16.451,-16.358,16.307,-16.404,16.225,-16.441,16.205,-16.48,16.097,-16.502,15.917,-16.535,15.838,-16.536,16.287,-16.481,16.454,-16.464,16.602,-16.347,16.926,-16.207,17.193,-16.079,17.546,-16.03,17.888,-16.047,18.223,-16.085,18.521,-16.15,18.718,-16.213,19.003,-16.306,19.154,-16.476,19.285,-16.514,19.362,-16.475,19.391,-16.371,19.41,-16.305,19.513,-16.445,19.473,-16.283,19.787,-16.233,20.001,-16.241,20.141,-16.21,20.228,-16.334,20.416,-16.43,20.652,-16.479,20.69,-16.53,20.71,-16.535,20.654,-16.563,20.604,-16.623,20.634,-16.728,20.806,-16.876,21.086,-16.928,21.115,-16.971,21.076,-16.998,21.04,-17.048,20.806,-17.064,20.899,-17.042,21.008,-17.006,21.142,-16.965,21.329,-16.836,21.329,-16.607,21.33,-16.378,21.33,-16.148,21.33,-15.919,21.33,-15.69,21.331,-15.461,21.331,-15.231,21.331,-15.002,21.332,-14.773,21.332,-14.543,21.332,-14.314,21.332,-14.085,21.333,-13.855,21.333,-13.626,21.333,-13.397,21.334,-13.167,21.334,-13.016,21.334,-13.025,21.467,-13.032,21.572,-13.042,21.714,-13.051,21.855,-13.061,21.996,-13.07,22.128,-13.078,22.26,-13.087,22.383,-13.094,22.496,-13.107,22.561,-13.156,22.689,-13.167,22.753,-13.153,22.821,-13.121,22.884,-13.031,23,-12.896,23.09,-12.74,23.193,-12.62,23.271,-12.559,23.291,-12.373,23.318,-12.226,23.377,-12.083,23.435,-12.023,23.468,-12.016,23.576,-12.016,23.698,-12.016,23.834,-12.016,23.97,-12.016,24.106,-12.016,24.242,-12.016,24.379,-12.016,24.515,-12.016,24.651,-12.016,24.787,-12.016,24.923,-12.016,25.059,-12.016,25.196,-12.016,25.332,-12.016,25.468,-12.016,25.604,-12.016,25.74,-12.016,25.876,-12.016,25.995,-11.867,25.995,-11.68,25.995,-11.494,25.995,-11.308,25.995,-11.121,25.995,-10.935,25.995,-10.749,25.995,-10.562,25.995,-10.376,25.995,-10.19,25.995,-10.004,25.995,-9.817,25.995,-9.631,25.996,-9.445,25.996,-9.258,25.996,-9.072,25.996,-8.886,25.996,-8.682,25.996,-8.682,26.109,-8.682,26.273,-8.682,26.498,-8.683,26.723,-8.683,26.921,-8.683,27.119,-8.683,27.286,-8.495,27.175,-8.307,27.065,-8.119,26.954,-7.931,26.844,-7.743,26.733,-7.555,26.622,-7.367,26.512,-7.179,26.401,-6.991,26.291,-6.803,26.18,-6.615,26.069,-6.427,25.959,-6.239,25.848,-6.051,25.738,-5.863,25.627,-5.675,25.516,-5.517,25.424,-5.275,25.275,-5.05,25.135,-4.823,24.996,-5.173,24.995,-5.641,24.995,-5.96,24.995,-6.287,24.995,-6.594,24.995,-6.567,24.767,-6.539,24.518,-6.51,24.269,-6.482,24.021,-6.454,23.772,-6.425,23.523,-6.397,23.275,-6.368,23.026,-6.34,22.777,-6.311,22.529,-6.283,22.28,-6.254,22.032,-6.226,21.783,-6.197,21.534,-6.169,21.286,-6.14,21.037,-6.112,20.788,-6.083,20.54,-6.055,20.291,-6.026,20.042,-5.998,19.794,-5.969,19.545,-5.941,19.296,-5.912,19.048,-5.884,18.799,-5.856,18.55,-5.827,18.302,-5.799,18.053,-5.77,17.804,-5.742,17.556,-5.713,17.307,-5.685,17.058,-5.656,16.81,-5.629,16.569,-5.51,16.442,-5.36,16.283,-5.404,16.058,-5.456,15.789,-5.512,15.496,-5.724,15.496,-5.928,15.496,-6.132,15.496,-6.336,15.496,-6.54,15.496,-6.744,15.496,-6.948,15.496,-7.152,15.496,-7.355,15.496,-7.559,15.496,-7.763,15.496,-7.967,15.496,-8.171,15.496,-8.375,15.496,-8.579,15.496,-8.783,15.496,-8.987,15.496,-9.177,15.496,-9.294,15.503,-9.335,15.526,-9.351,15.677,-9.385,15.668,-9.427,15.623,-9.448,15.575,-9.44,15.512,-9.447,15.458,-9.578,15.437,-9.755,15.401,-9.941,15.374,-10.13,15.384,-10.194,15.396,-10.262,15.416,-10.412,15.438,-10.493,15.44,-10.587,15.435,-10.697,15.423,-10.732,15.395,-10.815,15.282,-10.896,15.15,-10.948,15.151,-11.007,15.223,-11.169,15.359,-11.366,15.537,-11.455,15.625,-11.503,15.637,-11.597,15.573,-11.676,15.512,-11.76,15.426,-11.798,15.343,-11.829,15.245,-11.842,15.129,-11.873,14.995,-11.941,14.887,-12.022,14.805,-12.082,14.766,-12.105,14.745,-12.281,14.809]]]},{"n":"Malta","c":"MT","r":5,"ml":4,"lx":14.43,"ly":35.89,"b":[14.18,35.82,14.566,36.076],"p":[[[14.566,35.853,14.533,35.82,14.436,35.822,14.352,35.872,14.351,35.978,14.448,35.957,14.537,35.886,14.566,35.853]],[[14.313,36.028,14.254,36.012,14.194,36.042,14.18,36.06,14.263,36.076,14.304,36.062,14.321,36.036,14.313,36.028]]]},{"n":"Mali","c":"ML","r":3,"ml":3,"lx":-2.04,"ly":18.69,"b":[-12.281,10.143,4.235,24.996],"p":[[[-11.389,12.404,-11.382,12.479,-11.449,12.532,-11.451,12.558,-11.444,12.628,-11.414,12.775,-11.417,12.832,-11.39,12.942,-11.434,12.992,-11.444,13.028,-11.493,13.087,-11.549,13.17,-11.562,13.237,-11.581,13.29,-11.635,13.37,-11.674,13.382,-11.758,13.395,-11.772,13.367,-11.803,13.327,-11.832,13.316,-11.878,13.365,-11.895,13.406,-11.895,13.444,-11.957,13.511,-12.054,13.633,-12.044,13.734,-11.984,13.788,-11.966,13.829,-11.961,13.875,-11.988,13.931,-12.02,13.975,-12.011,14.072,-12.019,14.206,-12.068,14.274,-12.113,14.323,-12.175,14.377,-12.228,14.459,-12.207,14.571,-12.187,14.648,-12.281,14.809,-12.105,14.745,-12.082,14.766,-12.022,14.805,-11.941,14.887,-11.873,14.995,-11.842,15.129,-11.829,15.245,-11.798,15.343,-11.76,15.426,-11.676,15.512,-11.597,15.573,-11.503,15.637,-11.455,15.625,-11.366,15.537,-11.169,15.359,-11.007,15.223,-10.948,15.151,-10.896,15.15,-10.815,15.282,-10.732,15.395,-10.697,15.423,-10.587,15.435,-10.493,15.44,-10.412,15.438,-10.262,15.416,-10.194,15.396,-10.13,15.384,-9.941,15.374,-9.755,15.401,-9.578,15.437,-9.447,15.458,-9.44,15.512,-9.448,15.575,-9.427,15.623,-9.385,15.668,-9.351,15.677,-9.335,15.526,-9.294,15.503,-9.177,15.496,-8.987,15.496,-8.783,15.496,-8.579,15.496,-8.375,15.496,-8.171,15.496,-7.967,15.496,-7.763,15.496,-7.559,15.496,-7.355,15.496,-7.152,15.496,-6.948,15.496,-6.744,15.496,-6.54,15.496,-6.336,15.496,-6.132,15.496,-5.928,15.496,-5.724,15.496,-5.512,15.496,-5.456,15.789,-5.404,16.058,-5.36,16.283,-5.51,16.442,-5.629,16.569,-5.656,16.81,-5.685,17.058,-5.713,17.307,-5.742,17.556,-5.77,17.804,-5.799,18.053,-5.827,18.302,-5.856,18.55,-5.884,18.799,-5.912,19.048,-5.941,19.296,-5.969,19.545,-5.998,19.794,-6.026,20.042,-6.055,20.291,-6.083,20.54,-6.112,20.788,-6.14,21.037,-6.169,21.286,-6.197,21.534,-6.226,21.783,-6.254,22.032,-6.283,22.28,-6.311,22.529,-6.34,22.777,-6.368,23.026,-6.397,23.275,-6.425,23.523,-6.454,23.772,-6.482,24.021,-6.51,24.269,-6.539,24.518,-6.567,24.767,-6.594,24.995,-6.287,24.995,-5.96,24.995,-5.641,24.995,-5.173,24.995,-4.823,24.996,-4.517,24.804,-4.24,24.624,-3.913,24.409,-3.585,24.195,-3.258,23.981,-2.93,23.767,-2.603,23.553,-2.275,23.339,-1.948,23.125,-1.62,22.911,-1.293,22.697,-0.965,22.482,-0.638,22.268,-0.311,22.054,0.017,21.84,0.344,21.626,0.672,21.412,0.999,21.198,1.146,21.102,1.159,21.063,1.173,20.982,1.164,20.891,1.166,20.817,1.209,20.767,1.29,20.714,1.611,20.556,1.636,20.524,1.647,20.459,1.685,20.378,1.753,20.332,1.832,20.297,1.929,20.273,2.219,20.248,2.281,20.21,2.406,20.064,2.474,20.035,2.668,19.993,2.808,19.969,2.866,19.956,2.992,19.917,3.13,19.85,3.204,19.79,3.203,19.771,3.203,19.718,3.202,19.56,3.227,19.474,3.256,19.411,3.254,19.373,3.22,19.345,3.192,19.312,3.177,19.268,3.138,19.212,3.106,19.15,3.12,19.103,3.174,19.073,3.256,19.013,3.323,18.988,3.356,18.987,3.401,18.988,3.439,18.996,3.683,19.042,3.91,19.084,4.228,19.143,4.228,18.968,4.229,18.704,4.23,18.411,4.231,18.139,4.232,17.831,4.233,17.582,4.234,17.288,4.235,16.996,4.203,16.963,4.191,16.798,4.182,16.582,4.121,16.358,4.015,16.193,3.976,16.036,3.947,15.946,3.907,15.897,3.898,15.838,3.877,15.755,3.843,15.702,3.817,15.674,3.71,15.642,3.521,15.483,3.504,15.356,3.289,15.391,3.06,15.427,3.029,15.425,3.011,15.408,3.001,15.341,2.69,15.33,2.421,15.32,2.088,15.309,1.859,15.302,1.569,15.286,1.3,15.272,1.121,15.126,0.96,14.987,0.947,14.982,0.719,14.955,0.433,14.979,0.286,14.98,0.229,14.964,0.217,14.911,0.007,14.985,-0.236,15.059,-0.405,15.013,-0.432,15.029,-0.454,15.06,-0.537,15.078,-0.666,15.07,-0.76,15.048,-0.908,14.937,-1.019,14.841,-1.05,14.82,-1.205,14.762,-1.494,14.626,-1.657,14.527,-1.695,14.508,-1.768,14.486,-1.88,14.481,-1.973,14.457,-2.057,14.195,-2.113,14.168,-2.457,14.274,-2.527,14.258,-2.587,14.228,-2.779,14.074,-2.874,13.951,-2.926,13.787,-2.919,13.736,-2.917,13.679,-2.951,13.648,-2.997,13.637,-3.039,13.639,-3.198,13.673,-3.249,13.658,-3.27,13.577,-3.267,13.401,-3.302,13.281,-3.397,13.244,-3.47,13.196,-3.528,13.183,-3.576,13.194,-3.853,13.374,-3.947,13.402,-4.051,13.382,-4.151,13.306,-4.196,13.256,-4.259,13.197,-4.329,13.119,-4.31,13.052,-4.261,12.975,-4.225,12.879,-4.227,12.794,-4.481,12.672,-4.46,12.63,-4.422,12.582,-4.422,12.493,-4.429,12.338,-4.48,12.282,-4.546,12.226,-4.587,12.155,-4.627,12.12,-4.699,12.076,-4.798,12.032,-4.969,11.993,-5.106,11.968,-5.158,11.942,-5.23,11.89,-5.288,11.828,-5.302,11.76,-5.291,11.683,-5.27,11.62,-5.245,11.577,-5.229,11.522,-5.25,11.376,-5.3,11.206,-5.347,11.13,-5.424,11.089,-5.49,11.042,-5.469,10.931,-5.457,10.771,-5.476,10.644,-5.479,10.565,-5.507,10.483,-5.524,10.426,-5.557,10.44,-5.694,10.433,-5.844,10.39,-5.896,10.355,-5.908,10.307,-5.941,10.275,-5.989,10.239,-6.035,10.195,-6.117,10.202,-6.197,10.232,-6.238,10.262,-6.241,10.279,-6.215,10.322,-6.193,10.369,-6.191,10.4,-6.218,10.476,-6.24,10.558,-6.231,10.598,-6.25,10.718,-6.261,10.724,-6.366,10.693,-6.404,10.685,-6.426,10.672,-6.433,10.649,-6.408,10.572,-6.424,10.559,-6.483,10.561,-6.565,10.586,-6.654,10.656,-6.676,10.634,-6.686,10.578,-6.692,10.512,-6.669,10.392,-6.693,10.349,-6.753,10.357,-6.834,10.357,-6.904,10.345,-6.95,10.342,-6.979,10.3,-6.992,10.252,-6.964,10.199,-6.968,10.176,-6.989,10.156,-7.017,10.143,-7.04,10.145,-7.105,10.204,-7.182,10.226,-7.363,10.259,-7.385,10.34,-7.415,10.341,-7.457,10.384,-7.498,10.44,-7.533,10.437,-7.562,10.421,-7.661,10.427,-7.749,10.342,-7.814,10.237,-7.884,10.186,-7.961,10.163,-7.991,10.163,-7.974,10.23,-7.986,10.278,-8.007,10.322,-8.231,10.438,-8.267,10.486,-8.302,10.618,-8.324,10.75,-8.322,10.827,-8.306,10.896,-8.313,10.95,-8.337,10.991,-8.404,11.03,-8.475,11.048,-8.564,10.997,-8.606,10.987,-8.646,10.99,-8.667,11.009,-8.664,11.036,-8.567,11.177,-8.52,11.236,-8.464,11.281,-8.425,11.305,-8.401,11.339,-8.399,11.367,-8.407,11.386,-8.471,11.412,-8.569,11.478,-8.621,11.485,-8.665,11.515,-8.711,11.618,-8.733,11.638,-8.78,11.648,-8.822,11.673,-8.82,11.807,-8.818,11.923,-8.914,12.109,-8.951,12.226,-8.999,12.346,-9.043,12.402,-9.12,12.45,-9.216,12.483,-9.3,12.49,-9.365,12.479,-9.395,12.465,-9.394,12.442,-9.341,12.366,-9.332,12.324,-9.34,12.283,-9.358,12.255,-9.405,12.252,-9.487,12.229,-9.588,12.182,-9.658,12.143,-9.715,12.042,-9.754,12.03,-9.821,12.042,-10.011,12.116,-10.167,12.177,-10.275,12.213,-10.34,12.19,-10.373,12.18,-10.466,12.139,-10.59,11.99,-10.619,11.941,-10.644,11.926,-10.677,11.899,-10.709,11.899,-10.735,11.916,-10.743,11.927,-10.806,12.034,-10.876,12.152,-10.933,12.205,-11.005,12.208,-11.066,12.171,-11.129,12.095,-11.21,12.025,-11.261,12.004,-11.305,12.015,-11.415,12.104,-11.492,12.167,-11.502,12.199,-11.475,12.247,-11.448,12.319,-11.418,12.378,-11.389,12.404]]]},{"n":"Maldives","c":"MV","r":5,"ml":4,"lx":73.51,"ly":4.17,"b":[73.382,3.229,73.528,4.248],"p":[[[73.417,3.231,73.395,3.229,73.382,3.246,73.385,3.271,73.402,3.289,73.428,3.29,73.443,3.274,73.435,3.25,73.417,3.231]],[[73.512,4.165,73.495,4.155,73.479,4.159,73.473,4.171,73.481,4.188,73.495,4.21,73.504,4.235,73.518,4.248,73.528,4.243,73.527,4.23,73.522,4.211,73.519,4.187,73.512,4.165]]]},{"n":"Malaysia","c":"MY","r":3,"ml":3,"lx":113.84,"ly":2.53,"b":[99.646,0.862,119.266,7.352],"p":[[[100.119,6.442,100.138,6.489,100.161,6.642,100.177,6.672,100.217,6.687,100.261,6.683,100.345,6.55,100.564,6.468,100.629,6.448,100.716,6.481,100.754,6.46,100.794,6.426,100.817,6.332,100.874,6.245,100.989,6.258,101.029,6.245,101.054,6.243,101.076,6.166,101.087,6.034,101.076,5.956,100.993,5.846,100.982,5.771,101.025,5.725,101.082,5.675,101.114,5.637,101.148,5.643,101.191,5.669,101.23,5.734,101.257,5.789,101.404,5.852,101.556,5.908,101.577,5.902,101.601,5.877,101.65,5.796,101.678,5.779,101.72,5.771,101.791,5.779,101.874,5.825,101.917,5.911,101.936,5.979,102.055,6.097,102.068,6.185,102.101,6.242,102.274,6.203,102.34,6.172,102.534,5.863,102.79,5.645,102.899,5.564,102.982,5.525,103.097,5.408,103.197,5.262,103.416,4.85,103.454,4.669,103.469,4.393,103.421,3.977,103.362,3.769,103.373,3.671,103.454,3.521,103.429,3.379,103.445,3.261,103.439,2.933,103.485,2.837,103.537,2.775,103.812,2.58,103.832,2.508,103.968,2.261,104.219,1.723,104.288,1.481,104.28,1.416,104.25,1.389,104.176,1.365,104.115,1.412,104.094,1.446,104.101,1.488,104.076,1.53,104.016,1.579,103.981,1.624,103.991,1.55,103.992,1.455,103.915,1.447,103.817,1.477,103.695,1.45,103.55,1.333,103.48,1.329,103.427,1.43,103.4,1.498,103.357,1.546,102.897,1.792,102.727,1.856,102.548,2.042,102.146,2.248,101.89,2.449,101.781,2.574,101.52,2.684,101.407,2.813,101.351,2.839,101.296,2.885,101.354,3.011,101.33,3.142,101.3,3.253,101.115,3.472,101.025,3.625,100.851,3.777,100.782,3.864,100.715,3.966,100.757,4.002,100.796,4.023,100.76,4.097,100.661,4.226,100.615,4.373,100.615,4.652,100.473,5.044,100.353,5.588,100.374,5.778,100.343,5.984,100.263,6.183,100.158,6.324,100.119,6.442]],[[117.574,4.171,117.537,4.171,117.451,4.193,117.278,4.299,117.101,4.337,116.844,4.34,116.698,4.355,116.639,4.339,116.589,4.338,116.553,4.36,116.515,4.371,116.415,4.308,116.368,4.327,116.32,4.354,116.236,4.363,116.134,4.355,116.022,4.291,115.896,4.349,115.861,4.348,115.837,4.333,115.782,4.254,115.679,4.193,115.628,4.082,115.596,3.976,115.568,3.939,115.561,3.733,115.545,3.634,115.571,3.502,115.566,3.446,115.52,3.362,115.514,3.342,115.49,3.209,115.499,3.173,115.493,3.128,115.454,3.034,115.384,3.009,115.31,2.994,115.247,3.026,115.19,2.974,115.118,2.895,115.086,2.841,115.087,2.791,115.094,2.758,115.079,2.723,115.077,2.687,115.081,2.634,115.13,2.612,115.181,2.567,115.179,2.523,115.151,2.493,115.087,2.446,114.969,2.351,114.836,2.269,114.786,2.25,114.768,2.213,114.759,2.162,114.788,2.052,114.816,2.019,114.831,1.98,114.813,1.934,114.8,1.894,114.751,1.869,114.704,1.851,114.686,1.819,114.661,1.686,114.632,1.617,114.567,1.514,114.546,1.467,114.513,1.452,114.387,1.5,114.275,1.471,114.126,1.452,114,1.455,113.902,1.434,113.835,1.38,113.76,1.311,113.682,1.261,113.622,1.236,113.513,1.308,113.458,1.302,113.359,1.327,113.126,1.408,113.069,1.432,113.007,1.434,112.988,1.457,112.998,1.496,112.988,1.548,112.943,1.567,112.476,1.559,112.342,1.515,112.251,1.48,112.186,1.439,112.167,1.338,112.129,1.244,112.079,1.143,111.923,1.113,111.809,1.012,111.77,0.999,111.691,1.014,111.607,1.023,111.547,0.994,111.483,0.996,111.287,1.043,111.101,1.051,110.996,1.026,110.938,1.017,110.615,0.878,110.506,0.862,110.461,0.882,110.399,0.939,110.315,0.996,110.115,1.19,110.041,1.236,109.992,1.283,109.945,1.338,109.879,1.398,109.818,1.439,109.736,1.523,109.654,1.615,109.636,1.777,109.571,1.806,109.549,1.848,109.539,1.896,109.629,2.028,109.694,1.889,109.72,1.858,109.865,1.764,109.985,1.718,110.114,1.699,110.246,1.695,110.298,1.701,110.349,1.72,110.4,1.7,110.675,1.548,110.782,1.521,110.895,1.532,110.94,1.517,111.098,1.401,111.145,1.387,111.223,1.396,111.123,1.449,111.058,1.487,111.029,1.558,111.047,1.634,111.11,1.684,111.154,1.739,111.17,1.902,111.198,1.985,111.251,2.064,111.268,2.14,111.209,2.198,111.196,2.297,111.209,2.38,111.242,2.436,111.296,2.399,111.351,2.364,111.406,2.368,111.444,2.382,111.451,2.424,111.44,2.498,111.443,2.634,111.513,2.743,111.623,2.818,111.728,2.854,112.119,2.915,112.737,3.07,112.921,3.131,112.988,3.162,113.045,3.205,113.14,3.344,113.32,3.561,113.446,3.741,113.712,4.001,113.924,4.243,113.953,4.289,113.988,4.421,113.99,4.483,113.984,4.546,114.013,4.575,114.054,4.593,114.064,4.593,114.095,4.565,114.169,4.527,114.224,4.478,114.261,4.414,114.288,4.355,114.29,4.304,114.323,4.263,114.417,4.256,114.447,4.204,114.512,4.114,114.572,4.049,114.608,4.024,114.654,4.038,114.725,4.097,114.776,4.169,114.81,4.267,114.783,4.281,114.831,4.354,114.84,4.393,114.818,4.429,114.79,4.464,114.779,4.553,114.76,4.667,114.747,4.718,114.784,4.755,114.865,4.802,114.945,4.856,115.027,4.9,115.029,4.821,115.027,4.691,115.052,4.583,115.107,4.39,115.171,4.364,115.247,4.347,115.291,4.353,115.319,4.365,115.327,4.381,115.279,4.456,115.267,4.634,115.228,4.751,115.168,4.867,115.14,4.9,115.375,4.933,115.428,4.969,115.52,5.049,115.554,5.094,115.582,5.194,115.467,5.254,115.422,5.331,115.419,5.413,115.556,5.567,115.604,5.603,115.625,5.549,115.685,5.535,115.741,5.533,115.797,5.536,115.877,5.614,115.918,5.725,116.06,5.882,116.11,6.003,116.138,6.13,116.495,6.522,116.538,6.583,116.75,6.977,116.776,6.99,116.833,6.952,116.85,6.827,116.842,6.772,116.808,6.691,116.788,6.606,116.812,6.608,116.913,6.66,117.019,6.797,117.078,6.917,117.129,6.969,117.23,6.94,117.252,6.919,117.245,6.833,117.255,6.783,117.294,6.677,117.38,6.612,117.499,6.571,117.61,6.513,117.646,6.474,117.67,6.427,117.694,6.35,117.696,6.272,117.616,6.197,117.65,6.074,117.645,6.002,117.617,5.941,117.501,5.885,117.818,5.94,117.896,5.972,118.004,6.053,118.062,5.983,118.116,5.863,118.072,5.832,117.935,5.788,117.928,5.769,117.974,5.706,118.031,5.712,118.145,5.754,118.249,5.821,118.3,5.82,118.353,5.806,118.456,5.763,118.514,5.729,118.563,5.685,118.595,5.592,118.714,5.559,118.957,5.429,119.003,5.418,119.05,5.415,119.178,5.431,119.223,5.413,119.256,5.366,119.266,5.308,119.263,5.246,119.25,5.199,119.22,5.16,119.132,5.1,118.913,5.023,118.672,4.964,118.551,4.968,118.382,5.019,118.32,5.012,118.261,4.989,118.185,4.829,118.324,4.669,118.563,4.502,118.595,4.461,118.586,4.41,118.548,4.379,118.498,4.362,118.364,4.336,118.229,4.316,118.117,4.288,118.008,4.25,117.896,4.263,117.741,4.338,117.696,4.343,117.65,4.304,117.604,4.2,117.574,4.171]],[[104.222,2.732,104.173,2.721,104.147,2.728,104.129,2.767,104.17,2.857,104.185,2.872,104.223,2.774,104.222,2.732]],[[100.289,5.295,100.264,5.267,100.191,5.283,100.204,5.447,100.246,5.468,100.31,5.438,100.339,5.41,100.289,5.295]],[[99.848,6.466,99.919,6.359,99.883,6.311,99.866,6.297,99.823,6.313,99.783,6.272,99.744,6.263,99.705,6.338,99.657,6.367,99.646,6.418,99.711,6.427,99.749,6.41,99.822,6.445,99.848,6.466]],[[117.142,7.168,117.081,7.115,117.06,7.179,117.064,7.261,117.147,7.337,117.264,7.352,117.281,7.291,117.267,7.221,117.239,7.185,117.142,7.168]],[[101.319,2.988,101.268,2.97,101.265,2.996,101.274,3.033,101.311,3.067,101.328,3.048,101.319,2.988]],[[111.389,2.415,111.359,2.402,111.312,2.438,111.3,2.741,111.333,2.768,111.355,2.764,111.378,2.709,111.376,2.576,111.38,2.459,111.389,2.415]],[[117.885,4.186,117.745,4.167,117.649,4.169,117.667,4.204,117.662,4.25,117.708,4.262,117.761,4.252,117.885,4.186]]]},{"n":"Malawi","c":"MW","r":6,"ml":4,"lx":33.61,"ly":-13.39,"b":[32.67,-17.131,35.893,-9.395],"p":[[[34.719,-12.111,34.684,-12.119,34.662,-12.101,34.667,-12.048,34.68,-12.009,34.715,-12.003,34.739,-12.013,34.756,-12.031,34.756,-12.059,34.746,-12.088,34.719,-12.111]],[[34.642,-12.014,34.622,-12.067,34.58,-12.066,34.542,-12.019,34.554,-11.982,34.591,-11.971,34.624,-11.985,34.642,-12.014]],[[33.202,-14.013,33.148,-13.941,33.104,-13.959,33.042,-14.01,33.009,-14.024,32.992,-14.022,32.981,-14.009,32.968,-13.977,32.92,-13.884,32.867,-13.817,32.811,-13.792,32.765,-13.761,32.785,-13.731,32.807,-13.71,32.797,-13.688,32.772,-13.657,32.672,-13.61,32.67,-13.59,32.758,-13.55,32.814,-13.503,32.852,-13.457,32.9,-13.357,32.939,-13.257,32.968,-13.225,32.978,-13.159,32.971,-13.084,32.99,-12.989,33,-12.9,32.971,-12.865,32.946,-12.804,32.975,-12.701,33.022,-12.63,33.243,-12.557,33.398,-12.49,33.431,-12.46,33.483,-12.403,33.512,-12.348,33.491,-12.331,33.37,-12.33,33.34,-12.308,33.252,-12.113,33.301,-11.888,33.305,-11.8,33.304,-11.691,33.288,-11.611,33.25,-11.578,33.226,-11.535,33.233,-11.418,33.268,-11.404,33.346,-11.249,33.38,-11.158,33.339,-11.085,33.293,-10.981,33.273,-10.915,33.261,-10.893,33.293,-10.852,33.345,-10.813,33.403,-10.802,33.465,-10.783,33.659,-10.591,33.662,-10.553,33.626,-10.489,33.554,-10.391,33.538,-10.352,33.529,-10.235,33.5,-10.2,33.394,-10.121,33.312,-10.038,33.337,-9.954,33.351,-9.862,33.31,-9.812,33.25,-9.76,33.213,-9.683,33.196,-9.626,33.148,-9.604,33.104,-9.603,33.072,-9.638,33.038,-9.635,32.996,-9.623,32.982,-9.574,32.98,-9.52,32.951,-9.484,32.923,-9.434,32.92,-9.407,32.937,-9.4,32.974,-9.395,33.13,-9.496,33.225,-9.5,33.331,-9.519,33.421,-9.608,33.468,-9.62,33.528,-9.608,33.698,-9.598,33.766,-9.611,33.854,-9.663,33.889,-9.67,33.944,-9.672,33.954,-9.658,33.959,-9.627,33.95,-9.565,33.962,-9.532,33.996,-9.495,34.089,-9.538,34.321,-9.732,34.328,-9.757,34.476,-9.949,34.524,-10.03,34.524,-10.073,34.57,-10.241,34.58,-10.32,34.57,-10.38,34.572,-10.428,34.59,-10.496,34.584,-10.525,34.637,-10.626,34.662,-10.71,34.667,-10.792,34.652,-10.873,34.606,-10.99,34.598,-11.037,34.608,-11.08,34.638,-11.127,34.688,-11.177,34.726,-11.238,34.752,-11.309,34.774,-11.342,34.801,-11.341,34.851,-11.352,34.891,-11.394,34.937,-11.463,34.953,-11.544,34.959,-11.578,34.827,-11.576,34.66,-11.589,34.619,-11.62,34.606,-11.69,34.554,-11.834,34.525,-11.887,34.463,-11.984,34.376,-12.12,34.358,-12.165,34.361,-12.211,34.412,-12.396,34.466,-12.591,34.483,-12.667,34.521,-12.926,34.543,-13.109,34.546,-13.216,34.564,-13.36,34.612,-13.438,34.662,-13.487,34.85,-13.516,34.907,-13.552,35.014,-13.643,35.247,-13.897,35.376,-14.059,35.488,-14.201,35.69,-14.466,35.847,-14.671,35.867,-14.864,35.893,-14.892,35.84,-15.035,35.805,-15.266,35.83,-15.419,35.82,-15.68,35.791,-15.959,35.755,-16.058,35.709,-16.096,35.599,-16.126,35.358,-16.161,35.322,-16.193,35.292,-16.247,35.243,-16.375,35.185,-16.505,35.167,-16.56,35.178,-16.573,35.23,-16.639,35.281,-16.808,35.29,-17.097,35.273,-17.118,35.201,-17.131,35.125,-17.127,35.093,-17.111,35.065,-17.079,35.044,-17.017,35.094,-16.974,35.112,-16.899,35.08,-16.834,35.015,-16.82,34.933,-16.76,34.759,-16.567,34.613,-16.432,34.528,-16.319,34.441,-16.274,34.416,-16.247,34.396,-16.199,34.395,-16.131,34.403,-16.08,34.376,-16.024,34.288,-15.936,34.248,-15.887,34.246,-15.829,34.283,-15.773,34.358,-15.705,34.415,-15.567,34.435,-15.477,34.541,-15.297,34.555,-15.141,34.558,-15.016,34.551,-14.922,34.524,-14.731,34.505,-14.598,34.375,-14.425,34.333,-14.409,34.209,-14.424,34.102,-14.449,34.049,-14.485,33.97,-14.487,33.761,-14.517,33.696,-14.53,33.658,-14.562,33.636,-14.568,33.505,-14.434,33.39,-14.289,33.244,-14.043,33.202,-14.013]]]},{"n":"Madagascar","c":"MG","r":3,"ml":2.7,"lx":46.7,"ly":-18.63,"b":[43.257,-25.571,50.483,-12.08],"p":[[[49.538,-12.432,49.584,-12.537,49.638,-12.637,49.805,-12.88,49.876,-12.973,49.938,-13.072,49.967,-13.27,50.073,-13.578,50.174,-14.04,50.205,-14.514,50.235,-14.732,50.313,-14.937,50.441,-15.149,50.483,-15.386,50.405,-15.629,50.292,-15.858,50.262,-15.902,50.209,-15.96,50.185,-15.958,50.094,-15.899,50.02,-15.802,49.927,-15.574,49.893,-15.458,49.853,-15.439,49.744,-15.45,49.664,-15.522,49.65,-15.567,49.667,-15.696,49.697,-15.811,49.71,-15.929,49.713,-16.077,49.742,-16.121,49.786,-16.159,49.831,-16.256,49.839,-16.487,49.811,-16.603,49.734,-16.703,49.739,-16.758,49.767,-16.815,49.74,-16.849,49.637,-16.893,49.595,-16.931,49.54,-17.033,49.449,-17.241,49.437,-17.347,49.494,-17.67,49.478,-17.899,49.363,-18.336,49.297,-18.544,49.203,-18.792,49.06,-19.12,48.918,-19.53,48.797,-19.953,48.708,-20.207,48.607,-20.458,48.469,-20.9,48.351,-21.349,48.176,-21.843,47.934,-22.394,47.908,-22.466,47.858,-22.747,47.804,-22.992,47.739,-23.233,47.604,-23.633,47.589,-23.756,47.558,-23.875,47.428,-24.125,47.373,-24.218,47.334,-24.318,47.312,-24.443,47.273,-24.564,47.177,-24.787,47.035,-24.979,46.938,-25.049,46.729,-25.15,46.622,-25.17,46.387,-25.173,46.159,-25.23,45.921,-25.341,45.692,-25.468,45.605,-25.529,45.508,-25.563,45.206,-25.571,45.115,-25.543,44.813,-25.334,44.696,-25.3,44.474,-25.271,44.407,-25.253,44.346,-25.226,44.256,-25.117,44.078,-25.025,44.035,-24.996,44.008,-24.932,43.99,-24.863,43.944,-24.787,43.91,-24.641,43.852,-24.538,43.688,-24.358,43.67,-24.3,43.657,-24.109,43.662,-23.979,43.646,-23.742,43.665,-23.63,43.722,-23.53,43.699,-23.421,43.638,-23.307,43.615,-23.188,43.57,-23.08,43.398,-22.886,43.358,-22.791,43.33,-22.692,43.265,-22.384,43.257,-22.276,43.267,-22.049,43.291,-21.933,43.332,-21.851,43.343,-21.79,43.37,-21.738,43.411,-21.696,43.438,-21.647,43.502,-21.356,43.583,-21.292,43.704,-21.255,43.8,-21.179,43.856,-21.077,43.911,-20.866,44.063,-20.656,44.117,-20.546,44.24,-20.38,44.348,-20.146,44.381,-20.035,44.405,-19.922,44.432,-19.674,44.453,-19.551,44.449,-19.429,44.387,-19.303,44.239,-19.075,44.234,-19.033,44.246,-18.863,44.233,-18.741,44.179,-18.619,44.109,-18.504,44.04,-18.288,44.007,-17.933,44.014,-17.804,43.994,-17.69,43.944,-17.581,43.979,-17.392,44.421,-16.703,44.436,-16.621,44.418,-16.411,44.427,-16.289,44.442,-16.244,44.476,-16.217,44.552,-16.204,44.909,-16.175,44.955,-16.153,45.044,-16.095,45.167,-15.983,45.223,-15.95,45.271,-15.962,45.302,-16.01,45.342,-16.037,45.486,-15.986,45.542,-15.984,45.598,-15.993,45.625,-15.946,45.641,-15.883,45.662,-15.839,45.7,-15.814,45.886,-15.8,46.004,-15.782,46.158,-15.738,46.191,-15.747,46.314,-15.905,46.352,-15.918,46.4,-15.925,46.442,-15.896,46.341,-15.813,46.326,-15.767,46.331,-15.714,46.385,-15.6,46.475,-15.513,46.675,-15.382,46.882,-15.23,46.942,-15.219,46.993,-15.243,47.032,-15.423,47.027,-15.452,47.061,-15.456,47.099,-15.434,47.133,-15.362,47.135,-15.302,47.107,-15.244,47.094,-15.195,47.093,-15.15,47.198,-15.044,47.28,-14.943,47.319,-14.822,47.352,-14.766,47.439,-14.703,47.465,-14.713,47.485,-14.764,47.496,-14.818,47.474,-14.872,47.442,-14.925,47.429,-14.996,47.478,-15.009,47.525,-14.992,47.593,-14.864,47.67,-14.743,47.716,-14.68,47.774,-14.637,47.87,-14.646,47.964,-14.673,47.812,-14.545,47.773,-14.37,47.955,-14.067,47.957,-14.004,47.983,-13.985,47.996,-13.96,47.901,-13.858,47.884,-13.808,47.896,-13.731,47.941,-13.662,47.982,-13.615,48.04,-13.596,48.086,-13.623,48.187,-13.707,48.255,-13.719,48.338,-13.639,48.405,-13.538,48.506,-13.469,48.621,-13.426,48.796,-13.267,48.91,-12.936,48.919,-12.839,48.894,-12.722,48.854,-12.61,48.786,-12.471,48.804,-12.44,48.9,-12.458,48.932,-12.439,49.036,-12.316,49.207,-12.08,49.263,-12.08,49.312,-12.124,49.33,-12.189,49.364,-12.236,49.48,-12.348,49.538,-12.432]],[[48.342,-13.364,48.344,-13.4,48.212,-13.385,48.191,-13.26,48.256,-13.256,48.27,-13.205,48.309,-13.198,48.351,-13.31,48.342,-13.364]],[[49.936,-16.903,49.824,-17.087,49.856,-16.933,49.986,-16.712,50.023,-16.695,49.936,-16.903]]]},{"n":"North Macedonia","c":"MK","r":6,"ml":5,"lx":21.56,"ly":41.56,"b":[20.449,40.85,23.006,42.358],"p":[[[21.563,42.248,21.618,42.242,21.739,42.268,21.815,42.303,21.853,42.308,21.904,42.322,21.978,42.32,22.052,42.305,22.147,42.325,22.24,42.358,22.277,42.35,22.317,42.322,22.344,42.314,22.498,42.165,22.583,42.105,22.682,42.059,22.796,42.026,22.837,41.994,22.909,41.835,22.944,41.775,22.992,41.757,23.004,41.74,23.006,41.717,22.951,41.606,22.93,41.356,22.916,41.336,22.859,41.337,22.784,41.332,22.755,41.313,22.725,41.179,22.604,41.14,22.494,41.119,22.401,41.123,22.238,41.155,22.184,41.159,22.139,41.141,21.993,41.131,21.929,41.107,21.779,40.95,21.628,40.896,21.576,40.869,21.46,40.904,21.404,40.907,21.324,40.867,21.148,40.863,21.1,40.856,20.964,40.85,20.959,40.872,20.933,40.903,20.87,40.918,20.741,40.905,20.709,40.928,20.656,41.062,20.614,41.083,20.568,41.128,20.489,41.273,20.487,41.336,20.492,41.391,20.449,41.521,20.476,41.554,20.516,41.575,20.517,41.627,20.505,41.706,20.553,41.862,20.566,41.874,20.579,41.866,20.695,41.854,20.725,41.874,20.744,41.904,20.75,42.018,20.778,42.071,21.06,42.171,21.142,42.175,21.206,42.129,21.256,42.1,21.287,42.1,21.298,42.13,21.332,42.187,21.39,42.22,21.561,42.248,21.563,42.248]]]},{"n":"Luxembourg","c":"LU","r":6,"ml":5.7,"lx":6.08,"ly":49.73,"b":[5.725,49.445,6.494,50.167],"p":[[[6.117,50.121,6.108,50.094,6.11,50.034,6.138,49.974,6.205,49.915,6.256,49.872,6.325,49.838,6.441,49.805,6.487,49.798,6.494,49.754,6.485,49.708,6.445,49.682,6.407,49.645,6.378,49.6,6.348,49.513,6.344,49.453,6.277,49.478,6.242,49.494,6.181,49.499,6.12,49.485,6.074,49.455,6.011,49.445,5.959,49.455,5.929,49.478,5.901,49.49,5.823,49.505,5.79,49.538,5.815,49.554,5.838,49.578,5.857,49.613,5.88,49.645,5.804,49.732,5.788,49.759,5.725,49.808,5.726,49.833,5.741,49.857,5.735,49.876,5.744,49.92,5.788,49.961,5.817,50.013,5.867,50.083,5.976,50.167,6.055,50.154,6.089,50.155,6.11,50.124,6.117,50.121]]]},{"n":"Lithuania","c":"LT","r":5,"ml":4,"lx":24.09,"ly":55.1,"b":[20.9,53.893,26.776,56.411],"p":[[[20.958,55.279,20.9,55.287,21.014,55.402,21.058,55.477,21.088,55.583,21.115,55.617,21.116,55.568,21.104,55.488,21.032,55.35,20.958,55.279]],[[22.766,54.357,22.724,54.406,22.68,54.493,22.684,54.563,22.71,54.633,22.831,54.838,22.825,54.871,22.737,54.929,22.627,54.971,22.567,55.059,22.346,55.064,22.138,55.059,22.072,55.064,21.874,55.101,21.683,55.16,21.555,55.195,21.447,55.234,21.389,55.276,21.298,55.264,21.236,55.264,21.236,55.271,21.201,55.344,21.238,55.455,21.171,55.618,21.062,55.813,21.054,56.023,21.046,56.07,21.315,56.188,21.654,56.315,21.731,56.326,22.043,56.401,22.085,56.407,22.366,56.393,22.587,56.375,22.773,56.377,22.876,56.396,22.968,56.38,23.043,56.324,23.12,56.331,23.196,56.367,23.613,56.334,23.707,56.335,23.813,56.329,24.008,56.295,24.121,56.264,24.368,56.283,24.474,56.284,24.529,56.296,24.7,56.381,24.773,56.396,24.841,56.411,24.903,56.398,24.944,56.326,25.07,56.2,25.207,56.178,25.586,56.13,25.663,56.105,25.876,55.994,26.004,55.94,26.086,55.897,26.21,55.812,26.281,55.75,26.401,55.704,26.543,55.672,26.594,55.668,26.591,55.623,26.567,55.546,26.519,55.448,26.47,55.372,26.458,55.342,26.495,55.318,26.681,55.306,26.76,55.293,26.776,55.273,26.734,55.247,26.675,55.225,26.648,55.204,26.601,55.13,26.292,55.14,26.251,55.125,26.231,55.09,26.216,55.05,26.175,55.003,26.093,54.962,25.964,54.947,25.859,54.919,25.781,54.833,25.722,54.718,25.724,54.636,25.732,54.59,25.725,54.564,25.685,54.536,25.62,54.46,25.568,54.377,25.547,54.332,25.558,54.311,25.617,54.31,25.703,54.293,25.748,54.26,25.765,54.221,25.765,54.18,25.749,54.157,25.681,54.14,25.573,54.14,25.51,54.16,25.497,54.175,25.527,54.215,25.506,54.265,25.461,54.293,25.371,54.251,25.284,54.251,25.179,54.214,25.111,54.155,25.046,54.133,24.87,54.145,24.826,54.119,24.789,53.998,24.768,53.975,24.621,53.98,24.479,53.932,24.318,53.893,24.237,53.92,24.191,53.95,24.104,53.945,24.008,53.932,23.944,53.939,23.873,53.936,23.734,53.912,23.559,53.92,23.485,53.94,23.478,53.959,23.483,54.006,23.481,54.079,23.454,54.143,23.37,54.2,23.282,54.24,23.17,54.281,23.088,54.299,23.042,54.304,23.032,54.328,23.016,54.348,22.977,54.366,22.894,54.391,22.824,54.396,22.766,54.357]]]},{"n":"Liechtenstein","c":"LI","r":6,"ml":5,"lx":9.56,"ly":47.11,"b":[9.479,47.057,9.611,47.271],"p":[[[9.58,47.057,9.502,47.063,9.488,47.062,9.479,47.098,9.484,47.173,9.528,47.271,9.537,47.255,9.542,47.234,9.551,47.212,9.556,47.185,9.572,47.158,9.601,47.132,9.611,47.107,9.596,47.076,9.58,47.057]]]},{"n":"Libya","c":"LY","r":3,"ml":3,"lx":18.01,"ly":26.64,"b":[9.31,19.497,25.15,33.182],"p":[[[9.519,30.229,9.638,30.282,9.807,30.342,9.895,30.387,9.933,30.425,10.06,30.58,10.126,30.666,10.216,30.783,10.256,30.865,10.257,30.941,10.243,31.032,10.173,31.251,10.115,31.464,10.16,31.546,10.196,31.585,10.275,31.685,10.306,31.705,10.476,31.736,10.544,31.803,10.596,31.886,10.609,31.93,10.683,31.975,10.772,32.021,10.826,32.081,11.005,32.173,11.168,32.257,11.358,32.345,11.505,32.414,11.536,32.473,11.534,32.525,11.454,32.643,11.454,32.782,11.459,32.897,11.467,32.966,11.502,33.156,11.505,33.182,11.657,33.119,11.813,33.094,12.28,32.859,12.427,32.829,12.754,32.801,13.138,32.897,13.283,32.915,13.536,32.824,13.648,32.799,13.835,32.792,14.156,32.71,14.237,32.681,14.424,32.55,14.513,32.511,15.177,32.391,15.267,32.312,15.359,32.16,15.363,31.971,15.414,31.834,15.496,31.657,15.596,31.531,15.706,31.426,15.832,31.361,16.123,31.264,16.451,31.227,16.782,31.215,17.349,31.081,17.83,30.928,17.949,30.852,18.19,30.777,18.67,30.416,18.936,30.29,19.124,30.266,19.292,30.288,19.59,30.414,19.713,30.488,20.013,30.801,20.112,30.964,20.151,31.079,20.141,31.196,20.104,31.301,20.02,31.411,19.961,31.556,19.926,31.818,19.973,31.999,20.031,32.108,20.121,32.219,20.371,32.431,20.621,32.58,21.062,32.776,21.319,32.778,21.425,32.799,21.636,32.937,21.721,32.942,21.839,32.909,22.187,32.918,22.341,32.88,22.523,32.794,22.754,32.741,22.917,32.687,23.091,32.619,23.13,32.448,23.11,32.397,23.106,32.331,23.286,32.214,23.798,32.159,23.898,32.127,24.039,32.037,24.13,32.009,24.48,31.997,24.684,32.016,24.879,31.984,24.951,31.954,25.025,31.883,25.115,31.712,25.15,31.655,25.112,31.627,25.057,31.567,25.023,31.514,24.93,31.427,24.853,31.335,24.86,31.199,24.878,31.061,24.929,30.926,24.974,30.777,24.961,30.679,24.923,30.558,24.878,30.458,24.726,30.251,24.703,30.201,24.712,30.132,24.804,29.886,24.811,29.809,24.866,29.57,24.916,29.376,24.972,29.224,24.98,29.182,24.98,28.957,24.98,28.733,24.98,28.508,24.98,28.284,24.98,28.059,24.98,27.835,24.98,27.61,24.98,27.385,24.98,27.161,24.98,26.936,24.98,26.712,24.98,26.487,24.98,26.263,24.98,26.038,24.98,25.813,24.98,25.589,24.98,25.364,24.98,25.14,24.98,24.915,24.98,24.691,24.98,24.466,24.98,24.242,24.98,24.017,24.98,23.792,24.98,23.568,24.98,23.343,24.98,23.119,24.98,22.894,24.98,22.67,24.98,22.445,24.98,22.22,24.98,21.996,24.98,21.498,24.98,20.999,24.98,20.501,24.979,20.003,24.976,20.001,24.973,19.999,24.97,19.997,24.967,19.995,24.72,19.996,24.474,19.996,24.227,19.996,23.98,19.996,23.98,19.871,23.98,19.746,23.98,19.621,23.98,19.497,23.501,19.733,23.022,19.97,22.543,20.206,22.064,20.443,21.585,20.679,21.106,20.916,20.627,21.153,20.148,21.389,19.669,21.626,19.189,21.862,18.71,22.099,18.231,22.336,17.752,22.572,17.273,22.809,16.794,23.045,16.315,23.282,15.984,23.445,15.627,23.286,15.347,23.161,14.979,22.996,14.556,22.783,14.231,22.618,14.216,22.62,14.201,22.624,13.863,22.902,13.599,23.12,13.481,23.18,12.984,23.291,12.489,23.402,11.968,23.518,11.873,23.695,11.767,23.893,11.624,24.14,11.537,24.291,11.508,24.314,11.108,24.434,10.686,24.551,10.439,24.48,10.396,24.486,10.326,24.53,10.256,24.591,10.219,24.676,10.12,24.79,10.028,25.051,10.019,25.259,10.001,25.332,9.781,25.624,9.581,25.89,9.448,26.067,9.422,26.147,9.438,26.246,9.491,26.334,9.685,26.438,9.859,26.552,9.883,26.631,9.894,26.848,9.837,26.916,9.795,27.045,9.753,27.219,9.748,27.331,9.825,27.553,9.916,27.786,9.858,28.043,9.816,28.56,9.843,28.967,9.821,29.115,9.805,29.177,9.746,29.369,9.673,29.567,9.64,29.636,9.546,29.796,9.391,29.994,9.31,30.115,9.421,30.179,9.519,30.229]]]},{"n":"Liberia","c":"LR","r":4,"ml":4,"lx":-9.46,"ly":6.45,"b":[-11.508,4.351,-7.4,8.538],"p":[[[-11.508,6.907,-11.455,6.951,-11.377,7.095,-11.268,7.233,-11.166,7.314,-11.085,7.399,-11,7.463,-10.878,7.538,-10.691,7.736,-10.647,7.759,-10.618,7.896,-10.571,8.071,-10.517,8.125,-10.39,8.158,-10.36,8.188,-10.315,8.311,-10.286,8.454,-10.283,8.485,-10.233,8.489,-10.147,8.52,-10.098,8.506,-10.076,8.465,-10.064,8.43,-9.805,8.519,-9.782,8.538,-9.768,8.535,-9.736,8.454,-9.717,8.459,-9.701,8.482,-9.684,8.484,-9.664,8.474,-9.643,8.436,-9.61,8.402,-9.554,8.379,-9.518,8.346,-9.522,8.26,-9.508,8.176,-9.484,8.157,-9.471,8.107,-9.465,8.052,-9.451,8.023,-9.442,7.968,-9.446,7.908,-9.436,7.867,-9.395,7.795,-9.369,7.704,-9.369,7.64,-9.384,7.572,-9.411,7.51,-9.46,7.443,-9.464,7.416,-9.435,7.398,-9.392,7.395,-9.355,7.409,-9.263,7.378,-9.215,7.333,-9.173,7.278,-9.135,7.251,-9.118,7.216,-9.052,7.225,-8.977,7.259,-8.961,7.275,-8.938,7.266,-8.89,7.263,-8.856,7.323,-8.828,7.392,-8.769,7.467,-8.74,7.496,-8.733,7.544,-8.729,7.605,-8.708,7.659,-8.66,7.688,-8.607,7.688,-8.579,7.677,-8.564,7.625,-8.522,7.586,-8.486,7.558,-8.467,7.547,-8.437,7.516,-8.409,7.412,-8.297,7.074,-8.302,6.981,-8.325,6.92,-8.325,6.86,-8.333,6.802,-8.401,6.705,-8.604,6.508,-8.588,6.491,-8.54,6.468,-8.49,6.456,-8.45,6.463,-8.399,6.413,-8.345,6.351,-8.287,6.319,-8.204,6.291,-8.131,6.288,-8.069,6.298,-7.982,6.286,-7.889,6.235,-7.856,6.15,-7.833,6.076,-7.801,6.039,-7.797,5.975,-7.73,5.919,-7.636,5.908,-7.514,5.842,-7.483,5.846,-7.469,5.854,-7.454,5.841,-7.424,5.651,-7.4,5.551,-7.412,5.51,-7.429,5.478,-7.43,5.325,-7.485,5.236,-7.494,5.14,-7.51,5.108,-7.569,5.081,-7.569,5.006,-7.585,4.917,-7.591,4.822,-7.575,4.572,-7.572,4.386,-7.545,4.351,-7.66,4.367,-7.998,4.509,-8.259,4.59,-9.132,5.055,-9.375,5.241,-9.654,5.519,-10.276,6.078,-10.418,6.167,-10.597,6.211,-10.708,6.258,-10.786,6.31,-10.849,6.465,-11.005,6.557,-11.292,6.688,-11.508,6.907]]]},{"n":"Lesotho","c":"LS","r":6,"ml":4,"lx":28.25,"ly":-29.48,"b":[27.052,-30.642,29.391,-28.582],"p":[[[28.737,-30.102,28.647,-30.127,28.634,-30.129,28.577,-30.123,28.5,-30.129,28.439,-30.142,28.392,-30.148,28.315,-30.218,28.176,-30.41,28.139,-30.45,28.129,-30.525,28.096,-30.585,28.057,-30.631,28.018,-30.642,27.902,-30.624,27.753,-30.6,27.667,-30.542,27.59,-30.466,27.549,-30.411,27.507,-30.381,27.492,-30.364,27.431,-30.338,27.409,-30.325,27.388,-30.316,27.364,-30.279,27.35,-30.247,27.355,-30.159,27.313,-30.106,27.24,-30.015,27.194,-29.941,27.13,-29.84,27.092,-29.754,27.052,-29.664,27.057,-29.626,27.095,-29.599,27.207,-29.554,27.295,-29.519,27.357,-29.455,27.425,-29.36,27.458,-29.303,27.491,-29.277,27.527,-29.236,27.59,-29.146,27.66,-29.047,27.736,-28.94,27.83,-28.909,27.96,-28.873,28.084,-28.78,28.233,-28.701,28.472,-28.616,28.583,-28.594,28.626,-28.582,28.653,-28.598,28.681,-28.647,28.722,-28.688,28.816,-28.759,28.856,-28.776,28.954,-28.881,29.058,-28.954,29.178,-29.037,29.26,-29.078,29.301,-29.09,29.336,-29.164,29.371,-29.218,29.391,-29.27,29.387,-29.32,29.349,-29.442,29.294,-29.567,29.249,-29.619,29.195,-29.652,29.142,-29.701,29.122,-29.801,29.098,-29.919,29.029,-29.968,28.975,-29.999,28.901,-30.038,28.737,-30.102]]]},{"n":"Lebanon","c":"LB","r":5,"ml":4,"lx":35.99,"ly":34.13,"b":[35.109,33.076,36.585,34.679],"p":[[[35.976,34.629,36.151,34.629,36.264,34.633,36.296,34.679,36.384,34.658,36.433,34.613,36.389,34.567,36.326,34.513,36.33,34.5,36.376,34.495,36.456,34.466,36.504,34.432,36.585,34.221,36.535,34.134,36.458,34.057,36.423,34.05,36.355,34.011,36.298,33.959,36.278,33.925,36.282,33.894,36.363,33.855,36.365,33.839,36.349,33.827,36.283,33.836,36.199,33.84,36.15,33.84,36.092,33.832,36.019,33.784,35.986,33.753,35.968,33.732,35.942,33.668,35.972,33.623,36.027,33.598,36.034,33.585,36.022,33.563,35.968,33.535,35.927,33.5,35.915,33.465,35.869,33.432,35.841,33.416,35.788,33.37,35.734,33.333,35.627,33.275,35.603,33.241,35.579,33.271,35.533,33.25,35.493,33.119,35.411,33.076,35.309,33.08,35.223,33.092,35.109,33.084,35.155,33.16,35.204,33.259,35.251,33.393,35.336,33.503,35.511,33.88,35.612,34.032,35.648,34.248,35.804,34.437,35.921,34.493,35.978,34.547,35.976,34.629]]]},{"n":"Latvia","c":"LV","r":5,"ml":4,"lx":25.46,"ly":57.07,"b":[21.015,55.668,28.202,58.063],"p":[[[26.594,55.668,26.543,55.672,26.401,55.704,26.281,55.75,26.21,55.812,26.086,55.897,26.004,55.94,25.876,55.994,25.663,56.105,25.586,56.13,25.207,56.178,25.07,56.2,24.944,56.326,24.903,56.398,24.841,56.411,24.773,56.396,24.7,56.381,24.529,56.296,24.474,56.284,24.368,56.283,24.121,56.264,24.008,56.295,23.813,56.329,23.707,56.335,23.613,56.334,23.196,56.367,23.12,56.331,23.043,56.324,22.968,56.38,22.876,56.396,22.773,56.377,22.587,56.375,22.366,56.393,22.085,56.407,22.043,56.401,21.731,56.326,21.654,56.315,21.315,56.188,21.046,56.07,21.015,56.259,21.031,56.637,21.071,56.824,21.257,56.933,21.351,57.018,21.405,57.131,21.421,57.236,21.459,57.322,21.729,57.571,21.942,57.598,22.231,57.667,22.555,57.724,22.617,57.651,22.649,57.595,23.038,57.392,23.137,57.324,23.287,57.09,23.648,56.971,23.931,57.008,24.054,57.066,24.281,57.172,24.383,57.25,24.403,57.325,24.363,57.645,24.302,57.784,24.323,57.871,24.363,57.866,24.459,57.908,24.776,57.985,24.839,57.989,24.911,58.005,25.111,58.063,25.175,58.032,25.229,57.997,25.258,57.996,25.273,58.009,25.269,58.032,25.283,58.048,25.34,58.039,25.571,57.943,25.66,57.92,25.721,57.914,25.794,57.869,25.991,57.838,26.015,57.815,26.03,57.786,26.215,57.663,26.298,57.601,26.462,57.544,26.533,57.531,26.82,57.589,26.9,57.609,26.966,57.609,27.033,57.579,27.187,57.538,27.327,57.525,27.352,57.528,27.47,57.524,27.511,57.508,27.539,57.43,27.673,57.368,27.797,57.317,27.829,57.293,27.838,57.248,27.83,57.194,27.815,57.167,27.763,57.135,27.717,57.055,27.711,56.978,27.639,56.846,27.656,56.843,27.806,56.867,27.849,56.853,27.882,56.824,27.892,56.741,27.941,56.704,27.992,56.645,28.008,56.6,28.103,56.546,28.111,56.511,28.169,56.387,28.192,56.316,28.202,56.26,28.173,56.19,28.148,56.143,28.118,56.146,28.032,56.133,27.896,56.076,27.694,55.942,27.642,55.912,27.589,55.809,27.577,55.799,27.459,55.804,27.427,55.806,27.309,55.804,27.053,55.831,26.953,55.813,26.822,55.709,26.772,55.694,26.62,55.68,26.594,55.668]]]},{"n":"Laos","c":"LA","r":4,"ml":4,"lx":102.53,"ly":19.43,"b":[100.115,13.921,107.653,22.495],"p":[[[102.127,22.379,102.183,22.284,102.301,22.178,102.443,22.027,102.488,21.958,102.583,21.904,102.61,21.852,102.631,21.771,102.641,21.711,102.662,21.676,102.695,21.662,102.739,21.678,102.771,21.71,102.798,21.798,102.816,21.807,102.845,21.735,102.876,21.722,102.918,21.713,102.95,21.681,102.959,21.626,102.949,21.57,102.91,21.506,102.888,21.44,102.872,21.338,102.851,21.266,102.884,21.203,103.104,20.892,103.211,20.841,103.464,20.78,103.555,20.738,103.635,20.697,103.714,20.717,103.791,20.81,103.882,20.861,104.052,20.941,104.101,20.946,104.195,20.914,104.35,20.821,104.461,20.734,104.53,20.688,104.583,20.647,104.575,20.6,104.533,20.555,104.479,20.53,104.408,20.486,104.368,20.441,104.392,20.425,104.496,20.414,104.619,20.375,104.656,20.329,104.662,20.289,104.677,20.225,104.699,20.205,104.813,20.217,104.848,20.202,104.889,20.169,104.929,20.083,104.928,20.018,104.846,19.947,104.815,19.904,104.802,19.836,104.743,19.755,104.588,19.619,104.546,19.611,104.26,19.685,104.127,19.681,104.063,19.678,104.032,19.675,104.013,19.646,104.052,19.564,104.063,19.483,104.028,19.42,103.932,19.366,103.896,19.34,103.892,19.305,103.918,19.269,104.006,19.231,104.109,19.196,104.446,18.984,104.518,18.934,104.613,18.861,104.717,18.803,104.993,18.728,105.115,18.679,105.146,18.651,105.145,18.617,105.113,18.573,105.087,18.496,105.086,18.45,105.115,18.405,105.163,18.339,105.273,18.235,105.333,18.19,105.4,18.179,105.458,18.154,105.519,18.077,105.588,17.984,105.598,17.918,105.627,17.834,105.691,17.738,105.779,17.644,105.903,17.529,105.974,17.447,106.006,17.415,106.27,17.217,106.333,17.144,106.426,17.003,106.465,16.982,106.502,16.954,106.526,16.877,106.534,16.821,106.546,16.651,106.594,16.6,106.638,16.538,106.656,16.493,106.696,16.459,106.74,16.453,106.792,16.49,106.832,16.526,106.851,16.516,106.893,16.397,106.931,16.353,107.002,16.312,107.07,16.28,107.217,16.136,107.296,16.084,107.35,16.067,107.396,16.043,107.41,15.998,107.392,15.952,107.361,15.922,107.189,15.839,107.166,15.802,107.19,15.747,107.233,15.678,107.279,15.619,107.339,15.56,107.46,15.466,107.564,15.392,107.622,15.31,107.653,15.255,107.634,15.19,107.59,15.118,107.555,15.057,107.496,15.021,107.48,14.98,107.505,14.916,107.525,14.872,107.514,14.817,107.519,14.705,107.465,14.665,107.415,14.563,107.38,14.555,107.293,14.592,107.262,14.572,107.207,14.498,107.109,14.417,107.062,14.416,107.03,14.426,106.992,14.391,106.938,14.327,106.913,14.329,106.82,14.315,106.783,14.335,106.738,14.388,106.665,14.441,106.599,14.479,106.564,14.505,106.531,14.549,106.501,14.578,106.447,14.515,106.355,14.455,106.268,14.466,106.225,14.476,106.191,14.388,106.165,14.372,106.008,14.357,105.979,14.343,106.004,14.263,106.097,14.127,106.125,14.049,106.067,13.921,105.904,13.925,105.831,13.977,105.764,14.049,105.74,14.085,105.532,14.156,105.393,14.107,105.35,14.11,105.285,14.161,105.246,14.201,105.207,14.259,105.186,14.319,105.183,14.346,105.244,14.368,105.342,14.417,105.423,14.472,105.476,14.53,105.497,14.591,105.5,14.661,105.523,14.843,105.547,14.932,105.533,15.042,105.49,15.128,105.49,15.257,105.506,15.32,105.513,15.361,105.574,15.413,105.616,15.488,105.639,15.586,105.641,15.657,105.622,15.7,105.562,15.741,105.462,15.78,105.399,15.83,105.373,15.89,105.376,15.942,105.406,15.987,105.331,16.038,105.149,16.094,105.047,16.16,105.026,16.238,104.95,16.34,104.819,16.466,104.751,16.648,104.744,16.884,104.759,17.077,104.816,17.3,104.74,17.462,104.656,17.547,104.539,17.609,104.428,17.699,104.323,17.816,104.196,17.988,104.049,18.217,103.95,18.319,103.899,18.295,103.792,18.317,103.63,18.383,103.488,18.418,103.367,18.423,103.288,18.408,103.252,18.373,103.249,18.339,103.28,18.305,103.263,18.278,103.2,18.259,103.149,18.222,103.091,18.138,103.051,18.029,102.991,17.986,102.899,17.977,102.807,17.946,102.718,17.892,102.675,17.852,102.68,17.824,102.661,17.818,102.617,17.833,102.596,17.87,102.598,17.927,102.553,17.965,102.459,17.985,102.352,18.046,102.232,18.149,102.148,18.204,102.101,18.211,102.035,18.17,101.947,18.081,101.875,18.046,101.819,18.065,101.775,18.033,101.744,17.953,101.688,17.889,101.564,17.821,101.555,17.812,101.414,17.719,101.3,17.625,101.167,17.499,101.105,17.48,101.046,17.51,100.956,17.541,100.908,17.584,100.999,17.797,101.113,18.034,101.144,18.143,101.149,18.222,101.138,18.287,101.093,18.355,101.051,18.407,101.047,18.442,101.06,18.479,101.106,18.534,101.166,18.618,101.221,18.793,101.286,18.977,101.28,19.089,101.227,19.212,101.198,19.328,101.221,19.487,101.212,19.548,101.155,19.579,100.967,19.611,100.906,19.605,100.858,19.585,100.807,19.542,100.744,19.515,100.625,19.5,100.514,19.553,100.42,19.644,100.398,19.756,100.466,19.889,100.515,19.996,100.543,20.089,100.54,20.132,100.52,20.178,100.492,20.184,100.432,20.241,100.373,20.34,100.318,20.386,100.266,20.377,100.218,20.34,100.174,20.273,100.14,20.245,100.115,20.258,100.122,20.317,100.13,20.372,100.184,20.589,100.249,20.73,100.326,20.796,100.407,20.823,100.493,20.813,100.565,20.825,100.623,20.86,100.618,20.879,100.549,20.884,100.522,20.922,100.536,20.992,100.567,21.038,100.614,21.059,100.659,21.13,100.703,21.251,100.757,21.313,100.82,21.314,100.928,21.366,101.08,21.469,101.139,21.567,101.197,21.522,101.175,21.408,101.206,21.383,101.22,21.342,101.212,21.278,101.224,21.224,101.248,21.197,101.281,21.184,101.444,21.231,101.542,21.234,101.584,21.204,101.622,21.184,101.669,21.17,101.705,21.15,101.728,21.156,101.783,21.204,101.801,21.213,101.802,21.236,101.763,21.279,101.723,21.315,101.724,21.395,101.743,21.534,101.747,21.606,101.744,21.778,101.737,21.827,101.7,21.882,101.603,21.99,101.576,22.055,101.56,22.121,101.562,22.162,101.537,22.21,101.525,22.254,101.568,22.276,101.62,22.327,101.646,22.405,101.671,22.462,101.708,22.487,101.739,22.495,101.76,22.49,101.842,22.388,101.945,22.439,102.024,22.439,102.092,22.412,102.127,22.379]]]},{"n":"Kyrgyzstan","c":"KG","r":4,"ml":3,"lx":74.53,"ly":41.67,"b":[69.229,39.208,80.246,43.24],"p":[[[70.958,40.239,70.991,40.255,71.095,40.271,71.305,40.287,71.376,40.275,71.457,40.242,71.52,40.209,71.58,40.21,71.63,40.217,71.651,40.208,71.667,40.179,71.692,40.152,71.773,40.188,71.845,40.234,71.903,40.241,71.956,40.259,71.971,40.29,72.013,40.341,72.131,40.439,72.193,40.454,72.233,40.454,72.235,40.439,72.254,40.424,72.358,40.402,72.389,40.427,72.406,40.463,72.37,40.52,72.369,40.543,72.383,40.565,72.402,40.578,72.567,40.524,72.604,40.525,72.68,40.556,72.749,40.609,72.774,40.65,73.113,40.786,73.137,40.811,73.132,40.829,72.99,40.86,72.926,40.842,72.867,40.842,72.831,40.862,72.658,40.87,72.62,40.884,72.506,40.982,72.427,41.019,72.364,41.043,72.295,41.04,72.213,41.014,72.187,41.026,72.181,41.067,72.181,41.118,72.164,41.174,72.115,41.187,72.052,41.165,71.958,41.187,71.879,41.195,71.858,41.311,71.826,41.361,71.792,41.413,71.758,41.428,71.701,41.454,71.697,41.516,71.685,41.533,71.665,41.541,71.638,41.534,71.602,41.503,71.62,41.435,71.606,41.367,71.586,41.333,71.546,41.308,71.5,41.307,71.421,41.342,71.408,41.136,71.393,41.123,71.299,41.152,71.223,41.14,71.111,41.153,71.026,41.187,70.963,41.196,70.86,41.225,70.782,41.263,70.734,41.401,70.689,41.45,70.646,41.46,70.471,41.413,70.408,41.45,70.29,41.497,70.201,41.514,70.177,41.54,70.181,41.571,70.455,41.725,70.563,41.831,70.631,41.875,70.728,41.905,70.803,41.923,70.842,42.02,70.857,42.031,70.91,42.038,71.032,42.078,71.229,42.163,71.232,42.186,71.213,42.206,71.13,42.25,71.036,42.285,70.979,42.267,70.947,42.249,70.893,42.294,70.893,42.34,70.952,42.419,71.002,42.459,71.023,42.535,71.094,42.587,71.167,42.667,71.257,42.734,71.422,42.783,71.514,42.767,71.601,42.779,71.735,42.819,71.761,42.821,71.817,42.822,72.162,42.761,72.276,42.758,72.543,42.678,72.666,42.66,72.753,42.638,72.792,42.603,72.855,42.561,73.191,42.527,73.283,42.504,73.316,42.467,73.412,42.42,73.493,42.409,73.422,42.594,73.45,42.703,73.556,43.003,73.612,43.048,73.719,43.088,73.886,43.133,73.949,43.195,74.086,43.189,74.146,43.194,74.187,43.205,74.209,43.24,74.364,43.179,74.622,43.056,74.818,42.978,75.048,42.904,75.366,42.837,75.636,42.815,75.682,42.83,75.79,42.933,75.84,42.938,75.932,42.929,76.218,42.924,76.509,42.919,76.646,42.929,76.944,42.971,76.988,42.974,77.057,42.971,77.236,42.913,77.369,42.904,77.459,42.905,77.512,42.9,77.622,42.902,77.802,42.895,78.023,42.858,78.29,42.864,78.376,42.871,78.524,42.865,78.642,42.829,78.792,42.791,78.885,42.775,78.948,42.767,79.06,42.764,79.127,42.776,79.165,42.759,79.203,42.666,79.296,42.605,79.368,42.547,79.428,42.483,79.49,42.458,79.598,42.457,79.803,42.438,79.921,42.413,80.071,42.303,80.209,42.19,80.229,42.13,80.246,42.06,80.235,42.043,80.216,42.032,79.91,42.015,79.84,41.996,79.766,41.899,79.504,41.821,79.354,41.781,79.294,41.783,79.148,41.719,78.743,41.56,78.543,41.46,78.443,41.418,78.362,41.372,78.349,41.325,78.346,41.281,78.123,41.076,77.956,41.051,77.815,41.056,77.719,41.024,77.582,40.993,77.284,41.014,77.182,41.011,76.987,41.039,76.908,41.024,76.824,40.982,76.708,40.818,76.661,40.78,76.64,40.742,76.622,40.662,76.578,40.578,76.521,40.511,76.48,40.45,76.396,40.39,76.319,40.352,76.258,40.431,76.206,40.408,76.157,40.376,76.062,40.388,76.004,40.371,75.872,40.303,75.677,40.306,75.656,40.329,75.617,40.517,75.583,40.605,75.556,40.625,75.521,40.628,75.241,40.48,75.111,40.454,75.004,40.45,74.866,40.494,74.835,40.483,74.811,40.459,74.801,40.429,74.842,40.345,74.83,40.329,74.768,40.33,74.68,40.311,74.613,40.272,74.412,40.137,74.243,40.092,74.085,40.074,74.021,40.059,73.992,40.043,73.939,39.979,73.885,39.878,73.856,39.829,73.835,39.8,73.84,39.763,73.883,39.715,73.915,39.606,73.907,39.579,73.873,39.533,73.823,39.489,73.716,39.462,73.632,39.449,73.576,39.458,73.47,39.461,73.387,39.443,73.336,39.412,73.235,39.375,73.109,39.362,72.949,39.357,72.872,39.36,72.64,39.386,72.563,39.377,72.49,39.357,72.358,39.337,72.287,39.274,72.25,39.216,72.23,39.208,72.147,39.261,72.084,39.311,72.043,39.352,71.991,39.351,71.806,39.276,71.779,39.278,71.726,39.307,71.735,39.378,71.732,39.423,71.673,39.447,71.546,39.453,71.503,39.479,71.506,39.517,71.517,39.554,71.503,39.582,71.47,39.604,71.404,39.598,71.329,39.569,71.273,39.535,71.203,39.52,71.118,39.514,71.065,39.493,71.005,39.412,70.799,39.395,70.733,39.413,70.679,39.471,70.608,39.564,70.568,39.576,70.501,39.587,70.392,39.582,70.245,39.543,70.209,39.575,70.171,39.584,70.137,39.558,70.102,39.561,69.956,39.553,69.772,39.557,69.667,39.575,69.599,39.574,69.463,39.532,69.392,39.532,69.298,39.525,69.28,39.666,69.229,39.761,69.245,39.827,69.279,39.918,69.307,39.969,69.365,39.947,69.432,39.91,69.476,39.92,69.488,39.95,69.471,39.991,69.469,40.021,69.494,40.06,69.53,40.097,69.765,40.158,69.967,40.202,70.071,40.173,70.274,40.105,70.379,40.07,70.451,40.049,70.515,39.95,70.557,39.954,70.599,39.975,70.624,39.999,70.644,40.083,70.739,40.131,70.946,40.188,70.961,40.221,70.958,40.239],[71.206,39.893,71.216,39.907,71.179,39.98,71.229,40.048,71.13,40.06,71.08,40.08,71.024,40.149,71.005,40.152,70.976,40.133,70.961,40.088,70.974,40.039,71.014,40.006,71.041,39.995,71.045,39.993,71.044,39.976,71.012,39.895,71.064,39.885,71.156,39.883,71.206,39.893],[71.78,39.95,71.79,39.995,71.765,39.993,71.737,39.981,71.681,39.969,71.669,39.946,71.706,39.917,71.753,39.907,71.78,39.95],[70.702,39.825,70.698,39.846,70.664,39.855,70.567,39.867,70.498,39.882,70.483,39.883,70.489,39.863,70.519,39.828,70.56,39.791,70.612,39.787,70.702,39.825]]]},{"n":"Kuwait","c":"KW","r":6,"ml":5,"lx":47.31,"ly":29.41,"b":[46.531,28.533,48.442,30.097],"p":[[[48.275,29.624,48.218,29.602,48.18,29.611,48.143,29.665,48.081,29.799,48.115,29.849,48.113,29.87,48.12,29.886,48.139,29.897,48.159,29.96,48.185,29.979,48.228,29.936,48.348,29.783,48.347,29.72,48.34,29.695,48.275,29.624]],[[48.442,28.543,48.269,28.541,48.05,28.538,47.872,28.535,47.671,28.533,47.583,28.628,47.553,28.732,47.521,28.838,47.433,28.99,47.139,29.026,46.982,29.046,46.725,29.075,46.531,29.096,46.694,29.26,46.769,29.347,46.906,29.538,46.976,29.673,47.044,29.823,47.102,29.94,47.114,29.961,47.148,30.001,47.223,30.042,47.331,30.08,47.515,30.096,47.644,30.097,47.673,30.096,47.754,30.077,47.979,29.983,47.974,29.946,48.006,29.836,48.077,29.716,48.136,29.618,48.143,29.572,48.089,29.579,48.048,29.598,47.97,29.617,47.817,29.487,47.725,29.417,47.723,29.393,47.845,29.366,47.935,29.367,47.998,29.386,48.051,29.355,48.086,29.275,48.1,29.211,48.184,28.979,48.253,28.901,48.339,28.763,48.371,28.692,48.39,28.632,48.442,28.543]]]},{"n":"Kosovo","c":"XK","r":6,"ml":5,"lx":20.86,"ly":42.59,"b":[20.029,41.854,21.753,43.261],"p":[[[20.344,42.828,20.469,42.858,20.487,42.879,20.458,42.925,20.475,42.953,20.624,43.034,20.649,43.071,20.658,43.1,20.638,43.13,20.61,43.178,20.623,43.199,20.701,43.226,20.763,43.259,20.801,43.261,20.824,43.238,20.824,43.214,20.844,43.173,20.891,43.152,20.968,43.116,21.057,43.092,21.127,43.043,21.223,42.956,21.237,42.913,21.323,42.875,21.403,42.832,21.391,42.751,21.663,42.681,21.724,42.682,21.753,42.67,21.752,42.652,21.731,42.595,21.619,42.423,21.61,42.387,21.53,42.35,21.519,42.328,21.542,42.281,21.563,42.248,21.561,42.248,21.39,42.22,21.332,42.187,21.298,42.13,21.287,42.1,21.256,42.1,21.206,42.129,21.142,42.175,21.06,42.171,20.778,42.071,20.75,42.018,20.744,41.904,20.725,41.874,20.695,41.854,20.579,41.866,20.566,41.874,20.581,41.917,20.575,42.013,20.523,42.171,20.485,42.223,20.408,42.275,20.348,42.309,20.241,42.339,20.186,42.426,20.104,42.525,20.064,42.547,20.07,42.557,20.089,42.632,20.066,42.686,20.029,42.732,20.054,42.76,20.13,42.76,20.193,42.755,20.215,42.799,20.344,42.828]]]},{"n":"Kiribati","c":"KI","r":6,"ml":5,"lx":-157.38,"ly":1.82,"b":[-174.541,-11.457,174.779,3.924],"p":[[[172.97,3.129,172.906,3.096,172.887,3.101,172.963,3.149,172.962,3.143,172.97,3.129]],[[172.844,3.051,172.77,3.013,172.75,3.033,172.777,3.034,172.827,3.071,172.887,3.074,172.88,3.054,172.844,3.051]],[[173.019,1.846,173.024,1.823,172.967,1.885,172.933,1.926,172.935,1.944,172.95,1.933,172.969,1.913,172.982,1.897,173.019,1.846]],[[173.029,1.717,172.993,1.713,173.02,1.727,173.028,1.747,173.024,1.809,173.038,1.804,173.043,1.779,173.045,1.742,173.029,1.717]],[[173.033,1.013,173.087,0.973,173.079,0.946,173.061,0.915,172.991,0.835,172.97,0.843,173.039,0.915,173.065,0.963,173.026,0.999,173.01,0.991,173.004,0.991,172.99,1.025,173.004,1.025,173.033,1.013]],[[174.509,-0.802,174.476,-0.829,174.464,-0.804,174.48,-0.774,174.453,-0.647,174.408,-0.63,174.381,-0.592,174.394,-0.592,174.439,-0.627,174.475,-0.642,174.495,-0.726,174.509,-0.802]],[[174.773,-1.212,174.779,-1.263,174.756,-1.256,174.748,-1.236,174.741,-1.185,174.717,-1.134,174.744,-1.147,174.767,-1.187,174.773,-1.212]],[[173.038,1.342,173.011,1.338,173.029,1.359,173.143,1.381,173.153,1.388,173.172,1.375,173.171,1.363,173.163,1.358,173.106,1.357,173.062,1.346,173.038,1.342]],[[169.551,-0.874,169.542,-0.876,169.523,-0.866,169.526,-0.853,169.539,-0.847,169.555,-0.857,169.551,-0.874]],[[-172.215,-4.511,-172.208,-4.518,-172.194,-4.516,-172.181,-4.515,-172.189,-4.522,-172.215,-4.524,-172.228,-4.507,-172.212,-4.494,-172.198,-4.492,-172.196,-4.495,-172.197,-4.5,-172.204,-4.5,-172.215,-4.503,-172.215,-4.511]],[[-171.085,-3.135,-171.09,-3.143,-171.097,-3.137,-171.092,-3.125,-171.088,-3.115,-171.081,-3.12,-171.085,-3.135]],[[-171.233,-4.463,-171.243,-4.468,-171.255,-4.467,-171.262,-4.46,-171.262,-4.449,-171.252,-4.442,-171.239,-4.444,-171.232,-4.454,-171.233,-4.463]],[[-174.513,-4.675,-174.501,-4.688,-174.501,-4.695,-174.507,-4.694,-174.524,-4.69,-174.529,-4.682,-174.517,-4.687,-174.511,-4.686,-174.523,-4.674,-174.533,-4.665,-174.541,-4.662,-174.541,-4.657,-174.531,-4.659,-174.513,-4.675]],[[-154.956,-4.088,-154.959,-4.094,-154.971,-4.086,-154.995,-4.071,-155.015,-4.055,-155.015,-4.048,-154.987,-4.039,-154.951,-4.031,-154.943,-4.042,-154.95,-4.056,-154.956,-4.088]],[[-151.783,-11.441,-151.791,-11.457,-151.807,-11.451,-151.816,-11.431,-151.819,-11.409,-151.813,-11.392,-151.803,-11.393,-151.791,-11.414,-151.783,-11.441]],[[-155.864,-5.627,-155.887,-5.632,-155.914,-5.632,-155.928,-5.619,-155.929,-5.608,-155.919,-5.608,-155.911,-5.609,-155.872,-5.611,-155.862,-5.619,-155.864,-5.627]],[[-157.342,1.856,-157.176,1.74,-157.246,1.732,-157.42,1.788,-157.579,1.902,-157.531,1.927,-157.508,1.886,-157.436,1.847,-157.393,1.928,-157.365,1.946,-157.492,2.029,-157.442,2.025,-157.322,1.969,-157.342,1.856]],[[-159.339,3.924,-159.259,3.839,-159.275,3.797,-159.332,3.8,-159.359,3.815,-159.314,3.823,-159.306,3.838,-159.327,3.863,-159.354,3.881,-159.373,3.881,-159.378,3.847,-159.409,3.873,-159.391,3.9,-159.369,3.917,-159.339,3.924]],[[-171.698,-2.766,-171.665,-2.786,-171.64,-2.811,-171.628,-2.847,-171.628,-2.856,-171.647,-2.856,-171.671,-2.844,-171.687,-2.83,-171.696,-2.826,-171.698,-2.822,-171.678,-2.825,-171.655,-2.84,-171.639,-2.847,-171.64,-2.829,-171.66,-2.799,-171.673,-2.788,-171.688,-2.779,-171.706,-2.773,-171.718,-2.779,-171.725,-2.781,-171.728,-2.774,-171.725,-2.768,-171.719,-2.761,-171.698,-2.766]]]},{"n":"Kenya","c":"KE","r":2,"ml":1.7,"lx":37.91,"ly":0.55,"b":[33.9,-4.692,41.884,5.492],"p":[[[40.994,-2.158,40.957,-2.167,40.976,-2.11,41.086,-2.037,41.131,-2.053,41.139,-2.07,41.137,-2.085,41.118,-2.1,40.994,-2.158]],[[33.903,-1.002,33.9,-0.832,33.924,-0.398,33.921,-0.017,33.943,0.174,34.037,0.295,34.081,0.382,34.112,0.505,34.161,0.605,34.273,0.686,34.293,0.731,34.411,0.867,34.482,1.042,34.535,1.102,34.602,1.156,34.649,1.185,34.727,1.214,34.788,1.231,34.799,1.245,34.804,1.273,34.784,1.381,34.81,1.417,34.851,1.489,34.898,1.556,34.941,1.599,34.965,1.643,34.976,1.72,34.978,1.774,34.978,1.862,34.964,2.062,34.914,2.23,34.883,2.418,34.906,2.48,34.866,2.59,34.847,2.596,34.814,2.62,34.773,2.723,34.742,2.818,34.723,2.842,34.589,2.925,34.523,3.12,34.448,3.163,34.407,3.358,34.399,3.413,34.442,3.606,34.438,3.651,34.393,3.692,34.267,3.733,34.165,3.813,34.178,3.841,34.186,3.87,34.132,3.889,33.976,4.22,34.177,4.419,34.38,4.621,34.64,4.875,34.878,5.11,35.084,5.312,35.268,5.492,35.264,5.458,35.265,5.412,35.288,5.384,35.325,5.365,35.378,5.385,35.424,5.413,35.469,5.419,35.745,5.344,35.791,5.279,35.788,5.208,35.8,5.157,35.779,5.106,35.756,4.95,35.763,4.808,35.846,4.703,35.92,4.62,35.979,4.504,36.022,4.468,36.082,4.45,36.272,4.445,36.553,4.437,36.824,4.43,36.848,4.427,36.906,4.411,37.155,4.255,37.383,4.111,37.575,3.986,37.763,3.865,37.945,3.747,38.086,3.649,38.225,3.619,38.452,3.605,38.608,3.6,38.753,3.559,38.968,3.521,39.128,3.501,39.225,3.479,39.494,3.456,39.539,3.469,39.658,3.578,39.79,3.754,39.842,3.851,40.014,3.948,40.316,4.083,40.529,4.178,40.765,4.273,40.873,4.19,41.021,4.057,41.087,3.992,41.14,3.963,41.221,3.944,41.319,3.943,41.372,3.946,41.482,3.963,41.738,3.979,41.884,3.978,41.761,3.802,41.613,3.59,41.342,3.202,41.135,2.997,40.979,2.842,40.964,2.815,40.965,2.642,40.967,2.221,40.97,1.378,40.973,0.535,40.977,-0.307,40.978,-0.729,40.979,-0.87,41.116,-1.047,41.25,-1.221,41.427,-1.45,41.522,-1.572,41.538,-1.613,41.533,-1.695,41.387,-1.867,41.267,-1.945,41.107,-1.982,41.059,-1.975,40.996,-1.951,40.971,-1.992,40.952,-2.056,40.917,-2.042,40.89,-2.024,40.906,-2.137,40.922,-2.194,40.898,-2.27,40.82,-2.336,40.813,-2.392,40.644,-2.539,40.404,-2.556,40.279,-2.629,40.222,-2.688,40.18,-2.819,40.195,-3.019,40.128,-3.173,40.115,-3.251,39.992,-3.351,39.937,-3.442,39.896,-3.536,39.861,-3.577,39.819,-3.786,39.761,-3.913,39.746,-3.955,39.732,-3.993,39.687,-4.068,39.658,-4.119,39.637,-4.153,39.491,-4.478,39.377,-4.625,39.288,-4.609,39.228,-4.666,39.222,-4.692,39.19,-4.677,39.115,-4.624,38.962,-4.513,38.808,-4.402,38.655,-4.292,38.501,-4.181,38.348,-4.071,38.194,-3.96,38.041,-3.85,37.887,-3.739,37.797,-3.674,37.757,-3.636,37.726,-3.56,37.711,-3.541,37.67,-3.517,37.622,-3.512,37.608,-3.497,37.609,-3.46,37.625,-3.407,37.682,-3.306,37.688,-3.246,37.677,-3.178,37.659,-3.07,37.644,-3.045,37.542,-2.989,37.329,-2.87,37.116,-2.751,36.903,-2.632,36.689,-2.513,36.476,-2.394,36.263,-2.275,36.05,-2.156,35.837,-2.037,35.624,-1.918,35.411,-1.799,35.197,-1.68,34.984,-1.561,34.771,-1.442,34.558,-1.323,34.345,-1.204,34.132,-1.085,34.052,-1.04,33.979,-1.002,33.903,-1.002]]]},{"n":"Kazakhstan","c":"KZ","r":3,"ml":2.7,"lx":68.69,"ly":49.05,"b":[46.609,40.609,87.323,55.39],"p":[[[50.184,44.855,50.149,44.826,50.095,44.831,49.995,44.937,50.023,45.045,50.059,45.067,50.11,45.082,50.117,45.058,50.045,45.01,50.039,44.949,50.098,44.882,50.184,44.855]],[[50.312,44.972,50.277,44.959,50.256,45.022,50.295,45.076,50.35,45.083,50.331,44.998,50.312,44.972]],[[52.682,45.412,52.665,45.401,52.598,45.428,52.554,45.474,52.609,45.528,52.66,45.518,52.693,45.461,52.682,45.412]],[[70.947,42.249,70.86,42.207,70.765,42.194,70.715,42.169,70.663,42.107,70.613,42.055,70.584,42.036,70.54,42.039,70.489,42.08,70.416,42.079,70.329,42.028,70.226,41.946,70.096,41.821,69.96,41.754,69.788,41.697,69.664,41.672,69.565,41.629,69.401,41.542,69.368,41.491,69.249,41.46,69.154,41.425,69.065,41.367,69.043,41.264,68.987,41.205,68.851,41.124,68.737,41.042,68.663,40.962,68.584,40.876,68.559,40.829,68.557,40.765,68.594,40.711,68.601,40.66,68.573,40.623,68.496,40.609,68.415,40.619,68.292,40.656,68.16,40.722,68.112,40.754,68.048,40.809,68.057,40.861,68.09,40.96,68.113,41.029,68.059,41.061,68.02,41.096,67.991,41.13,67.936,41.197,67.866,41.18,67.805,41.164,67.735,41.187,67.528,41.177,67.372,41.17,67.225,41.162,67.039,41.153,66.814,41.142,66.75,41.157,66.71,41.179,66.669,41.271,66.645,41.349,66.602,41.494,66.573,41.607,66.538,41.741,66.515,41.889,66.499,41.995,66.329,41.998,66.193,42.001,66.01,42.005,66.011,42.089,66.013,42.194,66.016,42.315,66.05,42.473,66.063,42.605,66.079,42.767,66.089,42.873,66.1,42.991,66.006,42.955,65.901,42.915,65.803,42.877,65.736,42.972,65.67,43.065,65.571,43.205,65.496,43.311,65.367,43.372,65.271,43.418,65.171,43.494,65.085,43.574,65.003,43.649,64.905,43.715,64.812,43.694,64.706,43.653,64.604,43.613,64.496,43.572,64.443,43.551,64.318,43.559,64.209,43.566,64.013,43.578,63.848,43.588,63.68,43.599,63.445,43.613,63.207,43.628,63.048,43.608,62.846,43.584,62.634,43.558,62.459,43.537,62.238,43.51,62.072,43.489,61.99,43.492,61.888,43.577,61.723,43.714,61.624,43.796,61.526,43.877,61.385,43.994,61.271,44.082,61.161,44.169,61.097,44.248,61.065,44.348,61.008,44.394,60.879,44.455,60.741,44.521,60.603,44.587,60.465,44.652,60.327,44.718,60.188,44.784,60.05,44.85,59.912,44.916,59.774,44.981,59.636,45.047,59.498,45.113,59.36,45.179,59.221,45.244,59.083,45.31,58.945,45.376,58.807,45.442,58.669,45.508,58.555,45.555,58.449,45.543,58.291,45.509,58.125,45.474,57.961,45.44,57.667,45.377,57.477,45.337,57.329,45.304,57.172,45.268,56.965,45.221,56.792,45.181,56.589,45.135,56.409,45.094,56.258,45.059,56.1,45.023,55.976,44.995,55.976,44.765,55.976,44.536,55.976,44.306,55.976,44.077,55.976,43.847,55.976,43.618,55.976,43.388,55.977,43.159,55.977,42.929,55.977,42.7,55.977,42.47,55.977,42.24,55.977,42.011,55.977,41.781,55.977,41.552,55.977,41.322,55.935,41.324,55.839,41.311,55.679,41.279,55.545,41.263,55.487,41.272,55.434,41.296,55.388,41.347,55.32,41.408,55.25,41.458,55.162,41.56,55.102,41.639,54.952,41.81,54.932,41.864,54.904,41.919,54.854,41.965,54.678,42.078,54.473,42.18,54.272,42.28,54.215,42.304,54.121,42.335,54.005,42.336,53.926,42.33,53.685,42.297,53.501,42.258,53.25,42.206,53.056,42.148,53.013,42.131,52.871,42.061,52.697,41.944,52.494,41.78,52.468,41.886,52.459,42.048,52.462,42.101,52.517,42.237,52.573,42.331,52.618,42.428,52.638,42.556,52.597,42.76,52.55,42.805,52.494,42.82,52.434,42.824,52.324,42.816,52.273,42.8,52.184,42.869,52.076,42.88,52.019,42.861,51.961,42.851,51.898,42.87,51.844,42.91,51.811,42.954,51.785,43.004,51.7,43.104,51.616,43.158,51.514,43.171,51.348,43.167,51.295,43.174,51.292,43.231,51.313,43.356,51.314,43.421,51.302,43.482,51.274,43.533,51.239,43.577,51.14,43.649,51.065,43.75,50.94,43.959,50.831,44.193,50.783,44.228,50.685,44.265,50.472,44.295,50.331,44.325,50.276,44.355,50.253,44.406,50.253,44.462,50.265,44.527,50.297,44.582,50.409,44.624,50.652,44.633,50.86,44.629,51.049,44.53,51.111,44.508,51.177,44.501,51.311,44.532,51.377,44.541,51.544,44.531,51.494,44.578,51.431,44.602,51.366,44.6,51.31,44.619,51.218,44.709,51.058,44.812,51.021,44.854,51.009,44.922,51.04,44.98,51.154,45.04,51.25,45.122,51.294,45.23,51.333,45.28,51.416,45.358,51.54,45.343,51.733,45.399,52.049,45.388,52.427,45.405,52.531,45.399,52.772,45.344,52.911,45.32,53.079,45.308,53.2,45.332,53.086,45.407,52.838,45.497,52.774,45.573,52.888,45.78,53.042,45.968,53.135,46.192,53.109,46.414,53.064,46.475,53.079,46.547,53.132,46.608,53.17,46.669,53.138,46.742,53.069,46.856,53.035,46.893,52.916,46.954,52.678,46.957,52.483,46.991,52.42,46.964,52.385,46.922,52.34,46.895,52.189,46.84,52.138,46.829,52.086,46.84,52.011,46.902,51.945,46.895,51.745,46.934,51.65,47.018,51.615,47.03,51.291,47.097,51.178,47.11,50.92,47.041,50.733,46.952,50.68,46.939,50.583,46.882,50.528,46.873,50.472,46.883,50.419,46.879,50.306,46.795,50.102,46.696,50,46.634,49.886,46.596,49.761,46.571,49.632,46.568,49.584,46.545,49.437,46.537,49.347,46.519,49.344,46.486,49.362,46.41,49.286,46.437,49.206,46.386,49.232,46.337,49.184,46.349,48.959,46.442,48.774,46.508,48.61,46.566,48.586,46.577,48.541,46.606,48.509,46.65,48.502,46.699,48.519,46.734,48.558,46.757,48.605,46.766,48.647,46.759,48.694,46.737,48.776,46.71,48.884,46.705,48.95,46.726,48.959,46.775,48.832,46.955,48.714,47.1,48.601,47.262,48.553,47.321,48.413,47.456,48.276,47.59,48.167,47.709,48.11,47.745,47.935,47.761,47.6,47.79,47.482,47.804,47.387,47.769,47.292,47.741,47.202,47.792,47.131,47.877,47.093,47.948,47.112,48.02,47.119,48.127,47.065,48.232,47.004,48.284,46.853,48.324,46.661,48.412,46.609,48.574,46.703,48.806,46.853,48.97,46.962,49.038,47.014,49.098,47.031,49.15,47.018,49.2,46.953,49.253,46.853,49.304,46.802,49.367,46.823,49.502,46.89,49.697,46.992,49.853,47.13,49.939,47.248,50.001,47.295,50.058,47.298,50.14,47.295,50.217,47.326,50.274,47.376,50.318,47.429,50.358,47.504,50.403,47.6,50.414,47.706,50.378,47.85,50.282,48.061,50.094,48.181,49.97,48.225,49.932,48.335,49.858,48.434,49.829,48.6,49.875,48.759,49.928,48.81,49.962,48.843,50.013,48.818,50.1,48.785,50.156,48.749,50.228,48.7,50.354,48.666,50.55,48.625,50.613,48.655,50.62,48.735,50.607,48.808,50.601,48.914,50.645,49.059,50.726,49.323,50.852,49.379,50.935,49.425,51.027,49.498,51.084,49.666,51.102,49.822,51.132,49.932,51.197,50.105,51.255,50.247,51.29,50.309,51.322,50.354,51.37,50.516,51.506,50.644,51.589,50.756,51.675,50.794,51.729,50.882,51.719,51.018,51.682,51.163,51.647,51.27,51.594,51.291,51.54,51.301,51.497,51.345,51.475,51.396,51.471,51.473,51.482,51.609,51.484,51.775,51.554,52.007,51.673,52.219,51.709,52.331,51.681,52.423,51.594,52.496,51.512,52.571,51.482,52.618,51.481,52.635,51.48,52.728,51.498,52.735,51.498,52.821,51.495,52.903,51.467,53.038,51.464,53.227,51.485,53.247,51.494,53.338,51.482,53.449,51.445,53.535,51.4,53.688,51.252,53.776,51.214,53.957,51.161,54.042,51.115,54.14,51.041,54.191,50.996,54.298,50.914,54.421,50.78,54.443,50.674,54.471,50.584,54.517,50.541,54.555,50.536,54.596,50.551,54.636,50.592,54.65,50.66,54.638,50.781,54.606,50.88,54.566,50.911,54.546,50.946,54.573,50.99,54.642,51.012,54.727,50.998,54.868,50.941,55.015,50.87,55.195,50.745,55.361,50.665,55.542,50.602,55.686,50.583,55.798,50.602,55.929,50.654,56.05,50.714,56.104,50.776,56.144,50.845,56.326,50.936,56.491,51.02,56.567,51.004,56.62,50.981,56.79,51.032,56.85,51.046,57.012,51.065,57.179,51.036,57.313,50.947,57.442,50.889,57.558,50.896,57.654,50.925,57.717,50.981,57.765,51.047,57.829,51.089,57.839,51.092,58.045,51.069,58.175,51.072,58.188,51.082,58.359,51.064,58.547,50.971,58.665,50.868,58.814,50.737,58.884,50.694,58.985,50.676,59.064,50.668,59.171,50.648,59.452,50.62,59.495,50.604,59.524,50.583,59.498,50.511,59.523,50.493,59.751,50.544,59.812,50.582,59.888,50.69,59.955,50.799,60.005,50.84,60.059,50.85,60.112,50.834,60.187,50.77,60.288,50.704,60.425,50.679,60.508,50.669,60.638,50.664,60.942,50.696,61.227,50.775,61.389,50.861,61.465,50.99,61.512,51.137,61.585,51.23,61.555,51.325,61.411,51.415,61.363,51.442,61.015,51.492,60.993,51.529,60.974,51.537,60.63,51.617,60.465,51.651,60.418,51.704,60.388,51.773,60.28,51.835,60.067,51.891,60.03,51.933,60.066,51.976,60.234,52.025,60.425,52.126,60.499,52.146,60.67,52.151,60.828,52.233,60.938,52.281,60.995,52.337,60.979,52.395,60.821,52.57,60.774,52.676,60.802,52.745,60.893,52.819,60.945,52.86,61.007,52.933,61.047,52.972,61.207,52.989,61.401,52.996,61.534,52.979,61.719,52.969,61.889,52.956,61.974,52.944,62.037,52.966,62.083,53.005,62.081,53.057,62.015,53.108,61.766,53.174,61.66,53.228,61.576,53.222,61.437,53.239,61.311,53.275,61.199,53.287,61.163,53.337,61.186,53.406,61.229,53.446,61.312,53.466,61.401,53.456,61.499,53.485,61.527,53.502,61.535,53.523,61.519,53.554,61.474,53.58,61.41,53.587,61.336,53.565,61.248,53.551,61.099,53.583,60.979,53.622,60.986,53.657,61.074,53.71,61.113,53.753,61.113,53.813,61.113,53.882,61.144,53.964,61.231,54.019,61.334,54.049,61.598,53.995,61.929,53.946,61.986,53.954,62.002,53.98,62.04,54.003,62.499,54.013,62.588,54.044,62.633,54.069,63.074,54.105,63.127,54.139,63.191,54.171,63.293,54.17,63.414,54.183,63.582,54.222,63.701,54.243,63.721,54.245,63.847,54.236,64.004,54.267,64.037,54.28,64.063,54.303,64.199,54.347,64.461,54.384,64.525,54.362,64.65,54.352,64.809,54.369,64.927,54.397,64.995,54.369,65.088,54.34,65.158,54.364,65.192,54.441,65.237,54.516,65.316,54.552,65.378,54.564,65.434,54.593,65.477,54.623,65.708,54.619,65.914,54.693,65.955,54.66,66.223,54.667,66.555,54.715,66.754,54.738,67.098,54.788,67.257,54.829,67.485,54.854,67.693,54.872,67.83,54.944,67.94,54.954,68.074,54.96,68.156,54.977,68.209,55.003,68.244,55.052,68.225,55.115,68.206,55.161,68.302,55.187,68.438,55.194,68.525,55.205,68.713,55.308,68.843,55.358,68.977,55.39,69.247,55.373,69.493,55.357,69.74,55.307,69.87,55.246,69.982,55.199,70.087,55.177,70.182,55.162,70.293,55.184,70.371,55.212,70.417,55.253,70.486,55.282,70.738,55.305,70.79,55.261,70.91,55.128,70.992,54.95,71.126,54.715,71.186,54.599,71.159,54.539,71.16,54.455,71.152,54.364,71.053,54.26,71.093,54.212,71.336,54.158,71.677,54.178,71.887,54.221,72.004,54.206,72.066,54.232,72.105,54.308,72.186,54.326,72.269,54.272,72.329,54.181,72.387,54.123,72.383,54.054,72.404,53.964,72.447,53.942,72.53,53.976,72.586,53.996,72.599,54.023,72.576,54.056,72.564,54.09,72.583,54.122,72.622,54.134,72.741,54.125,72.914,54.107,73.119,53.981,73.23,53.958,73.277,53.956,73.381,53.963,73.506,53.999,73.59,54.045,73.618,54.067,73.666,54.063,73.712,54.042,73.716,53.996,73.679,53.929,73.554,53.868,73.399,53.811,73.306,53.707,73.286,53.598,73.327,53.543,73.362,53.506,73.372,53.454,73.407,53.448,73.47,53.469,73.643,53.576,73.731,53.603,73.859,53.62,74.069,53.611,74.21,53.576,74.277,53.528,74.352,53.488,74.403,53.504,74.429,53.551,74.43,53.604,74.452,53.647,74.681,53.754,74.834,53.826,74.887,53.834,74.989,53.819,75.052,53.827,75.22,53.894,75.377,53.97,75.392,54.022,75.398,54.069,75.437,54.09,75.657,54.106,75.693,54.115,75.881,54.168,76.141,54.259,76.267,54.312,76.496,54.336,76.539,54.351,76.616,54.387,76.759,54.437,76.837,54.442,76.789,54.322,76.703,54.182,76.655,54.145,76.422,54.152,76.422,54.114,76.459,54.055,76.485,54.023,76.513,53.993,76.576,53.943,76.821,53.823,77.132,53.67,77.469,53.499,77.704,53.379,77.799,53.317,77.86,53.269,78.033,53.095,78.198,52.93,78.475,52.638,78.721,52.357,78.992,52.047,79.149,51.868,79.469,51.493,79.554,51.378,79.716,51.16,79.86,50.955,79.986,50.775,80.066,50.758,80.072,50.807,80.086,50.84,80.127,50.858,80.22,50.912,80.27,50.925,80.345,50.919,80.424,50.946,80.452,50.998,80.434,51.093,80.421,51.136,80.448,51.183,80.491,51.202,80.551,51.217,80.605,51.224,80.65,51.277,80.735,51.293,80.813,51.283,80.877,51.281,80.934,51.243,80.966,51.19,81.027,51.186,81.127,51.191,81.141,51.147,81.112,51.072,81.078,51.015,81.071,50.969,81.125,50.946,81.319,50.966,81.388,50.956,81.41,50.91,81.438,50.871,81.452,50.824,81.431,50.771,81.466,50.74,81.634,50.739,81.752,50.764,81.934,50.766,82.098,50.711,82.212,50.719,82.326,50.742,82.494,50.728,82.612,50.771,82.693,50.826,82.719,50.869,82.761,50.893,82.919,50.893,83.019,50.897,83.093,50.961,83.16,50.989,83.274,50.995,83.357,50.995,83.581,50.936,83.718,50.887,83.86,50.818,83.945,50.775,84.002,50.677,84.099,50.605,84.176,50.521,84.195,50.437,84.258,50.288,84.323,50.239,84.401,50.239,84.499,50.219,84.607,50.202,84.839,50.091,84.924,50.088,84.989,50.061,85,50.01,84.975,49.951,85.001,49.894,85.076,49.822,85.137,49.751,85.21,49.665,85.233,49.616,85.292,49.599,85.372,49.624,85.498,49.605,85.88,49.557,85.934,49.55,85.974,49.499,86.03,49.503,86.093,49.505,86.181,49.499,86.242,49.546,86.292,49.588,86.418,49.638,86.522,49.708,86.61,49.769,86.675,49.777,86.729,49.749,86.731,49.696,86.665,49.657,86.614,49.61,86.626,49.563,86.714,49.559,86.812,49.488,86.953,49.322,87.001,49.287,87.071,49.255,87.148,49.24,87.234,49.216,87.297,49.148,87.323,49.086,87.23,49.106,87.049,49.11,86.938,49.098,86.886,49.091,86.808,49.05,86.753,49.009,86.729,48.939,86.758,48.861,86.718,48.697,86.664,48.636,86.549,48.529,86.483,48.505,86.373,48.486,86.266,48.455,86.056,48.424,85.83,48.408,85.749,48.385,85.692,48.312,85.652,48.251,85.626,48.204,85.562,48.052,85.526,47.916,85.562,47.746,85.588,47.558,85.587,47.494,85.642,47.397,85.67,47.338,85.657,47.255,85.577,47.188,85.53,47.101,85.485,47.064,85.355,47.047,85.233,47.036,85.111,46.961,85.012,46.909,84.858,46.843,84.786,46.831,84.746,46.864,84.72,46.939,84.667,46.972,84.592,46.975,84.532,46.976,84.339,46.996,84.215,46.995,84.122,46.979,84.016,46.971,83.833,46.998,83.714,47.021,83.634,47.043,83.444,47.109,83.193,47.187,83.09,47.209,83.029,47.186,83.02,47.141,83.004,47.033,82.975,46.966,82.8,46.624,82.692,46.387,82.555,46.159,82.512,46.006,82.43,45.812,82.348,45.672,82.315,45.595,82.312,45.564,82.327,45.52,82.452,45.472,82.583,45.443,82.612,45.424,82.626,45.374,82.621,45.293,82.597,45.216,82.559,45.155,82.521,45.125,82.479,45.124,82.397,45.162,82.323,45.206,82.267,45.219,82.123,45.195,81.989,45.162,81.945,45.161,81.867,45.182,81.79,45.226,81.759,45.311,81.692,45.349,81.602,45.311,81.335,45.246,81.04,45.169,80.853,45.129,80.78,45.136,80.635,45.127,80.509,45.105,80.415,45.075,80.228,45.034,80.059,45.006,79.95,44.944,79.872,44.884,79.875,44.861,79.932,44.825,79.997,44.797,80.128,44.804,80.255,44.808,80.361,44.77,80.455,44.746,80.482,44.715,80.455,44.684,80.401,44.677,80.381,44.655,80.391,44.627,80.355,44.552,80.336,44.438,80.355,44.327,80.365,44.223,80.359,44.171,80.355,44.097,80.396,44.047,80.432,43.952,80.496,43.892,80.593,43.685,80.651,43.564,80.704,43.427,80.665,43.353,80.668,43.31,80.73,43.274,80.757,43.204,80.786,43.162,80.778,43.119,80.751,43.102,80.617,43.128,80.507,43.086,80.39,43.043,80.375,43.02,80.371,42.996,80.383,42.974,80.451,42.936,80.544,42.912,80.539,42.873,80.424,42.856,80.25,42.797,80.202,42.734,80.165,42.666,80.162,42.626,80.179,42.518,80.206,42.399,80.255,42.274,80.259,42.235,80.233,42.208,80.209,42.19,80.071,42.303,79.921,42.413,79.803,42.438,79.598,42.457,79.49,42.458,79.428,42.483,79.368,42.547,79.296,42.605,79.203,42.666,79.165,42.759,79.127,42.776,79.06,42.764,78.948,42.767,78.885,42.775,78.792,42.791,78.642,42.829,78.524,42.865,78.376,42.871,78.29,42.864,78.023,42.858,77.802,42.895,77.622,42.902,77.512,42.9,77.459,42.905,77.369,42.904,77.236,42.913,77.057,42.971,76.988,42.974,76.944,42.971,76.646,42.929,76.509,42.919,76.218,42.924,75.932,42.929,75.84,42.938,75.79,42.933,75.682,42.83,75.636,42.815,75.366,42.837,75.048,42.904,74.818,42.978,74.622,43.056,74.364,43.179,74.209,43.24,74.187,43.205,74.146,43.194,74.086,43.189,73.949,43.195,73.886,43.133,73.719,43.088,73.612,43.048,73.556,43.003,73.45,42.703,73.422,42.594,73.493,42.409,73.412,42.42,73.316,42.467,73.283,42.504,73.191,42.527,72.855,42.561,72.792,42.603,72.753,42.638,72.666,42.66,72.543,42.678,72.276,42.758,72.162,42.761,71.817,42.822,71.761,42.821,71.735,42.819,71.601,42.779,71.514,42.767,71.422,42.783,71.257,42.734,71.167,42.667,71.094,42.587,71.023,42.535,71.002,42.459,70.952,42.419,70.893,42.34,70.893,42.294,70.947,42.249]]]},{"n":"Jordan","c":"JO","r":4,"ml":4,"lx":36.38,"ly":30.81,"b":[34.951,29.19,39.293,33.372],"p":[[[35.787,32.735,35.895,32.714,35.956,32.667,36.059,32.534,36.22,32.495,36.284,32.457,36.372,32.387,36.479,32.361,36.818,32.317,37.089,32.466,37.318,32.591,37.577,32.733,37.754,32.83,38.056,32.995,38.254,33.099,38.516,33.237,38.774,33.372,38.845,33.151,38.915,32.935,38.987,32.711,39.058,32.493,38.982,32.473,39.041,32.306,39.14,32.331,39.247,32.351,39.293,32.244,39.145,32.125,38.997,32.007,38.962,31.995,38.77,31.946,38.375,31.847,38.111,31.781,37.774,31.696,37.493,31.626,37.216,31.556,36.959,31.492,37.105,31.355,37.329,31.147,37.479,31.008,37.655,30.829,37.813,30.669,37.98,30.5,37.863,30.443,37.67,30.348,37.65,30.331,37.634,30.313,37.554,30.145,37.491,30.012,37.469,29.995,37.199,29.946,36.927,29.897,36.755,29.866,36.704,29.832,36.592,29.666,36.476,29.495,36.283,29.355,36.068,29.201,36.015,29.19,35.86,29.214,35.595,29.255,35.339,29.294,35.164,29.321,34.951,29.354,34.982,29.484,34.973,29.555,35.024,29.787,35.053,29.897,35.068,29.978,35.142,30.142,35.133,30.195,35.148,30.384,35.141,30.421,35.174,30.524,35.237,30.673,35.298,30.802,35.32,30.86,35.383,30.982,35.439,31.132,35.41,31.214,35.401,31.231,35.424,31.325,35.423,31.325,35.451,31.479,35.465,31.562,35.499,31.672,35.559,31.766,35.531,31.985,35.535,32.103,35.572,32.238,35.551,32.396,35.569,32.62,35.573,32.641,35.595,32.668,35.611,32.682,35.734,32.729,35.787,32.735]]]},{"n":"Japan","c":"JP","r":2,"ml":1.7,"lx":138.44,"ly":36.14,"b":[123.68,24.266,145.833,45.51],"p":[[[133.371,36.204,133.325,36.167,133.239,36.179,133.19,36.233,133.206,36.293,133.296,36.34,133.381,36.246,133.371,36.204]],[[138.344,37.822,138.249,37.82,138.225,37.829,138.283,37.854,138.288,37.896,138.322,37.97,138.322,37.991,138.246,37.995,138.25,38.078,138.306,38.161,138.461,38.291,138.504,38.316,138.51,38.259,138.463,38.124,138.454,38.076,138.575,38.066,138.497,37.904,138.344,37.822]],[[134.933,34.288,134.824,34.203,134.731,34.209,134.683,34.247,134.668,34.294,134.757,34.368,134.834,34.473,134.904,34.519,134.961,34.545,135.005,34.544,134.905,34.398,134.933,34.288]],[[130.623,30.263,130.508,30.241,130.446,30.265,130.388,30.388,130.497,30.466,130.644,30.389,130.673,30.367,130.623,30.263]],[[130.96,30.397,130.872,30.386,130.87,30.444,130.94,30.575,130.947,30.671,131.012,30.792,131.04,30.819,131.06,30.828,131.083,30.791,131.057,30.642,130.993,30.53,130.96,30.397]],[[130.083,32.23,130.004,32.194,129.993,32.228,129.96,32.244,130.017,32.292,130.015,32.314,129.979,32.346,130.021,32.469,130.01,32.522,130.168,32.541,130.197,32.492,130.2,32.341,130.083,32.23]],[[129.279,34.123,129.214,34.083,129.186,34.145,129.215,34.321,129.337,34.285,129.335,34.231,129.279,34.123]],[[129.386,34.354,129.365,34.306,129.297,34.34,129.267,34.37,129.329,34.522,129.322,34.579,129.326,34.607,129.451,34.687,129.472,34.671,129.48,34.649,129.469,34.616,129.475,34.54,129.381,34.416,129.386,34.354]],[[128.665,32.784,128.704,32.757,128.761,32.772,128.806,32.776,128.839,32.763,128.879,32.693,128.895,32.652,128.821,32.646,128.79,32.637,128.75,32.586,128.693,32.605,128.657,32.628,128.649,32.662,128.665,32.784]],[[130.381,32.424,130.293,32.419,130.256,32.431,130.242,32.463,130.365,32.527,130.461,32.516,130.419,32.458,130.381,32.424]],[[141.073,45.333,141.034,45.269,140.982,45.364,140.972,45.465,141.002,45.465,141.057,45.45,141.07,45.4,141.073,45.333]],[[141.295,45.119,141.226,45.112,141.145,45.154,141.135,45.206,141.194,45.248,141.252,45.232,141.31,45.179,141.329,45.15,141.295,45.119]],[[139.481,42.081,139.458,42.076,139.435,42.084,139.412,42.16,139.431,42.2,139.496,42.227,139.558,42.235,139.505,42.096,139.481,42.081]],[[134.352,34.484,134.333,34.464,134.315,34.469,134.252,34.423,134.238,34.467,134.188,34.496,134.182,34.519,134.326,34.534,134.372,34.522,134.352,34.484]],[[139.456,34.727,139.446,34.68,139.392,34.69,139.367,34.721,139.37,34.775,139.426,34.776,139.456,34.727]],[[129.077,32.84,129.052,32.829,129.02,32.92,128.997,32.952,129.035,32.969,129.11,33.133,129.124,33.068,129.153,33.003,129.182,32.993,129.154,32.946,129.112,32.929,129.077,32.84]],[[129.492,33.223,129.421,33.176,129.37,33.176,129.417,33.231,129.423,33.257,129.462,33.331,129.538,33.358,129.57,33.361,129.508,33.284,129.492,33.223]],[[129.796,33.749,129.727,33.707,129.675,33.74,129.7,33.829,129.717,33.858,129.776,33.829,129.796,33.749]],[[132.578,34.115,132.549,34.075,132.461,34.087,132.496,34.122,132.524,34.164,132.543,34.173,132.56,34.127,132.578,34.115]],[[132.266,33.945,132.315,33.909,132.43,33.924,132.445,33.913,132.411,33.88,132.36,33.847,132.267,33.871,132.209,33.873,132.201,33.928,132.208,33.948,132.266,33.945]],[[129.718,31.657,129.687,31.64,129.707,31.718,129.787,31.787,129.794,31.742,129.718,31.657]],[[142.188,26.617,142.17,26.616,142.107,26.722,142.125,26.726,142.162,26.71,142.202,26.649,142.188,26.617]],[[143.824,44.117,143.95,44.112,144.005,44.117,144.101,44.102,144.482,43.95,144.597,43.93,144.715,43.928,144.799,43.94,144.872,43.982,145.102,44.166,145.343,44.334,145.37,44.327,145.37,44.281,145.352,44.23,145.245,44.076,145.126,43.869,145.101,43.765,145.14,43.663,145.214,43.578,145.273,43.463,145.341,43.303,145.436,43.282,145.488,43.28,145.583,43.328,145.674,43.389,145.751,43.396,145.833,43.386,145.726,43.343,145.624,43.291,145.537,43.193,145.505,43.174,145.405,43.18,145.347,43.177,145.23,43.135,145.127,43.089,145.029,43.032,144.921,43.001,144.807,42.994,144.631,42.947,144.516,42.944,144.302,42.984,144.197,42.974,143.969,42.881,143.762,42.748,143.581,42.599,143.429,42.419,143.369,42.325,143.332,42.22,143.327,42.151,143.314,42.084,143.279,42.038,143.237,42,143.112,42.022,142.906,42.118,142.508,42.258,142.088,42.472,141.851,42.579,141.407,42.547,140.986,42.342,140.948,42.36,140.788,42.5,140.71,42.556,140.617,42.571,140.548,42.57,140.48,42.559,140.385,42.487,140.351,42.435,140.324,42.376,140.315,42.334,140.327,42.293,140.417,42.201,140.528,42.132,140.578,42.119,140.684,42.123,140.734,42.116,140.912,41.978,141.108,41.848,141.151,41.805,141.079,41.76,141,41.737,140.908,41.743,140.816,41.76,140.66,41.816,140.593,41.769,140.489,41.672,140.432,41.567,140.385,41.519,140.27,41.456,140.149,41.423,140.085,41.434,140.037,41.474,140.009,41.521,139.995,41.576,140.021,41.696,140.084,41.803,140.108,41.913,140.057,42.067,140.024,42.1,139.895,42.19,139.835,42.278,139.821,42.388,139.829,42.448,139.86,42.582,139.891,42.649,139.951,42.671,140.015,42.685,140.115,42.733,140.224,42.796,140.329,42.867,140.432,42.954,140.486,43.05,140.397,43.167,140.379,43.237,140.392,43.303,140.487,43.338,140.585,43.312,140.781,43.215,140.819,43.205,140.954,43.201,141.138,43.18,141.245,43.185,141.296,43.2,141.374,43.28,141.412,43.381,141.398,43.513,141.398,43.643,141.447,43.749,141.601,43.919,141.645,44.019,141.661,44.264,141.716,44.371,141.761,44.483,141.782,44.716,141.719,44.941,141.656,45.051,141.583,45.156,141.594,45.256,141.653,45.349,141.654,45.377,141.668,45.401,141.778,45.419,141.829,45.439,141.879,45.483,141.938,45.51,141.981,45.483,142.016,45.438,142.172,45.326,142.416,45.125,142.704,44.819,142.885,44.67,143.075,44.535,143.289,44.397,143.512,44.278,143.655,44.221,143.759,44.132,143.824,44.117]],[[131.175,33.603,131.309,33.573,131.366,33.571,131.419,33.584,131.499,33.624,131.583,33.652,131.643,33.638,131.696,33.603,131.724,33.554,131.711,33.502,131.616,33.392,131.537,33.274,131.717,33.252,131.897,33.255,131.855,33.182,131.848,33.118,131.903,33.088,131.949,33.047,131.937,33.01,131.91,32.974,132.009,32.919,132.002,32.882,131.977,32.844,131.732,32.593,131.66,32.466,131.61,32.325,131.565,32.223,131.531,32.117,131.506,32.002,131.46,31.883,131.475,31.778,131.46,31.671,131.337,31.405,131.25,31.41,131.14,31.442,131.071,31.437,131.035,31.378,131.098,31.256,130.902,31.112,130.686,31.015,130.684,31.059,130.704,31.094,130.736,31.122,130.758,31.156,130.79,31.269,130.774,31.383,130.709,31.526,130.704,31.577,130.749,31.598,130.78,31.604,130.796,31.624,130.797,31.671,130.776,31.706,130.715,31.718,130.655,31.718,130.613,31.665,130.556,31.563,130.528,31.46,130.54,31.403,130.566,31.352,130.645,31.267,130.621,31.218,130.589,31.179,130.311,31.267,130.251,31.273,130.201,31.292,130.147,31.408,130.261,31.437,130.294,31.451,130.307,31.488,130.322,31.601,130.269,31.696,130.224,31.73,130.188,31.769,130.211,31.849,130.196,31.95,130.194,32.091,130.214,32.115,130.319,32.144,130.395,32.219,130.462,32.305,130.56,32.456,130.641,32.619,130.563,32.626,130.498,32.657,130.569,32.734,130.547,32.832,130.44,32.951,130.382,33.093,130.287,33.155,130.238,33.178,130.177,33.145,130.127,33.105,130.173,33.013,130.168,32.932,130.175,32.851,130.222,32.847,130.28,32.867,130.326,32.853,130.354,32.81,130.361,32.756,130.34,32.702,130.298,32.675,130.246,32.677,130.193,32.706,130.152,32.748,130.054,32.771,129.951,32.722,129.853,32.622,129.769,32.571,129.808,32.645,129.827,32.725,129.786,32.782,129.69,32.875,129.668,32.929,129.662,32.995,129.679,33.06,129.778,32.986,129.828,32.893,129.901,32.852,129.992,32.852,129.922,32.988,129.897,33.022,129.799,33.084,129.665,33.187,129.58,33.236,129.61,33.344,129.66,33.365,129.702,33.36,129.844,33.322,129.858,33.375,129.837,33.404,129.826,33.437,129.919,33.483,130.072,33.522,130.103,33.54,130.131,33.578,130.168,33.598,130.275,33.598,130.365,33.634,130.439,33.734,130.457,33.789,130.484,33.835,130.67,33.915,130.716,33.928,130.84,33.918,130.953,33.872,131.009,33.776,131.058,33.673,131.175,33.603]],[[134.357,34.256,134.496,34.215,134.638,34.227,134.635,34.044,134.655,33.983,134.695,33.928,134.675,33.848,134.739,33.821,134.549,33.729,134.377,33.608,134.307,33.527,134.243,33.439,134.206,33.347,134.182,33.247,134.124,33.287,133.959,33.448,133.854,33.493,133.686,33.516,133.632,33.511,133.286,33.36,133.24,33.25,133.146,33.083,133.101,33.028,133.051,33.012,133.016,32.984,132.977,32.842,132.87,32.755,132.804,32.752,132.692,32.776,132.642,32.762,132.709,32.902,132.602,32.92,132.495,32.917,132.493,33.008,132.428,33.059,132.476,33.126,132.477,33.181,132.505,33.211,132.515,33.255,132.511,33.293,132.445,33.305,132.405,33.331,132.413,33.43,132.375,33.434,132.281,33.417,132.086,33.34,132.033,33.34,132.114,33.395,132.288,33.47,132.366,33.512,132.536,33.633,132.643,33.69,132.699,33.791,132.716,33.852,132.752,33.906,132.784,33.992,132.839,34.021,132.935,34.095,132.99,34.088,133.051,33.997,133.134,33.927,133.193,33.933,133.299,33.969,133.35,33.977,133.472,33.973,133.582,34.017,133.627,34.069,133.643,34.135,133.603,34.244,133.656,34.233,133.706,34.237,133.826,34.307,133.948,34.348,134.076,34.358,134.219,34.319,134.357,34.256]],[[141.229,41.373,141.269,41.354,141.455,41.405,141.42,41.251,141.4,41.096,141.414,40.839,141.43,40.723,141.463,40.611,141.542,40.531,141.646,40.474,141.797,40.291,141.878,40.067,141.935,39.958,141.978,39.844,141.991,39.792,141.992,39.74,141.979,39.668,141.993,39.611,141.977,39.429,141.909,39.219,141.901,39.111,141.842,39.09,141.807,39.04,141.776,39.017,141.742,39,141.694,38.995,141.659,38.975,141.645,38.918,141.622,38.865,141.58,38.817,141.546,38.763,141.519,38.632,141.509,38.498,141.467,38.404,141.368,38.38,141.254,38.381,141.108,38.338,141.077,38.313,140.962,38.149,140.929,38.053,140.928,37.95,140.96,37.823,141.003,37.698,141.036,37.467,141.002,37.115,140.968,37.002,140.895,36.926,140.84,36.89,140.792,36.847,140.73,36.732,140.627,36.503,140.619,36.445,140.619,36.386,140.592,36.308,140.574,36.231,140.59,36.142,140.622,36.059,140.76,35.846,140.813,35.783,140.874,35.725,140.639,35.661,140.597,35.632,140.457,35.51,140.413,35.395,140.417,35.267,140.393,35.221,140.355,35.181,140.315,35.155,140.159,35.096,140.059,35.038,139.96,34.947,139.92,34.9,139.844,34.915,139.799,34.957,139.843,35.01,139.83,35.072,139.851,35.232,139.826,35.297,139.906,35.345,139.944,35.423,140.027,35.485,140.086,35.54,140.097,35.585,140.044,35.633,139.988,35.668,139.91,35.668,139.835,35.658,139.786,35.612,139.77,35.55,139.774,35.52,139.768,35.495,139.65,35.409,139.666,35.319,139.7,35.274,139.744,35.252,139.731,35.222,139.675,35.149,139.636,35.142,139.564,35.243,139.474,35.299,139.363,35.298,139.249,35.278,139.163,35.211,139.134,35.155,139.116,35.097,139.122,34.956,139.086,34.839,139.016,34.736,138.983,34.698,138.897,34.628,138.838,34.619,138.795,34.651,138.761,34.699,138.804,34.876,138.803,34.975,138.904,35.025,138.821,35.096,138.72,35.124,138.577,35.086,138.537,35.044,138.51,34.987,138.433,34.915,138.349,34.848,138.253,34.733,138.189,34.596,137.979,34.641,137.864,34.651,137.749,34.647,137.543,34.664,137.318,34.636,137.062,34.583,137.077,34.621,137.288,34.704,137.295,34.728,137.275,34.773,137.223,34.775,137.097,34.759,137.032,34.766,137.006,34.814,136.963,34.835,136.935,34.815,136.944,34.722,136.913,34.709,136.871,34.733,136.885,34.806,136.856,34.913,136.853,34.979,136.897,35.036,136.852,35.06,136.804,35.05,136.744,35.023,136.69,34.984,136.577,34.79,136.533,34.678,136.616,34.589,136.842,34.464,136.88,34.434,136.881,34.38,136.854,34.324,136.792,34.299,136.544,34.258,136.33,34.177,136.268,34.095,136.073,33.778,135.916,33.562,135.695,33.487,135.453,33.553,135.394,33.629,135.347,33.722,135.257,33.806,135.175,33.898,135.128,34.007,135.135,34.183,135.1,34.288,135.132,34.317,135.266,34.381,135.309,34.417,135.385,34.5,135.412,34.547,135.416,34.617,135.355,34.654,135.198,34.653,135.042,34.631,134.93,34.662,134.785,34.747,134.74,34.765,134.584,34.771,134.472,34.755,134.363,34.724,134.247,34.714,134.208,34.698,134.075,34.593,133.968,34.527,133.876,34.495,133.678,34.486,133.579,34.465,133.474,34.43,133.445,34.433,133.336,34.385,133.21,34.344,133.142,34.302,133.019,34.33,132.775,34.255,132.657,34.246,132.534,34.287,132.421,34.353,132.313,34.325,132.238,34.227,132.202,34.032,132.159,33.944,132.146,33.839,132.09,33.855,131.763,34.045,131.741,34.052,131.476,34.019,131.408,34.004,131.323,33.965,131.233,33.948,131.15,33.976,131.072,34.021,130.996,34.007,130.919,33.976,130.889,34.262,130.904,34.3,130.952,34.35,131.004,34.393,131.132,34.407,131.262,34.393,131.354,34.413,131.433,34.47,131.515,34.55,131.608,34.615,131.734,34.667,131.856,34.726,131.963,34.809,132.065,34.9,132.158,34.967,132.26,35.022,132.414,35.156,132.619,35.307,132.698,35.418,132.747,35.449,132.923,35.511,133.157,35.559,133.267,35.557,133.376,35.459,133.435,35.472,133.495,35.497,133.615,35.511,133.739,35.495,133.86,35.495,133.981,35.507,134.214,35.539,134.337,35.578,134.456,35.628,134.882,35.663,135.174,35.747,135.221,35.741,135.265,35.722,135.269,35.66,135.232,35.592,135.268,35.551,135.327,35.526,135.602,35.518,135.68,35.503,135.795,35.55,135.903,35.607,136.016,35.683,136.095,35.768,136.022,35.874,136.006,35.991,136.067,36.117,136.156,36.223,136.262,36.288,136.359,36.362,136.556,36.572,136.698,36.742,136.749,36.951,136.719,37.198,136.843,37.382,136.962,37.414,137.199,37.497,137.323,37.522,137.341,37.485,137.338,37.437,137.152,37.283,137.046,37.22,136.982,37.2,136.924,37.172,136.9,37.118,136.994,37.027,137.019,36.96,137.013,36.895,137.017,36.837,137.124,36.774,137.246,36.753,137.298,36.754,137.343,36.77,137.483,36.925,137.514,36.952,137.913,37.065,138.11,37.151,138.218,37.173,138.32,37.218,138.548,37.392,138.633,37.472,138.709,37.561,138.771,37.663,138.819,37.775,138.885,37.844,139.247,38.009,139.364,38.099,139.401,38.143,139.446,38.269,139.477,38.4,139.521,38.503,139.58,38.599,139.66,38.697,139.749,38.788,139.802,38.882,139.879,39.105,139.912,39.229,139.939,39.273,139.977,39.311,140.011,39.358,140.037,39.411,140.048,39.464,140.065,39.624,140.055,39.749,139.995,39.855,139.945,39.885,139.891,39.887,139.81,39.878,139.742,39.921,139.755,39.959,139.826,39.966,139.874,39.986,139.908,40.022,139.972,40.137,140.011,40.26,140.014,40.315,139.964,40.414,139.924,40.534,139.923,40.598,139.967,40.673,140.029,40.733,140.085,40.747,140.146,40.752,140.201,40.775,140.252,40.809,140.281,40.846,140.326,40.948,140.344,41.006,140.315,41.161,140.344,41.203,140.386,41.23,140.441,41.21,140.498,41.206,140.564,41.212,140.628,41.195,140.64,41.156,140.679,40.893,140.702,40.858,140.749,40.83,140.801,40.834,140.846,40.875,140.876,40.93,140.936,40.941,141.119,40.882,141.183,40.924,141.225,40.988,141.262,41.103,141.244,41.206,141.2,41.244,141.155,41.237,141.115,41.208,141.07,41.193,140.801,41.139,140.802,41.254,140.86,41.425,140.892,41.48,140.937,41.506,141.05,41.476,141.106,41.456,141.229,41.373]],[[124.293,24.516,124.234,24.358,124.186,24.335,124.136,24.348,124.085,24.436,124.12,24.47,124.17,24.452,124.211,24.459,124.302,24.587,124.324,24.566,124.293,24.516]],[[123.889,24.28,123.826,24.266,123.75,24.283,123.681,24.288,123.68,24.318,123.752,24.348,123.754,24.391,123.771,24.414,123.935,24.362,123.928,24.324,123.889,24.28]],[[125.444,24.743,125.359,24.717,125.269,24.733,125.284,24.872,125.315,24.852,125.335,24.805,125.402,24.777,125.444,24.743]],[[128.259,26.653,128.163,26.607,128.127,26.552,128.038,26.534,127.951,26.456,127.867,26.442,127.869,26.381,127.905,26.328,127.849,26.319,127.79,26.255,127.786,26.209,127.806,26.171,127.804,26.153,127.729,26.097,127.653,26.095,127.65,26.154,127.655,26.199,127.727,26.308,127.729,26.434,127.796,26.449,127.82,26.466,127.926,26.556,127.946,26.594,127.891,26.631,127.895,26.675,127.907,26.694,127.994,26.679,128.03,26.647,128.047,26.643,128.098,26.668,128.122,26.711,128.217,26.797,128.255,26.882,128.332,26.812,128.311,26.721,128.259,26.653]],[[128.998,27.721,128.956,27.702,128.9,27.728,128.883,27.842,128.908,27.898,128.952,27.91,128.99,27.811,129.016,27.77,128.998,27.721]],[[129.453,28.209,129.366,28.128,129.275,28.201,129.165,28.25,129.217,28.263,129.248,28.283,129.251,28.314,129.322,28.36,129.465,28.395,129.51,28.398,129.561,28.431,129.577,28.461,129.598,28.476,129.69,28.517,129.715,28.47,129.71,28.432,129.642,28.411,129.575,28.361,129.513,28.299,129.457,28.272,129.439,28.255,129.453,28.209]],[[129.324,28.105,129.331,28.082,129.232,28.101,129.192,28.192,129.257,28.176,129.277,28.145,129.324,28.105]],[[139.841,33.056,139.824,33.045,139.776,33.078,139.769,33.107,139.777,33.125,139.809,33.129,139.874,33.094,139.841,33.056]]]},{"n":"Jamaica","c":"JM","r":4,"ml":4,"lx":-77.32,"ly":18.14,"b":[-78.34,17.715,-76.211,18.522],"p":[[[-77.261,18.457,-77.14,18.421,-77.014,18.403,-76.959,18.402,-76.908,18.39,-76.793,18.304,-76.701,18.257,-76.35,18.152,-76.233,17.97,-76.211,17.914,-76.301,17.88,-76.416,17.868,-76.525,17.866,-76.625,17.901,-76.669,17.928,-76.774,17.94,-76.748,17.965,-76.795,17.976,-76.853,17.974,-76.896,17.904,-76.944,17.849,-77.036,17.854,-77.071,17.901,-77.119,17.88,-77.158,17.845,-77.205,17.715,-77.28,17.78,-77.361,17.834,-77.464,17.856,-77.671,17.86,-77.768,17.877,-77.849,17.988,-77.881,18.019,-77.963,18.048,-78.044,18.174,-78.074,18.191,-78.294,18.218,-78.34,18.287,-78.326,18.35,-78.252,18.426,-78.217,18.448,-78.095,18.445,-77.978,18.468,-77.927,18.501,-77.873,18.522,-77.452,18.467,-77.354,18.466,-77.261,18.457]]]},{"n":"Italy","c":"IT","r":2,"ml":2,"lx":11.08,"ly":44.73,"b":[6.628,36.688,18.486,47.082],"p":[[[7.021,45.926,7.056,45.904,7.129,45.88,7.328,45.912,7.452,45.944,7.539,45.978,7.593,45.972,7.788,45.922,7.852,45.947,7.993,46.016,8.014,46.052,8.125,46.161,8.127,46.188,8.082,46.256,8.096,46.271,8.232,46.341,8.299,46.403,8.371,46.445,8.423,46.446,8.437,46.432,8.443,46.403,8.438,46.283,8.458,46.246,8.565,46.16,8.642,46.111,8.819,46.077,8.827,46.061,8.778,45.996,8.885,45.919,8.904,45.862,8.954,45.83,9.024,45.846,9.047,45.876,9.019,45.928,8.999,45.983,9.003,46.015,9.022,46.051,9.071,46.102,9.203,46.219,9.251,46.287,9.26,46.391,9.26,46.475,9.304,46.496,9.399,46.481,9.428,46.482,9.441,46.431,9.481,46.349,9.529,46.306,9.58,46.296,9.639,46.296,9.788,46.346,9.884,46.368,9.939,46.362,9.972,46.328,10.041,46.238,10.081,46.228,10.128,46.238,10.145,46.254,10.13,46.288,10.11,46.363,10.082,46.421,10.046,46.448,10.038,46.483,10.061,46.547,10.087,46.6,10.138,46.614,10.196,46.621,10.272,46.565,10.363,46.547,10.431,46.55,10.442,46.583,10.438,46.619,10.398,46.665,10.406,46.735,10.453,46.865,10.479,46.855,10.58,46.854,10.689,46.846,10.76,46.793,10.829,46.775,10.927,46.769,10.993,46.777,11.025,46.797,11.063,46.859,11.134,46.936,11.244,46.976,11.433,46.983,11.528,46.997,11.625,46.997,11.699,46.985,11.776,46.986,11.97,47.04,12.169,47.082,12.197,47.075,12.201,47.061,12.166,47.028,12.131,46.985,12.154,46.935,12.268,46.836,12.33,46.76,12.388,46.703,12.479,46.673,12.599,46.654,12.7,46.647,12.806,46.626,13.169,46.573,13.352,46.558,13.49,46.556,13.7,46.52,13.68,46.463,13.637,46.449,13.563,46.415,13.479,46.369,13.4,46.318,13.378,46.262,13.4,46.225,13.421,46.212,13.45,46.224,13.492,46.217,13.545,46.197,13.633,46.177,13.635,46.158,13.617,46.133,13.548,46.089,13.486,46.04,13.48,46.009,13.488,45.987,13.509,45.974,13.601,45.98,13.614,45.962,13.57,45.834,13.583,45.812,13.663,45.792,13.722,45.761,13.831,45.68,13.875,45.615,13.845,45.593,13.776,45.582,13.72,45.588,13.783,45.627,13.628,45.771,13.558,45.771,13.465,45.71,13.206,45.771,13.157,45.747,13.12,45.698,13.03,45.638,12.903,45.611,12.761,45.544,12.612,45.497,12.498,45.462,12.432,45.468,12.536,45.545,12.492,45.546,12.354,45.492,12.274,45.446,12.249,45.369,12.226,45.242,12.286,45.208,12.392,45.04,12.523,44.968,12.498,44.899,12.464,44.845,12.384,44.798,12.319,44.833,12.279,44.832,12.248,44.723,12.305,44.429,12.396,44.224,12.487,44.134,12.691,43.995,12.907,43.921,13.295,43.686,13.508,43.612,13.564,43.571,13.693,43.39,13.805,43.18,13.925,42.852,14.01,42.69,14.183,42.506,14.541,42.244,14.866,42.053,15.169,41.934,15.405,41.913,15.964,41.939,16.062,41.928,16.165,41.896,16.189,41.814,16.151,41.758,16.034,41.701,15.914,41.621,15.9,41.512,16.013,41.435,16.552,41.232,17.103,41.062,17.275,40.975,17.474,40.841,17.955,40.655,18.036,40.565,18.328,40.371,18.461,40.221,18.486,40.105,18.423,39.987,18.393,39.904,18.344,39.821,18.219,39.853,18.078,39.937,17.865,40.28,17.476,40.315,17.396,40.34,17.258,40.399,17.249,40.438,17.215,40.486,17.18,40.503,17.031,40.513,16.928,40.458,16.807,40.326,16.67,40.137,16.53,39.86,16.522,39.748,16.598,39.639,16.824,39.578,16.999,39.482,17.115,39.381,17.123,39.137,17.175,38.998,17.099,38.919,16.951,38.94,16.755,38.89,16.617,38.8,16.559,38.715,16.574,38.494,16.546,38.409,16.282,38.25,16.144,38.086,16.11,38.019,16.057,37.942,15.725,37.939,15.646,38.034,15.643,38.175,15.7,38.262,15.822,38.303,15.905,38.483,15.879,38.614,15.927,38.672,15.972,38.713,16.066,38.736,16.197,38.759,16.21,38.941,16.107,39.024,16.071,39.139,16.024,39.354,15.854,39.627,15.764,39.87,15.693,39.99,15.585,40.053,15.391,40.052,15.295,40.07,14.951,40.239,14.927,40.265,14.929,40.31,14.986,40.377,14.948,40.469,14.907,40.556,14.84,40.63,14.766,40.668,14.611,40.645,14.557,40.626,14.459,40.633,14.383,40.6,14.34,40.599,14.461,40.729,14.428,40.759,14.309,40.813,14.147,40.821,14.102,40.827,14.076,40.794,14.044,40.812,14.048,40.87,13.86,41.13,13.733,41.236,13.67,41.254,13.555,41.232,13.362,41.279,13.247,41.289,13.183,41.278,13.089,41.244,13.041,41.266,13.024,41.301,12.849,41.409,12.631,41.47,12.206,41.813,12.075,41.941,11.807,42.082,11.637,42.288,11.498,42.363,11.296,42.423,11.25,42.416,11.189,42.393,11.141,42.39,11.103,42.417,11.142,42.444,11.185,42.457,11.168,42.535,10.938,42.739,10.803,42.804,10.765,42.845,10.737,42.9,10.708,42.936,10.645,42.957,10.59,42.954,10.515,42.968,10.517,43.065,10.532,43.14,10.521,43.204,10.448,43.371,10.321,43.513,10.246,43.852,10.188,43.948,10.048,44.02,9.731,44.101,9.289,44.319,9.196,44.323,8.93,44.408,8.766,44.422,8.552,44.346,8.292,44.137,8.082,43.919,8.005,43.877,7.733,43.803,7.493,43.767,7.491,43.823,7.482,43.865,7.523,43.911,7.59,43.965,7.651,44.034,7.677,44.083,7.665,44.116,7.637,44.165,7.599,44.168,7.371,44.127,7.319,44.138,7.149,44.202,6.967,44.28,6.9,44.336,6.875,44.392,6.894,44.428,6.879,44.463,6.843,44.511,6.875,44.565,6.932,44.632,6.96,44.677,7.008,44.689,7.031,44.717,6.993,44.827,6.973,44.845,6.94,44.859,6.889,44.86,6.801,44.883,6.738,44.921,6.725,44.973,6.691,45.023,6.635,45.068,6.628,45.118,6.692,45.144,6.78,45.145,6.842,45.136,6.981,45.216,7.032,45.223,7.078,45.24,7.117,45.349,7.146,45.382,7.153,45.401,7.126,45.424,7.014,45.5,6.962,45.581,6.881,45.67,6.806,45.71,6.791,45.741,6.789,45.78,6.804,45.815,6.941,45.868,7.021,45.926],[12.439,41.898,12.438,41.906,12.431,41.905,12.428,41.901,12.431,41.898,12.439,41.898],[12.485,43.901,12.515,43.953,12.504,43.99,12.441,43.982,12.397,43.935,12.426,43.894,12.485,43.901]],[[10.395,42.858,10.428,42.819,10.432,42.797,10.41,42.771,10.419,42.713,10.336,42.761,10.209,42.737,10.131,42.742,10.11,42.785,10.128,42.81,10.248,42.816,10.286,42.828,10.359,42.822,10.395,42.858]],[[13.938,40.706,13.894,40.697,13.868,40.709,13.854,40.724,13.871,40.762,13.962,40.739,13.961,40.718,13.938,40.706]],[[12.051,36.757,12.003,36.746,11.941,36.78,11.936,36.829,11.948,36.843,12.024,36.821,12.048,36.776,12.051,36.757]],[[15.577,38.22,15.509,38.107,15.476,38.063,15.234,37.785,15.207,37.721,15.19,37.651,15.165,37.59,15.131,37.532,15.1,37.459,15.106,37.375,15.117,37.335,15.146,37.308,15.194,37.283,15.23,37.244,15.174,37.209,15.236,37.139,15.289,37.097,15.296,37.055,15.295,37.013,15.185,36.935,15.142,36.892,15.116,36.839,15.104,36.785,15.116,36.736,15.113,36.688,15.002,36.694,14.89,36.724,14.776,36.71,14.614,36.767,14.555,36.777,14.502,36.799,14.367,36.973,14.259,37.046,14.143,37.104,14.024,37.107,13.905,37.101,13.801,37.136,13.587,37.254,13.361,37.349,13.265,37.41,13.221,37.452,13.17,37.479,13.04,37.507,12.924,37.571,12.871,37.575,12.757,37.567,12.699,37.572,12.64,37.594,12.527,37.67,12.454,37.774,12.436,37.82,12.487,37.939,12.548,38.053,12.602,38.085,12.664,38.108,12.702,38.142,12.734,38.183,12.851,38.064,12.903,38.035,12.955,38.041,13.049,38.084,13.057,38.131,13.16,38.19,13.291,38.191,13.352,38.181,13.383,38.127,13.433,38.11,13.491,38.103,13.682,38.001,13.735,37.984,13.789,37.981,13.937,38.024,14.05,38.041,14.288,38.017,14.416,38.043,14.506,38.046,14.637,38.085,14.737,38.151,14.79,38.167,14.846,38.172,14.982,38.168,15.119,38.153,15.176,38.168,15.224,38.211,15.28,38.23,15.341,38.217,15.499,38.291,15.568,38.296,15.635,38.268,15.577,38.22]],[[9.632,40.882,9.682,40.818,9.794,40.556,9.805,40.5,9.783,40.442,9.754,40.4,9.643,40.268,9.659,40.159,9.701,40.092,9.707,40.017,9.686,39.924,9.617,39.354,9.584,39.254,9.563,39.166,9.486,39.14,9.388,39.168,9.264,39.217,9.207,39.214,9.149,39.197,9.102,39.211,9.056,39.239,9.023,39.043,8.967,38.964,8.881,38.913,8.801,38.91,8.719,38.927,8.649,38.927,8.595,38.964,8.553,39.03,8.486,39.11,8.418,39.206,8.411,39.292,8.399,39.482,8.419,39.523,8.447,39.563,8.461,39.648,8.451,39.722,8.471,39.748,8.511,39.722,8.541,39.732,8.539,39.77,8.548,39.839,8.496,39.897,8.408,39.917,8.399,39.978,8.409,40.038,8.455,40.078,8.471,40.131,8.471,40.293,8.409,40.352,8.385,40.443,8.353,40.501,8.296,40.559,8.23,40.606,8.19,40.652,8.181,40.771,8.204,40.871,8.224,40.913,8.245,40.907,8.31,40.858,8.363,40.846,8.468,40.834,8.572,40.85,8.699,40.895,8.821,40.95,8.998,41.11,9.107,41.143,9.163,41.185,9.182,41.242,9.228,41.257,9.283,41.202,9.351,41.196,9.455,41.15,9.5,41.106,9.539,41.054,9.576,41.031,9.615,41.017,9.621,41.005,9.59,40.992,9.554,40.932,9.574,40.915,9.632,40.882]],[[8.479,39.068,8.421,38.969,8.361,39.039,8.359,39.099,8.367,39.116,8.441,39.091,8.479,39.068]],[[8.286,41.04,8.253,40.994,8.206,40.997,8.224,41.031,8.267,41.099,8.32,41.122,8.344,41.102,8.319,41.063,8.286,41.04]]]},{"n":"Israel","c":"IL","r":4,"ml":3,"lx":34.85,"ly":30.91,"b":[34.245,29.477,35.888,33.416],"p":[[[35.841,33.416,35.816,33.362,35.793,33.334,35.785,33.291,35.8,33.256,35.809,33.209,35.827,33.195,35.833,33.164,35.817,33.133,35.825,33.108,35.845,33.085,35.859,32.989,35.888,32.944,35.842,32.876,35.833,32.82,35.787,32.735,35.734,32.729,35.611,32.682,35.595,32.668,35.573,32.641,35.569,32.62,35.551,32.396,35.484,32.402,35.403,32.451,35.387,32.493,35.362,32.507,35.304,32.513,35.193,32.534,35.065,32.46,35.011,32.338,35,32.281,34.956,32.161,34.971,32.087,34.979,31.992,34.99,31.913,34.978,31.866,34.954,31.841,34.961,31.823,34.983,31.817,35.053,31.838,35.127,31.817,35.198,31.776,35.204,31.75,35.153,31.734,35.035,31.673,34.951,31.602,34.929,31.537,34.873,31.397,34.88,31.368,34.908,31.351,35.101,31.366,35.277,31.423,35.409,31.483,35.451,31.479,35.423,31.325,35.424,31.325,35.401,31.231,35.41,31.214,35.439,31.132,35.383,30.982,35.32,30.86,35.298,30.802,35.237,30.673,35.174,30.524,35.141,30.421,35.148,30.384,35.133,30.195,35.142,30.142,35.068,29.978,35.053,29.897,35.024,29.787,34.973,29.555,34.904,29.477,34.87,29.564,34.791,29.812,34.735,29.982,34.659,30.191,34.53,30.446,34.518,30.507,34.49,30.596,34.401,30.828,34.329,30.995,34.245,31.208,34.348,31.293,34.35,31.363,34.526,31.526,34.524,31.542,34.477,31.585,34.484,31.592,34.678,31.896,34.804,32.196,34.922,32.614,35.006,32.827,35.077,32.967,35.109,33.084,35.223,33.092,35.309,33.08,35.411,33.076,35.493,33.119,35.533,33.25,35.579,33.271,35.603,33.241,35.627,33.275,35.734,33.333,35.788,33.37,35.841,33.416]]]},{"n":"Palestine","c":"PS","r":5,"ml":4.5,"lx":35.29,"ly":32.05,"b":[34.198,31.208,35.572,32.534],"p":[[[34.477,31.585,34.524,31.542,34.526,31.526,34.35,31.363,34.348,31.293,34.245,31.208,34.213,31.292,34.198,31.323,34.387,31.484,34.477,31.585]],[[35.551,32.396,35.572,32.238,35.535,32.103,35.531,31.985,35.559,31.766,35.499,31.672,35.465,31.562,35.451,31.479,35.409,31.483,35.277,31.423,35.101,31.366,34.908,31.351,34.88,31.368,34.873,31.397,34.929,31.537,34.951,31.602,35.035,31.673,35.153,31.734,35.204,31.75,35.198,31.776,35.127,31.817,35.053,31.838,34.983,31.817,34.961,31.823,34.954,31.841,34.978,31.866,34.99,31.913,34.979,31.992,34.971,32.087,34.956,32.161,35,32.281,35.011,32.338,35.065,32.46,35.193,32.534,35.304,32.513,35.362,32.507,35.387,32.493,35.403,32.451,35.484,32.402,35.551,32.396]]]},{"n":"Ireland","c":"IE","r":3,"ml":3,"lx":-7.8,"ly":53.08,"b":[-10.39,51.474,-6.027,55.366],"p":[[[-9.948,53.913,-9.952,53.885,-10.027,53.921,-10.062,53.96,-10.266,53.978,-10.181,54.017,-10.14,54.005,-9.996,54.004,-9.956,53.987,-9.948,53.913]],[[-7.219,55.092,-7.377,55.028,-7.401,55.003,-7.446,54.905,-7.451,54.877,-7.502,54.825,-7.55,54.768,-7.606,54.746,-7.69,54.728,-7.737,54.71,-7.797,54.719,-7.873,54.718,-7.911,54.698,-7.909,54.683,-7.886,54.666,-7.82,54.64,-7.746,54.616,-7.754,54.595,-7.794,54.571,-8.044,54.512,-8.119,54.477,-8.145,54.454,-8.118,54.414,-7.918,54.297,-7.884,54.284,-7.855,54.215,-7.679,54.187,-7.607,54.144,-7.544,54.134,-7.409,54.137,-7.355,54.121,-7.325,54.133,-7.307,54.156,-7.193,54.214,-7.155,54.24,-7.178,54.275,-7.203,54.302,-7.133,54.355,-7.05,54.408,-7.008,54.407,-6.936,54.374,-6.877,54.329,-6.869,54.294,-6.858,54.269,-6.803,54.214,-6.767,54.196,-6.67,54.185,-6.647,54.163,-6.664,54.085,-6.65,54.059,-6.548,54.057,-6.44,54.064,-6.403,54.061,-6.364,54.077,-6.304,54.095,-6.218,54.089,-6.176,54.054,-6.157,54.017,-6.231,54.004,-6.308,54.011,-6.345,53.987,-6.348,53.941,-6.322,53.882,-6.27,53.84,-6.229,53.746,-6.195,53.641,-6.142,53.578,-6.131,53.499,-6.139,53.46,-6.129,53.391,-6.152,53.366,-6.135,53.301,-6.072,53.166,-6.045,53.091,-6.027,52.927,-6.071,52.866,-6.131,52.807,-6.169,52.738,-6.199,52.663,-6.217,52.543,-6.345,52.402,-6.4,52.367,-6.463,52.345,-6.325,52.247,-6.438,52.203,-6.561,52.189,-6.697,52.214,-6.782,52.21,-6.86,52.179,-6.89,52.159,-6.915,52.169,-6.966,52.25,-7.003,52.166,-7.082,52.139,-7.216,52.145,-7.441,52.123,-7.527,52.099,-7.563,52.062,-7.59,52.019,-7.625,51.993,-7.665,51.98,-7.838,51.948,-7.872,51.935,-7.952,51.866,-8.058,51.826,-8.145,51.814,-8.222,51.854,-8.254,51.878,-8.29,51.891,-8.409,51.889,-8.372,51.876,-8.347,51.848,-8.336,51.793,-8.349,51.739,-8.408,51.712,-8.478,51.707,-8.588,51.651,-8.734,51.636,-8.813,51.585,-9.296,51.498,-9.324,51.497,-9.391,51.519,-9.463,51.529,-9.535,51.522,-9.737,51.474,-9.835,51.483,-9.71,51.604,-9.542,51.664,-9.525,51.681,-9.58,51.689,-9.899,51.647,-10.01,51.611,-10.121,51.601,-10.069,51.656,-9.926,51.731,-9.85,51.766,-9.803,51.78,-9.75,51.824,-9.599,51.874,-10.084,51.771,-10.212,51.784,-10.242,51.812,-10.341,51.799,-10.379,51.869,-10.232,51.975,-10.146,52.02,-10.044,52.045,-9.946,52.08,-9.91,52.123,-9.956,52.137,-10.25,52.126,-10.39,52.135,-10.383,52.169,-10.357,52.207,-10.211,52.272,-10.132,52.282,-10.062,52.276,-9.993,52.259,-9.937,52.238,-9.772,52.25,-9.841,52.291,-9.853,52.375,-9.906,52.404,-9.838,52.443,-9.761,52.466,-9.632,52.547,-9.586,52.559,-9.331,52.579,-9.056,52.621,-8.783,52.68,-8.923,52.712,-8.99,52.755,-9.098,52.668,-9.175,52.635,-9.394,52.617,-9.463,52.627,-9.561,52.654,-9.591,52.644,-9.62,52.623,-9.764,52.58,-9.917,52.57,-9.74,52.648,-9.515,52.781,-9.465,52.823,-9.394,52.896,-9.416,52.929,-9.462,52.947,-9.299,53.098,-9.242,53.125,-9.138,53.129,-9.061,53.153,-9.027,53.153,-8.997,53.162,-8.93,53.207,-9.034,53.236,-9.14,53.25,-9.471,53.235,-9.514,53.238,-9.555,53.252,-9.582,53.272,-9.602,53.323,-9.626,53.334,-9.701,53.334,-9.774,53.319,-9.825,53.32,-9.876,53.343,-9.795,53.395,-9.899,53.407,-10.004,53.397,-10.091,53.413,-10.094,53.446,-10.054,53.478,-10.106,53.509,-10.117,53.549,-10.062,53.568,-10.001,53.561,-9.878,53.59,-9.721,53.604,-9.856,53.633,-9.91,53.658,-9.912,53.695,-9.902,53.727,-9.745,53.781,-9.578,53.805,-9.591,53.841,-9.579,53.88,-9.748,53.891,-9.914,53.864,-9.896,53.938,-9.856,54.004,-9.848,54.048,-9.856,54.095,-9.934,54.075,-9.944,54.142,-9.977,54.187,-10.093,54.156,-10.09,54.216,-10.056,54.258,-9.996,54.276,-9.936,54.268,-9.825,54.269,-9.717,54.3,-9.562,54.309,-9.316,54.299,-9.146,54.21,-9.102,54.226,-9.034,54.282,-9.002,54.288,-8.747,54.263,-8.588,54.231,-8.546,54.241,-8.568,54.304,-8.623,54.347,-8.554,54.404,-8.471,54.442,-8.415,54.461,-8.287,54.485,-8.23,54.507,-8.193,54.58,-8.133,54.641,-8.457,54.609,-8.764,54.681,-8.715,54.732,-8.65,54.761,-8.538,54.783,-8.528,54.809,-8.471,54.832,-8.377,54.889,-8.412,54.965,-8.393,55.02,-8.326,55.056,-8.305,55.108,-8.275,55.146,-8.138,55.16,-8.006,55.195,-7.959,55.192,-7.803,55.2,-7.751,55.186,-7.763,55.248,-7.667,55.256,-7.63,55.244,-7.613,55.2,-7.57,55.171,-7.557,55.122,-7.586,55.084,-7.634,55.055,-7.59,55.025,-7.659,54.971,-7.584,54.994,-7.478,55.047,-7.484,55.09,-7.502,55.145,-7.531,55.194,-7.518,55.248,-7.458,55.282,-7.302,55.299,-7.366,55.36,-7.309,55.366,-7.247,55.353,-7.155,55.305,-7.06,55.268,-6.962,55.238,-7.056,55.178,-7.173,55.137,-7.219,55.092]]]},{"n":"Iraq","c":"IQ","r":3,"ml":3,"lx":43.26,"ly":33.09,"b":[38.774,29.064,48.546,37.372],"p":[[[42.359,37.109,42.456,37.129,42.635,37.249,42.741,37.362,42.775,37.372,42.869,37.335,42.937,37.325,43.092,37.367,43.185,37.345,43.263,37.317,43.307,37.315,43.516,37.245,43.568,37.236,43.676,37.227,43.836,37.224,43.94,37.269,44.013,37.314,44.065,37.312,44.114,37.302,44.156,37.283,44.192,37.25,44.208,37.203,44.202,37.052,44.217,37.012,44.246,36.983,44.282,36.978,44.326,37.011,44.402,37.058,44.496,37.111,44.566,37.158,44.606,37.176,44.669,37.174,44.731,37.165,44.765,37.142,44.765,37.135,44.798,37.064,44.881,36.799,44.928,36.766,44.981,36.738,45.019,36.698,45.034,36.659,45.029,36.598,45.031,36.526,45.053,36.472,45.084,36.43,45.112,36.409,45.155,36.407,45.207,36.397,45.241,36.356,45.351,36.055,45.362,36.015,45.408,36.003,45.484,36.009,45.562,35.977,45.645,35.928,45.723,35.837,45.776,35.822,45.941,35.835,46.167,35.821,46.273,35.773,46.263,35.744,46.181,35.711,46.037,35.673,45.995,35.608,45.971,35.524,45.975,35.477,46.011,35.425,46.112,35.322,46.118,35.284,46.136,35.232,46.155,35.197,46.134,35.128,46.042,35.08,45.921,35.029,45.678,34.798,45.66,34.749,45.662,34.613,45.638,34.574,45.561,34.575,45.501,34.582,45.498,34.534,45.459,34.47,45.438,34.415,45.527,34.285,45.543,34.216,45.529,34.153,45.446,34.044,45.397,33.971,45.409,33.954,45.473,33.925,45.674,33.687,45.738,33.603,45.823,33.625,45.854,33.623,45.879,33.61,45.895,33.582,45.895,33.546,45.873,33.492,45.981,33.47,46.02,33.416,46.146,33.23,46.141,33.174,46.081,33.087,46.08,33.028,46.093,32.976,46.113,32.958,46.298,32.95,46.377,32.929,46.57,32.834,46.789,32.688,46.969,32.568,47.121,32.467,47.285,32.474,47.33,32.456,47.371,32.424,47.418,32.34,47.512,32.151,47.592,32.088,47.715,31.936,47.83,31.794,47.754,31.601,47.679,31.401,47.679,31.142,47.679,31.002,47.836,30.996,48.011,30.99,48.012,30.824,48.013,30.656,48.015,30.466,48.066,30.458,48.148,30.417,48.182,30.355,48.226,30.321,48.279,30.316,48.331,30.285,48.383,30.23,48.401,30.188,48.388,30.16,48.399,30.11,48.435,30.038,48.479,30.004,48.546,29.962,48.454,29.938,48.355,29.957,48.142,30.041,48.073,30.043,47.983,30.011,47.979,29.983,47.754,30.077,47.673,30.096,47.644,30.097,47.515,30.096,47.331,30.08,47.223,30.042,47.148,30.001,47.114,29.961,47.102,29.94,47.044,29.823,46.976,29.673,46.906,29.538,46.769,29.347,46.694,29.26,46.531,29.096,46.356,29.064,45.95,29.096,45.499,29.132,45.05,29.167,44.717,29.194,44.691,29.202,44.361,29.435,44.1,29.619,43.774,29.849,43.441,30.084,43.103,30.322,42.858,30.495,42.56,30.718,42.289,30.92,42.074,31.08,41.8,31.22,41.585,31.33,41.272,31.489,41.022,31.616,40.808,31.725,40.479,31.893,40.369,31.939,40.028,31.995,39.704,32.043,39.369,32.092,39.145,32.125,39.293,32.244,39.247,32.351,39.14,32.331,39.041,32.306,38.982,32.473,39.058,32.493,38.987,32.711,38.915,32.935,38.845,33.151,38.774,33.372,39.057,33.514,39.268,33.62,39.564,33.768,39.85,33.911,40.122,34.048,40.421,34.198,40.689,34.332,40.935,34.387,40.987,34.429,41.099,34.612,41.195,34.769,41.199,34.805,41.2,35.027,41.216,35.288,41.248,35.427,41.303,35.551,41.354,35.64,41.359,35.725,41.353,35.81,41.3,35.939,41.246,36.073,41.252,36.203,41.262,36.272,41.296,36.383,41.354,36.464,41.417,36.515,41.65,36.566,41.789,36.597,41.974,36.741,42.084,36.826,42.237,36.961,42.35,37.061,42.359,37.095,42.359,37.109]]]},{"n":"Iran","c":"IR","r":2,"ml":2.5,"lx":54.93,"ly":32.17,"b":[44.023,25.102,63.305,39.769],"p":[[[56.188,26.921,56.095,26.801,55.954,26.701,55.894,26.732,55.848,26.731,55.747,26.692,55.675,26.686,55.543,26.618,55.424,26.583,55.34,26.586,55.312,26.593,55.295,26.639,55.296,26.658,55.347,26.648,55.532,26.71,55.763,26.812,55.785,26.857,55.747,26.931,55.758,26.948,55.907,26.91,56.074,26.983,56.214,27.003,56.279,26.952,56.188,26.921]],[[53.914,37.344,54.192,37.332,54.3,37.354,54.459,37.408,54.579,37.44,54.64,37.445,54.699,37.47,54.745,37.502,54.849,37.723,54.9,37.778,55.076,37.902,55.225,37.981,55.381,38.051,55.578,38.1,55.841,38.095,56.05,38.078,56.171,38.078,56.229,38.073,56.272,38.08,56.297,38.095,56.324,38.191,56.367,38.223,56.441,38.249,56.544,38.25,56.67,38.257,56.775,38.25,56.907,38.213,57.079,38.21,57.194,38.216,57.26,38.18,57.308,38.13,57.331,38.089,57.337,38.033,57.336,37.99,57.354,37.973,57.424,37.948,57.521,37.928,57.711,37.905,57.888,37.861,57.981,37.83,58.109,37.783,58.262,37.666,58.318,37.647,58.387,37.635,58.436,37.639,58.55,37.688,58.65,37.652,58.701,37.656,58.815,37.683,58.937,37.65,59.241,37.521,59.274,37.524,59.302,37.511,59.327,37.481,59.345,37.445,59.367,37.334,59.455,37.253,59.562,37.179,59.687,37.138,59.949,37.042,60.063,36.963,60.178,36.829,60.321,36.654,60.341,36.638,60.708,36.643,61.12,36.643,61.17,36.572,61.16,36.433,61.175,36.29,61.212,36.191,61.212,36.099,61.183,36.053,61.159,36,61.153,35.977,61.206,35.944,61.252,35.868,61.259,35.762,61.236,35.706,61.239,35.659,61.262,35.62,61.282,35.553,61.279,35.514,61.246,35.474,61.226,35.424,61.199,35.362,61.189,35.312,61.14,35.289,61.1,35.272,61.107,35.209,61.126,35.157,61.15,35.094,61.123,35.051,61.107,35.001,61.07,34.922,61.08,34.856,61.04,34.799,60.991,34.75,60.958,34.71,60.951,34.654,60.915,34.634,60.845,34.588,60.802,34.555,60.739,34.545,60.726,34.518,60.736,34.492,60.763,34.475,60.804,34.418,60.889,34.319,60.643,34.307,60.57,34.22,60.486,34.095,60.527,33.842,60.486,33.712,60.511,33.639,60.574,33.588,60.655,33.56,60.806,33.559,60.907,33.539,60.917,33.505,60.859,33.456,60.767,33.364,60.718,33.324,60.561,33.138,60.562,33.059,60.577,32.995,60.645,32.794,60.71,32.6,60.829,32.249,60.827,32.168,60.79,31.987,60.788,31.877,60.804,31.734,60.792,31.661,60.821,31.495,60.854,31.483,61.111,31.451,61.346,31.422,61.66,31.382,61.755,31.285,61.814,31.073,61.811,30.913,61.784,30.832,61.559,30.599,61.332,30.364,61.104,30.128,60.843,29.859,61.034,29.663,61.152,29.543,61.318,29.373,61.339,29.332,61.338,29.265,61.509,29.006,61.569,28.871,61.623,28.792,61.758,28.668,61.89,28.547,62.033,28.491,62.131,28.479,62.353,28.415,62.434,28.364,62.565,28.235,62.718,28.253,62.749,28.253,62.758,28.244,62.763,28.202,62.74,28.002,62.782,27.801,62.812,27.497,62.801,27.445,62.764,27.357,62.763,27.3,62.753,27.266,62.763,27.25,62.812,27.229,62.915,27.218,63.167,27.252,63.196,27.244,63.256,27.208,63.302,27.151,63.305,27.125,63.242,27.078,63.231,26.998,63.25,26.879,63.242,26.865,63.186,26.838,63.168,26.666,63.158,26.65,63.093,26.632,62.787,26.644,62.752,26.639,62.636,26.594,62.439,26.561,62.385,26.543,62.312,26.491,62.26,26.427,62.25,26.369,62.239,26.357,62.126,26.369,62.089,26.318,61.87,26.242,61.842,26.226,61.81,26.165,61.781,25.996,61.754,25.843,61.738,25.821,61.669,25.769,61.662,25.751,61.671,25.692,61.64,25.585,61.615,25.286,61.588,25.202,61.533,25.196,61.49,25.154,61.412,25.102,61.243,25.142,61.109,25.184,60.664,25.282,60.615,25.33,60.588,25.414,60.511,25.437,60.4,25.312,60.025,25.384,59.897,25.362,59.818,25.401,59.616,25.403,59.456,25.481,59.227,25.428,59.046,25.417,58.798,25.555,58.531,25.592,58.314,25.581,58.203,25.592,58.022,25.641,57.937,25.692,57.796,25.653,57.733,25.725,57.335,25.792,57.261,25.919,57.206,26.037,57.201,26.159,57.104,26.371,57.072,26.68,57.036,26.801,56.982,26.905,56.91,26.995,56.813,27.09,56.728,27.128,56.356,27.2,56.284,27.191,56.118,27.143,55.941,27.038,55.65,26.978,55.592,26.932,55.519,26.83,55.424,26.771,55.294,26.786,55.155,26.725,54.896,26.557,54.759,26.505,54.645,26.509,54.522,26.589,54.247,26.697,54.069,26.732,53.823,26.708,53.706,26.726,53.507,26.852,53.455,26.943,53.342,27.004,52.983,27.142,52.692,27.323,52.638,27.392,52.603,27.493,52.476,27.617,52.192,27.717,52.031,27.824,51.842,27.848,51.666,27.845,51.589,27.864,51.519,27.91,51.279,28.131,51.276,28.219,51.128,28.435,51.094,28.512,51.062,28.726,51.021,28.782,50.867,28.87,50.843,28.928,50.876,29.004,50.876,29.063,50.796,29.117,50.675,29.147,50.646,29.212,50.668,29.34,50.65,29.42,50.544,29.548,50.387,29.679,50.23,29.873,50.169,29.921,50.129,30.048,50.072,30.199,49.983,30.209,49.555,30.029,49.43,30.13,49.054,30.307,49.028,30.333,49.002,30.374,49.049,30.397,49.096,30.407,49.19,30.375,49.247,30.413,49.225,30.472,49.13,30.509,49.002,30.507,49.037,30.45,48.917,30.397,48.891,30.328,48.909,30.241,48.919,30.121,48.87,30.062,48.832,30.035,48.671,30.028,48.596,29.975,48.546,29.962,48.479,30.004,48.435,30.038,48.399,30.11,48.388,30.16,48.401,30.188,48.383,30.23,48.331,30.285,48.279,30.316,48.226,30.321,48.182,30.355,48.148,30.417,48.066,30.458,48.015,30.466,48.013,30.656,48.012,30.824,48.011,30.99,47.836,30.996,47.679,31.002,47.679,31.142,47.679,31.401,47.754,31.601,47.83,31.794,47.715,31.936,47.592,32.088,47.512,32.151,47.418,32.34,47.371,32.424,47.33,32.456,47.285,32.474,47.121,32.467,46.969,32.568,46.789,32.688,46.57,32.834,46.377,32.929,46.298,32.95,46.113,32.958,46.093,32.976,46.08,33.028,46.081,33.087,46.141,33.174,46.146,33.23,46.02,33.416,45.981,33.47,45.873,33.492,45.895,33.546,45.895,33.582,45.879,33.61,45.854,33.623,45.823,33.625,45.738,33.603,45.674,33.687,45.473,33.925,45.409,33.954,45.397,33.971,45.446,34.044,45.529,34.153,45.543,34.216,45.527,34.285,45.438,34.415,45.459,34.47,45.498,34.534,45.501,34.582,45.561,34.575,45.638,34.574,45.662,34.613,45.66,34.749,45.678,34.798,45.921,35.029,46.042,35.08,46.134,35.128,46.155,35.197,46.136,35.232,46.118,35.284,46.112,35.322,46.011,35.425,45.975,35.477,45.971,35.524,45.995,35.608,46.037,35.673,46.181,35.711,46.263,35.744,46.273,35.773,46.167,35.821,45.941,35.835,45.776,35.822,45.723,35.837,45.645,35.928,45.562,35.977,45.484,36.009,45.408,36.003,45.362,36.015,45.351,36.055,45.241,36.356,45.207,36.397,45.155,36.407,45.112,36.409,45.084,36.43,45.053,36.472,45.031,36.526,45.029,36.598,45.034,36.659,45.019,36.698,44.981,36.738,44.928,36.766,44.881,36.799,44.798,37.064,44.765,37.135,44.765,37.142,44.767,37.156,44.758,37.217,44.797,37.27,44.794,37.29,44.715,37.357,44.604,37.424,44.574,37.435,44.573,37.506,44.577,37.56,44.567,37.609,44.546,37.636,44.545,37.658,44.59,37.71,44.561,37.745,44.398,37.829,44.336,37.872,44.223,37.88,44.211,37.908,44.229,37.967,44.268,38.039,44.329,38.109,44.349,38.146,44.373,38.21,44.381,38.255,44.45,38.318,44.45,38.334,44.431,38.357,44.376,38.37,44.32,38.375,44.299,38.386,44.291,38.42,44.298,38.558,44.28,38.641,44.257,38.701,44.272,38.836,44.232,38.863,44.171,38.934,44.145,38.994,44.159,39.017,44.172,39.056,44.181,39.108,44.178,39.145,44.121,39.181,44.079,39.218,44.074,39.26,44.058,39.311,44.034,39.351,44.023,39.377,44.044,39.393,44.124,39.405,44.24,39.397,44.335,39.396,44.389,39.422,44.456,39.667,44.517,39.731,44.587,39.769,44.725,39.682,44.782,39.651,44.817,39.65,44.838,39.629,45,39.424,45.072,39.363,45.113,39.312,45.141,39.254,45.191,39.216,45.256,39.195,45.336,39.139,45.389,39.096,45.48,39.006,45.575,38.973,45.922,38.908,46.114,38.878,46.17,38.869,46.318,38.913,46.491,38.907,46.555,38.904,46.783,39.087,46.853,39.148,46.989,39.18,47.065,39.253,47.188,39.341,47.338,39.424,47.476,39.498,47.582,39.543,47.773,39.649,47.892,39.685,47.996,39.684,48.151,39.561,48.282,39.448,48.322,39.399,48.257,39.355,48.136,39.312,48.113,39.281,48.104,39.241,48.109,39.203,48.125,39.172,48.274,39.099,48.291,39.059,48.292,39.019,48.275,38.994,48.242,38.979,48.139,38.959,48.05,38.935,48.019,38.912,47.993,38.884,47.996,38.854,48.023,38.819,48.205,38.724,48.225,38.689,48.261,38.642,48.306,38.613,48.381,38.606,48.417,38.586,48.593,38.411,48.636,38.399,48.84,38.437,48.869,38.435,48.871,38.393,48.901,38.144,48.925,38.015,48.96,37.89,49.015,37.776,49.081,37.668,49.171,37.601,49.372,37.52,49.47,37.497,49.727,37.481,49.981,37.445,50.13,37.407,50.176,37.381,50.214,37.34,50.338,37.149,50.533,37.014,50.927,36.81,51.119,36.743,51.762,36.615,52.19,36.622,53.374,36.869,53.768,36.93,53.915,36.93,53.827,36.881,53.679,36.853,53.769,36.818,53.906,36.813,53.97,36.818,54.016,36.85,54.024,36.901,54.017,36.952,53.952,37.182,53.914,37.344]]]},{"n":"Indonesia","c":"ID","r":2,"ml":1.7,"lx":101.89,"ly":-0.95,"b":[95.207,-10.91,140.976,5.907],"p":[[[97.482,1.465,97.698,1.184,97.786,1.146,97.903,1.018,97.932,0.974,97.902,0.884,97.876,0.628,97.82,0.564,97.684,0.596,97.683,0.641,97.604,0.834,97.461,0.941,97.405,0.947,97.369,1.057,97.297,1.187,97.079,1.425,97.244,1.424,97.324,1.482,97.343,1.528,97.356,1.54,97.482,1.465]],[[99.164,-1.778,99.072,-1.783,98.874,-1.664,98.828,-1.61,98.816,-1.538,98.627,-1.261,98.602,-1.198,98.676,-0.971,98.869,-0.916,98.933,-0.954,98.955,-1.056,99.065,-1.241,99.101,-1.34,99.129,-1.384,99.14,-1.418,99.131,-1.442,99.21,-1.559,99.267,-1.628,99.271,-1.738,99.164,-1.778]],[[116.641,-8.614,116.514,-8.821,116.559,-8.854,116.587,-8.886,116.377,-8.929,116.29,-8.906,116.239,-8.912,116.027,-8.873,115.875,-8.826,115.857,-8.788,115.869,-8.743,115.914,-8.758,116.032,-8.765,116.076,-8.745,116.078,-8.611,116.061,-8.437,116.22,-8.295,116.304,-8.238,116.402,-8.204,116.647,-8.283,116.688,-8.304,116.719,-8.336,116.734,-8.387,116.641,-8.614]],[[115.448,-8.155,115.549,-8.208,115.691,-8.364,115.704,-8.407,115.661,-8.448,115.56,-8.514,115.334,-8.616,115.295,-8.664,115.247,-8.758,115.236,-8.798,115.22,-8.82,115.194,-8.835,115.145,-8.849,115.092,-8.829,115.14,-8.769,115.142,-8.697,115.106,-8.629,115.055,-8.573,114.952,-8.496,114.842,-8.429,114.731,-8.394,114.613,-8.378,114.571,-8.345,114.502,-8.261,114.479,-8.215,114.468,-8.166,114.475,-8.119,114.504,-8.117,114.62,-8.128,114.833,-8.183,114.938,-8.187,114.998,-8.174,115.154,-8.066,115.191,-8.067,115.34,-8.115,115.448,-8.155]],[[106.046,-1.669,106.08,-1.738,106.127,-1.8,106.162,-1.867,106.209,-2.189,106.366,-2.465,106.818,-2.573,106.744,-2.618,106.707,-2.658,106.679,-2.704,106.612,-2.896,106.619,-2.936,106.658,-3.001,106.667,-3.072,106.611,-3.071,106.547,-3.056,106.496,-3.029,106.449,-2.994,106.397,-2.967,106.342,-2.949,106.25,-2.894,106.126,-2.855,105.999,-2.825,105.937,-2.744,105.908,-2.643,105.939,-2.493,105.908,-2.452,105.862,-2.415,105.807,-2.307,105.786,-2.181,105.705,-2.133,105.599,-2.103,105.553,-2.079,105.343,-2.125,105.293,-2.114,105.248,-2.079,105.133,-2.043,105.138,-1.973,105.191,-1.917,105.316,-1.861,105.375,-1.813,105.387,-1.751,105.364,-1.705,105.373,-1.657,105.413,-1.611,105.46,-1.575,105.585,-1.527,105.64,-1.61,105.668,-1.68,105.701,-1.731,105.754,-1.659,105.72,-1.534,105.816,-1.506,105.91,-1.505,105.981,-1.539,106.027,-1.593,106.046,-1.669]],[[123.18,-4.551,123.203,-4.766,123.196,-4.823,123.139,-4.74,123.119,-4.723,123.104,-4.74,123.084,-4.749,123.055,-4.748,123.018,-4.832,123.015,-4.91,122.987,-4.963,122.972,-5.138,122.981,-5.186,123.025,-5.162,123.051,-5.156,123.15,-5.224,123.202,-5.273,123.187,-5.333,123.121,-5.393,123.043,-5.419,122.986,-5.394,122.969,-5.406,122.935,-5.437,122.909,-5.477,122.916,-5.519,122.85,-5.638,122.812,-5.671,122.733,-5.635,122.684,-5.666,122.645,-5.663,122.585,-5.545,122.586,-5.489,122.642,-5.426,122.643,-5.381,122.67,-5.331,122.731,-5.262,122.767,-5.21,122.768,-5.177,122.794,-5.052,122.804,-5,122.821,-4.944,122.849,-4.831,122.853,-4.618,122.947,-4.443,123.038,-4.395,123.075,-4.387,123.069,-4.434,123.18,-4.551]],[[122.645,-5.269,122.619,-5.336,122.564,-5.387,122.52,-5.391,122.474,-5.381,122.392,-5.335,122.371,-5.383,122.307,-5.381,122.283,-5.32,122.329,-5.138,122.396,-5.07,122.39,-4.999,122.334,-4.847,122.369,-4.767,122.524,-4.707,122.66,-4.634,122.702,-4.619,122.74,-4.675,122.76,-4.934,122.614,-5.139,122.645,-5.269]],[[108.316,3.69,108.18,3.653,108.1,3.705,108.186,3.768,108.216,3.772,108.236,3.785,108.243,3.81,108.088,3.852,108.045,3.889,108.002,3.983,108.004,4.043,108.202,4.2,108.248,4.217,108.256,4.152,108.393,3.986,108.399,3.876,108.394,3.836,108.316,3.69]],[[108.207,-2.998,108.192,-3.103,108.167,-3.143,108.084,-3.195,108.055,-3.227,107.977,-3.222,107.967,-3.167,107.941,-3.129,107.858,-3.086,107.837,-3.097,107.822,-3.161,107.66,-3.206,107.614,-3.209,107.637,-3.125,107.595,-3.058,107.592,-2.977,107.584,-2.941,107.563,-2.92,107.605,-2.863,107.598,-2.8,107.642,-2.732,107.666,-2.566,107.838,-2.53,107.875,-2.56,108.074,-2.597,108.215,-2.697,108.291,-2.83,108.207,-2.998]],[[135.383,-0.651,135.596,-0.69,135.673,-0.688,135.749,-0.733,135.841,-0.712,135.894,-0.726,136.069,-0.878,136.155,-0.978,136.283,-1.065,136.375,-1.094,136.305,-1.173,136.165,-1.215,136.11,-1.217,136.003,-1.17,135.915,-1.178,135.839,-1.119,135.826,-1.028,135.747,-0.823,135.646,-0.882,135.524,-0.787,135.491,-0.785,135.483,-0.801,135.432,-0.769,135.388,-0.705,135.383,-0.651]],[[135.474,-1.592,135.869,-1.642,135.976,-1.636,136.202,-1.655,136.39,-1.722,136.719,-1.734,136.817,-1.754,136.893,-1.8,136.709,-1.838,136.622,-1.873,136.461,-1.89,136.326,-1.872,136.228,-1.894,136.193,-1.859,136.049,-1.824,135.866,-1.752,135.488,-1.668,135.47,-1.616,135.474,-1.592]],[[130.813,-0.004,130.987,-0.047,131.026,-0.04,131.277,-0.15,131.317,-0.204,131.303,-0.241,131.34,-0.29,131.258,-0.366,131.218,-0.374,131.178,-0.346,131.098,-0.33,131.005,-0.361,130.946,-0.338,130.897,-0.268,130.808,-0.226,130.683,-0.081,130.622,-0.086,130.638,-0.143,130.691,-0.181,130.761,-0.291,130.802,-0.302,130.843,-0.298,130.899,-0.344,130.896,-0.416,130.75,-0.444,130.7,-0.392,130.689,-0.297,130.607,-0.329,130.575,-0.362,130.551,-0.366,130.496,-0.267,130.341,-0.262,130.237,-0.21,130.288,-0.155,130.295,-0.101,130.363,-0.073,130.431,-0.098,130.5,-0.06,130.548,-0.07,130.584,-0.045,130.722,-0.03,130.813,-0.004]],[[128.454,2.052,128.296,2.035,128.26,2.083,128.218,2.297,128.33,2.469,128.472,2.571,128.569,2.596,128.602,2.598,128.688,2.474,128.623,2.224,128.548,2.097,128.454,2.052]],[[128.153,-1.661,128.092,-1.701,128.061,-1.712,127.914,-1.685,127.741,-1.691,127.562,-1.729,127.458,-1.697,127.392,-1.645,127.395,-1.59,127.457,-1.454,127.592,-1.351,127.647,-1.332,127.743,-1.36,127.905,-1.439,128.033,-1.532,128.149,-1.604,128.153,-1.661]],[[126.055,-2.451,126.038,-2.469,125.978,-2.415,125.938,-2.263,125.903,-2.222,125.863,-2.077,125.873,-2.036,125.923,-1.975,125.963,-1.976,125.993,-2.012,125.976,-2.168,126.066,-2.366,126.055,-2.451]],[[126.024,-1.79,126.332,-1.823,126.288,-1.859,125.956,-1.917,125.839,-1.906,125.479,-1.94,125.433,-1.938,125.426,-1.882,125.387,-1.843,125.445,-1.809,125.521,-1.801,125.72,-1.814,126.024,-1.79]],[[124.97,-1.705,125.063,-1.741,125.096,-1.741,125.127,-1.699,125.146,-1.693,125.188,-1.713,125.198,-1.78,125.258,-1.771,125.305,-1.794,125.32,-1.81,125.314,-1.877,125.135,-1.889,125.007,-1.943,124.834,-1.894,124.639,-1.978,124.521,-2.007,124.418,-2.005,124.33,-1.859,124.381,-1.687,124.418,-1.659,124.483,-1.644,124.664,-1.636,124.97,-1.705]],[[131.326,-8,131.309,-8.011,131.185,-7.998,131.114,-7.997,131.123,-7.922,131.087,-7.865,131.137,-7.782,131.138,-7.685,131.19,-7.672,131.197,-7.617,131.26,-7.471,131.297,-7.438,131.349,-7.425,131.411,-7.34,131.446,-7.315,131.483,-7.251,131.535,-7.221,131.531,-7.165,131.561,-7.136,131.643,-7.113,131.701,-7.14,131.736,-7.197,131.644,-7.267,131.691,-7.439,131.624,-7.626,131.58,-7.682,131.498,-7.731,131.474,-7.777,131.377,-7.869,131.348,-7.948,131.343,-7.981,131.326,-8]],[[126.801,-7.668,126.814,-7.717,126.813,-7.738,126.693,-7.754,126.577,-7.808,126.518,-7.87,126.472,-7.95,126.313,-7.918,126.171,-7.912,126.108,-7.884,126.04,-7.886,125.952,-7.911,125.826,-7.979,125.798,-7.985,125.808,-7.881,125.843,-7.817,125.975,-7.663,126.085,-7.697,126.214,-7.707,126.359,-7.677,126.463,-7.608,126.61,-7.572,126.726,-7.662,126.801,-7.668]],[[124.576,-8.141,124.6,-8.202,124.677,-8.168,124.752,-8.16,124.924,-8.166,125.05,-8.18,125.125,-8.205,125.132,-8.326,125.097,-8.353,124.444,-8.445,124.381,-8.415,124.356,-8.386,124.426,-8.296,124.394,-8.253,124.431,-8.183,124.509,-8.135,124.576,-8.141]],[[131.002,-1.316,130.967,-1.343,130.845,-1.317,130.782,-1.255,130.739,-1.173,130.712,-1.104,130.704,-1.05,130.668,-0.984,130.673,-0.96,130.897,-0.89,130.939,-0.915,131.033,-0.918,131.074,-0.968,131.046,-1.188,131.002,-1.316]],[[96.493,5.229,96.615,5.22,96.843,5.274,96.968,5.269,97.086,5.23,97.19,5.207,97.451,5.236,97.5,5.228,97.547,5.206,97.588,5.17,97.707,5.04,97.908,4.88,97.967,4.777,98,4.662,98.021,4.635,98.248,4.415,98.273,4.322,98.241,4.195,98.307,4.093,98.528,3.998,98.659,3.928,98.687,3.886,98.706,3.835,98.778,3.759,98.869,3.71,99.151,3.581,99.521,3.311,99.732,3.183,99.907,2.988,99.969,2.895,100.021,2.794,100.127,2.648,100.307,2.467,100.353,2.411,100.401,2.332,100.457,2.257,100.524,2.189,100.604,2.137,100.685,2.12,100.817,1.989,100.888,1.948,100.877,2.051,100.817,2.14,100.818,2.194,100.828,2.243,100.877,2.283,100.936,2.295,101.046,2.257,101.225,2.102,101.301,2.012,101.358,1.887,101.405,1.757,101.477,1.693,101.575,1.671,101.684,1.661,101.785,1.621,102.02,1.442,102.098,1.358,102.157,1.259,102.198,1.142,102.223,1.019,102.239,0.99,102.39,0.842,102.469,0.779,102.566,0.749,102.849,0.715,102.949,0.664,103.032,0.579,103.067,0.492,103.008,0.415,102.786,0.298,102.55,0.216,102.78,0.244,102.896,0.279,103.003,0.332,103.109,0.4,103.277,0.495,103.339,0.514,103.412,0.507,103.479,0.48,103.579,0.387,103.673,0.289,103.743,0.174,103.787,0.047,103.706,-0.02,103.589,-0.069,103.429,-0.192,103.412,-0.24,103.444,-0.272,103.405,-0.362,103.495,-0.418,103.509,-0.466,103.431,-0.534,103.439,-0.576,103.533,-0.755,103.578,-0.796,103.721,-0.887,103.94,-0.979,104.061,-1.021,104.199,-1.054,104.258,-1.053,104.361,-1.038,104.381,-1.074,104.426,-1.251,104.447,-1.362,104.478,-1.6,104.519,-1.699,104.516,-1.819,104.569,-1.922,104.676,-1.987,104.791,-2.041,104.845,-2.093,104.845,-2.172,104.826,-2.234,104.787,-2.283,104.668,-2.386,104.647,-2.43,104.631,-2.543,104.651,-2.595,104.698,-2.598,104.736,-2.571,104.878,-2.419,104.917,-2.392,104.971,-2.371,105.026,-2.358,105.287,-2.356,105.397,-2.38,105.495,-2.43,105.582,-2.492,105.899,-2.888,106.044,-3.106,106.056,-3.161,106.058,-3.217,106.034,-3.261,105.901,-3.41,105.885,-3.451,105.844,-3.614,105.852,-3.731,105.896,-3.78,105.93,-3.833,105.928,-3.881,105.841,-4.122,105.831,-4.163,105.887,-4.554,105.891,-4.66,105.879,-4.794,105.887,-5.01,105.816,-5.677,105.803,-5.716,105.748,-5.818,105.677,-5.818,105.619,-5.8,105.578,-5.761,105.556,-5.712,105.523,-5.673,105.349,-5.55,105.304,-5.57,105.128,-5.723,105.081,-5.746,105.023,-5.727,104.93,-5.681,104.64,-5.52,104.622,-5.572,104.618,-5.642,104.676,-5.816,104.684,-5.893,104.631,-5.908,104.602,-5.905,104.481,-5.803,104.37,-5.691,104.243,-5.539,104.15,-5.467,104.067,-5.386,103.831,-5.08,103.77,-5.033,103.406,-4.816,103.332,-4.765,103.239,-4.676,103.139,-4.596,102.919,-4.471,102.538,-4.152,102.372,-3.969,102.188,-3.675,102.128,-3.599,101.818,-3.378,101.649,-3.244,101.579,-3.167,101.414,-2.899,101.366,-2.808,101.306,-2.729,101.206,-2.664,101.119,-2.588,100.944,-2.345,100.89,-2.249,100.848,-2.144,100.855,-1.934,100.487,-1.299,100.394,-1.101,100.308,-0.827,100.289,-0.799,100.088,-0.553,100.017,-0.474,99.931,-0.4,99.86,-0.314,99.721,-0.033,99.67,0.045,99.598,0.102,99.335,0.209,99.236,0.268,99.159,0.352,99.112,0.459,99.06,0.686,98.936,1.032,98.796,1.495,98.703,1.702,98.595,1.865,98.564,1.902,98.087,2.195,98.005,2.238,97.919,2.264,97.795,2.283,97.701,2.359,97.662,2.494,97.641,2.676,97.617,2.785,97.591,2.847,97.391,2.975,97.313,3.077,97.248,3.189,97.188,3.276,96.969,3.575,96.894,3.654,96.801,3.709,96.525,3.767,96.445,3.816,96.311,3.986,96.23,4.073,95.988,4.263,95.579,4.662,95.495,4.761,95.432,4.865,95.381,4.976,95.207,5.284,95.221,5.346,95.247,5.411,95.243,5.464,95.224,5.517,95.228,5.565,95.28,5.593,95.396,5.629,95.517,5.625,95.629,5.609,95.737,5.579,95.841,5.515,96.027,5.351,96.133,5.294,96.251,5.267,96.493,5.229]],[[122.783,-8.612,122.642,-8.647,122.554,-8.681,122.47,-8.725,122.417,-8.735,122.321,-8.738,122.186,-8.73,122.094,-8.745,121.839,-8.86,121.738,-8.87,121.651,-8.899,121.621,-8.854,121.585,-8.821,121.5,-8.812,121.415,-8.815,121.328,-8.917,121.191,-8.896,121.138,-8.904,121.086,-8.926,121.035,-8.935,120.982,-8.928,120.781,-8.849,120.55,-8.802,120.32,-8.82,120.121,-8.777,120.012,-8.81,119.909,-8.858,119.879,-8.808,119.841,-8.764,119.808,-8.698,119.807,-8.623,119.818,-8.571,119.848,-8.523,119.866,-8.473,119.875,-8.42,119.918,-8.445,119.964,-8.436,120.099,-8.378,120.231,-8.29,120.354,-8.258,120.425,-8.249,120.486,-8.266,120.547,-8.26,120.61,-8.24,120.71,-8.308,120.751,-8.321,120.886,-8.327,121.009,-8.366,121.118,-8.424,121.277,-8.478,121.372,-8.551,121.445,-8.578,121.498,-8.585,121.548,-8.575,121.61,-8.526,121.683,-8.506,121.747,-8.507,121.863,-8.494,121.912,-8.482,121.967,-8.455,122.02,-8.472,122.067,-8.497,122.263,-8.625,122.323,-8.628,122.433,-8.601,122.467,-8.566,122.484,-8.514,122.514,-8.47,122.556,-8.432,122.604,-8.402,122.75,-8.353,122.85,-8.304,122.919,-8.222,122.759,-8.186,122.792,-8.127,122.846,-8.093,122.917,-8.106,122.978,-8.152,123.006,-8.329,122.955,-8.354,122.924,-8.381,122.902,-8.416,122.811,-8.481,122.847,-8.562,122.82,-8.596,122.783,-8.612]],[[120.013,-9.375,120.058,-9.42,120.221,-9.506,120.248,-9.543,120.258,-9.603,120.291,-9.648,120.365,-9.655,120.444,-9.646,120.504,-9.674,120.556,-9.719,120.633,-9.806,120.7,-9.903,120.784,-9.957,120.833,-10.037,120.804,-10.108,120.698,-10.207,120.64,-10.228,120.562,-10.236,120.439,-10.294,120.395,-10.263,120.255,-10.242,120.145,-10.2,120.052,-10.123,119.998,-10.04,119.931,-9.967,119.813,-9.917,119.601,-9.774,119.47,-9.761,119.417,-9.771,119.363,-9.772,119.085,-9.707,119.042,-9.669,119.008,-9.621,118.977,-9.573,118.959,-9.519,118.994,-9.472,119.031,-9.44,119.186,-9.384,119.296,-9.367,119.424,-9.37,119.615,-9.352,119.795,-9.38,119.851,-9.36,119.942,-9.301,119.974,-9.322,120.013,-9.375]],[[118.242,-8.318,118.292,-8.357,118.338,-8.354,118.433,-8.293,118.491,-8.271,118.552,-8.27,118.612,-8.281,118.671,-8.323,118.692,-8.393,118.714,-8.415,118.748,-8.331,118.794,-8.306,118.846,-8.293,118.926,-8.298,118.988,-8.338,119.044,-8.457,119.042,-8.561,119.063,-8.6,119.101,-8.628,119.13,-8.668,119.104,-8.71,119.079,-8.73,119.006,-8.75,118.971,-8.741,118.939,-8.713,118.903,-8.703,118.821,-8.712,118.746,-8.735,118.756,-8.774,118.818,-8.791,118.837,-8.809,118.833,-8.833,118.808,-8.838,118.728,-8.805,118.674,-8.812,118.479,-8.856,118.427,-8.855,118.398,-8.813,118.4,-8.704,118.379,-8.675,118.234,-8.808,118.19,-8.841,118.132,-8.856,118.071,-8.851,117.861,-8.931,117.795,-8.92,117.732,-8.92,117.508,-9.008,117.388,-9.032,117.326,-9.034,117.265,-9.026,117.21,-9.034,117.161,-9.069,117.061,-9.099,116.958,-9.076,116.871,-9.046,116.788,-9.006,116.768,-8.955,116.772,-8.894,116.807,-8.811,116.783,-8.665,116.801,-8.598,116.835,-8.532,116.886,-8.508,116.953,-8.503,117.064,-8.444,117.165,-8.367,117.224,-8.375,117.357,-8.429,117.435,-8.435,117.567,-8.426,117.622,-8.46,117.643,-8.536,117.673,-8.563,117.712,-8.583,117.806,-8.711,117.893,-8.704,117.97,-8.728,118.104,-8.65,118.206,-8.652,118.235,-8.592,118.174,-8.528,118.1,-8.475,118.061,-8.464,118.018,-8.467,117.979,-8.459,117.815,-8.342,117.766,-8.279,117.738,-8.205,117.755,-8.15,117.868,-8.101,117.921,-8.089,118.117,-8.122,118.151,-8.15,118.203,-8.267,118.242,-8.318]],[[124.889,0.995,124.698,0.826,124.64,0.744,124.589,0.655,124.514,0.557,124.428,0.471,124.384,0.445,124.278,0.398,124.217,0.38,124.101,0.375,123.754,0.306,123.64,0.297,123.526,0.3,123.31,0.318,123.265,0.327,123.179,0.416,123.083,0.486,122.997,0.494,122.91,0.486,122.281,0.481,122.061,0.468,121.842,0.437,121.723,0.451,121.605,0.486,121.516,0.498,121.426,0.495,121.013,0.442,120.909,0.447,120.7,0.515,120.579,0.528,120.46,0.51,120.349,0.449,120.307,0.408,120.192,0.269,120.127,0.167,120.078,0.04,120.036,-0.09,120.013,-0.196,120.012,-0.307,120.032,-0.432,120.063,-0.556,120.097,-0.65,120.241,-0.868,120.27,-0.899,120.425,-0.961,120.518,-1.039,120.605,-1.258,120.667,-1.37,120.729,-1.371,120.797,-1.364,120.916,-1.378,121.034,-1.407,121.149,-1.339,121.213,-1.212,121.277,-1.118,121.431,-0.939,121.519,-0.856,121.576,-0.829,121.633,-0.84,121.681,-0.888,121.738,-0.926,121.853,-0.946,121.97,-0.933,122.094,-0.875,122.138,-0.839,122.175,-0.794,122.28,-0.757,122.53,-0.757,122.659,-0.77,122.889,-0.755,122.886,-0.722,122.841,-0.687,122.829,-0.659,122.872,-0.641,123.02,-0.6,123.171,-0.571,123.281,-0.592,123.38,-0.649,123.417,-0.707,123.434,-0.778,123.396,-0.962,123.378,-1.004,123.3,-1.026,123.226,-1.002,123.153,-0.907,123.049,-0.872,122.903,-0.901,122.853,-0.928,122.807,-0.966,122.725,-1.064,122.656,-1.175,122.507,-1.348,122.334,-1.498,122.251,-1.555,122.158,-1.594,121.859,-1.693,121.78,-1.767,121.719,-1.863,121.651,-1.895,121.573,-1.906,121.514,-1.888,121.395,-1.834,121.355,-1.878,121.349,-1.946,121.408,-1.97,121.502,-2.045,121.575,-2.151,121.622,-2.174,121.726,-2.208,121.77,-2.241,121.848,-2.332,121.972,-2.542,122.014,-2.656,122.083,-2.75,122.292,-2.908,122.303,-2.952,122.29,-3.004,122.307,-3.052,122.381,-3.142,122.399,-3.201,122.317,-3.275,122.313,-3.383,122.263,-3.527,122.251,-3.576,122.253,-3.62,122.288,-3.662,122.329,-3.694,122.385,-3.711,122.435,-3.74,122.529,-3.853,122.579,-3.882,122.61,-3.923,122.607,-3.985,122.65,-4.021,122.69,-4.084,122.75,-4.1,122.779,-4.082,122.798,-4.054,122.848,-4.065,122.877,-4.109,122.894,-4.166,122.9,-4.229,122.897,-4.349,122.872,-4.392,122.818,-4.39,122.72,-4.341,122.715,-4.376,122.722,-4.411,122.672,-4.422,122.615,-4.417,122.471,-4.422,122.207,-4.496,122.114,-4.54,122.054,-4.62,122.05,-4.675,122.073,-4.792,122.038,-4.832,121.917,-4.848,121.748,-4.817,121.646,-4.786,121.589,-4.76,121.514,-4.681,121.487,-4.581,121.541,-4.283,121.557,-4.245,121.583,-4.211,121.612,-4.156,121.618,-4.093,121.537,-4.015,121.416,-3.984,121.313,-3.919,120.914,-3.556,120.892,-3.521,120.891,-3.46,120.907,-3.404,121.038,-3.205,121.054,-3.167,121.07,-3.01,121.067,-2.881,121.052,-2.752,120.99,-2.67,120.879,-2.646,120.765,-2.642,120.654,-2.668,120.544,-2.733,120.341,-2.87,120.261,-2.949,120.254,-3.053,120.3,-3.154,120.36,-3.247,120.392,-3.348,120.437,-3.707,120.435,-3.748,120.383,-3.852,120.363,-4.086,120.385,-4.415,120.42,-4.617,120.405,-4.727,120.31,-4.963,120.281,-5.093,120.279,-5.146,120.391,-5.393,120.417,-5.49,120.43,-5.591,120.312,-5.542,120.256,-5.544,120.201,-5.559,120.077,-5.575,119.952,-5.578,119.908,-5.596,119.818,-5.662,119.764,-5.688,119.717,-5.693,119.557,-5.611,119.463,-5.522,119.376,-5.425,119.36,-5.314,119.391,-5.201,119.434,-5.079,119.52,-4.877,119.516,-4.742,119.545,-4.631,119.594,-4.523,119.612,-4.424,119.624,-4.034,119.611,-4,119.494,-3.769,119.48,-3.73,119.479,-3.667,119.492,-3.608,119.495,-3.554,119.467,-3.513,119.42,-3.475,119.362,-3.459,119.24,-3.475,118.995,-3.538,118.922,-3.483,118.868,-3.398,118.833,-3.28,118.813,-3.157,118.822,-3.041,118.858,-2.929,118.829,-2.85,118.784,-2.765,118.783,-2.721,118.809,-2.682,118.853,-2.65,118.908,-2.631,118.958,-2.597,119.092,-2.483,119.135,-2.382,119.138,-2.258,119.172,-2.14,119.241,-2.031,119.322,-1.93,119.348,-1.825,119.308,-1.66,119.324,-1.584,119.31,-1.496,119.309,-1.408,119.359,-1.243,119.508,-0.907,119.654,-0.728,119.711,-0.681,119.787,-0.764,119.844,-0.862,119.845,-0.773,119.83,-0.686,119.772,-0.484,119.722,-0.088,119.736,-0.051,119.787,-0.057,119.838,-0.022,119.866,0.04,119.812,0.187,119.809,0.239,119.913,0.445,119.998,0.52,120.035,0.567,120.056,0.693,120.101,0.74,120.157,0.774,120.23,0.861,120.27,0.971,120.294,0.979,120.322,0.983,120.367,0.888,120.416,0.849,120.517,0.818,120.603,0.854,120.626,0.902,120.659,0.944,120.711,0.987,120.755,1.036,120.804,1.149,120.868,1.253,120.912,1.289,120.965,1.312,121.025,1.326,121.082,1.328,121.208,1.263,121.282,1.25,121.357,1.255,121.404,1.244,121.44,1.214,121.473,1.156,121.513,1.105,121.551,1.08,121.592,1.068,121.867,1.089,122.108,1.031,122.437,1.018,122.549,0.984,122.657,0.941,122.79,0.863,122.838,0.846,122.892,0.85,122.96,0.923,123.013,0.939,123.067,0.942,123.278,0.928,123.847,0.838,123.931,0.85,124.274,1.022,124.411,1.185,124.534,1.23,124.575,1.304,124.6,1.392,124.644,1.416,124.747,1.441,124.788,1.468,124.861,1.576,124.947,1.672,124.989,1.701,125.111,1.686,125.165,1.644,125.234,1.502,125.222,1.479,125.141,1.408,125.117,1.379,125.028,1.18,124.967,1.083,124.889,0.995]],[[107.374,-6.008,107.475,-6.122,107.563,-6.183,107.667,-6.216,107.776,-6.219,107.884,-6.233,108.009,-6.277,108.138,-6.297,108.197,-6.289,108.254,-6.267,108.295,-6.265,108.33,-6.286,108.419,-6.383,108.516,-6.471,108.538,-6.516,108.604,-6.729,108.678,-6.791,108.78,-6.808,108.899,-6.808,109.018,-6.817,109.294,-6.867,109.404,-6.86,109.501,-6.81,109.587,-6.843,109.821,-6.902,109.936,-6.916,110.067,-6.899,110.198,-6.895,110.261,-6.912,110.321,-6.938,110.373,-6.948,110.426,-6.947,110.521,-6.897,110.584,-6.806,110.634,-6.69,110.674,-6.57,110.701,-6.518,110.737,-6.472,110.784,-6.443,110.835,-6.424,110.972,-6.436,111.001,-6.465,111.154,-6.669,111.182,-6.687,111.342,-6.7,111.387,-6.693,111.484,-6.652,111.54,-6.648,111.644,-6.699,111.688,-6.742,111.738,-6.773,111.99,-6.806,112.087,-6.893,112.137,-6.905,112.312,-6.894,112.434,-6.903,112.539,-6.926,112.587,-7.051,112.626,-7.178,112.649,-7.221,112.752,-7.265,112.794,-7.304,112.783,-7.432,112.795,-7.552,113.014,-7.658,113.248,-7.718,113.498,-7.724,113.747,-7.703,113.876,-7.677,114.037,-7.632,114.071,-7.633,114.383,-7.771,114.409,-7.792,114.444,-7.896,114.443,-8.005,114.385,-8.263,114.381,-8.334,114.387,-8.405,114.449,-8.559,114.482,-8.604,114.595,-8.685,114.599,-8.727,114.584,-8.77,114.459,-8.741,114.383,-8.705,114.339,-8.647,114.277,-8.615,114.16,-8.626,113.94,-8.568,113.693,-8.478,113.253,-8.287,113.134,-8.288,113.019,-8.313,112.898,-8.361,112.772,-8.396,112.679,-8.409,112.586,-8.4,112.352,-8.354,112.115,-8.324,111.51,-8.305,111.339,-8.262,111.055,-8.24,110.83,-8.202,110.607,-8.149,110.039,-7.891,109.853,-7.828,109.282,-7.705,109.194,-7.695,108.987,-7.704,108.856,-7.668,108.741,-7.667,108.571,-7.707,108.518,-7.736,108.452,-7.797,108.336,-7.794,108.221,-7.782,107.917,-7.724,107.804,-7.688,107.696,-7.636,107.598,-7.567,107.547,-7.542,107.285,-7.472,107.071,-7.447,106.631,-7.416,106.535,-7.394,106.455,-7.369,106.411,-7.312,106.417,-7.239,106.448,-7.177,106.492,-7.114,106.52,-7.054,106.198,-6.928,105.944,-6.859,105.835,-6.846,105.725,-6.846,105.601,-6.86,105.478,-6.854,105.421,-6.833,105.362,-6.826,105.303,-6.841,105.255,-6.835,105.243,-6.778,105.273,-6.729,105.336,-6.674,105.371,-6.664,105.387,-6.751,105.405,-6.768,105.46,-6.787,105.484,-6.782,105.581,-6.671,105.608,-6.617,105.655,-6.47,105.706,-6.498,105.757,-6.48,105.787,-6.457,105.868,-6.116,105.936,-6.017,106.029,-5.934,106.075,-5.914,106.166,-5.965,106.35,-5.984,106.459,-6.018,106.569,-6.022,106.676,-6.038,106.825,-6.098,106.878,-6.092,106.932,-6.073,107.012,-6.008,107.046,-5.904,107.162,-5.957,107.332,-5.978,107.374,-6.008]],[[109.629,2.028,109.539,1.896,109.549,1.848,109.571,1.806,109.636,1.777,109.654,1.615,109.736,1.523,109.818,1.439,109.879,1.398,109.945,1.338,109.992,1.283,110.041,1.236,110.115,1.19,110.315,0.996,110.399,0.939,110.461,0.882,110.506,0.862,110.615,0.878,110.938,1.017,110.996,1.026,111.101,1.051,111.287,1.043,111.483,0.996,111.547,0.994,111.607,1.023,111.691,1.014,111.77,0.999,111.809,1.012,111.923,1.113,112.079,1.143,112.129,1.244,112.167,1.338,112.186,1.439,112.251,1.48,112.342,1.515,112.476,1.559,112.943,1.567,112.988,1.548,112.998,1.496,112.988,1.457,113.007,1.434,113.069,1.432,113.126,1.408,113.359,1.327,113.458,1.302,113.513,1.308,113.622,1.236,113.682,1.261,113.76,1.311,113.835,1.38,113.902,1.434,114,1.455,114.126,1.452,114.275,1.471,114.387,1.5,114.513,1.452,114.546,1.467,114.567,1.514,114.632,1.617,114.661,1.686,114.686,1.819,114.704,1.851,114.751,1.869,114.8,1.894,114.813,1.934,114.831,1.98,114.816,2.019,114.788,2.052,114.759,2.162,114.768,2.213,114.786,2.25,114.836,2.269,114.969,2.351,115.087,2.446,115.151,2.493,115.179,2.523,115.181,2.567,115.13,2.612,115.081,2.634,115.077,2.687,115.079,2.723,115.094,2.758,115.087,2.791,115.086,2.841,115.118,2.895,115.19,2.974,115.247,3.026,115.31,2.994,115.384,3.009,115.454,3.034,115.493,3.128,115.499,3.173,115.49,3.209,115.514,3.342,115.52,3.362,115.566,3.446,115.571,3.502,115.545,3.634,115.561,3.733,115.568,3.939,115.596,3.976,115.628,4.082,115.679,4.193,115.782,4.254,115.837,4.333,115.861,4.348,115.896,4.349,116.022,4.291,116.134,4.355,116.236,4.363,116.32,4.354,116.368,4.327,116.415,4.308,116.515,4.371,116.553,4.36,116.589,4.338,116.639,4.339,116.698,4.355,116.844,4.34,117.101,4.337,117.278,4.299,117.451,4.193,117.537,4.171,117.574,4.171,117.566,4.162,117.497,4.133,117.465,4.076,117.559,3.988,117.566,3.93,117.639,3.878,117.728,3.797,117.732,3.77,117.762,3.734,117.777,3.689,117.714,3.645,117.63,3.636,117.567,3.678,117.51,3.73,117.495,3.666,117.45,3.629,117.288,3.639,117.172,3.639,117.056,3.623,117.114,3.613,117.166,3.592,117.346,3.427,117.385,3.365,117.322,3.244,117.352,3.194,117.422,3.165,117.507,3.105,117.567,3.098,117.611,3.064,117.612,3.005,117.638,2.951,117.569,2.929,117.637,2.915,117.698,2.887,117.665,2.859,117.639,2.825,117.667,2.807,117.75,2.776,117.786,2.747,117.805,2.669,117.886,2.542,118.034,2.378,118.067,2.318,118.066,2.263,118.042,2.215,117.957,2.16,117.889,2.087,117.881,2.061,117.789,2.027,117.831,2.002,117.865,1.968,117.928,1.867,118.08,1.702,118.157,1.64,118.472,1.416,118.639,1.319,118.853,1.096,118.963,1.044,118.985,0.982,118.892,0.887,118.757,0.839,118.535,0.814,118.312,0.847,118.196,0.874,118.096,0.929,118.016,1.039,117.912,1.099,117.952,1.032,117.977,0.964,117.964,0.89,117.923,0.831,117.853,0.789,117.777,0.754,117.745,0.73,117.553,0.341,117.522,0.236,117.464,-0.2,117.463,-0.324,117.549,-0.554,117.557,-0.675,117.574,-0.728,117.563,-0.771,117.522,-0.797,117.357,-0.867,117.241,-0.926,117.146,-1.009,117.07,-1.113,117.003,-1.188,116.914,-1.224,116.849,-1.218,116.797,-1.184,116.761,-1.117,116.74,-1.044,116.726,-1.098,116.729,-1.151,116.759,-1.207,116.771,-1.267,116.753,-1.327,116.715,-1.376,116.612,-1.429,116.554,-1.474,116.545,-1.553,116.518,-1.598,116.478,-1.633,116.332,-1.712,116.3,-1.744,116.275,-1.785,116.353,-1.779,116.424,-1.785,116.43,-1.864,116.452,-1.923,116.424,-2.053,116.314,-2.14,116.369,-2.158,116.418,-2.187,116.528,-2.208,116.565,-2.3,116.549,-2.411,116.529,-2.511,116.45,-2.538,116.401,-2.52,116.353,-2.522,116.317,-2.552,116.307,-2.603,116.375,-2.578,116.372,-2.707,116.353,-2.833,116.331,-2.902,116.289,-2.959,116.226,-2.977,116.166,-2.935,116.154,-2.984,116.172,-3.025,116.257,-3.126,116.205,-3.149,116.167,-3.183,116.15,-3.233,116.058,-3.348,116.017,-3.433,115.999,-3.523,115.956,-3.595,115.258,-3.907,114.694,-4.17,114.653,-4.152,114.625,-4.112,114.606,-3.703,114.536,-3.494,114.526,-3.377,114.446,-3.482,114.397,-3.471,114.344,-3.444,114.305,-3.41,114.302,-3.365,114.344,-3.235,114.293,-3.306,114.236,-3.361,114.178,-3.354,114.128,-3.327,114.109,-3.285,114.082,-3.279,113.959,-3.394,113.796,-3.456,113.705,-3.455,113.634,-3.42,113.637,-3.332,113.63,-3.246,113.61,-3.196,113.566,-3.178,113.526,-3.184,113.409,-3.229,113.367,-3.224,113.343,-3.246,113.034,-2.933,112.971,-3.187,112.758,-3.322,112.6,-3.4,112.444,-3.371,112.285,-3.321,112.127,-3.381,111.955,-3.53,111.907,-3.553,111.858,-3.552,111.822,-3.533,111.834,-3.42,111.836,-3.308,111.823,-3.057,111.809,-3.008,111.76,-2.939,111.695,-2.889,111.658,-2.926,111.625,-2.975,111.495,-2.973,111.368,-2.934,111.259,-2.956,111.044,-3.056,110.93,-3.071,110.869,-3.049,110.83,-2.995,110.852,-2.946,110.899,-2.909,110.811,-2.938,110.736,-2.989,110.703,-3.021,110.668,-3.005,110.574,-2.891,110.378,-2.934,110.351,-2.947,110.303,-2.985,110.256,-2.966,110.233,-2.925,110.224,-2.689,110.124,-2.234,110.097,-2.001,110.075,-1.946,109.96,-1.863,109.964,-1.743,110.023,-1.643,110.036,-1.526,110.019,-1.399,109.983,-1.275,109.938,-1.181,109.873,-1.101,109.787,-1.011,109.682,-0.944,109.454,-0.869,109.333,-0.875,109.289,-0.846,109.259,-0.807,109.271,-0.732,109.312,-0.68,109.366,-0.667,109.373,-0.638,109.257,-0.577,109.161,-0.495,109.13,-0.445,109.121,-0.391,109.122,-0.265,109.15,-0.186,109.165,-0.142,109.195,-0.009,109.258,0.031,109.247,0.056,109.22,0.074,109.181,0.117,109.149,0.168,109.075,0.253,108.945,0.356,108.923,0.533,108.906,0.794,108.917,0.913,108.959,1.135,109.031,1.204,109.088,1.224,109.132,1.254,109.096,1.258,109.065,1.247,109.01,1.24,109.055,1.438,109.076,1.496,109.167,1.607,109.273,1.705,109.318,1.821,109.379,1.923,109.629,2.028]],[[127.733,0.848,127.805,0.826,127.881,0.832,127.919,0.877,127.929,0.935,127.967,1.043,128.055,1.116,128.117,1.127,128.161,1.158,128.153,1.238,128.157,1.317,128.222,1.401,128.424,1.518,128.539,1.559,128.688,1.573,128.705,1.528,128.688,1.464,128.717,1.367,128.703,1.106,128.669,1.069,128.515,0.979,128.346,0.907,128.299,0.877,128.257,0.805,128.261,0.734,128.398,0.639,128.611,0.55,128.655,0.508,128.684,0.438,128.692,0.36,128.743,0.323,128.815,0.305,128.863,0.268,128.9,0.216,128.54,0.338,128.446,0.392,128.333,0.398,128.221,0.414,128.106,0.461,127.983,0.472,127.924,0.438,127.901,0.372,127.887,0.298,127.915,0.206,127.912,0.151,127.889,0.05,127.978,-0.248,128.089,-0.485,128.254,-0.732,128.335,-0.816,128.425,-0.893,128.278,-0.87,128.233,-0.788,128.046,-0.706,128.011,-0.657,127.889,-0.424,127.853,-0.38,127.741,-0.3,127.692,-0.242,127.675,-0.163,127.687,-0.08,127.681,0.035,127.685,0.149,127.709,0.288,127.669,0.337,127.616,0.383,127.555,0.49,127.537,0.611,127.542,0.681,127.567,0.743,127.601,0.796,127.608,0.848,127.52,0.924,127.429,1.14,127.42,1.252,127.537,1.467,127.535,1.572,127.558,1.634,127.571,1.7,127.632,1.844,127.731,1.966,127.9,2.137,127.964,2.175,128.036,2.199,128.043,2.157,128.031,2.12,127.907,1.946,127.89,1.906,127.887,1.833,127.946,1.79,128.011,1.701,128.024,1.583,128.026,1.458,128.012,1.332,127.988,1.29,127.885,1.163,127.653,1.014,127.633,0.977,127.634,0.936,127.677,0.887,127.733,0.848]],[[129.755,-2.866,129.984,-2.977,130.103,-2.993,130.304,-2.979,130.379,-2.989,130.57,-3.131,130.626,-3.228,130.642,-3.312,130.671,-3.392,130.718,-3.411,130.773,-3.419,130.846,-3.533,130.86,-3.57,130.805,-3.858,130.58,-3.749,130.363,-3.625,130.27,-3.579,130.02,-3.475,129.981,-3.439,129.953,-3.392,129.844,-3.327,129.627,-3.317,129.545,-3.319,129.512,-3.329,129.52,-3.363,129.522,-3.434,129.468,-3.453,129.333,-3.409,129.212,-3.393,129.108,-3.349,128.967,-3.326,128.952,-3.304,128.964,-3.272,128.958,-3.241,128.925,-3.229,128.863,-3.235,128.802,-3.266,128.751,-3.3,128.677,-3.397,128.639,-3.433,128.517,-3.449,128.466,-3.44,128.419,-3.416,128.28,-3.241,128.233,-3.203,128.181,-3.172,128.132,-3.157,128.082,-3.184,128.056,-3.239,128.044,-3.303,128.03,-3.341,127.97,-3.444,127.92,-3.506,127.902,-3.496,127.928,-3.397,127.928,-3.341,127.897,-3.282,127.878,-3.222,128.113,-2.935,128.199,-2.866,128.57,-2.842,128.791,-2.857,128.911,-2.85,128.991,-2.829,129.058,-2.838,129.074,-2.895,129.116,-2.937,129.174,-2.933,129.28,-2.889,129.371,-2.821,129.427,-2.791,129.484,-2.786,129.543,-2.79,129.6,-2.806,129.755,-2.866]],[[126.861,-3.088,127.025,-3.166,127.063,-3.217,127.092,-3.278,127.125,-3.311,127.163,-3.338,127.227,-3.391,127.244,-3.471,127.23,-3.633,127.155,-3.647,127.085,-3.671,126.941,-3.765,126.87,-3.783,126.794,-3.789,126.74,-3.814,126.686,-3.824,126.547,-3.772,126.411,-3.711,126.215,-3.605,126.178,-3.579,126.147,-3.523,126.057,-3.421,126.034,-3.356,126.026,-3.171,126.05,-3.128,126.088,-3.105,126.22,-3.148,126.306,-3.103,126.555,-3.065,126.808,-3.069,126.861,-3.088]],[[124.922,-8.942,124.915,-9.032,124.937,-9.053,124.973,-9.064,125.1,-9.004,125.124,-9.015,125.149,-9.043,125.149,-9.123,125.1,-9.19,124.978,-9.195,124.96,-9.214,124.959,-9.255,124.968,-9.294,124.997,-9.326,125.034,-9.382,125.068,-9.512,124.998,-9.565,124.963,-9.666,124.842,-9.76,124.708,-9.914,124.602,-9.993,124.508,-10.086,124.428,-10.149,124.327,-10.17,124.176,-10.183,123.971,-10.295,123.858,-10.344,123.747,-10.347,123.644,-10.311,123.605,-10.27,123.614,-10.215,123.648,-10.168,123.69,-10.129,123.716,-10.079,123.599,-10.015,123.589,-9.967,123.636,-9.838,123.666,-9.705,123.709,-9.615,123.877,-9.453,123.977,-9.373,124.036,-9.342,124.052,-9.375,124.09,-9.416,124.116,-9.423,124.135,-9.414,124.282,-9.428,124.319,-9.414,124.376,-9.35,124.413,-9.314,124.438,-9.239,124.444,-9.19,124.575,-9.155,124.646,-9.117,124.708,-9.062,124.89,-8.968,124.922,-8.942]],[[134.747,-5.707,134.739,-5.746,134.738,-5.817,134.755,-5.883,134.712,-5.95,134.752,-6.05,134.758,-6.1,134.756,-6.171,134.744,-6.202,134.714,-6.295,134.684,-6.328,134.661,-6.337,134.638,-6.365,134.441,-6.335,134.356,-6.271,134.28,-6.201,134.264,-6.172,134.175,-6.09,134.155,-6.063,134.153,-6.02,134.225,-6.008,134.302,-6.01,134.299,-5.971,134.343,-5.833,134.226,-5.744,134.205,-5.707,134.247,-5.682,134.341,-5.713,134.456,-5.558,134.49,-5.525,134.506,-5.438,134.571,-5.427,134.617,-5.439,134.646,-5.492,134.658,-5.539,134.646,-5.581,134.701,-5.603,134.747,-5.707]],[[134.537,-6.442,134.52,-6.513,134.504,-6.591,134.413,-6.68,134.356,-6.815,134.323,-6.849,134.2,-6.909,134.091,-6.834,134.059,-6.769,134.107,-6.472,134.154,-6.481,134.185,-6.479,134.195,-6.46,134.125,-6.426,134.111,-6.255,134.115,-6.191,134.168,-6.176,134.234,-6.226,134.318,-6.316,134.415,-6.387,134.537,-6.442]],[[138.535,-8.274,138.296,-8.405,137.983,-8.382,137.872,-8.38,137.688,-8.412,137.65,-8.386,137.685,-8.262,137.833,-7.932,138.008,-7.642,138.082,-7.566,138.185,-7.495,138.296,-7.438,138.544,-7.38,138.77,-7.39,138.802,-7.415,138.899,-7.512,138.963,-7.588,138.989,-7.696,138.893,-7.882,138.786,-8.059,138.612,-8.198,138.535,-8.274]],[[140.973,-2.61,140.973,-2.614,140.973,-2.681,140.974,-2.803,140.974,-3.007,140.974,-3.21,140.974,-3.413,140.974,-3.617,140.974,-3.82,140.974,-4.023,140.974,-4.226,140.974,-4.43,140.974,-4.633,140.974,-4.836,140.975,-5.04,140.975,-5.243,140.975,-5.446,140.975,-5.65,140.975,-5.853,140.975,-6.056,140.975,-6.259,140.975,-6.346,140.944,-6.452,140.875,-6.612,140.862,-6.74,140.92,-6.84,140.975,-6.905,140.975,-7.073,140.975,-7.276,140.975,-7.479,140.975,-7.683,140.976,-7.886,140.976,-8.089,140.976,-8.292,140.976,-8.496,140.976,-8.699,140.976,-8.902,140.976,-9.106,140.976,-9.119,140.925,-9.085,140.787,-8.974,140.662,-8.847,140.581,-8.728,140.49,-8.62,140.102,-8.301,140.003,-8.196,139.983,-8.167,139.993,-8.139,140.037,-8.084,140.117,-7.924,140.034,-8.023,139.935,-8.101,139.791,-8.106,139.649,-8.125,139.519,-8.173,139.386,-8.189,139.319,-8.166,139.279,-8.107,139.258,-8.047,139.249,-7.982,139.193,-8.086,139.083,-8.143,138.933,-8.262,138.891,-8.238,138.865,-8.192,138.856,-8.145,138.885,-8.095,138.905,-8.041,138.936,-7.913,139.003,-7.838,139.046,-7.691,139.074,-7.639,139.088,-7.587,139.049,-7.528,138.983,-7.508,138.938,-7.472,138.886,-7.373,138.853,-7.34,138.794,-7.299,138.748,-7.251,138.798,-7.216,138.865,-7.201,138.919,-7.204,139.018,-7.226,139.063,-7.227,139.177,-7.19,139.113,-7.202,139.049,-7.201,138.846,-7.136,138.72,-7.07,138.601,-6.937,138.6,-6.911,138.684,-6.887,138.865,-6.858,138.808,-6.79,138.727,-6.731,138.698,-6.626,138.642,-6.56,138.522,-6.454,138.439,-6.343,138.368,-6.119,138.296,-5.949,138.314,-5.887,138.375,-5.844,138.283,-5.839,138.2,-5.807,138.244,-5.724,138.34,-5.676,138.252,-5.688,138.167,-5.712,138.127,-5.717,138.087,-5.709,138.066,-5.676,138.063,-5.629,138.076,-5.546,138.061,-5.465,137.985,-5.428,137.922,-5.37,137.887,-5.349,137.84,-5.35,137.795,-5.312,137.759,-5.256,137.307,-5.014,137.28,-4.945,137.238,-4.976,137.196,-4.99,137.144,-4.951,137.089,-4.924,137.03,-4.929,136.975,-4.907,136.917,-4.895,136.857,-4.893,136.619,-4.819,136.394,-4.701,136.211,-4.651,136.097,-4.585,135.98,-4.531,135.717,-4.478,135.45,-4.443,135.354,-4.442,135.273,-4.453,135.196,-4.451,134.754,-4.195,134.68,-4.079,134.687,-4.011,134.707,-3.955,134.887,-3.938,134.76,-3.922,134.708,-3.93,134.603,-3.976,134.547,-3.979,134.467,-3.949,134.391,-3.91,134.266,-3.946,134.202,-3.887,134.18,-3.825,134.147,-3.797,134.1,-3.8,134.037,-3.822,133.974,-3.818,133.933,-3.776,133.904,-3.72,133.861,-3.68,133.808,-3.65,133.723,-3.578,133.678,-3.479,133.683,-3.309,133.697,-3.248,133.782,-3.149,133.842,-3.055,133.767,-3.044,133.7,-3.087,133.672,-3.132,133.661,-3.186,133.653,-3.364,133.599,-3.416,133.518,-3.412,133.542,-3.516,133.509,-3.616,133.415,-3.732,133.407,-3.785,133.422,-3.843,133.401,-3.899,133.249,-4.062,133.198,-4.07,133.085,-4.069,132.969,-4.095,132.914,-4.057,132.87,-4.007,132.837,-3.949,132.791,-3.828,132.754,-3.704,132.87,-3.551,132.83,-3.413,132.751,-3.295,132.554,-3.131,132.348,-2.975,132.255,-2.943,132.102,-2.93,132.054,-2.915,132.006,-2.856,131.971,-2.789,132.067,-2.76,132.231,-2.68,132.323,-2.684,132.575,-2.727,132.653,-2.766,132.725,-2.789,132.897,-2.658,133.034,-2.487,133.119,-2.45,133.191,-2.438,133.265,-2.454,133.411,-2.514,133.527,-2.542,133.609,-2.547,133.652,-2.601,133.7,-2.625,133.711,-2.544,133.753,-2.451,133.835,-2.422,133.878,-2.415,133.905,-2.391,133.899,-2.304,133.791,-2.294,133.85,-2.22,133.902,-2.184,133.921,-2.147,133.922,-2.102,133.71,-2.189,133.488,-2.226,133.356,-2.216,133.225,-2.214,132.963,-2.273,132.863,-2.27,132.631,-2.247,132.503,-2.218,132.403,-2.24,132.308,-2.242,132.207,-2.176,132.122,-2.092,132.08,-2.033,132.023,-1.99,131.998,-1.933,131.936,-1.715,131.93,-1.56,131.83,-1.557,131.731,-1.541,131.294,-1.393,131.241,-1.43,131.179,-1.448,131.118,-1.455,131.057,-1.448,130.996,-1.425,131.001,-1.384,131.046,-1.284,131.091,-1.247,131.152,-1.219,131.191,-1.166,131.254,-1.007,131.259,-0.953,131.252,-0.897,131.257,-0.855,131.296,-0.834,131.462,-0.782,131.804,-0.704,131.891,-0.657,131.962,-0.582,132.046,-0.537,132.084,-0.491,132.128,-0.454,132.394,-0.355,132.508,-0.347,132.625,-0.359,132.856,-0.417,133.077,-0.512,133.268,-0.636,133.473,-0.726,133.724,-0.741,133.85,-0.731,133.975,-0.744,134.025,-0.77,134.112,-0.847,134.087,-0.897,134.072,-1.002,134.116,-1.102,134.188,-1.203,134.247,-1.311,134.26,-1.363,134.237,-1.474,134.217,-1.529,134.145,-1.621,134.106,-1.721,134.131,-1.845,134.145,-1.969,134.143,-2.083,134.156,-2.195,134.195,-2.309,134.362,-2.621,134.46,-2.832,134.491,-2.714,134.483,-2.583,134.518,-2.536,134.567,-2.51,134.627,-2.537,134.645,-2.59,134.649,-2.706,134.702,-2.934,134.77,-2.944,134.843,-2.909,134.855,-2.979,134.853,-3.108,134.887,-3.21,134.917,-3.25,135.037,-3.333,135.092,-3.349,135.252,-3.369,135.372,-3.375,135.487,-3.345,135.561,-3.269,135.628,-3.186,135.859,-2.995,135.926,-2.904,135.991,-2.764,136.013,-2.734,136.243,-2.583,136.27,-2.529,136.303,-2.426,136.352,-2.325,136.39,-2.273,136.612,-2.224,136.843,-2.198,137.072,-2.105,137.171,-2.025,137.176,-1.973,137.125,-1.881,137.123,-1.841,137.176,-1.802,137.381,-1.686,137.617,-1.566,137.806,-1.483,137.911,-1.484,138.008,-1.557,138.111,-1.616,138.65,-1.791,138.736,-1.846,138.811,-1.918,138.919,-1.968,139.039,-1.992,139.149,-2.039,139.253,-2.099,139.482,-2.212,139.79,-2.348,139.868,-2.356,140.155,-2.35,140.204,-2.376,140.251,-2.412,140.295,-2.42,140.623,-2.446,140.673,-2.472,140.721,-2.508,140.747,-2.607,140.973,-2.61]],[[138.895,-8.389,138.846,-8.402,138.594,-8.371,138.567,-8.33,138.563,-8.309,138.621,-8.268,138.677,-8.199,138.763,-8.173,138.796,-8.174,138.898,-8.337,138.895,-8.389]],[[101.708,2.078,101.762,1.997,101.774,1.943,101.734,1.883,101.719,1.789,101.603,1.716,101.501,1.733,101.468,1.759,101.403,1.901,101.41,2.022,101.45,2.068,101.545,2.061,101.641,2.127,101.708,2.078]],[[102.427,0.99,102.381,0.96,102.325,1.007,102.28,1.076,102.255,1.147,102.234,1.264,102.229,1.348,102.256,1.397,102.276,1.395,102.359,1.346,102.413,1.261,102.443,1.234,102.449,1.156,102.429,1.067,102.427,0.99]],[[102.492,1.459,102.499,1.331,102.425,1.364,102.367,1.415,102.274,1.453,102.161,1.465,102.079,1.499,102.021,1.558,102.018,1.586,102.024,1.608,102.042,1.625,102.47,1.51,102.492,1.459]],[[103.028,0.747,103.009,0.708,102.971,0.737,102.776,0.78,102.711,0.784,102.542,0.832,102.49,0.857,102.454,0.89,102.466,0.95,102.491,0.987,102.507,1.089,102.549,1.13,102.633,1.054,102.726,0.989,102.78,0.959,102.944,0.893,103.002,0.859,103.028,0.747]],[[103.166,0.87,103.137,0.842,103.087,0.848,103.033,0.882,102.964,0.943,102.886,0.997,102.788,1.031,102.726,1.041,102.702,1.054,102.726,1.159,102.79,1.165,102.999,1.068,103.068,1.015,103.166,0.87]],[[103.284,0.542,103.172,0.536,103.14,0.549,103.153,0.643,103.187,0.7,103.238,0.699,103.295,0.614,103.284,0.542]],[[103.45,0.664,103.43,0.651,103.344,0.778,103.366,0.851,103.386,0.87,103.433,0.825,103.47,0.778,103.497,0.723,103.45,0.664]],[[104.025,1.181,104.088,1.137,104.14,1.166,104.138,1.128,104.127,1.092,104.066,0.99,103.964,1.013,103.94,1.046,103.932,1.071,103.947,1.087,103.955,1.137,104,1.137,104.025,1.181]],[[104.585,1.216,104.591,1.141,104.648,1.105,104.663,1.05,104.653,0.961,104.599,0.859,104.575,0.832,104.504,0.853,104.481,0.887,104.471,0.913,104.481,0.933,104.429,0.956,104.462,0.996,104.439,1.05,104.294,1.016,104.252,1.015,104.244,1.077,104.25,1.103,104.362,1.181,104.428,1.196,104.5,1.18,104.585,1.216]],[[104.779,-0.176,104.808,-0.192,104.843,-0.141,104.909,-0.212,104.95,-0.247,105.005,-0.283,104.951,-0.284,104.929,-0.317,104.914,-0.323,104.702,-0.209,104.567,-0.246,104.474,-0.212,104.447,-0.189,104.497,-0.126,104.543,0.018,104.636,-0.018,104.658,-0.063,104.653,-0.076,104.713,-0.103,104.779,-0.176]],[[104.474,-0.335,104.568,-0.432,104.59,-0.467,104.544,-0.521,104.507,-0.597,104.485,-0.613,104.414,-0.584,104.363,-0.659,104.33,-0.539,104.257,-0.463,104.302,-0.386,104.319,-0.38,104.341,-0.383,104.364,-0.403,104.474,-0.335]],[[103.737,-0.348,103.606,-0.383,103.461,-0.358,103.479,-0.297,103.549,-0.228,103.611,-0.231,103.724,-0.277,103.764,-0.318,103.737,-0.348]],[[109.71,-1.181,109.511,-1.283,109.464,-1.278,109.428,-1.241,109.45,-1.044,109.476,-0.985,109.615,-0.979,109.7,-1.007,109.743,-1.039,109.761,-1.105,109.751,-1.145,109.71,-1.181]],[[113.845,-7.105,113.826,-7.12,113.656,-7.112,113.546,-7.193,113.471,-7.218,113.198,-7.218,113.166,-7.207,113.142,-7.208,113.127,-7.224,113.04,-7.212,112.764,-7.14,112.726,-7.073,112.769,-7.001,112.868,-6.9,113.067,-6.88,113.975,-6.873,114.074,-6.96,114.083,-6.989,113.885,-7.049,113.845,-7.105]],[[134.965,-1.116,134.917,-1.134,134.862,-1.114,134.809,-1.038,134.828,-0.979,134.889,-0.938,134.941,-0.979,134.957,-1.031,134.996,-1.034,134.965,-1.116]],[[127.25,-0.495,127.187,-0.521,127.119,-0.521,127.104,-0.414,127.126,-0.279,127.19,-0.256,127.29,-0.284,127.253,-0.319,127.281,-0.391,127.25,-0.495]],[[127.567,-0.319,127.682,-0.468,127.605,-0.61,127.659,-0.689,127.804,-0.694,127.838,-0.724,127.863,-0.76,127.88,-0.809,127.842,-0.848,127.761,-0.884,127.668,-0.832,127.643,-0.784,127.624,-0.766,127.498,-0.802,127.463,-0.806,127.438,-0.739,127.469,-0.643,127.381,-0.6,127.3,-0.5,127.297,-0.46,127.329,-0.391,127.325,-0.336,127.371,-0.332,127.455,-0.406,127.492,-0.336,127.527,-0.307,127.567,-0.319]],[[132.926,-5.902,132.845,-5.988,132.922,-5.785,132.938,-5.683,133.009,-5.621,133.115,-5.311,133.138,-5.318,133.173,-5.348,133.12,-5.576,132.971,-5.736,132.926,-5.902]],[[132.807,-5.851,132.746,-5.947,132.705,-5.913,132.681,-5.913,132.667,-5.856,132.681,-5.739,132.63,-5.607,132.698,-5.609,132.717,-5.648,132.738,-5.662,132.804,-5.789,132.807,-5.851]],[[128.276,-3.675,128.25,-3.711,128.192,-3.735,128.143,-3.733,128.159,-3.698,128.147,-3.677,128.111,-3.686,128.052,-3.715,127.978,-3.771,127.934,-3.743,127.925,-3.699,127.928,-3.679,128.016,-3.601,128.119,-3.587,128.264,-3.512,128.329,-3.516,128.314,-3.564,128.291,-3.598,128.277,-3.633,128.276,-3.675]],[[126.817,4.033,126.776,4.013,126.711,4.02,126.704,4.071,126.77,4.162,126.814,4.258,126.767,4.283,126.722,4.344,126.721,4.416,126.757,4.548,126.813,4.537,126.865,4.48,126.887,4.373,126.921,4.291,126.848,4.18,126.817,4.033]],[[125.658,3.436,125.633,3.405,125.512,3.461,125.518,3.55,125.501,3.593,125.469,3.639,125.455,3.684,125.469,3.733,125.543,3.67,125.586,3.571,125.644,3.477,125.658,3.436]],[[130.862,-8.319,130.775,-8.35,130.833,-8.271,131.02,-8.091,131.087,-8.125,131.176,-8.131,131.044,-8.212,130.908,-8.246,130.862,-8.319]],[[129.839,-7.955,129.78,-8.046,129.713,-8.041,129.592,-7.917,129.599,-7.831,129.609,-7.803,129.655,-7.795,129.813,-7.82,129.844,-7.889,129.839,-7.955]],[[127.823,-8.099,127.998,-8.139,128.099,-8.135,128.119,-8.171,128.024,-8.255,127.821,-8.19,127.786,-8.12,127.823,-8.099]],[[130.353,-1.691,130.365,-1.75,130.425,-1.805,130.404,-1.89,130.381,-1.903,130.393,-1.942,130.419,-1.971,130.373,-1.992,130.339,-1.982,130.284,-2.009,130.248,-2.048,130.133,-2.064,130.093,-2.028,129.887,-1.986,129.754,-1.894,129.738,-1.867,129.994,-1.759,130.106,-1.73,130.2,-1.732,130.318,-1.692,130.353,-1.691]],[[102.367,-5.479,102.286,-5.483,102.136,-5.361,102.111,-5.323,102.154,-5.286,102.198,-5.289,102.372,-5.366,102.405,-5.405,102.367,-5.479]],[[100.425,-3.183,100.465,-3.329,100.346,-3.229,100.348,-3.159,100.332,-3.113,100.26,-3.057,100.204,-2.987,100.179,-2.82,100.199,-2.786,100.246,-2.783,100.455,-3.002,100.469,-3.039,100.464,-3.117,100.434,-3.141,100.425,-3.183]],[[100.204,-2.741,100.133,-2.821,100.015,-2.82,99.992,-2.77,99.997,-2.649,99.968,-2.61,99.969,-2.594,99.988,-2.525,100.012,-2.51,100.202,-2.68,100.204,-2.741]],[[99.843,-2.343,99.848,-2.37,99.685,-2.282,99.607,-2.258,99.537,-2.162,99.559,-2.115,99.562,-2.051,99.572,-2.026,99.622,-2.017,99.686,-2.063,99.735,-2.178,99.816,-2.284,99.843,-2.343]],[[98.459,-0.53,98.4,-0.577,98.31,-0.532,98.34,-0.468,98.355,-0.379,98.409,-0.309,98.427,-0.226,98.323,-0.001,98.375,0.007,98.415,-0.018,98.484,-0.168,98.544,-0.258,98.52,-0.38,98.459,-0.53]],[[96.464,2.36,96.401,2.351,96.341,2.372,96.29,2.43,96.022,2.596,95.938,2.598,95.88,2.641,95.809,2.656,95.733,2.767,95.717,2.826,95.772,2.855,95.806,2.916,95.896,2.889,95.998,2.781,96.102,2.741,96.13,2.721,96.18,2.661,96.417,2.515,96.443,2.466,96.459,2.416,96.464,2.36]],[[122.043,-5.438,121.98,-5.465,121.859,-5.35,121.808,-5.256,121.821,-5.203,121.857,-5.156,121.874,-5.145,121.866,-5.096,121.914,-5.072,121.966,-5.076,122,-5.141,122.041,-5.159,122.062,-5.221,122.043,-5.438]],[[123.212,-1.171,123.234,-1.234,123.198,-1.288,123.238,-1.389,123.339,-1.254,123.435,-1.237,123.489,-1.259,123.527,-1.286,123.547,-1.337,123.512,-1.447,123.449,-1.499,123.367,-1.507,123.329,-1.443,123.275,-1.437,123.237,-1.577,123.221,-1.598,123.173,-1.616,123.13,-1.577,123.123,-1.556,123.183,-1.493,123.15,-1.304,123.105,-1.34,122.984,-1.511,122.89,-1.587,122.858,-1.548,122.811,-1.432,122.832,-1.283,122.908,-1.182,122.972,-1.189,123.158,-1.158,123.212,-1.171]],[[121.864,-0.407,121.907,-0.451,121.881,-0.503,121.847,-0.49,121.756,-0.491,121.722,-0.495,121.681,-0.525,121.655,-0.526,121.672,-0.479,121.749,-0.407,121.797,-0.418,121.864,-0.407]],[[120.528,-6.298,120.487,-6.465,120.468,-6.406,120.461,-6.254,120.436,-6.18,120.452,-6.095,120.446,-5.876,120.477,-5.775,120.534,-5.904,120.549,-5.969,120.528,-6.298]],[[115.377,-6.971,115.296,-6.988,115.22,-6.953,115.222,-6.905,115.241,-6.861,115.354,-6.838,115.414,-6.84,115.479,-6.87,115.524,-6.902,115.546,-6.939,115.424,-6.941,115.377,-6.971]],[[116.303,-3.868,116.093,-4.054,116.059,-4.007,116.077,-3.817,116.018,-3.7,116.022,-3.612,116.064,-3.458,116.117,-3.34,116.239,-3.26,116.27,-3.251,116.262,-3.395,116.287,-3.449,116.295,-3.495,116.282,-3.535,116.305,-3.719,116.319,-3.763,116.289,-3.821,116.303,-3.868]],[[122.949,-10.909,122.856,-10.91,122.826,-10.899,122.818,-10.811,122.846,-10.762,123.061,-10.698,123.146,-10.64,123.265,-10.518,123.34,-10.486,123.358,-10.472,123.371,-10.475,123.383,-10.568,123.413,-10.623,123.418,-10.651,123.311,-10.698,123.215,-10.806,123.005,-10.876,122.949,-10.909]],[[124.287,-8.329,124.226,-8.391,124.184,-8.499,124.147,-8.531,124.066,-8.552,124.017,-8.444,123.928,-8.449,123.971,-8.354,124.014,-8.319,124.069,-8.318,124.096,-8.356,124.111,-8.364,124.24,-8.203,124.266,-8.202,124.287,-8.209,124.304,-8.229,124.287,-8.329]],[[123.925,-8.272,123.784,-8.3,123.698,-8.424,123.629,-8.422,123.592,-8.478,123.583,-8.502,123.588,-8.524,123.58,-8.545,123.553,-8.567,123.489,-8.532,123.434,-8.576,123.411,-8.587,123.33,-8.536,123.253,-8.539,123.23,-8.531,123.325,-8.439,123.455,-8.354,123.476,-8.322,123.425,-8.313,123.395,-8.301,123.391,-8.28,123.473,-8.267,123.53,-8.265,123.573,-8.292,123.601,-8.291,123.776,-8.19,123.846,-8.213,123.896,-8.239,123.925,-8.272]],[[123.317,-8.355,123.297,-8.399,123.025,-8.396,123.033,-8.338,123.108,-8.275,123.133,-8.254,123.217,-8.235,123.336,-8.269,123.317,-8.355]],[[134.716,-6.549,134.661,-6.559,134.634,-6.477,134.679,-6.456,134.729,-6.506,134.716,-6.549]],[[103.424,1.048,103.43,0.993,103.363,1.007,103.315,1.071,103.355,1.117,103.38,1.134,103.405,1.073,103.424,1.048]],[[103.829,0.801,103.834,0.772,103.742,0.83,103.74,0.872,103.752,0.891,103.807,0.846,103.829,0.801]],[[104.239,0.834,104.177,0.805,104.098,0.896,104.101,0.917,104.108,0.934,104.123,0.944,104.171,0.897,104.227,0.88,104.239,0.834]],[[104.689,0.06,104.698,0.035,104.651,0.063,104.622,0.08,104.604,0.095,104.499,0.232,104.544,0.223,104.66,0.103,104.689,0.06]],[[106.285,3.157,106.284,3.088,106.215,3.129,106.201,3.205,106.224,3.23,106.271,3.216,106.285,3.157]],[[105.76,2.863,105.719,2.859,105.706,2.889,105.708,2.94,105.704,2.981,105.692,3.011,105.692,3.063,105.731,3.037,105.76,3.013,105.795,2.996,105.822,2.984,105.837,2.977,105.809,2.904,105.76,2.863]],[[108.888,2.905,108.839,2.853,108.787,2.886,108.867,2.992,108.886,2.999,108.888,2.905]],[[108.953,-1.62,108.838,-1.662,108.804,-1.568,108.877,-1.54,108.957,-1.564,108.953,-1.62]],[[107.473,-2.9,107.433,-2.925,107.409,-2.901,107.402,-2.873,107.419,-2.838,107.474,-2.835,107.5,-2.845,107.473,-2.9]],[[106.886,-3.005,106.87,-3.025,106.814,-3.014,106.774,-2.987,106.749,-2.96,106.743,-2.933,106.797,-2.899,106.911,-2.934,106.886,-3.005]],[[114.413,-7.133,114.398,-7.173,114.347,-7.163,114.299,-7.098,114.322,-7.08,114.349,-7.073,114.384,-7.081,114.413,-7.133]],[[112.719,-5.811,112.698,-5.846,112.602,-5.844,112.586,-5.804,112.649,-5.731,112.69,-5.726,112.727,-5.753,112.719,-5.811]],[[105.253,-6.64,105.19,-6.662,105.143,-6.643,105.121,-6.615,105.192,-6.546,105.226,-6.529,105.261,-6.524,105.277,-6.561,105.253,-6.64]],[[97.334,2.076,97.328,2.053,97.225,2.158,97.108,2.217,97.157,2.232,97.253,2.216,97.291,2.201,97.329,2.149,97.334,2.076]],[[95.362,5.812,95.343,5.784,95.283,5.799,95.218,5.89,95.242,5.907,95.283,5.898,95.359,5.877,95.366,5.843,95.362,5.812]],[[123.598,-1.704,123.529,-1.711,123.483,-1.681,123.487,-1.535,123.529,-1.503,123.549,-1.508,123.561,-1.552,123.582,-1.591,123.616,-1.627,123.598,-1.704]],[[123.242,-4.113,123.145,-4.233,123.076,-4.227,122.995,-4.148,122.971,-4.061,122.969,-4.03,123.025,-3.981,123.212,-3.998,123.247,-4.041,123.242,-4.113]],[[123.848,-1.955,123.866,-1.996,123.804,-1.994,123.777,-1.919,123.783,-1.878,123.848,-1.955]],[[123.153,-1.817,123.079,-1.899,123.071,-1.855,123.086,-1.815,123.106,-1.787,123.138,-1.773,123.153,-1.817]],[[119.464,-8.741,119.425,-8.75,119.386,-8.736,119.402,-8.647,119.379,-8.587,119.42,-8.539,119.43,-8.455,119.446,-8.429,119.471,-8.456,119.482,-8.473,119.502,-8.481,119.547,-8.483,119.557,-8.519,119.555,-8.553,119.536,-8.589,119.483,-8.628,119.444,-8.672,119.464,-8.741]],[[119.074,-8.239,119.03,-8.24,119.021,-8.2,119.037,-8.158,119.079,-8.14,119.098,-8.139,119.128,-8.177,119.135,-8.197,119.107,-8.223,119.074,-8.239]],[[115.61,-8.77,115.582,-8.804,115.501,-8.743,115.48,-8.715,115.541,-8.675,115.561,-8.67,115.613,-8.713,115.61,-8.77]],[[117.649,4.169,117.745,4.167,117.885,4.186,117.918,4.091,117.923,4.054,117.737,4.004,117.625,4.121,117.649,4.169]],[[117.658,3.281,117.646,3.248,117.56,3.328,117.538,3.386,117.548,3.432,117.637,3.436,117.681,3.408,117.658,3.281]],[[124.051,-5.974,124.042,-6.022,124.006,-5.967,123.972,-5.939,123.976,-5.88,124.023,-5.902,124.051,-5.974]],[[123.627,-5.272,123.623,-5.373,123.583,-5.367,123.55,-5.332,123.541,-5.298,123.543,-5.271,123.561,-5.25,123.627,-5.272]],[[120.774,-7.119,120.672,-7.125,120.641,-7.116,120.633,-7.018,120.746,-7.06,120.782,-7.063,120.774,-7.119]],[[117.556,-8.367,117.534,-8.368,117.49,-8.349,117.506,-8.307,117.482,-8.239,117.491,-8.183,117.546,-8.152,117.665,-8.148,117.669,-8.189,117.556,-8.367]],[[116.424,-3.464,116.388,-3.637,116.327,-3.539,116.395,-3.423,116.427,-3.4,116.424,-3.464]],[[127.373,0.791,127.338,0.758,127.306,0.769,127.286,0.812,127.293,0.842,127.32,0.862,127.354,0.847,127.373,0.791]],[[134.374,-2.124,134.345,-2.139,134.335,-2.095,134.351,-2.037,134.37,-2.028,134.391,-2.031,134.419,-2.052,134.374,-2.124]],[[133.571,-4.246,133.622,-4.299,133.503,-4.257,133.333,-4.17,133.321,-4.111,133.464,-4.2,133.571,-4.246]],[[130.905,-0.777,130.88,-0.828,130.832,-0.863,130.402,-0.924,130.439,-0.887,130.457,-0.851,130.484,-0.833,130.527,-0.837,130.548,-0.826,130.57,-0.822,130.594,-0.827,130.635,-0.812,130.723,-0.822,130.813,-0.814,130.807,-0.765,130.905,-0.777]],[[130.627,-0.529,130.569,-0.53,130.465,-0.487,130.526,-0.449,130.564,-0.441,130.597,-0.418,130.616,-0.417,130.657,-0.437,130.684,-0.469,130.627,-0.529]],[[129.549,-0.187,129.506,-0.19,129.469,-0.131,129.37,-0.066,129.309,0.045,129.542,-0.139,129.549,-0.187]],[[127.453,-0.006,127.449,-0.037,127.418,0.006,127.397,0.017,127.42,0.124,127.431,0.143,127.449,0.069,127.453,-0.006]],[[127.42,0.642,127.384,0.631,127.374,0.635,127.363,0.675,127.383,0.744,127.425,0.744,127.443,0.733,127.446,0.683,127.42,0.642]],[[127.3,-0.781,127.289,-0.802,127.185,-0.775,127.156,-0.761,127.209,-0.619,127.258,-0.623,127.301,-0.758,127.3,-0.781]],[[128.722,-3.547,128.72,-3.589,128.713,-3.603,128.659,-3.588,128.62,-3.589,128.585,-3.512,128.595,-3.495,128.667,-3.517,128.694,-3.525,128.722,-3.547]],[[128.563,-3.585,128.392,-3.638,128.428,-3.54,128.452,-3.515,128.536,-3.541,128.563,-3.585]],[[126.719,3.875,126.722,3.833,126.661,3.928,126.638,4.042,126.686,4.001,126.74,3.918,126.719,3.875]],[[126.852,3.768,126.836,3.757,126.8,3.784,126.778,3.813,126.779,3.843,126.804,3.858,126.857,3.812,126.858,3.787,126.852,3.768]],[[125.407,2.652,125.397,2.63,125.36,2.747,125.391,2.805,125.435,2.784,125.446,2.763,125.404,2.707,125.407,2.652]],[[131.982,-7.202,131.97,-7.251,131.927,-7.225,131.884,-7.167,131.823,-7.159,131.778,-7.144,131.751,-7.117,131.922,-7.104,131.982,-7.202]],[[128.67,-7.183,128.625,-7.209,128.55,-7.156,128.53,-7.135,128.577,-7.083,128.628,-7.069,128.658,-7.091,128.673,-7.113,128.667,-7.138,128.67,-7.183]],[[127.419,-7.623,127.355,-7.646,127.375,-7.572,127.371,-7.513,127.475,-7.531,127.474,-7.579,127.464,-7.597,127.419,-7.623]],[[123.416,-10.303,123.326,-10.337,123.326,-10.264,123.395,-10.171,123.459,-10.14,123.494,-10.177,123.497,-10.194,123.405,-10.227,123.416,-10.303]],[[121.883,-10.59,121.833,-10.602,121.726,-10.573,121.705,-10.556,121.796,-10.507,121.867,-10.439,121.95,-10.433,121.998,-10.447,121.981,-10.528,121.883,-10.59]],[[134.82,-6.434,134.795,-6.442,134.795,-6.393,134.823,-6.35,134.852,-6.325,134.886,-6.324,134.82,-6.434]],[[134.674,-6.75,134.657,-6.765,134.631,-6.733,134.629,-6.713,134.663,-6.658,134.698,-6.626,134.736,-6.623,134.726,-6.669,134.674,-6.75]],[[127.988,-2.937,127.938,-3.02,127.85,-3.016,127.834,-3.004,127.938,-2.952,127.988,-2.937]],[[127.606,-3.315,127.629,-3.359,127.531,-3.331,127.488,-3.288,127.53,-3.262,127.554,-3.254,127.606,-3.315]],[[122.977,-8.545,122.946,-8.604,122.888,-8.587,122.904,-8.531,122.933,-8.497,123.011,-8.448,123.089,-8.44,123.138,-8.457,123.153,-8.476,123.03,-8.495,122.977,-8.545]]]},{"n":"India","c":"IN","r":2,"ml":1.7,"lx":79.36,"ly":22.69,"b":[68.165,6.749,97.344,35.496],"p":[[[68.165,23.857,68.234,23.901,68.283,23.928,68.381,23.951,68.489,23.967,68.587,23.967,68.724,23.965,68.728,24.266,68.74,24.292,68.759,24.307,68.781,24.314,68.8,24.309,68.828,24.264,68.863,24.267,68.901,24.292,68.985,24.273,69.052,24.286,69.12,24.269,69.235,24.268,69.443,24.275,69.559,24.273,69.634,24.225,69.716,24.173,69.805,24.165,69.934,24.171,70.021,24.192,70.065,24.241,70.098,24.288,70.289,24.356,70.489,24.412,70.547,24.418,70.565,24.386,70.556,24.331,70.579,24.279,70.659,24.246,70.716,24.238,70.767,24.245,70.805,24.262,70.886,24.344,70.928,24.362,70.983,24.361,71.044,24.4,71.045,24.43,71.006,24.444,70.973,24.487,70.979,24.522,70.97,24.572,70.976,24.619,71.002,24.654,71.048,24.688,71.021,24.758,70.951,24.892,70.878,25.063,70.8,25.206,70.703,25.331,70.652,25.423,70.657,25.626,70.648,25.667,70.615,25.692,70.57,25.706,70.506,25.685,70.449,25.681,70.325,25.686,70.265,25.707,70.1,25.91,70.079,25.99,70.078,26.072,70.133,26.215,70.149,26.348,70.157,26.471,70.148,26.506,70.115,26.548,70.059,26.579,69.911,26.586,69.736,26.627,69.601,26.699,69.507,26.743,69.481,26.771,69.47,26.804,69.495,26.954,69.537,27.123,69.568,27.175,69.622,27.228,69.661,27.265,69.725,27.313,69.896,27.474,70.05,27.695,70.145,27.849,70.194,27.895,70.244,27.934,70.318,27.982,70.404,28.025,70.489,28.023,70.569,27.984,70.629,27.937,70.649,27.835,70.692,27.769,70.737,27.729,70.798,27.71,70.875,27.714,71.185,27.832,71.29,27.855,71.543,27.87,71.717,27.915,71.87,27.963,71.889,28.047,71.948,28.177,72.129,28.346,72.179,28.422,72.234,28.566,72.292,28.697,72.342,28.752,72.626,28.896,72.903,29.029,72.949,29.089,73.128,29.364,73.231,29.551,73.258,29.611,73.317,29.773,73.382,29.934,73.467,29.972,73.658,30.033,73.809,30.093,73.887,30.162,73.933,30.222,73.925,30.282,73.883,30.352,73.892,30.394,73.899,30.435,74.009,30.52,74.216,30.769,74.339,30.894,74.38,30.893,74.51,30.96,74.633,31.035,74.626,31.069,74.61,31.113,74.54,31.133,74.518,31.186,74.535,31.261,74.594,31.465,74.582,31.524,74.51,31.713,74.526,31.765,74.556,31.819,74.636,31.89,74.739,31.949,75.071,32.089,75.139,32.105,75.254,32.14,75.325,32.215,75.333,32.279,75.303,32.319,75.234,32.372,75.104,32.42,74.987,32.462,74.789,32.458,74.686,32.494,74.658,32.519,74.643,32.608,74.663,32.758,74.632,32.771,74.588,32.753,74.483,32.771,74.355,32.769,74.305,32.81,74.33,32.861,74.323,32.928,74.304,32.992,74.284,33.005,74.222,33.02,74.126,33.075,74.049,33.143,74.004,33.189,73.99,33.221,73.994,33.242,74.05,33.301,74.118,33.384,74.143,33.455,74.15,33.507,74.131,33.545,74.07,33.592,74.004,33.632,73.978,33.668,73.976,33.721,74.001,33.788,74.078,33.839,74.216,33.887,74.251,33.946,74.246,33.99,74.209,34.003,74.113,34.004,73.95,34.019,73.922,34.043,73.904,34.076,73.904,34.108,73.938,34.145,73.979,34.191,73.972,34.237,73.925,34.288,73.81,34.325,73.795,34.378,73.812,34.422,73.85,34.485,73.883,34.529,73.961,34.653,74.056,34.681,74.172,34.721,74.3,34.765,74.498,34.732,74.594,34.716,74.789,34.678,74.952,34.646,75.118,34.637,75.188,34.639,75.264,34.601,75.453,34.537,75.606,34.503,75.709,34.503,75.862,34.56,75.938,34.613,76.041,34.67,76.172,34.668,76.457,34.756,76.51,34.741,76.594,34.736,76.696,34.787,76.749,34.848,76.758,34.878,76.783,34.9,76.892,34.939,77.001,34.992,77.031,35.062,77.049,35.11,77.169,35.172,77.293,35.236,77.423,35.303,77.572,35.379,77.697,35.443,77.799,35.496,77.803,35.493,77.811,35.485,77.852,35.461,77.895,35.449,77.946,35.472,78.009,35.49,78.043,35.48,78.047,35.449,78.009,35.307,78.012,35.251,78.076,35.135,78.158,34.946,78.236,34.77,78.282,34.654,78.327,34.606,78.516,34.558,78.671,34.518,78.763,34.453,78.865,34.39,78.936,34.352,78.97,34.303,78.977,34.258,78.971,34.228,78.932,34.189,78.753,34.088,78.732,34.056,78.727,34.013,78.762,33.888,78.784,33.809,78.79,33.65,78.802,33.5,78.865,33.431,78.917,33.387,78.948,33.347,79.013,33.291,79.067,33.25,79.113,33.226,79.135,33.172,79.122,33.108,79.103,33.053,79.109,33.023,79.146,33.001,79.202,32.946,79.21,32.865,79.206,32.809,79.228,32.759,79.234,32.703,79.217,32.564,79.219,32.508,79.219,32.501,79.17,32.497,79.127,32.476,79.067,32.388,78.998,32.365,78.919,32.358,78.838,32.412,78.771,32.468,78.754,32.499,78.737,32.558,78.701,32.597,78.632,32.579,78.526,32.571,78.413,32.558,78.392,32.545,78.39,32.52,78.417,32.467,78.441,32.397,78.455,32.3,78.486,32.236,78.496,32.216,78.678,32.023,78.726,31.984,78.735,31.958,78.72,31.888,78.687,31.806,78.693,31.74,78.754,31.668,78.803,31.618,78.755,31.55,78.727,31.472,78.759,31.437,78.744,31.324,78.758,31.302,78.792,31.294,78.845,31.302,78.9,31.331,78.946,31.337,78.974,31.329,79.011,31.414,79.044,31.426,79.107,31.403,79.233,31.242,79.339,31.106,79.37,31.08,79.388,31.064,79.493,30.994,79.565,30.949,79.664,30.965,79.795,30.968,79.872,30.925,79.917,30.894,79.919,30.89,79.925,30.889,80.081,30.782,80.149,30.79,80.194,30.759,80.207,30.684,80.186,30.605,80.191,30.568,80.261,30.561,80.41,30.509,80.541,30.464,80.609,30.449,80.682,30.415,80.747,30.36,80.874,30.291,80.985,30.237,81.01,30.165,80.966,30.18,80.908,30.172,80.848,30.14,80.82,30.119,80.684,29.994,80.613,29.956,80.549,29.9,80.402,29.73,80.317,29.572,80.255,29.423,80.256,29.318,80.233,29.195,80.17,29.124,80.13,29.1,80.085,28.994,80.052,28.87,80.071,28.83,80.15,28.776,80.227,28.723,80.325,28.666,80.419,28.612,80.479,28.605,80.496,28.636,80.518,28.665,80.587,28.65,80.671,28.596,80.726,28.554,80.751,28.54,80.896,28.469,81.017,28.41,81.169,28.335,81.206,28.289,81.239,28.241,81.311,28.176,81.486,28.062,81.636,27.98,81.757,27.914,81.853,27.867,81.897,27.874,81.945,27.899,81.988,27.914,82.037,27.901,82.112,27.865,82.288,27.757,82.451,27.672,82.63,27.687,82.677,27.673,82.711,27.597,82.733,27.519,82.933,27.468,83.064,27.445,83.214,27.402,83.29,27.371,83.369,27.41,83.384,27.445,83.447,27.465,83.552,27.456,83.747,27.396,83.829,27.378,83.897,27.435,84.025,27.462,84.091,27.491,84.23,27.428,84.481,27.348,84.61,27.299,84.641,27.25,84.655,27.204,84.654,27.092,84.685,27.041,84.937,26.927,85.02,26.879,85.087,26.863,85.125,26.861,85.152,26.847,85.174,26.782,85.192,26.767,85.24,26.75,85.293,26.741,85.456,26.797,85.568,26.84,85.648,26.829,85.7,26.782,85.707,26.713,85.737,26.64,85.795,26.604,85.856,26.6,86.007,26.649,86.129,26.612,86.242,26.598,86.366,26.574,86.414,26.556,86.544,26.496,86.701,26.435,86.763,26.442,87.016,26.555,87.038,26.542,87.09,26.433,87.167,26.394,87.287,26.36,87.414,26.423,87.513,26.405,87.633,26.399,87.749,26.429,87.849,26.437,87.995,26.382,88.027,26.395,88.055,26.43,88.112,26.586,88.162,26.725,88.157,26.807,88.111,26.928,87.993,27.086,87.984,27.134,88.024,27.409,88.068,27.567,88.106,27.642,88.147,27.749,88.154,27.799,88.15,27.843,88.11,27.871,88.099,27.905,88.109,27.933,88.141,27.949,88.275,27.969,88.426,28.012,88.486,28.034,88.532,28.057,88.578,28.093,88.621,28.092,88.756,28.04,88.804,28.007,88.829,27.907,88.849,27.869,88.83,27.767,88.749,27.522,88.765,27.43,88.833,27.363,88.891,27.316,88.882,27.297,88.76,27.218,88.739,27.176,88.766,27.134,88.814,27.099,88.835,27.066,88.858,26.961,88.919,26.932,89.041,26.865,89.148,26.816,89.332,26.849,89.384,26.827,89.475,26.803,89.545,26.796,89.586,26.779,89.609,26.762,89.606,26.741,89.61,26.719,89.711,26.714,89.764,26.702,89.943,26.724,90.123,26.755,90.206,26.848,90.242,26.854,90.346,26.89,90.448,26.851,90.56,26.797,90.62,26.78,90.74,26.772,90.856,26.778,91.134,26.803,91.287,26.79,91.427,26.867,91.456,26.867,91.518,26.807,91.672,26.802,91.754,26.831,91.842,26.853,91.899,26.86,91.944,26.861,91.998,26.855,92.05,26.875,92.073,26.915,92.068,26.975,92.031,27.041,91.999,27.079,91.992,27.1,92.003,27.147,92.031,27.214,92.083,27.291,92.045,27.365,91.991,27.45,91.951,27.458,91.851,27.439,91.743,27.443,91.658,27.494,91.595,27.558,91.579,27.611,91.598,27.677,91.626,27.737,91.632,27.76,91.713,27.76,91.825,27.746,91.909,27.73,91.978,27.73,92.101,27.808,92.158,27.812,92.222,27.827,92.25,27.842,92.27,27.83,92.341,27.821,92.415,27.825,92.481,27.846,92.547,27.879,92.664,27.949,92.688,27.989,92.688,28.026,92.666,28.05,92.643,28.062,92.653,28.093,92.702,28.147,92.882,28.228,93.035,28.328,93.119,28.402,93.158,28.493,93.207,28.591,93.252,28.629,93.361,28.654,93.665,28.69,93.761,28.73,93.902,28.803,93.974,28.861,94.013,28.908,94.018,28.96,94.112,28.976,94.193,29.06,94.293,29.145,94.468,29.216,94.623,29.312,94.677,29.297,94.733,29.252,94.763,29.201,94.769,29.176,94.967,29.144,94.999,29.149,95.145,29.104,95.279,29.05,95.353,29.036,95.389,29.037,95.42,29.054,95.457,29.102,95.494,29.137,95.517,29.151,95.516,29.206,95.71,29.314,95.885,29.391,96.035,29.447,96.08,29.424,96.129,29.381,96.195,29.272,96.235,29.246,96.337,29.261,96.356,29.249,96.34,29.21,96.271,29.161,96.181,29.118,96.122,29.082,96.141,28.963,96.137,28.923,96.162,28.91,96.347,29.027,96.436,29.051,96.467,29.022,96.477,28.959,96.55,28.83,96.581,28.764,96.396,28.607,96.327,28.525,96.33,28.497,96.326,28.469,96.279,28.428,96.281,28.412,96.32,28.387,96.366,28.367,96.389,28.368,96.428,28.406,96.603,28.46,96.653,28.45,96.776,28.367,96.833,28.362,96.981,28.338,97.075,28.369,97.145,28.34,97.289,28.237,97.322,28.218,97.31,28.155,97.303,28.086,97.339,28.031,97.344,27.982,97.335,27.938,97.306,27.907,97.226,27.89,97.158,27.837,97.05,27.76,96.963,27.698,96.9,27.644,96.877,27.587,96.884,27.515,96.902,27.44,97.104,27.163,97.102,27.115,97.038,27.102,96.953,27.133,96.88,27.178,96.798,27.296,96.732,27.331,96.666,27.339,96.274,27.278,96.191,27.261,96.061,27.217,95.971,27.128,95.905,27.047,95.837,27.014,95.738,26.95,95.464,26.756,95.305,26.672,95.201,26.641,95.129,26.597,95.089,26.525,95.06,26.474,95.051,26.347,95.069,26.191,95.108,26.091,95.129,26.07,95.132,26.041,95.093,25.987,95.041,25.941,95.015,25.913,94.992,25.77,94.946,25.7,94.861,25.597,94.786,25.519,94.668,25.459,94.623,25.41,94.58,25.32,94.554,25.243,94.553,25.216,94.567,25.192,94.616,25.165,94.675,25.139,94.704,25.098,94.708,25.049,94.663,24.931,94.584,24.767,94.493,24.638,94.399,24.514,94.377,24.474,94.293,24.322,94.22,24.113,94.17,23.973,94.128,23.876,94.075,23.872,94.011,23.903,93.855,23.944,93.756,23.977,93.683,24.007,93.633,24.005,93.564,23.986,93.494,23.973,93.452,23.987,93.356,24.074,93.326,24.064,93.307,24.022,93.373,23.774,93.415,23.682,93.408,23.528,93.391,23.339,93.366,23.133,93.349,23.085,93.308,23.03,93.254,23.015,93.204,23.037,93.164,23.032,93.151,22.997,93.163,22.908,93.114,22.806,93.079,22.718,93.088,22.633,93.105,22.547,93.162,22.36,93.162,22.292,93.151,22.231,93.121,22.205,93.071,22.209,93.043,22.184,93.022,22.146,92.965,22.004,92.909,21.989,92.854,22.01,92.771,22.105,92.721,22.132,92.689,22.131,92.675,22.106,92.653,22.049,92.63,22.011,92.575,21.978,92.561,22.048,92.532,22.41,92.51,22.526,92.491,22.685,92.464,22.734,92.43,22.822,92.393,22.897,92.362,22.929,92.341,23.07,92.334,23.242,92.334,23.324,92.289,23.492,92.246,23.684,92.187,23.676,92.152,23.722,92.127,23.721,92.044,23.678,91.979,23.692,91.93,23.686,91.929,23.598,91.938,23.505,91.919,23.471,91.79,23.361,91.754,23.287,91.758,23.21,91.774,23.106,91.751,23.054,91.695,23.005,91.62,22.98,91.554,22.992,91.511,23.034,91.471,23.141,91.436,23.2,91.399,23.214,91.371,23.198,91.367,23.13,91.369,23.075,91.359,23.068,91.339,23.077,91.315,23.104,91.254,23.374,91.166,23.581,91.16,23.661,91.192,23.763,91.232,23.92,91.336,24.019,91.35,24.06,91.367,24.094,91.393,24.1,91.526,24.091,91.571,24.107,91.611,24.153,91.669,24.19,91.727,24.205,91.772,24.211,91.846,24.175,91.877,24.195,91.899,24.261,91.931,24.326,91.952,24.357,92.001,24.371,92.064,24.374,92.085,24.386,92.102,24.408,92.117,24.494,92.198,24.686,92.227,24.771,92.231,24.786,92.228,24.881,92.251,24.895,92.385,24.849,92.443,24.849,92.475,24.869,92.485,24.903,92.468,24.944,92.373,25.015,92.205,25.111,92.05,25.169,91.763,25.161,91.48,25.142,91.397,25.152,91.293,25.178,91.038,25.174,90.73,25.159,90.613,25.168,90.555,25.167,90.439,25.158,90.25,25.185,90.12,25.22,90.004,25.258,89.866,25.293,89.833,25.293,89.814,25.305,89.801,25.336,89.796,25.376,89.825,25.56,89.8,25.84,89.823,25.941,89.71,26.171,89.671,26.214,89.619,26.216,89.586,26.186,89.573,26.132,89.591,26.072,89.55,26.005,89.467,25.984,89.37,26.006,89.289,26.038,89.186,26.106,89.108,26.202,89.102,26.308,89.067,26.377,89.019,26.41,88.983,26.42,88.952,26.412,88.924,26.375,88.948,26.338,88.982,26.286,88.97,26.251,88.941,26.245,88.896,26.26,88.828,26.252,88.762,26.279,88.722,26.282,88.683,26.292,88.681,26.353,88.62,26.431,88.518,26.518,88.418,26.572,88.37,26.564,88.346,26.505,88.351,26.483,88.386,26.472,88.437,26.437,88.448,26.401,88.44,26.369,88.378,26.312,88.334,26.258,88.235,26.178,88.151,26.087,88.129,26.018,88.097,25.956,88.085,25.888,88.107,25.841,88.147,25.811,88.253,25.79,88.363,25.698,88.452,25.574,88.502,25.537,88.593,25.495,88.769,25.49,88.795,25.456,88.82,25.366,88.855,25.334,88.944,25.291,88.952,25.259,88.93,25.223,88.89,25.194,88.817,25.176,88.748,25.169,88.678,25.18,88.574,25.188,88.456,25.188,88.373,24.962,88.313,24.882,88.279,24.882,88.189,24.921,88.15,24.915,88.045,24.713,88.03,24.664,88.023,24.628,88.079,24.55,88.146,24.486,88.225,24.461,88.287,24.48,88.338,24.454,88.397,24.389,88.499,24.347,88.642,24.326,88.724,24.275,88.734,24.231,88.727,24.186,88.714,24.07,88.7,24.003,88.623,23.826,88.567,23.674,88.596,23.602,88.616,23.573,88.636,23.55,88.698,23.493,88.741,23.437,88.704,23.293,88.724,23.255,88.808,23.23,88.897,23.21,88.928,23.187,88.851,23.041,88.867,22.939,88.9,22.844,88.923,22.688,88.927,22.671,88.921,22.632,88.971,22.511,89.05,22.275,89.056,22.186,89.051,22.093,89.028,21.937,88.949,21.938,89.02,21.834,89.042,21.759,89.052,21.654,88.967,21.641,88.907,21.653,88.858,21.745,88.834,21.661,88.745,21.584,88.713,21.622,88.695,21.662,88.691,21.733,88.74,22.005,88.73,22.036,88.708,22.056,88.66,22.067,88.642,22.122,88.567,21.832,88.6,21.714,88.585,21.66,88.446,21.614,88.305,21.723,88.288,21.758,88.279,21.697,88.254,21.622,88.122,21.636,88.057,21.694,88.099,21.794,88.181,22.033,88.196,22.14,88.087,22.218,87.994,22.266,87.941,22.374,87.962,22.255,88.011,22.213,88.083,22.183,88.159,22.122,88.104,22.047,88.051,22.001,87.948,21.825,87.824,21.727,87.678,21.654,87.201,21.545,87.101,21.501,86.954,21.365,86.86,21.237,86.842,21.106,86.896,20.966,86.939,20.745,86.975,20.7,86.925,20.62,86.836,20.534,86.763,20.419,86.769,20.356,86.75,20.313,86.499,20.172,86.446,20.089,86.377,20.007,86.294,20.054,86.245,20.053,86.312,19.988,86.303,19.945,86.279,19.919,86.216,19.896,85.853,19.792,85.575,19.693,85.497,19.697,85.511,19.727,85.56,19.753,85.555,19.867,85.504,19.888,85.46,19.896,85.249,19.758,85.163,19.621,85.181,19.595,85.229,19.601,85.371,19.679,85.437,19.657,85.442,19.627,85.226,19.508,84.771,19.125,84.75,19.05,84.691,18.965,84.609,18.884,84.463,18.69,84.182,18.401,84.104,18.293,83.654,18.07,83.572,18.004,83.388,17.787,83.198,17.609,82.977,17.462,82.593,17.274,82.36,17.096,82.287,16.978,82.282,16.936,82.307,16.879,82.35,16.825,82.36,16.783,82.339,16.707,82.327,16.664,82.259,16.56,82.142,16.485,81.762,16.329,81.712,16.334,81.402,16.365,81.286,16.337,81.239,16.264,81.132,15.962,81.03,15.881,80.993,15.809,80.979,15.758,80.918,15.76,80.865,15.782,80.826,15.766,80.782,15.867,80.708,15.888,80.647,15.895,80.385,15.793,80.293,15.711,80.101,15.324,80.053,15.074,80.099,14.798,80.165,14.578,80.179,14.478,80.17,14.349,80.136,14.287,80.112,14.212,80.144,14.059,80.224,13.858,80.244,13.773,80.246,13.686,80.307,13.485,80.266,13.521,80.233,13.606,80.156,13.714,80.062,13.606,80.114,13.529,80.29,13.437,80.342,13.361,80.229,12.69,80.143,12.452,80.038,12.296,79.982,12.235,79.858,11.989,79.771,11.69,79.754,11.575,79.793,11.447,79.749,11.371,79.693,11.313,79.799,11.339,79.835,11.269,79.849,11.197,79.85,10.769,79.838,10.323,79.757,10.304,79.667,10.3,79.589,10.312,79.532,10.33,79.391,10.306,79.315,10.257,79.254,10.175,79.258,10.035,78.996,9.683,78.94,9.566,78.919,9.453,78.953,9.394,79.02,9.333,79.107,9.309,79.275,9.285,79.356,9.252,79.411,9.192,79.213,9.256,78.98,9.269,78.421,9.105,78.275,8.99,78.192,8.891,78.136,8.663,78.126,8.511,78.06,8.385,77.77,8.19,77.587,8.13,77.518,8.078,77.301,8.145,77.066,8.316,76.967,8.407,76.617,8.847,76.553,8.903,76.483,9.091,76.472,9.161,76.452,9.189,76.419,9.208,76.403,9.237,76.325,9.452,76.292,9.676,76.242,9.927,76.285,9.91,76.343,9.827,76.372,9.707,76.376,9.54,76.42,9.52,76.459,9.536,76.346,9.922,76.249,10.018,76.223,10.024,76.196,10.086,76.193,10.164,76.201,10.201,76.123,10.327,76.096,10.402,75.923,10.784,75.845,11.058,75.724,11.362,75.646,11.468,75.525,11.703,75.423,11.812,75.315,11.958,75.23,12.023,75.197,12.058,74.946,12.565,74.868,12.845,74.803,12.977,74.771,13.077,74.682,13.507,74.682,13.584,74.671,13.668,74.608,13.85,74.499,14.046,74.467,14.169,74.467,14.217,74.397,14.407,74.382,14.495,74.335,14.575,74.28,14.65,74.223,14.709,74.089,14.902,74.041,14.949,73.949,15.075,73.884,15.306,73.801,15.397,73.932,15.397,73.852,15.482,73.814,15.539,73.772,15.573,73.833,15.659,73.733,15.657,73.68,15.709,73.608,15.871,73.476,16.054,73.454,16.152,73.338,16.46,73.239,17.199,73.149,17.527,73.156,17.622,73.047,17.907,72.994,18.098,72.972,18.259,72.943,18.366,72.917,18.576,72.875,18.643,72.871,18.683,72.899,18.779,72.977,18.927,73.006,19.021,72.972,19.153,72.901,19.015,72.835,18.976,72.803,19.079,72.803,19.219,72.795,19.252,72.812,19.299,72.987,19.277,72.788,19.363,72.764,19.413,72.756,19.451,72.799,19.52,72.727,19.578,72.697,19.757,72.676,19.798,72.668,19.831,72.709,20.078,72.881,20.563,72.894,20.673,72.879,20.829,72.841,20.952,72.824,21.084,72.814,21.117,72.752,21.129,72.692,21.178,72.624,21.372,72.687,21.436,72.735,21.471,72.668,21.456,72.613,21.462,72.718,21.551,72.811,21.62,73.022,21.7,73.113,21.75,72.979,21.705,72.84,21.687,72.543,21.697,72.592,21.878,72.644,21.938,72.7,21.972,72.617,21.962,72.522,21.976,72.553,22.16,72.628,22.2,72.709,22.207,72.809,22.233,72.702,22.264,72.59,22.278,72.456,22.248,72.333,22.27,72.183,22.27,72.243,22.245,72.306,22.189,72.274,22.09,72.244,22.028,72.162,21.985,72.094,21.92,72.076,21.863,72.037,21.823,72.103,21.795,72.171,21.774,72.21,21.728,72.257,21.661,72.254,21.531,72.077,21.224,72.015,21.156,71.571,20.971,71.396,20.87,71.025,20.739,70.88,20.715,70.719,20.74,70.485,20.84,70.127,21.095,70.034,21.179,69.748,21.506,69.542,21.679,69.385,21.84,69.192,21.992,69.009,22.197,68.97,22.29,68.983,22.385,69.052,22.437,69.131,22.416,69.194,22.336,69.239,22.3,69.277,22.285,69.549,22.408,69.655,22.404,69.728,22.465,69.819,22.452,70.006,22.548,70.084,22.554,70.177,22.573,70.328,22.816,70.44,22.97,70.513,23.002,70.509,23.04,70.489,23.09,70.435,23.077,70.396,23.03,70.368,22.973,70.339,22.94,70.251,22.971,70.192,22.966,70.118,22.947,69.85,22.856,69.74,22.775,69.665,22.759,69.236,22.849,68.817,23.054,68.641,23.19,68.529,23.364,68.417,23.571,68.454,23.629,68.627,23.754,68.777,23.852,68.642,23.808,68.497,23.748,68.425,23.706,68.343,23.617,68.235,23.597,68.192,23.729,68.165,23.857]],[[93.89,6.831,93.829,6.749,93.709,7.001,93.658,7.016,93.656,7.136,93.684,7.184,93.822,7.237,93.859,7.207,93.93,6.973,93.89,6.831]],[[93.734,7.356,93.638,7.262,93.597,7.319,93.614,7.358,93.655,7.38,93.692,7.411,93.734,7.356]],[[93.141,8.25,93.171,8.212,93.115,8.219,93.064,8.275,93.078,8.328,93.097,8.349,93.141,8.25]],[[93.443,7.878,93.365,7.877,93.342,7.919,93.309,7.964,93.334,8.007,93.375,8.018,93.434,7.948,93.447,7.899,93.443,7.878]],[[93.537,8.057,93.49,8.019,93.478,8.024,93.472,8.053,93.47,8.073,93.461,8.109,93.456,8.172,93.494,8.225,93.532,8.214,93.512,8.16,93.537,8.057]],[[92.788,9.137,92.744,9.131,92.717,9.165,92.713,9.205,92.739,9.231,92.762,9.244,92.786,9.241,92.809,9.173,92.788,9.137]],[[92.503,10.555,92.473,10.521,92.37,10.547,92.377,10.651,92.353,10.751,92.371,10.794,92.448,10.866,92.51,10.897,92.554,10.8,92.574,10.704,92.503,10.555]],[[92.693,11.381,92.645,11.361,92.596,11.386,92.634,11.427,92.64,11.509,92.69,11.463,92.687,11.411,92.693,11.381]],[[92.723,11.536,92.701,11.513,92.668,11.539,92.576,11.718,92.56,11.833,92.534,11.873,92.567,11.931,92.608,11.95,92.632,12.014,92.641,12.112,92.676,12.192,92.695,12.215,92.769,12.216,92.788,12.226,92.778,12.303,92.734,12.336,92.719,12.357,92.721,12.541,92.732,12.616,92.759,12.669,92.74,12.78,92.753,12.821,92.807,12.879,92.831,13.003,92.809,13.04,92.86,13.231,92.857,13.358,92.925,13.486,93.029,13.544,93.062,13.545,93.067,13.436,93.077,13.401,93.016,13.336,93.074,13.252,93.066,13.222,93.043,13.155,93.005,13.089,92.951,13.063,92.91,12.975,92.886,12.942,92.965,12.85,92.99,12.539,92.933,12.453,92.864,12.436,92.879,12.228,92.867,12.181,92.799,12.079,92.786,12.035,92.748,11.993,92.764,11.94,92.797,11.918,92.798,11.875,92.767,11.765,92.765,11.639,92.723,11.536]],[[93.017,12.037,93.062,11.899,92.982,11.959,92.955,12.002,92.996,12.032,93.017,12.037]],[[92.718,12.865,92.686,12.8,92.68,12.939,92.694,12.957,92.711,12.962,92.731,12.949,92.718,12.865]],[[72.78,11.202,72.773,11.196,72.772,11.214,72.782,11.243,72.793,11.263,72.796,11.26,72.793,11.242,72.788,11.216,72.78,11.202]],[[73.067,8.269,73.053,8.257,73.039,8.252,73.029,8.254,73.023,8.266,73.026,8.275,73.039,8.265,73.056,8.275,73.075,8.306,73.079,8.317,73.084,8.311,73.08,8.293,73.067,8.269]]]},{"n":"Iceland","c":"IS","r":3,"ml":2,"lx":-18.67,"ly":64.78,"b":[-24.476,63.407,-13.556,66.526],"p":[[[-15.543,66.229,-15.428,66.225,-15.241,66.259,-15.162,66.282,-14.97,66.36,-14.856,66.381,-14.681,66.376,-14.596,66.382,-14.594,66.374,-14.702,66.342,-14.788,66.331,-14.912,66.284,-15.03,66.178,-15.117,66.126,-15.116,66.102,-15.01,66.061,-14.894,66.038,-14.787,66.059,-14.74,66.051,-14.698,66.02,-14.674,65.99,-14.669,65.96,-14.688,65.897,-14.753,65.834,-14.839,65.781,-14.827,65.764,-14.758,65.756,-14.426,65.79,-14.392,65.787,-14.373,65.77,-14.369,65.739,-14.351,65.71,-14.318,65.684,-14.328,65.658,-14.473,65.575,-14.302,65.628,-14.167,65.642,-13.935,65.616,-13.841,65.586,-13.785,65.533,-13.705,65.551,-13.67,65.55,-13.618,65.519,-13.616,65.487,-13.654,65.441,-13.668,65.399,-13.708,65.382,-13.783,65.369,-13.805,65.355,-13.772,65.323,-13.723,65.291,-13.653,65.29,-13.641,65.275,-13.64,65.257,-13.649,65.237,-13.672,65.223,-13.707,65.215,-13.755,65.193,-13.581,65.143,-13.559,65.125,-13.556,65.098,-13.57,65.068,-13.599,65.036,-13.652,65.017,-13.777,65.014,-13.854,64.993,-13.828,64.958,-13.83,64.914,-13.853,64.862,-13.952,64.784,-14.044,64.742,-14.135,64.715,-14.297,64.724,-14.385,64.745,-14.375,64.677,-14.465,64.636,-14.448,64.601,-14.417,64.583,-14.433,64.538,-14.475,64.494,-14.547,64.446,-14.628,64.416,-14.79,64.38,-14.927,64.32,-15.022,64.296,-15.256,64.297,-15.495,64.258,-15.833,64.177,-16.06,64.111,-16.236,64.037,-16.468,63.916,-16.64,63.865,-16.74,63.852,-16.933,63.841,-17.095,63.808,-17.633,63.747,-17.816,63.713,-17.839,63.682,-17.915,63.636,-17.92,63.62,-17.886,63.607,-17.88,63.59,-17.947,63.536,-18.08,63.496,-18.143,63.497,-18.219,63.531,-18.252,63.53,-18.265,63.525,-18.266,63.514,-18.222,63.473,-18.303,63.454,-18.654,63.407,-19.25,63.442,-19.487,63.479,-19.778,63.537,-19.952,63.552,-20.198,63.556,-20.4,63.637,-20.494,63.687,-20.502,63.708,-20.491,63.732,-20.47,63.748,-20.438,63.757,-20.372,63.758,-20.363,63.765,-20.414,63.805,-20.463,63.792,-20.593,63.735,-20.651,63.737,-20.727,63.766,-20.73,63.793,-20.879,63.804,-21.008,63.838,-21.137,63.888,-21.156,63.907,-21.094,63.934,-21.106,63.94,-21.152,63.945,-21.246,63.935,-21.388,63.873,-21.449,63.858,-22.373,63.844,-22.607,63.837,-22.652,63.828,-22.693,63.869,-22.729,63.959,-22.743,64.019,-22.734,64.048,-22.701,64.083,-22.651,64.077,-22.603,64.05,-22.56,64.01,-22.51,63.991,-22.188,64.039,-22.057,64.071,-22.001,64.102,-21.935,64.154,-21.866,64.18,-21.833,64.205,-21.768,64.285,-21.723,64.322,-21.669,64.349,-21.606,64.367,-21.463,64.379,-21.557,64.398,-21.647,64.398,-21.951,64.314,-22.053,64.314,-22.049,64.327,-22.006,64.351,-21.901,64.392,-21.973,64.395,-22.001,64.413,-22.004,64.452,-21.95,64.515,-21.702,64.598,-21.617,64.61,-21.591,64.626,-21.623,64.64,-21.675,64.648,-21.924,64.563,-22.106,64.533,-22.16,64.539,-22.254,64.572,-22.284,64.587,-22.325,64.624,-22.32,64.647,-22.234,64.714,-22.248,64.727,-22.307,64.733,-22.467,64.795,-22.72,64.789,-23.347,64.824,-23.476,64.809,-23.69,64.757,-23.819,64.739,-23.879,64.751,-23.933,64.779,-23.982,64.816,-24.026,64.863,-24.007,64.896,-23.924,64.915,-23.864,64.924,-23.693,64.913,-23.485,64.946,-23.353,64.953,-23.315,64.958,-23.237,64.993,-23.198,65.002,-23.138,64.99,-23.109,64.966,-22.9,65.003,-22.828,65.022,-22.82,65.033,-22.788,65.046,-22.684,65.026,-22.6,65.026,-22.494,65.04,-22.308,65.046,-21.892,65.049,-21.83,65.079,-21.8,65.106,-21.764,65.174,-21.78,65.188,-22.04,65.125,-22.099,65.126,-22.4,65.159,-22.509,65.197,-22.473,65.227,-22.314,65.292,-22.149,65.344,-21.907,65.4,-21.85,65.422,-21.844,65.447,-22.006,65.493,-22.311,65.481,-22.39,65.535,-22.644,65.568,-22.813,65.547,-22.902,65.58,-23.122,65.535,-23.605,65.469,-23.796,65.423,-23.9,65.408,-24.019,65.445,-24.224,65.487,-24.455,65.5,-24.476,65.525,-24.341,65.601,-24.249,65.615,-24.156,65.608,-23.979,65.555,-23.857,65.538,-24.01,65.616,-24.006,65.646,-24.018,65.691,-24.065,65.71,-24.112,65.76,-24.093,65.776,-24.032,65.782,-23.909,65.766,-23.616,65.68,-23.472,65.695,-23.393,65.727,-23.285,65.75,-23.316,65.762,-23.569,65.764,-23.705,65.781,-23.773,65.806,-23.833,65.849,-23.812,65.869,-23.741,65.885,-23.525,65.88,-23.667,65.954,-23.767,65.997,-23.777,66.018,-23.771,66.043,-23.757,66.061,-23.737,66.069,-23.489,66.026,-23.434,66.024,-23.485,66.052,-23.594,66.093,-23.599,66.109,-23.553,66.122,-23.53,66.145,-23.528,66.164,-23.453,66.181,-23.377,66.182,-23.3,66.167,-23.063,66.086,-23.029,66.064,-23.017,66.034,-23.029,65.997,-23.019,65.982,-22.926,65.995,-22.852,65.979,-22.815,65.983,-22.723,66.039,-22.66,66.026,-22.622,66,-22.61,65.976,-22.604,65.944,-22.62,65.877,-22.616,65.867,-22.552,65.905,-22.442,65.908,-22.428,65.927,-22.424,65.998,-22.433,66.058,-22.445,66.07,-22.806,66.153,-22.869,66.172,-22.948,66.213,-22.932,66.233,-22.862,66.251,-22.756,66.259,-22.509,66.258,-22.484,66.266,-22.532,66.288,-22.646,66.302,-22.673,66.314,-22.686,66.338,-22.821,66.325,-22.972,66.324,-23.117,66.339,-23.12,66.357,-23.063,66.384,-22.944,66.429,-22.889,66.441,-22.724,66.433,-22.559,66.445,-22.426,66.43,-22.32,66.385,-22.17,66.307,-21.967,66.257,-21.948,66.241,-21.84,66.2,-21.625,66.09,-21.407,66.026,-21.397,66.009,-21.433,65.99,-21.517,65.968,-21.497,65.955,-21.388,65.939,-21.309,65.895,-21.303,65.876,-21.375,65.742,-21.413,65.713,-21.457,65.698,-21.658,65.724,-21.61,65.681,-21.466,65.635,-21.434,65.61,-21.455,65.585,-21.439,65.579,-21.387,65.592,-21.365,65.578,-21.374,65.536,-21.396,65.502,-21.432,65.474,-21.422,65.462,-21.365,65.466,-21.313,65.459,-21.23,65.421,-21.163,65.304,-21.13,65.267,-21.106,65.3,-21.076,65.385,-21.047,65.428,-21.021,65.43,-20.998,65.445,-20.979,65.471,-20.94,65.565,-20.804,65.636,-20.74,65.658,-20.679,65.663,-20.649,65.654,-20.548,65.579,-20.487,65.567,-20.455,65.571,-20.412,65.622,-20.357,65.719,-20.344,65.828,-20.374,65.948,-20.357,66.033,-20.292,66.084,-20.208,66.1,-20.103,66.08,-20.026,66.049,-19.875,65.93,-19.753,65.868,-19.648,65.801,-19.594,65.779,-19.49,65.768,-19.462,65.772,-19.443,65.788,-19.434,65.814,-19.456,65.985,-19.427,66.038,-19.383,66.076,-19.195,66.098,-19.093,66.122,-18.994,66.16,-18.911,66.181,-18.846,66.184,-18.778,66.169,-18.706,66.136,-18.595,66.071,-18.455,65.965,-18.277,65.885,-18.184,65.758,-18.164,65.737,-18.142,65.734,-18.118,65.751,-18.103,65.774,-18.099,65.83,-18.149,65.905,-18.315,66.093,-18.318,66.129,-18.297,66.157,-18.18,66.161,-17.907,66.143,-17.82,66.114,-17.634,65.999,-17.582,65.971,-17.55,65.964,-17.539,65.978,-17.467,66,-17.417,66.026,-17.334,66.089,-17.153,66.203,-17.115,66.206,-17.062,66.197,-16.97,66.167,-16.925,66.143,-16.838,66.125,-16.748,66.132,-16.625,66.172,-16.485,66.196,-16.437,66.253,-16.428,66.278,-16.541,66.447,-16.493,66.481,-16.249,66.523,-16.036,66.526,-15.985,66.515,-15.851,66.433,-15.76,66.392,-15.714,66.359,-15.703,66.286,-15.647,66.259,-15.543,66.229]]]},{"n":"Hungary","c":"HU","r":5,"ml":4,"lx":19.45,"ly":47.09,"b":[16.093,45.753,22.877,48.553],"p":[[[22.132,48.405,22.227,48.413,22.231,48.412,22.254,48.407,22.269,48.361,22.272,48.358,22.295,48.327,22.317,48.287,22.35,48.256,22.424,48.243,22.52,48.205,22.582,48.134,22.676,48.104,22.683,48.104,22.702,48.107,22.769,48.11,22.782,48.095,22.836,48.06,22.857,48.03,22.846,47.997,22.856,47.96,22.877,47.947,22.852,47.923,22.677,47.799,22.608,47.766,22.563,47.76,22.491,47.773,22.417,47.763,22.351,47.736,22.291,47.728,22.245,47.696,22.185,47.629,22.112,47.572,22.038,47.537,22,47.505,21.995,47.396,21.954,47.364,21.899,47.333,21.869,47.305,21.785,47.138,21.722,47.085,21.661,47.044,21.651,47.007,21.653,46.964,21.584,46.878,21.494,46.79,21.477,46.753,21.497,46.704,21.411,46.648,21.361,46.621,21.32,46.608,21.295,46.572,21.252,46.486,21.263,46.448,21.265,46.412,21.192,46.392,21.17,46.353,21.152,46.304,21.122,46.282,21.04,46.242,20.837,46.26,20.76,46.246,20.737,46.217,20.733,46.194,20.707,46.173,20.661,46.146,20.614,46.133,20.508,46.167,20.281,46.133,20.242,46.109,20.21,46.126,20.161,46.142,19.934,46.161,19.844,46.146,19.725,46.152,19.613,46.169,19.531,46.155,19.458,46.087,19.421,46.064,19.393,46.05,19.33,46.029,19.278,46.003,19.208,45.984,19.146,45.987,19.087,46.016,19.066,46.01,19.048,45.983,19.016,45.96,18.928,45.931,18.905,45.932,18.9,45.932,18.833,45.911,18.722,45.899,18.666,45.907,18.565,45.813,18.534,45.796,18.437,45.767,18.358,45.753,18.291,45.764,18.264,45.765,17.964,45.77,17.807,45.79,17.706,45.827,17.64,45.868,17.607,45.914,17.529,45.941,17.406,45.951,17.311,45.996,17.242,46.077,17.15,46.14,17.033,46.187,16.94,46.254,16.871,46.339,16.748,46.416,16.57,46.485,16.516,46.5,16.506,46.522,16.418,46.607,16.381,46.639,16.385,46.681,16.367,46.705,16.335,46.722,16.318,46.783,16.308,46.828,16.284,46.857,16.093,46.863,16.253,46.972,16.332,47.002,16.424,46.997,16.453,47.007,16.461,47.022,16.477,47.058,16.485,47.091,16.493,47.123,16.483,47.14,16.438,47.146,16.417,47.223,16.44,47.253,16.463,47.273,16.434,47.367,16.443,47.4,16.515,47.405,16.574,47.425,16.623,47.448,16.637,47.477,16.677,47.536,16.64,47.609,16.432,47.656,16.421,47.674,16.47,47.695,16.521,47.724,16.551,47.747,16.591,47.751,16.647,47.739,16.748,47.686,16.786,47.679,16.823,47.694,16.863,47.697,16.973,47.695,17.067,47.708,17.046,47.764,17.046,47.805,17.03,47.837,17.04,47.873,17.078,47.901,17.089,47.964,17.147,48.006,17.175,48.012,17.277,48.004,17.302,47.993,17.317,47.991,17.481,47.888,17.635,47.81,17.762,47.77,17.948,47.767,18.146,47.763,18.476,47.777,18.724,47.787,18.741,47.806,18.778,47.853,18.748,47.893,18.75,47.939,18.792,48,18.914,48.051,19.265,48.073,19.467,48.111,19.497,48.162,19.564,48.213,19.625,48.223,19.709,48.2,19.81,48.155,19.899,48.131,19.95,48.147,20.129,48.222,20.334,48.296,20.475,48.495,20.49,48.527,20.643,48.55,20.867,48.546,20.981,48.52,21.067,48.506,21.196,48.511,21.382,48.553,21.451,48.552,21.505,48.522,21.563,48.496,21.603,48.464,21.633,48.419,21.649,48.401,21.675,48.378,21.721,48.347,21.767,48.338,22.111,48.393,22.132,48.405]]]},{"n":"Honduras","c":"HN","r":5,"ml":4.5,"lx":-86.89,"ly":14.79,"b":[-89.363,12.979,-83.158,16.514],"p":[[[-83.158,14.993,-83.415,15.008,-83.537,14.977,-83.59,14.908,-83.635,14.876,-83.674,14.884,-83.751,14.856,-83.867,14.794,-83.972,14.771,-84.066,14.786,-84.093,14.771,-84.1,14.751,-84.114,14.731,-84.151,14.72,-84.192,14.726,-84.239,14.748,-84.264,14.739,-84.267,14.698,-84.292,14.687,-84.34,14.706,-84.394,14.692,-84.454,14.644,-84.538,14.633,-84.646,14.661,-84.73,14.713,-84.789,14.79,-84.86,14.81,-84.985,14.752,-85.037,14.686,-85.049,14.645,-85.037,14.608,-85.059,14.583,-85.117,14.571,-85.161,14.525,-85.192,14.447,-85.198,14.386,-85.179,14.343,-85.208,14.312,-85.284,14.292,-85.374,14.224,-85.477,14.109,-85.58,14.028,-85.682,13.983,-85.731,13.932,-85.728,13.876,-85.734,13.859,-85.753,13.852,-85.787,13.844,-85.984,13.966,-86.04,14.05,-86.089,14.037,-86.151,13.995,-86.238,13.899,-86.332,13.77,-86.377,13.756,-86.61,13.775,-86.734,13.763,-86.759,13.746,-86.771,13.699,-86.764,13.635,-86.73,13.407,-86.711,13.313,-86.729,13.284,-86.792,13.28,-86.874,13.267,-86.918,13.224,-86.929,13.179,-86.933,13.118,-86.959,13.054,-87.009,13.008,-87.059,12.991,-87.337,12.979,-87.333,13.085,-87.413,13.127,-87.458,13.215,-87.498,13.275,-87.485,13.311,-87.489,13.353,-87.602,13.386,-87.708,13.36,-87.769,13.377,-87.814,13.399,-87.737,13.451,-87.732,13.483,-87.756,13.506,-87.782,13.521,-87.774,13.58,-87.759,13.65,-87.715,13.813,-87.731,13.841,-87.802,13.89,-87.892,13.895,-87.991,13.88,-88.039,13.905,-88.08,13.961,-88.151,13.987,-88.276,13.943,-88.408,13.875,-88.449,13.851,-88.483,13.854,-88.498,13.905,-88.504,13.964,-88.513,13.979,-88.583,14,-88.666,14.016,-88.708,14.032,-88.747,14.072,-88.846,14.125,-88.868,14.164,-89,14.253,-89.027,14.297,-89.057,14.329,-89.121,14.37,-89.17,14.36,-89.337,14.411,-89.363,14.416,-89.339,14.461,-89.287,14.53,-89.172,14.607,-89.162,14.669,-89.192,14.789,-89.222,14.866,-89.206,14.901,-89.17,15.04,-89.143,15.072,-88.976,15.143,-88.961,15.152,-88.83,15.251,-88.684,15.36,-88.533,15.481,-88.365,15.616,-88.271,15.695,-88.228,15.729,-88.131,15.701,-88.055,15.765,-88.01,15.786,-87.907,15.863,-87.875,15.879,-87.702,15.911,-87.618,15.91,-87.545,15.832,-87.487,15.79,-87.377,15.826,-87.286,15.834,-86.907,15.762,-86.757,15.794,-86.481,15.801,-86.357,15.783,-86.181,15.885,-86.069,15.906,-85.936,15.953,-85.954,16.002,-85.986,16.024,-85.784,16.003,-85.484,15.9,-85.164,15.918,-85.048,15.974,-84.974,15.99,-84.646,15.884,-84.56,15.802,-84.492,15.794,-84.44,15.813,-84.426,15.829,-84.49,15.847,-84.52,15.873,-84.261,15.823,-83.775,15.437,-83.765,15.405,-83.973,15.52,-84.083,15.511,-84.111,15.492,-84.105,15.43,-84.095,15.401,-84.048,15.398,-84.013,15.414,-83.927,15.394,-83.871,15.353,-83.802,15.289,-83.76,15.22,-83.716,15.219,-83.672,15.261,-83.59,15.266,-83.536,15.219,-83.498,15.222,-83.551,15.294,-83.676,15.365,-83.646,15.368,-83.369,15.24,-83.291,15.079,-83.226,15.042,-83.158,14.993]],[[-86.42,16.378,-86.58,16.3,-86.63,16.302,-86.557,16.362,-86.438,16.414,-86.338,16.439,-86.256,16.428,-86.42,16.378]],[[-85.871,16.462,-85.947,16.404,-85.961,16.43,-85.924,16.483,-85.878,16.514,-85.834,16.511,-85.844,16.488,-85.871,16.462]]]},{"n":"Haiti","c":"HT","r":5,"ml":4,"lx":-72.22,"ly":19.26,"b":[-74.478,18.039,-71.645,20.094],"p":[[[-71.779,19.718,-71.757,19.688,-71.711,19.487,-71.707,19.422,-71.753,19.324,-71.746,19.286,-71.647,19.196,-71.645,19.164,-71.657,19.131,-71.742,19.046,-71.807,18.987,-71.786,18.92,-71.734,18.856,-71.727,18.803,-71.743,18.733,-71.824,18.646,-71.867,18.614,-71.987,18.61,-72,18.598,-71.94,18.513,-71.873,18.416,-71.762,18.341,-71.737,18.271,-71.764,18.204,-71.768,18.039,-71.853,18.119,-71.946,18.186,-72.002,18.212,-72.06,18.229,-72.504,18.22,-72.553,18.208,-72.592,18.187,-72.633,18.176,-72.755,18.156,-72.877,18.152,-73.16,18.206,-73.272,18.234,-73.385,18.251,-73.515,18.245,-73.644,18.229,-73.747,18.19,-73.825,18.122,-73.839,18.058,-73.885,18.042,-73.989,18.143,-74.085,18.215,-74.195,18.269,-74.419,18.346,-74.46,18.393,-74.478,18.45,-74.387,18.625,-74.284,18.657,-74.228,18.663,-74.1,18.641,-73.976,18.601,-73.862,18.575,-73.687,18.565,-73.592,18.522,-72.917,18.456,-72.789,18.435,-72.739,18.442,-72.696,18.468,-72.66,18.515,-72.618,18.551,-72.418,18.559,-72.376,18.574,-72.347,18.624,-72.348,18.675,-72.465,18.744,-72.649,18.894,-72.811,19.072,-72.741,19.131,-72.768,19.241,-72.742,19.342,-72.703,19.441,-72.863,19.526,-73.053,19.611,-73.316,19.637,-73.396,19.659,-73.438,19.722,-73.401,19.807,-73.315,19.855,-73.218,19.884,-73.118,19.904,-72.877,19.928,-72.637,19.901,-72.43,19.813,-72.22,19.745,-71.954,19.722,-71.835,19.697,-71.779,19.718]],[[-72.805,18.778,-72.822,18.707,-73.078,18.791,-73.285,18.897,-73.276,18.954,-73.171,18.967,-73.069,18.932,-72.919,18.861,-72.805,18.778]],[[-72.664,20.038,-72.623,20.014,-72.639,19.986,-72.74,20.003,-72.844,20.035,-72.878,20.027,-72.899,20.031,-72.96,20.062,-72.907,20.086,-72.851,20.094,-72.791,20.092,-72.664,20.038]]]},{"n":"Guyana","c":"GY","r":4,"ml":4,"lx":-58.94,"ly":5.12,"b":[-61.391,1.201,-56.483,8.549],"p":[[[-60.742,5.202,-60.954,5.437,-61.167,5.674,-61.377,5.907,-61.391,5.939,-61.303,6.05,-61.225,6.129,-61.159,6.174,-61.129,6.214,-61.152,6.385,-61.151,6.447,-61.182,6.513,-61.204,6.588,-61.177,6.651,-61.146,6.695,-61.105,6.711,-61.007,6.727,-60.938,6.733,-60.914,6.758,-60.873,6.787,-60.821,6.788,-60.718,6.768,-60.671,6.806,-60.586,6.857,-60.395,6.945,-60.352,7.003,-60.322,7.092,-60.325,7.134,-60.345,7.15,-60.392,7.165,-60.465,7.167,-60.523,7.144,-60.583,7.156,-60.633,7.211,-60.636,7.257,-60.607,7.321,-60.624,7.363,-60.719,7.499,-60.719,7.536,-60.649,7.597,-60.61,7.648,-60.556,7.772,-60.514,7.813,-60.381,7.828,-60.347,7.854,-60.279,7.919,-60.178,7.994,-60.032,8.054,-59.991,8.162,-59.965,8.192,-59.849,8.249,-59.829,8.279,-59.832,8.306,-60.018,8.549,-59.981,8.533,-59.837,8.374,-59.757,8.34,-59.74,8.339,-59.739,8.38,-59.666,8.363,-59.477,8.254,-59.2,8.075,-58.812,7.736,-58.701,7.607,-58.627,7.546,-58.511,7.398,-58.477,7.326,-58.481,7.038,-58.583,6.844,-58.608,6.697,-58.613,6.503,-58.673,6.391,-58.594,6.452,-58.569,6.627,-58.502,6.734,-58.415,6.851,-58.298,6.879,-58.173,6.829,-58.072,6.821,-57.983,6.786,-57.793,6.599,-57.608,6.45,-57.54,6.332,-57.344,6.272,-57.228,6.178,-57.19,6.097,-57.167,5.885,-57.205,5.565,-57.195,5.548,-57.248,5.485,-57.258,5.445,-57.292,5.374,-57.319,5.335,-57.28,5.247,-57.235,5.243,-57.218,5.232,-57.207,5.214,-57.21,5.195,-57.227,5.179,-57.269,5.157,-57.31,5.106,-57.306,5.05,-57.331,5.02,-57.412,5.005,-57.571,5.004,-57.649,5.001,-57.711,4.991,-57.752,4.954,-57.804,4.929,-57.845,4.923,-57.881,4.881,-57.917,4.82,-57.905,4.779,-57.868,4.724,-57.846,4.668,-57.875,4.577,-57.906,4.507,-57.925,4.453,-57.95,4.35,-58.011,4.236,-58.054,4.172,-58.054,4.102,-58.032,4.002,-57.908,3.857,-57.867,3.787,-57.833,3.676,-57.72,3.588,-57.649,3.517,-57.656,3.424,-57.647,3.395,-57.603,3.371,-57.55,3.353,-57.491,3.354,-57.438,3.362,-57.426,3.375,-57.304,3.377,-57.29,3.354,-57.283,3.219,-57.278,3.164,-57.249,3.142,-57.232,3.109,-57.231,3.079,-57.225,3.003,-57.207,2.963,-57.21,2.883,-57.197,2.853,-57.164,2.833,-57.121,2.776,-57.105,2.768,-57.097,2.748,-57.06,2.666,-57.042,2.641,-57.029,2.638,-57.023,2.609,-56.997,2.532,-56.979,2.513,-56.945,2.457,-56.931,2.395,-56.886,2.326,-56.841,2.277,-56.82,2.227,-56.761,2.115,-56.704,2.036,-56.627,2.016,-56.563,2.005,-56.522,1.975,-56.483,1.942,-56.525,1.927,-56.564,1.907,-56.616,1.923,-56.69,1.914,-56.766,1.892,-56.837,1.881,-56.97,1.916,-57.01,1.921,-57.038,1.936,-57.093,2.006,-57.119,2.014,-57.19,1.982,-57.276,1.959,-57.317,1.963,-57.367,1.94,-57.413,1.909,-57.5,1.774,-57.546,1.726,-57.594,1.704,-57.692,1.705,-57.796,1.7,-57.873,1.667,-57.946,1.651,-57.983,1.648,-57.995,1.574,-58.012,1.54,-58.035,1.52,-58.091,1.514,-58.142,1.517,-58.173,1.548,-58.23,1.563,-58.281,1.574,-58.314,1.592,-58.341,1.588,-58.363,1.557,-58.38,1.53,-58.396,1.482,-58.473,1.466,-58.506,1.439,-58.487,1.348,-58.496,1.312,-58.512,1.285,-58.605,1.279,-58.685,1.281,-58.73,1.248,-58.787,1.208,-58.822,1.201,-58.862,1.204,-58.917,1.249,-58.969,1.305,-59.1,1.344,-59.231,1.376,-59.317,1.465,-59.337,1.508,-59.378,1.527,-59.479,1.632,-59.536,1.7,-59.597,1.718,-59.667,1.746,-59.664,1.795,-59.669,1.842,-59.699,1.861,-59.741,1.874,-59.756,1.901,-59.752,1.962,-59.744,2.122,-59.755,2.274,-59.849,2.327,-59.89,2.363,-59.961,2.588,-59.994,2.69,-59.996,2.765,-59.972,2.99,-59.946,3.088,-59.873,3.283,-59.831,3.349,-59.829,3.399,-59.833,3.462,-59.854,3.588,-59.732,3.667,-59.679,3.7,-59.67,3.753,-59.604,3.82,-59.575,3.883,-59.551,3.934,-59.558,3.96,-59.586,3.975,-59.62,4.023,-59.691,4.16,-59.717,4.188,-59.739,4.227,-59.727,4.288,-59.7,4.354,-59.703,4.381,-59.746,4.417,-59.833,4.476,-59.906,4.48,-59.962,4.502,-60.045,4.505,-60.111,4.511,-60.149,4.533,-60.141,4.57,-60.125,4.598,-60.069,4.667,-60.032,4.741,-60.027,4.813,-60.015,4.908,-59.999,4.99,-59.991,5.083,-60.078,5.144,-60.106,5.194,-60.142,5.239,-60.182,5.239,-60.242,5.258,-60.335,5.199,-60.409,5.21,-60.46,5.188,-60.576,5.192,-60.651,5.221,-60.742,5.202]]]},{"n":"Guinea-Bissau","c":"GW","r":6,"ml":5,"lx":-14.52,"ly":12.16,"b":[-16.712,10.94,-13.674,12.68],"p":[[[-16.712,12.355,-16.657,12.364,-16.521,12.349,-16.416,12.368,-16.342,12.4,-16.242,12.443,-16.144,12.457,-15.84,12.438,-15.575,12.49,-15.378,12.589,-15.196,12.68,-14.961,12.679,-14.708,12.678,-14.349,12.676,-14.065,12.675,-13.729,12.674,-13.733,12.593,-13.674,12.479,-13.682,12.393,-13.708,12.313,-13.73,12.281,-13.76,12.262,-13.849,12.263,-13.887,12.247,-13.947,12.215,-13.949,12.178,-13.901,12.143,-13.862,12.093,-13.816,12.054,-13.738,12.01,-13.731,11.96,-13.729,11.834,-13.733,11.736,-13.953,11.665,-14.122,11.652,-14.266,11.66,-14.328,11.63,-14.452,11.556,-14.605,11.512,-14.683,11.508,-14.72,11.482,-14.779,11.406,-14.944,11.072,-14.999,10.992,-15.043,10.94,-15.094,11.011,-15.055,11.142,-15.097,11.14,-15.181,11.034,-15.222,11.031,-15.217,11.156,-15.263,11.161,-15.317,11.152,-15.393,11.217,-15.401,11.266,-15.395,11.334,-15.348,11.378,-15.355,11.396,-15.399,11.401,-15.449,11.39,-15.479,11.41,-15.429,11.499,-15.253,11.573,-15.164,11.581,-15.073,11.598,-15.122,11.662,-15.23,11.687,-15.317,11.669,-15.36,11.623,-15.413,11.615,-15.502,11.724,-15.5,11.778,-15.467,11.843,-15.416,11.872,-15.211,11.871,-15.133,11.907,-15.102,11.914,-15.072,11.947,-15.078,11.969,-15.112,11.97,-15.188,11.927,-15.435,11.944,-15.513,11.918,-15.651,11.818,-15.819,11.763,-15.942,11.787,-15.903,11.92,-15.92,11.938,-15.959,11.96,-16.138,11.917,-16.274,11.978,-16.328,12.052,-16.319,12.144,-16.255,12.206,-16.245,12.237,-16.312,12.243,-16.437,12.204,-16.712,12.355]],[[-16.115,11.059,-16.195,11.045,-16.231,11.094,-16.236,11.113,-16.195,11.13,-16.176,11.131,-16.144,11.167,-16.105,11.191,-16.087,11.199,-16.067,11.197,-16.053,11.118,-16.072,11.084,-16.115,11.059]],[[-15.896,11.082,-15.905,11.055,-15.964,11.059,-15.951,11.087,-15.963,11.095,-15.946,11.18,-15.938,11.193,-15.909,11.161,-15.905,11.148,-15.896,11.082]],[[-15.725,11.215,-15.725,11.175,-15.767,11.182,-15.78,11.195,-15.755,11.269,-15.717,11.302,-15.672,11.296,-15.658,11.286,-15.667,11.258,-15.687,11.234,-15.725,11.215]],[[-15.553,11.537,-15.563,11.514,-15.62,11.533,-15.537,11.618,-15.482,11.632,-15.484,11.568,-15.526,11.554,-15.553,11.537]],[[-15.902,11.466,-15.949,11.434,-15.997,11.449,-16.023,11.477,-16.019,11.527,-15.965,11.598,-15.915,11.589,-15.902,11.466]],[[-15.986,11.882,-16.038,11.76,-16.102,11.773,-16.147,11.846,-16.152,11.877,-16.022,11.887,-15.986,11.882]]]},{"n":"Guinea","c":"GN","r":3,"ml":3,"lx":-10.02,"ly":10.62,"b":[-15.051,7.216,-7.681,12.674],"p":[[[-10.283,8.485,-10.36,8.496,-10.394,8.481,-10.496,8.362,-10.558,8.316,-10.604,8.319,-10.653,8.33,-10.687,8.322,-10.712,8.335,-10.702,8.364,-10.677,8.401,-10.628,8.53,-10.503,8.66,-10.501,8.688,-10.552,8.764,-10.606,8.868,-10.606,8.979,-10.616,9.059,-10.727,9.082,-10.747,9.095,-10.75,9.122,-10.721,9.194,-10.688,9.261,-10.683,9.289,-10.691,9.314,-10.759,9.385,-10.865,9.516,-10.963,9.662,-11.047,9.786,-11.116,9.843,-11.181,9.925,-11.206,9.978,-11.274,9.997,-11.472,9.995,-11.71,9.994,-11.911,9.993,-11.923,9.923,-12.142,9.875,-12.278,9.93,-12.428,9.898,-12.501,9.862,-12.524,9.787,-12.558,9.705,-12.59,9.671,-12.604,9.634,-12.622,9.601,-12.652,9.562,-12.684,9.484,-12.756,9.374,-12.831,9.302,-12.959,9.263,-12.999,9.147,-13.028,9.104,-13.077,9.07,-13.13,9.048,-13.178,9.061,-13.234,9.07,-13.293,9.049,-13.303,9.078,-13.269,9.171,-13.296,9.219,-13.396,9.314,-13.406,9.361,-13.436,9.42,-13.568,9.543,-13.691,9.536,-13.657,9.639,-13.659,9.776,-13.7,9.851,-13.69,9.928,-13.713,9.923,-13.754,9.87,-13.82,9.887,-13.955,9.969,-14.022,10.048,-14.03,10.115,-14.045,10.141,-14.086,10.127,-14.17,10.129,-14.427,10.248,-14.61,10.55,-14.614,10.618,-14.587,10.735,-14.594,10.767,-14.677,10.689,-14.693,10.741,-14.757,10.862,-14.776,10.932,-14.837,10.963,-14.887,10.968,-14.925,10.945,-14.975,10.803,-15.012,10.804,-15.051,10.835,-15.043,10.94,-14.999,10.992,-14.944,11.072,-14.779,11.406,-14.72,11.482,-14.683,11.508,-14.605,11.512,-14.452,11.556,-14.328,11.63,-14.266,11.66,-14.122,11.652,-13.953,11.665,-13.733,11.736,-13.729,11.834,-13.731,11.96,-13.738,12.01,-13.816,12.054,-13.862,12.093,-13.901,12.143,-13.949,12.178,-13.947,12.215,-13.887,12.247,-13.849,12.263,-13.76,12.262,-13.73,12.281,-13.708,12.313,-13.682,12.393,-13.674,12.479,-13.733,12.593,-13.729,12.674,-13.406,12.662,-13.373,12.654,-13.228,12.64,-13.138,12.64,-13.083,12.634,-13.06,12.615,-13.064,12.581,-13.08,12.536,-13.061,12.49,-13.012,12.478,-12.986,12.492,-12.961,12.514,-12.931,12.532,-12.888,12.52,-12.797,12.452,-12.713,12.433,-12.621,12.396,-12.534,12.376,-12.457,12.378,-12.399,12.34,-12.291,12.328,-12.152,12.377,-12.042,12.398,-11.889,12.403,-11.808,12.387,-11.574,12.426,-11.457,12.418,-11.389,12.404,-11.418,12.378,-11.448,12.319,-11.475,12.247,-11.502,12.199,-11.492,12.167,-11.415,12.104,-11.305,12.015,-11.261,12.004,-11.21,12.025,-11.129,12.095,-11.066,12.171,-11.005,12.208,-10.933,12.205,-10.876,12.152,-10.806,12.034,-10.743,11.927,-10.735,11.916,-10.709,11.899,-10.677,11.899,-10.644,11.926,-10.619,11.941,-10.59,11.99,-10.466,12.139,-10.373,12.18,-10.34,12.19,-10.275,12.213,-10.167,12.177,-10.011,12.116,-9.821,12.042,-9.754,12.03,-9.715,12.042,-9.658,12.143,-9.588,12.182,-9.487,12.229,-9.405,12.252,-9.358,12.255,-9.34,12.283,-9.332,12.324,-9.341,12.366,-9.394,12.442,-9.395,12.465,-9.365,12.479,-9.3,12.49,-9.216,12.483,-9.12,12.45,-9.043,12.402,-8.999,12.346,-8.951,12.226,-8.914,12.109,-8.818,11.923,-8.82,11.807,-8.822,11.673,-8.78,11.648,-8.733,11.638,-8.711,11.618,-8.665,11.515,-8.621,11.485,-8.569,11.478,-8.471,11.412,-8.407,11.386,-8.399,11.367,-8.401,11.339,-8.425,11.305,-8.464,11.281,-8.52,11.236,-8.567,11.177,-8.664,11.036,-8.667,11.009,-8.646,10.99,-8.606,10.987,-8.564,10.997,-8.475,11.048,-8.404,11.03,-8.337,10.991,-8.313,10.95,-8.306,10.896,-8.322,10.827,-8.324,10.75,-8.302,10.618,-8.267,10.486,-8.231,10.438,-8.007,10.322,-7.986,10.278,-7.974,10.23,-7.991,10.163,-8.014,10.125,-8.078,10.067,-8.137,10.022,-8.155,9.973,-8.146,9.882,-8.146,9.675,-8.137,9.496,-8.089,9.431,-8.031,9.398,-7.963,9.404,-7.896,9.416,-7.9,9.309,-7.918,9.189,-7.839,9.152,-7.8,9.115,-7.778,9.081,-7.902,9.017,-7.938,8.98,-7.955,8.879,-7.951,8.787,-7.784,8.721,-7.72,8.643,-7.691,8.563,-7.681,8.41,-7.696,8.376,-7.739,8.375,-7.787,8.422,-7.824,8.468,-7.869,8.468,-7.953,8.478,-8.049,8.495,-8.168,8.491,-8.21,8.483,-8.237,8.456,-8.244,8.408,-8.256,8.254,-8.217,8.22,-8.141,8.181,-8.091,8.165,-8.049,8.17,-8.017,8.145,-8.01,8.079,-8.032,8.03,-8.074,7.984,-8.127,7.868,-8.118,7.824,-8.115,7.761,-8.206,7.59,-8.232,7.557,-8.352,7.591,-8.43,7.602,-8.486,7.558,-8.522,7.586,-8.564,7.625,-8.579,7.677,-8.607,7.688,-8.66,7.688,-8.708,7.659,-8.729,7.605,-8.733,7.544,-8.74,7.496,-8.769,7.467,-8.828,7.392,-8.856,7.323,-8.89,7.263,-8.938,7.266,-8.961,7.275,-8.977,7.259,-9.052,7.225,-9.118,7.216,-9.135,7.251,-9.173,7.278,-9.215,7.333,-9.263,7.378,-9.355,7.409,-9.392,7.395,-9.435,7.398,-9.464,7.416,-9.46,7.443,-9.411,7.51,-9.384,7.572,-9.369,7.64,-9.369,7.704,-9.395,7.795,-9.436,7.867,-9.446,7.908,-9.442,7.968,-9.451,8.023,-9.465,8.052,-9.471,8.107,-9.484,8.157,-9.508,8.176,-9.522,8.26,-9.518,8.346,-9.554,8.379,-9.61,8.402,-9.643,8.436,-9.664,8.474,-9.684,8.484,-9.701,8.482,-9.717,8.459,-9.736,8.454,-9.768,8.535,-9.782,8.538,-9.805,8.519,-10.064,8.43,-10.076,8.465,-10.098,8.506,-10.147,8.52,-10.233,8.489,-10.283,8.485]]]},{"n":"Guatemala","c":"GT","r":3,"ml":3,"lx":-90.5,"ly":14.98,"b":[-92.235,13.737,-88.228,17.816],"p":[[[-92.235,14.545,-92.209,14.571,-92.187,14.63,-92.16,14.691,-92.176,14.761,-92.186,14.818,-92.156,14.901,-92.159,14.964,-92.144,15.002,-92.099,15.027,-92.075,15.074,-92.204,15.238,-92.204,15.275,-92.187,15.321,-92.082,15.496,-91.957,15.703,-91.819,15.932,-91.737,16.07,-91.434,16.07,-91.234,16.071,-90.98,16.071,-90.703,16.071,-90.522,16.071,-90.447,16.073,-90.46,16.162,-90.45,16.261,-90.417,16.351,-90.417,16.391,-90.471,16.44,-90.576,16.468,-90.634,16.511,-90.634,16.565,-90.66,16.631,-90.711,16.708,-90.816,16.787,-90.976,16.868,-91.112,16.976,-91.224,17.112,-91.319,17.2,-91.392,17.236,-91.41,17.256,-91.196,17.254,-90.993,17.252,-90.992,17.447,-90.99,17.621,-90.989,17.816,-90.622,17.816,-90.184,17.816,-89.729,17.815,-89.372,17.815,-89.161,17.815,-89.171,17.572,-89.182,17.291,-89.19,17.085,-89.201,16.809,-89.212,16.527,-89.228,16.143,-89.237,15.894,-89.233,15.889,-89.114,15.901,-88.937,15.89,-88.894,15.891,-88.84,15.869,-88.798,15.863,-88.709,15.807,-88.603,15.764,-88.536,15.85,-88.572,15.901,-88.598,15.927,-88.594,15.95,-88.228,15.729,-88.271,15.695,-88.365,15.616,-88.533,15.481,-88.684,15.36,-88.83,15.251,-88.961,15.152,-88.976,15.143,-89.143,15.072,-89.17,15.04,-89.206,14.901,-89.222,14.866,-89.192,14.789,-89.162,14.669,-89.172,14.607,-89.287,14.53,-89.339,14.461,-89.363,14.416,-89.383,14.428,-89.419,14.431,-89.501,14.414,-89.541,14.41,-89.574,14.39,-89.577,14.347,-89.555,14.277,-89.547,14.241,-89.57,14.225,-89.671,14.183,-89.711,14.141,-89.749,14.077,-89.794,14.05,-89.84,14.055,-89.873,14.046,-89.943,13.997,-90.048,13.904,-90.105,13.835,-90.106,13.783,-90.095,13.737,-90.479,13.901,-90.607,13.929,-91.146,13.926,-91.377,13.99,-91.641,14.115,-91.819,14.228,-92.235,14.545]]]},{"n":"Grenada","c":"GD","r":6,"ml":4,"lx":-61.68,"ly":12.11,"b":[-61.782,12.008,-61.607,12.237],"p":[[[-61.716,12.013,-61.782,12.008,-61.756,12.045,-61.75,12.108,-61.715,12.185,-61.66,12.237,-61.607,12.223,-61.627,12.054,-61.716,12.013]]]},{"n":"Greece","c":"GR","r":3,"ml":2.7,"lx":21.73,"ly":39.49,"b":[19.646,34.934,28.232,41.744],"p":[[[27.86,36.554,27.838,36.538,27.788,36.584,27.786,36.608,27.837,36.635,27.863,36.641,27.87,36.623,27.869,36.583,27.86,36.554]],[[20.612,38.383,20.625,38.268,20.695,38.246,20.789,38.142,20.781,38.089,20.761,38.071,20.606,38.12,20.569,38.098,20.524,38.107,20.496,38.164,20.499,38.184,20.473,38.219,20.452,38.234,20.391,38.188,20.353,38.18,20.352,38.222,20.409,38.337,20.435,38.356,20.481,38.318,20.52,38.332,20.548,38.395,20.55,38.457,20.563,38.475,20.612,38.383]],[[20.888,37.805,20.994,37.708,20.909,37.732,20.819,37.665,20.704,37.743,20.635,37.823,20.62,37.855,20.692,37.93,20.759,37.853,20.84,37.841,20.888,37.805]],[[20.687,38.609,20.648,38.601,20.614,38.606,20.584,38.602,20.555,38.583,20.558,38.662,20.592,38.76,20.635,38.818,20.694,38.844,20.72,38.799,20.715,38.638,20.687,38.609]],[[20.759,38.329,20.709,38.319,20.646,38.414,20.624,38.48,20.65,38.484,20.675,38.476,20.701,38.451,20.701,38.426,20.712,38.399,20.739,38.366,20.759,38.329]],[[20.078,39.433,20.1,39.377,19.975,39.411,19.884,39.462,19.809,39.585,19.649,39.726,19.646,39.767,19.707,39.798,19.839,39.82,19.892,39.797,19.926,39.774,19.937,39.747,19.862,39.693,19.847,39.668,19.904,39.619,19.903,39.6,19.927,39.506,19.955,39.47,20.028,39.442,20.078,39.433]],[[23.415,38.959,23.472,38.85,23.525,38.813,23.636,38.77,23.688,38.765,23.878,38.687,24.099,38.671,24.128,38.648,24.155,38.588,24.2,38.541,24.211,38.504,24.188,38.463,24.22,38.339,24.276,38.22,24.36,38.163,24.464,38.145,24.563,38.148,24.588,38.124,24.579,38.02,24.537,37.98,24.502,37.97,24.473,37.981,24.446,38.005,24.417,38.017,24.359,38.019,24.318,38.06,24.212,38.118,24.193,38.152,24.189,38.204,24.144,38.243,24.103,38.317,24.064,38.337,24.042,38.374,24.04,38.39,23.886,38.401,23.759,38.401,23.651,38.443,23.617,38.553,23.553,38.582,23.505,38.613,23.465,38.656,23.364,38.735,23.252,38.801,23.144,38.845,23.029,38.873,22.936,38.84,22.881,38.848,22.87,38.871,22.986,38.916,23.146,39.003,23.258,39.031,23.313,39.035,23.415,38.959]],[[23.78,39.114,23.735,39.081,23.666,39.095,23.594,39.209,23.78,39.114]],[[23.888,39.158,23.841,39.147,23.888,39.226,23.971,39.268,23.94,39.201,23.888,39.158]],[[24.675,38.809,24.569,38.785,24.541,38.789,24.565,38.819,24.566,38.832,24.461,38.889,24.473,38.962,24.486,38.98,24.564,38.942,24.581,38.879,24.675,38.809]],[[24.774,40.615,24.646,40.579,24.516,40.647,24.517,40.687,24.586,40.769,24.623,40.793,24.719,40.786,24.774,40.73,24.786,40.704,24.769,40.659,24.774,40.615]],[[23.551,37.926,23.511,37.901,23.467,37.902,23.435,37.911,23.419,37.931,23.439,37.941,23.462,37.98,23.484,37.991,23.516,37.986,23.535,37.97,23.551,37.926]],[[23.054,36.19,23.042,36.146,22.939,36.176,22.911,36.221,22.906,36.32,22.933,36.369,22.95,36.384,22.998,36.328,23.097,36.247,23.054,36.19]],[[27.176,35.465,27.138,35.409,27.099,35.456,27.116,35.511,27.071,35.598,27.156,35.726,27.158,35.789,27.223,35.82,27.207,35.714,27.157,35.629,27.209,35.559,27.234,35.479,27.176,35.465]],[[27.02,36.959,26.92,36.945,26.938,37.025,26.889,37.087,26.967,37.052,27.016,37.01,27.04,37.002,27.035,36.976,27.02,36.959]],[[26.95,36.727,26.918,36.726,26.956,36.774,27.061,36.84,27.215,36.899,27.266,36.905,27.352,36.869,27.193,36.809,27.151,36.778,27.034,36.771,26.95,36.727]],[[25.546,36.968,25.457,36.93,25.396,36.984,25.362,37.07,25.525,37.196,25.564,37.185,25.588,37.153,25.584,37.039,25.546,36.968]],[[25.279,37.068,25.199,36.991,25.133,37,25.105,37.035,25.146,37.107,25.235,37.149,25.275,37.138,25.271,37.084,25.279,37.068]],[[25.482,36.393,25.436,36.34,25.371,36.359,25.397,36.379,25.413,36.405,25.415,36.442,25.397,36.465,25.409,36.474,25.467,36.435,25.482,36.393]],[[25.382,36.674,25.364,36.658,25.289,36.722,25.26,36.758,25.296,36.789,25.407,36.717,25.382,36.674]],[[26.824,37.811,26.947,37.778,26.982,37.782,27.04,37.77,27.055,37.709,26.978,37.7,26.845,37.645,26.788,37.657,26.721,37.705,26.613,37.71,26.581,37.724,26.639,37.781,26.743,37.81,26.824,37.811]],[[26.029,37.529,25.982,37.526,25.997,37.566,26.086,37.635,26.212,37.638,26.326,37.673,26.351,37.674,26.297,37.62,26.205,37.569,26.029,37.529]],[[25.859,36.79,25.771,36.782,25.743,36.79,25.797,36.807,25.834,36.825,25.852,36.848,25.942,36.887,26.001,36.937,26.064,36.903,25.985,36.88,25.859,36.79]],[[26.461,36.585,26.382,36.562,26.331,36.511,26.27,36.547,26.27,36.595,26.337,36.581,26.384,36.608,26.37,36.639,26.421,36.624,26.461,36.585]],[[24.356,37.577,24.289,37.528,24.277,37.601,24.32,37.678,24.379,37.683,24.401,37.649,24.356,37.577]],[[24.436,37.344,24.379,37.314,24.398,37.383,24.37,37.42,24.395,37.45,24.431,37.475,24.449,37.45,24.481,37.408,24.436,37.344]],[[24.536,36.764,24.538,36.705,24.531,36.684,24.326,36.656,24.345,36.723,24.357,36.744,24.425,36.713,24.45,36.729,24.46,36.747,24.536,36.764]],[[24.943,37.494,24.938,37.39,24.912,37.391,24.896,37.407,24.895,37.446,24.907,37.509,24.943,37.494]],[[24.992,37.76,24.962,37.692,24.884,37.771,24.799,37.824,24.767,37.871,24.714,37.899,24.7,37.962,24.763,37.988,24.79,37.99,24.855,37.914,24.956,37.905,24.948,37.858,24.98,37.797,24.992,37.76]],[[25.256,37.6,25.219,37.535,25.156,37.545,25.052,37.614,25.016,37.646,24.996,37.677,25.039,37.681,25.092,37.648,25.225,37.631,25.256,37.6]],[[24.721,36.921,24.703,36.917,24.676,36.959,24.671,36.999,24.681,37.022,24.716,37.024,24.763,36.949,24.721,36.921]],[[26.094,38.218,25.999,38.162,25.892,38.243,25.874,38.27,25.953,38.303,25.991,38.354,25.96,38.416,25.851,38.508,25.846,38.574,26.013,38.602,26.11,38.545,26.16,38.541,26.141,38.486,26.15,38.468,26.157,38.303,26.111,38.28,26.103,38.234,26.094,38.218]],[[26.41,39.329,26.393,39.27,26.531,39.172,26.578,39.11,26.596,39.049,26.584,39.031,26.532,39.064,26.489,39.075,26.503,39.031,26.547,38.994,26.469,38.973,26.39,38.974,26.161,39.026,26.108,39.081,26.245,39.164,26.273,39.198,26.176,39.194,26.072,39.096,25.906,39.139,25.855,39.179,25.844,39.2,25.91,39.288,26.026,39.285,26.088,39.304,26.165,39.332,26.165,39.374,26.348,39.383,26.41,39.329]],[[25.686,40.427,25.573,40.4,25.448,40.483,25.569,40.516,25.624,40.492,25.664,40.464,25.686,40.427]],[[25.438,39.983,25.4,39.95,25.372,39.891,25.357,39.808,25.299,39.806,25.263,39.823,25.252,39.854,25.249,39.894,25.224,39.893,25.203,39.849,25.185,39.83,25.126,39.826,25.062,39.852,25.065,39.91,25.052,39.976,25.058,40,25.234,40.005,25.286,39.956,25.348,39.985,25.374,40.016,25.449,40.035,25.438,39.983]],[[25.403,37.419,25.307,37.413,25.313,37.489,25.348,37.509,25.463,37.471,25.457,37.447,25.403,37.419]],[[24.524,37.125,24.487,37.11,24.425,37.132,24.441,37.187,24.484,37.21,24.529,37.192,24.536,37.168,24.524,37.125]],[[27.843,35.929,27.771,35.908,27.746,35.911,27.716,35.957,27.757,36.069,27.719,36.141,27.716,36.172,27.774,36.214,27.815,36.277,27.914,36.345,28.171,36.426,28.232,36.434,28.23,36.37,28.144,36.21,28.068,36.13,28.088,36.065,27.966,36.048,27.843,35.929]],[[23.852,35.535,23.921,35.528,24.013,35.529,24.034,35.535,24.093,35.594,24.166,35.595,24.198,35.537,24.124,35.511,24.109,35.496,24.123,35.484,24.179,35.46,24.255,35.469,24.258,35.423,24.275,35.386,24.313,35.364,24.354,35.359,24.445,35.366,24.535,35.381,24.627,35.41,24.721,35.425,25.003,35.41,25.104,35.347,25.297,35.339,25.476,35.306,25.57,35.328,25.73,35.349,25.756,35.326,25.735,35.184,25.745,35.143,25.791,35.123,25.837,35.133,25.893,35.179,26.028,35.215,26.168,35.215,26.286,35.31,26.32,35.315,26.299,35.269,26.281,35.159,26.256,35.095,26.244,35.045,26.166,35.019,26.047,35.014,25.83,35.025,25.611,35.007,25.206,34.959,24.8,34.934,24.745,34.951,24.744,35.014,24.735,35.058,24.709,35.089,24.583,35.115,24.464,35.16,23.994,35.222,23.884,35.246,23.704,35.233,23.638,35.235,23.593,35.257,23.562,35.295,23.548,35.416,23.57,35.535,23.609,35.566,23.627,35.53,23.673,35.514,23.715,35.55,23.715,35.605,23.737,35.656,23.771,35.634,23.793,35.556,23.852,35.535]],[[26.321,41.717,26.411,41.696,26.463,41.663,26.495,41.633,26.545,41.607,26.581,41.601,26.61,41.512,26.625,41.402,26.602,41.354,26.536,41.343,26.331,41.239,26.326,41.143,26.328,41.097,26.333,41.064,26.354,41.037,26.354,40.997,26.331,40.954,26.241,40.883,26.179,40.827,26.109,40.75,26.07,40.74,26.039,40.727,26.011,40.769,25.856,40.844,25.497,40.888,25.325,40.943,25.25,40.933,25.104,40.995,25.005,40.968,24.793,40.858,24.679,40.869,24.557,40.936,24.477,40.948,24.384,40.913,24.234,40.786,24.082,40.724,23.946,40.748,23.763,40.748,23.743,40.677,23.779,40.628,23.879,40.544,23.832,40.482,23.867,40.419,23.932,40.406,24.031,40.409,24.213,40.328,24.292,40.242,24.343,40.148,24.232,40.215,24.159,40.28,24.056,40.304,23.913,40.359,23.823,40.368,23.728,40.33,23.721,40.286,23.823,40.205,23.918,40.155,23.967,40.115,24.001,40.025,23.982,39.994,23.947,39.966,23.835,40.022,23.665,40.224,23.426,40.264,23.386,40.222,23.433,40.115,23.467,40.074,23.674,39.959,23.658,39.934,23.627,39.924,23.396,39.99,23.328,40.09,23.312,40.216,23.098,40.304,22.896,40.4,22.851,40.491,22.893,40.524,22.922,40.591,22.811,40.579,22.742,40.536,22.629,40.496,22.625,40.429,22.643,40.367,22.605,40.276,22.569,40.119,22.592,40.037,22.836,39.801,22.919,39.629,22.979,39.564,23.103,39.492,23.233,39.358,23.288,39.289,23.328,39.175,23.218,39.104,23.155,39.101,23.119,39.133,23.169,39.21,23.162,39.258,22.993,39.331,22.921,39.306,22.839,39.259,22.886,39.17,22.939,39.112,22.966,39.031,23.067,39.038,22.93,38.948,22.803,38.902,22.677,38.899,22.597,38.891,22.569,38.867,22.634,38.851,22.688,38.849,22.774,38.8,23.02,38.742,23.138,38.668,23.253,38.661,23.369,38.526,23.57,38.489,23.684,38.352,23.836,38.325,23.967,38.275,24.005,38.227,24.025,38.14,24.033,37.955,24.061,37.818,24.062,37.775,24.055,37.71,24.02,37.678,23.972,37.677,23.877,37.778,23.733,37.884,23.58,38.011,23.537,38.033,23.502,38.035,23.42,37.992,23.194,37.959,23.087,37.913,23.047,37.903,23.036,37.878,23.086,37.853,23.147,37.795,23.147,37.716,23.198,37.62,23.263,37.595,23.348,37.598,23.396,37.58,23.409,37.542,23.458,37.497,23.491,37.464,23.489,37.44,23.253,37.377,23.203,37.349,23.162,37.334,23.1,37.364,23.096,37.441,23.015,37.482,22.941,37.517,22.851,37.532,22.775,37.585,22.725,37.542,22.765,37.393,22.851,37.291,22.995,37.016,23.06,36.854,23.074,36.775,23.041,36.645,23.112,36.548,23.16,36.448,23.107,36.452,23.061,36.487,22.983,36.528,22.832,36.687,22.78,36.786,22.717,36.794,22.608,36.78,22.489,36.568,22.489,36.447,22.428,36.476,22.375,36.514,22.381,36.646,22.376,36.702,22.231,36.883,22.165,36.903,22.134,36.964,22.08,37.029,22.012,37.017,21.956,36.99,21.94,36.892,21.934,36.804,21.892,36.737,21.738,36.863,21.583,37.081,21.579,37.2,21.692,37.309,21.679,37.387,21.571,37.541,21.416,37.64,21.329,37.669,21.288,37.775,21.205,37.829,21.138,37.854,21.125,37.892,21.145,37.919,21.308,38.027,21.404,38.197,21.451,38.205,21.549,38.165,21.658,38.175,21.748,38.274,21.825,38.328,21.953,38.321,22.244,38.189,22.556,38.113,22.712,38.047,22.8,37.981,22.846,37.968,22.92,37.958,22.917,38.007,22.893,38.051,22.955,38.075,23.122,38.073,23.153,38.096,23.183,38.134,23.149,38.176,23.094,38.196,23.034,38.202,22.995,38.216,22.933,38.202,22.834,38.235,22.784,38.262,22.754,38.29,22.583,38.345,22.422,38.439,22.385,38.386,22.32,38.357,22.227,38.353,21.965,38.412,21.805,38.367,21.717,38.355,21.65,38.354,21.568,38.334,21.473,38.321,21.39,38.408,21.355,38.475,21.331,38.487,21.33,38.424,21.303,38.374,21.183,38.346,21.113,38.385,21.06,38.503,20.992,38.654,20.873,38.776,20.777,38.808,20.769,38.874,20.777,38.928,20.893,38.941,21.074,38.885,21.112,38.896,21.152,38.922,21.145,38.979,21.118,39.03,21.069,39.032,21.034,39.026,20.923,39.037,20.78,39.009,20.713,39.035,20.691,39.067,20.572,39.148,20.468,39.255,20.301,39.327,20.191,39.546,20.099,39.641,20.001,39.709,20.023,39.711,20.06,39.699,20.131,39.662,20.207,39.654,20.248,39.678,20.272,39.701,20.288,39.739,20.294,39.782,20.306,39.797,20.364,39.792,20.382,39.803,20.382,39.842,20.344,39.891,20.311,39.951,20.311,39.979,20.338,39.991,20.384,40.017,20.408,40.049,20.456,40.066,20.527,40.069,20.606,40.083,20.657,40.117,20.665,40.152,20.697,40.246,20.718,40.293,20.752,40.335,20.77,40.392,20.806,40.445,20.882,40.468,20.95,40.494,21.002,40.563,21.031,40.622,21.031,40.659,20.988,40.718,20.956,40.775,20.964,40.85,21.1,40.856,21.148,40.863,21.324,40.867,21.404,40.907,21.46,40.904,21.576,40.869,21.628,40.896,21.779,40.95,21.929,41.107,21.993,41.131,22.139,41.141,22.184,41.159,22.238,41.155,22.401,41.123,22.494,41.119,22.604,41.14,22.725,41.179,22.755,41.313,22.784,41.332,22.859,41.337,22.916,41.336,23.026,41.326,23.156,41.322,23.24,41.385,23.372,41.39,23.433,41.399,23.536,41.386,23.635,41.387,23.762,41.413,23.881,41.456,23.974,41.452,24.011,41.46,24.033,41.469,24.056,41.527,24.23,41.531,24.289,41.525,24.387,41.524,24.488,41.555,24.518,41.553,24.569,41.467,24.596,41.443,24.651,41.42,24.774,41.356,24.796,41.373,24.847,41.394,24.994,41.365,25.133,41.316,25.251,41.244,25.382,41.264,25.527,41.3,25.621,41.31,25.724,41.315,25.785,41.33,25.923,41.312,26.066,41.351,26.135,41.386,26.155,41.435,26.144,41.522,26.111,41.608,26.077,41.64,26.066,41.673,26.086,41.704,26.107,41.726,26.201,41.744,26.321,41.717]]]},{"n":"Ghana","c":"GH","r":3,"ml":2.7,"lx":-1.04,"ly":7.72,"b":[-3.244,4.762,1.187,11.167],"p":[[[-0.069,11.116,-0.005,11.056,0.009,11.021,-0.014,10.891,-0.061,10.801,-0.09,10.716,-0.086,10.673,-0.058,10.631,0.039,10.564,0.089,10.521,0.148,10.455,0.216,10.391,0.332,10.307,0.381,10.292,0.379,10.269,0.363,10.236,0.352,9.925,0.343,9.845,0.335,9.804,0.324,9.688,0.312,9.671,0.29,9.672,0.27,9.668,0.265,9.645,0.273,9.621,0.343,9.604,0.327,9.587,0.275,9.571,0.252,9.536,0.262,9.496,0.233,9.464,0.242,9.442,0.26,9.426,0.289,9.432,0.371,9.486,0.405,9.491,0.448,9.48,0.526,9.398,0.529,9.358,0.497,9.221,0.466,9.115,0.46,8.974,0.493,8.895,0.489,8.851,0.453,8.814,0.373,8.759,0.379,8.722,0.415,8.653,0.483,8.575,0.616,8.48,0.686,8.355,0.688,8.304,0.647,8.253,0.599,8.21,0.584,8.146,0.605,7.728,0.5,7.547,0.499,7.495,0.51,7.435,0.537,7.399,0.591,7.389,0.635,7.354,0.62,7.227,0.596,7.097,0.592,7.034,0.579,7.004,0.538,6.98,0.523,6.939,0.533,6.888,0.526,6.851,0.548,6.802,0.596,6.742,0.673,6.593,0.702,6.581,0.715,6.549,0.707,6.519,0.737,6.453,0.822,6.386,0.912,6.329,0.985,6.32,1.002,6.269,1.05,6.203,1.084,6.174,1.14,6.155,1.185,6.145,1.187,6.089,1.106,6.051,1.05,5.994,1.008,5.906,0.95,5.81,0.749,5.76,0.672,5.76,0.26,5.757,-0.127,5.568,-0.349,5.501,-0.485,5.394,-0.669,5.319,-0.798,5.227,-1.064,5.183,-1.502,5.038,-1.638,4.981,-1.777,4.88,-2.002,4.762,-2.09,4.764,-2.266,4.874,-2.399,4.929,-2.723,5.014,-2.965,5.046,-3.082,5.082,-3.114,5.089,-3.087,5.128,-3.019,5.131,-2.948,5.119,-2.895,5.149,-2.816,5.153,-2.795,5.185,-2.789,5.264,-2.79,5.328,-2.762,5.357,-2.755,5.433,-2.794,5.6,-2.821,5.619,-2.962,5.643,-2.973,5.676,-2.998,5.711,-3.025,5.798,-3.056,5.926,-3.106,6.086,-3.201,6.348,-3.224,6.441,-3.24,6.536,-3.244,6.649,-3.224,6.691,-3.227,6.749,-3.236,6.807,-3.169,6.941,-3.038,7.105,-3.01,7.164,-2.986,7.205,-2.982,7.264,-2.959,7.455,-2.896,7.685,-2.857,7.772,-2.83,7.819,-2.798,7.896,-2.79,7.932,-2.669,8.022,-2.613,8.047,-2.601,8.082,-2.62,8.121,-2.612,8.148,-2.583,8.161,-2.538,8.172,-2.506,8.209,-2.557,8.493,-2.598,8.776,-2.6,8.8,-2.625,8.84,-2.649,8.957,-2.69,9.025,-2.747,9.045,-2.747,9.11,-2.689,9.219,-2.674,9.283,-2.702,9.302,-2.706,9.351,-2.686,9.432,-2.696,9.481,-2.706,9.534,-2.766,9.658,-2.781,9.746,-2.75,9.797,-2.751,9.91,-2.783,10.083,-2.788,10.193,-2.767,10.238,-2.777,10.282,-2.82,10.323,-2.823,10.363,-2.787,10.402,-2.791,10.432,-2.837,10.455,-2.878,10.508,-2.915,10.592,-2.907,10.728,-2.839,10.977,-2.83,10.998,-2.752,10.997,-2.752,10.986,-2.509,10.989,-2.232,10.991,-1.901,10.995,-1.6,10.998,-1.586,11.009,-1.537,11.023,-1.233,10.997,-1.042,11.01,-0.962,11.002,-0.903,10.985,-0.772,10.995,-0.701,10.989,-0.649,10.927,-0.627,10.927,-0.598,10.954,-0.545,10.984,-0.492,11.008,-0.454,11.056,-0.43,11.093,-0.396,11.086,-0.346,11.088,-0.313,11.119,-0.299,11.167,-0.069,11.116]]]},{"n":"Germany","c":"DE","r":2,"ml":1.7,"lx":9.68,"ly":50.96,"b":[5.858,47.279,15.017,55.059],"p":[[[9.524,47.524,9.35,47.599,9.183,47.671,9.128,47.671,8.881,47.656,8.874,47.663,8.831,47.704,8.793,47.717,8.77,47.71,8.755,47.698,8.728,47.7,8.618,47.766,8.573,47.776,8.51,47.767,8.436,47.731,8.403,47.688,8.413,47.663,8.452,47.652,8.552,47.659,8.567,47.652,8.571,47.638,8.559,47.624,8.478,47.613,8.454,47.596,8.43,47.592,8.415,47.59,8.328,47.607,8.198,47.607,8.094,47.576,7.927,47.564,7.698,47.57,7.616,47.593,7.565,47.607,7.529,47.674,7.539,47.774,7.593,47.906,7.608,48.003,7.584,48.064,7.617,48.157,7.706,48.28,7.765,48.41,7.795,48.547,7.838,48.636,7.923,48.699,8.124,48.873,8.14,48.886,8.135,48.974,8.081,48.986,8.001,49.011,7.799,49.042,7.611,49.062,7.525,49.086,7.451,49.152,7.404,49.153,7.313,49.13,7.2,49.114,7.117,49.128,7.066,49.125,7.037,49.113,7.022,49.123,7.001,49.18,6.958,49.195,6.891,49.208,6.85,49.202,6.821,49.174,6.776,49.154,6.735,49.161,6.608,49.291,6.575,49.32,6.566,49.346,6.534,49.395,6.458,49.443,6.382,49.458,6.344,49.453,6.348,49.513,6.378,49.6,6.407,49.645,6.445,49.682,6.485,49.708,6.494,49.754,6.487,49.798,6.441,49.805,6.325,49.838,6.256,49.872,6.205,49.915,6.138,49.974,6.11,50.034,6.108,50.094,6.117,50.121,6.121,50.139,6.175,50.233,6.364,50.316,6.344,50.4,6.341,50.452,6.295,50.485,6.203,50.499,6.179,50.523,6.168,50.545,6.236,50.597,6.154,50.637,6.119,50.679,6.006,50.732,5.994,50.75,6.048,50.905,6.007,50.95,5.955,50.973,5.895,50.984,5.867,51.006,5.858,51.03,5.868,51.045,5.939,51.041,5.961,51.057,6.13,51.147,6.137,51.165,6.113,51.175,6.082,51.18,6.075,51.199,6.076,51.224,6.166,51.355,6.193,51.411,6.199,51.45,6.193,51.489,6.142,51.55,6.091,51.599,6.089,51.638,6.053,51.658,5.949,51.762,5.949,51.803,6.008,51.834,6.09,51.854,6.117,51.87,6.167,51.881,6.297,51.851,6.356,51.825,6.372,51.83,6.425,51.858,6.518,51.854,6.742,51.911,6.775,51.938,6.8,51.967,6.802,51.98,6.716,52.036,6.713,52.057,6.725,52.08,6.749,52.099,6.8,52.111,6.855,52.136,6.977,52.206,7.02,52.266,7.033,52.331,7.035,52.38,7.002,52.419,6.968,52.444,6.922,52.44,6.833,52.442,6.749,52.464,6.703,52.499,6.692,52.53,6.712,52.55,6.719,52.574,6.705,52.598,6.711,52.618,6.748,52.634,7.013,52.634,7.033,52.651,7.051,52.745,7.117,52.887,7.179,52.966,7.19,53,7.189,53.187,7.197,53.282,7.152,53.327,7.053,53.376,7.074,53.478,7.107,53.557,7.206,53.655,7.285,53.681,7.629,53.697,8.009,53.691,8.167,53.543,8.108,53.468,8.201,53.432,8.245,53.445,8.279,53.511,8.302,53.584,8.334,53.606,8.451,53.552,8.493,53.514,8.495,53.394,8.538,53.557,8.506,53.671,8.528,53.781,8.576,53.838,8.619,53.875,8.898,53.836,9.206,53.856,9.322,53.813,9.585,53.6,9.673,53.566,9.784,53.555,9.631,53.6,9.312,53.859,9.216,53.891,9.07,53.901,8.978,53.926,8.92,53.965,8.904,54,8.907,54.261,8.852,54.3,8.78,54.313,8.736,54.295,8.645,54.295,8.626,54.354,8.648,54.398,8.831,54.428,8.952,54.468,8.957,54.538,8.881,54.594,8.79,54.696,8.682,54.792,8.67,54.903,8.671,54.903,8.857,54.901,8.903,54.897,9.186,54.845,9.255,54.808,9.342,54.806,9.499,54.84,9.616,54.855,9.661,54.834,9.725,54.826,9.74,54.826,9.746,54.807,9.892,54.781,9.954,54.738,10.022,54.674,10.029,54.581,9.941,54.515,9.869,54.472,10.143,54.488,10.171,54.45,10.212,54.409,10.36,54.438,10.732,54.316,10.956,54.376,11.013,54.379,11.064,54.281,11.009,54.181,10.811,54.075,10.855,54.01,10.918,53.995,11.104,54.009,11.4,53.945,11.461,53.965,11.701,54.114,11.796,54.145,12.111,54.168,12.169,54.226,12.296,54.284,12.379,54.347,12.575,54.467,12.779,54.446,12.898,54.423,13.029,54.411,13.147,54.283,13.448,54.141,13.724,54.153,13.822,54.019,13.866,53.853,13.95,53.801,14.025,53.767,14.25,53.732,14.259,53.73,14.266,53.707,14.28,53.625,14.299,53.556,14.415,53.283,14.412,53.217,14.411,53.199,14.369,53.106,14.293,53.027,14.194,52.982,14.139,52.933,14.129,52.878,14.254,52.783,14.514,52.646,14.619,52.529,14.57,52.431,14.555,52.36,14.574,52.314,14.616,52.278,14.68,52.25,14.705,52.207,14.692,52.15,14.705,52.11,14.753,52.082,14.748,52.071,14.725,52.031,14.693,51.958,14.675,51.905,14.602,51.832,14.624,51.771,14.681,51.698,14.725,51.662,14.739,51.627,14.711,51.545,14.725,51.524,14.906,51.463,14.936,51.435,14.953,51.377,15.017,51.253,14.964,51.095,14.917,51.009,14.814,50.872,14.809,50.859,14.797,50.842,14.767,50.818,14.723,50.815,14.658,50.833,14.614,50.856,14.624,50.915,14.595,50.919,14.56,50.955,14.546,50.994,14.507,51.01,14.367,51.026,14.32,51.038,14.283,51.029,14.256,51.002,14.273,50.977,14.299,50.953,14.377,50.914,14.369,50.899,14.202,50.861,14.096,50.823,13.998,50.801,13.899,50.761,13.701,50.717,13.557,50.705,13.527,50.693,13.473,50.617,13.436,50.601,13.401,50.609,13.375,50.622,13.341,50.611,13.306,50.586,13.27,50.576,13.238,50.577,13.181,50.51,13.016,50.49,12.997,50.456,12.967,50.416,12.943,50.406,12.868,50.422,12.765,50.431,12.706,50.409,12.636,50.397,12.549,50.393,12.453,50.35,12.359,50.273,12.306,50.206,12.277,50.181,12.231,50.245,12.175,50.288,12.135,50.311,12.099,50.311,12.09,50.302,12.09,50.269,12.128,50.213,12.175,50.176,12.183,50.148,12.208,50.098,12.276,50.042,12.384,49.999,12.458,49.956,12.512,49.896,12.513,49.877,12.498,49.853,12.472,49.83,12.45,49.8,12.391,49.74,12.408,49.713,12.457,49.68,12.5,49.64,12.556,49.575,12.632,49.461,12.681,49.415,12.748,49.366,12.813,49.329,12.917,49.33,13.024,49.26,13.141,49.158,13.228,49.112,13.289,49.097,13.339,49.061,13.384,49.008,13.401,48.978,13.441,48.956,13.548,48.96,13.685,48.877,13.77,48.816,13.815,48.767,13.803,48.748,13.797,48.686,13.799,48.622,13.785,48.587,13.724,48.542,13.692,48.533,13.675,48.523,13.487,48.582,13.472,48.572,13.46,48.565,13.409,48.394,13.375,48.361,13.323,48.331,13.215,48.302,13.14,48.29,13.082,48.275,12.897,48.204,12.814,48.161,12.76,48.107,12.76,48.076,12.85,47.985,12.954,47.891,12.954,47.808,12.908,47.746,12.898,47.722,12.928,47.713,12.986,47.709,13.034,47.699,13.054,47.655,13.048,47.579,13.032,47.508,13.014,47.478,12.968,47.476,12.879,47.506,12.809,47.542,12.783,47.564,12.781,47.59,12.796,47.607,12.771,47.639,12.686,47.669,12.594,47.656,12.527,47.636,12.483,47.637,12.436,47.666,12.363,47.688,12.268,47.703,12.209,47.718,12.197,47.709,12.204,47.647,12.186,47.62,11.717,47.583,11.574,47.55,11.47,47.506,11.393,47.487,11.374,47.46,11.298,47.425,11.212,47.414,11.191,47.425,11.136,47.409,11.042,47.393,10.981,47.398,10.952,47.427,10.894,47.47,10.871,47.501,10.873,47.52,10.742,47.524,10.659,47.547,10.483,47.542,10.439,47.552,10.43,47.541,10.404,47.417,10.369,47.366,10.313,47.313,10.241,47.284,10.183,47.279,10.186,47.317,10.2,47.363,10.159,47.374,10.096,47.38,10.066,47.393,10.074,47.429,10.06,47.449,10.034,47.474,9.972,47.505,9.839,47.552,9.749,47.576,9.715,47.551,9.651,47.526,9.549,47.534,9.524,47.524]],[[13.709,54.383,13.734,54.315,13.707,54.281,13.595,54.338,13.482,54.337,13.415,54.25,13.364,54.246,13.19,54.326,13.162,54.365,13.156,54.397,13.181,54.509,13.177,54.544,13.231,54.583,13.24,54.638,13.337,54.697,13.423,54.699,13.45,54.65,13.491,54.615,13.636,54.577,13.658,54.56,13.671,54.535,13.603,54.488,13.58,54.464,13.602,54.425,13.709,54.383]],[[14.211,53.95,14.198,53.919,14.214,53.871,14.172,53.874,14.048,53.863,13.926,53.879,13.902,53.939,13.922,53.997,13.872,54.036,13.827,54.06,13.82,54.093,13.828,54.127,14.039,54.035,14.211,53.95]],[[11.283,54.418,11.129,54.416,11.071,54.456,11.012,54.466,11.043,54.515,11.085,54.533,11.234,54.501,11.28,54.438,11.283,54.418]],[[8.308,54.787,8.285,54.767,8.296,54.908,8.405,55.059,8.451,55.055,8.404,55.015,8.39,54.986,8.371,54.929,8.38,54.9,8.63,54.892,8.601,54.865,8.347,54.848,8.308,54.787]],[[8.588,54.713,8.549,54.688,8.454,54.691,8.4,54.714,8.418,54.739,8.468,54.757,8.51,54.76,8.573,54.749,8.588,54.713]]]},{"n":"Georgia","c":"GE","r":5,"ml":4,"lx":43.74,"ly":41.87,"b":[39.978,41.07,46.673,43.57],"p":[[[43.439,41.107,43.442,41.126,43.433,41.156,43.402,41.177,43.359,41.19,43.279,41.185,43.205,41.199,43.153,41.236,43.141,41.265,43.171,41.288,43.149,41.307,43.057,41.353,42.907,41.467,42.822,41.492,42.788,41.564,42.754,41.579,42.682,41.586,42.607,41.579,42.59,41.571,42.567,41.559,42.508,41.47,42.466,41.44,42.364,41.454,42.28,41.475,42.211,41.487,42.078,41.494,41.926,41.496,41.824,41.432,41.779,41.441,41.702,41.472,41.577,41.497,41.51,41.517,41.702,41.705,41.759,41.817,41.761,41.885,41.763,41.97,41.663,42.147,41.578,42.398,41.489,42.659,41.419,42.738,41.129,42.828,41.062,42.931,40.837,43.063,40.524,43.121,40.462,43.146,40.191,43.312,39.978,43.42,40.024,43.485,40.085,43.553,40.15,43.57,40.342,43.543,40.519,43.512,40.648,43.534,40.802,43.48,40.942,43.418,41.083,43.374,41.358,43.333,41.461,43.276,41.581,43.219,42.05,43.19,42.088,43.199,42.122,43.207,42.28,43.228,42.419,43.224,42.566,43.155,42.66,43.159,42.761,43.17,42.89,43.133,42.992,43.092,43,43.05,43.089,42.989,43.348,42.897,43.558,42.844,43.623,42.808,43.783,42.747,43.799,42.728,43.795,42.703,43.75,42.658,43.738,42.617,43.76,42.594,43.826,42.572,43.957,42.567,44.005,42.596,44.103,42.616,44.2,42.654,44.329,42.704,44.506,42.749,44.576,42.748,44.644,42.735,44.692,42.71,44.771,42.617,44.85,42.747,44.871,42.756,44.943,42.73,45.072,42.694,45.16,42.675,45.208,42.648,45.344,42.53,45.563,42.536,45.656,42.518,45.705,42.498,45.728,42.475,45.688,42.357,45.634,42.235,45.639,42.205,45.727,42.159,45.846,42.11,45.91,42.071,45.954,42.035,46.048,42.009,46.16,41.992,46.213,41.99,46.268,41.96,46.412,41.905,46.43,41.891,46.405,41.855,46.348,41.79,46.303,41.757,46.252,41.752,46.202,41.737,46.184,41.702,46.182,41.657,46.191,41.625,46.204,41.613,46.255,41.602,46.305,41.508,46.385,41.46,46.509,41.406,46.619,41.344,46.673,41.287,46.662,41.246,46.626,41.16,46.534,41.089,46.458,41.07,46.431,41.077,46.381,41.099,46.28,41.154,46.171,41.198,46.087,41.184,46.031,41.167,45.922,41.187,45.793,41.224,45.725,41.262,45.696,41.289,45.716,41.338,45.422,41.425,45.281,41.45,45.217,41.423,45.001,41.291,44.976,41.277,44.811,41.259,44.811,41.249,44.849,41.22,44.841,41.211,44.565,41.208,44.473,41.191,44.227,41.213,44.146,41.203,44.077,41.183,43.909,41.159,43.793,41.131,43.645,41.117,43.492,41.116,43.439,41.107]]]},{"n":"The Gambia","c":"GM","r":6,"ml":5,"lx":-15,"ly":13.64,"b":[-16.825,13.064,-13.827,13.812],"p":[[[-16.562,13.587,-16.309,13.597,-16.002,13.593,-15.667,13.588,-15.51,13.586,-15.427,13.727,-15.27,13.789,-15.108,13.812,-15.024,13.806,-14.936,13.785,-14.766,13.669,-14.66,13.643,-14.571,13.616,-14.507,13.56,-14.405,13.504,-14.326,13.489,-14.278,13.497,-14.199,13.519,-14.147,13.536,-13.977,13.543,-13.853,13.479,-13.827,13.408,-13.848,13.335,-14.015,13.296,-14.247,13.236,-14.439,13.269,-14.672,13.352,-14.808,13.411,-14.865,13.435,-14.95,13.473,-15.025,13.513,-15.096,13.54,-15.151,13.556,-15.192,13.535,-15.212,13.485,-15.245,13.429,-15.286,13.396,-15.482,13.376,-15.657,13.356,-15.752,13.338,-15.814,13.325,-15.834,13.156,-16.033,13.158,-16.228,13.16,-16.431,13.157,-16.649,13.154,-16.705,13.12,-16.763,13.064,-16.769,13.148,-16.825,13.341,-16.75,13.425,-16.669,13.475,-16.615,13.435,-16.598,13.357,-16.556,13.303,-16.413,13.27,-16.272,13.294,-16.185,13.283,-16.188,13.326,-16.158,13.384,-15.986,13.409,-15.804,13.425,-15.618,13.46,-15.471,13.459,-15.427,13.468,-15.438,13.483,-15.57,13.5,-15.85,13.46,-16.135,13.448,-16.352,13.343,-16.441,13.353,-16.53,13.458,-16.562,13.587]]]},{"n":"Gabon","c":"GA","r":4,"ml":3,"lx":11.84,"ly":-0.44,"b":[8.703,-3.916,14.481,2.302],"p":[[[13.294,2.162,13.289,2.092,13.209,1.92,13.172,1.789,13.163,1.648,13.185,1.535,13.223,1.455,13.247,1.367,13.228,1.305,13.19,1.279,13.216,1.248,13.274,1.241,13.372,1.268,13.523,1.315,13.721,1.382,13.851,1.419,14.066,1.396,14.181,1.37,14.24,1.323,14.303,1.121,14.334,1.09,14.386,1.004,14.43,0.901,14.439,0.849,14.434,0.811,14.391,0.756,14.342,0.674,14.324,0.624,14.283,0.587,14.231,0.551,14.088,0.537,14.066,0.515,14.025,0.428,13.95,0.354,13.915,0.284,13.885,0.191,13.891,0.075,13.875,-0.091,13.86,-0.203,13.898,-0.243,14.069,-0.27,14.103,-0.292,14.148,-0.362,14.207,-0.427,14.364,-0.469,14.425,-0.519,14.474,-0.573,14.481,-0.618,14.445,-0.799,14.411,-0.972,14.424,-1.104,14.437,-1.23,14.456,-1.413,14.447,-1.525,14.403,-1.593,14.403,-1.647,14.423,-1.712,14.384,-1.89,14.359,-1.92,14.288,-1.954,14.251,-2.001,14.24,-2.077,14.202,-2.18,14.163,-2.218,14.163,-2.266,14.2,-2.301,14.2,-2.354,14.13,-2.418,14.087,-2.467,13.994,-2.491,13.887,-2.465,13.862,-2.43,13.888,-2.375,13.879,-2.33,13.842,-2.284,13.784,-2.164,13.734,-2.138,13.706,-2.187,13.619,-2.279,13.465,-2.395,13.357,-2.405,13.159,-2.369,12.992,-2.313,12.914,-2.176,12.864,-2.063,12.794,-1.932,12.714,-1.869,12.628,-1.83,12.59,-1.827,12.469,-1.9,12.432,-1.929,12.432,-1.99,12.444,-2.048,12.463,-2.075,12.479,-2.112,12.476,-2.169,12.454,-2.246,12.446,-2.33,12.064,-2.413,11.998,-2.383,11.95,-2.345,11.892,-2.351,11.727,-2.395,11.666,-2.365,11.605,-2.343,11.578,-2.361,11.575,-2.397,11.603,-2.595,11.595,-2.671,11.557,-2.77,11.538,-2.837,11.639,-2.855,11.676,-2.887,11.711,-2.937,11.76,-2.983,11.763,-3.011,11.708,-3.063,11.689,-3.127,11.715,-3.177,11.784,-3.229,11.885,-3.283,11.934,-3.319,11.929,-3.351,11.883,-3.42,11.865,-3.479,11.833,-3.531,11.839,-3.58,11.885,-3.625,11.88,-3.666,11.849,-3.697,11.786,-3.69,11.733,-3.695,11.686,-3.682,11.537,-3.525,11.504,-3.52,11.288,-3.641,11.234,-3.691,11.19,-3.762,11.13,-3.916,11.032,-3.826,10.947,-3.662,10.849,-3.561,10.641,-3.398,10.585,-3.278,10.348,-3.013,10.006,-2.748,9.759,-2.519,9.722,-2.468,9.764,-2.474,10.002,-2.588,10.034,-2.576,10.062,-2.55,9.959,-2.49,9.861,-2.443,9.769,-2.413,9.676,-2.416,9.625,-2.367,9.591,-2.293,9.574,-2.23,9.533,-2.164,9.402,-2.028,9.371,-1.975,9.299,-1.903,9.342,-1.894,9.483,-1.962,9.495,-1.935,9.483,-1.895,9.342,-1.829,9.266,-1.825,9.248,-1.779,9.258,-1.726,9.158,-1.528,9.053,-1.379,9.036,-1.309,9.319,-1.632,9.357,-1.638,9.406,-1.635,9.523,-1.598,9.501,-1.555,9.448,-1.509,9.397,-1.53,9.331,-1.535,9.296,-1.515,9.28,-1.482,9.347,-1.325,9.318,-1.333,9.297,-1.361,9.26,-1.374,9.204,-1.382,9.065,-1.298,8.942,-1.071,8.909,-1.025,8.877,-0.946,8.844,-0.914,8.703,-0.591,8.757,-0.615,8.821,-0.708,8.946,-0.689,8.995,-0.635,9.038,-0.637,9.082,-0.624,9.137,-0.573,9.297,-0.351,9.339,-0.058,9.325,0.116,9.302,0.289,9.355,0.344,9.376,0.307,9.386,0.246,9.411,0.2,9.468,0.16,9.574,0.149,9.738,0.085,9.797,0.044,9.813,0.126,10.001,0.195,9.944,0.22,9.777,0.192,9.546,0.296,9.47,0.362,9.399,0.487,9.325,0.552,9.33,0.611,9.495,0.665,9.539,0.659,9.557,0.594,9.601,0.568,9.618,0.577,9.625,0.632,9.626,0.779,9.575,0.991,9.591,1.032,9.636,1.047,9.676,1.075,9.705,1.08,9.761,1.075,9.789,1.026,9.804,0.999,9.86,0.986,9.907,0.96,9.947,0.967,9.98,0.998,10.029,1.004,10.179,1.004,10.315,1.003,10.587,1.002,10.859,1.001,11.131,1,11.335,1,11.335,1.121,11.334,1.308,11.332,1.528,11.331,1.74,11.33,1.936,11.329,2.167,11.34,2.234,11.353,2.261,11.348,2.3,11.559,2.302,11.94,2.285,12.106,2.288,12.153,2.284,12.361,2.296,12.53,2.281,12.601,2.265,12.666,2.257,12.867,2.247,13.131,2.259,13.22,2.256,13.27,2.224,13.294,2.162]]]},{"n":"France","c":"FR","r":2,"ml":1.7,"lx":2.55,"ly":46.7,"b":[-61.794,-21.369,55.839,51.097],"p":[[[9.48,42.805,9.454,42.659,9.473,42.616,9.509,42.586,9.526,42.553,9.556,42.161,9.551,42.13,9.428,41.972,9.401,41.926,9.395,41.731,9.374,41.679,9.331,41.627,9.253,41.46,9.186,41.385,9.003,41.477,8.895,41.516,8.842,41.559,8.808,41.588,8.83,41.628,8.879,41.669,8.887,41.701,8.771,41.737,8.718,41.761,8.719,41.804,8.759,41.87,8.74,41.925,8.674,41.922,8.622,41.931,8.615,41.959,8.653,41.996,8.703,42.043,8.701,42.096,8.642,42.118,8.588,42.161,8.566,42.219,8.608,42.258,8.675,42.284,8.626,42.343,8.592,42.345,8.566,42.358,8.588,42.385,8.64,42.427,8.713,42.55,8.815,42.608,8.995,42.645,9.044,42.662,9.088,42.705,9.138,42.733,9.198,42.729,9.254,42.712,9.288,42.695,9.313,42.713,9.338,42.767,9.323,42.814,9.331,42.944,9.363,43.017,9.415,43.021,9.463,42.981,9.461,42.945,9.479,42.86,9.48,42.805]],[[7.616,47.593,7.495,47.547,7.467,47.508,7.42,47.455,7.343,47.433,7.266,47.426,7.203,47.433,7.167,47.454,7.169,47.473,7.136,47.49,7.053,47.489,6.968,47.453,6.9,47.394,6.921,47.361,6.984,47.353,7.001,47.339,7.001,47.323,6.979,47.302,6.952,47.267,6.821,47.163,6.688,47.058,6.667,47.027,6.625,47.004,6.456,46.948,6.439,46.926,6.429,46.832,6.41,46.755,6.285,46.683,6.161,46.611,6.13,46.567,6.107,46.516,6.068,46.459,6.06,46.428,6.123,46.379,6.116,46.338,6.096,46.279,6.036,46.238,5.97,46.215,5.971,46.151,6.007,46.142,6.087,46.147,6.199,46.193,6.273,46.252,6.23,46.308,6.224,46.319,6.235,46.333,6.322,46.394,6.429,46.431,6.578,46.437,6.758,46.416,6.776,46.407,6.767,46.369,6.784,46.314,6.817,46.275,6.772,46.165,6.806,46.131,6.858,46.089,6.897,46.052,6.954,46.017,7.004,45.959,7.021,45.926,6.941,45.868,6.804,45.815,6.789,45.78,6.791,45.741,6.806,45.71,6.881,45.67,6.962,45.581,7.014,45.5,7.126,45.424,7.153,45.401,7.146,45.382,7.117,45.349,7.078,45.24,7.032,45.223,6.981,45.216,6.842,45.136,6.78,45.145,6.692,45.144,6.628,45.118,6.635,45.068,6.691,45.023,6.725,44.973,6.738,44.921,6.801,44.883,6.889,44.86,6.94,44.859,6.973,44.845,6.993,44.827,7.031,44.717,7.008,44.689,6.96,44.677,6.932,44.632,6.875,44.565,6.843,44.511,6.879,44.463,6.894,44.428,6.875,44.392,6.9,44.336,6.967,44.28,7.149,44.202,7.319,44.138,7.371,44.127,7.599,44.168,7.637,44.165,7.665,44.116,7.677,44.083,7.651,44.034,7.59,43.965,7.523,43.911,7.482,43.865,7.491,43.823,7.493,43.767,7.439,43.75,7.437,43.761,7.414,43.771,7.395,43.765,7.38,43.753,7.378,43.732,7.262,43.696,7.181,43.659,6.865,43.438,6.717,43.374,6.687,43.335,6.657,43.262,6.57,43.199,6.494,43.169,6.305,43.139,6.116,43.072,6.031,43.101,5.809,43.098,5.672,43.178,5.407,43.229,5.32,43.345,5.2,43.352,5.12,43.349,5.073,43.367,5.061,43.406,5.06,43.445,4.976,43.427,4.912,43.427,4.874,43.412,4.844,43.394,4.808,43.405,4.787,43.401,4.789,43.379,4.712,43.373,4.629,43.387,4.41,43.447,4.376,43.456,4.224,43.48,4.163,43.504,4.113,43.563,4.075,43.582,4.053,43.593,3.911,43.563,3.862,43.516,3.785,43.462,3.259,43.193,3.163,43.081,3.052,42.915,3.043,42.838,3.091,42.591,3.198,42.461,3.211,42.431,3.152,42.431,3.053,42.447,2.97,42.467,2.891,42.456,2.816,42.429,2.749,42.413,2.702,42.408,2.67,42.393,2.655,42.362,2.652,42.34,2.568,42.346,2.374,42.39,2.2,42.421,2.098,42.386,2.033,42.354,1.987,42.358,1.951,42.393,1.928,42.426,1.86,42.457,1.706,42.503,1.714,42.526,1.74,42.557,1.739,42.576,1.71,42.604,1.568,42.635,1.501,42.643,1.459,42.622,1.428,42.596,1.349,42.691,1.293,42.71,1.208,42.713,1.111,42.742,1.01,42.779,0.764,42.838,0.697,42.845,0.67,42.836,0.652,42.8,0.642,42.701,0.632,42.69,0.518,42.686,0.377,42.7,0.313,42.693,0.255,42.693,0.201,42.719,-0.041,42.689,-0.081,42.704,-0.14,42.749,-0.205,42.785,-0.256,42.804,-0.299,42.825,-0.339,42.829,-0.398,42.808,-0.481,42.799,-0.55,42.802,-0.586,42.799,-0.74,42.91,-0.763,42.94,-0.839,42.948,-0.934,42.95,-1.175,43.021,-1.285,43.06,-1.302,43.082,-1.3,43.101,-1.319,43.097,-1.353,43.064,-1.371,43.038,-1.394,43.033,-1.429,43.037,-1.461,43.052,-1.48,43.071,-1.459,43.105,-1.423,43.149,-1.407,43.197,-1.411,43.24,-1.472,43.268,-1.561,43.279,-1.627,43.282,-1.713,43.307,-1.753,43.325,-1.793,43.373,-1.794,43.407,-1.631,43.438,-1.485,43.564,-1.346,44.02,-1.246,44.56,-1.171,44.662,-1.077,44.69,-1.153,44.764,-1.2,44.726,-1.22,44.687,-1.245,44.667,-1.189,45.161,-1.149,45.343,-1.081,45.532,-0.942,45.457,-0.826,45.381,-0.767,45.314,-0.691,45.093,-0.634,45.047,-0.548,45.001,-0.582,45.051,-0.641,45.09,-0.733,45.385,-0.791,45.468,-0.881,45.538,-1.17,45.686,-1.196,45.714,-1.21,45.771,-1.114,45.769,-1.032,45.741,-1.042,45.773,-1.066,45.806,-1.104,45.925,-1.136,46.205,-1.132,46.253,-1.146,46.311,-1.239,46.325,-1.313,46.327,-1.392,46.35,-1.787,46.515,-1.921,46.685,-2.059,46.81,-2.092,46.865,-2.09,46.921,-2.019,47.038,-2.082,47.112,-2.144,47.126,-2.197,47.163,-2.149,47.224,-2.108,47.263,-2.028,47.274,-1.922,47.261,-1.821,47.225,-1.743,47.216,-1.975,47.311,-2.353,47.279,-2.434,47.291,-2.503,47.312,-2.53,47.382,-2.476,47.413,-2.428,47.471,-2.483,47.512,-2.554,47.527,-2.666,47.526,-2.77,47.514,-2.797,47.537,-2.733,47.602,-2.787,47.626,-2.859,47.614,-2.964,47.601,-3.064,47.621,-3.159,47.695,-3.222,47.694,-3.265,47.685,-3.329,47.713,-3.396,47.72,-3.444,47.711,-3.508,47.753,-3.901,47.838,-4.071,47.848,-4.226,47.81,-4.312,47.823,-4.375,47.877,-4.428,47.969,-4.679,48.04,-4.629,48.086,-4.512,48.097,-4.378,48.129,-4.329,48.17,-4.435,48.218,-4.512,48.23,-4.544,48.247,-4.577,48.29,-4.531,48.31,-4.498,48.299,-4.403,48.293,-4.241,48.304,-4.302,48.347,-4.364,48.357,-4.393,48.368,-4.525,48.372,-4.585,48.358,-4.719,48.363,-4.749,48.41,-4.762,48.45,-4.721,48.54,-4.531,48.62,-4.059,48.708,-3.856,48.695,-3.715,48.71,-3.546,48.766,-3.471,48.813,-3.231,48.841,-3.003,48.791,-2.793,48.601,-2.692,48.537,-2.446,48.648,-2.079,48.645,-2.004,48.582,-1.973,48.635,-1.906,48.697,-1.852,48.669,-1.825,48.631,-1.438,48.641,-1.376,48.653,-1.48,48.698,-1.565,48.806,-1.583,49.202,-1.69,49.313,-1.813,49.49,-1.87,49.595,-1.875,49.631,-1.856,49.684,-1.705,49.681,-1.588,49.668,-1.366,49.707,-1.259,49.68,-1.265,49.598,-1.232,49.495,-1.195,49.445,-1.139,49.388,-0.959,49.393,-0.766,49.36,-0.521,49.355,-0.163,49.297,-0.011,49.33,0.136,49.402,0.417,49.448,0.439,49.473,0.278,49.463,0.129,49.508,0.109,49.558,0.127,49.602,0.187,49.703,0.616,49.863,0.924,49.91,1.246,49.998,1.407,50.089,1.514,50.205,1.548,50.231,1.593,50.252,1.552,50.294,1.579,50.739,1.61,50.819,1.672,50.885,1.768,50.936,1.913,50.991,2.446,51.067,2.525,51.097,2.536,51.05,2.575,50.989,2.601,50.955,2.579,50.912,2.597,50.876,2.669,50.811,2.759,50.751,2.84,50.712,2.862,50.716,2.922,50.727,3.023,50.767,3.107,50.779,3.155,50.749,3.182,50.732,3.235,50.663,3.25,50.591,3.273,50.532,3.316,50.507,3.477,50.499,3.595,50.477,3.627,50.457,3.667,50.325,3.689,50.306,3.719,50.322,3.748,50.344,3.789,50.347,3.858,50.339,3.95,50.336,4.044,50.321,4.175,50.246,4.17,50.222,4.144,50.178,4.135,50.144,4.158,50.13,4.192,50.094,4.184,50.053,4.15,50.024,4.137,50,4.137,49.984,4.149,49.972,4.176,49.96,4.369,49.945,4.545,49.96,4.656,50.002,4.675,50.047,4.707,50.097,4.773,50.139,4.819,50.153,4.861,50.136,4.79,49.96,4.842,49.915,4.849,49.847,4.868,49.788,4.931,49.789,5.007,49.778,5.061,49.757,5.124,49.721,5.215,49.689,5.279,49.678,5.302,49.651,5.354,49.62,5.435,49.554,5.507,49.511,5.542,49.511,5.61,49.528,5.71,49.539,5.79,49.538,5.823,49.505,5.901,49.49,5.929,49.478,5.959,49.455,6.011,49.445,6.074,49.455,6.12,49.485,6.181,49.499,6.242,49.494,6.277,49.478,6.344,49.453,6.382,49.458,6.458,49.443,6.534,49.395,6.566,49.346,6.575,49.32,6.608,49.291,6.735,49.161,6.776,49.154,6.821,49.174,6.85,49.202,6.891,49.208,6.958,49.195,7.001,49.18,7.022,49.123,7.037,49.113,7.066,49.125,7.117,49.128,7.2,49.114,7.313,49.13,7.404,49.153,7.451,49.152,7.525,49.086,7.611,49.062,7.799,49.042,8.001,49.011,8.081,48.986,8.135,48.974,8.14,48.886,8.124,48.873,7.923,48.699,7.838,48.636,7.795,48.547,7.765,48.41,7.706,48.28,7.617,48.157,7.584,48.064,7.608,48.003,7.593,47.906,7.539,47.774,7.529,47.674,7.565,47.607,7.616,47.593]],[[-1.178,45.904,-1.214,45.817,-1.28,45.897,-1.369,45.968,-1.389,46.033,-1.389,46.05,-1.285,46.003,-1.178,45.904]],[[45.18,-12.977,45.118,-12.985,45.088,-12.958,45.069,-12.896,45.088,-12.835,45.094,-12.786,45.043,-12.701,45.092,-12.653,45.135,-12.709,45.159,-12.713,45.223,-12.752,45.204,-12.824,45.209,-12.848,45.179,-12.92,45.18,-12.977]],[[55.797,-21.339,55.656,-21.369,55.558,-21.358,55.363,-21.274,55.31,-21.217,55.233,-21.058,55.25,-21.002,55.311,-20.904,55.45,-20.865,55.596,-20.88,55.662,-20.906,55.739,-21.021,55.839,-21.139,55.822,-21.278,55.797,-21.339]],[[-60.826,14.494,-60.837,14.437,-60.862,14.426,-60.899,14.474,-61.064,14.467,-61.089,14.51,-61.09,14.53,-61.011,14.602,-61.104,14.621,-61.141,14.652,-61.22,14.804,-61.213,14.849,-61.181,14.872,-61.127,14.875,-61.027,14.826,-60.953,14.756,-60.927,14.755,-60.919,14.735,-60.934,14.686,-60.889,14.645,-60.87,14.614,-60.826,14.494]],[[-61.327,16.23,-61.445,16.219,-61.522,16.228,-61.54,16.3,-61.501,16.36,-61.529,16.434,-61.511,16.478,-61.471,16.507,-61.406,16.468,-61.396,16.413,-61.355,16.363,-61.173,16.256,-61.327,16.23]],[[-61.59,16.007,-61.67,15.962,-61.71,15.976,-61.759,16.062,-61.794,16.301,-61.767,16.34,-61.748,16.355,-61.642,16.326,-61.597,16.292,-61.552,16.271,-61.575,16.227,-61.564,16.048,-61.59,16.007]],[[-61.23,15.89,-61.286,15.886,-61.311,15.895,-61.318,15.955,-61.275,15.996,-61.25,16.006,-61.212,15.96,-61.203,15.921,-61.23,15.89]],[[-54.616,2.327,-54.605,2.336,-54.568,2.343,-54.536,2.343,-54.486,2.416,-54.402,2.462,-54.257,2.714,-54.196,2.818,-54.188,2.875,-54.171,2.994,-54.203,3.138,-54.188,3.179,-54.063,3.353,-54.01,3.449,-54.006,3.531,-53.99,3.59,-54.006,3.62,-54.034,3.629,-54.082,3.706,-54.113,3.769,-54.197,3.834,-54.256,3.901,-54.351,4.054,-54.342,4.14,-54.369,4.171,-54.398,4.202,-54.396,4.241,-54.416,4.338,-54.441,4.428,-54.45,4.485,-54.426,4.583,-54.44,4.692,-54.471,4.749,-54.48,4.837,-54.473,4.915,-54.447,4.959,-54.452,5.013,-54.332,5.187,-54.24,5.288,-54.156,5.359,-54.085,5.412,-53.99,5.676,-53.92,5.769,-53.847,5.782,-53.454,5.563,-53.27,5.543,-52.899,5.425,-52.765,5.273,-52.454,5.021,-52.291,4.942,-52.289,4.876,-52.325,4.771,-52.22,4.863,-52.058,4.717,-52.012,4.646,-51.962,4.514,-51.979,4.43,-52.002,4.386,-52.003,4.352,-51.955,4.399,-51.928,4.436,-51.92,4.524,-51.88,4.634,-51.828,4.636,-51.786,4.571,-51.699,4.287,-51.666,4.229,-51.653,4.139,-51.658,4.098,-51.653,4.061,-51.683,4.04,-51.767,3.993,-51.805,3.93,-51.827,3.87,-51.879,3.829,-51.929,3.777,-51.944,3.735,-51.991,3.702,-52,3.647,-52.116,3.452,-52.163,3.365,-52.229,3.272,-52.271,3.237,-52.328,3.182,-52.357,3.118,-52.357,3.052,-52.396,2.972,-52.418,2.904,-52.456,2.864,-52.555,2.648,-52.559,2.573,-52.583,2.529,-52.653,2.426,-52.701,2.364,-52.783,2.317,-52.87,2.267,-52.903,2.212,-52.965,2.184,-53.01,2.182,-53.082,2.202,-53.18,2.211,-53.23,2.205,-53.252,2.232,-53.285,2.295,-53.334,2.34,-53.366,2.324,-53.432,2.279,-53.509,2.253,-53.564,2.262,-53.684,2.293,-53.735,2.309,-53.75,2.335,-53.768,2.355,-53.794,2.346,-53.83,2.313,-53.877,2.278,-53.946,2.233,-54.09,2.15,-54.13,2.121,-54.167,2.137,-54.228,2.153,-54.293,2.154,-54.433,2.208,-54.515,2.245,-54.55,2.293,-54.592,2.314,-54.616,2.327]]]},{"n":"Saint Pierre and Miquelon","c":"PM","r":4,"ml":5,"lx":-56.33,"ly":47.04,"b":[-56.387,46.753,-56.137,47.099],"p":[[[-56.151,46.762,-56.172,46.753,-56.243,46.767,-56.209,46.798,-56.185,46.807,-56.153,46.811,-56.137,46.802,-56.139,46.779,-56.151,46.762]],[[-56.267,46.838,-56.354,46.795,-56.385,46.819,-56.377,46.848,-56.333,46.916,-56.334,46.936,-56.387,47.068,-56.378,47.09,-56.365,47.099,-56.287,47.071,-56.278,47.035,-56.315,46.954,-56.29,46.9,-56.255,46.861,-56.267,46.838]]]},{"n":"Wallis and Futuna","c":"WF","r":4,"ml":4.7,"lx":-178.14,"ly":-14.29,"b":[-178.194,-14.325,-176.128,-13.222],"p":[[[-176.161,-13.333,-176.177,-13.341,-176.195,-13.302,-176.171,-13.243,-176.148,-13.222,-176.128,-13.268,-176.161,-13.333]],[[-178.047,-14.318,-178.103,-14.325,-178.159,-14.312,-178.194,-14.255,-178.178,-14.232,-178.142,-14.243,-178.105,-14.284,-178.044,-14.303,-178.047,-14.318]]]},{"n":"Saint Martin","c":"MF","r":6,"ml":5,"lx":-63.05,"ly":18.08,"b":[-63.123,18.069,-63.009,18.115],"p":[[[-63.011,18.069,-63.123,18.069,-63.115,18.091,-63.063,18.115,-63.025,18.113,-63.009,18.104,-63.011,18.069]]]},{"n":"Saint Barthélemy","c":"BL","r":6,"ml":5.7,"lx":-62.83,"ly":17.9,"b":[-62.875,17.875,-62.8,17.922],"p":[[[-62.832,17.876,-62.847,17.875,-62.859,17.884,-62.869,17.899,-62.875,17.914,-62.874,17.922,-62.865,17.918,-62.8,17.909,-62.807,17.898,-62.818,17.885,-62.832,17.876]]]},{"n":"French Polynesia","c":"PF","r":4,"ml":3.5,"lx":-149.46,"ly":-17.63,"b":[-151.512,-20.876,-136.294,-8.782],"p":[[[-151.467,-16.658,-151.485,-16.665,-151.504,-16.647,-151.512,-16.619,-151.506,-16.574,-151.457,-16.604,-151.438,-16.623,-151.467,-16.658]],[[-149.814,-17.545,-149.845,-17.571,-149.887,-17.553,-149.905,-17.528,-149.912,-17.501,-149.902,-17.47,-149.809,-17.474,-149.782,-17.488,-149.814,-17.545]],[[-151.41,-16.878,-151.449,-16.879,-151.485,-16.864,-151.476,-16.761,-151.467,-16.74,-151.411,-16.774,-151.365,-16.864,-151.41,-16.878]],[[-149.322,-17.69,-149.178,-17.737,-149.151,-17.812,-149.182,-17.862,-149.254,-17.85,-149.29,-17.822,-149.341,-17.732,-149.482,-17.753,-149.579,-17.735,-149.633,-17.618,-149.635,-17.564,-149.611,-17.532,-149.508,-17.496,-149.379,-17.522,-149.33,-17.589,-149.322,-17.69]],[[-139.024,-9.695,-138.874,-9.747,-138.827,-9.742,-138.875,-9.793,-139.024,-9.821,-139.074,-9.846,-139.134,-9.829,-139.166,-9.77,-139.024,-9.695]],[[-139.06,-9.931,-139.134,-10.01,-139.134,-9.926,-139.107,-9.915,-139.083,-9.915,-139.06,-9.931]],[[-138.651,-10.515,-138.688,-10.532,-138.69,-10.426,-138.643,-10.446,-138.624,-10.463,-138.632,-10.492,-138.651,-10.515]],[[-140.073,-8.91,-140.171,-8.934,-140.217,-8.93,-140.253,-8.848,-140.24,-8.798,-140.224,-8.782,-140.058,-8.801,-140.044,-8.838,-140.046,-8.874,-140.073,-8.91]],[[-140.076,-9.426,-140.097,-9.444,-140.138,-9.384,-140.144,-9.359,-140.071,-9.328,-140.031,-9.345,-140.076,-9.426]],[[-140.809,-17.857,-140.804,-17.876,-140.841,-17.873,-140.852,-17.867,-140.851,-17.831,-140.824,-17.788,-140.804,-17.752,-140.761,-17.718,-140.686,-17.684,-140.65,-17.67,-140.638,-17.678,-140.652,-17.683,-140.776,-17.754,-140.815,-17.804,-140.833,-17.838,-140.829,-17.849,-140.809,-17.857]],[[-139.556,-8.94,-139.621,-8.948,-139.632,-8.899,-139.612,-8.872,-139.584,-8.86,-139.535,-8.875,-139.508,-8.897,-139.51,-8.916,-139.556,-8.94]],[[-140.685,-18.38,-140.672,-18.416,-140.696,-18.399,-140.773,-18.364,-140.782,-18.334,-140.685,-18.38]],[[-140.83,-18.189,-140.823,-18.217,-140.86,-18.199,-140.895,-18.148,-140.959,-18.085,-140.974,-18.059,-140.925,-18.084,-140.893,-18.121,-140.83,-18.189]],[[-136.294,-18.544,-136.314,-18.566,-136.316,-18.545,-136.344,-18.535,-136.383,-18.514,-136.436,-18.489,-136.464,-18.485,-136.479,-18.471,-136.459,-18.463,-136.426,-18.474,-136.38,-18.497,-136.328,-18.519,-136.294,-18.544]],[[-136.972,-18.342,-136.971,-18.361,-137.068,-18.265,-137.03,-18.273,-136.972,-18.342]],[[-138.506,-20.857,-138.535,-20.876,-138.524,-20.851,-138.546,-20.795,-138.568,-20.787,-138.546,-20.771,-138.515,-20.813,-138.506,-20.857]],[[-142.512,-16.096,-142.53,-16.107,-142.507,-16.028,-142.481,-16.018,-142.512,-16.096]],[[-143.441,-16.62,-143.386,-16.669,-143.459,-16.635,-143.551,-16.621,-143.516,-16.612,-143.465,-16.614,-143.441,-16.62]],[[-143.571,-16.635,-143.611,-16.64,-143.707,-16.581,-143.67,-16.581,-143.615,-16.618,-143.571,-16.635]],[[-145.051,-15.856,-145.058,-15.901,-145.076,-15.858,-145.138,-15.788,-145.161,-15.757,-145.134,-15.762,-145.051,-15.856]],[[-145.487,-16.33,-145.482,-16.347,-145.503,-16.346,-145.54,-16.295,-145.553,-16.251,-145.577,-16.201,-145.609,-16.165,-145.613,-16.132,-145.614,-16.079,-145.577,-16.16,-145.542,-16.225,-145.517,-16.278,-145.487,-16.33]]]},{"n":"New Caledonia","c":"NC","r":3,"ml":4.6,"lx":165.08,"ly":-21.06,"b":[159.928,-22.661,168.139,-19.115],"p":[[[164.202,-20.246,164.315,-20.309,164.436,-20.282,164.588,-20.381,164.976,-20.681,165.112,-20.745,165.192,-20.769,165.252,-20.818,165.307,-20.887,165.381,-20.936,165.413,-20.981,165.421,-21.043,165.447,-21.081,165.582,-21.18,165.663,-21.267,165.775,-21.312,165.823,-21.364,165.885,-21.389,165.95,-21.442,166.058,-21.484,166.303,-21.637,166.493,-21.783,166.588,-21.873,166.69,-21.953,166.82,-22.017,166.942,-22.09,167.004,-22.262,166.97,-22.323,166.9,-22.353,166.835,-22.355,166.774,-22.376,166.571,-22.266,166.522,-22.249,166.468,-22.256,166.438,-22.232,166.416,-22.196,166.292,-22.155,166.177,-22.089,166.143,-22.044,166.124,-21.989,166.096,-21.957,165.933,-21.908,165.823,-21.854,165.744,-21.777,165.62,-21.724,165.428,-21.615,165.329,-21.58,165.242,-21.525,165.01,-21.327,164.927,-21.29,164.855,-21.202,164.656,-20.992,164.559,-20.906,164.455,-20.829,164.375,-20.739,164.313,-20.633,164.17,-20.48,164.152,-20.415,164.158,-20.348,164.124,-20.305,164.065,-20.279,164.037,-20.234,164.041,-20.173,164.06,-20.142,164.202,-20.246]],[[167.544,-22.623,167.513,-22.661,167.473,-22.653,167.444,-22.639,167.422,-22.619,167.443,-22.541,167.529,-22.579,167.544,-22.623]],[[159.952,-19.312,159.936,-19.333,159.928,-19.174,159.96,-19.115,159.975,-19.238,159.952,-19.312]],[[168.011,-21.43,168.058,-21.448,168.139,-21.445,168.121,-21.616,168.006,-21.643,167.967,-21.642,167.941,-21.606,167.876,-21.582,167.879,-21.524,167.815,-21.393,167.926,-21.373,167.988,-21.338,167.985,-21.37,167.995,-21.407,168.011,-21.43]],[[166.547,-20.699,166.494,-20.709,166.558,-20.617,166.56,-20.561,166.585,-20.45,166.583,-20.413,166.625,-20.418,166.671,-20.45,166.618,-20.478,166.6,-20.525,166.602,-20.585,166.623,-20.596,166.589,-20.662,166.547,-20.699]],[[167.401,-21.161,167.346,-21.169,167.273,-21.097,167.134,-21.061,167.073,-20.997,167.033,-20.923,167.112,-20.904,167.189,-20.804,167.136,-20.766,167.045,-20.759,167.056,-20.72,167.204,-20.674,167.269,-20.701,167.298,-20.733,167.293,-20.892,167.361,-20.942,167.431,-21.055,167.43,-21.087,167.401,-21.161]]]},{"n":"French Southern and Antarctic Lands","c":"TF","r":6,"ml":4,"lx":69.12,"ly":-49.3,"b":[51.659,-49.71,70.555,-46.327],"p":[[[69.185,-49.11,69.265,-49.115,69.314,-49.106,69.535,-48.974,69.593,-48.971,69.587,-49.072,69.644,-49.117,69.572,-49.129,69.436,-49.124,69.405,-49.182,69.542,-49.256,69.611,-49.266,69.667,-49.265,69.771,-49.248,69.854,-49.222,69.984,-49.16,70.061,-49.136,70.208,-49.135,70.285,-49.076,70.32,-49.059,70.406,-49.061,70.484,-49.084,70.531,-49.137,70.555,-49.201,70.537,-49.266,70.485,-49.328,70.39,-49.366,70.411,-49.411,70.386,-49.434,70.338,-49.435,70.298,-49.425,70.238,-49.372,70.166,-49.343,69.993,-49.345,69.916,-49.349,69.902,-49.389,69.861,-49.421,69.818,-49.438,69.76,-49.43,69.749,-49.448,69.78,-49.49,69.856,-49.544,69.986,-49.582,70.063,-49.589,70.073,-49.518,70.166,-49.509,70.248,-49.531,70.307,-49.583,70.259,-49.601,70.216,-49.629,70.207,-49.665,70.124,-49.704,70.075,-49.709,69.919,-49.689,69.826,-49.645,69.804,-49.614,69.747,-49.602,69.682,-49.642,69.613,-49.651,69.478,-49.617,69.353,-49.563,69.275,-49.543,69.153,-49.53,69.086,-49.653,68.993,-49.705,68.873,-49.71,68.815,-49.7,68.783,-49.651,68.791,-49.6,68.811,-49.55,68.848,-49.5,68.872,-49.444,68.862,-49.392,68.818,-49.354,68.841,-49.285,68.799,-49.232,68.814,-49.192,68.883,-49.165,68.854,-49.141,68.817,-49.135,68.79,-49.104,68.77,-49.066,68.797,-48.995,68.837,-48.926,68.832,-48.849,68.9,-48.776,68.959,-48.694,69.002,-48.661,69.057,-48.656,69.081,-48.679,69.093,-48.724,69.072,-48.753,69.123,-48.766,69.136,-48.861,69.104,-48.9,69.099,-48.938,69.033,-49.018,69.052,-49.082,69.185,-49.11]],[[69.282,-49.059,69.221,-49.067,69.202,-49.034,69.204,-48.991,69.17,-48.957,69.15,-48.919,69.167,-48.883,69.266,-48.879,69.369,-48.89,69.395,-48.951,69.321,-49.034,69.282,-49.059]],[[51.835,-46.44,51.762,-46.449,51.697,-46.428,51.659,-46.374,51.742,-46.327,51.784,-46.359,51.815,-46.395,51.835,-46.44]]]},{"n":"Åland","c":"AX","r":6,"ml":5,"lx":19.87,"ly":60.16,"b":[19.519,60.012,20.611,60.406],"p":[[[19.99,60.351,20.02,60.351,20.034,60.359,20.087,60.353,20.168,60.315,20.184,60.294,20.24,60.283,20.259,60.261,20.195,60.194,20.155,60.192,20.125,60.201,20.073,60.193,20.043,60.181,20.032,60.152,20.034,60.094,19.8,60.082,19.746,60.099,19.672,60.233,19.687,60.268,19.737,60.282,19.779,60.286,19.785,60.213,19.848,60.221,19.867,60.268,19.872,60.302,19.855,60.319,19.812,60.332,19.788,60.354,19.823,60.39,19.888,60.406,19.945,60.358,19.99,60.351]],[[19.662,60.187,19.667,60.165,19.629,60.17,19.6,60.163,19.58,60.135,19.537,60.145,19.519,60.185,19.551,60.244,19.629,60.246,19.662,60.187]],[[20.611,60.041,20.603,60.017,20.522,60.012,20.488,60.033,20.411,60.03,20.398,60.041,20.43,60.062,20.49,60.075,20.569,60.07,20.611,60.041]]]},{"n":"Finland","c":"FI","r":3,"ml":3,"lx":27.28,"ly":63.25,"b":[20.622,59.816,31.537,70.065],"p":[[[24.155,65.805,24.049,65.99,23.995,66.06,23.907,66.148,23.751,66.191,23.721,66.215,23.7,66.253,23.694,66.304,23.674,66.381,23.682,66.443,23.701,66.481,23.768,66.506,23.866,66.577,23.886,66.628,23.894,66.707,23.939,66.776,23.989,66.811,23.976,66.838,23.942,66.878,23.869,66.934,23.759,67.003,23.677,67.068,23.642,67.129,23.623,67.184,23.626,67.234,23.657,67.268,23.761,67.31,23.775,67.329,23.734,67.423,23.661,67.44,23.537,67.449,23.468,67.45,23.455,67.46,23.451,67.479,23.465,67.518,23.504,67.562,23.537,67.59,23.541,67.614,23.5,67.696,23.488,67.797,23.502,67.875,23.633,67.933,23.639,67.954,23.474,68.017,23.355,68.089,23.319,68.13,23.183,68.137,23.098,68.258,22.975,68.316,22.854,68.367,22.782,68.391,22.362,68.464,22.195,68.478,21.997,68.521,21.85,68.574,21.724,68.609,21.616,68.651,21.465,68.691,21.422,68.725,21.26,68.787,21.183,68.829,20.919,68.907,20.909,68.938,20.907,68.967,20.895,68.98,20.622,69.037,20.676,69.069,20.889,69.071,21.066,69.042,21.104,69.054,21.128,69.081,21.053,69.187,21.066,69.214,21.144,69.247,21.267,69.274,21.461,69.277,21.594,69.274,21.622,69.271,21.82,69.154,21.989,69.041,22.08,68.993,22.3,68.856,22.383,68.777,22.411,68.72,22.501,68.72,22.811,68.695,23.072,68.674,23.144,68.643,23.324,68.649,23.463,68.678,23.707,68.714,23.773,68.758,23.854,68.806,23.997,68.798,24.154,68.761,24.332,68.712,24.491,68.689,24.703,68.653,24.802,68.606,24.941,68.593,25.087,68.64,25.173,68.765,25.249,68.821,25.357,68.862,25.481,68.881,25.575,68.887,25.647,68.919,25.748,68.99,25.768,69.076,25.749,69.231,25.767,69.283,25.85,69.367,25.962,69.589,26.012,69.653,26.072,69.692,26.156,69.715,26.308,69.782,26.525,69.915,26.584,69.926,26.74,69.933,26.934,69.928,27.109,69.905,27.128,69.906,27.206,69.919,27.348,69.96,27.592,70.042,27.748,70.065,27.89,70.062,28.047,69.972,28.269,69.871,28.412,69.823,28.8,69.731,29.142,69.671,29.333,69.473,29.239,69.394,29.192,69.367,29.025,69.288,28.846,69.177,28.833,69.119,28.892,69.061,28.966,69.022,28.899,69.01,28.692,68.961,28.566,68.928,28.414,68.904,28.454,68.872,28.706,68.866,28.745,68.856,28.773,68.84,28.778,68.814,28.752,68.771,28.479,68.538,28.471,68.488,28.56,68.351,28.685,68.19,29.063,68.118,29.344,68.062,29.524,67.929,29.822,67.754,29.979,67.689,29.988,67.668,29.941,67.547,29.751,67.426,29.572,67.324,29.388,67.201,29.243,67.097,29.087,66.971,29.069,66.93,29.066,66.892,29.093,66.849,29.293,66.696,29.371,66.617,29.464,66.532,29.544,66.44,29.591,66.357,29.671,66.276,29.721,66.235,29.804,66.177,29.903,66.091,29.937,66.023,30.088,65.787,30.103,65.726,30.095,65.682,30.029,65.671,29.883,65.664,29.724,65.634,29.716,65.625,29.819,65.569,29.728,65.473,29.715,65.337,29.617,65.265,29.608,65.249,29.612,65.235,29.63,65.224,29.811,65.205,29.826,65.185,29.827,65.145,29.811,65.108,29.72,65.08,29.622,65.04,29.601,65.002,29.604,64.968,29.638,64.912,29.702,64.846,29.783,64.804,30.073,64.765,30.11,64.733,30.126,64.688,30.12,64.645,29.986,64.558,29.987,64.524,30.042,64.443,30.108,64.366,30.391,64.282,30.488,64.237,30.514,64.2,30.528,64.141,30.526,64.077,30.504,64.021,30.415,63.948,30.21,63.803,30.004,63.747,29.992,63.735,30.055,63.689,30.419,63.504,30.655,63.417,30.975,63.301,31.181,63.208,31.247,63.142,31.337,63.068,31.437,63.008,31.509,62.955,31.537,62.922,31.534,62.885,31.437,62.776,31.382,62.692,31.286,62.568,31.187,62.481,30.936,62.324,30.566,62.128,30.48,62.068,30.306,61.965,30.01,61.757,29.933,61.712,29.69,61.546,29.579,61.493,29.492,61.444,29.252,61.288,28.993,61.169,28.739,61.059,28.663,61.003,28.568,60.96,28.455,60.92,28.407,60.897,28.152,60.746,27.798,60.536,27.762,60.533,27.669,60.499,27.525,60.491,27.462,60.465,27.242,60.539,27.205,60.543,27.076,60.525,26.951,60.471,26.721,60.455,26.607,60.438,26.535,60.413,26.52,60.472,26.551,60.546,26.602,60.596,26.606,60.628,26.569,60.625,26.496,60.552,26.456,60.467,26.378,60.424,26.205,60.407,26.036,60.475,25.956,60.474,26.006,60.425,26.04,60.372,26.036,60.342,25.946,60.347,25.846,60.315,25.758,60.268,25.715,60.267,25.656,60.333,25.548,60.302,25.456,60.261,25.268,60.248,25.156,60.194,24.958,60.157,24.849,60.158,24.6,60.114,24.518,60.046,24.446,60.021,24.343,60.042,24.025,60.009,23.722,59.966,23.593,59.968,23.464,59.986,23.327,59.926,23.181,59.845,23.021,59.816,22.964,59.826,23.01,59.869,23.116,59.913,23.188,59.972,23.198,60.022,23.148,60.041,23.08,60.047,22.994,60.099,22.912,60.21,22.867,60.216,22.844,60.187,22.819,60.101,22.793,60.077,22.75,60.057,22.697,60.038,22.646,60.028,22.463,60.029,22.439,60.072,22.439,60.09,22.471,60.147,22.443,60.157,22.47,60.201,22.513,60.199,22.564,60.206,22.59,60.228,22.588,60.256,22.517,60.263,22.512,60.281,22.576,60.359,22.585,60.381,22.56,60.385,22.521,60.377,22.258,60.401,21.934,60.5,21.854,60.505,21.805,60.594,21.727,60.583,21.613,60.531,21.528,60.57,21.436,60.596,21.411,60.637,21.412,60.697,21.404,60.767,21.379,60.85,21.361,60.967,21.378,61.059,21.451,61.127,21.479,61.171,21.513,61.281,21.521,61.411,21.502,61.455,21.507,61.484,21.565,61.484,21.552,61.51,21.527,61.523,21.498,61.552,21.522,61.567,21.592,61.568,21.598,61.578,21.606,61.592,21.552,61.667,21.546,61.703,21.471,61.812,21.385,61.915,21.256,61.99,21.302,62.113,21.354,62.224,21.343,62.277,21.323,62.343,21.166,62.414,21.142,62.515,21.104,62.623,21.118,62.689,21.144,62.74,21.196,62.791,21.458,62.95,21.474,63.033,21.651,63.039,21.569,63.114,21.549,63.156,21.545,63.204,21.8,63.238,21.896,63.21,22.12,63.244,22.32,63.31,22.316,63.346,22.286,63.377,22.243,63.438,22.273,63.455,22.346,63.442,22.313,63.473,22.319,63.504,22.398,63.491,22.528,63.58,22.532,63.648,22.756,63.683,23.014,63.822,23.134,63.865,23.249,63.896,23.494,64.034,23.599,64.041,23.653,64.134,23.861,64.258,23.925,64.274,24.022,64.386,24.278,64.515,24.441,64.68,24.53,64.739,24.558,64.801,24.658,64.806,24.748,64.852,24.942,64.884,25.134,64.875,25.214,64.853,25.288,64.86,25.281,64.916,25.228,64.951,25.271,64.984,25.373,65.009,25.362,65.065,25.34,65.099,25.256,65.143,25.298,65.243,25.308,65.353,25.348,65.479,25.242,65.546,24.839,65.66,24.764,65.656,24.675,65.671,24.582,65.757,24.623,65.832,24.628,65.859,24.592,65.858,24.533,65.822,24.404,65.78,24.238,65.812,24.155,65.805]],[[21.994,60.337,21.921,60.332,21.819,60.382,21.806,60.401,21.846,60.412,21.819,60.452,21.827,60.47,21.907,60.438,21.95,60.402,21.908,60.393,21.98,60.355,21.994,60.337]],[[21.218,63.241,21.229,63.223,21.287,63.228,21.366,63.262,21.422,63.246,21.416,63.197,21.378,63.199,21.367,63.207,21.318,63.179,21.31,63.163,21.253,63.152,21.149,63.199,21.084,63.278,21.236,63.278,21.222,63.259,21.218,63.241]],[[22.175,60.371,22.302,60.348,22.355,60.356,22.416,60.303,22.313,60.27,22.306,60.229,22.346,60.203,22.361,60.166,22.258,60.166,22.209,60.197,22.188,60.237,22.141,60.265,22.077,60.286,22.108,60.315,22.126,60.356,22.175,60.371]],[[21.451,60.53,21.437,60.483,21.369,60.488,21.3,60.48,21.244,60.526,21.215,60.604,21.225,60.621,21.268,60.638,21.301,60.596,21.451,60.53]],[[21.833,60.141,21.733,60.106,21.695,60.114,21.705,60.172,21.764,60.199,21.864,60.202,21.833,60.141]],[[21.628,60.108,21.541,60.098,21.486,60.127,21.507,60.148,21.568,60.172,21.634,60.169,21.648,60.141,21.628,60.108]],[[24.848,64.991,24.699,64.958,24.579,64.979,24.577,65.043,24.651,65.074,24.786,65.086,24.971,65.055,24.998,65.039,24.892,65.026,24.848,64.991]]]},{"n":"Fiji","c":"FJ","r":6,"ml":3,"lx":177.98,"ly":-17.83,"b":[-180,-21.706,180,-12.477],"p":[[[179.999,-16.169,179.848,-16.302,179.794,-16.37,179.748,-16.446,179.619,-16.528,179.564,-16.637,179.568,-16.747,179.697,-16.632,179.841,-16.537,179.885,-16.518,179.93,-16.519,179.927,-16.552,179.906,-16.584,179.89,-16.667,179.928,-16.744,179.821,-16.737,179.715,-16.744,179.589,-16.787,179.465,-16.806,179.419,-16.807,179.375,-16.792,179.346,-16.77,179.323,-16.718,179.3,-16.71,179.202,-16.713,179.055,-16.814,179.007,-16.9,178.95,-16.904,178.884,-16.886,178.803,-16.952,178.707,-16.976,178.665,-16.92,178.638,-16.851,178.604,-16.801,178.497,-16.788,178.514,-16.726,178.542,-16.7,178.568,-16.664,178.584,-16.622,178.634,-16.649,178.686,-16.666,178.745,-16.634,178.805,-16.631,178.866,-16.54,178.961,-16.483,179.091,-16.437,179.225,-16.405,179.294,-16.399,179.359,-16.38,179.475,-16.294,179.552,-16.25,179.635,-16.223,179.715,-16.208,179.789,-16.221,179.848,-16.214,180,-16.153,179.999,-16.169]],[[178.28,-17.372,178.28,-17.416,178.309,-17.435,178.339,-17.438,178.411,-17.523,178.523,-17.596,178.592,-17.651,178.596,-17.699,178.575,-17.749,178.604,-17.839,178.618,-17.933,178.668,-18.081,178.597,-18.109,178.487,-18.112,178.461,-18.139,178.423,-18.124,178.332,-18.135,178.244,-18.184,178.16,-18.25,178.064,-18.25,177.955,-18.264,177.847,-18.255,177.771,-18.22,177.636,-18.181,177.457,-18.148,177.383,-18.121,177.321,-18.078,177.263,-17.969,177.255,-17.915,177.264,-17.863,177.316,-17.846,177.36,-17.82,177.366,-17.786,177.386,-17.762,177.411,-17.754,177.423,-17.737,177.406,-17.682,177.401,-17.632,177.504,-17.54,177.618,-17.461,177.818,-17.388,177.94,-17.395,178.128,-17.339,178.188,-17.313,178.247,-17.329,178.28,-17.372]],[[-179.975,-16.925,-180,-16.963,-180,-16.908,-180,-16.859,-180,-16.824,-180,-16.786,-179.894,-16.7,-179.861,-16.688,-179.822,-16.765,-179.868,-16.85,-179.975,-16.925]],[[180,-16.963,179.926,-17,179.897,-16.964,179.931,-16.876,180,-16.786,179.999,-16.859,180,-16.963]],[[178.488,-18.974,178.488,-19.017,178.359,-19.046,178.316,-19.01,178.288,-19.004,178.211,-19.067,178.189,-19.092,178.182,-19.112,178.162,-19.121,178.021,-19.152,177.959,-19.122,178.001,-19.101,178.052,-19.06,178.104,-19.066,178.157,-19.028,178.208,-18.97,178.282,-18.957,178.334,-18.934,178.42,-18.951,178.488,-18.974]],[[-178.988,-17.977,-179.018,-17.992,-179.039,-17.988,-179.064,-17.972,-179.079,-17.944,-179.048,-17.92,-178.999,-17.947,-178.988,-17.977]],[[-178.762,-18.234,-178.774,-18.252,-178.827,-18.222,-178.848,-18.202,-178.791,-18.186,-178.763,-18.191,-178.762,-18.234]],[[-178.251,-17.953,-178.307,-17.963,-178.357,-17.921,-178.325,-17.876,-178.28,-17.886,-178.255,-17.93,-178.251,-17.953]],[[-178.956,-17.273,-178.982,-17.307,-179.004,-17.295,-178.976,-17.237,-178.971,-17.213,-179.015,-17.182,-179.018,-17.161,-179.005,-17.148,-178.953,-17.182,-178.921,-17.208,-178.915,-17.223,-178.925,-17.249,-178.956,-17.273]],[[-178.535,-19.166,-178.546,-19.175,-178.574,-19.165,-178.596,-19.151,-178.599,-19.137,-178.589,-19.119,-178.568,-19.109,-178.557,-19.113,-178.563,-19.119,-178.576,-19.125,-178.574,-19.143,-178.557,-19.154,-178.541,-19.157,-178.535,-19.166]],[[-179.8,-18.94,-179.798,-18.97,-179.812,-18.968,-179.83,-18.956,-179.839,-18.962,-179.846,-18.971,-179.849,-18.991,-179.851,-19.003,-179.865,-18.999,-179.867,-18.978,-179.863,-18.964,-179.856,-18.943,-179.831,-18.924,-179.8,-18.94]],[[177.234,-17.147,177.183,-17.164,177.21,-17.084,177.239,-17.059,177.258,-17.054,177.287,-17.049,177.276,-17.105,177.234,-17.147]],[[179.422,-17.367,179.389,-17.394,179.373,-17.256,179.408,-17.257,179.433,-17.272,179.447,-17.306,179.422,-17.367]],[[179.349,-18.102,179.34,-18.11,179.254,-18.031,179.256,-17.999,179.272,-17.971,179.306,-17.944,179.338,-17.99,179.362,-18.065,179.349,-18.102]],[[178.828,-17.729,178.776,-17.747,178.748,-17.686,178.787,-17.624,178.831,-17.619,178.853,-17.681,178.828,-17.729]],[[-179.929,-16.503,-180,-16.54,-180,-16.489,-179.944,-16.441,-179.901,-16.432,-179.927,-16.479,-179.929,-16.503]],[[-179.956,-16.149,-180,-16.168,-180,-16.156,-180,-16.153,-179.969,-16.126,-179.945,-16.126,-179.956,-16.149]],[[180,-16.54,179.987,-16.541,179.985,-16.522,180,-16.489,180,-16.54]],[[177.121,-12.505,177.082,-12.516,177.019,-12.507,177.006,-12.491,177.026,-12.487,177.068,-12.477,177.118,-12.482,177.127,-12.493,177.121,-12.505]],[[174.63,-21.695,174.622,-21.706,174.593,-21.702,174.587,-21.68,174.604,-21.667,174.628,-21.676,174.63,-21.695]],[[-178.712,-20.668,-178.71,-20.671,-178.715,-20.67,-178.723,-20.667,-178.729,-20.66,-178.731,-20.653,-178.728,-20.645,-178.725,-20.646,-178.719,-20.652,-178.714,-20.66,-178.712,-20.668]]]},{"n":"Ethiopia","c":"ET","r":2,"ml":2,"lx":39.09,"ly":8.03,"b":[32.999,3.456,47.978,14.852],"p":[[[35.268,5.492,35.252,5.511,35.164,5.581,35.082,5.673,35.032,5.775,34.984,5.858,34.959,6.045,34.898,6.16,34.838,6.3,34.749,6.568,34.711,6.66,34.639,6.722,34.563,6.78,34.484,6.898,34.279,7.003,34.2,7.085,34.064,7.226,34.03,7.297,34.02,7.368,33.978,7.435,33.902,7.51,33.666,7.671,33.601,7.69,33.516,7.708,33.392,7.724,33.226,7.761,33.081,7.824,33.015,7.869,32.999,7.9,33.013,7.952,33.065,8.04,33.165,8.251,33.234,8.396,33.281,8.437,33.409,8.448,33.545,8.443,33.645,8.433,33.785,8.431,33.953,8.444,34.02,8.492,34.073,8.545,34.095,8.582,34.102,8.676,34.102,8.752,34.091,9.041,34.085,9.219,34.077,9.421,34.078,9.462,34.079,9.513,34.12,9.73,34.159,9.853,34.185,9.919,34.292,10.125,34.311,10.191,34.315,10.252,34.276,10.528,34.344,10.659,34.431,10.788,34.508,10.843,34.572,10.88,34.602,10.865,34.675,10.805,34.771,10.746,34.816,10.759,34.882,10.811,34.931,10.865,34.925,10.962,34.969,11.162,34.961,11.277,35.008,11.42,35.06,11.621,35.083,11.748,35.112,11.817,35.252,11.957,35.373,12.156,35.45,12.301,35.596,12.537,35.67,12.624,35.731,12.661,35.821,12.685,35.988,12.706,36.108,12.726,36.125,12.757,36.135,12.805,36.137,12.911,36.16,13.093,36.212,13.271,36.274,13.406,36.307,13.467,36.346,13.526,36.391,13.626,36.447,13.842,36.444,13.988,36.524,14.257,36.542,14.258,36.679,14.308,36.812,14.315,36.941,14.281,37.025,14.272,37.063,14.289,37.099,14.334,37.133,14.406,37.185,14.446,37.257,14.454,37.354,14.372,37.507,14.156,37.547,14.144,37.571,14.149,37.648,14.323,37.708,14.457,37.82,14.708,37.884,14.852,37.943,14.811,38.003,14.737,38.07,14.703,38.142,14.681,38.177,14.679,38.221,14.65,38.377,14.47,38.431,14.429,38.504,14.424,38.812,14.482,38.996,14.587,39.024,14.628,39.074,14.628,39.135,14.582,39.159,14.538,39.198,14.479,39.27,14.47,39.446,14.512,39.532,14.537,39.605,14.516,39.698,14.499,39.756,14.499,39.895,14.441,40.062,14.459,40.141,14.456,40.221,14.431,40.353,14.338,40.524,14.225,40.77,14.144,40.82,14.112,40.939,13.983,41.122,13.736,41.363,13.5,41.625,13.313,41.765,13.184,41.86,13.026,41.952,12.882,42.047,12.821,42.134,12.771,42.225,12.662,42.29,12.57,42.379,12.466,42.28,12.324,42.149,12.134,41.996,11.912,41.95,11.858,41.816,11.724,41.793,11.686,41.767,11.589,41.765,11.413,41.782,11.188,41.798,10.98,41.872,10.956,41.957,10.941,42.052,10.968,42.166,10.992,42.308,11.005,42.465,11.047,42.558,11.081,42.655,11.078,42.741,11.042,42.783,11.009,42.844,10.998,42.923,10.999,42.906,10.96,42.863,10.903,42.81,10.846,42.763,10.787,42.66,10.621,42.656,10.6,42.669,10.568,42.725,10.492,42.784,10.37,42.816,10.257,42.842,10.203,42.913,10.141,43.015,10.013,43.069,9.926,43.182,9.88,43.218,9.77,43.303,9.609,43.394,9.48,43.483,9.379,43.581,9.341,43.621,9.337,43.827,9.151,43.984,9.009,44.023,8.986,44.306,8.893,44.632,8.786,44.894,8.7,45.227,8.591,45.555,8.483,45.863,8.38,46.296,8.235,46.645,8.118,46.92,8.026,46.978,7.997,47.306,7.997,47.638,7.997,47.978,7.997,47.732,7.759,47.453,7.49,47.16,7.208,46.971,7.026,46.672,6.737,46.423,6.497,46.167,6.235,45.935,5.997,45.634,5.668,45.438,5.455,45.133,5.122,44.941,4.912,44.912,4.9,44.637,4.916,44.37,4.931,44.028,4.951,43.989,4.951,43.889,4.931,43.829,4.911,43.583,4.855,43.538,4.84,43.334,4.75,43.126,4.644,43.016,4.563,42.931,4.445,42.895,4.361,42.857,4.324,42.792,4.292,42.355,4.212,42.228,4.202,42.024,4.138,41.915,4.031,41.884,3.978,41.738,3.979,41.482,3.963,41.372,3.946,41.319,3.943,41.221,3.944,41.14,3.963,41.087,3.992,41.021,4.057,40.873,4.19,40.765,4.273,40.529,4.178,40.316,4.083,40.014,3.948,39.842,3.851,39.79,3.754,39.658,3.578,39.539,3.469,39.494,3.456,39.225,3.479,39.128,3.501,38.968,3.521,38.753,3.559,38.608,3.6,38.452,3.605,38.225,3.619,38.086,3.649,37.945,3.747,37.763,3.865,37.575,3.986,37.383,4.111,37.155,4.255,36.906,4.411,36.848,4.427,36.824,4.43,36.553,4.437,36.272,4.445,36.082,4.45,36.022,4.468,35.979,4.504,35.92,4.62,35.846,4.703,35.763,4.808,35.756,4.95,35.779,5.106,35.8,5.157,35.788,5.208,35.791,5.279,35.745,5.344,35.469,5.419,35.424,5.413,35.378,5.385,35.325,5.365,35.288,5.384,35.265,5.412,35.264,5.458,35.268,5.492]]]},{"n":"Estonia","c":"EE","r":6,"ml":3,"lx":25.87,"ly":58.72,"b":[21.854,57.525,28.151,59.639],"p":[[[27.352,57.528,27.327,57.525,27.187,57.538,27.033,57.579,26.966,57.609,26.9,57.609,26.82,57.589,26.533,57.531,26.462,57.544,26.298,57.601,26.215,57.663,26.03,57.786,26.015,57.815,25.991,57.838,25.794,57.869,25.721,57.914,25.66,57.92,25.571,57.943,25.34,58.039,25.283,58.048,25.269,58.032,25.273,58.009,25.258,57.996,25.229,57.997,25.175,58.032,25.111,58.063,24.911,58.005,24.839,57.989,24.776,57.985,24.459,57.908,24.363,57.866,24.323,57.871,24.332,57.91,24.464,58.106,24.488,58.262,24.536,58.283,24.55,58.305,24.529,58.354,24.392,58.386,24.337,58.381,24.287,58.328,24.236,58.29,24.115,58.266,24.011,58.307,23.768,58.361,23.706,58.433,23.692,58.506,23.563,58.576,23.509,58.659,23.531,58.716,23.647,58.754,23.681,58.787,23.534,58.782,23.504,58.79,23.497,58.82,23.432,58.921,23.49,58.96,23.515,58.999,23.468,59.032,23.48,59.07,23.517,59.108,23.494,59.196,23.641,59.242,23.783,59.275,24.083,59.292,24.054,59.372,24.175,59.376,24.38,59.473,24.584,59.456,24.878,59.522,25.444,59.521,25.521,59.559,25.507,59.598,25.509,59.639,25.616,59.628,25.794,59.635,26.461,59.554,26.625,59.554,26.852,59.472,26.975,59.451,27.336,59.45,27.893,59.414,28.002,59.47,28.013,59.484,28.066,59.453,28.133,59.403,28.151,59.374,28.128,59.358,28.061,59.343,28.046,59.328,28.016,59.302,27.938,59.297,27.898,59.278,27.85,59.193,27.758,59.052,27.622,58.945,27.513,58.886,27.464,58.841,27.434,58.787,27.427,58.733,27.531,58.435,27.53,58.381,27.506,58.326,27.488,58.27,27.502,58.221,27.571,58.138,27.644,58.014,27.673,57.935,27.722,57.905,27.769,57.884,27.779,57.871,27.777,57.857,27.753,57.841,27.542,57.799,27.515,57.764,27.492,57.725,27.4,57.667,27.372,57.613,27.354,57.55,27.352,57.528]],[[22.617,58.621,22.688,58.597,22.754,58.605,22.82,58.622,22.964,58.606,23.293,58.483,23.323,58.451,23.127,58.436,23.083,58.398,23.035,58.372,22.98,58.364,22.885,58.311,22.757,58.261,22.73,58.231,22.498,58.236,22.372,58.217,22.269,58.161,22.227,58.052,22.152,57.967,22.076,57.936,21.997,57.931,21.978,57.963,21.986,57.995,22.153,58.115,22.188,58.154,22.104,58.172,22.035,58.213,21.882,58.262,21.854,58.302,21.891,58.305,21.924,58.316,21.965,58.349,21.984,58.387,21.862,58.497,21.924,58.514,22.002,58.51,22.081,58.478,22.169,58.516,22.206,58.521,22.267,58.508,22.328,58.581,22.474,58.605,22.547,58.627,22.617,58.621]],[[22.924,58.827,22.842,58.777,22.793,58.797,22.767,58.821,22.661,58.709,22.542,58.69,22.473,58.712,22.479,58.754,22.411,58.863,22.307,58.895,22.162,58.898,22.056,58.944,22.463,58.974,22.505,59.026,22.587,59.081,22.649,59.087,22.702,59.074,22.712,59.032,22.725,59.015,22.91,58.991,22.982,58.92,23.009,58.834,22.924,58.827]],[[23.344,58.55,23.26,58.54,23.063,58.611,23.109,58.659,23.165,58.678,23.333,58.649,23.356,58.576,23.344,58.55]]]},{"n":"Eritrea","c":"ER","r":4,"ml":4,"lx":38.29,"ly":15.79,"b":[36.427,12.377,43.117,18.005],"p":[[[36.524,14.257,36.492,14.544,36.471,14.736,36.448,14.94,36.427,15.132,36.522,15.25,36.566,15.362,36.679,15.726,36.725,15.799,36.813,15.994,36.826,16.05,36.914,16.296,36.905,16.46,36.888,16.625,36.936,16.722,36.979,16.801,36.976,16.867,36.995,17.021,37.009,17.059,37.062,17.061,37.17,17.041,37.249,17.057,37.34,17.057,37.411,17.062,37.453,17.109,37.51,17.288,37.547,17.324,37.576,17.335,37.657,17.368,37.726,17.421,37.782,17.458,37.803,17.466,37.863,17.47,37.923,17.492,37.95,17.518,38.025,17.538,38.099,17.526,38.149,17.549,38.182,17.563,38.219,17.564,38.254,17.585,38.267,17.617,38.29,17.637,38.347,17.684,38.374,17.717,38.386,17.751,38.397,17.778,38.422,17.824,38.523,17.939,38.609,18.005,38.912,17.427,39.034,17.086,39.143,16.729,39.223,16.194,39.299,15.921,39.422,15.787,39.507,15.532,39.579,15.523,39.631,15.453,39.721,15.214,39.786,15.125,39.819,15.201,39.816,15.245,39.79,15.319,39.813,15.414,39.864,15.47,39.978,15.393,40.041,15.335,40.058,15.217,40.084,15.152,40.204,15.014,40.305,14.974,40.437,14.964,40.546,14.934,40.634,14.883,40.799,14.743,41.176,14.62,41.48,14.244,41.658,13.983,42.245,13.588,42.346,13.398,42.399,13.213,42.523,13.221,42.734,13.019,42.796,12.864,42.97,12.808,42.999,12.9,43.083,12.825,43.117,12.709,43.006,12.662,42.883,12.621,42.866,12.623,42.825,12.569,42.767,12.423,42.704,12.38,42.67,12.377,42.479,12.514,42.45,12.521,42.409,12.494,42.379,12.466,42.29,12.57,42.225,12.662,42.134,12.771,42.047,12.821,41.952,12.882,41.86,13.026,41.765,13.184,41.625,13.313,41.363,13.5,41.122,13.736,40.939,13.983,40.82,14.112,40.77,14.144,40.524,14.225,40.353,14.338,40.221,14.431,40.141,14.456,40.062,14.459,39.895,14.441,39.756,14.499,39.698,14.499,39.605,14.516,39.532,14.537,39.446,14.512,39.27,14.47,39.198,14.479,39.159,14.538,39.135,14.582,39.074,14.628,39.024,14.628,38.996,14.587,38.812,14.482,38.504,14.424,38.431,14.429,38.377,14.47,38.221,14.65,38.177,14.679,38.142,14.681,38.07,14.703,38.003,14.737,37.943,14.811,37.884,14.852,37.82,14.708,37.708,14.457,37.648,14.323,37.571,14.149,37.547,14.144,37.507,14.156,37.354,14.372,37.257,14.454,37.185,14.446,37.133,14.406,37.099,14.334,37.063,14.289,37.025,14.272,36.941,14.281,36.812,14.315,36.679,14.308,36.542,14.258,36.524,14.257]],[[40.076,16.082,40.11,15.986,40.012,16.023,39.996,16.043,40.039,16.081,40.048,16.104,40.076,16.082]],[[40.141,15.696,40.183,15.643,40.211,15.648,40.234,15.666,40.25,15.703,40.408,15.629,40.399,15.58,40.305,15.577,40.196,15.598,40.095,15.591,39.975,15.612,39.947,15.696,40.024,15.656,40.063,15.666,40.071,15.677,40.016,15.733,39.94,15.745,39.945,15.789,39.979,15.807,40,15.828,39.957,15.889,40.043,15.875,40.097,15.838,40.132,15.795,40.141,15.696]]]},{"n":"Equatorial Guinea","c":"GQ","r":4,"ml":4,"lx":8.99,"ly":2.33,"b":[8.434,0.96,11.335,3.758],"p":[[[8.736,3.758,8.76,3.754,8.91,3.758,8.951,3.705,8.946,3.628,8.792,3.4,8.763,3.305,8.704,3.224,8.652,3.217,8.475,3.265,8.445,3.294,8.434,3.332,8.452,3.423,8.465,3.451,8.55,3.468,8.577,3.482,8.623,3.58,8.638,3.669,8.676,3.736,8.736,3.758]],[[11.329,2.167,11.33,1.936,11.331,1.74,11.332,1.528,11.334,1.308,11.335,1.121,11.335,1,11.131,1,10.859,1.001,10.587,1.002,10.315,1.003,10.179,1.004,10.029,1.004,9.98,0.998,9.947,0.967,9.907,0.96,9.86,0.986,9.804,0.999,9.789,1.026,9.761,1.075,9.705,1.08,9.676,1.075,9.636,1.047,9.591,1.032,9.599,1.054,9.51,1.115,9.445,1.121,9.386,1.139,9.434,1.296,9.494,1.435,9.584,1.54,9.632,1.566,9.648,1.618,9.719,1.789,9.807,1.927,9.78,2.068,9.801,2.304,9.826,2.298,9.83,2.275,9.837,2.242,9.87,2.213,9.98,2.168,10.307,2.168,10.502,2.168,10.791,2.168,11.097,2.167,11.329,2.167]]]},{"n":"El Salvador","c":"SV","r":6,"ml":5,"lx":-88.89,"ly":13.69,"b":[-90.106,13.164,-87.715,14.431],"p":[[[-89.363,14.416,-89.337,14.411,-89.17,14.36,-89.121,14.37,-89.057,14.329,-89.027,14.297,-89,14.253,-88.868,14.164,-88.846,14.125,-88.747,14.072,-88.708,14.032,-88.666,14.016,-88.583,14,-88.513,13.979,-88.504,13.964,-88.498,13.905,-88.483,13.854,-88.449,13.851,-88.408,13.875,-88.276,13.943,-88.151,13.987,-88.08,13.961,-88.039,13.905,-87.991,13.88,-87.892,13.895,-87.802,13.89,-87.731,13.841,-87.715,13.813,-87.759,13.65,-87.774,13.58,-87.782,13.521,-87.756,13.506,-87.732,13.483,-87.737,13.451,-87.814,13.399,-87.838,13.386,-87.821,13.285,-87.878,13.224,-87.931,13.181,-88.023,13.169,-88.181,13.164,-88.417,13.214,-88.592,13.281,-88.686,13.281,-88.656,13.259,-88.582,13.245,-88.484,13.197,-88.512,13.184,-88.867,13.283,-89.278,13.478,-89.523,13.509,-89.804,13.56,-89.97,13.683,-90.095,13.737,-90.106,13.783,-90.105,13.835,-90.048,13.904,-89.943,13.997,-89.873,14.046,-89.84,14.055,-89.794,14.05,-89.749,14.077,-89.711,14.141,-89.671,14.183,-89.57,14.225,-89.547,14.241,-89.555,14.277,-89.577,14.347,-89.574,14.39,-89.541,14.41,-89.501,14.414,-89.419,14.431,-89.383,14.428,-89.363,14.416]]]},{"n":"Egypt","c":"EG","r":2,"ml":1.7,"lx":29.45,"ly":26.19,"b":[24.703,21.995,36.871,31.655],"p":[[[36.871,21.997,36.543,21.997,36.215,21.997,35.887,21.997,35.559,21.996,35.231,21.996,34.903,21.996,34.575,21.996,34.246,21.996,33.918,21.996,33.59,21.996,33.262,21.996,32.934,21.996,32.606,21.996,32.278,21.996,31.95,21.996,31.622,21.996,31.434,21.996,31.466,22.085,31.486,22.148,31.464,22.192,31.4,22.202,31.358,22.189,31.261,22.002,31.209,21.995,31.093,21.995,30.711,21.995,30.329,21.995,29.947,21.995,29.565,21.995,29.183,21.995,28.801,21.995,28.419,21.995,28.036,21.995,27.654,21.995,27.272,21.996,26.89,21.996,26.508,21.996,26.126,21.996,25.744,21.996,25.362,21.996,24.98,21.996,24.98,22.22,24.98,22.445,24.98,22.67,24.98,22.894,24.98,23.119,24.98,23.343,24.98,23.568,24.98,23.792,24.98,24.017,24.98,24.242,24.98,24.466,24.98,24.691,24.98,24.915,24.98,25.14,24.98,25.364,24.98,25.589,24.98,25.813,24.98,26.038,24.98,26.263,24.98,26.487,24.98,26.712,24.98,26.936,24.98,27.161,24.98,27.385,24.98,27.61,24.98,27.835,24.98,28.059,24.98,28.284,24.98,28.508,24.98,28.733,24.98,28.957,24.98,29.182,24.972,29.224,24.916,29.376,24.866,29.57,24.811,29.809,24.804,29.886,24.712,30.132,24.703,30.201,24.726,30.251,24.878,30.458,24.923,30.558,24.961,30.679,24.974,30.777,24.929,30.926,24.878,31.061,24.86,31.199,24.853,31.335,24.93,31.427,25.023,31.514,25.057,31.567,25.112,31.627,25.15,31.655,25.225,31.534,25.382,31.513,25.893,31.621,26.457,31.512,26.769,31.47,27.248,31.378,27.54,31.213,27.62,31.192,27.83,31.195,27.968,31.097,28.515,31.05,28.807,30.943,28.973,30.857,29.072,30.83,29.16,30.835,29.279,30.867,29.429,30.927,29.592,31.012,29.93,31.227,30.049,31.265,30.128,31.256,30.223,31.258,30.262,31.317,30.312,31.357,30.344,31.403,30.395,31.458,30.571,31.473,30.924,31.567,30.884,31.522,30.563,31.417,30.7,31.404,30.841,31.44,31.002,31.463,31.031,31.508,31.052,31.592,31.083,31.603,31.194,31.588,31.524,31.458,31.607,31.456,31.839,31.526,31.889,31.541,31.964,31.502,32.136,31.341,32.076,31.344,31.892,31.482,31.876,31.414,31.771,31.293,31.902,31.24,32.008,31.221,32.066,31.153,32.102,31.093,32.207,31.119,32.282,31.201,32.243,31.247,32.216,31.294,32.251,31.295,32.324,31.256,32.533,31.101,32.603,31.069,32.685,31.074,32.854,31.118,32.902,31.111,33.13,31.168,33.157,31.126,33.194,31.085,33.378,31.131,33.667,31.13,33.903,31.181,34.176,31.304,34.198,31.323,34.213,31.292,34.245,31.208,34.329,30.995,34.401,30.828,34.49,30.596,34.518,30.507,34.53,30.446,34.659,30.191,34.735,29.982,34.791,29.812,34.87,29.564,34.904,29.477,34.849,29.432,34.736,29.271,34.617,28.758,34.446,28.357,34.427,28.106,34.4,28.016,34.319,27.889,34.22,27.764,34.045,27.829,33.76,28.048,33.594,28.256,33.416,28.39,33.248,28.568,33.202,28.696,33.204,28.778,33.13,28.978,33.076,29.073,32.871,29.286,32.812,29.4,32.767,29.45,32.721,29.522,32.647,29.798,32.566,29.974,32.473,29.925,32.489,29.852,32.409,29.749,32.36,29.631,32.397,29.534,32.565,29.386,32.599,29.322,32.638,29.182,32.632,28.992,32.659,28.928,32.784,28.787,32.829,28.703,32.857,28.631,32.898,28.565,33.023,28.442,33.202,28.208,33.372,28.051,33.495,27.974,33.547,27.898,33.559,27.701,33.55,27.607,33.657,27.431,33.697,27.341,33.802,27.268,33.849,27.185,33.893,27.049,33.959,26.649,34.05,26.551,34.329,26.024,34.565,25.691,34.679,25.443,34.853,25.14,35.194,24.475,35.397,24.27,35.478,24.155,35.625,24.066,35.784,23.938,35.632,23.95,35.594,23.943,35.541,23.921,35.515,23.843,35.504,23.779,35.523,23.443,35.564,23.271,35.698,22.946,35.797,22.849,35.846,22.786,35.913,22.74,36.23,22.629,36.415,22.394,36.83,22.098,36.87,22.016,36.871,21.997]]]},{"n":"Ecuador","c":"EC","r":3,"ml":3,"lx":-78.19,"ly":-1.26,"b":[-91.654,-4.991,-75.25,1.455],"p":[[[-75.284,-0.107,-75.34,-0.142,-75.398,-0.146,-75.476,-0.157,-75.584,-0.123,-75.626,-0.123,-75.632,-0.158,-75.561,-0.2,-75.491,-0.248,-75.466,-0.322,-75.425,-0.409,-75.325,-0.507,-75.263,-0.555,-75.259,-0.59,-75.279,-0.654,-75.284,-0.707,-75.25,-0.952,-75.272,-0.967,-75.309,-0.968,-75.348,-0.967,-75.38,-0.94,-75.408,-0.924,-75.42,-0.962,-75.449,-1.071,-75.514,-1.316,-75.571,-1.531,-75.642,-1.607,-75.745,-1.728,-75.885,-1.893,-76.09,-2.133,-76.241,-2.244,-76.36,-2.331,-76.499,-2.432,-76.679,-2.563,-76.881,-2.636,-77.161,-2.738,-77.36,-2.81,-77.506,-2.86,-77.659,-2.912,-77.861,-2.982,-77.938,-3.047,-78.068,-3.207,-78.128,-3.284,-78.183,-3.35,-78.195,-3.38,-78.187,-3.4,-78.161,-3.432,-78.158,-3.465,-78.195,-3.486,-78.226,-3.489,-78.24,-3.473,-78.251,-3.436,-78.284,-3.399,-78.323,-3.388,-78.345,-3.397,-78.347,-3.431,-78.398,-3.595,-78.4,-3.674,-78.421,-3.706,-78.42,-3.777,-78.471,-3.843,-78.493,-3.902,-78.509,-3.952,-78.55,-3.987,-78.565,-4.042,-78.603,-4.157,-78.648,-4.248,-78.679,-4.326,-78.685,-4.384,-78.661,-4.425,-78.653,-4.458,-78.674,-4.518,-78.686,-4.562,-78.743,-4.593,-78.862,-4.665,-78.908,-4.714,-78.926,-4.771,-78.914,-4.819,-78.919,-4.858,-78.975,-4.873,-78.995,-4.908,-79.033,-4.969,-79.076,-4.991,-79.187,-4.958,-79.268,-4.958,-79.331,-4.928,-79.399,-4.84,-79.456,-4.766,-79.502,-4.671,-79.516,-4.539,-79.578,-4.501,-79.639,-4.455,-79.711,-4.468,-79.797,-4.476,-79.845,-4.446,-79.963,-4.39,-80.064,-4.328,-80.14,-4.296,-80.197,-4.311,-80.232,-4.349,-80.293,-4.417,-80.383,-4.464,-80.424,-4.461,-80.479,-4.43,-80.488,-4.394,-80.444,-4.336,-80.353,-4.208,-80.454,-4.205,-80.488,-4.166,-80.493,-4.119,-80.51,-4.07,-80.49,-4.01,-80.437,-3.979,-80.358,-4.003,-80.303,-4.005,-80.267,-3.949,-80.231,-3.924,-80.194,-3.906,-80.179,-3.878,-80.218,-3.788,-80.229,-3.739,-80.217,-3.711,-80.219,-3.654,-80.221,-3.613,-80.244,-3.577,-80.245,-3.522,-80.265,-3.492,-80.272,-3.461,-80.274,-3.425,-80.298,-3.406,-80.325,-3.388,-80.303,-3.375,-80.16,-3.324,-80.1,-3.274,-80.027,-3.228,-79.963,-3.158,-79.922,-3.09,-79.823,-2.777,-79.73,-2.579,-79.746,-2.485,-79.822,-2.357,-79.84,-2.168,-79.833,-2.111,-79.842,-2.067,-79.893,-2.146,-79.88,-2.424,-79.926,-2.549,-79.989,-2.579,-80.03,-2.557,-80.007,-2.354,-80.053,-2.391,-80.127,-2.528,-80.249,-2.631,-80.255,-2.665,-80.285,-2.707,-80.379,-2.668,-80.45,-2.626,-80.685,-2.397,-80.839,-2.349,-80.932,-2.269,-80.952,-2.235,-80.963,-2.189,-80.868,-2.141,-80.77,-2.077,-80.761,-1.935,-80.763,-1.823,-80.835,-1.632,-80.801,-1.383,-80.82,-1.286,-80.902,-1.079,-80.841,-0.975,-80.624,-0.899,-80.554,-0.848,-80.505,-0.684,-80.455,-0.585,-80.358,-0.625,-80.282,-0.621,-80.385,-0.584,-80.468,-0.436,-80.482,-0.368,-80.321,-0.166,-80.237,-0.113,-80.133,-0.006,-80.046,0.155,-80.025,0.41,-80.061,0.592,-80.088,0.785,-80.036,0.835,-79.904,0.86,-79.796,0.922,-79.741,0.98,-79.613,0.971,-79.465,1.06,-79.229,1.105,-78.9,1.206,-78.827,1.296,-78.86,1.455,-78.829,1.435,-78.737,1.359,-78.682,1.283,-78.588,1.237,-78.512,1.199,-78.312,1.046,-78.181,0.969,-78.037,0.899,-77.83,0.825,-77.703,0.838,-77.673,0.782,-77.649,0.724,-77.601,0.69,-77.526,0.66,-77.481,0.651,-77.468,0.637,-77.423,0.425,-77.396,0.394,-77.293,0.36,-77.166,0.348,-77.114,0.355,-77.002,0.296,-76.92,0.269,-76.829,0.248,-76.768,0.242,-76.739,0.251,-76.729,0.272,-76.679,0.268,-76.603,0.241,-76.495,0.235,-76.427,0.261,-76.418,0.304,-76.413,0.379,-76.388,0.405,-76.311,0.448,-76.271,0.439,-76.068,0.346,-76.026,0.313,-75.975,0.248,-75.88,0.151,-75.777,0.089,-75.617,0.063,-75.464,-0.038,-75.284,-0.107]],[[-80.132,-2.973,-80.151,-3.012,-80.246,-3.008,-80.273,-2.996,-80.272,-2.952,-80.25,-2.812,-80.224,-2.753,-80.146,-2.696,-80.081,-2.669,-79.997,-2.674,-79.909,-2.726,-80.013,-2.82,-80.071,-2.834,-80.093,-2.846,-80.132,-2.973]],[[-78.909,1.253,-78.966,1.245,-78.992,1.293,-78.923,1.349,-78.9,1.36,-78.909,1.253]],[[-90.335,-0.772,-90.387,-0.773,-90.542,-0.676,-90.532,-0.581,-90.47,-0.517,-90.269,-0.485,-90.185,-0.545,-90.193,-0.659,-90.261,-0.742,-90.315,-0.757,-90.335,-0.772]],[[-89.419,-0.911,-89.537,-0.952,-89.577,-0.934,-89.603,-0.913,-89.609,-0.889,-89.543,-0.827,-89.48,-0.793,-89.423,-0.722,-89.318,-0.68,-89.288,-0.69,-89.267,-0.705,-89.259,-0.728,-89.295,-0.786,-89.358,-0.826,-89.419,-0.911]],[[-91.426,-0.461,-91.526,-0.478,-91.611,-0.444,-91.647,-0.391,-91.654,-0.311,-91.647,-0.284,-91.46,-0.256,-91.399,-0.322,-91.4,-0.421,-91.426,-0.461]],[[-90.424,-1.34,-90.464,-1.342,-90.52,-1.299,-90.477,-1.221,-90.432,-1.24,-90.399,-1.262,-90.379,-1.292,-90.424,-1.34]],[[-91.272,0.025,-91.21,-0.039,-91.176,-0.223,-90.976,-0.417,-90.951,-0.525,-90.968,-0.576,-90.959,-0.595,-90.863,-0.672,-90.8,-0.752,-90.906,-0.941,-91.131,-1.02,-91.372,-1.017,-91.419,-0.997,-91.484,-0.925,-91.495,-0.861,-91.458,-0.8,-91.334,-0.706,-91.145,-0.623,-91.121,-0.559,-91.197,-0.497,-91.25,-0.374,-91.369,-0.287,-91.429,-0.023,-91.469,-0.01,-91.55,-0.047,-91.59,-0.015,-91.597,0.002,-91.509,0.062,-91.491,0.105,-91.361,0.126,-91.306,0.091,-91.272,0.025]],[[-90.574,-0.334,-90.62,-0.364,-90.809,-0.329,-90.868,-0.271,-90.82,-0.192,-90.78,-0.16,-90.668,-0.19,-90.553,-0.278,-90.574,-0.334]]]},{"n":"Dominican Republic","c":"DO","r":5,"ml":4.5,"lx":-70.65,"ly":19.1,"b":[-72,17.636,-68.339,19.914],"p":[[[-71.768,18.039,-71.764,18.204,-71.737,18.271,-71.762,18.341,-71.873,18.416,-71.94,18.513,-72,18.598,-71.987,18.61,-71.867,18.614,-71.824,18.646,-71.743,18.733,-71.727,18.803,-71.734,18.856,-71.786,18.92,-71.807,18.987,-71.742,19.046,-71.657,19.131,-71.645,19.164,-71.647,19.196,-71.746,19.286,-71.753,19.324,-71.707,19.422,-71.711,19.487,-71.757,19.688,-71.779,19.718,-71.735,19.735,-71.706,19.795,-71.667,19.849,-71.616,19.877,-71.558,19.895,-71.442,19.894,-71.281,19.847,-71.236,19.848,-71.082,19.89,-70.954,19.914,-70.834,19.887,-70.785,19.851,-70.686,19.793,-70.636,19.776,-70.479,19.777,-70.436,19.771,-70.305,19.676,-70.194,19.638,-70.129,19.636,-70.014,19.673,-69.957,19.672,-69.891,19.59,-69.878,19.473,-69.823,19.367,-69.739,19.299,-69.325,19.328,-69.232,19.272,-69.264,19.226,-69.323,19.201,-69.52,19.212,-69.606,19.206,-69.623,19.16,-69.624,19.118,-69.508,19.108,-69.395,19.086,-69.28,19.052,-69.163,19.028,-69.031,19.013,-68.901,18.988,-68.685,18.905,-68.445,18.714,-68.381,18.671,-68.339,18.612,-68.359,18.538,-68.445,18.418,-68.493,18.379,-68.564,18.355,-68.612,18.306,-68.659,18.222,-68.687,18.215,-68.721,18.218,-68.778,18.266,-68.82,18.339,-68.935,18.408,-69.072,18.399,-69.275,18.44,-69.397,18.42,-69.519,18.416,-69.645,18.436,-69.771,18.444,-69.896,18.418,-70.018,18.374,-70.063,18.346,-70.142,18.277,-70.183,18.252,-70.48,18.217,-70.565,18.268,-70.645,18.336,-70.759,18.346,-70.924,18.292,-71.028,18.273,-71.07,18.25,-71.082,18.224,-71.083,18.128,-71.106,18.07,-71.267,17.85,-71.358,17.694,-71.396,17.646,-71.439,17.636,-71.518,17.725,-71.569,17.757,-71.632,17.774,-71.658,17.821,-71.657,17.889,-71.674,17.954,-71.712,18.005,-71.768,18.039]]]},{"n":"Dominica","c":"DM","r":6,"ml":4,"lx":-61.34,"ly":15.46,"b":[-61.481,15.227,-61.251,15.633],"p":[[[-61.282,15.249,-61.375,15.227,-61.416,15.4,-61.481,15.525,-61.47,15.603,-61.458,15.633,-61.32,15.585,-61.277,15.527,-61.251,15.373,-61.282,15.249]]]},{"n":"Djibouti","c":"DJ","r":5,"ml":4,"lx":42.5,"ly":11.98,"b":[41.765,10.941,43.41,12.709],"p":[[[43.246,11.5,43.159,11.366,43.049,11.194,42.923,10.999,42.844,10.998,42.783,11.009,42.741,11.042,42.655,11.078,42.558,11.081,42.465,11.047,42.308,11.005,42.166,10.992,42.052,10.968,41.957,10.941,41.872,10.956,41.798,10.98,41.782,11.188,41.765,11.413,41.767,11.589,41.793,11.686,41.816,11.724,41.95,11.858,41.996,11.912,42.149,12.134,42.28,12.324,42.379,12.466,42.409,12.494,42.45,12.521,42.479,12.514,42.67,12.377,42.704,12.38,42.767,12.423,42.825,12.569,42.866,12.623,42.883,12.621,43.006,12.662,43.117,12.709,43.131,12.66,43.299,12.464,43.354,12.367,43.41,12.19,43.38,12.091,43.337,12.027,43.272,11.97,43.048,11.829,42.799,11.739,42.64,11.56,42.522,11.572,42.54,11.504,42.584,11.497,42.653,11.51,42.79,11.562,42.912,11.587,43.043,11.588,43.162,11.566,43.246,11.5]]]},{"n":"Greenland","c":"GL","r":3,"ml":1.7,"lx":-39.34,"ly":74.32,"b":[-72.818,59.815,-11.426,83.6],"p":[[[-29.953,83.565,-28.992,83.505,-28.484,83.435,-28.377,83.437,-27.688,83.41,-27.034,83.377,-25.947,83.29,-25.795,83.261,-25.912,83.238,-26.183,83.221,-27.572,83.193,-30.092,83.157,-31.534,83.089,-31.993,83.085,-32.033,82.983,-31.837,82.978,-31.516,82.992,-30.386,83.094,-29.964,83.11,-29.175,83.102,-28.151,83.064,-27.739,83.077,-27.002,83.067,-26.141,83.096,-25.123,83.16,-24.845,83.019,-24.47,82.877,-24.174,82.893,-23.92,82.885,-23.834,82.839,-23.695,82.819,-23.407,82.83,-22.525,82.789,-21.92,82.716,-21.692,82.683,-21.583,82.634,-21.521,82.595,-21.616,82.548,-21.994,82.463,-22.473,82.385,-23.118,82.325,-23.862,82.287,-29.579,82.161,-29.773,82.131,-29.887,82.055,-29.811,81.955,-29.544,81.94,-28.919,81.996,-27.84,82.049,-27.046,82.046,-25.149,82.001,-24.589,81.883,-24.293,81.701,-23.637,81.742,-23.496,81.773,-23.393,81.827,-23.311,81.885,-23.249,81.947,-23.18,81.989,-23.104,82.012,-22.94,82.031,-22.563,82.053,-21.576,82.075,-21.338,82.069,-21.167,81.984,-21.13,81.934,-21.118,81.87,-21.123,81.79,-21.147,81.695,-21.231,81.601,-21.504,81.438,-21.724,81.348,-21.961,81.284,-22.415,81.137,-22.573,81.098,-23.072,80.927,-23.196,80.847,-23.204,80.789,-23.118,80.778,-22.973,80.833,-22.919,80.872,-22.826,80.913,-22.089,81.02,-21.931,81.05,-21.45,81.178,-21.142,81.226,-20.89,81.276,-20.756,81.312,-20.016,81.564,-19.63,81.64,-19.225,81.64,-19.153,81.512,-18.667,81.492,-18.457,81.498,-18.118,81.467,-17.969,81.441,-17.717,81.428,-17.456,81.398,-17.226,81.43,-17.159,81.451,-16.937,81.544,-16.637,81.626,-16.359,81.729,-16.267,81.754,-16.121,81.777,-15.969,81.785,-15.556,81.834,-15.451,81.837,-15.227,81.822,-14.242,81.814,-13.704,81.789,-12.956,81.72,-12.434,81.683,-12.193,81.649,-11.841,81.578,-11.557,81.503,-11.426,81.481,-11.431,81.457,-11.529,81.424,-12.231,81.309,-12.461,81.233,-13.126,81.088,-13.451,81.038,-13.804,81.019,-14.197,81.014,-14.452,80.993,-14.49,80.973,-14.308,80.913,-14.229,80.87,-14.24,80.832,-14.431,80.776,-14.504,80.763,-15.194,80.721,-15.543,80.65,-15.998,80.642,-16.319,80.65,-16.761,80.573,-16.588,80.511,-16.429,80.484,-15.937,80.428,-15.933,80.395,-16.168,80.329,-16.489,80.252,-16.868,80.198,-17.011,80.19,-17.191,80.204,-17.357,80.201,-17.723,80.176,-18.071,80.172,-18.693,80.207,-19.029,80.248,-19.206,80.262,-19.429,80.258,-19.515,80.241,-19.867,80.145,-20.04,80.079,-20.15,80.011,-20.197,79.938,-20.181,79.858,-20.138,79.803,-20.069,79.774,-19.985,79.756,-19.839,79.746,-19.518,79.755,-19.392,79.75,-19.353,79.734,-19.284,79.683,-19.296,79.635,-19.354,79.567,-19.399,79.488,-19.431,79.398,-19.414,79.349,-19.284,79.338,-19.223,79.342,-19.152,79.325,-19.072,79.289,-19.011,79.251,-18.971,79.211,-18.992,79.178,-19.075,79.152,-19.262,79.123,-19.723,79.065,-19.77,79.047,-19.806,79.012,-19.832,78.959,-19.887,78.911,-19.973,78.868,-20.05,78.842,-20.2,78.83,-20.396,78.829,-20.616,78.804,-21.134,78.659,-21.141,78.643,-20.947,78.596,-20.956,78.555,-21.195,78.38,-21.26,78.293,-21.312,78.174,-21.397,78.074,-21.516,77.992,-21.633,77.897,-21.748,77.791,-21.73,77.709,-21.579,77.651,-21.38,77.698,-21.132,77.847,-20.863,77.912,-20.572,77.892,-20.319,77.862,-19.995,77.803,-19.724,77.767,-19.49,77.719,-19.394,77.678,-19.297,77.621,-19.296,77.585,-19.468,77.566,-19.524,77.572,-19.953,77.666,-20.162,77.69,-20.439,77.662,-20.681,77.619,-20.464,77.447,-20.232,77.368,-19.809,77.332,-19.588,77.294,-19.426,77.246,-19.3,77.222,-19.131,77.233,-18.903,77.28,-18.586,77.283,-18.443,77.259,-18.339,77.215,-18.292,77.133,-18.303,77.012,-18.337,76.921,-18.396,76.86,-18.51,76.778,-18.606,76.763,-18.74,76.768,-18.865,76.785,-18.981,76.814,-19.156,76.837,-19.509,76.861,-19.865,76.914,-20.064,76.928,-20.487,76.921,-20.942,76.887,-20.96,76.843,-21.615,76.688,-21.749,76.69,-21.931,76.743,-22.185,76.794,-22.334,76.794,-22.555,76.729,-22.609,76.704,-22.607,76.681,-22.444,76.625,-22.379,76.612,-22.295,76.601,-22.004,76.588,-21.877,76.573,-21.758,76.401,-21.569,76.294,-21.488,76.272,-21.417,76.264,-21.185,76.268,-20.887,76.304,-20.783,76.275,-20.564,76.24,-20.435,76.231,-20.279,76.232,-20.104,76.219,-19.863,76.121,-19.958,75.997,-19.807,75.897,-19.566,75.795,-19.509,75.758,-19.486,75.69,-19.48,75.645,-19.462,75.604,-19.431,75.567,-19.4,75.494,-19.366,75.386,-19.375,75.298,-19.426,75.23,-19.526,75.18,-19.676,75.149,-19.798,75.157,-19.893,75.205,-20.027,75.255,-20.199,75.308,-20.485,75.314,-20.906,75.157,-21.094,75.149,-21.247,75.133,-21.409,75.065,-21.649,75.023,-21.861,75.04,-22.233,75.12,-22.098,75.066,-21.904,75.004,-21.784,74.971,-21.695,74.964,-21.598,74.972,-21.457,74.998,-21.141,75.069,-21.057,75.079,-20.986,75.074,-20.928,75.053,-20.861,74.993,-20.786,74.892,-20.795,74.806,-20.89,74.735,-20.971,74.69,-21.038,74.67,-21.038,74.654,-20.862,74.636,-20.611,74.728,-20.532,74.843,-20.417,74.975,-20.214,75.019,-19.985,74.975,-19.8,74.852,-19.538,74.625,-19.427,74.601,-19.287,74.546,-19.225,74.479,-19.242,74.4,-19.272,74.343,-19.315,74.307,-19.369,74.284,-19.467,74.269,-19.646,74.258,-20.048,74.282,-20.256,74.283,-20.231,74.205,-20.653,74.137,-21.129,74.111,-21.581,74.163,-21.955,74.244,-21.832,74.357,-21.762,74.483,-21.943,74.566,-21.983,74.567,-21.92,74.439,-21.973,74.39,-22.177,74.33,-22.322,74.303,-22.334,74.286,-22.264,74.272,-22.217,74.246,-22.196,74.206,-22.22,74.166,-22.291,74.125,-22.329,74.091,-22.335,74.063,-22.271,74.03,-22.135,73.99,-21.988,73.971,-21.298,73.962,-21.022,73.941,-20.367,73.848,-20.338,73.82,-20.449,73.653,-20.51,73.493,-20.637,73.464,-21.326,73.457,-21.548,73.432,-21.873,73.358,-22.185,73.27,-22.347,73.269,-22.987,73.346,-23.233,73.398,-23.761,73.543,-24.158,73.764,-24.34,73.672,-24.451,73.629,-24.566,73.606,-24.677,73.602,-24.784,73.618,-24.905,73.653,-25.109,73.734,-25.351,73.814,-25.521,73.852,-25.528,73.841,-25.427,73.794,-25.281,73.74,-24.909,73.58,-24.778,73.54,-24.791,73.511,-25.026,73.486,-25.311,73.431,-25.45,73.391,-25.665,73.293,-25.74,73.278,-26.062,73.253,-26.169,73.259,-26.407,73.313,-26.765,73.348,-26.977,73.38,-27.27,73.436,-27.169,73.374,-26.604,73.279,-26.542,73.249,-26.658,73.192,-26.729,73.171,-26.863,73.167,-27.062,73.179,-27.265,73.176,-27.472,73.16,-27.562,73.138,-27.533,73.113,-27.483,73.089,-27.413,73.068,-27.348,73.068,-27.19,73.132,-27.07,73.137,-26.753,73.121,-26.433,73.171,-26.202,73.193,-26.029,73.199,-25.399,73.276,-25.268,73.362,-25.057,73.396,-24.587,73.423,-24.133,73.409,-23.899,73.398,-23.71,73.317,-23.456,73.259,-23.244,73.193,-22.996,73.172,-22.852,73.084,-22.45,72.986,-22.194,72.965,-22.036,72.918,-22.023,72.721,-22.007,72.635,-22.075,72.399,-22.28,72.345,-22.239,72.22,-22.293,72.12,-22.498,72.158,-22.707,72.224,-23.208,72.327,-23.674,72.393,-23.856,72.452,-24.069,72.499,-24.359,72.687,-24.547,72.922,-24.63,73.038,-24.789,73.044,-24.992,73.013,-25.171,72.98,-25.256,72.924,-25.861,72.847,-26.08,72.794,-26.206,72.796,-26.658,72.716,-26.477,72.678,-26.392,72.673,-26.209,72.694,-26.1,72.722,-25.688,72.797,-25.357,72.81,-25.237,72.843,-24.985,72.889,-24.813,72.902,-24.789,72.89,-24.771,72.869,-24.65,72.583,-24.701,72.506,-24.837,72.473,-25.128,72.419,-25.204,72.393,-25.118,72.347,-24.844,72.39,-24.667,72.437,-24.572,72.42,-24.417,72.348,-24.242,72.311,-23.798,72.201,-23.587,72.14,-23.291,72.081,-22.956,71.999,-22.869,71.971,-22.562,71.928,-22.497,71.914,-22.37,71.77,-22.265,71.754,-21.96,71.745,-22.013,71.689,-22.311,71.565,-22.465,71.525,-22.503,71.5,-22.489,71.457,-22.48,71.383,-22.418,71.249,-22.348,71.373,-22.299,71.432,-22.234,71.45,-22.17,71.453,-21.961,71.508,-21.752,71.478,-21.698,71.337,-21.671,71.206,-21.69,71.092,-21.667,70.916,-21.675,70.856,-21.625,70.805,-21.574,70.59,-21.523,70.526,-21.626,70.469,-21.944,70.443,-22.069,70.472,-22.384,70.462,-22.385,70.513,-22.4,70.571,-22.401,70.612,-22.422,70.649,-22.437,70.86,-22.526,70.808,-22.531,70.765,-22.555,70.721,-22.61,70.493,-22.691,70.437,-22.943,70.451,-23.191,70.442,-23.328,70.451,-23.792,70.555,-23.971,70.649,-24.13,70.791,-24.229,70.923,-24.266,71.046,-24.377,71.146,-24.562,71.224,-24.781,71.286,-25.033,71.334,-25.255,71.396,-25.446,71.471,-25.656,71.53,-25.885,71.572,-26.211,71.59,-26.689,71.583,-27.011,71.631,-27.087,71.627,-27.162,71.602,-27.107,71.533,-26.737,71.501,-26.452,71.494,-26.074,71.498,-25.843,71.48,-25.758,71.44,-25.699,71.368,-25.668,71.265,-25.742,71.184,-26.014,71.093,-26.158,71.05,-26.576,70.969,-26.718,70.95,-27.067,70.945,-27.336,70.953,-27.689,70.993,-27.889,71.002,-28.303,71.007,-28.398,70.993,-28.292,70.949,-28.116,70.925,-27.992,70.895,-27.979,70.84,-28.024,70.757,-28.07,70.699,-28.146,70.656,-28.417,70.574,-28.53,70.548,-29.037,70.462,-29.072,70.445,-28.953,70.447,-28.633,70.478,-28.541,70.477,-28.015,70.402,-27.596,70.407,-26.747,70.476,-26.677,70.474,-26.622,70.463,-26.565,70.438,-26.508,70.397,-26.577,70.357,-26.771,70.319,-27.073,70.281,-27.203,70.256,-27.328,70.217,-27.561,70.124,-27.629,70.028,-27.384,69.992,-27.274,70.038,-27.144,70.141,-27.028,70.201,-26.752,70.242,-26.416,70.221,-26.156,70.246,-25.625,70.347,-25.53,70.353,-24.749,70.295,-24.041,70.181,-23.667,70.139,-23.173,70.115,-22.284,70.126,-22.207,70.108,-22.235,70.068,-22.287,70.033,-22.435,69.986,-22.615,69.954,-22.726,69.945,-22.821,69.923,-23.034,69.901,-23.088,69.883,-23.015,69.805,-23.05,69.793,-23.237,69.791,-23.553,69.741,-23.812,69.744,-23.866,69.737,-23.817,69.718,-23.764,69.681,-23.709,69.627,-23.739,69.589,-23.856,69.566,-23.944,69.558,-24.248,69.59,-24.297,69.586,-24.252,69.562,-24.227,69.527,-24.221,69.479,-24.296,69.439,-24.451,69.407,-24.741,69.318,-24.867,69.293,-25.133,69.272,-25.189,69.261,-25.08,69.192,-25.092,69.165,-25.272,69.092,-25.544,69.046,-25.581,69.021,-25.606,68.954,-25.626,68.928,-25.698,68.89,-25.956,68.817,-26.139,68.781,-26.229,68.752,-26.341,68.702,-26.483,68.676,-26.654,68.673,-26.815,68.654,-27.081,68.602,-27.266,68.584,-27.851,68.494,-28.126,68.479,-28.365,68.447,-28.854,68.36,-29.088,68.332,-29.25,68.299,-29.426,68.289,-29.714,68.311,-29.869,68.312,-29.964,68.299,-30.051,68.272,-30.196,68.199,-30.318,68.193,-30.72,68.251,-30.712,68.225,-30.606,68.162,-30.611,68.118,-30.85,68.073,-30.979,68.061,-31.168,68.08,-31.419,68.128,-31.742,68.23,-32.137,68.385,-32.327,68.437,-32.314,68.388,-32.27,68.339,-32.195,68.292,-32.18,68.257,-32.224,68.236,-32.282,68.225,-32.355,68.225,-32.367,68.213,-32.249,68.139,-32.156,68.063,-32.165,67.991,-32.275,67.923,-32.37,67.883,-32.449,67.871,-32.918,67.701,-33.049,67.679,-33.108,67.658,-33.157,67.627,-33.294,67.486,-33.349,67.443,-33.458,67.387,-33.504,67.377,-33.518,67.354,-33.498,67.318,-33.528,67.258,-33.608,67.174,-33.881,66.942,-34.102,66.726,-34.198,66.655,-34.269,66.625,-34.314,66.636,-34.423,66.63,-34.476,66.592,-34.524,66.523,-34.576,66.471,-34.633,66.435,-35.075,66.279,-35.189,66.25,-35.291,66.269,-35.412,66.262,-35.662,66.344,-35.705,66.374,-35.867,66.441,-35.862,66.406,-35.835,66.387,-35.812,66.358,-35.756,66.324,-35.63,66.14,-35.729,66.102,-35.818,66.059,-36.044,65.987,-36.289,65.865,-36.379,65.831,-36.399,65.93,-36.389,65.96,-36.527,66.008,-36.523,65.973,-36.537,65.941,-36.637,65.812,-36.665,65.79,-36.715,65.795,-36.822,65.771,-36.932,65.783,-37.026,65.841,-37.063,65.871,-37.233,65.788,-37.316,65.79,-37.33,65.72,-37.41,65.656,-37.516,65.629,-37.664,65.631,-37.754,65.593,-37.955,65.634,-38.001,65.71,-37.842,65.814,-37.797,65.857,-37.827,65.91,-37.788,65.978,-37.484,66.195,-37.279,66.304,-37.291,66.324,-37.57,66.348,-37.814,66.385,-38.052,66.398,-38.157,66.386,-37.989,66.323,-37.752,66.262,-37.869,66.203,-37.969,66.141,-38.073,65.973,-38.14,65.904,-38.398,65.983,-38.52,66.01,-38.443,65.922,-38.216,65.838,-38.202,65.811,-38.203,65.712,-38.637,65.624,-39.089,65.611,-39.413,65.586,-39.961,65.556,-40.174,65.556,-40.192,65.523,-39.656,65.369,-39.578,65.341,-39.653,65.288,-39.763,65.255,-39.937,65.142,-40.028,65.103,-40.253,65.049,-40.668,65.109,-40.881,65.082,-41.084,65.101,-41.089,65.035,-41.028,64.988,-40.966,64.869,-40.829,64.878,-40.655,64.915,-40.521,64.825,-40.433,64.673,-40.278,64.596,-40.21,64.536,-40.182,64.48,-40.278,64.424,-40.478,64.344,-40.699,64.33,-40.686,64.267,-40.782,64.222,-40.985,64.235,-41.079,64.267,-41.178,64.281,-41.581,64.298,-41.175,64.177,-41.031,64.121,-40.966,64.154,-40.826,64.163,-40.618,64.132,-40.652,63.928,-40.561,63.762,-40.55,63.725,-40.772,63.626,-40.775,63.534,-40.907,63.508,-41.049,63.514,-41.056,63.412,-41.152,63.349,-41.135,63.309,-41.108,63.274,-41.195,63.209,-41.275,63.131,-41.388,63.062,-41.448,63.069,-41.628,63.065,-41.844,63.07,-42.02,63.16,-42.092,63.189,-42.175,63.209,-42.143,63.151,-42.094,63.117,-41.932,63.052,-41.634,62.972,-41.644,62.916,-41.723,62.891,-41.909,62.737,-41.975,62.734,-42.058,62.694,-42.316,62.707,-42.424,62.723,-42.741,62.713,-42.849,62.727,-42.942,62.72,-42.855,62.677,-42.674,62.638,-42.467,62.598,-42.153,62.568,-42.164,62.512,-42.243,62.466,-42.198,62.397,-42.233,62.348,-42.248,62.289,-42.321,62.153,-42.236,62.059,-42.143,62.014,-42.154,61.953,-42.11,61.857,-42.25,61.771,-42.365,61.775,-42.53,61.755,-42.585,61.717,-42.324,61.682,-42.347,61.617,-42.419,61.537,-42.494,61.363,-42.646,61.064,-42.717,60.767,-43.044,60.524,-43.16,60.517,-43.189,60.507,-43.348,60.52,-43.598,60.576,-43.792,60.595,-43.923,60.595,-43.94,60.567,-43.831,60.522,-43.665,60.503,-43.533,60.473,-43.296,60.445,-43.213,60.391,-43.156,60.333,-43.165,60.301,-43.165,60.263,-43.123,60.061,-43.235,59.991,-43.32,59.928,-43.617,59.937,-43.669,59.959,-43.955,60.025,-43.937,59.994,-43.73,59.904,-43.658,59.859,-43.706,59.849,-43.79,59.846,-43.907,59.815,-44.117,59.832,-44.105,59.878,-44.065,59.925,-44.162,59.917,-44.269,59.893,-44.331,59.902,-44.384,59.899,-44.413,59.923,-44.453,60.015,-44.405,60.061,-44.231,60.18,-44.176,60.244,-44.224,60.274,-44.348,60.205,-44.476,60.096,-44.533,60.029,-44.613,60.017,-44.812,60.05,-45.379,60.203,-45.362,60.296,-45.368,60.373,-45.202,60.383,-45.082,60.416,-44.975,60.457,-44.854,60.532,-44.742,60.655,-44.757,60.665,-45.083,60.507,-45.283,60.455,-45.381,60.445,-45.429,60.468,-45.59,60.519,-45.695,60.542,-45.934,60.579,-45.977,60.6,-46.047,60.616,-46.142,60.777,-46.019,60.972,-45.934,61.028,-45.88,61.094,-45.849,61.181,-45.87,61.218,-45.942,61.206,-45.976,61.176,-45.97,61.129,-46.012,61.097,-46.297,61.022,-46.582,60.962,-46.718,60.905,-46.806,60.86,-46.874,60.816,-46.98,60.82,-47.125,60.811,-47.224,60.783,-47.37,60.8,-47.465,60.843,-47.579,60.847,-47.707,60.827,-47.796,60.829,-47.789,60.8,-47.73,60.729,-47.828,60.725,-48.014,60.722,-48.108,60.742,-48.181,60.769,-48.242,60.807,-48.205,60.856,-47.906,60.946,-47.77,60.998,-47.859,61.016,-48.146,60.999,-48.194,61.013,-48.386,61.005,-48.378,61.138,-48.425,61.172,-48.428,61.187,-48.495,61.225,-48.558,61.234,-48.597,61.247,-48.922,61.277,-48.965,61.352,-48.987,61.429,-49.049,61.524,-49.205,61.549,-49.289,61.59,-49.222,61.632,-49.193,61.686,-49.265,61.71,-49.311,61.748,-49.304,61.772,-49.363,61.839,-49.38,61.89,-49.313,61.939,-49.13,61.993,-49.071,62.015,-49.04,62.039,-48.829,62.08,-49.008,62.108,-49.12,62.113,-49.202,62.099,-49.278,62.046,-49.349,62.01,-49.624,61.999,-49.664,62.017,-49.683,62.093,-49.668,62.151,-49.553,62.233,-49.685,62.273,-49.806,62.287,-49.943,62.324,-50.07,62.365,-50.179,62.411,-50.285,62.466,-50.319,62.473,-50.281,62.531,-50.259,62.578,-50.256,62.68,-50.299,62.722,-50.204,62.809,-50.076,62.904,-49.793,63.045,-50.092,62.977,-50.338,62.829,-50.39,62.822,-50.408,62.849,-50.502,62.945,-50.572,62.971,-50.604,63,-50.744,63.051,-50.804,63.091,-50.89,63.167,-51.013,63.258,-51.188,63.436,-51.469,63.642,-51.538,63.758,-51.451,63.905,-51.548,64.006,-51.28,64.053,-50.898,64.106,-50.699,64.149,-50.585,64.162,-50.342,64.17,-50.261,64.214,-50.396,64.203,-50.487,64.209,-50.492,64.229,-50.459,64.266,-50.437,64.313,-50.483,64.304,-50.721,64.223,-51.072,64.159,-51.347,64.123,-51.391,64.125,-51.487,64.103,-51.542,64.097,-51.585,64.103,-51.682,64.165,-51.708,64.205,-51.534,64.314,-51.404,64.463,-51.232,64.561,-51.11,64.573,-50.907,64.568,-50.835,64.559,-50.858,64.617,-50.849,64.645,-50.684,64.678,-50.492,64.693,-50.355,64.683,-50.269,64.615,-50.158,64.49,-50.009,64.447,-50.016,64.507,-50.093,64.585,-50.122,64.704,-50.22,64.754,-50.299,64.779,-50.517,64.767,-50.648,64.853,-50.678,64.885,-50.681,64.928,-50.812,65.052,-50.854,65.114,-50.924,65.197,-50.961,65.201,-50.914,65.097,-50.852,65.024,-50.765,64.863,-50.722,64.798,-50.78,64.746,-50.891,64.695,-50.989,64.665,-51.221,64.628,-51.171,64.708,-51.139,64.786,-51.255,64.758,-51.364,64.702,-51.401,64.623,-51.47,64.552,-51.677,64.377,-51.758,64.28,-51.835,64.232,-51.923,64.219,-51.999,64.257,-52.063,64.346,-52.093,64.416,-52.097,64.597,-52.089,64.682,-52.124,64.795,-52.235,65.061,-52.259,65.155,-52.448,65.205,-52.45,65.221,-52.5,65.275,-52.538,65.329,-52.506,65.348,-52.461,65.363,-52.18,65.442,-51.971,65.531,-51.721,65.67,-51.619,65.713,-51.253,65.746,-51.09,65.751,-51.092,65.776,-51.146,65.786,-51.394,65.779,-51.723,65.723,-51.78,65.703,-51.924,65.617,-52.035,65.569,-52.348,65.461,-52.551,65.461,-52.761,65.591,-52.995,65.566,-53.153,65.575,-53.199,65.594,-53.234,65.771,-53.106,65.977,-53.244,65.979,-53.272,65.987,-53.345,66.034,-53.392,66.048,-53.357,66.073,-53.018,66.171,-52.511,66.362,-52.293,66.438,-52.158,66.47,-52.056,66.507,-51.932,66.588,-51.891,66.623,-51.822,66.652,-51.676,66.684,-51.517,66.732,-51.259,66.841,-51.225,66.882,-51.281,66.891,-51.402,66.854,-51.648,66.754,-51.823,66.698,-52.421,66.447,-52.676,66.355,-52.814,66.297,-52.922,66.241,-53.036,66.201,-53.156,66.178,-53.413,66.16,-53.539,66.139,-53.615,66.154,-53.648,66.274,-53.623,66.344,-53.635,66.414,-53.571,66.513,-53.476,66.584,-53.436,66.622,-53.419,66.649,-53.223,66.721,-53.115,66.754,-53.038,66.827,-52.603,66.853,-52.491,66.85,-52.431,66.86,-52.387,66.881,-52.43,66.898,-52.56,66.909,-52.907,66.907,-53.227,66.919,-53.373,66.932,-53.444,66.925,-53.56,66.946,-53.687,66.986,-53.884,67.136,-53.805,67.327,-53.799,67.418,-53.548,67.498,-53.414,67.525,-53.224,67.585,-52.97,67.687,-52.666,67.75,-52.512,67.761,-52.384,67.752,-51.909,67.664,-51.665,67.646,-51.451,67.668,-51.181,67.637,-50.705,67.509,-50.613,67.528,-50.64,67.559,-51.171,67.694,-51.168,67.734,-51.032,67.744,-50.887,67.784,-50.969,67.807,-51.321,67.787,-51.423,67.754,-51.765,67.738,-51.944,67.765,-52.104,67.779,-52.345,67.837,-52.546,67.818,-52.673,67.795,-52.898,67.773,-52.98,67.758,-53.419,67.575,-53.604,67.536,-53.735,67.549,-53.643,67.668,-53.616,67.715,-53.616,67.767,-53.578,67.837,-53.353,67.971,-53.211,68.117,-53.152,68.208,-53.041,68.218,-52.89,68.205,-52.436,68.146,-52.058,68.075,-51.78,68.057,-51.597,68.055,-51.518,68.077,-51.456,68.116,-51.433,68.143,-51.415,68.198,-51.394,68.218,-51.333,68.242,-51.207,68.326,-51.169,68.385,-51.21,68.42,-51.293,68.416,-51.456,68.394,-51.478,68.384,-51.475,68.365,-51.632,68.273,-51.804,68.252,-52.199,68.221,-52.379,68.219,-52.698,68.262,-52.747,68.278,-52.78,68.31,-53.173,68.303,-53.29,68.293,-53.383,68.297,-53.337,68.352,-53.213,68.413,-53.039,68.611,-52.894,68.662,-52.605,68.709,-52.303,68.701,-51.781,68.548,-51.623,68.535,-51.479,68.547,-51.133,68.598,-51.07,68.619,-50.946,68.683,-50.801,68.791,-50.808,68.817,-51.03,68.756,-51.149,68.74,-51.249,68.74,-51.156,68.938,-51.12,69.091,-51.085,69.128,-50.792,69.117,-50.393,69.137,-50.297,69.171,-50.299,69.185,-50.459,69.206,-50.537,69.248,-50.671,69.234,-50.851,69.206,-51.077,69.209,-51.058,69.275,-50.892,69.412,-50.875,69.474,-50.811,69.599,-50.804,69.663,-50.72,69.725,-50.459,69.77,-50.349,69.796,-50.343,69.825,-50.5,69.936,-50.46,69.966,-50.337,69.994,-50.292,70.014,-50.323,70.027,-50.436,70.039,-50.61,70.015,-50.802,70.003,-50.973,70.04,-51.106,70.057,-51.19,70.052,-51.419,69.989,-51.499,69.987,-51.598,70.005,-52.255,70.059,-52.336,70.078,-52.571,70.172,-52.765,70.234,-53.023,70.302,-53.358,70.353,-53.769,70.389,-54.014,70.422,-54.136,70.468,-54.343,70.571,-54.501,70.657,-54.531,70.699,-54.438,70.752,-54.344,70.789,-54.166,70.82,-53.859,70.81,-53.694,70.796,-53.513,70.767,-53.376,70.761,-53.091,70.769,-52.802,70.751,-52.63,70.73,-52.405,70.687,-51.784,70.503,-51.524,70.439,-51.412,70.432,-50.947,70.364,-50.872,70.365,-50.682,70.397,-50.663,70.418,-50.728,70.438,-50.933,70.454,-51.173,70.529,-51.323,70.589,-51.34,70.688,-51.32,70.743,-51.283,70.768,-51.257,70.853,-51.396,70.903,-51.494,70.919,-51.753,70.992,-51.774,71.01,-51.65,71.019,-51.528,71.014,-51.267,70.977,-51.13,70.972,-51.03,70.986,-51.019,71.001,-51.178,71.043,-51.377,71.119,-51.792,71.13,-52.061,71.122,-52.234,71.148,-52.417,71.19,-52.535,71.2,-52.775,71.174,-52.897,71.171,-53.008,71.18,-53.117,71.313,-53.088,71.353,-53.002,71.37,-52.937,71.413,-52.892,71.458,-52.749,71.502,-51.967,71.599,-51.77,71.672,-51.779,71.683,-51.912,71.669,-52.082,71.637,-52.196,71.63,-52.656,71.672,-52.728,71.663,-52.915,71.602,-53.168,71.536,-53.284,71.54,-53.44,71.579,-53.465,71.607,-53.476,71.64,-53.305,71.686,-53.25,71.71,-53.139,71.775,-53.145,71.807,-53.334,71.79,-53.358,71.82,-53.355,71.871,-53.374,71.936,-53.42,72,-53.575,72.098,-53.64,72.123,-53.693,72.16,-53.81,72.293,-53.776,72.326,-53.672,72.351,-53.652,72.363,-53.901,72.342,-53.928,72.319,-53.881,72.285,-53.847,72.24,-53.828,72.183,-53.793,72.134,-53.703,72.08,-53.631,72.052,-53.514,71.976,-53.462,71.894,-53.478,71.85,-53.569,71.806,-53.715,71.758,-53.76,71.718,-53.78,71.679,-53.894,71.642,-53.964,71.656,-54.02,71.658,-53.954,71.593,-53.912,71.526,-53.963,71.459,-54.099,71.419,-54.173,71.417,-54.318,71.384,-54.689,71.367,-54.818,71.375,-55.055,71.409,-55.336,71.427,-55.448,71.472,-55.594,71.554,-55.668,71.627,-55.669,71.692,-55.63,71.739,-55.549,71.768,-55.452,71.958,-55.316,72.111,-54.971,72.268,-54.873,72.325,-54.84,72.356,-54.841,72.379,-54.896,72.394,-55.32,72.2,-55.581,72.179,-55.659,72.223,-55.636,72.3,-55.589,72.319,-55.378,72.311,-55.296,72.354,-55.428,72.42,-55.569,72.437,-55.602,72.453,-55.457,72.503,-55.122,72.5,-55.046,72.534,-54.925,72.572,-54.79,72.642,-54.74,72.7,-54.729,72.75,-54.758,72.791,-54.761,72.832,-54.738,72.873,-54.773,72.918,-54.866,72.967,-55.073,73.015,-55.134,72.961,-55.198,72.938,-55.289,72.933,-55.372,72.956,-55.46,72.964,-55.545,72.985,-55.634,72.991,-55.669,73.008,-55.691,73.054,-55.693,73.113,-55.592,73.14,-55.452,73.162,-55.359,73.203,-55.297,73.262,-55.288,73.327,-55.332,73.397,-55.446,73.46,-55.656,73.399,-55.739,73.384,-55.758,73.428,-55.787,73.46,-55.876,73.505,-55.992,73.537,-56.104,73.558,-56.109,73.591,-56.083,73.627,-56.033,73.67,-55.968,73.76,-55.897,73.752,-55.838,73.76,-55.872,73.833,-55.929,73.895,-55.997,73.931,-55.999,73.946,-56.014,73.964,-56.066,74.007,-56.124,74.039,-56.225,74.129,-56.298,74.163,-56.392,74.181,-56.493,74.182,-56.655,74.159,-56.954,74.131,-57.191,74.118,-57.231,74.125,-57.112,74.159,-56.937,74.195,-56.706,74.219,-56.639,74.278,-56.664,74.33,-56.654,74.378,-56.718,74.429,-56.656,74.458,-56.446,74.486,-56.35,74.49,-56.255,74.527,-56.522,74.614,-56.801,74.672,-56.871,74.695,-56.933,74.733,-56.986,74.787,-57.072,74.84,-57.191,74.894,-57.365,74.945,-57.813,75.04,-57.967,75.105,-58.109,75.205,-58.18,75.247,-58.253,75.279,-58.566,75.353,-58.603,75.385,-58.281,75.472,-58.25,75.507,-58.381,75.612,-58.516,75.689,-58.663,75.716,-58.881,75.73,-59.082,75.765,-59.264,75.819,-59.445,75.859,-59.717,75.896,-60.173,75.993,-60.875,76.097,-61.188,76.158,-61.375,76.18,-61.621,76.186,-62.097,76.242,-62.496,76.26,-62.743,76.252,-62.823,76.262,-63.006,76.319,-63.291,76.352,-63.439,76.339,-63.622,76.278,-63.843,76.217,-63.96,76.209,-64.135,76.265,-64.223,76.303,-64.307,76.317,-64.387,76.304,-64.543,76.253,-64.692,76.216,-64.912,76.173,-65.088,76.152,-65.313,76.146,-65.37,76.131,-65.457,76.13,-65.574,76.144,-65.683,76.173,-65.785,76.215,-65.876,76.238,-65.954,76.242,-66.134,76.22,-66.362,76.155,-66.466,76.139,-66.553,76.146,-66.66,76.166,-66.874,76.218,-66.993,76.213,-67.079,76.195,-67.055,76.152,-66.854,76.05,-66.675,75.977,-66.826,75.969,-68.149,76.067,-68.317,76.091,-68.561,76.15,-68.763,76.187,-69.108,76.281,-69.373,76.332,-69.461,76.372,-69.484,76.399,-69.4,76.436,-68.865,76.561,-68.661,76.587,-68.245,76.617,-68.147,76.636,-68.114,76.651,-68.223,76.678,-68.767,76.668,-69.252,76.686,-69.674,76.736,-69.747,76.752,-69.819,76.783,-69.888,76.827,-69.872,76.877,-69.771,76.931,-69.712,76.969,-69.694,76.989,-70.224,76.855,-70.441,76.807,-70.613,76.822,-70.734,76.844,-70.793,76.869,-70.791,76.896,-70.771,76.917,-70.735,76.929,-71.015,76.985,-71.141,77.029,-71.155,77.074,-71.055,77.121,-70.958,77.154,-70.863,77.175,-70.604,77.194,-69.657,77.229,-68.978,77.195,-68.747,77.307,-68.592,77.343,-68.136,77.38,-67.434,77.385,-66.938,77.364,-66.706,77.338,-66.389,77.28,-66.371,77.298,-66.448,77.35,-66.453,77.393,-66.325,77.468,-66.266,77.515,-66.306,77.565,-66.445,77.616,-66.691,77.681,-66.824,77.687,-66.971,77.671,-67.147,77.635,-67.515,77.543,-67.688,77.524,-67.977,77.519,-68.137,77.53,-68.292,77.544,-68.533,77.593,-68.622,77.602,-68.728,77.581,-68.854,77.529,-68.975,77.493,-69.091,77.472,-69.2,77.463,-69.351,77.467,-69.977,77.548,-70.118,77.583,-70.126,77.638,-70.318,77.69,-70.535,77.7,-70.562,77.717,-70.287,77.798,-70.081,77.831,-70.114,77.841,-70.412,77.843,-70.614,77.8,-70.729,77.793,-70.994,77.792,-71.272,77.813,-71.39,77.832,-71.512,77.875,-71.65,77.9,-72.065,77.937,-72.159,77.957,-72.247,77.99,-72.586,78.085,-72.792,78.155,-72.818,78.194,-72.581,78.279,-72.571,78.299,-72.672,78.335,-72.715,78.362,-72.68,78.4,-72.473,78.482,-72.396,78.504,-72.024,78.553,-71.651,78.623,-71.516,78.639,-71.395,78.643,-70.906,78.638,-70.754,78.656,-70.625,78.69,-70.414,78.725,-69.974,78.778,-68.993,78.857,-68.93,78.867,-68.924,78.882,-69.012,78.923,-69.031,78.943,-68.83,78.98,-68.377,79.038,-68.068,79.066,-67.868,79.068,-67.708,79.08,-67.482,79.117,-67.355,79.123,-66.584,79.138,-66.243,79.118,-66.075,79.118,-65.968,79.132,-65.826,79.174,-65.56,79.276,-65.42,79.34,-65.288,79.437,-65.117,79.589,-64.989,79.737,-64.905,79.881,-64.839,79.969,-64.792,80.001,-64.632,80.041,-64.466,80.072,-64.179,80.099,-64.205,80.112,-64.327,80.134,-64.44,80.142,-64.545,80.137,-64.735,80.104,-64.982,80.082,-65.222,80.086,-65.395,80.078,-65.553,80.048,-65.81,80.024,-65.982,80.029,-66.292,80.072,-66.448,80.08,-66.844,80.076,-66.959,80.092,-67.061,80.123,-67.141,80.166,-67.201,80.222,-67.193,80.28,-67.051,80.385,-66.996,80.413,-66.61,80.53,-66.372,80.584,-66.136,80.625,-65.963,80.649,-65.801,80.66,-65.645,80.685,-65.358,80.767,-65.062,80.836,-64.694,80.966,-64.516,81,-63.892,81.056,-63.722,81.057,-63.578,81.043,-63.442,81.014,-63.059,80.886,-63.029,80.89,-63.095,80.938,-63.235,81.083,-63.212,81.143,-62.993,81.207,-62.903,81.218,-62.672,81.214,-62.299,81.194,-62.049,81.173,-61.86,81.138,-61.636,81.116,-61.519,81.117,-61.436,81.134,-61.317,81.188,-61.162,81.281,-61.1,81.396,-61.131,81.532,-61.176,81.632,-61.236,81.695,-61.203,81.747,-61.015,81.81,-60.843,81.855,-60.432,81.92,-60.099,81.937,-59.902,81.933,-59.642,81.903,-59.282,81.884,-58.957,81.825,-58.43,81.69,-58.08,81.622,-57.79,81.592,-57.505,81.54,-57.083,81.43,-56.862,81.383,-56.731,81.366,-56.615,81.363,-56.658,81.394,-56.86,81.46,-57.168,81.532,-57.853,81.662,-58.23,81.754,-58.568,81.858,-58.817,81.92,-59.268,81.982,-59.262,82.007,-58.717,82.093,-57.717,82.168,-56.589,82.227,-56.212,82.221,-55.549,82.246,-55.486,82.283,-55.344,82.3,-54.726,82.351,-54.549,82.351,-54.277,82.326,-53.987,82.279,-53.853,82.237,-53.671,82.164,-53.582,82.062,-53.596,81.738,-53.591,81.677,-53.556,81.653,-53.43,81.688,-53.28,81.754,-53.145,81.8,-53.041,81.871,-52.969,81.967,-52.926,82.038,-53.102,82.119,-53.111,82.251,-53.023,82.322,-52.776,82.322,-51.754,82.078,-51.352,82.026,-50.894,81.895,-50.36,81.909,-49.867,81.893,-49.649,81.898,-49.541,81.918,-49.694,81.972,-50.395,82.121,-50.713,82.237,-50.936,82.383,-50.99,82.46,-50.82,82.474,-50.037,82.472,-48.861,82.405,-47.357,82.174,-46.617,82.097,-45.291,81.829,-44.891,81.788,-44.729,81.78,-44.608,81.813,-44.532,81.849,-44.527,81.897,-44.591,81.957,-44.628,82.026,-44.637,82.104,-44.547,82.26,-44.333,82.311,-44.239,82.368,-44.327,82.472,-44.577,82.543,-45.552,82.725,-45.557,82.747,-45.36,82.771,-45.067,82.785,-42.651,82.741,-42.233,82.725,-42.055,82.71,-41.977,82.689,-41.876,82.68,-41.357,82.705,-41.37,82.75,-41.434,82.779,-44.239,82.857,-44.762,82.884,-45.028,82.886,-45.303,82.865,-45.873,82.855,-46.137,82.859,-46.478,82.952,-46.169,83.064,-45.909,83.061,-45.415,83.018,-45.122,83.079,-44.657,83.129,-44.197,83.147,-43.195,83.255,-43.009,83.265,-42.776,83.259,-42.26,83.232,-42.055,83.205,-41.82,83.148,-41.683,83.13,-41.522,83.127,-41.3,83.101,-40.979,83.185,-40.689,83.275,-40.357,83.332,-39.886,83.299,-39.588,83.256,-39.316,83.204,-38.931,83.175,-38.278,82.999,-38.156,82.999,-38.099,83.014,-38.037,83.046,-38.015,83.095,-37.935,83.161,-37.993,83.185,-38.54,83.258,-38.643,83.286,-38.748,83.333,-38.75,83.371,-38.648,83.401,-38.541,83.415,-38.188,83.402,-38.071,83.412,-37.961,83.438,-37.828,83.486,-37.723,83.498,-37.487,83.499,-37.123,83.468,-36.804,83.466,-36.69,83.48,-36.672,83.51,-36.644,83.529,-36.606,83.537,-35.452,83.539,-35.166,83.546,-34.942,83.568,-34.668,83.571,-34.428,83.558,-34.132,83.529,-33.837,83.53,-33.398,83.577,-32.984,83.6,-30.703,83.593,-29.953,83.565]],[[-52.731,69.945,-52.398,69.863,-52.045,69.807,-52.011,69.782,-51.983,69.743,-51.977,69.722,-51.985,69.704,-52.007,69.686,-51.982,69.664,-51.908,69.637,-51.9,69.605,-51.988,69.55,-52.113,69.489,-52.77,69.364,-53.003,69.343,-53.578,69.257,-53.754,69.26,-53.793,69.264,-53.902,69.302,-54.051,69.337,-54.121,69.364,-54.183,69.404,-54.158,69.428,-54.047,69.437,-53.89,69.437,-53.658,69.465,-53.722,69.491,-53.783,69.506,-53.825,69.54,-53.921,69.534,-53.994,69.553,-54.133,69.565,-54.497,69.577,-54.734,69.611,-54.804,69.631,-54.866,69.665,-54.919,69.714,-54.841,69.902,-54.788,69.95,-54.665,69.966,-54.363,69.924,-54.323,69.942,-54.652,70.011,-54.774,70.053,-54.809,70.085,-54.831,70.133,-54.83,70.161,-54.816,70.189,-54.786,70.218,-54.706,70.256,-54.372,70.317,-54.007,70.296,-53.375,70.221,-53.297,70.205,-53.103,70.141,-52.731,69.945]],[[-51.014,69.552,-51.17,69.517,-51.202,69.525,-51.234,69.552,-51.315,69.674,-51.339,69.732,-51.319,69.804,-51.35,69.855,-51.209,69.914,-51.095,69.924,-50.94,69.909,-50.679,69.849,-50.698,69.829,-50.754,69.798,-50.912,69.757,-50.967,69.664,-50.978,69.618,-50.97,69.583,-51.014,69.552]],[[-53.535,71.041,-53.629,71.034,-53.898,71.085,-53.941,71.104,-53.958,71.128,-53.947,71.156,-53.862,71.207,-53.701,71.283,-53.584,71.297,-53.512,71.25,-53.441,71.186,-53.432,71.153,-53.437,71.115,-53.455,71.083,-53.488,71.056,-53.535,71.041]],[[-55.017,72.791,-55.158,72.723,-55.274,72.684,-55.524,72.568,-55.567,72.564,-55.634,72.579,-55.687,72.61,-55.781,72.617,-55.814,72.636,-55.827,72.652,-55.869,72.662,-55.936,72.668,-56.043,72.656,-56.141,72.668,-56.215,72.719,-56.078,72.753,-55.994,72.782,-55.666,72.794,-55.574,72.78,-55.516,72.781,-55.428,72.789,-55.235,72.825,-55.206,72.842,-55.033,72.821,-55.017,72.791]],[[-71.667,77.325,-72.024,77.316,-72.374,77.355,-72.495,77.386,-72.49,77.432,-72.436,77.448,-72.247,77.464,-72.089,77.467,-71.983,77.46,-71.733,77.432,-71.552,77.403,-71.433,77.394,-71.467,77.354,-71.667,77.325]],[[-44.865,82.084,-45.067,82.066,-45.491,82.172,-46.161,82.278,-46.752,82.348,-47.308,82.533,-47.351,82.599,-47.272,82.657,-46.787,82.666,-46.399,82.692,-45.411,82.578,-44.917,82.481,-44.75,82.401,-44.776,82.242,-44.865,82.084]],[[-25.432,70.921,-25.397,70.862,-25.394,70.835,-25.401,70.811,-25.421,70.795,-25.468,70.78,-25.38,70.741,-25.352,70.714,-25.346,70.693,-25.402,70.653,-25.801,70.599,-25.911,70.573,-26.05,70.509,-26.218,70.454,-26.274,70.454,-26.339,70.511,-26.605,70.553,-27.105,70.531,-27.69,70.479,-27.898,70.454,-28.003,70.467,-28.035,70.487,-28.037,70.514,-27.968,70.595,-27.94,70.615,-27.805,70.642,-27.714,70.713,-27.744,70.79,-27.709,70.897,-27.617,70.914,-27.387,70.876,-27.239,70.868,-26.976,70.863,-26.622,70.876,-26.337,70.919,-25.819,71.044,-25.727,71.042,-25.661,70.998,-25.612,70.976,-25.458,70.943,-25.432,70.921]],[[-18.665,81.846,-18.768,81.814,-19.031,81.827,-19.369,81.917,-19.594,81.991,-19.611,82.078,-19.495,82.117,-19.315,82.123,-19.067,82.049,-18.813,81.949,-18.665,81.846]],[[-17.612,79.826,-18.036,79.711,-18.662,79.72,-19.032,79.773,-19.138,79.852,-18.997,79.94,-18.547,80.011,-17.983,80.055,-17.471,80.029,-17.401,79.94,-17.612,79.826]],[[-18.997,77.974,-19.129,77.939,-19.218,78.044,-19.297,78.185,-19.315,78.344,-19.112,78.424,-19.006,78.441,-18.935,78.424,-18.953,78.353,-18.953,78.212,-18.882,78.115,-18.997,77.974]],[[-18.583,76.042,-18.697,76.016,-19.085,76.43,-19.085,76.58,-19.059,76.695,-18.882,76.704,-18.733,76.642,-18.662,76.404,-18.583,76.042]],[[-18.001,75.407,-17.921,75.302,-17.886,75.204,-17.762,75.143,-17.498,75.152,-17.392,75.037,-17.586,74.993,-18.353,75.01,-18.671,75.002,-18.891,75.072,-18.882,75.196,-18.856,75.319,-18.636,75.39,-18.45,75.328,-18.23,75.372,-18.001,75.407]],[[-17.954,77.642,-18.148,77.642,-18.22,77.668,-18.174,77.714,-17.904,77.863,-17.814,77.875,-17.681,77.859,-17.641,77.782,-17.729,77.706,-17.954,77.642]],[[-37.031,65.532,-37.187,65.531,-37.238,65.61,-37.223,65.695,-37.048,65.722,-36.953,65.663,-36.987,65.576,-37.031,65.532]],[[-46.267,60.781,-46.382,60.66,-46.496,60.687,-46.553,60.741,-46.666,60.766,-46.788,60.758,-46.79,60.78,-46.394,60.909,-46.205,60.944,-46.219,60.889,-46.254,60.842,-46.267,60.781]],[[-51.675,70.855,-51.809,70.853,-52.119,70.871,-52.144,70.882,-52.148,70.904,-52.107,70.968,-51.97,70.976,-51.807,70.942,-51.631,70.892,-51.607,70.869,-51.675,70.855]]]},{"n":"Faroe Islands","c":"FO","r":6,"ml":4,"lx":-7.06,"ly":62.19,"b":[-7.423,61.414,-6.406,62.356],"p":[[[-6.623,61.806,-6.643,61.768,-6.67,61.769,-6.764,61.815,-6.839,61.841,-6.864,61.862,-6.885,61.899,-6.842,61.904,-6.791,61.895,-6.662,61.862,-6.626,61.827,-6.623,61.806]],[[-6.699,61.445,-6.68,61.414,-6.703,61.418,-6.771,61.452,-6.888,61.535,-6.929,61.603,-6.935,61.634,-6.906,61.631,-6.882,61.603,-6.77,61.584,-6.741,61.571,-6.741,61.536,-6.704,61.496,-6.699,61.445]],[[-7.187,62.139,-7.097,62.101,-7.065,62.073,-7.117,62.047,-7.179,62.04,-7.255,62.046,-7.379,62.075,-7.423,62.14,-7.337,62.139,-7.235,62.151,-7.187,62.139]],[[-6.631,62.228,-6.656,62.094,-6.696,62.094,-6.769,62.131,-6.823,62.139,-6.841,62.119,-6.838,62.095,-6.809,62.08,-6.723,61.99,-6.714,61.964,-6.725,61.951,-6.81,61.977,-7.014,62.094,-7.172,62.286,-6.959,62.316,-6.804,62.266,-6.631,62.228]],[[-6.406,62.259,-6.454,62.187,-6.525,62.198,-6.544,62.206,-6.559,62.225,-6.552,62.278,-6.555,62.356,-6.473,62.292,-6.406,62.259]]]},{"n":"Denmark","c":"DK","r":4,"ml":3,"lx":9.02,"ly":55.97,"b":[8.121,54.629,15.137,57.737],"p":[[[12.569,55.785,12.571,55.685,12.545,55.656,12.507,55.637,12.407,55.616,12.321,55.588,12.243,55.538,12.215,55.467,12.275,55.414,12.385,55.386,12.413,55.286,12.322,55.237,12.09,55.188,12.066,55.07,12.073,54.977,12.069,54.909,12.05,54.815,11.862,54.773,11.741,54.915,11.74,54.972,11.704,55.039,11.697,55.096,11.654,55.187,11.476,55.212,11.407,55.215,11.31,55.198,11.286,55.204,11.171,55.329,11.19,55.466,11.128,55.535,11.12,55.566,11.121,55.601,11.07,55.629,11.009,55.644,10.979,55.722,11.05,55.74,11.224,55.731,11.275,55.736,11.322,55.753,11.464,55.879,11.46,55.907,11.475,55.943,11.628,55.957,11.696,55.908,11.682,55.829,11.691,55.729,11.784,55.702,11.82,55.698,11.858,55.772,11.885,55.808,11.922,55.828,11.935,55.896,11.913,55.937,11.866,55.968,12.04,56.052,12.219,56.119,12.323,56.122,12.428,56.106,12.526,56.083,12.579,56.064,12.608,56.033,12.543,55.959,12.525,55.918,12.569,55.785]],[[9.74,54.826,9.725,54.826,9.661,54.834,9.616,54.855,9.499,54.84,9.342,54.806,9.255,54.808,9.186,54.845,8.903,54.897,8.857,54.901,8.671,54.903,8.67,54.903,8.661,54.986,8.638,55.046,8.573,55.134,8.67,55.156,8.651,55.329,8.616,55.418,8.345,55.51,8.132,55.6,8.181,55.901,8.202,55.982,8.121,56.14,8.13,56.321,8.164,56.607,8.232,56.618,8.281,56.617,8.473,56.565,8.553,56.56,8.608,56.515,8.672,56.496,8.718,56.544,8.736,56.627,8.888,56.735,8.995,56.775,9.067,56.794,9.14,56.75,9.196,56.702,9.21,56.808,9.255,57.012,9.11,57.044,8.993,57.016,8.876,56.887,8.772,56.725,8.603,56.71,8.468,56.665,8.347,56.712,8.268,56.754,8.266,56.815,8.284,56.852,8.427,56.984,8.619,57.111,8.812,57.11,8.952,57.151,9.036,57.155,9.299,57.147,9.434,57.174,9.554,57.232,9.815,57.478,9.962,57.581,10.259,57.617,10.533,57.735,10.61,57.737,10.481,57.649,10.46,57.615,10.445,57.562,10.537,57.449,10.518,57.379,10.524,57.243,10.437,57.172,10.338,57.021,10.296,56.999,10.287,56.823,10.297,56.781,10.283,56.621,10.384,56.555,10.49,56.521,10.846,56.522,10.883,56.493,10.926,56.443,10.894,56.359,10.856,56.296,10.753,56.242,10.621,56.202,10.539,56.2,10.427,56.276,10.374,56.252,10.319,56.213,10.227,56.005,10.183,55.865,10.159,55.854,10.107,55.874,10.017,55.876,9.904,55.843,9.962,55.813,10.024,55.761,9.999,55.736,9.899,55.708,9.81,55.651,9.773,55.608,9.661,55.557,9.591,55.493,9.626,55.414,9.64,55.344,9.671,55.266,9.643,55.205,9.505,55.116,9.454,55.04,9.572,55.041,9.645,55.023,9.688,55,9.732,54.968,9.705,54.928,9.74,54.826]],[[10.645,55.61,10.687,55.558,10.738,55.446,10.819,55.322,10.785,55.27,10.808,55.203,10.785,55.133,10.624,55.052,10.443,55.049,10.255,55.088,9.989,55.163,9.967,55.205,9.93,55.229,9.859,55.357,9.861,55.515,9.994,55.535,10.286,55.611,10.354,55.599,10.424,55.56,10.505,55.558,10.623,55.613,10.645,55.61]],[[11.361,54.892,11.538,54.83,11.658,54.833,11.74,54.807,11.759,54.768,11.766,54.679,11.68,54.654,11.586,54.662,11.457,54.629,11.036,54.773,11.042,54.893,11.059,54.941,11.258,54.952,11.361,54.892]],[[10.734,54.751,10.69,54.745,10.629,54.826,10.622,54.851,10.692,54.903,10.738,54.962,10.857,55.052,10.925,55.158,10.951,55.156,10.921,55.062,10.765,54.8,10.734,54.751]],[[12.549,54.966,12.511,54.951,12.358,54.962,12.184,54.892,12.119,54.914,12.144,54.959,12.162,54.975,12.22,54.994,12.259,55.021,12.274,55.064,12.31,55.041,12.417,55.031,12.47,55.017,12.513,54.997,12.549,54.966]],[[12.666,55.597,12.572,55.554,12.551,55.556,12.52,55.615,12.57,55.65,12.599,55.68,12.62,55.679,12.648,55.647,12.666,55.597]],[[10.484,54.848,10.417,54.837,10.341,54.859,10.216,54.941,10.2,54.963,10.266,54.949,10.347,54.906,10.414,54.897,10.505,54.861,10.484,54.848]],[[10.061,54.886,9.957,54.872,9.904,54.897,9.806,54.906,9.771,55.06,9.781,55.069,9.83,55.058,9.999,54.986,10.058,54.908,10.061,54.886]],[[10.607,55.783,10.59,55.765,10.527,55.784,10.52,55.848,10.544,55.907,10.516,55.959,10.547,55.992,10.636,55.914,10.662,55.878,10.627,55.834,10.607,55.783]],[[11.052,57.253,11.011,57.229,10.874,57.262,10.935,57.309,11.086,57.33,11.175,57.323,11.077,57.277,11.052,57.253]],[[15.088,55.022,15.051,55.005,14.886,55.033,14.684,55.102,14.714,55.238,14.765,55.297,15.133,55.145,15.137,55.087,15.088,55.022]]]},{"n":"Czech Republic","c":"CZ","r":5,"ml":4,"lx":15.38,"ly":49.88,"b":[12.09,48.576,18.832,51.038],"p":[[[18.832,49.511,18.807,49.509,18.75,49.494,18.676,49.488,18.596,49.491,18.535,49.465,18.476,49.421,18.416,49.391,18.383,49.364,18.365,49.336,18.161,49.257,18.133,49.225,18.11,49.18,18.1,49.119,18.086,49.065,18.051,49.037,17.941,49.012,17.913,48.999,17.893,48.971,17.831,48.929,17.758,48.888,17.625,48.842,17.483,48.828,17.297,48.843,17.188,48.861,17.136,48.841,17.063,48.781,16.985,48.677,16.953,48.599,16.928,48.621,16.884,48.704,16.833,48.714,16.764,48.722,16.713,48.734,16.601,48.782,16.544,48.796,16.478,48.8,16.415,48.772,16.367,48.739,16.219,48.739,16.057,48.755,15.825,48.864,15.765,48.865,15.701,48.86,15.599,48.886,15.403,48.957,15.311,48.974,15.253,48.964,15.2,48.948,15.162,48.946,15.14,48.969,15.067,48.998,14.993,49.001,14.972,48.984,14.947,48.828,14.923,48.771,14.822,48.774,14.786,48.747,14.707,48.672,14.691,48.599,14.554,48.613,14.489,48.626,14.431,48.616,14.368,48.576,14.19,48.579,14.049,48.602,13.989,48.692,13.924,48.728,13.843,48.76,13.815,48.767,13.77,48.816,13.685,48.877,13.548,48.96,13.441,48.956,13.401,48.978,13.384,49.008,13.339,49.061,13.289,49.097,13.228,49.112,13.141,49.158,13.024,49.26,12.917,49.33,12.813,49.329,12.748,49.366,12.681,49.415,12.632,49.461,12.556,49.575,12.5,49.64,12.457,49.68,12.408,49.713,12.391,49.74,12.45,49.8,12.472,49.83,12.498,49.853,12.513,49.877,12.512,49.896,12.458,49.956,12.384,49.999,12.276,50.042,12.208,50.098,12.183,50.148,12.175,50.176,12.128,50.213,12.09,50.269,12.09,50.302,12.099,50.311,12.135,50.311,12.175,50.288,12.231,50.245,12.277,50.181,12.306,50.206,12.359,50.273,12.453,50.35,12.549,50.393,12.636,50.397,12.706,50.409,12.765,50.431,12.868,50.422,12.943,50.406,12.967,50.416,12.997,50.456,13.016,50.49,13.181,50.51,13.238,50.577,13.27,50.576,13.306,50.586,13.341,50.611,13.375,50.622,13.401,50.609,13.436,50.601,13.473,50.617,13.527,50.693,13.557,50.705,13.701,50.717,13.899,50.761,13.998,50.801,14.096,50.823,14.202,50.861,14.369,50.899,14.377,50.914,14.299,50.953,14.273,50.977,14.256,51.002,14.283,51.029,14.32,51.038,14.367,51.026,14.507,51.01,14.546,50.994,14.56,50.955,14.595,50.919,14.624,50.915,14.614,50.856,14.658,50.833,14.723,50.815,14.767,50.818,14.797,50.842,14.809,50.859,14.896,50.861,14.983,50.887,14.99,50.927,14.984,51.003,14.994,51.014,15.126,50.993,15.259,50.959,15.277,50.883,15.313,50.846,15.354,50.812,15.395,50.796,15.464,50.794,15.644,50.749,15.731,50.74,15.819,50.709,15.894,50.677,15.949,50.67,15.974,50.635,16.007,50.612,16.066,50.63,16.282,50.656,16.36,50.621,16.413,50.585,16.42,50.574,16.392,50.542,16.379,50.517,16.357,50.5,16.283,50.483,16.241,50.455,16.21,50.424,16.231,50.394,16.291,50.372,16.334,50.367,16.35,50.345,16.488,50.248,16.597,50.122,16.639,50.102,16.679,50.097,16.725,50.116,16.779,50.157,16.842,50.187,16.895,50.202,16.99,50.237,16.993,50.26,16.915,50.345,16.869,50.415,16.88,50.427,16.981,50.416,17.152,50.378,17.415,50.255,17.462,50.255,17.555,50.264,17.655,50.284,17.702,50.307,17.72,50.299,17.735,50.231,17.709,50.194,17.589,50.157,17.596,50.14,17.627,50.116,17.681,50.101,17.747,50.057,17.792,50.007,17.831,49.983,17.875,49.972,17.984,49.999,18.015,50.02,18.028,50.035,18.05,50.032,18.088,50.007,18.099,49.993,18.205,49.965,18.266,49.93,18.305,49.914,18.348,49.93,18.516,49.902,18.562,49.879,18.577,49.841,18.569,49.818,18.595,49.758,18.807,49.614,18.829,49.54,18.832,49.511]]]},{"n":"Turkish Republic of Northern Cyprus","c":"","r":6,"ml":6,"lx":33.69,"ly":35.22,"b":[32.713,35,34.556,35.662],"p":[[[34.004,35.065,33.966,35.057,33.903,35.085,33.866,35.094,33.832,35.067,33.792,35.048,33.757,35.04,33.726,35.037,33.675,35.018,33.614,35.023,33.526,35.039,33.476,35,33.464,35.005,33.456,35.101,33.424,35.141,33.384,35.163,33.326,35.154,33.248,35.157,33.191,35.173,33.078,35.146,32.986,35.116,32.92,35.088,32.869,35.089,32.784,35.116,32.72,35.145,32.713,35.171,32.772,35.16,32.88,35.181,32.926,35.278,32.942,35.39,33.123,35.358,33.308,35.342,33.459,35.336,33.608,35.354,34.063,35.474,34.192,35.546,34.272,35.57,34.411,35.629,34.556,35.662,34.463,35.594,33.942,35.292,33.908,35.202,33.931,35.14,34.004,35.065]]]},{"n":"Cyprus","c":"CY","r":5,"ml":4.5,"lx":33.08,"ly":34.91,"b":[32.301,34.57,34.05,35.183],"p":[[[32.713,35.171,32.72,35.145,32.784,35.116,32.869,35.089,32.92,35.088,32.986,35.116,33.078,35.146,33.191,35.173,33.248,35.157,33.326,35.154,33.384,35.163,33.424,35.141,33.456,35.101,33.464,35.005,33.476,35,33.526,35.039,33.614,35.023,33.675,35.018,33.726,35.037,33.757,35.04,33.792,35.048,33.832,35.067,33.866,35.094,33.903,35.085,33.966,35.057,34.004,35.065,34.024,35.046,34.05,34.988,33.937,34.971,33.822,34.966,33.759,34.973,33.699,34.97,33.514,34.806,33.415,34.751,33.297,34.718,33.176,34.698,33.116,34.696,33.062,34.675,33.025,34.637,33.024,34.6,33.008,34.57,32.942,34.576,32.914,34.635,32.867,34.661,32.75,34.648,32.693,34.649,32.506,34.706,32.449,34.729,32.414,34.778,32.317,34.953,32.301,35.083,32.391,35.05,32.475,35.09,32.556,35.156,32.652,35.183,32.713,35.171]]]},{"n":"Cuba","c":"CU","r":3,"ml":2.7,"lx":-77.98,"ly":21.33,"b":[-84.887,19.855,-74.137,23.19],"p":[[[-81.837,23.163,-81.575,23.117,-81.364,23.13,-81.262,23.157,-81.272,23.129,-81.179,23.06,-81.145,23.055,-81.008,23.09,-80.65,23.103,-80.613,23.084,-80.55,23.017,-80.459,22.975,-80.365,22.943,-80.266,22.935,-80.168,22.949,-80.075,22.942,-79.96,22.877,-79.924,22.869,-79.82,22.887,-79.851,22.827,-79.677,22.743,-79.549,22.578,-79.456,22.51,-79.358,22.449,-79.276,22.408,-79.183,22.388,-78.902,22.396,-78.835,22.391,-78.776,22.367,-78.719,22.358,-78.686,22.367,-78.143,22.109,-77.971,21.972,-77.865,21.901,-77.637,21.797,-77.545,21.775,-77.497,21.788,-77.507,21.811,-77.573,21.868,-77.583,21.889,-77.497,21.872,-77.342,21.755,-77.3,21.712,-77.222,21.672,-77.144,21.644,-77.181,21.598,-77.245,21.594,-77.366,21.613,-77.27,21.538,-77.253,21.483,-77.208,21.479,-77.141,21.539,-77.099,21.589,-76.928,21.459,-76.836,21.4,-76.86,21.365,-76.867,21.33,-76.765,21.362,-76.726,21.359,-76.689,21.34,-76.647,21.285,-76.552,21.272,-76.455,21.274,-76.259,21.227,-76.074,21.133,-75.899,21.114,-75.723,21.111,-75.634,21.061,-75.596,20.995,-75.639,20.947,-75.663,20.898,-75.597,20.838,-75.74,20.812,-75.76,20.776,-75.753,20.736,-75.725,20.715,-75.643,20.733,-75.525,20.717,-75.338,20.702,-75.213,20.714,-74.96,20.673,-74.883,20.651,-74.732,20.573,-74.662,20.522,-74.513,20.385,-74.384,20.33,-74.273,20.317,-74.234,20.326,-74.198,20.311,-74.167,20.292,-74.137,20.232,-74.154,20.169,-74.217,20.117,-74.253,20.08,-74.412,20.075,-74.635,20.058,-74.85,20.002,-74.955,19.958,-75.003,19.929,-75.116,19.901,-75.124,19.925,-75.122,19.954,-75.152,20.008,-75.177,19.959,-75.219,19.924,-75.29,19.893,-75.552,19.891,-75.657,19.932,-75.765,19.96,-76.158,19.99,-76.253,19.987,-76.516,19.957,-76.78,19.94,-76.89,19.921,-76.999,19.893,-77.212,19.894,-77.463,19.861,-77.715,19.855,-77.554,20.082,-77.213,20.3,-77.149,20.347,-77.104,20.408,-77.093,20.453,-77.108,20.492,-77.189,20.56,-77.205,20.611,-77.23,20.644,-77.348,20.672,-77.467,20.69,-77.593,20.69,-77.857,20.714,-77.997,20.715,-78.116,20.762,-78.314,20.927,-78.406,20.974,-78.454,21.011,-78.491,21.054,-78.537,21.297,-78.577,21.414,-78.636,21.516,-78.728,21.593,-78.823,21.619,-79.189,21.553,-79.274,21.563,-79.357,21.585,-79.91,21.743,-80.138,21.829,-80.231,21.872,-80.311,21.933,-80.393,22.034,-80.485,22.123,-80.485,22.087,-80.499,22.064,-80.962,22.053,-81.036,22.074,-81.083,22.098,-81.117,22.134,-81.141,22.207,-81.185,22.268,-81.2,22.203,-81.222,22.143,-81.284,22.109,-81.355,22.104,-81.441,22.184,-81.816,22.2,-81.849,22.214,-81.973,22.291,-82.078,22.388,-81.973,22.422,-81.757,22.467,-81.71,22.497,-81.683,22.535,-81.703,22.592,-81.746,22.633,-81.79,22.657,-81.839,22.672,-81.903,22.679,-82.738,22.689,-82.786,22.658,-82.861,22.595,-83.009,22.514,-83.107,22.43,-83.144,22.386,-83.189,22.355,-83.292,22.303,-83.38,22.223,-83.486,22.187,-83.544,22.209,-83.602,22.209,-83.643,22.189,-83.687,22.18,-83.901,22.17,-83.933,22.15,-83.963,22.092,-83.998,21.98,-84.031,21.943,-84.138,21.929,-84.241,21.898,-84.449,21.792,-84.503,21.776,-84.491,21.854,-84.501,21.93,-84.56,21.933,-84.627,21.92,-84.683,21.899,-84.786,21.842,-84.838,21.828,-84.887,21.857,-84.877,21.894,-84.533,22.031,-84.494,22.042,-84.433,22.031,-84.373,22.036,-84.326,22.074,-84.383,22.256,-84.361,22.379,-84.281,22.474,-84.122,22.619,-84.045,22.666,-83.258,22.968,-83.177,22.983,-82.666,23.044,-82.588,23.065,-82.351,23.154,-82.101,23.19,-81.837,23.163]],[[-77.669,21.952,-77.71,21.921,-77.755,21.966,-77.784,21.97,-77.823,21.988,-77.9,22.037,-77.919,22.088,-77.855,22.092,-77.774,22.083,-77.634,22.054,-77.646,21.996,-77.669,21.952]],[[-78.027,22.285,-78.048,22.269,-78.102,22.306,-78.18,22.322,-78.226,22.38,-78.27,22.402,-78.274,22.424,-78.201,22.438,-78.151,22.431,-78.094,22.387,-78.062,22.306,-78.027,22.285]],[[-78.63,22.552,-78.493,22.531,-78.445,22.544,-78.4,22.547,-78.351,22.539,-78.284,22.455,-78.343,22.445,-78.39,22.445,-78.425,22.46,-78.548,22.464,-78.629,22.488,-78.674,22.509,-78.696,22.534,-78.63,22.552]],[[-77.879,22.128,-77.912,22.125,-78.012,22.166,-78.042,22.201,-78.007,22.248,-77.999,22.299,-77.986,22.302,-77.97,22.241,-77.894,22.215,-77.889,22.201,-77.842,22.149,-77.879,22.128]],[[-82.562,21.572,-82.655,21.519,-82.853,21.444,-82.96,21.441,-83.067,21.469,-83.142,21.532,-83.184,21.593,-83.18,21.623,-83.113,21.574,-83.055,21.549,-83.007,21.566,-82.974,21.592,-83.083,21.791,-83.078,21.833,-82.991,21.943,-82.756,21.91,-82.715,21.89,-82.682,21.821,-82.629,21.767,-82.568,21.622,-82.562,21.572]],[[-79.35,22.664,-79.348,22.638,-79.523,22.711,-79.598,22.788,-79.628,22.805,-79.579,22.807,-79.382,22.681,-79.35,22.664]]]},{"n":"Croatia","c":"HR","r":6,"ml":4,"lx":16.37,"ly":45.81,"b":[13.517,42.433,19.401,46.535],"p":[[[13.578,45.517,13.615,45.477,13.879,45.428,13.936,45.45,13.97,45.483,13.97,45.503,13.993,45.509,14.086,45.478,14.161,45.485,14.283,45.487,14.37,45.481,14.427,45.506,14.505,45.595,14.534,45.645,14.569,45.657,14.592,45.651,14.609,45.61,14.65,45.571,14.734,45.508,14.793,45.478,14.847,45.467,14.9,45.493,14.955,45.5,15.11,45.451,15.242,45.441,15.339,45.467,15.327,45.502,15.291,45.542,15.284,45.58,15.29,45.613,15.357,45.646,15.354,45.66,15.273,45.718,15.277,45.733,15.454,45.798,15.625,45.834,15.652,45.862,15.668,45.904,15.676,45.984,15.666,46.048,15.597,46.109,15.593,46.14,15.609,46.172,15.636,46.201,15.704,46.213,15.784,46.234,15.848,46.258,15.933,46.278,16.001,46.305,16.067,46.371,16.106,46.382,16.227,46.373,16.253,46.389,16.237,46.484,16.258,46.508,16.301,46.521,16.321,46.535,16.428,46.524,16.516,46.5,16.57,46.485,16.748,46.416,16.871,46.339,16.94,46.254,17.033,46.187,17.15,46.14,17.242,46.077,17.311,45.996,17.406,45.951,17.529,45.941,17.607,45.914,17.64,45.868,17.706,45.827,17.807,45.79,17.964,45.77,18.264,45.765,18.291,45.764,18.358,45.753,18.437,45.767,18.534,45.796,18.565,45.813,18.666,45.907,18.722,45.899,18.833,45.911,18.9,45.932,18.905,45.932,18.901,45.908,18.894,45.866,18.839,45.836,18.895,45.767,18.947,45.656,18.918,45.601,18.954,45.558,19.055,45.527,19.064,45.515,19.033,45.502,19.008,45.466,19.005,45.4,19.093,45.337,19.273,45.278,19.33,45.268,19.352,45.245,19.382,45.231,19.4,45.213,19.401,45.189,19.388,45.173,19.303,45.167,19.206,45.168,19.137,45.196,19.131,45.175,19.13,45.152,19.063,45.137,19.1,44.974,19.085,44.927,19.061,44.911,19.038,44.918,19.01,44.919,18.996,44.904,19.007,44.869,18.941,44.865,18.836,44.883,18.788,44.915,18.78,44.947,18.779,44.977,18.746,45.027,18.663,45.077,18.488,45.086,18.424,45.102,18.358,45.121,18.285,45.134,18.218,45.133,18.137,45.119,17.996,45.142,17.949,45.112,17.874,45.077,17.813,45.078,17.69,45.158,17.654,45.163,17.546,45.123,17.503,45.12,17.469,45.133,17.324,45.164,17.259,45.171,17.211,45.156,17.125,45.172,16.919,45.277,16.791,45.197,16.531,45.217,16.454,45.162,16.365,45.058,16.293,45.009,16.231,45.027,16.157,45.072,16.028,45.19,15.963,45.211,15.888,45.216,15.823,45.203,15.788,45.179,15.762,45.008,15.738,44.856,15.737,44.766,15.88,44.682,16.049,44.538,16.103,44.521,16.13,44.474,16.17,44.352,16.214,44.215,16.3,44.125,16.378,44.06,16.472,44.003,16.591,43.913,16.688,43.815,16.713,43.779,16.902,43.649,17.085,43.517,17.248,43.47,17.274,43.446,17.275,43.344,17.293,43.306,17.402,43.199,17.625,43.043,17.65,43.007,17.658,42.98,17.643,42.96,17.585,42.938,17.537,42.962,17.33,43.115,17.129,43.211,16.903,43.392,16.6,43.464,16.394,43.543,16.269,43.531,16.131,43.506,16.046,43.506,15.986,43.52,15.943,43.569,15.949,43.607,15.942,43.657,15.821,43.736,15.656,43.811,15.499,43.909,15.186,44.172,15.123,44.257,15.185,44.273,15.231,44.271,15.284,44.289,15.37,44.289,15.471,44.272,15.381,44.328,15.27,44.383,14.981,44.603,14.895,44.707,14.885,44.818,14.907,44.971,14.855,45.081,14.632,45.223,14.55,45.298,14.386,45.342,14.313,45.338,14.269,45.283,14.236,45.16,14.091,44.998,14.042,44.927,13.966,44.836,13.9,44.829,13.861,44.837,13.742,44.992,13.629,45.108,13.613,45.163,13.603,45.231,13.517,45.482,13.578,45.517]],[[16.651,42.997,16.836,42.969,16.971,42.981,17.094,42.964,17.17,42.933,17.188,42.917,17.089,42.915,16.978,42.928,16.851,42.896,16.739,42.913,16.696,42.934,16.666,42.96,16.651,42.997]],[[17.194,43.126,17.124,43.115,16.679,43.123,16.55,43.144,16.406,43.197,16.376,43.214,16.521,43.229,16.656,43.214,16.697,43.175,17.061,43.144,17.194,43.126]],[[16.785,43.271,16.627,43.268,16.49,43.286,16.423,43.317,16.428,43.343,16.449,43.387,16.602,43.382,16.834,43.351,16.891,43.315,16.874,43.298,16.785,43.271]],[[15.231,44.062,15.247,44.027,15.122,44.093,15.075,44.138,15.066,44.158,15.231,44.062]],[[14.81,44.977,14.687,44.956,14.628,44.994,14.613,45.025,14.512,45.035,14.45,45.079,14.438,45.099,14.525,45.147,14.571,45.225,14.63,45.178,14.701,45.09,14.739,45.065,14.81,44.977]],[[14.831,44.759,14.857,44.715,14.763,44.755,14.678,44.77,14.66,44.8,14.672,44.824,14.691,44.848,14.754,44.845,14.764,44.821,14.831,44.759]],[[15.188,44.336,15.163,44.309,15.098,44.358,15.039,44.393,14.996,44.434,14.913,44.486,14.885,44.545,14.76,44.665,14.742,44.697,14.804,44.649,14.855,44.618,14.898,44.611,15.006,44.534,15.113,44.436,15.24,44.35,15.214,44.348,15.188,44.336]],[[15.189,43.922,15.203,43.908,15.202,43.898,15.15,43.912,15.136,43.907,14.891,44.126,14.865,44.168,14.953,44.117,15.189,43.922]],[[15.371,43.974,15.437,43.9,15.374,43.915,15.309,43.961,15.27,44.011,15.371,43.974]],[[14.488,44.66,14.48,44.621,14.42,44.67,14.389,44.758,14.312,44.9,14.303,44.94,14.342,44.98,14.34,45.02,14.286,45.145,14.331,45.165,14.358,45.167,14.369,45.081,14.394,45.031,14.467,44.97,14.453,44.869,14.468,44.725,14.483,44.693,14.488,44.66]],[[17.608,42.769,17.744,42.7,17.344,42.79,17.39,42.799,17.432,42.8,17.608,42.769]],[[18.436,42.56,18.438,42.523,18.477,42.481,18.517,42.433,18.333,42.528,18.161,42.634,17.824,42.797,17.585,42.837,17.258,42.968,17.045,43.015,17.126,43.026,17.22,43.026,17.724,42.851,17.668,42.897,17.74,42.915,17.802,42.902,17.841,42.845,17.919,42.807,18.045,42.741,18.124,42.691,18.304,42.599,18.347,42.587,18.436,42.56]]]},{"n":"Ivory Coast","c":"CI","r":3,"ml":2.5,"lx":-5.57,"ly":7.49,"b":[-8.604,4.351,-2.506,10.724],"p":[[[-3.087,5.128,-3.114,5.089,-3.246,5.114,-3.215,5.147,-3.087,5.128]],[[-7.991,10.163,-7.961,10.163,-7.884,10.186,-7.814,10.237,-7.749,10.342,-7.661,10.427,-7.562,10.421,-7.533,10.437,-7.498,10.44,-7.457,10.384,-7.415,10.341,-7.385,10.34,-7.363,10.259,-7.182,10.226,-7.105,10.204,-7.04,10.145,-7.017,10.143,-6.989,10.156,-6.968,10.176,-6.964,10.199,-6.992,10.252,-6.979,10.3,-6.95,10.342,-6.904,10.345,-6.834,10.357,-6.753,10.357,-6.693,10.349,-6.669,10.392,-6.692,10.512,-6.686,10.578,-6.676,10.634,-6.654,10.656,-6.565,10.586,-6.483,10.561,-6.424,10.559,-6.408,10.572,-6.433,10.649,-6.426,10.672,-6.404,10.685,-6.366,10.693,-6.261,10.724,-6.25,10.718,-6.231,10.598,-6.24,10.558,-6.218,10.476,-6.191,10.4,-6.193,10.369,-6.215,10.322,-6.241,10.279,-6.238,10.262,-6.197,10.232,-6.117,10.202,-6.035,10.195,-5.989,10.239,-5.941,10.275,-5.908,10.307,-5.896,10.355,-5.844,10.39,-5.694,10.433,-5.557,10.44,-5.524,10.426,-5.461,10.36,-5.382,10.314,-5.262,10.32,-5.175,10.293,-5.1,10.242,-5.049,10.128,-4.994,10.046,-4.97,9.93,-4.883,9.869,-4.814,9.841,-4.722,9.757,-4.626,9.714,-4.527,9.723,-4.48,9.679,-4.406,9.648,-4.332,9.646,-4.267,9.743,-4.181,9.782,-3.963,9.86,-3.878,9.895,-3.791,9.917,-3.581,9.924,-3.386,9.9,-3.29,9.882,-3.224,9.895,-3.161,9.849,-3.096,9.752,-3.043,9.721,-2.988,9.687,-2.948,9.611,-2.901,9.535,-2.875,9.501,-2.817,9.426,-2.767,9.425,-2.717,9.457,-2.696,9.481,-2.686,9.432,-2.706,9.351,-2.702,9.302,-2.674,9.283,-2.689,9.219,-2.747,9.11,-2.747,9.045,-2.69,9.025,-2.649,8.957,-2.625,8.84,-2.6,8.8,-2.598,8.776,-2.557,8.493,-2.506,8.209,-2.538,8.172,-2.583,8.161,-2.612,8.148,-2.62,8.121,-2.601,8.082,-2.613,8.047,-2.669,8.022,-2.79,7.932,-2.798,7.896,-2.83,7.819,-2.857,7.772,-2.896,7.685,-2.959,7.455,-2.982,7.264,-2.986,7.205,-3.01,7.164,-3.038,7.105,-3.169,6.941,-3.236,6.807,-3.227,6.749,-3.224,6.691,-3.244,6.649,-3.24,6.536,-3.224,6.441,-3.201,6.348,-3.106,6.086,-3.056,5.926,-3.025,5.798,-2.998,5.711,-2.973,5.676,-2.962,5.643,-2.821,5.619,-2.794,5.6,-2.755,5.433,-2.762,5.357,-2.79,5.328,-2.789,5.264,-2.795,5.185,-2.816,5.153,-2.895,5.149,-2.948,5.119,-3.019,5.131,-3.026,5.151,-3.064,5.158,-3.169,5.203,-3.151,5.348,-3.2,5.354,-3.238,5.335,-3.312,5.161,-3.348,5.131,-3.871,5.221,-3.984,5.293,-4.12,5.31,-4.357,5.301,-4.553,5.28,-4.609,5.236,-4.115,5.262,-4.062,5.257,-4.037,5.23,-4.662,5.173,-4.9,5.138,-4.97,5.148,-5.024,5.204,-5.282,5.21,-5.335,5.192,-5.368,5.151,-5.266,5.16,-5.105,5.162,-5.062,5.131,-5.565,5.089,-5.914,5.011,-6.062,4.953,-6.548,4.762,-6.845,4.671,-6.923,4.638,-7.058,4.545,-7.231,4.486,-7.426,4.376,-7.545,4.351,-7.572,4.386,-7.575,4.572,-7.591,4.822,-7.585,4.917,-7.569,5.006,-7.569,5.081,-7.51,5.108,-7.494,5.14,-7.485,5.236,-7.43,5.325,-7.429,5.478,-7.412,5.51,-7.4,5.551,-7.424,5.651,-7.454,5.841,-7.469,5.854,-7.483,5.846,-7.514,5.842,-7.636,5.908,-7.73,5.919,-7.797,5.975,-7.801,6.039,-7.833,6.076,-7.856,6.15,-7.889,6.235,-7.982,6.286,-8.069,6.298,-8.131,6.288,-8.204,6.291,-8.287,6.319,-8.345,6.351,-8.399,6.413,-8.45,6.463,-8.49,6.456,-8.54,6.468,-8.588,6.491,-8.604,6.508,-8.401,6.705,-8.333,6.802,-8.325,6.86,-8.325,6.92,-8.302,6.981,-8.297,7.074,-8.409,7.412,-8.437,7.516,-8.467,7.547,-8.486,7.558,-8.43,7.602,-8.352,7.591,-8.232,7.557,-8.206,7.59,-8.115,7.761,-8.118,7.824,-8.127,7.868,-8.074,7.984,-8.032,8.03,-8.01,8.079,-8.017,8.145,-8.049,8.17,-8.091,8.165,-8.141,8.181,-8.217,8.22,-8.256,8.254,-8.244,8.408,-8.237,8.456,-8.21,8.483,-8.168,8.491,-8.049,8.495,-7.953,8.478,-7.869,8.468,-7.824,8.468,-7.787,8.422,-7.739,8.375,-7.696,8.376,-7.681,8.41,-7.691,8.563,-7.72,8.643,-7.784,8.721,-7.951,8.787,-7.955,8.879,-7.938,8.98,-7.902,9.017,-7.778,9.081,-7.8,9.115,-7.839,9.152,-7.918,9.189,-7.9,9.309,-7.896,9.416,-7.963,9.404,-8.031,9.398,-8.089,9.431,-8.137,9.496,-8.146,9.675,-8.146,9.882,-8.155,9.973,-8.137,10.022,-8.078,10.067,-8.014,10.125,-7.991,10.163]]]},{"n":"Costa Rica","c":"CR","r":5,"ml":2.5,"lx":-84.08,"ly":10.07,"b":[-85.908,8.071,-82.564,11.189],"p":[[[-82.564,9.577,-82.569,9.558,-82.587,9.539,-82.611,9.519,-82.644,9.506,-82.723,9.546,-82.801,9.592,-82.844,9.571,-82.86,9.511,-82.889,9.481,-82.925,9.469,-82.94,9.449,-82.943,9.249,-82.94,9.06,-82.881,9.056,-82.783,8.99,-82.741,8.952,-82.728,8.916,-82.74,8.899,-82.812,8.857,-82.882,8.805,-82.917,8.74,-82.856,8.635,-82.843,8.564,-82.845,8.489,-82.862,8.454,-82.998,8.368,-83.027,8.338,-83.023,8.316,-82.948,8.257,-82.913,8.2,-82.883,8.131,-82.879,8.071,-82.947,8.182,-83.041,8.288,-83.123,8.353,-83.13,8.505,-83.162,8.588,-83.286,8.664,-83.391,8.718,-83.47,8.707,-83.422,8.619,-83.298,8.507,-83.29,8.464,-83.292,8.406,-83.377,8.415,-83.452,8.438,-83.544,8.446,-83.605,8.48,-83.734,8.614,-83.642,8.729,-83.614,8.804,-83.616,8.96,-83.637,9.035,-83.737,9.15,-83.896,9.276,-84.118,9.379,-84.222,9.463,-84.483,9.526,-84.582,9.568,-84.659,9.647,-84.67,9.703,-84.643,9.789,-84.715,9.899,-85.025,10.116,-85.198,10.195,-85.236,10.242,-85.263,10.257,-85.237,10.107,-85.161,10.017,-84.963,9.933,-84.908,9.885,-84.886,9.821,-85.001,9.699,-85.06,9.668,-85.077,9.602,-85.115,9.582,-85.154,9.62,-85.315,9.811,-85.625,9.902,-85.681,9.959,-85.796,10.133,-85.85,10.292,-85.831,10.398,-85.703,10.563,-85.663,10.635,-85.671,10.68,-85.667,10.745,-85.715,10.791,-85.833,10.85,-85.908,10.898,-85.887,10.921,-85.752,10.985,-85.744,11.043,-85.744,11.062,-85.722,11.066,-85.703,11.082,-85.691,11.097,-85.654,11.153,-85.621,11.184,-85.584,11.189,-85.539,11.166,-85.368,11.106,-85.179,11.04,-84.909,10.945,-84.797,11.006,-84.701,11.052,-84.634,11.046,-84.489,10.992,-84.402,10.974,-84.348,10.98,-84.256,10.901,-84.205,10.841,-84.197,10.802,-84.168,10.78,-84.096,10.776,-83.919,10.735,-83.811,10.743,-83.713,10.786,-83.659,10.837,-83.642,10.917,-83.617,10.877,-83.588,10.815,-83.575,10.735,-83.448,10.466,-83.347,10.315,-83.125,10.042,-83.029,9.991,-82.866,9.771,-82.81,9.735,-82.778,9.67,-82.61,9.616,-82.564,9.577]]]},{"n":"Democratic Republic of the Congo","c":"CD","r":2,"ml":2,"lx":23.46,"ly":-1.86,"b":[12.214,-13.454,31.274,5.312],"p":[[[30.751,-8.194,30.578,-8.22,30.328,-8.258,30.051,-8.3,29.766,-8.344,29.484,-8.387,29.216,-8.428,28.972,-8.465,28.898,-8.485,28.934,-8.59,28.918,-8.701,28.87,-8.786,28.794,-8.891,28.759,-8.933,28.681,-9.015,28.617,-9.072,28.484,-9.169,28.401,-9.225,28.4,-9.275,28.541,-9.51,28.604,-9.679,28.63,-9.831,28.629,-9.919,28.624,-10.099,28.617,-10.313,28.607,-10.397,28.646,-10.55,28.639,-10.669,28.544,-10.802,28.518,-10.933,28.47,-11.11,28.404,-11.354,28.357,-11.483,28.383,-11.567,28.407,-11.623,28.432,-11.698,28.483,-11.812,28.542,-11.879,28.575,-11.908,28.769,-12.051,28.85,-12.121,28.973,-12.258,29.064,-12.349,29.191,-12.37,29.344,-12.405,29.428,-12.431,29.486,-12.418,29.505,-12.386,29.502,-12.318,29.492,-12.267,29.508,-12.228,29.56,-12.202,29.692,-12.198,29.75,-12.164,29.795,-12.155,29.795,-12.306,29.796,-12.451,29.796,-12.626,29.796,-12.827,29.796,-12.992,29.796,-13.167,29.796,-13.37,29.795,-13.393,29.775,-13.438,29.723,-13.454,29.652,-13.414,29.648,-13.373,29.63,-13.299,29.597,-13.261,29.554,-13.249,29.481,-13.268,29.382,-13.323,29.254,-13.371,29.202,-13.398,29.112,-13.395,29.014,-13.369,28.942,-13.307,28.922,-13.215,28.859,-13.119,28.773,-12.982,28.73,-12.925,28.673,-12.861,28.615,-12.854,28.551,-12.836,28.511,-12.742,28.474,-12.623,28.451,-12.577,28.413,-12.518,28.358,-12.482,28.237,-12.435,28.069,-12.368,27.857,-12.285,27.757,-12.281,27.644,-12.267,27.574,-12.227,27.533,-12.195,27.487,-12.08,27.424,-11.945,27.238,-11.783,27.196,-11.605,27.159,-11.579,27.095,-11.594,27.046,-11.616,27.027,-11.664,26.977,-11.825,26.95,-11.899,26.931,-11.919,26.89,-11.944,26.824,-11.965,26.73,-11.976,26.596,-11.972,26.43,-11.948,26.34,-11.93,26.096,-11.903,26.026,-11.89,25.927,-11.855,25.855,-11.82,25.619,-11.744,25.512,-11.753,25.46,-11.7,25.413,-11.674,25.349,-11.623,25.321,-11.554,25.283,-11.405,25.292,-11.325,25.319,-11.237,25.289,-11.212,25.246,-11.212,25.185,-11.243,25.076,-11.26,24.877,-11.299,24.806,-11.321,24.728,-11.338,24.668,-11.353,24.519,-11.438,24.467,-11.448,24.378,-11.417,24.335,-11.371,24.378,-11.319,24.396,-11.255,24.366,-11.13,24.32,-11.072,24.187,-11.03,24.137,-11.026,24.115,-10.956,24.078,-10.892,24.003,-10.879,23.967,-10.872,23.929,-10.892,23.907,-10.943,23.901,-10.983,23.834,-11.014,23.696,-11.008,23.56,-10.979,23.464,-10.969,23.4,-10.976,23.157,-11.075,23.076,-11.088,22.815,-11.08,22.667,-11.06,22.561,-11.056,22.486,-11.087,22.393,-11.159,22.315,-11.199,22.279,-11.194,22.257,-11.164,22.226,-11.122,22.217,-11.013,22.178,-10.892,22.204,-10.829,22.28,-10.784,22.307,-10.691,22.283,-10.552,22.282,-10.453,22.302,-10.397,22.275,-10.259,22.198,-10.041,22.089,-9.863,21.949,-9.726,21.857,-9.594,21.813,-9.469,21.829,-9.168,21.872,-8.904,21.905,-8.693,21.896,-8.341,21.801,-8.112,21.78,-7.865,21.834,-7.602,21.842,-7.421,21.806,-7.329,21.782,-7.315,21.751,-7.305,21.511,-7.297,21.19,-7.285,20.911,-7.281,20.608,-7.278,20.558,-7.244,20.536,-7.183,20.537,-7.122,20.599,-6.935,20.59,-6.92,20.482,-6.916,20.19,-6.946,19.997,-6.976,19.875,-6.986,19.66,-7.037,19.528,-7.144,19.484,-7.279,19.487,-7.391,19.48,-7.472,19.419,-7.557,19.372,-7.655,19.37,-7.707,19.341,-7.967,19.143,-8.001,18.944,-8.001,18.898,-7.998,18.653,-7.936,18.563,-7.936,18.485,-7.969,18.335,-8,18.192,-8.024,18.047,-8.101,18.009,-8.108,17.913,-8.068,17.779,-8.071,17.643,-8.091,17.58,-8.099,17.536,-8.076,17.411,-7.882,17.245,-7.623,17.155,-7.461,17.122,-7.419,17.064,-7.363,16.985,-7.257,16.952,-7.157,16.966,-7.062,16.919,-6.934,16.813,-6.773,16.743,-6.618,16.709,-6.472,16.701,-6.346,16.718,-6.241,16.697,-6.164,16.64,-6.115,16.608,-6.052,16.585,-6.025,16.537,-5.966,16.431,-5.9,16.315,-5.866,16.06,-5.865,15.727,-5.864,15.425,-5.869,15.089,-5.875,14.749,-5.88,14.658,-5.889,14.399,-5.893,14.191,-5.876,14.114,-5.865,13.979,-5.857,13.765,-5.855,13.649,-5.862,13.371,-5.862,13.346,-5.863,13.303,-5.882,13.184,-5.856,13.068,-5.865,13.003,-5.836,12.861,-5.854,12.792,-5.878,12.681,-5.961,12.515,-6.004,12.453,-6,12.412,-5.986,12.315,-5.895,12.24,-5.807,12.214,-5.759,12.255,-5.746,12.386,-5.728,12.485,-5.719,12.504,-5.696,12.519,-5.425,12.522,-5.149,12.487,-5.113,12.453,-5.091,12.451,-5.071,12.503,-5.037,12.574,-4.997,12.596,-4.978,12.675,-4.905,12.83,-4.737,12.947,-4.695,13.057,-4.651,13.073,-4.635,13.087,-4.602,13.137,-4.604,13.152,-4.62,13.176,-4.656,13.22,-4.706,13.297,-4.765,13.376,-4.829,13.415,-4.837,13.478,-4.805,13.552,-4.757,13.66,-4.721,13.685,-4.689,13.699,-4.618,13.708,-4.543,13.717,-4.454,13.739,-4.442,13.778,-4.434,13.85,-4.459,13.882,-4.485,13.941,-4.485,13.978,-4.461,14.047,-4.417,14.134,-4.4,14.227,-4.358,14.316,-4.304,14.358,-4.299,14.403,-4.37,14.443,-4.419,14.45,-4.45,14.41,-4.508,14.365,-4.586,14.403,-4.682,14.412,-4.775,14.411,-4.831,14.441,-4.854,14.462,-4.865,14.494,-4.852,14.558,-4.856,14.634,-4.885,14.708,-4.882,14.779,-4.846,14.912,-4.706,15.106,-4.461,15.267,-4.308,15.395,-4.245,15.481,-4.172,15.526,-4.088,15.6,-4.031,15.755,-3.986,15.872,-3.934,15.99,-3.766,16.147,-3.464,16.191,-3.194,16.217,-3.03,16.202,-2.465,16.192,-2.279,16.215,-2.178,16.274,-2.108,16.434,-1.961,16.541,-1.84,16.622,-1.699,16.78,-1.376,16.849,-1.272,16.88,-1.226,16.975,-1.14,17.108,-1.064,17.279,-1,17.543,-0.775,17.753,-0.549,17.724,-0.278,17.773,-0.052,17.888,0.234,17.925,0.537,17.886,0.857,17.902,1.118,18.012,1.422,18.058,1.535,18.073,1.719,18.072,2.013,18.212,2.415,18.343,2.655,18.491,2.924,18.547,3.087,18.622,3.304,18.61,3.478,18.597,3.679,18.634,3.954,18.62,4.117,18.567,4.258,18.594,4.346,18.7,4.383,18.832,4.523,19.069,4.891,19.323,5.071,19.501,5.127,19.686,5.121,19.807,5.089,19.863,5.031,20.002,4.945,20.226,4.83,20.394,4.686,20.487,4.542,20.558,4.463,20.647,4.436,20.793,4.447,20.956,4.413,21.126,4.332,21.23,4.302,21.268,4.323,21.35,4.311,21.538,4.245,21.687,4.281,21.908,4.254,22.422,4.135,22.45,4.155,22.462,4.16,22.506,4.208,22.617,4.446,22.712,4.592,22.756,4.647,22.865,4.724,22.993,4.744,23.116,4.737,23.219,4.703,23.313,4.664,23.417,4.663,23.524,4.701,23.682,4.771,23.848,4.816,23.992,4.866,24.228,4.954,24.32,4.994,24.437,5.01,24.766,4.93,24.978,4.983,25.065,4.967,25.249,5.025,25.283,5.063,25.4,5.256,25.525,5.312,25.714,5.284,25.82,5.254,26.174,5.171,26.633,5.085,26.768,5.072,26.822,5.062,26.87,5.076,27.021,5.184,27.072,5.2,27.115,5.198,27.403,5.109,27.439,5.039,27.491,4.968,27.664,4.846,27.719,4.778,27.761,4.703,27.788,4.645,27.842,4.598,27.917,4.568,27.981,4.532,28.02,4.479,28.079,4.425,28.192,4.35,28.247,4.349,28.311,4.338,28.367,4.319,28.428,4.324,28.525,4.373,28.64,4.454,28.727,4.505,28.939,4.487,29.057,4.446,29.151,4.388,29.225,4.392,29.385,4.498,29.47,4.612,29.552,4.636,29.677,4.587,29.78,4.481,29.87,4.327,29.934,4.269,30.021,4.178,30.195,3.982,30.421,3.884,30.508,3.836,30.537,3.787,30.554,3.723,30.559,3.653,30.587,3.624,30.648,3.634,30.7,3.644,30.757,3.624,30.797,3.573,30.817,3.533,30.839,3.491,30.895,3.464,30.906,3.409,30.868,3.342,30.828,3.283,30.779,3.163,30.754,3.042,30.787,3.001,30.821,2.968,30.84,2.933,30.851,2.894,30.847,2.847,30.77,2.678,30.73,2.53,30.729,2.455,30.83,2.4,30.962,2.403,31.004,2.369,31.045,2.316,31.082,2.288,31.138,2.289,31.176,2.27,31.191,2.232,31.236,2.191,31.274,2.146,31.256,2.088,31.253,2.045,31.159,1.922,30.943,1.683,30.478,1.239,30.321,1.185,30.24,1.103,30.183,0.973,30.047,0.864,29.943,0.819,29.932,0.793,29.924,0.674,29.934,0.499,29.885,0.419,29.815,0.264,29.778,0.166,29.75,0.147,29.718,0.098,29.698,-0.06,29.684,-0.114,29.633,-0.442,29.648,-0.535,29.608,-0.691,29.606,-0.783,29.59,-0.887,29.562,-0.977,29.564,-1.121,29.58,-1.357,29.577,-1.388,29.538,-1.41,29.468,-1.468,29.402,-1.507,29.352,-1.518,29.268,-1.622,29.197,-1.72,29.143,-1.816,29.129,-1.86,29.141,-1.985,29.148,-2.132,29.132,-2.195,29.106,-2.233,28.99,-2.313,28.913,-2.37,28.876,-2.4,28.858,-2.447,28.891,-2.556,28.894,-2.635,28.922,-2.682,29.014,-2.72,29.014,-2.758,29.017,-2.8,29.065,-2.851,29.153,-2.955,29.224,-3.054,29.226,-3.139,29.212,-3.281,29.21,-3.363,29.217,-3.476,29.217,-3.685,29.212,-3.834,29.223,-3.911,29.331,-4.095,29.379,-4.3,29.403,-4.449,29.404,-4.497,29.368,-4.669,29.326,-4.836,29.323,-4.899,29.343,-4.983,29.42,-5.176,29.476,-5.317,29.504,-5.401,29.542,-5.5,29.594,-5.651,29.607,-5.723,29.596,-5.776,29.491,-5.965,29.48,-6.025,29.506,-6.172,29.541,-6.314,29.591,-6.394,29.71,-6.617,29.798,-6.692,29.962,-6.803,30.106,-6.915,30.162,-6.973,30.213,-7.038,30.313,-7.204,30.375,-7.339,30.407,-7.461,30.486,-7.627,30.559,-7.782,30.654,-7.971,30.721,-8.104,30.751,-8.194]]]},{"n":"Republic of the Congo","c":"CG","r":4,"ml":4,"lx":15.9,"ly":0.14,"b":[11.13,-5.004,18.622,3.687],"p":[[[11.13,-3.916,11.19,-3.762,11.234,-3.691,11.288,-3.641,11.504,-3.52,11.537,-3.525,11.686,-3.682,11.733,-3.695,11.786,-3.69,11.849,-3.697,11.88,-3.666,11.885,-3.625,11.839,-3.58,11.833,-3.531,11.865,-3.479,11.883,-3.42,11.929,-3.351,11.934,-3.319,11.885,-3.283,11.784,-3.229,11.715,-3.177,11.689,-3.127,11.708,-3.063,11.763,-3.011,11.76,-2.983,11.711,-2.937,11.676,-2.887,11.639,-2.855,11.538,-2.837,11.557,-2.77,11.595,-2.671,11.603,-2.595,11.575,-2.397,11.578,-2.361,11.605,-2.343,11.666,-2.365,11.727,-2.395,11.892,-2.351,11.95,-2.345,11.998,-2.383,12.064,-2.413,12.446,-2.33,12.454,-2.246,12.476,-2.169,12.479,-2.112,12.463,-2.075,12.444,-2.048,12.432,-1.99,12.432,-1.929,12.469,-1.9,12.59,-1.827,12.628,-1.83,12.714,-1.869,12.794,-1.932,12.864,-2.063,12.914,-2.176,12.992,-2.313,13.159,-2.369,13.357,-2.405,13.465,-2.395,13.619,-2.279,13.706,-2.187,13.734,-2.138,13.784,-2.164,13.842,-2.284,13.879,-2.33,13.888,-2.375,13.862,-2.43,13.887,-2.465,13.994,-2.491,14.087,-2.467,14.13,-2.418,14.2,-2.354,14.2,-2.301,14.163,-2.266,14.163,-2.218,14.202,-2.18,14.24,-2.077,14.251,-2.001,14.288,-1.954,14.359,-1.92,14.384,-1.89,14.423,-1.712,14.403,-1.647,14.403,-1.593,14.447,-1.525,14.456,-1.413,14.437,-1.23,14.424,-1.104,14.411,-0.972,14.445,-0.799,14.481,-0.618,14.474,-0.573,14.425,-0.519,14.364,-0.469,14.207,-0.427,14.148,-0.362,14.103,-0.292,14.069,-0.27,13.898,-0.243,13.86,-0.203,13.875,-0.091,13.891,0.075,13.885,0.191,13.915,0.284,13.95,0.354,14.025,0.428,14.066,0.515,14.088,0.537,14.231,0.551,14.283,0.587,14.324,0.624,14.342,0.674,14.391,0.756,14.434,0.811,14.439,0.849,14.43,0.901,14.386,1.004,14.334,1.09,14.303,1.121,14.24,1.323,14.181,1.37,14.066,1.396,13.851,1.419,13.721,1.382,13.523,1.315,13.372,1.268,13.274,1.241,13.216,1.248,13.19,1.279,13.228,1.305,13.247,1.367,13.223,1.455,13.185,1.535,13.163,1.648,13.172,1.789,13.209,1.92,13.289,2.092,13.294,2.162,13.533,2.16,13.773,2.157,14.034,2.159,14.287,2.16,14.484,2.155,14.579,2.199,14.669,2.132,14.713,2.117,14.728,2.122,14.763,2.075,14.875,2.08,14.893,2.069,14.902,2.012,15.006,2.014,15.058,2.001,15.1,2.002,15.16,2.036,15.204,2.024,15.282,1.982,15.339,1.945,15.417,1.957,15.6,1.95,15.742,1.915,15.882,1.817,15.975,1.76,16.059,1.676,16.09,1.691,16.12,1.714,16.136,1.724,16.135,1.796,16.088,1.918,16.07,2.022,16.08,2.107,16.116,2.168,16.177,2.205,16.183,2.262,16.183,2.27,16.252,2.407,16.32,2.543,16.401,2.701,16.469,2.832,16.46,2.897,16.466,2.993,16.48,3.101,16.477,3.165,16.496,3.209,16.543,3.37,16.57,3.463,16.611,3.505,16.673,3.535,16.764,3.536,17.003,3.557,17.225,3.598,17.298,3.617,17.438,3.685,17.492,3.687,17.538,3.662,17.807,3.584,17.88,3.554,17.907,3.558,17.948,3.552,18.011,3.551,18.072,3.56,18.111,3.551,18.161,3.5,18.194,3.505,18.237,3.543,18.318,3.581,18.474,3.623,18.5,3.604,18.554,3.51,18.61,3.478,18.622,3.304,18.547,3.087,18.491,2.924,18.343,2.655,18.212,2.415,18.072,2.013,18.073,1.719,18.058,1.535,18.012,1.422,17.902,1.118,17.886,0.857,17.925,0.537,17.888,0.234,17.773,-0.052,17.724,-0.278,17.753,-0.549,17.543,-0.775,17.279,-1,17.108,-1.064,16.975,-1.14,16.88,-1.226,16.849,-1.272,16.78,-1.376,16.622,-1.699,16.541,-1.84,16.434,-1.961,16.274,-2.108,16.215,-2.178,16.192,-2.279,16.202,-2.465,16.217,-3.03,16.191,-3.194,16.147,-3.464,15.99,-3.766,15.872,-3.934,15.755,-3.986,15.6,-4.031,15.526,-4.088,15.481,-4.172,15.395,-4.245,15.267,-4.308,15.106,-4.461,14.912,-4.706,14.779,-4.846,14.708,-4.882,14.634,-4.885,14.558,-4.856,14.494,-4.852,14.462,-4.865,14.441,-4.854,14.411,-4.831,14.412,-4.775,14.403,-4.682,14.365,-4.586,14.41,-4.508,14.45,-4.45,14.443,-4.419,14.403,-4.37,14.358,-4.299,14.316,-4.304,14.227,-4.358,14.134,-4.4,14.047,-4.417,13.978,-4.461,13.941,-4.485,13.882,-4.485,13.85,-4.459,13.778,-4.434,13.739,-4.442,13.717,-4.454,13.708,-4.543,13.699,-4.618,13.685,-4.689,13.66,-4.721,13.552,-4.757,13.478,-4.805,13.415,-4.837,13.376,-4.829,13.297,-4.765,13.22,-4.706,13.176,-4.656,13.152,-4.62,13.137,-4.604,13.087,-4.602,13.073,-4.635,13.048,-4.619,12.971,-4.552,12.881,-4.445,12.848,-4.429,12.798,-4.431,12.719,-4.47,12.642,-4.531,12.501,-4.587,12.385,-4.619,12.374,-4.658,12.347,-4.724,12.308,-4.766,12.204,-4.779,12.167,-4.838,12.078,-4.952,12.018,-5.004,12.003,-4.982,11.967,-4.954,11.893,-4.866,11.821,-4.755,11.801,-4.705,11.781,-4.677,11.778,-4.566,11.668,-4.434,11.394,-4.2,11.364,-4.131,11.13,-3.916]]]},{"n":"Comoros","c":"KM","r":6,"ml":4,"lx":43.32,"ly":-11.73,"b":[43.227,-12.368,44.527,-11.368],"p":[[[44.476,-12.082,44.527,-12.22,44.526,-12.324,44.505,-12.357,44.46,-12.335,44.377,-12.252,44.22,-12.171,44.292,-12.165,44.334,-12.173,44.379,-12.166,44.407,-12.12,44.413,-12.093,44.452,-12.071,44.476,-12.082]],[[43.789,-12.307,43.859,-12.368,43.664,-12.343,43.633,-12.288,43.631,-12.247,43.704,-12.256,43.789,-12.307]],[[43.466,-11.901,43.447,-11.915,43.355,-11.858,43.303,-11.844,43.227,-11.752,43.256,-11.432,43.281,-11.391,43.299,-11.375,43.342,-11.368,43.393,-11.409,43.379,-11.614,43.448,-11.753,43.492,-11.862,43.466,-11.901]]]},{"n":"Colombia","c":"CO","r":2,"ml":3,"lx":-73.17,"ly":3.37,"b":[-79.025,-4.236,-66.876,12.434],"p":[[[-71.32,11.862,-71.356,11.85,-71.4,11.824,-71.536,11.774,-71.719,11.727,-71.958,11.666,-72.012,11.602,-72.248,11.196,-72.446,11.114,-72.518,11.054,-72.572,10.977,-72.69,10.836,-72.739,10.727,-72.869,10.491,-72.94,10.196,-72.967,10.03,-73.007,9.789,-73.064,9.668,-73.141,9.555,-73.224,9.444,-73.296,9.322,-73.356,9.227,-73.366,9.194,-73.337,9.168,-73.193,9.194,-73.137,9.223,-73.058,9.26,-73.009,9.24,-72.96,9.135,-72.904,9.122,-72.852,9.135,-72.796,9.109,-72.726,8.848,-72.665,8.628,-72.526,8.49,-72.417,8.382,-72.39,8.287,-72.364,8.153,-72.358,8.087,-72.392,8.048,-72.446,7.966,-72.46,7.81,-72.469,7.758,-72.479,7.613,-72.472,7.524,-72.443,7.455,-72.395,7.415,-72.296,7.395,-72.208,7.37,-72.157,7.25,-72.084,7.097,-72.007,7.033,-71.893,6.99,-71.811,7.006,-71.621,7.033,-71.457,7.026,-71.218,6.985,-71.129,6.987,-71.013,6.994,-70.811,7.078,-70.737,7.09,-70.655,7.083,-70.536,7.041,-70.471,7.007,-70.387,6.973,-70.266,6.948,-70.188,6.952,-70.129,6.954,-70.095,6.938,-69.904,6.7,-69.739,6.494,-69.595,6.321,-69.439,6.135,-69.427,6.124,-69.357,6.148,-69.311,6.138,-69.268,6.1,-69.195,6.115,-69.09,6.184,-68.937,6.198,-68.736,6.157,-68.472,6.157,-68.143,6.198,-67.939,6.242,-67.859,6.29,-67.727,6.285,-67.568,6.242,-67.482,6.18,-67.472,6.12,-67.439,6.026,-67.474,5.93,-67.575,5.833,-67.631,5.709,-67.642,5.559,-67.695,5.448,-67.788,5.375,-67.825,5.27,-67.804,5.133,-67.814,4.931,-67.855,4.665,-67.855,4.507,-67.814,4.455,-67.795,4.381,-67.799,4.284,-67.783,4.198,-67.732,4.087,-67.662,3.864,-67.603,3.769,-67.551,3.734,-67.499,3.691,-67.348,3.464,-67.311,3.416,-67.322,3.374,-67.336,3.343,-67.354,3.323,-67.515,3.187,-67.835,2.893,-67.861,2.855,-67.859,2.794,-67.766,2.833,-67.667,2.8,-67.619,2.794,-67.597,2.769,-67.568,2.69,-67.535,2.677,-67.486,2.644,-67.392,2.56,-67.312,2.472,-67.253,2.429,-67.211,2.39,-67.198,2.333,-67.215,2.275,-67.165,2.143,-67.131,2.101,-67.114,2.051,-67.131,2,-67.09,1.94,-67.044,1.823,-66.988,1.68,-66.982,1.601,-66.958,1.564,-66.931,1.458,-66.884,1.358,-66.896,1.29,-66.876,1.223,-67.065,1.178,-67.082,1.185,-67.094,1.21,-67.088,1.401,-67.09,1.616,-67.119,1.704,-67.206,1.845,-67.321,2.032,-67.352,2.086,-67.4,2.117,-67.458,2.121,-67.5,2.108,-67.556,2.073,-67.609,2.035,-67.712,1.922,-67.815,1.79,-67.876,1.761,-67.936,1.748,-67.99,1.753,-68.033,1.788,-68.077,1.86,-68.13,1.956,-68.194,1.987,-68.218,1.958,-68.239,1.901,-68.256,1.846,-68.213,1.775,-68.177,1.72,-68.24,1.722,-68.443,1.722,-68.678,1.721,-68.913,1.721,-69.124,1.721,-69.32,1.721,-69.394,1.726,-69.47,1.758,-69.543,1.773,-69.581,1.771,-69.65,1.739,-69.74,1.735,-69.8,1.705,-69.849,1.709,-69.849,1.544,-69.851,1.309,-69.852,1.06,-69.798,1.078,-69.751,1.077,-69.717,1.059,-69.621,1.073,-69.568,1.066,-69.517,1.059,-69.47,1.059,-69.442,1.039,-69.403,1.042,-69.361,1.064,-69.312,1.05,-69.259,1.015,-69.224,0.963,-69.194,0.898,-69.163,0.864,-69.165,0.802,-69.166,0.753,-69.177,0.713,-69.163,0.687,-69.153,0.659,-69.156,0.643,-69.174,0.635,-69.213,0.63,-69.254,0.625,-69.283,0.627,-69.306,0.652,-69.327,0.655,-69.359,0.652,-69.392,0.667,-69.421,0.698,-69.472,0.73,-69.527,0.716,-69.565,0.7,-69.604,0.68,-69.639,0.66,-69.674,0.665,-69.719,0.65,-69.757,0.626,-69.807,0.607,-69.862,0.598,-69.925,0.589,-69.985,0.586,-70.054,0.579,-70.058,0.447,-70.066,0.189,-70.071,0.019,-70.071,-0.139,-70.044,-0.196,-69.923,-0.317,-69.828,-0.381,-69.747,-0.453,-69.667,-0.482,-69.634,-0.509,-69.612,-0.553,-69.601,-0.6,-69.592,-0.639,-69.601,-0.681,-69.621,-0.721,-69.612,-0.763,-69.583,-0.796,-69.574,-0.838,-69.555,-0.877,-69.544,-0.917,-69.519,-0.946,-69.488,-0.966,-69.449,-0.999,-69.444,-1.03,-69.449,-1.065,-69.449,-1.092,-69.411,-1.152,-69.4,-1.195,-69.418,-1.246,-69.435,-1.422,-69.479,-1.622,-69.506,-1.775,-69.552,-2.024,-69.605,-2.314,-69.669,-2.668,-69.733,-3.017,-69.794,-3.355,-69.85,-3.66,-69.911,-3.997,-69.948,-4.201,-69.966,-4.236,-70.017,-4.162,-70.095,-4.092,-70.168,-4.05,-70.198,-3.995,-70.24,-3.883,-70.298,-3.844,-70.34,-3.814,-70.379,-3.819,-70.421,-3.85,-70.486,-3.869,-70.53,-3.866,-70.706,-3.789,-70.735,-3.782,-70.622,-3.605,-70.419,-3.288,-70.29,-3.087,-70.147,-2.864,-70.074,-2.75,-70.064,-2.731,-70.065,-2.702,-70.096,-2.658,-70.165,-2.64,-70.244,-2.607,-70.295,-2.553,-70.364,-2.529,-70.418,-2.491,-70.517,-2.453,-70.576,-2.418,-70.648,-2.406,-70.705,-2.342,-70.915,-2.219,-70.969,-2.207,-71.027,-2.226,-71.113,-2.245,-71.196,-2.313,-71.3,-2.335,-71.397,-2.334,-71.447,-2.294,-71.496,-2.279,-71.559,-2.224,-71.671,-2.182,-71.753,-2.153,-71.803,-2.166,-71.867,-2.228,-71.932,-2.289,-71.984,-2.327,-72.054,-2.325,-72.137,-2.381,-72.218,-2.4,-72.301,-2.409,-72.396,-2.429,-72.501,-2.395,-72.587,-2.365,-72.625,-2.352,-72.66,-2.361,-72.714,-2.392,-72.811,-2.405,-72.887,-2.408,-72.941,-2.394,-72.99,-2.34,-73.068,-2.312,-73.154,-2.278,-73.173,-2.208,-73.16,-2.156,-73.127,-2.081,-73.145,-2.003,-73.181,-1.88,-73.197,-1.83,-73.224,-1.788,-73.266,-1.772,-73.35,-1.784,-73.44,-1.737,-73.496,-1.693,-73.525,-1.639,-73.494,-1.537,-73.521,-1.45,-73.575,-1.401,-73.61,-1.316,-73.664,-1.249,-73.736,-1.214,-73.807,-1.218,-73.863,-1.197,-73.927,-1.125,-73.987,-1.098,-74.054,-1.029,-74.181,-0.998,-74.246,-0.971,-74.284,-0.928,-74.334,-0.851,-74.329,-0.808,-74.353,-0.767,-74.375,-0.691,-74.418,-0.581,-74.465,-0.518,-74.514,-0.47,-74.555,-0.43,-74.616,-0.37,-74.692,-0.335,-74.755,-0.299,-74.78,-0.245,-74.802,-0.2,-74.837,-0.203,-74.889,-0.199,-74.945,-0.188,-75.005,-0.156,-75.055,-0.117,-75.138,-0.05,-75.184,-0.042,-75.225,-0.042,-75.284,-0.107,-75.464,-0.038,-75.617,0.063,-75.777,0.089,-75.88,0.151,-75.975,0.248,-76.026,0.313,-76.068,0.346,-76.271,0.439,-76.311,0.448,-76.388,0.405,-76.413,0.379,-76.418,0.304,-76.427,0.261,-76.495,0.235,-76.603,0.241,-76.679,0.268,-76.729,0.272,-76.739,0.251,-76.768,0.242,-76.829,0.248,-76.92,0.269,-77.002,0.296,-77.114,0.355,-77.166,0.348,-77.293,0.36,-77.396,0.394,-77.423,0.425,-77.468,0.637,-77.481,0.651,-77.526,0.66,-77.601,0.69,-77.649,0.724,-77.673,0.782,-77.703,0.838,-77.83,0.825,-78.037,0.899,-78.181,0.969,-78.312,1.046,-78.512,1.199,-78.588,1.237,-78.682,1.283,-78.737,1.359,-78.829,1.435,-78.86,1.455,-78.888,1.524,-79.025,1.624,-78.958,1.752,-78.793,1.849,-78.577,1.774,-78.55,1.924,-78.629,2.056,-78.617,2.307,-78.592,2.357,-78.535,2.424,-78.46,2.47,-78.417,2.483,-78.343,2.461,-78.296,2.51,-78.12,2.488,-78.067,2.509,-78.03,2.543,-77.987,2.569,-77.932,2.629,-77.901,2.699,-77.875,2.726,-77.814,2.716,-77.808,2.746,-77.777,2.787,-77.67,2.879,-77.671,2.919,-77.701,3.008,-77.694,3.04,-77.632,3.051,-77.559,3.076,-77.52,3.16,-77.472,3.234,-77.417,3.342,-77.357,3.349,-77.324,3.475,-77.243,3.585,-77.077,3.913,-77.127,3.906,-77.167,3.862,-77.212,3.867,-77.264,3.893,-77.248,4.041,-77.278,4.058,-77.358,3.945,-77.427,4.06,-77.434,4.131,-77.404,4.201,-77.409,4.248,-77.521,4.213,-77.516,4.256,-77.446,4.301,-77.414,4.348,-77.354,4.398,-77.328,4.475,-77.314,4.594,-77.286,4.722,-77.307,4.785,-77.339,4.839,-77.367,5.077,-77.359,5.215,-77.373,5.324,-77.402,5.416,-77.534,5.537,-77.325,5.676,-77.249,5.78,-77.345,5.995,-77.469,6.177,-77.473,6.286,-77.44,6.272,-77.398,6.275,-77.36,6.504,-77.369,6.576,-77.439,6.69,-77.526,6.693,-77.602,6.837,-77.646,6.87,-77.681,6.96,-77.804,7.137,-77.901,7.229,-77.828,7.443,-77.765,7.484,-77.744,7.537,-77.769,7.668,-77.762,7.699,-77.747,7.712,-77.732,7.711,-77.706,7.691,-77.659,7.635,-77.619,7.565,-77.587,7.543,-77.538,7.566,-77.351,7.706,-77.363,7.749,-77.346,7.837,-77.283,7.908,-77.216,7.933,-77.196,7.972,-77.212,8.034,-77.283,8.187,-77.346,8.27,-77.386,8.352,-77.407,8.427,-77.479,8.498,-77.448,8.566,-77.393,8.645,-77.374,8.658,-77.344,8.637,-77.262,8.494,-77.13,8.401,-76.992,8.25,-76.936,8.147,-76.891,8.128,-76.852,8.09,-76.869,8.063,-76.912,8.033,-76.925,7.973,-76.897,7.939,-76.867,7.918,-76.787,7.932,-76.742,8.002,-76.772,8.311,-76.819,8.465,-76.872,8.513,-76.92,8.574,-76.888,8.62,-76.802,8.641,-76.689,8.695,-76.277,8.989,-76.135,9.266,-76.027,9.366,-75.905,9.431,-75.756,9.416,-75.639,9.45,-75.604,9.538,-75.635,9.658,-75.68,9.73,-75.637,9.834,-75.593,9.993,-75.596,10.126,-75.539,10.205,-75.558,10.236,-75.642,10.172,-75.708,10.143,-75.671,10.196,-75.554,10.328,-75.493,10.528,-75.446,10.611,-75.281,10.727,-75.248,10.783,-75.123,10.87,-74.922,11.058,-74.845,11.11,-74.454,10.989,-74.33,10.997,-74.352,10.975,-74.41,10.967,-74.492,10.934,-74.516,10.863,-74.46,10.787,-74.401,10.765,-74.35,10.814,-74.3,10.952,-74.219,11.105,-74.2,11.266,-74.143,11.321,-74.059,11.341,-73.91,11.309,-73.796,11.276,-73.677,11.271,-73.313,11.296,-72.722,11.712,-72.447,11.802,-72.275,11.889,-72.165,12.06,-72.136,12.189,-72.055,12.238,-71.97,12.238,-71.931,12.27,-71.919,12.309,-71.715,12.42,-71.597,12.434,-71.494,12.432,-71.262,12.335,-71.155,12.164,-71.137,12.046,-71.284,11.918,-71.32,11.862]],[[-78.114,2.542,-78.141,2.52,-78.192,2.559,-78.21,2.609,-78.178,2.646,-78.138,2.634,-78.119,2.604,-78.114,2.542]]]},{"n":"People's Republic of China","c":"CN","r":2,"ml":1.7,"lx":106.34,"ly":32.5,"b":[73.607,18.218,134.752,53.556],"p":[[[118.183,24.496,118.15,24.436,118.091,24.446,118.089,24.489,118.077,24.501,118.093,24.541,118.104,24.552,118.171,24.519,118.183,24.496]],[[121.863,31.492,121.78,31.464,121.52,31.55,121.336,31.644,121.227,31.758,121.211,31.805,121.339,31.797,121.464,31.756,121.492,31.694,121.542,31.674,121.577,31.637,121.808,31.552,121.844,31.526,121.863,31.492]],[[122.296,29.963,122.282,29.944,122.158,30.001,122.024,30.013,121.978,30.064,121.969,30.143,122.111,30.14,122.284,30.068,122.322,30.031,122.296,29.963]],[[122.173,29.679,122.169,29.66,122.084,29.725,122.043,29.736,122.062,29.773,122.12,29.782,122.165,29.701,122.173,29.679]],[[122.404,29.892,122.394,29.846,122.368,29.853,122.332,29.935,122.351,29.955,122.402,29.95,122.404,29.892]],[[119.821,25.457,119.747,25.411,119.7,25.433,119.699,25.495,119.723,25.551,119.696,25.591,119.723,25.639,119.778,25.653,119.797,25.623,119.829,25.607,119.838,25.591,119.839,25.56,119.809,25.508,119.832,25.48,119.821,25.457]],[[110.385,21.093,110.422,21.059,110.522,21.083,110.54,21.039,110.539,21.018,110.504,20.968,110.422,21.007,110.34,20.998,110.281,21.001,110.265,21.025,110.31,21.075,110.385,21.093]],[[121.251,28.086,121.164,28.063,121.132,28.063,121.134,28.135,121.205,28.204,121.234,28.181,121.251,28.145,121.251,28.086]],[[113.555,22.804,113.564,22.758,113.486,22.828,113.463,22.832,113.426,22.859,113.404,22.903,113.465,22.905,113.521,22.852,113.555,22.804]],[[112.79,21.602,112.771,21.582,112.742,21.618,112.733,21.67,112.713,21.698,112.761,21.733,112.782,21.772,112.839,21.765,112.863,21.753,112.813,21.712,112.801,21.695,112.79,21.602]],[[112.644,21.64,112.546,21.619,112.525,21.623,112.559,21.675,112.648,21.71,112.644,21.64]],[[107.973,21.508,107.908,21.56,107.802,21.645,107.759,21.655,107.641,21.614,107.471,21.598,107.433,21.642,107.351,21.609,107.272,21.711,107.179,21.717,107.062,21.794,107.02,21.835,107.006,21.893,106.971,21.924,106.925,21.92,106.875,21.951,106.794,21.982,106.729,22,106.698,21.986,106.664,21.979,106.658,22.018,106.66,22.136,106.654,22.241,106.637,22.289,106.593,22.325,106.554,22.342,106.536,22.395,106.55,22.501,106.582,22.573,106.633,22.586,106.702,22.638,106.736,22.711,106.78,22.779,106.624,22.874,106.542,22.908,106.451,22.894,106.338,22.863,106.279,22.857,106.249,22.869,106.184,22.955,106.148,22.97,106.068,22.976,106.001,22.975,105.962,22.937,105.903,22.925,105.843,22.923,105.782,22.969,105.691,23.03,105.548,23.073,105.531,23.122,105.495,23.181,105.44,23.235,105.35,23.308,105.275,23.345,105.239,23.322,105.189,23.281,104.996,23.194,104.91,23.161,104.865,23.136,104.827,23.1,104.815,23.011,104.796,22.911,104.74,22.86,104.687,22.822,104.632,22.818,104.578,22.82,104.527,22.804,104.372,22.704,104.298,22.712,104.238,22.769,104.213,22.809,104.143,22.8,104.054,22.752,104.013,22.666,103.991,22.586,103.971,22.55,103.942,22.54,103.915,22.538,103.637,22.77,103.62,22.782,103.571,22.734,103.525,22.612,103.493,22.588,103.471,22.597,103.356,22.755,103.327,22.77,103.301,22.764,103.266,22.714,103.193,22.639,103.138,22.593,103.136,22.542,103.076,22.498,103.005,22.453,102.982,22.448,102.935,22.466,102.874,22.525,102.83,22.587,102.721,22.648,102.599,22.7,102.517,22.741,102.471,22.751,102.428,22.733,102.406,22.708,102.376,22.647,102.302,22.546,102.237,22.466,102.176,22.415,102.127,22.379,102.092,22.412,102.024,22.439,101.945,22.439,101.842,22.388,101.76,22.49,101.739,22.495,101.708,22.487,101.671,22.462,101.646,22.405,101.62,22.327,101.568,22.276,101.525,22.254,101.537,22.21,101.562,22.162,101.56,22.121,101.576,22.055,101.603,21.99,101.7,21.882,101.737,21.827,101.744,21.778,101.747,21.606,101.743,21.534,101.724,21.395,101.723,21.315,101.763,21.279,101.802,21.236,101.801,21.213,101.783,21.204,101.728,21.156,101.705,21.15,101.669,21.17,101.622,21.184,101.584,21.204,101.542,21.234,101.444,21.231,101.281,21.184,101.248,21.197,101.224,21.224,101.212,21.278,101.22,21.342,101.206,21.383,101.175,21.408,101.197,21.522,101.139,21.567,101.147,21.582,101.128,21.705,101.131,21.736,101.121,21.746,101.08,21.756,101.019,21.736,100.835,21.655,100.677,21.505,100.605,21.472,100.531,21.458,100.446,21.484,100.351,21.501,100.215,21.463,100.148,21.481,100.117,21.511,100.089,21.558,100.106,21.617,100.096,21.661,100.041,21.683,99.978,21.702,99.941,21.759,99.926,21.821,99.94,21.902,99.948,21.988,99.918,22.028,99.825,22.05,99.593,22.089,99.389,22.111,99.303,22.101,99.233,22.11,99.193,22.126,99.173,22.153,99.172,22.192,99.205,22.283,99.243,22.37,99.338,22.498,99.343,22.587,99.338,22.689,99.385,22.825,99.467,22.927,99.507,22.959,99.497,23.005,99.465,23.046,99.418,23.069,99.341,23.096,99.22,23.103,99.055,23.131,98.864,23.191,98.886,23.307,98.883,23.38,98.859,23.44,98.82,23.483,98.798,23.52,98.832,23.624,98.788,23.738,98.735,23.783,98.681,23.842,98.677,23.905,98.702,23.964,98.834,24.091,98.835,24.121,98.802,24.119,98.764,24.116,98.583,24.07,98.564,24.099,98.499,24.116,98.367,24.119,98.213,24.111,98.017,24.065,97.838,23.986,97.756,23.932,97.686,23.898,97.63,23.887,97.565,23.911,97.568,23.988,97.691,24.131,97.708,24.229,97.671,24.313,97.667,24.38,97.624,24.423,97.563,24.444,97.531,24.492,97.529,24.631,97.583,24.775,97.671,24.82,97.724,24.842,97.738,24.87,97.711,24.97,97.715,25.034,97.767,25.158,97.82,25.252,97.918,25.236,97.962,25.259,98.011,25.293,98.064,25.349,98.1,25.416,98.143,25.571,98.173,25.595,98.297,25.569,98.334,25.587,98.402,25.678,98.466,25.789,98.558,25.823,98.625,25.827,98.656,25.864,98.655,25.918,98.591,26.004,98.564,26.072,98.572,26.114,98.663,26.139,98.686,26.189,98.672,26.299,98.709,26.43,98.732,26.583,98.739,26.698,98.738,26.786,98.729,26.877,98.717,27.045,98.675,27.191,98.682,27.245,98.677,27.422,98.651,27.572,98.6,27.599,98.504,27.648,98.453,27.657,98.409,27.639,98.392,27.587,98.35,27.538,98.299,27.55,98.274,27.599,98.241,27.663,98.13,27.968,98.118,28.055,98.099,28.142,98.062,28.186,98.022,28.212,97.934,28.314,97.888,28.356,97.865,28.364,97.817,28.356,97.769,28.356,97.73,28.407,97.695,28.469,97.659,28.5,97.599,28.517,97.538,28.51,97.502,28.456,97.478,28.426,97.431,28.354,97.356,28.254,97.322,28.218,97.289,28.237,97.145,28.34,97.075,28.369,96.981,28.338,96.833,28.362,96.776,28.367,96.653,28.45,96.603,28.46,96.428,28.406,96.389,28.368,96.366,28.367,96.32,28.387,96.281,28.412,96.279,28.428,96.326,28.469,96.33,28.497,96.327,28.525,96.396,28.607,96.581,28.764,96.55,28.83,96.477,28.959,96.467,29.022,96.436,29.051,96.347,29.027,96.162,28.91,96.137,28.923,96.141,28.963,96.122,29.082,96.181,29.118,96.271,29.161,96.34,29.21,96.356,29.249,96.337,29.261,96.235,29.246,96.195,29.272,96.129,29.381,96.08,29.424,96.035,29.447,95.885,29.391,95.71,29.314,95.516,29.206,95.517,29.151,95.494,29.137,95.457,29.102,95.42,29.054,95.389,29.037,95.353,29.036,95.279,29.05,95.145,29.104,94.999,29.149,94.967,29.144,94.769,29.176,94.763,29.201,94.733,29.252,94.677,29.297,94.623,29.312,94.468,29.216,94.293,29.145,94.193,29.06,94.112,28.976,94.018,28.96,94.013,28.908,93.974,28.861,93.902,28.803,93.761,28.73,93.665,28.69,93.361,28.654,93.252,28.629,93.207,28.591,93.158,28.493,93.119,28.402,93.035,28.328,92.882,28.228,92.702,28.147,92.653,28.093,92.643,28.062,92.666,28.05,92.688,28.026,92.688,27.989,92.664,27.949,92.547,27.879,92.481,27.846,92.415,27.825,92.341,27.821,92.27,27.83,92.25,27.842,92.222,27.827,92.158,27.812,92.101,27.808,91.978,27.73,91.909,27.73,91.825,27.746,91.713,27.76,91.632,27.76,91.629,27.801,91.642,27.923,91.606,27.952,91.493,27.982,91.368,28.022,91.307,28.064,91.273,28.078,91.226,28.071,91.15,28.027,91.078,27.974,91.021,27.97,90.963,27.995,90.907,28.027,90.716,28.072,90.63,28.079,90.477,28.071,90.353,28.08,90.333,28.094,90.334,28.119,90.352,28.168,90.363,28.217,90.348,28.244,90.221,28.278,90.104,28.302,89.981,28.311,89.898,28.294,89.817,28.256,89.75,28.188,89.653,28.158,89.537,28.107,89.481,28.06,89.396,27.958,89.273,27.833,89.16,27.711,89.102,27.593,89.025,27.518,88.948,27.464,88.891,27.316,88.833,27.363,88.765,27.43,88.749,27.522,88.83,27.767,88.849,27.869,88.829,27.907,88.804,28.007,88.756,28.04,88.621,28.092,88.578,28.093,88.532,28.057,88.486,28.034,88.426,28.012,88.275,27.969,88.141,27.949,88.109,27.933,88.099,27.905,88.11,27.871,88.023,27.883,87.933,27.891,87.861,27.886,87.683,27.821,87.623,27.815,87.555,27.822,87.464,27.824,87.291,27.822,87.141,27.838,87.02,27.929,86.934,27.968,86.842,27.999,86.75,28.022,86.72,28.071,86.691,28.095,86.614,28.103,86.554,28.085,86.517,27.964,86.485,27.94,86.409,27.929,86.329,27.96,86.218,28.022,86.174,28.092,86.137,28.114,86.079,28.084,86.075,27.995,86.064,27.935,85.995,27.91,85.954,27.928,85.922,27.99,85.84,28.135,85.759,28.221,85.678,28.277,85.411,28.276,85.212,28.293,85.122,28.316,85.089,28.372,85.121,28.484,85.16,28.572,85.159,28.592,85.126,28.603,85.069,28.61,84.855,28.554,84.797,28.56,84.759,28.579,84.714,28.596,84.677,28.622,84.651,28.66,84.465,28.753,84.411,28.804,84.312,28.868,84.229,28.912,84.176,29.036,84.128,29.156,84.101,29.22,84.022,29.254,83.936,29.279,83.79,29.227,83.671,29.188,83.583,29.184,83.457,29.306,83.355,29.439,83.235,29.555,83.155,29.613,83.014,29.618,82.854,29.683,82.641,29.831,82.487,29.942,82.221,30.064,82.159,30.115,82.135,30.159,82.099,30.245,82.043,30.327,81.855,30.362,81.642,30.388,81.417,30.338,81.255,30.093,81.177,30.04,81.11,30.037,81.056,30.099,81.01,30.165,80.985,30.237,80.874,30.291,80.747,30.36,80.682,30.415,80.609,30.449,80.541,30.464,80.41,30.509,80.261,30.561,80.191,30.568,80.186,30.605,80.207,30.684,80.194,30.759,80.149,30.79,80.081,30.782,79.925,30.889,79.919,30.89,79.917,30.894,79.872,30.925,79.795,30.968,79.664,30.965,79.565,30.949,79.493,30.994,79.388,31.064,79.37,31.08,79.339,31.106,79.233,31.242,79.107,31.403,79.044,31.426,79.011,31.414,78.974,31.329,78.946,31.337,78.9,31.331,78.845,31.302,78.792,31.294,78.758,31.302,78.744,31.324,78.759,31.437,78.727,31.472,78.755,31.55,78.803,31.618,78.754,31.668,78.693,31.74,78.687,31.806,78.72,31.888,78.735,31.958,78.726,31.984,78.678,32.023,78.496,32.216,78.486,32.236,78.455,32.3,78.441,32.397,78.417,32.467,78.39,32.52,78.392,32.545,78.413,32.558,78.526,32.571,78.632,32.579,78.701,32.597,78.737,32.558,78.754,32.499,78.771,32.468,78.838,32.412,78.919,32.358,78.998,32.365,79.067,32.388,79.127,32.476,79.17,32.497,79.219,32.501,79.219,32.508,79.217,32.564,79.234,32.703,79.228,32.759,79.206,32.809,79.21,32.865,79.202,32.946,79.146,33.001,79.109,33.023,79.103,33.053,79.122,33.108,79.135,33.172,79.113,33.226,79.067,33.25,79.013,33.291,78.948,33.347,78.917,33.387,78.865,33.431,78.802,33.5,78.79,33.65,78.784,33.809,78.762,33.888,78.727,34.013,78.732,34.056,78.753,34.088,78.932,34.189,78.971,34.228,78.977,34.258,78.97,34.303,78.936,34.352,78.865,34.39,78.763,34.453,78.671,34.518,78.516,34.558,78.327,34.606,78.282,34.654,78.236,34.77,78.158,34.946,78.076,35.135,78.012,35.251,78.009,35.307,78.047,35.449,78.043,35.48,78.009,35.49,77.946,35.472,77.895,35.449,77.852,35.461,77.811,35.485,77.803,35.493,77.799,35.496,77.724,35.481,77.573,35.472,77.52,35.473,77.446,35.476,77.295,35.508,77.09,35.552,76.879,35.613,76.767,35.662,76.728,35.679,76.632,35.729,76.563,35.773,76.551,35.887,76.502,35.878,76.386,35.837,76.252,35.811,76.178,35.811,76.148,35.829,76.103,35.949,76.071,35.983,76.01,35.996,75.945,36.018,75.912,36.049,75.905,36.088,75.934,36.134,75.969,36.169,75.974,36.382,75.952,36.458,75.933,36.522,75.885,36.601,75.84,36.65,75.772,36.695,75.667,36.742,75.574,36.759,75.46,36.725,75.424,36.738,75.377,36.884,75.347,36.913,75.145,36.973,75.054,36.987,74.949,36.968,74.889,36.952,74.841,36.979,74.766,37.013,74.692,37.036,74.601,37.037,74.541,37.022,74.526,37.031,74.498,37.057,74.376,37.137,74.372,37.158,74.559,37.237,74.669,37.267,74.727,37.291,74.739,37.286,74.767,37.249,74.84,37.225,74.891,37.232,74.918,37.25,75.008,37.294,75.079,37.344,75.119,37.386,75.097,37.451,74.986,37.53,74.916,37.573,74.894,37.601,74.912,37.687,74.938,37.773,74.921,37.805,74.9,37.833,74.891,37.926,74.842,38.038,74.79,38.104,74.775,38.192,74.772,38.275,74.836,38.404,74.812,38.46,74.745,38.51,74.514,38.6,74.277,38.66,74.187,38.658,74.131,38.661,74.065,38.608,74.026,38.54,73.97,38.534,73.869,38.563,73.802,38.607,73.754,38.699,73.717,38.817,73.696,38.854,73.707,38.886,73.73,38.915,73.795,38.941,73.805,38.969,73.796,39.002,73.744,39.045,73.69,39.105,73.607,39.229,73.623,39.298,73.636,39.397,73.632,39.449,73.716,39.462,73.823,39.489,73.873,39.533,73.907,39.579,73.915,39.606,73.883,39.715,73.84,39.763,73.835,39.8,73.856,39.829,73.885,39.878,73.939,39.979,73.992,40.043,74.021,40.059,74.085,40.074,74.243,40.092,74.412,40.137,74.613,40.272,74.68,40.311,74.768,40.33,74.83,40.329,74.842,40.345,74.801,40.429,74.811,40.459,74.835,40.483,74.866,40.494,75.004,40.45,75.111,40.454,75.241,40.48,75.521,40.628,75.556,40.625,75.583,40.605,75.617,40.517,75.656,40.329,75.677,40.306,75.872,40.303,76.004,40.371,76.062,40.388,76.157,40.376,76.206,40.408,76.258,40.431,76.319,40.352,76.396,40.39,76.48,40.45,76.521,40.511,76.578,40.578,76.622,40.662,76.64,40.742,76.661,40.78,76.708,40.818,76.824,40.982,76.908,41.024,76.987,41.039,77.182,41.011,77.284,41.014,77.582,40.993,77.719,41.024,77.815,41.056,77.956,41.051,78.123,41.076,78.346,41.281,78.349,41.325,78.362,41.372,78.443,41.418,78.543,41.46,78.743,41.56,79.148,41.719,79.294,41.783,79.354,41.781,79.504,41.821,79.766,41.899,79.84,41.996,79.91,42.015,80.216,42.032,80.235,42.043,80.246,42.06,80.229,42.13,80.209,42.19,80.233,42.208,80.259,42.235,80.255,42.274,80.206,42.399,80.179,42.518,80.162,42.626,80.165,42.666,80.202,42.734,80.25,42.797,80.424,42.856,80.539,42.873,80.544,42.912,80.451,42.936,80.383,42.974,80.371,42.996,80.375,43.02,80.39,43.043,80.507,43.086,80.617,43.128,80.751,43.102,80.778,43.119,80.786,43.162,80.757,43.204,80.73,43.274,80.668,43.31,80.665,43.353,80.704,43.427,80.651,43.564,80.593,43.685,80.496,43.892,80.432,43.952,80.396,44.047,80.355,44.097,80.359,44.171,80.365,44.223,80.355,44.327,80.336,44.438,80.355,44.552,80.391,44.627,80.381,44.655,80.401,44.677,80.455,44.684,80.482,44.715,80.455,44.746,80.361,44.77,80.255,44.808,80.128,44.804,79.997,44.797,79.932,44.825,79.875,44.861,79.872,44.884,79.95,44.944,80.059,45.006,80.228,45.034,80.415,45.075,80.509,45.105,80.635,45.127,80.78,45.136,80.853,45.129,81.04,45.169,81.335,45.246,81.602,45.311,81.692,45.349,81.759,45.311,81.79,45.226,81.867,45.182,81.945,45.161,81.989,45.162,82.123,45.195,82.267,45.219,82.323,45.206,82.397,45.162,82.479,45.124,82.521,45.125,82.559,45.155,82.597,45.216,82.621,45.293,82.626,45.374,82.612,45.424,82.583,45.443,82.452,45.472,82.327,45.52,82.312,45.564,82.315,45.595,82.348,45.672,82.43,45.812,82.512,46.006,82.555,46.159,82.692,46.387,82.8,46.624,82.975,46.966,83.004,47.033,83.02,47.141,83.029,47.186,83.09,47.209,83.193,47.187,83.444,47.109,83.634,47.043,83.714,47.021,83.833,46.998,84.016,46.971,84.122,46.979,84.215,46.995,84.339,46.996,84.532,46.976,84.592,46.975,84.667,46.972,84.72,46.939,84.746,46.864,84.786,46.831,84.858,46.843,85.012,46.909,85.111,46.961,85.233,47.036,85.355,47.047,85.485,47.064,85.53,47.101,85.577,47.188,85.657,47.255,85.67,47.338,85.642,47.397,85.587,47.494,85.588,47.558,85.562,47.746,85.526,47.916,85.562,48.052,85.626,48.204,85.652,48.251,85.692,48.312,85.749,48.385,85.83,48.408,86.056,48.424,86.266,48.455,86.373,48.486,86.483,48.505,86.549,48.529,86.664,48.636,86.718,48.697,86.758,48.861,86.729,48.939,86.753,49.009,86.808,49.05,86.886,49.091,86.938,49.098,87.049,49.11,87.23,49.106,87.323,49.086,87.417,49.077,87.476,49.091,87.516,49.122,87.577,49.132,87.668,49.147,87.763,49.166,87.814,49.162,87.825,49.116,87.816,49.08,87.835,49.032,87.872,49,87.86,48.966,87.807,48.946,87.755,48.919,87.743,48.882,87.809,48.836,87.832,48.792,87.942,48.765,88.028,48.736,88.06,48.707,88.05,48.675,88.011,48.64,87.972,48.603,87.967,48.581,87.98,48.555,88.063,48.538,88.158,48.509,88.31,48.472,88.414,48.403,88.517,48.384,88.567,48.317,88.576,48.22,88.682,48.171,88.838,48.102,88.918,48.089,88.971,48.05,89.048,48.003,89.116,47.988,89.196,47.981,89.33,48.025,89.479,48.029,89.561,48.004,89.638,47.909,89.693,47.879,89.726,47.852,89.778,47.827,89.831,47.823,89.91,47.844,89.959,47.886,90.028,47.878,90.054,47.85,90.067,47.804,90.103,47.745,90.191,47.702,90.313,47.676,90.331,47.655,90.347,47.597,90.381,47.557,90.425,47.504,90.467,47.408,90.476,47.329,90.496,47.285,90.553,47.214,90.643,47.1,90.716,47.004,90.799,46.985,90.87,46.954,90.911,46.883,90.986,46.749,90.998,46.661,91.004,46.596,91.029,46.566,91.034,46.529,90.971,46.388,90.918,46.324,90.912,46.271,90.948,46.177,90.997,46.105,91.002,46.036,90.96,45.985,90.887,45.922,90.852,45.885,90.796,45.854,90.71,45.731,90.671,45.595,90.662,45.525,90.694,45.475,90.75,45.419,90.763,45.371,90.853,45.263,90.877,45.196,90.914,45.194,90.954,45.216,91.05,45.217,91.138,45.194,91.222,45.145,91.312,45.118,91.441,45.125,91.51,45.098,91.584,45.077,91.738,45.069,91.853,45.069,92.03,45.069,92.173,45.035,92.424,45.009,92.579,45.011,92.788,45.036,92.916,45.02,93.294,44.983,93.516,44.944,93.656,44.901,93.755,44.832,93.868,44.724,93.958,44.675,94.199,44.645,94.365,44.519,94.494,44.473,94.712,44.351,94.866,44.303,95.05,44.259,95.35,44.278,95.367,44.262,95.344,44.195,95.326,44.105,95.326,44.039,95.356,44.006,95.471,43.986,95.526,43.954,95.567,43.892,95.591,43.854,95.687,43.664,95.842,43.384,95.86,43.276,95.913,43.206,96.08,43.096,96.168,43.015,96.3,42.929,96.342,42.849,96.352,42.747,96.385,42.72,96.625,42.744,96.833,42.76,97.206,42.79,97.719,42.736,98.248,42.685,98.716,42.639,98.947,42.616,99.468,42.568,99.757,42.629,99.984,42.677,100.086,42.671,100.519,42.617,100.773,42.588,101.092,42.551,101.314,42.538,101.495,42.539,101.579,42.524,101.66,42.5,101.714,42.466,101.88,42.292,101.973,42.216,102.157,42.158,102.575,42.092,102.807,42.052,103.073,42.006,103.248,41.937,103.45,41.856,103.711,41.751,103.997,41.797,104.305,41.846,104.498,41.877,104.498,41.659,104.774,41.641,104.86,41.644,104.982,41.596,105.051,41.616,105.115,41.663,105.197,41.738,105.314,41.771,105.517,41.855,105.566,41.875,105.868,41.994,106.317,42.141,106.519,42.212,106.579,42.227,106.693,42.264,106.77,42.289,106.906,42.309,107.091,42.322,107.292,42.349,107.749,42.401,107.806,42.406,108.062,42.427,108.171,42.447,108.334,42.437,108.546,42.429,108.687,42.416,108.875,42.426,109.132,42.441,109.34,42.438,109.443,42.456,109.596,42.511,109.698,42.554,109.859,42.606,110.058,42.661,110.197,42.71,110.289,42.743,110.4,42.774,110.43,42.814,110.462,42.844,110.521,42.895,110.628,42.991,110.709,43.074,110.749,43.111,110.84,43.194,110.913,43.257,111.007,43.341,111.087,43.369,111.187,43.392,111.451,43.475,111.504,43.493,111.547,43.496,111.641,43.563,111.72,43.621,111.771,43.665,111.878,43.68,111.933,43.711,111.943,43.752,111.932,43.815,111.88,43.879,111.837,43.935,111.684,44.041,111.603,44.107,111.52,44.192,111.486,44.272,111.43,44.322,111.402,44.367,111.411,44.419,111.489,44.512,111.515,44.57,111.547,44.673,111.621,44.827,111.681,44.899,111.751,44.97,111.898,45.064,112.033,45.082,112.113,45.063,112.292,45.063,112.411,45.058,112.499,45.011,112.597,44.918,112.707,44.883,113.049,44.81,113.196,44.795,113.301,44.792,113.456,44.767,113.508,44.762,113.587,44.746,113.653,44.763,113.752,44.826,113.877,44.896,113.931,44.912,114.03,44.943,114.08,44.971,114.167,45.05,114.281,45.111,114.419,45.203,114.487,45.272,114.502,45.316,114.517,45.365,114.56,45.39,114.644,45.413,114.739,45.42,114.919,45.378,115.163,45.39,115.217,45.396,115.439,45.42,115.539,45.44,115.681,45.458,115.789,45.535,115.934,45.626,116.04,45.677,116.11,45.687,116.198,45.739,116.241,45.796,116.229,45.846,116.213,45.887,116.265,45.963,116.358,46.097,116.445,46.159,116.517,46.209,116.563,46.29,116.619,46.313,116.689,46.322,116.787,46.377,116.859,46.388,116.979,46.362,117.156,46.355,117.269,46.352,117.333,46.362,117.357,46.391,117.356,46.437,117.392,46.538,117.406,46.571,117.438,46.586,117.547,46.588,117.621,46.552,117.671,46.522,117.741,46.518,117.813,46.538,117.91,46.619,118.071,46.667,118.157,46.679,118.309,46.717,118.404,46.703,118.58,46.692,118.649,46.702,118.723,46.692,118.79,46.747,118.844,46.76,118.957,46.735,119.029,46.692,119.162,46.639,119.332,46.614,119.474,46.627,119.62,46.604,119.707,46.606,119.747,46.627,119.867,46.672,119.896,46.733,119.884,46.791,119.898,46.858,119.863,46.907,119.788,46.979,119.76,47.027,119.757,47.09,119.711,47.15,119.6,47.222,119.527,47.256,119.377,47.381,119.326,47.41,119.309,47.431,119.291,47.473,119.235,47.493,119.162,47.525,119.123,47.558,119.097,47.616,119.082,47.654,119.018,47.685,118.953,47.703,118.88,47.725,118.76,47.758,118.691,47.822,118.568,47.943,118.498,47.984,118.24,48,118.147,48.029,118.042,48.019,117.979,48,117.84,48,117.768,47.988,117.677,47.908,117.555,47.805,117.455,47.741,117.384,47.676,117.351,47.652,117.286,47.666,117.197,47.74,117.07,47.806,116.952,47.837,116.901,47.853,116.761,47.87,116.652,47.865,116.513,47.84,116.378,47.844,116.317,47.86,116.231,47.858,116.075,47.79,115.994,47.711,115.898,47.687,115.812,47.738,115.712,47.799,115.616,47.875,115.558,47.945,115.525,48.131,115.639,48.186,115.786,48.248,115.797,48.346,115.792,48.456,115.821,48.577,115.954,48.689,116.025,48.782,116.034,48.84,116.098,48.936,116.16,49.037,116.243,49.17,116.402,49.406,116.59,49.685,116.683,49.824,116.889,49.738,117.022,49.693,117.246,49.625,117.477,49.609,117.698,49.536,117.813,49.514,117.873,49.513,118.187,49.693,118.452,49.844,118.756,49.963,118.979,49.979,119.147,50.013,119.26,50.066,119.326,50.155,119.346,50.279,119.302,50.354,119.192,50.38,119.164,50.406,119.217,50.433,119.256,50.484,119.281,50.561,119.344,50.634,119.446,50.703,119.502,50.779,119.512,50.863,119.573,50.947,119.685,51.03,119.746,51.108,119.757,51.179,119.813,51.267,119.967,51.422,120.067,51.601,120.237,51.723,120.511,51.849,120.681,51.973,120.75,52.097,120.745,52.205,120.665,52.3,120.65,52.396,120.699,52.494,120.656,52.567,120.521,52.615,120.36,52.627,120.173,52.602,120.068,52.633,120.044,52.718,120.095,52.787,120.218,52.84,120.421,52.968,120.704,53.172,120.985,53.285,121.405,53.317,121.744,53.384,122.089,53.451,122.338,53.485,122.38,53.463,122.516,53.457,122.745,53.469,122.958,53.498,123.154,53.545,123.31,53.556,123.424,53.531,123.489,53.529,123.535,53.526,123.56,53.527,123.608,53.547,123.741,53.511,123.995,53.406,124.154,53.359,124.22,53.37,124.291,53.341,124.369,53.271,124.466,53.23,124.64,53.211,124.812,53.134,124.882,53.13,124.907,53.173,124.971,53.197,125.075,53.204,125.226,53.166,125.422,53.084,125.546,53.048,125.596,53.057,125.649,53.042,125.692,53.004,125.695,52.956,125.681,52.931,125.728,52.891,125.783,52.891,125.872,52.872,125.942,52.801,126.004,52.768,126.048,52.739,126.056,52.716,126.06,52.692,126.047,52.673,126.023,52.643,126.016,52.61,126.046,52.573,126.157,52.547,126.194,52.519,126.203,52.484,126.238,52.445,126.313,52.4,126.342,52.362,126.324,52.332,126.346,52.306,126.383,52.287,126.392,52.215,126.395,52.173,126.456,52.126,126.468,52.031,126.511,51.926,126.654,51.781,126.701,51.703,126.689,51.61,126.709,51.566,126.775,51.545,126.805,51.506,126.802,51.448,126.827,51.412,126.848,51.374,126.834,51.315,126.854,51.261,126.888,51.23,126.912,51.172,126.925,51.1,127.02,50.986,127.198,50.829,127.307,50.708,127.347,50.621,127.347,50.55,127.308,50.494,127.306,50.454,127.341,50.428,127.351,50.394,127.337,50.35,127.395,50.299,127.59,50.209,127.512,50.072,127.492,49.975,127.502,49.873,127.551,49.802,127.637,49.76,127.69,49.717,127.711,49.672,127.814,49.622,128,49.569,128.237,49.559,128.527,49.594,128.704,49.6,128.769,49.577,128.791,49.542,128.77,49.495,128.819,49.464,128.938,49.449,129.02,49.419,129.065,49.375,129.12,49.362,129.185,49.381,129.248,49.379,129.31,49.354,129.35,49.362,129.385,49.389,129.441,49.389,129.498,49.389,129.534,49.323,129.591,49.287,129.671,49.279,129.793,49.199,130.037,48.972,130.196,48.892,130.355,48.866,130.553,48.861,130.617,48.773,130.566,48.68,130.552,48.602,130.597,48.575,130.659,48.483,130.747,48.43,130.763,48.388,130.804,48.342,130.787,48.255,130.712,48.128,130.733,48.019,130.849,47.929,130.915,47.843,130.933,47.76,130.962,47.709,131.003,47.691,131.122,47.698,131.319,47.728,131.464,47.723,131.557,47.682,131.785,47.681,132.15,47.718,132.38,47.729,132.476,47.715,132.562,47.769,132.637,47.89,132.707,47.947,132.773,47.94,132.877,47.979,133.02,48.064,133.144,48.106,133.301,48.102,133.468,48.097,133.573,48.133,133.672,48.208,133.842,48.274,134.206,48.36,134.293,48.373,134.335,48.369,134.456,48.355,134.564,48.322,134.665,48.254,134.681,48.21,134.669,48.153,134.647,48.12,134.605,48.083,134.566,48.023,134.591,47.975,134.65,47.874,134.699,47.801,134.752,47.715,134.728,47.684,134.696,47.625,134.596,47.524,134.542,47.485,134.483,47.447,134.383,47.438,134.339,47.429,134.291,47.414,134.26,47.378,134.225,47.353,134.168,47.302,134.163,47.259,134.189,47.194,134.202,47.128,134.137,47.069,134.086,46.978,134.071,46.951,134.046,46.882,134.039,46.858,134.023,46.713,133.958,46.614,133.867,46.499,133.887,46.431,133.903,46.367,133.88,46.336,133.875,46.309,133.861,46.248,133.833,46.224,133.75,46.186,133.701,46.14,133.711,46.07,133.686,46.009,133.648,45.955,133.608,45.92,133.551,45.898,133.513,45.879,133.485,45.81,133.476,45.758,133.449,45.705,133.466,45.651,133.436,45.605,133.355,45.572,133.31,45.553,133.267,45.545,133.186,45.495,133.113,45.321,133.097,45.22,133.113,45.131,133.012,45.075,132.936,45.03,132.889,45.046,132.839,45.061,132.723,45.081,132.666,45.094,132.549,45.123,132.363,45.16,132.181,45.203,132.067,45.226,131.978,45.244,131.909,45.274,131.852,45.327,131.795,45.305,131.742,45.243,131.654,45.205,131.614,45.137,131.579,45.084,131.488,45.013,131.447,44.984,131.268,44.936,131.228,44.92,131.082,44.91,131.033,44.889,130.982,44.844,130.968,44.8,131.004,44.753,131.061,44.66,131.087,44.596,131.126,44.469,131.255,44.072,131.213,44.003,131.174,43.705,131.184,43.651,131.18,43.567,131.182,43.506,131.209,43.49,131.244,43.469,131.262,43.433,131.257,43.378,131.239,43.338,131.212,43.258,131.176,43.142,131.136,43.098,131.109,43.062,131.086,43.038,131.083,42.956,131.069,42.902,131.006,42.883,130.943,42.852,130.869,42.863,130.803,42.857,130.722,42.836,130.577,42.812,130.493,42.779,130.453,42.755,130.425,42.727,130.42,42.7,130.439,42.686,130.521,42.674,130.577,42.623,130.584,42.567,130.527,42.535,130.498,42.571,130.45,42.582,130.361,42.631,130.296,42.685,130.247,42.745,130.249,42.873,130.24,42.892,130.151,42.918,130.125,42.956,130.083,42.974,130.022,42.963,129.977,42.975,129.941,42.996,129.898,42.998,129.861,42.965,129.842,42.894,129.779,42.777,129.773,42.705,129.746,42.604,129.72,42.475,129.698,42.448,129.628,42.444,129.604,42.436,129.568,42.392,129.524,42.385,129.485,42.41,129.424,42.436,129.366,42.439,129.314,42.414,129.253,42.358,129.218,42.313,129.205,42.271,129.196,42.218,129.134,42.169,129.077,42.142,128.961,42.069,128.923,42.038,128.84,42.038,128.749,42.041,128.627,42.021,128.427,42.011,128.308,42.026,128.16,42.012,128.045,41.988,128.029,41.952,128.033,41.898,128.056,41.864,128.084,41.841,128.132,41.769,128.182,41.7,128.258,41.655,128.289,41.607,128.291,41.563,128.255,41.507,128.2,41.433,128.149,41.388,128.111,41.389,128.053,41.416,128.013,41.449,127.919,41.461,127.688,41.44,127.572,41.455,127.517,41.482,127.42,41.484,127.271,41.52,127.18,41.531,127.137,41.555,127.128,41.607,127.085,41.644,127.061,41.687,127.007,41.742,126.955,41.769,126.904,41.781,126.847,41.748,126.788,41.718,126.743,41.725,126.722,41.717,126.697,41.692,126.601,41.641,126.578,41.594,126.54,41.496,126.514,41.394,126.49,41.358,126.451,41.352,126.412,41.321,126.329,41.226,126.254,41.138,126.145,41.078,126.093,41.024,126.067,40.974,125.989,40.905,125.875,40.892,125.784,40.872,125.728,40.867,125.688,40.839,125.659,40.796,125.645,40.779,125.594,40.779,125.543,40.743,125.417,40.66,125.314,40.645,125.186,40.589,125.073,40.547,125.026,40.524,125.013,40.498,124.997,40.465,124.942,40.458,124.889,40.46,124.772,40.384,124.712,40.319,124.481,40.182,124.387,40.104,124.362,40.004,124.35,40.012,124.267,39.924,124.106,39.841,123.76,39.822,123.651,39.882,123.611,39.841,123.581,39.786,123.49,39.768,123.348,39.763,123.269,39.727,123.227,39.687,123.032,39.674,122.961,39.62,122.84,39.601,122.335,39.366,122.225,39.267,122.121,39.152,122.048,39.094,121.982,39.053,121.923,39.037,121.864,38.996,121.805,38.991,121.745,39.01,121.677,39.003,121.633,38.955,121.67,38.892,121.65,38.865,121.517,38.831,121.32,38.808,121.236,38.767,121.207,38.744,121.164,38.732,121.122,38.813,121.107,38.921,121.188,38.947,121.263,38.96,121.68,39.109,121.628,39.22,121.665,39.269,121.758,39.348,121.818,39.387,121.785,39.401,121.513,39.375,121.356,39.377,121.275,39.385,121.3,39.452,121.286,39.519,121.267,39.545,121.406,39.621,121.47,39.64,121.518,39.639,121.514,39.685,121.474,39.755,121.517,39.845,121.801,39.951,121.869,40.046,121.983,40.136,122.191,40.358,122.203,40.396,122.264,40.5,122.275,40.542,122.179,40.603,122.14,40.688,121.859,40.842,121.835,40.974,121.809,40.969,121.766,40.876,121.729,40.846,121.599,40.843,121.537,40.878,121.175,40.901,121.086,40.842,121.003,40.749,120.922,40.683,120.841,40.649,120.771,40.589,120.479,40.231,120.369,40.204,119.85,39.987,119.591,39.903,119.391,39.752,119.322,39.662,119.261,39.561,119.225,39.408,119.04,39.222,118.977,39.183,118.912,39.166,118.826,39.172,118.752,39.16,118.626,39.177,118.472,39.118,118.298,39.067,118.148,39.195,118.041,39.227,117.866,39.191,117.785,39.134,117.617,38.853,117.554,38.691,117.558,38.625,117.656,38.424,117.767,38.312,118.015,38.183,118.543,38.095,118.667,38.126,118.8,38.127,118.94,38.043,119.028,37.904,119.036,37.809,119.038,37.777,119.07,37.749,119.089,37.701,119.033,37.661,118.991,37.641,118.955,37.494,118.953,37.331,118.998,37.277,119.112,37.201,119.287,37.138,119.45,37.125,119.761,37.155,119.888,37.253,119.88,37.296,119.883,37.351,120.156,37.495,120.312,37.623,120.287,37.656,120.257,37.679,120.285,37.692,120.37,37.701,120.75,37.834,121.049,37.725,121.22,37.6,121.388,37.579,121.505,37.515,121.64,37.46,121.816,37.457,121.965,37.445,122.01,37.496,122.057,37.529,122.11,37.522,122.169,37.456,122.338,37.405,122.493,37.408,122.602,37.426,122.667,37.403,122.573,37.318,122.587,37.181,122.516,37.138,122.447,37.068,122.487,37.022,122.523,37.003,122.52,36.947,122.457,36.915,122.341,36.832,122.274,36.834,122.242,36.85,122.22,36.88,122.203,36.927,122.162,36.959,122.05,36.971,121.933,36.959,121.67,36.836,121.413,36.738,121.144,36.66,121.054,36.611,120.99,36.598,120.879,36.635,120.811,36.633,120.797,36.607,120.883,36.539,120.905,36.485,120.896,36.444,120.847,36.426,120.776,36.456,120.712,36.413,120.682,36.341,120.681,36.168,120.638,36.13,120.519,36.109,120.393,36.054,120.348,36.079,120.33,36.11,120.343,36.189,120.328,36.228,120.27,36.226,120.183,36.202,120.117,36.15,120.094,36.119,120.181,36.017,120.265,36.007,120.285,35.984,120.219,35.935,120.055,35.861,120.027,35.799,119.979,35.74,119.912,35.693,119.866,35.644,119.811,35.618,119.72,35.589,119.608,35.47,119.526,35.359,119.43,35.301,119.353,35.114,119.216,35.012,119.165,34.849,119.201,34.748,119.351,34.749,119.427,34.714,119.583,34.582,119.77,34.496,119.964,34.448,120.201,34.326,120.267,34.274,120.323,34.169,120.426,33.866,120.5,33.716,120.505,33.638,120.616,33.491,120.734,33.237,120.871,33.017,120.897,32.843,120.853,32.764,120.853,32.661,120.99,32.567,121.293,32.457,121.342,32.425,121.401,32.372,121.404,32.206,121.451,32.153,121.491,32.121,121.674,32.051,121.751,31.993,121.832,31.9,121.856,31.816,121.866,31.704,121.764,31.7,121.681,31.712,121.352,31.859,121.266,31.863,121.146,31.842,120.974,31.869,120.792,32.032,120.661,32.081,120.52,32.106,120.184,31.966,120.099,31.976,120.074,31.96,120.036,31.936,120.192,31.906,120.347,31.952,120.497,32.02,120.716,31.984,120.752,31.923,120.788,31.82,120.938,31.75,121.055,31.719,121.205,31.628,121.351,31.485,121.661,31.32,121.786,31.163,121.834,31.062,121.878,30.917,121.769,30.87,121.675,30.864,121.528,30.841,121.419,30.79,121.31,30.7,120.998,30.558,120.938,30.47,120.897,30.393,120.821,30.355,120.63,30.391,120.45,30.388,120.246,30.284,120.195,30.241,120.229,30.25,120.261,30.263,120.353,30.247,120.495,30.303,120.633,30.133,120.904,30.161,121.159,30.302,121.258,30.304,121.341,30.282,121.433,30.227,121.678,29.979,121.812,29.952,121.944,29.894,122.017,29.888,122.083,29.87,121.906,29.78,121.677,29.584,121.575,29.537,121.506,29.485,121.69,29.511,121.822,29.605,121.888,29.628,121.941,29.606,121.968,29.491,121.918,29.135,121.854,29.129,121.791,29.226,121.717,29.256,121.656,29.236,121.534,29.237,121.487,29.193,121.448,29.131,121.521,29.118,121.665,29.011,121.68,28.953,121.641,28.916,121.54,28.932,121.663,28.851,121.63,28.768,121.59,28.735,121.519,28.714,121.475,28.641,121.538,28.521,121.602,28.367,121.61,28.292,121.51,28.324,121.355,28.23,121.272,28.222,121.217,28.346,121.146,28.327,121.098,28.291,121.035,28.157,120.959,28.037,120.892,28.004,120.813,28.013,120.748,28.01,120.763,27.977,120.833,27.938,120.833,27.891,120.685,27.745,120.661,27.688,120.665,27.639,120.588,27.581,120.629,27.482,120.608,27.412,120.54,27.318,120.469,27.256,120.385,27.156,120.279,27.097,120.139,26.886,120.097,26.781,120.087,26.672,120.043,26.634,119.968,26.586,119.882,26.61,119.879,26.683,119.842,26.689,119.821,26.737,119.815,26.798,119.824,26.846,119.789,26.831,119.767,26.775,119.71,26.729,119.652,26.747,119.588,26.785,119.59,26.73,119.624,26.676,119.638,26.621,119.726,26.609,119.785,26.547,119.831,26.45,119.84,26.414,119.876,26.371,119.881,26.334,119.797,26.3,119.693,26.236,119.567,26.127,119.463,26.055,119.37,26.054,119.313,26.063,119.232,26.104,119.139,26.122,119.264,25.975,119.332,25.949,119.418,25.954,119.501,26.009,119.619,26.004,119.648,25.919,119.617,25.823,119.553,25.699,119.539,25.591,119.619,25.437,119.622,25.391,119.593,25.368,119.499,25.409,119.422,25.46,119.344,25.446,119.263,25.468,119.18,25.45,119.146,25.414,119.169,25.356,119.244,25.307,119.286,25.232,119.236,25.206,119.025,25.223,118.978,25.209,118.914,25.127,118.956,25.005,118.909,24.929,118.822,24.911,118.708,24.85,118.637,24.836,118.64,24.809,118.692,24.782,118.719,24.746,118.657,24.621,118.56,24.58,118.412,24.601,118.295,24.573,118.195,24.626,118.087,24.627,118.014,24.56,118.006,24.482,117.935,24.474,117.897,24.48,117.843,24.474,117.848,24.432,117.879,24.396,118.024,24.38,118.051,24.327,118.056,24.246,117.904,24.106,117.839,24.012,117.742,24.015,117.668,23.939,117.628,23.837,117.579,23.857,117.466,23.841,117.433,23.792,117.46,23.771,117.462,23.736,117.417,23.621,117.368,23.589,117.347,23.636,117.331,23.709,117.291,23.714,117.225,23.647,117.148,23.599,117.083,23.579,117.033,23.623,116.911,23.647,116.861,23.453,116.76,23.383,116.712,23.36,116.629,23.354,116.682,23.327,116.699,23.278,116.669,23.228,116.586,23.218,116.538,23.18,116.52,23.007,116.471,22.946,116.346,22.941,116.252,22.981,116.222,22.95,116.206,22.919,116.157,22.887,116.063,22.879,115.852,22.802,115.756,22.824,115.64,22.853,115.561,22.825,115.535,22.765,115.498,22.719,115.383,22.719,115.29,22.776,115.196,22.817,115.092,22.782,115.012,22.709,114.914,22.685,114.896,22.64,114.854,22.617,114.75,22.626,114.711,22.739,114.652,22.755,114.593,22.698,114.572,22.654,114.544,22.621,114.554,22.529,114.496,22.527,114.42,22.583,114.341,22.593,114.266,22.541,114.228,22.554,114.188,22.565,114.123,22.565,114.098,22.551,114.05,22.543,114.018,22.514,114.015,22.512,113.931,22.531,113.828,22.607,113.754,22.734,113.661,22.802,113.62,22.861,113.603,22.969,113.586,23.02,113.592,23.077,113.621,23.127,113.52,23.102,113.445,23.055,113.46,22.996,113.442,22.941,113.331,22.912,113.338,22.889,113.345,22.865,113.432,22.789,113.45,22.726,113.485,22.692,113.553,22.594,113.551,22.404,113.589,22.35,113.576,22.297,113.549,22.225,113.547,22.224,113.527,22.246,113.494,22.242,113.481,22.217,113.479,22.196,113.473,22.194,113.416,22.178,113.367,22.165,113.328,22.145,113.266,22.089,113.149,22.075,113.089,22.208,113.008,22.119,112.984,21.938,112.954,21.907,112.904,21.881,112.809,21.945,112.725,21.902,112.661,21.859,112.634,21.82,112.586,21.777,112.495,21.818,112.421,21.881,112.439,21.927,112.429,21.958,112.396,21.981,112.36,21.978,112.377,21.917,112.39,21.801,112.356,21.768,112.305,21.742,112.193,21.763,112.117,21.806,112.025,21.843,111.944,21.85,111.926,21.776,111.873,21.717,111.825,21.71,111.776,21.719,111.712,21.655,111.682,21.608,111.603,21.559,111.392,21.535,111.319,21.486,111.221,21.494,111.144,21.482,111.101,21.485,111.061,21.511,111.017,21.512,110.997,21.43,110.878,21.396,110.771,21.387,110.652,21.279,110.567,21.214,110.504,21.207,110.458,21.231,110.435,21.327,110.411,21.338,110.397,21.248,110.375,21.172,110.331,21.131,110.194,21.038,110.154,20.945,110.18,20.859,110.365,20.838,110.388,20.791,110.371,20.752,110.326,20.72,110.313,20.672,110.512,20.518,110.518,20.46,110.487,20.427,110.45,20.355,110.345,20.295,110.123,20.264,109.938,20.295,109.883,20.364,109.886,20.413,109.932,20.399,109.984,20.403,109.968,20.448,109.946,20.474,109.861,20.514,109.792,20.622,109.805,20.711,109.767,20.781,109.726,20.839,109.685,20.874,109.663,20.917,109.704,21.053,109.681,21.132,109.76,21.228,109.78,21.337,109.921,21.376,109.931,21.481,109.83,21.484,109.759,21.56,109.743,21.528,109.687,21.525,109.594,21.672,109.566,21.691,109.521,21.693,109.544,21.538,109.436,21.479,109.347,21.454,109.22,21.443,109.149,21.426,109.082,21.44,109.098,21.487,109.133,21.544,109.102,21.59,109.031,21.627,108.922,21.624,108.846,21.634,108.772,21.63,108.744,21.651,108.675,21.725,108.616,21.77,108.589,21.816,108.616,21.869,108.594,21.901,108.48,21.905,108.481,21.829,108.493,21.739,108.526,21.671,108.502,21.633,108.444,21.607,108.383,21.679,108.355,21.697,108.325,21.694,108.302,21.622,108.246,21.558,108.146,21.565,108.067,21.526,107.973,21.508]],[[110.889,19.992,110.938,19.948,110.971,19.883,110.998,19.765,111.014,19.655,110.913,19.586,110.822,19.558,110.641,19.291,110.603,19.207,110.572,19.172,110.563,19.135,110.566,19.099,110.519,18.97,110.478,18.813,110.451,18.748,110.4,18.698,110.334,18.673,110.291,18.67,110.252,18.656,110.156,18.57,110.049,18.505,110.066,18.476,110.067,18.448,110.02,18.416,109.968,18.422,109.816,18.397,109.76,18.348,109.703,18.259,109.681,18.247,109.59,18.226,109.519,18.218,109.4,18.281,109.341,18.3,109.183,18.325,109.03,18.368,108.922,18.416,108.702,18.535,108.676,18.75,108.638,18.866,108.636,18.908,108.65,19.265,108.666,19.304,108.694,19.338,108.791,19.418,108.903,19.481,109.063,19.614,109.179,19.674,109.277,19.761,109.22,19.757,109.177,19.768,109.219,19.843,109.263,19.883,109.315,19.904,109.418,19.889,109.514,19.904,109.584,19.97,109.651,19.984,109.906,19.963,110.083,19.993,110.172,20.054,110.213,20.056,110.344,20.039,110.392,19.976,110.388,20.018,110.394,20.059,110.418,20.055,110.588,19.976,110.589,20.072,110.598,20.098,110.652,20.138,110.679,20.137,110.745,20.059,110.809,20.014,110.889,19.992]]]},{"n":"Macau","c":"MO","r":4,"ml":4,"lx":113.56,"ly":22.13,"b":[113.479,22.196,113.548,22.246],"p":[[[113.479,22.196,113.481,22.217,113.494,22.242,113.527,22.246,113.548,22.223,113.546,22.221,113.499,22.202,113.484,22.198,113.479,22.196]]]},{"n":"Hong Kong","c":"HK","r":4,"ml":4,"lx":114.1,"ly":22.45,"b":[113.839,22.195,114.335,22.565],"p":[[[114.015,22.512,114.018,22.514,114.05,22.543,114.098,22.551,114.123,22.565,114.188,22.565,114.228,22.554,114.266,22.541,114.27,22.537,114.291,22.499,114.285,22.458,114.325,22.437,114.335,22.396,114.291,22.374,114.288,22.325,114.268,22.296,114.139,22.348,114.033,22.376,113.937,22.365,113.903,22.396,113.896,22.428,114.007,22.484,114.015,22.512]],[[114.232,22.211,114.207,22.195,114.139,22.268,114.134,22.292,114.187,22.297,114.247,22.264,114.244,22.234,114.232,22.211]],[[113.998,22.21,113.877,22.21,113.852,22.22,113.839,22.242,113.882,22.28,114.044,22.333,114.003,22.278,113.998,22.21]]]},{"n":"Chile","c":"CL","r":2,"ml":1.7,"lx":-72.32,"ly":-38.15,"b":[-109.434,-55.892,-66.436,-17.506],"p":[[[-109.28,-27.14,-109.434,-27.171,-109.429,-27.116,-109.39,-27.068,-109.276,-27.096,-109.223,-27.101,-109.28,-27.14]],[[-78.804,-33.646,-78.983,-33.668,-78.989,-33.662,-78.979,-33.644,-78.938,-33.614,-78.888,-33.576,-78.877,-33.575,-78.859,-33.578,-78.838,-33.585,-78.785,-33.61,-78.769,-33.627,-78.775,-33.642,-78.804,-33.646]],[[-70.418,-18.346,-70.377,-18.334,-70.282,-18.325,-70.184,-18.325,-70.059,-18.283,-69.926,-18.206,-69.84,-18.093,-69.803,-17.99,-69.802,-17.9,-69.842,-17.785,-69.852,-17.704,-69.806,-17.665,-69.685,-17.65,-69.586,-17.573,-69.511,-17.506,-69.495,-17.62,-69.358,-17.772,-69.313,-17.943,-69.282,-17.965,-69.094,-18.05,-69.09,-18.071,-69.118,-18.103,-69.145,-18.144,-69.126,-18.202,-69.092,-18.282,-69.081,-18.357,-69.06,-18.433,-69.039,-18.55,-69.027,-18.656,-68.979,-18.813,-68.969,-18.91,-68.968,-18.968,-68.931,-19.025,-68.858,-19.093,-68.759,-19.162,-68.681,-19.242,-68.621,-19.297,-68.548,-19.341,-68.492,-19.382,-68.47,-19.41,-68.463,-19.433,-68.487,-19.454,-68.575,-19.56,-68.698,-19.721,-68.696,-19.741,-68.578,-19.857,-68.559,-19.902,-68.561,-19.967,-68.6,-20.045,-68.727,-20.07,-68.756,-20.091,-68.759,-20.116,-68.73,-20.148,-68.735,-20.225,-68.689,-20.31,-68.712,-20.339,-68.759,-20.378,-68.761,-20.416,-68.745,-20.459,-68.696,-20.493,-68.5,-20.612,-68.484,-20.628,-68.487,-20.641,-68.563,-20.72,-68.571,-20.769,-68.569,-20.85,-68.558,-20.902,-68.534,-20.924,-68.435,-20.948,-68.314,-21.13,-68.197,-21.3,-68.199,-21.447,-68.186,-21.619,-68.112,-21.753,-68.102,-21.861,-68.077,-21.983,-67.988,-22.057,-67.954,-22.204,-67.945,-22.282,-67.95,-22.334,-67.882,-22.493,-67.874,-22.631,-67.89,-22.729,-67.889,-22.784,-67.879,-22.823,-67.821,-22.858,-67.794,-22.879,-67.707,-22.889,-67.58,-22.892,-67.362,-22.855,-67.195,-22.822,-67.009,-23.001,-67.09,-23.245,-67.219,-23.634,-67.319,-23.935,-67.336,-23.975,-67.356,-24.034,-67.572,-24.119,-67.886,-24.243,-68.047,-24.308,-68.25,-24.392,-68.3,-24.46,-68.358,-24.497,-68.423,-24.545,-68.447,-24.597,-68.507,-24.63,-68.562,-24.747,-68.562,-24.838,-68.527,-24.899,-68.466,-24.925,-68.447,-24.999,-68.428,-25.051,-68.384,-25.092,-68.395,-25.125,-68.431,-25.149,-68.496,-25.163,-68.541,-25.237,-68.592,-25.42,-68.6,-25.486,-68.542,-25.652,-68.511,-25.741,-68.427,-26.065,-68.415,-26.154,-68.53,-26.277,-68.576,-26.352,-68.592,-26.418,-68.592,-26.47,-68.581,-26.518,-68.485,-26.67,-68.373,-26.806,-68.319,-26.878,-68.319,-26.973,-68.346,-27.028,-68.405,-27.048,-68.537,-27.085,-68.592,-27.14,-68.652,-27.148,-68.71,-27.104,-68.77,-27.115,-68.846,-27.154,-68.875,-27.247,-68.942,-27.405,-68.999,-27.449,-69.042,-27.57,-69.119,-27.744,-69.155,-27.848,-69.174,-27.925,-69.251,-27.974,-69.341,-28.071,-69.41,-28.165,-69.437,-28.193,-69.489,-28.201,-69.527,-28.286,-69.657,-28.414,-69.688,-28.562,-69.735,-28.641,-69.743,-28.784,-69.815,-29.046,-69.828,-29.103,-69.9,-29.149,-69.996,-29.25,-70.027,-29.324,-69.983,-29.545,-69.928,-29.769,-69.924,-29.874,-69.945,-30.016,-69.96,-30.078,-69.924,-30.104,-69.863,-30.12,-69.844,-30.175,-69.888,-30.213,-69.907,-30.282,-69.956,-30.358,-70.102,-30.388,-70.153,-30.361,-70.17,-30.386,-70.161,-30.44,-70.194,-30.505,-70.269,-30.677,-70.319,-30.834,-70.348,-30.902,-70.336,-30.96,-70.312,-30.993,-70.309,-31.023,-70.351,-31.06,-70.388,-31.121,-70.429,-31.129,-70.473,-31.113,-70.52,-31.148,-70.529,-31.223,-70.555,-31.317,-70.566,-31.428,-70.585,-31.569,-70.526,-31.666,-70.45,-31.842,-70.394,-31.884,-70.331,-31.881,-70.282,-31.917,-70.254,-31.958,-70.291,-32.031,-70.356,-32.042,-70.364,-32.083,-70.345,-32.176,-70.32,-32.267,-70.258,-32.31,-70.23,-32.431,-70.17,-32.472,-70.177,-32.626,-70.116,-32.807,-70.052,-32.86,-70.022,-32.885,-70.042,-32.964,-70.093,-33.027,-70.104,-33.128,-70.085,-33.202,-70.02,-33.271,-69.969,-33.279,-69.896,-33.251,-69.82,-33.284,-69.809,-33.344,-69.798,-33.399,-69.839,-33.47,-69.883,-33.601,-69.894,-33.731,-69.881,-33.93,-69.862,-34.084,-69.857,-34.18,-69.852,-34.224,-69.88,-34.254,-69.946,-34.27,-70.003,-34.276,-70.052,-34.301,-70.063,-34.35,-70.101,-34.432,-70.141,-34.493,-70.211,-34.581,-70.255,-34.673,-70.29,-34.733,-70.287,-34.775,-70.312,-34.855,-70.338,-34.922,-70.393,-35.147,-70.467,-35.194,-70.525,-35.217,-70.555,-35.247,-70.532,-35.308,-70.47,-35.326,-70.449,-35.375,-70.457,-35.452,-70.416,-35.523,-70.42,-35.609,-70.38,-35.772,-70.416,-35.879,-70.404,-35.971,-70.405,-36.062,-70.457,-36.133,-70.563,-36.146,-70.622,-36.212,-70.722,-36.283,-70.733,-36.341,-70.749,-36.393,-70.79,-36.412,-70.853,-36.412,-70.905,-36.42,-70.978,-36.487,-71.056,-36.524,-71.073,-36.578,-71.066,-36.644,-71.107,-36.685,-71.159,-36.762,-71.192,-36.844,-71.159,-36.92,-71.124,-37.057,-71.118,-37.114,-71.163,-37.227,-71.2,-37.3,-71.165,-37.393,-71.135,-37.445,-71.163,-37.559,-71.187,-37.631,-71.168,-37.762,-71.096,-37.91,-71.028,-38.041,-71.018,-38.194,-71,-38.315,-70.968,-38.446,-70.9,-38.498,-70.848,-38.542,-70.859,-38.604,-70.897,-38.681,-70.952,-38.738,-71.087,-38.758,-71.197,-38.809,-71.286,-38.845,-71.353,-38.889,-71.402,-38.935,-71.426,-38.986,-71.409,-39.206,-71.42,-39.287,-71.465,-39.402,-71.508,-39.495,-71.526,-39.523,-71.531,-39.564,-71.539,-39.602,-71.587,-39.611,-71.654,-39.594,-71.693,-39.605,-71.72,-39.635,-71.697,-39.707,-71.672,-39.833,-71.638,-39.887,-71.647,-39.929,-71.66,-40.021,-71.704,-40.095,-71.764,-40.095,-71.802,-40.125,-71.818,-40.177,-71.801,-40.244,-71.723,-40.3,-71.695,-40.335,-71.709,-40.382,-71.769,-40.401,-71.805,-40.439,-71.839,-40.524,-71.884,-40.621,-71.932,-40.692,-71.941,-40.789,-71.873,-40.893,-71.881,-40.995,-71.886,-41.292,-71.892,-41.393,-71.871,-41.561,-71.898,-41.607,-71.911,-41.65,-71.844,-41.772,-71.77,-41.969,-71.75,-42.047,-71.761,-42.101,-71.861,-42.148,-71.944,-42.167,-71.993,-42.134,-72.026,-42.148,-72.064,-42.205,-72.108,-42.252,-72.125,-42.298,-72.078,-42.358,-72.053,-42.473,-72.105,-42.522,-72.144,-42.577,-72.13,-42.648,-72.114,-42.777,-72.146,-42.99,-72.102,-43.066,-72.055,-43.102,-71.899,-43.145,-71.781,-43.167,-71.751,-43.237,-71.764,-43.295,-71.82,-43.323,-71.905,-43.348,-71.905,-43.44,-71.832,-43.527,-71.751,-43.59,-71.733,-43.647,-71.737,-43.705,-71.795,-43.753,-71.716,-43.858,-71.68,-43.93,-71.716,-43.984,-71.767,-44.067,-71.812,-44.106,-71.812,-44.151,-71.831,-44.241,-71.835,-44.33,-71.82,-44.383,-71.326,-44.425,-71.213,-44.441,-71.151,-44.494,-71.16,-44.56,-71.221,-44.631,-71.261,-44.763,-71.358,-44.785,-71.455,-44.75,-71.56,-44.762,-71.652,-44.77,-71.783,-44.774,-71.957,-44.792,-72.064,-44.772,-72.073,-44.82,-72.042,-44.904,-71.812,-44.931,-71.596,-44.979,-71.531,-45.068,-71.443,-45.168,-71.354,-45.23,-71.349,-45.332,-71.49,-45.438,-71.508,-45.513,-71.693,-45.535,-71.746,-45.579,-71.773,-45.724,-71.751,-45.839,-71.68,-45.879,-71.632,-45.954,-71.684,-46.042,-71.809,-46.103,-71.876,-46.161,-71.834,-46.207,-71.778,-46.28,-71.762,-46.32,-71.731,-46.428,-71.695,-46.578,-71.7,-46.651,-71.733,-46.706,-71.856,-46.792,-71.94,-46.831,-71.957,-46.937,-71.963,-47.016,-71.954,-47.087,-71.901,-47.144,-71.905,-47.202,-71.979,-47.214,-72.042,-47.241,-72.103,-47.343,-72.283,-47.446,-72.346,-47.493,-72.342,-47.572,-72.413,-47.686,-72.472,-47.784,-72.518,-47.876,-72.509,-47.973,-72.408,-48.016,-72.328,-48.11,-72.293,-48.229,-72.355,-48.366,-72.498,-48.417,-72.583,-48.475,-72.608,-48.519,-72.586,-48.662,-72.592,-48.73,-72.614,-48.793,-72.651,-48.842,-72.728,-48.896,-72.865,-48.944,-72.982,-48.977,-73.034,-49.014,-73.095,-49.097,-73.149,-49.188,-73.135,-49.301,-73.462,-49.314,-73.484,-49.398,-73.554,-49.464,-73.576,-49.583,-73.505,-49.698,-73.47,-49.795,-73.529,-49.911,-73.508,-50.03,-73.501,-50.125,-73.387,-50.231,-73.312,-50.362,-73.274,-50.473,-73.252,-50.558,-73.222,-50.611,-73.175,-50.67,-73.153,-50.738,-73.082,-50.76,-72.956,-50.696,-72.866,-50.653,-72.804,-50.638,-72.62,-50.648,-72.51,-50.608,-72.46,-50.612,-72.393,-50.634,-72.34,-50.682,-72.301,-50.79,-72.276,-50.91,-72.307,-51.033,-72.359,-51.06,-72.377,-51.095,-72.359,-51.17,-72.302,-51.223,-72.303,-51.299,-72.366,-51.47,-72.408,-51.541,-72.335,-51.62,-72.269,-51.691,-72.137,-51.744,-72.028,-51.819,-71.953,-51.88,-71.971,-51.964,-71.919,-51.99,-71.717,-51.991,-71.415,-51.994,-70.943,-51.998,-70.483,-52.002,-69.96,-52.008,-69.713,-52.075,-69.488,-52.136,-69.206,-52.136,-68.925,-52.208,-68.715,-52.255,-68.59,-52.273,-68.461,-52.29,-68.443,-52.357,-69.007,-52.263,-69.134,-52.211,-69.241,-52.205,-69.447,-52.269,-69.561,-52.422,-69.62,-52.465,-69.763,-52.506,-69.907,-52.514,-70.391,-52.661,-70.563,-52.673,-70.68,-52.712,-70.795,-52.769,-70.839,-52.89,-70.821,-52.963,-70.952,-53.227,-70.984,-53.374,-70.985,-53.448,-70.948,-53.57,-70.996,-53.779,-71.083,-53.825,-71.298,-53.883,-71.444,-53.841,-71.694,-53.803,-71.872,-53.723,-72.101,-53.666,-72.174,-53.632,-72.377,-53.471,-72.398,-53.418,-72.413,-53.35,-72.306,-53.254,-72.249,-53.247,-72.081,-53.25,-71.942,-53.234,-71.853,-53.286,-71.828,-53.398,-71.867,-53.458,-71.903,-53.496,-71.892,-53.524,-71.791,-53.485,-71.741,-53.233,-71.4,-53.107,-71.289,-53.034,-71.18,-52.921,-71.163,-52.888,-71.155,-52.846,-71.227,-52.811,-71.388,-52.764,-71.898,-53.002,-72.129,-53.064,-72.278,-53.132,-72.458,-53.254,-72.493,-53.291,-72.531,-53.372,-72.549,-53.461,-72.727,-53.42,-72.998,-53.291,-73.053,-53.243,-72.998,-53.177,-72.916,-53.122,-72.91,-52.937,-72.889,-52.872,-72.832,-52.82,-72.728,-52.762,-72.676,-52.749,-72.632,-52.774,-72.627,-52.818,-72.453,-52.814,-72.118,-52.65,-71.979,-52.646,-71.797,-52.683,-71.591,-52.661,-71.554,-52.644,-71.511,-52.605,-71.665,-52.56,-71.812,-52.537,-72.226,-52.521,-72.315,-52.539,-72.438,-52.626,-72.478,-52.604,-72.504,-52.56,-72.645,-52.529,-72.712,-52.536,-72.777,-52.577,-72.766,-52.643,-72.802,-52.712,-72.932,-52.782,-73.02,-52.892,-73.016,-52.977,-73.023,-53.022,-73.055,-53.046,-73.122,-53.074,-73.338,-53.055,-73.46,-52.965,-73.508,-52.904,-73.645,-52.837,-73.346,-52.754,-73.241,-52.707,-73.145,-52.602,-73.073,-52.535,-73.124,-52.488,-73.184,-52.488,-73.178,-52.563,-73.244,-52.624,-73.382,-52.595,-73.586,-52.686,-73.711,-52.662,-73.915,-52.688,-74.014,-52.639,-74.036,-52.577,-74,-52.513,-74.037,-52.403,-74.094,-52.376,-74.151,-52.383,-74.177,-52.317,-74.238,-52.202,-74.266,-52.171,-74.296,-52.118,-74.265,-52.105,-74.195,-52.12,-74.134,-52.155,-74.04,-52.159,-73.834,-52.234,-73.749,-52.216,-73.703,-52.199,-73.685,-52.137,-73.684,-52.078,-73.649,-52.078,-73.532,-52.153,-73.458,-52.146,-73.327,-52.166,-73.26,-52.158,-73.137,-52.13,-72.944,-52.047,-72.843,-51.961,-72.795,-51.95,-72.735,-51.961,-72.695,-51.985,-72.695,-52.045,-72.65,-52.1,-72.588,-52.145,-72.571,-52.2,-72.583,-52.254,-72.694,-52.33,-72.714,-52.357,-72.677,-52.385,-72.631,-52.372,-72.569,-52.334,-72.533,-52.282,-72.523,-52.255,-72.519,-52.217,-72.524,-52.17,-72.614,-52.037,-72.625,-52.007,-72.625,-51.946,-72.523,-51.891,-72.494,-51.848,-72.49,-51.764,-72.543,-51.706,-72.761,-51.573,-73.127,-51.44,-73.169,-51.454,-73.197,-51.478,-73.163,-51.496,-73.115,-51.504,-72.789,-51.614,-72.705,-51.678,-72.649,-51.695,-72.583,-51.737,-72.6,-51.799,-72.928,-51.86,-73.189,-51.991,-73.383,-52.07,-73.518,-52.041,-73.582,-51.96,-73.65,-51.856,-73.753,-51.796,-73.811,-51.801,-73.858,-51.79,-73.894,-51.758,-73.973,-51.784,-74.146,-51.712,-74.197,-51.681,-74.07,-51.579,-73.93,-51.618,-73.896,-51.331,-73.94,-51.266,-74.121,-51.195,-74.21,-51.205,-74.332,-51.195,-74.414,-51.162,-74.508,-51.15,-74.587,-51.131,-74.69,-51.087,-74.815,-51.063,-74.983,-50.881,-75.055,-50.786,-75.095,-50.681,-74.837,-50.679,-74.686,-50.662,-74.649,-50.618,-74.702,-50.535,-74.776,-50.47,-74.722,-50.408,-74.644,-50.361,-74.564,-50.382,-74.366,-50.488,-74.331,-50.56,-74.19,-50.778,-74.156,-50.797,-74.139,-50.818,-73.847,-50.94,-73.807,-50.938,-73.825,-50.836,-73.741,-50.697,-73.659,-50.651,-73.618,-50.651,-73.614,-50.628,-73.693,-50.57,-73.654,-50.493,-73.68,-50.49,-73.75,-50.54,-73.892,-50.783,-73.978,-50.827,-74.097,-50.717,-74.164,-50.638,-74.197,-50.61,-74.186,-50.485,-73.95,-50.511,-74.031,-50.47,-74.306,-50.398,-74.374,-50.363,-74.425,-50.35,-74.516,-50.266,-74.63,-50.194,-74.434,-50.065,-74.334,-49.975,-74.019,-50.023,-73.959,-49.995,-74.011,-49.929,-74.073,-49.949,-74.171,-49.907,-74.324,-49.783,-74.319,-49.72,-74.291,-49.604,-74.23,-49.579,-74.102,-49.555,-73.956,-49.593,-73.892,-49.624,-73.836,-49.609,-73.892,-49.523,-73.988,-49.491,-74.094,-49.43,-74.083,-49.362,-74.049,-49.306,-74.023,-49.244,-74.006,-49.158,-74.015,-49.091,-73.985,-49.06,-73.938,-49.046,-73.935,-49.021,-74.028,-49.026,-74.061,-49.111,-74.074,-49.188,-74.14,-49.25,-74.168,-49.321,-74.185,-49.404,-74.221,-49.501,-74.302,-49.464,-74.349,-49.426,-74.367,-49.4,-74.358,-49.351,-74.38,-49.048,-74.382,-48.794,-74.341,-48.596,-74.228,-48.517,-74.176,-48.494,-74.129,-48.504,-74.057,-48.504,-74.009,-48.475,-74.172,-48.427,-74.27,-48.455,-74.343,-48.493,-74.474,-48.464,-74.499,-48.362,-74.577,-48.274,-74.591,-48.162,-74.585,-47.999,-74.4,-48.013,-74.25,-48.045,-73.854,-48.042,-73.528,-48.198,-73.384,-48.177,-73.391,-48.146,-73.501,-48.107,-73.57,-48.019,-73.61,-47.994,-73.629,-47.942,-73.635,-47.88,-73.716,-47.655,-73.748,-47.661,-73.779,-47.738,-73.847,-47.867,-73.941,-47.929,-74.085,-47.955,-74.227,-47.969,-74.351,-47.944,-74.379,-47.891,-74.376,-47.83,-74.43,-47.8,-74.569,-47.773,-74.609,-47.758,-74.655,-47.702,-74.588,-47.618,-74.534,-47.568,-74.467,-47.578,-74.404,-47.6,-74.323,-47.667,-74.243,-47.679,-74.152,-47.627,-74.134,-47.591,-74.191,-47.568,-74.243,-47.56,-74.324,-47.531,-74.483,-47.43,-74.403,-47.328,-74.216,-47.21,-74.158,-47.183,-74.208,-47.083,-74.152,-46.974,-74.209,-46.885,-74.314,-46.788,-74.454,-46.767,-74.484,-46.795,-74.489,-46.835,-74.467,-46.864,-74.48,-46.886,-74.512,-46.885,-74.691,-46.864,-74.811,-46.8,-75.006,-46.741,-75.031,-46.695,-75.053,-46.628,-74.984,-46.512,-75.019,-46.511,-75.146,-46.6,-75.337,-46.647,-75.478,-46.662,-75.54,-46.699,-75.565,-46.729,-75.528,-46.746,-75.446,-46.751,-75.386,-46.863,-75.401,-46.906,-75.43,-46.935,-75.497,-46.94,-75.635,-46.863,-75.708,-46.775,-75.706,-46.705,-75.657,-46.61,-75.437,-46.483,-75.376,-46.429,-75.247,-46.369,-75.075,-46.235,-74.924,-46.16,-74.998,-46.098,-75.075,-46.004,-75.067,-45.875,-74.763,-45.824,-74.631,-45.845,-74.463,-45.841,-74.369,-45.81,-74.301,-45.803,-74.158,-45.767,-74.096,-45.717,-74.082,-45.678,-74.083,-45.645,-74.099,-45.603,-74.123,-45.496,-74.099,-45.46,-74.038,-45.418,-73.957,-45.404,-73.92,-45.408,-73.825,-45.447,-73.844,-45.502,-73.882,-45.569,-73.96,-45.835,-74,-45.895,-74.061,-45.947,-74.02,-46.056,-74.082,-46.132,-74.357,-46.213,-74.393,-46.217,-74.372,-46.246,-74.213,-46.239,-74.09,-46.222,-73.968,-46.154,-73.929,-46.05,-73.879,-45.847,-73.813,-45.818,-73.735,-45.812,-73.695,-45.86,-73.708,-45.967,-73.708,-46.07,-73.811,-46.377,-73.935,-46.501,-73.949,-46.533,-73.944,-46.572,-73.845,-46.566,-73.77,-46.5,-73.716,-46.415,-73.662,-46.297,-73.668,-46.212,-73.652,-46.159,-73.629,-45.987,-73.592,-45.899,-73.594,-45.777,-73.662,-45.731,-73.757,-45.703,-73.78,-45.628,-73.731,-45.48,-73.55,-45.484,-73.379,-45.383,-73.266,-45.346,-73.202,-45.354,-72.978,-45.451,-72.934,-45.452,-72.941,-45.417,-72.976,-45.393,-73.064,-45.36,-73.226,-45.255,-73.445,-45.238,-73.405,-45.102,-73.362,-44.978,-73.256,-44.961,-73.078,-44.92,-72.739,-44.734,-72.68,-44.594,-72.664,-44.436,-72.828,-44.395,-73.001,-44.292,-73.141,-44.237,-73.265,-44.169,-73.241,-44.066,-73.224,-43.898,-73.069,-43.862,-72.997,-43.632,-73.101,-43.455,-73.076,-43.324,-72.94,-43.211,-72.915,-43.134,-72.878,-43.048,-72.758,-43.039,-72.755,-42.993,-72.766,-42.908,-72.845,-42.808,-72.848,-42.669,-72.774,-42.505,-72.655,-42.517,-72.632,-42.51,-72.716,-42.41,-72.785,-42.301,-72.773,-42.258,-72.707,-42.221,-72.631,-42.2,-72.548,-42.256,-72.43,-42.434,-72.412,-42.388,-72.46,-42.207,-72.499,-41.981,-72.624,-42.011,-72.738,-41.995,-72.781,-41.96,-72.824,-41.909,-72.784,-41.847,-72.744,-41.801,-72.66,-41.742,-72.486,-41.722,-72.36,-41.649,-72.318,-41.499,-72.359,-41.514,-72.428,-41.646,-72.542,-41.691,-72.601,-41.684,-72.67,-41.659,-72.805,-41.544,-72.88,-41.518,-72.953,-41.515,-73.015,-41.544,-73.174,-41.747,-73.242,-41.781,-73.521,-41.797,-73.624,-41.774,-73.735,-41.742,-73.722,-41.692,-73.688,-41.639,-73.625,-41.612,-73.624,-41.581,-73.711,-41.574,-73.811,-41.517,-73.855,-41.446,-73.876,-41.319,-73.966,-41.118,-73.984,-40.974,-73.92,-40.872,-73.784,-40.468,-73.742,-40.263,-73.669,-40.082,-73.671,-39.963,-73.482,-39.854,-73.41,-39.789,-73.25,-39.422,-73.226,-39.224,-73.481,-38.624,-73.52,-38.509,-73.533,-38.367,-73.472,-38.13,-73.465,-38.04,-73.517,-37.911,-73.662,-37.699,-73.665,-37.59,-73.603,-37.479,-73.662,-37.341,-73.634,-37.255,-73.602,-37.188,-73.375,-37.224,-73.271,-37.207,-73.216,-37.167,-73.173,-37.054,-73.151,-36.876,-73.138,-36.8,-73.118,-36.688,-73.007,-36.643,-72.968,-36.538,-72.875,-36.39,-72.778,-35.979,-72.683,-35.877,-72.587,-35.76,-72.624,-35.586,-72.562,-35.505,-72.505,-35.447,-72.455,-35.341,-72.387,-35.24,-72.224,-35.096,-72.182,-34.92,-72.056,-34.616,-72.031,-34.421,-71.992,-34.288,-72.003,-34.165,-71.927,-34.016,-71.854,-33.89,-71.831,-33.82,-71.664,-33.653,-71.636,-33.519,-71.696,-33.429,-71.697,-33.289,-71.743,-33.095,-71.636,-33.023,-71.592,-32.97,-71.452,-32.66,-71.461,-32.538,-71.421,-32.387,-71.513,-32.208,-71.526,-31.806,-71.577,-31.496,-71.662,-31.17,-71.654,-30.987,-71.706,-30.759,-71.709,-30.628,-71.669,-30.33,-71.4,-30.143,-71.348,-29.933,-71.316,-29.65,-71.327,-29.443,-71.353,-29.35,-71.486,-29.198,-71.519,-28.926,-71.494,-28.855,-71.384,-28.779,-71.307,-28.672,-71.267,-28.508,-71.186,-28.378,-71.154,-28.064,-71.087,-27.814,-71.053,-27.727,-70.946,-27.618,-70.926,-27.589,-70.909,-27.505,-70.914,-27.308,-70.898,-27.187,-70.813,-26.951,-70.803,-26.841,-70.708,-26.597,-70.687,-26.422,-70.647,-26.329,-70.662,-26.225,-70.635,-25.993,-70.7,-25.861,-70.714,-25.784,-70.633,-25.546,-70.578,-25.487,-70.49,-25.376,-70.452,-25.252,-70.445,-25.173,-70.559,-24.779,-70.574,-24.644,-70.546,-24.332,-70.507,-24.13,-70.52,-23.969,-70.507,-23.886,-70.488,-23.782,-70.41,-23.656,-70.392,-23.566,-70.42,-23.529,-70.512,-23.483,-70.588,-23.368,-70.593,-23.255,-70.569,-23.173,-70.563,-23.057,-70.45,-23.034,-70.389,-22.97,-70.332,-22.849,-70.26,-22.556,-70.229,-22.193,-70.185,-21.975,-70.155,-21.867,-70.13,-21.641,-70.088,-21.493,-70.08,-21.357,-70.088,-21.253,-70.197,-20.725,-70.194,-20.531,-70.147,-20.23,-70.148,-19.805,-70.157,-19.706,-70.198,-19.613,-70.21,-19.487,-70.276,-19.268,-70.335,-18.828,-70.336,-18.595,-70.362,-18.398,-70.418,-18.346]],[[-68.63,-52.653,-68.632,-52.95,-68.633,-53.242,-68.635,-53.515,-68.637,-53.789,-68.638,-54.053,-68.64,-54.324,-68.648,-54.628,-68.653,-54.854,-68.804,-54.854,-68.844,-54.877,-69.082,-54.91,-69.486,-54.859,-69.588,-54.813,-69.723,-54.712,-69.772,-54.739,-69.899,-54.782,-70.031,-54.816,-70.138,-54.819,-70.238,-54.778,-70.259,-54.756,-70.282,-54.752,-70.497,-54.81,-70.735,-54.751,-70.925,-54.714,-71.229,-54.694,-71.441,-54.62,-71.832,-54.626,-71.902,-54.602,-71.928,-54.529,-71.907,-54.495,-71.823,-54.474,-71.8,-54.434,-71.716,-54.444,-71.606,-54.497,-71.573,-54.495,-71.5,-54.445,-71.393,-54.4,-71.355,-54.395,-71.159,-54.451,-71.08,-54.444,-70.966,-54.42,-70.946,-54.398,-70.928,-54.36,-70.898,-54.338,-70.797,-54.327,-70.699,-54.349,-70.688,-54.415,-70.701,-54.485,-70.573,-54.504,-70.418,-54.502,-70.311,-54.529,-70.298,-54.486,-70.468,-54.373,-70.54,-54.303,-70.636,-54.262,-70.76,-54.241,-70.863,-54.11,-70.857,-53.996,-70.868,-53.884,-70.644,-53.823,-70.696,-53.727,-70.619,-53.655,-70.531,-53.627,-70.443,-53.893,-70.38,-53.987,-70.461,-54.006,-70.63,-54.006,-70.535,-54.136,-70.38,-54.181,-70.246,-54.277,-70.243,-54.348,-70.169,-54.379,-69.99,-54.381,-69.867,-54.367,-69.809,-54.321,-69.742,-54.306,-69.622,-54.364,-69.419,-54.407,-69.365,-54.438,-69.325,-54.488,-69.322,-54.543,-69.312,-54.571,-69.253,-54.557,-69.169,-54.483,-69.128,-54.458,-69.077,-54.445,-69.045,-54.428,-69.044,-54.407,-69.196,-54.354,-69.988,-54.109,-70.086,-54.011,-70.151,-53.888,-70.149,-53.761,-70.091,-53.722,-69.95,-53.672,-69.69,-53.601,-69.39,-53.499,-69.352,-53.48,-69.356,-53.416,-69.394,-53.373,-69.513,-53.342,-69.637,-53.334,-69.756,-53.337,-69.874,-53.35,-70.09,-53.418,-70.213,-53.414,-70.329,-53.378,-70.416,-53.305,-70.46,-53.206,-70.46,-53.143,-70.443,-53.086,-70.391,-53.026,-70.32,-53.001,-70.256,-53.004,-70.196,-52.99,-70.161,-52.97,-70.131,-52.943,-70.14,-52.919,-70.163,-52.899,-70.259,-52.857,-70.297,-52.817,-70.38,-52.752,-70.335,-52.734,-70.19,-52.724,-70.088,-52.769,-69.994,-52.821,-69.935,-52.821,-69.883,-52.799,-69.764,-52.731,-69.663,-52.646,-69.572,-52.549,-69.498,-52.491,-69.414,-52.486,-69.167,-52.668,-69.08,-52.674,-68.79,-52.577,-68.758,-52.582,-68.659,-52.632,-68.63,-52.653]],[[-67.08,-55.154,-67.109,-55.192,-67.173,-55.243,-67.257,-55.282,-67.34,-55.293,-67.399,-55.272,-67.429,-55.237,-67.443,-55.201,-67.463,-55.182,-67.495,-55.177,-67.535,-55.179,-67.585,-55.192,-67.691,-55.243,-67.737,-55.256,-67.768,-55.26,-68.07,-55.221,-68.1,-55.207,-68.135,-55.173,-68.174,-55.071,-68.301,-54.981,-68.107,-54.929,-67.874,-54.93,-67.425,-54.969,-67.245,-54.978,-67.107,-55.064,-67.085,-55.115,-67.08,-55.154]],[[-73.773,-43.346,-73.849,-43.367,-73.919,-43.372,-73.99,-43.357,-74.114,-43.358,-74.239,-43.319,-74.355,-43.264,-74.387,-43.232,-74.373,-43.186,-74.289,-43.079,-74.209,-42.879,-74.156,-42.591,-74.199,-42.481,-74.194,-42.436,-74.174,-42.382,-74.164,-42.325,-74.17,-42.269,-74.16,-42.216,-74.072,-42.106,-74.059,-42.056,-74.057,-42.002,-74.019,-41.891,-74.031,-41.854,-74.063,-41.823,-74.037,-41.796,-73.731,-41.877,-73.528,-41.896,-73.517,-41.981,-73.478,-42.047,-73.454,-42.166,-73.423,-42.193,-73.439,-42.278,-73.533,-42.314,-73.525,-42.393,-73.471,-42.466,-73.549,-42.493,-73.634,-42.508,-73.653,-42.529,-73.715,-42.545,-73.789,-42.586,-73.767,-42.622,-73.673,-42.704,-73.568,-42.762,-73.511,-42.847,-73.436,-42.937,-73.473,-42.993,-73.541,-43.074,-73.65,-43.127,-73.75,-43.159,-73.738,-43.291,-73.773,-43.346]],[[-74.476,-49.148,-74.467,-49.295,-74.484,-49.442,-74.522,-49.623,-74.516,-49.66,-74.471,-49.669,-74.459,-49.691,-74.472,-49.786,-74.496,-49.859,-74.543,-49.919,-74.57,-49.991,-74.595,-50.007,-74.703,-50.019,-74.763,-50.011,-74.811,-49.93,-74.825,-49.879,-74.822,-49.814,-74.88,-49.726,-74.882,-49.692,-74.859,-49.634,-74.812,-49.605,-74.805,-49.516,-74.781,-49.489,-74.727,-49.452,-74.719,-49.437,-74.724,-49.424,-74.744,-49.422,-74.96,-49.533,-74.981,-49.564,-74.991,-49.606,-74.994,-49.752,-75.032,-49.836,-75.066,-49.852,-75.167,-49.856,-75.3,-49.847,-75.451,-49.77,-75.55,-49.791,-75.57,-49.697,-75.521,-49.622,-75.337,-49.628,-75.306,-49.494,-75.364,-49.462,-75.429,-49.408,-75.467,-49.359,-75.433,-49.322,-75.327,-49.269,-75.27,-49.263,-75.217,-49.293,-75.086,-49.27,-75.094,-49.185,-75.21,-49.148,-75.184,-49.084,-75.037,-49.022,-74.949,-48.96,-74.945,-48.889,-74.981,-48.819,-74.97,-48.791,-74.896,-48.733,-74.793,-48.705,-74.747,-48.709,-74.652,-48.75,-74.567,-48.755,-74.546,-48.767,-74.531,-48.813,-74.476,-49.148]],[[-75.51,-48.763,-75.623,-48.765,-75.651,-48.586,-75.518,-48.329,-75.509,-48.231,-75.554,-48.157,-75.571,-48.096,-75.561,-48.071,-75.391,-48.02,-75.338,-48.074,-75.275,-48.218,-75.156,-48.425,-75.158,-48.623,-75.225,-48.671,-75.434,-48.721,-75.51,-48.763]],[[-74.386,-52.922,-74.369,-52.931,-74.33,-52.929,-74.275,-52.946,-74.066,-52.965,-73.879,-53.012,-73.782,-53.056,-73.654,-53.07,-73.549,-53.126,-73.505,-53.14,-73.451,-53.144,-73.31,-53.248,-73.302,-53.259,-73.143,-53.341,-73.135,-53.354,-73.226,-53.358,-73.409,-53.321,-73.501,-53.318,-73.567,-53.307,-73.583,-53.3,-73.596,-53.253,-73.617,-53.23,-73.794,-53.121,-73.867,-53.097,-73.994,-53.076,-74.139,-53.091,-74.236,-53.076,-74.27,-53.082,-74.414,-52.995,-74.558,-52.922,-74.62,-52.835,-74.712,-52.768,-74.712,-52.749,-74.67,-52.734,-74.572,-52.771,-74.475,-52.836,-74.422,-52.86,-74.386,-52.922]],[[-74.567,-48.592,-74.586,-48.616,-74.71,-48.601,-74.923,-48.626,-75.013,-48.536,-75.052,-48.391,-75.079,-48.362,-75.132,-48.279,-75.158,-48.225,-75.213,-48.142,-75.234,-48.053,-75.247,-48.027,-75.198,-47.975,-74.975,-47.923,-74.896,-47.839,-74.827,-47.85,-74.846,-48.021,-74.805,-48.078,-74.729,-48.126,-74.715,-48.146,-74.702,-48.206,-74.664,-48.299,-74.615,-48.343,-74.602,-48.37,-74.6,-48.393,-74.618,-48.425,-74.567,-48.592]],[[-72.923,-53.482,-72.896,-53.563,-72.882,-53.578,-72.809,-53.565,-72.685,-53.558,-72.482,-53.588,-72.459,-53.599,-72.373,-53.687,-72.307,-53.725,-72.205,-53.807,-72.306,-53.862,-72.366,-53.941,-72.369,-53.981,-72.409,-54.004,-72.471,-54.028,-72.563,-54.074,-72.677,-54.079,-72.789,-54.103,-72.84,-54.125,-72.871,-54.127,-72.907,-54.115,-72.946,-54.092,-72.959,-54.066,-72.882,-54.042,-72.782,-53.955,-72.764,-53.865,-72.872,-53.849,-72.936,-53.861,-72.984,-53.861,-73.039,-53.833,-73.073,-53.875,-73.086,-53.916,-73.071,-53.978,-73.081,-53.998,-73.12,-54.009,-73.211,-53.986,-73.305,-53.944,-73.312,-53.92,-73.293,-53.836,-73.295,-53.792,-73.314,-53.729,-73.325,-53.723,-73.36,-53.724,-73.471,-53.736,-73.582,-53.655,-73.642,-53.57,-73.845,-53.546,-73.687,-53.427,-73.447,-53.41,-73.366,-53.47,-73.099,-53.512,-73.115,-53.448,-73.111,-53.425,-73.074,-53.397,-73.054,-53.394,-73.022,-53.415,-72.971,-53.423,-72.947,-53.442,-72.923,-53.482]],[[-74.823,-51.63,-74.78,-51.825,-74.75,-51.852,-74.647,-51.866,-74.537,-51.965,-74.532,-51.992,-74.666,-52.16,-74.694,-52.279,-74.852,-52.271,-74.918,-52.152,-75.017,-52.038,-75.051,-51.904,-75.105,-51.789,-75.008,-51.724,-74.915,-51.738,-74.91,-51.65,-74.823,-51.63]],[[-69.703,-54.919,-68.901,-55.018,-68.654,-54.958,-68.458,-54.96,-68.4,-55.042,-68.598,-55.128,-68.613,-55.156,-68.586,-55.178,-68.382,-55.192,-68.33,-55.219,-68.283,-55.255,-68.323,-55.308,-68.327,-55.333,-68.305,-55.357,-68.153,-55.437,-68.09,-55.478,-68.058,-55.518,-68.046,-55.587,-68.048,-55.643,-68.083,-55.651,-68.157,-55.634,-68.23,-55.602,-68.293,-55.521,-68.338,-55.505,-68.467,-55.489,-68.594,-55.45,-68.694,-55.452,-68.785,-55.436,-68.867,-55.45,-68.896,-55.424,-68.931,-55.371,-68.932,-55.347,-68.889,-55.263,-68.89,-55.241,-68.913,-55.239,-69.008,-55.256,-69.047,-55.244,-69.151,-55.183,-69.193,-55.172,-69.297,-55.166,-69.356,-55.274,-69.359,-55.301,-69.299,-55.369,-69.181,-55.475,-69.241,-55.477,-69.412,-55.444,-69.456,-55.424,-69.509,-55.371,-69.61,-55.34,-69.646,-55.321,-69.656,-55.298,-69.657,-55.229,-69.68,-55.219,-69.824,-55.237,-69.854,-55.22,-69.866,-55.191,-69.887,-55.174,-69.98,-55.147,-69.988,-55.131,-69.947,-55.111,-69.921,-55.061,-69.884,-54.882,-69.703,-54.919]],[[-71.39,-54.033,-71.169,-54.113,-71.022,-54.112,-71.023,-54.162,-71.005,-54.247,-71.028,-54.281,-71.083,-54.316,-71.118,-54.366,-71.143,-54.374,-71.305,-54.314,-71.473,-54.231,-71.558,-54.246,-71.671,-54.225,-71.761,-54.23,-71.818,-54.276,-71.949,-54.301,-71.972,-54.207,-72.092,-54.119,-72.21,-54.048,-72.146,-53.939,-72.069,-53.921,-71.996,-53.885,-71.705,-53.923,-71.554,-53.956,-71.39,-54.033]],[[-73.735,-44.395,-73.785,-44.437,-73.862,-44.445,-73.983,-44.495,-73.996,-44.538,-74.002,-44.591,-73.919,-44.655,-73.877,-44.729,-73.828,-44.84,-73.792,-44.946,-73.795,-44.979,-73.786,-45.034,-73.727,-45.119,-73.722,-45.158,-73.728,-45.196,-73.752,-45.267,-73.771,-45.277,-73.83,-45.283,-73.834,-45.327,-73.849,-45.341,-74.016,-45.345,-74.099,-45.325,-74.089,-45.196,-74.195,-45.145,-74.268,-45.059,-74.35,-44.911,-74.419,-44.865,-74.499,-44.748,-74.618,-44.648,-74.481,-44.585,-74.502,-44.474,-74.422,-44.435,-74.301,-44.396,-74.212,-44.427,-74.133,-44.416,-74.097,-44.389,-74.108,-44.276,-74.083,-44.186,-73.995,-44.14,-73.9,-44.135,-73.865,-44.185,-73.818,-44.235,-73.703,-44.274,-73.704,-44.325,-73.735,-44.395]],[[-72.986,-44.78,-73.228,-44.86,-73.35,-44.833,-73.397,-44.774,-73.42,-44.725,-73.445,-44.641,-73.404,-44.596,-73.315,-44.531,-73.282,-44.49,-73.266,-44.44,-73.272,-44.394,-73.26,-44.35,-73.208,-44.335,-73.028,-44.384,-72.842,-44.458,-72.776,-44.509,-72.764,-44.549,-72.845,-44.638,-72.897,-44.712,-72.986,-44.78]],[[-75.042,-44.89,-75.067,-44.907,-75.099,-44.902,-75.124,-44.87,-75.142,-44.816,-75.107,-44.795,-75.079,-44.795,-75.048,-44.824,-75.032,-44.871,-75.042,-44.89]],[[-75.302,-50.68,-75.33,-50.772,-75.411,-50.764,-75.439,-50.741,-75.453,-50.683,-75.477,-50.654,-75.443,-50.596,-75.42,-50.53,-75.428,-50.481,-75.304,-50.484,-75.156,-50.497,-75.115,-50.51,-75.16,-50.554,-75.203,-50.581,-75.292,-50.597,-75.302,-50.68]],[[-75.107,-48.837,-75.115,-48.916,-75.263,-49.069,-75.39,-49.159,-75.506,-49.231,-75.58,-49.23,-75.641,-49.195,-75.573,-49.139,-75.488,-49.082,-75.515,-49.01,-75.54,-48.988,-75.576,-48.981,-75.638,-48.943,-75.619,-48.886,-75.583,-48.859,-75.535,-48.838,-75.49,-48.85,-75.297,-48.811,-75.236,-48.779,-75.119,-48.773,-75.107,-48.837]],[[-75.112,-47.838,-75.186,-47.851,-75.194,-47.818,-75.261,-47.764,-75.203,-47.728,-75.09,-47.691,-75.004,-47.695,-74.926,-47.723,-74.916,-47.757,-75.051,-47.8,-75.084,-47.825,-75.112,-47.838]],[[-74.669,-43.608,-74.81,-43.625,-74.843,-43.596,-74.842,-43.57,-74.818,-43.549,-74.745,-43.536,-74.697,-43.553,-74.673,-43.577,-74.665,-43.6,-74.669,-43.608]],[[-73.632,-44.821,-73.665,-44.833,-73.695,-44.831,-73.725,-44.797,-73.735,-44.752,-73.8,-44.684,-73.818,-44.652,-73.817,-44.614,-73.779,-44.559,-73.724,-44.544,-73.686,-44.546,-73.641,-44.611,-73.628,-44.681,-73.617,-44.753,-73.632,-44.821]],[[-74.559,-51.277,-74.561,-51.361,-74.593,-51.387,-74.62,-51.396,-74.691,-51.37,-74.731,-51.367,-74.797,-51.412,-74.853,-51.434,-74.937,-51.428,-75.047,-51.398,-75.146,-51.524,-75.192,-51.567,-75.289,-51.625,-75.3,-51.556,-75.238,-51.454,-75.21,-51.383,-75.154,-51.279,-75.04,-51.318,-74.881,-51.279,-74.737,-51.208,-74.612,-51.207,-74.571,-51.245,-74.559,-51.277]],[[-75.055,-50.296,-75.25,-50.376,-75.308,-50.343,-75.449,-50.343,-75.412,-50.257,-75.398,-50.193,-75.377,-50.168,-75.369,-50.113,-75.327,-50.012,-75.21,-50.045,-75.123,-50.055,-75.004,-50.089,-74.876,-50.11,-74.839,-50.197,-74.963,-50.237,-75.055,-50.296]],[[-73.811,-43.827,-73.79,-43.876,-73.834,-43.883,-73.904,-43.875,-73.938,-43.914,-73.956,-43.922,-74.118,-43.887,-74.143,-43.872,-74.14,-43.821,-73.967,-43.817,-73.857,-43.784,-73.841,-43.789,-73.811,-43.827]],[[-74.142,-51.931,-74.172,-51.942,-74.283,-51.919,-74.339,-51.898,-74.424,-51.845,-74.437,-51.791,-74.475,-51.726,-74.451,-51.725,-74.362,-51.751,-74.326,-51.77,-74.277,-51.812,-74.133,-51.871,-74.115,-51.888,-74.119,-51.911,-74.142,-51.931]],[[-74.313,-45.692,-74.368,-45.736,-74.466,-45.757,-74.562,-45.722,-74.678,-45.739,-74.69,-45.663,-74.646,-45.6,-74.558,-45.526,-74.495,-45.426,-74.502,-45.285,-74.45,-45.253,-74.422,-45.203,-74.311,-45.173,-74.285,-45.277,-74.315,-45.464,-74.24,-45.575,-74.229,-45.611,-74.244,-45.654,-74.313,-45.692]],[[-67.575,-55.89,-67.611,-55.892,-67.7,-55.873,-67.832,-55.865,-67.846,-55.857,-67.85,-55.843,-67.834,-55.828,-67.762,-55.816,-67.545,-55.826,-67.517,-55.833,-67.51,-55.844,-67.545,-55.877,-67.575,-55.89]],[[-66.472,-55.229,-66.552,-55.273,-66.611,-55.27,-66.63,-55.254,-66.637,-55.234,-66.625,-55.213,-66.6,-55.194,-66.542,-55.169,-66.523,-55.166,-66.436,-55.19,-66.472,-55.229]],[[-70.992,-54.868,-70.945,-54.931,-70.928,-54.943,-70.805,-54.968,-70.749,-54.953,-70.615,-54.946,-70.535,-54.921,-70.418,-54.909,-70.283,-55.066,-70.298,-55.114,-70.404,-55.166,-70.476,-55.177,-70.543,-55.161,-70.539,-55.135,-70.551,-55.112,-70.597,-55.082,-70.641,-55.085,-70.711,-55.107,-70.744,-55.104,-70.815,-55.08,-70.94,-55.062,-70.965,-55.04,-70.967,-55.007,-70.991,-54.99,-71.12,-54.938,-71.203,-54.893,-71.274,-54.887,-71.299,-54.892,-71.325,-54.914,-71.389,-54.934,-71.407,-54.931,-71.427,-54.914,-71.437,-54.889,-71.411,-54.839,-71.374,-54.835,-71.197,-54.844,-71.089,-54.867,-70.992,-54.868]],[[-67.289,-55.777,-67.325,-55.785,-67.352,-55.766,-67.393,-55.753,-67.56,-55.725,-67.563,-55.708,-67.546,-55.684,-67.513,-55.662,-67.449,-55.641,-67.397,-55.585,-67.374,-55.589,-67.351,-55.612,-67.31,-55.689,-67.262,-55.744,-67.267,-55.763,-67.289,-55.777]]]},{"n":"Chad","c":"TD","r":3,"ml":3,"lx":18.65,"ly":15.14,"b":[13.448,7.475,23.983,23.445],"p":[[[23.98,19.497,23.981,19.051,23.981,18.605,23.981,18.158,23.982,17.712,23.982,17.266,23.983,16.82,23.983,16.374,23.983,15.928,23.983,15.78,23.971,15.722,23.965,15.713,23.946,15.704,23.708,15.745,23.604,15.746,23.458,15.714,23.243,15.697,23.105,15.703,23.009,15.626,22.934,15.533,22.97,15.311,22.961,15.238,22.932,15.162,22.867,15.097,22.802,15.044,22.763,14.999,22.715,14.898,22.679,14.851,22.682,14.789,22.671,14.722,22.632,14.688,22.532,14.663,22.468,14.633,22.416,14.585,22.382,14.55,22.4,14.504,22.425,14.441,22.439,14.342,22.449,14.284,22.498,14.237,22.528,14.203,22.539,14.162,22.51,14.127,22.388,14.056,22.339,14.029,22.283,13.992,22.262,13.979,22.173,13.911,22.128,13.85,22.106,13.8,22.108,13.73,22.153,13.626,22.202,13.538,22.221,13.472,22.233,13.399,22.228,13.33,22.203,13.269,22.158,13.215,21.99,13.113,21.908,13.001,21.842,12.865,21.825,12.791,21.843,12.741,21.878,12.699,21.928,12.678,22.001,12.672,22.121,12.695,22.233,12.709,22.352,12.66,22.414,12.546,22.39,12.463,22.435,12.312,22.475,12.129,22.472,12.068,22.49,12.045,22.564,12.033,22.581,11.99,22.556,11.67,22.591,11.58,22.641,11.516,22.697,11.483,22.754,11.44,22.783,11.41,22.849,11.403,22.923,11.345,22.943,11.267,22.938,11.192,22.895,11.029,22.86,10.92,22.817,10.927,22.73,10.954,22.624,10.977,22.494,10.996,22.37,10.952,22.236,10.894,22.194,10.851,22.156,10.826,22.097,10.83,22.043,10.823,22.014,10.782,21.965,10.737,21.771,10.643,21.731,10.609,21.707,10.575,21.707,10.538,21.726,10.462,21.726,10.367,21.683,10.29,21.633,10.238,21.576,10.219,21.528,10.208,21.497,10.176,21.396,10.001,21.352,9.969,21.264,9.975,21.009,9.713,20.984,9.636,20.891,9.527,20.773,9.406,20.668,9.347,20.66,9.325,20.631,9.301,20.567,9.275,20.342,9.127,20.073,9.133,19.954,9.075,19.838,9.049,19.668,9.021,19.617,9.024,19.4,9.012,19.146,9.016,19.048,8.995,18.956,8.939,18.888,8.89,18.878,8.873,18.889,8.852,18.886,8.836,19.064,8.715,19.109,8.656,19.064,8.599,19.042,8.59,19.04,8.587,19.011,8.541,18.906,8.405,18.747,8.244,18.666,8.198,18.634,8.168,18.592,8.061,18.564,8.046,18.455,8.032,18.239,8.02,17.94,7.985,17.761,7.974,17.649,7.984,17.493,7.91,17.436,7.891,17.402,7.885,17.247,7.813,17.118,7.702,17.072,7.681,16.89,7.634,16.818,7.557,16.785,7.551,16.668,7.652,16.589,7.743,16.55,7.836,16.545,7.865,16.523,7.86,16.459,7.819,16.404,7.772,16.379,7.684,16.191,7.623,16.031,7.572,15.958,7.508,15.845,7.475,15.701,7.488,15.589,7.515,15.48,7.524,15.532,7.604,15.553,7.665,15.558,7.738,15.55,7.788,15.484,7.813,15.443,7.852,15.349,8.084,15.252,8.322,15.116,8.557,14.968,8.707,14.861,8.799,14.826,8.81,14.771,8.839,14.733,8.866,14.536,9.025,14.332,9.204,14.28,9.285,14.178,9.406,14.064,9.532,14.005,9.589,13.977,9.692,14.056,9.784,14.14,9.902,14.243,9.98,14.377,9.985,14.598,9.953,14.836,9.942,15.072,9.966,15.133,9.983,15.193,9.981,15.32,9.954,15.541,9.96,15.655,10.008,15.532,10.088,15.4,10.217,15.276,10.357,15.201,10.485,15.132,10.648,15.069,10.851,15.03,11.114,15.036,11.263,15.055,11.369,15.122,11.541,15.078,11.643,15.088,11.724,15.081,11.846,15.06,11.907,14.974,12.108,14.957,12.13,14.881,12.269,14.847,12.502,14.761,12.656,14.623,12.73,14.545,12.82,14.516,12.98,14.462,13.022,14.245,13.077,14.064,13.079,13.932,13.258,13.763,13.49,13.606,13.705,13.506,14.134,13.448,14.381,13.514,14.456,13.642,14.631,13.807,14.966,14.178,15.485,14.368,15.75,14.747,16.147,15.212,16.634,15.474,16.908,15.517,17.408,15.562,17.937,15.596,18.337,15.638,18.811,15.673,19.207,15.699,19.495,15.735,19.904,15.766,19.983,15.949,20.303,15.963,20.346,15.929,20.4,15.668,20.672,15.587,20.733,15.54,20.875,15.607,20.954,15.294,21.412,15.216,21.467,15.182,21.523,15.178,21.606,15.172,21.922,15.089,22.418,14.979,22.996,15.347,23.161,15.627,23.286,15.984,23.445,16.315,23.282,16.794,23.045,17.273,22.809,17.752,22.572,18.231,22.336,18.71,22.099,19.189,21.862,19.669,21.626,20.148,21.389,20.627,21.153,21.106,20.916,21.585,20.679,22.064,20.443,22.543,20.206,23.022,19.97,23.501,19.733,23.98,19.497]]]},{"n":"Central African Republic","c":"CF","r":4,"ml":4,"lx":20.91,"ly":6.99,"b":[14.431,2.27,27.403,10.996],"p":[[[24.147,8.666,24.195,8.653,24.221,8.608,24.18,8.461,24.208,8.369,24.291,8.291,24.375,8.258,24.456,8.239,24.737,8.192,24.853,8.138,25.007,7.965,25.2,7.808,25.247,7.725,25.239,7.649,25.181,7.557,25.19,7.519,25.279,7.427,25.381,7.333,25.567,7.229,25.889,7.065,26.037,6.955,26.087,6.872,26.169,6.782,26.285,6.699,26.362,6.635,26.309,6.455,26.325,6.396,26.353,6.345,26.421,6.274,26.447,6.183,26.514,6.069,26.594,6.018,26.726,5.998,26.796,5.946,26.942,5.855,27.083,5.777,27.144,5.723,27.181,5.675,27.213,5.619,27.229,5.563,27.233,5.441,27.257,5.29,27.332,5.186,27.403,5.109,27.115,5.198,27.072,5.2,27.021,5.184,26.87,5.076,26.822,5.062,26.768,5.072,26.633,5.085,26.174,5.171,25.82,5.254,25.714,5.284,25.525,5.312,25.4,5.256,25.283,5.063,25.249,5.025,25.065,4.967,24.978,4.983,24.766,4.93,24.437,5.01,24.32,4.994,24.228,4.954,23.992,4.866,23.848,4.816,23.682,4.771,23.524,4.701,23.417,4.663,23.313,4.664,23.219,4.703,23.116,4.737,22.993,4.744,22.865,4.724,22.756,4.647,22.712,4.592,22.617,4.446,22.506,4.208,22.462,4.16,22.45,4.155,22.422,4.135,21.908,4.254,21.687,4.281,21.538,4.245,21.35,4.311,21.268,4.323,21.23,4.302,21.126,4.332,20.956,4.413,20.793,4.447,20.647,4.436,20.558,4.463,20.487,4.542,20.394,4.686,20.226,4.83,20.002,4.945,19.863,5.031,19.807,5.089,19.686,5.121,19.501,5.127,19.323,5.071,19.069,4.891,18.832,4.523,18.7,4.383,18.594,4.346,18.567,4.258,18.62,4.117,18.634,3.954,18.597,3.679,18.61,3.478,18.554,3.51,18.5,3.604,18.474,3.623,18.318,3.581,18.237,3.543,18.194,3.505,18.161,3.5,18.111,3.551,18.072,3.56,18.011,3.551,17.948,3.552,17.907,3.558,17.88,3.554,17.807,3.584,17.538,3.662,17.492,3.687,17.438,3.685,17.298,3.617,17.225,3.598,17.003,3.557,16.764,3.536,16.673,3.535,16.611,3.505,16.57,3.463,16.543,3.37,16.496,3.209,16.477,3.165,16.48,3.101,16.466,2.993,16.46,2.897,16.469,2.832,16.401,2.701,16.32,2.543,16.252,2.407,16.183,2.27,16.136,2.364,16.107,2.473,16.096,2.599,16.102,2.633,16.083,2.67,16.082,2.678,16.059,2.773,16.082,2.839,16.063,2.909,16.008,2.977,15.958,3.029,15.929,3.076,15.905,3.096,15.849,3.103,15.775,3.127,15.677,3.23,15.581,3.329,15.458,3.457,15.36,3.567,15.24,3.702,15.129,3.827,15.062,3.947,15.035,4.016,15.067,4.023,15.115,4.024,15.136,4.037,15.137,4.069,15.088,4.164,15.064,4.285,15.023,4.359,14.894,4.472,14.77,4.558,14.731,4.602,14.709,4.666,14.662,5.066,14.641,5.179,14.602,5.229,14.574,5.252,14.563,5.28,14.568,5.351,14.584,5.415,14.584,5.44,14.617,5.496,14.617,5.865,14.599,5.884,14.577,5.916,14.542,5.914,14.503,5.917,14.464,5.971,14.431,6.039,14.441,6.087,14.475,6.127,14.512,6.162,14.559,6.191,14.7,6.25,14.739,6.28,14.764,6.316,14.78,6.366,14.862,6.556,14.983,6.745,15.035,6.784,15.086,6.91,15.157,7.064,15.186,7.135,15.207,7.206,15.246,7.264,15.379,7.358,15.48,7.524,15.589,7.515,15.701,7.488,15.845,7.475,15.958,7.508,16.031,7.572,16.191,7.623,16.379,7.684,16.404,7.772,16.459,7.819,16.523,7.86,16.545,7.865,16.55,7.836,16.589,7.743,16.668,7.652,16.785,7.551,16.818,7.557,16.89,7.634,17.072,7.681,17.118,7.702,17.247,7.813,17.402,7.885,17.436,7.891,17.493,7.91,17.649,7.984,17.761,7.974,17.94,7.985,18.239,8.02,18.455,8.032,18.564,8.046,18.592,8.061,18.634,8.168,18.666,8.198,18.747,8.244,18.906,8.405,19.011,8.541,19.04,8.587,19.042,8.59,19.064,8.599,19.109,8.656,19.064,8.715,18.886,8.836,18.889,8.852,18.878,8.873,18.888,8.89,18.956,8.939,19.048,8.995,19.146,9.016,19.4,9.012,19.617,9.024,19.668,9.021,19.838,9.049,19.954,9.075,20.073,9.133,20.342,9.127,20.567,9.275,20.631,9.301,20.66,9.325,20.668,9.347,20.773,9.406,20.891,9.527,20.984,9.636,21.009,9.713,21.264,9.975,21.352,9.969,21.396,10.001,21.497,10.176,21.528,10.208,21.576,10.219,21.633,10.238,21.683,10.29,21.726,10.367,21.726,10.462,21.707,10.538,21.707,10.575,21.731,10.609,21.771,10.643,21.965,10.737,22.014,10.782,22.043,10.823,22.097,10.83,22.156,10.826,22.194,10.851,22.236,10.894,22.37,10.952,22.494,10.996,22.624,10.977,22.73,10.954,22.817,10.927,22.86,10.92,22.931,10.795,22.964,10.752,23.256,10.458,23.312,10.388,23.457,10.174,23.545,10.03,23.646,9.823,23.656,9.71,23.643,9.614,23.623,9.341,23.596,9.262,23.468,9.115,23.463,9.048,23.489,8.993,23.528,8.971,23.552,8.943,23.537,8.816,23.583,8.766,23.679,8.732,23.922,8.71,24.048,8.691,24.147,8.666]]]},{"n":"Cape Verde","c":"CV","r":4,"ml":4,"lx":-23.64,"ly":15.07,"b":[-25.342,14.818,-22.682,17.194],"p":[[[-25.17,16.946,-25.267,16.926,-25.308,16.936,-25.322,17.015,-25.342,17.068,-25.337,17.091,-25.113,17.194,-25.035,17.176,-24.98,17.095,-25.017,17.049,-25.17,16.946]],[[-23.444,15.008,-23.505,14.916,-23.637,14.923,-23.705,14.961,-23.785,15.077,-23.783,15.166,-23.754,15.244,-23.759,15.311,-23.748,15.329,-23.707,15.317,-23.701,15.272,-23.58,15.161,-23.535,15.139,-23.444,15.008]],[[-24.887,16.818,-24.969,16.794,-25.02,16.797,-25.093,16.833,-25.07,16.871,-24.991,16.913,-24.936,16.922,-24.892,16.846,-24.887,16.818]],[[-22.918,16.237,-22.834,16.219,-22.803,16.226,-22.749,16.222,-22.693,16.169,-22.682,16.113,-22.71,16.043,-22.821,15.986,-22.884,15.993,-22.959,16.045,-22.916,16.148,-22.918,16.237]],[[-24.308,14.856,-24.386,14.818,-24.441,14.835,-24.492,14.874,-24.517,14.931,-24.497,14.98,-24.392,15.038,-24.329,15.019,-24.296,14.93,-24.308,14.856]],[[-24.088,16.623,-24.046,16.593,-24.033,16.572,-24.094,16.561,-24.243,16.599,-24.283,16.576,-24.322,16.493,-24.398,16.618,-24.393,16.664,-24.377,16.678,-24.271,16.645,-24.088,16.623]],[[-22.888,16.659,-22.92,16.608,-22.959,16.683,-22.981,16.701,-22.991,16.809,-22.933,16.841,-22.905,16.844,-22.904,16.732,-22.888,16.659]],[[-23.182,15.137,-23.21,15.133,-23.252,15.178,-23.242,15.241,-23.247,15.257,-23.21,15.324,-23.138,15.318,-23.119,15.268,-23.116,15.167,-23.182,15.137]]]},{"n":"Canada","c":"CA","r":2,"ml":1.7,"lx":-101.91,"ly":60.32,"b":[-141.002,41.675,-52.654,83.116],"p":[[[-132.656,54.127,-132.564,54.069,-132.344,54.106,-132.303,54.099,-132.262,54.076,-132.216,54.028,-132.166,53.955,-132.155,53.875,-132.175,53.847,-132.215,53.815,-132.565,53.688,-132.574,53.675,-132.567,53.664,-132.535,53.652,-132.464,53.653,-132.187,53.685,-132.172,53.707,-132.152,53.807,-132.114,53.86,-132.111,53.9,-132.136,53.996,-132.134,54.034,-131.941,54.042,-131.82,54.077,-131.696,54.143,-131.668,54.141,-131.685,54.023,-131.703,53.986,-131.821,53.842,-131.889,53.714,-131.922,53.588,-131.928,53.379,-131.957,53.309,-132.011,53.265,-132.347,53.189,-132.52,53.194,-132.675,53.263,-132.748,53.31,-132.693,53.368,-132.655,53.371,-132.546,53.359,-132.462,53.338,-132.425,53.337,-132.431,53.35,-132.67,53.459,-132.845,53.508,-132.898,53.563,-132.9,53.605,-132.913,53.629,-133.052,53.778,-133.079,53.837,-133.098,53.92,-133.098,54.006,-133.064,54.144,-133.048,54.159,-132.991,54.158,-132.893,54.141,-132.656,54.127]],[[-131.754,53.196,-131.652,53.103,-131.622,53.02,-131.635,52.922,-131.795,52.885,-131.88,52.915,-131.916,52.909,-131.972,52.88,-131.904,52.867,-131.81,52.819,-131.727,52.756,-131.611,52.745,-131.455,52.702,-131.573,52.623,-131.591,52.578,-131.444,52.453,-131.43,52.422,-131.383,52.416,-131.274,52.426,-131.26,52.416,-131.26,52.39,-131.327,52.318,-131.32,52.303,-131.259,52.292,-131.143,52.291,-131.116,52.219,-131.222,52.154,-131.422,52.238,-131.511,52.322,-131.562,52.4,-131.624,52.444,-131.81,52.542,-132.092,52.753,-132.165,52.783,-132.239,52.867,-132.26,52.907,-132.258,52.934,-132.23,52.948,-132.145,52.957,-132.144,52.999,-132.469,53.072,-132.505,53.087,-132.547,53.138,-132.524,53.145,-132.345,53.136,-132.154,53.16,-132.036,53.179,-131.99,53.202,-131.893,53.231,-131.853,53.23,-131.754,53.196]],[[-127.197,50.64,-126.701,50.516,-126.204,50.454,-125.839,50.381,-125.615,50.359,-125.534,50.342,-125.482,50.317,-125.42,50.255,-125.314,50.107,-125.233,50.012,-125.066,49.848,-124.935,49.732,-124.905,49.685,-124.932,49.67,-124.931,49.643,-124.831,49.53,-124.643,49.429,-124.496,49.38,-124.186,49.301,-123.996,49.224,-123.937,49.171,-123.854,49.119,-123.82,49.083,-123.752,48.951,-123.627,48.824,-123.497,48.582,-123.473,48.602,-123.458,48.674,-123.443,48.69,-123.415,48.698,-123.39,48.67,-123.366,48.606,-123.284,48.455,-123.311,48.411,-123.335,48.406,-123.446,48.427,-123.485,48.4,-123.536,48.345,-123.573,48.323,-123.595,48.334,-123.917,48.387,-124.115,48.436,-124.376,48.515,-124.689,48.597,-124.868,48.654,-125.017,48.711,-125.121,48.761,-125.14,48.803,-125.136,48.822,-124.935,48.956,-124.85,49.028,-124.817,49.083,-124.8,49.142,-124.813,49.213,-124.821,49.207,-124.839,49.139,-124.868,49.079,-124.904,49.031,-124.927,49.014,-125.168,48.991,-125.363,48.998,-125.46,48.941,-125.489,48.934,-125.543,48.953,-125.66,49.029,-125.829,49.092,-125.812,49.107,-125.702,49.139,-125.644,49.186,-125.655,49.193,-125.694,49.19,-125.728,49.2,-125.796,49.26,-125.835,49.277,-125.918,49.25,-125.952,49.248,-125.984,49.288,-125.938,49.38,-125.935,49.401,-126.02,49.368,-126.048,49.379,-126.075,49.409,-126.1,49.421,-126.169,49.415,-126.244,49.443,-126.27,49.432,-126.28,49.392,-126.304,49.382,-126.419,49.449,-126.445,49.451,-126.5,49.4,-126.519,49.397,-126.549,49.419,-126.564,49.543,-126.557,49.579,-126.542,49.59,-126.443,49.619,-126.158,49.65,-126.134,49.672,-126.348,49.661,-126.403,49.678,-126.463,49.72,-126.525,49.72,-126.558,49.733,-126.593,49.764,-126.683,49.876,-126.745,49.905,-126.849,49.923,-126.903,49.944,-126.926,49.935,-126.948,49.903,-126.977,49.883,-127.049,49.872,-127.114,49.88,-127.166,49.91,-127.196,49.949,-127.208,49.992,-127.179,50.05,-127.18,50.073,-127.192,50.1,-127.216,50.121,-127.25,50.138,-127.268,50.129,-127.272,50.096,-127.29,50.071,-127.349,50.052,-127.398,50.085,-127.43,50.131,-127.467,50.163,-127.675,50.163,-127.77,50.121,-127.816,50.118,-127.864,50.128,-127.873,50.15,-127.828,50.211,-127.839,50.293,-127.851,50.314,-127.947,50.326,-127.963,50.346,-127.906,50.445,-127.874,50.464,-127.832,50.471,-127.641,50.479,-127.578,50.465,-127.487,50.405,-127.489,50.427,-127.524,50.496,-127.529,50.537,-127.466,50.583,-127.526,50.597,-127.751,50.607,-127.75,50.578,-127.731,50.536,-127.865,50.499,-127.964,50.493,-128.058,50.498,-128.136,50.521,-128.267,50.609,-128.35,50.697,-128.346,50.744,-128.301,50.794,-128.242,50.828,-128.101,50.858,-127.918,50.861,-127.713,50.821,-127.197,50.64]],[[-130.025,55.888,-130.014,55.951,-130.023,56.015,-130.056,56.065,-130.098,56.109,-130.215,56.083,-130.413,56.123,-130.477,56.231,-130.649,56.264,-130.742,56.341,-130.93,56.379,-131.083,56.405,-131.199,56.449,-131.336,56.501,-131.472,56.557,-131.575,56.599,-131.652,56.596,-131.824,56.59,-131.833,56.685,-131.886,56.742,-131.866,56.793,-131.962,56.819,-132.104,56.857,-132.063,56.953,-132.032,57.027,-132.157,57.048,-132.338,57.079,-132.279,57.145,-132.232,57.199,-132.302,57.276,-132.442,57.407,-132.55,57.5,-132.692,57.645,-132.816,57.773,-132.917,57.877,-133.001,57.949,-133.12,58.078,-133.275,58.223,-133.423,58.337,-133.401,58.411,-133.546,58.503,-133.674,58.597,-133.821,58.705,-133.966,58.758,-134.069,58.796,-134.219,58.85,-134.297,58.898,-134.33,58.94,-134.364,58.969,-134.393,59.009,-134.41,59.056,-134.441,59.085,-134.622,59.155,-134.677,59.199,-134.802,59.25,-134.907,59.271,-134.944,59.288,-135.071,59.441,-135.051,59.496,-135.037,59.551,-135.051,59.579,-135.261,59.695,-135.368,59.743,-135.476,59.793,-135.703,59.729,-135.935,59.663,-136.097,59.638,-136.322,59.605,-136.247,59.533,-136.278,59.48,-136.348,59.456,-136.466,59.459,-136.467,59.28,-136.579,59.152,-136.813,59.15,-136.939,59.106,-137.126,59.041,-137.278,58.988,-137.439,58.903,-137.521,58.915,-137.484,58.991,-137.544,59.119,-137.593,59.226,-137.697,59.281,-137.871,59.374,-138.001,59.443,-138.187,59.542,-138.318,59.611,-138.454,59.683,-138.632,59.778,-138.705,59.901,-138.869,59.946,-139.043,59.993,-139.185,60.084,-139.137,60.173,-139.079,60.279,-139.079,60.344,-139.235,60.34,-139.468,60.334,-139.676,60.328,-139.831,60.253,-139.973,60.183,-140.197,60.238,-140.453,60.3,-140.525,60.218,-140.763,60.259,-141.002,60.3,-141.002,60.592,-141.002,60.885,-141.002,61.177,-141.002,61.469,-141.002,61.761,-141.002,62.053,-141.002,62.346,-141.002,62.638,-141.002,62.93,-141.002,63.222,-141.002,63.514,-141.002,63.807,-141.002,64.099,-141.002,64.391,-141.002,64.683,-141.002,64.976,-141.002,65.268,-141.002,65.56,-141.002,65.852,-141.002,66.144,-141.002,66.437,-141.002,66.729,-141.002,67.021,-141.002,67.313,-141.002,67.605,-141.002,67.898,-141.002,68.19,-141.002,68.482,-141.002,68.774,-141.002,69.066,-141.002,69.359,-141.002,69.651,-140.86,69.635,-140.405,69.602,-139.977,69.622,-139.182,69.516,-138.69,69.317,-138.291,69.219,-138.128,69.152,-137.869,69.093,-137.26,68.964,-137.07,68.951,-136.717,68.889,-136.499,68.897,-136.122,68.882,-135.867,68.833,-135.362,68.696,-135.259,68.684,-135.231,68.694,-135.407,68.829,-135.435,68.842,-135.638,68.892,-135.876,68.917,-135.895,68.927,-135.939,68.974,-135.925,68.993,-135.873,69.001,-135.695,69.001,-135.59,69.008,-135.576,69.027,-135.651,69.031,-135.743,69.049,-135.85,69.081,-135.91,69.111,-135.691,69.311,-135.615,69.291,-135.5,69.337,-135.293,69.308,-135.255,69.324,-135.23,69.425,-135.199,69.45,-135.141,69.468,-134.853,69.486,-134.494,69.468,-134.457,69.478,-134.491,69.545,-134.495,69.572,-134.474,69.633,-134.451,69.665,-134.409,69.682,-134.242,69.669,-134.19,69.639,-134.134,69.587,-134.077,69.558,-133.9,69.528,-133.88,69.508,-133.947,69.429,-134.018,69.388,-134.165,69.281,-134.174,69.253,-133.948,69.301,-133.694,69.368,-133.476,69.405,-133.294,69.412,-133.163,69.434,-133.084,69.471,-133.028,69.508,-132.915,69.63,-132.84,69.651,-132.527,69.643,-132.452,69.647,-132.404,69.659,-132.413,69.674,-132.479,69.693,-132.568,69.698,-132.571,69.707,-132.538,69.727,-132.488,69.738,-132.334,69.752,-132.232,69.708,-132.163,69.705,-131.934,69.753,-131.582,69.882,-131.441,69.918,-131.319,69.924,-131.216,69.901,-131.136,69.907,-131.032,69.979,-130.991,70.018,-130.926,70.052,-130.665,70.127,-130.498,70.143,-130.396,70.129,-130.275,70.098,-130.175,70.086,-130.043,70.095,-129.945,70.091,-129.898,70.106,-129.73,70.192,-129.676,70.193,-129.623,70.168,-129.538,70.105,-129.538,70.074,-129.648,69.998,-130.459,69.78,-130.709,69.686,-130.832,69.651,-130.96,69.632,-131.208,69.616,-131.306,69.597,-131.473,69.579,-131.863,69.549,-131.938,69.535,-131.989,69.518,-132.129,69.402,-132.197,69.365,-132.331,69.308,-132.481,69.273,-132.687,69.26,-132.817,69.206,-132.968,69.101,-133.089,69.029,-133.228,68.967,-133.379,68.887,-133.418,68.844,-133.373,68.788,-133.348,68.77,-133.197,68.74,-133.138,68.747,-133.192,68.777,-133.32,68.82,-133.337,68.835,-133.304,68.847,-132.706,68.815,-132.578,68.848,-132.533,68.876,-132.542,68.89,-132.704,68.896,-132.739,68.922,-132.765,68.972,-132.77,69.012,-132.755,69.042,-132.719,69.079,-132.545,69.141,-132.358,69.167,-132.214,69.202,-132.134,69.234,-131.92,69.291,-131.833,69.336,-131.787,69.371,-131.781,69.389,-131.82,69.402,-131.788,69.432,-131.632,69.459,-131.563,69.461,-131.343,69.435,-131.303,69.415,-131.325,69.361,-131.294,69.364,-131.209,69.432,-131.162,69.455,-131.113,69.459,-131.063,69.451,-131.013,69.429,-130.986,69.363,-130.982,69.253,-130.971,69.209,-130.914,69.285,-130.875,69.32,-130.661,69.481,-130.516,69.57,-130.354,69.656,-130.118,69.72,-129.572,69.827,-129.265,69.855,-129.109,69.882,-129.033,69.905,-128.984,69.933,-128.899,69.966,-128.884,69.963,-128.917,69.895,-128.939,69.875,-129.138,69.833,-129.158,69.8,-129.136,69.75,-129.102,69.717,-129.054,69.701,-128.971,69.712,-128.853,69.751,-128.706,69.81,-128.387,69.96,-128.359,69.988,-128.279,70.108,-128.096,70.161,-127.765,70.222,-127.684,70.26,-127.974,70.293,-128.034,70.315,-128.044,70.329,-127.989,70.363,-128.121,70.397,-128.17,70.418,-128.168,70.48,-128.127,70.524,-128.04,70.566,-127.991,70.574,-127.862,70.549,-127.753,70.517,-127.377,70.369,-127.226,70.296,-127.138,70.239,-126.927,70.062,-126.833,69.959,-126.759,69.853,-126.685,69.777,-126.612,69.73,-126.25,69.545,-126.064,69.467,-125.907,69.419,-125.728,69.38,-125.525,69.352,-125.387,69.349,-125.172,69.428,-125.167,69.48,-125.262,69.566,-125.357,69.626,-125.346,69.662,-125.219,69.732,-125.228,69.757,-125.201,69.829,-125.114,69.815,-125.08,69.818,-125.031,69.844,-124.968,69.894,-124.889,69.936,-124.794,69.969,-124.768,69.99,-124.863,70.006,-124.92,70.006,-124.963,70.013,-124.99,70.027,-124.952,70.042,-124.745,70.08,-124.706,70.117,-124.64,70.141,-124.555,70.151,-124.503,70.141,-124.444,70.111,-124.442,70.062,-124.467,69.983,-124.472,69.919,-124.407,69.767,-124.349,69.735,-124.125,69.69,-124.138,69.653,-124.398,69.494,-124.454,69.455,-124.481,69.425,-124.472,69.4,-124.426,69.379,-124.338,69.365,-124.112,69.359,-124.05,69.373,-123.609,69.377,-123.528,69.389,-123.46,69.42,-123.361,69.497,-123.249,69.52,-123.214,69.542,-123.144,69.632,-123.11,69.738,-123.077,69.782,-123.026,69.81,-122.957,69.819,-122.785,69.808,-122.705,69.817,-122.387,69.808,-122.07,69.816,-121.742,69.798,-121.531,69.776,-121.336,69.742,-120.962,69.66,-120.815,69.617,-120.293,69.421,-120.14,69.381,-119.853,69.342,-118.869,69.257,-118.745,69.234,-118.486,69.145,-118.307,69.093,-118.095,69.043,-117.83,69,-117.311,68.935,-117.227,68.913,-117.132,68.907,-117.026,68.916,-116.55,68.879,-116.425,68.881,-116.334,68.874,-116.223,68.847,-116.059,68.837,-116.065,68.855,-116.252,68.958,-116.243,68.974,-116.167,68.975,-115.936,68.958,-115.883,68.987,-115.806,68.987,-115.631,68.973,-115.442,68.941,-115.24,68.892,-114.994,68.85,-114.62,68.746,-114.414,68.66,-114.218,68.552,-114.111,68.477,-114.092,68.435,-114.051,68.415,-113.988,68.415,-113.964,68.399,-114.021,68.306,-114.053,68.283,-114.096,68.267,-114.275,68.248,-114.765,68.27,-114.852,68.195,-115.127,68.132,-115.176,68.104,-115.187,68.084,-115.167,68.019,-115.202,67.998,-115.427,67.924,-115.434,67.902,-115.288,67.872,-115.133,67.819,-115.011,67.806,-114.857,67.814,-114.663,67.795,-114.429,67.751,-114.267,67.731,-114.176,67.735,-114.051,67.727,-113.893,67.707,-113.682,67.7,-113.215,67.702,-113.075,67.687,-112.879,67.68,-112.503,67.682,-112.435,67.685,-112.315,67.72,-112.237,67.731,-112.101,67.732,-111.711,67.757,-111.576,67.757,-111.451,67.776,-111.291,67.815,-111.192,67.823,-111.155,67.798,-111.087,67.788,-110.99,67.791,-110.805,67.832,-110.372,67.954,-110.216,67.954,-110.102,67.992,-110.074,67.993,-110.042,67.977,-109.937,67.888,-109.904,67.874,-109.831,67.866,-109.76,67.82,-109.686,67.752,-109.63,67.733,-109.224,67.73,-109.081,67.711,-109.038,67.691,-108.994,67.637,-108.968,67.532,-108.95,67.494,-108.891,67.438,-108.852,67.422,-108.815,67.438,-108.715,67.583,-108.68,67.606,-108.613,67.598,-108.593,67.591,-108.492,67.483,-108.347,67.403,-107.989,67.256,-107.931,67.202,-107.909,67.163,-107.929,67.127,-107.991,67.095,-108.088,67.07,-108.221,67.051,-108.344,67.058,-108.459,67.09,-108.496,67.092,-108.455,67.063,-108.218,66.941,-108.158,66.893,-108.101,66.86,-108.05,66.844,-108.002,66.818,-107.958,66.781,-107.761,66.684,-107.705,66.637,-107.48,66.492,-107.374,66.435,-107.291,66.402,-107.259,66.399,-107.278,66.425,-107.564,66.619,-107.71,66.74,-107.731,66.769,-107.74,66.814,-107.746,66.961,-107.725,66.984,-107.626,67.003,-107.499,66.936,-107.451,66.927,-107.419,66.931,-107.402,66.948,-107.329,66.932,-107.2,66.883,-107.156,66.882,-107.254,66.976,-107.323,67.023,-107.348,67.055,-107.283,67.103,-107.318,67.128,-107.482,67.199,-107.567,67.273,-107.644,67.385,-107.651,67.428,-107.638,67.474,-107.65,67.511,-107.753,67.587,-107.865,67.639,-107.954,67.7,-107.972,67.732,-107.958,67.819,-107.891,67.856,-107.763,67.907,-107.729,67.959,-107.787,68.013,-107.798,68.037,-107.761,68.032,-107.509,68.059,-107.446,68.05,-107.351,68.061,-107.224,68.094,-107.125,68.108,-106.994,68.106,-106.923,68.114,-106.836,68.129,-106.791,68.145,-106.711,68.207,-106.668,68.216,-106.535,68.209,-106.459,68.196,-106.424,68.201,-106.429,68.288,-106.404,68.319,-106.271,68.383,-106.132,68.39,-106.039,68.407,-105.933,68.443,-105.857,68.475,-105.781,68.527,-105.75,68.592,-105.774,68.611,-105.932,68.637,-106.027,68.623,-106.237,68.577,-106.458,68.516,-106.543,68.461,-106.567,68.389,-106.608,68.357,-106.78,68.387,-106.854,68.387,-106.946,68.374,-107.043,68.347,-107.146,68.304,-107.298,68.296,-107.499,68.324,-107.619,68.331,-107.742,68.286,-107.734,68.252,-107.678,68.203,-107.734,68.174,-108.027,68.163,-108.105,68.169,-108.261,68.15,-108.323,68.154,-108.368,68.178,-108.687,68.277,-108.718,68.297,-108.641,68.379,-108.346,68.598,-108.313,68.611,-107.766,68.649,-107.436,68.689,-106.831,68.809,-106.713,68.819,-106.324,68.899,-106.164,68.92,-106.016,68.906,-105.798,68.865,-105.686,68.828,-105.606,68.782,-105.54,68.719,-105.457,68.578,-105.429,68.458,-105.377,68.414,-105.195,68.33,-105.101,68.298,-105.044,68.288,-104.994,68.307,-104.96,68.311,-104.937,68.303,-104.912,68.25,-104.879,68.245,-104.77,68.252,-104.653,68.23,-104.636,68.214,-104.661,68.149,-104.628,68.121,-104.487,68.063,-104.351,68.041,-104.194,68.031,-103.902,68.041,-103.657,68.069,-103.474,68.115,-103.323,68.064,-103.022,67.94,-102.842,67.853,-102.692,67.812,-102.389,67.762,-102.32,67.736,-102.21,67.733,-102.057,67.753,-101.884,67.745,-101.689,67.709,-101.555,67.693,-101.096,67.762,-101.026,67.766,-100.856,67.799,-100.746,67.809,-100.616,67.808,-100.52,67.818,-100.456,67.839,-100.213,67.839,-99.773,67.815,-99.472,67.784,-99.294,67.745,-99.147,67.724,-99.032,67.719,-98.92,67.726,-98.812,67.744,-98.697,67.78,-98.453,67.798,-98.412,67.807,-98.417,67.826,-98.468,67.856,-98.606,67.911,-98.704,67.966,-98.722,68,-98.72,68.042,-98.69,68.066,-98.632,68.073,-98.54,68.047,-98.415,67.988,-98.063,67.77,-97.978,67.739,-97.931,67.711,-97.607,67.631,-97.455,67.617,-97.274,67.666,-97.194,67.697,-97.155,67.726,-97.157,67.755,-97.14,67.796,-97.158,67.822,-97.207,67.855,-97.336,67.901,-97.547,67.961,-97.739,67.978,-97.913,67.954,-98.11,67.903,-98.193,67.923,-98.438,68.065,-98.5,68.118,-98.5,68.132,-98.386,68.115,-98.381,68.132,-98.449,68.201,-98.491,68.224,-98.633,68.331,-98.65,68.364,-98.562,68.371,-98.522,68.383,-98.469,68.382,-98.219,68.317,-98.091,68.346,-97.794,68.388,-97.911,68.45,-97.939,68.51,-97.925,68.524,-97.829,68.533,-97.64,68.482,-97.548,68.475,-97.481,68.495,-97.41,68.497,-97.336,68.479,-97.266,68.453,-97.136,68.378,-97.072,68.333,-97,68.265,-96.977,68.255,-96.628,68.25,-96.431,68.311,-96.435,68.29,-96.48,68.243,-96.725,68.061,-96.722,68.039,-96.592,68.048,-96.531,68.063,-96.494,68.085,-96.461,68.136,-96.439,68.151,-96.076,68.237,-95.97,68.249,-96.036,68.158,-96.171,67.832,-96.199,67.718,-96.228,67.679,-96.371,67.554,-96.369,67.51,-96.213,67.404,-96.185,67.376,-96.169,67.289,-96.141,67.272,-96.013,67.271,-95.879,67.298,-95.72,67.317,-95.695,67.299,-95.783,67.194,-95.778,67.185,-95.626,67.212,-95.557,67.215,-95.529,67.209,-95.416,67.156,-95.405,67.116,-95.407,67.056,-95.419,67.013,-95.457,66.989,-95.502,66.98,-95.559,66.973,-95.611,66.976,-95.769,66.967,-95.862,66.978,-95.954,67.011,-96.02,67.019,-96.095,66.994,-96.216,66.998,-96.35,67.07,-96.404,67.063,-96.423,67.052,-96.42,67.036,-96.36,66.989,-95.885,66.741,-95.813,66.69,-95.797,66.617,-95.788,66.617,-95.743,66.69,-95.772,66.726,-96.016,66.87,-96.045,66.923,-96.037,66.938,-95.972,66.952,-95.625,66.916,-95.49,66.924,-95.4,66.949,-95.354,66.981,-95.321,67.152,-95.259,67.263,-95.296,67.361,-95.39,67.518,-95.463,67.61,-95.634,67.704,-95.65,67.737,-95.461,68.021,-95.427,68.045,-95.384,68.056,-95.235,68.06,-95.126,68.083,-94.955,68.05,-94.861,68.042,-94.744,68.071,-94.485,68.19,-94.384,68.227,-94.255,68.297,-94.098,68.399,-93.928,68.474,-93.652,68.543,-93.483,68.599,-93.449,68.619,-93.606,68.624,-93.644,68.633,-93.676,68.686,-93.66,68.784,-93.663,68.838,-93.681,68.887,-93.716,68.931,-93.766,68.97,-93.811,68.993,-93.852,69,-93.881,68.997,-93.896,68.982,-93.938,68.889,-93.992,68.821,-94.065,68.785,-94.217,68.761,-94.478,68.743,-94.587,68.776,-94.6,68.803,-94.563,68.912,-94.476,68.958,-94.237,69.05,-94.084,69.123,-94.081,69.136,-94.222,69.136,-94.255,69.151,-94.285,69.242,-94.277,69.275,-94.255,69.314,-94.156,69.342,-93.854,69.376,-93.619,69.417,-93.613,69.403,-93.801,69.281,-93.82,69.253,-93.749,69.226,-93.567,69.297,-93.451,69.355,-93.431,69.375,-93.537,69.382,-93.543,69.406,-93.522,69.451,-93.532,69.481,-93.65,69.519,-93.794,69.498,-93.915,69.458,-94.015,69.447,-94.163,69.446,-94.271,69.455,-94.338,69.474,-94.419,69.517,-94.514,69.583,-94.634,69.65,-94.676,69.657,-94.713,69.649,-94.789,69.585,-94.823,69.578,-95.292,69.667,-95.491,69.718,-95.588,69.756,-95.707,69.778,-95.851,69.785,-95.965,69.803,-96.05,69.831,-96.119,69.872,-96.172,69.925,-96.269,69.992,-96.492,70.125,-96.551,70.21,-96.56,70.243,-96.546,70.327,-96.337,70.47,-96.298,70.511,-96.226,70.542,-96.123,70.561,-96.048,70.567,-95.879,70.549,-95.98,70.593,-95.988,70.617,-95.886,70.694,-95.906,70.698,-96.186,70.638,-96.258,70.642,-96.359,70.679,-96.549,70.809,-96.551,70.89,-96.491,71.002,-96.47,71.07,-96.525,71.127,-96.504,71.143,-96.445,71.159,-96.421,71.176,-96.447,71.24,-96.406,71.274,-96.271,71.339,-96.14,71.396,-96.062,71.414,-95.994,71.411,-95.924,71.393,-95.851,71.361,-95.725,71.328,-95.633,71.319,-95.564,71.337,-95.448,71.46,-95.406,71.492,-95.445,71.505,-95.674,71.504,-95.773,71.514,-95.83,71.526,-95.872,71.573,-95.838,71.598,-95.616,71.685,-95.512,71.777,-95.201,71.904,-94.887,71.963,-94.735,71.983,-94.611,71.987,-94.557,71.979,-94.491,71.916,-94.479,71.849,-94.308,71.765,-94.171,71.758,-94.086,71.771,-93.81,71.766,-93.746,71.743,-93.751,71.717,-93.782,71.674,-93.763,71.638,-93.576,71.569,-93.407,71.521,-93.256,71.461,-93.031,71.336,-92.983,71.3,-92.949,71.262,-92.89,71.122,-92.883,71.069,-92.904,70.916,-92.922,70.887,-92.981,70.852,-92.961,70.838,-92.783,70.798,-92.642,70.719,-92.567,70.693,-92.388,70.65,-92.356,70.634,-92.315,70.608,-92.214,70.493,-92.049,70.39,-92.037,70.367,-92.073,70.319,-92.047,70.303,-91.984,70.286,-91.926,70.295,-91.876,70.331,-91.82,70.342,-91.761,70.326,-91.716,70.299,-91.654,70.233,-91.564,70.178,-91.572,70.162,-91.616,70.148,-91.859,70.133,-91.995,70.143,-92.121,70.17,-92.209,70.198,-92.29,70.24,-92.321,70.235,-92.363,70.201,-92.455,70.15,-92.512,70.104,-92.446,70.083,-92.127,70.085,-92.058,70.071,-91.977,70.039,-92.069,69.984,-92.285,69.892,-92.751,69.714,-92.888,69.668,-92.855,69.655,-92.803,69.651,-92.643,69.659,-92.493,69.683,-92.312,69.673,-92.231,69.653,-92.258,69.634,-92.209,69.603,-91.912,69.531,-91.724,69.546,-91.532,69.615,-91.384,69.649,-91.202,69.645,-91.151,69.637,-91.17,69.62,-91.305,69.581,-91.427,69.538,-91.44,69.526,-91.288,69.543,-90.95,69.515,-90.786,69.509,-90.667,69.516,-90.555,69.504,-90.451,69.475,-90.416,69.457,-90.513,69.445,-90.606,69.445,-90.684,69.428,-90.749,69.392,-90.795,69.347,-90.822,69.29,-90.892,69.267,-91.005,69.277,-91.049,69.293,-91.025,69.315,-91.058,69.318,-91.148,69.303,-91.218,69.302,-91.237,69.286,-90.745,69.106,-90.587,68.947,-90.479,68.881,-90.468,68.864,-90.539,68.82,-90.543,68.786,-90.51,68.689,-90.525,68.611,-90.574,68.475,-90.528,68.432,-90.423,68.395,-90.36,68.347,-90.317,68.33,-90.285,68.292,-90.248,68.267,-90.205,68.257,-90.174,68.27,-90.157,68.306,-90.116,68.339,-90.005,68.398,-89.898,68.491,-89.879,68.522,-89.897,68.594,-89.884,68.626,-89.783,68.736,-89.751,68.812,-89.72,68.932,-89.667,69.015,-89.552,69.085,-89.352,69.227,-89.28,69.255,-89.198,69.269,-89.057,69.266,-88.954,69.22,-88.815,69.136,-88.638,69.059,-88.316,68.954,-88.224,68.915,-88.041,68.812,-87.964,68.709,-87.912,68.565,-87.866,68.478,-87.828,68.448,-87.81,68.404,-87.814,68.346,-87.828,68.3,-87.853,68.267,-87.893,68.248,-87.991,68.242,-88.111,68.251,-88.146,68.266,-88.209,68.335,-88.235,68.339,-88.347,68.288,-88.361,68.26,-88.32,68.166,-88.325,67.989,-88.314,67.95,-88.196,67.766,-87.997,67.626,-87.499,67.355,-87.471,67.325,-87.418,67.214,-87.392,67.191,-87.359,67.177,-87.32,67.173,-87.266,67.184,-87.083,67.268,-86.924,67.356,-86.813,67.402,-86.75,67.406,-86.682,67.422,-86.609,67.451,-86.561,67.482,-86.536,67.516,-86.504,67.649,-86.476,67.713,-86.398,67.8,-86.37,67.825,-85.984,68.045,-85.953,68.072,-85.789,68.328,-85.731,68.445,-85.723,68.515,-85.745,68.578,-85.734,68.63,-85.69,68.671,-85.643,68.7,-85.562,68.729,-85.518,68.77,-85.491,68.774,-85.425,68.774,-85.338,68.746,-85.275,68.741,-84.868,68.773,-84.867,68.79,-85.107,68.844,-85.104,68.871,-85.083,68.908,-85.008,68.949,-84.916,68.962,-84.895,68.989,-84.893,69.021,-84.862,69.074,-84.89,69.093,-85.114,69.166,-85.243,69.163,-85.275,69.172,-85.387,69.232,-85.428,69.318,-85.432,69.354,-85.416,69.411,-85.402,69.427,-85.409,69.452,-85.437,69.488,-85.44,69.52,-85.416,69.548,-85.43,69.581,-85.482,69.619,-85.502,69.652,-85.448,69.748,-85.446,69.778,-85.497,69.819,-85.535,69.835,-85.507,69.845,-85.415,69.85,-85.305,69.836,-85.177,69.805,-85.02,69.805,-84.834,69.835,-84.645,69.85,-84.319,69.844,-84.242,69.835,-83.917,69.745,-83.665,69.7,-83.552,69.704,-82.991,69.686,-82.746,69.695,-82.618,69.691,-82.374,69.642,-82.39,69.601,-82.496,69.532,-82.633,69.518,-82.755,69.494,-82.642,69.458,-82.31,69.41,-82.232,69.333,-82.208,69.297,-82.247,69.265,-82.228,69.249,-82.151,69.249,-81.952,69.276,-81.732,69.258,-81.412,69.198,-81.378,69.186,-81.322,69.139,-81.329,69.12,-81.611,69.003,-81.758,68.957,-81.952,68.909,-81.958,68.884,-81.687,68.879,-81.476,68.866,-81.381,68.85,-81.331,68.828,-81.264,68.781,-81.252,68.743,-81.259,68.692,-81.282,68.657,-81.527,68.556,-81.64,68.524,-81.831,68.487,-81.915,68.459,-82.006,68.463,-82.106,68.499,-82.21,68.506,-82.397,68.478,-82.499,68.479,-82.549,68.469,-82.553,68.446,-82.464,68.382,-82.413,68.357,-82.393,68.338,-82.431,68.307,-82.423,68.297,-82.393,68.285,-82.222,68.145,-82.187,68.134,-82.151,68.14,-82.078,68.18,-82.034,68.196,-82.012,68.194,-82.013,68.173,-82.092,68.051,-82.102,68.019,-82.1,67.99,-82.063,67.928,-81.976,67.862,-81.869,67.802,-81.709,67.722,-81.493,67.637,-81.412,67.595,-81.294,67.497,-81.27,67.46,-81.301,67.357,-81.387,67.189,-81.443,67.093,-81.468,67.07,-81.63,67.002,-81.722,66.986,-81.874,66.988,-81.926,66.975,-82.005,66.92,-82.113,66.825,-82.198,66.765,-82.261,66.739,-82.375,66.709,-82.554,66.621,-82.642,66.588,-82.949,66.551,-83.199,66.431,-83.298,66.392,-83.406,66.371,-83.523,66.369,-83.59,66.388,-83.628,66.461,-83.651,66.485,-83.739,66.534,-83.92,66.679,-83.998,66.729,-84.05,66.74,-84.154,66.732,-84.208,66.736,-84.324,66.782,-84.366,66.811,-84.361,66.823,-84.273,66.839,-84.31,66.863,-84.466,66.927,-84.531,66.961,-84.538,66.973,-84.693,67.017,-84.846,67.029,-85.04,66.956,-85.114,66.907,-85.111,66.891,-85.018,66.872,-84.978,66.881,-84.899,66.927,-84.857,66.941,-84.738,66.934,-84.639,66.902,-84.603,66.875,-84.59,66.857,-84.319,66.712,-84.223,66.682,-84.183,66.648,-84.153,66.59,-84.094,66.526,-83.964,66.421,-83.826,66.29,-83.798,66.238,-83.869,66.214,-83.905,66.212,-84.012,66.231,-84.293,66.292,-84.324,66.291,-84.398,66.259,-84.459,66.186,-84.478,66.179,-84.628,66.208,-84.908,66.271,-85.096,66.325,-85.192,66.37,-85.307,66.44,-85.442,66.537,-85.604,66.568,-85.792,66.533,-86.063,66.52,-86.633,66.531,-86.708,66.523,-86.737,66.511,-86.689,66.457,-86.7,66.443,-86.738,66.433,-86.747,66.417,-86.685,66.36,-86.585,66.322,-86.301,66.27,-86.113,66.225,-86.001,66.187,-85.964,66.154,-85.959,66.119,-86.012,66.048,-86.043,66.023,-86.702,65.671,-86.953,65.528,-87.081,65.441,-87.194,65.383,-87.291,65.355,-87.453,65.339,-87.678,65.335,-87.97,65.349,-88.121,65.395,-88.395,65.516,-88.587,65.588,-88.672,65.612,-88.744,65.679,-88.808,65.692,-88.946,65.703,-89.088,65.739,-89.42,65.861,-89.593,65.909,-89.749,65.936,-89.89,65.941,-89.944,65.934,-89.848,65.872,-89.89,65.869,-90.004,65.883,-90.117,65.882,-90.316,65.926,-90.513,65.921,-90.655,65.929,-90.826,65.954,-91.01,65.966,-91.305,65.965,-91.412,65.959,-91.427,65.948,-91.285,65.894,-91.041,65.83,-91.074,65.886,-91.065,65.9,-90.983,65.919,-90.597,65.885,-90.159,65.813,-90.048,65.806,-89.924,65.78,-89.788,65.737,-89.6,65.648,-89.242,65.446,-89.127,65.396,-88.974,65.348,-88.198,65.28,-87.93,65.28,-87.392,65.261,-87.108,65.225,-87.028,65.198,-87.003,65.109,-87.029,65.064,-87.183,64.927,-87.281,64.826,-87.885,64.4,-87.964,64.302,-87.998,64.244,-88.106,64.183,-88.379,64.089,-88.653,64.009,-88.818,63.992,-88.964,64.011,-89.06,64.034,-89.201,64.114,-89.209,64.105,-89.108,63.981,-89.132,63.969,-89.215,63.984,-89.404,64.04,-89.465,64.03,-89.501,64.015,-89.551,64.015,-89.616,64.031,-89.733,64.077,-89.764,64.1,-89.792,64.168,-89.811,64.181,-90.042,64.141,-90.08,64.128,-89.986,64.1,-89.954,64.081,-89.861,63.979,-89.856,63.957,-89.922,63.944,-90.142,63.982,-90.168,63.979,-90.06,63.877,-90.018,63.829,-90.013,63.804,-90.155,63.69,-90.245,63.642,-90.369,63.624,-90.446,63.636,-90.533,63.665,-90.596,63.661,-90.635,63.624,-90.707,63.597,-90.812,63.581,-90.946,63.588,-91.108,63.618,-91.539,63.726,-91.675,63.742,-91.926,63.757,-91.956,63.772,-91.954,63.787,-91.919,63.801,-91.929,63.812,-91.982,63.822,-92.038,63.813,-92.095,63.784,-92.195,63.776,-92.338,63.788,-92.55,63.83,-92.97,63.938,-93.43,64.029,-93.696,64.147,-93.597,64.041,-93.605,64.004,-93.656,63.973,-93.664,63.941,-93.56,63.865,-93.416,63.838,-93.27,63.841,-93.266,63.853,-93.327,63.872,-93.38,63.9,-93.406,63.941,-93.379,63.948,-93.25,63.927,-93.166,63.902,-92.529,63.761,-92.339,63.735,-92.196,63.708,-92.157,63.692,-92.205,63.657,-92.461,63.569,-92.465,63.555,-92.29,63.563,-92.077,63.64,-91.957,63.676,-91.842,63.698,-91.686,63.66,-91.489,63.562,-91.33,63.507,-91.103,63.476,-90.97,63.443,-90.747,63.352,-90.711,63.304,-90.691,63.111,-90.699,63.064,-90.728,63.017,-90.778,62.972,-90.871,62.946,-91.008,62.94,-91.115,62.922,-91.349,62.819,-91.449,62.804,-91.87,62.835,-92.034,62.863,-92.11,62.862,-92.152,62.839,-92.196,62.829,-92.361,62.819,-92.388,62.801,-92.378,62.772,-92.345,62.734,-92.305,62.712,-92.243,62.684,-92.149,62.665,-91.956,62.645,-91.936,62.592,-91.944,62.575,-92.008,62.541,-92.081,62.544,-92.207,62.585,-92.27,62.587,-92.324,62.565,-92.4,62.557,-92.497,62.565,-92.551,62.547,-92.562,62.503,-92.595,62.47,-92.707,62.418,-92.768,62.38,-92.766,62.35,-92.701,62.328,-92.627,62.279,-92.544,62.202,-92.528,62.168,-92.579,62.177,-92.648,62.208,-92.735,62.26,-92.866,62.306,-93.154,62.367,-93.205,62.365,-93.179,62.35,-92.988,62.286,-92.914,62.245,-92.906,62.215,-93.066,62.15,-93.07,62.128,-93.028,62.109,-93.016,62.093,-93.073,62.061,-93.167,62.034,-93.35,62.03,-93.366,62.015,-93.297,61.982,-93.273,61.961,-93.333,61.933,-93.372,61.929,-93.582,61.942,-93.527,61.872,-93.494,61.847,-93.43,61.812,-93.314,61.78,-93.312,61.767,-93.352,61.74,-93.421,61.706,-93.71,61.603,-93.913,61.481,-93.941,61.444,-93.889,61.364,-93.889,61.344,-93.942,61.308,-94.061,61.318,-94.083,61.304,-94.055,61.266,-94.05,61.211,-94.068,61.139,-94.154,61.025,-94.309,60.871,-94.427,60.731,-94.509,60.605,-94.569,60.542,-94.679,60.538,-94.762,60.498,-94.705,60.478,-94.671,60.453,-94.647,60.416,-94.67,60.301,-94.742,60.107,-94.786,59.953,-94.777,59.478,-94.788,59.268,-94.82,59.151,-94.87,59.088,-94.957,59.069,-94.847,59.05,-94.776,59.021,-94.744,58.975,-94.713,58.903,-94.673,58.87,-94.624,58.876,-94.579,58.868,-94.54,58.848,-94.419,58.746,-94.287,58.716,-94.281,58.659,-94.333,58.339,-94.332,58.297,-94.272,58.378,-94.209,58.626,-94.123,58.737,-94.056,58.76,-93.78,58.773,-93.486,58.744,-93.375,58.741,-93.278,58.756,-93.179,58.726,-93.155,58.695,-93.127,58.564,-93.1,58.49,-92.925,58.225,-92.842,58.076,-92.74,57.844,-92.702,57.778,-92.49,57.469,-92.449,57.385,-92.433,57.32,-92.44,57.275,-92.478,57.205,-92.548,57.111,-92.614,57.039,-92.675,56.99,-92.738,56.953,-92.802,56.928,-92.798,56.922,-92.725,56.934,-92.651,56.958,-92.51,57.022,-92.456,57.037,-92.303,57.046,-92.298,57.023,-92.372,56.975,-92.356,56.971,-92.249,57.009,-92.018,57.064,-91.111,57.241,-90.897,57.257,-90.592,57.224,-90.345,57.149,-90.075,57.052,-89.791,56.981,-89.342,56.915,-89.212,56.884,-88.948,56.851,-88.826,56.814,-88.68,56.725,-88.447,56.609,-88.271,56.536,-88.075,56.467,-87.878,56.342,-87.561,56.056,-87.482,56.021,-87.287,55.975,-86.919,55.915,-86.377,55.773,-86.139,55.718,-85.984,55.696,-85.831,55.657,-85.677,55.601,-85.559,55.54,-85.478,55.474,-85.407,55.431,-85.283,55.383,-85.218,55.349,-85.212,55.297,-85.362,55.095,-85.365,55.079,-85.214,55.224,-85.129,55.266,-85.061,55.286,-84.92,55.283,-84.706,55.259,-84.518,55.259,-84.356,55.283,-84.219,55.293,-84.105,55.291,-84.023,55.298,-83.972,55.314,-83.911,55.315,-83.668,55.265,-83.569,55.262,-83.214,55.215,-82.986,55.231,-82.947,55.222,-82.868,55.161,-82.801,55.156,-82.687,55.166,-82.577,55.149,-82.393,55.068,-82.308,54.998,-82.227,54.856,-82.219,54.813,-82.371,54.483,-82.418,54.356,-82.424,54.245,-82.394,54.18,-82.264,54.073,-82.24,54.045,-82.163,53.886,-82.141,53.818,-82.15,53.74,-82.191,53.611,-82.18,53.513,-82.146,53.365,-82.159,53.264,-82.219,53.211,-82.26,53.16,-82.292,53.066,-82.292,53.031,-82.26,52.961,-82.203,52.922,-82.108,52.877,-82.02,52.812,-81.859,52.651,-81.742,52.564,-81.599,52.433,-81.572,52.367,-81.612,52.324,-81.661,52.294,-81.776,52.254,-81.828,52.224,-81.815,52.217,-81.648,52.239,-81.55,52.237,-81.466,52.204,-81.398,52.142,-81.285,52.089,-81.127,52.045,-80.969,51.972,-80.706,51.798,-80.658,51.758,-80.588,51.667,-80.496,51.525,-80.448,51.432,-80.443,51.389,-80.496,51.345,-80.673,51.265,-80.851,51.125,-80.795,51.132,-80.677,51.191,-80.478,51.307,-80.368,51.33,-80.266,51.316,-80.104,51.283,-79.96,51.235,-79.836,51.173,-79.652,51.008,-79.456,50.876,-79.348,50.763,-79.381,50.835,-79.453,50.917,-79.636,51.049,-79.714,51.118,-79.731,51.15,-79.737,51.186,-79.723,51.252,-79.689,51.347,-79.643,51.414,-79.586,51.452,-79.547,51.494,-79.528,51.538,-79.498,51.57,-79.339,51.628,-79.297,51.623,-79.264,51.552,-79.226,51.537,-79.153,51.526,-79.091,51.502,-79.041,51.464,-79.005,51.425,-78.984,51.386,-78.937,51.259,-78.903,51.2,-78.898,51.272,-78.858,51.384,-78.827,51.43,-78.731,51.497,-78.736,51.527,-78.776,51.566,-78.978,51.734,-78.982,51.775,-78.928,51.799,-78.891,51.845,-78.871,51.913,-78.828,51.963,-78.702,52.033,-78.593,52.14,-78.537,52.213,-78.492,52.252,-78.448,52.261,-78.513,52.291,-78.526,52.311,-78.529,52.399,-78.557,52.492,-78.601,52.535,-78.724,52.628,-78.744,52.655,-78.766,52.76,-78.754,52.812,-78.722,52.856,-78.74,52.899,-78.854,52.976,-78.898,53.043,-78.947,53.206,-78.992,53.41,-79.043,53.56,-79.1,53.657,-79.113,53.717,-79.081,53.742,-79.04,53.818,-79.003,53.837,-78.946,53.832,-78.944,53.84,-79.032,53.881,-79.075,53.932,-79.073,53.951,-78.996,54.002,-79.01,54.024,-79.067,54.052,-79.242,54.099,-79.179,54.117,-79.139,54.157,-79.147,54.169,-79.216,54.186,-79.296,54.217,-79.356,54.263,-79.431,54.337,-79.476,54.395,-79.521,54.492,-79.598,54.602,-79.632,54.629,-79.67,54.647,-79.714,54.655,-79.712,54.672,-79.666,54.697,-78.909,54.881,-78.846,54.908,-78.475,55.011,-78.304,55.069,-78.129,55.151,-77.891,55.236,-77.775,55.291,-77.702,55.344,-77.325,55.556,-77.165,55.664,-77.073,55.756,-76.938,55.867,-76.762,55.996,-76.65,56.107,-76.604,56.2,-76.546,56.359,-76.53,56.5,-76.52,56.707,-76.526,56.892,-76.573,57.181,-76.601,57.272,-76.655,57.381,-76.786,57.599,-76.81,57.658,-76.891,57.758,-77.157,58.019,-77.489,58.195,-77.552,58.24,-77.684,58.291,-77.884,58.351,-78.014,58.399,-78.352,58.581,-78.463,58.602,-78.506,58.649,-78.515,58.682,-78.502,58.769,-78.483,58.829,-78.459,58.873,-78.431,58.902,-78.244,59.035,-78.14,59.142,-78.068,59.2,-77.988,59.246,-77.843,59.305,-77.761,59.38,-77.779,59.41,-77.845,59.444,-77.859,59.476,-77.749,59.558,-77.733,59.581,-77.748,59.658,-77.726,59.676,-77.59,59.681,-77.397,59.569,-77.349,59.579,-77.411,59.61,-77.485,59.685,-77.475,59.716,-77.332,59.797,-77.328,59.833,-77.368,59.884,-77.373,59.925,-77.289,60.022,-77.312,60.042,-77.547,60.061,-77.586,60.088,-77.572,60.101,-77.461,60.133,-77.453,60.146,-77.649,60.363,-77.681,60.427,-77.598,60.507,-77.504,60.543,-77.516,60.563,-77.639,60.567,-77.715,60.578,-77.791,60.64,-77.761,60.679,-77.734,60.697,-77.661,60.79,-77.59,60.809,-77.603,60.825,-77.872,60.786,-77.998,60.818,-78.122,60.81,-78.181,60.819,-78.16,60.852,-77.934,61.003,-77.83,61.084,-77.765,61.158,-77.731,61.206,-77.727,61.231,-77.75,61.393,-77.736,61.437,-77.649,61.479,-77.514,61.556,-77.698,61.626,-77.814,61.695,-77.89,61.729,-77.948,61.762,-78.021,61.832,-78.077,61.923,-78.137,62.107,-78.147,62.209,-78.133,62.282,-78.109,62.318,-78.068,62.355,-77.9,62.427,-77.604,62.531,-77.372,62.573,-77.205,62.55,-76.879,62.525,-76.616,62.466,-75.817,62.316,-75.676,62.25,-75.809,62.193,-75.79,62.18,-75.489,62.286,-75.409,62.307,-75.341,62.312,-75.114,62.271,-75.023,62.264,-74.908,62.23,-74.633,62.116,-74.613,62.125,-74.69,62.183,-74.646,62.211,-74.429,62.272,-74.205,62.321,-74.046,62.37,-73.878,62.434,-73.764,62.469,-73.705,62.473,-73.63,62.454,-73.428,62.369,-73.299,62.325,-73.195,62.279,-73.049,62.198,-72.992,62.18,-72.882,62.125,-72.735,62.131,-72.687,62.125,-72.671,62.114,-72.646,62.077,-72.633,62.053,-72.632,62.027,-72.666,61.955,-72.772,61.84,-72.727,61.839,-72.661,61.863,-72.574,61.907,-72.506,61.923,-72.361,61.888,-72.226,61.832,-72.178,61.802,-72.126,61.753,-72.081,61.728,-72.04,61.68,-72.043,61.665,-72.082,61.641,-72.247,61.602,-72.216,61.587,-72.023,61.612,-71.964,61.636,-71.922,61.677,-71.866,61.689,-71.638,61.617,-71.605,61.592,-71.619,61.573,-71.656,61.551,-71.756,61.527,-71.841,61.466,-71.854,61.44,-71.794,61.421,-71.645,61.413,-71.646,61.399,-71.732,61.372,-71.743,61.337,-71.552,61.213,-71.423,61.159,-71.348,61.149,-71.175,61.147,-71.035,61.126,-70.723,61.055,-70.541,61.042,-70.384,61.064,-70.279,61.069,-70.187,61.041,-70.158,61.021,-70.144,60.981,-70.146,60.922,-70.095,60.88,-69.992,60.856,-69.909,60.86,-69.8,60.907,-69.708,60.915,-69.678,60.95,-69.65,61.014,-69.624,61.05,-69.557,61.06,-69.503,61.04,-69.472,61.011,-69.414,60.922,-69.398,60.883,-69.405,60.847,-69.433,60.814,-69.49,60.78,-69.574,60.743,-69.64,60.69,-69.721,60.567,-69.751,60.487,-69.759,60.44,-69.756,60.389,-69.741,60.332,-69.708,60.286,-69.633,60.22,-69.629,60.199,-69.623,60.145,-69.63,60.122,-69.674,60.076,-69.796,60.03,-69.963,60.018,-70.509,60.015,-70.655,60.026,-70.62,59.984,-70.467,59.971,-70.327,59.971,-69.806,59.945,-69.734,59.918,-69.673,59.871,-69.63,59.822,-69.587,59.722,-69.579,59.675,-69.602,59.623,-69.656,59.565,-69.692,59.488,-69.711,59.393,-69.682,59.342,-69.4,59.338,-69.344,59.303,-69.35,59.277,-69.45,59.18,-69.46,59.152,-69.414,59.087,-69.42,59.068,-69.448,59.049,-69.475,59,-69.5,58.921,-69.532,58.869,-69.608,58.829,-69.648,58.821,-69.677,58.831,-69.753,58.94,-69.784,58.956,-69.814,58.946,-69.829,58.929,-69.829,58.905,-69.842,58.881,-69.868,58.856,-69.979,58.816,-70.16,58.789,-70.154,58.761,-70.033,58.745,-69.879,58.697,-69.79,58.689,-69.651,58.728,-69.382,58.851,-69.271,58.884,-69.173,58.897,-69.064,58.898,-68.942,58.889,-68.698,58.905,-68.637,58.893,-68.563,58.866,-68.475,58.823,-68.414,58.783,-68.381,58.744,-68.326,58.595,-68.253,58.557,-68.235,58.528,-68.229,58.485,-68.234,58.399,-68.315,58.227,-68.357,58.163,-68.468,58.076,-68.597,58.037,-68.826,58,-68.945,57.969,-69.035,57.926,-69.041,57.902,-68.781,57.976,-68.495,58.012,-68.414,58.052,-68.352,58.091,-68.289,58.178,-68.176,58.403,-68.111,58.473,-68.021,58.485,-67.981,58.461,-67.888,58.329,-67.888,58.296,-67.911,58.267,-68.064,58.139,-68.009,58.152,-67.856,58.273,-67.823,58.31,-67.805,58.365,-67.756,58.405,-67.737,58.385,-67.69,58.244,-67.688,58.14,-67.681,58.107,-67.698,58.009,-67.678,57.991,-67.632,58.076,-67.617,58.14,-67.596,58.186,-67.57,58.213,-67.382,58.3,-67.163,58.37,-67.019,58.433,-66.9,58.463,-66.722,58.491,-66.608,58.549,-66.558,58.637,-66.515,58.697,-66.48,58.731,-66.362,58.791,-66.299,58.795,-66.237,58.772,-66.168,58.727,-66.091,58.659,-66.045,58.606,-66.03,58.567,-66.017,58.431,-66.002,58.431,-65.931,58.535,-65.923,58.572,-65.928,58.611,-65.95,58.65,-66.021,58.735,-66.049,58.788,-66.043,58.821,-65.967,58.839,-65.855,58.847,-65.836,58.86,-65.918,58.896,-65.921,58.915,-65.841,58.977,-65.795,58.98,-65.704,58.971,-65.722,59.003,-65.721,59.024,-65.695,59.032,-65.544,59.012,-65.526,59.036,-65.396,59.038,-65.384,59.06,-65.496,59.091,-65.606,59.111,-65.64,59.128,-65.666,59.153,-65.7,59.213,-65.692,59.229,-65.661,59.23,-65.607,59.213,-65.578,59.245,-65.545,59.32,-65.513,59.35,-65.412,59.315,-65.407,59.33,-65.489,59.448,-65.475,59.47,-65.35,59.479,-65.274,59.464,-65.074,59.378,-65.038,59.388,-65.069,59.411,-65.171,59.462,-65.263,59.495,-65.346,59.511,-65.407,59.539,-65.475,59.617,-65.486,59.649,-65.481,59.69,-65.433,59.777,-65.406,59.795,-65.358,59.809,-65.289,59.818,-65.212,59.81,-65.054,59.753,-65.028,59.771,-65.113,59.802,-65.159,59.83,-65.181,59.867,-65.172,59.908,-65.105,59.993,-65.073,60.062,-64.931,60.252,-64.89,60.287,-64.845,60.308,-64.817,60.331,-64.706,60.336,-64.499,60.268,-64.436,60.228,-64.42,60.171,-64.528,60.095,-64.713,60.037,-64.768,60.012,-64.733,59.998,-64.559,60.043,-64.408,60.065,-64.283,60.064,-64.183,59.973,-64.169,59.847,-64.226,59.741,-64.151,59.794,-64.056,59.823,-63.979,59.754,-63.969,59.698,-63.929,59.645,-63.841,59.574,-63.75,59.513,-63.85,59.448,-63.971,59.409,-63.945,59.38,-63.781,59.349,-63.759,59.319,-63.776,59.277,-63.752,59.277,-63.637,59.341,-63.54,59.333,-63.415,59.194,-63.506,59.115,-63.646,59.079,-63.756,59.063,-63.91,59.066,-63.971,59.054,-63.941,59.027,-63.794,59.027,-63.568,59.047,-63.399,59.08,-63.326,59.082,-63.248,59.068,-63.223,59.057,-63.304,59.034,-63.31,59.026,-63.279,59.003,-63.216,58.928,-63.222,58.911,-63.282,58.867,-63.185,58.858,-63.05,58.878,-63.008,58.855,-62.926,58.765,-62.874,58.672,-63.102,58.546,-63.219,58.52,-63.39,58.453,-63.438,58.399,-63.537,58.33,-63.474,58.331,-63.296,58.441,-63.21,58.467,-63.146,58.46,-63.12,58.442,-63.132,58.411,-63.076,58.415,-62.837,58.479,-62.737,58.492,-62.608,58.496,-62.594,58.474,-62.674,58.319,-62.812,58.2,-63.063,58.127,-63.152,58.084,-63.262,58.015,-63.22,58.002,-62.981,58.093,-62.818,58.129,-62.588,58.158,-62.486,58.154,-62.306,57.972,-62.202,57.955,-62.117,57.964,-61.959,57.912,-61.899,57.861,-61.914,57.825,-61.968,57.803,-61.995,57.769,-61.931,57.669,-61.968,57.612,-62.084,57.562,-62.167,57.537,-62.254,57.529,-62.339,57.485,-62.377,57.478,-62.496,57.489,-62.455,57.462,-62.396,57.448,-62.303,57.441,-62.194,57.455,-62.088,57.453,-61.921,57.421,-61.851,57.381,-61.85,57.37,-61.886,57.348,-61.939,57.274,-61.977,57.248,-61.945,57.228,-61.861,57.198,-61.798,57.186,-61.716,57.196,-61.629,57.183,-61.334,57.011,-61.346,56.922,-61.39,56.853,-61.373,56.776,-61.372,56.681,-61.532,56.655,-62.062,56.699,-62.366,56.767,-62.382,56.788,-62.296,56.833,-62.372,56.836,-62.46,56.818,-62.497,56.802,-62.396,56.73,-62.117,56.667,-61.992,56.591,-61.855,56.584,-61.813,56.571,-61.738,56.526,-61.76,56.511,-61.899,56.505,-62.01,56.454,-61.94,56.424,-61.692,56.397,-61.515,56.39,-61.425,56.361,-61.499,56.328,-61.707,56.289,-61.713,56.231,-61.559,56.208,-61.421,56.222,-61.365,56.216,-61.324,56.076,-61.301,56.047,-61.449,56.022,-61.45,55.996,-61.351,55.974,-61.188,55.955,-61.134,55.93,-61.123,55.889,-61.089,55.866,-60.996,55.862,-60.893,55.914,-60.832,55.958,-60.743,55.941,-60.737,55.887,-60.631,55.825,-60.593,55.815,-60.562,55.727,-60.476,55.805,-60.413,55.789,-60.341,55.785,-60.365,55.709,-60.408,55.65,-60.352,55.612,-60.308,55.557,-60.192,55.481,-60.224,55.444,-60.361,55.366,-60.433,55.243,-60.45,55.2,-60.521,55.129,-60.617,55.06,-60.557,55.067,-60.341,55.194,-60.213,55.236,-59.93,55.259,-59.862,55.295,-59.759,55.31,-59.696,55.269,-59.689,55.196,-59.605,55.173,-59.518,55.197,-59.438,55.176,-59.486,55.13,-59.742,54.943,-59.816,54.867,-59.838,54.814,-59.75,54.887,-59.429,55.056,-59.394,55.081,-59.324,55.153,-59.26,55.2,-59.086,55.183,-58.997,55.149,-58.956,55.055,-58.886,54.952,-58.78,54.838,-58.5,54.783,-58.398,54.774,-58.223,54.813,-58.195,54.866,-58.058,54.882,-57.962,54.876,-57.929,54.773,-57.827,54.719,-57.725,54.674,-57.627,54.65,-57.483,54.64,-57.404,54.591,-57.404,54.57,-57.485,54.517,-57.563,54.44,-57.699,54.387,-57.889,54.384,-58.151,54.35,-58.162,54.32,-58.22,54.286,-58.359,54.253,-58.435,54.228,-58.558,54.103,-58.633,54.05,-58.719,54.039,-58.841,54.044,-58.92,54.033,-58.978,54.01,-59.013,53.976,-59.039,53.964,-59.201,53.929,-59.497,53.834,-59.653,53.831,-59.749,53.842,-59.823,53.834,-59.873,53.808,-60.014,53.762,-60.057,53.733,-60.081,53.701,-60.1,53.634,-60.117,53.61,-60.145,53.596,-60.263,53.61,-60.395,53.653,-60.37,53.607,-60.16,53.53,-60.1,53.487,-60.157,53.45,-60.29,53.391,-60.306,53.36,-60.251,53.344,-60.273,53.317,-60.346,53.289,-60.337,53.277,-60.329,53.266,-60.148,53.307,-59.987,53.393,-59.882,53.48,-59.829,53.505,-59.621,53.537,-59.498,53.575,-59.322,53.644,-59.129,53.744,-58.92,53.875,-58.652,53.978,-58.327,54.052,-58.088,54.09,-57.936,54.091,-57.928,54.104,-58.065,54.127,-58.177,54.131,-58.317,54.114,-58.361,54.154,-58.356,54.172,-58.31,54.202,-58.192,54.228,-57.615,54.191,-57.416,54.163,-57.199,53.924,-57.149,53.848,-57.135,53.792,-57.157,53.757,-57.244,53.715,-57.489,53.633,-57.524,53.611,-57.527,53.6,-57.42,53.583,-57.386,53.561,-57.332,53.469,-57.221,53.529,-57.012,53.673,-56.841,53.739,-56.697,53.758,-56.524,53.766,-56.465,53.765,-56.444,53.718,-56.354,53.624,-56.27,53.6,-56.11,53.588,-55.966,53.471,-55.911,53.391,-55.859,53.344,-55.863,53.312,-55.855,53.286,-55.817,53.246,-55.798,53.212,-55.808,53.135,-55.892,53,-55.83,52.878,-55.858,52.823,-55.873,52.736,-55.819,52.677,-55.803,52.643,-55.848,52.623,-56.167,52.575,-56.292,52.574,-56.325,52.545,-56.228,52.536,-56.053,52.537,-55.84,52.508,-55.746,52.475,-55.706,52.428,-55.716,52.392,-55.777,52.364,-55.897,52.37,-56.012,52.394,-56.005,52.37,-55.834,52.31,-55.783,52.28,-55.691,52.242,-55.673,52.19,-55.695,52.138,-56.017,51.929,-56.283,51.797,-56.549,51.681,-56.976,51.458,-57.018,51.447,-57.096,51.443,-57.299,51.478,-57.462,51.469,-57.77,51.426,-57.854,51.4,-58.023,51.322,-58.089,51.311,-58.27,51.295,-58.442,51.306,-58.51,51.295,-58.593,51.257,-58.615,51.237,-58.638,51.172,-59.055,50.879,-59.165,50.78,-59.378,50.675,-59.612,50.492,-59.815,50.418,-59.886,50.316,-60.08,50.255,-60.438,50.239,-60.608,50.221,-60.807,50.25,-60.956,50.205,-61.181,50.192,-61.29,50.202,-61.725,50.104,-61.835,50.197,-61.92,50.233,-62.165,50.239,-62.362,50.277,-62.541,50.285,-62.715,50.302,-62.83,50.301,-62.95,50.291,-63.136,50.294,-63.239,50.243,-63.587,50.258,-63.734,50.305,-63.854,50.314,-64.016,50.304,-64.17,50.269,-64.509,50.309,-64.868,50.275,-65.181,50.298,-65.269,50.32,-65.762,50.259,-65.955,50.294,-66.126,50.201,-66.242,50.22,-66.369,50.207,-66.411,50.224,-66.496,50.212,-66.55,50.161,-66.622,50.155,-66.741,50.066,-66.941,49.994,-67.234,49.602,-67.262,49.451,-67.372,49.348,-67.469,49.335,-67.549,49.332,-68.056,49.257,-68.282,49.197,-68.221,49.15,-68.295,49.114,-68.414,49.1,-68.544,49.056,-68.628,49.007,-68.669,48.94,-68.929,48.829,-69.231,48.574,-69.375,48.386,-69.55,48.251,-69.674,48.199,-69.762,48.191,-69.852,48.207,-70.001,48.271,-70.111,48.278,-70.384,48.367,-71.018,48.456,-70.923,48.422,-70.839,48.367,-70.671,48.353,-70.501,48.354,-70.145,48.244,-69.971,48.206,-69.866,48.172,-69.775,48.098,-69.84,47.953,-69.906,47.832,-69.994,47.74,-70.3,47.503,-70.448,47.423,-70.706,47.14,-70.973,47.007,-71.116,46.925,-71.268,46.796,-71.625,46.698,-71.757,46.674,-71.88,46.687,-72.028,46.607,-72.205,46.559,-72.257,46.485,-72.68,46.287,-72.843,46.262,-72.981,46.21,-73.022,46.12,-73.145,46.066,-73.18,46.025,-73.284,45.9,-73.477,45.738,-73.712,45.711,-73.798,45.655,-73.897,45.564,-74.038,45.502,-74.315,45.531,-74.248,45.493,-74,45.433,-73.974,45.345,-74.098,45.324,-74.358,45.206,-74.709,45.004,-74.762,44.999,-74.857,45.004,-74.996,44.97,-75.179,44.899,-75.401,44.772,-75.792,44.497,-75.819,44.468,-75.876,44.417,-76.02,44.363,-76.151,44.304,-76.186,44.242,-76.249,44.214,-76.465,44.058,-76.586,43.924,-76.696,43.785,-76.82,43.629,-77.073,43.627,-77.267,43.627,-77.597,43.629,-77.879,43.63,-78.215,43.631,-78.458,43.631,-78.72,43.625,-78.846,43.583,-79.002,43.527,-79.172,43.467,-79.083,43.331,-79.059,43.278,-79.066,43.106,-79.048,43.087,-79.029,43.062,-79.026,43.017,-79.012,42.997,-78.981,42.981,-78.946,42.961,-78.921,42.935,-78.915,42.909,-78.939,42.864,-79.037,42.802,-79.174,42.749,-79.446,42.651,-79.762,42.539,-80.036,42.441,-80.248,42.366,-80.683,42.3,-81.028,42.247,-81.278,42.209,-81.507,42.103,-81.761,41.987,-81.974,41.889,-82.213,41.779,-82.439,41.675,-82.69,41.675,-82.866,41.753,-83.03,41.833,-83.142,41.976,-83.15,42.142,-83.11,42.251,-83.073,42.3,-83.004,42.332,-82.868,42.385,-82.744,42.493,-82.645,42.558,-82.545,42.625,-82.488,42.74,-82.417,43.017,-82.408,43.073,-82.305,43.263,-82.19,43.474,-82.138,43.571,-82.197,43.822,-82.241,44.015,-82.281,44.192,-82.327,44.392,-82.368,44.573,-82.407,44.744,-82.447,44.916,-82.485,45.084,-82.515,45.204,-82.551,45.347,-82.76,45.448,-82.919,45.518,-83.179,45.633,-83.397,45.729,-83.593,45.817,-83.469,45.995,-83.48,46.024,-83.525,46.059,-83.616,46.117,-83.669,46.123,-83.763,46.109,-83.913,46.073,-83.978,46.085,-84.029,46.147,-84.088,46.227,-84.108,46.289,-84.115,46.371,-84.15,46.445,-84.128,46.484,-84.123,46.503,-84.125,46.527,-84.149,46.543,-84.192,46.55,-84.337,46.519,-84.402,46.516,-84.44,46.498,-84.502,46.462,-84.562,46.457,-84.666,46.543,-84.779,46.637,-84.827,46.767,-84.876,46.9,-85.07,46.98,-85.264,47.06,-85.458,47.14,-85.652,47.22,-85.846,47.3,-86.04,47.38,-86.234,47.46,-86.429,47.54,-86.496,47.567,-86.672,47.636,-86.922,47.735,-87.208,47.848,-87.494,47.962,-87.744,48.061,-87.921,48.13,-87.987,48.157,-88.161,48.225,-88.378,48.303,-88.612,48.264,-88.899,48.156,-89.063,48.094,-89.186,48.047,-89.273,48.02,-89.456,47.996,-89.551,48,-89.775,48.015,-89.901,47.995,-89.994,48.015,-90.04,48.078,-90.092,48.118,-90.32,48.099,-90.607,48.113,-90.744,48.105,-90.797,48.131,-90.84,48.201,-90.916,48.209,-91.043,48.194,-91.221,48.105,-91.387,48.059,-91.518,48.058,-91.647,48.105,-91.858,48.198,-92.005,48.302,-92.172,48.338,-92.299,48.329,-92.348,48.277,-92.415,48.277,-92.461,48.366,-92.501,48.435,-92.583,48.465,-92.733,48.532,-92.837,48.568,-92.996,48.612,-93.052,48.62,-93.155,48.625,-93.258,48.629,-93.378,48.617,-93.464,48.561,-93.564,48.537,-93.708,48.525,-93.804,48.549,-93.852,48.607,-94.055,48.659,-94.414,48.704,-94.621,48.743,-94.675,48.774,-94.705,48.808,-94.713,48.863,-94.803,49.003,-94.843,49.119,-94.86,49.259,-94.854,49.305,-94.875,49.319,-94.939,49.349,-95.155,49.37,-95.158,49.203,-95.162,48.992,-95.398,48.993,-95.824,48.993,-96.251,48.993,-96.677,48.993,-97.103,48.993,-97.53,48.993,-97.956,48.993,-98.383,48.993,-98.809,48.993,-99.235,48.993,-99.662,48.993,-100.088,48.993,-100.515,48.993,-100.941,48.993,-101.367,48.993,-101.794,48.993,-102.22,48.993,-102.646,48.993,-103.073,48.993,-103.499,48.993,-103.926,48.993,-104.352,48.993,-104.778,48.993,-105.205,48.993,-105.631,48.993,-106.057,48.993,-106.484,48.993,-106.91,48.993,-107.337,48.993,-107.763,48.993,-108.189,48.993,-108.616,48.993,-109.042,48.993,-109.469,48.993,-109.895,48.993,-110.321,48.993,-110.748,48.993,-111.174,48.993,-111.6,48.993,-112.027,48.993,-112.453,48.993,-112.88,48.993,-113.306,48.993,-113.732,48.993,-114.159,48.993,-114.585,48.993,-115.012,48.993,-115.438,48.993,-115.864,48.993,-116.291,48.993,-116.717,48.993,-117.143,48.993,-117.57,48.993,-117.996,48.993,-118.423,48.993,-118.849,48.993,-119.275,48.993,-119.702,48.993,-120.128,48.993,-120.554,48.993,-120.981,48.993,-121.407,48.993,-121.834,48.993,-122.26,48.993,-122.686,48.993,-122.789,48.993,-122.827,49.028,-122.924,49.075,-122.963,49.075,-123.002,49.061,-123.027,49.039,-123.049,48.993,-123.063,48.978,-123.077,48.98,-123.086,48.993,-123.118,49.056,-123.109,49.085,-123.077,49.118,-123.08,49.131,-123.15,49.121,-123.182,49.129,-123.196,49.148,-123.191,49.22,-123.229,49.26,-123.184,49.278,-123.067,49.292,-122.948,49.293,-122.913,49.323,-122.879,49.399,-122.964,49.329,-123.016,49.322,-123.174,49.348,-123.277,49.344,-123.291,49.359,-123.286,49.375,-123.264,49.39,-123.248,49.443,-123.223,49.59,-123.191,49.644,-123.18,49.674,-123.187,49.68,-123.325,49.578,-123.337,49.545,-123.322,49.517,-123.336,49.459,-123.399,49.442,-123.437,49.451,-123.508,49.402,-123.531,49.397,-123.859,49.483,-123.892,49.495,-123.948,49.535,-124.029,49.603,-124.054,49.662,-124.024,49.711,-123.993,49.736,-123.96,49.736,-123.923,49.718,-123.847,49.637,-123.817,49.587,-123.739,49.594,-123.613,49.658,-123.582,49.681,-123.708,49.657,-123.763,49.658,-123.818,49.685,-123.874,49.737,-123.904,49.795,-123.904,49.981,-123.885,50.017,-123.824,50.044,-123.785,50.088,-123.788,50.107,-123.825,50.144,-123.88,50.174,-123.934,50.188,-123.946,50.184,-123.863,50.103,-123.866,50.072,-123.957,49.993,-123.971,49.97,-123.972,49.892,-123.985,49.876,-124.059,49.854,-124.142,49.793,-124.281,49.772,-124.413,49.778,-124.483,49.808,-124.702,49.958,-124.782,50.02,-124.784,50.073,-124.934,50.258,-124.933,50.298,-124.986,50.356,-125.044,50.364,-125.057,50.419,-124.937,50.537,-124.863,50.637,-124.854,50.669,-124.858,50.717,-124.875,50.826,-124.86,50.872,-124.934,50.811,-124.949,50.765,-124.931,50.718,-124.943,50.666,-124.985,50.592,-125.059,50.514,-125.21,50.476,-125.476,50.497,-125.507,50.507,-125.526,50.534,-125.539,50.649,-125.556,50.635,-125.586,50.574,-125.61,50.486,-125.641,50.466,-125.698,50.465,-125.741,50.479,-125.772,50.508,-125.84,50.511,-125.965,50.487,-126.024,50.497,-126.094,50.498,-126.237,50.523,-126.404,50.53,-126.45,50.55,-126.447,50.588,-126.416,50.607,-126.239,50.624,-126.067,50.664,-125.898,50.684,-125.904,50.705,-125.981,50.711,-126.37,50.667,-126.493,50.672,-126.514,50.679,-126.517,50.724,-126.472,50.767,-126.397,50.807,-126.375,50.837,-126.418,50.85,-126.488,50.842,-126.522,50.866,-126.485,50.96,-126.517,51.057,-126.563,50.965,-126.632,50.915,-126.96,50.894,-127.014,50.867,-127.058,50.868,-127.267,50.916,-127.357,50.946,-127.441,50.989,-127.591,51.088,-127.708,51.151,-127.714,51.269,-127.689,51.343,-127.633,51.427,-127.42,51.608,-127.347,51.642,-127.281,51.654,-126.968,51.67,-126.735,51.693,-126.691,51.703,-127.034,51.717,-127.339,51.707,-127.443,51.679,-127.576,51.563,-127.61,51.514,-127.645,51.478,-127.669,51.478,-127.714,51.49,-127.729,51.506,-127.748,51.544,-127.819,51.604,-127.851,51.673,-127.869,51.775,-127.863,51.821,-127.83,51.879,-127.728,51.993,-127.859,51.99,-127.843,52.086,-127.795,52.191,-127.673,52.253,-127.55,52.298,-127.438,52.356,-127.242,52.395,-127.176,52.315,-127.008,52.291,-126.959,52.255,-126.9,52.188,-126.826,52.125,-126.739,52.065,-126.714,52.061,-126.753,52.112,-126.895,52.225,-126.901,52.265,-126.938,52.309,-127.127,52.371,-127.161,52.395,-127.194,52.458,-127.208,52.498,-127.187,52.538,-126.995,52.658,-126.951,52.721,-126.951,52.751,-126.966,52.785,-127.008,52.843,-127.019,52.842,-127.006,52.755,-127.013,52.72,-127.035,52.682,-127.066,52.653,-127.107,52.633,-127.519,52.359,-127.56,52.343,-127.713,52.319,-127.792,52.289,-127.834,52.251,-127.902,52.151,-127.995,51.951,-128.102,51.788,-128.194,51.998,-128.358,52.159,-128.037,52.318,-128.029,52.342,-128.06,52.428,-128.052,52.453,-128.021,52.491,-127.94,52.545,-127.943,52.551,-128.038,52.531,-128.184,52.408,-128.241,52.368,-128.272,52.363,-128.275,52.435,-128.197,52.623,-128.132,52.806,-128.109,52.858,-128.053,52.911,-128.106,52.907,-128.365,52.826,-128.452,52.877,-128.525,53.141,-128.652,53.244,-128.869,53.328,-129.081,53.367,-129.13,53.442,-129.172,53.534,-129.114,53.641,-129.021,53.692,-128.936,53.715,-128.855,53.705,-128.85,53.665,-128.906,53.559,-128.833,53.549,-128.542,53.421,-128.479,53.41,-128.358,53.46,-128.291,53.458,-128.133,53.418,-128.079,53.369,-127.928,53.275,-127.95,53.33,-128.115,53.446,-128.207,53.483,-128.369,53.49,-128.47,53.471,-128.512,53.477,-128.6,53.506,-128.676,53.555,-128.751,53.661,-128.768,53.71,-128.764,53.747,-128.746,53.78,-128.715,53.81,-128.653,53.832,-128.56,53.845,-128.532,53.858,-128.651,53.919,-128.705,53.919,-128.89,53.83,-128.928,53.823,-128.944,53.84,-128.959,53.841,-129.014,53.797,-129.056,53.778,-129.208,53.642,-129.232,53.576,-129.24,53.479,-129.258,53.418,-129.284,53.393,-129.462,53.347,-129.564,53.251,-129.687,53.334,-129.822,53.413,-129.912,53.551,-130.074,53.576,-130.263,53.654,-130.335,53.724,-130.233,53.867,-130.086,53.976,-130.064,54.106,-130.043,54.134,-129.791,54.166,-129.626,54.23,-129.795,54.236,-129.898,54.226,-130.084,54.181,-130.29,54.27,-130.397,54.352,-130.43,54.421,-130.393,54.48,-130.389,54.539,-130.37,54.62,-130.35,54.655,-130.307,54.7,-130.219,54.73,-130.141,54.823,-130.109,54.887,-129.949,55.081,-129.89,55.165,-129.781,55.28,-129.561,55.463,-129.63,55.452,-129.667,55.437,-129.701,55.439,-129.734,55.458,-129.765,55.498,-129.795,55.56,-129.812,55.533,-129.816,55.418,-129.838,55.319,-129.877,55.251,-129.985,55.111,-130.048,55.057,-130.092,55.108,-130.058,55.195,-129.996,55.264,-129.985,55.359,-130.044,55.472,-130.08,55.563,-130.093,55.632,-130.095,55.695,-130.085,55.752,-130.06,55.814,-130.02,55.881,-130.025,55.888]],[[-109.816,78.65,-109.641,78.592,-109.581,78.593,-109.504,78.582,-109.467,78.567,-109.362,78.493,-109.342,78.456,-109.336,78.408,-109.352,78.369,-109.391,78.337,-109.484,78.316,-109.709,78.304,-110.022,78.323,-110.293,78.298,-110.418,78.295,-110.755,78.311,-110.84,78.322,-111.027,78.368,-111.169,78.386,-111.229,78.376,-111.3,78.337,-111.435,78.287,-111.517,78.275,-111.76,78.283,-112.131,78.366,-112.558,78.342,-113,78.293,-113.173,78.284,-113.223,78.298,-113.293,78.334,-113.282,78.353,-113.15,78.408,-112.856,78.467,-112.641,78.5,-112.214,78.548,-111.709,78.575,-111.52,78.603,-111.4,78.644,-111.071,78.708,-110.878,78.735,-110.618,78.758,-110.408,78.757,-110.14,78.704,-109.941,78.678,-109.816,78.65]],[[-110.458,78.103,-109.657,78.079,-109.622,78.075,-109.619,78.057,-109.679,77.999,-109.772,77.957,-110.2,77.905,-110.751,77.857,-110.866,77.834,-110.857,77.82,-110.812,77.803,-110.719,77.781,-110.292,77.786,-110.189,77.777,-110.153,77.763,-110.131,77.742,-110.118,77.716,-110.117,77.625,-110.139,77.572,-110.198,77.525,-110.372,77.491,-110.683,77.446,-110.894,77.426,-111.06,77.433,-111.226,77.429,-111.952,77.344,-112.177,77.344,-112.373,77.364,-112.644,77.444,-112.926,77.475,-113.046,77.511,-113.164,77.53,-113.197,77.559,-113.209,77.58,-113.189,77.6,-113.137,77.618,-113.121,77.633,-113.168,77.676,-113.19,77.718,-113.271,77.778,-113.283,77.813,-113.283,77.836,-113.269,77.86,-113.215,77.904,-113.187,77.912,-113.022,77.919,-112.805,77.942,-112.305,78.007,-111.207,78.088,-110.873,78.081,-110.727,78.097,-110.458,78.103]],[[-115.551,77.363,-115.476,77.324,-115.47,77.309,-115.507,77.292,-115.624,77.266,-116.214,77.178,-116.329,77.137,-116.286,77.102,-116.073,77.03,-115.857,76.969,-115.81,76.939,-115.913,76.908,-116.11,76.918,-116.183,76.916,-116.253,76.901,-116.234,76.874,-116.016,76.785,-115.945,76.736,-115.946,76.711,-115.985,76.687,-116.076,76.654,-116.22,76.611,-116.468,76.577,-116.999,76.532,-117.017,76.496,-117.013,76.469,-117.026,76.404,-117.044,76.373,-117.108,76.322,-117.154,76.298,-117.234,76.282,-117.347,76.273,-117.492,76.273,-117.732,76.317,-117.841,76.345,-117.993,76.406,-118.02,76.447,-118.005,76.497,-117.965,76.574,-117.9,76.653,-117.808,76.734,-117.78,76.784,-117.818,76.804,-117.881,76.805,-118.076,76.772,-118.203,76.76,-118.301,76.737,-118.37,76.701,-118.409,76.662,-118.431,76.588,-118.468,76.547,-118.574,76.525,-118.732,76.526,-118.791,76.513,-118.821,76.486,-118.8,76.464,-118.644,76.418,-118.625,76.366,-118.643,76.335,-118.812,76.277,-118.851,76.258,-118.955,76.168,-118.994,76.145,-119.081,76.124,-119.168,76.127,-119.249,76.159,-119.368,76.222,-119.448,76.275,-119.489,76.32,-119.524,76.34,-119.58,76.327,-119.649,76.28,-119.651,76.244,-119.636,76.19,-119.64,76.157,-119.74,76.118,-119.725,76.1,-119.55,76.052,-119.527,76.031,-119.526,75.997,-119.538,75.982,-119.608,75.985,-119.667,75.946,-119.735,75.915,-119.913,75.859,-120.161,75.852,-120.365,75.825,-120.409,75.826,-120.458,75.87,-120.514,75.958,-120.563,76.008,-120.637,76.034,-120.729,76.134,-120.772,76.166,-120.813,76.179,-120.848,76.183,-120.9,76.163,-121.019,76.02,-121.213,75.984,-121.32,75.977,-121.428,75.981,-121.695,76.02,-121.908,76.035,-122.057,76.018,-122.303,75.96,-122.4,75.944,-122.533,75.951,-122.591,75.973,-122.64,76.009,-122.646,76.031,-122.607,76.039,-122.546,76.081,-122.548,76.097,-122.609,76.121,-122.609,76.14,-122.588,76.153,-122.593,76.162,-122.624,76.167,-122.685,76.162,-122.903,76.135,-122.878,76.165,-122.774,76.228,-122.519,76.353,-122.423,76.39,-122.365,76.401,-121.614,76.441,-121.561,76.453,-121.204,76.622,-121.102,76.661,-120.998,76.691,-120.485,76.793,-120.438,76.816,-120.358,76.887,-120.311,76.905,-120.2,76.931,-119.831,77.074,-119.495,77.177,-119.324,77.241,-119.09,77.305,-118.82,77.333,-118.005,77.381,-117.419,77.317,-117.279,77.313,-117.211,77.331,-117.149,77.361,-117.061,77.348,-116.844,77.34,-116.795,77.347,-116.704,77.38,-116.766,77.398,-117.03,77.432,-117.046,77.449,-117.04,77.465,-116.947,77.504,-116.835,77.529,-116.511,77.548,-116.363,77.543,-116.209,77.516,-116.008,77.461,-115.551,77.363]],[[-108.292,76.057,-108.166,76.054,-108.019,76.065,-107.852,76.058,-107.777,76.035,-107.723,75.995,-107.721,75.974,-107.732,75.956,-107.755,75.94,-107.97,75.84,-108.021,75.805,-107.951,75.796,-107.918,75.802,-107.703,75.878,-107.541,75.901,-107.418,75.907,-107.216,75.892,-107.136,75.879,-107.08,75.863,-107.05,75.845,-106.971,75.773,-106.914,75.68,-106.904,75.689,-106.903,75.742,-106.892,75.782,-106.693,75.81,-106.688,75.819,-106.759,75.842,-106.82,75.872,-106.862,75.93,-106.846,75.952,-106.804,75.975,-106.677,76.024,-106.529,76.053,-106.397,76.06,-105.905,76.009,-105.711,75.967,-105.633,75.945,-105.604,75.93,-105.563,75.881,-105.481,75.746,-105.481,75.702,-105.519,75.632,-105.678,75.501,-105.703,75.413,-105.863,75.192,-105.972,75.131,-106.093,75.089,-106.588,75.015,-106.961,74.94,-107.056,74.928,-107.153,74.927,-107.462,74.952,-107.82,75,-108.024,74.986,-108.227,74.952,-108.354,74.943,-108.475,74.947,-108.594,74.96,-108.751,74.992,-108.67,75.007,-108.633,75.023,-108.666,75.04,-108.831,75.065,-109.003,75.01,-109.503,74.883,-110.176,74.84,-110.387,74.814,-110.543,74.78,-110.625,74.753,-110.749,74.688,-110.941,74.639,-111.288,74.585,-111.729,74.502,-112.519,74.417,-113.017,74.402,-113.514,74.43,-113.672,74.453,-113.837,74.489,-114.175,74.574,-114.268,74.604,-114.377,74.671,-114.313,74.715,-114.132,74.766,-113.863,74.813,-113.324,74.875,-112.836,74.976,-112.663,74.994,-112.193,75.01,-111.956,75,-111.784,75.006,-111.671,75.019,-111.503,75.056,-111.258,75.128,-111.079,75.195,-111.033,75.227,-111.093,75.256,-111.181,75.26,-111.474,75.191,-111.621,75.168,-111.781,75.166,-112,75.142,-112.214,75.133,-112.256,75.134,-112.478,75.2,-112.597,75.212,-112.652,75.205,-112.703,75.187,-112.8,75.138,-112.855,75.121,-112.951,75.108,-113.34,75.093,-113.712,75.069,-113.795,75.084,-113.845,75.112,-113.855,75.129,-113.861,75.188,-113.886,75.211,-113.853,75.259,-113.811,75.296,-113.759,75.322,-113.503,75.397,-113.467,75.416,-113.589,75.412,-113.879,75.375,-113.916,75.388,-113.984,75.43,-114.017,75.434,-114.053,75.417,-114.075,75.392,-114.125,75.291,-114.168,75.24,-114.285,75.25,-114.429,75.281,-114.483,75.285,-114.514,75.275,-114.504,75.258,-114.358,75.171,-114.356,75.141,-114.452,75.088,-114.859,75,-115.02,74.976,-115.077,74.985,-115.128,75.009,-115.174,75.049,-115.28,75.102,-115.343,75.113,-115.413,75.115,-115.478,75.104,-115.537,75.081,-115.574,75.056,-115.609,75.01,-115.683,74.974,-115.729,74.968,-116.143,75.042,-116.476,75.172,-116.841,75.152,-117.005,75.156,-117.502,75.204,-117.565,75.233,-117.6,75.272,-117.597,75.293,-117.576,75.314,-117.513,75.357,-117.388,75.421,-117.336,75.442,-117.258,75.46,-117.154,75.473,-116.891,75.481,-116.213,75.483,-116.077,75.493,-115.335,75.618,-115.251,75.639,-115.142,75.679,-115.117,75.695,-115.122,75.706,-116.034,75.607,-116.426,75.585,-117.025,75.602,-117.138,75.617,-117.164,75.645,-117.039,75.718,-116.973,75.746,-116.802,75.772,-116.39,75.808,-115.838,75.841,-115.477,75.841,-115.174,75.867,-114.992,75.896,-115.602,75.895,-116.338,75.881,-116.444,75.891,-116.654,75.929,-116.665,75.958,-116.58,75.992,-116.55,76.017,-116.61,76.074,-116.591,76.096,-116.454,76.143,-116.21,76.194,-116.059,76.202,-115.768,76.184,-114.939,76.166,-114.779,76.173,-114.88,76.195,-115.025,76.211,-115.664,76.24,-115.797,76.253,-115.822,76.27,-115.832,76.296,-115.826,76.33,-115.779,76.365,-115.581,76.438,-114.998,76.497,-114.767,76.506,-114.535,76.502,-114.299,76.475,-114.194,76.451,-114.141,76.423,-114.116,76.396,-114.113,76.349,-114.101,76.331,-114.059,76.301,-113.923,76.229,-113.823,76.207,-113.363,76.248,-113.171,76.258,-112.978,76.245,-112.698,76.202,-112.334,76.072,-111.865,75.939,-111.868,75.911,-112.047,75.866,-112.081,75.847,-112.057,75.834,-111.877,75.826,-111.709,75.832,-111.549,75.822,-111.513,75.811,-111.454,75.762,-111.373,75.676,-111.276,75.613,-111.163,75.57,-111.053,75.549,-110.89,75.547,-110.726,75.56,-110.459,75.555,-109.086,75.506,-109.005,75.515,-108.947,75.542,-108.913,75.587,-108.9,75.624,-108.918,75.675,-108.945,75.699,-109.796,75.863,-109.871,75.929,-109.455,76.021,-109.425,76.043,-109.417,76.072,-109.43,76.109,-109.487,76.145,-109.71,76.212,-109.908,76.223,-110.201,76.289,-110.247,76.306,-110.285,76.333,-110.314,76.369,-110.309,76.397,-110.27,76.417,-109.982,76.485,-109.865,76.522,-109.505,76.692,-109.339,76.76,-109.22,76.792,-109.098,76.812,-108.832,76.821,-108.554,76.758,-108.492,76.754,-108.467,76.738,-108.478,76.708,-108.512,76.68,-108.612,76.63,-108.635,76.609,-108.628,76.587,-108.56,76.536,-108.539,76.503,-108.524,76.447,-108.512,76.439,-108.345,76.392,-108.194,76.33,-108.123,76.233,-108.178,76.2,-108.306,76.154,-108.382,76.116,-108.406,76.085,-108.387,76.067,-108.292,76.057]],[[-114.522,72.593,-114.458,72.58,-114.342,72.591,-114.174,72.624,-113.958,72.651,-113.692,72.673,-113.622,72.647,-113.578,72.652,-113.5,72.694,-113.486,72.722,-113.496,72.754,-113.491,72.822,-113.45,72.863,-113.292,72.95,-113.208,72.981,-113.074,72.995,-112.754,72.986,-112.454,72.937,-112.048,72.888,-111.455,72.766,-111.27,72.714,-111.25,72.669,-111.356,72.572,-111.611,72.436,-111.816,72.386,-111.895,72.356,-111.762,72.335,-111.675,72.3,-111.544,72.351,-111.447,72.408,-111.311,72.455,-111.265,72.459,-111.253,72.449,-111.277,72.425,-111.287,72.401,-111.284,72.378,-111.268,72.364,-111.185,72.357,-111.14,72.365,-110.959,72.432,-110.782,72.534,-110.513,72.6,-110.439,72.633,-110.205,72.661,-110.208,72.681,-110.197,72.759,-110.279,72.792,-110.554,72.861,-110.689,72.945,-110.661,73.008,-110.509,72.999,-110.095,72.992,-110.008,72.984,-109.61,72.876,-109.469,72.808,-109.357,72.775,-109.122,72.726,-109.043,72.687,-108.987,72.671,-108.968,72.654,-108.985,72.637,-108.994,72.596,-108.951,72.583,-108.798,72.568,-108.755,72.551,-108.698,72.499,-108.628,72.412,-108.566,72.317,-108.47,72.139,-108.276,71.9,-108.21,71.751,-108.188,71.724,-108.145,71.705,-108.021,71.677,-107.925,71.639,-107.813,71.626,-107.785,71.63,-107.757,71.663,-107.687,71.716,-107.347,71.819,-107.329,71.835,-107.369,71.859,-107.382,71.875,-107.377,71.886,-107.306,71.895,-107.543,72.025,-107.696,72.149,-107.794,72.303,-107.809,72.347,-107.824,72.443,-107.856,72.468,-107.91,72.491,-107.933,72.52,-107.924,72.557,-107.934,72.588,-107.997,72.653,-108.238,73.106,-108.237,73.15,-108.204,73.183,-108.118,73.202,-107.98,73.207,-107.936,73.217,-107.987,73.233,-108.077,73.281,-108.089,73.304,-108.029,73.349,-107.72,73.329,-107.496,73.288,-107.113,73.192,-107.074,73.197,-107.033,73.245,-106.951,73.276,-106.828,73.266,-106.482,73.196,-106.082,73.072,-105.813,73.011,-105.624,72.927,-105.496,72.849,-105.415,72.788,-105.412,72.765,-105.43,72.74,-105.411,72.709,-105.355,72.67,-105.323,72.635,-105.298,72.56,-105.247,72.464,-105.234,72.415,-104.878,71.98,-104.81,71.903,-104.767,71.868,-104.518,71.699,-104.386,71.577,-104.373,71.495,-104.355,71.472,-104.35,71.434,-104.356,71.382,-104.385,71.338,-104.437,71.3,-104.487,71.248,-104.563,71.132,-104.57,71.104,-104.515,71.064,-104.167,70.927,-103.953,70.763,-103.853,70.734,-103.585,70.631,-103.295,70.572,-103.197,70.547,-103.105,70.51,-103.077,70.509,-103.021,70.516,-103.005,70.526,-103.001,70.541,-103.083,70.619,-103.089,70.65,-103.05,70.655,-102.75,70.522,-102.589,70.469,-102.369,70.413,-101.99,70.285,-101.937,70.275,-101.732,70.286,-101.676,70.278,-101.641,70.266,-101.627,70.248,-101.618,70.172,-101.562,70.135,-101.239,70.151,-101.149,70.148,-101.091,70.136,-101.043,70.111,-100.973,70.029,-100.909,69.869,-100.906,69.812,-100.935,69.715,-100.982,69.68,-101.044,69.669,-101.216,69.68,-101.337,69.71,-101.4,69.749,-101.457,69.834,-101.484,69.85,-101.508,69.833,-101.565,69.756,-101.602,69.721,-101.648,69.699,-101.734,69.704,-101.86,69.738,-102.098,69.825,-102.182,69.846,-102.234,69.842,-102.348,69.813,-102.523,69.758,-102.596,69.718,-102.565,69.692,-102.545,69.66,-102.535,69.621,-102.541,69.592,-102.563,69.574,-102.621,69.552,-102.744,69.548,-102.92,69.565,-103.059,69.595,-103.303,69.674,-103.359,69.685,-103.435,69.668,-103.465,69.644,-103.418,69.611,-103.294,69.568,-103.142,69.497,-103.102,69.483,-103.063,69.485,-103.049,69.472,-103.032,69.433,-103.04,69.368,-103.113,69.236,-103.12,69.205,-103.09,69.212,-102.884,69.341,-102.777,69.378,-102.546,69.434,-102.447,69.476,-102.151,69.488,-102.046,69.465,-101.978,69.425,-101.976,69.407,-102.053,69.36,-102.067,69.337,-102.071,69.308,-102.064,69.281,-102.046,69.258,-101.993,69.236,-101.899,69.246,-101.873,69.24,-101.823,69.217,-101.789,69.182,-101.788,69.132,-101.857,69.024,-101.981,68.989,-102.359,68.923,-102.488,68.889,-102.738,68.865,-102.835,68.833,-102.895,68.824,-103.162,68.829,-103.468,68.809,-103.82,68.848,-104.067,68.866,-104.353,68.928,-104.46,68.912,-104.571,68.872,-105.106,68.92,-105.169,68.955,-105.148,68.978,-105.022,69.052,-105.014,69.068,-105.02,69.081,-105.262,69.094,-105.533,69.134,-105.805,69.153,-106.008,69.148,-106.141,69.162,-106.27,69.195,-106.341,69.224,-106.354,69.251,-106.356,69.281,-106.344,69.34,-106.361,69.381,-106.42,69.414,-106.54,69.443,-106.659,69.44,-106.76,69.407,-106.856,69.347,-107.033,69.181,-107.123,69.152,-107.353,69.032,-107.44,69.002,-107.863,68.954,-108.365,68.935,-108.553,68.897,-108.73,68.827,-108.946,68.76,-109.472,68.677,-109.959,68.63,-110.468,68.61,-110.848,68.578,-110.957,68.594,-111.128,68.588,-111.311,68.542,-111.518,68.533,-112.305,68.516,-112.666,68.485,-112.864,68.477,-113.02,68.481,-113.128,68.494,-113.231,68.535,-113.338,68.599,-113.555,68.768,-113.617,68.838,-113.593,68.96,-113.609,69.03,-113.681,69.182,-113.694,69.195,-114.073,69.251,-114.323,69.269,-114.699,69.273,-115.159,69.265,-115.618,69.283,-115.861,69.304,-116.102,69.337,-116.513,69.425,-116.537,69.434,-116.569,69.463,-116.609,69.512,-116.712,69.576,-116.993,69.719,-117.104,69.804,-117.122,69.826,-117.149,69.888,-117.184,69.991,-117.195,70.054,-117.163,70.092,-117.135,70.1,-116.554,70.175,-115.529,70.257,-114.592,70.312,-114.167,70.307,-113.917,70.282,-113.666,70.27,-113.211,70.264,-112.638,70.225,-112.523,70.229,-112.266,70.255,-112.19,70.276,-111.784,70.273,-111.705,70.286,-111.633,70.309,-111.726,70.352,-112.114,70.447,-113.146,70.616,-113.397,70.652,-113.757,70.691,-113.966,70.696,-114.232,70.674,-114.331,70.675,-114.593,70.642,-114.841,70.621,-115.311,70.601,-115.991,70.586,-116.086,70.591,-116.226,70.616,-116.327,70.624,-116.993,70.604,-117.587,70.63,-118.264,70.888,-118.377,70.968,-118.353,71,-118.269,71.035,-117.934,71.135,-117.814,71.158,-117.314,71.212,-116.815,71.277,-116.422,71.338,-116.228,71.359,-116.042,71.362,-115.892,71.382,-115.922,71.401,-116.045,71.423,-116.044,71.454,-115.98,71.469,-115.734,71.485,-115.472,71.466,-115.341,71.472,-115.303,71.494,-115.338,71.511,-115.587,71.546,-116.78,71.444,-117.337,71.435,-117.723,71.391,-117.936,71.392,-118.188,71.436,-118.222,71.449,-118.226,71.467,-118.148,71.526,-117.878,71.561,-117.742,71.659,-117.888,71.661,-118.372,71.64,-118.583,71.649,-118.868,71.687,-118.952,71.732,-118.988,71.764,-118.994,71.803,-118.984,71.913,-118.96,71.972,-118.945,71.986,-118.59,72.167,-118.369,72.205,-118.213,72.263,-118.207,72.287,-118.246,72.311,-118.39,72.37,-118.449,72.399,-118.481,72.428,-118.457,72.473,-118.375,72.534,-118.133,72.633,-117.552,72.831,-117.256,72.914,-116.972,72.959,-116.573,73.055,-115.552,73.213,-114.638,73.373,-114.302,73.331,-114.206,73.298,-114.164,73.27,-114.127,73.231,-114.095,73.18,-114.052,73.071,-114.046,73.015,-114.054,72.958,-114.075,72.907,-114.109,72.861,-114.178,72.805,-114.28,72.739,-114.498,72.626,-114.522,72.593]],[[-119.736,74.113,-119.729,74.108,-119.471,74.201,-119.315,74.206,-119.206,74.198,-119.171,74.186,-119.15,74.168,-119.139,74.128,-119.132,74.028,-119.118,74.016,-119.083,74.021,-119.026,74.045,-118.744,74.192,-118.625,74.233,-118.544,74.245,-118.2,74.267,-117.966,74.266,-117.707,74.252,-117.515,74.232,-117.199,74.171,-116.95,74.101,-116.722,74.027,-115.958,73.748,-115.634,73.666,-115.511,73.619,-115.456,73.585,-115.408,73.542,-115.393,73.502,-115.412,73.465,-115.447,73.439,-115.524,73.417,-115.992,73.323,-116.239,73.295,-116.483,73.253,-117.065,73.107,-117.464,73.038,-117.983,72.902,-118.962,72.684,-119.078,72.64,-119.132,72.609,-119.408,72.36,-119.513,72.303,-119.767,72.244,-120.09,72.229,-120.18,72.213,-120.194,72.127,-120.31,71.984,-120.366,71.888,-120.443,71.631,-120.461,71.605,-120.52,71.557,-120.619,71.506,-120.93,71.446,-121.16,71.415,-121.472,71.389,-121.547,71.407,-121.622,71.448,-121.701,71.451,-121.749,71.445,-122.157,71.266,-122.55,71.194,-122.72,71.128,-122.84,71.097,-122.937,71.088,-123.096,71.094,-123.211,71.123,-123.315,71.169,-123.393,71.219,-123.595,71.423,-123.682,71.493,-123.756,71.528,-123.953,71.652,-124.008,71.677,-124.76,71.835,-125.126,71.924,-125.215,71.955,-125.297,71.973,-125.767,71.961,-125.829,71.966,-125.845,71.979,-125.79,72.025,-125.768,72.054,-125.76,72.083,-125.769,72.129,-125.763,72.138,-125.584,72.183,-125.613,72.193,-125.634,72.21,-125.647,72.237,-125.627,72.255,-125.575,72.265,-125.512,72.308,-125.438,72.382,-125.383,72.424,-125.306,72.451,-125.168,72.523,-125.07,72.552,-124.987,72.588,-124.985,72.604,-125.019,72.617,-125.03,72.645,-125.015,72.731,-125.015,72.776,-125,72.813,-124.97,72.843,-124.931,72.863,-124.583,72.926,-124.565,72.944,-124.561,72.965,-124.57,72.989,-124.588,73.005,-124.643,73.019,-124.736,73.023,-124.817,73.059,-124.836,73.076,-124.804,73.126,-124.647,73.204,-124.594,73.243,-124.424,73.419,-124.114,73.527,-124.03,73.644,-123.797,73.768,-123.798,73.785,-123.873,73.828,-124.088,73.857,-124.192,73.902,-124.261,73.953,-124.575,74.248,-124.629,74.27,-124.645,74.304,-124.709,74.327,-124.696,74.348,-123.468,74.436,-122.623,74.464,-121.748,74.541,-121.504,74.545,-121.315,74.53,-121.129,74.49,-120.882,74.421,-120.554,74.353,-119.944,74.254,-119.563,74.233,-119.715,74.154,-119.737,74.13,-119.736,74.113]],[[-69.489,83.017,-68.673,82.999,-68.409,83.005,-68.107,82.961,-67.925,82.956,-67.624,82.964,-67.406,82.954,-66.592,82.944,-66.423,82.927,-66.425,82.906,-66.6,82.861,-66.836,82.818,-68.358,82.677,-68.469,82.653,-68.173,82.646,-67.736,82.652,-67.397,82.668,-66.998,82.716,-66.866,82.719,-66.612,82.742,-66.12,82.807,-65.727,82.842,-65.55,82.827,-65.4,82.802,-65.299,82.8,-65.247,82.819,-65.162,82.87,-65.113,82.889,-64.984,82.902,-64.905,82.901,-64.777,82.876,-64.635,82.819,-64.504,82.778,-64.433,82.778,-64.134,82.823,-63.984,82.829,-63.641,82.813,-63.499,82.793,-63.473,82.771,-63.564,82.749,-63.621,82.729,-63.643,82.713,-63.593,82.694,-63.385,82.653,-63.085,82.565,-63.087,82.533,-63.251,82.467,-63.247,82.45,-62.475,82.52,-61.697,82.489,-61.477,82.467,-61.392,82.442,-61.302,82.4,-61.207,82.341,-61.274,82.28,-61.615,82.184,-61.969,82.11,-62.177,82.043,-62.496,82.007,-63.592,81.846,-64.128,81.794,-64.436,81.743,-64.574,81.734,-65.226,81.744,-65.399,81.715,-65.495,81.668,-65.701,81.646,-66.005,81.629,-66.626,81.616,-66.765,81.563,-66.801,81.527,-66.861,81.499,-66.914,81.485,-68.689,81.293,-68.721,81.261,-68.543,81.248,-68.318,81.261,-65.736,81.494,-65.24,81.51,-64.78,81.493,-64.833,81.439,-65.484,81.285,-66.313,81.146,-66.727,81.041,-67.774,80.859,-68.63,80.679,-68.959,80.587,-69.4,80.423,-69.551,80.383,-69.734,80.367,-69.949,80.374,-70.144,80.398,-70.403,80.459,-70.639,80.528,-70.713,80.54,-70.668,80.506,-70.213,80.278,-70.265,80.234,-71.1,80.187,-71.47,80.146,-71.661,80.136,-71.796,80.143,-71.928,80.139,-72.056,80.123,-72.063,80.106,-71.949,80.086,-71.616,80.071,-70.877,80.122,-70.758,80.119,-70.568,80.094,-70.559,80.071,-70.758,79.998,-71.356,79.911,-71.278,79.906,-71.106,79.876,-71.11,79.848,-71.299,79.783,-71.388,79.762,-71.965,79.701,-72.216,79.687,-72.437,79.694,-73.448,79.827,-73.805,79.846,-74.144,79.88,-74.394,79.874,-74.66,79.835,-74.541,79.816,-74.051,79.778,-73.642,79.771,-73.472,79.756,-73.406,79.732,-73.229,79.644,-73.201,79.597,-73.24,79.552,-73.294,79.522,-73.362,79.504,-73.466,79.495,-73.866,79.501,-74.015,79.491,-74.189,79.465,-74.406,79.454,-74.798,79.459,-75.259,79.421,-75.503,79.414,-75.774,79.431,-76.067,79.473,-76.376,79.494,-76.899,79.512,-76.855,79.488,-76.671,79.478,-76.296,79.414,-76.116,79.326,-75.948,79.311,-75.603,79.24,-75.354,79.228,-75.094,79.204,-74.727,79.235,-74.481,79.229,-74.532,79.053,-74.641,79.036,-75.233,79.036,-75.515,79.061,-75.639,79.088,-75.912,79.118,-76.158,79.1,-76.38,79.104,-76.531,79.087,-76.771,79.087,-77.398,79.057,-77.729,79.057,-77.974,79.076,-78.258,79.082,-78.582,79.075,-78.559,79.055,-78.422,79.048,-78.222,79.015,-78.037,78.964,-77.883,78.942,-77.698,78.955,-77.51,78.978,-76.825,79.018,-76.524,79.024,-76.256,79.007,-76.077,78.985,-75.953,78.959,-75.795,78.89,-75.4,78.881,-75.099,78.858,-74.618,78.758,-74.486,78.75,-74.433,78.724,-74.535,78.659,-74.547,78.62,-74.879,78.545,-75.397,78.523,-75.966,78.53,-76.373,78.521,-76.416,78.512,-76.137,78.492,-75.488,78.404,-75.237,78.356,-75.193,78.328,-75.551,78.221,-75.866,78.01,-75.97,77.993,-76.078,77.987,-76.356,77.991,-76.708,77.938,-76.974,77.927,-77.456,77.947,-78.013,77.946,-78.056,77.912,-78.084,77.846,-78.081,77.747,-78.047,77.615,-78.076,77.519,-78.168,77.458,-78.284,77.413,-78.493,77.369,-78.708,77.342,-78.87,77.333,-79.138,77.331,-79.906,77.3,-80.282,77.301,-80.573,77.315,-80.875,77.359,-81.377,77.482,-81.519,77.51,-81.659,77.525,-81.654,77.499,-81.504,77.43,-81.378,77.385,-81.278,77.365,-81.301,77.344,-81.523,77.311,-81.767,77.296,-82.057,77.297,-82.066,77.284,-81.968,77.248,-81.84,77.214,-81.756,77.204,-81.534,77.214,-81.277,77.257,-81.117,77.27,-80.798,77.259,-80.673,77.244,-80.274,77.151,-80.219,77.147,-79.924,77.194,-79.497,77.196,-79.341,77.158,-79.281,77.085,-79.274,77.026,-79.319,76.98,-79.221,76.936,-78.979,76.893,-78.792,76.884,-78.659,76.908,-78.456,76.967,-78.37,76.981,-78.289,76.978,-78.165,76.935,-77.999,76.852,-77.983,76.755,-78.119,76.644,-78.284,76.571,-78.934,76.451,-79.131,76.404,-79.286,76.355,-79.511,76.31,-79.954,76.251,-80.187,76.24,-80.69,76.176,-80.8,76.174,-80.963,76.184,-80.997,76.215,-80.955,76.27,-80.901,76.322,-80.835,76.369,-80.832,76.409,-80.975,76.47,-81.074,76.498,-81.171,76.513,-81.365,76.504,-81.474,76.488,-81.592,76.484,-81.717,76.495,-81.823,76.521,-82.034,76.629,-82.114,76.643,-82.218,76.64,-82.311,76.655,-82.393,76.69,-82.53,76.723,-82.493,76.698,-82.357,76.636,-82.262,76.575,-82.208,76.514,-82.233,76.466,-83.389,76.439,-83.886,76.453,-83.986,76.495,-84.224,76.675,-84.275,76.357,-85.141,76.305,-85.344,76.313,-85.681,76.349,-86.116,76.435,-86.296,76.492,-86.367,76.549,-86.419,76.58,-86.454,76.585,-86.562,76.517,-86.68,76.377,-86.978,76.413,-87.354,76.448,-87.49,76.586,-87.498,76.386,-88.104,76.413,-88.396,76.405,-88.482,76.58,-88.496,76.773,-88.614,76.651,-88.563,76.547,-88.546,76.421,-88.804,76.457,-89.37,76.474,-89.57,76.492,-89.544,76.66,-89.5,76.827,-88.771,76.993,-88.556,77.072,-88.398,77.104,-88.148,77.124,-87.828,77.136,-87.61,77.127,-87.362,77.136,-87.064,77.166,-86.852,77.174,-86.812,77.185,-86.874,77.2,-87.101,77.308,-87.182,77.332,-87.265,77.343,-87.43,77.348,-87.589,77.395,-87.681,77.436,-87.78,77.493,-87.938,77.6,-88.095,77.719,-88.017,77.785,-87.757,77.836,-87.497,77.872,-87.236,77.892,-87.018,77.892,-86.755,77.864,-86.385,77.809,-86.173,77.746,-85.907,77.614,-85.731,77.509,-85.588,77.461,-84.951,77.375,-84.739,77.361,-84.487,77.368,-83.974,77.391,-83.721,77.414,-83.608,77.442,-83.55,77.483,-83.477,77.514,-83.25,77.585,-82.903,77.733,-82.71,77.85,-82.665,77.889,-82.626,77.936,-82.595,77.992,-82.704,77.962,-83.304,77.674,-83.428,77.621,-83.779,77.533,-83.928,77.518,-84.168,77.523,-84.486,77.562,-84.861,77.5,-85.088,77.515,-85.289,77.559,-85.292,77.764,-85.548,77.928,-85.265,78.011,-85.031,78.062,-84.615,78.196,-84.524,78.197,-84.223,78.176,-84.388,78.206,-84.55,78.251,-84.91,78.24,-84.783,78.528,-85.024,78.312,-85.27,78.2,-85.419,78.142,-85.586,78.11,-86.218,78.081,-86.063,78.187,-85.92,78.343,-86.071,78.285,-86.427,78.197,-86.694,78.151,-86.913,78.127,-87.339,78.133,-87.552,78.177,-87.491,78.284,-87.491,78.417,-87.361,78.479,-87.164,78.558,-86.953,78.664,-86.808,78.774,-86.242,78.824,-85.691,78.844,-85.23,78.902,-85.004,78.912,-84.787,78.885,-83.908,78.839,-83.547,78.804,-83.389,78.779,-83.271,78.77,-83.147,78.808,-82.99,78.844,-82.442,78.84,-82.291,78.847,-82.151,78.864,-81.981,78.898,-81.781,78.95,-81.75,78.976,-81.889,78.975,-82.028,78.962,-82.237,78.924,-82.439,78.904,-82.644,78.908,-83.059,78.94,-83.779,78.945,-84.146,78.96,-84.316,78.975,-84.412,78.997,-84.496,79.029,-84.568,79.071,-84.53,79.101,-84.384,79.119,-84.257,79.122,-84.053,79.099,-83.825,79.059,-83.576,79.054,-83.662,79.09,-83.978,79.163,-84.197,79.225,-84.381,79.301,-84.522,79.377,-84.836,79.495,-85.09,79.612,-85.269,79.664,-85.457,79.69,-86.031,79.722,-86.147,79.743,-86.421,79.845,-86.494,80.018,-86.615,80.124,-86.499,80.258,-86.307,80.319,-85.16,80.272,-84.675,80.279,-84.057,80.262,-83.724,80.229,-83.344,80.147,-83.004,80.055,-82.677,79.993,-82.377,79.908,-82.049,79.783,-81.856,79.723,-81.688,79.686,-81.463,79.654,-81.038,79.614,-80.668,79.601,-80.476,79.606,-80.271,79.635,-80.124,79.669,-80.287,79.679,-80.714,79.675,-81.01,79.693,-81.179,79.733,-81.359,79.788,-81.644,79.89,-81.86,79.957,-82.332,80.066,-82.681,80.175,-82.961,80.278,-82.987,80.323,-82.785,80.354,-82.536,80.376,-80.98,80.445,-80.051,80.529,-79.674,80.625,-79.629,80.648,-78.386,80.784,-77.507,80.835,-77.169,80.843,-76.863,80.865,-76.85,80.878,-77.119,80.896,-77.389,80.905,-78.004,80.905,-78.551,80.921,-78.716,80.952,-78.682,81.001,-78.629,81.043,-78.464,81.114,-78.287,81.168,-77.536,81.321,-77.031,81.386,-76.885,81.43,-77.972,81.331,-78.352,81.259,-78.734,81.151,-78.932,81.119,-79.072,81.128,-79.198,81.118,-79.309,81.089,-79.402,81.037,-79.477,80.961,-79.545,80.909,-79.607,80.882,-79.761,80.842,-80.134,80.764,-81.007,80.655,-81.301,80.627,-81.553,80.623,-82.368,80.561,-82.613,80.559,-82.884,80.578,-82.768,80.631,-82.337,80.729,-82.222,80.772,-82.498,80.763,-82.78,80.736,-83.401,80.714,-83.647,80.674,-83.885,80.602,-84.076,80.556,-84.22,80.538,-84.418,80.527,-85.146,80.521,-85.307,80.526,-85.726,80.581,-86.097,80.562,-86.25,80.566,-86.532,80.605,-86.615,80.63,-86.603,80.664,-86.44,80.728,-86.252,80.79,-85.639,80.925,-85.246,80.988,-84.68,81.042,-83.349,81.103,-83.289,81.148,-84.635,81.098,-85.781,81.035,-85.967,81.012,-86.233,80.95,-87.08,80.726,-87.33,80.67,-87.712,80.656,-88.004,80.675,-88.232,80.704,-88.625,80.77,-88.921,80.806,-89.062,80.83,-89.145,80.854,-89.212,80.882,-89.263,80.914,-89.167,80.941,-88.413,81,-87.389,80.988,-86.929,81,-86.477,81.036,-85.81,81.124,-85.083,81.247,-84.941,81.286,-85.206,81.295,-85.402,81.285,-85.875,81.241,-86.623,81.123,-87.275,81.081,-88.887,81.058,-89.398,81.025,-89.623,81.032,-89.792,81.065,-89.981,81.125,-89.947,81.173,-89.563,81.226,-89.263,81.239,-89.209,81.25,-89.636,81.302,-89.674,81.329,-89.427,81.387,-88.892,81.474,-88.622,81.501,-88.127,81.519,-87.617,81.509,-87.597,81.526,-88.101,81.559,-88.479,81.565,-88.978,81.542,-90.304,81.401,-90.416,81.405,-90.609,81.43,-90.554,81.464,-89.845,81.612,-89.822,81.635,-90.331,81.632,-90.48,81.639,-90.626,81.656,-90.834,81.64,-91.103,81.592,-91.292,81.571,-91.403,81.578,-91.684,81.636,-91.648,81.684,-91.424,81.744,-91.219,81.788,-90.942,81.827,-90.49,81.877,-90.163,81.894,-89.633,81.895,-89.381,81.917,-89.156,81.955,-88.875,82.018,-88.567,82.061,-88.063,82.096,-87.639,82.085,-87.404,82.054,-87.218,82,-87.018,81.959,-86.999,81.992,-86.834,82.033,-86.627,82.051,-86.378,82.045,-86.158,82.026,-85.875,81.976,-85.646,81.953,-85.538,81.955,-85.403,81.982,-85.045,81.983,-85.052,81.995,-85.169,82.023,-85.311,82.044,-86.581,82.187,-86.616,82.219,-86.188,82.248,-85.92,82.283,-85.794,82.292,-85.481,82.366,-85.276,82.405,-84.897,82.449,-84.745,82.437,-84.553,82.398,-84.368,82.374,-83.824,82.351,-83.591,82.326,-83.176,82.187,-83.01,82.142,-82.774,82.095,-82.634,82.077,-82.356,82.066,-82.327,82.092,-82.657,82.158,-82.747,82.196,-82.709,82.229,-82.638,82.246,-82.537,82.247,-82.277,82.218,-81.584,82.121,-80.55,82.005,-80.153,81.978,-79.909,81.936,-79.686,81.886,-79.466,81.851,-79.425,81.854,-79.629,81.932,-80.13,82.028,-81.468,82.192,-81.998,82.278,-82.254,82.336,-82.448,82.395,-82.451,82.427,-82.269,82.465,-82.023,82.494,-81.718,82.506,-81.681,82.519,-81.959,82.563,-82.123,82.602,-82.117,82.629,-81.785,82.649,-81.58,82.643,-81.189,82.594,-80.862,82.572,-80.81,82.586,-81.147,82.716,-81.178,82.745,-81.128,82.762,-81.01,82.779,-80.657,82.769,-80.076,82.706,-79.035,82.675,-78.749,82.679,-78.792,82.694,-79.207,82.733,-79.642,82.785,-79.834,82.817,-79.974,82.859,-80.141,82.894,-80.155,82.911,-79.886,82.939,-79.181,82.933,-78.525,82.891,-77.969,82.906,-77.618,82.896,-77.48,82.883,-77.226,82.837,-76.421,82.671,-76.336,82.644,-76.244,82.604,-76.146,82.55,-76.009,82.535,-75.744,82.572,-75.566,82.609,-75.643,82.644,-76.087,82.724,-76.188,82.758,-76.41,82.816,-76.908,82.919,-77.041,82.968,-77.125,83.009,-75.745,83.047,-74.414,83.013,-74.198,82.989,-74.056,82.955,-73.917,82.904,-73.703,82.852,-73.272,82.772,-72.659,82.722,-72.776,82.756,-73.235,82.844,-73.442,82.905,-73.441,82.946,-73.404,82.977,-73.331,82.999,-72.812,83.081,-72.069,83.106,-71.983,83.101,-71.406,82.975,-71.132,82.923,-70.94,82.902,-70.933,82.911,-71.198,82.97,-71.402,83.001,-71.424,83.021,-71.085,83.083,-70.871,83.098,-69.97,83.116,-69.868,83.11,-69.782,83.093,-69.569,83.025,-69.489,83.017]],[[-95.484,77.792,-95.233,77.754,-94.96,77.774,-94.667,77.776,-94.015,77.76,-93.583,77.771,-93.471,77.764,-93.301,77.74,-93.211,77.71,-93.129,77.66,-93.339,77.63,-93.52,77.474,-93.544,77.467,-93.74,77.465,-93.836,77.452,-94.409,77.474,-95.987,77.484,-96.056,77.503,-96.264,77.595,-96.277,77.631,-96.239,77.673,-96.195,77.701,-96.143,77.714,-95.684,77.782,-95.484,77.792]],[[-93.543,75.028,-93.478,74.952,-93.467,74.921,-93.463,74.856,-93.491,74.772,-93.509,74.756,-93.536,74.749,-93.548,74.728,-93.547,74.691,-93.573,74.669,-93.626,74.661,-93.985,74.644,-94.206,74.647,-94.535,74.637,-94.697,74.642,-94.804,74.66,-94.959,74.7,-95.286,74.794,-95.451,74.797,-95.865,74.83,-96.094,74.933,-96.182,74.951,-96.27,74.92,-96.294,74.927,-96.319,74.948,-96.343,74.982,-96.386,74.999,-96.56,74.99,-96.591,75.002,-96.6,75.032,-96.597,75.058,-96.566,75.099,-96.383,75.211,-96.292,75.219,-96.18,75.24,-96.118,75.301,-96.125,75.358,-95.955,75.444,-95.853,75.469,-95.671,75.529,-95.05,75.622,-94.878,75.63,-94.649,75.623,-94.427,75.593,-94.257,75.544,-93.909,75.423,-93.751,75.349,-93.667,75.274,-93.591,75.23,-93.498,75.137,-93.532,75.1,-93.552,75.051,-93.543,75.028]],[[-100.002,73.946,-99.158,73.732,-99.04,73.749,-98.785,73.761,-98.519,73.792,-98.152,73.818,-97.928,73.866,-97.832,73.879,-97.67,73.888,-97.582,73.888,-97.327,73.862,-97.225,73.844,-97.171,73.825,-97.112,73.79,-97.011,73.706,-96.997,73.675,-97.002,73.667,-97.095,73.615,-97.156,73.592,-97.284,73.571,-97.395,73.564,-97.49,73.527,-97.597,73.537,-97.626,73.502,-97.615,73.481,-97.586,73.471,-97.532,73.474,-97.47,73.488,-97.35,73.481,-97.287,73.458,-97.23,73.421,-97.273,73.387,-97.484,73.339,-97.796,73.285,-98.176,73.116,-98.376,73.045,-98.417,73.023,-98.437,73,-98.431,72.958,-98.422,72.941,-98.367,72.934,-98.181,72.993,-98.061,73.021,-97.939,73.036,-97.725,73.037,-97.636,73.028,-97.476,72.992,-97.329,72.938,-97.296,72.918,-97.31,72.898,-97.371,72.878,-97.378,72.865,-97.238,72.837,-97.083,72.763,-97.073,72.718,-97.14,72.673,-97.159,72.643,-97.128,72.628,-97.052,72.637,-96.869,72.687,-96.671,72.713,-96.592,72.71,-96.542,72.699,-96.489,72.63,-96.446,72.552,-96.44,72.487,-96.473,72.434,-96.52,72.393,-96.638,72.342,-96.746,72.323,-96.801,72.322,-96.796,72.314,-96.669,72.271,-96.616,72.237,-96.593,72.204,-96.601,72.173,-96.618,72.146,-96.766,72.046,-96.758,72.032,-96.717,72.025,-96.624,71.968,-96.613,71.834,-96.946,71.792,-97.025,71.761,-97.117,71.711,-97.222,71.673,-97.461,71.634,-97.582,71.63,-98.181,71.662,-98.242,71.681,-98.284,71.716,-98.307,71.765,-98.313,71.803,-98.303,71.831,-98.306,71.848,-98.323,71.852,-98.389,71.824,-98.459,71.773,-98.421,71.717,-98.231,71.559,-98.195,71.491,-98.19,71.462,-98.199,71.441,-98.412,71.349,-98.536,71.318,-98.663,71.302,-98.784,71.314,-98.899,71.352,-98.986,71.369,-99.167,71.367,-99.224,71.387,-99.276,71.424,-99.404,71.557,-99.581,71.652,-99.735,71.757,-100.124,71.912,-100.326,72.004,-100.594,72.152,-100.707,72.186,-100.8,72.199,-100.984,72.21,-101.026,72.229,-101.093,72.279,-101.209,72.317,-101.251,72.322,-101.319,72.313,-101.498,72.278,-101.724,72.315,-101.775,72.341,-101.804,72.385,-101.833,72.409,-101.909,72.431,-101.974,72.486,-102.402,72.595,-102.657,72.719,-102.709,72.765,-102.714,72.783,-102.687,72.843,-102.628,72.911,-102.551,72.978,-102.504,73.006,-102.336,73.064,-102.204,73.077,-102.02,73.07,-101.922,73.057,-101.835,73.018,-101.798,72.973,-101.755,72.943,-101.618,72.91,-101.544,72.883,-101.435,72.821,-101.351,72.746,-101.273,72.722,-101.088,72.713,-100.896,72.726,-100.485,72.773,-100.468,72.779,-100.443,72.807,-100.396,72.977,-100.368,72.978,-100.228,72.899,-100.188,72.89,-100.128,72.907,-100.092,72.945,-100.097,72.963,-100.184,73.055,-100.236,73.095,-100.283,73.12,-100.334,73.128,-100.446,73.121,-100.531,73.138,-100.55,73.164,-100.536,73.198,-100.489,73.234,-100.439,73.255,-100.341,73.265,-100.226,73.255,-100.067,73.211,-99.966,73.201,-99.825,73.214,-100.006,73.24,-100.258,73.34,-100.366,73.359,-100.498,73.316,-100.587,73.3,-100.755,73.278,-100.889,73.275,-101.451,73.431,-101.482,73.446,-101.523,73.486,-101.518,73.505,-101.463,73.534,-101.323,73.572,-101.115,73.596,-100.976,73.6,-100.854,73.571,-100.677,73.494,-100.522,73.449,-100.509,73.465,-100.536,73.51,-100.607,73.575,-100.658,73.593,-100.783,73.613,-100.898,73.658,-100.953,73.691,-100.982,73.727,-100.985,73.765,-100.963,73.791,-100.915,73.805,-100.484,73.844,-100.182,73.801,-99.991,73.795,-99.912,73.847,-99.94,73.857,-100.04,73.844,-100.154,73.844,-100.225,73.873,-100.227,73.889,-100.138,73.929,-100.002,73.946]],[[-84.92,65.261,-84.885,65.249,-84.842,65.256,-84.771,65.305,-84.612,65.447,-84.568,65.461,-84.501,65.458,-84.266,65.367,-84.18,65.316,-84.133,65.245,-84.085,65.218,-83.9,65.181,-83.723,65.169,-83.491,65.132,-83.407,65.104,-83.222,64.968,-83.201,64.96,-82.991,64.904,-82.668,64.78,-82.586,64.762,-82.272,64.721,-82.159,64.691,-82.05,64.644,-81.929,64.559,-81.787,64.426,-81.676,64.213,-81.667,64.171,-81.681,64.146,-81.721,64.119,-81.903,64.031,-81.887,64.016,-81.716,64.022,-81.336,64.076,-81.104,64.037,-81.024,64.031,-81.005,64.033,-80.921,64.1,-80.829,64.09,-80.694,64.025,-80.608,63.972,-80.569,63.932,-80.579,63.909,-80.668,63.901,-80.451,63.863,-80.261,63.802,-80.302,63.762,-80.504,63.674,-80.712,63.596,-80.954,63.48,-81.014,63.463,-81.046,63.462,-81.18,63.483,-81.372,63.538,-81.963,63.664,-82.146,63.691,-82.378,63.707,-82.412,63.737,-82.467,63.927,-82.571,63.961,-82.93,64,-83.034,64.023,-83.039,64.061,-83.016,64.127,-83.065,64.159,-83.186,64.158,-83.304,64.144,-83.494,64.099,-83.584,64.058,-83.617,64.013,-83.638,63.918,-83.662,63.873,-83.728,63.813,-84.022,63.66,-84.142,63.614,-84.26,63.6,-84.308,63.586,-84.387,63.529,-84.506,63.39,-84.555,63.35,-84.633,63.309,-84.796,63.247,-84.962,63.197,-85.238,63.139,-85.393,63.12,-85.496,63.139,-85.566,63.271,-85.714,63.658,-85.739,63.684,-85.769,63.7,-85.805,63.707,-86.302,63.657,-86.576,63.662,-86.847,63.575,-86.915,63.569,-87.053,63.572,-87.152,63.586,-87.177,63.595,-87.194,63.633,-87.189,63.672,-87.154,63.715,-87.032,63.83,-86.932,63.902,-86.886,63.924,-86.422,64.052,-86.309,64.094,-86.252,64.137,-86.252,64.181,-86.274,64.238,-86.354,64.377,-86.375,64.503,-86.374,64.566,-86.344,64.662,-86.228,64.896,-86.188,65.01,-86.114,65.417,-86.075,65.534,-86.017,65.64,-85.962,65.704,-85.814,65.832,-85.699,65.883,-85.555,65.919,-85.523,65.915,-85.496,65.9,-85.442,65.846,-85.241,65.796,-85.176,65.747,-85.13,65.693,-85.105,65.623,-85.13,65.592,-85.226,65.546,-85.243,65.526,-85.24,65.51,-85.056,65.437,-84.92,65.261]],[[-97.701,76.467,-97.69,76.422,-97.702,76.387,-97.737,76.363,-97.739,76.335,-97.707,76.304,-97.573,76.224,-97.531,76.182,-97.524,76.139,-97.531,76.109,-97.613,76.053,-97.65,75.979,-97.652,75.94,-97.603,75.879,-97.602,75.851,-97.694,75.803,-97.891,75.76,-97.863,75.738,-97.44,75.685,-97.408,75.673,-97.41,75.552,-97.336,75.42,-97.363,75.417,-97.465,75.459,-97.653,75.508,-97.878,75.416,-97.853,75.26,-97.705,75.191,-97.66,75.151,-97.674,75.127,-97.799,75.117,-97.843,75.122,-97.971,75.153,-98.045,75.201,-98.069,75.199,-98.092,75.176,-98.077,75.153,-97.99,75.111,-97.953,75.06,-97.992,75.046,-98.121,75.033,-98.295,75.032,-98.569,75.009,-98.704,75.006,-98.835,75.018,-99.01,75.021,-99.156,75.016,-99.245,75.026,-99.326,75.049,-99.421,75.044,-99.627,74.984,-99.947,75.003,-100.234,75.008,-100.292,75.028,-100.357,75.067,-100.483,75.188,-100.459,75.219,-100.152,75.236,-100.146,75.246,-100.364,75.29,-100.615,75.321,-100.731,75.347,-100.704,75.394,-100.712,75.406,-100.28,75.461,-99.965,75.569,-99.77,75.612,-99.756,75.633,-99.591,75.655,-99.209,75.669,-99.195,75.698,-99.915,75.681,-100.902,75.62,-101.207,75.59,-101.461,75.608,-102.541,75.514,-102.587,75.514,-102.7,75.544,-102.798,75.6,-102.728,75.639,-102.411,75.713,-102.252,75.778,-102.271,75.813,-102.145,75.875,-101.943,75.884,-101.6,75.833,-101.421,75.782,-101.261,75.758,-101.119,75.763,-100.973,75.798,-101.01,75.802,-101.259,75.784,-101.288,75.789,-101.415,75.846,-101.47,75.882,-101.506,75.918,-101.508,75.944,-101.431,75.992,-101.717,76.008,-101.823,76.041,-101.872,76.083,-101.861,76.101,-101.771,76.15,-101.529,76.217,-101.557,76.236,-101.91,76.234,-101.987,76.243,-102.138,76.285,-102.105,76.331,-101.964,76.399,-101.858,76.439,-101.788,76.451,-101.677,76.451,-101.415,76.425,-101.34,76.41,-101.139,76.345,-101.088,76.308,-101.094,76.272,-101.056,76.246,-100.9,76.207,-100.231,76.008,-100.106,75.96,-100.02,75.94,-99.865,75.924,-99.775,75.927,-99.701,75.941,-99.689,75.96,-99.978,76.029,-100.051,76.067,-100.113,76.117,-100.086,76.134,-100.002,76.139,-99.79,76.133,-99.541,76.146,-99.817,76.168,-99.998,76.196,-100.183,76.197,-100.414,76.243,-100.414,76.257,-100.358,76.271,-100.043,76.291,-99.983,76.3,-99.978,76.312,-100.082,76.343,-100.175,76.359,-100.651,76.396,-100.82,76.437,-100.874,76.457,-100.891,76.475,-100.83,76.524,-100.574,76.585,-100.388,76.614,-100.069,76.635,-99.814,76.632,-99.669,76.624,-99.329,76.521,-99.17,76.454,-98.89,76.466,-98.971,76.537,-99.024,76.615,-98.941,76.643,-98.711,76.694,-98.528,76.667,-98.289,76.599,-98.236,76.575,-97.967,76.533,-97.808,76.519,-97.726,76.496,-97.701,76.467]],[[-103.426,79.316,-103.192,79.295,-102.914,79.231,-102.652,79.095,-102.639,79.078,-102.638,79.055,-102.648,79.027,-102.683,78.991,-102.731,78.969,-102.596,78.943,-102.581,78.93,-102.593,78.901,-102.576,78.879,-102.495,78.901,-102.425,78.933,-102.407,78.954,-102.393,79.01,-102.189,79.038,-101.974,79.079,-101.873,79.088,-101.704,79.079,-101.299,78.982,-101.145,78.973,-101.088,78.962,-101.037,78.939,-101.034,78.915,-101.116,78.858,-101.147,78.824,-101.128,78.802,-100.917,78.783,-100.435,78.82,-100.015,78.729,-99.782,78.62,-99.609,78.583,-99.582,78.563,-99.631,78.545,-99.68,78.494,-99.818,78.455,-99.848,78.438,-99.774,78.393,-99.769,78.365,-99.778,78.325,-99.751,78.303,-99.562,78.279,-99.132,78.118,-99.053,78.072,-99.005,78.016,-99,77.997,-99.061,77.966,-99.128,77.877,-99.166,77.857,-99.341,77.84,-99.659,77.824,-99.956,77.794,-100.275,77.833,-100.586,77.892,-100.68,77.931,-100.758,77.978,-100.778,77.996,-100.81,78.072,-100.826,78.088,-100.958,78.13,-101.074,78.194,-101.298,78.199,-101.829,78.264,-102.057,78.28,-102.284,78.275,-102.607,78.249,-102.668,78.256,-102.723,78.275,-102.772,78.307,-102.784,78.33,-102.731,78.371,-103.677,78.32,-103.947,78.26,-104.324,78.269,-104.513,78.295,-104.764,78.352,-104.879,78.401,-104.985,78.468,-104.996,78.519,-104.91,78.553,-104.82,78.573,-104.727,78.579,-104.214,78.54,-103.764,78.52,-103.571,78.54,-103.483,78.594,-103.588,78.623,-104.021,78.635,-103.929,78.663,-103.563,78.693,-103.372,78.736,-103.408,78.752,-103.518,78.769,-104.009,78.764,-104.185,78.781,-104.195,78.796,-104.155,78.814,-103.876,78.903,-103.887,78.919,-104.007,78.948,-104.113,78.986,-104.152,78.99,-104.395,78.956,-104.736,78.826,-104.817,78.807,-104.896,78.808,-104.97,78.829,-104.97,78.856,-104.893,78.89,-104.735,78.991,-104.747,79.027,-104.901,79.051,-105.309,79.033,-105.536,79.033,-105.571,79.061,-105.58,79.114,-105.571,79.164,-105.515,79.242,-105.436,79.302,-105.388,79.324,-104.847,79.311,-103.965,79.348,-103.706,79.352,-103.426,79.316]],[[-91.886,81.133,-91.755,81.049,-91.272,80.85,-91.054,80.778,-90.683,80.688,-90.637,80.655,-90.632,80.642,-90.643,80.594,-90.537,80.576,-90.218,80.548,-89.862,80.498,-89.798,80.501,-89.674,80.531,-89.525,80.539,-89.329,80.532,-89.236,80.511,-89.167,80.48,-89.138,80.457,-89.134,80.44,-89.204,80.407,-89.197,80.394,-89.155,80.379,-89.147,80.36,-89.218,80.289,-89.198,80.263,-89.019,80.198,-88.857,80.166,-88.538,80.131,-88.329,80.134,-88.2,80.111,-88.197,80.125,-88.255,80.167,-88.381,80.225,-88.612,80.255,-88.646,80.29,-88.663,80.348,-88.644,80.387,-88.525,80.418,-88.424,80.428,-88.125,80.429,-87.96,80.416,-87.675,80.372,-87.646,80.348,-87.63,80.302,-87.618,80.207,-87.625,80.187,-87.869,80.134,-87.922,80.098,-87.861,80.088,-87.651,80.079,-87.329,80.047,-87.202,80.043,-87.076,79.967,-86.977,79.894,-87.05,79.805,-87.144,79.663,-87.22,79.63,-87.295,79.58,-87.243,79.571,-86.925,79.591,-86.861,79.598,-86.649,79.646,-86.337,79.635,-86.232,79.622,-86.18,79.605,-86.086,79.551,-86.007,79.479,-85.949,79.486,-85.804,79.573,-85.751,79.595,-85.679,79.615,-85.648,79.611,-85.501,79.53,-85.176,79.387,-85.064,79.328,-85.042,79.285,-85.181,79.234,-85.29,79.208,-86.092,79.1,-86.451,79.039,-86.629,78.991,-86.721,78.975,-86.913,78.983,-86.957,78.975,-87.016,78.899,-87.08,78.866,-87.246,78.813,-87.479,78.718,-87.617,78.676,-87.861,78.707,-87.922,78.751,-87.956,78.852,-87.961,78.893,-87.953,78.915,-87.923,78.951,-87.817,79.036,-87.829,79.045,-87.878,79.038,-88.04,78.995,-88.104,78.973,-88.164,78.933,-88.19,78.867,-88.167,78.746,-88.19,78.696,-88.254,78.672,-88.228,78.653,-88.037,78.627,-88.003,78.616,-87.982,78.595,-87.974,78.565,-87.983,78.537,-88.04,78.494,-88.148,78.477,-88.285,78.497,-88.581,78.602,-88.709,78.596,-88.742,78.584,-88.714,78.546,-88.623,78.462,-88.606,78.392,-88.648,78.334,-88.733,78.242,-88.791,78.192,-88.822,78.186,-88.97,78.184,-89.096,78.209,-89.47,78.37,-89.655,78.439,-89.926,78.573,-89.995,78.601,-90.037,78.607,-90.076,78.549,-90.001,78.496,-89.757,78.37,-89.612,78.279,-89.507,78.203,-89.49,78.172,-89.526,78.16,-89.579,78.167,-89.651,78.193,-89.873,78.238,-89.965,78.262,-90.025,78.291,-90.136,78.313,-90.297,78.328,-90.459,78.331,-90.622,78.322,-90.652,78.308,-90.469,78.269,-90.405,78.247,-90.358,78.219,-90.327,78.185,-90.387,78.163,-90.614,78.15,-90.918,78.158,-91.41,78.188,-91.899,78.237,-92.351,78.313,-92.678,78.389,-92.808,78.43,-92.848,78.46,-92.726,78.487,-92.297,78.521,-91.867,78.543,-91.935,78.562,-92.716,78.605,-92.973,78.613,-93.109,78.602,-93.267,78.608,-93.389,78.643,-93.552,78.708,-93.634,78.751,-93.623,78.768,-93.561,78.777,-93.208,78.769,-93.16,78.776,-93.336,78.808,-93.902,78.872,-94.115,78.929,-94.154,78.951,-94.17,78.973,-94.163,78.994,-93.95,79.037,-93.294,79.14,-93.068,79.155,-92.842,79.156,-92.684,79.186,-92.547,79.283,-91.868,79.317,-91.344,79.361,-91.3,79.373,-91.693,79.365,-92.248,79.373,-92.485,79.439,-92.645,79.45,-92.822,79.45,-93.028,79.429,-93.381,79.368,-93.55,79.354,-93.933,79.291,-94.04,79.295,-94.093,79.303,-94.109,79.315,-94.04,79.357,-93.94,79.386,-93.96,79.396,-94.11,79.402,-94.284,79.4,-94.405,79.391,-94.846,79.335,-95.044,79.294,-95.103,79.29,-95.317,79.355,-95.657,79.39,-95.733,79.418,-95.663,79.527,-95.563,79.55,-95.302,79.568,-94.52,79.667,-94.476,79.686,-94.402,79.736,-94.581,79.726,-94.973,79.677,-95.297,79.653,-95.552,79.653,-95.739,79.66,-95.858,79.674,-96,79.705,-96.463,79.848,-96.589,79.917,-96.607,79.978,-96.639,80.024,-96.773,80.136,-95.782,80.066,-95.394,80.053,-94.646,80.049,-94.611,80.056,-94.6,80.074,-94.613,80.103,-94.607,80.126,-94.583,80.141,-94.304,80.182,-94.263,80.195,-94.59,80.202,-95.192,80.134,-95.405,80.135,-95.646,80.231,-95.904,80.214,-96.026,80.222,-96.215,80.246,-96.308,80.267,-96.368,80.293,-96.394,80.315,-96.385,80.333,-96.334,80.353,-96.112,80.38,-96.012,80.383,-95.747,80.365,-95.549,80.367,-95.614,80.396,-95.901,80.471,-96.152,80.553,-96.133,80.691,-95.927,80.721,-95.714,80.725,-95.505,80.691,-95.226,80.686,-95.026,80.646,-94.893,80.571,-94.734,80.572,-94.485,80.558,-93.928,80.559,-94.029,80.586,-94.202,80.61,-94.596,80.641,-94.788,80.751,-95.196,80.808,-95.515,80.838,-95.509,80.863,-95.27,81.001,-94.981,81.05,-94.519,81.031,-94.216,81.057,-93.826,81.106,-93.444,81.083,-93.345,81.085,-93.287,81.1,-93.236,81.129,-93.235,81.155,-93.286,81.179,-93.407,81.209,-93.894,81.213,-94.11,81.225,-94.194,81.241,-94.219,81.265,-94.231,81.29,-94.233,81.315,-94.22,81.331,-94.179,81.339,-94.06,81.349,-93.605,81.351,-93.333,81.364,-93.035,81.346,-92.413,81.278,-92.212,81.244,-91.998,81.185,-91.886,81.133]],[[-94.295,76.912,-94.108,76.904,-93.948,76.917,-93.811,76.914,-93.608,76.874,-93.421,76.812,-93.277,76.784,-93.23,76.77,-93.212,76.755,-93.189,76.708,-93.19,76.686,-93.201,76.669,-93.264,76.626,-93.317,76.574,-93.426,76.527,-93.485,76.492,-93.535,76.448,-93.422,76.474,-92.995,76.62,-92.716,76.603,-92.297,76.616,-91.789,76.676,-91.548,76.685,-91.305,76.681,-91.124,76.662,-90.738,76.581,-90.605,76.543,-90.555,76.516,-90.543,76.496,-90.622,76.465,-90.864,76.484,-91.263,76.5,-91.336,76.511,-91.398,76.51,-91.443,76.499,-91.415,76.456,-91.334,76.446,-90.855,76.437,-89.285,76.302,-89.219,76.258,-89.237,76.239,-89.292,76.218,-89.407,76.189,-90.312,76.158,-90.827,76.186,-91.26,76.23,-91.407,76.22,-91.279,76.16,-91.02,76.142,-90.802,76.106,-90.712,76.076,-90.251,76.053,-90.176,76.03,-90.033,75.971,-89.913,75.966,-89.793,75.925,-89.695,75.854,-89.65,75.844,-89.511,75.857,-89.278,75.795,-89.205,75.762,-89.205,75.737,-89.257,75.698,-89.361,75.646,-89.625,75.584,-89.646,75.565,-89.337,75.572,-89.28,75.564,-88.917,75.454,-88.869,75.452,-88.839,75.463,-88.804,75.502,-88.82,75.539,-88.864,75.589,-88.852,75.625,-88.784,75.647,-88.715,75.659,-88.645,75.658,-88.569,75.645,-88.201,75.512,-87.73,75.576,-87.644,75.547,-87.572,75.494,-87.539,75.485,-87.365,75.591,-87.257,75.618,-86.814,75.491,-86.545,75.463,-86.437,75.436,-86.236,75.406,-85.951,75.395,-85.905,75.442,-86.069,75.502,-85.973,75.529,-85.581,75.58,-85.372,75.573,-84.987,75.645,-84.75,75.655,-84.605,75.653,-84.128,75.763,-84.014,75.78,-83.932,75.819,-83.745,75.813,-83.237,75.751,-83.093,75.756,-82.553,75.818,-82.354,75.833,-82.154,75.831,-81.647,75.795,-81.269,75.756,-81.151,75.736,-81.193,75.684,-81.174,75.669,-81.124,75.658,-81.001,75.643,-80.528,75.642,-80.322,75.629,-80.158,75.581,-80.119,75.562,-80.126,75.542,-80.287,75.49,-80.26,75.479,-80.1,75.467,-79.738,75.461,-79.66,75.45,-79.586,75.385,-79.508,75.295,-79.509,75.26,-79.634,75.199,-79.977,75.119,-80.358,75.052,-80.382,75.034,-80.261,75.002,-80.135,74.988,-80.036,74.991,-79.733,75.021,-79.664,75.021,-79.525,74.99,-79.46,74.959,-79.401,74.918,-79.508,74.88,-79.944,74.834,-80.202,74.895,-80.289,74.908,-80.348,74.903,-80.315,74.876,-80.19,74.828,-80.149,74.796,-80.192,74.78,-80.213,74.749,-80.21,74.704,-80.221,74.657,-80.263,74.584,-80.278,74.582,-81.226,74.567,-81.34,74.554,-81.607,74.502,-81.809,74.477,-81.94,74.473,-82.069,74.482,-82.415,74.535,-82.645,74.525,-82.736,74.53,-82.931,74.566,-82.978,74.583,-83.058,74.63,-83.117,74.693,-83.112,74.732,-83.087,74.788,-83.103,74.817,-83.158,74.817,-83.22,74.828,-83.407,74.885,-83.522,74.901,-83.544,74.892,-83.51,74.848,-83.487,74.834,-83.364,74.802,-83.341,74.765,-83.394,74.67,-83.412,74.655,-83.532,74.586,-83.622,74.566,-83.868,74.564,-84.245,74.515,-84.426,74.508,-84.667,74.52,-84.818,74.542,-84.916,74.568,-85.012,74.604,-85.061,74.607,-85.087,74.528,-85.133,74.517,-85.214,74.519,-85.339,74.543,-85.442,74.601,-85.474,74.6,-85.489,74.567,-85.512,74.545,-85.544,74.535,-85.808,74.499,-85.956,74.499,-86.11,74.54,-86.211,74.536,-86.341,74.513,-86.655,74.555,-86.731,74.557,-86.666,74.489,-86.77,74.479,-86.995,74.48,-87.364,74.502,-87.593,74.47,-88.006,74.489,-88.423,74.494,-88.501,74.51,-88.556,74.541,-88.558,74.57,-88.538,74.609,-88.477,74.667,-88.375,74.744,-88.34,74.785,-88.431,74.804,-88.488,74.829,-88.535,74.832,-88.682,74.802,-88.778,74.715,-88.851,74.69,-88.883,74.711,-88.908,74.764,-88.94,74.79,-88.98,74.788,-89.02,74.774,-89.058,74.747,-89.115,74.738,-89.192,74.745,-89.219,74.732,-89.197,74.698,-89.189,74.667,-89.196,74.638,-89.262,74.609,-89.45,74.568,-89.559,74.555,-89.844,74.549,-90.015,74.561,-90.362,74.61,-90.553,74.613,-90.784,74.696,-90.967,74.715,-90.958,74.745,-90.878,74.801,-90.88,74.818,-91.13,74.736,-91.164,74.71,-91.135,74.65,-91.168,74.646,-91.339,74.667,-91.508,74.651,-91.549,74.656,-91.666,74.699,-91.871,74.744,-91.962,74.793,-92.103,74.948,-92.174,75.051,-92.165,75.072,-92.06,75.101,-92.076,75.124,-92.207,75.181,-92.347,75.23,-92.389,75.263,-92.408,75.297,-92.427,75.346,-92.428,75.383,-92.411,75.406,-92.331,75.479,-92.11,75.611,-92.081,75.634,-92.069,75.658,-92.099,75.727,-92.142,75.797,-92.185,75.847,-92.307,75.915,-92.474,75.986,-92.709,76.114,-92.883,76.214,-93.092,76.354,-93.192,76.366,-93.309,76.36,-93.56,76.311,-93.644,76.289,-93.665,76.273,-93.852,76.27,-94.383,76.282,-94.585,76.297,-94.737,76.293,-94.997,76.258,-95.274,76.264,-95.447,76.363,-95.842,76.416,-95.959,76.446,-96.04,76.487,-96.013,76.513,-95.789,76.537,-95.696,76.563,-95.651,76.585,-95.873,76.566,-95.971,76.57,-96.64,76.703,-96.846,76.726,-96.881,76.738,-96.898,76.754,-96.898,76.773,-96.878,76.803,-96.679,76.766,-96.59,76.763,-96.451,76.774,-96.402,76.797,-96.433,76.811,-96.661,76.855,-96.771,76.889,-96.814,76.913,-96.77,76.948,-96.758,76.972,-96.685,76.985,-96.55,76.988,-96.377,77.005,-96.061,77.05,-95.85,77.066,-95.638,77.064,-95.126,77.017,-94.616,76.958,-94.295,76.912]],[[-96.204,78.531,-95.968,78.505,-95.561,78.517,-95.413,78.498,-95.031,78.43,-94.915,78.391,-94.888,78.36,-94.887,78.345,-95.014,78.313,-95.268,78.263,-95.329,78.225,-95.103,78.178,-94.988,78.136,-94.936,78.106,-94.934,78.076,-95.087,77.993,-95.199,77.968,-95.371,77.971,-95.452,77.963,-95.671,77.924,-96.012,77.887,-96.477,77.872,-96.603,77.849,-96.834,77.812,-96.99,77.806,-97.04,77.827,-97.064,77.859,-97.052,77.881,-97.019,77.908,-97.093,77.933,-97.427,77.982,-97.621,78.05,-97.648,78.072,-97.658,78.091,-97.227,78.103,-97.041,78.117,-96.958,78.139,-96.945,78.152,-97.027,78.157,-97.323,78.203,-97.819,78.231,-97.843,78.262,-98.05,78.326,-98.069,78.386,-98.114,78.403,-98.255,78.429,-98.276,78.438,-98.317,78.477,-98.324,78.498,-98.316,78.517,-98.06,78.558,-98.096,78.587,-98.29,78.692,-98.341,78.751,-98.333,78.774,-98.212,78.805,-98.043,78.805,-97.596,78.796,-97.382,78.783,-97.169,78.758,-96.936,78.72,-96.587,78.687,-96.475,78.665,-96.265,78.595,-96.243,78.573,-96.256,78.551,-96.204,78.531]],[[-93.171,74.161,-92.778,74.114,-92.587,74.083,-92.493,74.062,-92.314,73.992,-92.223,73.972,-91.874,74.013,-91.63,74.028,-91.088,74.009,-90.627,73.952,-90.458,73.908,-90.355,73.869,-90.381,73.825,-90.466,73.754,-90.566,73.686,-90.765,73.581,-90.934,73.528,-90.975,73.502,-91.002,73.467,-91.068,73.416,-91.249,73.304,-91.298,73.285,-91.554,73.236,-91.466,73.214,-91.426,73.195,-91.46,73.145,-91.621,73.026,-91.788,72.915,-91.905,72.849,-92.118,72.754,-92.235,72.727,-92.392,72.718,-93.341,72.802,-93.579,72.801,-94.211,72.757,-94.152,72.736,-93.92,72.703,-93.771,72.668,-93.572,72.559,-93.546,72.531,-93.534,72.499,-93.542,72.437,-93.555,72.421,-93.871,72.253,-93.973,72.13,-94.038,72.029,-94.144,72.001,-94.497,72.044,-94.611,72.042,-95.008,72.013,-95.193,72.027,-95.167,72.18,-95.193,72.345,-95.251,72.502,-95.548,72.782,-95.58,72.831,-95.602,72.884,-95.613,72.942,-95.612,72.999,-95.592,73.115,-95.589,73.174,-95.604,73.328,-95.644,73.557,-95.648,73.639,-95.645,73.671,-95.633,73.695,-95.569,73.728,-95.447,73.752,-95.386,73.755,-94.996,73.686,-94.817,73.663,-94.698,73.664,-94.691,73.671,-94.797,73.686,-94.897,73.716,-95.059,73.805,-95.134,73.881,-95.149,73.906,-95.153,73.933,-95.145,73.96,-95.121,73.985,-95.04,74.024,-94.974,74.041,-94.729,74.086,-94.483,74.113,-93.939,74.132,-93.785,74.118,-93.549,74.167,-93.41,74.179,-93.171,74.161]],[[-97.439,69.643,-97.409,69.631,-97.351,69.641,-97.306,69.673,-97.278,69.68,-97.236,69.673,-97.096,69.615,-96.989,69.554,-96.875,69.51,-96.695,69.471,-96.3,69.344,-96.184,69.259,-96.061,69.125,-95.951,69.024,-95.855,68.954,-95.751,68.898,-95.585,68.835,-95.438,68.881,-95.374,68.892,-95.32,68.873,-95.268,68.826,-95.295,68.805,-95.359,68.778,-95.466,68.747,-95.614,68.745,-95.686,68.736,-95.802,68.686,-95.895,68.627,-96.024,68.607,-96.268,68.508,-96.402,68.471,-96.599,68.461,-97.008,68.539,-97.264,68.528,-97.472,68.544,-97.705,68.626,-97.885,68.672,-98.235,68.739,-98.258,68.749,-98.273,68.772,-98.28,68.807,-98.296,68.831,-98.321,68.843,-98.376,68.842,-98.432,68.818,-98.54,68.798,-98.704,68.803,-98.775,68.817,-98.83,68.839,-98.859,68.864,-98.864,68.894,-98.879,68.916,-98.904,68.932,-98.964,68.933,-99.057,68.918,-99.094,68.899,-99.073,68.877,-99.091,68.863,-99.254,68.863,-99.318,68.876,-99.441,68.918,-99.495,68.96,-99.564,69.034,-99.557,69.054,-99.513,69.1,-99.456,69.131,-99.085,69.15,-98.912,69.168,-98.724,69.219,-98.504,69.308,-98.456,69.335,-98.45,69.354,-98.467,69.375,-98.535,69.426,-98.559,69.461,-98.537,69.478,-98.448,69.48,-98.495,69.499,-98.534,69.527,-98.548,69.545,-98.546,69.573,-98.476,69.579,-98.389,69.565,-98.222,69.485,-98.156,69.469,-98.041,69.457,-98.163,69.512,-98.289,69.629,-98.304,69.669,-98.301,69.692,-98.268,69.754,-98.239,69.78,-98.2,69.797,-98.081,69.833,-97.889,69.858,-97.791,69.862,-97.691,69.841,-97.604,69.802,-97.411,69.738,-97.383,69.712,-97.386,69.7,-97.46,69.683,-97.469,69.667,-97.439,69.643]],[[-61.105,45.945,-61.071,45.937,-60.937,45.986,-60.865,45.983,-60.868,45.949,-60.984,45.911,-61.038,45.882,-60.971,45.856,-60.972,45.838,-61.052,45.795,-61.092,45.748,-61.059,45.703,-60.93,45.748,-60.878,45.748,-60.806,45.738,-60.738,45.751,-60.699,45.773,-60.472,45.947,-60.461,45.969,-60.705,45.933,-60.733,45.957,-60.573,46.061,-60.586,46.117,-60.505,46.204,-60.431,46.256,-60.377,46.285,-60.298,46.311,-60.244,46.27,-60.226,46.196,-60.092,46.206,-59.961,46.191,-59.865,46.16,-59.85,46.141,-59.849,46.113,-59.881,46.062,-59.934,46.019,-59.828,45.965,-59.842,45.942,-60.016,45.88,-60.114,45.819,-60.205,45.743,-60.386,45.655,-60.673,45.591,-60.764,45.591,-60.872,45.611,-60.979,45.606,-61.084,45.582,-61.186,45.585,-61.236,45.573,-61.284,45.574,-61.323,45.598,-61.408,45.669,-61.45,45.716,-61.495,45.941,-61.481,46.06,-61.409,46.17,-61.302,46.244,-61.241,46.303,-60.983,46.65,-60.932,46.729,-60.87,46.797,-60.76,46.863,-60.617,46.976,-60.571,46.999,-60.489,47.01,-60.408,47.004,-60.431,46.963,-60.425,46.923,-60.332,46.768,-60.333,46.737,-60.384,46.613,-60.482,46.414,-60.508,46.303,-60.495,46.27,-60.534,46.215,-60.577,46.172,-60.745,46.093,-60.831,46.074,-60.912,46.045,-61.105,45.945]],[[-67.125,45.169,-67.171,45.182,-67.213,45.193,-67.25,45.201,-67.271,45.187,-67.291,45.168,-67.315,45.154,-67.367,45.174,-67.4,45.21,-67.453,45.248,-67.473,45.276,-67.462,45.309,-67.439,45.34,-67.428,45.378,-67.454,45.421,-67.477,45.446,-67.494,45.474,-67.488,45.501,-67.455,45.514,-67.424,45.53,-67.414,45.566,-67.433,45.603,-67.487,45.618,-67.531,45.613,-67.596,45.621,-67.658,45.644,-67.699,45.671,-67.731,45.686,-67.755,45.686,-67.785,45.702,-67.802,45.728,-67.8,45.77,-67.792,45.796,-67.775,45.818,-67.774,45.843,-67.781,45.86,-67.782,45.874,-67.778,45.892,-67.767,45.927,-67.785,45.953,-67.786,46.042,-67.79,46.209,-67.793,46.337,-67.796,46.498,-67.798,46.616,-67.8,46.78,-67.803,46.936,-67.807,47.083,-67.935,47.168,-68.097,47.275,-68.235,47.346,-68.311,47.354,-68.358,47.345,-68.377,47.316,-68.48,47.286,-68.669,47.253,-68.829,47.203,-68.887,47.203,-68.937,47.211,-69.003,47.236,-69.049,47.274,-69.064,47.338,-69.05,47.427,-69.146,47.445,-69.243,47.463,-69.302,47.402,-69.359,47.351,-69.471,47.239,-69.63,47.081,-69.718,46.995,-69.872,46.843,-70.008,46.709,-70.038,46.571,-70.067,46.441,-70.18,46.342,-70.248,46.251,-70.279,46.15,-70.304,46.057,-70.306,45.98,-70.287,45.939,-70.296,45.906,-70.333,45.868,-70.408,45.802,-70.421,45.738,-70.467,45.707,-70.596,45.644,-70.702,45.551,-70.707,45.499,-70.692,45.455,-70.69,45.428,-70.711,45.409,-70.753,45.411,-70.799,45.405,-70.838,45.366,-70.837,45.311,-70.865,45.271,-70.898,45.262,-70.926,45.291,-70.96,45.333,-71,45.337,-71.06,45.309,-71.135,45.263,-71.202,45.26,-71.327,45.29,-71.419,45.2,-71.518,45.008,-71.934,45.007,-72.35,45.007,-72.766,45.006,-73.182,45.006,-73.598,45.005,-74.014,45.005,-74.43,45.004,-74.663,45.004,-74.709,45.004,-74.566,45.042,-74.269,45.188,-74.05,45.241,-73.765,45.395,-73.558,45.425,-73.519,45.459,-73.484,45.587,-73.465,45.632,-73.369,45.758,-73.253,45.864,-73.16,46.01,-72.99,46.104,-72.733,46.182,-72.496,46.353,-72.366,46.405,-72.24,46.442,-72.187,46.512,-72.109,46.551,-71.901,46.632,-71.671,46.654,-71.439,46.721,-71.261,46.756,-71.152,46.819,-70.993,46.852,-70.519,47.033,-70.388,47.117,-70.218,47.29,-70.07,47.378,-70.017,47.471,-69.802,47.623,-69.581,47.824,-69.471,47.967,-69.306,48.047,-68.987,48.275,-68.816,48.366,-68.746,48.376,-68.552,48.457,-68.431,48.542,-68.238,48.626,-67.889,48.731,-67.561,48.856,-67.117,48.964,-66.598,49.126,-66.178,49.213,-65.883,49.226,-65.523,49.266,-65.396,49.262,-64.836,49.192,-64.568,49.105,-64.262,48.922,-64.216,48.874,-64.209,48.806,-64.371,48.839,-64.514,48.841,-64.415,48.804,-64.246,48.691,-64.254,48.55,-64.349,48.423,-64.633,48.36,-64.706,48.311,-64.765,48.228,-64.822,48.196,-64.96,48.16,-65.036,48.106,-65.259,48.021,-65.36,48.011,-65.476,48.031,-65.755,48.112,-65.927,48.189,-66.013,48.147,-66.083,48.103,-66.249,48.117,-66.324,48.098,-66.449,48.12,-66.704,48.022,-66.632,48.011,-66.429,48.067,-66.36,48.061,-66.21,47.989,-65.849,47.911,-65.756,47.86,-65.666,47.696,-65.607,47.67,-65.483,47.687,-65.344,47.768,-65.228,47.811,-65.002,47.847,-65.046,47.793,-64.874,47.797,-64.703,47.725,-64.766,47.673,-64.852,47.57,-64.912,47.369,-65.086,47.234,-65.319,47.101,-65.26,47.069,-65.192,47.05,-65.042,47.089,-64.942,47.086,-64.831,47.061,-64.866,46.958,-64.906,46.888,-64.883,46.823,-64.817,46.699,-64.726,46.671,-64.69,46.512,-64.641,46.426,-64.648,46.356,-64.557,46.311,-64.542,46.24,-64.212,46.22,-64.145,46.193,-63.916,46.166,-63.873,46.146,-63.832,46.107,-64.056,46.021,-63.875,45.959,-63.703,45.858,-63.568,45.878,-63.509,45.875,-63.358,45.811,-63.316,45.78,-63.293,45.752,-63.217,45.758,-63.108,45.782,-62.911,45.776,-62.701,45.741,-62.718,45.686,-62.75,45.648,-62.586,45.661,-62.483,45.622,-62.447,45.641,-62.422,45.665,-62.218,45.731,-61.956,45.868,-61.924,45.851,-61.912,45.799,-61.877,45.714,-61.777,45.656,-61.657,45.642,-61.492,45.687,-61.428,45.648,-61.35,45.574,-61.277,45.476,-61.282,45.441,-61.376,45.411,-61.461,45.367,-61.107,45.349,-61.071,45.33,-61.032,45.292,-61.068,45.253,-61.101,45.233,-61.165,45.256,-61.284,45.235,-61.387,45.185,-61.498,45.157,-61.569,45.154,-61.647,45.131,-61.719,45.094,-61.794,45.084,-62.027,44.994,-62.265,44.936,-62.514,44.844,-62.768,44.785,-63.032,44.715,-63.089,44.709,-63.156,44.711,-63.306,44.643,-63.381,44.652,-63.457,44.64,-63.544,44.655,-63.604,44.683,-63.558,44.611,-63.545,44.544,-63.568,44.514,-63.61,44.48,-63.761,44.486,-63.821,44.511,-63.891,44.546,-63.924,44.604,-64,44.645,-64.045,44.588,-64.045,44.545,-64.101,44.487,-64.167,44.587,-64.286,44.55,-64.339,44.445,-64.312,44.415,-64.276,44.334,-64.335,44.292,-64.378,44.304,-64.469,44.185,-64.578,44.142,-64.692,44.021,-64.826,43.929,-64.862,43.868,-65.087,43.727,-65.172,43.731,-65.235,43.727,-65.33,43.668,-65.344,43.55,-65.386,43.565,-65.429,43.561,-65.45,43.524,-65.482,43.518,-65.564,43.553,-65.662,43.534,-65.738,43.561,-65.835,43.734,-65.887,43.795,-65.978,43.815,-66.002,43.778,-66.038,43.742,-66.126,43.814,-66.193,44.08,-66.193,44.144,-66.1,44.367,-65.868,44.569,-65.942,44.576,-66.146,44.436,-66.125,44.47,-66.091,44.505,-66.022,44.562,-65.917,44.615,-65.778,44.646,-65.682,44.651,-65.616,44.68,-65.52,44.733,-65.502,44.76,-65.587,44.729,-65.728,44.697,-65.692,44.738,-65.657,44.76,-64.903,45.121,-64.751,45.18,-64.449,45.256,-64.407,45.306,-64.448,45.337,-64.331,45.309,-64.34,45.268,-64.359,45.238,-64.366,45.187,-64.354,45.138,-64.235,45.114,-64.135,45.023,-64.183,45.147,-64.093,45.217,-63.748,45.311,-63.46,45.321,-63.368,45.365,-63.614,45.394,-63.906,45.378,-64.087,45.411,-64.336,45.39,-64.6,45.41,-64.681,45.383,-64.747,45.324,-64.832,45.35,-64.873,45.355,-64.913,45.375,-64.827,45.476,-64.56,45.625,-64.397,45.756,-64.351,45.783,-64.315,45.836,-64.404,45.827,-64.482,45.806,-64.536,45.867,-64.633,45.947,-64.642,45.913,-64.594,45.814,-64.779,45.638,-64.898,45.626,-65.057,45.544,-65.282,45.473,-65.545,45.337,-65.884,45.223,-65.956,45.222,-66.11,45.317,-66.067,45.359,-66.027,45.418,-66.065,45.401,-66.09,45.376,-66.183,45.335,-66.107,45.257,-66.144,45.228,-66.252,45.189,-66.352,45.133,-66.44,45.096,-66.511,45.143,-66.707,45.083,-66.872,45.067,-66.908,45.098,-66.919,45.146,-66.977,45.157,-67.084,45.144,-67.125,45.169]],[[-55.459,51.537,-55.532,51.437,-55.583,51.389,-55.631,51.373,-55.731,51.359,-55.941,51.343,-56.031,51.328,-56.044,51.262,-56.031,51.227,-56,51.199,-55.961,51.191,-55.874,51.208,-55.841,51.205,-55.815,51.191,-55.796,51.166,-55.785,51.131,-55.785,51.087,-55.8,51.033,-55.871,50.907,-55.962,50.838,-56.078,50.781,-56.107,50.759,-56.121,50.734,-56.136,50.651,-56.196,50.585,-56.382,50.417,-56.454,50.38,-56.455,50.35,-56.484,50.271,-56.539,50.207,-56.694,50.06,-56.732,50.008,-56.75,49.967,-56.747,49.908,-56.754,49.883,-56.79,49.834,-56.839,49.788,-56.849,49.765,-56.829,49.725,-56.809,49.71,-56.807,49.673,-56.822,49.613,-56.757,49.652,-56.611,49.788,-56.501,49.87,-56.428,49.897,-56.376,49.934,-56.322,50.014,-56.247,50.09,-56.179,50.115,-56.148,50.1,-56.122,50.063,-56.127,50.015,-56.164,49.957,-56.161,49.94,-56.075,49.983,-55.927,50.018,-55.873,50.013,-55.765,49.96,-55.674,49.967,-55.53,49.997,-55.503,49.983,-55.527,49.937,-55.584,49.892,-55.718,49.829,-56.04,49.705,-56.14,49.619,-56.121,49.622,-56.052,49.658,-55.979,49.678,-55.902,49.681,-55.87,49.67,-55.882,49.646,-55.892,49.58,-56.087,49.452,-56.041,49.457,-55.815,49.515,-55.678,49.435,-55.49,49.463,-55.376,49.49,-55.379,49.473,-55.354,49.438,-55.355,49.381,-55.344,49.373,-55.29,49.392,-55.28,49.413,-55.283,49.514,-55.266,49.524,-55.23,49.508,-55.207,49.482,-55.2,49.408,-55.225,49.335,-55.259,49.267,-55.342,49.168,-55.332,49.126,-55.353,49.079,-55.335,49.078,-55.252,49.121,-55.247,49.139,-55.254,49.18,-55.245,49.2,-55.176,49.244,-55.063,49.297,-55.026,49.305,-55.01,49.293,-55.016,49.26,-54.983,49.268,-54.911,49.316,-54.844,49.345,-54.782,49.355,-54.718,49.389,-54.651,49.445,-54.579,49.491,-54.502,49.527,-54.469,49.53,-54.481,49.469,-54.465,49.401,-54.463,49.342,-54.448,49.329,-54.389,49.392,-54.356,49.415,-54.317,49.424,-54.271,49.419,-53.958,49.442,-53.862,49.426,-53.755,49.385,-53.619,49.322,-53.57,49.264,-53.56,49.192,-53.573,49.141,-53.671,49.078,-53.758,49.035,-53.809,48.993,-53.825,48.951,-53.845,48.925,-53.903,48.889,-54.161,48.788,-54.1,48.785,-53.951,48.807,-53.853,48.811,-53.848,48.797,-53.887,48.768,-53.962,48.739,-53.97,48.725,-53.966,48.707,-53.886,48.685,-53.784,48.695,-53.698,48.68,-53.706,48.656,-53.775,48.576,-53.795,48.526,-53.886,48.485,-54.068,48.419,-54.114,48.394,-54.104,48.388,-53.937,48.437,-53.853,48.449,-53.799,48.449,-53.739,48.496,-53.644,48.511,-53.552,48.482,-53.411,48.562,-53.361,48.573,-53.275,48.563,-53.22,48.578,-53.127,48.633,-53.057,48.659,-53.043,48.657,-53.028,48.635,-53.021,48.572,-53.037,48.516,-53.06,48.48,-53.136,48.402,-53.182,48.374,-53.225,48.364,-53.301,48.368,-53.334,48.356,-53.406,48.294,-53.531,48.232,-53.61,48.208,-53.56,48.174,-53.542,48.108,-53.569,48.088,-53.704,48.068,-53.71,48.057,-53.758,48.042,-53.87,48.02,-53.794,48.01,-53.653,48.026,-53.638,48.015,-53.658,47.969,-53.695,47.921,-53.862,47.799,-53.864,47.787,-53.838,47.727,-53.805,47.682,-53.765,47.65,-53.672,47.648,-53.604,47.662,-53.504,47.744,-53.283,47.998,-53.085,48.069,-52.921,48.147,-52.883,48.131,-52.866,48.113,-52.872,48.094,-52.955,48.029,-52.998,47.976,-53.111,47.812,-53.154,47.735,-53.176,47.653,-53.17,47.512,-53.158,47.488,-53.122,47.455,-53.057,47.483,-52.945,47.553,-52.873,47.619,-52.817,47.728,-52.782,47.769,-52.745,47.769,-52.711,47.745,-52.703,47.693,-52.672,47.622,-52.654,47.549,-52.669,47.47,-52.684,47.426,-52.912,47.103,-52.888,47.046,-52.882,47.011,-52.889,46.974,-52.962,46.819,-53.032,46.723,-53.07,46.681,-53.115,46.656,-53.167,46.646,-53.214,46.66,-53.255,46.698,-53.291,46.717,-53.323,46.718,-53.382,46.711,-53.536,46.633,-53.568,46.628,-53.59,46.639,-53.616,46.68,-53.595,46.888,-53.581,46.957,-53.612,47.01,-53.58,47.099,-53.578,47.133,-53.597,47.146,-53.636,47.138,-53.695,47.093,-53.774,47.012,-53.86,46.939,-54.01,46.84,-54.076,46.82,-54.102,46.825,-54.133,46.839,-54.174,46.88,-54.173,46.917,-54.155,46.967,-54.093,47.086,-53.971,47.262,-53.869,47.387,-53.85,47.44,-53.878,47.464,-53.901,47.509,-53.94,47.645,-53.989,47.756,-54.047,47.806,-54.192,47.86,-54.218,47.847,-54.234,47.772,-54.405,47.556,-54.434,47.462,-54.456,47.428,-54.488,47.404,-54.563,47.375,-54.542,47.425,-54.463,47.536,-54.474,47.547,-54.575,47.458,-54.651,47.408,-54.745,47.395,-54.802,47.399,-54.857,47.385,-55.09,47.174,-55.099,47.104,-55.14,47.046,-55.255,46.942,-55.316,46.906,-55.401,46.899,-55.479,46.917,-55.531,46.914,-55.652,46.881,-55.789,46.867,-55.845,46.874,-55.881,46.887,-55.95,46.928,-55.958,46.956,-55.954,46.973,-55.919,47.017,-55.838,47.072,-55.772,47.092,-55.61,47.12,-55.492,47.161,-55.401,47.221,-55.361,47.259,-55.191,47.449,-54.976,47.516,-54.87,47.571,-54.795,47.64,-54.785,47.665,-54.891,47.629,-54.946,47.621,-55.035,47.634,-55.075,47.658,-55.197,47.65,-55.366,47.661,-55.391,47.643,-55.413,47.55,-55.435,47.501,-55.461,47.485,-55.499,47.475,-55.576,47.465,-55.775,47.498,-55.811,47.516,-55.862,47.53,-56.081,47.5,-56.127,47.503,-56.084,47.525,-55.867,47.592,-55.844,47.788,-55.858,47.819,-55.918,47.792,-56.02,47.764,-56.09,47.772,-56.121,47.789,-56.151,47.775,-56.221,47.671,-56.263,47.658,-56.326,47.654,-56.46,47.617,-56.722,47.592,-56.774,47.565,-56.952,47.574,-57.473,47.631,-57.66,47.625,-57.884,47.66,-57.926,47.675,-58.239,47.669,-58.333,47.677,-58.327,47.72,-58.337,47.731,-58.428,47.683,-58.509,47.653,-58.613,47.626,-58.941,47.58,-59.117,47.571,-59.219,47.603,-59.26,47.634,-59.321,47.737,-59.362,47.866,-59.362,47.889,-59.341,47.934,-59.272,47.996,-58.961,48.159,-58.711,48.325,-58.605,48.411,-58.503,48.442,-58.336,48.514,-58.33,48.522,-58.492,48.513,-58.606,48.533,-58.723,48.541,-58.944,48.522,-59.167,48.522,-59.168,48.558,-59.063,48.628,-58.842,48.746,-58.819,48.747,-58.887,48.692,-58.906,48.65,-58.877,48.623,-58.843,48.605,-58.716,48.598,-58.687,48.622,-58.642,48.749,-58.546,48.897,-58.494,49.003,-58.404,49.084,-58.359,49.097,-58.319,49.081,-58.186,49.062,-58.05,48.988,-58.006,48.981,-57.991,48.988,-58.041,49.01,-58.082,49.045,-58.099,49.077,-58.049,49.18,-57.991,49.209,-57.98,49.23,-58.097,49.23,-58.191,49.259,-58.219,49.305,-58.213,49.387,-58.183,49.435,-58.107,49.5,-58.016,49.542,-57.961,49.532,-57.856,49.474,-57.791,49.49,-57.799,49.509,-57.897,49.6,-57.929,49.668,-57.926,49.701,-57.712,50.025,-57.608,50.199,-57.466,50.464,-57.433,50.506,-57.36,50.584,-57.331,50.605,-57.237,50.605,-57.18,50.615,-57.264,50.649,-57.294,50.673,-57.298,50.699,-57.275,50.725,-57.242,50.745,-57.132,50.787,-57.053,50.857,-57.006,50.94,-57.013,50.968,-57.037,50.996,-57.036,51.011,-56.976,51.028,-56.825,51.126,-56.805,51.144,-56.75,51.275,-56.682,51.333,-56.619,51.362,-56.518,51.399,-56.207,51.489,-56.026,51.568,-55.902,51.564,-55.866,51.508,-55.69,51.471,-55.66,51.511,-55.701,51.559,-55.666,51.579,-55.522,51.596,-55.496,51.59,-55.453,51.562,-55.459,51.537]],[[-86.589,71.011,-86.55,70.989,-86.321,71.017,-86.127,71.049,-85.825,71.126,-85.644,71.152,-85.095,71.152,-85.002,71.137,-85.043,71.092,-85.066,71.079,-85.048,71.059,-84.988,71.032,-84.87,71.002,-84.824,71.029,-84.79,71.093,-84.709,71.359,-84.674,71.439,-84.658,71.515,-84.66,71.586,-84.699,71.631,-84.84,71.659,-85.032,71.654,-85.131,71.661,-85.25,71.675,-85.339,71.697,-85.397,71.727,-85.512,71.817,-85.596,71.866,-85.813,71.956,-85.912,71.987,-85.862,72.022,-85.665,72.063,-85.546,72.102,-85.406,72.215,-85.322,72.233,-85.019,72.218,-84.608,72.129,-84.352,72.053,-84.284,72.044,-84.282,72.058,-84.347,72.094,-84.643,72.19,-84.778,72.259,-84.842,72.308,-84.811,72.33,-84.645,72.351,-84.623,72.377,-84.849,72.406,-84.964,72.406,-85.057,72.384,-85.156,72.383,-85.341,72.422,-85.391,72.444,-85.498,72.511,-85.554,72.569,-85.616,72.605,-85.638,72.633,-85.65,72.722,-85.645,72.774,-85.619,72.819,-85.575,72.856,-85.455,72.925,-85.388,72.945,-85.262,72.954,-84.99,72.92,-84.257,72.797,-84.274,72.836,-85.094,73.003,-85.384,73.045,-85.455,73.105,-85.018,73.335,-84.616,73.39,-84.416,73.456,-84.089,73.459,-83.782,73.417,-83.777,73.428,-83.915,73.508,-83.904,73.528,-83.73,73.576,-83.41,73.632,-83.02,73.676,-82.943,73.699,-82.843,73.715,-82.66,73.73,-82.203,73.736,-81.946,73.73,-81.605,73.696,-81.406,73.635,-81.344,73.598,-81.238,73.48,-81.152,73.314,-81.025,73.245,-80.822,73.207,-80.681,73.166,-80.603,73.121,-80.583,73.065,-80.619,72.997,-80.592,72.928,-80.501,72.857,-80.431,72.816,-80.277,72.77,-80.275,72.746,-80.323,72.717,-80.424,72.679,-80.675,72.559,-80.999,72.426,-81.229,72.312,-81.241,72.278,-80.761,72.457,-80.611,72.451,-80.605,72.426,-80.702,72.338,-80.821,72.26,-80.941,72.21,-80.919,72.191,-80.691,72.103,-80.733,72.089,-80.843,72.096,-80.888,72.088,-80.921,72.072,-80.941,72.048,-80.943,72.014,-80.925,71.971,-80.927,71.938,-80.948,71.917,-80.925,71.908,-80.858,71.911,-80.802,71.929,-80.705,71.988,-80.386,72.149,-80.182,72.209,-80.116,72.214,-79.928,72.175,-79.884,72.177,-80.091,72.301,-80.109,72.332,-80.067,72.378,-80.042,72.394,-79.927,72.428,-79.831,72.446,-79.778,72.439,-79.693,72.376,-79.654,72.332,-79.584,72.315,-79.427,72.337,-79.323,72.391,-79.194,72.356,-79,72.272,-79.018,72.188,-79.018,72.104,-79.008,72.043,-78.776,71.93,-78.614,71.881,-78.585,71.881,-78.589,71.898,-78.623,71.935,-78.711,71.972,-78.791,72.03,-78.863,72.101,-78.82,72.265,-78.699,72.351,-78.582,72.329,-78.429,72.28,-78.307,72.275,-78.116,72.28,-77.726,72.18,-77.517,72.178,-77.536,72.219,-77.694,72.238,-77.926,72.294,-78.287,72.36,-78.453,72.435,-78.484,72.471,-78.479,72.509,-78.459,72.542,-78.422,72.572,-78.35,72.6,-78.001,72.688,-77.753,72.725,-77.567,72.737,-77.255,72.736,-76.894,72.721,-76.698,72.695,-76.473,72.633,-76.189,72.572,-76.087,72.561,-75.969,72.563,-75.833,72.577,-75.704,72.572,-75.294,72.481,-75.186,72.434,-75.12,72.378,-75.071,72.323,-75.04,72.27,-75.053,72.226,-75.394,72.04,-75.543,72.008,-75.641,71.937,-75.787,71.803,-75.911,71.731,-75.923,71.717,-75.897,71.714,-75.822,71.746,-75.693,71.839,-75.6,71.918,-75.428,71.984,-75.148,72.063,-74.903,72.1,-74.695,72.097,-74.52,72.086,-74.377,72.067,-74.293,72.051,-74.266,72.038,-74.209,71.979,-74.213,71.939,-74.248,71.894,-74.316,71.843,-74.621,71.786,-74.789,71.742,-74.893,71.726,-75.205,71.709,-75.191,71.692,-74.959,71.667,-74.701,71.676,-74.707,71.647,-74.829,71.571,-74.868,71.505,-74.834,71.451,-74.841,71.407,-74.931,71.314,-75.035,71.231,-74.996,71.218,-74.759,71.338,-74.695,71.469,-74.6,71.585,-74.488,71.648,-74.404,71.673,-74.139,71.682,-73.992,71.75,-73.867,71.771,-73.814,71.771,-73.707,71.746,-73.714,71.72,-73.869,71.599,-74.197,71.404,-74.063,71.426,-73.973,71.473,-73.851,71.519,-73.713,71.588,-73.622,71.526,-73.482,71.479,-73.398,71.373,-73.262,71.322,-73.181,71.283,-73.192,71.35,-73.31,71.484,-73.278,71.538,-73.187,71.565,-72.902,71.678,-72.703,71.64,-72.581,71.607,-72.519,71.616,-72.365,71.611,-72.117,71.593,-71.875,71.561,-71.641,71.516,-71.46,71.464,-71.333,71.403,-71.256,71.362,-71.229,71.339,-71.187,71.279,-71.219,71.239,-71.397,71.147,-71.495,71.105,-71.593,71.086,-71.856,71.105,-71.938,71.094,-72.024,71.065,-72.298,70.939,-72.449,70.884,-72.598,70.849,-72.633,70.831,-72.313,70.833,-72.224,70.87,-72.15,70.941,-72.009,71.013,-71.743,71.047,-71.371,70.975,-71.186,70.978,-71.045,71.05,-70.888,71.099,-70.826,71.109,-70.792,71.103,-70.673,71.052,-70.636,71.007,-70.639,70.902,-70.655,70.871,-70.762,70.792,-71.022,70.674,-71.192,70.63,-71.38,70.606,-71.586,70.566,-71.658,70.534,-71.729,70.488,-71.8,70.457,-71.89,70.432,-71.772,70.394,-71.727,70.395,-71.684,70.418,-71.565,70.506,-71.477,70.544,-71.427,70.552,-71.375,70.548,-71.325,70.531,-71.276,70.5,-71.28,70.425,-71.429,70.128,-71.405,70.129,-71.313,70.209,-71.045,70.519,-70.98,70.581,-70.851,70.644,-70.561,70.738,-70.337,70.788,-70.085,70.83,-69.95,70.845,-69.796,70.835,-69.696,70.786,-69.56,70.777,-69.395,70.789,-69.289,70.783,-69.169,70.764,-69.066,70.728,-68.891,70.687,-68.496,70.61,-68.447,70.594,-68.401,70.565,-68.358,70.523,-68.364,70.481,-68.417,70.44,-68.483,70.415,-68.561,70.406,-68.643,70.383,-68.794,70.324,-68.843,70.314,-69.079,70.289,-69.299,70.277,-69.436,70.253,-69.699,70.189,-70.061,70.071,-70.058,70.043,-69.913,70.029,-69.796,70.047,-69.635,70.129,-69.483,70.16,-69.246,70.185,-68.919,70.207,-68.778,70.204,-68.753,70.199,-68.735,70.18,-68.723,70.146,-68.777,70.101,-68.839,70.08,-69.008,69.979,-68.897,69.953,-68.744,69.941,-68.657,69.968,-68.578,70.03,-68.489,70.065,-68.391,70.072,-68.305,70.087,-68.231,70.112,-68.21,70.128,-68.319,70.161,-68.327,70.18,-68.283,70.228,-68.204,70.281,-68.121,70.315,-68.059,70.317,-67.855,70.282,-67.716,70.22,-67.364,70.034,-67.318,69.998,-67.196,69.861,-67.173,69.799,-67.193,69.757,-67.222,69.731,-67.259,69.721,-67.337,69.721,-67.806,69.777,-68.02,69.77,-68.114,69.754,-68.189,69.731,-68.248,69.701,-68.29,69.665,-68.372,69.644,-68.67,69.644,-68.837,69.624,-69.125,69.575,-69.228,69.547,-69.251,69.512,-69.075,69.518,-68.785,69.564,-68.513,69.577,-68.058,69.476,-67.908,69.46,-67.825,69.475,-67.725,69.479,-67.361,69.473,-67.237,69.46,-67.053,69.421,-66.771,69.337,-66.717,69.312,-66.685,69.286,-66.676,69.258,-66.679,69.191,-66.707,69.168,-66.803,69.153,-67.208,69.171,-67.332,69.185,-67.484,69.167,-67.607,69.173,-67.765,69.2,-67.938,69.248,-68.198,69.203,-68.406,69.232,-68.619,69.206,-69.041,69.098,-68.993,69.079,-68.416,69.172,-68.304,69.166,-68.121,69.133,-67.833,69.066,-67.752,69.039,-67.751,68.934,-67.795,68.863,-67.883,68.784,-68.016,68.795,-68.324,68.844,-68.45,68.851,-68.543,68.843,-68.667,68.811,-68.725,68.81,-69.219,68.873,-69.33,68.876,-69.343,68.869,-69.319,68.857,-68.871,68.76,-68.541,68.749,-68.333,68.733,-68.21,68.703,-68.152,68.681,-68.148,68.616,-68.038,68.551,-67.938,68.524,-67.875,68.523,-67.766,68.547,-67.656,68.551,-67.567,68.534,-67.456,68.498,-67.321,68.488,-67.202,68.466,-67.111,68.461,-66.854,68.472,-66.743,68.458,-66.714,68.446,-66.762,68.425,-66.997,68.374,-67.033,68.326,-66.9,68.264,-66.831,68.216,-66.834,68.18,-66.905,68.098,-66.923,68.066,-66.9,68.063,-66.729,68.129,-66.702,68.121,-66.685,68.029,-66.663,68.034,-66.605,68.11,-66.631,68.211,-66.531,68.25,-66.212,68.28,-66.266,68.123,-66.275,68.041,-66.414,67.904,-66.53,67.86,-66.526,67.851,-66.444,67.834,-66.392,67.832,-66.343,67.853,-66.225,67.959,-65.986,68.069,-65.942,68.071,-65.944,68.031,-65.975,67.957,-65.864,67.923,-65.759,67.957,-65.702,67.987,-65.569,67.982,-65.509,67.968,-65.491,67.936,-65.552,67.799,-65.541,67.766,-65.401,67.675,-65.387,67.68,-65.413,67.724,-65.442,67.832,-65.415,67.879,-65.3,67.94,-65.064,68.026,-64.977,68.043,-64.922,68.032,-64.835,67.99,-64.863,67.965,-64.956,67.939,-65.026,67.892,-65.071,67.824,-65.021,67.788,-64.83,67.784,-64.638,67.84,-64.528,67.813,-64.396,67.74,-64.156,67.623,-64.019,67.655,-63.85,67.566,-64.077,67.496,-64.008,67.347,-64.303,67.353,-64.469,67.342,-64.58,67.355,-64.7,67.351,-64.589,67.316,-64.376,67.301,-64.356,67.256,-64.189,67.257,-64.063,67.266,-63.836,67.264,-63.824,67.316,-63.676,67.345,-63.592,67.378,-63.521,67.358,-63.316,67.336,-63.04,67.235,-63.162,67.174,-63.195,67.117,-63.236,67.069,-63.258,67.025,-63.307,66.994,-63.702,66.822,-63.636,66.821,-63.469,66.862,-63.144,66.924,-62.962,66.949,-62.833,66.933,-62.768,66.932,-62.71,66.954,-62.603,66.929,-62.38,66.905,-62.124,67.047,-61.969,67.019,-61.824,66.932,-61.515,66.778,-61.353,66.689,-61.3,66.649,-61.307,66.609,-61.453,66.567,-61.528,66.558,-61.724,66.638,-61.904,66.678,-62.014,66.674,-62.123,66.643,-62.089,66.626,-61.653,66.503,-61.576,66.413,-61.571,66.373,-61.863,66.313,-61.956,66.309,-62.158,66.338,-62.277,66.392,-62.375,66.411,-62.51,66.417,-62.553,66.407,-62.406,66.316,-62.42,66.289,-62.496,66.271,-62.534,66.227,-62.242,66.148,-62.024,66.068,-61.992,66.035,-62.139,66.011,-62.244,66.006,-62.468,66.017,-62.59,66.034,-62.624,66.016,-62.497,65.974,-62.448,65.946,-62.41,65.906,-62.388,65.868,-62.382,65.833,-62.486,65.804,-62.61,65.724,-62.659,65.64,-62.772,65.632,-62.817,65.648,-62.969,65.622,-63.169,65.657,-63.241,65.696,-63.459,65.853,-63.464,65.835,-63.41,65.756,-63.421,65.709,-63.652,65.674,-63.651,65.661,-63.509,65.636,-63.337,65.617,-63.364,65.543,-63.363,65.23,-63.402,65.118,-63.486,65.021,-63.607,64.928,-63.737,64.989,-63.789,65.051,-63.833,65.083,-63.896,65.109,-63.976,65.121,-64.061,65.122,-64.152,65.066,-64.25,65.114,-64.346,65.172,-64.31,65.325,-64.27,65.401,-64.286,65.4,-64.34,65.364,-64.47,65.253,-64.555,65.117,-64.665,65.169,-64.765,65.234,-64.847,65.3,-64.98,65.375,-65.108,65.464,-65.176,65.568,-65.207,65.59,-65.282,65.677,-65.311,65.702,-65.337,65.71,-65.402,65.764,-65.378,65.822,-65.277,65.891,-65.185,65.94,-65.032,65.989,-64.854,66.016,-64.773,66.079,-64.673,66.193,-64.564,66.272,-64.445,66.317,-64.504,66.325,-64.655,66.287,-64.761,66.231,-64.887,66.137,-65.004,66.078,-65.305,66.008,-65.416,65.995,-65.544,65.987,-65.826,65.997,-65.891,66.02,-65.857,66.086,-65.656,66.205,-65.688,66.213,-65.759,66.171,-65.856,66.142,-65.94,66.127,-66.064,66.133,-66.209,66.206,-66.277,66.229,-66.419,66.254,-66.477,66.28,-66.712,66.46,-66.76,66.508,-66.787,66.556,-66.863,66.595,-66.986,66.627,-67.015,66.622,-66.97,66.582,-66.969,66.547,-67.077,66.525,-67.19,66.533,-67.307,66.57,-67.318,66.52,-67.192,66.433,-67.19,66.322,-67.225,66.31,-67.311,66.304,-67.369,66.317,-67.56,66.4,-67.741,66.458,-67.868,66.49,-67.883,66.467,-67.801,66.367,-67.704,66.269,-67.547,66.187,-67.297,66.09,-67.183,66.034,-67.273,65.956,-67.35,65.93,-67.399,65.922,-67.551,65.922,-67.828,65.965,-67.958,66.014,-68.147,66.13,-68.46,66.249,-68.528,66.249,-68.749,66.2,-68.714,66.192,-68.572,66.189,-68.467,66.173,-68.217,66.079,-68.198,66.039,-68.261,65.995,-68.257,65.939,-68.187,65.871,-68.115,65.828,-67.968,65.797,-67.894,65.793,-67.866,65.774,-67.954,65.623,-67.962,65.582,-67.937,65.565,-67.906,65.563,-67.717,65.625,-67.638,65.64,-67.57,65.644,-67.49,65.626,-67.4,65.588,-67.346,65.549,-67.33,65.509,-67.303,65.483,-67.118,65.44,-67.135,65.421,-67.326,65.357,-67.337,65.347,-67.298,65.342,-67.178,65.304,-67.067,65.244,-66.999,65.173,-66.985,65.138,-66.986,65.105,-66.97,65.085,-66.912,65.081,-66.887,65.094,-66.861,65.092,-66.831,65.074,-66.8,65.02,-66.733,64.86,-66.697,64.815,-66.677,64.814,-66.667,64.974,-66.635,65,-66.518,64.972,-66.345,64.91,-66.224,64.854,-66.21,64.828,-66.302,64.778,-66.282,64.755,-66.215,64.722,-66.152,64.735,-66.108,64.791,-66.03,64.847,-65.939,64.886,-65.768,64.854,-65.627,64.771,-65.605,64.742,-65.513,64.706,-65.432,64.726,-65.275,64.632,-65.349,64.589,-65.513,64.526,-65.529,64.505,-65.49,64.51,-65.179,64.51,-65.095,64.485,-65.075,64.437,-65.213,64.303,-65.34,64.315,-65.507,64.318,-65.594,64.311,-65.58,64.294,-65.348,64.232,-65.282,64.182,-65.193,64.13,-65.15,64.087,-65.151,64.068,-65.187,64.038,-65.17,64.028,-65.011,64.009,-64.912,64.026,-64.788,64.033,-64.678,64.028,-64.67,64.01,-64.686,63.961,-64.798,63.916,-64.768,63.905,-64.637,63.918,-64.576,63.897,-64.498,63.79,-64.411,63.706,-64.482,63.687,-64.562,63.68,-64.55,63.573,-64.499,63.463,-64.498,63.358,-64.514,63.264,-64.587,63.243,-64.665,63.245,-64.696,63.269,-64.886,63.549,-64.933,63.599,-64.99,63.643,-65.192,63.764,-65.184,63.745,-65.134,63.689,-65.089,63.606,-65.031,63.44,-65.005,63.333,-65.017,63.293,-65.058,63.283,-65.069,63.263,-65.049,63.235,-64.895,63.126,-64.82,63.06,-64.767,62.992,-64.718,62.946,-64.672,62.922,-64.684,62.902,-64.752,62.887,-64.869,62.88,-64.923,62.889,-65.133,62.952,-65.163,62.933,-65.047,62.701,-65.05,62.646,-65.108,62.626,-65.18,62.649,-65.266,62.715,-65.397,62.788,-65.572,62.869,-65.74,62.932,-65.78,62.93,-65.806,62.912,-65.834,62.909,-65.864,62.921,-65.92,62.969,-65.979,63.001,-66.224,63.107,-66.249,63.108,-66.226,63.076,-66.201,63.006,-66.229,62.991,-66.293,62.997,-66.414,63.027,-66.496,63.097,-66.6,63.219,-66.655,63.265,-66.66,63.235,-66.631,63.119,-66.636,63.08,-66.697,63.07,-66.723,63.08,-66.749,63.111,-66.773,63.162,-66.831,63.201,-66.923,63.228,-66.975,63.256,-67,63.305,-67.018,63.317,-67.18,63.305,-67.261,63.341,-67.495,63.481,-67.709,63.634,-67.844,63.715,-67.893,63.734,-67.821,63.635,-67.743,63.489,-67.723,63.423,-67.759,63.42,-67.838,63.449,-68.244,63.637,-68.494,63.725,-68.633,63.741,-68.859,63.752,-68.911,63.703,-68.789,63.595,-68.671,63.514,-68.555,63.459,-68.374,63.352,-68.208,63.215,-68.141,63.172,-67.915,63.114,-67.797,63.098,-67.676,63.094,-67.665,63.073,-67.724,63.034,-67.737,63.01,-67.468,62.948,-67.367,62.914,-67.269,62.858,-67.213,62.844,-66.98,62.701,-66.922,62.678,-66.714,62.632,-66.645,62.602,-66.531,62.51,-66.459,62.463,-66.357,62.352,-66.281,62.303,-66.095,62.246,-66.016,62.23,-65.98,62.209,-66.004,62.158,-66.027,62.137,-66.133,62.102,-66.116,62.054,-66.056,61.967,-66.059,61.914,-66.124,61.893,-66.257,61.868,-66.324,61.87,-66.425,61.891,-66.551,61.926,-66.803,62.013,-67.181,62.073,-67.322,62.105,-67.369,62.134,-67.44,62.151,-68.379,62.235,-68.536,62.256,-68.634,62.281,-68.724,62.319,-69.082,62.405,-69.126,62.424,-69.366,62.572,-69.545,62.745,-69.605,62.768,-69.8,62.79,-69.962,62.776,-70.071,62.757,-70.236,62.763,-70.344,62.792,-70.571,62.869,-70.801,62.91,-71.002,62.978,-71.106,63.002,-71.096,63.02,-70.946,63.121,-70.993,63.119,-71.254,63.043,-71.347,63.066,-71.501,63.126,-71.617,63.187,-71.855,63.355,-71.992,63.416,-71.973,63.43,-71.819,63.435,-71.697,63.43,-71.614,63.444,-71.456,63.512,-71.387,63.555,-71.381,63.58,-71.513,63.587,-71.542,63.599,-71.566,63.627,-71.627,63.663,-71.725,63.706,-71.838,63.725,-72.223,63.709,-72.29,63.728,-72.289,63.757,-72.213,63.839,-72.172,63.872,-72.159,63.89,-72.174,63.893,-72.226,63.891,-72.45,63.818,-72.498,63.823,-72.586,63.901,-72.639,63.989,-72.678,64.02,-72.73,64.03,-72.913,64.117,-73.174,64.282,-73.27,64.333,-73.377,64.38,-73.455,64.399,-73.444,64.423,-73.278,64.56,-73.271,64.583,-73.413,64.574,-73.627,64.603,-73.728,64.568,-73.793,64.566,-73.868,64.585,-73.91,64.578,-73.95,64.466,-73.981,64.438,-74.026,64.423,-74.065,64.425,-74.099,64.444,-74.098,64.47,-74.13,64.608,-74.205,64.628,-74.416,64.633,-74.461,64.645,-74.512,64.67,-74.556,64.717,-74.593,64.786,-74.634,64.824,-74.681,64.831,-74.719,64.825,-74.748,64.807,-74.813,64.796,-74.916,64.792,-74.919,64.766,-74.823,64.717,-74.73,64.647,-74.64,64.557,-74.695,64.497,-74.894,64.466,-75.067,64.457,-75.215,64.469,-75.328,64.49,-75.488,64.541,-75.715,64.524,-75.767,64.392,-75.815,64.385,-76.032,64.388,-76.118,64.376,-76.407,64.303,-76.495,64.293,-76.562,64.302,-76.627,64.284,-76.724,64.242,-76.856,64.238,-77.024,64.271,-77.166,64.285,-77.283,64.28,-77.403,64.3,-77.527,64.344,-77.628,64.363,-77.76,64.36,-77.791,64.367,-77.985,64.461,-78.045,64.499,-78.175,64.618,-78.198,64.665,-78.201,64.715,-78.19,64.752,-78.145,64.808,-78.096,64.939,-78.055,64.983,-77.995,65.023,-77.876,65.073,-77.447,65.162,-77.361,65.197,-77.364,65.22,-77.461,65.328,-77.46,65.356,-77.428,65.372,-77.358,65.435,-77.327,65.453,-77.251,65.463,-77.094,65.431,-76.959,65.418,-76.779,65.414,-76.482,65.37,-76.067,65.285,-75.828,65.227,-75.648,65.141,-75.52,65.056,-75.502,65.013,-75.561,64.947,-75.591,64.928,-75.589,64.905,-75.556,64.879,-75.452,64.842,-75.427,64.856,-75.435,64.901,-75.414,64.939,-75.363,64.969,-75.357,65.009,-75.397,65.058,-75.446,65.1,-75.505,65.135,-75.773,65.257,-75.799,65.298,-75.709,65.316,-75.317,65.275,-75.166,65.284,-75.109,65.331,-75.048,65.364,-74.982,65.381,-74.85,65.389,-74.665,65.367,-74.575,65.364,-74.495,65.372,-74.391,65.398,-74.237,65.484,-74.138,65.503,-73.99,65.517,-73.878,65.519,-73.675,65.484,-73.551,65.485,-73.561,65.543,-73.643,65.653,-73.746,65.767,-73.826,65.805,-74.033,65.877,-74.276,66.013,-74.401,66.097,-74.434,66.139,-74.416,66.167,-74.375,66.208,-73.934,66.358,-73.584,66.507,-73.431,66.583,-73.357,66.636,-73.281,66.675,-73.201,66.699,-73.033,66.728,-72.985,66.765,-72.975,66.829,-72.947,66.883,-72.789,67.031,-72.668,67.07,-72.485,67.098,-72.365,67.133,-72.22,67.254,-72.234,67.284,-72.301,67.307,-72.353,67.342,-72.576,67.659,-72.725,67.812,-72.904,67.945,-73.063,68.107,-73.328,68.267,-73.331,68.309,-73.284,68.357,-73.307,68.368,-73.58,68.298,-73.644,68.295,-73.749,68.325,-73.821,68.363,-73.879,68.429,-73.873,68.464,-73.834,68.497,-73.783,68.578,-73.781,68.619,-73.798,68.659,-73.822,68.686,-73.852,68.701,-73.935,68.711,-74.073,68.715,-74.118,68.701,-73.966,68.579,-73.989,68.549,-74.183,68.535,-74.27,68.541,-74.35,68.556,-74.422,68.58,-74.648,68.708,-74.696,68.756,-74.681,68.79,-74.7,68.808,-74.746,68.797,-74.808,68.796,-74.893,68.808,-74.91,68.823,-74.752,68.892,-74.743,68.913,-74.816,68.936,-74.925,68.941,-74.954,68.961,-74.917,68.983,-74.769,69.021,-74.717,69.046,-74.805,69.064,-74.855,69.066,-74.954,69.025,-75.104,68.941,-75.213,68.909,-75.363,68.948,-75.457,68.961,-75.523,68.953,-75.623,68.888,-75.842,68.84,-76.235,68.728,-76.403,68.692,-76.585,68.699,-76.619,68.721,-76.616,68.76,-76.604,68.792,-76.582,68.816,-76.575,68.847,-76.588,68.974,-76.557,69.009,-76.495,69.03,-76.381,69.052,-76.089,69.026,-75.954,69.031,-75.859,69.06,-75.763,69.103,-75.668,69.159,-75.648,69.213,-75.749,69.3,-75.787,69.319,-76.046,69.386,-76.19,69.411,-76.316,69.422,-76.408,69.441,-76.465,69.469,-76.52,69.517,-76.525,69.549,-76.516,69.591,-76.463,69.62,-76.231,69.653,-76.234,69.662,-76.424,69.687,-76.513,69.684,-76.59,69.656,-76.687,69.591,-76.742,69.573,-76.916,69.611,-77.02,69.617,-77.09,69.635,-77.129,69.653,-77.105,69.671,-77.019,69.689,-76.869,69.745,-76.859,69.775,-76.962,69.825,-77.016,69.836,-77.232,69.855,-77.494,69.836,-77.592,69.846,-77.635,69.9,-77.663,69.966,-77.675,70.042,-77.722,70.171,-77.774,70.239,-77.843,70.247,-78.157,70.219,-78.231,70.219,-78.283,70.229,-78.491,70.316,-78.575,70.346,-78.621,70.353,-78.773,70.445,-78.831,70.463,-78.9,70.509,-78.98,70.581,-79.066,70.604,-79.16,70.575,-79.253,70.535,-79.347,70.482,-79.397,70.437,-79.405,70.401,-79.347,70.372,-79.018,70.325,-78.934,70.294,-78.863,70.242,-78.81,70.179,-78.775,70.104,-78.778,70.048,-78.819,70.01,-78.89,69.977,-79.093,69.925,-79.303,69.895,-79.515,69.888,-79.616,69.895,-80.162,69.996,-80.26,69.997,-80.387,70.01,-80.67,70.052,-80.826,70.057,-81.098,70.091,-81.56,70.111,-81.652,70.095,-81.529,70.048,-81.422,70.025,-81.329,70.024,-81.197,69.983,-81.024,69.9,-80.925,69.851,-80.843,69.792,-80.84,69.771,-80.922,69.731,-81.565,69.943,-81.958,69.869,-82.139,69.841,-82.294,69.837,-82.488,69.866,-82.925,69.968,-83.091,70.004,-83.15,70.009,-83.531,69.965,-83.859,69.963,-84.522,70.005,-84.765,70.034,-84.829,70.063,-84.909,70.078,-85.053,70.078,-85.432,70.111,-85.78,70.037,-86.198,70.105,-86.322,70.145,-86.361,70.173,-86.483,70.289,-86.5,70.35,-86.465,70.406,-86.431,70.445,-86.397,70.465,-86.624,70.401,-86.704,70.391,-86.809,70.388,-87.122,70.412,-87.172,70.4,-87.156,70.377,-87.074,70.345,-87.063,70.325,-87.238,70.31,-87.502,70.326,-87.618,70.319,-87.67,70.31,-87.789,70.258,-87.838,70.247,-87.901,70.252,-88.178,70.369,-88.402,70.442,-88.663,70.471,-88.783,70.494,-88.848,70.523,-89.208,70.76,-89.258,70.811,-89.372,70.996,-89.41,71.036,-89.456,71.062,-89.366,71.067,-89.025,71.045,-88.696,71.046,-88.517,71.031,-88.309,70.984,-88.039,70.951,-87.845,70.944,-87.534,70.957,-87.182,70.988,-87.14,71.012,-87.369,71.053,-87.572,71.108,-87.76,71.179,-87.872,71.209,-88.061,71.227,-88.59,71.24,-89.079,71.288,-89.418,71.352,-89.693,71.423,-89.805,71.462,-89.846,71.492,-89.889,71.586,-89.934,71.743,-89.977,71.848,-90.02,71.902,-90.025,71.949,-89.931,72.049,-89.664,72.158,-89.657,72.175,-89.711,72.18,-89.823,72.208,-89.859,72.248,-89.873,72.313,-89.874,72.367,-89.862,72.412,-89.817,72.468,-89.702,72.568,-89.536,72.69,-89.358,72.804,-89.327,72.842,-89.311,72.943,-89.288,73.017,-89.263,73.069,-89.225,73.108,-89.115,73.182,-88.977,73.252,-88.761,73.312,-88.743,73.335,-88.74,73.365,-88.727,73.388,-88.705,73.403,-88.17,73.595,-87.926,73.673,-87.72,73.723,-87.472,73.759,-86.769,73.834,-86.406,73.855,-85.951,73.85,-85.11,73.808,-85.009,73.779,-84.984,73.764,-84.947,73.722,-84.975,73.695,-85.204,73.604,-85.494,73.528,-85.682,73.461,-86.001,73.313,-86.086,73.26,-86.481,72.96,-86.575,72.911,-86.629,72.871,-86.668,72.763,-86.656,72.724,-86.595,72.661,-86.38,72.525,-86.323,72.461,-86.324,72.402,-86.348,72.262,-86.351,72.191,-86.341,72.123,-86.297,72.026,-86.218,71.899,-86.036,71.771,-85.75,71.641,-85.537,71.555,-85.327,71.492,-85.079,71.398,-85.023,71.353,-85.138,71.303,-85.405,71.227,-85.757,71.194,-85.945,71.163,-86.179,71.096,-86.473,71.043,-86.589,71.011]],[[-61.801,49.094,-62.22,49.079,-62.553,49.141,-62.8,49.171,-63.042,49.225,-63.566,49.399,-63.626,49.46,-63.676,49.534,-63.777,49.602,-63.885,49.658,-64.44,49.828,-64.485,49.887,-64.373,49.926,-64.244,49.944,-64.131,49.942,-63.76,49.875,-63.292,49.817,-63.089,49.773,-62.859,49.705,-62.633,49.624,-62.133,49.407,-62.043,49.39,-61.817,49.284,-61.736,49.204,-61.696,49.139,-61.746,49.106,-61.801,49.094]],[[-63.811,46.469,-63.784,46.455,-63.737,46.481,-63.681,46.562,-63.534,46.541,-63.456,46.504,-63.413,46.512,-63.369,46.508,-63.286,46.46,-63.129,46.422,-62.964,46.428,-62.712,46.45,-62.682,46.459,-62.423,46.478,-62.164,46.487,-62.074,46.466,-62.041,46.446,-62.024,46.422,-62.172,46.355,-62.32,46.278,-62.526,46.203,-62.552,46.166,-62.539,46.098,-62.543,46.029,-62.503,46.023,-62.478,46,-62.531,45.977,-62.743,45.967,-62.805,45.973,-62.878,46.001,-62.904,46.068,-62.995,46.058,-63.022,46.067,-62.895,46.124,-62.953,46.195,-63.015,46.19,-63.056,46.224,-63.053,46.27,-62.995,46.292,-62.978,46.316,-63.057,46.295,-63.117,46.253,-63.195,46.237,-63.271,46.2,-63.153,46.188,-63.213,46.16,-63.277,46.153,-63.569,46.209,-63.641,46.23,-63.732,46.289,-63.801,46.367,-63.763,46.37,-63.751,46.384,-63.759,46.398,-63.861,46.408,-64.02,46.405,-64.111,46.425,-64.107,46.562,-64.136,46.6,-64.236,46.631,-64.388,46.641,-64.403,46.692,-64.355,46.769,-64.28,46.836,-64.223,46.901,-64.157,46.955,-63.994,47.062,-63.997,46.982,-63.981,46.913,-64.088,46.775,-63.903,46.639,-63.879,46.609,-63.864,46.572,-63.876,46.539,-63.906,46.509,-63.834,46.494,-63.811,46.469]],[[-82,62.954,-81.961,62.926,-81.949,62.884,-81.964,62.828,-81.99,62.776,-82.026,62.73,-82.114,62.652,-82.388,62.519,-82.491,62.447,-82.568,62.403,-83.016,62.21,-83.071,62.2,-83.13,62.204,-83.252,62.233,-83.377,62.238,-83.699,62.16,-83.714,62.174,-83.729,62.257,-83.761,62.304,-83.903,62.402,-83.912,62.426,-83.91,62.454,-83.899,62.476,-83.739,62.569,-83.376,62.905,-83.289,62.922,-83.111,62.884,-83.026,62.872,-82.966,62.874,-82.706,62.945,-82.46,62.936,-82.235,62.977,-82.129,62.978,-82.048,62.971,-82,62.954]],[[-79.545,62.412,-79.466,62.385,-79.336,62.294,-79.286,62.248,-79.272,62.186,-79.306,62.104,-79.324,62.026,-79.372,61.968,-79.462,61.894,-79.542,61.808,-79.611,61.71,-79.669,61.644,-79.714,61.613,-79.763,61.596,-79.816,61.595,-79.896,61.63,-80.004,61.703,-80.092,61.747,-80.205,61.777,-80.265,61.818,-80.276,61.859,-80.28,61.99,-80.275,62.055,-80.26,62.109,-80.235,62.153,-80.179,62.213,-80.022,62.343,-79.927,62.393,-79.868,62.404,-79.713,62.395,-79.65,62.398,-79.598,62.413,-79.545,62.412]],[[-75.676,68.323,-75.154,68.234,-75.103,68.202,-75.078,68.173,-75.063,68.141,-75.062,68.075,-75.073,68.049,-75.124,67.985,-75.127,67.965,-75.086,67.751,-75.091,67.635,-75.127,67.537,-75.202,67.459,-75.315,67.4,-75.4,67.367,-75.78,67.284,-76.049,67.262,-76.333,67.258,-76.694,67.236,-76.859,67.24,-76.944,67.25,-77.005,67.267,-77.076,67.32,-77.157,67.408,-77.224,67.508,-77.304,67.685,-77.306,67.706,-77.229,67.85,-77.126,67.947,-76.945,68.091,-76.74,68.231,-76.688,68.254,-76.596,68.279,-76.364,68.319,-76.173,68.309,-76.088,68.314,-75.983,68.332,-75.867,68.337,-75.676,68.323]],[[-79.537,73.654,-79.367,73.641,-78.287,73.666,-78.063,73.648,-77.382,73.537,-77.207,73.5,-77.12,73.45,-77.042,73.373,-77.005,73.356,-76.759,73.31,-76.657,73.254,-76.622,73.225,-76.57,73.159,-76.458,73.122,-76.331,73.1,-76.29,73.081,-76.31,72.998,-76.255,72.959,-76.135,72.912,-76.09,72.881,-76.183,72.843,-76.401,72.821,-77.014,72.844,-77.836,72.897,-78.314,72.882,-78.554,72.858,-79.134,72.772,-79.319,72.758,-79.501,72.756,-79.821,72.826,-79.937,72.864,-79.975,72.892,-80.052,72.977,-80.114,73.078,-80.146,73.161,-80.183,73.225,-80.293,73.246,-80.618,73.271,-80.727,73.305,-80.776,73.334,-80.824,73.381,-80.823,73.429,-80.798,73.472,-80.777,73.482,-80.736,73.483,-80.827,73.535,-80.858,73.591,-80.861,73.671,-80.849,73.721,-80.823,73.743,-80.763,73.758,-80.621,73.767,-80.412,73.765,-80.12,73.707,-79.889,73.702,-79.537,73.654]],[[-80.732,52.747,-80.802,52.734,-81.01,52.761,-81.097,52.78,-81.352,52.852,-81.839,52.958,-82.005,53.01,-82.039,53.05,-81.951,53.132,-81.901,53.166,-81.847,53.186,-81.335,53.224,-81.136,53.206,-80.9,53.037,-80.765,52.923,-80.71,52.832,-80.71,52.787,-80.732,52.747]],[[-78.827,56.145,-78.877,56.131,-78.914,56.133,-78.907,56.166,-78.857,56.232,-78.828,56.29,-78.822,56.34,-78.799,56.383,-78.762,56.421,-78.725,56.439,-78.669,56.439,-78.657,56.317,-78.673,56.26,-78.71,56.213,-78.761,56.175,-78.827,56.145]],[[-78.936,56.266,-79.018,56.165,-79.084,56.068,-79.175,55.885,-79.228,55.879,-79.274,55.922,-79.142,56.136,-79.136,56.16,-79.142,56.181,-79.182,56.212,-79.222,56.176,-79.407,55.935,-79.455,55.896,-79.495,55.875,-79.527,55.871,-79.606,55.876,-79.765,55.807,-79.497,56.093,-79.495,56.115,-79.545,56.128,-79.565,56.121,-79.781,55.941,-79.905,55.871,-79.987,55.892,-80.008,55.911,-80.001,55.932,-79.79,56.114,-79.596,56.244,-79.515,56.327,-79.482,56.404,-79.468,56.46,-79.469,56.523,-79.459,56.54,-79.448,56.537,-79.435,56.513,-79.432,56.447,-79.476,56.313,-79.512,56.247,-79.554,56.192,-79.536,56.18,-79.458,56.211,-79.393,56.276,-79.339,56.376,-79.305,56.463,-79.272,56.6,-79.261,56.596,-79.246,56.568,-79.21,56.549,-79.155,56.538,-79.124,56.52,-79.1,56.474,-79.078,56.454,-78.995,56.436,-78.963,56.422,-78.94,56.371,-78.942,56.345,-78.931,56.328,-78.907,56.32,-78.936,56.266]],[[-79.978,56.207,-80.029,56.199,-80.089,56.214,-80.057,56.287,-80.005,56.318,-79.874,56.348,-79.852,56.367,-79.81,56.377,-79.749,56.377,-79.681,56.404,-79.606,56.459,-79.58,56.466,-79.633,56.387,-79.688,56.327,-79.978,56.207]],[[-89.833,77.268,-90.095,77.21,-90.228,77.212,-90.993,77.329,-91.147,77.387,-91.177,77.426,-91.185,77.482,-91.183,77.557,-91.149,77.608,-91.109,77.626,-91.019,77.644,-90.843,77.655,-90.675,77.649,-90.423,77.628,-90.172,77.595,-89.839,77.491,-89.719,77.442,-89.694,77.378,-89.695,77.339,-89.712,77.31,-89.747,77.293,-89.833,77.268]],[[-104.558,77.142,-104.711,77.124,-105.016,77.165,-105.215,77.182,-105.38,77.254,-105.556,77.353,-105.695,77.461,-105.747,77.525,-105.848,77.563,-105.883,77.627,-106.066,77.725,-106.036,77.74,-105.863,77.754,-105.588,77.736,-105.456,77.701,-105.29,77.642,-105.074,77.548,-105.007,77.507,-104.994,77.45,-104.955,77.419,-104.77,77.413,-104.542,77.338,-104.501,77.309,-104.454,77.249,-104.457,77.221,-104.493,77.162,-104.558,77.142]],[[-98.792,79.981,-98.769,79.851,-98.79,79.785,-98.841,79.737,-98.885,79.726,-98.945,79.724,-99.218,79.762,-99.302,79.784,-99.306,79.803,-99.333,79.84,-99.516,79.887,-99.857,79.879,-100,79.884,-100.057,79.898,-100.092,79.919,-100.126,80.001,-100.12,80.03,-100.079,80.081,-100.053,80.093,-99.803,80.14,-99.731,80.144,-99.425,80.126,-99.153,80.124,-99.017,80.111,-98.895,80.082,-98.823,80.037,-98.792,79.981]],[[-105.289,72.92,-105.339,72.915,-105.434,72.938,-105.573,72.989,-105.8,73.093,-106.071,73.196,-106.113,73.258,-106.18,73.304,-106.526,73.413,-106.75,73.458,-106.922,73.48,-106.95,73.51,-106.831,73.599,-106.695,73.67,-106.614,73.696,-106.362,73.719,-105.512,73.766,-105.318,73.767,-105.114,73.744,-104.835,73.647,-104.718,73.636,-104.649,73.614,-104.587,73.578,-104.555,73.541,-104.552,73.466,-104.583,73.354,-104.622,73.311,-104.791,73.168,-104.969,73.089,-105.003,73.038,-105.075,72.997,-105.201,72.947,-105.289,72.92]],[[-102.227,76.015,-102.018,75.954,-102.008,75.939,-102.047,75.928,-102.318,75.895,-102.423,75.869,-102.511,75.808,-102.58,75.78,-102.944,75.763,-103.315,75.764,-103.245,75.823,-103.042,75.919,-103.202,75.958,-103.77,75.892,-103.985,75.933,-103.801,76.037,-103.985,76.047,-104.242,76.047,-104.406,76.108,-104.351,76.182,-104.012,76.223,-103.571,76.258,-103.098,76.311,-102.728,76.307,-102.584,76.282,-102.536,76.196,-102.49,76.095,-102.426,76.086,-102.227,76.015]],[[-104.023,76.583,-103.973,76.578,-103.821,76.598,-103.723,76.601,-103.613,76.563,-103.585,76.539,-103.19,76.477,-103.051,76.45,-103.034,76.431,-103.083,76.405,-103.2,76.371,-103.311,76.348,-103.472,76.329,-104.271,76.326,-104.358,76.335,-104.408,76.365,-104.506,76.479,-104.577,76.54,-104.603,76.583,-104.586,76.606,-104.5,76.63,-104.205,76.666,-104.075,76.666,-103.992,76.657,-103.959,76.639,-103.969,76.614,-104.023,76.583]],[[-118.328,75.58,-118.614,75.515,-118.817,75.522,-119.087,75.569,-119.306,75.585,-119.383,75.601,-119.395,75.617,-119.32,75.663,-119.227,75.699,-119.003,75.77,-118.626,75.906,-118.379,75.958,-118.137,75.994,-117.889,76.076,-117.752,76.112,-117.634,76.115,-117.513,76.099,-117.499,76.077,-117.626,75.966,-117.716,75.921,-117.891,75.805,-118.227,75.611,-118.328,75.58]],[[-113.832,77.755,-114.106,77.721,-114.287,77.721,-114.608,77.769,-114.98,77.915,-115.029,77.968,-114.89,77.977,-114.79,77.993,-114.726,78.016,-114.607,78.04,-114.33,78.078,-114.297,78.063,-114.303,78.033,-114.28,78.004,-114.181,77.998,-114.087,77.978,-113.898,77.916,-113.768,77.904,-113.721,77.89,-113.697,77.869,-113.618,77.832,-113.619,77.813,-113.726,77.776,-113.832,77.755]],[[-130.927,54.479,-130.95,54.478,-130.959,54.499,-130.953,54.542,-130.922,54.615,-130.907,54.632,-130.777,54.619,-130.758,54.614,-130.753,54.6,-130.763,54.577,-130.805,54.544,-130.927,54.479]],[[-131.029,51.962,-131.047,51.96,-131.081,51.98,-131.103,52.014,-131.117,52.101,-131.107,52.137,-131.098,52.151,-131.011,52.095,-131.029,51.962]],[[-130.236,53.959,-130.267,53.923,-130.338,53.866,-130.384,53.844,-130.407,53.856,-130.47,53.862,-130.537,53.918,-130.59,53.94,-130.625,53.941,-130.642,53.921,-130.646,53.894,-130.638,53.86,-130.644,53.845,-130.664,53.848,-130.683,53.863,-130.703,53.892,-130.707,53.921,-130.696,53.951,-130.647,53.991,-130.495,54.074,-130.448,54.089,-130.397,54.086,-130.316,54.047,-130.298,54.036,-130.236,53.959]],[[-129.849,53.168,-129.869,53.165,-129.934,53.177,-130.151,53.346,-130.306,53.407,-130.411,53.491,-130.518,53.544,-130.452,53.631,-130.395,53.62,-130.195,53.55,-130.035,53.481,-129.945,53.436,-129.755,53.245,-129.769,53.217,-129.849,53.168]],[[-130.575,54.77,-130.493,54.834,-130.313,54.946,-130.214,55.026,-130.204,54.947,-130.349,54.815,-130.535,54.749,-130.575,54.77]],[[-128.552,52.94,-128.507,52.621,-128.51,52.519,-128.577,52.452,-128.624,52.34,-128.679,52.29,-128.731,52.357,-128.736,52.468,-128.749,52.556,-128.766,52.598,-128.746,52.763,-128.77,52.751,-128.831,52.679,-128.9,52.674,-129.023,52.756,-129.085,52.822,-129.095,52.892,-129.176,52.965,-129.184,52.991,-129.178,53.018,-129.111,53.091,-129.084,53.14,-129.06,53.241,-129.033,53.28,-128.97,53.274,-128.858,53.229,-128.74,53.179,-128.633,53.113,-128.552,52.94]],[[-126.092,49.354,-126.064,49.264,-126.187,49.278,-126.23,49.296,-126.231,49.339,-126.209,49.38,-126.115,49.365,-126.092,49.354]],[[-124.154,49.531,-124.14,49.51,-124.362,49.588,-124.457,49.634,-124.494,49.667,-124.518,49.686,-124.631,49.736,-124.65,49.758,-124.623,49.775,-124.547,49.765,-124.421,49.728,-124.309,49.667,-124.154,49.531]],[[-126.641,49.606,-126.68,49.601,-126.743,49.613,-126.814,49.642,-126.939,49.718,-126.951,49.736,-126.94,49.75,-126.905,49.763,-126.897,49.783,-126.926,49.838,-126.826,49.872,-126.738,49.844,-126.698,49.808,-126.65,49.746,-126.628,49.675,-126.626,49.627,-126.641,49.606]],[[-125.184,50.097,-125.195,50.044,-125.26,50.13,-125.358,50.312,-125.345,50.354,-125.301,50.414,-125.261,50.418,-125.196,50.39,-125.14,50.34,-125.126,50.32,-125.091,50.268,-125.074,50.221,-125.113,50.163,-125.184,50.097]],[[-128.937,52.51,-128.969,52.464,-129.102,52.574,-129.151,52.605,-129.25,52.722,-129.268,52.772,-129.264,52.801,-129.246,52.811,-129.215,52.804,-129.186,52.791,-128.994,52.662,-128.94,52.601,-128.937,52.51]],[[-127.925,51.474,-127.941,51.457,-127.981,51.457,-128.045,51.474,-128.092,51.511,-128.149,51.627,-128.142,51.647,-128.123,51.667,-128.032,51.708,-127.999,51.704,-127.987,51.674,-127.933,51.605,-127.916,51.585,-127.916,51.506,-127.925,51.474]],[[-128.369,52.401,-128.445,52.388,-128.42,52.441,-128.412,52.473,-128.426,52.503,-128.436,52.56,-128.44,52.696,-128.365,52.782,-128.247,52.784,-128.248,52.741,-128.298,52.548,-128.324,52.459,-128.344,52.426,-128.369,52.401]],[[-129.314,52.992,-129.329,52.984,-129.37,52.998,-129.41,53.024,-129.478,53.098,-129.5,53.129,-129.515,53.179,-129.501,53.188,-129.471,53.183,-129.451,53.175,-129.344,53.053,-129.314,52.992]],[[-129.168,53.118,-129.173,53.111,-129.277,53.111,-129.306,53.121,-129.324,53.142,-129.331,53.174,-129.314,53.212,-129.253,53.285,-129.251,53.317,-129.238,53.33,-129.195,53.293,-129.177,53.259,-129.168,53.118]],[[-123.435,48.754,-123.477,48.729,-123.5,48.732,-123.518,48.75,-123.582,48.926,-123.555,48.922,-123.468,48.867,-123.488,48.846,-123.423,48.793,-123.407,48.756,-123.435,48.754]],[[-59.788,43.94,-59.922,43.904,-60.038,43.907,-60.114,43.939,-60.117,43.953,-59.936,43.94,-59.866,43.947,-59.727,44.003,-59.788,43.94]],[[-60.962,45.49,-61.003,45.482,-61.012,45.496,-61.076,45.537,-61.082,45.558,-61.026,45.577,-60.912,45.567,-60.953,45.516,-60.962,45.49]],[[-61.914,47.285,-61.879,47.266,-61.815,47.268,-61.773,47.26,-61.834,47.223,-61.951,47.219,-62.008,47.234,-61.925,47.425,-61.827,47.469,-61.628,47.594,-61.548,47.632,-61.474,47.647,-61.396,47.638,-61.476,47.564,-61.582,47.56,-61.684,47.499,-61.751,47.431,-61.831,47.392,-61.887,47.345,-61.914,47.285]],[[-64.509,47.887,-64.534,47.814,-64.621,47.752,-64.665,47.748,-64.685,47.754,-64.66,47.794,-64.663,47.863,-64.591,47.872,-64.565,47.866,-64.509,47.887]],[[-64.476,47.959,-64.591,47.907,-64.541,47.985,-64.52,48.005,-64.5,48.014,-64.481,48.007,-64.476,47.959]],[[-66.274,44.292,-66.324,44.257,-66.312,44.292,-66.25,44.379,-66.21,44.392,-66.274,44.292]],[[-66.762,44.682,-66.897,44.629,-66.845,44.764,-66.802,44.805,-66.745,44.791,-66.753,44.71,-66.762,44.682]],[[-73.567,45.469,-73.644,45.449,-73.775,45.468,-73.92,45.442,-73.961,45.441,-73.853,45.516,-73.687,45.561,-73.522,45.701,-73.476,45.705,-73.539,45.546,-73.552,45.49,-73.567,45.469]],[[-73.695,45.585,-73.816,45.565,-73.858,45.574,-73.725,45.672,-73.572,45.694,-73.695,45.585]],[[-71.026,46.873,-71.117,46.865,-71.095,46.9,-70.971,46.961,-70.88,46.996,-70.826,46.995,-70.913,46.92,-71.026,46.873]],[[-55.536,50.72,-55.57,50.709,-55.601,50.709,-55.629,50.721,-55.634,50.74,-55.604,50.781,-55.527,50.801,-55.469,50.796,-55.473,50.776,-55.504,50.742,-55.536,50.72]],[[-54.554,49.589,-54.709,49.531,-54.744,49.508,-54.787,49.496,-54.819,49.514,-54.864,49.576,-54.855,49.597,-54.813,49.599,-54.789,49.591,-54.783,49.572,-54.764,49.562,-54.733,49.562,-54.619,49.622,-54.559,49.631,-54.538,49.62,-54.554,49.589]],[[-55.361,51.89,-55.409,51.889,-55.42,51.9,-55.4,51.938,-55.346,51.983,-55.274,51.995,-55.294,51.93,-55.361,51.89]],[[-54.094,49.744,-54.02,49.679,-53.981,49.662,-54.238,49.592,-54.269,49.587,-54.286,49.595,-54.289,49.661,-54.278,49.711,-54.259,49.719,-54.199,49.689,-54.138,49.751,-54.094,49.744]],[[-54.227,47.441,-54.276,47.407,-54.326,47.408,-54.32,47.439,-54.259,47.498,-54.227,47.54,-54.226,47.566,-54.215,47.585,-54.168,47.607,-54.128,47.647,-54.148,47.573,-54.227,47.441]],[[-64.824,62.559,-64.632,62.548,-64.515,62.552,-64.465,62.536,-64.418,62.487,-64.478,62.418,-64.546,62.391,-64.657,62.384,-64.837,62.406,-64.901,62.421,-64.956,62.458,-64.931,62.485,-64.842,62.494,-64.827,62.505,-64.85,62.525,-64.849,62.543,-64.824,62.559]],[[-68.234,60.241,-68.324,60.233,-68.365,60.254,-68.368,60.315,-68.338,60.361,-68.235,60.456,-68.142,60.562,-68.088,60.588,-67.978,60.57,-67.914,60.54,-67.848,60.489,-67.819,60.45,-67.844,60.392,-67.922,60.34,-68.012,60.305,-68.234,60.241]],[[-70.337,62.549,-70.406,62.545,-70.542,62.552,-70.687,62.573,-70.766,62.597,-70.838,62.648,-70.851,62.704,-70.986,62.788,-71.137,62.816,-71.22,62.874,-71.135,62.878,-71.014,62.865,-70.835,62.84,-70.674,62.807,-70.443,62.734,-70.367,62.666,-70.292,62.616,-70.269,62.578,-70.289,62.562,-70.337,62.549]],[[-64.833,61.366,-64.857,61.354,-64.88,61.357,-64.954,61.41,-65.054,61.432,-65.092,61.453,-65.394,61.563,-65.427,61.611,-65.432,61.65,-65.332,61.668,-65.13,61.686,-64.954,61.685,-64.79,61.662,-64.756,61.638,-64.67,61.593,-64.691,61.539,-64.696,61.471,-64.732,61.438,-64.788,61.413,-64.833,61.366]],[[-65.031,61.879,-65.008,61.87,-64.981,61.881,-64.961,61.872,-64.947,61.843,-64.924,61.824,-64.865,61.798,-64.846,61.78,-64.847,61.762,-64.897,61.733,-64.928,61.733,-65.166,61.798,-65.23,61.864,-65.235,61.898,-65.211,61.928,-65.174,61.943,-65.126,61.942,-65.068,61.926,-65.031,61.879]],[[-69.16,59.04,-69.221,58.968,-69.302,58.977,-69.331,58.962,-69.353,58.961,-69.316,59.029,-69.312,59.075,-69.33,59.121,-69.303,59.145,-69.195,59.146,-69.194,59.093,-69.181,59.073,-69.155,59.064,-69.16,59.04]],[[-64.407,60.367,-64.442,60.298,-64.558,60.323,-64.738,60.376,-64.809,60.41,-64.834,60.448,-64.836,60.501,-64.783,60.51,-64.646,60.515,-64.533,60.441,-64.5,60.43,-64.407,60.367]],[[-60.994,56.039,-60.983,56.015,-61.137,56.033,-61.191,56.048,-61.196,56.064,-61.188,56.089,-61.158,56.118,-61.087,56.141,-61.049,56.129,-60.966,56.099,-60.955,56.08,-60.994,56.039]],[[-61.744,57.555,-61.66,57.525,-61.637,57.416,-61.795,57.422,-61.975,57.495,-62.011,57.548,-62.007,57.558,-61.983,57.567,-61.937,57.554,-61.893,57.573,-61.848,57.579,-61.744,57.555]],[[-67.915,69.541,-67.94,69.535,-68.202,69.58,-68.221,69.617,-68.093,69.657,-67.989,69.679,-67.909,69.682,-67.829,69.675,-67.755,69.631,-67.845,69.592,-67.915,69.541]],[[-62.682,67.056,-62.805,67.029,-62.872,67.063,-62.825,67.072,-62.757,67.113,-62.664,67.148,-62.625,67.177,-62.47,67.19,-62.417,67.188,-62.396,67.178,-62.485,67.134,-62.682,67.056]],[[-79.063,75.926,-79.052,75.867,-79.124,75.87,-79.356,75.831,-79.545,75.826,-79.639,75.843,-79.699,75.883,-79.551,75.958,-79.382,76.011,-79.178,76.092,-79.009,76.146,-78.926,76.135,-78.845,76.106,-78.946,76.025,-79.057,75.985,-79.063,75.926]],[[-79.867,56.775,-79.894,56.757,-79.944,56.777,-79.946,56.827,-79.898,56.865,-79.861,56.864,-79.827,56.843,-79.835,56.816,-79.867,56.775]],[[-79.717,57.516,-79.732,57.508,-79.775,57.514,-79.792,57.449,-79.808,57.442,-79.838,57.483,-79.816,57.518,-79.819,57.542,-79.811,57.559,-79.768,57.599,-79.743,57.608,-79.727,57.605,-79.713,57.555,-79.717,57.516]],[[-79.518,56.657,-79.553,56.644,-79.577,56.645,-79.551,56.733,-79.582,56.765,-79.584,56.781,-79.57,56.796,-79.553,56.799,-79.511,56.771,-79.491,56.743,-79.482,56.714,-79.485,56.687,-79.497,56.667,-79.518,56.657]],[[-79.938,53.304,-79.939,53.274,-80.004,53.28,-80.039,53.297,-80.068,53.324,-80.074,53.344,-80.05,53.364,-79.975,53.352,-79.938,53.304]],[[-79.384,51.952,-79.426,51.945,-79.521,51.953,-79.597,51.978,-79.644,52.01,-79.335,52.098,-79.271,52.087,-79.27,52.071,-79.317,52.024,-79.329,51.992,-79.352,51.968,-79.384,51.952]],[[-123.372,48.886,-123.385,48.875,-123.541,48.946,-123.646,49.039,-123.689,49.095,-123.482,48.955,-123.378,48.908,-123.372,48.886]],[[-124.978,50.03,-125.002,50.021,-125.026,50.134,-124.996,50.175,-124.987,50.196,-124.991,50.217,-124.938,50.166,-124.916,50.132,-124.907,50.084,-124.908,50.071,-124.978,50.03]],[[-73.622,67.784,-74.109,67.783,-74.374,67.79,-74.481,67.805,-74.573,67.829,-74.679,67.906,-74.746,67.985,-74.749,68.018,-74.731,68.049,-74.707,68.067,-74.379,68.093,-74.111,68.061,-73.881,68.022,-73.584,68.015,-73.494,68.001,-73.459,67.99,-73.435,67.97,-73.402,67.879,-73.398,67.83,-73.407,67.793,-73.622,67.784]],[[-77.877,63.471,-77.792,63.428,-77.704,63.431,-77.655,63.396,-77.538,63.287,-77.527,63.269,-77.533,63.234,-77.594,63.188,-77.658,63.165,-77.791,63.13,-77.942,63.114,-78.024,63.139,-78.256,63.24,-78.469,63.358,-78.537,63.424,-78.507,63.451,-78.417,63.47,-78.235,63.49,-77.934,63.479,-77.877,63.471]],[[-94.527,75.749,-94.624,75.749,-94.751,75.77,-94.787,75.791,-94.815,75.821,-94.834,75.859,-94.86,75.889,-94.894,75.912,-94.901,75.931,-94.881,75.946,-94.84,75.954,-94.745,75.957,-94.538,75.996,-94.499,75.992,-94.471,75.971,-94.443,75.917,-94.414,75.885,-94.332,75.826,-94.296,75.788,-94.304,75.776,-94.33,75.766,-94.527,75.749]],[[-80.285,59.624,-80.317,59.621,-80.325,59.633,-80.299,59.674,-80.257,59.679,-80.21,59.725,-80.167,59.709,-80.183,59.683,-80.241,59.645,-80.285,59.624]],[[-80.064,59.771,-80.167,59.764,-80.122,59.823,-80.084,59.852,-80.041,59.87,-79.956,59.877,-79.899,59.853,-79.95,59.81,-80.064,59.771]],[[-96.782,72.937,-96.944,72.927,-97.093,72.997,-97.098,73.062,-97.088,73.098,-97.069,73.13,-97.015,73.157,-96.862,73.189,-96.793,73.165,-96.768,73.137,-96.744,73.126,-96.646,73.102,-96.598,73.074,-96.604,73.042,-96.635,72.992,-96.671,72.961,-96.709,72.947,-96.782,72.937]],[[-97.356,74.526,-97.656,74.466,-97.722,74.489,-97.75,74.511,-97.516,74.602,-97.417,74.627,-97.318,74.598,-97.291,74.576,-97.304,74.56,-97.356,74.526]],[[-98.27,73.869,-98.558,73.847,-98.691,73.856,-98.761,73.829,-98.817,73.817,-98.974,73.812,-99.298,73.862,-99.385,73.879,-99.417,73.895,-99.404,73.911,-99.346,73.926,-99.097,73.948,-99.005,73.965,-98.967,73.988,-98.904,74.007,-98.818,74.021,-98.585,74.035,-98.061,74.105,-97.8,74.115,-97.698,74.109,-97.667,74.09,-97.659,74.072,-97.673,74.053,-97.755,74.006,-97.861,73.968,-98.147,73.889,-98.27,73.869]],[[-90.2,69.419,-90.177,69.357,-90.267,69.273,-90.295,69.258,-90.33,69.252,-90.364,69.263,-90.465,69.329,-90.492,69.37,-90.455,69.39,-90.377,69.416,-90.322,69.429,-90.253,69.418,-90.229,69.436,-90.2,69.419]],[[-90.493,69.221,-90.574,69.209,-90.626,69.251,-90.667,69.259,-90.686,69.287,-90.772,69.293,-90.766,69.336,-90.742,69.357,-90.663,69.374,-90.6,69.368,-90.54,69.325,-90.511,69.29,-90.485,69.247,-90.493,69.221]],[[-74,62.618,-74.054,62.61,-74.254,62.622,-74.5,62.669,-74.626,62.713,-74.62,62.726,-74.564,62.733,-74.501,62.727,-74.395,62.696,-74.109,62.68,-74.017,62.663,-73.988,62.636,-74,62.618]],[[-74.881,68.349,-74.959,68.342,-75.073,68.404,-75.31,68.474,-75.4,68.525,-75.403,68.55,-75.396,68.589,-75.37,68.636,-75.287,68.688,-75.2,68.696,-75.075,68.685,-74.984,68.648,-74.885,68.545,-74.819,68.494,-74.798,68.458,-74.831,68.441,-74.828,68.424,-74.813,68.413,-74.819,68.394,-74.845,68.366,-74.881,68.349]],[[-78.532,60.729,-78.669,60.717,-78.669,60.731,-78.612,60.772,-78.4,60.808,-78.242,60.819,-78.279,60.784,-78.372,60.756,-78.532,60.729]],[[-78.983,68.193,-79.064,68.182,-79.174,68.235,-79.175,68.264,-79.153,68.335,-78.953,68.353,-78.869,68.31,-78.829,68.268,-78.983,68.193]],[[-76.678,63.394,-76.783,63.384,-76.922,63.406,-77.057,63.45,-77.365,63.588,-77.134,63.682,-76.764,63.574,-76.652,63.504,-76.678,63.394]],[[-79.431,69.788,-79.39,69.73,-79.365,69.712,-79.402,69.685,-79.553,69.631,-79.882,69.609,-80.048,69.634,-79.971,69.556,-79.954,69.523,-79.978,69.51,-80.047,69.514,-80.161,69.536,-80.227,69.562,-80.244,69.593,-80.269,69.6,-80.3,69.583,-80.33,69.587,-80.398,69.633,-80.448,69.65,-80.778,69.677,-80.795,69.689,-80.778,69.71,-80.727,69.74,-80.653,69.751,-80.466,69.737,-80.451,69.745,-80.438,69.783,-80.424,69.798,-80.295,69.794,-80.214,69.802,-80.169,69.782,-80.125,69.737,-80.062,69.746,-79.971,69.739,-79.87,69.756,-79.715,69.796,-79.594,69.81,-79.431,69.788]],[[-78.029,69.715,-77.978,69.665,-77.969,69.639,-78.04,69.608,-78.307,69.552,-78.47,69.503,-78.552,69.492,-78.662,69.503,-78.795,69.48,-78.848,69.483,-78.789,69.523,-78.579,69.639,-78.402,69.651,-78.344,69.675,-78.296,69.667,-78.267,69.687,-78.262,69.717,-78.201,69.74,-78.145,69.739,-78.029,69.715]],[[-77.642,63.992,-77.714,63.946,-77.929,63.962,-77.958,63.976,-77.966,63.993,-77.931,64.015,-77.711,64.036,-77.617,64.037,-77.569,64.03,-77.564,64.022,-77.642,63.992]],[[-83.123,66.283,-83.024,66.271,-82.948,66.272,-82.931,66.257,-83.011,66.208,-83.06,66.199,-83.148,66.234,-83.214,66.277,-83.233,66.303,-83.238,66.332,-83.222,66.336,-83.123,66.283]],[[-79.211,68.845,-79.28,68.839,-79.361,68.858,-79.39,68.89,-79.406,68.923,-79.391,68.94,-79.355,68.956,-79.305,68.992,-79.243,69.049,-79.145,69.087,-78.93,69.123,-78.9,69.135,-78.804,69.235,-78.772,69.252,-78.662,69.262,-78.65,69.275,-78.689,69.3,-78.689,69.325,-78.65,69.351,-78.597,69.371,-78.458,69.39,-78.333,69.386,-78.3,69.379,-78.272,69.361,-78.234,69.315,-78.229,69.304,-78.287,69.263,-78.439,69.199,-78.533,69.146,-78.552,69.129,-78.56,69.106,-78.596,69.079,-78.705,69.014,-78.779,68.95,-78.853,68.916,-79.054,68.883,-79.211,68.845]],[[-76.995,69.144,-77.122,69.132,-77.215,69.138,-77.276,69.162,-77.322,69.194,-77.379,69.274,-77.358,69.312,-77.352,69.379,-77.341,69.404,-77.319,69.416,-77.188,69.44,-77.109,69.437,-76.994,69.412,-76.746,69.404,-76.684,69.38,-76.669,69.366,-76.67,69.349,-76.687,69.328,-76.81,69.267,-76.869,69.225,-76.911,69.175,-76.995,69.144]],[[-86.913,70.113,-86.799,70.105,-86.691,70.115,-86.613,70.106,-86.563,70.077,-86.531,70.048,-86.515,70.017,-86.558,69.995,-86.734,69.976,-86.855,69.986,-86.984,70.011,-87.044,70,-87.191,70.019,-87.264,70.044,-87.323,70.08,-87.323,70.102,-87.168,70.127,-87.107,70.147,-86.913,70.113]],[[-83.726,65.797,-83.598,65.757,-83.469,65.735,-83.263,65.723,-83.234,65.715,-83.234,65.697,-83.264,65.668,-83.332,65.631,-83.381,65.63,-83.495,65.656,-83.537,65.669,-83.583,65.699,-83.607,65.701,-83.636,65.692,-83.644,65.679,-83.631,65.662,-83.65,65.658,-83.788,65.669,-83.809,65.678,-83.798,65.71,-83.702,65.756,-83.787,65.77,-83.814,65.788,-83.939,65.758,-84.008,65.752,-84.118,65.772,-84.13,65.877,-84.143,65.916,-84.193,65.942,-84.223,65.97,-84.271,65.991,-84.37,66.012,-84.451,66.064,-84.467,66.088,-84.456,66.106,-84.407,66.131,-84.122,66.078,-83.95,66.027,-83.787,65.966,-83.701,65.92,-83.694,65.89,-83.715,65.861,-83.765,65.831,-83.726,65.797]],[[-86.596,67.736,-86.638,67.735,-86.706,67.75,-86.861,67.81,-86.893,67.837,-86.908,67.867,-86.908,67.902,-86.895,67.938,-86.847,68.01,-86.938,68.068,-86.96,68.1,-86.949,68.119,-86.899,68.163,-86.885,68.191,-86.834,68.23,-86.702,68.306,-86.57,68.288,-86.452,68.225,-86.421,68.183,-86.43,68.139,-86.42,68.074,-86.39,67.989,-86.382,67.927,-86.396,67.889,-86.447,67.817,-86.49,67.784,-86.546,67.752,-86.596,67.736]],[[-84.675,65.575,-84.727,65.564,-84.783,65.57,-84.83,65.599,-84.869,65.651,-84.931,65.689,-85.072,65.737,-85.096,65.756,-85.136,65.821,-85.144,65.885,-85.174,65.944,-85.176,65.972,-85.15,66.015,-85.031,66.025,-84.939,66.009,-84.92,65.997,-84.889,65.972,-84.87,65.942,-84.757,65.859,-84.692,65.793,-84.603,65.657,-84.602,65.631,-84.626,65.604,-84.675,65.575]],[[-93.044,61.844,-93.085,61.842,-93.177,61.893,-93.197,61.919,-93.076,61.935,-92.993,61.89,-93,61.867,-93.044,61.844]],[[-101.226,76.579,-101.485,76.575,-101.605,76.587,-101.613,76.605,-101.509,76.628,-101.165,76.665,-100.962,76.734,-100.886,76.743,-100.622,76.752,-100.467,76.75,-100.269,76.734,-100.747,76.649,-101.226,76.579]],[[-103.003,78.146,-103.118,78.126,-103.252,78.138,-103.271,78.151,-103.274,78.166,-103.26,78.183,-103.11,78.246,-102.973,78.267,-102.892,78.271,-102.826,78.25,-102.788,78.218,-103.003,78.146]],[[-101.694,77.697,-101.831,77.687,-102.08,77.692,-102.378,77.728,-102.458,77.77,-102.475,77.837,-102.472,77.873,-102.448,77.881,-102.263,77.889,-101.918,77.9,-101.639,77.892,-101.322,77.854,-101.193,77.83,-101.128,77.813,-101.046,77.778,-101.02,77.762,-101.002,77.735,-101.398,77.729,-101.585,77.718,-101.694,77.697]],[[-89.726,76.507,-89.773,76.494,-89.924,76.501,-89.974,76.488,-90.054,76.495,-90.165,76.524,-90.294,76.579,-90.441,76.663,-90.556,76.735,-90.562,76.754,-90.525,76.788,-90.41,76.81,-90.136,76.837,-89.949,76.836,-89.775,76.782,-89.725,76.763,-89.695,76.741,-89.694,76.72,-89.709,76.701,-89.788,76.66,-89.822,76.631,-89.822,76.602,-89.805,76.561,-89.773,76.534,-89.726,76.521,-89.726,76.507]],[[-96.079,75.51,-96.156,75.477,-96.237,75.475,-96.344,75.506,-96.462,75.494,-96.622,75.431,-96.679,75.394,-96.723,75.381,-96.857,75.369,-96.915,75.38,-96.97,75.413,-97.021,75.468,-96.983,75.51,-96.856,75.538,-96.523,75.584,-96.428,75.606,-96.417,75.631,-96.397,75.647,-96.368,75.655,-96.145,75.614,-96.04,75.586,-95.96,75.554,-95.969,75.542,-96.079,75.51]],[[-95.307,74.505,-95.352,74.5,-95.442,74.506,-95.777,74.551,-95.834,74.569,-95.851,74.582,-95.774,74.599,-95.746,74.616,-95.66,74.637,-95.51,74.637,-95.353,74.586,-95.278,74.54,-95.274,74.519,-95.307,74.505]],[[-121.076,75.745,-121.154,75.741,-121.241,75.752,-121.221,75.777,-121.026,75.848,-121.015,75.868,-121.018,75.884,-121.042,75.903,-120.993,75.927,-120.914,75.938,-120.888,75.928,-120.879,75.907,-120.897,75.845,-120.921,75.814,-120.955,75.789,-121.007,75.766,-121.076,75.745]],[[-113.561,76.743,-113.712,76.711,-114.751,76.759,-114.808,76.774,-114.835,76.795,-114.647,76.851,-114.42,76.875,-113.892,76.895,-113.708,76.873,-113.585,76.847,-113.517,76.825,-113.488,76.783,-113.561,76.743]],[[-104.12,75.036,-104.309,75.031,-104.634,75.061,-104.828,75.12,-104.887,75.148,-104.882,75.16,-104.848,75.173,-104.801,75.211,-104.69,75.321,-104.649,75.35,-104.474,75.413,-104.346,75.43,-104.075,75.425,-103.917,75.392,-103.851,75.371,-103.804,75.346,-103.758,75.289,-103.746,75.252,-103.667,75.211,-103.644,75.187,-103.642,75.163,-103.664,75.139,-103.71,75.115,-103.814,75.08,-104.12,75.036]],[[-100.217,68.807,-100.249,68.775,-100.288,68.766,-100.366,68.729,-100.397,68.724,-100.443,68.748,-100.481,68.786,-100.497,68.792,-100.521,68.791,-100.573,68.766,-100.597,68.766,-100.616,68.783,-100.625,68.816,-100.625,68.865,-100.6,68.941,-100.598,68.969,-100.612,68.99,-100.601,69.009,-100.565,69.027,-100.52,69.035,-100.414,69.028,-100.33,68.998,-100.289,68.958,-100.207,68.926,-100.178,68.904,-100.217,68.807]],[[-99.995,69.014,-100.018,68.954,-100.141,68.97,-100.196,68.991,-100.242,69.04,-100.247,69.053,-100.237,69.071,-100.187,69.114,-100.153,69.129,-100.073,69.111,-100.035,69.087,-100.006,69.047,-99.995,69.014]],[[-100.308,70.496,-100.321,70.488,-100.537,70.525,-100.621,70.547,-100.648,70.563,-100.667,70.596,-100.678,70.646,-100.635,70.67,-100.538,70.669,-100.434,70.649,-100.276,70.595,-100.321,70.578,-100.323,70.542,-100.306,70.508,-100.308,70.496]],[[-95.514,69.574,-95.381,69.507,-95.382,69.474,-95.399,69.42,-95.437,69.378,-95.496,69.35,-95.579,69.336,-95.684,69.336,-95.73,69.348,-95.696,69.39,-95.67,69.402,-95.666,69.439,-95.683,69.5,-95.704,69.538,-95.764,69.56,-95.806,69.56,-95.818,69.541,-95.798,69.5,-95.812,69.447,-95.858,69.382,-95.893,69.352,-95.956,69.367,-95.986,69.392,-95.978,69.433,-95.995,69.47,-95.979,69.509,-95.936,69.567,-95.876,69.606,-95.798,69.626,-95.707,69.624,-95.602,69.602,-95.514,69.574]],[[-101.172,69.397,-101.254,69.388,-101.269,69.391,-101.262,69.418,-101.268,69.431,-101.29,69.441,-101.218,69.463,-101.207,69.48,-101.23,69.493,-101.328,69.517,-101.356,69.54,-101.351,69.559,-101.313,69.576,-101.245,69.574,-101.098,69.541,-101.031,69.495,-101.001,69.462,-101.049,69.457,-101.087,69.443,-101.127,69.415,-101.172,69.397]],[[-101.846,68.586,-101.887,68.585,-101.945,68.603,-102.266,68.664,-102.308,68.682,-102.271,68.708,-102.153,68.74,-102.074,68.774,-102.013,68.825,-101.828,68.799,-101.759,68.775,-101.733,68.753,-101.722,68.724,-101.732,68.652,-101.794,68.637,-101.846,68.586]],[[-104.541,68.406,-104.596,68.402,-104.699,68.418,-104.851,68.454,-104.965,68.492,-105.042,68.532,-105.051,68.559,-104.994,68.574,-104.907,68.582,-104.7,68.577,-104.602,68.562,-104.472,68.504,-104.445,68.471,-104.44,68.45,-104.457,68.431,-104.541,68.406]],[[-107.9,67.402,-107.95,67.318,-107.97,67.326,-108.004,67.366,-108.073,67.385,-108.152,67.429,-108.151,67.525,-108.121,67.568,-108.128,67.629,-108.049,67.665,-107.991,67.622,-107.975,67.549,-107.989,67.514,-107.932,67.476,-107.905,67.467,-107.891,67.437,-107.9,67.402]],[[-109.166,67.982,-109.054,67.972,-108.971,67.98,-108.91,67.939,-108.886,67.899,-108.894,67.884,-108.92,67.879,-109.096,67.924,-109.162,67.952,-109.184,67.975,-109.166,67.982]],[[-108.093,67.005,-107.966,66.997,-107.806,66.999,-107.833,66.921,-107.895,66.872,-107.944,66.858,-107.965,66.885,-108.06,66.947,-108.093,67.005]],[[-109.323,67.991,-109.361,67.988,-109.498,68.047,-109.469,68.098,-109.342,68.046,-109.324,68.013,-109.323,67.991]],[[-139.043,69.577,-139.126,69.539,-139.257,69.579,-139.291,69.598,-139.14,69.65,-139.073,69.648,-138.932,69.617,-138.879,69.59,-139.043,69.577]]]},{"n":"Cameroon","c":"CM","r":3,"ml":3,"lx":12.47,"ly":4.59,"b":[8.533,1.676,16.183,13.079],"p":[[[8.556,4.755,8.585,4.833,8.641,4.927,8.716,5.047,8.801,5.197,8.859,5.464,8.899,5.63,8.935,5.781,8.997,5.918,9.06,6.009,9.239,6.186,9.373,6.32,9.442,6.373,9.49,6.419,9.574,6.47,9.66,6.532,9.726,6.65,9.78,6.76,9.821,6.784,9.874,6.803,10.039,6.921,10.144,6.996,10.168,6.959,10.186,6.913,10.205,6.892,10.293,6.877,10.413,6.878,10.482,6.891,10.519,6.93,10.556,7.037,10.578,7.058,10.606,7.063,10.738,6.988,10.846,6.882,10.954,6.777,11.009,6.739,11.033,6.698,11.08,6.506,11.106,6.458,11.153,6.438,11.237,6.451,11.325,6.485,11.402,6.534,11.478,6.597,11.529,6.655,11.552,6.697,11.563,6.855,11.58,6.889,11.658,6.952,11.787,7.056,11.861,7.116,11.855,7.138,11.809,7.202,11.767,7.272,11.809,7.345,11.852,7.401,12.016,7.59,12.017,7.652,12.025,7.728,12.156,7.942,12.231,8.227,12.233,8.282,12.311,8.42,12.404,8.596,12.583,8.624,12.652,8.668,12.731,8.746,12.782,8.818,12.807,8.887,12.824,9.019,12.856,9.171,12.876,9.304,12.929,9.426,13.019,9.488,13.175,9.54,13.199,9.564,13.221,9.645,13.239,9.814,13.244,9.916,13.25,9.96,13.27,10.036,13.415,10.171,13.479,10.383,13.535,10.605,13.7,10.873,13.892,11.14,13.981,11.212,14.057,11.245,14.143,11.249,14.202,11.268,14.409,11.401,14.496,11.446,14.56,11.492,14.575,11.532,14.582,11.591,14.562,11.729,14.597,11.83,14.618,11.987,14.627,12.109,14.62,12.151,14.587,12.209,14.581,12.222,14.519,12.298,14.415,12.344,14.273,12.356,14.197,12.384,14.185,12.447,14.178,12.484,14.17,12.524,14.16,12.613,14.064,13.079,14.245,13.077,14.462,13.022,14.516,12.98,14.545,12.82,14.623,12.73,14.761,12.656,14.847,12.502,14.881,12.269,14.957,12.13,14.974,12.108,15.06,11.907,15.081,11.846,15.088,11.724,15.078,11.643,15.122,11.541,15.055,11.369,15.036,11.263,15.03,11.114,15.069,10.851,15.132,10.648,15.201,10.485,15.276,10.357,15.4,10.217,15.532,10.088,15.655,10.008,15.541,9.96,15.32,9.954,15.193,9.981,15.133,9.983,15.072,9.966,14.836,9.942,14.598,9.953,14.377,9.985,14.243,9.98,14.14,9.902,14.056,9.784,13.977,9.692,14.005,9.589,14.064,9.532,14.178,9.406,14.28,9.285,14.332,9.204,14.536,9.025,14.733,8.866,14.771,8.839,14.826,8.81,14.861,8.799,14.968,8.707,15.116,8.557,15.252,8.322,15.349,8.084,15.443,7.852,15.484,7.813,15.55,7.788,15.558,7.738,15.553,7.665,15.532,7.604,15.48,7.524,15.379,7.358,15.246,7.264,15.207,7.206,15.186,7.135,15.157,7.064,15.086,6.91,15.035,6.784,14.983,6.745,14.862,6.556,14.78,6.366,14.764,6.316,14.739,6.28,14.7,6.25,14.559,6.191,14.512,6.162,14.475,6.127,14.441,6.087,14.431,6.039,14.464,5.971,14.503,5.917,14.542,5.914,14.577,5.916,14.599,5.884,14.617,5.865,14.617,5.496,14.584,5.44,14.584,5.415,14.568,5.351,14.563,5.28,14.574,5.252,14.602,5.229,14.641,5.179,14.662,5.066,14.709,4.666,14.731,4.602,14.77,4.558,14.894,4.472,15.023,4.359,15.064,4.285,15.088,4.164,15.137,4.069,15.136,4.037,15.115,4.024,15.067,4.023,15.035,4.016,15.062,3.947,15.129,3.827,15.24,3.702,15.36,3.567,15.458,3.457,15.581,3.329,15.677,3.23,15.775,3.127,15.849,3.103,15.905,3.096,15.929,3.076,15.958,3.029,16.008,2.977,16.063,2.909,16.082,2.839,16.059,2.773,16.082,2.678,16.083,2.67,16.102,2.633,16.096,2.599,16.107,2.473,16.136,2.364,16.183,2.27,16.183,2.262,16.177,2.205,16.116,2.168,16.08,2.107,16.07,2.022,16.088,1.918,16.135,1.796,16.136,1.724,16.12,1.714,16.09,1.691,16.059,1.676,15.975,1.76,15.882,1.817,15.742,1.915,15.6,1.95,15.417,1.957,15.339,1.945,15.282,1.982,15.204,2.024,15.16,2.036,15.1,2.002,15.058,2.001,15.006,2.014,14.902,2.012,14.893,2.069,14.875,2.08,14.763,2.075,14.728,2.122,14.713,2.117,14.669,2.132,14.579,2.199,14.484,2.155,14.287,2.16,14.034,2.159,13.773,2.157,13.533,2.16,13.294,2.162,13.27,2.224,13.22,2.256,13.131,2.259,12.867,2.247,12.666,2.257,12.601,2.265,12.53,2.281,12.361,2.296,12.153,2.284,12.106,2.288,11.94,2.285,11.559,2.302,11.348,2.3,11.353,2.261,11.34,2.234,11.329,2.167,11.097,2.167,10.791,2.168,10.502,2.168,10.307,2.168,9.98,2.168,9.87,2.213,9.837,2.242,9.83,2.275,9.826,2.298,9.801,2.304,9.822,2.539,9.868,2.735,9.885,2.917,9.948,3.079,9.915,3.24,9.876,3.31,9.672,3.538,9.766,3.624,9.642,3.612,9.616,3.696,9.556,3.798,9.593,3.814,9.628,3.87,9.74,3.853,9.736,3.88,9.64,3.965,9.649,4.008,9.689,4.056,9.67,4.077,9.6,4.027,9.551,4.028,9.512,4.061,9.484,4.066,9.501,4.001,9.462,3.943,9.425,3.922,9.362,3.926,9.311,3.94,9.297,3.973,9.249,3.998,9.114,4.041,9,4.092,8.977,4.23,8.932,4.29,8.914,4.358,8.903,4.435,8.918,4.554,8.889,4.573,8.856,4.579,8.807,4.573,8.762,4.58,8.708,4.646,8.66,4.671,8.69,4.55,8.656,4.516,8.574,4.526,8.54,4.572,8.533,4.606,8.571,4.752,8.556,4.755]]]},{"n":"Cambodia","c":"KH","r":3,"ml":3,"lx":104.5,"ly":12.65,"b":[102.32,10.411,107.605,14.705],"p":[[[103.045,11.285,103.027,11.275,103.011,11.276,102.993,11.29,102.995,11.348,103.008,11.383,103.037,11.39,103.045,11.285]],[[103.318,10.719,103.281,10.68,103.223,10.76,103.223,10.782,103.318,10.719]],[[107.519,14.705,107.535,14.65,107.493,14.546,107.448,14.451,107.364,14.369,107.36,14.308,107.331,14.127,107.343,14.069,107.362,14.019,107.389,13.993,107.462,13.816,107.529,13.654,107.594,13.522,107.605,13.438,107.546,13.225,107.475,13.03,107.482,12.933,107.512,12.836,107.544,12.706,107.555,12.54,107.538,12.432,107.506,12.365,107.446,12.296,107.393,12.26,107.33,12.319,107.28,12.322,107.212,12.304,107.159,12.277,107.051,12.176,106.931,12.077,106.765,12.052,106.7,11.979,106.631,11.969,106.5,11.966,106.414,11.948,106.418,11.912,106.411,11.738,106.413,11.698,106.399,11.687,106.34,11.682,106.239,11.708,106.103,11.751,106.006,11.758,105.956,11.682,105.927,11.653,105.89,11.648,105.851,11.635,105.838,11.601,105.835,11.559,105.854,11.487,105.861,11.372,105.856,11.294,105.892,11.245,106.1,11.079,106.161,11.037,106.168,11.012,106.132,10.922,106.164,10.795,106.099,10.797,105.99,10.852,105.938,10.885,105.875,10.858,105.853,10.864,105.811,10.926,105.755,10.99,105.698,10.994,105.577,10.969,105.453,10.951,105.406,10.952,105.387,10.94,105.315,10.845,105.284,10.861,105.159,10.898,105.046,10.911,105.022,10.887,105.036,10.809,105.061,10.734,105.046,10.702,104.984,10.662,104.901,10.59,104.851,10.534,104.815,10.521,104.69,10.523,104.564,10.516,104.514,10.463,104.467,10.422,104.426,10.411,104.262,10.541,103.937,10.587,103.902,10.644,103.871,10.655,103.841,10.581,103.662,10.509,103.587,10.552,103.532,10.605,103.54,10.669,103.592,10.721,103.681,10.759,103.722,10.89,103.654,11.059,103.595,11.108,103.532,11.147,103.467,11.084,103.411,10.977,103.354,10.922,103.272,10.909,103.153,10.914,103.106,11.074,103.091,11.211,103.107,11.368,103.125,11.461,103.011,11.589,103.004,11.711,102.949,11.773,102.932,11.742,102.934,11.707,102.918,11.732,102.737,12.09,102.706,12.256,102.737,12.383,102.756,12.426,102.703,12.494,102.63,12.57,102.5,12.67,102.491,12.828,102.462,13.015,102.423,13.078,102.363,13.193,102.331,13.288,102.32,13.54,102.336,13.56,102.429,13.568,102.547,13.586,102.566,13.626,102.545,13.66,102.62,13.717,102.729,13.842,102.813,13.972,102.873,14.055,102.909,14.137,103.031,14.253,103.199,14.333,103.313,14.351,103.432,14.379,103.546,14.417,103.6,14.421,103.742,14.374,103.818,14.362,103.899,14.363,103.982,14.358,104.054,14.363,104.228,14.396,104.412,14.37,104.576,14.39,104.779,14.428,104.879,14.404,104.97,14.366,104.982,14.289,105.003,14.254,105.034,14.227,105.074,14.227,105.126,14.281,105.169,14.336,105.183,14.346,105.186,14.319,105.207,14.259,105.246,14.201,105.285,14.161,105.35,14.11,105.393,14.107,105.532,14.156,105.74,14.085,105.764,14.049,105.831,13.977,105.904,13.925,106.067,13.921,106.125,14.049,106.097,14.127,106.004,14.263,105.979,14.343,106.008,14.357,106.165,14.372,106.191,14.388,106.225,14.476,106.268,14.466,106.355,14.455,106.447,14.515,106.501,14.578,106.531,14.549,106.564,14.505,106.599,14.479,106.665,14.441,106.738,14.388,106.783,14.335,106.82,14.315,106.913,14.329,106.938,14.327,106.992,14.391,107.03,14.426,107.062,14.416,107.109,14.417,107.207,14.498,107.262,14.572,107.293,14.592,107.38,14.555,107.415,14.563,107.465,14.665,107.519,14.705]]]},{"n":"Myanmar","c":"MM","r":3,"ml":3,"lx":95.8,"ly":21.57,"b":[92.18,9.875,101.147,28.517],"p":[[[100.122,20.317,100.004,20.38,99.954,20.415,99.89,20.424,99.825,20.384,99.773,20.341,99.72,20.325,99.639,20.32,99.532,20.343,99.459,20.363,99.448,20.352,99.488,20.261,99.502,20.188,99.486,20.15,99.452,20.118,99.399,20.093,99.338,20.079,99.284,20.08,99.197,20.115,99.131,20.117,99.074,20.099,99.04,20.074,99.021,20.042,98.987,19.861,98.958,19.805,98.917,19.773,98.876,19.77,98.82,19.778,98.761,19.771,98.494,19.701,98.455,19.694,98.371,19.689,98.294,19.687,98.239,19.691,98.111,19.762,98.049,19.77,98.015,19.75,97.991,19.654,97.916,19.593,97.817,19.46,97.794,19.266,97.804,19.13,97.714,18.996,97.706,18.932,97.754,18.621,97.746,18.588,97.728,18.572,97.672,18.561,97.577,18.529,97.515,18.498,97.485,18.494,97.397,18.518,97.374,18.518,97.381,18.494,97.451,18.36,97.524,18.296,97.599,18.303,97.632,18.29,97.622,18.258,97.652,18.174,97.72,18.037,97.74,17.935,97.699,17.834,97.706,17.797,97.729,17.776,97.793,17.681,97.929,17.533,98.063,17.373,98.175,17.24,98.257,17.148,98.439,16.976,98.471,16.895,98.478,16.732,98.523,16.638,98.565,16.571,98.594,16.515,98.661,16.33,98.689,16.305,98.835,16.418,98.869,16.394,98.888,16.352,98.888,16.298,98.866,16.237,98.818,16.181,98.592,16.051,98.574,15.939,98.558,15.769,98.554,15.56,98.565,15.404,98.557,15.368,98.537,15.351,98.452,15.357,98.329,15.279,98.286,15.272,98.232,15.241,98.191,15.204,98.178,15.147,98.202,14.976,98.246,14.815,98.332,14.696,98.4,14.603,98.495,14.473,98.57,14.36,98.721,14.236,98.934,14.05,99.015,13.947,99.086,13.823,99.137,13.717,99.156,13.576,99.172,13.497,99.176,13.233,99.137,13.173,99.107,13.104,99.124,13.031,99.174,12.961,99.174,12.882,99.22,12.74,99.297,12.653,99.372,12.594,99.405,12.548,99.394,12.474,99.416,12.395,99.432,12.309,99.463,12.19,99.523,12.09,99.615,11.781,99.613,11.75,99.573,11.687,99.515,11.631,99.478,11.613,99.443,11.554,99.359,11.389,99.19,11.105,99.025,10.92,98.887,10.788,98.787,10.708,98.757,10.661,98.757,10.624,98.775,10.557,98.768,10.431,98.747,10.351,98.718,10.266,98.703,10.19,98.658,10.179,98.563,10.035,98.521,10.107,98.497,10.183,98.523,10.353,98.465,10.676,98.501,10.719,98.536,10.741,98.599,10.864,98.676,10.987,98.683,11.133,98.745,11.24,98.73,11.33,98.733,11.435,98.746,11.521,98.741,11.592,98.791,11.665,98.876,11.72,98.84,11.739,98.805,11.779,98.694,11.718,98.636,11.738,98.625,11.801,98.639,11.869,98.645,11.91,98.689,11.957,98.686,12.047,98.664,12.127,98.696,12.225,98.631,12.225,98.6,12.245,98.619,12.3,98.679,12.348,98.624,12.441,98.665,12.54,98.663,12.662,98.636,12.771,98.637,12.848,98.595,12.986,98.576,13.162,98.487,13.293,98.421,13.484,98.245,13.733,98.248,13.84,98.239,13.934,98.2,13.98,98.15,13.648,98.111,13.713,98.098,13.898,98.073,13.986,98.1,14.162,97.998,14.335,97.977,14.461,97.91,14.653,97.929,14.696,98.019,14.653,97.937,14.764,97.869,14.739,97.812,14.859,97.8,15.185,97.744,15.307,97.774,15.431,97.71,15.876,97.584,16.02,97.609,16.144,97.641,16.254,97.634,16.458,97.665,16.52,97.726,16.569,97.668,16.552,97.62,16.537,97.505,16.525,97.376,16.523,97.331,16.672,97.267,16.743,97.212,16.893,97.178,17.062,97.2,17.095,97.1,17.165,97.075,17.207,96.97,17.317,96.851,17.401,96.878,17.342,96.91,17.305,96.851,17.203,96.909,17.031,96.858,16.921,96.811,16.778,96.765,16.71,96.622,16.564,96.507,16.514,96.431,16.505,96.364,16.521,96.282,16.596,96.262,16.659,96.249,16.765,96.22,16.781,96.189,16.768,96.238,16.631,96.237,16.567,96.324,16.444,96.293,16.41,96.135,16.343,96.081,16.353,96.043,16.34,96.032,16.285,96.012,16.254,95.763,16.169,95.711,16.073,95.679,15.977,95.556,15.838,95.39,15.723,95.348,15.729,95.301,15.756,95.308,15.88,95.365,15.985,95.347,16.098,95.333,16.033,95.226,15.877,95.177,15.826,95.078,15.839,94.943,15.818,94.891,15.979,94.892,16.038,94.882,16.088,94.898,16.141,94.893,16.183,94.86,16.102,94.848,16.033,94.798,15.971,94.662,15.904,94.656,15.989,94.651,16.065,94.681,16.133,94.677,16.242,94.72,16.399,94.717,16.452,94.703,16.512,94.679,16.426,94.665,16.336,94.638,16.309,94.588,16.289,94.496,16.186,94.442,16.094,94.299,16.008,94.224,16.016,94.214,16.127,94.271,16.517,94.327,16.572,94.353,16.64,94.4,16.868,94.452,16.954,94.473,17.135,94.494,17.167,94.564,17.309,94.589,17.569,94.56,17.699,94.494,17.825,94.431,18.202,94.266,18.507,94.252,18.609,94.171,18.732,94.246,18.741,94.091,18.849,94.07,18.893,94.039,19.146,94.045,19.287,94.022,19.269,94.002,19.182,93.941,19.146,93.968,18.995,93.961,18.958,93.929,18.9,93.8,18.961,93.705,19.027,93.598,19.188,93.493,19.369,93.531,19.398,93.579,19.401,93.728,19.267,93.825,19.238,93.886,19.272,93.962,19.329,93.998,19.441,93.961,19.482,93.888,19.504,93.84,19.534,93.77,19.61,93.761,19.648,93.74,19.697,93.669,19.732,93.612,19.776,93.66,19.854,93.707,19.912,93.582,19.91,93.439,20.009,93.41,20.038,93.362,20.058,93.25,20.07,93.157,20.041,93.199,19.898,93.191,19.851,93.129,19.858,93.002,20.075,93.04,20.13,93.096,20.181,93.068,20.189,93.015,20.185,93.067,20.378,93.035,20.406,93.019,20.346,92.991,20.288,92.882,20.152,92.828,20.178,92.791,20.211,92.844,20.283,92.872,20.302,92.891,20.34,92.851,20.415,92.787,20.469,92.736,20.563,92.709,20.564,92.733,20.453,92.723,20.296,92.608,20.47,92.378,20.718,92.324,20.792,92.312,20.864,92.286,20.932,92.268,21.005,92.264,21.061,92.215,21.113,92.192,21.202,92.18,21.293,92.208,21.358,92.28,21.428,92.331,21.44,92.373,21.409,92.472,21.363,92.539,21.32,92.569,21.263,92.6,21.27,92.632,21.306,92.625,21.351,92.593,21.467,92.584,21.609,92.583,21.94,92.575,21.978,92.63,22.011,92.653,22.049,92.675,22.106,92.689,22.131,92.721,22.132,92.771,22.105,92.854,22.01,92.909,21.989,92.965,22.004,93.022,22.146,93.043,22.184,93.071,22.209,93.121,22.205,93.151,22.231,93.162,22.292,93.162,22.36,93.105,22.547,93.088,22.633,93.079,22.718,93.114,22.806,93.163,22.908,93.151,22.997,93.164,23.032,93.204,23.037,93.254,23.015,93.308,23.03,93.349,23.085,93.366,23.133,93.391,23.339,93.408,23.528,93.415,23.682,93.373,23.774,93.307,24.022,93.326,24.064,93.356,24.074,93.452,23.987,93.494,23.973,93.564,23.986,93.633,24.005,93.683,24.007,93.756,23.977,93.855,23.944,94.011,23.903,94.075,23.872,94.128,23.876,94.17,23.973,94.22,24.113,94.293,24.322,94.377,24.474,94.399,24.514,94.493,24.638,94.584,24.767,94.663,24.931,94.708,25.049,94.704,25.098,94.675,25.139,94.616,25.165,94.567,25.192,94.553,25.216,94.554,25.243,94.58,25.32,94.623,25.41,94.668,25.459,94.786,25.519,94.861,25.597,94.946,25.7,94.992,25.77,95.015,25.913,95.041,25.941,95.093,25.987,95.132,26.041,95.129,26.07,95.108,26.091,95.069,26.191,95.051,26.347,95.06,26.474,95.089,26.525,95.129,26.597,95.201,26.641,95.305,26.672,95.464,26.756,95.738,26.95,95.837,27.014,95.905,27.047,95.971,27.128,96.061,27.217,96.191,27.261,96.274,27.278,96.666,27.339,96.732,27.331,96.798,27.296,96.88,27.178,96.953,27.133,97.038,27.102,97.102,27.115,97.104,27.163,96.902,27.44,96.884,27.515,96.877,27.587,96.9,27.644,96.963,27.698,97.05,27.76,97.158,27.837,97.226,27.89,97.306,27.907,97.335,27.938,97.344,27.982,97.339,28.031,97.303,28.086,97.31,28.155,97.322,28.218,97.356,28.254,97.431,28.354,97.478,28.426,97.502,28.456,97.538,28.51,97.599,28.517,97.659,28.5,97.695,28.469,97.73,28.407,97.769,28.356,97.817,28.356,97.865,28.364,97.888,28.356,97.934,28.314,98.022,28.212,98.062,28.186,98.099,28.142,98.118,28.055,98.13,27.968,98.241,27.663,98.274,27.599,98.299,27.55,98.35,27.538,98.392,27.587,98.409,27.639,98.453,27.657,98.504,27.648,98.6,27.599,98.651,27.572,98.677,27.422,98.682,27.245,98.675,27.191,98.717,27.045,98.729,26.877,98.738,26.786,98.739,26.698,98.732,26.583,98.709,26.43,98.672,26.299,98.686,26.189,98.663,26.139,98.572,26.114,98.564,26.072,98.591,26.004,98.655,25.918,98.656,25.864,98.625,25.827,98.558,25.823,98.466,25.789,98.402,25.678,98.334,25.587,98.297,25.569,98.173,25.595,98.143,25.571,98.1,25.416,98.064,25.349,98.011,25.293,97.962,25.259,97.918,25.236,97.82,25.252,97.767,25.158,97.715,25.034,97.711,24.97,97.738,24.87,97.724,24.842,97.671,24.82,97.583,24.775,97.529,24.631,97.531,24.492,97.563,24.444,97.624,24.423,97.667,24.38,97.671,24.313,97.708,24.229,97.691,24.131,97.568,23.988,97.565,23.911,97.63,23.887,97.686,23.898,97.756,23.932,97.838,23.986,98.017,24.065,98.213,24.111,98.367,24.119,98.499,24.116,98.564,24.099,98.583,24.07,98.764,24.116,98.802,24.119,98.835,24.121,98.834,24.091,98.702,23.964,98.677,23.905,98.681,23.842,98.735,23.783,98.788,23.738,98.832,23.624,98.798,23.52,98.82,23.483,98.859,23.44,98.883,23.38,98.886,23.307,98.864,23.191,99.055,23.131,99.22,23.103,99.341,23.096,99.418,23.069,99.465,23.046,99.497,23.005,99.507,22.959,99.467,22.927,99.385,22.825,99.338,22.689,99.343,22.587,99.338,22.498,99.243,22.37,99.205,22.283,99.172,22.192,99.173,22.153,99.193,22.126,99.233,22.11,99.303,22.101,99.389,22.111,99.593,22.089,99.825,22.05,99.918,22.028,99.948,21.988,99.94,21.902,99.926,21.821,99.941,21.759,99.978,21.702,100.041,21.683,100.096,21.661,100.106,21.617,100.089,21.558,100.117,21.511,100.148,21.481,100.215,21.463,100.351,21.501,100.446,21.484,100.531,21.458,100.605,21.472,100.677,21.505,100.835,21.655,101.019,21.736,101.08,21.756,101.121,21.746,101.131,21.736,101.128,21.705,101.147,21.582,101.139,21.567,101.08,21.469,100.928,21.366,100.82,21.314,100.757,21.313,100.703,21.251,100.659,21.13,100.614,21.059,100.567,21.038,100.536,20.992,100.522,20.922,100.549,20.884,100.618,20.879,100.623,20.86,100.565,20.825,100.493,20.813,100.407,20.823,100.326,20.796,100.249,20.73,100.184,20.589,100.13,20.372,100.122,20.317]],[[98.21,10.953,98.293,10.78,98.284,10.753,98.271,10.74,98.252,10.744,98.218,10.838,98.155,10.898,98.08,10.887,98.143,10.963,98.167,10.98,98.21,10.953]],[[98.183,9.933,98.134,9.875,98.118,9.878,98.14,9.975,98.221,10.045,98.292,10.051,98.283,10.008,98.231,9.954,98.183,9.933]],[[98.222,11.478,98.216,11.456,98.209,11.457,98.187,11.472,98.201,11.567,98.239,11.645,98.278,11.758,98.3,11.783,98.308,11.723,98.284,11.594,98.263,11.524,98.222,11.478]],[[98.516,11.905,98.474,11.899,98.454,12.061,98.466,12.084,98.525,12.005,98.61,11.957,98.576,11.925,98.516,11.905]],[[98.554,11.745,98.528,11.539,98.465,11.567,98.435,11.567,98.397,11.684,98.4,11.715,98.376,11.792,98.524,11.805,98.554,11.745]],[[98.414,12.598,98.436,12.571,98.468,12.571,98.459,12.474,98.381,12.354,98.334,12.336,98.314,12.336,98.331,12.511,98.303,12.612,98.312,12.678,98.396,12.647,98.414,12.598]],[[98.137,12.15,98.125,12.145,98.108,12.148,98.075,12.164,98.037,12.232,98.057,12.28,98.071,12.292,98.105,12.288,98.122,12.279,98.128,12.261,98.118,12.223,98.12,12.191,98.137,12.15]],[[94.477,15.946,94.412,15.848,94.388,15.994,94.494,16.075,94.546,16.153,94.601,16.206,94.619,16.141,94.566,16.019,94.477,15.946]],[[97.575,16.253,97.537,16.24,97.48,16.306,97.469,16.461,97.516,16.497,97.542,16.505,97.579,16.486,97.593,16.461,97.6,16.43,97.589,16.397,97.575,16.253]],[[93.691,18.684,93.674,18.676,93.57,18.76,93.488,18.868,93.618,18.889,93.745,18.866,93.746,18.808,93.718,18.716,93.691,18.684]],[[93.492,19.893,93.513,19.755,93.445,19.806,93.42,19.878,93.413,19.95,93.492,19.893]],[[93.715,19.558,93.829,19.475,93.875,19.481,93.946,19.429,93.947,19.408,93.934,19.365,93.902,19.332,93.815,19.299,93.756,19.326,93.732,19.416,93.662,19.459,93.644,19.495,93.688,19.544,93.715,19.558]],[[98.542,10.962,98.519,10.959,98.498,10.964,98.477,10.98,98.527,11.087,98.542,10.962]],[[98.075,11.692,98.084,11.637,98.021,11.696,98.01,11.86,98.06,11.757,98.081,11.733,98.075,11.692]],[[98.066,12.39,98.06,12.354,98.002,12.279,97.952,12.322,97.939,12.346,97.99,12.394,98.045,12.387,98.06,12.398,98.066,12.39]],[[98.315,13.099,98.309,12.935,98.259,13.014,98.251,13.104,98.255,13.189,98.265,13.202,98.269,13.189,98.299,13.152,98.315,13.099]],[[94.805,15.819,94.784,15.794,94.743,15.812,94.733,15.823,94.828,15.933,94.838,15.892,94.805,15.819]],[[93.01,19.924,93.023,19.829,92.975,19.868,92.913,20,92.915,20.086,92.96,20.046,93.01,19.924]]]},{"n":"Burundi","c":"BI","r":6,"ml":4,"lx":29.92,"ly":-3.33,"b":[29.014,-4.456,30.811,-2.313],"p":[[[30.554,-2.4,30.534,-2.426,30.442,-2.613,30.424,-2.642,30.434,-2.659,30.473,-2.694,30.45,-2.753,30.441,-2.769,30.424,-2.824,30.433,-2.875,30.456,-2.893,30.515,-2.918,30.604,-2.935,30.709,-2.977,30.78,-2.985,30.797,-3.015,30.794,-3.069,30.811,-3.116,30.811,-3.201,30.79,-3.275,30.682,-3.309,30.626,-3.347,30.611,-3.366,30.625,-3.389,30.632,-3.419,30.53,-3.492,30.425,-3.589,30.4,-3.654,30.379,-3.731,30.348,-3.78,30.269,-3.85,30.187,-3.993,30.147,-4.085,29.947,-4.307,29.77,-4.418,29.718,-4.456,29.403,-4.449,29.379,-4.3,29.331,-4.095,29.223,-3.911,29.212,-3.834,29.217,-3.685,29.217,-3.476,29.21,-3.363,29.212,-3.281,29.226,-3.139,29.224,-3.054,29.153,-2.955,29.065,-2.851,29.017,-2.8,29.014,-2.758,29.014,-2.72,29.029,-2.665,29.063,-2.603,29.102,-2.596,29.198,-2.62,29.297,-2.673,29.35,-2.792,29.39,-2.809,29.464,-2.808,29.651,-2.793,29.698,-2.795,29.783,-2.766,29.868,-2.716,29.893,-2.665,29.912,-2.549,29.93,-2.34,29.973,-2.337,30.092,-2.412,30.117,-2.417,30.142,-2.414,30.183,-2.377,30.234,-2.347,30.271,-2.348,30.408,-2.313,30.482,-2.376,30.529,-2.396,30.554,-2.4]]]},{"n":"Burkina Faso","c":"BF","r":3,"ml":3,"lx":-1.36,"ly":12.67,"b":[-5.524,9.425,2.389,15.078],"p":[[[0.9,10.993,0.643,10.983,0.549,10.955,0.493,10.955,0.491,10.978,0.484,10.992,0.159,11.07,-0.069,11.116,-0.299,11.167,-0.313,11.119,-0.346,11.088,-0.396,11.086,-0.43,11.093,-0.454,11.056,-0.492,11.008,-0.545,10.984,-0.598,10.954,-0.627,10.927,-0.649,10.927,-0.701,10.989,-0.772,10.995,-0.903,10.985,-0.962,11.002,-1.042,11.01,-1.233,10.997,-1.537,11.023,-1.586,11.009,-1.6,10.998,-1.901,10.995,-2.232,10.991,-2.509,10.989,-2.752,10.986,-2.752,10.997,-2.83,10.998,-2.839,10.977,-2.907,10.728,-2.915,10.592,-2.878,10.508,-2.837,10.455,-2.791,10.432,-2.787,10.402,-2.823,10.363,-2.82,10.323,-2.777,10.282,-2.767,10.238,-2.788,10.193,-2.783,10.083,-2.751,9.91,-2.75,9.797,-2.781,9.746,-2.766,9.658,-2.706,9.534,-2.696,9.481,-2.717,9.457,-2.767,9.425,-2.817,9.426,-2.875,9.501,-2.901,9.535,-2.948,9.611,-2.988,9.687,-3.043,9.721,-3.096,9.752,-3.161,9.849,-3.224,9.895,-3.29,9.882,-3.386,9.9,-3.581,9.924,-3.791,9.917,-3.878,9.895,-3.963,9.86,-4.181,9.782,-4.267,9.743,-4.332,9.646,-4.406,9.648,-4.48,9.679,-4.527,9.723,-4.626,9.714,-4.722,9.757,-4.814,9.841,-4.883,9.869,-4.97,9.93,-4.994,10.046,-5.049,10.128,-5.1,10.242,-5.175,10.293,-5.262,10.32,-5.382,10.314,-5.461,10.36,-5.524,10.426,-5.507,10.483,-5.479,10.565,-5.476,10.644,-5.457,10.771,-5.469,10.931,-5.49,11.042,-5.424,11.089,-5.347,11.13,-5.3,11.206,-5.25,11.376,-5.229,11.522,-5.245,11.577,-5.27,11.62,-5.291,11.683,-5.302,11.76,-5.288,11.828,-5.23,11.89,-5.158,11.942,-5.106,11.968,-4.969,11.993,-4.798,12.032,-4.699,12.076,-4.627,12.12,-4.587,12.155,-4.546,12.226,-4.48,12.282,-4.429,12.338,-4.422,12.493,-4.422,12.582,-4.46,12.63,-4.481,12.672,-4.227,12.794,-4.225,12.879,-4.261,12.975,-4.31,13.052,-4.329,13.119,-4.259,13.197,-4.196,13.256,-4.151,13.306,-4.051,13.382,-3.947,13.402,-3.853,13.374,-3.576,13.194,-3.528,13.183,-3.47,13.196,-3.397,13.244,-3.302,13.281,-3.267,13.401,-3.27,13.577,-3.249,13.658,-3.198,13.673,-3.039,13.639,-2.997,13.637,-2.951,13.648,-2.917,13.679,-2.919,13.736,-2.926,13.787,-2.874,13.951,-2.779,14.074,-2.587,14.228,-2.527,14.258,-2.457,14.274,-2.113,14.168,-2.057,14.195,-1.973,14.457,-1.88,14.481,-1.768,14.486,-1.695,14.508,-1.657,14.527,-1.494,14.626,-1.205,14.762,-1.05,14.82,-1.019,14.841,-0.908,14.937,-0.76,15.048,-0.666,15.07,-0.537,15.078,-0.454,15.06,-0.432,15.029,-0.405,15.013,-0.236,15.059,0.007,14.985,0.217,14.911,0.204,14.865,0.203,14.783,0.185,14.653,0.164,14.497,0.251,14.396,0.355,14.288,0.383,14.246,0.355,14.139,0.374,14.076,0.429,13.972,0.522,13.84,0.618,13.703,0.685,13.685,0.748,13.675,0.786,13.65,0.842,13.626,0.898,13.611,0.947,13.581,0.978,13.552,1.018,13.468,1.126,13.412,1.201,13.358,1.171,13.33,1.077,13.341,0.988,13.365,0.977,13.325,0.973,13.17,0.987,13.042,1.008,13.025,1.097,13.001,1.309,12.834,1.5,12.676,1.565,12.635,1.671,12.62,1.79,12.613,1.841,12.628,1.956,12.707,2.017,12.716,2.074,12.714,2.105,12.701,2.16,12.636,2.212,12.538,2.226,12.466,2.221,12.427,2.204,12.413,2.109,12.394,2.069,12.379,2.058,12.358,2.073,12.309,2.091,12.278,2.194,12.136,2.343,11.946,2.389,11.897,2.363,11.84,2.287,11.691,2.231,11.629,1.98,11.418,1.858,11.443,1.6,11.401,1.561,11.449,1.501,11.456,1.427,11.447,1.4,11.429,1.392,11.408,1.365,11.379,1.317,11.295,1.28,11.274,1.235,11.261,1.179,11.263,1.146,11.252,1.146,11.21,1.136,11.174,1.098,11.156,1.082,11.116,1.085,11.076,1.062,11.058,1.014,11.068,0.985,11.079,0.958,11.028,0.925,10.993,0.9,10.993]]]},{"n":"Bulgaria","c":"BG","r":4,"ml":4,"lx":25.16,"ly":42.51,"b":[22.344,41.244,28.585,44.238],"p":[[[28.014,41.969,27.879,41.987,27.832,41.981,27.802,41.957,27.739,41.962,27.661,41.961,27.58,41.933,27.535,41.921,27.475,41.947,27.363,42.025,27.295,42.08,27.244,42.093,27.193,42.077,27.012,42.059,26.969,42.027,26.885,41.992,26.8,41.975,26.679,41.963,26.615,41.965,26.58,41.948,26.55,41.897,26.529,41.847,26.511,41.826,26.36,41.802,26.327,41.773,26.318,41.745,26.321,41.717,26.201,41.744,26.107,41.726,26.086,41.704,26.066,41.673,26.077,41.64,26.111,41.608,26.144,41.522,26.155,41.435,26.135,41.386,26.066,41.351,25.923,41.312,25.785,41.33,25.724,41.315,25.621,41.31,25.527,41.3,25.382,41.264,25.251,41.244,25.133,41.316,24.994,41.365,24.847,41.394,24.796,41.373,24.774,41.356,24.651,41.42,24.596,41.443,24.569,41.467,24.518,41.553,24.488,41.555,24.387,41.524,24.289,41.525,24.23,41.531,24.056,41.527,24.033,41.469,24.011,41.46,23.974,41.452,23.881,41.456,23.762,41.413,23.635,41.387,23.536,41.386,23.433,41.399,23.372,41.39,23.24,41.385,23.156,41.322,23.026,41.326,22.916,41.336,22.93,41.356,22.951,41.606,23.006,41.717,23.004,41.74,22.992,41.757,22.944,41.775,22.909,41.835,22.837,41.994,22.796,42.026,22.682,42.059,22.583,42.105,22.498,42.165,22.344,42.314,22.422,42.329,22.446,42.359,22.524,42.441,22.532,42.481,22.524,42.504,22.472,42.543,22.436,42.629,22.463,42.709,22.466,42.751,22.439,42.792,22.467,42.842,22.523,42.87,22.558,42.878,22.706,42.884,22.8,42.986,22.857,43.018,22.915,43.076,22.942,43.097,22.968,43.142,22.977,43.188,22.86,43.252,22.82,43.301,22.768,43.354,22.697,43.391,22.555,43.454,22.499,43.519,22.474,43.602,22.436,43.665,22.395,43.707,22.387,43.74,22.37,43.781,22.365,43.862,22.399,43.97,22.421,44.007,22.469,44.018,22.597,44.075,22.603,44.149,22.627,44.194,22.667,44.22,22.705,44.238,22.775,44.195,22.945,44.127,23.029,44.078,23.024,44.047,22.985,44.017,22.911,43.987,22.868,43.948,22.856,43.899,22.868,43.865,22.919,43.834,23.225,43.874,23.535,43.854,23.951,43.787,24.227,43.763,24.431,43.794,24.808,43.738,25.16,43.686,25.497,43.671,25.686,43.712,25.819,43.767,25.933,43.871,26.216,44.007,26.489,44.084,26.848,44.146,27.087,44.167,27.121,44.146,27.425,44.021,27.561,44.02,27.671,43.998,27.711,43.965,27.739,43.956,27.884,43.987,27.949,43.919,28.05,43.822,28.222,43.773,28.375,43.745,28.423,43.74,28.585,43.742,28.562,43.501,28.465,43.389,28.32,43.427,28.134,43.396,28.035,43.268,27.979,43.231,27.929,43.186,27.896,43.021,27.889,42.75,27.818,42.717,27.754,42.707,27.485,42.468,27.64,42.401,27.708,42.35,27.821,42.208,27.983,42.047,28.014,41.969]]]},{"n":"Brunei","c":"BN","r":6,"ml":4,"lx":114.55,"ly":4.45,"b":[114.064,4.024,115.327,5.022],"p":[[[115.14,4.9,115.168,4.867,115.228,4.751,115.267,4.634,115.279,4.456,115.327,4.381,115.319,4.365,115.291,4.353,115.247,4.347,115.171,4.364,115.107,4.39,115.052,4.583,115.027,4.691,115.029,4.821,115.027,4.9,115.14,4.9]],[[115.027,4.9,114.945,4.856,114.865,4.802,114.784,4.755,114.747,4.718,114.76,4.667,114.779,4.553,114.79,4.464,114.818,4.429,114.84,4.393,114.831,4.354,114.783,4.281,114.81,4.267,114.776,4.169,114.725,4.097,114.654,4.038,114.608,4.024,114.572,4.049,114.512,4.114,114.447,4.204,114.417,4.256,114.323,4.263,114.29,4.304,114.288,4.355,114.261,4.414,114.224,4.478,114.169,4.527,114.095,4.565,114.064,4.593,114.178,4.591,114.299,4.607,114.424,4.66,114.545,4.725,114.646,4.798,114.741,4.881,114.841,4.946,114.995,5.022,115.048,5.016,115.047,4.962,115.027,4.9]]]},{"n":"Brazil","c":"BR","r":2,"ml":1.7,"lx":-49.56,"ly":-12.1,"b":[-74.002,-33.742,-34.805,5.258],"p":[[[-66.876,1.223,-66.619,0.992,-66.429,0.822,-66.347,0.767,-66.302,0.752,-66.191,0.763,-66.06,0.785,-65.996,0.81,-65.926,0.863,-65.811,0.937,-65.718,0.978,-65.681,0.983,-65.645,0.97,-65.566,0.926,-65.523,0.843,-65.563,0.748,-65.556,0.688,-65.473,0.691,-65.407,0.79,-65.361,0.869,-65.264,0.932,-65.17,1.022,-65.104,1.108,-65.027,1.158,-64.91,1.22,-64.818,1.257,-64.732,1.253,-64.667,1.294,-64.584,1.37,-64.526,1.431,-64.486,1.453,-64.405,1.447,-64.304,1.455,-64.205,1.529,-64.115,1.619,-64.067,1.771,-64.035,1.904,-64.008,1.932,-63.976,1.953,-63.937,1.967,-63.844,1.977,-63.682,2.048,-63.57,2.121,-63.464,2.136,-63.433,2.156,-63.394,2.223,-63.375,2.34,-63.389,2.412,-63.585,2.434,-63.713,2.434,-63.924,2.452,-64.025,2.482,-64.047,2.502,-64.049,2.525,-64.029,2.576,-64.009,2.672,-64.038,2.802,-64.144,3.005,-64.219,3.205,-64.229,3.344,-64.227,3.491,-64.221,3.587,-64.275,3.663,-64.568,3.9,-64.669,4.012,-64.703,4.089,-64.818,4.232,-64.789,4.276,-64.722,4.274,-64.666,4.237,-64.614,4.158,-64.576,4.14,-64.526,4.14,-64.256,4.14,-64.192,4.127,-64.154,4.1,-64.122,4.067,-64.073,3.974,-64.021,3.929,-63.915,3.931,-63.747,3.933,-63.653,3.941,-63.597,3.915,-63.527,3.894,-63.38,3.943,-63.339,3.944,-63.295,3.922,-63.136,3.756,-63.045,3.686,-62.969,3.594,-62.857,3.593,-62.765,3.673,-62.74,3.94,-62.712,4.018,-62.665,4.04,-62.61,4.042,-62.544,4.084,-62.473,4.139,-62.411,4.157,-62.153,4.098,-62.082,4.126,-61.821,4.197,-61.554,4.288,-61.479,4.402,-61.368,4.433,-61.28,4.517,-61.209,4.508,-61.102,4.505,-61.036,4.519,-61.003,4.535,-60.966,4.575,-60.906,4.687,-60.833,4.729,-60.742,4.774,-60.679,4.827,-60.628,4.893,-60.604,4.949,-60.604,4.995,-60.635,5.082,-60.672,5.164,-60.712,5.192,-60.742,5.202,-60.651,5.221,-60.576,5.192,-60.46,5.188,-60.409,5.21,-60.335,5.199,-60.242,5.258,-60.182,5.239,-60.142,5.239,-60.106,5.194,-60.078,5.144,-59.991,5.083,-59.999,4.99,-60.015,4.908,-60.027,4.813,-60.032,4.741,-60.069,4.667,-60.125,4.598,-60.141,4.57,-60.149,4.533,-60.111,4.511,-60.045,4.505,-59.962,4.502,-59.906,4.48,-59.833,4.476,-59.746,4.417,-59.703,4.381,-59.7,4.354,-59.727,4.288,-59.739,4.227,-59.717,4.188,-59.691,4.16,-59.62,4.023,-59.586,3.975,-59.558,3.96,-59.551,3.934,-59.575,3.883,-59.604,3.82,-59.67,3.753,-59.679,3.7,-59.732,3.667,-59.854,3.588,-59.833,3.462,-59.829,3.399,-59.831,3.349,-59.873,3.283,-59.946,3.088,-59.972,2.99,-59.996,2.765,-59.994,2.69,-59.961,2.588,-59.89,2.363,-59.849,2.327,-59.755,2.274,-59.744,2.122,-59.752,1.962,-59.756,1.901,-59.741,1.874,-59.699,1.861,-59.669,1.842,-59.664,1.795,-59.667,1.746,-59.597,1.718,-59.536,1.7,-59.479,1.632,-59.378,1.527,-59.337,1.508,-59.317,1.465,-59.231,1.376,-59.1,1.344,-58.969,1.305,-58.917,1.249,-58.862,1.204,-58.822,1.201,-58.787,1.208,-58.73,1.248,-58.685,1.281,-58.605,1.279,-58.512,1.285,-58.496,1.312,-58.487,1.348,-58.506,1.439,-58.473,1.466,-58.396,1.482,-58.38,1.53,-58.363,1.557,-58.341,1.588,-58.314,1.592,-58.281,1.574,-58.23,1.563,-58.173,1.548,-58.142,1.517,-58.091,1.514,-58.035,1.52,-58.012,1.54,-57.995,1.574,-57.983,1.648,-57.946,1.651,-57.873,1.667,-57.796,1.7,-57.692,1.705,-57.594,1.704,-57.546,1.726,-57.5,1.774,-57.413,1.909,-57.367,1.94,-57.317,1.963,-57.276,1.959,-57.19,1.982,-57.119,2.014,-57.093,2.006,-57.038,1.936,-57.01,1.921,-56.97,1.916,-56.837,1.881,-56.766,1.892,-56.69,1.914,-56.616,1.923,-56.564,1.907,-56.525,1.927,-56.483,1.942,-56.453,1.932,-56.386,1.924,-56.227,1.885,-56.02,1.842,-55.963,1.857,-55.93,1.888,-55.922,1.977,-55.915,2.04,-55.962,2.095,-56.02,2.158,-56.074,2.237,-56.138,2.259,-56.129,2.3,-56.088,2.341,-56.045,2.364,-56.02,2.393,-55.994,2.498,-55.976,2.516,-55.957,2.52,-55.936,2.517,-55.894,2.49,-55.731,2.406,-55.659,2.419,-55.385,2.441,-55.344,2.489,-55.286,2.5,-55.188,2.548,-55.149,2.551,-55.114,2.539,-55.07,2.548,-55.006,2.593,-54.979,2.598,-54.968,2.548,-54.927,2.497,-54.876,2.45,-54.852,2.44,-54.767,2.455,-54.722,2.442,-54.703,2.398,-54.697,2.36,-54.662,2.328,-54.616,2.327,-54.592,2.314,-54.55,2.293,-54.515,2.245,-54.433,2.208,-54.293,2.154,-54.228,2.153,-54.167,2.137,-54.13,2.121,-54.09,2.15,-53.946,2.233,-53.877,2.278,-53.83,2.313,-53.794,2.346,-53.768,2.355,-53.75,2.335,-53.735,2.309,-53.684,2.293,-53.564,2.262,-53.509,2.253,-53.432,2.279,-53.366,2.324,-53.334,2.34,-53.285,2.295,-53.252,2.232,-53.23,2.205,-53.18,2.211,-53.082,2.202,-53.01,2.182,-52.965,2.184,-52.903,2.212,-52.87,2.267,-52.783,2.317,-52.701,2.364,-52.653,2.426,-52.583,2.529,-52.559,2.573,-52.555,2.648,-52.456,2.864,-52.418,2.904,-52.396,2.972,-52.357,3.052,-52.357,3.118,-52.328,3.182,-52.271,3.237,-52.229,3.272,-52.163,3.365,-52.116,3.452,-52,3.647,-51.991,3.702,-51.944,3.735,-51.929,3.777,-51.879,3.829,-51.827,3.87,-51.805,3.93,-51.767,3.993,-51.683,4.04,-51.653,4.061,-51.558,4.234,-51.547,4.311,-51.462,4.314,-51.327,4.225,-51.22,4.094,-51.076,3.672,-51.052,3.282,-50.994,3.078,-50.827,2.652,-50.817,2.573,-50.79,2.478,-50.737,2.377,-50.679,2.21,-50.677,2.179,-50.714,2.134,-50.659,2.131,-50.609,2.104,-50.576,1.999,-50.534,1.927,-50.459,1.83,-50.304,1.798,-50.188,1.786,-50.055,1.731,-49.957,1.66,-49.882,1.42,-49.906,1.269,-49.899,1.163,-49.938,1.121,-50.047,1.052,-50.071,1.015,-50.294,0.836,-50.343,0.751,-50.463,0.637,-50.582,0.421,-50.755,0.223,-50.816,0.173,-50.91,0.161,-50.967,0.13,-51.102,-0.031,-51.283,-0.085,-51.3,-0.179,-51.404,-0.393,-51.496,-0.509,-51.555,-0.549,-51.703,-0.762,-51.722,-0.855,-51.721,-1.018,-51.819,-1.118,-51.922,-1.181,-51.934,-1.32,-51.981,-1.368,-52.02,-1.399,-52.229,-1.362,-52.553,-1.514,-52.664,-1.552,-52.31,-1.56,-52.197,-1.64,-51.948,-1.587,-51.646,-1.394,-51.531,-1.354,-51.297,-1.224,-51.202,-1.137,-51.029,-1.032,-50.992,-0.986,-50.895,-0.938,-50.842,-1,-50.838,-1.039,-50.918,-1.115,-50.897,-1.164,-50.845,-1.226,-50.826,-1.311,-50.819,-1.376,-50.786,-1.49,-50.679,-1.644,-50.675,-1.695,-50.69,-1.762,-50.639,-1.817,-50.586,-1.85,-50.403,-2.016,-50.26,-1.923,-50.173,-1.896,-50.117,-1.858,-49.999,-1.832,-49.903,-1.871,-49.72,-1.926,-49.585,-1.867,-49.314,-1.732,-49.399,-1.972,-49.46,-2.192,-49.507,-2.28,-49.553,-2.52,-49.599,-2.584,-49.637,-2.657,-49.576,-2.631,-49.524,-2.597,-49.458,-2.505,-49.408,-2.344,-49.211,-1.917,-49.155,-1.879,-48.991,-1.83,-48.71,-1.488,-48.6,-1.489,-48.53,-1.567,-48.463,-1.614,-48.446,-1.52,-48.35,-1.482,-48.451,-1.436,-48.468,-1.394,-48.478,-1.324,-48.409,-1.229,-48.45,-1.146,-48.306,-1.04,-48.318,-0.961,-48.266,-0.895,-48.202,-0.828,-48.128,-0.795,-48.115,-0.737,-48.069,-0.714,-48.033,-0.705,-47.961,-0.77,-47.883,-0.693,-47.808,-0.663,-47.774,-0.677,-47.731,-0.71,-47.687,-0.725,-47.651,-0.719,-47.557,-0.67,-47.471,-0.749,-47.419,-0.766,-47.433,-0.722,-47.46,-0.681,-47.439,-0.648,-47.398,-0.627,-47.269,-0.645,-47.201,-0.68,-47.127,-0.745,-47.025,-0.75,-46.944,-0.743,-46.894,-0.78,-46.811,-0.78,-46.77,-0.837,-46.644,-0.916,-46.617,-0.971,-46.516,-0.997,-46.422,-1.03,-46.321,-1.039,-46.219,-1.031,-46.215,-1.1,-46.14,-1.118,-46.045,-1.103,-45.972,-1.187,-45.779,-1.251,-45.645,-1.348,-45.557,-1.331,-45.459,-1.356,-45.353,-1.567,-45.329,-1.717,-45.282,-1.697,-45.239,-1.629,-45.182,-1.507,-45.076,-1.466,-45.026,-1.513,-44.92,-1.589,-44.828,-1.672,-44.79,-1.725,-44.721,-1.733,-44.779,-1.799,-44.721,-1.792,-44.651,-1.746,-44.592,-1.842,-44.547,-1.946,-44.538,-2.053,-44.58,-2.114,-44.617,-2.152,-44.659,-2.228,-44.708,-2.241,-44.756,-2.266,-44.701,-2.32,-44.662,-2.373,-44.579,-2.23,-44.52,-2.19,-44.435,-2.168,-44.391,-2.27,-44.382,-2.366,-44.52,-2.405,-44.521,-2.481,-44.562,-2.524,-44.589,-2.573,-44.611,-2.677,-44.639,-2.762,-44.721,-3.142,-44.723,-3.205,-44.623,-3.138,-44.438,-2.944,-44.381,-2.738,-44.308,-2.535,-44.229,-2.471,-44.179,-2.471,-44.106,-2.493,-44.101,-2.56,-44.113,-2.599,-44.192,-2.7,-44.225,-2.755,-44.193,-2.81,-44.013,-2.642,-43.933,-2.583,-43.864,-2.595,-43.729,-2.518,-43.455,-2.502,-43.435,-2.414,-43.38,-2.376,-43.23,-2.386,-42.937,-2.465,-42.832,-2.53,-42.676,-2.59,-42.594,-2.661,-42.25,-2.792,-42,-2.806,-41.876,-2.747,-41.722,-2.809,-41.64,-2.879,-41.48,-2.917,-41.318,-2.936,-41.195,-2.886,-40.876,-2.87,-40.475,-2.796,-40.235,-2.813,-39.965,-2.862,-39.772,-2.986,-39.609,-3.056,-39.511,-3.126,-39.353,-3.197,-39.014,-3.39,-38.896,-3.502,-38.686,-3.654,-38.476,-3.717,-38.362,-3.876,-38.272,-3.948,-38.049,-4.216,-37.796,-4.404,-37.626,-4.592,-37.301,-4.713,-37.175,-4.912,-36.955,-4.937,-36.861,-4.967,-36.747,-5.051,-36.591,-5.098,-36.387,-5.084,-36.162,-5.094,-35.98,-5.054,-35.549,-5.129,-35.482,-5.166,-35.393,-5.251,-35.235,-5.567,-35.142,-5.917,-35.095,-6.185,-34.988,-6.394,-34.93,-6.785,-34.88,-6.908,-34.876,-7.003,-34.834,-7.024,-34.805,-7.288,-34.817,-7.395,-34.858,-7.533,-34.861,-7.595,-34.855,-7.634,-34.873,-7.692,-34.879,-7.747,-34.837,-7.872,-34.835,-7.971,-34.891,-8.092,-34.967,-8.408,-35.158,-8.931,-35.341,-9.231,-35.597,-9.541,-35.764,-9.703,-35.83,-9.719,-35.891,-9.687,-35.848,-9.772,-35.885,-9.848,-36.055,-10.076,-36.224,-10.225,-36.398,-10.484,-36.412,-10.49,-36.636,-10.59,-36.768,-10.672,-36.938,-10.82,-37.093,-11.055,-37.125,-11.085,-37.183,-11.068,-37.181,-11.187,-37.315,-11.376,-37.356,-11.404,-37.355,-11.35,-37.332,-11.31,-37.321,-11.267,-37.322,-11.215,-37.359,-11.253,-37.438,-11.394,-37.412,-11.497,-37.469,-11.654,-37.689,-12.1,-37.957,-12.475,-38.019,-12.591,-38.24,-12.844,-38.402,-12.966,-38.447,-12.967,-38.499,-12.957,-38.525,-12.762,-38.654,-12.645,-38.691,-12.624,-38.744,-12.749,-38.788,-12.783,-38.852,-12.79,-38.784,-12.844,-38.764,-12.907,-38.833,-13.033,-38.835,-13.147,-38.959,-13.273,-39.031,-13.365,-39.067,-13.48,-39.089,-13.588,-39.035,-13.559,-39.009,-13.581,-38.989,-13.615,-39.001,-13.665,-39.041,-13.758,-39.035,-13.991,-39.048,-14.044,-39.008,-14.101,-38.967,-14.003,-38.942,-14.031,-39.06,-14.655,-39.013,-14.936,-38.996,-15.254,-38.943,-15.564,-38.885,-15.842,-38.881,-15.864,-38.961,-16.187,-39.063,-16.504,-39.125,-16.764,-39.164,-17.044,-39.203,-17.178,-39.215,-17.316,-39.171,-17.642,-39.154,-17.704,-39.278,-17.849,-39.413,-17.92,-39.487,-17.99,-39.651,-18.252,-39.74,-18.64,-39.742,-18.846,-39.7,-19.278,-39.731,-19.454,-39.783,-19.572,-39.845,-19.649,-40.001,-19.742,-40.142,-19.968,-40.203,-20.206,-40.299,-20.293,-40.319,-20.426,-40.396,-20.569,-40.597,-20.784,-40.727,-20.846,-40.789,-20.906,-40.829,-21.031,-40.955,-21.238,-41.047,-21.506,-41.023,-21.597,-41.022,-21.611,-40.988,-21.92,-41,-21.999,-41.123,-22.084,-41.583,-22.244,-41.706,-22.31,-41.98,-22.581,-41.998,-22.645,-41.986,-22.736,-41.941,-22.788,-41.987,-22.845,-42.042,-22.947,-42.122,-22.941,-42.581,-22.941,-42.829,-22.973,-42.958,-22.967,-43.016,-22.943,-43.081,-22.903,-43.101,-22.85,-43.065,-22.771,-43.086,-22.723,-43.154,-22.725,-43.229,-22.748,-43.242,-22.795,-43.237,-22.829,-43.209,-22.878,-43.194,-22.939,-43.224,-22.991,-43.369,-22.998,-43.533,-23.046,-43.737,-23.067,-43.899,-23.101,-43.974,-23.057,-43.899,-23.035,-43.791,-23.046,-43.676,-23.009,-43.703,-22.966,-43.866,-22.911,-44.047,-22.945,-44.148,-23.011,-44.368,-23.005,-44.637,-23.055,-44.681,-23.107,-44.674,-23.207,-44.621,-23.229,-44.57,-23.274,-44.619,-23.316,-44.667,-23.335,-44.952,-23.381,-45.215,-23.576,-45.325,-23.6,-45.423,-23.685,-45.433,-23.758,-45.464,-23.803,-45.527,-23.805,-45.665,-23.765,-45.843,-23.764,-45.972,-23.796,-46.631,-24.11,-46.867,-24.236,-47.137,-24.493,-47.592,-24.781,-47.831,-24.953,-47.877,-24.997,-47.914,-25,-47.989,-25.036,-47.959,-25.065,-47.908,-25.068,-47.929,-25.168,-48.024,-25.237,-48.203,-25.417,-48.242,-25.403,-48.186,-25.31,-48.273,-25.306,-48.402,-25.272,-48.458,-25.311,-48.428,-25.403,-48.476,-25.443,-48.564,-25.447,-48.644,-25.437,-48.732,-25.369,-48.692,-25.492,-48.507,-25.521,-48.43,-25.55,-48.401,-25.597,-48.545,-25.816,-48.666,-25.844,-48.679,-25.875,-48.613,-25.875,-48.576,-25.935,-48.619,-26.179,-48.679,-26.226,-48.714,-26.227,-48.748,-26.269,-48.701,-26.348,-48.652,-26.406,-48.658,-26.519,-48.677,-26.612,-48.678,-26.703,-48.616,-26.878,-48.593,-27.058,-48.568,-27.123,-48.554,-27.196,-48.596,-27.264,-48.572,-27.373,-48.643,-27.558,-48.606,-27.825,-48.621,-28.076,-48.648,-28.207,-48.693,-28.31,-48.797,-28.443,-48.8,-28.575,-49.024,-28.699,-49.271,-28.871,-49.5,-29.075,-49.746,-29.363,-50.033,-29.801,-50.3,-30.426,-50.62,-30.898,-50.748,-31.068,-50.921,-31.258,-51.152,-31.48,-51.46,-31.702,-51.798,-31.9,-51.92,-31.99,-52.039,-32.115,-52.069,-32.063,-52.043,-31.978,-52.06,-31.913,-52.063,-31.83,-51.995,-31.815,-51.893,-31.868,-51.841,-31.832,-51.803,-31.797,-51.681,-31.775,-51.446,-31.557,-51.272,-31.477,-51.174,-31.34,-51.158,-31.267,-51.161,-31.119,-51.106,-31.081,-50.98,-31.094,-50.954,-31.052,-50.965,-31.005,-50.941,-30.904,-50.77,-30.813,-50.689,-30.704,-50.716,-30.426,-50.685,-30.413,-50.615,-30.457,-50.582,-30.439,-50.547,-30.317,-50.564,-30.254,-50.646,-30.237,-50.932,-30.374,-51.025,-30.369,-51.04,-30.261,-51.179,-30.211,-51.234,-30.121,-51.25,-30.06,-51.298,-30.035,-51.295,-30.141,-51.282,-30.244,-51.157,-30.364,-51.188,-30.412,-51.247,-30.468,-51.288,-30.591,-51.283,-30.752,-51.316,-30.703,-51.359,-30.675,-51.376,-30.847,-51.459,-30.913,-51.485,-30.978,-51.464,-31.053,-51.506,-31.104,-51.717,-31.244,-51.927,-31.339,-51.972,-31.384,-51.995,-31.49,-52.027,-31.599,-52.12,-31.695,-52.194,-31.886,-52.192,-31.968,-52.167,-32.088,-52.127,-32.168,-52.19,-32.221,-52.275,-32.324,-52.342,-32.44,-52.508,-32.875,-52.652,-33.138,-52.763,-33.266,-52.921,-33.402,-53.371,-33.742,-53.398,-33.737,-53.464,-33.71,-53.519,-33.677,-53.531,-33.655,-53.538,-33.623,-53.53,-33.5,-53.531,-33.171,-53.512,-33.109,-53.483,-33.069,-53.395,-33.01,-53.31,-32.927,-53.214,-32.821,-53.126,-32.737,-53.157,-32.68,-53.231,-32.625,-53.363,-32.581,-53.489,-32.503,-53.602,-32.403,-53.654,-32.299,-53.701,-32.186,-53.747,-32.097,-53.762,-32.057,-53.806,-32.04,-53.877,-31.995,-53.921,-31.952,-53.985,-31.928,-54.1,-31.902,-54.221,-31.855,-54.37,-31.745,-54.478,-31.623,-54.531,-31.542,-54.588,-31.485,-54.896,-31.391,-55.036,-31.279,-55.091,-31.314,-55.174,-31.28,-55.255,-31.226,-55.279,-31.184,-55.313,-31.142,-55.346,-31.093,-55.366,-31.046,-55.45,-30.964,-55.557,-30.876,-55.603,-30.851,-55.627,-30.858,-55.65,-30.892,-55.665,-30.925,-55.706,-30.947,-55.756,-30.987,-55.808,-31.037,-55.874,-31.07,-55.952,-31.081,-56.005,-31.079,-56.016,-31.06,-56.018,-30.992,-55.999,-30.837,-56.045,-30.778,-56.106,-30.714,-56.176,-30.628,-56.407,-30.447,-56.722,-30.187,-56.833,-30.107,-56.937,-30.101,-57.033,-30.11,-57.121,-30.144,-57.187,-30.265,-57.214,-30.283,-57.384,-30.281,-57.552,-30.261,-57.609,-30.188,-57.564,-30.14,-57.405,-30.034,-57.317,-29.939,-57.301,-29.857,-57.225,-29.782,-57.089,-29.716,-56.939,-29.595,-56.772,-29.418,-56.672,-29.287,-56.636,-29.203,-56.571,-29.138,-56.476,-29.092,-56.393,-28.997,-56.322,-28.852,-56.226,-28.737,-56.103,-28.652,-56.034,-28.581,-56.02,-28.525,-55.985,-28.489,-55.93,-28.473,-55.904,-28.443,-55.905,-28.4,-55.891,-28.37,-55.859,-28.354,-55.806,-28.36,-55.732,-28.387,-55.687,-28.382,-55.672,-28.345,-55.692,-28.303,-55.746,-28.255,-55.725,-28.204,-55.582,-28.121,-55.477,-28.089,-55.41,-28.038,-55.346,-27.956,-55.244,-27.899,-55.102,-27.867,-55.064,-27.836,-55.069,-27.796,-55.04,-27.768,-54.956,-27.747,-54.91,-27.709,-54.903,-27.652,-54.876,-27.599,-54.829,-27.551,-54.777,-27.533,-54.72,-27.545,-54.666,-27.527,-54.615,-27.477,-54.555,-27.454,-54.484,-27.457,-54.448,-27.446,-54.327,-27.424,-54.26,-27.382,-54.205,-27.29,-54.156,-27.254,-54.114,-27.275,-54.04,-27.244,-53.935,-27.161,-53.916,-27.16,-53.838,-27.121,-53.758,-26.978,-53.717,-26.883,-53.727,-26.805,-53.753,-26.749,-53.745,-26.667,-53.718,-26.443,-53.711,-26.352,-53.669,-26.288,-53.671,-26.225,-53.747,-26.084,-53.823,-25.96,-53.864,-25.749,-53.891,-25.669,-53.955,-25.648,-54.012,-25.578,-54.085,-25.572,-54.119,-25.545,-54.155,-25.523,-54.206,-25.53,-54.25,-25.57,-54.332,-25.572,-54.383,-25.589,-54.444,-25.625,-54.502,-25.608,-54.538,-25.576,-54.616,-25.576,-54.611,-25.433,-54.473,-25.22,-54.436,-25.121,-54.454,-25.065,-54.413,-24.867,-54.313,-24.528,-54.281,-24.306,-54.317,-24.201,-54.318,-24.128,-54.267,-24.066,-54.242,-24.047,-54.371,-23.971,-54.44,-23.902,-54.53,-23.852,-54.625,-23.812,-54.672,-23.829,-54.721,-23.852,-54.817,-23.888,-54.926,-23.951,-54.983,-23.975,-55.082,-23.998,-55.194,-24.017,-55.287,-24.004,-55.366,-23.991,-55.416,-23.951,-55.442,-23.865,-55.442,-23.793,-55.459,-23.687,-55.518,-23.627,-55.538,-23.581,-55.542,-23.525,-55.535,-23.462,-55.518,-23.416,-55.528,-23.359,-55.555,-23.32,-55.548,-23.25,-55.561,-23.154,-55.601,-23.095,-55.621,-23.025,-55.621,-22.956,-55.651,-22.886,-55.654,-22.81,-55.628,-22.741,-55.618,-22.671,-55.647,-22.622,-55.704,-22.592,-55.747,-22.513,-55.753,-22.41,-55.8,-22.354,-55.849,-22.308,-55.905,-22.308,-55.991,-22.281,-56.067,-22.284,-56.19,-22.281,-56.246,-22.265,-56.276,-22.228,-56.352,-22.179,-56.395,-22.093,-56.448,-22.076,-56.524,-22.103,-56.55,-22.136,-56.58,-22.182,-56.633,-22.235,-56.702,-22.232,-56.775,-22.261,-56.845,-22.265,-56.937,-22.271,-57.03,-22.245,-57.142,-22.215,-57.238,-22.195,-57.331,-22.215,-57.394,-22.198,-57.476,-22.189,-57.569,-22.182,-57.642,-22.129,-57.721,-22.099,-57.764,-22.109,-57.82,-22.142,-57.88,-22.136,-57.956,-22.109,-57.986,-22.046,-57.979,-22.007,-57.962,-21.967,-57.933,-21.911,-57.949,-21.851,-57.943,-21.798,-57.929,-21.752,-57.916,-21.699,-57.926,-21.65,-57.929,-21.597,-57.936,-21.547,-57.946,-21.494,-57.906,-21.418,-57.873,-21.355,-57.893,-21.302,-57.886,-21.266,-57.86,-21.206,-57.827,-21.134,-57.83,-20.998,-57.86,-20.919,-57.892,-20.897,-57.9,-20.873,-57.885,-20.842,-57.902,-20.809,-57.908,-20.776,-57.891,-20.747,-57.915,-20.69,-57.962,-20.674,-57.979,-20.657,-57.996,-20.594,-58.009,-20.522,-58.002,-20.465,-58.025,-20.416,-58.058,-20.386,-58.092,-20.333,-58.125,-20.293,-58.138,-20.237,-58.16,-20.165,-58.094,-20.151,-58.068,-20.11,-58.021,-20.055,-57.96,-20.041,-57.888,-20.02,-57.861,-19.98,-58.03,-19.833,-58.131,-19.745,-58.072,-19.625,-57.972,-19.424,-57.875,-19.229,-57.8,-19.081,-57.781,-19.054,-57.717,-19.044,-57.729,-18.967,-57.731,-18.917,-57.783,-18.914,-57.725,-18.733,-57.639,-18.475,-57.574,-18.279,-57.553,-18.246,-57.506,-18.237,-57.496,-18.215,-57.552,-18.183,-57.586,-18.122,-57.662,-17.947,-57.78,-17.672,-57.789,-17.573,-57.832,-17.512,-57.905,-17.532,-57.991,-17.513,-58.206,-17.363,-58.348,-17.282,-58.396,-17.234,-58.417,-17.081,-58.46,-16.911,-58.478,-16.701,-58.471,-16.65,-58.35,-16.491,-58.351,-16.41,-58.341,-16.34,-58.346,-16.284,-58.375,-16.284,-58.424,-16.308,-58.497,-16.327,-58.538,-16.328,-58.957,-16.313,-59.434,-16.296,-59.831,-16.282,-60.176,-16.269,-60.187,-16.132,-60.207,-15.902,-60.22,-15.739,-60.242,-15.48,-60.38,-15.318,-60.53,-15.143,-60.583,-15.098,-60.402,-15.093,-60.273,-15.089,-60.299,-14.619,-60.338,-14.571,-60.373,-14.419,-60.396,-14.333,-60.46,-14.263,-60.475,-14.185,-60.463,-14.132,-60.428,-14.1,-60.405,-14.019,-60.422,-13.938,-60.46,-13.862,-60.507,-13.79,-60.595,-13.745,-60.722,-13.664,-60.915,-13.561,-61.077,-13.49,-61.129,-13.499,-61.416,-13.527,-61.512,-13.541,-61.576,-13.525,-61.79,-13.526,-61.874,-13.47,-61.945,-13.406,-62.095,-13.242,-62.118,-13.16,-62.176,-13.134,-62.264,-13.144,-62.353,-13.132,-62.526,-13.064,-62.687,-12.994,-62.765,-12.997,-62.835,-12.954,-62.958,-12.847,-63.015,-12.806,-63.041,-12.75,-63.067,-12.669,-63.117,-12.652,-63.181,-12.666,-63.25,-12.708,-63.347,-12.68,-63.465,-12.605,-63.542,-12.547,-63.586,-12.519,-63.689,-12.478,-63.788,-12.469,-63.939,-12.53,-64.062,-12.505,-64.255,-12.483,-64.421,-12.44,-64.481,-12.326,-64.513,-12.251,-64.612,-12.204,-64.69,-12.146,-64.783,-12.059,-64.83,-12.03,-64.914,-12.006,-64.993,-11.975,-65.001,-11.92,-65.03,-11.847,-65.037,-11.829,-65.09,-11.741,-65.115,-11.735,-65.143,-11.752,-65.163,-11.765,-65.186,-11.75,-65.19,-11.71,-65.175,-11.647,-65.206,-11.581,-65.282,-11.511,-65.322,-11.439,-65.325,-11.365,-65.342,-11.315,-65.373,-11.29,-65.39,-11.246,-65.394,-11.184,-65.372,-11.11,-65.324,-11.025,-65.334,-10.893,-65.402,-10.715,-65.44,-10.586,-65.447,-10.507,-65.437,-10.449,-65.395,-10.392,-65.313,-10.253,-65.299,-10.147,-65.325,-10.027,-65.328,-9.936,-65.309,-9.873,-65.338,-9.79,-65.396,-9.712,-65.437,-9.71,-65.492,-9.732,-65.559,-9.797,-65.637,-9.809,-65.707,-9.768,-65.925,-9.785,-66.264,-9.826,-66.399,-9.868,-66.479,-9.886,-66.575,-9.9,-66.73,-9.975,-67.112,-10.269,-67.19,-10.311,-67.28,-10.317,-67.333,-10.358,-67.417,-10.39,-67.582,-10.506,-67.667,-10.599,-67.722,-10.683,-67.786,-10.686,-67.835,-10.663,-67.992,-10.674,-68.072,-10.703,-68.159,-10.785,-68.267,-10.933,-68.311,-10.975,-68.398,-11.019,-68.498,-11.055,-68.623,-11.109,-68.678,-11.113,-68.727,-11.122,-68.77,-11.098,-68.784,-11.045,-68.848,-11.011,-69.002,-10.994,-69.229,-10.956,-69.463,-10.948,-69.579,-10.952,-69.674,-10.954,-69.84,-10.933,-69.96,-10.93,-70.066,-10.982,-70.22,-11.048,-70.29,-11.064,-70.342,-11.067,-70.392,-11.059,-70.451,-11.025,-70.533,-10.947,-70.597,-10.977,-70.642,-11.01,-70.642,-10.841,-70.64,-10.586,-70.639,-10.361,-70.639,-10.182,-70.638,-9.972,-70.637,-9.824,-70.594,-9.767,-70.567,-9.705,-70.599,-9.621,-70.592,-9.543,-70.57,-9.49,-70.541,-9.437,-70.608,-9.464,-70.637,-9.478,-70.672,-9.518,-70.758,-9.572,-70.816,-9.625,-70.885,-9.669,-70.971,-9.766,-71.042,-9.819,-71.115,-9.852,-71.238,-9.966,-71.339,-9.989,-71.608,-10.006,-71.887,-10.006,-72.143,-10.005,-72.182,-10.004,-72.179,-9.91,-72.173,-9.844,-72.26,-9.774,-72.266,-9.688,-72.289,-9.629,-72.318,-9.557,-72.379,-9.51,-72.465,-9.492,-72.605,-9.452,-72.814,-9.41,-73.014,-9.407,-73.209,-9.411,-73.09,-9.266,-72.97,-9.12,-72.974,-8.993,-73.071,-8.883,-73.123,-8.814,-73.203,-8.719,-73.302,-8.654,-73.357,-8.567,-73.352,-8.514,-73.36,-8.479,-73.398,-8.459,-73.436,-8.427,-73.488,-8.392,-73.549,-8.346,-73.549,-8.299,-73.572,-8.25,-73.61,-8.192,-73.61,-8.145,-73.645,-8.073,-73.683,-8.021,-73.72,-7.986,-73.776,-7.936,-73.773,-7.896,-73.732,-7.875,-73.715,-7.829,-73.72,-7.783,-73.767,-7.754,-73.822,-7.739,-73.895,-7.655,-73.947,-7.611,-73.982,-7.585,-74.002,-7.556,-73.982,-7.536,-73.958,-7.507,-73.953,-7.46,-73.964,-7.417,-73.964,-7.379,-73.929,-7.367,-73.892,-7.373,-73.854,-7.35,-73.805,-7.341,-73.749,-7.335,-73.72,-7.309,-73.723,-7.263,-73.758,-7.173,-73.793,-7.135,-73.805,-7.08,-73.776,-6.974,-73.758,-6.906,-73.695,-6.834,-73.5,-6.679,-73.325,-6.575,-73.24,-6.564,-73.177,-6.525,-73.137,-6.466,-73.126,-6.401,-73.135,-6.344,-73.168,-6.261,-73.206,-6.156,-73.236,-6.098,-73.209,-6.029,-73.163,-5.933,-73.068,-5.79,-72.98,-5.635,-72.97,-5.59,-72.959,-5.495,-72.918,-5.303,-72.896,-5.198,-72.907,-5.158,-72.887,-5.123,-72.832,-5.094,-72.699,-5.067,-72.608,-5.01,-72.469,-4.901,-72.353,-4.786,-72.257,-4.749,-72.083,-4.642,-71.982,-4.575,-71.943,-4.553,-71.845,-4.504,-71.668,-4.487,-71.521,-4.47,-71.438,-4.438,-71.317,-4.424,-71.235,-4.388,-71.144,-4.387,-70.974,-4.35,-70.916,-4.295,-70.866,-4.23,-70.8,-4.173,-70.722,-4.159,-70.635,-4.169,-70.531,-4.168,-70.405,-4.15,-70.344,-4.194,-70.317,-4.247,-70.239,-4.301,-70.184,-4.298,-70.129,-4.287,-70.053,-4.333,-70.004,-4.327,-69.972,-4.301,-69.966,-4.236,-69.948,-4.201,-69.911,-3.997,-69.85,-3.66,-69.794,-3.355,-69.733,-3.017,-69.669,-2.668,-69.605,-2.314,-69.552,-2.024,-69.506,-1.775,-69.479,-1.622,-69.435,-1.422,-69.418,-1.246,-69.4,-1.195,-69.411,-1.152,-69.449,-1.092,-69.449,-1.065,-69.444,-1.03,-69.449,-0.999,-69.488,-0.966,-69.519,-0.946,-69.544,-0.917,-69.555,-0.877,-69.574,-0.838,-69.583,-0.796,-69.612,-0.763,-69.621,-0.721,-69.601,-0.681,-69.592,-0.639,-69.601,-0.6,-69.612,-0.553,-69.634,-0.509,-69.667,-0.482,-69.747,-0.453,-69.828,-0.381,-69.923,-0.317,-70.044,-0.196,-70.071,-0.139,-70.071,0.019,-70.066,0.189,-70.058,0.447,-70.054,0.579,-69.985,0.586,-69.925,0.589,-69.862,0.598,-69.807,0.607,-69.757,0.626,-69.719,0.65,-69.674,0.665,-69.639,0.66,-69.604,0.68,-69.565,0.7,-69.527,0.716,-69.472,0.73,-69.421,0.698,-69.392,0.667,-69.359,0.652,-69.327,0.655,-69.306,0.652,-69.283,0.627,-69.254,0.625,-69.213,0.63,-69.174,0.635,-69.156,0.643,-69.153,0.659,-69.163,0.687,-69.177,0.713,-69.166,0.753,-69.165,0.802,-69.163,0.864,-69.194,0.898,-69.224,0.963,-69.259,1.015,-69.312,1.05,-69.361,1.064,-69.403,1.042,-69.442,1.039,-69.47,1.059,-69.517,1.059,-69.568,1.066,-69.621,1.073,-69.717,1.059,-69.751,1.077,-69.798,1.078,-69.852,1.06,-69.851,1.309,-69.849,1.544,-69.849,1.709,-69.8,1.705,-69.74,1.735,-69.65,1.739,-69.581,1.771,-69.543,1.773,-69.47,1.758,-69.394,1.726,-69.32,1.721,-69.124,1.721,-68.913,1.721,-68.678,1.721,-68.443,1.722,-68.24,1.722,-68.177,1.72,-68.213,1.775,-68.256,1.846,-68.239,1.901,-68.218,1.958,-68.194,1.987,-68.13,1.956,-68.077,1.86,-68.033,1.788,-67.99,1.753,-67.936,1.748,-67.876,1.761,-67.815,1.79,-67.712,1.922,-67.609,2.035,-67.556,2.073,-67.5,2.108,-67.458,2.121,-67.4,2.117,-67.352,2.086,-67.321,2.032,-67.206,1.845,-67.119,1.704,-67.09,1.616,-67.088,1.401,-67.094,1.21,-67.082,1.185,-67.065,1.178,-66.876,1.223]],[[-49.629,-0.229,-49.535,-0.234,-49.403,-0.215,-49.314,-0.168,-49.215,-0.159,-49.117,-0.164,-48.787,-0.216,-48.588,-0.232,-48.515,-0.248,-48.444,-0.272,-48.393,-0.297,-48.38,-0.353,-48.428,-0.442,-48.464,-0.535,-48.497,-0.665,-48.523,-0.691,-48.567,-0.684,-48.54,-0.801,-48.55,-0.848,-48.571,-0.893,-48.624,-0.987,-48.705,-1.107,-48.729,-1.132,-48.79,-1.173,-48.84,-1.227,-48.829,-1.277,-48.804,-1.327,-48.834,-1.39,-48.929,-1.482,-48.986,-1.505,-49.038,-1.514,-49.087,-1.505,-49.173,-1.413,-49.182,-1.485,-49.205,-1.559,-49.234,-1.6,-49.345,-1.595,-49.407,-1.556,-49.507,-1.512,-49.526,-1.63,-49.588,-1.712,-49.651,-1.738,-49.749,-1.755,-49.805,-1.79,-49.911,-1.763,-50.01,-1.708,-50.066,-1.704,-50.109,-1.748,-50.338,-1.756,-50.443,-1.801,-50.508,-1.788,-50.602,-1.698,-50.617,-1.638,-50.673,-1.516,-50.724,-1.371,-50.76,-1.24,-50.729,-1.127,-50.668,-1.131,-50.596,-1.147,-50.581,-1.139,-50.577,-1.103,-50.593,-1.073,-50.71,-1.078,-50.783,-1.01,-50.796,-0.906,-50.781,-0.69,-50.771,-0.645,-50.72,-0.583,-50.703,-0.529,-50.716,-0.47,-50.694,-0.364,-50.646,-0.273,-50.462,-0.157,-50.248,-0.116,-49.629,-0.229]],[[-44.129,-23.142,-44.098,-23.169,-44.156,-23.167,-44.221,-23.191,-44.32,-23.212,-44.36,-23.172,-44.274,-23.116,-44.243,-23.074,-44.22,-23.083,-44.192,-23.113,-44.129,-23.142]],[[-48.584,-26.402,-48.603,-26.414,-48.666,-26.29,-48.54,-26.17,-48.498,-26.219,-48.531,-26.313,-48.568,-26.38,-48.584,-26.402]],[[-45.26,-23.889,-45.261,-23.941,-45.303,-23.915,-45.413,-23.935,-45.451,-23.896,-45.302,-23.728,-45.272,-23.752,-45.249,-23.783,-45.233,-23.825,-45.25,-23.853,-45.26,-23.889]],[[-44.499,-2.94,-44.598,-3.038,-44.565,-2.924,-44.582,-2.846,-44.569,-2.785,-44.502,-2.726,-44.481,-2.718,-44.487,-2.79,-44.483,-2.812,-44.499,-2.94]],[[-38.744,-13.097,-38.783,-13.119,-38.787,-13.055,-38.685,-12.975,-38.668,-12.88,-38.615,-12.924,-38.6,-12.972,-38.601,-12.993,-38.744,-13.097]],[[-38.904,-13.473,-38.938,-13.532,-38.978,-13.524,-38.993,-13.484,-39.022,-13.446,-39.007,-13.416,-38.98,-13.398,-38.907,-13.401,-38.904,-13.473]],[[-44.883,-1.318,-44.947,-1.366,-44.968,-1.391,-45.021,-1.372,-45.011,-1.345,-44.996,-1.348,-44.979,-1.267,-44.888,-1.277,-44.883,-1.318]],[[-49.738,0.268,-49.697,0.216,-49.839,0.007,-49.917,-0.023,-50.002,-0.029,-50.113,0.033,-50.286,0.029,-50.339,0.043,-50.345,0.134,-50.273,0.232,-50.128,0.227,-49.879,0.305,-49.738,0.268]],[[-50.299,1.939,-50.399,1.893,-50.456,1.91,-50.509,2.03,-50.491,2.129,-50.419,2.161,-50.363,2.154,-50.342,2.142,-50.292,1.98,-50.299,1.939]],[[-50.653,-0.132,-50.926,-0.327,-51.019,-0.263,-51.038,-0.226,-51.022,-0.188,-51.026,-0.172,-50.995,-0.105,-50.842,-0.05,-50.765,-0.041,-50.667,-0.058,-50.651,-0.106,-50.653,-0.132]],[[-49.444,-0.112,-49.709,-0.144,-49.83,-0.094,-49.803,-0.052,-49.712,0.015,-49.602,0.063,-49.503,0.084,-49.4,0.057,-49.372,0.001,-49.381,-0.055,-49.444,-0.112]],[[-50.426,0.139,-50.444,-0.008,-50.624,0.054,-50.61,0.205,-50.526,0.247,-50.452,0.327,-50.426,0.425,-50.425,0.558,-50.397,0.581,-50.373,0.591,-50.351,0.582,-50.343,0.382,-50.332,0.259,-50.426,0.139]],[[-50.153,0.393,-50.261,0.359,-50.282,0.391,-50.282,0.517,-50.251,0.585,-50.113,0.605,-50.099,0.625,-50.059,0.638,-50.037,0.595,-50.04,0.523,-50.153,0.393]],[[-51.833,-1.434,-51.938,-1.453,-51.802,-1.203,-51.68,-1.086,-51.678,-0.855,-51.546,-0.65,-51.424,-0.566,-51.254,-0.541,-51.161,-0.667,-51.276,-1.022,-51.31,-1.024,-51.465,-1.211,-51.638,-1.342,-51.833,-1.434]],[[-48.486,-27.767,-48.555,-27.812,-48.542,-27.575,-48.505,-27.496,-48.465,-27.436,-48.415,-27.4,-48.378,-27.451,-48.41,-27.566,-48.497,-27.707,-48.486,-27.767]]]},{"n":"Botswana","c":"BW","r":4,"ml":4,"lx":24.18,"ly":-22.1,"b":[19.977,-26.854,29.365,-17.788],"p":[[[25.259,-17.794,25.239,-17.843,25.224,-17.915,25.242,-17.969,25.282,-18.041,25.34,-18.104,25.384,-18.142,25.437,-18.235,25.489,-18.351,25.558,-18.442,25.761,-18.649,25.784,-18.724,25.812,-18.797,25.939,-18.939,25.959,-18.986,25.951,-19.082,26.082,-19.37,26.168,-19.538,26.241,-19.569,26.475,-19.749,26.678,-19.893,26.917,-19.99,27.092,-20.054,27.178,-20.101,27.221,-20.146,27.257,-20.232,27.275,-20.382,27.281,-20.479,27.469,-20.475,27.625,-20.484,27.679,-20.503,27.7,-20.531,27.695,-20.595,27.697,-20.69,27.704,-20.766,27.688,-20.848,27.677,-20.945,27.669,-21.064,27.693,-21.111,27.844,-21.262,27.907,-21.359,27.975,-21.507,28.014,-21.554,28.046,-21.573,28.182,-21.589,28.532,-21.651,28.748,-21.708,28.919,-21.766,28.991,-21.781,29.026,-21.797,29.037,-21.811,29.016,-21.94,29.023,-21.981,29.042,-22.018,29.071,-22.047,29.107,-22.066,29.237,-22.079,29.315,-22.158,29.365,-22.194,29.13,-22.213,29.013,-22.278,28.946,-22.395,28.84,-22.481,28.696,-22.535,28.543,-22.573,28.382,-22.593,28.21,-22.694,28.028,-22.874,27.935,-22.987,27.931,-23.034,27.891,-23.074,27.813,-23.108,27.769,-23.149,27.758,-23.197,27.717,-23.22,27.644,-23.218,27.593,-23.253,27.563,-23.325,27.499,-23.368,27.399,-23.384,27.313,-23.424,27.241,-23.49,27.186,-23.523,27.146,-23.524,27.086,-23.578,26.987,-23.705,26.971,-23.763,26.835,-24.241,26.761,-24.297,26.618,-24.396,26.502,-24.513,26.452,-24.583,26.397,-24.614,26.131,-24.671,26.032,-24.702,25.912,-24.747,25.882,-24.788,25.852,-24.935,25.77,-25.146,25.703,-25.302,25.659,-25.438,25.584,-25.606,25.518,-25.663,25.444,-25.714,25.346,-25.74,25.213,-25.756,25.092,-25.751,24.999,-25.754,24.869,-25.813,24.748,-25.817,24.556,-25.783,24.4,-25.75,24.331,-25.743,24.193,-25.633,24.104,-25.635,23.97,-25.626,23.894,-25.601,23.823,-25.545,23.671,-25.434,23.521,-25.344,23.389,-25.291,23.266,-25.267,23.149,-25.289,23.058,-25.312,23.022,-25.324,22.951,-25.37,22.879,-25.458,22.819,-25.595,22.796,-25.679,22.729,-25.857,22.64,-26.071,22.598,-26.133,22.549,-26.178,22.471,-26.219,22.218,-26.389,22.091,-26.58,22.011,-26.636,21.915,-26.662,21.833,-26.678,21.788,-26.71,21.738,-26.807,21.695,-26.841,21.646,-26.854,21.501,-26.843,21.455,-26.833,21.071,-26.852,20.954,-26.821,20.871,-26.809,20.74,-26.849,20.685,-26.822,20.641,-26.742,20.62,-26.581,20.627,-26.444,20.698,-26.34,20.757,-26.264,20.815,-26.165,20.823,-26.121,20.811,-26.081,20.799,-25.999,20.793,-25.916,20.711,-25.733,20.609,-25.491,20.473,-25.221,20.431,-25.147,20.345,-25.03,20.029,-24.807,19.98,-24.777,19.98,-24.752,19.98,-24.536,19.98,-24.249,19.98,-23.962,19.979,-23.676,19.979,-23.389,19.979,-23.103,19.978,-22.816,19.978,-22.529,19.978,-22.243,19.977,-22,20.205,-22,20.488,-22,20.823,-22,20.971,-22,20.979,-21.962,20.979,-21.784,20.979,-21.376,20.978,-20.968,20.977,-20.56,20.977,-20.152,20.976,-19.744,20.976,-19.336,20.975,-18.929,20.974,-18.521,20.974,-18.319,21.233,-18.307,21.53,-18.266,22.011,-18.199,22.46,-18.116,22.753,-18.067,23.1,-18.01,23.219,-18,23.252,-18.008,23.299,-18.027,23.46,-18.231,23.56,-18.386,23.581,-18.453,23.6,-18.46,23.647,-18.449,23.7,-18.424,23.864,-18.27,23.898,-18.229,24.003,-18.154,24.129,-18.078,24.244,-18.023,24.359,-17.978,24.412,-17.989,24.475,-18.029,24.531,-18.053,24.792,-17.865,24.909,-17.821,25.216,-17.788,25.259,-17.794]]]},{"n":"Bosnia and Herzegovina","c":"BA","r":5,"ml":4.5,"lx":18.07,"ly":44.09,"b":[15.737,42.56,19.584,45.277],"p":[[[19.349,44.881,19.357,44.859,19.334,44.781,19.292,44.697,19.223,44.61,19.151,44.527,19.132,44.484,19.127,44.415,19.118,44.36,19.128,44.33,19.152,44.303,19.232,44.281,19.339,44.226,19.43,44.154,19.547,44.073,19.584,44.043,19.584,44.011,19.55,43.987,19.449,43.978,19.345,43.985,19.305,43.993,19.268,43.983,19.245,43.965,19.257,43.943,19.364,43.845,19.488,43.704,19.495,43.643,19.48,43.595,19.451,43.562,19.4,43.568,19.36,43.593,19.301,43.592,19.254,43.584,19.194,43.533,19.164,43.535,19.113,43.528,19.08,43.518,19.028,43.533,18.974,43.542,18.951,43.527,18.94,43.497,18.974,43.442,19.037,43.357,19.027,43.292,18.979,43.285,18.935,43.339,18.896,43.348,18.851,43.346,18.749,43.284,18.674,43.231,18.657,43.194,18.63,43.154,18.622,43.125,18.624,43.028,18.488,43.012,18.46,42.998,18.444,42.968,18.455,42.844,18.466,42.777,18.543,42.674,18.546,42.642,18.535,42.62,18.48,42.579,18.454,42.565,18.436,42.56,18.347,42.587,18.304,42.599,18.124,42.691,18.045,42.741,17.919,42.807,17.841,42.845,17.802,42.902,17.74,42.915,17.668,42.897,17.585,42.938,17.643,42.96,17.658,42.98,17.65,43.007,17.625,43.043,17.402,43.199,17.293,43.306,17.275,43.344,17.274,43.446,17.248,43.47,17.085,43.517,16.902,43.649,16.713,43.779,16.688,43.815,16.591,43.913,16.472,44.003,16.378,44.06,16.3,44.125,16.214,44.215,16.17,44.352,16.13,44.474,16.103,44.521,16.049,44.538,15.88,44.682,15.737,44.766,15.738,44.856,15.762,45.008,15.788,45.179,15.823,45.203,15.888,45.216,15.963,45.211,16.028,45.19,16.157,45.072,16.231,45.027,16.293,45.009,16.365,45.058,16.454,45.162,16.531,45.217,16.791,45.197,16.919,45.277,17.125,45.172,17.211,45.156,17.259,45.171,17.324,45.164,17.469,45.133,17.503,45.12,17.546,45.123,17.654,45.163,17.69,45.158,17.813,45.078,17.874,45.077,17.949,45.112,17.996,45.142,18.137,45.119,18.218,45.133,18.285,45.134,18.358,45.121,18.424,45.102,18.488,45.086,18.663,45.077,18.746,45.027,18.779,44.977,18.78,44.947,18.788,44.915,18.836,44.883,18.941,44.865,19.007,44.869,19.042,44.871,19.132,44.9,19.237,44.914,19.313,44.897,19.349,44.881]]]},{"n":"Bolivia","c":"BO","r":3,"ml":3,"lx":-64.59,"ly":-16.67,"b":[-69.646,-22.892,-57.496,-9.71],"p":[[[-69.511,-17.506,-69.511,-17.505,-69.511,-17.46,-69.522,-17.389,-69.564,-17.333,-69.626,-17.294,-69.646,-17.249,-69.625,-17.2,-69.503,-17.105,-69.438,-17.088,-69.421,-17.04,-69.382,-17.001,-69.267,-16.861,-69.2,-16.768,-69.133,-16.713,-69.055,-16.674,-69.021,-16.642,-69.038,-16.543,-69.033,-16.476,-69.006,-16.434,-68.928,-16.389,-68.858,-16.355,-68.843,-16.338,-68.849,-16.313,-68.913,-16.262,-69.046,-16.218,-69.134,-16.222,-69.188,-16.183,-69.218,-16.149,-69.392,-15.737,-69.421,-15.641,-69.419,-15.603,-69.302,-15.399,-69.254,-15.333,-69.172,-15.237,-69.187,-15.199,-69.331,-15.039,-69.375,-14.963,-69.374,-14.887,-69.359,-14.795,-69.276,-14.746,-69.252,-14.671,-69.235,-14.597,-69.199,-14.573,-69.163,-14.531,-69.12,-14.47,-69.053,-14.418,-69.013,-14.377,-69.004,-14.265,-68.972,-14.234,-68.88,-14.199,-68.871,-14.17,-68.892,-14.094,-68.937,-14.015,-68.974,-13.976,-69.023,-13.78,-69.074,-13.683,-69.053,-13.644,-69.018,-13.594,-68.983,-13.496,-68.972,-13.382,-68.981,-12.963,-68.979,-12.88,-68.934,-12.822,-68.868,-12.755,-68.812,-12.73,-68.759,-12.687,-68.763,-12.608,-68.728,-12.561,-68.685,-12.502,-68.819,-12.27,-68.936,-12.067,-69.046,-11.876,-69.174,-11.654,-69.258,-11.509,-69.362,-11.328,-69.454,-11.169,-69.579,-10.952,-69.463,-10.948,-69.229,-10.956,-69.002,-10.994,-68.848,-11.011,-68.784,-11.045,-68.77,-11.098,-68.727,-11.122,-68.678,-11.113,-68.623,-11.109,-68.498,-11.055,-68.398,-11.019,-68.311,-10.975,-68.267,-10.933,-68.159,-10.785,-68.072,-10.703,-67.992,-10.674,-67.835,-10.663,-67.786,-10.686,-67.722,-10.683,-67.667,-10.599,-67.582,-10.506,-67.417,-10.39,-67.333,-10.358,-67.28,-10.317,-67.19,-10.311,-67.112,-10.269,-66.73,-9.975,-66.575,-9.9,-66.479,-9.886,-66.399,-9.868,-66.264,-9.826,-65.925,-9.785,-65.707,-9.768,-65.637,-9.809,-65.559,-9.797,-65.492,-9.732,-65.437,-9.71,-65.396,-9.712,-65.338,-9.79,-65.309,-9.873,-65.328,-9.936,-65.325,-10.027,-65.299,-10.147,-65.313,-10.253,-65.395,-10.392,-65.437,-10.449,-65.447,-10.507,-65.44,-10.586,-65.402,-10.715,-65.334,-10.893,-65.324,-11.025,-65.372,-11.11,-65.394,-11.184,-65.39,-11.246,-65.373,-11.29,-65.342,-11.315,-65.325,-11.365,-65.322,-11.439,-65.282,-11.511,-65.206,-11.581,-65.175,-11.647,-65.19,-11.71,-65.186,-11.75,-65.163,-11.765,-65.143,-11.752,-65.115,-11.735,-65.09,-11.741,-65.037,-11.829,-65.03,-11.847,-65.001,-11.92,-64.993,-11.975,-64.914,-12.006,-64.83,-12.03,-64.783,-12.059,-64.69,-12.146,-64.612,-12.204,-64.513,-12.251,-64.481,-12.326,-64.421,-12.44,-64.255,-12.483,-64.062,-12.505,-63.939,-12.53,-63.788,-12.469,-63.689,-12.478,-63.586,-12.519,-63.542,-12.547,-63.465,-12.605,-63.347,-12.68,-63.25,-12.708,-63.181,-12.666,-63.117,-12.652,-63.067,-12.669,-63.041,-12.75,-63.015,-12.806,-62.958,-12.847,-62.835,-12.954,-62.765,-12.997,-62.687,-12.994,-62.526,-13.064,-62.353,-13.132,-62.264,-13.144,-62.176,-13.134,-62.118,-13.16,-62.095,-13.242,-61.945,-13.406,-61.874,-13.47,-61.79,-13.526,-61.576,-13.525,-61.512,-13.541,-61.416,-13.527,-61.129,-13.499,-61.077,-13.49,-60.915,-13.561,-60.722,-13.664,-60.595,-13.745,-60.507,-13.79,-60.46,-13.862,-60.422,-13.938,-60.405,-14.019,-60.428,-14.1,-60.463,-14.132,-60.475,-14.185,-60.46,-14.263,-60.396,-14.333,-60.373,-14.419,-60.338,-14.571,-60.299,-14.619,-60.273,-15.089,-60.402,-15.093,-60.583,-15.098,-60.53,-15.143,-60.38,-15.318,-60.242,-15.48,-60.22,-15.739,-60.207,-15.902,-60.187,-16.132,-60.176,-16.269,-59.831,-16.282,-59.434,-16.296,-58.957,-16.313,-58.538,-16.328,-58.497,-16.327,-58.424,-16.308,-58.375,-16.284,-58.346,-16.284,-58.341,-16.34,-58.351,-16.41,-58.35,-16.491,-58.471,-16.65,-58.478,-16.701,-58.46,-16.911,-58.417,-17.081,-58.396,-17.234,-58.348,-17.282,-58.206,-17.363,-57.991,-17.513,-57.905,-17.532,-57.832,-17.512,-57.789,-17.573,-57.78,-17.672,-57.662,-17.947,-57.586,-18.122,-57.552,-18.183,-57.496,-18.215,-57.506,-18.237,-57.553,-18.246,-57.574,-18.279,-57.639,-18.475,-57.725,-18.733,-57.783,-18.914,-57.731,-18.917,-57.729,-18.967,-57.717,-19.044,-57.781,-19.054,-57.8,-19.081,-57.875,-19.229,-57.972,-19.424,-58.072,-19.625,-58.131,-19.745,-58.03,-19.833,-57.861,-19.98,-57.888,-20.02,-57.96,-20.041,-58.021,-20.055,-58.068,-20.11,-58.094,-20.151,-58.16,-20.165,-58.14,-19.999,-58.16,-19.855,-58.18,-19.818,-58.474,-19.646,-58.741,-19.49,-59.091,-19.286,-59.541,-19.292,-60.007,-19.298,-60.452,-19.389,-60.889,-19.479,-61.096,-19.521,-61.512,-19.606,-61.757,-19.645,-61.821,-19.809,-61.917,-20.055,-62.012,-20.199,-62.122,-20.35,-62.276,-20.562,-62.277,-20.821,-62.277,-21.066,-62.385,-21.412,-62.478,-21.705,-62.567,-21.989,-62.629,-22.184,-62.651,-22.234,-62.665,-22.218,-62.745,-22.16,-62.815,-22.05,-62.834,-21.999,-62.843,-21.997,-63.267,-22.001,-63.675,-22.004,-63.717,-22.028,-63.776,-22.027,-63.819,-22.005,-63.861,-22.007,-63.922,-22.029,-63.976,-22.073,-64.132,-22.366,-64.209,-22.491,-64.266,-22.603,-64.308,-22.795,-64.325,-22.828,-64.374,-22.761,-64.446,-22.585,-64.478,-22.485,-64.524,-22.372,-64.606,-22.229,-64.7,-22.186,-64.759,-22.171,-64.843,-22.144,-64.993,-22.11,-65.058,-22.103,-65.485,-22.098,-65.519,-22.095,-65.686,-22.11,-65.771,-22.1,-65.86,-22.02,-66.059,-21.879,-66.099,-21.835,-66.175,-21.806,-66.22,-21.803,-66.248,-21.83,-66.282,-21.947,-66.322,-22.053,-66.365,-22.114,-66.507,-22.158,-66.639,-22.205,-66.712,-22.216,-66.751,-22.269,-66.767,-22.343,-66.8,-22.41,-66.991,-22.51,-67.034,-22.552,-67.055,-22.651,-67.162,-22.774,-67.195,-22.822,-67.362,-22.855,-67.58,-22.892,-67.707,-22.889,-67.794,-22.879,-67.821,-22.858,-67.879,-22.823,-67.889,-22.784,-67.89,-22.729,-67.874,-22.631,-67.882,-22.493,-67.95,-22.334,-67.945,-22.282,-67.954,-22.204,-67.988,-22.057,-68.077,-21.983,-68.102,-21.861,-68.112,-21.753,-68.186,-21.619,-68.199,-21.447,-68.197,-21.3,-68.314,-21.13,-68.435,-20.948,-68.534,-20.924,-68.558,-20.902,-68.569,-20.85,-68.571,-20.769,-68.563,-20.72,-68.487,-20.641,-68.484,-20.628,-68.5,-20.612,-68.696,-20.493,-68.745,-20.459,-68.761,-20.416,-68.759,-20.378,-68.712,-20.339,-68.689,-20.31,-68.735,-20.225,-68.73,-20.148,-68.759,-20.116,-68.756,-20.091,-68.727,-20.07,-68.6,-20.045,-68.561,-19.967,-68.559,-19.902,-68.578,-19.857,-68.696,-19.741,-68.698,-19.721,-68.575,-19.56,-68.487,-19.454,-68.463,-19.433,-68.47,-19.41,-68.492,-19.382,-68.548,-19.341,-68.621,-19.297,-68.681,-19.242,-68.759,-19.162,-68.858,-19.093,-68.931,-19.025,-68.968,-18.968,-68.969,-18.91,-68.979,-18.813,-69.027,-18.656,-69.039,-18.55,-69.06,-18.433,-69.081,-18.357,-69.092,-18.282,-69.126,-18.202,-69.145,-18.144,-69.118,-18.103,-69.09,-18.071,-69.094,-18.05,-69.282,-17.965,-69.313,-17.943,-69.358,-17.772,-69.495,-17.62,-69.511,-17.506]]]},{"n":"Bhutan","c":"BT","r":5,"ml":4,"lx":90.04,"ly":27.54,"b":[88.739,26.702,92.083,28.311],"p":[[[91.632,27.76,91.626,27.737,91.598,27.677,91.579,27.611,91.595,27.558,91.658,27.494,91.743,27.443,91.851,27.439,91.951,27.458,91.991,27.45,92.045,27.365,92.083,27.291,92.031,27.214,92.003,27.147,91.992,27.1,91.999,27.079,92.031,27.041,92.068,26.975,92.073,26.915,92.05,26.875,91.998,26.855,91.944,26.861,91.899,26.86,91.842,26.853,91.754,26.831,91.672,26.802,91.518,26.807,91.456,26.867,91.427,26.867,91.287,26.79,91.134,26.803,90.856,26.778,90.74,26.772,90.62,26.78,90.56,26.797,90.448,26.851,90.346,26.89,90.242,26.854,90.206,26.848,90.123,26.755,89.943,26.724,89.764,26.702,89.711,26.714,89.61,26.719,89.606,26.741,89.609,26.762,89.586,26.779,89.545,26.796,89.475,26.803,89.384,26.827,89.332,26.849,89.148,26.816,89.041,26.865,88.919,26.932,88.858,26.961,88.835,27.066,88.814,27.099,88.766,27.134,88.739,27.176,88.76,27.218,88.882,27.297,88.891,27.316,88.948,27.464,89.025,27.518,89.102,27.593,89.16,27.711,89.273,27.833,89.396,27.958,89.481,28.06,89.537,28.107,89.653,28.158,89.75,28.188,89.817,28.256,89.898,28.294,89.981,28.311,90.104,28.302,90.221,28.278,90.348,28.244,90.363,28.217,90.352,28.168,90.334,28.119,90.333,28.094,90.353,28.08,90.477,28.071,90.63,28.079,90.716,28.072,90.907,28.027,90.963,27.995,91.021,27.97,91.078,27.974,91.15,28.027,91.226,28.071,91.273,28.078,91.307,28.064,91.368,28.022,91.493,27.982,91.606,27.952,91.642,27.923,91.629,27.801,91.632,27.76]]]},{"n":"Benin","c":"BJ","r":5,"ml":4,"lx":2.35,"ly":10.32,"b":[0.763,6.217,3.834,12.384],"p":[[[1.623,6.217,1.611,6.251,1.778,6.295,1.743,6.426,1.639,6.582,1.599,6.61,1.578,6.687,1.603,6.738,1.591,6.772,1.582,6.877,1.531,6.992,1.625,6.997,1.625,7.369,1.625,7.726,1.625,8.03,1.625,8.271,1.607,8.559,1.604,8.771,1.6,9.05,1.566,9.137,1.424,9.285,1.386,9.362,1.379,9.463,1.347,9.568,1.345,9.75,1.343,9.963,1.33,9.997,1.176,10.098,0.958,10.242,0.792,10.352,0.78,10.36,0.763,10.387,0.788,10.71,0.822,10.753,0.875,10.886,0.9,10.993,0.925,10.993,0.958,11.028,0.985,11.079,1.014,11.068,1.062,11.058,1.085,11.076,1.082,11.116,1.098,11.156,1.136,11.174,1.146,11.21,1.146,11.252,1.179,11.263,1.235,11.261,1.28,11.274,1.317,11.295,1.365,11.379,1.392,11.408,1.4,11.429,1.427,11.447,1.501,11.456,1.561,11.449,1.6,11.401,1.858,11.443,1.98,11.418,2.231,11.629,2.287,11.691,2.363,11.84,2.389,11.897,2.413,11.999,2.363,12.188,2.366,12.222,2.469,12.263,2.598,12.294,2.648,12.297,2.681,12.313,2.729,12.354,2.805,12.384,2.85,12.374,2.878,12.368,3.15,12.118,3.267,11.992,3.299,11.927,3.36,11.88,3.45,11.852,3.532,11.787,3.595,11.696,3.554,11.632,3.491,11.499,3.488,11.395,3.639,11.177,3.656,11.155,3.695,11.12,3.716,11.08,3.734,10.972,3.745,10.85,3.757,10.769,3.83,10.654,3.834,10.607,3.784,10.436,3.772,10.418,3.758,10.413,3.68,10.428,3.647,10.409,3.604,10.351,3.578,10.292,3.577,10.268,3.646,10.16,3.602,10.005,3.557,9.907,3.477,9.852,3.405,9.839,3.354,9.813,3.325,9.778,3.329,9.667,3.223,9.566,3.165,9.495,3.136,9.452,3.148,9.321,3.11,9.188,3.045,9.084,2.898,9.061,2.775,9.049,2.733,8.783,2.735,8.614,2.724,8.442,2.703,8.372,2.712,8.273,2.702,8.05,2.686,7.874,2.708,7.827,2.72,7.723,2.719,7.616,2.751,7.542,2.785,7.477,2.784,7.443,2.766,7.423,2.75,7.395,2.751,7.143,2.757,7.068,2.748,7.02,2.721,6.98,2.732,6.853,2.753,6.772,2.775,6.712,2.754,6.662,2.736,6.596,2.708,6.428,2.706,6.369,2.287,6.328,1.818,6.261,1.623,6.217]]]},{"n":"Belize","c":"BZ","r":6,"ml":5,"lx":-88.71,"ly":17.2,"b":[-89.237,15.889,-87.789,18.482],"p":[[[-89.161,17.815,-89.162,17.902,-89.134,17.971,-89.05,18,-88.943,17.94,-88.898,17.915,-88.857,17.929,-88.806,17.966,-88.744,18.072,-88.586,18.291,-88.523,18.446,-88.461,18.477,-88.372,18.482,-88.296,18.472,-88.349,18.359,-88.296,18.344,-88.247,18.355,-88.13,18.351,-88.085,18.226,-88.097,18.122,-88.207,17.846,-88.221,17.751,-88.272,17.61,-88.203,17.517,-88.267,17.393,-88.289,17.313,-88.294,17.192,-88.262,16.963,-88.313,16.633,-88.405,16.489,-88.461,16.434,-88.562,16.29,-88.695,16.248,-88.879,16.017,-88.912,15.956,-88.894,15.891,-88.937,15.89,-89.114,15.901,-89.233,15.889,-89.237,15.894,-89.228,16.143,-89.212,16.527,-89.201,16.809,-89.19,17.085,-89.182,17.291,-89.171,17.572,-89.161,17.815]],[[-87.951,17.925,-87.998,17.906,-87.959,17.964,-87.953,18.001,-87.898,18.155,-87.859,18.154,-87.849,18.14,-87.951,17.925]],[[-87.853,17.423,-87.93,17.283,-87.935,17.323,-87.903,17.426,-87.859,17.463,-87.833,17.501,-87.826,17.546,-87.789,17.524,-87.798,17.48,-87.853,17.423]]]},{"n":"Belgium","c":"BE","r":2,"ml":4,"lx":4.8,"ly":50.79,"b":[2.525,49.511,6.364,51.491],"p":[[[4.226,51.386,4.304,51.362,4.374,51.356,4.404,51.367,4.385,51.428,4.441,51.46,4.503,51.475,4.532,51.449,4.589,51.422,4.634,51.422,4.756,51.491,4.784,51.477,4.811,51.453,4.816,51.433,4.821,51.412,4.848,51.403,4.944,51.408,4.993,51.445,5.031,51.469,5.059,51.453,5.073,51.407,5.1,51.346,5.214,51.279,5.311,51.26,5.43,51.273,5.477,51.285,5.509,51.275,5.54,51.239,5.609,51.198,5.752,51.169,5.796,51.153,5.827,51.126,5.818,51.086,5.75,50.989,5.741,50.96,5.75,50.95,5.737,50.932,5.648,50.867,5.639,50.844,5.669,50.806,5.694,50.775,5.747,50.76,5.797,50.755,5.892,50.753,5.994,50.75,6.006,50.732,6.119,50.679,6.154,50.637,6.236,50.597,6.168,50.545,6.179,50.523,6.203,50.499,6.295,50.485,6.341,50.452,6.344,50.4,6.364,50.316,6.175,50.233,6.121,50.139,6.117,50.121,6.11,50.124,6.089,50.155,6.055,50.154,5.976,50.167,5.867,50.083,5.817,50.013,5.788,49.961,5.744,49.92,5.735,49.876,5.741,49.857,5.726,49.833,5.725,49.808,5.788,49.759,5.804,49.732,5.88,49.645,5.857,49.613,5.838,49.578,5.815,49.554,5.79,49.538,5.71,49.539,5.61,49.528,5.542,49.511,5.507,49.511,5.435,49.554,5.354,49.62,5.302,49.651,5.279,49.678,5.215,49.689,5.124,49.721,5.061,49.757,5.007,49.778,4.931,49.789,4.868,49.788,4.849,49.847,4.842,49.915,4.79,49.96,4.861,50.136,4.819,50.153,4.773,50.139,4.707,50.097,4.675,50.047,4.656,50.002,4.545,49.96,4.369,49.945,4.176,49.96,4.149,49.972,4.137,49.984,4.137,50,4.15,50.024,4.184,50.053,4.192,50.094,4.158,50.13,4.135,50.144,4.144,50.178,4.17,50.222,4.175,50.246,4.044,50.321,3.95,50.336,3.858,50.339,3.789,50.347,3.748,50.344,3.719,50.322,3.689,50.306,3.667,50.325,3.627,50.457,3.595,50.477,3.477,50.499,3.316,50.507,3.273,50.532,3.25,50.591,3.235,50.663,3.182,50.732,3.155,50.749,3.107,50.779,3.023,50.767,2.922,50.727,2.862,50.716,2.84,50.712,2.759,50.751,2.669,50.811,2.597,50.876,2.579,50.912,2.601,50.955,2.575,50.989,2.536,51.05,2.525,51.097,2.96,51.265,3.225,51.352,3.35,51.378,3.38,51.291,3.403,51.264,3.433,51.246,3.472,51.242,3.517,51.264,3.58,51.286,3.682,51.276,3.756,51.255,3.782,51.233,3.831,51.213,3.902,51.208,4.04,51.247,4.173,51.307,4.211,51.349,4.226,51.386]]]},{"n":"Belarus","c":"BY","r":4,"ml":3,"lx":28.42,"ly":53.82,"b":[23.175,51.265,32.71,56.146],"p":[[[31.763,52.101,31.574,52.108,31.346,52.105,31.218,52.05,31.168,52.063,31.079,52.077,30.981,52.046,30.846,51.953,30.755,51.895,30.667,51.814,30.639,51.77,30.584,51.689,30.533,51.596,30.561,51.531,30.602,51.471,30.612,51.406,30.633,51.355,30.577,51.318,30.545,51.265,30.45,51.274,30.333,51.326,30.309,51.4,30.22,51.451,30.161,51.478,30.064,51.482,29.909,51.458,29.706,51.44,29.553,51.435,29.47,51.408,29.346,51.383,29.299,51.413,29.23,51.497,29.174,51.581,29.136,51.617,29.102,51.628,29.061,51.625,29.013,51.599,28.978,51.572,28.928,51.562,28.85,51.54,28.793,51.51,28.731,51.433,28.69,51.439,28.648,51.457,28.599,51.543,28.532,51.562,28.425,51.564,28.292,51.582,28.184,51.608,28.144,51.602,28.08,51.565,28.011,51.56,27.859,51.592,27.829,51.577,27.789,51.529,27.741,51.483,27.7,51.478,27.677,51.49,27.69,51.572,27.601,51.602,27.452,51.606,27.348,51.594,27.296,51.597,27.27,51.614,27.142,51.752,27.074,51.761,26.953,51.754,26.773,51.771,26.567,51.802,26.453,51.813,26.394,51.844,26.267,51.855,25.925,51.914,25.786,51.924,25.58,51.925,25.267,51.938,25.067,51.931,24.974,51.911,24.866,51.899,24.685,51.888,24.611,51.89,24.495,51.883,24.362,51.868,24.324,51.838,24.28,51.775,24.127,51.665,23.978,51.591,23.951,51.585,23.864,51.624,23.792,51.637,23.707,51.641,23.647,51.629,23.609,51.61,23.614,51.525,23.605,51.518,23.54,51.619,23.545,51.71,23.581,51.762,23.626,51.809,23.607,51.88,23.651,51.973,23.652,52.04,23.633,52.07,23.598,52.103,23.501,52.14,23.458,52.17,23.327,52.208,23.197,52.257,23.175,52.287,23.181,52.307,23.204,52.338,23.303,52.428,23.411,52.516,23.48,52.552,23.845,52.664,23.901,52.704,23.915,52.77,23.916,52.819,23.909,52.905,23.887,53.028,23.859,53.112,23.789,53.271,23.599,53.599,23.485,53.94,23.559,53.92,23.734,53.912,23.873,53.936,23.944,53.939,24.008,53.932,24.104,53.945,24.191,53.95,24.237,53.92,24.318,53.893,24.479,53.932,24.621,53.98,24.768,53.975,24.789,53.998,24.826,54.119,24.87,54.145,25.046,54.133,25.111,54.155,25.179,54.214,25.284,54.251,25.371,54.251,25.461,54.293,25.506,54.265,25.527,54.215,25.497,54.175,25.51,54.16,25.573,54.14,25.681,54.14,25.749,54.157,25.765,54.18,25.765,54.221,25.748,54.26,25.703,54.293,25.617,54.31,25.558,54.311,25.547,54.332,25.568,54.377,25.62,54.46,25.685,54.536,25.725,54.564,25.732,54.59,25.724,54.636,25.722,54.718,25.781,54.833,25.859,54.919,25.964,54.947,26.093,54.962,26.175,55.003,26.216,55.05,26.231,55.09,26.251,55.125,26.292,55.14,26.601,55.13,26.648,55.204,26.675,55.225,26.734,55.247,26.776,55.273,26.76,55.293,26.681,55.306,26.495,55.318,26.458,55.342,26.47,55.372,26.519,55.448,26.567,55.546,26.591,55.623,26.594,55.668,26.62,55.68,26.772,55.694,26.822,55.709,26.953,55.813,27.053,55.831,27.309,55.804,27.427,55.806,27.459,55.804,27.577,55.799,27.589,55.809,27.642,55.912,27.694,55.942,27.896,56.076,28.032,56.133,28.118,56.146,28.148,56.143,28.284,56.056,28.316,56.053,28.392,56.087,28.407,56.089,28.564,56.092,28.637,56.062,28.691,56.003,28.741,55.955,28.795,55.943,28.947,56.002,29.032,56.022,29.087,56.021,29.283,55.968,29.375,55.939,29.396,55.912,29.398,55.881,29.373,55.835,29.353,55.784,29.413,55.725,29.482,55.685,29.63,55.751,29.685,55.77,29.744,55.77,29.824,55.795,29.882,55.832,29.937,55.845,30.043,55.836,30.234,55.845,30.456,55.787,30.475,55.769,30.587,55.7,30.626,55.666,30.662,55.655,30.722,55.622,30.801,55.601,30.856,55.608,30.882,55.596,30.907,55.57,30.909,55.525,30.901,55.397,30.862,55.36,30.821,55.33,30.811,55.307,30.814,55.279,30.877,55.223,30.959,55.138,30.978,55.088,30.978,55.05,30.867,54.941,30.83,54.915,30.804,54.861,30.791,54.806,30.799,54.783,30.984,54.696,31.121,54.648,31.152,54.625,31.155,54.611,31.082,54.517,31.075,54.492,31.185,54.453,31.246,54.392,31.299,54.292,31.404,54.196,31.628,54.111,31.792,54.056,31.826,54.031,31.838,54.001,31.825,53.935,31.783,53.855,31.754,53.81,31.821,53.792,31.992,53.797,32.2,53.781,32.45,53.693,32.451,53.653,32.425,53.617,32.442,53.579,32.47,53.547,32.686,53.448,32.706,53.419,32.71,53.371,32.704,53.336,32.644,53.329,32.578,53.312,32.469,53.27,32.426,53.211,32.251,53.128,32.142,53.091,32.055,53.089,31.85,53.106,31.777,53.147,31.747,53.184,31.668,53.201,31.563,53.202,31.418,53.196,31.388,53.185,31.365,53.139,31.303,53.061,31.259,53.017,31.295,52.99,31.353,52.933,31.443,52.862,31.535,52.798,31.565,52.759,31.563,52.731,31.519,52.699,31.526,52.633,31.616,52.546,31.586,52.532,31.577,52.426,31.577,52.312,31.602,52.285,31.65,52.262,31.691,52.221,31.759,52.126,31.763,52.101]]]},{"n":"Barbados","c":"BB","r":5,"ml":4.5,"lx":-59.57,"ly":13.16,"b":[-59.647,13.062,-59.428,13.318],"p":[[[-59.493,13.082,-59.522,13.062,-59.611,13.102,-59.643,13.15,-59.647,13.303,-59.592,13.318,-59.488,13.197,-59.428,13.153,-59.493,13.082]]]},{"n":"Bangladesh","c":"BD","r":3,"ml":3,"lx":89.68,"ly":24.21,"b":[88.023,20.79,92.632,26.572],"p":[[[89.051,22.093,89.056,22.186,89.05,22.275,88.971,22.511,88.921,22.632,88.927,22.671,88.923,22.688,88.9,22.844,88.867,22.939,88.851,23.041,88.928,23.187,88.897,23.21,88.808,23.23,88.724,23.255,88.704,23.293,88.741,23.437,88.698,23.493,88.636,23.55,88.616,23.573,88.596,23.602,88.567,23.674,88.623,23.826,88.7,24.003,88.714,24.07,88.727,24.186,88.734,24.231,88.724,24.275,88.642,24.326,88.499,24.347,88.397,24.389,88.338,24.454,88.287,24.48,88.225,24.461,88.146,24.486,88.079,24.55,88.023,24.628,88.03,24.664,88.045,24.713,88.15,24.915,88.189,24.921,88.279,24.882,88.313,24.882,88.373,24.962,88.456,25.188,88.574,25.188,88.678,25.18,88.748,25.169,88.817,25.176,88.89,25.194,88.93,25.223,88.952,25.259,88.944,25.291,88.855,25.334,88.82,25.366,88.795,25.456,88.769,25.49,88.593,25.495,88.502,25.537,88.452,25.574,88.363,25.698,88.253,25.79,88.147,25.811,88.107,25.841,88.085,25.888,88.097,25.956,88.129,26.018,88.151,26.087,88.235,26.178,88.334,26.258,88.378,26.312,88.44,26.369,88.448,26.401,88.437,26.437,88.386,26.472,88.351,26.483,88.346,26.505,88.37,26.564,88.418,26.572,88.518,26.518,88.62,26.431,88.681,26.353,88.683,26.292,88.722,26.282,88.762,26.279,88.828,26.252,88.896,26.26,88.941,26.245,88.97,26.251,88.982,26.286,88.948,26.338,88.924,26.375,88.952,26.412,88.983,26.42,89.019,26.41,89.067,26.377,89.102,26.308,89.108,26.202,89.186,26.106,89.289,26.038,89.37,26.006,89.467,25.984,89.55,26.005,89.591,26.072,89.573,26.132,89.586,26.186,89.619,26.216,89.671,26.214,89.71,26.171,89.823,25.941,89.8,25.84,89.825,25.56,89.796,25.376,89.801,25.336,89.814,25.305,89.833,25.293,89.866,25.293,90.004,25.258,90.12,25.22,90.25,25.185,90.439,25.158,90.555,25.167,90.613,25.168,90.73,25.159,91.038,25.174,91.293,25.178,91.397,25.152,91.48,25.142,91.763,25.161,92.05,25.169,92.205,25.111,92.373,25.015,92.468,24.944,92.485,24.903,92.475,24.869,92.443,24.849,92.385,24.849,92.251,24.895,92.228,24.881,92.231,24.786,92.227,24.771,92.198,24.686,92.117,24.494,92.102,24.408,92.085,24.386,92.064,24.374,92.001,24.371,91.952,24.357,91.931,24.326,91.899,24.261,91.877,24.195,91.846,24.175,91.772,24.211,91.727,24.205,91.669,24.19,91.611,24.153,91.571,24.107,91.526,24.091,91.393,24.1,91.367,24.094,91.35,24.06,91.336,24.019,91.232,23.92,91.192,23.763,91.16,23.661,91.166,23.581,91.254,23.374,91.315,23.104,91.339,23.077,91.359,23.068,91.369,23.075,91.367,23.13,91.371,23.198,91.399,23.214,91.436,23.2,91.471,23.141,91.511,23.034,91.554,22.992,91.62,22.98,91.695,23.005,91.751,23.054,91.774,23.106,91.758,23.21,91.754,23.287,91.79,23.361,91.919,23.471,91.938,23.505,91.929,23.598,91.93,23.686,91.979,23.692,92.044,23.678,92.127,23.721,92.152,23.722,92.187,23.676,92.246,23.684,92.289,23.492,92.334,23.324,92.334,23.242,92.341,23.07,92.362,22.929,92.393,22.897,92.43,22.822,92.464,22.734,92.491,22.685,92.51,22.526,92.532,22.41,92.561,22.048,92.575,21.978,92.583,21.94,92.584,21.609,92.593,21.467,92.625,21.351,92.632,21.306,92.6,21.27,92.569,21.263,92.539,21.32,92.472,21.363,92.373,21.409,92.331,21.44,92.28,21.428,92.208,21.358,92.18,21.293,92.192,21.202,92.215,21.113,92.264,21.061,92.268,21.005,92.286,20.932,92.312,20.864,92.324,20.792,92.308,20.79,92.248,20.884,92.195,20.984,92.056,21.175,92.011,21.516,92.008,21.685,91.913,21.883,91.85,22.157,91.825,22.229,91.858,22.317,91.863,22.35,91.845,22.343,91.797,22.297,91.734,22.407,91.693,22.505,91.53,22.708,91.482,22.797,91.48,22.885,91.41,22.797,91.314,22.735,91.216,22.642,91.151,22.614,90.946,22.597,90.827,22.721,90.656,23.025,90.634,23.094,90.656,23.273,90.616,23.442,90.616,23.532,90.604,23.591,90.573,23.578,90.562,23.537,90.568,23.474,90.556,23.422,90.408,23.432,90.269,23.456,90.392,23.367,90.523,23.346,90.591,23.266,90.599,23.204,90.595,23.134,90.528,23.085,90.466,23.054,90.478,22.987,90.552,22.905,90.462,22.882,90.437,22.828,90.435,22.752,90.481,22.685,90.498,22.635,90.487,22.589,90.532,22.539,90.596,22.436,90.616,22.362,90.589,22.258,90.553,22.218,90.494,22.179,90.356,22.048,90.288,21.899,90.231,21.83,90.159,21.817,90.131,21.847,90.071,21.887,90.07,21.96,90.088,22.017,90.21,22.157,90.143,22.138,90.069,22.098,89.954,22.023,89.918,22.116,89.894,22.203,89.894,22.308,89.985,22.466,89.882,22.388,89.853,22.289,89.866,22.173,89.853,22.091,89.812,21.983,89.757,21.919,89.668,21.878,89.628,21.814,89.569,21.767,89.567,21.861,89.547,21.984,89.483,22.276,89.469,22.213,89.503,22.032,89.501,21.914,89.452,21.821,89.354,21.721,89.279,21.707,89.234,21.722,89.167,21.784,89.094,21.873,89.082,22.015,89.051,22.093]],[[91.151,22.175,91.045,22.105,91.079,22.52,91.158,22.365,91.178,22.283,91.151,22.175]],[[91.557,22.382,91.51,22.353,91.467,22.378,91.411,22.476,91.439,22.599,91.456,22.617,91.484,22.577,91.523,22.491,91.548,22.425,91.557,22.382]],[[90.778,22.089,90.604,22.054,90.515,22.065,90.68,22.327,90.675,22.445,90.649,22.541,90.565,22.618,90.56,22.673,90.523,22.748,90.503,22.835,90.596,22.864,90.672,22.813,90.683,22.785,90.699,22.714,90.737,22.639,90.868,22.485,90.866,22.391,90.83,22.16,90.778,22.089]],[[91.874,21.832,91.838,21.75,91.82,21.81,91.835,21.885,91.851,21.927,91.861,21.927,91.883,21.884,91.874,21.832]],[[91.949,21.508,91.889,21.503,91.859,21.533,91.873,21.574,91.857,21.709,91.908,21.723,91.934,21.722,91.949,21.683,91.962,21.61,91.949,21.508]],[[90.642,22.963,90.66,22.92,90.604,22.946,90.562,22.975,90.536,23.015,90.58,23.035,90.642,22.963]]]},{"n":"Bahrain","c":"BH","r":4,"ml":4,"lx":50.55,"ly":26.06,"b":[50.452,25.807,50.617,26.246],"p":[[[50.607,25.883,50.575,25.807,50.544,25.833,50.466,25.966,50.489,26.058,50.452,26.191,50.47,26.229,50.564,26.246,50.586,26.241,50.558,26.198,50.61,26.124,50.617,26.002,50.607,25.883]]]},{"n":"The Bahamas","c":"BS","r":4,"ml":4,"lx":-77.15,"ly":26.4,"b":[-78.986,20.937,-72.747,26.94],"p":[[[-77.658,24.249,-77.656,24.227,-77.755,24.163,-77.683,24.118,-77.615,24.216,-77.562,24.137,-77.532,23.988,-77.537,23.962,-77.532,23.939,-77.521,23.911,-77.519,23.869,-77.574,23.739,-77.771,23.753,-77.776,23.862,-77.806,23.884,-77.852,24.04,-77.914,24.091,-78,24.22,-77.95,24.253,-77.884,24.242,-77.85,24.258,-77.757,24.27,-77.701,24.288,-77.658,24.249]],[[-77.226,25.904,-77.246,25.895,-77.333,25.996,-77.403,26.025,-77.294,26.096,-77.247,26.156,-77.248,26.289,-77.221,26.362,-77.23,26.425,-77.206,26.489,-77.239,26.561,-77.33,26.618,-77.511,26.846,-77.796,26.901,-77.944,26.904,-77.863,26.94,-77.788,26.936,-77.672,26.914,-77.534,26.903,-77.449,26.836,-77.369,26.748,-77.296,26.712,-77.266,26.689,-77.269,26.663,-77.257,26.639,-77.162,26.597,-77.066,26.53,-77.038,26.333,-77.167,26.24,-77.191,25.955,-77.226,25.904]],[[-73.027,21.192,-73.059,21.119,-73.165,20.979,-73.401,20.944,-73.661,20.937,-73.681,20.976,-73.687,21.009,-73.668,21.062,-73.67,21.082,-73.68,21.103,-73.585,21.126,-73.523,21.191,-73.425,21.202,-73.302,21.156,-73.235,21.154,-73.137,21.205,-73.058,21.313,-73.012,21.3,-73.027,21.192]],[[-77.744,24.707,-77.746,24.586,-77.735,24.496,-77.745,24.463,-77.853,24.403,-77.881,24.369,-77.983,24.335,-78.045,24.287,-78.076,24.365,-78.136,24.412,-78.146,24.493,-78.192,24.466,-78.258,24.483,-78.367,24.544,-78.435,24.628,-78.339,24.642,-78.319,24.59,-78.243,24.654,-78.26,24.687,-78.274,24.692,-78.299,24.754,-78.184,24.917,-78.159,25.022,-78.211,25.191,-78.163,25.202,-78.033,25.143,-77.975,25.085,-77.973,25.005,-77.919,24.943,-77.84,24.794,-77.744,24.707]],[[-78.493,26.729,-78.372,26.698,-78.307,26.702,-78.268,26.723,-78.089,26.714,-77.944,26.744,-77.922,26.691,-77.926,26.663,-78.234,26.637,-78.516,26.559,-78.671,26.507,-78.744,26.501,-78.799,26.528,-78.986,26.69,-78.936,26.673,-78.798,26.582,-78.712,26.599,-78.633,26.659,-78.621,26.705,-78.633,26.726,-78.597,26.798,-78.493,26.729]],[[-77.348,25.014,-77.46,24.993,-77.541,25.014,-77.562,25.03,-77.527,25.058,-77.451,25.081,-77.329,25.083,-77.276,25.056,-77.269,25.044,-77.348,25.014]],[[-74.058,22.723,-74.035,22.706,-74.099,22.665,-74.242,22.715,-74.275,22.712,-74.303,22.764,-74.314,22.804,-74.307,22.84,-74.221,22.812,-74.175,22.76,-74.058,22.723]],[[-74.429,24.068,-74.509,23.96,-74.551,23.969,-74.527,24.105,-74.472,24.127,-74.45,24.125,-74.429,24.068]],[[-74.207,22.214,-74.277,22.184,-74.261,22.236,-74.127,22.323,-74.052,22.401,-74.01,22.428,-73.995,22.449,-73.936,22.478,-73.906,22.527,-73.915,22.568,-73.976,22.635,-73.975,22.682,-73.954,22.716,-73.85,22.731,-73.877,22.681,-73.837,22.538,-73.975,22.361,-74.093,22.306,-74.207,22.214]],[[-76.649,25.487,-76.484,25.375,-76.344,25.332,-76.192,25.191,-76.127,25.141,-76.115,25.095,-76.141,24.886,-76.175,24.76,-76.17,24.649,-76.205,24.682,-76.241,24.754,-76.3,24.796,-76.32,24.818,-76.214,24.822,-76.204,24.936,-76.153,25.026,-76.16,25.119,-76.284,25.222,-76.369,25.313,-76.5,25.342,-76.621,25.432,-76.693,25.443,-76.781,25.427,-76.749,25.481,-76.727,25.552,-76.711,25.565,-76.649,25.487]],[[-75.665,23.45,-75.706,23.444,-75.781,23.471,-75.956,23.592,-76.037,23.603,-76.01,23.671,-75.949,23.647,-75.808,23.543,-75.754,23.49,-75.665,23.45]],[[-74.84,22.894,-74.847,22.869,-74.973,23.069,-75.132,23.117,-75.223,23.165,-75.204,23.193,-75.141,23.205,-75.131,23.268,-75.158,23.336,-75.241,23.475,-75.288,23.568,-75.31,23.59,-75.316,23.668,-75.217,23.547,-75.175,23.439,-75.109,23.333,-75.064,23.15,-74.937,23.088,-74.846,23,-74.84,22.894]],[[-75.308,24.2,-75.302,24.149,-75.369,24.159,-75.468,24.14,-75.503,24.139,-75.481,24.174,-75.412,24.221,-75.409,24.266,-75.494,24.33,-75.593,24.491,-75.639,24.529,-75.661,24.59,-75.744,24.655,-75.727,24.689,-75.71,24.698,-75.654,24.681,-75.526,24.45,-75.518,24.427,-75.308,24.2]],[[-73.041,22.429,-72.979,22.415,-72.945,22.416,-72.831,22.386,-72.763,22.344,-72.747,22.327,-72.784,22.291,-72.889,22.36,-72.981,22.369,-73.11,22.368,-73.162,22.381,-73.127,22.455,-73.041,22.429]],[[-72.916,21.507,-73.049,21.458,-73.063,21.515,-72.995,21.562,-72.916,21.507]]]},{"n":"Azerbaijan","c":"AZ","r":5,"ml":4,"lx":47.21,"ly":40.4,"b":[44.768,38.399,50.366,41.891],"p":[[[44.817,39.65,44.783,39.685,44.768,39.704,44.867,39.719,45.032,39.765,45.076,39.743,45.125,39.696,45.149,39.657,45.153,39.583,45.173,39.571,45.253,39.595,45.288,39.566,45.35,39.53,45.457,39.494,45.611,39.55,45.687,39.564,45.75,39.563,45.784,39.546,45.796,39.488,45.784,39.417,45.766,39.378,45.799,39.35,45.925,39.282,45.977,39.244,45.952,39.178,46.046,39.018,46.077,38.955,46.114,38.878,45.922,38.908,45.575,38.973,45.48,39.006,45.389,39.096,45.336,39.139,45.256,39.195,45.191,39.216,45.141,39.254,45.113,39.312,45.072,39.363,45,39.424,44.838,39.629,44.817,39.65]],[[48.869,38.435,48.84,38.437,48.636,38.399,48.593,38.411,48.417,38.586,48.381,38.606,48.306,38.613,48.261,38.642,48.225,38.689,48.205,38.724,48.023,38.819,47.996,38.854,47.993,38.884,48.019,38.912,48.05,38.935,48.139,38.959,48.242,38.979,48.275,38.994,48.292,39.019,48.291,39.059,48.274,39.099,48.125,39.172,48.109,39.203,48.104,39.241,48.113,39.281,48.136,39.312,48.257,39.355,48.322,39.399,48.282,39.448,48.151,39.561,47.996,39.684,47.892,39.685,47.773,39.649,47.582,39.543,47.476,39.498,47.338,39.424,47.188,39.341,47.065,39.253,46.989,39.18,46.853,39.148,46.783,39.087,46.555,38.904,46.491,38.907,46.487,38.997,46.49,39.069,46.475,39.111,46.401,39.168,46.4,39.192,46.42,39.207,46.477,39.198,46.55,39.201,46.585,39.224,46.507,39.299,46.437,39.349,46.378,39.382,46.365,39.402,46.365,39.417,46.378,39.434,46.478,39.475,46.488,39.513,46.481,39.555,46.322,39.617,46.202,39.594,46.095,39.664,46.026,39.719,45.94,39.777,45.863,39.808,45.79,39.881,45.662,39.956,45.58,39.978,45.581,39.989,45.596,40.003,45.63,40.014,45.858,40.011,45.886,40.025,45.9,40.057,45.931,40.105,45.968,40.175,45.965,40.234,45.736,40.329,45.57,40.417,45.454,40.532,45.376,40.638,45.379,40.674,45.401,40.707,45.579,40.804,45.591,40.83,45.588,40.847,45.524,40.897,45.444,40.948,45.419,40.986,45.369,41.005,45.273,41.006,45.106,41.069,45.071,41.076,45.063,41.088,45.071,41.101,45.19,41.126,45.189,41.147,45.152,41.175,45.085,41.195,45.023,41.246,45.001,41.291,45.217,41.423,45.281,41.45,45.422,41.425,45.716,41.338,45.696,41.289,45.725,41.262,45.793,41.224,45.922,41.187,46.031,41.167,46.087,41.184,46.171,41.198,46.28,41.154,46.381,41.099,46.431,41.077,46.458,41.07,46.534,41.089,46.626,41.16,46.662,41.246,46.673,41.287,46.619,41.344,46.509,41.406,46.385,41.46,46.305,41.508,46.255,41.602,46.204,41.613,46.191,41.625,46.182,41.657,46.184,41.702,46.202,41.737,46.252,41.752,46.303,41.757,46.348,41.79,46.405,41.855,46.43,41.891,46.538,41.87,46.552,41.812,46.571,41.8,46.616,41.807,46.69,41.831,46.749,41.813,46.826,41.743,46.931,41.67,46.988,41.621,47.01,41.588,47.064,41.555,47.143,41.516,47.205,41.456,47.261,41.315,47.318,41.282,47.521,41.229,47.592,41.218,47.791,41.199,47.861,41.213,47.964,41.334,48.056,41.459,48.142,41.485,48.298,41.545,48.391,41.602,48.431,41.663,48.519,41.779,48.573,41.844,48.665,41.787,48.824,41.63,49.051,41.374,49.107,41.302,49.143,41.218,49.175,41.116,49.226,41.026,49.457,40.8,49.556,40.716,49.718,40.608,49.776,40.584,49.852,40.577,49.991,40.577,50.119,40.535,50.183,40.505,50.248,40.462,50.307,40.412,50.366,40.279,50.143,40.323,49.919,40.316,49.792,40.288,49.669,40.249,49.551,40.194,49.477,40.087,49.415,39.84,49.324,39.608,49.328,39.501,49.367,39.398,49.363,39.35,49.321,39.329,49.269,39.285,49.2,39.073,49.165,39.03,49.121,39.004,49.109,39.029,49.111,39.085,49.013,39.134,48.962,39.079,48.926,38.962,48.854,38.839,48.851,38.815,48.869,38.435],[45.552,40.616,45.562,40.649,45.534,40.664,45.504,40.665,45.479,40.648,45.479,40.607,45.514,40.6,45.552,40.616]],[[45.024,41.027,45.002,41.016,44.969,41.027,44.959,41.053,44.961,41.079,44.994,41.086,45.021,41.078,45.029,41.054,45.024,41.027]]]},{"n":"Austria","c":"AT","r":4,"ml":3,"lx":14.13,"ly":47.52,"b":[9.524,46.4,17.147,49.001],"p":[[[9.528,47.271,9.609,47.392,9.626,47.467,9.554,47.511,9.524,47.524,9.549,47.534,9.651,47.526,9.715,47.551,9.749,47.576,9.839,47.552,9.972,47.505,10.034,47.474,10.06,47.449,10.074,47.429,10.066,47.393,10.096,47.38,10.159,47.374,10.2,47.363,10.186,47.317,10.183,47.279,10.241,47.284,10.313,47.313,10.369,47.366,10.404,47.417,10.43,47.541,10.439,47.552,10.483,47.542,10.659,47.547,10.742,47.524,10.873,47.52,10.871,47.501,10.894,47.47,10.952,47.427,10.981,47.398,11.042,47.393,11.136,47.409,11.191,47.425,11.212,47.414,11.298,47.425,11.374,47.46,11.393,47.487,11.47,47.506,11.574,47.55,11.717,47.583,12.186,47.62,12.204,47.647,12.197,47.709,12.209,47.718,12.268,47.703,12.363,47.688,12.436,47.666,12.483,47.637,12.527,47.636,12.594,47.656,12.686,47.669,12.771,47.639,12.796,47.607,12.781,47.59,12.783,47.564,12.809,47.542,12.879,47.506,12.968,47.476,13.014,47.478,13.032,47.508,13.048,47.579,13.054,47.655,13.034,47.699,12.986,47.709,12.928,47.713,12.898,47.722,12.908,47.746,12.954,47.808,12.954,47.891,12.85,47.985,12.76,48.076,12.76,48.107,12.814,48.161,12.897,48.204,13.082,48.275,13.14,48.29,13.215,48.302,13.323,48.331,13.375,48.361,13.409,48.394,13.46,48.565,13.472,48.572,13.487,48.582,13.675,48.523,13.692,48.533,13.724,48.542,13.785,48.587,13.799,48.622,13.797,48.686,13.803,48.748,13.815,48.767,13.843,48.76,13.924,48.728,13.989,48.692,14.049,48.602,14.19,48.579,14.368,48.576,14.431,48.616,14.489,48.626,14.554,48.613,14.691,48.599,14.707,48.672,14.786,48.747,14.822,48.774,14.923,48.771,14.947,48.828,14.972,48.984,14.993,49.001,15.067,48.998,15.14,48.969,15.162,48.946,15.2,48.948,15.253,48.964,15.311,48.974,15.403,48.957,15.599,48.886,15.701,48.86,15.765,48.865,15.825,48.864,16.057,48.755,16.219,48.739,16.367,48.739,16.415,48.772,16.478,48.8,16.544,48.796,16.601,48.782,16.713,48.734,16.764,48.722,16.833,48.714,16.884,48.704,16.928,48.621,16.953,48.599,16.949,48.589,16.943,48.551,16.904,48.504,16.863,48.441,16.865,48.387,16.973,48.198,17.068,48.083,17.086,48.04,17.147,48.006,17.089,47.964,17.078,47.901,17.04,47.873,17.03,47.837,17.046,47.805,17.046,47.764,17.067,47.708,16.973,47.695,16.863,47.697,16.823,47.694,16.786,47.679,16.748,47.686,16.647,47.739,16.591,47.751,16.551,47.747,16.521,47.724,16.47,47.695,16.421,47.674,16.432,47.656,16.64,47.609,16.677,47.536,16.637,47.477,16.623,47.448,16.574,47.425,16.515,47.405,16.443,47.4,16.434,47.367,16.463,47.273,16.44,47.253,16.417,47.223,16.438,47.146,16.483,47.14,16.493,47.123,16.485,47.091,16.477,47.058,16.461,47.022,16.453,47.007,16.424,46.997,16.332,47.002,16.253,46.972,16.093,46.863,16.037,46.845,15.977,46.801,15.98,46.706,15.972,46.697,15.958,46.678,15.767,46.711,15.76,46.711,15.633,46.698,15.545,46.655,15.439,46.63,15.217,46.643,15.001,46.626,14.949,46.613,14.893,46.606,14.841,46.58,14.811,46.545,14.757,46.499,14.68,46.463,14.597,46.436,14.577,46.413,14.55,46.4,14.504,46.417,14.466,46.416,14.42,46.428,14.267,46.441,14.1,46.462,14.02,46.482,13.929,46.498,13.831,46.511,13.744,46.514,13.7,46.52,13.49,46.556,13.352,46.558,13.169,46.573,12.806,46.626,12.7,46.647,12.599,46.654,12.479,46.673,12.388,46.703,12.33,46.76,12.268,46.836,12.154,46.935,12.131,46.985,12.166,47.028,12.201,47.061,12.197,47.075,12.169,47.082,11.97,47.04,11.776,46.986,11.699,46.985,11.625,46.997,11.528,46.997,11.433,46.983,11.244,46.976,11.134,46.936,11.063,46.859,11.025,46.797,10.993,46.777,10.927,46.769,10.829,46.775,10.76,46.793,10.689,46.846,10.58,46.854,10.479,46.855,10.453,46.865,10.455,46.899,10.415,46.964,10.349,46.985,10.18,46.862,10.133,46.852,9.997,46.885,9.878,46.938,9.865,46.976,9.845,47.007,9.745,47.037,9.62,47.057,9.58,47.057,9.596,47.076,9.611,47.107,9.601,47.132,9.572,47.158,9.556,47.185,9.551,47.212,9.542,47.234,9.537,47.255,9.528,47.271]]]},{"n":"Australia","c":"AU","r":2,"ml":1.7,"lx":134.05,"ly":-24.13,"b":[112.908,-54.749,158.959,-10.052],"p":[[[143.179,-11.954,143.153,-12.076,143.105,-12.17,143.099,-12.226,143.11,-12.304,143.191,-12.361,143.254,-12.398,143.29,-12.499,143.402,-12.64,143.398,-12.736,143.458,-12.856,143.512,-13.095,143.529,-13.304,143.587,-13.444,143.548,-13.741,143.589,-13.863,143.643,-13.964,143.707,-14.165,143.756,-14.349,143.822,-14.401,143.962,-14.463,144.106,-14.395,144.21,-14.302,144.322,-14.279,144.473,-14.232,144.586,-14.355,144.648,-14.492,144.916,-14.674,145.064,-14.791,145.18,-14.857,145.288,-14.943,145.277,-15.029,145.252,-15.097,145.276,-15.204,145.293,-15.327,145.272,-15.477,145.35,-15.702,145.375,-15.881,145.458,-16.056,145.452,-16.237,145.436,-16.305,145.426,-16.406,145.49,-16.532,145.55,-16.625,145.638,-16.726,145.755,-16.879,145.838,-16.91,145.912,-16.912,145.902,-17.07,146.05,-17.381,146.126,-17.635,146.074,-17.977,146.023,-18.176,146.032,-18.273,146.223,-18.51,146.333,-18.554,146.312,-18.667,146.297,-18.841,146.383,-18.977,146.481,-19.079,146.587,-19.139,146.692,-19.187,146.829,-19.236,147.003,-19.256,147.093,-19.333,147.139,-19.393,147.278,-19.414,147.342,-19.403,147.419,-19.378,147.471,-19.419,147.51,-19.474,147.586,-19.623,147.742,-19.77,147.853,-19.795,147.916,-19.869,148.004,-19.89,148.081,-19.899,148.19,-19.956,148.367,-20.087,148.527,-20.109,148.6,-20.145,148.759,-20.29,148.821,-20.366,148.885,-20.481,148.805,-20.492,148.73,-20.468,148.684,-20.58,148.789,-20.736,148.912,-20.845,149.061,-20.961,149.205,-21.125,149.241,-21.25,149.28,-21.3,149.329,-21.476,149.454,-21.579,149.46,-21.765,149.524,-22.024,149.596,-22.258,149.645,-22.328,149.704,-22.441,149.772,-22.426,149.822,-22.39,149.92,-22.501,149.974,-22.551,150.006,-22.522,149.942,-22.308,149.981,-22.184,150.021,-22.168,150.076,-22.164,150.143,-22.265,150.235,-22.373,150.405,-22.469,150.541,-22.559,150.58,-22.556,150.564,-22.486,150.569,-22.384,150.623,-22.367,150.672,-22.418,150.764,-22.576,150.783,-22.903,150.783,-23.177,150.843,-23.458,150.931,-23.532,150.989,-23.602,151.088,-23.696,151.154,-23.784,151.236,-23.825,151.501,-24.012,151.575,-24.034,151.691,-24.038,151.832,-24.123,151.903,-24.201,152.055,-24.494,152.13,-24.598,152.282,-24.699,152.353,-24.733,152.456,-24.802,152.493,-24.904,152.502,-24.964,152.563,-25.072,152.654,-25.202,152.789,-25.274,152.913,-25.432,152.921,-25.689,152.985,-25.816,153.028,-25.87,153.125,-25.923,153.165,-25.964,153.084,-26.304,153.162,-26.983,153.117,-27.194,153.198,-27.405,153.386,-27.769,153.428,-27.898,153.455,-28.048,153.576,-28.241,153.569,-28.533,153.617,-28.673,153.605,-28.854,153.462,-29.05,153.348,-29.29,153.347,-29.497,153.272,-29.892,153.224,-29.999,153.188,-30.164,153.031,-30.563,153.024,-30.72,153.048,-30.907,153.022,-31.087,152.982,-31.209,152.944,-31.435,152.786,-31.786,152.559,-32.046,152.545,-32.243,152.517,-32.33,152.47,-32.439,152.331,-32.558,152.247,-32.609,152.216,-32.678,152.137,-32.678,152.135,-32.7,152.188,-32.722,152.164,-32.757,151.954,-32.82,151.813,-32.901,151.668,-33.099,151.608,-33.202,151.53,-33.301,151.484,-33.347,151.463,-33.397,151.432,-33.522,151.358,-33.544,151.292,-33.581,151.323,-33.699,151.288,-33.835,151.28,-33.927,151.245,-33.985,151.202,-33.964,151.168,-33.973,151.125,-34.005,151.191,-34.015,151.232,-34.03,151.09,-34.162,150.96,-34.297,150.927,-34.387,150.871,-34.499,150.822,-34.749,150.781,-34.892,150.809,-34.994,150.805,-35.013,150.775,-35.02,150.756,-35.007,150.697,-35.042,150.681,-35.077,150.706,-35.12,150.722,-35.135,150.715,-35.155,150.69,-35.178,150.634,-35.178,150.567,-35.214,150.374,-35.584,150.292,-35.682,150.195,-35.834,150.158,-35.971,150.129,-36.12,150.095,-36.373,150.063,-36.55,149.988,-36.723,149.96,-36.846,149.951,-37.08,149.986,-37.258,149.963,-37.353,149.962,-37.444,149.933,-37.529,149.809,-37.548,149.709,-37.617,149.565,-37.73,149.481,-37.771,149.298,-37.802,148.944,-37.788,148.263,-37.831,148.131,-37.856,147.877,-37.934,147.631,-38.056,147.396,-38.219,146.857,-38.663,146.436,-38.712,146.356,-38.712,146.293,-38.7,146.217,-38.727,146.216,-38.783,146.286,-38.84,146.337,-38.894,146.427,-38.82,146.467,-38.84,146.482,-38.978,146.484,-39.065,146.457,-39.112,146.4,-39.146,146.34,-39.124,146.332,-39.077,146.254,-38.964,146.158,-38.866,146.07,-38.834,146.018,-38.867,145.935,-38.902,145.866,-38.776,145.791,-38.667,145.692,-38.656,145.606,-38.657,145.535,-38.61,145.397,-38.535,145.424,-38.477,145.463,-38.416,145.542,-38.394,145.518,-38.311,145.476,-38.244,145.366,-38.226,145.293,-38.238,145.249,-38.291,145.191,-38.384,144.96,-38.501,144.847,-38.436,144.718,-38.34,144.78,-38.347,144.911,-38.344,145.02,-38.258,145.067,-38.205,145.12,-38.091,145.05,-38.011,144.985,-37.952,144.891,-37.9,144.538,-38.077,144.465,-38.103,144.396,-38.137,144.518,-38.166,144.589,-38.158,144.665,-38.21,144.544,-38.284,144.448,-38.304,144.329,-38.348,144.102,-38.462,143.812,-38.699,143.687,-38.767,143.539,-38.821,143.338,-38.758,143.226,-38.743,143.083,-38.646,142.84,-38.581,142.612,-38.452,142.456,-38.386,142.345,-38.372,142.188,-38.399,141.925,-38.284,141.725,-38.271,141.594,-38.388,141.492,-38.38,141.424,-38.363,141.214,-38.172,141.011,-38.077,140.627,-38.028,140.39,-37.897,140.212,-37.642,139.875,-37.352,139.784,-37.246,139.742,-37.142,139.738,-37.06,139.784,-36.903,139.847,-36.748,139.857,-36.662,139.729,-36.371,139.549,-36.097,139.466,-36.01,139.245,-35.827,139.038,-35.689,138.985,-35.618,138.969,-35.581,139.067,-35.598,139.113,-35.542,139.178,-35.523,139.231,-35.598,139.289,-35.611,139.292,-35.486,139.325,-35.427,139.303,-35.399,139.283,-35.375,139.193,-35.347,139.094,-35.39,139.018,-35.443,138.915,-35.489,138.875,-35.537,138.771,-35.538,138.73,-35.551,138.522,-35.642,138.389,-35.645,138.184,-35.613,138.252,-35.487,138.333,-35.412,138.4,-35.326,138.511,-35.024,138.49,-34.764,138.436,-34.656,138.264,-34.44,138.186,-34.307,138.089,-34.17,138.041,-34.25,138.012,-34.334,137.919,-34.456,137.874,-34.727,137.692,-35.143,137.566,-35.148,137.46,-35.131,137.272,-35.179,137.144,-35.236,137.03,-35.237,136.967,-35.255,136.884,-35.24,137.014,-34.916,137.128,-34.925,137.252,-34.912,137.308,-34.917,137.391,-34.913,137.454,-34.764,137.493,-34.598,137.469,-34.49,137.459,-34.379,137.484,-34.252,137.494,-34.161,137.65,-33.859,137.781,-33.703,137.932,-33.579,137.914,-33.461,137.866,-33.314,137.852,-33.201,137.924,-33.165,137.993,-33.094,137.913,-32.771,137.863,-32.674,137.783,-32.578,137.782,-32.702,137.791,-32.823,137.68,-32.978,137.536,-33.089,137.442,-33.194,137.354,-33.43,137.237,-33.629,137.13,-33.703,137.034,-33.72,136.937,-33.75,136.783,-33.83,136.636,-33.897,136.526,-33.984,136.431,-34.03,136.121,-34.429,135.98,-34.562,135.951,-34.616,135.891,-34.661,135.903,-34.724,135.951,-34.767,135.999,-34.944,135.97,-34.982,135.919,-34.962,135.792,-34.863,135.713,-34.899,135.648,-34.94,135.481,-34.758,135.412,-34.716,135.324,-34.643,135.231,-34.58,135.191,-34.573,135.123,-34.586,135.13,-34.537,135.176,-34.497,135.217,-34.487,135.292,-34.546,135.379,-34.598,135.427,-34.602,135.45,-34.581,135.368,-34.376,135.312,-34.196,135.286,-34.142,135.219,-33.96,135.185,-33.907,135.042,-33.778,134.889,-33.626,134.847,-33.445,134.791,-33.328,134.719,-33.255,134.608,-33.19,134.301,-33.165,134.174,-32.979,134.1,-32.749,134.158,-32.733,134.227,-32.731,134.249,-32.659,134.234,-32.549,133.93,-32.412,133.787,-32.269,133.665,-32.207,133.551,-32.183,133.401,-32.188,133.212,-32.184,132.757,-31.956,132.649,-31.949,132.324,-32.02,132.215,-32.007,131.721,-31.696,131.393,-31.549,131.285,-31.521,131.144,-31.496,131.029,-31.532,130.948,-31.566,130.783,-31.604,130.13,-31.579,129.569,-31.627,129.188,-31.66,128.946,-31.703,128.546,-31.888,128.068,-32.067,127.678,-32.151,127.32,-32.264,127.084,-32.297,126.779,-32.311,126.137,-32.257,125.917,-32.297,125.567,-32.506,125.464,-32.557,125.267,-32.614,124.759,-32.883,124.525,-32.94,124.373,-32.958,124.244,-33.015,124.126,-33.129,123.967,-33.446,123.868,-33.596,123.65,-33.836,123.507,-33.916,123.365,-33.905,123.208,-33.988,123.068,-33.901,122.956,-33.884,122.778,-33.891,122.151,-33.992,122.061,-33.874,121.946,-33.857,121.73,-33.862,121.405,-33.827,120.815,-33.871,120.531,-33.92,120.418,-33.963,120.209,-33.935,119.854,-33.975,119.729,-34.042,119.635,-34.101,119.451,-34.368,119.248,-34.456,119.081,-34.459,118.895,-34.48,118.52,-34.737,118.136,-34.987,118.006,-35.013,117.863,-35.055,117.675,-35.075,117.582,-35.098,117.144,-35.034,116.865,-35.027,116.517,-34.988,116.217,-34.866,115.987,-34.795,115.726,-34.526,115.565,-34.426,115.278,-34.304,115.195,-34.308,115.128,-34.342,115.009,-34.256,115.006,-34.145,114.973,-34.051,114.976,-33.804,114.994,-33.515,115.099,-33.58,115.182,-33.643,115.359,-33.64,115.515,-33.531,115.604,-33.372,115.683,-33.193,115.671,-33.002,115.619,-32.667,115.654,-32.597,115.708,-32.568,115.725,-32.401,115.738,-31.888,115.698,-31.695,115.455,-31.303,115.294,-30.962,115.177,-30.808,115.078,-30.56,114.995,-30.216,114.969,-30.042,114.942,-29.722,114.971,-29.54,114.959,-29.434,114.857,-29.143,114.628,-28.872,114.591,-28.772,114.592,-28.666,114.537,-28.543,114.354,-28.295,114.165,-28.081,114.133,-27.976,114.098,-27.544,114.028,-27.347,113.709,-26.848,113.333,-26.417,113.231,-26.241,113.185,-26.182,113.211,-26.174,113.253,-26.197,113.3,-26.24,113.323,-26.244,113.345,-26.208,113.343,-26.126,113.356,-26.08,113.389,-26.106,113.427,-26.198,113.547,-26.437,113.582,-26.558,113.734,-26.595,113.78,-26.563,113.836,-26.501,113.853,-26.332,113.776,-26.256,113.706,-26.224,113.589,-26.099,113.513,-25.898,113.395,-25.713,113.397,-25.647,113.451,-25.599,113.539,-25.625,113.621,-25.732,113.713,-25.831,113.698,-26.004,113.684,-26.052,113.692,-26.092,113.724,-26.13,113.766,-26.16,113.812,-26.116,113.854,-26.014,113.88,-26.028,113.942,-26.259,113.992,-26.321,114.09,-26.394,114.176,-26.337,114.216,-26.289,114.203,-26.126,114.229,-25.969,114.214,-25.852,113.993,-25.545,113.792,-25.166,113.671,-24.977,113.569,-24.693,113.504,-24.595,113.418,-24.436,113.413,-24.254,113.421,-24.132,113.49,-23.87,113.553,-23.733,113.757,-23.418,113.767,-23.283,113.765,-23.18,113.795,-23.024,113.795,-22.915,113.768,-22.813,113.683,-22.638,113.795,-22.332,113.958,-21.939,114.023,-21.881,114.124,-21.829,114.143,-21.91,114.093,-22.181,114.164,-22.323,114.142,-22.483,114.205,-22.456,114.304,-22.425,114.378,-22.342,114.417,-22.261,114.603,-21.942,114.709,-21.823,114.859,-21.736,115.162,-21.631,115.456,-21.492,115.596,-21.358,115.771,-21.242,115.894,-21.117,116.011,-21.03,116.606,-20.713,116.707,-20.654,116.836,-20.647,116.995,-20.658,117.139,-20.641,117.293,-20.713,117.406,-20.721,117.684,-20.643,117.832,-20.573,118.087,-20.419,118.199,-20.375,118.458,-20.327,118.751,-20.262,119.104,-19.995,119.359,-20.012,119.586,-20.038,119.768,-19.958,120.196,-19.909,120.434,-19.842,120.878,-19.665,120.998,-19.604,121.18,-19.478,121.338,-19.32,121.494,-19.106,121.589,-18.915,121.631,-18.817,121.722,-18.66,121.785,-18.536,121.834,-18.477,122.006,-18.394,122.262,-18.159,122.345,-18.112,122.361,-18.037,122.306,-17.995,122.237,-17.969,122.191,-17.72,122.147,-17.549,122.143,-17.428,122.16,-17.314,122.261,-17.136,122.333,-17.059,122.432,-16.97,122.523,-16.943,122.598,-16.865,122.72,-16.788,122.772,-16.71,122.848,-16.552,122.917,-16.433,122.971,-16.437,123.074,-16.715,123.142,-16.863,123.266,-17.037,123.383,-17.293,123.479,-17.41,123.525,-17.486,123.563,-17.521,123.571,-17.472,123.562,-17.415,123.608,-17.22,123.586,-17.083,123.594,-17.03,123.618,-17.008,123.664,-17.023,123.754,-17.1,123.799,-17.127,123.831,-17.121,123.829,-16.997,123.874,-16.919,123.856,-16.865,123.778,-16.868,123.745,-16.801,123.68,-16.724,123.607,-16.668,123.518,-16.541,123.49,-16.491,123.525,-16.468,123.581,-16.471,123.626,-16.416,123.646,-16.343,123.607,-16.224,123.647,-16.18,123.729,-16.192,123.859,-16.382,123.915,-16.364,123.961,-16.287,124.044,-16.265,124.13,-16.279,124.186,-16.334,124.3,-16.388,124.453,-16.382,124.53,-16.395,124.692,-16.386,124.772,-16.403,124.757,-16.373,124.669,-16.339,124.57,-16.332,124.454,-16.335,124.405,-16.299,124.388,-16.203,124.416,-16.133,124.435,-16.104,124.51,-16.116,124.577,-16.114,124.585,-16.02,124.609,-15.937,124.649,-15.87,124.648,-15.805,124.607,-15.823,124.504,-15.972,124.455,-15.851,124.382,-15.758,124.397,-15.626,124.44,-15.494,124.506,-15.475,124.562,-15.496,124.644,-15.419,124.691,-15.36,124.68,-15.311,124.693,-15.274,124.75,-15.285,124.972,-15.404,125.016,-15.467,125.063,-15.442,125.078,-15.375,125.073,-15.307,125.024,-15.317,124.909,-15.31,124.883,-15.272,124.893,-15.241,124.839,-15.161,124.914,-15.11,124.979,-15.107,125.023,-15.072,125.024,-15.024,125.038,-15.004,125.073,-15.032,125.189,-15.045,125.302,-15.107,125.356,-15.12,125.376,-15.087,125.384,-15.016,125.243,-14.945,125.239,-14.875,125.18,-14.794,125.179,-14.715,125.267,-14.648,125.285,-14.584,125.335,-14.558,125.436,-14.557,125.504,-14.502,125.58,-14.483,125.598,-14.362,125.597,-14.278,125.628,-14.257,125.705,-14.291,125.681,-14.388,125.681,-14.48,125.662,-14.529,125.691,-14.525,125.708,-14.505,125.738,-14.444,125.82,-14.469,125.84,-14.534,125.85,-14.597,125.891,-14.618,125.946,-14.52,126.021,-14.495,126.017,-14.371,126.045,-14.283,126.054,-14.217,126.101,-14.184,126.111,-14.114,126.073,-14.066,126.054,-13.977,126.119,-13.958,126.184,-14.002,126.228,-14.113,126.258,-14.164,126.299,-14.136,126.323,-14.062,126.403,-14.019,126.482,-14.079,126.57,-14.161,126.679,-14.089,126.781,-13.955,126.764,-13.873,126.776,-13.788,126.903,-13.744,127.006,-13.777,127.099,-13.867,127.293,-13.935,127.458,-14.031,127.531,-14.095,127.673,-14.195,127.763,-14.299,127.888,-14.485,128.18,-14.712,128.199,-14.752,128.16,-14.827,128.124,-14.924,128.08,-15.088,128.069,-15.329,128.112,-15.312,128.155,-15.226,128.202,-15.243,128.255,-15.299,128.259,-15.246,128.227,-15.214,128.173,-15.102,128.175,-15.043,128.218,-14.996,128.285,-14.939,128.358,-14.902,128.403,-14.869,128.41,-14.829,128.477,-14.788,128.576,-14.775,128.636,-14.781,129.058,-14.884,129.165,-14.988,129.175,-15.115,129.216,-15.16,129.238,-15.08,129.234,-14.906,129.268,-14.871,129.381,-14.898,129.459,-14.933,129.567,-15.047,129.588,-15.103,129.635,-15.14,129.65,-15.087,129.628,-15.012,129.613,-14.926,129.637,-14.851,129.763,-14.845,129.849,-14.829,129.808,-14.8,129.754,-14.79,129.663,-14.721,129.605,-14.647,129.699,-14.575,129.698,-14.557,129.608,-14.56,129.484,-14.49,129.379,-14.392,129.459,-14.213,129.62,-14.038,129.71,-13.98,129.718,-13.921,129.762,-13.812,129.789,-13.72,129.797,-13.648,129.839,-13.573,129.938,-13.502,130.073,-13.476,130.136,-13.448,130.199,-13.383,130.26,-13.302,130.135,-13.146,130.145,-13.059,130.168,-12.957,130.318,-12.883,130.4,-12.688,130.454,-12.659,130.572,-12.664,130.617,-12.647,130.61,-12.491,130.623,-12.431,130.672,-12.407,130.736,-12.428,130.777,-12.495,130.867,-12.558,130.898,-12.524,130.883,-12.455,130.874,-12.367,130.957,-12.348,131.023,-12.343,131.03,-12.271,131.02,-12.214,131.046,-12.19,131.22,-12.178,131.265,-12.119,131.292,-12.068,131.314,-12.096,131.342,-12.21,131.438,-12.277,131.726,-12.278,131.888,-12.232,131.957,-12.259,132.064,-12.281,132.182,-12.227,132.253,-12.186,132.372,-12.239,132.411,-12.295,132.442,-12.176,132.511,-12.135,132.584,-12.11,132.676,-12.13,132.713,-12.123,132.63,-12.035,132.635,-11.955,132.63,-11.836,132.645,-11.727,132.674,-11.649,132.475,-11.492,132.278,-11.468,132.134,-11.501,132.073,-11.475,131.945,-11.349,131.822,-11.302,131.812,-11.271,131.962,-11.181,132.019,-11.196,132.106,-11.281,132.155,-11.311,132.198,-11.305,132.225,-11.239,132.263,-11.204,132.334,-11.224,132.557,-11.367,132.683,-11.506,132.747,-11.469,132.857,-11.391,132.961,-11.407,133.025,-11.453,133.114,-11.622,133.185,-11.706,133.356,-11.728,133.443,-11.76,133.533,-11.816,133.654,-11.811,133.904,-11.832,134.139,-11.94,134.237,-12.008,134.351,-12.026,134.417,-12.053,134.538,-12.061,134.73,-11.984,134.816,-12.055,134.855,-12.103,135.03,-12.194,135.218,-12.222,135.352,-12.129,135.549,-12.061,135.686,-11.956,135.788,-11.907,135.885,-11.822,135.922,-11.826,135.844,-11.905,135.834,-11.951,135.896,-11.97,135.889,-11.993,135.804,-12.055,135.703,-12.152,135.704,-12.21,135.744,-12.242,135.791,-12.228,135.857,-12.179,135.938,-12.152,136.008,-12.191,136.031,-12.331,136.082,-12.422,136.193,-12.435,136.261,-12.434,136.329,-12.306,136.292,-12.196,136.25,-12.173,136.27,-12.132,136.443,-11.951,136.54,-11.958,136.61,-12.134,136.719,-12.226,136.836,-12.219,136.897,-12.244,136.947,-12.35,136.537,-12.784,136.518,-12.833,136.573,-12.912,136.594,-13.004,136.461,-13.225,136.412,-13.236,136.365,-13.176,136.294,-13.138,136.232,-13.165,136.166,-13.181,135.927,-13.304,135.929,-13.622,135.99,-13.81,135.954,-13.935,135.883,-14.153,135.806,-14.234,135.745,-14.287,135.539,-14.585,135.473,-14.657,135.405,-14.758,135.428,-14.856,135.453,-14.923,135.531,-15,135.833,-15.16,135.97,-15.27,136.205,-15.403,136.259,-15.495,136.291,-15.57,136.462,-15.655,136.584,-15.707,136.619,-15.693,136.644,-15.676,136.675,-15.675,136.705,-15.685,136.7,-15.752,136.687,-15.788,136.698,-15.835,136.785,-15.894,136.923,-15.892,137.002,-15.878,137.09,-15.941,137.169,-15.982,137.299,-16.066,137.526,-16.167,137.704,-16.233,137.913,-16.477,138.072,-16.617,138.245,-16.718,138.506,-16.79,138.626,-16.778,138.82,-16.861,139.01,-16.899,139.11,-17.014,139.145,-17.101,139.154,-17.168,139.248,-17.329,139.441,-17.381,139.69,-17.541,139.895,-17.611,139.946,-17.654,140.036,-17.703,140.21,-17.704,140.511,-17.625,140.648,-17.544,140.83,-17.414,140.916,-17.193,140.966,-17.015,141.219,-16.646,141.291,-16.463,141.356,-16.221,141.412,-16.07,141.393,-15.905,141.452,-15.605,141.581,-15.195,141.625,-15.057,141.604,-14.853,141.523,-14.47,141.559,-14.338,141.594,-14.153,141.535,-14.019,141.481,-13.927,141.473,-13.798,141.534,-13.554,141.589,-13.425,141.645,-13.259,141.614,-12.943,141.735,-12.833,141.782,-12.779,141.876,-12.778,141.92,-12.803,141.93,-12.74,141.893,-12.681,141.878,-12.613,141.852,-12.579,141.795,-12.567,141.747,-12.529,141.678,-12.491,141.689,-12.351,141.806,-12.08,141.871,-11.976,141.913,-12.019,141.961,-12.054,141.968,-11.976,141.952,-11.896,142.041,-11.632,142.139,-11.273,142.168,-10.947,142.326,-10.884,142.407,-10.802,142.456,-10.707,142.545,-10.707,142.605,-10.748,142.565,-10.819,142.553,-10.874,142.723,-11.01,142.78,-11.115,142.803,-11.214,142.837,-11.307,142.853,-11.432,142.851,-11.632,142.873,-11.821,142.934,-11.881,142.988,-11.919,143.066,-11.924,143.179,-11.954]],[[153.077,-25.751,153.052,-25.778,153.007,-25.729,152.977,-25.551,152.999,-25.448,153.052,-25.354,153.061,-25.302,153.038,-25.193,153.189,-25.071,153.228,-25.006,153.242,-24.923,153.186,-24.833,153.144,-24.815,153.181,-24.765,153.223,-24.74,153.257,-24.729,153.282,-24.738,153.298,-24.915,153.359,-24.978,153.35,-25.063,153.141,-25.513,153.084,-25.683,153.077,-25.751]],[[139.508,-16.573,139.431,-16.661,139.392,-16.649,139.354,-16.697,139.283,-16.719,139.239,-16.719,139.16,-16.742,139.148,-16.714,139.163,-16.626,139.229,-16.528,139.293,-16.467,139.459,-16.438,139.588,-16.395,139.604,-16.403,139.698,-16.515,139.56,-16.529,139.508,-16.573]],[[136.715,-13.804,136.758,-13.845,136.804,-13.842,136.845,-13.751,136.871,-13.764,136.891,-13.787,136.906,-13.827,136.843,-13.897,136.815,-13.907,136.788,-13.946,136.745,-14.073,136.75,-14.115,136.787,-14.158,136.885,-14.197,136.934,-14.179,136.951,-14.184,136.931,-14.246,136.894,-14.293,136.763,-14.273,136.65,-14.28,136.461,-14.235,136.363,-14.229,136.335,-14.212,136.392,-14.175,136.428,-14.126,136.411,-14.011,136.425,-13.865,136.534,-13.794,136.583,-13.721,136.656,-13.676,136.702,-13.682,136.696,-13.726,136.715,-13.804]],[[130.459,-11.679,130.542,-11.703,130.58,-11.737,130.603,-11.773,130.606,-11.817,130.503,-11.836,130.317,-11.772,130.131,-11.825,130.077,-11.825,130.043,-11.787,130.072,-11.681,130.139,-11.697,130.198,-11.658,130.187,-11.541,130.153,-11.478,130.251,-11.361,130.295,-11.337,130.339,-11.337,130.377,-11.42,130.386,-11.51,130.433,-11.592,130.459,-11.679]],[[130.619,-11.376,130.752,-11.384,130.913,-11.309,130.987,-11.34,131.023,-11.334,131.141,-11.263,131.217,-11.243,131.268,-11.19,131.321,-11.247,131.437,-11.313,131.473,-11.383,131.522,-11.415,131.539,-11.437,131.468,-11.51,131.459,-11.588,131.383,-11.583,131.292,-11.711,130.951,-11.926,130.645,-11.742,130.512,-11.618,130.423,-11.446,130.405,-11.305,130.369,-11.215,130.385,-11.192,130.403,-11.18,130.427,-11.183,130.519,-11.279,130.56,-11.306,130.619,-11.376]],[[113.183,-26.053,113.156,-26.095,112.964,-25.783,112.908,-25.57,112.947,-25.532,112.982,-25.52,113.096,-25.815,113.132,-25.883,113.132,-25.952,113.148,-25.974,113.183,-26.053]],[[137.596,-35.739,137.836,-35.762,137.929,-35.726,138.047,-35.755,138.123,-35.852,138.067,-35.901,138.012,-35.908,137.836,-35.868,137.671,-35.898,137.622,-35.938,137.59,-36.027,137.448,-36.075,137.382,-36.021,137.21,-35.982,137.148,-36.039,137.026,-36.024,136.913,-36.047,136.755,-36.033,136.589,-35.935,136.541,-35.89,136.579,-35.809,136.639,-35.749,137.092,-35.664,137.334,-35.592,137.53,-35.605,137.585,-35.62,137.635,-35.656,137.598,-35.722,137.596,-35.739]],[[145.487,-38.355,145.336,-38.421,145.28,-38.391,145.286,-38.341,145.295,-38.319,145.427,-38.314,145.487,-38.355]],[[145.314,-38.491,145.349,-38.538,145.355,-38.557,145.271,-38.52,145.128,-38.528,145.218,-38.459,145.288,-38.472,145.314,-38.491]],[[149.044,-20.292,149.02,-20.303,148.987,-20.302,148.939,-20.284,148.981,-20.154,149.004,-20.221,149.045,-20.278,149.044,-20.292]],[[148.936,-20.15,148.913,-20.154,148.887,-20.144,148.906,-20.102,148.932,-20.069,148.968,-20.044,148.956,-20.135,148.936,-20.15]],[[151.147,-23.491,151.181,-23.516,151.212,-23.513,151.24,-23.53,151.229,-23.595,151.274,-23.668,151.296,-23.72,151.262,-23.762,151.238,-23.776,151.184,-23.741,151.033,-23.53,151.06,-23.461,151.147,-23.491]],[[150.517,-22.323,150.488,-22.325,150.462,-22.308,150.485,-22.268,150.488,-22.211,150.521,-22.228,150.549,-22.307,150.517,-22.323]],[[149.928,-22.193,149.894,-22.223,149.87,-22.15,149.875,-22.074,149.912,-22.049,149.928,-22.149,149.928,-22.193]],[[153.539,-27.436,153.453,-27.712,153.427,-27.706,153.396,-27.665,153.401,-27.506,153.435,-27.405,153.522,-27.422,153.539,-27.436]],[[153.442,-27.316,153.421,-27.331,153.377,-27.235,153.365,-27.139,153.38,-27.049,153.432,-27.03,153.467,-27.038,153.426,-27.201,153.442,-27.316]],[[142.275,-10.705,142.191,-10.762,142.137,-10.732,142.125,-10.668,142.131,-10.641,142.198,-10.592,142.275,-10.705]],[[142.339,-10.192,142.279,-10.254,142.216,-10.236,142.195,-10.199,142.219,-10.149,142.299,-10.14,142.339,-10.192]],[[142.168,-10.154,142.142,-10.181,142.098,-10.122,142.149,-10.052,142.192,-10.085,142.168,-10.154]],[[146.278,-18.231,146.299,-18.326,146.342,-18.4,146.327,-18.449,146.299,-18.485,146.236,-18.451,146.191,-18.363,146.116,-18.292,146.099,-18.252,146.187,-18.255,146.231,-18.241,146.249,-18.226,146.278,-18.231]],[[136.599,-11.379,136.527,-11.439,136.522,-11.394,136.56,-11.358,136.649,-11.212,136.688,-11.178,136.711,-11.158,136.727,-11.105,136.732,-11.025,136.78,-11.012,136.741,-11.195,136.599,-11.379]],[[136.339,-11.602,136.18,-11.677,136.267,-11.576,136.449,-11.487,136.479,-11.466,136.471,-11.509,136.379,-11.583,136.339,-11.602]],[[137.094,-15.778,137.051,-15.824,136.996,-15.776,136.985,-15.726,136.943,-15.712,136.963,-15.666,136.986,-15.652,137.01,-15.595,137.065,-15.663,137.071,-15.738,137.094,-15.778]],[[136.863,-15.62,136.847,-15.627,136.846,-15.544,136.877,-15.503,136.89,-15.589,136.863,-15.62]],[[136.591,-15.628,136.531,-15.632,136.514,-15.627,136.503,-15.583,136.523,-15.543,136.586,-15.534,136.612,-15.544,136.591,-15.628]],[[139.459,-17.115,139.422,-17.132,139.408,-17.091,139.459,-17.049,139.493,-16.99,139.56,-17.042,139.571,-17.094,139.459,-17.115]],[[136.237,-13.825,136.214,-13.836,136.123,-13.817,136.122,-13.781,136.134,-13.753,136.16,-13.737,136.215,-13.665,136.257,-13.707,136.275,-13.791,136.237,-13.825]],[[132.593,-11.303,132.574,-11.318,132.494,-11.164,132.516,-11.116,132.484,-11.037,132.538,-11.028,132.579,-10.969,132.593,-10.998,132.597,-11.106,132.629,-11.169,132.593,-11.303]],[[125.199,-14.579,125.135,-14.642,125.091,-14.592,125.117,-14.492,125.16,-14.456,125.198,-14.475,125.194,-14.553,125.199,-14.579]],[[124.597,-15.402,124.56,-15.43,124.524,-15.421,124.524,-15.382,124.483,-15.34,124.504,-15.292,124.519,-15.267,124.551,-15.27,124.565,-15.311,124.605,-15.357,124.597,-15.402]],[[115.446,-20.788,115.388,-20.866,115.318,-20.851,115.309,-20.811,115.354,-20.746,115.435,-20.668,115.458,-20.716,115.446,-20.788]],[[145.043,-40.787,145.159,-40.791,145.224,-40.765,145.283,-40.77,145.349,-40.826,145.429,-40.858,145.485,-40.852,145.533,-40.864,145.576,-40.904,145.686,-40.939,145.734,-40.962,145.775,-40.997,145.821,-41.025,146.111,-41.118,146.317,-41.163,146.574,-41.142,146.651,-41.116,146.723,-41.078,146.786,-41.114,146.848,-41.168,146.836,-41.109,146.857,-41.058,146.919,-41.018,146.99,-40.992,147.106,-40.994,147.219,-40.983,147.269,-40.96,147.321,-40.956,147.388,-40.986,147.455,-41.002,147.501,-40.964,147.579,-40.876,147.622,-40.845,147.818,-40.872,147.873,-40.873,147.969,-40.78,148.033,-40.781,148.215,-40.855,148.293,-40.947,148.285,-41.115,148.292,-41.175,148.306,-41.233,148.312,-41.35,148.29,-41.465,148.287,-41.555,148.297,-41.646,148.288,-41.816,148.316,-41.928,148.302,-42.004,148.301,-42.04,148.328,-42.074,148.341,-42.111,148.331,-42.159,148.343,-42.215,148.331,-42.262,148.29,-42.255,148.277,-42.219,148.285,-42.173,148.277,-42.136,148.256,-42.103,148.183,-42.065,148.204,-42.042,148.242,-42.022,148.214,-41.97,148.167,-42.012,148.141,-42.07,148.156,-42.088,148.128,-42.104,148.067,-42.17,148.023,-42.259,148.005,-42.345,148.009,-42.436,147.974,-42.506,147.924,-42.572,147.912,-42.658,147.915,-42.816,147.958,-42.96,147.981,-43.157,147.945,-43.182,147.839,-43.195,147.786,-43.22,147.699,-43.123,147.648,-43.021,147.687,-42.98,147.774,-43.003,147.8,-42.98,147.807,-42.954,147.8,-42.928,147.693,-42.872,147.574,-42.846,147.536,-42.878,147.549,-42.975,147.537,-42.996,147.452,-43.033,147.408,-42.894,147.298,-42.791,147.302,-42.841,147.348,-42.927,147.343,-42.964,147.325,-43.013,147.281,-43.032,147.26,-43.071,147.26,-43.126,147.245,-43.216,147.173,-43.256,146.997,-43.156,146.985,-43.19,146.988,-43.219,147.077,-43.276,147.036,-43.319,147.005,-43.37,146.955,-43.502,146.874,-43.612,146.834,-43.619,146.699,-43.602,146.549,-43.509,146.413,-43.52,146.187,-43.513,146.043,-43.547,146.013,-43.445,145.982,-43.408,145.994,-43.376,146.109,-43.354,146.226,-43.355,146.208,-43.316,146.176,-43.302,146.125,-43.311,145.975,-43.277,145.873,-43.292,145.803,-43.244,145.682,-43.076,145.61,-42.998,145.567,-42.968,145.518,-42.951,145.488,-42.927,145.268,-42.544,145.237,-42.456,145.199,-42.231,145.373,-42.338,145.435,-42.407,145.468,-42.493,145.527,-42.388,145.517,-42.354,145.36,-42.228,145.34,-42.191,145.331,-42.147,145.294,-42.191,145.235,-42.197,145.259,-42.107,145.238,-42.02,145.055,-41.827,144.916,-41.644,144.778,-41.419,144.766,-41.39,144.764,-41.342,144.698,-41.191,144.662,-41.079,144.646,-40.981,144.71,-40.783,144.719,-40.672,144.819,-40.722,145.043,-40.787]],[[143.928,-40.116,143.899,-40.12,143.876,-40.064,143.888,-39.984,143.839,-39.904,143.865,-39.824,143.862,-39.738,143.879,-39.7,143.939,-39.658,143.949,-39.584,144.001,-39.58,144.091,-39.638,144.121,-39.785,144.106,-39.874,144.141,-39.954,144.112,-40.022,144.035,-40.078,143.928,-40.116]],[[148,-39.758,148.178,-39.938,148.27,-39.967,148.297,-39.986,148.29,-40.065,148.251,-40.1,148.323,-40.144,148.314,-40.174,148.299,-40.172,148.21,-40.234,148.106,-40.262,148.074,-40.241,148.047,-40.213,148.025,-40.172,147.891,-40.015,147.906,-39.971,147.876,-39.905,147.812,-39.91,147.767,-39.87,147.839,-39.832,147.933,-39.726,148,-39.758]],[[147.356,-43.397,147.309,-43.501,147.231,-43.483,147.154,-43.5,147.105,-43.431,147.105,-43.413,147.163,-43.43,147.185,-43.408,147.198,-43.379,147.22,-43.371,147.234,-43.33,147.284,-43.279,147.313,-43.28,147.342,-43.346,147.356,-43.397]],[[148.104,-42.71,148.048,-42.719,148.03,-42.715,148.031,-42.663,148.023,-42.64,148.073,-42.593,148.143,-42.616,148.17,-42.652,148.101,-42.681,148.104,-42.71]],[[147.435,-43.241,147.372,-43.241,147.349,-43.232,147.338,-43.183,147.296,-43.162,147.319,-43.145,147.327,-43.115,147.353,-43.08,147.397,-43.118,147.435,-43.241]],[[144.784,-40.507,144.748,-40.589,144.71,-40.485,144.751,-40.47,144.783,-40.435,144.791,-40.44,144.784,-40.507]],[[148.326,-40.307,148.421,-40.367,148.474,-40.432,148.404,-40.487,148.353,-40.497,148.319,-40.435,148.214,-40.458,148.103,-40.452,148.02,-40.404,148.01,-40.381,148.059,-40.357,148.198,-40.358,148.326,-40.307]],[[148.237,-40.515,148.188,-40.593,148.127,-40.544,148.117,-40.521,148.193,-40.503,148.218,-40.505,148.237,-40.515]],[[158.879,-54.71,158.845,-54.749,158.836,-54.704,158.897,-54.506,158.959,-54.472,158.946,-54.575,158.879,-54.71]]]},{"n":"Australian Indian Ocean Territories","c":"AU","r":5,"ml":5,"lx":105.67,"ly":-10.49,"b":[96.826,-12.2,105.725,-10.431],"p":[[[105.725,-10.493,105.697,-10.564,105.644,-10.525,105.584,-10.512,105.596,-10.46,105.646,-10.452,105.67,-10.449,105.705,-10.431,105.725,-10.493]],[[96.84,-12.182,96.852,-12.187,96.867,-12.181,96.874,-12.188,96.85,-12.197,96.835,-12.18,96.828,-12.151,96.826,-12.126,96.833,-12.126,96.833,-12.136,96.834,-12.144,96.839,-12.16,96.836,-12.171,96.84,-12.182]],[[96.918,-12.194,96.907,-12.2,96.897,-12.196,96.894,-12.193,96.893,-12.187,96.904,-12.187,96.913,-12.182,96.919,-12.173,96.921,-12.162,96.925,-12.173,96.924,-12.185,96.918,-12.194]]]},{"n":"Heard Island and McDonald Islands","c":"HM","r":5,"ml":4.5,"lx":73.51,"ly":-53.1,"b":[73.251,-53.185,73.838,-52.966],"p":[[[73.707,-53.137,73.588,-53.185,73.465,-53.184,73.413,-53.147,73.336,-53.03,73.285,-53.021,73.254,-52.989,73.251,-52.976,73.305,-52.966,73.388,-53,73.586,-53.027,73.731,-53.091,73.838,-53.113,73.795,-53.13,73.707,-53.137]]]},{"n":"Norfolk Island","c":"NF","r":5,"ml":4.5,"lx":167.95,"ly":-29.03,"b":[167.906,-29.096,167.99,-29.014],"p":[[[167.939,-29.018,167.96,-29.028,167.978,-29.034,167.99,-29.042,167.989,-29.059,167.979,-29.076,167.968,-29.083,167.964,-29.085,167.962,-29.088,167.961,-29.092,167.961,-29.096,167.955,-29.082,167.944,-29.073,167.934,-29.072,167.927,-29.083,167.92,-29.083,167.918,-29.072,167.914,-29.062,167.912,-29.053,167.916,-29.045,167.924,-29.036,167.925,-29.029,167.919,-29.025,167.906,-29.028,167.921,-29.014,167.939,-29.018]]]},{"n":"Ashmore and Cartier Islands","c":"AU","r":5,"ml":4.5,"lx":123.59,"ly":-12.43,"b":[123.572,-12.436,123.595,-12.424],"p":[[[123.595,-12.426,123.595,-12.436,123.573,-12.434,123.572,-12.424,123.595,-12.426]]]},{"n":"Armenia","c":"AM","r":6,"ml":5,"lx":44.8,"ly":40.46,"b":[43.439,38.869,46.585,41.291],"p":[[[44.768,39.704,44.734,39.746,44.56,39.888,44.4,39.996,44.289,40.04,44.178,40.036,44.005,40.014,43.942,40.023,43.792,40.07,43.666,40.126,43.683,40.15,43.71,40.167,43.678,40.239,43.608,40.357,43.616,40.393,43.594,40.444,43.569,40.482,43.668,40.574,43.713,40.648,43.723,40.72,43.696,40.794,43.632,40.929,43.592,40.968,43.517,41.005,43.455,41.065,43.439,41.107,43.492,41.116,43.645,41.117,43.793,41.131,43.909,41.159,44.077,41.183,44.146,41.203,44.227,41.213,44.473,41.191,44.565,41.208,44.841,41.211,44.849,41.22,44.811,41.249,44.811,41.259,44.976,41.277,45.001,41.291,45.023,41.246,45.085,41.195,45.152,41.175,45.189,41.147,45.19,41.126,45.071,41.101,45.063,41.088,45.071,41.076,45.106,41.069,45.273,41.006,45.369,41.005,45.419,40.986,45.444,40.948,45.524,40.897,45.588,40.847,45.591,40.83,45.579,40.804,45.401,40.707,45.379,40.674,45.376,40.638,45.454,40.532,45.57,40.417,45.736,40.329,45.965,40.234,45.968,40.175,45.931,40.105,45.9,40.057,45.886,40.025,45.858,40.011,45.63,40.014,45.596,40.003,45.581,39.989,45.58,39.978,45.662,39.956,45.79,39.881,45.863,39.808,45.94,39.777,46.026,39.719,46.095,39.664,46.202,39.594,46.322,39.617,46.481,39.555,46.488,39.513,46.478,39.475,46.378,39.434,46.365,39.417,46.365,39.402,46.378,39.382,46.437,39.349,46.507,39.299,46.585,39.224,46.55,39.201,46.477,39.198,46.42,39.207,46.4,39.192,46.401,39.168,46.475,39.111,46.49,39.069,46.487,38.997,46.491,38.907,46.318,38.913,46.17,38.869,46.114,38.878,46.077,38.955,46.046,39.018,45.952,39.178,45.977,39.244,45.925,39.282,45.799,39.35,45.766,39.378,45.784,39.417,45.796,39.488,45.784,39.546,45.75,39.563,45.687,39.564,45.611,39.55,45.457,39.494,45.35,39.53,45.288,39.566,45.253,39.595,45.173,39.571,45.153,39.583,45.149,39.657,45.125,39.696,45.076,39.743,45.032,39.765,44.867,39.719,44.768,39.704],[45.024,41.027,45.029,41.054,45.021,41.078,44.994,41.086,44.961,41.079,44.959,41.053,44.969,41.027,45.002,41.016,45.024,41.027]],[[45.552,40.616,45.514,40.6,45.479,40.607,45.479,40.648,45.504,40.665,45.534,40.664,45.562,40.649,45.552,40.616]]]},{"n":"Argentina","c":"AR","r":2,"ml":2,"lx":-64.17,"ly":-33.5,"b":[-73.576,-55.032,-53.669,-21.803],"p":[[[-57.609,-30.188,-57.646,-30.227,-57.651,-30.295,-57.713,-30.384,-57.831,-30.495,-57.873,-30.591,-57.819,-30.712,-57.811,-30.859,-57.834,-30.917,-57.886,-30.937,-57.898,-30.975,-57.87,-31.031,-57.868,-31.104,-57.893,-31.195,-57.948,-31.299,-58.033,-31.417,-58.054,-31.495,-58.01,-31.534,-57.988,-31.576,-57.989,-31.621,-58.007,-31.685,-58.042,-31.769,-58.096,-31.832,-58.167,-31.873,-58.189,-31.924,-58.16,-31.987,-58.156,-32.052,-58.177,-32.119,-58.165,-32.185,-58.12,-32.249,-58.123,-32.322,-58.201,-32.472,-58.22,-32.564,-58.171,-32.959,-58.201,-33.015,-58.25,-33.078,-58.309,-33.083,-58.376,-33.072,-58.424,-33.112,-58.455,-33.286,-58.547,-33.663,-58.531,-33.753,-58.457,-33.898,-58.429,-33.991,-58.409,-34.061,-58.392,-34.193,-58.435,-34.253,-58.475,-34.263,-58.525,-34.296,-58.466,-34.457,-58.419,-34.532,-58.283,-34.683,-57.764,-34.895,-57.548,-35.019,-57.304,-35.188,-57.171,-35.362,-57.159,-35.506,-57.354,-35.72,-57.375,-35.9,-57.335,-36.027,-57.265,-36.144,-57.076,-36.297,-56.937,-36.353,-56.749,-36.346,-56.717,-36.389,-56.698,-36.426,-56.668,-36.735,-56.672,-36.851,-56.727,-36.958,-57.088,-37.446,-57.396,-37.745,-57.507,-37.909,-57.547,-38.086,-57.646,-38.17,-58.179,-38.436,-59.007,-38.673,-59.676,-38.797,-59.828,-38.838,-60.904,-38.974,-61.112,-38.993,-61.383,-38.981,-61.603,-38.999,-61.848,-38.962,-62.067,-38.919,-62.189,-38.813,-62.335,-38.8,-62.374,-38.853,-62.304,-38.988,-62.338,-39.151,-62.295,-39.243,-62.209,-39.262,-62.126,-39.31,-62.054,-39.374,-62.179,-39.38,-62.131,-39.432,-62.077,-39.462,-62.083,-39.568,-62.132,-39.825,-62.254,-39.88,-62.287,-39.895,-62.324,-39.951,-62.402,-40.197,-62.427,-40.356,-62.394,-40.459,-62.246,-40.675,-62.302,-40.815,-62.395,-40.891,-62.798,-41.047,-62.959,-41.11,-63.213,-41.152,-63.622,-41.16,-63.773,-41.15,-64.123,-41.008,-64.383,-40.922,-64.621,-40.854,-64.853,-40.814,-64.82,-40.793,-64.804,-40.757,-64.869,-40.736,-64.917,-40.731,-65.069,-40.805,-65.133,-40.881,-65.152,-40.947,-65.155,-41.106,-65.128,-41.239,-65.018,-41.567,-65.007,-41.745,-65.059,-41.97,-64.986,-42.102,-64.898,-42.162,-64.7,-42.221,-64.622,-42.261,-64.538,-42.255,-64.512,-42.27,-64.524,-42.299,-64.574,-42.356,-64.571,-42.416,-64.42,-42.434,-64.265,-42.422,-64.101,-42.395,-64.062,-42.353,-64.061,-42.266,-64.253,-42.251,-64.229,-42.218,-64.083,-42.183,-63.893,-42.125,-63.796,-42.114,-63.729,-42.153,-63.685,-42.189,-63.63,-42.283,-63.596,-42.407,-63.594,-42.556,-63.617,-42.696,-63.644,-42.746,-63.692,-42.805,-64.035,-42.881,-64.131,-42.861,-64.22,-42.756,-64.248,-42.646,-64.324,-42.572,-64.488,-42.513,-64.65,-42.531,-64.812,-42.633,-64.971,-42.666,-65.027,-42.759,-64.629,-42.909,-64.442,-42.951,-64.38,-42.949,-64.319,-42.969,-64.376,-43.025,-64.432,-43.059,-64.715,-43.136,-64.84,-43.189,-64.986,-43.294,-65.19,-43.522,-65.252,-43.572,-65.284,-43.63,-65.305,-43.787,-65.239,-44.049,-65.308,-44.158,-65.266,-44.28,-65.29,-44.361,-65.361,-44.477,-65.648,-44.661,-65.698,-44.796,-65.599,-44.876,-65.606,-44.945,-65.639,-45.008,-65.758,-45.007,-66.19,-44.965,-66.348,-45.034,-66.494,-45.118,-66.533,-45.158,-66.585,-45.183,-66.882,-45.228,-66.941,-45.257,-67.258,-45.577,-67.393,-45.776,-67.557,-45.97,-67.6,-46.053,-67.609,-46.167,-67.586,-46.27,-67.563,-46.345,-67.506,-46.443,-67.387,-46.554,-66.777,-47.006,-66.65,-47.045,-65.999,-47.094,-65.854,-47.157,-65.769,-47.257,-65.738,-47.345,-65.775,-47.568,-65.814,-47.638,-65.886,-47.702,-66.041,-47.783,-66.225,-47.827,-66.172,-47.858,-66.097,-47.853,-65.934,-47.827,-65.864,-47.853,-65.81,-47.941,-65.912,-47.977,-65.943,-48.019,-66.017,-48.084,-66.393,-48.342,-66.596,-48.42,-66.783,-48.523,-67.033,-48.628,-67.131,-48.688,-67.263,-48.814,-67.466,-48.952,-67.685,-49.247,-67.694,-49.304,-67.662,-49.342,-67.783,-49.859,-67.826,-49.92,-67.914,-49.984,-68.146,-50.091,-68.257,-50.105,-68.405,-50.043,-68.488,-49.978,-68.569,-49.867,-68.668,-49.753,-68.673,-49.793,-68.638,-49.863,-68.662,-49.936,-68.913,-49.969,-68.98,-50.003,-68.753,-49.988,-68.598,-50.009,-68.533,-50.036,-68.474,-50.091,-68.422,-50.158,-68.465,-50.195,-68.589,-50.225,-68.75,-50.281,-68.939,-50.382,-69.045,-50.499,-69.09,-50.583,-69.141,-50.753,-69.155,-50.864,-69.235,-50.951,-69.359,-51.028,-69.352,-51.046,-69.268,-51.006,-69.201,-50.994,-69.144,-51.097,-69.066,-51.304,-69.03,-51.446,-69.035,-51.489,-69.058,-51.547,-69.218,-51.561,-69.361,-51.559,-69.465,-51.584,-69.409,-51.61,-69.313,-51.601,-69.18,-51.662,-69.033,-51.636,-68.965,-51.677,-68.917,-51.715,-68.691,-52.013,-68.494,-52.198,-68.394,-52.307,-68.443,-52.357,-68.461,-52.29,-68.59,-52.273,-68.715,-52.255,-68.925,-52.208,-69.206,-52.136,-69.488,-52.136,-69.713,-52.075,-69.96,-52.008,-70.483,-52.002,-70.943,-51.998,-71.415,-51.994,-71.717,-51.991,-71.919,-51.99,-71.971,-51.964,-71.953,-51.88,-72.028,-51.819,-72.137,-51.744,-72.269,-51.691,-72.335,-51.62,-72.408,-51.541,-72.366,-51.47,-72.303,-51.299,-72.302,-51.223,-72.359,-51.17,-72.377,-51.095,-72.359,-51.06,-72.307,-51.033,-72.276,-50.91,-72.301,-50.79,-72.34,-50.682,-72.393,-50.634,-72.46,-50.612,-72.51,-50.608,-72.62,-50.648,-72.804,-50.638,-72.866,-50.653,-72.956,-50.696,-73.082,-50.76,-73.153,-50.738,-73.175,-50.67,-73.222,-50.611,-73.252,-50.558,-73.274,-50.473,-73.312,-50.362,-73.387,-50.231,-73.501,-50.125,-73.508,-50.03,-73.529,-49.911,-73.47,-49.795,-73.505,-49.698,-73.576,-49.583,-73.554,-49.464,-73.484,-49.398,-73.462,-49.314,-73.135,-49.301,-73.149,-49.188,-73.095,-49.097,-73.034,-49.014,-72.982,-48.977,-72.865,-48.944,-72.728,-48.896,-72.651,-48.842,-72.614,-48.793,-72.592,-48.73,-72.586,-48.662,-72.608,-48.519,-72.583,-48.475,-72.498,-48.417,-72.355,-48.366,-72.293,-48.229,-72.328,-48.11,-72.408,-48.016,-72.509,-47.973,-72.518,-47.876,-72.472,-47.784,-72.413,-47.686,-72.342,-47.572,-72.346,-47.493,-72.283,-47.446,-72.103,-47.343,-72.042,-47.241,-71.979,-47.214,-71.905,-47.202,-71.901,-47.144,-71.954,-47.087,-71.963,-47.016,-71.957,-46.937,-71.94,-46.831,-71.856,-46.792,-71.733,-46.706,-71.7,-46.651,-71.695,-46.578,-71.731,-46.428,-71.762,-46.32,-71.778,-46.28,-71.834,-46.207,-71.876,-46.161,-71.809,-46.103,-71.684,-46.042,-71.632,-45.954,-71.68,-45.879,-71.751,-45.839,-71.773,-45.724,-71.746,-45.579,-71.693,-45.535,-71.508,-45.513,-71.49,-45.438,-71.349,-45.332,-71.354,-45.23,-71.443,-45.168,-71.531,-45.068,-71.596,-44.979,-71.812,-44.931,-72.042,-44.904,-72.073,-44.82,-72.064,-44.772,-71.957,-44.792,-71.783,-44.774,-71.652,-44.77,-71.56,-44.762,-71.455,-44.75,-71.358,-44.785,-71.261,-44.763,-71.221,-44.631,-71.16,-44.56,-71.151,-44.494,-71.213,-44.441,-71.326,-44.425,-71.82,-44.383,-71.835,-44.33,-71.831,-44.241,-71.812,-44.151,-71.812,-44.106,-71.767,-44.067,-71.716,-43.984,-71.68,-43.93,-71.716,-43.858,-71.795,-43.753,-71.737,-43.705,-71.733,-43.647,-71.751,-43.59,-71.832,-43.527,-71.905,-43.44,-71.905,-43.348,-71.82,-43.323,-71.764,-43.295,-71.751,-43.237,-71.781,-43.167,-71.899,-43.145,-72.055,-43.102,-72.102,-43.066,-72.146,-42.99,-72.114,-42.777,-72.13,-42.648,-72.144,-42.577,-72.105,-42.522,-72.053,-42.473,-72.078,-42.358,-72.125,-42.298,-72.108,-42.252,-72.064,-42.205,-72.026,-42.148,-71.993,-42.134,-71.944,-42.167,-71.861,-42.148,-71.761,-42.101,-71.75,-42.047,-71.77,-41.969,-71.844,-41.772,-71.911,-41.65,-71.898,-41.607,-71.871,-41.561,-71.892,-41.393,-71.886,-41.292,-71.881,-40.995,-71.873,-40.893,-71.941,-40.789,-71.932,-40.692,-71.884,-40.621,-71.839,-40.524,-71.805,-40.439,-71.769,-40.401,-71.709,-40.382,-71.695,-40.335,-71.723,-40.3,-71.801,-40.244,-71.818,-40.177,-71.802,-40.125,-71.764,-40.095,-71.704,-40.095,-71.66,-40.021,-71.647,-39.929,-71.638,-39.887,-71.672,-39.833,-71.697,-39.707,-71.72,-39.635,-71.693,-39.605,-71.654,-39.594,-71.587,-39.611,-71.539,-39.602,-71.531,-39.564,-71.526,-39.523,-71.508,-39.495,-71.465,-39.402,-71.42,-39.287,-71.409,-39.206,-71.426,-38.986,-71.402,-38.935,-71.353,-38.889,-71.286,-38.845,-71.197,-38.809,-71.087,-38.758,-70.952,-38.738,-70.897,-38.681,-70.859,-38.604,-70.848,-38.542,-70.9,-38.498,-70.968,-38.446,-71,-38.315,-71.018,-38.194,-71.028,-38.041,-71.096,-37.91,-71.168,-37.762,-71.187,-37.631,-71.163,-37.559,-71.135,-37.445,-71.165,-37.393,-71.2,-37.3,-71.163,-37.227,-71.118,-37.114,-71.124,-37.057,-71.159,-36.92,-71.192,-36.844,-71.159,-36.762,-71.107,-36.685,-71.066,-36.644,-71.073,-36.578,-71.056,-36.524,-70.978,-36.487,-70.905,-36.42,-70.853,-36.412,-70.79,-36.412,-70.749,-36.393,-70.733,-36.341,-70.722,-36.283,-70.622,-36.212,-70.563,-36.146,-70.457,-36.133,-70.405,-36.062,-70.404,-35.971,-70.416,-35.879,-70.38,-35.772,-70.42,-35.609,-70.416,-35.523,-70.457,-35.452,-70.449,-35.375,-70.47,-35.326,-70.532,-35.308,-70.555,-35.247,-70.525,-35.217,-70.467,-35.194,-70.393,-35.147,-70.338,-34.922,-70.312,-34.855,-70.287,-34.775,-70.29,-34.733,-70.255,-34.673,-70.211,-34.581,-70.141,-34.493,-70.101,-34.432,-70.063,-34.35,-70.052,-34.301,-70.003,-34.276,-69.946,-34.27,-69.88,-34.254,-69.852,-34.224,-69.857,-34.18,-69.862,-34.084,-69.881,-33.93,-69.894,-33.731,-69.883,-33.601,-69.839,-33.47,-69.798,-33.399,-69.809,-33.344,-69.82,-33.284,-69.896,-33.251,-69.969,-33.279,-70.02,-33.271,-70.085,-33.202,-70.104,-33.128,-70.093,-33.027,-70.042,-32.964,-70.022,-32.885,-70.052,-32.86,-70.116,-32.807,-70.177,-32.626,-70.17,-32.472,-70.23,-32.431,-70.258,-32.31,-70.32,-32.267,-70.345,-32.176,-70.364,-32.083,-70.356,-32.042,-70.291,-32.031,-70.254,-31.958,-70.282,-31.917,-70.331,-31.881,-70.394,-31.884,-70.45,-31.842,-70.526,-31.666,-70.585,-31.569,-70.566,-31.428,-70.555,-31.317,-70.529,-31.223,-70.52,-31.148,-70.473,-31.113,-70.429,-31.129,-70.388,-31.121,-70.351,-31.06,-70.309,-31.023,-70.312,-30.993,-70.336,-30.96,-70.348,-30.902,-70.319,-30.834,-70.269,-30.677,-70.194,-30.505,-70.161,-30.44,-70.17,-30.386,-70.153,-30.361,-70.102,-30.388,-69.956,-30.358,-69.907,-30.282,-69.888,-30.213,-69.844,-30.175,-69.863,-30.12,-69.924,-30.104,-69.96,-30.078,-69.945,-30.016,-69.924,-29.874,-69.928,-29.769,-69.983,-29.545,-70.027,-29.324,-69.996,-29.25,-69.9,-29.149,-69.828,-29.103,-69.815,-29.046,-69.743,-28.784,-69.735,-28.641,-69.688,-28.562,-69.657,-28.414,-69.527,-28.286,-69.489,-28.201,-69.437,-28.193,-69.41,-28.165,-69.341,-28.071,-69.251,-27.974,-69.174,-27.925,-69.155,-27.848,-69.119,-27.744,-69.042,-27.57,-68.999,-27.449,-68.942,-27.405,-68.875,-27.247,-68.846,-27.154,-68.77,-27.115,-68.71,-27.104,-68.652,-27.148,-68.592,-27.14,-68.537,-27.085,-68.405,-27.048,-68.346,-27.028,-68.319,-26.973,-68.319,-26.878,-68.373,-26.806,-68.485,-26.67,-68.581,-26.518,-68.592,-26.47,-68.592,-26.418,-68.576,-26.352,-68.53,-26.277,-68.415,-26.154,-68.427,-26.065,-68.511,-25.741,-68.542,-25.652,-68.6,-25.486,-68.592,-25.42,-68.541,-25.237,-68.496,-25.163,-68.431,-25.149,-68.395,-25.125,-68.384,-25.092,-68.428,-25.051,-68.447,-24.999,-68.466,-24.925,-68.527,-24.899,-68.562,-24.838,-68.562,-24.747,-68.507,-24.63,-68.447,-24.597,-68.423,-24.545,-68.358,-24.497,-68.3,-24.46,-68.25,-24.392,-68.047,-24.308,-67.886,-24.243,-67.572,-24.119,-67.356,-24.034,-67.336,-23.975,-67.319,-23.935,-67.219,-23.634,-67.09,-23.245,-67.009,-23.001,-67.195,-22.822,-67.162,-22.774,-67.055,-22.651,-67.034,-22.552,-66.991,-22.51,-66.8,-22.41,-66.767,-22.343,-66.751,-22.269,-66.712,-22.216,-66.639,-22.205,-66.507,-22.158,-66.365,-22.114,-66.322,-22.053,-66.282,-21.947,-66.248,-21.83,-66.22,-21.803,-66.175,-21.806,-66.099,-21.835,-66.059,-21.879,-65.86,-22.02,-65.771,-22.1,-65.686,-22.11,-65.519,-22.095,-65.485,-22.098,-65.058,-22.103,-64.993,-22.11,-64.843,-22.144,-64.759,-22.171,-64.7,-22.186,-64.606,-22.229,-64.524,-22.372,-64.478,-22.485,-64.446,-22.585,-64.374,-22.761,-64.325,-22.828,-64.308,-22.795,-64.266,-22.603,-64.209,-22.491,-64.132,-22.366,-63.976,-22.073,-63.922,-22.029,-63.861,-22.007,-63.819,-22.005,-63.776,-22.027,-63.717,-22.028,-63.675,-22.004,-63.267,-22.001,-62.843,-21.997,-62.834,-21.999,-62.815,-22.05,-62.745,-22.16,-62.665,-22.218,-62.651,-22.234,-62.626,-22.262,-62.626,-22.29,-62.542,-22.35,-62.373,-22.439,-62.214,-22.612,-62.067,-22.869,-61.928,-23.059,-61.799,-23.182,-61.679,-23.269,-61.571,-23.319,-61.513,-23.36,-61.506,-23.392,-61.404,-23.458,-61.208,-23.557,-61.085,-23.656,-61.033,-23.756,-60.84,-23.858,-60.505,-23.964,-60.262,-24.014,-60.11,-24.009,-59.892,-24.094,-59.609,-24.267,-59.435,-24.387,-59.373,-24.454,-59.187,-24.562,-58.724,-24.787,-58.52,-24.843,-58.423,-24.894,-58.365,-24.959,-58.309,-24.979,-58.253,-24.954,-58.136,-24.977,-57.96,-25.049,-57.822,-25.136,-57.644,-25.328,-57.587,-25.405,-57.563,-25.474,-57.572,-25.534,-57.626,-25.599,-57.725,-25.667,-57.755,-25.697,-57.757,-25.726,-57.782,-25.784,-57.865,-25.907,-57.886,-25.964,-57.891,-26.007,-57.943,-26.053,-58.082,-26.139,-58.111,-26.18,-58.118,-26.225,-58.136,-26.251,-58.155,-26.263,-58.181,-26.307,-58.203,-26.381,-58.205,-26.477,-58.188,-26.593,-58.191,-26.63,-58.222,-26.65,-58.239,-26.677,-58.246,-26.731,-58.272,-26.771,-58.318,-26.796,-58.335,-26.825,-58.323,-26.858,-58.356,-26.89,-58.436,-26.922,-58.485,-26.968,-58.503,-27.029,-58.548,-27.084,-58.619,-27.132,-58.642,-27.196,-58.605,-27.314,-58.168,-27.273,-57.812,-27.317,-57.391,-27.43,-57.112,-27.47,-56.974,-27.436,-56.872,-27.441,-56.805,-27.485,-56.716,-27.494,-56.603,-27.468,-56.511,-27.488,-56.437,-27.554,-56.371,-27.537,-56.311,-27.439,-56.242,-27.367,-56.164,-27.321,-56.067,-27.308,-55.951,-27.326,-55.859,-27.362,-55.79,-27.416,-55.715,-27.415,-55.633,-27.357,-55.594,-27.288,-55.597,-27.208,-55.565,-27.15,-55.497,-27.115,-55.451,-27.068,-55.427,-27.009,-55.346,-26.973,-55.208,-26.96,-55.136,-26.931,-55.13,-26.886,-55.089,-26.845,-55.014,-26.807,-54.962,-26.759,-54.934,-26.703,-54.889,-26.667,-54.825,-26.652,-54.755,-26.533,-54.678,-26.309,-54.632,-26.006,-54.616,-25.576,-54.538,-25.576,-54.502,-25.608,-54.444,-25.625,-54.383,-25.589,-54.332,-25.572,-54.25,-25.57,-54.206,-25.53,-54.155,-25.523,-54.119,-25.545,-54.085,-25.572,-54.012,-25.578,-53.955,-25.648,-53.891,-25.669,-53.864,-25.749,-53.823,-25.96,-53.747,-26.084,-53.671,-26.225,-53.669,-26.288,-53.711,-26.352,-53.718,-26.443,-53.745,-26.667,-53.753,-26.749,-53.727,-26.805,-53.717,-26.883,-53.758,-26.978,-53.838,-27.121,-53.916,-27.16,-53.935,-27.161,-54.04,-27.244,-54.114,-27.275,-54.156,-27.254,-54.205,-27.29,-54.26,-27.382,-54.327,-27.424,-54.448,-27.446,-54.484,-27.457,-54.555,-27.454,-54.615,-27.477,-54.666,-27.527,-54.72,-27.545,-54.777,-27.533,-54.829,-27.551,-54.876,-27.599,-54.903,-27.652,-54.91,-27.709,-54.956,-27.747,-55.04,-27.768,-55.069,-27.796,-55.064,-27.836,-55.102,-27.867,-55.244,-27.899,-55.346,-27.956,-55.41,-28.038,-55.477,-28.089,-55.582,-28.121,-55.725,-28.204,-55.746,-28.255,-55.692,-28.303,-55.672,-28.345,-55.687,-28.382,-55.732,-28.387,-55.806,-28.36,-55.859,-28.354,-55.891,-28.37,-55.905,-28.4,-55.904,-28.443,-55.93,-28.473,-55.985,-28.489,-56.02,-28.525,-56.034,-28.581,-56.103,-28.652,-56.226,-28.737,-56.322,-28.852,-56.393,-28.997,-56.476,-29.092,-56.571,-29.138,-56.636,-29.203,-56.672,-29.287,-56.772,-29.418,-56.939,-29.595,-57.089,-29.716,-57.225,-29.782,-57.301,-29.857,-57.317,-29.939,-57.405,-30.034,-57.564,-30.14,-57.609,-30.188]],[[-68.653,-54.854,-68.648,-54.628,-68.64,-54.324,-68.638,-54.053,-68.637,-53.789,-68.635,-53.515,-68.633,-53.242,-68.632,-52.95,-68.63,-52.653,-68.571,-52.695,-68.339,-52.9,-68.278,-52.984,-68.24,-53.082,-68.333,-53.02,-68.431,-53.055,-68.479,-53.114,-68.521,-53.177,-68.521,-53.222,-68.489,-53.261,-68.393,-53.295,-68.161,-53.306,-68.144,-53.319,-68.008,-53.564,-67.94,-53.619,-67.861,-53.662,-67.678,-53.787,-67.503,-53.922,-67.294,-54.05,-67.069,-54.148,-66.865,-54.223,-66.67,-54.314,-66.462,-54.441,-66.236,-54.533,-65.993,-54.599,-65.747,-54.653,-65.369,-54.632,-65.252,-54.638,-65.179,-54.678,-65.252,-54.789,-65.346,-54.878,-65.471,-54.915,-65.603,-54.928,-65.723,-54.926,-65.842,-54.91,-65.954,-54.919,-66.061,-54.957,-66.172,-54.975,-66.287,-54.978,-66.399,-55.009,-66.511,-55.032,-66.628,-55.013,-66.93,-54.925,-67.127,-54.904,-67.793,-54.869,-68.007,-54.848,-68.22,-54.818,-68.332,-54.816,-68.491,-54.836,-68.619,-54.834,-68.653,-54.854]],[[-64.549,-54.716,-64.439,-54.739,-64.221,-54.722,-64.105,-54.722,-64.055,-54.73,-64.032,-54.742,-63.882,-54.723,-63.815,-54.725,-63.833,-54.768,-63.971,-54.811,-64.028,-54.793,-64.323,-54.796,-64.453,-54.84,-64.509,-54.84,-64.637,-54.903,-64.731,-54.863,-64.757,-54.827,-64.689,-54.775,-64.625,-54.774,-64.581,-54.753,-64.549,-54.716]],[[-61.876,-39.172,-61.866,-39.235,-61.918,-39.227,-62.042,-39.167,-62.083,-39.11,-62.093,-39.086,-61.967,-39.112,-61.907,-39.136,-61.876,-39.172]]]},{"n":"Antigua and Barbuda","c":"AG","r":6,"ml":5,"lx":-61.79,"ly":17.35,"b":[-61.887,16.997,-61.686,17.714],"p":[[[-61.716,17.037,-61.748,16.997,-61.86,17.013,-61.882,17.063,-61.887,17.098,-61.817,17.169,-61.739,17.138,-61.708,17.105,-61.686,17.098,-61.686,17.07,-61.695,17.049,-61.716,17.037]],[[-61.747,17.575,-61.762,17.549,-61.844,17.596,-61.869,17.685,-61.866,17.704,-61.852,17.714,-61.82,17.697,-61.777,17.69,-61.75,17.661,-61.747,17.575]]]},{"n":"Angola","c":"AO","r":3,"ml":3,"lx":17.98,"ly":-12.18,"b":[11.743,-18.02,24.047,-4.429],"p":[[[13.073,-4.635,13.057,-4.651,12.947,-4.695,12.83,-4.737,12.675,-4.905,12.596,-4.978,12.574,-4.997,12.503,-5.037,12.451,-5.071,12.453,-5.091,12.487,-5.113,12.522,-5.149,12.519,-5.425,12.504,-5.696,12.485,-5.719,12.386,-5.728,12.255,-5.746,12.214,-5.759,12.199,-5.732,12.155,-5.633,12.18,-5.539,12.207,-5.468,12.177,-5.325,12.111,-5.197,12.04,-5.035,12.018,-5.004,12.078,-4.952,12.167,-4.838,12.204,-4.779,12.308,-4.766,12.347,-4.724,12.374,-4.658,12.385,-4.619,12.501,-4.587,12.642,-4.531,12.719,-4.47,12.798,-4.431,12.848,-4.429,12.881,-4.445,12.971,-4.552,13.048,-4.619,13.073,-4.635]],[[23.967,-10.872,23.988,-11.003,24.01,-11.185,24.026,-11.316,24.041,-11.374,24.047,-11.405,24.029,-11.439,24.015,-11.518,23.987,-11.587,23.971,-11.636,23.984,-11.725,23.973,-11.853,23.962,-11.988,23.959,-12.118,23.996,-12.351,23.991,-12.422,23.945,-12.544,23.909,-12.636,23.887,-12.743,23.882,-12.799,23.968,-12.957,23.963,-12.988,23.897,-12.998,23.843,-13.001,23.636,-13.001,23.339,-13.001,23.042,-13.001,22.744,-13.001,22.471,-13.001,22.21,-13.001,21.979,-13.001,21.979,-13.157,21.979,-13.478,21.979,-13.799,21.979,-14.12,21.979,-14.441,21.979,-14.761,21.98,-15.082,21.98,-15.403,21.98,-15.724,21.98,-15.956,22.04,-16.263,22.151,-16.597,22.194,-16.628,22.305,-16.69,22.459,-16.815,22.546,-16.91,22.722,-17.075,22.956,-17.286,23.182,-17.474,23.381,-17.641,23.068,-17.699,22.624,-17.782,22.324,-17.837,21.961,-17.905,21.718,-17.948,21.417,-18.001,21.369,-18,21.288,-17.963,21.113,-17.956,20.908,-18.006,20.746,-18.02,20.625,-17.997,20.508,-17.953,20.393,-17.887,20.194,-17.864,19.912,-17.881,19.639,-17.869,19.377,-17.825,19.189,-17.808,19.076,-17.818,18.955,-17.804,18.826,-17.766,18.718,-17.703,18.588,-17.57,18.487,-17.443,18.46,-17.425,18.428,-17.405,18.396,-17.399,18.109,-17.396,17.835,-17.393,17.679,-17.393,17.296,-17.392,16.914,-17.391,16.531,-17.391,16.148,-17.39,15.766,-17.39,15.383,-17.389,15.001,-17.389,14.618,-17.388,14.415,-17.388,14.226,-17.398,14.017,-17.409,13.987,-17.404,13.938,-17.389,13.904,-17.361,13.792,-17.288,13.694,-17.233,13.562,-17.141,13.476,-17.04,13.404,-17.008,13.276,-16.99,13.179,-16.972,13.101,-16.968,12.963,-17.015,12.859,-17.063,12.785,-17.108,12.657,-17.161,12.548,-17.213,12.359,-17.206,12.318,-17.213,12.213,-17.21,12.114,-17.165,12.014,-17.169,11.903,-17.227,11.743,-17.249,11.78,-16.871,11.819,-16.704,11.82,-16.504,11.797,-15.986,11.769,-15.915,11.751,-15.832,11.85,-15.768,11.9,-15.72,11.968,-15.634,12.016,-15.514,12.073,-15.248,12.28,-14.637,12.379,-14.039,12.504,-13.755,12.55,-13.438,12.898,-13.028,12.983,-12.776,13.163,-12.652,13.417,-12.52,13.598,-12.286,13.686,-12.124,13.785,-11.813,13.784,-11.488,13.847,-11.054,13.834,-10.93,13.739,-10.757,13.721,-10.634,13.633,-10.512,13.539,-10.421,13.495,-10.257,13.332,-9.999,13.288,-9.827,13.209,-9.703,13.197,-9.551,13.156,-9.39,13.076,-9.23,12.999,-9.048,12.999,-8.991,13.047,-8.922,13.093,-8.9,13.077,-8.934,13.047,-8.975,13.054,-9.007,13.359,-8.687,13.378,-8.625,13.368,-8.555,13.366,-8.469,13.379,-8.37,13.091,-7.78,12.862,-7.232,12.823,-6.955,12.521,-6.59,12.402,-6.353,12.334,-6.187,12.283,-6.124,12.303,-6.093,12.38,-6.084,12.554,-6.046,12.791,-6.004,13.01,-5.908,13.068,-5.865,13.184,-5.856,13.303,-5.882,13.346,-5.863,13.371,-5.862,13.649,-5.862,13.765,-5.855,13.979,-5.857,14.114,-5.865,14.191,-5.876,14.399,-5.893,14.658,-5.889,14.749,-5.88,15.089,-5.875,15.425,-5.869,15.727,-5.864,16.06,-5.865,16.315,-5.866,16.431,-5.9,16.537,-5.966,16.585,-6.025,16.608,-6.052,16.64,-6.115,16.697,-6.164,16.718,-6.241,16.701,-6.346,16.709,-6.472,16.743,-6.618,16.813,-6.773,16.919,-6.934,16.966,-7.062,16.952,-7.157,16.985,-7.257,17.064,-7.363,17.122,-7.419,17.155,-7.461,17.245,-7.623,17.411,-7.882,17.536,-8.076,17.58,-8.099,17.643,-8.091,17.779,-8.071,17.913,-8.068,18.009,-8.108,18.047,-8.101,18.192,-8.024,18.335,-8,18.485,-7.969,18.563,-7.936,18.653,-7.936,18.898,-7.998,18.944,-8.001,19.143,-8.001,19.341,-7.967,19.37,-7.707,19.372,-7.655,19.419,-7.557,19.48,-7.472,19.487,-7.391,19.484,-7.279,19.528,-7.144,19.66,-7.037,19.875,-6.986,19.997,-6.976,20.19,-6.946,20.482,-6.916,20.59,-6.92,20.599,-6.935,20.537,-7.122,20.536,-7.183,20.558,-7.244,20.608,-7.278,20.911,-7.281,21.19,-7.285,21.511,-7.297,21.751,-7.305,21.782,-7.315,21.806,-7.329,21.842,-7.421,21.834,-7.602,21.78,-7.865,21.801,-8.112,21.896,-8.341,21.905,-8.693,21.872,-8.904,21.829,-9.168,21.813,-9.469,21.857,-9.594,21.949,-9.726,22.089,-9.863,22.198,-10.041,22.275,-10.259,22.302,-10.397,22.282,-10.453,22.283,-10.552,22.307,-10.691,22.28,-10.784,22.204,-10.829,22.178,-10.892,22.217,-11.013,22.226,-11.122,22.257,-11.164,22.279,-11.194,22.315,-11.199,22.393,-11.159,22.486,-11.087,22.561,-11.056,22.667,-11.06,22.815,-11.08,23.076,-11.088,23.157,-11.075,23.4,-10.976,23.464,-10.969,23.56,-10.979,23.696,-11.008,23.834,-11.014,23.901,-10.983,23.907,-10.943,23.929,-10.892,23.967,-10.872]]]},{"n":"Andorra","c":"AD","r":6,"ml":5,"lx":1.54,"ly":42.55,"b":[1.415,42.434,1.74,42.643],"p":[[[1.706,42.503,1.679,42.497,1.586,42.456,1.534,42.442,1.486,42.434,1.449,42.437,1.428,42.461,1.43,42.498,1.422,42.531,1.415,42.548,1.428,42.596,1.459,42.622,1.501,42.643,1.568,42.635,1.71,42.604,1.739,42.576,1.74,42.557,1.714,42.526,1.706,42.503]]]},{"n":"Algeria","c":"DZ","r":3,"ml":2.5,"lx":2.81,"ly":27.4,"b":[-8.683,18.987,11.968,37.092],"p":[[[8.577,36.937,8.598,36.884,8.601,36.834,8.507,36.788,8.444,36.761,8.37,36.633,8.231,36.545,8.208,36.519,8.209,36.495,8.303,36.456,8.334,36.418,8.349,36.368,8.307,36.189,8.28,36.051,8.246,35.871,8.247,35.802,8.283,35.719,8.318,35.655,8.329,35.582,8.316,35.403,8.36,35.3,8.394,35.204,8.312,35.085,8.277,34.979,8.255,34.829,8.246,34.734,8.193,34.646,8.123,34.564,8.046,34.513,7.949,34.469,7.838,34.41,7.749,34.254,7.554,34.125,7.514,34.081,7.496,33.977,7.5,33.832,7.534,33.718,7.628,33.549,7.709,33.362,7.731,33.269,7.763,33.233,7.877,33.172,8.076,33.089,8.113,33.055,8.211,32.927,8.304,32.696,8.333,32.544,8.515,32.422,8.683,32.31,8.844,32.212,9.019,32.105,9.044,32.072,9.102,31.846,9.16,31.621,9.224,31.374,9.288,31.125,9.363,30.833,9.406,30.667,9.458,30.465,9.519,30.229,9.421,30.179,9.31,30.115,9.391,29.994,9.546,29.796,9.64,29.636,9.673,29.567,9.746,29.369,9.805,29.177,9.821,29.115,9.843,28.967,9.816,28.56,9.858,28.043,9.916,27.786,9.825,27.553,9.748,27.331,9.753,27.219,9.795,27.045,9.837,26.916,9.894,26.848,9.883,26.631,9.859,26.552,9.685,26.438,9.491,26.334,9.438,26.246,9.422,26.147,9.448,26.067,9.581,25.89,9.781,25.624,10.001,25.332,10.019,25.259,10.028,25.051,10.12,24.79,10.219,24.676,10.256,24.591,10.326,24.53,10.396,24.486,10.439,24.48,10.686,24.551,11.108,24.434,11.508,24.314,11.537,24.291,11.624,24.14,11.767,23.893,11.873,23.695,11.968,23.518,11.45,23.213,10.932,22.907,10.414,22.602,9.896,22.297,9.379,21.991,8.861,21.686,8.343,21.381,7.825,21.076,7.482,20.873,7.263,20.694,6.989,20.471,6.731,20.248,6.527,20.073,6.263,19.846,6.131,19.732,5.837,19.479,5.748,19.434,5.359,19.36,5.001,19.291,4.671,19.228,4.446,19.185,4.228,19.143,3.91,19.084,3.683,19.042,3.439,18.996,3.401,18.988,3.356,18.987,3.323,18.988,3.256,19.013,3.174,19.073,3.12,19.103,3.106,19.15,3.138,19.212,3.177,19.268,3.192,19.312,3.22,19.345,3.254,19.373,3.256,19.411,3.227,19.474,3.202,19.56,3.203,19.718,3.203,19.771,3.204,19.79,3.13,19.85,2.992,19.917,2.866,19.956,2.808,19.969,2.668,19.993,2.474,20.035,2.406,20.064,2.281,20.21,2.219,20.248,1.929,20.273,1.832,20.297,1.753,20.332,1.685,20.378,1.647,20.459,1.636,20.524,1.611,20.556,1.29,20.714,1.209,20.767,1.166,20.817,1.164,20.891,1.173,20.982,1.159,21.063,1.146,21.102,0.999,21.198,0.672,21.412,0.344,21.626,0.017,21.84,-0.311,22.054,-0.638,22.268,-0.965,22.482,-1.293,22.697,-1.62,22.911,-1.948,23.125,-2.275,23.339,-2.603,23.553,-2.93,23.767,-3.258,23.981,-3.585,24.195,-3.913,24.409,-4.24,24.624,-4.517,24.804,-4.823,24.996,-5.05,25.135,-5.275,25.275,-5.517,25.424,-5.675,25.516,-5.863,25.627,-6.051,25.738,-6.239,25.848,-6.427,25.959,-6.615,26.069,-6.803,26.18,-6.991,26.291,-7.179,26.401,-7.367,26.512,-7.555,26.622,-7.743,26.733,-7.931,26.844,-8.119,26.954,-8.307,27.065,-8.495,27.175,-8.683,27.286,-8.683,27.49,-8.683,27.656,-8.683,27.9,-8.683,28.112,-8.683,28.324,-8.683,28.469,-8.683,28.621,-8.678,28.689,-8.66,28.719,-8.558,28.768,-8.399,28.88,-8.34,28.93,-8.265,28.981,-7.999,29.132,-7.944,29.175,-7.685,29.35,-7.625,29.375,-7.486,29.392,-7.428,29.425,-7.35,29.495,-7.235,29.575,-7.16,29.613,-7.142,29.62,-7.095,29.625,-6.856,29.602,-6.755,29.584,-6.635,29.569,-6.598,29.579,-6.566,29.604,-6.521,29.66,-6.511,29.726,-6.508,29.784,-6.501,29.809,-6.48,29.82,-6.428,29.816,-6.358,29.808,-6.215,29.811,-6.167,29.819,-6.004,29.831,-5.775,29.869,-5.593,29.918,-5.449,29.957,-5.294,30.059,-5.18,30.166,-5.062,30.326,-4.968,30.465,-4.779,30.552,-4.62,30.605,-4.529,30.626,-4.323,30.699,-4.149,30.81,-3.985,30.914,-3.86,30.927,-3.702,30.944,-3.667,30.964,-3.627,31.001,-3.625,31.066,-3.673,31.111,-3.73,31.135,-3.771,31.162,-3.812,31.167,-3.833,31.198,-3.821,31.255,-3.815,31.309,-3.789,31.362,-3.796,31.437,-3.837,31.512,-3.85,31.566,-3.847,31.62,-3.827,31.662,-3.768,31.69,-3.7,31.7,-3.605,31.687,-3.44,31.705,-3.017,31.834,-2.988,31.874,-2.961,31.964,-2.931,32.043,-2.887,32.069,-2.863,32.075,-2.723,32.096,-2.523,32.126,-2.448,32.13,-2.231,32.121,-2.073,32.115,-1.817,32.105,-1.635,32.1,-1.477,32.095,-1.275,32.089,-1.226,32.107,-1.226,32.165,-1.262,32.271,-1.24,32.338,-1.163,32.399,-1.066,32.468,-1.111,32.552,-1.188,32.608,-1.296,32.676,-1.352,32.703,-1.45,32.785,-1.51,32.878,-1.551,33.074,-1.625,33.183,-1.679,33.319,-1.631,33.567,-1.703,33.717,-1.714,33.782,-1.715,33.858,-1.693,33.99,-1.707,34.176,-1.792,34.368,-1.752,34.433,-1.733,34.467,-1.739,34.496,-1.817,34.557,-1.85,34.607,-1.832,34.655,-1.792,34.723,-1.796,34.752,-1.921,34.836,-2.132,34.971,-2.191,35.03,-2.22,35.104,-2.018,35.085,-1.913,35.094,-1.674,35.183,-1.484,35.303,-1.336,35.364,-1.205,35.496,-1.088,35.579,-0.917,35.668,-0.426,35.862,-0.351,35.863,-0.189,35.819,-0.048,35.833,0.048,35.901,0.152,36.063,0.312,36.162,0.515,36.262,0.791,36.357,0.972,36.444,1.257,36.52,1.975,36.568,2.343,36.61,2.593,36.601,2.846,36.739,2.973,36.784,3.521,36.795,3.779,36.896,4.758,36.896,4.878,36.862,4.995,36.808,5.196,36.677,5.295,36.648,5.425,36.675,5.725,36.8,6.065,36.864,6.249,36.938,6.328,37.046,6.487,37.086,6.576,37.003,6.928,36.919,7.143,36.943,7.238,36.969,7.204,37.092,7.432,37.059,7.608,37,7.792,36.88,7.91,36.856,8.127,36.91,8.577,36.937]]]},{"n":"Albania","c":"AL","r":6,"ml":5,"lx":20.11,"ly":40.65,"b":[19.281,39.654,21.031,42.648],"p":[[[19.342,41.869,19.346,41.919,19.361,41.998,19.352,42.024,19.361,42.069,19.331,42.129,19.281,42.173,19.329,42.249,19.4,42.342,19.465,42.415,19.545,42.492,19.597,42.565,19.654,42.629,19.703,42.648,19.728,42.635,19.741,42.607,19.738,42.525,19.754,42.497,19.788,42.476,19.86,42.486,19.939,42.507,20.046,42.55,20.064,42.547,20.104,42.525,20.186,42.426,20.241,42.339,20.348,42.309,20.408,42.275,20.485,42.223,20.523,42.171,20.575,42.013,20.581,41.917,20.566,41.874,20.553,41.862,20.505,41.706,20.517,41.627,20.516,41.575,20.476,41.554,20.449,41.521,20.492,41.391,20.487,41.336,20.489,41.273,20.568,41.128,20.614,41.083,20.656,41.062,20.709,40.928,20.741,40.905,20.87,40.918,20.933,40.903,20.959,40.872,20.964,40.85,20.956,40.775,20.988,40.718,21.031,40.659,21.031,40.622,21.002,40.563,20.95,40.494,20.882,40.468,20.806,40.445,20.77,40.392,20.752,40.335,20.718,40.293,20.697,40.246,20.665,40.152,20.657,40.117,20.606,40.083,20.527,40.069,20.456,40.066,20.408,40.049,20.384,40.017,20.338,39.991,20.311,39.979,20.311,39.951,20.344,39.891,20.382,39.842,20.382,39.803,20.364,39.792,20.306,39.797,20.294,39.782,20.288,39.739,20.272,39.701,20.248,39.678,20.207,39.654,20.131,39.662,20.06,39.699,20.023,39.711,20.001,39.709,19.996,39.801,19.965,39.872,19.852,40.044,19.485,40.21,19.398,40.285,19.36,40.348,19.322,40.407,19.359,40.409,19.395,40.394,19.441,40.376,19.459,40.405,19.439,40.47,19.345,40.622,19.338,40.664,19.384,40.791,19.461,40.933,19.456,41.106,19.48,41.236,19.453,41.321,19.441,41.425,19.497,41.563,19.546,41.597,19.576,41.64,19.578,41.788,19.468,41.856,19.342,41.869]]]},{"n":"Afghanistan","c":"AF","r":3,"ml":3,"lx":66.5,"ly":34.16,"b":[60.486,29.392,74.891,38.456],"p":[[[66.522,37.348,66.828,37.371,67.069,37.335,67.196,37.235,67.32,37.21,67.442,37.258,67.517,37.267,67.546,37.236,67.607,37.223,67.7,37.227,67.753,37.2,67.759,37.172,67.766,37.14,67.834,37.064,67.958,36.972,68.068,36.95,68.212,37.022,68.261,37.013,68.285,37.036,68.3,37.088,68.387,37.138,68.546,37.183,68.637,37.224,68.669,37.258,68.723,37.268,68.782,37.258,68.824,37.271,68.838,37.303,68.855,37.317,68.885,37.328,68.912,37.334,68.96,37.325,69.05,37.267,69.18,37.158,69.265,37.108,69.304,37.117,69.354,37.15,69.414,37.208,69.43,37.291,69.399,37.399,69.42,37.487,69.492,37.553,69.626,37.594,69.821,37.61,69.941,37.6,69.985,37.566,70.045,37.547,70.12,37.544,70.189,37.582,70.251,37.664,70.255,37.765,70.199,37.886,70.215,37.924,70.239,37.941,70.313,37.985,70.418,38.075,70.519,38.192,70.616,38.334,70.736,38.423,70.879,38.456,71.052,38.418,71.256,38.307,71.333,38.17,71.283,38.008,71.279,37.918,71.32,37.902,71.39,37.906,71.488,37.932,71.552,37.933,71.582,37.91,71.58,37.864,71.546,37.796,71.505,37.603,71.48,37.436,71.455,37.272,71.433,37.128,71.472,37.015,71.531,36.845,71.597,36.733,71.666,36.697,71.734,36.684,71.802,36.694,71.942,36.766,72.154,36.901,72.359,36.983,72.657,37.029,72.757,37.173,72.896,37.268,73.211,37.408,73.383,37.462,73.481,37.472,73.605,37.446,73.633,37.437,73.657,37.43,73.721,37.419,73.734,37.376,73.717,37.329,73.649,37.291,73.628,37.262,73.654,37.239,73.75,37.232,73.949,37.283,74.078,37.316,74.167,37.329,74.204,37.372,74.26,37.415,74.349,37.419,74.445,37.396,74.524,37.382,74.659,37.394,74.731,37.357,74.83,37.286,74.875,37.242,74.891,37.232,74.84,37.225,74.767,37.249,74.739,37.286,74.727,37.291,74.669,37.267,74.559,37.237,74.372,37.158,74.376,37.137,74.498,37.057,74.526,37.031,74.541,37.022,74.431,36.984,74.195,36.897,74.039,36.826,74.002,36.823,73.908,36.853,73.769,36.888,73.732,36.888,73.411,36.882,73.117,36.869,72.994,36.852,72.766,36.835,72.623,36.83,72.531,36.802,72.431,36.766,72.327,36.742,72.25,36.735,72.157,36.701,72.096,36.634,71.921,36.534,71.822,36.486,71.773,36.432,71.716,36.427,71.621,36.436,71.546,36.378,71.463,36.293,71.313,36.171,71.233,36.122,71.185,36.042,71.22,36.001,71.343,35.939,71.398,35.88,71.428,35.834,71.484,35.715,71.519,35.598,71.572,35.547,71.587,35.461,71.601,35.408,71.572,35.37,71.546,35.329,71.546,35.289,71.577,35.248,71.605,35.212,71.621,35.183,71.602,35.151,71.546,35.101,71.517,35.051,71.455,34.967,71.358,34.91,71.294,34.868,71.226,34.78,71.113,34.682,71.066,34.6,71.016,34.555,70.966,34.53,70.979,34.486,71.023,34.431,71.096,34.369,71.092,34.273,71.089,34.204,71.091,34.12,71.052,34.05,70.848,33.982,70.654,33.952,70.416,33.95,70.326,33.961,70.254,33.976,69.995,34.052,69.89,34.007,69.868,33.898,70.057,33.72,70.134,33.621,70.22,33.455,70.284,33.369,70.261,33.289,70.09,33.198,69.92,33.113,69.704,33.095,69.568,33.064,69.502,33.02,69.453,32.833,69.405,32.764,69.405,32.683,69.359,32.59,69.29,32.531,69.241,32.434,69.257,32.249,69.279,31.937,69.187,31.838,69.083,31.738,68.973,31.667,68.869,31.634,68.782,31.646,68.714,31.708,68.673,31.76,68.598,31.803,68.521,31.794,68.443,31.754,68.32,31.768,68.214,31.807,68.161,31.803,68.13,31.763,68.017,31.678,67.74,31.548,67.627,31.539,67.578,31.506,67.598,31.453,67.647,31.41,67.733,31.379,67.738,31.344,67.662,31.313,67.596,31.278,67.453,31.235,67.287,31.218,67.116,31.243,67.028,31.3,66.924,31.306,66.829,31.264,66.731,31.195,66.624,31.046,66.596,31.02,66.567,30.997,66.497,30.965,66.397,30.912,66.347,30.803,66.287,30.608,66.301,30.503,66.305,30.321,66.282,30.193,66.238,30.11,66.247,30.044,66.313,29.969,66.287,29.92,66.231,29.866,66.177,29.836,65.962,29.779,65.666,29.701,65.471,29.652,65.18,29.578,65.096,29.559,64.919,29.553,64.827,29.564,64.704,29.567,64.521,29.565,64.394,29.544,64.266,29.507,64.172,29.46,64.118,29.414,64.099,29.392,63.971,29.43,63.568,29.498,62.477,29.408,62.373,29.425,62.001,29.53,61.521,29.666,61.224,29.749,60.843,29.859,61.104,30.128,61.332,30.364,61.559,30.599,61.784,30.832,61.811,30.913,61.814,31.073,61.755,31.285,61.66,31.382,61.346,31.422,61.111,31.451,60.854,31.483,60.821,31.495,60.792,31.661,60.804,31.734,60.788,31.877,60.79,31.987,60.827,32.168,60.829,32.249,60.71,32.6,60.645,32.794,60.577,32.995,60.562,33.059,60.561,33.138,60.718,33.324,60.767,33.364,60.859,33.456,60.917,33.505,60.907,33.539,60.806,33.559,60.655,33.56,60.574,33.588,60.511,33.639,60.486,33.712,60.527,33.842,60.486,34.095,60.57,34.22,60.643,34.307,60.889,34.319,60.804,34.418,60.763,34.475,60.736,34.492,60.726,34.518,60.739,34.545,60.802,34.555,60.845,34.588,60.915,34.634,60.951,34.654,60.958,34.71,60.991,34.75,61.04,34.799,61.08,34.856,61.07,34.922,61.107,35.001,61.123,35.051,61.15,35.094,61.126,35.157,61.107,35.209,61.1,35.272,61.14,35.289,61.189,35.312,61.199,35.362,61.226,35.424,61.246,35.474,61.279,35.514,61.282,35.553,61.262,35.62,61.345,35.629,61.378,35.593,61.422,35.546,61.543,35.458,61.621,35.432,61.72,35.419,61.841,35.431,61.938,35.448,61.984,35.444,62.09,35.38,62.213,35.29,62.253,35.25,62.271,35.189,62.308,35.171,62.387,35.231,62.463,35.251,62.533,35.24,62.611,35.233,62.688,35.255,62.723,35.271,62.858,35.35,62.98,35.409,63.057,35.446,63.084,35.568,63.119,35.638,63.17,35.678,63.151,35.728,63.13,35.767,63.109,35.819,63.13,35.846,63.179,35.858,63.302,35.858,63.517,35.913,63.697,35.968,63.863,36.012,63.938,36.02,64.01,36.012,64.042,36.025,64.051,36.068,64.092,36.113,64.184,36.149,64.358,36.226,64.511,36.341,64.566,36.428,64.603,36.555,64.674,36.75,64.753,36.965,64.782,37.059,64.816,37.132,64.952,37.194,65.09,37.238,65.304,37.247,65.555,37.251,65.608,37.368,65.641,37.468,65.683,37.519,65.744,37.561,65.765,37.569,65.901,37.508,66.108,37.415,66.35,37.368,66.472,37.345,66.522,37.348]]]},{"n":"Siachen Glacier","c":"","r":5,"ml":6.5,"lx":77.13,"ly":35.34,"b":[76.767,35.11,77.799,35.662],"p":[[[77.049,35.11,77.004,35.196,76.979,35.246,76.928,35.347,76.882,35.436,76.813,35.572,76.767,35.662,76.879,35.613,77.09,35.552,77.295,35.508,77.446,35.476,77.52,35.473,77.573,35.472,77.724,35.481,77.799,35.496,77.697,35.443,77.572,35.379,77.423,35.303,77.293,35.236,77.169,35.172,77.049,35.11]]]},{"n":"Antarctica","c":"AQ","r":4,"ml":4,"lx":35.89,"ly":-79.84,"b":[-180,-89.999,180,-60.521],"p":[[[-45.718,-60.521,-45.5,-60.546,-45.386,-60.583,-45.357,-60.624,-45.228,-60.64,-45.211,-60.648,-45.186,-60.672,-45.173,-60.699,-45.174,-60.733,-45.398,-60.65,-45.709,-60.645,-45.78,-60.586,-45.937,-60.62,-45.955,-60.597,-45.956,-60.568,-45.935,-60.527,-45.834,-60.543,-45.718,-60.521]],[[-90.536,-68.798,-90.547,-68.798,-90.567,-68.798,-90.581,-68.799,-90.595,-68.8,-90.609,-68.802,-90.617,-68.803,-90.62,-68.803,-90.624,-68.801,-90.628,-68.797,-90.638,-68.788,-90.647,-68.78,-90.652,-68.771,-90.648,-68.76,-90.637,-68.754,-90.637,-68.747,-90.637,-68.738,-90.631,-68.73,-90.628,-68.721,-90.629,-68.716,-90.626,-68.714,-90.617,-68.712,-90.6,-68.712,-90.59,-68.713,-90.571,-68.714,-90.562,-68.715,-90.548,-68.718,-90.535,-68.728,-90.525,-68.738,-90.525,-68.743,-90.523,-68.749,-90.521,-68.756,-90.519,-68.764,-90.515,-68.771,-90.518,-68.783,-90.526,-68.795,-90.533,-68.797,-90.536,-68.798]],[[-57.021,-63.373,-56.927,-63.506,-56.782,-63.572,-56.835,-63.631,-56.974,-63.625,-57.119,-63.638,-57.152,-63.572,-57.098,-63.524,-57.152,-63.479,-57.284,-63.491,-57.461,-63.514,-57.581,-63.547,-57.737,-63.617,-57.857,-63.657,-58.263,-63.763,-58.532,-63.915,-58.723,-64.077,-58.839,-64.187,-59.005,-64.195,-59.047,-64.234,-58.977,-64.266,-58.923,-64.279,-58.799,-64.293,-58.819,-64.339,-58.905,-64.352,-58.895,-64.389,-58.806,-64.445,-58.786,-64.524,-58.892,-64.537,-59.051,-64.451,-59.229,-64.444,-59.37,-64.404,-59.461,-64.346,-59.547,-64.359,-59.612,-64.44,-59.573,-64.531,-59.646,-64.584,-59.734,-64.559,-59.765,-64.451,-59.85,-64.434,-59.963,-64.431,-60.242,-64.547,-60.341,-64.551,-60.394,-64.609,-60.556,-64.677,-60.66,-64.729,-60.915,-64.907,-61.06,-64.981,-61.332,-65.024,-61.439,-65.018,-61.503,-65,-61.603,-64.988,-61.703,-64.987,-61.736,-65.033,-61.577,-65.186,-61.663,-65.239,-61.856,-65.235,-61.948,-65.192,-62.025,-65.233,-62.085,-65.273,-62.145,-65.332,-62.054,-65.457,-61.903,-65.513,-61.796,-65.523,-61.756,-65.569,-61.991,-65.589,-62.151,-65.699,-62.222,-65.775,-62.305,-65.84,-62.293,-65.916,-62.169,-66.031,-62.005,-66.113,-61.839,-66.12,-61.625,-66.095,-61.575,-66.071,-61.359,-66.059,-61.266,-65.98,-61.198,-65.975,-61.138,-65.989,-61.039,-65.992,-60.988,-65.94,-60.913,-65.921,-60.813,-65.934,-60.618,-65.933,-60.565,-65.979,-60.625,-66.032,-60.744,-66.105,-60.856,-66.065,-60.956,-66.072,-61.009,-66.111,-60.903,-66.191,-60.942,-66.264,-61.028,-66.337,-61.134,-66.29,-61.15,-66.212,-61.293,-66.165,-61.432,-66.145,-61.526,-66.226,-61.676,-66.25,-61.696,-66.343,-61.756,-66.429,-61.842,-66.403,-61.875,-66.296,-62.117,-66.209,-62.241,-66.197,-62.494,-66.219,-62.583,-66.217,-62.682,-66.237,-62.755,-66.31,-62.65,-66.364,-62.615,-66.436,-62.618,-66.49,-62.638,-66.511,-62.655,-66.556,-62.543,-66.621,-62.537,-66.707,-62.629,-66.706,-62.705,-66.68,-62.997,-66.453,-63.18,-66.353,-63.258,-66.264,-63.449,-66.244,-63.587,-66.242,-63.753,-66.278,-63.688,-66.32,-63.654,-66.383,-63.756,-66.409,-63.88,-66.506,-63.964,-66.589,-64.015,-66.607,-64.078,-66.654,-63.809,-66.761,-63.769,-66.803,-63.755,-66.873,-63.84,-66.912,-64.043,-66.927,-64.401,-66.853,-64.554,-66.852,-64.607,-66.8,-64.686,-66.806,-64.735,-66.894,-64.793,-66.972,-64.878,-67.025,-64.854,-67.105,-64.785,-67.124,-64.839,-67.156,-64.951,-67.183,-65.027,-67.214,-64.858,-67.243,-64.826,-67.269,-64.819,-67.307,-65.08,-67.335,-65.249,-67.342,-65.35,-67.311,-65.443,-67.326,-65.503,-67.377,-65.523,-67.445,-65.504,-67.528,-65.471,-67.588,-65.447,-67.61,-65.418,-67.66,-65.574,-67.788,-65.589,-67.816,-65.6,-67.876,-65.528,-67.93,-65.469,-68.009,-65.551,-68.048,-65.64,-68.131,-65.546,-68.147,-65.387,-68.15,-65.218,-68.14,-64.959,-68.068,-64.885,-68.056,-64.853,-68.083,-64.829,-68.127,-64.896,-68.168,-65.365,-68.287,-65.452,-68.337,-65.331,-68.364,-65.09,-68.37,-64.996,-68.408,-65.055,-68.449,-65.14,-68.489,-65.242,-68.583,-65.158,-68.618,-64.898,-68.673,-64.429,-68.746,-64.078,-68.771,-64.157,-68.687,-64.169,-68.583,-63.924,-68.498,-63.796,-68.47,-63.217,-68.419,-63.057,-68.421,-62.933,-68.443,-62.98,-68.486,-63.115,-68.471,-63.348,-68.499,-63.707,-68.592,-63.773,-68.632,-63.747,-68.705,-63.443,-68.764,-63.344,-68.81,-63.478,-68.951,-63.456,-69.042,-63.301,-69.141,-63.094,-69.253,-62.994,-69.329,-62.84,-69.372,-62.587,-69.477,-62.451,-69.584,-62.407,-69.827,-62.202,-70.028,-61.961,-70.12,-61.935,-70.2,-62.014,-70.279,-62.218,-70.233,-62.331,-70.279,-62.378,-70.365,-62.232,-70.424,-62.001,-70.497,-61.505,-70.491,-61.491,-70.57,-61.605,-70.617,-61.696,-70.676,-61.809,-70.709,-61.994,-70.729,-62.04,-70.801,-61.961,-70.901,-61.702,-70.857,-61.513,-70.851,-61.313,-70.868,-61.252,-71.002,-61.017,-71.167,-60.962,-71.245,-61.003,-71.319,-61.148,-71.342,-61.237,-71.401,-61.369,-71.452,-61.516,-71.479,-61.79,-71.616,-61.91,-71.631,-61.959,-71.658,-61.725,-71.673,-61.563,-71.675,-61.214,-71.564,-61.081,-71.589,-60.995,-71.661,-60.949,-71.747,-61.035,-71.82,-61.645,-71.863,-61.939,-71.904,-62.257,-72.018,-61.894,-72.071,-61.628,-72.053,-61.493,-72.073,-61.31,-72.113,-61.107,-72.092,-60.952,-72.05,-60.833,-72.052,-60.719,-72.073,-60.704,-72.144,-60.691,-72.27,-60.665,-72.362,-60.73,-72.426,-61.048,-72.471,-61.28,-72.468,-61.286,-72.601,-60.939,-72.7,-60.724,-72.647,-60.532,-72.673,-60.532,-72.832,-60.385,-73.007,-60.254,-73.017,-60.149,-72.938,-60.01,-72.938,-59.957,-73.031,-60.016,-73.189,-60.122,-73.275,-60.404,-73.24,-60.561,-73.211,-60.687,-73.271,-60.896,-73.32,-61.081,-73.328,-61.242,-73.25,-61.428,-73.191,-61.726,-73.161,-62.008,-73.148,-61.915,-73.216,-61.788,-73.255,-61.738,-73.375,-61.637,-73.5,-61.405,-73.467,-61.08,-73.539,-60.879,-73.612,-60.79,-73.712,-60.903,-73.871,-61.088,-73.929,-61.203,-73.957,-61.404,-73.896,-61.545,-73.896,-61.692,-73.924,-61.742,-73.996,-61.838,-74.032,-61.319,-74.036,-61.161,-74.056,-61.042,-74.122,-61.227,-74.208,-61.571,-74.195,-61.718,-74.228,-61.843,-74.29,-61.332,-74.329,-61.121,-74.307,-60.784,-74.241,-60.704,-74.307,-60.838,-74.373,-61.011,-74.478,-61.37,-74.512,-61.64,-74.514,-61.995,-74.476,-62.089,-74.453,-62.235,-74.441,-62.226,-74.506,-62.133,-74.55,-61.894,-74.713,-61.855,-74.777,-61.928,-74.863,-62.138,-74.926,-62.372,-74.952,-62.567,-74.896,-62.708,-74.737,-62.887,-74.691,-63.072,-74.678,-63.178,-74.684,-63.168,-74.765,-63.125,-74.85,-63.198,-74.909,-63.357,-74.878,-63.559,-74.906,-63.751,-74.952,-63.925,-75.004,-63.571,-75.03,-63.337,-75.035,-63.173,-75.115,-63.231,-75.154,-63.551,-75.171,-63.858,-75.206,-64.28,-75.293,-63.972,-75.329,-63.678,-75.328,-63.475,-75.336,-63.304,-75.352,-63.258,-75.399,-63.363,-75.451,-64.053,-75.58,-64.778,-75.738,-65.044,-75.787,-65.322,-75.815,-65.966,-75.952,-66.37,-76.013,-67.518,-76.11,-69.304,-76.351,-69.915,-76.522,-70.096,-76.654,-70.21,-76.674,-70.551,-76.718,-70.895,-76.739,-71.799,-76.753,-72.722,-76.689,-73.472,-76.675,-73.88,-76.697,-75.268,-76.581,-75.444,-76.587,-75.659,-76.608,-75.831,-76.608,-75.963,-76.592,-76.244,-76.585,-77.19,-76.63,-77.287,-76.702,-77.168,-76.834,-76.824,-76.993,-76.249,-77.275,-75.937,-77.334,-75.748,-77.398,-75.387,-77.474,-74.581,-77.478,-73.478,-77.536,-72.852,-77.59,-72.875,-77.694,-73.252,-77.894,-73.485,-77.971,-74.042,-78.109,-74.812,-78.178,-75.398,-78.158,-76.438,-78.044,-77.742,-77.94,-79.679,-77.843,-80.104,-77.797,-80.602,-77.752,-80.889,-77.798,-81.103,-77.842,-81.581,-77.846,-81.441,-77.886,-79.51,-78.154,-77.858,-78.351,-77.665,-78.401,-77.433,-78.435,-77.452,-78.56,-77.545,-78.66,-77.869,-78.746,-78.712,-78.752,-79.767,-78.821,-80.292,-78.823,-80.816,-78.754,-81.929,-78.559,-82.608,-78.412,-83.083,-78.247,-83.412,-78.115,-83.779,-77.984,-83.752,-78.066,-83.688,-78.148,-83.508,-78.248,-83.246,-78.357,-83.227,-78.402,-83.355,-78.408,-83.544,-78.355,-83.706,-78.404,-83.763,-78.461,-83.697,-78.537,-83.595,-78.611,-83.26,-78.774,-82.971,-78.817,-82.589,-78.916,-81.661,-79.1,-81.503,-79.163,-81.222,-79.298,-81.163,-79.4,-80.892,-79.502,-80.705,-79.517,-80.535,-79.513,-80.479,-79.426,-80.489,-79.321,-80.416,-79.295,-80.151,-79.268,-79.456,-79.304,-76.499,-79.326,-76.218,-79.387,-76.105,-79.465,-76.032,-79.627,-76.344,-79.821,-76.558,-79.904,-76.904,-79.955,-77.222,-79.994,-77.702,-80.01,-78.692,-79.995,-79.66,-79.997,-78.907,-80.09,-78.176,-80.167,-77.16,-80.153,-76.757,-80.131,-76.407,-80.095,-76.26,-80.16,-75.986,-80.295,-75.822,-80.338,-75.709,-80.383,-75.555,-80.531,-75.495,-80.617,-75.345,-80.719,-75.237,-80.803,-75.076,-80.86,-74.807,-80.887,-74.511,-80.838,-73.938,-80.816,-73.383,-80.894,-73.029,-80.917,-72.553,-80.853,-72.174,-80.764,-71.38,-80.682,-71.231,-80.647,-71.018,-80.619,-70.688,-80.626,-70.56,-80.647,-70.392,-80.735,-70.239,-80.857,-70.012,-80.918,-69.772,-80.962,-69.634,-80.966,-69.182,-81.005,-68.59,-80.968,-68.327,-81.004,-68.285,-81.074,-68.144,-81.13,-67.965,-81.148,-65.574,-81.461,-64.75,-81.522,-63.478,-81.553,-62.49,-81.557,-62.354,-81.577,-62.165,-81.636,-62.542,-81.678,-62.946,-81.684,-63.554,-81.667,-63.769,-81.676,-64.233,-81.66,-64.476,-81.672,-64.696,-81.652,-65.022,-81.696,-65.62,-81.729,-65.487,-81.775,-65.264,-81.786,-64.811,-81.803,-64.19,-81.795,-64.137,-81.869,-64.706,-81.887,-65.916,-81.902,-66.042,-81.914,-66.134,-81.953,-65.953,-81.971,-65.844,-81.993,-65.787,-82.046,-65.913,-82.183,-65.714,-82.279,-65.572,-82.294,-65.424,-82.28,-65.17,-82.318,-64.92,-82.371,-64.397,-82.374,-63.773,-82.304,-63.466,-82.307,-62.645,-82.263,-61.902,-82.271,-60.859,-82.187,-60.687,-82.189,-60.528,-82.2,-60.817,-82.276,-62.095,-82.467,-62.553,-82.503,-62.736,-82.527,-62.631,-82.621,-62.466,-82.718,-62.129,-82.822,-61.917,-82.977,-61.709,-83.01,-61.313,-82.939,-61.218,-82.992,-61.2,-83.098,-61.303,-83.184,-61.436,-83.232,-61.531,-83.279,-61.59,-83.341,-61.425,-83.396,-60.983,-83.428,-60.397,-83.441,-59.854,-83.442,-59.516,-83.458,-58.29,-83.121,-57.798,-82.959,-57.557,-82.89,-57.354,-82.84,-56.318,-82.633,-56.075,-82.57,-55.801,-82.478,-55.295,-82.465,-54.601,-82.316,-53.986,-82.201,-53.74,-82.178,-53.558,-82.169,-53.339,-82.145,-52.799,-82.154,-52.415,-82.135,-51.731,-82.062,-51.21,-82.015,-50.653,-81.975,-50.029,-81.968,-48.361,-81.892,-47.887,-81.925,-47.36,-82.004,-47.02,-82.003,-46.567,-81.979,-46.258,-81.947,-46.119,-82.04,-46.046,-82.159,-46.199,-82.271,-46.448,-82.34,-46.517,-82.455,-46.175,-82.512,-45.789,-82.495,-45.044,-82.438,-44.455,-82.366,-44.292,-82.318,-44.064,-82.331,-43.669,-82.27,-43.18,-82.017,-42.565,-81.762,-42.046,-81.598,-41.712,-81.408,-41.434,-81.298,-41.126,-81.215,-40.915,-81.172,-40.441,-81.165,-39.762,-81.032,-38.772,-80.882,-38.011,-80.954,-37.209,-81.064,-36.812,-80.975,-36.5,-80.96,-36.234,-80.921,-35.966,-80.891,-35.776,-80.813,-35.521,-80.746,-35.327,-80.651,-34.35,-80.603,-33.329,-80.54,-33.191,-80.519,-33.057,-80.532,-32.706,-80.514,-32.256,-80.461,-31.634,-80.445,-31.312,-80.45,-31.015,-80.308,-30.425,-80.28,-29.797,-80.223,-29.531,-80.182,-29.329,-80.172,-24.24,-80.062,-24.02,-80.009,-23.574,-79.965,-23.407,-79.859,-24.088,-79.815,-24.3,-79.771,-24.534,-79.758,-24.67,-79.775,-25.259,-79.762,-29.949,-79.599,-30.049,-79.585,-30.211,-79.485,-30.178,-79.304,-30.316,-79.163,-30.645,-79.124,-30.985,-79.128,-31.413,-79.145,-32.542,-79.222,-32.994,-79.229,-34.197,-79.11,-34.995,-78.978,-35.516,-78.933,-35.89,-78.844,-36.239,-78.774,-36.266,-78.616,-36.181,-78.468,-35.509,-78.041,-35.088,-77.837,-34.808,-77.821,-34.551,-77.729,-34.29,-77.522,-34.076,-77.425,-33.591,-77.311,-33.377,-77.282,-32.614,-77.141,-32.405,-77.136,-32.063,-77.16,-31.676,-77.033,-30.489,-76.762,-30.222,-76.66,-29.892,-76.598,-28.934,-76.37,-28.079,-76.258,-27.653,-76.226,-27.135,-76.157,-26.56,-76.055,-26.059,-75.957,-24.27,-75.767,-23.197,-75.718,-22.465,-75.661,-21.948,-75.694,-21.434,-75.683,-20.989,-75.634,-20.783,-75.594,-20.487,-75.492,-19.493,-75.54,-18.851,-75.47,-18.585,-75.463,-18.305,-75.431,-18.415,-75.396,-18.517,-75.39,-18.617,-75.342,-18.749,-75.242,-18.617,-75.115,-18.517,-75.052,-18.221,-74.975,-18.068,-74.863,-17.923,-74.699,-17.436,-74.379,-17.299,-74.334,-16.989,-74.32,-16.727,-74.328,-16.43,-74.324,-15.673,-74.407,-15.531,-74.376,-15.29,-74.281,-15.089,-74.163,-14.659,-73.989,-14.574,-73.937,-14.611,-73.852,-15.26,-73.889,-15.749,-73.946,-16.22,-73.916,-16.282,-73.867,-16.181,-73.83,-16.003,-73.816,-15.936,-73.758,-16.097,-73.709,-16.388,-73.681,-16.519,-73.644,-16.507,-73.556,-16.435,-73.426,-16.279,-73.388,-16.149,-73.334,-15.803,-73.152,-15.596,-73.097,-15.007,-73.047,-14.321,-73.123,-14.165,-73.102,-14,-73.001,-14.168,-72.843,-14.298,-72.785,-14.298,-72.733,-13.939,-72.756,-13.603,-72.792,-13.209,-72.785,-12.747,-72.629,-12.095,-72.498,-11.777,-72.444,-11.497,-72.413,-11.346,-72.282,-11.121,-72.032,-10.958,-71.902,-10.961,-71.822,-11.009,-71.758,-11.179,-71.777,-11.333,-71.786,-11.697,-71.719,-12.148,-71.614,-12.285,-71.495,-12.351,-71.39,-12.208,-71.332,-12.074,-71.297,-11.926,-71.289,-11.663,-71.331,-11.328,-71.44,-11.16,-71.481,-10.97,-71.56,-10.825,-71.553,-10.659,-71.443,-10.52,-71.296,-10.407,-71.25,-10.231,-71.201,-10.033,-71.131,-10.122,-71.061,-10.331,-71.024,-10.36,-70.982,-10.271,-70.936,-10.099,-70.926,-9.888,-71.027,-9.599,-71.095,-9.402,-71.118,-9.231,-71.174,-8.966,-71.361,-8.646,-71.673,-8.498,-71.675,-8.216,-71.647,-7.916,-71.635,-7.714,-71.546,-7.669,-71.324,-7.59,-71.224,-7.618,-71.121,-7.757,-71.017,-7.873,-70.94,-7.855,-70.885,-7.753,-70.843,-7.62,-70.829,-7.388,-70.787,-7.032,-70.835,-6.838,-70.845,-6.548,-70.817,-6.245,-70.756,-5.936,-70.713,-5.695,-70.745,-5.588,-70.857,-5.709,-70.968,-5.904,-71.052,-6.08,-71.154,-6.127,-71.266,-6.117,-71.326,-5.95,-71.342,-4.45,-71.328,-4.253,-71.338,-3.995,-71.339,-3.713,-71.375,-3.24,-71.36,-2.812,-71.321,-2.61,-71.321,-2.261,-71.357,-2.015,-71.433,-1.501,-71.412,-1.354,-71.387,-1.216,-71.284,-1.068,-71.266,-0.896,-71.349,-0.84,-71.54,-0.76,-71.63,-0.543,-71.713,-0.327,-71.642,-0.185,-71.559,0.154,-71.398,0.538,-71.274,0.835,-71.202,1.552,-71.08,1.909,-71.004,2.609,-70.9,3.507,-70.844,5.113,-70.656,5.644,-70.636,6.508,-70.586,6.951,-70.535,7.401,-70.494,7.677,-70.356,8.307,-70.462,8.523,-70.474,8.817,-70.391,9.142,-70.184,9.613,-70.269,9.886,-70.403,10.218,-70.508,10.969,-70.688,11.204,-70.729,11.701,-70.767,11.834,-70.737,12.068,-70.617,12.309,-70.443,12.462,-70.37,12.682,-70.309,12.929,-70.213,12.865,-70.162,12.723,-70.144,12.595,-70.117,12.626,-70.066,13.066,-70.054,13.298,-70.23,13.533,-70.287,13.823,-70.343,14.492,-70.3,15.064,-70.295,15.563,-70.331,15.807,-70.324,16.025,-70.193,16.381,-70.145,16.585,-70.204,16.709,-70.397,17.167,-70.451,18.125,-70.54,18.232,-70.518,18.351,-70.416,18.433,-70.29,18.627,-70.269,18.877,-70.201,19.009,-70.212,19.196,-70.293,19.132,-70.492,19.027,-70.674,19.153,-70.821,19.265,-70.902,19.409,-70.917,19.652,-70.921,19.944,-70.91,20.128,-70.918,21.071,-70.843,21.186,-70.681,21.337,-70.495,21.705,-70.258,21.849,-70.277,21.962,-70.3,22.216,-70.417,22.366,-70.475,22.396,-70.561,22.234,-70.643,22.278,-70.696,22.445,-70.74,22.979,-70.81,23.15,-70.796,23.407,-70.723,23.665,-70.575,23.804,-70.405,24.024,-70.413,24.236,-70.449,24.386,-70.537,24.386,-70.704,24.588,-70.82,24.757,-70.892,25.187,-70.971,25.65,-70.991,25.974,-71.037,26.499,-71.02,26.754,-70.967,26.918,-70.954,27.207,-70.911,27.509,-70.813,27.698,-70.772,28.386,-70.682,28.912,-70.583,29.464,-70.406,30.003,-70.3,30.834,-70.246,31.063,-70.225,31.379,-70.226,32.16,-70.1,32.457,-70.026,32.621,-70.001,32.81,-69.909,32.912,-69.734,32.989,-69.624,32.976,-69.517,32.903,-69.379,32.738,-69.255,32.568,-69.074,32.642,-68.869,32.776,-68.783,33.121,-68.689,33.466,-68.671,33.854,-68.683,34.193,-68.702,34.219,-68.791,34.074,-68.885,33.885,-68.979,33.772,-69.02,33.814,-69.099,34.059,-69.111,34.596,-69.095,34.75,-69.168,35.131,-69.487,35.225,-69.637,35.357,-69.681,35.568,-69.66,36.018,-69.662,36.331,-69.639,36.586,-69.638,36.719,-69.652,36.856,-69.726,37.115,-69.81,37.375,-69.748,37.56,-69.718,37.787,-69.726,38.144,-69.824,38.499,-70.056,38.886,-70.172,38.912,-70.098,38.859,-70.006,39.019,-69.924,39.211,-69.786,39.487,-69.608,39.705,-69.426,39.762,-69.173,39.864,-68.967,40.042,-68.868,40.216,-68.805,40.484,-68.739,40.817,-68.724,41.133,-68.575,41.356,-68.515,41.825,-68.433,42.409,-68.352,42.82,-68.123,42.961,-68.095,43.171,-68.06,43.554,-68.046,44.178,-67.972,44.373,-67.961,44.7,-67.904,44.99,-67.769,45.197,-67.731,45.569,-67.736,45.888,-67.66,46.154,-67.657,46.399,-67.618,46.437,-67.533,46.32,-67.477,46.317,-67.402,46.454,-67.304,46.56,-67.268,46.884,-67.275,47.154,-67.357,47.352,-67.362,47.403,-67.409,47.231,-67.468,47.117,-67.573,47.314,-67.665,47.49,-67.728,47.704,-67.716,47.959,-67.66,48.21,-67.699,48.322,-67.785,48.322,-67.917,48.375,-67.988,48.551,-67.926,48.648,-67.794,48.62,-67.625,48.63,-67.521,49.053,-67.352,49.219,-67.227,48.923,-67.2,48.714,-67.217,48.598,-67.171,48.465,-67.043,48.83,-66.938,49.247,-66.942,49.489,-67.031,50.006,-67.175,50.293,-67.172,50.553,-67.194,50.606,-67.15,50.509,-66.939,50.521,-66.82,50.306,-66.753,50.244,-66.603,50.332,-66.445,50.588,-66.356,50.937,-66.315,51.688,-66.072,51.885,-66.02,52.378,-65.969,52.955,-65.946,53.672,-65.859,54.948,-65.916,55.29,-65.954,55.504,-66.003,55.71,-66.08,55.974,-66.209,56.362,-66.373,56.859,-66.423,57,-66.475,57.185,-66.613,56.987,-66.704,56.824,-66.713,56.51,-66.659,56.295,-66.603,56.146,-66.626,56.292,-66.721,56.453,-66.78,56.48,-66.859,56.391,-66.974,55.803,-67.199,56.155,-67.265,56.366,-67.212,56.562,-67.116,56.76,-67.073,56.892,-67.056,57.361,-67.053,57.627,-67.014,57.828,-67.041,58.027,-67.103,58.317,-67.163,58.737,-67.23,59.251,-67.485,59.65,-67.459,59.868,-67.403,60.482,-67.385,61.012,-67.5,61.309,-67.54,62.174,-67.575,62.688,-67.648,63.018,-67.562,63.238,-67.527,63.699,-67.508,63.931,-67.526,64.574,-67.62,65.708,-67.716,66.488,-67.766,67.175,-67.768,67.502,-67.81,68.099,-67.854,68.328,-67.89,68.9,-67.862,69.167,-67.825,69.416,-67.743,69.559,-67.763,69.656,-67.865,69.603,-68.041,69.704,-68.161,69.789,-68.279,69.907,-68.379,69.982,-68.464,69.928,-68.535,69.762,-68.599,69.534,-68.737,69.547,-68.857,69.646,-68.932,69.531,-69.024,69.615,-69.154,69.629,-69.232,69.549,-69.294,69.372,-69.331,69.065,-69.337,68.906,-69.373,68.88,-69.47,68.959,-69.54,69.136,-69.578,69.188,-69.655,69.162,-69.77,69.083,-69.867,68.921,-69.912,68.744,-69.921,68.415,-69.902,68.178,-69.837,68.027,-69.894,67.917,-69.953,67.575,-70.088,67.417,-70.177,67.268,-70.273,67.659,-70.326,67.941,-70.423,68.559,-70.412,68.757,-70.37,69.021,-70.325,69.162,-70.334,69.25,-70.431,69.197,-70.585,69.188,-70.705,68.873,-71.035,68.768,-71.091,68.624,-71.181,68.448,-71.252,68.311,-71.287,68.037,-71.391,67.873,-71.58,67.694,-71.737,67.432,-72.003,67.281,-72.291,67.215,-72.461,67.113,-72.641,66.892,-72.949,66.746,-73,66.498,-73.125,66.569,-73.209,66.765,-73.217,67.003,-73.236,67.322,-73.3,67.749,-73.168,67.971,-73.086,68.016,-72.918,67.971,-72.751,68.107,-72.651,68.42,-72.515,69.157,-72.419,69.309,-72.409,69.555,-72.375,69.77,-72.254,69.962,-72.133,70.294,-72.055,70.573,-71.931,70.616,-71.842,70.732,-71.822,71.079,-71.737,71.168,-71.672,71.277,-71.624,71.349,-71.514,71.379,-71.309,71.465,-71.155,71.634,-70.949,71.771,-70.801,71.905,-70.707,72.263,-70.657,72.418,-70.599,72.622,-70.472,72.76,-70.396,72.744,-70.239,72.822,-70.096,73.041,-70.01,73.325,-69.849,73.676,-69.826,73.942,-69.743,74.227,-69.8,74.571,-69.88,75.148,-69.855,75.424,-69.893,75.636,-69.849,75.821,-69.725,75.891,-69.576,76.112,-69.487,76.36,-69.49,76.77,-69.34,77.192,-69.206,77.541,-69.174,77.817,-69.069,78.015,-68.892,78.229,-68.756,78.489,-68.626,78.563,-68.394,78.726,-68.278,79.035,-68.175,79.288,-68.119,80.363,-67.947,81.187,-67.831,82.017,-67.69,82.273,-67.692,82.607,-67.613,83.158,-67.611,83.304,-67.603,83.494,-67.441,83.904,-67.292,84.161,-67.244,84.485,-67.114,84.748,-67.102,85.117,-67.126,85.429,-67.161,85.711,-67.161,86.118,-67.055,86.75,-67.037,86.947,-66.986,87.085,-66.94,87.98,-66.788,88.314,-66.817,88.789,-66.792,89.077,-66.799,89.352,-66.818,89.698,-66.823,90.293,-66.77,90.547,-66.734,91.022,-66.603,91.546,-66.572,91.777,-66.537,92.073,-66.508,92.312,-66.559,92.486,-66.604,92.592,-66.614,92.731,-66.624,93.075,-66.571,93.358,-66.585,93.722,-66.643,93.964,-66.69,94.089,-66.689,94.313,-66.647,94.587,-66.544,94.84,-66.501,95.084,-66.527,95.248,-66.571,95.541,-66.631,95.991,-66.621,96.424,-66.6,96.789,-66.551,97.101,-66.499,97.388,-66.579,97.72,-66.607,98.258,-66.467,98.462,-66.499,98.603,-66.535,98.72,-66.553,98.859,-66.671,99.37,-66.648,99.824,-66.549,100.212,-66.474,100.591,-66.425,100.889,-66.358,101.327,-66.1,101.321,-66.021,101.381,-65.973,101.474,-65.951,102.174,-65.954,102.392,-65.933,102.674,-65.865,103.167,-65.917,103.639,-65.999,103.763,-65.99,103.951,-65.988,104.289,-66.039,104.667,-66.137,105,-66.164,106.387,-66.411,107.171,-66.47,107.566,-66.552,107.667,-66.58,107.785,-66.664,107.992,-66.672,108.158,-66.639,108.376,-66.766,108.91,-66.862,109.463,-66.909,109.824,-66.834,110.437,-66.621,110.622,-66.524,110.587,-66.312,110.907,-66.077,111.453,-65.961,112.13,-65.9,112.548,-65.848,113.099,-65.8,113.368,-65.849,113.502,-65.886,113.71,-65.93,113.954,-66.06,114.337,-66.36,114.619,-66.468,114.87,-66.477,115.082,-66.493,115.31,-66.561,115.635,-66.771,115.442,-66.958,115.274,-67.028,114.571,-67.108,114.26,-67.172,113.991,-67.212,113.913,-67.368,114.027,-67.441,114.319,-67.406,114.658,-67.388,114.926,-67.357,115.172,-67.308,115.384,-67.238,115.885,-67.202,116.215,-67.143,116.509,-67.108,116.713,-67.047,116.924,-67.055,117.132,-67.114,117.298,-67.109,117.745,-67.129,117.952,-67.085,118.139,-67.082,118.326,-67.115,118.519,-67.161,118.714,-67.172,118.965,-67.145,119.318,-67.071,119.768,-66.992,120.187,-66.966,120.289,-66.967,120.375,-66.984,119.954,-67.076,119.281,-67.199,118.922,-67.32,119.133,-67.371,120.4,-67.236,120.979,-67.136,121.488,-67.091,121.613,-67.057,122.033,-66.902,122.183,-66.859,122.633,-66.805,123.222,-66.745,123.667,-66.677,123.969,-66.608,124.196,-66.601,124.371,-66.652,124.598,-66.708,124.822,-66.695,125.095,-66.641,125.286,-66.516,125.398,-66.424,125.603,-66.393,125.866,-66.364,126.077,-66.396,126.424,-66.462,126.665,-66.498,126.874,-66.759,127.365,-66.99,127.541,-67.051,127.968,-67.028,128.431,-67.119,128.628,-67.107,128.816,-67.08,128.982,-67.098,129.237,-67.042,129.5,-66.753,129.741,-66.469,129.976,-66.345,130.121,-66.292,130.301,-66.268,130.579,-66.209,130.952,-66.191,131.232,-66.216,131.831,-66.236,132.32,-66.165,132.874,-66.178,133.148,-66.095,133.445,-66.081,133.843,-66.154,133.958,-66.204,134.179,-66.277,134.232,-66.348,134.289,-66.477,134.398,-66.48,134.77,-66.353,134.971,-66.33,135.352,-66.127,135.555,-66.18,136.009,-66.267,136.194,-66.292,136.553,-66.439,136.74,-66.408,136.889,-66.34,137.336,-66.346,137.754,-66.406,137.926,-66.457,138.14,-66.544,138.271,-66.564,138.376,-66.54,139.242,-66.574,139.613,-66.638,139.9,-66.715,140.902,-66.752,141.286,-66.832,141.517,-66.794,141.973,-66.807,142.159,-66.874,142.327,-66.948,142.688,-67.013,142.888,-67,143.169,-66.949,143.448,-66.877,143.73,-66.877,143.863,-66.939,143.911,-67.091,144.118,-67.088,144.348,-67.018,144.551,-67.036,144.621,-67.141,144.515,-67.283,144.26,-67.479,144.154,-67.644,143.942,-67.794,143.977,-67.865,144.189,-67.9,144.404,-67.794,144.879,-67.721,145.128,-67.626,145.556,-67.591,145.975,-67.624,146.276,-67.751,146.828,-67.965,146.852,-68.041,146.897,-68.12,146.878,-68.191,146.798,-68.274,147.094,-68.369,147.354,-68.384,147.569,-68.375,148.456,-68.467,148.881,-68.431,149.263,-68.431,149.717,-68.418,150.066,-68.42,150.342,-68.436,150.672,-68.403,150.936,-68.358,151.068,-68.385,151.121,-68.623,151.139,-68.764,151.289,-68.817,151.448,-68.764,151.562,-68.694,152.265,-68.726,152.546,-68.73,152.814,-68.768,153.082,-68.857,153.34,-68.818,153.496,-68.764,153.705,-68.729,153.767,-68.641,153.792,-68.493,153.785,-68.35,153.908,-68.323,154.032,-68.35,154.2,-68.418,154.576,-68.634,154.866,-68.774,154.988,-68.841,155.164,-68.895,155.52,-69.024,156.011,-69.078,156.489,-69.183,157.046,-69.176,157.481,-69.309,157.776,-69.205,157.933,-69.181,158.158,-69.209,158.433,-69.299,158.647,-69.32,159.386,-69.468,159.784,-69.522,159.931,-69.631,160.126,-69.734,160.126,-69.84,160.21,-69.975,160.652,-70.081,160.827,-70.182,161.037,-70.317,161.425,-70.827,161.625,-70.916,161.916,-70.907,162.189,-71.04,162.278,-71.022,162.287,-70.969,162.04,-70.625,162.022,-70.44,162.216,-70.334,162.675,-70.305,163.026,-70.501,163.349,-70.621,163.567,-70.642,163.998,-70.637,164.403,-70.51,164.716,-70.557,165.209,-70.571,165.854,-70.645,166.132,-70.633,166.627,-70.664,167.229,-70.771,167.569,-70.81,167.64,-70.854,167.799,-70.925,167.878,-71.013,167.966,-71.092,168.173,-71.183,168.383,-71.197,168.798,-71.275,169.664,-71.511,169.977,-71.581,170.162,-71.63,170.25,-71.569,170.277,-71.444,170.436,-71.419,170.603,-71.604,170.78,-71.745,170.859,-71.869,170.675,-71.969,170.409,-71.948,170.224,-71.948,170.03,-72.116,169.954,-72.403,170.127,-72.398,170.259,-72.371,170.286,-72.477,170.206,-72.565,170.048,-72.601,169.775,-72.534,169.44,-72.487,169.072,-72.469,168.719,-72.384,168.576,-72.379,168.428,-72.383,168.622,-72.473,168.82,-72.552,169.27,-72.621,169.829,-72.729,169.845,-72.795,169.712,-72.877,169.545,-73.05,169.033,-73.2,168.736,-73.091,168.381,-73.066,168.204,-73.13,167.853,-73.122,167.156,-73.147,166.883,-73.011,166.453,-72.936,166.467,-72.997,166.834,-73.224,167.226,-73.276,167.616,-73.337,167.709,-73.394,167.534,-73.447,167.296,-73.44,166.996,-73.544,166.429,-73.527,166.159,-73.534,166.001,-73.577,165.86,-73.593,165.971,-73.631,166.106,-73.735,165.913,-73.823,165.734,-73.867,165.549,-73.846,165.347,-73.879,165.25,-73.782,165.245,-73.571,165.129,-73.383,165.005,-73.375,164.813,-73.397,164.75,-73.559,164.888,-73.838,164.98,-73.926,164.906,-74.003,164.776,-74.029,165.037,-74.263,165.263,-74.426,165.4,-74.479,165.409,-74.559,165.303,-74.594,165.001,-74.563,164.853,-74.578,164.689,-74.568,164.411,-74.533,164.174,-74.523,163.936,-74.567,163.735,-74.564,163.557,-74.417,163.398,-74.382,163.266,-74.426,163.167,-74.602,162.961,-74.656,162.752,-74.736,162.604,-74.823,162.534,-75.167,162.41,-75.238,162.226,-75.235,162.088,-75.262,161.91,-75.234,161.68,-75.218,160.911,-75.335,161.033,-75.396,161.227,-75.386,161.904,-75.404,162.19,-75.467,162.239,-75.622,162.352,-75.687,162.578,-75.758,162.754,-75.793,162.816,-75.846,162.745,-75.952,162.648,-76.049,162.437,-76.155,162.498,-76.208,162.728,-76.225,162.825,-76.464,162.675,-76.569,162.745,-76.658,162.763,-76.746,162.61,-76.829,162.489,-76.869,162.45,-76.956,162.679,-77.007,162.85,-77.024,163.087,-77.032,163.25,-77.126,163.458,-77.269,163.608,-77.388,163.619,-77.582,163.766,-77.7,164.045,-77.775,164.036,-77.853,164.232,-77.877,164.421,-77.883,164.492,-77.954,164.43,-78.042,164.108,-78.147,163.978,-78.224,164.297,-78.236,164.628,-78.316,165.051,-78.226,165.274,-78.129,165.418,-78.042,165.524,-78.064,165.534,-78.154,165.663,-78.306,166.209,-78.452,166.51,-78.497,166.801,-78.522,167.058,-78.518,167.13,-78.606,167.049,-78.686,166.85,-78.68,166.525,-78.695,166.287,-78.628,166.117,-78.571,164.635,-78.603,164.301,-78.63,163.902,-78.717,163.503,-78.759,162.895,-78.845,162.639,-78.898,161.975,-78.694,161.757,-78.545,161.669,-78.536,161.51,-78.571,161.502,-78.677,161.813,-78.907,161.951,-78.968,161.951,-79.03,161.864,-79.061,161.737,-79.06,161.546,-79.015,161.191,-78.979,160.874,-79.05,160.483,-79.201,160.567,-79.302,160.67,-79.359,160.646,-79.427,160.209,-79.554,160.068,-79.561,159.976,-79.586,160.084,-79.633,160.323,-79.636,160.346,-79.692,160.265,-79.737,159.872,-79.79,159.896,-79.859,160.111,-79.892,160.387,-79.879,160.558,-79.93,160.559,-80.011,160.382,-80.054,160.179,-80.088,158.767,-80.293,158.56,-80.349,158.574,-80.423,159.065,-80.443,160.542,-80.425,160.637,-80.45,160.603,-80.508,160.521,-80.583,160.601,-80.637,160.823,-80.674,160.83,-80.73,160.72,-80.753,160.503,-80.779,160.26,-80.787,160.276,-80.847,160.607,-80.901,160.717,-80.907,160.728,-81.113,160.717,-81.2,160.54,-81.242,160.479,-81.27,160.47,-81.341,160.908,-81.39,161.582,-81.61,161.73,-81.61,161.996,-81.653,162.425,-81.765,162.577,-81.832,162.821,-81.866,162.854,-81.921,163.004,-81.969,163.602,-82.121,163.68,-82.187,162.427,-82.314,161.167,-82.408,161.283,-82.49,162.644,-82.482,163.012,-82.535,163.175,-82.519,163.269,-82.463,164.001,-82.397,164.747,-82.354,164.98,-82.385,165.982,-82.63,166.446,-82.722,166.742,-82.757,166.957,-82.765,167.116,-82.801,167.271,-82.879,167.233,-82.952,167.297,-82.986,167.404,-82.999,167.602,-83.047,167.828,-83.031,168.092,-82.975,168.276,-82.987,168.607,-83.065,168.482,-83.127,168.408,-83.155,168.32,-83.211,168.24,-83.23,167.973,-83.243,167.825,-83.243,167.674,-83.231,167.658,-83.272,167.843,-83.316,168.11,-83.362,169.838,-83.399,170.332,-83.479,170.817,-83.436,171.036,-83.448,171.221,-83.475,171.289,-83.555,171.537,-83.581,171.917,-83.644,172.45,-83.675,172.874,-83.673,173.397,-83.759,173.662,-83.761,173.822,-83.81,175.011,-83.839,175.187,-83.877,175.323,-83.94,175.606,-83.968,175.911,-83.973,177.581,-84.075,178.209,-84.13,178.353,-84.127,178.496,-84.136,178.944,-84.181,179.403,-84.206,179.62,-84.268,180,-84.352,180,-89.999,178.594,-89.999,177.188,-89.999,175.781,-89.999,174.375,-89.999,172.969,-89.999,171.563,-89.999,170.156,-89.999,168.75,-89.999,167.344,-89.999,165.938,-89.999,164.531,-89.999,163.125,-89.999,161.719,-89.999,160.313,-89.999,158.906,-89.999,157.5,-89.999,156.094,-89.999,154.688,-89.999,153.281,-89.999,151.875,-89.999,150.469,-89.999,149.063,-89.999,147.656,-89.999,146.25,-89.999,144.844,-89.999,143.438,-89.999,142.031,-89.999,140.625,-89.999,139.219,-89.999,137.813,-89.999,136.406,-89.999,135,-89.999,133.594,-89.999,132.188,-89.999,130.781,-89.999,129.375,-89.999,127.969,-89.999,126.562,-89.999,125.156,-89.999,123.75,-89.999,122.344,-89.999,120.938,-89.999,119.531,-89.999,118.125,-89.999,116.719,-89.999,115.313,-89.999,113.906,-89.999,112.5,-89.999,111.094,-89.999,109.688,-89.999,108.281,-89.999,106.875,-89.999,105.469,-89.999,104.063,-89.999,102.656,-89.999,101.25,-89.999,99.844,-89.999,98.438,-89.999,97.031,-89.999,95.625,-89.999,94.219,-89.999,92.813,-89.999,91.406,-89.999,90,-89.999,88.594,-89.999,87.188,-89.999,85.781,-89.999,84.375,-89.999,82.969,-89.999,81.563,-89.999,80.156,-89.999,78.75,-89.999,77.344,-89.999,75.938,-89.999,74.531,-89.999,73.125,-89.999,71.719,-89.999,70.313,-89.999,68.906,-89.999,67.5,-89.999,66.094,-89.999,64.688,-89.999,63.281,-89.999,61.875,-89.999,60.469,-89.999,59.063,-89.999,57.656,-89.999,56.25,-89.999,54.844,-89.999,53.438,-89.999,52.031,-89.999,50.625,-89.999,49.219,-89.999,47.813,-89.999,46.406,-89.999,45,-89.999,43.594,-89.999,42.188,-89.999,40.781,-89.999,39.375,-89.999,37.969,-89.999,36.563,-89.999,35.156,-89.999,33.75,-89.999,32.344,-89.999,30.938,-89.999,29.531,-89.999,28.125,-89.999,26.719,-89.999,25.313,-89.999,23.906,-89.999,22.5,-89.999,21.094,-89.999,19.688,-89.999,18.281,-89.999,16.875,-89.999,15.469,-89.999,14.063,-89.999,12.656,-89.999,11.25,-89.999,9.844,-89.999,8.438,-89.999,7.031,-89.999,5.625,-89.999,4.219,-89.999,2.813,-89.999,1.406,-89.999,0,-89.999,-1.406,-89.999,-2.812,-89.999,-4.219,-89.999,-5.625,-89.999,-7.031,-89.999,-8.438,-89.999,-9.844,-89.999,-11.25,-89.999,-12.656,-89.999,-14.062,-89.999,-15.469,-89.999,-16.875,-89.999,-18.281,-89.999,-19.687,-89.999,-21.094,-89.999,-22.5,-89.999,-23.906,-89.999,-25.313,-89.999,-26.719,-89.999,-28.125,-89.999,-29.531,-89.999,-30.937,-89.999,-32.344,-89.999,-33.75,-89.999,-35.156,-89.999,-36.562,-89.999,-37.969,-89.999,-39.375,-89.999,-40.781,-89.999,-42.187,-89.999,-43.594,-89.999,-45,-89.999,-46.406,-89.999,-47.812,-89.999,-49.219,-89.999,-50.625,-89.999,-52.031,-89.999,-53.438,-89.999,-54.844,-89.999,-56.25,-89.999,-57.656,-89.999,-59.062,-89.999,-60.469,-89.999,-61.875,-89.999,-63.281,-89.999,-64.687,-89.999,-66.094,-89.999,-67.5,-89.999,-68.906,-89.999,-70.312,-89.999,-71.719,-89.999,-73.125,-89.999,-74.531,-89.999,-75.937,-89.999,-77.344,-89.999,-78.75,-89.999,-80.156,-89.999,-81.562,-89.999,-82.969,-89.999,-84.375,-89.999,-85.781,-89.999,-87.187,-89.999,-88.594,-89.999,-90,-89.999,-91.406,-89.999,-92.812,-89.999,-94.219,-89.999,-95.625,-89.999,-97.031,-89.999,-98.437,-89.999,-99.844,-89.999,-101.25,-89.999,-102.656,-89.999,-104.062,-89.999,-105.469,-89.999,-106.875,-89.999,-108.281,-89.999,-109.687,-89.999,-111.094,-89.999,-112.5,-89.999,-113.906,-89.999,-115.312,-89.999,-116.719,-89.999,-118.125,-89.999,-119.531,-89.999,-120.937,-89.999,-122.344,-89.999,-123.75,-89.999,-125.156,-89.999,-126.562,-89.999,-127.969,-89.999,-129.375,-89.999,-130.781,-89.999,-132.187,-89.999,-133.594,-89.999,-135,-89.999,-136.406,-89.999,-137.812,-89.999,-139.219,-89.999,-140.625,-89.999,-142.031,-89.999,-143.437,-89.999,-144.844,-89.999,-146.25,-89.999,-147.656,-89.999,-149.062,-89.999,-150.469,-89.999,-151.875,-89.999,-153.281,-89.999,-154.688,-89.999,-156.094,-89.999,-157.5,-89.999,-158.906,-89.999,-160.312,-89.999,-161.719,-89.999,-163.125,-89.999,-164.531,-89.999,-165.938,-89.999,-167.344,-89.999,-168.75,-89.999,-170.156,-89.999,-171.562,-89.999,-172.969,-89.999,-174.375,-89.999,-175.781,-89.999,-177.187,-89.999,-178.594,-89.999,-180,-89.999,-180,-89.583,-180,-89.293,-180,-88.587,-180,-87.881,-180,-87.175,-180,-86.469,-180,-85.763,-180,-85.058,-180,-84.352,-178.39,-84.337,-178.069,-84.352,-177.73,-84.395,-176.986,-84.399,-176.289,-84.418,-176.107,-84.475,-175.875,-84.51,-175.381,-84.48,-174.987,-84.465,-174.663,-84.463,-171.704,-84.542,-168.668,-84.684,-168.049,-84.729,-167.492,-84.834,-166.911,-84.819,-163.464,-84.901,-162.933,-84.901,-160.821,-84.987,-157.127,-85.186,-156.81,-85.192,-156.459,-85.186,-156.643,-85.079,-156.988,-84.982,-157.454,-84.912,-157.15,-84.891,-156.49,-84.889,-156.621,-84.84,-156.986,-84.811,-158.303,-84.778,-163.569,-84.529,-163.685,-84.513,-163.759,-84.493,-163.897,-84.47,-164.114,-84.445,-164.917,-84.431,-165.135,-84.41,-165.24,-84.381,-165.185,-84.37,-165.125,-84.375,-163.899,-84.353,-163.765,-84.324,-163.758,-84.305,-163.821,-84.291,-164.032,-84.274,-164.528,-84.191,-164.685,-84.155,-164.603,-84.097,-164.503,-84.072,-164.124,-84.054,-164.011,-84.016,-164.083,-83.946,-164.951,-83.806,-165.536,-83.757,-165.922,-83.79,-166.649,-83.792,-167.553,-83.811,-167.801,-83.791,-168.053,-83.735,-168.347,-83.637,-168.497,-83.611,-168.785,-83.529,-169.168,-83.45,-171.188,-83.256,-171.539,-83.204,-174.066,-82.9,-174.172,-82.848,-174.236,-82.793,-173.071,-82.916,-172.852,-82.917,-172.593,-82.884,-172.392,-82.893,-172.124,-82.862,-171.821,-82.847,-171.031,-82.943,-169.441,-83.096,-169.016,-83.15,-168.79,-83.188,-168.604,-83.202,-168.418,-83.229,-168.191,-83.213,-168.055,-83.227,-167.724,-83.217,-166.217,-83.201,-165.619,-83.216,-164.916,-83.29,-164.644,-83.412,-164.446,-83.468,-164.058,-83.425,-163.733,-83.373,-163.111,-83.329,-162.912,-83.347,-162.574,-83.411,-162.197,-83.519,-160.595,-83.49,-159.924,-83.495,-159.444,-83.543,-157.699,-83.381,-157.428,-83.346,-157.028,-83.234,-157.356,-83.198,-157.589,-83.187,-157.679,-83.129,-157.522,-83.107,-157.018,-83.075,-156.037,-83.027,-155.459,-82.981,-155.15,-82.858,-153.822,-82.669,-153.399,-82.586,-153.01,-82.45,-153.883,-82.177,-154.717,-81.941,-154.451,-81.868,-154.188,-81.811,-154.061,-81.765,-153.957,-81.7,-154.232,-81.623,-154.485,-81.566,-154.908,-81.51,-156.493,-81.377,-157.033,-81.319,-156.815,-81.231,-156.528,-81.162,-155.921,-81.133,-152.035,-81.029,-148.123,-80.901,-148.023,-80.836,-148.543,-80.76,-148.984,-80.742,-149.147,-80.719,-149.207,-80.67,-149.214,-80.604,-149.264,-80.593,-149.429,-80.586,-150.133,-80.51,-150.282,-80.48,-150.516,-80.409,-150.575,-80.354,-150.435,-80.211,-150.221,-80.15,-149.845,-80.118,-149.578,-80.106,-148.766,-80.108,-148.448,-80.091,-148.317,-80.071,-148.34,-80.003,-148.43,-79.971,-148.433,-79.929,-148.296,-79.907,-148.129,-79.908,-148.083,-79.857,-148.177,-79.776,-148.417,-79.731,-149.051,-79.657,-150.491,-79.546,-151.048,-79.46,-151.368,-79.393,-151.636,-79.318,-151.904,-79.281,-152.091,-79.242,-152.053,-79.193,-152.138,-79.116,-152.244,-79.103,-152.701,-79.135,-153.518,-79.117,-154.518,-79.047,-155.21,-78.965,-156.115,-78.745,-156.469,-78.635,-156.208,-78.559,-155.92,-78.51,-154.716,-78.398,-154.538,-78.359,-154.293,-78.259,-154.695,-78.217,-155.037,-78.221,-155.342,-78.192,-156.569,-78.186,-157.267,-78.2,-157.848,-78.074,-158.286,-77.951,-158.407,-77.888,-158.5,-77.778,-158.351,-77.615,-158.23,-77.498,-158.246,-77.354,-158.214,-77.157,-158.003,-77.091,-157.842,-77.079,-157.465,-77.231,-157.139,-77.242,-156.668,-77.213,-156.368,-77.135,-156.211,-77.106,-155.92,-77.098,-155.359,-77.133,-155.102,-77.12,-154.815,-77.127,-153.91,-77.227,-153.713,-77.274,-153.606,-77.31,-153.573,-77.363,-153.461,-77.416,-153.077,-77.442,-151.998,-77.413,-151.719,-77.426,-150.956,-77.574,-150.306,-77.731,-150.084,-77.771,-149.718,-77.797,-149.588,-77.774,-149.474,-77.715,-149.126,-77.643,-148.34,-77.551,-148.156,-77.462,-148.26,-77.413,-148.559,-77.361,-148.744,-77.343,-148.844,-77.284,-148.839,-77.202,-148.777,-77.125,-148.572,-77.105,-148.196,-77.211,-147.73,-77.31,-147.566,-77.325,-147.442,-77.321,-147.207,-77.286,-146.928,-77.26,-146.391,-77.472,-146.074,-77.487,-145.677,-77.488,-145.601,-77.455,-145.65,-77.398,-145.714,-77.338,-145.794,-77.33,-145.808,-77.273,-145.635,-77.221,-145.516,-77.199,-145.563,-77.162,-145.753,-77.103,-145.864,-77.094,-145.967,-77.069,-145.934,-77.029,-145.806,-77.012,-145.629,-76.954,-145.686,-76.884,-145.676,-76.797,-145.75,-76.749,-146.166,-76.658,-146.777,-76.507,-147.34,-76.438,-148.601,-76.493,-149.046,-76.458,-149.34,-76.419,-149.654,-76.365,-149.285,-76.311,-148.895,-76.272,-148.78,-76.238,-148.632,-76.168,-148.459,-76.118,-148.32,-76.104,-147.86,-76.131,-146.817,-76.318,-146.597,-76.338,-145.886,-76.424,-145.687,-76.429,-145.442,-76.409,-145.642,-76.326,-145.86,-76.267,-146.383,-76.1,-146.323,-76.02,-145.988,-75.889,-145.106,-75.879,-144.721,-75.832,-144.221,-75.731,-143.574,-75.564,-143.022,-75.543,-142.33,-75.491,-142.094,-75.53,-141.506,-75.69,-141.135,-75.746,-141.009,-75.751,-140.874,-75.746,-141.223,-75.546,-140.999,-75.52,-140.709,-75.498,-140.471,-75.447,-140.294,-75.406,-139.691,-75.213,-139.149,-75.16,-137.618,-75.076,-137.09,-75.153,-136.65,-75.162,-136.55,-75.139,-136.462,-75.036,-136.228,-74.836,-136.03,-74.765,-135.362,-74.69,-134.84,-74.694,-134.465,-74.776,-134.117,-74.83,-133.796,-74.855,-133.475,-74.852,-132.992,-74.806,-132.351,-74.789,-132.049,-74.766,-131.707,-74.811,-130.857,-74.826,-130.196,-74.891,-129.791,-74.891,-129.238,-74.829,-128.941,-74.82,-127.863,-74.719,-127.02,-74.698,-126.384,-74.743,-125.353,-74.715,-124.312,-74.736,-123.889,-74.773,-121.544,-74.75,-119.677,-74.655,-119.422,-74.622,-119.022,-74.518,-118.803,-74.422,-118.656,-74.393,-118.342,-74.382,-117.806,-74.403,-117.068,-74.473,-116.433,-74.447,-115.223,-74.487,-115.105,-74.455,-114.991,-74.275,-114.791,-73.989,-114.624,-73.903,-114.346,-73.925,-113.508,-74.089,-113.489,-74.158,-113.575,-74.208,-113.714,-74.228,-113.753,-74.367,-113.641,-74.406,-113.454,-74.394,-113.333,-74.454,-113.597,-74.559,-113.783,-74.618,-113.903,-74.644,-113.985,-74.843,-114.097,-74.909,-114.11,-74.982,-113.932,-74.982,-113.753,-74.952,-113.593,-74.944,-113.092,-74.892,-112.17,-74.832,-111.868,-74.801,-111.696,-74.792,-111.584,-74.751,-111.739,-74.653,-111.789,-74.572,-111.696,-74.504,-111.722,-74.387,-111.806,-74.27,-111.63,-74.181,-111.467,-74.201,-111.18,-74.188,-111.02,-74.23,-110.77,-74.269,-110.534,-74.289,-110.307,-74.367,-110.23,-74.536,-110.3,-74.711,-110.532,-74.836,-110.968,-74.951,-111.463,-75.133,-111.359,-75.22,-111.104,-75.191,-109.99,-75.199,-109.272,-75.185,-108.822,-75.207,-108.254,-75.253,-107.805,-75.322,-107.267,-75.334,-106.932,-75.309,-106.619,-75.344,-105.399,-75.198,-104.902,-75.115,-104.618,-75.156,-104.16,-75.121,-103.901,-75.153,-103.425,-75.101,-103.121,-75.095,-102.771,-75.117,-101.708,-75.127,-101.628,-75.222,-101.304,-75.366,-101.039,-75.422,-100.706,-75.398,-100.463,-75.353,-100.083,-75.37,-99.531,-75.309,-98.98,-75.327,-98.752,-75.317,-98.646,-75.277,-98.558,-75.19,-98.727,-75.141,-99.208,-75.079,-99.652,-74.949,-99.849,-74.922,-100.164,-74.938,-100.313,-74.914,-100.473,-74.872,-100.265,-74.823,-100.013,-74.662,-100.119,-74.515,-100.238,-74.484,-100.531,-74.489,-100.882,-74.541,-101.023,-74.505,-101.252,-74.486,-101.343,-74.35,-101.587,-74.096,-101.715,-74.024,-102.105,-73.958,-102.441,-73.926,-102.766,-73.884,-102.863,-73.784,-102.8,-73.646,-102.411,-73.616,-102.037,-73.631,-101.828,-73.655,-101.587,-73.667,-101.311,-73.695,-101.13,-73.735,-100.985,-73.757,-100.718,-73.758,-99.781,-73.72,-99.656,-73.694,-99.541,-73.645,-99.343,-73.634,-99.162,-73.641,-98.896,-73.611,-99.2,-73.571,-99.528,-73.495,-100.021,-73.403,-100.436,-73.353,-101.189,-73.318,-101.574,-73.33,-101.816,-73.311,-102.675,-73.321,-102.909,-73.285,-103.076,-73.185,-103.308,-72.945,-103.375,-72.819,-103.217,-72.772,-103.11,-72.721,-102.856,-72.716,-102.485,-72.736,-102.363,-72.76,-102.272,-72.835,-102.363,-72.911,-102.482,-72.951,-102.409,-72.987,-102.029,-72.998,-101.842,-73.021,-101.681,-73.03,-101.332,-72.995,-100.821,-72.981,-100.564,-73.016,-100.259,-73.041,-99.811,-73,-98.209,-73.022,-98.012,-73.033,-97.819,-73.102,-97.651,-73.144,-97.476,-73.126,-96.956,-73.206,-96.676,-73.269,-96.394,-73.301,-96.152,-73.309,-95.881,-73.294,-95.529,-73.241,-95.237,-73.22,-95.03,-73.239,-94.586,-73.25,-94.246,-73.313,-93.985,-73.287,-93.706,-73.215,-92.828,-73.165,-92.241,-73.178,-91.169,-73.307,-90.921,-73.319,-90.431,-73.243,-90.274,-73.119,-90.295,-72.978,-90.152,-72.945,-90.035,-72.96,-89.818,-72.863,-89.522,-72.871,-89.341,-72.89,-89.229,-72.826,-89.127,-72.693,-88.78,-72.683,-88.527,-72.702,-88.194,-72.787,-88.195,-72.859,-88.332,-72.934,-88.561,-73.121,-88.419,-73.229,-88.205,-73.22,-87.936,-73.241,-87.608,-73.195,-87.401,-73.192,-87.038,-73.354,-86.791,-73.364,-86.602,-73.354,-85.981,-73.208,-85.801,-73.192,-85.582,-73.259,-85.261,-73.413,-84.981,-73.502,-84.571,-73.557,-84.214,-73.573,-83.796,-73.645,-83.565,-73.706,-83.042,-73.707,-82.815,-73.732,-82.183,-73.857,-81.606,-73.796,-81.309,-73.738,-81.163,-73.632,-81.236,-73.474,-81.262,-73.315,-81.176,-73.249,-81.024,-73.236,-80.336,-73.414,-80.38,-73.308,-80.439,-73.225,-80.614,-73.083,-80.588,-72.978,-80.442,-72.945,-80.152,-73,-79.808,-73.028,-79.522,-73.09,-78.964,-73.312,-78.786,-73.507,-78.408,-73.556,-78.144,-73.547,-77.846,-73.515,-77.444,-73.488,-77.136,-73.496,-76.85,-73.46,-76.765,-73.566,-77.033,-73.718,-77.135,-73.818,-77.049,-73.844,-76.887,-73.821,-76.755,-73.789,-76.291,-73.805,-75.916,-73.736,-75.595,-73.711,-75.293,-73.639,-75.044,-73.645,-74.855,-73.658,-74.594,-73.715,-74.345,-73.684,-74.197,-73.696,-73.996,-73.7,-72.929,-73.448,-72.687,-73.452,-72.381,-73.438,-71.994,-73.379,-71.698,-73.353,-71.453,-73.354,-71.017,-73.263,-70.323,-73.274,-69.969,-73.226,-69.282,-73.17,-68.821,-73.105,-68,-72.936,-67.667,-72.835,-67.307,-72.611,-67.08,-72.388,-66.828,-72.09,-66.952,-71.897,-67.084,-71.812,-67.196,-71.719,-67.46,-71.527,-67.53,-71.285,-67.505,-71.058,-67.598,-70.845,-67.692,-70.686,-67.888,-70.422,-68.126,-70.25,-68.403,-70.02,-68.404,-69.809,-68.47,-69.644,-68.638,-69.526,-68.708,-69.432,-68.58,-69.413,-68.462,-69.384,-68.141,-69.348,-67.372,-69.412,-67.304,-69.318,-67.11,-69.248,-66.975,-69.161,-67.021,-69.029,-67.188,-68.974,-67.391,-68.861,-67.299,-68.771,-67.134,-68.771,-67.054,-68.671,-67.117,-68.575,-67.041,-68.453,-66.894,-68.298,-66.793,-68.24,-66.978,-68.147,-67.15,-68.025,-67.107,-67.93,-67.021,-67.831,-66.915,-67.693,-66.77,-67.593,-66.677,-67.56,-66.705,-67.527,-66.923,-67.492,-67.124,-67.485,-67.487,-67.547,-67.545,-67.535,-67.565,-67.503,-67.586,-67.435,-67.55,-67.269,-67.493,-67.113,-67.44,-67.091,-67.299,-67.071,-67.16,-66.952,-67.034,-66.945,-66.955,-66.985,-66.929,-67.144,-66.886,-67.18,-66.902,-67.256,-66.836,-67.282,-66.757,-67.233,-66.61,-67.209,-66.552,-67.263,-66.499,-67.289,-66.472,-67.243,-66.491,-67.114,-66.515,-67.062,-66.533,-66.979,-66.502,-66.94,-66.465,-66.875,-66.527,-66.741,-66.504,-66.69,-66.371,-66.609,-66.307,-66.592,-66.182,-66.592,-65.954,-66.646,-65.847,-66.65,-65.766,-66.625,-65.718,-66.573,-65.678,-66.403,-65.776,-66.343,-65.775,-66.288,-65.717,-66.254,-65.617,-66.135,-65.465,-66.129,-65.316,-66.14,-65.172,-66.117,-65.222,-66.068,-65.267,-65.994,-65.105,-65.958,-64.999,-65.946,-64.722,-65.993,-64.614,-66.019,-64.514,-65.96,-64.547,-65.9,-64.653,-65.867,-64.673,-65.814,-64.647,-65.748,-64.475,-65.781,-64.435,-65.768,-64.39,-65.708,-64.417,-65.68,-64.439,-65.641,-64.213,-65.633,-64.18,-65.617,-64.132,-65.571,-64.066,-65.554,-63.862,-65.556,-63.818,-65.532,-63.798,-65.48,-63.908,-65.467,-64.051,-65.417,-64.071,-65.278,-64.038,-65.179,-63.912,-65.093,-63.76,-65.033,-63.482,-65.085,-63.264,-65.073,-63.178,-65.126,-63.059,-65.139,-63.033,-65.08,-63.086,-65.028,-63.12,-64.942,-62.775,-64.842,-62.665,-64.858,-62.528,-64.833,-62.576,-64.756,-62.503,-64.656,-62.404,-64.643,-62.338,-64.729,-62.243,-64.747,-62.14,-64.727,-61.883,-64.625,-61.756,-64.61,-61.632,-64.605,-61.5,-64.546,-61.47,-64.476,-61.396,-64.427,-61.174,-64.362,-61.082,-64.315,-60.887,-64.15,-60.922,-64.108,-60.864,-64.073,-60.277,-63.924,-59.99,-63.91,-59.51,-63.821,-59.218,-63.714,-59.036,-63.67,-58.872,-63.552,-58.674,-63.534,-58.216,-63.451,-57.868,-63.319,-57.39,-63.226,-57.168,-63.235,-57.077,-63.262,-57.021,-63.373]],[[-45.223,-78.811,-45.092,-78.814,-44.566,-78.804,-44.041,-78.807,-43.722,-78.818,-43.627,-78.846,-43.544,-78.902,-43.451,-78.99,-43.364,-79.085,-43.267,-79.163,-43.211,-79.3,-43.119,-79.35,-42.965,-79.477,-42.945,-79.579,-42.99,-79.801,-43.066,-79.891,-43.267,-79.979,-43.496,-79.969,-43.6,-79.974,-43.704,-79.99,-43.742,-80.003,-43.758,-80.021,-43.49,-80.096,-43.459,-80.123,-43.454,-80.155,-43.489,-80.178,-43.528,-80.191,-49.187,-80.643,-49.41,-80.667,-49.63,-80.712,-49.701,-80.753,-49.773,-80.784,-54.162,-80.87,-54.202,-80.864,-54.241,-80.847,-54.351,-80.76,-54.372,-80.624,-54.347,-80.569,-54.13,-80.517,-54.045,-80.487,-53.676,-80.284,-53.482,-80.189,-53.394,-80.109,-53.346,-80.114,-53.176,-80.161,-53.053,-80.175,-52.807,-80.156,-52.567,-80.099,-52.461,-80.067,-52.357,-80.078,-52.338,-80.126,-52.297,-80.141,-51.711,-79.99,-51.184,-79.82,-50.664,-79.627,-50.402,-79.512,-50.339,-79.479,-50.295,-79.43,-50.331,-79.381,-50.379,-79.338,-50.42,-79.321,-50.464,-79.313,-50.733,-79.283,-50.649,-79.233,-50.573,-79.172,-50.52,-79.104,-50.502,-79.022,-50.514,-78.98,-50.502,-78.95,-50.38,-78.923,-50.298,-78.882,-50.242,-78.833,-50.335,-78.818,-50.377,-78.78,-50.294,-78.696,-50.22,-78.605,-50.142,-78.557,-49.94,-78.462,-49.354,-78.222,-49.144,-78.094,-49.081,-78.047,-47.692,-77.84,-47.463,-77.819,-47.03,-77.791,-46.826,-77.785,-46.258,-77.805,-45.993,-77.827,-45.53,-77.881,-44.852,-77.988,-44.594,-78.035,-44.34,-78.093,-44.094,-78.167,-43.854,-78.258,-43.809,-78.287,-43.785,-78.336,-43.777,-78.385,-43.788,-78.433,-43.852,-78.53,-43.947,-78.598,-45.068,-78.661,-45.213,-78.687,-45.295,-78.74,-45.352,-78.791,-45.223,-78.811]],[[-59.734,-80.344,-59.774,-80.558,-59.771,-80.657,-59.825,-80.733,-59.926,-80.774,-60.125,-80.841,-60.268,-80.881,-60.583,-80.948,-62.023,-80.889,-62.671,-80.834,-62.94,-80.766,-62.986,-80.735,-63.068,-80.627,-63.144,-80.595,-63.715,-80.617,-64.065,-80.65,-64.126,-80.668,-64.22,-80.734,-64.268,-80.749,-65.203,-80.607,-66.184,-80.442,-66.591,-80.358,-66.734,-80.319,-66.771,-80.294,-66.681,-80.26,-66.588,-80.239,-66.483,-80.224,-66.377,-80.222,-66.296,-80.235,-66.217,-80.258,-66.168,-80.346,-66.115,-80.361,-65.98,-80.384,-62.519,-80.373,-62.232,-80.369,-61.633,-80.344,-61.313,-80.307,-61.194,-80.257,-61.485,-80.244,-61.597,-80.206,-61.694,-80.134,-61.717,-80.069,-61.684,-80.02,-61.302,-79.996,-61.246,-79.978,-61.346,-79.951,-61.343,-79.887,-61.115,-79.862,-61.026,-79.809,-60.579,-79.741,-59.873,-79.777,-59.706,-79.875,-59.752,-79.938,-59.786,-80.001,-59.788,-80.101,-59.498,-80.115,-59.408,-80.151,-59.322,-80.196,-59.427,-80.198,-59.53,-80.208,-59.612,-80.255,-59.683,-80.315,-59.734,-80.344]],[[-70.051,-69.189,-70.079,-69.311,-69.913,-69.267,-69.855,-69.277,-69.708,-69.321,-69.417,-69.583,-69.353,-69.666,-69.234,-69.909,-69.091,-70.09,-68.731,-70.408,-68.554,-70.581,-68.459,-70.683,-68.451,-70.818,-68.336,-70.856,-68.314,-70.912,-68.278,-71.097,-68.252,-71.313,-68.228,-71.725,-68.241,-71.822,-68.394,-71.975,-68.461,-72.085,-68.543,-72.158,-68.64,-72.21,-69.149,-72.427,-69.209,-72.534,-70.063,-72.626,-70.543,-72.664,-70.731,-72.623,-70.923,-72.613,-71.159,-72.627,-71.846,-72.639,-72.367,-72.67,-72.433,-72.658,-72.48,-72.617,-72.531,-72.59,-72.67,-72.596,-72.78,-72.581,-72.888,-72.547,-73.007,-72.484,-73.057,-72.448,-73.086,-72.408,-72.855,-72.304,-72.737,-72.281,-72.618,-72.275,-72.376,-72.296,-72.135,-72.331,-71.605,-72.359,-70.873,-72.366,-70.671,-72.356,-70.428,-72.323,-70.206,-72.228,-70.314,-72.191,-70.424,-72.168,-70.533,-72.163,-70.641,-72.17,-70.945,-72.229,-71.178,-72.264,-71.413,-72.284,-71.661,-72.25,-71.892,-72.153,-71.898,-72.121,-71.107,-72.047,-71.034,-72.035,-70.891,-71.987,-70.845,-71.946,-70.821,-71.907,-71.355,-71.836,-71.465,-71.838,-71.574,-71.851,-71.816,-71.822,-72.046,-71.74,-72.259,-71.641,-72.337,-71.632,-72.412,-71.662,-72.928,-71.922,-72.972,-71.924,-73.167,-71.905,-73.41,-71.853,-73.633,-71.835,-73.776,-71.849,-73.83,-71.87,-73.691,-71.929,-73.572,-71.981,-73.537,-72.022,-73.899,-72.152,-73.996,-72.17,-74.153,-72.159,-74.209,-72.142,-74.322,-72.073,-74.429,-72.056,-74.663,-72.07,-74.786,-72.064,-74.908,-72.033,-75.024,-71.988,-75.13,-71.964,-75.259,-71.914,-75.353,-71.878,-75.383,-71.828,-75.373,-71.78,-75.331,-71.752,-75.325,-71.726,-75.353,-71.68,-75.335,-71.645,-75.293,-71.615,-75.1,-71.555,-74.863,-71.543,-74.636,-71.617,-74.487,-71.642,-74.419,-71.643,-74.392,-71.638,-74.373,-71.618,-74.38,-71.579,-74.42,-71.507,-74.425,-71.457,-74.375,-71.415,-74.308,-71.4,-74.236,-71.388,-74.187,-71.383,-74.041,-71.41,-73.937,-71.438,-73.724,-71.517,-73.545,-71.573,-73.479,-71.579,-73.427,-71.559,-73.38,-71.528,-73.592,-71.448,-73.617,-71.397,-73.604,-71.351,-73.474,-71.325,-73.397,-71.321,-73.02,-71.369,-72.821,-71.384,-72.622,-71.388,-72.212,-71.335,-72.43,-71.275,-72.905,-71.223,-72.995,-71.187,-73.06,-71.127,-72.71,-71.073,-72.356,-71.075,-71.719,-71.145,-71.504,-71.112,-71.308,-71.011,-71.194,-70.985,-70.741,-70.993,-70.381,-70.946,-70.323,-70.951,-70.268,-70.965,-69.916,-71.134,-69.87,-71.126,-69.835,-71.093,-69.823,-71.034,-69.823,-70.973,-69.83,-70.914,-69.876,-70.876,-69.933,-70.88,-69.993,-70.897,-70.094,-70.883,-70.196,-70.851,-70.299,-70.836,-70.661,-70.818,-70.917,-70.786,-71.049,-70.762,-71.173,-70.713,-71.19,-70.66,-71.061,-70.537,-70.562,-70.404,-70.328,-70.361,-70.09,-70.351,-69.975,-70.36,-69.702,-70.415,-69.66,-70.412,-69.618,-70.398,-69.883,-70.305,-70.118,-70.234,-70.234,-70.18,-70.328,-70.16,-70.72,-70.139,-70.926,-70.192,-71.024,-70.201,-71.121,-70.196,-71.696,-70.068,-71.729,-70.054,-71.81,-70.005,-71.854,-69.969,-71.879,-69.909,-71.868,-69.847,-71.852,-69.807,-71.767,-69.649,-71.718,-69.524,-71.743,-69.423,-71.834,-69.367,-71.963,-69.329,-72.081,-69.267,-72.114,-69.225,-72.135,-69.177,-72.138,-69.115,-72.109,-69.06,-72.058,-69.001,-71.99,-68.971,-71.869,-68.941,-71.392,-68.874,-70.417,-68.789,-70.312,-68.832,-70.154,-68.923,-70.105,-68.959,-70.053,-69.14,-70.051,-69.189]],[[-98.091,-71.912,-98.176,-72.018,-98.168,-72.123,-97.923,-72.117,-97.816,-71.919,-97.585,-71.883,-97.473,-72,-97.582,-72.095,-97.526,-72.149,-97.46,-72.188,-97.345,-72.189,-97.242,-72.132,-97.196,-72.091,-97.155,-72.045,-97.089,-71.944,-96.869,-71.851,-96.383,-71.836,-96.125,-71.896,-96.298,-72.045,-96.715,-72.132,-96.979,-72.222,-96.89,-72.247,-96.799,-72.259,-96.718,-72.255,-96.482,-72.208,-95.906,-72.122,-95.685,-72.057,-95.609,-72.068,-95.61,-72.175,-95.531,-72.249,-95.575,-72.41,-95.826,-72.439,-96.078,-72.454,-96.014,-72.525,-96.03,-72.554,-96.052,-72.577,-96.693,-72.548,-96.804,-72.558,-96.915,-72.578,-97.028,-72.574,-97.25,-72.521,-97.366,-72.522,-97.596,-72.548,-97.828,-72.557,-98.163,-72.556,-98.408,-72.548,-98.641,-72.49,-98.882,-72.473,-99.149,-72.472,-99.434,-72.407,-99.672,-72.38,-100.014,-72.312,-100.104,-72.287,-100.195,-72.273,-100.357,-72.278,-101.602,-72.176,-101.785,-72.178,-101.903,-72.19,-102.022,-72.185,-102.265,-72.135,-102.314,-72.081,-102.288,-72.032,-102.237,-72.009,-102.128,-71.985,-100.401,-71.866,-100.219,-71.833,-100.085,-71.837,-99.985,-71.939,-99.833,-72.046,-99.784,-72.044,-99.735,-72.033,-99.563,-71.945,-99.254,-71.972,-99.082,-71.933,-98.965,-71.854,-98.615,-71.764,-98.394,-71.782,-98.189,-71.82,-98.091,-71.912]],[[-120.556,-73.756,-120.378,-73.856,-120.312,-73.922,-120.272,-73.989,-120.99,-74.157,-121.019,-74.173,-121.054,-74.26,-121.036,-74.279,-121.005,-74.293,-121.002,-74.326,-121.062,-74.337,-122.287,-74.403,-122.859,-74.343,-122.938,-74.302,-122.956,-74.24,-122.891,-74.227,-122.765,-74.219,-122.794,-74.19,-122.875,-74.141,-122.881,-74.099,-122.71,-73.994,-122.625,-73.966,-122.951,-73.867,-122.992,-73.844,-123.035,-73.838,-123.191,-73.849,-123.346,-73.843,-123.292,-73.803,-123.249,-73.739,-123.112,-73.682,-123.012,-73.673,-122.91,-73.68,-122.436,-73.682,-121.967,-73.712,-121.497,-73.733,-120.722,-73.752,-120.556,-73.756]],[[-126.33,-73.286,-126.065,-73.315,-125.976,-73.357,-125.857,-73.388,-125.736,-73.406,-125.627,-73.453,-125.561,-73.536,-125.504,-73.562,-125.326,-73.618,-125.264,-73.666,-125.276,-73.691,-125.612,-73.711,-125.723,-73.703,-125.828,-73.718,-125.86,-73.749,-125.857,-73.78,-125.799,-73.802,-125.674,-73.822,-125.552,-73.82,-125.327,-73.796,-125.224,-73.801,-125.109,-73.826,-124.993,-73.83,-124.694,-73.75,-124.617,-73.735,-124.54,-73.74,-124.129,-73.834,-124.042,-73.88,-124.101,-73.907,-124.152,-73.944,-124.129,-73.971,-123.932,-74.008,-123.851,-74.057,-123.8,-74.076,-123.811,-74.117,-123.839,-74.168,-123.837,-74.226,-123.937,-74.256,-123.982,-74.256,-124.199,-74.226,-124.873,-74.208,-125.09,-74.182,-125.421,-74.07,-125.549,-74.063,-125.683,-74.035,-125.887,-73.955,-126.244,-73.891,-126.472,-73.812,-126.466,-73.746,-126.496,-73.7,-126.538,-73.68,-126.583,-73.67,-126.711,-73.654,-126.838,-73.657,-126.902,-73.677,-127.006,-73.726,-127.122,-73.734,-127.212,-73.724,-127.232,-73.713,-127.233,-73.586,-127.332,-73.567,-127.414,-73.516,-127.429,-73.447,-127.394,-73.382,-127.268,-73.304,-127.124,-73.294,-126.978,-73.308,-126.83,-73.291,-126.597,-73.279,-126.33,-73.286]],[[-159.053,-79.807,-160.302,-79.845,-160.807,-79.812,-161.866,-79.704,-163.317,-79.505,-163.712,-79.442,-163.971,-79.389,-164.226,-79.321,-164.282,-79.246,-164.245,-79.138,-164.2,-79.051,-164.126,-78.995,-163.815,-78.929,-163.66,-78.856,-163.345,-78.78,-163.256,-78.722,-163.124,-78.719,-162.873,-78.725,-162.622,-78.742,-162.39,-78.76,-162.161,-78.793,-161.643,-78.901,-161.283,-79.007,-160.764,-79.132,-160.25,-79.271,-159.964,-79.324,-159.684,-79.402,-159.419,-79.508,-159.366,-79.545,-159.256,-79.591,-159.19,-79.637,-159.119,-79.675,-159.051,-79.695,-158.997,-79.735,-159.01,-79.78,-159.053,-79.807]],[[167.084,-77.322,167.461,-77.394,168.451,-77.386,169.276,-77.455,169.353,-77.525,169.117,-77.561,168.755,-77.653,168.519,-77.681,168.323,-77.683,167.918,-77.644,167.392,-77.649,167.279,-77.703,167.025,-77.756,166.729,-77.851,166.65,-77.774,166.533,-77.7,166.237,-77.547,166.217,-77.525,166.378,-77.494,166.458,-77.444,166.626,-77.377,166.607,-77.336,166.469,-77.289,166.413,-77.252,166.506,-77.189,166.716,-77.162,166.987,-77.187,167.107,-77.271,167.085,-77.307,167.084,-77.322]],[[-31.119,-79.798,-30.985,-79.818,-30.841,-79.771,-30.861,-79.726,-30.78,-79.647,-30.66,-79.733,-29.871,-79.823,-29.614,-79.91,-29.721,-79.93,-29.8,-79.926,-30.029,-79.936,-30.422,-80.011,-30.844,-79.938,-31.594,-79.888,-31.824,-79.85,-32,-79.732,-31.68,-79.634,-31.605,-79.645,-31.119,-79.798]],[[-33.934,-79.32,-34.05,-79.357,-36.481,-79.294,-36.601,-79.283,-36.566,-79.209,-36.238,-79.196,-36.048,-79.181,-35.79,-79.149,-35.597,-79.092,-35.535,-79.09,-34.392,-79.223,-33.995,-79.279,-33.947,-79.305,-33.934,-79.32]],[[-70.334,-79.68,-70.553,-79.683,-70.984,-79.674,-71.414,-79.64,-71.526,-79.624,-71.687,-79.568,-71.735,-79.539,-71.778,-79.501,-71.784,-79.444,-71.667,-79.246,-71.454,-79.129,-71.254,-79.06,-70.626,-78.902,-70.544,-78.884,-69.972,-78.809,-69.748,-78.769,-69.398,-78.686,-67.479,-78.362,-67.038,-78.316,-66.84,-78.35,-66.728,-78.384,-66.787,-78.422,-67.046,-78.514,-67.166,-78.57,-67.481,-78.682,-68.157,-78.871,-68.638,-79.013,-69.251,-79.21,-69.394,-79.28,-69.686,-79.443,-69.634,-79.518,-69.732,-79.618,-70.116,-79.666,-70.334,-79.68]],[[-57.846,-64.054,-57.809,-64.068,-57.774,-64.062,-57.741,-64.048,-57.71,-64.015,-57.593,-63.967,-57.48,-63.962,-57.517,-64.011,-57.249,-64.097,-57.273,-64.166,-57.222,-64.221,-57.328,-64.238,-57.414,-64.296,-57.338,-64.318,-57.295,-64.367,-57.388,-64.379,-57.581,-64.35,-57.683,-64.357,-57.671,-64.311,-57.703,-64.293,-57.823,-64.302,-57.871,-64.401,-57.91,-64.41,-57.952,-64.394,-57.921,-64.331,-57.971,-64.32,-58.022,-64.322,-58.169,-64.369,-58.214,-64.37,-58.304,-64.315,-58.02,-64.242,-58.138,-64.206,-58.162,-64.161,-58.147,-64.097,-58.25,-64.107,-58.352,-64.131,-58.398,-64.135,-58.438,-64.113,-58.425,-64.068,-58.342,-63.994,-58.275,-63.916,-58.146,-63.878,-58.07,-63.847,-57.971,-63.835,-57.926,-63.806,-57.831,-63.804,-57.779,-63.868,-57.781,-63.907,-57.827,-63.949,-57.846,-64.054]],[[-55.528,-63.174,-55.466,-63.2,-55.216,-63.199,-55.156,-63.205,-55.106,-63.249,-55.075,-63.324,-55.157,-63.353,-55.594,-63.336,-55.75,-63.297,-55.83,-63.298,-56.009,-63.342,-56.083,-63.383,-56.379,-63.437,-56.463,-63.418,-56.499,-63.358,-56.505,-63.334,-56.475,-63.318,-56.461,-63.302,-56.466,-63.283,-56.385,-63.234,-56.042,-63.157,-55.59,-63.128,-55.529,-63.157,-55.528,-63.174]],[[-57.978,-61.912,-57.849,-61.94,-57.738,-61.921,-57.677,-61.942,-57.637,-61.998,-57.64,-62.02,-57.807,-62.012,-57.963,-62.078,-58.148,-62.063,-58.172,-62.118,-58.133,-62.146,-58.183,-62.17,-58.341,-62.119,-58.467,-62.137,-58.507,-62.226,-58.562,-62.244,-58.594,-62.248,-58.644,-62.225,-58.746,-62.218,-58.755,-62.206,-58.819,-62.171,-59.004,-62.21,-58.955,-62.164,-58.71,-62.045,-58.684,-62.008,-58.399,-61.938,-58.265,-61.953,-57.978,-61.912]],[[-63.181,-64.47,-63.277,-64.573,-63.131,-64.572,-63.032,-64.535,-62.928,-64.519,-62.837,-64.572,-63.026,-64.611,-63.203,-64.68,-63.275,-64.717,-63.355,-64.734,-63.458,-64.727,-63.558,-64.734,-63.647,-64.803,-63.74,-64.834,-63.77,-64.808,-63.804,-64.792,-64.007,-64.769,-64.099,-64.733,-64.184,-64.71,-64.272,-64.698,-64.226,-64.635,-64.171,-64.582,-63.867,-64.51,-63.897,-64.487,-63.916,-64.457,-63.674,-64.421,-63.668,-64.384,-63.683,-64.343,-63.606,-64.314,-63.534,-64.273,-63.486,-64.261,-63.334,-64.266,-63.23,-64.324,-63.271,-64.381,-63.181,-64.47]],[[-62.326,-64.424,-62.396,-64.465,-62.455,-64.472,-62.508,-64.454,-62.58,-64.514,-62.727,-64.496,-62.782,-64.479,-62.747,-64.472,-62.721,-64.445,-62.643,-64.392,-62.504,-64.253,-62.48,-64.211,-62.59,-64.14,-62.611,-64.116,-62.586,-64.076,-62.545,-64.046,-62.451,-64.012,-62.329,-64.013,-62.268,-64.04,-62.269,-64.09,-62.058,-64.138,-62.094,-64.235,-62.174,-64.296,-62.186,-64.369,-62.304,-64.401,-62.326,-64.424]],[[-67.988,-67.474,-68.093,-67.539,-68.175,-67.558,-68.25,-67.54,-68.325,-67.532,-68.381,-67.555,-68.439,-67.656,-68.507,-67.707,-68.58,-67.733,-68.622,-67.723,-68.664,-67.723,-68.734,-67.746,-68.818,-67.753,-68.901,-67.744,-68.982,-67.68,-69.098,-67.603,-69.12,-67.578,-69.138,-67.515,-69.133,-67.453,-69.082,-67.403,-68.82,-67.234,-68.734,-67.157,-68.656,-67.07,-68.575,-66.993,-68.417,-66.853,-68.336,-66.802,-67.938,-66.657,-67.831,-66.624,-67.711,-66.633,-67.681,-66.709,-67.741,-66.746,-67.932,-66.845,-67.969,-66.982,-67.968,-67.032,-67.949,-67.045,-67.876,-67.062,-67.828,-67.082,-67.761,-67.123,-67.688,-67.147,-67.848,-67.219,-67.956,-67.255,-68.03,-67.3,-68.175,-67.344,-68.235,-67.372,-68.144,-67.382,-68.007,-67.418,-67.969,-67.45,-67.988,-67.474]],[[-73.707,-70.635,-73.55,-70.723,-73.695,-70.794,-74.205,-70.924,-74.505,-70.973,-74.806,-71.012,-76.176,-71.132,-76.271,-71.133,-76.364,-71.117,-76.421,-71.09,-76.512,-70.991,-76.5,-70.941,-76.378,-70.894,-76.249,-70.864,-76.035,-70.836,-75.21,-70.773,-75.127,-70.752,-75.06,-70.706,-75.038,-70.651,-75.007,-70.609,-74.954,-70.59,-74.898,-70.591,-74.79,-70.631,-74.59,-70.792,-74.527,-70.77,-74.469,-70.727,-74.456,-70.587,-74.401,-70.576,-74.225,-70.615,-74.115,-70.655,-74.113,-70.577,-74.038,-70.553,-73.958,-70.561,-73.879,-70.578,-73.707,-70.635]],[[-74.987,-69.728,-74.81,-69.752,-74.55,-69.861,-74.465,-69.917,-74.438,-69.95,-74.46,-69.972,-74.578,-69.998,-74.672,-70.132,-74.849,-70.179,-75.268,-70.149,-75.727,-70.096,-75.764,-70.085,-75.804,-70.038,-75.813,-69.984,-75.76,-69.916,-75.681,-69.882,-75.34,-69.84,-75.314,-69.817,-75.265,-69.749,-75.179,-69.735,-75.136,-69.741,-74.987,-69.728]],[[-74.354,-73.098,-74.499,-73.229,-74.522,-73.244,-74.668,-73.275,-74.615,-73.311,-74.575,-73.328,-74.551,-73.369,-74.467,-73.427,-74.366,-73.464,-74.452,-73.565,-74.575,-73.611,-75.901,-73.333,-76.003,-73.288,-76.053,-73.255,-76.09,-73.203,-76.096,-73.15,-76.062,-73.109,-76.018,-73.085,-75.898,-73.056,-75.775,-73.054,-75.506,-73.109,-75.468,-73.101,-75.417,-73.052,-75.276,-73.05,-75.244,-73.009,-75.439,-72.994,-75.6,-72.953,-75.702,-72.911,-75.731,-72.879,-75.377,-72.82,-74.474,-72.894,-74.336,-72.919,-74.276,-72.951,-74.224,-72.997,-74.354,-73.098]],[[-91.161,-73.182,-91.344,-73.207,-91.511,-73.196,-91.45,-72.968,-91.357,-72.909,-91.382,-72.868,-91.551,-72.754,-91.67,-72.624,-91.612,-72.594,-91.304,-72.547,-90.947,-72.556,-90.807,-72.611,-90.763,-72.681,-90.78,-72.732,-90.895,-72.824,-90.776,-72.854,-90.751,-72.917,-90.776,-72.993,-90.893,-73.084,-90.998,-73.137,-91.161,-73.182]],[[167.643,-78.141,167.517,-78.216,167.377,-78.249,166.936,-78.222,166.626,-78.284,166.285,-78.306,166.122,-78.275,166.051,-78.213,166.013,-78.131,166.013,-78.102,166.111,-78.09,166.567,-78.148,166.76,-78.198,166.864,-78.196,167.138,-78.13,167.364,-78.046,167.422,-78.006,167.498,-77.992,167.594,-78.022,167.64,-78.112,167.643,-78.141]],[[100.981,-65.678,100.547,-65.701,100.512,-65.675,100.351,-65.673,100.293,-65.651,100.27,-65.603,100.324,-65.521,100.409,-65.466,100.545,-65.409,100.607,-65.396,100.883,-65.378,101.079,-65.403,101.221,-65.472,101.259,-65.528,101.238,-65.565,100.981,-65.678]],[[26.857,-70.381,26.793,-70.419,26.609,-70.412,26.47,-70.448,26.358,-70.434,26.005,-70.373,25.964,-70.295,25.954,-70.261,25.983,-70.2,26.301,-70.072,26.426,-70.061,26.605,-70.078,26.686,-70.114,26.737,-70.186,26.875,-70.33,26.857,-70.381]],[[-5.894,-70.552,-6.156,-70.612,-6.181,-70.586,-6.266,-70.55,-6.438,-70.453,-6.244,-70.446,-6.068,-70.405,-5.972,-70.421,-5.95,-70.432,-5.894,-70.552]],[[-3.28,-70.534,-3.442,-70.535,-3.49,-70.508,-3.497,-70.488,-3.287,-70.344,-3.173,-70.307,-2.95,-70.28,-2.805,-70.288,-2.714,-70.32,-2.684,-70.376,-2.683,-70.462,-2.738,-70.507,-3.28,-70.534]],[[-66.174,-80.078,-66.267,-80.081,-66.319,-80.075,-66.367,-80.054,-66.41,-79.973,-66.904,-79.909,-66.962,-79.873,-66.994,-79.793,-67.077,-79.762,-67.719,-79.62,-67.771,-79.589,-67.809,-79.546,-67.688,-79.528,-67.438,-79.56,-66.979,-79.569,-66.881,-79.582,-66.785,-79.608,-66.274,-79.612,-66.014,-79.624,-65.87,-79.738,-65.579,-79.771,-65.54,-79.837,-65.504,-79.954,-65.899,-80.041,-65.989,-80.054,-66.174,-80.078]],[[-67.262,-79.453,-67.434,-79.501,-68.161,-79.479,-68.408,-79.464,-68.549,-79.437,-68.422,-79.333,-68.324,-79.298,-68.233,-79.285,-68.033,-79.227,-67.714,-79.214,-67.475,-79.223,-67.069,-79.268,-67.173,-79.312,-67.24,-79.328,-67.305,-79.394,-67.262,-79.453]],[[-55.165,-61.22,-55.297,-61.249,-55.347,-61.212,-55.369,-61.146,-55.44,-61.106,-55.387,-61.073,-54.671,-61.117,-54.71,-61.14,-55.058,-61.169,-55.165,-61.22]],[[-55.873,-63.536,-55.957,-63.58,-56.178,-63.513,-56.235,-63.469,-56.21,-63.437,-55.858,-63.407,-55.762,-63.422,-55.719,-63.492,-55.873,-63.536]],[[-57.24,-64.567,-57.326,-64.571,-57.433,-64.54,-57.448,-64.488,-57.446,-64.46,-57.366,-64.439,-57.315,-64.435,-57.023,-64.352,-56.895,-64.333,-56.952,-64.382,-56.945,-64.427,-56.991,-64.468,-57.24,-64.567]],[[-56.06,-63.079,-56.259,-63.173,-56.354,-63.169,-56.546,-63.098,-56.601,-63.062,-56.614,-63.045,-56.489,-62.982,-56.14,-63.005,-56.062,-63.013,-56.058,-63.019,-56.051,-63.055,-56.06,-63.079]],[[-57.374,-63.807,-57.36,-63.825,-57.161,-63.816,-57.104,-63.841,-57.218,-63.876,-57.248,-63.868,-57.344,-63.879,-57.616,-63.854,-57.683,-63.813,-57.439,-63.791,-57.374,-63.807]],[[-61.998,-69.722,-62.085,-69.729,-62.172,-69.637,-62.216,-69.495,-62.496,-69.288,-62.568,-69.18,-62.516,-69.155,-62.442,-69.146,-62.239,-69.176,-62.118,-69.215,-61.978,-69.3,-61.816,-69.376,-61.784,-69.442,-61.807,-69.515,-61.911,-69.533,-61.908,-69.588,-61.97,-69.691,-61.998,-69.722]],[[-60.625,-62.56,-60.576,-62.573,-60.14,-62.549,-60.003,-62.618,-59.85,-62.615,-60.221,-62.745,-60.322,-62.708,-60.354,-62.679,-60.378,-62.617,-60.62,-62.633,-60.697,-62.621,-60.796,-62.662,-60.995,-62.679,-61.063,-62.679,-61.15,-62.634,-61.152,-62.589,-60.975,-62.592,-60.838,-62.534,-60.799,-62.475,-60.732,-62.491,-60.625,-62.56]],[[-71.985,-69.698,-72.202,-69.74,-72.345,-69.707,-72.777,-69.645,-72.957,-69.529,-72.937,-69.469,-72.857,-69.433,-72.726,-69.413,-72.464,-69.452,-72.331,-69.492,-71.985,-69.698]],[[-95.027,-72.665,-95.219,-72.67,-95.273,-72.647,-95.216,-72.599,-94.753,-72.517,-94.566,-72.468,-94.538,-72.476,-94.514,-72.492,-94.434,-72.589,-94.426,-72.613,-95.027,-72.665]],[[-104.538,-73.166,-104.66,-73.212,-104.881,-73.201,-105.053,-73.126,-105.123,-73.026,-105.132,-72.992,-105.085,-72.966,-104.972,-72.941,-104.538,-73.166]],[[-119.549,-74.11,-119.758,-74.122,-119.821,-74.12,-119.887,-74.097,-119.905,-74.082,-119.797,-74.029,-119.694,-74.006,-119.662,-73.989,-119.803,-73.815,-119.669,-73.809,-119.516,-73.775,-119.216,-73.778,-118.959,-73.809,-118.91,-73.834,-118.877,-73.878,-118.99,-73.967,-119.059,-73.998,-119.449,-74.076,-119.549,-74.11]],[[-127.366,-74.623,-127.518,-74.641,-127.817,-74.575,-127.915,-74.543,-128,-74.489,-128.07,-74.478,-128.096,-74.466,-128.133,-74.327,-128.043,-74.312,-127.853,-74.332,-127.486,-74.405,-127.23,-74.425,-127.145,-74.48,-127.232,-74.578,-127.366,-74.623]],[[-161.994,-83.119,-162.305,-83.142,-163.047,-83.097,-163.242,-83.06,-163.348,-83.022,-163.552,-82.988,-163.602,-82.969,-163.602,-82.927,-163.634,-82.902,-163.704,-82.879,-163.735,-82.857,-163.796,-82.843,-162.798,-82.865,-162.411,-82.899,-162.34,-82.923,-161.635,-83.027,-161.828,-83.043,-161.994,-83.119]],[[-160.467,-81.589,-160.571,-81.598,-163.253,-81.482,-163.766,-81.445,-163.89,-81.424,-163.939,-81.404,-163.951,-81.391,-163.93,-81.352,-163.869,-81.324,-163.201,-81.281,-162.456,-81.313,-161.559,-81.397,-160.938,-81.463,-160.617,-81.522,-160.485,-81.567,-160.467,-81.589]],[[163.976,-74.833,163.845,-74.833,163.763,-74.803,163.737,-74.734,163.742,-74.712,164.002,-74.629,164.208,-74.608,164.098,-74.732,164.059,-74.753,163.976,-74.833]],[[162.968,-75.567,162.788,-75.696,162.662,-75.692,162.591,-75.669,162.72,-75.597,162.842,-75.566,162.917,-75.557,162.968,-75.567]],[[169.844,-73.605,169.709,-73.625,169.522,-73.562,169.479,-73.539,169.659,-73.418,169.645,-73.379,169.672,-73.346,169.74,-73.32,169.783,-73.324,169.887,-73.459,169.96,-73.514,169.859,-73.568,169.844,-73.605]],[[164.834,-67.54,164.746,-67.569,164.692,-67.56,164.639,-67.5,164.696,-67.408,164.675,-67.289,164.684,-67.259,164.825,-67.326,164.85,-67.364,164.907,-67.419,164.919,-67.447,164.86,-67.504,164.834,-67.54]],[[163.302,-66.821,163.284,-66.882,163.235,-66.868,163.164,-66.819,163.09,-66.701,163.156,-66.688,163.238,-66.709,163.271,-66.768,163.299,-66.798,163.302,-66.821]],[[162.611,-66.477,162.557,-66.525,162.511,-66.52,162.303,-66.4,162.326,-66.347,162.297,-66.304,162.302,-66.265,162.311,-66.251,162.563,-66.433,162.611,-66.477]],[[96.613,-66.036,96.727,-66.061,96.932,-66.058,97.006,-66.097,97.019,-66.139,97.016,-66.164,96.934,-66.201,96.395,-66.225,96.307,-66.186,96.399,-66.08,96.5,-66.046,96.613,-66.036]],[[100.265,-66.217,100.133,-66.229,100.082,-66.203,100.076,-66.188,100.174,-66.131,100.291,-66.112,100.282,-66.18,100.265,-66.217]],[[98.846,-66.47,98.752,-66.482,98.655,-66.453,98.605,-66.4,98.596,-66.383,98.749,-66.369,98.95,-66.421,98.846,-66.47]],[[103.397,-65.445,103.337,-65.469,103.176,-65.455,103.138,-65.435,103.124,-65.338,103.113,-65.312,103.054,-65.285,102.789,-65.236,102.76,-65.168,102.796,-65.136,102.893,-65.13,103.137,-65.191,103.191,-65.237,103.182,-65.308,103.186,-65.331,103.261,-65.377,103.379,-65.426,103.397,-65.445]],[[92.601,-65.808,92.471,-65.822,92.333,-65.807,92.263,-65.76,92.248,-65.74,92.301,-65.707,92.496,-65.702,92.634,-65.731,92.665,-65.76,92.67,-65.775,92.601,-65.808]],[[85.822,-66.953,85.65,-66.98,85.622,-66.965,85.617,-66.951,85.359,-66.854,85.314,-66.776,85.34,-66.723,85.553,-66.729,85.806,-66.775,85.938,-66.894,85.822,-66.953]],[[86.542,-66.767,86.427,-66.792,86.337,-66.788,86.232,-66.733,86.278,-66.697,86.383,-66.675,86.521,-66.687,86.557,-66.706,86.652,-66.718,86.542,-66.767]],[[85.329,-66.612,85.222,-66.643,85.136,-66.637,85.08,-66.604,85.069,-66.584,85.121,-66.519,85.165,-66.522,85.194,-66.556,85.329,-66.612]],[[69.918,-71.918,69.792,-72.047,69.744,-72.044,69.693,-71.968,69.737,-71.922,69.796,-71.894,69.895,-71.908,69.918,-71.918]],[[68.462,-72.3,68.409,-72.3,68.436,-72.26,68.566,-72.19,68.667,-72.103,68.729,-72.089,68.84,-72.165,68.817,-72.229,68.67,-72.276,68.462,-72.3]],[[72.002,-70.633,71.929,-70.633,71.841,-70.622,71.726,-70.549,71.659,-70.497,71.637,-70.444,71.647,-70.336,71.705,-70.284,71.797,-70.264,71.838,-70.312,71.851,-70.368,71.88,-70.406,72,-70.457,72.056,-70.501,72.073,-70.525,72.097,-70.575,72.078,-70.609,72.002,-70.633]],[[48.546,-66.784,48.378,-66.807,48.304,-66.798,48.296,-66.774,48.294,-66.75,48.301,-66.724,48.358,-66.704,48.638,-66.701,48.751,-66.72,48.782,-66.731,48.786,-66.768,48.775,-66.778,48.546,-66.784]],[[16.223,-70.008,16.159,-70.072,15.845,-69.982,15.663,-69.955,15.614,-69.939,15.571,-69.885,15.563,-69.863,15.597,-69.828,15.699,-69.773,15.91,-69.728,16.247,-69.705,16.573,-69.723,16.625,-69.75,16.315,-69.844,16.223,-70.008]],[[3.037,-70.597,2.698,-70.624,2.623,-70.593,2.585,-70.535,2.631,-70.5,3.072,-70.382,3.193,-70.393,3.231,-70.403,3.26,-70.449,3.221,-70.519,3.171,-70.554,3.037,-70.597]],[[-2.955,-71.214,-3.061,-71.237,-3.201,-71.23,-3.309,-71.201,-3.386,-71.143,-3.404,-71.12,-3.392,-71.081,-3.399,-71.062,-3.263,-71.052,-3.213,-71.076,-3.191,-71.095,-2.955,-71.214]],[[1.299,-70.255,1.212,-70.381,1.156,-70.378,1.105,-70.304,0.99,-70.224,0.953,-70.169,0.95,-70.094,1.027,-70.05,1.315,-70.023,1.412,-70.041,1.461,-70.136,1.299,-70.255]],[[-2.533,-70.768,-2.423,-70.8,-2.256,-70.796,-2.092,-70.821,-2.119,-70.855,-2.213,-70.902,-2.293,-70.998,-2.369,-71.044,-2.607,-71.141,-2.783,-71.167,-2.825,-71.113,-2.822,-71.057,-2.805,-71.015,-2.801,-70.982,-2.963,-70.94,-2.975,-70.883,-3.007,-70.851,-3.489,-70.736,-3.575,-70.703,-3.537,-70.683,-3.04,-70.674,-2.75,-70.694,-2.533,-70.768]],[[4.526,-70.479,4.365,-70.503,4.18,-70.451,4.13,-70.417,4.076,-70.325,4.07,-70.29,4.112,-70.267,4.256,-70.241,4.495,-70.251,4.586,-70.294,4.618,-70.369,4.59,-70.433,4.526,-70.479]],[[-20.607,-73.887,-20.654,-74.105,-20.641,-74.151,-20.6,-74.197,-20.423,-74.317,-20.411,-74.408,-20.417,-74.443,-20.489,-74.493,-20.737,-74.481,-20.818,-74.455,-20.846,-74.438,-20.977,-74.225,-21.051,-74.176,-21.167,-74.133,-21.61,-74.092,-22.035,-74.107,-21.93,-74.057,-21.288,-73.989,-21.126,-73.94,-21.025,-73.88,-20.979,-73.79,-20.867,-73.677,-20.69,-73.625,-20.58,-73.619,-20.521,-73.712,-20.521,-73.798,-20.607,-73.887]],[[-16.104,-72.679,-16.175,-72.703,-16.318,-72.702,-16.453,-72.652,-16.51,-72.582,-16.517,-72.531,-16.455,-72.474,-16.356,-72.459,-16.303,-72.478,-16.173,-72.6,-16.104,-72.679]],[[-12.509,-72.173,-12.588,-72.196,-12.72,-72.188,-12.888,-72.137,-12.944,-72.099,-12.963,-72.064,-12.915,-72.015,-12.875,-72.001,-12.789,-72.007,-12.637,-72.071,-12.535,-72.14,-12.509,-72.173]],[[-32.343,-79.674,-32.515,-79.683,-32.583,-79.658,-32.501,-79.592,-32.377,-79.535,-32.15,-79.53,-31.933,-79.568,-31.957,-79.604,-32.001,-79.607,-32.343,-79.674]],[[-54.071,-61.299,-54.115,-61.308,-54.184,-61.27,-54.192,-61.247,-54.122,-61.202,-54.05,-61.142,-54.024,-61.135,-54.041,-61.255,-54.071,-61.299]],[[-58.838,-62.303,-59.059,-62.348,-59.175,-62.302,-59.202,-62.283,-59.064,-62.239,-58.991,-62.249,-58.962,-62.264,-58.879,-62.268,-58.838,-62.303]],[[-61.158,-69.976,-61.309,-69.978,-61.378,-69.95,-61.404,-69.933,-61.386,-69.893,-61.327,-69.856,-61.152,-69.883,-61.108,-69.955,-61.158,-69.976]],[[-60.656,-68.768,-60.693,-68.795,-60.82,-68.778,-60.894,-68.759,-61.015,-68.71,-60.947,-68.681,-60.814,-68.688,-60.705,-68.722,-60.656,-68.768]],[[-60.741,-70.711,-60.826,-70.711,-60.896,-70.69,-60.958,-70.629,-60.976,-70.599,-60.942,-70.533,-60.884,-70.518,-60.554,-70.509,-60.452,-70.544,-60.449,-70.603,-60.488,-70.647,-60.741,-70.711]],[[-60.552,-71.053,-60.652,-71.059,-60.79,-71.041,-60.906,-71.007,-60.946,-70.967,-60.889,-70.934,-60.783,-70.914,-60.613,-70.92,-60.533,-70.962,-60.516,-71,-60.536,-71.041,-60.552,-71.053]],[[-59.389,-62.444,-59.525,-62.451,-59.619,-62.395,-59.661,-62.354,-59.479,-62.352,-59.396,-62.367,-59.353,-62.413,-59.389,-62.444]],[[-60.505,-62.967,-60.555,-62.978,-60.62,-62.969,-60.618,-62.987,-60.564,-63.009,-60.622,-63.018,-60.693,-62.996,-60.74,-62.949,-60.706,-62.906,-60.637,-62.895,-60.505,-62.967]],[[-60.653,-63.867,-60.778,-63.902,-60.852,-63.891,-60.972,-63.849,-60.81,-63.837,-60.797,-63.717,-60.715,-63.669,-60.562,-63.696,-60.656,-63.759,-60.689,-63.808,-60.655,-63.85,-60.653,-63.867]],[[-61.952,-64.077,-62.044,-64.08,-62.021,-64.027,-61.936,-63.99,-61.798,-63.967,-61.886,-64.027,-61.911,-64.054,-61.952,-64.077]],[[-63.316,-64.861,-63.474,-64.907,-63.558,-64.906,-63.459,-64.796,-63.367,-64.792,-63.219,-64.73,-63.177,-64.739,-63.257,-64.791,-63.316,-64.861]],[[-62.615,-63.069,-62.655,-63.074,-62.639,-63.032,-62.527,-62.924,-62.317,-62.874,-62.344,-62.918,-62.411,-62.972,-62.615,-63.069]],[[-65.845,-65.842,-66.064,-65.881,-66.175,-65.867,-66.181,-65.826,-66.153,-65.774,-66.05,-65.745,-66.067,-65.666,-66,-65.633,-65.968,-65.571,-65.834,-65.527,-65.637,-65.548,-65.668,-65.626,-65.67,-65.653,-65.784,-65.674,-65.814,-65.687,-65.841,-65.738,-65.836,-65.814,-65.845,-65.842]],[[-66.595,-66.201,-66.819,-66.313,-66.85,-66.305,-66.868,-66.294,-66.867,-66.275,-66.792,-66.234,-66.779,-66.111,-66.631,-66.067,-66.575,-66.082,-66.623,-66.134,-66.593,-66.179,-66.595,-66.201]],[[-67.362,-66.895,-67.409,-66.902,-67.521,-66.897,-67.593,-66.876,-67.5,-66.804,-67.511,-66.756,-67.426,-66.737,-67.332,-66.754,-67.269,-66.815,-67.257,-66.841,-67.362,-66.895]],[[-67.349,-67.766,-67.545,-67.785,-67.693,-67.763,-67.69,-67.688,-67.731,-67.679,-67.743,-67.661,-67.557,-67.604,-67.418,-67.591,-67.247,-67.599,-67.175,-67.625,-67.149,-67.65,-67.28,-67.712,-67.3,-67.737,-67.349,-67.766]],[[-71.695,-70.265,-71.648,-70.295,-71.432,-70.267,-71.355,-70.298,-71.34,-70.317,-71.438,-70.392,-71.551,-70.439,-71.685,-70.442,-71.782,-70.319,-71.795,-70.288,-71.695,-70.265]],[[-73.878,-73.357,-73.975,-73.376,-74.038,-73.366,-74.147,-73.315,-74.134,-73.277,-74.084,-73.249,-74.049,-73.22,-73.832,-73.113,-73.674,-73.1,-73.542,-73.124,-73.682,-73.225,-73.721,-73.296,-73.878,-73.357]],[[-93.796,-72.92,-93.966,-72.92,-94.078,-72.884,-94.113,-72.86,-94.047,-72.823,-94.004,-72.82,-93.8,-72.882,-93.756,-72.908,-93.796,-72.92]],[[-116.739,-74.165,-117.23,-74.193,-117.363,-74.161,-117.398,-74.122,-117.376,-74.083,-116.381,-73.866,-116.203,-73.896,-116.155,-73.91,-116.451,-74.018,-116.585,-74.056,-116.609,-74.069,-116.534,-74.083,-116.514,-74.096,-116.571,-74.126,-116.739,-74.165]],[[-132.391,-74.442,-132.546,-74.498,-132.857,-74.462,-132.832,-74.422,-132.552,-74.387,-132.362,-74.41,-132.391,-74.442]],[[-131.067,-74.584,-131.179,-74.605,-131.598,-74.554,-131.841,-74.543,-131.952,-74.514,-132.025,-74.489,-132.049,-74.464,-132.163,-74.426,-131.938,-74.349,-131.763,-74.324,-131.594,-74.33,-131.56,-74.367,-131.234,-74.414,-130.981,-74.414,-130.957,-74.456,-130.967,-74.515,-131.067,-74.584]],[[-147.588,-76.65,-147.579,-76.663,-147.73,-76.653,-147.954,-76.597,-148.001,-76.577,-147.9,-76.558,-147.77,-76.577,-147.649,-76.611,-147.588,-76.65]],[[-145.238,-75.711,-145.348,-75.716,-145.541,-75.693,-146.043,-75.612,-146.151,-75.574,-146.076,-75.533,-145.896,-75.505,-145.761,-75.514,-145.417,-75.588,-145.315,-75.641,-145.252,-75.683,-145.238,-75.711]],[[-146.607,-76.961,-146.981,-77.006,-147.079,-76.993,-147.044,-76.93,-147.101,-76.887,-147.116,-76.866,-147.087,-76.837,-146.867,-76.837,-146.244,-76.883,-146.164,-76.949,-146.607,-76.961]],[[-148.596,-77.007,-149.015,-77.019,-149.245,-76.993,-149.303,-76.916,-149.238,-76.9,-148.704,-76.936,-148.509,-76.955,-148.44,-76.977,-148.474,-76.998,-148.596,-77.007]],[[-149.333,-76.717,-148.928,-76.73,-148.663,-76.721,-148.384,-76.744,-148.321,-76.772,-148.371,-76.795,-148.67,-76.802,-148.815,-76.841,-148.984,-76.845,-149.238,-76.818,-149.469,-76.757,-149.333,-76.717]],[[-149.218,-77.336,-148.929,-77.387,-149.439,-77.371,-149.662,-77.301,-149.519,-77.275,-149.375,-77.28,-149.249,-77.315,-149.218,-77.336]],[[-150.233,-76.776,-150.655,-76.789,-150.83,-76.762,-150.874,-76.737,-150.838,-76.714,-150.177,-76.691,-150.104,-76.719,-150.085,-76.735,-150.233,-76.776]],[[-146.69,-76.246,-146.894,-76.261,-147.151,-76.197,-147.345,-76.147,-147.408,-76.105,-147.421,-76.09,-147.418,-76.073,-147.361,-76.063,-146.949,-76.098,-146.69,-76.246]],[[-146.79,-76.633,-146.908,-76.714,-147.221,-76.671,-147.355,-76.619,-147.279,-76.553,-147.135,-76.532,-146.947,-76.555,-146.878,-76.563,-146.79,-76.633]],[[-150.397,-77.369,-150.475,-77.374,-151.344,-77.296,-151.512,-77.273,-151.218,-77.226,-151.022,-77.22,-150.499,-77.335,-150.356,-77.349,-150.397,-77.369]],[[-153.93,-80.033,-154.114,-80.036,-154.349,-80.026,-154.529,-80,-154.942,-79.966,-155.045,-79.9,-155.525,-79.846,-155.751,-79.83,-155.674,-79.766,-155.162,-79.851,-154.535,-79.936,-154.025,-79.988,-153.93,-80.033]],[[-149.231,-77.121,-149.293,-77.137,-149.729,-77.129,-149.817,-77.114,-149.856,-77.099,-150.462,-77.076,-150.736,-77.004,-150.789,-76.982,-150.68,-76.948,-150.476,-76.926,-150.393,-76.899,-149.871,-76.875,-149.79,-76.889,-149.743,-76.927,-149.506,-77.002,-149.442,-77.049,-149.416,-77.079,-149.288,-77.093,-149.231,-77.121]],[[-157.987,-82.105,-158.076,-82.112,-158.154,-82.058,-158.545,-81.949,-158.773,-81.875,-158.926,-81.819,-158.989,-81.779,-158.914,-81.78,-158.347,-81.9,-158.261,-81.947,-157.835,-82.031,-157.987,-82.105]]]},{"n":"Sint Maarten","c":"SX","r":6,"ml":5,"lx":-63.07,"ly":18.04,"b":[-63.125,18.019,-63.011,18.069],"p":[[[-63.123,18.069,-63.011,18.069,-63.012,18.045,-63.023,18.019,-63.09,18.041,-63.125,18.064,-63.123,18.069]]]},{"n":"Tuvalu","c":"TV","r":6,"ml":5,"lx":179.21,"ly":-8.51,"b":[179.196,-8.535,179.217,-8.466],"p":[[[179.214,-8.524,179.201,-8.535,179.196,-8.535,179.201,-8.512,179.198,-8.489,179.199,-8.47,179.203,-8.466,179.212,-8.488,179.217,-8.515,179.214,-8.524]]]}]} \ No newline at end of file diff --git a/src/maps/data/lakes_50m.json b/src/maps/data/lakes_50m.json new file mode 100644 index 0000000..9f59147 --- /dev/null +++ b/src/maps/data/lakes_50m.json @@ -0,0 +1 @@ +{"f":[{"n":"Mälaren","z":2,"b":[16.044,59.268,17.98,59.621],"p":[[[17.98,59.329,17.876,59.271,17.571,59.268,17.475,59.292,17.371,59.295,17.305,59.272,17.175,59.356,17.066,59.373,16.914,59.446,16.742,59.431,16.61,59.454,16.144,59.448,16.044,59.478,16.252,59.493,16.473,59.519,16.574,59.612,16.647,59.559,16.752,59.543,16.841,59.588,16.978,59.551,17.063,59.569,17.372,59.496,17.391,59.584,17.534,59.539,17.687,59.542,17.672,59.595,17.76,59.621,17.786,59.598,17.809,59.553,17.773,59.414,17.829,59.379,17.964,59.359,17.98,59.329]]]},{"n":"Ladoga","z":1,"b":[29.825,59.907,32.944,61.775],"p":[[[29.837,61.226,29.86,61.239,29.865,61.251,29.855,61.271,29.891,61.295,29.949,61.289,30.029,61.254,30.079,61.243,30.094,61.259,30.089,61.271,30.09,61.299,30.13,61.313,30.174,61.314,30.193,61.327,30.196,61.348,30.183,61.361,30.179,61.373,30.201,61.389,30.21,61.418,30.217,61.431,30.237,61.446,30.252,61.459,30.252,61.473,30.235,61.493,30.239,61.515,30.285,61.517,30.466,61.487,30.481,61.49,30.486,61.515,30.513,61.525,30.551,61.535,30.606,61.541,30.617,61.56,30.593,61.579,30.594,61.606,30.758,61.733,30.852,61.775,30.888,61.765,30.896,61.754,30.906,61.742,31.026,61.714,31.057,61.689,31.071,61.645,31.098,61.62,31.161,61.619,31.172,61.63,31.238,61.643,31.258,61.641,31.286,61.615,31.308,61.605,31.328,61.617,31.334,61.627,31.365,61.627,31.486,61.551,31.533,61.497,31.576,61.437,31.604,61.423,31.62,61.427,31.617,61.442,31.62,61.461,31.637,61.463,31.698,61.415,31.762,61.384,31.871,61.347,31.899,61.324,32.36,61.181,32.527,61.118,32.563,61.079,32.581,61.045,32.581,61.014,32.593,60.998,32.633,60.987,32.641,60.969,32.765,60.886,32.808,60.826,32.812,60.756,32.844,60.712,32.903,60.694,32.937,60.67,32.944,60.643,32.907,60.586,32.841,60.497,32.816,60.482,32.782,60.488,32.722,60.521,32.642,60.537,32.6,60.533,32.596,60.511,32.618,60.476,32.664,60.427,32.677,60.392,32.656,60.372,32.595,60.355,32.59,60.33,32.614,60.286,32.612,60.245,32.584,60.209,32.5,60.177,32.362,60.148,32.26,60.154,32.196,60.195,32.057,60.227,31.843,60.248,31.699,60.236,31.599,60.17,31.591,60.156,31.597,60.144,31.606,60.142,31.606,60.134,31.548,60.072,31.541,60.035,31.565,59.998,31.567,59.967,31.547,59.94,31.51,59.92,31.455,59.907,31.401,59.911,31.349,59.933,31.274,59.938,31.176,59.926,31.106,59.928,31.064,59.943,31.043,59.951,31.063,59.963,31.145,60.006,31.143,60.032,31.106,60.056,31.095,60.084,31.115,60.135,31.109,60.146,31.019,60.193,30.957,60.264,30.849,60.443,30.832,60.458,30.817,60.459,30.803,60.447,30.776,60.448,30.774,60.492,30.759,60.525,30.724,60.562,30.663,60.593,30.534,60.63,30.524,60.655,30.504,60.704,30.504,60.732,30.525,60.739,30.533,60.766,30.528,60.812,30.502,60.843,30.456,60.86,30.441,60.874,30.436,60.903,30.409,60.928,30.293,61.005,30.125,61.087,29.857,61.188,29.825,61.216,29.837,61.226]]]},{"n":"Albert","z":1,"b":[30.387,1.021,31.475,2.401],"p":[[[31.472,2.385,31.396,2.28,31.384,2.261,31.387,2.2,31.4,1.896,31.387,1.857,31.369,1.836,31.343,1.834,31.322,1.803,31.305,1.742,31.268,1.688,31.166,1.605,31.129,1.588,31.04,1.564,31.014,1.546,30.969,1.527,30.893,1.448,30.785,1.309,30.727,1.224,30.719,1.191,30.594,1.041,30.569,1.029,30.539,1.021,30.507,1.041,30.501,1.05,30.504,1.121,30.492,1.173,30.492,1.203,30.502,1.212,30.495,1.23,30.478,1.239,30.468,1.258,30.45,1.259,30.414,1.255,30.4,1.256,30.387,1.297,30.441,1.382,30.479,1.467,30.529,1.532,30.591,1.579,30.674,1.676,30.714,1.702,30.868,1.859,30.936,1.915,30.982,1.932,31.179,2.073,31.259,2.153,31.303,2.226,31.344,2.291,31.431,2.388,31.475,2.401,31.472,2.385]]]},{"n":"Erie","z":1,"b":[-83.456,41.398,-78.872,43.106],"p":[[[-83.105,42.286,-83.076,42.275,-83.105,42.215,-83.117,42.12,-83.108,42.071,-83.061,42.05,-82.937,42.006,-82.834,41.997,-82.69,42.036,-82.645,42.037,-82.604,42.025,-82.567,42,-82.512,41.932,-82.496,41.944,-82.49,42.005,-82.462,42.058,-82.412,42.103,-82.311,42.158,-82.16,42.223,-82.044,42.257,-81.938,42.269,-81.929,42.279,-81.874,42.315,-81.866,42.308,-81.887,42.274,-81.887,42.257,-81.867,42.258,-81.841,42.295,-81.809,42.368,-81.73,42.447,-81.495,42.591,-81.404,42.63,-81.252,42.654,-81.039,42.662,-80.827,42.643,-80.617,42.596,-80.473,42.574,-80.397,42.577,-80.094,42.546,-80.062,42.554,-80.105,42.572,-80.305,42.59,-80.368,42.608,-80.446,42.604,-80.451,42.613,-80.396,42.676,-80.365,42.683,-80.326,42.707,-80.286,42.738,-80.22,42.774,-80.099,42.802,-79.923,42.822,-79.747,42.858,-79.699,42.862,-79.661,42.851,-79.594,42.854,-79.386,42.881,-79.264,42.88,-79.176,42.873,-79.122,42.858,-79.057,42.862,-78.936,42.907,-78.922,42.924,-78.947,42.952,-79.011,42.991,-79.04,43.021,-79.035,43.044,-79.043,43.064,-79.073,43.093,-79.066,43.106,-78.908,43.07,-78.895,43.058,-78.9,43.033,-78.924,42.998,-78.92,42.948,-78.886,42.886,-78.872,42.836,-78.878,42.799,-78.918,42.764,-78.994,42.731,-79.063,42.68,-79.125,42.611,-79.209,42.555,-79.315,42.512,-79.416,42.457,-79.512,42.391,-79.704,42.3,-80.076,42.146,-80.125,42.128,-80.168,42.109,-80.252,42.075,-80.334,42.041,-80.869,41.893,-81.229,41.77,-81.447,41.671,-81.637,41.548,-81.723,41.511,-81.817,41.501,-81.966,41.508,-82.121,41.493,-82.282,41.457,-82.387,41.427,-82.434,41.405,-82.48,41.398,-82.524,41.407,-82.643,41.467,-82.651,41.462,-82.673,41.454,-82.743,41.464,-82.822,41.465,-82.891,41.443,-82.96,41.44,-83.027,41.455,-82.971,41.476,-82.7,41.521,-82.703,41.529,-82.801,41.556,-82.831,41.58,-82.856,41.578,-82.875,41.55,-82.911,41.538,-82.963,41.542,-83.016,41.563,-83.069,41.601,-83.243,41.662,-83.291,41.693,-83.352,41.714,-83.443,41.725,-83.456,41.742,-83.441,41.766,-83.362,41.88,-83.316,41.923,-83.297,41.935,-83.273,41.935,-83.262,41.946,-83.265,41.971,-83.256,41.986,-83.205,42.027,-83.177,42.049,-83.188,42.083,-83.175,42.146,-83.142,42.228,-83.105,42.286],[-78.99,42.992,-78.961,42.988,-78.93,43.022,-78.914,43.042,-78.916,43.053,-78.988,43.064,-79.014,43.052,-79.019,43.024,-79.012,43.003,-78.99,42.992],[-82.638,41.747,-82.627,41.771,-82.643,41.815,-82.654,41.827,-82.674,41.806,-82.685,41.757,-82.685,41.736,-82.638,41.747]]]},{"n":"Chad","z":1,"b":[14.259,12.877,14.743,13.239],"p":[[[14.742,13.078,14.743,13.062,14.716,13.047,14.694,13.024,14.686,12.993,14.661,12.982,14.62,12.992,14.592,12.99,14.516,12.98,14.475,12.952,14.407,12.899,14.347,12.877,14.295,12.884,14.275,12.903,14.259,12.986,14.259,13.004,14.282,13.014,14.289,13.029,14.307,13.065,14.311,13.082,14.312,13.087,14.33,13.116,14.33,13.135,14.342,13.162,14.37,13.185,14.415,13.209,14.496,13.239,14.554,13.238,14.591,13.214,14.653,13.208,14.668,13.195,14.707,13.137,14.742,13.078]]]},{"n":"Malawi","z":1,"b":[33.905,-14.403,35.284,-9.495],"p":[[[35.26,-14.277,35.284,-14.365,35.273,-14.391,35.258,-14.403,35.236,-14.401,35.193,-14.353,35.13,-14.289,35.048,-14.177,35.014,-14.157,34.958,-14.148,34.947,-14.134,34.942,-14.071,34.93,-14.042,34.906,-14.021,34.881,-14.012,34.857,-14.014,34.849,-14.035,34.858,-14.074,34.85,-14.112,34.825,-14.15,34.816,-14.178,34.825,-14.197,34.819,-14.217,34.77,-14.254,34.729,-14.268,34.707,-14.262,34.701,-14.237,34.678,-14.214,34.637,-14.195,34.6,-14.159,34.567,-14.106,34.547,-14.048,34.538,-13.985,34.546,-13.938,34.571,-13.908,34.596,-13.846,34.622,-13.751,34.605,-13.669,34.547,-13.6,34.498,-13.567,34.458,-13.572,34.41,-13.53,34.321,-13.379,34.313,-13.346,34.319,-13.308,34.338,-13.265,34.344,-13.158,34.33,-12.945,34.32,-12.921,34.299,-12.857,34.275,-12.825,34.267,-12.791,34.273,-12.756,34.263,-12.73,34.236,-12.713,34.214,-12.684,34.196,-12.642,34.197,-12.481,34.18,-12.425,34.15,-12.393,34.107,-12.384,34.074,-12.353,34.037,-12.25,34.033,-12.209,34.059,-12.049,34.1,-11.976,34.164,-11.925,34.194,-11.88,34.192,-11.84,34.222,-11.788,34.318,-11.679,34.323,-11.653,34.289,-11.425,34.262,-11.312,34.23,-11.228,34.224,-11.167,34.241,-11.131,34.245,-11.099,34.234,-11.073,34.203,-10.614,34.19,-10.59,34.195,-10.567,34.218,-10.544,34.229,-10.517,34.227,-10.485,34.238,-10.461,34.26,-10.448,34.248,-10.4,34.204,-10.319,34.155,-10.256,34.057,-10.157,34.016,-10.098,33.985,-10.024,33.941,-9.874,33.919,-9.843,33.907,-9.802,33.905,-9.751,33.916,-9.712,33.954,-9.658,33.959,-9.627,33.95,-9.565,33.962,-9.532,33.996,-9.495,34.089,-9.538,34.321,-9.732,34.328,-9.757,34.476,-9.949,34.524,-10.03,34.524,-10.073,34.57,-10.241,34.58,-10.32,34.57,-10.38,34.572,-10.428,34.59,-10.496,34.584,-10.525,34.637,-10.626,34.662,-10.71,34.667,-10.792,34.652,-10.873,34.606,-10.99,34.598,-11.037,34.608,-11.08,34.638,-11.127,34.688,-11.177,34.726,-11.238,34.752,-11.309,34.774,-11.342,34.801,-11.341,34.851,-11.352,34.891,-11.394,34.937,-11.463,34.959,-11.577,34.957,-11.735,34.934,-11.871,34.891,-11.985,34.857,-12.048,34.832,-12.058,34.788,-12.094,34.726,-12.157,34.7,-12.2,34.711,-12.224,34.714,-12.279,34.711,-12.364,34.702,-12.403,34.694,-12.422,34.791,-12.643,34.808,-12.767,34.772,-12.87,34.77,-12.963,34.8,-13.044,34.81,-13.142,34.8,-13.258,34.812,-13.332,34.846,-13.366,34.862,-13.46,34.859,-13.616,34.868,-13.701,34.888,-13.717,35.03,-13.73,35.056,-13.743,35.097,-13.825,35.18,-14.064,35.178,-14.088,35.26,-14.277],[34.622,-12.067,34.642,-12.014,34.624,-11.985,34.591,-11.971,34.554,-11.982,34.542,-12.019,34.58,-12.066,34.622,-12.067],[34.662,-12.101,34.684,-12.119,34.719,-12.111,34.746,-12.088,34.756,-12.059,34.756,-12.031,34.739,-12.013,34.715,-12.003,34.68,-12.009,34.667,-12.048,34.662,-12.101]]]},{"n":"Nyanza","z":1,"b":[31.664,-2.985,34.813,0.472],"p":[[[34.718,-0.092,34.813,-0.276,34.798,-0.296,34.703,-0.325,34.577,-0.33,34.478,-0.353,34.458,-0.369,34.475,-0.464,34.456,-0.495,34.425,-0.502,34.376,-0.459,34.329,-0.459,34.275,-0.445,34.217,-0.442,34.175,-0.47,34.079,-0.579,34.06,-0.645,34.068,-0.725,34.106,-0.798,34.163,-0.879,34.144,-0.94,34.121,-0.975,34.07,-0.997,34.063,-1.034,33.932,-1.188,33.903,-1.247,33.907,-1.278,33.952,-1.321,33.942,-1.34,33.844,-1.342,33.829,-1.363,33.829,-1.39,33.816,-1.406,33.822,-1.425,33.915,-1.495,33.921,-1.514,33.889,-1.513,33.791,-1.485,33.73,-1.48,33.686,-1.488,33.668,-1.506,33.646,-1.571,33.667,-1.604,33.675,-1.638,33.602,-1.691,33.593,-1.761,33.546,-1.8,33.489,-1.822,33.402,-1.812,33.366,-1.83,33.331,-1.864,33.298,-1.922,33.45,-1.915,33.478,-1.928,33.491,-1.953,33.521,-1.98,33.524,-1.996,33.412,-1.997,33.374,-2.032,33.3,-2.023,33.233,-2.072,33.226,-2.096,33.276,-2.124,33.517,-2.162,33.585,-2.168,33.753,-2.111,33.794,-2.123,33.81,-2.157,33.809,-2.192,33.767,-2.238,33.594,-2.358,33.51,-2.43,33.432,-2.46,33.442,-2.502,33.437,-2.522,33.416,-2.531,33.21,-2.489,33.067,-2.41,32.999,-2.402,32.945,-2.421,32.908,-2.449,32.892,-2.484,32.884,-2.664,32.947,-2.818,32.963,-2.849,32.892,-2.862,32.78,-2.971,32.76,-2.985,32.773,-2.91,32.764,-2.867,32.848,-2.797,32.862,-2.757,32.814,-2.647,32.824,-2.544,32.811,-2.515,32.751,-2.514,32.703,-2.502,32.685,-2.491,32.671,-2.456,32.644,-2.439,32.548,-2.508,32.535,-2.494,32.535,-2.462,32.521,-2.454,32.44,-2.444,32.392,-2.422,32.357,-2.394,32.32,-2.304,32.283,-2.282,32.24,-2.283,32.211,-2.31,32.174,-2.366,32.114,-2.377,32.096,-2.394,32.141,-2.486,32.15,-2.525,32.13,-2.537,32.031,-2.516,31.986,-2.547,31.969,-2.608,31.979,-2.677,32.003,-2.757,31.971,-2.752,31.918,-2.674,31.898,-2.681,31.885,-2.7,31.848,-2.705,31.819,-2.798,31.801,-2.791,31.784,-2.75,31.782,-2.693,31.815,-2.621,31.77,-2.597,31.743,-2.567,31.735,-2.53,31.786,-2.465,31.781,-2.44,31.767,-2.399,31.739,-2.357,31.702,-2.328,31.675,-2.277,31.675,-2.08,31.664,-2.016,31.664,-1.932,31.763,-1.492,31.811,-1.419,31.826,-1.359,31.865,-1.092,31.861,-1.044,31.849,-1.017,31.829,-1.013,31.804,-1.022,31.783,-1.01,31.768,-0.936,31.756,-0.888,31.732,-0.837,31.723,-0.79,31.818,-0.568,31.867,-0.481,31.994,-0.321,32.001,-0.255,31.976,-0.212,31.94,-0.18,31.933,-0.158,31.95,-0.132,32.033,-0.09,32.055,-0.057,32.159,-0.04,32.209,0.009,32.258,-0.005,32.287,0.021,32.351,0.015,32.384,0.066,32.413,0.089,32.438,0.093,32.449,0.056,32.486,0.053,32.533,0.07,32.564,0.099,32.588,0.193,32.615,0.235,32.647,0.253,32.681,0.189,32.711,0.179,32.676,0.137,32.671,0.117,32.692,0.108,32.735,0.108,32.779,0.119,32.853,0.169,32.879,0.169,32.895,0.112,32.915,0.096,32.958,0.108,33.021,0.147,33.07,0.193,33.076,0.239,33.137,0.252,33.201,0.388,33.187,0.428,33.245,0.451,33.273,0.463,33.312,0.451,33.364,0.472,33.368,0.453,33.332,0.369,33.354,0.349,33.461,0.334,33.484,0.315,33.485,0.293,33.441,0.271,33.448,0.244,33.483,0.218,33.533,0.207,33.58,0.222,33.64,0.3,33.68,0.316,33.713,0.308,33.728,0.23,33.745,0.208,33.861,0.185,33.945,0.218,33.975,0.213,33.974,0.089,33.982,0.025,34.003,-0.024,34.029,-0.062,34.059,-0.081,34.115,-0.079,34.125,-0.091,34.108,-0.133,34.117,-0.163,34.226,-0.259,34.285,-0.35,34.31,-0.356,34.334,-0.337,34.353,-0.314,34.37,-0.243,34.42,-0.196,34.538,-0.144,34.718,-0.092],[32.054,-2.328,32.06,-2.265,32.045,-2.243,31.975,-2.266,31.9,-2.284,31.876,-2.264,31.84,-2.216,31.818,-2.192,31.791,-2.234,31.783,-2.321,31.843,-2.381,31.848,-2.454,31.9,-2.443,31.917,-2.372,31.966,-2.366,32.038,-2.351,32.054,-2.328],[32.298,-0.392,32.319,-0.337,32.269,-0.29,32.194,-0.332,32.179,-0.311,32.182,-0.235,32.147,-0.232,32.086,-0.231,32.065,-0.254,32.081,-0.29,32.104,-0.322,32.13,-0.337,32.208,-0.363,32.205,-0.397,32.166,-0.442,32.145,-0.487,32.132,-0.525,32.272,-0.472,32.298,-0.392],[33.175,-2.159,33.183,-2.116,33.14,-2.069,33.12,-2.011,33.097,-1.965,33.027,-1.973,32.972,-1.973,32.949,-1.902,32.884,-1.904,32.83,-1.937,32.847,-1.979,32.851,-2.061,32.899,-2.085,32.989,-2.105,33.059,-2.135,33.125,-2.158,33.175,-2.159],[33.372,0.216,33.388,0.272,33.318,0.298,33.224,0.277,33.221,0.233,33.218,0.19,33.18,0.175,33.184,0.15,33.264,0.144,33.273,0.121,33.339,0.08,33.325,0.142,33.317,0.184,33.372,0.216]]]},{"n":"Baikal","z":1,"b":[103.713,51.482,109.955,55.779],"p":[[[109.864,55.539,109.857,55.446,109.825,55.402,109.791,55.33,109.781,55.28,109.75,55.185,109.744,55.14,109.768,55.083,109.765,55.04,109.716,54.932,109.702,54.902,109.684,54.85,109.646,54.807,109.633,54.772,109.607,54.676,109.544,54.629,109.523,54.559,109.49,54.38,109.496,54.262,109.554,54.174,109.556,54.154,109.538,54.116,109.5,54.061,109.426,53.998,109.244,53.869,109.211,53.828,109.198,53.793,109.204,53.737,109.204,53.679,109.199,53.66,109.178,53.638,109.03,53.639,109.009,53.668,109.034,53.76,109.061,53.804,109.061,53.831,109.033,53.858,108.994,53.869,108.944,53.862,108.891,53.842,108.836,53.807,108.619,53.582,108.532,53.513,108.545,53.508,108.646,53.548,108.732,53.571,108.842,53.587,108.921,53.566,108.968,53.508,108.989,53.45,108.982,53.393,108.931,53.344,108.768,53.281,108.729,53.278,108.703,53.283,108.676,53.304,108.661,53.302,108.392,53.163,108.302,53.109,108.297,53.093,108.33,53.056,108.335,53.038,108.288,52.989,108.234,52.946,108.007,52.837,107.948,52.78,107.763,52.714,107.674,52.701,107.579,52.701,107.387,52.648,107.099,52.542,106.938,52.475,106.905,52.447,106.843,52.356,106.806,52.329,106.752,52.317,106.693,52.338,106.599,52.371,106.497,52.383,106.39,52.355,106.314,52.323,106.269,52.282,106.258,52.231,106.281,52.169,106.261,52.103,106.187,51.956,106.134,51.916,106.065,51.863,105.967,51.774,105.798,51.701,105.435,51.614,105.065,51.557,104.903,51.522,104.826,51.49,104.655,51.482,104.39,51.5,104.156,51.535,103.817,51.634,103.745,51.669,103.713,51.694,103.723,51.711,103.743,51.722,103.791,51.732,104.133,51.769,104.343,51.783,104.537,51.784,104.663,51.806,104.721,51.849,104.812,51.87,104.867,51.87,104.935,51.87,105.053,51.882,105.165,51.906,105.243,51.938,105.286,51.979,105.345,52.009,105.419,52.03,105.57,52.108,105.692,52.216,105.86,52.387,106.06,52.512,106.292,52.592,106.452,52.656,106.537,52.706,106.579,52.742,106.702,52.832,106.757,52.869,106.783,52.906,106.881,52.973,106.897,53.003,106.873,53.029,106.774,53.022,106.78,53.045,106.852,53.103,107.072,53.241,107.232,53.316,107.535,53.439,107.547,53.453,107.551,53.502,107.576,53.548,107.627,53.611,107.789,53.711,108.062,53.849,108.224,53.976,108.275,54.036,108.298,54.078,108.362,54.173,108.408,54.21,108.473,54.277,108.519,54.35,108.546,54.429,108.593,54.492,108.723,54.601,108.786,54.672,108.827,54.739,108.847,54.8,108.882,54.853,108.933,54.898,108.944,54.931,108.917,54.951,108.953,54.983,109.053,55.028,109.11,55.099,109.171,55.194,109.198,55.261,109.213,55.405,109.191,55.464,109.193,55.536,109.239,55.575,109.313,55.608,109.507,55.731,109.576,55.771,109.667,55.779,109.76,55.774,109.847,55.755,109.955,55.672,109.861,55.575,109.864,55.539],[107.767,53.267,107.795,53.393,107.744,53.399,107.584,53.283,107.353,53.21,107.091,53.133,106.995,53.107,106.96,53.078,106.963,53.023,107.314,53.068,107.438,53.103,107.639,53.22,107.767,53.267]]]},{"n":"Winnipeg","z":1,"b":[-99.24,50.398,-96.288,54.413],"p":[[[-96.373,50.729,-96.359,50.691,-96.294,50.624,-96.392,50.629,-96.452,50.656,-96.542,50.728,-96.566,50.695,-96.588,50.664,-96.594,50.599,-96.608,50.552,-96.576,50.468,-96.596,50.442,-96.683,50.398,-96.761,50.398,-96.866,50.418,-96.931,50.461,-96.962,50.57,-96.976,50.644,-96.983,50.683,-96.956,50.797,-96.954,50.844,-96.967,50.927,-96.938,51.06,-96.927,51.082,-96.841,51.128,-96.832,51.157,-96.774,51.221,-96.693,51.255,-96.627,51.324,-96.631,51.343,-96.708,51.364,-96.725,51.366,-96.908,51.206,-96.937,51.193,-96.979,51.204,-96.982,51.251,-96.914,51.331,-96.742,51.554,-96.736,51.593,-96.751,51.612,-96.785,51.611,-96.797,51.626,-96.817,51.676,-96.826,51.71,-96.844,51.722,-96.881,51.724,-97.065,51.665,-97.106,51.642,-97.127,51.608,-97.124,51.536,-97.141,51.502,-97.22,51.45,-97.253,51.444,-97.276,51.491,-97.289,51.593,-97.277,51.685,-97.24,51.766,-97.25,51.865,-97.307,51.981,-97.357,52.04,-97.446,52.027,-97.496,52.001,-97.55,51.925,-97.573,51.924,-97.6,51.988,-97.526,52.061,-97.495,52.108,-97.497,52.133,-97.531,52.137,-97.68,52.138,-97.743,52.135,-97.774,52.109,-97.729,52.088,-97.735,52.045,-97.792,51.914,-97.823,51.889,-97.963,51.927,-98.036,51.981,-98.045,52.052,-98.071,52.1,-98.139,52.162,-98.143,52.211,-98.188,52.28,-98.429,52.382,-98.454,52.416,-98.469,52.486,-98.553,52.556,-98.809,52.725,-98.826,52.767,-98.82,52.783,-98.896,52.856,-98.904,52.901,-98.895,52.916,-98.773,52.965,-98.501,53.018,-98.46,53.044,-98.525,53.065,-98.696,53.082,-98.796,53.076,-98.903,53.06,-98.99,53.048,-99.05,53.073,-99.186,53.175,-99.23,53.185,-99.24,53.21,-99.202,53.353,-99.085,53.48,-99.042,53.557,-98.994,53.677,-98.983,53.718,-99.001,53.754,-98.929,53.867,-98.811,53.897,-98.731,53.877,-98.613,53.843,-98.456,53.852,-98.299,53.834,-98.009,53.71,-97.963,53.695,-97.91,53.7,-97.9,53.738,-98.076,53.823,-98.204,53.874,-98.261,53.912,-98.299,53.965,-98.297,54.018,-98.254,54.069,-98.174,54.218,-98.021,54.41,-98.003,54.413,-97.932,54.375,-97.932,54.353,-97.99,54.322,-97.983,54.316,-97.844,54.317,-97.82,54.3,-97.864,54.265,-98.083,54.202,-98.172,54.079,-98.213,54.025,-98.192,53.994,-98.061,53.931,-98.046,53.94,-98.033,53.977,-98.001,54.002,-97.94,54.029,-97.811,54.042,-97.734,54.081,-97.729,54.06,-97.762,54.013,-97.972,53.98,-97.977,53.969,-97.938,53.89,-97.832,53.736,-97.82,53.695,-97.828,53.641,-97.791,53.581,-97.707,53.515,-97.652,53.4,-97.582,53.291,-97.475,53.201,-97.437,53.119,-97.352,53.026,-97.347,53.006,-97.376,52.98,-97.424,52.965,-97.398,52.879,-97.333,52.784,-97.198,52.673,-97.112,52.478,-97.071,52.413,-97.002,52.269,-97.007,52.257,-97.019,52.204,-96.986,52.133,-96.958,52.077,-96.859,51.993,-96.835,51.942,-96.854,51.892,-96.844,51.866,-96.769,51.855,-96.707,51.815,-96.689,51.791,-96.706,51.773,-96.757,51.763,-96.722,51.704,-96.601,51.596,-96.551,51.53,-96.546,51.476,-96.495,51.413,-96.344,51.309,-96.29,51.251,-96.288,51.208,-96.337,51.179,-96.349,51.153,-96.394,51.088,-96.403,51.02,-96.367,50.857,-96.357,50.76,-96.373,50.729],[-97.938,52.318,-97.887,52.358,-97.944,52.531,-97.995,52.542,-98.046,52.46,-98.043,52.398,-97.978,52.315,-97.938,52.318],[-96.632,51.148,-96.598,51.188,-96.709,51.199,-96.737,51.137,-96.782,51.092,-96.842,51.066,-96.845,51.027,-96.762,51.01,-96.68,51.041,-96.632,51.148]]]},{"n":"Great Slave","z":1,"b":[-117.668,60.838,-108.919,62.952],"p":[[[-109.022,62.807,-108.919,62.788,-108.928,62.768,-108.952,62.752,-108.993,62.739,-108.987,62.712,-109.006,62.69,-109.252,62.597,-109.292,62.603,-109.263,62.657,-109.301,62.717,-109.396,62.739,-109.691,62.777,-109.747,62.798,-109.944,62.814,-110.284,62.827,-110.469,62.828,-110.498,62.819,-110.603,62.832,-110.662,62.829,-110.701,62.81,-110.756,62.763,-110.967,62.727,-111.269,62.661,-111.294,62.634,-111.272,62.617,-110.836,62.666,-110.536,62.66,-110.514,62.67,-110.411,62.685,-109.864,62.673,-109.768,62.668,-109.735,62.65,-109.836,62.607,-109.958,62.576,-110.101,62.557,-110.187,62.554,-110.263,62.571,-110.33,62.569,-110.455,62.542,-110.554,62.538,-110.764,62.546,-110.78,62.538,-110.683,62.516,-110.647,62.482,-110.684,62.42,-110.705,62.395,-110.75,62.387,-110.95,62.403,-111.06,62.381,-111.1,62.357,-111.236,62.302,-111.294,62.281,-111.302,62.258,-111.307,62.233,-111.388,62.192,-111.587,62.129,-111.591,62.098,-111.634,62.071,-111.638,62.059,-111.509,62.065,-111.502,62.049,-111.62,61.95,-111.843,61.857,-112.09,61.736,-112.204,61.645,-112.208,61.624,-112.329,61.577,-112.676,61.463,-112.893,61.41,-112.969,61.39,-113.001,61.405,-112.99,61.426,-113.007,61.449,-113.091,61.484,-113.148,61.466,-113.359,61.445,-113.398,61.416,-113.515,61.378,-113.602,61.335,-113.677,61.301,-113.699,61.276,-113.704,61.246,-113.682,61.214,-113.691,61.189,-113.729,61.172,-113.725,61.163,-113.648,61.157,-113.633,61.145,-113.63,61.106,-113.664,61.072,-113.729,61.028,-113.832,60.997,-113.973,60.979,-114.086,60.982,-114.173,61.006,-114.338,60.988,-114.581,60.929,-114.727,60.907,-114.773,60.923,-114.913,60.91,-115.333,60.845,-115.473,60.838,-115.744,60.857,-115.877,60.848,-116.057,60.868,-116.425,60.953,-116.502,60.994,-116.528,61.029,-116.612,61.017,-116.664,61.021,-116.719,61.039,-117.089,61.051,-117.226,61.075,-117.278,61.097,-117.438,61.195,-117.505,61.24,-117.557,61.259,-117.629,61.285,-117.668,61.308,-117.627,61.307,-117.473,61.268,-117.206,61.19,-117.067,61.15,-116.98,61.169,-116.794,61.206,-116.738,61.224,-116.726,61.241,-116.797,61.298,-116.784,61.321,-116.763,61.329,-116.594,61.324,-116.511,61.309,-116.45,61.285,-116.367,61.273,-116.199,61.266,-116.121,61.228,-115.95,61.183,-115.939,61.193,-115.955,61.218,-115.988,61.235,-115.98,61.245,-115.87,61.284,-115.858,61.302,-115.905,61.327,-116.011,61.359,-116.039,61.381,-115.956,61.406,-115.918,61.423,-115.881,61.409,-115.827,61.411,-115.717,61.44,-115.711,61.45,-115.744,61.468,-115.752,61.481,-115.745,61.511,-115.708,61.568,-115.679,61.607,-115.658,61.641,-115.571,61.687,-115.566,61.723,-115.557,61.74,-115.508,61.757,-115.337,61.778,-115.215,61.773,-115.197,61.756,-115.19,61.742,-115.162,61.736,-114.898,61.767,-114.845,61.768,-114.721,61.812,-114.648,61.857,-114.636,61.869,-114.651,61.923,-114.667,61.935,-114.724,61.937,-114.762,61.956,-114.781,61.994,-114.848,62.032,-114.942,62.055,-115.033,62.099,-115.053,62.119,-115.038,62.145,-115.039,62.154,-115.045,62.174,-115.167,62.244,-115.208,62.247,-115.29,62.237,-115.307,62.254,-115.259,62.299,-115.254,62.329,-115.291,62.345,-115.309,62.371,-115.307,62.408,-115.294,62.44,-115.271,62.467,-115.289,62.482,-115.37,62.48,-115.701,62.485,-115.756,62.539,-115.792,62.565,-115.809,62.599,-115.867,62.633,-115.992,62.682,-116.038,62.726,-116.004,62.766,-115.913,62.764,-115.763,62.72,-115.738,62.696,-115.754,62.667,-115.724,62.66,-115.672,62.664,-115.289,62.607,-115.246,62.591,-115.217,62.569,-115.202,62.543,-115.165,62.519,-115.108,62.498,-114.902,62.466,-114.77,62.412,-114.714,62.376,-114.625,62.359,-114.437,62.373,-114.373,62.388,-114.351,62.408,-114.322,62.416,-114.284,62.413,-114.227,62.385,-114.225,62.373,-114.247,62.326,-114.224,62.312,-114.155,62.279,-114.136,62.277,-114.085,62.283,-114.023,62.268,-113.892,62.206,-113.861,62.176,-113.756,62.103,-113.574,62.019,-113.493,62.015,-113.309,62.005,-113.114,62.029,-113.037,62.019,-113.005,62.044,-112.923,62.073,-112.906,62.07,-112.892,62.037,-112.529,62.074,-112.302,62.134,-112.082,62.23,-111.962,62.29,-111.78,62.368,-111.7,62.42,-111.643,62.496,-111.593,62.541,-111.551,62.556,-111.512,62.595,-111.478,62.661,-111.393,62.722,-111.259,62.779,-111.081,62.835,-110.673,62.927,-110.525,62.945,-110.056,62.952,-109.983,62.944,-109.89,62.924,-109.705,62.911,-109.638,62.895,-109.348,62.867,-109.185,62.847,-109.046,62.829,-109.022,62.807],[-111.745,62.081,-111.668,62.123,-111.706,62.131,-111.828,62.113,-111.985,62.03,-112.071,61.974,-112.306,61.916,-112.524,61.875,-112.696,61.868,-112.875,61.84,-112.971,61.832,-113.011,61.809,-112.92,61.762,-112.754,61.764,-112.741,61.721,-112.73,61.65,-112.69,61.605,-112.596,61.584,-112.455,61.605,-112.331,61.65,-112.248,61.756,-112.137,61.837,-111.927,61.916,-111.787,62.002,-111.745,62.081]]]},{"n":"Ontario","z":1,"b":[-79.774,43.198,-75.776,44.495],"p":[[[-75.787,44.484,-75.776,44.459,-75.791,44.43,-75.832,44.397,-76.238,44.183,-76.352,44.106,-76.31,44.063,-76.306,44.043,-76.281,44.046,-76.198,44.083,-76.173,44.079,-76.161,44.069,-76.154,44.045,-76.192,44.003,-76.175,43.994,-76.102,44.003,-76.071,43.998,-76.099,43.967,-76.136,43.947,-76.143,43.927,-76.198,43.874,-76.223,43.864,-76.236,43.871,-76.233,43.895,-76.285,43.869,-76.288,43.849,-76.258,43.829,-76.235,43.799,-76.218,43.758,-76.21,43.67,-76.199,43.653,-76.195,43.645,-76.2,43.635,-76.205,43.614,-76.194,43.596,-76.203,43.575,-76.232,43.55,-76.29,43.537,-76.376,43.535,-76.473,43.507,-76.58,43.454,-76.653,43.405,-76.693,43.361,-76.722,43.343,-76.741,43.35,-76.885,43.314,-76.937,43.29,-76.95,43.268,-76.963,43.264,-76.976,43.277,-77.079,43.288,-77.271,43.295,-77.421,43.288,-77.529,43.267,-77.625,43.279,-77.707,43.323,-77.838,43.357,-78.175,43.395,-78.457,43.388,-78.689,43.361,-79.053,43.285,-79.213,43.242,-79.291,43.207,-79.338,43.198,-79.401,43.209,-79.528,43.21,-79.638,43.235,-79.728,43.253,-79.773,43.301,-79.774,43.32,-79.656,43.453,-79.61,43.513,-79.596,43.552,-79.569,43.578,-79.499,43.612,-79.48,43.636,-79.446,43.647,-79.396,43.645,-79.303,43.69,-79.165,43.78,-79.061,43.835,-78.989,43.855,-78.793,43.876,-78.669,43.902,-78.581,43.912,-78.528,43.905,-78.446,43.916,-78.335,43.944,-78.186,43.966,-78,43.979,-77.881,43.994,-77.829,44.011,-77.726,44.009,-77.709,44.033,-77.682,44.045,-77.583,44.038,-77.549,44.025,-77.544,44.014,-77.568,44.008,-77.554,43.99,-77.503,43.963,-77.421,43.954,-77.325,43.972,-77.259,43.964,-77.276,43.952,-77.311,43.952,-77.315,43.947,-77.289,43.923,-77.267,43.922,-77.19,43.948,-77.185,43.938,-77.219,43.9,-77.208,43.876,-77.151,43.866,-77.054,43.883,-76.934,43.93,-76.879,43.943,-76.87,43.965,-76.91,43.965,-77.013,43.957,-77.042,43.943,-77.044,43.952,-77.024,43.987,-76.93,44.038,-76.888,44.068,-76.863,44.1,-76.89,44.099,-76.969,44.066,-77.04,44.049,-77.103,44.046,-77.117,44.079,-77.083,44.148,-77.115,44.173,-77.271,44.139,-77.29,44.127,-77.29,44.118,-77.273,44.111,-77.275,44.104,-77.296,44.098,-77.332,44.104,-77.331,44.119,-77.314,44.143,-77.333,44.153,-77.381,44.146,-77.446,44.131,-77.586,44.084,-77.584,44.094,-77.547,44.124,-77.398,44.161,-77.326,44.178,-77.28,44.176,-77.251,44.189,-77.225,44.189,-77.203,44.175,-77.148,44.181,-77.059,44.205,-77.02,44.197,-77.033,44.156,-77.01,44.153,-76.951,44.186,-76.913,44.198,-76.896,44.188,-77.021,44.133,-77.055,44.106,-77.062,44.091,-77.041,44.071,-76.992,44.074,-76.787,44.182,-76.688,44.218,-76.589,44.238,-76.49,44.244,-76.408,44.262,-76.341,44.295,-76.057,44.367,-75.942,44.411,-75.831,44.49,-75.805,44.495,-75.787,44.484],[-76.688,44.138,-76.64,44.181,-76.643,44.199,-76.761,44.172,-76.799,44.155,-76.809,44.14,-76.787,44.13,-76.731,44.127,-76.688,44.138],[-76.275,44.009,-76.277,43.987,-76.262,43.99,-76.231,44.018,-76.231,44.029,-76.26,44.022,-76.275,44.009],[-76.237,44.233,-76.227,44.254,-76.279,44.247,-76.329,44.239,-76.43,44.227,-76.451,44.202,-76.483,44.179,-76.492,44.162,-76.477,44.153,-76.474,44.141,-76.485,44.128,-76.469,44.125,-76.423,44.122,-76.365,44.156,-76.353,44.176,-76.33,44.208,-76.237,44.233],[-76.081,44.295,-76.075,44.306,-76.119,44.306,-76.145,44.292,-76.15,44.281,-76.143,44.269,-76.111,44.277,-76.081,44.295]]]},{"n":"Great Bear","z":1,"b":[-125.09,64.827,-117.514,67.029],"p":[[[-117.842,66.424,-117.676,66.456,-117.542,66.483,-117.514,66.46,-117.55,66.424,-117.613,66.385,-117.632,66.366,-117.674,66.336,-117.673,66.3,-117.68,66.253,-117.728,66.226,-117.781,66.207,-117.813,66.187,-117.872,66.15,-117.923,66.139,-117.987,66.105,-117.971,66.084,-117.907,66.067,-117.924,66.044,-118.073,66.036,-118.116,66.011,-118.134,65.962,-118.199,65.905,-118.209,65.856,-118.157,65.831,-118.064,65.831,-117.874,65.847,-117.846,65.84,-117.901,65.8,-117.903,65.764,-117.943,65.723,-118.034,65.693,-118.148,65.682,-118.264,65.67,-118.379,65.664,-118.387,65.68,-118.453,65.683,-118.569,65.644,-118.715,65.694,-118.834,65.755,-118.944,65.784,-119.276,65.796,-119.504,65.793,-119.619,65.782,-119.705,65.752,-119.698,65.714,-119.892,65.641,-119.974,65.617,-119.988,65.586,-119.762,65.493,-119.541,65.408,-119.509,65.368,-119.556,65.344,-119.717,65.342,-119.885,65.325,-119.99,65.267,-120.095,65.203,-120.159,65.176,-120.466,65.073,-120.861,64.956,-120.952,64.935,-121.073,64.9,-121.133,64.861,-121.23,64.827,-121.367,64.834,-121.471,64.879,-121.48,64.933,-121.397,64.969,-121.32,64.974,-121.214,64.993,-121.089,65.004,-121.003,65.038,-120.991,65.082,-120.55,65.289,-120.406,65.411,-120.368,65.509,-120.383,65.557,-120.466,65.585,-120.663,65.605,-120.978,65.553,-121.182,65.496,-121.368,65.435,-121.548,65.364,-121.546,65.342,-121.569,65.297,-121.606,65.229,-121.612,65.175,-121.616,65.128,-121.615,65.093,-121.663,65.074,-121.727,65.061,-121.77,65.037,-121.929,64.996,-122.04,64.961,-122.558,65.023,-122.891,65.068,-123.371,65.114,-123.446,65.116,-123.472,65.152,-123.444,65.185,-123.344,65.2,-123.188,65.216,-122.853,65.278,-122.767,65.342,-122.769,65.409,-122.887,65.487,-122.786,65.531,-122.589,65.588,-122.374,65.657,-122.206,65.753,-122.164,65.817,-122.531,65.944,-122.549,65.986,-122.387,66.007,-122.067,65.977,-121.772,65.948,-121.475,65.957,-121.293,66.001,-121.249,66.054,-121.278,66.092,-121.533,66.122,-121.695,66.178,-122.11,66.234,-122.436,66.251,-122.745,66.251,-123.016,66.255,-123.02,66.221,-122.993,66.173,-123.04,66.126,-123.092,66.1,-123.098,66.073,-123.116,66.054,-123.154,66.064,-123.198,66.104,-123.396,66.135,-123.569,66.139,-123.77,66.125,-124.051,66.13,-124.261,66.157,-124.499,66.153,-124.59,66.11,-124.574,66.056,-124.59,66.016,-124.642,66.007,-124.676,66.025,-124.732,66.04,-124.785,65.995,-124.817,65.941,-124.898,65.966,-124.963,66.01,-125.02,66.057,-125.09,66.157,-125.063,66.248,-124.806,66.28,-124.59,66.274,-124.525,66.276,-124.404,66.309,-123.56,66.427,-123.177,66.517,-123.146,66.511,-123.143,66.483,-123.088,66.473,-122.987,66.495,-122.844,66.533,-122.529,66.56,-122.523,66.599,-122.411,66.599,-122.344,66.583,-121.98,66.636,-121.606,66.709,-121.528,66.719,-121.455,66.74,-121.233,66.832,-120.692,66.907,-120.593,66.93,-120.223,66.978,-120.103,67.001,-119.878,67.029,-119.785,67.004,-119.755,66.99,-119.671,66.992,-119.443,66.966,-119.211,66.933,-119.097,66.909,-119.252,66.829,-119.568,66.746,-119.762,66.696,-119.993,66.662,-120.084,66.654,-120.095,66.634,-120.13,66.6,-120.209,66.568,-120.33,66.537,-120.34,66.527,-120.314,66.493,-120.325,66.47,-120.411,66.456,-120.478,66.422,-120.445,66.395,-120.23,66.392,-120.016,66.38,-119.962,66.356,-119.768,66.327,-119.722,66.311,-119.592,66.307,-119.521,66.316,-119.489,66.338,-119.105,66.326,-118.756,66.331,-118.605,66.348,-118.337,66.399,-118.247,66.46,-118.192,66.515,-118.004,66.601,-117.838,66.639,-117.726,66.659,-117.696,66.658,-117.683,66.642,-117.699,66.621,-117.691,66.596,-117.734,66.551,-117.841,66.487,-117.864,66.461,-117.862,66.438,-117.842,66.424]]]},{"n":"Saint Clair","z":1,"b":[-83.105,42.275,-82.414,42.697],"p":[[[-83.076,42.275,-83.105,42.286,-83.063,42.323,-83.017,42.347,-82.926,42.375,-82.902,42.394,-82.883,42.442,-82.87,42.519,-82.841,42.568,-82.796,42.589,-82.784,42.613,-82.804,42.639,-82.787,42.665,-82.733,42.69,-82.687,42.697,-82.65,42.687,-82.633,42.661,-82.63,42.595,-82.639,42.574,-82.645,42.558,-82.634,42.542,-82.613,42.525,-82.481,42.5,-82.453,42.495,-82.432,42.498,-82.418,42.472,-82.414,42.411,-82.428,42.367,-82.462,42.339,-82.516,42.322,-82.59,42.315,-82.867,42.335,-82.971,42.327,-83.014,42.312,-83.047,42.297,-83.076,42.275]]]},{"n":"Vänern","z":1.7,"b":[12.313,58.372,14.091,59.39],"p":[[[14.038,59.083,14.066,59.097,14.083,59.1,14.089,59.092,14.091,59.079,14.083,59.064,14.072,59.028,14.056,59.017,14.05,59.012,14.039,59.004,14.012,59.004,13.993,58.986,13.995,58.945,13.987,58.898,13.975,58.865,13.954,58.844,13.924,58.823,13.89,58.8,13.859,58.772,13.83,58.744,13.8,58.722,13.737,58.704,13.631,58.684,13.517,58.663,13.437,58.642,13.358,58.617,13.3,58.561,13.24,58.515,13.195,58.511,13.172,58.512,13.148,58.521,13.137,58.541,13.149,58.559,13.149,58.573,13.148,58.597,13.115,58.608,13.041,58.604,12.952,58.572,12.913,58.546,12.869,58.504,12.798,58.486,12.728,58.452,12.679,58.421,12.65,58.397,12.624,58.382,12.602,58.376,12.58,58.372,12.562,58.373,12.551,58.379,12.554,58.394,12.573,58.419,12.571,58.432,12.554,58.43,12.507,58.397,12.375,58.383,12.335,58.374,12.313,58.395,12.329,58.434,12.488,58.513,12.607,58.617,12.611,58.641,12.563,58.65,12.525,58.661,12.504,58.695,12.501,58.728,12.5,58.764,12.471,58.783,12.448,58.783,12.425,58.801,12.432,58.813,12.459,58.817,12.474,58.84,12.472,58.877,12.464,58.921,12.469,58.935,12.482,58.931,12.497,58.92,12.51,58.901,12.52,58.867,12.526,58.846,12.535,58.832,12.557,58.819,12.576,58.826,12.6,58.846,12.618,58.887,12.647,58.931,12.676,58.987,12.708,59.036,12.746,59.065,12.784,59.076,12.818,59.082,12.869,59.073,12.945,59.048,13.041,59.014,13.062,58.977,13.088,58.947,13.122,58.921,13.16,58.905,13.195,58.899,13.236,58.905,13.244,58.93,13.221,58.993,13.177,59.097,13.123,59.198,13.103,59.255,13.103,59.274,13.108,59.284,13.118,59.296,13.13,59.319,13.143,59.342,13.155,59.353,13.173,59.348,13.191,59.325,13.218,59.311,13.246,59.31,13.252,59.315,13.251,59.325,13.249,59.336,13.251,59.345,13.261,59.352,13.278,59.357,13.316,59.361,13.378,59.364,13.461,59.37,13.548,59.381,13.628,59.39,13.68,59.39,13.72,59.378,13.738,59.359,13.744,59.341,13.752,59.331,13.766,59.331,13.8,59.337,13.83,59.341,13.867,59.335,13.904,59.328,13.933,59.33,13.962,59.334,13.997,59.329,14.028,59.32,14.046,59.314,14.052,59.305,14.056,59.29,14.054,59.268,14.047,59.241,14.027,59.204,13.994,59.157,13.946,59.113,13.931,59.09,13.945,59.067,13.973,59.066,14.005,59.068,14.038,59.083]]]},{"n":"Okeechobee","z":1.7,"b":[-81.081,26.691,-80.621,27.182],"p":[[[-80.629,26.887,-80.668,26.826,-80.711,26.742,-80.743,26.697,-80.784,26.691,-80.827,26.707,-80.858,26.723,-80.896,26.752,-80.932,26.81,-80.979,26.856,-81.027,26.876,-81.071,26.917,-81.081,26.956,-81.055,26.991,-80.997,27.033,-80.942,27.077,-80.87,27.147,-80.803,27.182,-80.759,27.182,-80.711,27.133,-80.69,27.11,-80.671,27.087,-80.654,27.054,-80.639,27.008,-80.621,26.949,-80.629,26.887]]]},{"n":"Nicaragua","z":1.7,"b":[-85.914,11.035,-84.769,12.109],"p":[[[-84.814,11.26,-84.792,11.206,-84.781,11.157,-84.769,11.118,-84.803,11.065,-84.842,11.049,-84.897,11.035,-84.94,11.052,-85.005,11.057,-85.11,11.081,-85.26,11.123,-85.411,11.174,-85.517,11.216,-85.584,11.25,-85.64,11.283,-85.684,11.317,-85.719,11.353,-85.754,11.406,-85.798,11.483,-85.834,11.563,-85.854,11.626,-85.864,11.664,-85.877,11.683,-85.89,11.691,-85.899,11.702,-85.903,11.721,-85.901,11.745,-85.901,11.795,-85.908,11.874,-85.914,11.953,-85.912,12.003,-85.894,12.035,-85.871,12.098,-85.835,12.109,-85.798,12.107,-85.751,12.094,-85.676,12.064,-85.588,12.016,-85.513,11.959,-85.441,11.902,-85.362,11.855,-85.259,11.783,-85.132,11.663,-85.009,11.527,-84.916,11.414,-84.857,11.329,-84.814,11.26]]]},{"n":"Tana","z":1.7,"b":[37.002,11.632,37.592,12.303],"p":[[[37.286,11.794,37.253,11.82,37.211,11.822,37.169,11.817,37.146,11.833,37.113,11.855,37.096,11.84,37.078,11.829,37.058,11.834,37.04,11.852,37.024,11.88,37.002,11.907,37.006,11.972,37.008,12.029,37.017,12.087,37.028,12.165,37.048,12.21,37.072,12.243,37.096,12.266,37.126,12.27,37.164,12.26,37.195,12.231,37.219,12.23,37.269,12.224,37.333,12.272,37.377,12.288,37.413,12.291,37.46,12.303,37.496,12.286,37.529,12.259,37.555,12.223,37.566,12.164,37.592,12.117,37.585,12.084,37.583,12.049,37.577,12.018,37.56,11.986,37.55,11.942,37.513,11.91,37.501,11.876,37.496,11.835,37.44,11.753,37.387,11.632,37.361,11.635,37.342,11.632,37.327,11.636,37.32,11.648,37.301,11.668,37.322,11.709,37.309,11.741,37.289,11.771,37.286,11.794]]]},{"n":"Titicaca","z":1.7,"b":[-70.021,-16.589,-68.586,-15.264],"p":[[[-69.366,-16.223,-69.4,-16.2,-69.436,-16.187,-69.483,-16.182,-69.487,-16.161,-69.449,-16.112,-69.439,-16.059,-69.447,-16.029,-69.489,-16.012,-69.558,-15.991,-69.627,-15.967,-69.668,-15.943,-69.691,-15.922,-69.717,-15.907,-69.748,-15.887,-69.782,-15.855,-69.812,-15.828,-69.834,-15.819,-69.846,-15.827,-69.854,-15.841,-69.852,-15.861,-69.842,-15.884,-69.833,-15.905,-69.835,-15.916,-69.845,-15.92,-69.859,-15.92,-69.876,-15.914,-69.895,-15.9,-69.923,-15.88,-69.961,-15.86,-69.994,-15.842,-70.011,-15.828,-70.016,-15.807,-70.021,-15.771,-70.017,-15.733,-70.002,-15.706,-69.979,-15.68,-69.956,-15.646,-69.921,-15.634,-69.866,-15.66,-69.818,-15.692,-69.798,-15.693,-69.818,-15.638,-69.869,-15.533,-69.929,-15.427,-69.938,-15.399,-69.914,-15.374,-69.855,-15.344,-69.844,-15.324,-69.833,-15.31,-69.799,-15.299,-69.761,-15.287,-69.742,-15.269,-69.687,-15.264,-69.625,-15.344,-69.541,-15.372,-69.449,-15.474,-69.405,-15.491,-69.361,-15.511,-69.299,-15.621,-69.27,-15.628,-69.224,-15.66,-69.19,-15.679,-69.17,-15.697,-69.147,-15.725,-69.103,-15.765,-69.039,-15.796,-69.014,-15.848,-69.014,-15.91,-68.995,-15.926,-68.904,-15.943,-68.831,-15.948,-68.796,-15.972,-68.809,-15.994,-68.826,-16.017,-68.853,-16.049,-68.889,-16.084,-68.871,-16.136,-68.829,-16.19,-68.764,-16.202,-68.694,-16.219,-68.635,-16.221,-68.593,-16.239,-68.594,-16.272,-68.586,-16.288,-68.588,-16.302,-68.612,-16.312,-68.655,-16.318,-68.693,-16.326,-68.705,-16.339,-68.712,-16.359,-68.739,-16.383,-68.787,-16.407,-68.86,-16.415,-68.906,-16.424,-68.928,-16.448,-68.897,-16.465,-68.868,-16.5,-68.858,-16.524,-68.856,-16.547,-68.862,-16.569,-68.871,-16.589,-68.904,-16.589,-68.942,-16.582,-69.006,-16.551,-69.049,-16.525,-69.076,-16.521,-69.098,-16.483,-69.052,-16.417,-69.039,-16.364,-69.043,-16.318,-69.044,-16.281,-69.005,-16.252,-68.946,-16.223,-68.905,-16.241,-68.885,-16.241,-68.891,-16.206,-68.947,-16.176,-69.015,-16.111,-69.077,-16.081,-69.103,-16.087,-69.107,-16.132,-69.121,-16.178,-69.136,-16.232,-69.178,-16.25,-69.222,-16.254,-69.269,-16.251,-69.324,-16.243,-69.366,-16.223]]]},{"n":"Cedar","z":1.7,"b":[-100.891,53.052,-99.33,54.252],"p":[[[-99.349,53.199,-99.33,53.159,-99.369,53.15,-99.389,53.131,-99.396,53.1,-99.395,53.085,-99.403,53.074,-99.416,53.065,-99.436,53.058,-99.459,53.053,-99.487,53.052,-99.518,53.057,-99.539,53.067,-99.54,53.083,-99.538,53.097,-99.552,53.105,-99.569,53.112,-99.579,53.123,-99.587,53.141,-99.605,53.163,-99.624,53.18,-99.638,53.188,-99.644,53.183,-99.645,53.17,-99.647,53.155,-99.657,53.145,-99.685,53.14,-99.73,53.14,-99.798,53.137,-99.876,53.102,-99.972,53.098,-100.048,53.117,-100.125,53.131,-100.2,53.152,-100.274,53.185,-100.346,53.231,-100.412,53.27,-100.47,53.289,-100.542,53.307,-100.646,53.347,-100.709,53.375,-100.771,53.402,-100.785,53.409,-100.842,53.426,-100.876,53.449,-100.891,53.471,-100.881,53.488,-100.855,53.501,-100.779,53.504,-100.707,53.5,-100.641,53.495,-100.604,53.498,-100.593,53.507,-100.592,53.519,-100.606,53.535,-100.632,53.554,-100.634,53.569,-100.591,53.572,-100.51,53.577,-100.39,53.583,-100.323,53.593,-100.313,53.64,-100.324,53.669,-100.352,53.712,-100.383,53.758,-100.432,53.79,-100.413,53.822,-100.33,53.865,-100.347,53.898,-100.393,53.892,-100.474,53.896,-100.498,53.905,-100.506,53.917,-100.483,53.95,-100.387,53.954,-100.331,53.958,-100.303,53.973,-100.3,53.99,-100.303,54.008,-100.306,54.031,-100.302,54.061,-100.306,54.098,-100.328,54.14,-100.351,54.177,-100.358,54.202,-100.35,54.215,-100.335,54.222,-100.31,54.221,-100.278,54.213,-100.244,54.211,-100.216,54.222,-100.191,54.24,-100.163,54.252,-100.122,54.246,-100.064,54.219,-99.994,54.192,-99.927,54.188,-99.882,54.192,-99.869,54.186,-99.928,54.154,-100.056,54.097,-100.18,54.042,-100.226,54.015,-100.209,54.008,-100.178,54.004,-100.14,53.993,-100.105,53.971,-100.094,53.941,-100.113,53.911,-100.14,53.883,-100.151,53.861,-100.141,53.846,-100.118,53.837,-100.06,53.847,-99.963,53.876,-99.845,53.927,-99.737,53.989,-99.66,54.034,-99.617,54.043,-99.604,54.027,-99.602,54.011,-99.617,53.989,-99.649,53.958,-99.69,53.918,-99.729,53.87,-99.776,53.835,-99.833,53.827,-99.883,53.831,-99.908,53.827,-99.914,53.81,-99.915,53.785,-99.926,53.76,-99.955,53.747,-99.995,53.735,-100.033,53.714,-100.076,53.694,-100.125,53.686,-100.17,53.679,-100.201,53.664,-100.215,53.646,-100.219,53.632,-100.185,53.611,-100.103,53.576,-100.019,53.533,-99.976,53.491,-99.96,53.456,-99.94,53.43,-99.929,53.407,-99.938,53.381,-99.948,53.355,-99.94,53.332,-99.925,53.311,-99.914,53.288,-99.906,53.268,-99.894,53.255,-99.872,53.248,-99.839,53.245,-99.799,53.253,-99.759,53.273,-99.693,53.299,-99.589,53.32,-99.491,53.333,-99.437,53.337,-99.421,53.329,-99.411,53.311,-99.407,53.285,-99.405,53.257,-99.375,53.223,-99.349,53.199]]]},{"n":"Onega","z":1.7,"b":[33.941,60.867,36.428,62.916],"p":[[[36.419,61.392,36.428,61.373,36.425,61.349,36.408,61.319,36.396,61.302,36.383,61.285,36.38,61.281,36.374,61.271,36.371,61.257,36.378,61.242,36.388,61.225,36.395,61.206,36.387,61.184,36.364,61.16,36.325,61.135,36.279,61.114,36.224,61.096,36.163,61.083,36.099,61.063,36.037,61.03,35.976,60.994,35.915,60.967,35.88,60.948,35.884,60.931,35.904,60.917,35.91,60.902,35.899,60.89,35.88,60.878,35.855,60.871,35.829,60.867,35.801,60.872,35.772,60.886,35.719,60.905,35.635,60.918,35.551,60.938,35.499,60.97,35.473,61.002,35.45,61.023,35.426,61.033,35.4,61.04,35.363,61.038,35.316,61.028,35.255,61.015,35.184,61.005,35.133,61.007,35.117,61.022,35.093,61.045,35.022,61.064,34.942,61.07,34.896,61.047,34.869,61.027,34.846,61.027,34.829,61.039,34.815,61.055,34.802,61.074,34.796,61.096,34.797,61.123,34.804,61.15,34.819,61.174,34.838,61.194,34.887,61.206,34.974,61.212,35.075,61.207,35.157,61.194,35.207,61.177,35.231,61.165,35.208,61.159,35.143,61.156,35.078,61.151,35.053,61.141,35.062,61.126,35.08,61.108,35.108,61.092,35.142,61.077,35.192,61.069,35.259,61.067,35.345,61.073,35.441,61.063,35.527,61.023,35.583,61.034,35.615,61.076,35.632,61.108,35.629,61.15,35.611,61.197,35.578,61.246,35.537,61.292,35.495,61.329,35.457,61.356,35.398,61.386,35.304,61.43,35.154,61.485,34.962,61.543,34.772,61.601,34.63,61.654,34.531,61.704,34.448,61.753,34.388,61.796,34.345,61.831,34.323,61.854,34.315,61.869,34.336,61.864,34.385,61.843,34.445,61.819,34.494,61.808,34.531,61.812,34.562,61.824,34.583,61.846,34.594,61.874,34.594,61.906,34.586,61.938,34.566,61.969,34.537,61.999,34.477,62.039,34.381,62.093,34.294,62.142,34.255,62.169,34.255,62.186,34.262,62.21,34.265,62.238,34.257,62.262,34.223,62.294,34.165,62.339,34.098,62.379,34.042,62.397,34.003,62.401,33.978,62.408,33.962,62.425,33.947,62.453,33.941,62.482,33.944,62.5,33.959,62.51,33.979,62.516,34.003,62.516,34.027,62.512,34.054,62.495,34.084,62.463,34.137,62.414,34.218,62.352,34.292,62.297,34.328,62.264,34.329,62.246,34.321,62.227,34.343,62.199,34.417,62.158,34.514,62.12,34.591,62.096,34.636,62.095,34.653,62.109,34.644,62.136,34.632,62.182,34.647,62.213,34.658,62.245,34.615,62.304,34.609,62.339,34.633,62.348,34.682,62.339,34.696,62.365,34.66,62.406,34.592,62.458,34.547,62.498,34.527,62.524,34.52,62.542,34.532,62.546,34.557,62.54,34.595,62.519,34.641,62.489,34.696,62.443,34.758,62.383,34.811,62.332,34.841,62.31,34.852,62.294,34.859,62.26,34.871,62.219,34.892,62.187,34.928,62.16,34.976,62.118,35.029,62.078,35.075,62.054,35.115,62.054,35.15,62.071,35.165,62.081,35.168,62.097,35.144,62.127,35.094,62.172,35.052,62.212,35.046,62.229,35.096,62.218,35.188,62.19,35.284,62.162,35.341,62.149,35.387,62.158,35.455,62.181,35.518,62.21,35.554,62.236,35.564,62.26,35.567,62.287,35.559,62.319,35.54,62.354,35.498,62.404,35.433,62.469,35.352,62.523,35.271,62.545,35.201,62.55,35.147,62.559,35.093,62.58,35.024,62.613,34.967,62.639,34.943,62.643,34.868,62.611,34.839,62.591,34.776,62.622,34.739,62.621,34.704,62.643,34.658,62.68,34.621,62.712,34.583,62.756,34.536,62.818,34.48,62.874,34.489,62.904,34.533,62.916,34.605,62.873,34.703,62.844,34.788,62.831,34.855,62.826,34.91,62.815,34.958,62.797,35.006,62.773,35.083,62.741,35.202,62.705,35.326,62.666,35.416,62.627,35.464,62.604,35.488,62.606,35.495,62.619,35.504,62.625,35.519,62.617,35.544,62.599,35.585,62.576,35.641,62.556,35.698,62.538,35.74,62.52,35.771,62.496,35.803,62.461,35.822,62.412,35.823,62.352,35.8,62.281,35.764,62.205,35.73,62.139,35.674,62.079,35.689,62.04,35.75,61.999,35.818,61.951,35.843,61.902,35.871,61.855,35.926,61.806,35.985,61.756,36.026,61.704,36.061,61.654,36.109,61.611,36.159,61.571,36.2,61.531,36.247,61.493,36.312,61.459,36.371,61.432,36.406,61.411,36.419,61.392],[35.326,61.935,35.347,61.998,35.339,62.033,35.308,62.077,35.252,62.08,35.196,61.988,35.171,61.906,35.16,61.859,35.202,61.839,35.274,61.87,35.326,61.935]]]},{"n":"Great Salt","z":1.7,"b":[-113.014,40.699,-111.951,41.667],"p":[[[-111.974,41.002,-111.951,40.968,-111.961,40.942,-112.053,40.902,-112.097,40.881,-112.19,40.769,-112.267,40.74,-112.332,40.699,-112.37,40.701,-112.394,40.757,-112.436,40.816,-112.446,40.875,-112.478,40.943,-112.493,40.938,-112.508,40.919,-112.522,40.878,-112.546,40.839,-112.58,40.849,-112.632,40.927,-112.69,40.98,-112.728,41.019,-112.751,41.059,-112.768,41.114,-112.788,41.174,-112.814,41.224,-112.846,41.261,-112.881,41.289,-112.918,41.328,-112.961,41.377,-112.985,41.427,-112.997,41.489,-112.998,41.534,-112.995,41.56,-112.997,41.576,-113.007,41.582,-113.014,41.597,-112.999,41.612,-112.966,41.614,-112.909,41.637,-112.844,41.657,-112.8,41.667,-112.77,41.666,-112.742,41.659,-112.719,41.637,-112.707,41.603,-112.734,41.563,-112.738,41.537,-112.708,41.496,-112.666,41.466,-112.604,41.431,-112.554,41.43,-112.527,41.421,-112.512,41.381,-112.489,41.309,-112.458,41.247,-112.437,41.219,-112.422,41.217,-112.406,41.217,-112.39,41.223,-112.377,41.232,-112.366,41.248,-112.381,41.324,-112.388,41.385,-112.402,41.432,-112.39,41.447,-112.374,41.457,-112.33,41.443,-112.29,41.41,-112.243,41.393,-112.171,41.397,-112.092,41.408,-112.064,41.4,-112.063,41.388,-112.076,41.347,-112.114,41.336,-112.179,41.342,-112.241,41.345,-112.27,41.332,-112.287,41.317,-112.304,41.283,-112.284,41.268,-112.243,41.222,-112.212,41.192,-112.181,41.163,-112.152,41.134,-112.083,41.065,-111.974,41.002],[-112.171,40.875,-112.156,40.956,-112.198,41.056,-112.236,41.069,-112.263,41.058,-112.252,41.02,-112.25,40.964,-112.216,40.924,-112.171,40.875]]]},{"n":"Michigan","z":1,"b":[-88.028,41.638,-84.757,46.109],"p":[[[-84.763,45.792,-84.772,45.795,-84.806,45.775,-84.866,45.769,-84.952,45.775,-84.981,45.767,-84.955,45.743,-84.977,45.707,-85.047,45.656,-85.088,45.605,-85.099,45.553,-85.088,45.507,-85.055,45.467,-85.006,45.44,-84.943,45.429,-84.93,45.413,-84.967,45.393,-85.033,45.381,-85.127,45.378,-85.223,45.356,-85.319,45.314,-85.376,45.277,-85.391,45.244,-85.394,45.196,-85.382,45.078,-85.389,45.033,-85.43,44.949,-85.485,44.84,-85.549,44.772,-85.561,44.781,-85.551,44.815,-85.497,44.924,-85.489,44.97,-85.494,44.994,-85.513,44.997,-85.534,44.976,-85.558,44.931,-85.563,44.907,-85.549,44.903,-85.559,44.873,-85.593,44.816,-85.623,44.804,-85.647,44.838,-85.645,44.882,-85.605,44.97,-85.619,44.987,-85.596,45.042,-85.585,45.082,-85.597,45.123,-85.588,45.161,-85.556,45.196,-85.566,45.207,-85.617,45.193,-85.689,45.128,-85.784,45.012,-85.858,44.962,-85.91,44.976,-85.953,44.967,-85.985,44.933,-86.02,44.916,-86.058,44.916,-86.08,44.876,-86.088,44.794,-86.126,44.748,-86.237,44.72,-86.259,44.699,-86.261,44.669,-86.243,44.63,-86.242,44.551,-86.259,44.434,-86.296,44.331,-86.353,44.242,-86.415,44.169,-86.519,44.074,-86.521,44.055,-86.47,43.98,-86.449,43.924,-86.438,43.813,-86.441,43.794,-86.541,43.657,-86.542,43.637,-86.435,43.407,-86.34,43.242,-86.329,43.224,-86.301,43.158,-86.248,43.029,-86.222,42.9,-86.22,42.773,-86.233,42.65,-86.26,42.531,-86.3,42.418,-86.352,42.309,-86.407,42.224,-86.52,42.082,-86.575,41.988,-86.632,41.911,-86.692,41.853,-86.798,41.789,-87.073,41.669,-87.159,41.646,-87.253,41.638,-87.356,41.643,-87.412,41.656,-87.425,41.679,-87.447,41.681,-87.477,41.695,-87.515,41.721,-87.557,41.766,-87.602,41.859,-87.651,42,-87.695,42.084,-87.733,42.112,-87.767,42.154,-87.797,42.211,-87.812,42.341,-87.812,42.543,-87.802,42.68,-87.781,42.753,-87.787,42.809,-87.819,42.848,-87.838,42.896,-87.846,42.953,-87.862,42.992,-87.888,43.012,-87.893,43.041,-87.879,43.078,-87.879,43.103,-87.893,43.117,-87.896,43.137,-87.889,43.165,-87.902,43.245,-87.898,43.294,-87.881,43.347,-87.824,43.443,-87.806,43.49,-87.801,43.538,-87.774,43.599,-87.7,43.724,-87.698,43.752,-87.723,43.809,-87.729,43.851,-87.724,43.907,-87.7,43.977,-87.658,44.059,-87.603,44.125,-87.535,44.174,-87.512,44.23,-87.536,44.293,-87.527,44.386,-87.487,44.509,-87.441,44.601,-87.391,44.662,-87.356,44.72,-87.337,44.776,-87.346,44.801,-87.383,44.827,-87.42,44.866,-87.48,44.874,-87.555,44.849,-87.566,44.85,-87.604,44.847,-87.677,44.79,-87.741,44.692,-87.788,44.652,-87.851,44.624,-87.898,44.593,-87.929,44.559,-87.969,44.552,-88.019,44.571,-88.028,44.618,-87.998,44.692,-87.952,44.764,-87.857,44.88,-87.854,44.908,-87.83,44.942,-87.683,44.993,-87.627,45.032,-87.608,45.083,-87.602,45.157,-87.593,45.178,-87.571,45.187,-87.506,45.249,-87.399,45.365,-87.302,45.484,-87.216,45.607,-87.154,45.677,-87.114,45.692,-87.085,45.73,-87.039,45.845,-87.005,45.897,-86.982,45.917,-86.971,45.905,-86.993,45.842,-86.993,45.79,-86.974,45.723,-86.938,45.7,-86.884,45.721,-86.837,45.765,-86.799,45.832,-86.754,45.859,-86.703,45.848,-86.661,45.852,-86.627,45.873,-86.563,45.883,-86.552,45.857,-86.559,45.806,-86.58,45.782,-86.612,45.784,-86.65,45.757,-86.691,45.701,-86.69,45.657,-86.644,45.625,-86.622,45.623,-86.622,45.652,-86.601,45.682,-86.534,45.736,-86.527,45.753,-86.4,45.801,-86.351,45.831,-86.338,45.866,-86.272,45.937,-86.209,45.959,-86.123,45.969,-86.04,45.963,-85.959,45.941,-85.914,45.941,-85.903,45.964,-85.834,45.98,-85.708,45.988,-85.615,46.02,-85.555,46.078,-85.479,46.108,-85.386,46.109,-85.26,46.088,-85.101,46.046,-84.972,45.989,-84.802,45.874,-84.757,45.863,-84.763,45.792],[-85.492,45.76,-85.539,45.775,-85.573,45.738,-85.608,45.656,-85.612,45.609,-85.586,45.598,-85.552,45.603,-85.509,45.624,-85.492,45.663,-85.501,45.75,-85.492,45.76],[-86.052,45.156,-86.06,45.147,-86.038,45.103,-86.016,45.089,-85.988,45.088,-85.976,45.102,-85.98,45.132,-85.998,45.15,-86.052,45.156],[-86.926,45.415,-86.938,45.426,-86.967,45.368,-86.947,45.346,-86.892,45.342,-86.862,45.352,-86.857,45.394,-86.862,45.409,-86.878,45.414,-86.926,45.415],[-87.049,45.303,-87.088,45.279,-87.122,45.225,-87.166,45.19,-87.221,45.174,-87.259,45.138,-87.279,45.081,-87.316,45.031,-87.396,44.954,-87.403,44.933,-87.398,44.903,-87.382,44.864,-87.355,44.839,-87.316,44.827,-87.28,44.835,-87.188,44.922,-87.179,44.941,-87.191,44.95,-87.179,44.983,-87.141,45.041,-87.114,45.068,-87.096,45.067,-87.078,45.078,-87.059,45.102,-87.062,45.125,-87.086,45.145,-87.084,45.154,-87.057,45.151,-87.047,45.172,-87.054,45.218,-87.042,45.237,-87.01,45.228,-86.992,45.241,-86.986,45.275,-87.005,45.296,-87.049,45.303]]]},{"n":"Superior","z":1,"b":[-92.107,46.436,-84.344,49.001],"p":[[[-84.432,46.522,-84.344,46.531,-84.358,46.505,-84.387,46.502,-84.424,46.491,-84.467,46.464,-84.528,46.436,-84.592,46.443,-84.635,46.482,-84.697,46.491,-84.779,46.468,-84.851,46.469,-84.914,46.494,-85.034,46.506,-85.049,46.524,-85.046,46.543,-85.027,46.563,-85.017,46.611,-85.015,46.686,-84.998,46.74,-84.968,46.771,-85.015,46.783,-85.138,46.778,-85.281,46.752,-85.442,46.707,-85.616,46.691,-85.804,46.704,-85.947,46.702,-86.105,46.679,-86.132,46.687,-86.231,46.656,-86.403,46.588,-86.531,46.524,-86.637,46.453,-86.654,46.452,-86.679,46.468,-86.733,46.49,-86.79,46.491,-86.851,46.47,-86.914,46.483,-86.977,46.53,-87.034,46.543,-87.086,46.523,-87.171,46.514,-87.358,46.524,-87.378,46.54,-87.394,46.59,-87.415,46.612,-87.451,46.627,-87.509,46.683,-87.637,46.827,-87.673,46.844,-87.778,46.888,-87.877,46.908,-88.129,46.935,-88.184,46.955,-88.216,46.952,-88.383,46.874,-88.39,46.857,-88.4,46.841,-88.427,46.814,-88.449,46.802,-88.463,46.814,-88.476,46.837,-88.474,46.886,-88.456,46.963,-88.466,47.013,-88.504,47.035,-88.521,47.063,-88.515,47.096,-88.543,47.122,-88.604,47.141,-88.633,47.167,-88.628,47.199,-88.634,47.22,-88.65,47.232,-88.716,47.207,-88.83,47.146,-88.907,47.091,-88.948,47.043,-89.009,47.016,-89.09,47.009,-89.155,46.989,-89.269,46.918,-89.355,46.882,-89.497,46.855,-89.695,46.839,-89.861,46.793,-89.993,46.718,-90.154,46.654,-90.341,46.602,-90.469,46.584,-90.536,46.599,-90.617,46.631,-90.737,46.689,-90.754,46.684,-90.745,46.672,-90.748,46.662,-90.77,46.645,-90.923,46.603,-90.944,46.619,-90.886,46.677,-90.864,46.721,-90.865,46.761,-90.84,46.81,-90.788,46.868,-90.768,46.908,-90.782,46.929,-90.84,46.962,-90.887,46.965,-90.946,46.954,-91.127,46.868,-91.145,46.874,-91.203,46.888,-91.218,46.886,-91.357,46.811,-91.499,46.766,-91.836,46.702,-91.921,46.694,-91.987,46.699,-92.073,46.736,-92.107,46.762,-92.104,46.789,-92.065,46.819,-91.645,47.05,-91.386,47.215,-90.957,47.524,-90.847,47.585,-90.597,47.688,-90.197,47.786,-90,47.841,-89.829,47.901,-89.72,47.947,-89.672,47.979,-89.634,47.994,-89.605,47.992,-89.547,48.013,-89.45,48.092,-89.438,48.099,-89.345,48.121,-89.315,48.159,-89.297,48.204,-89.252,48.28,-89.221,48.311,-89.214,48.345,-89.229,48.382,-89.225,48.418,-89.201,48.451,-89.086,48.498,-88.775,48.583,-88.764,48.572,-88.818,48.506,-88.876,48.381,-88.897,48.378,-88.916,48.363,-88.933,48.335,-88.888,48.337,-88.783,48.368,-88.734,48.384,-88.722,48.415,-88.706,48.476,-88.683,48.51,-88.654,48.517,-88.635,48.546,-88.625,48.599,-88.602,48.63,-88.565,48.64,-88.55,48.658,-88.552,48.684,-88.542,48.753,-88.535,48.786,-88.503,48.833,-88.458,48.848,-88.395,48.845,-88.352,48.821,-88.329,48.776,-88.323,48.738,-88.334,48.709,-88.394,48.656,-88.567,48.528,-88.585,48.492,-88.569,48.474,-88.559,48.447,-88.539,48.443,-88.501,48.467,-88.484,48.488,-88.489,48.507,-88.476,48.531,-88.444,48.56,-88.418,48.569,-88.4,48.557,-88.369,48.562,-88.325,48.584,-88.248,48.596,-88.233,48.597,-88.138,48.683,-88.109,48.733,-88.109,48.776,-88.147,48.821,-88.224,48.867,-88.247,48.917,-88.249,48.942,-88.264,48.974,-88.248,48.986,-88.2,48.977,-88.161,48.982,-88.128,48.998,-88.07,49.001,-88.023,48.999,-87.966,48.959,-87.761,48.92,-87.685,48.899,-87.592,48.878,-87.568,48.871,-87.552,48.848,-87.517,48.839,-87.464,48.842,-87.407,48.835,-87.311,48.799,-87.224,48.776,-87.085,48.779,-87.009,48.789,-86.888,48.772,-86.82,48.773,-86.752,48.801,-86.693,48.808,-86.643,48.795,-86.621,48.777,-86.581,48.753,-86.459,48.765,-86.432,48.758,-86.396,48.731,-86.39,48.714,-86.371,48.688,-86.325,48.637,-86.314,48.619,-86.283,48.598,-86.257,48.548,-86.237,48.471,-86.216,48.423,-86.191,48.406,-86.181,48.385,-86.159,48.304,-86.104,48.21,-86.022,48.119,-85.914,48.03,-85.778,47.97,-85.615,47.941,-85.47,47.934,-85.342,47.95,-85.174,47.959,-84.964,47.96,-84.855,47.95,-84.847,47.928,-84.867,47.896,-84.915,47.855,-84.92,47.828,-84.922,47.793,-84.961,47.746,-84.981,47.718,-84.975,47.698,-84.986,47.672,-85.014,47.639,-84.998,47.595,-84.936,47.54,-84.86,47.496,-84.771,47.462,-84.716,47.427,-84.693,47.39,-84.668,47.371,-84.641,47.367,-84.616,47.35,-84.594,47.32,-84.618,47.267,-84.688,47.191,-84.72,47.142,-84.712,47.12,-84.772,47.037,-84.775,47.004,-84.75,46.981,-84.656,46.949,-84.625,46.922,-84.6,46.918,-84.582,46.937,-84.545,46.946,-84.488,46.944,-84.435,46.931,-84.385,46.906,-84.367,46.88,-84.382,46.853,-84.408,46.844,-84.485,46.848,-84.528,46.834,-84.553,46.806,-84.561,46.73,-84.54,46.697,-84.528,46.697,-84.51,46.713,-84.488,46.744,-84.464,46.747,-84.438,46.723,-84.466,46.674,-84.548,46.6,-84.569,46.577,-84.568,46.543,-84.501,46.483,-84.475,46.489,-84.459,46.504,-84.432,46.522],[-90.756,46.965,-90.766,46.951,-90.761,46.937,-90.723,46.92,-90.708,46.927,-90.713,46.941,-90.738,46.962,-90.756,46.965],[-90.653,46.828,-90.589,46.855,-90.596,46.869,-90.619,46.87,-90.752,46.814,-90.782,46.791,-90.792,46.772,-90.768,46.77,-90.711,46.783,-90.68,46.798,-90.678,46.813,-90.653,46.828],[-90.547,46.919,-90.535,46.954,-90.543,46.963,-90.576,46.957,-90.65,46.937,-90.652,46.922,-90.58,46.914,-90.547,46.919],[-90.41,47.044,-90.41,47.064,-90.422,47.071,-90.447,47.066,-90.464,47.048,-90.472,47.018,-90.459,47.01,-90.427,47.026,-90.41,47.044],[-88.057,48.837,-88.087,48.808,-88.076,48.741,-88.057,48.717,-88.031,48.718,-87.998,48.743,-87.941,48.757,-87.859,48.758,-87.816,48.768,-87.812,48.785,-87.782,48.795,-87.77,48.811,-87.765,48.841,-87.813,48.859,-87.915,48.865,-87.996,48.857,-88.057,48.837],[-88.911,47.936,-88.625,48.042,-88.593,48.072,-88.597,48.087,-88.581,48.111,-88.485,48.167,-88.499,48.176,-88.588,48.158,-88.756,48.104,-89.147,47.953,-89.193,47.929,-89.2,47.917,-89.169,47.917,-89.169,47.902,-89.199,47.872,-89.195,47.852,-89.157,47.841,-89.082,47.851,-88.97,47.881,-88.93,47.896,-88.924,47.905,-88.96,47.909,-88.955,47.926,-88.911,47.936],[-87.79,47.411,-87.741,47.434,-87.793,47.467,-87.878,47.484,-87.994,47.484,-88.11,47.473,-88.32,47.421,-88.394,47.389,-88.562,47.272,-88.609,47.217,-88.616,47.174,-88.574,47.143,-88.434,47.122,-88.427,47.137,-88.433,47.143,-88.454,47.152,-88.417,47.191,-88.405,47.175,-88.409,47.131,-88.438,47.1,-88.468,47.077,-88.469,47.048,-88.439,47.011,-88.402,47.019,-88.358,47.073,-88.237,47.159,-88.24,47.178,-88.222,47.211,-88.016,47.321,-87.975,47.355,-87.983,47.368,-88.027,47.386,-88.021,47.392,-87.79,47.411],[-87.69,48.75,-87.622,48.757,-87.626,48.78,-87.667,48.821,-87.702,48.841,-87.731,48.84,-87.748,48.835,-87.753,48.824,-87.731,48.794,-87.73,48.778,-87.744,48.764,-87.73,48.755,-87.69,48.75],[-85.606,47.742,-85.596,47.757,-85.641,47.782,-85.765,47.807,-85.827,47.804,-85.883,47.791,-85.931,47.766,-85.951,47.747,-85.942,47.734,-85.859,47.719,-85.666,47.733,-85.606,47.742]]]},{"n":"Huron","z":1,"b":[-84.763,43.017,-79.678,46.563],"p":[[[-84.344,46.531,-84.236,46.557,-84.165,46.563,-84.118,46.539,-84.087,46.507,-84.084,46.473,-84.106,46.43,-84.109,46.393,-84.092,46.361,-84.034,46.344,-83.934,46.342,-83.882,46.333,-83.863,46.306,-83.843,46.302,-83.661,46.29,-83.593,46.278,-83.572,46.26,-83.459,46.238,-83.025,46.181,-82.895,46.188,-82.719,46.207,-82.664,46.176,-82.638,46.172,-82.598,46.177,-82.35,46.193,-82.318,46.163,-82.196,46.134,-82.116,46.132,-82.012,46.129,-81.817,46.115,-81.769,46.104,-81.766,46.071,-81.765,46.058,-81.75,46.057,-81.727,46.073,-81.689,46.079,-81.662,46.093,-81.646,46.115,-81.615,46.119,-81.545,46.09,-81.542,46.075,-81.64,46.04,-81.629,46.025,-81.624,46.017,-81.597,46.012,-81.595,45.996,-81.627,45.978,-81.597,45.967,-81.502,45.993,-81.449,45.994,-81.304,45.974,-81.237,45.974,-81.197,45.991,-81.191,46.006,-81.174,46.016,-81.162,46.012,-81.158,45.992,-81.16,45.955,-81.097,45.942,-80.968,45.952,-80.89,45.951,-80.818,45.951,-80.805,45.942,-80.787,45.919,-80.732,45.877,-80.713,45.858,-80.696,45.816,-80.656,45.79,-80.643,45.767,-80.641,45.747,-80.629,45.727,-80.593,45.682,-80.546,45.65,-80.52,45.623,-80.511,45.59,-80.486,45.577,-80.444,45.584,-80.427,45.598,-80.429,45.625,-80.414,45.62,-80.389,45.574,-80.392,45.501,-80.385,45.458,-80.35,45.399,-80.302,45.383,-80.271,45.36,-80.177,45.344,-80.168,45.348,-80.185,45.387,-80.176,45.397,-80.145,45.399,-80.092,45.393,-80.057,45.375,-80.041,45.342,-80.05,45.301,-80.111,45.259,-80.121,45.223,-80.108,45.201,-80.092,45.197,-80.075,45.211,-80.049,45.204,-80.015,45.176,-80.006,45.153,-80.023,45.136,-80.083,45.126,-80.086,45.109,-80.022,45.072,-79.972,45.022,-79.946,45.017,-79.932,45.001,-79.929,44.974,-79.902,44.957,-79.828,44.936,-79.8,44.885,-79.785,44.854,-79.772,44.819,-79.75,44.815,-79.733,44.832,-79.732,44.863,-79.696,44.875,-79.692,44.859,-79.713,44.821,-79.709,44.791,-79.678,44.769,-79.717,44.761,-79.827,44.768,-79.876,44.799,-79.891,44.809,-79.925,44.813,-79.935,44.824,-79.92,44.855,-79.93,44.863,-79.997,44.856,-80.065,44.834,-80.108,44.807,-80.108,44.775,-80.079,44.743,-80.021,44.71,-79.997,44.652,-80.008,44.572,-80.037,44.517,-80.085,44.489,-80.226,44.51,-80.46,44.578,-80.593,44.639,-80.65,44.721,-80.67,44.727,-80.79,44.7,-80.854,44.666,-80.91,44.618,-80.925,44.633,-80.901,44.712,-80.891,44.761,-80.895,44.781,-80.919,44.797,-80.963,44.809,-81.022,44.802,-81.11,44.765,-81.13,44.77,-81.128,44.782,-81.036,44.843,-81.006,44.876,-81.006,44.907,-80.993,44.934,-80.966,44.959,-80.973,44.97,-81.012,44.967,-81.032,44.957,-81.074,44.914,-81.134,44.918,-81.141,44.928,-81.125,44.953,-81.138,44.968,-81.18,44.974,-81.193,45.003,-81.207,45.009,-81.26,45.028,-81.264,45.081,-81.282,45.124,-81.313,45.156,-81.316,45.189,-81.285,45.241,-81.296,45.249,-81.4,45.256,-81.595,45.262,-81.697,45.251,-81.706,45.223,-81.683,45.203,-81.629,45.189,-81.59,45.166,-81.577,45.145,-81.541,45.125,-81.499,45.081,-81.451,45.015,-81.415,44.987,-81.377,44.984,-81.355,44.978,-81.358,44.911,-81.341,44.87,-81.3,44.832,-81.278,44.79,-81.281,44.716,-81.278,44.644,-81.298,44.597,-81.352,44.542,-81.387,44.497,-81.402,44.461,-81.431,44.436,-81.474,44.425,-81.519,44.399,-81.565,44.36,-81.595,44.311,-81.609,44.253,-81.647,44.192,-81.708,44.131,-81.732,44.06,-81.718,43.982,-81.717,43.642,-81.705,43.558,-81.713,43.474,-81.739,43.392,-81.785,43.327,-81.848,43.28,-81.911,43.246,-81.974,43.223,-82.033,43.186,-82.088,43.133,-82.181,43.085,-82.312,43.043,-82.417,43.017,-82.432,43.039,-82.461,43.081,-82.516,43.28,-82.597,43.634,-82.663,43.854,-82.716,43.939,-82.788,44.004,-82.878,44.048,-82.947,44.067,-82.995,44.061,-83.069,44.025,-83.209,43.994,-83.267,43.973,-83.299,43.945,-83.338,43.929,-83.383,43.924,-83.387,43.917,-83.353,43.906,-83.353,43.879,-83.447,43.758,-83.469,43.731,-83.526,43.721,-83.573,43.697,-83.622,43.636,-83.668,43.621,-83.735,43.627,-83.848,43.664,-83.916,43.701,-83.938,43.738,-83.94,43.782,-83.92,43.834,-83.922,43.875,-83.879,43.963,-83.821,43.993,-83.738,44.001,-83.692,44.014,-83.683,44.035,-83.657,44.05,-83.614,44.058,-83.577,44.113,-83.548,44.215,-83.512,44.271,-83.487,44.281,-83.464,44.277,-83.463,44.265,-83.366,44.33,-83.331,44.375,-83.328,44.421,-83.319,44.556,-83.308,44.644,-83.294,44.683,-83.3,44.785,-83.328,44.869,-83.436,44.961,-83.452,44.992,-83.448,45.025,-83.426,45.059,-83.378,45.067,-83.293,45.04,-83.279,45.047,-83.302,45.062,-83.316,45.093,-83.32,45.139,-83.331,45.164,-83.349,45.169,-83.396,45.221,-83.403,45.248,-83.397,45.274,-83.415,45.299,-83.479,45.339,-83.594,45.379,-83.869,45.456,-83.918,45.486,-83.986,45.504,-84.073,45.509,-84.128,45.532,-84.151,45.574,-84.189,45.611,-84.242,45.644,-84.326,45.666,-84.441,45.676,-84.563,45.712,-84.691,45.774,-84.763,45.792,-84.757,45.863,-84.727,45.861,-84.713,45.871,-84.713,45.882,-84.725,45.895,-84.717,45.944,-84.725,45.958,-84.675,46.033,-84.632,46.053,-84.581,46.046,-84.55,46.029,-84.537,46.004,-84.521,45.995,-84.49,45.994,-84.473,45.99,-84.463,45.992,-84.447,46.006,-84.341,46,-83.979,45.962,-83.929,45.968,-83.901,45.981,-83.894,46.001,-83.917,46.021,-84.012,46.061,-84.041,46.088,-84.048,46.114,-84.035,46.137,-84.055,46.159,-84.109,46.178,-84.227,46.184,-84.248,46.203,-84.238,46.222,-84.204,46.247,-84.202,46.283,-84.213,46.304,-84.229,46.366,-84.255,46.408,-84.307,46.469,-84.327,46.491,-84.358,46.505,-84.344,46.531],[-83.345,45.993,-83.392,45.988,-83.437,45.963,-83.467,45.936,-83.48,45.907,-83.453,45.884,-83.387,45.869,-83.332,45.88,-83.269,45.947,-83.271,45.961,-83.296,45.977,-83.345,45.993],[-83.529,46.024,-83.58,46.08,-83.608,46.097,-83.684,46.106,-83.699,46.096,-83.68,46.07,-83.683,46.057,-83.715,46.038,-83.775,46.011,-83.82,46.005,-83.851,46.019,-83.871,46.017,-83.875,45.991,-83.866,45.979,-83.823,45.956,-83.731,45.943,-83.588,45.941,-83.518,45.958,-83.494,45.991,-83.503,46.013,-83.529,46.024],[-83.829,46.148,-83.808,46.167,-83.795,46.214,-83.822,46.253,-83.891,46.284,-83.971,46.303,-84.061,46.31,-84.097,46.288,-84.078,46.236,-84.039,46.177,-83.979,46.109,-83.93,46.092,-83.907,46.097,-83.887,46.119,-83.876,46.148,-83.856,46.157,-83.829,46.148],[-84.169,46.324,-84.188,46.323,-84.196,46.303,-84.185,46.279,-84.135,46.24,-84.121,46.246,-84.116,46.261,-84.12,46.285,-84.137,46.306,-84.169,46.324],[-84.133,46.341,-84.133,46.362,-84.164,46.41,-84.165,46.457,-84.137,46.503,-84.142,46.531,-84.18,46.542,-84.216,46.536,-84.274,46.488,-84.235,46.445,-84.209,46.384,-84.189,46.36,-84.163,46.346,-84.133,46.341],[-84.429,45.807,-84.557,45.82,-84.563,45.804,-84.487,45.754,-84.441,45.734,-84.412,45.736,-84.39,45.747,-84.375,45.765,-84.38,45.788,-84.417,45.814,-84.429,45.807],[-82.962,45.833,-82.937,45.811,-82.736,45.797,-82.626,45.772,-82.51,45.727,-82.409,45.698,-82.323,45.684,-82.256,45.66,-82.208,45.627,-82.091,45.591,-82.076,45.57,-82.055,45.561,-82.029,45.566,-81.821,45.692,-81.76,45.698,-81.76,45.685,-81.865,45.611,-81.902,45.603,-81.922,45.604,-81.977,45.57,-81.977,45.561,-81.946,45.566,-81.914,45.557,-81.881,45.535,-81.828,45.547,-81.755,45.591,-81.686,45.657,-81.593,45.792,-81.601,45.801,-81.685,45.801,-81.699,45.815,-81.673,45.845,-81.665,45.872,-81.675,45.896,-81.698,45.894,-81.735,45.864,-81.763,45.821,-81.796,45.74,-81.804,45.743,-81.838,45.788,-81.842,45.823,-81.828,45.862,-81.841,45.885,-81.879,45.892,-81.897,45.91,-81.895,45.939,-81.909,45.962,-81.939,45.979,-81.987,45.977,-82.055,45.955,-82.106,45.919,-82.143,45.869,-82.175,45.871,-82.205,45.924,-82.236,45.946,-82.268,45.936,-82.288,45.945,-82.295,45.973,-82.332,45.982,-82.401,45.971,-82.444,45.955,-82.463,45.934,-82.481,45.93,-82.498,45.944,-82.527,45.939,-82.569,45.914,-82.58,45.896,-82.556,45.87,-82.56,45.86,-82.52,45.849,-82.515,45.833,-82.531,45.806,-82.554,45.807,-82.583,45.837,-82.608,45.848,-82.627,45.839,-82.645,45.843,-82.66,45.86,-82.691,45.863,-82.737,45.852,-82.765,45.858,-82.774,45.88,-82.793,45.898,-82.823,45.91,-82.832,45.936,-82.821,45.977,-82.845,45.99,-82.905,45.978,-82.945,45.963,-82.965,45.945,-82.996,45.943,-83.035,45.957,-83.069,45.955,-83.097,45.935,-83.125,45.938,-83.151,45.963,-83.176,45.969,-83.201,45.955,-83.216,45.933,-83.222,45.902,-83.16,45.875,-82.962,45.833],[-81.713,45.526,-81.735,45.531,-81.829,45.498,-81.837,45.484,-81.833,45.473,-81.815,45.463,-81.788,45.466,-81.727,45.5,-81.713,45.526],[-80.173,44.796,-80.174,44.809,-80.158,44.848,-80.171,44.865,-80.216,44.872,-80.236,44.865,-80.233,44.843,-80.216,44.823,-80.173,44.796]]]},{"n":"Laguna Madre","z":2,"b":[-97.833,23.915,-97.427,25.447],"p":[[[-97.427,25.354,-97.456,25.273,-97.537,25.082,-97.612,24.782,-97.687,24.476,-97.72,24.255,-97.758,23.915,-97.765,23.917,-97.775,24.082,-97.759,24.159,-97.75,24.264,-97.741,24.391,-97.768,24.436,-97.805,24.444,-97.828,24.477,-97.833,24.517,-97.824,24.544,-97.8,24.546,-97.742,24.502,-97.725,24.501,-97.689,24.663,-97.7,24.709,-97.703,24.845,-97.643,24.908,-97.655,24.937,-97.707,24.966,-97.729,24.994,-97.699,25.159,-97.7,25.201,-97.706,25.225,-97.754,25.257,-97.721,25.288,-97.68,25.346,-97.654,25.365,-97.643,25.364,-97.611,25.326,-97.576,25.312,-97.543,25.312,-97.531,25.327,-97.518,25.435,-97.508,25.447,-97.473,25.44,-97.438,25.406,-97.427,25.354]]]},{"n":"Wollaston","z":2,"b":[-103.832,57.695,-102.791,58.552],"p":[[[-102.837,58.33,-102.862,58.318,-102.911,58.323,-102.948,58.306,-102.982,58.275,-103.001,58.243,-102.983,58.22,-102.939,58.198,-102.941,58.173,-103.056,58.124,-103.102,58.134,-103.231,58.081,-103.259,58.053,-103.281,57.984,-103.281,57.897,-103.269,57.87,-103.245,57.851,-103.218,57.844,-103.211,57.828,-103.269,57.738,-103.33,57.697,-103.36,57.695,-103.347,57.729,-103.306,57.778,-103.314,57.828,-103.359,57.861,-103.399,57.868,-103.469,57.877,-103.559,57.869,-103.728,57.796,-103.794,57.777,-103.829,57.779,-103.832,57.794,-103.796,57.823,-103.656,57.919,-103.67,57.939,-103.777,57.932,-103.803,57.947,-103.793,57.964,-103.697,58.019,-103.673,58.037,-103.672,58.054,-103.682,58.078,-103.709,58.102,-103.706,58.127,-103.654,58.173,-103.619,58.234,-103.613,58.267,-103.619,58.283,-103.662,58.284,-103.627,58.348,-103.61,58.409,-103.581,58.431,-103.55,58.455,-103.495,58.462,-103.384,58.432,-103.239,58.472,-103.223,58.485,-103.221,58.525,-103.205,58.544,-103.185,58.552,-103.141,58.533,-102.952,58.533,-102.946,58.503,-102.921,58.477,-102.803,58.412,-102.791,58.381,-102.837,58.33]]]},{"n":"Athabasca","z":2,"b":[-111.301,58.505,-106.018,59.6],"p":[[[-109.233,59.127,-109.279,59.127,-109.453,59.083,-109.886,59.006,-110.074,58.949,-110.276,58.86,-110.336,58.826,-110.403,58.768,-110.44,58.716,-110.435,58.699,-110.37,58.676,-110.376,58.644,-110.411,58.631,-110.673,58.63,-110.747,58.614,-110.777,58.583,-110.787,58.546,-110.812,58.512,-110.839,58.511,-110.877,58.629,-110.903,58.631,-110.944,58.621,-110.99,58.594,-111.036,58.522,-111.075,58.505,-111.093,58.527,-111.107,58.547,-111.11,58.572,-111.098,58.616,-111.262,58.703,-111.295,58.729,-111.301,58.756,-111.28,58.788,-111.194,58.788,-111.15,58.775,-111.161,58.739,-111.125,58.735,-111.019,58.764,-110.872,58.837,-110.612,59.041,-110.483,59.11,-110.352,59.156,-110.035,59.297,-109.875,59.357,-109.835,59.389,-109.777,59.413,-109.799,59.433,-109.801,59.445,-109.705,59.504,-109.623,59.543,-109.547,59.57,-109.398,59.597,-109.26,59.6,-109.172,59.587,-109.036,59.498,-108.828,59.51,-108.795,59.504,-108.863,59.406,-108.857,59.387,-108.748,59.387,-108.538,59.435,-108.219,59.473,-107.953,59.434,-107.558,59.396,-107.239,59.348,-106.816,59.327,-106.609,59.32,-106.021,59.29,-106.018,59.271,-106.042,59.252,-106.71,59.263,-107.205,59.284,-107.452,59.304,-107.589,59.292,-107.606,59.3,-107.626,59.331,-107.708,59.34,-107.734,59.336,-107.757,59.32,-107.786,59.288,-107.819,59.229,-107.837,59.216,-107.958,59.204,-108.211,59.161,-108.75,59.12,-108.942,59.115,-109.103,59.098,-109.163,59.102,-109.233,59.127]]]},{"n":"Mistassini","z":2,"b":[-74.22,50.038,-72.69,51.397],"p":[[[-72.9,51.146,-72.988,51.104,-73.114,50.997,-73.135,50.989,-73.254,50.913,-73.371,50.845,-73.459,50.793,-73.458,50.814,-73.394,50.893,-73.262,50.977,-73.189,51.069,-73.175,51.071,-73.151,51.07,-73.132,51.09,-73.129,51.106,-73.143,51.145,-73.151,51.156,-73.173,51.158,-73.293,51.077,-73.396,50.994,-73.573,50.835,-73.599,50.825,-73.631,50.824,-73.771,50.641,-73.8,50.595,-73.817,50.499,-73.81,50.452,-73.799,50.412,-73.788,50.39,-73.807,50.388,-73.832,50.413,-73.843,50.406,-73.837,50.341,-73.819,50.301,-73.8,50.236,-73.818,50.211,-73.904,50.162,-74.008,50.12,-74.034,50.091,-74.076,50.043,-74.152,50.038,-74.22,50.058,-74.2,50.076,-74.098,50.092,-74.013,50.178,-73.879,50.218,-73.828,50.249,-73.836,50.276,-73.88,50.297,-73.896,50.327,-73.899,50.352,-73.878,50.381,-73.839,50.639,-73.887,50.59,-73.916,50.544,-73.926,50.514,-73.917,50.489,-73.919,50.474,-73.932,50.458,-74.002,50.478,-74.105,50.399,-74.132,50.403,-74.104,50.448,-73.954,50.722,-73.845,50.888,-73.856,50.898,-73.911,50.897,-73.916,50.91,-73.89,50.954,-73.905,50.963,-73.892,50.981,-73.641,51.078,-73.378,51.24,-73.202,51.32,-73.067,51.39,-72.986,51.397,-72.984,51.378,-73.067,51.317,-73.04,51.309,-72.89,51.354,-72.811,51.361,-72.792,51.353,-72.8,51.335,-72.851,51.305,-73.067,51.206,-73.092,51.178,-73.072,51.16,-73.041,51.154,-72.811,51.244,-72.723,51.254,-72.69,51.241,-72.719,51.217,-72.9,51.146]]]},{"n":"IJsselmeer","z":2,"b":[5.014,52.252,5.87,53.065],"p":[[[5.867,52.563,5.87,52.485,5.795,52.428,5.495,52.252,5.316,52.292,5.04,52.344,5.014,52.364,5.036,52.405,5.062,52.431,5.069,52.451,5.07,52.52,5.06,52.566,5.066,52.624,5.138,52.627,5.242,52.67,5.247,52.73,5.165,52.786,5.118,52.836,5.094,52.886,5.068,52.923,5.152,52.967,5.269,53.027,5.377,53.065,5.398,53.042,5.413,52.903,5.42,52.874,5.455,52.857,5.547,52.846,5.657,52.851,5.667,52.817,5.625,52.782,5.618,52.758,5.613,52.684,5.619,52.658,5.653,52.63,5.835,52.609,5.867,52.563],[5.801,52.466,5.834,52.502,5.829,52.535,5.797,52.565,5.736,52.582,5.658,52.588,5.539,52.558,5.377,52.49,5.169,52.379,5.184,52.358,5.242,52.334,5.424,52.298,5.499,52.302,5.548,52.324,5.621,52.376,5.741,52.429,5.801,52.466]]]},{"n":"Rybinsk","z":2,"b":[37.151,58.016,39.005,59.21],"p":[[[38.681,58.108,38.612,58.113,38.383,58.122,38.357,58.1,38.437,58.036,38.437,58.018,38.415,58.016,38.369,58.032,38.31,58.041,38.281,58.054,38.259,58.071,38.25,58.103,38.251,58.199,38.221,58.237,38.149,58.249,37.944,58.301,37.897,58.292,37.507,58.535,37.417,58.613,37.317,58.657,37.155,58.695,37.155,58.707,37.169,58.726,37.151,58.849,37.308,58.76,37.372,58.731,37.423,58.727,37.41,58.689,37.41,58.67,37.459,58.634,37.538,58.613,37.565,58.553,37.609,58.545,37.661,58.557,37.667,58.572,37.621,58.626,37.632,58.636,37.715,58.618,37.768,58.618,37.995,58.55,38.074,58.542,38.074,58.592,38.103,58.619,38.096,58.634,37.988,58.672,37.984,58.692,37.945,58.72,37.942,58.735,37.961,58.744,38.024,58.746,37.996,58.862,37.918,58.858,37.869,58.897,37.828,58.914,37.777,58.926,37.714,58.928,37.642,58.919,37.581,58.899,37.501,58.845,37.471,58.857,37.466,58.875,37.484,58.927,37.499,58.944,37.609,58.989,37.673,59.025,37.696,59.064,37.676,59.102,37.64,59.138,37.645,59.157,37.843,59.155,37.94,59.174,37.987,59.177,38.002,59.16,38.007,59.144,38.027,59.128,38.116,59.103,38.159,59.102,38.18,59.113,38.18,59.132,38.176,59.148,38.124,59.203,38.143,59.21,38.183,59.203,38.294,59.161,38.415,59.173,38.407,59.152,38.332,59.093,38.239,59.063,38.171,59.065,37.91,59.103,37.866,59.094,37.858,59.075,37.92,59.042,38.047,58.994,38.119,58.955,38.427,58.747,38.61,58.637,38.753,58.547,38.989,58.496,39.004,58.476,38.97,58.441,39,58.387,39.005,58.358,38.871,58.331,38.83,58.296,38.812,58.251,38.819,58.135,38.805,58.122,38.779,58.135,38.692,58.207,38.638,58.22,38.619,58.201,38.698,58.133,38.704,58.113,38.681,58.108]]]},{"n":"Issyk-Kul","z":2,"b":[76.215,42.168,78.303,42.734],"p":[[[78.303,42.588,78.292,42.564,78.158,42.55,78.014,42.487,77.871,42.428,77.852,42.402,77.854,42.367,77.716,42.24,77.595,42.2,77.515,42.182,77.137,42.168,77.023,42.173,76.921,42.19,76.578,42.291,76.432,42.32,76.329,42.33,76.263,42.328,76.227,42.389,76.215,42.435,76.226,42.461,76.453,42.489,76.747,42.565,76.965,42.612,77.19,42.647,77.337,42.67,77.535,42.651,77.684,42.702,78.064,42.727,78.249,42.734,78.151,42.672,78.119,42.642,78.136,42.608,78.303,42.588]]]},{"n":"Woods","z":2,"b":[-95.315,48.863,-94.041,49.993],"p":[[[-94.052,49.373,-94.041,49.358,-94.047,49.268,-94.065,49.238,-94.092,49.219,-94.119,49.206,-94.129,49.21,-94.115,49.231,-94.109,49.263,-94.182,49.33,-94.221,49.394,-94.278,49.407,-94.251,49.457,-94.254,49.471,-94.365,49.483,-94.474,49.472,-94.646,49.445,-94.745,49.409,-94.77,49.393,-94.79,49.353,-94.778,49.34,-94.64,49.354,-94.609,49.345,-94.577,49.298,-94.522,49.267,-94.464,49.232,-94.354,49.266,-94.303,49.253,-94.272,49.227,-94.267,49.194,-94.24,49.181,-94.099,49.167,-94.074,49.155,-94.117,49.126,-94.197,49.096,-94.265,49.114,-94.31,49.104,-94.324,49.071,-94.537,48.945,-94.713,48.863,-94.818,48.868,-94.916,48.926,-95.003,48.964,-95.037,48.959,-95.179,48.897,-95.274,48.886,-95.313,48.928,-95.315,48.97,-95.267,49.029,-95.266,49.06,-95.277,49.105,-95.261,49.137,-95.199,49.166,-95.085,49.176,-94.983,49.195,-94.936,49.24,-94.941,49.321,-95.009,49.342,-95.014,49.368,-94.941,49.4,-94.909,49.437,-94.822,49.512,-94.691,49.561,-94.645,49.603,-94.65,49.621,-94.688,49.626,-94.745,49.616,-94.796,49.599,-94.911,49.526,-94.985,49.497,-95.107,49.463,-95.139,49.472,-95.116,49.543,-95.152,49.622,-95.099,49.639,-95.008,49.635,-94.962,49.621,-94.973,49.584,-94.958,49.575,-94.926,49.576,-94.888,49.585,-94.84,49.613,-94.87,49.663,-94.823,49.69,-94.588,49.724,-94.56,49.764,-94.576,49.807,-94.678,49.953,-94.659,49.974,-94.615,49.993,-94.588,49.988,-94.539,49.931,-94.53,49.853,-94.496,49.804,-94.451,49.738,-94.346,49.724,-94.312,49.685,-94.332,49.639,-94.314,49.6,-94.362,49.576,-94.368,49.563,-94.302,49.515,-94.173,49.497,-94.134,49.448,-94.144,49.427,-94.052,49.373],[-94.702,49.191,-94.787,49.192,-94.784,49.17,-94.731,49.092,-94.657,49.09,-94.631,49.056,-94.594,49.036,-94.528,49.041,-94.506,49.118,-94.446,49.179,-94.448,49.202,-94.515,49.215,-94.621,49.191,-94.702,49.191]]]},{"n":"Nipigon","z":2,"b":[-89.092,49.389,-88.03,50.291],"p":[[[-88.03,49.961,-88.106,49.934,-88.145,49.912,-88.143,49.895,-88.108,49.868,-88.129,49.785,-88.112,49.711,-88.134,49.635,-88.166,49.58,-88.159,49.507,-88.18,49.466,-88.305,49.459,-88.368,49.434,-88.406,49.389,-88.439,49.394,-88.467,49.443,-88.494,49.463,-88.513,49.514,-88.542,49.549,-88.552,49.544,-88.547,49.488,-88.563,49.462,-88.622,49.428,-88.668,49.433,-88.706,49.442,-88.773,49.458,-88.786,49.488,-88.759,49.527,-88.71,49.587,-88.61,49.606,-88.59,49.608,-88.587,49.628,-88.607,49.647,-88.688,49.633,-88.744,49.631,-88.786,49.567,-88.823,49.54,-88.869,49.521,-88.909,49.51,-88.96,49.501,-89,49.529,-89.019,49.568,-89.016,49.583,-88.96,49.56,-88.92,49.56,-88.907,49.579,-88.906,49.618,-88.929,49.694,-88.93,49.755,-88.956,49.756,-89.008,49.714,-89.045,49.726,-89.092,49.776,-89.087,49.804,-89.069,49.824,-89.004,49.787,-88.949,49.81,-88.941,49.82,-88.95,49.855,-88.945,49.88,-88.888,49.933,-88.905,50.023,-88.893,50.057,-88.769,50.213,-88.705,50.254,-88.639,50.277,-88.551,50.239,-88.465,50.238,-88.356,50.162,-88.315,50.148,-88.288,50.15,-88.278,50.159,-88.288,50.178,-88.34,50.226,-88.415,50.265,-88.421,50.289,-88.384,50.291,-88.344,50.275,-88.232,50.213,-88.199,50.188,-88.171,50.135,-88.172,50.113,-88.204,50.111,-88.215,50.096,-88.222,50.059,-88.218,50.027,-88.235,49.991,-88.213,49.993,-88.169,50.028,-88.133,50.035,-88.084,50.001,-88.033,49.979,-88.03,49.961],[-88.554,50.022,-88.554,50.036,-88.59,50.047,-88.616,50.047,-88.63,50.044,-88.633,50.017,-88.639,49.985,-88.654,49.95,-88.688,49.927,-88.719,49.9,-88.747,49.879,-88.748,49.868,-88.716,49.85,-88.676,49.85,-88.621,49.82,-88.588,49.805,-88.582,49.816,-88.59,49.836,-88.611,49.862,-88.636,49.897,-88.636,49.916,-88.625,49.951,-88.588,49.983,-88.567,49.997,-88.554,50.022],[-88.385,49.647,-88.374,49.689,-88.393,49.7,-88.422,49.685,-88.489,49.66,-88.49,49.612,-88.399,49.617,-88.385,49.647]]]},{"n":"Reindeer","z":2,"b":[-103.298,56.277,-101.533,58.107],"p":[[[-101.724,57.91,-101.678,57.918,-101.544,57.92,-101.533,57.88,-101.543,57.816,-101.63,57.757,-101.735,57.647,-101.828,57.579,-101.961,57.517,-102.048,57.484,-102.106,57.427,-102.094,57.368,-101.888,57.305,-101.836,57.27,-101.864,57.25,-101.957,57.239,-102.032,57.191,-102.048,57.114,-101.999,57.054,-101.962,57.025,-102.041,56.965,-102.127,56.915,-102.177,56.864,-102.313,56.838,-102.34,56.81,-102.356,56.771,-102.398,56.697,-102.437,56.658,-102.439,56.627,-102.465,56.608,-102.544,56.588,-102.601,56.564,-102.885,56.473,-102.927,56.441,-102.938,56.402,-102.977,56.369,-103.044,56.38,-103.087,56.419,-103.137,56.414,-103.201,56.358,-103.232,56.309,-103.241,56.278,-103.272,56.277,-103.288,56.304,-103.271,56.387,-103.261,56.437,-103.257,56.475,-103.298,56.496,-103.293,56.543,-103.241,56.574,-103.192,56.555,-103.153,56.62,-103.129,56.764,-103.106,56.853,-103.069,56.849,-103.08,56.677,-103.051,56.569,-102.978,56.562,-102.862,56.683,-102.794,56.709,-102.732,56.716,-102.692,56.742,-102.677,56.773,-102.625,56.787,-102.57,56.83,-102.544,56.93,-102.554,56.998,-102.584,57.046,-102.676,57.083,-102.742,57.101,-102.722,57.154,-102.693,57.21,-102.721,57.245,-102.789,57.283,-102.852,57.299,-102.919,57.295,-102.933,57.364,-102.89,57.451,-102.791,57.504,-102.694,57.564,-102.674,57.597,-102.693,57.637,-102.692,57.688,-102.64,57.702,-102.523,57.73,-102.44,57.799,-102.205,57.931,-102.149,57.993,-102.151,58.038,-102.052,58.107,-101.991,58.088,-102.013,58.003,-101.971,57.963,-101.913,57.991,-101.868,58.054,-101.819,58.065,-101.803,58.02,-101.847,57.898,-101.846,57.866,-101.815,57.843,-101.733,57.873,-101.724,57.91]]]},{"n":"Oneida","z":3,"b":[-76.096,43.177,-75.733,43.263],"p":[[[-75.75,43.226,-75.733,43.206,-75.8,43.181,-75.959,43.177,-76.096,43.238,-76.096,43.254,-76.06,43.263,-75.861,43.248,-75.75,43.226]]]},{"n":"Iliamna","z":3,"b":[-155.914,59.335,-153.971,59.791],"p":[[[-153.971,59.769,-154.109,59.718,-154.23,59.687,-154.371,59.665,-154.534,59.628,-154.563,59.592,-154.555,59.548,-154.579,59.503,-154.583,59.474,-154.614,59.451,-154.685,59.443,-154.907,59.432,-155.743,59.335,-155.881,59.377,-155.914,59.456,-155.846,59.538,-155.26,59.662,-154.993,59.705,-154.328,59.791,-154.19,59.781,-154.074,59.78,-153.971,59.769]]]},{"n":"","z":3,"b":[-156.884,57.633,-155.961,58.086],"p":[[[-155.961,57.87,-156.021,57.837,-156.021,57.812,-155.996,57.793,-155.993,57.763,-156.04,57.705,-156.13,57.64,-156.159,57.633,-156.127,57.689,-156.115,57.749,-156.161,57.797,-156.358,57.836,-156.605,57.879,-156.678,57.888,-156.832,57.931,-156.884,57.999,-156.817,58.067,-156.638,58.086,-156.279,58.006,-155.976,57.909,-155.961,57.87]]]},{"n":"Tahoe","z":3,"b":[-120.139,38.945,-119.918,39.254],"p":[[[-119.943,39.025,-119.966,38.957,-120.033,38.945,-120.081,38.971,-120.113,39.06,-120.139,39.091,-120.132,39.143,-120.059,39.221,-119.986,39.242,-119.962,39.254,-119.936,39.254,-119.918,39.207,-119.943,39.025]]]},{"n":"","z":3,"b":[35.197,-14.739,35.324,-14.51],"p":[[[35.324,-14.619,35.293,-14.684,35.27,-14.733,35.249,-14.739,35.197,-14.682,35.198,-14.58,35.208,-14.531,35.24,-14.51,35.268,-14.517,35.298,-14.563,35.324,-14.619]]]},{"n":"Zaysan","z":3,"b":[83.027,47.699,84.516,49.726],"p":[[[84.516,47.93,84.427,47.857,84.394,47.816,84.357,47.732,84.296,47.699,84.242,47.706,83.994,47.843,83.766,47.951,83.625,48.013,83.445,48.049,83.259,48.095,83.083,48.178,83.027,48.212,83.039,48.25,83.182,48.249,83.275,48.261,83.435,48.27,83.498,48.305,83.533,48.375,83.461,48.432,83.392,48.5,83.387,48.554,83.417,48.597,83.423,48.743,83.471,48.858,83.589,48.962,83.747,49.039,83.872,49.063,84.036,49.111,84.121,49.159,84.1,49.238,83.972,49.329,83.849,49.417,83.724,49.44,83.66,49.443,83.608,49.486,83.539,49.532,83.479,49.538,83.426,49.573,83.365,49.626,83.364,49.644,83.391,49.652,83.608,49.606,83.74,49.631,83.89,49.72,83.964,49.726,83.961,49.703,83.818,49.634,83.797,49.597,83.757,49.564,83.729,49.535,83.823,49.484,84.288,49.238,84.361,49.161,84.329,49.125,84.149,49.086,83.864,48.994,83.777,48.975,83.609,48.91,83.514,48.813,83.487,48.718,83.517,48.617,83.613,48.437,83.774,48.41,83.919,48.397,83.907,48.374,83.682,48.353,83.596,48.303,83.581,48.267,83.634,48.242,83.74,48.198,83.881,48.152,84.051,48.083,84.167,48.056,84.334,48.023,84.468,47.969,84.516,47.93]]]},{"n":"Winnebago","z":3,"b":[-88.519,43.808,-88.321,44.196],"p":[[[-88.325,44.068,-88.334,43.938,-88.383,43.866,-88.431,43.808,-88.465,43.813,-88.492,43.939,-88.519,44.029,-88.484,44.11,-88.418,44.195,-88.358,44.196,-88.321,44.18,-88.325,44.068]]]},{"n":"Nettilling","z":3,"b":[-71.438,65.954,-69.221,67.028],"p":[[[-71.438,66.596,-71.421,66.628,-71.373,66.663,-71.331,66.726,-71.209,66.862,-71.194,66.945,-71.227,67.001,-71.174,67.028,-71.001,67.02,-70.826,66.994,-70.788,66.967,-70.737,66.923,-70.63,66.868,-70.611,66.832,-70.569,66.794,-70.463,66.753,-70.353,66.737,-70.288,66.748,-70.211,66.731,-69.945,66.688,-69.736,66.628,-69.621,66.602,-69.49,66.581,-69.466,66.547,-69.527,66.503,-69.646,66.476,-69.646,66.442,-69.553,66.432,-69.423,66.429,-69.402,66.432,-69.347,66.449,-69.253,66.451,-69.221,66.406,-69.34,66.335,-69.471,66.29,-69.511,66.268,-69.53,66.238,-69.557,66.212,-69.585,66.203,-69.636,66.219,-69.648,66.246,-69.684,66.243,-69.732,66.22,-69.742,66.205,-69.77,66.203,-69.89,66.254,-69.975,66.258,-70.185,66.22,-70.376,66.195,-70.519,66.265,-70.582,66.289,-70.619,66.282,-70.656,66.252,-70.677,66.226,-70.71,66.204,-70.851,66.156,-71.104,65.991,-71.226,65.954,-71.311,65.969,-71.321,65.987,-71.221,66.024,-71.179,66.083,-71.194,66.151,-71.145,66.215,-71.15,66.256,-71.147,66.29,-71.127,66.331,-71.163,66.372,-71.231,66.418,-71.306,66.515,-71.438,66.596]]]},{"n":"Turkana","z":3,"b":[35.847,2.405,36.697,4.654],"p":[[[36.685,2.538,36.633,2.425,36.571,2.405,36.535,2.448,36.424,2.734,36.331,2.874,36.242,2.966,36.2,2.974,36.154,3.038,36.113,3.167,36.038,3.264,35.966,3.368,35.937,3.482,35.915,3.519,35.872,3.598,35.847,3.775,35.857,3.876,35.888,4.038,35.919,4.27,35.926,4.385,35.95,4.553,35.995,4.566,36.045,4.539,36.074,4.555,36.098,4.623,36.132,4.653,36.155,4.654,36.174,4.632,36.197,4.431,36.215,4.148,36.213,4.054,36.226,3.88,36.221,3.473,36.311,3.207,36.427,3.011,36.538,2.924,36.652,2.858,36.697,2.727,36.685,2.538]]]},{"n":"Dubawnt","z":3,"b":[-102.174,62.664,-100.644,63.546],"p":[[[-101.935,63.221,-101.807,63.267,-101.749,63.337,-101.688,63.37,-101.635,63.375,-101.611,63.382,-101.583,63.397,-101.584,63.428,-101.548,63.456,-101.457,63.443,-101.396,63.418,-101.365,63.397,-101.319,63.394,-101.293,63.415,-101.332,63.467,-101.276,63.491,-101.137,63.43,-101.081,63.439,-101.061,63.46,-101.008,63.463,-100.902,63.53,-100.856,63.52,-100.799,63.54,-100.706,63.546,-100.644,63.468,-100.673,63.373,-100.77,63.319,-100.823,63.297,-100.864,63.294,-100.917,63.301,-100.983,63.303,-101.041,63.292,-101.06,63.268,-101.064,63.243,-101.113,63.171,-101.104,63.102,-101.061,63.06,-101.007,63.052,-100.884,63.007,-100.884,62.956,-100.982,62.911,-101.028,62.896,-101.059,62.858,-101.023,62.803,-101.028,62.761,-101.138,62.713,-101.29,62.666,-101.382,62.683,-101.392,62.721,-101.354,62.766,-101.322,62.828,-101.33,62.891,-101.381,62.928,-101.414,62.911,-101.434,62.845,-101.473,62.791,-101.499,62.74,-101.512,62.675,-101.529,62.664,-101.554,62.684,-101.553,62.723,-101.59,62.776,-101.673,62.797,-101.755,62.801,-101.823,62.881,-101.916,62.877,-101.989,62.887,-102.035,62.925,-102.048,63.004,-102.091,63.066,-102.097,63.076,-102.127,63.089,-102.174,63.122,-102.16,63.165,-102.079,63.194,-101.935,63.221]]]},{"n":"Baker","z":3,"b":[-96.273,63.996,-94.225,64.325],"p":[[[-94.626,64.159,-94.556,64.149,-94.456,64.14,-94.346,64.135,-94.247,64.124,-94.225,64.076,-94.312,64.012,-94.455,63.996,-94.624,64.031,-94.86,64.066,-95.299,64.079,-95.502,64.058,-95.671,64.056,-95.784,64.056,-95.839,64.084,-95.946,64.127,-96.227,64.193,-96.273,64.301,-96.203,64.325,-95.744,64.302,-95.251,64.274,-95.037,64.239,-94.917,64.225,-94.793,64.217,-94.65,64.197,-94.626,64.159]]]},{"n":"Vistula Lagoon","z":3,"b":[19.717,54.434,20.405,54.71],"p":[[[19.764,54.434,19.717,54.482,19.879,54.589,19.942,54.647,19.958,54.682,19.988,54.71,20.033,54.702,20.084,54.673,20.208,54.684,20.405,54.682,20.102,54.583,19.929,54.517,19.769,54.438,19.764,54.434]]]},{"n":"Vistula Lagoon","z":3,"b":[19.251,54.268,19.764,54.482],"p":[[[19.717,54.482,19.764,54.434,19.738,54.412,19.658,54.374,19.586,54.348,19.481,54.3,19.347,54.268,19.251,54.311,19.279,54.338,19.503,54.381,19.616,54.426,19.717,54.482]]]},{"n":"Champlain","z":3,"b":[-73.451,43.757,-73.088,45.078],"p":[[[-73.373,44.148,-73.405,44.054,-73.369,43.876,-73.356,43.781,-73.368,43.757,-73.386,43.81,-73.403,43.866,-73.43,44.005,-73.447,44.031,-73.451,44.083,-73.444,44.193,-73.354,44.281,-73.377,44.393,-73.4,44.431,-73.444,44.638,-73.4,44.775,-73.383,44.936,-73.347,45.018,-73.324,45.029,-73.308,44.956,-73.296,44.914,-73.285,44.913,-73.255,44.942,-73.204,45.022,-73.147,45.073,-73.104,45.078,-73.088,45.046,-73.111,45.009,-73.163,44.985,-73.193,44.957,-73.173,44.919,-73.176,44.874,-73.194,44.707,-73.214,44.586,-73.227,44.563,-73.263,44.542,-73.256,44.507,-73.229,44.493,-73.228,44.445,-73.236,44.433,-73.248,44.437,-73.271,44.397,-73.288,44.32,-73.326,44.234,-73.373,44.148]]]},{"n":"Salton Sea","z":3,"b":[-116.062,33.138,-115.612,33.522],"p":[[[-115.612,33.274,-115.616,33.223,-115.698,33.138,-115.789,33.147,-115.911,33.303,-116.062,33.433,-116.053,33.502,-116.01,33.522,-115.948,33.519,-115.884,33.452,-115.66,33.321,-115.612,33.274]]]},{"n":"Martre","z":3,"b":[-118.628,63.113,-117.306,63.57],"p":[[[-117.319,63.209,-117.306,63.185,-117.36,63.146,-117.554,63.113,-117.738,63.113,-117.785,63.133,-118.107,63.223,-118.137,63.25,-118.164,63.261,-118.201,63.283,-118.315,63.291,-118.467,63.31,-118.627,63.337,-118.628,63.375,-118.564,63.428,-118.344,63.513,-118.1,63.57,-118.001,63.544,-117.915,63.466,-117.764,63.404,-117.63,63.384,-117.575,63.372,-117.57,63.358,-117.47,63.305,-117.435,63.298,-117.339,63.224,-117.319,63.209]]]},{"n":"Llanquihue","z":3,"b":[-72.994,-41.294,-72.599,-40.974],"p":[[[-72.967,-41.285,-72.989,-41.228,-72.994,-41.15,-72.974,-41.087,-72.912,-41.021,-72.849,-40.974,-72.776,-40.989,-72.703,-41.026,-72.639,-41.1,-72.599,-41.184,-72.629,-41.214,-72.727,-41.2,-72.876,-41.294,-72.967,-41.285]]]},{"n":"Hongze Lake","z":3,"b":[117.809,32.885,118.861,33.645],"p":[[[118.605,33.172,118.574,33.141,118.519,33.036,118.492,32.998,118.431,32.973,118.267,32.925,118.227,32.897,118.183,32.885,118.171,32.906,118.192,32.941,118.181,32.971,118.142,32.986,118.088,32.958,118.066,32.936,118.05,32.964,118.084,33.005,118.13,33.028,118.226,32.991,118.24,33.008,118.234,33.046,118.182,33.139,118.102,33.144,117.979,33.142,117.915,33.126,117.892,33.065,117.838,33.038,117.809,33.048,117.851,33.062,117.874,33.093,117.886,33.157,117.912,33.181,117.992,33.187,118.114,33.215,118.175,33.21,118.227,33.143,118.275,33.045,118.34,32.994,118.407,33.01,118.417,33.038,118.44,33.068,118.46,33.099,118.47,33.128,118.454,33.16,118.322,33.182,118.249,33.221,118.233,33.288,118.273,33.267,118.318,33.268,118.392,33.338,118.446,33.407,118.49,33.401,118.57,33.372,118.607,33.376,118.547,33.437,118.488,33.54,118.493,33.623,118.544,33.645,118.571,33.62,118.608,33.515,118.664,33.463,118.774,33.443,118.861,33.357,118.861,33.339,118.834,33.304,118.778,33.176,118.716,33.089,118.681,33.077,118.678,33.124,118.653,33.174,118.605,33.172]]]},{"n":"Khövsgöl Nuur","z":3,"b":[100.179,50.494,100.787,51.61],"p":[[[100.787,51.397,100.77,51.293,100.749,51.222,100.73,51.138,100.685,51.019,100.688,50.983,100.507,50.809,100.472,50.739,100.445,50.659,100.473,50.625,100.473,50.603,100.444,50.568,100.37,50.538,100.193,50.494,100.179,50.531,100.194,50.594,100.228,50.654,100.248,50.743,100.296,51.109,100.284,51.25,100.356,51.352,100.402,51.392,100.388,51.441,100.38,51.466,100.491,51.556,100.525,51.597,100.559,51.61,100.752,51.447,100.787,51.397]]]},{"n":"Uvs","z":3,"b":[92.235,50.005,93.362,50.658],"p":[[[92.316,50.302,92.235,50.413,92.348,50.475,92.546,50.54,92.789,50.617,92.893,50.658,92.965,50.638,92.99,50.608,92.978,50.54,93.076,50.428,93.227,50.364,93.259,50.358,93.254,50.327,93.325,50.308,93.362,50.282,93.301,50.255,93.216,50.251,93.095,50.198,93.018,50.145,92.913,50.056,92.722,50.01,92.601,50.005,92.498,50.053,92.382,50.142,92.337,50.228,92.316,50.302]]]},{"n":"Mweru","z":3,"b":[28.324,-9.505,29.128,-8.513],"p":[[[29.128,-8.662,29.107,-8.74,29.021,-8.922,28.891,-9.092,28.769,-9.272,28.696,-9.396,28.592,-9.407,28.521,-9.404,28.504,-9.441,28.51,-9.485,28.474,-9.505,28.377,-9.503,28.333,-9.45,28.324,-9.364,28.326,-9.237,28.367,-9.101,28.643,-8.768,28.867,-8.513,28.954,-8.521,29.078,-8.6,29.128,-8.662]]]},{"n":"Khanka","z":3,"b":[132.005,44.529,132.81,45.329],"p":[[[132.81,45.052,132.743,44.943,132.614,44.711,132.46,44.613,132.366,44.529,132.276,44.569,132.238,44.606,132.241,44.63,132.169,44.674,132.069,44.743,132.084,44.851,132.109,44.969,132.015,45.074,132.017,45.127,132.005,45.214,132.071,45.287,132.23,45.328,132.402,45.329,132.579,45.296,132.725,45.23,132.808,45.149,132.81,45.052]]]},{"n":"Tonlé Sap","z":3,"b":[103.699,12.426,104.591,13.25],"p":[[[104.57,12.426,104.532,12.458,104.464,12.506,104.401,12.508,104.258,12.564,104.14,12.671,104.105,12.789,104.008,12.804,103.895,12.867,103.806,12.947,103.755,13.039,103.724,13.121,103.699,13.207,103.731,13.25,103.842,13.235,103.981,13.179,104.1,13.036,104.199,12.845,104.235,12.764,104.268,12.739,104.37,12.747,104.44,12.724,104.468,12.64,104.468,12.571,104.471,12.559,104.498,12.54,104.555,12.514,104.558,12.486,104.565,12.463,104.591,12.432,104.57,12.426]]]},{"n":"Qinghai","z":3,"b":[99.65,36.557,100.753,37.218],"p":[[[100.753,36.811,100.746,36.781,100.723,36.745,100.723,36.673,100.704,36.589,100.628,36.557,100.523,36.58,100.431,36.641,100.228,36.645,99.958,36.679,99.827,36.739,99.7,36.853,99.65,36.911,99.681,36.947,99.762,36.948,99.816,36.955,99.834,36.988,99.804,37.067,99.787,37.149,99.836,37.182,99.956,37.218,100.104,37.202,100.275,37.15,100.448,37.025,100.586,36.903,100.753,36.811]]]},{"n":"Yarroto Vtoroe","z":3,"b":[70.93,67.988,71.381,68.103],"p":[[[71.239,67.99,71.133,68,71.065,67.996,70.971,68.012,70.93,68.065,70.964,68.098,71.093,68.103,71.277,68.059,71.381,68.025,71.381,67.997,71.343,67.988,71.239,67.99]]]},{"n":"Tharthar","z":3,"b":[43.066,33.706,43.493,34.329],"p":[[[43.261,33.728,43.196,33.792,43.106,33.915,43.074,34.113,43.066,34.258,43.084,34.315,43.116,34.329,43.239,34.141,43.362,34.043,43.467,33.95,43.493,33.888,43.476,33.807,43.478,33.729,43.42,33.706,43.332,33.717,43.261,33.728]]]},{"n":"Milh","z":3,"b":[43.423,32.531,43.878,33.079],"p":[[[43.878,32.688,43.863,32.612,43.793,32.535,43.739,32.531,43.668,32.553,43.629,32.58,43.577,32.596,43.482,32.642,43.438,32.715,43.423,32.81,43.432,32.869,43.451,32.879,43.475,32.919,43.469,33.011,43.508,33.06,43.55,33.079,43.583,33.039,43.72,32.808,43.821,32.732,43.878,32.688]]]},{"n":"Dead Sea","z":3,"b":[35.402,31.325,35.585,31.766],"p":[[[35.585,31.724,35.567,31.574,35.545,31.359,35.521,31.327,35.423,31.325,35.403,31.348,35.402,31.492,35.421,31.619,35.463,31.71,35.522,31.756,35.559,31.766,35.585,31.724]]]},{"n":"Pihkva","z":3,"b":[27.497,57.86,28.169,58.215],"p":[[[27.497,58.184,27.569,58.215,27.592,58.175,27.714,58.164,27.865,58.173,27.973,58.133,28.097,58.044,28.149,57.975,28.169,57.915,28.102,57.86,27.992,57.868,27.911,57.896,27.875,57.92,27.882,57.937,27.803,57.99,27.678,58.019,27.624,58.014,27.497,58.184]]]},{"n":"Saint-Jean","z":4,"b":[-72.331,48.444,-71.774,48.755],"p":[[[-72.055,48.454,-72.133,48.469,-72.165,48.481,-72.242,48.575,-72.306,48.619,-72.331,48.669,-72.291,48.733,-72.158,48.755,-72,48.724,-71.907,48.681,-71.846,48.659,-71.776,48.61,-71.774,48.561,-71.781,48.518,-71.823,48.475,-71.924,48.444,-72.055,48.454]]]},{"n":"Keuka","z":4,"b":[-77.203,42.425,-77.058,42.648],"p":[[[-77.058,42.644,-77.107,42.521,-77.191,42.425,-77.203,42.437,-77.17,42.512,-77.148,42.568,-77.113,42.569,-77.077,42.648,-77.058,42.644]]]},{"n":"Seneca","z":4,"b":[-76.97,42.416,-76.876,42.878],"p":[[[-76.879,42.416,-76.91,42.459,-76.918,42.591,-76.954,42.73,-76.97,42.878,-76.954,42.87,-76.882,42.683,-76.883,42.537,-76.876,42.444,-76.879,42.416]]]},{"n":"Cayuga","z":4,"b":[-76.773,42.489,-76.52,42.943],"p":[[[-76.52,42.501,-76.528,42.489,-76.586,42.547,-76.667,42.603,-76.773,42.778,-76.751,42.925,-76.735,42.943,-76.701,42.758,-76.665,42.668,-76.603,42.594,-76.52,42.501]]]},{"n":"Kainji","z":4,"b":[4.4,9.865,4.685,10.644],"p":[[[4.685,10.365,4.679,10.344,4.598,10.196,4.6,10.113,4.617,10.003,4.659,9.885,4.626,9.865,4.594,9.867,4.566,9.903,4.537,10.028,4.496,10.066,4.474,10.07,4.468,10.098,4.495,10.126,4.51,10.148,4.506,10.174,4.47,10.186,4.464,10.196,4.485,10.229,4.465,10.306,4.4,10.375,4.4,10.403,4.438,10.418,4.451,10.438,4.459,10.46,4.47,10.627,4.492,10.644,4.567,10.603,4.606,10.617,4.656,10.607,4.68,10.585,4.65,10.517,4.667,10.41,4.685,10.365]]]},{"n":"Urmia","z":4,"b":[45.015,37.135,45.858,38.264],"p":[[[45.666,37.742,45.747,37.701,45.749,37.663,45.755,37.578,45.858,37.426,45.831,37.341,45.703,37.303,45.663,37.288,45.613,37.212,45.536,37.135,45.449,37.149,45.365,37.227,45.292,37.438,45.257,37.615,45.253,37.685,45.234,37.764,45.087,37.885,45.08,37.988,45.155,38.045,45.188,38.108,45.171,38.144,45.092,38.14,45.015,38.144,45.026,38.193,45.106,38.264,45.357,38.264,45.458,38.173,45.487,38.073,45.447,37.932,45.436,37.806,45.483,37.754,45.546,37.754,45.666,37.742]]]},{"n":"Cree","z":4,"b":[-107.091,57.224,-106.087,57.713],"p":[[[-106.258,57.696,-106.181,57.681,-106.11,57.655,-106.114,57.595,-106.106,57.56,-106.087,57.535,-106.097,57.507,-106.095,57.465,-106.14,57.414,-106.193,57.398,-106.254,57.374,-106.374,57.369,-106.541,57.326,-106.608,57.337,-106.698,57.327,-106.732,57.342,-106.764,57.356,-106.804,57.356,-106.884,57.321,-106.933,57.287,-106.97,57.254,-107.035,57.229,-107.079,57.224,-107.091,57.236,-107.073,57.326,-107.083,57.361,-107.06,57.386,-107.028,57.426,-107.064,57.457,-107.069,57.476,-107.018,57.477,-106.924,57.462,-106.831,57.49,-106.655,57.597,-106.422,57.649,-106.316,57.705,-106.28,57.713,-106.258,57.696]]]},{"n":"","z":4,"b":[-99.764,62.807,-99.142,63.187],"p":[[[-99.606,62.959,-99.558,63.004,-99.348,63.173,-99.298,63.187,-99.303,63.155,-99.332,63.1,-99.304,63.048,-99.234,63.025,-99.159,62.991,-99.142,62.929,-99.24,62.873,-99.386,62.817,-99.573,62.807,-99.73,62.819,-99.762,62.848,-99.764,62.873,-99.606,62.959]]]},{"n":"Kasba","z":4,"b":[-102.6,59.929,-101.849,60.649],"p":[[[-102.549,60.322,-102.485,60.314,-102.379,60.368,-102.315,60.424,-102.367,60.443,-102.437,60.5,-102.434,60.585,-102.352,60.633,-102.261,60.649,-102.215,60.615,-102.177,60.561,-102.158,60.512,-102.108,60.457,-101.998,60.375,-101.886,60.238,-101.849,60.112,-101.878,60.037,-101.885,59.983,-101.983,60.01,-101.988,59.965,-102.023,59.929,-102.085,60.019,-102.275,60.211,-102.382,60.25,-102.477,60.236,-102.588,60.246,-102.6,60.309,-102.549,60.322]]]},{"n":"Sarygamysh","z":4,"b":[57.053,41.573,57.865,42.335],"p":[[[57.865,41.773,57.818,41.7,57.685,41.636,57.527,41.594,57.43,41.579,57.346,41.573,57.247,41.653,57.174,41.735,57.116,41.731,57.062,41.725,57.053,41.773,57.094,41.968,57.183,42.185,57.273,42.303,57.392,42.335,57.55,42.261,57.627,42.15,57.613,42.105,57.632,42.066,57.682,42.01,57.7,41.913,57.845,41.838,57.865,41.773]]]},{"n":"Chany","z":4,"b":[76.734,54.614,78.049,55.057],"p":[[[76.747,54.832,76.869,54.883,77.067,54.919,77.134,54.923,77.164,54.887,77.245,54.856,77.294,54.89,77.307,54.925,77.327,54.968,77.43,55.02,77.57,55.053,77.636,55.057,77.667,55.054,77.696,55.047,77.724,55.025,77.878,55.009,78.033,54.975,78.049,54.927,77.938,54.891,77.807,54.863,77.777,54.859,77.694,54.832,77.643,54.803,77.636,54.769,77.693,54.741,77.73,54.692,77.741,54.661,77.694,54.634,77.6,54.614,77.565,54.624,77.574,54.647,77.522,54.665,77.435,54.684,77.354,54.694,77.245,54.723,77.052,54.732,77.017,54.73,76.943,54.715,76.826,54.701,76.734,54.717,76.753,54.787,76.747,54.832]]]},{"n":"Alakol","z":4,"b":[81.399,45.764,82.133,46.456],"p":[[[82.133,45.764,81.749,45.872,81.555,45.965,81.427,46.055,81.404,46.151,81.41,46.235,81.399,46.305,81.443,46.392,81.53,46.452,81.65,46.456,81.761,46.431,81.841,46.396,81.912,46.351,81.976,46.279,82.025,46.185,82.038,46.08,82.064,45.944,82.133,45.764]]]},{"n":"","z":4,"b":[-90.58,30.179,-90.425,30.348],"p":[[[-90.425,30.274,-90.467,30.198,-90.537,30.179,-90.574,30.197,-90.58,30.235,-90.557,30.284,-90.493,30.348,-90.433,30.326,-90.425,30.274]]]},{"n":"Wiyâshâkimî","z":4,"b":[-74.825,55.935,-73.985,56.336],"p":[[[-73.99,56.026,-74.07,55.975,-74.184,55.957,-74.228,55.946,-74.221,55.935,-74.249,55.938,-74.276,55.951,-74.231,55.985,-74.336,56.024,-74.564,56.067,-74.732,56.091,-74.8,56.137,-74.825,56.214,-74.819,56.273,-74.771,56.308,-74.664,56.336,-74.477,56.318,-74.312,56.262,-74.248,56.161,-74.161,56.144,-74.054,56.137,-73.985,56.097,-73.99,56.026]]]},{"n":"Nipissing","z":4,"b":[-80.447,46.161,-79.436,46.358],"p":[[[-80.447,46.282,-80.409,46.29,-80.346,46.286,-80.314,46.264,-80.253,46.249,-80.147,46.258,-80.118,46.277,-80.143,46.302,-80.125,46.324,-80.053,46.346,-79.856,46.34,-79.779,46.348,-79.653,46.358,-79.494,46.311,-79.436,46.235,-79.487,46.184,-79.573,46.166,-79.772,46.213,-79.876,46.193,-79.986,46.161,-80.006,46.166,-79.991,46.199,-80.015,46.232,-80.063,46.244,-80.293,46.225,-80.447,46.282]]]},{"n":"Hulun","z":4,"b":[116.988,48.561,118.035,49.351],"p":[[[117.229,48.585,117.114,48.561,117.089,48.579,117.105,48.629,117.096,48.705,117.014,48.786,116.988,48.841,117.059,48.907,117.225,49.003,117.389,49.139,117.529,49.27,117.636,49.342,117.69,49.351,117.707,49.322,117.688,49.274,117.686,49.22,117.716,49.18,117.749,49.174,117.776,49.161,117.812,49.125,117.899,49.11,118.007,49.105,118.035,49.071,117.964,49.035,117.875,49.033,117.824,49.059,117.772,49.059,117.733,49.03,117.716,48.986,117.516,48.846,117.328,48.694,117.31,48.636,117.229,48.585]]]},{"n":"Bangweulu","z":4,"b":[29.496,-11.416,30.019,-10.878],"p":[[[29.995,-11.091,29.937,-11.216,29.917,-11.301,29.837,-11.38,29.752,-11.416,29.669,-11.415,29.588,-11.394,29.562,-11.298,29.55,-11.248,29.511,-11.23,29.496,-11.195,29.52,-11.172,29.565,-11.157,29.741,-10.931,29.772,-10.933,29.847,-10.887,29.927,-10.878,30,-10.938,30.019,-11.014,29.995,-11.091]]]},{"n":"Inari","z":4,"b":[27.162,68.715,28.76,69.419],"p":[[[28.447,69.326,28.53,69.336,28.632,69.364,28.677,69.395,28.76,69.419,28.749,69.394,28.696,69.363,28.554,69.299,28.532,69.257,28.566,69.222,28.54,69.192,28.448,69.153,28.442,69.116,28.4,69.082,28.251,69.064,28.091,69.007,28.092,68.981,28.264,68.922,28.329,68.893,28.356,68.872,28.336,68.857,28.295,68.852,28.079,68.876,27.989,68.88,27.891,68.861,27.919,68.828,27.978,68.816,27.992,68.788,27.87,68.742,27.84,68.749,27.836,68.782,27.779,68.792,27.7,68.759,27.655,68.732,27.566,68.749,27.464,68.761,27.434,68.76,27.366,68.751,27.282,68.715,27.26,68.737,27.359,68.771,27.428,68.802,27.417,68.841,27.328,68.884,27.271,68.891,27.172,68.906,27.162,68.923,27.242,68.946,27.484,69.004,27.526,69.031,27.56,69.062,27.745,69.129,27.941,69.178,27.984,69.162,28.088,69.162,28.084,69.19,28.049,69.203,28.011,69.227,27.991,69.254,28.031,69.288,28.101,69.299,28.115,69.271,28.138,69.237,28.232,69.249,28.309,69.284,28.323,69.305,28.345,69.323,28.447,69.326]]]},{"n":"Oulujärvi","z":4,"b":[26.78,64.12,28.188,64.53],"p":[[[26.78,64.391,26.815,64.415,26.831,64.44,26.814,64.469,26.827,64.505,26.866,64.53,27.112,64.523,27.214,64.497,27.217,64.414,27.262,64.369,27.365,64.33,27.469,64.339,27.578,64.38,27.727,64.397,27.925,64.381,27.913,64.333,27.93,64.307,27.883,64.309,27.801,64.304,27.738,64.282,27.765,64.245,27.859,64.233,27.94,64.205,28.058,64.178,28.157,64.16,28.186,64.142,28.188,64.131,28.164,64.121,28.073,64.121,28.009,64.128,27.929,64.145,27.653,64.225,27.562,64.274,27.506,64.27,27.481,64.25,27.421,64.23,27.323,64.209,27.282,64.173,27.288,64.131,27.258,64.12,27.2,64.158,27.073,64.201,26.942,64.238,26.921,64.278,26.793,64.362,26.78,64.391]]]},{"n":"Peipus","z":4,"b":[26.966,58.184,27.821,59.007],"p":[[[27.569,58.215,27.497,58.184,27.473,58.217,27.425,58.303,27.324,58.402,27.237,58.452,27.157,58.648,26.966,58.806,26.974,58.884,27.107,58.954,27.298,58.995,27.524,59.007,27.683,58.98,27.748,58.912,27.814,58.571,27.821,58.485,27.798,58.447,27.748,58.43,27.583,58.306,27.561,58.257,27.569,58.215]]]},{"n":"Kivu","z":4,"b":[28.85,-2.448,29.358,-1.605],"p":[[[29.198,-1.644,29.249,-1.736,29.289,-1.86,29.358,-2.016,29.338,-2.058,29.283,-2.095,29.256,-2.13,29.24,-2.162,29.149,-2.277,29.112,-2.303,29.077,-2.345,29.047,-2.355,28.995,-2.36,28.91,-2.433,28.85,-2.448,28.857,-2.371,28.917,-2.288,28.903,-2.24,28.876,-2.228,28.872,-2.187,28.879,-2.153,28.916,-2.082,28.909,-2.008,28.988,-1.838,29.033,-1.647,29.071,-1.605,29.198,-1.644]]]},{"n":"Edward","z":4,"b":[29.327,-0.64,29.887,-0.12],"p":[[[29.419,-0.63,29.366,-0.64,29.335,-0.577,29.327,-0.53,29.36,-0.452,29.417,-0.339,29.496,-0.18,29.597,-0.134,29.644,-0.12,29.783,-0.121,29.887,-0.172,29.868,-0.303,29.642,-0.507,29.51,-0.612,29.452,-0.607,29.419,-0.63]]]},{"n":"Tai","z":4,"b":[119.939,30.936,120.602,31.532],"p":[[[120.602,31.087,120.394,30.971,120.236,30.936,120.103,30.973,119.978,31.091,119.939,31.253,119.987,31.392,120.108,31.492,120.219,31.532,120.254,31.51,120.256,31.495,120.251,31.475,120.271,31.445,120.325,31.442,120.38,31.429,120.372,31.386,120.331,31.35,120.326,31.32,120.357,31.27,120.391,31.227,120.431,31.2,120.44,31.153,120.395,31.1,120.423,31.083,120.461,31.081,120.54,31.127,120.602,31.087]]]},{"n":"","z":4,"b":[-76.547,56.127,-75.935,56.55],"p":[[[-76.547,56.164,-76.525,56.184,-76.504,56.213,-76.417,56.306,-76.397,56.345,-76.42,56.365,-76.425,56.389,-76.391,56.499,-76.366,56.55,-76.341,56.531,-76.33,56.504,-76.305,56.468,-76.245,56.423,-76.234,56.397,-76.275,56.379,-76.205,56.322,-76.116,56.277,-76.025,56.237,-75.939,56.191,-75.935,56.174,-75.953,56.145,-76.147,56.127,-76.446,56.139,-76.547,56.164]]]},{"n":"Mar Chiquita","z":4,"b":[-62.979,-30.917,-62.23,-30.521],"p":[[[-62.23,-30.776,-62.408,-30.841,-62.571,-30.863,-62.727,-30.917,-62.81,-30.847,-62.86,-30.801,-62.931,-30.751,-62.979,-30.692,-62.931,-30.649,-62.827,-30.624,-62.722,-30.584,-62.688,-30.548,-62.644,-30.521,-62.426,-30.559,-62.413,-30.543,-62.374,-30.548,-62.288,-30.589,-62.233,-30.671,-62.23,-30.776]]]},{"n":"Kremenchuk","z":4,"b":[31.888,48.994,33.194,49.57],"p":[[[33.167,49.019,33.163,48.994,33.076,49.004,33.066,49.021,33.043,49.063,32.981,49.083,32.863,49.087,32.77,49.116,32.718,49.14,32.621,49.199,32.408,49.302,32.294,49.346,32.17,49.404,31.9,49.513,31.888,49.554,31.955,49.57,32.114,49.541,32.184,49.521,32.35,49.469,32.461,49.426,32.632,49.382,32.682,49.401,32.659,49.464,32.665,49.537,32.712,49.559,32.754,49.485,32.861,49.408,33.015,49.281,33.104,49.2,33.194,49.119,33.194,49.094,33.167,49.019]]]},{"n":"La Grande 3","z":4,"b":[-76.124,53.465,-74.433,53.897],"p":[[[-76.088,53.666,-76.1,53.737,-76.036,53.737,-75.93,53.766,-75.902,53.791,-75.912,53.813,-75.85,53.855,-75.552,53.892,-75.292,53.897,-75.268,53.878,-75.241,53.843,-75.184,53.82,-74.799,53.893,-74.734,53.896,-74.725,53.857,-74.82,53.838,-74.889,53.794,-75.049,53.753,-75.194,53.721,-75.27,53.709,-75.265,53.684,-75.162,53.677,-74.943,53.672,-74.771,53.654,-74.593,53.667,-74.456,53.701,-74.433,53.685,-74.461,53.663,-74.679,53.609,-74.807,53.605,-74.935,53.578,-75.027,53.576,-74.998,53.622,-75.034,53.639,-75.12,53.623,-75.238,53.619,-75.478,53.631,-75.633,53.638,-75.658,53.62,-75.614,53.602,-75.601,53.582,-75.632,53.538,-75.716,53.497,-75.934,53.465,-76.102,53.475,-76.124,53.523,-76.064,53.569,-76.024,53.591,-75.945,53.624,-75.907,53.653,-75.945,53.686,-76.018,53.672,-76.088,53.666]]]},{"n":"Robert-Bourassa","z":4,"b":[-77.685,53.493,-76.088,54.188],"p":[[[-76.1,53.737,-76.088,53.666,-76.36,53.63,-76.385,53.596,-76.515,53.557,-76.839,53.499,-76.912,53.501,-77.187,53.493,-77.223,53.515,-77.29,53.523,-77.397,53.521,-77.488,53.56,-77.493,53.607,-77.465,53.638,-77.511,53.651,-77.621,53.659,-77.685,53.679,-77.612,53.722,-77.42,53.798,-77.341,53.811,-77.288,53.791,-77.016,53.821,-76.902,53.847,-76.766,53.91,-76.731,53.977,-76.846,54.031,-76.888,54.057,-76.868,54.072,-76.84,54.088,-76.66,54.026,-76.618,54.033,-76.602,54.055,-76.561,54.056,-76.509,54.048,-76.471,54.059,-76.472,54.082,-76.533,54.115,-76.573,54.14,-76.557,54.157,-76.454,54.178,-76.368,54.188,-76.329,54.162,-76.264,54.136,-76.202,54.139,-76.191,54.153,-76.15,54.143,-76.105,54.116,-76.14,54.108,-76.183,54.103,-76.255,54.073,-76.304,53.963,-76.349,53.946,-76.376,53.927,-76.433,53.898,-76.487,53.851,-76.596,53.788,-76.703,53.75,-76.75,53.723,-76.696,53.699,-76.554,53.712,-76.143,53.737,-76.1,53.737]]]},{"n":"","z":4,"b":[-157.044,57.443,-156.548,57.785],"p":[[[-156.548,57.766,-156.604,57.696,-156.777,57.566,-156.783,57.533,-156.768,57.507,-156.796,57.464,-156.834,57.443,-156.927,57.473,-157.019,57.512,-157.044,57.548,-157.006,57.566,-156.954,57.569,-156.893,57.58,-156.842,57.611,-156.837,57.653,-156.716,57.725,-156.683,57.755,-156.59,57.785,-156.548,57.766]]]},{"n":"Enriquillo","z":4,"b":[-71.794,18.421,-71.509,18.551],"p":[[[-71.509,18.47,-71.537,18.434,-71.607,18.421,-71.794,18.494,-71.777,18.531,-71.683,18.551,-71.584,18.514,-71.509,18.47]]]},{"n":"","z":4,"b":[-160.134,66.43,-159.663,66.544],"p":[[[-159.665,66.474,-159.663,66.433,-159.801,66.43,-159.945,66.442,-159.992,66.469,-160.134,66.52,-160.115,66.544,-159.922,66.535,-159.729,66.512,-159.665,66.474]]]},{"n":"","z":4,"b":[-115.896,60.109,-115.062,60.35],"p":[[[-115.062,60.329,-115.148,60.214,-115.157,60.195,-115.219,60.183,-115.286,60.16,-115.295,60.131,-115.294,60.109,-115.477,60.138,-115.605,60.173,-115.744,60.163,-115.879,60.163,-115.896,60.201,-115.804,60.254,-115.65,60.254,-115.531,60.262,-115.349,60.295,-115.221,60.337,-115.11,60.35,-115.062,60.329]]]},{"n":"Sakami","z":4,"b":[-76.938,52.968,-76.497,53.485],"p":[[[-76.637,53.26,-76.648,53.242,-76.665,53.221,-76.692,53.212,-76.685,53.19,-76.688,53.17,-76.784,53.147,-76.843,53.095,-76.839,53.034,-76.799,52.981,-76.855,52.968,-76.899,52.995,-76.938,53.119,-76.904,53.165,-76.881,53.216,-76.89,53.244,-76.892,53.291,-76.873,53.324,-76.892,53.345,-76.844,53.394,-76.72,53.469,-76.703,53.444,-76.703,53.419,-76.661,53.417,-76.613,53.447,-76.564,53.485,-76.515,53.468,-76.497,53.414,-76.523,53.379,-76.637,53.361,-76.672,53.34,-76.676,53.314,-76.656,53.298,-76.608,53.306,-76.589,53.289,-76.637,53.26]]]},{"n":"Argentino","z":4,"b":[-73.3,-50.62,-71.982,-49.982],"p":[[[-71.982,-50.224,-71.999,-50.244,-72.113,-50.264,-72.274,-50.302,-72.441,-50.307,-72.595,-50.293,-72.672,-50.278,-72.794,-50.291,-72.973,-50.357,-73.004,-50.419,-72.962,-50.471,-72.858,-50.47,-72.795,-50.46,-72.782,-50.483,-72.85,-50.538,-72.921,-50.612,-72.955,-50.62,-73.01,-50.515,-73.109,-50.365,-73.168,-50.332,-73.223,-50.335,-73.274,-50.346,-73.285,-50.331,-73.214,-50.27,-73.028,-50.289,-72.972,-50.261,-72.983,-50.226,-73.044,-50.199,-73.163,-50.148,-73.232,-50.165,-73.291,-50.211,-73.3,-50.185,-73.278,-50.121,-73.286,-50.07,-73.251,-50.033,-73.205,-50.001,-73.188,-49.982,-73.137,-50.031,-73.062,-50.133,-72.947,-50.18,-72.8,-50.196,-72.731,-50.186,-72.44,-50.173,-72.116,-50.132,-72.039,-50.162,-71.982,-50.224]]]},{"n":"Namtso","z":4,"b":[90.289,30.524,91.014,30.924],"p":[[[90.346,30.524,90.314,30.573,90.292,30.698,90.289,30.847,90.353,30.88,90.476,30.855,90.579,30.829,90.651,30.84,90.676,30.871,90.712,30.895,90.866,30.885,90.944,30.924,90.975,30.923,91.012,30.888,91.014,30.839,90.933,30.793,90.843,30.718,90.714,30.641,90.596,30.596,90.346,30.524]]]},{"n":"Tuz","z":4,"b":[33.158,38.523,33.674,39.127],"p":[[[33.458,38.854,33.525,38.812,33.597,38.799,33.674,38.712,33.617,38.578,33.533,38.523,33.423,38.559,33.326,38.611,33.236,38.667,33.158,38.772,33.219,38.876,33.293,38.967,33.309,39.057,33.305,39.127,33.35,39.12,33.424,39.056,33.454,38.992,33.44,38.938,33.458,38.854]]]},{"n":"Scott","z":4,"b":[-106.407,59.805,-105.704,60.228],"p":[[[-105.959,59.998,-106.019,59.994,-106.073,59.959,-106.081,59.892,-106.055,59.845,-106.104,59.809,-106.167,59.808,-106.195,59.836,-106.206,59.86,-106.237,59.862,-106.259,59.827,-106.278,59.813,-106.338,59.805,-106.375,59.831,-106.326,59.935,-106.332,60.022,-106.398,60.043,-106.407,60.064,-106.374,60.087,-106.308,60.101,-106.276,60.087,-106.263,60.061,-106.228,60.035,-106.195,60.021,-106.18,60.035,-106.153,60.097,-106.122,60.144,-106.019,60.192,-105.873,60.228,-105.826,60.214,-105.801,60.203,-105.704,60.184,-105.77,60.131,-105.914,60.117,-106.028,60.111,-106.069,60.083,-106.046,60.05,-105.939,60.059,-105.88,60.038,-105.875,60.006,-105.959,59.998]]]},{"n":"","z":4,"b":[27.34,60.938,27.849,61.078],"p":[[[27.535,60.938,27.429,60.969,27.342,61.001,27.34,61.021,27.367,61.033,27.448,60.994,27.579,60.984,27.692,61.012,27.704,61.056,27.724,61.078,27.777,61.068,27.81,61.044,27.849,60.984,27.744,60.95,27.535,60.938]]]},{"n":"Rikkavesi","z":4,"b":[29.422,62.682,29.822,63.051],"p":[[[29.472,62.869,29.521,62.833,29.562,62.834,29.583,62.888,29.537,62.976,29.494,63.029,29.522,63.051,29.595,62.981,29.747,62.876,29.822,62.795,29.812,62.732,29.742,62.697,29.666,62.682,29.623,62.74,29.541,62.774,29.458,62.833,29.422,62.883,29.44,62.894,29.472,62.869]]]},{"n":"","z":4,"b":[29.188,62.641,29.522,62.805],"p":[[[29.229,62.705,29.219,62.772,29.293,62.805,29.345,62.745,29.39,62.721,29.465,62.734,29.522,62.706,29.454,62.641,29.339,62.658,29.219,62.65,29.188,62.684,29.229,62.705]]]},{"n":"Nerpichye","z":4,"b":[159.808,69.228,160.48,69.378],"p":[[[160.48,69.25,160.433,69.228,160.256,69.238,159.965,69.282,159.808,69.347,159.844,69.378,159.998,69.357,160.17,69.327,160.325,69.296,160.48,69.25]]]},{"n":"Yarroto Pervoe","z":4,"b":[71.298,67.87,71.786,68.037],"p":[[[71.359,67.87,71.298,67.889,71.303,67.906,71.37,67.947,71.552,68.004,71.708,68.037,71.786,67.993,71.776,67.95,71.65,67.895,71.359,67.87]]]},{"n":"Ozero Neyto","z":4,"b":[70.128,70.011,70.512,70.136],"p":[[[70.489,70.092,70.512,70.055,70.371,70.015,70.231,70.011,70.128,70.076,70.174,70.136,70.368,70.117,70.489,70.092]]]},{"n":"Ozero Yambuto","z":4,"b":[70.749,69.953,71.058,70.139],"p":[[[70.851,69.969,70.749,70.033,70.754,70.067,70.79,70.096,70.837,70.136,70.941,70.139,70.939,70.1,70.943,70.054,71.058,69.988,71.026,69.953,70.851,69.969]]]},{"n":"","z":4,"b":[70.344,70.123,70.821,70.217],"p":[[[70.563,70.123,70.372,70.153,70.344,70.2,70.77,70.217,70.821,70.191,70.793,70.166,70.696,70.136,70.563,70.123]]]},{"n":"Vygozero","z":4,"b":[34.214,62.919,35.413,63.851],"p":[[[35.013,63.506,35.145,63.482,35.249,63.492,35.374,63.492,35.413,63.448,35.382,63.434,35.193,63.404,35.172,63.381,35.099,63.383,35.049,63.399,34.977,63.429,34.872,63.464,34.829,63.434,34.885,63.294,34.953,63.181,34.957,63.119,34.946,63.033,34.91,62.951,34.863,62.919,34.861,62.941,34.867,62.976,34.864,63.01,34.873,63.04,34.899,63.055,34.891,63.107,34.858,63.172,34.853,63.225,34.835,63.276,34.804,63.326,34.702,63.399,34.552,63.459,34.517,63.484,34.525,63.502,34.453,63.623,34.358,63.697,34.308,63.692,34.266,63.656,34.215,63.613,34.214,63.676,34.284,63.777,34.347,63.851,34.428,63.848,34.563,63.816,34.644,63.789,34.616,63.766,34.857,63.721,34.861,63.688,34.99,63.585,34.984,63.564,34.953,63.54,35.013,63.506]]]},{"n":"Segozero","z":4,"b":[33.257,63.189,34.066,63.487],"p":[[[33.441,63.444,33.586,63.487,33.637,63.476,33.657,63.433,33.697,63.409,33.847,63.438,33.933,63.418,33.991,63.297,34.066,63.229,34.017,63.201,33.897,63.189,33.703,63.19,33.572,63.203,33.499,63.216,33.307,63.284,33.274,63.316,33.274,63.341,33.283,63.382,33.257,63.423,33.355,63.424,33.441,63.444]]]},{"n":"Koitere","z":4,"b":[30.481,62.767,30.847,63.05],"p":[[[30.604,62.876,30.587,62.937,30.615,62.966,30.676,62.973,30.704,63.012,30.755,63.05,30.831,63.027,30.847,62.954,30.742,62.864,30.689,62.847,30.659,62.808,30.667,62.784,30.659,62.767,30.634,62.775,30.502,62.779,30.481,62.788,30.539,62.82,30.604,62.876]]]},{"n":"Pielinen","z":4,"b":[29.042,62.722,30.33,63.573],"p":[[[29.994,63.293,30.004,63.271,29.966,63.265,29.959,63.227,29.994,63.14,30.138,62.973,30.33,62.829,30.317,62.809,30.229,62.804,30.143,62.722,30.09,62.729,30.155,62.79,30.216,62.868,30.137,62.929,30.065,62.944,30.033,62.959,29.854,63.087,29.814,63.145,29.594,63.158,29.528,63.179,29.393,63.183,29.299,63.256,29.238,63.323,29.123,63.407,29.042,63.475,29.071,63.508,29.077,63.564,29.092,63.573,29.187,63.514,29.309,63.461,29.372,63.44,29.527,63.377,29.722,63.315,29.784,63.294,29.831,63.291,29.915,63.315,29.994,63.293]]]},{"n":"Pyaozero","z":4,"b":[30.651,65.905,31.335,66.27],"p":[[[31.17,66.107,31.211,66.059,31.24,66.004,31.277,65.955,31.335,65.918,31.295,65.905,31.155,65.914,30.95,65.964,30.651,66.052,30.704,66.135,30.722,66.186,30.789,66.23,30.899,66.263,31.033,66.27,31.103,66.245,31.174,66.138,31.17,66.107]]]},{"n":"Topozero","z":4,"b":[31.507,65.388,32.712,65.89],"p":[[[32.517,65.532,32.664,65.477,32.688,65.452,32.712,65.416,32.706,65.388,32.668,65.389,32.635,65.418,32.481,65.469,32.274,65.506,32.109,65.546,31.996,65.569,31.946,65.599,31.943,65.622,31.895,65.636,31.845,65.644,31.733,65.67,31.574,65.735,31.507,65.795,31.693,65.856,31.983,65.89,32.051,65.876,32.118,65.834,32.139,65.784,32.204,65.764,32.329,65.7,32.36,65.667,32.363,65.629,32.377,65.577,32.425,65.542,32.517,65.532]]]},{"n":"","z":4,"b":[-72.841,81.67,-68.968,81.94],"p":[[[-72.841,81.68,-72.618,81.705,-72.347,81.728,-69.582,81.918,-69.285,81.933,-68.968,81.94,-69.476,81.887,-70.016,81.84,-72.22,81.698,-72.714,81.67,-72.736,81.677,-72.841,81.68]]]},{"n":"","z":4,"b":[-89.522,52.854,-88.952,53.006],"p":[[[-89.522,52.883,-89.471,52.897,-89.434,52.918,-89.422,52.948,-89.283,53.006,-89.199,52.988,-89.123,52.966,-89.004,52.985,-88.952,52.988,-88.983,52.94,-89.044,52.87,-89.151,52.854,-89.362,52.866,-89.478,52.868,-89.522,52.883]]]},{"n":"Bosten","z":4,"b":[86.728,41.825,87.375,42.108],"p":[[[87.375,41.851,87.259,41.894,87.102,41.901,86.894,41.87,86.753,41.825,86.728,41.85,86.735,41.903,86.795,41.975,86.829,42.073,86.889,42.108,87.008,42.084,87.315,42.017,87.375,41.908,87.375,41.851]]]},{"n":"","z":4,"b":[30.85,61.921,31.053,62.083],"p":[[[30.868,61.974,30.868,62.009,30.853,62.049,30.85,62.083,30.926,62.083,30.971,62.065,31.053,61.97,31.02,61.929,30.939,61.921,30.885,61.941,30.868,61.974]]]},{"n":"Ilmen","z":4,"b":[30.857,58.142,31.688,58.477],"p":[[[31.531,58.23,31.446,58.206,31.394,58.142,31.156,58.171,30.876,58.214,30.857,58.239,30.955,58.284,31.019,58.32,31.08,58.332,31.17,58.359,31.252,58.425,31.298,58.477,31.374,58.429,31.43,58.417,31.511,58.389,31.518,58.373,31.637,58.388,31.653,58.369,31.646,58.351,31.647,58.308,31.688,58.202,31.631,58.207,31.531,58.23]]]},{"n":"Poyang","z":4,"b":[115.965,28.804,116.709,29.751],"p":[[[116.706,29.136,116.709,29.102,116.672,29.098,116.635,29.102,116.618,29.115,116.581,29.128,116.521,29.143,116.496,29.122,116.477,29.043,116.503,28.953,116.53,28.894,116.537,28.845,116.512,28.804,116.468,28.805,116.441,28.86,116.418,28.88,116.409,28.905,116.348,28.906,116.329,28.961,116.097,29.106,116.064,29.149,116.047,29.19,115.982,29.279,115.965,29.311,116.002,29.394,116.026,29.453,116.091,29.555,116.117,29.653,116.1,29.688,116.082,29.703,116.113,29.726,116.18,29.751,116.198,29.751,116.23,29.698,116.235,29.666,116.207,29.645,116.199,29.604,116.181,29.573,116.159,29.519,116.145,29.454,116.11,29.427,116.115,29.402,116.143,29.386,116.188,29.382,116.196,29.362,116.153,29.334,116.137,29.309,116.153,29.253,116.31,29.232,116.343,29.234,116.368,29.223,116.358,29.186,116.362,29.154,116.407,29.176,116.44,29.236,116.469,29.25,116.54,29.24,116.604,29.237,116.64,29.237,116.664,29.226,116.661,29.19,116.674,29.159,116.706,29.136]]]},{"n":"Daguan Lake","z":4,"b":[116.257,29.964,116.52,30.094],"p":[[[116.461,30.055,116.496,30.02,116.52,29.988,116.498,29.964,116.388,29.966,116.29,29.994,116.259,30.048,116.257,30.094,116.281,30.079,116.298,30.086,116.327,30.087,116.386,30.069,116.442,30.077,116.461,30.055]]]},{"n":"Longgan","z":4,"b":[115.977,29.881,116.26,30.061],"p":[[[116.195,30.038,116.242,30.032,116.253,30.01,116.26,29.976,116.254,29.935,116.211,29.908,116.131,29.898,116.044,29.881,115.977,29.952,115.985,30.016,116.073,30,116.118,30,116.129,30.026,116.15,30.061,116.195,30.038]]]},{"n":"Liangzi","z":4,"b":[114.35,30.085,114.642,30.366],"p":[[[114.35,30.196,114.371,30.211,114.395,30.236,114.406,30.268,114.444,30.285,114.496,30.289,114.501,30.311,114.474,30.333,114.458,30.364,114.501,30.366,114.539,30.345,114.603,30.3,114.623,30.258,114.642,30.133,114.623,30.096,114.581,30.137,114.542,30.188,114.509,30.194,114.474,30.158,114.445,30.085,114.408,30.106,114.365,30.165,114.35,30.196]]]},{"n":"Siling","z":4,"b":[88.566,31.596,89.323,31.918],"p":[[[89.318,31.794,89.287,31.788,89.253,31.749,89.211,31.679,89.138,31.602,89.058,31.596,88.989,31.609,88.946,31.625,88.937,31.658,88.948,31.7,88.927,31.728,88.85,31.726,88.764,31.712,88.685,31.729,88.608,31.796,88.566,31.844,88.608,31.88,88.704,31.867,88.759,31.834,88.789,31.829,88.814,31.841,88.864,31.887,88.968,31.918,89.12,31.913,89.266,31.872,89.323,31.823,89.318,31.794]]]},{"n":"Khar-Us","z":4,"b":[91.976,47.769,92.802,48.361],"p":[[[92.787,48.178,92.622,48.098,92.442,48.03,92.354,47.964,92.28,47.851,92.2,47.769,92.084,47.816,91.991,47.933,91.976,48.01,92.105,48.054,92.249,48.13,92.25,48.263,92.287,48.352,92.363,48.361,92.425,48.36,92.483,48.339,92.402,48.237,92.405,48.156,92.48,48.138,92.544,48.138,92.643,48.193,92.645,48.271,92.638,48.33,92.733,48.31,92.802,48.253,92.787,48.178]]]},{"n":"Taupo","z":4,"b":[175.749,-38.948,176.083,-38.677],"p":[[[176.066,-38.812,176.003,-38.865,175.927,-38.902,175.851,-38.931,175.795,-38.948,175.755,-38.943,175.8,-38.845,175.782,-38.797,175.749,-38.773,175.763,-38.727,175.809,-38.677,175.944,-38.677,176.057,-38.721,176.083,-38.758,176.066,-38.812]]]},{"n":"Geneva","z":4,"b":[6.133,46.205,6.874,46.529],"p":[[[6.874,46.432,6.866,46.403,6.845,46.385,6.665,46.402,6.469,46.389,6.336,46.361,6.157,46.205,6.133,46.205,6.158,46.302,6.344,46.458,6.588,46.529,6.819,46.466,6.874,46.432]]]},{"n":"Neagh","z":4,"b":[-6.585,54.499,-6.272,54.723],"p":[[[-6.488,54.723,-6.459,54.715,-6.318,54.709,-6.272,54.66,-6.282,54.604,-6.349,54.525,-6.354,54.502,-6.427,54.499,-6.562,54.515,-6.585,54.56,-6.541,54.623,-6.511,54.698,-6.488,54.723]]]},{"n":"Vättern","z":4,"b":[14.121,57.796,14.968,58.854],"p":[[[14.933,58.784,14.941,58.756,14.929,58.7,14.9,58.635,14.891,58.592,14.963,58.539,14.946,58.511,14.827,58.462,14.686,58.395,14.622,58.299,14.582,58.202,14.543,58.138,14.499,58.085,14.339,57.933,14.245,57.809,14.159,57.796,14.121,57.925,14.213,58.15,14.391,58.406,14.507,58.561,14.632,58.68,14.726,58.731,14.817,58.791,14.932,58.854,14.968,58.849,14.962,58.835,14.933,58.784]]]},{"n":"Van","z":4,"b":[42.353,38.326,43.611,38.988],"p":[[[42.989,38.346,42.923,38.39,42.912,38.426,42.882,38.477,42.856,38.484,42.781,38.459,42.683,38.457,42.586,38.482,42.538,38.49,42.353,38.5,42.365,38.549,42.434,38.61,42.458,38.643,42.458,38.692,42.526,38.745,42.723,38.782,42.845,38.794,42.935,38.794,43.079,38.809,43.163,38.846,43.159,38.869,43.126,38.882,43.088,38.881,43.075,38.916,43.152,38.948,43.285,38.964,43.436,38.988,43.56,38.978,43.611,38.947,43.531,38.898,43.383,38.836,43.292,38.813,43.21,38.716,43.186,38.603,43.287,38.548,43.318,38.489,43.235,38.407,43.148,38.337,43.088,38.326,42.989,38.346]]]},{"n":"Yellowstone","z":4,"b":[-110.555,44.319,-110.199,44.567],"p":[[[-110.199,44.335,-110.235,44.322,-110.273,44.319,-110.292,44.336,-110.328,44.356,-110.341,44.383,-110.394,44.391,-110.39,44.425,-110.426,44.447,-110.489,44.437,-110.531,44.414,-110.555,44.447,-110.529,44.471,-110.49,44.473,-110.45,44.48,-110.418,44.503,-110.381,44.556,-110.327,44.567,-110.272,44.544,-110.264,44.447,-110.202,44.391,-110.199,44.335]]]},{"n":"Teshekpuk","z":4,"b":[-154.257,70.48,-152.94,70.737],"p":[[[-152.94,70.569,-153.275,70.518,-153.656,70.48,-153.719,70.494,-153.794,70.487,-153.926,70.512,-154.019,70.543,-154.101,70.598,-154.174,70.634,-154.237,70.639,-154.257,70.661,-154.161,70.678,-154.035,70.684,-153.975,70.679,-153.938,70.698,-153.812,70.725,-153.611,70.737,-153.014,70.603,-152.94,70.569]]]},{"n":"Flathead","z":4,"b":[-114.254,47.711,-114.005,48.091],"p":[[[-114.156,47.933,-114.18,48.022,-114.199,48.04,-114.193,48.069,-114.134,48.091,-114.046,48.015,-114.024,47.934,-114.005,47.785,-114.021,47.756,-114.056,47.711,-114.118,47.718,-114.15,47.714,-114.163,47.74,-114.126,47.784,-114.148,47.812,-114.193,47.826,-114.25,47.837,-114.254,47.858,-114.194,47.891,-114.156,47.933]]]},{"n":"Mead","z":4,"b":[-114.816,35.831,-113.633,36.519],"p":[[[-113.636,35.831,-113.67,35.843,-113.726,35.922,-113.795,36.01,-113.895,36.079,-113.952,36.13,-113.989,36.165,-114.039,36.163,-114.11,36.032,-114.208,36.006,-114.294,36.03,-114.321,36.076,-114.373,36.1,-114.406,36.091,-114.451,36.079,-114.504,36.106,-114.64,36.106,-114.714,36.024,-114.741,36.014,-114.769,36.02,-114.812,36.075,-114.816,36.121,-114.653,36.154,-114.512,36.194,-114.432,36.237,-114.413,36.348,-114.387,36.483,-114.364,36.515,-114.336,36.519,-114.335,36.444,-114.359,36.335,-114.37,36.209,-114.295,36.11,-114.226,36.05,-114.159,36.077,-114.097,36.152,-114.03,36.245,-113.996,36.244,-113.924,36.176,-113.858,36.116,-113.777,36.048,-113.699,35.958,-113.633,35.859,-113.636,35.831]]]},{"n":"Claire","z":4,"b":[-112.447,58.303,-111.717,58.825],"p":[[[-111.817,58.56,-111.809,58.517,-111.831,58.484,-111.838,58.46,-111.843,58.425,-111.831,58.394,-111.789,58.331,-111.809,58.303,-111.898,58.306,-112.08,58.347,-112.189,58.413,-112.237,58.46,-112.208,58.499,-112.211,58.55,-112.255,58.576,-112.315,58.563,-112.393,58.578,-112.447,58.618,-112.431,58.683,-112.337,58.778,-112.154,58.825,-112.041,58.819,-112.037,58.787,-112.113,58.778,-112.119,58.736,-111.985,58.713,-111.902,58.726,-111.844,58.721,-111.802,58.697,-111.766,58.685,-111.731,58.686,-111.717,58.659,-111.817,58.56]]]},{"n":"Smallwood","z":4,"b":[-66.621,53.421,-63.104,55.102],"p":[[[-63.176,54.129,-63.184,54.109,-63.203,54.074,-63.104,54.004,-63.137,53.973,-63.2,53.956,-63.247,53.966,-63.305,53.99,-63.417,54.006,-63.489,54.012,-63.555,54.006,-63.568,53.982,-63.529,53.955,-63.529,53.929,-63.652,53.925,-63.703,53.918,-63.77,53.902,-63.764,53.879,-63.827,53.858,-64.009,53.862,-64.124,53.896,-64.138,53.96,-64.176,54.012,-64.495,54.079,-64.525,54.11,-64.564,54.125,-64.652,54.114,-64.688,54.081,-64.642,54.031,-64.658,53.996,-64.694,53.961,-64.751,53.934,-64.836,53.931,-64.922,53.916,-65.014,53.888,-65.123,53.84,-65.177,53.818,-65.263,53.796,-65.298,53.76,-65.295,53.724,-65.191,53.674,-65.19,53.639,-65.148,53.564,-65.164,53.54,-65.128,53.5,-64.973,53.455,-65.02,53.421,-65.234,53.455,-65.529,53.49,-65.583,53.542,-65.48,53.549,-65.388,53.559,-65.335,53.587,-65.317,53.605,-65.326,53.636,-65.364,53.675,-65.381,53.715,-65.363,53.756,-65.328,53.821,-65.338,53.879,-65.407,53.904,-65.453,53.914,-65.507,53.95,-65.517,53.997,-65.583,54.061,-65.728,54.087,-65.826,54.135,-66.112,54.347,-66.254,54.426,-66.276,54.478,-66.364,54.541,-66.44,54.562,-66.429,54.508,-66.349,54.424,-66.377,54.375,-66.406,54.338,-66.387,54.263,-66.331,54.179,-66.324,54.154,-66.382,54.164,-66.491,54.273,-66.546,54.363,-66.577,54.424,-66.608,54.488,-66.616,54.521,-66.615,54.582,-66.621,54.623,-66.535,54.628,-66.508,54.654,-66.529,54.77,-66.509,54.802,-66.555,54.883,-66.6,54.922,-66.595,54.949,-66.567,54.958,-66.574,54.991,-66.598,55.029,-66.584,55.049,-66.553,55.054,-66.541,55.073,-66.556,55.102,-66.532,55.1,-66.449,55.052,-66.345,54.979,-66.349,54.952,-66.352,54.937,-66.351,54.914,-66.38,54.904,-66.42,54.901,-66.432,54.893,-66.428,54.881,-66.399,54.853,-66.412,54.838,-66.423,54.833,-66.429,54.813,-66.36,54.715,-66.354,54.687,-66.2,54.577,-66.191,54.525,-66.191,54.476,-66.023,54.346,-65.994,54.34,-65.938,54.271,-65.909,54.285,-65.872,54.281,-65.791,54.202,-65.713,54.163,-65.682,54.176,-65.503,54.055,-65.4,54.007,-65.345,54.043,-65.192,53.993,-65.103,54.012,-65.178,54.075,-65.13,54.083,-64.864,54.053,-64.816,54.076,-64.941,54.152,-65.011,54.224,-65.072,54.34,-65.165,54.435,-65.305,54.46,-65.302,54.475,-65.279,54.523,-65.271,54.608,-65.224,54.65,-65.157,54.656,-65.135,54.638,-65.218,54.557,-65.187,54.528,-65.14,54.511,-65.103,54.473,-65.059,54.396,-64.96,54.303,-64.98,54.289,-64.97,54.236,-64.92,54.171,-64.883,54.148,-64.879,54.16,-64.855,54.167,-64.763,54.144,-64.68,54.143,-64.617,54.175,-64.583,54.186,-64.51,54.189,-64.431,54.187,-64.333,54.166,-64.219,54.178,-64.234,54.211,-64.274,54.231,-64.284,54.256,-64.325,54.273,-64.407,54.256,-64.464,54.253,-64.48,54.273,-64.425,54.332,-64.4,54.511,-64.366,54.615,-64.378,54.679,-64.416,54.737,-64.397,54.743,-64.32,54.696,-64.296,54.659,-64.329,54.566,-64.32,54.517,-64.202,54.406,-64.138,54.35,-64.029,54.285,-63.947,54.243,-63.87,54.169,-63.748,54.096,-63.584,54.078,-63.465,54.098,-63.373,54.14,-63.319,54.143,-63.228,54.137,-63.176,54.129]]]},{"n":"Taymyr","z":4,"b":[99.507,73.838,105.333,75.16],"p":[[[101.023,73.86,100.949,73.894,101.026,73.936,101.104,73.992,101.062,74.095,100.968,74.196,100.9,74.256,100.768,74.292,100.607,74.296,100.5,74.308,100.412,74.337,100.218,74.347,100.079,74.343,99.957,74.331,99.71,74.34,99.585,74.36,99.507,74.4,99.607,74.42,99.83,74.407,99.972,74.409,100.171,74.44,100.232,74.482,100.351,74.529,100.495,74.577,100.501,74.631,100.434,74.722,100.318,74.8,100.097,74.861,100.132,74.896,100.266,74.936,100.419,74.948,100.463,75.007,100.285,75.079,100.042,75.103,100.027,75.125,100.033,75.148,100.109,75.16,100.192,75.15,100.301,75.127,100.435,75.087,100.537,75.073,100.612,75.036,100.616,74.982,100.544,74.932,100.534,74.873,100.573,74.823,100.707,74.757,100.841,74.72,101.059,74.681,101.806,74.641,102.484,74.641,103.253,74.676,103.493,74.71,103.762,74.763,104.032,74.795,104.234,74.814,104.209,74.859,104.028,74.885,103.878,74.928,103.912,74.985,103.991,75.009,104.047,75.021,104.117,75.055,104.129,75.088,104.146,75.112,104.264,75.121,104.467,75.093,104.628,75.012,104.864,74.915,105.307,74.855,105.333,74.814,105.232,74.785,104.973,74.772,104.508,74.74,103.999,74.651,103.92,74.633,103.843,74.629,103.742,74.633,103.569,74.593,103.204,74.568,102.833,74.54,102.464,74.516,102.041,74.502,101.986,74.413,102.073,74.322,102.19,74.24,102.161,74.233,102.059,74.231,101.979,74.236,101.848,74.258,101.691,74.29,101.426,74.356,101.313,74.404,101.331,74.439,101.366,74.479,101.213,74.491,101.049,74.484,100.856,74.463,100.882,74.425,100.981,74.412,101.103,74.387,101.1,74.33,101.156,74.246,101.277,74.143,101.439,74.043,101.529,73.953,101.553,73.878,101.533,73.841,101.435,73.838,101.377,73.844,101.125,73.849,101.023,73.86]]]},{"n":"Izabal","z":4.7,"b":[-89.388,15.382,-88.843,15.765],"p":[[[-88.878,15.712,-88.925,15.678,-88.97,15.636,-88.997,15.573,-89.022,15.489,-89.079,15.444,-89.182,15.417,-89.238,15.41,-89.278,15.395,-89.301,15.382,-89.326,15.412,-89.366,15.46,-89.388,15.488,-89.372,15.505,-89.317,15.522,-89.247,15.549,-89.183,15.575,-89.105,15.602,-88.971,15.688,-88.862,15.764,-88.843,15.765,-88.852,15.741,-88.878,15.712]]]},{"n":"Mogotoyevo","z":4.7,"b":[148.916,71.906,149.318,72.163],"p":[[[149.318,72.037,149.311,72.008,149.251,71.969,149.187,71.921,149.113,71.906,149.01,71.925,148.942,71.979,148.916,72.034,148.93,72.081,148.961,72.129,148.987,72.153,149.021,72.163,149.079,72.162,149.155,72.151,149.224,72.127,149.283,72.082,149.318,72.037]]]},{"n":"Ayakum","z":4.7,"b":[89.082,37.485,89.645,37.633],"p":[[[89.611,37.495,89.514,37.485,89.365,37.504,89.228,37.528,89.145,37.538,89.097,37.56,89.082,37.587,89.147,37.598,89.277,37.615,89.396,37.633,89.487,37.628,89.565,37.613,89.618,37.594,89.645,37.575,89.639,37.553,89.64,37.527,89.645,37.51,89.611,37.495]]]},{"n":"Storsjön","z":4.7,"b":[13.972,62.826,14.671,63.325],"p":[[[14.084,63.259,14.054,63.296,14.026,63.319,14.063,63.325,14.22,63.305,14.394,63.262,14.446,63.264,14.51,63.265,14.612,63.205,14.671,63.139,14.569,63.162,14.504,63.165,14.455,63.153,14.427,63.127,14.422,63.09,14.444,63.028,14.469,62.952,14.474,62.887,14.461,62.836,14.444,62.827,14.427,62.826,14.39,62.874,14.345,62.925,14.341,62.953,14.368,62.956,14.39,62.953,14.413,62.954,14.43,62.98,14.398,63.028,14.357,63.087,14.208,63.152,14.114,63.178,14.057,63.194,13.981,63.246,13.972,63.263,14.005,63.259,14.061,63.236,14.083,63.242,14.084,63.259]]]},{"n":"St. Joseph","z":4.7,"b":[-91.367,50.918,-90.427,51.215],"p":[[[-91.345,50.969,-91.302,51.035,-91.274,51.069,-91.214,51.066,-91.091,51.038,-90.976,51.011,-90.911,51.037,-90.844,51.073,-90.795,51.094,-90.745,51.12,-90.663,51.176,-90.604,51.215,-90.52,51.192,-90.472,51.17,-90.431,51.14,-90.427,51.119,-90.528,51.068,-90.747,51.014,-90.937,50.954,-91.12,50.946,-91.238,50.934,-91.318,50.918,-91.367,50.93,-91.345,50.969]]]},{"n":"Rukwa","z":4.7,"b":[31.794,-8.09,32.53,-7.585],"p":[[[32.514,-8.039,32.53,-8.07,32.521,-8.09,32.363,-8.033,32.171,-7.963,32.052,-7.971,31.96,-7.986,31.921,-7.959,31.873,-7.884,31.823,-7.783,31.794,-7.701,31.8,-7.653,31.834,-7.619,31.897,-7.585,32.026,-7.633,32.254,-7.826,32.448,-7.989,32.514,-8.039]]]},{"n":"Nam Ngum Dam","z":4.7,"b":[102.506,18.45,102.894,18.759],"p":[[[102.879,18.527,102.826,18.527,102.719,18.497,102.675,18.465,102.646,18.45,102.615,18.476,102.571,18.56,102.546,18.615,102.527,18.669,102.506,18.746,102.527,18.759,102.561,18.735,102.582,18.696,102.622,18.681,102.638,18.673,102.659,18.662,102.704,18.657,102.747,18.65,102.753,18.599,102.791,18.569,102.851,18.55,102.885,18.544,102.894,18.541,102.879,18.527]]]},{"n":"Gouin","z":4.7,"b":[-75.414,48.288,-74.1,48.802],"p":[[[-74.531,48.461,-74.545,48.439,-74.575,48.425,-74.596,48.418,-74.621,48.42,-74.638,48.414,-74.651,48.4,-74.677,48.364,-74.686,48.357,-74.692,48.382,-74.706,48.42,-74.722,48.434,-74.756,48.449,-74.816,48.512,-74.867,48.56,-74.911,48.575,-74.945,48.576,-74.964,48.556,-74.969,48.519,-74.96,48.499,-74.942,48.487,-74.945,48.454,-74.954,48.38,-74.96,48.364,-74.986,48.365,-75.023,48.359,-75.059,48.347,-75.082,48.341,-75.13,48.288,-75.14,48.289,-75.13,48.33,-75.115,48.414,-75.099,48.478,-75.073,48.495,-75.042,48.514,-75.008,48.564,-75.018,48.612,-75.07,48.596,-75.095,48.577,-75.129,48.555,-75.165,48.526,-75.182,48.479,-75.181,48.438,-75.174,48.414,-75.213,48.406,-75.269,48.408,-75.3,48.424,-75.336,48.437,-75.363,48.429,-75.387,48.444,-75.414,48.438,-75.389,48.483,-75.35,48.532,-75.302,48.58,-75.282,48.597,-75.25,48.598,-75.219,48.592,-75.181,48.602,-75.146,48.636,-75.109,48.675,-75.061,48.706,-75.042,48.712,-74.97,48.694,-74.921,48.71,-74.812,48.757,-74.762,48.773,-74.723,48.785,-74.63,48.801,-74.558,48.802,-74.543,48.778,-74.522,48.756,-74.544,48.704,-74.58,48.64,-74.555,48.608,-74.507,48.594,-74.485,48.58,-74.456,48.581,-74.412,48.589,-74.397,48.579,-74.406,48.547,-74.37,48.486,-74.314,48.449,-74.263,48.439,-74.223,48.426,-74.174,48.398,-74.118,48.388,-74.1,48.372,-74.151,48.36,-74.237,48.347,-74.312,48.377,-74.395,48.44,-74.463,48.471,-74.51,48.48,-74.531,48.461]]]},{"n":"Gaoyou","z":4.7,"b":[118.882,32.686,119.407,33.201],"p":[[[119.407,32.794,119.392,32.751,119.35,32.749,119.3,32.748,119.253,32.736,119.201,32.691,119.173,32.686,119.165,32.706,119.147,32.753,119.125,32.798,119.092,32.836,119.079,32.855,119.084,32.866,119.13,32.877,119.188,32.878,119.229,32.882,119.242,32.896,119.241,32.919,119.253,32.943,119.251,32.978,119.245,33.013,119.264,33.034,119.296,33.041,119.314,33.054,119.303,33.085,119.269,33.121,119.237,33.154,119.215,33.156,119.18,33.148,119.168,33.118,119.164,33.09,119.154,33.065,119.099,33.027,119.035,33.031,118.966,33.038,118.907,33.058,118.882,33.077,118.957,33.071,119.059,33.058,119.102,33.069,119.135,33.144,119.17,33.186,119.226,33.2,119.254,33.201,119.304,33.164,119.349,33.078,119.346,33.024,119.372,33,119.397,32.916,119.407,32.794]]]},{"n":"Pyasino","z":4.7,"b":[87.349,69.478,88.161,70.097],"p":[[[88.161,69.498,88.146,69.483,88.108,69.482,88.04,69.479,87.937,69.478,87.875,69.483,87.814,69.493,87.728,69.513,87.695,69.541,87.705,69.587,87.721,69.624,87.685,69.705,87.609,69.809,87.472,69.871,87.373,69.876,87.349,69.889,87.372,69.92,87.395,69.953,87.41,70.004,87.494,70.024,87.647,70.071,87.81,70.097,87.925,70.09,87.969,70.061,87.939,70.025,87.771,69.982,87.657,69.96,87.66,69.921,87.682,69.895,87.71,69.878,87.785,69.841,87.881,69.784,87.93,69.756,87.996,69.687,88.025,69.614,88.015,69.59,88.039,69.563,88.114,69.529,88.161,69.498]]]},{"n":"","z":4.7,"b":[26.276,61.558,27.045,62.037],"p":[[[27.037,61.662,26.998,61.664,26.938,61.67,26.863,61.696,26.795,61.703,26.743,61.694,26.77,61.666,26.837,61.648,26.879,61.626,26.884,61.594,26.886,61.581,26.89,61.569,26.87,61.564,26.834,61.573,26.811,61.571,26.796,61.558,26.778,61.576,26.758,61.612,26.665,61.681,26.554,61.758,26.468,61.791,26.406,61.809,26.35,61.834,26.29,61.854,26.276,61.866,26.305,61.9,26.351,61.935,26.412,61.976,26.442,61.962,26.428,61.945,26.391,61.905,26.376,61.87,26.418,61.863,26.464,61.875,26.491,61.89,26.524,61.9,26.54,61.915,26.527,61.928,26.531,61.939,26.575,61.937,26.603,61.917,26.631,61.886,26.668,61.868,26.684,61.872,26.686,61.881,26.692,61.888,26.713,61.886,26.744,61.871,26.774,61.861,26.796,61.871,26.81,61.887,26.809,61.902,26.764,61.913,26.698,61.931,26.644,61.968,26.591,62.022,26.588,62.037,26.64,62.011,26.705,61.993,26.755,61.975,26.819,61.948,26.873,61.902,26.881,61.871,26.877,61.855,26.894,61.797,26.9,61.753,26.927,61.73,26.983,61.712,27.028,61.704,27.043,61.688,27.045,61.671,27.037,61.662]]]},{"n":"Hjälmaren","z":4.7,"b":[15.344,59.149,16.406,59.317],"p":[[[16.404,59.309,16.406,59.305,16.356,59.288,16.31,59.275,16.13,59.24,15.921,59.194,15.892,59.176,15.881,59.162,15.816,59.149,15.701,59.151,15.624,59.167,15.62,59.192,15.59,59.225,15.545,59.246,15.52,59.255,15.481,59.261,15.344,59.27,15.354,59.283,15.5,59.302,15.613,59.317,15.728,59.298,15.822,59.295,15.857,59.304,15.99,59.286,16.187,59.288,16.271,59.3,16.334,59.303,16.404,59.309]]]},{"n":"","z":4.7,"b":[26.232,62.952,26.741,63.365],"p":[[[26.63,63.114,26.592,63.089,26.603,63.035,26.648,62.982,26.65,62.961,26.629,62.952,26.538,62.981,26.462,63.015,26.468,63.024,26.469,63.039,26.462,63.065,26.454,63.095,26.427,63.123,26.353,63.168,26.272,63.216,26.235,63.248,26.232,63.264,26.257,63.26,26.304,63.226,26.36,63.187,26.429,63.164,26.491,63.17,26.565,63.196,26.586,63.234,26.511,63.276,26.449,63.302,26.42,63.329,26.423,63.354,26.459,63.365,26.524,63.341,26.623,63.321,26.697,63.322,26.717,63.314,26.685,63.304,26.649,63.293,26.692,63.278,26.733,63.257,26.741,63.227,26.714,63.191,26.663,63.164,26.644,63.138,26.63,63.114]]]},{"n":"Bienville","z":4.7,"b":[-73.604,54.903,-72.225,55.281],"p":[[[-73.182,55.098,-73.154,55.113,-73.154,55.125,-73.067,55.147,-72.923,55.18,-72.832,55.191,-72.732,55.2,-72.647,55.213,-72.589,55.216,-72.517,55.221,-72.418,55.241,-72.305,55.272,-72.225,55.281,-72.25,55.262,-72.281,55.226,-72.286,55.193,-72.29,55.166,-72.358,55.126,-72.473,55.083,-72.629,55.049,-72.706,55.01,-72.789,54.998,-72.918,54.974,-73.045,54.946,-73.11,54.947,-73.149,54.964,-73.213,54.968,-73.313,54.947,-73.413,54.921,-73.555,54.903,-73.604,54.927,-73.511,54.942,-73.428,54.97,-73.409,54.987,-73.433,55.006,-73.485,55.022,-73.514,55.034,-73.511,55.048,-73.491,55.056,-73.434,55.051,-73.365,55.048,-73.272,55.07,-73.206,55.089,-73.182,55.098]]]},{"n":"Beloe","z":4.7,"b":[37.231,60.077,38.004,60.329],"p":[[[37.232,60.164,37.231,60.186,37.286,60.233,37.4,60.289,37.542,60.319,37.737,60.329,37.898,60.301,37.981,60.239,38.004,60.161,37.999,60.103,37.968,60.086,37.823,60.079,37.715,60.077,37.469,60.088,37.397,60.098,37.28,60.131,37.232,60.164]]]},{"n":"Brokopondo","z":4.7,"b":[-55.335,4.462,-54.796,4.999],"p":[[[-54.894,4.783,-54.925,4.747,-54.941,4.725,-54.938,4.699,-54.924,4.673,-54.892,4.665,-54.865,4.655,-54.864,4.626,-54.874,4.601,-54.9,4.594,-54.932,4.581,-54.952,4.596,-54.983,4.609,-55.013,4.599,-55.048,4.57,-55.064,4.533,-55.087,4.489,-55.123,4.462,-55.149,4.471,-55.19,4.484,-55.22,4.492,-55.23,4.514,-55.26,4.525,-55.299,4.524,-55.335,4.528,-55.329,4.574,-55.303,4.609,-55.279,4.621,-55.273,4.663,-55.284,4.699,-55.235,4.779,-55.173,4.879,-55.15,4.964,-55.127,4.999,-55.093,4.999,-55.02,4.971,-54.968,4.918,-54.938,4.904,-54.885,4.908,-54.84,4.912,-54.807,4.908,-54.796,4.881,-54.814,4.836,-54.838,4.789,-54.862,4.787,-54.894,4.783]]]},{"n":"Manyara","z":4.7,"b":[35.742,-3.757,35.882,-3.416],"p":[[[35.744,-3.636,35.762,-3.553,35.792,-3.48,35.822,-3.432,35.848,-3.416,35.869,-3.424,35.882,-3.452,35.869,-3.576,35.838,-3.701,35.806,-3.737,35.775,-3.757,35.753,-3.75,35.742,-3.707,35.744,-3.636]]]},{"n":"Abaya","z":4.7,"b":[37.696,6.014,38.143,6.589],"p":[[[38.055,6.578,38.102,6.576,38.138,6.577,38.143,6.56,38.129,6.519,38.116,6.473,38.107,6.449,38.087,6.423,38.058,6.38,38.014,6.307,37.944,6.202,37.875,6.108,37.843,6.073,37.802,6.049,37.739,6.014,37.701,6.015,37.696,6.044,37.709,6.088,37.745,6.136,37.79,6.172,37.826,6.189,37.865,6.22,37.887,6.263,37.875,6.36,37.863,6.455,37.869,6.491,37.936,6.548,38.017,6.589,38.055,6.578]]]},{"n":"Akkajaure","z":4.7,"b":[17.131,67.419,18.428,67.792],"p":[[[18.184,67.478,17.838,67.586,17.632,67.612,17.575,67.62,17.475,67.644,17.339,67.654,17.246,67.674,17.22,67.687,17.154,67.735,17.131,67.792,17.303,67.753,17.435,67.714,18.116,67.561,18.203,67.533,18.396,67.462,18.428,67.419,18.184,67.478]]]},{"n":"Balaton","z":4.7,"b":[17.269,46.721,18.164,47.055],"p":[[[17.586,46.766,17.476,46.724,17.277,46.721,17.269,46.742,17.271,46.76,17.309,46.763,17.383,46.779,17.479,46.8,17.665,46.852,17.824,46.911,17.881,46.943,17.955,46.987,18.039,47.037,18.078,47.055,18.13,47.039,18.164,47.011,18.143,46.963,17.881,46.866,17.672,46.791,17.586,46.766]]]},{"n":"Canandaigua","z":4.7,"b":[-77.364,42.689,-77.245,42.895],"p":[[[-77.262,42.815,-77.309,42.768,-77.354,42.689,-77.364,42.704,-77.361,42.734,-77.334,42.785,-77.291,42.836,-77.271,42.895,-77.245,42.876,-77.262,42.815]]]},{"n":"Owasco","z":4.7,"b":[-76.549,42.769,-76.468,42.922],"p":[[[-76.479,42.77,-76.527,42.836,-76.549,42.897,-76.536,42.922,-76.469,42.781,-76.468,42.769,-76.479,42.77]]]},{"n":"Skaneateles","z":4.7,"b":[-76.428,42.787,-76.272,42.96],"p":[[[-76.272,42.787,-76.362,42.85,-76.428,42.91,-76.425,42.96,-76.372,42.887,-76.293,42.833,-76.272,42.787]]]},{"n":"","z":4.7,"b":[-164.054,60.167,-163.468,60.439],"p":[[[-163.468,60.414,-163.529,60.393,-163.557,60.362,-163.577,60.342,-163.557,60.27,-163.629,60.214,-163.77,60.167,-163.775,60.178,-163.795,60.211,-163.784,60.259,-163.775,60.286,-163.793,60.298,-163.836,60.297,-163.937,60.285,-164.042,60.275,-164.054,60.295,-164.018,60.302,-163.901,60.333,-163.85,60.351,-163.881,60.369,-163.939,60.389,-163.893,60.408,-163.866,60.43,-163.852,60.433,-163.797,60.416,-163.734,60.431,-163.721,60.439,-163.66,60.433,-163.499,60.425,-163.468,60.414]]]},{"n":"Bear","z":4.7,"b":[-111.407,41.871,-111.282,42.152],"p":[[[-111.288,42.033,-111.308,41.949,-111.327,41.889,-111.349,41.871,-111.373,41.883,-111.394,41.937,-111.407,41.988,-111.403,42.062,-111.393,42.131,-111.344,42.152,-111.313,42.148,-111.285,42.109,-111.282,42.059,-111.288,42.033]]]},{"n":"Utah","z":4.7,"b":[-111.914,40.064,-111.68,40.377],"p":[[[-111.742,40.19,-111.827,40.158,-111.848,40.095,-111.867,40.07,-111.892,40.064,-111.894,40.081,-111.89,40.117,-111.899,40.154,-111.914,40.176,-111.852,40.252,-111.86,40.323,-111.881,40.377,-111.793,40.369,-111.763,40.337,-111.733,40.285,-111.701,40.238,-111.68,40.225,-111.69,40.206,-111.742,40.19]]]},{"n":"","z":4.7,"b":[-126.414,66.754,-126.129,67.021],"p":[[[-126.414,66.938,-126.399,66.983,-126.356,67.011,-126.29,67.021,-126.218,67.01,-126.158,66.979,-126.129,66.941,-126.149,66.909,-126.183,66.883,-126.191,66.855,-126.177,66.817,-126.189,66.796,-126.236,66.775,-126.275,66.754,-126.314,66.76,-126.369,66.784,-126.408,66.857,-126.414,66.938]]]},{"n":"Leech","z":4.7,"b":[-94.621,47.062,-94.245,47.285],"p":[[[-94.245,47.191,-94.26,47.15,-94.302,47.097,-94.346,47.075,-94.395,47.067,-94.433,47.077,-94.461,47.109,-94.519,47.143,-94.566,47.12,-94.57,47.075,-94.578,47.062,-94.586,47.079,-94.605,47.123,-94.621,47.166,-94.611,47.206,-94.589,47.202,-94.502,47.221,-94.437,47.232,-94.379,47.241,-94.296,47.285,-94.281,47.27,-94.297,47.221,-94.308,47.183,-94.278,47.186,-94.245,47.191]]]},{"n":"Mille Lacs","z":4.7,"b":[-90.688,48.785,-90.288,48.974],"p":[[[-90.292,48.935,-90.288,48.916,-90.314,48.878,-90.34,48.821,-90.375,48.786,-90.44,48.785,-90.458,48.798,-90.525,48.806,-90.601,48.811,-90.634,48.833,-90.632,48.867,-90.65,48.917,-90.684,48.92,-90.688,48.944,-90.666,48.967,-90.626,48.974,-90.591,48.96,-90.561,48.926,-90.546,48.895,-90.48,48.885,-90.4,48.886,-90.361,48.893,-90.324,48.916,-90.292,48.935]]]},{"n":"Baskatong","z":4.7,"b":[-76.033,46.715,-75.688,46.997],"p":[[[-75.694,46.897,-75.688,46.872,-75.698,46.825,-75.732,46.768,-75.751,46.718,-75.795,46.715,-75.864,46.755,-75.943,46.759,-76.017,46.747,-76.033,46.759,-76.01,46.778,-75.971,46.819,-75.958,46.859,-75.955,46.875,-75.89,46.889,-75.831,46.902,-75.779,46.964,-75.737,46.997,-75.727,46.974,-75.694,46.897]]]},{"n":"Ranco","z":4.7,"b":[-72.558,-40.302,-72.263,-40.128],"p":[[[-72.265,-40.26,-72.263,-40.292,-72.273,-40.302,-72.331,-40.299,-72.457,-40.293,-72.547,-40.272,-72.558,-40.235,-72.552,-40.201,-72.529,-40.172,-72.425,-40.135,-72.33,-40.128,-72.299,-40.15,-72.273,-40.166,-72.267,-40.192,-72.27,-40.221,-72.265,-40.26]]]},{"n":"Trout","z":4.7,"b":[-121.586,60.4,-121.091,60.771],"p":[[[-121.256,60.448,-121.355,60.408,-121.408,60.4,-121.429,60.414,-121.456,60.443,-121.497,60.459,-121.586,60.438,-121.566,60.464,-121.484,60.517,-121.389,60.56,-121.316,60.588,-121.301,60.623,-121.314,60.7,-121.336,60.771,-121.276,60.762,-121.176,60.722,-121.108,60.665,-121.091,60.592,-121.149,60.513,-121.256,60.448]]]},{"n":"Viedma","z":4.7,"b":[-73.03,-49.784,-72.101,-49.44],"p":[[[-72.306,-49.761,-72.49,-49.691,-72.621,-49.641,-72.71,-49.639,-72.753,-49.657,-72.825,-49.659,-72.943,-49.668,-73.029,-49.673,-73.03,-49.662,-73.002,-49.639,-72.973,-49.616,-72.969,-49.588,-72.991,-49.552,-73.018,-49.508,-72.985,-49.481,-72.93,-49.44,-72.878,-49.44,-72.814,-49.478,-72.52,-49.552,-72.261,-49.611,-72.211,-49.626,-72.132,-49.668,-72.101,-49.709,-72.101,-49.736,-72.114,-49.759,-72.136,-49.775,-72.184,-49.784,-72.306,-49.761]]]},{"n":"Tumba","z":4.7,"b":[17.811,-0.989,18.129,-0.652],"p":[[[18.036,-0.957,18.014,-0.931,17.982,-0.892,17.941,-0.852,17.917,-0.816,17.918,-0.771,17.901,-0.736,17.843,-0.713,17.817,-0.695,17.811,-0.672,17.833,-0.669,17.901,-0.665,17.983,-0.652,18.045,-0.657,18.089,-0.68,18.114,-0.708,18.121,-0.731,18.111,-0.782,18.107,-0.799,18.117,-0.86,18.126,-0.931,18.129,-0.981,18.102,-0.989,18.077,-0.971,18.057,-0.956,18.036,-0.957]]]},{"n":"","z":4.7,"b":[30.727,63.613,31.199,63.922],"p":[[[30.768,63.834,30.795,63.879,30.797,63.922,30.833,63.918,30.873,63.903,30.882,63.887,30.896,63.868,31.023,63.836,31.167,63.815,31.199,63.786,31.191,63.775,31.17,63.771,31.14,63.783,31.1,63.79,31.08,63.78,31.088,63.754,31.121,63.733,31.138,63.709,31.134,63.698,31.114,63.681,31.122,63.654,31.144,63.629,31.129,63.613,31.068,63.64,30.999,63.668,30.949,63.683,30.912,63.708,30.919,63.738,30.916,63.767,30.883,63.79,30.749,63.805,30.727,63.825,30.768,63.834]]]},{"n":"Po","z":4.7,"b":[116.314,30.078,116.54,30.245],"p":[[[116.424,30.108,116.401,30.118,116.387,30.143,116.373,30.161,116.351,30.175,116.317,30.203,116.314,30.23,116.335,30.234,116.357,30.245,116.371,30.244,116.39,30.224,116.443,30.207,116.472,30.204,116.514,30.208,116.539,30.207,116.54,30.18,116.527,30.149,116.525,30.116,116.526,30.089,116.506,30.078,116.494,30.085,116.468,30.104,116.424,30.108]]]},{"n":"Buir","z":4.7,"b":[117.529,47.666,117.869,47.961],"p":[[[117.581,47.686,117.558,47.71,117.529,47.739,117.531,47.776,117.57,47.835,117.638,47.901,117.709,47.948,117.764,47.961,117.801,47.95,117.841,47.91,117.869,47.865,117.856,47.825,117.806,47.772,117.729,47.718,117.655,47.68,117.606,47.666,117.586,47.671,117.581,47.686]]]},{"n":"Ang Laren","z":4.7,"b":[82.837,31.445,83.341,31.652],"p":[[[83.23,31.464,83.201,31.464,83.094,31.492,82.991,31.515,82.913,31.517,82.857,31.541,82.837,31.564,82.894,31.575,82.928,31.596,82.934,31.612,82.974,31.627,83.06,31.641,83.1,31.652,83.142,31.597,83.209,31.557,83.29,31.508,83.341,31.462,83.333,31.445,83.296,31.446,83.23,31.464]]]},{"n":"Tangra Yumco","z":4.7,"b":[86.396,30.795,86.777,31.367],"p":[[[86.777,31.166,86.764,31.136,86.704,31.123,86.628,31.084,86.607,31.034,86.565,30.943,86.523,30.858,86.494,30.811,86.457,30.795,86.419,30.807,86.397,30.84,86.396,30.871,86.412,30.894,86.445,30.952,86.516,31.058,86.607,31.221,86.654,31.367,86.742,31.342,86.765,31.256,86.777,31.166]]]},{"n":"Kulundinskoe","z":4.7,"b":[79.387,52.83,79.809,53.143],"p":[[[79.77,52.967,79.744,52.932,79.731,52.9,79.736,52.873,79.75,52.86,79.734,52.842,79.691,52.83,79.603,52.837,79.517,52.858,79.451,52.899,79.4,52.944,79.387,52.974,79.387,53.007,79.399,53.041,79.427,53.076,79.472,53.102,79.594,53.143,79.638,53.137,79.695,53.118,79.738,53.092,79.767,53.048,79.791,53.012,79.809,52.996,79.77,52.967]]]},{"n":"Siletiteniz","z":4.7,"b":[72.923,53.07,73.424,53.398],"p":[[[73.387,53.07,73.325,53.113,73.197,53.205,73.088,53.269,73.014,53.271,72.958,53.25,72.933,53.26,72.923,53.281,72.957,53.309,72.981,53.344,72.987,53.368,73.031,53.384,73.109,53.395,73.204,53.398,73.321,53.382,73.405,53.361,73.424,53.337,73.421,53.255,73.419,53.154,73.408,53.108,73.403,53.088,73.407,53.072,73.387,53.07]]]},{"n":"Ubinskoye","z":4.7,"b":[79.823,55.385,80.328,55.564],"p":[[[80.328,55.54,80.308,55.494,80.208,55.452,80.1,55.413,79.987,55.39,79.898,55.385,79.847,55.397,79.823,55.423,79.823,55.452,79.881,55.479,79.984,55.516,80.086,55.55,80.186,55.564,80.269,55.561,80.305,55.563,80.328,55.54]]]},{"n":"","z":4.7,"b":[38.647,61.195,38.917,61.467],"p":[[[38.651,61.344,38.675,61.39,38.708,61.421,38.797,61.452,38.894,61.467,38.917,61.445,38.884,61.381,38.858,61.323,38.875,61.289,38.895,61.255,38.886,61.229,38.837,61.207,38.761,61.195,38.697,61.199,38.663,61.217,38.647,61.273,38.651,61.344]]]},{"n":"","z":4.7,"b":[27.886,66.034,29.089,66.338],"p":[[[28.869,66.311,28.949,66.306,29.03,66.31,29.089,66.299,29.053,66.284,29.03,66.27,29.007,66.243,28.941,66.22,28.791,66.162,28.701,66.106,28.626,66.063,28.523,66.049,28.364,66.034,28.07,66.034,28.043,66.048,28.049,66.066,28.047,66.09,28.034,66.108,28.025,66.119,27.952,66.144,27.886,66.168,27.94,66.172,28.03,66.162,28.086,66.145,28.118,66.125,28.128,66.097,28.138,66.074,28.177,66.072,28.32,66.1,28.433,66.125,28.506,66.136,28.54,66.167,28.561,66.186,28.703,66.212,28.833,66.233,28.836,66.242,28.835,66.262,28.78,66.3,28.727,66.331,28.755,66.338,28.807,66.335,28.869,66.311]]]},{"n":"Umbozero","z":4.7,"b":[34.194,67.543,34.582,67.887],"p":[[[34.194,67.871,34.216,67.882,34.28,67.887,34.335,67.858,34.363,67.805,34.449,67.734,34.551,67.645,34.582,67.593,34.534,67.552,34.409,67.543,34.318,67.545,34.317,67.572,34.313,67.6,34.315,67.655,34.319,67.701,34.301,67.724,34.254,67.75,34.237,67.771,34.248,67.793,34.218,67.836,34.194,67.871]]]},{"n":"Kovdozero","z":4.7,"b":[31.554,66.425,32.397,66.912],"p":[[[32.315,66.502,32.3,66.486,32.304,66.481,32.325,66.466,32.366,66.458,32.381,66.454,32.397,66.445,32.39,66.438,32.355,66.425,32.224,66.453,32.12,66.471,31.991,66.471,31.978,66.49,31.983,66.499,32.107,66.519,32.135,66.538,32.119,66.564,32.028,66.634,31.963,66.7,31.901,66.741,31.736,66.75,31.577,66.758,31.561,66.769,31.554,66.783,31.576,66.799,31.609,66.813,31.621,66.822,31.708,66.823,31.764,66.838,31.765,66.858,31.701,66.873,31.68,66.885,31.746,66.912,31.861,66.886,31.948,66.841,32.087,66.845,32.218,66.835,32.239,66.816,32.203,66.779,32.18,66.761,32.196,66.745,32.249,66.731,32.31,66.706,32.329,66.69,32.339,66.674,32.311,66.652,32.255,66.624,32.22,66.601,32.242,66.566,32.306,66.525,32.315,66.502]]]},{"n":"Tekapo","z":4.7,"b":[170.5,-44.003,170.656,-43.534],"p":[[[170.656,-43.658,170.653,-43.681,170.618,-43.737,170.576,-43.851,170.575,-43.935,170.51,-44.003,170.508,-43.967,170.509,-43.896,170.512,-43.853,170.53,-43.816,170.5,-43.596,170.519,-43.534,170.531,-43.579,170.543,-43.675,170.559,-43.707,170.579,-43.718,170.6,-43.712,170.632,-43.678,170.656,-43.658]]]},{"n":"Bustakh","z":4.7,"b":[141.586,72.465,142.194,72.608],"p":[[[142.194,72.562,142.153,72.549,142.057,72.518,141.96,72.484,141.829,72.466,141.69,72.465,141.639,72.476,141.637,72.5,141.623,72.521,141.607,72.541,141.586,72.57,141.622,72.579,141.794,72.593,141.965,72.608,142.127,72.595,142.194,72.562]]]},{"n":"Yamdrok","z":4.7,"b":[90.386,28.786,91.003,29.211],"p":[[[90.931,28.923,90.89,28.934,90.867,28.927,90.856,28.928,90.835,28.933,90.808,28.938,90.768,28.914,90.752,28.875,90.739,28.866,90.715,28.883,90.67,28.864,90.631,28.838,90.59,28.797,90.566,28.787,90.535,28.786,90.508,28.804,90.479,28.835,90.454,28.86,90.447,28.878,90.456,28.887,90.474,28.885,90.492,28.87,90.51,28.859,90.53,28.861,90.561,28.874,90.594,28.903,90.621,28.939,90.647,28.975,90.672,29.011,90.664,29.047,90.659,29.089,90.654,29.126,90.63,29.151,90.59,29.167,90.534,29.165,90.477,29.145,90.424,29.111,90.388,29.093,90.386,29.107,90.424,29.138,90.503,29.188,90.562,29.211,90.633,29.192,90.692,29.161,90.729,29.13,90.726,29.109,90.719,29.094,90.721,29.072,90.73,29.052,90.768,29.012,90.788,28.991,90.829,28.989,90.844,29.013,90.876,29.024,90.919,29.021,90.963,29.003,90.992,28.978,90.988,28.951,91.003,28.904,90.993,28.892,90.956,28.912,90.931,28.923]]]},{"n":"Ozero Tenis","z":4.7,"b":[71.625,56.058,72.009,56.211],"p":[[[72.003,56.202,72.009,56.177,71.989,56.143,71.947,56.109,71.869,56.106,71.817,56.095,71.778,56.087,71.75,56.064,71.718,56.058,71.676,56.067,71.662,56.115,71.625,56.178,71.642,56.191,71.721,56.197,71.82,56.189,71.862,56.192,71.88,56.206,71.941,56.211,72.003,56.202]]]},{"n":"Ozero Chernoye","z":4.7,"b":[67.296,55.713,67.679,55.869],"p":[[[67.679,55.801,67.64,55.792,67.621,55.78,67.608,55.765,67.594,55.75,67.504,55.713,67.396,55.718,67.298,55.745,67.296,55.777,67.321,55.815,67.348,55.851,67.351,55.868,67.382,55.869,67.433,55.835,67.483,55.806,67.536,55.806,67.595,55.821,67.668,55.817,67.679,55.801]]]},{"n":"","z":4.7,"b":[32.704,65.762,33.087,66.044],"p":[[[32.91,65.975,32.931,65.957,32.974,65.94,33.055,65.93,33.087,65.909,33.081,65.879,33.076,65.852,33.072,65.833,32.946,65.794,32.8,65.762,32.743,65.769,32.736,65.787,32.734,65.805,32.704,65.83,32.778,65.858,32.842,65.876,32.849,65.894,32.838,65.913,32.795,65.929,32.745,65.948,32.751,65.975,32.796,66.007,32.811,66.033,32.836,66.044,32.898,66.043,32.891,66.023,32.863,65.998,32.871,65.983,32.91,65.975]]]},{"n":"Bolshoe Morskoe","z":4.7,"b":[158.546,69.99,158.934,70.138],"p":[[[158.934,70.092,158.913,70.084,158.883,70.073,158.845,70.036,158.819,70,158.792,69.99,158.752,69.994,158.642,70.029,158.573,70.066,158.546,70.099,158.558,70.124,158.61,70.138,158.709,70.138,158.854,70.122,158.925,70.119,158.934,70.092]]]},{"n":"Chukochye","z":4.7,"b":[160.025,69.352,160.445,69.539],"p":[[[160.025,69.456,160.066,69.468,160.099,69.485,160.068,69.514,160.055,69.538,160.104,69.539,160.22,69.519,160.325,69.489,160.367,69.462,160.413,69.441,160.445,69.43,160.424,69.42,160.3,69.417,160.206,69.411,160.249,69.386,160.278,69.359,160.248,69.352,160.152,69.369,160.056,69.394,160.033,69.41,160.047,69.428,160.044,69.446,160.025,69.456]]]},{"n":"Shijiu","z":4.7,"b":[118.723,31.393,118.964,31.554],"p":[[[118.78,31.409,118.768,31.419,118.747,31.459,118.743,31.492,118.724,31.522,118.723,31.542,118.758,31.545,118.867,31.546,118.941,31.554,118.964,31.503,118.96,31.465,118.915,31.435,118.85,31.393,118.815,31.402,118.78,31.409]]]},{"n":"Hong","z":4.7,"b":[113.23,29.749,113.454,29.953],"p":[[[113.451,29.94,113.454,29.914,113.426,29.876,113.391,29.826,113.369,29.785,113.34,29.76,113.312,29.749,113.275,29.78,113.237,29.848,113.23,29.893,113.249,29.904,113.302,29.888,113.331,29.894,113.362,29.949,113.406,29.953,113.451,29.94]]]},{"n":"Võrtsjärv","z":4.7,"b":[25.928,58.143,26.143,58.396],"p":[[[26.143,58.327,26.141,58.269,26.11,58.21,26.085,58.143,26.051,58.146,26.004,58.219,25.928,58.305,25.941,58.345,25.966,58.382,26.019,58.396,26.077,58.391,26.121,58.374,26.143,58.327]]]},{"n":"Chao","z":4.7,"b":[117.297,31.445,117.941,31.709],"p":[[[117.941,31.521,117.912,31.522,117.83,31.57,117.784,31.579,117.706,31.524,117.623,31.455,117.563,31.445,117.468,31.467,117.382,31.536,117.317,31.592,117.297,31.65,117.315,31.697,117.366,31.709,117.413,31.694,117.434,31.651,117.448,31.61,117.487,31.593,117.543,31.591,117.594,31.609,117.652,31.654,117.733,31.66,117.81,31.621,117.919,31.551,117.941,31.521]]]},{"n":"","z":4.7,"b":[12.899,63.427,13.358,63.726],"p":[[[12.899,63.652,12.93,63.686,13.012,63.712,13.117,63.726,13.209,63.724,13.273,63.711,13.3,63.695,13.276,63.686,13.16,63.69,13.11,63.671,13.085,63.65,13.097,63.608,13.119,63.554,13.213,63.486,13.358,63.451,13.335,63.427,13.243,63.452,13.179,63.471,13.113,63.497,13.01,63.544,12.936,63.6,12.899,63.652]]]},{"n":"Rossignol","z":4.7,"b":[-65.199,44.149,-64.964,44.272],"p":[[[-64.966,44.252,-64.964,44.238,-65.001,44.182,-65.025,44.164,-65.099,44.149,-65.176,44.155,-65.198,44.181,-65.199,44.208,-65.189,44.235,-65.167,44.257,-65.134,44.27,-65.104,44.272,-65.095,44.247,-65.08,44.228,-65.053,44.224,-65.01,44.23,-64.966,44.252]]]},{"n":"Foshay Lake","z":4.7,"b":[-66.19,45.856,-65.92,46.099],"p":[[[-66.106,45.871,-66.15,45.856,-66.19,45.881,-66.131,45.91,-66.077,45.953,-66.048,46.01,-66.027,46.05,-66.002,46.066,-65.97,46.082,-65.958,46.091,-65.942,46.099,-65.92,46.093,-65.92,46.064,-65.93,46.034,-65.95,46.006,-65.979,45.969,-65.989,45.942,-66.041,45.904,-66.106,45.871]]]},{"n":"Goose","z":4.7,"b":[-120.489,41.785,-120.323,42.107],"p":[[[-120.324,41.987,-120.35,41.923,-120.378,41.832,-120.396,41.792,-120.443,41.785,-120.466,41.803,-120.484,41.869,-120.489,41.988,-120.475,42.065,-120.449,42.088,-120.411,42.107,-120.375,42.09,-120.344,42.066,-120.323,42.04,-120.324,41.987]]]},{"n":"Pyramid","z":4.7,"b":[-119.696,39.868,-119.432,40.209],"p":[[[-119.432,39.903,-119.433,39.886,-119.446,39.868,-119.487,39.878,-119.547,39.927,-119.636,40.028,-119.696,40.123,-119.684,40.15,-119.641,40.191,-119.577,40.209,-119.534,40.181,-119.515,40.082,-119.483,39.974,-119.454,39.933,-119.432,39.903]]]},{"n":"Sevan","z":4.7,"b":[44.972,40.149,45.659,40.614],"p":[[[44.99,40.515,44.972,40.542,44.989,40.582,44.986,40.612,45.015,40.614,45.095,40.59,45.242,40.51,45.311,40.488,45.373,40.479,45.437,40.435,45.537,40.365,45.602,40.318,45.636,40.279,45.656,40.243,45.659,40.215,45.651,40.195,45.608,40.172,45.543,40.156,45.481,40.156,45.389,40.151,45.313,40.149,45.276,40.164,45.248,40.197,45.218,40.296,45.183,40.385,45.153,40.404,45.125,40.428,45.095,40.461,45.04,40.49,44.99,40.515]]]},{"n":"","z":4.7,"b":[114.674,30.211,114.846,30.345],"p":[[[114.839,30.287,114.812,30.283,114.783,30.278,114.753,30.283,114.739,30.279,114.743,30.263,114.76,30.238,114.781,30.221,114.778,30.215,114.762,30.211,114.735,30.212,114.704,30.212,114.679,30.228,114.679,30.25,114.68,30.277,114.674,30.3,114.713,30.33,114.764,30.345,114.784,30.344,114.806,30.345,114.834,30.327,114.846,30.302,114.839,30.287]]]},{"n":"","z":4.7,"b":[114.584,30.608,114.786,30.751],"p":[[[114.601,30.662,114.622,30.656,114.64,30.66,114.649,30.674,114.646,30.715,114.674,30.724,114.703,30.735,114.719,30.751,114.746,30.738,114.781,30.701,114.786,30.663,114.756,30.65,114.696,30.613,114.622,30.608,114.584,30.65,114.586,30.663,114.601,30.662]]]},{"n":"Nansi","z":4.7,"b":[117.128,34.498,117.326,34.725],"p":[[[117.326,34.587,117.295,34.549,117.25,34.502,117.21,34.498,117.174,34.543,117.141,34.612,117.128,34.663,117.139,34.725,117.179,34.719,117.266,34.665,117.318,34.621,117.326,34.587]]]},{"n":"","z":4.7,"b":[114.482,30.339,114.756,30.512],"p":[[[114.601,30.344,114.553,30.384,114.499,30.418,114.482,30.431,114.495,30.439,114.533,30.435,114.575,30.405,114.601,30.399,114.631,30.42,114.633,30.44,114.629,30.456,114.607,30.483,114.609,30.496,114.64,30.486,114.666,30.49,114.678,30.497,114.701,30.512,114.739,30.494,114.756,30.477,114.752,30.463,114.748,30.443,114.753,30.429,114.727,30.42,114.683,30.397,114.655,30.346,114.623,30.339,114.601,30.344]]]},{"n":"Futou Lake","z":4.7,"b":[114.143,29.901,114.301,30.141],"p":[[[114.301,30.025,114.289,30.002,114.252,29.972,114.244,29.938,114.264,29.915,114.255,29.906,114.218,29.901,114.171,29.921,114.143,29.988,114.147,30.083,114.177,30.114,114.218,30.141,114.249,30.141,114.246,30.115,114.237,30.084,114.253,30.045,114.273,30.044,114.294,30.043,114.301,30.025]]]},{"n":"","z":4.7,"b":[113.941,29.862,114.158,30.027],"p":[[[114.158,29.872,114.137,29.862,114.114,29.883,114.087,29.934,114.063,29.927,114.029,29.898,113.99,29.879,113.969,29.88,113.963,29.892,113.984,29.916,114.003,29.942,113.996,29.959,113.97,29.963,113.944,29.966,113.941,29.981,114.019,30.001,114.074,30.027,114.101,30.022,114.13,29.935,114.152,29.89,114.158,29.872]]]},{"n":"Millerton","z":4.7,"b":[-119.693,37,-119.598,37.057],"p":[[[-119.603,37.057,-119.598,37.051,-119.603,37.031,-119.641,37.008,-119.693,37,-119.672,37.028,-119.623,37.046,-119.603,37.057]]]},{"n":"Bluenose","z":4.7,"b":[-119.875,68.246,-119.47,68.755],"p":[[[-119.875,68.558,-119.848,68.583,-119.806,68.619,-119.809,68.642,-119.825,68.661,-119.786,68.684,-119.744,68.719,-119.72,68.746,-119.691,68.755,-119.665,68.754,-119.663,68.744,-119.625,68.715,-119.613,68.698,-119.572,68.672,-119.526,68.658,-119.486,68.633,-119.47,68.608,-119.487,68.595,-119.552,68.594,-119.595,68.574,-119.621,68.532,-119.65,68.471,-119.642,68.44,-119.59,68.396,-119.511,68.361,-119.525,68.332,-119.646,68.289,-119.734,68.255,-119.817,68.246,-119.848,68.254,-119.844,68.321,-119.833,68.421,-119.835,68.484,-119.859,68.529,-119.875,68.558]]]},{"n":"Pipmuacan","z":4.7,"b":[-70.963,49.333,-69.783,49.887],"p":[[[-70.08,49.44,-70.137,49.491,-70.14,49.53,-70.137,49.565,-70.157,49.592,-70.195,49.617,-70.242,49.635,-70.283,49.641,-70.324,49.624,-70.351,49.59,-70.335,49.54,-70.329,49.523,-70.354,49.504,-70.406,49.519,-70.431,49.532,-70.45,49.537,-70.467,49.532,-70.487,49.499,-70.505,49.461,-70.508,49.427,-70.516,49.411,-70.539,49.439,-70.571,49.473,-70.639,49.466,-70.677,49.448,-70.701,49.427,-70.736,49.442,-70.756,49.488,-70.789,49.536,-70.816,49.543,-70.834,49.542,-70.855,49.525,-70.892,49.457,-70.94,49.408,-70.963,49.406,-70.921,49.497,-70.859,49.599,-70.834,49.625,-70.826,49.616,-70.792,49.596,-70.744,49.56,-70.716,49.525,-70.667,49.531,-70.585,49.572,-70.514,49.63,-70.471,49.672,-70.442,49.684,-70.411,49.682,-70.34,49.67,-70.305,49.701,-70.307,49.748,-70.358,49.832,-70.377,49.887,-70.339,49.884,-70.317,49.864,-70.289,49.813,-70.259,49.75,-70.236,49.699,-70.206,49.682,-70.171,49.684,-70.135,49.708,-70.1,49.69,-70.073,49.643,-70.017,49.597,-69.951,49.555,-69.899,49.545,-69.844,49.472,-69.787,49.37,-69.783,49.339,-69.825,49.333,-69.856,49.342,-69.866,49.366,-69.889,49.407,-69.904,49.451,-69.902,49.481,-69.917,49.504,-69.946,49.518,-69.982,49.517,-70.029,49.498,-70.064,49.475,-70.069,49.459,-70.08,49.44]]]},{"n":"Grand Lake","z":4.7,"b":[-58.013,48.643,-56.928,49.336],"p":[[[-57.035,49.265,-57.011,49.297,-56.99,49.332,-56.963,49.336,-56.944,49.304,-56.928,49.264,-56.97,49.227,-57.025,49.213,-57.053,49.215,-57.13,49.166,-57.24,49.056,-57.435,48.907,-57.513,48.858,-57.578,48.786,-57.76,48.672,-57.859,48.643,-57.947,48.658,-58.013,48.663,-57.965,48.685,-57.78,48.774,-57.665,48.859,-57.562,48.894,-57.371,49.011,-57.27,49.099,-57.262,49.147,-57.262,49.181,-57.221,49.189,-57.115,49.237,-57.035,49.265]]]},{"n":"","z":4.7,"b":[-126.762,67.256,-126.191,67.578],"p":[[[-126.357,67.271,-126.419,67.256,-126.495,67.263,-126.514,67.32,-126.536,67.398,-126.548,67.442,-126.584,67.471,-126.6,67.495,-126.613,67.522,-126.691,67.535,-126.762,67.56,-126.73,67.573,-126.614,67.578,-126.494,67.552,-126.398,67.488,-126.298,67.444,-126.231,67.434,-126.205,67.419,-126.198,67.403,-126.208,67.382,-126.208,67.358,-126.191,67.33,-126.206,67.304,-126.357,67.271]]]},{"n":"Colville","z":4.7,"b":[-126.241,67.059,-125.73,67.313],"p":[[[-125.751,67.313,-125.73,67.297,-125.756,67.238,-125.793,67.177,-125.828,67.124,-125.918,67.079,-126.061,67.059,-126.166,67.065,-126.222,67.085,-126.224,67.104,-126.196,67.115,-126.184,67.138,-126.191,67.169,-126.216,67.182,-126.241,67.199,-126.235,67.215,-126.033,67.254,-125.793,67.305,-125.751,67.313]]]},{"n":"Hottah","z":4.7,"b":[-118.832,64.781,-118.146,65.291],"p":[[[-118.666,64.999,-118.682,65.03,-118.681,65.061,-118.677,65.081,-118.698,65.109,-118.767,65.158,-118.832,65.192,-118.825,65.217,-118.733,65.245,-118.659,65.262,-118.552,65.279,-118.445,65.291,-118.423,65.281,-118.395,65.261,-118.365,65.245,-118.303,65.227,-118.265,65.199,-118.246,65.136,-118.279,65.09,-118.282,65.069,-118.275,65.043,-118.248,65.014,-118.237,64.986,-118.248,64.956,-118.276,64.929,-118.325,64.903,-118.319,64.873,-118.239,64.845,-118.156,64.852,-118.149,64.843,-118.146,64.795,-118.176,64.786,-118.333,64.781,-118.488,64.841,-118.578,64.884,-118.636,64.928,-118.664,64.97,-118.666,64.999]]]},{"n":"Big Trout","z":4.7,"b":[-90.357,53.679,-89.609,53.886],"p":[[[-90.357,53.766,-90.344,53.789,-90.321,53.835,-90.29,53.868,-90.252,53.87,-90.227,53.856,-90.201,53.841,-90.171,53.838,-90.145,53.854,-90.101,53.871,-90.046,53.881,-90.002,53.886,-89.928,53.863,-89.862,53.834,-89.847,53.815,-89.729,53.791,-89.609,53.772,-89.678,53.733,-89.814,53.69,-89.939,53.679,-90.034,53.685,-90.103,53.717,-90.167,53.747,-90.275,53.757,-90.357,53.766]]]},{"n":"Trout","z":4.7,"b":[-93.481,51.033,-93.072,51.302],"p":[[[-93.165,51.071,-93.182,51.055,-93.2,51.039,-93.235,51.033,-93.32,51.075,-93.429,51.154,-93.481,51.208,-93.48,51.237,-93.455,51.264,-93.406,51.292,-93.37,51.302,-93.344,51.286,-93.283,51.279,-93.215,51.299,-93.175,51.295,-93.141,51.273,-93.123,51.241,-93.101,51.207,-93.072,51.173,-93.074,51.159,-93.137,51.123,-93.165,51.071]]]},{"n":"Island","z":4.7,"b":[-94.949,53.71,-94.002,53.989],"p":[[[-94.236,53.734,-94.351,53.73,-94.446,53.72,-94.518,53.71,-94.553,53.713,-94.682,53.733,-94.855,53.786,-94.933,53.827,-94.949,53.859,-94.938,53.902,-94.933,53.933,-94.889,53.946,-94.851,53.956,-94.822,53.975,-94.754,53.989,-94.634,53.986,-94.601,53.972,-94.621,53.945,-94.686,53.915,-94.689,53.897,-94.654,53.876,-94.59,53.858,-94.477,53.864,-94.346,53.874,-94.229,53.883,-94.131,53.892,-94.048,53.882,-94.002,53.847,-94.014,53.798,-94.103,53.759,-94.18,53.742,-94.236,53.734]]]},{"n":"Naococane","z":4.7,"b":[-70.982,52.61,-70.397,53.06],"p":[[[-70.509,52.996,-70.486,52.983,-70.437,52.976,-70.397,52.974,-70.442,52.941,-70.476,52.901,-70.484,52.872,-70.464,52.863,-70.467,52.841,-70.474,52.813,-70.468,52.746,-70.444,52.677,-70.422,52.639,-70.433,52.61,-70.487,52.63,-70.522,52.682,-70.519,52.725,-70.522,52.761,-70.521,52.785,-70.538,52.8,-70.577,52.803,-70.592,52.789,-70.603,52.759,-70.64,52.736,-70.656,52.751,-70.703,52.775,-70.762,52.787,-70.78,52.809,-70.777,52.863,-70.783,52.899,-70.817,52.909,-70.847,52.878,-70.899,52.848,-70.94,52.817,-70.965,52.819,-70.981,52.841,-70.982,52.872,-70.952,52.907,-70.924,52.938,-70.899,52.94,-70.838,52.943,-70.768,52.956,-70.75,52.963,-70.741,52.981,-70.748,52.996,-70.771,53.008,-70.828,53.015,-70.831,53.028,-70.833,53.043,-70.82,53.06,-70.75,53.038,-70.691,52.998,-70.652,52.99,-70.599,52.988,-70.548,53.007,-70.527,53.007,-70.509,52.996]]]},{"n":"Ronge","z":4.7,"b":[-105.258,54.925,-104.369,55.307],"p":[[[-104.396,55.16,-104.455,55.138,-104.507,55.133,-104.591,55.154,-104.637,55.148,-104.69,55.126,-104.735,55.09,-104.79,55.034,-104.822,54.994,-104.901,54.977,-105.026,54.947,-105.128,54.925,-105.194,54.936,-105.237,54.957,-105.245,55.002,-105.247,55.048,-105.258,55.074,-105.233,55.117,-105.218,55.156,-105.224,55.179,-105.229,55.2,-105.237,55.219,-105.205,55.248,-105.138,55.283,-105.081,55.302,-105.06,55.298,-104.955,55.28,-104.833,55.306,-104.808,55.301,-104.798,55.292,-104.755,55.302,-104.714,55.296,-104.674,55.291,-104.621,55.307,-104.59,55.302,-104.574,55.281,-104.519,55.257,-104.418,55.254,-104.4,55.247,-104.369,55.182,-104.396,55.16]]]},{"n":"Mille Lacs","z":4.7,"b":[-93.795,46.136,-93.514,46.36],"p":[[[-93.534,46.249,-93.521,46.21,-93.514,46.177,-93.54,46.15,-93.6,46.136,-93.668,46.137,-93.726,46.158,-93.775,46.2,-93.795,46.255,-93.758,46.308,-93.679,46.345,-93.607,46.36,-93.569,46.357,-93.552,46.341,-93.542,46.3,-93.538,46.269,-93.534,46.249]]]},{"n":"Winnipesaukee","z":4.7,"b":[-71.443,43.532,-71.214,43.725],"p":[[[-71.293,43.635,-71.262,43.609,-71.216,43.587,-71.214,43.569,-71.232,43.54,-71.277,43.532,-71.371,43.598,-71.443,43.628,-71.436,43.656,-71.43,43.679,-71.409,43.695,-71.394,43.711,-71.399,43.725,-71.374,43.722,-71.331,43.7,-71.306,43.667,-71.293,43.635]]]},{"n":"Simcoe","z":4.7,"b":[-79.584,44.227,-79.163,44.698],"p":[[[-79.448,44.31,-79.479,44.27,-79.48,44.236,-79.499,44.227,-79.519,44.274,-79.537,44.353,-79.574,44.393,-79.584,44.405,-79.567,44.431,-79.503,44.463,-79.445,44.51,-79.413,44.562,-79.393,44.603,-79.387,44.641,-79.373,44.698,-79.354,44.69,-79.341,44.636,-79.265,44.563,-79.211,44.535,-79.177,44.491,-79.163,44.45,-79.172,44.419,-79.202,44.384,-79.342,44.343,-79.448,44.31]]]},{"n":"Gods","z":4.7,"b":[-94.768,54.412,-93.645,54.85],"p":[[[-93.923,54.835,-93.777,54.808,-93.69,54.792,-93.645,54.788,-93.647,54.779,-93.702,54.758,-93.835,54.758,-93.912,54.744,-93.934,54.709,-93.989,54.671,-94.157,54.636,-94.291,54.618,-94.34,54.607,-94.414,54.586,-94.45,54.542,-94.465,54.521,-94.495,54.502,-94.551,54.48,-94.675,54.44,-94.763,54.412,-94.767,54.427,-94.768,54.454,-94.758,54.476,-94.735,54.5,-94.65,54.541,-94.558,54.623,-94.508,54.669,-94.489,54.724,-94.476,54.786,-94.477,54.801,-94.437,54.805,-94.369,54.819,-94.272,54.842,-94.11,54.85,-94.012,54.847,-93.923,54.835]]]},{"n":"Hoh Xil Hu","z":4.7,"b":[90.95,35.528,91.316,35.653],"p":[[[91.316,35.595,91.303,35.574,91.286,35.544,91.268,35.528,91.165,35.547,91.043,35.565,90.979,35.609,90.95,35.634,90.953,35.645,90.993,35.653,91.033,35.65,91.115,35.637,91.199,35.634,91.222,35.646,91.271,35.629,91.316,35.595]]]},{"n":"Hammar","z":4.7,"b":[46.712,30.663,47.608,30.917],"p":[[[47.608,30.689,47.573,30.664,47.491,30.663,47.407,30.678,47.245,30.711,47.104,30.74,47.04,30.742,46.953,30.733,46.879,30.727,46.852,30.722,46.822,30.725,46.807,30.738,46.804,30.764,46.802,30.786,46.792,30.803,46.765,30.817,46.75,30.823,46.719,30.837,46.712,30.867,46.748,30.903,46.801,30.913,46.818,30.915,46.861,30.917,46.897,30.9,46.926,30.892,46.983,30.857,47.063,30.828,47.103,30.826,47.146,30.814,47.194,30.8,47.29,30.772,47.357,30.782,47.429,30.812,47.472,30.814,47.5,30.809,47.504,30.797,47.53,30.768,47.59,30.713,47.608,30.689]]]},{"n":"Malheur","z":4.7,"b":[-118.948,43.24,-118.612,43.425],"p":[[[-118.68,43.29,-118.721,43.291,-118.766,43.258,-118.821,43.24,-118.864,43.263,-118.903,43.303,-118.935,43.354,-118.948,43.394,-118.929,43.414,-118.791,43.425,-118.664,43.405,-118.633,43.344,-118.612,43.298,-118.623,43.282,-118.643,43.281,-118.654,43.284,-118.68,43.29]]]},{"n":"","z":4.7,"b":[-56.712,48.182,-56.466,48.363],"p":[[[-56.483,48.3,-56.586,48.232,-56.712,48.182,-56.689,48.219,-56.639,48.266,-56.604,48.3,-56.587,48.321,-56.56,48.352,-56.524,48.363,-56.494,48.346,-56.466,48.329,-56.483,48.3]]]},{"n":"","z":4.7,"b":[-95.918,55.419,-95.6,55.615],"p":[[[-95.6,55.615,-95.635,55.576,-95.683,55.52,-95.714,55.477,-95.769,55.442,-95.861,55.421,-95.918,55.419,-95.913,55.436,-95.906,55.461,-95.896,55.487,-95.817,55.526,-95.795,55.569,-95.699,55.596,-95.6,55.615]]]},{"n":"","z":4.7,"b":[-165.898,65.08,-165.376,65.205],"p":[[[-165.382,65.205,-165.376,65.193,-165.403,65.171,-165.482,65.134,-165.597,65.097,-165.708,65.08,-165.792,65.091,-165.888,65.138,-165.898,65.165,-165.852,65.182,-165.82,65.191,-165.655,65.188,-165.455,65.196,-165.382,65.205]]]},{"n":"","z":4.7,"b":[-163.936,61.055,-163.547,61.231],"p":[[[-163.798,61.144,-163.783,61.129,-163.781,61.106,-163.801,61.076,-163.816,61.055,-163.842,61.055,-163.858,61.077,-163.897,61.115,-163.936,61.145,-163.931,61.169,-163.889,61.196,-163.826,61.217,-163.76,61.219,-163.633,61.226,-163.547,61.231,-163.591,61.211,-163.628,61.198,-163.709,61.174,-163.798,61.144]]]},{"n":"","z":4.7,"b":[-162.768,60.76,-162.502,60.843],"p":[[[-162.508,60.795,-162.502,60.772,-162.527,60.763,-162.573,60.76,-162.666,60.777,-162.75,60.801,-162.768,60.818,-162.755,60.832,-162.714,60.842,-162.655,60.843,-162.573,60.826,-162.508,60.795]]]},{"n":"Tikchik","z":4.7,"b":[-159.058,59.164,-158.863,59.337],"p":[[[-158.863,59.201,-158.875,59.166,-158.913,59.164,-158.941,59.18,-158.951,59.208,-158.959,59.246,-158.998,59.292,-159.058,59.337,-158.945,59.314,-158.878,59.252,-158.865,59.221,-158.863,59.201]]]},{"n":"Tikchik","z":4.7,"b":[-159.132,59.896,-158.223,60.016],"p":[[[-158.223,59.943,-158.257,59.935,-158.381,59.949,-158.535,59.961,-158.705,59.934,-158.826,59.911,-158.971,59.896,-159.024,59.896,-159.092,59.9,-159.108,59.913,-159.12,59.934,-159.132,59.951,-159.1,59.952,-159,59.948,-158.759,59.98,-158.542,60.016,-158.486,60.012,-158.423,59.983,-158.307,59.98,-158.242,59.963,-158.223,59.943]]]},{"n":"Tikchik","z":4.7,"b":[-159.159,60.19,-158.774,60.283],"p":[[[-159.159,60.196,-159.132,60.22,-159.058,60.242,-158.919,60.273,-158.853,60.283,-158.874,60.264,-158.831,60.246,-158.778,60.23,-158.774,60.216,-158.774,60.201,-158.781,60.19,-158.913,60.201,-159.083,60.212,-159.149,60.196,-159.159,60.196]]]},{"n":"Tikchik","z":4.7,"b":[-159.133,59.425,-158.582,59.653],"p":[[[-158.708,59.564,-158.825,59.543,-158.835,59.518,-158.804,59.491,-158.663,59.465,-158.605,59.443,-158.582,59.425,-158.633,59.427,-158.757,59.438,-158.805,59.445,-158.869,59.481,-158.992,59.516,-159.046,59.547,-159.083,59.594,-159.133,59.651,-159.094,59.653,-158.945,59.586,-158.892,59.59,-158.794,59.603,-158.647,59.596,-158.708,59.564]]]},{"n":"Khyargas Nuur","z":4.7,"b":[92.832,48.991,93.799,49.322],"p":[[[93.799,49.129,93.779,49.103,93.678,49.07,93.536,49.034,93.45,49.009,93.397,48.991,93.322,49.037,93.241,49.097,93.179,49.112,93.104,49.139,92.975,49.187,92.865,49.225,92.832,49.253,92.835,49.282,92.864,49.306,92.94,49.315,93.059,49.322,93.26,49.292,93.421,49.226,93.471,49.205,93.516,49.213,93.56,49.206,93.654,49.215,93.723,49.19,93.786,49.158,93.799,49.129]]]},{"n":"","z":4.7,"b":[85.352,30.759,85.889,31.045],"p":[[[85.888,30.929,85.851,30.886,85.845,30.827,85.827,30.783,85.802,30.759,85.783,30.759,85.786,30.789,85.757,30.84,85.692,30.87,85.64,30.869,85.6,30.855,85.568,30.842,85.513,30.846,85.438,30.871,85.377,30.906,85.352,30.944,85.361,30.992,85.402,31.035,85.476,31.045,85.561,31.028,85.647,31.025,85.725,31.028,85.81,31.005,85.875,30.978,85.889,30.952,85.888,30.929]]]},{"n":"Assal","z":4.7,"b":[42.396,11.629,42.447,11.712],"p":[[[42.447,11.677,42.441,11.64,42.417,11.629,42.399,11.645,42.396,11.672,42.399,11.693,42.411,11.712,42.436,11.697,42.447,11.677]]]},{"n":"Rainy","z":4.7,"b":[-93.613,48.351,-92.435,48.934],"p":[[[-92.918,48.433,-92.995,48.451,-93.05,48.48,-93.071,48.504,-93.056,48.513,-92.982,48.491,-92.865,48.462,-92.788,48.455,-92.745,48.477,-92.704,48.505,-92.698,48.517,-92.813,48.54,-92.936,48.565,-92.95,48.576,-92.948,48.586,-93.001,48.593,-93.079,48.591,-93.109,48.581,-93.1,48.564,-93.117,48.553,-93.162,48.558,-93.196,48.579,-93.279,48.607,-93.339,48.6,-93.382,48.616,-93.37,48.638,-93.37,48.657,-93.401,48.681,-93.403,48.705,-93.378,48.71,-93.352,48.717,-93.365,48.748,-93.417,48.785,-93.496,48.813,-93.579,48.826,-93.613,48.834,-93.583,48.843,-93.491,48.839,-93.434,48.846,-93.439,48.869,-93.45,48.887,-93.463,48.903,-93.408,48.915,-93.345,48.929,-93.322,48.934,-93.294,48.885,-93.262,48.825,-93.227,48.805,-93.203,48.791,-93.201,48.767,-93.2,48.746,-93.229,48.718,-93.265,48.686,-93.261,48.674,-93.245,48.667,-93.223,48.67,-93.171,48.701,-93.124,48.728,-93.135,48.706,-93.149,48.675,-93.119,48.668,-93.015,48.683,-92.979,48.688,-92.949,48.681,-92.896,48.683,-92.793,48.695,-92.779,48.678,-92.853,48.646,-92.87,48.635,-92.868,48.625,-92.866,48.615,-92.808,48.604,-92.732,48.588,-92.685,48.591,-92.655,48.6,-92.641,48.587,-92.603,48.565,-92.585,48.534,-92.583,48.51,-92.57,48.5,-92.6,48.485,-92.567,48.468,-92.491,48.456,-92.47,48.44,-92.449,48.424,-92.435,48.391,-92.441,48.357,-92.456,48.351,-92.478,48.366,-92.52,48.399,-92.6,48.424,-92.764,48.43,-92.918,48.433]]]},{"n":"Williston","z":4.7,"b":[-124.836,55.176,-122.222,56.757],"p":[[[-122.502,56.084,-122.603,56.09,-122.7,56.077,-122.819,56.061,-122.925,56.058,-123.012,56.04,-123.139,55.974,-123.429,55.985,-123.716,56.015,-123.782,55.995,-123.79,55.942,-123.774,55.887,-123.76,55.863,-123.742,55.854,-123.715,55.848,-123.687,55.802,-123.657,55.752,-123.614,55.728,-123.536,55.678,-123.449,55.623,-123.387,55.572,-123.303,55.49,-123.232,55.425,-123.177,55.402,-123.169,55.393,-123.169,55.358,-123.173,55.33,-123.169,55.291,-123.159,55.235,-123.147,55.191,-123.144,55.176,-123.172,55.199,-123.237,55.3,-123.325,55.434,-123.415,55.534,-123.506,55.613,-123.638,55.684,-123.809,55.794,-123.883,55.813,-123.921,55.829,-123.996,55.893,-124.028,55.906,-124.04,55.913,-124.049,55.922,-124.043,55.943,-124.027,55.972,-124.033,56,-124.1,56.014,-124.21,56.014,-124.385,56.064,-124.508,56.114,-124.482,56.115,-124.354,56.081,-124.215,56.053,-124.166,56.067,-124.158,56.087,-124.238,56.146,-124.345,56.21,-124.481,56.361,-124.834,56.713,-124.836,56.757,-124.759,56.742,-124.661,56.658,-124.562,56.566,-124.459,56.471,-124.397,56.401,-124.375,56.368,-124.359,56.345,-124.312,56.31,-124.249,56.263,-124.194,56.24,-124.142,56.237,-124.073,56.255,-124.053,56.223,-124.073,56.154,-124.038,56.1,-123.97,56.061,-123.788,56.072,-123.606,56.061,-123.458,56.049,-123.238,56.029,-123.118,56.048,-123.091,56.076,-122.971,56.116,-122.832,56.13,-122.757,56.139,-122.638,56.159,-122.566,56.158,-122.423,56.128,-122.264,56.084,-122.223,56.058,-122.222,56.021,-122.293,56.023,-122.345,56.029,-122.435,56.055,-122.502,56.084]]]},{"n":"Split","z":4.7,"b":[-96.572,56.063,-95.842,56.302],"p":[[[-96.049,56.136,-96.081,56.118,-96.088,56.1,-96.073,56.085,-96.086,56.079,-96.173,56.085,-96.245,56.092,-96.358,56.094,-96.452,56.087,-96.553,56.063,-96.572,56.086,-96.504,56.15,-96.371,56.165,-96.279,56.177,-96.205,56.22,-96.054,56.273,-95.906,56.302,-95.842,56.291,-95.865,56.265,-95.933,56.246,-96.036,56.222,-96.105,56.203,-96.075,56.182,-96.044,56.156,-96.049,56.136]]]},{"n":"Fort Peck","z":4.7,"b":[-108.196,47.39,-106.217,48.027],"p":[[[-106.217,47.585,-106.251,47.55,-106.269,47.548,-106.252,47.6,-106.251,47.644,-106.285,47.697,-106.318,47.755,-106.336,47.785,-106.36,47.813,-106.384,47.848,-106.42,47.876,-106.454,47.879,-106.48,47.858,-106.521,47.828,-106.55,47.785,-106.556,47.75,-106.599,47.753,-106.683,47.739,-106.768,47.724,-106.813,47.716,-106.838,47.694,-106.85,47.649,-106.862,47.631,-106.879,47.649,-106.906,47.672,-106.944,47.667,-106.993,47.644,-107.015,47.645,-107.03,47.666,-107.183,47.685,-107.306,47.685,-107.333,47.685,-107.386,47.654,-107.429,47.635,-107.507,47.624,-107.612,47.623,-107.719,47.563,-107.846,47.468,-107.911,47.39,-107.924,47.407,-107.909,47.471,-107.916,47.509,-107.93,47.542,-107.949,47.558,-107.982,47.569,-108.044,47.58,-108.17,47.584,-108.196,47.602,-108.173,47.613,-108.045,47.614,-107.959,47.601,-107.931,47.588,-107.889,47.538,-107.841,47.526,-107.779,47.57,-107.712,47.613,-107.685,47.646,-107.688,47.671,-107.714,47.688,-107.605,47.676,-107.455,47.662,-107.422,47.68,-107.38,47.716,-107.336,47.728,-107.295,47.722,-107.231,47.724,-107.121,47.725,-106.99,47.716,-106.93,47.725,-106.938,47.746,-106.933,47.764,-106.881,47.778,-106.821,47.783,-106.769,47.788,-106.738,47.806,-106.709,47.822,-106.655,47.841,-106.596,47.86,-106.582,47.879,-106.566,47.907,-106.574,47.936,-106.59,47.958,-106.538,47.983,-106.433,48.027,-106.406,48.021,-106.366,48.015,-106.325,47.985,-106.331,47.95,-106.358,47.925,-106.345,47.879,-106.285,47.837,-106.253,47.815,-106.267,47.793,-106.253,47.769,-106.241,47.739,-106.232,47.691,-106.217,47.628,-106.217,47.585]]]},{"n":"Sakakawea","z":4.7,"b":[-103.772,47.464,-101.14,48.18],"p":[[[-102.147,47.568,-102.237,47.589,-102.333,47.586,-102.409,47.564,-102.429,47.562,-102.42,47.59,-102.361,47.625,-102.299,47.657,-102.308,47.705,-102.336,47.757,-102.425,47.764,-102.561,47.785,-102.616,47.819,-102.653,47.916,-102.622,47.956,-102.614,47.992,-102.657,48.035,-102.761,48.084,-102.901,48.12,-103.059,48.125,-103.186,48.097,-103.238,48.063,-103.339,48.03,-103.459,48.016,-103.52,48.027,-103.551,48.072,-103.583,48.097,-103.665,48.063,-103.75,48.017,-103.772,48.027,-103.759,48.074,-103.721,48.102,-103.666,48.142,-103.575,48.153,-103.524,48.108,-103.482,48.081,-103.45,48.067,-103.353,48.076,-103.211,48.145,-103.118,48.171,-103.03,48.18,-102.916,48.171,-102.79,48.145,-102.675,48.105,-102.598,48.053,-102.557,47.968,-102.579,47.911,-102.577,47.867,-102.575,47.844,-102.527,47.822,-102.462,47.814,-102.408,47.806,-102.384,47.82,-102.4,47.877,-102.415,47.938,-102.375,47.952,-102.311,47.964,-102.273,47.978,-102.258,47.978,-102.265,47.961,-102.279,47.938,-102.287,47.925,-102.294,47.912,-102.276,47.856,-102.253,47.793,-102.24,47.766,-102.187,47.748,-102.169,47.729,-102.212,47.677,-102.16,47.624,-102.093,47.604,-102.021,47.585,-101.884,47.561,-101.718,47.567,-101.625,47.58,-101.601,47.586,-101.589,47.599,-101.453,47.608,-101.276,47.628,-101.203,47.645,-101.163,47.634,-101.14,47.606,-101.184,47.585,-101.247,47.567,-101.328,47.533,-101.4,47.506,-101.59,47.515,-101.679,47.498,-101.772,47.488,-101.883,47.467,-101.917,47.464,-101.951,47.488,-102.032,47.529,-102.096,47.553,-102.147,47.568]]]},{"n":"Teslin","z":4.7,"b":[-133.245,59.604,-132.089,60.449],"p":[[[-132.095,59.607,-132.125,59.604,-132.159,59.626,-132.213,59.688,-132.316,59.835,-132.399,59.922,-132.61,60.05,-132.834,60.135,-132.958,60.201,-133.012,60.251,-133.187,60.382,-133.245,60.449,-133.138,60.427,-132.98,60.297,-132.798,60.192,-132.702,60.153,-132.589,60.123,-132.522,60.098,-132.461,60.055,-132.364,59.986,-132.323,59.951,-132.277,59.902,-132.23,59.849,-132.225,59.838,-132.214,59.816,-132.202,59.77,-132.123,59.694,-132.089,59.644,-132.095,59.607]]]},{"n":"Arrow Lakes","z":4.7,"b":[-118.156,49.336,-117.907,49.802],"p":[[[-117.907,49.342,-117.957,49.336,-118.063,49.412,-118.096,49.45,-118.139,49.521,-118.156,49.601,-118.138,49.703,-118.128,49.752,-118.092,49.802,-118.084,49.774,-118.109,49.583,-118.087,49.515,-118.048,49.457,-117.976,49.406,-117.911,49.375,-117.907,49.342]]]},{"n":"Pend Oreille","z":4.7,"b":[-116.569,47.964,-116.215,48.302],"p":[[[-116.215,48.16,-116.306,48.139,-116.41,48.107,-116.441,48.075,-116.446,48.026,-116.456,47.984,-116.489,47.97,-116.529,47.964,-116.553,47.971,-116.541,47.995,-116.522,48.044,-116.498,48.115,-116.458,48.168,-116.408,48.191,-116.374,48.208,-116.381,48.229,-116.4,48.247,-116.431,48.254,-116.469,48.263,-116.525,48.242,-116.569,48.25,-116.53,48.284,-116.499,48.301,-116.433,48.302,-116.362,48.284,-116.302,48.236,-116.235,48.183,-116.215,48.16]]]},{"n":"Chelan","z":4.7,"b":[-120.628,47.852,-120.011,48.279],"p":[[[-120.011,47.852,-120.05,47.857,-120.118,47.88,-120.172,47.914,-120.205,47.962,-120.333,48.04,-120.53,48.162,-120.625,48.257,-120.628,48.279,-120.535,48.204,-120.32,48.064,-120.163,47.958,-120.069,47.884,-120.011,47.852]]]},{"n":"Black","z":4.7,"b":[-105.868,58.919,-104.948,59.296],"p":[[[-105.394,59.128,-105.554,59.099,-105.66,59.055,-105.733,58.98,-105.785,58.922,-105.835,58.919,-105.868,58.939,-105.855,58.978,-105.806,59.031,-105.55,59.15,-105.215,59.296,-105.119,59.286,-105.012,59.263,-104.948,59.237,-104.975,59.2,-105.012,59.173,-105.216,59.146,-105.303,59.138,-105.394,59.128]]]},{"n":"Louise","z":4.7,"b":[-146.704,62.282,-146.473,62.508],"p":[[[-146.473,62.332,-146.478,62.312,-146.515,62.282,-146.564,62.293,-146.653,62.362,-146.704,62.411,-146.703,62.445,-146.69,62.477,-146.684,62.504,-146.661,62.508,-146.648,62.484,-146.634,62.46,-146.61,62.414,-146.54,62.363,-146.473,62.332]]]},{"n":"Red","z":4.7,"b":[-95.271,47.889,-94.777,48.065],"p":[[[-94.97,48.032,-94.871,48.004,-94.783,47.97,-94.777,47.936,-94.792,47.91,-94.897,47.892,-95.111,47.889,-95.248,47.907,-95.271,47.942,-95.251,47.989,-95.187,48.033,-95.086,48.065,-95.011,48.06,-94.97,48.032]]]},{"n":"Red","z":4.7,"b":[-95.031,48.064,-94.558,48.203],"p":[[[-95.02,48.125,-94.975,48.152,-94.84,48.184,-94.69,48.203,-94.612,48.199,-94.561,48.178,-94.558,48.141,-94.6,48.106,-94.663,48.083,-94.75,48.066,-94.894,48.064,-95.008,48.072,-95.031,48.094,-95.02,48.125]]]},{"n":"Yathkyed","z":4.7,"b":[-98.458,62.422,-97.309,62.979],"p":[[[-97.969,62.548,-97.995,62.566,-98.051,62.607,-98.132,62.664,-98.206,62.69,-98.227,62.67,-98.315,62.618,-98.35,62.616,-98.348,62.665,-98.356,62.708,-98.387,62.73,-98.419,62.762,-98.447,62.804,-98.458,62.829,-98.432,62.828,-98.409,62.816,-98.356,62.811,-98.263,62.812,-98.207,62.81,-98.202,62.829,-98.194,62.851,-98.145,62.855,-98.099,62.842,-98.055,62.849,-97.983,62.864,-97.874,62.867,-97.764,62.864,-97.714,62.891,-97.68,62.947,-97.63,62.979,-97.617,62.967,-97.618,62.924,-97.638,62.891,-97.662,62.864,-97.647,62.828,-97.635,62.781,-97.631,62.716,-97.613,62.663,-97.561,62.638,-97.518,62.603,-97.509,62.568,-97.475,62.534,-97.427,62.502,-97.384,62.485,-97.36,62.484,-97.358,62.496,-97.36,62.507,-97.349,62.508,-97.326,62.501,-97.309,62.485,-97.318,62.461,-97.377,62.432,-97.445,62.422,-97.528,62.453,-97.626,62.492,-97.702,62.507,-97.78,62.509,-97.842,62.5,-97.876,62.488,-97.889,62.47,-97.913,62.459,-97.965,62.474,-98.023,62.504,-98.048,62.527,-98.016,62.537,-97.969,62.548]]]},{"n":"Amadjuak","z":4.7,"b":[-72.053,64.581,-70.378,65.408],"p":[[[-70.647,64.686,-70.637,64.711,-70.694,64.739,-70.804,64.774,-70.917,64.792,-70.999,64.787,-71.052,64.768,-71.097,64.707,-71.225,64.643,-71.373,64.636,-71.446,64.626,-71.499,64.609,-71.563,64.613,-71.657,64.632,-71.797,64.681,-71.899,64.728,-71.919,64.751,-71.898,64.778,-71.928,64.806,-71.982,64.835,-72.001,64.867,-72.019,64.889,-72.036,64.9,-72.053,64.91,-72.039,64.925,-71.995,64.942,-71.956,64.948,-71.959,64.931,-71.963,64.913,-71.938,64.907,-71.851,64.918,-71.819,64.91,-71.872,64.873,-71.873,64.844,-71.818,64.824,-71.748,64.819,-71.708,64.829,-71.725,64.845,-71.75,64.861,-71.584,64.884,-71.512,64.906,-71.504,64.921,-71.534,64.947,-71.638,64.985,-71.624,65.016,-71.583,65.04,-71.555,65.051,-71.546,65.082,-71.57,65.129,-71.642,65.189,-71.691,65.242,-71.676,65.278,-71.665,65.308,-71.717,65.353,-71.765,65.397,-71.737,65.408,-71.645,65.367,-71.542,65.31,-71.483,65.289,-71.429,65.286,-71.357,65.28,-71.3,65.266,-71.283,65.251,-71.27,65.234,-71.231,65.211,-71.093,65.163,-70.937,65.124,-70.909,65.111,-70.875,65.101,-70.771,65.07,-70.695,65.024,-70.667,64.985,-70.648,64.959,-70.636,64.934,-70.633,64.908,-70.532,64.832,-70.414,64.744,-70.384,64.717,-70.378,64.707,-70.429,64.686,-70.48,64.664,-70.477,64.655,-70.473,64.64,-70.467,64.621,-70.419,64.605,-70.385,64.59,-70.416,64.581,-70.509,64.6,-70.627,64.619,-70.721,64.622,-70.729,64.639,-70.682,64.661,-70.669,64.671,-70.647,64.686]]]},{"n":"Amistad","z":4.7,"b":[-101.312,29.458,-100.918,29.648],"p":[[[-100.918,29.502,-100.92,29.495,-100.936,29.486,-100.978,29.48,-101.039,29.46,-101.1,29.458,-101.151,29.467,-101.19,29.492,-101.233,29.503,-101.272,29.498,-101.287,29.506,-101.275,29.526,-101.261,29.562,-101.269,29.578,-101.28,29.585,-101.302,29.584,-101.312,29.603,-101.31,29.622,-101.305,29.634,-101.275,29.648,-101.214,29.603,-101.181,29.579,-101.142,29.558,-101.108,29.528,-101.072,29.51,-101.035,29.526,-101.004,29.548,-100.993,29.535,-100.977,29.557,-100.951,29.61,-100.944,29.606,-100.956,29.565,-100.951,29.529,-100.933,29.507,-100.918,29.502]]]},{"n":"Cabonga","z":4.7,"b":[-76.823,47.056,-76.327,47.691],"p":[[[-76.479,47.138,-76.435,47.122,-76.394,47.122,-76.359,47.119,-76.327,47.102,-76.331,47.083,-76.363,47.068,-76.393,47.056,-76.443,47.064,-76.505,47.092,-76.535,47.109,-76.553,47.121,-76.582,47.178,-76.603,47.241,-76.625,47.269,-76.651,47.278,-76.668,47.266,-76.654,47.222,-76.639,47.185,-76.669,47.2,-76.707,47.242,-76.715,47.311,-76.741,47.355,-76.781,47.357,-76.789,47.385,-76.778,47.416,-76.767,47.449,-76.779,47.476,-76.823,47.498,-76.819,47.517,-76.748,47.558,-76.658,47.611,-76.562,47.667,-76.522,47.691,-76.516,47.673,-76.595,47.624,-76.699,47.57,-76.722,47.54,-76.715,47.524,-76.656,47.552,-76.605,47.572,-76.618,47.543,-76.633,47.51,-76.556,47.498,-76.459,47.476,-76.408,47.446,-76.422,47.4,-76.475,47.331,-76.489,47.284,-76.494,47.25,-76.502,47.222,-76.517,47.201,-76.512,47.171,-76.479,47.138]]]},{"n":"Chapala","z":4.7,"b":[-103.372,20.124,-102.7,20.329],"p":[[[-102.711,20.194,-102.7,20.182,-102.713,20.162,-102.744,20.139,-102.786,20.124,-102.852,20.139,-102.934,20.168,-103.008,20.183,-103.089,20.181,-103.17,20.179,-103.255,20.2,-103.34,20.231,-103.372,20.26,-103.272,20.281,-103.105,20.308,-103.002,20.329,-102.894,20.319,-102.77,20.198,-102.744,20.198,-102.711,20.194]]]},{"n":"Managua","z":4.7,"b":[-86.589,12.172,-86.102,12.48],"p":[[[-86.104,12.312,-86.102,12.271,-86.105,12.208,-86.175,12.185,-86.261,12.172,-86.315,12.177,-86.307,12.206,-86.297,12.242,-86.304,12.265,-86.323,12.28,-86.387,12.274,-86.47,12.274,-86.529,12.303,-86.572,12.343,-86.589,12.38,-86.589,12.405,-86.546,12.41,-86.468,12.428,-86.4,12.464,-86.339,12.48,-86.294,12.474,-86.236,12.383,-86.161,12.315,-86.119,12.329,-86.104,12.312]]]},{"n":"Itaipu","z":4.7,"b":[-54.795,-25.44,-54.183,-24.066],"p":[[[-54.183,-24.708,-54.227,-24.726,-54.292,-24.751,-54.312,-24.783,-54.308,-24.819,-54.257,-24.878,-54.221,-24.92,-54.251,-24.916,-54.314,-24.892,-54.357,-24.892,-54.375,-24.92,-54.393,-24.946,-54.387,-24.982,-54.337,-25.033,-54.318,-25.079,-54.332,-25.115,-54.348,-25.139,-54.377,-25.151,-54.377,-25.18,-54.288,-25.216,-54.201,-25.24,-54.268,-25.261,-54.34,-25.291,-54.383,-25.303,-54.433,-25.309,-54.432,-25.335,-54.402,-25.371,-54.424,-25.389,-54.491,-25.387,-54.527,-25.396,-54.554,-25.419,-54.582,-25.44,-54.611,-25.433,-54.634,-25.417,-54.634,-25.302,-54.608,-25.267,-54.547,-25.235,-54.519,-25.19,-54.527,-25.155,-54.559,-25.138,-54.58,-25.128,-54.576,-25.112,-54.624,-25.101,-54.721,-25.088,-54.754,-25.063,-54.712,-25.043,-54.61,-25.045,-54.528,-25.053,-54.51,-25.016,-54.506,-24.965,-54.526,-24.935,-54.67,-24.905,-54.795,-24.88,-54.77,-24.868,-54.7,-24.873,-54.612,-24.884,-54.531,-24.877,-54.474,-24.848,-54.466,-24.804,-54.524,-24.75,-54.49,-24.712,-54.4,-24.69,-54.386,-24.659,-54.387,-24.62,-54.419,-24.579,-54.42,-24.542,-54.388,-24.509,-54.4,-24.454,-54.382,-24.416,-54.332,-24.404,-54.312,-24.368,-54.313,-24.322,-54.348,-24.264,-54.368,-24.211,-54.357,-24.165,-54.35,-24.122,-54.323,-24.092,-54.267,-24.066,-54.283,-24.144,-54.271,-24.225,-54.252,-24.286,-54.258,-24.468,-54.259,-24.642,-54.216,-24.686,-54.183,-24.708]]]},{"n":"Gatun","z":4.7,"b":[-80.076,9.016,-79.671,9.32],"p":[[[-79.671,9.144,-79.688,9.124,-79.691,9.104,-79.8,9.073,-79.83,9.046,-79.847,9.045,-79.855,9.055,-79.834,9.08,-79.811,9.105,-79.836,9.118,-79.875,9.139,-79.9,9.154,-79.912,9.121,-79.926,9.078,-79.947,9.068,-79.953,9.043,-79.96,9.023,-79.973,9.024,-79.993,9.039,-80.012,9.06,-80.021,9.056,-80.019,9.037,-80.03,9.026,-80.058,9.021,-80.076,9.016,-80.068,9.041,-80.013,9.097,-79.953,9.192,-79.923,9.267,-79.857,9.294,-79.822,9.32,-79.809,9.316,-79.805,9.285,-79.799,9.254,-79.822,9.238,-79.844,9.222,-79.838,9.207,-79.816,9.185,-79.808,9.172,-79.776,9.148,-79.739,9.13,-79.717,9.127,-79.686,9.134,-79.671,9.144]]]},{"n":"Kariba","z":4.7,"b":[27.018,-17.963,28.988,-16.495],"p":[[[28.988,-16.611,28.968,-16.626,28.943,-16.658,28.931,-16.689,28.901,-16.682,28.867,-16.676,28.854,-16.724,28.829,-16.77,28.79,-16.79,28.762,-16.808,28.725,-16.798,28.697,-16.774,28.7,-16.747,28.69,-16.71,28.655,-16.7,28.613,-16.716,28.586,-16.728,28.555,-16.739,28.509,-16.759,28.474,-16.793,28.459,-16.834,28.461,-16.866,28.454,-16.865,28.419,-16.837,28.382,-16.795,28.341,-16.788,28.242,-16.818,28.15,-16.839,28.117,-16.861,28.101,-16.896,28.084,-16.947,28.089,-16.978,28.102,-16.993,28.096,-17.017,28.076,-17.046,28.047,-17.065,27.999,-17.051,27.959,-17.04,27.943,-17.051,27.914,-17.079,27.871,-17.118,27.841,-17.146,27.813,-17.156,27.75,-17.175,27.705,-17.207,27.683,-17.23,27.668,-17.254,27.654,-17.294,27.611,-17.381,27.576,-17.451,27.565,-17.455,27.549,-17.474,27.502,-17.508,27.455,-17.533,27.44,-17.553,27.45,-17.558,27.462,-17.559,27.453,-17.568,27.407,-17.58,27.353,-17.602,27.336,-17.618,27.287,-17.673,27.228,-17.754,27.195,-17.801,27.164,-17.844,27.116,-17.912,27.077,-17.956,27.051,-17.963,27.021,-17.958,27.018,-17.927,27.04,-17.904,27.065,-17.874,27.094,-17.848,27.096,-17.807,27.107,-17.771,27.172,-17.689,27.255,-17.61,27.292,-17.581,27.318,-17.564,27.338,-17.55,27.354,-17.529,27.371,-17.504,27.372,-17.479,27.344,-17.46,27.371,-17.389,27.431,-17.3,27.472,-17.247,27.55,-17.152,27.609,-17.073,27.641,-17.068,27.669,-17.075,27.688,-17.066,27.686,-17.042,27.704,-16.998,27.717,-16.959,27.705,-16.94,27.718,-16.928,27.753,-16.923,27.8,-16.917,27.831,-16.901,27.817,-16.869,27.789,-16.836,27.794,-16.819,27.826,-16.811,27.858,-16.793,27.877,-16.773,27.899,-16.769,27.917,-16.782,27.942,-16.797,27.975,-16.819,28.009,-16.824,28.042,-16.818,28.073,-16.79,28.106,-16.755,28.136,-16.736,28.168,-16.707,28.179,-16.679,28.175,-16.655,28.206,-16.647,28.246,-16.63,28.283,-16.601,28.298,-16.568,28.29,-16.543,28.314,-16.549,28.35,-16.56,28.373,-16.573,28.393,-16.586,28.441,-16.558,28.497,-16.54,28.53,-16.546,28.552,-16.536,28.56,-16.521,28.579,-16.506,28.623,-16.495,28.681,-16.508,28.761,-16.532,28.854,-16.572,28.899,-16.569,28.943,-16.576,28.967,-16.597,28.988,-16.611]]]},{"n":"Kyoga","z":4.7,"b":[32.537,1.223,33.487,1.731],"p":[[[33.487,1.643,33.443,1.641,33.334,1.623,33.227,1.585,33.161,1.542,33.126,1.503,33.14,1.483,33.206,1.477,33.258,1.47,33.251,1.454,33.208,1.43,33.197,1.399,33.243,1.386,33.282,1.402,33.303,1.419,33.317,1.42,33.321,1.398,33.326,1.356,33.305,1.305,33.271,1.253,33.26,1.223,33.23,1.254,33.167,1.289,33.123,1.313,33.019,1.382,32.899,1.43,32.869,1.432,32.864,1.419,32.868,1.405,32.892,1.381,32.893,1.311,32.86,1.332,32.824,1.388,32.786,1.402,32.764,1.394,32.752,1.357,32.742,1.34,32.661,1.376,32.58,1.411,32.554,1.437,32.537,1.497,32.545,1.522,32.611,1.514,32.736,1.545,32.801,1.581,32.803,1.572,32.797,1.556,32.811,1.549,32.862,1.556,32.938,1.563,32.982,1.553,32.999,1.547,33.022,1.56,33.067,1.601,33.101,1.634,33.113,1.624,33.129,1.616,33.163,1.639,33.206,1.66,33.237,1.67,33.269,1.664,33.31,1.656,33.357,1.677,33.414,1.731,33.446,1.728,33.457,1.684,33.471,1.671,33.483,1.653,33.487,1.643]]]},{"n":"Kubenskoe","z":4.7,"b":[39.053,59.48,39.798,59.869],"p":[[[39.267,59.783,39.305,59.774,39.348,59.766,39.454,59.711,39.511,59.707,39.582,59.665,39.707,59.634,39.78,59.638,39.744,59.61,39.721,59.578,39.764,59.546,39.798,59.525,39.734,59.48,39.553,59.546,39.402,59.624,39.194,59.747,39.053,59.839,39.054,59.868,39.093,59.869,39.139,59.842,39.192,59.809,39.23,59.794,39.267,59.783]]]},{"n":"Constance","z":4.7,"b":[9.113,47.506,9.731,47.782],"p":[[[9.731,47.527,9.719,47.515,9.685,47.506,9.554,47.511,9.452,47.514,9.25,47.598,9.183,47.671,9.113,47.772,9.132,47.782,9.231,47.733,9.373,47.677,9.514,47.625,9.644,47.566,9.731,47.527]]]},{"n":"","z":4.7,"b":[22.579,61.202,23.293,61.46],"p":[[[23.241,61.417,23.185,61.407,23.1,61.392,23.046,61.386,22.987,61.369,22.847,61.324,22.702,61.263,22.659,61.219,22.613,61.202,22.579,61.211,22.632,61.234,22.663,61.268,22.718,61.3,22.841,61.341,22.975,61.395,23.053,61.429,23.137,61.446,23.26,61.46,23.293,61.452,23.249,61.433,23.244,61.424,23.241,61.417]]]},{"n":"Dead Sea","z":4.7,"b":[35.377,31.096,35.522,31.232],"p":[[[35.522,31.177,35.517,31.154,35.508,31.125,35.498,31.105,35.459,31.096,35.417,31.104,35.391,31.135,35.377,31.178,35.382,31.216,35.401,31.231,35.446,31.232,35.494,31.205,35.522,31.177]]]},{"n":"Sea of Galilee","z":4.7,"b":[35.534,32.72,35.636,32.9],"p":[[[35.632,32.782,35.616,32.749,35.577,32.72,35.551,32.75,35.534,32.803,35.539,32.856,35.565,32.892,35.599,32.9,35.623,32.878,35.636,32.834,35.632,32.782]]]},{"n":"Argyle","z":4.7,"b":[128.53,-16.788,129.032,-16.076],"p":[[[129.032,-16.143,129.015,-16.161,129.001,-16.19,128.97,-16.228,128.944,-16.271,128.958,-16.302,128.951,-16.319,128.938,-16.349,128.928,-16.37,128.909,-16.394,128.895,-16.424,128.878,-16.436,128.864,-16.428,128.851,-16.422,128.827,-16.422,128.802,-16.45,128.792,-16.508,128.798,-16.554,128.794,-16.593,128.788,-16.637,128.784,-16.694,128.757,-16.778,128.734,-16.788,128.72,-16.759,128.696,-16.717,128.663,-16.681,128.626,-16.66,128.611,-16.645,128.597,-16.633,128.583,-16.615,128.587,-16.602,128.591,-16.572,128.584,-16.542,128.537,-16.536,128.53,-16.508,128.568,-16.444,128.62,-16.314,128.635,-16.25,128.659,-16.236,128.678,-16.223,128.671,-16.195,128.645,-16.159,128.658,-16.144,128.694,-16.137,128.737,-16.134,128.774,-16.087,128.805,-16.076,128.809,-16.087,128.778,-16.126,128.784,-16.149,128.791,-16.171,128.812,-16.171,128.844,-16.158,128.891,-16.139,128.946,-16.117,128.991,-16.117,129.022,-16.128,129.032,-16.143]]]},{"n":"Ngoring","z":4.7,"b":[97.547,34.788,97.887,35.088],"p":[[[97.887,34.838,97.861,34.823,97.801,34.827,97.751,34.828,97.717,34.826,97.695,34.831,97.689,34.812,97.683,34.792,97.648,34.788,97.603,34.798,97.575,34.825,97.547,34.876,97.555,34.902,97.583,34.929,97.625,34.953,97.644,34.978,97.636,35.003,97.651,35.028,97.687,35.053,97.735,35.08,97.763,35.088,97.794,35.033,97.797,34.982,97.801,34.932,97.847,34.882,97.887,34.838]]]},{"n":"Gyaring","z":4.7,"b":[97.062,34.826,97.468,35.023],"p":[[[97.468,34.976,97.46,34.961,97.413,34.916,97.36,34.865,97.288,34.835,97.23,34.826,97.084,34.907,97.072,34.931,97.062,34.979,97.067,34.997,97.085,35,97.118,35.008,97.231,35.023,97.351,35.02,97.419,34.997,97.468,34.976]]]},{"n":"Toktogul","z":4.7,"b":[72.635,41.663,73.3,41.9],"p":[[[73.291,41.759,73.266,41.755,73.202,41.745,73.107,41.757,72.972,41.755,72.839,41.74,72.679,41.663,72.654,41.667,72.635,41.687,72.658,41.701,72.689,41.728,72.709,41.767,72.688,41.822,72.658,41.874,72.685,41.9,72.817,41.898,72.94,41.878,73.011,41.855,73.086,41.828,73.24,41.801,73.3,41.776,73.291,41.759]]]},{"n":"Rakshastal","z":4.7,"b":[81.126,30.608,81.309,30.863],"p":[[[81.309,30.636,81.29,30.617,81.255,30.608,81.212,30.611,81.158,30.634,81.126,30.662,81.15,30.688,81.185,30.721,81.184,30.804,81.199,30.852,81.234,30.863,81.275,30.839,81.294,30.817,81.296,30.789,81.252,30.753,81.279,30.709,81.309,30.662,81.309,30.636]]]},{"n":"Manasarovar","z":4.7,"b":[81.373,30.591,81.584,30.778],"p":[[[81.584,30.7,81.552,30.654,81.494,30.61,81.434,30.591,81.392,30.61,81.373,30.659,81.376,30.758,81.409,30.778,81.46,30.774,81.524,30.757,81.574,30.733,81.584,30.7]]]},{"n":"Volta","z":4.7,"b":[-1.715,6.338,0.32,9.117],"p":[[[0.272,6.675,0.299,6.719,0.317,6.738,0.309,6.698,0.276,6.648,0.23,6.616,0.192,6.607,0.172,6.615,0.16,6.618,0.152,6.611,0.154,6.597,0.151,6.57,0.147,6.538,0.14,6.505,0.133,6.45,0.138,6.366,0.103,6.338,0.081,6.353,0.073,6.404,0.041,6.405,-0.011,6.379,-0.033,6.408,-0.06,6.452,-0.099,6.49,-0.175,6.533,-0.255,6.585,-0.324,6.622,-0.428,6.668,-0.55,6.717,-0.634,6.753,-0.674,6.802,-0.684,6.834,-0.66,6.836,-0.616,6.807,-0.58,6.785,-0.551,6.811,-0.518,6.794,-0.491,6.737,-0.467,6.723,-0.442,6.727,-0.419,6.731,-0.409,6.766,-0.406,6.816,-0.393,6.828,-0.368,6.773,-0.335,6.716,-0.305,6.717,-0.271,6.722,-0.228,6.734,-0.194,6.741,-0.175,6.724,-0.163,6.686,-0.144,6.648,-0.096,6.629,-0.057,6.63,-0.041,6.677,-0.026,6.72,-0.017,6.733,-0.023,6.748,-0.049,6.769,-0.067,6.789,-0.071,6.809,-0.066,6.825,-0.042,6.847,-0.011,6.853,0.012,6.845,0.03,6.85,0.077,6.87,0.112,6.897,0.104,6.91,0.059,6.914,0.042,6.946,0.087,6.974,0.12,6.971,0.123,6.983,0.099,7.004,0.088,7.023,0.093,7.037,0.101,7.053,0.098,7.08,0.086,7.121,0.085,7.147,0.066,7.151,0.045,7.164,0.028,7.176,0.01,7.187,0.007,7.209,0.015,7.211,0.035,7.2,0.063,7.195,0.083,7.194,0.099,7.193,0.118,7.203,0.142,7.222,0.153,7.241,0.16,7.252,0.174,7.252,0.184,7.266,0.184,7.287,0.172,7.304,0.156,7.309,0.135,7.313,0.102,7.343,0.068,7.393,0.05,7.432,0.051,7.449,0.047,7.46,0.046,7.478,-0.002,7.5,-0.063,7.508,-0.058,7.466,-0.066,7.444,-0.107,7.458,-0.128,7.472,-0.136,7.496,-0.157,7.51,-0.207,7.512,-0.242,7.517,-0.232,7.532,-0.208,7.546,-0.169,7.55,-0.134,7.553,-0.105,7.551,-0.071,7.549,-0.027,7.546,0.012,7.543,-0.018,7.592,-0.064,7.641,-0.11,7.645,-0.182,7.647,-0.242,7.639,-0.336,7.618,-0.432,7.594,-0.475,7.589,-0.498,7.597,-0.531,7.576,-0.603,7.554,-0.654,7.551,-0.611,7.567,-0.543,7.6,-0.483,7.625,-0.422,7.649,-0.357,7.664,-0.294,7.667,-0.262,7.684,-0.256,7.706,-0.254,7.72,-0.21,7.714,-0.161,7.721,-0.125,7.74,-0.098,7.746,-0.1,7.755,-0.134,7.767,-0.157,7.779,-0.159,7.794,-0.152,7.813,-0.152,7.841,-0.17,7.89,-0.186,7.927,-0.192,7.955,-0.224,7.977,-0.285,8.015,-0.315,8.04,-0.344,8.038,-0.351,8.002,-0.384,7.98,-0.417,7.972,-0.394,8,-0.379,8.048,-0.415,8.079,-0.446,8.091,-0.498,8.057,-0.527,8.062,-0.526,8.128,-0.543,8.154,-0.561,8.134,-0.591,8.1,-0.624,8.085,-0.643,8.087,-0.643,8.106,-0.693,8.11,-0.756,8.102,-0.761,8.118,-0.748,8.135,-0.721,8.135,-0.655,8.142,-0.604,8.159,-0.6,8.173,-0.621,8.191,-0.676,8.239,-0.724,8.28,-0.754,8.297,-0.795,8.315,-0.801,8.334,-0.784,8.366,-0.838,8.447,-0.912,8.555,-0.965,8.604,-0.999,8.63,-1.041,8.646,-1.087,8.64,-1.128,8.632,-1.141,8.595,-1.153,8.551,-1.18,8.604,-1.219,8.658,-1.254,8.668,-1.304,8.697,-1.351,8.724,-1.405,8.739,-1.443,8.731,-1.488,8.697,-1.545,8.664,-1.605,8.644,-1.664,8.629,-1.715,8.63,-1.663,8.673,-1.591,8.695,-1.519,8.731,-1.466,8.761,-1.412,8.781,-1.376,8.805,-1.355,8.792,-1.294,8.75,-1.269,8.726,-1.126,8.705,-1.062,8.722,-1.067,8.75,-1.096,8.784,-1.134,8.813,-1.145,8.874,-1.174,8.945,-1.179,8.979,-1.178,9.027,-1.174,9.074,-1.152,9.117,-1.13,9.076,-1.127,9.028,-1.125,8.981,-1.121,8.929,-1.106,8.897,-1.075,8.853,-1.045,8.843,-1.026,8.844,-0.997,8.775,-0.931,8.653,-0.873,8.586,-0.843,8.579,-0.817,8.6,-0.818,8.65,-0.823,8.707,-0.809,8.743,-0.791,8.761,-0.782,8.756,-0.795,8.721,-0.806,8.689,-0.77,8.656,-0.722,8.639,-0.705,8.643,-0.707,8.625,-0.757,8.578,-0.805,8.52,-0.797,8.497,-0.782,8.469,-0.721,8.402,-0.66,8.357,-0.627,8.329,-0.594,8.288,-0.566,8.275,-0.554,8.251,-0.527,8.232,-0.485,8.209,-0.449,8.209,-0.394,8.221,-0.355,8.243,-0.318,8.244,-0.286,8.253,-0.245,8.245,-0.202,8.213,-0.193,8.186,-0.257,8.185,-0.321,8.177,-0.33,8.158,-0.308,8.142,-0.263,8.108,-0.19,8.053,-0.128,8.011,-0.103,7.976,-0.082,7.876,-0.052,7.811,-0.024,7.793,0.002,7.793,0.019,7.789,0.055,7.792,0.07,7.821,0.058,7.863,0.056,7.886,0.059,7.92,0.073,7.969,0.079,8.003,0.062,8.041,0.07,8.081,0.099,8.093,0.129,8.086,0.154,8.078,0.17,8.086,0.165,8.115,0.152,8.161,0.123,8.21,0.105,8.242,0.137,8.254,0.169,8.289,0.198,8.338,0.217,8.363,0.161,8.381,0.092,8.403,0.081,8.429,0.093,8.461,0.091,8.5,0.085,8.548,0.106,8.583,0.135,8.598,0.15,8.633,0.16,8.7,0.184,8.742,0.198,8.738,0.185,8.69,0.168,8.625,0.152,8.592,0.128,8.574,0.121,8.518,0.113,8.457,0.103,8.434,0.115,8.416,0.178,8.399,0.242,8.379,0.261,8.355,0.28,8.341,0.294,8.334,0.284,8.327,0.242,8.322,0.214,8.314,0.217,8.296,0.19,8.251,0.184,8.206,0.201,8.166,0.231,8.125,0.268,8.106,0.267,8.094,0.24,8.088,0.198,8.079,0.161,8.06,0.146,8.021,0.121,7.972,0.108,7.939,0.124,7.913,0.146,7.894,0.168,7.863,0.221,7.883,0.258,7.905,0.251,7.876,0.252,7.863,0.248,7.849,0.237,7.834,0.248,7.803,0.278,7.762,0.279,7.699,0.281,7.617,0.282,7.575,0.259,7.581,0.24,7.578,0.228,7.556,0.229,7.536,0.242,7.511,0.25,7.491,0.285,7.515,0.32,7.542,0.313,7.506,0.302,7.47,0.297,7.453,0.295,7.416,0.287,7.382,0.276,7.353,0.277,7.339,0.277,7.33,0.292,7.303,0.294,7.278,0.276,7.258,0.272,7.234,0.279,7.218,0.281,7.201,0.279,7.182,0.276,7.172,0.271,7.138,0.289,7.106,0.305,7.099,0.3,7.071,0.281,7.047,0.273,7.001,0.263,6.948,0.24,6.896,0.237,6.768,0.254,6.673,0.272,6.675],[0.13,6.679,0.181,6.726,0.159,6.767,0.115,6.796,0.062,6.808,0.017,6.779,0.011,6.718,0.049,6.695,0.09,6.659,0.13,6.679]]]},{"n":"South Aral Sea","z":1,"b":[58.221,44.425,59.303,46.002],"p":[[[58.312,45.062,58.372,45.131,58.417,45.291,58.549,45.408,58.574,45.532,58.638,45.59,58.653,45.722,58.712,45.835,58.838,45.834,58.814,45.882,58.849,45.918,59.06,45.895,59.079,45.943,59.179,45.926,59.24,45.994,59.303,46.002,59.244,45.892,59.105,45.897,58.914,45.795,58.869,45.596,58.766,45.563,58.672,45.46,58.629,45.264,58.682,45.13,58.547,45.008,58.588,44.99,58.567,44.915,58.624,44.818,58.563,44.772,58.524,44.569,58.483,44.504,58.364,44.425,58.26,44.431,58.235,44.461,58.237,44.524,58.321,44.603,58.221,44.847,58.312,45.062]]]},{"n":"North Aral Sea","z":1,"b":[59.988,46.086,61.528,46.784],"p":[[[60.783,46.086,60.752,46.118,60.825,46.201,60.722,46.374,60.58,46.293,60.348,46.403,60.133,46.357,59.988,46.438,60.002,46.572,60.287,46.7,60.368,46.617,60.401,46.509,60.41,46.504,60.42,46.508,60.453,46.618,60.459,46.623,60.471,46.622,60.58,46.563,60.741,46.627,60.746,46.639,60.745,46.653,60.526,46.751,60.525,46.777,60.58,46.784,60.811,46.747,60.776,46.581,60.892,46.473,61.134,46.45,61.256,46.493,61.264,46.5,61.265,46.513,61.203,46.577,61.297,46.611,61.315,46.648,61.528,46.677,61.45,46.584,61.37,46.528,61.283,46.448,61.3,46.385,61.217,46.237,60.977,46.17,60.862,46.09,60.783,46.086]]]},{"n":"Barsakelmes","z":5,"b":[59.504,46.083,59.813,46.322],"p":[[[59.729,46.083,59.592,46.209,59.504,46.319,59.731,46.322,59.785,46.284,59.813,46.124,59.729,46.083]]]},{"n":"Balkhash","z":1,"b":[73.437,44.989,79.245,46.818],"p":[[[78.991,46.749,79.019,46.768,79.022,46.795,79.052,46.808,79.084,46.81,79.122,46.799,79.177,46.764,79.222,46.72,79.245,46.68,79.245,46.645,79.236,46.634,79.217,46.61,79.157,46.603,79.095,46.582,79.068,46.526,79.023,46.447,78.971,46.406,78.89,46.37,78.787,46.385,78.711,46.396,78.625,46.38,78.544,46.401,78.465,46.392,78.438,46.377,78.435,46.352,78.462,46.313,78.445,46.297,78.409,46.299,78.367,46.339,78.317,46.35,78.233,46.337,78.161,46.319,78.128,46.319,78.075,46.328,78.018,46.343,77.865,46.429,77.817,46.439,77.781,46.423,77.726,46.407,77.652,46.415,77.563,46.435,77.429,46.5,77.355,46.502,77.282,46.473,77.165,46.489,77.104,46.454,77.004,46.464,76.853,46.492,76.742,46.532,76.464,46.542,76.238,46.555,76.176,46.532,76.033,46.512,75.909,46.536,75.867,46.536,75.715,46.516,75.611,46.507,75.558,46.53,75.515,46.567,75.492,46.596,75.49,46.631,75.508,46.636,75.548,46.634,75.562,46.646,75.56,46.653,75.553,46.673,75.509,46.678,75.463,46.671,75.417,46.641,75.404,46.61,75.432,46.542,75.448,46.498,75.406,46.471,75.35,46.47,75.33,46.513,75.304,46.519,75.278,46.515,75.256,46.474,75.241,46.448,75.212,46.427,75.152,46.438,75.121,46.446,75.093,46.461,75.067,46.451,75.013,46.417,74.911,46.405,74.888,46.377,74.857,46.294,74.815,46.216,74.794,46.13,74.771,46.108,74.599,46.022,74.55,46.006,74.37,46.02,74.278,46.004,74.258,45.977,74.253,45.942,74.303,45.79,74.289,45.765,74.251,45.761,74.243,45.752,74.245,45.74,74.311,45.695,74.313,45.671,74.289,45.651,74.231,45.615,74.101,45.537,74.089,45.509,74.083,45.352,74.087,45.344,74.128,45.337,74.151,45.317,74.156,45.271,74.143,45.103,74.136,45.07,74.101,45.059,74.094,45.04,74.12,45,74.1,44.989,74.056,44.994,74.017,45.013,74.01,45.034,74.016,45.056,73.993,45.112,74.008,45.147,73.999,45.189,73.981,45.204,73.877,45.239,73.84,45.264,73.663,45.436,73.615,45.513,73.465,45.582,73.437,45.609,73.444,45.657,73.494,45.687,73.532,45.734,73.519,45.763,73.451,45.78,73.44,45.806,73.463,45.826,73.488,45.831,73.56,45.816,73.633,45.907,73.715,45.983,73.737,46.013,73.663,46.062,73.654,46.093,73.646,46.15,73.679,46.183,73.752,46.203,73.775,46.206,73.829,46.185,73.898,46.208,73.982,46.186,74.021,46.205,74.027,46.222,73.95,46.288,74.023,46.369,74.094,46.428,74.28,46.47,74.375,46.518,74.43,46.578,74.524,46.623,74.553,46.69,74.625,46.752,74.804,46.781,74.939,46.817,74.974,46.814,75.089,46.814,75.163,46.775,75.201,46.759,75.338,46.73,75.427,46.738,75.826,46.818,75.951,46.795,76.203,46.78,76.298,46.753,76.336,46.729,76.405,46.662,76.45,46.641,76.496,46.636,76.6,46.665,76.668,46.673,76.7,46.667,76.793,46.631,76.859,46.644,77.082,46.625,77.175,46.556,77.226,46.567,77.266,46.611,77.37,46.623,77.602,46.64,77.645,46.656,77.69,46.659,77.86,46.648,78.136,46.595,78.169,46.58,78.297,46.469,78.324,46.482,78.348,46.512,78.359,46.623,78.368,46.643,78.397,46.658,78.433,46.652,78.452,46.618,78.476,46.625,78.504,46.665,78.551,46.707,78.581,46.723,78.63,46.747,78.761,46.724,78.784,46.735,78.787,46.755,78.793,46.78,78.823,46.795,78.868,46.799,78.93,46.788,78.991,46.749]]]},{"n":"Tanganyika","z":1,"b":[29.058,-8.791,31.198,-3.338],"p":[[[30.806,-8.578,30.799,-8.566,30.711,-8.534,30.681,-8.514,30.672,-8.503,30.654,-8.507,30.62,-8.544,30.555,-8.58,30.534,-8.579,30.56,-8.556,30.569,-8.537,30.568,-8.514,30.543,-8.504,30.495,-8.507,30.464,-8.498,30.448,-8.449,30.461,-8.382,30.478,-8.355,30.508,-8.332,30.549,-8.313,30.573,-8.262,30.581,-8.181,30.567,-8.115,30.532,-8.064,30.499,-8.033,30.468,-8.021,30.442,-7.997,30.396,-7.928,30.367,-7.903,30.294,-7.873,30.277,-7.848,30.265,-7.768,30.221,-7.67,30.204,-7.587,30.214,-7.558,30.208,-7.474,30.147,-7.299,30.09,-7.214,30.013,-7.145,29.928,-7.095,29.836,-7.063,29.752,-6.995,29.616,-6.822,29.537,-6.754,29.511,-6.72,29.48,-6.633,29.484,-6.538,29.476,-6.515,29.454,-6.517,29.436,-6.509,29.424,-6.491,29.311,-6.191,29.289,-6.176,29.27,-6.131,29.25,-6.109,29.215,-6.088,29.193,-6.038,29.184,-5.958,29.19,-5.915,29.214,-5.892,29.33,-5.799,29.34,-5.766,29.341,-5.746,29.333,-5.662,29.342,-5.635,29.371,-5.63,29.372,-5.616,29.226,-5.371,29.157,-5.236,29.117,-5.124,29.102,-5.054,29.112,-5.026,29.114,-4.714,29.124,-4.586,29.147,-4.529,29.169,-4.504,29.203,-4.511,29.202,-4.437,29.254,-4.101,29.245,-4.065,29.228,-4.071,29.218,-4.118,29.145,-4.335,29.133,-4.343,29.063,-4.317,29.058,-4.286,29.089,-4.245,29.099,-4.206,29.088,-4.169,29.086,-4.092,29.103,-3.896,29.117,-3.852,29.117,-3.816,29.103,-3.788,29.11,-3.75,29.138,-3.703,29.147,-3.617,29.139,-3.49,29.142,-3.408,29.158,-3.37,29.175,-3.346,29.196,-3.338,29.298,-3.355,29.332,-3.39,29.331,-3.462,29.335,-3.663,29.346,-3.794,29.364,-3.856,29.434,-3.977,29.433,-4.001,29.523,-4.203,29.579,-4.3,29.629,-4.366,29.653,-4.42,29.641,-4.489,29.607,-4.789,29.614,-4.881,29.6,-4.896,29.608,-4.907,29.637,-4.913,29.684,-4.939,29.749,-4.984,29.792,-5.041,29.813,-5.109,29.816,-5.152,29.803,-5.169,29.8,-5.257,29.788,-5.263,29.774,-5.354,29.752,-5.405,29.758,-5.467,29.812,-5.528,29.865,-5.63,29.924,-5.771,29.951,-5.861,29.948,-5.899,29.916,-5.938,29.856,-5.98,29.817,-5.997,29.798,-5.99,29.775,-6.001,29.748,-6.03,29.729,-6.095,29.717,-6.196,29.722,-6.244,29.748,-6.288,29.82,-6.37,29.943,-6.479,29.95,-6.498,29.965,-6.5,30.023,-6.469,30.094,-6.457,30.137,-6.465,30.166,-6.483,30.182,-6.51,30.299,-6.616,30.356,-6.69,30.398,-6.778,30.455,-6.856,30.528,-6.923,30.563,-6.993,30.561,-7.065,30.543,-7.104,30.521,-7.107,30.513,-7.116,30.522,-7.166,30.577,-7.276,30.599,-7.376,30.589,-7.464,30.604,-7.542,30.645,-7.61,30.725,-7.701,30.825,-7.911,30.906,-8.038,30.913,-8.075,30.925,-8.098,30.957,-8.121,30.962,-8.139,30.948,-8.162,30.95,-8.19,30.964,-8.238,31.015,-8.303,31.102,-8.385,31.143,-8.451,31.138,-8.501,31.151,-8.551,31.184,-8.602,31.198,-8.652,31.189,-8.73,31.179,-8.742,31.166,-8.739,31.15,-8.72,31.133,-8.725,31.113,-8.754,31.084,-8.776,31.045,-8.791,31.022,-8.787,30.971,-8.745,30.806,-8.578]]]},{"n":"Lagoa Mirim","z":1.7,"b":[-53.624,-33.557,-52.591,-32.199],"p":[[[-52.598,-32.585,-52.617,-32.638,-52.659,-32.698,-52.694,-32.757,-52.731,-32.812,-52.773,-32.876,-52.808,-32.907,-52.847,-32.917,-52.895,-32.91,-52.939,-32.861,-52.976,-32.822,-53.015,-32.799,-53.068,-32.808,-53.131,-32.846,-53.173,-32.956,-53.228,-33.055,-53.292,-33.081,-53.326,-33.085,-53.352,-33.095,-53.377,-33.12,-53.409,-33.159,-53.437,-33.235,-53.453,-33.351,-53.437,-33.473,-53.454,-33.532,-53.491,-33.547,-53.508,-33.557,-53.527,-33.553,-53.544,-33.539,-53.557,-33.515,-53.567,-33.483,-53.573,-33.433,-53.577,-33.368,-53.586,-33.299,-53.604,-33.242,-53.62,-33.191,-53.624,-33.136,-53.6,-33.079,-53.546,-33.024,-53.473,-32.959,-53.406,-32.954,-53.354,-32.926,-53.323,-32.883,-53.313,-32.834,-53.313,-32.793,-53.302,-32.761,-53.265,-32.733,-53.217,-32.707,-53.178,-32.679,-53.139,-32.649,-53.092,-32.62,-53.03,-32.598,-52.991,-32.534,-52.959,-32.468,-52.877,-32.432,-52.81,-32.391,-52.804,-32.348,-52.792,-32.311,-52.78,-32.263,-52.745,-32.217,-52.72,-32.199,-52.701,-32.204,-52.69,-32.237,-52.69,-32.348,-52.653,-32.394,-52.625,-32.454,-52.593,-32.499,-52.591,-32.54,-52.598,-32.585]]]},{"n":"Manitoba","z":2,"b":[-99.549,50.202,-97.951,51.781],"p":[[[-97.959,50.357,-98.016,50.304,-98.072,50.274,-98.151,50.24,-98.242,50.214,-98.33,50.202,-98.402,50.204,-98.455,50.211,-98.519,50.242,-98.551,50.287,-98.549,50.421,-98.568,50.488,-98.608,50.568,-98.716,50.737,-98.815,50.864,-98.853,50.946,-98.941,51.008,-98.991,51.076,-99.006,51.114,-99.008,51.14,-98.988,51.152,-98.941,51.115,-98.881,51.037,-98.867,51.028,-98.847,51.039,-98.829,51.072,-98.836,51.1,-98.856,51.109,-98.897,51.188,-98.953,51.202,-98.979,51.223,-99.026,51.267,-99.077,51.339,-99.138,51.503,-99.163,51.543,-99.258,51.563,-99.32,51.617,-99.37,51.635,-99.426,51.627,-99.527,51.546,-99.542,51.578,-99.538,51.66,-99.549,51.699,-99.545,51.714,-99.491,51.758,-99.443,51.777,-99.39,51.781,-99.338,51.759,-99.292,51.716,-99.239,51.646,-99.217,51.636,-99.181,51.653,-99.166,51.675,-99.177,51.762,-99.166,51.777,-99.105,51.736,-99.064,51.686,-98.999,51.511,-98.956,51.433,-98.918,51.395,-98.907,51.404,-98.901,51.428,-98.911,51.513,-98.972,51.675,-98.929,51.702,-98.894,51.709,-98.865,51.707,-98.833,51.684,-98.79,51.639,-98.758,51.588,-98.752,51.552,-98.779,51.466,-98.778,51.41,-98.754,51.363,-98.705,51.326,-98.591,51.269,-98.572,51.243,-98.583,51.218,-98.633,51.165,-98.669,51.104,-98.718,51.088,-98.724,51.059,-98.754,51.016,-98.76,50.995,-98.74,50.935,-98.72,50.912,-98.5,50.86,-98.41,50.823,-98.244,50.719,-98.174,50.66,-98.071,50.552,-97.971,50.47,-97.951,50.42,-97.959,50.357]]]},{"n":"Winnipegosis","z":2,"b":[-101.102,51.59,-99.62,53.171],"p":[[[-99.674,51.613,-99.775,51.59,-99.858,51.643,-99.935,51.717,-99.95,51.763,-99.899,51.841,-99.953,51.999,-99.984,52.04,-100,52.044,-100.005,52.016,-99.984,51.812,-99.999,51.744,-100.011,51.731,-100.025,51.727,-100.049,51.756,-100.113,51.891,-100.159,52.116,-100.165,52.254,-100.156,52.306,-100.136,52.351,-100.027,52.464,-99.992,52.567,-100.045,52.649,-100.062,52.763,-100.096,52.796,-100.241,52.865,-100.268,52.872,-100.292,52.859,-100.326,52.833,-100.343,52.807,-100.326,52.794,-100.279,52.782,-100.242,52.64,-100.261,52.621,-100.275,52.623,-100.301,52.651,-100.339,52.672,-100.358,52.713,-100.418,52.773,-100.44,52.924,-100.453,52.953,-100.523,52.967,-100.564,52.996,-100.6,52.998,-100.665,52.98,-100.667,52.959,-100.621,52.886,-100.678,52.778,-100.746,52.752,-100.823,52.753,-100.839,52.767,-100.825,52.799,-100.833,52.813,-100.915,52.814,-100.941,52.845,-100.993,52.88,-100.984,52.931,-100.994,52.987,-101.007,53.008,-101.102,53.081,-101.091,53.1,-101.051,53.124,-100.964,53.147,-100.895,53.098,-100.82,53.091,-100.726,53.098,-100.644,53.115,-100.59,53.138,-100.543,53.17,-100.515,53.171,-100.335,53.107,-100.105,53.054,-99.844,52.975,-99.79,52.945,-99.757,52.913,-99.699,52.915,-99.685,52.91,-99.678,52.9,-99.705,52.812,-99.662,52.61,-99.661,52.539,-99.675,52.473,-99.713,52.407,-99.839,52.243,-99.827,52.152,-99.809,52.095,-99.749,52.028,-99.744,52.005,-99.744,51.973,-99.756,51.939,-99.798,51.876,-99.803,51.83,-99.781,51.761,-99.746,51.752,-99.666,51.854,-99.647,51.842,-99.642,51.72,-99.62,51.651,-99.624,51.629,-99.645,51.617,-99.674,51.613]]]},{"n":"Manicouagan","z":2,"b":[-69.25,50.716,-68.197,51.883],"p":[[[-68.255,51.399,-68.217,51.345,-68.204,51.306,-68.223,51.265,-68.313,51.192,-68.352,51.149,-68.447,51.12,-68.529,51.075,-68.642,50.991,-68.74,50.716,-68.744,50.776,-68.735,50.842,-68.743,50.922,-68.782,51.004,-68.868,51.094,-68.995,51.18,-69.148,51.274,-69.176,51.324,-69.161,51.391,-69.134,51.518,-69.155,51.58,-69.25,51.682,-69.232,51.7,-69.202,51.684,-69.138,51.656,-69.118,51.661,-69.099,51.709,-69.092,51.813,-69.063,51.838,-69.043,51.675,-69.01,51.633,-68.979,51.631,-68.935,51.648,-68.856,51.721,-68.808,51.683,-68.768,51.684,-68.7,51.691,-68.636,51.71,-68.596,51.756,-68.535,51.755,-68.501,51.811,-68.457,51.883,-68.437,51.874,-68.429,51.859,-68.443,51.818,-68.483,51.702,-68.461,51.672,-68.319,51.642,-68.252,51.686,-68.206,51.707,-68.197,51.692,-68.269,51.615,-68.267,51.572,-68.291,51.537,-68.286,51.507,-68.251,51.465,-68.255,51.399],[-68.538,51.393,-68.58,51.457,-68.562,51.48,-68.539,51.473,-68.473,51.402,-68.436,51.376,-68.391,51.368,-68.407,51.442,-68.396,51.503,-68.405,51.532,-68.486,51.591,-68.605,51.631,-68.721,51.632,-68.783,51.62,-68.844,51.6,-68.959,51.536,-69.054,51.472,-69.101,51.41,-69.092,51.368,-69.063,51.332,-69.046,51.302,-69.015,51.28,-68.924,51.242,-68.865,51.198,-68.758,51.165,-68.683,51.156,-68.596,51.168,-68.537,51.2,-68.468,51.214,-68.427,51.232,-68.4,51.256,-68.403,51.285,-68.492,51.349,-68.538,51.393]]]},{"n":"Syvash","z":2,"b":[33.696,45.255,35.439,46.267],"p":[[[35.399,45.255,35.276,45.302,35.089,45.358,35.062,45.379,35.043,45.402,35.04,45.429,35.08,45.499,35.052,45.565,35.024,45.604,35,45.631,34.882,45.687,34.834,45.725,34.762,45.743,34.743,45.786,34.683,45.804,34.676,45.819,34.645,45.837,34.495,45.785,34.495,45.803,34.618,45.915,34.619,45.935,34.608,45.944,34.568,45.952,34.525,45.907,34.497,45.899,34.46,45.917,34.412,45.89,34.39,45.888,34.38,45.901,34.388,45.95,34.382,45.979,34.363,46,34.271,46.027,34.227,46.021,34.19,46.03,34.182,46.016,34.187,45.977,34.187,45.956,34.164,45.944,34.14,45.963,34.133,46.01,34.121,46.026,34.072,46.059,33.998,46.072,33.978,46.052,33.96,46.04,33.924,46.039,33.885,46.06,33.831,46.143,33.815,46.142,33.771,46.133,33.708,46.182,33.696,46.2,33.704,46.215,33.728,46.228,33.778,46.234,33.848,46.231,33.922,46.213,34.03,46.157,34.078,46.142,34.107,46.144,34.103,46.182,34.112,46.193,34.13,46.19,34.147,46.191,34.164,46.203,34.158,46.248,34.159,46.267,34.175,46.266,34.236,46.214,34.27,46.191,34.298,46.181,34.312,46.18,34.329,46.19,34.544,46.161,34.555,46.138,34.548,46.115,34.492,46.113,34.469,46.101,34.429,46.022,34.443,45.999,34.479,45.983,34.523,45.988,34.588,46.021,34.64,46.153,34.667,46.173,34.716,46.165,34.754,46.131,34.755,46.113,34.718,46.09,34.713,46.053,34.74,46.035,34.789,46.024,34.816,45.959,34.84,45.843,34.909,45.793,35.032,45.638,35.182,45.472,35.3,45.364,35.426,45.295,35.439,45.275,35.399,45.255]]]},{"n":"Saimaa","z":2,"b":[26.899,61.068,29.982,63.584],"p":[[[29.518,62.123,29.492,62.089,29.47,62.08,29.492,62.046,29.552,62.003,29.615,61.983,29.638,62.005,29.685,62.004,29.757,61.949,29.775,61.906,29.777,61.889,29.733,61.874,29.727,61.848,29.694,61.834,29.66,61.826,29.61,61.802,29.531,61.778,29.477,61.742,29.426,61.734,29.29,61.649,29.266,61.615,29.115,61.6,29.098,61.578,28.941,61.626,28.73,61.671,28.629,61.642,28.608,61.617,28.76,61.61,28.79,61.59,28.722,61.581,28.68,61.558,28.679,61.524,28.693,61.484,28.646,61.476,28.582,61.473,28.568,61.441,28.464,61.399,28.406,61.37,28.345,61.349,28.36,61.331,28.477,61.332,28.641,61.352,28.663,61.368,28.714,61.352,28.82,61.297,28.867,61.247,28.788,61.204,28.61,61.162,28.359,61.093,28.292,61.08,27.988,61.08,27.947,61.068,27.919,61.08,27.892,61.114,27.882,61.157,27.966,61.188,28.082,61.179,28.172,61.153,28.197,61.163,28.174,61.188,28.072,61.233,27.862,61.252,27.678,61.232,27.625,61.214,27.519,61.23,27.406,61.259,27.432,61.27,27.435,61.296,27.368,61.326,27.303,61.329,27.27,61.324,27.265,61.379,27.301,61.433,27.274,61.468,27.298,61.484,27.296,61.557,27.276,61.653,27.329,61.693,27.327,61.632,27.339,61.597,27.359,61.531,27.459,61.523,27.476,61.539,27.472,61.55,27.471,61.599,27.488,61.618,27.499,61.6,27.515,61.577,27.593,61.581,27.665,61.607,27.678,61.636,27.758,61.656,27.773,61.685,27.705,61.731,27.623,61.764,27.605,61.781,27.626,61.801,27.816,61.728,27.866,61.696,27.918,61.657,27.921,61.627,27.946,61.601,28,61.549,28.054,61.511,28.104,61.522,28.129,61.549,28.147,61.551,28.185,61.544,28.234,61.621,28.248,61.716,28.262,61.744,28.306,61.756,28.339,61.749,28.369,61.751,28.442,61.768,28.539,61.841,28.672,61.826,28.807,61.828,28.84,61.846,28.838,61.863,28.737,61.891,28.697,61.921,28.579,61.993,28.427,62.052,28.403,62.069,28.297,62.097,28.168,62.096,28.086,62.1,28.032,62.108,27.926,62.133,27.899,62.157,27.902,62.244,27.885,62.306,27.877,62.329,27.852,62.354,27.844,62.376,27.8,62.429,27.755,62.429,27.679,62.412,27.597,62.418,27.547,62.412,27.489,62.443,27.459,62.479,27.419,62.496,27.367,62.482,27.297,62.477,27.236,62.507,27.178,62.553,27.159,62.577,27.191,62.572,27.295,62.518,27.339,62.519,27.352,62.544,27.373,62.562,27.398,62.576,27.6,62.494,27.647,62.498,27.67,62.513,27.669,62.528,27.608,62.601,27.619,62.71,27.648,62.735,27.666,62.76,27.65,62.816,27.667,62.874,27.684,62.912,27.652,62.925,27.621,62.916,27.588,62.9,27.564,62.877,27.56,62.851,27.494,62.863,27.415,62.902,27.391,62.941,27.33,63.001,27.176,63.084,27.195,63.099,27.207,63.122,27.193,63.169,27.197,63.229,27.193,63.289,27.213,63.309,27.246,63.308,27.239,63.332,27.19,63.388,27.175,63.438,27.135,63.496,27.068,63.523,27.038,63.517,27.02,63.501,26.959,63.524,26.899,63.566,26.899,63.584,27.113,63.546,27.183,63.518,27.234,63.457,27.234,63.427,27.211,63.417,27.25,63.376,27.297,63.356,27.338,63.346,27.363,63.316,27.347,63.279,27.337,63.213,27.264,63.146,27.358,63.068,27.44,63.023,27.559,63.001,27.605,63.015,27.677,62.979,27.723,62.954,27.77,62.946,27.774,62.996,27.736,63.059,27.767,63.1,27.81,63.092,27.848,63.071,27.851,63.032,27.888,63.019,28.006,63.04,28.106,63.009,28.279,63.031,28.352,63.04,28.35,63.026,28.308,63.003,28.262,62.984,28.192,62.964,28.154,62.933,28.187,62.857,28.144,62.825,28.149,62.804,28.181,62.772,28.211,62.73,28.239,62.706,28.268,62.689,28.264,62.663,28.653,62.527,28.799,62.456,28.898,62.39,28.81,62.382,28.659,62.42,28.529,62.474,28.496,62.501,28.565,62.499,28.555,62.523,28.482,62.555,28.424,62.575,28.367,62.587,28.34,62.577,28.274,62.565,28.211,62.508,28.191,62.448,28.169,62.434,28.103,62.442,28.106,62.476,28.138,62.538,28.057,62.69,27.979,62.732,27.849,62.719,27.766,62.68,27.755,62.652,27.801,62.627,27.758,62.583,27.846,62.524,27.915,62.43,28.04,62.409,28.042,62.379,27.991,62.337,27.963,62.298,27.97,62.259,28.067,62.244,28.144,62.252,28.276,62.256,28.277,62.276,28.324,62.287,28.358,62.27,28.406,62.237,28.506,62.22,28.566,62.222,28.608,62.279,28.63,62.294,28.67,62.306,28.699,62.328,28.668,62.368,28.666,62.387,28.693,62.383,28.765,62.348,28.835,62.267,28.87,62.235,28.852,62.212,28.837,62.185,28.786,62.177,28.826,62.134,28.976,62.142,29.139,62.169,29.198,62.177,29.33,62.166,29.345,62.186,29.353,62.219,29.319,62.242,29.298,62.275,29.298,62.313,29.278,62.349,29.265,62.394,29.297,62.426,29.3,62.476,29.261,62.551,29.279,62.599,29.335,62.6,29.352,62.571,29.364,62.53,29.422,62.523,29.509,62.467,29.626,62.419,29.65,62.427,29.612,62.462,29.589,62.5,29.593,62.527,29.616,62.559,29.633,62.597,29.697,62.609,29.776,62.577,29.825,62.518,29.885,62.463,29.926,62.434,29.835,62.389,29.802,62.362,29.793,62.335,29.768,62.313,29.688,62.316,29.641,62.31,29.62,62.294,29.599,62.27,29.648,62.223,29.727,62.199,29.791,62.2,29.874,62.185,29.96,62.164,29.982,62.133,29.911,62.115,29.708,62.151,29.688,62.138,29.607,62.145,29.518,62.123],[27.849,62.951,27.842,62.911,27.886,62.834,28.011,62.775,28.072,62.746,28.109,62.755,28.109,62.788,28.099,62.852,28.075,62.935,28.062,62.976,27.991,62.978,27.986,62.977,27.903,62.973,27.849,62.951],[27.637,61.273,27.677,61.31,27.659,61.355,27.629,61.383,27.556,61.397,27.509,61.409,27.418,61.437,27.36,61.422,27.328,61.4,27.368,61.369,27.475,61.315,27.588,61.266,27.637,61.273],[27.959,61.48,27.969,61.503,27.946,61.527,27.923,61.526,27.859,61.519,27.794,61.538,27.732,61.547,27.712,61.534,27.7,61.488,27.666,61.483,27.59,61.486,27.548,61.491,27.52,61.485,27.564,61.456,27.662,61.435,27.712,61.403,27.75,61.401,27.798,61.405,27.914,61.442,27.959,61.48],[28.496,61.478,28.445,61.516,28.459,61.558,28.493,61.602,28.536,61.622,28.515,61.699,28.476,61.723,28.388,61.712,28.328,61.73,28.277,61.71,28.281,61.665,28.286,61.623,28.308,61.582,28.243,61.539,28.24,61.523,28.255,61.497,28.245,61.473,28.226,61.443,28.24,61.394,28.307,61.408,28.416,61.416,28.495,61.444,28.496,61.478],[29.477,61.967,29.442,62.038,29.363,62.105,29.322,62.13,29.232,62.126,29.187,62.122,29.102,62.118,29.019,62.125,28.939,62.105,28.809,62.082,28.703,62.082,28.691,62.036,28.714,61.995,28.828,61.963,28.944,61.873,28.99,61.802,29.137,61.775,29.255,61.807,29.309,61.87,29.327,61.944,29.342,61.971,29.403,61.975,29.445,61.948,29.474,61.951,29.477,61.967]]]},{"n":"Zaysan","z":3,"b":[83.027,47.699,84.516,49.726],"p":[[[84.516,47.93,84.427,47.857,84.394,47.816,84.357,47.732,84.296,47.699,84.242,47.706,83.994,47.843,83.766,47.951,83.625,48.013,83.445,48.049,83.259,48.095,83.083,48.178,83.027,48.212,83.039,48.25,83.182,48.249,83.275,48.261,83.435,48.27,83.498,48.305,83.533,48.375,83.461,48.432,83.392,48.5,83.387,48.554,83.417,48.597,83.423,48.743,83.471,48.858,83.589,48.962,83.747,49.039,83.872,49.063,84.036,49.111,84.121,49.159,84.1,49.238,83.972,49.329,83.849,49.417,83.724,49.44,83.66,49.443,83.608,49.486,83.539,49.532,83.479,49.538,83.426,49.573,83.365,49.626,83.364,49.644,83.391,49.652,83.608,49.606,83.74,49.631,83.89,49.72,83.964,49.726,83.961,49.703,83.818,49.634,83.797,49.597,83.757,49.564,83.729,49.535,83.823,49.484,84.288,49.238,84.361,49.161,84.329,49.125,84.149,49.086,83.864,48.994,83.777,48.975,83.609,48.91,83.514,48.813,83.487,48.718,83.517,48.617,83.613,48.437,83.774,48.41,83.919,48.397,83.907,48.374,83.682,48.353,83.596,48.303,83.581,48.267,83.634,48.242,83.74,48.198,83.881,48.152,84.051,48.083,84.167,48.056,84.334,48.023,84.468,47.969,84.516,47.93]]]},{"n":"Churchill","z":3,"b":[-109.158,55.552,-108.074,56.813],"p":[[[-108.561,56.51,-108.634,56.488,-108.692,56.49,-108.715,56.521,-108.721,56.611,-108.736,56.668,-108.787,56.69,-108.859,56.709,-108.87,56.748,-108.837,56.784,-108.765,56.813,-108.703,56.792,-108.634,56.715,-108.571,56.696,-108.53,56.704,-108.495,56.693,-108.425,56.629,-108.412,56.464,-108.324,56.441,-108.218,56.435,-108.134,56.426,-108.074,56.382,-108.111,56.308,-108.189,56.271,-108.191,56.253,-108.174,56.234,-108.179,56.143,-108.122,56.063,-108.135,56.038,-108.178,56.015,-108.219,55.966,-108.247,55.915,-108.3,55.858,-108.486,55.765,-108.572,55.698,-108.605,55.648,-108.598,55.585,-108.627,55.552,-108.665,55.569,-108.687,55.628,-108.678,55.695,-108.688,55.749,-108.716,55.842,-108.759,55.903,-108.918,55.933,-109.034,55.93,-109.1,55.981,-109.158,56.068,-109.137,56.108,-109.086,56.122,-108.875,56.08,-108.671,55.979,-108.618,55.887,-108.6,55.848,-108.52,55.879,-108.467,55.936,-108.467,56.033,-108.433,56.104,-108.352,56.156,-108.273,56.191,-108.268,56.244,-108.315,56.29,-108.345,56.284,-108.376,56.285,-108.489,56.329,-108.539,56.366,-108.534,56.428,-108.53,56.479,-108.561,56.51]]]},{"n":"General Carrera/Buenos Aires","z":3,"b":[-72.904,-47.011,-71.166,-46.297],"p":[[[-71.166,-46.527,-71.207,-46.563,-71.376,-46.575,-71.628,-46.538,-71.782,-46.499,-71.998,-46.521,-72.258,-46.586,-72.554,-46.744,-72.774,-46.873,-72.815,-46.909,-72.826,-46.951,-72.86,-46.997,-72.904,-47.011,-72.87,-46.911,-72.789,-46.785,-72.707,-46.727,-72.661,-46.664,-72.695,-46.592,-72.724,-46.512,-72.692,-46.499,-72.645,-46.564,-72.585,-46.605,-72.478,-46.596,-72.237,-46.513,-71.988,-46.437,-71.872,-46.382,-71.88,-46.341,-71.872,-46.324,-71.847,-46.316,-71.755,-46.33,-71.626,-46.297,-71.605,-46.313,-71.578,-46.361,-71.442,-46.408,-71.313,-46.436,-71.221,-46.482,-71.166,-46.527]]]},{"n":"Päijänne","z":3,"b":[25.188,60.961,27.092,63.216],"p":[[[26.95,62.852,26.986,62.858,27.092,62.842,27.084,62.833,27.018,62.832,27.018,62.8,26.957,62.794,26.88,62.796,26.916,62.744,26.983,62.686,26.952,62.652,26.999,62.613,26.984,62.603,26.896,62.624,26.853,62.606,26.819,62.564,26.825,62.526,26.851,62.497,26.886,62.495,26.93,62.482,26.909,62.456,26.778,62.479,26.736,62.494,26.724,62.553,26.69,62.596,26.662,62.583,26.665,62.551,26.629,62.479,26.58,62.412,26.5,62.391,26.426,62.366,26.432,62.329,26.42,62.318,26.379,62.346,26.384,62.381,26.407,62.418,26.465,62.427,26.508,62.424,26.51,62.453,26.558,62.49,26.526,62.539,26.383,62.567,26.273,62.551,26.239,62.522,26.238,62.481,26.254,62.437,26.288,62.407,26.312,62.368,26.277,62.34,26.173,62.358,26.128,62.329,26.128,62.28,26.089,62.295,26.05,62.348,25.992,62.373,25.956,62.356,25.979,62.311,26.002,62.277,25.974,62.254,25.946,62.21,25.951,62.145,25.923,62.149,25.898,62.2,25.876,62.206,25.826,62.148,25.821,62.072,25.878,62.021,25.858,62.001,25.79,61.992,25.774,61.967,25.703,61.964,25.641,61.95,25.632,61.916,25.651,61.903,25.701,61.885,25.682,61.869,25.615,61.842,25.588,61.793,25.622,61.774,25.683,61.802,25.721,61.813,25.723,61.778,25.68,61.712,25.682,61.638,25.706,61.601,25.63,61.602,25.586,61.591,25.583,61.541,25.573,61.494,25.641,61.467,25.607,61.442,25.543,61.423,25.557,61.393,25.547,61.335,25.58,61.308,25.759,61.278,25.977,61.255,26.013,61.235,26.027,61.212,26.082,61.196,26.118,61.211,26.106,61.246,26.163,61.31,26.275,61.374,26.278,61.36,26.238,61.317,26.255,61.284,26.257,61.247,26.234,61.242,26.212,61.263,26.173,61.271,26.148,61.256,26.175,61.218,26.197,61.175,26.184,61.148,26.241,61.102,26.216,61.081,26.178,61.059,26.175,61.041,26.15,61.003,26.095,60.964,26.025,60.961,26.071,61.004,26.102,61.101,26.026,61.17,25.961,61.19,25.913,61.184,25.871,61.168,25.788,61.215,25.67,61.273,25.621,61.263,25.645,61.235,25.657,61.206,25.583,61.166,25.531,61.115,25.564,61.08,25.603,61.055,25.592,61.024,25.549,61.027,25.518,61.062,25.48,61.088,25.454,61.073,25.405,61.058,25.356,61.076,25.397,61.116,25.459,61.156,25.5,61.229,25.465,61.28,25.442,61.273,25.431,61.25,25.406,61.251,25.394,61.284,25.356,61.315,25.303,61.334,25.271,61.352,25.255,61.366,25.263,61.388,25.194,61.475,25.188,61.532,25.233,61.546,25.27,61.568,25.338,61.58,25.398,61.584,25.418,61.626,25.403,61.67,25.358,61.697,25.228,61.759,25.209,61.794,25.237,61.796,25.306,61.777,25.389,61.794,25.444,61.844,25.393,61.872,25.314,61.878,25.317,61.902,25.423,61.921,25.51,61.931,25.506,61.95,25.543,61.989,25.625,62.005,25.671,62.059,25.586,62.101,25.498,62.111,25.472,62.142,25.55,62.143,25.668,62.136,25.713,62.162,25.716,62.194,25.735,62.219,25.761,62.235,25.824,62.252,25.881,62.292,25.907,62.33,25.922,62.385,25.897,62.441,25.865,62.443,25.841,62.433,25.825,62.448,25.815,62.491,25.763,62.534,25.69,62.543,25.644,62.589,25.632,62.627,25.781,62.652,25.795,62.705,25.827,62.725,25.894,62.72,25.888,62.756,25.831,62.783,25.818,62.794,25.857,62.825,25.866,62.875,25.934,62.913,25.87,62.987,25.832,63.014,25.737,63.046,25.617,63.071,25.541,63.03,25.522,62.966,25.495,62.957,25.436,62.975,25.368,63.007,25.384,63.028,25.444,63.052,25.449,63.098,25.44,63.128,25.505,63.145,25.589,63.211,25.623,63.216,25.621,63.182,25.681,63.137,25.792,63.092,25.822,63.103,25.837,63.13,25.903,63.111,25.932,63.082,25.991,63.043,26.084,62.993,26.079,62.981,26.015,62.991,26.014,62.972,26.077,62.93,26.22,62.854,26.364,62.768,26.371,62.725,26.302,62.729,26.203,62.781,26.103,62.826,26.067,62.819,26.038,62.796,26.013,62.752,26.007,62.71,25.98,62.706,26.02,62.665,26.029,62.632,25.913,62.648,25.821,62.613,25.834,62.535,25.938,62.462,26.024,62.477,26.056,62.521,26.096,62.519,26.103,62.488,26.069,62.454,26.084,62.421,26.16,62.388,26.185,62.46,26.178,62.55,26.217,62.572,26.342,62.608,26.359,62.633,26.451,62.633,26.469,62.704,26.387,62.789,26.358,62.826,26.359,62.854,26.447,62.797,26.502,62.75,26.563,62.706,26.69,62.637,26.785,62.609,26.813,62.623,26.827,62.659,26.822,62.697,26.75,62.742,26.689,62.787,26.671,62.817,26.595,62.872,26.568,62.9,26.59,62.901,26.68,62.846,26.73,62.836,26.743,62.866,26.713,62.929,26.729,62.957,26.762,62.959,26.765,62.98,26.917,63.007,26.996,63.031,27.033,63.019,26.978,62.998,26.931,62.941,26.948,62.916,26.93,62.913,26.894,62.917,26.889,62.895,26.927,62.859,26.95,62.852]]]},{"n":"Srednee Kuyto","z":3,"b":[30.362,64.864,31.955,65.183],"p":[[[30.362,65.149,30.397,65.159,30.467,65.124,30.646,65.074,30.804,65.105,30.903,65.122,30.93,65.157,30.974,65.183,31.346,65.181,31.576,65.137,31.616,65.112,31.628,65.065,31.661,65.032,31.829,64.96,31.942,64.897,31.955,64.864,31.905,64.88,31.752,64.935,31.664,64.945,31.545,65.003,31.51,65.04,31.51,65.067,31.482,65.096,31.373,65.116,31.246,65.117,31.191,65.102,31.181,65.078,31.129,65.078,31.019,65.092,30.986,65.082,30.906,65.074,30.839,65.043,30.814,64.982,30.743,64.944,30.661,64.94,30.54,64.931,30.556,64.95,30.606,64.977,30.721,64.995,30.709,65.021,30.533,65.063,30.404,65.112,30.362,65.149]]]},{"n":"Kakhovka","z":4,"b":[33.54,46.822,35.302,47.842],"p":[[[35.302,47.492,35.269,47.45,35.213,47.45,35.049,47.428,34.961,47.406,34.808,47.463,34.592,47.52,34.419,47.487,34.19,47.42,34.177,47.389,34.183,47.346,34.173,47.325,34.097,47.316,33.904,47.159,33.718,46.941,33.7,46.899,33.687,46.861,33.559,46.822,33.54,46.831,33.576,46.883,33.63,47.034,33.655,47.05,33.667,47.042,33.705,47.061,33.761,47.119,33.777,47.163,33.835,47.212,33.933,47.282,33.963,47.331,33.965,47.359,33.947,47.451,33.936,47.541,33.984,47.556,34.067,47.558,34.148,47.542,34.211,47.536,34.24,47.575,34.266,47.59,34.316,47.567,34.38,47.558,34.436,47.569,34.487,47.593,34.731,47.578,34.997,47.588,35.088,47.676,35.086,47.824,35.106,47.842,35.13,47.84,35.242,47.647,35.302,47.492]]]},{"n":"","z":4,"b":[-105.063,21.796,-105.042,21.809],"p":[[[-105.042,21.801,-105.042,21.796,-105.063,21.809,-105.042,21.801]]]},{"n":"Kuybyshev","z":4,"b":[48.378,53.27,51.937,55.833],"p":[[[48.387,54.168,48.378,54.231,48.399,54.337,48.405,54.452,48.416,54.597,48.551,54.652,48.745,54.675,48.808,54.733,48.832,54.804,48.864,54.871,48.859,55.012,48.977,55.074,49.159,55.163,49.172,55.293,49.104,55.353,49.048,55.443,49.059,55.483,49.12,55.476,49.189,55.381,49.233,55.338,49.303,55.323,49.358,55.339,49.328,55.379,49.332,55.452,49.329,55.518,49.367,55.544,49.368,55.524,49.371,55.465,49.455,55.404,49.583,55.39,49.788,55.383,50,55.415,50.09,55.463,50.225,55.438,50.379,55.431,50.481,55.46,50.675,55.451,50.834,55.48,51.126,55.501,51.255,55.526,51.331,55.499,51.353,55.496,51.414,55.494,51.469,55.511,51.467,55.59,51.398,55.685,51.414,55.774,51.448,55.812,51.48,55.833,51.472,55.795,51.461,55.701,51.521,55.632,51.635,55.646,51.763,55.72,51.875,55.704,51.937,55.699,51.886,55.671,51.8,55.684,51.748,55.675,51.728,55.643,51.714,55.593,51.615,55.565,51.55,55.558,51.524,55.503,51.41,55.441,51.206,55.489,51.174,55.463,51.198,55.441,51.219,55.4,51.184,55.357,51.13,55.393,51.071,55.415,50.877,55.452,50.764,55.429,50.734,55.407,49.892,55.294,49.811,55.244,49.692,55.191,49.523,55.134,49.461,55.114,49.452,55.074,49.446,55.052,49.396,55.016,49.32,55.014,49.224,55.023,49.136,54.998,49.008,54.97,48.94,54.902,48.969,54.828,49.03,54.809,49.098,54.8,49.081,54.776,48.986,54.728,48.976,54.667,48.973,54.641,48.943,54.617,48.896,54.582,48.909,54.55,48.895,54.528,48.863,54.501,48.864,54.462,48.787,54.447,48.631,54.435,48.53,54.394,48.499,54.334,48.549,54.241,48.659,54.171,48.756,54.15,48.798,54.131,48.809,54.099,48.848,54.045,49.02,54.015,49.275,54.071,49.395,54.12,49.563,54.179,49.727,54.225,49.742,54.191,49.666,54.159,49.537,54.127,49.441,54.038,49.309,53.973,49.218,53.934,49.219,53.878,49.27,53.818,49.32,53.791,49.351,53.77,49.372,53.73,49.369,53.695,49.345,53.698,49.31,53.722,49.279,53.728,49.19,53.807,49.127,53.825,49.091,53.794,49.072,53.713,49.063,53.607,49.087,53.534,49.247,53.502,49.561,53.499,49.882,53.486,49.905,53.457,49.852,53.426,49.495,53.436,49.316,53.402,49.258,53.352,49.23,53.284,49.094,53.27,48.801,53.281,48.759,53.324,48.925,53.309,49.137,53.309,49.206,53.338,49.205,53.368,49.106,53.405,48.992,53.44,49.009,53.46,48.956,53.535,48.865,53.648,48.877,53.754,48.903,53.828,48.883,53.866,48.813,53.939,48.661,54.038,48.567,54.098,48.468,54.124,48.387,54.168]]]},{"n":"Mead","z":4,"b":[-114.816,35.831,-113.633,36.519],"p":[[[-113.636,35.831,-113.67,35.843,-113.726,35.922,-113.795,36.01,-113.895,36.079,-113.952,36.13,-113.989,36.165,-114.039,36.163,-114.11,36.032,-114.208,36.006,-114.294,36.03,-114.321,36.076,-114.373,36.1,-114.406,36.091,-114.451,36.079,-114.504,36.106,-114.64,36.106,-114.714,36.024,-114.741,36.014,-114.769,36.02,-114.812,36.075,-114.816,36.121,-114.653,36.154,-114.512,36.194,-114.432,36.237,-114.413,36.348,-114.387,36.483,-114.364,36.515,-114.336,36.519,-114.335,36.444,-114.359,36.335,-114.37,36.209,-114.295,36.11,-114.226,36.05,-114.159,36.077,-114.097,36.152,-114.03,36.245,-113.996,36.244,-113.924,36.176,-113.858,36.116,-113.777,36.048,-113.699,35.958,-113.633,35.859,-113.636,35.831]]]},{"n":"Powell","z":4,"b":[-111.53,36.919,-110.366,37.906],"p":[[[-110.368,37.898,-110.366,37.871,-110.406,37.81,-110.433,37.711,-110.561,37.582,-110.673,37.45,-110.697,37.393,-110.776,37.31,-110.836,37.225,-110.792,37.193,-110.65,37.208,-110.676,37.175,-110.792,37.17,-110.996,37.114,-111.112,37.076,-111.189,37.023,-111.307,37.016,-111.382,36.977,-111.455,36.919,-111.473,36.932,-111.53,37.029,-111.439,37.059,-111.386,37.055,-111.339,37.062,-111.304,37.099,-111.256,37.146,-111.211,37.119,-111.183,37.136,-111.163,37.165,-111.073,37.139,-110.963,37.151,-110.887,37.264,-110.858,37.335,-110.799,37.352,-110.736,37.431,-110.755,37.526,-110.733,37.535,-110.681,37.531,-110.596,37.592,-110.555,37.631,-110.515,37.673,-110.447,37.769,-110.435,37.871,-110.404,37.906,-110.368,37.898]]]},{"n":"Southern Indian","z":4,"b":[-99.686,56.641,-97.995,57.654],"p":[[[-98.295,57.493,-98.201,57.405,-98.098,57.392,-98.024,57.399,-97.995,57.387,-98.028,57.362,-98.109,57.317,-98.19,57.215,-98.226,57.136,-98.25,57.112,-98.392,57.094,-98.561,57.061,-98.685,56.979,-98.779,56.887,-98.855,56.841,-98.939,56.841,-99.002,56.862,-99.068,56.846,-99.218,56.736,-99.342,56.733,-99.47,56.733,-99.553,56.711,-99.544,56.683,-99.549,56.654,-99.571,56.641,-99.643,56.681,-99.686,56.73,-99.663,56.753,-99.546,56.776,-99.407,56.903,-99.378,56.96,-99.329,56.988,-99.226,56.951,-99.155,56.956,-99.124,57.005,-99.069,57.049,-99.015,57.085,-99.011,57.05,-99.037,56.975,-99.006,56.927,-98.905,56.925,-98.873,56.946,-98.88,57.004,-98.857,57.06,-98.775,57.073,-98.695,57.117,-98.661,57.18,-98.651,57.219,-98.668,57.262,-98.632,57.276,-98.542,57.275,-98.508,57.299,-98.518,57.35,-98.537,57.456,-98.593,57.529,-98.596,57.585,-98.556,57.611,-98.54,57.596,-98.544,57.566,-98.528,57.543,-98.472,57.549,-98.397,57.589,-98.293,57.638,-98.206,57.654,-98.208,57.625,-98.224,57.58,-98.271,57.53,-98.295,57.493]]]},{"n":"Sobradinho","z":4,"b":[-43.16,-11.257,-40.818,-9.004],"p":[[[-40.92,-9.257,-40.818,-9.406,-40.818,-9.438,-40.84,-9.455,-41.097,-9.5,-41.138,-9.577,-41.147,-9.664,-41.188,-9.702,-41.266,-9.71,-41.35,-9.736,-41.464,-9.794,-41.541,-9.801,-41.561,-9.789,-41.649,-9.819,-41.818,-9.82,-41.866,-9.77,-41.935,-9.787,-41.999,-9.833,-42.075,-9.888,-42.171,-10.024,-42.167,-10.081,-42.071,-10.115,-41.986,-10.152,-41.916,-10.237,-41.874,-10.3,-41.917,-10.29,-41.938,-10.253,-41.995,-10.196,-42.119,-10.151,-42.201,-10.145,-42.243,-10.114,-42.278,-10.107,-42.335,-10.098,-42.363,-10.192,-42.252,-10.462,-42.157,-10.668,-42.196,-10.746,-42.24,-10.896,-42.269,-10.931,-42.3,-10.949,-42.336,-11.001,-42.351,-10.999,-42.295,-10.84,-42.237,-10.66,-42.259,-10.548,-42.317,-10.495,-42.37,-10.427,-42.415,-10.415,-42.482,-10.467,-42.538,-10.55,-42.611,-10.675,-42.725,-10.842,-42.734,-10.875,-42.74,-11.044,-42.764,-11.142,-42.812,-11.187,-42.825,-11.251,-42.863,-11.249,-42.902,-11.257,-42.934,-11.244,-42.978,-11.174,-43.017,-11.138,-43.072,-11.109,-43.15,-11.143,-43.16,-11.115,-43.107,-10.996,-43.087,-10.94,-43.051,-10.916,-42.968,-10.832,-42.893,-10.774,-42.924,-10.713,-42.947,-10.69,-42.994,-10.664,-42.991,-10.623,-42.955,-10.594,-42.904,-10.651,-42.839,-10.741,-42.78,-10.744,-42.745,-10.706,-42.748,-10.615,-42.79,-10.561,-42.851,-10.498,-42.848,-10.462,-42.816,-10.462,-42.733,-10.517,-42.668,-10.538,-42.626,-10.509,-42.614,-10.48,-42.679,-10.398,-42.685,-10.354,-42.673,-10.288,-42.642,-10.301,-42.558,-10.352,-42.497,-10.331,-42.494,-10.267,-42.53,-10.212,-42.596,-10.093,-42.791,-9.998,-42.816,-9.962,-42.791,-9.945,-42.753,-9.956,-42.734,-9.916,-42.71,-9.954,-42.691,-9.967,-42.675,-9.955,-42.639,-9.973,-42.606,-9.999,-42.593,-10.031,-42.567,-10.071,-42.547,-10.078,-42.508,-10.069,-42.482,-10.044,-42.519,-9.983,-42.51,-9.948,-42.472,-9.959,-42.419,-9.995,-42.361,-9.985,-42.341,-9.949,-42.333,-9.906,-42.303,-9.872,-42.287,-9.834,-42.305,-9.772,-42.354,-9.708,-42.318,-9.696,-42.221,-9.696,-42.195,-9.613,-42.141,-9.607,-42.09,-9.592,-42.077,-9.546,-42.048,-9.511,-41.998,-9.517,-41.979,-9.573,-41.924,-9.618,-41.74,-9.565,-41.592,-9.591,-41.503,-9.586,-41.457,-9.596,-41.446,-9.591,-41.365,-9.622,-41.328,-9.608,-41.305,-9.591,-41.264,-9.594,-41.224,-9.511,-41.224,-9.432,-41.264,-9.362,-41.245,-9.346,-41.186,-9.366,-41.175,-9.352,-41.191,-9.309,-41.184,-9.283,-41.132,-9.291,-41.064,-9.282,-41.042,-9.242,-41.029,-9.194,-41.047,-9.149,-41.041,-9.125,-41.038,-9.058,-41.027,-9.004,-40.976,-9.018,-40.959,-9.038,-40.935,-9.057,-40.953,-9.174,-40.92,-9.257],[-42.803,-10.961,-42.849,-10.935,-42.839,-10.905,-42.804,-10.87,-42.772,-10.843,-42.783,-10.831,-42.847,-10.846,-42.891,-10.902,-42.991,-10.943,-42.996,-10.961,-42.899,-10.945,-42.879,-10.975,-42.918,-11.038,-42.931,-11.079,-42.91,-11.111,-42.879,-11.148,-42.849,-11.143,-42.807,-11.083,-42.803,-10.961]]]},{"n":"Toba","z":4.7,"b":[98.526,2.344,99.143,2.876],"p":[[[99.143,2.412,99.143,2.397,99.093,2.37,98.999,2.349,98.908,2.344,98.846,2.353,98.818,2.38,98.754,2.467,98.689,2.555,98.711,2.577,98.784,2.515,98.885,2.476,98.927,2.483,98.949,2.504,98.919,2.558,98.826,2.671,98.76,2.729,98.723,2.724,98.69,2.688,98.662,2.644,98.641,2.65,98.581,2.732,98.526,2.824,98.53,2.859,98.572,2.876,98.64,2.873,98.707,2.854,98.792,2.799,98.87,2.731,98.918,2.665,98.979,2.575,99.042,2.483,99.102,2.437,99.143,2.412]]]},{"n":"","z":4.7,"b":[25.891,61.542,26.57,61.841],"p":[[[26.57,61.623,26.567,61.616,26.545,61.609,26.473,61.612,26.346,61.655,26.24,61.718,26.183,61.73,26.121,61.716,26.077,61.704,26.072,61.679,26.085,61.657,26.103,61.648,26.123,61.657,26.144,61.669,26.167,61.653,26.183,61.629,26.172,61.6,26.158,61.565,26.148,61.546,26.114,61.542,26.059,61.555,26.007,61.59,25.958,61.619,25.9,61.647,25.891,61.675,25.931,61.691,25.955,61.717,25.966,61.743,25.953,61.767,25.936,61.786,25.944,61.796,26.032,61.772,26.126,61.767,26.176,61.798,26.219,61.841,26.237,61.841,26.246,61.822,26.265,61.785,26.278,61.751,26.279,61.733,26.295,61.716,26.329,61.714,26.354,61.715,26.406,61.717,26.438,61.735,26.466,61.735,26.507,61.718,26.543,61.688,26.563,61.648,26.57,61.623]]]},{"n":"Pukaki","z":4.7,"b":[170.131,-44.173,170.248,-43.704],"p":[[[170.248,-44.117,170.23,-44.173,170.204,-44.171,170.178,-44.17,170.178,-44.065,170.153,-43.914,170.131,-43.809,170.136,-43.766,170.152,-43.746,170.176,-43.723,170.198,-43.704,170.21,-43.714,170.2,-43.782,170.184,-43.831,170.213,-43.968,170.243,-44.075,170.248,-44.117]]]},{"n":"Ōhau","z":4.7,"b":[169.86,-44.295,169.995,-43.973],"p":[[[169.995,-44.287,169.945,-44.295,169.893,-44.277,169.875,-44.248,169.865,-44.224,169.87,-44.181,169.874,-44.087,169.86,-43.987,169.881,-43.973,169.885,-43.998,169.891,-44.039,169.912,-44.051,169.948,-44.008,169.98,-43.974,169.982,-44.017,169.925,-44.091,169.913,-44.172,169.921,-44.221,169.947,-44.255,169.962,-44.269,169.995,-44.287]]]},{"n":"Abitibi","z":4.7,"b":[-80.18,48.449,-79.226,48.935],"p":[[[-80.18,48.812,-80.153,48.835,-80.123,48.877,-80.105,48.913,-80.055,48.919,-79.952,48.935,-79.889,48.924,-79.865,48.89,-79.836,48.875,-79.836,48.853,-79.857,48.807,-79.855,48.765,-79.832,48.73,-79.77,48.717,-79.701,48.723,-79.654,48.739,-79.617,48.77,-79.576,48.797,-79.499,48.761,-79.413,48.715,-79.324,48.705,-79.259,48.69,-79.248,48.665,-79.257,48.599,-79.243,48.544,-79.233,48.503,-79.226,48.472,-79.247,48.456,-79.283,48.449,-79.313,48.452,-79.311,48.475,-79.312,48.498,-79.311,48.532,-79.295,48.578,-79.297,48.608,-79.319,48.631,-79.373,48.645,-79.439,48.648,-79.478,48.642,-79.483,48.63,-79.485,48.618,-79.511,48.613,-79.602,48.62,-79.701,48.641,-79.753,48.644,-79.804,48.632,-79.867,48.633,-79.916,48.659,-79.972,48.699,-79.981,48.717,-80,48.734,-80.017,48.748,-80.06,48.783,-80.091,48.775,-80.148,48.79,-80.18,48.812]]]},{"n":"Fort Peck","z":4.7,"b":[-108.196,47.39,-106.217,48.027],"p":[[[-106.217,47.585,-106.251,47.55,-106.269,47.548,-106.252,47.6,-106.251,47.644,-106.285,47.697,-106.318,47.755,-106.336,47.785,-106.36,47.813,-106.384,47.848,-106.42,47.876,-106.454,47.879,-106.48,47.858,-106.521,47.828,-106.55,47.785,-106.556,47.75,-106.599,47.753,-106.683,47.739,-106.768,47.724,-106.813,47.716,-106.838,47.694,-106.85,47.649,-106.862,47.631,-106.879,47.649,-106.906,47.672,-106.944,47.667,-106.993,47.644,-107.015,47.645,-107.03,47.666,-107.183,47.685,-107.306,47.685,-107.333,47.685,-107.386,47.654,-107.429,47.635,-107.507,47.624,-107.612,47.623,-107.719,47.563,-107.846,47.468,-107.911,47.39,-107.924,47.407,-107.909,47.471,-107.916,47.509,-107.93,47.542,-107.949,47.558,-107.982,47.569,-108.044,47.58,-108.17,47.584,-108.196,47.602,-108.173,47.613,-108.045,47.614,-107.959,47.601,-107.931,47.588,-107.889,47.538,-107.841,47.526,-107.779,47.57,-107.712,47.613,-107.685,47.646,-107.688,47.671,-107.714,47.688,-107.605,47.676,-107.455,47.662,-107.422,47.68,-107.38,47.716,-107.336,47.728,-107.295,47.722,-107.231,47.724,-107.121,47.725,-106.99,47.716,-106.93,47.725,-106.938,47.746,-106.933,47.764,-106.881,47.778,-106.821,47.783,-106.769,47.788,-106.738,47.806,-106.709,47.822,-106.655,47.841,-106.596,47.86,-106.582,47.879,-106.566,47.907,-106.574,47.936,-106.59,47.958,-106.538,47.983,-106.433,48.027,-106.406,48.021,-106.366,48.015,-106.325,47.985,-106.331,47.95,-106.358,47.925,-106.345,47.879,-106.285,47.837,-106.253,47.815,-106.267,47.793,-106.253,47.769,-106.241,47.739,-106.232,47.691,-106.217,47.628,-106.217,47.585]]]},{"n":"Oahe","z":4.7,"b":[-101.188,44.419,-100.255,46.714],"p":[[[-100.289,45.017,-100.341,45.006,-100.37,44.988,-100.382,44.951,-100.392,44.896,-100.427,44.847,-100.476,44.794,-100.532,44.763,-100.573,44.753,-100.625,44.767,-100.636,44.75,-100.614,44.669,-100.595,44.601,-100.559,44.584,-100.516,44.593,-100.488,44.573,-100.504,44.522,-100.477,44.499,-100.418,44.492,-100.397,44.47,-100.388,44.451,-100.394,44.434,-100.412,44.419,-100.478,44.443,-100.567,44.446,-100.61,44.47,-100.637,44.503,-100.62,44.531,-100.633,44.575,-100.671,44.627,-100.676,44.676,-100.682,44.714,-100.724,44.732,-100.774,44.747,-100.803,44.761,-100.848,44.764,-100.946,44.747,-101.072,44.732,-101.163,44.725,-101.188,44.727,-101.158,44.747,-101.071,44.764,-100.995,44.777,-100.976,44.798,-100.96,44.818,-100.908,44.82,-100.861,44.831,-100.805,44.834,-100.767,44.85,-100.778,44.882,-100.737,44.883,-100.67,44.867,-100.604,44.828,-100.559,44.811,-100.54,44.839,-100.5,44.87,-100.457,44.872,-100.441,44.897,-100.443,44.939,-100.447,44.999,-100.401,45.052,-100.347,45.102,-100.335,45.157,-100.323,45.215,-100.317,45.26,-100.345,45.305,-100.407,45.348,-100.543,45.353,-100.612,45.355,-100.528,45.377,-100.4,45.383,-100.344,45.39,-100.355,45.422,-100.378,45.447,-100.434,45.504,-100.543,45.583,-100.627,45.647,-100.637,45.662,-100.606,45.664,-100.531,45.636,-100.448,45.644,-100.4,45.687,-100.39,45.71,-100.398,45.747,-100.414,45.808,-100.478,45.89,-100.579,46.008,-100.659,46.12,-100.668,46.162,-100.641,46.223,-100.612,46.25,-100.594,46.314,-100.594,46.392,-100.606,46.489,-100.621,46.537,-100.634,46.58,-100.713,46.641,-100.767,46.673,-100.786,46.696,-100.777,46.714,-100.714,46.705,-100.667,46.669,-100.585,46.617,-100.564,46.559,-100.543,46.485,-100.527,46.386,-100.552,46.279,-100.574,46.187,-100.557,46.14,-100.559,46.096,-100.535,46.059,-100.493,45.988,-100.432,45.923,-100.377,45.895,-100.35,45.854,-100.342,45.767,-100.345,45.685,-100.354,45.645,-100.398,45.618,-100.408,45.564,-100.376,45.524,-100.307,45.469,-100.281,45.427,-100.279,45.369,-100.271,45.296,-100.255,45.242,-100.273,45.16,-100.286,45.088,-100.265,45.071,-100.262,45.044,-100.289,45.017]]]},{"n":"Teslin","z":4.7,"b":[-133.245,59.604,-132.089,60.449],"p":[[[-132.095,59.607,-132.125,59.604,-132.159,59.626,-132.213,59.688,-132.316,59.835,-132.399,59.922,-132.61,60.05,-132.834,60.135,-132.958,60.201,-133.012,60.251,-133.187,60.382,-133.245,60.449,-133.138,60.427,-132.98,60.297,-132.798,60.192,-132.702,60.153,-132.589,60.123,-132.522,60.098,-132.461,60.055,-132.364,59.986,-132.323,59.951,-132.277,59.902,-132.23,59.849,-132.225,59.838,-132.214,59.816,-132.202,59.77,-132.123,59.694,-132.089,59.644,-132.095,59.607]]]},{"n":"Arrow Lakes","z":4.7,"b":[-118.156,49.336,-117.907,49.802],"p":[[[-117.907,49.342,-117.957,49.336,-118.063,49.412,-118.096,49.45,-118.139,49.521,-118.156,49.601,-118.138,49.703,-118.128,49.752,-118.092,49.802,-118.084,49.774,-118.109,49.583,-118.087,49.515,-118.048,49.457,-117.976,49.406,-117.911,49.375,-117.907,49.342]]]},{"n":"Nasser","z":4.7,"b":[31.091,21.609,33.007,23.988],"p":[[[32.234,22.674,32.167,22.68,32.127,22.688,32.096,22.683,32.056,22.665,32.02,22.634,31.986,22.6,31.962,22.573,31.953,22.535,31.936,22.481,31.897,22.441,31.855,22.415,31.831,22.381,31.826,22.351,31.791,22.358,31.746,22.363,31.714,22.351,31.694,22.334,31.679,22.327,31.664,22.322,31.645,22.312,31.64,22.297,31.648,22.273,31.625,22.242,31.552,22.209,31.487,22.159,31.441,22.102,31.412,22.057,31.391,21.984,31.355,21.881,31.319,21.812,31.295,21.785,31.257,21.764,31.203,21.709,31.156,21.649,31.124,21.612,31.091,21.609,31.112,21.671,31.169,21.749,31.236,21.848,31.303,21.932,31.336,22.01,31.372,22.103,31.45,22.21,31.593,22.339,31.684,22.43,31.704,22.453,31.74,22.47,31.785,22.483,31.752,22.535,31.679,22.604,31.676,22.626,31.729,22.603,31.794,22.569,31.824,22.565,31.841,22.583,31.864,22.614,31.884,22.639,31.911,22.661,31.965,22.698,32.024,22.734,32.061,22.752,32.088,22.764,32.132,22.781,32.167,22.788,32.191,22.779,32.222,22.769,32.292,22.723,32.337,22.691,32.413,22.694,32.501,22.758,32.526,22.795,32.542,22.829,32.562,22.899,32.582,22.929,32.614,22.968,32.636,23.004,32.667,23.078,32.7,23.151,32.739,23.229,32.788,23.273,32.819,23.278,32.833,23.293,32.843,23.318,32.856,23.342,32.851,23.382,32.853,23.438,32.841,23.483,32.787,23.503,32.743,23.516,32.706,23.493,32.661,23.472,32.622,23.477,32.599,23.496,32.65,23.534,32.707,23.57,32.697,23.587,32.676,23.594,32.661,23.58,32.646,23.567,32.624,23.587,32.655,23.633,32.739,23.624,32.798,23.584,32.834,23.619,32.854,23.677,32.843,23.708,32.848,23.733,32.87,23.76,32.873,23.792,32.863,23.829,32.828,23.873,32.823,23.91,32.879,23.988,32.933,23.941,32.945,23.791,32.986,23.695,32.981,23.655,32.951,23.643,32.921,23.634,32.909,23.61,32.911,23.584,32.924,23.567,32.941,23.563,32.951,23.555,32.988,23.53,33.005,23.509,32.951,23.509,32.922,23.485,32.944,23.43,32.979,23.396,32.99,23.371,32.995,23.337,33.006,23.3,33.007,23.26,33.007,23.237,32.995,23.231,32.957,23.241,32.921,23.249,32.929,23.204,32.911,23.161,32.865,23.156,32.853,23.142,32.855,23.117,32.869,23.088,32.907,23.051,32.944,23.004,32.99,22.955,32.972,22.935,32.859,22.978,32.785,23.039,32.744,23.033,32.739,23.008,32.722,22.972,32.705,22.947,32.69,22.938,32.66,22.922,32.632,22.906,32.618,22.896,32.611,22.869,32.607,22.823,32.586,22.783,32.516,22.725,32.429,22.651,32.368,22.604,32.332,22.581,32.328,22.55,32.297,22.531,32.254,22.536,32.248,22.547,32.27,22.564,32.299,22.592,32.296,22.612,32.279,22.623,32.256,22.642,32.238,22.659,32.234,22.674]]]},{"n":"","z":4.7,"b":[23.773,61.031,24.702,61.721],"p":[[[24.608,61.654,24.558,61.64,24.493,61.614,24.456,61.59,24.507,61.552,24.469,61.528,24.342,61.52,24.265,61.496,24.194,61.457,24.172,61.423,24.169,61.404,24.153,61.376,24.128,61.344,24.158,61.328,24.24,61.315,24.279,61.293,24.245,61.289,24.161,61.28,24.08,61.26,24.024,61.247,24.014,61.231,24.041,61.207,24.092,61.185,24.176,61.183,24.242,61.195,24.296,61.187,24.355,61.162,24.372,61.145,24.354,61.142,24.33,61.139,24.337,61.12,24.396,61.071,24.439,61.031,24.414,61.036,24.352,61.073,24.277,61.093,24.03,61.154,23.875,61.183,23.781,61.214,23.773,61.283,23.803,61.26,23.845,61.228,23.919,61.211,23.952,61.224,23.981,61.256,24.021,61.282,24.035,61.312,24.017,61.369,24.043,61.423,24.171,61.484,24.254,61.529,24.306,61.553,24.358,61.587,24.333,61.607,24.309,61.621,24.321,61.633,24.368,61.629,24.422,61.621,24.454,61.626,24.481,61.647,24.497,61.668,24.491,61.679,24.524,61.687,24.604,61.703,24.691,61.721,24.702,61.702,24.674,61.668,24.659,61.656,24.635,61.656,24.608,61.654]]]},{"n":"Pyhäjärvi","z":4.7,"b":[23.349,61.262,24.728,62.266],"p":[[[23.511,61.419,23.507,61.391,23.528,61.369,23.568,61.348,23.646,61.331,23.707,61.317,23.712,61.304,23.717,61.287,23.725,61.27,23.717,61.262,23.675,61.276,23.572,61.296,23.485,61.331,23.44,61.371,23.38,61.382,23.349,61.39,23.365,61.403,23.416,61.418,23.509,61.451,23.639,61.491,23.709,61.511,23.677,61.531,23.638,61.556,23.627,61.589,23.599,61.623,23.57,61.641,23.603,61.647,23.632,61.652,23.627,61.665,23.603,61.688,23.621,61.712,23.667,61.728,23.667,61.745,23.68,61.777,23.692,61.81,23.703,61.825,23.725,61.836,23.708,61.865,23.751,61.877,23.838,61.87,23.861,61.879,23.859,61.894,23.865,61.911,23.951,61.944,24.032,61.974,24.025,61.987,24.016,62,24.015,62.038,24.003,62.076,23.974,62.09,23.873,62.095,23.738,62.103,23.681,62.116,23.684,62.125,23.673,62.137,23.649,62.166,23.638,62.197,23.645,62.215,23.675,62.227,23.699,62.227,23.691,62.213,23.683,62.191,23.701,62.164,23.75,62.159,23.81,62.155,23.855,62.14,23.885,62.142,23.906,62.154,23.915,62.188,23.928,62.221,23.968,62.233,23.999,62.232,23.979,62.208,23.953,62.178,23.954,62.16,23.98,62.16,24.034,62.154,24.122,62.151,24.181,62.157,24.146,62.143,24.123,62.123,24.117,62.092,24.126,62.057,24.181,62.045,24.22,62.045,24.257,62.08,24.285,62.117,24.301,62.12,24.321,62.115,24.335,62.103,24.319,62.073,24.304,62.042,24.324,62.03,24.375,62.029,24.498,62.027,24.565,62.045,24.524,62.081,24.459,62.099,24.384,62.119,24.343,62.146,24.363,62.156,24.438,62.141,24.533,62.106,24.595,62.09,24.611,62.109,24.599,62.146,24.582,62.18,24.585,62.197,24.601,62.201,24.615,62.205,24.613,62.227,24.611,62.251,24.63,62.263,24.662,62.266,24.674,62.229,24.682,62.194,24.722,62.198,24.728,62.181,24.709,62.134,24.686,62.095,24.63,62.043,24.634,61.986,24.684,61.949,24.706,61.921,24.678,61.929,24.599,61.965,24.549,61.993,24.482,62.001,24.417,61.995,24.417,61.987,24.377,61.99,24.294,61.996,24.181,62.01,24.094,62.02,24.119,61.988,24.105,61.951,24.053,61.927,24.004,61.907,23.962,61.894,23.954,61.88,23.852,61.835,23.758,61.786,23.776,61.756,23.786,61.729,23.767,61.703,23.743,61.673,23.739,61.651,23.82,61.646,23.834,61.636,23.772,61.618,23.784,61.612,23.798,61.599,23.791,61.56,23.776,61.521,23.73,61.495,23.611,61.46,23.511,61.419]]]},{"n":"Volta","z":4.7,"b":[-1.715,6.338,0.32,9.117],"p":[[[0.272,6.675,0.299,6.719,0.317,6.738,0.309,6.698,0.276,6.648,0.23,6.616,0.192,6.607,0.172,6.615,0.16,6.618,0.152,6.611,0.154,6.597,0.151,6.57,0.147,6.538,0.14,6.505,0.133,6.45,0.138,6.366,0.103,6.338,0.081,6.353,0.073,6.404,0.041,6.405,-0.011,6.379,-0.033,6.408,-0.06,6.452,-0.099,6.49,-0.175,6.533,-0.255,6.585,-0.324,6.622,-0.428,6.668,-0.55,6.717,-0.634,6.753,-0.674,6.802,-0.684,6.834,-0.66,6.836,-0.616,6.807,-0.58,6.785,-0.551,6.811,-0.518,6.794,-0.491,6.737,-0.467,6.723,-0.442,6.727,-0.419,6.731,-0.409,6.766,-0.406,6.816,-0.393,6.828,-0.368,6.773,-0.335,6.716,-0.305,6.717,-0.271,6.722,-0.228,6.734,-0.194,6.741,-0.175,6.724,-0.163,6.686,-0.144,6.648,-0.096,6.629,-0.057,6.63,-0.041,6.677,-0.026,6.72,-0.017,6.733,-0.023,6.748,-0.049,6.769,-0.067,6.789,-0.071,6.809,-0.066,6.825,-0.042,6.847,-0.011,6.853,0.012,6.845,0.03,6.85,0.077,6.87,0.112,6.897,0.104,6.91,0.059,6.914,0.042,6.946,0.087,6.974,0.12,6.971,0.123,6.983,0.099,7.004,0.088,7.023,0.093,7.037,0.101,7.053,0.098,7.08,0.086,7.121,0.085,7.147,0.066,7.151,0.045,7.164,0.028,7.176,0.01,7.187,0.007,7.209,0.015,7.211,0.035,7.2,0.063,7.195,0.083,7.194,0.099,7.193,0.118,7.203,0.142,7.222,0.153,7.241,0.16,7.252,0.174,7.252,0.184,7.266,0.184,7.287,0.172,7.304,0.156,7.309,0.135,7.313,0.102,7.343,0.068,7.393,0.05,7.432,0.051,7.449,0.047,7.46,0.046,7.478,-0.002,7.5,-0.063,7.508,-0.058,7.466,-0.066,7.444,-0.107,7.458,-0.128,7.472,-0.136,7.496,-0.157,7.51,-0.207,7.512,-0.242,7.517,-0.232,7.532,-0.208,7.546,-0.169,7.55,-0.134,7.553,-0.105,7.551,-0.071,7.549,-0.027,7.546,0.012,7.543,-0.018,7.592,-0.064,7.641,-0.11,7.645,-0.182,7.647,-0.242,7.639,-0.336,7.618,-0.432,7.594,-0.475,7.589,-0.498,7.597,-0.531,7.576,-0.603,7.554,-0.654,7.551,-0.611,7.567,-0.543,7.6,-0.483,7.625,-0.422,7.649,-0.357,7.664,-0.294,7.667,-0.262,7.684,-0.256,7.706,-0.254,7.72,-0.21,7.714,-0.161,7.721,-0.125,7.74,-0.098,7.746,-0.1,7.755,-0.134,7.767,-0.157,7.779,-0.159,7.794,-0.152,7.813,-0.152,7.841,-0.17,7.89,-0.186,7.927,-0.192,7.955,-0.224,7.977,-0.285,8.015,-0.315,8.04,-0.344,8.038,-0.351,8.002,-0.384,7.98,-0.417,7.972,-0.394,8,-0.379,8.048,-0.415,8.079,-0.446,8.091,-0.498,8.057,-0.527,8.062,-0.526,8.128,-0.543,8.154,-0.561,8.134,-0.591,8.1,-0.624,8.085,-0.643,8.087,-0.643,8.106,-0.693,8.11,-0.756,8.102,-0.761,8.118,-0.748,8.135,-0.721,8.135,-0.655,8.142,-0.604,8.159,-0.6,8.173,-0.621,8.191,-0.676,8.239,-0.724,8.28,-0.754,8.297,-0.795,8.315,-0.801,8.334,-0.784,8.366,-0.838,8.447,-0.912,8.555,-0.965,8.604,-0.999,8.63,-1.041,8.646,-1.087,8.64,-1.128,8.632,-1.141,8.595,-1.153,8.551,-1.18,8.604,-1.219,8.658,-1.254,8.668,-1.304,8.697,-1.351,8.724,-1.405,8.739,-1.443,8.731,-1.488,8.697,-1.545,8.664,-1.605,8.644,-1.664,8.629,-1.715,8.63,-1.663,8.673,-1.591,8.695,-1.519,8.731,-1.466,8.761,-1.412,8.781,-1.376,8.805,-1.355,8.792,-1.294,8.75,-1.269,8.726,-1.126,8.705,-1.062,8.722,-1.067,8.75,-1.096,8.784,-1.134,8.813,-1.145,8.874,-1.174,8.945,-1.179,8.979,-1.178,9.027,-1.174,9.074,-1.152,9.117,-1.13,9.076,-1.127,9.028,-1.125,8.981,-1.121,8.929,-1.106,8.897,-1.075,8.853,-1.045,8.843,-1.026,8.844,-0.997,8.775,-0.931,8.653,-0.873,8.586,-0.843,8.579,-0.817,8.6,-0.818,8.65,-0.823,8.707,-0.809,8.743,-0.791,8.761,-0.782,8.756,-0.795,8.721,-0.806,8.689,-0.77,8.656,-0.722,8.639,-0.705,8.643,-0.707,8.625,-0.757,8.578,-0.805,8.52,-0.797,8.497,-0.782,8.469,-0.721,8.402,-0.66,8.357,-0.627,8.329,-0.594,8.288,-0.566,8.275,-0.554,8.251,-0.527,8.232,-0.485,8.209,-0.449,8.209,-0.394,8.221,-0.355,8.243,-0.318,8.244,-0.286,8.253,-0.245,8.245,-0.202,8.213,-0.193,8.186,-0.257,8.185,-0.321,8.177,-0.33,8.158,-0.308,8.142,-0.263,8.108,-0.19,8.053,-0.128,8.011,-0.103,7.976,-0.082,7.876,-0.052,7.811,-0.024,7.793,0.002,7.793,0.019,7.789,0.055,7.792,0.07,7.821,0.058,7.863,0.056,7.886,0.059,7.92,0.073,7.969,0.079,8.003,0.062,8.041,0.07,8.081,0.099,8.093,0.129,8.086,0.154,8.078,0.17,8.086,0.165,8.115,0.152,8.161,0.123,8.21,0.105,8.242,0.137,8.254,0.169,8.289,0.198,8.338,0.217,8.363,0.161,8.381,0.092,8.403,0.081,8.429,0.093,8.461,0.091,8.5,0.085,8.548,0.106,8.583,0.135,8.598,0.15,8.633,0.16,8.7,0.184,8.742,0.198,8.738,0.185,8.69,0.168,8.625,0.152,8.592,0.128,8.574,0.121,8.518,0.113,8.457,0.103,8.434,0.115,8.416,0.178,8.399,0.242,8.379,0.261,8.355,0.28,8.341,0.294,8.334,0.284,8.327,0.242,8.322,0.214,8.314,0.217,8.296,0.19,8.251,0.184,8.206,0.201,8.166,0.231,8.125,0.268,8.106,0.267,8.094,0.24,8.088,0.198,8.079,0.161,8.06,0.146,8.021,0.121,7.972,0.108,7.939,0.124,7.913,0.146,7.894,0.168,7.863,0.221,7.883,0.258,7.905,0.251,7.876,0.252,7.863,0.248,7.849,0.237,7.834,0.248,7.803,0.278,7.762,0.279,7.699,0.281,7.617,0.282,7.575,0.259,7.581,0.24,7.578,0.228,7.556,0.229,7.536,0.242,7.511,0.25,7.491,0.285,7.515,0.32,7.542,0.313,7.506,0.302,7.47,0.297,7.453,0.295,7.416,0.287,7.382,0.276,7.353,0.277,7.339,0.277,7.33,0.292,7.303,0.294,7.278,0.276,7.258,0.272,7.234,0.279,7.218,0.281,7.201,0.279,7.182,0.276,7.172,0.271,7.138,0.289,7.106,0.305,7.099,0.3,7.071,0.281,7.047,0.273,7.001,0.263,6.948,0.24,6.896,0.237,6.768,0.254,6.673,0.272,6.675],[0.13,6.679,0.181,6.726,0.159,6.767,0.115,6.796,0.062,6.808,0.017,6.779,0.011,6.718,0.049,6.695,0.09,6.659,0.13,6.679]]]},{"n":"Bratsk","z":5,"b":[101.275,53.107,103.743,56.349],"p":[[[103.533,53.636,103.725,53.602,103.737,53.554,103.743,53.496,103.726,53.484,103.686,53.512,103.609,53.552,103.49,53.563,103.44,53.519,103.425,53.473,103.44,53.223,103.374,53.107,103.343,53.228,103.343,53.572,103.275,53.677,103.096,53.742,102.868,53.79,102.927,53.82,103.117,53.774,103.208,53.806,103.119,53.938,103.003,54.068,102.987,54.156,103.017,54.283,103.106,54.415,103.173,54.424,103.218,54.487,103.186,54.627,103.089,54.713,103.033,54.782,103.07,54.921,103.226,55.046,103.282,55.159,103.272,55.302,103.179,55.486,103.149,55.578,103.139,55.685,103.074,55.789,102.99,55.854,102.961,55.958,102.921,56.063,102.852,56.104,102.797,56.108,102.717,56.143,102.593,56.156,102.53,56.158,102.506,56.156,102.494,56.127,102.48,56.108,102.439,56.091,102.393,56.088,102.309,56.11,102.236,56.103,102.18,56.068,101.986,56.026,102,55.961,101.978,55.903,101.906,55.834,101.913,55.783,101.99,55.731,102.085,55.718,102.094,55.696,102.051,55.663,102.102,55.628,102.332,55.59,102.386,55.544,102.366,55.485,102.315,55.436,102.311,55.401,102.327,55.353,102.342,55.32,102.39,55.302,102.43,55.285,102.39,55.262,102.344,55.232,102.32,55.195,102.285,55.143,102.233,55.101,102.186,55.07,102.118,55.053,102.142,55.08,102.184,55.118,102.208,55.146,102.259,55.183,102.274,55.222,102.33,55.271,102.31,55.301,102.284,55.345,102.26,55.409,102.268,55.445,102.307,55.495,102.3,55.53,102.217,55.514,102.072,55.457,101.789,55.414,101.761,55.436,101.757,55.469,101.979,55.523,101.97,55.563,101.91,55.59,101.806,55.654,101.788,55.772,101.741,55.807,101.504,55.889,101.411,55.923,101.322,55.946,101.275,55.982,101.352,55.987,101.483,55.968,101.613,55.969,101.633,55.977,101.706,55.973,101.73,56.014,101.732,56.07,101.699,56.095,101.672,56.135,101.706,56.175,101.697,56.218,101.71,56.264,101.767,56.287,101.803,56.291,101.867,56.236,101.931,56.242,101.964,56.227,101.922,56.167,101.945,56.125,102.054,56.156,102.255,56.227,102.26,56.255,102.462,56.253,102.474,56.311,102.522,56.349,102.582,56.332,102.588,56.298,102.626,56.253,102.727,56.25,102.799,56.262,102.823,56.235,102.872,56.199,102.941,56.197,103.001,56.223,103.023,56.214,103.009,56.144,103.02,56.045,103.063,55.92,103.143,55.808,103.247,55.714,103.249,55.672,103.226,55.558,103.334,55.358,103.364,55.219,103.348,55.132,103.304,55.02,103.185,54.878,103.154,54.801,103.216,54.719,103.279,54.578,103.286,54.482,103.349,54.323,103.425,54.205,103.463,54.16,103.397,54.17,103.318,54.303,103.261,54.342,103.205,54.311,103.144,54.239,103.103,54.147,103.16,54.001,103.298,53.874,103.3,53.841,103.33,53.779,103.435,53.679,103.533,53.636]]]},{"n":"Khantayskoye","z":5,"b":[89.313,68.24,92.331,68.509],"p":[[[91.937,68.489,92.104,68.509,92.222,68.46,92.331,68.426,92.114,68.408,91.898,68.398,91.541,68.397,91.105,68.368,90.92,68.311,90.533,68.287,89.935,68.24,89.807,68.263,89.772,68.305,89.667,68.315,89.556,68.31,89.472,68.313,89.399,68.33,89.313,68.335,89.33,68.351,89.315,68.388,89.523,68.399,90.232,68.369,90.515,68.39,90.658,68.407,90.869,68.405,91.092,68.443,91.483,68.44,91.937,68.489]]]},{"n":"Keta","z":5,"b":[89.547,68.7,91.713,68.862],"p":[[[91.306,68.793,90.843,68.755,90.544,68.71,90.356,68.7,90.05,68.702,90.006,68.707,89.989,68.727,89.547,68.819,89.582,68.848,89.737,68.856,89.999,68.837,90.223,68.787,90.445,68.758,90.684,68.779,90.787,68.799,90.918,68.816,91.192,68.832,91.335,68.831,91.452,68.836,91.713,68.862,91.484,68.787,91.306,68.793]]]},{"n":"Lama","z":5,"b":[88.825,69.323,91.559,69.625],"p":[[[89.469,69.378,89.225,69.349,88.958,69.323,88.825,69.339,88.845,69.371,89.123,69.425,89.346,69.459,89.546,69.495,89.901,69.603,90.205,69.625,90.523,69.577,91.08,69.488,91.436,69.51,91.559,69.418,90.988,69.459,90.66,69.453,90.408,69.519,90.099,69.539,89.761,69.489,89.582,69.444,89.469,69.378]]]},{"n":"Uddjaure","z":5,"b":[17.346,65.852,18.009,66.201],"p":[[[17.967,65.852,17.955,65.864,17.926,65.88,17.882,65.89,17.815,65.866,17.74,65.857,17.693,65.875,17.691,65.905,17.704,65.931,17.736,65.949,17.72,65.979,17.637,66.012,17.591,66.014,17.577,66.005,17.546,66.012,17.517,66.05,17.46,66.066,17.401,66.051,17.36,66.047,17.346,66.08,17.395,66.1,17.471,66.113,17.547,66.108,17.595,66.085,17.672,66.067,17.733,66.083,17.723,66.115,17.615,66.168,17.621,66.193,17.676,66.201,17.777,66.187,17.835,66.155,17.968,66.106,18.009,66.051,17.964,66.048,17.945,65.997,17.949,65.959,17.948,65.934,17.964,65.912,18.001,65.882,17.967,65.852]]]},{"n":"Storavan","z":5,"b":[17.967,65.616,18.509,65.882],"p":[[[18.001,65.882,18.031,65.857,18.14,65.778,18.286,65.776,18.37,65.752,18.381,65.73,18.424,65.7,18.509,65.66,18.378,65.648,18.255,65.677,18.173,65.664,18.151,65.644,18.161,65.621,18.131,65.616,18.093,65.629,18.083,65.649,18.071,65.715,18.036,65.774,17.967,65.852,18.001,65.882]]]},{"n":"","z":5,"b":[13.71,63.754,15.824,64.66],"p":[[[15.755,63.758,15.642,63.808,15.576,63.784,15.542,63.76,15.536,63.798,15.52,63.85,15.303,63.932,15.263,63.961,15.39,63.926,15.417,63.939,15.419,63.959,15.376,63.997,15.316,64.031,15.319,64.11,15.24,64.157,15.125,64.245,15.037,64.285,14.328,64.388,14.192,64.447,14.093,64.519,13.961,64.563,13.71,64.636,13.802,64.66,14.096,64.557,14.195,64.481,14.356,64.42,14.493,64.417,14.664,64.383,14.851,64.347,14.94,64.344,14.991,64.336,15.013,64.334,15.048,64.338,15.111,64.341,15.21,64.322,15.261,64.262,15.322,64.163,15.419,64.075,15.394,64.051,15.395,64.019,15.471,63.967,15.578,63.868,15.745,63.83,15.824,63.754,15.755,63.758]]]},{"n":"","z":5,"b":[13.198,64.671,13.635,64.785],"p":[[[13.29,64.738,13.399,64.785,13.527,64.727,13.635,64.686,13.6,64.675,13.368,64.693,13.234,64.671,13.198,64.684,13.29,64.738]]]},{"n":"Habbaniyah","z":5,"b":[43.304,33.19,43.595,33.435],"p":[[[43.595,33.306,43.566,33.219,43.483,33.19,43.434,33.199,43.402,33.257,43.304,33.309,43.309,33.349,43.382,33.435,43.428,33.39,43.477,33.338,43.554,33.348,43.595,33.306]]]},{"n":"Skadar","z":5,"b":[19.099,42.079,19.481,42.29],"p":[[[19.293,42.265,19.364,42.29,19.392,42.228,19.451,42.157,19.481,42.108,19.467,42.079,19.394,42.088,19.216,42.158,19.099,42.278,19.175,42.277,19.293,42.265]]]},{"n":"Aylmer","z":5,"b":[-109.639,63.945,-107.932,64.413],"p":[[[-107.932,64.036,-108.084,64.045,-108.161,64.038,-108.252,64.022,-108.372,63.983,-108.459,63.945,-108.661,63.988,-108.749,64.036,-108.858,64.065,-109.114,64.076,-109.394,64.02,-109.571,64.018,-109.639,64.051,-109.363,64.066,-109.154,64.104,-109.029,64.103,-108.634,64.17,-108.598,64.195,-108.601,64.227,-108.623,64.276,-108.602,64.331,-108.541,64.388,-108.467,64.413,-108.432,64.398,-108.438,64.314,-108.419,64.267,-108.361,64.278,-108.285,64.317,-108.247,64.316,-108.261,64.277,-108.335,64.209,-108.358,64.181,-108.336,64.156,-108.263,64.142,-108.257,64.16,-108.186,64.177,-108.102,64.137,-107.945,64.094,-107.932,64.036]]]},{"n":"Clinton-Colden","z":5,"b":[-107.945,63.776,-107.088,64.131],"p":[[[-107.945,64.094,-107.837,64.089,-107.752,64.071,-107.675,64.081,-107.589,64.112,-107.464,64.128,-107.323,64.131,-107.238,64.1,-107.233,64.067,-107.24,64.037,-107.24,64.022,-107.269,63.983,-107.222,63.926,-107.104,63.924,-107.088,63.912,-107.106,63.894,-107.2,63.824,-107.274,63.776,-107.502,63.789,-107.529,63.798,-107.557,63.833,-107.632,63.877,-107.614,63.937,-107.622,63.97,-107.668,63.985,-107.789,63.981,-107.805,63.956,-107.836,63.983,-107.932,64.036,-107.945,64.094]]]},{"n":"Francis Case","z":5,"b":[-99.471,43.059,-98.56,43.991],"p":[[[-99.147,43.433,-99.04,43.358,-98.905,43.285,-98.831,43.206,-98.706,43.135,-98.571,43.102,-98.56,43.06,-98.666,43.059,-98.747,43.102,-98.9,43.159,-98.93,43.226,-99.069,43.31,-99.178,43.374,-99.315,43.468,-99.344,43.539,-99.383,43.606,-99.471,43.669,-99.441,43.752,-99.365,43.843,-99.352,43.888,-99.374,43.947,-99.364,43.991,-99.322,43.957,-99.303,43.903,-99.315,43.841,-99.37,43.757,-99.411,43.703,-99.35,43.643,-99.244,43.477,-99.147,43.433]]]},{"n":"Falcon International","z":5,"b":[-99.464,26.562,-99.113,27.057],"p":[[[-99.184,26.76,-99.15,26.751,-99.119,26.749,-99.113,26.727,-99.149,26.652,-99.15,26.592,-99.172,26.564,-99.2,26.562,-99.243,26.584,-99.255,26.67,-99.283,26.76,-99.331,26.803,-99.394,26.795,-99.386,26.807,-99.364,26.821,-99.368,26.886,-99.401,26.954,-99.464,27.025,-99.457,27.057,-99.415,27.044,-99.369,27.002,-99.277,26.921,-99.218,26.91,-99.218,26.805,-99.184,26.76]]]},{"n":"Île-à-la-Crosse","z":5,"b":[-108.044,55.211,-107.666,55.921],"p":[[[-107.789,55.211,-107.843,55.303,-107.963,55.42,-107.999,55.471,-108.044,55.508,-108.036,55.554,-107.985,55.575,-107.85,55.528,-107.806,55.553,-107.851,55.618,-107.857,55.661,-107.805,55.665,-107.769,55.698,-107.767,55.802,-107.766,55.894,-107.737,55.921,-107.666,55.744,-107.706,55.582,-107.74,55.48,-107.74,55.408,-107.792,55.367,-107.769,55.267,-107.789,55.211]]]},{"n":"Wholdaia","z":5,"b":[-104.643,60.341,-103.647,61.026],"p":[[[-103.836,60.554,-103.907,60.505,-103.935,60.477,-103.949,60.421,-104.117,60.355,-104.187,60.341,-104.152,60.425,-104.135,60.533,-104.229,60.587,-104.332,60.633,-104.395,60.685,-104.468,60.703,-104.494,60.726,-104.39,60.809,-104.343,60.871,-104.38,60.901,-104.425,60.885,-104.44,60.852,-104.505,60.818,-104.588,60.813,-104.637,60.835,-104.643,60.856,-104.53,60.86,-104.509,60.883,-104.45,60.973,-104.449,61.001,-104.419,61.022,-104.335,61.026,-104.297,61.005,-104.281,60.981,-104.213,60.938,-104.212,60.903,-104.23,60.846,-104.219,60.814,-104.141,60.826,-104.139,60.805,-104.219,60.757,-104.217,60.738,-104.124,60.752,-104.073,60.781,-104.02,60.793,-103.983,60.785,-103.851,60.824,-103.769,60.866,-103.752,60.909,-103.742,60.945,-103.716,60.952,-103.647,60.902,-103.667,60.835,-103.813,60.785,-103.97,60.707,-104.082,60.693,-104.137,60.668,-104.123,60.624,-104.042,60.602,-103.937,60.614,-103.879,60.641,-103.859,60.677,-103.81,60.71,-103.746,60.701,-103.736,60.642,-103.836,60.554]]]},{"n":"Tehek","z":5,"b":[-96.032,64.762,-95.249,65.042],"p":[[[-95.249,65.003,-95.39,64.972,-95.463,64.921,-95.465,64.859,-95.456,64.814,-95.459,64.789,-95.487,64.766,-95.55,64.762,-95.593,64.782,-95.62,64.806,-95.625,64.826,-95.636,64.846,-95.712,64.874,-95.799,64.923,-95.858,64.915,-95.87,64.871,-95.881,64.851,-95.993,64.891,-96.032,64.923,-96.003,64.974,-95.931,65.003,-95.855,65.011,-95.726,65.022,-95.632,65.039,-95.503,65.042,-95.25,65.034,-95.249,65.003]]]},{"n":"Caniapiscau","z":5,"b":[-70.141,53.946,-69.269,54.379],"p":[[[-69.351,54.083,-69.269,54.047,-69.32,54.025,-69.412,54.036,-69.502,54.019,-69.595,53.998,-69.722,53.961,-69.782,53.947,-69.834,53.946,-69.869,53.955,-69.856,53.977,-69.827,53.999,-69.741,54.024,-69.703,54.038,-69.707,54.069,-69.758,54.078,-69.847,54.101,-69.983,54.139,-70.055,54.134,-70.129,54.11,-70.141,54.129,-70.084,54.178,-70.011,54.223,-69.98,54.237,-69.926,54.252,-69.879,54.261,-69.869,54.289,-69.88,54.311,-69.927,54.32,-69.979,54.318,-70.001,54.325,-70.029,54.337,-70.071,54.337,-70.082,54.343,-70.041,54.372,-69.971,54.365,-69.901,54.379,-69.858,54.368,-69.804,54.312,-69.793,54.283,-69.682,54.268,-69.687,54.24,-69.671,54.225,-69.664,54.21,-69.704,54.172,-69.697,54.153,-69.588,54.107,-69.477,54.092,-69.433,54.111,-69.406,54.121,-69.351,54.083]]]},{"n":"Bermen","z":5,"b":[-69.243,53.428,-68.68,54.042],"p":[[[-68.68,53.798,-68.705,53.775,-68.726,53.689,-68.707,53.61,-68.716,53.583,-68.777,53.577,-68.853,53.594,-68.869,53.576,-68.869,53.523,-68.9,53.483,-68.935,53.469,-68.976,53.456,-69.055,53.428,-69.085,53.444,-69.045,53.492,-68.984,53.559,-68.947,53.609,-68.868,53.645,-68.827,53.676,-68.837,53.717,-68.866,53.737,-68.85,53.755,-68.805,53.765,-68.781,53.775,-68.767,53.796,-68.814,53.846,-68.907,53.876,-68.967,53.908,-69.008,53.94,-69.061,53.958,-69.225,53.949,-69.243,53.961,-69.231,53.979,-69.2,53.989,-69.171,53.99,-69.154,54,-69.16,54.023,-69.146,54.04,-69.116,54.023,-69.062,54.007,-69.022,54.028,-69.005,54.042,-68.979,54.011,-68.949,54,-68.903,53.985,-68.864,53.952,-68.846,53.911,-68.733,53.836,-68.685,53.812,-68.68,53.798]]]},{"n":"Caniapiscau","z":5,"b":[-70.517,54.359,-69.766,54.742],"p":[[[-70.233,54.444,-70.274,54.436,-70.312,54.408,-70.419,54.359,-70.497,54.407,-70.517,54.441,-70.46,54.432,-70.424,54.41,-70.357,54.429,-70.214,54.479,-70.095,54.473,-69.97,54.49,-69.932,54.516,-69.912,54.552,-69.912,54.617,-69.92,54.654,-69.96,54.684,-70.029,54.676,-70.008,54.722,-69.983,54.742,-69.935,54.732,-69.9,54.704,-69.86,54.622,-69.84,54.556,-69.839,54.525,-69.774,54.492,-69.766,54.466,-69.882,54.456,-69.949,54.425,-69.988,54.425,-70.015,54.438,-70.155,54.436,-70.233,54.444]]]},{"n":"Kentucky","z":5,"b":[-88.283,35.945,-87.91,37.022],"p":[[[-88.057,36.669,-88.023,36.528,-88.007,36.427,-87.956,36.303,-87.91,36.187,-87.934,36.074,-87.975,35.945,-88.041,35.949,-87.983,36.099,-87.991,36.234,-88.039,36.307,-88.066,36.314,-88.092,36.26,-88.116,36.293,-88.119,36.355,-88.086,36.447,-88.084,36.555,-88.126,36.655,-88.272,36.951,-88.283,37.022,-88.207,36.971,-88.057,36.669]]]},{"n":"Strom Thurmond","z":5,"b":[-82.589,33.641,-82.188,34.017],"p":[[[-82.411,33.954,-82.381,33.927,-82.327,33.847,-82.247,33.774,-82.188,33.694,-82.208,33.664,-82.332,33.672,-82.468,33.641,-82.575,33.643,-82.541,33.662,-82.439,33.671,-82.275,33.725,-82.308,33.776,-82.419,33.846,-82.491,33.898,-82.521,33.893,-82.555,33.879,-82.579,33.865,-82.553,33.937,-82.577,33.956,-82.589,34.017,-82.546,34.009,-82.485,33.966,-82.441,33.938,-82.411,33.954]]]},{"n":"Kinbasket","z":5,"b":[-119.128,52.06,-118.18,52.739],"p":[[[-118.402,52.172,-118.287,52.159,-118.18,52.178,-118.218,52.129,-118.322,52.103,-118.366,52.09,-118.392,52.069,-118.448,52.06,-118.499,52.069,-118.527,52.102,-118.551,52.241,-118.642,52.332,-118.751,52.404,-118.89,52.549,-119.128,52.739,-119.058,52.727,-118.931,52.618,-118.677,52.411,-118.565,52.303,-118.402,52.172]]]},{"n":"George","z":5,"b":[-81.684,29.198,-81.529,29.505],"p":[[[-81.684,29.368,-81.683,29.505,-81.64,29.414,-81.54,29.287,-81.529,29.224,-81.595,29.198,-81.64,29.258,-81.663,29.314,-81.684,29.368]]]},{"n":"Havasu","z":5,"b":[-114.405,34.242,-114.082,34.54],"p":[[[-114.359,34.438,-114.405,34.488,-114.377,34.54,-114.327,34.478,-114.221,34.421,-114.082,34.318,-114.134,34.242,-114.164,34.253,-114.189,34.318,-114.261,34.368,-114.359,34.438]]]},{"n":"Shasta","z":5,"b":[-122.426,40.721,-122.095,40.871],"p":[[[-122.232,40.871,-122.242,40.806,-122.229,40.793,-122.116,40.8,-122.095,40.783,-122.102,40.766,-122.151,40.746,-122.241,40.754,-122.364,40.724,-122.411,40.721,-122.426,40.744,-122.417,40.768,-122.395,40.829,-122.362,40.82,-122.351,40.784,-122.301,40.804,-122.259,40.861,-122.232,40.871]]]},{"n":"Point","z":5,"b":[-114.442,64.952,-112.217,65.677],"p":[[[-114.098,65.378,-114.185,65.446,-114.442,65.477,-114.405,65.561,-114.381,65.616,-114.397,65.657,-114.229,65.677,-114.173,65.638,-114.183,65.611,-114.298,65.6,-114.287,65.555,-114.222,65.522,-114.169,65.496,-114.077,65.476,-114.001,65.448,-113.937,65.444,-113.93,65.425,-113.91,65.402,-113.84,65.368,-113.621,65.416,-113.517,65.385,-113.401,65.382,-113.271,65.401,-113.113,65.385,-113.125,65.361,-113.224,65.356,-113.316,65.332,-113.312,65.318,-113.272,65.306,-113.125,65.292,-112.871,65.242,-112.678,65.203,-112.55,65.207,-112.462,65.22,-112.427,65.221,-112.294,65.22,-112.217,65.225,-112.222,65.192,-112.307,65.183,-112.346,65.128,-112.29,65.038,-112.242,64.966,-112.288,64.952,-112.318,64.96,-112.35,65.013,-112.417,65.104,-112.457,65.153,-112.528,65.16,-112.65,65.16,-112.822,65.19,-112.884,65.203,-112.939,65.213,-112.997,65.196,-113.063,65.216,-113.324,65.26,-113.507,65.272,-113.618,65.253,-113.787,65.244,-113.95,65.335,-114.055,65.361,-114.098,65.378]]]},{"n":"Upper Klamath","z":5,"b":[-122.06,42.251,-121.806,42.604],"p":[[[-121.941,42.604,-121.948,42.499,-121.921,42.475,-121.828,42.409,-121.806,42.289,-121.809,42.251,-121.842,42.256,-121.922,42.332,-121.921,42.36,-121.957,42.396,-122.042,42.428,-122.06,42.475,-122.017,42.523,-121.965,42.59,-121.941,42.604]]]},{"n":"Harney","z":5,"b":[-119.205,43.16,-119.005,43.308],"p":[[[-119.195,43.229,-119.205,43.256,-119.183,43.282,-119.133,43.308,-119.019,43.259,-119.005,43.239,-119.023,43.197,-119.081,43.16,-119.163,43.184,-119.195,43.229]]]},{"n":"Chiputneticook Lakes","z":5,"b":[-67.863,45.589,-67.449,45.759],"p":[[[-67.708,45.692,-67.6,45.637,-67.533,45.643,-67.48,45.683,-67.461,45.642,-67.449,45.607,-67.512,45.589,-67.625,45.602,-67.779,45.67,-67.863,45.74,-67.787,45.759,-67.708,45.692]]]},{"n":"Tucuruí Dam","z":5,"b":[-49.865,-5.388,-48.937,-3.835],"p":[[[-49.709,-4.422,-49.81,-4.409,-49.826,-4.391,-49.865,-4.321,-49.859,-4.298,-49.815,-4.26,-49.816,-4.194,-49.809,-4.175,-49.753,-4.115,-49.749,-4.058,-49.719,-4.028,-49.716,-3.958,-49.715,-3.855,-49.687,-3.835,-49.626,-3.848,-49.592,-3.958,-49.543,-4.125,-49.524,-4.226,-49.524,-4.257,-49.499,-4.283,-49.448,-4.36,-49.429,-4.454,-49.435,-4.49,-49.35,-4.602,-49.298,-4.679,-49.316,-4.725,-49.283,-4.8,-49.29,-4.836,-49.347,-4.881,-49.338,-4.945,-49.281,-5.165,-49.176,-5.275,-48.962,-5.276,-48.937,-5.303,-48.971,-5.333,-49.17,-5.388,-49.281,-5.301,-49.351,-5.183,-49.415,-5.044,-49.532,-4.846,-49.549,-4.74,-49.592,-4.652,-49.677,-4.617,-49.713,-4.593,-49.675,-4.546,-49.653,-4.484,-49.675,-4.437,-49.709,-4.422]]]},{"n":"Três Marias Dam","z":5,"b":[-45.516,-19.051,-44.903,-18.201],"p":[[[-44.972,-18.782,-44.942,-18.794,-44.932,-18.824,-44.903,-18.841,-44.92,-18.857,-44.967,-18.85,-45.044,-18.828,-45.118,-18.83,-45.129,-18.849,-45.121,-18.887,-45.077,-19.005,-45.089,-19.051,-45.112,-19.029,-45.156,-18.948,-45.199,-18.957,-45.217,-18.962,-45.31,-18.974,-45.397,-19.007,-45.396,-18.939,-45.386,-18.923,-45.251,-18.909,-45.209,-18.882,-45.195,-18.831,-45.214,-18.753,-45.236,-18.758,-45.247,-18.838,-45.281,-18.853,-45.34,-18.829,-45.34,-18.801,-45.29,-18.764,-45.258,-18.678,-45.255,-18.623,-45.283,-18.625,-45.359,-18.66,-45.372,-18.636,-45.349,-18.621,-45.331,-18.603,-45.298,-18.541,-45.318,-18.501,-45.367,-18.488,-45.4,-18.524,-45.429,-18.556,-45.476,-18.564,-45.516,-18.586,-45.507,-18.545,-45.479,-18.474,-45.434,-18.433,-45.355,-18.395,-45.353,-18.359,-45.328,-18.303,-45.329,-18.273,-45.379,-18.268,-45.36,-18.239,-45.287,-18.201,-45.262,-18.204,-45.23,-18.231,-45.253,-18.296,-45.285,-18.414,-45.259,-18.474,-45.212,-18.489,-45.192,-18.514,-45.185,-18.569,-45.181,-18.607,-45.148,-18.598,-45.133,-18.608,-45.136,-18.64,-45.156,-18.686,-45.152,-18.735,-45.097,-18.775,-45.036,-18.759,-45.017,-18.723,-45,-18.739,-44.989,-18.769,-44.972,-18.782]]]},{"n":"Engineer Souza Dias Dam","z":5,"b":[-51.438,-20.529,-50.582,-19.668],"p":[[[-51.416,-20.426,-51.423,-20.397,-51.413,-20.373,-51.396,-20.355,-51.438,-20.284,-51.427,-20.257,-51.381,-20.277,-51.314,-20.281,-51.272,-20.255,-51.181,-20.257,-51.156,-20.24,-51.172,-20.185,-51.15,-20.147,-51.104,-20.179,-51.078,-20.175,-51.05,-20.1,-51.049,-20.013,-51.051,-19.781,-51.03,-19.718,-50.997,-19.668,-50.97,-19.727,-50.955,-19.912,-50.949,-19.981,-50.807,-19.932,-50.668,-19.841,-50.582,-19.82,-50.669,-19.947,-50.792,-19.995,-50.844,-20.032,-50.936,-20.09,-50.985,-20.17,-50.978,-20.232,-50.934,-20.265,-50.913,-20.319,-50.983,-20.307,-51.036,-20.329,-51.09,-20.338,-51.112,-20.337,-51.148,-20.351,-51.207,-20.368,-51.245,-20.392,-51.177,-20.458,-51.065,-20.513,-51.066,-20.529,-51.197,-20.499,-51.305,-20.441,-51.363,-20.452,-51.397,-20.441,-51.416,-20.426]]]},{"n":"","z":5,"b":[-72.609,-40.704,-72.365,-40.617],"p":[[[-72.609,-40.658,-72.541,-40.617,-72.391,-40.622,-72.365,-40.639,-72.37,-40.663,-72.464,-40.704,-72.57,-40.69,-72.609,-40.658]]]},{"n":"Colhué Huapí","z":5,"b":[-68.976,-45.657,-68.566,-45.264],"p":[[[-68.676,-45.422,-68.664,-45.461,-68.641,-45.512,-68.601,-45.55,-68.574,-45.568,-68.566,-45.607,-68.599,-45.645,-68.708,-45.657,-68.848,-45.644,-68.923,-45.529,-68.966,-45.447,-68.976,-45.324,-68.97,-45.282,-68.934,-45.264,-68.875,-45.294,-68.859,-45.388,-68.836,-45.413,-68.802,-45.413,-68.766,-45.365,-68.702,-45.369,-68.676,-45.422]]]},{"n":"","z":5,"b":[8.881,47.656,9.128,47.752],"p":[[[9.128,47.671,8.881,47.656,8.948,47.686,8.981,47.702,8.977,47.73,8.999,47.752,9.094,47.716,9.128,47.671]]]},{"n":"","z":5,"b":[36.768,62.179,37.035,62.466],"p":[[[36.867,62.198,36.827,62.223,36.776,62.239,36.768,62.26,36.784,62.291,36.802,62.364,36.813,62.43,36.847,62.459,36.909,62.466,37.015,62.428,37.035,62.368,37.012,62.295,37.025,62.262,36.983,62.211,36.898,62.179,36.867,62.198]]]},{"n":"","z":5,"b":[38.844,60.394,39.188,60.777],"p":[[[38.909,60.586,38.892,60.585,38.89,60.664,38.844,60.734,38.866,60.777,38.938,60.767,39.085,60.718,39.173,60.68,39.185,60.617,39.188,60.496,39.15,60.442,39.096,60.394,39.031,60.395,39.028,60.411,39.042,60.438,39.069,60.475,39.054,60.523,39.008,60.573,38.958,60.595,38.909,60.586]]]},{"n":"","z":5,"b":[31.346,64.405,32.044,64.557],"p":[[[31.545,64.435,31.346,64.465,31.37,64.5,31.473,64.482,31.594,64.477,31.648,64.475,31.743,64.477,31.79,64.495,31.783,64.524,31.765,64.557,31.821,64.553,31.898,64.515,32.015,64.495,32.044,64.46,32.002,64.405,31.873,64.416,31.545,64.435]]]},{"n":"Imandra","z":5,"b":[31.899,67.415,32.406,67.544],"p":[[[32.406,67.457,32.342,67.442,32.246,67.415,32.125,67.434,31.899,67.512,31.944,67.544,32.071,67.543,32.216,67.539,32.342,67.487,32.406,67.457]]]},{"n":"","z":5,"b":[34.995,67.707,35.297,68.1],"p":[[[35.159,67.811,35.136,67.782,35.126,67.751,35.08,67.707,34.995,67.801,35.055,67.827,35.129,67.875,35.158,67.906,35.138,67.926,35.092,67.94,35.099,67.979,35.221,68.059,35.247,68.1,35.291,68.078,35.297,68.006,35.243,67.956,35.256,67.923,35.26,67.88,35.222,67.845,35.159,67.811]]]},{"n":"Chardara Reservoir","z":5,"b":[67.933,41.048,68.456,41.255],"p":[[[67.991,41.255,68.162,41.2,68.243,41.189,68.393,41.161,68.456,41.107,68.403,41.078,68.28,41.052,68.135,41.048,68.024,41.099,67.933,41.225,67.991,41.255]]]},{"n":"Vilyuy","z":5,"b":[109.558,61.783,112.48,63.625],"p":[[[111.12,62.62,111.166,62.672,111.194,62.707,111.158,62.74,111.165,62.792,111.154,62.808,111.139,62.848,110.839,62.912,110.648,62.982,110.431,62.989,110.433,63.046,110.494,63.099,110.484,63.136,110.373,63.139,110.23,63.155,110.109,63.172,110.039,63.208,110.094,63.222,110.2,63.217,110.291,63.218,110.374,63.242,110.393,63.292,110.389,63.35,110.274,63.4,110.162,63.439,110.002,63.429,109.848,63.382,109.75,63.405,109.648,63.424,109.587,63.455,109.746,63.466,109.899,63.456,109.946,63.48,110.038,63.558,110.056,63.594,110.096,63.625,110.136,63.621,110.146,63.6,110.113,63.573,110.127,63.536,110.247,63.485,110.431,63.459,110.482,63.46,110.563,63.422,110.61,63.388,110.568,63.336,110.474,63.25,110.5,63.233,110.546,63.198,110.668,63.199,110.691,63.217,110.744,63.239,110.795,63.219,110.843,63.19,110.918,63.178,110.939,63.159,110.914,63.146,110.886,63.125,110.728,63.09,110.719,63.063,110.772,63.036,110.865,63.019,110.944,62.997,111.015,63.046,111.086,63.094,111.044,63.269,111.05,63.293,111.042,63.311,111.076,63.349,111.103,63.415,111.143,63.424,111.17,63.353,111.162,63.308,111.17,63.295,111.144,63.219,111.16,63.176,111.211,63.138,111.273,63.119,111.334,63.116,111.473,63.13,111.558,63.165,111.584,63.218,111.536,63.288,111.596,63.283,111.652,63.283,111.69,63.289,111.798,63.266,111.9,63.219,111.977,63.119,111.985,63.097,111.83,63.003,111.829,62.96,111.924,62.913,111.998,62.9,112.06,62.914,112.12,62.917,112.155,62.97,112.115,63.017,112.108,63.054,112.12,63.13,112.078,63.235,112.047,63.315,111.908,63.37,111.816,63.383,111.91,63.412,112.079,63.409,112.125,63.432,112.171,63.424,112.139,63.381,112.135,63.315,112.148,63.249,112.234,63.126,112.44,63.075,112.48,63.02,112.4,63,112.38,62.92,112.368,62.842,112.278,62.79,112.175,62.765,112.106,62.783,112.049,62.818,111.915,62.849,111.846,62.873,111.777,62.862,111.729,62.846,111.693,62.864,111.631,62.863,111.581,62.858,111.488,62.873,111.435,62.894,111.399,62.919,111.408,62.94,111.47,62.944,111.521,62.953,111.561,62.968,111.633,62.973,111.657,63.041,111.774,63.07,111.839,63.121,111.844,63.16,111.786,63.196,111.709,63.187,111.64,63.143,111.592,63.077,111.518,63.047,111.416,63.028,111.318,63.028,111.207,63.005,111.191,62.94,111.293,62.883,111.345,62.847,111.376,62.805,111.388,62.723,111.424,62.653,111.39,62.611,111.317,62.55,111.359,62.494,111.358,62.472,111.294,62.46,111.293,62.431,111.328,62.397,111.489,62.402,111.605,62.39,111.64,62.361,111.666,62.331,111.663,62.311,111.504,62.349,111.39,62.327,111.372,62.283,111.604,62.214,111.551,62.187,111.483,62.196,111.423,62.219,111.35,62.223,111.286,62.191,111.296,62.131,111.399,62.058,111.251,61.99,111.046,61.99,110.9,62.011,110.618,62.064,110.554,62.079,110.417,62.093,110.35,62.11,110.303,62.134,110.16,62.124,110.016,62.078,109.827,61.994,109.665,61.911,109.635,61.836,109.601,61.783,109.56,61.799,109.568,61.878,109.558,61.9,109.604,61.942,109.637,62.007,109.627,62.053,109.714,62.039,109.816,62.055,109.932,62.103,110.145,62.204,110.433,62.194,110.606,62.131,110.674,62.121,110.757,62.099,110.81,62.111,110.887,62.084,110.937,62.086,110.947,62.083,110.973,62.061,111.088,62.051,111.156,62.11,111.123,62.164,111.12,62.221,111.209,62.256,111.243,62.309,111.235,62.375,111.209,62.431,111.137,62.483,111.012,62.504,110.916,62.488,110.875,62.468,110.859,62.478,110.852,62.499,110.784,62.528,110.851,62.546,110.979,62.54,111.051,62.544,111.09,62.57,111.12,62.62]]]},{"n":"","z":5,"b":[139.658,53.34,139.922,53.556],"p":[[[139.881,53.556,139.922,53.527,139.855,53.46,139.809,53.4,139.823,53.361,139.816,53.34,139.749,53.348,139.672,53.39,139.658,53.456,139.748,53.524,139.881,53.556]]]},{"n":"Krasnoye","z":5,"b":[174.207,64.433,174.804,64.702],"p":[[[174.429,64.569,174.575,64.581,174.605,64.615,174.607,64.652,174.713,64.669,174.804,64.65,174.766,64.568,174.671,64.481,174.541,64.433,174.338,64.437,174.207,64.513,174.273,64.608,174.352,64.676,174.373,64.702,174.378,64.66,174.352,64.594,174.429,64.569]]]},{"n":"Biwa","z":5,"b":[135.889,35,136.268,35.466],"p":[[[136.058,35.387,136.083,35.443,136.137,35.466,136.182,35.453,136.225,35.407,136.268,35.347,136.214,35.264,135.981,35.098,135.922,35.026,135.889,35,135.892,35.093,135.969,35.203,136.032,35.304,136.058,35.387]]]},{"n":"Aishihik","z":5,"b":[-137.522,61.227,-136.989,61.652],"p":[[[-137.148,61.503,-137.096,61.457,-137.075,61.386,-137.042,61.31,-136.992,61.273,-136.989,61.235,-137,61.227,-137.012,61.235,-137.033,61.262,-137.083,61.292,-137.168,61.45,-137.332,61.542,-137.522,61.636,-137.442,61.652,-137.331,61.612,-137.263,61.577,-137.148,61.503]]]},{"n":"","z":5,"b":[-156.409,58.515,-155.435,58.729],"p":[[[-155.453,58.637,-155.576,58.617,-155.655,58.602,-155.646,58.576,-155.496,58.548,-155.473,58.53,-155.52,58.515,-155.653,58.52,-155.748,58.555,-155.841,58.597,-156.028,58.6,-156.162,58.605,-156.323,58.629,-156.38,58.665,-156.409,58.691,-156.343,58.709,-156.198,58.712,-156.197,58.724,-156.153,58.729,-156.09,58.713,-156.037,58.7,-156.006,58.688,-155.838,58.683,-155.56,58.663,-155.435,58.66,-155.453,58.637]]]},{"n":"","z":5,"b":[-163.031,61.041,-162.713,61.109],"p":[[[-162.838,61.041,-163.031,61.083,-163.002,61.109,-162.801,61.081,-162.713,61.077,-162.838,61.041]]]},{"n":"Clark","z":5,"b":[-154.752,60.04,-153.627,60.436],"p":[[[-154.473,60.222,-154.312,60.279,-154.089,60.34,-153.689,60.431,-153.627,60.436,-153.659,60.404,-154.056,60.301,-154.26,60.23,-154.549,60.113,-154.72,60.04,-154.752,60.07,-154.642,60.143,-154.573,60.183,-154.541,60.189,-154.473,60.222]]]},{"n":"","z":5,"b":[-151.166,60.048,-150.654,60.305],"p":[[[-151.166,60.227,-151.149,60.253,-151.065,60.305,-150.894,60.272,-150.717,60.159,-150.654,60.121,-150.681,60.048,-150.716,60.052,-150.767,60.082,-150.831,60.128,-150.937,60.168,-151.021,60.206,-151.06,60.218,-151.166,60.227]]]},{"n":"","z":5,"b":[-160.131,61.34,-159.934,61.422],"p":[[[-160.002,61.35,-160.089,61.34,-160.131,61.358,-160.103,61.406,-160.038,61.422,-159.971,61.402,-159.934,61.367,-160.002,61.35]]]},{"n":"","z":5,"b":[-162.397,60.821,-162.204,60.99],"p":[[[-162.307,60.951,-162.297,60.918,-162.265,60.89,-162.204,60.853,-162.242,60.827,-162.275,60.821,-162.37,60.83,-162.396,60.899,-162.397,60.95,-162.362,60.99,-162.307,60.951]]]},{"n":"","z":5,"b":[-163.973,61.472,-163.638,61.648],"p":[[[-163.706,61.562,-163.638,61.519,-163.699,61.472,-163.729,61.523,-163.875,61.578,-163.959,61.619,-163.973,61.645,-163.931,61.648,-163.783,61.602,-163.706,61.562]]]},{"n":"","z":5,"b":[-163.619,62.574,-163.41,62.71],"p":[[[-163.459,62.671,-163.5,62.652,-163.524,62.574,-163.582,62.6,-163.619,62.675,-163.565,62.686,-163.456,62.71,-163.41,62.679,-163.459,62.671]]]},{"n":"Wheeler","z":5,"b":[-87.7,34.565,-86.899,34.851],"p":[[[-87.661,34.814,-87.542,34.851,-87.383,34.851,-87.268,34.808,-87.006,34.683,-86.935,34.61,-86.899,34.575,-86.959,34.565,-87.323,34.767,-87.512,34.792,-87.637,34.765,-87.7,34.773,-87.661,34.814]]]},{"n":"Üüreg","z":5,"b":[90.891,50.08,91.131,50.214],"p":[[[91.052,50.208,91.117,50.175,91.131,50.13,91.078,50.099,90.961,50.08,90.891,50.136,90.942,50.214,91.052,50.208]]]},{"n":"","z":5,"b":[90.006,33.325,90.401,33.702],"p":[[[90.401,33.526,90.394,33.436,90.322,33.363,90.247,33.325,90.101,33.377,90.033,33.385,90.006,33.437,90.047,33.445,90.186,33.416,90.29,33.475,90.282,33.569,90.289,33.638,90.319,33.702,90.401,33.526]]]},{"n":"","z":5,"b":[89.71,33.256,89.97,33.523],"p":[[[89.71,33.469,89.771,33.469,89.832,33.48,89.876,33.505,89.935,33.523,89.97,33.48,89.919,33.376,89.89,33.329,89.83,33.256,89.806,33.286,89.786,33.417,89.734,33.447,89.71,33.469]]]},{"n":"","z":5,"b":[75.936,31.9,76.171,32.153],"p":[[[76.169,31.9,76.082,31.909,75.975,31.972,75.938,32.061,75.936,32.139,75.983,32.153,76.095,32.073,76.171,31.962,76.169,31.9]]]},{"n":"","z":5,"b":[91.424,31.943,91.532,32.133],"p":[[[91.469,31.947,91.437,31.991,91.424,32.062,91.455,32.112,91.511,32.133,91.532,32.08,91.529,31.966,91.518,31.943,91.469,31.947]]]},{"n":"","z":5,"b":[87.415,31.847,87.596,31.969],"p":[[[87.415,31.852,87.424,31.912,87.49,31.954,87.546,31.969,87.591,31.948,87.596,31.903,87.53,31.847,87.415,31.852]]]},{"n":"Pangong Tso","z":5,"b":[78.443,33.677,78.976,33.974],"p":[[[78.443,33.939,78.49,33.974,78.658,33.816,78.758,33.747,78.961,33.741,78.976,33.723,78.914,33.697,78.734,33.677,78.553,33.851,78.443,33.939]]]},{"n":"Tengiz","z":5,"b":[68.69,50.179,69.617,50.719],"p":[[[69.288,50.688,69.371,50.719,69.516,50.693,69.617,50.676,69.613,50.615,69.5,50.591,69.39,50.599,69.335,50.604,69.275,50.588,69.109,50.46,69.047,50.426,69.036,50.397,69.057,50.364,69.237,50.354,69.259,50.311,69.256,50.254,69.2,50.254,69.148,50.241,69.074,50.201,68.938,50.179,68.778,50.228,68.69,50.266,68.719,50.33,68.759,50.438,68.779,50.513,68.861,50.571,68.993,50.564,69.091,50.564,69.227,50.616,69.288,50.688]]]},{"n":"Kushmurun","z":5,"b":[64.501,52.516,64.982,52.908],"p":[[[64.574,52.605,64.685,52.687,64.79,52.768,64.884,52.856,64.949,52.908,64.982,52.883,64.967,52.731,64.89,52.672,64.794,52.624,64.765,52.613,64.722,52.594,64.649,52.553,64.515,52.516,64.501,52.535,64.574,52.605]]]},{"n":"Femund","z":5,"b":[11.742,61.929,11.797,62.052],"p":[[[11.797,61.939,11.753,61.929,11.742,62.008,11.778,62.052,11.797,61.939]]]},{"n":"Femund","z":5,"b":[11.721,61.931,11.967,62.421],"p":[[[11.846,62.212,11.893,62.3,11.892,62.4,11.922,62.421,11.967,62.407,11.95,62.31,11.929,62.205,11.9,62.144,11.885,62.089,11.902,62.026,11.927,61.931,11.883,61.931,11.843,62.046,11.721,62.103,11.736,62.138,11.766,62.154,11.846,62.212]]]},{"n":"","z":5,"b":[-77.026,51.288,-76.554,51.416],"p":[[[-76.677,51.373,-76.554,51.364,-76.563,51.344,-76.751,51.288,-77.026,51.329,-76.983,51.343,-76.903,51.355,-76.862,51.38,-76.84,51.404,-76.819,51.416,-76.762,51.391,-76.677,51.373]]]},{"n":"","z":5,"b":[-91.647,51.071,-91.39,51.166],"p":[[[-91.418,51.12,-91.39,51.071,-91.542,51.085,-91.591,51.118,-91.647,51.154,-91.609,51.166,-91.504,51.162,-91.441,51.163,-91.417,51.143,-91.418,51.12]]]},{"n":"","z":5,"b":[-91.991,51.894,-91.895,51.982],"p":[[[-91.97,51.958,-91.95,51.982,-91.895,51.952,-91.91,51.911,-91.932,51.894,-91.968,51.924,-91.991,51.937,-91.97,51.958]]]},{"n":"","z":5,"b":[-78.827,51.034,-78.55,51.152],"p":[[[-78.616,51.077,-78.55,51.034,-78.813,51.097,-78.827,51.152,-78.794,51.146,-78.72,51.109,-78.616,51.077]]]},{"n":"","z":5,"b":[-78.813,47.57,-78.571,47.691],"p":[[[-78.628,47.597,-78.696,47.577,-78.763,47.57,-78.787,47.595,-78.798,47.626,-78.813,47.656,-78.792,47.691,-78.724,47.684,-78.612,47.681,-78.571,47.656,-78.584,47.625,-78.604,47.608,-78.628,47.597]]]},{"n":"Dozois","z":5,"b":[-77.322,47.347,-76.935,47.614],"p":[[[-77.273,47.591,-77.235,47.55,-77.177,47.547,-77.129,47.561,-77.102,47.545,-77.065,47.508,-76.963,47.502,-76.935,47.488,-76.966,47.442,-77.119,47.477,-77.185,47.456,-77.179,47.412,-77.165,47.389,-77.181,47.369,-77.259,47.347,-77.289,47.386,-77.27,47.463,-77.304,47.513,-77.322,47.573,-77.304,47.607,-77.288,47.614,-77.273,47.591]]]},{"n":"","z":5,"b":[-108.09,64.697,-107.979,64.749],"p":[[[-108.09,64.715,-108.085,64.737,-108.044,64.749,-108.016,64.745,-107.979,64.72,-107.987,64.71,-108.063,64.697,-108.09,64.715]]]},{"n":"","z":5,"b":[-108.533,64.559,-108.18,64.686],"p":[[[-108.189,64.646,-108.209,64.634,-108.24,64.625,-108.481,64.559,-108.533,64.562,-108.494,64.598,-108.329,64.654,-108.24,64.684,-108.18,64.686,-108.189,64.646]]]},{"n":"","z":5,"b":[-109.1,64.552,-108.821,64.727],"p":[[[-108.929,64.6,-108.989,64.613,-109.056,64.594,-109.09,64.587,-109.1,64.603,-109.098,64.656,-109.061,64.686,-109.071,64.711,-109.062,64.727,-108.93,64.706,-108.893,64.664,-108.852,64.606,-108.821,64.552,-108.854,64.561,-108.929,64.6]]]},{"n":"","z":5,"b":[-109.362,64.536,-109.149,64.758],"p":[[[-109.251,64.594,-109.282,64.608,-109.34,64.655,-109.362,64.702,-109.36,64.734,-109.347,64.758,-109.291,64.758,-109.24,64.74,-109.256,64.727,-109.284,64.702,-109.272,64.662,-109.149,64.58,-109.16,64.547,-109.224,64.536,-109.273,64.536,-109.235,64.565,-109.251,64.594]]]},{"n":"","z":5,"b":[-79.367,71.089,-78.671,71.178],"p":[[[-78.866,71.16,-78.792,71.146,-78.671,71.126,-78.671,71.112,-78.699,71.1,-78.896,71.089,-79.211,71.097,-79.339,71.122,-79.367,71.149,-79.326,71.17,-79.277,71.175,-79.09,71.178,-78.88,71.173,-78.866,71.16]]]},{"n":"","z":5,"b":[-104.862,69.718,-104.358,69.858],"p":[[[-104.511,69.718,-104.646,69.733,-104.862,69.856,-104.762,69.858,-104.482,69.84,-104.422,69.83,-104.358,69.815,-104.388,69.756,-104.511,69.718]]]},{"n":"","z":5,"b":[-98.552,67.192,-98.229,67.421],"p":[[[-98.382,67.384,-98.302,67.35,-98.229,67.332,-98.272,67.289,-98.421,67.192,-98.491,67.206,-98.546,67.243,-98.552,67.272,-98.523,67.351,-98.486,67.421,-98.435,67.414,-98.382,67.384]]]},{"n":"","z":5,"b":[-96.285,55.212,-95.676,55.401],"p":[[[-96.285,55.238,-96.205,55.271,-96.055,55.287,-95.853,55.361,-95.789,55.4,-95.702,55.401,-95.676,55.364,-95.733,55.308,-95.83,55.28,-96.106,55.217,-96.229,55.212,-96.285,55.238]]]},{"n":"","z":5,"b":[-102.378,54.449,-102.106,54.738],"p":[[[-102.363,54.578,-102.336,54.655,-102.287,54.715,-102.197,54.738,-102.116,54.708,-102.106,54.598,-102.128,54.494,-102.169,54.455,-102.256,54.449,-102.34,54.475,-102.378,54.516,-102.363,54.578]]]},{"n":"","z":5,"b":[-105.812,54.055,-105.553,54.509],"p":[[[-105.812,54.234,-105.728,54.414,-105.714,54.492,-105.683,54.509,-105.643,54.488,-105.568,54.459,-105.553,54.39,-105.57,54.327,-105.636,54.268,-105.673,54.222,-105.707,54.15,-105.714,54.086,-105.755,54.055,-105.805,54.078,-105.81,54.161,-105.812,54.234]]]},{"n":"","z":5,"b":[-71.125,53.407,-70.843,53.596],"p":[[[-70.932,53.519,-70.935,53.483,-70.904,53.458,-70.861,53.452,-70.843,53.427,-70.865,53.407,-70.98,53.431,-71.048,53.501,-71.125,53.565,-71.117,53.591,-71.019,53.596,-70.95,53.565,-70.932,53.519]]]},{"n":"","z":5,"b":[-61.297,52.315,-60.6,52.619],"p":[[[-60.904,52.55,-60.724,52.444,-60.708,52.409,-60.657,52.358,-60.604,52.329,-60.6,52.32,-60.648,52.315,-60.739,52.378,-60.79,52.424,-60.917,52.477,-60.968,52.516,-61.15,52.589,-61.297,52.603,-61.139,52.619,-60.904,52.55]]]},{"n":"","z":5,"b":[-112.343,64.638,-111.887,64.899],"p":[[[-111.996,64.66,-111.988,64.69,-112.151,64.78,-112.326,64.86,-112.343,64.899,-112.114,64.848,-111.887,64.701,-111.927,64.659,-111.966,64.638,-111.996,64.66]]]},{"n":"","z":5,"b":[-83.925,48.305,-83.429,48.506],"p":[[[-83.611,48.378,-83.688,48.339,-83.781,48.305,-83.832,48.315,-83.925,48.307,-83.854,48.337,-83.721,48.421,-83.64,48.427,-83.618,48.426,-83.573,48.424,-83.429,48.506,-83.468,48.434,-83.611,48.378]]]},{"n":"","z":5,"b":[-107.063,55.806,-106.88,55.927],"p":[[[-107.063,55.82,-107.059,55.855,-107.011,55.89,-106.947,55.921,-106.897,55.927,-106.88,55.908,-106.984,55.832,-106.973,55.815,-107.009,55.806,-107.063,55.82]]]},{"n":"","z":5,"b":[-106.551,55.631,-106.33,55.76],"p":[[[-106.472,55.631,-106.514,55.639,-106.524,55.657,-106.551,55.668,-106.539,55.689,-106.441,55.728,-106.344,55.76,-106.33,55.74,-106.359,55.691,-106.341,55.662,-106.386,55.648,-106.472,55.631]]]},{"n":"","z":5,"b":[-102.774,62.41,-102.551,62.762],"p":[[[-102.597,62.657,-102.58,62.605,-102.56,62.552,-102.551,62.499,-102.576,62.45,-102.67,62.418,-102.737,62.41,-102.677,62.56,-102.692,62.602,-102.769,62.669,-102.774,62.704,-102.694,62.762,-102.66,62.76,-102.597,62.657]]]},{"n":"","z":5,"b":[-103.478,61.366,-103.144,61.677],"p":[[[-103.447,61.366,-103.469,61.385,-103.478,61.431,-103.456,61.513,-103.342,61.562,-103.233,61.642,-103.21,61.677,-103.144,61.653,-103.191,61.588,-103.296,61.521,-103.392,61.395,-103.447,61.366]]]},{"n":"","z":5,"b":[-103.836,58.545,-103.6,58.702],"p":[[[-103.836,58.609,-103.798,58.669,-103.786,58.691,-103.663,58.702,-103.621,58.678,-103.6,58.661,-103.623,58.607,-103.668,58.545,-103.771,58.574,-103.819,58.583,-103.836,58.609]]]},{"n":"","z":5,"b":[-69.274,56.055,-68.795,56.65],"p":[[[-69.153,56.507,-69.123,56.332,-69.077,56.272,-68.808,56.089,-68.795,56.055,-69.121,56.248,-69.189,56.326,-69.195,56.427,-69.274,56.504,-69.219,56.563,-69.128,56.65,-69.136,56.606,-69.153,56.507]]]},{"n":"","z":5,"b":[-89.089,66.483,-88.792,66.633],"p":[[[-88.93,66.577,-89.089,66.483,-89.075,66.556,-88.96,66.607,-88.792,66.633,-88.82,66.596,-88.93,66.577]]]},{"n":"","z":5,"b":[-88.711,66.579,-88.091,66.714],"p":[[[-88.202,66.633,-88.091,66.605,-88.213,66.581,-88.284,66.634,-88.396,66.657,-88.547,66.64,-88.682,66.579,-88.71,66.597,-88.711,66.632,-88.658,66.665,-88.615,66.711,-88.567,66.714,-88.463,66.701,-88.341,66.705,-88.202,66.633]]]},{"n":"Kumpupintil","z":5.7,"b":[122.608,-23.685,122.947,-23.255],"p":[[[122.823,-23.685,122.768,-23.661,122.641,-23.538,122.608,-23.469,122.697,-23.322,122.703,-23.274,122.758,-23.264,122.879,-23.255,122.932,-23.277,122.947,-23.434,122.926,-23.591,122.876,-23.662,122.823,-23.685]]]},{"n":"Mackay","z":5.7,"b":[128.317,-22.678,129.219,-22.002],"p":[[[128.601,-22.648,128.385,-22.598,128.317,-22.491,128.341,-22.404,128.429,-22.369,128.478,-22.345,128.599,-22.309,128.794,-22.22,128.908,-22.12,128.934,-22.064,129,-22.009,129.135,-22.002,129.216,-22.035,129.201,-22.085,129.072,-22.174,129.06,-22.271,129.142,-22.335,129.219,-22.366,129.215,-22.417,129.158,-22.448,129.099,-22.498,129.026,-22.563,128.899,-22.654,128.82,-22.678,128.601,-22.648]]]},{"n":"Gairdner","z":5.7,"b":[135.418,-32.326,136.312,-31.063],"p":[[[135.896,-32.326,135.89,-32.241,135.921,-32.15,136.045,-31.966,136.052,-31.902,135.992,-31.835,135.852,-31.743,135.812,-31.668,135.788,-31.617,135.749,-31.593,135.715,-31.597,135.691,-31.606,135.637,-31.59,135.611,-31.514,135.634,-31.445,135.638,-31.402,135.624,-31.382,135.595,-31.366,135.562,-31.354,135.522,-31.315,135.49,-31.28,135.439,-31.232,135.418,-31.096,135.429,-31.063,135.493,-31.084,135.605,-31.189,135.777,-31.238,135.886,-31.224,135.955,-31.225,136.08,-31.254,136.106,-31.305,136.104,-31.353,136.11,-31.415,136.145,-31.505,136.203,-31.577,136.275,-31.845,136.312,-31.926,136.253,-32.148,136.256,-32.211,136.238,-32.24,136.185,-32.253,136.123,-32.232,136.056,-32.209,135.975,-32.23,135.916,-32.318,135.896,-32.326]]]},{"n":"Torrens","z":5.7,"b":[137.212,-31.897,138.133,-30.179],"p":[[[137.83,-31.717,137.775,-31.864,137.748,-31.897,137.71,-31.877,137.613,-31.808,137.601,-31.778,137.672,-31.732,137.703,-31.664,137.733,-31.326,137.703,-31.273,137.652,-31.241,137.557,-31.156,137.531,-31.106,137.565,-31.082,137.632,-31.063,137.659,-31.027,137.662,-30.999,137.642,-30.832,137.621,-30.765,137.626,-30.673,137.595,-30.633,137.427,-30.629,137.395,-30.573,137.383,-30.471,137.328,-30.365,137.256,-30.288,137.212,-30.223,137.227,-30.179,137.323,-30.217,137.421,-30.307,137.482,-30.383,137.602,-30.427,137.746,-30.494,137.84,-30.597,137.928,-30.717,138.002,-30.874,138.03,-30.951,138.02,-31.003,138.026,-31.06,138.133,-31.259,138.111,-31.393,137.83,-31.717]]]},{"n":"Barlee","z":6,"b":[119.129,-29.58,120.006,-28.882],"p":[[[119.798,-29.505,119.786,-29.58,119.728,-29.58,119.636,-29.55,119.662,-29.51,119.693,-29.452,119.666,-29.387,119.636,-29.338,119.665,-29.277,119.668,-29.21,119.629,-29.186,119.58,-29.188,119.548,-29.216,119.542,-29.379,119.517,-29.505,119.442,-29.554,119.382,-29.542,119.437,-29.236,119.38,-29.214,119.33,-29.235,119.204,-29.351,119.142,-29.28,119.129,-29.114,119.226,-29.091,119.314,-29.012,119.398,-28.994,119.548,-29.057,119.65,-29.027,119.715,-28.936,119.764,-28.888,119.857,-28.899,119.938,-28.882,120.006,-28.928,119.993,-28.981,119.946,-28.985,119.877,-28.943,119.718,-29.067,119.683,-29.12,119.751,-29.297,119.79,-29.373,119.823,-29.413,119.798,-29.505]]]},{"n":"Frome","z":5.7,"b":[139.488,-31.126,140.044,-30.321],"p":[[[139.938,-30.964,139.92,-31.027,139.876,-31.084,139.835,-31.126,139.8,-31.124,139.744,-31.08,139.659,-31.033,139.613,-31.011,139.519,-30.925,139.488,-30.877,139.55,-30.774,139.719,-30.566,139.896,-30.374,139.951,-30.321,140.011,-30.341,140.044,-30.423,140.026,-30.524,139.982,-30.854,139.938,-30.964]]]},{"n":"Eyre South","z":6,"b":[137.111,-29.468,137.72,-29.131],"p":[[[137.661,-29.348,137.605,-29.371,137.522,-29.395,137.407,-29.444,137.374,-29.454,137.28,-29.449,137.189,-29.468,137.111,-29.449,137.119,-29.346,137.2,-29.245,137.331,-29.222,137.589,-29.159,137.698,-29.131,137.72,-29.197,137.661,-29.348]]]},{"n":"Eyre North","z":5.6,"b":[136.785,-29.1,138.014,-27.859],"p":[[[137.914,-29.1,137.777,-29.038,137.619,-29.051,137.478,-29.012,137.407,-29.029,137.344,-29.025,137.301,-28.988,137.239,-28.892,137.146,-28.887,137.125,-28.918,137.12,-28.978,137.075,-29.014,137.018,-29.015,136.949,-28.953,136.894,-28.832,136.917,-28.756,136.922,-28.689,136.883,-28.45,136.907,-28.426,136.966,-28.394,136.99,-28.314,136.969,-28.199,136.913,-28.103,136.889,-28.064,136.83,-28.044,136.785,-28.019,136.8,-27.967,136.891,-27.911,137.033,-27.859,137.328,-27.974,137.453,-28.087,137.566,-28.116,137.672,-28.12,137.702,-28.143,137.707,-28.196,137.72,-28.28,137.774,-28.35,137.733,-28.39,137.712,-28.537,137.68,-28.562,137.676,-28.645,137.786,-28.84,137.905,-28.985,138.014,-29.051,138.01,-29.091,137.914,-29.1]]]}]} \ No newline at end of file diff --git a/src/maps/data/places_110m.json b/src/maps/data/places_110m.json new file mode 100644 index 0000000..a042065 --- /dev/null +++ b/src/maps/data/places_110m.json @@ -0,0 +1 @@ +{"f":[{"n":"Vatican City","a":"Vatican","i":"VA","p":832,"la":41.903,"lo":12.453,"k":1,"z":7,"s":8},{"n":"San Marino","a":"San Marino","i":"SM","p":29579,"la":43.936,"lo":12.442,"k":1,"z":6.1,"s":7},{"n":"Vaduz","a":"Liechtenstein","i":"LI","p":36281,"la":47.134,"lo":9.517,"k":1,"z":6.7,"s":7},{"n":"Lobamba","a":"eSwatini","i":"SZ","p":9782,"la":-26.467,"lo":31.2,"k":0,"z":6,"s":6},{"n":"Luxembourg","a":"Luxembourg","i":"LU","p":107260,"la":49.612,"lo":6.13,"k":1,"z":6,"s":6},{"n":"Palikir","a":"Federated States of Micronesia","i":"FM","p":4645,"la":6.917,"lo":158.15,"k":1,"z":6,"s":6},{"n":"Majuro","a":"Marshall Islands","i":"MH","p":25400,"la":7.103,"lo":171.38,"k":1,"z":6,"s":6},{"n":"Funafuti","a":"Tuvalu","i":"TV","p":4749,"la":-8.517,"lo":179.217,"k":1,"z":6,"s":6},{"n":"Melekeok","a":"Palau","i":"PW","p":7026,"la":7.487,"lo":134.627,"k":1,"z":6,"s":6},{"n":"Bir Lehlou","a":"Western Sahara","i":"EH","p":500,"la":26.119,"lo":-9.653,"k":0,"z":6,"s":6},{"n":"Monaco","a":"Monaco","i":"MC","p":36371,"la":43.74,"lo":7.407,"k":1,"z":5.1,"s":6},{"n":"Tarawa","a":"Kiribati","i":"KI","p":28802,"la":1.338,"lo":173.018,"k":1,"z":6,"s":6},{"n":"Moroni","a":"Comoros","i":"KM","p":128698,"la":-11.704,"lo":43.24,"k":1,"z":6,"s":6},{"n":"Andorra","a":"Andorra","i":"AD","p":53998,"la":42.511,"lo":1.527,"k":1,"z":6,"s":6},{"n":"Port-of-Spain","a":"Trinidad and Tobago","i":"TT","p":294934,"la":10.652,"lo":-61.517,"k":1,"z":5.1,"s":4},{"n":"Kigali","a":"Rwanda","i":"RW","p":860000,"la":-1.952,"lo":30.059,"k":1,"z":5.1,"s":4},{"n":"Mbabane","a":"eSwatini","i":"SZ","p":90138,"la":-26.317,"lo":31.133,"k":1,"z":5.6,"s":4},{"n":"Juba","a":"South Sudan","i":"SS","p":111975,"la":4.83,"lo":31.58,"k":0,"z":5.1,"s":4},{"n":"The Hague","a":"Netherlands","i":"NL","p":1406000,"la":52.08,"lo":4.27,"k":0,"z":5.6,"s":4},{"n":"Ljubljana","a":"Slovenia","i":"SI","p":314807,"la":46.055,"lo":14.515,"k":1,"z":5.6,"s":4},{"n":"Bratislava","a":"Slovakia","i":"SK","p":423737,"la":48.15,"lo":17.117,"k":1,"z":5.1,"s":4},{"n":"Doha","a":"Qatar","i":"QA","p":1450000,"la":25.287,"lo":51.533,"k":1,"z":5.6,"s":4},{"n":"Podgorica","a":"Montenegro","i":"ME","p":145850,"la":42.466,"lo":19.266,"k":1,"z":5.6,"s":4},{"n":"Sri Jayawardenepura Kotte","a":"Sri Lanka","i":"LK","p":115826,"la":6.9,"lo":79.95,"k":0,"z":5.6,"s":4},{"n":"Baguio","a":"Philippines","i":"PH","p":447824,"la":16.43,"lo":120.57,"k":0,"z":5.6,"s":4},{"n":"Dodoma","a":"Tanzania","i":"TZ","p":218269,"la":-6.183,"lo":35.75,"k":0,"z":5.6,"s":4},{"n":"Bern","a":"Switzerland","i":"CH","p":275329,"la":46.917,"lo":7.467,"k":1,"z":5.6,"s":4},{"n":"Laayoune","a":"Morocco","i":"MA","p":188084,"la":27.15,"lo":-13.2,"k":0,"z":5.6,"s":4},{"n":"Pristina","a":"Kosovo","i":"-99","p":465186,"la":42.667,"lo":21.166,"k":1,"z":5.6,"s":4},{"n":"Roseau","a":"Dominica","i":"DM","p":23336,"la":15.301,"lo":-61.387,"k":1,"z":5.1,"s":4},{"n":"Djibouti","a":"Djibouti","i":"DJ","p":923000,"la":11.595,"lo":43.148,"k":1,"z":5.6,"s":4},{"n":"Putrajaya","a":"Malaysia","i":"MY","p":67964,"la":2.933,"lo":101.695,"k":0,"z":5.6,"s":4},{"n":"Kyoto","a":"Japan","i":"JP","p":1805000,"la":35.032,"lo":135.748,"k":0,"z":5.1,"s":4},{"n":"Banjul","a":"The Gambia","i":"GM","p":43094,"la":13.454,"lo":-16.592,"k":1,"z":5.6,"s":4},{"n":"Skopje","a":"North Macedonia","i":"MK","p":494087,"la":42,"lo":21.433,"k":1,"z":5.6,"s":4},{"n":"Bridgetown","a":"Barbados","i":"BB","p":191152,"la":13.102,"lo":-59.617,"k":1,"z":5.6,"s":4},{"n":"Porto-Novo","a":"Benin","i":"BJ","p":300000,"la":6.483,"lo":2.617,"k":0,"z":5.6,"s":4},{"n":"Bujumbura","a":"Burundi","i":"BI","p":331700,"la":-3.376,"lo":29.36,"k":1,"z":5.6,"s":4},{"n":"Kingstown","a":"Saint Vincent and the Grenadines","i":"VC","p":49485,"la":13.156,"lo":-61.22,"k":1,"z":5.1,"s":4},{"n":"Castries","a":"Saint Lucia","i":"LC","p":37963,"la":14.008,"lo":-60.993,"k":1,"z":5.6,"s":4},{"n":"Basseterre","a":"Saint Kitts and Nevis","i":"KN","p":21887,"la":17.302,"lo":-62.717,"k":1,"z":5.6,"s":4},{"n":"Port Louis","a":"Mauritius","i":"MU","p":595491,"la":-20.167,"lo":57.5,"k":1,"z":5.6,"s":4},{"n":"Saint George's","a":"Grenada","i":"GD","p":33734,"la":12.053,"lo":-61.742,"k":1,"z":5.1,"s":4},{"n":"Manama","a":"Bahrain","i":"BH","p":563920,"la":26.236,"lo":50.583,"k":1,"z":5.6,"s":4},{"n":"Saint John's","a":"Antigua and Barbuda","i":"AG","p":35499,"la":17.118,"lo":-61.85,"k":1,"z":5.1,"s":4},{"n":"Montevideo","a":"Uruguay","i":"UY","p":1513000,"la":-34.905,"lo":-56.187,"k":1,"z":4,"s":3},{"n":"Lomé","a":"Togo","i":"TG","p":1452000,"la":6.134,"lo":1.221,"k":1,"z":5,"s":3},{"n":"Tunis","a":"Tunisia","i":"TN","p":2412500,"la":36.803,"lo":10.18,"k":1,"z":5,"s":3},{"n":"Abu Dhabi","a":"United Arab Emirates","i":"AE","p":603492,"la":24.467,"lo":54.367,"k":1,"z":5,"s":3},{"n":"Ashgabat","a":"Turkmenistan","i":"TM","p":727700,"la":37.95,"lo":58.383,"k":1,"z":5,"s":3},{"n":"Lusaka","a":"Zambia","i":"ZM","p":1328000,"la":-15.415,"lo":28.281,"k":1,"z":4,"s":3},{"n":"Harare","a":"Zimbabwe","i":"ZW","p":1572000,"la":-17.816,"lo":31.043,"k":1,"z":4,"s":3},{"n":"Dili","a":"East Timor","i":"TL","p":234331,"la":-8.559,"lo":125.579,"k":1,"z":5,"s":3},{"n":"Port Vila","a":"Vanuatu","i":"VU","p":44040,"la":-17.733,"lo":168.317,"k":1,"z":4,"s":3},{"n":"Tegucigalpa","a":"Honduras","i":"HN","p":946000,"la":14.104,"lo":-87.219,"k":1,"z":5,"s":3},{"n":"Georgetown","a":"Guyana","i":"GY","p":264350,"la":6.802,"lo":-58.167,"k":1,"z":4,"s":3},{"n":"Reykjavík","a":"Iceland","i":"IS","p":166212,"la":64.143,"lo":-21.937,"k":1,"z":3.7,"s":3},{"n":"Port-au-Prince","a":"Haiti","i":"HT","p":1998000,"la":18.543,"lo":-72.338,"k":1,"z":5,"s":3},{"n":"Kampala","a":"Uganda","i":"UG","p":1420000,"la":0.319,"lo":32.581,"k":1,"z":5,"s":3},{"n":"Paramaribo","a":"Suriname","i":"SR","p":254169,"la":5.835,"lo":-55.167,"k":1,"z":5,"s":3},{"n":"Niamey","a":"Niger","i":"NE","p":915000,"la":13.519,"lo":2.115,"k":1,"z":5,"s":3},{"n":"Dushanbe","a":"Tajikistan","i":"TJ","p":1086244,"la":38.56,"lo":68.774,"k":1,"z":5,"s":3},{"n":"Asunción","a":"Paraguay","i":"PY","p":1870000,"la":-25.291,"lo":-57.626,"k":1,"z":4,"s":3},{"n":"Managua","a":"Nicaragua","i":"NI","p":920000,"la":12.155,"lo":-86.27,"k":1,"z":5,"s":3},{"n":"Freetown","a":"Sierra Leone","i":"SL","p":827000,"la":8.472,"lo":-13.236,"k":1,"z":5,"s":3},{"n":"Islamabad","a":"Pakistan","i":"PK","p":780000,"la":33.689,"lo":73.081,"k":1,"z":5,"s":3},{"n":"Kathmandu","a":"Nepal","i":"NP","p":895000,"la":27.719,"lo":85.315,"k":1,"z":5,"s":3},{"n":"Bloemfontein","a":"South Africa","i":"ZA","p":463064,"la":-29.12,"lo":26.23,"k":1,"z":5,"s":3},{"n":"Pretoria","a":"South Africa","i":"ZA","p":1338000,"la":-25.705,"lo":28.227,"k":1,"z":5,"s":3},{"n":"Port Moresby","a":"Papua New Guinea","i":"PG","p":283733,"la":-9.465,"lo":147.193,"k":1,"z":4,"s":3},{"n":"Honiara","a":"Solomon Islands","i":"SB","p":76328,"la":-9.438,"lo":159.95,"k":1,"z":5,"s":3},{"n":"Panama City","a":"Panama","i":"PA","p":1281000,"la":8.97,"lo":-79.535,"k":1,"z":5,"s":3},{"n":"Rabat","a":"Morocco","i":"MA","p":1705000,"la":34.025,"lo":-6.836,"k":1,"z":5,"s":3},{"n":"Chișinău","a":"Moldova","i":"MD","p":688134,"la":47.005,"lo":28.858,"k":1,"z":5,"s":3},{"n":"Maputo","a":"Mozambique","i":"MZ","p":1446000,"la":-25.953,"lo":32.587,"k":1,"z":5,"s":3},{"n":"Mogadishu","a":"Somalia","i":"SO","p":1100000,"la":2.069,"lo":45.365,"k":1,"z":4,"s":3},{"n":"Muscat","a":"Oman","i":"OM","p":734697,"la":23.585,"lo":58.378,"k":1,"z":5,"s":3},{"n":"Colombo","a":"Sri Lanka","i":"LK","p":217000,"la":6.932,"lo":79.858,"k":1,"z":5,"s":3},{"n":"Ulaanbaatar","a":"Mongolia","i":"MN","p":885000,"la":47.919,"lo":106.915,"k":1,"z":4,"s":3},{"n":"Windhoek","a":"Namibia","i":"NA","p":268132,"la":-22.57,"lo":17.084,"k":1,"z":5,"s":3},{"n":"Abuja","a":"Nigeria","i":"NG","p":1576000,"la":9.055,"lo":7.49,"k":1,"z":4,"s":3},{"n":"Bissau","a":"Guinea Bissau","i":"GW","p":403339,"la":11.865,"lo":-15.598,"k":1,"z":5,"s":3},{"n":"Amman","a":"Jordan","i":"JO","p":1060000,"la":31.952,"lo":35.931,"k":1,"z":5,"s":3},{"n":"Vilnius","a":"Lithuania","i":"LT","p":542366,"la":54.683,"lo":25.317,"k":1,"z":5,"s":3},{"n":"Riga","a":"Latvia","i":"LV","p":742572,"la":56.95,"lo":24.1,"k":1,"z":4,"s":3},{"n":"Bishkek","a":"Kyrgyzstan","i":"KG","p":837000,"la":42.875,"lo":74.583,"k":1,"z":5,"s":3},{"n":"Maseru","a":"Lesotho","i":"LS","p":361324,"la":-29.317,"lo":27.483,"k":1,"z":5,"s":3},{"n":"Antananarivo","a":"Madagascar","i":"MG","p":1697000,"la":-18.915,"lo":47.515,"k":1,"z":5,"s":3},{"n":"Quito","a":"Ecuador","i":"EC","p":1701000,"la":-0.213,"lo":-78.502,"k":1,"z":4,"s":3},{"n":"San José","a":"Costa Rica","i":"CR","p":1284000,"la":9.93,"lo":-84.079,"k":1,"z":5,"s":3},{"n":"San Salvador","a":"El Salvador","i":"SV","p":1433000,"la":13.703,"lo":-89.216,"k":1,"z":5,"s":3},{"n":"Kingston","a":"Jamaica","i":"JM","p":937700,"la":17.977,"lo":-76.767,"k":1,"z":5,"s":3},{"n":"N'Djamena","a":"Chad","i":"TD","p":989000,"la":12.115,"lo":15.047,"k":1,"z":5,"s":3},{"n":"Malabo","a":"Equatorial Guinea","i":"GQ","p":155963,"la":3.75,"lo":8.783,"k":1,"z":5,"s":3},{"n":"Asmara","a":"Eritrea","i":"ER","p":620802,"la":15.333,"lo":38.933,"k":1,"z":5,"s":3},{"n":"Zagreb","a":"Croatia","i":"HR","p":722526,"la":45.8,"lo":16,"k":1,"z":5,"s":3},{"n":"Tallinn","a":"Estonia","i":"EE","p":394024,"la":59.434,"lo":24.728,"k":1,"z":5,"s":3},{"n":"Lilongwe","a":"Malawi","i":"MW","p":646750,"la":-13.983,"lo":33.783,"k":1,"z":5,"s":3},{"n":"Guatemala City","a":"Guatemala","i":"GT","p":1024000,"la":14.623,"lo":-90.529,"k":1,"z":5,"s":3},{"n":"Libreville","a":"Gabon","i":"GA","p":578156,"la":0.385,"lo":9.458,"k":1,"z":5,"s":3},{"n":"Suva","a":"Fiji","i":"FJ","p":175399,"la":-18.133,"lo":178.442,"k":1,"z":5,"s":3},{"n":"Valparaíso","a":"Chile","i":"CL","p":854000,"la":-33.048,"lo":-71.617,"k":0,"z":5,"s":3},{"n":"Nouakchott","a":"Mauritania","i":"MR","p":742144,"la":18.086,"lo":-15.975,"k":1,"z":5,"s":3},{"n":"Bamako","a":"Mali","i":"ML","p":1494000,"la":12.652,"lo":-8.002,"k":1,"z":4,"s":3},{"n":"Beirut","a":"Lebanon","i":"LB","p":1846000,"la":33.874,"lo":35.508,"k":1,"z":5,"s":3},{"n":"Tbilisi","a":"Georgia","i":"GE","p":1100000,"la":41.727,"lo":44.789,"k":1,"z":5,"s":3},{"n":"Nur-Sultan","a":"Kazakhstan","i":"KZ","p":345604,"la":51.181,"lo":71.428,"k":1,"z":4,"s":3},{"n":"Vientiane","a":"Laos","i":"LA","p":754000,"la":17.967,"lo":102.6,"k":1,"z":5,"s":3},{"n":"Brazzaville","a":"Congo (Brazzaville)","i":"CG","p":1355000,"la":-4.257,"lo":15.283,"k":1,"z":5,"s":3},{"n":"Conakry","a":"Guinea","i":"GN","p":1494000,"la":9.533,"lo":-13.682,"k":1,"z":5,"s":3},{"n":"Yamoussoukro","a":"Ivory Coast","i":"CI","p":206499,"la":6.818,"lo":-5.276,"k":1,"z":5,"s":3},{"n":"Ottawa","a":"Canada","i":"CA","p":1145000,"la":45.419,"lo":-75.702,"k":1,"z":3,"s":3},{"n":"Belgrade","a":"Serbia","i":"RS","p":1099000,"la":44.821,"lo":20.466,"k":1,"z":5,"s":3},{"n":"Bandar Seri Begawan","a":"Brunei","i":"BN","p":296500,"la":4.883,"lo":114.933,"k":1,"z":5,"s":3},{"n":"Sucre","a":"Bolivia","i":"BO","p":224838,"la":-19.041,"lo":-65.26,"k":1,"z":4.7,"s":3},{"n":"Belmopan","a":"Belize","i":"BZ","p":15220,"la":17.252,"lo":-88.767,"k":1,"z":5,"s":3},{"n":"Bangui","a":"Central African Republic","i":"CF","p":831925,"la":4.367,"lo":18.558,"k":1,"z":5,"s":3},{"n":"Yaoundé","a":"Cameroon","i":"CM","p":1611000,"la":3.869,"lo":11.515,"k":1,"z":5,"s":3},{"n":"Tirana","a":"Albania","i":"AL","p":895350,"la":41.328,"lo":19.819,"k":1,"z":5,"s":3},{"n":"Yerevan","a":"Armenia","i":"AM","p":1102000,"la":40.183,"lo":44.512,"k":1,"z":5,"s":3},{"n":"Baku","a":"Azerbaijan","i":"AZ","p":2122300,"la":40.397,"lo":49.86,"k":1,"z":5,"s":3},{"n":"Phnom Penh","a":"Cambodia","i":"KH","p":1466000,"la":11.552,"lo":104.915,"k":1,"z":5,"s":3},{"n":"La Paz","a":"Bolivia","i":"BO","p":1590000,"la":-16.496,"lo":-68.152,"k":1,"z":4,"s":3},{"n":"Cotonou","a":"Benin","i":"BJ","p":762000,"la":6.363,"lo":2.404,"k":1,"z":5,"s":3},{"n":"Sofia","a":"Bulgaria","i":"BG","p":1185000,"la":42.685,"lo":23.315,"k":1,"z":5,"s":3},{"n":"Minsk","a":"Belarus","i":"BY","p":1805000,"la":53.902,"lo":27.565,"k":1,"z":5,"s":3},{"n":"Thimphu","a":"Bhutan","i":"BT","p":98676,"la":27.473,"lo":89.639,"k":1,"z":5,"s":3},{"n":"Gaborone","a":"Botswana","i":"BW","p":208411,"la":-24.646,"lo":25.912,"k":1,"z":5,"s":3},{"n":"Canberra","a":"Australia","i":"AU","p":327700,"la":-35.283,"lo":149.129,"k":1,"z":4,"s":3},{"n":"Ouagadougou","a":"Burkina Faso","i":"BF","p":1149000,"la":12.372,"lo":-1.527,"k":1,"z":5,"s":3},{"n":"Sarajevo","a":"Bosnia and Herzegovina","i":"BA","p":696731,"la":43.85,"lo":18.383,"k":1,"z":5,"s":3},{"n":"Naypyidaw","a":"Myanmar","i":"MM","p":930000,"la":19.769,"lo":96.117,"k":1,"z":5,"s":3},{"n":"Nuku'alofa","a":"Tonga","i":"TO","p":42620,"la":-21.139,"lo":-175.221,"k":1,"z":5,"s":3},{"n":"Hargeisa","a":"Somaliland","i":"-99","p":477876,"la":9.56,"lo":44.065,"k":1,"z":5,"s":3},{"n":"Victoria","a":"Seychelles","i":"SC","p":33576,"la":-4.617,"lo":55.45,"k":1,"z":5,"s":3},{"n":"São Tomé","a":"Sao Tome and Principe","i":"ST","p":88219,"la":0.337,"lo":6.73,"k":1,"z":5,"s":3},{"n":"Apia","a":"Samoa","i":"WS","p":61916,"la":-13.836,"lo":-171.769,"k":1,"z":5,"s":3},{"n":"Valletta","a":"Malta","i":"MT","p":368250,"la":35.9,"lo":14.515,"k":1,"z":5,"s":3},{"n":"Malé","a":"Maldives","i":"MV","p":112927,"la":4.172,"lo":73.509,"k":1,"z":5,"s":3},{"n":"Jerusalem","a":"Israel","i":"IL","p":1029300,"la":31.778,"lo":35.207,"k":1,"z":3.7,"s":3},{"n":"Praia","a":"Cape Verde","i":"CV","p":113364,"la":14.917,"lo":-23.517,"k":1,"z":5,"s":3},{"n":"Nassau","a":"The Bahamas","i":"BS","p":227940,"la":25.083,"lo":-77.35,"k":1,"z":5,"s":3},{"n":"Nicosia","a":"Cyprus","i":"CY","p":224300,"la":35.167,"lo":33.367,"k":1,"z":5,"s":3},{"n":"Wellington","a":"New Zealand","i":"NZ","p":393400,"la":-41.292,"lo":174.777,"k":1,"z":4,"s":3},{"n":"Hanoi","a":"Vietnam","i":"VN","p":4378000,"la":21.035,"lo":105.848,"k":1,"z":4,"s":2},{"n":"Ankara","a":"Turkey","i":"TR","p":3716000,"la":39.929,"lo":32.862,"k":1,"z":4,"s":2},{"n":"Budapest","a":"Hungary","i":"HU","p":1679000,"la":47.502,"lo":19.081,"k":1,"z":4.7,"s":2},{"n":"Sanaa","a":"Yemen","i":"YE","p":2008000,"la":15.357,"lo":44.205,"k":1,"z":4,"s":2},{"n":"Bucharest","a":"Romania","i":"RO","p":1942000,"la":44.435,"lo":26.098,"k":1,"z":4,"s":2},{"n":"Damascus","a":"Syria","i":"SY","p":2466000,"la":33.502,"lo":36.298,"k":1,"z":4.7,"s":2},{"n":"Lisbon","a":"Portugal","i":"PT","p":2812000,"la":38.725,"lo":-9.147,"k":1,"z":4,"s":2},{"n":"Khartoum","a":"Sudan","i":"SD","p":4754000,"la":15.59,"lo":32.532,"k":1,"z":4,"s":2},{"n":"Oslo","a":"Norway","i":"NO","p":835000,"la":59.919,"lo":10.748,"k":1,"z":4,"s":2},{"n":"Warsaw","a":"Poland","i":"PL","p":1707000,"la":52.231,"lo":21.005,"k":1,"z":4,"s":2},{"n":"Pyongyang","a":"North Korea","i":"KP","p":3300000,"la":39.021,"lo":125.753,"k":1,"z":4.7,"s":2},{"n":"Dar es Salaam","a":"Tanzania","i":"TZ","p":2930000,"la":-6.798,"lo":39.266,"k":1,"z":4,"s":2},{"n":"Dublin","a":"Ireland","i":"IE","p":1059000,"la":53.347,"lo":-6.257,"k":1,"z":4,"s":2},{"n":"Monrovia","a":"Liberia","i":"LR","p":1041000,"la":6.315,"lo":-10.8,"k":1,"z":4,"s":2},{"n":"Kuala Lumpur","a":"Malaysia","i":"MY","p":1448000,"la":3.14,"lo":101.689,"k":1,"z":4,"s":2},{"n":"Havana","a":"Cuba","i":"CU","p":2174000,"la":23.134,"lo":-82.366,"k":1,"z":4,"s":2},{"n":"Prague","a":"Czechia","i":"CZ","p":1162000,"la":50.087,"lo":14.423,"k":1,"z":4.7,"s":2},{"n":"Kuwait City","a":"Kuwait","i":"KW","p":2063000,"la":29.372,"lo":47.976,"k":1,"z":4,"s":2},{"n":"Santo Domingo","a":"Dominican Republic","i":"DO","p":2154000,"la":18.471,"lo":-69.93,"k":1,"z":4,"s":2},{"n":"Accra","a":"Ghana","i":"GH","p":2121000,"la":5.552,"lo":-0.219,"k":1,"z":4,"s":2},{"n":"Tripoli","a":"Libya","i":"LY","p":2189000,"la":32.893,"lo":13.18,"k":1,"z":4,"s":2},{"n":"Tel Aviv","a":"Israel","i":"IL","p":3112000,"la":32.082,"lo":34.768,"k":0,"z":4,"s":2},{"n":"Helsinki","a":"Finland","i":"FI","p":1115000,"la":60.164,"lo":24.932,"k":1,"z":4,"s":2},{"n":"København","a":"Denmark","i":"DK","p":1085000,"la":55.681,"lo":12.562,"k":1,"z":4,"s":2},{"n":"Abidjan","a":"Ivory Coast","i":"CI","p":3802000,"la":5.323,"lo":-4.02,"k":1,"z":4,"s":2},{"n":"Brasília","a":"Brazil","i":"BR","p":3716996,"la":-15.781,"lo":-47.918,"k":1,"z":3.7,"s":2},{"n":"Brussels","a":"Belgium","i":"BE","p":1743000,"la":50.835,"lo":4.331,"k":1,"z":4.7,"s":2},{"n":"Dhaka","a":"Bangladesh","i":"BD","p":12797394,"la":23.725,"lo":90.407,"k":1,"z":4,"s":2},{"n":"Luanda","a":"Angola","i":"AO","p":5172900,"la":-8.836,"lo":13.232,"k":1,"z":4,"s":2},{"n":"Algiers","a":"Algeria","i":"DZ","p":3354000,"la":36.765,"lo":3.049,"k":1,"z":4,"s":2},{"n":"Yangon","a":"Myanmar","i":"MM","p":4088000,"la":16.785,"lo":96.165,"k":0,"z":4,"s":2},{"n":"San Francisco","a":"United States of America","i":"US","p":3450000,"la":37.784,"lo":-122.4,"k":0,"z":2.7,"s":1},{"n":"Denver","a":"United States of America","i":"US","p":2313000,"la":39.741,"lo":-104.986,"k":0,"z":3.7,"s":1},{"n":"Houston","a":"United States of America","i":"US","p":4459000,"la":29.741,"lo":-95.348,"k":0,"z":3,"s":1},{"n":"Miami","a":"United States of America","i":"US","p":5585000,"la":25.79,"lo":-80.226,"k":0,"z":2.1,"s":1},{"n":"Atlanta","a":"United States of America","i":"US","p":4506000,"la":33.739,"lo":-84.368,"k":0,"z":3,"s":1},{"n":"Chicago","a":"United States of America","i":"US","p":8990000,"la":41.848,"lo":-87.635,"k":0,"z":3,"s":1},{"n":"Caracas","a":"Venezuela","i":"VE","p":2985000,"la":10.503,"lo":-66.919,"k":1,"z":3,"s":1},{"n":"Kyiv","a":"Ukraine","i":"UA","p":2709000,"la":50.435,"lo":30.515,"k":1,"z":3.7,"s":1},{"n":"Dubai","a":"United Arab Emirates","i":"AE","p":1379000,"la":25.215,"lo":55.287,"k":0,"z":3.7,"s":1},{"n":"Tashkent","a":"Uzbekistan","i":"UZ","p":2184000,"la":41.304,"lo":69.269,"k":1,"z":3.7,"s":1},{"n":"Madrid","a":"Spain","i":"ES","p":5567000,"la":40.402,"lo":-3.685,"k":1,"z":3,"s":1},{"n":"Geneva","a":"Switzerland","i":"CH","p":1240000,"la":46.21,"lo":6.14,"k":0,"z":3.7,"s":1},{"n":"Stockholm","a":"Sweden","i":"SE","p":1264000,"la":59.324,"lo":18.066,"k":1,"z":3,"s":1},{"n":"Bangkok","a":"Thailand","i":"TH","p":6704000,"la":13.752,"lo":100.515,"k":1,"z":3,"s":1},{"n":"Lima","a":"Peru","i":"PE","p":8012000,"la":-12.046,"lo":-77.052,"k":1,"z":3,"s":1},{"n":"Dakar","a":"Senegal","i":"SN","p":2604000,"la":14.718,"lo":-17.475,"k":1,"z":3,"s":1},{"n":"Johannesburg","a":"South Africa","i":"ZA","p":3435000,"la":-26.168,"lo":28.028,"k":0,"z":3,"s":1},{"n":"Amsterdam","a":"Netherlands","i":"NL","p":1031000,"la":52.352,"lo":4.915,"k":1,"z":3.7,"s":1},{"n":"Casablanca","a":"Morocco","i":"MA","p":3181000,"la":33.602,"lo":-7.618,"k":0,"z":3.7,"s":1},{"n":"Seoul","a":"South Korea","i":"KR","p":9796000,"la":37.568,"lo":126.998,"k":1,"z":3,"s":1},{"n":"Manila","a":"Philippines","i":"PH","p":11100000,"la":14.606,"lo":120.98,"k":1,"z":2.7,"s":1},{"n":"Monterrey","a":"Mexico","i":"MX","p":3712000,"la":25.672,"lo":-100.332,"k":0,"z":3.7,"s":1},{"n":"Berlin","a":"Germany","i":"DE","p":3406000,"la":52.524,"lo":13.4,"k":1,"z":2.7,"s":1},{"n":"Ürümqi","a":"China","i":"CN","p":3575000,"la":43.807,"lo":87.573,"k":0,"z":3.7,"s":1},{"n":"Chengdu","a":"China","i":"CN","p":4123000,"la":30.672,"lo":104.068,"k":0,"z":3,"s":1},{"n":"Ōsaka","a":"Japan","i":"JP","p":11294000,"la":34.691,"lo":135.504,"k":0,"z":3,"s":1},{"n":"Kinshasa","a":"Congo (Kinshasa)","i":"CD","p":7843000,"la":-4.328,"lo":15.313,"k":1,"z":3,"s":1},{"n":"New Delhi","a":"India","i":"IN","p":317797,"la":28.6,"lo":77.2,"k":1,"z":2.1,"s":1},{"n":"Bengaluru","a":"India","i":"IN","p":6787000,"la":12.972,"lo":77.558,"k":0,"z":3.7,"s":1},{"n":"Athens","a":"Greece","i":"GR","p":3242000,"la":37.985,"lo":23.731,"k":1,"z":3.7,"s":1},{"n":"Baghdad","a":"Iraq","i":"IQ","p":5054000,"la":33.341,"lo":44.392,"k":1,"z":3.7,"s":1},{"n":"Addis Ababa","a":"Ethiopia","i":"ET","p":3100000,"la":9.035,"lo":38.698,"k":1,"z":3,"s":1},{"n":"Tehran","a":"Iran","i":"IR","p":7873000,"la":35.674,"lo":51.422,"k":1,"z":3,"s":1},{"n":"Vancouver","a":"Canada","i":"CA","p":2313328,"la":49.275,"lo":-123.124,"k":0,"z":2.1,"s":1},{"n":"Toronto","a":"Canada","i":"CA","p":5213000,"la":43.665,"lo":-79.389,"k":0,"z":2.1,"s":1},{"n":"Buenos Aires","a":"Argentina","i":"AR","p":12795000,"la":-34.611,"lo":-58.433,"k":1,"z":2.1,"s":1},{"n":"Kabul","a":"Afghanistan","i":"AF","p":3277000,"la":34.519,"lo":69.181,"k":1,"z":3.7,"s":1},{"n":"Vienna","a":"Austria","i":"AT","p":2400000,"la":48.202,"lo":16.365,"k":1,"z":3.7,"s":1},{"n":"Melbourne","a":"Australia","i":"AU","p":4170000,"la":-37.818,"lo":144.973,"k":0,"z":3,"s":1},{"n":"Taipei","a":"Taiwan","i":"TW","p":6900273,"la":25.036,"lo":121.568,"k":1,"z":3.7,"s":1},{"n":"Auckland","a":"New Zealand","i":"NZ","p":1377200,"la":-36.848,"lo":174.763,"k":0,"z":3,"s":1},{"n":"Los Angeles","a":"United States of America","i":"US","p":12500000,"la":34.049,"lo":-118.232,"k":0,"z":2,"s":0},{"n":"Washington, D.C.","a":"United States of America","i":"US","p":4338000,"la":38.901,"lo":-77.011,"k":1,"z":2.1,"s":0},{"n":"New York","a":"United States of America","i":"US","p":19040000,"la":40.722,"lo":-73.996,"k":0,"z":1.7,"s":0},{"n":"London","a":"United Kingdom","i":"GB","p":8567000,"la":51.502,"lo":-0.119,"k":1,"z":1.7,"s":0},{"n":"Istanbul","a":"Turkey","i":"TR","p":10061000,"la":41.018,"lo":28.974,"k":0,"z":3,"s":0},{"n":"Riyadh","a":"Saudi Arabia","i":"SA","p":4465000,"la":24.634,"lo":46.72,"k":1,"z":3.7,"s":0},{"n":"Cape Town","a":"South Africa","i":"ZA","p":3215000,"la":-33.918,"lo":18.433,"k":1,"z":1.7,"s":0},{"n":"Moscow","a":"Russia","i":"RU","p":10452000,"la":55.754,"lo":37.614,"k":1,"z":1.7,"s":0},{"n":"Mexico City","a":"Mexico","i":"MX","p":19028000,"la":19.444,"lo":-99.133,"k":1,"z":2.1,"s":0},{"n":"Lagos","a":"Nigeria","i":"NG","p":9466000,"la":6.445,"lo":3.39,"k":0,"z":2,"s":0},{"n":"Rome","a":"Italy","i":"IT","p":3339000,"la":41.898,"lo":12.481,"k":1,"z":2.7,"s":0},{"n":"Beijing","a":"China","i":"CN","p":11106000,"la":39.902,"lo":116.394,"k":1,"z":1.7,"s":0},{"n":"Nairobi","a":"Kenya","i":"KE","p":3010000,"la":-1.281,"lo":36.815,"k":1,"z":2,"s":0},{"n":"Jakarta","a":"Indonesia","i":"ID","p":9125000,"la":-6.172,"lo":106.827,"k":1,"z":2.1,"s":0},{"n":"Bogota","a":"Colombia","i":"CO","p":7772000,"la":4.598,"lo":-74.085,"k":1,"z":2.1,"s":0},{"n":"Cairo","a":"Egypt","i":"EG","p":11893000,"la":30.052,"lo":31.248,"k":1,"z":1.7,"s":0},{"n":"Shanghai","a":"China","i":"CN","p":14987000,"la":31.218,"lo":121.435,"k":0,"z":2,"s":0},{"n":"Tokyo","a":"Japan","i":"JP","p":35676000,"la":35.687,"lo":139.749,"k":1,"z":1.7,"s":0},{"n":"Mumbai","a":"India","i":"IN","p":18978000,"la":19.068,"lo":72.876,"k":0,"z":1.7,"s":0},{"n":"Paris","a":"France","i":"FR","p":9904000,"la":48.858,"lo":2.353,"k":1,"z":1.7,"s":0},{"n":"Santiago","a":"Chile","i":"CL","p":5720000,"la":-33.44,"lo":-70.651,"k":1,"z":2,"s":0},{"n":"Kolkata","a":"India","i":"IN","p":14787000,"la":22.57,"lo":88.369,"k":0,"z":3.7,"s":0},{"n":"Rio de Janeiro","a":"Brazil","i":"BR","p":11748000,"la":-22.907,"lo":-43.212,"k":0,"z":1.7,"s":0},{"n":"São Paulo","a":"Brazil","i":"BR","p":18845000,"la":-23.557,"lo":-46.627,"k":0,"z":3,"s":0},{"n":"Sydney","a":"Australia","i":"AU","p":4630000,"la":-33.871,"lo":151.213,"k":0,"z":1.7,"s":0},{"n":"Singapore","a":"Singapore","i":"SG","p":5183700,"la":1.295,"lo":103.854,"k":1,"z":2.1,"s":0},{"n":"Hong Kong","a":"Hong Kong S.A.R.","i":"HK","p":7206000,"la":22.307,"lo":114.183,"k":0,"z":3,"s":0}]} \ No newline at end of file diff --git a/src/maps/data/places_50m.json b/src/maps/data/places_50m.json new file mode 100644 index 0000000..ba16935 --- /dev/null +++ b/src/maps/data/places_50m.json @@ -0,0 +1 @@ +{"f":[{"n":"Bombo","a":"Uganda","i":"UG","p":75000,"la":0.583,"lo":32.533,"k":0,"z":7,"s":10},{"n":"Fort Portal","a":"Uganda","i":"UG","p":42670,"la":0.671,"lo":30.275,"k":0,"z":7,"s":10},{"n":"Potenza","a":"Italy","i":"IT","p":69060,"la":40.642,"lo":15.799,"k":0,"z":7,"s":10},{"n":"Campobasso","a":"Italy","i":"IT","p":50762,"la":41.563,"lo":14.656,"k":0,"z":7,"s":10},{"n":"Aosta","a":"Italy","i":"IT","p":34062,"la":45.737,"lo":7.315,"k":0,"z":7,"s":10},{"n":"Mariehamn","a":"Aland","i":"AX","p":10682,"la":60.097,"lo":19.949,"k":0,"z":7,"s":10},{"n":"Ramallah","a":"Palestine","i":"PS","p":24599,"la":31.903,"lo":35.206,"k":0,"z":9,"s":10},{"n":"Vatican City","a":"Vatican","i":"VA","p":832,"la":41.903,"lo":12.453,"k":1,"z":7,"s":8},{"n":"Poitier","a":"France","i":"FR","p":85960,"la":46.583,"lo":0.333,"k":0,"z":6.7,"s":8},{"n":"Clermont-Ferrand","a":"France","i":"FR","p":233050,"la":45.78,"lo":3.08,"k":0,"z":6,"s":8},{"n":"Besançon","a":"France","i":"FR","p":128426,"la":47.23,"lo":6.03,"k":0,"z":7,"s":8},{"n":"Artigas Base","a":"Antarctica","i":"AQ","p":60,"la":-62.174,"lo":-58.864,"k":0,"z":7,"s":8},{"n":"Chipata","a":"Zambia","i":"ZM","p":85963,"la":-13.63,"lo":32.64,"k":0,"z":6.7,"s":7},{"n":"Jinja","a":"Uganda","i":"UG","p":301619,"la":0.44,"lo":33.2,"k":0,"z":6.7,"s":7},{"n":"Arua","a":"Uganda","i":"UG","p":250000,"la":3.02,"lo":30.9,"k":0,"z":6.7,"s":7},{"n":"Mbale","a":"Uganda","i":"UG","p":402368,"la":1.09,"lo":34.17,"k":0,"z":6.7,"s":7},{"n":"Moroto","a":"Uganda","i":"UG","p":371,"la":2.54,"lo":34.64,"k":0,"z":6.7,"s":7},{"n":"Masaka","a":"Uganda","i":"UG","p":65373,"la":-0.33,"lo":31.73,"k":0,"z":6.7,"s":7},{"n":"Mbarara","a":"Uganda","i":"UG","p":83700,"la":-0.6,"lo":30.65,"k":0,"z":6.7,"s":7},{"n":"Otjiwarongo","a":"Namibia","i":"NA","p":24815,"la":-20.46,"lo":16.64,"k":0,"z":6.7,"s":7},{"n":"Bologna","a":"Italy","i":"IT","p":488172,"la":44.5,"lo":11.34,"k":0,"z":6.1,"s":7},{"n":"Cagliari","a":"Italy","i":"IT","p":291511,"la":39.222,"lo":9.104,"k":0,"z":6.1,"s":7},{"n":"Catanzaro","a":"Italy","i":"IT","p":95251,"la":38.9,"lo":16.6,"k":0,"z":6.7,"s":7},{"n":"Bari","a":"Italy","i":"IT","p":500577,"la":41.114,"lo":16.873,"k":0,"z":5.1,"s":7},{"n":"L'Aquila","a":"Italy","i":"IT","p":68503,"la":42.35,"lo":13.39,"k":0,"z":6.7,"s":7},{"n":"Ancona","a":"Italy","i":"IT","p":100507,"la":43.6,"lo":13.5,"k":0,"z":6.1,"s":7},{"n":"Perugia","a":"Italy","i":"IT","p":149125,"la":43.11,"lo":12.39,"k":0,"z":6.7,"s":7},{"n":"Trieste","a":"Italy","i":"IT","p":216035,"la":45.65,"lo":13.8,"k":0,"z":6.7,"s":7},{"n":"Trento","a":"Italy","i":"IT","p":107808,"la":46.08,"lo":11.12,"k":0,"z":6.1,"s":7},{"n":"Fort-de-France","a":"France","i":"MQ","p":253995,"la":14.61,"lo":-61.08,"k":0,"z":6.7,"s":7},{"n":"Gifu","a":"Japan","i":"JP","p":412895,"la":35.423,"lo":136.763,"k":0,"z":6.1,"s":7},{"n":"Saint-Laurent-du-Maroni","a":"France","i":"GF","p":24287,"la":5.498,"lo":-54.033,"k":0,"z":6.7,"s":7},{"n":"Caen","a":"France","i":"FR","p":190099,"la":49.184,"lo":-0.35,"k":0,"z":6.7,"s":7},{"n":"Nantes","a":"France","i":"FR","p":438537,"la":47.21,"lo":-1.59,"k":0,"z":5.1,"s":7},{"n":"Ajaccio","a":"France","i":"FR","p":54364,"la":41.927,"lo":8.728,"k":0,"z":6.1,"s":7},{"n":"Montpellier","a":"France","i":"FR","p":327254,"la":43.61,"lo":3.87,"k":0,"z":6.1,"s":7},{"n":"Dijon","a":"France","i":"FR","p":169946,"la":47.33,"lo":5.03,"k":0,"z":6,"s":7},{"n":"Orléans","a":"France","i":"FR","p":217301,"la":47.9,"lo":1.9,"k":0,"z":6.1,"s":7},{"n":"Rouen","a":"France","i":"FR","p":532559,"la":49.43,"lo":1.08,"k":0,"z":6,"s":7},{"n":"Reims","a":"France","i":"FR","p":196565,"la":49.25,"lo":4.03,"k":0,"z":6.1,"s":7},{"n":"Amiens","a":"France","i":"FR","p":143086,"la":49.9,"lo":2.3,"k":0,"z":6.7,"s":7},{"n":"Nancy","a":"France","i":"FR","p":268976,"la":48.684,"lo":6.2,"k":0,"z":5.1,"s":7},{"n":"Basse-terre","a":"France","i":"GP","p":307,"la":16.01,"lo":-61.705,"k":0,"z":6.7,"s":7},{"n":"Novi Sad","a":"Serbia","i":"RS","p":225457,"la":45.25,"lo":19.85,"k":0,"z":6.1,"s":7},{"n":"Banja Luka","a":"Bosnia and Herzegovina","i":"BA","p":221738,"la":44.78,"lo":17.18,"k":0,"z":6.1,"s":7},{"n":"George Town","a":"Cayman Islands","i":"KY","p":4268,"la":19.295,"lo":-81.372,"k":0,"z":6.7,"s":7},{"n":"Grand Turk","a":"Turks and Caicos Islands","i":"TC","p":5801,"la":21.466,"lo":-71.136,"k":0,"z":6.7,"s":7},{"n":"Douglas","a":"Isle of Man","i":"IM","p":35854,"la":54.149,"lo":-4.48,"k":0,"z":6.7,"s":7},{"n":"San Marino","a":"San Marino","i":"SM","p":29579,"la":43.936,"lo":12.442,"k":1,"z":6.1,"s":7},{"n":"Willemstad","a":"Curacao","i":"CW","p":146813,"la":12.112,"lo":-68.872,"k":0,"z":6.7,"s":7},{"n":"Oranjestad","a":"Aruba","i":"AW","p":68775,"la":12.522,"lo":-70.026,"k":0,"z":6.7,"s":7},{"n":"Vaduz","a":"Liechtenstein","i":"LI","p":36281,"la":47.134,"lo":9.517,"k":1,"z":6.7,"s":7},{"n":"Capitan Arturo Prat Station","a":"Antarctica","i":"AQ","p":41,"la":-62.5,"lo":-59.683,"k":0,"z":6.1,"s":7},{"n":"Marambio Base","a":"Antarctica","i":"AQ","p":150,"la":-64.233,"lo":-56.65,"k":0,"z":5,"s":7},{"n":"Zucchelli Station","a":"Antarctica","i":"AQ","p":80,"la":-74.619,"lo":164.219,"k":0,"z":6.7,"s":7},{"n":"Rothera Station","a":"Antarctica","i":"AQ","p":130,"la":-67.565,"lo":-68.124,"k":0,"z":4,"s":7},{"n":"Palmer Station","a":"Antarctica","i":"AQ","p":46,"la":-64.762,"lo":-64.047,"k":0,"z":4,"s":7},{"n":"Base Presidente Montalva","a":"Antarctica","i":"AQ","p":150,"la":-62.183,"lo":-58.908,"k":0,"z":5,"s":7},{"n":"Carlini Base","a":"Antarctica","i":"AQ","p":60,"la":-62.224,"lo":-58.65,"k":0,"z":6.1,"s":7},{"n":"King Sejong Station","a":"Antarctica","i":"AQ","p":90,"la":-62.225,"lo":-58.772,"k":0,"z":6.1,"s":7},{"n":"Great Wall Station","a":"Antarctica","i":"AQ","p":40,"la":-62.216,"lo":-58.967,"k":0,"z":5,"s":7},{"n":"Escudero Base","a":"Antarctica","i":"AQ","p":20,"la":-62.195,"lo":-58.953,"k":0,"z":6.1,"s":7},{"n":"Elephant Island","a":"Antarctica","i":"AQ","p":6,"la":-62,"lo":-58,"k":0,"z":6.1,"s":7},{"n":"Scott Base","a":"Antarctica","i":"AQ","p":85,"la":-77.847,"lo":166.749,"k":0,"z":6.7,"s":7},{"n":"McMurdo Station","a":"Antarctica","i":"AQ","p":1000,"la":-77.732,"lo":166.869,"k":0,"z":3,"s":7},{"n":"Zhongshan Station","a":"Antarctica","i":"AQ","p":60,"la":-69.434,"lo":76.339,"k":0,"z":6.1,"s":7},{"n":"Vostok Station","a":"Antarctica","i":"AQ","p":25,"la":-78.466,"lo":106.8,"k":0,"z":6.7,"s":7},{"n":"Peter I Island","a":"Antarctica","i":"AQ","p":1,"la":-68.795,"lo":-90.584,"k":0,"z":6.1,"s":7},{"n":"Mirny Station","a":"Antarctica","i":"AQ","p":169,"la":-66.576,"lo":93.006,"k":0,"z":3,"s":7},{"n":"Mawson Station","a":"Antarctica","i":"AQ","p":60,"la":-67.615,"lo":62.875,"k":0,"z":6.1,"s":7},{"n":"Davis Station","a":"Antarctica","i":"AQ","p":70,"la":-68.778,"lo":78.141,"k":0,"z":6.1,"s":7},{"n":"Concordia Research Station","a":"Antarctica","i":"AQ","p":30,"la":-74.666,"lo":124.167,"k":0,"z":6.7,"s":7},{"n":"Casey Station","a":"Antarctica","i":"AQ","p":200,"la":-66.286,"lo":110.536,"k":0,"z":3,"s":7},{"n":"Amundsen–Scott South Pole Station","a":"Antarctica","i":"AQ","p":200,"la":-90,"lo":176.994,"k":0,"z":5,"s":7},{"n":"Wasa Station","a":"Antarctica","i":"AQ","p":10,"la":-73.05,"lo":-13.417,"k":0,"z":6.1,"s":7},{"n":"Troll Station","a":"Antarctica","i":"AQ","p":44,"la":-72.016,"lo":2.533,"k":0,"z":6.1,"s":7},{"n":"Svea Station","a":"Antarctica","i":"AQ","p":10,"la":-74.583,"lo":-11.217,"k":0,"z":6.1,"s":7},{"n":"Novolazarevskaya Station","a":"Antarctica","i":"AQ","p":70,"la":-71.301,"lo":-11.85,"k":0,"z":6.1,"s":7},{"n":"Neumayer Station III","a":"Antarctica","i":"AQ","p":40,"la":-70.89,"lo":-7.838,"k":0,"z":6.1,"s":7},{"n":"Maitri Station","a":"Antarctica","i":"AQ","p":65,"la":-70.782,"lo":11.73,"k":0,"z":6.1,"s":7},{"n":"Halley Station","a":"Antarctica","i":"AQ","p":70,"la":-76.092,"lo":-26.474,"k":0,"z":6.1,"s":7},{"n":"Belgrano II Base","a":"Antarctica","i":"AQ","p":100,"la":-77.866,"lo":-34.617,"k":0,"z":6.1,"s":7},{"n":"Sobral Base","a":"Antarctica","i":"AQ","p":40,"la":-81.179,"lo":-40.502,"k":0,"z":6.1,"s":7},{"n":"Aboa Station","a":"Antarctica","i":"AQ","p":18,"la":-75.05,"lo":-13.417,"k":0,"z":6.1,"s":7},{"n":"San Martín Base","a":"Antarctica","i":"AQ","p":20,"la":-68.116,"lo":-67.1,"k":0,"z":6.1,"s":7},{"n":"Gen. O'Higgins Base","a":"Antarctica","i":"AQ","p":44,"la":-63.328,"lo":-57.89,"k":0,"z":6.1,"s":7},{"n":"Esperanza Base","a":"Antarctica","i":"AQ","p":250,"la":-63.375,"lo":-57.026,"k":0,"z":3,"s":7},{"n":"Orcadas Station","a":"Antarctica","i":"AQ","p":45,"la":-60.733,"lo":-44.733,"k":0,"z":6.1,"s":7},{"n":"Signy Research Station","a":"Antarctica","i":"AQ","p":8,"la":-60.631,"lo":-45.601,"k":0,"z":6.1,"s":7},{"n":"Dumont d'Urville Station","a":"Antarctica","i":"AQ","p":120,"la":-66.775,"lo":139.964,"k":0,"z":4,"s":7},{"n":"Showa Station","a":"Antarctica","i":"AQ","p":60,"la":-69.03,"lo":39.746,"k":0,"z":6.1,"s":7},{"n":"Gibraltar","a":"Gibraltar","i":"GI","p":187083,"la":36.128,"lo":-5.346,"k":0,"z":5.1,"s":6},{"n":"Lobamba","a":"eSwatini","i":"SZ","p":9782,"la":-26.467,"lo":31.2,"k":0,"z":6,"s":6},{"n":"Edinburgh","a":"United Kingdom","i":"GB","p":504966,"la":55.948,"lo":-3.219,"k":0,"z":5.1,"s":6},{"n":"Cardiff","a":"United Kingdom","i":"GB","p":861400,"la":51.483,"lo":-3.168,"k":0,"z":5.1,"s":6},{"n":"Longyearbyen","a":"Svalbard and Jan Mayen Islands","i":"SJ","p":1232,"la":78.221,"lo":15.621,"k":0,"z":6,"s":6},{"n":"Luxembourg","a":"Luxembourg","i":"LU","p":107260,"la":49.612,"lo":6.13,"k":1,"z":6,"s":6},{"n":"Turin","a":"Italy","i":"IT","p":1652000,"la":45.072,"lo":7.668,"k":0,"z":6,"s":6},{"n":"Nouméa","a":"New Caledonia","i":"NC","p":93060,"la":-22.263,"lo":166.444,"k":0,"z":6,"s":6},{"n":"Matsuyama","a":"Japan","i":"JP","p":533541,"la":33.846,"lo":132.766,"k":0,"z":6,"s":6},{"n":"Rennes","a":"France","i":"FR","p":209375,"la":48.1,"lo":-1.67,"k":0,"z":6,"s":6},{"n":"Toulouse","a":"France","i":"FR","p":847000,"la":43.622,"lo":1.448,"k":0,"z":6,"s":6},{"n":"Limoges","a":"France","i":"FR","p":152199,"la":45.83,"lo":1.25,"k":0,"z":6.7,"s":6},{"n":"Lille","a":"France","i":"FR","p":1044000,"la":50.652,"lo":3.078,"k":0,"z":6,"s":6},{"n":"Strasbourg","a":"France","i":"FR","p":439972,"la":48.58,"lo":7.75,"k":0,"z":6,"s":6},{"n":"Batumi","a":"Georgia","i":"GE","p":155542,"la":41.6,"lo":41.63,"k":0,"z":6,"s":6},{"n":"Funchal","a":"Portugal","i":"PT","p":204767,"la":32.65,"lo":-16.88,"k":0,"z":6,"s":6},{"n":"El Fasher","a":"Sudan","i":"SD","p":252609,"la":13.63,"lo":25.35,"k":0,"z":6,"s":6},{"n":"Keetmanshoop","a":"Namibia","i":"NA","p":18039,"la":-26.574,"lo":18.13,"k":0,"z":6,"s":6},{"n":"Swakopmund","a":"Namibia","i":"NA","p":29491,"la":-22.669,"lo":14.535,"k":0,"z":6,"s":6},{"n":"Genoa","a":"Italy","i":"IT","p":647497,"la":44.41,"lo":8.93,"k":0,"z":5.1,"s":6},{"n":"Sukhumi","a":"Georgia","i":"GE","p":81546,"la":43.02,"lo":41.02,"k":0,"z":6,"s":6},{"n":"Palikir","a":"Federated States of Micronesia","i":"FM","p":4645,"la":6.917,"lo":158.15,"k":1,"z":6,"s":6},{"n":"Majuro","a":"Marshall Islands","i":"MH","p":25400,"la":7.103,"lo":171.38,"k":1,"z":6,"s":6},{"n":"Agana","a":"Guam","i":"GU","p":122411,"la":13.47,"lo":144.75,"k":0,"z":6,"s":6},{"n":"Funafuti","a":"Tuvalu","i":"TV","p":4749,"la":-8.517,"lo":179.217,"k":1,"z":6,"s":6},{"n":"Melekeok","a":"Palau","i":"PW","p":7026,"la":7.487,"lo":134.627,"k":1,"z":6,"s":6},{"n":"Bir Lehlou","a":"Western Sahara","i":"EH","p":500,"la":26.119,"lo":-9.653,"k":0,"z":6,"s":6},{"n":"Monaco","a":"Monaco","i":"MC","p":36371,"la":43.74,"lo":7.407,"k":1,"z":5.1,"s":6},{"n":"Tarawa","a":"Kiribati","i":"KI","p":28802,"la":1.338,"lo":173.018,"k":1,"z":6,"s":6},{"n":"Moroni","a":"Comoros","i":"KM","p":128698,"la":-11.704,"lo":43.24,"k":1,"z":6,"s":6},{"n":"Macau","a":"Macau S.A.R","i":"MO","p":568700,"la":22.189,"lo":113.544,"k":0,"z":6,"s":6},{"n":"Andorra","a":"Andorra","i":"AD","p":53998,"la":42.511,"lo":1.527,"k":1,"z":6,"s":6},{"n":"San Bernardino","a":"United States of America","i":"US","p":1745000,"la":34.122,"lo":-117.302,"k":0,"z":5.6,"s":4},{"n":"Bridgeport","a":"United States of America","i":"US","p":1018000,"la":41.182,"lo":-73.202,"k":0,"z":5.6,"s":4},{"n":"Rochester","a":"United States of America","i":"US","p":755000,"la":43.172,"lo":-77.622,"k":0,"z":5.1,"s":4},{"n":"Manchester","a":"United Kingdom","i":"GB","p":2230000,"la":53.475,"lo":-2.249,"k":0,"z":5,"s":4},{"n":"Gujranwala","a":"Pakistan","i":"PK","p":1513000,"la":32.162,"lo":74.183,"k":0,"z":5.6,"s":4},{"n":"Incheon","a":"South Korea","i":"KR","p":2550000,"la":37.478,"lo":126.64,"k":0,"z":5.6,"s":4},{"n":"Benin City","a":"Nigeria","i":"NG","p":1190000,"la":6.342,"lo":5.618,"k":0,"z":5.6,"s":4},{"n":"Xiamen","a":"China","i":"CN","p":2519000,"la":24.452,"lo":118.078,"k":0,"z":5.6,"s":4},{"n":"Nanchong","a":"China","i":"CN","p":2174000,"la":30.782,"lo":106.128,"k":0,"z":5.6,"s":4},{"n":"Neijiang","a":"China","i":"CN","p":1466000,"la":29.582,"lo":105.048,"k":0,"z":5.6,"s":4},{"n":"Nanyang","a":"China","i":"CN","p":1944000,"la":33.002,"lo":112.528,"k":0,"z":5.6,"s":4},{"n":"Jinxi","a":"China","i":"CN","p":2426000,"la":40.752,"lo":120.828,"k":0,"z":5.6,"s":4},{"n":"Yantai","a":"China","i":"CN","p":2116000,"la":37.532,"lo":121.398,"k":0,"z":5.6,"s":4},{"n":"Zaozhuang","a":"China","i":"CN","p":2145000,"la":34.882,"lo":117.568,"k":0,"z":5.1,"s":4},{"n":"Suzhou","a":"China","i":"CN","p":1650000,"la":31.302,"lo":120.618,"k":0,"z":5.6,"s":4},{"n":"Xuzhou","a":"China","i":"CN","p":2091000,"la":34.282,"lo":117.178,"k":0,"z":5.6,"s":4},{"n":"Wuxi","a":"China","i":"CN","p":1749000,"la":31.582,"lo":120.298,"k":0,"z":5.6,"s":4},{"n":"Jilin","a":"China","i":"CN","p":2396000,"la":43.852,"lo":126.548,"k":0,"z":5.6,"s":4},{"n":"Chandigarh","a":"India","i":"IN","p":979000,"la":30.722,"lo":76.778,"k":0,"z":5.6,"s":4},{"n":"Jammu","a":"India","i":"IN","p":791000,"la":32.714,"lo":74.845,"k":0,"z":5.6,"s":4},{"n":"Sholapur","a":"India","i":"IN","p":1057000,"la":17.672,"lo":75.898,"k":0,"z":5.6,"s":4},{"n":"Aurangabad","a":"India","i":"IN","p":1113000,"la":19.862,"lo":75.329,"k":0,"z":5.6,"s":4},{"n":"Nasik","a":"India","i":"IN","p":1473000,"la":20.002,"lo":73.778,"k":0,"z":5.6,"s":4},{"n":"Dispur","a":"India","i":"IN","p":16140,"la":26.144,"lo":91.767,"k":0,"z":5.6,"s":4},{"n":"Jullundur","a":"India","i":"IN","p":855000,"la":31.337,"lo":75.567,"k":0,"z":5.6,"s":4},{"n":"Allahabad","a":"India","i":"IN","p":1201000,"la":25.457,"lo":81.838,"k":0,"z":5.6,"s":4},{"n":"Moradabad","a":"India","i":"IN","p":787000,"la":28.844,"lo":78.755,"k":0,"z":5.6,"s":4},{"n":"Ghaziabad","a":"India","i":"IN","p":1341000,"la":28.662,"lo":77.406,"k":0,"z":5.6,"s":4},{"n":"Agra","a":"India","i":"IN","p":1592000,"la":27.172,"lo":78.013,"k":0,"z":5.6,"s":4},{"n":"Aligarh","a":"India","i":"IN","p":805000,"la":27.894,"lo":78.06,"k":0,"z":5.6,"s":4},{"n":"Meerut","a":"India","i":"IN","p":1398000,"la":29.002,"lo":77.698,"k":0,"z":5.6,"s":4},{"n":"Dhanbad","a":"India","i":"IN","p":1246000,"la":23.802,"lo":86.418,"k":0,"z":5.6,"s":4},{"n":"Gwalior","a":"India","i":"IN","p":978000,"la":26.2,"lo":78.169,"k":0,"z":5.6,"s":4},{"n":"Vadodara","a":"India","i":"IN","p":1756000,"la":22.312,"lo":73.178,"k":0,"z":5.6,"s":4},{"n":"Rajkot","a":"India","i":"IN","p":1260000,"la":22.312,"lo":70.798,"k":0,"z":5.6,"s":4},{"n":"Durazno","a":"Uruguay","i":"UY","p":34037,"la":-33.41,"lo":-56.51,"k":0,"z":5.6,"s":4},{"n":"International Falls","a":"United States of America","i":"US","p":15240,"la":48.601,"lo":-93.411,"k":0,"z":5.6,"s":4},{"n":"St. Paul","a":"United States of America","i":"US","p":734854,"la":44.944,"lo":-93.085,"k":0,"z":5.6,"s":4},{"n":"Billings","a":"United States of America","i":"US","p":104552,"la":45.788,"lo":-108.54,"k":0,"z":5.6,"s":4},{"n":"Great Falls","a":"United States of America","i":"US","p":66558,"la":47.5,"lo":-111.3,"k":0,"z":5.6,"s":4},{"n":"Missoula","a":"United States of America","i":"US","p":72856,"la":46.872,"lo":-113.993,"k":0,"z":5.6,"s":4},{"n":"Minot","a":"United States of America","i":"US","p":39439,"la":48.232,"lo":-101.296,"k":0,"z":5.6,"s":4},{"n":"Fargo","a":"United States of America","i":"US","p":162842,"la":46.877,"lo":-96.789,"k":0,"z":5.1,"s":4},{"n":"Hilo","a":"United States of America","i":"US","p":52391,"la":19.706,"lo":-155.082,"k":0,"z":5.1,"s":4},{"n":"Olympia","a":"United States of America","i":"US","p":156984,"la":47.038,"lo":-122.899,"k":0,"z":5.6,"s":4},{"n":"Spokane","a":"United States of America","i":"US","p":347705,"la":47.67,"lo":-117.42,"k":0,"z":5.1,"s":4},{"n":"Vancouver","a":"United States of America","i":"US","p":525802,"la":45.63,"lo":-122.64,"k":0,"z":5.6,"s":4},{"n":"Flagstaff","a":"United States of America","i":"US","p":63993,"la":35.198,"lo":-111.651,"k":0,"z":5.1,"s":4},{"n":"Tucson","a":"United States of America","i":"US","p":823000,"la":32.207,"lo":-110.892,"k":0,"z":5.1,"s":4},{"n":"Santa Barbara","a":"United States of America","i":"US","p":181632,"la":34.411,"lo":-119.717,"k":0,"z":5.6,"s":4},{"n":"Fresno","a":"United States of America","i":"US","p":616353,"la":36.748,"lo":-119.773,"k":0,"z":5.1,"s":4},{"n":"Eureka","a":"United States of America","i":"US","p":42398,"la":40.793,"lo":-124.15,"k":0,"z":5,"s":4},{"n":"Colorado Springs","a":"United States of America","i":"US","p":493654,"la":38.863,"lo":-104.792,"k":0,"z":5.6,"s":4},{"n":"Reno","a":"United States of America","i":"US","p":328585,"la":39.53,"lo":-119.82,"k":0,"z":5.1,"s":4},{"n":"Elko","a":"United States of America","i":"US","p":19252,"la":40.833,"lo":-115.762,"k":0,"z":5.6,"s":4},{"n":"Albuquerque","a":"United States of America","i":"US","p":898642,"la":35.105,"lo":-106.641,"k":0,"z":5.1,"s":4},{"n":"Salem","a":"United States of America","i":"US","p":229010,"la":44.928,"lo":-123.024,"k":0,"z":5.6,"s":4},{"n":"Casper","a":"United States of America","i":"US","p":60791,"la":42.847,"lo":-106.323,"k":0,"z":5.6,"s":4},{"n":"Topeka","a":"United States of America","i":"US","p":132091,"la":39.05,"lo":-95.67,"k":0,"z":5.1,"s":4},{"n":"Kansas City","a":"United States of America","i":"US","p":1469000,"la":39.109,"lo":-94.606,"k":0,"z":5.1,"s":4},{"n":"Tulsa","a":"United States of America","i":"US","p":946962,"la":36.12,"lo":-95.93,"k":0,"z":5.6,"s":4},{"n":"Sioux Falls","a":"United States of America","i":"US","p":155724,"la":43.55,"lo":-96.73,"k":0,"z":5.6,"s":4},{"n":"Shreveport","a":"United States of America","i":"US","p":248053,"la":32.5,"lo":-93.77,"k":0,"z":5.6,"s":4},{"n":"Baton Rouge","a":"United States of America","i":"US","p":422072,"la":30.458,"lo":-91.14,"k":0,"z":5.6,"s":4},{"n":"Ft. Worth","a":"United States of America","i":"US","p":1440454,"la":32.74,"lo":-97.34,"k":0,"z":5.1,"s":4},{"n":"Corpus Christi","a":"United States of America","i":"US","p":277454,"la":27.743,"lo":-97.402,"k":0,"z":5.6,"s":4},{"n":"Austin","a":"United States of America","i":"US","p":1161000,"la":30.269,"lo":-97.745,"k":0,"z":5.1,"s":4},{"n":"Amarillo","a":"United States of America","i":"US","p":181766,"la":35.23,"lo":-101.83,"k":0,"z":5.6,"s":4},{"n":"El Paso","a":"United States of America","i":"US","p":753000,"la":31.782,"lo":-106.512,"k":0,"z":5.1,"s":4},{"n":"Laredo","a":"United States of America","i":"US","p":434768,"la":27.506,"lo":-99.507,"k":0,"z":5.6,"s":4},{"n":"Mérida","a":"Venezuela","i":"VE","p":345489,"la":8.57,"lo":-71.183,"k":0,"z":5.6,"s":4},{"n":"Burlington","a":"United States of America","i":"US","p":93807,"la":44.476,"lo":-73.212,"k":0,"z":5.6,"s":4},{"n":"Montgomery","a":"United States of America","i":"US","p":198325,"la":32.362,"lo":-86.279,"k":0,"z":5.6,"s":4},{"n":"Tallahassee","a":"United States of America","i":"US","p":221222,"la":30.45,"lo":-84.28,"k":0,"z":5.6,"s":4},{"n":"Orlando","a":"United States of America","i":"US","p":1350000,"la":28.512,"lo":-81.382,"k":0,"z":5.1,"s":4},{"n":"Jacksonville","a":"United States of America","i":"US","p":988000,"la":30.332,"lo":-81.672,"k":0,"z":5.1,"s":4},{"n":"Savannah","a":"United States of America","i":"US","p":180187,"la":32.021,"lo":-81.11,"k":0,"z":5.1,"s":4},{"n":"Columbia","a":"United States of America","i":"US","p":398093,"la":33.997,"lo":-81.031,"k":0,"z":5.6,"s":4},{"n":"Indianapolis","a":"United States of America","i":"US","p":1436000,"la":39.752,"lo":-86.172,"k":0,"z":5.1,"s":4},{"n":"Wilmington","a":"United States of America","i":"US","p":161560,"la":34.226,"lo":-77.945,"k":0,"z":5.1,"s":4},{"n":"Knoxville","a":"United States of America","i":"US","p":655400,"la":35.97,"lo":-83.92,"k":0,"z":5.6,"s":4},{"n":"Richmond","a":"United States of America","i":"US","p":912000,"la":37.552,"lo":-77.452,"k":0,"z":5,"s":4},{"n":"Charleston","a":"United States of America","i":"US","p":123799,"la":38.35,"lo":-81.633,"k":0,"z":5.1,"s":4},{"n":"Baltimore","a":"United States of America","i":"US","p":2255000,"la":39.282,"lo":-76.615,"k":0,"z":5.6,"s":4},{"n":"Syracuse","a":"United States of America","i":"US","p":662577,"la":43.05,"lo":-76.15,"k":0,"z":5.6,"s":4},{"n":"Puerto Ayacucho","a":"Venezuela","i":"VE","p":52526,"la":5.664,"lo":-67.624,"k":0,"z":5.6,"s":4},{"n":"Port-of-Spain","a":"Trinidad and Tobago","i":"TT","p":294934,"la":10.652,"lo":-61.517,"k":1,"z":5.1,"s":4},{"n":"Augusta","a":"United States of America","i":"US","p":24042,"la":44.311,"lo":-69.78,"k":0,"z":5.6,"s":4},{"n":"Sault Ste. Marie","a":"United States of America","i":"US","p":83805,"la":46.495,"lo":-84.345,"k":0,"z":5.1,"s":4},{"n":"Atakpamé","a":"Togo","i":"TG","p":80683,"la":7.53,"lo":1.12,"k":0,"z":5.6,"s":4},{"n":"Sousse","a":"Tunisia","i":"TN","p":327004,"la":35.83,"lo":10.625,"k":0,"z":5.6,"s":4},{"n":"Taizz","a":"Yemen","i":"YE","p":751000,"la":13.606,"lo":44.037,"k":0,"z":5.1,"s":4},{"n":"Sitka","a":"United States of America","i":"US","p":8931,"la":57.06,"lo":-135.328,"k":0,"z":5.1,"s":4},{"n":"Lviv","a":"Ukraine","i":"UA","p":803880,"la":49.835,"lo":24.03,"k":0,"z":5.1,"s":4},{"n":"Odessa","a":"Ukraine","i":"UA","p":991000,"la":46.492,"lo":30.708,"k":0,"z":5.1,"s":4},{"n":"Zhytomyr","a":"Ukraine","i":"UA","p":282192,"la":50.246,"lo":28.662,"k":0,"z":5.6,"s":4},{"n":"Dnipro","a":"Ukraine","i":"UA","p":1050000,"la":48.482,"lo":34.998,"k":0,"z":5.1,"s":4},{"n":"Donetsk","a":"Ukraine","i":"UA","p":988000,"la":48.002,"lo":37.828,"k":0,"z":5,"s":4},{"n":"Kharkiv","a":"Ukraine","i":"UA","p":1461000,"la":50.002,"lo":36.248,"k":0,"z":5.1,"s":4},{"n":"Türkmenbaşy","a":"Turkmenistan","i":"TM","p":68292,"la":40.023,"lo":52.97,"k":0,"z":5.6,"s":4},{"n":"Bukhara","a":"Uzbekistan","i":"UZ","p":319476,"la":39.78,"lo":64.43,"k":0,"z":5.1,"s":4},{"n":"Nukus","a":"Uzbekistan","i":"UZ","p":230006,"la":42.47,"lo":59.615,"k":0,"z":5.6,"s":4},{"n":"Türkmenabat","a":"Turkmenistan","i":"TM","p":234817,"la":39.11,"lo":63.58,"k":0,"z":5.6,"s":4},{"n":"Mary","a":"Turkmenistan","i":"TM","p":178708,"la":37.6,"lo":61.833,"k":0,"z":5.1,"s":4},{"n":"Andijan","a":"Uzbekistan","i":"UZ","p":650000,"la":40.79,"lo":72.34,"k":0,"z":5.1,"s":4},{"n":"Haiphong","a":"Vietnam","i":"VN","p":1969000,"la":20.832,"lo":106.678,"k":0,"z":5.6,"s":4},{"n":"Da Nang","a":"Vietnam","i":"VN","p":1000000,"la":16.06,"lo":108.25,"k":0,"z":5.6,"s":4},{"n":"Kabwe","a":"Zambia","i":"ZM","p":188979,"la":-14.44,"lo":28.45,"k":0,"z":5.6,"s":4},{"n":"Mufulira","a":"Zambia","i":"ZM","p":153624,"la":-12.55,"lo":28.26,"k":0,"z":5.6,"s":4},{"n":"Kitwe","a":"Zambia","i":"ZM","p":404901,"la":-12.81,"lo":28.22,"k":0,"z":5.6,"s":4},{"n":"Livingstone","a":"Zambia","i":"ZM","p":165480,"la":-17.86,"lo":25.86,"k":0,"z":5.6,"s":4},{"n":"Chitungwiza","a":"Zimbabwe","i":"ZW","p":340360,"la":-18,"lo":31.1,"k":0,"z":5.6,"s":4},{"n":"Douala","a":"Cameroon","i":"CM","p":1906000,"la":4.062,"lo":9.708,"k":0,"z":5.6,"s":4},{"n":"Birmingham","a":"United Kingdom","i":"GB","p":2285000,"la":52.477,"lo":-1.922,"k":0,"z":5,"s":4},{"n":"Belfast","a":"United Kingdom","i":"GB","p":450406,"la":54.594,"lo":-5.928,"k":0,"z":5,"s":4},{"n":"İzmir","a":"Turkey","i":"TR","p":2587000,"la":38.438,"lo":27.15,"k":0,"z":5.1,"s":4},{"n":"Bursa","a":"Turkey","i":"TR","p":1492000,"la":40.202,"lo":29.068,"k":0,"z":5.1,"s":4},{"n":"Samsun","a":"Turkey","i":"TR","p":609339,"la":41.28,"lo":36.344,"k":0,"z":5.6,"s":4},{"n":"Konya","a":"Turkey","i":"TR","p":919000,"la":37.877,"lo":32.473,"k":0,"z":5.6,"s":4},{"n":"Adana","a":"Turkey","i":"TR","p":1293000,"la":36.997,"lo":35.318,"k":0,"z":5.1,"s":4},{"n":"Gulu","a":"Uganda","i":"UG","p":146858,"la":2.78,"lo":32.28,"k":0,"z":5.6,"s":4},{"n":"Kigali","a":"Rwanda","i":"RW","p":860000,"la":-1.952,"lo":30.059,"k":1,"z":5.1,"s":4},{"n":"Cottica","a":"Suriname","i":"SR","p":29210,"la":3.85,"lo":-54.233,"k":0,"z":5.6,"s":4},{"n":"Córdoba","a":"Spain","i":"ES","p":321376,"la":37.88,"lo":-4.77,"k":0,"z":5.6,"s":4},{"n":"Niamey","a":"Niger","i":"NE","p":232555,"la":13.492,"lo":7.096,"k":0,"z":5.6,"s":4},{"n":"Tahoua","a":"Niger","i":"NE","p":115956,"la":14.9,"lo":5.26,"k":0,"z":5.6,"s":4},{"n":"Constanța","a":"Romania","i":"RO","p":303399,"la":44.203,"lo":28.61,"k":0,"z":5.6,"s":4},{"n":"Luleå","a":"Sweden","i":"SE","p":48638,"la":65.597,"lo":22.158,"k":0,"z":5.1,"s":4},{"n":"Sundsvall","a":"Sweden","i":"SE","p":73389,"la":62.4,"lo":17.317,"k":0,"z":5.1,"s":4},{"n":"Iași","a":"Romania","i":"RO","p":325914,"la":47.168,"lo":27.575,"k":0,"z":5.6,"s":4},{"n":"Surat Thani","a":"Thailand","i":"TH","p":157627,"la":9.15,"lo":99.34,"k":0,"z":5.6,"s":4},{"n":"Chiang Mai","a":"Thailand","i":"TH","p":397211,"la":18.8,"lo":98.98,"k":0,"z":5.6,"s":4},{"n":"Nakhon Ratchasima","a":"Thailand","i":"TH","p":334984,"la":15,"lo":102.1,"k":0,"z":5.6,"s":4},{"n":"Mbabane","a":"eSwatini","i":"SZ","p":90138,"la":-26.317,"lo":31.133,"k":1,"z":5.6,"s":4},{"n":"Piura","a":"Peru","i":"PE","p":396932,"la":-5.21,"lo":-80.63,"k":0,"z":5.6,"s":4},{"n":"Arequipa","a":"Peru","i":"PE","p":815000,"la":-16.418,"lo":-71.532,"k":0,"z":5.6,"s":4},{"n":"Chimbote","a":"Peru","i":"PE","p":349846,"la":-9.07,"lo":-78.57,"k":0,"z":5.6,"s":4},{"n":"Pucallpa","a":"Peru","i":"PE","p":310750,"la":-8.369,"lo":-74.535,"k":0,"z":5.1,"s":4},{"n":"Iquitos","a":"Peru","i":"PE","p":458729,"la":-3.75,"lo":-73.25,"k":0,"z":5.1,"s":4},{"n":"Huancayo","a":"Peru","i":"PE","p":412733,"la":-12.08,"lo":-75.2,"k":0,"z":5.6,"s":4},{"n":"Ciudad del Este","a":"Paraguay","i":"PY","p":320872,"la":-25.517,"lo":-54.616,"k":0,"z":5.6,"s":4},{"n":"Ponta Delgada","a":"Portugal","i":"PT","p":61526,"la":37.748,"lo":-25.667,"k":0,"z":5.6,"s":4},{"n":"Vigo","a":"Spain","i":"ES","p":378952,"la":42.22,"lo":-8.73,"k":0,"z":5.6,"s":4},{"n":"Bilbao","a":"Spain","i":"ES","p":875552,"la":43.25,"lo":-2.93,"k":0,"z":5,"s":4},{"n":"Kaolack","a":"Senegal","i":"SN","p":277812,"la":14.15,"lo":-16.1,"k":0,"z":5.6,"s":4},{"n":"Kaédi","a":"Senegal","i":"SN","p":21656,"la":16.15,"lo":-13.5,"k":0,"z":5.6,"s":4},{"n":"Geneina","a":"Sudan","i":"SD","p":162981,"la":13.45,"lo":22.44,"k":0,"z":5.6,"s":4},{"n":"Medina","a":"Saudi Arabia","i":"SA","p":1010000,"la":24.502,"lo":39.578,"k":0,"z":5.1,"s":4},{"n":"Tabuk","a":"Saudi Arabia","i":"SA","p":547957,"la":28.384,"lo":36.555,"k":0,"z":5.6,"s":4},{"n":"Juba","a":"South Sudan","i":"SS","p":111975,"la":4.83,"lo":31.58,"k":0,"z":5.1,"s":4},{"n":"Malakal","a":"South Sudan","i":"SS","p":160765,"la":9.537,"lo":31.656,"k":0,"z":5.6,"s":4},{"n":"Omdurman","a":"Sudan","i":"SD","p":2395159,"la":15.617,"lo":32.48,"k":0,"z":5.6,"s":4},{"n":"Al-Ubayyid","a":"Sudan","i":"SD","p":393311,"la":13.183,"lo":30.217,"k":0,"z":5.6,"s":4},{"n":"The Hague","a":"Netherlands","i":"NL","p":1406000,"la":52.08,"lo":4.27,"k":0,"z":5.6,"s":4},{"n":"Kristiansand","a":"Norway","i":"NO","p":63814,"la":58.146,"lo":7.975,"k":0,"z":5.6,"s":4},{"n":"Ljubljana","a":"Slovenia","i":"SI","p":314807,"la":46.055,"lo":14.515,"k":1,"z":5.6,"s":4},{"n":"Bratislava","a":"Slovakia","i":"SK","p":423737,"la":48.15,"lo":17.117,"k":1,"z":5.1,"s":4},{"n":"Hammerfest","a":"Norway","i":"NO","p":10000,"la":70.661,"lo":23.688,"k":0,"z":5.1,"s":4},{"n":"Doha","a":"Qatar","i":"QA","p":1450000,"la":25.287,"lo":51.533,"k":1,"z":5.6,"s":4},{"n":"Quetta","a":"Pakistan","i":"PK","p":768000,"la":30.222,"lo":67.023,"k":0,"z":5.6,"s":4},{"n":"Larkana","a":"Pakistan","i":"PK","p":364033,"la":27.562,"lo":68.207,"k":0,"z":5.6,"s":4},{"n":"Springbok","a":"South Africa","i":"ZA","p":10438,"la":-29.667,"lo":17.883,"k":0,"z":5.6,"s":4},{"n":"Upington","a":"South Africa","i":"ZA","p":71373,"la":-28.46,"lo":21.23,"k":0,"z":5.6,"s":4},{"n":"Worcester","a":"South Africa","i":"ZA","p":127597,"la":-33.64,"lo":19.44,"k":0,"z":5.6,"s":4},{"n":"George","a":"South Africa","i":"ZA","p":174582,"la":-33.95,"lo":22.45,"k":0,"z":5.6,"s":4},{"n":"Tete","a":"Mozambique","i":"MZ","p":129316,"la":-16.17,"lo":33.58,"k":0,"z":5.6,"s":4},{"n":"Pemba","a":"Mozambique","i":"MZ","p":110643,"la":-12.983,"lo":40.532,"k":0,"z":5.6,"s":4},{"n":"Nampula","a":"Mozambique","i":"MZ","p":388526,"la":-15.136,"lo":39.293,"k":0,"z":5.6,"s":4},{"n":"Welkom","a":"South Africa","i":"ZA","p":431944,"la":-27.97,"lo":26.73,"k":0,"z":5.1,"s":4},{"n":"Xai-Xai","a":"Mozambique","i":"MZ","p":128805,"la":-25.04,"lo":33.64,"k":0,"z":5.6,"s":4},{"n":"Goroka","a":"Papua New Guinea","i":"PG","p":39699,"la":-6.083,"lo":145.385,"k":0,"z":5.6,"s":4},{"n":"Mt. Hagen","a":"Papua New Guinea","i":"PG","p":59064,"la":-5.863,"lo":144.217,"k":0,"z":5.6,"s":4},{"n":"Rabaul","a":"Papua New Guinea","i":"PG","p":8074,"la":-4.205,"lo":152.143,"k":0,"z":5.6,"s":4},{"n":"Lae","a":"Papua New Guinea","i":"PG","p":131052,"la":-6.733,"lo":146.99,"k":0,"z":5.6,"s":4},{"n":"David","a":"Panama","i":"PA","p":110037,"la":8.433,"lo":-82.433,"k":0,"z":5.6,"s":4},{"n":"Oujda","a":"Morocco","i":"MA","p":409391,"la":34.69,"lo":-1.91,"k":0,"z":5.6,"s":4},{"n":"Safi","a":"Morocco","i":"MA","p":353476,"la":32.32,"lo":-9.24,"k":0,"z":5.6,"s":4},{"n":"Podgorica","a":"Montenegro","i":"ME","p":145850,"la":42.466,"lo":19.266,"k":1,"z":5.6,"s":4},{"n":"Quelimane","a":"Mozambique","i":"MZ","p":188964,"la":-17.88,"lo":36.89,"k":0,"z":5.6,"s":4},{"n":"East London","a":"South Africa","i":"ZA","p":338627,"la":-32.97,"lo":27.87,"k":0,"z":5.6,"s":4},{"n":"Middelburg","a":"South Africa","i":"ZA","p":18164,"la":-31.5,"lo":25.01,"k":0,"z":5.1,"s":4},{"n":"Nalchik","a":"Russia","i":"RU","p":307866,"la":43.498,"lo":43.618,"k":0,"z":5.6,"s":4},{"n":"Stavropol","a":"Russia","i":"RU","p":363064,"la":45.05,"lo":41.98,"k":0,"z":5.6,"s":4},{"n":"Ugolnye Kopi","a":"Russia","i":"RU","p":3367,"la":64.733,"lo":177.7,"k":0,"z":5.6,"s":4},{"n":"Kaliningrad","a":"Russia","i":"RU","p":434954,"la":54.7,"lo":20.497,"k":0,"z":5,"s":4},{"n":"Pskov","a":"Russia","i":"RU","p":201990,"la":57.83,"lo":28.33,"k":0,"z":5.1,"s":4},{"n":"Bryansk","a":"Russia","i":"RU","p":427236,"la":53.26,"lo":34.43,"k":0,"z":5.1,"s":4},{"n":"Smolensk","a":"Russia","i":"RU","p":320991,"la":54.783,"lo":32.047,"k":0,"z":5.1,"s":4},{"n":"Petrozavodsk","a":"Russia","i":"RU","p":265025,"la":61.781,"lo":34.361,"k":0,"z":5.1,"s":4},{"n":"Tver","a":"Russia","i":"RU","p":400212,"la":56.86,"lo":35.89,"k":0,"z":5.6,"s":4},{"n":"Vologda","a":"Russia","i":"RU","p":294889,"la":59.21,"lo":39.92,"k":0,"z":5.1,"s":4},{"n":"Yaroslavl","a":"Russia","i":"RU","p":606730,"la":57.62,"lo":39.87,"k":0,"z":5.1,"s":4},{"n":"Rostov","a":"Russia","i":"RU","p":1052000,"la":47.237,"lo":39.711,"k":0,"z":5.1,"s":4},{"n":"Sochi","a":"Russia","i":"RU","p":327608,"la":43.59,"lo":39.73,"k":0,"z":5.1,"s":4},{"n":"Krasnodar","a":"Russia","i":"RU","p":649851,"la":45.02,"lo":39,"k":0,"z":5.1,"s":4},{"n":"Penza","a":"Russia","i":"RU","p":512602,"la":53.18,"lo":45,"k":0,"z":5.1,"s":4},{"n":"Ryazan","a":"Russia","i":"RU","p":520173,"la":54.62,"lo":39.72,"k":0,"z":5.6,"s":4},{"n":"Voronezh","a":"Russia","i":"RU","p":844000,"la":51.665,"lo":39.187,"k":0,"z":5.1,"s":4},{"n":"Magnitogorsk","a":"Russia","i":"RU","p":413351,"la":53.423,"lo":58.98,"k":0,"z":5.1,"s":4},{"n":"Chelyabinsk","a":"Russia","i":"RU","p":1091000,"la":55.157,"lo":61.437,"k":0,"z":5.1,"s":4},{"n":"Vorkuta","a":"Russia","i":"RU","p":80039,"la":67.5,"lo":64.01,"k":0,"z":5.6,"s":4},{"n":"Kirov","a":"Russia","i":"RU","p":457437,"la":58.59,"lo":49.67,"k":0,"z":5.1,"s":4},{"n":"Nizhny Tagil","a":"Russia","i":"RU","p":381116,"la":57.92,"lo":59.975,"k":0,"z":5.1,"s":4},{"n":"Astrakhan","a":"Russia","i":"RU","p":502533,"la":46.349,"lo":48.055,"k":0,"z":5.1,"s":4},{"n":"Orenburg","a":"Russia","i":"RU","p":550204,"la":51.78,"lo":55.11,"k":0,"z":5.1,"s":4},{"n":"Saratov","a":"Russia","i":"RU","p":843000,"la":51.582,"lo":46.028,"k":0,"z":5.1,"s":4},{"n":"Ulyanovsk","a":"Russia","i":"RU","p":640680,"la":54.33,"lo":48.41,"k":0,"z":5.6,"s":4},{"n":"Omsk","a":"Russia","i":"RU","p":1135000,"la":54.992,"lo":73.398,"k":0,"z":3.7,"s":4},{"n":"Tyumen","a":"Russia","i":"RU","p":519119,"la":57.14,"lo":65.53,"k":0,"z":5.6,"s":4},{"n":"Novokuznetsk","a":"Russia","i":"RU","p":539616,"la":53.75,"lo":87.115,"k":0,"z":5.1,"s":4},{"n":"Kemerovo","a":"Russia","i":"RU","p":477090,"la":55.34,"lo":86.09,"k":0,"z":5.1,"s":4},{"n":"Grozny","a":"Russia","i":"RU","p":226100,"la":43.319,"lo":45.699,"k":0,"z":5,"s":4},{"n":"Kandy","a":"Sri Lanka","i":"LK","p":111701,"la":7.28,"lo":80.67,"k":0,"z":5.6,"s":4},{"n":"Sri Jayawardenepura Kotte","a":"Sri Lanka","i":"LK","p":115826,"la":6.9,"lo":79.95,"k":0,"z":5.6,"s":4},{"n":"Daejeon","a":"South Korea","i":"KR","p":1468000,"la":36.337,"lo":127.423,"k":0,"z":5.1,"s":4},{"n":"Gwangju","a":"South Korea","i":"KR","p":1440000,"la":35.173,"lo":126.908,"k":0,"z":5.6,"s":4},{"n":"Busan","a":"South Korea","i":"KR","p":3480000,"la":35.097,"lo":129.008,"k":0,"z":5.6,"s":4},{"n":"Zamboanga","a":"Philippines","i":"PH","p":773000,"la":6.922,"lo":122.078,"k":0,"z":5.6,"s":4},{"n":"Laoag","a":"Philippines","i":"PH","p":207048,"la":18.199,"lo":120.594,"k":0,"z":5.6,"s":4},{"n":"Baguio","a":"Philippines","i":"PH","p":447824,"la":16.43,"lo":120.57,"k":0,"z":5.6,"s":4},{"n":"General Santos","a":"Philippines","i":"PH","p":950530,"la":6.111,"lo":125.175,"k":0,"z":5.6,"s":4},{"n":"Ust-Ilimsk","a":"Russia","i":"RU","p":100271,"la":57.99,"lo":102.633,"k":0,"z":5.6,"s":4},{"n":"Angarsk","a":"Russia","i":"RU","p":243158,"la":52.56,"lo":103.92,"k":0,"z":5.6,"s":4},{"n":"Abakan","a":"Russia","i":"RU","p":167289,"la":53.704,"lo":91.445,"k":0,"z":5.6,"s":4},{"n":"Norilsk","a":"Russia","i":"RU","p":165873,"la":69.34,"lo":88.225,"k":0,"z":5.6,"s":4},{"n":"Khatanga","a":"Russia","i":"RU","p":3205,"la":72.041,"lo":102.465,"k":0,"z":5.6,"s":4},{"n":"Kyzyl","a":"Russia","i":"RU","p":108240,"la":51.707,"lo":94.383,"k":0,"z":5.6,"s":4},{"n":"Ulan-Ude","a":"Russia","i":"RU","p":360278,"la":51.825,"lo":107.625,"k":0,"z":5.6,"s":4},{"n":"Blagoveshchensk","a":"Russia","i":"RU","p":221296,"la":50.267,"lo":127.533,"k":0,"z":5.6,"s":4},{"n":"Bukachacha","a":"Russia","i":"RU","p":3372,"la":52.983,"lo":116.917,"k":0,"z":5.6,"s":4},{"n":"Dalnegorsk","a":"Russia","i":"RU","p":8123,"la":44.537,"lo":135.517,"k":0,"z":5.6,"s":4},{"n":"Ambarchik","a":"Russia","i":"RU","p":0,"la":69.651,"lo":162.334,"k":0,"z":5.6,"s":4},{"n":"Batagay","a":"Russia","i":"RU","p":4266,"la":67.656,"lo":134.635,"k":0,"z":5.6,"s":4},{"n":"Chokurdakh","a":"Russia","i":"RU","p":2506,"la":70.618,"lo":147.895,"k":0,"z":5.6,"s":4},{"n":"Ust-Nera","a":"Russia","i":"RU","p":9148,"la":64.567,"lo":143.2,"k":0,"z":5.6,"s":4},{"n":"Lensk","a":"Russia","i":"RU","p":25589,"la":60.725,"lo":114.947,"k":0,"z":5.6,"s":4},{"n":"Aldan","a":"Russia","i":"RU","p":24426,"la":58.603,"lo":125.389,"k":0,"z":5.6,"s":4},{"n":"Mirny","a":"Russia","i":"RU","p":40308,"la":62.54,"lo":113.961,"k":0,"z":5.1,"s":4},{"n":"Zhigansk","a":"Russia","i":"RU","p":3237,"la":66.77,"lo":123.371,"k":0,"z":5.1,"s":4},{"n":"Okhotsk","a":"Russia","i":"RU","p":5570,"la":59.383,"lo":143.217,"k":0,"z":5.1,"s":4},{"n":"Khabarovsk","a":"Russia","i":"RU","p":579000,"la":48.455,"lo":135.12,"k":0,"z":5.6,"s":4},{"n":"Okha","a":"Russia","i":"RU","p":26560,"la":53.574,"lo":142.948,"k":0,"z":5.6,"s":4},{"n":"Yuzhno Sakhalinsk","a":"Russia","i":"RU","p":176484,"la":46.965,"lo":142.74,"k":0,"z":5.1,"s":4},{"n":"Mexicali","a":"Mexico","i":"MX","p":885000,"la":32.652,"lo":-115.482,"k":0,"z":5.6,"s":4},{"n":"La Paz","a":"Mexico","i":"MX","p":189767,"la":24.14,"lo":-110.32,"k":0,"z":5.6,"s":4},{"n":"Torreón","a":"Mexico","i":"MX","p":1144000,"la":25.572,"lo":-103.422,"k":0,"z":5.6,"s":4},{"n":"Culiacán","a":"Mexico","i":"MX","p":809000,"la":24.832,"lo":-107.382,"k":0,"z":5.6,"s":4},{"n":"Nogales","a":"Mexico","i":"MX","p":178097,"la":31.305,"lo":-110.945,"k":0,"z":5.6,"s":4},{"n":"Hermosillo","a":"Mexico","i":"MX","p":595811,"la":29.099,"lo":-110.954,"k":0,"z":5.6,"s":4},{"n":"Guaymas","a":"Mexico","i":"MX","p":103449,"la":27.93,"lo":-110.89,"k":0,"z":5.6,"s":4},{"n":"San Luis Potosí","a":"Mexico","i":"MX","p":992000,"la":22.172,"lo":-101.002,"k":0,"z":5.6,"s":4},{"n":"Matamoros","a":"Mexico","i":"MX","p":538785,"la":25.88,"lo":-97.5,"k":0,"z":5.6,"s":4},{"n":"Nuevo Laredo","a":"Mexico","i":"MX","p":349550,"la":27.5,"lo":-99.55,"k":0,"z":5.6,"s":4},{"n":"Colima","a":"Mexico","i":"MX","p":223287,"la":19.23,"lo":-103.72,"k":0,"z":5.6,"s":4},{"n":"Campeche","a":"Mexico","i":"MX","p":205212,"la":19.83,"lo":-90.5,"k":0,"z":5.6,"s":4},{"n":"Oaxaca","a":"Mexico","i":"MX","p":530728,"la":17.083,"lo":-96.67,"k":0,"z":5.1,"s":4},{"n":"León","a":"Mexico","i":"MX","p":1488000,"la":21.152,"lo":-101.702,"k":0,"z":5.6,"s":4},{"n":"Maiduguri","a":"Nigeria","i":"NG","p":896000,"la":11.852,"lo":13.158,"k":0,"z":5.6,"s":4},{"n":"Port Harcourt","a":"Nigeria","i":"NG","p":1020000,"la":4.812,"lo":7.008,"k":0,"z":5.6,"s":4},{"n":"Makurdi","a":"Nigeria","i":"NG","p":292645,"la":7.73,"lo":8.53,"k":0,"z":5.6,"s":4},{"n":"Ibadan","a":"Nigeria","i":"NG","p":2628000,"la":7.382,"lo":3.928,"k":0,"z":5.1,"s":4},{"n":"Ogbomosho","a":"Nigeria","i":"NG","p":951000,"la":8.132,"lo":4.238,"k":0,"z":5.6,"s":4},{"n":"Warri","a":"Nigeria","i":"NG","p":830106,"la":5.52,"lo":5.76,"k":0,"z":5.6,"s":4},{"n":"Kaduna","a":"Nigeria","i":"NG","p":1442000,"la":10.522,"lo":7.438,"k":0,"z":5.6,"s":4},{"n":"Gdańsk","a":"Poland","i":"PL","p":740000,"la":54.36,"lo":18.64,"k":0,"z":5,"s":4},{"n":"Kraków","a":"Poland","i":"PL","p":756000,"la":50.062,"lo":19.958,"k":0,"z":5.1,"s":4},{"n":"Dalandzadgad","a":"Mongolia","i":"MN","p":15093,"la":43.584,"lo":104.44,"k":0,"z":5.6,"s":4},{"n":"Wonsan","a":"North Korea","i":"KP","p":329207,"la":39.16,"lo":127.431,"k":0,"z":5.6,"s":4},{"n":"Sinuiju","a":"North Korea","i":"KP","p":288112,"la":40.086,"lo":124.421,"k":0,"z":5.6,"s":4},{"n":"Dund-Us","a":"Mongolia","i":"MN","p":30500,"la":48.017,"lo":91.633,"k":0,"z":5.6,"s":4},{"n":"Choibalsan","a":"Mongolia","i":"MN","p":33376,"la":48.067,"lo":114.506,"k":0,"z":5.6,"s":4},{"n":"Lüderitz","a":"Namibia","i":"NA","p":15137,"la":-26.648,"lo":15.159,"k":0,"z":5.6,"s":4},{"n":"Walvis Bay","a":"Namibia","i":"NA","p":52058,"la":-22.958,"lo":14.505,"k":0,"z":5.6,"s":4},{"n":"Mwanza","a":"Tanzania","i":"TZ","p":493944,"la":-2.52,"lo":32.93,"k":0,"z":5.6,"s":4},{"n":"Morogoro","a":"Tanzania","i":"TZ","p":250902,"la":-6.82,"lo":37.66,"k":0,"z":5.6,"s":4},{"n":"Dodoma","a":"Tanzania","i":"TZ","p":218269,"la":-6.183,"lo":35.75,"k":0,"z":5.6,"s":4},{"n":"Arusha","a":"Tanzania","i":"TZ","p":341136,"la":-3.36,"lo":36.67,"k":0,"z":5.6,"s":4},{"n":"Bern","a":"Switzerland","i":"CH","p":275329,"la":46.917,"lo":7.467,"k":1,"z":5.6,"s":4},{"n":"Malmö","a":"Sweden","i":"SE","p":269349,"la":55.599,"lo":13.023,"k":0,"z":5.1,"s":4},{"n":"Laayoune","a":"Morocco","i":"MA","p":188084,"la":27.15,"lo":-13.2,"k":0,"z":5.6,"s":4},{"n":"Ternate","a":"Indonesia","i":"ID","p":187521,"la":0.793,"lo":127.363,"k":0,"z":5.6,"s":4},{"n":"Ambon","a":"Indonesia","i":"ID","p":355596,"la":-3.717,"lo":128.2,"k":0,"z":5.6,"s":4},{"n":"Raba","a":"Indonesia","i":"ID","p":106101,"la":-8.45,"lo":118.767,"k":0,"z":5.6,"s":4},{"n":"Jayapura","a":"Indonesia","i":"ID","p":169341,"la":-2.533,"lo":140.7,"k":0,"z":5.6,"s":4},{"n":"Florence","a":"Italy","i":"IT","p":1500000,"la":43.78,"lo":11.25,"k":0,"z":5.6,"s":4},{"n":"Catania","a":"Italy","i":"IT","p":674542,"la":37.5,"lo":15.08,"k":0,"z":5.6,"s":4},{"n":"Pristina","a":"Kosovo","i":"-99","p":465186,"la":42.667,"lo":21.166,"k":1,"z":5.6,"s":4},{"n":"Meru","a":"Kenya","i":"KE","p":47226,"la":0.06,"lo":37.64,"k":0,"z":5.6,"s":4},{"n":"Eldoret","a":"Kenya","i":"KE","p":353381,"la":0.52,"lo":35.27,"k":0,"z":5.6,"s":4},{"n":"Banda Aceh","a":"Indonesia","i":"ID","p":513698,"la":5.55,"lo":95.32,"k":0,"z":5.6,"s":4},{"n":"George Town","a":"Malaysia","i":"MY","p":2500000,"la":5.414,"lo":100.329,"k":0,"z":5.1,"s":4},{"n":"Zhangye","a":"China","i":"CN","p":230728,"la":38.93,"lo":100.45,"k":0,"z":5.6,"s":4},{"n":"Wuwei","a":"China","i":"CN","p":493092,"la":37.928,"lo":102.641,"k":0,"z":5.6,"s":4},{"n":"Dunhuang","a":"China","i":"CN","p":140094,"la":40.143,"lo":94.662,"k":0,"z":5,"s":4},{"n":"Tianshui","a":"China","i":"CN","p":1225000,"la":34.602,"lo":105.918,"k":0,"z":5.6,"s":4},{"n":"Dulan","a":"China","i":"CN","p":100,"la":36.167,"lo":98.267,"k":0,"z":5.6,"s":4},{"n":"Golmud","a":"China","i":"CN","p":107092,"la":36.417,"lo":94.883,"k":0,"z":5,"s":4},{"n":"Yulin","a":"China","i":"CN","p":1127000,"la":22.632,"lo":110.148,"k":0,"z":5.6,"s":4},{"n":"Bose","a":"China","i":"CN","p":175282,"la":23.9,"lo":106.613,"k":0,"z":5.6,"s":4},{"n":"Wuzhou","a":"China","i":"CN","p":442315,"la":23.48,"lo":111.32,"k":0,"z":5.6,"s":4},{"n":"Liupanshui","a":"China","i":"CN","p":1221000,"la":26.596,"lo":104.831,"k":0,"z":5.6,"s":4},{"n":"Quanzhou","a":"China","i":"CN","p":1463000,"la":24.902,"lo":118.578,"k":0,"z":5.6,"s":4},{"n":"Hefei","a":"China","i":"CN","p":2035000,"la":31.852,"lo":117.278,"k":0,"z":5.6,"s":4},{"n":"Suzhou","a":"China","i":"CN","p":1964000,"la":33.638,"lo":116.977,"k":0,"z":5.6,"s":4},{"n":"Zhanjiang","a":"China","i":"CN","p":1590000,"la":21.202,"lo":110.378,"k":0,"z":5.6,"s":4},{"n":"Shaoguan","a":"China","i":"CN","p":720266,"la":24.8,"lo":113.58,"k":0,"z":5.1,"s":4},{"n":"Balikpapan","a":"Indonesia","i":"ID","p":445905,"la":-1.25,"lo":116.83,"k":0,"z":5.6,"s":4},{"n":"Kuching","a":"Malaysia","i":"MY","p":570407,"la":1.53,"lo":110.33,"k":0,"z":5.6,"s":4},{"n":"Antsiranana","a":"Madagascar","i":"MG","p":82937,"la":-12.277,"lo":49.312,"k":0,"z":5.6,"s":4},{"n":"Fianarantsoa","a":"Madagascar","i":"MG","p":184184,"la":-21.433,"lo":47.083,"k":0,"z":5.6,"s":4},{"n":"Mahajanga","a":"Madagascar","i":"MG","p":154657,"la":-15.67,"lo":46.345,"k":0,"z":5.6,"s":4},{"n":"Toliara","a":"Madagascar","i":"MG","p":115319,"la":-23.357,"lo":43.69,"k":0,"z":5.1,"s":4},{"n":"Surakarta","a":"Indonesia","i":"ID","p":555308,"la":-7.565,"lo":110.825,"k":0,"z":5.6,"s":4},{"n":"Bandar Lampung","a":"Indonesia","i":"ID","p":865000,"la":-5.428,"lo":105.268,"k":0,"z":5.6,"s":4},{"n":"Tanjung Pandan","a":"Indonesia","i":"ID","p":62374,"la":-2.75,"lo":107.65,"k":0,"z":5.6,"s":4},{"n":"Malang","a":"Indonesia","i":"ID","p":805000,"la":-7.978,"lo":112.608,"k":0,"z":5.6,"s":4},{"n":"Kupang","a":"Indonesia","i":"ID","p":282396,"la":-10.179,"lo":123.583,"k":0,"z":5.6,"s":4},{"n":"Parepare","a":"Indonesia","i":"ID","p":87776,"la":-4.017,"lo":119.633,"k":0,"z":5.6,"s":4},{"n":"Cuenca","a":"Ecuador","i":"EC","p":286878,"la":-2.9,"lo":-79,"k":0,"z":5.6,"s":4},{"n":"Santa Cruz","a":"Ecuador","i":"EC","p":11262,"la":-0.533,"lo":-90.35,"k":0,"z":5.6,"s":4},{"n":"Limón","a":"Costa Rica","i":"CR","p":85001,"la":10,"lo":-83.033,"k":0,"z":5.6,"s":4},{"n":"Santiago de Cuba","a":"Cuba","i":"CU","p":555865,"la":20.025,"lo":-75.821,"k":0,"z":5.1,"s":4},{"n":"Santiago","a":"Dominican Republic","i":"DO","p":1550753,"la":19.5,"lo":-70.67,"k":0,"z":5.6,"s":4},{"n":"Manizales","a":"Colombia","i":"CO","p":375848,"la":5.06,"lo":-75.52,"k":0,"z":5.6,"s":4},{"n":"Pasto","a":"Colombia","i":"CO","p":382236,"la":1.214,"lo":-77.281,"k":0,"z":5.6,"s":4},{"n":"Barranquilla","a":"Colombia","i":"CO","p":1798000,"la":10.962,"lo":-74.802,"k":0,"z":5.6,"s":4},{"n":"Roseau","a":"Dominica","i":"DM","p":23336,"la":15.301,"lo":-61.387,"k":1,"z":5.1,"s":4},{"n":"Mbandaka","a":"Congo (Kinshasa)","i":"CD","p":274996,"la":0.04,"lo":18.26,"k":0,"z":5.6,"s":4},{"n":"Moundou","a":"Chad","i":"TD","p":156705,"la":8.55,"lo":16.09,"k":0,"z":5.6,"s":4},{"n":"Suez","a":"Egypt","i":"EG","p":508335,"la":30.005,"lo":32.55,"k":0,"z":5.6,"s":4},{"n":"Bur Said","a":"Egypt","i":"EG","p":623864,"la":31.26,"lo":32.29,"k":0,"z":5.6,"s":4},{"n":"El Faiyum","a":"Egypt","i":"EG","p":316772,"la":29.31,"lo":30.84,"k":0,"z":5.6,"s":4},{"n":"Aswan","a":"Egypt","i":"EG","p":313442,"la":24.088,"lo":32.899,"k":0,"z":5.6,"s":4},{"n":"Asyut","a":"Egypt","i":"EG","p":420585,"la":27.19,"lo":31.18,"k":0,"z":5.6,"s":4},{"n":"Kisangani","a":"Congo (Kinshasa)","i":"CD","p":578470,"la":0.52,"lo":25.22,"k":0,"z":5.6,"s":4},{"n":"Assab","a":"Eritrea","i":"ER","p":105496,"la":13.01,"lo":42.73,"k":0,"z":5.6,"s":4},{"n":"Djibouti","a":"Djibouti","i":"DJ","p":923000,"la":11.595,"lo":43.148,"k":1,"z":5.6,"s":4},{"n":"Dresden","a":"Germany","i":"DE","p":617515,"la":51.05,"lo":13.75,"k":0,"z":5,"s":4},{"n":"Xigaze","a":"China","i":"CN","p":80000,"la":29.25,"lo":88.883,"k":0,"z":5,"s":4},{"n":"Shache","a":"China","i":"CN","p":851374,"la":38.426,"lo":77.25,"k":0,"z":5.6,"s":4},{"n":"Yining","a":"China","i":"CN","p":542507,"la":43.9,"lo":81.35,"k":0,"z":5.1,"s":4},{"n":"Altay","a":"China","i":"CN","p":142000,"la":47.867,"lo":88.117,"k":0,"z":5.6,"s":4},{"n":"Putrajaya","a":"Malaysia","i":"MY","p":67964,"la":2.933,"lo":101.695,"k":0,"z":5.6,"s":4},{"n":"Shizuishan","a":"China","i":"CN","p":136570,"la":39.233,"lo":106.769,"k":0,"z":5.6,"s":4},{"n":"Yulin","a":"China","i":"CN","p":155960,"la":38.283,"lo":109.733,"k":0,"z":5.6,"s":4},{"n":"Ankang","a":"China","i":"CN","p":1100000,"la":32.68,"lo":109.02,"k":0,"z":5.6,"s":4},{"n":"Houma","a":"China","i":"CN","p":102400,"la":35.62,"lo":111.21,"k":0,"z":5.1,"s":4},{"n":"Yueyang","a":"China","i":"CN","p":826000,"la":29.382,"lo":113.098,"k":0,"z":5.6,"s":4},{"n":"Hengyang","a":"China","i":"CN","p":1016000,"la":26.882,"lo":112.588,"k":0,"z":5.6,"s":4},{"n":"Mianyang","a":"China","i":"CN","p":1396000,"la":31.465,"lo":104.69,"k":0,"z":5.6,"s":4},{"n":"Xichang","a":"China","i":"CN","p":379993,"la":27.88,"lo":102.3,"k":0,"z":5,"s":4},{"n":"Baoshan","a":"China","i":"CN","p":1000000,"la":25.12,"lo":99.15,"k":0,"z":5.6,"s":4},{"n":"Gejiu","a":"China","i":"CN","p":149105,"la":23.38,"lo":103.15,"k":0,"z":5.1,"s":4},{"n":"Shijiazhuang","a":"China","i":"CN","p":2417000,"la":38.052,"lo":114.478,"k":0,"z":5.6,"s":4},{"n":"Handan","a":"China","i":"CN","p":1631000,"la":36.582,"lo":114.478,"k":0,"z":5.1,"s":4},{"n":"Anshan","a":"China","i":"CN","p":1639000,"la":41.117,"lo":122.938,"k":0,"z":5.6,"s":4},{"n":"Dalian","a":"China","i":"CN","p":3167000,"la":38.925,"lo":121.628,"k":0,"z":5.1,"s":4},{"n":"Qingdao","a":"China","i":"CN","p":2866000,"la":36.092,"lo":120.328,"k":0,"z":5,"s":4},{"n":"Linyi","a":"China","i":"CN","p":2082000,"la":35.082,"lo":118.328,"k":0,"z":5.6,"s":4},{"n":"Huaiyin","a":"China","i":"CN","p":1264000,"la":33.585,"lo":119.066,"k":0,"z":5.6,"s":4},{"n":"Wenzhou","a":"China","i":"CN","p":2350000,"la":28.022,"lo":120.648,"k":0,"z":5.1,"s":4},{"n":"Ningbo","a":"China","i":"CN","p":1923000,"la":29.882,"lo":121.548,"k":0,"z":5.1,"s":4},{"n":"Fukuoka","a":"Japan","i":"JP","p":2792000,"la":33.597,"lo":130.408,"k":0,"z":5.6,"s":4},{"n":"Miyazaki","a":"Japan","i":"JP","p":324384,"la":31.918,"lo":131.418,"k":0,"z":5.6,"s":4},{"n":"Naha","a":"Japan","i":"JP","p":905238,"la":26.207,"lo":127.673,"k":0,"z":5.1,"s":4},{"n":"Kōchi","a":"Japan","i":"JP","p":335570,"la":33.562,"lo":133.538,"k":0,"z":5.6,"s":4},{"n":"Gorontalo","a":"Indonesia","i":"ID","p":365497,"la":0.55,"lo":123.07,"k":0,"z":5.6,"s":4},{"n":"Tongliao","a":"China","i":"CN","p":884000,"la":43.622,"lo":122.268,"k":0,"z":5.6,"s":4},{"n":"Hohhot","a":"China","i":"CN","p":1726000,"la":40.822,"lo":111.658,"k":0,"z":5.1,"s":4},{"n":"Chifeng","a":"China","i":"CN","p":1277000,"la":42.272,"lo":118.948,"k":0,"z":5.6,"s":4},{"n":"Ulan Hot","a":"China","i":"CN","p":241894,"la":46.08,"lo":122.08,"k":0,"z":5.6,"s":4},{"n":"Hailar","a":"China","i":"CN","p":231171,"la":49.2,"lo":119.7,"k":0,"z":5.6,"s":4},{"n":"Jiamusi","a":"China","i":"CN","p":1020000,"la":46.832,"lo":130.348,"k":0,"z":5.1,"s":4},{"n":"Bei'an","a":"China","i":"CN","p":154936,"la":48.246,"lo":126.529,"k":0,"z":5.6,"s":4},{"n":"Daqing","a":"China","i":"CN","p":1693000,"la":46.582,"lo":124.998,"k":0,"z":5.1,"s":4},{"n":"Jixi","a":"China","i":"CN","p":965000,"la":45.302,"lo":130.968,"k":0,"z":5.6,"s":4},{"n":"Nagoya","a":"Japan","i":"JP","p":3230000,"la":35.157,"lo":136.913,"k":0,"z":5.1,"s":4},{"n":"Nagano","a":"Japan","i":"JP","p":594311,"la":36.65,"lo":138.17,"k":0,"z":5.6,"s":4},{"n":"Kushiro","a":"Japan","i":"JP","p":198566,"la":42.975,"lo":144.375,"k":0,"z":5.1,"s":4},{"n":"Hakodate","a":"Japan","i":"JP","p":302984,"la":41.795,"lo":140.74,"k":0,"z":5.6,"s":4},{"n":"Kyoto","a":"Japan","i":"JP","p":1805000,"la":35.032,"lo":135.748,"k":0,"z":5.1,"s":4},{"n":"Sendai","a":"Japan","i":"JP","p":2250000,"la":38.268,"lo":140.87,"k":0,"z":5.1,"s":4},{"n":"Sakata","a":"Japan","i":"JP","p":100446,"la":38.92,"lo":139.85,"k":0,"z":5.6,"s":4},{"n":"Bandundu","a":"Congo (Kinshasa)","i":"CD","p":118211,"la":-3.31,"lo":17.38,"k":0,"z":5.6,"s":4},{"n":"Kananga","a":"Congo (Kinshasa)","i":"CD","p":765000,"la":-5.888,"lo":22.398,"k":0,"z":5.6,"s":4},{"n":"Kasongo","a":"Congo (Kinshasa)","i":"CD","p":63000,"la":-4.45,"lo":26.66,"k":0,"z":5.6,"s":4},{"n":"Mbuji-Mayi","a":"Congo (Kinshasa)","i":"CD","p":1295000,"la":-6.148,"lo":23.598,"k":0,"z":5.6,"s":4},{"n":"Kalemie","a":"Congo (Kinshasa)","i":"CD","p":206257,"la":-5.933,"lo":29.2,"k":0,"z":5.6,"s":4},{"n":"Butembo","a":"Congo (Kinshasa)","i":"CD","p":286403,"la":0.13,"lo":29.28,"k":0,"z":5.6,"s":4},{"n":"Goma","a":"Congo (Kinshasa)","i":"CD","p":144124,"la":-1.679,"lo":29.222,"k":0,"z":5.1,"s":4},{"n":"Mzuzu","a":"Malawi","i":"MW","p":110201,"la":-11.46,"lo":34.02,"k":0,"z":5.6,"s":4},{"n":"Blantyre","a":"Malawi","i":"MW","p":584877,"la":-15.79,"lo":34.99,"k":0,"z":5.6,"s":4},{"n":"Quetzaltenango","a":"Guatemala","i":"GT","p":575263,"la":14.83,"lo":-91.52,"k":0,"z":5.6,"s":4},{"n":"Banjul","a":"The Gambia","i":"GM","p":43094,"la":13.454,"lo":-16.592,"k":1,"z":5.6,"s":4},{"n":"Faridabad","a":"India","i":"IN","p":1394000,"la":28.435,"lo":77.315,"k":0,"z":5.6,"s":4},{"n":"Srinagar","a":"India","i":"IN","p":1140000,"la":34.069,"lo":74.828,"k":0,"z":5.6,"s":4},{"n":"Vijayawada","a":"India","i":"IN","p":1137000,"la":16.522,"lo":80.628,"k":0,"z":5.6,"s":4},{"n":"Thiruvananthapuram","a":"India","i":"IN","p":954000,"la":8.516,"lo":76.925,"k":0,"z":5.6,"s":4},{"n":"Kochi","a":"India","i":"IN","p":1519000,"la":10.017,"lo":76.222,"k":0,"z":5.6,"s":4},{"n":"Cuttack","a":"India","i":"IN","p":580000,"la":20.47,"lo":85.89,"k":0,"z":5.6,"s":4},{"n":"Hubballi","a":"India","i":"IN","p":890000,"la":15.362,"lo":75.123,"k":0,"z":5.6,"s":4},{"n":"Mangaluru","a":"India","i":"IN","p":776632,"la":12.9,"lo":74.85,"k":0,"z":5.6,"s":4},{"n":"Mysuru","a":"India","i":"IN","p":887000,"la":12.312,"lo":76.658,"k":0,"z":5.6,"s":4},{"n":"Kalaburagi","a":"India","i":"IN","p":483615,"la":17.35,"lo":76.82,"k":0,"z":5.6,"s":4},{"n":"Kolhapur","a":"India","i":"IN","p":750000,"la":16.7,"lo":74.22,"k":0,"z":5.1,"s":4},{"n":"Nanded","a":"India","i":"IN","p":623708,"la":19.197,"lo":77.308,"k":0,"z":5.6,"s":4},{"n":"Akola","a":"India","i":"IN","p":503502,"la":20.71,"lo":77.01,"k":0,"z":5.6,"s":4},{"n":"Guwahati","a":"India","i":"IN","p":983000,"la":26.162,"lo":91.768,"k":0,"z":5.6,"s":4},{"n":"Kayes","a":"Congo (Brazzaville)","i":"CG","p":62521,"la":-4.18,"lo":13.28,"k":0,"z":5.6,"s":4},{"n":"Franceville","a":"Gabon","i":"GA","p":42967,"la":-1.633,"lo":13.583,"k":0,"z":5.6,"s":4},{"n":"Bordeaux","a":"France","i":"FR","p":803000,"la":44.852,"lo":-0.597,"k":0,"z":5,"s":4},{"n":"Marseille","a":"France","i":"FR","p":1400000,"la":43.292,"lo":5.373,"k":0,"z":5,"s":4},{"n":"Le Havre","a":"France","i":"FR","p":242124,"la":49.505,"lo":0.105,"k":0,"z":5,"s":4},{"n":"Gao","a":"Mali","i":"ML","p":116967,"la":16.267,"lo":-0.05,"k":0,"z":5.1,"s":4},{"n":"Coihaique","a":"Chile","i":"CL","p":45787,"la":-45.57,"lo":-72.07,"k":0,"z":5.6,"s":4},{"n":"Arica","a":"Chile","i":"CL","p":185999,"la":-18.5,"lo":-70.29,"k":0,"z":5.6,"s":4},{"n":"Copiapó","a":"Chile","i":"CL","p":129280,"la":-27.372,"lo":-70.33,"k":0,"z":5.6,"s":4},{"n":"La Serena","a":"Chile","i":"CL","p":154521,"la":-29.9,"lo":-71.25,"k":0,"z":5.6,"s":4},{"n":"Los Angeles","a":"Chile","i":"CL","p":145239,"la":-37.48,"lo":-72.347,"k":0,"z":5.6,"s":4},{"n":"Narsarsuaq","a":"Greenland","i":"GL","p":145,"la":61.167,"lo":-45.417,"k":0,"z":5.1,"s":4},{"n":"Sisimiut","a":"Greenland","i":"GL","p":5227,"la":66.95,"lo":-53.667,"k":0,"z":5.1,"s":4},{"n":"Upernavik","a":"Greenland","i":"GL","p":1129,"la":72.709,"lo":-56.142,"k":0,"z":5.1,"s":4},{"n":"Qaanaaq","a":"Greenland","i":"GL","p":616,"la":77.467,"lo":-69.23,"k":0,"z":5.6,"s":4},{"n":"Nouadhibou","a":"Mauritania","i":"MR","p":86738,"la":20.9,"lo":-17.056,"k":0,"z":5.6,"s":4},{"n":"Kayes","a":"Mali","i":"ML","p":77207,"la":14.45,"lo":-11.44,"k":0,"z":5.6,"s":4},{"n":"Ayoun el Atrous","a":"Mauritania","i":"MR","p":1423,"la":16.667,"lo":-9.617,"k":0,"z":5.6,"s":4},{"n":"Ségou","a":"Mali","i":"ML","p":122952,"la":13.44,"lo":-6.26,"k":0,"z":5.6,"s":4},{"n":"Skopje","a":"North Macedonia","i":"MK","p":494087,"la":42,"lo":21.433,"k":1,"z":5.6,"s":4},{"n":"Al Jawf","a":"Libya","i":"LY","p":24132,"la":24.2,"lo":23.29,"k":0,"z":5.6,"s":4},{"n":"Tmassa","a":"Libya","i":"LY","p":500,"la":26.367,"lo":15.8,"k":0,"z":5.6,"s":4},{"n":"Misrata","a":"Libya","i":"LY","p":386120,"la":32.38,"lo":15.1,"k":0,"z":5.6,"s":4},{"n":"Zuwara","a":"Libya","i":"LY","p":180310,"la":32.934,"lo":12.079,"k":0,"z":5.6,"s":4},{"n":"Kirkuk","a":"Iraq","i":"IQ","p":601433,"la":35.472,"lo":44.392,"k":0,"z":5.1,"s":4},{"n":"Mosul","a":"Iraq","i":"IQ","p":1316000,"la":36.347,"lo":43.143,"k":0,"z":5.6,"s":4},{"n":"Najaf","a":"Iraq","i":"IQ","p":665552,"la":32,"lo":44.335,"k":0,"z":5.6,"s":4},{"n":"Bahir Dar","a":"Ethiopia","i":"ET","p":206748,"la":11.6,"lo":37.383,"k":0,"z":5.6,"s":4},{"n":"Mekele","a":"Ethiopia","i":"ET","p":95856,"la":13.5,"lo":39.47,"k":0,"z":5.6,"s":4},{"n":"Dire Dawa","a":"Ethiopia","i":"ET","p":252279,"la":9.59,"lo":41.86,"k":0,"z":5.6,"s":4},{"n":"Rovaniemi","a":"Finland","i":"FI","p":34781,"la":66.5,"lo":25.716,"k":0,"z":5.1,"s":4},{"n":"Vaasa","a":"Finland","i":"FI","p":57014,"la":63.1,"lo":21.6,"k":0,"z":5.1,"s":4},{"n":"Tampere","a":"Finland","i":"FI","p":259279,"la":61.5,"lo":23.75,"k":0,"z":5.1,"s":4},{"n":"Aqtobe","a":"Kazakhstan","i":"KZ","p":262457,"la":50.28,"lo":57.17,"k":0,"z":5.6,"s":4},{"n":"Rudniy","a":"Kazakhstan","i":"KZ","p":124000,"la":52.953,"lo":63.13,"k":0,"z":5.6,"s":4},{"n":"Qyzylorda","a":"Kazakhstan","i":"KZ","p":300000,"la":44.8,"lo":65.465,"k":0,"z":5.6,"s":4},{"n":"Atyrau","a":"Kazakhstan","i":"KZ","p":182753,"la":47.113,"lo":51.92,"k":0,"z":5,"s":4},{"n":"Ekibastuz","a":"Kazakhstan","i":"KZ","p":127868,"la":51.73,"lo":75.32,"k":0,"z":5.6,"s":4},{"n":"Pavlodar","a":"Kazakhstan","i":"KZ","p":329002,"la":52.3,"lo":76.95,"k":0,"z":5.6,"s":4},{"n":"Semey","a":"Kazakhstan","i":"KZ","p":311353,"la":50.435,"lo":80.275,"k":0,"z":5.6,"s":4},{"n":"Oskemen","a":"Kazakhstan","i":"KZ","p":319067,"la":49.99,"lo":82.615,"k":0,"z":5.6,"s":4},{"n":"Yazd","a":"Iran","i":"IR","p":477905,"la":31.92,"lo":54.37,"k":0,"z":5.6,"s":4},{"n":"Ahvaz","a":"Iran","i":"IR","p":996000,"la":31.282,"lo":48.718,"k":0,"z":5.1,"s":4},{"n":"Basra","a":"Iraq","i":"IQ","p":870000,"la":30.515,"lo":47.812,"k":0,"z":5.1,"s":4},{"n":"Bandar-e-Abbas","a":"Iran","i":"IR","p":461499,"la":27.204,"lo":56.272,"k":0,"z":5.6,"s":4},{"n":"Hamadan","a":"Iran","i":"IR","p":528256,"la":34.796,"lo":48.515,"k":0,"z":5.6,"s":4},{"n":"Tabriz","a":"Iran","i":"IR","p":1413000,"la":38.088,"lo":46.299,"k":0,"z":5.1,"s":4},{"n":"Ludhiana","a":"India","i":"IN","p":1649000,"la":30.93,"lo":75.87,"k":0,"z":5.6,"s":4},{"n":"Kota","a":"India","i":"IN","p":827000,"la":25.182,"lo":75.833,"k":0,"z":5.6,"s":4},{"n":"Jodhpur","a":"India","i":"IN","p":995000,"la":26.294,"lo":73.015,"k":0,"z":5.6,"s":4},{"n":"Shymkent","a":"Kazakhstan","i":"KZ","p":465392,"la":42.32,"lo":69.595,"k":0,"z":5.6,"s":4},{"n":"Taraz","a":"Kazakhstan","i":"KZ","p":358153,"la":42.9,"lo":71.365,"k":0,"z":5.6,"s":4},{"n":"Lucknow","a":"India","i":"IN","p":2695000,"la":26.857,"lo":80.913,"k":0,"z":5.6,"s":4},{"n":"Saharanpur","a":"India","i":"IN","p":484873,"la":29.97,"lo":77.55,"k":0,"z":5.6,"s":4},{"n":"Ranchi","a":"India","i":"IN","p":1044000,"la":23.372,"lo":85.328,"k":0,"z":5.6,"s":4},{"n":"Bhagalpur","a":"India","i":"IN","p":361548,"la":25.23,"lo":86.98,"k":0,"z":5.6,"s":4},{"n":"Raipur","a":"India","i":"IN","p":875000,"la":21.237,"lo":81.633,"k":0,"z":5.6,"s":4},{"n":"Jabalpur","a":"India","i":"IN","p":1285000,"la":23.177,"lo":79.953,"k":0,"z":5.6,"s":4},{"n":"Indore","a":"India","i":"IN","p":2026000,"la":22.717,"lo":75.863,"k":0,"z":5.6,"s":4},{"n":"Puducherry","a":"India","i":"IN","p":227411,"la":11.935,"lo":79.83,"k":0,"z":5.1,"s":4},{"n":"Salem","a":"India","i":"IN","p":873000,"la":11.672,"lo":78.178,"k":0,"z":5.6,"s":4},{"n":"Tiruchirappalli","a":"India","i":"IN","p":951000,"la":10.801,"lo":78.688,"k":0,"z":5.6,"s":4},{"n":"Pointe-Noire","a":"Congo (Brazzaville)","i":"CG","p":659084,"la":-4.77,"lo":11.88,"k":0,"z":5.6,"s":4},{"n":"Kankan","a":"Guinea","i":"GN","p":114009,"la":10.39,"lo":-9.31,"k":0,"z":5.1,"s":4},{"n":"Nzérékoré","a":"Guinea","i":"GN","p":168121,"la":7.76,"lo":-8.83,"k":0,"z":5.1,"s":4},{"n":"Bouaké","a":"Ivory Coast","i":"CI","p":567481,"la":7.69,"lo":-5.03,"k":0,"z":5.6,"s":4},{"n":"St.-Denis","a":"France","i":"RE","p":190047,"la":-20.879,"lo":55.448,"k":0,"z":5.6,"s":4},{"n":"Rio Branco","a":"Brazil","i":"BR","p":257642,"la":-9.967,"lo":-67.8,"k":0,"z":5.6,"s":4},{"n":"São Luís","a":"Brazil","i":"BR","p":1038000,"la":-2.514,"lo":-44.268,"k":0,"z":5.6,"s":4},{"n":"Porto Velho","a":"Brazil","i":"BR","p":306180,"la":-8.75,"lo":-63.9,"k":0,"z":5.1,"s":4},{"n":"Alvorada","a":"Brazil","i":"BR","p":10232,"la":-12.47,"lo":-49.082,"k":0,"z":5.6,"s":4},{"n":"Corumbá","a":"Brazil","i":"BR","p":96520,"la":-19.016,"lo":-57.65,"k":0,"z":5.6,"s":4},{"n":"Belo Horizonte","a":"Brazil","i":"BR","p":5575000,"la":-19.913,"lo":-43.917,"k":0,"z":5.1,"s":4},{"n":"Montes Claros","a":"Brazil","i":"BR","p":332379,"la":-16.72,"lo":-43.86,"k":0,"z":5.6,"s":4},{"n":"Uberlândia","a":"Brazil","i":"BR","p":563536,"la":-18.9,"lo":-48.28,"k":0,"z":5.6,"s":4},{"n":"Colíder","a":"Brazil","i":"BR","p":27139,"la":-10.817,"lo":-55.451,"k":0,"z":5.6,"s":4},{"n":"Alta Floresta","a":"Brazil","i":"BR","p":40466,"la":-9.9,"lo":-55.91,"k":0,"z":5.6,"s":4},{"n":"Cuiabá","a":"Brazil","i":"BR","p":806000,"la":-15.568,"lo":-56.087,"k":0,"z":5.1,"s":4},{"n":"Pelotas","a":"Brazil","i":"BR","p":320674,"la":-31.75,"lo":-52.33,"k":0,"z":5.6,"s":4},{"n":"Caxias do Sul","a":"Brazil","i":"BR","p":381270,"la":-29.18,"lo":-51.17,"k":0,"z":5.6,"s":4},{"n":"Ponta Grossa","a":"Brazil","i":"BR","p":292177,"la":-25.09,"lo":-50.16,"k":0,"z":5.6,"s":4},{"n":"Teresina","a":"Brazil","i":"BR","p":907000,"la":-5.093,"lo":-42.782,"k":0,"z":5.6,"s":4},{"n":"Maceió","a":"Brazil","i":"BR","p":1186000,"la":-9.618,"lo":-35.732,"k":0,"z":5.6,"s":4},{"n":"Vitória da Conquista","a":"Brazil","i":"BR","p":308204,"la":-14.85,"lo":-40.84,"k":0,"z":5.6,"s":4},{"n":"Barreiras","a":"Brazil","i":"BR","p":158292,"la":-12.14,"lo":-45,"k":0,"z":5.6,"s":4},{"n":"Vila Velha","a":"Brazil","i":"BR","p":1209091,"la":-20.338,"lo":-40.29,"k":0,"z":5.6,"s":4},{"n":"Natal","a":"Brazil","i":"BR","p":1088000,"la":-5.778,"lo":-35.242,"k":0,"z":5.6,"s":4},{"n":"Thompson","a":"Canada","i":"CA","p":13727,"la":55.75,"lo":-97.867,"k":0,"z":5.6,"s":4},{"n":"Brandon","a":"Canada","i":"CA","p":28418,"la":49.833,"lo":-99.95,"k":0,"z":5.6,"s":4},{"n":"Fort Smith","a":"Canada","i":"CA","p":518,"la":60,"lo":-111.883,"k":0,"z":5.1,"s":4},{"n":"Fort McMurray","a":"Canada","i":"CA","p":21863,"la":56.733,"lo":-111.383,"k":0,"z":5.6,"s":4},{"n":"Peace River","a":"Canada","i":"CA","p":5340,"la":56.233,"lo":-117.283,"k":0,"z":5.6,"s":4},{"n":"Fort St. John","a":"Canada","i":"CA","p":18776,"la":56.25,"lo":-120.833,"k":0,"z":5.1,"s":4},{"n":"Iqaluit","a":"Canada","i":"CA","p":6124,"la":63.75,"lo":-68.5,"k":0,"z":5.1,"s":4},{"n":"Cambridge Bay","a":"Canada","i":"CA","p":1477,"la":69.117,"lo":-105.033,"k":0,"z":5.6,"s":4},{"n":"Kugluktuk","a":"Canada","i":"CA","p":1302,"la":67.799,"lo":-115.125,"k":0,"z":5.6,"s":4},{"n":"Chesterfield Inlet","a":"Canada","i":"CA","p":374,"la":63.338,"lo":-90.7,"k":0,"z":5.6,"s":4},{"n":"Arviat","a":"Canada","i":"CA","p":1868,"la":61.109,"lo":-94.059,"k":0,"z":5.6,"s":4},{"n":"Taloyoak","a":"Canada","i":"CA","p":774,"la":69.533,"lo":-93.533,"k":0,"z":5.6,"s":4},{"n":"Igloolik","a":"Canada","i":"CA","p":1612,"la":69.257,"lo":-81.794,"k":0,"z":5.6,"s":4},{"n":"Dawson City","a":"Canada","i":"CA","p":1319,"la":64.067,"lo":-139.417,"k":0,"z":5.1,"s":4},{"n":"Timmins","a":"Canada","i":"CA","p":34974,"la":48.467,"lo":-81.333,"k":0,"z":5.6,"s":4},{"n":"North Bay","a":"Canada","i":"CA","p":50170,"la":46.3,"lo":-79.45,"k":0,"z":5.6,"s":4},{"n":"Kuujjuarapik","a":"Canada","i":"CA","p":1243,"la":55.281,"lo":-77.766,"k":0,"z":5.6,"s":4},{"n":"Kuujjuaq","a":"Canada","i":"CA","p":1273,"la":58.1,"lo":-68.4,"k":0,"z":5.6,"s":4},{"n":"Sydney","a":"Canada","i":"CA","p":37538,"la":46.066,"lo":-60.18,"k":0,"z":5.6,"s":4},{"n":"Labrador City","a":"Canada","i":"CA","p":10313,"la":52.941,"lo":-66.916,"k":0,"z":5.6,"s":4},{"n":"Ebolowa","a":"Cameroon","i":"CM","p":87875,"la":2.9,"lo":11.15,"k":0,"z":5.6,"s":4},{"n":"Bambari","a":"Central African Republic","i":"CF","p":62098,"la":5.762,"lo":20.667,"k":0,"z":5.6,"s":4},{"n":"Venice","a":"Italy","i":"IT","p":270816,"la":45.439,"lo":12.335,"k":0,"z":5.1,"s":4},{"n":"El Calafate","a":"Argentina","i":"AR","p":8000,"la":-50.333,"lo":-72.3,"k":0,"z":5.6,"s":4},{"n":"San Juan","a":"Argentina","i":"AR","p":447048,"la":-31.55,"lo":-68.52,"k":0,"z":5.6,"s":4},{"n":"Rawson","a":"Argentina","i":"AR","p":26335,"la":-43.3,"lo":-65.1,"k":0,"z":5.1,"s":4},{"n":"Neuquén","a":"Argentina","i":"AR","p":242092,"la":-38.95,"lo":-68.06,"k":0,"z":5.1,"s":4},{"n":"Trinidad","a":"Bolivia","i":"BO","p":84259,"la":-14.833,"lo":-64.9,"k":0,"z":5.6,"s":4},{"n":"Santa Rosa","a":"Argentina","i":"AR","p":111424,"la":-36.62,"lo":-64.3,"k":0,"z":5.1,"s":4},{"n":"Bariloche","a":"Argentina","i":"AR","p":95394,"la":-41.15,"lo":-71.3,"k":0,"z":5.6,"s":4},{"n":"Salta","a":"Argentina","i":"AR","p":512686,"la":-24.783,"lo":-65.417,"k":0,"z":5.6,"s":4},{"n":"Tucumán","a":"Argentina","i":"AR","p":830000,"la":-26.814,"lo":-65.219,"k":0,"z":5.6,"s":4},{"n":"Formosa","a":"Argentina","i":"AR","p":221383,"la":-26.173,"lo":-58.183,"k":0,"z":5.6,"s":4},{"n":"Santa Fe","a":"Argentina","i":"AR","p":489505,"la":-31.624,"lo":-60.69,"k":0,"z":5.6,"s":4},{"n":"Rosario","a":"Argentina","i":"AR","p":1203000,"la":-32.949,"lo":-60.668,"k":0,"z":5.1,"s":4},{"n":"Campinas","a":"Brazil","i":"BR","p":2791000,"la":-22.908,"lo":-47.056,"k":0,"z":5.6,"s":4},{"n":"Sorocaba","a":"Brazil","i":"BR","p":563281,"la":-23.49,"lo":-47.47,"k":0,"z":5.6,"s":4},{"n":"Ribeirão Preto","a":"Brazil","i":"BR","p":551267,"la":-21.17,"lo":-47.83,"k":0,"z":5.6,"s":4},{"n":"Petrolina","a":"Brazil","i":"BR","p":260985,"la":-9.38,"lo":-40.51,"k":0,"z":5.6,"s":4},{"n":"Bamenda","a":"Cameroon","i":"CM","p":445299,"la":5.96,"lo":10.15,"k":0,"z":5.6,"s":4},{"n":"Garoua","a":"Cameroon","i":"CM","p":436899,"la":9.3,"lo":13.39,"k":0,"z":5.6,"s":4},{"n":"Herat","a":"Afghanistan","i":"AF","p":481009,"la":34.33,"lo":62.17,"k":0,"z":5.1,"s":4},{"n":"Mazar-i-Sharif","a":"Afghanistan","i":"AF","p":458151,"la":36.7,"lo":67.1,"k":0,"z":5.1,"s":4},{"n":"Battambang","a":"Cambodia","i":"KH","p":154773,"la":13.1,"lo":103.2,"k":0,"z":5.6,"s":4},{"n":"Siem Reap","a":"Cambodia","i":"KH","p":109398,"la":13.367,"lo":103.85,"k":0,"z":5.6,"s":4},{"n":"Malanje","a":"Angola","i":"AO","p":125856,"la":-9.54,"lo":16.34,"k":0,"z":5.6,"s":4},{"n":"Benguela","a":"Angola","i":"AO","p":151226,"la":-12.578,"lo":13.407,"k":0,"z":5.6,"s":4},{"n":"Lubango","a":"Angola","i":"AO","p":125632,"la":-14.91,"lo":13.49,"k":0,"z":5.6,"s":4},{"n":"Moçâmedes","a":"Angola","i":"AO","p":132900,"la":-15.19,"lo":12.16,"k":0,"z":5.6,"s":4},{"n":"Tarija","a":"Bolivia","i":"BO","p":159269,"la":-21.517,"lo":-64.75,"k":0,"z":5.6,"s":4},{"n":"Bridgetown","a":"Barbados","i":"BB","p":191152,"la":13.102,"lo":-59.617,"k":1,"z":5.6,"s":4},{"n":"Annaba","a":"Algeria","i":"DZ","p":503524,"la":36.92,"lo":7.76,"k":0,"z":5.6,"s":4},{"n":"Parakou","a":"Benin","i":"BJ","p":188853,"la":9.34,"lo":2.62,"k":0,"z":5.6,"s":4},{"n":"Porto-Novo","a":"Benin","i":"BJ","p":300000,"la":6.483,"lo":2.617,"k":0,"z":5.6,"s":4},{"n":"Constantine","a":"Algeria","i":"DZ","p":605179,"la":36.36,"lo":6.6,"k":0,"z":5.6,"s":4},{"n":"Brest","a":"Belarus","i":"BY","p":300715,"la":52.1,"lo":23.7,"k":0,"z":5.1,"s":4},{"n":"Khulna","a":"Bangladesh","i":"BD","p":1553000,"la":22.842,"lo":89.558,"k":0,"z":5.6,"s":4},{"n":"Francistown","a":"Botswana","i":"BW","p":89979,"la":-21.17,"lo":27.5,"k":0,"z":5.6,"s":4},{"n":"Mahalapye","a":"Botswana","i":"BW","p":50744,"la":-23.1,"lo":26.82,"k":0,"z":5.6,"s":4},{"n":"Serowe","a":"Botswana","i":"BW","p":48573,"la":-22.39,"lo":26.71,"k":0,"z":5.6,"s":4},{"n":"Katherine","a":"Australia","i":"AU","p":10141,"la":-14.467,"lo":132.267,"k":0,"z":5.6,"s":4},{"n":"Busselton","a":"Australia","i":"AU","p":10214,"la":-33.656,"lo":115.349,"k":0,"z":5.6,"s":4},{"n":"Mandurah","a":"Australia","i":"AU","p":73356,"la":-32.523,"lo":115.747,"k":0,"z":5.6,"s":4},{"n":"Broome","a":"Australia","i":"AU","p":13218,"la":-17.962,"lo":122.231,"k":0,"z":5,"s":4},{"n":"Kalgoorlie","a":"Australia","i":"AU","p":36852,"la":-30.735,"lo":121.46,"k":0,"z":5.1,"s":4},{"n":"Albany","a":"Australia","i":"AU","p":26445,"la":-35.017,"lo":117.892,"k":0,"z":5,"s":4},{"n":"Port Hedland","a":"Australia","i":"AU","p":14288,"la":-20.31,"lo":118.606,"k":0,"z":5.1,"s":4},{"n":"Karratha","a":"Australia","i":"AU","p":16796,"la":-20.73,"lo":116.87,"k":0,"z":5.6,"s":4},{"n":"Geraldton","a":"Australia","i":"AU","p":27258,"la":-28.767,"lo":114.6,"k":0,"z":5,"s":4},{"n":"Griffith","a":"Australia","i":"AU","p":15455,"la":-34.29,"lo":146.04,"k":0,"z":5,"s":4},{"n":"Orange","a":"Australia","i":"AU","p":39329,"la":-33.28,"lo":149.1,"k":0,"z":5.6,"s":4},{"n":"Dubbo","a":"Australia","i":"AU","p":31574,"la":-32.26,"lo":148.597,"k":0,"z":5,"s":4},{"n":"Armidale","a":"Australia","i":"AU","p":22673,"la":-30.512,"lo":151.667,"k":0,"z":5.6,"s":4},{"n":"Broken Hill","a":"Australia","i":"AU","p":17395,"la":-31.95,"lo":141.433,"k":0,"z":5.1,"s":4},{"n":"Port Lincoln","a":"Australia","i":"AU","p":13630,"la":-34.733,"lo":135.867,"k":0,"z":5.6,"s":4},{"n":"Whyalla","a":"Australia","i":"AU","p":22559,"la":-33.025,"lo":137.561,"k":0,"z":5,"s":4},{"n":"Portland","a":"Australia","i":"AU","p":11808,"la":-38.34,"lo":141.59,"k":0,"z":5,"s":4},{"n":"Bendigo","a":"Australia","i":"AU","p":81657,"la":-36.76,"lo":144.28,"k":0,"z":5.6,"s":4},{"n":"Wangaratta","a":"Australia","i":"AU","p":14022,"la":-36.36,"lo":146.3,"k":0,"z":5.6,"s":4},{"n":"Windorah","a":"Australia","i":"AU","p":158,"la":-25.433,"lo":142.65,"k":0,"z":5,"s":4},{"n":"Mount Isa","a":"Australia","i":"AU","p":33200,"la":-20.724,"lo":139.49,"k":0,"z":5.1,"s":4},{"n":"Rockhampton","a":"Australia","i":"AU","p":65850,"la":-23.364,"lo":150.52,"k":0,"z":5.6,"s":4},{"n":"Cairns","a":"Australia","i":"AU","p":154225,"la":-16.888,"lo":145.763,"k":0,"z":5,"s":4},{"n":"Gold Coast","a":"Australia","i":"AU","p":527660,"la":-28.082,"lo":153.448,"k":0,"z":5,"s":4},{"n":"Devonport","a":"Australia","i":"AU","p":19317,"la":-41.193,"lo":146.331,"k":0,"z":5.6,"s":4},{"n":"Bobo Dioulasso","a":"Burkina Faso","i":"BF","p":360106,"la":11.18,"lo":-4.29,"k":0,"z":5.6,"s":4},{"n":"Rajshahi","a":"Bangladesh","i":"BD","p":810000,"la":24.377,"lo":88.603,"k":0,"z":5.6,"s":4},{"n":"Mandalay","a":"Myanmar","i":"MM","p":1300000,"la":21.972,"lo":96.083,"k":0,"z":5.6,"s":4},{"n":"Sittwe","a":"Myanmar","i":"MM","p":179032,"la":20.14,"lo":92.88,"k":0,"z":5.6,"s":4},{"n":"Bujumbura","a":"Burundi","i":"BI","p":331700,"la":-3.376,"lo":29.36,"k":1,"z":5.6,"s":4},{"n":"Pago Pago","a":"American Samoa","i":"AS","p":12576,"la":-14.277,"lo":-170.707,"k":0,"z":5.6,"s":4},{"n":"Kingstown","a":"Saint Vincent and the Grenadines","i":"VC","p":49485,"la":13.156,"lo":-61.22,"k":1,"z":5.1,"s":4},{"n":"Castries","a":"Saint Lucia","i":"LC","p":37963,"la":14.008,"lo":-60.993,"k":1,"z":5.6,"s":4},{"n":"Basseterre","a":"Saint Kitts and Nevis","i":"KN","p":21887,"la":17.302,"lo":-62.717,"k":1,"z":5.6,"s":4},{"n":"Las Palmas","a":"Spain","i":"ES","p":378495,"la":28.1,"lo":-15.43,"k":0,"z":5.1,"s":4},{"n":"Berbera","a":"Somaliland","i":"-99","p":242344,"la":10.436,"lo":45.016,"k":0,"z":5.6,"s":4},{"n":"Port Louis","a":"Mauritius","i":"MU","p":595491,"la":-20.167,"lo":57.5,"k":1,"z":5.6,"s":4},{"n":"Gaza City","a":"Palestine","i":"PS","p":505700,"la":31.53,"lo":34.445,"k":0,"z":5.6,"s":4},{"n":"Saint George's","a":"Grenada","i":"GD","p":33734,"la":12.053,"lo":-61.742,"k":1,"z":5.1,"s":4},{"n":"Papeete","a":"French Polynesia","i":"PF","p":131695,"la":-17.533,"lo":-149.567,"k":0,"z":5.6,"s":4},{"n":"Manama","a":"Bahrain","i":"BH","p":563920,"la":26.236,"lo":50.583,"k":1,"z":5.6,"s":4},{"n":"Freeport","a":"The Bahamas","i":"BS","p":25383,"la":26.533,"lo":-78.7,"k":0,"z":5.6,"s":4},{"n":"Saint John's","a":"Antigua and Barbuda","i":"AG","p":35499,"la":17.118,"lo":-61.85,"k":1,"z":5.1,"s":4},{"n":"Taichung","a":"Taiwan","i":"TW","p":2629323,"la":24.152,"lo":120.682,"k":0,"z":5.6,"s":4},{"n":"Napier","a":"New Zealand","i":"NZ","p":57800,"la":-39.49,"lo":176.926,"k":0,"z":5,"s":4},{"n":"Manukau","a":"New Zealand","i":"NZ","p":375600,"la":-37,"lo":174.885,"k":0,"z":5.6,"s":4},{"n":"Hamilton","a":"New Zealand","i":"NZ","p":148200,"la":-37.778,"lo":175.29,"k":0,"z":5,"s":4},{"n":"Blenheim","a":"New Zealand","i":"NZ","p":30300,"la":-41.514,"lo":173.955,"k":0,"z":5,"s":4},{"n":"Dunedin","a":"New Zealand","i":"NZ","p":117700,"la":-45.885,"lo":170.491,"k":0,"z":5,"s":4},{"n":"Leh","a":"India","i":"IN","p":30870,"la":34.162,"lo":77.572,"k":0,"z":5.6,"s":4},{"n":"Amaravati","a":"India","i":"IN","p":5800000,"la":16.534,"lo":80.524,"k":0,"z":5.6,"s":4},{"n":"Kozhikode","a":"India","i":"IN","p":953000,"la":11.252,"lo":75.768,"k":0,"z":5,"s":3},{"n":"Bhubaneswar","a":"India","i":"IN","p":844000,"la":20.272,"lo":85.825,"k":0,"z":5,"s":3},{"n":"Jamshedpur","a":"India","i":"IN","p":1300000,"la":22.789,"lo":86.196,"k":0,"z":5,"s":3},{"n":"Montevideo","a":"Uruguay","i":"UY","p":1513000,"la":-34.905,"lo":-56.187,"k":1,"z":4,"s":3},{"n":"Helena","a":"United States of America","i":"US","p":38725,"la":46.593,"lo":-112.035,"k":0,"z":5,"s":3},{"n":"Bismarck","a":"United States of America","i":"US","p":63871,"la":46.808,"lo":-100.783,"k":0,"z":5,"s":3},{"n":"Boise","a":"United States of America","i":"US","p":338071,"la":43.609,"lo":-116.227,"k":0,"z":5,"s":3},{"n":"San Jose","a":"United States of America","i":"US","p":1668000,"la":37.331,"lo":-121.888,"k":0,"z":5,"s":3},{"n":"Sacramento","a":"United States of America","i":"US","p":1604000,"la":38.577,"lo":-121.472,"k":0,"z":5,"s":3},{"n":"Las Vegas","a":"United States of America","i":"US","p":1823000,"la":36.165,"lo":-115.151,"k":0,"z":5,"s":3},{"n":"Santa Fe","a":"United States of America","i":"US","p":92681,"la":35.687,"lo":-105.937,"k":0,"z":5,"s":3},{"n":"Portland","a":"United States of America","i":"US","p":1875000,"la":45.522,"lo":-122.682,"k":0,"z":4,"s":3},{"n":"Salt Lake City","a":"United States of America","i":"US","p":966000,"la":40.777,"lo":-111.932,"k":0,"z":4,"s":3},{"n":"Cheyenne","a":"United States of America","i":"US","p":72927,"la":41.14,"lo":-104.82,"k":0,"z":5,"s":3},{"n":"Des Moines","a":"United States of America","i":"US","p":380655,"la":41.58,"lo":-93.62,"k":0,"z":5,"s":3},{"n":"Omaha","a":"United States of America","i":"US","p":877110,"la":41.24,"lo":-96.01,"k":0,"z":5,"s":3},{"n":"Oklahoma City","a":"United States of America","i":"US","p":788000,"la":35.472,"lo":-97.521,"k":0,"z":5,"s":3},{"n":"Pierre","a":"United States of America","i":"US","p":13879,"la":44.368,"lo":-100.351,"k":0,"z":5,"s":3},{"n":"San Antonio","a":"United States of America","i":"US","p":1473000,"la":29.42,"lo":-98.493,"k":0,"z":5,"s":3},{"n":"San Cristóbal","a":"Venezuela","i":"VE","p":438798,"la":7.77,"lo":-72.25,"k":0,"z":5,"s":3},{"n":"Valencia","a":"Venezuela","i":"VE","p":1770000,"la":10.232,"lo":-67.982,"k":0,"z":5,"s":3},{"n":"Jackson","a":"United States of America","i":"US","p":250902,"la":32.299,"lo":-90.185,"k":0,"z":5,"s":3},{"n":"Raleigh","a":"United States of America","i":"US","p":1163515,"la":35.773,"lo":-78.647,"k":0,"z":5,"s":3},{"n":"Cleveland","a":"United States of America","i":"US","p":1890000,"la":41.472,"lo":-81.697,"k":0,"z":5,"s":3},{"n":"Cincinnati","a":"United States of America","i":"US","p":1636000,"la":39.164,"lo":-84.459,"k":0,"z":5,"s":3},{"n":"Nashville","a":"United States of America","i":"US","p":877000,"la":36.172,"lo":-86.782,"k":0,"z":5,"s":3},{"n":"Memphis","a":"United States of America","i":"US","p":1081000,"la":35.145,"lo":-90.029,"k":0,"z":5,"s":3},{"n":"Norfolk","a":"United States of America","i":"US","p":1047869,"la":36.85,"lo":-76.28,"k":0,"z":5.7,"s":3},{"n":"Milwaukee","a":"United States of America","i":"US","p":1388000,"la":43.03,"lo":-87.917,"k":0,"z":5,"s":3},{"n":"Buffalo","a":"United States of America","i":"US","p":1016000,"la":42.882,"lo":-78.882,"k":0,"z":5,"s":3},{"n":"Pittsburgh","a":"United States of America","i":"US","p":1838000,"la":40.432,"lo":-80.002,"k":0,"z":5,"s":3},{"n":"Ciudad Guayana","a":"Venezuela","i":"VE","p":746535,"la":8.37,"lo":-62.62,"k":0,"z":5,"s":3},{"n":"Lomé","a":"Togo","i":"TG","p":1452000,"la":6.134,"lo":1.221,"k":1,"z":5,"s":3},{"n":"Tunis","a":"Tunisia","i":"TN","p":2412500,"la":36.803,"lo":10.18,"k":1,"z":5,"s":3},{"n":"Kodiak","a":"United States of America","i":"US","p":9461,"la":57.79,"lo":-152.407,"k":0,"z":5,"s":3},{"n":"Cold Bay","a":"United States of America","i":"US","p":200,"la":55.2,"lo":-162.715,"k":0,"z":5,"s":3},{"n":"Bethel","a":"United States of America","i":"US","p":6228,"la":60.793,"lo":-161.756,"k":0,"z":5,"s":3},{"n":"Point Hope","a":"United States of America","i":"US","p":461,"la":68.348,"lo":-166.808,"k":0,"z":5,"s":3},{"n":"Utqiaġvik","a":"United States of America","i":"US","p":4336,"la":71.291,"lo":-156.789,"k":0,"z":5,"s":3},{"n":"Nome","a":"United States of America","i":"US","p":3485,"la":64.506,"lo":-165.406,"k":0,"z":4,"s":3},{"n":"Valdez","a":"United States of America","i":"US","p":4036,"la":61.136,"lo":-146.348,"k":0,"z":5,"s":3},{"n":"Juneau","a":"United States of America","i":"US","p":30711,"la":58.303,"lo":-134.401,"k":0,"z":4,"s":3},{"n":"Fairbanks","a":"United States of America","i":"US","p":56993,"la":64.837,"lo":-147.711,"k":0,"z":4,"s":3},{"n":"Prudhoe Bay","a":"United States of America","i":"US","p":2500,"la":70.292,"lo":-148.669,"k":0,"z":3.7,"s":3},{"n":"Sevastopol","a":"Russia","i":"RU","p":379200,"la":44.604,"lo":33.524,"k":0,"z":4,"s":3},{"n":"Abu Dhabi","a":"United Arab Emirates","i":"AE","p":603492,"la":24.467,"lo":54.367,"k":1,"z":5,"s":3},{"n":"Ashgabat","a":"Turkmenistan","i":"TM","p":727700,"la":37.95,"lo":58.383,"k":1,"z":5,"s":3},{"n":"Samarkand","a":"Uzbekistan","i":"UZ","p":708000,"la":39.67,"lo":66.945,"k":0,"z":5,"s":3},{"n":"Lusaka","a":"Zambia","i":"ZM","p":1328000,"la":-15.415,"lo":28.281,"k":1,"z":4,"s":3},{"n":"Harare","a":"Zimbabwe","i":"ZW","p":1572000,"la":-17.816,"lo":31.043,"k":1,"z":4,"s":3},{"n":"Bulawayo","a":"Zimbabwe","i":"ZW","p":699385,"la":-20.17,"lo":28.58,"k":0,"z":5,"s":3},{"n":"Dili","a":"East Timor","i":"TL","p":234331,"la":-8.559,"lo":125.579,"k":1,"z":5,"s":3},{"n":"Port Vila","a":"Vanuatu","i":"VU","p":44040,"la":-17.733,"lo":168.317,"k":1,"z":4,"s":3},{"n":"Tegucigalpa","a":"Honduras","i":"HN","p":946000,"la":14.104,"lo":-87.219,"k":1,"z":5,"s":3},{"n":"Georgetown","a":"Guyana","i":"GY","p":264350,"la":6.802,"lo":-58.167,"k":1,"z":4,"s":3},{"n":"Reykjavík","a":"Iceland","i":"IS","p":166212,"la":64.143,"lo":-21.937,"k":1,"z":3.7,"s":3},{"n":"Port-au-Prince","a":"Haiti","i":"HT","p":1998000,"la":18.543,"lo":-72.338,"k":1,"z":5,"s":3},{"n":"Glasgow","a":"United Kingdom","i":"GB","p":1160000,"la":55.862,"lo":-4.239,"k":0,"z":4,"s":3},{"n":"Kampala","a":"Uganda","i":"UG","p":1420000,"la":0.319,"lo":32.581,"k":1,"z":5,"s":3},{"n":"Aden","a":"Yemen","i":"YE","p":1000000,"la":12.78,"lo":45.009,"k":0,"z":5,"s":3},{"n":"Paramaribo","a":"Suriname","i":"SR","p":254169,"la":5.835,"lo":-55.167,"k":1,"z":5,"s":3},{"n":"Seville","a":"Spain","i":"ES","p":1212045,"la":37.405,"lo":-5.98,"k":0,"z":5,"s":3},{"n":"Zinder","a":"Niger","i":"NE","p":230358,"la":13.8,"lo":8.983,"k":0,"z":5,"s":3},{"n":"Niamey","a":"Niger","i":"NE","p":915000,"la":13.519,"lo":2.115,"k":1,"z":5,"s":3},{"n":"Port Sudan","a":"Sudan","i":"SD","p":489725,"la":19.616,"lo":37.216,"k":0,"z":5,"s":3},{"n":"Dushanbe","a":"Tajikistan","i":"TJ","p":1086244,"la":38.56,"lo":68.774,"k":1,"z":5,"s":3},{"n":"Cusco","a":"Peru","i":"PE","p":361182,"la":-13.525,"lo":-71.972,"k":0,"z":4,"s":3},{"n":"Tacna","a":"Peru","i":"PE","p":280098,"la":-18,"lo":-70.25,"k":0,"z":5,"s":3},{"n":"Trujillo","a":"Peru","i":"PE","p":765171,"la":-8.12,"lo":-79.02,"k":0,"z":4,"s":3},{"n":"Ica","a":"Peru","i":"PE","p":279420,"la":-14.068,"lo":-75.725,"k":0,"z":5,"s":3},{"n":"Asunción","a":"Paraguay","i":"PY","p":1870000,"la":-25.291,"lo":-57.626,"k":1,"z":4,"s":3},{"n":"Managua","a":"Nicaragua","i":"NI","p":920000,"la":12.155,"lo":-86.27,"k":1,"z":5,"s":3},{"n":"Freetown","a":"Sierra Leone","i":"SL","p":827000,"la":8.472,"lo":-13.236,"k":1,"z":5,"s":3},{"n":"Agadez","a":"Niger","i":"NE","p":117770,"la":16.996,"lo":7.983,"k":0,"z":5,"s":3},{"n":"Nyala","a":"Sudan","i":"SD","p":392373,"la":12.06,"lo":24.89,"k":0,"z":5,"s":3},{"n":"Wau","a":"South Sudan","i":"SS","p":127384,"la":7.7,"lo":27.99,"k":0,"z":5,"s":3},{"n":"Dongola","a":"Sudan","i":"SD","p":26404,"la":19.167,"lo":30.483,"k":0,"z":5,"s":3},{"n":"Kassala","a":"Sudan","i":"SD","p":401477,"la":15.46,"lo":36.39,"k":0,"z":5,"s":3},{"n":"Tromsø","a":"Norway","i":"NO","p":52436,"la":69.635,"lo":18.992,"k":0,"z":3.7,"s":3},{"n":"Trondheim","a":"Norway","i":"NO","p":147139,"la":63.417,"lo":10.417,"k":0,"z":4,"s":3},{"n":"Bergen","a":"Norway","i":"NO","p":213585,"la":60.391,"lo":5.325,"k":0,"z":5,"s":3},{"n":"Islamabad","a":"Pakistan","i":"PK","p":780000,"la":33.689,"lo":73.081,"k":1,"z":5,"s":3},{"n":"Multan","a":"Pakistan","i":"PK","p":1522000,"la":30.202,"lo":71.453,"k":0,"z":5,"s":3},{"n":"Hyderabad","a":"Pakistan","i":"PK","p":1459000,"la":25.382,"lo":68.373,"k":0,"z":5,"s":3},{"n":"Peshawar","a":"Pakistan","i":"PK","p":1303000,"la":34.007,"lo":71.533,"k":0,"z":5,"s":3},{"n":"Kathmandu","a":"Nepal","i":"NP","p":895000,"la":27.719,"lo":85.315,"k":1,"z":5,"s":3},{"n":"Nacala","a":"Mozambique","i":"MZ","p":224795,"la":-14.519,"lo":40.715,"k":0,"z":5,"s":3},{"n":"Bloemfontein","a":"South Africa","i":"ZA","p":463064,"la":-29.12,"lo":26.23,"k":1,"z":5,"s":3},{"n":"Pretoria","a":"South Africa","i":"ZA","p":1338000,"la":-25.705,"lo":28.227,"k":1,"z":5,"s":3},{"n":"Port Moresby","a":"Papua New Guinea","i":"PG","p":283733,"la":-9.465,"lo":147.193,"k":1,"z":4,"s":3},{"n":"Honiara","a":"Solomon Islands","i":"SB","p":76328,"la":-9.438,"lo":159.95,"k":1,"z":5,"s":3},{"n":"Panama City","a":"Panama","i":"PA","p":1281000,"la":8.97,"lo":-79.535,"k":1,"z":5,"s":3},{"n":"Fez","a":"Morocco","i":"MA","p":1002000,"la":34.057,"lo":-5.002,"k":0,"z":5,"s":3},{"n":"Rabat","a":"Morocco","i":"MA","p":1705000,"la":34.025,"lo":-6.836,"k":1,"z":5,"s":3},{"n":"Marrakesh","a":"Morocco","i":"MA","p":872000,"la":31.632,"lo":-8.002,"k":0,"z":5,"s":3},{"n":"Chișinău","a":"Moldova","i":"MD","p":688134,"la":47.005,"lo":28.858,"k":1,"z":5,"s":3},{"n":"Beira","a":"Mozambique","i":"MZ","p":530604,"la":-19.82,"lo":34.87,"k":0,"z":5,"s":3},{"n":"Port Elizabeth","a":"South Africa","i":"ZA","p":1021000,"la":-33.968,"lo":25.598,"k":0,"z":5,"s":3},{"n":"Maputo","a":"Mozambique","i":"MZ","p":1446000,"la":-25.953,"lo":32.587,"k":1,"z":5,"s":3},{"n":"Tomsk","a":"Russia","i":"RU","p":535479,"la":56.495,"lo":84.975,"k":0,"z":4,"s":3},{"n":"Anadyr","a":"Russia","i":"RU","p":10332,"la":64.737,"lo":177.475,"k":0,"z":5,"s":3},{"n":"Murmansk","a":"Russia","i":"RU","p":319263,"la":68.97,"lo":33.1,"k":0,"z":3.7,"s":3},{"n":"Archangel","a":"Russia","i":"RU","p":356051,"la":64.575,"lo":40.545,"k":0,"z":4,"s":3},{"n":"Nizhny Novgorod","a":"Russia","i":"RU","p":1278000,"la":56.335,"lo":43.998,"k":0,"z":5,"s":3},{"n":"Volgograd","a":"Russia","i":"RU","p":984000,"la":48.712,"lo":44.498,"k":0,"z":4,"s":3},{"n":"Ufa","a":"Russia","i":"RU","p":1018000,"la":54.792,"lo":56.038,"k":0,"z":5,"s":3},{"n":"Yekaterinburg","a":"Russia","i":"RU","p":1313000,"la":56.852,"lo":60.598,"k":0,"z":5,"s":3},{"n":"Samara","a":"Russia","i":"RU","p":1137000,"la":53.197,"lo":50.149,"k":0,"z":3.7,"s":3},{"n":"Kazan","a":"Russia","i":"RU","p":1115000,"la":55.752,"lo":49.124,"k":0,"z":5,"s":3},{"n":"Surgut","a":"Russia","i":"RU","p":400000,"la":61.26,"lo":73.425,"k":0,"z":4,"s":3},{"n":"Barnaul","a":"Russia","i":"RU","p":599579,"la":53.355,"lo":83.745,"k":0,"z":5,"s":3},{"n":"Novosibirsk","a":"Russia","i":"RU","p":1389000,"la":55.032,"lo":82.958,"k":0,"z":5,"s":3},{"n":"Mogadishu","a":"Somalia","i":"SO","p":1100000,"la":2.069,"lo":45.365,"k":1,"z":4,"s":3},{"n":"Muscat","a":"Oman","i":"OM","p":734697,"la":23.585,"lo":58.378,"k":1,"z":5,"s":3},{"n":"Colombo","a":"Sri Lanka","i":"LK","p":217000,"la":6.932,"lo":79.858,"k":1,"z":5,"s":3},{"n":"Cebu","a":"Philippines","i":"PH","p":815000,"la":10.322,"lo":123.898,"k":0,"z":4,"s":3},{"n":"Iloilo","a":"Philippines","i":"PH","p":387681,"la":10.705,"lo":122.545,"k":0,"z":5,"s":3},{"n":"Davao","a":"Philippines","i":"PH","p":1402000,"la":7.112,"lo":125.628,"k":0,"z":5,"s":3},{"n":"Bratsk","a":"Russia","i":"RU","p":246348,"la":56.157,"lo":101.615,"k":0,"z":5,"s":3},{"n":"Irkutsk","a":"Russia","i":"RU","p":586695,"la":52.285,"lo":104.284,"k":0,"z":3.7,"s":3},{"n":"Krasnoyarsk","a":"Russia","i":"RU","p":925000,"la":56.016,"lo":92.864,"k":0,"z":5,"s":3},{"n":"Dikson","a":"Russia","i":"RU","p":1113,"la":73.507,"lo":80.545,"k":0,"z":5,"s":3},{"n":"Chita","a":"Russia","i":"RU","p":308500,"la":52.055,"lo":113.465,"k":0,"z":5,"s":3},{"n":"Vladivostok","a":"Russia","i":"RU","p":587022,"la":43.13,"lo":131.91,"k":0,"z":5,"s":3},{"n":"Nizhneyansk","a":"Russia","i":"RU","p":400,"la":71.433,"lo":136.067,"k":0,"z":5,"s":3},{"n":"Yakutsk","a":"Russia","i":"RU","p":235600,"la":62.035,"lo":129.735,"k":0,"z":4,"s":3},{"n":"Tiksi","a":"Russia","i":"RU","p":5700,"la":71.627,"lo":128.835,"k":0,"z":4,"s":3},{"n":"Magadan","a":"Russia","i":"RU","p":95282,"la":59.575,"lo":150.81,"k":0,"z":3.7,"s":3},{"n":"Tijuana","a":"Mexico","i":"MX","p":1553000,"la":32.502,"lo":-117.082,"k":0,"z":5,"s":3},{"n":"Chihuahua","a":"Mexico","i":"MX","p":793000,"la":28.647,"lo":-106.087,"k":0,"z":4,"s":3},{"n":"Mazatlán","a":"Mexico","i":"MX","p":368204,"la":23.221,"lo":-106.42,"k":0,"z":5,"s":3},{"n":"Tampico","a":"Mexico","i":"MX","p":859419,"la":22.3,"lo":-97.87,"k":0,"z":5,"s":3},{"n":"Acapulco","a":"Mexico","i":"MX","p":715584,"la":16.85,"lo":-99.916,"k":0,"z":5,"s":3},{"n":"Veracruz","a":"Mexico","i":"MX","p":578963,"la":19.177,"lo":-96.16,"k":0,"z":5,"s":3},{"n":"Tuxtla Gutiérrez","a":"Mexico","i":"MX","p":481128,"la":16.75,"lo":-93.15,"k":0,"z":5,"s":3},{"n":"Cancún","a":"Mexico","i":"MX","p":542043,"la":21.17,"lo":-86.83,"k":0,"z":4,"s":3},{"n":"Mérida","a":"Mexico","i":"MX","p":965000,"la":20.969,"lo":-89.619,"k":0,"z":5,"s":3},{"n":"Enugu","a":"Nigeria","i":"NG","p":688862,"la":6.45,"lo":7.5,"k":0,"z":5,"s":3},{"n":"Sokoto","a":"Nigeria","i":"NG","p":732178,"la":13.06,"lo":5.24,"k":0,"z":5,"s":3},{"n":"Perm","a":"Russia","i":"RU","p":997000,"la":58.002,"lo":56.248,"k":0,"z":4,"s":3},{"n":"Erdenet","a":"Mongolia","i":"MN","p":79647,"la":49.053,"lo":104.118,"k":0,"z":5,"s":3},{"n":"Ulaanbaatar","a":"Mongolia","i":"MN","p":885000,"la":47.919,"lo":106.915,"k":1,"z":4,"s":3},{"n":"Mbeya","a":"Tanzania","i":"TZ","p":291649,"la":-8.89,"lo":33.43,"k":0,"z":5,"s":3},{"n":"Windhoek","a":"Namibia","i":"NA","p":268132,"la":-22.57,"lo":17.084,"k":1,"z":5,"s":3},{"n":"Grootfontein","a":"Namibia","i":"NA","p":24099,"la":-19.567,"lo":18.117,"k":0,"z":5,"s":3},{"n":"Zanzibar","a":"Tanzania","i":"TZ","p":403658,"la":-6.16,"lo":39.2,"k":0,"z":5,"s":3},{"n":"Valencia","a":"Spain","i":"ES","p":808000,"la":39.487,"lo":-0.402,"k":0,"z":4.7,"s":3},{"n":"Palana","a":"Russia","i":"RU","p":3671,"la":59.084,"lo":159.95,"k":0,"z":5,"s":3},{"n":"Petropavlovsk-Kamchatsky","a":"Russia","i":"RU","p":187282,"la":53.062,"lo":158.623,"k":0,"z":5,"s":3},{"n":"Abuja","a":"Nigeria","i":"NG","p":1576000,"la":9.055,"lo":7.49,"k":1,"z":4,"s":3},{"n":"Padang","a":"Indonesia","i":"ID","p":855000,"la":-0.958,"lo":100.358,"k":0,"z":5,"s":3},{"n":"Bissau","a":"Guinea Bissau","i":"GW","p":403339,"la":11.865,"lo":-15.598,"k":1,"z":5,"s":3},{"n":"Palermo","a":"Italy","i":"IT","p":863000,"la":38.127,"lo":13.348,"k":0,"z":5,"s":3},{"n":"Amman","a":"Jordan","i":"JO","p":1060000,"la":31.952,"lo":35.931,"k":1,"z":5,"s":3},{"n":"Vilnius","a":"Lithuania","i":"LT","p":542366,"la":54.683,"lo":25.317,"k":1,"z":5,"s":3},{"n":"Riga","a":"Latvia","i":"LV","p":742572,"la":56.95,"lo":24.1,"k":1,"z":4,"s":3},{"n":"Bishkek","a":"Kyrgyzstan","i":"KG","p":837000,"la":42.875,"lo":74.583,"k":1,"z":5,"s":3},{"n":"Jiayuguan","a":"China","i":"CN","p":148279,"la":39.82,"lo":98.3,"k":0,"z":5,"s":3},{"n":"Xining","a":"China","i":"CN","p":1048000,"la":36.622,"lo":101.768,"k":0,"z":4.7,"s":3},{"n":"Guilin","a":"China","i":"CN","p":987000,"la":25.282,"lo":110.278,"k":0,"z":5,"s":3},{"n":"Huainan","a":"China","i":"CN","p":1451000,"la":32.632,"lo":116.978,"k":0,"z":5,"s":3},{"n":"Shantou","a":"China","i":"CN","p":1601000,"la":23.372,"lo":116.668,"k":0,"z":5,"s":3},{"n":"Tarakan","a":"Indonesia","i":"ID","p":193069,"la":3.3,"lo":117.633,"k":0,"z":5,"s":3},{"n":"Mombasa","a":"Kenya","i":"KE","p":882000,"la":-4.038,"lo":39.688,"k":0,"z":5,"s":3},{"n":"Maseru","a":"Lesotho","i":"LS","p":361324,"la":-29.317,"lo":27.483,"k":1,"z":5,"s":3},{"n":"Antananarivo","a":"Madagascar","i":"MG","p":1697000,"la":-18.915,"lo":47.515,"k":1,"z":5,"s":3},{"n":"Semarang","a":"Indonesia","i":"ID","p":1396000,"la":-6.965,"lo":110.418,"k":0,"z":5,"s":3},{"n":"Palembang","a":"Indonesia","i":"ID","p":1749000,"la":-2.978,"lo":104.748,"k":0,"z":5,"s":3},{"n":"Bandjarmasin","a":"Indonesia","i":"ID","p":603576,"la":-3.33,"lo":114.58,"k":0,"z":5,"s":3},{"n":"Makassar","a":"Indonesia","i":"ID","p":1262000,"la":-5.138,"lo":119.43,"k":0,"z":5,"s":3},{"n":"Lyon","a":"France","i":"FR","p":1423000,"la":45.772,"lo":4.828,"k":0,"z":4.7,"s":3},{"n":"Quito","a":"Ecuador","i":"EC","p":1701000,"la":-0.213,"lo":-78.502,"k":1,"z":4,"s":3},{"n":"San José","a":"Costa Rica","i":"CR","p":1284000,"la":9.93,"lo":-84.079,"k":1,"z":5,"s":3},{"n":"San Salvador","a":"El Salvador","i":"SV","p":1433000,"la":13.703,"lo":-89.216,"k":1,"z":5,"s":3},{"n":"Kingston","a":"Jamaica","i":"JM","p":937700,"la":17.977,"lo":-76.767,"k":1,"z":5,"s":3},{"n":"Cartagena","a":"Colombia","i":"CO","p":887000,"la":10.402,"lo":-75.516,"k":0,"z":5,"s":3},{"n":"Mitú","a":"Colombia","i":"CO","p":5917,"la":1.198,"lo":-70.174,"k":0,"z":5,"s":3},{"n":"Bumba","a":"Congo (Kinshasa)","i":"CD","p":160539,"la":2.19,"lo":22.46,"k":0,"z":5,"s":3},{"n":"N'Djamena","a":"Chad","i":"TD","p":989000,"la":12.115,"lo":15.047,"k":1,"z":5,"s":3},{"n":"Abéché","a":"Chad","i":"TD","p":158317,"la":13.84,"lo":20.83,"k":0,"z":5,"s":3},{"n":"Malabo","a":"Equatorial Guinea","i":"GQ","p":155963,"la":3.75,"lo":8.783,"k":1,"z":5,"s":3},{"n":"Luxor","a":"Egypt","i":"EG","p":609248,"la":25.7,"lo":32.65,"k":0,"z":5,"s":3},{"n":"Asmara","a":"Eritrea","i":"ER","p":620802,"la":15.333,"lo":38.933,"k":1,"z":5,"s":3},{"n":"Zagreb","a":"Croatia","i":"HR","p":722526,"la":45.8,"lo":16,"k":1,"z":5,"s":3},{"n":"Tallinn","a":"Estonia","i":"EE","p":394024,"la":59.434,"lo":24.728,"k":1,"z":5,"s":3},{"n":"Lhasa","a":"China","i":"CN","p":219599,"la":29.645,"lo":91.1,"k":0,"z":4,"s":3},{"n":"Hami","a":"China","i":"CN","p":580000,"la":42.827,"lo":93.515,"k":0,"z":5,"s":3},{"n":"Hotan","a":"China","i":"CN","p":408894,"la":37.1,"lo":79.927,"k":0,"z":4,"s":3},{"n":"Kashgar","a":"China","i":"CN","p":506640,"la":39.476,"lo":75.97,"k":0,"z":5,"s":3},{"n":"Yinchuan","a":"China","i":"CN","p":991000,"la":38.47,"lo":106.271,"k":0,"z":5,"s":3},{"n":"Pingxiang","a":"China","i":"CN","p":961000,"la":27.622,"lo":113.848,"k":0,"z":5,"s":3},{"n":"Nagasaki","a":"Japan","i":"JP","p":435455,"la":32.765,"lo":129.885,"k":0,"z":5,"s":3},{"n":"Qiqihar","a":"China","i":"CN","p":1641000,"la":47.347,"lo":123.988,"k":0,"z":4,"s":3},{"n":"Kikwit","a":"Congo (Kinshasa)","i":"CD","p":637736,"la":-5.03,"lo":18.85,"k":0,"z":5,"s":3},{"n":"Matadi","a":"Congo (Kinshasa)","i":"CD","p":245862,"la":-5.817,"lo":13.45,"k":0,"z":5,"s":3},{"n":"Kolwezi","a":"Congo (Kinshasa)","i":"CD","p":418000,"la":-10.717,"lo":25.472,"k":0,"z":5,"s":3},{"n":"Lubumbashi","a":"Congo (Kinshasa)","i":"CD","p":1352000,"la":-11.678,"lo":27.478,"k":0,"z":5,"s":3},{"n":"Lilongwe","a":"Malawi","i":"MW","p":646750,"la":-13.983,"lo":33.783,"k":1,"z":5,"s":3},{"n":"Guatemala City","a":"Guatemala","i":"GT","p":1024000,"la":14.623,"lo":-90.529,"k":1,"z":5,"s":3},{"n":"Cayenne","a":"France","i":"GF","p":61550,"la":4.933,"lo":-52.33,"k":0,"z":4,"s":3},{"n":"Libreville","a":"Gabon","i":"GA","p":578156,"la":0.385,"lo":9.458,"k":1,"z":5,"s":3},{"n":"Vishakhapatnam","a":"India","i":"IN","p":1529000,"la":17.683,"lo":83.22,"k":0,"z":5,"s":3},{"n":"Suva","a":"Fiji","i":"FJ","p":175399,"la":-18.133,"lo":178.442,"k":1,"z":5,"s":3},{"n":"Port-Gentil","a":"Gabon","i":"GA","p":116836,"la":-0.72,"lo":8.78,"k":0,"z":5,"s":3},{"n":"Timbuktu","a":"Mali","i":"ML","p":68872,"la":16.767,"lo":-3.017,"k":0,"z":5,"s":3},{"n":"Punta Arenas","a":"Chile","i":"CL","p":117430,"la":-53.165,"lo":-70.94,"k":0,"z":3,"s":3},{"n":"Iquique","a":"Chile","i":"CL","p":227499,"la":-20.25,"lo":-70.13,"k":0,"z":5,"s":3},{"n":"Antofagasta","a":"Chile","i":"CL","p":309832,"la":-23.65,"lo":-70.4,"k":0,"z":4,"s":3},{"n":"Valparaíso","a":"Chile","i":"CL","p":854000,"la":-33.048,"lo":-71.617,"k":0,"z":5,"s":3},{"n":"Valdivia","a":"Chile","i":"CL","p":159599,"la":-39.822,"lo":-73.233,"k":0,"z":4,"s":3},{"n":"Concepción","a":"Chile","i":"CL","p":889725,"la":-36.83,"lo":-73.05,"k":0,"z":5,"s":3},{"n":"Puerto Montt","a":"Chile","i":"CL","p":174629,"la":-41.47,"lo":-72.93,"k":0,"z":5,"s":3},{"n":"Nuuk","a":"Greenland","i":"GL","p":14798,"la":64.198,"lo":-51.733,"k":0,"z":4,"s":3},{"n":"Nouakchott","a":"Mauritania","i":"MR","p":742144,"la":18.086,"lo":-15.975,"k":1,"z":5,"s":3},{"n":"Bamako","a":"Mali","i":"ML","p":1494000,"la":12.652,"lo":-8.002,"k":1,"z":4,"s":3},{"n":"Atar","a":"Mauritania","i":"MR","p":44265,"la":20.517,"lo":-13.05,"k":0,"z":5,"s":3},{"n":"Djenné","a":"Mali","i":"ML","p":32944,"la":13.9,"lo":-4.55,"k":0,"z":5,"s":3},{"n":"Sabha","a":"Libya","i":"LY","p":100249,"la":27.033,"lo":14.433,"k":0,"z":5,"s":3},{"n":"Banghazi","a":"Libya","i":"LY","p":1180000,"la":32.119,"lo":20.065,"k":0,"z":4.7,"s":3},{"n":"Thessaloniki","a":"Greece","i":"GR","p":828000,"la":40.698,"lo":22.883,"k":0,"z":5,"s":3},{"n":"Beirut","a":"Lebanon","i":"LB","p":1846000,"la":33.874,"lo":35.508,"k":1,"z":5,"s":3},{"n":"Tbilisi","a":"Georgia","i":"GE","p":1100000,"la":41.727,"lo":44.789,"k":1,"z":5,"s":3},{"n":"Gondar","a":"Ethiopia","i":"ET","p":156230,"la":12.61,"lo":37.46,"k":0,"z":5,"s":3},{"n":"Nur-Sultan","a":"Kazakhstan","i":"KZ","p":345604,"la":51.181,"lo":71.428,"k":1,"z":4,"s":3},{"n":"Qaraghandy","a":"Kazakhstan","i":"KZ","p":451800,"la":49.885,"lo":73.115,"k":0,"z":5,"s":3},{"n":"Almaty","a":"Kazakhstan","i":"KZ","p":1209000,"la":43.327,"lo":76.913,"k":0,"z":5,"s":3},{"n":"Isfahan","a":"Iran","i":"IR","p":1628000,"la":32.702,"lo":51.698,"k":0,"z":5,"s":3},{"n":"Shiraz","a":"Iran","i":"IR","p":1240000,"la":29.632,"lo":52.568,"k":0,"z":5,"s":3},{"n":"Amritsar","a":"India","i":"IN","p":1212000,"la":31.642,"lo":74.868,"k":0,"z":5,"s":3},{"n":"Varanasi","a":"India","i":"IN","p":1352000,"la":25.332,"lo":82.998,"k":0,"z":5,"s":3},{"n":"Asansol","a":"India","i":"IN","p":1328000,"la":23.685,"lo":86.981,"k":0,"z":5,"s":3},{"n":"Bhilai","a":"India","i":"IN","p":1097000,"la":21.211,"lo":81.372,"k":0,"z":5,"s":3},{"n":"Bhopal","a":"India","i":"IN","p":1727000,"la":23.252,"lo":77.408,"k":0,"z":5,"s":3},{"n":"Madurai","a":"India","i":"IN","p":1294000,"la":9.922,"lo":78.118,"k":0,"z":5,"s":3},{"n":"Coimbatore","a":"India","i":"IN","p":1696000,"la":11.002,"lo":76.948,"k":0,"z":5,"s":3},{"n":"Vientiane","a":"Laos","i":"LA","p":754000,"la":17.967,"lo":102.6,"k":1,"z":5,"s":3},{"n":"Brazzaville","a":"Congo (Brazzaville)","i":"CG","p":1355000,"la":-4.257,"lo":15.283,"k":1,"z":5,"s":3},{"n":"Conakry","a":"Guinea","i":"GN","p":1494000,"la":9.533,"lo":-13.682,"k":1,"z":5,"s":3},{"n":"Yamoussoukro","a":"Ivory Coast","i":"CI","p":206499,"la":6.818,"lo":-5.276,"k":1,"z":5,"s":3},{"n":"Cruzeiro do Sul","a":"Brazil","i":"BR","p":56862,"la":-7.63,"lo":-72.67,"k":0,"z":5,"s":3},{"n":"Leticia","a":"Colombia","i":"CO","p":59575,"la":-4.201,"lo":-69.955,"k":0,"z":5,"s":3},{"n":"Manaus","a":"Brazil","i":"BR","p":1753000,"la":-3.098,"lo":-60.002,"k":0,"z":4,"s":3},{"n":"Caxias","a":"Brazil","i":"BR","p":134640,"la":-4.833,"lo":-43.35,"k":0,"z":5,"s":3},{"n":"Santarém","a":"Brazil","i":"BR","p":230428,"la":-2.433,"lo":-54.7,"k":0,"z":5,"s":3},{"n":"Marabá","a":"Brazil","i":"BR","p":166182,"la":-5.35,"lo":-49.116,"k":0,"z":5,"s":3},{"n":"Vilhena","a":"Brazil","i":"BR","p":63231,"la":-12.717,"lo":-60.117,"k":0,"z":5,"s":3},{"n":"Ji-Paraná","a":"Brazil","i":"BR","p":65016,"la":-10.833,"lo":-61.967,"k":0,"z":5,"s":3},{"n":"Campo Grande","a":"Brazil","i":"BR","p":778000,"la":-20.448,"lo":-54.619,"k":0,"z":5,"s":3},{"n":"Florianópolis","a":"Brazil","i":"BR","p":1023000,"la":-27.578,"lo":-48.522,"k":0,"z":5,"s":3},{"n":"Feira de Santana","a":"Brazil","i":"BR","p":481911,"la":-12.25,"lo":-38.97,"k":0,"z":5,"s":3},{"n":"Winnipeg","a":"Canada","i":"CA","p":632063,"la":49.883,"lo":-97.166,"k":0,"z":4,"s":3},{"n":"Churchill","a":"Canada","i":"CA","p":1000,"la":58.766,"lo":-94.166,"k":0,"z":4,"s":3},{"n":"Regina","a":"Canada","i":"CA","p":176183,"la":50.45,"lo":-104.617,"k":0,"z":5,"s":3},{"n":"Saskatoon","a":"Canada","i":"CA","p":198958,"la":52.17,"lo":-106.67,"k":0,"z":5,"s":3},{"n":"Calgary","a":"Canada","i":"CA","p":1110000,"la":51.085,"lo":-114.082,"k":0,"z":4,"s":3},{"n":"Prince Rupert","a":"Canada","i":"CA","p":14708,"la":54.317,"lo":-130.33,"k":0,"z":4,"s":3},{"n":"Victoria","a":"Canada","i":"CA","p":289625,"la":48.433,"lo":-123.35,"k":0,"z":5,"s":3},{"n":"Arctic Bay","a":"Canada","i":"CA","p":604,"la":73.033,"lo":-85.167,"k":0,"z":5,"s":3},{"n":"Resolute","a":"Canada","i":"CA","p":250,"la":74.683,"lo":-94.9,"k":0,"z":5,"s":3},{"n":"Naujaat","a":"Canada","i":"CA","p":1000,"la":66.529,"lo":-86.283,"k":0,"z":4,"s":3},{"n":"Yellowknife","a":"Canada","i":"CA","p":19234,"la":62.442,"lo":-114.397,"k":0,"z":4,"s":3},{"n":"Fort Good Hope","a":"Canada","i":"CA","p":597,"la":66.267,"lo":-128.633,"k":0,"z":5,"s":3},{"n":"Whitehorse","a":"Canada","i":"CA","p":23276,"la":60.717,"lo":-135.05,"k":0,"z":4,"s":3},{"n":"Boa Vista","a":"Brazil","i":"BR","p":235150,"la":2.816,"lo":-60.666,"k":0,"z":5,"s":3},{"n":"Macapá","a":"Brazil","i":"BR","p":499166,"la":0.033,"lo":-51.05,"k":0,"z":5,"s":3},{"n":"Ottawa","a":"Canada","i":"CA","p":1145000,"la":45.419,"lo":-75.702,"k":1,"z":3,"s":3},{"n":"Fort Severn","a":"Canada","i":"CA","p":125,"la":55.983,"lo":-87.65,"k":0,"z":5,"s":3},{"n":"Thunder Bay","a":"Canada","i":"CA","p":99334,"la":48.446,"lo":-89.275,"k":0,"z":5,"s":3},{"n":"Québec","a":"Canada","i":"CA","p":624177,"la":46.84,"lo":-71.246,"k":0,"z":4,"s":3},{"n":"Halifax","a":"Canada","i":"CA","p":359111,"la":44.65,"lo":-63.6,"k":0,"z":4,"s":3},{"n":"St. John's","a":"Canada","i":"CA","p":131469,"la":47.585,"lo":-52.681,"k":0,"z":4,"s":3},{"n":"Nain","a":"Canada","i":"CA","p":1151,"la":56.547,"lo":-61.686,"k":0,"z":5,"s":3},{"n":"Charlottetown","a":"Canada","i":"CA","p":42402,"la":46.249,"lo":-63.131,"k":0,"z":5,"s":3},{"n":"N'Délé","a":"Central African Republic","i":"CF","p":11764,"la":8.409,"lo":20.653,"k":0,"z":5,"s":3},{"n":"Belgrade","a":"Serbia","i":"RS","p":1099000,"la":44.821,"lo":20.466,"k":1,"z":5,"s":3},{"n":"Obo","a":"Central African Republic","i":"CF","p":12887,"la":5.4,"lo":26.5,"k":0,"z":5,"s":3},{"n":"Bandar Seri Begawan","a":"Brunei","i":"BN","p":296500,"la":4.883,"lo":114.933,"k":1,"z":5,"s":3},{"n":"Puerto Deseado","a":"Argentina","i":"AR","p":3305,"la":-47.75,"lo":-65.9,"k":0,"z":5,"s":3},{"n":"Río Gallegos","a":"Argentina","i":"AR","p":85700,"la":-51.633,"lo":-69.217,"k":0,"z":5,"s":3},{"n":"Comodoro Rivadavia","a":"Argentina","i":"AR","p":140850,"la":-45.87,"lo":-67.5,"k":0,"z":5,"s":3},{"n":"Mendoza","a":"Argentina","i":"AR","p":893000,"la":-32.881,"lo":-68.819,"k":0,"z":5,"s":3},{"n":"Sucre","a":"Bolivia","i":"BO","p":224838,"la":-19.041,"lo":-65.26,"k":1,"z":4.7,"s":3},{"n":"Riberalta","a":"Bolivia","i":"BO","p":74014,"la":-10.983,"lo":-66.1,"k":0,"z":5,"s":3},{"n":"Bahía Blanca","a":"Argentina","i":"AR","p":281536,"la":-38.74,"lo":-62.265,"k":0,"z":5,"s":3},{"n":"Mar del Plata","a":"Argentina","i":"AR","p":555897,"la":-38,"lo":-57.58,"k":0,"z":5,"s":3},{"n":"Córdoba","a":"Argentina","i":"AR","p":1452000,"la":-31.398,"lo":-64.184,"k":0,"z":4,"s":3},{"n":"Posadas","a":"Argentina","i":"AR","p":357119,"la":-27.358,"lo":-55.885,"k":0,"z":5,"s":3},{"n":"Belmopan","a":"Belize","i":"BZ","p":15220,"la":17.252,"lo":-88.767,"k":1,"z":5,"s":3},{"n":"Bangui","a":"Central African Republic","i":"CF","p":831925,"la":4.367,"lo":18.558,"k":1,"z":5,"s":3},{"n":"Maroua","a":"Cameroon","i":"CM","p":319941,"la":10.596,"lo":14.325,"k":0,"z":5,"s":3},{"n":"Yaoundé","a":"Cameroon","i":"CM","p":1611000,"la":3.869,"lo":11.515,"k":1,"z":5,"s":3},{"n":"Tirana","a":"Albania","i":"AL","p":895350,"la":41.328,"lo":19.819,"k":1,"z":5,"s":3},{"n":"Yerevan","a":"Armenia","i":"AM","p":1102000,"la":40.183,"lo":44.512,"k":1,"z":5,"s":3},{"n":"Baku","a":"Azerbaijan","i":"AZ","p":2122300,"la":40.397,"lo":49.86,"k":1,"z":5,"s":3},{"n":"Kandahar","a":"Afghanistan","i":"AF","p":715542,"la":31.61,"lo":65.695,"k":0,"z":5,"s":3},{"n":"Phnom Penh","a":"Cambodia","i":"KH","p":1466000,"la":11.552,"lo":104.915,"k":1,"z":5,"s":3},{"n":"Menongue","a":"Angola","i":"AO","p":13030,"la":-14.667,"lo":17.7,"k":0,"z":5,"s":3},{"n":"Huambo","a":"Angola","i":"AO","p":1100000,"la":-12.748,"lo":15.758,"k":0,"z":5,"s":3},{"n":"La Paz","a":"Bolivia","i":"BO","p":1590000,"la":-16.496,"lo":-68.152,"k":1,"z":4,"s":3},{"n":"Santa Cruz","a":"Bolivia","i":"BO","p":2102998,"la":-17.752,"lo":-63.228,"k":0,"z":5,"s":3},{"n":"Oran","a":"Algeria","i":"DZ","p":798000,"la":35.712,"lo":-0.622,"k":0,"z":5,"s":3},{"n":"Cotonou","a":"Benin","i":"BJ","p":762000,"la":6.363,"lo":2.404,"k":1,"z":5,"s":3},{"n":"Tamanrasset","a":"Algeria","i":"DZ","p":76000,"la":22.785,"lo":5.523,"k":0,"z":5,"s":3},{"n":"Ghardaia","a":"Algeria","i":"DZ","p":125480,"la":32.49,"lo":3.67,"k":0,"z":5,"s":3},{"n":"Sofia","a":"Bulgaria","i":"BG","p":1185000,"la":42.685,"lo":23.315,"k":1,"z":5,"s":3},{"n":"Minsk","a":"Belarus","i":"BY","p":1805000,"la":53.902,"lo":27.565,"k":1,"z":5,"s":3},{"n":"Thimphu","a":"Bhutan","i":"BT","p":98676,"la":27.473,"lo":89.639,"k":1,"z":5,"s":3},{"n":"Gaborone","a":"Botswana","i":"BW","p":208411,"la":-24.646,"lo":25.912,"k":1,"z":5,"s":3},{"n":"Darwin","a":"Australia","i":"AU","p":93080,"la":-12.425,"lo":130.85,"k":0,"z":3.7,"s":3},{"n":"Alice Springs","a":"Australia","i":"AU","p":27710,"la":-23.701,"lo":133.88,"k":0,"z":5,"s":3},{"n":"Canberra","a":"Australia","i":"AU","p":327700,"la":-35.283,"lo":149.129,"k":1,"z":4,"s":3},{"n":"Newcastle","a":"Australia","i":"AU","p":540796,"la":-32.93,"lo":151.77,"k":0,"z":5,"s":3},{"n":"Adelaide","a":"Australia","i":"AU","p":1145000,"la":-34.933,"lo":138.598,"k":0,"z":3.7,"s":3},{"n":"Townsville","a":"Australia","i":"AU","p":138954,"la":-19.25,"lo":146.77,"k":0,"z":5,"s":3},{"n":"Brisbane","a":"Australia","i":"AU","p":1860000,"la":-27.453,"lo":153.033,"k":0,"z":4,"s":3},{"n":"Hobart","a":"Australia","i":"AU","p":80870,"la":-42.85,"lo":147.295,"k":0,"z":5,"s":3},{"n":"Ouagadougou","a":"Burkina Faso","i":"BF","p":1149000,"la":12.372,"lo":-1.527,"k":1,"z":5,"s":3},{"n":"Sarajevo","a":"Bosnia and Herzegovina","i":"BA","p":696731,"la":43.85,"lo":18.383,"k":1,"z":5,"s":3},{"n":"Naypyidaw","a":"Myanmar","i":"MM","p":930000,"la":19.769,"lo":96.117,"k":1,"z":5,"s":3},{"n":"San Juan","a":"Puerto Rico","i":"PR","p":2478905,"la":18.44,"lo":-66.13,"k":0,"z":5,"s":3},{"n":"Stanley","a":"Falkland Islands","i":"FK","p":2213,"la":-51.7,"lo":-57.85,"k":0,"z":5,"s":3},{"n":"Hamilton","a":"Bermuda","i":"BM","p":52320,"la":32.294,"lo":-64.784,"k":1,"z":5,"s":3},{"n":"Nuku'alofa","a":"Tonga","i":"TO","p":42620,"la":-21.139,"lo":-175.221,"k":1,"z":5,"s":3},{"n":"Hargeisa","a":"Somaliland","i":"-99","p":477876,"la":9.56,"lo":44.065,"k":1,"z":5,"s":3},{"n":"Victoria","a":"Seychelles","i":"SC","p":33576,"la":-4.617,"lo":55.45,"k":1,"z":5,"s":3},{"n":"São Tomé","a":"Sao Tome and Principe","i":"ST","p":88219,"la":0.337,"lo":6.73,"k":1,"z":5,"s":3},{"n":"Apia","a":"Samoa","i":"WS","p":61916,"la":-13.836,"lo":-171.769,"k":1,"z":5,"s":3},{"n":"Valletta","a":"Malta","i":"MT","p":368250,"la":35.9,"lo":14.515,"k":1,"z":5,"s":3},{"n":"Malé","a":"Maldives","i":"MV","p":112927,"la":4.172,"lo":73.509,"k":1,"z":5,"s":3},{"n":"Jerusalem","a":"Israel","i":"IL","p":1029300,"la":31.778,"lo":35.207,"k":1,"z":3.7,"s":3},{"n":"Praia","a":"Cape Verde","i":"CV","p":113364,"la":14.917,"lo":-23.517,"k":1,"z":5,"s":3},{"n":"Nassau","a":"The Bahamas","i":"BS","p":227940,"la":25.083,"lo":-77.35,"k":1,"z":5,"s":3},{"n":"Nicosia","a":"Cyprus","i":"CY","p":224300,"la":35.167,"lo":33.367,"k":1,"z":5,"s":3},{"n":"Kaohsiung","a":"Taiwan","i":"TW","p":2769072,"la":22.633,"lo":120.267,"k":0,"z":5,"s":3},{"n":"Wellington","a":"New Zealand","i":"NZ","p":393400,"la":-41.292,"lo":174.777,"k":1,"z":4,"s":3},{"n":"Christchurch","a":"New Zealand","i":"NZ","p":363200,"la":-43.535,"lo":172.63,"k":0,"z":4,"s":3},{"n":"Shenzhen","a":"China","i":"CN","p":7581000,"la":22.548,"lo":114.061,"k":0,"z":4.7,"s":2},{"n":"Zibo","a":"China","i":"CN","p":3061000,"la":36.821,"lo":117.996,"k":0,"z":4.7,"s":2},{"n":"Minneapolis","a":"United States of America","i":"US","p":2616000,"la":44.982,"lo":-93.254,"k":0,"z":4,"s":2},{"n":"Honolulu","a":"United States of America","i":"US","p":786000,"la":21.303,"lo":-157.858,"k":0,"z":4,"s":2},{"n":"Seattle","a":"United States of America","i":"US","p":3074000,"la":47.6,"lo":-122.315,"k":0,"z":4,"s":2},{"n":"Phoenix","a":"United States of America","i":"US","p":3551000,"la":33.448,"lo":-112.067,"k":0,"z":4,"s":2},{"n":"San Diego","a":"United States of America","i":"US","p":2916000,"la":32.719,"lo":-117.151,"k":0,"z":4,"s":2},{"n":"St. Louis","a":"United States of America","i":"US","p":2199000,"la":38.637,"lo":-90.242,"k":0,"z":4,"s":2},{"n":"New Orleans","a":"United States of America","i":"US","p":785000,"la":29.969,"lo":-90.088,"k":0,"z":4,"s":2},{"n":"Dallas","a":"United States of America","i":"US","p":4798000,"la":32.772,"lo":-96.795,"k":0,"z":4,"s":2},{"n":"Maracaibo","a":"Venezuela","i":"VE","p":2072000,"la":10.732,"lo":-71.662,"k":0,"z":4,"s":2},{"n":"Boston","a":"United States of America","i":"US","p":4467000,"la":42.332,"lo":-71.072,"k":0,"z":4,"s":2},{"n":"Tampa","a":"United States of America","i":"US","p":2314000,"la":27.949,"lo":-82.461,"k":0,"z":5,"s":2},{"n":"Philadelphia","a":"United States of America","i":"US","p":5492000,"la":39.946,"lo":-75.18,"k":0,"z":5,"s":2},{"n":"Detroit","a":"United States of America","i":"US","p":4101000,"la":42.334,"lo":-83.051,"k":0,"z":4,"s":2},{"n":"Anchorage","a":"United States of America","i":"US","p":260283,"la":61.214,"lo":-149.887,"k":0,"z":3,"s":2},{"n":"Hanoi","a":"Vietnam","i":"VN","p":4378000,"la":21.035,"lo":105.848,"k":1,"z":4,"s":2},{"n":"Ho Chi Minh City","a":"Vietnam","i":"VN","p":5314000,"la":10.762,"lo":106.703,"k":0,"z":4,"s":2},{"n":"Ankara","a":"Turkey","i":"TR","p":3716000,"la":39.929,"lo":32.862,"k":1,"z":4,"s":2},{"n":"Budapest","a":"Hungary","i":"HU","p":1679000,"la":47.502,"lo":19.081,"k":1,"z":4.7,"s":2},{"n":"Sanaa","a":"Yemen","i":"YE","p":2008000,"la":15.357,"lo":44.205,"k":1,"z":4,"s":2},{"n":"Barcelona","a":"Spain","i":"ES","p":4920000,"la":41.385,"lo":2.181,"k":0,"z":4,"s":2},{"n":"Bucharest","a":"Romania","i":"RO","p":1942000,"la":44.435,"lo":26.098,"k":1,"z":4,"s":2},{"n":"Aleppo","a":"Syria","i":"SY","p":2738000,"la":36.232,"lo":37.168,"k":0,"z":4.7,"s":2},{"n":"Damascus","a":"Syria","i":"SY","p":2466000,"la":33.502,"lo":36.298,"k":1,"z":4.7,"s":2},{"n":"Zürich","a":"Switzerland","i":"CH","p":1108000,"la":47.382,"lo":8.548,"k":0,"z":4.7,"s":2},{"n":"Lisbon","a":"Portugal","i":"PT","p":2812000,"la":38.725,"lo":-9.147,"k":1,"z":4,"s":2},{"n":"Khartoum","a":"Sudan","i":"SD","p":4754000,"la":15.59,"lo":32.532,"k":1,"z":4,"s":2},{"n":"Jeddah","a":"Saudi Arabia","i":"SA","p":3012000,"la":21.519,"lo":39.217,"k":0,"z":4,"s":2},{"n":"Makkah","a":"Saudi Arabia","i":"SA","p":1385000,"la":21.432,"lo":39.818,"k":0,"z":3.7,"s":2},{"n":"Oslo","a":"Norway","i":"NO","p":835000,"la":59.919,"lo":10.748,"k":1,"z":4,"s":2},{"n":"Lahore","a":"Pakistan","i":"PK","p":6577000,"la":31.562,"lo":74.348,"k":0,"z":4,"s":2},{"n":"Karachi","a":"Pakistan","i":"PK","p":12130000,"la":24.872,"lo":66.988,"k":0,"z":4,"s":2},{"n":"Durban","a":"South Africa","i":"ZA","p":2729000,"la":-29.861,"lo":31.011,"k":0,"z":4,"s":2},{"n":"St. Petersburg","a":"Russia","i":"RU","p":4553000,"la":59.941,"lo":30.314,"k":0,"z":3.7,"s":2},{"n":"Guadalajara","a":"Mexico","i":"MX","p":4198000,"la":20.672,"lo":-103.332,"k":0,"z":4,"s":2},{"n":"Puebla","a":"Mexico","i":"MX","p":2195000,"la":19.033,"lo":-98.204,"k":0,"z":4,"s":2},{"n":"Kano","a":"Nigeria","i":"NG","p":3140000,"la":12.002,"lo":8.518,"k":0,"z":4.7,"s":2},{"n":"Warsaw","a":"Poland","i":"PL","p":1707000,"la":52.231,"lo":21.005,"k":1,"z":4,"s":2},{"n":"Pyongyang","a":"North Korea","i":"KP","p":3300000,"la":39.021,"lo":125.753,"k":1,"z":4.7,"s":2},{"n":"Dar es Salaam","a":"Tanzania","i":"TZ","p":2930000,"la":-6.798,"lo":39.266,"k":1,"z":4,"s":2},{"n":"Medan","a":"Indonesia","i":"ID","p":2115000,"la":3.582,"lo":98.648,"k":0,"z":4,"s":2},{"n":"Dublin","a":"Ireland","i":"IE","p":1059000,"la":53.347,"lo":-6.257,"k":1,"z":4,"s":2},{"n":"Monrovia","a":"Liberia","i":"LR","p":1041000,"la":6.315,"lo":-10.8,"k":1,"z":4,"s":2},{"n":"Naples","a":"Italy","i":"IT","p":2250000,"la":40.842,"lo":14.243,"k":0,"z":4.7,"s":2},{"n":"Milan","a":"Italy","i":"IT","p":2945000,"la":45.472,"lo":9.203,"k":0,"z":4.7,"s":2},{"n":"Kuala Lumpur","a":"Malaysia","i":"MY","p":1448000,"la":3.14,"lo":101.689,"k":1,"z":4,"s":2},{"n":"Lanzhou","a":"China","i":"CN","p":2561000,"la":36.058,"lo":103.79,"k":0,"z":4,"s":2},{"n":"Nanning","a":"China","i":"CN","p":2167000,"la":22.822,"lo":108.318,"k":0,"z":4,"s":2},{"n":"Guiyang","a":"China","i":"CN","p":3662000,"la":26.582,"lo":106.718,"k":0,"z":4.7,"s":2},{"n":"Chongqing","a":"China","i":"CN","p":6461000,"la":29.567,"lo":106.593,"k":0,"z":4,"s":2},{"n":"Fuzhou","a":"China","i":"CN","p":2606000,"la":26.082,"lo":119.298,"k":0,"z":4.7,"s":2},{"n":"Guangzhou","a":"China","i":"CN","p":8829000,"la":23.125,"lo":113.262,"k":0,"z":4,"s":2},{"n":"Dongguan","a":"China","i":"CN","p":4528000,"la":23.022,"lo":113.75,"k":0,"z":5.6,"s":2},{"n":"Bandung","a":"Indonesia","i":"ID","p":2394000,"la":-6.948,"lo":107.568,"k":0,"z":4.7,"s":2},{"n":"Surabaya","a":"Indonesia","i":"ID","p":2845000,"la":-7.247,"lo":112.749,"k":0,"z":4,"s":2},{"n":"Guayaquil","a":"Ecuador","i":"EC","p":2514000,"la":-2.218,"lo":-79.922,"k":0,"z":4.7,"s":2},{"n":"Medellín","a":"Colombia","i":"CO","p":3297000,"la":6.277,"lo":-75.577,"k":0,"z":4,"s":2},{"n":"Cali","a":"Colombia","i":"CO","p":2254000,"la":3.402,"lo":-76.502,"k":0,"z":4,"s":2},{"n":"Havana","a":"Cuba","i":"CU","p":2174000,"la":23.134,"lo":-82.366,"k":1,"z":4,"s":2},{"n":"Alexandria","a":"Egypt","i":"EG","p":4165000,"la":31.202,"lo":29.948,"k":0,"z":4,"s":2},{"n":"Frankfurt","a":"Germany","i":"DE","p":2895000,"la":50.1,"lo":8.675,"k":0,"z":4,"s":2},{"n":"Hamburg","a":"Germany","i":"DE","p":1757000,"la":53.552,"lo":9.998,"k":0,"z":5,"s":2},{"n":"Munich","a":"Germany","i":"DE","p":1275000,"la":48.132,"lo":11.573,"k":0,"z":4.7,"s":2},{"n":"Prague","a":"Czechia","i":"CZ","p":1162000,"la":50.087,"lo":14.423,"k":1,"z":4.7,"s":2},{"n":"Kuwait City","a":"Kuwait","i":"KW","p":2063000,"la":29.372,"lo":47.976,"k":1,"z":4,"s":2},{"n":"Xian","a":"China","i":"CN","p":4009000,"la":34.277,"lo":108.893,"k":0,"z":3.7,"s":2},{"n":"Taiyuan","a":"China","i":"CN","p":2913000,"la":37.877,"lo":112.543,"k":0,"z":4,"s":2},{"n":"Wuhan","a":"China","i":"CN","p":7243000,"la":30.582,"lo":114.268,"k":0,"z":4,"s":2},{"n":"Changsha","a":"China","i":"CN","p":2604000,"la":28.202,"lo":112.968,"k":0,"z":4.7,"s":2},{"n":"Kunming","a":"China","i":"CN","p":2931000,"la":25.043,"lo":102.697,"k":0,"z":4,"s":2},{"n":"Zhengzhou","a":"China","i":"CN","p":2636000,"la":34.757,"lo":113.663,"k":0,"z":4,"s":2},{"n":"Shenyeng","a":"China","i":"CN","p":4787000,"la":41.807,"lo":123.448,"k":0,"z":4,"s":2},{"n":"Jinan","a":"China","i":"CN","p":2798000,"la":36.677,"lo":116.993,"k":0,"z":4,"s":2},{"n":"Tianjin","a":"China","i":"CN","p":7180000,"la":39.083,"lo":117.197,"k":0,"z":4,"s":2},{"n":"Nanchang","a":"China","i":"CN","p":2350000,"la":28.682,"lo":115.878,"k":0,"z":4.7,"s":2},{"n":"Nanjing","a":"China","i":"CN","p":3679000,"la":32.052,"lo":118.778,"k":0,"z":4,"s":2},{"n":"Hangzhou","a":"China","i":"CN","p":3007000,"la":30.252,"lo":120.168,"k":0,"z":4,"s":2},{"n":"Hiroshima","a":"Japan","i":"JP","p":2045000,"la":34.39,"lo":132.441,"k":0,"z":4,"s":2},{"n":"Changchun","a":"China","i":"CN","p":3183000,"la":43.867,"lo":125.338,"k":0,"z":4,"s":2},{"n":"Baotou","a":"China","i":"CN","p":2036000,"la":40.654,"lo":109.82,"k":0,"z":4,"s":2},{"n":"Harbin","a":"China","i":"CN","p":3621000,"la":45.752,"lo":126.648,"k":0,"z":3.7,"s":2},{"n":"Sapporo","a":"Japan","i":"JP","p":2544000,"la":43.077,"lo":141.338,"k":0,"z":4,"s":2},{"n":"Santo Domingo","a":"Dominican Republic","i":"DO","p":2154000,"la":18.471,"lo":-69.93,"k":1,"z":4,"s":2},{"n":"Accra","a":"Ghana","i":"GH","p":2121000,"la":5.552,"lo":-0.219,"k":1,"z":4,"s":2},{"n":"Delhi","a":"India","i":"IN","p":15926000,"la":28.672,"lo":77.228,"k":0,"z":4,"s":2},{"n":"Hyderabad","a":"India","i":"IN","p":6376000,"la":17.402,"lo":78.478,"k":0,"z":4,"s":2},{"n":"Pune","a":"India","i":"IN","p":4672000,"la":18.516,"lo":73.852,"k":0,"z":4,"s":2},{"n":"Nagpur","a":"India","i":"IN","p":2454000,"la":21.172,"lo":79.088,"k":0,"z":4,"s":2},{"n":"Tripoli","a":"Libya","i":"LY","p":2189000,"la":32.893,"lo":13.18,"k":1,"z":4,"s":2},{"n":"Tel Aviv","a":"Israel","i":"IL","p":3112000,"la":32.082,"lo":34.768,"k":0,"z":4,"s":2},{"n":"Helsinki","a":"Finland","i":"FI","p":1115000,"la":60.164,"lo":24.932,"k":1,"z":4,"s":2},{"n":"Mashhad","a":"Iran","i":"IR","p":2469000,"la":36.272,"lo":59.568,"k":0,"z":4,"s":2},{"n":"Jaipur","a":"India","i":"IN","p":2917000,"la":26.923,"lo":75.808,"k":0,"z":4,"s":2},{"n":"Kanpur","a":"India","i":"IN","p":3162000,"la":26.462,"lo":80.318,"k":0,"z":4,"s":2},{"n":"Patna","a":"India","i":"IN","p":2158000,"la":25.627,"lo":85.128,"k":0,"z":4.7,"s":2},{"n":"Chennai","a":"India","i":"IN","p":7163000,"la":13.092,"lo":80.278,"k":0,"z":4,"s":2},{"n":"Ahmedabad","a":"India","i":"IN","p":5375000,"la":23.032,"lo":72.578,"k":0,"z":4.7,"s":2},{"n":"Surat","a":"India","i":"IN","p":3842000,"la":21.202,"lo":72.838,"k":0,"z":4,"s":2},{"n":"København","a":"Denmark","i":"DK","p":1085000,"la":55.681,"lo":12.562,"k":1,"z":4,"s":2},{"n":"Abidjan","a":"Ivory Coast","i":"CI","p":3802000,"la":5.323,"lo":-4.02,"k":1,"z":4,"s":2},{"n":"Belém","a":"Brazil","i":"BR","p":2167000,"la":-1.448,"lo":-48.482,"k":0,"z":4,"s":2},{"n":"Brasília","a":"Brazil","i":"BR","p":3716996,"la":-15.781,"lo":-47.918,"k":1,"z":3.7,"s":2},{"n":"Porto Alegre","a":"Brazil","i":"BR","p":3917000,"la":-30.048,"lo":-51.202,"k":0,"z":4,"s":2},{"n":"Curitiba","a":"Brazil","i":"BR","p":3084000,"la":-25.418,"lo":-49.322,"k":0,"z":4,"s":2},{"n":"Fortaleza","a":"Brazil","i":"BR","p":3602319,"la":-3.748,"lo":-38.582,"k":0,"z":4,"s":2},{"n":"Salvador","a":"Brazil","i":"BR","p":3484000,"la":-12.968,"lo":-38.482,"k":0,"z":3.7,"s":2},{"n":"Edmonton","a":"Canada","i":"CA","p":1058000,"la":53.552,"lo":-113.502,"k":0,"z":4,"s":2},{"n":"Montréal","a":"Canada","i":"CA","p":3678000,"la":45.502,"lo":-73.585,"k":0,"z":4,"s":2},{"n":"Goiânia","a":"Brazil","i":"BR","p":2022000,"la":-16.718,"lo":-49.302,"k":0,"z":4.7,"s":2},{"n":"Recife","a":"Brazil","i":"BR","p":3651000,"la":-8.061,"lo":-34.909,"k":0,"z":3.7,"s":2},{"n":"Brussels","a":"Belgium","i":"BE","p":1743000,"la":50.835,"lo":4.331,"k":1,"z":4.7,"s":2},{"n":"Dhaka","a":"Bangladesh","i":"BD","p":12797394,"la":23.725,"lo":90.407,"k":1,"z":4,"s":2},{"n":"Luanda","a":"Angola","i":"AO","p":5172900,"la":-8.836,"lo":13.232,"k":1,"z":4,"s":2},{"n":"Algiers","a":"Algeria","i":"DZ","p":3354000,"la":36.765,"lo":3.049,"k":1,"z":4,"s":2},{"n":"Chattogram","a":"Bangladesh","i":"BD","p":4529000,"la":22.332,"lo":91.798,"k":0,"z":4,"s":2},{"n":"Perth","a":"Australia","i":"AU","p":1532000,"la":-31.953,"lo":115.838,"k":0,"z":3.7,"s":2},{"n":"Yangon","a":"Myanmar","i":"MM","p":4088000,"la":16.785,"lo":96.165,"k":0,"z":4,"s":2},{"n":"San Francisco","a":"United States of America","i":"US","p":3450000,"la":37.784,"lo":-122.4,"k":0,"z":2.7,"s":1},{"n":"Denver","a":"United States of America","i":"US","p":2313000,"la":39.741,"lo":-104.986,"k":0,"z":3.7,"s":1},{"n":"Houston","a":"United States of America","i":"US","p":4459000,"la":29.741,"lo":-95.348,"k":0,"z":3,"s":1},{"n":"Miami","a":"United States of America","i":"US","p":5585000,"la":25.79,"lo":-80.226,"k":0,"z":2.1,"s":1},{"n":"Atlanta","a":"United States of America","i":"US","p":4506000,"la":33.739,"lo":-84.368,"k":0,"z":3,"s":1},{"n":"Chicago","a":"United States of America","i":"US","p":8990000,"la":41.848,"lo":-87.635,"k":0,"z":3,"s":1},{"n":"Caracas","a":"Venezuela","i":"VE","p":2985000,"la":10.503,"lo":-66.919,"k":1,"z":3,"s":1},{"n":"Kyiv","a":"Ukraine","i":"UA","p":2709000,"la":50.435,"lo":30.515,"k":1,"z":3.7,"s":1},{"n":"Dubai","a":"United Arab Emirates","i":"AE","p":1379000,"la":25.215,"lo":55.287,"k":0,"z":3.7,"s":1},{"n":"Tashkent","a":"Uzbekistan","i":"UZ","p":2184000,"la":41.304,"lo":69.269,"k":1,"z":3.7,"s":1},{"n":"Madrid","a":"Spain","i":"ES","p":5567000,"la":40.402,"lo":-3.685,"k":1,"z":3,"s":1},{"n":"Geneva","a":"Switzerland","i":"CH","p":1240000,"la":46.21,"lo":6.14,"k":0,"z":3.7,"s":1},{"n":"Stockholm","a":"Sweden","i":"SE","p":1264000,"la":59.324,"lo":18.066,"k":1,"z":3,"s":1},{"n":"Bangkok","a":"Thailand","i":"TH","p":6704000,"la":13.752,"lo":100.515,"k":1,"z":3,"s":1},{"n":"Lima","a":"Peru","i":"PE","p":8012000,"la":-12.046,"lo":-77.052,"k":1,"z":3,"s":1},{"n":"Dakar","a":"Senegal","i":"SN","p":2604000,"la":14.718,"lo":-17.475,"k":1,"z":3,"s":1},{"n":"Johannesburg","a":"South Africa","i":"ZA","p":3435000,"la":-26.168,"lo":28.028,"k":0,"z":3,"s":1},{"n":"Amsterdam","a":"Netherlands","i":"NL","p":1031000,"la":52.352,"lo":4.915,"k":1,"z":3.7,"s":1},{"n":"Casablanca","a":"Morocco","i":"MA","p":3181000,"la":33.602,"lo":-7.618,"k":0,"z":3.7,"s":1},{"n":"Seoul","a":"South Korea","i":"KR","p":9796000,"la":37.568,"lo":126.998,"k":1,"z":3,"s":1},{"n":"Manila","a":"Philippines","i":"PH","p":11100000,"la":14.606,"lo":120.98,"k":1,"z":2.7,"s":1},{"n":"Monterrey","a":"Mexico","i":"MX","p":3712000,"la":25.672,"lo":-100.332,"k":0,"z":3.7,"s":1},{"n":"Berlin","a":"Germany","i":"DE","p":3406000,"la":52.524,"lo":13.4,"k":1,"z":2.7,"s":1},{"n":"Ürümqi","a":"China","i":"CN","p":3575000,"la":43.807,"lo":87.573,"k":0,"z":3.7,"s":1},{"n":"Chengdu","a":"China","i":"CN","p":4123000,"la":30.672,"lo":104.068,"k":0,"z":3,"s":1},{"n":"Ōsaka","a":"Japan","i":"JP","p":11294000,"la":34.691,"lo":135.504,"k":0,"z":3,"s":1},{"n":"Kinshasa","a":"Congo (Kinshasa)","i":"CD","p":7843000,"la":-4.328,"lo":15.313,"k":1,"z":3,"s":1},{"n":"New Delhi","a":"India","i":"IN","p":317797,"la":28.6,"lo":77.2,"k":1,"z":2.1,"s":1},{"n":"Bengaluru","a":"India","i":"IN","p":6787000,"la":12.972,"lo":77.558,"k":0,"z":3.7,"s":1},{"n":"Athens","a":"Greece","i":"GR","p":3242000,"la":37.985,"lo":23.731,"k":1,"z":3.7,"s":1},{"n":"Baghdad","a":"Iraq","i":"IQ","p":5054000,"la":33.341,"lo":44.392,"k":1,"z":3.7,"s":1},{"n":"Addis Ababa","a":"Ethiopia","i":"ET","p":3100000,"la":9.035,"lo":38.698,"k":1,"z":3,"s":1},{"n":"Tehran","a":"Iran","i":"IR","p":7873000,"la":35.674,"lo":51.422,"k":1,"z":3,"s":1},{"n":"Vancouver","a":"Canada","i":"CA","p":2313328,"la":49.275,"lo":-123.124,"k":0,"z":2.1,"s":1},{"n":"Toronto","a":"Canada","i":"CA","p":5213000,"la":43.665,"lo":-79.389,"k":0,"z":2.1,"s":1},{"n":"Buenos Aires","a":"Argentina","i":"AR","p":12795000,"la":-34.611,"lo":-58.433,"k":1,"z":2.1,"s":1},{"n":"Kabul","a":"Afghanistan","i":"AF","p":3277000,"la":34.519,"lo":69.181,"k":1,"z":3.7,"s":1},{"n":"Vienna","a":"Austria","i":"AT","p":2400000,"la":48.202,"lo":16.365,"k":1,"z":3.7,"s":1},{"n":"Melbourne","a":"Australia","i":"AU","p":4170000,"la":-37.818,"lo":144.973,"k":0,"z":3,"s":1},{"n":"Taipei","a":"Taiwan","i":"TW","p":6900273,"la":25.036,"lo":121.568,"k":1,"z":3.7,"s":1},{"n":"Auckland","a":"New Zealand","i":"NZ","p":1377200,"la":-36.848,"lo":174.763,"k":0,"z":3,"s":1},{"n":"Los Angeles","a":"United States of America","i":"US","p":12500000,"la":34.049,"lo":-118.232,"k":0,"z":2,"s":0},{"n":"Washington, D.C.","a":"United States of America","i":"US","p":4338000,"la":38.901,"lo":-77.011,"k":1,"z":2.1,"s":0},{"n":"New York","a":"United States of America","i":"US","p":19040000,"la":40.722,"lo":-73.996,"k":0,"z":1.7,"s":0},{"n":"London","a":"United Kingdom","i":"GB","p":8567000,"la":51.502,"lo":-0.119,"k":1,"z":1.7,"s":0},{"n":"Istanbul","a":"Turkey","i":"TR","p":10061000,"la":41.018,"lo":28.974,"k":0,"z":3,"s":0},{"n":"Riyadh","a":"Saudi Arabia","i":"SA","p":4465000,"la":24.634,"lo":46.72,"k":1,"z":3.7,"s":0},{"n":"Cape Town","a":"South Africa","i":"ZA","p":3215000,"la":-33.918,"lo":18.433,"k":1,"z":1.7,"s":0},{"n":"Moscow","a":"Russia","i":"RU","p":10452000,"la":55.754,"lo":37.614,"k":1,"z":1.7,"s":0},{"n":"Mexico City","a":"Mexico","i":"MX","p":19028000,"la":19.444,"lo":-99.133,"k":1,"z":2.1,"s":0},{"n":"Lagos","a":"Nigeria","i":"NG","p":9466000,"la":6.445,"lo":3.39,"k":0,"z":2,"s":0},{"n":"Rome","a":"Italy","i":"IT","p":3339000,"la":41.898,"lo":12.481,"k":1,"z":2.7,"s":0},{"n":"Beijing","a":"China","i":"CN","p":11106000,"la":39.902,"lo":116.394,"k":1,"z":1.7,"s":0},{"n":"Nairobi","a":"Kenya","i":"KE","p":3010000,"la":-1.281,"lo":36.815,"k":1,"z":2,"s":0},{"n":"Jakarta","a":"Indonesia","i":"ID","p":9125000,"la":-6.172,"lo":106.827,"k":1,"z":2.1,"s":0},{"n":"Bogota","a":"Colombia","i":"CO","p":7772000,"la":4.598,"lo":-74.085,"k":1,"z":2.1,"s":0},{"n":"Cairo","a":"Egypt","i":"EG","p":11893000,"la":30.052,"lo":31.248,"k":1,"z":1.7,"s":0},{"n":"Shanghai","a":"China","i":"CN","p":14987000,"la":31.218,"lo":121.435,"k":0,"z":2,"s":0},{"n":"Tokyo","a":"Japan","i":"JP","p":35676000,"la":35.687,"lo":139.749,"k":1,"z":1.7,"s":0},{"n":"Mumbai","a":"India","i":"IN","p":18978000,"la":19.068,"lo":72.876,"k":0,"z":1.7,"s":0},{"n":"Paris","a":"France","i":"FR","p":9904000,"la":48.858,"lo":2.353,"k":1,"z":1.7,"s":0},{"n":"Santiago","a":"Chile","i":"CL","p":5720000,"la":-33.44,"lo":-70.651,"k":1,"z":2,"s":0},{"n":"Kolkata","a":"India","i":"IN","p":14787000,"la":22.57,"lo":88.369,"k":0,"z":3.7,"s":0},{"n":"Rio de Janeiro","a":"Brazil","i":"BR","p":11748000,"la":-22.907,"lo":-43.212,"k":0,"z":1.7,"s":0},{"n":"São Paulo","a":"Brazil","i":"BR","p":18845000,"la":-23.557,"lo":-46.627,"k":0,"z":3,"s":0},{"n":"Sydney","a":"Australia","i":"AU","p":4630000,"la":-33.871,"lo":151.213,"k":0,"z":1.7,"s":0},{"n":"Singapore","a":"Singapore","i":"SG","p":5183700,"la":1.295,"lo":103.854,"k":1,"z":2.1,"s":0},{"n":"Hong Kong","a":"Hong Kong S.A.R.","i":"HK","p":7206000,"la":22.307,"lo":114.183,"k":0,"z":3,"s":0}]} \ No newline at end of file diff --git a/src/maps/data/rivers_50m.json b/src/maps/data/rivers_50m.json new file mode 100644 index 0000000..669bdd0 --- /dev/null +++ b/src/maps/data/rivers_50m.json @@ -0,0 +1 @@ +{"f":[{"n":"Kama","z":5,"b":[49.248,55.113,51.937,55.701],"l":[[51.937,55.701,51.881,55.686,51.82,55.697,51.748,55.694,51.663,55.608,51.579,55.599,51.513,55.583,51.509,55.529,51.486,55.496,51.369,55.468,51.213,55.503,51.135,55.483,51.079,55.468,50.98,55.466,50.834,55.456,50.688,55.42,50.412,55.401,50.078,55.381,49.822,55.335,49.532,55.261,49.382,55.172,49.248,55.113]]},{"n":"Kama","z":5,"b":[51.936,55.695,56.686,60.5],"l":[[53.694,58.206,53.677,58.273,53.626,58.357,53.552,58.394,53.465,58.419,53.429,58.435,53.439,58.485,53.471,58.511,53.492,58.587,53.381,58.672,53.3,58.78,53.247,58.825,53.201,58.93,53.239,59.011,53.219,59.099,53.15,59.128,53.063,59.178,53.003,59.209,52.964,59.282,52.912,59.34,52.866,59.402,52.815,59.494,52.689,59.582,52.615,59.608,52.609,59.68,52.639,59.71,52.687,59.734,52.767,59.724,52.803,59.758,52.866,59.798,52.92,59.896,52.975,59.956,53.035,59.973,53.126,59.99,53.221,60.015,53.322,60.052,53.401,60.056,53.451,60.074,53.492,60.124,53.526,60.139,53.595,60.138,53.732,60.184,53.825,60.21,53.897,60.259,53.947,60.292,53.986,60.323,54.038,60.337,54.105,60.336,54.155,60.357,54.267,60.361,54.313,60.337,54.391,60.277,54.521,60.239,54.849,60.245,55.035,60.226,55.146,60.203,55.25,60.221,55.358,60.251,55.571,60.311,55.664,60.417,55.715,60.485,55.873,60.5,55.979,60.459,56.11,60.424,56.137,60.392,56.135,60.385,56.165,60.334,56.156,60.287,56.15,60.234,56.152,60.205,56.168,60.155,56.236,60.159,56.277,60.14,56.281,60.107,56.299,60.052,56.294,60.001,56.311,59.945,56.341,59.93,56.364,59.923,56.385,59.917,56.434,59.883,56.471,59.85,56.55,59.811,56.591,59.766,56.623,59.691,56.661,59.625,56.683,59.562,56.686,59.452,56.678,59.389,56.612,59.349,56.575,59.333,56.427,59.304,56.342,59.279,56.24,59.25,56.209,59.197,56.179,59.098,56.224,58.997,56.252,58.885,56.268,58.81,56.145,58.696,56.089,58.655,56.095,58.572,56.177,58.554,56.251,58.522,56.393,58.443,56.341,58.261,56.334,58.146,56.333,58.136,56.331,58.11,56.328,58.096,56.322,58.078,56.308,58.059,56.201,58.028,56.066,58.03,55.891,57.949,55.832,57.946,55.794,58.022,55.726,58.076,55.661,58.069,55.633,58.015,55.475,57.896,55.299,57.877,55.247,57.874,55.212,57.832,55.267,57.796,55.347,57.763,55.4,57.68,55.449,57.59,55.525,57.577,55.544,57.532,55.456,57.33,55.355,57.313,55.248,57.306,55.136,57.342,55.081,57.341,55.043,57.305,55.016,57.237,54.979,57.114,54.922,57.091,54.85,57.086,54.781,57.055,54.67,57.042,54.539,57.055,54.453,57.042,54.379,56.998,54.313,56.984,54.222,56.979,54.167,56.942,54.15,56.87,54.136,56.82,54.088,56.771,54.046,56.753,53.996,56.736,53.949,56.74,53.917,56.777,53.91,56.812,53.885,56.828,53.831,56.839,53.797,56.834,53.746,56.805,53.743,56.794,53.75,56.777,53.752,56.759,53.747,56.715,53.757,56.674,53.774,56.661,53.781,56.654,53.792,56.613,53.8,56.528,53.814,56.49,53.842,56.458,53.854,56.419,53.866,56.401,53.9,56.393,53.975,56.36,54.035,56.343,54.102,56.278,54.157,56.24,54.185,56.188,54.117,56.158,54.099,56.144,54.083,56.123,54.047,56.058,54.006,56.029,53.949,56.022,53.888,56.032,53.834,56.051,53.774,56.051,53.739,56.034,53.663,55.993,53.643,55.982,53.565,55.97,53.543,55.959,53.54,55.951,53.539,55.94,53.54,55.918,53.539,55.914,53.536,55.911,53.526,55.904,53.499,55.893,53.286,55.852,53.063,55.87,52.992,55.9,52.917,55.926,52.842,55.93,52.76,55.954,52.708,55.965,52.609,55.928,52.53,55.863,52.468,55.852,52.377,55.87,52.332,55.871,52.306,55.842,52.316,55.803,52.344,55.753,52.352,55.729,52.335,55.705,52.277,55.696,52.094,55.695,52.023,55.722,51.936,55.701]]},{"n":"Abay","z":3,"b":[37.113,11.632,37.387,11.906],"l":[[37.113,11.855,37.15,11.894,37.197,11.906,37.264,11.903,37.32,11.834,37.387,11.632]]},{"n":"Al Furat","z":3,"b":[38.066,35.863,38.561,36.342],"l":[[38.561,35.863,38.365,35.903,38.246,35.951,38.139,36.02,38.079,36.074,38.066,36.114,38.092,36.167,38.157,36.232,38.188,36.284,38.184,36.342]]},{"n":"Alabama","z":5,"b":[-86.522,32.811,-86.2,33.588],"l":[[-86.522,33.032,-86.521,33.014,-86.461,32.836,-86.445,32.811],[-86.2,33.588,-86.212,33.56,-86.33,33.444]]},{"n":"Albany","z":5,"b":[-91.97,50.998,-90.417,51.988],"l":[[-91.312,51.063,-91.075,50.998,-90.875,51.014,-90.634,51.087,-90.489,51.116,-90.417,51.093],[-91.598,51.162,-91.418,51.068],[-91.781,51.335,-91.886,51.458],[-91.915,51.762,-91.881,51.723,-91.885,51.67,-91.917,51.59,-91.923,51.534,-91.893,51.487],[-91.929,51.892,-91.97,51.988]]},{"n":"Albert Nile","z":2,"b":[31.356,2.193,31.475,2.4],"l":[[31.367,2.193,31.356,2.254,31.383,2.306,31.475,2.4]]},{"n":"Amur","z":3,"b":[136.77,49.759,140.114,51.764],"l":[[137.07,50.454,137.101,50.298,137.057,50.216,136.946,50.155,136.872,50.098,136.834,50.045,136.771,49.915,136.77,49.85,136.81,49.759],[140.114,51.764,139.95,51.64,139.902,51.597,139.858,51.579]]},{"n":"Angara","z":2.1,"b":[101.777,51.816,106.497,57.943],"l":[[106.497,52.383,106.438,52.415,106.352,52.404,106.221,52.36,106.069,52.264,105.896,52.117,105.725,51.998,105.556,51.908,105.361,51.847,105.141,51.816,104.99,51.819,104.866,51.872],[103.374,53.107,103.403,53.282,103.404,53.425,103.384,53.594,103.279,53.797,103.087,54.032,103.058,54.218,103.19,54.355,103.251,54.481,103.24,54.598,103.192,54.696,103.107,54.774,103.133,54.892,103.268,55.05,103.329,55.18,103.316,55.282,103.278,55.379,103.215,55.47,103.186,55.575,103.189,55.692,103.151,55.777,103.071,55.828,103.024,55.895,103.009,55.98,102.965,56.057,102.892,56.127,102.751,56.173,102.544,56.195,102.304,56.168,102.031,56.092,101.863,56.072,101.8,56.107,101.777,56.166,101.803,56.291],[102.674,57.943,102.674,57.855,102.651,57.789,102.606,57.717,102.534,57.638,102.434,57.551,102.371,57.446,102.345,57.321,102.338,57.195,102.358,57.004]]},{"n":"Atbara","z":5,"b":[35.895,14.62,35.924,14.935],"l":[[35.924,14.935,35.9,14.863,35.895,14.62]]},{"n":"Athabasca","z":4.7,"b":[-111.28,58.505,-111.016,58.788],"l":[[-111.016,58.691,-111.075,58.505],[-111.28,58.788,-111.208,58.72,-111.142,58.696,-111.016,58.691]]},{"n":"Back","z":5,"b":[-108.533,64.562,-96.995,66.151],"l":[[-108.533,64.562,-108.18,64.686],[-108.089,64.715,-107.979,64.72],[-98.656,65.928,-98.677,66.003,-98.716,66.023,-98.782,66.014,-98.874,66.032,-98.992,66.076,-99.127,66.062,-99.279,65.989,-99.467,65.932,-99.692,65.892,-99.852,65.886,-99.948,65.912,-100.115,65.909,-100.353,65.876,-100.582,65.871,-100.8,65.895,-100.973,65.898,-101.099,65.879,-101.314,65.889,-101.77,65.947],[-97.267,65.974,-96.995,66.151]]},{"n":"Biya","z":5,"b":[87.392,51.364,87.756,51.762],"l":[[87.756,51.364,87.686,51.636,87.595,51.735,87.392,51.762]]},{"n":"Chattahoochee","z":5,"b":[-85.125,30.721,-83.815,34.364],"l":[[-84.966,30.923,-84.887,30.759,-84.884,30.721],[-85.067,31.635,-85.125,31.777,-85.124,31.881,-85.064,32.051],[-83.815,34.364,-84.076,34.167]]},{"n":"Churchill","z":5,"b":[-64.713,53.125,-64.678,53.382],"l":[[-64.682,53.125,-64.713,53.275,-64.712,53.339,-64.678,53.382]]},{"n":"Colorado","z":4.7,"b":[-114.741,34.253,-105.821,40.185],"l":[[-114.377,34.54,-114.371,34.488,-114.308,34.433,-114.159,34.355,-114.125,34.317,-114.125,34.314,-114.126,34.287,-114.164,34.253],[-114.591,35.353,-114.649,35.476,-114.662,35.545,-114.645,35.631],[-114.741,36.014,-114.716,36.085,-114.669,36.122,-114.593,36.148,-114.505,36.156,-114.406,36.148,-114.332,36.116,-114.282,36.06,-114.232,36.032,-114.183,36.03,-114.119,36.077,-114.062,36.175,-114.005,36.195,-113.949,36.16,-113.76,36.007,-113.636,35.831],[-110.368,37.898,-110.399,37.883,-110.421,37.832,-110.43,37.772,-110.47,37.702,-110.54,37.626,-110.654,37.521,-110.692,37.483,-110.698,37.452,-110.722,37.406,-110.781,37.336,-110.916,37.157,-111.019,37.127,-111.301,37.054,-111.431,37.003,-111.473,36.932],[-105.821,40.185,-105.861,40.165]]},{"n":"Columbia","z":3,"b":[-118.527,48.232,-117.517,52.102],"l":[[-118.072,48.664,-118.174,48.512,-118.198,48.404,-118.168,48.232],[-118.092,49.802,-118.13,49.664,-118.128,49.574,-118.097,49.484,-118.014,49.404,-117.907,49.342],[-117.906,50.682,-117.917,50.465,-117.892,50.339,-117.837,50.231,-117.827,50.159,-117.88,50.103],[-117.517,51.659,-117.873,51.87],[-118.527,52.102,-118.366,52.09]]},{"n":"Coppermine","z":5,"b":[-114.366,64.638,-111.956,65.64],"l":[[-112.343,64.899,-112.05,64.776,-111.956,64.711,-111.966,64.638],[-114.173,65.639,-114.305,65.64,-114.355,65.609,-114.366,65.544,-114.29,65.496,-114.127,65.465,-114.034,65.425,-114.011,65.378,-113.787,65.336,-113.36,65.298,-113.025,65.252,-112.781,65.199,-112.6,65.177,-112.483,65.186,-112.389,65.138,-112.317,65.032,-112.29,64.974,-112.318,64.96]]},{"n":"Dalälven","z":5,"b":[13.212,60.122,17.416,61.663],"l":[[16.434,60.156,16.323,60.122],[17.416,60.51,17.389,60.461,17.246,60.402,16.978,60.315,16.832,60.252,16.807,60.211,16.721,60.184,16.499,60.163],[14.551,60.998,14.632,60.922,14.729,60.881,14.868,60.848,14.946,60.806,14.973,60.728],[13.717,61.414,13.672,61.468,13.591,61.502,13.459,61.533,13.348,61.577,13.212,61.663]]},{"n":"Dniester","z":5,"b":[30.259,46.101,30.478,46.32],"l":[[30.478,46.101,30.311,46.233,30.259,46.288,30.269,46.32]]},{"n":"Dnieper","z":4,"b":[30.478,46.831,35.149,51.217],"l":[[33.54,46.831,33.606,46.869,33.662,46.957,33.739,47.05,33.859,47.149,33.976,47.307,33.998,47.411,34.155,47.49,34.448,47.542,34.712,47.553,34.946,47.521,35.088,47.528,35.138,47.573,35.149,47.657,35.106,47.842],[33.194,49.094,32.884,49.225,32.603,49.302,31.886,49.555],[31.44,50,31.48,49.933,31.486,49.871,31.467,49.754],[30.508,51.217,30.478,50.904,30.479,50.749,30.515,50.597]]},{"n":"Don","z":5,"b":[42.11,47.602,43.985,49.507],"l":[[42.11,47.602,42.303,47.661,42.484,47.728,42.663,47.794,42.832,47.838,42.881,47.885,42.927,47.961,43.042,48.186,43.132,48.294,43.237,48.389,43.459,48.539,43.528,48.614,43.522,48.653,43.481,48.699,43.465,48.737,43.474,48.769,43.522,48.821,43.606,48.892,43.69,48.929,43.773,48.931,43.844,48.949,43.896,48.968,43.923,49.01,43.938,49.072,43.985,49.136,43.956,49.186,43.905,49.204,43.785,49.203,43.749,49.207,43.708,49.25,43.7,49.328,43.659,49.402,43.602,49.486,43.549,49.507]]},{"n":"Blue Nile","z":3,"b":[34.38,11.296,34.778,11.783],"l":[[34.38,11.783,34.409,11.639,34.509,11.517,34.778,11.296]]},{"n":"Ertis","z":2.1,"b":[83.364,47.732,84.357,49.644],"l":[[83.364,49.644,83.603,49.52,83.731,49.471,83.826,49.454,83.959,49.379,84.132,49.245,84.204,49.162,84.176,49.129,84.012,49.07,83.714,48.984,83.533,48.889,83.468,48.784,83.45,48.668,83.479,48.543,83.524,48.455,83.587,48.404,83.589,48.359,83.531,48.318,83.507,48.273,83.516,48.222,83.642,48.137,83.887,48.018,84.072,47.902,84.198,47.791,84.285,47.734,84.357,47.732]]},{"n":"Firat","z":3,"b":[38.74,38.813,38.813,39.078],"l":[[38.768,38.813,38.813,38.875,38.74,39.078]]},{"n":"Gan","z":5,"b":[115.982,29.279,116.198,29.751],"l":[[115.982,29.279,116.034,29.319,116.088,29.437,116.198,29.751]]},{"n":"Grande","z":4.7,"b":[-50.994,-21.465,-44.387,-19.82],"l":[[-44.387,-21.465,-44.616,-21.318],[-45.205,-21.093,-45.252,-21.086,-45.276,-21.067,-45.336,-21.035,-45.443,-21.011,-45.577,-20.918,-45.737,-20.754,-45.847,-20.686,-45.906,-20.714,-45.951,-20.714,-45.981,-20.686,-46.078,-20.671,-46.326,-20.668],[-47.073,-20.284,-46.741,-20.48,-46.619,-20.564,-46.587,-20.623],[-50.994,-20.102,-50.582,-19.82],[-49.385,-19.98,-50.365,-19.858]]},{"n":"Han","z":5,"b":[111,32.568,111.478,32.741],"l":[[111.478,32.568,111,32.741]]},{"n":"Huang","z":3,"b":[97.072,34.835,97.763,35.088],"l":[[97.288,34.835,97.248,34.889,97.194,34.913,97.072,34.931],[97.763,35.088,97.714,34.962,97.659,34.909,97.546,34.876]]},{"n":"Indus","z":3,"b":[72.707,34.086,72.848,34.623],"l":[[72.782,34.623,72.835,34.371,72.848,34.258,72.838,34.198,72.802,34.148,72.707,34.086]]},{"n":"Jordan","z":5,"b":[35.423,31.325,35.599,32.9],"l":[[35.423,31.325,35.465,31.562,35.499,31.672,35.559,31.766],[35.599,32.9,35.577,32.72]]},{"n":"Kemijoki","z":5,"b":[27.359,66.467,27.519,66.765],"l":[[27.385,66.765,27.519,66.668,27.513,66.621,27.414,66.592,27.363,66.55,27.359,66.467]]},{"n":"Klamath","z":4.7,"b":[-121.921,42.251,-121.809,42.475],"l":[[-121.921,42.475,-121.916,42.419,-121.809,42.251]]},{"n":"Krishna","z":5,"b":[78.997,16.23,79.313,16.579],"l":[[78.997,16.23,79.157,16.235,79.211,16.301,79.213,16.429,79.238,16.515,79.313,16.579]]},{"n":"La Grande Rivière","z":4,"b":[-77.42,53.427,-70.843,53.798],"l":[[-70.843,53.427,-70.948,53.439,-70.991,53.468,-71.007,53.518,-71.041,53.555,-71.117,53.591],[-72.034,53.598,-71.926,53.572,-71.871,53.584,-71.817,53.646],[-74.433,53.685,-74.697,53.631,-74.852,53.628,-74.965,53.648,-75.239,53.656,-75.617,53.674,-75.802,53.664,-75.895,53.71,-76.136,53.699,-76.664,53.668,-77.04,53.677,-77.196,53.699,-77.311,53.732,-77.42,53.798]]},{"n":"Mackenzie","z":2.1,"b":[-117.668,61.048,-113.704,61.339],"l":[[-117.668,61.308,-117.221,61.15,-116.826,61.082,-116.335,61.051,-115.996,61.048,-115.81,61.073,-115.502,61.157,-115.072,61.299,-114.569,61.339,-113.704,61.246]]},{"n":"Madison","z":2,"b":[-111.309,44.757,-111.1,44.868],"l":[[-111.1,44.757,-111.118,44.762,-111.189,44.771,-111.242,44.8,-111.301,44.863,-111.309,44.868]]},{"n":"Mahäna Nadï","z":5,"b":[83.3,21.52,83.858,21.736],"l":[[83.858,21.52,83.713,21.673,83.636,21.728,83.578,21.736,83.528,21.717,83.488,21.669,83.425,21.658,83.3,21.695]]},{"n":"Mississippi","z":4.7,"b":[-94.637,47.406,-94.094,47.464],"l":[[-94.326,47.406,-94.202,47.457,-94.144,47.464,-94.094,47.433],[-94.507,47.456,-94.637,47.44]]},{"n":"Missouri","z":2,"b":[-111.697,43.06,-98.56,48.151],"l":[[-99.365,43.991,-99.328,43.894,-99.353,43.818,-99.43,43.729,-99.442,43.674,-99.39,43.653,-99.347,43.599,-99.313,43.515,-99.196,43.415,-98.997,43.299,-98.892,43.225,-98.882,43.192,-98.798,43.146,-98.56,43.06],[-111.516,46.362,-111.54,46.469,-111.585,46.544,-111.697,46.662],[-100.777,46.713,-100.625,46.608,-100.569,46.513,-100.556,46.393,-100.571,46.287,-100.614,46.196,-100.557,46.041,-100.4,45.822,-100.369,45.705,-100.369,45.674,-100.402,45.643,-100.433,45.613,-100.438,45.561,-100.433,45.535,-100.406,45.505,-100.342,45.448,-100.297,45.316,-100.302,45.134,-100.321,45.053,-100.348,45.037,-100.375,45.033,-100.402,44.993,-100.41,44.918,-100.444,44.854,-100.505,44.8,-100.583,44.783,-100.68,44.802,-100.691,44.795,-100.697,44.776,-100.659,44.735,-100.635,44.68,-100.633,44.62,-100.609,44.578,-100.561,44.555,-100.556,44.53,-100.562,44.504,-100.536,44.476,-100.418,44.45,-100.394,44.434],[-101.4,47.506,-101.428,47.552,-101.549,47.556,-101.772,47.533,-101.976,47.547,-102.161,47.598,-102.269,47.669,-102.299,47.76,-102.345,47.799,-102.409,47.786,-102.491,47.794,-102.59,47.824,-102.62,47.881,-102.58,47.965,-102.614,48.038,-102.724,48.1,-102.873,48.137,-103.06,48.151,-103.179,48.136,-103.23,48.093,-103.308,48.059,-103.414,48.036,-103.496,48.054,-103.555,48.114,-103.631,48.115,-103.772,48.027],[-106.406,48.02,-106.519,47.886,-106.6,47.822,-106.879,47.707,-107.04,47.694,-107.262,47.706,-107.385,47.694,-107.41,47.659,-107.473,47.645,-107.575,47.653,-107.689,47.608,-107.813,47.512,-107.888,47.502,-107.94,47.581,-108.02,47.598,-108.196,47.602]]},{"n":"Black Volta","z":4.7,"b":[-1.713,8.631,-0.976,8.762],"l":[[-1.713,8.631,-1.57,8.682,-1.502,8.714,-1.437,8.756,-1.376,8.762,-1.3,8.714,-1.177,8.672,-0.976,8.662]]},{"n":"Murray","z":4,"b":[146.028,-36.108,147.272,-36.015],"l":[[147.043,-36.108,147.1,-36.05,147.157,-36.036,147.272,-36.049],[146.028,-36.015,146.216,-36.056]]},{"n":"White Volta","z":5,"b":[-1.155,8.661,-0.974,9.117],"l":[[-0.974,8.661,-1.111,8.856,-1.155,8.97,-1.152,9.117]]},{"n":"Naryn","z":4.7,"b":[72.654,41.667,73.3,41.79],"l":[[73.3,41.776,72.932,41.79,72.771,41.762,72.654,41.667]]},{"n":"Nelson","z":5,"b":[-98.015,50.979,-94.779,56.394],"l":[[-96.598,50.979,-96.595,51.308,-96.631,51.482,-96.703,51.611,-96.952,51.86,-97.377,52.23,-97.643,52.532,-97.75,52.766,-97.831,53.042,-97.914,53.519,-97.869,53.714,-97.906,53.776,-97.981,53.837,-98.015,53.886,-98.009,53.925,-98.005,53.966,-97.987,53.988,-97.914,54.003,-97.798,54.026,-97.731,54.082],[-97.901,54.705,-97.791,54.571],[-97.946,54.948,-97.254,55.236],[-96.553,56.063,-96.504,56.107,-96.421,56.129,-96.285,56.141,-96.196,56.171,-96.152,56.219,-96.058,56.255,-95.841,56.291],[-95.431,56.339,-94.779,56.394]]},{"n":"Neva","z":4.7,"b":[31.043,59.951,31.414,60.235],"l":[[31.043,59.951,31.181,59.983,31.274,60.054,31.414,60.235]]},{"n":"Niger","z":3,"b":[-4.304,9.865,4.657,15.507],"l":[[4.626,9.865,4.529,10.239,4.537,10.425,4.657,10.609],[-3.98,15.281,-4.166,15.365,-4.247,15.421,-4.304,15.507]]},{"n":"Nile","z":2,"b":[31.091,21.609,32.914,23.988],"l":[[32.879,23.988,32.909,23.808,32.903,23.696,32.872,23.591,32.878,23.481,32.903,23.415,32.914,23.332,32.855,23.208,32.68,23.009,32.587,22.876,32.576,22.806,32.514,22.74,32.418,22.672,32.326,22.653,32.232,22.718,32.137,22.733,32.042,22.699,31.961,22.63,31.894,22.526,31.735,22.395,31.544,22.252,31.4,22.093,31.362,21.98,31.28,21.845,31.091,21.609]]},{"n":"Ob","z":4,"b":[81.327,53.886,83.025,54.845],"l":[[83.025,54.845,82.312,54.491,82.004,54.358,81.862,54.33,81.741,54.269,81.639,54.176,81.522,54.104,81.391,54.052,81.327,53.991,81.334,53.886]]},{"n":"Orange","z":5,"b":[24.731,-30.664,25.972,-29.979],"l":[[25.972,-30.545,25.902,-30.548,25.841,-30.581,25.764,-30.648,25.67,-30.664,25.506,-30.612],[24.731,-29.979,24.978,-30.263]]},{"n":"Ord","z":5,"b":[128.708,-16.788,128.756,-16.133],"l":[[128.737,-16.133,128.708,-16.434,128.714,-16.575,128.747,-16.638,128.756,-16.699,128.734,-16.788]]},{"n":"Ottawa","z":5,"b":[-79.559,46.827,-79.166,47.577],"l":[[-79.166,46.827,-79.365,47.022,-79.435,47.134,-79.442,47.228,-79.479,47.319,-79.547,47.407,-79.559,47.483,-79.496,47.577]]},{"n":"Panama Canal","z":5,"b":[-79.923,9.104,-79.691,9.267],"l":[[-79.691,9.104,-79.756,9.112,-79.814,9.153,-79.923,9.267]]},{"n":"Paranaíba","z":4,"b":[-50.499,-19.02,-47.447,-18.171],"l":[[-50.49,-19.02,-50.499,-18.953,-50.458,-18.877,-50.368,-18.769,-50.247,-18.69,-50.094,-18.641,-49.96,-18.616,-49.789,-18.615],[-48.227,-18.34,-48.952,-18.326,-49.108,-18.388],[-47.447,-18.171,-47.786,-18.379,-47.929,-18.445,-48.017,-18.434]]},{"n":"Paraná","z":4,"b":[-54.611,-25.433,-50.994,-19.668],"l":[[-54.611,-25.433,-54.473,-25.22,-54.436,-25.121,-54.454,-25.065,-54.413,-24.867,-54.313,-24.528,-54.281,-24.306,-54.317,-24.201,-54.318,-24.128,-54.267,-24.066],[-51.936,-21.423,-51.955,-21.469,-51.99,-21.494,-52.048,-21.511,-52.073,-21.554,-52.065,-21.623,-52.123,-21.718,-52.246,-21.84,-52.339,-21.957,-52.403,-22.069,-52.566,-22.207,-52.961,-22.455],[-50.997,-19.668,-50.994,-20.102,-51.064,-20.223,-51.165,-20.308,-51.397,-20.442]]},{"n":"Peace","z":2.1,"b":[-124.836,56,-113.714,58.71],"l":[[-122.222,56.021,-122.482,56.112,-122.691,56.124,-122.934,56.086,-123.088,56.046,-123.152,56.005,-123.323,56,-123.601,56.03,-123.797,56.033,-123.912,56.011,-124.083,56.085,-124.309,56.256,-124.454,56.389,-124.517,56.483,-124.621,56.587,-124.836,56.757],[-113.823,58.71,-113.714,58.669]]},{"n":"Pit","z":5,"b":[-122.369,40.763,-122.095,40.784],"l":[[-122.095,40.784,-122.369,40.763]]},{"n":"R. des Outaouais","z":5,"b":[-78.813,47.478,-76.935,47.83],"l":[[-77.457,47.556,-77.471,47.566,-77.484,47.595,-77.53,47.596,-77.592,47.576,-77.613,47.582,-77.615,47.597,-77.544,47.671],[-77.322,47.573,-77.301,47.562,-77.265,47.527,-77.216,47.507,-77.158,47.531,-77.114,47.526,-77.086,47.492,-77.037,47.478,-76.935,47.488],[-78.724,47.684,-78.744,47.672,-78.813,47.656],[-77.527,47.744,-77.528,47.766,-77.544,47.82,-77.567,47.83]]},{"n":"Rainy","z":5,"b":[-93.382,48.616,-91.319,48.912],"l":[[-93.382,48.616,-92.779,48.678],[-91.319,48.828,-91.44,48.903,-91.518,48.912,-91.63,48.865]]},{"n":"Rhein","z":4,"b":[8.881,47.511,9.554,47.671],"l":[[9.128,47.671,8.881,47.656],[9.183,47.671,9.554,47.511]]},{"n":"Rhône","z":5,"b":[6.133,46.205,6.866,46.437],"l":[[6.133,46.205,6.235,46.333,6.322,46.394,6.429,46.431,6.578,46.437,6.866,46.403]]},{"n":"Rio Grande","z":4,"b":[-107.293,26.564,-99.172,33.356],"l":[[-99.457,27.057,-99.444,27.037,-99.302,26.885,-99.23,26.762,-99.172,26.564],[-101.039,29.46,-101.304,29.634],[-107.293,32.91,-107.281,33.114],[-107.181,33.173,-107.17,33.356]]},{"n":"Rupert","z":5,"b":[-76.819,51.338,-74.818,51.446],"l":[[-76.819,51.416,-76.781,51.354,-76.73,51.338,-76.553,51.364],[-75.204,51.446,-75.086,51.442,-75,51.419,-74.948,51.374,-74.895,51.367,-74.818,51.412]]},{"n":"Río Grande de Santiago","z":5,"b":[-105.104,20.198,-102.77,21.822],"l":[[-102.78,20.31,-102.77,20.198],[-103.709,20.938,-103.594,20.971],[-105.104,21.822,-105.06,21.807,-105.034,21.779]]},{"n":"Sacramento","z":4.7,"b":[-122.411,40.721,-122.369,40.763],"l":[[-122.369,40.763,-122.411,40.721]]},{"n":"San Joaquin","z":5,"b":[-119.698,37,-119.601,37.062],"l":[[-119.698,37,-119.601,37.062]]},{"n":"San Juan","z":4.7,"b":[-85.871,11.118,-84.77,12.098],"l":[[-84.77,11.118,-84.978,11.207,-85.253,11.453,-85.871,12.098]]},{"n":"Saskatchewan","z":4,"b":[-103.891,53.159,-97.914,53.982],"l":[[-99.23,53.185,-98.416,53.354,-98.082,53.436,-97.914,53.519],[-100.323,53.593,-100.163,53.503,-100.066,53.405,-99.976,53.268,-99.852,53.212,-99.693,53.237,-99.543,53.227,-99.33,53.159],[-103.421,53.652,-103.58,53.629,-103.697,53.586,-103.891,53.481],[-102.437,53.982,-102.381,53.966]]},{"n":"Sassandra","z":5,"b":[-7.057,6.264,-6.989,7.041],"l":[[-7.043,7.041,-7.057,6.911,-7.039,6.825,-6.993,6.739,-6.989,6.586,-7.026,6.365,-7.008,6.264]]},{"n":"Savannah","z":5,"b":[-82.589,33.664,-82.208,34.018],"l":[[-82.589,34.018,-82.352,33.838,-82.257,33.749,-82.208,33.664]]},{"n":"Semliki","z":5,"b":[29.452,-0.607,31.367,2.193],"l":[[29.452,-0.607,29.597,-0.134],[31.367,2.193,31.159,1.922,30.943,1.683,30.478,1.239]]},{"n":"Shire","z":3,"b":[35.257,-14.758,35.278,-14.504],"l":[[35.257,-14.504,35.278,-14.758]]},{"n":"Shishhid Gol","z":4,"b":[99.31,51.349,99.427,51.41],"l":[[99.427,51.349,99.31,51.41]]},{"n":"Snake","z":4,"b":[-113.472,42.654,-111.048,43.338],"l":[[-113.316,42.654,-113.472,42.675],[-112.864,42.813,-112.64,43.023],[-111.048,43.207,-111.093,43.234,-111.186,43.338]]},{"n":"South Saskatchewan","z":5,"b":[-108.006,50.636,-106.69,51.262],"l":[[-106.853,51.262,-106.69,51.13,-106.691,51.072,-106.803,51.045,-106.901,50.963,-106.986,50.824,-107.159,50.723,-107.42,50.66,-107.665,50.636,-108.006,50.658]]},{"n":"Suez Canal","z":5,"b":[32.36,30.181,32.561,30.424],"l":[[32.561,30.181,32.36,30.424]]},{"n":"Sukhona","z":4,"b":[39.053,59.525,39.799,59.868],"l":[[39.053,59.868,39.5,59.616,39.686,59.53,39.799,59.525]]},{"n":"Sutlej","z":5,"b":[76.435,30.792,81.295,31.422],"l":[[81.295,30.792,81.199,30.852],[76.765,31.409,76.744,31.334,76.715,31.298,76.671,31.278,76.596,31.306,76.435,31.422]]},{"n":"Svir’","z":4.7,"b":[31.414,60.235,32.821,60.655],"l":[[31.414,60.235,31.595,60.408,31.798,60.52,32.086,60.629,32.356,60.655,32.611,60.598,32.711,60.544,32.821,60.483]]},{"n":"Syr Darya","z":4.7,"b":[61.562,40.273,70.355,46.11],"l":[[70.355,40.435,70.319,40.371,70.186,40.331,69.827,40.273],[68.456,41.107,68.173,41.13,68.05,41.165,68.003,41.212,67.991,41.255],[61.76,46.021,61.562,46.11]]},{"n":"São Francisco","z":4,"b":[-45.336,-19.051,-40.818,-9.406],"l":[[-45.089,-19.051,-45.176,-18.801,-45.202,-18.591,-45.249,-18.501,-45.324,-18.459,-45.336,-18.38,-45.262,-18.203],[-43.15,-11.143,-43.057,-10.988,-42.945,-10.888,-42.781,-10.791,-42.633,-10.638,-42.5,-10.427,-42.439,-10.273,-42.45,-10.175,-42.404,-10.09,-42.301,-10.019,-42.209,-9.911,-42.128,-9.768,-41.91,-9.696,-41.554,-9.694,-41.335,-9.676,-41.253,-9.643,-41.175,-9.568,-41.102,-9.453,-41.003,-9.406,-40.818,-9.438]]},{"n":"Tajo","z":4.7,"b":[-5.996,39.797,-2.577,40.724],"l":[[-5.996,39.852,-5.851,39.807,-5.656,39.797,-5.215,39.812],[-2.734,40.528,-2.577,40.724]]},{"n":"Tennessee","z":4.7,"b":[-88.283,34.575,-86.899,37.022],"l":[[-86.899,34.575,-87.242,34.754,-87.4,34.819,-87.486,34.828,-87.572,34.818,-87.7,34.773],[-87.975,35.945,-88.004,35.962,-87.992,36.011,-87.949,36.1,-87.959,36.216,-88.023,36.359,-88.056,36.484,-88.057,36.59,-88.114,36.738,-88.283,37.022]]},{"n":"Teslin","z":3,"b":[-133.245,59.607,-132.095,60.449],"l":[[-132.095,59.607,-132.353,59.921,-132.545,60.069,-132.759,60.156,-132.96,60.262,-133.245,60.449]]},{"n":"Thelon","z":4.7,"b":[-104.834,63.071,-94.225,64.761],"l":[[-104.834,63.071,-104.729,63.128,-104.697,63.178,-104.706,63.27],[-96.273,64.301,-95.448,64.158,-95.049,64.158,-94.811,64.126,-94.602,64.062,-94.429,64.041,-94.225,64.076],[-101.811,64.411,-101.536,64.493],[-97.103,64.761,-97.163,64.715,-97.292,64.716,-97.511,64.75,-97.773,64.741,-98.08,64.687,-98.264,64.666,-98.326,64.679,-98.382,64.652,-98.433,64.585,-98.493,64.555,-98.563,64.562,-98.687,64.539,-98.864,64.487,-99.003,64.491,-99.104,64.552,-99.293,64.583,-99.571,64.584,-99.789,64.603,-99.949,64.639,-100.088,64.633,-100.208,64.586,-100.4,64.573,-100.795,64.604]]},{"n":"Tocantins","z":4,"b":[-49.687,-5.335,-48.937,-3.835],"l":[[-49.687,-3.835,-49.639,-4.211,-49.546,-4.546,-49.392,-4.966,-49.28,-5.218,-49.21,-5.303,-49.115,-5.335,-48.937,-5.303]]},{"n":"Tugaloo","z":5,"b":[-83.166,34.366,-82.819,34.599],"l":[[-83.166,34.599,-83.053,34.511,-82.976,34.476,-82.897,34.466,-82.848,34.437,-82.819,34.366]]},{"n":"Ural","z":4,"b":[58.631,51.695,58.835,52.197],"l":[[58.835,52.197,58.782,52.071,58.767,51.976,58.773,51.87,58.74,51.786,58.631,51.695]]},{"n":"Vaal","z":4,"b":[25.622,-27.699,28.497,-26.867],"l":[[26.091,-27.603,26.06,-27.69,26.037,-27.699,26.012,-27.659,25.955,-27.624,25.865,-27.594,25.771,-27.601,25.622,-27.668],[28.497,-26.991,28.446,-26.973,28.124,-26.867]]},{"n":"Verkhniy Yenisey","z":4,"b":[90.939,53.735,92.408,55.503],"l":[[91.511,53.735,91.454,53.83,91.466,53.904,91.528,53.987,91.556,54.046,91.551,54.079,91.484,54.167,91.358,54.311,91.286,54.415,91.269,54.479,91.179,54.55,91.016,54.628,90.939,54.722,90.947,54.832,90.988,54.926,91.062,55.004,91.351,55.087,91.855,55.176,92.182,55.291,92.408,55.503]]},{"n":"Victoria Nile","z":2,"b":[31.768,-0.936,33.259,1.666],"l":[[33.187,0.428,33.259,0.379,33.232,0.294,33.129,0.159,33.018,-0.052,32.897,-0.338,32.744,-0.545,32.56,-0.675,32.292,-0.789,31.768,-0.936],[32.545,1.522,32.614,1.454,32.685,1.427,32.78,1.422,32.835,1.405,32.893,1.311],[32.241,1.666,32.475,1.552]]},{"n":"Vilyuy","z":5,"b":[109.587,62.857,112.48,63.48],"l":[[109.587,63.455,109.814,63.426,109.939,63.435,110.038,63.473,110.142,63.48,110.251,63.457,110.345,63.423,110.423,63.378,110.449,63.313,110.421,63.227,110.474,63.177,110.61,63.163,110.644,63.128,110.577,63.073,110.665,63.008,110.909,62.935,111.057,62.935,111.107,63.01,111.234,63.057,111.436,63.076,111.576,63.126,111.656,63.208,111.741,63.239,111.831,63.218,111.886,63.176,111.907,63.112,111.869,63.059,111.772,63.019,111.777,62.965,111.885,62.896,111.995,62.86,112.107,62.857,112.208,62.906,112.296,63.007,112.375,63.048,112.48,63.021]]},{"n":"Vistula","z":4.7,"b":[18.81,49.925,18.937,49.932],"l":[[18.81,49.925,18.937,49.932]]},{"n":"Volga","z":3,"b":[36.375,50.222,49.905,58.288],"l":[[45.664,50.222,45.831,50.357,45.836,50.457,45.733,50.567,45.68,50.647,45.675,50.696,45.744,50.742,45.885,50.782,45.981,50.824,46.03,50.866,46.044,50.925,46.023,51,45.968,51.066,45.879,51.124,45.836,51.188,45.837,51.26,45.87,51.322,45.935,51.375,45.971,51.423,45.979,51.465,46.033,51.506,46.131,51.547,46.218,51.614,46.294,51.708,46.365,51.746,46.477,51.773,46.566,51.752,46.68,51.744,46.775,51.748],[49.059,55.483,49.285,55.234,49.248,55.113,49.023,55.036,48.9,54.91,48.878,54.736,48.782,54.617,48.611,54.554,48.507,54.489,48.47,54.422,48.453,54.344,48.458,54.257,48.585,54.135,48.833,53.978,48.964,53.827,48.978,53.681,49.006,53.579,49.048,53.522,49.104,53.483,49.209,53.461,49.567,53.467,49.905,53.457],[43.432,56.675,43.344,56.733,43.286,56.814,43.231,56.937,43.175,57.024,43.119,57.075,43.089,57.119,43.082,57.156,43.104,57.213,43.178,57.329,42.874,57.41,42.724,57.431,42.576,57.415],[37.151,56.753,36.882,56.783,36.736,56.75,36.624,56.664,36.52,56.643,36.375,56.706],[37.761,57.291,37.944,57.312,38.079,57.382,38.301,57.572],[41.343,57.493,41.206,57.517,41.099,57.588,40.976,57.714,40.889,57.784,40.838,57.799,40.706,57.79,40.494,57.755,40.293,57.695,40.007,57.567],[38.437,58.018,38.326,58.083,38.319,58.15,38.408,58.288,38.704,58.113]]},{"n":"Volta","z":4.7,"b":[-0.976,6.338,0.231,8.662],"l":[[-0.976,8.662,-0.726,8.362,-0.526,8.198,-0.295,8.068,-0.153,7.945,-0.101,7.828,-0.054,7.759,-0.011,7.738,0.039,7.648,0.098,7.49,0.155,7.394,0.209,7.36,0.231,7.245,0.218,7.05,0.195,6.922,0.16,6.863,0.161,6.802,0.197,6.74,0.194,6.688,0.134,6.635,0.108,6.551,0.103,6.338]]},{"n":"Vorma","z":5,"b":[11.157,59.389,11.203,59.911],"l":[[11.17,59.389,11.201,59.451,11.203,59.498],[11.2,59.67,11.194,59.72,11.159,59.831,11.157,59.911]]},{"n":"Vuoksi","z":5,"b":[29.596,60.236,31.412,60.717],"l":[[30.478,60.574,30.298,60.654,30.117,60.676,29.874,60.669,29.714,60.678,29.596,60.717],[30.534,60.63,30.658,60.649,30.793,60.635,30.979,60.593,31.157,60.489,31.412,60.236]]},{"n":"Waikato","z":5,"b":[175.802,-38.939,176.059,-38.694],"l":[[175.802,-38.939,175.83,-38.917,175.965,-38.821,176.033,-38.758,176.059,-38.694]]},{"n":"Winnipeg","z":5,"b":[-96.598,48.863,-94.508,50.979],"l":[[-94.615,49.993,-94.508,49.707,-94.549,49.574,-94.703,49.496,-94.802,49.425,-94.847,49.361,-94.86,49.259,-94.843,49.119,-94.803,49.003,-94.713,48.863],[-96.026,50.283,-95.989,50.405],[-96.598,50.979,-96.467,50.774,-96.392,50.685,-96.294,50.624]]},{"n":"Yuan","z":5,"b":[111.993,28.838,113.071,29.389],"l":[[112.905,29.031,112.801,28.927,112.693,28.874,112.545,28.838,112.351,28.861,111.993,28.985],[112.994,29.147,113.071,29.389]]},{"n":"Zambezi","z":3,"b":[27.021,-17.958,32.708,-15.565],"l":[[27.021,-17.958,27.131,-17.849,27.271,-17.643,27.438,-17.512,27.637,-17.262,27.757,-17.06,27.932,-16.896,28.164,-16.77,28.4,-16.663,28.761,-16.532],[32.708,-15.579,32.497,-15.565,32.346,-15.589,32.184,-15.647,31.924,-15.667,31.566,-15.65,31.329,-15.662,31.213,-15.704,31.065,-15.704,30.796,-15.642]]},{"n":"","z":5,"b":[-111.016,58.544,-103.665,59.361],"l":[[-103.785,58.691,-103.665,58.544],[-111.016,58.691,-110.802,58.744,-110.637,58.82,-110.448,58.937,-110.257,59.031,-110.064,59.103,-109.775,59.183,-109.389,59.272,-109.105,59.305,-108.92,59.282,-108.518,59.298,-107.898,59.351,-107.511,59.361,-107.358,59.329,-106.966,59.303,-106.018,59.271],[-105.55,59.15,-105.249,59.194,-105.115,59.2,-105.012,59.173]]},{"n":"","z":4.7,"b":[-103.447,61.366,-95.447,64.158],"l":[[-103.22,61.681,-103.192,61.643,-103.224,61.609,-103.306,61.567,-103.372,61.5,-103.447,61.366],[-102.919,62.319,-102.909,62.281,-102.862,62.26,-102.777,62.241,-102.727,62.215,-102.714,62.182,-102.783,62.13,-102.936,62.061,-103.049,61.979,-103.158,61.836],[-100.552,62.355,-100.074,62.227,-99.841,62.213,-99.617,62.3],[-101.163,62.367,-100.735,62.356],[-101.831,62.52,-101.847,62.451,-101.826,62.39,-101.775,62.314,-101.654,62.295,-101.366,62.35],[-98.35,62.616,-98.24,62.717,-98.082,62.755,-97.841,62.764,-97.697,62.822,-97.63,62.979],[-101.529,62.664,-101.537,62.791,-101.581,62.862,-101.66,62.919,-101.783,62.941,-102.035,62.925],[-102.66,62.76,-102.68,62.694,-102.665,62.642,-102.622,62.583,-102.634,62.517,-102.735,62.411],[-97.459,63.157,-97.287,63.514],[-95.878,63.656,-96.301,63.613,-96.569,63.608,-96.952,63.637],[-95.497,64.051,-95.447,64.158]]},{"n":"Abay","z":3,"b":[36.981,11.036,37.18,11.855],"l":[[37.179,11.036,37.022,11.192,36.981,11.287,37.015,11.416,37.05,11.58,37.097,11.656,37.167,11.7,37.18,11.755,37.113,11.855]]},{"n":"Blue Nile","z":3,"b":[35.353,9.884,38.514,11.634],"l":[[37.387,11.634,37.393,11.571,37.45,11.534,37.56,11.504,37.68,11.421,37.812,11.288,38.019,11.186,38.302,11.116,38.462,11.037,38.5,10.949,38.514,10.772,38.503,10.505,38.478,10.314,38.439,10.199,38.376,10.136,38.289,10.123,38.11,10.048,37.839,9.911,37.66,9.884,37.574,9.966,37.49,10.021,37.409,10.049,37.354,10.052,37.328,10.032,37.308,10.04,37.296,10.076,37.263,10.105,37.212,10.125,37.189,10.164,37.194,10.224,37.159,10.266,37.046,10.303,36.677,10.36,36.541,10.404,36.514,10.453,36.483,10.469,36.45,10.454,36.395,10.375,36.32,10.232,36.207,10.155,36.055,10.143,35.957,10.111,35.912,10.058,35.849,10.036,35.768,10.044,35.726,10.03,35.724,10.009,35.72,9.998,35.706,9.996,35.628,10.017,35.369,10.14,35.353,10.155]]},{"n":"Al Furat","z":3,"b":[38.026,34.428,40.993,36.836],"l":[[38.56,35.863,38.916,35.913,39.227,35.881,39.613,35.784,39.833,35.683,39.889,35.579,40.004,35.473,40.179,35.363,40.263,35.298,40.257,35.277,40.294,35.253,40.377,35.225,40.424,35.167,40.438,35.079,40.475,35.016,40.535,34.978,40.584,34.918,40.62,34.837,40.711,34.752,40.855,34.663,40.928,34.581,40.93,34.506,40.946,34.458,40.993,34.428],[38.026,36.836,38.056,36.741,38.103,36.694,38.174,36.663,38.231,36.618,38.272,36.558,38.266,36.482,38.184,36.342]]},{"n":"Alabama","z":5,"b":[-88.016,30.694,-85.183,34.259],"l":[[-86.446,32.812,-86.268,32.622,-86.243,32.527,-86.31,32.462,-86.328,32.42,-86.339,32.409,-86.374,32.418,-86.423,32.408,-86.482,32.368,-86.543,32.363,-86.605,32.394,-86.652,32.398,-86.684,32.373,-86.72,32.369,-86.759,32.384,-86.786,32.374,-86.795,32.354,-86.81,32.353,-86.824,32.362,-86.84,32.41,-86.855,32.42,-86.871,32.42,-86.911,32.381,-86.95,32.373,-86.989,32.398,-87.025,32.404,-87.057,32.391,-87.058,32.371,-87.03,32.342,-87.05,32.317,-87.119,32.297,-87.139,32.26,-87.11,32.207,-87.133,32.176,-87.208,32.167,-87.246,32.138,-87.25,32.089,-87.285,32.086,-87.354,32.127,-87.409,32.11,-87.453,32.036,-87.452,31.98,-87.407,31.942,-87.418,31.916,-87.484,31.901,-87.494,31.868,-87.447,31.816,-87.456,31.765,-87.521,31.715,-87.543,31.66,-87.522,31.601,-87.535,31.559,-87.583,31.535,-87.602,31.499,-87.591,31.451,-87.63,31.413,-87.718,31.383,-87.791,31.316,-87.848,31.214,-87.893,31.159,-87.921,31.152,-87.941,31.14,-87.945,31.118,-87.951,30.919,-87.971,30.829,-88.005,30.8,-88.016,30.76,-88.011,30.694],[-86.33,33.444,-86.366,33.418,-86.376,33.382,-86.373,33.327,-86.401,33.276,-86.462,33.228,-86.484,33.189,-86.468,33.158,-86.476,33.115,-86.517,33.061,-86.522,33.032],[-85.183,34.259,-85.501,34.209,-85.657,34.205,-85.703,34.203,-85.727,34.192,-85.733,34.174,-85.707,34.154,-85.705,34.134,-85.734,34.112,-85.771,34.107,-85.813,34.113,-85.838,34.1,-85.845,34.076,-85.863,34.058,-85.881,34.058,-85.898,34.05,-85.901,34.032,-85.922,34.01,-85.95,34.01,-85.965,33.992,-85.967,33.967,-85.984,33.944,-86.033,33.928,-86.055,33.885,-86.046,33.811,-86.082,33.753,-86.161,33.712,-86.191,33.667,-86.194,33.615,-86.2,33.588]]},{"n":"Albany","z":5,"b":[-92.299,51.063,-81.828,52.224],"l":[[-91.418,51.069,-91.312,51.063],[-90.416,51.092,-89.862,51.198,-89.593,51.278,-89.423,51.368,-89.293,51.419,-89.205,51.43,-89.141,51.461,-89.104,51.51,-89.056,51.515,-88.998,51.474,-88.912,51.483,-88.799,51.541,-88.689,51.57,-88.583,51.57,-88.51,51.55,-88.469,51.511,-88.329,51.493,-88.088,51.494,-87.853,51.434,-87.624,51.314,-87.448,51.26,-87.324,51.27,-87.198,51.309,-87.067,51.378,-86.941,51.412,-86.82,51.413,-86.702,51.442,-86.529,51.528,-86.439,51.582,-86.4,51.635,-86.384,51.705,-86.352,51.75,-86.306,51.77,-86.247,51.775,-86.177,51.766,-86.095,51.731,-86.001,51.669,-85.841,51.646,-85.616,51.663,-85.4,51.605,-85.194,51.471,-85.078,51.378,-85.053,51.325,-84.906,51.251,-84.638,51.158,-84.455,51.14,-84.358,51.198,-84.078,51.296,-83.615,51.433,-83.314,51.572,-83.176,51.713,-83.035,51.821,-82.893,51.896,-82.758,51.936,-82.631,51.943,-82.505,51.985,-82.381,52.063,-82.253,52.111,-82.055,52.138,-81.883,52.194,-81.828,52.224],[-91.782,51.336,-91.724,51.277,-91.684,51.258,-91.659,51.244,-91.646,51.215,-91.641,51.19,-91.598,51.161],[-91.893,51.487,-91.886,51.458],[-91.929,51.892,-91.915,51.762],[-92.299,51.974,-92.237,51.948,-92.155,51.951,-91.97,51.988]]},{"n":"Albert Nile","z":2,"b":[31.382,2.401,32.016,3.614],"l":[[32.016,3.614,31.935,3.528,31.885,3.508,31.839,3.526,31.785,3.523,31.724,3.499,31.624,3.363,31.485,3.117,31.404,2.951,31.382,2.864,31.388,2.804,31.421,2.77,31.434,2.719,31.427,2.651,31.45,2.588,31.503,2.53,31.515,2.476,31.475,2.401]]},{"n":"Aldan","z":4,"b":[123.063,56.528,135.834,63.49],"l":[[123.566,56.528,123.205,56.561,123.101,56.558,123.066,56.58,123.063,56.625,123.086,56.68,123.134,56.746,123.17,56.837,123.193,56.953,123.294,57.091,123.474,57.248,123.555,57.341,123.537,57.369,123.552,57.407,123.598,57.454,123.59,57.485,123.528,57.5,123.505,57.539,123.521,57.603,123.546,57.651,123.58,57.682,123.63,57.685,123.697,57.658,123.757,57.669,123.81,57.718,123.804,57.789,123.739,57.882,123.719,57.977,123.746,58.074,123.824,58.154,123.955,58.214,123.985,58.271,123.989,58.291,124.021,58.315,124.101,58.328,124.229,58.332,124.298,58.366,124.31,58.43,124.348,58.503,124.445,58.628,124.578,58.72,124.654,58.749,124.716,58.744,124.759,58.761,124.782,58.8,124.845,58.824,124.945,58.832,125.032,58.86,125.104,58.908,125.144,58.95,125.152,58.988,125.205,59.016,125.304,59.035,125.339,59.065,125.31,59.107,125.309,59.142,125.336,59.17,125.338,59.206,125.315,59.249,125.314,59.286,125.335,59.316,125.406,59.314,125.527,59.281,125.636,59.226,125.735,59.15,125.877,59.092,126.064,59.053,126.193,59.007,126.265,58.955,126.471,58.896,126.809,58.832,127.008,58.78,127.068,58.742,127.14,58.747,127.221,58.795,127.32,58.811,127.436,58.795,127.521,58.805,127.577,58.843,127.657,58.85,127.761,58.825,127.815,58.792,127.818,58.75,127.855,58.716,127.928,58.689,128.004,58.712,128.082,58.784,128.157,58.818,128.229,58.813,128.287,58.792,128.329,58.754,128.377,58.766,128.432,58.826,128.478,58.817,128.515,58.739,128.569,58.694,128.64,58.684,128.688,58.648,128.712,58.588,128.816,58.545,128.999,58.518,129.17,58.518,129.326,58.545,129.489,58.538,129.658,58.5,129.837,58.524,130.026,58.612,130.234,58.645,130.461,58.622,130.586,58.628,130.608,58.662,130.609,58.714,130.588,58.785,130.602,58.835,130.65,58.865,130.782,58.9,130.998,58.938,131.141,58.937,131.21,58.899,131.266,58.904,131.307,58.952,131.362,58.965,131.431,58.945,131.526,58.947,131.648,58.97,131.768,59.01,131.887,59.067,131.948,59.116,131.95,59.157,132.003,59.172,132.106,59.161,132.209,59.177,132.366,59.242,132.638,59.372,132.806,59.412,132.96,59.406,133.058,59.459,133.101,59.573,133.179,59.635,133.291,59.646,133.53,59.79,133.896,60.069,134.198,60.267,134.554,60.444,134.705,60.46,134.839,60.44,135.007,60.389,135.104,60.392,135.13,60.446,135.089,60.506,134.982,60.57,134.927,60.629,134.924,60.685,135.019,60.754,135.211,60.836,135.362,60.946,135.472,61.084,135.506,61.187,135.462,61.256,135.462,61.313,135.504,61.357,135.478,61.394,135.383,61.424,135.383,61.47,135.476,61.532,135.526,61.58,135.533,61.614,135.485,61.632,135.383,61.633,135.293,61.662,135.215,61.718,135.264,61.761,135.438,61.791,135.53,61.843,135.539,61.917,135.599,61.982,135.709,62.036,135.738,62.094,135.685,62.156,135.715,62.212,135.826,62.261,135.834,62.315,135.739,62.376,135.651,62.473,135.572,62.608,135.489,62.677,135.404,62.679,135.325,62.661,135.252,62.622,135.11,62.617,134.898,62.647,134.638,62.718,134.332,62.83,134.184,62.911,134.194,62.962,134.146,62.989,134.039,62.992,133.972,63.007,133.946,63.035,133.946,63.074,133.803,63.132,133.441,63.091,133.224,63.092,133.151,63.135,133.146,63.177,133.209,63.219,133.236,63.274,133.23,63.345,132.974,63.356,132.469,63.31,132.018,63.318,131.619,63.382,131.343,63.386,131.19,63.331,131.007,63.313,130.689,63.341,130.431,63.339,130.105,63.377,129.384,63.49]]},{"n":"Allegheny","z":3,"b":[-79.991,40.448,-77.86,42.144],"l":[[-77.86,41.832,-77.999,41.781,-78.083,41.771,-78.197,41.79,-78.33,41.878,-78.367,41.942,-78.351,42.013,-78.42,42.059,-78.573,42.081,-78.674,42.11,-78.723,42.144,-78.797,42.128,-78.895,42.061,-78.955,41.985,-78.977,41.899,-79.059,41.849,-79.201,41.834,-79.308,41.789,-79.381,41.714,-79.417,41.65,-79.416,41.571,-79.452,41.506,-79.513,41.48,-79.61,41.472,-79.699,41.447,-79.78,41.405,-79.81,41.374,-79.79,41.352,-79.798,41.328,-79.834,41.3,-79.816,41.264,-79.746,41.221,-79.682,41.155,-79.624,41.066,-79.604,41.012,-79.62,40.992,-79.587,40.966,-79.523,40.939,-79.515,40.906,-79.543,40.824,-79.698,40.637,-79.83,40.519,-79.946,40.456,-79.991,40.448]]},{"n":"Amazonas","z":2,"b":[-73.489,-4.445,-52.664,-1.552],"l":[[-52.664,-1.552,-52.874,-1.611,-53,-1.668,-53.145,-1.764,-53.358,-1.838,-53.639,-1.892,-53.843,-1.958,-53.971,-2.038,-54.045,-2.141,-54.064,-2.267,-54.187,-2.358,-54.468,-2.44,-54.525,-2.44,-54.564,-2.441,-54.639,-2.419,-54.676,-2.359,-54.675,-2.253,-54.694,-2.184,-54.734,-2.152,-54.794,-2.12,-54.873,-2.089,-54.957,-2.104,-55.046,-2.167,-55.15,-2.18,-55.269,-2.145,-55.375,-2.083,-55.458,-1.987,-55.515,-1.949,-55.555,-1.924,-55.606,-1.918,-55.669,-1.927,-55.736,-1.964,-55.84,-1.99,-55.981,-2.006,-56.129,-2.08,-56.284,-2.212,-56.401,-2.336,-56.48,-2.454,-56.549,-2.527,-56.607,-2.556,-56.676,-2.575,-56.756,-2.582,-56.847,-2.567,-56.951,-2.528,-57.05,-2.523,-57.143,-2.551,-57.244,-2.526,-57.353,-2.448,-57.44,-2.405,-57.506,-2.396,-57.605,-2.481,-57.74,-2.662,-57.883,-2.784,-58.035,-2.848,-58.135,-2.959,-58.184,-3.117,-58.235,-3.2,-58.268,-3.214,-58.316,-3.212,-58.38,-3.202,-58.453,-3.182,-58.549,-3.205,-58.642,-3.304,-58.689,-3.342,-58.805,-3.347,-58.89,-3.323,-58.982,-3.271,-59.091,-3.239,-59.217,-3.228,-59.381,-3.184,-59.584,-3.109,-59.735,-3.078,-59.837,-3.092,-59.897,-3.128,-59.906,-3.171,-59.925,-3.206,-59.957,-3.237,-60.105,-3.306,-60.203,-3.325,-60.279,-3.302,-60.411,-3.3,-60.598,-3.319,-60.747,-3.398,-60.858,-3.538,-61.036,-3.61,-61.28,-3.614,-61.468,-3.676,-61.598,-3.796,-61.717,-3.883,-61.824,-3.936,-61.893,-3.947,-62.011,-3.896,-62.277,-3.762,-62.367,-3.718,-62.393,-3.716,-62.423,-3.731,-62.449,-3.802,-62.543,-3.851,-62.705,-3.878,-62.863,-3.938,-63.018,-4.032,-63.097,-4.078,-63.152,-4.072,-63.177,-4.035,-63.216,-3.974,-63.283,-3.923,-63.45,-3.871,-63.573,-3.854,-63.654,-3.872,-63.747,-3.868,-63.854,-3.843,-64.035,-3.765,-64.291,-3.632,-64.458,-3.524,-64.576,-3.398,-64.79,-3.222,-64.905,-3.128,-65.07,-2.971,-65.18,-2.835,-65.288,-2.747,-65.395,-2.707,-65.459,-2.663,-65.48,-2.615,-65.558,-2.594,-65.761,-2.603,-65.901,-2.493,-65.977,-2.466,-66.038,-2.485,-66.096,-2.477,-66.153,-2.442,-66.21,-2.46,-66.269,-2.532,-66.334,-2.537,-66.405,-2.475,-66.471,-2.439,-66.531,-2.429,-66.573,-2.478,-66.596,-2.588,-66.641,-2.659,-66.706,-2.692,-66.801,-2.715,-66.925,-2.727,-67.077,-2.695,-67.258,-2.619,-67.384,-2.623,-67.455,-2.707,-67.572,-2.779,-67.736,-2.838,-67.846,-2.928,-67.932,-3.108,-67.927,-3.223,-67.946,-3.283,-67.988,-3.326,-68.095,-3.334,-68.266,-3.307,-68.596,-3.345,-69.084,-3.449,-69.352,-3.553,-69.399,-3.655,-69.416,-3.764,-69.404,-3.879,-69.425,-3.974,-69.478,-4.049,-69.481,-4.111,-69.434,-4.161,-69.459,-4.223,-69.556,-4.299,-69.685,-4.335,-69.847,-4.331,-69.948,-4.285,-69.966,-4.236,-70.017,-4.162,-70.095,-4.092,-70.168,-4.05,-70.198,-3.995,-70.24,-3.883,-70.298,-3.844,-70.339,-3.814,-70.379,-3.819,-70.421,-3.85,-70.486,-3.869,-70.53,-3.866,-70.557,-3.87,-70.9,-3.96,-71.058,-3.985,-71.138,-3.963,-71.193,-3.903,-71.283,-3.869,-71.408,-3.862,-71.521,-3.826,-71.623,-3.761,-71.689,-3.65,-71.718,-3.492,-71.752,-3.395,-71.789,-3.359,-71.914,-3.384,-72.126,-3.468,-72.293,-3.503,-72.476,-3.481,-72.747,-3.457,-72.891,-3.467,-72.997,-3.504,-73.094,-3.562,-73.18,-3.643,-73.211,-3.783,-73.189,-3.984,-73.192,-4.099,-73.221,-4.13,-73.268,-4.136,-73.334,-4.117,-73.366,-4.133,-73.364,-4.183,-73.385,-4.231,-73.431,-4.277,-73.463,-4.336,-73.489,-4.445]]},{"n":"Amu Darya","z":4.7,"b":[58.316,36.95,68.212,44.426],"l":[[68.212,37.022,68.068,36.95,67.958,36.972,67.834,37.064,67.766,37.14,67.753,37.2,67.7,37.227,67.607,37.223,67.547,37.236,67.517,37.267,67.442,37.258,67.32,37.21,67.195,37.235,67.069,37.335,66.828,37.371,66.472,37.345,66.108,37.415,65.737,37.582,65.48,37.68,65.337,37.712,65.22,37.797,65.131,37.938,64.907,38.088,64.549,38.247,64.272,38.432,64.076,38.642,63.953,38.8,63.904,38.904,63.724,39.049,63.413,39.232,63.198,39.33,63.081,39.344,63.014,39.4,62.998,39.5,62.897,39.608,62.71,39.723,62.573,39.845,62.442,40.036,62.375,40.332,62.298,40.468,62.188,40.541,62.095,40.683,62.018,40.894,61.954,41.031,61.903,41.094,61.8,41.163,61.645,41.24,61.497,41.276,61.357,41.272,61.263,41.3,61.215,41.36,61.031,41.487,60.71,41.68,60.527,41.825,60.482,41.919,60.42,41.978,60.343,42,60.291,42.04,60.266,42.1,60.203,42.164,60.102,42.232,59.95,42.291,59.745,42.341,59.587,42.416,59.475,42.516,59.387,42.574,59.322,42.591,59.3,42.632,59.319,42.697,59.298,42.744,59.236,42.772,59.21,42.808,59.222,42.852,59.204,42.904,59.156,42.966,59.139,43.026,59.153,43.084,59.141,43.152,59.103,43.23,59.093,43.288,59.097,43.294],[59.099,43.292,59.097,43.294],[59.097,43.294,59.112,43.326,59.098,43.379,59.052,43.446,59.036,43.498,59.05,43.533,59.106,43.561,59.204,43.581,59.255,43.633,59.26,43.716,59.211,43.805,59.107,43.901,59.082,43.971,59.134,44.016,59.255,44.066,59.541,44.148],[59.097,43.294,59.005,43.423,58.993,43.526,59.032,43.643,59.019,43.732,58.952,43.792,58.831,43.839,58.655,43.872,58.57,43.948,58.575,44.067,58.512,44.202,58.316,44.426]]},{"n":"Amur","z":3,"b":[127.492,47.681,140.84,53.22],"l":[[136.81,49.759,136.843,49.729,136.844,49.678,136.825,49.595,136.721,49.52,136.53,49.452,136.394,49.367,136.311,49.265,136.271,49.171,136.275,49.086,136.162,48.987,135.818,48.817,135.745,48.764,135.669,48.755,135.565,48.771,135.445,48.768,135.307,48.746,135.185,48.705,135.077,48.645,135.026,48.571,135.032,48.482,134.965,48.421,134.823,48.388,134.709,48.383,134.623,48.406,134.487,48.403,134.206,48.36,133.842,48.274,133.672,48.208,133.573,48.133,133.468,48.097,133.301,48.102,133.144,48.106,133.02,48.064,132.877,47.979,132.773,47.94,132.707,47.947,132.637,47.89,132.562,47.769,132.476,47.715,132.38,47.729,132.15,47.718,131.785,47.681,131.557,47.682,131.464,47.723,131.319,47.728,131.122,47.698,131.003,47.691,130.962,47.709,130.933,47.76,130.915,47.843,130.849,47.929,130.733,48.019,130.712,48.128,130.787,48.255,130.804,48.341,130.763,48.388,130.747,48.43,130.659,48.483,130.597,48.575,130.552,48.603,130.566,48.68,130.617,48.773,130.553,48.861,130.355,48.866,130.196,48.892,130.037,48.972,129.793,49.199,129.671,49.278,129.591,49.287,129.534,49.323,129.498,49.389,129.441,49.389,129.385,49.389,129.35,49.362,129.31,49.354,129.248,49.379,129.185,49.381,129.12,49.362,129.065,49.375,129.02,49.419,128.938,49.449,128.819,49.464,128.77,49.495,128.791,49.542,128.769,49.577,128.704,49.6,128.527,49.594,128.237,49.559,128,49.569,127.814,49.622,127.711,49.672,127.69,49.717,127.637,49.76,127.551,49.802,127.502,49.873,127.492,49.975,127.512,50.072,127.59,50.209],[139.858,51.579,139.806,51.563,139.705,51.558,139.602,51.544,139.495,51.505,139.449,51.449,139.378,51.405,139.283,51.374,139.159,51.363,139.008,51.375,138.918,51.35,138.89,51.291,138.825,51.248,138.675,51.207,138.068,50.856,137.708,50.714,137.391,50.665,137.187,50.607,137.095,50.54,137.054,50.493,137.07,50.454],[140.114,51.764,140.147,51.786,140.206,51.814,140.218,51.875,140.182,51.964,140.215,52.046,140.317,52.121,140.385,52.226,140.42,52.362,140.405,52.468,140.338,52.545,140.262,52.592,140.178,52.61,140.125,52.657,140.104,52.734,139.996,52.822,139.801,52.922,139.757,53.032,139.863,53.153,140.003,53.215,140.176,53.22,140.343,53.188,140.504,53.119,140.614,53.091,140.707,53.111,140.783,53.105,140.84,53.088]]},{"n":"Anadyr’","z":5,"b":[168.574,64.684,174.549,67.099],"l":[[170.966,67.099,171.345,66.974,171.448,66.826,171.508,66.766,171.559,66.745,171.377,66.686,170.962,66.587,170.659,66.486,170.468,66.383,170.248,66.304,169.998,66.249,169.857,66.206,169.824,66.175,169.81,66.071,169.815,65.893,169.766,65.791,169.612,65.755,169.123,65.647,168.935,65.582,168.883,65.526,168.786,65.475,168.643,65.428,168.574,65.37,168.581,65.302,168.654,65.245,168.795,65.201,168.814,65.14,168.711,65.065,168.646,64.995,168.618,64.931,168.704,64.866,168.902,64.8,169.146,64.781,169.434,64.808,169.755,64.795,170.107,64.742,170.4,64.72,170.632,64.729,170.733,64.777,170.76,64.823,170.776,64.859,170.788,64.912,170.832,64.928,170.891,64.926,170.977,64.959,171.09,65.026,171.125,65.076,171.079,65.107,171.067,65.162,171.089,65.24,171.261,65.281,171.583,65.284,171.793,65.252,171.892,65.186,172.022,65.152,172.185,65.148,172.294,65.169,172.352,65.214,172.498,65.266,172.732,65.325,172.889,65.392,172.968,65.467,173.053,65.516,173.145,65.54,173.234,65.544,173.322,65.529,173.37,65.502,173.38,65.464,173.456,65.415,173.6,65.354,173.676,65.31,173.685,65.282,173.668,65.242,173.606,65.164,173.55,65.124,173.543,65.09,173.567,65.048,173.648,65.021,173.787,65.007,173.91,64.951,174.017,64.855,174.164,64.778,174.442,64.692,174.549,64.684]]},{"n":"Angara","z":2.1,"b":[92.998,51.87,104.867,58.973],"l":[[104.867,51.87,104.437,52.165,104.29,52.289,104.281,52.339,104.205,52.399,104.059,52.47,103.973,52.54,103.945,52.61,103.877,52.674,103.77,52.733,103.71,52.799,103.698,52.873,103.617,52.934,103.469,52.982,103.39,53.032,103.374,53.107],[101.803,56.291,101.817,56.561,101.77,56.687,101.668,56.76,101.561,56.801,101.449,56.808,101.391,56.848,101.387,56.919,101.461,56.97,101.612,57.002,101.778,56.988,101.959,56.929,102.113,56.898,102.239,56.894,102.316,56.92,102.358,57.004],[102.674,57.943,102.795,58.122,102.837,58.245,102.84,58.373,102.78,58.524,102.659,58.698,102.48,58.83,102.462,58.836],[102.489,58.829,102.462,58.836],[102.462,58.836,102.101,58.933],[102.462,58.836,102.243,58.919,102.101,58.933],[102.101,58.933,101.842,58.956,101.276,58.941,100.98,58.899,100.954,58.83,100.817,58.758,100.568,58.681,100.437,58.627,100.424,58.598,100.434,58.555,100.465,58.5,100.441,58.439,100.361,58.371,100.251,58.346,100.11,58.364,99.986,58.412,99.881,58.489,99.754,58.536,99.603,58.553,99.472,58.594,99.361,58.66,99.241,58.701,99.113,58.717,98.962,58.701,98.788,58.652,98.691,58.6,98.67,58.547,98.628,58.506,98.565,58.476,98.322,58.446,97.899,58.413,97.572,58.408,97.339,58.432,97.171,58.43,97.066,58.404,96.984,58.393,96.923,58.399,96.85,58.432,96.765,58.491,96.514,58.527,96.098,58.541,95.777,58.522,95.55,58.472,95.418,58.425,95.383,58.381,95.235,58.307,94.975,58.203,94.599,58.14,94.108,58.12,93.73,58.129,93.467,58.168,93.279,58.161,93.166,58.11,93.08,58.086,92.998,58.1],[102.101,58.933,101.948,58.973]]},{"n":"Araguaia","z":4,"b":[-53.211,-17.855,-48.159,-5.303],"l":[[-53.115,-17.855,-53.211,-17.409,-53.202,-17.302,-53.158,-17.209,-53.078,-17.095,-53.033,-16.995,-53.024,-16.91,-52.943,-16.814,-52.789,-16.705,-52.68,-16.576,-52.615,-16.427,-52.545,-16.329,-52.469,-16.282,-52.434,-16.218,-52.441,-16.136,-52.417,-16.082,-52.362,-16.058,-52.308,-16.009,-52.257,-15.936,-52.141,-15.872,-51.962,-15.819,-51.847,-15.747,-51.799,-15.656,-51.779,-15.593,-51.788,-15.557,-51.773,-15.523,-51.732,-15.49,-51.696,-15.403,-51.665,-15.261,-51.59,-15.142,-51.471,-15.044,-51.372,-15.024,-51.286,-15.003,-51.246,-15.004,-51.189,-14.977,-51.114,-14.838,-50.978,-14.463,-50.941,-14.201,-50.909,-14.107,-50.868,-14.095,-50.852,-13.995,-50.861,-13.807,-50.8,-13.612,-50.667,-13.41,-50.598,-13.249,-50.595,-13.131,-50.566,-13.015,-50.484,-12.844,-50.566,-12.82,-50.614,-12.764,-50.655,-12.667,-50.66,-12.558,-50.629,-12.437,-50.632,-12.289,-50.668,-12.112,-50.675,-11.99,-50.653,-11.922,-50.661,-11.854,-50.717,-11.749,-50.666,-11.652,-50.671,-11.574,-50.717,-11.483,-50.708,-11.346,-50.645,-11.163,-50.61,-11.001,-50.6,-10.79,-50.599,-10.69,-50.551,-10.567,-50.455,-10.388,-50.403,-10.258,-50.395,-10.177,-50.337,-10.034,-50.175,-9.73,-50.084,-9.422,-49.979,-9.22,-49.83,-9.022,-49.708,-8.895,-49.613,-8.839,-49.502,-8.704,-49.374,-8.489,-49.283,-8.301,-49.227,-8.14,-49.19,-7.995,-49.172,-7.867,-49.22,-7.752,-49.334,-7.649,-49.34,-7.51,-49.24,-7.335,-49.196,-7.167,-49.21,-7.006,-49.08,-6.856,-48.805,-6.718,-48.653,-6.596,-48.622,-6.489,-48.58,-6.414,-48.527,-6.372,-48.471,-6.353,-48.41,-6.358,-48.391,-6.309,-48.414,-6.207,-48.39,-6.142,-48.319,-6.112,-48.297,-6.067,-48.321,-6.007,-48.307,-5.966,-48.254,-5.945,-48.245,-5.889,-48.28,-5.796,-48.259,-5.724,-48.181,-5.672,-48.159,-5.619,-48.193,-5.565,-48.231,-5.533,-48.275,-5.523,-48.317,-5.486,-48.357,-5.424,-48.422,-5.398,-48.511,-5.408,-48.598,-5.399,-48.723,-5.355,-48.937,-5.303]]},{"n":"Ergun","z":3,"b":[117.873,49.513,121.405,53.317],"l":[[121.405,53.317,120.985,53.285,120.704,53.172,120.421,52.968,120.218,52.84,120.095,52.787,120.044,52.718,120.068,52.633,120.173,52.602,120.36,52.627,120.521,52.615,120.656,52.567,120.699,52.494,120.65,52.396,120.665,52.3,120.744,52.205,120.75,52.097,120.682,51.973,120.511,51.849,120.237,51.723,120.067,51.601,119.967,51.422,119.813,51.267,119.757,51.179,119.746,51.108,119.685,51.03,119.573,50.947,119.512,50.863,119.502,50.779,119.446,50.703,119.344,50.634,119.281,50.561,119.256,50.484,119.217,50.433,119.164,50.406,119.192,50.38,119.302,50.354,119.346,50.279,119.326,50.155,119.26,50.066,119.147,50.013,118.98,49.979,118.756,49.963,118.452,49.845,117.873,49.513]]},{"n":"Arkansas","z":4,"b":[-106.381,33.797,-91.077,39.222],"l":[[-91.077,33.797,-91.122,33.824,-91.15,33.864,-91.167,33.922,-91.217,33.955,-91.299,33.962,-91.323,33.974,-91.33,33.991,-91.343,34.004,-91.387,34.005,-91.419,34.026,-91.439,34.066,-91.488,34.091,-91.564,34.1,-91.682,34.153,-91.844,34.249,-91.921,34.271,-91.939,34.267,-91.973,34.275,-92.017,34.316,-92.109,34.448,-92.155,34.566,-92.155,34.673,-92.225,34.756,-92.365,34.818,-92.447,34.877,-92.469,34.934,-92.506,34.972,-92.558,34.993,-92.577,35.035,-92.562,35.096,-92.595,35.119,-92.713,35.095,-92.831,35.161,-92.93,35.184,-93.05,35.185,-93.193,35.244,-93.358,35.363,-93.521,35.415,-93.682,35.399,-93.797,35.421,-93.864,35.479,-93.958,35.494,-94.078,35.467,-94.139,35.434,-94.141,35.396,-94.162,35.373,-94.202,35.367,-94.268,35.385,-94.358,35.43,-94.43,35.415,-94.484,35.342,-94.529,35.314,-94.567,35.331,-94.602,35.325,-94.635,35.297,-94.666,35.3,-94.694,35.333,-94.792,35.375,-94.959,35.425,-95.074,35.488,-95.138,35.565,-95.19,35.603,-95.229,35.605,-95.245,35.637,-95.236,35.699,-95.251,35.747,-95.29,35.78,-95.388,35.795,-95.545,35.79,-95.63,35.816,-95.643,35.875,-95.721,35.925,-95.864,35.967,-95.958,36.029,-96.004,36.11,-96.084,36.152,-96.199,36.153,-96.26,36.174,-96.266,36.213,-96.316,36.256,-96.409,36.303,-96.477,36.322,-96.521,36.312,-96.542,36.332,-96.54,36.382,-96.578,36.415,-96.656,36.431,-96.711,36.473,-96.742,36.539,-96.767,36.542,-96.806,36.532,-96.865,36.494,-96.921,36.469,-96.96,36.472,-96.986,36.486,-96.999,36.513,-96.979,36.542,-96.944,36.567,-96.94,36.579,-96.957,36.591,-97,36.595,-97.041,36.619,-97.053,36.664,-96.993,36.706,-96.862,36.745,-96.836,36.766,-96.838,36.782,-96.877,36.797,-96.913,36.866,-96.932,36.981,-96.986,37.058,-97.074,37.098,-97.135,37.189,-97.167,37.332,-97.237,37.499,-97.344,37.69,-97.695,37.925,-98.289,38.205,-98.633,38.346,-98.728,38.349,-98.854,38.308,-99.011,38.223,-99.218,38.055,-99.474,37.805,-99.698,37.697,-99.987,37.748,-100.776,37.935,-101.127,37.971,-101.304,37.918,-101.627,37.95,-102.095,38.066,-102.559,38.118,-103.017,38.106,-103.32,38.081,-103.467,38.042,-103.594,38.048,-103.699,38.099,-103.833,38.136,-104.075,38.174,-104.237,38.238,-104.411,38.26,-104.649,38.261,-104.826,38.298,-104.94,38.371,-105.093,38.425,-105.283,38.459,-105.47,38.451,-105.654,38.4,-105.832,38.425,-106.005,38.527,-106.089,38.611,-106.087,38.677,-106.135,38.802,-106.286,39.078,-106.381,39.222]]},{"n":"Atbara","z":5,"b":[33.952,14.935,35.958,17.699],"l":[[35.583,16,35.352,16.313,35.273,16.445,35.268,16.503,35.19,16.632,35.036,16.834,34.955,16.963,34.945,17.02,34.767,17.138,34.421,17.319,34.228,17.453,34.188,17.541,34.122,17.606,33.952,17.699],[35.586,16.004,35.613,15.716,35.714,15.469,35.9,15.169,35.95,15.045,35.958,14.999,35.924,14.935]]},{"n":"Athabasca","z":4.7,"b":[-118.084,52.171,-111.07,59.005],"l":[[-117.575,52.171,-117.543,52.209,-117.552,52.271,-117.645,52.456,-117.674,52.537,-117.727,52.578,-117.814,52.606,-117.864,52.647,-117.878,52.701,-117.926,52.757,-118.007,52.815,-118.045,52.872,-118.039,52.929,-118.052,52.977,-118.084,53.018,-118.036,53.113,-117.907,53.263,-117.679,53.415,-117.354,53.57,-117.184,53.676,-117.17,53.735,-117.173,53.775,-117.193,53.796,-117.172,53.839,-117.108,53.907,-116.924,54.01,-116.617,54.151,-116.414,54.228,-116.315,54.243,-116.133,54.224,-115.868,54.172,-115.615,54.149,-115.373,54.155,-115.176,54.2,-115.021,54.285,-114.859,54.331,-114.689,54.34,-114.536,54.39,-114.399,54.481,-114.332,54.565,-114.336,54.642,-114.265,54.804,-114.049,55.174],[-114.042,55.168,-113.76,55.228,-113.599,55.172,-113.466,55.018,-113.413,54.901,-113.44,54.822,-113.41,54.771,-113.324,54.748,-113.259,54.773,-113.215,54.846,-113.077,54.909,-112.846,54.961,-112.733,55,-112.739,55.028,-112.772,55.05,-112.834,55.066,-112.85,55.116,-112.82,55.198,-112.774,55.256,-112.712,55.291,-112.708,55.385,-112.761,55.537,-112.74,55.648,-112.645,55.716,-112.614,55.789,-112.648,55.868,-112.658,55.947,-112.645,56.025,-112.61,56.098,-112.555,56.165,-112.58,56.256,-112.683,56.371,-112.65,56.464,-112.482,56.536,-112.257,56.573,-111.974,56.574,-111.728,56.615,-111.518,56.694,-111.423,56.798,-111.442,56.926,-111.488,57.023,-111.559,57.09,-111.612,57.169,-111.647,57.261,-111.657,57.337,-111.644,57.397,-111.57,57.513,-111.435,57.684,-111.381,57.808,-111.407,57.885,-111.399,57.951,-111.357,58.004,-111.348,58.048,-111.372,58.081,-111.377,58.121,-111.365,58.167,-111.391,58.207,-111.455,58.241,-111.497,58.294,-111.518,58.365,-111.514,58.412,-111.487,58.434,-111.372,58.448,-111.17,58.454,-111.07,58.468,-111.075,58.505],[-111.28,58.788,-111.2,58.892,-111.233,58.946,-111.411,59.005]]},{"n":"Ayeyarwady","z":3,"b":[94.74,16.183,97.519,25.701],"l":[[97.519,25.701,97.508,25.69,97.515,25.668,97.506,25.59,97.449,25.417,97.394,25.337,97.34,25.35,97.292,25.31,97.249,25.216,97.25,25.152,97.293,25.12,97.29,25.1,97.239,25.091,97.216,25.064,97.22,25.019,97.196,24.981,97.117,24.933,97.055,24.835,97.055,24.755,97.095,24.659,97.117,24.552,97.12,24.432,97.142,24.347,97.184,24.296,97.198,24.252,97.183,24.214,97.114,24.178,96.991,24.145,96.892,24.169,96.819,24.25,96.737,24.307,96.647,24.341,96.561,24.344,96.479,24.317,96.402,24.251,96.331,24.148,96.291,24.059,96.28,23.946,96.085,23.653,96.006,23.359,95.979,22.966,95.949,22.754,95.917,22.723,95.923,22.663,95.968,22.574,95.984,22.495,95.97,22.427,95.976,22.377,96,22.347,96.02,22.23,96.034,22.026,96.009,21.907,95.946,21.873,95.879,21.875,95.811,21.913,95.71,21.928,95.578,21.921,95.481,21.871,95.418,21.779,95.373,21.666,95.344,21.531,95.315,21.458,95.269,21.438,95.173,21.345,95.061,21.287,94.9,21.232,94.82,21.146,94.82,21.03,94.794,20.933,94.74,20.854,94.761,20.702,94.857,20.477,94.907,20.305,94.912,20.184,94.956,20.071,95.081,19.91,95.14,19.845,95.169,19.71,95.188,19.485,95.177,19.277,95.136,19.087,95.14,18.945,95.189,18.852,95.18,18.771,95.113,18.702,95.076,18.641,95.067,18.588,95.143,18.479,95.303,18.314,95.4,18.181,95.433,18.083,95.452,17.956,95.455,17.856,95.456,17.803,95.472,17.744,95.485,17.698,95.539,17.642,95.561,17.586,95.552,17.529,95.58,17.48,95.645,17.439,95.655,17.39,95.611,17.332,95.603,17.254,95.632,17.158,95.625,17.086,95.582,17.039,95.574,16.977,95.6,16.902,95.543,16.838,95.481,16.814,95.404,16.784,95.331,16.721,95.322,16.647,95.236,16.52,94.992,16.251,94.952,16.21,94.893,16.183]]},{"n":"Back","z":5,"b":[-108.828,64.562,-95.797,66.617],"l":[[-108.828,64.562,-108.533,64.562],[-108.18,64.686,-108.09,64.715],[-107.979,64.72,-107.849,64.755,-107.775,64.803,-107.712,64.877,-107.605,64.953,-107.454,65.032,-107.374,65.108,-107.365,65.18,-107.377,65.244,-107.408,65.298,-107.362,65.366,-107.239,65.447,-107.103,65.478,-106.954,65.458,-106.802,65.399,-106.648,65.301,-106.462,65.249,-106.244,65.244,-106.127,65.229,-106.111,65.205,-105.897,65.164,-105.275,65.077,-105.005,65.071,-104.648,65.112,-104.112,65.197,-103.709,65.283,-103.436,65.369,-103.277,65.445,-103.231,65.509,-103.159,65.556,-103.061,65.584,-102.812,65.601,-102.413,65.605,-102.178,65.63,-102.107,65.676,-102.096,65.724,-102.144,65.772,-102.068,65.834,-101.77,65.947],[-98.656,65.927,-98.6,65.884,-98.541,65.887,-98.461,65.92,-98.356,65.929,-98.225,65.914,-98.145,65.926,-98.116,65.966,-98.06,65.999,-97.979,66.024,-97.858,66.013,-97.698,65.964,-97.53,65.949,-97.267,65.974],[-96.995,66.152,-96.654,66.092,-96.475,66.093,-96.344,66.137,-96.192,66.206,-96.018,66.302,-95.896,66.389,-95.826,66.466,-95.799,66.527,-95.797,66.617]]},{"n":"Bafing","z":4,"b":[-12.088,10.522,-10.215,13.778],"l":[[-12.088,10.59,-12.006,10.529,-11.93,10.522,-11.835,10.549,-11.775,10.619,-11.752,10.731,-11.728,10.792,-11.691,10.809,-11.621,10.812,-11.602,10.864,-11.611,10.965,-11.58,11.073,-11.509,11.188,-11.443,11.251,-11.385,11.263,-11.303,11.317,-11.198,11.413,-11.128,11.498,-11.092,11.574,-11.059,11.6,-11.029,11.575,-10.965,11.574,-10.869,11.595,-10.744,11.734,-10.59,11.99,-10.466,12.139,-10.373,12.18,-10.341,12.231,-10.37,12.292,-10.345,12.395,-10.267,12.54,-10.238,12.636,-10.26,12.684,-10.252,12.725,-10.215,12.759,-10.223,12.797,-10.274,12.838,-10.294,12.883,-10.282,12.932,-10.292,12.957,-10.323,12.96,-10.352,13.021,-10.379,13.138,-10.43,13.212,-10.505,13.241,-10.609,13.256,-10.742,13.255,-10.804,13.297,-10.796,13.382,-10.772,13.427,-10.738,13.438,-10.734,13.454,-10.753,13.503,-10.833,13.64,-10.867,13.735,-10.873,13.751,-10.892,13.778]]},{"n":"Bahr el Zeraf","z":5,"b":[30.536,7.767,31.139,9.426],"l":[[30.536,7.767,30.621,8.031,30.646,8.212,30.641,8.4,30.669,8.516,30.731,8.56,30.784,8.641,30.829,8.758,30.84,8.873,30.819,8.986,30.891,9.138,31.139,9.426]]},{"n":"Mountain Nile","z":2,"b":[31.148,3.614,32.016,6.83],"l":[[32.016,3.614,31.711,3.944,31.588,4.101,31.544,4.194,31.517,4.326,31.507,4.496,31.525,4.606,31.57,4.656,31.635,4.817,31.719,5.087,31.77,5.3,31.788,5.455,31.789,5.589,31.774,5.702,31.743,5.802,31.673,5.93,31.46,6.357,31.329,6.582,31.148,6.83]]},{"n":"White Nile","z":2,"b":[30.2,6.83,31.148,9.531],"l":[[31.148,6.83,30.922,6.983,30.77,7.153,30.617,7.39,30.547,7.521,30.56,7.545,30.549,7.582,30.512,7.632,30.508,7.693,30.536,7.765,30.508,7.88,30.426,8.037,30.366,8.124,30.329,8.141,30.323,8.227,30.348,8.383,30.339,8.485,30.296,8.534,30.284,8.59,30.301,8.654,30.289,8.704,30.248,8.742,30.218,8.818,30.2,8.933,30.233,9.043,30.316,9.147,30.35,9.237,30.333,9.311,30.37,9.395,30.46,9.489,30.618,9.531,30.846,9.52,31.001,9.494,31.125,9.433]]},{"n":"Bamingui","z":5,"b":[18.886,7.538,19.733,8.836],"l":[[19.733,7.538,19.627,7.56,19.592,7.601,19.592,7.666,19.524,7.725,19.389,7.778,19.331,7.886,19.349,8.049,19.285,8.247,19.064,8.599,19.109,8.656,19.064,8.715,18.886,8.836]]},{"n":"Barito","z":5,"b":[114.067,-3.377,114.93,0.565],"l":[[114.838,0.565,114.871,0.458,114.853,0.416,114.792,0.403,114.736,0.355,114.659,0.229,114.659,0.183,114.637,0.159,114.592,0.142,114.522,0.145,114.425,0.167,114.349,0.174,114.295,0.166,114.259,0.143,114.239,0.106,114.219,0.094,114.192,0.041,114.221,-0.004,114.276,-0.045,114.249,-0.11,114.139,-0.198,114.079,-0.287,114.067,-0.377,114.14,-0.503,114.297,-0.664,114.427,-0.737,114.533,-0.72,114.586,-0.688,114.587,-0.641,114.639,-0.618,114.741,-0.619,114.824,-0.662,114.887,-0.747,114.923,-0.825,114.93,-0.894,114.904,-0.945,114.846,-0.975,114.836,-1.025,114.874,-1.094,114.864,-1.163,114.806,-1.231,114.807,-1.28,114.865,-1.31,114.886,-1.386,114.861,-1.566,114.796,-1.771,114.791,-1.843,114.824,-1.852,114.853,-1.891,114.877,-1.962,114.876,-2.037,114.848,-2.116,114.853,-2.222,114.889,-2.353,114.865,-2.505,114.779,-2.678,114.752,-2.803,114.782,-2.881,114.787,-2.946,114.766,-2.998,114.716,-3.052,114.636,-3.109,114.584,-3.19,114.526,-3.377]]},{"n":"Barwon","z":3,"b":[147.415,-30.125,150.47,-28.558],"l":[[150.47,-28.65,150.341,-28.564,150.15,-28.558,149.853,-28.604,149.677,-28.612,149.595,-28.565,149.512,-28.579,149.467,-28.608,149.433,-28.657,149.335,-28.723,149.174,-28.807,149.081,-28.875,149.058,-28.928,148.993,-28.986,148.887,-29.05,148.796,-29.188,148.72,-29.4,148.641,-29.528,148.558,-29.571,148.429,-29.688,148.254,-29.879,147.978,-30.013,147.415,-30.125]]},{"n":"Benue","z":4,"b":[6.776,7.788,11.924,9.497],"l":[[11.924,9.491,11.783,9.497,11.574,9.409,11.249,9.23,11.038,9.061,10.941,8.901,10.858,8.795,10.788,8.745,10.636,8.707,10.401,8.684,10.218,8.59,10.086,8.427,9.977,8.327,9.847,8.272,9.53,8.121,9.395,8.039,9.337,7.976,9.141,7.892,8.807,7.788,8.437,7.799,7.829,7.99,7.422,8.019,7.156,7.968,6.776,7.793]]},{"n":"Biya","z":5,"b":[85.056,51.762,87.392,52.633],"l":[[87.392,51.762,87.177,51.849,87.107,51.976,87.112,52.171,87.07,52.293,86.982,52.341,86.929,52.387,86.911,52.429,86.868,52.454,86.798,52.46,86.688,52.507,86.537,52.595,86.431,52.633,86.372,52.619,86.334,52.586,86.318,52.534,86.247,52.54,86.121,52.603,86.019,52.617,85.942,52.58,85.855,52.574,85.758,52.599,85.604,52.6,85.392,52.578,85.228,52.534,85.056,52.434]]},{"n":"Borcea","z":2.1,"b":[27.267,44.138,27.935,44.744],"l":[[27.267,44.138,27.769,44.363,27.93,44.486,27.935,44.592,27.922,44.67,27.877,44.744]]},{"n":"Braco Menor","z":4,"b":[-50.484,-12.846,-49.889,-9.73],"l":[[-50.484,-12.846,-50.392,-12.617,-50.296,-12.49,-50.164,-12.388,-50.105,-12.313,-50.12,-12.266,-50.114,-12.209,-50.06,-12.093,-50.036,-12.065,-49.999,-12.058,-49.948,-12.071,-49.914,-12.034,-49.897,-11.945,-49.904,-11.873,-49.935,-11.816,-49.931,-11.693,-49.892,-11.504,-49.889,-11.399,-49.92,-11.376,-49.933,-11.316,-49.926,-11.217,-49.939,-11.111,-49.974,-10.998,-49.978,-10.772,-49.954,-10.435,-49.963,-10.191,-50.006,-10.04,-50.048,-9.96,-50.09,-9.952,-50.126,-9.923,-50.158,-9.873,-50.174,-9.819,-50.175,-9.73]]},{"n":"Brahmaputra","z":2.1,"b":[89.613,23.459,95.444,28.003],"l":[[89.739,24.33,89.714,24.135,89.708,23.962,89.744,23.859,89.868,23.72,89.994,23.621,90.074,23.573,90.267,23.459],[89.739,24.33,89.809,24.172,89.923,24.082,90.116,23.971,90.214,23.869,90.218,23.776,90.244,23.735,90.291,23.745,90.355,23.709,90.476,23.583,90.562,23.537],[95.396,28.003,95.444,27.884,95.369,27.806,95.189,27.729,95.116,27.698,95.104,27.683,95.109,27.678,95.122,27.676,95.137,27.666,95.139,27.644,95.084,27.615,94.972,27.586,94.878,27.543,94.801,27.487,94.725,27.413,94.652,27.322,94.63,27.278,94.597,27.234,94.567,27.189,94.559,27.131,94.518,27.072,94.443,27.014,94.335,26.952,94.193,26.885,94.12,26.867,94.023,26.867,93.752,26.732,93.403,26.739,93.245,26.718,93.161,26.671,93.009,26.636,92.787,26.612,92.656,26.58,92.615,26.538,92.544,26.521,92.442,26.529,92.312,26.495,92.155,26.418,92.071,26.361,92.062,26.323,91.966,26.27,91.644,26.164,91.547,26.148,91.436,26.151,91.25,26.183,91.152,26.205,91.052,26.192,90.917,26.151,90.76,26.155,90.58,26.204,90.356,26.174,90.088,26.065,89.89,25.901,89.762,25.682,89.699,25.49,89.701,25.325,89.68,25.168,89.621,25.026,89.613,24.948,89.62,24.895,89.729,24.649,89.75,24.411,89.739,24.33]]},{"n":"Branco","z":5,"b":[-63.78,-1.389,-60.525,3.717],"l":[[-63.78,2.846,-63.703,3.001,-63.699,3.075,-63.741,3.12,-63.761,3.163,-63.757,3.227,-63.779,3.464,-63.743,3.597,-63.658,3.705,-63.541,3.717,-63.392,3.634,-63.289,3.615,-63.231,3.66,-63.189,3.635,-63.161,3.539,-63.111,3.459,-63.04,3.393,-62.865,3.304,-62.586,3.194,-62.383,3.142,-62.256,3.148,-62.165,3.177,-62.11,3.227,-62.047,3.251,-61.975,3.247,-61.901,3.269,-61.825,3.316,-61.72,3.333,-61.585,3.321,-61.5,3.324,-61.446,3.355,-61.353,3.359,-61.299,3.382,-61.253,3.425,-61.095,3.452,-60.825,3.465,-60.666,3.455,-60.616,3.421,-60.569,3.318,-60.525,3.146,-60.576,2.947,-60.722,2.722,-60.801,2.54,-60.813,2.404,-60.871,2.284,-60.974,2.182,-61.03,2.113,-61.039,2.079,-61.023,2.03,-60.983,1.967,-61.026,1.864,-61.154,1.722,-61.256,1.471,-61.334,1.114,-61.46,0.815,-61.634,0.575,-61.755,0.25,-61.825,-0.161,-61.847,-0.419,-61.812,-0.576,-61.868,-0.792,-61.878,-0.956,-61.861,-1.141,-61.863,-1.27,-61.881,-1.389]]},{"n":"Bratul Chillia","z":2.1,"b":[28.761,45.18,29.706,45.42],"l":[[29.706,45.26,29.652,45.314,29.568,45.371,29.404,45.42,29.224,45.403,29.027,45.321,28.894,45.29,28.824,45.311,28.782,45.31,28.767,45.286,28.77,45.267,28.791,45.252,28.788,45.241,28.761,45.234,28.781,45.218,28.883,45.18]]},{"n":"Bratul Sfintu Gheorghe","z":2.1,"b":[28.883,44.915,29.605,45.18],"l":[[28.883,45.18,29.075,45.1,29.147,45.084,29.175,45.089,29.205,45.08,29.276,45.007,29.348,44.97,29.42,44.97,29.495,44.95,29.605,44.915]]},{"n":"Bratul Sulina","z":2.1,"b":[28.883,45.152,29.679,45.18],"l":[[28.883,45.18,29.679,45.152]]},{"n":"Brazos","z":5,"b":[-100.501,28.898,-95.378,33.584],"l":[[-100.501,32.905,-100.441,32.957,-100.319,32.994,-100.115,33.034,-99.998,33.091,-99.968,33.165,-99.964,33.248,-99.987,33.34,-99.891,33.435,-99.679,33.532,-99.494,33.582,-99.337,33.584,-99.247,33.556,-99.225,33.497,-99.184,33.468,-99.126,33.466,-99.081,33.438,-99.05,33.383,-99.022,33.361,-98.996,33.373,-98.973,33.355,-98.953,33.307,-98.882,33.246,-98.7,33.136,-98.712,33.082,-98.693,33.039,-98.647,32.99,-98.608,32.987,-98.577,33.032,-98.519,33.033,-98.433,32.992,-98.406,32.952,-98.437,32.915,-98.435,32.879,-98.399,32.846,-98.34,32.837,-98.258,32.853,-98.201,32.836,-98.169,32.784,-98.176,32.752,-98.194,32.739,-98.186,32.719,-98.156,32.704,-97.973,32.649,-97.85,32.574,-97.788,32.479,-97.716,32.416,-97.636,32.384,-97.623,32.358,-97.676,32.337,-97.696,32.303,-97.682,32.258,-97.632,32.213,-97.546,32.168,-97.498,32.157,-97.474,32.168,-97.464,32.163,-97.462,32.152,-97.466,32.09,-97.417,32.003,-97.315,31.892,-97.263,31.809,-97.261,31.752,-97.193,31.663,-97.06,31.541,-96.971,31.421,-96.904,31.244,-96.654,30.791,-96.512,30.61,-96.394,30.551,-96.319,30.485,-96.288,30.412,-96.242,30.376,-96.181,30.38,-96.131,30.362,-96.093,30.325,-96.099,30.258,-96.149,30.164,-96.153,30.092,-96.11,30.042,-96.078,29.931,-96.058,29.758,-96.03,29.666,-95.979,29.647,-95.905,29.632,-95.758,29.601,-95.656,29.559,-95.599,29.505,-95.572,29.418,-95.573,29.298,-95.589,29.206,-95.619,29.141,-95.57,29.074,-95.44,29.004,-95.378,28.947,-95.388,28.898]]},{"n":"Busira","z":5,"b":[18.951,-0.294,20.754,-0.105],"l":[[20.754,-0.282,20.612,-0.294,20.503,-0.28,20.38,-0.242,20.297,-0.234,20.254,-0.254,20.221,-0.227,20.196,-0.154,20.156,-0.13,20.101,-0.155,20.028,-0.147,19.937,-0.105,19.864,-0.105,19.809,-0.148,19.713,-0.15,19.577,-0.112,19.411,-0.114,19.214,-0.155,19.099,-0.199,19.064,-0.247,19.023,-0.258,18.951,-0.219]]},{"n":"Bykovskaya Protoka","z":2,"b":[126.657,72.294,127.726,72.524],"l":[[126.69,72.294,126.657,72.423,126.674,72.485,126.729,72.524,126.984,72.515,127.666,72.43,127.726,72.413]]},{"n":"Bénoué","z":4,"b":[11.924,7.777,13.999,9.506],"l":[[13.524,7.777,13.674,7.855,13.717,7.922,13.705,8.006,13.736,8.071,13.85,8.138,13.96,8.24,13.998,8.312,13.999,8.388,13.977,8.453,13.93,8.506,13.927,8.597,13.966,8.726,13.925,8.833,13.801,8.917,13.681,9.054,13.565,9.244,13.475,9.314,13.412,9.264,13.319,9.226,13.197,9.199,13.084,9.223,12.927,9.335,12.821,9.375,12.717,9.351,12.581,9.277,12.456,9.307,12.342,9.443,12.195,9.506,11.924,9.491]]},{"n":"Bío-Bío","z":4.7,"b":[-73.133,-38.77,-71.127,-36.795],"l":[[-71.241,-38.77,-71.127,-38.691,-71.127,-38.602,-71.241,-38.413,-71.304,-38.353,-71.329,-38.28,-71.336,-38.172,-71.401,-38.081,-71.526,-38.007,-71.62,-37.92,-71.684,-37.82,-71.754,-37.754,-71.83,-37.721,-71.918,-37.714,-72.02,-37.732,-72.09,-37.719,-72.148,-37.652,-72.256,-37.593,-72.355,-37.568,-72.482,-37.557,-72.576,-37.531,-72.636,-37.489,-72.691,-37.406,-72.741,-37.281,-72.807,-37.205,-72.89,-37.178,-72.941,-37.115,-72.958,-37.016,-72.999,-36.927,-73.062,-36.846,-73.112,-36.803,-73.133,-36.795]]},{"n":"Caquetá","z":4.7,"b":[-76.536,-1.53,-69.462,1.33],"l":[[-76.533,1.33,-76.536,1.139,-76.508,1.057,-76.422,1.002,-76.263,0.98,-76.145,0.988,-76.015,1.019,-75.92,0.996,-75.859,0.918,-75.69,0.844,-75.413,0.772,-75.252,0.676,-75.209,0.556,-75.139,0.49,-75.044,0.476,-74.988,0.42,-74.971,0.321,-74.893,0.241,-74.753,0.181,-74.678,0.1,-74.669,-0.003,-74.638,-0.07,-74.585,-0.101,-74.527,-0.108,-74.462,-0.091,-74.404,-0.093,-74.353,-0.113,-74.306,-0.149,-74.262,-0.202,-74.166,-0.263,-74.016,-0.334,-73.885,-0.374,-73.771,-0.383,-73.693,-0.406,-73.629,-0.463,-73.517,-0.515,-73.438,-0.528,-73.356,-0.518,-73.288,-0.536,-73.233,-0.582,-73.154,-0.585,-73.05,-0.546,-72.969,-0.545,-72.913,-0.583,-72.851,-0.591,-72.784,-0.57,-72.704,-0.591,-72.611,-0.654,-72.529,-0.653,-72.458,-0.589,-72.398,-0.575,-72.349,-0.611,-72.286,-0.607,-72.209,-0.563,-72.139,-0.593,-72.076,-0.697,-71.99,-0.765,-71.883,-0.798,-71.803,-0.847,-71.75,-0.914,-71.677,-0.947,-71.584,-0.945,-71.543,-0.978,-71.557,-1.044,-71.55,-1.087,-71.523,-1.107,-71.477,-1.111,-71.379,-1.093,-71.227,-1.061,-71.13,-1.095,-71.036,-1.184,-70.945,-1.24,-70.858,-1.263,-70.778,-1.305,-70.706,-1.367,-70.623,-1.41,-70.529,-1.435,-70.44,-1.438,-70.356,-1.419,-70.256,-1.441,-70.141,-1.503,-70.067,-1.53,-70.033,-1.52,-69.975,-1.435,-69.893,-1.275,-69.801,-1.215,-69.7,-1.255,-69.6,-1.256,-69.524,-1.226,-69.462,-1.231]]},{"n":"Yangtze","z":2,"b":[103.851,28.621,113.128,31.061],"l":[[103.851,28.668,103.942,28.627,104.094,28.621,104.335,28.636,104.492,28.679,104.564,28.749,104.62,28.779,104.65,28.795,104.753,28.817,104.833,28.816,104.893,28.79,104.939,28.793,104.985,28.79,105.012,28.778,105.043,28.746,105.091,28.724,105.128,28.732,105.153,28.769,105.209,28.786,105.296,28.785,105.351,28.805,105.373,28.848,105.424,28.877,105.503,28.892,105.547,28.915,105.577,28.922,105.619,28.906,105.673,28.898,105.741,28.918,105.785,28.906,105.803,28.862,105.829,28.839,105.861,28.837,105.882,28.852,105.89,28.884,105.876,28.915,105.838,28.943,105.834,28.98,105.865,29.025,105.946,29.058,106.076,29.08,106.146,29.125,106.157,29.195,106.253,29.274,106.434,29.363,106.518,29.426,106.505,29.462,106.523,29.516,106.572,29.589,106.618,29.612,106.66,29.586,106.721,29.578,106.802,29.588,106.894,29.65,106.998,29.763,107.084,29.787,107.15,29.721,107.237,29.699,107.345,29.718,107.419,29.771,107.458,29.856,107.53,29.89,107.634,29.873,107.842,30.043,108.308,30.58,108.391,30.692,108.418,30.755,108.417,30.808,108.443,30.853,108.496,30.892,108.757,30.936,109.46,31.01,109.545,31.034,109.598,31.031,109.646,31.008,109.742,31.016,109.884,31.054,109.984,31.061,110.043,31.037,110.143,31.032,110.285,31.045,110.474,31.02,110.828,30.922,110.971,30.852,111.045,30.835,111.098,30.847,111.159,30.829,111.228,30.781,111.309,30.673,111.399,30.507,111.465,30.418,111.507,30.407,111.52,30.384,111.507,30.351,111.512,30.318,111.538,30.287,111.571,30.287,111.612,30.311,111.736,30.389,111.818,30.426,111.86,30.421,111.911,30.387,111.969,30.323,112.058,30.293,112.179,30.295,112.247,30.278,112.263,30.242,112.252,30.191,112.215,30.125,112.252,30.068,112.363,30.018,112.406,29.927,112.381,29.793,112.41,29.738,112.492,29.762,112.544,29.792,112.569,29.784,112.593,29.766,112.604,29.752,112.629,29.752,112.632,29.765,112.657,29.788,112.707,29.799,112.739,29.786,112.732,29.772,112.745,29.754,112.782,29.744,112.819,29.737,112.885,29.76,112.942,29.74,112.955,29.691,112.94,29.669,112.917,29.6,112.924,29.53,112.951,29.508,112.989,29.505,113.017,29.494,113.031,29.481,113.053,29.469,113.097,29.456,113.128,29.463]]},{"n":"Chari","z":5,"b":[14.176,8.816,18.886,12.98],"l":[[18.886,8.836,18.842,8.816,18.76,8.844,18.625,8.914,18.476,9.041,18.231,9.319,17.791,9.722,17.623,9.913,17.582,10.026,17.521,10.129,17.372,10.296,17.316,10.35,17.248,10.388,17.17,10.41,17.104,10.456,17.051,10.525,17.004,10.564,16.963,10.573,16.876,10.548,16.742,10.489,16.611,10.481,16.483,10.522,16.34,10.598,16.18,10.708,16.017,10.773,15.849,10.793,15.711,10.834,15.603,10.895,15.521,10.957,15.466,11.019,15.433,11.105,15.423,11.214,15.386,11.344,15.322,11.496,15.301,11.638,15.322,11.773,15.283,11.895,15.184,12.007,15.075,12.085],[15.075,12.085,14.95,12.043,14.843,12.073,14.692,12.144,14.584,12.219,14.519,12.298,14.415,12.344,14.273,12.357,14.197,12.384,14.176,12.471],[15.075,12.085,14.957,12.13,14.881,12.269,14.847,12.502,14.761,12.656,14.623,12.73,14.545,12.82,14.516,12.98]]},{"n":"Chattahoochee","z":5,"b":[-85.185,29.745,-83.636,34.716],"l":[[-84.886,30.721,-84.893,30.668,-84.961,30.574,-85.08,30.272,-85.117,30.17,-85.113,30.08,-85.043,29.995,-85.016,29.909,-85.033,29.822,-85.024,29.772,-84.969,29.745],[-85.067,31.635,-85.055,31.573,-85.093,31.296,-85.067,31.118,-84.966,30.923],[-84.076,34.167,-84.105,34.091,-84.153,34.042,-84.23,33.995,-84.292,33.98,-84.339,33.998,-84.43,33.926,-84.566,33.765,-84.729,33.622,-84.921,33.496,-85.037,33.396,-85.077,33.323,-85.089,33.252,-85.072,33.184,-85.096,33.107,-85.161,33.023,-85.185,32.937,-85.157,32.804,-85.015,32.526,-84.974,32.409,-84.986,32.361,-84.969,32.32,-84.922,32.286,-84.93,32.247,-84.993,32.202,-85.035,32.147,-85.065,32.051],[-83.746,34.716,-83.691,34.7,-83.661,34.659,-83.636,34.587,-83.672,34.504,-83.817,34.364]]},{"n":"Chattooga","z":5,"b":[-83.356,34.708,-83.121,35.076],"l":[[-83.121,35.076,-83.121,35.001,-83.17,34.933,-83.317,34.806,-83.356,34.708]]},{"n":"Chenab","z":5,"b":[70.612,29.023,77.436,33.371],"l":[[72.139,31.183,72.139,31.116,72.103,31.048,71.995,30.909,71.771,30.614,71.679,30.583,71.577,30.508,71.435,30.38,71.316,30.206,71.221,29.986,71.173,29.829,71.172,29.736,71.131,29.649,71.049,29.571,71.017,29.505,71.036,29.452,71.035,29.392,71.014,29.326,70.987,29.275,70.955,29.238,70.912,29.221,70.857,29.223,70.797,29.178,70.732,29.084,70.684,29.037,70.612,29.023],[77.436,32.293,77.134,32.457,76.94,32.593,76.767,32.671,76.552,32.736,76.437,32.842,76.423,32.989,76.338,33.122,76.182,33.243,76.02,33.323,75.854,33.363,75.759,33.371,75.737,33.347,75.746,33.305,75.788,33.246,75.802,33.202,75.788,33.171,75.677,33.152,75.468,33.144,75.308,33.174,75.198,33.241,75.122,33.246,75.08,33.191,74.999,33.17,74.879,33.186,74.809,33.123,74.789,32.982,74.583,32.789,74.188,32.542,73.844,32.371,73.548,32.275,73.335,32.146,73.139,31.902,72.974,31.758,72.854,31.699,72.722,31.677,72.582,31.622,72.435,31.533,72.334,31.457,72.279,31.395,72.247,31.328,72.238,31.256,72.21,31.211,72.139,31.183]]},{"n":"Chixoy","z":4.7,"b":[-91.303,15.017,-90.417,16.391],"l":[[-91.278,15.017,-91.264,15.157,-91.303,15.237,-91.296,15.27,-91.257,15.285,-91.047,15.308,-90.881,15.288,-90.677,15.236,-90.555,15.225,-90.492,15.286,-90.483,15.316,-90.534,15.347,-90.647,15.378,-90.676,15.427,-90.624,15.493,-90.611,15.558,-90.64,15.622,-90.686,15.66,-90.749,15.673,-90.79,15.697,-90.809,15.733,-90.789,15.817,-90.732,15.951,-90.637,16.019,-90.506,16.023,-90.447,16.071,-90.46,16.162,-90.45,16.261,-90.417,16.351,-90.417,16.391]]},{"n":"Chubut","z":5,"b":[-71.22,-43.846,-65.047,-42.617],"l":[[-71.22,-43.582,-71.092,-43.608,-71.013,-43.562,-70.906,-43.396],[-70.899,-43.393,-70.811,-43.096,-70.744,-42.982,-70.669,-42.953,-70.626,-42.904,-70.615,-42.835,-70.579,-42.766,-70.518,-42.698,-70.461,-42.651,-70.408,-42.625,-70.244,-42.617,-69.97,-42.628,-69.784,-42.643,-69.686,-42.664,-69.508,-42.821,-69.252,-43.114,-69.134,-43.305,-69.155,-43.391,-69.153,-43.447,-69.117,-43.486,-68.976,-43.582,-68.901,-43.668,-68.844,-43.774,-68.765,-43.818,-68.665,-43.798,-68.547,-43.806,-68.41,-43.842,-68.102,-43.824,-67.358,-43.714,-67.308,-43.714,-67.264,-43.742,-67.225,-43.797,-67.16,-43.832,-67.068,-43.846,-66.879,-43.838,-66.523,-43.8,-66.493,-43.795,-66.458,-43.768,-66.457,-43.745,-66.465,-43.706,-66.444,-43.677,-66.356,-43.619,-66.277,-43.577,-66.107,-43.521,-65.982,-43.459,-65.902,-43.392,-65.793,-43.353,-65.656,-43.344,-65.551,-43.322,-65.479,-43.288,-65.344,-43.286,-65.047,-43.333]]},{"n":"Chulyshman","z":5,"b":[87.735,50.505,89.656,51.364],"l":[[89.656,50.505,89.403,50.598,89.194,50.622,88.946,50.608,88.76,50.656,88.636,50.764,88.512,50.831,88.387,50.857,88.22,50.929,88.01,51.048,87.861,51.151,87.772,51.238,87.735,51.302,87.756,51.364]]},{"n":"Churchill","z":5,"b":[-64.682,52.886,-60.337,53.546],"l":[[-64.622,52.891,-64.682,53.125],[-64.678,53.382,-64.577,53.49,-64.449,53.532,-64.26,53.546,-64.067,53.538,-63.867,53.508,-63.638,53.425,-63.378,53.291,-63.034,53.21,-62.606,53.181,-62.352,53.145,-62.272,53.103,-62.214,53.042,-62.179,52.963,-62.036,52.911,-61.785,52.886,-61.65,52.906,-61.631,52.968,-61.578,52.996,-61.448,52.986,-60.981,53.198,-60.724,53.268,-60.558,53.25,-60.337,53.277]]},{"n":"Coco","z":5,"b":[-86.844,13.42,-83.158,15.008],"l":[[-86.844,13.42,-86.639,13.513,-86.492,13.61,-86.411,13.636,-86.346,13.622,-86.282,13.572,-86.217,13.487,-86.148,13.461,-86.075,13.493,-86.004,13.505,-85.935,13.496,-85.856,13.581,-85.769,13.759,-85.728,13.876,-85.731,13.932,-85.682,13.983,-85.58,14.028,-85.477,14.109,-85.374,14.224,-85.284,14.292,-85.208,14.312,-85.179,14.343,-85.198,14.386,-85.192,14.447,-85.161,14.525,-85.117,14.571,-85.059,14.583,-85.037,14.608,-85.049,14.645,-85.037,14.686,-84.985,14.752,-84.86,14.81,-84.789,14.79,-84.73,14.713,-84.646,14.661,-84.538,14.633,-84.454,14.644,-84.394,14.692,-84.34,14.706,-84.292,14.687,-84.267,14.698,-84.264,14.739,-84.239,14.748,-84.192,14.726,-84.151,14.72,-84.114,14.731,-84.1,14.751,-84.093,14.771,-84.066,14.786,-83.972,14.771,-83.867,14.794,-83.751,14.856,-83.674,14.884,-83.635,14.876,-83.59,14.908,-83.537,14.977,-83.415,15.008,-83.158,14.993]]},{"n":"Colorado","z":4.7,"b":[-115.199,-39.88,-62.254,40.404],"l":[[-64.97,-38.804,-64.596,-38.841,-64.39,-38.878,-64.228,-38.925,-63.989,-39.038,-63.674,-39.217,-63.456,-39.316,-63.336,-39.335,-63.214,-39.38,-63.091,-39.451,-62.918,-39.499,-62.697,-39.524,-62.517,-39.608,-62.309,-39.822,-62.279,-39.852,-62.254,-39.88],[-70.302,-35.288,-70.223,-35.591,-70.166,-35.722,-70.072,-35.813,-69.865,-35.86,-69.762,-35.964,-69.694,-36.14,-69.671,-36.26,-69.692,-36.324,-69.693,-36.407,-69.676,-36.508,-69.694,-36.596,-69.748,-36.67,-69.785,-36.78,-69.806,-36.925,-69.797,-37.02,-69.758,-37.064,-69.699,-37.106,-69.621,-37.146,-69.463,-37.174,-69.226,-37.19,-69.076,-37.24,-69.016,-37.323,-68.87,-37.383,-68.641,-37.422,-68.501,-37.465,-68.452,-37.514,-68.273,-37.558,-67.963,-37.598,-67.783,-37.659,-67.735,-37.74,-67.751,-37.82,-67.83,-37.901,-67.866,-37.969,-67.858,-38.022,-67.824,-38.056,-67.765,-38.069,-67.69,-38.122,-67.599,-38.215,-67.46,-38.252,-67.271,-38.232,-67.15,-38.269,-67.097,-38.362,-66.993,-38.436,-66.763,-38.516,-66.653,-38.564,-66.603,-38.61,-66.576,-38.671,-66.52,-38.71,-66.435,-38.726,-66.068,-38.74,-65.893,-38.761,-65.757,-38.799,-65.658,-38.807,-65.595,-38.786,-65.514,-38.791,-65.416,-38.823,-65.27,-38.83,-64.978,-38.804],[-114.721,32.724,-114.788,32.565,-114.839,32.504,-114.897,32.49,-114.959,32.42,-115.025,32.294,-115.093,32.213,-115.164,32.176,-115.199,32.124,-115.196,32.058,-115.187,32.044,-115.173,32.045,-115.153,32.036,-115.126,31.988,-115.093,31.965,-115.035,31.966,-115.009,31.946,-114.981,31.918,-114.934,31.901],[-114.164,34.253,-114.329,34.142,-114.418,34.051,-114.509,33.904,-114.495,33.785,-114.499,33.686,-114.522,33.608,-114.556,33.536,-114.603,33.47,-114.651,33.431,-114.702,33.418,-114.719,33.331,-114.704,33.17,-114.648,33.072,-114.551,33.037,-114.491,32.984,-114.469,32.912,-114.478,32.842,-114.516,32.773,-114.582,32.735,-114.721,32.724],[-114.591,35.353,-114.611,34.998,-114.611,34.907,-114.557,34.795,-114.398,34.59,-114.379,34.541],[-114.741,36.014,-114.732,35.984,-114.687,35.917,-114.684,35.814,-114.677,35.73,-114.65,35.684,-114.645,35.631],[-112.876,36.28,-113.206,36.124,-113.296,36.048,-113.325,35.995,-113.323,35.835,-113.356,35.764,-113.422,35.762,-113.579,35.801,-113.636,35.831],[-111.473,36.932,-111.591,36.85,-111.683,36.748,-111.788,36.599,-111.852,36.488,-111.874,36.414,-111.865,36.32,-111.826,36.206,-111.834,36.126,-111.891,36.081,-111.995,36.073,-112.147,36.102,-112.256,36.151,-112.322,36.221,-112.377,36.246,-112.42,36.226,-112.459,36.229,-112.495,36.254,-112.499,36.288,-112.471,36.332,-112.467,36.366,-112.489,36.389,-112.533,36.401,-112.598,36.402,-112.693,36.372,-112.881,36.28],[-105.861,40.165,-105.985,40.127,-106.126,40.093,-106.285,40.091,-106.401,40.062,-106.482,40.019,-106.595,39.923,-106.638,39.894,-106.676,39.89,-106.726,39.92,-106.793,39.924,-106.877,39.902,-106.969,39.828,-107.071,39.701,-107.132,39.644,-107.235,39.602,-107.541,39.579,-107.734,39.579,-107.847,39.557,-107.944,39.512,-108.106,39.41,-108.242,39.315,-108.314,39.178,-108.434,39.099,-108.558,39.09,-108.801,39.184,-108.867,39.201,-108.952,39.198,-109.051,39.141,-109.196,39.033,-109.265,38.935,-109.306,38.812,-109.363,38.734,-109.434,38.702,-109.499,38.651,-109.556,38.581,-109.596,38.554,-109.614,38.561,-109.635,38.546,-109.648,38.485,-109.67,38.453,-109.7,38.449,-109.723,38.414,-109.739,38.35,-109.785,38.279,-109.897,38.165,-110.131,37.962,-110.248,37.895,-110.368,37.898],[-105.846,40.404,-105.816,40.215,-105.821,40.185]]},{"n":"Columbia","z":3,"b":[-123.221,45.591,-115.865,52.102],"l":[[-122.15,45.594,-122.203,45.591,-122.561,45.625,-122.65,45.627,-122.726,45.674,-122.746,45.75,-122.843,45.978,-122.942,46.115,-123.042,46.16,-123.121,46.179,-123.221,46.154],[-118.168,48.232,-118.232,48.086,-118.284,48.035,-118.346,48.032,-118.368,47.996,-118.349,47.927,-118.351,47.879,-118.375,47.849,-118.433,47.857,-118.525,47.903,-118.665,47.927,-118.854,47.93,-118.983,47.982,-119.052,48.082,-119.228,48.101,-119.511,48.04,-119.681,48.03,-119.74,48.073,-119.801,48.085,-119.864,48.066,-119.887,48.029,-119.868,47.975,-119.87,47.929,-119.894,47.891,-119.969,47.813,-120.04,47.783,-120.14,47.763,-120.214,47.693,-120.262,47.575,-120.273,47.487,-120.248,47.429,-120.194,47.382,-120.068,47.329,-120.068,47.275,-120.049,47.245,-120.01,47.219,-119.997,47.176,-120.011,47.117,-119.992,46.985,-119.939,46.78,-119.838,46.674,-119.687,46.667,-119.577,46.681,-119.506,46.716,-119.417,46.679,-119.311,46.569,-119.256,46.456,-119.253,46.341,-119.201,46.269,-119.108,46.237,-119.085,46.233,-119.057,46.22,-119.036,46.206,-118.968,46.132,-118.948,46.11,-118.932,46.095,-118.925,46.08,-118.931,46.063,-118.959,46.038,-118.99,46.019,-119.05,45.981,-119.177,45.945,-119.429,45.934,-119.934,45.838,-120.156,45.762,-120.351,45.725,-120.716,45.69,-121.045,45.639,-121.184,45.644,-121.243,45.687,-121.402,45.712,-121.663,45.718,-121.882,45.689,-122.085,45.603,-122.15,45.594],[-117.907,49.342,-117.721,49.331,-117.676,49.275,-117.711,49.172,-117.701,49.098,-117.648,49.055,-117.682,49.015,-117.717,48.975,-117.908,48.858,-118.021,48.766,-118.074,48.663],[-117.88,50.103,-117.917,50.092,-117.925,50.064,-117.915,50.016,-117.95,49.971,-118.029,49.929,-118.074,49.881,-118.092,49.802],[-115.865,50.287,-116.081,50.591,-116.224,50.758,-116.365,50.89,-116.545,51.01,-116.762,51.12,-116.931,51.244,-117.052,51.383,-117.183,51.471,-117.324,51.507,-117.425,51.558,-117.517,51.659],[-117.873,51.87,-118.366,52.09],[-118.527,52.102,-118.581,52.052,-118.595,51.999,-118.589,51.925,-118.611,51.858,-118.661,51.796,-118.681,51.742,-118.672,51.696,-118.599,51.59,-118.463,51.426,-118.359,51.323,-118.289,51.281,-118.237,51.214,-118.205,51.12,-118.195,51.049,-118.208,51,-118.138,50.903,-117.906,50.683]]},{"n":"Congo","z":2.1,"b":[13.068,-8.272,27.049,2.178],"l":[[26.441,-8.272,26.654,-8.223,26.742,-8.193,26.776,-8.165,26.794,-8.124,26.794,-8.069,26.838,-8.011,26.928,-7.949,26.973,-7.9,26.973,-7.864,26.929,-7.804,26.839,-7.72,26.865,-7.529,27.009,-7.229,27.049,-6.991,26.984,-6.815,26.961,-6.664,26.98,-6.537,26.97,-6.422,26.931,-6.319,26.906,-6.173,26.895,-5.982,26.9,-5.818,26.93,-5.61],[24.292,0.782,24.178,0.924,24.005,1.022,23.734,1.123,23.461,1.342,23.184,1.679,22.968,1.873,22.735,1.95,22.557,2.101,22.396,2.161,22.194,2.178,21.969,2.178,21.721,2.158,21.499,2.113,21.304,2.041,20.944,1.995,20.418,1.972,19.902,1.864,19.143,1.574,18.714,1.223,18.526,1.052,18.436,0.944,18.343,0.692,18.199,0.1,18.184,0.063,18.109,-0.124,17.9,-0.391,17.753,-0.549,17.543,-0.775,17.279,-1,17.108,-1.064,16.975,-1.14,16.88,-1.226,16.849,-1.272,16.78,-1.376,16.622,-1.699,16.541,-1.84,16.434,-1.961,16.274,-2.108,16.215,-2.178,16.192,-2.279,16.202,-2.465,16.217,-3.03,16.191,-3.194,16.147,-3.464,15.99,-3.766,15.872,-3.934,15.755,-3.986,15.6,-4.031,15.526,-4.088,15.481,-4.172,15.395,-4.245,15.267,-4.308,15.106,-4.461,14.912,-4.706,14.779,-4.846,14.708,-4.882,14.634,-4.885,14.558,-4.856,14.494,-4.852,14.418,-4.883,14.234,-4.912,14.133,-4.945,14.054,-4.991,14.005,-5.052,13.985,-5.129,13.938,-5.205,13.863,-5.278,13.832,-5.301,13.809,-5.303,13.777,-5.279,13.752,-5.279,13.735,-5.303,13.692,-5.31,13.623,-5.3,13.574,-5.316,13.543,-5.359,13.55,-5.414,13.595,-5.482,13.591,-5.513,13.565,-5.531,13.553,-5.576,13.564,-5.655,13.512,-5.743,13.399,-5.841,13.303,-5.882,13.184,-5.856,13.068,-5.865]]},{"n":"Copper","z":5,"b":[-145.419,60.669,-143.658,62.699],"l":[[-143.658,62.474,-143.675,62.557,-143.713,62.605,-143.776,62.646,-143.907,62.674,-144.203,62.699,-144.458,62.663,-144.733,62.568,-145.114,62.403,-145.336,62.286,-145.399,62.219,-145.419,62.157,-145.384,62.073,-145.166,61.916,-144.936,61.81,-144.621,61.701,-144.455,61.617,-144.427,61.526,-144.486,61.453,-144.58,61.407,-144.729,61.366,-144.828,61.309,-144.877,61.238,-144.873,61.151,-144.815,61.049,-144.716,60.951,-144.574,60.859,-144.534,60.833,-144.523,60.815,-144.532,60.788,-144.616,60.714,-144.638,60.69,-144.691,60.669]]},{"n":"Coppermine","z":5,"b":[-116.326,64.552,-111.212,67.819],"l":[[-111.212,64.583,-111.351,64.552,-111.456,64.562,-111.572,64.604,-111.715,64.628,-111.966,64.638],[-112.318,64.96,-112.343,64.899],[-114.173,65.638,-114.168,65.684,-114.231,65.728,-114.36,65.787,-114.43,65.864,-114.439,65.958,-114.403,66.035,-114.321,66.096,-114.374,66.233,-114.56,66.444,-114.751,66.586,-114.948,66.658,-115.265,66.728,-115.921,66.831,-116.157,66.81,-116.266,66.832,-116.326,66.89,-116.293,66.972,-116.166,67.08,-116.134,67.156,-116.196,67.201,-116.074,67.224,-115.768,67.226,-115.6,67.236,-115.571,67.253,-115.595,67.276,-115.67,67.303,-115.713,67.341,-115.724,67.389,-115.694,67.44,-115.625,67.495,-115.593,67.536,-115.598,67.563,-115.553,67.615,-115.458,67.691,-115.335,67.747,-115.133,67.819]]},{"n":"Cubango","z":5,"b":[16.145,-18.06,21.503,-12.634],"l":[[16.145,-12.654,16.192,-12.634,16.234,-12.66,16.288,-12.725,16.328,-12.821,16.365,-13.01,16.431,-13.436,16.414,-13.711,16.335,-13.977,16.288,-14.196,16.272,-14.368,16.294,-14.493,16.354,-14.573,16.446,-14.641,16.568,-14.699,16.646,-14.821,16.679,-15.005,16.75,-15.166,16.859,-15.305,16.944,-15.395,17.005,-15.436,17.12,-15.474,17.288,-15.508,17.379,-15.555,17.393,-15.615,17.462,-15.714,17.586,-15.853,17.664,-16.019,17.696,-16.212,17.767,-16.418,17.876,-16.637,18.095,-16.953,18.588,-17.57,18.718,-17.703,18.826,-17.766,18.955,-17.804,19.076,-17.818,19.189,-17.808,19.377,-17.825,19.639,-17.869,19.912,-17.881,20.194,-17.864,20.393,-17.887,20.508,-17.953,20.625,-17.997,20.746,-18.02,20.908,-18.006,21.113,-17.956,21.288,-17.963,21.503,-18.06]]},{"n":"Culuene","z":4.7,"b":[-54.609,-14.984,-52.877,-11.444],"l":[[-54.609,-14.984,-54.357,-14.897,-54.251,-14.837,-54.207,-14.775,-54.136,-14.758,-54.04,-14.786,-53.967,-14.79,-53.894,-14.761,-53.659,-14.592,-53.512,-14.346,-53.374,-13.968,-53.269,-13.736,-53.197,-13.651,-53.16,-13.583,-53.159,-13.532,-53.08,-13.344,-52.922,-13.019,-52.877,-12.807,-52.945,-12.71,-52.97,-12.644,-52.953,-12.611,-53.004,-12.517,-53.125,-12.361,-53.213,-12.268,-53.27,-12.235,-53.329,-12.161,-53.391,-12.047,-53.459,-11.966,-53.532,-11.918,-53.579,-11.846,-53.599,-11.75,-53.567,-11.637,-53.444,-11.444]]},{"n":"Dadu","z":5,"b":[100.07,29.236,103.719,33.006],"l":[[100.07,33.006,100.165,32.896,100.24,32.839,100.371,32.779,100.509,32.627,100.616,32.546,100.739,32.486,100.801,32.427,100.802,32.37,100.842,32.326,100.921,32.295,100.97,32.212,100.999,32.009,101.214,31.837,101.422,31.8,101.694,31.841,101.879,31.823,101.977,31.748,102.039,31.646,102.064,31.518,102.055,31.432,102.011,31.389,102.004,31.35,102.034,31.316,102.01,31.258,101.932,31.176,101.887,31.105,101.876,30.985,101.882,30.925,101.928,30.846,102.013,30.748,102.058,30.655,102.065,30.568,102.102,30.446,102.169,30.291,102.195,30.175,102.179,30.097,102.185,30.032,102.214,29.979,102.213,29.84,102.182,29.614,102.206,29.439,102.285,29.316,102.397,29.278,102.616,29.35,102.76,29.313,102.818,29.279,102.839,29.237,102.877,29.237,102.931,29.281,102.979,29.288,103.022,29.258,103.061,29.258,103.096,29.288,103.151,29.289,103.225,29.26,103.278,29.261,103.309,29.293,103.359,29.311,103.427,29.316,103.506,29.292,103.594,29.237,103.644,29.236,103.655,29.289,103.639,29.321,103.596,29.333,103.569,29.36,103.559,29.4,103.595,29.455,103.642,29.495,103.677,29.525,103.719,29.537]]},{"n":"Daly","z":5,"b":[130.26,-14.706,131.844,-13.302],"l":[[131.844,-14.706,131.75,-14.669,131.706,-14.601,131.681,-14.491,131.566,-14.345,131.257,-14.073,131.2,-13.981,131.186,-13.927,131.195,-13.879,131.174,-13.849,131.123,-13.836,131.037,-13.855,130.916,-13.904,130.827,-13.914,130.771,-13.883,130.721,-13.819,130.678,-13.721,130.637,-13.671,130.598,-13.669,130.546,-13.618,130.482,-13.516,130.409,-13.456,130.327,-13.438,130.305,-13.401,130.324,-13.353,130.325,-13.33,130.293,-13.309,130.26,-13.302]]},{"n":"Dalälven","z":5,"b":[12.095,60.122,17.457,62.157],"l":[[16.433,60.156,16.499,60.163],[17.417,60.51,17.457,60.642],[14.973,60.728,15.047,60.711,15.057,60.681,15.029,60.632,15.045,60.591,15.107,60.557,15.206,60.54,15.343,60.541,15.478,60.505,15.611,60.432,15.754,60.401,15.904,60.411,16.006,60.39,16.058,60.336,16.073,60.28,16.052,60.22,16.112,60.173,16.323,60.122],[13.718,61.413,13.724,61.334,13.74,61.3,13.768,61.284,13.949,61.268,14.034,61.237,14.094,61.183,14.231,61.116,14.552,60.997],[12.095,62.15,12.212,62.157,12.291,62.12,12.41,61.999,13.212,61.663]]},{"n":"Damietta Branch","z":2,"b":[31.078,30.151,31.839,31.526],"l":[[31.163,30.151,31.078,30.325,31.107,30.419,31.21,30.508,31.25,30.623,31.226,30.766,31.224,30.869,31.245,30.932,31.31,31.019,31.42,31.128,31.509,31.186,31.579,31.192,31.671,31.278,31.839,31.526]]},{"n":"Danube","z":2.1,"b":[17.206,43.671,28.761,48.061],"l":[[17.206,48.061,17.481,47.888,17.635,47.81,17.762,47.77,18.146,47.763,18.754,47.788,18.795,47.809,19,47.819,19.071,47.807,19.103,47.779,19.123,47.731,19.124,47.709,19.073,47.552,19.06,47.481,19.03,47.413,18.959,47.365,18.907,47.26,18.884,47.151,18.928,47.083,18.935,47.033,18.94,47,18.936,46.885,18.945,46.804,18.983,46.74,18.966,46.685,18.894,46.638,18.874,46.586,18.905,46.528,18.919,46.423,18.916,46.271,18.859,46.151,18.748,46.063,18.727,46.014,18.759,45.935,18.839,45.836,18.894,45.767,18.947,45.656,18.918,45.601,18.954,45.558,19.055,45.527,19.064,45.515,19.033,45.502,19.008,45.466,19.005,45.4,19.093,45.337,19.273,45.278,19.475,45.243,19.699,45.233,19.828,45.241,19.863,45.269,19.907,45.259,19.959,45.213,20.045,45.186,20.163,45.179,20.253,45.15,20.282,45.126,20.315,45.099,20.339,45.038,20.326,44.969,20.353,44.911,20.421,44.864,20.5,44.843,20.591,44.847,20.636,44.828,20.634,44.786,20.666,44.741,20.731,44.693,20.913,44.708,21.36,44.827,21.523,44.79,21.597,44.755,21.636,44.71,21.74,44.681,21.909,44.666,22.027,44.62,22.093,44.542,22.201,44.561,22.351,44.676,22.498,44.706,22.642,44.651,22.721,44.606,22.734,44.57,22.701,44.556,22.62,44.562,22.554,44.54,22.502,44.49,22.495,44.435,22.531,44.378,22.582,44.338,22.648,44.316,22.683,44.286,22.688,44.248,22.775,44.195,22.945,44.127,23.028,44.078,23.024,44.047,22.985,44.017,22.911,43.987,22.868,43.948,22.856,43.899,22.868,43.865,22.919,43.834,23.225,43.874,23.535,43.854,23.951,43.787,24.227,43.763,24.431,43.794,25.16,43.686,25.497,43.671,25.686,43.712,25.819,43.767,25.933,43.871,26.216,44.007,26.489,44.084,26.848,44.146,27.087,44.167,27.222,44.143,27.267,44.138,27.338,44.133,27.412,44.138,27.594,44.179,27.86,44.263,28.022,44.343,28.08,44.419,28.083,44.476,28.032,44.513,28.013,44.558,28.025,44.611,27.992,44.664,27.907,44.719,27.877,44.744,27.85,44.768,27.83,44.796,27.831,44.83,27.863,44.865,27.895,44.968,27.926,45.139,27.965,45.269,28.011,45.358,28.079,45.414,28.212,45.45,28.318,45.347,28.451,45.292,28.761,45.234]]},{"n":"Darling","z":3,"b":[141.918,-34.117,147.415,-29.928],"l":[[147.415,-30.125,147.262,-30.122,147.201,-30.107,147.182,-30.078,147.048,-30.027,146.799,-29.953,146.656,-29.928,146.616,-29.95,146.46,-29.962,146.188,-29.963,146.043,-29.983,146.026,-30.023,145.909,-30.103,145.692,-30.224,145.565,-30.316,145.528,-30.378,145.467,-30.409,145.338,-30.409,145.272,-30.464,145.219,-30.481,145.157,-30.479,145.11,-30.51,145.075,-30.575,145.008,-30.626,144.906,-30.662,144.813,-30.733,144.728,-30.838,144.622,-30.9,144.494,-30.921,144.411,-30.965,144.372,-31.033,144.27,-31.102,144.107,-31.173,144.022,-31.25,144.017,-31.333,143.904,-31.403,143.683,-31.458,143.548,-31.514,143.5,-31.57,143.454,-31.59,143.411,-31.575,143.373,-31.609,143.341,-31.692,143.298,-31.737,143.245,-31.745,143.197,-31.779,143.156,-31.839,143.104,-31.881,143.04,-31.903,143.005,-31.956,142.999,-32.039,142.978,-32.09,142.926,-32.116,142.538,-32.333,142.404,-32.505,142.388,-32.804,142.397,-32.951,142.375,-33.048,142.326,-33.143,142.368,-33.239,142.501,-33.335,142.57,-33.4,142.575,-33.435,142.535,-33.512,142.449,-33.632,142.312,-33.723,142.124,-33.787,142.002,-33.867,141.944,-33.964,141.918,-34.038,141.926,-34.117]]},{"n":"Zapadnaya Dvina","z":4.7,"b":[24.054,55.069,32.318,57.066],"l":[[32.078,56.908,32.062,56.877,32.097,56.861,32.177,56.85,32.242,56.821,32.318,56.75,32.273,56.609,32.233,56.552,32.184,56.531,32.135,56.478,32.087,56.395,32.073,56.315,32.092,56.24,32.068,56.194,32.001,56.177,31.974,56.144,31.986,56.095,31.875,56.069,31.641,56.066,31.479,56.039,31.39,55.989,31.379,55.889,31.449,55.738,31.415,55.655,31.278,55.639,31.171,55.601,31.056,55.509,30.643,55.436,30.409,55.369,30.214,55.285,30.167,55.235,30.148,55.195,30.087,55.183,29.925,55.204,29.815,55.205,29.757,55.187,29.726,55.161,29.722,55.129,29.687,55.116,29.62,55.122,29.555,55.107,29.491,55.069,29.452,55.071,29.438,55.112,29.406,55.132,29.355,55.133,29.316,55.155,29.288,55.2,29.202,55.258,29.057,55.329,29.001,55.38,28.993,55.409,28.919,55.455,28.724,55.524,28.504,55.566,28.26,55.582,28.087,55.62,27.986,55.681,27.928,55.74,27.914,55.796,27.881,55.817,27.855,55.819,27.764,55.808,27.604,55.798,27.459,55.803,27.349,55.831,27.015,55.899,26.809,55.907,26.621,55.88,26.489,55.899,26.414,55.965,26.364,56.039,26.34,56.121,26.294,56.178,26.227,56.21,26.184,56.264,26.166,56.342,26.064,56.418,25.879,56.493,25.779,56.553,25.762,56.598,25.71,56.609,25.623,56.587,25.534,56.59,25.444,56.617,25.358,56.619,25.276,56.597,25.174,56.594,25.053,56.612,24.891,56.668,24.689,56.764,24.505,56.827,24.341,56.858,24.22,56.894,24.144,56.936,24.091,56.985,24.054,57.066]]},{"n":"Desaguadero","z":5,"b":[-67.362,-38.804,-64.97,-32.397],"l":[[-67.362,-32.397,-67.235,-32.67,-67.201,-32.8,-67.218,-32.877,-67.208,-33.044,-67.171,-33.301,-67.051,-33.588,-66.848,-33.905,-66.75,-34.094,-66.756,-34.154,-66.775,-34.196,-66.806,-34.218,-66.819,-34.268,-66.815,-34.346,-66.794,-34.442,-66.757,-34.558,-66.697,-34.675,-66.614,-34.794,-66.555,-34.921,-66.522,-35.056,-66.534,-35.346,-66.592,-35.79,-66.679,-36.078,-66.793,-36.209,-66.833,-36.395,-66.797,-36.635,-66.795,-36.8,-66.828,-36.889,-66.828,-36.99,-66.794,-37.103,-66.796,-37.173,-66.832,-37.201,-66.784,-37.251,-66.651,-37.325,-66.542,-37.407,-66.458,-37.498,-66.371,-37.544,-66.279,-37.547,-66.211,-37.606,-66.168,-37.722,-66.067,-37.799,-65.91,-37.838,-65.822,-37.915,-65.802,-38.029,-65.818,-38.097,-65.87,-38.117,-65.912,-38.161,-65.946,-38.228,-65.912,-38.282,-65.81,-38.324,-65.743,-38.37,-65.712,-38.422,-65.63,-38.489,-65.494,-38.571,-65.313,-38.66,-64.97,-38.804]]},{"n":"Dicle","z":4,"b":[39.643,37.062,42.377,38.416],"l":[[39.643,38.416,40.018,38.348,40.024,38.306,40.068,38.269,40.15,38.223,40.178,38.179,40.153,38.136,40.171,38.106,40.235,38.091,40.262,38.023,40.252,37.903,40.427,37.832,40.969,37.8,41.155,37.757,41.358,37.744,41.64,37.746,41.8,37.709,41.84,37.632,41.854,37.556,41.841,37.481,41.882,37.441,41.979,37.435,42.094,37.395,42.226,37.322,42.313,37.23,42.377,37.062]]},{"n":"Digul","z":5,"b":[139.177,-7.252,140.742,-4.856],"l":[[140.683,-4.856,140.731,-4.915,140.742,-4.967,140.734,-5.03,140.697,-5.109,140.597,-5.253,140.319,-5.536,140.223,-5.673,140.217,-5.758,140.239,-5.828,140.289,-5.883,140.309,-5.961,140.295,-6.116,140.264,-6.153,140.266,-6.216,140.289,-6.319,140.314,-6.375,140.338,-6.382,140.346,-6.427,140.337,-6.51,140.349,-6.573,140.382,-6.616,140.372,-6.675,140.321,-6.75,140.265,-6.802,140.205,-6.831,140.154,-6.879,140.111,-6.947,140.09,-7.009,140.092,-7.066,140.062,-7.114,139.999,-7.153,139.918,-7.146,139.85,-7.105,139.798,-7.105,139.74,-7.138,139.707,-7.204,139.652,-7.222,139.626,-7.193,139.589,-7.2,139.558,-7.231,139.521,-7.252,139.489,-7.244,139.468,-7.215,139.458,-7.164,139.421,-7.16,139.388,-7.185,139.355,-7.204,139.237,-7.177,139.177,-7.19]]},{"n":"Dihang","z":2.1,"b":[94.356,28.003,95.418,29.876],"l":[[94.356,29.31,94.526,29.439,94.641,29.484,94.759,29.489,94.844,29.521,94.897,29.58,94.919,29.636,94.909,29.689,94.959,29.728,95.069,29.752,95.125,29.793,95.128,29.851,95.171,29.876,95.255,29.867,95.294,29.85,95.29,29.824,95.312,29.801,95.36,29.781,95.395,29.691,95.418,29.531,95.385,29.41,95.298,29.329,95.191,29.258,95.064,29.198,94.944,29.09,94.83,28.933,94.809,28.846,94.88,28.829,94.961,28.751,95.052,28.61,95.097,28.506,95.094,28.439,95.068,28.376,95.018,28.316,95.003,28.258,95.022,28.202,95.088,28.172,95.199,28.169,95.291,28.127,95.396,28.003]]},{"n":"Dnepre","z":4,"b":[33.391,54.917,33.891,55.874],"l":[[33.735,55.874,33.818,55.827,33.826,55.802,33.788,55.784,33.803,55.764,33.872,55.742,33.891,55.713,33.845,55.657,33.768,55.57,33.702,55.546,33.622,55.556,33.585,55.498,33.59,55.372,33.57,55.287,33.524,55.243,33.473,55.145,33.391,54.917]]},{"n":"Dniester","z":5,"b":[22.847,46.09,30.493,49.565],"l":[[30.478,46.101,30.493,46.09],[22.847,49.267,23.082,49.461,23.388,49.554,23.607,49.565,23.841,49.524,24.019,49.481,24.139,49.436,24.227,49.372,24.281,49.29,24.36,49.245,24.466,49.237,24.594,49.182,24.745,49.08,24.851,49.028,24.912,49.026,24.987,48.998,25.122,48.918,25.155,48.878,25.185,48.882,25.211,48.885,25.23,48.864,25.251,48.842,25.302,48.842,25.339,48.832,25.398,48.845,25.521,48.809,25.668,48.689,25.731,48.659,25.781,48.66,25.827,48.649,25.894,48.608,25.958,48.602,26.005,48.614,26.037,48.617,26.072,48.572,26.093,48.563,26.13,48.572,26.169,48.547,26.253,48.529,26.398,48.535,26.498,48.517,26.555,48.474,26.591,48.466,26.607,48.493,26.639,48.504,26.668,48.517,26.694,48.539,26.72,48.55,26.759,48.562,26.784,48.565,26.804,48.579,26.835,48.583,26.884,48.558,26.997,48.56,27.272,48.601,27.433,48.584,27.48,48.51,27.574,48.465,27.714,48.449,27.82,48.416,27.891,48.365,27.963,48.334,28.038,48.321,28.08,48.296,28.088,48.257,28.159,48.238,28.291,48.239,28.347,48.213,28.327,48.161,28.341,48.144,28.388,48.162,28.423,48.147,28.442,48.109,28.463,48.091,28.496,48.096,28.539,48.104,28.574,48.069,28.572,48.028,28.604,48.009,28.653,48.017,28.766,47.987,28.934,47.898,29.01,47.796,28.994,47.68,29.017,47.583,29.079,47.504,29.103,47.45,29.089,47.421,29.103,47.392,29.12,47.369,29.116,47.341,29.104,47.31,29.154,47.235,29.145,47.181,29.132,47.153,29.137,47.136,29.172,47.136,29.215,47.143,29.265,47.146,29.314,47.116,29.359,47.066,29.36,47.029,29.317,47.005,29.3,46.984,29.311,46.967,29.424,46.955,29.472,46.912,29.499,46.829,29.537,46.801,29.589,46.827,29.635,46.813,29.677,46.756,29.69,46.715,29.674,46.689,29.693,46.665,29.748,46.641,29.763,46.62,29.77,46.601,29.805,46.581,29.898,46.553,30.131,46.423,30.22,46.426,30.254,46.401,30.269,46.32]]},{"n":"Dnieper","z":4,"b":[30.034,46.616,35.194,55.001],"l":[[32.578,46.616,32.645,46.65,32.934,46.717,33.067,46.759,33.237,46.777,33.357,46.786,33.451,46.809,33.54,46.831],[35.105,47.84,35.13,47.972,35.118,48.038,35.077,48.081,35.087,48.108,35.149,48.12,35.185,48.15,35.194,48.199,35.17,48.28,35.113,48.394,34.959,48.477,34.819,48.492,34.708,48.53,34.41,48.64,34.225,48.736,34.065,48.808,33.717,48.942,33.508,49.008,33.363,49.065,33.194,49.094],[31.888,49.554,31.691,49.636,31.462,49.757],[31.44,50,31.244,49.979,31.071,50.023,30.856,50.126,30.707,50.222,30.625,50.312,30.567,50.417,30.515,50.597],[33.391,54.917,33.109,54.986,32.978,55.001,32.903,54.986,32.823,54.958,32.738,54.916,32.687,54.871,32.67,54.822,32.611,54.763,32.512,54.694,32.382,54.695,32.221,54.765,32.006,54.777,31.598,54.707,30.974,54.655,30.718,54.613,30.622,54.564,30.552,54.544,30.509,54.553,30.463,54.537,30.413,54.495,30.36,54.425,30.304,54.326,30.318,54.185,30.405,54.001,30.407,53.899,30.324,53.879,30.275,53.802,30.261,53.666,30.284,53.523,30.371,53.297,30.279,53.25,30.26,53.206,30.284,53.148,30.251,53.12,30.162,53.124,30.091,53.101,30.038,53.051,30.034,52.954,30.081,52.809,30.143,52.71,30.222,52.658,30.258,52.606,30.252,52.555,30.279,52.507,30.342,52.462,30.365,52.43,30.351,52.409,30.397,52.38,30.503,52.342,30.546,52.306,30.525,52.272,30.584,52.197,30.722,52.08,30.796,51.996,30.805,51.944,30.773,51.894,30.699,51.844,30.625,51.766,30.551,51.658,30.547,51.545,30.612,51.427,30.611,51.33,30.508,51.217]]},{"n":"Don","z":5,"b":[38.306,47.106,43.549,54.064],"l":[[42.11,47.602,42.015,47.56,41.853,47.564,41.755,47.59,41.593,47.599,41.407,47.599,41.295,47.564,41.184,47.547,41.073,47.549,41.009,47.567,40.993,47.601,40.97,47.61,40.926,47.586,40.588,47.491,40.412,47.401,40.283,47.284,40.112,47.222,39.9,47.216,39.689,47.187,39.378,47.111,39.293,47.106],[43.549,49.507,43.379,49.487,43.316,49.456,43.302,49.408,43.252,49.365,43.165,49.329,43.095,49.324,43.042,49.351,43.016,49.38,43.017,49.412,42.957,49.442,42.835,49.472,42.797,49.516,42.77,49.573,42.731,49.582,42.647,49.567,42.446,49.579,42.297,49.599,42.006,49.61,41.833,49.597,41.713,49.616,41.467,49.613,41.38,49.588,41.314,49.587,41.27,49.609,41.261,49.63,41.288,49.648,41.279,49.683,41.235,49.734,41.14,49.77,40.997,49.791,40.921,49.827,40.914,49.879,40.89,49.922,40.849,49.956,40.778,49.964,40.677,49.945,40.644,49.953,40.621,49.977,40.596,50.027,40.502,50.12,40.432,50.139,40.386,50.083,40.322,50.065,40.198,50.096,40.04,50.103,40.001,50.133,40.03,50.186,40.027,50.238,39.99,50.289,40.002,50.346,40.064,50.408,40.036,50.475,39.92,50.547,39.907,50.617,39.879,50.65,39.794,50.692,39.757,50.733,39.768,50.775,39.794,50.797,39.814,50.833,39.789,50.874,39.775,50.919,39.778,50.952,39.745,50.962,39.677,50.955,39.513,50.96,39.266,50.987,39.143,51.024,39.144,51.071,39.182,51.111,39.256,51.142,39.262,51.173,39.199,51.203,39.177,51.238,39.16,51.294,39.131,51.364,39.052,51.43,39.052,51.477,39.059,51.507,39.037,51.54,39.018,51.568,39.012,51.597,39.038,51.626,39.046,51.657,39.035,51.69,39.06,51.757,39.122,51.859,39.14,51.934,39.133,52.012],[39.133,52.019,39.152,52.052,39.164,52.084,39.154,52.125,39.096,52.179,38.99,52.248,38.958,52.289,38.958,52.327,38.905,52.438,38.931,52.576,38.933,52.665,38.911,52.703,38.964,52.794,39.093,52.938,39.155,53.118,39.152,53.333,39.112,53.449,39.056,53.519,39.021,53.561,38.998,53.576,38.871,53.618,38.641,53.687,38.531,53.754,38.541,53.819,38.529,53.861,38.478,53.889,38.306,54.064]]},{"n":"Danube","z":2.1,"b":[8.22,47.931,17.206,49.027],"l":[[8.22,48.047,8.49,47.931,8.696,47.936,8.836,47.976,8.979,48.053,9.132,48.086,9.295,48.078,9.421,48.116,9.509,48.202,9.589,48.244,9.661,48.241,9.784,48.289,9.959,48.388,10.127,48.453,10.289,48.483,10.385,48.516,10.415,48.552,10.524,48.607,10.712,48.679,11.025,48.735,11.684,48.794,11.766,48.871,11.85,48.906,11.964,48.925,12.024,48.958,12.033,49.027,12.242,49.018,12.361,48.992,12.507,48.924],[17.206,48.061,17.165,48.117,17.111,48.146,17.029,48.167,16.878,48.172,16.659,48.161,16.486,48.213,16.36,48.327,16.209,48.38,16.034,48.369,15.861,48.379,15.693,48.411,15.573,48.424,15.502,48.417,15.431,48.371,15.36,48.286,15.265,48.229,15.147,48.198,15.036,48.198,14.932,48.227,14.829,48.227,14.726,48.197,14.583,48.219,14.4,48.292,14.243,48.328,14.114,48.329,14.037,48.352,14.012,48.399,13.872,48.462,13.487,48.582,13.316,48.602,13.2,48.636,13.081,48.691,13.005,48.748,12.972,48.806,12.909,48.836,12.816,48.836,12.704,48.86,12.507,48.931]]},{"n":"Double Mountain Fork Brazos","z":5,"b":[-101.667,32.905,-100.501,33.54],"l":[[-101.667,33.54,-101.426,33.4,-101.323,33.266,-101.253,33.201,-101.18,33.16,-101.107,33.14,-100.995,33.14,-100.832,33.103,-100.709,33.044,-100.501,32.905]]},{"n":"Drava","z":5,"b":[13.822,45.558,18.954,47.402],"l":[[13.822,47.09,14.367,47.147,14.507,47.209,14.62,47.224,14.752,47.214,14.861,47.245,14.946,47.319,15.062,47.371,15.145,47.4,15.208,47.402,15.303,47.394,15.346,47.347,15.352,47.316,15.323,47.3,15.316,47.253,15.329,47.174,15.361,47.121,15.413,47.095,15.476,46.997,15.56,46.831,15.582,46.767,15.63,46.736,15.701,46.723,16.042,46.663,16.211,46.57,16.321,46.535,16.428,46.524,16.57,46.485,16.748,46.416,16.871,46.339,16.94,46.254,17.033,46.187,17.15,46.14,17.242,46.077,17.311,45.996,17.406,45.951,17.529,45.941,17.607,45.914,17.64,45.868,17.706,45.827,17.807,45.79,17.964,45.77,18.283,45.765,18.551,45.627,18.712,45.574,18.954,45.558]]},{"n":"Duero","z":5,"b":[-8.675,41.062,-2.357,41.928],"l":[[-2.837,41.928,-2.542,41.85,-2.444,41.797,-2.445,41.743,-2.424,41.688,-2.357,41.605,-2.498,41.521,-2.617,41.505,-2.76,41.528,-2.866,41.529,-2.935,41.508,-3.038,41.524,-3.176,41.578,-3.411,41.633,-3.743,41.688,-3.941,41.693,-4.002,41.649,-4.146,41.631,-4.372,41.64,-4.682,41.592,-5.075,41.489,-5.307,41.464,-5.378,41.516,-5.518,41.527,-5.726,41.498,-5.838,41.492,-5.888,41.474,-5.971,41.49,-6.092,41.543,-6.178,41.55,-6.244,41.516,-6.289,41.455,-6.403,41.375,-6.566,41.304,-6.69,41.214,-6.776,41.108,-6.883,41.062,-7.011,41.079,-7.085,41.113,-7.103,41.166,-7.244,41.189,-7.651,41.181,-8.235,41.092,-8.425,41.084,-8.555,41.128,-8.675,41.154]]},{"n":"Ebro","z":4.7,"b":[-4.189,40.722,0.891,43.011],"l":[[-4.189,43.011,-4.102,43.001,-4.055,42.953,-4.017,42.861,-3.963,42.825,-3.89,42.844,-3.822,42.842,-3.757,42.817,-3.709,42.833,-3.678,42.888,-3.627,42.898,-3.522,42.845,-3.451,42.807,-3.337,42.792,-3.155,42.787,-3.011,42.755,-2.905,42.695,-2.843,42.644,-2.823,42.602,-2.756,42.56,-2.641,42.518,-2.563,42.507,-2.522,42.527,-2.395,42.514,-2.183,42.466,-1.994,42.391,-1.748,42.236,-1.405,41.978,-1.11,41.802,-0.748,41.622,-0.526,41.479,-0.444,41.374,-0.301,41.309,-0.097,41.284,0.04,41.306,0.111,41.375,0.167,41.411,0.208,41.413,0.263,41.399,0.331,41.368,0.362,41.328,0.357,41.28,0.364,41.256,0.395,41.254,0.525,41.249,0.59,41.22,0.632,41.165,0.652,41.119,0.652,41.083,0.614,41.038,0.538,40.982,0.517,40.904,0.551,40.802,0.651,40.745,0.891,40.722]]},{"n":"White Nile","z":2,"b":[31.125,9.354,32.796,15.635],"l":[[31.125,9.433,31.346,9.366,31.453,9.354,31.518,9.376,31.581,9.445,31.64,9.562,31.746,9.657,31.899,9.731,32.036,9.834,32.158,9.968,32.235,10.072,32.267,10.148,32.27,10.246,32.244,10.369,32.201,10.435,32.139,10.446,32.108,10.471,32.106,10.51,32.24,10.656,32.511,10.908,32.645,11.103,32.642,11.24,32.673,11.414,32.737,11.624,32.764,11.795,32.749,11.995,32.73,12.19,32.743,12.333,32.783,12.489,32.791,12.652,32.765,12.822,32.767,12.939,32.796,13.004,32.752,13.117,32.635,13.277,32.586,13.424,32.604,13.559,32.587,13.638,32.535,13.662,32.5,13.704,32.481,13.764,32.439,13.825,32.373,13.886,32.295,14.032,32.205,14.264,32.172,14.465,32.196,14.637,32.262,14.783,32.37,14.902,32.44,15.13,32.49,15.635]]},{"n":"Blue Nile","z":3,"b":[32.49,10.155,35.353,15.635],"l":[[35.353,10.155,35.343,10.187,35.293,10.411,35.242,10.517,35.147,10.573,35.182,10.974,35.161,11.14,35.097,11.202,34.993,11.249,34.778,11.296],[34.38,11.783,34.324,12.048,34.317,12.194,34.341,12.308,34.301,12.403,34.199,12.478,34.157,12.534,34.176,12.572,34.175,12.637,34.154,12.729,34.117,12.804,34.065,12.863,34.04,12.872,34,12.902,33.981,12.958,33.961,13.144,33.93,13.258,33.89,13.3,33.828,13.325,33.744,13.334,33.692,13.381,33.672,13.466,33.639,13.53,33.593,13.573,33.582,13.619,33.607,13.669,33.602,13.708,33.578,13.734,33.581,13.756,33.597,13.772,33.599,13.793,33.58,13.819,33.574,13.839,33.581,13.856,33.596,13.867,33.604,13.887,33.591,13.937,33.609,14.011,33.657,14.109,33.566,14.344,33.337,14.716,33.211,14.956,33.189,15.064,33.134,15.16,33.046,15.244,32.949,15.304,32.842,15.339,32.735,15.423,32.628,15.553,32.554,15.622,32.49,15.635]]},{"n":"Elbe","z":4.7,"b":[9.784,50.031,15.889,53.555],"l":[[15.583,50.693,15.615,50.614,15.653,50.576,15.706,50.553,15.734,50.522,15.736,50.484,15.782,50.44,15.864,50.394,15.884,50.372,15.889,50.328,15.845,50.272,15.819,50.197,15.817,50.107,15.677,50.052,15.4,50.031,15.236,50.035,15.159,50.075,15.088,50.152,14.969,50.179,14.779,50.181,14.631,50.222,14.473,50.343,14.388,50.438,14.332,50.46,14.277,50.442,14.241,50.454,14.222,50.498,14.178,50.52,14.109,50.52,14.062,50.535,14.039,50.567,14.04,50.589,14.063,50.602,14.067,50.619,14.07,50.641,14.084,50.661,14.16,50.686,14.203,50.75,14.212,50.851,14.136,50.918,13.977,50.95,13.874,50.991,13.828,51.041,13.744,51.079,13.622,51.104,13.522,51.141,13.443,51.19,13.398,51.235,13.386,51.278,13.345,51.312,13.275,51.339,13.226,51.382,13.184,51.474],[13.191,51.474,12.934,51.623,12.842,51.711,12.829,51.79,12.742,51.843,12.58,51.871,12.382,51.881,12.147,51.874,11.995,51.909,11.925,51.986,11.843,52.031,11.749,52.046,11.691,52.072,11.668,52.109,11.668,52.155,11.692,52.21,11.757,52.273,11.917,52.378,11.978,52.438,12.003,52.562,12.014,52.77,11.921,52.918,11.724,53.006,11.555,53.054,11.414,53.062,11.297,53.093,11.203,53.147,11.139,53.166,11.106,53.151,10.994,53.2,10.804,53.312,10.682,53.369,10.601,53.372,10.413,53.417,10.305,53.426,10.215,53.413,10.082,53.44],[10.082,53.44,9.819,53.54,9.784,53.555,9.9,53.552,9.988,53.538,10.048,53.516,10.082,53.44]]},{"n":"Ertis","z":2.1,"b":[74.994,47.727,85.267,53.986],"l":[[85.267,47.994,84.999,47.928,84.88,47.88,84.822,47.828,84.729,47.783,84.6,47.744,84.491,47.727,84.357,47.732],[83.364,49.644,83.128,49.682,82.991,49.748,82.872,49.856,82.696,49.947,82.462,50.021,82.321,50.101,82.273,50.185,82.15,50.219,81.953,50.202,81.775,50.228,81.614,50.295,81.482,50.325,81.379,50.319,81.248,50.338,81.089,50.382,80.963,50.392,80.87,50.367,80.691,50.36,80.426,50.373,80.242,50.41,80.139,50.473,80.019,50.496,79.882,50.479,79.702,50.517,79.479,50.608,79.313,50.662,79.203,50.678,79.116,50.667,79.05,50.63,78.9,50.651,78.545,50.767,78.348,50.858,78.245,50.923,78.171,50.993,78.106,51.094,78.05,51.225,77.96,51.335,77.837,51.424,77.691,51.499,77.521,51.56,77.441,51.651,77.451,51.771,77.334,51.886,77.089,51.997,76.96,52.108,76.948,52.219,76.9,52.303,76.814,52.36,76.78,52.411,76.796,52.455,76.794,52.502,76.775,52.552,76.734,52.589,76.673,52.612,76.654,52.64,76.678,52.672,76.679,52.704,76.659,52.736,76.55,52.816,76.514,52.827,76.494,52.83,76.479,52.85,76.485,52.885,76.461,52.899,76.314,52.905,76.26,52.93,76.242,52.98,76.144,53.015,75.967,53.034,75.855,53.078,75.806,53.146,75.727,53.202,75.619,53.246,75.568,53.282,75.567,53.304,75.545,53.322,75.521,53.333,75.503,53.344,75.506,53.361,75.503,53.397,75.457,53.455,75.357,53.52,75.203,53.59,75.101,53.654,75.05,53.71,75.04,53.789,75.07,53.891,75.062,53.953,74.994,53.986]]},{"n":"Irtysh","z":2.1,"b":[85.267,46.99,90.325,48.019],"l":[[90.325,47.65,90.284,47.491,90.253,47.43,90.201,47.408,90.132,47.401,90.064,47.356,89.973,47.269,89.872,47.213,89.763,47.187,89.655,47.126,89.546,47.03,89.406,46.99,89.233,47.005,89.032,47.061,88.804,47.159,88.515,47.233,88.166,47.284,87.847,47.348,87.559,47.427,87.387,47.495,87.331,47.555,87.266,47.58,87.19,47.572,87.1,47.591,86.944,47.66,86.607,47.832,86.433,47.878,86.31,47.853,86.154,47.876,85.966,47.945,85.792,47.98,85.634,47.982,85.544,47.994,85.521,48.016,85.449,48.019,85.267,47.994]]},{"n":"Euphrates","z":3,"b":[40.993,30.902,47.468,34.509],"l":[[45.463,31.305,45.541,31.229,45.685,31.184,45.922,31.146,46.121,31.086,46.363,30.965,46.483,30.903,46.58,30.902,46.692,30.942,46.886,30.959,47.16,30.953,47.331,30.967,47.399,31,47.442,31.004,47.468,30.968],[40.993,34.428,41.119,34.388,41.177,34.393,41.208,34.43,41.276,34.443,41.38,34.43,41.43,34.436,41.448,34.44,41.471,34.449,41.502,34.436,41.591,34.448,41.717,34.497,41.827,34.509,41.921,34.484,41.99,34.446,42.055,34.369,42.122,34.302,42.17,34.288,42.22,34.306,42.248,34.297,42.263,34.29,42.286,34.292,42.303,34.334,42.33,34.346,42.366,34.329,42.366,34.302,42.331,34.264,42.332,34.231,42.369,34.205,42.383,34.165,42.375,34.114,42.417,34.059,42.447,34.041,42.51,34.001,42.551,33.944,42.541,33.888,42.563,33.87,42.617,33.891,42.673,33.89,42.73,33.865,42.748,33.82,42.724,33.752,42.767,33.671,42.877,33.577,42.965,33.521,43.032,33.503,43.079,33.505,43.106,33.526,43.155,33.515,43.228,33.471,43.288,33.455,43.334,33.466,43.454,33.442,43.536,33.418,43.65,33.384,43.776,33.311,43.832,33.226,43.872,33.199,43.905,33.199,43.948,33.195,44.029,33.16,44.122,33.053,44.254,32.828,44.263,32.793,44.264,32.727,44.237,32.624,44.255,32.496,44.321,32.349,44.355,32.235,44.356,32.157,44.387,32.072,44.448,31.981,44.477,31.878,44.475,31.762,44.548,31.648,44.77,31.479,45.042,31.441,45.158,31.403,45.209,31.352,45.293,31.321,45.467,31.305]]},{"n":"Ferenc Csatorna","z":5,"b":[18.839,45.836,18.92,46.267],"l":[[18.92,46.267,18.909,45.95,18.905,45.932,18.894,45.866,18.839,45.836]]},{"n":"Finlay","z":5,"b":[-127.235,56.757,-124.836,57.674],"l":[[-127.235,56.815,-126.926,56.916,-126.915,57.082,-126.822,57.19,-126.645,57.296,-126.551,57.415,-126.542,57.546,-126.518,57.627,-126.478,57.659,-126.403,57.674,-126.294,57.674,-126.167,57.622,-126.022,57.516,-125.845,57.439,-125.634,57.391,-125.393,57.261,-125.12,57.049,-124.946,56.897,-124.836,56.757]]},{"n":"Firat","z":3,"b":[37.848,36.836,41.455,40.18],"l":[[38.768,38.813,38.555,38.768,38.45,38.714,38.383,38.635,38.355,38.581,38.367,38.551,38.452,38.508,38.61,38.45,38.719,38.426,38.78,38.435,38.847,38.413,38.921,38.358,38.997,38.334,39.075,38.34,39.115,38.306,39.115,38.23,39.149,38.19,39.218,38.183,39.248,38.154,39.239,38.102,39.171,38.034,39.042,37.949,38.978,37.887,38.978,37.849,38.956,37.82,38.949,37.797,38.924,37.745,38.841,37.713,38.829,37.69,38.838,37.666,38.813,37.645,38.701,37.631,38.608,37.593,38.533,37.532,38.439,37.495,38.328,37.483,38.25,37.461,38.207,37.429,38.137,37.421,38.039,37.437,37.966,37.425,37.92,37.384,37.855,37.292,37.848,37.217,37.862,37.116,37.897,37.061,37.951,37.054,37.991,36.996,38.026,36.836],[41.455,40.18,41.328,40.016,40.985,39.944,40.77,39.928,40.569,39.945,40.396,39.942,40.251,39.919,40.169,39.895,40.15,39.869,40.17,39.824,40.23,39.76,40.24,39.694,40.203,39.626,40.163,39.584,40.122,39.568,39.939,39.595,39.613,39.666,39.415,39.683,39.343,39.648,39.196,39.619,38.976,39.594,38.74,39.536,38.488,39.445,38.426,39.342,38.555,39.229,38.643,39.17,38.689,39.167,38.718,39.144,38.74,39.078]]},{"n":"Fitzroy","z":5,"b":[123.563,-18.492,127.152,-17.383],"l":[[127.152,-17.383,126.998,-17.454,126.903,-17.478,126.775,-17.478,126.4,-17.458,126.237,-17.48,126.162,-17.538,126.124,-17.582,126.124,-17.613,126.098,-17.633,126.045,-17.641,125.985,-17.707,125.918,-17.83,125.792,-17.988,125.511,-18.277,125.139,-18.452,124.923,-18.492,124.736,-18.456,124.581,-18.353,124.456,-18.183,124.351,-18.09,124.267,-18.075,124.178,-18.085,124.087,-18.119,124,-18.116,123.918,-18.075,123.817,-17.967,123.698,-17.793,123.622,-17.701,123.588,-17.691,123.57,-17.661,123.568,-17.588,123.563,-17.521]]},{"n":"Fraser","z":4.7,"b":[-123.08,49.131,-118.319,54.245],"l":[[-118.319,52.522,-118.572,52.762,-118.601,52.825,-118.721,52.891,-118.941,52.983,-119.175,53.023,-119.425,53.009,-119.669,53.056,-119.906,53.161,-120.244,53.346,-120.681,53.609,-121.024,53.778,-121.396,53.889,-121.584,53.93,-121.649,53.963,-121.654,54.002,-121.738,54.061,-121.903,54.141,-122.089,54.201,-122.295,54.24,-122.435,54.245,-122.509,54.215,-122.551,54.174,-122.56,54.121,-122.611,54.046,-122.704,53.947,-122.744,53.816,-122.733,53.652,-122.708,53.534,-122.67,53.461,-122.709,53.399,-122.827,53.348,-122.804,53.266,-122.64,53.153,-122.53,53.042,-122.475,52.932,-122.457,52.798,-122.475,52.642,-122.436,52.508,-122.34,52.395,-122.285,52.259,-122.27,52.097,-122.272,51.99,-122.29,51.937,-122.321,51.902,-122.365,51.884,-122.392,51.846,-122.408,51.758,-122.268,51.519,-122.225,51.407,-122.232,51.345,-122.143,51.221,-121.955,51.038,-121.858,50.901,-121.85,50.812,-121.866,50.766,-121.906,50.763,-121.924,50.74,-121.92,50.698,-121.86,50.615,-121.744,50.491,-121.628,50.302,-121.51,50.047,-121.449,49.791,-121.445,49.532,-121.49,49.387,-121.582,49.354,-121.652,49.307,-121.698,49.246,-121.874,49.19,-122.179,49.139,-122.445,49.141,-122.673,49.197,-122.85,49.207,-123.041,49.151,-123.08,49.131]]},{"n":"Gan","z":5,"b":[114.412,24.707,116.016,29.279],"l":[[114.412,24.707,114.48,24.708,114.53,24.735,114.614,24.816,114.73,24.882,114.762,24.953,114.748,25.051,114.783,25.113,114.865,25.137,114.899,25.196,114.885,25.29,114.93,25.43,115.033,25.614,115.09,25.746,115.101,25.826,115.068,25.864,114.992,25.86,114.94,25.882,114.913,25.93,114.909,25.992,114.938,26.105,114.904,26.125,114.908,26.153,114.939,26.194,114.946,26.237,114.931,26.283,114.862,26.369,114.738,26.497,114.717,26.614,114.797,26.722,114.879,26.789,114.961,26.813,114.995,26.849,114.98,26.897,114.988,26.942,115.021,26.985,115.021,27.032,114.99,27.083,115.009,27.138,115.076,27.198,115.098,27.284,115.075,27.396,115.13,27.512,115.264,27.634,115.369,27.785,115.445,27.966,115.577,28.11,115.765,28.218,115.848,28.317,115.818,28.452,115.816,28.585,115.869,28.716,115.975,28.888,116.016,29.05,115.982,29.279]]},{"n":"Ganges","z":3,"b":[78.022,21.721,90.435,30.888],"l":[[89.483,22.276,89.425,22.496,89.372,22.581,89.3,22.619],[89.3,22.619,89.261,22.574,89.255,22.445,89.286,22.308,89.354,22.16,89.379,22.058,89.352,21.972,89.351,21.886,89.336,21.788,89.354,21.721],[88.44,22.986,88.385,22.868,88.347,22.735,88.344,22.629,88.29,22.549,88.185,22.496,88.118,22.41,88.087,22.218],[88.44,22.986,88.518,22.975,88.598,22.975,88.682,22.954,88.756,22.915,88.82,22.858,88.843,22.803,88.823,22.748,88.845,22.7,88.91,22.659,88.971,22.511,89.05,22.275,89.056,22.186,89.051,22.093],[90.074,23.573,90.096,23.34,90.127,23.252,90.181,23.206,90.228,23.178,90.244,23.138,90.265,23.116,90.299,23.088,90.302,23.055,90.315,23.016,90.362,22.961,90.361,22.876,90.394,22.775,90.435,22.752],[89.109,23.992,89.219,23.808,89.302,23.733,89.381,23.694,89.447,23.64,89.5,23.572,89.52,23.525,89.509,23.499,89.527,23.483,89.575,23.477,89.598,23.443,89.598,23.383,89.615,23.337,89.632,23.301,89.638,23.252,89.66,23.223,89.709,23.192,89.719,23.133,89.765,23.02,89.926,22.84,90.008,22.685,90.014,22.563,89.985,22.466],[89.109,23.992,89.2,23.933,89.281,23.931,89.38,23.901,89.497,23.844,89.604,23.823,89.749,23.848],[88.397,24.389,88.51,24.261,88.534,24.208,88.527,24.166,88.554,24.129,88.616,24.098,88.643,24.059,88.636,24.012,88.69,23.968,88.805,23.928,88.864,23.856,88.868,23.752,88.837,23.666,88.772,23.598,88.764,23.531,88.811,23.463,88.853,23.441,88.885,23.423,88.906,23.39,88.926,23.363,88.989,23.333,89.002,23.263,89.028,23.186,89.089,23.113,89.113,23.04,89.1,22.97,89.113,22.893,89.154,22.809,89.208,22.749,89.268,22.708,89.276,22.703,89.271,22.679,89.3,22.619],[88.397,24.389,88.499,24.347,88.642,24.326,88.724,24.275,88.742,24.193,88.77,24.144,88.806,24.127,88.858,24.131,88.926,24.155,89.01,24.109,89.109,23.992],[87.92,24.825,87.94,24.666,88.019,24.543,88.175,24.389,88.238,24.234,88.209,24.079,88.205,23.945,88.227,23.831,88.215,23.74,88.183,23.7,88.214,23.637,88.272,23.587,88.304,23.543,88.345,23.53,88.361,23.461,88.352,23.333,88.375,23.255,88.43,23.227,88.467,23.186,88.486,23.134,88.459,23.027,88.44,22.986],[87.92,24.825,87.946,24.753,87.999,24.661,88.079,24.55,88.145,24.486,88.225,24.461,88.287,24.48,88.338,24.454,88.397,24.389],[79.841,30.888,79.82,30.837,79.829,30.787,79.859,30.722,79.865,30.679,79.834,30.648,79.745,30.543,79.667,30.522,79.571,30.55,79.47,30.516,79.313,30.372,79.232,30.301,79.159,30.285,79.067,30.3,78.939,30.292,78.777,30.26,78.669,30.201,78.614,30.116,78.52,30.09,78.387,30.124,78.281,30.093,78.203,29.998,78.17,29.889,78.183,29.768,78.139,29.673,78.04,29.606,78.022,29.492,78.086,29.332,78.106,29.181,78.084,29.04,78.098,28.882,78.176,28.621,78.242,28.522,78.271,28.443,78.284,28.352,78.337,28.253,78.43,28.146,78.603,28.026,78.855,27.893,79.025,27.825,79.112,27.82,79.167,27.786,79.192,27.723,79.255,27.68,79.358,27.658,79.446,27.62,79.519,27.568,79.591,27.466,79.662,27.316,79.733,27.217,79.838,27.145,79.922,27.126,79.998,27.025,80.094,26.837,80.238,26.648,80.43,26.459,80.544,26.317,80.58,26.222,80.652,26.138,80.76,26.066,80.869,26.038,80.978,26.053,81.123,25.989,81.303,25.844,81.395,25.746,81.398,25.692,81.458,25.646,81.573,25.606,81.649,25.564,81.686,25.518,81.756,25.499,81.857,25.507,81.907,25.487,81.903,25.413,82.042,25.312,82.12,25.285,82.183,25.296,82.215,25.272,82.217,25.259,82.226,25.243,82.239,25.241,82.254,25.246,82.274,25.262,82.3,25.26,82.318,25.23,82.345,25.227,82.38,25.249,82.436,25.235,82.513,25.184,82.552,25.184,82.563,25.203,82.585,25.218,82.62,25.22,82.786,25.144,82.879,25.142,82.965,25.188,83.017,25.239,83.033,25.295,83.074,25.33,83.139,25.344,83.159,25.377,83.135,25.427,83.139,25.472,83.17,25.51,83.242,25.519,83.353,25.498,83.44,25.466,83.489,25.444,83.513,25.462,83.513,25.497,83.518,25.534,83.6,25.584,83.67,25.607,83.713,25.59,83.752,25.542,83.803,25.519,83.866,25.522,83.959,25.577,84.082,25.683,84.171,25.722,84.226,25.694,84.284,25.701,84.343,25.744,84.428,25.747,84.54,25.711,84.634,25.708,84.711,25.738,84.827,25.747,84.981,25.734,85.119,25.683,85.239,25.592,85.338,25.544,85.412,25.535,85.456,25.538,85.501,25.545,85.555,25.544,85.618,25.494,85.697,25.474,85.786,25.495,85.88,25.462,85.979,25.377,86.098,25.324,86.237,25.303,86.371,25.341,86.5,25.437,86.571,25.448,86.584,25.373,86.617,25.315,86.668,25.276,86.743,25.28,86.841,25.327,86.965,25.32,87.115,25.26,87.219,25.267,87.275,25.34,87.35,25.383,87.444,25.394,87.518,25.366,87.572,25.3,87.644,25.257,87.734,25.237,87.779,25.193,87.78,25.124,87.824,25.064,87.911,25.013,87.942,24.956,87.919,24.893,87.92,24.825]]},{"n":"Ariège","z":5,"b":[1.454,42.627,1.825,43.415],"l":[[1.809,42.627,1.825,42.693,1.791,42.732,1.675,42.783,1.616,42.856,1.594,42.97,1.587,43.149,1.54,43.298,1.454,43.415]]},{"n":"Genala","z":5,"b":[38.724,4.324,41.966,6.605],"l":[[38.774,6.605,38.724,6.541,38.76,6.453,38.92,6.253,38.991,6.126,39.11,6.054,39.357,5.981,39.388,5.969,39.407,5.952,39.478,5.795,39.559,5.724,39.654,5.709,39.701,5.658,39.699,5.572,39.736,5.526,39.81,5.521,39.88,5.488,39.946,5.426,40.031,5.385,40.135,5.365,40.273,5.37,40.443,5.4,40.627,5.487,40.824,5.629,40.954,5.702,41.016,5.708,41.073,5.693,41.124,5.66,41.196,5.526,41.288,5.291,41.394,5.09,41.515,4.922,41.579,4.801,41.587,4.728,41.685,4.599,41.966,4.324]]},{"n":"Gharraf Canal","z":4,"b":[45.809,30.964,46.405,32.502],"l":[[45.809,32.502,45.901,32.431,45.928,32.377,45.923,32.316,45.943,32.264,45.989,32.219,45.998,32.133,45.97,32.005,45.986,31.922,46.046,31.883,46.089,31.773,46.116,31.592,46.154,31.457,46.203,31.367,46.276,31.282,46.371,31.202,46.405,31.112,46.366,30.964]]},{"n":"Gila","z":5,"b":[-114.582,32.657,-108.114,33.577],"l":[[-111.249,33.104,-111.455,33.04,-111.648,33.054,-111.899,33.126,-112.122,33.222,-112.32,33.342,-112.511,33.372,-112.697,33.311,-112.767,33.238,-112.72,33.154,-112.704,33.087,-112.717,33.036,-112.809,33.03,-112.98,33.067,-113.084,33.067,-113.12,33.03,-113.197,33.001,-113.317,32.979,-113.525,32.906,-113.824,32.782,-114.005,32.725,-114.068,32.735,-114.13,32.725,-114.19,32.696,-114.261,32.709,-114.344,32.763,-114.428,32.776,-114.582,32.735],[-108.204,33.577,-108.114,33.413,-108.246,33.176,-108.348,33.091,-108.464,33.079,-108.555,32.992,-108.62,32.828,-108.71,32.718,-108.825,32.659,-108.944,32.657,-109.067,32.711,-109.156,32.786,-109.209,32.881,-109.264,32.94,-109.321,32.962,-109.397,32.943,-109.492,32.884,-109.591,32.853,-109.695,32.85,-109.846,32.932,-110.042,33.099,-110.226,33.186,-110.398,33.193,-110.554,33.15,-110.694,33.056,-110.814,33.038,-110.916,33.095,-111.039,33.121,-111.256,33.112]]},{"n":"Glåma","z":5,"b":[10.624,60.525,12.028,62.687],"l":[[11.484,62.687,11.188,62.496,11.085,62.443,10.984,62.391,10.773,62.309,10.655,62.245,10.632,62.201,10.624,62.157,10.634,62.112,10.699,62.062,10.821,62.006,10.888,61.95,10.901,61.893,10.883,61.835,10.835,61.776,10.863,61.7,10.968,61.607,11.033,61.526,11.072,61.423,11.247,61.317,11.318,61.233,11.344,61.134,11.402,61.029,11.493,60.919,11.579,60.849,11.661,60.818,11.737,60.763,11.806,60.685,11.881,60.633,11.962,60.609,12.009,60.579,12.028,60.525]]},{"n":"Godävari","z":4.7,"b":[73.652,16.334,82.339,20.036],"l":[[81.739,16.942,81.833,16.669,81.856,16.553,81.839,16.503,81.8,16.465,81.736,16.441,81.706,16.4,81.712,16.334],[81.739,16.942,81.888,16.777,82.034,16.713,82.339,16.707],[73.652,20.013,73.889,19.99,73.99,19.999,74.035,20.031,74.084,20.036,74.163,20.003,74.422,19.918,74.589,19.835,74.749,19.726,74.917,19.652,75.093,19.613,75.197,19.579,75.247,19.533,75.473,19.425,75.591,19.38,75.678,19.362,75.769,19.361,75.846,19.376,75.905,19.363,75.917,19.326,75.941,19.314,75.987,19.333,76.011,19.326,76.027,19.31,76.092,19.291,76.319,19.257,76.347,19.241,76.353,19.202,76.337,19.14,76.347,19.124,76.368,19.12,76.396,19.111,76.427,19.077,76.473,19.073,76.535,19.099,76.582,19.086,76.614,19.035,76.657,19.002,76.71,18.986,76.748,18.996,76.77,19.034,76.856,19.07,77.005,19.105,77.093,19.108,77.12,19.081,77.196,19.089,77.319,19.134,77.405,19.12,77.453,19.048,77.579,18.966,77.784,18.873,77.968,18.87,78.133,18.958,78.235,18.984,78.275,18.948,78.319,18.946,78.367,18.976,78.429,18.98,78.504,18.956,78.575,18.966,78.643,19.012,78.701,19.027,78.751,19.014,78.835,19.026,78.953,19.063,79.05,19.033,79.125,18.937,79.201,18.871,79.317,18.816,79.401,18.843,79.496,18.809,79.631,18.723,79.727,18.69,79.785,18.711,79.822,18.762,79.874,18.802,79.925,18.807,80.029,18.744,80.142,18.702,80.244,18.709,80.319,18.686,80.366,18.632,80.39,18.573,80.389,18.509,80.408,18.463,80.447,18.434,80.473,18.388,80.485,18.324,80.541,18.261,80.688,18.166,80.84,17.996,80.892,17.865,80.895,17.716,80.986,17.622,81.165,17.582,81.267,17.543,81.291,17.506,81.352,17.482,81.45,17.472,81.528,17.448,81.587,17.41,81.647,17.279,81.739,16.942]]},{"n":"Grande","z":3,"b":[-66.323,-22.155,-44.058,-15.86],"l":[[-44.45,-22.155,-44.304,-22.047,-44.208,-21.948,-44.112,-21.822,-44.062,-21.734,-44.058,-21.684,-44.068,-21.658,-44.093,-21.657,-44.127,-21.613,-44.171,-21.524,-44.241,-21.476,-44.387,-21.465],[-44.616,-21.318,-44.686,-21.236,-44.739,-21.206,-44.799,-21.201,-44.857,-21.176,-44.915,-21.13,-44.971,-21.125,-45.026,-21.161,-45.076,-21.175,-45.122,-21.169,-45.164,-21.14,-45.205,-21.093],[-46.326,-20.668,-46.587,-20.623],[-47.073,-20.284,-47.329,-20.118,-47.428,-20.036,-47.46,-20.001,-47.518,-19.994,-47.594,-20.019,-47.656,-20.023,-47.696,-19.991,-47.75,-19.977,-47.819,-19.981,-47.86,-20.012,-47.862,-20.056,-47.909,-20.082,-47.991,-20.083,-48.051,-20.12,-48.109,-20.117,-48.178,-20.083,-48.253,-20.093,-48.361,-20.139,-48.716,-20.144,-48.812,-20.159,-48.858,-20.233,-48.887,-20.37,-48.918,-20.429,-48.952,-20.41,-48.978,-20.338,-48.995,-20.214,-49.04,-20.187,-49.113,-20.259,-49.166,-20.295,-49.2,-20.294,-49.239,-20.246,-49.284,-20.149,-49.29,-20.072,-49.259,-20.017,-49.278,-19.987,-49.385,-19.98],[-50.364,-19.859,-50.434,-19.8,-50.489,-19.791,-50.584,-19.821],[-63.757,-19.028,-63.619,-19.071,-63.565,-19.049,-63.548,-18.977,-63.497,-18.928,-63.411,-18.904,-63.31,-18.847,-63.195,-18.756,-63.074,-18.615,-62.949,-18.425,-62.858,-18.197,-62.773,-17.799,-62.786,-17.598,-62.775,-17.465,-62.743,-17.334,-62.738,-17.243,-62.762,-17.192,-62.823,-17.151,-62.922,-17.121,-63,-17.031,-63.059,-16.88,-63.259,-16.732,-63.601,-16.585,-63.804,-16.449,-63.869,-16.322,-63.965,-16.237,-64.092,-16.192,-64.162,-16.148,-64.176,-16.104,-64.303,-16.027,-64.664,-15.86],[-66.323,-17.577,-66.019,-17.862,-65.96,-17.952,-65.916,-17.978,-65.868,-17.972,-65.793,-18.009,-65.642,-18.129,-65.39,-18.371,-65.287,-18.493,-65.25,-18.575,-65.19,-18.626,-65.108,-18.645,-65.051,-18.63,-65.018,-18.581,-64.956,-18.538,-64.863,-18.503,-64.756,-18.53,-64.635,-18.621,-64.529,-18.651,-64.437,-18.618,-64.342,-18.686,-64.242,-18.855,-64.16,-18.954,-64.097,-18.985,-64.05,-19.042,-64.021,-19.126,-63.985,-19.165,-63.94,-19.158,-63.906,-19.122,-63.884,-19.058,-63.842,-19.027,-63.751,-19.028]]},{"n":"Great Pee Dee","z":5,"b":[-80.481,33.405,-79.106,36.232],"l":[[-80.464,36.232,-80.456,36.206,-80.481,36.093,-80.459,36.011,-80.392,35.943,-80.365,35.893,-80.378,35.86,-80.406,35.843,-80.447,35.841,-80.465,35.817,-80.46,35.773,-80.423,35.742,-80.354,35.724,-80.27,35.652,-80.122,35.466,-80.071,35.401,-80.055,35.312,-80.058,35.176,-80.027,35.109,-79.963,35.113,-79.915,35.096,-79.884,35.06,-79.884,34.976,-79.915,34.846,-79.891,34.738,-79.812,34.652,-79.781,34.58,-79.8,34.523,-79.791,34.505,-79.754,34.503,-79.722,34.465,-79.676,34.359,-79.634,34.3,-79.596,34.288,-79.551,34.195,-79.471,33.934,-79.334,33.786,-79.246,33.715,-79.162,33.673,-79.115,33.629,-79.106,33.583,-79.123,33.529,-79.19,33.437,-79.226,33.405]]},{"n":"Guaporé","z":3,"b":[-65.447,-11.913,-65.031,-10.392],"l":[[-65.031,-11.913,-65.037,-11.829,-65.09,-11.741,-65.115,-11.735,-65.143,-11.752,-65.163,-11.765,-65.186,-11.75,-65.19,-11.71,-65.175,-11.647,-65.206,-11.581,-65.282,-11.511,-65.322,-11.439,-65.325,-11.365,-65.342,-11.315,-65.373,-11.29,-65.39,-11.246,-65.394,-11.184,-65.372,-11.11,-65.324,-11.025,-65.334,-10.893,-65.402,-10.715,-65.44,-10.586,-65.447,-10.507,-65.437,-10.449,-65.395,-10.392]]},{"n":"Göta älv","z":5,"b":[11.729,57.764,12.335,58.374],"l":[[12.335,58.374,12.171,58.24,12.112,58.14,12.105,58.031,12.029,57.928,11.81,57.781,11.729,57.764]]},{"n":"Hailar","z":3,"b":[117.873,49.192,122.287,49.849],"l":[[117.873,49.513,117.912,49.429,117.969,49.374,118.057,49.319,118.175,49.332,118.323,49.412,118.44,49.437,118.524,49.405,118.583,49.347,118.617,49.262,118.747,49.212,119.086,49.192,119.232,49.199,119.337,49.226,119.45,49.275,119.71,49.275,120.118,49.225,120.518,49.256,120.911,49.368,121.216,49.473,121.433,49.572,121.626,49.635,121.878,49.678,122.287,49.849]]},{"n":"Han","z":5,"b":[106.398,30.407,114.288,33.234],"l":[[111.478,32.568,111.602,32.468,111.642,32.407,111.639,32.354,111.685,32.265,111.78,32.141,111.919,32.065,112.101,32.036,112.189,31.966,112.181,31.853,112.225,31.771,112.322,31.719,112.39,31.657,112.432,31.586,112.44,31.496,112.417,31.388,112.434,31.308,112.521,31.23,112.556,31.219,112.572,31.183,112.58,31.118,112.569,31.096,112.539,31.077,112.523,31.039,112.556,30.939,112.599,30.875,112.634,30.848,112.632,30.815,112.622,30.787,112.605,30.713,112.627,30.67,112.689,30.658,112.713,30.627,112.707,30.601,112.716,30.575,112.754,30.572,112.797,30.564,112.849,30.519,112.886,30.515,112.973,30.531,113.073,30.475,113.21,30.43,113.381,30.407,113.512,30.443,113.602,30.538,113.672,30.597,113.769,30.626,113.801,30.622,113.838,30.63,113.86,30.644,113.9,30.637,113.926,30.644,113.939,30.66,113.964,30.657,114.009,30.615,114.096,30.586,114.288,30.572],[106.398,33.068,106.676,33.142,106.832,33.134,106.931,33.102,107.026,33.042,107.189,33.063,107.42,33.165,107.596,33.2,107.718,33.171,107.807,33.178,107.861,33.222,107.905,33.234,107.939,33.216,107.956,33.175,107.956,33.112,108.036,33.062,108.195,33.024,108.266,32.964,108.247,32.881,108.288,32.811,108.388,32.753,108.411,32.734,108.411,32.709,108.425,32.665,108.512,32.55,108.651,32.506,108.718,32.528,108.759,32.599,108.782,32.614,108.798,32.617,108.819,32.609,108.836,32.603,108.855,32.608,108.922,32.636,109.063,32.735,109.175,32.775,109.257,32.757,109.325,32.778,109.377,32.838,109.487,32.886,109.716,32.934,109.739,32.937,109.767,32.934,110.14,32.825,110.305,32.807,110.367,32.841,110.475,32.843,110.628,32.814,110.715,32.815,110.736,32.846,110.808,32.848,110.931,32.821,110.967,32.788,110.969,32.771,111,32.741]]},{"n":"Amur","z":3,"b":[121.405,50.209,127.59,53.556],"l":[[127.59,50.209,127.395,50.299,127.337,50.35,127.351,50.394,127.341,50.428,127.306,50.454,127.308,50.494,127.347,50.55,127.347,50.621,127.307,50.708,127.198,50.829,127.02,50.986,126.925,51.1,126.912,51.172,126.888,51.23,126.854,51.261,126.834,51.315,126.848,51.374,126.827,51.412,126.802,51.448,126.805,51.506,126.775,51.545,126.709,51.566,126.689,51.61,126.701,51.703,126.654,51.781,126.511,51.926,126.468,52.031,126.456,52.126,126.395,52.173,126.391,52.214,126.384,52.286,126.346,52.306,126.324,52.332,126.342,52.362,126.313,52.4,126.238,52.445,126.203,52.484,126.194,52.519,126.157,52.547,126.046,52.573,126.016,52.61,126.023,52.643,126.047,52.673,126.06,52.692,126.056,52.716,126.048,52.739,126.004,52.768,125.942,52.801,125.872,52.872,125.783,52.891,125.728,52.891,125.681,52.931,125.695,52.956,125.692,53.004,125.649,53.042,125.596,53.057,125.546,53.048,125.422,53.084,125.226,53.166,125.075,53.204,124.971,53.197,124.907,53.173,124.882,53.13,124.812,53.134,124.64,53.211,124.466,53.23,124.369,53.271,124.291,53.341,124.22,53.37,124.154,53.359,123.995,53.406,123.741,53.511,123.608,53.547,123.56,53.527,123.535,53.526,123.489,53.529,123.424,53.531,123.31,53.556,123.154,53.545,122.958,53.498,122.745,53.469,122.516,53.457,122.38,53.463,122.338,53.485,122.089,53.451,121.405,53.317]]},{"n":"Helmand","z":5,"b":[61.604,30.192,68.489,34.572],"l":[[61.604,31.204,61.754,31.014,61.817,30.802,61.842,30.506,62.031,30.31,62.383,30.216,62.685,30.192,62.937,30.238,63.194,30.342,63.457,30.503,63.663,30.57,63.812,30.543,63.908,30.538,63.949,30.557,64.002,30.617,64.065,30.719,64.12,30.872,64.166,31.075,64.236,31.239,64.328,31.366,64.371,31.488,64.366,31.578,64.391,31.657,64.455,31.728,64.64,31.847,64.801,31.999,64.932,32.181,65.064,32.289,65.195,32.322,65.332,32.486,65.475,32.782,65.62,32.964,65.842,33.065,65.937,33.121,66.017,33.202,66.114,33.326,66.193,33.393,66.255,33.402,66.309,33.441,66.356,33.509,66.452,33.585,66.599,33.668,66.728,33.716,66.84,33.731,66.959,33.792,67.086,33.898,67.172,33.952,67.216,33.953,67.233,33.989,67.222,34.057,67.229,34.109,67.266,34.157,67.498,34.306,67.734,34.395,68.212,34.513,68.307,34.493,68.376,34.507,68.489,34.572]]},{"n":"Holston","z":4.7,"b":[-83.974,35.87,-81.42,37.009],"l":[[-81.42,37.009,-81.695,36.932,-81.875,36.853,-82.136,36.762,-82.589,36.61,-82.665,36.522,-82.936,36.401,-83.026,36.37,-83.193,36.324,-83.387,36.256,-83.454,36.2,-83.747,36.076,-83.9,35.978,-83.974,35.915,-83.969,35.87]]},{"n":"Red","z":5,"b":[100.183,20.289,106.518,25.443],"l":[[106.518,20.289,106.387,20.348,106.329,20.356,106.288,20.34,106.27,20.347,106.276,20.377,106.264,20.398,106.234,20.408,106.196,20.466,106.149,20.572,106.109,20.623,106.076,20.619,106.023,20.665,105.951,20.761,105.909,20.861,105.897,20.964,105.836,21.051,105.725,21.122,105.626,21.162,105.538,21.17,105.477,21.203,105.426,21.292,105.325,21.273,105.275,21.301,105.241,21.37,105.216,21.389,105.197,21.358,105.118,21.43,104.977,21.604,104.692,21.875,104.052,22.424,103.479,22.902,103.161,23.121,102.906,23.24,102.614,23.353,102.119,23.514,101.892,23.699,101.745,23.857,101.601,24.049,101.507,24.208,101.463,24.335,101.41,24.434,101.349,24.505,101.292,24.54,101.238,24.538,101.118,24.655,100.931,24.891,100.812,25.003,100.76,24.993,100.642,25.021,100.459,25.088,100.351,25.15,100.303,25.235,100.183,25.443]]},{"n":"Hongshui","z":4,"b":[106.944,23.662,109.529,25.225],"l":[[106.944,25.225,106.963,25.223,106.974,25.2,106.982,25.134,107.019,25.061,107.063,25.029,107.156,25.009,107.277,24.875,107.315,24.796,107.312,24.729,107.362,24.603,107.467,24.417,107.542,24.318,107.586,24.308,107.608,24.281,107.608,24.236,107.578,24.189,107.516,24.139,107.494,24.087,107.512,24.031,107.547,23.986,107.601,23.954,107.643,23.945,107.674,23.961,107.733,23.905,107.821,23.776,107.902,23.722,107.975,23.744,108.039,23.782,108.094,23.836,108.131,23.857,108.152,23.843,108.223,23.889,108.343,23.995,108.453,24.035,108.552,24.007,108.631,23.96,108.691,23.891,108.758,23.85,108.833,23.836,108.865,23.806,108.855,23.758,108.873,23.71,108.919,23.662,108.999,23.663,109.112,23.712,109.192,23.723,109.238,23.695,109.281,23.688,109.322,23.7,109.369,23.746,109.423,23.825,109.469,23.847,109.529,23.796]]},{"n":"Huang","z":3,"b":[96.164,33.356,114.831,40.875],"l":[[97.288,34.835,97.547,34.876],[97.763,35.088,97.934,35.082,98.028,35.034,98.101,34.94,98.197,34.874,98.318,34.835,98.382,34.798,98.396,34.747,98.4,34.674,98.384,34.631,98.348,34.593,98.376,34.531,98.469,34.445,98.563,34.39,98.661,34.367,98.713,34.38,98.72,34.43,98.752,34.461,98.809,34.473,98.892,34.462,99.001,34.428,99.058,34.301,99.061,34.081,99.123,33.915,99.245,33.803,99.324,33.748,99.359,33.751,99.381,33.771,99.391,33.809,99.439,33.812,99.526,33.781,99.62,33.775,99.721,33.791,99.868,33.744,100.062,33.631,100.191,33.604,100.256,33.662,100.278,33.709,100.33,33.757,100.434,33.799,100.587,33.883,100.689,33.926,100.74,33.928,100.825,33.878,100.942,33.777,101.009,33.764,101.026,33.839,101.123,33.827,101.301,33.727,101.481,33.678,101.663,33.679,101.809,33.611,101.919,33.473,102.005,33.388,102.066,33.356,102.193,33.371,102.385,33.434,102.482,33.484,102.482,33.521,102.45,33.557,102.387,33.592,102.353,33.638,102.33,33.725,102.316,33.745,102.285,33.8,102.206,33.897,102.103,33.959,101.976,33.986,101.838,34.047,101.687,34.143,101.517,34.219,101.328,34.273,101.21,34.35,101.163,34.45,101.087,34.513,100.982,34.539,100.912,34.593,100.878,34.677,100.815,34.708,100.721,34.686,100.63,34.708,100.54,34.773,100.455,34.813,100.376,34.827,100.307,34.936,100.22,35.241,100.259,35.282,100.236,35.338,100.166,35.424,100.172,35.543,100.255,35.695,100.334,35.774,100.409,35.777,100.546,35.869,100.744,36.05,100.898,36.142,101.009,36.145,101.071,36.135,101.085,36.112,101.176,36.089,101.343,36.065,101.495,36.086,101.63,36.151,101.763,36.152,101.893,36.086,101.993,35.997,102.064,35.886,102.185,35.845,102.356,35.873,102.61,35.86,102.948,35.806,103.177,35.812,103.299,35.878,103.317,35.931,103.232,35.97,103.256,36.04,103.388,36.139,103.56,36.158,103.771,36.096,103.916,36.068,103.995,36.075,104.031,36.104,104.023,36.156,104.072,36.229,104.179,36.322,104.352,36.428,104.591,36.547,104.713,36.628,104.72,36.671,104.69,36.728,104.625,36.798,104.528,36.85,104.4,36.884,104.327,36.946,104.308,37.035,104.314,37.093,104.353,37.174,104.446,37.197,104.52,37.241,104.576,37.307,104.692,37.359,104.957,37.415,104.998,37.461,105.152,37.483,105.433,37.495,105.649,37.522,105.8,37.565,105.905,37.661,105.964,37.81,106.037,37.916,106.125,37.977,106.196,38.064,106.251,38.177,106.338,38.282,106.457,38.378,106.531,38.498,106.559,38.64,106.64,38.78,106.774,38.918,106.851,39.017,106.871,39.075,106.849,39.183,106.783,39.34,106.759,39.483,106.776,39.611,106.768,39.76,106.718,40.016,107.043,40.286,107.149,40.396,107.144,40.434,107.174,40.5,107.239,40.592,107.38,40.671,107.598,40.738,107.72,40.793,107.745,40.837,107.862,40.865,108.07,40.875,108.19,40.863,108.224,40.83,108.29,40.815,108.389,40.816,108.476,40.778,108.553,40.7,108.651,40.632,108.77,40.573,108.884,40.547,108.992,40.554,109.134,40.535,109.309,40.49,109.45,40.489,109.555,40.53,109.635,40.532,109.687,40.494,109.836,40.491,110.08,40.524,110.303,40.484,110.505,40.373,110.675,40.302,110.811,40.271,110.919,40.266,110.996,40.286,111.125,40.235,111.307,40.11,111.407,40.013,111.426,39.942,111.416,39.867,111.378,39.789,111.385,39.718,111.437,39.653,111.433,39.576,111.373,39.488,111.284,39.43,111.212,39.414,111.168,39.4,111.156,39.37,111.155,39.347,111.192,39.297,111.199,39.24,111.145,39.125,111.081,39.044,111.009,38.997,110.98,38.956,110.996,38.92,110.97,38.832,110.904,38.692,110.878,38.6,110.891,38.557,110.798,38.443,110.599,38.258,110.503,38.113,110.511,38.008,110.585,37.881,110.725,37.732,110.782,37.614,110.758,37.525,110.719,37.465,110.664,37.434,110.644,37.384,110.658,37.316,110.61,37.217,110.5,37.087,110.427,37.021,110.391,37.019,110.384,37.005,110.407,36.98,110.408,36.919,110.387,36.823,110.393,36.763,110.425,36.739,110.428,36.717,110.403,36.698,110.417,36.648,110.471,36.566,110.486,36.429,110.46,36.237,110.49,36.006,110.576,35.733,110.579,35.534,110.459,35.343,110.366,35.234,110.316,35.135,110.278,35.008,110.262,34.868,110.27,34.637,110.465,34.597,110.619,34.604,110.795,34.645,110.89,34.683,110.97,34.724,111.027,34.746,111.101,34.795,111.243,34.83,111.453,34.852,111.589,34.899,111.651,34.972,111.736,35.03,111.844,35.074,112.118,35.022,112.559,34.872,112.857,34.809,113.012,34.831,113.156,34.875,113.288,34.94,113.399,34.974,113.489,34.978,113.617,34.954,113.784,34.902,113.9,34.895,113.965,34.931,114.156,34.936,114.473,34.91,114.681,34.928,114.798,34.978,114.831,35.02],[96.164,35.13,96.748,35.053,97.072,34.931]]},{"n":"Yellow","z":3,"b":[114.838,35.02,119.036,37.824],"l":[[114.838,35.02,114.862,35.061,114.866,35.145,114.905,35.229,115.014,35.33,115.136,35.406,115.272,35.459,115.34,35.503,115.341,35.538,115.382,35.597,115.463,35.681,115.596,35.758,115.781,35.83,115.871,35.88,115.866,35.91,115.883,35.939,115.924,35.966,115.966,35.976,116.036,36.071,116.078,36.11,116.116,36.143,116.227,36.201,116.451,36.339,116.581,36.44,116.593,36.49,116.644,36.561,116.735,36.656,116.862,36.726,117.024,36.773,117.151,36.862,117.24,36.994,117.352,37.082,117.485,37.127,117.571,37.175,117.608,37.228,117.693,37.256,117.825,37.26,117.97,37.29,118.128,37.347,118.238,37.431,118.298,37.541,118.385,37.604,118.498,37.619,118.645,37.678,118.826,37.78,118.946,37.824,119.036,37.809]]},{"n":"Hudson","z":5,"b":[-74.178,41.25,-73.617,44.1],"l":[[-74.074,44.1,-74.064,44.016,-74.08,43.982,-74.119,43.972,-74.152,43.942,-74.176,43.894,-74.178,43.86,-74.146,43.832,-73.971,43.735,-73.888,43.661,-73.839,43.576,-73.83,43.506,-73.862,43.451,-73.867,43.383,-73.846,43.302,-73.781,43.274,-73.671,43.297,-73.617,43.232,-73.622,43,-73.728,42.714,-73.767,42.544,-73.773,42.394,-73.818,42.265,-73.901,42.159,-73.944,42.02,-73.948,41.765,-73.984,41.574,-73.984,41.46,-73.96,41.331,-73.969,41.281,-73.97,41.25]]},{"n":"Ideriyn","z":2.1,"b":[97.413,47.999,100.7,49.263],"l":[[98.007,47.999,97.617,48.075,97.452,48.141,97.413,48.202,97.444,48.279,97.454,48.357,97.443,48.435,97.529,48.541,97.711,48.673,98.05,48.736,98.547,48.73,98.923,48.697,99.179,48.638,99.588,48.725,100.152,48.958,100.473,49.076,100.551,49.077,100.613,49.098,100.659,49.139,100.686,49.185,100.7,49.263]]},{"n":"Indaiá","z":4,"b":[-46.499,-20.352,-45.089,-19.051],"l":[[-46.499,-20.321,-46.322,-20.352,-46.097,-20.348,-46.002,-20.337,-45.961,-20.318,-45.947,-20.296,-45.961,-20.273,-45.943,-20.231,-45.896,-20.173,-45.819,-20.14,-45.714,-20.133,-45.64,-20.068,-45.598,-19.945,-45.554,-19.87,-45.506,-19.841,-45.492,-19.8,-45.513,-19.746,-45.496,-19.688,-45.443,-19.626,-45.395,-19.524,-45.352,-19.383,-45.281,-19.284,-45.182,-19.227,-45.121,-19.161,-45.089,-19.051]]},{"n":"Indigirka","z":4.7,"b":[141.885,62.507,150.098,71.227],"l":[[144.713,62.766,144.441,63.009,144.316,63.091,144.249,63.092,143.974,63.178,143.492,63.351,143.055,63.47,142.663,63.536,142.446,63.599,142.402,63.658,142.3,63.729,142.058,63.851,141.915,63.954,141.885,64.037,141.921,64.136,142.012,64.215,142.159,64.276,142.366,64.293,142.634,64.269,142.789,64.303,142.831,64.396,142.84,64.459,142.862,64.501,142.917,64.532,143.151,64.579,143.267,64.633,143.265,64.694,143.295,64.748,143.356,64.793,143.338,64.871,143.243,64.983,143.161,65.055,143.092,65.088,143.085,65.134,143.14,65.195,143.157,65.24,143.161,65.277,143.169,65.312,143.218,65.344,143.23,65.371,143.208,65.389,143.134,65.404,142.814,65.424,142.637,65.461,142.603,65.515,142.618,65.592,142.68,65.692,142.811,65.789,143.011,65.884,143.147,66.017,143.219,66.189,143.223,66.309,143.158,66.377,143.106,66.466,143.068,66.574,142.987,66.65,142.865,66.693,142.754,66.813,142.654,67.01,142.615,67.16,142.635,67.265,142.599,67.314,142.574,67.341,142.578,67.363,142.618,67.395,142.636,67.425,142.655,67.452,142.845,67.503,143.337,67.6,143.647,67.637,143.776,67.611,144.015,67.604,144.362,67.614,144.574,67.626,144.65,67.639,144.695,67.661,144.708,67.692,144.671,67.714,144.582,67.726,144.527,67.75,144.507,67.785,144.594,67.814,144.885,67.847,145.025,67.871,145.059,67.908,145.067,67.954,145.093,67.98,145.167,67.985,145.238,67.995,145.282,68.023,145.279,68.047,145.278,68.082,145.313,68.111,145.346,68.142,145.358,68.179,145.423,68.2,145.539,68.192,145.644,68.218,145.653,68.267,145.715,68.293,145.809,68.288,145.856,68.304,145.869,68.351,145.913,68.383,145.901,68.431,145.969,68.498,146.071,68.547,146.206,68.578,146.358,68.578,146.528,68.547,146.646,68.545,146.711,68.571,146.809,68.581,146.94,68.573,147.022,68.624,147.056,68.735,147.144,68.801,147.286,68.821,147.346,68.855,147.325,68.903,147.327,68.93,147.354,68.939,147.422,68.934,147.483,68.942,147.492,68.977,147.463,69.003,147.444,69.072,147.488,69.147,147.471,69.221,147.392,69.295,147.434,69.352,147.597,69.392,147.638,69.44,147.598,69.508,147.575,69.557,147.575,69.586,147.568,69.599,147.538,69.619,147.445,69.642,147.429,69.656,147.423,69.668,147.435,69.677,147.463,69.695,147.474,69.73,147.451,69.773,147.466,69.847,147.519,69.953,147.519,70.004,147.496,70.023,147.453,70.039,147.39,70.067,147.355,70.111,147.352,70.144,147.385,70.167,147.393,70.195,147.365,70.225,147.379,70.272,147.489,70.336,147.538,70.38,147.525,70.402,147.538,70.425,147.576,70.449,147.657,70.481,147.783,70.522,147.853,70.56,147.867,70.595,148.08,70.635,148.492,70.679,148.709,70.73,148.732,70.787,148.941,70.837,149.333,70.881,149.561,70.925,149.622,70.97,149.646,71.002,149.662,71.03,149.691,71.046,149.848,71.083,149.913,71.111,149.893,71.131,149.892,71.152,149.91,71.174,149.984,71.209,150.098,71.227],[144.712,62.768,145.009,62.507]]},{"n":"Indragi","z":5,"b":[100.55,-0.675,103.405,-0.331],"l":[[100.55,-0.566,100.66,-0.572,100.724,-0.602,100.779,-0.657,100.84,-0.675,100.937,-0.647,101.007,-0.659,101.043,-0.638,101.07,-0.59,101.115,-0.586,101.179,-0.624,101.259,-0.636,101.356,-0.622,101.424,-0.627,101.483,-0.632,101.534,-0.611,101.631,-0.51,101.77,-0.486,101.953,-0.539,102.097,-0.535,102.256,-0.442,102.372,-0.356,102.521,-0.378,102.742,-0.481,102.843,-0.506,102.937,-0.479,103.105,-0.374,103.229,-0.331,103.346,-0.36,103.405,-0.362]]},{"n":"Indus","z":3,"b":[67.454,23.848,79.735,35.836],"l":[[67.86,23.903,67.925,24.001],[67.646,23.92,67.711,23.957,67.714,24.001,67.713,24.066],[67.925,24.001,68,24.007,68.017,23.991,68.025,23.934,68.042,23.892,68.037,23.848],[67.925,24.001,67.924,24.056,67.801,24.059,67.76,24.053,67.713,24.066],[67.713,24.066,67.661,24.105],[79.735,32.444,79.43,32.745],[79.437,32.738,79.341,32.881,79.22,32.996,79.044,33.133,78.907,33.189,78.809,33.165,78.64,33.208,78.245,33.398,78.183,33.445,78.152,33.493,78.15,33.542,78.077,33.62,77.931,33.727,77.792,33.857,77.66,34.011,77.53,34.106,77.339,34.16,76.967,34.288,76.782,34.386,76.661,34.498,76.529,34.583,76.388,34.639,76.285,34.708,76.222,34.789,76.196,34.861,76.209,34.924,76.142,35.008,75.995,35.113,75.902,35.21,75.863,35.297,75.791,35.335,75.688,35.321,75.568,35.357,75.431,35.443,75.353,35.511,75.333,35.561,75.264,35.59,75.145,35.598,75.001,35.632,74.83,35.694,74.74,35.753,74.73,35.812,74.701,35.836,74.654,35.826,74.631,35.748,74.632,35.601,74.575,35.5,74.461,35.445,74.342,35.415,74.22,35.408,74.058,35.436,73.857,35.497,73.645,35.531,73.423,35.538,73.302,35.471,73.28,35.33,73.175,35.204,72.986,35.093,72.885,35.004,72.872,34.939,72.895,34.886,72.955,34.845,72.945,34.809,72.864,34.777,72.814,34.726,72.782,34.623],[72.707,34.086,72.652,34.032,72.592,34.015,72.509,34.019,72.411,34,72.297,33.959,72.239,33.895,72.236,33.809,72.197,33.772,72.124,33.784,72.071,33.779,72.038,33.757,71.986,33.665,71.914,33.504,71.839,33.406,71.762,33.372,71.722,33.274,71.72,33.111,71.651,32.994,71.516,32.923,71.425,32.838,71.355,32.689,71.344,32.484,71.273,32.311,71.07,31.999,70.861,31.569,70.801,31.3,70.829,30.92,70.77,30.811,70.775,30.716,70.824,30.6,70.83,30.485,70.793,30.372,70.792,30.212,70.827,30.004,70.792,29.825,70.688,29.676,70.657,29.55,70.699,29.45,70.702,29.353,70.666,29.261,70.651,29.176,70.657,29.098,70.612,29.023,70.515,28.952,70.443,28.916,70.397,28.915,70.287,28.835,70.115,28.677,69.962,28.576,69.829,28.531,69.715,28.444,69.621,28.315,69.535,28.246,69.458,28.237,69.409,28.212,69.389,28.172,69.356,28.157,69.311,28.165,69.249,28.141,69.171,28.085,69.107,28.058,69.059,28.061,69.026,28.04,69.008,27.995,68.98,27.975,68.942,27.981,68.929,27.955,68.943,27.897,68.928,27.853,68.883,27.823,68.884,27.792,68.932,27.761,68.95,27.737,68.938,27.719,68.826,27.678,68.762,27.698,68.705,27.759,68.669,27.771,68.654,27.731,68.573,27.688,68.426,27.641,68.337,27.603,68.307,27.572,68.3,27.511,68.318,27.42,68.297,27.371,68.239,27.366,68.194,27.342,68.162,27.3,68.16,27.263,68.188,27.231,68.175,27.208,68.121,27.193,68.099,27.163,68.109,27.117,68.063,27.062,67.963,26.998,67.911,26.91,67.905,26.742,67.891,26.487,67.9,26.369,67.926,26.302,67.982,26.243,68.068,26.192,68.117,26.14,68.13,26.088,68.202,25.999,68.332,25.873,68.379,25.795,68.341,25.765,68.348,25.728,68.4,25.684,68.398,25.621,68.342,25.539,68.323,25.444,68.34,25.334,68.337,25.262,68.313,25.226,68.319,25.192,68.353,25.158,68.352,25.139,68.314,25.132,68.286,25.089,68.269,25.009,68.245,24.979,68.214,24.997,68.183,24.973,68.155,24.907,68.116,24.873,68.068,24.87,68.025,24.76,67.986,24.544,67.936,24.43,67.874,24.418,67.85,24.396,67.863,24.363,67.849,24.344,67.806,24.339,67.796,24.318,67.818,24.282,67.787,24.239,67.702,24.191,67.659,24.146,67.659,24.104,67.627,24.078,67.532,24.061,67.454,24.04]]},{"n":"Irrawaddy Delta","z":2.1,"b":[94.656,15.839,96.189,17.908],"l":[[94.703,16.512,94.728,16.673,94.744,16.783,94.742,16.908,94.795,17.035,94.903,17.164,94.954,17.257,94.948,17.315,94.977,17.352,95.042,17.371,95.12,17.472,95.21,17.656,95.304,17.766,95.455,17.856,95.482,17.88,95.52,17.908,95.575,17.904,95.633,17.81,95.694,17.625,95.783,17.438,95.899,17.251,95.956,17.13,95.952,17.074,96.042,16.969,96.09,16.847,96.189,16.768],[95.481,16.814,95.323,16.328,95.237,16.133,95.133,16.025,95.099,15.958,95.078,15.839],[95.6,16.902,95.649,16.768,95.719,16.709,95.829,16.653,95.872,16.593,95.847,16.529,95.845,16.478,95.867,16.44,95.91,16.412,95.994,16.39,96.043,16.34],[95.472,17.744,95.406,17.718,95.356,17.666,95.296,17.554,95.291,17.502,95.323,17.468,95.34,17.431,95.335,17.379,95.317,17.348,95.276,17.323,95.263,17.302,95.253,17.224,95.248,17.178,95.213,17.144,95.186,17.078,95.166,16.979,95.175,16.903,95.213,16.852,95.222,16.796,95.176,16.674,95.145,16.61,95.107,16.574,95.061,16.566,95.014,16.503,94.968,16.388,94.907,16.288,94.794,16.162,94.748,16.08,94.656,15.989]]},{"n":"Irtysh","z":2.1,"b":[68.215,53.986,74.994,61.124],"l":[[74.994,53.986,74.918,54.003,74.904,54.028,74.924,54.066,74.872,54.128,74.745,54.214,74.66,54.291,74.617,54.36,74.558,54.4,74.484,54.41,74.4,54.449,74.305,54.517,74.125,54.586,73.728,54.694],[73.722,54.694,73.539,54.805,73.434,54.845,73.347,54.85,73.318,54.888,73.346,54.96,73.308,55.009,73.203,55.036,73.157,55.097,73.17,55.193,73.128,55.242,73.029,55.245,72.993,55.266,73.02,55.305,73.163,55.389,73.424,55.518,73.662,55.618,73.878,55.689,74.081,55.729,74.272,55.739,74.469,55.833,74.672,56.011,74.709,56.058,74.709,56.092,74.676,56.126,74.67,56.148,74.69,56.162,74.699,56.203,74.681,56.264,74.698,56.286,74.729,56.289,74.737,56.317,74.721,56.337,74.74,56.371,74.749,56.396,74.735,56.419,74.706,56.427,74.678,56.478,74.68,56.563,74.656,56.607,74.606,56.609,74.59,56.627,74.609,56.663,74.595,56.693,74.577,56.717,74.585,56.734,74.597,56.757,74.552,56.845,74.511,56.893,74.456,56.923,74.387,56.936,74.366,56.95,74.334,56.988,74.181,57.017,74.117,57.053,74.142,57.096,74.119,57.133,74.049,57.163,73.965,57.174,73.869,57.166,73.77,57.179,73.669,57.213,73.635,57.239,73.633,57.258,73.599,57.272,73.504,57.265,73.441,57.278,73.436,57.306,73.371,57.327,73.247,57.342,73.18,57.362,73.171,57.388,73.113,57.408,73.007,57.422,72.949,57.441,72.903,57.458,72.867,57.447,72.78,57.442,72.625,57.472,72.529,57.502,72.492,57.52,72.45,57.534,72.413,57.529,72.389,57.536,72.334,57.565,72.231,57.579,71.951,57.581,71.855,57.608,71.82,57.63,71.751,57.643,71.606,57.635,71.527,57.645,71.515,57.674,71.473,57.699,71.4,57.719,71.312,57.721,71.209,57.704,71.108,57.71,71.007,57.74,70.964,57.764,70.92,57.765,70.873,57.761,70.785,57.777,70.683,57.822,70.615,57.821,70.597,57.823,70.569,57.851,70.526,57.859,70.468,57.847,70.425,57.861,70.398,57.902,70.396,57.928,70.373,57.957,70.294,57.977,70.225,57.977,70.176,57.954,70.137,57.959,70.107,57.99,70.053,58.009,69.974,58.015,69.877,58.001,69.763,57.967,69.649,57.96,69.536,57.98,69.48,58,69.427,58.017,69.316,57.995,69.239,58,69.197,58.032,69.14,58.032,69.07,58,69,58,68.931,58.032,68.871,58.037,68.818,58.014,68.73,58.037,68.605,58.108,68.464,58.149,68.226,58.168],[68.861,61.124,69.011,61.019,69.161,60.976,69.362,60.959,69.516,60.885,69.623,60.755,69.724,60.683,69.802,60.668,69.83,60.65,69.836,60.621,69.816,60.604,69.782,60.567,69.769,60.512,69.801,60.463,69.804,60.419,69.778,60.38,69.785,60.354,69.825,60.342,69.854,60.311,69.874,60.259,69.862,60.239,69.821,60.237,69.77,60.207,69.729,60.17,69.721,60.126,69.738,60.095,69.781,60.078,69.854,60.058,69.888,60.005,69.866,59.943,69.865,59.901,69.888,59.866,69.871,59.835,69.82,59.839,69.777,59.817,69.757,59.766,69.699,59.744,69.601,59.75,69.498,59.727,69.334,59.65,69.284,59.577,69.223,59.555,69.133,59.561,69.075,59.536,69.047,59.481,68.997,59.452,68.925,59.451,68.911,59.43,68.929,59.402,68.905,59.354,68.831,59.292,68.799,59.24,68.81,59.198,68.843,59.173,68.882,59.161,68.882,59.138,68.862,59.12,68.854,59.084,68.873,59.057,68.856,59.021,68.812,59.013,68.801,59.002,68.821,58.967,68.812,58.935,68.762,58.926,68.749,58.9,68.75,58.877,68.737,58.859,68.697,58.853,68.689,58.827,68.705,58.788,68.671,58.764,68.627,58.77,68.569,58.753,68.556,58.679,68.564,58.607,68.566,58.562,68.549,58.521,68.469,58.512,68.399,58.5,68.355,58.47,68.331,58.431,68.328,58.383,68.283,58.355,68.24,58.342,68.219,58.328,68.215,58.29,68.233,58.246,68.222,58.166]]},{"n":"Ishikari","z":5,"b":[141.358,43.13,143.066,43.895],"l":[[143.066,43.7,142.885,43.81,142.722,43.878,142.632,43.895,142.56,43.883,142.481,43.849,142.396,43.792,142.254,43.738,141.943,43.648,141.909,43.623,141.884,43.564,141.861,43.428,141.71,43.329,141.644,43.254,141.613,43.169,141.567,43.13,141.508,43.135,141.448,43.163,141.358,43.238]]},{"n":"Ismailiya Canal","z":5,"b":[31.238,30.121,32.303,30.593],"l":[[31.238,30.121,31.282,30.129,31.324,30.18,31.378,30.278,31.483,30.379,31.64,30.484,31.865,30.551,32.303,30.593]]},{"n":"Japurá","z":4.7,"b":[-69.462,-3.222,-64.774,-1.231],"l":[[-69.462,-1.231,-69.452,-1.299,-69.447,-1.448,-69.339,-1.605,-69.128,-1.754,-68.914,-1.818,-68.699,-1.797,-68.458,-1.748,-68.191,-1.672,-67.964,-1.639,-67.776,-1.651,-67.629,-1.685,-67.525,-1.742,-67.361,-1.798,-67.138,-1.854,-66.805,-1.823,-66.363,-1.706,-65.981,-1.716,-65.658,-1.853,-65.445,-1.905,-65.344,-1.869,-65.268,-1.904,-65.193,-2.063,-65.227,-2.138,-65.18,-2.266,-65.066,-2.472,-65.009,-2.606,-65.011,-2.667,-64.979,-2.709,-64.914,-2.733,-64.889,-2.777,-64.903,-2.842,-64.875,-2.927,-64.802,-3.031,-64.774,-3.109,-64.79,-3.222]]},{"n":"Jequitinhonha","z":5,"b":[-43.665,-18.294,-38.885,-15.821],"l":[[-43.409,-18.294,-43.532,-18.104,-43.559,-18.012,-43.515,-17.923,-43.476,-17.839,-43.494,-17.818,-43.558,-17.832,-43.611,-17.786,-43.655,-17.68,-43.665,-17.608,-43.642,-17.57,-43.57,-17.505,-43.488,-17.496,-43.365,-17.507,-43.299,-17.476,-43.233,-17.418,-43.213,-17.339,-43.181,-17.304,-43.118,-17.279,-43.048,-17.212,-42.972,-17.105,-42.905,-17.046,-42.846,-17.035,-42.777,-16.96,-42.698,-16.819,-42.555,-16.716,-42.347,-16.651,-42.178,-16.651,-42.046,-16.717,-41.919,-16.703,-41.796,-16.61,-41.687,-16.562,-41.548,-16.56,-41.332,-16.524,-41.236,-16.486,-41.187,-16.435,-41.124,-16.416,-41.044,-16.43,-40.908,-16.355,-40.715,-16.19,-40.52,-16.116,-40.324,-16.134,-40.17,-16.125,-40.059,-16.092,-39.99,-16.056,-39.963,-16.019,-39.84,-15.986,-39.513,-15.942,-39.402,-16.025,-39.323,-16.04,-39.237,-16.015,-39.164,-15.955,-39.104,-15.86,-39.021,-15.821,-38.949,-15.825,-38.885,-15.842]]},{"n":"Yangtze","z":2,"b":[98.542,25.981,103.859,31.69],"l":[[99.399,28.12,99.431,27.842,99.494,27.642,99.6,27.426,99.727,27.246,99.874,27.101,99.954,26.994,99.967,26.922,99.996,26.904,100.041,26.94,100.063,27.012,100.063,27.12,100.109,27.221,100.199,27.314,100.232,27.399,100.206,27.474,100.219,27.554,100.27,27.64,100.296,27.721,100.298,27.795,100.33,27.834,100.39,27.837,100.427,27.754,100.442,27.584,100.472,27.484,100.519,27.454,100.545,27.393,100.55,27.301,100.523,27.238,100.463,27.202,100.45,27.169,100.481,27.139,100.486,27.05,100.463,26.901,100.437,26.817,100.408,26.797,100.411,26.691,100.444,26.499,100.449,26.378,100.425,26.326,100.428,26.279,100.459,26.236,100.582,26.203,100.798,26.178,100.961,26.211,101.071,26.301,101.2,26.357,101.349,26.381,101.447,26.442,101.495,26.541,101.59,26.593,101.734,26.599,101.841,26.554,101.912,26.46,101.94,26.363,101.925,26.261,101.899,26.193,101.863,26.157,101.847,26.098,101.852,26.016,101.912,25.981,102.027,25.994,102.126,26.057,102.21,26.171,102.331,26.266,102.489,26.341,102.583,26.37,102.612,26.353,102.622,26.324,102.614,26.281,102.635,26.252,102.686,26.237,102.753,26.263,102.836,26.331,102.905,26.365,102.961,26.365,102.99,26.395,102.994,26.455,103.011,26.498,103.042,26.523,103.018,26.633,102.94,26.828,102.897,27.013,102.89,27.189,102.903,27.313,102.938,27.385,102.969,27.412,102.998,27.395,103.046,27.398,103.147,27.434,103.2,27.534,103.237,27.572,103.276,27.583,103.293,27.601,103.287,27.627,103.326,27.67,103.41,27.73,103.479,27.814,103.535,27.922,103.532,27.992,103.471,28.026,103.444,28.065,103.451,28.111,103.484,28.161,103.589,28.25,103.626,28.264,103.663,28.257,103.702,28.229,103.762,28.243,103.841,28.298,103.859,28.377,103.816,28.48,103.809,28.565,103.851,28.668],[98.542,31.69,98.799,31.438,98.855,31.323,98.797,31.261,98.738,31.252,98.679,31.295,98.638,31.28,98.615,31.206,98.653,31.125,98.753,31.036,98.799,30.963,98.791,30.908,98.831,30.842,98.917,30.766,98.947,30.711,98.919,30.68,98.943,30.477,99.018,30.102,99.039,29.849,99.004,29.717,99.014,29.551,99.096,29.25,99.146,28.641,99.221,28.36,99.399,28.126]]},{"n":"Jordan","z":5,"b":[35.401,31.23,35.864,33.596],"l":[[35.424,31.325,35.401,31.23],[35.577,32.72,35.551,32.396,35.572,32.238,35.535,32.103,35.532,31.985,35.559,31.766],[35.864,33.596,35.696,33.431,35.619,33.244,35.595,33.111,35.599,32.9]]},{"n":"Jubba","z":5,"b":[41.966,-0.251,42.751,4.324],"l":[[41.966,4.324,42.029,4.291,42.056,4.255,42.067,4.206,42.121,4.173,42.219,4.155,42.342,4.078,42.49,3.943,42.542,3.795,42.499,3.635,42.497,3.522,42.536,3.456,42.533,3.398,42.488,3.35,42.473,3.28,42.487,3.188,42.516,3.13,42.56,3.107,42.575,3.07,42.562,3.02,42.502,2.942,42.393,2.838,42.346,2.749,42.36,2.677,42.345,2.614,42.302,2.559,42.295,2.503,42.322,2.444,42.316,2.388,42.274,2.333,42.229,2.319,42.179,2.348,42.145,2.345,42.126,2.312,42.144,2.28,42.2,2.252,42.205,2.221,42.157,2.188,42.177,2.135,42.265,2.061,42.297,1.993,42.275,1.929,42.306,1.835,42.389,1.711,42.426,1.617,42.416,1.553,42.424,1.513,42.425,1.462,42.462,1.375,42.561,1.219,42.605,1.07,42.595,0.929,42.626,0.808,42.698,0.705,42.738,0.581,42.751,0.362,42.73,0.135,42.685,0.015,42.608,-0.072,42.585,-0.148,42.634,-0.251]]},{"n":"Juruena","z":4.7,"b":[-59.077,-14.532,-58.622,-10.975],"l":[[-59.07,-14.532,-59.036,-14.373,-59.038,-14.249,-59.077,-14.038,-59.041,-13.979,-59.026,-13.821,-59.019,-13.544,-58.997,-13.367,-58.96,-13.289,-58.933,-12.998,-58.915,-12.494,-58.863,-12.181,-58.778,-12.058,-58.731,-11.92,-58.724,-11.769,-58.699,-11.657,-58.654,-11.586,-58.628,-11.503,-58.622,-11.407,-58.633,-11.364,-58.669,-11.33,-58.68,-11.234,-58.659,-10.975]]},{"n":"Kadéï","z":5,"b":[14.243,1.654,16.263,5.865],"l":[[14.617,5.865,14.617,5.496,14.493,5.287,14.463,5.182,14.485,5.111,14.437,5.03,14.319,4.941,14.254,4.797,14.243,4.598,14.297,4.43,14.417,4.292,14.498,4.23,14.539,4.229,14.57,4.211,14.574,4.189,14.623,4.172,14.68,4.205,14.754,4.228,14.827,4.23,14.873,4.203,14.894,4.147,14.957,4.118,15.064,4.116,15.147,4.091,15.236,4.019,15.368,4.055,15.451,4.046,15.531,4.005,15.578,3.931,15.593,3.825,15.651,3.756,15.755,3.724,15.881,3.65,16.032,3.535,16.108,3.4,16.11,3.246,16.158,3.103,16.252,2.971,16.263,2.924,16.245,2.884,16.174,2.824,16.118,2.716,16.095,2.599,16.107,2.473,16.136,2.364,16.183,2.27,16.177,2.205,16.116,2.168,16.08,2.107,16.07,2.022,16.088,1.918,16.135,1.796,16.136,1.715,16.098,1.654]]},{"n":"Kafue","z":5,"b":[25.947,-15.953,28.973,-11.626],"l":[[27.157,-11.626,27.143,-11.749,27.183,-11.852,27.317,-12.039,27.366,-12.154,27.445,-12.255,27.568,-12.379,27.655,-12.429,27.706,-12.405,27.821,-12.42,28.002,-12.474,28.136,-12.585,28.223,-12.755,28.24,-12.943,28.185,-13.152,28.125,-13.284,28.06,-13.339,27.964,-13.368,27.837,-13.372,27.76,-13.408,27.733,-13.476,27.688,-13.516,27.626,-13.526,27.603,-13.573,27.616,-13.659,27.561,-13.769,27.436,-13.903,27.386,-14.012,27.412,-14.095,27.41,-14.165,27.38,-14.22,27.317,-14.279,27.222,-14.343,27.12,-14.383,26.959,-14.409,26.656,-14.44,26.5,-14.517,26.39,-14.648,26.292,-14.691,26.206,-14.644,26.151,-14.647,26.127,-14.701,26.131,-14.736,26.163,-14.754,26.179,-14.789,26.178,-14.841,26.112,-14.881,25.983,-14.907,25.947,-14.947,26.006,-14.999,26.015,-15.054,25.977,-15.111,25.967,-15.288,25.987,-15.585,26.048,-15.755,26.15,-15.797,26.285,-15.776,26.453,-15.692,26.568,-15.664,26.63,-15.69,26.748,-15.667,26.921,-15.595,27.08,-15.606,27.227,-15.698,27.373,-15.721,27.517,-15.673,27.573,-15.673,27.625,-15.685,27.643,-15.683,27.653,-15.665,27.675,-15.648,27.71,-15.646,27.894,-15.66,28.037,-15.704,28.17,-15.78,28.275,-15.81,28.352,-15.793,28.482,-15.826,28.663,-15.91,28.802,-15.953,28.973,-15.95]]},{"n":"Kagera","z":2,"b":[29.593,-3.365,31.768,-0.936],"l":[[29.593,-2.984,29.827,-3.047,29.901,-3.091,29.895,-3.137,29.909,-3.16,29.943,-3.16,29.976,-3.184,30.021,-3.256,29.99,-3.315,29.99,-3.345,30.01,-3.365,30.105,-3.315,30.274,-3.196,30.391,-3.088,30.455,-2.991,30.47,-2.92,30.433,-2.875,30.424,-2.824,30.441,-2.769,30.53,-2.679,30.691,-2.554,30.774,-2.463,30.779,-2.404,30.798,-2.363,30.829,-2.339,30.855,-2.265,30.877,-2.143,30.865,-2.044,30.819,-1.967,30.807,-1.851,30.828,-1.694,30.813,-1.563,30.762,-1.459,30.711,-1.397,30.632,-1.367,30.508,-1.208,30.47,-1.131,30.477,-1.083,30.52,-1.063,30.599,-1.07,30.673,-1.051,30.742,-1.008,30.809,-0.995,30.874,-1.013,30.942,-1.063,31.012,-1.143,31.125,-1.2,31.28,-1.234,31.409,-1.239,31.512,-1.213,31.573,-1.157,31.594,-1.072,31.645,-1.006,31.768,-0.936]]},{"n":"Kamchatka","z":5,"b":[157.632,54.081,162.461,56.398],"l":[[157.632,54.29,157.694,54.131,157.748,54.081,157.817,54.086,157.884,54.115,157.982,54.194,158.124,54.364,158.205,54.434,158.271,54.461,158.314,54.505,158.333,54.565,158.456,54.642,158.684,54.737,158.857,54.866,158.975,55.031,159.101,55.145,159.233,55.209,159.31,55.261,159.329,55.301,159.329,55.328,159.309,55.341,159.315,55.352,159.336,55.378,159.343,55.391,159.36,55.41,159.405,55.433,159.488,55.451,159.608,55.462,159.665,55.483,159.659,55.511,159.677,55.542,159.722,55.575,159.74,55.642,159.733,55.741,159.697,55.815,159.632,55.863,159.633,55.875,159.641,55.887,159.652,55.905,159.647,55.941,159.693,55.986,159.79,56.042,159.868,56.116,159.926,56.208,160.022,56.271,160.156,56.304,160.247,56.346,160.295,56.398,160.684,56.386,161.778,56.271,161.906,56.235,162.044,56.224,162.176,56.261,162.373,56.264,162.461,56.235]]},{"n":"Kapuas","z":5,"b":[109.15,-0.352,113.465,1.201],"l":[[113.465,1.201,113.233,0.965,112.954,0.855,112.794,0.823,112.66,0.834,112.536,0.802,112.421,0.728,112.252,0.672,112.028,0.636,111.95,0.599,111.945,0.546,111.917,0.503,111.84,0.462,111.821,0.43,111.827,0.408,111.802,0.375,111.736,0.33,111.681,0.321,111.645,0.311,111.632,0.272,111.618,0.242,111.58,0.242,111.556,0.221,111.542,0.158,111.501,0.104,111.465,0.098,111.404,0.106,111.367,0.089,111.329,0.098,111.304,0.102,111.282,0.082,111.263,0.088,111.232,0.106,111.206,0.089,111.156,0.079,111.077,0.092,111.024,0.082,110.997,0.048,110.933,0.023,110.833,0.008,110.772,0.029,110.732,0.046,110.696,0.033,110.653,0.043,110.613,0.06,110.58,0.06,110.568,0.034,110.544,0.02,110.517,0.023,110.485,-0.002,110.465,-0.038,110.418,-0.066,110.335,-0.081,110.177,-0.056,110.098,-0.07,110.007,-0.095,109.947,-0.185,109.81,-0.269,109.596,-0.346,109.522,-0.352,109.453,-0.337,109.403,-0.304,109.336,-0.267,109.219,-0.231,109.15,-0.186]]},{"n":"Kasai","z":3,"b":[16.191,-11.249,22.307,-3.052],"l":[[22.237,-11.249,22.217,-11.013,22.178,-10.892,22.203,-10.829,22.28,-10.784,22.307,-10.691,22.283,-10.552,22.282,-10.453,22.302,-10.397,22.275,-10.259,22.198,-10.041,22.089,-9.863,21.949,-9.726,21.857,-9.594,21.813,-9.469,21.829,-9.168,21.905,-8.693,21.896,-8.341,21.801,-8.112,21.78,-7.865,21.834,-7.602,21.842,-7.421,21.785,-7.275,21.692,-7.109,21.547,-6.964,21.206,-6.692,20.939,-6.535,20.783,-6.366,20.648,-6.134,20.58,-5.993,20.579,-5.943,20.638,-5.876,20.755,-5.793,20.835,-5.712,20.898,-5.596,21.038,-5.339,21.075,-5.102,21.055,-4.8,20.966,-4.611,20.808,-4.537,20.688,-4.455,20.606,-4.365,20.522,-4.309,20.464,-4.295,20.403,-4.3,20.375,-4.319,20.355,-4.362,20.323,-4.39,20.285,-4.39,20.145,-4.315,19.904,-4.162,19.742,-4.081,19.66,-4.072,19.41,-3.959,18.991,-3.743,18.74,-3.626,18.616,-3.601,18.51,-3.599,18.414,-3.551,18.292,-3.457,18.13,-3.412,17.928,-3.415,17.796,-3.391,17.735,-3.34,17.674,-3.313,17.613,-3.31,17.427,-3.234,17.355,-3.2,17.06,-3.059,16.978,-3.052,16.885,-3.073,16.745,-3.086,16.519,-3.07,16.364,-3.073,16.281,-3.097,16.231,-3.139,16.191,-3.194]]},{"n":"Katherine","z":5,"b":[131.844,-14.706,133.227,-13.298],"l":[[133.134,-13.298,133.2,-13.421,133.224,-13.507,133.227,-13.595,133.21,-13.664,133.173,-13.712,133.133,-13.736,133.069,-13.733,132.966,-13.705,132.9,-13.73,132.835,-13.801,132.781,-13.914,132.741,-14.069,132.695,-14.172,132.645,-14.223,132.568,-14.266,132.466,-14.302,132.302,-14.414,132.076,-14.602,131.933,-14.698,131.844,-14.706]]},{"n":"Kayan","z":5,"b":[114.762,1.76,117.569,2.929],"l":[[114.762,1.76,114.876,1.885,114.956,1.933,115.085,1.949,115.261,2.011,115.36,2.031,115.44,2.028,115.49,2.01,115.51,1.976,115.552,1.969,115.617,1.988,115.694,2.061,115.781,2.188,115.845,2.204,115.886,2.11,115.959,2.066,116.063,2.073,116.141,2.146,116.194,2.286,116.326,2.466,116.536,2.685,116.656,2.798,116.686,2.805,116.719,2.782,116.756,2.73,116.812,2.736,116.887,2.801,116.982,2.81,117.099,2.765,117.168,2.755,117.191,2.779,117.229,2.787,117.284,2.776,117.342,2.809,117.402,2.885,117.46,2.922,117.512,2.929,117.569,2.929]]},{"n":"Kem","z":5,"b":[31.57,63.766,34.803,65.111],"l":[[31.57,63.869,31.714,63.819,31.834,63.766,31.944,63.767,32.236,63.853,32.294,63.903,32.357,63.984,32.378,64.037,32.357,64.062,32.36,64.109,32.388,64.177,32.339,64.249,32.215,64.325,32.185,64.377,32.25,64.404,32.302,64.445,32.34,64.499,32.303,64.57,32.193,64.656,32.144,64.723,32.149,64.748],[32.044,64.46,32.2,64.384],[32.149,64.748,32.154,64.771,32.259,64.82,32.459,64.872,32.589,64.93,32.65,64.995,32.742,65.016,32.865,64.994,32.952,65.016,33.001,65.081,33.066,65.111,33.186,65.104,34.005,64.975,34.386,64.944,34.709,64.98,34.803,64.986],[31.955,64.864,32.149,64.748]]},{"n":"Kemijoki","z":5,"b":[24.628,65.859,28.955,68.105],"l":[[27.359,66.467,27.324,66.406,27.271,66.373,27.19,66.349,26.901,66.356,26.403,66.395,26.066,66.441,25.889,66.493,25.709,66.494,25.525,66.444,25.4,66.363,25.303,66.193,24.946,66.118,24.798,66.048,24.711,65.912,24.628,65.859],[28.781,68.105,28.93,68.012,28.955,67.961,28.878,67.901,28.851,67.813,28.803,67.751,28.724,67.686,28.553,67.59,28.289,67.462,28.187,67.369,28.245,67.309,28.227,67.287,28.132,67.304,28.085,67.293,28.086,67.255,28.063,67.231,27.992,67.214,27.608,67.158,27.5,67.106,27.54,67.039,27.516,66.945,27.385,66.765]]},{"n":"Khatanga","z":5,"b":[102.078,71.952,105.144,72.777],"l":[[105.144,72.777,104.86,72.75,104.616,72.671,104.429,72.646,104.2,72.576,104.17,72.52,103.89,72.462,103.36,72.4,103.078,72.349,103.044,72.307,102.886,72.214,102.602,72.071,102.364,71.987,102.078,71.952]]},{"n":"Kheta","z":5,"b":[92.22,69.844,102.078,71.952],"l":[[92.22,69.987,92.861,69.948,93.228,69.926,93.62,69.888,93.733,69.853,94.006,69.844,94.439,69.862,94.819,69.933,95.145,70.056,95.405,70.131,95.598,70.155,95.744,70.211,95.843,70.298,95.876,70.356,95.844,70.385,95.834,70.439,95.846,70.517,95.737,70.557,95.384,70.565,95.325,70.631,95.158,70.67,94.726,70.652,94.388,70.677,94.145,70.747,94.139,70.831,94.371,70.93,94.867,70.945,96.007,70.846,97.365,70.813,97.835,70.825,97.869,70.87,97.947,70.908,98.069,70.939,98.141,70.983,98.164,71.04,98.18,71.12,98.305,71.191,98.704,71.158,98.97,71.151,99.102,71.171,99.265,71.248,99.513,71.433],[102.078,71.952,101.584,71.816,101.081,71.752,100.405,71.713,99.91,71.659,99.597,71.591,99.54,71.515,99.515,71.435]]},{"n":"Kibali","z":3,"b":[28.586,2.333,30.439,3.598],"l":[[30.371,2.333,30.439,2.663,30.363,2.825,30.278,2.878,30.159,2.876,30.008,2.924,29.825,3.024,29.68,3.081,29.571,3.094,29.509,3.15,29.495,3.249,29.44,3.3,29.347,3.303,29.245,3.381,29.135,3.535,29.033,3.598,28.938,3.568,28.814,3.564,28.586,3.596]]},{"n":"Klamath","z":4.7,"b":[-124.093,41.225,-121.785,42.251],"l":[[-123.78,41.277,-123.837,41.302,-123.884,41.362,-123.938,41.466,-123.991,41.523,-124.068,41.539,-124.093,41.54],[-121.809,42.251,-121.785,42.215,-121.8,42.181,-121.845,42.137,-121.901,42.126,-121.966,42.149,-122.052,42.119,-122.157,42.036,-122.257,41.991,-122.353,41.986,-122.447,41.951,-122.54,41.887,-122.637,41.867,-122.738,41.889,-122.812,41.892,-122.882,41.866,-122.999,41.826,-123.1,41.83,-123.223,41.866,-123.342,41.811,-123.458,41.665,-123.511,41.532,-123.503,41.411,-123.535,41.315,-123.609,41.244,-123.679,41.225,-123.78,41.277]]},{"n":"Kokemäenjoki","z":5,"b":[21.598,61.211,22.579,61.578],"l":[[22.579,61.211,22.399,61.293,22.341,61.302,22.345,61.265,22.301,61.256,22.21,61.276,22.053,61.344,21.716,61.519,21.655,61.545,21.598,61.578]]},{"n":"Kolyma","z":4,"b":[145.216,61.454,160.856,68.789],"l":[[145.216,61.856,145.606,61.899,145.801,61.877,145.943,61.839,146.096,61.78,146.23,61.697,146.344,61.59,146.486,61.509,146.657,61.455,146.812,61.454,146.95,61.508,147.052,61.522,147.119,61.497,147.225,61.52,147.371,61.59,147.433,61.708,147.412,61.873,147.355,61.985,147.26,62.045,147.255,62.14,147.342,62.271,147.464,62.331,147.624,62.32,147.718,62.299,147.747,62.266,147.816,62.243,147.924,62.229,148.025,62.18,148.12,62.095,148.244,62.075,148.397,62.119,148.524,62.107,148.625,62.037,148.663,61.98,148.622,61.913,148.727,61.785,148.747,61.729,148.718,61.704,148.722,61.68,148.758,61.659,148.859,61.666,149.027,61.7,149.2,61.699,149.379,61.66,149.501,61.674,149.566,61.741,149.601,61.802,149.606,61.859,149.656,61.897,149.752,61.917,149.81,61.943,149.83,61.976,149.915,62.02,150.064,62.075,150.19,62.092,150.292,62.072,150.429,62.088,150.601,62.141,150.714,62.197,150.766,62.257,150.8,62.341,150.817,62.449,150.804,62.533,150.763,62.593,150.777,62.639,150.845,62.672,151.056,62.665,151.41,62.619,151.618,62.559,151.682,62.486,151.742,62.454,151.798,62.462,151.865,62.499,151.942,62.565,151.992,62.556,152.015,62.471,152.084,62.427,152.201,62.425,152.271,62.435,152.293,62.458,152.292,62.488,152.27,62.525,152.326,62.608,152.462,62.736,152.505,62.823,152.456,62.87,152.461,62.929,152.521,63,152.543,63.063,152.53,63.118,152.543,63.162,152.584,63.194,152.598,63.244,152.583,63.312,152.65,63.361,152.798,63.394,152.908,63.456,152.982,63.549,153.158,63.67,153.437,63.819,153.664,63.902,153.841,63.917,154.057,63.96,154.315,64.033,154.468,64.088,154.517,64.127,154.542,64.168,154.542,64.213,154.498,64.224,154.41,64.203,154.372,64.227,154.383,64.295,154.317,64.354,154.175,64.406,154.08,64.512,154.031,64.671,153.835,64.734,153.493,64.701,153.222,64.716,153.022,64.779,152.818,64.813,152.611,64.817,152.487,64.839,152.447,64.879,152.327,64.925,152.127,64.98,151.964,65.042,151.837,65.113,151.768,65.164,151.758,65.195,151.703,65.22,151.606,65.237,151.549,65.265,151.534,65.303,151.497,65.331,151.439,65.349,151.343,65.415,151.21,65.532,151.145,65.603,151.15,65.628,151.059,65.69,150.873,65.788,150.768,65.887,150.744,65.986,150.755,66.04,150.777,66.061,150.785,66.088,150.773,66.129,150.798,66.149,150.944,66.139,151.037,66.162,151.08,66.223,151.105,66.235,151.148,66.235,151.254,66.211,151.374,66.216,151.509,66.25,151.596,66.256,151.634,66.232,151.654,66.241,151.654,66.285,151.692,66.332,151.77,66.382,151.798,66.421,151.778,66.447,151.921,66.503,152.4,66.647,152.439,66.673,152.434,66.702,152.385,66.732,152.392,66.751,152.455,66.758,152.538,66.806,152.64,66.896,152.768,66.964,152.922,67.012,153.007,67.061,153.025,67.109,153.18,67.151,153.473,67.186,153.625,67.214,153.635,67.234,153.6,67.278,153.52,67.346,153.535,67.407,153.644,67.463,154.088,67.534,154.865,67.62,155.354,67.646,155.555,67.614,155.804,67.61,156.101,67.636,156.217,67.657,156.247,67.671,156.25,67.692,156.282,67.717,156.348,67.736,156.354,67.753,156.336,67.775,156.2,67.819,156.132,67.874,156.132,67.918,156.146,67.944,156.256,67.961,156.502,67.937,156.667,67.939,156.793,67.982,157.432,68.093,157.654,68.175,157.671,68.265,157.772,68.36,157.956,68.46,158.019,68.545,157.963,68.616,158.015,68.656,158.175,68.666,158.318,68.705,158.446,68.771,158.549,68.789,158.667,68.741,159.059,68.774,159.157,68.783,159.19,68.768,159.192,68.735,159.179,68.694,159.215,68.67,159.395,68.632,160.127,68.54,160.559,68.512,160.679,68.542,160.856,68.538]]},{"n":"Konqi","z":5,"b":[86.372,40.633,89.387,41.315],"l":[[89.387,40.633,88.92,40.672,88.715,40.69,88.422,40.744,88.286,40.815,88.127,40.86,87.943,40.877,87.787,40.91,87.659,40.958,87.529,40.974,87.397,40.957,87.258,40.971,87.114,41.016,87.025,41.069,86.991,41.13,86.899,41.205,86.749,41.294,86.598,41.315,86.372,41.243]]},{"n":"Koukourou","z":5,"b":[19.733,7.026,20.935,7.538],"l":[[20.935,7.343,20.829,7.26,20.767,7.234,20.713,7.239,20.673,7.212,20.647,7.154,20.597,7.105,20.487,7.046,20.383,7.026,20.304,7.034,20.213,7.061,20.106,7.117,19.982,7.203,19.874,7.319,19.733,7.538]]},{"n":"Koyukuk","z":5,"b":[-157.769,64.85,-150.726,68.043],"l":[[-150.821,68.043,-150.892,67.854,-150.988,67.747,-151.027,67.678,-151.041,67.611,-150.991,67.548,-150.878,67.49,-150.789,67.426,-150.726,67.357,-150.726,67.279,-150.791,67.193,-151.032,67.092,-151.451,66.975,-151.619,66.918,-151.676,66.857,-151.709,66.739,-151.779,66.658,-151.885,66.615,-152.032,66.58,-152.219,66.555,-152.396,66.552,-152.562,66.571,-152.734,66.551,-152.914,66.492,-153.074,66.46,-153.214,66.456,-153.296,66.438,-153.33,66.39,-153.416,66.411,-153.6,66.382,-153.909,66.308,-154.134,66.195,-154.276,66.043,-154.454,65.923,-154.668,65.834,-154.847,65.794,-154.991,65.802,-155.055,65.833,-155.088,65.852,-155.222,65.883,-155.272,65.91,-155.31,65.93,-155.363,65.921,-155.39,65.936,-155.393,65.962,-155.434,65.994,-155.521,65.998,-155.653,65.975,-155.769,65.98,-155.869,66.013,-156.01,66.022,-156.192,66.008,-156.219,65.987,-156.225,65.963,-156.25,65.947,-156.328,65.93,-156.348,65.911,-156.334,65.886,-156.349,65.833,-156.481,65.755,-156.503,65.726,-156.488,65.696,-156.475,65.674,-156.49,65.654,-156.471,65.63,-156.426,65.621,-156.391,65.617,-156.369,65.577,-156.346,65.559,-156.336,65.534,-156.348,65.516,-156.382,65.504,-156.388,65.487,-156.391,65.467,-156.426,65.455,-156.498,65.457,-156.57,65.458,-156.638,65.469,-156.629,65.491,-156.673,65.517,-156.77,65.549,-156.858,65.564,-156.935,65.564,-157.028,65.539,-157.137,65.49,-157.291,65.475,-157.492,65.492,-157.609,65.491,-157.643,65.47,-157.684,65.415,-157.676,65.396,-157.643,65.389,-157.624,65.364,-157.621,65.323,-157.642,65.299,-157.627,65.26,-157.652,65.216,-157.662,65.164,-157.647,65.084,-157.595,65.055,-157.506,65.078,-157.432,65.075,-157.375,65.044,-157.397,65.027,-157.499,65.026,-157.545,65.003,-157.535,64.961,-157.582,64.921,-157.74,64.867,-157.769,64.85]]},{"n":"Krishna","z":5,"b":[73.796,15.782,80.892,17.992],"l":[[79.313,16.579,79.657,16.69,79.818,16.703,79.912,16.655,79.993,16.678,80.063,16.773,80.121,16.768,80.169,16.664,80.262,16.607,80.401,16.597,80.524,16.553,80.63,16.475,80.717,16.393,80.784,16.307,80.834,16.201,80.883,16.014,80.892,15.971,80.892,15.866,80.878,15.82,80.865,15.782],[73.796,17.992,73.898,17.931,73.962,17.854,74.054,17.686,74.118,17.633,74.127,17.579,74.103,17.507,74.121,17.423,74.18,17.329,74.204,17.255,74.193,17.203,74.232,17.157,74.322,17.119,74.369,17.083,74.376,17.049,74.396,17.034,74.429,17.038,74.457,17.016,74.492,16.942,74.537,16.858,74.587,16.808,74.659,16.766,74.672,16.733,74.628,16.711,74.623,16.667,74.66,16.602,74.754,16.596,74.907,16.649,75.013,16.634,75.075,16.552,75.126,16.54,75.167,16.598,75.231,16.621,75.317,16.608,75.383,16.58,75.429,16.536,75.449,16.492,75.443,16.448,75.49,16.43,75.591,16.441,75.677,16.422,75.749,16.373,75.838,16.338,75.943,16.317,76.016,16.279,76.059,16.225,76.124,16.188,76.213,16.167,76.405,16.227,76.699,16.368,76.882,16.47,76.954,16.534,77.057,16.528,77.259,16.415,77.616,16.349,77.845,16.232,78.066,16.044,78.193,15.956,78.227,15.97,78.244,15.992,78.244,16.023,78.283,16.05,78.361,16.072,78.498,16.071,78.693,16.047,78.805,16.064,78.833,16.122,78.884,16.171,78.997,16.23]]},{"n":"Kuskokwim","z":5,"b":[-161.947,60.685,-154.721,63.079],"l":[[-154.721,63.079,-154.844,63.02,-154.926,63.006,-155.01,63.018,-155.125,63.005,-155.271,62.965,-155.368,62.962,-155.416,62.996,-155.492,63,-155.595,62.972,-155.619,62.949,-155.564,62.931,-155.605,62.886,-155.741,62.815,-155.817,62.766,-155.831,62.739,-155.823,62.72,-155.794,62.71,-155.775,62.678,-155.767,62.624,-155.776,62.586,-155.814,62.554,-156.025,62.466,-156.142,62.38,-156.236,62.268,-156.256,62.163,-156.203,62.065,-156.203,62.007,-156.256,61.988,-156.298,61.951,-156.327,61.895,-156.519,61.817,-156.873,61.715,-157.244,61.725,-157.632,61.848,-157.885,61.892,-158.004,61.856,-158.081,61.85,-158.118,61.873,-158.182,61.821,-158.273,61.693,-158.393,61.61,-158.544,61.573,-158.71,61.566,-158.892,61.59,-159.187,61.594,-159.801,61.573,-160.215,61.543,-160.415,61.504,-160.538,61.445,-160.604,61.388,-160.615,61.334,-160.652,61.306,-160.716,61.302,-160.755,61.28,-160.77,61.239,-160.846,61.187,-160.984,61.123,-161.068,61.058,-161.1,60.992,-161.175,60.938,-161.292,60.897,-161.386,60.885,-161.457,60.903,-161.487,60.893,-161.476,60.854,-161.483,60.827,-161.508,60.811,-161.555,60.816,-161.624,60.844,-161.661,60.838,-161.666,60.799,-161.694,60.78,-161.745,60.781,-161.762,60.768,-161.746,60.74,-161.774,60.721,-161.881,60.704,-161.947,60.685]]},{"n":"Kwango","z":5,"b":[16.508,-11.256,18.797,-3.2],"l":[[18.797,-11.256,18.707,-10.879,18.632,-10.731,18.622,-10.657,18.652,-10.609,18.61,-10.526,18.497,-10.41,18.48,-10.363,18.487,-10.339,18.496,-10.317,18.481,-10.281,18.495,-10.232,18.536,-10.17,18.588,-10.117,18.65,-10.072,18.677,-9.997,18.67,-9.891,18.648,-9.805,18.611,-9.74,18.546,-9.681,18.455,-9.63,18.361,-9.522,18.267,-9.357,18.189,-9.254,18.128,-9.211,18.091,-9.204,18.056,-9.191,18.043,-9.152,18.05,-9.115,18.042,-9.011,18.014,-8.875,17.938,-8.715,17.816,-8.531,17.708,-8.414,17.613,-8.363,17.559,-8.272,17.536,-8.076,17.245,-7.623,17.155,-7.461,17.122,-7.419,17.064,-7.363,16.985,-7.257,16.952,-7.157,16.966,-7.062,16.919,-6.934,16.813,-6.773,16.743,-6.618,16.709,-6.472,16.701,-6.346,16.718,-6.241,16.697,-6.164,16.64,-6.115,16.602,-6.04,16.577,-5.89,16.515,-5.391,16.508,-5.158,16.536,-5.023,16.536,-4.841,16.508,-4.611,16.508,-4.46,16.535,-4.386,16.536,-4.315,16.513,-4.245,16.587,-4.18,16.758,-4.119,16.87,-4.062,16.921,-4.011,16.993,-3.991,17.086,-4.003,17.183,-3.92,17.286,-3.742,17.322,-3.61,17.291,-3.525,17.298,-3.454,17.365,-3.369,17.376,-3.295,17.355,-3.2]]},{"n":"Kyzyl-Khem","z":4,"b":[95.78,51.295,98,51.519],"l":[[98,51.514,97.879,51.519,97.703,51.48,97.298,51.36,96.524,51.346,96.235,51.356,96.174,51.383,96.099,51.383,95.965,51.342,95.78,51.295]]},{"n":"La Grande Rivière","z":4,"b":[-78.946,53.406,-70.362,53.916],"l":[[-70.48,53.406,-70.42,53.408,-70.387,53.435,-70.362,53.487,-70.473,53.492,-70.845,53.428],[-71.117,53.591,-71.277,53.664,-71.452,53.678,-71.816,53.646],[-72.036,53.598,-72.094,53.602,-72.102,53.616,-72.078,53.64,-72.082,53.655,-72.113,53.659,-72.28,53.621,-72.35,53.63,-72.389,53.675,-72.503,53.691,-72.692,53.679,-72.727,53.704,-72.852,53.765,-73.143,53.866,-73.328,53.916,-73.407,53.914,-73.536,53.877,-73.714,53.805,-73.962,53.748,-74.439,53.684],[-77.42,53.798,-77.626,53.82,-77.826,53.806,-78.088,53.764,-78.39,53.762,-78.901,53.817,-78.924,53.823,-78.946,53.832]]},{"n":"Mekong","z":2.1,"b":[96.941,17.821,102.233,31.966],"l":[[96.941,31.966,97.055,31.692,97.121,31.577,97.175,31.528,97.207,31.463,97.215,31.381,97.204,31.325,97.173,31.296,97.168,31.233,97.188,31.134,97.243,31.05,97.332,30.979,97.377,30.89,97.378,30.785,97.495,30.618,97.846,30.278,97.963,30.162,98.037,30.046,98.109,29.89,98.161,29.81,98.194,29.806,98.242,29.763,98.307,29.68,98.363,29.568,98.411,29.427,98.484,29.307,98.582,29.209,98.619,29.126,98.595,29.058,98.593,29.009,98.624,28.963,98.67,28.772,98.727,28.632,98.809,28.478,98.863,28.304,98.889,28.109,98.938,27.959,99.012,27.856,99.036,27.768,99.01,27.695,99.005,27.623,99.028,27.516,99.088,27.377,99.104,27.308,99.094,27.265,99.11,27.215,99.151,27.16,99.175,27.071,99.182,26.948,99.165,26.801,99.124,26.632,99.114,26.531,99.135,26.498,99.141,26.38,99.134,26.176,99.139,26.021,99.157,25.918,99.213,25.792,99.31,25.644,99.341,25.521,99.308,25.423,99.341,25.315,99.44,25.198,99.525,25.129,99.599,25.109,99.7,25.017,99.828,24.853,99.939,24.762,100.031,24.743,100.087,24.718,100.106,24.687,100.14,24.69,100.187,24.727,100.251,24.747,100.332,24.75,100.418,24.682,100.511,24.545,100.548,24.388,100.529,24.211,100.485,24.108,100.416,24.078,100.388,24.026,100.401,23.953,100.356,23.854,100.252,23.73,100.185,23.62,100.139,23.473,100.067,23.278,100.106,23.149,100.233,23.023,100.313,22.887,100.347,22.744,100.415,22.636,100.517,22.563,100.567,22.502,100.565,22.451,100.588,22.398,100.636,22.343,100.647,22.306,100.622,22.288,100.632,22.268,100.677,22.247,100.705,22.205,100.715,22.141,100.77,22.047,100.869,21.923,100.984,21.845,101.115,21.813,101.166,21.789,101.134,21.774,101.128,21.705,101.147,21.582,101.08,21.469,100.928,21.366,100.82,21.314,100.757,21.313,100.703,21.251,100.659,21.13,100.614,21.059,100.567,21.038,100.536,20.992,100.522,20.922,100.549,20.884,100.618,20.879,100.623,20.86,100.565,20.825,100.493,20.813,100.407,20.823,100.326,20.796,100.249,20.73,100.184,20.589,100.13,20.372,100.115,20.258,100.14,20.245,100.174,20.273,100.218,20.34,100.266,20.377,100.318,20.386,100.373,20.34,100.432,20.241,100.492,20.184,100.553,20.17,100.589,20.073,100.597,19.893,100.653,19.825,100.755,19.87,100.857,19.878,100.959,19.849,101.037,19.848,101.091,19.875,101.153,19.872,101.223,19.838,101.333,19.822,101.484,19.823,101.699,19.89,101.979,20.022,102.137,20.087,102.173,20.083,102.205,20.058,102.233,20.011,102.141,19.903,101.93,19.734,101.823,19.604,101.822,19.513,101.842,19.451,101.885,19.419,101.886,19.372,101.845,19.31,101.819,19.181,101.808,18.984,101.816,18.855,101.841,18.794,101.826,18.698,101.77,18.568,101.658,18.422,101.488,18.262,101.408,18.119,101.419,17.992,101.459,17.902,101.564,17.821]]},{"n":"Lek","z":4,"b":[4.085,51.896,5.331,51.994],"l":[[5.331,51.963,5.161,51.994,4.999,51.977,4.787,51.925,4.596,51.898,4.426,51.896,4.28,51.919,4.085,51.994]]},{"n":"Lena","z":2,"b":[105.07,53.845,130.051,72.294],"l":[[120.648,60.403,120.343,60.396,120.184,60.35,120.069,60.262,119.874,60.229,119.599,60.252,119.355,60.191,119.142,60.046,118.959,59.966,118.805,59.949,118.678,59.917,118.575,59.87,118.51,59.824,118.482,59.778,118.42,59.745,118.323,59.725,117.998,59.805,117.447,59.986,117.129,60.134,117.046,60.249,116.964,60.331,116.884,60.381,116.669,60.439,116.321,60.505,116.031,60.598,115.798,60.717,115.503,60.775,115.146,60.773,114.893,60.745,114.743,60.692,114.549,60.567,114.311,60.369,114.137,60.26,114.029,60.241,113.823,60.125,113.519,59.912,113.24,59.779,112.983,59.728,112.836,59.674,112.798,59.618,112.745,59.578,112.643,59.545,112.543,59.363,112.394,59.274,112.161,59.215,112.01,59.197,111.942,59.218,111.846,59.187,111.723,59.104,111.549,59.019,111.323,58.93,111.202,58.847,111.186,58.769,111.117,58.739,110.993,58.758,110.949,58.746,110.929,58.71,110.805,58.659,110.56,58.662,110.282,58.645,109.97,58.606,109.798,58.539,109.764,58.445,109.694,58.359,109.587,58.279,109.498,58.231,109.427,58.215,109.341,58.225,109.239,58.261,109.154,58.275,109.085,58.264,108.94,58.21,108.721,58.113,108.606,58.044,108.594,58.001,108.553,57.972,108.482,57.956,108.409,57.907,108.332,57.825,108.229,57.785,108.1,57.787,108.034,57.759,108.029,57.702,107.957,57.659,107.904,57.635,107.894,57.61,107.918,57.57,107.872,57.523,107.697,57.438,107.619,57.408,107.593,57.386,107.584,57.357,107.565,57.343,107.519,57.346,107.487,57.329,107.471,57.292,107.415,57.271,107.318,57.264,107.251,57.276,107.213,57.308,107.149,57.326,107.059,57.328,106.989,57.31,106.937,57.27,106.918,57.232,106.93,57.196,106.912,57.146,106.865,57.083,106.747,57.043,106.558,57.026,106.425,57.028,106.348,57.051,106.256,57.011,106.15,56.909,105.986,56.833,105.764,56.783,105.672,56.742,105.712,56.711,105.831,56.696,106.031,56.698,106.173,56.671,106.256,56.617,106.292,56.56,106.281,56.501,106.25,56.451,106.201,56.411,106.058,56.366,105.822,56.316,105.695,56.266,105.676,56.217,105.718,56.152,105.821,56.07,105.852,55.976,105.811,55.869,105.731,55.755,105.613,55.633,105.52,55.49,105.45,55.326,105.331,55.197,105.163,55.103,105.076,55.008,105.07,54.913,105.108,54.85,105.19,54.82,105.234,54.754,105.241,54.649,105.224,54.55,105.185,54.456,105.221,54.384,105.332,54.334,105.457,54.238,105.665,54.022,105.806,54.006,105.873,53.973,105.913,53.916,105.974,53.878,106.056,53.858,106.305,53.857,106.927,53.884,106.996,53.85,107.053,53.845,107.119,53.858,107.159,53.894,107.171,53.952,107.234,53.999,107.347,54.036,107.456,54.105,107.562,54.205,107.657,54.265,107.783,54.294,108.021,54.246,108.107,54.2,108.118,54.138,108.088,54.082,107.98,54.004],[126.69,72.294,126.897,72.05,127.02,71.946,127.127,71.899,127.168,71.845,127.142,71.784,127.162,71.683,127.229,71.542,127.258,71.412,127.25,71.294,127.317,71.184,127.459,71.082,127.507,70.999,127.46,70.934,127.472,70.879,127.414,70.742,127.091,70.606,126.789,70.541,126.509,70.545,126.231,70.527,125.954,70.487,125.801,70.449,125.77,70.414,125.833,70.367,125.99,70.309,126.019,70.235,125.919,70.145,125.773,70.069,125.485,69.974,125.201,69.904,125.079,69.826,125.023,69.716,124.902,69.58,124.722,69.4,124.622,69.336,124.251,69.181,124.094,69.097,124.026,69.03,123.985,68.926,123.971,68.785,123.927,68.634,123.853,68.474,123.774,68.349,123.689,68.259,123.535,68.173,123.31,68.092,123.15,67.929,123.057,67.685,123.095,67.455,123.263,67.237,123.398,66.997,123.5,66.737,123.588,66.581,123.697,66.503,123.738,66.461,123.876,66.198,124.028,66.022,124.211,65.873,124.285,65.733,124.251,65.602,124.268,65.504,124.337,65.441,124.374,65.379,124.377,65.316,124.408,65.252,124.466,65.184,124.593,65.098,124.79,64.992,124.954,64.931,125.085,64.916,125.22,64.879,125.359,64.818,125.454,64.747,125.503,64.665,125.744,64.575,126.175,64.478,126.423,64.403,126.506,64.378,126.853,64.224,127.292,63.965,127.586,63.843,127.88,63.772,128.078,63.697,128.178,63.618,128.291,63.561,128.417,63.527,128.641,63.516,128.963,63.528,129.188,63.523,129.38,63.49,129.586,63.305,129.637,63.199,129.603,63.11,129.622,63.034,129.695,62.971,129.764,62.858,129.828,62.696,129.909,62.569,130.009,62.479,130.051,62.396,130.037,62.322,129.997,62.27,129.934,62.24,129.892,62.16,129.873,62.031,129.788,61.893,129.638,61.748,129.476,61.631,129.216,61.499,128.751,61.323,128.481,61.266,128.249,61.269,127.893,61.228,127.413,61.142,127.047,61.103,126.796,61.112,126.554,61.101,126.322,61.071,126.097,61.014,125.881,60.931,125.578,60.857,124.995,60.758,124.893,60.71,124.73,60.688,124.473,60.675,124.279,60.646,124.147,60.599,123.552,60.576,122.297,60.575,121.653,60.598,121.425,60.643,121.162,60.622,120.864,60.537,120.7,60.473,120.654,60.407]]},{"n":"Liao","z":5,"b":[122.14,40.688,123.831,43.483],"l":[[123.554,43.483,123.614,43.399,123.604,43.29,123.545,43.128,123.534,43.019,123.571,42.963,123.583,42.877,123.568,42.76,123.611,42.66,123.712,42.576,123.774,42.504,123.796,42.442,123.831,42.406,123.771,42.327,123.513,42.203,123.35,42.149,123.283,42.166,123.177,42.145,123.033,42.085,122.893,41.957,122.758,41.762,122.677,41.579,122.636,41.325,122.457,41.214,122.401,41.133,122.44,41.055,122.409,41.003,122.326,40.963,122.294,40.898,122.236,40.852,122.152,40.823,122.147,40.78,122.14,40.688]]},{"n":"Liard","z":5,"b":[-131.373,59.311,-121.221,61.864],"l":[[-131.373,61.204,-131.32,61.072,-131.246,61.043,-131.132,61.075,-131.044,61.082,-130.951,61.054,-130.619,60.862,-130.374,60.765,-130.105,60.698,-129.845,60.582,-129.593,60.417,-129.374,60.309,-129.185,60.26,-129.028,60.186,-128.904,60.087,-128.791,60.035,-128.691,60.029,-128.61,60.003,-128.547,59.956,-128.397,59.909,-128.161,59.863,-127.991,59.883,-127.888,59.971,-127.772,60.005,-127.644,59.986,-127.54,59.952,-127.462,59.902,-127.446,59.843,-127.494,59.773,-127.463,59.738,-127.354,59.738,-127.259,59.712,-127.138,59.636,-126.987,59.66,-126.835,59.65,-126.63,59.614,-126.404,59.55,-126.156,59.457,-125.979,59.425,-125.872,59.455,-125.81,59.456,-125.672,59.384,-125.447,59.322,-125.232,59.311,-125.028,59.349,-124.88,59.413,-124.789,59.503,-124.72,59.553,-124.682,59.616,-124.646,59.665,-124.554,59.729,-124.414,59.724,-124.225,59.651,-124.16,59.619,-124.146,59.561,-124.081,59.558,-124.048,59.594,-123.931,59.71,-123.86,59.853,-123.835,60.025,-123.713,60.165,-123.494,60.274,-123.42,60.366,-123.49,60.443,-123.534,60.523,-123.553,60.605,-123.552,60.666,-123.53,60.704,-123.468,60.735,-123.437,60.801,-123.395,60.893,-123.346,60.947,-123.326,60.974,-123.277,61.022,-123.182,61.078,-123.136,61.097,-123.003,61.101,-122.886,61.135,-122.753,61.218,-122.405,61.307,-121.842,61.403,-121.521,61.482,-121.442,61.544,-121.414,61.596,-121.434,61.64,-121.384,61.685,-121.263,61.731,-121.221,61.777,-121.28,61.864]]},{"n":"Limpopo","z":5,"b":[26.962,-25.189,33.631,-22.146],"l":[[26.962,-23.737,27.086,-23.578,27.146,-23.524,27.186,-23.523,27.241,-23.49,27.313,-23.424,27.399,-23.384,27.499,-23.368,27.563,-23.325,27.593,-23.253,27.644,-23.218,27.717,-23.22,27.758,-23.197,27.769,-23.149,27.813,-23.108,27.891,-23.074,27.931,-23.034,27.935,-22.987,28.028,-22.874,28.21,-22.694,28.382,-22.593,28.543,-22.573,28.696,-22.535,28.84,-22.481,28.946,-22.395,29.014,-22.278,29.13,-22.213,29.377,-22.193,29.663,-22.146,29.902,-22.184,30.19,-22.291,30.46,-22.329,30.712,-22.298,30.916,-22.291,31.073,-22.308,31.197,-22.345,31.288,-22.402,31.393,-22.437,31.512,-22.451,31.63,-22.506,31.749,-22.6,31.877,-22.74,32.015,-22.924,32.119,-23.036,32.188,-23.076,32.231,-23.133,32.247,-23.205,32.302,-23.302,32.396,-23.424,32.488,-23.636,32.577,-23.937,32.673,-24.14,32.825,-24.297,32.871,-24.404,32.906,-24.44,32.943,-24.441,33.01,-24.485,33.108,-24.572,33.183,-24.614,33.234,-24.609,33.264,-24.624,33.273,-24.657,33.329,-24.699,33.431,-24.751,33.485,-24.772,33.51,-24.771,33.526,-24.757,33.537,-24.745,33.552,-24.739,33.568,-24.751,33.597,-24.823,33.631,-24.949,33.614,-25.041,33.545,-25.098,33.515,-25.142,33.53,-25.189]]},{"n":"Lom","z":5,"b":[13.126,4.933,14.705,6.698],"l":[[14.665,6.698,14.705,6.661,14.687,6.585,14.596,6.393,14.471,6.302,14.431,6.222,14.432,6.124,14.417,6.042,14.386,5.977,14.314,5.926,14.201,5.89,14.13,5.814,14.1,5.699,14.067,5.64,14.029,5.637,13.991,5.607,13.951,5.549,13.91,5.522,13.866,5.528,13.848,5.499,13.858,5.435,13.801,5.372,13.678,5.311,13.595,5.299,13.528,5.358,13.466,5.381,13.423,5.362,13.378,5.307,13.364,5.234,13.381,5.141,13.359,5.056,13.299,4.978,13.233,4.938,13.126,4.933]]},{"n":"Nizhnyaya Tunguska","z":4.7,"b":[87.961,57.8,108.603,65.969],"l":[[105.689,58.068,105.809,58,105.877,57.982,105.962,57.999,106.199,58.028,106.319,58.011,106.402,57.958,106.534,57.908,106.89,57.826,107.057,57.804,107.173,57.805,107.238,57.828,107.317,57.827,107.409,57.8,107.504,57.816,107.6,57.874,107.687,57.902,107.762,57.899,107.892,57.961,108.075,58.089,108.195,58.152,108.251,58.149,108.342,58.211,108.467,58.34,108.506,58.435,108.461,58.497,108.458,58.547,108.498,58.585,108.484,58.644,108.416,58.721,108.406,58.781,108.452,58.822,108.466,58.903,108.448,59.025,108.392,59.126,108.252,59.247,108.134,59.395,108.074,59.446,108.033,59.449,108.023,59.485,108.045,59.553,108.018,59.61,107.941,59.657,107.963,59.726,108.084,59.818,108.099,59.893,108.091,59.936,108.104,60.002,108.094,60.051,108.062,60.085,108.062,60.123,108.038,60.205,107.897,60.246,107.819,60.292,107.803,60.343,107.811,60.391,107.842,60.437,107.841,60.493,107.872,60.524,107.895,60.554,107.893,60.593,107.866,60.624,107.867,60.679,107.891,60.721,107.956,60.84,107.98,60.914,107.965,60.942,107.93,60.956,107.875,60.955,107.854,60.995,107.866,61.074,107.909,61.117,107.982,61.126,108.007,61.145,107.985,61.175,107.959,61.231,107.967,61.269,108.029,61.327,108.036,61.379,107.988,61.423,107.978,61.467,108.006,61.511,108.008,61.552,108.053,61.597,108.085,61.635,108.072,61.674,108.04,61.7,108.013,61.729,108,61.752,108.014,61.815,108.034,61.879,108.048,61.929,108.089,61.971,108.223,62.034,108.245,62.085,108.221,62.135,108.225,62.191,108.279,62.256,108.29,62.333,108.256,62.421,108.225,62.468,108.196,62.471,108.179,62.451,108.173,62.406,108.14,62.373,108.08,62.35,108.021,62.355,107.962,62.388,107.922,62.442,107.95,62.527,108.062,62.583,108.407,62.641,108.587,62.681,108.603,62.704,108.595,62.729,108.563,62.755,108.445,62.763,108.239,62.753,108.17,62.779,108.237,62.841,108.366,62.878,108.555,62.887,108.592,62.919,108.552,62.948,108.471,62.955,108.369,62.961,108.317,62.974,108.188,62.973,108.119,62.99,108.112,63.026,108.144,63.064,108.216,63.102,108.218,63.143,108.151,63.188,108.078,63.203,108,63.189,107.949,63.148,107.927,63.078,107.887,63.054,107.829,63.078,107.805,63.111,107.815,63.153,107.772,63.166,107.676,63.15,107.611,63.159,107.576,63.192,107.525,63.21,107.455,63.214,107.422,63.236,107.372,63.272,107.278,63.304,106.829,63.334,106.809,63.35,106.839,63.38,106.918,63.424,106.925,63.471,106.859,63.519,106.741,63.538,106.569,63.526,106.441,63.495,106.357,63.444,106.266,63.437,106.172,63.445,106.099,63.42,106.07,63.375,106.026,63.367,105.818,63.392,105.646,63.381,105.51,63.333,105.385,63.327,105.273,63.362,105.183,63.408,105.117,63.464,104.775,63.517,104.157,63.568,103.807,63.625,103.725,63.687,103.705,63.734,103.637,63.785,103.487,63.82,103.271,63.899,103.03,64.032,102.831,64.103,102.674,64.111,102.539,64.098,102.373,64.047,101.994,64.094,101.784,64.14,101.617,64.203,101.432,64.202,101.228,64.137,101.023,64.13,100.818,64.181,100.671,64.2,100.581,64.187,100.487,64.211,100.389,64.272,100.278,64.294,100.153,64.277,100.037,64.232,99.929,64.158,99.443,64.132,98.578,64.155,98.124,64.154,98.083,64.13,98.097,64.101,98.168,64.066,98.189,64.03,98.159,63.993,98.073,63.96,97.932,63.933,97.906,63.889,97.998,63.828,98.03,63.785,98.003,63.761,97.78,63.775,97.361,63.825,96.993,63.837,96.675,63.809,96.476,63.818,96.397,63.865,96.271,63.892,96.1,63.901,95.887,63.945,95.632,64.025,95.429,64.059,95.278,64.046,95.078,64.079,94.829,64.16,94.519,64.191,94.147,64.172,93.714,64.232,93.22,64.373,92.831,64.438,92.548,64.427,92.382,64.404,92.334,64.368,92.252,64.366,92.08,64.412],[87.961,65.776,88.132,65.83,88.228,65.844,88.307,65.835,88.386,65.855,88.466,65.904,88.616,65.942,88.836,65.969,89.211,65.904,89.74,65.748,90.018,65.656,90.045,65.627,90.035,65.582,89.989,65.521,89.974,65.399,89.992,65.216,90.046,65.116,90.137,65.099,90.212,65.066,90.274,65.017,90.304,64.956,90.302,64.883,90.337,64.831,90.406,64.8,90.514,64.782,90.66,64.776,90.824,64.718,91.007,64.608,91.345,64.518,92.081,64.411]]},{"n":"Lualaba","z":2.1,"b":[25.413,-11.743,26.441,-8.272],"l":[[26.249,-11.743,26.166,-11.672,26.119,-11.658,26.08,-11.678,26.03,-11.68,25.938,-11.653,25.886,-11.623,25.84,-11.547,25.782,-11.417,25.715,-11.331,25.639,-11.29,25.602,-11.247,25.604,-11.2,25.59,-11.169,25.56,-11.153,25.552,-11.114,25.566,-11.054,25.611,-11.005,25.684,-10.969,25.726,-10.878,25.735,-10.731,25.696,-10.607,25.61,-10.507,25.541,-10.467,25.49,-10.486,25.449,-10.485,25.417,-10.462,25.414,-10.407,25.441,-10.319,25.44,-10.23,25.413,-10.137,25.441,-10.036,25.525,-9.926,25.547,-9.811,25.506,-9.691,25.489,-9.587,25.502,-9.455,25.561,-9.266,25.611,-9.229,25.67,-9.278,25.729,-9.276,25.786,-9.221,25.843,-9.197,25.901,-9.204,25.966,-9.137,26.037,-8.996,26.058,-8.881,26.031,-8.792,26.086,-8.705,26.223,-8.621,26.284,-8.544,26.268,-8.474,26.282,-8.42,26.326,-8.383,26.346,-8.347,26.341,-8.313,26.364,-8.29,26.441,-8.272]]},{"n":"Congo","z":2.1,"b":[24.292,-5.61,27.014,0.782],"l":[[26.93,-5.61,27.003,-5.378,27.014,-5.286,26.988,-5.256,26.959,-5.123,26.927,-4.885,26.879,-4.731,26.813,-4.662,26.652,-4.55,26.395,-4.397,26.199,-4.25,26.065,-4.11,26.012,-3.958,26.04,-3.796,26.013,-3.649,25.93,-3.519,25.904,-3.363,25.935,-3.18,25.949,-3.023,25.946,-2.89,25.927,-2.804,25.876,-2.745],[25.876,-2.752,25.788,-2.457,25.785,-2.113,25.837,-1.623,25.818,-1.286,25.682,-1.012,25.534,-0.816,25.465,-0.682,25.423,-0.545,25.443,-0.386,25.525,-0.204,25.554,-0.036,25.529,0.121,25.489,0.235,25.409,0.346,25.255,0.459,25.136,0.507,24.934,0.539,24.791,0.577,24.69,0.628,24.584,0.705,24.472,0.753,24.292,0.782]]},{"n":"Mackenzie","z":2.1,"b":[-135.638,61.248,-117.668,69.324],"l":[[-117.668,61.308,-117.706,61.367,-117.86,61.415,-118.143,61.47,-118.323,61.455,-118.402,61.369,-118.7,61.3,-119.22,61.248,-119.773,61.304,-120.36,61.468,-120.673,61.567,-120.714,61.603,-120.718,61.648,-120.686,61.704,-120.684,61.752,-120.713,61.792,-120.868,61.821,-121.148,61.839,-121.28,61.864,-121.363,61.88,-121.589,61.974,-122.235,62.118,-122.603,62.167,-122.906,62.17,-123.139,62.204,-123.388,62.31,-123.402,62.326,-123.356,62.405,-123.25,62.548,-123.204,62.709,-123.218,62.889,-123.254,63.024,-123.311,63.113,-123.419,63.199,-123.577,63.28,-123.683,63.379,-123.736,63.493,-123.83,63.63,-123.966,63.79,-124.084,63.897,-124.183,63.952,-124.266,63.978,-124.333,63.977,-124.39,63.999,-124.438,64.046,-124.451,64.104,-124.429,64.174,-124.448,64.232,-124.506,64.278,-124.584,64.311,-124.682,64.333,-124.789,64.405,-124.906,64.526,-124.947,64.615,-124.912,64.671,-124.943,64.707,-125.039,64.724,-125.109,64.764,-125.153,64.826,-125.375,64.875,-125.619,64.895,-125.775,64.908,-126.027,64.956,-126.182,65.049,-126.459,65.177,-126.646,65.238,-126.929,65.291,-127.061,65.295,-127.337,65.375,-128.03,65.608,-128.401,65.62,-128.588,65.643,-128.715,65.681,-128.75,65.739,-128.817,65.791,-128.97,65.867,-129.065,65.95,-129.102,66.04,-129.093,66.109,-129.038,66.155,-128.923,66.202,-128.748,66.251,-128.666,66.292,-128.678,66.327,-128.745,66.358,-128.867,66.385,-129.089,66.48,-129.411,66.644,-129.699,66.75,-129.955,66.797,-130.118,66.86,-130.189,66.941,-130.236,67.046,-130.26,67.174,-130.306,67.258,-130.375,67.296,-130.496,67.325,-130.705,67.34,-130.759,67.354,-130.808,67.374,-130.882,67.42,-130.956,67.461,-131.16,67.489,-131.437,67.473,-131.907,67.403,-132.569,67.278,-133.02,67.245,-133.26,67.304,-133.433,67.371,-133.539,67.444,-133.64,67.479,-133.737,67.477,-133.887,67.526,-134.091,67.625,-134.199,67.718,-134.212,67.803,-134.277,67.889,-134.394,67.976,-134.448,68.073,-134.44,68.177,-134.409,68.231,-134.366,68.292,-134.33,68.314,-134.272,68.318,-134.199,68.345,-134.125,68.392,-134.099,68.429,-134.075,68.517,-134.103,68.576,-134.244,68.706],[-134.244,68.706,-134.391,68.706,-134.539,68.77,-134.836,68.964],[-134.244,68.706,-134.545,68.919,-134.626,69.017,-134.586,69.071,-134.503,69.111,-134.313,69.154,-134.245,69.192,-134.174,69.253],[-134.836,68.964,-135.034,68.908,-135.131,68.903,-135.195,68.93,-135.322,68.936,-135.606,68.911,-135.638,68.892],[-134.836,68.964,-135.037,69.062,-135.076,69.11,-135.02,69.139,-135.039,69.181,-135.183,69.263,-135.255,69.324]]},{"n":"Madeira","z":3,"b":[-65.422,-10.392,-58.689,-3.342],"l":[[-65.395,-10.392,-65.313,-10.253,-65.299,-10.147,-65.325,-10.027,-65.328,-9.936,-65.309,-9.873,-65.338,-9.79,-65.414,-9.689,-65.422,-9.611,-65.361,-9.556,-65.233,-9.546,-65.038,-9.581,-64.914,-9.583,-64.863,-9.554,-64.833,-9.499,-64.827,-9.416,-64.802,-9.372,-64.759,-9.367,-64.701,-9.32,-64.627,-9.231,-64.534,-9.185,-64.423,-9.181,-64.341,-9.138,-64.287,-9.056,-64.216,-9.002,-64.127,-8.976,-64.08,-8.937,-64.075,-8.884,-64.035,-8.832,-63.96,-8.783,-63.919,-8.729,-63.913,-8.672,-63.849,-8.619,-63.728,-8.572,-63.636,-8.556,-63.571,-8.571,-63.547,-8.555,-63.562,-8.507,-63.545,-8.466,-63.495,-8.432,-63.438,-8.358,-63.343,-8.186,-63.245,-8.215,-63.131,-8.177,-62.888,-8.034,-62.867,-7.945,-62.9,-7.803,-62.98,-7.577,-62.98,-7.351,-62.898,-7.123,-62.824,-7.004,-62.757,-6.994,-62.688,-6.934,-62.615,-6.823,-62.529,-6.762,-62.429,-6.752,-62.367,-6.723,-62.344,-6.675,-62.344,-6.634,-62.367,-6.598,-62.348,-6.538,-62.288,-6.454,-62.25,-6.367,-62.234,-6.278,-62.184,-6.23,-62.126,-6.222,-62.092,-6.218,-62.058,-6.22,-61.905,-6.236,-61.844,-6.21,-61.827,-6.147,-61.78,-6.086,-61.706,-6.026,-61.668,-5.973,-61.668,-5.927,-61.648,-5.901,-61.609,-5.896,-61.599,-5.881,-61.62,-5.856,-61.549,-5.838,-61.385,-5.827,-61.289,-5.781,-61.258,-5.699,-61.117,-5.615,-60.865,-5.529,-60.734,-5.448,-60.725,-5.37,-60.647,-5.293,-60.529,-5.23,-60.459,-5.199,-60.425,-5.176,-60.388,-5.147,-60.362,-5.103,-60.26,-4.977,-60.171,-4.913,-60.105,-4.917,-60.037,-4.897,-59.966,-4.851,-59.931,-4.791,-59.931,-4.714,-59.914,-4.671,-59.883,-4.595,-59.786,-4.432,-59.699,-4.367,-59.648,-4.364,-59.603,-4.37,-59.574,-4.366,-59.548,-4.357,-59.521,-4.342,-59.489,-4.318,-59.427,-4.25,-59.362,-4.156,-59.328,-4.045,-59.253,-3.952,-59.138,-3.875,-59.083,-3.798,-59.09,-3.72,-58.997,-3.596,-58.689,-3.342]]},{"n":"Madison","z":2,"b":[-111.719,44.653,-110.744,46.36],"l":[[-110.744,44.727,-110.859,44.668,-110.933,44.653,-111.004,44.662,-111.054,44.689,-111.091,44.747,-111.1,44.757],[-111.309,44.868,-111.329,44.867,-111.406,44.843,-111.477,44.852,-111.553,44.892,-111.626,44.988,-111.694,45.141,-111.719,45.263,-111.701,45.354,-111.646,45.458,-111.553,45.573,-111.493,45.722,-111.464,45.904,-111.43,46.025,-111.39,46.085,-111.406,46.177,-111.516,46.36]]},{"n":"Madre de Dios","z":5,"b":[-72.165,-12.661,-65.395,-10.392],"l":[[-72.165,-12.182,-72.094,-12.063,-72.093,-11.96,-72.139,-11.835,-72.117,-11.742,-72.028,-11.683,-71.931,-11.659,-71.776,-11.675,-71.604,-11.688,-71.533,-11.716,-71.503,-11.765,-71.399,-11.848,-71.221,-11.965,-71.086,-12.08,-70.997,-12.192,-70.91,-12.259,-70.825,-12.278,-70.754,-12.33,-70.695,-12.412,-70.59,-12.489,-70.438,-12.561,-70.319,-12.598,-70.232,-12.6,-70.18,-12.589,-70.155,-12.565,-70.115,-12.568,-70.026,-12.586,-69.768,-12.655,-69.578,-12.661,-69.455,-12.603,-69.391,-12.559,-69.344,-12.554,-69.309,-12.568,-69.268,-12.582,-69.235,-12.575,-69.215,-12.552,-69.187,-12.548,-69.156,-12.573,-69.107,-12.574,-69.063,-12.543,-69.008,-12.534,-68.949,-12.556,-68.859,-12.548,-68.678,-12.495,-68.606,-12.429,-68.556,-12.409,-68.503,-12.414,-68.455,-12.373,-68.412,-12.286,-68.354,-12.22,-68.282,-12.176,-68.239,-12.12,-68.223,-12.053,-68.16,-12.005,-68.051,-11.974,-67.976,-11.897,-67.936,-11.773,-67.881,-11.704,-67.811,-11.688,-67.749,-11.635,-67.695,-11.546,-67.641,-11.494,-67.588,-11.481,-67.519,-11.498,-67.436,-11.545,-67.36,-11.551,-67.293,-11.514,-67.239,-11.463,-67.198,-11.397,-67.127,-11.36,-66.976,-11.344,-66.96,-11.294,-66.916,-11.276,-66.838,-11.274,-66.803,-11.254,-66.786,-11.222,-66.749,-11.23,-66.673,-11.205,-66.567,-11.132,-66.48,-11.095,-66.413,-11.095,-66.37,-11.062,-66.343,-11.026,-66.315,-11.018,-66.276,-11.007,-66.214,-10.95,-66.158,-10.947,-66.118,-10.957,-66.079,-10.937,-66.065,-10.893,-66.061,-10.861,-66.092,-10.848,-66.084,-10.828,-66.036,-10.802,-65.983,-10.795,-65.924,-10.806,-65.902,-10.78,-65.897,-10.725,-65.874,-10.696,-65.82,-10.697,-65.78,-10.667,-65.768,-10.598,-65.74,-10.566,-65.696,-10.57,-65.604,-10.527,-65.395,-10.392]]},{"n":"Magdalena","z":4,"b":[-76.507,1.896,-73.765,11.11],"l":[[-76.507,1.943,-76.281,1.896,-76.179,1.898,-76.097,1.948,-75.791,2.118,-75.69,2.198,-75.692,2.246,-75.662,2.297,-75.599,2.352,-75.572,2.401,-75.583,2.446,-75.518,2.57,-75.376,2.774,-75.292,2.981,-75.266,3.19,-75.159,3.461,-74.971,3.793,-74.858,4.038,-74.82,4.194,-74.824,4.274,-74.869,4.278,-74.861,4.422,-74.771,4.848,-74.756,5.189,-74.723,5.367,-74.668,5.498,-74.646,5.615,-74.657,5.72,-74.641,5.829,-74.596,5.944,-74.576,6.055,-74.579,6.162,-74.531,6.265,-74.433,6.363,-74.385,6.465,-74.386,6.573,-74.317,6.667,-74.178,6.749,-74.053,6.848,-73.943,6.962,-73.9,7.078,-73.925,7.195,-73.907,7.385,-73.846,7.648,-73.83,7.846,-73.858,7.979,-73.852,8.071,-73.79,8.15],[-73.796,8.15,-73.765,8.329,-73.784,8.535,-73.843,8.827,-73.942,8.992,-74.083,9.03,-74.223,9.104,-74.361,9.213,-74.456,9.261,-74.507,9.249,-74.559,9.278,-74.611,9.347,-74.663,9.391,-74.742,9.418,-74.786,9.441,-74.795,9.494,-74.786,9.586,-74.806,9.691,-74.854,9.81,-74.86,9.909,-74.823,9.986,-74.84,10.06,-74.912,10.13,-74.892,10.275,-74.781,10.495,-74.727,10.684,-74.728,10.841,-74.761,10.972,-74.845,11.11]]},{"n":"Mahäna Nadï","z":5,"b":[80.625,20.053,86.245,21.863],"l":[[80.625,20.599,80.63,20.715,80.665,20.765,80.763,20.798,80.889,20.97,81.006,21.046,81.156,21.083,81.271,21.22,81.35,21.456,81.428,21.588,81.543,21.632,81.641,21.59,81.681,21.622,81.694,21.713,81.745,21.764,81.834,21.776,81.92,21.809,82.003,21.863,82.114,21.848,82.255,21.762,82.376,21.728,82.479,21.745,82.723,21.739,83.3,21.695],[83.857,21.52,83.949,21.454,83.943,21.337,83.83,21.055,83.832,20.962,83.866,20.907,83.932,20.86,84.046,20.846,84.21,20.867,84.31,20.847,84.346,20.787,84.444,20.73,84.603,20.674,84.708,20.615,84.757,20.552,84.922,20.48,85.344,20.357,85.665,20.443,85.832,20.445,85.952,20.393,86.048,20.311,86.159,20.143,86.177,20.094,86.245,20.053]]},{"n":"Malyy Yenisey","z":4,"b":[94.465,51.295,95.78,51.734],"l":[[95.78,51.295,95.558,51.476,95.23,51.585,94.465,51.734]]},{"n":"Mamberamo","z":5,"b":[137.815,-3.202,138.702,-1.484],"l":[[138.702,-3.191,138.668,-3.202,138.623,-3.198,138.598,-3.158,138.592,-3.027,138.557,-2.949,138.494,-2.925,138.453,-2.854,138.434,-2.736,138.407,-2.654,138.356,-2.587,138.221,-2.419,138.123,-2.342,138.018,-2.3,137.923,-2.223,137.838,-2.109,137.815,-2.027,137.852,-1.977,137.858,-1.909,137.834,-1.822,137.838,-1.769,137.852,-1.735,137.844,-1.699,137.849,-1.649,137.911,-1.484]]},{"n":"Mamoré","z":3,"b":[-65.304,-15.86,-64.664,-11.913],"l":[[-64.664,-15.86,-64.72,-15.799,-64.769,-15.676,-64.828,-15.469,-64.89,-15.323,-64.954,-15.237,-64.998,-15.157,-65.021,-15.083,-65.02,-15.025,-64.996,-14.981,-65.017,-14.892,-65.082,-14.756,-65.087,-14.717,-65.066,-14.697,-65.025,-14.615,-64.978,-14.446,-64.976,-14.327,-65.019,-14.257,-65.038,-14.199,-65.034,-14.153,-65.062,-14.086,-65.121,-13.997,-65.167,-13.952,-65.199,-13.949,-65.211,-13.917,-65.201,-13.855,-65.232,-13.781,-65.304,-13.694,-65.302,-13.533,-65.226,-13.299,-65.191,-13.204,-65.183,-13.144,-65.137,-13.112,-65.118,-13.053,-65.099,-13.022,-65.056,-12.998,-65.029,-12.937,-65.017,-12.839,-65.053,-12.694,-65.137,-12.501,-65.169,-12.39,-65.15,-12.361,-65.151,-12.335,-65.171,-12.311,-65.169,-12.292,-65.142,-12.268,-65.145,-12.235,-65.173,-12.165,-65.155,-12.126,-65.122,-12.111,-65.114,-12.081,-65.126,-12.027,-65.114,-11.986,-65.061,-11.955,-65.031,-11.913]]},{"n":"Mananantanana","z":5,"b":[45.653,-22.011,47.167,-21.275],"l":[[47.167,-22.011,47.164,-21.973,47.101,-21.939,46.976,-21.896,46.905,-21.852,46.879,-21.786,46.73,-21.736,46.675,-21.653,46.665,-21.543,46.637,-21.513,46.596,-21.473,46.574,-21.464,46.493,-21.38,46.345,-21.302,46.186,-21.275,46.016,-21.301,45.898,-21.348,45.829,-21.416,45.76,-21.444,45.653,-21.428]]},{"n":"Mangoky","z":5,"b":[43.542,-21.89,45.653,-21.316],"l":[[45.653,-21.428,45.514,-21.44,45.425,-21.501,45.293,-21.672,44.806,-21.724,44.565,-21.77,44.409,-21.828,44.302,-21.845,44.244,-21.818,44.166,-21.829,44.066,-21.876,43.986,-21.89,43.914,-21.872,43.895,-21.861,43.874,-21.838,43.785,-21.637,43.697,-21.501,43.542,-21.316]]},{"n":"Damqogkanbab","z":2.1,"b":[82.4,29.126,87.66,30.411],"l":[[82.4,30.411,82.723,30.365,82.825,30.371,82.886,30.336,82.939,30.261,83.164,30.126,83.761,29.833,83.925,29.761,83.983,29.711,83.987,29.659,84.014,29.617,84.063,29.586,84.189,29.555,84.392,29.525,84.501,29.452,84.517,29.335,84.634,29.254,84.854,29.209,85.014,29.221,85.115,29.291,85.226,29.313,85.409,29.274,85.592,29.204,85.867,29.195,86.295,29.225,86.734,29.209,87.184,29.148,87.472,29.126,87.66,29.152]]},{"n":"Marañón","z":4.7,"b":[-78.716,-9.913,-73.489,-4.433],"l":[[-76.617,-9.913,-76.711,-9.64,-76.765,-9.473,-76.766,-9.365,-76.733,-9.26,-76.835,-9.117,-77.073,-8.937,-77.224,-8.769,-77.29,-8.612,-77.346,-8.522,-77.392,-8.496,-77.437,-8.424,-77.483,-8.304,-77.534,-8.215,-77.592,-8.158,-77.621,-8.057,-77.622,-7.91,-77.719,-7.628,-77.909,-7.21,-78.01,-6.936,-78.02,-6.805,-78.062,-6.67,-78.138,-6.529,-78.219,-6.432,-78.306,-6.378,-78.374,-6.301,-78.45,-6.15,-78.568,-6.089,-78.643,-6.017,-78.716,-5.915,-78.703,-5.762,-78.603,-5.561,-78.528,-5.446,-78.479,-5.417,-78.456,-5.355,-78.459,-5.259,-78.434,-5.198,-78.382,-5.171,-78.366,-5.114,-78.387,-5.027,-78.382,-4.964,-78.35,-4.924,-78.335,-4.871,-78.336,-4.804,-78.305,-4.762,-78.242,-4.744,-78.207,-4.708,-78.202,-4.653,-78.163,-4.632,-78.091,-4.646,-77.958,-4.598,-77.763,-4.488,-77.623,-4.433,-77.538,-4.434,-77.473,-4.45,-77.43,-4.481,-77.408,-4.526,-77.41,-4.584,-77.387,-4.617,-77.342,-4.623,-77.333,-4.642,-77.344,-4.651,-77.338,-4.683,-77.307,-4.729,-77.282,-4.732,-77.263,-4.69,-77.169,-4.708,-77.002,-4.784,-76.832,-4.822,-76.659,-4.823,-76.566,-4.84,-76.552,-4.873,-76.511,-4.901,-76.442,-4.926,-76.352,-4.935,-76.24,-4.928,-76.173,-4.944,-76.137,-4.999,-75.841,-5.039,-75.723,-5.043,-75.686,-5.025,-75.654,-5.031,-75.629,-5.062,-75.559,-5.006,-75.445,-4.865,-75.259,-4.78,-75.002,-4.751,-74.847,-4.691,-74.796,-4.6,-74.722,-4.529,-74.625,-4.478,-74.549,-4.485,-74.492,-4.549,-74.413,-4.606,-74.312,-4.654,-74.247,-4.638,-74.218,-4.556,-74.177,-4.502,-74.125,-4.477,-74.053,-4.474,-73.962,-4.491,-73.888,-4.531,-73.829,-4.592,-73.724,-4.579,-73.489,-4.445]]},{"n":"Mekong","z":2.1,"b":[101.564,10.001,106.136,18.423],"l":[[104.951,11.591,104.951,11.508,104.976,11.472,105.026,11.453,105.041,11.428,105.023,11.395,105.044,11.212,105.105,10.879,105.311,10.535,105.831,10.001],[104.951,11.591,105.181,11.446,105.266,11.356,105.282,11.274,105.266,11.18,105.218,11.075,105.199,10.978,105.21,10.89,105.249,10.825,105.315,10.785,105.358,10.723,105.377,10.641,105.49,10.529,105.697,10.39,105.879,10.299,105.931,10.284,106.136,10.222],[101.564,17.821,101.687,17.889,101.744,17.953,101.775,18.033,101.819,18.065,101.875,18.046,101.947,18.081,102.035,18.17,102.101,18.211,102.148,18.204,102.232,18.149,102.352,18.046,102.459,17.985,102.553,17.965,102.598,17.927,102.596,17.87,102.617,17.833,102.661,17.818,102.68,17.824,102.675,17.852,102.718,17.892,102.807,17.946,102.899,17.977,102.991,17.986,103.051,18.029,103.091,18.138],[103.09,18.137,103.091,18.138],[103.091,18.138,103.092,18.141],[103.091,18.138,103.149,18.222,103.2,18.259,103.263,18.278,103.28,18.305,103.249,18.339,103.252,18.373,103.288,18.408,103.367,18.423,103.488,18.418,103.63,18.383,103.792,18.316,103.899,18.295,103.95,18.319,104.049,18.217,104.196,17.988,104.323,17.816,104.428,17.699,104.539,17.609,104.656,17.547,104.74,17.462,104.816,17.3,104.759,17.077,104.744,16.884,104.751,16.648,104.819,16.466,104.95,16.34,105.026,16.238,105.047,16.16,105.149,16.094,105.331,16.038,105.406,15.987,105.376,15.942,105.373,15.89,105.399,15.83,105.462,15.78,105.562,15.741,105.622,15.7,105.641,15.657,105.639,15.586,105.616,15.488,105.574,15.413,105.513,15.361,105.52,15.331,105.595,15.324,105.666,15.273,105.782,15.121,105.868,15.033,105.907,14.944,105.926,14.826,105.915,14.713,105.873,14.606,105.86,14.495,105.873,14.38,105.864,14.267,105.832,14.158,105.855,14.07,105.934,14.005,105.981,13.948,105.996,13.9,105.996,13.852,105.982,13.803,105.991,13.752,106.021,13.698,106.005,13.609,105.94,13.486,105.918,13.405,105.949,13.35,106.023,13.196,106.025,13.061,105.981,12.893,105.978,12.683,106.018,12.428,105.985,12.289,105.88,12.266,105.763,12.264,105.636,12.284,105.543,12.204,105.486,12.025,105.389,11.936,105.252,11.939,105.168,11.92,105.137,11.88,105.09,11.855,105.025,11.845,105,11.822,105.013,11.786,105.002,11.747,104.965,11.706,104.948,11.662,104.951,11.591]]},{"n":"Mendoza","z":5,"b":[-70.136,-32.355,-69.418,-31.283],"l":[[-70.136,-32.238,-70.043,-32.334,-69.97,-32.355,-69.889,-32.332,-69.833,-32.281,-69.803,-32.202,-69.796,-32.144,-69.81,-32.106,-69.787,-32.033,-69.698,-31.871,-69.589,-31.782,-69.521,-31.693,-69.458,-31.574,-69.427,-31.455,-69.429,-31.304,-69.418,-31.283]]},{"n":"Meta","z":5,"b":[-73.917,3.958,-67.423,6.29],"l":[[-73.872,4.67,-73.909,4.608,-73.917,4.541,-73.908,4.447,-73.886,4.365,-73.831,4.258,-73.741,4.193,-73.719,4.142,-73.733,4.083,-73.571,4.022,-73.233,3.96,-73.034,3.958,-72.976,4.014,-72.927,4.1,-72.885,4.215,-72.836,4.282,-72.748,4.313,-72.479,4.332,-72.373,4.356,-72.34,4.392,-72.28,4.42,-72.193,4.44,-72.116,4.438,-72.047,4.412,-71.962,4.443,-71.86,4.533,-71.661,4.637,-71.364,4.756,-71.174,4.844,-71.09,4.901,-71.027,4.977,-70.982,5.071,-70.894,5.167,-70.762,5.265,-70.687,5.336,-70.671,5.382,-70.52,5.465,-70.234,5.585,-70.03,5.743,-69.907,5.939,-69.768,6.043,-69.611,6.056,-69.507,6.077,-69.432,6.122,-69.357,6.148,-69.311,6.138,-69.268,6.1,-69.195,6.115,-69.09,6.184,-68.937,6.198,-68.736,6.157,-68.472,6.157,-68.143,6.198,-67.939,6.242,-67.859,6.29,-67.727,6.285,-67.423,6.202]]},{"n":"Min","z":5,"b":[103.719,28.779,104.62,29.537],"l":[[103.719,29.537,103.767,29.517,103.832,29.386,103.866,29.316,103.857,29.29,103.926,29.224,104.145,29.062,104.251,28.996,104.291,28.948,104.302,28.896,104.38,28.849,104.62,28.779]]},{"n":"Mississippi","z":2,"b":[-95.026,28.981,-89.016,47.482],"l":[[-89.376,28.981,-89.257,29.155,-89.016,29.203],[-89.261,29.155,-89.289,29.178],[-89.289,29.178,-89.181,29.336],[-89.289,29.178,-89.433,29.294,-89.519,29.347,-89.616,29.387,-89.666,29.416,-89.67,29.444,-89.714,29.486,-89.758,29.506,-89.869,29.608,-89.973,29.689,-90.007,29.754,-90.012,29.811,-89.987,29.86,-89.958,29.876,-89.932,29.878,-89.923,29.888,-89.923,29.903,-89.933,29.914,-90.041,29.932,-90.25,29.943,-90.39,29.976,-90.464,30.032,-90.582,30.048,-90.744,30.023,-90.849,30.04,-90.878,30.075,-90.897,30.098,-90.944,30.127,-90.99,30.126,-91.021,30.147,-91.038,30.181,-91.074,30.204,-91.11,30.209,-91.133,30.227,-91.13,30.248,-91.148,30.268,-91.179,30.285,-91.191,30.307,-91.187,30.328,-91.195,30.352,-91.226,30.368,-91.235,30.402,-91.218,30.479,-91.226,30.505,-91.258,30.534,-91.257,30.565,-91.276,30.615,-91.338,30.706,-91.388,30.744,-91.423,30.753,-91.529,30.756,-91.557,30.78,-91.547,30.815,-91.523,30.854,-91.526,30.871,-91.546,30.908,-91.618,30.938,-91.646,30.952,-91.66,30.972,-91.657,30.996,-91.653,31.03,-91.611,31.065,-91.596,31.11,-91.631,31.218,-91.618,31.277,-91.559,31.288,-91.529,31.345,-91.529,31.449,-91.506,31.519,-91.459,31.555,-91.415,31.64,-91.373,31.774,-91.286,31.884,-91.157,31.971,-91.093,32.046,-91.089,32.104,-91.075,32.131,-91.048,32.146,-91.005,32.186,-90.948,32.24,-90.938,32.277,-90.94,32.303,-90.945,32.331,-90.99,32.381,-91.059,32.424,-91.104,32.49,-91.101,32.541,-91.116,32.586,-91.15,32.625,-91.142,32.661,-91.094,32.694,-91.092,32.726,-91.136,32.756,-91.136,32.809,-91.093,32.885,-91.096,32.938,-91.144,32.967,-91.153,33.041,-91.107,33.218,-91.117,33.241,-91.163,33.294,-91.168,33.359,-91.141,33.439,-91.154,33.5,-91.193,33.523,-91.203,33.547,-91.215,33.583,-91.173,33.617,-91.17,33.647,-91.207,33.676,-91.184,33.716,-91.1,33.767,-91.077,33.797,-91.053,33.828,-91.038,33.901,-91.05,33.945,-91.07,33.974,-91.06,33.989,-91.043,34.003,-90.943,34.03,-90.915,34.049,-90.909,34.072,-90.94,34.129,-90.91,34.189,-90.817,34.252,-90.768,34.308,-90.762,34.356,-90.718,34.392,-90.635,34.414,-90.584,34.504,-90.564,34.661,-90.539,34.728,-90.497,34.738,-90.475,34.76,-90.468,34.805,-90.416,34.852,-90.32,34.899,-90.282,34.945,-90.293,34.97,-90.294,35.004,-90.256,35.046,-90.142,35.114,-90.093,35.204,-90.108,35.314,-90.066,35.414,-89.967,35.503,-89.927,35.592,-89.943,35.679,-89.895,35.751,-89.78,35.806,-89.764,35.828,-89.768,35.845,-89.77,35.864,-89.769,35.872,-89.764,35.889,-89.738,35.899,-89.704,35.907,-89.689,35.921,-89.689,35.944,-89.709,35.983,-89.698,36.03,-89.642,36.105,-89.634,36.168,-89.675,36.221,-89.654,36.248,-89.628,36.255,-89.595,36.259,-89.584,36.272,-89.584,36.288,-89.612,36.322,-89.604,36.352,-89.555,36.373,-89.541,36.427,-89.56,36.513,-89.555,36.541,-89.55,36.553,-89.536,36.555,-89.52,36.555,-89.497,36.529,-89.485,36.498,-89.471,36.489,-89.452,36.498,-89.397,36.588,-89.351,36.611,-89.331,36.616,-89.28,36.6,-89.256,36.594,-89.203,36.626,-89.156,36.716,-89.13,36.811,-89.122,36.912,-89.154,36.992,-89.225,37.053,-89.272,37.08,-89.281,37.078,-89.292,37.071,-89.296,37.052,-89.293,37.038,-89.286,37.023,-89.29,37.013,-89.301,37.012,-89.352,37.036,-89.414,37.102,-89.487,37.213,-89.503,37.301,-89.462,37.366,-89.462,37.434,-89.503,37.506,-89.523,37.576,-89.523,37.645,-89.623,37.747,-89.823,37.884,-89.97,37.969,-90.064,38.001,-90.174,38.069,-90.299,38.174,-90.366,38.254,-90.374,38.31,-90.317,38.458,-90.151,38.781,-90.132,38.819,-90.127,38.833,-90.134,38.847,-90.15,38.86,-90.22,38.898,-90.354,38.93,-90.478,38.948,-90.577,38.91,-90.664,38.987,-90.737,39.182,-90.919,39.387,-91.21,39.603,-91.379,39.749,-91.426,39.827,-91.467,39.943,-91.502,40.098,-91.5,40.226,-91.442,40.379,-91.391,40.397,-91.381,40.448,-91.394,40.537,-91.339,40.604,-91.215,40.65,-91.102,40.75,-91,40.905,-90.954,41.016,-90.964,41.083,-91.001,41.146,-91.067,41.206,-91.082,41.28,-91.048,41.369,-90.939,41.426,-90.754,41.45,-90.587,41.495,-90.437,41.56,-90.348,41.632,-90.319,41.71,-90.276,41.77,-90.218,41.813,-90.177,41.884,-90.152,41.982,-90.149,42.054,-90.168,42.1,-90.233,42.154,-90.347,42.215,-90.41,42.272,-90.425,42.326,-90.485,42.383,-90.59,42.445,-90.657,42.521,-90.688,42.61,-90.789,42.677,-90.96,42.72,-91.07,42.789,-91.118,42.882,-91.148,42.986,-91.161,43.102,-91.148,43.2,-91.107,43.278,-91.117,43.331,-91.178,43.359,-91.219,43.395,-91.238,43.44,-91.278,43.797,-91.32,43.936,-91.383,43.991,-91.515,44.054,-91.716,44.126,-91.841,44.194,-91.912,44.289,-92.055,44.4,-92.149,44.445,-92.24,44.462,-92.313,44.498,-92.367,44.552,-92.442,44.587,-92.537,44.601,-92.644,44.645,-92.82,44.758,-92.963,44.79,-93.03,44.838,-93.07,44.912,-93.119,44.937,-93.175,44.913,-93.232,44.963,-93.291,45.087,-93.349,45.167,-93.408,45.202,-93.936,45.389,-94.063,45.439,-94.133,45.496,-94.189,45.577,-94.212,45.632,-94.202,45.662,-94.238,45.714,-94.32,45.789,-94.371,45.853,-94.389,45.906,-94.386,45.972,-94.363,46.051,-94.366,46.125,-94.394,46.194,-94.333,46.284,-94.183,46.396,-94.091,46.482,-94.057,46.543,-93.959,46.569,-93.798,46.559,-93.692,46.586,-93.641,46.651,-93.572,46.698,-93.486,46.725,-93.404,46.807,-93.325,46.944,-93.309,47.023,-93.357,47.044,-93.402,47.09,-93.445,47.16,-93.509,47.211,-93.593,47.243,-93.676,47.249,-93.759,47.228,-93.798,47.242,-93.795,47.29,-93.829,47.312,-93.901,47.306,-93.976,47.336,-94.094,47.433],[-95.026,47.156,-94.968,47.318,-94.877,47.451,-94.794,47.482,-94.637,47.44],[-94.508,47.457,-94.326,47.406]]},{"n":"Missouri","z":2,"b":[-111.989,38.579,-90.133,48.15],"l":[[-98.56,43.06,-98.143,42.852,-97.965,42.806,-97.881,42.85,-97.698,42.875,-97.417,42.881,-97.239,42.861,-97.164,42.813,-97.02,42.76,-96.805,42.701,-96.676,42.638,-96.633,42.569,-96.558,42.525,-96.451,42.506,-96.384,42.418,-96.356,42.26,-96.295,42.133,-96.201,42.036,-96.154,41.965,-96.154,41.92,-96.131,41.872,-96.087,41.821,-96.069,41.734,-96.078,41.611,-96.059,41.546,-96.011,41.538,-95.991,41.52,-95.997,41.494,-95.982,41.474,-95.944,41.461,-95.933,41.425,-95.95,41.367,-95.936,41.331,-95.892,41.316,-95.884,41.288,-95.911,41.245,-95.907,41.212,-95.871,41.188,-95.847,41.08,-95.834,40.889,-95.838,40.778,-95.859,40.745,-95.826,40.676,-95.74,40.571,-95.685,40.466,-95.662,40.363,-95.607,40.295,-95.521,40.265,-95.464,40.198,-95.434,40.096,-95.362,40.01,-95.116,39.872,-95.056,39.871,-94.984,39.891,-94.93,39.861,-94.893,39.782,-94.899,39.741,-94.946,39.739,-95.005,39.692,-95.075,39.597,-95.058,39.509,-94.953,39.426,-94.882,39.343,-94.847,39.259,-94.778,39.194,-94.622,39.124,-94.504,39.124,-94.421,39.151,-94.334,39.204,-94.239,39.207,-94.136,39.159,-94.028,39.154,-93.915,39.19,-93.773,39.211,-93.604,39.218,-93.513,39.237,-93.501,39.27,-93.469,39.271,-93.418,39.24,-93.343,39.27,-93.243,39.361,-93.108,39.359,-92.939,39.265,-92.877,39.192,-92.922,39.141,-92.933,39.087,-92.909,39.03,-92.816,38.993,-92.654,38.976,-92.503,38.89,-92.293,38.659,-92.124,38.586,-91.978,38.598,-91.801,38.67,-91.619,38.706,-91.433,38.707,-91.238,38.667,-91.034,38.585,-90.872,38.579,-90.752,38.648,-90.661,38.681,-90.598,38.681,-90.513,38.732,-90.404,38.835,-90.316,38.86,-90.256,38.845,-90.133,38.819],[-100.394,44.436,-100.372,44.415,-100.345,44.374,-100.225,44.329,-100.019,44.286,-99.922,44.245,-99.934,44.205,-99.921,44.171,-99.883,44.142,-99.804,44.122,-99.685,44.113,-99.647,44.132,-99.691,44.179,-99.684,44.205,-99.626,44.208,-99.587,44.178,-99.568,44.115,-99.51,44.06,-99.364,43.991],[-111.699,46.663,-111.758,46.671,-111.814,46.711,-111.885,46.786,-111.913,46.837,-111.898,46.863,-111.919,46.916,-111.976,46.997,-111.989,47.057,-111.959,47.096,-111.616,47.342,-111.454,47.415,-111.348,47.412,-111.293,47.439,-111.29,47.498,-111.237,47.541,-111.137,47.567,-111.037,47.632,-110.938,47.736,-110.856,47.784,-110.792,47.777,-110.617,47.843,-110.331,47.984,-110.167,48.043,-110.124,48.021,-110.089,47.968,-110.062,47.885,-109.995,47.816,-109.886,47.762,-109.609,47.755,-109.164,47.794,-108.935,47.795,-108.922,47.758,-108.874,47.718,-108.788,47.677,-108.607,47.643,-108.191,47.602],[-101.4,47.506,-101.423,47.484,-101.407,47.431,-101.362,47.339,-101.248,47.282,-101.066,47.26,-100.979,47.231,-100.987,47.196,-100.967,47.148,-100.917,47.087,-100.902,47.034,-100.921,46.99,-100.892,46.904,-100.777,46.714],[-106.406,48.021,-106.367,48.058,-106.221,48.058,-105.956,48.033,-105.729,48.042,-105.542,48.086,-105.34,48.1,-105.125,48.083,-104.978,48.095,-104.899,48.136,-104.75,48.15,-104.529,48.137,-104.277,48.093,-103.996,48.02,-103.835,47.994,-103.772,48.027]]},{"n":"Mitchell","z":5,"b":[141.581,-16.484,143.469,-15.195],"l":[[143.469,-16.484,143.138,-16.38,142.75,-16.104,142.565,-16.001,142.452,-15.981,142.365,-15.947,142.271,-15.874,142.043,-15.473,141.877,-15.306,141.751,-15.245,141.581,-15.195]]},{"n":"Black Volta","z":4.7,"b":[-5.263,8.16,-1.713,12.738],"l":[[-5.263,11.139,-5.185,11.124,-5.126,11.154,-5.059,11.224,-4.99,11.264,-4.879,11.282,-4.715,11.406,-4.639,11.432,-4.596,11.402,-4.539,11.413,-4.469,11.467,-4.382,11.507,-4.277,11.533,-4.124,11.697,-3.922,11.998,-3.82,12.176,-3.815,12.232,-3.784,12.296,-3.725,12.366,-3.666,12.401,-3.607,12.402,-3.569,12.477,-3.55,12.628,-3.511,12.715,-3.45,12.738,-3.406,12.72,-3.379,12.663,-3.336,12.645,-3.278,12.666,-3.249,12.64,-3.248,12.567,-3.208,12.5,-3.126,12.44,-3.023,12.397,-2.899,12.372,-2.849,12.339,-2.875,12.298,-2.864,12.235,-2.816,12.151,-2.824,12.098,-2.886,12.075,-2.896,12.019,-2.853,11.927,-2.854,11.861,-2.901,11.819,-2.883,11.779,-2.802,11.743,-2.79,11.606,-2.849,11.369,-2.858,11.242,-2.817,11.226,-2.804,11.165,-2.83,11.008,-2.907,10.728,-2.915,10.592,-2.878,10.508,-2.837,10.455,-2.791,10.432,-2.787,10.402,-2.823,10.363,-2.82,10.323,-2.777,10.282,-2.767,10.238,-2.788,10.193,-2.783,10.083,-2.751,9.91,-2.75,9.797,-2.781,9.746,-2.766,9.658,-2.706,9.534,-2.686,9.432,-2.706,9.351,-2.702,9.302,-2.674,9.283,-2.689,9.219,-2.747,9.11,-2.747,9.045,-2.69,9.025,-2.649,8.957,-2.625,8.84,-2.557,8.731,-2.446,8.631,-2.364,8.514,-2.311,8.38,-2.247,8.274,-2.17,8.196,-2.087,8.16,-2,8.166,-1.935,8.192,-1.892,8.237,-1.845,8.255,-1.793,8.244,-1.768,8.26,-1.77,8.304,-1.797,8.328,-1.851,8.333,-1.887,8.357,-1.904,8.398,-1.899,8.47,-1.871,8.573,-1.821,8.625,-1.713,8.631]]},{"n":"Murray","z":3,"b":[139.289,-36.967,148.197,-33.999],"l":[[148.005,-36.967,148.162,-36.73,148.197,-36.625,148.147,-36.546,148.034,-36.181,147.975,-36.046,147.931,-36.019,147.762,-35.956,147.674,-35.954,147.596,-35.982,147.538,-35.984,147.502,-35.959,147.458,-35.954,147.407,-35.971,147.371,-36.004,147.35,-36.052,147.323,-36.07,147.272,-36.049],[147.045,-36.108,146.896,-36.1,146.761,-36.067,146.59,-36.005,146.47,-35.994,146.403,-36.033,146.331,-36.054,146.216,-36.056],[146.028,-36.015,145.815,-35.977,145.692,-35.931,145.588,-35.863,145.432,-35.835,145.224,-35.846,145.083,-35.862,145.008,-35.884,144.962,-35.923,144.943,-35.978,144.944,-36.029,144.965,-36.075,144.916,-36.107,144.799,-36.126,144.668,-36.088,144.524,-35.994,144.427,-35.905,144.378,-35.82,144.29,-35.73,144.163,-35.634,143.971,-35.528,143.714,-35.411,143.585,-35.327,143.582,-35.275,143.54,-35.234,143.459,-35.203,143.404,-35.168,143.377,-35.127,143.363,-35.032,143.361,-34.882,143.325,-34.788,143.221,-34.733,142.965,-34.693,142.857,-34.655,142.812,-34.607,142.774,-34.588,142.742,-34.598,142.705,-34.649,142.663,-34.741,142.619,-34.783,142.571,-34.775,142.507,-34.712,142.426,-34.593,142.388,-34.485,142.394,-34.387,142.373,-34.333,142.328,-34.322,142.287,-34.286,142.252,-34.223,142.158,-34.173,141.926,-34.117,141.761,-34.112,141.666,-34.137,141.586,-34.191,141.47,-34.183,141.318,-34.112,141.192,-34.073,141.094,-34.066,141.026,-34.043,140.997,-34.016,140.948,-33.999,140.914,-34.011,140.806,-34.062,140.763,-34.096,140.784,-34.113,140.787,-34.145,140.773,-34.189,140.715,-34.246,140.613,-34.315,140.565,-34.375,140.571,-34.426,140.533,-34.433,140.452,-34.394,140.392,-34.324,140.352,-34.223,140.248,-34.175,140.08,-34.181,139.979,-34.176,139.944,-34.16,139.933,-34.136,139.93,-34.103,139.886,-34.074,139.801,-34.057,139.753,-34.051,139.721,-34.077,139.657,-34.148,139.607,-34.383,139.603,-34.544,139.645,-34.631,139.635,-34.695,139.576,-34.734,139.57,-34.787,139.617,-34.852,139.592,-34.878,139.495,-34.864,139.4,-34.884,139.307,-34.939,139.289,-34.981,139.347,-35.011,139.355,-35.048,139.313,-35.093,139.299,-35.133,139.31,-35.168,139.351,-35.207,139.423,-35.25,139.435,-35.297,139.374,-35.365,139.303,-35.399]]},{"n":"N. Fork Kuskokwim","z":5,"b":[-154.721,63.079,-151.674,64.326],"l":[[-151.674,64.301,-152.207,64.326,-152.399,64.235,-152.499,64.161,-152.568,64.072,-152.597,64.008,-152.585,63.97,-152.791,63.884,-153.215,63.751,-153.455,63.649,-153.513,63.577,-153.605,63.515,-153.733,63.464,-153.85,63.403,-153.959,63.332,-154.009,63.286,-154.001,63.265,-154.178,63.211,-154.721,63.079]]},{"n":"White Volta","z":5,"b":[-2.135,9.117,-0.336,14.01],"l":[[-2.083,14.01,-2.055,13.681,-2.108,13.566,-2.104,13.473,-2.063,13.365,-2.069,13.253,-2.123,13.138,-2.135,13.068,-2.105,13.042,-1.989,13.038,-1.787,13.055,-1.628,13.032,-1.511,12.966,-1.411,12.946,-1.327,12.97,-1.262,12.962,-1.216,12.921,-1.183,12.836,-1.163,12.708,-1.141,12.651,-1.115,12.667,-1.096,12.658,-1.083,12.624,-1.092,12.333,-1.045,12.153,-0.94,11.975,-0.858,11.867,-0.798,11.829,-0.771,11.754,-0.778,11.64,-0.724,11.557,-0.609,11.505,-0.534,11.454,-0.499,11.405,-0.492,11.37,-0.512,11.35,-0.483,11.291,-0.406,11.194,-0.373,11.081,-0.384,10.954,-0.374,10.867,-0.344,10.82,-0.339,10.784,-0.359,10.76,-0.361,10.72,-0.336,10.638,-0.608,10.546,-0.7,10.546,-0.703,10.608,-0.724,10.64,-0.765,10.642,-0.817,10.604,-0.88,10.526,-0.937,10.499,-0.988,10.522,-1.027,10.522,-1.054,10.497,-1.067,10.445,-1.067,10.367,-1.05,10.303,-1.018,10.253,-1.024,10.206,-1.068,10.161,-1.07,10.117,-1.031,10.074,-1,9.961,-0.977,9.777,-1.021,9.696,-1.13,9.715,-1.237,9.686,-1.342,9.608,-1.385,9.523,-1.366,9.431,-1.328,9.359,-1.27,9.307,-1.242,9.254,-1.246,9.199,-1.223,9.158,-1.152,9.117]]},{"n":"Nanpan","z":4,"b":[103.053,23.802,106.944,25.879],"l":[[103.922,25.879,103.855,25.801,103.836,25.725,103.846,25.574,103.883,25.341,103.867,25.219,103.81,25.129,103.713,25.048,103.488,24.939,103.448,24.935,103.416,24.955,103.392,25,103.326,25.013,103.219,24.994,103.165,24.944,103.165,24.863,103.132,24.774,103.064,24.677,103.053,24.609,103.098,24.57,103.105,24.52,103.074,24.458,103.08,24.321,103.146,24.004,103.169,23.87,103.195,23.818,103.232,23.802,103.262,23.81,103.288,23.84,103.376,23.863,103.527,23.88,103.61,23.916,103.626,23.97,103.619,24.02,103.59,24.065,103.591,24.11,103.623,24.153,103.674,24.185,103.746,24.204,103.825,24.27,103.912,24.385,103.987,24.442,104.052,24.441,104.151,24.472,104.353,24.562,104.537,24.741,104.681,24.659,104.8,24.673,104.941,24.756,105.056,24.85,105.144,24.956,105.208,24.991,105.247,24.956,105.308,24.937,105.39,24.935,105.45,24.905,105.489,24.847,105.583,24.79,105.731,24.735,105.833,24.712,105.888,24.723,105.942,24.707,105.994,24.664,106.065,24.689,106.154,24.783,106.188,24.862,106.166,24.928,106.231,24.979,106.381,25.017,106.509,25.07,106.614,25.139,106.742,25.189,106.874,25.215,106.944,25.225]]},{"n":"Narmada","z":5,"b":[73.112,21.75,81.574,23.129],"l":[[81.574,22.754,81.28,22.877,81.096,22.956,81.027,22.969,81.01,22.943,80.973,22.936,80.917,22.947,80.877,22.979,80.854,23.03,80.832,23.04,80.812,23.01,80.732,22.985,80.59,22.964,80.509,22.925,80.487,22.87,80.484,22.788,80.498,22.678,80.486,22.611,80.448,22.586,80.411,22.584,80.373,22.602,80.333,22.658,80.29,22.75,80.217,22.79,80.06,22.775,79.963,22.852,79.933,22.934,79.938,23.047,79.834,23.112,79.622,23.129,79.419,23.122,79.224,23.091,79.116,23.061,79.096,23.03,78.997,23.024,78.82,23.04,78.672,23.038,78.553,23.016,78.442,22.97,78.338,22.899,78.257,22.868,78.2,22.878,78.158,22.859,78.133,22.812,78.075,22.794,77.984,22.803,77.919,22.825,77.88,22.857,77.79,22.825,77.65,22.729,77.457,22.639,77.086,22.514,76.884,22.458,76.803,22.407,76.776,22.342,76.714,22.289,76.615,22.248,76.53,22.254,76.458,22.309,76.306,22.299,76.071,22.225,75.608,22.146,74.567,22.021,74.466,22.035,74.387,22.012,74.295,21.957,74.202,21.932,74.108,21.938,73.985,21.914,73.832,21.862,73.703,21.872,73.596,21.944,73.518,21.981,73.469,21.985,73.417,21.961,73.363,21.911,73.311,21.897,73.258,21.919,73.235,21.908,73.242,21.866,73.218,21.844,73.163,21.844,73.135,21.821,73.137,21.783,73.112,21.75]]},{"n":"Naryn","z":4.7,"b":[70.355,40.435,78.019,41.88],"l":[[72.654,41.667,72.349,41.497,72.222,41.376,72.171,41.246,72.053,41.117,71.87,40.988,71.597,40.907,71.234,40.872,71.101,40.842,71.062,40.822,71.026,40.809,70.965,40.796,70.863,40.756,70.698,40.661,70.355,40.435],[77.737,41.88,77.978,41.869,78.019,41.836,77.901,41.75,77.763,41.624,77.673,41.567,77.586,41.536,77.278,41.518,76.75,41.51,76.388,41.492,76.092,41.449,75.882,41.459,75.747,41.442,75.617,41.4,75.478,41.397,75.33,41.431,75.066,41.446,74.686,41.443,74.436,41.424,74.317,41.39,74.18,41.4,74.025,41.456,73.946,41.519,73.942,41.591,73.922,41.649,73.886,41.696,73.797,41.74,73.655,41.783,73.513,41.797,73.298,41.776]]},{"n":"Nederrijn","z":4,"b":[5.331,51.872,6.039,51.981],"l":[[6.039,51.872,5.922,51.961,5.747,51.981,5.331,51.963]]},{"n":"Negro","z":3,"b":[-70.336,-41.047,-59.897,2.833],"l":[[-68.014,-38.975,-67.757,-39.052,-67.598,-39.074,-67.453,-39.069,-67.314,-39.085,-67.18,-39.123,-66.987,-39.129,-66.735,-39.104,-66.531,-39.1,-66.373,-39.117,-66.224,-39.154,-66.084,-39.212,-65.931,-39.248,-65.765,-39.261,-65.629,-39.331,-65.521,-39.457,-65.434,-39.593,-65.366,-39.74,-65.234,-39.83,-65.037,-39.862,-64.899,-39.91,-64.82,-39.975,-64.728,-40.025,-64.622,-40.062,-64.503,-40.078,-64.371,-40.074,-64.172,-40.166,-63.905,-40.354,-63.738,-40.455,-63.67,-40.469,-63.574,-40.534,-63.388,-40.709,-63.174,-40.743,-63.028,-40.826,-62.86,-40.948,-62.798,-41.047],[-70.336,2.058,-69.967,2.11,-69.683,2.159,-69.526,2.204,-69.401,2.262,-69.252,2.271,-69.079,2.23,-68.932,2.211,-68.81,2.215,-68.701,2.264,-68.603,2.36,-68.497,2.379,-68.385,2.321,-68.277,2.372,-68.175,2.534,-68.113,2.606,-68.093,2.588,-68.068,2.608,-68.038,2.665,-67.955,2.73,-67.859,2.794,-67.766,2.833,-67.667,2.8,-67.619,2.794,-67.597,2.769,-67.568,2.69,-67.535,2.677,-67.486,2.644,-67.392,2.56,-67.312,2.472,-67.253,2.429,-67.211,2.39,-67.198,2.333,-67.215,2.275,-67.165,2.143,-67.131,2.101,-67.114,2.051,-67.131,2,-67.09,1.94,-66.988,1.68,-66.982,1.601,-66.958,1.564,-66.931,1.458,-66.884,1.358,-66.896,1.29,-66.876,1.223,-66.86,1.022,-66.877,0.938,-66.921,0.903,-67.008,0.887,-67.137,0.889,-67.231,0.768,-67.32,0.403,-67.277,0.156,-67.201,0.008,-67.077,-0.122,-66.984,-0.18,-66.922,-0.164,-66.772,-0.216,-66.535,-0.335,-66.373,-0.377,-66.285,-0.343,-66.172,-0.324,-66.034,-0.322,-65.942,-0.303,-65.896,-0.268,-65.84,-0.264,-65.772,-0.291,-65.681,-0.301,-65.566,-0.292,-65.447,-0.316,-65.261,-0.401,-65.005,-0.457,-64.798,-0.449,-64.43,-0.367,-64.152,-0.314,-63.972,-0.304,-63.796,-0.32,-63.633,-0.382,-63.481,-0.492,-63.31,-0.585,-63.025,-0.702,-62.879,-0.874,-62.73,-0.985,-62.432,-1.146,-61.933,-1.377,-61.881,-1.389,-61.799,-1.389,-61.717,-1.401,-61.62,-1.449,-61.538,-1.53,-61.471,-1.642,-61.437,-1.736,-61.435,-1.814,-61.355,-1.926,-61.197,-2.074,-61.142,-2.15,-61.092,-2.218,-61.04,-2.36,-60.903,-2.553,-60.571,-2.922,-60.451,-3.028,-60.36,-3.065,-60.26,-3.069,-60.158,-3.096,-60.052,-3.145,-59.976,-3.158,-59.897,-3.128]]},{"n":"Nelson","z":5,"b":[-98.012,54.082,-92.802,56.928],"l":[[-97.731,54.082,-97.595,54.241,-97.569,54.319,-97.615,54.373,-97.622,54.429,-97.591,54.487,-97.629,54.53,-97.791,54.571],[-97.901,54.705,-97.924,54.774,-97.957,54.807,-98.008,54.826,-98.012,54.863,-97.946,54.948],[-97.254,55.236,-97.074,55.249,-96.954,55.302,-96.833,55.4,-96.768,55.483,-96.76,55.551,-96.704,55.671,-96.602,55.843,-96.552,55.962,-96.553,56.063],[-95.842,56.291,-95.431,56.339],[-94.779,56.394,-94.344,56.421,-93.996,56.549,-93.591,56.788,-93.262,56.909,-92.882,56.915,-92.802,56.928]]},{"n":"Neuquén","z":4,"b":[-70.803,-38.975,-68.014,-36.529],"l":[[-70.803,-36.529,-70.7,-36.909,-70.648,-37.077,-70.648,-37.149,-70.681,-37.182,-70.698,-37.227,-70.699,-37.282,-70.675,-37.325,-70.626,-37.355,-70.51,-37.375,-70.325,-37.387,-70.199,-37.42,-70.13,-37.474,-70.077,-37.539,-70.04,-37.615,-69.991,-37.658,-69.931,-37.67,-69.886,-37.767,-69.857,-37.947,-69.809,-38.078,-69.74,-38.159,-69.7,-38.245,-69.687,-38.337,-69.604,-38.435,-69.452,-38.538,-69.325,-38.592,-69.224,-38.597,-69.102,-38.543,-68.961,-38.43,-68.849,-38.371,-68.769,-38.366,-68.603,-38.447,-68.351,-38.614,-68.173,-38.766,-68.014,-38.975]]},{"n":"Neva","z":4.7,"b":[30.157,59.811,31.043,59.951],"l":[[31.043,59.951,31.014,59.933,30.982,59.882,30.906,59.843,30.795,59.811,30.652,59.835,30.477,59.917,30.347,59.947,30.22,59.916,30.157,59.904]]},{"n":"Niagara","z":3,"b":[-79.066,43.106,-79.059,43.278],"l":[[-79.059,43.278,-79.066,43.106]]},{"n":"Niger","z":3,"b":[-10.816,4.291,6.776,17.057],"l":[[6.266,4.991,6.224,4.919,6.131,4.844,6.078,4.71,6.055,4.42,6.077,4.291],[6.266,4.991,6.271,4.432],[6.455,5.363,6.374,5.346,6.302,5.316,6.24,5.274,6.21,5.235,6.21,5.201,6.17,5.164,6.09,5.124,5.992,5.13,5.876,5.182,5.738,5.191,5.476,5.154],[6.455,5.363,6.394,5.301,6.349,5.256,6.33,5.221,6.333,5.196,6.358,5.18,6.354,5.153,6.321,5.114,6.292,5.095,6.266,5.095,6.257,5.061,6.266,4.991],[6.557,5.542,6.606,5.386,6.587,5.301,6.522,5.235,6.474,5.156,6.443,5.065,6.461,5.003,6.526,4.97,6.573,4.908,6.599,4.816,6.643,4.729,6.709,4.607,6.715,4.501,6.71,4.452,6.71,4.425,6.715,4.342],[-10.732,9.09,-10.665,9.154,-10.622,9.235,-10.58,9.355,-10.583,9.453,-10.656,9.566,-10.642,9.75,-10.684,9.915,-10.779,10.121,-10.816,10.248,-10.796,10.294,-10.747,10.322,-10.668,10.331,-10.634,10.359,-10.644,10.403,-10.613,10.426,-10.541,10.426,-10.481,10.452,-10.433,10.503,-10.363,10.524,-10.271,10.513,-10.218,10.491,-10.199,10.478,-10.174,10.472,-10.151,10.482,-10.053,10.569,-9.97,10.608,-9.901,10.602,-9.866,10.625,-9.848,10.642,-9.801,10.662,-9.658,10.678,-9.535,10.781,-9.433,10.969,-9.343,11.065,-9.268,11.067,-9.199,11.097,-9.136,11.154,-9.117,11.239,-9.14,11.352,-9.089,11.419,-8.962,11.438,-8.852,11.505,-8.759,11.62,-8.651,11.719,-8.527,11.801,-8.433,11.905,-8.367,12.031,-8.281,12.144,-8.175,12.243,-8.098,12.375,-8.051,12.539,-7.964,12.627,-7.837,12.636,-7.666,12.747,-7.453,12.958,-7.279,13.104,-7.078,13.224,-6.949,13.325,-6.788,13.362,-6.551,13.368,-6.367,13.409,-6.237,13.484,-6.146,13.572,-6.094,13.672,-6.021,13.71,-5.902,13.719,-5.785,13.745,-5.595,13.887,-5.435,13.962,-5.305,13.969,-5.214,14.008,-5.164,14.079,-4.996,14.155,-4.712,14.236,-4.549,14.266,-4.487,14.274,-4.414,14.313,-4.201,14.551,-4.117,14.772,-4.042,14.885,-3.948,14.964,-3.921,15.073,-3.979,15.281,-3.954,15.29,-3.914,15.307,-3.852,15.354,-3.82,15.395,-3.818,15.432,-3.853,15.456,-3.927,15.467,-3.923,15.504,-3.796,15.603,-3.787,15.62,-3.861,15.642,-3.849,15.66,-3.812,15.674,-3.748,15.684,-3.714,15.732,-3.708,15.818,-3.682,15.872,-3.635,15.894,-3.598,15.939,-3.57,16.005,-3.503,16.047],[4.626,9.865,4.644,9.755,4.631,9.669,4.593,9.571,4.586,9.473,4.612,9.374,4.654,9.312,4.713,9.287,4.75,9.243,4.764,9.18,4.83,9.161,4.947,9.185,5.057,9.15,5.159,9.055,5.292,8.989,5.456,8.949,5.604,8.883,5.802,8.742,6.02,8.754,6.159,8.742,6.291,8.71,6.385,8.628,6.438,8.497,6.521,8.377,6.633,8.268,6.716,8.144,6.771,8.006,6.776,7.793,6.71,7.362,6.664,6.973,6.66,6.743,6.691,6.44,6.741,6.202,6.707,5.978,6.556,5.541,6.495,5.464,6.48,5.422,6.49,5.388,6.455,5.363],[-4.304,15.507,-4.338,15.621,-4.317,15.683,-4.252,15.732,-4.208,15.793,-4.185,15.865,-4.138,15.892,-4.066,15.873,-3.961,15.925,-3.824,16.047,-3.689,16.113,-3.556,16.123,-3.467,16.161],[-3.503,16.047,-3.467,16.161],[-3.467,16.161,-3.424,16.226,-3.379,16.261,-3.333,16.266,-3.31,16.292,-3.31,16.337,-3.263,16.386,-3.222,16.408],[-3.222,16.408,-3.189,16.3,-3.187,16.253,-3.206,16.225,-3.234,16.207,-3.271,16.199,-3.302,16.163,-3.329,16.101,-3.396,16.062,-3.503,16.047],[-3.222,16.408,-3.167,16.438,-3.106,16.511,-3.08,16.606,-3.028,16.65,-2.948,16.643,-2.743,16.69,-2.411,16.792,-2.197,16.837,-2.101,16.826,-2.043,16.835,-2.023,16.864,-1.897,16.915,-1.664,16.988,-1.448,17.035,-1.25,17.057,-1.118,17.048,-1.052,17.009,-0.965,16.996,-0.857,17.008,-0.77,16.993,-0.705,16.95,-0.601,16.938,-0.457,16.955,-0.346,16.873,-0.269,16.691,-0.203,16.577,-0.148,16.533,-0.089,16.426,0.003,16.17,0.144,15.983,0.259,15.872,0.397,15.775,0.471,15.679,0.481,15.585,0.548,15.51,0.672,15.456,0.739,15.41,0.749,15.371,0.727,15.283,0.675,15.144,0.657,15.046,0.675,14.988,0.722,14.948,0.799,14.926,0.855,14.861,0.891,14.751,0.965,14.63,1.076,14.498,1.208,14.381,1.362,14.28,1.463,14.146,1.512,13.98,1.581,13.847,1.671,13.747,1.812,13.651,2.004,13.558,2.125,13.476,2.174,13.402,2.236,13.343,2.31,13.299,2.355,13.176,2.37,12.976,2.415,12.818,2.49,12.704,2.521,12.627,2.508,12.59,2.515,12.552,2.54,12.529,2.571,12.53,2.597,12.552,2.615,12.552,2.626,12.531,2.653,12.522,2.692,12.53,2.718,12.519,2.736,12.468,2.768,12.432,2.815,12.419,2.844,12.399,3.15,12.118,3.267,11.992,3.299,11.927,3.36,11.88,3.45,11.852,3.532,11.787,3.595,11.696,3.695,11.62,3.851,11.57,4.022,11.484,4.133,11.391,4.241,11.261,4.368,11.17,4.513,11.115,4.62,11.045,4.688,10.957,4.705,10.837,4.656,10.607]]},{"n":"Nile","z":2,"b":[30.317,15.635,33.969,30.121],"l":[[32.49,15.635,32.535,16.04,32.629,16.256,32.785,16.418,32.929,16.508,33.061,16.526,33.165,16.566,33.241,16.628,33.359,16.686,33.519,16.738,33.635,16.825,33.706,16.944,33.732,17.029,33.712,17.079,33.715,17.151,33.74,17.246,33.81,17.379,33.925,17.55,33.952,17.699,33.969,17.792,33.942,18.107,33.9,18.278,33.845,18.305,33.784,18.364,33.718,18.455,33.682,18.555,33.675,18.662,33.645,18.724,33.59,18.741,33.567,18.847,33.574,19.043,33.539,19.173,33.463,19.237,33.413,19.308,33.38,19.423,33.138,19.503,33.018,19.511,32.938,19.474,32.838,19.383,32.717,19.238,32.62,19.161,32.546,19.152,32.468,19.083,32.385,18.954,32.306,18.89,32.23,18.891,32.153,18.865,32.076,18.812,32.039,18.743,32.044,18.661,32.005,18.606,31.921,18.58,31.8,18.452,31.642,18.223,31.474,18.077,31.296,18.015,31.146,18.002,31.023,18.036,30.911,18.087,30.809,18.154,30.744,18.257,30.717,18.397,30.674,18.499,30.616,18.562,30.562,18.66,30.511,18.794,30.486,18.988,30.488,19.243,30.458,19.471,30.396,19.672,30.35,19.786,30.348,19.822,30.37,19.857,30.5,19.914,30.575,19.967,30.594,20.016,30.6,20.098,30.591,20.214,30.571,20.291,30.539,20.329,30.49,20.351,30.422,20.358,30.366,20.417,30.321,20.527,30.317,20.616,30.352,20.683,30.363,20.735,30.37,20.774,30.397,20.805,30.51,20.84,30.576,20.9,30.595,20.985,30.632,21.044,30.687,21.076,30.709,21.121,30.699,21.177,30.763,21.249,30.903,21.337,30.97,21.399,30.965,21.436,30.994,21.493,31.091,21.609],[32.879,23.988,32.913,24.328,32.913,24.454,32.889,24.478,32.892,24.54,32.92,24.639,32.921,24.77,32.895,24.933,32.871,25.037,32.848,25.082,32.767,25.149,32.629,25.239,32.54,25.35,32.502,25.483,32.493,25.565,32.523,25.612,32.704,25.754,32.764,25.879,32.763,26.034,32.734,26.129,32.678,26.163,32.553,26.143,32.362,26.067,32.229,26.077,32.154,26.171,32.094,26.189,32.041,26.19,32.015,26.2,31.997,26.247,31.966,26.297,31.871,26.375,31.82,26.445,31.812,26.509,31.782,26.542,31.73,26.543,31.706,26.564,31.712,26.605,31.695,26.632,31.655,26.644,31.553,26.762,31.369,27.01,31.341,27.057,31.304,27.099,31.278,27.123,31.245,27.144,30.969,27.356,30.868,27.5,30.889,27.619,30.884,27.768,30.877,27.902,30.863,27.985,30.837,28.038,30.807,28.085,30.774,28.12,30.744,28.165,30.74,28.23,30.762,28.309,30.811,28.401,30.836,28.492,30.837,28.582,30.858,28.682,30.899,28.791,30.995,28.943,31.146,29.136,31.219,29.282,31.215,29.38,31.236,29.489,31.281,29.609,31.287,29.783,31.255,29.999,31.254,30.08,31.238,30.121]]},{"n":"Nmai","z":3,"b":[97.519,25.62,98.373,28.425],"l":[[97.983,28.425,98.192,28.25,98.297,28.132,98.335,28.008,98.342,27.84,98.302,27.73,98.215,27.679,98.154,27.674,98.118,27.715,98.08,27.726,98.04,27.707,98.042,27.629,98.087,27.494,98.172,27.329,98.296,27.133,98.362,26.974,98.373,26.85,98.354,26.701,98.281,26.443,98.158,26.03,98.096,25.867,98.055,25.815,97.976,25.74,97.86,25.641,97.775,25.62,97.72,25.678,97.641,25.706,97.558,25.703,97.519,25.701]]},{"n":"North Saskatchewan","z":4,"b":[-117.132,51.993,-105.065,54.041],"l":[[-117.132,52.014,-117.011,52.077,-116.937,52.072,-116.838,51.993,-116.534,52.022,-116.438,52.079,-116.448,52.176,-116.39,52.276,-116.262,52.379,-116.053,52.437,-115.764,52.453,-115.576,52.44,-115.488,52.399,-115.398,52.387,-115.308,52.404,-115.211,52.393,-115.108,52.355,-115.029,52.345,-114.975,52.363,-114.984,52.463,-115.057,52.644,-115.111,52.74,-115.146,52.752,-115.164,52.785,-115.166,52.838,-115.184,52.875,-115.218,52.895,-115.227,52.94,-115.211,53.01,-115.159,53.05,-115.071,53.061,-115.014,53.083,-114.974,53.131,-114.841,53.276,-114.729,53.321,-114.596,53.314,-114.483,53.338,-114.388,53.392,-114.262,53.396,-114.104,53.348,-113.947,53.333,-113.789,53.35,-113.691,53.389,-113.651,53.451,-113.569,53.5,-113.445,53.536,-113.306,53.628,-113.153,53.777,-113.024,53.876,-112.919,53.926,-112.856,53.975,-112.834,54.023,-112.732,54.032,-112.55,54.003,-112.41,54.006,-112.313,54.041,-112.193,54.022,-112.05,53.948,-111.92,53.908,-111.802,53.902,-111.737,53.87,-111.726,53.812,-111.578,53.774,-111.292,53.755,-111.094,53.772,-110.986,53.825,-110.858,53.851,-110.712,53.849,-110.563,53.801,-110.411,53.707,-110.302,53.668,-110.235,53.686,-110.123,53.661,-109.967,53.593,-109.826,53.564,-109.702,53.573,-109.613,53.549,-109.558,53.492,-109.535,53.452,-109.543,53.428,-109.482,53.409,-109.352,53.393,-109.236,53.36,-109.135,53.308,-109.022,53.216,-108.897,53.082,-108.71,52.971,-108.459,52.882,-108.343,52.819,-108.362,52.781,-108.361,52.755,-108.328,52.734],[-108.334,52.734,-108.103,52.611,-107.985,52.567,-107.904,52.56,-107.747,52.502,-107.513,52.394,-107.357,52.338,-107.281,52.334,-107.18,52.343,-107.057,52.366,-106.962,52.441,-106.895,52.568,-106.691,52.764,-106.352,53.029,-106.15,53.171,-106.085,53.191,-106.034,53.185,-105.996,53.154,-105.88,53.163,-105.684,53.211,-105.456,53.236,-105.065,53.24]]},{"n":"Nu","z":4,"b":[96.596,20.951,99.089,30.448],"l":[[96.596,30.448,96.675,30.443,96.707,30.406,96.717,30.333,96.854,30.245,97.116,30.142,97.29,30.032,97.375,29.918,97.424,29.817,97.436,29.729,97.477,29.635,97.546,29.536,97.66,29.447,97.819,29.367,97.949,29.226,98.052,29.023,98.163,28.87,98.343,28.715,98.367,28.577,98.391,28.522,98.423,28.502,98.451,28.402,98.474,28.22,98.536,28.088,98.635,28.003,98.682,27.913,98.676,27.817,98.709,27.722,98.816,27.581,98.869,27.341,98.889,27.059,98.895,26.656,98.885,26.362,98.857,26.177,98.856,25.931,98.882,25.625,98.882,25.372,98.856,25.173,98.871,24.991,98.927,24.828,98.98,24.723,99.03,24.677,99.036,24.594,98.998,24.475,99.016,24.383,99.089,24.317,99.058,24.248,98.922,24.175,98.764,24.116,98.583,24.07,98.473,24.026,98.432,23.984,98.42,23.933,98.437,23.872,98.496,23.783,98.598,23.667,98.638,23.563,98.617,23.472,98.635,23.417,98.692,23.398,98.721,23.357,98.723,23.294,98.654,23.248,98.514,23.22,98.474,23.173,98.534,23.109,98.548,23.068,98.516,23.051,98.504,23.024,98.513,22.988,98.554,22.953,98.628,22.918,98.629,22.871,98.558,22.812,98.553,22.706,98.614,22.555,98.626,22.432,98.588,22.336,98.587,22.251,98.625,22.177,98.676,22.115,98.742,22.065,98.718,21.995,98.605,21.905,98.574,21.79,98.624,21.651,98.629,21.564,98.589,21.531,98.595,21.483,98.647,21.421,98.681,21.337,98.696,21.231,98.726,21.145,98.773,21.077,98.724,21.02,98.508,20.951]]},{"n":"Salween","z":4,"b":[91.518,30.437,96.596,31.943],"l":[[91.518,31.943,91.546,31.815,91.6,31.747,91.691,31.696,91.755,31.607,91.794,31.478,91.863,31.418,91.963,31.425,92.032,31.396,92.071,31.33,92.134,31.299,92.221,31.304,92.282,31.347,92.319,31.429,92.428,31.486,92.703,31.533,92.869,31.563,92.958,31.56,93.027,31.534,93.156,31.529,93.347,31.544,93.524,31.536,93.687,31.504,93.819,31.501,93.922,31.525,94.106,31.523,94.371,31.495,94.528,31.468,94.575,31.443,94.576,31.387,94.529,31.301,94.531,31.251,94.583,31.237,94.629,31.196,94.669,31.129,94.71,31.091,94.752,31.08,94.821,31.102,94.919,31.158,95.039,31.186,95.181,31.188,95.301,31.171,95.399,31.136,95.486,31.074,95.563,30.985,95.667,30.957,95.799,30.99,95.962,30.978,96.157,30.921,96.283,30.873,96.34,30.835,96.411,30.721,96.497,30.529,96.554,30.437,96.596,30.448]]},{"n":"Ob","z":4,"b":[70.354,52.332,85.056,61.304],"l":[[85.056,52.434,84.589,52.422,84.347,52.393,84.175,52.342,84.003,52.332,83.831,52.362,83.728,52.414,83.694,52.489,83.683,52.592,83.699,52.789,83.658,52.961,83.658,53.04,83.685,53.083,83.817,53.109,83.86,53.175,83.849,53.199,83.838,53.353,83.712,53.419,83.493,53.429,83.332,53.457,83.228,53.503,83.128,53.505,83.03,53.464,82.897,53.437,82.728,53.425,82.581,53.446,82.454,53.499,82.388,53.545,82.38,53.585,82.33,53.631,82.237,53.684,82.114,53.718,81.96,53.734,81.867,53.758,81.836,53.789,81.727,53.8,81.542,53.788,81.42,53.808,81.334,53.886],[70.354,61.301,70.784,61.304,70.979,61.298,71.081,61.283,71.154,61.251,71.199,61.202,71.257,61.195,71.329,61.23,71.447,61.253,71.695,61.269,72.044,61.255,72.223,61.264,72.347,61.293,72.466,61.288,72.581,61.251,72.678,61.243,72.757,61.263,72.907,61.263,73.237,61.235,73.566,61.196,73.71,61.196,73.779,61.221,73.877,61.214,74.002,61.177,74.118,61.175,74.223,61.207,74.314,61.201,74.385,61.168,74.429,61.168,74.449,61.189,74.515,61.213,74.645,61.215,74.75,61.202,74.868,61.16,75.333,61.124,75.535,61.082,75.627,61.024,75.714,61.01,75.794,61.04,75.887,61.05,75.994,61.04,76.135,61,76.308,60.93,76.435,60.905,76.517,60.925,76.626,60.896,76.764,60.82,76.96,60.745,77.215,60.674,77.399,60.649,77.512,60.669,77.602,60.662,77.672,60.627,77.714,60.586,77.729,60.538,77.807,60.483,77.949,60.422,78.111,60.396,78.293,60.405,78.341,60.371,78.345,60.332,78.389,60.304,78.437,60.292,78.481,60.244,78.563,60.196,78.683,60.15,78.785,60.133,78.836,60.141,78.871,60.116,78.876,60.084,78.909,60.065,78.962,60.055,78.979,60.019,78.959,59.981,78.952,59.933,78.979,59.884,79.039,59.841,79.13,59.805,79.166,59.766,79.147,59.724,79.148,59.689,79.183,59.647,79.283,59.558,79.388,59.517,79.531,59.495,79.623,59.46,79.665,59.412,79.779,59.402,79.967,59.429,80.093,59.424,80.158,59.385,80.172,59.349,80.133,59.317,80.152,59.289,80.228,59.265,80.295,59.273,80.354,59.312,80.411,59.33,80.467,59.328,80.502,59.299,80.516,59.243,80.616,59.18,80.802,59.108,80.973,59.091,81.128,59.13,81.256,59.118,81.358,59.056,81.411,59.004,81.415,58.963,81.444,58.945,81.478,58.937,81.496,58.914,81.505,58.886,81.535,58.848,81.568,58.831,81.573,58.797,81.542,58.745,81.554,58.718,81.648,58.701,81.704,58.707,81.735,58.723,81.775,58.736,81.858,58.702,81.902,58.66,81.908,58.61,81.965,58.585,82.073,58.584,82.172,58.549,82.262,58.48,82.339,58.439,82.403,58.427,82.434,58.404,82.435,58.369,82.463,58.359,82.519,58.376,82.575,58.363,82.631,58.322,82.734,58.305,82.884,58.313,82.928,58.283,82.934,58.24,82.961,58.193,83.031,58.15,83.097,58.128,83.16,58.127,83.224,58.057,83.289,57.92,83.351,57.826,83.41,57.776,83.55,57.742,83.7,57.73,83.777,57.699,83.816,57.641,83.833,57.58,83.907,57.506,83.943,57.441,83.94,57.384,83.958,57.339,83.997,57.306,84.105,57.277,84.28,57.253,84.344,57.21,84.351,57.163,84.376,57.137,84.406,57.109,84.414,57.067,84.407,57.011,84.436,56.911,84.401,56.792,84.308,56.69,84.144,56.565,84.064,56.458,84.066,56.369,84.054,56.314,84.027,56.293,84.017,56.264,84.024,56.227,83.994,56.18,83.925,56.121,83.887,56.038,83.879,55.929,83.819,55.833,83.708,55.748,83.65,55.68,83.645,55.629,83.536,55.561,83.323,55.475,83.156,55.436,83.033,55.444,82.944,55.433,82.888,55.404,82.869,55.34,82.887,55.243,82.877,55.173,82.837,55.129,82.879,55.065,83.002,54.981,83.053,54.915,83.025,54.845]]},{"n":"Ob","z":4,"b":[68.857,61.124,70.354,61.301],"l":[[68.857,61.124,69.01,61.198,69.104,61.213,69.189,61.196,69.29,61.215,69.408,61.272,69.578,61.284,69.8,61.251,70.021,61.252,70.354,61.301]]},{"n":"Ob","z":2.1,"b":[65.14,61.089,69.013,66.814],"l":[[68.857,61.124,68.786,61.089,68.589,61.095,68.465,61.125,68.348,61.181,68.309,61.256,68.348,61.351,68.353,61.416,68.325,61.451,68.134,61.519,67.779,61.62,67.586,61.712,67.553,61.794,67.455,61.919,67.292,62.087,67.043,62.2,66.707,62.258,66.511,62.318,66.455,62.38,66.39,62.42,66.281,62.447,65.988,62.482,65.68,62.622,65.258,62.879,65.163,62.996,65.14,63.072,65.149,63.131,65.211,63.206,65.335,63.296,65.472,63.343,65.527,63.395,65.524,63.456,65.557,63.496,65.701,63.532,65.767,63.584,65.757,63.652,65.732,63.707,65.709,63.734,65.721,63.769,65.788,63.792,65.805,63.809,65.796,63.843,65.781,63.878,65.803,63.899,65.805,63.95,65.789,64.031,65.806,64.076,65.858,64.085,65.885,64.101,65.889,64.126,65.93,64.136,66.008,64.131,66.031,64.154,65.998,64.206,65.953,64.242,65.895,64.262,65.885,64.289,65.923,64.325,65.92,64.37,65.876,64.426,65.8,64.482,65.69,64.537,65.685,64.587,65.706,64.615,65.688,64.659,65.554,64.709,65.501,64.756,65.529,64.799,65.533,64.842,65.512,64.885,65.543,64.93,65.565,64.982,65.524,65.016,65.408,65.022,65.301,65.053,65.257,65.099,65.277,65.176,65.362,65.283,65.455,65.362,65.557,65.411,65.606,65.468,65.605,65.532,65.625,65.604,65.667,65.685,65.736,65.764,65.831,65.842,65.877,65.933,65.871,66.035,65.83,66.094,65.821,66.137,65.839,66.157,65.883,66.196,65.957,66.22,66.097,66.278,66.3,66.411,66.451,66.571,66.663,66.627,66.936,66.578,67.153,66.583,67.315,66.641],[67.315,66.641,67.573,66.629,67.927,66.547,68.197,66.538,68.385,66.6,68.497,66.656,68.533,66.707,68.655,66.752],[67.315,66.641,67.56,66.759,67.692,66.783,67.869,66.773,68.007,66.784,68.105,66.814,68.261,66.807,68.655,66.752],[68.655,66.752,68.907,66.799,69.013,66.788]]},{"n":"Oder","z":4,"b":[14.129,49.637,18.317,53.639],"l":[[17.618,49.739,17.847,49.654,17.946,49.637,18.016,49.672,18.142,49.73,18.225,49.795,18.307,49.886,18.317,49.972,18.253,50.055,18.231,50.137,18.252,50.22,18.185,50.321,18.032,50.441,17.958,50.532,17.964,50.593,17.938,50.656,17.88,50.722,17.762,50.78,17.45,50.882,17.361,50.928,17.3,50.975,17.269,51.023,17.194,51.068,17.015,51.131,16.869,51.229,16.738,51.253,16.573,51.236,16.475,51.245,16.442,51.279,16.44,51.315,16.467,51.354,16.474,51.421,16.462,51.518,16.408,51.595,16.31,51.652,16.223,51.677,16.146,51.669,16.02,51.691,15.847,51.743,15.769,51.811,15.787,51.894,15.758,51.961,15.682,52.011,15.424,52.046,14.761,52.077,14.705,52.11,14.692,52.15,14.705,52.207,14.68,52.25,14.616,52.278,14.574,52.314,14.555,52.36,14.57,52.431,14.619,52.528,14.514,52.646,14.254,52.783,14.129,52.878,14.139,52.933,14.194,52.982,14.293,53.027,14.369,53.106,14.446,53.275,14.581,53.445,14.618,53.526,14.61,53.584,14.584,53.639]]},{"n":"Ohio","z":3,"b":[-89.165,36.992,-79.991,40.686],"l":[[-85.186,38.694,-85.211,38.704,-85.247,38.727,-85.307,38.737,-85.405,38.73,-85.445,38.683,-85.425,38.597,-85.459,38.53,-85.546,38.484,-85.609,38.423,-85.646,38.347,-85.706,38.301,-85.788,38.285,-85.855,38.215,-85.906,38.091,-85.961,38.014,-86.021,37.984,-86.103,37.992,-86.208,38.037,-86.268,38.083,-86.284,38.13,-86.315,38.164,-86.343,38.179,-86.361,38.179,-86.363,38.172,-86.355,38.159,-86.365,38.145,-86.43,38.14,-86.457,38.12,-86.448,38.086,-86.463,38.062,-86.5,38.047,-86.52,38.015,-86.522,37.964,-86.549,37.917,-86.599,37.875,-86.673,37.889,-86.77,37.96,-86.869,37.976,-86.969,37.938,-87.044,37.886,-87.094,37.821,-87.141,37.805,-87.209,37.855,-87.372,37.923,-87.457,37.938,-87.518,37.922,-87.561,37.929,-87.576,37.948,-87.587,37.956,-87.599,37.946,-87.601,37.887,-87.615,37.849,-87.643,37.833,-87.665,37.846,-87.679,37.886,-87.714,37.9,-87.77,37.885,-87.825,37.891,-87.874,37.916,-87.892,37.913,-87.906,37.902,-87.902,37.855,-87.912,37.818,-87.938,37.794,-87.975,37.788,-88.022,37.8,-88.042,37.789,-88.073,37.771,-88.129,37.7,-88.14,37.621,-88.108,37.534,-88.191,37.465,-88.389,37.411,-88.495,37.364,-88.507,37.324,-88.493,37.262,-88.453,37.178,-88.446,37.12,-88.472,37.088,-88.614,37.11,-88.873,37.186,-89.046,37.183,-89.135,37.102,-89.165,37.042,-89.154,36.992],[-79.991,40.448,-80.149,40.522,-80.223,40.584,-80.265,40.661,-80.344,40.686,-80.519,40.647,-80.638,40.603,-80.659,40.563,-80.623,40.512,-80.604,40.445,-80.602,40.364,-80.668,40.185,-80.804,39.909,-80.872,39.736,-80.874,39.664,-80.963,39.571,-81.139,39.456,-81.263,39.387,-81.333,39.363,-81.392,39.365,-81.44,39.393,-81.493,39.374,-81.552,39.308,-81.606,39.274,-81.657,39.272,-81.701,39.229,-81.737,39.147,-81.771,39.099,-81.803,39.086,-81.805,39.044,-81.778,38.973,-81.78,38.947,-81.795,38.946,-81.809,38.95,-81.844,38.934,-81.882,38.894,-81.897,38.894,-81.907,38.9,-81.919,38.952,-81.95,38.989,-82.001,39.011,-82.072,38.963,-82.163,38.845,-82.204,38.768,-82.188,38.715,-82.18,38.638,-82.202,38.606,-82.252,38.594,-82.291,38.552,-82.318,38.48,-82.387,38.434,-82.498,38.415,-82.621,38.451,-82.758,38.541,-82.845,38.628,-82.88,38.71,-83,38.714,-83.203,38.639,-83.357,38.627,-83.461,38.68,-83.538,38.7,-83.616,38.683,-83.69,38.65,-83.752,38.669,-83.828,38.731,-83.948,38.774,-84.113,38.798,-84.227,38.865,-84.29,38.975,-84.358,39.05,-84.43,39.091,-84.509,39.103,-84.595,39.086,-84.661,39.094,-84.707,39.129,-84.766,39.125,-84.837,39.083,-84.861,39.04,-84.835,38.996,-84.837,38.961,-84.864,38.933,-84.854,38.913,-84.807,38.9,-84.79,38.868,-84.801,38.819,-84.901,38.77,-85.094,38.714,-85.136,38.697,-85.17,38.69,-85.186,38.694]]},{"n":"Okavango","z":5,"b":[21.503,-21.265,24.739,-18.06],"l":[[24.739,-21.265,24.69,-21.185,24.597,-21.117,24.444,-21.036,24.367,-20.944,24.367,-20.84,24.4,-20.705,24.467,-20.54,24.495,-20.44,24.484,-20.406,24.453,-20.391,24.403,-20.393,24.323,-20.345,24.212,-20.248,24.098,-20.228,23.981,-20.287,23.881,-20.282,23.799,-20.212,23.698,-20.16,23.577,-20.126,23.46,-20.119,23.404,-20.118,23.37,-20.103,23.368,-20.08,23.392,-20.048,23.545,-19.886,23.595,-19.735,23.579,-19.683],[23.354,-19.556,23.435,-19.622,23.579,-19.683],[23.528,-19.522,23.579,-19.683],[23.237,-19.338,23.354,-19.556],[23.237,-19.338,23.292,-19.387,23.37,-19.405,23.438,-19.441,23.526,-19.521],[22.527,-18.942,22.542,-19.072,22.635,-19.2,22.771,-19.368,22.892,-19.473,22.998,-19.517,23.124,-19.545,23.354,-19.556],[22.527,-18.942,22.58,-19.04,22.663,-19.082,22.774,-19.068,22.894,-19.078,23.023,-19.111,23.129,-19.19,23.212,-19.316,23.237,-19.338],[21.503,-18.06,21.637,-18.097,21.738,-18.185,21.85,-18.339,21.937,-18.417,22,-18.422,22.088,-18.518,22.2,-18.705,22.317,-18.822,22.439,-18.87,22.527,-18.942]]},{"n":"Olenekskaya Protoka","z":2,"b":[122.752,72.294,126.69,72.907],"l":[[126.69,72.294,126.475,72.353,126.336,72.35,126.203,72.308,126.019,72.304,125.786,72.34,125.424,72.436,124.932,72.591,124.614,72.665,124.469,72.657,124.221,72.691,123.871,72.767,123.607,72.793,123.43,72.768,123.189,72.773,122.752,72.907]]},{"n":"Olenëk","z":5,"b":[104.957,67.124,123.743,72.979],"l":[[104.957,67.568,105.339,67.514,105.468,67.57,105.562,67.584,105.665,67.573,105.693,67.536,105.707,67.512,105.751,67.486,105.814,67.477,105.895,67.483,105.962,67.505,106.013,67.542,106.026,67.57,106.113,67.594,106.252,67.557,106.283,67.526,106.248,67.486,106.217,67.44,106.285,67.375,106.402,67.351,106.567,67.368,106.872,67.34,107.315,67.269,107.618,67.244,107.78,67.266,108.068,67.239,108.482,67.164,108.851,67.124,109.292,67.13,109.337,67.141,109.371,67.167,109.448,67.175,109.576,67.159,109.773,67.166,110.038,67.197,110.128,67.235,110.145,67.261,110.168,67.278,110.231,67.28,110.27,67.286,110.286,67.296,110.277,67.31,110.261,67.331,110.257,67.354,110.319,67.391,110.447,67.426,110.639,67.46,110.808,67.508,110.954,67.569,110.956,67.602,110.941,67.618,110.924,67.637,110.935,67.664,110.931,67.697,110.893,67.747,110.908,67.786,111.054,67.848,111.195,67.894,111.331,67.922,111.447,67.924,111.543,67.898,111.586,67.866,111.599,67.846,111.641,67.825,111.776,67.858,111.964,67.863,112.204,67.839,112.336,67.838,112.359,67.861,112.35,67.884,112.31,67.908,112.285,67.947,112.274,68.002,112.191,68.048,112.037,68.084,111.98,68.121,112.021,68.161,112.108,68.199,112.205,68.211,112.237,68.229,112.234,68.26,112.192,68.281,112.192,68.305,112.239,68.344,112.254,68.377,112.239,68.407,112.194,68.441,112.186,68.495,112.241,68.547,112.323,68.553,112.434,68.514,112.532,68.5,112.617,68.511,112.843,68.487,113.209,68.427,113.542,68.432,113.839,68.502,114.048,68.518,114.166,68.481,114.339,68.469,114.565,68.483,114.693,68.481,114.721,68.464,114.694,68.442,114.612,68.415,114.642,68.39,114.783,68.368,115.064,68.405,115.483,68.501,115.705,68.543,115.727,68.529,115.73,68.51,115.713,68.485,115.639,68.45,115.51,68.404,115.582,68.347,115.855,68.279,115.947,68.238,115.858,68.223,115.811,68.194,115.804,68.151,115.872,68.124,116.016,68.114,116.07,68.075,116.102,68.007,116.177,67.895,116.203,67.813,116.179,67.761,116.258,67.74,116.44,67.751,116.527,67.781,116.555,67.827,116.638,67.862,116.887,67.877,116.994,67.897,116.958,67.924,116.965,67.958,117.012,68.001,117.122,68.031,117.292,68.049,117.462,68.094,117.633,68.169,117.594,68.234,117.345,68.291,117.224,68.338,117.231,68.374,117.203,68.404,117.141,68.426,117.098,68.459,117.075,68.504,117.139,68.549,117.292,68.596,117.434,68.573,117.568,68.479,117.741,68.469,117.955,68.545,118.07,68.615,118.087,68.68,118.052,68.714,117.965,68.716,117.959,68.733,118.035,68.763,118.193,68.761,118.434,68.724,118.573,68.731,118.609,68.783,118.689,68.823,118.811,68.851,118.822,68.886,118.72,68.929,118.637,68.947,118.573,68.941,118.525,68.919,118.493,68.882,118.293,68.902,117.961,68.962,117.912,69.015,117.939,69.052,118.228,69.092,118.357,69.12,118.371,69.159,118.33,69.248,118.34,69.284,118.358,69.323,118.389,69.346,118.375,69.382,118.296,69.415,118.262,69.456,118.272,69.503,118.227,69.551,118.177,69.575,118.187,69.629,118.299,69.647,118.741,69.696,119.026,69.752,119.156,69.814,119.248,69.876,119.303,69.938,119.321,70.007,119.303,70.082,119.195,70.164,118.996,70.254,118.909,70.314,118.934,70.342,118.986,70.343,119.067,70.314,119.208,70.312,119.409,70.337,119.558,70.387,119.653,70.462,119.751,70.511,119.852,70.534,120.15,70.533,120.896,70.497,121.727,70.512,122.124,70.534,122.364,70.57,122.511,70.606,122.549,70.64,122.524,70.672,122.382,70.695,122.307,70.734,122.297,70.79,122.322,70.841,122.381,70.887,122.386,70.923,122.338,70.949,122.319,70.971,122.33,70.986,122.373,71.005,122.45,71.026,122.48,71.061,122.495,71.09,122.501,71.141,122.545,71.193,122.625,71.225,122.648,71.26,122.615,71.298,122.739,71.379,123.02,71.503,123.274,71.587,123.499,71.632,123.612,71.668,123.613,71.696,123.657,71.721,123.743,71.744,123.738,71.79,123.643,71.859,123.631,71.935,123.702,72.02,123.424,72.146,122.799,72.316,122.232,72.502,121.722,72.705,121.355,72.799,121.133,72.782,120.967,72.799,120.857,72.85,120.745,72.878,120.573,72.889,120.199,72.894,119.885,72.933,119.75,72.979]]},{"n":"Omo","z":5,"b":[35.912,4.539,37.6,9.151],"l":[[37.032,9.151,37.031,9.052,37.067,8.997,37.14,8.953,37.184,8.907,37.204,8.838,37.157,8.741,37.149,8.692,37.166,8.658,37.41,8.364,37.518,8.261,37.574,8.228,37.6,8.112,37.59,7.877,37.527,7.649,37.411,7.426,37.377,7.286,37.427,7.23,37.475,7.202,37.521,7.202,37.524,7.184,37.483,7.147,37.452,7.072,37.43,6.958,37.396,6.893,37.35,6.877,37.3,6.821,37.247,6.724,37.165,6.659,37.054,6.626,36.983,6.633,36.954,6.681,36.878,6.706,36.754,6.71,36.579,6.635,36.355,6.481,36.209,6.415,36.143,6.435,36.085,6.419,36.036,6.367,36.004,6.306,35.983,6.198,35.955,6.172,35.96,6.119,35.989,6.028,35.978,5.84,35.927,5.553,35.912,5.391,35.933,5.353,35.984,5.344,36.066,5.365,36.113,5.41,36.128,5.481,36.155,5.501,36.196,5.47,36.202,5.418,36.175,5.344,36.171,5.285,36.192,5.24,36.167,5.208,36.095,5.187,36.066,5.165,36.08,5.141,36.072,5.114,36.043,5.084,36.048,5.043,36.087,4.99,36.074,4.914,36.009,4.812,35.994,4.706,36.045,4.539]]},{"n":"Orange","z":4,"b":[16.448,-30.679,29.057,-28.031],"l":[[25.506,-30.612,25.344,-30.56,25.212,-30.473,24.978,-30.263],[24.731,-29.979,24.489,-29.818,24.392,-29.736,24.358,-29.678,24.296,-29.643,24.207,-29.632,24.103,-29.586,23.984,-29.506,23.923,-29.441,23.92,-29.393,23.882,-29.347,23.808,-29.304,23.736,-29.23,23.628,-29.074,23.577,-29.166,23.539,-29.205,23.498,-29.222,23.452,-29.219,23.412,-29.203,23.376,-29.211,23.348,-29.245,23.28,-29.288,23.163,-29.332,23.1,-29.37,23.088,-29.39,23.056,-29.412,23.014,-29.431,23.002,-29.486,22.981,-29.512,22.924,-29.522,22.892,-29.529,22.87,-29.537,22.852,-29.558,22.863,-29.624,22.85,-29.661,22.812,-29.669,22.741,-29.613,22.638,-29.493,22.507,-29.403,22.35,-29.343,22.266,-29.268,22.257,-29.177,22.209,-29.061,22.122,-28.918,22.056,-28.854,22.012,-28.867,21.963,-28.858,21.908,-28.825,21.835,-28.721,21.745,-28.544,21.662,-28.44,21.586,-28.408,21.486,-28.4,21.362,-28.417,21.236,-28.494,21.107,-28.629,20.97,-28.714,20.827,-28.748,20.704,-28.76,20.602,-28.747,20.485,-28.692,20.352,-28.593,20.21,-28.514,20.057,-28.453,19.878,-28.449,19.671,-28.504,19.54,-28.575,19.483,-28.662,19.407,-28.714,19.313,-28.733,19.271,-28.778,19.282,-28.848,19.246,-28.902,19.162,-28.939,19.026,-28.928,18.839,-28.869,18.6,-28.855,18.311,-28.886,18.103,-28.872,17.976,-28.811,17.842,-28.777,17.699,-28.768,17.617,-28.743,17.448,-28.698,17.416,-28.621,17.396,-28.563,17.348,-28.501,17.343,-28.452,17.38,-28.414,17.386,-28.353,17.359,-28.269,17.312,-28.229,17.246,-28.231,17.205,-28.199,17.188,-28.132,17.149,-28.082,17.056,-28.031,16.933,-28.07,16.875,-28.128,16.841,-28.219,16.81,-28.265,16.795,-28.341,16.787,-28.395,16.756,-28.452,16.723,-28.476,16.689,-28.465,16.626,-28.488,16.487,-28.573,16.448,-28.618],[29.057,-29.082,29.017,-29.218,28.958,-29.277,28.868,-29.304,28.809,-29.359,28.766,-29.481,28.721,-29.55,28.725,-29.586,28.762,-29.612,28.769,-29.643,28.744,-29.678,28.746,-29.732,28.773,-29.802,28.753,-29.851,28.686,-29.877,28.666,-29.91,28.693,-29.948,28.686,-29.984,28.645,-30.018,28.567,-30.038,28.454,-30.045,28.374,-30.032,28.325,-30.001,28.209,-30.006,28.025,-30.049,27.925,-30.101,27.906,-30.164,27.876,-30.213,27.835,-30.25,27.792,-30.264,27.744,-30.257,27.713,-30.282,27.698,-30.339,27.657,-30.374,27.591,-30.386,27.51,-30.371,27.431,-30.339,27.409,-30.325,27.39,-30.326,27.388,-30.335,27.389,-30.353,27.377,-30.372,27.345,-30.386,27.338,-30.402,27.337,-30.416,27.334,-30.427,27.31,-30.431,27.295,-30.445,27.283,-30.476,27.226,-30.499,27.082,-30.522,27.041,-30.541,27.028,-30.573,26.988,-30.609,26.897,-30.654,26.796,-30.678,26.684,-30.679,26.581,-30.65,26.488,-30.59,26.401,-30.565,26.32,-30.574,26.247,-30.556,26.18,-30.509,26.103,-30.5,25.972,-30.545]]},{"n":"Ord","z":5,"b":[127.578,-17.706,128.891,-15.298],"l":[[127.578,-17.623,127.929,-17.463,128.157,-17.523,128.258,-17.58,128.309,-17.654,128.356,-17.696,128.398,-17.706,128.502,-17.653,128.668,-17.537,128.789,-17.432,128.867,-17.335,128.891,-17.259,128.862,-17.204,128.856,-17.151,128.873,-17.102,128.837,-17.048,128.746,-16.99,128.697,-16.931,128.689,-16.871,128.698,-16.828,128.734,-16.788],[128.737,-16.134,128.703,-16.084,128.701,-16.032,128.744,-15.929,128.747,-15.802,128.712,-15.651,128.612,-15.563,128.447,-15.538,128.346,-15.495,128.289,-15.402,128.28,-15.35,128.255,-15.298]]},{"n":"Orinoco","z":3,"b":[-67.855,2.153,-61.099,9.788],"l":[[-63.436,2.374,-63.641,2.312,-63.732,2.258,-63.779,2.191,-63.849,2.156,-63.991,2.153,-64.279,2.265,-64.408,2.292,-64.475,2.271,-64.68,2.326,-65.022,2.456,-65.196,2.587,-65.202,2.718,-65.246,2.818,-65.328,2.886,-65.393,2.966,-65.442,3.059,-65.49,3.117,-65.537,3.142,-65.613,3.152,-65.772,3.142,-65.942,3.162,-66.011,3.13,-66.036,3.052,-66.129,3.024,-66.29,3.045,-66.413,3.091,-66.5,3.161,-66.556,3.237,-66.581,3.318,-66.645,3.42,-66.801,3.606,-66.946,3.706,-67.002,3.792,-67.015,3.896,-67.116,3.948,-67.302,3.946,-67.48,3.981,-67.732,4.087,-67.783,4.198,-67.799,4.284,-67.795,4.381,-67.814,4.455,-67.855,4.507,-67.855,4.665,-67.814,4.931,-67.804,5.133,-67.825,5.27,-67.788,5.375,-67.695,5.448,-67.642,5.559,-67.631,5.709,-67.575,5.833,-67.474,5.93,-67.439,6.026,-67.472,6.12,-67.423,6.202,-67.4,6.24,-67.225,6.386,-67.119,6.557,-67.062,6.851,-67.071,7.033,-66.959,7.151,-66.73,7.266,-66.563,7.396,-66.461,7.542,-66.297,7.629,-66.072,7.657,-65.912,7.714,-65.769,7.841,-65.641,7.892,-65.518,7.901,-65.358,7.883,-65.221,7.857,-65.105,7.823,-65.004,7.771,-64.928,7.71,-64.904,7.681,-64.887,7.668,-64.873,7.666,-64.854,7.668,-64.838,7.67,-64.787,7.685,-64.742,7.709,-64.59,7.76,-64.51,7.824,-64.538,7.892,-64.545,7.943,-64.531,7.978,-64.481,7.999,-64.395,8.006,-64.331,8.028,-64.288,8.065,-64.222,8.057,-64.155,8.005,-64.114,7.981,-64.089,7.975,-64.05,7.971,-63.993,7.974,-63.924,8.013,-63.849,8.092,-63.727,8.136,-63.558,8.145,-63.399,8.191,-63.249,8.273,-63.101,8.304,-62.953,8.284,-62.791,8.334,-62.615,8.454,-62.429,8.553,-62.235,8.633,-62.152,8.642],[-62.152,8.642,-62.092,8.648,-62.002,8.6,-61.88,8.582,-61.65,8.603,-61.619,8.597,-61.395,8.774,-61.099,8.941],[-62.063,9.036,-62.077,8.975,-62.065,8.928,-62.025,8.896,-62.039,8.828,-62.137,8.673,-62.152,8.642],[-61.736,9.631,-61.743,9.603,-61.849,9.289,-61.924,9.152,-62.044,9.055,-62.063,9.036],[-62.3,9.788,-62.309,9.748,-62.326,9.721,-62.414,9.562,-62.388,9.421,-62.277,9.245,-62.18,9.134,-62.098,9.089,-62.063,9.036]]},{"n":"Ottawa","z":5,"b":[-79.166,45.42,-74.315,46.827],"l":[[-76.903,45.892,-76.824,45.88,-76.754,45.798,-76.667,45.643,-76.553,45.542,-76.413,45.496,-76.27,45.484,-76.123,45.505,-76.001,45.484,-75.904,45.42,-75.626,45.451,-75.167,45.575,-74.864,45.642,-74.717,45.651,-74.577,45.632,-74.378,45.561,-74.315,45.531],[-77.371,46.063,-77.272,45.954,-77.155,45.912,-76.902,45.892],[-77.683,46.187,-77.373,46.064],[-77.683,46.187,-77.682,46.187],[-77.684,46.187,-77.683,46.187],[-79.166,46.827,-79.015,46.607,-78.903,46.486,-78.781,46.393,-78.46,46.307,-77.683,46.187]]},{"n":"Ouham","z":5,"b":[15.323,6.264,18.285,9.316],"l":[[15.323,6.462,15.398,6.342,15.496,6.325,15.714,6.393,15.79,6.461,15.846,6.486,15.908,6.49,15.96,6.437,16.001,6.328,16.075,6.27,16.182,6.264,16.276,6.302,16.358,6.383,16.404,6.448,16.414,6.497,16.474,6.56,16.584,6.637,16.663,6.671,16.709,6.662,16.774,6.609,16.857,6.514,16.933,6.472,17.001,6.486,17.082,6.475,17.174,6.441,17.281,6.448,17.401,6.497,17.509,6.52,17.605,6.515,17.709,6.558,17.821,6.65,17.9,6.772,17.948,6.926,18.002,7.023,18.062,7.062,18.127,7.139,18.197,7.254,18.249,7.313,18.285,7.314,18.275,7.362,18.22,7.457,18.145,7.49,18.051,7.46,17.984,7.469,17.945,7.517,17.877,7.548,17.782,7.561,17.749,7.611,17.778,7.698,17.768,7.826,17.718,7.995,17.699,8.127,17.709,8.224,17.77,8.369,17.882,8.56,17.945,8.744,17.96,8.92,17.992,9.011,18.04,9.018,18.098,9.056,18.164,9.126,18.207,9.2,18.235,9.316]]},{"n":"Pamir","z":4.7,"b":[72.657,37.029,73.605,37.472],"l":[[73.605,37.446,73.481,37.472,73.383,37.462,73.211,37.409,72.896,37.268,72.757,37.173,72.657,37.029]]},{"n":"Panama Canal","z":5,"b":[-79.927,8.924,-79.552,9.361],"l":[[-79.691,9.104,-79.566,8.968,-79.552,8.924],[-79.923,9.267,-79.927,9.304,-79.915,9.361]]},{"n":"Panj","z":4.7,"b":[68.212,36.684,72.657,38.456],"l":[[72.657,37.029,72.359,36.983,72.154,36.901,71.942,36.766,71.802,36.694,71.734,36.684,71.666,36.697,71.598,36.733,71.531,36.845,71.433,37.128,71.505,37.603,71.546,37.796,71.58,37.864,71.582,37.91,71.552,37.933,71.488,37.932,71.39,37.906,71.32,37.902,71.279,37.918,71.283,38.008,71.333,38.17,71.256,38.307,71.052,38.418,70.879,38.456,70.736,38.423,70.616,38.334,70.519,38.192,70.418,38.075,70.313,37.985,70.239,37.941,70.215,37.924,70.199,37.886,70.255,37.765,70.251,37.664,70.189,37.582,70.12,37.543,70.045,37.547,69.985,37.566,69.941,37.6,69.821,37.61,69.626,37.594,69.492,37.553,69.42,37.487,69.399,37.399,69.43,37.291,69.415,37.208,69.354,37.15,69.304,37.117,69.265,37.108,69.18,37.158,69.05,37.267,68.96,37.325,68.912,37.334,68.885,37.328,68.855,37.317,68.838,37.303,68.824,37.271,68.782,37.258,68.723,37.268,68.669,37.258,68.637,37.224,68.547,37.183,68.387,37.137,68.3,37.088,68.285,37.036,68.261,37.013,68.212,37.022]]},{"n":"Paraiba","z":5,"b":[-46.021,-23.396,-41.022,-21.582],"l":[[-45.049,-23.049,-45.505,-23.309,-45.606,-23.351,-45.709,-23.361,-45.85,-23.354,-45.934,-23.365,-45.961,-23.393,-45.99,-23.396,-46.021,-23.375,-46.004,-23.309,-45.941,-23.197,-45.861,-23.127,-45.765,-23.096,-45.666,-23.038,-45.565,-22.952,-45.446,-22.893,-45.31,-22.86,-45.167,-22.773,-45.016,-22.631,-44.863,-22.547,-44.706,-22.521,-44.651,-22.524,-44.593,-22.538,-44.522,-22.517,-44.362,-22.435,-44.256,-22.426,-44.205,-22.49,-44.151,-22.508,-44.094,-22.48,-44.04,-22.477,-43.989,-22.498,-43.852,-22.438,-43.629,-22.299,-43.425,-22.196,-43.144,-22.097],[-43.138,-22.097,-42.28,-21.714,-41.991,-21.6,-41.855,-21.582,-41.775,-21.619,-41.684,-21.631,-41.581,-21.618,-41.467,-21.646,-41.341,-21.714,-41.213,-21.714,-41.022,-21.611]]},{"n":"Paranaíba","z":4,"b":[-49.789,-19.171,-46.314,-18.083],"l":[[-46.314,-19.171,-46.561,-18.852,-46.517,-18.793,-46.514,-18.765,-46.538,-18.748,-46.54,-18.71,-46.519,-18.652,-46.535,-18.564,-46.586,-18.448,-46.697,-18.355,-46.867,-18.284,-46.992,-18.258,-47.071,-18.277,-47.152,-18.228,-47.235,-18.112,-47.319,-18.083,-47.447,-18.171],[-48.018,-18.434,-48.227,-18.34],[-49.108,-18.388,-49.179,-18.405,-49.247,-18.468,-49.337,-18.585,-49.417,-18.603,-49.488,-18.522,-49.553,-18.51,-49.612,-18.567,-49.678,-18.6,-49.789,-18.615]]},{"n":"Paraná","z":2.1,"b":[-60.757,-34.038,-50.49,-14.319],"l":[[-59.182,-33.806,-59.082,-33.822,-58.934,-33.884,-58.737,-33.994,-58.591,-34.038,-58.429,-33.991],[-60.39,-33.173,-60.282,-33.259,-59.854,-33.534,-59.548,-33.684,-59.362,-33.709,-59.287,-33.742,-59.241,-33.779,-59.182,-33.806],[-60.39,-33.173,-60.111,-33.249,-59.947,-33.268,-59.823,-33.25,-59.664,-33.315,-59.472,-33.463,-59.328,-33.604,-59.182,-33.806],[-60.391,-33.173,-60.39,-33.173],[-58.605,-27.314,-58.816,-27.438,-58.878,-27.512,-58.85,-27.611,-58.824,-27.718,-58.831,-27.826,-58.862,-27.971,-58.93,-28.076,-59.035,-28.142,-59.087,-28.288,-59.087,-28.514,-59.122,-28.735,-59.192,-28.948,-59.194,-28.981,-59.205,-29.032,-59.227,-29.058,-59.256,-29.083,-59.304,-29.104,-59.44,-29.17,-59.529,-29.25,-59.565,-29.335,-59.605,-29.495,-59.65,-29.73,-59.654,-29.898,-59.615,-29.999,-59.614,-30.11,-59.651,-30.233,-59.656,-30.336,-59.629,-30.421,-59.625,-30.526,-59.637,-30.604,-59.638,-30.669,-59.654,-30.717,-59.675,-30.76,-59.738,-30.83,-60.064,-31.27,-60.302,-31.554,-60.448,-31.666,-60.581,-31.699,-60.666,-31.768,-60.702,-31.871,-60.706,-31.959,-60.676,-32.033,-60.678,-32.136,-60.726,-32.335,-60.757,-32.517,-60.744,-32.645,-60.698,-32.78,-60.631,-32.916,-60.541,-33.053,-60.39,-33.173],[-54.611,-25.433,-54.632,-26.006,-54.678,-26.309,-54.755,-26.533,-54.826,-26.652,-54.889,-26.667,-54.934,-26.703,-54.962,-26.759,-55.014,-26.807,-55.089,-26.845,-55.13,-26.886,-55.136,-26.931,-55.208,-26.96,-55.346,-26.973,-55.427,-27.009,-55.451,-27.068,-55.497,-27.115,-55.565,-27.15,-55.597,-27.208,-55.594,-27.288,-55.633,-27.357,-55.715,-27.415,-55.79,-27.416,-55.859,-27.362,-55.951,-27.326,-56.067,-27.308,-56.164,-27.321,-56.242,-27.367,-56.311,-27.439,-56.37,-27.537,-56.437,-27.554,-56.511,-27.488,-56.603,-27.468,-56.716,-27.494,-56.805,-27.485,-56.872,-27.441,-56.974,-27.436,-57.112,-27.47,-57.391,-27.43,-57.812,-27.317,-58.168,-27.273,-58.605,-27.314],[-53.786,-23.333,-53.908,-23.407,-53.996,-23.57,-54.05,-23.823,-54.125,-23.978,-54.267,-24.066,-54.067,-24.004,-53.99,-23.838,-53.916,-23.577,-53.858,-23.423,-53.786,-23.333],[-52.961,-22.455,-53.116,-22.64,-53.272,-22.752,-53.481,-22.852,-53.62,-23.001,-53.69,-23.198,-53.786,-23.333],[-51.397,-20.441,-51.534,-20.558,-51.593,-20.666,-51.619,-20.807,-51.692,-20.944,-51.811,-21.077,-51.866,-21.183,-51.857,-21.26,-51.873,-21.33,-51.936,-21.423],[-50.49,-19.02,-50.532,-19.087,-50.578,-19.112,-50.642,-19.118,-50.713,-19.161,-50.792,-19.238,-50.844,-19.311,-50.869,-19.377,-50.871,-19.424,-50.864,-19.438,-50.881,-19.459,-50.924,-19.464,-50.964,-19.511,-50.997,-19.668],[-55.075,-14.319,-55.432,-14.357,-55.8,-14.549,-56.006,-14.635,-56.173,-14.68,-56.316,-14.778,-56.436,-14.928,-56.497,-15.037,-56.497,-15.104,-56.444,-15.171,-56.337,-15.239,-56.275,-15.328,-56.258,-15.437,-56.205,-15.531,-56.117,-15.609,-56.089,-15.68,-56.121,-15.746,-56.133,-15.801,-56.122,-15.845,-56.07,-15.896,-55.977,-15.952,-55.917,-16.009,-55.89,-16.069,-55.905,-16.13,-55.963,-16.195,-55.977,-16.254,-55.947,-16.309,-55.965,-16.362,-56.031,-16.413,-56.134,-16.453,-56.273,-16.483,-56.382,-16.565,-56.461,-16.699,-56.529,-16.785,-56.586,-16.822,-56.62,-16.872,-56.633,-16.936,-56.627,-16.993,-56.59,-17.067,-56.79,-17.386,-56.888,-17.518,-56.98,-17.595,-57.08,-17.734,-57.198,-17.812,-57.37,-17.876,-57.432,-18.045,-57.386,-18.321,-57.407,-18.497,-57.496,-18.573,-57.549,-18.664,-57.567,-18.769,-57.607,-18.838,-57.668,-18.873,-57.688,-18.919,-57.668,-18.978,-57.588,-19.019,-57.448,-19.042,-57.365,-19.07,-57.342,-19.103,-57.304,-19.233,-57.3,-19.297,-57.309,-19.351,-57.345,-19.414,-57.408,-19.485,-57.427,-19.529,-57.403,-19.546,-57.413,-19.587,-57.458,-19.652,-57.522,-19.697,-57.606,-19.721,-57.649,-19.752,-57.649,-19.789,-57.711,-19.861,-57.835,-19.97,-57.962,-20.059,-58.094,-20.13,-58.149,-20.177,-58.129,-20.202,-58.129,-20.226,-58.148,-20.25,-58.115,-20.304,-58.032,-20.39,-57.988,-20.498,-57.984,-20.628,-57.966,-20.687,-57.934,-20.675,-57.904,-20.685,-57.875,-20.715,-57.881,-20.746,-57.921,-20.778,-57.921,-20.803,-57.881,-20.822,-57.874,-20.85,-57.902,-20.887,-57.891,-20.918,-57.843,-20.942,-57.822,-21.009,-57.831,-21.119,-57.852,-21.199,-57.887,-21.25,-57.893,-21.289,-57.868,-21.317,-57.88,-21.375,-57.927,-21.464,-57.937,-21.553,-57.91,-21.643,-57.918,-21.789,-57.981,-22.09,-57.97,-22.215,-57.931,-22.29,-57.862,-22.356,-57.814,-22.455,-57.788,-22.589,-57.817,-22.671,-57.899,-22.701,-57.903,-22.751,-57.829,-22.822,-57.792,-22.898,-57.79,-22.98,-57.713,-23.084,-57.561,-23.21,-57.476,-23.348,-57.459,-23.499,-57.407,-23.613,-57.32,-23.692,-57.293,-23.779,-57.327,-23.875,-57.299,-23.972,-57.208,-24.069,-57.172,-24.132,-57.19,-24.162,-57.17,-24.223,-57.112,-24.318,-57.08,-24.403,-57.073,-24.478,-57.084,-24.547,-57.13,-24.64,-57.124,-24.724,-57.154,-24.787,-57.217,-24.857,-57.237,-24.915,-57.212,-24.961,-57.221,-24.984,-57.264,-24.983,-57.32,-25.015,-57.388,-25.08,-57.448,-25.112,-57.499,-25.11,-57.561,-25.154,-57.635,-25.245,-57.644,-25.328,-57.587,-25.405,-57.563,-25.474,-57.572,-25.534,-57.626,-25.599,-57.725,-25.667,-57.755,-25.697,-57.757,-25.726,-57.782,-25.784,-57.865,-25.907,-57.886,-25.964,-57.891,-26.007,-57.943,-26.053,-58.082,-26.139,-58.111,-26.18,-58.118,-26.225,-58.136,-26.251,-58.155,-26.263,-58.181,-26.307,-58.203,-26.381,-58.205,-26.477,-58.188,-26.593,-58.191,-26.63,-58.222,-26.65,-58.239,-26.677,-58.246,-26.731,-58.272,-26.771,-58.318,-26.796,-58.335,-26.825,-58.323,-26.858,-58.356,-26.89,-58.436,-26.922,-58.485,-26.968,-58.503,-27.03,-58.548,-27.084,-58.619,-27.132,-58.642,-27.196,-58.605,-27.314]]},{"n":"Peace","z":2.1,"b":[-122.222,55.934,-111.411,59.152],"l":[[-122.222,56.021,-122.17,55.972,-122.118,55.962,-122.048,55.974,-121.951,56.017,-121.826,56.091,-121.631,56.165,-121.365,56.24,-121.039,56.238,-120.654,56.16,-120.389,56.121,-120.244,56.122,-119.735,56.18,-119.492,56.231,-119.298,56.294,-119.145,56.316,-119.032,56.296,-118.978,56.267,-118.984,56.23,-118.963,56.188,-118.917,56.14,-118.907,56.1,-118.917,56.07,-118.869,56.017,-118.716,55.956,-118.449,55.934,-118.068,55.949,-117.739,56.023,-117.462,56.153,-117.273,56.288,-117.171,56.427,-117.128,56.536,-117.143,56.616,-117.177,56.669,-117.228,56.705,-117.23,56.733,-117.253,56.755,-117.291,56.787,-117.319,56.843,-117.295,56.925,-117.219,57.033,-117.209,57.083,-117.213,57.117,-117.195,57.137,-117.17,57.148,-117.121,57.18,-117.097,57.229,-117.106,57.268,-117.133,57.309,-117.127,57.357,-117.083,57.417,-117.091,57.477,-117.105,57.507,-117.095,57.53,-117.058,57.558,-117.054,57.6,-117.076,57.622,-117.094,57.664,-117.088,57.692,-117.093,57.719,-117.106,57.752,-117.098,57.774,-117.107,57.795,-117.1,57.823,-117.078,57.845,-117.083,57.886,-117.133,57.921,-117.148,57.971,-117.116,58.024,-117.053,58.067,-116.956,58.099,-116.832,58.112,-116.68,58.107,-116.592,58.115,-116.569,58.137,-116.573,58.162,-116.604,58.189,-116.612,58.216,-116.598,58.242,-116.616,58.261,-116.642,58.278,-116.644,58.292,-116.6,58.312,-116.544,58.31,-116.495,58.306,-116.496,58.278,-116.482,58.263,-116.455,58.259,-116.384,58.293,-116.271,58.363,-116.144,58.416,-116.003,58.45,-115.767,58.439,-115.435,58.382,-115.169,58.359,-114.968,58.37,-114.694,58.444,-114.347,58.58,-114.086,58.664,-113.823,58.71],[-113.714,58.669,-113.271,58.779,-113.127,58.832,-113.134,58.865,-113.101,58.889,-113.026,58.903,-112.944,58.958,-112.853,59.054,-112.769,59.118,-112.692,59.152,-112.528,59.143,-112.277,59.092,-112.102,59.078,-112.001,59.102,-111.944,59.099,-111.931,59.07,-111.962,59.035,-112.037,58.994,-112.059,58.963,-112.03,58.941,-111.966,58.94,-111.868,58.961,-111.76,58.953,-111.642,58.918,-111.539,58.927,-111.411,59.005]]},{"n":"Pechora","z":4,"b":[52.126,61.699,59.287,68.27],"l":[[59.287,62.058,59.175,62.022,59.076,62.022,58.89,62.057,58.777,61.996,58.639,62.008,58.438,62.073,58.234,62.067,58.027,61.988,57.909,61.913,57.879,61.84,57.828,61.806,57.756,61.812,57.617,61.783,57.412,61.72,57.229,61.699,57.009,61.728,56.962,61.763,56.882,61.797,56.857,61.803,56.842,61.816,56.851,61.846,56.836,61.903,56.796,61.984,56.729,62.049,56.666,62.082,56.641,62.1,56.631,62.13,56.63,62.16,56.644,62.178,56.7,62.234,56.726,62.344,56.706,62.489,56.625,62.595,56.483,62.661,56.36,62.701,56.257,62.715,56.213,62.753,56.227,62.817,56.201,62.884,56.103,62.99,55.936,63.16,55.941,63.191,55.956,63.209,55.993,63.22,56.056,63.196,56.174,63.222,56.292,63.29,56.37,63.305,56.419,63.313,56.441,63.35,56.445,63.383,56.47,63.416,56.519,63.427,56.541,63.442,56.53,63.464,56.478,63.502,56.467,63.539,56.504,63.566,56.556,63.589,56.639,63.661,56.712,63.763,56.944,63.839,57.336,63.89,57.47,63.912,57.507,63.942,57.511,63.986,57.525,64.017,57.584,64.052,57.603,64.09,57.577,64.123,57.581,64.16,57.559,64.197,57.565,64.231,57.579,64.258,57.624,64.29,57.638,64.325,57.673,64.34,57.716,64.373,57.73,64.397,57.773,64.411,57.783,64.443,57.787,64.474,57.793,64.496,57.805,64.535,57.807,64.579,57.828,64.651,57.879,64.74,57.844,64.806,57.722,64.847,57.608,64.908,57.503,64.988,57.4,65.029,57.301,65.034,57.256,65.06,57.265,65.108,57.203,65.137,57.068,65.147,57.016,65.241,57.047,65.421,56.967,65.568,56.777,65.683,56.744,65.789,56.777,65.847,56.834,65.912,56.837,65.971,56.806,66.003,56.736,66.033,56.621,66.122,56.491,66.257,56.283,66.325,55.998,66.326,55.763,66.302,55.412,66.2,55.264,66.142,55.171,66.065,55.133,65.968,55.093,65.91,55.029,65.878,54.836,65.752,54.711,65.702,54.588,65.686,54.456,65.625,54.315,65.516,54.103,65.458,53.822,65.449,53.588,65.417,53.403,65.361,53.194,65.333,52.959,65.332,52.67,65.357,52.327,65.408,52.153,65.466,52.15,65.532,52.179,65.629,52.239,65.759,52.349,65.869,52.507,65.96,52.567,66.02,52.528,66.049,52.507,66.111,52.506,66.206,52.487,66.287,52.451,66.354,52.454,66.404,52.496,66.438,52.504,66.474,52.478,66.514,52.471,66.556,52.484,66.599,52.451,66.651,52.373,66.71,52.354,66.831,52.392,67.014,52.381,67.115,52.287,67.145,52.169,67.26,52.132,67.318,52.136,67.358,52.17,67.396,52.234,67.43,52.231,67.456,52.16,67.473,52.126,67.507,52.128,67.557,52.247,67.614,52.483,67.678,52.627,67.699],[52.627,67.699,52.743,67.755,53.107,67.864],[52.627,67.699,52.68,67.678,52.813,67.673,53.025,67.684,53.184,67.734,53.291,67.824,53.514,67.911,53.852,67.995,54.067,68.081,54.207,68.209,54.233,68.266],[53.107,67.864,53.215,67.933,53.085,67.965,53.027,68.011,53.043,68.069,53.093,68.118,53.179,68.157,53.227,68.196,53.242,68.242,53.262,68.27],[53.914,68.231,53.749,68.175,53.672,68.125,53.621,68.057,53.495,67.987,53.192,67.88,53.107,67.864]]},{"n":"Pend Oreille","z":5,"b":[-117.682,48.178,-116.569,49.027],"l":[[-117.682,49.015,-117.414,49.027,-117.337,49.007,-117.324,48.961,-117.339,48.905,-117.383,48.84,-117.391,48.753,-117.363,48.645,-117.325,48.569,-117.278,48.527,-117.249,48.447,-117.239,48.33,-117.122,48.241,-116.901,48.182,-116.761,48.178,-116.705,48.23,-116.65,48.254,-116.569,48.25]]},{"n":"Pilcomayo","z":5,"b":[-66.444,-25.328,-57.644,-19.12],"l":[[-66.444,-19.37,-66.387,-19.308,-66.373,-19.268,-66.384,-19.23,-66.359,-19.196,-66.297,-19.165,-66.207,-19.206,-66.089,-19.318,-65.98,-19.366,-65.827,-19.342,-65.778,-19.25,-65.702,-19.186,-65.582,-19.12,-65.465,-19.143,-65.35,-19.254,-65.252,-19.328,-65.171,-19.362,-65.078,-19.377,-64.973,-19.373,-64.84,-19.545,-64.679,-19.894,-64.59,-20.157,-64.572,-20.336,-64.499,-20.441,-64.371,-20.472,-64.274,-20.586,-64.209,-20.785,-64.132,-20.899,-64.044,-20.929,-63.975,-21.002,-63.922,-21.117,-63.843,-21.176,-63.736,-21.178,-63.552,-21.242,-63.293,-21.367,-63.122,-21.466,-63.041,-21.539,-62.977,-21.633,-62.908,-21.806,-62.815,-22.05,-62.745,-22.16,-62.665,-22.218,-62.626,-22.262,-62.626,-22.29,-62.542,-22.35,-62.372,-22.439,-62.214,-22.612,-62.067,-22.869,-61.928,-23.059,-61.799,-23.182,-61.68,-23.269,-61.571,-23.319,-61.513,-23.36,-61.506,-23.392,-61.404,-23.457,-61.208,-23.557,-61.085,-23.656,-61.033,-23.756,-60.84,-23.858,-60.505,-23.964,-60.262,-24.014,-60.11,-24.009,-59.892,-24.094,-59.609,-24.267,-59.435,-24.387,-59.373,-24.454,-59.187,-24.562,-58.724,-24.787,-58.52,-24.843,-58.423,-24.894,-58.365,-24.959,-58.309,-24.979,-58.253,-24.954,-58.136,-24.977,-57.96,-25.049,-57.822,-25.136,-57.644,-25.328]]},{"n":"Pit","z":5,"b":[-122.095,40.784,-120.429,41.709],"l":[[-120.429,41.709,-120.457,41.558,-120.514,41.498,-120.679,41.451,-120.971,41.397,-121.115,41.289,-121.112,41.128,-121.13,41.023,-121.171,40.974,-121.223,40.977,-121.284,41.032,-121.347,41.042,-121.411,41.007,-121.496,41,-121.6,41.02,-121.689,41.015,-121.763,40.984,-121.829,40.986,-121.888,41.02,-121.932,41.02,-121.963,40.986,-121.97,40.947,-121.952,40.901,-121.981,40.854,-122.095,40.784]]},{"n":"Po","z":5,"b":[7.11,44.665,12.523,45.22],"l":[[7.11,44.706,7.326,44.665,7.424,44.687,7.502,44.795,7.619,44.849,7.665,44.925,7.677,45.041,7.731,45.129,7.824,45.19,7.976,45.22,8.185,45.219,8.36,45.199,8.5,45.161,8.594,45.113,8.644,45.054,8.808,45.054,9.224,45.141,9.462,45.1,9.505,45.093,9.55,45.116,9.607,45.129,9.652,45.114,9.699,45.086,9.733,45.083,9.754,45.104,9.782,45.103,9.816,45.08,9.846,45.085,9.87,45.118,9.909,45.133,9.96,45.13,10.008,45.106,10.05,45.06,10.154,45.026,10.317,45.003,10.43,44.972,10.491,44.935,10.557,44.928,10.628,44.953,10.666,44.986,10.671,45.028,10.74,45.057,10.871,45.072,11.02,45.075,11.188,45.065,11.312,45.029,11.392,44.968,11.479,44.927,11.574,44.906,11.684,44.918,11.869,44.985,12.085,45.02,12.199,45.013,12.283,44.976,12.377,44.961,12.523,44.968]]},{"n":"Porcupine","z":5,"b":[-145.453,65.65,-137.491,67.654],"l":[[-139.811,65.65,-139.728,65.748,-139.697,65.811,-139.687,65.903,-139.698,65.933,-139.671,65.959,-139.609,65.989,-139.488,66.002,-139.306,65.998,-139.166,66.016,-139.066,66.056,-138.923,66.177,-138.736,66.38,-138.554,66.511,-138.379,66.57,-138.249,66.642,-138.163,66.727,-138.068,66.768,-137.963,66.767,-137.886,66.787,-137.839,66.83,-137.821,66.862,-137.826,66.886,-137.801,66.902,-137.682,66.928,-137.603,66.986,-137.558,67.001,-137.521,66.992,-137.499,67.003,-137.491,67.032,-137.526,67.054,-137.604,67.07,-137.612,67.102,-137.551,67.152,-137.561,67.178,-137.639,67.18,-137.664,67.2,-137.701,67.237,-137.788,67.297,-137.816,67.339,-137.783,67.361,-137.773,67.395,-137.786,67.442,-137.857,67.469,-137.988,67.475,-138.103,67.505,-138.202,67.558,-138.335,67.581,-138.599,67.58,-138.629,67.594,-138.637,67.612,-138.622,67.634,-138.633,67.648,-138.67,67.654,-139.287,67.548,-139.668,67.575,-139.826,67.574,-139.887,67.554,-139.912,67.535,-139.9,67.517,-139.995,67.539,-140.109,67.538,-140.27,67.514,-140.352,67.492,-140.356,67.471,-140.511,67.454,-140.817,67.443,-141.103,67.409,-141.501,67.328,-141.617,67.29,-141.665,67.252,-141.683,67.201,-141.709,67.171,-141.742,67.162,-142.055,67.164,-142.111,67.168,-142.132,67.178,-142.137,67.197,-142.148,67.211,-142.181,67.22,-142.32,67.168,-142.565,67.054,-142.861,66.998,-143.209,67,-143.457,66.988,-143.606,66.961,-143.668,66.936,-143.644,66.912,-143.643,66.894,-143.664,66.884,-143.781,66.902,-143.851,66.885,-143.921,66.835,-144.041,66.797,-144.212,66.773,-144.32,66.773,-144.363,66.796,-144.39,66.796,-144.403,66.771,-144.465,66.759,-144.575,66.758,-144.686,66.729,-144.797,66.672,-144.911,66.649,-145.027,66.66,-145.197,66.645,-145.453,66.587]]},{"n":"Potomac","z":5,"b":[-78.325,38.889,-77.03,39.686],"l":[[-78.325,39.639,-78.181,39.686,-78.098,39.678,-78.027,39.631,-77.956,39.609,-77.883,39.611,-77.855,39.6,-77.872,39.576,-77.853,39.548,-77.798,39.517,-77.78,39.488,-77.797,39.461,-77.792,39.442,-77.762,39.429,-77.745,39.4,-77.739,39.354,-77.669,39.31,-77.534,39.266,-77.479,39.221,-77.503,39.175,-77.506,39.143,-77.481,39.113,-77.301,39.053,-77.19,38.969,-77.102,38.936,-77.053,38.915,-77.03,38.889]]},{"n":"Purari","z":5,"b":[143.492,-7.841,145.302,-5.938],"l":[[143.492,-5.938,143.639,-6.294,143.638,-6.387,143.682,-6.457,143.769,-6.536,143.878,-6.589,144.01,-6.617,144.124,-6.597,144.221,-6.528,144.301,-6.491,144.363,-6.485,144.494,-6.57,144.757,-6.801,144.781,-6.83,144.797,-6.859,144.802,-6.897,144.831,-6.914,144.884,-6.905,144.926,-6.91,144.959,-6.929,144.97,-6.952,144.966,-6.974,144.984,-6.992,145.042,-6.988,145.083,-7,145.106,-7.026,145.127,-7.03,145.153,-7.019,145.191,-7.027,145.265,-7.078,145.302,-7.166,145.301,-7.291,145.257,-7.361,145.17,-7.374,145.118,-7.411,145.101,-7.473,145.127,-7.538,145.195,-7.608,145.215,-7.687,145.186,-7.787,145.194,-7.841]]},{"n":"Purús","z":5,"b":[-72.426,-10.907,-61.406,-3.676],"l":[[-72.426,-10.845,-72.349,-10.907,-72.221,-10.901,-71.912,-10.824,-71.801,-10.718,-71.689,-10.662,-71.539,-10.621,-71.38,-10.543,-71.214,-10.429,-71.136,-10.356,-71.147,-10.323,-71.126,-10.292,-71.074,-10.263,-71.037,-10.18,-71.013,-10.043,-70.929,-9.912,-70.786,-9.788,-70.708,-9.696,-70.696,-9.635,-70.659,-9.595,-70.596,-9.575,-70.55,-9.514,-70.52,-9.412,-70.455,-9.33,-70.353,-9.266,-70.298,-9.211,-70.291,-9.166,-70.225,-9.123,-70.1,-9.083,-69.994,-9.077,-69.907,-9.104,-69.847,-9.1,-69.815,-9.065,-69.764,-9.047,-69.658,-9.046,-69.616,-9.059,-69.593,-9.045,-69.57,-9.013,-69.541,-9,-69.492,-9.004,-69.419,-8.964,-69.323,-8.874,-69.188,-8.798,-69.014,-8.738,-68.923,-8.731,-68.889,-8.748,-68.867,-8.77,-68.823,-8.782,-68.772,-8.771,-68.711,-8.795,-68.634,-8.883,-68.593,-8.953,-68.588,-9.006,-68.563,-9.028,-68.518,-9.018,-68.488,-9.029,-68.472,-9.06,-68.407,-9.065,-68.292,-9.046,-68.218,-9.046,-68.183,-9.066,-68.105,-9.044,-67.924,-8.947,-67.518,-8.777,-67.378,-8.682,-67.368,-8.603,-67.379,-8.574,-67.398,-8.561,-67.405,-8.535,-67.393,-8.487,-67.4,-8.459,-67.412,-8.443,-67.41,-8.425,-67.355,-8.38,-67.347,-8.343,-67.364,-8.316,-67.358,-8.295,-67.342,-8.254,-67.264,-8.149,-67.233,-8.069,-67.249,-8.014,-67.201,-7.939,-67.09,-7.843,-67.048,-7.798,-67.045,-7.775,-67.025,-7.76,-66.999,-7.76,-66.978,-7.742,-66.968,-7.701,-66.923,-7.672,-66.843,-7.653,-66.789,-7.658,-66.761,-7.685,-66.719,-7.673,-66.662,-7.621,-66.605,-7.606,-66.547,-7.626,-66.511,-7.617,-66.499,-7.577,-66.453,-7.541,-66.374,-7.509,-66.335,-7.523,-66.329,-7.562,-66.298,-7.578,-66.24,-7.567,-66.195,-7.587,-66.165,-7.638,-66.122,-7.658,-66.069,-7.657,-66.044,-7.684,-66.019,-7.696,-65.957,-7.675,-65.911,-7.68,-65.886,-7.702,-65.856,-7.701,-65.835,-7.683,-65.789,-7.684,-65.741,-7.698,-65.673,-7.691,-65.648,-7.662,-65.62,-7.646,-65.578,-7.647,-65.543,-7.632,-65.522,-7.599,-65.487,-7.592,-65.45,-7.596,-65.429,-7.582,-65.431,-7.561,-65.41,-7.549,-65.385,-7.552,-65.373,-7.535,-65.38,-7.514,-65.367,-7.493,-65.302,-7.45,-65.23,-7.411,-65.193,-7.408,-65.167,-7.426,-65.152,-7.4,-65.147,-7.327,-65.121,-7.311,-65.098,-7.307,-65.035,-7.325,-64.99,-7.312,-64.975,-7.268,-64.957,-7.257,-64.933,-7.25,-64.871,-7.276,-64.79,-7.252,-64.69,-7.178,-64.645,-7.131,-64.656,-7.11,-64.646,-7.096,-64.616,-7.087,-64.612,-7.075,-64.609,-7.063,-64.584,-7.049,-64.585,-7.02,-64.636,-6.929,-64.635,-6.872,-64.58,-6.848,-64.565,-6.825,-64.589,-6.803,-64.595,-6.77,-64.586,-6.725,-64.552,-6.702,-64.495,-6.7,-64.462,-6.679,-64.454,-6.638,-64.415,-6.6,-64.345,-6.567,-64.298,-6.474,-64.261,-6.246,-64.304,-6.025,-64.357,-5.934,-64.433,-5.901,-64.454,-5.867,-64.418,-5.833,-64.397,-5.787,-64.392,-5.728,-64.368,-5.695,-64.326,-5.689,-64.311,-5.665,-64.323,-5.622,-64.292,-5.598,-64.218,-5.592,-64.181,-5.603,-64.182,-5.631,-64.163,-5.647,-64.124,-5.652,-64.095,-5.638,-64.075,-5.605,-64.019,-5.589,-63.927,-5.59,-63.857,-5.624,-63.811,-5.69,-63.744,-5.716,-63.657,-5.702,-63.63,-5.721,-63.635,-5.757,-63.624,-5.788,-63.59,-5.811,-63.542,-5.8,-63.48,-5.755,-63.427,-5.742,-63.383,-5.762,-63.304,-5.733,-63.191,-5.655,-63.158,-5.662,-63.144,-5.683,-63.122,-5.716,-63.072,-5.728,-63.052,-5.707,-63.063,-5.622,-63.047,-5.573,-63.002,-5.56,-62.995,-5.531,-62.98,-5.473,-62.957,-5.46,-62.936,-5.437,-62.901,-5.437,-62.856,-5.43,-62.83,-5.392,-62.829,-5.276,-62.839,-5.208,-62.861,-5.187,-62.843,-5.153,-62.785,-5.106,-62.763,-5.061,-62.778,-5.02,-62.748,-4.968,-62.7,-4.944,-62.658,-4.951,-62.617,-4.937,-62.601,-4.913,-62.589,-4.875,-62.551,-4.857,-62.51,-4.862,-62.473,-4.837,-62.462,-4.808,-62.476,-4.776,-62.453,-4.744,-62.393,-4.713,-62.313,-4.704,-62.214,-4.716,-62.115,-4.664,-62.079,-4.631,-62.033,-4.59,-62.016,-4.538,-62.04,-4.487,-62.023,-4.444,-61.992,-4.429,-61.921,-4.415,-61.875,-4.38,-61.854,-4.324,-61.79,-4.28,-61.681,-4.248,-61.564,-4.146,-61.441,-3.973,-61.406,-3.832,-61.468,-3.676]]},{"n":"Pánuco","z":5,"b":[-98.59,21.965,-97.782,22.279],"l":[[-98.59,21.981,-98.471,21.965,-98.425,21.98,-98.414,22.019,-98.376,22.041,-98.311,22.046,-98.258,22.066,-98.217,22.102,-98.178,22.099,-98.139,22.056,-98.097,22.047,-98.051,22.07,-98.047,22.107,-98.051,22.123,-98.015,22.178,-97.966,22.209,-97.871,22.219,-97.782,22.279]]},{"n":"Quan","z":4,"b":[109.529,23.405,110.089,23.796],"l":[[109.529,23.796,109.543,23.729,109.575,23.7,109.629,23.688,109.651,23.665,109.641,23.629,109.663,23.575,109.717,23.502,109.796,23.454,109.9,23.43,109.963,23.431,109.986,23.455,110.021,23.452,110.089,23.405]]},{"n":"R. des Outaouais","z":5,"b":[-79.496,47.488,-76.819,47.938],"l":[[-76.819,47.517,-76.891,47.539,-76.92,47.532,-76.935,47.488],[-77.322,47.573,-77.361,47.578,-77.431,47.557,-77.457,47.556],[-78.813,47.656,-78.987,47.554,-79.068,47.537,-79.113,47.572,-79.225,47.586,-79.496,47.577],[-77.544,47.671,-77.527,47.744],[-77.567,47.83,-77.621,47.818,-77.729,47.733,-77.834,47.728,-77.933,47.79,-78.004,47.784,-78.047,47.709,-78.127,47.688,-78.246,47.722,-78.342,47.764,-78.414,47.815,-78.449,47.861,-78.445,47.903,-78.485,47.929,-78.57,47.938,-78.641,47.925,-78.698,47.888,-78.726,47.823,-78.716,47.709,-78.724,47.684]]},{"n":"Rainy","z":5,"b":[-94.713,48.525,-90.503,49.329],"l":[[-93.377,48.617,-93.464,48.561,-93.564,48.537,-93.708,48.525,-93.804,48.549,-93.852,48.607,-94.055,48.659,-94.414,48.704,-94.621,48.743,-94.675,48.774,-94.705,48.809,-94.713,48.863],[-91.629,48.865,-91.659,48.792,-91.757,48.762,-91.935,48.751,-92.06,48.76,-92.132,48.787,-92.321,48.77,-92.779,48.678],[-90.503,49.329,-90.613,49.219,-90.59,49.169,-90.601,49.13,-90.637,49.086,-90.756,49.054,-90.957,49.032,-91.123,48.973,-91.319,48.828]]},{"n":"Rhein","z":4,"b":[8.751,46.676,9.626,47.671],"l":[[8.751,46.676,9.046,46.758,9.416,46.835],[9.409,46.835,9.517,46.907,9.534,46.963,9.496,47.028,9.479,47.098,9.484,47.173,9.528,47.271,9.609,47.392,9.626,47.467,9.554,47.511],[9.128,47.671,9.183,47.671]]},{"n":"Rhein","z":4,"b":[7.529,47.564,8.881,48.965],"l":[[8.881,47.656,8.7,47.686,8.62,47.662,8.58,47.595,8.482,47.576,8.328,47.607,8.198,47.607,8.094,47.576,7.927,47.564,7.698,47.57,7.565,47.607,7.529,47.674,7.539,47.774,7.593,47.906,7.608,48.003,7.584,48.064,7.617,48.157,7.706,48.28,7.765,48.41,7.795,48.547,7.838,48.636,7.923,48.699,8.23,48.965]]},{"n":"Rhine","z":4,"b":[4.985,48.965,8.498,51.888],"l":[[8.23,48.965,8.417,49.214,8.486,49.33,8.498,49.397,8.489,49.436,8.458,49.447,8.422,49.505,8.381,49.611,8.389,49.69,8.431,49.728,8.437,49.767,8.401,49.815,8.357,49.911,8.305,49.981,8.243,50.023,8.129,50.026,7.962,49.991,7.803,50.042,7.654,50.181,7.607,50.222,7.611,50.266,7.629,50.324,7.608,50.367,7.557,50.4,7.444,50.433,7.344,50.486,7.257,50.559,7.204,50.622,7.185,50.673,7.125,50.734,7.025,50.805,6.995,50.853,6.995,50.904,6.973,50.938,6.968,50.998,6.938,51.04,6.881,51.064,6.851,51.097,6.847,51.139,6.816,51.169,6.758,51.187,6.76,51.227,6.741,51.268,6.69,51.303,6.698,51.364,6.707,51.422,6.698,51.458,6.693,51.487,6.687,51.524,6.624,51.559,6.593,51.592,6.596,51.637,6.357,51.775,6.212,51.837,6.08,51.869,6.039,51.872,5.975,51.877,5.898,51.86,5.78,51.863,5.622,51.888,5.492,51.878,5.389,51.833,5.25,51.813,4.985,51.824]]},{"n":"Rhône","z":5,"b":[4.371,43.401,8.25,46.482],"l":[[4.617,43.71,4.514,43.702,4.467,43.683,4.44,43.65,4.436,43.627,4.444,43.613,4.434,43.591,4.379,43.56,4.371,43.528,4.41,43.447],[6.133,46.205,5.902,46.123,5.814,46.013,5.793,45.849,5.747,45.737,5.676,45.676,5.573,45.704,5.437,45.821,5.35,45.847,5.285,45.812,5.191,45.799,5.058,45.817,4.952,45.808,4.871,45.771,4.824,45.713,4.809,45.633,4.819,45.582,4.853,45.561,4.841,45.513,4.784,45.439,4.79,45.289,4.858,45.063,4.864,44.911,4.806,44.834,4.781,44.761,4.786,44.693,4.772,44.637,4.721,44.573,4.674,44.35,4.675,44.228,4.711,44.133,4.759,44.071,4.82,44.04,4.843,44.012,4.821,43.973,4.69,43.906,4.638,43.828,4.619,43.715,4.631,43.639,4.695,43.579,4.724,43.554,4.735,43.508,4.743,43.449,4.787,43.401],[8.25,46.482,8.068,46.374,7.955,46.335,7.799,46.325,7.653,46.327,7.471,46.277,7.205,46.178,7.089,46.152,7.02,46.197,6.866,46.403]]},{"n":"Rio Grande","z":4,"b":[-107.427,25.871,-97.146,37.809],"l":[[-99.172,26.564,-99.108,26.447,-99.015,26.399,-98.873,26.381,-98.765,26.34,-98.691,26.276,-98.598,26.238,-98.486,26.225,-98.378,26.182,-98.275,26.111,-98.083,26.064,-97.801,26.042,-97.587,25.984,-97.44,25.891,-97.376,25.872,-97.358,25.871,-97.35,25.885,-97.339,25.911,-97.282,25.942,-97.146,25.961],[-101.039,29.46,-101.016,29.401,-100.924,29.315,-100.755,29.183,-100.659,29.069,-100.636,28.973,-100.55,28.821,-100.399,28.614,-100.332,28.503,-100.348,28.486,-100.336,28.428,-100.296,28.328,-100.221,28.243,-100.112,28.173,-100.001,28.048,-99.89,27.867,-99.754,27.73,-99.595,27.636,-99.505,27.548,-99.484,27.467,-99.486,27.398,-99.51,27.34,-99.5,27.286,-99.455,27.234,-99.44,27.17,-99.458,27.082,-99.457,27.057],[-106.566,31.82,-106.436,31.764,-106.347,31.679,-106.256,31.545,-106.148,31.451,-106.024,31.398,-105.813,31.241,-105.514,30.981,-105.276,30.807,-105.098,30.721,-104.979,30.646,-104.918,30.583,-104.836,30.448,-104.681,30.134,-104.681,29.991,-104.622,29.854,-104.504,29.678,-104.401,29.574,-104.312,29.542,-104.216,29.48,-104.111,29.386,-103.99,29.323,-103.853,29.291,-103.664,29.207,-103.423,29.071,-103.258,29.001,-103.168,28.998,-103.09,29.042,-103.023,29.132,-102.957,29.19,-102.892,29.216,-102.866,29.258,-102.878,29.315,-102.834,29.444,-102.734,29.644,-102.615,29.752,-102.476,29.769,-102.386,29.807,-102.343,29.865,-102.269,29.871,-102.163,29.825,-101.991,29.796,-101.752,29.782,-101.612,29.787,-101.569,29.809,-101.546,29.808,-101.545,29.784,-101.509,29.773,-101.44,29.777,-101.38,29.743,-101.304,29.634],[-107.293,32.91,-107.24,32.743,-107.172,32.674,-107.07,32.649,-106.975,32.584,-106.889,32.479,-106.846,32.399,-106.847,32.346,-106.777,32.195,-106.566,31.82],[-107.181,33.173,-107.28,33.116],[-107.427,37.776,-107.067,37.744,-106.924,37.809,-106.814,37.792,-106.688,37.716,-106.542,37.678,-106.374,37.678,-106.196,37.647,-106.007,37.584,-105.879,37.525,-105.811,37.469,-105.763,37.406,-105.735,37.335,-105.732,37.24,-105.752,37.119,-105.743,36.98,-105.703,36.823,-105.695,36.651,-105.719,36.464,-105.761,36.339,-105.85,36.244,-106.006,36.133,-106.073,36.048,-106.101,35.951,-106.166,35.855,-106.268,35.76,-106.343,35.648,-106.392,35.519,-106.487,35.376,-106.628,35.218,-106.692,35.089,-106.682,34.989,-106.716,34.781,-106.796,34.465,-106.85,34.295,-106.878,34.269,-106.886,34.147,-106.874,33.928,-106.928,33.749,-107.048,33.61,-107.123,33.494,-107.17,33.356]]},{"n":"Rosetta Branch","z":2,"b":[30.395,30.121,31.238,31.458],"l":[[31.238,30.121,31.212,30.139,31.163,30.151,31.053,30.192,30.972,30.245,30.935,30.306,30.896,30.335,30.855,30.354,30.858,30.384,30.848,30.471,30.786,30.666,30.77,30.78,30.785,30.811,30.772,30.844,30.762,30.866,30.763,30.944,30.747,31.011,30.715,31.065,30.656,31.118,30.569,31.17,30.523,31.229,30.52,31.295,30.488,31.357,30.395,31.458]]},{"n":"Ruki","z":5,"b":[18.184,-0.219,18.951,0.082],"l":[[18.951,-0.219,18.804,-0.121,18.662,-0.069,18.475,-0.031,18.36,0.012,18.318,0.062,18.272,0.082,18.184,0.063]]},{"n":"Rupert","z":5,"b":[-78.731,51.07,-73.641,51.546],"l":[[-74.818,51.412,-74.59,51.48,-74.491,51.49,-74.445,51.467,-74.389,51.462,-74.337,51.46,-74.297,51.435,-74.256,51.405,-74.169,51.376,-74.073,51.371,-73.966,51.387,-73.891,51.349,-73.847,51.257,-73.837,51.175,-73.861,51.103,-73.815,51.07,-73.641,51.078],[-75.204,51.446,-75.229,51.485,-75.226,51.508,-75.242,51.527,-75.327,51.54,-75.481,51.546,-75.822,51.5,-76.553,51.364],[-78.731,51.497,-78.69,51.487,-78.457,51.397,-78.157,51.362,-77.713,51.354,-77.304,51.378,-76.931,51.435,-76.821,51.415]]},{"n":"Ruvuma","z":5,"b":[35.199,-11.716,40.464,-10.464],"l":[[35.599,-10.807,35.358,-10.712,35.26,-10.696,35.224,-10.726,35.204,-10.776,35.199,-10.879,35.368,-11.406,35.451,-11.59,35.504,-11.605,35.564,-11.602,35.631,-11.582,35.705,-11.532,35.785,-11.453,35.911,-11.455,36.082,-11.537,36.175,-11.609,36.191,-11.671,36.306,-11.706,36.519,-11.716,36.674,-11.684,36.771,-11.61,36.873,-11.571,36.979,-11.567,37.059,-11.592,37.114,-11.647,37.218,-11.687,37.373,-11.71,37.542,-11.675,37.725,-11.581,37.829,-11.482,37.855,-11.379,37.885,-11.317,37.92,-11.295,38.017,-11.282,38.177,-11.279,38.315,-11.311,38.492,-11.413,38.795,-11.229,38.988,-11.167,39.171,-11.167,39.322,-11.123,39.439,-11.035,39.563,-10.979,39.694,-10.955,39.817,-10.912,39.989,-10.821,40.464,-10.464]]},{"n":"Río Grande de Santiago","z":5,"b":[-105.457,20.31,-102.78,21.822],"l":[[-102.78,20.31,-102.79,20.368,-102.866,20.398,-103.07,20.443,-103.121,20.478,-103.153,20.525,-103.147,20.587,-103.19,20.663,-103.293,20.735,-103.341,20.795,-103.332,20.841,-103.351,20.91,-103.398,21.002,-103.44,21.051,-103.478,21.059,-103.521,21.04,-103.592,20.972],[-103.709,20.938,-103.842,20.989,-103.885,21.027,-103.882,21.068,-103.93,21.118,-104.079,21.205],[-104.079,21.206,-104.18,21.2,-104.253,21.22,-104.33,21.263,-104.364,21.298,-104.442,21.433,-104.526,21.502,-104.634,21.708,-104.664,21.765,-104.724,21.817,-104.779,21.822,-104.892,21.767,-105,21.729,-105.021,21.739,-105.033,21.777],[-105.104,21.822,-105.176,21.804,-105.207,21.795,-105.246,21.756,-105.339,21.705,-105.411,21.691,-105.457,21.672]]},{"n":"S. Branch Potomac","z":5,"b":[-79.571,38.415,-78.325,39.639],"l":[[-79.571,38.415,-79.385,38.634,-79.253,38.901,-79.163,38.999,-79.071,39.017,-78.956,39.124,-78.818,39.32,-78.705,39.447,-78.575,39.533,-78.496,39.533,-78.46,39.556,-78.443,39.601,-78.407,39.628,-78.325,39.639]]},{"n":"Sacramento","z":4.7,"b":[-122.432,38.08,-121.504,40.721],"l":[[-121.581,38.659,-121.514,38.618,-121.504,38.58,-121.528,38.532,-121.531,38.475,-121.514,38.41,-121.519,38.366,-121.546,38.342,-121.544,38.311,-121.512,38.273,-121.544,38.228,-121.572,38.195,-121.689,38.148,-121.708,38.128,-121.725,38.101,-121.749,38.08],[-122.411,40.721,-122.432,40.648,-122.374,40.574,-122.244,40.474,-122.182,40.401,-122.188,40.354,-122.177,40.327,-122.15,40.319,-122.153,40.282,-122.187,40.216,-122.184,40.175,-122.144,40.159,-122.104,40.067,-122.064,39.9,-122.017,39.794,-121.964,39.75,-121.956,39.632,-121.993,39.44,-122.005,39.317,-121.992,39.262,-121.941,39.186,-121.853,39.091,-121.805,39.002,-121.797,38.919,-121.762,38.851,-121.701,38.796,-121.649,38.769,-121.606,38.772,-121.584,38.744,-121.581,38.659]]},{"n":"Saint John","z":5,"b":[-70.264,45.335,-66.053,47.354],"l":[[-70.264,46.183,-70.208,46.315,-70.161,46.374,-70.075,46.42,-70.014,46.494,-69.96,46.514,-69.893,46.504,-69.862,46.518,-69.867,46.558,-69.729,46.7,-69.449,46.943,-69.261,47.08,-69.167,47.113,-69.096,47.122,-69.05,47.108,-68.961,47.135,-68.829,47.203,-68.669,47.253,-68.48,47.286,-68.377,47.316,-68.358,47.345,-68.311,47.354,-68.235,47.346,-68.097,47.275,-67.794,47.074,-67.711,46.984,-67.698,46.868,-67.725,46.696,-67.702,46.584,-67.628,46.532,-67.599,46.492,-67.615,46.462,-67.598,46.407,-67.546,46.327,-67.528,46.227,-67.545,46.109,-67.497,46.03,-67.386,45.993,-67.292,45.977,-67.214,45.985,-67.123,45.964,-67.02,45.914,-66.935,45.912,-66.867,45.959,-66.796,45.982,-66.684,45.985,-66.524,45.899,-66.394,45.858,-66.24,45.834,-66.131,45.757,-66.065,45.628,-66.053,45.569,-66.081,45.52,-66.179,45.435,-66.218,45.379,-66.218,45.355,-66.183,45.335]]},{"n":"Salado","z":5,"b":[-65.865,-31.959,-60.608,-24.208],"l":[[-65.865,-24.208,-65.82,-24.417,-65.855,-24.53,-65.831,-24.642,-65.76,-24.791,-65.649,-24.925,-65.498,-25.046,-65.424,-25.136,-65.427,-25.194,-65.382,-25.235,-65.29,-25.258,-65.205,-25.228,-65.126,-25.144,-65.049,-25.119,-64.974,-25.151,-64.923,-25.202,-64.896,-25.271,-64.83,-25.321,-64.722,-25.351,-64.601,-25.329,-64.465,-25.257,-64.368,-25.185,-64.309,-25.114,-64.253,-25.086,-64.2,-25.102,-64.117,-25.227,-64.006,-25.463,-63.951,-25.613,-63.954,-25.677,-63.897,-25.795,-63.781,-25.966,-63.738,-26.092,-63.769,-26.172,-63.754,-26.306,-63.692,-26.493,-63.676,-26.658,-63.707,-26.801,-63.712,-26.937,-63.691,-27.066,-63.646,-27.206,-63.576,-27.356,-63.546,-27.477,-63.559,-27.617,-63.572,-27.663,-63.529,-27.772,-63.435,-27.958,-63.321,-28.103,-63.188,-28.209,-63.116,-28.296,-63.105,-28.365,-63.058,-28.417,-62.976,-28.452,-62.883,-28.521,-62.78,-28.624,-62.695,-28.779,-62.627,-28.987,-62.504,-29.155,-62.326,-29.283,-62.166,-29.358,-62.023,-29.382,-61.896,-29.363,-61.785,-29.302,-61.703,-29.299,-61.649,-29.355,-61.508,-29.422,-61.281,-29.5,-61.064,-29.597,-60.859,-29.714,-60.742,-29.799,-60.714,-29.852,-60.706,-29.96,-60.717,-30.123,-60.705,-30.223,-60.67,-30.26,-60.658,-30.345,-60.669,-30.477,-60.655,-30.576,-60.618,-30.642,-60.608,-30.695,-60.625,-30.733,-60.679,-30.764,-60.771,-30.787,-60.821,-30.831,-60.835,-30.926,-60.807,-31.07,-60.828,-31.172,-60.889,-31.28,-60.889,-31.341,-60.828,-31.357,-60.787,-31.447,-60.766,-31.61,-60.777,-31.733,-60.819,-31.816,-60.811,-31.882,-60.706,-31.959]]},{"n":"Salween","z":4,"b":[97.332,16.552,98.679,20.951],"l":[[98.508,20.951,98.502,20.828,98.538,20.712,98.613,20.561,98.66,20.414,98.679,20.272,98.552,20.149,98.141,19.99,97.943,19.961,97.865,19.929,97.841,19.884,97.81,19.858,97.771,19.85,97.696,19.718,97.531,19.331,97.457,18.852,97.44,18.831,97.406,18.828,97.353,18.843,97.332,18.778,97.341,18.634,97.381,18.494,97.451,18.36,97.524,18.296,97.599,18.303,97.632,18.29,97.622,18.258,97.652,18.174,97.72,18.037,97.74,17.935,97.699,17.834,97.755,17.571,97.749,17.444,97.7,17.364,97.659,17.251,97.626,17.107,97.609,16.947,97.606,16.772,97.622,16.652,97.668,16.552]]},{"n":"San Joaquin","z":5,"b":[-121.525,36.805,-118.737,38.056],"l":[[-119.698,37,-119.78,36.897,-119.94,36.843,-120.205,36.805,-120.393,36.866,-120.504,37.024,-120.605,37.118,-120.695,37.148,-120.856,37.285,-121.204,37.652],[-118.737,37.096,-118.808,37.203,-119.108,37.412,-119.153,37.441,-119.188,37.456,-119.223,37.438,-119.246,37.405,-119.323,37.253,-119.404,37.167,-119.488,37.149,-119.548,37.12,-119.601,37.062],[-121.204,37.651,-121.255,37.703,-121.282,37.78,-121.303,37.9,-121.347,37.971,-121.445,38.004,-121.468,38.017,-121.496,38.042,-121.525,38.056]]},{"n":"San Juan","z":4.7,"b":[-86.105,-32.389,-67.362,12.208],"l":[[-69.418,-31.283,-69.397,-31.277,-69.185,-31.269,-69.078,-31.295,-69.029,-31.353,-69.004,-31.415,-69.002,-31.481,-68.944,-31.519,-68.828,-31.529,-68.693,-31.515,-68.539,-31.477,-68.451,-31.472,-68.418,-31.512,-68.367,-31.573,-68.357,-31.654,-68.37,-31.775,-68.349,-31.883,-68.294,-31.979,-68.28,-32.044,-68.27,-32.072,-68.226,-32.084,-68.089,-32.074,-67.974,-32.112,-67.879,-32.197,-67.744,-32.25,-67.567,-32.27,-67.449,-32.308,-67.362,-32.389],[-84.769,11.118,-84.488,11.044,-84.348,10.98,-84.256,10.901,-84.205,10.841,-84.197,10.802,-84.168,10.78,-84.096,10.776,-83.919,10.735,-83.811,10.743,-83.713,10.786,-83.659,10.837,-83.641,10.915],[-86.105,12.208,-85.871,12.098]]},{"n":"Sanaga","z":5,"b":[9.766,3.624,13.126,4.993],"l":[[13.126,4.933,12.994,4.984,12.901,4.993,12.712,4.96,12.631,4.936,12.548,4.877,12.463,4.782,12.385,4.733,12.28,4.725,12.131,4.575,12.058,4.544,12.012,4.583,11.907,4.542,11.742,4.423,11.596,4.399,11.468,4.469,11.296,4.412,11.079,4.227,10.859,4.108,10.636,4.053,10.502,4.047,10.459,4.088,10.392,4.045,10.304,3.919,10.235,3.859,10.186,3.865,10.116,3.813,10.024,3.703,9.938,3.643,9.82,3.628,9.766,3.624]]},{"n":"Sangha","z":5,"b":[16.09,-1.272,17.237,1.654],"l":[[16.098,1.654,16.09,1.617,16.15,1.487,16.212,1.426,16.281,1.419,16.357,1.366,16.438,1.268,16.501,1.221,16.554,1.184,16.601,1.058,16.664,0.716,16.73,0.524,16.797,0.482,16.86,0.475,16.905,0.456,16.923,0.431,16.964,0.41,16.991,0.408,17.036,0.394,17.074,0.362,17.096,0.316,17.103,0.256,17.131,0.221,17.18,0.211,17.19,0.169,17.161,0.095,17.156,0,17.177,-0.115,17.174,-0.214,17.148,-0.298,17.146,-0.346,17.169,-0.36,17.173,-0.391,17.159,-0.44,17.17,-0.471,17.207,-0.485,17.229,-0.556,17.237,-0.682,17.228,-0.744,17.181,-0.773,17.166,-0.842,17.103,-0.92,16.99,-1.009,16.913,-1.102,16.849,-1.272]]},{"n":"Santa Cruz","z":4.7,"b":[-71.982,-50.24,-68.98,-50.003],"l":[[-71.982,-50.224,-71.362,-50.209,-71.112,-50.215,-71.024,-50.236,-70.937,-50.232,-70.852,-50.202,-70.671,-50.204,-70.395,-50.24,-70.239,-50.24,-70.204,-50.205,-70.154,-50.188,-70.06,-50.187,-69.83,-50.187,-69.722,-50.158,-69.659,-50.101,-69.516,-50.086,-69.294,-50.115,-69.141,-50.101,-69.013,-50.017,-68.98,-50.003]]},{"n":"Saskatchewan","z":4,"b":[-105.065,53.157,-99.23,54.071],"l":[[-99.33,53.159,-99.23,53.185],[-105.065,53.24,-104.901,53.164,-104.819,53.157,-104.765,53.193,-104.571,53.233,-104.238,53.276,-104.027,53.344,-103.894,53.482],[-103.421,53.653,-103.129,53.752,-103.045,53.809,-103.072,53.855,-103.05,53.922,-102.978,54.009,-102.9,54.059,-102.813,54.071,-102.686,54.053,-102.434,53.982],[-102.381,53.966,-102.188,53.904,-102.04,53.884,-101.872,53.885,-101.732,53.904,-101.62,53.942,-101.548,53.981,-101.516,54.021,-101.46,53.994,-101.381,53.903,-101.265,53.862,-101.112,53.874,-101.043,53.859,-101.056,53.817,-101.026,53.761,-100.953,53.69,-100.782,53.66,-100.514,53.67,-100.366,53.654,-100.323,53.591]]},{"n":"Sassandra","z":5,"b":[-7.255,4.953,-6.062,9.714],"l":[[-7.008,6.264,-6.953,6.185,-6.776,6.054,-6.701,5.973,-6.731,5.945,-6.733,5.902,-6.708,5.845,-6.676,5.81,-6.637,5.797,-6.592,5.736,-6.543,5.628,-6.481,5.538,-6.373,5.43,-6.274,5.32,-6.232,5.226,-6.215,5.111,-6.171,5.03,-6.062,4.953],[-7.236,8.945,-7.201,8.828,-7.199,8.747,-7.22,8.665,-7.201,8.549,-7.142,8.398,-7.103,8.172,-7.072,7.719,-7.064,7.643,-7.042,7.611,-7.003,7.596,-6.989,7.553,-7,7.481,-7.026,7.426,-7.066,7.386,-7.075,7.285,-7.042,7.042],[-7.167,9.714,-7.214,9.605,-7.23,9.511,-7.23,9.336,-7.204,9.193,-7.212,9.126,-7.244,9.088,-7.255,9.04,-7.236,8.952]]},{"n":"Savannah","z":5,"b":[-82.819,32.03,-80.872,34.366],"l":[[-82.208,33.664,-82.006,33.523,-81.936,33.447,-81.933,33.39,-81.909,33.349,-81.864,33.325,-81.821,33.274,-81.78,33.195,-81.699,33.127,-81.578,33.069,-81.49,32.936,-81.434,32.728,-81.377,32.607,-81.29,32.557,-81.171,32.38,-81.133,32.275,-81.135,32.182,-81.075,32.11,-80.872,32.03],[-82.819,34.366,-82.589,34.017]]},{"n":"Seine","z":4,"b":[0.439,47.578,4.708,49.494],"l":[[4.708,47.578,4.584,47.74,4.554,47.812,4.574,47.85,4.565,47.891,4.506,47.958,4.408,48.082,4.322,48.145,4.214,48.19,4.096,48.284,3.969,48.427,3.858,48.517,3.763,48.551,3.561,48.526,3.252,48.44,3.038,48.393,2.922,48.385,2.819,48.409,2.687,48.492,2.573,48.533,2.499,48.582,2.428,48.65,2.405,48.699,2.43,48.728,2.43,48.766,2.404,48.811,2.352,48.834,2.296,48.852,2.27,48.875,2.25,48.908,2.217,48.912,2.173,48.904,2.144,48.928,2.138,48.948,2.111,48.958,2.062,48.954,2.008,48.961,1.976,48.996,1.908,49.006,1.803,48.99,1.718,48.992,1.675,49.017,1.668,49.047,1.635,49.06,1.585,49.052,1.497,49.084,1.397,49.16,1.354,49.211,1.314,49.219,1.279,49.212,1.244,49.221,1.238,49.244,1.228,49.282,1.189,49.307,1.092,49.312,1.056,49.328,1.043,49.355,1.023,49.381,0.972,49.378,0.93,49.39,0.897,49.424,0.853,49.415,0.828,49.426,0.785,49.471,0.719,49.48,0.652,49.46,0.585,49.453,0.493,49.494,0.439,49.473]]},{"n":"Selenge (Selenga)","z":2.1,"b":[100.7,49.263,107.554,52.383],"l":[[100.7,49.263,101.061,49.346,101.164,49.41,101.256,49.439,101.37,49.455,101.467,49.445,101.548,49.407,101.696,49.391,101.911,49.397,102.061,49.385,102.148,49.356,102.321,49.355,102.581,49.382,102.738,49.385,102.791,49.363,102.832,49.363,102.86,49.385,102.976,49.377,103.182,49.338,103.444,49.361,103.765,49.446,104.012,49.484,104.187,49.475,104.342,49.513,104.477,49.596,104.763,49.692,105.198,49.803,105.6,49.958,106.155,50.254,106.083,50.375,106.115,50.446,106.226,50.508,106.271,50.559,106.248,50.6,106.255,50.644,106.293,50.69,106.301,50.751,106.277,50.826,106.277,50.885,106.3,50.929,106.394,50.974,106.556,51.021,106.679,51.091,106.763,51.184,106.851,51.252,106.992,51.317,107.063,51.424,107.148,51.478,107.269,51.514,107.391,51.609,107.512,51.763,107.554,51.853,107.516,51.88,107.502,51.932,107.513,52.009,107.5,52.074,107.462,52.125,107.35,52.145,107.162,52.132,107,52.104,106.862,52.059,106.753,52.055,106.672,52.092,106.615,52.145],[106.615,52.145,106.583,52.214,106.614,52.266,106.752,52.316],[106.615,52.145,106.499,52.234,106.477,52.29,106.497,52.383]]},{"n":"Semliki","z":5,"b":[29.367,-1.22,30.478,1.239],"l":[[29.838,-1.2,29.782,-1.141,29.697,-1.149,29.564,-1.204,29.476,-1.22,29.433,-1.199,29.397,-1.158,29.367,-1.098,29.377,-0.952,29.452,-0.607],[29.597,-0.134,29.638,0.113,29.614,0.234,29.537,0.31,29.534,0.386,29.604,0.464,29.661,0.559,29.705,0.671,29.834,0.773,30.047,0.864,30.183,0.973,30.24,1.103,30.321,1.185,30.478,1.239]]},{"n":"Setit","z":5,"b":[35.865,13.875,37.817,14.62],"l":[[37.817,13.875,37.7,13.91,37.629,13.97,37.535,14.117,37.354,14.372,37.257,14.454,37.185,14.446,37.133,14.406,37.099,14.334,37.064,14.289,37.025,14.272,36.941,14.281,36.812,14.315,36.679,14.308,36.542,14.258,36.427,14.25,36.334,14.282,36.22,14.279,36.085,14.242,35.977,14.258,35.895,14.326,35.865,14.425,35.895,14.62]]},{"n":"Shatt al Arab","z":3,"b":[47.468,29.962,48.547,30.968],"l":[[47.468,30.968,47.582,30.831,47.647,30.775,47.702,30.754,47.736,30.704,47.749,30.625,47.785,30.561,47.845,30.512,47.938,30.478,48.066,30.458,48.148,30.417,48.182,30.355,48.226,30.321,48.279,30.316,48.331,30.285,48.383,30.23,48.401,30.188,48.388,30.16,48.399,30.11,48.435,30.038,48.479,30.004,48.547,29.962]]},{"n":"Shiquan","z":3,"b":[79.735,31.4,81.64,32.553],"l":[[81.64,31.45,81.549,31.404,81.5,31.4,81.446,31.432,81.293,31.535,81.225,31.602,81.191,31.667,81.187,31.743,81.213,31.828,81.268,31.9,81.352,31.957,81.366,32.002,81.31,32.033,81.282,32.08,81.283,32.142,81.252,32.224,81.188,32.325,81.029,32.42,80.775,32.509,80.535,32.553,80.309,32.552,80.08,32.525,79.735,32.444]]},{"n":"Shire","z":3,"b":[34.743,-17.705,35.293,-14.401],"l":[[35.278,-14.758,35.293,-14.921,35.251,-15.057,35.107,-15.303,34.96,-15.372,34.874,-15.445,34.802,-15.544,34.758,-15.624,34.743,-15.685,34.751,-15.784,34.795,-15.991,34.889,-16.13,34.925,-16.225,34.934,-16.323,34.975,-16.392,35.049,-16.434,35.134,-16.516,35.23,-16.639,35.281,-16.808,35.291,-17.129,35.291,-17.705],[35.257,-14.504,35.236,-14.401]]},{"n":"Shishhid Gol","z":4,"b":[98,50.552,99.491,51.514],"l":[[98.809,50.627,98.985,50.557,99.063,50.552,99.121,50.61,99.207,50.751,99.322,50.809,99.403,50.898,99.478,51.048,99.491,51.149,99.444,51.2,99.466,51.329,99.424,51.349],[99.31,51.41,99.004,51.473,98.764,51.474,98.487,51.437,98.262,51.442,98,51.514]]},{"n":"Skeena","z":5,"b":[-129.626,54.228,-127.384,57.079],"l":[[-128.57,57.079,-128.284,56.907,-127.851,56.717,-127.621,56.574,-127.448,56.417,-127.384,56.322,-127.428,56.289,-127.543,56.278,-127.728,56.291,-127.858,56.274,-127.931,56.228,-127.967,56.169,-127.965,56.096,-127.939,56.03,-127.89,55.972,-127.889,55.904,-127.936,55.829,-127.891,55.779,-127.753,55.756,-127.703,55.721,-127.739,55.675,-127.72,55.594,-127.645,55.478,-127.627,55.405,-127.665,55.374,-127.685,55.33,-127.685,55.274,-127.717,55.218,-127.781,55.162,-127.903,55.126,-128.085,55.11,-128.225,55.073,-128.323,55.018,-128.374,54.967,-128.377,54.923,-128.351,54.87,-128.294,54.809,-128.317,54.725,-128.47,54.567,-129.484,54.242,-129.542,54.228,-129.578,54.228,-129.626,54.23]]},{"n":"Slave","z":2.1,"b":[-113.704,59.005,-111.384,61.253],"l":[[-111.411,59.005,-111.384,59.153,-111.408,59.213,-111.474,59.234,-111.473,59.262,-111.404,59.296,-111.394,59.346,-111.442,59.411,-111.453,59.49,-111.425,59.582,-111.45,59.683,-111.569,59.848,-111.631,59.922,-111.7,59.966,-111.799,60.005,-111.923,60.026,-112.07,60.029,-112.206,60.097,-112.33,60.232,-112.465,60.317,-112.523,60.332,-112.56,60.346,-112.551,60.362,-112.51,60.369,-112.456,60.381,-112.449,60.41,-112.461,60.438,-112.495,60.466,-112.497,60.49,-112.511,60.52,-112.547,60.543,-112.734,60.591,-112.838,60.643,-112.857,60.701,-112.898,60.739,-112.933,60.743,-112.957,60.754,-112.969,60.786,-112.978,60.801,-113.001,60.802,-113.061,60.777,-113.13,60.769,-113.186,60.784,-113.226,60.832,-113.247,60.911,-113.219,60.974,-113.142,61.02,-113.134,61.054,-113.151,61.069,-113.181,61.087,-113.192,61.12,-113.16,61.158,-113.164,61.172,-113.177,61.182,-113.211,61.188,-113.223,61.207,-113.214,61.226,-113.215,61.24,-113.252,61.249,-113.319,61.253,-113.704,61.246]]},{"n":"Snake","z":4,"b":[-119.036,42.522,-110.631,46.715],"l":[[-113.474,42.675,-113.624,42.578,-113.785,42.538,-114.008,42.522,-114.284,42.554,-114.614,42.635,-114.813,42.707,-114.882,42.771,-114.913,42.825,-114.908,42.871,-114.961,42.9,-115.072,42.913,-115.144,42.941,-115.176,42.985,-115.257,42.989,-115.386,42.953,-115.569,42.956,-115.806,42.999,-115.916,42.995,-115.974,42.978,-116.079,43.019,-116.257,43.129,-116.359,43.212,-116.386,43.269,-116.439,43.304,-116.516,43.316,-116.606,43.364,-116.707,43.448,-116.767,43.514,-116.785,43.562,-116.865,43.617,-117.006,43.679,-117.064,43.734,-117.024,43.806,-116.958,43.964,-116.944,44.038,-116.961,44.079,-116.95,44.119,-116.911,44.157,-116.912,44.199,-116.953,44.245,-117.029,44.276,-117.14,44.29,-117.197,44.344,-117.202,44.436,-117.119,44.578,-116.949,44.77,-116.856,44.906,-116.84,44.985,-116.737,45.169,-116.549,45.457,-116.477,45.641,-116.521,45.722,-116.617,45.799,-116.765,45.873,-116.869,45.958,-116.93,46.056,-116.948,46.124,-116.922,46.164,-116.943,46.232,-117.01,46.33,-117.039,46.397,-117.029,46.433,-117.07,46.449,-117.162,46.445,-117.214,46.468,-117.224,46.517,-117.284,46.582,-117.393,46.664,-117.503,46.709,-117.615,46.715,-117.702,46.698,-117.764,46.655,-117.875,46.618,-118.035,46.586,-118.205,46.577,-118.386,46.59,-118.546,46.527,-118.688,46.387,-118.827,46.291,-119.036,46.206],[-112.864,42.812,-113.022,42.686,-113.135,42.646,-113.316,42.654],[-111.188,43.339,-111.348,43.448,-111.409,43.51,-111.425,43.561,-111.586,43.635,-111.894,43.73,-112.043,43.708,-112.033,43.568,-112.072,43.446,-112.159,43.343,-112.312,43.225,-112.639,43.024],[-110.631,43.837,-110.727,43.62,-110.78,43.535,-110.822,43.512,-110.837,43.478,-110.825,43.435,-110.792,43.402,-110.738,43.378,-110.725,43.336,-110.754,43.274,-110.837,43.226,-110.983,43.19,-111.048,43.207]]},{"n":"Sungari","z":5,"b":[124.675,45.389,132.562,47.769],"l":[[124.675,45.45,124.814,45.446,124.875,45.466,124.902,45.511,124.947,45.523,125.01,45.503,125.052,45.466,125.072,45.413,125.156,45.389,125.302,45.395,125.394,45.42,125.432,45.463,125.549,45.488,125.745,45.495,125.888,45.55,125.979,45.652,126.107,45.702,126.273,45.698,126.415,45.711,126.534,45.741,126.63,45.797,126.704,45.876,126.802,45.943,126.923,45.997,127.083,45.998,127.284,45.945,127.416,45.922,127.479,45.93,127.527,45.961,127.559,46.014,127.64,46.011,127.772,45.953,127.91,45.922,128.056,45.918,128.187,45.932,128.303,45.963,128.419,45.959,128.533,45.921,128.648,45.92,128.762,45.955,128.86,45.948,128.941,45.899,129.036,45.899,129.145,45.947,129.247,46.042,129.342,46.183,129.432,46.277,129.515,46.321,129.577,46.382,129.617,46.458,129.702,46.525,129.831,46.582,129.915,46.649,129.953,46.724,130.003,46.764,130.065,46.767,130.104,46.784,130.12,46.816,130.179,46.833,130.332,46.835],[132.562,47.769,132.414,47.567,132.3,47.48,132.142,47.4,132.044,47.334,132.007,47.28,131.705,47.237,131.137,47.205,130.81,47.151,130.722,47.074,130.645,47.027,130.578,47.01,130.531,46.97,130.503,46.908,130.449,46.867,130.33,46.837]]},{"n":"Soroksari Duna","z":5,"b":[18.935,47.033,19.098,47.481],"l":[[19.06,47.481,19.091,47.439,19.098,47.418,19.094,47.394,19.052,47.354,18.974,47.275,18.949,47.199,18.973,47.145,18.976,47.101,18.935,47.033]]},{"n":"South Saskatchewan","z":5,"b":[-116.422,49.904,-105.065,53.24],"l":[[-106.853,51.263,-107.015,51.388,-107.067,51.464,-107.062,51.533,-106.993,51.608,-106.859,51.687,-106.776,51.748,-106.744,51.792,-106.738,51.853,-106.758,51.932,-106.755,51.976,-106.727,51.986,-106.713,52.012,-106.713,52.055,-106.574,52.198,-106.294,52.441,-106.148,52.616,-106.135,52.722,-106.103,52.808,-106.054,52.873,-105.903,52.932,-105.651,52.983,-105.497,53.046,-105.442,53.122,-105.373,53.153,-105.289,53.139,-105.201,53.159,-105.065,53.24],[-116.422,51.667,-116.357,51.651,-116.319,51.612,-116.271,51.51,-115.86,51.273,-115.658,51.194,-115.53,51.194,-115.414,51.159,-115.312,51.09,-115.1,51.103,-114.781,51.198,-114.48,51.196,-114.197,51.094,-114.041,50.999,-114.01,50.909,-113.94,50.85,-113.832,50.825,-113.581,50.799,-113.447,50.813,-113.31,50.848,-113.19,50.838,-113.088,50.783,-113.003,50.767,-112.933,50.788,-112.809,50.789,-112.632,50.77,-112.524,50.729,-112.485,50.665,-112.444,50.631,-112.4,50.626,-112.357,50.574,-112.316,50.475,-112.263,50.422,-112.197,50.415,-112.168,50.393,-112.173,50.355,-112.151,50.303,-112.101,50.238,-112.031,50.205,-111.941,50.204,-111.825,50.176,-111.684,50.121,-111.609,50.065,-111.6,50.01,-111.624,49.968,-111.643,49.937,-111.607,49.904,-111.523,49.909,-111.422,49.934,-111.342,50.001,-111.264,50.041,-111.188,50.055,-110.775,50.046,-110.66,50.059,-110.681,50.095,-110.664,50.12,-110.609,50.134,-110.58,50.155,-110.577,50.185,-110.594,50.211,-110.632,50.235,-110.632,50.299,-110.594,50.404,-110.529,50.448,-110.436,50.433,-110.391,50.463,-110.393,50.537,-110.344,50.59,-110.245,50.62,-110.158,50.661,-110.084,50.714,-110.054,50.754,-110.068,50.781,-110.054,50.81,-110.013,50.838,-109.998,50.863,-110.011,50.884,-109.977,50.909,-109.897,50.938,-109.634,50.97,-109.188,51.004,-108.925,51.002,-108.846,50.965,-108.73,50.939,-108.576,50.925,-108.441,50.858,-108.326,50.737,-108.203,50.672,-108.006,50.658]]},{"n":"Sprague","z":4.7,"b":[-121.921,42.4,-120.882,42.583],"l":[[-120.882,42.4,-121.048,42.441,-121.437,42.482,-121.604,42.525,-121.679,42.583,-121.776,42.579,-121.889,42.512,-121.921,42.475]]},{"n":"St. Clair","z":3,"b":[-82.645,42.558,-82.417,43.017],"l":[[-82.417,43.017,-82.488,42.74,-82.545,42.625,-82.645,42.558]]},{"n":"St. Lawrence","z":2.1,"b":[-75.792,44.497,-74.709,45.004],"l":[[-74.709,45.004,-74.762,44.999,-74.857,45.004,-74.996,44.97,-75.179,44.899,-75.401,44.772,-75.792,44.497]]},{"n":"Stikine","z":4.7,"b":[-132.358,56.626,-127.596,58.148],"l":[[-128.13,57.292,-127.939,57.37,-127.833,57.394,-127.705,57.389,-127.601,57.432,-127.596,57.49,-127.656,57.576,-127.664,57.639,-127.62,57.678,-127.723,57.706,-127.974,57.722,-128.128,57.762,-128.186,57.826,-128.286,57.89,-128.428,57.956,-128.615,57.976,-128.846,57.952,-129.04,57.949,-129.196,57.966,-129.332,57.96,-129.449,57.931,-129.733,57.975,-130.185,58.093,-130.475,58.148,-130.605,58.14,-130.76,58.105,-130.939,58.041,-131.055,57.988,-131.108,57.945,-131.19,57.909,-131.302,57.882,-131.418,57.821,-131.598,57.679,-131.728,57.553,-131.767,57.467,-131.757,57.379,-131.775,57.333,-131.821,57.329,-131.841,57.306,-131.833,57.263,-131.794,57.214,-131.725,57.159,-131.71,57.116,-131.751,57.086,-131.775,56.978,-131.781,56.792,-131.801,56.753,-131.834,56.715,-131.881,56.703,-132.075,56.711,-132.206,56.696,-132.311,56.639,-132.358,56.626]]},{"n":"Suez Canal","z":5,"b":[32.303,29.974,32.566,31.256],"l":[[32.561,30.182,32.566,29.974],[32.36,30.424,32.303,30.593,32.334,30.689,32.337,30.859,32.324,31.256]]},{"n":"Sukhona","z":4,"b":[38.656,59.302,46.34,60.76],"l":[[39.798,59.525,40.099,59.476,40.215,59.447,40.248,59.421,40.262,59.38,40.26,59.323,40.35,59.302,40.532,59.318,40.684,59.352,40.808,59.404,40.917,59.418,41.011,59.396,41.155,59.391,41.349,59.403,41.558,59.43,41.784,59.473,41.9,59.512,41.906,59.546,41.954,59.584,42.044,59.628,42.169,59.657,42.328,59.67,42.462,59.7,42.57,59.746,42.653,59.816,42.711,59.91,42.853,59.982,43.191,60.057,43.731,60.19,44.032,60.292,44.276,60.407,44.492,60.474,44.679,60.493,44.835,60.526,44.962,60.573,45.156,60.6,45.418,60.605,45.606,60.632,45.72,60.68,45.831,60.697,45.939,60.684,46.051,60.699,46.166,60.742,46.252,60.76,46.34,60.749],[39.053,59.868,38.928,59.939,38.656,59.93]]},{"n":"Susitna","z":5,"b":[-150.57,61.307,-146.684,62.859],"l":[[-146.684,62.505,-146.829,62.576,-146.879,62.615,-146.884,62.646,-146.959,62.676,-147.103,62.705,-147.228,62.702,-147.335,62.668,-147.493,62.689,-147.705,62.765,-147.89,62.812,-148.125,62.838,-148.51,62.849,-148.68,62.839,-148.763,62.811,-148.867,62.813,-148.993,62.844,-149.188,62.859,-149.453,62.855,-149.694,62.803,-149.912,62.702,-150.044,62.619,-150.113,62.519,-150.159,62.434,-150.162,62.355,-150.136,62.255,-150.138,62.193,-150.168,62.169,-150.161,62.14,-150.114,62.106,-150.094,62.049,-150.099,61.968,-150.172,61.843,-150.313,61.676,-150.425,61.577,-150.509,61.547,-150.559,61.488,-150.57,61.364,-150.567,61.307]]},{"n":"Sutlej","z":5,"b":[71.014,29.326,81.376,31.837],"l":[[81.376,30.758,81.294,30.792],[81.199,30.852,80.874,31.054,80.731,31.121,80.628,31.121,80.534,31.066,80.474,31.053,80.418,31.064,80.296,31.125,80.107,31.235,79.98,31.336,79.917,31.426,79.795,31.481,79.616,31.5,79.456,31.55,79.315,31.632,79.171,31.67,79.023,31.664,78.922,31.697,78.869,31.769,78.811,31.816,78.749,31.837,78.652,31.804,78.521,31.718,78.452,31.657,78.445,31.619,78.405,31.6,78.33,31.6,78.284,31.581,78.269,31.541,78.153,31.532,77.937,31.553,77.757,31.512,77.611,31.407,77.495,31.356,77.411,31.358,77.353,31.338,77.323,31.297,77.27,31.267,77.196,31.25,77.09,31.273,76.952,31.337,76.883,31.38,76.884,31.403,76.854,31.413,76.765,31.409],[76.435,31.422,76.384,31.407,76.37,31.387,76.375,31.355,76.535,31.144,76.567,31.053,76.526,31.003,76.36,30.989,76.069,31.011,75.791,31.013,75.527,30.994,75.305,31.035,75.124,31.134,74.984,31.177,74.886,31.165,74.728,31.092,74.51,30.96,74.38,30.893,74.339,30.894,74.216,30.769,74.009,30.52,73.806,30.363,73.505,30.268,73.382,30.229,73.325,30.194,73.295,30.151,73.268,30.139,73.245,30.16,73.158,30.124,73.008,30.03,72.858,29.96,72.71,29.915,72.631,29.876,72.623,29.843,72.554,29.829,72.426,29.835,72.309,29.794,72.202,29.704,72.153,29.64,72.162,29.599,72.121,29.557,72.03,29.513,71.905,29.487,71.747,29.478,71.65,29.452,71.615,29.409,71.565,29.382,71.5,29.372,71.418,29.382,71.32,29.413,71.209,29.413,71.014,29.326]]},{"n":"Severnaya Dvina","z":4,"b":[40.408,60.749,46.617,64.755],"l":[[46.34,60.749,46.456,60.843,46.512,60.91,46.547,60.982,46.565,61.044,46.565,61.096,46.58,61.13,46.61,61.145,46.617,61.186,46.591,61.287,46.255,61.472,45.945,61.595,45.551,61.717,45.291,61.84,45.167,61.964,45.091,62.061,45.063,62.13,44.975,62.206,44.826,62.29,44.562,62.347,44.184,62.377,43.907,62.442,43.732,62.543,43.404,62.67,42.924,62.824,42.657,62.934,42.605,63.002,42.487,63.045,42.212,63.073,42.077,63.141,42.041,63.195,42.057,63.258,42.016,63.329,41.916,63.408,41.872,63.483,41.885,63.553,41.806,63.605,41.634,63.639,41.515,63.681,41.448,63.732,41.47,63.785,41.58,63.84,41.633,63.88,41.628,63.904,41.658,63.944,41.724,63.999,41.752,64.052,41.742,64.102,41.777,64.137,41.857,64.157,41.884,64.192,41.856,64.244,41.654,64.295,41.277,64.345,40.949,64.416,40.53,64.555,40.45,64.63,40.408,64.755]]},{"n":"Svir’","z":4.7,"b":[32.816,60.477,34.846,61.027],"l":[[32.816,60.482,32.863,60.477,32.996,60.511,33.14,60.576,33.29,60.68,33.438,60.738,33.583,60.752,33.713,60.811,33.828,60.915,33.918,60.964,34.017,60.958,34.086,60.926,34.293,60.943,34.846,61.027]]},{"n":"Syr Darya","z":4.7,"b":[60.878,40.225,69.827,46.137],"l":[[69.827,40.273,69.684,40.302,69.563,40.286,69.414,40.234,69.299,40.225,69.218,40.259,69.152,40.338,69.101,40.461,69.087,40.539,69.094,40.577,69.082,40.599,69.02,40.658,68.817,40.797,68.702,40.898,68.675,40.962,68.61,41.022,68.456,41.107],[67.991,41.255,68.008,41.285,68.069,41.336,68.087,41.463,68.051,41.658,68.067,41.834,68.134,41.991,68.186,42.081,68.201,42.099,68.207,42.132,68.192,42.15,68.188,42.182,68.255,42.237,68.285,42.302,68.277,42.377,68.253,42.443,68.211,42.501,68.208,42.554,68.243,42.603,68.255,42.663,68.245,42.735,68.229,42.769,68.224,42.794,68.2,42.825,68.135,42.866,68.119,42.908,68.129,42.947,68.089,42.991,67.929,43.031,67.838,43.121,67.818,43.26,67.789,43.341,67.751,43.365,67.72,43.358,67.693,43.331,67.66,43.343,67.646,43.389,67.605,43.418,67.548,43.421,67.514,43.459,67.504,43.534,67.444,43.631,67.333,43.751,67.259,43.811,67.222,43.812,67.195,43.839,67.176,43.894,67.107,43.949,66.988,44.003,66.902,44.009,66.85,43.967,66.771,43.959,66.665,43.985,66.603,43.989,66.561,44.009,66.533,44.102,66.494,44.152,66.442,44.158,66.418,44.181,66.422,44.218,66.382,44.268,66.299,44.33,66.263,44.371,66.188,44.425,66.019,44.457,65.975,44.509,65.957,44.54,65.916,44.584,65.833,44.64,65.794,44.687,65.791,44.723,65.738,44.748,65.616,44.756,65.493,44.816,65.37,44.927,65.23,44.993,65.072,45.014,64.964,45.012,64.904,44.989,64.784,45.002,64.602,45.052,64.466,45.113,64.375,45.187,64.334,45.234,64.318,45.27,64.261,45.281,64.221,45.322,64.198,45.394,64.112,45.446,63.964,45.48,63.883,45.511,63.869,45.539,63.877,45.567,63.907,45.595,63.876,45.632,63.784,45.68,63.752,45.713,63.7,45.718,63.336,45.635,63.124,45.633,63.005,45.7,62.9,45.719,62.809,45.691,62.733,45.691,62.673,45.72,62.634,45.759,62.616,45.809,62.565,45.813,62.483,45.772,62.376,45.758,62.246,45.772,62.127,45.751,62.019,45.696,61.897,45.705,61.76,45.776,61.703,45.833,61.725,45.875,61.763,45.903,61.817,45.918,61.823,45.949,61.76,46.021],[61.562,46.11,61.264,46.051,61.093,46.058,60.878,46.137]]},{"n":"São Francisco","z":4,"b":[-45.262,-18.204,-36.412,-8.559],"l":[[-45.262,-18.204,-45.242,-18.126,-45.221,-18.103,-45.19,-18.109,-45.16,-18.036,-45.13,-17.886,-45.086,-17.797,-45.028,-17.77,-45.008,-17.717,-45.027,-17.637,-45.007,-17.514,-44.95,-17.347,-44.888,-17.219,-44.823,-17.13,-44.87,-16.94,-45.028,-16.649,-45.089,-16.483,-45.052,-16.444,-45.038,-16.316,-45.049,-15.991,-44.922,-15.959,-44.861,-15.917,-44.823,-15.854,-44.744,-15.795,-44.563,-15.711,-44.271,-15.458,-44.154,-15.311,-44.116,-15.185,-44.054,-15.057,-43.971,-14.926,-43.906,-14.752,-43.861,-14.534,-43.8,-14.37,-43.724,-14.258,-43.654,-14.062,-43.591,-13.781,-43.532,-13.613,-43.479,-13.556,-43.454,-13.408,-43.457,-13.17,-43.443,-13.008,-43.411,-12.923,-43.342,-12.82,-43.238,-12.698,-43.209,-12.559,-43.256,-12.403,-43.265,-12.291,-43.235,-12.222,-43.254,-12.13,-43.323,-12.013,-43.348,-11.931,-43.329,-11.885,-43.336,-11.805,-43.37,-11.691,-43.362,-11.614,-43.314,-11.574,-43.255,-11.451,-43.15,-11.143],[-40.818,-9.438,-40.671,-9.474,-40.552,-9.457,-40.413,-9.397,-40.32,-9.293,-40.273,-9.145,-40.212,-9.102,-40.148,-9.095,-40.078,-9.088,-40.005,-9.058,-39.949,-9.022,-39.909,-8.98,-39.888,-8.924,-39.885,-8.853,-39.835,-8.809,-39.738,-8.791,-39.688,-8.749,-39.683,-8.683,-39.601,-8.62,-39.442,-8.559,-39.327,-8.566,-39.257,-8.64,-39.093,-8.718,-38.835,-8.8,-38.705,-8.88,-38.668,-8.896,-38.619,-8.896,-38.584,-8.866,-38.544,-8.831,-38.505,-8.835,-38.484,-8.878,-38.482,-8.96,-38.433,-9.004,-38.336,-9.01,-38.294,-9.043,-38.308,-9.102,-38.283,-9.204,-38.221,-9.348,-37.938,-9.538,-37.183,-9.893,-37.019,-9.957,-36.956,-10.014,-36.94,-10.084,-36.88,-10.154,-36.777,-10.224,-36.699,-10.257,-36.647,-10.255,-36.607,-10.293,-36.58,-10.371,-36.538,-10.411,-36.483,-10.411,-36.443,-10.432,-36.412,-10.49]]},{"n":"Sénégal","z":4,"b":[-16.535,13.778,-10.892,16.679],"l":[[-10.892,13.778,-10.914,13.789,-10.952,13.801,-11.011,13.856,-11.062,13.972,-11.129,14.045,-11.213,14.074,-11.246,14.105,-11.227,14.138,-11.23,14.192,-11.253,14.267,-11.3,14.343,-11.371,14.418,-11.477,14.49,-11.618,14.557,-11.74,14.598,-11.843,14.612,-11.936,14.647,-12.061,14.729,-12.303,14.817,-12.409,14.889,-12.46,14.975,-12.544,15.039,-12.66,15.082,-12.735,15.131,-12.77,15.187,-12.813,15.224,-12.859,15.243,-12.863,15.262,-12.852,15.29,-12.863,15.34,-12.931,15.453,-12.994,15.505,-13.049,15.497,-13.079,15.51,-13.098,15.535,-13.105,15.572,-13.142,15.603,-13.206,15.617,-13.258,15.7,-13.297,15.854,-13.348,15.973,-13.41,16.059,-13.454,16.091,-13.487,16.097,-13.498,16.11,-13.507,16.135,-13.556,16.144,-13.624,16.118,-13.685,16.127,-13.715,16.169,-13.757,16.173,-13.81,16.138,-13.868,16.148,-13.933,16.203,-13.968,16.257,-13.975,16.311,-14.086,16.419,-14.3,16.58,-14.534,16.656,-14.787,16.646,-14.929,16.654,-14.96,16.679,-14.991,16.677,-15.022,16.647,-15.055,16.641,-15.091,16.657,-15.113,16.645,-15.121,16.604,-15.211,16.583,-15.38,16.582,-15.517,16.557,-15.621,16.507,-15.768,16.485,-15.959,16.492,-16.074,16.51,-16.113,16.54,-16.168,16.547,-16.239,16.531,-16.302,16.451,-16.358,16.307,-16.404,16.225,-16.441,16.205,-16.48,16.097,-16.502,15.917,-16.535,15.838]]},{"n":"Tajo","z":4.7,"b":[-7.118,39.682,-1.906,40.811],"l":[[-5.996,39.852,-6.326,39.764,-6.533,39.746,-6.726,39.77,-6.896,39.757,-7.118,39.682],[-2.733,40.528,-2.961,40.275,-3.1,40.156,-3.226,40.087,-3.381,40.048,-3.565,40.038,-3.739,39.995,-3.901,39.92,-4.059,39.881,-4.213,39.879,-4.318,39.839,-4.428,39.867,-4.628,39.948,-4.757,39.982,-4.816,39.968,-4.889,39.921,-4.978,39.84,-5.071,39.803,-5.217,39.812],[-1.906,40.601,-2.06,40.745,-2.144,40.797,-2.242,40.811,-2.577,40.724]]},{"n":"Tanana","z":5,"b":[-152.048,62.027,-141.395,65.165],"l":[[-142.022,62.027,-142.071,62.066,-142.087,62.109,-142.085,62.171,-142.051,62.217,-141.951,62.264,-141.817,62.306,-141.743,62.355,-141.685,62.424,-141.602,62.48,-141.494,62.523,-141.425,62.564,-141.395,62.604,-141.413,62.673,-141.479,62.771,-141.552,62.829,-141.633,62.847,-141.656,62.872,-141.622,62.905,-141.738,62.983,-142.004,63.105,-142.192,63.165,-142.302,63.165,-142.42,63.186,-142.544,63.227,-142.632,63.279,-142.686,63.34,-142.744,63.368,-142.807,63.362,-142.879,63.378,-142.961,63.417,-143.038,63.43,-143.149,63.411,-143.617,63.404,-143.789,63.42,-143.822,63.457,-143.827,63.499,-143.803,63.544,-143.827,63.593,-143.899,63.645,-144.101,63.688,-144.433,63.722,-144.678,63.791,-144.835,63.894,-144.895,63.958,-144.887,64,-144.915,64.034,-145.032,64.068,-145.158,64.084,-145.294,64.083,-145.439,64.108,-145.594,64.16,-145.726,64.18,-145.834,64.168,-146.029,64.192,-146.309,64.254,-146.505,64.278,-146.672,64.256,-146.934,64.383,-147.021,64.456,-147.021,64.516,-147.08,64.582,-147.197,64.653,-147.322,64.711,-147.456,64.754,-147.626,64.782,-147.832,64.796,-147.993,64.783,-148.163,64.726,-148.566,64.62,-148.801,64.583,-149.116,64.582,-149.138,64.594,-149.14,64.647,-149.123,64.741,-149.131,64.783,-149.156,64.804,-149.156,64.824,-149.129,64.853,-149.139,64.878,-149.187,64.901,-149.307,64.911,-149.499,64.909,-149.676,64.877,-149.839,64.814,-150.082,64.832,-150.404,64.93,-150.674,64.977,-150.89,64.973,-151.09,64.947,-151.273,64.9,-151.415,64.905,-151.517,64.962,-151.553,64.995,-151.544,65.025,-151.565,65.034,-151.623,65.016,-151.675,65.029,-151.697,65.085,-151.793,65.127,-152.048,65.165]]},{"n":"Tapajós","z":4.7,"b":[-58.659,-10.975,-54.639,-2.385],"l":[[-58.659,-10.975,-58.591,-10.664,-58.513,-10.521,-58.404,-10.442,-58.316,-10.277,-58.248,-10.024,-58.228,-9.802,-58.257,-9.609,-58.352,-9.475,-58.512,-9.4,-58.609,-9.303,-58.644,-9.182,-58.64,-9.069,-58.599,-8.963,-58.515,-8.858,-58.387,-8.754,-58.358,-8.691,-58.428,-8.668,-58.427,-8.523,-58.353,-8.256,-58.329,-8.051,-58.354,-7.906,-58.33,-7.78,-58.259,-7.673,-58.224,-7.575,-58.224,-7.485,-58.202,-7.415,-58.136,-7.34,-58.146,-7.263,-58.228,-7.142,-58.386,-6.949,-58.455,-6.794,-58.437,-6.676,-58.375,-6.57,-58.269,-6.478,-58.144,-6.413,-58.002,-6.378,-57.872,-6.318,-57.754,-6.235,-57.681,-6.127,-57.652,-5.996,-57.492,-5.82,-57.2,-5.598,-57.042,-5.435,-57.019,-5.329,-56.97,-5.247,-56.894,-5.188,-56.821,-5.059,-56.75,-4.862,-56.686,-4.745,-56.63,-4.708,-56.56,-4.7,-56.475,-4.722,-56.397,-4.695,-56.326,-4.62,-56.283,-4.533,-56.268,-4.435,-56.155,-4.333,-56.008,-4.26,-55.945,-4.229,-55.786,-4.128,-55.678,-4.03,-55.567,-3.892,-55.394,-3.627,-55.233,-3.279,-55.145,-2.982,-55.077,-2.62,-54.992,-2.423,-54.89,-2.392,-54.798,-2.385,-54.639,-2.419]]},{"n":"Tarim","z":5,"b":[80.888,40.467,86.753,41.826],"l":[[80.888,40.467,81.026,40.519,81.129,40.535,81.242,40.533,81.343,40.574,81.429,40.659,81.546,40.707,81.693,40.719,81.81,40.753,81.896,40.808,82.121,40.886,82.486,40.987,82.749,41.004,82.912,40.939,83.082,40.928,83.26,40.971,83.455,40.998,83.666,41.009,83.793,41.001,83.835,40.975,83.896,40.998,83.975,41.07,84.091,41.131,84.246,41.182,84.477,41.195,84.784,41.169,84.984,41.129,85.077,41.077,85.218,41.028,85.407,40.984,85.617,40.993,85.848,41.054,86.038,41.071,86.185,41.042,86.335,41.037,86.486,41.058,86.505,41.1,86.392,41.163,86.345,41.206,86.372,41.243,86.382,41.278,86.368,41.285,86.334,41.275,86.266,41.297,86.163,41.352,86.061,41.382,85.959,41.388,85.877,41.426,85.814,41.496,85.786,41.571,85.795,41.651,85.829,41.707,85.888,41.738,85.973,41.753,86.085,41.752,86.165,41.772,86.212,41.815,86.305,41.819,86.443,41.786,86.573,41.783,86.753,41.826]]},{"n":"Taritatu","z":5,"b":[138.702,-3.711,140.295,-3.191],"l":[[140.295,-3.711,139.942,-3.629,139.789,-3.552,139.684,-3.528,139.575,-3.531,139.522,-3.493,139.487,-3.471,139.416,-3.481,139.384,-3.473,139.335,-3.439,139.21,-3.443,139.122,-3.403,139.07,-3.318,138.958,-3.254,138.755,-3.204,138.702,-3.191]]},{"n":"Taz","z":5,"b":[78.59,62.637,85.617,67.578],"l":[[84.22,62.637,84.445,62.811,84.582,62.886,84.833,62.966,85.245,63.077,85.506,63.189,85.617,63.303,85.609,63.357,85.482,63.352,85.389,63.391,85.329,63.475,85.184,63.537,84.955,63.576,84.806,63.62,84.738,63.667,84.636,63.696,84.5,63.707,84.323,63.682,84.105,63.623,83.915,63.588,83.753,63.58,83.706,63.595,83.774,63.634,83.765,63.681,83.681,63.736,83.602,63.758,83.529,63.746,83.496,63.751,83.502,63.771,83.459,63.807,83.323,63.884,83.198,63.924,83.109,63.926,83.015,63.902,82.985,63.875,83.018,63.844,83,63.81,82.931,63.775,82.898,63.766,82.902,63.785,82.862,63.784,82.778,63.763,82.686,63.763,82.585,63.784,82.523,63.784,82.502,63.763,82.459,63.767,82.397,63.797,82.38,63.82,82.409,63.835,82.349,63.866,82.2,63.912,82.077,63.967,81.98,64.03,81.885,64.114,81.791,64.22,81.754,64.288,81.773,64.317,81.75,64.35,81.683,64.387,81.669,64.41,81.706,64.42,81.713,64.437,81.687,64.462,81.439,64.543,81.388,64.571,81.441,64.581,81.488,64.606,81.528,64.646,81.537,64.674,81.516,64.691,81.522,64.699,81.553,64.699,81.535,64.732,81.47,64.796,81.468,64.846,81.53,64.881,81.528,64.909,81.463,64.929,81.43,64.979,81.43,65.057,81.528,65.112,81.724,65.144,81.89,65.192,82.025,65.258,82.101,65.309,82.116,65.346,82.088,65.372,82.017,65.388,81.99,65.42,82.005,65.47,82.042,65.488,82.099,65.475,82.12,65.478,82.104,65.497,82.123,65.524,82.176,65.56,82.399,65.651,82.422,65.703,82.337,65.756,82.303,65.815,82.32,65.88,82.279,65.941,82.179,65.999,82.126,66.04,82.119,66.064,82.148,66.109,82.214,66.176,82.212,66.213,82.142,66.219,82.096,66.245,82.073,66.293,82.121,66.336,82.241,66.373,82.272,66.406,82.214,66.433,82.216,66.466,82.277,66.506,82.294,66.538,82.268,66.562,82.262,66.602,82.276,66.656,82.156,66.756,81.9,66.9,81.527,67.011,81.037,67.087,80.725,67.12,80.592,67.11,80.535,67.091,80.552,67.063,80.49,67.073,80.348,67.118,80.252,67.135,80.176,67.117,79.635,67.213,79.451,67.259,79.444,67.286,79.409,67.296,79.347,67.29,79.131,67.36,78.59,67.578]]},{"n":"Tagus","z":4.7,"b":[-8.792,39.078,-7.118,39.682],"l":[[-7.118,39.682,-7.454,39.681,-7.653,39.634,-7.828,39.541,-7.982,39.492,-8.185,39.485,-8.376,39.468,-8.521,39.371,-8.661,39.249,-8.742,39.115,-8.792,39.078]]},{"n":"Tennessee","z":4.7,"b":[-88.472,34.403,-83.969,37.088],"l":[[-87.7,34.773,-87.814,34.763,-87.965,34.844,-88.084,34.933,-88.191,35.012,-88.306,35.126,-88.311,35.185,-88.282,35.239,-88.218,35.287,-88.188,35.329,-88.19,35.366,-88.139,35.391,-88.034,35.404,-87.994,35.418,-88.018,35.434,-88.015,35.464,-87.986,35.511,-87.987,35.579,-88.02,35.67,-88.01,35.767,-87.958,35.868,-87.943,35.925,-87.975,35.945],[-83.969,35.87,-84.229,35.767,-84.344,35.736,-84.4,35.743,-84.457,35.775,-84.542,35.863,-84.598,35.803,-84.63,35.789,-84.656,35.803,-84.701,35.749,-84.766,35.629,-84.82,35.566,-84.862,35.559,-84.89,35.528,-84.902,35.472,-84.924,35.45,-84.957,35.461,-85.013,35.391,-85.092,35.242,-85.182,35.132,-85.285,35.062,-85.347,35.05,-85.367,35.095,-85.392,35.096,-85.424,35.055,-85.46,35.05,-85.501,35.081,-85.559,35.08,-85.635,35.049,-85.68,35.01,-85.694,34.963,-85.839,34.803,-86.115,34.528,-86.294,34.403,-86.378,34.427,-86.462,34.478,-86.546,34.557,-86.666,34.591,-86.899,34.575],[-88.283,37.022,-88.472,37.088]]},{"n":"Teslin","z":3,"b":[-134.913,59.072,-131.208,61.57],"l":[[-131.208,59.087,-131.393,59.072,-131.513,59.084,-131.689,59.135,-131.792,59.141,-131.861,59.178,-131.93,59.248,-131.997,59.364,-132.095,59.607],[-133.245,60.449,-134.288,61.089,-134.647,61.352,-134.667,61.45,-134.737,61.517,-134.913,61.57]]},{"n":"Thames","z":5,"b":[-2.022,51.392,0.425,51.763],"l":[[-2.022,51.686,-1.939,51.648,-1.824,51.652,-1.562,51.7,-1.434,51.705,-1.369,51.725,-1.324,51.763,-1.282,51.759,-1.242,51.713,-1.239,51.682,-1.22,51.65,-1.17,51.628,-1.124,51.587,-1.11,51.525,-1.069,51.485,-0.999,51.465,-0.944,51.479,-0.903,51.527,-0.841,51.554,-0.76,51.56,-0.715,51.549,-0.706,51.521,-0.677,51.504,-0.604,51.494,-0.503,51.416,-0.433,51.392,-0.36,51.398,-0.32,51.419,-0.314,51.455,-0.245,51.48,-0.049,51.501,0.385,51.453,0.425,51.466]]},{"n":"Thelon","z":4.7,"b":[-105.942,62.356,-93.656,64.761],"l":[[-105.942,62.356,-105.664,62.41,-105.305,62.439,-104.772,62.461,-104.586,62.486,-104.544,62.5,-104.55,62.517,-104.598,62.551,-104.676,62.577,-104.734,62.589,-104.757,62.605,-104.735,62.622,-104.743,62.659,-104.763,62.682,-104.772,62.709,-104.744,62.729,-104.761,62.762,-104.819,62.808,-104.801,62.864,-104.769,62.906,-104.767,62.937,-104.817,62.948,-104.846,62.983,-104.834,63.071],[-104.706,63.27,-104.681,63.352,-104.612,63.44,-104.489,63.562,-104.452,63.63,-104.501,63.644,-104.468,63.691,-104.354,63.772,-104.214,63.836,-104.05,63.884,-103.901,63.974,-103.766,64.108,-103.696,64.193,-103.692,64.227,-103.532,64.269,-103.217,64.317,-102.966,64.328,-102.779,64.302,-102.676,64.263,-102.657,64.211,-102.626,64.178,-102.585,64.165,-102.409,64.184,-102.098,64.235,-101.91,64.298,-101.811,64.411],[-94.225,64.076,-93.766,63.994,-93.656,63.973],[-101.536,64.493,-100.795,64.604],[-97.103,64.761,-96.728,64.697,-96.58,64.655,-96.536,64.614,-96.504,64.554,-96.484,64.474,-96.423,64.401,-96.273,64.301]]},{"n":"Tigris","z":4,"b":[42.377,30.999,47.441,37.062],"l":[[42.377,37.062,42.434,36.99,42.466,36.967,42.489,36.96,42.509,36.941,42.513,36.876,42.559,36.832,42.713,36.785,42.759,36.755,42.774,36.733,42.761,36.719,42.858,36.708,42.895,36.691,42.901,36.662,42.886,36.646,42.85,36.644,42.811,36.609,42.771,36.54,42.844,36.478,43.031,36.425,43.169,36.334,43.258,36.205,43.317,36.076,43.346,35.946,43.343,35.858,43.308,35.812,43.297,35.765,43.312,35.718,43.299,35.682,43.261,35.657,43.248,35.586,43.262,35.47,43.348,35.316,43.505,35.126,43.568,35.006,43.538,34.957,43.584,34.832,43.708,34.63,43.785,34.453,43.815,34.3,43.854,34.194,43.904,34.135,43.96,34.098,44.055,34.075,44.232,34.05,44.329,34.019,44.403,33.974,44.427,33.909,44.402,33.825,44.398,33.769,44.417,33.739,44.4,33.697,44.348,33.641,44.318,33.586,44.31,33.531,44.324,33.451,44.361,33.347,44.397,33.293,44.431,33.291,44.48,33.235,44.544,33.127,44.675,33.024,44.871,32.925,44.981,32.887,45.027,32.891,45.041,32.879,45.041,32.856,45.044,32.832,45.08,32.803,45.14,32.785,45.225,32.778,45.26,32.752,45.259,32.714,45.298,32.663,45.384,32.604,45.522,32.556,45.809,32.502,46.03,32.619,46.137,32.632,46.201,32.588,46.344,32.542,46.563,32.494,46.692,32.443,46.73,32.386,46.735,32.321,46.706,32.248,46.699,32.191,46.713,32.149,46.755,32.112,46.827,32.079,46.888,32.014,46.94,31.917,47.008,31.868,47.093,31.865,47.139,31.826,47.146,31.749,47.221,31.627,47.363,31.459,47.434,31.286,47.441,30.999]]},{"n":"Tisa","z":5,"b":[20.061,45.126,20.308,46.261],"l":[[20.179,46.261,20.087,46.166,20.066,46.138,20.064,46.117,20.079,46.076,20.074,46.049,20.061,46.029,20.08,46.001,20.104,45.969,20.124,45.922,20.122,45.89,20.098,45.873,20.105,45.834,20.14,45.778,20.136,45.742,20.11,45.724,20.085,45.689,20.07,45.632,20.105,45.563,20.188,45.483,20.223,45.431,20.209,45.407,20.231,45.349,20.289,45.256,20.308,45.192,20.282,45.126]]},{"n":"Tisza","z":5,"b":[20.066,46.261,24.338,48.415],"l":[[21.346,48.026,21.182,48.006,21.103,47.944,21.057,47.835,20.871,47.69,20.546,47.51,20.4,47.382,20.434,47.306,20.426,47.27,20.377,47.272,20.306,47.242,20.214,47.178,20.175,47.119,20.189,47.066,20.218,47.038,20.192,47.01,20.155,46.98,20.144,46.939,20.125,46.912,20.066,46.874,20.08,46.811,20.166,46.724,20.192,46.653,20.157,46.598,20.163,46.5,20.207,46.36,20.217,46.283,20.179,46.261],[24.256,48.322,24.338,48.294,24.333,48.234,24.234,48.083,24.17,47.955,24.002,47.936,23.709,47.983,23.477,48.049,23.307,48.136,23.146,48.155,22.996,48.106,22.885,48.091,22.815,48.111,22.702,48.107,22.545,48.078,22.42,48.095,22.325,48.159,22.276,48.244,22.273,48.35,22.254,48.407,22.219,48.415,22.133,48.374,21.997,48.284,21.869,48.222,21.75,48.186,21.637,48.17,21.53,48.175,21.465,48.146,21.443,48.082,21.409,48.045,21.339,48.026]]},{"n":"Tobol","z":4.7,"b":[60.91,51.442,68.218,58.168],"l":[[60.91,51.669,61.015,51.492,61.363,51.442,61.516,51.454,61.589,51.513,61.606,51.636,61.565,51.822,61.555,51.949,61.575,52.016,61.56,52.086,61.509,52.158,61.547,52.253,61.674,52.37,61.735,52.442,61.777,52.466,61.835,52.483,62.047,52.487,62.298,52.529,62.589,52.609,62.767,52.696,62.834,52.79,63.024,52.891,63.337,52.999,63.587,53.146,63.864,53.423,63.876,53.567,63.846,53.671,63.778,53.782,63.752,53.887,63.767,53.984,63.798,54.041,63.844,54.056,63.858,54.079,63.859,54.118,63.877,54.153,63.964,54.203,64.007,54.252,64.063,54.303,64.199,54.347,64.59,54.402,64.809,54.48,64.858,54.582,64.916,54.652,64.983,54.689,65.001,54.718,64.971,54.739,64.974,54.773,65.009,54.821,65.008,54.863,64.972,54.899,64.958,54.936,64.966,54.973,65.044,55.064,65.193,55.21,65.255,55.312,65.23,55.37,65.264,55.411,65.357,55.437,65.448,55.501,65.58,55.654,65.611,55.788,65.641,55.841,65.682,55.86,65.695,55.899,65.68,55.96,65.758,56.027,65.928,56.102,65.973,56.152,65.965,56.191,65.997,56.251,66.185,56.308,66.256,56.362,66.26,56.384,66.278,56.425,66.337,56.481,66.349,56.531,66.311,56.574,66.329,56.632,66.402,56.704,66.436,56.766,66.433,56.818,66.465,56.851,66.533,56.862,66.562,56.884,66.552,56.917,66.577,56.961,66.637,57.018,66.731,57.071,66.934,57.158,66.96,57.186,66.963,57.211,66.944,57.235,66.953,57.258,66.993,57.28,67.004,57.297,67.043,57.32,67.076,57.339,67.1,57.375,67.08,57.432,67.097,57.464,67.149,57.471,67.16,57.5,67.129,57.55,67.091,57.58,67.099,57.612,67.115,57.635,67.148,57.678,67.153,57.735,67.208,57.786,67.313,57.831,67.534,57.864,67.871,57.886,68.053,57.91,68.082,57.938,68.083,57.97,68.058,58.006,68.089,58.06,68.218,58.168]]},{"n":"Tocantins","z":4,"b":[-49.717,-15.926,-47.397,-2.652],"l":[[-49.179,-15.926,-49.258,-15.768,-49.35,-15.725,-49.437,-15.634,-49.55,-15.479,-49.592,-15.348,-49.564,-15.242,-49.514,-15.165,-49.444,-15.118,-49.356,-15.011,-49.252,-14.845,-49.155,-14.746,-49.064,-14.715,-49.009,-14.629,-48.992,-14.486,-48.994,-14.387,-49.014,-14.33,-49.008,-14.282,-48.973,-14.243,-48.796,-14.125,-48.713,-14.111,-48.653,-14.148,-48.598,-14.154,-48.546,-14.129,-48.384,-13.996,-48.342,-13.922,-48.348,-13.855,-48.325,-13.828,-48.273,-13.84,-48.216,-13.82,-48.155,-13.767,-48.116,-13.691,-48.1,-13.591,-48.123,-13.516,-48.185,-13.467,-48.198,-13.329,-48.164,-13.103,-48.181,-12.897,-48.284,-12.619,-48.238,-12.512,-48.235,-12.466,-48.259,-12.445,-48.266,-12.413,-48.256,-12.37,-48.307,-12.309,-48.42,-12.23,-48.524,-12.029,-48.646,-11.636,-48.648,-11.536,-48.598,-11.467,-48.516,-11.384,-48.489,-11.257,-48.541,-11.11,-48.526,-10.979,-48.443,-10.863,-48.417,-10.732,-48.448,-10.586,-48.454,-10.43,-48.437,-10.262,-48.405,-10.14,-48.357,-10.065,-48.345,-10.001,-48.367,-9.951,-48.365,-9.908,-48.339,-9.874,-48.346,-9.799,-48.387,-9.685,-48.398,-9.553,-48.379,-9.402,-48.307,-9.19,-48.182,-8.917,-48.127,-8.722,-48.141,-8.607,-48.134,-8.522,-48.106,-8.467,-48.113,-8.369,-48.154,-8.229,-48.162,-8.142,-48.137,-8.108,-48.087,-8.085,-48.011,-8.072,-47.963,-8.003,-47.941,-7.877,-47.862,-7.779,-47.724,-7.709,-47.647,-7.627,-47.63,-7.532,-47.589,-7.459,-47.522,-7.407,-47.495,-7.356,-47.507,-7.306,-47.546,-7.288,-47.612,-7.302,-47.669,-7.28,-47.717,-7.222,-47.716,-7.185,-47.665,-7.167,-47.606,-7.115,-47.537,-7.027,-47.47,-6.796,-47.406,-6.421,-47.397,-6.072,-47.445,-5.75,-47.486,-5.561,-47.52,-5.506,-47.612,-5.455,-47.763,-5.407,-47.854,-5.351,-47.885,-5.286,-47.953,-5.256,-48.059,-5.263,-48.169,-5.241,-48.282,-5.189,-48.378,-5.17,-48.458,-5.181,-48.524,-5.22,-48.574,-5.288,-48.631,-5.33,-48.723,-5.355],[-49.687,-3.835,-49.649,-3.361,-49.66,-3.091,-49.708,-2.867,-49.717,-2.736,-49.67,-2.676,-49.635,-2.652]]},{"n":"Tom’","z":5,"b":[84.436,53.435,89.771,56.911],"l":[[89.771,53.646,89.395,53.435,89.176,53.441,89.08,53.483,89.033,53.564,88.975,53.621,88.907,53.654,88.744,53.677,88.487,53.689,88.316,53.684,88.232,53.661,88.115,53.678,87.967,53.733,87.788,53.777,87.579,53.808,87.401,53.81,87.254,53.781,87.179,53.798,87.175,53.861,87.249,53.937,87.402,54.028,87.491,54.105,87.517,54.171,87.512,54.232,87.477,54.288,87.484,54.344,87.536,54.399,87.54,54.475,87.497,54.571,87.4,54.635,87.173,54.681,87.087,54.716,87.069,54.75,87.051,54.83,87.057,54.856,87.103,54.896,87.121,54.945,87.112,55.002,87.04,55.026,86.842,55.067,86.801,55.103,86.715,55.14,86.594,55.143,86.436,55.109,86.371,55.164,86.338,55.236,86.262,55.332,86.166,55.385,86.048,55.395,85.967,55.434,85.924,55.503,85.833,55.571,85.696,55.639,85.537,55.67,85.358,55.663,85.194,55.68,85.045,55.719,84.967,55.766,84.959,55.822,84.977,55.866,84.989,55.917,84.984,55.953,84.958,55.995,84.947,56.049,84.95,56.141,84.916,56.199,84.906,56.235,84.911,56.295,84.898,56.343,84.9,56.378,84.943,56.402,84.947,56.448,84.912,56.516,84.856,56.567,84.782,56.6,84.717,56.663,84.662,56.755,84.597,56.807,84.523,56.819,84.474,56.846,84.436,56.911]]},{"n":"Tongtian","z":2,"b":[92.993,31.69,98.542,34.741],"l":[[92.993,34.1,93.095,34.195,93.262,34.222,93.529,34.215,93.707,34.239,93.838,34.321,94.01,34.501,94.117,34.567,94.215,34.578,94.338,34.629,94.485,34.719,94.661,34.741,94.865,34.694,95.018,34.624,95.119,34.532,95.212,34.486,95.296,34.486,95.398,34.442,95.518,34.355,95.575,34.293,95.57,34.257,95.596,34.229,95.652,34.21,95.757,34.111,95.91,33.933,96.023,33.824,96.095,33.784,96.153,33.77,96.196,33.781,96.222,33.769,96.231,33.735,96.276,33.725,96.359,33.739,96.418,33.715,96.454,33.654,96.529,33.604,96.644,33.566,96.702,33.516,96.704,33.454,96.723,33.406,96.757,33.373,96.781,33.364,96.794,33.381,96.808,33.371,96.822,33.333,96.873,33.32,96.962,33.334,97.011,33.31,97.02,33.246,97.085,33.144,97.207,33.002,97.291,32.923,97.337,32.906,97.378,32.848,97.413,32.749,97.624,32.614,98.012,32.442,98.209,32.33,98.216,32.278,98.278,32.185,98.395,32.051,98.446,31.941,98.432,31.856,98.454,31.782,98.542,31.69]]},{"n":"Trofimovskaya Protoka","z":4.7,"b":[126.444,72.353,128.258,73.267],"l":[[126.475,72.353,126.444,72.508,126.519,72.594,126.712,72.685,126.811,72.762,126.814,72.827,126.901,72.898,127.072,72.975,127.318,73.032,127.641,73.069,127.911,73.132,128.258,73.267]]},{"n":"Tshuapa","z":5,"b":[20.754,-3.177,24.617,-0.191],"l":[[24.594,-3.177,24.617,-2.995,24.583,-2.902,24.499,-2.839,24.44,-2.769,24.388,-2.656,24.369,-2.477,24.271,-2.309,24.088,-2.092,23.971,-1.828,23.92,-1.519,23.848,-1.402,23.769,-1.361,23.708,-1.331,23.669,-1.273,23.488,-1.211,23.166,-1.146,22.96,-1.064,22.871,-0.965,22.771,-0.905,22.661,-0.885,22.557,-0.851,22.458,-0.802,22.397,-0.754,22.375,-0.706,22.273,-0.651,22.001,-0.559,21.785,-0.486,21.683,-0.438,21.624,-0.389,21.575,-0.371,21.535,-0.384,21.475,-0.367,21.393,-0.317,21.303,-0.295,21.204,-0.3,21.127,-0.285,21.072,-0.249,21.029,-0.261,21.009,-0.249,20.978,-0.205,20.936,-0.191,20.877,-0.197,20.821,-0.222,20.796,-0.269,20.754,-0.282]]},{"n":"Tugaloo","z":5,"b":[-83.356,34.6,-83.167,34.708],"l":[[-83.356,34.708,-83.167,34.6]]},{"n":"Tumatskaya Protoka","z":4.7,"b":[126.298,72.353,126.698,73.335],"l":[[126.475,72.353,126.375,72.435,126.309,72.486,126.298,72.522,126.298,72.585,126.31,72.675,126.333,72.735,126.367,72.766,126.372,72.819,126.349,72.895,126.416,72.954,126.575,72.995,126.669,73.036,126.698,73.078,126.676,73.132,126.565,73.233,126.553,73.335]]},{"n":"Tuotuo","z":2,"b":[90.795,34.091,92.993,34.321],"l":[[90.795,34.3,91.152,34.321,91.647,34.281,91.853,34.235,91.936,34.169,92.037,34.165,92.157,34.222,92.309,34.24,92.493,34.22,92.62,34.18,92.688,34.118,92.79,34.091,92.993,34.1]]},{"n":"Ubangi","z":3,"b":[17.724,-0.549,18.622,3.679],"l":[[18.597,3.679,18.622,3.304,18.491,2.924,18.212,2.415,18.072,2.013,18.073,1.719,18.058,1.535,18.012,1.422,17.902,1.118,17.886,0.857,17.925,0.537,17.888,0.234,17.773,-0.052,17.724,-0.278,17.753,-0.549]]},{"n":"Ucayali","z":2,"b":[-75.28,-15.336,-71.457,-4.445],"l":[[-71.669,-15.336,-71.658,-15.217,-71.637,-15.173,-71.604,-15.164,-71.597,-15.115,-71.617,-15.027,-71.596,-14.936,-71.501,-14.792,-71.457,-14.693,-71.457,-14.568,-71.489,-14.384,-71.519,-14.264,-71.549,-14.207,-71.6,-14.163,-71.672,-14.13,-71.738,-14.04,-71.799,-13.892,-71.914,-13.81,-72.081,-13.793,-72.232,-13.738,-72.364,-13.644,-72.549,-13.547,-72.786,-13.446,-72.952,-13.411,-73.049,-13.442,-73.128,-13.449,-73.221,-13.422,-73.478,-13.175,-73.562,-13.061,-73.558,-12.997,-73.584,-12.897,-73.64,-12.76,-73.716,-12.645,-73.811,-12.551,-73.916,-12.392,-74.031,-12.168,-74.055,-12.007,-73.989,-11.909,-74.031,-11.718,-74.183,-11.433,-74.258,-11.256,-74.257,-11.188,-74.204,-11.154,-74.101,-11.155,-73.983,-11.188,-73.851,-11.252,-73.763,-11.28,-73.719,-11.272,-73.719,-11.195,-73.763,-11.048,-73.774,-10.912,-73.753,-10.787,-73.804,-10.684,-73.927,-10.603,-74.003,-10.509,-74.03,-10.403,-74.033,-10.31,-74.013,-10.233,-74.017,-10.205,-74.038,-10.188,-74.051,-10.173,-74.041,-10.128,-74.017,-10.098,-74.004,-10.074,-74.01,-10.059,-74.029,-10.051,-74.058,-10.015,-74.071,-9.961,-74.057,-9.933,-74.015,-9.933,-73.984,-9.908,-73.963,-9.859,-74.001,-9.833,-74.098,-9.829,-74.151,-9.767,-74.16,-9.648,-74.18,-9.559,-74.212,-9.501,-74.247,-9.487,-74.268,-9.489,-74.281,-9.475,-74.291,-9.453,-74.315,-9.415,-74.379,-9.367,-74.405,-9.293,-74.392,-9.192,-74.412,-9.139,-74.447,-9.115,-74.459,-9.089,-74.454,-9.062,-74.458,-9.041,-74.475,-9.029,-74.539,-8.849,-74.508,-8.732,-74.405,-8.62,-74.331,-8.57,-74.286,-8.583,-74.266,-8.553,-74.269,-8.479,-74.298,-8.457,-74.352,-8.486,-74.4,-8.486,-74.441,-8.455,-74.473,-8.447,-74.496,-8.446,-74.506,-8.419,-74.507,-8.321,-74.532,-8.26,-74.582,-8.237,-74.599,-8.207,-74.584,-8.17,-74.594,-8.153,-74.628,-8.157,-74.639,-8.131,-74.625,-8.076,-74.634,-8.03,-74.665,-7.992,-74.706,-7.977,-74.724,-7.975,-74.743,-7.962,-74.728,-7.943,-74.737,-7.917,-74.816,-7.883,-74.86,-7.885,-74.869,-7.924,-74.899,-7.922,-74.95,-7.879,-74.965,-7.84,-74.946,-7.806,-74.962,-7.751,-75.013,-7.676,-75.013,-7.652,-74.99,-7.647,-74.979,-7.623,-75.015,-7.558,-75.016,-7.497,-74.983,-7.438,-75.019,-7.365,-75.123,-7.279,-75.196,-7.24,-75.238,-7.25,-75.255,-7.237,-75.247,-7.203,-75.269,-7.183,-75.28,-7.159,-75.265,-7.129,-75.151,-7.05,-75.136,-7.012,-75.142,-6.988,-75.166,-6.96,-75.15,-6.902,-75.091,-6.827,-75.084,-6.748,-75.13,-6.665,-75.128,-6.61,-75.106,-6.587,-75.125,-6.536,-75.163,-6.496,-75.178,-6.454,-75.152,-6.419,-75.151,-6.345,-75.187,-6.236,-75.18,-6.157,-75.129,-6.106,-75.1,-6.106,-75.081,-6.112,-75.062,-6.101,-75.052,-6.076,-75.018,-6.063,-74.962,-6.075,-74.888,-6.051,-74.8,-5.98,-74.754,-5.985,-74.722,-5.987,-74.697,-5.975,-74.694,-5.942,-74.647,-5.863,-74.555,-5.758,-74.484,-5.727,-74.434,-5.769,-74.4,-5.774,-74.381,-5.742,-74.358,-5.738,-74.335,-5.749,-74.307,-5.739,-74.285,-5.669,-74.244,-5.626,-74.192,-5.603,-74.177,-5.579,-74.186,-5.556,-74.183,-5.533,-74.165,-5.513,-74.167,-5.484,-74.175,-5.47,-74.173,-5.455,-74.159,-5.435,-74.156,-5.413,-74.162,-5.394,-74.148,-5.373,-74.107,-5.345,-74.089,-5.305,-74.099,-5.216,-74.086,-5.125,-74.049,-5.032,-74.027,-5.021,-74,-5.022,-73.963,-5.063,-73.9,-5.058,-73.818,-4.994,-73.774,-4.938,-73.765,-4.895,-73.74,-4.882,-73.714,-4.887,-73.704,-4.863,-73.724,-4.827,-73.702,-4.771,-73.635,-4.711,-73.594,-4.701,-73.563,-4.696,-73.556,-4.676,-73.56,-4.644,-73.547,-4.606,-73.49,-4.553,-73.489,-4.521,-73.506,-4.51,-73.512,-4.489,-73.489,-4.445]]},{"n":"Uele","z":3,"b":[18.567,3.402,28.586,5.127],"l":[[28.586,3.596,28.43,3.647,28.348,3.65,28.29,3.622,28.239,3.637,28.194,3.695,28.141,3.729,28.08,3.74,27.978,3.72,27.835,3.67,27.696,3.644,27.56,3.643,27.431,3.696,27.309,3.803,27.228,3.851,27.185,3.84,27.17,3.789,27.18,3.698,27.153,3.617,27.089,3.544,27.04,3.517,27.007,3.536,26.954,3.535,26.879,3.515,26.833,3.584,26.816,3.741,26.785,3.864,26.74,3.952,26.697,4.002,26.656,4.012,26.561,3.954,26.335,3.765,26.235,3.68,26.157,3.669,26.068,3.703,25.988,3.715,25.916,3.705,25.859,3.657,25.816,3.569,25.762,3.543,25.696,3.577,25.619,3.576,25.53,3.539,25.428,3.524,25.313,3.531,25.226,3.506,25.167,3.45,25.102,3.415,25.031,3.402,24.979,3.417,24.949,3.462,24.839,3.482,24.649,3.477,24.461,3.533,24.276,3.651,24.081,3.709,23.877,3.708,23.733,3.745,23.651,3.818,23.545,3.845,23.415,3.824,23.3,3.836,23.199,3.88,23.049,3.885,22.85,3.851,22.668,3.909,22.422,4.135,21.908,4.254,21.687,4.281,21.538,4.245,21.35,4.311,21.268,4.323,21.23,4.302,21.126,4.332,20.956,4.413,20.793,4.447,20.647,4.436,20.558,4.463,20.486,4.542,20.394,4.686,20.226,4.83,20.002,4.945,19.862,5.031,19.807,5.089,19.686,5.121,19.501,5.127,19.323,5.071,19.069,4.891,18.832,4.523,18.7,4.383,18.594,4.346,18.567,4.258,18.62,4.117,18.634,3.954,18.597,3.679]]},{"n":"Ural","z":4,"b":[51.11,46.944,59.426,54.52],"l":[[58.631,51.695,58.628,51.619,58.659,51.561,58.723,51.496,58.751,51.425,58.744,51.347,58.713,51.287,58.657,51.247,58.527,51.201,58.325,51.149,58.209,51.106,58.181,51.072,58.045,51.069,57.801,51.096,57.536,51.165,57.248,51.275,56.906,51.353,56.51,51.399,56.224,51.472,56.049,51.573,55.877,51.648,55.709,51.696,55.456,51.735,55.119,51.766,54.65,51.726,54.047,51.613,53.667,51.56,53.511,51.566,53.406,51.551,53.35,51.514,53.227,51.485,53.038,51.464,52.903,51.467,52.82,51.495,52.728,51.498,52.626,51.478,52.554,51.442,52.512,51.391,52.428,51.383,52.301,51.418,52.225,51.421,52.199,51.392,52.128,51.382,52.012,51.391,51.941,51.378,51.916,51.343,51.769,51.286,51.499,51.205,51.395,51.148,51.364,51.075,51.316,50.998,51.221,50.904,51.183,50.826,51.201,50.765,51.189,50.708,51.148,50.657,51.136,50.595,51.154,50.523,51.15,50.478,51.121,50.46,51.116,50.436,51.133,50.406,51.131,50.365,51.11,50.312,51.156,50.253,51.269,50.187,51.329,50.122,51.341,50.027,51.32,49.955,51.347,49.913,51.416,49.875,51.471,49.802,51.512,49.692,51.558,49.631,51.594,49.614,51.608,49.585,51.636,49.546,51.707,49.488,51.739,49.449,51.744,49.42,51.76,49.404,51.828,49.374,51.848,49.352,51.847,49.336,51.859,49.323,51.884,49.314,51.916,49.275,51.948,49.206,51.936,49.138,51.881,49.071,51.85,49.001,51.841,48.927,51.854,48.895,51.88,48.869,51.876,48.815,51.804,48.623,51.776,48.586,51.73,48.56,51.667,48.547,51.625,48.515,51.606,48.469,51.603,48.443,51.61,48.424,51.635,48.405,51.633,48.386,51.602,48.37,51.589,48.318,51.592,48.229,51.584,48.181,51.569,48.167,51.582,48.119,51.64,48.016,51.654,47.882,51.623,47.716,51.674,47.579,51.806,47.47,51.879,47.397,51.895,47.36,51.892,47.331,51.872,47.31,51.877,47.293,51.908,47.28,51.927,47.25,51.934,47.204,51.884,47.121,51.724,46.944],[59.426,54.52,59.307,54.403,59.194,54.325,59.162,54.278,59.172,54.236,59.15,54.175,59.095,54.093,59.102,54.012,59.173,53.932,59.19,53.863,59.156,53.805,59.169,53.746,59.229,53.686,59.254,53.64,59.243,53.61,59.182,53.573,59.072,53.531,59.021,53.464,59.029,53.374,59.067,53.307,59.136,53.265,59.13,53.217,59.049,53.165,58.974,53.071,58.905,52.936,58.904,52.811,58.971,52.697,58.996,52.62,58.986,52.6,58.99,52.569,59.01,52.546,59.058,52.527,59.083,52.513,59.08,52.47,59.062,52.42,58.834,52.196]]},{"n":"Uruguay","z":5,"b":[-58.201,-32.472,-49.737,-27.081],"l":[[-49.737,-28.511,-49.839,-28.465,-49.938,-28.448,-50.069,-28.444,-50.141,-28.453,-50.155,-28.474,-50.233,-28.469,-50.446,-28.422,-50.578,-28.386,-50.736,-28.236,-50.966,-27.959,-51.117,-27.808,-51.19,-27.781,-51.287,-27.716,-51.406,-27.613,-51.488,-27.564,-51.53,-27.569,-51.582,-27.548,-51.645,-27.5,-51.702,-27.488,-51.754,-27.512,-51.8,-27.512,-51.873,-27.509,-51.913,-27.473,-51.984,-27.377,-52.06,-27.314,-52.139,-27.283,-52.192,-27.281,-52.229,-27.293,-52.265,-27.277,-52.304,-27.283,-52.348,-27.276,-52.415,-27.236,-52.511,-27.231,-52.636,-27.259,-52.742,-27.246,-52.826,-27.19,-52.9,-27.177,-52.953,-27.179,-52.994,-27.163,-53.031,-27.137,-53.071,-27.142,-53.104,-27.154,-53.134,-27.14,-53.184,-27.15,-53.227,-27.174,-53.263,-27.163,-53.285,-27.129,-53.305,-27.098,-53.348,-27.081,-53.398,-27.082,-53.457,-27.103,-53.49,-27.133,-53.498,-27.172,-53.54,-27.198,-53.616,-27.21,-53.66,-27.2,-53.672,-27.169,-53.718,-27.158,-53.8,-27.165,-53.852,-27.156,-53.935,-27.161,-54.04,-27.244,-54.114,-27.275,-54.156,-27.254,-54.205,-27.29,-54.26,-27.382,-54.327,-27.423,-54.448,-27.446,-54.484,-27.457,-54.555,-27.454,-54.615,-27.477,-54.666,-27.527,-54.72,-27.545,-54.777,-27.533,-54.829,-27.551,-54.876,-27.599,-54.903,-27.652,-54.91,-27.709,-54.956,-27.747,-55.04,-27.768,-55.069,-27.796,-55.064,-27.836,-55.102,-27.867,-55.244,-27.899,-55.347,-27.956,-55.41,-28.038,-55.477,-28.089,-55.582,-28.121,-55.726,-28.204,-55.746,-28.255,-55.692,-28.303,-55.672,-28.345,-55.687,-28.382,-55.732,-28.387,-55.806,-28.36,-55.859,-28.354,-55.891,-28.37,-55.905,-28.4,-55.904,-28.443,-55.93,-28.473,-55.985,-28.489,-56.02,-28.525,-56.034,-28.581,-56.103,-28.652,-56.226,-28.737,-56.322,-28.852,-56.393,-28.997,-56.476,-29.092,-56.571,-29.138,-56.636,-29.203,-56.672,-29.287,-56.772,-29.418,-56.939,-29.595,-57.089,-29.716,-57.225,-29.782,-57.301,-29.857,-57.318,-29.939,-57.405,-30.034,-57.564,-30.14,-57.646,-30.227,-57.651,-30.295,-57.713,-30.384,-57.831,-30.495,-57.873,-30.591,-57.819,-30.712,-57.811,-30.859,-57.834,-30.917,-57.886,-30.937,-57.898,-30.975,-57.87,-31.031,-57.868,-31.104,-57.893,-31.195,-57.948,-31.299,-58.033,-31.417,-58.054,-31.495,-58.01,-31.534,-57.988,-31.576,-57.989,-31.621,-58.007,-31.685,-58.042,-31.769,-58.096,-31.832,-58.168,-31.873,-58.189,-31.924,-58.16,-31.987,-58.156,-32.052,-58.177,-32.119,-58.165,-32.185,-58.12,-32.249,-58.123,-32.322,-58.201,-32.472]]},{"n":"Usumacinta","z":4.7,"b":[-92.71,16.391,-90.417,18.665],"l":[[-90.417,16.391,-90.471,16.44,-90.576,16.468,-90.634,16.511,-90.634,16.565,-90.66,16.631,-90.711,16.708,-90.816,16.787,-90.976,16.868,-91.112,16.976,-91.224,17.112,-91.319,17.2,-91.392,17.236,-91.417,17.264,-91.417,17.287,-91.4,17.314,-91.414,17.35,-91.466,17.384,-91.496,17.42,-91.504,17.458,-91.488,17.481,-91.449,17.488,-91.45,17.515,-91.49,17.563,-91.473,17.589,-91.398,17.594,-91.366,17.627,-91.378,17.686,-91.4,17.711,-91.433,17.701,-91.446,17.715,-91.44,17.751,-91.456,17.779,-91.494,17.798,-91.524,17.794,-91.546,17.766,-91.567,17.773,-91.587,17.813,-91.613,17.818,-91.645,17.787,-91.652,17.764,-91.64,17.75,-91.641,17.736,-91.69,17.732,-91.732,17.762,-91.79,17.844,-91.85,17.878,-91.912,17.862,-91.993,17.887,-92.093,17.954,-92.137,18.013,-92.127,18.062,-92.144,18.122,-92.189,18.191,-92.212,18.204],[-92.212,18.204,-92.296,18.255,-92.464,18.316,-92.572,18.335,-92.621,18.312,-92.652,18.333,-92.67,18.431,-92.675,18.481,-92.679,18.543,-92.688,18.578,-92.71,18.612],[-92.212,18.204,-92.263,18.361,-92.31,18.426,-92.371,18.467,-92.422,18.531,-92.485,18.665]]},{"n":"Vaal","z":4,"b":[23.628,-29.073,30.21,-26.383],"l":[[25.622,-27.668,25.366,-27.758,25.223,-27.855,25.11,-27.991,24.99,-28.07,24.864,-28.093,24.774,-28.149,24.719,-28.238,24.694,-28.336,24.699,-28.443,24.676,-28.496,24.631,-28.503,24.605,-28.53,24.601,-28.562,24.582,-28.581,24.555,-28.564,24.516,-28.554,24.468,-28.572,24.424,-28.556,24.402,-28.509,24.359,-28.463,24.297,-28.432,24.216,-28.473,24.115,-28.587,24.04,-28.723,23.992,-28.882,23.883,-28.989,23.628,-29.073],[28.124,-26.867,28,-26.717,27.909,-26.694,27.81,-26.75,27.716,-26.768,27.627,-26.748,27.557,-26.772,27.506,-26.84,27.437,-26.871,27.351,-26.865,27.276,-26.887,27.212,-26.936,27.118,-26.937,26.994,-26.889,26.943,-26.89,26.921,-26.933,26.887,-26.948,26.825,-26.935,26.698,-26.99,26.507,-27.115,26.45,-27.177,26.45,-27.217,26.467,-27.235,26.477,-27.279,26.459,-27.328,26.466,-27.362,26.432,-27.398,26.333,-27.407,26.228,-27.46,26.091,-27.603],[30.135,-26.383,30.21,-26.479,30.188,-26.56,30.049,-26.709,29.839,-26.811,29.697,-26.845,29.552,-26.846,29.473,-26.867,29.461,-26.909,29.415,-26.924,29.336,-26.911,29.269,-26.933,29.215,-26.99,29.156,-27.025,29.104,-27.042,29.062,-27.044,28.992,-27.007,28.933,-27.006,28.859,-27.028,28.78,-27.018,28.694,-26.977,28.665,-26.976,28.656,-26.993,28.627,-27.019,28.565,-27.045,28.531,-27.043,28.524,-27.014,28.496,-26.99]]},{"n":"Verde","z":5,"b":[-100.344,21.664,-98.59,22.117],"l":[[-100.344,22.117,-100.171,22.027,-99.993,21.945,-99.902,21.864,-99.84,21.756,-99.782,21.703,-99.73,21.705,-99.69,21.692,-99.665,21.664,-99.621,21.673,-99.559,21.719,-99.46,21.719,-99.324,21.672,-99.191,21.699,-99.06,21.799,-98.952,21.851,-98.868,21.855,-98.807,21.89,-98.769,21.957,-98.728,21.988,-98.662,21.983,-98.59,21.981]]},{"n":"Verkhniy Yenisey","z":4,"b":[91.367,51.566,94.465,58.1],"l":[[94.465,51.734,94.07,51.604,93.646,51.566,92.769,51.581,92.35,51.701,92.152,51.796,92.035,51.905,92.068,52.012,92.251,52.117,92.377,52.214,92.444,52.303,92.438,52.378,92.358,52.437,92.314,52.5,92.307,52.565,92.172,52.597,91.91,52.594,91.769,52.616,91.748,52.662,91.672,52.677,91.541,52.662,91.439,52.674,91.367,52.714,91.378,52.797,91.472,52.924,91.501,53.012,91.465,53.06,91.454,53.102,91.468,53.136,91.538,53.16,91.664,53.173,91.788,53.23,91.909,53.329,91.955,53.398,91.924,53.437,91.847,53.479,91.725,53.522,91.634,53.57,91.575,53.621,91.559,53.66,91.586,53.687,91.578,53.709,91.511,53.735],[92.998,58.1,92.989,58.049,93.028,58.029,93.113,58.024,93.176,57.98,93.22,57.897,93.305,57.637,93.305,57.516,93.262,57.419,93.293,57.332,93.398,57.255,93.453,57.183,93.457,57.114,93.438,57.063,93.396,57.028,93.383,56.993,93.457,56.861,93.509,56.823,93.58,56.802,93.657,56.726,93.741,56.596,93.735,56.48,93.64,56.38,93.55,56.317,93.465,56.292,93.418,56.262,93.408,56.227,93.345,56.195,93.228,56.167,93.162,56.131,93.148,56.085,92.935,56.035,92.523,55.979,92.3,55.923,92.266,55.865,92.301,55.813,92.406,55.768,92.47,55.716,92.493,55.658,92.48,55.598,92.408,55.503]]},{"n":"Victoria Nile","z":2,"b":[31.366,0.428,33.187,2.333],"l":[[32.893,1.311,33.011,0.809,33.085,0.589,33.187,0.428],[32.475,1.552,32.545,1.522],[31.366,2.193,31.387,2.2,31.751,2.324,31.948,2.333,32.087,2.265,32.187,2.236,32.247,2.245,32.295,2.199,32.333,2.1,32.349,2.005,32.344,1.914,32.282,1.83,32.164,1.754,32.104,1.699,32.104,1.665,32.138,1.652,32.241,1.666]]},{"n":"Vilyuy","z":5,"b":[103.516,61.939,126.423,66.008],"l":[[126.423,64.403,126.421,64.285,126.31,64.18,126.094,64.057,125.808,63.964,125.454,63.901,125.115,63.894,124.793,63.944,124.621,63.986,124.599,64.021,124.345,64.051,123.616,64.091,123.225,63.944,123.035,63.907,122.917,63.931,122.607,63.925,122.106,63.891,121.795,63.852,121.675,63.808,121.503,63.789,121.28,63.795,121.141,63.777,121.085,63.736,121.023,63.726,120.955,63.747,120.869,63.729,120.766,63.674,120.658,63.648,120.545,63.652,120.482,63.611,120.469,63.524,120.329,63.509,120.063,63.564,119.674,63.566,119.163,63.516,118.891,63.481,118.858,63.461,118.859,63.43,118.894,63.388,118.833,63.349,118.678,63.314,118.408,63.285,118.024,63.264,117.769,63.221,117.642,63.156,117.544,63.049,117.476,62.902,117.457,62.813,117.487,62.784,117.481,62.754,117.439,62.722,117.419,62.661,117.421,62.573,117.456,62.516,117.524,62.491,117.549,62.445,117.53,62.379,117.573,62.306,117.679,62.228,117.709,62.161,117.665,62.106,117.619,62.088,117.57,62.106,117.522,62.09,117.473,62.039,117.38,61.992,117.244,61.95,117.044,61.939,116.782,61.961,116.55,62,116.35,62.056,116.216,62.104,116.148,62.143,116.126,62.19,116.151,62.245,116.054,62.301,115.835,62.358,115.764,62.421,115.735,62.467,115.735,62.505,115.763,62.559,115.717,62.584,115.596,62.579,115.494,62.592,115.41,62.621,115.352,62.656,115.32,62.698,115.323,62.738,115.362,62.776,115.338,62.802,115.251,62.815,115.229,62.851,115.273,62.907,115.281,62.952,115.255,62.984,115.198,63.015,115.11,63.045,115.029,63.043,114.955,63.009,114.865,63.001,114.759,63.02,114.683,63.046,114.638,63.079,114.631,63.114,114.664,63.149,114.671,63.173,114.654,63.185,114.344,63.145,114.207,63.148,114.133,63.186,114.059,63.195,113.984,63.175,113.937,63.179,113.917,63.206,113.855,63.228,113.752,63.246,113.638,63.215,113.515,63.136,113.497,63.097,113.455,63.061,113.378,63.057,113.355,63.04,113.363,62.996,113.375,62.96,113.389,62.934,113.432,62.905,113.431,62.873,113.385,62.839,113.339,62.829,113.292,62.842,113.254,62.88,113.225,62.941,113.157,62.978,113.051,62.99,112.992,63.013,112.98,63.047,112.926,63.056,112.83,63.039,112.783,63.009,112.766,62.981,112.709,62.96,112.48,63.02],[103.516,65.994,103.677,66.008,103.769,65.993,103.885,65.935,104.057,65.803,104.268,65.731,104.576,65.674,104.811,65.647,104.973,65.65,105.152,65.683,105.348,65.744,105.495,65.765,105.593,65.744,105.711,65.685,105.85,65.587,105.918,65.515,105.916,65.47,105.932,65.437,105.966,65.419,106.021,65.422,106.097,65.447,106.216,65.448,106.378,65.424,106.54,65.435,106.703,65.48,106.782,65.517,106.821,65.555,106.935,65.575,107.415,65.618,107.704,65.592,107.814,65.568,107.84,65.538,107.845,65.507,107.829,65.475,107.841,65.458,107.858,65.446,107.856,65.427,107.88,65.385,107.957,65.317,108.064,65.296,108.203,65.323,108.349,65.314,108.594,65.236,108.628,65.213,108.638,65.194,108.623,65.179,108.667,65.151,108.771,65.11,108.883,65.105,109.004,65.139,109.106,65.125,109.19,65.065,109.299,65.019,109.432,64.987,109.487,64.939,109.463,64.875,109.418,64.829,109.351,64.8,109.317,64.763,109.315,64.721,109.392,64.675,109.547,64.626,109.555,64.581,109.416,64.541,109.367,64.484,109.407,64.411,109.408,64.36,109.368,64.331,109.296,64.321,109.19,64.329,109.114,64.29,109.069,64.203,109.125,64.165,109.283,64.176,109.421,64.167,109.54,64.137,109.546,64.103,109.438,64.066,109.405,64.026,109.447,63.984,109.463,63.95,109.451,63.924,109.477,63.895,109.542,63.863,109.545,63.818,109.486,63.761,109.386,63.707,109.284,63.685,109.245,63.655,109.271,63.626,109.348,63.569,109.411,63.528,109.587,63.455]]},{"n":"Vistula","z":4.7,"b":[18.2,49.644,21.961,54.349],"l":[[18.879,49.644,18.788,49.817,18.771,49.888,18.81,49.925],[18.937,49.932,19.068,49.952,19.141,49.987,19.2,50.042,19.348,50.046,19.586,49.999,19.852,50.006,20.291,50.096,20.522,50.137,20.645,50.188,20.738,50.261,20.883,50.307,21.082,50.326,21.289,50.386,21.505,50.485,21.67,50.586,21.784,50.689,21.824,50.846,21.79,51.057,21.784,51.188,21.805,51.239,21.856,51.288,21.935,51.335,21.961,51.386,21.933,51.442,21.897,51.473,21.854,51.478,21.83,51.501,21.824,51.542,21.732,51.591,21.553,51.647,21.452,51.705,21.43,51.763,21.371,51.82,21.276,51.877,21.241,51.918,21.265,51.944,21.259,51.993,21.221,52.067,21.126,52.17,20.974,52.302,20.842,52.384,20.731,52.416,20.524,52.419,20.222,52.392,19.964,52.421,19.64,52.549,19.202,52.644,18.959,52.76,18.766,52.928,18.538,53.038,18.274,53.089,18.2,53.174,18.315,53.293,18.463,53.383,18.645,53.444,18.739,53.519,18.745,53.608,18.777,53.674,18.835,53.716,18.871,53.777,18.884,53.858,18.873,53.947,18.839,54.043,18.853,54.129,18.916,54.205,18.949,54.272,18.976,54.349]]},{"n":"Volga","z":3,"b":[32.598,45.657,50.148,58.113],"l":[[47.842,46.735,48.114,46.683,48.22,46.605,48.25,46.484,48.314,46.401,48.411,46.356,48.501,46.272,48.684,46.086],[45.664,50.222,45.49,50.122,45.414,50.056,45.379,49.991,45.361,49.916,45.36,49.832,45.305,49.737,45.198,49.632,45.107,49.513,45.048,49.408,45.011,49.351,44.996,49.313,44.99,49.294,44.987,49.259,44.976,49.195,44.842,49.013,44.588,48.727,44.544,48.665,44.528,48.598,44.596,48.545,44.865,48.466,45.043,48.434,45.129,48.451,45.228,48.447,45.337,48.42,45.411,48.424,45.449,48.461,45.559,48.451,45.733,48.399,45.798,48.383,45.831,48.369,45.865,48.353,45.909,48.322,46.078,48.218,46.155,48.11,46.147,47.994,46.193,47.918,46.295,47.881,46.35,47.843,46.359,47.802,46.409,47.773,46.498,47.756,46.616,47.673,46.764,47.525,46.875,47.444,46.948,47.431,47.009,47.403,47.057,47.359,47.082,47.316,47.085,47.252,47.086,47.214,47.212,47.144,47.463,47.029,47.585,46.942,47.577,46.884,47.612,46.855,47.69,46.855,47.8,46.773,47.942,46.609,48.002,46.506,47.981,46.464,47.98,46.418,47.998,46.37,47.916,46.25,47.735,46.057,47.641,45.929,47.636,45.866,47.61,45.834,47.578,45.813,47.561,45.79,47.563,45.761,47.587,45.737,47.631,45.694,47.65,45.657],[49.905,53.457,50.042,53.455,50.108,53.405,50.148,53.306,50.129,53.236,50.048,53.193,49.957,53.185,49.856,53.213,49.717,53.203,49.543,53.156,49.428,53.152,49.372,53.193,49.269,53.219,49.119,53.23,48.919,53.209,48.667,53.155,48.524,53.105,48.491,53.057,48.503,52.995,48.561,52.919,48.505,52.816,48.334,52.688,48.177,52.506,48.035,52.271,47.901,52.125,47.775,52.066,47.644,52.043,47.507,52.055,47.273,51.981,47.114,51.919,46.971,51.83,46.843,51.773,46.775,51.748],[43.432,56.675,43.657,56.496,43.837,56.404,44.047,56.337,44.167,56.274,44.198,56.214,44.258,56.18,44.348,56.172,44.451,56.14,44.566,56.086,44.776,56.082,45.082,56.131,45.281,56.137,45.373,56.102,45.443,56.104,45.489,56.143,45.56,56.16,45.655,56.153,45.737,56.166,45.806,56.201,45.872,56.199,45.936,56.161,46.025,56.172,46.138,56.232,46.294,56.287,46.492,56.336,46.68,56.309,46.857,56.205,47.166,56.151,47.829,56.145,47.886,56.122,47.927,56.073,47.972,55.992,48.067,55.925,48.212,55.872,48.46,55.825,48.809,55.784,49.003,55.693,49.059,55.483],[37.148,56.752,37.392,56.862,37.477,56.935,37.486,57.008,37.558,57.106,37.761,57.291],[32.598,57.25,32.681,57.079,32.675,57.009,32.692,56.972,32.729,56.945,32.851,56.932,33.057,56.933,33.282,56.899,33.528,56.829,33.675,56.765,33.722,56.707,33.816,56.641,33.956,56.565,33.98,56.509,33.886,56.473,33.844,56.445,33.857,56.423,34.05,56.357,34.424,56.245,34.668,56.209,34.784,56.247,34.862,56.29,34.903,56.337,34.909,56.369,34.88,56.385,34.891,56.416,34.94,56.459,34.956,56.503,34.937,56.546,35.001,56.578,35.223,56.613,35.334,56.703,35.422,56.742,35.523,56.76,35.607,56.791,35.672,56.835,35.773,56.861,35.908,56.867,36.066,56.846,36.245,56.799,36.344,56.758,36.375,56.706],[41.344,57.493,41.715,57.448,41.889,57.451,41.989,57.489,42.173,57.485,42.576,57.415],[40.007,57.567,39.853,57.669,39.798,57.725,39.79,57.77,39.697,57.831,39.517,57.909,39.246,57.99,38.704,58.113],[38.301,57.572,38.446,57.761,38.48,57.872,38.437,58.018]]},{"n":"Volta","z":4.7,"b":[0.099,5.76,0.672,6.338],"l":[[0.103,6.338,0.099,6.188,0.157,6.114,0.275,6.065,0.38,6.042,0.47,6.046,0.544,6.021,0.6,5.968,0.639,5.897,0.672,5.76]]},{"n":"Vorma","z":5,"b":[10.945,59.171,12.055,60.525],"l":[[11.17,59.389,11.141,59.3,11.083,59.249,10.945,59.171],[11.2,59.67,11.179,59.632,11.127,59.604,11.127,59.562,11.184,59.513,11.203,59.498],[12.028,60.525,12.055,60.492,12.048,60.411,12.017,60.27,11.925,60.212,11.774,60.236,11.641,60.228,11.527,60.186,11.385,60.102,11.192,59.956,11.157,59.911]]},{"n":"Vuoksi","z":5,"b":[28.788,60.574,30.534,61.204],"l":[[30.478,60.574,30.504,60.587,30.511,60.616,30.534,60.63],[28.788,61.204,28.822,61.129,29.095,60.952,29.288,60.849,29.596,60.717]]},{"n":"Vychegda","z":4.7,"b":[46.591,61.287,55.398,62.51],"l":[[53.691,61.65,53.653,61.686,53.651,61.713,53.673,61.744,53.635,61.775,53.539,61.807,53.438,61.809,53.333,61.782,53.25,61.79,53.19,61.833,53.034,61.865,52.658,61.897,52.583,61.931,52.452,61.939,52.241,61.933,52.076,61.915,51.956,61.883,51.894,61.854,51.889,61.828,51.779,61.823,51.564,61.84,51.307,61.815,51.007,61.748,50.877,61.719,50.803,61.751,50.78,61.836,50.766,61.875,50.762,61.966,50.7,62.005,50.546,62.017,50.313,62.001,50.242,62.041,50.247,62.077,50.298,62.13,50.343,62.167,50.331,62.208,50.259,62.221,50.081,62.218,49.866,62.174,49.611,62.088,49.379,62.074,49.168,62.131,48.902,62.098,48.581,61.977,48.4,61.879,48.359,61.804,48.235,61.709,48.028,61.594,47.802,61.495,47.557,61.411,47.222,61.347,46.591,61.287],[55.398,62.362,55.183,62.438,55.015,62.438,54.908,62.462,54.805,62.51,54.727,62.506,54.676,62.45,54.568,62.422,54.402,62.422,54.297,62.397,54.252,62.347,54.223,62.298,54.211,62.252,54.236,62.165,54.298,62.036,54.394,61.944,54.524,61.889,54.662,61.858,54.809,61.848,54.891,61.797,54.908,61.703,54.905,61.648,54.882,61.631,54.126,61.532,53.829,61.539,53.684,61.65]]},{"n":"Waal","z":4,"b":[3.979,51.719,4.985,51.848],"l":[[4.985,51.824,4.725,51.757,4.581,51.719,4.418,51.74,4.074,51.841,3.979,51.848]]},{"n":"Wabash","z":5,"b":[-88.095,37.789,-84.639,40.872],"l":[[-84.755,40.359,-84.647,40.356,-84.639,40.371,-84.724,40.422,-84.757,40.44,-84.767,40.483,-84.759,40.525,-84.794,40.567,-84.886,40.588,-85.007,40.65,-85.156,40.752,-85.317,40.826,-85.49,40.872,-85.677,40.863,-85.877,40.801,-86.113,40.763,-86.383,40.75,-86.618,40.66,-86.818,40.494,-86.955,40.412,-87.029,40.415,-87.117,40.39,-87.218,40.337,-87.404,40.214,-87.445,40.127,-87.438,40.018,-87.419,39.941,-87.388,39.895,-87.378,39.808,-87.388,39.682,-87.413,39.568,-87.452,39.467,-87.507,39.392,-87.579,39.344,-87.611,39.291,-87.603,39.234,-87.615,39.193,-87.647,39.169,-87.632,39.107,-87.568,39.007,-87.53,38.909,-87.518,38.812,-87.529,38.742,-87.584,38.679,-87.636,38.543,-87.681,38.488,-87.735,38.47,-87.784,38.419,-87.827,38.336,-87.873,38.295,-87.921,38.298,-87.957,38.282,-87.98,38.249,-87.977,38.214,-87.949,38.178,-87.958,38.144,-88.006,38.113,-88.005,38.098,-87.99,38.089,-87.996,38.074,-88.026,38.06,-88.045,38.013,-88.043,37.937,-88.059,37.898,-88.091,37.897,-88.095,37.876,-88.042,37.789]]},{"n":"Waikato","z":5,"b":[174.768,-39.072,176.326,-37.287],"l":[[175.744,-39.072,175.807,-39.014,175.817,-38.983,175.803,-38.967,175.802,-38.939],[176.059,-38.694,176.125,-38.628,176.183,-38.603,176.256,-38.595,176.304,-38.567,176.326,-38.519,176.303,-38.498,176.234,-38.506,176.167,-38.482,176.101,-38.428,176.003,-38.405,175.873,-38.415,175.778,-38.394,175.72,-38.341,175.679,-38.274,175.656,-38.192,175.654,-38.107,175.673,-38.019,175.655,-37.963,175.573,-37.928,175.451,-37.892,175.353,-37.828,175.239,-37.725,175.178,-37.625,175.169,-37.527,175.138,-37.43,175.083,-37.334,175,-37.287,174.833,-37.292,174.768,-37.339]]},{"n":"Waitaki","z":5,"b":[170.223,-44.911,170.999,-44.003],"l":[[170.51,-44.003,170.387,-44.216,170.332,-44.288,170.279,-44.32,170.257,-44.361,170.243,-44.388,170.223,-44.49,170.283,-44.591,170.491,-44.739,170.558,-44.795,170.655,-44.833,170.882,-44.891,170.999,-44.911]]},{"n":"Weir","z":3,"b":[150.47,-29.139,151.863,-28.639],"l":[[151.863,-28.739,151.75,-28.77,151.673,-28.824,151.554,-28.955,151.528,-29.035,151.478,-29.087,151.393,-29.139,151.335,-29.104,151.303,-28.982,151.231,-28.898,151.118,-28.851,151.058,-28.805,151.05,-28.758,150.982,-28.71,150.856,-28.66,150.712,-28.639,150.47,-28.65]]},{"n":"Willamette","z":5,"b":[-123.22,43.422,-122.095,45.674],"l":[[-122.095,43.422,-122.219,43.485,-122.371,43.515,-122.424,43.574,-122.427,43.672,-122.458,43.746,-122.517,43.795,-122.684,43.888,-122.96,44.024,-123.119,44.15,-123.184,44.323,-123.22,44.513,-123.189,44.599,-123.103,44.644,-123.077,44.68,-123.112,44.707,-123.116,44.747,-123.09,44.802,-123.098,44.836,-123.14,44.849,-123.124,44.887,-123.05,44.947,-123.024,44.999,-123.046,45.042,-123.036,45.081,-122.993,45.117,-122.987,45.157,-123.018,45.202,-123.016,45.238,-122.981,45.267,-122.902,45.288,-122.718,45.306,-122.621,45.368,-122.603,45.427,-122.634,45.502,-122.677,45.56,-122.732,45.603,-122.745,45.649,-122.726,45.674]]},{"n":"Winnipeg","z":5,"b":[-96.295,49.993,-94.615,50.625],"l":[[-94.615,49.993,-94.651,50.024,-94.648,50.062,-94.639,50.101,-94.668,50.135,-94.745,50.143,-94.809,50.15,-94.931,50.131,-94.999,50.141,-95.013,50.18,-95.073,50.22,-95.179,50.261,-95.207,50.34,-95.276,50.366,-95.398,50.369,-95.506,50.323,-95.6,50.228,-95.726,50.17,-95.883,50.148,-95.987,50.148,-96.036,50.168,-96.052,50.205,-96.026,50.283],[-95.989,50.404,-96.005,50.451,-96.081,50.506,-96.295,50.625]]},{"n":"Xar Moron","z":5,"b":[117.172,42.592,120.759,43.423],"l":[[117.209,42.592,117.172,42.613,117.179,42.645,117.216,42.696,117.23,42.748,117.214,42.827,117.189,42.886,117.194,42.935,117.222,42.994,117.347,43.063,117.57,43.142,117.746,43.185,117.874,43.192,118.016,43.22,118.172,43.271,118.471,43.276,118.911,43.237,119.22,43.229,119.484,43.262,120.759,43.423]]},{"n":"Xi","z":4,"b":[111.301,22.178,113.552,23.481],"l":[[113.035,22.795,113.095,22.73,113.166,22.576,113.214,22.483,113.264,22.363,113.416,22.178],[113.035,22.795,113.299,22.698,113.395,22.651,113.444,22.599,113.492,22.591,113.552,22.587],[112.98,23.041,113.445,23.055],[112.805,23.148,112.819,23.082,112.891,22.918,112.963,22.822,113.035,22.795],[112.805,23.148,112.912,23.113,112.974,23.052,113.024,22.954,113.09,22.907,113.213,22.912,113.338,22.889],[111.301,23.481,111.433,23.449,111.499,23.375,111.542,23.249,111.707,23.161,112.137,23.09,112.271,23.142,112.353,23.132,112.427,23.076,112.498,23.078,112.563,23.136,112.643,23.165,112.736,23.164,112.805,23.148]]},{"n":"Xiliao","z":5,"b":[120.759,43.423,123.554,43.815],"l":[[120.759,43.423,121.059,43.509,121.349,43.517,121.729,43.474,122.03,43.521,122.251,43.656,122.449,43.748,122.624,43.795,122.769,43.815,122.886,43.808,123.017,43.765,123.162,43.687,123.269,43.647,123.338,43.643,123.405,43.628,123.468,43.6,123.513,43.561,123.554,43.483]]},{"n":"Xingu","z":4.7,"b":[-53.444,-11.444,-51.641,-1.64],"l":[[-53.444,-11.444,-53.305,-11.298,-53.273,-11.175,-53.302,-11.025,-53.266,-10.917,-53.166,-10.852,-53.101,-10.706,-53.072,-10.48,-53.032,-10.343,-52.982,-10.296,-52.972,-10.25,-53.001,-10.206,-53.011,-10.155,-53.002,-10.095,-52.945,-10.005,-52.839,-9.884,-52.755,-9.808,-52.69,-9.775,-52.636,-9.715,-52.59,-9.628,-52.48,-9.596,-52.304,-9.62,-52.205,-9.585,-52.182,-9.491,-52.133,-9.4,-52.058,-9.311,-52.036,-9.237,-52.067,-9.176,-52.083,-9.085,-52.085,-8.965,-52.132,-8.878,-52.222,-8.824,-52.292,-8.746,-52.342,-8.643,-52.413,-8.581,-52.505,-8.558,-52.572,-8.522,-52.612,-8.473,-52.637,-8.4,-52.645,-8.303,-52.688,-8.167,-52.765,-7.993,-52.742,-7.726,-52.618,-7.365,-52.513,-7.131,-52.427,-7.022,-52.31,-6.954,-52.164,-6.925,-52.064,-6.863,-52.011,-6.767,-52.011,-6.677,-52.063,-6.594,-52.132,-6.548,-52.216,-6.539,-52.301,-6.483,-52.387,-6.381,-52.435,-6.29,-52.45,-6.172,-52.512,-6.089,-52.534,-6.009,-52.536,-5.902,-52.58,-5.754,-52.667,-5.565,-52.75,-5.445,-52.829,-5.395,-52.882,-5.298,-52.908,-5.156,-52.856,-4.933,-52.775,-4.745,-52.725,-4.629,-52.679,-4.441,-52.719,-4.37,-52.714,-4.301,-52.665,-4.233,-52.634,-4.114,-52.613,-3.861],[-52.609,-3.855,-52.345,-3.595,-52.233,-3.438,-52.184,-3.299,-52.109,-3.275,-52.009,-3.367,-51.952,-3.441,-51.939,-3.499,-51.905,-3.543,-51.849,-3.574,-51.787,-3.56,-51.719,-3.502,-51.693,-3.445,-51.709,-3.388,-51.691,-3.313,-51.641,-3.218,-51.657,-3.15,-51.738,-3.108,-51.836,-3.021,-51.952,-2.887,-52.003,-2.765,-51.989,-2.655,-52.039,-2.489,-52.092,-2.386,-52.153,-2.267,-52.221,-2.041,-52.251,-1.746,-52.231,-1.683,-52.197,-1.64]]},{"n":"Xun","z":4,"b":[110.089,23.397,111.301,23.555],"l":[[110.089,23.408,110.201,23.545,110.334,23.555,110.622,23.45,110.745,23.466,110.814,23.447,110.868,23.397,110.997,23.399,111.301,23.481]]},{"n":"Yadkin","z":5,"b":[-81.576,36.047,-80.464,36.259],"l":[[-81.576,36.074,-81.522,36.047,-81.465,36.055,-81.349,36.106,-80.899,36.225,-80.674,36.259,-80.49,36.242,-80.464,36.232]]},{"n":"Yamuna","z":5,"b":[77.074,25.289,81.903,30.915],"l":[[78.34,30.915,78.072,30.738,77.987,30.568,77.942,30.518,77.91,30.532,77.815,30.511,77.655,30.456,77.574,30.391,77.57,30.278,77.528,30.203,77.443,30.127,77.301,30.027,77.187,29.871,77.101,29.661,77.074,29.538,77.105,29.502,77.107,29.469,77.094,29.45,77.093,29.427,77.121,29.401,77.145,29.34,77.137,29.191,77.156,29.066,77.199,28.965,77.221,28.858,77.222,28.745,77.249,28.647,77.327,28.52,77.455,28.425,77.489,28.374,77.474,28.335,77.484,28.282,77.521,28.215,77.521,28.147,77.486,28.08,77.496,27.985,77.552,27.86,77.607,27.797,77.662,27.797,77.681,27.749,77.664,27.653,77.668,27.601,77.693,27.592,77.7,27.57,77.69,27.533,77.711,27.467,77.765,27.371,77.821,27.305,77.88,27.269,77.92,27.263,77.942,27.286,77.966,27.29,77.994,27.274,78.012,27.245,78.02,27.202,78.068,27.183,78.156,27.188,78.203,27.165,78.21,27.113,78.249,27.096,78.292,27.106,78.309,27.095,78.313,27.079,78.327,27.062,78.383,27.058,78.437,27.024,78.488,26.961,78.508,26.953,78.522,26.96,78.544,26.958,78.609,26.91,78.663,26.894,78.707,26.911,78.779,26.88,78.879,26.801,78.948,26.762,78.986,26.762,79.024,26.721,79.064,26.639,79.136,26.58,79.24,26.542,79.263,26.511,79.262,26.485,79.251,26.472,79.266,26.446,79.28,26.433,79.311,26.428,79.402,26.436,79.473,26.416,79.494,26.372,79.518,26.359,79.536,26.355,79.545,26.335,79.538,26.306,79.592,26.244,79.712,26.156,79.804,26.108,79.868,26.099,79.912,26.112,79.932,26.125,79.947,26.125,79.956,26.095,79.989,26.072,80.05,26.071,80.08,26.053,80.08,26.017,80.113,25.979,80.211,25.92,80.442,25.889,80.524,25.87,80.535,25.852,80.523,25.835,80.489,25.819,80.524,25.781,80.63,25.723,80.717,25.702,80.784,25.72,80.844,25.717,80.897,25.696,80.93,25.668,80.944,25.632,80.969,25.617,81.005,25.623,81.022,25.605,81.019,25.563,81.059,25.531,81.142,25.507,81.181,25.466,81.176,25.407,81.222,25.367,81.318,25.345,81.377,25.319,81.399,25.289,81.472,25.295,81.598,25.337,81.694,25.351,81.762,25.337,81.804,25.352,81.82,25.395,81.847,25.416,81.903,25.415]]},{"n":"Yangtze","z":2,"b":[113.128,29.463,120.074,32.235],"l":[[113.128,29.463,113.166,29.484,113.21,29.538,113.53,29.832,113.696,29.949,113.76,29.939,113.86,29.972,113.995,30.05,114.011,30.124,113.909,30.196,113.885,30.246,113.94,30.276,113.99,30.279,114.032,30.253,114.116,30.328,114.288,30.572,114.303,30.593,114.396,30.664,114.472,30.661,114.558,30.603,114.662,30.582,114.784,30.598,114.841,30.565,114.833,30.482,114.885,30.423,114.997,30.387,115.058,30.336,115.067,30.269,115.102,30.229,115.16,30.216,115.252,30.128,115.376,29.966,115.504,29.879,115.638,29.867,115.751,29.828,115.843,29.764,115.983,29.746,116.171,29.775,116.212,29.785],[116.198,29.751,116.212,29.785],[116.212,29.785,116.327,29.815,116.451,29.868,116.544,29.931,116.606,30.006,116.683,30.061,116.776,30.097,116.847,30.168,116.896,30.272,116.913,30.349,116.9,30.399,116.972,30.446,117.13,30.491,117.212,30.552,117.217,30.629,117.247,30.674,117.327,30.695,117.605,30.776,117.725,30.886,117.78,31.052,117.844,31.124,117.916,31.104,117.968,31.13,117.999,31.202,118.089,31.255,118.24,31.29,118.312,31.341,118.305,31.406,118.333,31.51,118.395,31.653,118.515,31.841,118.692,32.074,118.961,32.203,119.324,32.227,119.53,32.229,119.643,32.235,119.771,32.207,119.867,32.1,119.941,32.04,120.074,31.96]]},{"n":"Yarkant","z":5,"b":[75.941,35.521,80.888,40.467],"l":[[77.525,35.521,77.638,35.582,77.683,35.631,77.708,35.715,77.585,35.857,77.597,35.971,77.704,36.105,77.701,36.196,77.588,36.243,77.511,36.308,77.471,36.389,77.358,36.437,77.172,36.452,77.017,36.44,76.891,36.402,76.783,36.388,76.69,36.396,76.52,36.43,76.464,36.468,76.435,36.524,76.341,36.586,76.182,36.655,76.058,36.756,75.969,36.889,75.947,37.011,75.992,37.122,75.988,37.212,75.941,37.272,75.952,37.293,75.97,37.303,76.006,37.296,76.056,37.286,76.142,37.311,76.214,37.382,76.229,37.523,76.188,37.732,76.216,37.875,76.314,37.95,76.402,37.989,76.479,37.993,76.523,37.978,76.533,37.947,76.597,37.919,76.716,37.895,76.927,37.997,77.23,38.225,77.368,38.4,77.343,38.522,77.39,38.655,77.512,38.799,77.568,38.905,77.56,38.973,77.579,39.04,77.625,39.106,77.67,39.141,77.713,39.146,77.802,39.204,77.937,39.316,78.121,39.425,78.354,39.531,78.607,39.597,78.882,39.622,79.101,39.66,79.265,39.713,79.597,39.862,80.097,40.108,80.377,40.274,80.437,40.361,80.572,40.42,80.888,40.467]]},{"n":"Brahmaputra","z":2.1,"b":[87.66,29.013,94.356,29.381],"l":[[87.66,29.152,87.747,29.288,87.823,29.349,87.917,29.38,88.015,29.381,88.118,29.352,88.182,29.347,88.208,29.368,88.257,29.366,88.327,29.343,88.43,29.336,88.631,29.351,89.404,29.366,89.699,29.359,89.774,29.335,89.862,29.334,89.963,29.356,90.082,29.36,90.218,29.344,90.321,29.315,90.392,29.274,90.506,29.278,90.662,29.329,90.746,29.34,90.759,29.31,90.786,29.308,90.828,29.332,90.897,29.337,91.038,29.317,92.076,29.281,92.465,29.231,92.551,29.155,92.695,29.101,92.895,29.069,93.023,29.073,93.078,29.11,93.117,29.107,93.138,29.061,93.183,29.029,93.25,29.013,93.331,29.05,93.427,29.14,93.568,29.178,93.755,29.163,93.932,29.171,94.097,29.202,94.224,29.24,94.356,29.31]]},{"n":"Yenisey","z":4,"b":[83.11,58.1,92.998,70.11],"l":[[83.11,70.11,83.307,70.049,83.464,69.976,83.551,69.891,83.791,69.79,84.183,69.67,84.487,69.647,84.702,69.719,84.906,69.766,85.098,69.788,85.413,69.721,85.85,69.567,85.953,69.49,85.963,69.382,85.992,69.294,86.025,69.187,86.051,69.119,86.012,69.031,85.955,68.933,85.992,68.841,86.193,68.74,86.248,68.7,86.287,68.666,86.33,68.599,86.353,68.529,86.345,68.456,86.401,68.381,86.524,68.302,86.536,68.214,86.437,68.114,86.436,68.016,86.48,67.928,86.451,67.84,86.347,67.748,86.286,67.66,86.288,67.573,86.353,67.488,86.447,67.437,86.568,67.42,86.618,67.359,86.598,67.255,86.618,67.184,86.679,67.146,86.698,67.093,86.677,67.025,86.688,66.942,86.73,66.845,86.63,66.747,86.388,66.647,86.286,66.585,86.326,66.558,86.477,66.564,86.741,66.601,87.032,66.557,87.35,66.431,87.458,66.387,87.505,66.348,87.514,66.32,87.479,66.289,87.454,66.273,87.316,66.225,87.275,66.19,87.312,66.163,87.474,66.135,87.762,66.105,87.914,66.062,87.93,66.004,87.896,65.962,87.81,65.936,87.765,65.91,87.763,65.884,87.772,65.854,87.793,65.818,87.85,65.791,87.944,65.773,88.003,65.74,88.029,65.694,87.98,65.457,87.848,64.999,87.822,64.878,87.795,64.817,87.768,64.787,87.7,64.737,87.631,64.679,87.564,64.595,87.54,64.516,87.544,64.442,87.577,64.373,87.579,64.271,87.55,64.137,87.558,64.039,87.602,63.975,87.551,63.886,87.405,63.77,87.347,63.678,87.377,63.607,87.36,63.528,87.293,63.439,87.266,63.382,87.277,63.357,87.442,63.299,87.76,63.207,88.024,63.093,88.34,62.888,88.434,62.854,88.48,62.803,88.51,62.724,88.595,62.635,88.736,62.535,88.857,62.472,88.961,62.443,89.014,62.388,89.016,62.307,89.083,62.179,89.212,62.004,89.325,61.894,89.47,61.827,89.706,61.751,89.786,61.688,89.789,61.613,89.843,61.586,89.95,61.606,90.052,61.601,90.15,61.573,90.174,61.537,90.124,61.494,90.001,61.45,89.806,61.405,89.685,61.337,89.637,61.244,89.659,61.11,89.751,60.935,89.847,60.815,89.947,60.751,90.006,60.657,90.025,60.535,90.056,60.459,90.12,60.417,90.166,60.381,90.172,60.307,90.309,60.238,90.495,60.115,90.698,59.927,90.783,59.782,90.804,59.745,90.858,59.707,90.884,59.657,90.882,59.597,90.99,59.498,91.207,59.361,91.325,59.267,91.345,59.217,91.431,59.163,91.582,59.106,91.678,59.051,91.719,58.997,91.733,58.953,91.718,58.919,91.769,58.901,91.885,58.898,91.977,58.876,92.045,58.835,92.046,58.795,91.982,58.757,91.982,58.727,92.048,58.707,92.084,58.649,92.091,58.555,92.139,58.498,92.228,58.48,92.275,58.451,92.282,58.412,92.32,58.38,92.39,58.357,92.428,58.326,92.435,58.287,92.473,58.245,92.539,58.199,92.679,58.157,92.998,58.1]]},{"n":"Yuan","z":5,"b":[107.501,26.164,113.128,29.463],"l":[[107.505,26.338,107.501,26.239,107.519,26.191,107.558,26.164,107.61,26.167,107.708,26.218,107.78,26.358,107.8,26.432,107.793,26.485,107.826,26.53,107.9,26.564,107.959,26.617,108.005,26.688,108.072,26.753,108.159,26.812,108.251,26.839,108.348,26.835,108.396,26.817,108.396,26.785,108.453,26.725,108.568,26.636,108.645,26.618,108.683,26.67,108.718,26.696,108.748,26.695,108.762,26.679,108.76,26.649,108.795,26.614,108.869,26.575,108.961,26.573,109.073,26.608,109.194,26.679,109.39,26.838,109.438,26.894,109.444,26.954,109.424,27.038,109.441,27.08,109.494,27.079,109.556,27.107,109.628,27.165,109.667,27.177,109.675,27.144,109.692,27.128,109.719,27.131,109.74,27.152,109.755,27.19,109.823,27.189,109.943,27.147,110.051,27.19,110.217,27.423,110.264,27.504,110.284,27.566,110.277,27.611,110.299,27.653,110.351,27.694,110.367,27.729,110.347,27.758,110.348,27.791,110.371,27.828,110.347,27.863,110.275,27.896,110.251,27.927,110.275,27.955,110.252,27.981,110.182,28.005,110.135,28.036,110.113,28.076,110.128,28.128,110.179,28.193,110.195,28.242,110.175,28.275,110.182,28.289,110.216,28.286,110.272,28.32,110.351,28.393,110.419,28.486,110.477,28.602,110.633,28.69,110.887,28.751,111.04,28.803,111.094,28.845,111.149,28.838,111.207,28.781,111.279,28.762,111.405,28.791,111.522,28.986,111.559,29.005,111.593,28.99,111.626,28.996,111.655,29.013,111.679,29.013,111.705,28.992,111.744,28.988,111.796,29.01,111.865,29.012,111.993,28.985],[112.905,29.031,112.994,29.147],[113.069,29.388,113.128,29.463]]},{"n":"Yukon","z":3,"b":[-165.244,61.269,-134.87,66.611],"l":[[-134.916,61.568,-134.87,61.628,-134.885,61.685,-134.946,61.759,-134.964,61.816,-134.941,61.856,-134.94,61.889,-134.961,61.912,-135.121,61.957,-135.419,62.024,-135.629,62.049,-135.75,62.032,-135.928,62.046,-136.307,62.121,-136.362,62.145,-136.378,62.197,-136.354,62.275,-136.583,62.428,-137.356,62.781,-137.452,62.809,-137.733,62.82,-138.398,62.801,-139.236,62.97,-139.418,63.011,-139.532,63.071,-139.564,63.161,-139.545,63.234,-139.472,63.288,-139.506,63.353,-139.646,63.427,-139.73,63.513,-139.76,63.609,-139.759,63.674,-139.728,63.707,-139.723,63.75,-139.744,63.805,-139.743,63.865,-139.721,63.932,-139.653,63.983,-139.541,64.017,-139.482,64.057,-139.476,64.102,-139.511,64.158,-139.587,64.227,-139.849,64.297,-140.296,64.367,-140.453,64.405,-140.477,64.426,-140.47,64.453,-140.465,64.498,-140.479,64.526,-140.545,64.518,-140.589,64.528,-140.61,64.558,-140.649,64.572,-140.705,64.57,-140.73,64.585,-140.724,64.616,-140.746,64.627,-140.796,64.62,-140.882,64.641,-140.933,64.674,-141.006,64.689,-141.065,64.726,-141.059,64.749,-141.093,64.775,-141.164,64.802,-141.176,64.833,-141.128,64.867,-141.129,64.888,-141.179,64.895,-141.198,64.913,-141.187,64.943,-141.23,64.954,-141.327,64.945,-141.373,64.966,-141.369,65.041,-141.404,65.086,-141.533,65.129,-141.768,65.183,-141.924,65.241,-142.002,65.3,-142.147,65.335,-142.359,65.344,-142.502,65.363,-142.576,65.392,-142.655,65.393,-142.738,65.366,-142.929,65.355,-143.227,65.359,-143.447,65.383,-143.589,65.427,-143.673,65.472,-143.698,65.518,-143.781,65.554,-143.922,65.579,-144.023,65.613,-144.085,65.655,-144.087,65.722,-144.03,65.812,-144.017,65.876,-144.048,65.914,-144.251,66.027,-144.624,66.215,-144.832,66.335,-144.874,66.386,-145.003,66.453,-145.219,66.537,-145.453,66.587,-145.828,66.611],[-163.259,62.016,-163.241,61.94,-163.242,61.886,-163.289,61.847,-163.388,61.829,-163.539,61.832,-163.644,61.816,-163.705,61.783,-163.774,61.777,-163.836,61.797,-163.845,61.835,-163.86,61.851,-163.898,61.853,-163.959,61.839,-164.002,61.851,-164.026,61.891,-164.087,61.879,-164.183,61.816,-164.264,61.789,-164.331,61.798,-164.429,61.782,-164.56,61.742,-164.646,61.744,-164.746,61.762,-164.831,61.739,-164.879,61.698,-164.869,61.665,-164.766,61.629,-164.743,61.591,-164.778,61.551,-164.888,61.507,-165.007,61.48,-165.091,61.441,-165.188,61.392,-165.206,61.33,-165.201,61.286,-165.244,61.269],[-163.259,62.016,-163.434,62.061,-163.731,62.092,-163.904,62.134,-163.953,62.184,-163.948,62.248,-163.889,62.325,-163.859,62.392,-163.861,62.448,-163.93,62.532,-164.067,62.643,-164.222,62.71,-164.482,62.745,-164.522,62.737,-164.589,62.709],[-145.828,66.611,-146.664,66.508,-147.015,66.445,-147.159,66.386,-147.323,66.352,-147.509,66.344,-147.596,66.321,-147.585,66.284,-147.746,66.264,-148.079,66.261,-148.364,66.225,-148.601,66.155,-148.729,66.104,-148.764,66.067,-148.79,66.062,-148.812,66.085,-148.873,66.09,-148.971,66.077,-149.014,66.056,-149,66.029,-149.054,66.007,-149.175,65.991,-149.251,65.957,-149.284,65.905,-149.438,65.889,-149.714,65.91,-149.92,65.89,-150.058,65.83,-150.048,65.798,-149.89,65.795,-149.827,65.755,-149.858,65.679,-149.93,65.65,-150.041,65.669,-150.125,65.641,-150.183,65.565,-150.324,65.52,-150.549,65.505,-150.792,65.446,-151.054,65.343,-151.401,65.26,-152.048,65.165,-152.349,65.124,-152.534,65.134,-152.699,65.182,-152.857,65.201,-153.007,65.191,-153.126,65.166,-153.215,65.124,-153.331,65.112,-153.476,65.129,-153.74,65.084,-154.122,64.976,-154.402,64.927,-154.581,64.936,-154.848,64.902,-155.134,64.867,-155.381,64.79,-155.575,64.756,-155.754,64.764,-155.998,64.721,-156.202,64.713,-156.425,64.68,-156.733,64.664,-156.76,64.696,-156.809,64.718,-156.917,64.73,-156.993,64.755,-157.036,64.795,-157.271,64.826,-157.396,64.82,-157.51,64.844,-157.769,64.85,-157.842,64.869,-157.913,64.858,-158.003,64.801,-158.169,64.681,-158.286,64.643,-158.501,64.464,-158.678,64.343,-158.691,64.311,-158.659,64.223,-158.674,64.138,-158.734,64.058,-158.826,63.994,-159.01,63.925,-159.149,63.884,-159.203,63.848,-159.217,63.802,-159.256,63.767,-159.318,63.743,-159.386,63.679,-159.458,63.575,-159.479,63.494,-159.448,63.437,-159.502,63.372,-159.642,63.301,-159.725,63.214,-159.753,63.111,-159.826,63.035,-159.945,62.987,-160.019,62.916,-160.049,62.822,-160.09,62.756,-160.144,62.717,-160.17,62.66,-160.17,62.586,-160.133,62.523,-160.058,62.471,-160.006,62.415,-159.975,62.371,-159.963,62.321,-159.956,62.28,-159.913,62.253,-159.805,62.236,-159.744,62.214,-159.73,62.188,-159.742,62.154,-159.782,62.114,-159.852,62.086,-159.952,62.071,-160.032,62.033,-160.093,61.973,-160.336,61.941,-160.762,61.936,-161.067,61.907,-161.253,61.853,-161.339,61.805,-161.326,61.764,-161.346,61.72,-161.401,61.673,-161.528,61.637,-161.754,61.617,-161.826,61.601,-161.92,61.576,-161.956,61.597,-161.963,61.656,-162.023,61.745,-162.135,61.863,-162.249,61.923,-162.367,61.924,-162.44,61.948,-162.47,61.994,-162.527,62.006,-162.613,61.984,-162.668,61.957,-162.691,61.923,-162.74,61.918,-162.814,61.942,-162.888,61.94,-162.962,61.913,-163.043,61.908,-163.132,61.923,-163.204,61.959,-163.259,62.016],[-145.828,66.611,-145.833,66.611],[-145.825,66.611,-145.828,66.611]]},{"n":"Za","z":2.1,"b":[94.084,31.966,96.941,33.205],"l":[[94.084,33.156,94.448,33.163,94.507,33.204,94.61,33.205,94.777,33.181,94.916,33.14,95.026,33.083,95.089,33.025,95.106,32.966,95.225,32.915,95.447,32.87,95.581,32.815,95.627,32.748,95.728,32.672,95.882,32.587,96.011,32.552,96.113,32.567,96.183,32.562,96.239,32.526,96.381,32.433,96.455,32.343,96.507,32.225,96.585,32.155,96.69,32.133,96.77,32.085,96.824,32.012,96.874,31.973,96.941,31.966]]},{"n":"Zambezi","z":3,"b":[22.544,-18.842,36.498,-11.101],"l":[[36.039,-18.337,36.105,-18.424,36.14,-18.536,36.145,-18.62,36.12,-18.677,36.129,-18.72,36.15,-18.75,36.153,-18.795,36.125,-18.842],[36.039,-18.337,36.498,-18.576],[28.761,-16.532,28.833,-16.424,28.857,-16.306,28.857,-16.142,28.876,-16.036,28.913,-15.988,28.973,-15.95,29.051,-15.901,29.288,-15.777,29.487,-15.697,29.73,-15.645,30.796,-15.642],[32.707,-15.58,32.925,-15.586,33.028,-15.634,33.089,-15.725,33.203,-15.835,33.372,-15.963,33.564,-16.147,33.885,-16.508,33.984,-16.599,34.205,-16.697,34.582,-16.834,34.795,-16.923,34.844,-16.964,34.897,-17.059,34.981,-17.28,35.096,-17.521,35.187,-17.636,35.291,-17.705,35.389,-17.8,35.48,-17.92,35.602,-18.013,35.755,-18.08,35.846,-18.146,35.877,-18.211,35.941,-18.275,36.039,-18.337],[24.266,-11.379,24.327,-11.311,24.334,-11.239,24.308,-11.141,24.226,-11.101,24.022,-11.126,23.75,-11.332,23.599,-11.423,23.477,-11.467,23.387,-11.521,23.33,-11.586,23.173,-11.686,22.915,-11.823,22.76,-11.952,22.707,-12.074,22.65,-12.166,22.588,-12.227,22.553,-12.346,22.544,-12.522,22.581,-12.69,22.662,-12.849,22.698,-12.965,22.687,-13.038,22.753,-13.132,22.964,-13.305,23.023,-13.355,23.074,-13.458,23.137,-13.63,23.186,-13.726,23.221,-13.747,23.228,-13.785,23.207,-13.84,23.207,-13.909,23.228,-13.993,23.225,-14.066,23.197,-14.131,23.195,-14.216,23.218,-14.324,23.189,-14.443,23.107,-14.573,23.034,-14.784,22.969,-15.077,22.982,-15.278,23.073,-15.387,23.125,-15.482,23.136,-15.564,23.127,-15.625,23.099,-15.666,23.12,-15.737,23.189,-15.837,23.246,-16.013,23.292,-16.264,23.492,-16.549,23.846,-16.868,24.077,-17.14,24.238,-17.478,24.733,-17.518,24.932,-17.543,25.002,-17.569,25.092,-17.634,25.259,-17.794,25.452,-17.845,25.557,-17.85,25.64,-17.824,25.742,-17.858,25.863,-17.952,25.996,-17.97,26.14,-17.912,26.333,-17.929,26.578,-18.023,26.78,-18.042,27.021,-17.958]]},{"n":"","z":5,"b":[170.001,-44.361,170.257,-44.286],"l":[[170.001,-44.286,170.11,-44.291,170.257,-44.361]]},{"n":"","z":5,"b":[170.204,-44.32,170.279,-44.171],"l":[[170.204,-44.171,170.279,-44.32]]},{"n":"","z":5,"b":[-106.018,58.455,-103.55,59.271],"l":[[-103.55,58.455,-103.665,58.545],[-103.786,58.691,-103.959,58.772,-103.999,58.809,-103.962,58.829,-103.95,58.868,-103.963,58.927,-104.144,58.979,-104.495,59.023,-104.792,59.037,-104.92,59.08,-104.951,59.142,-105.012,59.173],[-105.55,59.15,-105.572,59.221,-105.689,59.251,-106.018,59.271]]},{"n":"","z":5,"b":[-138.515,59.166,-137,61.227],"l":[[-137,61.227,-137.061,60.991,-137.069,60.952,-137.09,60.91,-137.177,60.874,-137.348,60.824,-137.511,60.797,-137.664,60.794,-137.768,60.757,-137.822,60.687,-137.845,60.607,-137.836,60.517,-137.878,60.412,-137.972,60.293,-137.997,60.231,-137.967,60.178,-137.863,60.067,-137.851,60.027,-137.863,59.984,-137.966,59.927,-137.991,59.881,-137.936,59.846,-137.929,59.811,-137.969,59.776,-137.972,59.738,-137.92,59.678,-137.83,59.587,-137.817,59.544,-137.824,59.51,-137.968,59.461,-137.987,59.452,-138.021,59.439,-138.061,59.437,-138.184,59.401,-138.233,59.372,-138.26,59.32,-138.25,59.291,-138.229,59.275,-138.209,59.266,-138.198,59.252,-138.185,59.224,-138.182,59.202,-138.189,59.19,-138.202,59.18,-138.221,59.174,-138.239,59.176,-138.438,59.183,-138.482,59.177,-138.515,59.166]]},{"n":"","z":5,"b":[-125.619,64.895,-123.472,65.152],"l":[[-123.472,65.152,-123.737,65.062,-124.205,65.026,-124.966,65.014,-125.41,64.982,-125.619,64.895]]},{"n":"","z":5,"b":[168.633,-46.566,168.957,-45.327],"l":[[168.775,-45.327,168.724,-45.461,168.683,-45.516,168.633,-45.537,168.637,-45.57,168.692,-45.614,168.712,-45.669,168.694,-45.736,168.701,-45.789,168.747,-45.847,168.922,-46.028,168.957,-46.107,168.929,-46.209,168.799,-46.448,168.767,-46.566]]},{"n":"","z":4.7,"b":[-73.177,46.044,-72.991,46.177],"l":[[-72.991,46.177,-73.079,46.16,-73.146,46.124,-73.177,46.071,-73.164,46.044]]},{"n":"","z":4.7,"b":[-103.759,60.952,-95.492,64.05],"l":[[-103.447,61.366,-103.468,61.283,-103.55,61.212,-103.699,61.126,-103.759,61.05,-103.716,60.952],[-103.158,61.836,-103.236,61.773,-103.251,61.734,-103.22,61.681],[-99.618,62.301,-98.574,62.252,-98.209,62.252,-98.175,62.285,-98.206,62.38,-98.35,62.616],[-101.366,62.35,-101.318,62.345],[-100.735,62.356,-100.552,62.355],[-102.737,62.41,-102.919,62.319],[-101.831,62.52,-101.529,62.664],[-102.035,62.925,-102.118,62.915,-102.181,62.884,-102.25,62.828,-102.379,62.79,-102.66,62.76],[-97.63,62.979,-97.61,63.047,-97.567,63.092,-97.459,63.157],[-97.288,63.514,-97.235,63.573,-97.182,63.593,-97.111,63.593,-97.084,63.604,-97.071,63.635,-97.014,63.64,-96.952,63.637],[-95.878,63.656,-95.811,63.669,-95.799,63.694,-95.809,63.719,-95.799,63.746,-95.723,63.766,-95.677,63.798,-95.677,63.846,-95.631,63.898,-95.537,63.956,-95.492,64.001,-95.497,64.05]]},{"n":"","z":4.7,"b":[105.931,9.949,106.658,10.33],"l":[[106.464,10.298,106.385,10.33,106.138,10.306],[106.138,10.306,105.931,10.284],[106.138,10.306,106.184,10.253,106.222,10.245,106.292,10.262,106.33,10.247,106.36,10.211,106.408,10.117,106.468,10.05,106.519,10.015,106.579,9.979,106.658,9.949]]},{"n":"","z":4.7,"b":[-101.318,62.345,-101.163,62.367],"l":[[-101.163,62.367,-101.318,62.345]]},{"n":"Garonne","z":5,"b":[-0.567,43.415,1.454,45.001],"l":[[1.454,43.415,1.453,43.417,1.409,43.514,1.409,43.588,1.361,43.689,1.22,43.881,1.082,44.031,0.861,44.126,0.509,44.215,0.323,44.295,0.304,44.366,0.255,44.422,0.137,44.484,0.003,44.561,-0.102,44.587,-0.224,44.588,-0.343,44.636,-0.534,44.796,-0.562,44.83,-0.567,44.86,-0.542,44.898,-0.536,44.952,-0.548,45.001]]},{"n":"Loire","z":4.7,"b":[-1.743,44.775,4.256,47.894],"l":[[3.083,46.985,3.022,47.149,2.916,47.287,2.893,47.363,2.917,47.423,2.883,47.499,2.789,47.59,2.614,47.691,2.36,47.804,2.145,47.871,1.971,47.894,1.845,47.893,1.769,47.866,1.608,47.764,1.364,47.587,1.158,47.474,0.99,47.425,0.815,47.396,0.635,47.386,0.428,47.341,0.207,47.244,0.075,47.218,-0.038,47.249,-0.216,47.364,-0.418,47.402,-0.731,47.371,-1.056,47.375,-1.249,47.336,-1.417,47.258,-1.559,47.216,-1.633,47.202,-1.743,47.216],[3.083,46.985,3.168,46.977,3.249,46.952,3.329,46.846,3.49,46.829,3.625,46.74,3.724,46.639,3.749,46.558,3.86,46.507,3.984,46.46,3.99,46.379,4.039,46.238,4.101,46.166,4.101,46.063,4.033,45.977,4.052,45.925,4.157,45.852,4.213,45.745,4.219,45.628,4.256,45.477,4.2,45.368,4.132,45.307,4.083,45.238,3.953,45.199,3.91,45.116,3.922,45.037,3.904,44.972,3.928,44.871,4.07,44.81,4.169,44.775,4.2,44.823]]}]} \ No newline at end of file diff --git a/src/maps/data/states_50m.json b/src/maps/data/states_50m.json new file mode 100644 index 0000000..64dcd18 --- /dev/null +++ b/src/maps/data/states_50m.json @@ -0,0 +1 @@ +{"f":[{"z":3,"b":[-53.935,-29.356,-49.699,-27.081],"l":[-53.935,-27.161,-53.852,-27.156,-53.8,-27.165,-53.719,-27.158,-53.671,-27.169,-53.66,-27.201,-53.616,-27.21,-53.54,-27.198,-53.498,-27.172,-53.49,-27.133,-53.457,-27.103,-53.398,-27.082,-53.347,-27.081,-53.305,-27.098,-53.263,-27.163,-53.227,-27.174,-53.184,-27.149,-53.134,-27.14,-53.104,-27.154,-53.071,-27.142,-53.03,-27.138,-52.994,-27.163,-52.953,-27.179,-52.9,-27.177,-52.826,-27.189,-52.742,-27.246,-52.636,-27.259,-52.511,-27.23,-52.415,-27.236,-52.348,-27.276,-52.304,-27.283,-52.265,-27.277,-52.229,-27.293,-52.192,-27.281,-52.139,-27.283,-52.06,-27.313,-51.984,-27.377,-51.913,-27.473,-51.873,-27.509,-51.754,-27.512,-51.702,-27.488,-51.645,-27.5,-51.582,-27.547,-51.53,-27.569,-51.488,-27.564,-51.406,-27.613,-51.287,-27.716,-51.19,-27.781,-51.117,-27.808,-50.966,-27.959,-50.736,-28.236,-50.578,-28.386,-50.447,-28.422,-50.233,-28.469,-50.155,-28.474,-50.141,-28.453,-50.069,-28.444,-49.939,-28.448,-49.839,-28.465,-49.737,-28.511,-49.708,-28.53,-49.699,-28.597,-49.797,-28.624,-49.846,-28.695,-49.954,-28.794,-49.945,-28.95,-50.007,-29.102,-50.07,-29.093,-50.164,-29.195,-50.168,-29.285,-50.052,-29.356,-50.034,-29.32,-50.079,-29.244,-49.954,-29.195,-49.869,-29.218,-49.815,-29.271,-49.713,-29.325]},{"z":3,"b":[-38.221,-9.356,-35.15,-8.834],"l":[-38.221,-9.348,-38.155,-9.267,-38.092,-9.186,-37.967,-9.142,-37.815,-8.977,-37.806,-8.896,-37.748,-8.861,-37.681,-8.977,-37.628,-8.981,-37.521,-8.945,-37.471,-9.003,-37.391,-9.044,-37.181,-9.24,-36.998,-9.307,-36.94,-9.356,-36.905,-9.289,-36.833,-9.262,-36.579,-9.294,-36.44,-9.209,-36.346,-9.2,-36.266,-9.142,-36.239,-9.088,-36.123,-9.017,-36.119,-8.968,-36.016,-8.896,-35.971,-8.905,-35.882,-8.874,-35.802,-8.869,-35.721,-8.919,-35.61,-8.865,-35.471,-8.834,-35.391,-8.883,-35.302,-8.887,-35.15,-8.914]},{"z":3,"b":[-40.538,-7.856,-38.699,-7.347],"l":[-38.699,-7.619,-38.82,-7.664,-38.864,-7.704,-38.967,-7.847,-38.998,-7.82,-39.074,-7.856,-39.11,-7.753,-39.244,-7.682,-39.306,-7.624,-39.351,-7.548,-39.534,-7.477,-39.655,-7.374,-39.846,-7.347,-39.954,-7.361,-40.065,-7.405,-40.172,-7.419,-40.262,-7.392,-40.538,-7.392]},{"z":3,"b":[-38.699,-8.285,-34.81,-7.271],"l":[-34.81,-7.567,-34.98,-7.512,-35.065,-7.41,-35.271,-7.383,-35.373,-7.459,-35.498,-7.454,-35.556,-7.655,-35.681,-7.713,-35.713,-7.709,-35.837,-7.744,-35.887,-7.732,-35.931,-7.838,-35.967,-7.816,-36.074,-7.825,-36.123,-7.78,-36.168,-7.825,-36.217,-7.78,-36.266,-7.825,-36.338,-7.811,-36.422,-7.825,-36.449,-7.91,-36.57,-7.923,-36.659,-8.012,-36.628,-8.084,-36.784,-8.222,-36.833,-8.227,-36.963,-8.285,-37.061,-8.231,-37.128,-8.164,-37.15,-7.977,-37.213,-7.959,-37.342,-7.999,-37.226,-7.816,-37.168,-7.762,-37.213,-7.646,-37.199,-7.575,-37.016,-7.508,-37.016,-7.401,-37.15,-7.347,-37.253,-7.271,-37.346,-7.298,-37.382,-7.352,-37.436,-7.347,-37.552,-7.477,-37.708,-7.548,-37.793,-7.637,-37.896,-7.686,-37.976,-7.771,-38.057,-7.758,-38.088,-7.82,-38.15,-7.771,-38.168,-7.807,-38.226,-7.811,-38.257,-7.852,-38.302,-7.829,-38.304,-7.782,-38.355,-7.7,-38.418,-7.749,-38.449,-7.736,-38.525,-7.767,-38.588,-7.74,-38.592,-7.691,-38.646,-7.677,-38.699,-7.619]},{"z":3,"b":[-41.369,-8.713,-40.53,-7.392],"l":[-40.538,-7.392,-40.646,-7.401,-40.69,-7.427,-40.69,-7.512,-40.619,-7.637,-40.659,-7.762,-40.547,-7.834,-40.53,-7.905,-40.543,-8.03,-40.592,-8.124,-40.748,-8.244,-40.833,-8.365,-40.887,-8.352,-40.922,-8.432,-41.016,-8.419,-41.083,-8.526,-41.159,-8.548,-41.213,-8.633,-41.369,-8.713]},{"z":3,"b":[-46.271,-14.869,-45.913,-12.798],"l":[-46.025,-14.869,-45.931,-14.749,-45.913,-14.691,-45.963,-14.535,-45.963,-14.468,-45.922,-14.352,-46.012,-14.298,-46.038,-14.253,-46.128,-14.182,-46.172,-14.075,-46.222,-13.999,-46.262,-13.869,-46.271,-13.655,-46.186,-13.405,-46.097,-13.352,-46.083,-13.253,-46.177,-13.218,-46.181,-13.16,-46.15,-13.026,-46.074,-12.977,-46.088,-12.914,-46.235,-12.798]},{"z":3,"b":[-48.28,-16.044,-47.302,-15.49],"l":[-47.302,-16.039,-47.855,-16.044,-48.181,-16.039,-48.253,-16.03,-48.235,-15.959,-48.28,-15.838,-48.204,-15.736,-48.239,-15.691,-48.181,-15.49,-47.766,-15.49,-47.422,-15.499,-47.413,-15.535,-47.306,-15.588,-47.306,-15.709,-47.351,-15.834,-47.36,-15.986,-47.302,-16.039]},{"z":3,"b":[-53.056,-19.511,-50.964,-18.017],"l":[-50.964,-19.511,-51.034,-19.369,-51.137,-19.285,-51.311,-19.253,-51.405,-19.169,-51.543,-19.137,-51.641,-19.128,-51.842,-19.048,-51.913,-18.99,-52.061,-18.945,-52.097,-18.896,-52.271,-18.811,-52.342,-18.816,-52.498,-18.704,-52.614,-18.722,-52.762,-18.709,-52.869,-18.682,-52.891,-18.637,-52.847,-18.535,-52.788,-18.463,-52.784,-18.392,-52.909,-18.347,-52.989,-18.387,-53.034,-18.356,-53.056,-18.294,-53.043,-18.097,-53.052,-18.017]},{"z":3,"b":[-50.965,-19.513,-46.025,-14.713],"l":[-46.025,-14.869,-46.079,-14.923,-46.19,-14.936,-46.297,-14.91,-46.315,-14.847,-46.387,-14.771,-46.516,-14.713,-46.583,-14.794,-46.556,-14.869,-46.579,-14.914,-46.547,-15.039,-46.637,-15.07,-46.735,-15.017,-46.833,-15.012,-46.891,-15.048,-46.931,-15.227,-46.891,-15.24,-46.855,-15.325,-46.936,-15.432,-46.931,-15.544,-46.869,-15.593,-46.833,-15.843,-46.864,-15.883,-47.079,-15.936,-47.128,-15.923,-47.23,-16.035,-47.302,-16.039,-47.338,-16.146,-47.324,-16.227,-47.351,-16.303,-47.431,-16.41,-47.454,-16.494,-47.405,-16.57,-47.266,-16.66,-47.159,-16.919,-47.15,-16.977,-47.23,-17.026,-47.213,-17.075,-47.311,-17.142,-47.422,-17.271,-47.454,-17.347,-47.498,-17.329,-47.53,-17.454,-47.458,-17.526,-47.409,-17.499,-47.288,-17.548,-47.271,-17.669,-47.329,-17.744,-47.355,-17.825,-47.347,-17.878,-47.28,-18.061,-47.319,-18.083,-47.447,-18.171,-47.786,-18.379,-47.929,-18.445,-48.018,-18.434,-48.227,-18.34,-48.954,-18.326,-49.108,-18.388,-49.179,-18.405,-49.247,-18.468,-49.248,-18.469,-49.248,-18.47,-49.249,-18.471,-49.25,-18.472,-49.251,-18.473,-49.252,-18.475,-49.254,-18.476,-49.255,-18.478,-49.257,-18.481,-49.258,-18.483,-49.26,-18.485,-49.262,-18.488,-49.264,-18.491,-49.267,-18.494,-49.269,-18.497,-49.271,-18.5,-49.274,-18.503,-49.276,-18.506,-49.279,-18.509,-49.281,-18.513,-49.284,-18.516,-49.287,-18.52,-49.289,-18.523,-49.292,-18.527,-49.295,-18.53,-49.297,-18.534,-49.3,-18.537,-49.303,-18.541,-49.305,-18.544,-49.308,-18.547,-49.31,-18.55,-49.313,-18.554,-49.315,-18.557,-49.317,-18.56,-49.32,-18.563,-49.322,-18.565,-49.324,-18.568,-49.326,-18.57,-49.327,-18.573,-49.329,-18.575,-49.33,-18.577,-49.332,-18.579,-49.333,-18.58,-49.334,-18.582,-49.335,-18.583,-49.336,-18.584,-49.337,-18.585,-49.417,-18.603,-49.488,-18.522,-49.553,-18.51,-49.612,-18.567,-49.678,-18.6,-49.789,-18.615,-49.96,-18.616,-50.094,-18.641,-50.247,-18.69,-50.368,-18.769,-50.458,-18.877,-50.499,-18.953,-50.49,-19.02,-50.532,-19.087,-50.578,-19.112,-50.642,-19.118,-50.713,-19.161,-50.792,-19.238,-50.844,-19.311,-50.869,-19.377,-50.871,-19.424,-50.864,-19.438,-50.881,-19.459,-50.924,-19.464,-50.964,-19.511,-50.965,-19.513]},{"z":3,"b":[-44.864,-23.294,-44.168,-22.405],"l":[-44.815,-23.294,-44.864,-23.204,-44.806,-23.137,-44.797,-22.99,-44.726,-22.936,-44.588,-22.878,-44.472,-22.852,-44.36,-22.856,-44.275,-22.82,-44.248,-22.749,-44.168,-22.7,-44.217,-22.593,-44.538,-22.619,-44.637,-22.602,-44.65,-22.561,-44.771,-22.423,-44.833,-22.405]},{"z":3,"b":[-46.57,-12.798,-45.757,-10.329],"l":[-46.235,-12.798,-46.235,-12.713,-46.159,-12.602,-46.159,-12.503,-46.315,-12.423,-46.347,-12.343,-46.338,-12.128,-46.324,-12.093,-46.248,-12.048,-46.213,-11.999,-46.079,-11.927,-46.101,-11.865,-46.159,-11.834,-46.248,-11.843,-46.275,-11.767,-46.244,-11.727,-46.105,-11.664,-46.083,-11.602,-46.19,-11.544,-46.427,-11.499,-46.489,-11.414,-46.552,-11.378,-46.57,-11.316,-46.53,-11.236,-46.463,-11.177,-46.369,-10.968,-46.248,-10.914,-46.235,-10.883,-46.302,-10.758,-46.088,-10.584,-46.038,-10.57,-45.86,-10.468,-45.757,-10.329]},{"z":3,"b":[-45.945,-10.329,-45.757,-10.316],"l":[-45.757,-10.329,-45.945,-10.316]},{"z":3,"b":[-50.485,-13.383,-46.235,-12.388],"l":[-50.485,-12.843,-50.392,-12.617,-50.296,-12.49,-50.164,-12.388,-50.137,-12.477,-50.204,-12.57,-50.199,-12.655,-50.275,-12.789,-50.271,-12.892,-50.239,-12.932,-50.052,-13.021,-49.972,-13.026,-49.913,-13.07,-49.686,-13.177,-49.521,-13.2,-49.382,-13.249,-49.36,-13.133,-49.297,-13.003,-49.137,-12.736,-49.016,-12.655,-48.998,-12.749,-48.927,-12.785,-48.824,-12.874,-48.797,-12.928,-48.802,-12.999,-48.766,-13.097,-48.762,-13.209,-48.775,-13.352,-48.739,-13.383,-48.686,-13.338,-48.655,-13.177,-48.588,-13.155,-48.48,-13.195,-48.369,-13.186,-48.226,-13.093,-48.164,-13.103,-48.175,-13.181,-48.074,-13.195,-48.003,-13.249,-47.936,-13.244,-47.855,-13.294,-47.681,-13.356,-47.659,-13.32,-47.722,-13.151,-47.641,-13.115,-47.556,-13.244,-47.44,-13.249,-47.293,-13.2,-47.092,-13.097,-46.972,-13.07,-46.829,-13.07,-46.753,-13.035,-46.646,-12.923,-46.592,-12.892,-46.436,-12.847,-46.302,-12.834,-46.235,-12.798]},{"z":3,"b":[-38.244,-11.557,-37.388,-9.512],"l":[-37.388,-11.497,-37.574,-11.539,-37.632,-11.521,-37.659,-11.557,-37.797,-11.526,-37.851,-11.441,-37.967,-11.401,-38.007,-11.356,-37.98,-11.24,-38.061,-11.169,-38.11,-11.03,-38.177,-10.981,-38.235,-10.896,-38.244,-10.825,-38.19,-10.718,-38.11,-10.709,-38.012,-10.753,-37.971,-10.753,-37.815,-10.677,-37.784,-10.628,-37.824,-10.548,-37.838,-10.423,-37.784,-10.307,-37.78,-10.079,-37.9,-9.95,-37.9,-9.914,-37.963,-9.874,-38.03,-9.727,-37.994,-9.646,-38.043,-9.606,-37.976,-9.512]},{"z":3,"b":[-53.663,-26.7,-48.581,-25.972],"l":[-48.581,-25.985,-48.65,-25.972,-48.918,-25.977,-48.945,-26.008,-49.11,-25.994,-49.213,-26.03,-49.297,-26.106,-49.454,-26.169,-49.489,-26.222,-49.597,-26.222,-49.722,-26.16,-49.753,-26.124,-49.882,-26.039,-49.949,-26.012,-50.19,-26.053,-50.315,-26.053,-50.369,-26.086,-50.458,-26.026,-50.543,-26.026,-50.646,-26.07,-50.735,-26.231,-50.771,-26.222,-50.9,-26.28,-50.945,-26.244,-51.074,-26.236,-51.248,-26.347,-51.288,-26.423,-51.28,-26.499,-51.239,-26.606,-51.284,-26.651,-51.422,-26.7,-51.503,-26.602,-51.873,-26.602,-52.007,-26.584,-52.199,-26.45,-52.458,-26.432,-52.543,-26.401,-52.641,-26.401,-52.672,-26.378,-52.815,-26.338,-52.913,-26.365,-52.994,-26.352,-53.123,-26.369,-53.28,-26.262,-53.355,-26.24,-53.458,-26.289,-53.516,-26.289,-53.663,-26.258]},{"z":3,"b":[-63.394,-1.449,-58.869,2.224],"l":[-63.394,2.224,-63.306,2.166,-63.15,2.18,-63.123,2.113,-63.003,2.019,-62.847,2.019,-62.708,1.948,-62.69,1.912,-62.73,1.831,-62.713,1.738,-62.789,1.604,-62.722,1.497,-62.623,1.416,-62.614,1.363,-62.516,1.059,-62.512,0.961,-62.445,0.805,-62.534,0.733,-62.539,0.675,-62.485,0.541,-62.489,0.488,-62.53,0.43,-62.534,0.314,-62.57,0.247,-62.565,0.175,-62.525,0.095,-62.579,0.019,-62.57,-0.048,-62.512,-0.124,-62.476,-0.222,-62.405,-0.271,-62.373,-0.343,-62.369,-0.468,-62.32,-0.521,-62.311,-0.642,-62.378,-0.718,-62.476,-0.695,-62.507,-0.771,-62.418,-0.829,-62.315,-0.945,-62.244,-0.981,-62.204,-1.044,-62.141,-1.066,-62.03,-1.146,-61.936,-1.244,-61.878,-1.369,-61.881,-1.389,-61.799,-1.389,-61.717,-1.401,-61.62,-1.449,-61.619,-1.276,-61.574,-1.142,-61.561,-1.021,-61.579,-0.945,-61.534,-0.727,-61.458,-0.637,-61.244,-0.548,-61.222,-0.499,-61.105,-0.494,-61.061,-0.53,-60.931,-0.557,-60.905,-0.611,-60.815,-0.686,-60.735,-0.852,-60.641,-0.861,-60.516,-0.829,-60.467,-0.74,-60.338,-0.704,-60.306,-0.673,-60.315,-0.619,-60.391,-0.521,-60.387,-0.463,-60.315,-0.307,-60.311,-0.236,-60.253,-0.151,-60.217,-0.044,-60.168,0.006,-60.128,0.131,-60.025,0.224,-59.771,0.229,-58.869,0.224]},{"z":3,"b":[-58.974,0.224,-58.869,1.386],"l":[-58.974,1.386,-58.869,0.224]},{"z":3,"b":[-73.797,-9.838,-66.828,-7.117],"l":[-73.797,-7.117,-72.664,-7.593,-70.373,-8.155,-69.806,-8.454,-66.829,-9.838,-66.828,-9.838]},{"z":3,"b":[-61.61,-13.663,-59.824,-8.767],"l":[-61.61,-8.767,-61.507,-8.847,-61.485,-8.914,-61.53,-8.999,-61.556,-9.128,-61.53,-9.227,-61.592,-9.24,-61.61,-9.32,-61.547,-9.41,-61.552,-9.463,-61.48,-9.637,-61.521,-9.704,-61.565,-9.727,-61.521,-9.861,-61.534,-9.994,-61.57,-10.061,-61.565,-10.262,-61.472,-10.436,-61.503,-10.686,-61.472,-10.758,-61.521,-10.789,-61.507,-10.878,-61.521,-10.954,-61.472,-10.999,-60.44,-11.003,-60.44,-11.039,-60.382,-11.102,-60.28,-11.079,-60.177,-11.119,-60.07,-11.115,-60.003,-11.146,-59.909,-11.383,-60.025,-11.535,-60.101,-11.602,-60.101,-11.744,-60.061,-11.905,-59.985,-11.919,-59.931,-12.052,-59.887,-12.128,-59.891,-12.244,-59.824,-12.387,-59.909,-12.619,-60.003,-12.731,-60.043,-12.874,-60.083,-12.928,-60.195,-12.972,-60.271,-13.07,-60.275,-13.137,-60.351,-13.271,-60.382,-13.419,-60.659,-13.602,-60.725,-13.663]},{"z":3,"b":[-38.605,-6.396,-37.158,-4.799],"l":[-38.525,-6.383,-38.605,-6.396,-38.579,-6.262,-38.512,-6.182,-38.494,-6.128,-38.413,-6.061,-38.369,-6.093,-38.302,-6.075,-38.128,-5.878,-38.079,-5.762,-38.079,-5.686,-38.047,-5.615,-37.913,-5.463,-37.721,-5.061,-37.596,-4.959,-37.543,-4.932,-37.158,-4.799]},{"z":3,"b":[-38.525,-6.977,-34.92,-6.026],"l":[-38.525,-6.383,-38.436,-6.414,-38.284,-6.503,-38.239,-6.486,-38.132,-6.521,-38.056,-6.445,-38.003,-6.432,-37.842,-6.343,-37.793,-6.294,-37.757,-6.177,-37.641,-6.128,-37.476,-6.084,-37.422,-6.097,-37.262,-6.026,-37.177,-6.052,-37.177,-6.115,-37.396,-6.392,-37.431,-6.517,-37.507,-6.552,-37.516,-6.682,-37.338,-6.7,-37.271,-6.74,-37.217,-6.825,-37.155,-6.785,-37.043,-6.753,-36.989,-6.709,-36.945,-6.749,-36.793,-6.767,-36.744,-6.825,-36.771,-6.932,-36.713,-6.977,-36.659,-6.932,-36.574,-6.927,-36.503,-6.785,-36.53,-6.736,-36.534,-6.637,-36.445,-6.624,-36.516,-6.481,-36.494,-6.378,-36.382,-6.302,-36.293,-6.307,-36.253,-6.414,-35.927,-6.468,-35.721,-6.445,-35.668,-6.427,-35.467,-6.463,-35.306,-6.53,-35.257,-6.508,-35.047,-6.535,-34.92,-6.481]},{"z":3,"b":[-41.864,-21.276,-40.967,-20.776],"l":[-41.864,-20.776,-41.744,-20.82,-41.713,-20.977,-41.73,-21.044,-41.713,-21.111,-41.44,-21.2,-41.391,-21.186,-41.266,-21.231,-41.07,-21.213,-40.967,-21.276]},{"z":3,"b":[-45.757,-10.896,-41.369,-8.713],"l":[-45.757,-10.329,-45.605,-10.334,-45.516,-10.414,-45.472,-10.486,-45.44,-10.619,-45.315,-10.78,-45.043,-10.896,-44.931,-10.861,-44.851,-10.878,-44.784,-10.856,-44.753,-10.785,-44.646,-10.736,-44.619,-10.686,-44.538,-10.628,-44.418,-10.588,-44.311,-10.602,-44.217,-10.628,-44.119,-10.588,-43.994,-10.454,-43.913,-10.432,-43.802,-10.204,-43.762,-10.169,-43.753,-10.106,-43.704,-10.035,-43.713,-9.95,-43.681,-9.802,-43.726,-9.749,-43.784,-9.566,-43.829,-9.499,-43.811,-9.427,-43.695,-9.445,-43.659,-9.365,-43.605,-9.338,-43.538,-9.361,-43.449,-9.302,-43.415,-9.336,-43.378,-9.414,-43.186,-9.419,-43.128,-9.374,-43.043,-9.396,-43.03,-9.436,-42.937,-9.474,-42.931,-9.517,-42.851,-9.548,-42.753,-9.521,-42.619,-9.566,-42.574,-9.486,-42.489,-9.499,-42.431,-9.41,-42.311,-9.307,-42.15,-9.294,-42.043,-9.209,-41.918,-9.213,-41.851,-9.253,-41.797,-9.173,-41.735,-9.133,-41.735,-8.981,-41.61,-8.963,-41.556,-8.972,-41.498,-8.936,-41.467,-8.865,-41.4,-8.785,-41.369,-8.713]},{"z":3,"b":[-41.449,-7.392,-40.418,-2.869],"l":[-40.538,-7.392,-40.534,-7.325,-40.583,-7.195,-40.512,-7.003,-40.44,-6.905,-40.418,-6.811,-40.48,-6.736,-40.614,-6.709,-40.708,-6.66,-40.722,-6.57,-40.771,-6.494,-40.797,-6.378,-40.806,-6.253,-40.891,-6.012,-40.891,-5.905,-40.922,-5.682,-40.918,-5.602,-40.949,-5.419,-41.025,-5.365,-41.07,-5.307,-41.061,-5.169,-41.114,-5.066,-41.123,-5.008,-41.186,-4.914,-41.181,-4.811,-41.226,-4.727,-41.244,-4.615,-41.235,-4.539,-41.141,-4.427,-41.074,-4.325,-41.119,-4.177,-41.181,-4.124,-41.23,-4.044,-41.284,-3.811,-41.355,-3.709,-41.382,-3.593,-41.449,-3.436,-41.427,-3.32,-41.378,-3.271,-41.257,-3.079,-41.273,-2.869]},{"z":3,"b":[-38.73,-7.619,-38.525,-6.383],"l":[-38.699,-7.619,-38.637,-7.535,-38.632,-7.459,-38.583,-7.427,-38.534,-7.294,-38.547,-7.236,-38.601,-7.222,-38.677,-7.16,-38.686,-7.03,-38.73,-7.003,-38.726,-6.892,-38.65,-6.838,-38.619,-6.758,-38.65,-6.691,-38.579,-6.481,-38.525,-6.383]},{"z":3,"b":[-54.793,-1.177,-51.888,2.511],"l":[-54.682,2.511,-54.735,2.416,-54.717,2.265,-54.788,2.131,-54.753,2.081,-54.793,2.01,-54.762,1.97,-54.766,1.898,-54.735,1.773,-54.579,1.782,-54.498,1.747,-54.364,1.76,-54.204,1.657,-54.172,1.657,-54.083,1.541,-54.079,1.506,-54.007,1.523,-53.922,1.461,-53.891,1.407,-53.757,1.394,-53.739,1.434,-53.659,1.421,-53.65,1.363,-53.556,1.367,-53.538,1.242,-53.44,1.26,-53.409,1.184,-53.467,1.153,-53.418,0.943,-53.266,0.782,-53.132,0.751,-53.114,0.72,-53.141,0.537,-53.128,0.385,-53.043,0.247,-53.007,0.135,-53.016,0.055,-52.976,-0.017,-52.913,-0.191,-52.829,-0.182,-52.699,-0.302,-52.628,-0.396,-52.632,-0.557,-52.605,-0.611,-52.525,-0.646,-52.507,-0.731,-52.516,-0.874,-52.4,-0.869,-52.369,-0.923,-52.391,-0.954,-52.36,-1.07,-52.239,-1.146,-52.132,-1.151,-52.052,-1.177,-51.985,-1.142,-51.888,-1.16]},{"z":3,"b":[-58.869,-7.338,-56.391,0.224],"l":[-58.869,0.224,-58.851,-0.084,-58.864,-0.173,-58.855,-0.352,-58.739,-0.432,-58.748,-0.624,-58.704,-0.695,-58.628,-0.767,-58.57,-0.762,-58.44,-0.861,-58.391,-1.048,-58.315,-1.119,-58.253,-1.128,-58.159,-1.235,-58.083,-1.298,-57.98,-1.347,-57.9,-1.423,-57.829,-1.445,-57.655,-1.588,-57.565,-1.624,-57.449,-1.691,-57.338,-1.731,-57.275,-1.713,-57.248,-1.758,-57.168,-1.771,-57.088,-1.811,-57.007,-1.936,-56.824,-2.035,-56.744,-2.052,-56.748,-2.177,-56.637,-2.222,-56.498,-2.16,-56.485,-2.249,-56.401,-2.336,-56.391,-2.392,-56.757,-3.186,-58.293,-6.499,-58.375,-6.57,-58.437,-6.676,-58.455,-6.794,-58.386,-6.949,-58.228,-7.142,-58.146,-7.263,-58.137,-7.338]},{"z":3,"b":[-58.137,-9.843,-50.235,-7.338],"l":[-50.235,-9.843,-51.297,-9.789,-55.092,-9.566,-56.463,-9.468,-56.489,-9.468,-56.677,-9.378,-56.771,-9.396,-56.838,-9.271,-56.927,-9.244,-57.047,-9.231,-57.074,-9.186,-57.083,-9.079,-57.101,-9.052,-57.297,-8.959,-57.48,-8.794,-57.579,-8.758,-57.641,-8.615,-57.65,-8.499,-57.681,-8.436,-57.641,-8.231,-57.78,-8.048,-57.896,-7.691,-57.949,-7.619,-57.98,-7.53,-58.061,-7.41,-58.137,-7.338]},{"z":3,"b":[-41.882,-20.776,-40.217,-17.892],"l":[-41.864,-20.776,-41.882,-20.758,-41.811,-20.646,-41.851,-20.624,-41.797,-20.535,-41.797,-20.428,-41.855,-20.369,-41.717,-20.209,-41.405,-20.213,-41.364,-20.186,-41.306,-19.954,-41.186,-19.865,-41.159,-19.66,-41.03,-19.548,-41.047,-19.49,-40.958,-19.472,-40.927,-19.294,-40.945,-19.146,-41.052,-19.035,-41.025,-18.981,-41.105,-18.887,-41.163,-18.905,-41.235,-18.852,-41.195,-18.807,-41.092,-18.829,-40.931,-18.807,-40.927,-18.695,-41.03,-18.637,-41.003,-18.454,-41.025,-18.405,-41.128,-18.334,-41.141,-18.298,-41.061,-18.177,-41.016,-18.173,-40.913,-18.102,-40.838,-18.151,-40.766,-18.137,-40.775,-18.093,-40.909,-17.972,-40.788,-17.972,-40.726,-17.945,-40.668,-17.954,-40.534,-17.901,-40.476,-17.928,-40.427,-17.892,-40.217,-17.972]},{"z":3,"b":[-40.217,-17.977,-40.208,-17.972],"l":[-40.217,-17.972,-40.208,-17.977]},{"z":3,"b":[-61.61,-8.767,-58.137,-7.338],"l":[-61.61,-8.767,-58.547,-8.749,-58.472,-8.691,-58.427,-8.523,-58.397,-8.416,-58.354,-8.276,-58.329,-8.051,-58.354,-7.906,-58.33,-7.78,-58.259,-7.673,-58.224,-7.575,-58.224,-7.485,-58.202,-7.415,-58.137,-7.338]},{"z":3,"b":[-53.065,-18.021,-53.052,-18.017],"l":[-53.052,-18.017,-53.065,-18.021]},{"z":3,"b":[-40.208,-18.325,-39.672,-17.977],"l":[-40.208,-17.977,-40.186,-18.008,-39.672,-18.325]},{"z":3,"b":[-50.717,-12.843,-50.237,-9.846],"l":[-50.485,-12.843,-50.566,-12.82,-50.614,-12.763,-50.655,-12.667,-50.66,-12.558,-50.629,-12.437,-50.632,-12.289,-50.668,-12.112,-50.675,-11.99,-50.653,-11.922,-50.661,-11.854,-50.717,-11.749,-50.666,-11.652,-50.671,-11.574,-50.717,-11.483,-50.708,-11.346,-50.645,-11.163,-50.61,-11.001,-50.599,-10.69,-50.551,-10.567,-50.455,-10.388,-50.403,-10.258,-50.395,-10.177,-50.337,-10.034,-50.237,-9.846]},{"z":3,"b":[-48.73,-5.356,-46.006,-1.146],"l":[-48.722,-5.356,-48.73,-5.338,-48.253,-4.954,-47.802,-4.597,-47.655,-4.606,-47.583,-4.548,-47.489,-4.423,-47.458,-4.338,-47.382,-4.276,-47.333,-4.164,-47.311,-4.066,-47.288,-4.079,-47.065,-3.843,-47.025,-3.597,-47.03,-3.57,-46.967,-3.526,-46.945,-3.396,-46.86,-3.329,-46.824,-3.329,-46.744,-3.191,-46.668,-3.093,-46.637,-2.986,-46.65,-2.914,-46.574,-2.847,-46.668,-2.74,-46.601,-2.664,-46.507,-2.615,-46.485,-2.548,-46.427,-2.512,-46.436,-2.41,-46.409,-2.387,-46.413,-2.24,-46.378,-2.253,-46.28,-2.142,-46.213,-1.927,-46.217,-1.807,-46.302,-1.802,-46.315,-1.731,-46.244,-1.722,-46.199,-1.677,-46.181,-1.575,-46.199,-1.486,-46.123,-1.347,-46.159,-1.316,-46.141,-1.24,-46.006,-1.146]},{"z":3,"b":[-45.98,-10.316,-41.815,-2.679],"l":[-45.945,-10.316,-45.936,-10.213,-45.954,-10.182,-45.9,-10.084,-45.9,-10.026,-45.86,-9.999,-45.86,-9.834,-45.838,-9.535,-45.797,-9.463,-45.829,-9.374,-45.896,-9.329,-45.905,-9.177,-45.936,-9.119,-45.94,-9.012,-45.98,-8.927,-45.927,-8.789,-45.82,-8.7,-45.753,-8.561,-45.735,-8.432,-45.659,-8.311,-45.655,-8.253,-45.579,-8.151,-45.543,-7.869,-45.472,-7.673,-45.297,-7.557,-45.025,-7.494,-44.909,-7.445,-44.815,-7.365,-44.713,-7.396,-44.579,-7.249,-44.449,-7.146,-44.306,-7.111,-44.257,-7.008,-44.168,-6.923,-44.163,-6.887,-44.092,-6.807,-44.061,-6.829,-44.038,-6.767,-43.954,-6.762,-43.815,-6.709,-43.677,-6.7,-43.561,-6.753,-43.463,-6.843,-43.302,-6.802,-43.199,-6.753,-43.132,-6.78,-42.98,-6.744,-42.918,-6.669,-42.913,-6.615,-42.855,-6.481,-42.869,-6.436,-42.851,-6.253,-42.963,-6.186,-43.003,-6.124,-43.074,-6.057,-43.11,-5.771,-43.083,-5.713,-43.101,-5.624,-43.047,-5.597,-42.913,-5.392,-42.833,-5.311,-42.806,-5.16,-42.833,-5.097,-42.855,-4.936,-42.905,-4.829,-42.954,-4.776,-42.931,-4.731,-42.949,-4.66,-42.913,-4.637,-42.864,-4.499,-42.896,-4.405,-42.954,-4.392,-42.989,-4.24,-42.891,-4.142,-42.851,-4.039,-42.735,-3.923,-42.663,-3.789,-42.677,-3.7,-42.628,-3.615,-42.574,-3.57,-42.503,-3.45,-42.454,-3.477,-42.364,-3.45,-42.217,-3.432,-42.101,-3.302,-42.105,-3.262,-42.003,-3.231,-41.922,-3.111,-41.842,-3.035,-41.815,-2.936,-41.869,-2.852,-41.831,-2.679]},{"z":3,"b":[-41.369,-9.472,-38.216,-8.559],"l":[-41.369,-8.713,-41.324,-8.736,-41.159,-8.709,-41.105,-8.718,-41.101,-8.776,-41.004,-8.781,-40.98,-8.816,-40.891,-8.856,-40.891,-9.035,-40.855,-9.093,-40.851,-9.151,-40.704,-9.218,-40.69,-9.343,-40.744,-9.423,-40.686,-9.472,-40.565,-9.46,-40.413,-9.397,-40.32,-9.293,-40.273,-9.145,-40.212,-9.102,-40.078,-9.088,-40.005,-9.058,-39.949,-9.022,-39.909,-8.98,-39.888,-8.924,-39.885,-8.853,-39.835,-8.809,-39.738,-8.791,-39.688,-8.749,-39.683,-8.683,-39.601,-8.62,-39.442,-8.559,-39.333,-8.566,-39.257,-8.64,-39.098,-8.715,-38.835,-8.8,-38.705,-8.88,-38.668,-8.896,-38.619,-8.896,-38.577,-8.861,-38.544,-8.831,-38.505,-8.835,-38.484,-8.878,-38.482,-8.96,-38.433,-9.004,-38.336,-9.01,-38.294,-9.043,-38.308,-9.102,-38.283,-9.204,-38.221,-9.348,-38.216,-9.351]},{"z":3,"b":[-46.074,-17.977,-39.864,-14.24],"l":[-40.208,-17.977,-40.23,-17.919,-40.186,-17.816,-40.208,-17.767,-40.284,-17.718,-40.382,-17.624,-40.413,-17.557,-40.48,-17.557,-40.498,-17.419,-40.61,-17.392,-40.605,-17.311,-40.565,-17.262,-40.574,-17.133,-40.525,-16.932,-40.471,-16.869,-40.297,-16.878,-40.244,-16.834,-40.284,-16.744,-40.275,-16.615,-40.253,-16.566,-40.137,-16.544,-40.132,-16.503,-40.056,-16.396,-39.94,-16.311,-39.864,-16.111,-39.936,-15.999,-40.003,-15.994,-40.132,-15.892,-40.168,-15.896,-40.235,-15.82,-40.329,-15.825,-40.476,-15.776,-40.534,-15.798,-40.574,-15.758,-40.668,-15.718,-40.762,-15.74,-40.829,-15.682,-40.891,-15.695,-40.945,-15.673,-41.03,-15.736,-41.159,-15.78,-41.333,-15.718,-41.36,-15.494,-41.802,-15.111,-41.94,-15.173,-42.088,-15.182,-42.177,-15.106,-42.262,-15.106,-42.431,-15.035,-42.579,-14.932,-42.632,-14.941,-42.891,-14.749,-42.954,-14.677,-43.23,-14.637,-43.387,-14.7,-43.449,-14.78,-43.494,-14.789,-43.708,-14.736,-43.824,-14.695,-43.864,-14.66,-43.879,-14.625,-43.879,-14.624,-43.879,-14.623,-43.879,-14.622,-43.879,-14.621,-43.878,-14.62,-43.878,-14.618,-43.878,-14.617,-43.878,-14.615,-43.877,-14.613,-43.877,-14.611,-43.876,-14.609,-43.876,-14.607,-43.875,-14.605,-43.875,-14.603,-43.874,-14.6,-43.874,-14.598,-43.873,-14.595,-43.873,-14.593,-43.872,-14.59,-43.872,-14.588,-43.871,-14.585,-43.871,-14.582,-43.87,-14.579,-43.87,-14.577,-43.869,-14.574,-43.868,-14.571,-43.868,-14.569,-43.867,-14.566,-43.867,-14.564,-43.866,-14.561,-43.866,-14.559,-43.865,-14.556,-43.865,-14.554,-43.864,-14.552,-43.864,-14.549,-43.864,-14.547,-43.863,-14.546,-43.863,-14.544,-43.862,-14.542,-43.862,-14.541,-43.862,-14.539,-43.862,-14.538,-43.861,-14.537,-43.861,-14.536,-43.861,-14.535,-43.861,-14.534,-43.8,-14.37,-43.781,-14.343,-43.838,-14.316,-44.159,-14.271,-44.213,-14.24,-44.32,-14.244,-44.36,-14.271,-44.561,-14.347,-44.838,-14.517,-44.882,-14.597,-45.101,-14.718,-45.226,-14.74,-45.32,-14.856,-45.463,-14.941,-45.552,-14.936,-45.655,-15.044,-45.713,-15.124,-45.766,-15.146,-45.927,-15.128,-45.967,-15.186,-46.074,-15.249,-45.976,-14.986,-46.025,-14.869]},{"z":3,"b":[-50.994,-22.892,-44.833,-19.791],"l":[-44.833,-22.405,-44.922,-22.45,-45.056,-22.468,-45.244,-22.561,-45.271,-22.606,-45.409,-22.646,-45.471,-22.612,-45.53,-22.651,-45.583,-22.615,-45.63,-22.623,-45.672,-22.619,-45.802,-22.7,-45.744,-22.727,-45.744,-22.798,-45.793,-22.852,-45.86,-22.861,-45.909,-22.82,-46.021,-22.874,-46.146,-22.892,-46.155,-22.847,-46.288,-22.883,-46.36,-22.843,-46.369,-22.749,-46.472,-22.682,-46.396,-22.615,-46.454,-22.517,-46.543,-22.472,-46.547,-22.441,-46.641,-22.41,-46.699,-22.32,-46.663,-22.204,-46.601,-22.133,-46.659,-22.053,-46.619,-21.986,-46.659,-21.905,-46.668,-21.811,-46.628,-21.771,-46.605,-21.682,-46.561,-21.673,-46.494,-21.526,-46.507,-21.459,-46.605,-21.428,-46.655,-21.374,-46.686,-21.396,-46.815,-21.361,-46.896,-21.405,-47.003,-21.401,-46.994,-21.343,-47.056,-21.195,-47.123,-21.133,-47.146,-20.981,-47.222,-20.914,-47.213,-20.807,-47.11,-20.633,-47.155,-20.526,-47.253,-20.477,-47.293,-20.423,-47.266,-20.285,-47.235,-20.204,-47.244,-20.174,-47.329,-20.118,-47.428,-20.036,-47.46,-20.001,-47.518,-19.994,-47.594,-20.019,-47.656,-20.023,-47.696,-19.991,-47.75,-19.977,-47.819,-19.981,-47.86,-20.012,-47.862,-20.056,-47.909,-20.082,-47.991,-20.083,-48.051,-20.12,-48.109,-20.117,-48.178,-20.083,-48.253,-20.093,-48.364,-20.138,-48.716,-20.144,-48.812,-20.159,-48.858,-20.233,-48.887,-20.37,-48.918,-20.429,-48.952,-20.41,-48.978,-20.338,-48.995,-20.214,-49.04,-20.187,-49.113,-20.259,-49.166,-20.295,-49.2,-20.294,-49.239,-20.246,-49.284,-20.149,-49.288,-20.074,-49.259,-20.017,-49.278,-19.987,-49.377,-19.978,-50.364,-19.859,-50.434,-19.8,-50.489,-19.791,-50.584,-19.821,-50.994,-20.102]},{"z":3,"b":[-53.119,-22.642,-50.994,-20.102],"l":[-50.994,-20.102,-51.064,-20.223,-51.161,-20.307,-51.397,-20.442,-51.534,-20.558,-51.593,-20.666,-51.619,-20.807,-51.692,-20.944,-51.811,-21.077,-51.866,-21.183,-51.857,-21.26,-51.873,-21.33,-51.936,-21.423,-51.955,-21.469,-51.99,-21.494,-52.048,-21.511,-52.073,-21.554,-52.065,-21.623,-52.123,-21.718,-52.246,-21.84,-52.339,-21.957,-52.403,-22.069,-52.566,-22.207,-52.961,-22.455,-53.116,-22.64,-53.119,-22.642]},{"z":3,"b":[-53.116,-22.794,-51.003,-22.521],"l":[-51.003,-22.794,-51.181,-22.74,-51.355,-22.651,-51.547,-22.686,-51.641,-22.651,-51.695,-22.664,-51.744,-22.619,-51.873,-22.611,-51.985,-22.548,-52.065,-22.521,-52.141,-22.544,-52.163,-22.602,-52.217,-22.642,-52.262,-22.602,-52.315,-22.619,-52.445,-22.602,-52.521,-22.615,-52.614,-22.57,-52.695,-22.602,-52.949,-22.57,-53.045,-22.636,-53.116,-22.64]},{"z":3,"b":[-51.003,-25.297,-48.082,-22.794],"l":[-48.082,-25.297,-48.092,-25.236,-48.186,-25.191,-48.23,-25.012,-48.266,-25.035,-48.413,-24.959,-48.512,-25.084,-48.565,-25.057,-48.543,-24.811,-48.489,-24.744,-48.614,-24.682,-48.78,-24.695,-48.829,-24.664,-49.025,-24.669,-49.092,-24.686,-49.217,-24.691,-49.297,-24.664,-49.311,-24.53,-49.239,-24.419,-49.226,-24.338,-49.275,-24.316,-49.338,-24.213,-49.333,-24.142,-49.409,-24.084,-49.601,-23.865,-49.57,-23.834,-49.552,-23.713,-49.605,-23.642,-49.655,-23.508,-49.614,-23.41,-49.637,-23.258,-49.73,-23.106,-49.909,-23.035,-49.913,-22.986,-49.958,-22.963,-49.972,-22.914,-50.123,-22.941,-50.293,-22.954,-50.369,-22.914,-50.431,-22.945,-50.628,-22.923,-50.726,-22.95,-50.784,-22.945,-50.882,-22.82,-51.003,-22.794]},{"z":3,"b":[-57.753,-18.03,-53.065,-17.186],"l":[-53.065,-18.021,-53.146,-18.03,-53.306,-17.999,-53.494,-18.012,-53.605,-17.994,-53.726,-18.008,-53.869,-17.945,-53.976,-17.932,-53.989,-17.901,-53.86,-17.691,-53.753,-17.642,-53.681,-17.258,-53.744,-17.253,-53.82,-17.307,-53.949,-17.459,-54.038,-17.512,-54.083,-17.588,-54.146,-17.628,-54.235,-17.637,-54.32,-17.673,-54.391,-17.673,-54.422,-17.579,-54.525,-17.512,-54.699,-17.517,-54.78,-17.557,-54.811,-17.602,-54.9,-17.651,-55.074,-17.682,-55.181,-17.686,-55.338,-17.584,-55.534,-17.503,-55.61,-17.392,-55.788,-17.352,-55.833,-17.311,-55.985,-17.267,-56.025,-17.213,-56.101,-17.186,-56.248,-17.218,-56.329,-17.285,-56.436,-17.32,-56.489,-17.302,-56.668,-17.32,-56.745,-17.317,-56.79,-17.386,-56.888,-17.518,-56.98,-17.595,-57.08,-17.734,-57.198,-17.812,-57.37,-17.876,-57.419,-17.882,-57.472,-17.901,-57.494,-17.869,-57.601,-17.816,-57.632,-17.74,-57.753,-17.735]},{"z":2,"b":[138.002,-25.999,138.002,-16.557],"l":[138.002,-16.557,138.002,-25.999]},{"z":2,"b":[129.002,-25.999,129.002,-14.871],"l":[129.002,-25.999,129.002,-14.871]},{"z":2,"b":[129.002,-25.999,138.002,-25.999],"l":[138.002,-25.999,129.002,-25.999]},{"z":2,"b":[138.002,-28.999,141.002,-25.999],"l":[141.002,-28.999,141.002,-25.999,138.002,-25.999]},{"z":2,"b":[129.002,-31.693,129.002,-25.999],"l":[129.002,-31.693,129.002,-25.999]},{"z":2,"b":[140.966,-38.072,140.997,-34.005],"l":[140.997,-34.016,140.968,-34.005,140.966,-38.072]},{"z":2,"b":[141.002,-29.139,153.516,-28.144],"l":[141.002,-28.999,148.974,-28.999,148.993,-28.986,148.994,-28.986,148.994,-28.985,148.995,-28.984,148.996,-28.984,148.997,-28.983,148.998,-28.982,148.999,-28.981,149,-28.98,149.001,-28.979,149.003,-28.978,149.004,-28.976,149.006,-28.975,149.007,-28.973,149.009,-28.972,149.011,-28.97,149.012,-28.969,149.014,-28.967,149.016,-28.966,149.018,-28.964,149.02,-28.962,149.022,-28.96,149.024,-28.959,149.026,-28.957,149.028,-28.955,149.029,-28.953,149.031,-28.952,149.033,-28.95,149.035,-28.948,149.037,-28.947,149.039,-28.945,149.041,-28.943,149.042,-28.942,149.044,-28.94,149.046,-28.939,149.047,-28.937,149.049,-28.936,149.05,-28.935,149.051,-28.934,149.052,-28.933,149.054,-28.932,149.055,-28.931,149.055,-28.93,149.056,-28.929,149.057,-28.929,149.057,-28.928,149.058,-28.928,149.081,-28.875,149.174,-28.807,149.335,-28.723,149.433,-28.657,149.467,-28.608,149.512,-28.579,149.596,-28.566,149.677,-28.612,149.853,-28.604,150.15,-28.558,150.341,-28.564,150.47,-28.65,150.712,-28.639,150.856,-28.66,150.983,-28.71,151.05,-28.758,151.058,-28.805,151.118,-28.851,151.231,-28.898,151.303,-28.982,151.335,-29.104,151.393,-29.139,151.478,-29.087,151.528,-29.035,151.554,-28.955,151.73,-28.887,151.761,-28.945,151.824,-28.972,151.855,-28.919,151.935,-28.923,152.024,-28.874,152.069,-28.704,152.007,-28.637,151.966,-28.535,152.163,-28.436,152.216,-28.436,152.292,-28.387,152.413,-28.343,152.493,-28.262,152.57,-28.323,152.609,-28.294,152.752,-28.356,152.846,-28.325,153.118,-28.352,153.194,-28.258,153.382,-28.236,153.516,-28.144]},{"z":2,"b":[148.77,-35.914,149.404,-35.146],"l":[148.77,-35.494,148.774,-35.637,148.792,-35.709,148.85,-35.749,148.89,-35.745,148.899,-35.82,148.949,-35.887,149.051,-35.914,149.105,-35.829,149.087,-35.611,149.127,-35.584,149.149,-35.508,149.14,-35.441,149.185,-35.383,149.279,-35.347,149.368,-35.356,149.404,-35.329,149.257,-35.271,149.194,-35.218,149.19,-35.178,149.105,-35.146,148.828,-35.311,148.77,-35.494]},{"z":2,"b":[141.002,-34.043,141.026,-28.999],"l":[141.002,-28.999,141.026,-34.043]},{"z":2,"b":[140.997,-37.501,149.944,-34.016],"l":[149.944,-37.501,148.216,-36.803,148.133,-36.777,148.162,-36.73,148.197,-36.625,148.147,-36.546,148.034,-36.181,147.975,-36.046,147.931,-36.019,147.762,-35.956,147.674,-35.954,147.591,-35.982,147.538,-35.984,147.502,-35.959,147.458,-35.954,147.407,-35.971,147.407,-35.972,147.406,-35.972,147.405,-35.973,147.404,-35.974,147.404,-35.975,147.403,-35.975,147.402,-35.976,147.401,-35.977,147.4,-35.977,147.4,-35.978,147.399,-35.979,147.398,-35.98,147.397,-35.981,147.396,-35.982,147.395,-35.983,147.394,-35.984,147.393,-35.985,147.392,-35.986,147.39,-35.987,147.389,-35.987,147.388,-35.988,147.387,-35.989,147.386,-35.99,147.385,-35.991,147.384,-35.992,147.383,-35.993,147.382,-35.994,147.381,-35.995,147.38,-35.996,147.379,-35.997,147.378,-35.998,147.377,-35.998,147.377,-35.999,147.376,-36,147.375,-36,147.375,-36.001,147.374,-36.002,147.373,-36.002,147.373,-36.003,147.372,-36.003,147.372,-36.004,147.371,-36.004,147.35,-36.052,147.323,-36.069,147.272,-36.049,147.157,-36.036,147.1,-36.05,147.047,-36.106,146.896,-36.1,146.746,-36.061,146.59,-36.005,146.47,-35.994,146.403,-36.033,146.325,-36.054,146.216,-36.057,146.038,-36.017,145.815,-35.977,145.692,-35.931,145.588,-35.863,145.432,-35.835,145.431,-35.835,145.43,-35.835,145.429,-35.835,145.426,-35.835,145.424,-35.835,145.421,-35.835,145.418,-35.835,145.415,-35.836,145.411,-35.836,145.407,-35.836,145.402,-35.836,145.397,-35.837,145.393,-35.837,145.387,-35.837,145.382,-35.837,145.377,-35.838,145.371,-35.838,145.365,-35.838,145.359,-35.839,145.353,-35.839,145.347,-35.839,145.341,-35.84,145.335,-35.84,145.328,-35.84,145.322,-35.841,145.316,-35.841,145.31,-35.841,145.304,-35.842,145.298,-35.842,145.292,-35.842,145.286,-35.842,145.28,-35.843,145.275,-35.843,145.269,-35.843,145.264,-35.844,145.259,-35.844,145.255,-35.844,145.25,-35.844,145.246,-35.845,145.242,-35.845,145.239,-35.845,145.236,-35.845,145.233,-35.845,145.23,-35.845,145.228,-35.846,145.227,-35.846,145.225,-35.846,145.224,-35.846,145.223,-35.846,145.222,-35.846,145.22,-35.846,145.219,-35.846,145.217,-35.847,145.215,-35.847,145.212,-35.847,145.21,-35.847,145.207,-35.848,145.204,-35.848,145.201,-35.848,145.197,-35.849,145.194,-35.849,145.19,-35.85,145.187,-35.85,145.183,-35.851,145.179,-35.851,145.175,-35.851,145.171,-35.852,145.166,-35.852,145.162,-35.853,145.158,-35.853,145.154,-35.854,145.15,-35.854,145.145,-35.855,145.141,-35.855,145.137,-35.856,145.133,-35.856,145.129,-35.857,145.125,-35.857,145.121,-35.858,145.117,-35.858,145.114,-35.859,145.11,-35.859,145.107,-35.859,145.104,-35.86,145.101,-35.86,145.098,-35.86,145.095,-35.861,145.093,-35.861,145.091,-35.861,145.089,-35.861,145.087,-35.862,145.086,-35.862,145.085,-35.862,145.084,-35.862,145.083,-35.862,145.008,-35.884,144.962,-35.923,144.943,-35.978,144.944,-36.029,144.965,-36.075,144.916,-36.107,144.799,-36.126,144.668,-36.088,144.524,-35.994,144.427,-35.905,144.378,-35.82,144.29,-35.73,144.163,-35.634,143.971,-35.528,143.714,-35.411,143.585,-35.327,143.584,-35.279,143.54,-35.234,143.459,-35.203,143.404,-35.168,143.377,-35.127,143.363,-35.032,143.361,-34.882,143.325,-34.788,143.221,-34.733,142.965,-34.691,142.857,-34.655,142.812,-34.607,142.774,-34.588,142.742,-34.598,142.705,-34.649,142.663,-34.741,142.619,-34.783,142.571,-34.775,142.507,-34.712,142.426,-34.593,142.388,-34.485,142.394,-34.387,142.373,-34.333,142.328,-34.322,142.287,-34.286,142.252,-34.223,142.158,-34.173,141.926,-34.117,141.761,-34.112,141.666,-34.137,141.586,-34.191,141.47,-34.183,141.318,-34.112,141.192,-34.073,141.094,-34.066,141.026,-34.043,140.997,-34.016]},{"z":2,"b":[150.612,-35.19,150.705,-35.12],"l":[150.705,-35.12,150.636,-35.146,150.612,-35.19]},{"z":3,"b":[-66.829,-9.925,-66.628,-9.838],"l":[-66.829,-9.838,-66.628,-9.925]},{"z":2,"b":[-84.325,34.988,-81.659,36.611],"l":[-84.325,34.988,-84.287,35.201,-84.261,35.25,-84.232,35.261,-84.129,35.261,-84.062,35.286,-84.004,35.414,-83.871,35.515,-83.812,35.54,-83.723,35.56,-83.511,35.584,-83.284,35.682,-83.12,35.775,-82.99,35.8,-82.949,35.835,-82.92,35.879,-82.902,35.939,-82.888,35.952,-82.813,35.947,-82.756,36,-82.7,36.03,-82.645,36.047,-82.611,36.047,-82.598,36.032,-82.589,35.987,-82.577,35.972,-82.553,35.971,-82.521,35.981,-82.478,36.01,-82.368,36.1,-82.295,36.134,-82.182,36.154,-82.101,36.107,-82.051,36.123,-81.908,36.306,-81.851,36.34,-81.808,36.353,-81.734,36.355,-81.733,36.406,-81.708,36.482,-81.704,36.534,-81.659,36.611]},{"z":2,"b":[-75.192,39.983,-74.699,41.357],"l":[-74.699,41.357,-74.778,41.325,-74.812,41.302,-74.841,41.269,-74.912,41.155,-75.032,41.052,-75.123,40.999,-75.123,40.972,-75.075,40.884,-75.076,40.856,-75.112,40.802,-75.175,40.776,-75.186,40.744,-75.192,40.69,-75.189,40.596,-75.171,40.577,-75.097,40.543,-75.034,40.42,-74.976,40.406,-74.735,40.155,-74.909,40.08,-75.024,40.017,-75.074,39.983]},{"z":2,"b":[-72.553,42.73,-71.51,45.008],"l":[-71.518,45.008,-71.533,44.988,-71.51,44.908,-71.621,44.772,-71.618,44.728,-71.568,44.608,-71.571,44.579,-71.588,44.565,-71.609,44.514,-71.638,44.482,-71.683,44.45,-71.826,44.374,-71.868,44.355,-72.002,44.329,-72.031,44.301,-72.062,44.116,-72.106,44.013,-72.115,43.965,-72.174,43.884,-72.222,43.791,-72.297,43.715,-72.338,43.622,-72.362,43.587,-72.385,43.529,-72.407,43.332,-72.438,43.225,-72.474,43.039,-72.496,42.992,-72.519,42.967,-72.55,42.887,-72.553,42.856,-72.54,42.83,-72.484,42.766,-72.467,42.73]},{"z":2,"b":[-82.6,38.42,-82.589,38.432],"l":[-82.589,38.42,-82.6,38.432]},{"z":2,"b":[-82.613,38.42,-82.589,38.448],"l":[-82.613,38.448,-82.589,38.42]},{"z":2,"b":[-82.62,37.54,-81.965,38.42],"l":[-81.965,37.54,-82.103,37.571,-82.28,37.687,-82.394,37.828,-82.43,37.894,-82.472,37.938,-82.482,37.984,-82.589,38.1,-82.618,38.15,-82.62,38.182,-82.585,38.243,-82.578,38.272,-82.589,38.42]},{"z":2,"b":[-83.668,36.605,-81.965,37.54],"l":[-81.965,37.54,-82.343,37.297,-82.517,37.209,-82.682,37.138,-82.695,37.126,-82.732,37.048,-82.832,36.979,-82.895,36.906,-83.044,36.846,-83.15,36.762,-83.417,36.681,-83.566,36.653,-83.668,36.605]},{"z":2,"b":[-89.838,30.193,-89.521,31.001],"l":[-89.735,31.001,-89.765,30.886,-89.835,30.731,-89.838,30.69,-89.82,30.64,-89.82,30.611,-89.794,30.569,-89.737,30.527,-89.686,30.463,-89.641,30.349,-89.629,30.296,-89.592,30.223,-89.572,30.209,-89.545,30.201,-89.521,30.193]},{"z":2,"b":[-97.226,45.943,-96.556,48.994],"l":[-96.556,45.943,-96.57,46.019,-96.561,46.138,-96.592,46.228,-96.599,46.272,-96.617,46.327,-96.731,46.473,-96.758,46.59,-96.787,46.648,-96.796,46.797,-96.793,46.839,-96.77,46.92,-96.818,46.974,-96.825,47.019,-96.848,47.373,-96.863,47.424,-96.859,47.588,-96.946,47.762,-96.988,47.82,-97.009,47.892,-97.007,47.926,-97.079,48.043,-97.126,48.173,-97.122,48.286,-97.137,48.371,-97.131,48.437,-97.144,48.537,-97.129,48.593,-97.128,48.681,-97.226,48.994]},{"z":2,"b":[-94.484,33.554,-94.047,33.648],"l":[-94.047,33.554,-94.099,33.577,-94.192,33.589,-94.238,33.581,-94.295,33.587,-94.333,33.565,-94.358,33.561,-94.378,33.566,-94.39,33.586,-94.432,33.6,-94.484,33.648]},{"z":2,"b":[-75.785,39.722,-75.421,39.843],"l":[-75.421,39.815,-75.51,39.843,-75.635,39.839,-75.677,39.827,-75.709,39.803,-75.785,39.722]},{"z":2,"b":[-81.087,35.066,-81.045,35.156],"l":[-81.087,35.156,-81.057,35.146,-81.047,35.131,-81.051,35.089,-81.045,35.066]},{"z":2,"b":[-82.778,35.084,-82.356,35.211],"l":[-82.778,35.087,-82.744,35.084,-82.651,35.128,-82.464,35.178,-82.393,35.211,-82.356,35.202]},{"z":2,"b":[-120.001,48.993,-114.051,60.001],"l":[-120,60.001,-120,59.616,-120,59.231,-120,58.847,-120,58.462,-120,58.077,-120,57.692,-120,57.307,-120.001,56.922,-120.001,56.538,-120.001,56.153,-120.001,55.768,-120.001,55.383,-120.001,54.998,-120.001,54.613,-120.001,54.229,-120.001,53.844,-119.973,53.797,-119.917,53.77,-119.904,53.723,-119.885,53.708,-119.812,53.699,-119.735,53.634,-119.743,53.615,-119.887,53.615,-119.918,53.612,-119.921,53.602,-119.896,53.567,-119.894,53.516,-119.855,53.509,-119.82,53.491,-119.723,53.4,-119.68,53.371,-119.648,53.363,-119.592,53.372,-119.435,53.362,-119.4,53.353,-119.309,53.242,-119.261,53.209,-119.046,53.144,-119.022,53.139,-119.016,53.204,-118.993,53.236,-118.914,53.212,-118.804,53.153,-118.774,53.122,-118.766,53.065,-118.731,53.058,-118.678,53.026,-118.668,52.972,-118.631,52.909,-118.605,52.895,-118.502,52.888,-118.477,52.879,-118.428,52.836,-118.413,52.784,-118.358,52.742,-118.33,52.709,-118.319,52.674,-118.35,52.623,-118.288,52.562,-118.266,52.518,-118.225,52.483,-118.247,52.441,-118.243,52.409,-118.221,52.386,-118.191,52.384,-118.06,52.413,-118.044,52.427,-118.029,52.466,-118.002,52.486,-117.883,52.43,-117.758,52.395,-117.732,52.383,-117.735,52.353,-117.822,52.272,-117.788,52.233,-117.751,52.208,-117.587,52.142,-117.385,52.144,-117.348,52.159,-117.32,52.156,-117.245,52.047,-117.077,51.926,-117.037,51.886,-116.939,51.736,-116.913,51.714,-116.83,51.715,-116.815,51.722,-116.808,51.753,-116.791,51.771,-116.758,51.789,-116.718,51.8,-116.674,51.791,-116.634,51.758,-116.567,51.651,-116.502,51.615,-116.458,51.574,-116.411,51.544,-116.377,51.497,-116.31,51.448,-116.279,51.348,-116.25,51.323,-116.176,51.297,-116.135,51.266,-116.054,51.237,-116.042,51.23,-116.035,51.218,-116.014,51.151,-115.998,51.13,-115.94,51.102,-115.794,51.07,-115.619,50.962,-115.596,50.936,-115.588,50.909,-115.64,50.869,-115.634,50.855,-115.433,50.756,-115.399,50.731,-115.386,50.726,-115.376,50.727,-115.36,50.729,-115.347,50.729,-115.335,50.72,-115.311,50.697,-115.298,50.673,-115.308,50.656,-115.311,50.642,-115.305,50.635,-115.267,50.594,-115.222,50.563,-115.206,50.554,-115.189,50.557,-115.128,50.585,-115.091,50.585,-115.048,50.577,-114.993,50.545,-114.789,50.359,-114.716,50.129,-114.668,50.061,-114.652,50.02,-114.651,49.985,-114.678,49.955,-114.685,49.934,-114.638,49.797,-114.643,49.73,-114.686,49.643,-114.737,49.604,-114.734,49.587,-114.717,49.569,-114.681,49.556,-114.602,49.551,-114.577,49.544,-114.576,49.519,-114.59,49.452,-114.588,49.42,-114.565,49.394,-114.48,49.333,-114.426,49.274,-114.386,49.252,-114.378,49.232,-114.355,49.211,-114.19,49.168,-114.164,49.147,-114.133,49.094,-114.057,49.038,-114.051,49.015,-114.063,48.993]},{"z":2,"b":[-136.445,60.001,-123.819,68.895],"l":[-123.819,60.001,-123.842,60.02,-123.879,60.04,-123.994,60.04,-124.011,60.046,-124.013,60.06,-123.992,60.099,-123.997,60.127,-124.103,60.225,-124.185,60.321,-124.205,60.353,-124.225,60.453,-124.249,60.473,-124.403,60.494,-124.469,60.567,-124.599,60.662,-124.611,60.682,-124.611,60.701,-124.505,60.788,-124.506,60.822,-124.583,60.953,-124.616,60.96,-124.807,60.961,-124.837,60.948,-124.86,60.924,-124.88,60.86,-125.152,60.844,-125.23,60.826,-125.322,60.792,-125.379,60.792,-125.707,60.84,-125.864,60.89,-125.899,60.89,-125.931,60.883,-125.957,60.865,-126.003,60.809,-126.064,60.816,-126.099,60.829,-126.115,60.863,-126.162,60.866,-126.223,60.856,-126.235,60.844,-126.235,60.809,-126.255,60.794,-126.297,60.785,-126.35,60.781,-126.52,60.8,-126.665,60.762,-126.767,60.775,-126.832,60.764,-126.859,60.769,-126.89,60.796,-126.888,60.833,-126.912,60.86,-126.918,60.884,-126.909,60.95,-126.944,61.052,-126.964,61.063,-127.055,61.051,-127.093,61.071,-127.096,61.081,-127.082,61.094,-127.027,61.126,-127.015,61.198,-127.021,61.237,-127.073,61.37,-127.116,61.404,-127.167,61.465,-127.229,61.494,-127.299,61.512,-127.468,61.511,-127.609,61.533,-127.656,61.552,-127.713,61.588,-127.807,61.619,-127.994,61.711,-128.019,61.78,-128.047,61.813,-128.094,61.841,-128.206,61.865,-128.232,61.884,-128.258,61.928,-128.366,61.991,-128.4,62.034,-128.561,62.117,-128.616,62.122,-128.679,62.112,-128.776,62.069,-128.814,62.064,-128.841,62.073,-128.88,62.111,-128.907,62.119,-129.014,62.132,-129.136,62.126,-129.209,62.136,-129.255,62.153,-129.269,62.166,-129.262,62.184,-129.239,62.202,-129.233,62.22,-129.244,62.236,-129.293,62.294,-129.291,62.322,-129.239,62.373,-129.249,62.389,-129.294,62.411,-129.296,62.425,-129.287,62.441,-129.2,62.47,-129.189,62.49,-129.211,62.513,-129.449,62.58,-129.49,62.599,-129.495,62.619,-129.525,62.674,-129.544,62.688,-129.62,62.712,-129.636,62.728,-129.642,62.756,-129.658,62.776,-129.719,62.832,-129.729,62.865,-129.714,62.895,-129.714,62.931,-129.617,63.037,-129.612,63.059,-129.63,63.068,-129.734,63.07,-129.827,63.092,-129.87,63.157,-129.906,63.187,-129.979,63.216,-130.06,63.261,-130.134,63.276,-130.14,63.293,-130.102,63.319,-129.948,63.376,-129.905,63.406,-129.845,63.486,-129.855,63.522,-129.888,63.555,-129.946,63.586,-129.976,63.616,-130.063,63.636,-130.082,63.65,-130.098,63.679,-130.111,63.695,-130.142,63.698,-130.244,63.678,-130.285,63.686,-130.303,63.704,-130.298,63.721,-130.26,63.737,-130.145,63.762,-130.121,63.773,-130.118,63.793,-130.14,63.808,-130.283,63.823,-130.364,63.85,-130.513,63.92,-130.661,63.951,-130.758,63.993,-130.792,64.031,-130.847,64.049,-130.872,64.081,-130.93,64.125,-130.941,64.141,-130.913,64.168,-130.913,64.193,-130.931,64.219,-130.982,64.259,-131.012,64.273,-131.026,64.298,-131.022,64.324,-131.107,64.401,-131.265,64.447,-131.359,64.458,-131.399,64.451,-131.45,64.416,-131.513,64.393,-131.586,64.38,-131.764,64.381,-131.797,64.388,-131.803,64.407,-131.79,64.426,-131.73,64.489,-131.716,64.515,-131.729,64.534,-131.752,64.545,-131.893,64.577,-132.052,64.684,-132.106,64.702,-132.321,64.763,-132.402,64.777,-132.554,64.78,-132.588,64.794,-132.6,64.809,-132.601,64.826,-132.584,64.842,-132.505,64.884,-132.497,64.913,-132.497,64.957,-132.481,64.97,-132.381,65.022,-132.354,65.039,-132.353,65.057,-132.381,65.075,-132.516,65.094,-132.538,65.109,-132.54,65.17,-132.579,65.185,-132.61,65.181,-132.708,65.167,-132.752,65.173,-132.768,65.185,-132.773,65.203,-132.762,65.225,-132.597,65.28,-132.566,65.3,-132.542,65.341,-132.346,65.443,-132.268,65.538,-132.195,65.598,-132.196,65.628,-132.295,65.716,-132.378,65.767,-132.489,65.82,-132.533,65.834,-132.555,65.857,-132.539,65.886,-132.341,65.955,-132.343,65.979,-132.371,65.994,-132.483,65.989,-132.577,66.024,-132.614,66.029,-132.714,65.998,-132.766,65.971,-132.821,65.928,-132.903,65.914,-132.948,65.917,-132.968,65.934,-132.94,65.976,-132.916,66.002,-132.918,66.018,-132.938,66.029,-133.041,66.028,-133.229,66.006,-133.412,65.956,-133.5,65.955,-133.589,65.963,-133.622,65.975,-133.624,66.01,-133.645,66.049,-133.676,66.074,-133.677,66.096,-133.66,66.112,-133.594,66.15,-133.569,66.177,-133.567,66.208,-133.571,66.249,-133.583,66.285,-133.612,66.295,-133.651,66.3,-133.76,66.304,-133.79,66.304,-133.8,66.326,-133.79,66.348,-133.76,66.416,-133.739,66.44,-133.706,66.444,-133.674,66.446,-133.654,66.453,-133.652,66.472,-133.658,66.509,-133.649,66.536,-133.635,66.566,-133.652,66.586,-133.726,66.631,-133.764,66.651,-133.769,66.669,-133.771,66.684,-133.799,66.711,-133.806,66.733,-133.798,66.752,-133.783,66.785,-133.794,66.819,-133.848,66.862,-133.924,66.903,-134.031,66.944,-134.053,66.973,-134.056,67.004,-134.118,67.004,-134.405,67.004,-134.692,67.005,-134.979,67.005,-135.265,67.005,-135.552,67.005,-135.839,67.005,-136.126,67.005,-136.169,67.015,-136.195,67.036,-136.223,67.088,-136.224,67.135,-136.221,67.163,-136.218,67.181,-136.15,67.226,-136.108,67.288,-136.105,67.304,-136.129,67.354,-136.141,67.375,-136.159,67.394,-136.178,67.423,-136.178,67.45,-136.169,67.514,-136.208,67.577,-136.233,67.6,-136.265,67.616,-136.401,67.666,-136.434,67.703,-136.445,67.747,-136.445,68.038,-136.445,68.329,-136.445,68.62,-136.444,68.895]},{"z":2,"b":[-69.054,47.295,-68.379,47.589],"l":[-68.379,47.589,-68.394,47.563,-68.43,47.528,-68.496,47.48,-68.628,47.421,-69.054,47.295]},{"z":2,"b":[-67.613,47.855,-66.704,48.022],"l":[-67.613,47.999,-67.609,47.969,-67.579,47.94,-67.365,47.855,-67.307,47.887,-67.187,47.894,-67.058,47.919,-66.952,47.899,-66.925,47.964,-66.843,47.998,-66.704,48.022]},{"z":2,"b":[-124.228,42.001,-120.001,42.001],"l":[-120.001,42.001,-120.264,42.001,-120.528,42.001,-120.792,42.001,-121.055,42.001,-121.319,42.001,-121.583,42.001,-121.846,42.001,-122.11,42.001,-122.374,42.001,-122.637,42.001,-122.901,42.001,-123.165,42.001,-123.428,42.001,-123.692,42.001,-123.956,42.001,-124.228,42.001]},{"z":2,"b":[-116.049,44.397,-111.051,48.993],"l":[-111.051,44.499,-111.117,44.525,-111.161,44.556,-111.198,44.591,-111.265,44.688,-111.304,44.723,-111.337,44.745,-111.373,44.746,-111.416,44.734,-111.448,44.711,-111.459,44.671,-111.491,44.624,-111.489,44.585,-111.475,44.565,-111.479,44.554,-111.5,44.545,-111.659,44.56,-111.797,44.54,-111.892,44.555,-112.074,44.548,-112.231,44.561,-112.285,44.554,-112.32,44.533,-112.364,44.48,-112.375,44.47,-112.393,44.468,-112.648,44.497,-112.716,44.498,-112.787,44.463,-112.81,44.438,-112.816,44.406,-112.831,44.397,-112.854,44.398,-112.91,44.419,-112.97,44.454,-113,44.48,-113.006,44.539,-113.054,44.605,-113.063,44.665,-113.093,44.715,-113.141,44.765,-113.187,44.799,-113.234,44.81,-113.32,44.803,-113.339,44.808,-113.432,44.874,-113.461,44.904,-113.469,44.93,-113.438,44.994,-113.447,45.029,-113.471,45.073,-113.504,45.109,-113.557,45.14,-113.607,45.209,-113.697,45.305,-113.722,45.379,-113.756,45.441,-113.766,45.503,-113.8,45.54,-113.797,45.588,-113.802,45.601,-113.813,45.607,-113.873,45.634,-113.943,45.682,-113.978,45.692,-113.992,45.693,-114.001,45.681,-114.006,45.658,-114.117,45.579,-114.21,45.542,-114.256,45.506,-114.298,45.488,-114.319,45.486,-114.351,45.497,-114.448,45.551,-114.529,45.576,-114.537,45.587,-114.533,45.633,-114.496,45.686,-114.534,45.782,-114.523,45.815,-114.493,45.839,-114.405,45.874,-114.397,45.891,-114.416,45.982,-114.465,46.016,-114.478,46.036,-114.464,46.11,-114.485,46.158,-114.447,46.199,-114.444,46.271,-114.413,46.344,-114.38,46.453,-114.387,46.5,-114.346,46.551,-114.332,46.601,-114.33,46.638,-114.337,46.659,-114.351,46.668,-114.381,46.668,-114.519,46.648,-114.563,46.65,-114.602,46.66,-114.624,46.678,-114.631,46.719,-114.662,46.746,-114.678,46.75,-114.74,46.734,-114.754,46.735,-114.761,46.742,-114.786,46.777,-114.887,46.845,-114.936,46.923,-115.034,46.991,-115.127,47.085,-115.307,47.231,-115.349,47.261,-115.486,47.312,-115.593,47.385,-115.702,47.438,-115.713,47.447,-115.714,47.452,-115.708,47.457,-115.677,47.471,-115.67,47.498,-115.711,47.553,-115.691,47.607,-115.718,47.681,-115.734,47.706,-115.811,47.776,-115.862,47.842,-115.941,47.904,-116.049,48.005,-116.049,48.252,-116.049,48.499,-116.048,48.747,-116.048,48.993]},{"z":2,"b":[-111.051,42.001,-111.05,44.499],"l":[-111.051,44.499,-111.051,44.343,-111.051,44.187,-111.051,44.031,-111.051,43.874,-111.051,43.718,-111.051,43.562,-111.051,43.406,-111.051,43.25,-111.051,43.094,-111.051,42.938,-111.051,42.782,-111.051,42.625,-111.051,42.469,-111.05,42.313,-111.05,42.157,-111.05,42.001]},{"z":2,"b":[-111.051,44.499,-104.037,45.001],"l":[-111.051,44.499,-111.051,44.624,-111.051,44.75,-111.051,44.876,-111.051,45.001,-110.832,45.001,-110.613,45.001,-110.393,45.001,-110.174,45.001,-109.955,45.001,-109.736,45.001,-109.517,45.001,-109.297,45.001,-109.078,45.001,-108.859,45.001,-108.64,45.001,-108.421,45.001,-108.202,45.001,-107.982,45.001,-107.763,45.001,-107.544,45.001,-107.325,45.001,-107.106,45.001,-106.887,45.001,-106.667,45.001,-106.448,45.001,-106.229,45.001,-106.01,45.001,-105.791,45.001,-105.571,45.001,-105.352,45.001,-105.133,45.001,-104.914,45.001,-104.695,45.001,-104.476,45.001,-104.256,45.001,-104.037,45.001]},{"z":2,"b":[-120.001,42.001,-117.017,42.001],"l":[-117.017,42.001,-117.39,42.001,-117.763,42.001,-118.136,42.001,-118.509,42.001,-118.882,42.001,-119.255,42.001,-119.628,42.001,-120.001,42.001]},{"z":2,"b":[-117.017,42.001,-114.043,42.001],"l":[-117.017,42.001,-116.645,42.001,-116.273,42.001,-115.901,42.001,-115.53,42.001,-115.158,42.001,-114.786,42.001,-114.414,42.001,-114.043,42.001]},{"z":2,"b":[-114.043,42.001,-111.05,42.001],"l":[-111.05,42.001,-111.237,42.001,-111.424,42.001,-111.611,42.001,-111.798,42.001,-111.985,42.001,-112.172,42.001,-112.359,42.001,-112.546,42.001,-112.733,42.001,-112.92,42.001,-113.107,42.001,-113.295,42.001,-113.482,42.001,-113.669,42.001,-113.856,42.001,-114.043,42.001]},{"z":2,"b":[-111.05,41.001,-109.046,42.001],"l":[-111.05,42.001,-111.05,41.876,-111.05,41.751,-111.05,41.626,-111.05,41.501,-111.05,41.376,-111.05,41.251,-111.05,41.126,-111.05,41.001,-110.8,41.001,-110.549,41.001,-110.299,41.001,-110.048,41.001,-109.798,41.001,-109.547,41.001,-109.297,41.001,-109.046,41.001]},{"z":2,"b":[-114.043,37.004,-114.04,42.001],"l":[-114.043,42.001,-114.043,41.845,-114.042,41.689,-114.042,41.533,-114.042,41.376,-114.042,41.22,-114.042,41.064,-114.042,40.908,-114.042,40.752,-114.042,40.596,-114.042,40.44,-114.042,40.283,-114.042,40.127,-114.042,39.971,-114.042,39.815,-114.042,39.659,-114.041,39.503,-114.041,39.346,-114.041,39.19,-114.041,39.034,-114.041,38.878,-114.041,38.722,-114.041,38.566,-114.041,38.41,-114.041,38.253,-114.041,38.097,-114.041,37.941,-114.041,37.785,-114.041,37.629,-114.04,37.473,-114.04,37.316,-114.04,37.16,-114.04,37.004]},{"z":2,"b":[-114.04,37.001,-109.047,37.004],"l":[-114.04,37.004,-113.728,37.004,-113.416,37.004,-113.104,37.004,-112.792,37.003,-112.48,37.003,-112.168,37.003,-111.856,37.003,-111.543,37.002,-111.231,37.002,-110.919,37.002,-110.607,37.002,-110.295,37.002,-109.983,37.001,-109.671,37.001,-109.359,37.001,-109.047,37.001]},{"z":2,"b":[-109.048,31.328,-109.047,37.001],"l":[-109.047,37.001,-109.047,36.646,-109.047,36.292,-109.047,35.937,-109.047,35.583,-109.047,35.228,-109.047,34.874,-109.047,34.519,-109.047,34.165,-109.047,33.81,-109.047,33.456,-109.047,33.101,-109.047,32.747,-109.047,32.392,-109.047,32.038,-109.047,31.683,-109.048,31.328]},{"z":2,"b":[-109.046,41.001,-104.022,41.001],"l":[-109.046,41.001,-108.732,41.001,-108.418,41.001,-108.104,41.001,-107.79,41.001,-107.476,41.001,-107.162,41.001,-106.848,41.001,-106.534,41.001,-106.22,41.001,-105.906,41.001,-105.592,41.001,-105.278,41.001,-104.964,41.001,-104.65,41.001,-104.336,41.001,-104.022,41.001]},{"z":2,"b":[-109.047,37.001,-109.046,41.001],"l":[-109.046,41.001,-109.046,40.751,-109.046,40.501,-109.046,40.251,-109.046,40.001,-109.046,39.751,-109.046,39.501,-109.047,39.251,-109.047,39.001,-109.047,38.751,-109.047,38.501,-109.047,38.251,-109.047,38.001,-109.047,37.751,-109.047,37.501,-109.047,37.251,-109.047,37.001]},{"z":2,"b":[-109.047,37.001,-103.001,37.001],"l":[-103.001,37.001,-103.379,37.001,-103.757,37.001,-104.134,37.001,-104.512,37.001,-104.89,37.001,-105.268,37.001,-105.646,37.001,-106.024,37.001,-106.402,37.001,-106.779,37.001,-107.157,37.001,-107.535,37.001,-107.913,37.001,-108.291,37.001,-108.669,37.001,-109.047,37.001]},{"z":2,"b":[-104.034,45.943,-104.011,48.993],"l":[-104.011,45.943,-104.012,46.134,-104.014,46.324,-104.015,46.515,-104.017,46.706,-104.018,46.896,-104.019,47.087,-104.021,47.278,-104.022,47.469,-104.024,47.659,-104.025,47.85,-104.026,48.041,-104.028,48.231,-104.029,48.422,-104.031,48.613,-104.032,48.803,-104.034,48.993]},{"z":2,"b":[-104.037,45.001,-104.005,45.943],"l":[-104.037,45.001,-104.029,45.001,-104.021,45.001,-104.013,45.001,-104.005,45.001,-104.005,45.004,-104.005,45.007,-104.005,45.01,-104.005,45.013,-104.006,45.246,-104.008,45.478,-104.01,45.711,-104.011,45.943]},{"z":2,"b":[-104.037,43.001,-104.029,45.001],"l":[-104.029,43.001,-104.03,43.251,-104.031,43.501,-104.032,43.751,-104.033,44.001,-104.034,44.251,-104.035,44.501,-104.036,44.751,-104.037,45.001]},{"z":2,"b":[-104.011,45.943,-96.556,45.943],"l":[-96.556,45.943,-96.789,45.943,-97.022,45.943,-97.255,45.943,-97.488,45.943,-97.721,45.943,-97.954,45.943,-98.187,45.943,-98.42,45.943,-98.653,45.943,-98.886,45.943,-99.119,45.943,-99.352,45.943,-99.585,45.943,-99.818,45.943,-100.051,45.943,-100.284,45.943,-100.517,45.943,-100.75,45.943,-100.983,45.943,-101.216,45.943,-101.449,45.943,-101.682,45.943,-101.914,45.943,-102.147,45.943,-102.38,45.943,-102.613,45.943,-102.846,45.943,-103.079,45.943,-103.312,45.943,-103.545,45.943,-103.778,45.943,-104.011,45.943]},{"z":2,"b":[-104.029,41.001,-104.022,43.001],"l":[-104.022,41.001,-104.023,41.251,-104.023,41.501,-104.024,41.751,-104.025,42.001,-104.026,42.251,-104.027,42.501,-104.028,42.751,-104.029,43.001]},{"z":2,"b":[-104.022,40.001,-102.024,41.001],"l":[-102.024,40.001,-102.025,40.126,-102.025,40.251,-102.025,40.376,-102.025,40.501,-102.025,40.626,-102.025,40.751,-102.025,40.876,-102.025,41.001,-102.275,41.001,-102.524,41.001,-102.774,41.001,-103.023,41.001,-103.273,41.001,-103.522,41.001,-103.772,41.001,-104.022,41.001]},{"z":2,"b":[-102.024,37.001,-102.013,40.001],"l":[-102.013,37.001,-102.014,37.188,-102.014,37.376,-102.015,37.563,-102.016,37.751,-102.016,37.938,-102.017,38.126,-102.018,38.313,-102.019,38.501,-102.019,38.688,-102.02,38.876,-102.021,39.064,-102.022,39.251,-102.022,39.439,-102.023,39.626,-102.024,39.814,-102.024,40.001]},{"z":2,"b":[-102.024,40.001,-95.347,40.001],"l":[-95.347,40.001,-95.527,40.001,-95.737,40.001,-95.946,40.001,-96.156,40.001,-96.365,40.001,-96.575,40.001,-96.784,40.001,-96.994,40.001,-97.204,40.001,-97.413,40.001,-97.623,40.001,-97.832,40.001,-98.042,40.001,-98.252,40.001,-98.461,40.001,-98.671,40.001,-98.88,40.001,-99.09,40.001,-99.3,40.001,-99.509,40.001,-99.719,40.001,-99.928,40.001,-100.138,40.001,-100.348,40.001,-100.557,40.001,-100.767,40.001,-100.976,40.001,-101.186,40.001,-101.396,40.001,-101.605,40.001,-101.815,40.001,-102.024,40.001]},{"z":2,"b":[-103.001,36.501,-103,37.001],"l":[-103,36.501,-103,36.626,-103.001,36.751,-103.001,36.876,-103.001,37.001]},{"z":2,"b":[-103.001,37.001,-102.013,37.001],"l":[-102.013,37.001,-102.26,37.001,-102.507,37.001,-102.754,37.001,-103.001,37.001]},{"z":2,"b":[-103,33.648,-94.484,36.501],"l":[-94.484,33.648,-94.709,33.699,-94.766,33.732,-94.869,33.768,-94.962,33.843,-95.146,33.937,-95.216,33.959,-95.238,33.956,-95.276,33.911,-95.374,33.874,-95.427,33.873,-95.523,33.887,-95.542,33.898,-95.556,33.926,-95.594,33.945,-95.623,33.93,-95.763,33.891,-95.784,33.865,-95.863,33.863,-95.942,33.883,-96.139,33.822,-96.207,33.774,-96.279,33.758,-96.315,33.711,-96.337,33.718,-96.411,33.775,-96.487,33.792,-96.592,33.849,-96.595,33.899,-96.606,33.906,-96.625,33.911,-96.649,33.906,-96.694,33.864,-96.72,33.853,-96.746,33.854,-96.848,33.883,-96.89,33.935,-96.911,33.95,-96.934,33.953,-96.978,33.938,-97.002,33.882,-97.022,33.867,-97.064,33.857,-97.061,33.829,-97.099,33.75,-97.119,33.735,-97.145,33.738,-97.172,33.757,-97.192,33.785,-97.201,33.808,-97.186,33.87,-97.195,33.891,-97.215,33.897,-97.265,33.868,-97.308,33.869,-97.364,33.84,-97.398,33.831,-97.43,33.839,-97.482,33.886,-97.571,33.916,-97.606,33.972,-97.622,33.988,-97.641,33.992,-97.66,33.99,-97.692,33.972,-97.795,33.899,-97.842,33.876,-97.883,33.871,-97.922,33.878,-97.946,33.897,-97.951,33.971,-97.974,33.994,-98.069,34.04,-98.09,34.084,-98.099,34.145,-98.214,34.135,-98.356,34.142,-98.42,34.088,-98.454,34.078,-98.494,34.089,-98.608,34.161,-98.73,34.147,-98.799,34.155,-98.969,34.209,-99.102,34.213,-99.158,34.223,-99.187,34.235,-99.2,34.318,-99.253,34.388,-99.27,34.407,-99.36,34.456,-99.375,34.458,-99.385,34.447,-99.405,34.392,-99.432,34.389,-99.536,34.405,-99.632,34.388,-99.686,34.416,-99.867,34.543,-99.941,34.576,-99.999,34.587,-100,34.706,-100,34.826,-100,34.946,-100,35.065,-100,35.185,-100.001,35.305,-100.001,35.424,-100.001,35.544,-100.001,35.663,-100.001,35.783,-100.001,35.903,-100.002,36.022,-100.002,36.142,-100.002,36.262,-100.002,36.381,-100.002,36.501,-100.19,36.501,-100.377,36.501,-100.565,36.501,-100.752,36.501,-100.939,36.501,-101.127,36.501,-101.314,36.501,-101.501,36.501,-101.689,36.501,-101.876,36.501,-102.063,36.501,-102.251,36.501,-102.438,36.501,-102.626,36.501,-102.813,36.501,-103,36.501]},{"z":2,"b":[-102.013,37.001,-94.618,37.001],"l":[-94.618,37.001,-94.849,37.001,-95.08,37.001,-95.311,37.001,-95.542,37.001,-95.774,37.001,-96.005,37.001,-96.236,37.001,-96.467,37.001,-96.698,37.001,-96.929,37.001,-97.16,37.001,-97.391,37.001,-97.622,37.001,-97.853,37.001,-98.084,37.001,-98.315,37.001,-98.547,37.001,-98.778,37.001,-99.009,37.001,-99.24,37.001,-99.471,37.001,-99.702,37.001,-99.933,37.001,-100.164,37.001,-100.395,37.001,-100.626,37.001,-100.857,37.001,-101.088,37.001,-101.32,37.001,-101.551,37.001,-101.782,37.001,-102.013,37.001]},{"z":2,"b":[-96.627,42.511,-96.454,43.501],"l":[-96.454,43.501,-96.599,43.498,-96.581,43.435,-96.542,43.373,-96.535,43.338,-96.543,43.309,-96.57,43.283,-96.563,43.243,-96.555,43.232,-96.49,43.206,-96.468,43.17,-96.461,43.13,-96.463,43.099,-96.502,43.043,-96.513,42.969,-96.563,42.859,-96.616,42.791,-96.627,42.757,-96.622,42.73,-96.539,42.659,-96.534,42.63,-96.506,42.594,-96.481,42.511]},{"z":2,"b":[-94.625,37.001,-94.618,39.086],"l":[-94.625,39.086,-94.624,38.956,-94.624,38.825,-94.623,38.695,-94.623,38.565,-94.623,38.434,-94.622,38.304,-94.622,38.174,-94.621,38.043,-94.621,37.913,-94.62,37.783,-94.62,37.652,-94.62,37.522,-94.619,37.392,-94.619,37.261,-94.618,37.131,-94.618,37.001]},{"z":2,"b":[-94.625,39.086,-94.622,39.124],"l":[-94.622,39.124,-94.625,39.086]},{"z":2,"b":[-94.618,36.501,-94.618,37.001],"l":[-94.618,37.001,-94.618,36.876,-94.618,36.751,-94.618,36.626,-94.618,36.501]},{"z":2,"b":[-95.765,40.379,-91.442,40.625],"l":[-95.765,40.602,-95.515,40.603,-95.263,40.604,-95.011,40.606,-94.759,40.607,-94.506,40.609,-94.254,40.61,-94.002,40.612,-93.75,40.613,-93.497,40.615,-93.245,40.616,-92.993,40.617,-92.741,40.619,-92.488,40.62,-92.236,40.622,-91.984,40.623,-91.732,40.625,-91.682,40.552,-91.627,40.531,-91.617,40.507,-91.545,40.469,-91.53,40.432,-91.442,40.379]},{"z":2,"b":[-94.047,33.012,-94.041,33.554],"l":[-94.041,33.012,-94.043,33.148,-94.044,33.283,-94.046,33.419,-94.047,33.554]},{"z":2,"b":[-94.618,33.648,-94.439,36.501],"l":[-94.618,36.501,-94.596,36.362,-94.574,36.222,-94.551,36.083,-94.529,35.944,-94.506,35.805,-94.484,35.666,-94.462,35.526,-94.439,35.387,-94.442,35.279,-94.445,35.17,-94.448,35.061,-94.45,34.953,-94.453,34.844,-94.456,34.735,-94.459,34.627,-94.462,34.518,-94.464,34.409,-94.467,34.3,-94.47,34.192,-94.473,34.083,-94.476,33.974,-94.479,33.866,-94.481,33.757,-94.484,33.648]},{"z":2,"b":[-94.618,35.993,-89.705,36.501],"l":[-94.618,36.501,-94.34,36.501,-94.061,36.501,-93.783,36.501,-93.504,36.501,-93.226,36.501,-92.947,36.501,-92.669,36.501,-92.39,36.501,-92.112,36.501,-91.833,36.501,-91.555,36.501,-91.276,36.501,-90.998,36.501,-90.719,36.501,-90.441,36.501,-90.162,36.501,-90.118,36.423,-90.074,36.372,-90.067,36.334,-90.076,36.297,-90.149,36.216,-90.216,36.178,-90.253,36.138,-90.303,36.099,-90.381,35.993,-90.217,35.995,-90.052,35.997,-89.888,35.999,-89.705,36.002]},{"z":2,"b":[-80.521,40.647,-80.519,42.324],"l":[-80.519,40.647,-80.52,40.753,-80.52,40.858,-80.52,40.962,-80.52,41.067,-80.52,41.172,-80.52,41.277,-80.52,41.382,-80.52,41.487,-80.52,41.591,-80.52,41.696,-80.52,41.801,-80.52,41.906,-80.52,42.011,-80.52,42.116,-80.52,42.22,-80.521,42.324]},{"z":2,"b":[-80.52,39.723,-79.477,40.647],"l":[-79.477,39.723,-79.608,39.723,-79.738,39.723,-79.868,39.723,-79.998,39.723,-80.129,39.723,-80.259,39.723,-80.389,39.723,-80.519,39.723,-80.52,39.838,-80.52,39.954,-80.52,40.069,-80.52,40.185,-80.52,40.301,-80.52,40.416,-80.52,40.532,-80.519,40.647]},{"z":2,"b":[-94.043,29.977,-93.531,33.012],"l":[-93.794,29.977,-93.727,30.077,-93.711,30.113,-93.721,30.283,-93.75,30.345,-93.747,30.38,-93.715,30.473,-93.72,30.558,-93.665,30.661,-93.625,30.714,-93.579,30.824,-93.569,30.895,-93.549,30.948,-93.563,31.005,-93.531,31.046,-93.551,31.104,-93.558,31.18,-93.597,31.21,-93.666,31.323,-93.664,31.372,-93.736,31.478,-93.738,31.514,-93.807,31.569,-93.821,31.604,-93.827,31.75,-93.846,31.793,-93.905,31.877,-94.043,31.999,-94.043,32.126,-94.043,32.252,-94.043,32.379,-94.042,32.506,-94.042,32.632,-94.042,32.759,-94.042,32.885,-94.041,33.012]},{"z":2,"b":[-94.041,33.012,-91.15,33.016],"l":[-94.041,33.012,-93.862,33.012,-93.682,33.012,-93.502,33.013,-93.323,33.013,-93.143,33.013,-92.963,33.013,-92.784,33.013,-92.604,33.014,-92.424,33.014,-92.245,33.014,-92.065,33.014,-91.885,33.015,-91.706,33.015,-91.526,33.015,-91.346,33.015,-91.15,33.016]},{"z":2,"b":[-90.294,34.999,-88.173,35.001],"l":[-90.294,35.001,-90.039,35.001,-89.773,35.001,-89.507,35,-89.242,35,-88.976,35,-88.71,34.999,-88.445,34.999,-88.173,34.999]},{"z":2,"b":[-87.607,30.378,-85.007,31.002],"l":[-85.007,31.002,-85.33,31.001,-85.653,31.001,-85.977,31.001,-86.3,31.001,-86.624,31.001,-86.947,31.001,-87.27,31.001,-87.594,31.001,-87.607,30.929,-87.601,30.861,-87.566,30.796,-87.422,30.672,-87.408,30.641,-87.434,30.546,-87.429,30.478,-87.445,30.443,-87.48,30.412,-87.49,30.378]},{"z":2,"b":[-83.097,35.001,-83.096,35.002],"l":[-83.097,35.002,-83.096,35.001]},{"z":2,"b":[-85.624,34.988,-84.325,35.001],"l":[-84.325,34.988,-84.649,34.991,-84.974,34.994,-85.299,34.998,-85.624,35.001]},{"z":2,"b":[-84.325,34.988,-83.115,35.001],"l":[-83.115,35.001,-83.417,34.998,-83.72,34.995,-84.022,34.991,-84.325,34.988]},{"z":2,"b":[-83.115,35.001,-82.778,35.087],"l":[-82.778,35.087,-82.937,35.044,-83.097,35.002,-83.115,35.001]},{"z":2,"b":[-81.045,33.877,-78.564,35.114],"l":[-81.045,35.066,-80.934,35.114,-80.882,35.058,-80.793,34.963,-80.789,34.907,-80.784,34.829,-80.643,34.825,-80.501,34.821,-80.36,34.817,-80.219,34.813,-80.078,34.809,-79.936,34.805,-79.795,34.801,-79.654,34.797,-79.519,34.683,-79.383,34.569,-79.248,34.454,-79.113,34.34,-78.978,34.226,-78.843,34.112,-78.708,33.998,-78.564,33.877]},{"z":2,"b":[-82.356,35.156,-81.087,35.202],"l":[-82.356,35.202,-82.197,35.196,-82.039,35.19,-81.88,35.184,-81.722,35.179,-81.563,35.173,-81.405,35.167,-81.246,35.161,-81.087,35.156]},{"z":2,"b":[-71.801,41.707,-71.234,42.017],"l":[-71.801,42.012,-71.726,42.013,-71.623,42.014,-71.524,42.015,-71.463,42.016,-71.387,42.017,-71.384,41.972,-71.379,41.902,-71.338,41.891,-71.339,41.835,-71.341,41.798,-71.305,41.775,-71.268,41.751,-71.234,41.707]},{"z":2,"b":[-73.481,42.008,-71.801,42.056],"l":[-73.481,42.056,-73.396,42.053,-73.312,42.05,-73.228,42.048,-73.144,42.045,-73.06,42.042,-72.976,42.039,-72.892,42.037,-72.808,42.034,-72.806,42.008,-72.763,42.011,-72.758,42.034,-72.638,42.033,-72.519,42.031,-72.399,42.03,-72.28,42.028,-72.16,42.027,-72.041,42.026,-71.921,42.024,-71.802,42.023,-71.801,42.012]},{"z":2,"b":[-71.842,41.336,-71.796,42.012],"l":[-71.801,42.012,-71.8,41.958,-71.8,41.883,-71.799,41.829,-71.798,41.753,-71.797,41.668,-71.797,41.589,-71.796,41.52,-71.799,41.479,-71.805,41.417,-71.83,41.393,-71.842,41.336]},{"z":2,"b":[-81.971,37.208,-77.727,39.452],"l":[-81.965,37.54,-81.958,37.508,-81.971,37.473,-81.939,37.433,-81.913,37.369,-81.876,37.33,-81.829,37.299,-81.752,37.271,-81.661,37.222,-81.589,37.208,-81.565,37.21,-81.486,37.257,-81.419,37.282,-81.35,37.334,-81.227,37.263,-81.178,37.263,-81.057,37.294,-80.944,37.302,-80.915,37.311,-80.873,37.341,-80.872,37.399,-80.862,37.415,-80.843,37.422,-80.821,37.423,-80.77,37.402,-80.72,37.401,-80.507,37.469,-80.491,37.464,-80.465,37.433,-80.371,37.473,-80.316,37.51,-80.292,37.566,-80.246,37.621,-80.255,37.645,-80.285,37.677,-80.246,37.751,-80.188,37.824,-79.972,38.046,-79.903,38.181,-79.813,38.267,-79.783,38.336,-79.715,38.404,-79.669,38.544,-79.64,38.572,-79.598,38.577,-79.555,38.559,-79.525,38.533,-79.505,38.491,-79.396,38.454,-79.304,38.45,-79.24,38.477,-79.203,38.526,-79.153,38.618,-79.104,38.673,-79.073,38.742,-79.041,38.79,-78.992,38.82,-78.869,38.818,-78.805,38.86,-78.757,38.908,-78.727,38.924,-78.693,38.922,-78.581,38.999,-78.435,39.161,-78.404,39.263,-78.366,39.336,-78.351,39.411,-78.322,39.452,-78.193,39.37,-78.118,39.323,-77.949,39.216,-77.836,39.145,-77.792,39.227,-77.727,39.346]},{"z":2,"b":[-79.477,39.722,-75.785,39.723],"l":[-79.477,39.723,-79.247,39.723,-79.016,39.723,-78.785,39.723,-78.554,39.723,-78.324,39.723,-78.093,39.723,-77.862,39.723,-77.631,39.723,-77.4,39.723,-77.169,39.723,-76.939,39.723,-76.708,39.722,-76.477,39.722,-76.246,39.722,-76.016,39.722,-75.785,39.722]},{"z":2,"b":[-79.488,39.211,-79.477,39.723],"l":[-79.477,39.723,-79.48,39.595,-79.483,39.467,-79.485,39.339,-79.488,39.211]},{"z":2,"b":[-75.785,38.455,-75.038,39.722],"l":[-75.785,39.722,-75.775,39.564,-75.765,39.406,-75.756,39.247,-75.746,39.089,-75.736,38.93,-75.726,38.772,-75.717,38.613,-75.707,38.455,-75.705,38.455,-75.702,38.455,-75.7,38.455,-75.698,38.455,-75.696,38.455,-75.693,38.455,-75.691,38.455,-75.689,38.455,-75.588,38.455,-75.366,38.455,-75.145,38.455,-75.038,38.456]},{"z":2,"b":[-75.659,37.954,-75.224,38.041],"l":[-75.659,37.954,-75.62,37.999,-75.469,38.015,-75.376,38.025,-75.252,38.037,-75.224,38.041]},{"z":2,"b":[-77.122,38.807,-76.931,39.012],"l":[-77.02,38.807,-76.931,38.888,-76.989,38.952,-77.042,39.012,-77.122,38.944]},{"z":2,"b":[-79.763,41.357,-74.699,42.539],"l":[-74.699,41.357,-74.72,41.395,-74.757,41.424,-74.935,41.474,-74.981,41.508,-75.023,41.553,-75.051,41.608,-75.066,41.667,-75.067,41.713,-75.054,41.765,-75.095,41.799,-75.096,41.832,-75.122,41.853,-75.239,41.892,-75.274,41.947,-75.351,41.998,-75.489,41.998,-75.627,41.999,-75.765,41.999,-75.903,41.999,-76.041,41.999,-76.179,41.999,-76.316,41.999,-76.454,41.999,-76.592,41.999,-76.73,41.999,-76.868,41.999,-77.006,41.999,-77.144,41.999,-77.281,42,-77.419,42,-77.557,42,-77.695,42,-77.833,42,-77.971,42,-78.109,42,-78.246,42,-78.384,42,-78.522,42,-78.66,42,-78.798,42,-78.936,42,-79.074,42.001,-79.212,42.001,-79.349,42.001,-79.487,42.001,-79.625,42.001,-79.763,42.001,-79.763,42.136,-79.763,42.27,-79.763,42.405,-79.762,42.539]},{"z":2,"b":[-73.507,42.056,-73.253,42.752],"l":[-73.253,42.752,-73.285,42.668,-73.317,42.584,-73.349,42.5,-73.38,42.416,-73.412,42.332,-73.444,42.248,-73.476,42.164,-73.507,42.08,-73.481,42.056]},{"z":2,"b":[-73.723,40.992,-73.481,42.056],"l":[-73.481,42.056,-73.489,41.961,-73.497,41.866,-73.505,41.771,-73.513,41.676,-73.521,41.581,-73.529,41.486,-73.537,41.391,-73.545,41.296,-73.514,41.257,-73.484,41.219,-73.522,41.201,-73.58,41.173,-73.642,41.143,-73.723,41.105,-73.682,41.055,-73.63,40.992]},{"z":2,"b":[-74.699,40.992,-73.91,41.357],"l":[-74.699,41.357,-74.6,41.312,-74.501,41.266,-74.402,41.22,-74.303,41.175,-74.204,41.129,-74.105,41.083,-74.006,41.037,-73.91,40.992]},{"z":2,"b":[-73.253,42.73,-72.467,42.752],"l":[-72.467,42.73,-72.565,42.733,-72.663,42.736,-72.762,42.739,-72.86,42.741,-72.958,42.744,-73.057,42.747,-73.155,42.749,-73.253,42.752]},{"z":2,"b":[-73.426,42.752,-73.247,45.005],"l":[-73.253,42.752,-73.28,42.813,-73.267,42.865,-73.265,42.935,-73.262,43.027,-73.259,43.128,-73.255,43.265,-73.253,43.357,-73.25,43.44,-73.247,43.553,-73.306,43.616,-73.33,43.627,-73.349,43.62,-73.391,43.579,-73.405,43.579,-73.416,43.59,-73.413,43.628,-73.376,43.762,-73.375,43.804,-73.383,43.876,-73.425,44.038,-73.426,44.075,-73.41,44.131,-73.381,44.187,-73.332,44.245,-73.321,44.269,-73.325,44.368,-73.31,44.46,-73.372,44.597,-73.349,44.775,-73.366,44.86,-73.345,44.939,-73.352,45.005]},{"z":2,"b":[-72.467,42.702,-70.806,42.882],"l":[-70.806,42.877,-70.923,42.882,-70.974,42.872,-71.076,42.825,-71.139,42.808,-71.242,42.73,-71.33,42.702,-71.472,42.706,-71.614,42.709,-71.756,42.713,-71.898,42.716,-72.04,42.72,-72.183,42.723,-72.325,42.727,-72.467,42.73]},{"z":2,"b":[-71.085,43.07,-70.733,45.294],"l":[-70.733,43.07,-70.813,43.164,-70.829,43.239,-70.92,43.328,-70.956,43.389,-70.968,43.458,-70.962,43.532,-70.97,43.643,-70.978,43.754,-70.985,43.865,-70.993,43.976,-71.001,44.086,-71.008,44.197,-71.016,44.308,-71.024,44.419,-71.031,44.53,-71.039,44.641,-71.047,44.751,-71.054,44.862,-71.062,44.973,-71.07,45.084,-71.077,45.195,-71.085,45.294]},{"z":2,"b":[-96.851,43.501,-96.454,45.943],"l":[-96.454,43.501,-96.454,43.726,-96.454,43.95,-96.454,44.175,-96.454,44.399,-96.454,44.624,-96.454,44.848,-96.454,45.073,-96.454,45.297,-96.53,45.372,-96.696,45.432,-96.738,45.468,-96.815,45.557,-96.845,45.596,-96.851,45.626,-96.814,45.659,-96.663,45.756,-96.615,45.8,-96.591,45.843,-96.556,45.943]},{"z":2,"b":[-96.454,43.501,-91.245,43.502],"l":[-91.245,43.502,-91.575,43.502,-91.901,43.502,-92.226,43.502,-92.551,43.502,-92.876,43.502,-93.201,43.502,-93.527,43.502,-93.852,43.502,-94.177,43.502,-94.502,43.501,-94.828,43.501,-95.153,43.501,-95.478,43.501,-95.803,43.501,-96.128,43.501,-96.454,43.501]},{"z":2,"b":[-84.796,41.701,-83.122,41.95],"l":[-84.796,41.701,-84.463,41.711,-84.13,41.72,-83.797,41.73,-83.464,41.739,-83.304,41.838,-83.122,41.95]},{"z":2,"b":[-89.958,47.287,-89.498,47.998],"l":[-89.958,47.287,-89.843,47.464,-89.728,47.642,-89.614,47.819,-89.498,47.998]},{"z":2,"b":[-87.221,41.761,-87.039,42.493],"l":[-87.221,41.761,-87.176,41.944,-87.13,42.127,-87.085,42.31,-87.039,42.493]},{"z":2,"b":[-90.651,42.493,-87.039,42.513],"l":[-90.651,42.513,-90.191,42.51,-89.741,42.508,-89.291,42.505,-88.84,42.503,-88.39,42.5,-87.94,42.498,-87.489,42.495,-87.039,42.493]},{"z":2,"b":[-84.822,39.092,-84.796,41.701],"l":[-84.822,39.092,-84.82,39.424,-84.817,39.749,-84.813,40.075,-84.81,40.4,-84.806,40.725,-84.803,41.051,-84.8,41.376,-84.796,41.701]},{"z":2,"b":[-87.221,41.701,-84.796,41.761],"l":[-87.221,41.761,-86.918,41.761,-86.615,41.76,-86.312,41.76,-86.009,41.76,-85.706,41.76,-85.403,41.76,-85.1,41.76,-84.796,41.76,-84.796,41.701]},{"z":2,"b":[-90.413,42.493,-86.265,47.287],"l":[-87.039,42.493,-87.079,42.782,-87.114,43.031,-87.155,43.327,-87.145,43.571,-87.142,43.658,-87.083,43.89,-87.032,44.091,-86.919,44.362,-86.822,44.591,-86.715,44.846,-86.536,45.041,-86.401,45.133,-86.265,45.227,-86.482,45.323,-86.774,45.452,-86.942,45.452,-87.115,45.452,-87.244,45.29,-87.42,45.184,-87.46,45.068,-87.654,45.122,-87.694,45.161,-87.719,45.201,-87.721,45.243,-87.663,45.345,-87.658,45.362,-87.664,45.371,-87.696,45.383,-87.769,45.364,-87.842,45.361,-87.865,45.368,-87.875,45.385,-87.874,45.413,-87.859,45.445,-87.815,45.506,-87.815,45.563,-87.795,45.596,-87.814,45.657,-87.809,45.686,-87.84,45.718,-87.923,45.76,-88.116,45.816,-88.096,45.878,-88.104,45.905,-88.152,45.945,-88.355,45.992,-88.493,46.014,-88.63,46.014,-88.727,46.031,-88.837,46.037,-88.908,46.065,-88.978,46.093,-89.049,46.121,-89.119,46.149,-89.244,46.173,-89.369,46.197,-89.494,46.221,-89.619,46.245,-89.744,46.269,-89.869,46.294,-89.994,46.318,-90.119,46.342,-90.147,46.384,-90.174,46.426,-90.202,46.468,-90.23,46.51,-90.288,46.527,-90.318,46.537,-90.341,46.558,-90.392,46.549,-90.413,46.585,-90.356,46.673,-90.299,46.761,-90.242,46.848,-90.185,46.936,-90.128,47.024,-90.071,47.112,-90.015,47.199,-89.958,47.287]},{"z":2,"b":[-91.656,31.001,-89.735,31.002],"l":[-91.656,31.002,-91.417,31.001,-91.177,31.001,-90.937,31.001,-90.696,31.001,-90.456,31.001,-90.216,31.001,-89.976,31.001,-89.735,31.001]},{"z":2,"b":[-89.557,36.501,-89.487,36.503],"l":[-89.487,36.503,-89.557,36.501]},{"z":2,"b":[-120.001,34.991,-114.611,42.001],"l":[-114.611,34.991,-114.882,35.203,-115.136,35.401,-115.39,35.599,-115.644,35.797,-115.898,35.995,-116.152,36.194,-116.406,36.392,-116.66,36.59,-116.843,36.728,-117.026,36.866,-117.21,37.003,-117.393,37.141,-117.576,37.279,-117.76,37.417,-117.943,37.555,-118.126,37.692,-118.36,37.856,-118.595,38.019,-118.829,38.183,-119.063,38.346,-119.297,38.51,-119.532,38.673,-119.766,38.837,-120,39,-120,39.188,-120,39.375,-120,39.563,-120,39.75,-120,39.938,-120,40.125,-120,40.313,-120,40.5,-120.001,40.688,-120.001,40.875,-120.001,41.063,-120.001,41.251,-120.001,41.438,-120.001,41.626,-120.001,41.813,-120.001,42.001]},{"z":2,"b":[-68.379,47.589,-67.613,47.999],"l":[-67.613,47.999,-67.739,47.999,-67.913,47.999,-68.023,47.999,-68.114,47.999,-68.114,47.929,-68.252,47.929,-68.378,47.929,-68.378,47.838,-68.379,47.765,-68.379,47.692,-68.379,47.589]},{"z":2,"b":[-139.057,60.001,-123.819,60.002],"l":[-123.819,60.001,-124.771,60.001,-125.723,60.001,-126.674,60.001,-127.626,60.001,-128.578,60.001,-129.53,60.001,-130.481,60.001,-131.433,60.001,-132.385,60.001,-133.337,60.001,-134.288,60.001,-135.24,60.001,-136.192,60.001,-137.144,60.001,-138.095,60.001,-139.057,60.002]},{"z":2,"b":[-123.819,60.001,-120,60.001],"l":[-120,60.001,-120.477,60.001,-120.955,60.001,-121.432,60.001,-121.91,60.001,-122.387,60.001,-122.864,60.001,-123.342,60.001,-123.819,60.001]},{"z":2,"b":[-64.315,45.836,-64.031,46.013],"l":[-64.031,46.013,-64.051,45.978,-64.16,45.965,-64.216,45.901,-64.273,45.85,-64.315,45.836]},{"z":2,"b":[-67.761,51.443,-57.1,60.306],"l":[-64.614,60.306,-64.816,60.267,-64.847,60.251,-64.847,60.238,-64.789,60.196,-64.645,60.161,-64.622,60.141,-64.65,60.11,-64.711,60.091,-64.782,60.064,-64.88,60.05,-64.887,60.035,-64.868,60.009,-64.834,59.986,-64.682,59.943,-64.665,59.933,-64.661,59.922,-64.666,59.906,-64.762,59.864,-64.791,59.816,-64.818,59.54,-64.776,59.488,-64.715,59.466,-64.445,59.528,-64.385,59.515,-64.379,59.475,-64.426,59.456,-64.503,59.399,-64.519,59.356,-64.519,59.226,-64.505,59.166,-64.478,59.116,-64.442,59.091,-64.332,59.044,-64.324,59.02,-64.358,59.002,-64.394,58.998,-64.77,59.049,-64.802,59.04,-64.833,59.02,-64.851,58.984,-64.848,58.949,-64.809,58.929,-64.618,58.911,-64.294,58.866,-64.236,58.786,-64.192,58.769,-64.155,58.771,-63.991,58.811,-63.727,58.862,-63.666,58.854,-63.592,58.83,-63.535,58.799,-63.504,58.771,-63.497,58.753,-63.506,58.743,-63.553,58.732,-63.961,58.684,-64.031,58.666,-64.071,58.642,-64.085,58.621,-64.086,58.599,-64.062,58.555,-64.039,58.545,-63.882,58.555,-63.841,58.505,-63.837,58.483,-63.848,58.46,-64.023,58.399,-64.098,58.359,-64.371,58.169,-64.395,58.144,-64.4,58.121,-64.387,58.099,-64.227,58.018,-64.134,57.875,-64.079,57.813,-64.045,57.785,-63.986,57.803,-63.961,57.802,-63.921,57.783,-63.872,57.731,-63.842,57.713,-63.793,57.706,-63.716,57.696,-63.618,57.726,-63.601,57.723,-63.598,57.692,-63.615,57.669,-63.744,57.581,-63.759,57.553,-63.762,57.513,-63.742,57.396,-63.8,57.319,-63.793,57.279,-63.76,57.241,-63.758,57.228,-63.792,57.13,-63.836,57.083,-63.876,57.023,-63.903,56.889,-63.95,56.863,-64.004,56.821,-64.03,56.787,-64.101,56.738,-64.116,56.707,-64.112,56.691,-63.955,56.527,-63.926,56.475,-63.955,56.439,-64.053,56.43,-64.136,56.395,-64.119,56.334,-64.121,56.299,-64.115,56.289,-64.084,56.275,-63.913,56.229,-63.899,56.212,-63.992,56.145,-64.004,56.097,-63.968,56.095,-63.886,56.088,-63.836,56.063,-63.578,56.026,-63.558,55.992,-63.586,55.976,-63.661,55.948,-63.698,55.923,-63.708,55.883,-63.701,55.861,-63.524,55.706,-63.415,55.596,-63.363,55.565,-63.305,55.542,-63.154,55.514,-63.151,55.484,-63.159,55.44,-63.151,55.404,-63.112,55.34,-63.161,55.307,-63.429,55.21,-63.511,55.168,-63.534,55.14,-63.551,55.086,-63.48,54.987,-63.476,54.962,-63.491,54.918,-63.57,54.776,-63.658,54.644,-63.699,54.615,-63.751,54.612,-63.91,54.634,-64.341,54.726,-64.815,54.734,-65.122,54.719,-65.676,54.745,-65.742,54.769,-65.797,54.805,-65.868,54.898,-65.884,54.907,-66.083,54.942,-66.157,54.968,-66.24,55.008,-66.618,55.256,-66.733,55.319,-66.774,55.328,-66.8,55.311,-66.797,55.294,-66.725,55.213,-66.719,55.193,-66.747,55.129,-66.693,54.998,-66.71,54.948,-66.657,54.847,-66.651,54.799,-66.724,54.748,-66.867,54.781,-67.167,54.937,-67.302,55.064,-67.339,55.075,-67.408,55.072,-67.426,55.059,-67.437,55.036,-67.416,54.984,-67.372,54.917,-67.29,54.826,-67.205,54.699,-67.199,54.682,-67.147,54.653,-67.15,54.617,-67.236,54.586,-67.286,54.519,-67.315,54.517,-67.537,54.573,-67.579,54.565,-67.601,54.553,-67.62,54.507,-67.61,54.452,-67.582,54.392,-67.515,54.282,-67.508,54.253,-67.511,54.236,-67.681,54.141,-67.731,54.1,-67.761,54.061,-67.757,54.034,-67.531,53.839,-67.471,53.796,-67.456,53.73,-67.477,53.635,-67.474,53.613,-67.457,53.601,-67.165,53.543,-67.128,53.503,-67.076,53.417,-67.026,53.393,-66.98,53.384,-66.944,53.355,-66.938,53.329,-66.965,53.183,-66.97,53.073,-67.028,52.98,-67.054,52.92,-67.07,52.833,-67.068,52.755,-67.029,52.72,-66.951,52.698,-66.897,52.694,-66.856,52.698,-66.815,52.687,-66.688,52.727,-66.671,52.749,-66.631,52.91,-66.615,52.932,-66.521,52.958,-66.49,53.005,-66.468,53.019,-66.428,53.011,-66.39,52.992,-66.328,52.934,-66.3,52.897,-66.31,52.861,-66.341,52.84,-66.357,52.806,-66.366,52.741,-66.417,52.688,-66.426,52.661,-66.394,52.418,-66.402,52.398,-66.463,52.341,-66.462,52.295,-66.439,52.229,-66.407,52.184,-66.379,52.165,-66.345,52.155,-66.327,52.189,-66.296,52.295,-66.28,52.301,-66.149,52.23,-66.097,52.21,-66.036,52.109,-65.979,52.081,-65.883,52.054,-65.827,52.052,-65.806,52.064,-65.781,52.104,-65.691,52.094,-65.631,52.073,-65.589,52.073,-65.552,52.094,-65.474,52.196,-65.452,52.203,-65.398,52.203,-65.268,52.185,-65.137,52.155,-65.036,52.113,-64.932,52.055,-64.765,51.905,-64.658,51.849,-64.627,51.825,-64.639,51.791,-64.693,51.763,-64.702,51.745,-64.697,51.72,-64.598,51.6,-64.506,51.609,-64.291,51.729,-64.289,51.769,-64.326,51.811,-64.335,51.842,-64.321,51.93,-64.271,52.083,-64.26,52.1,-64.16,52.148,-64.131,52.187,-64.103,52.378,-64.183,52.554,-64.187,52.626,-64.178,52.815,-64.139,52.854,-64.008,52.878,-63.973,52.895,-63.917,53.066,-63.905,53.077,-63.799,53.104,-63.723,53.109,-63.672,53.101,-63.606,53.035,-63.555,52.964,-63.549,52.94,-63.567,52.826,-63.563,52.797,-63.55,52.774,-63.41,52.689,-63.423,52.67,-63.523,52.649,-63.811,52.611,-63.899,52.589,-63.955,52.566,-64.018,52.525,-64.059,52.485,-64.066,52.466,-64.062,52.458,-63.951,52.429,-63.943,52.418,-63.935,52.381,-63.884,52.345,-63.766,52.298,-63.684,52.138,-63.668,52.094,-63.664,52.06,-63.704,52.048,-63.786,52.051,-63.792,52.044,-63.783,52.02,-63.757,52.011,-63.607,52.001,-63.2,52.001,-62.793,52.001,-62.387,52.001,-61.98,52.001,-61.574,52.001,-61.167,52.001,-60.76,52.001,-60.354,52.001,-59.947,52.001,-59.541,52.001,-59.134,52.001,-58.727,52.001,-58.321,52.001,-57.914,52.001,-57.508,52.001,-57.101,52.001,-57.101,51.711,-57.1,51.443]},{"z":2,"b":[-120.682,60.001,-101.987,78.687],"l":[-102.002,60.001,-102.002,60.242,-102.002,60.482,-102.002,60.723,-102.003,60.963,-102.003,61.204,-102.003,61.444,-102.003,61.685,-102.004,61.925,-102.004,62.166,-102.004,62.406,-102.004,62.647,-102.005,62.887,-102.005,63.128,-102.005,63.368,-102.005,63.609,-102.006,63.849,-101.987,64.08,-101.999,64.228,-102.435,64.263,-102.871,64.297,-103.307,64.332,-103.743,64.366,-104.178,64.401,-104.614,64.435,-105.05,64.469,-105.486,64.504,-105.922,64.538,-106.358,64.573,-106.794,64.607,-107.229,64.642,-107.665,64.676,-108.101,64.71,-108.537,64.745,-108.973,64.779,-109.215,64.813,-109.516,64.903,-109.805,65.051,-110.093,65.2,-110.381,65.349,-110.669,65.497,-111.128,65.498,-111.587,65.5,-112.045,65.501,-112.504,65.502,-112.759,65.58,-113.015,65.659,-113.27,65.737,-113.526,65.815,-113.781,65.893,-114.037,65.971,-114.292,66.049,-114.547,66.127,-114.803,66.206,-115.058,66.284,-115.314,66.362,-115.569,66.44,-115.825,66.518,-116.08,66.596,-116.336,66.674,-116.591,66.753,-116.846,66.831,-117.102,66.909,-117.357,66.987,-117.613,67.065,-117.868,67.143,-118.124,67.221,-118.379,67.3,-118.634,67.378,-118.89,67.456,-119.145,67.534,-119.401,67.612,-119.656,67.69,-119.912,67.768,-120.167,67.847,-120.422,67.925,-120.678,68.003,-120.678,68.209,-120.679,68.414,-120.679,68.62,-120.679,68.826,-120.68,69.031,-120.68,69.237,-120.682,69.567,-120.681,69.649,-120.203,69.649,-119.725,69.649,-119.246,69.649,-118.768,69.649,-118.29,69.649,-117.812,69.649,-117.334,69.649,-116.856,69.65,-116.65,69.65,-116.694,69.695,-116.821,69.76,-116.948,69.825,-117.075,69.889,-117.055,70.002,-116.596,70.002,-116.068,70.002,-115.539,70.002,-115.011,70.002,-114.483,70.002,-113.955,70.001,-113.427,70.001,-112.881,70.001,-112.878,69.912,-112.875,69.833,-112.762,69.834,-112.65,69.834,-112.5,69.912,-112.5,70.001,-112.188,70.001,-111.875,70.001,-111.563,70.001,-111.25,70.001,-110.938,70.001,-110.625,70.001,-110.313,70.001,-110,70.001,-110,70.544,-110,71.087,-110.001,71.63,-110.001,72.173,-110.001,72.716,-110.002,72.982,-110.001,73.259,-110.002,73.802,-110.002,74.345,-110.003,74.851,-110.002,74.888,-110.002,75.43,-110.003,75.973,-110.003,76.244,-110.003,76.48,-110.003,76.516,-110.003,77.059,-110.003,77.602,-110.004,77.929,-110.004,78.09,-110.004,78.145,-110.004,78.322,-110.004,78.687]},{"z":2,"b":[-110,60.001,-102.002,60.001],"l":[-102.002,60.001,-102.502,60.001,-103.001,60.001,-103.501,60.001,-104.001,60.001,-104.501,60.001,-105.001,60.001,-105.501,60.001,-106.001,60.001,-106.501,60.001,-107.001,60.001,-107.501,60.001,-108,60.001,-108.5,60.001,-109,60.001,-109.5,60.001,-110,60.001]},{"z":2,"b":[-120,60.001,-110,60.001],"l":[-120,60.001,-119.375,60.001,-118.75,60.001,-118.125,60.001,-117.5,60.001,-116.875,60.001,-116.25,60.001,-115.625,60.001,-115,60.001,-114.375,60.001,-113.75,60.001,-113.125,60.001,-112.5,60.001,-111.875,60.001,-111.25,60.001,-110.625,60.001,-110,60.001]},{"z":2,"b":[-110,48.993,-109.999,60.001],"l":[-110,60.001,-110,59.657,-110,59.313,-110,58.969,-110,58.625,-110,58.281,-110,57.937,-110,57.593,-110,57.249,-110,56.905,-110,56.561,-110,56.217,-110,55.873,-110,55.529,-110,55.185,-110,54.842,-110,54.498,-110,54.154,-110,53.81,-110,53.466,-110,53.122,-110,52.778,-110,52.434,-110,52.09,-110,51.746,-110,51.402,-110,51.058,-110,50.714,-109.999,50.37,-109.999,50.026,-109.999,49.682,-109.999,49.338,-109.999,48.993]},{"z":2,"b":[-95.16,49.37,-88.948,56.851],"l":[-88.948,56.851,-89.205,56.695,-89.464,56.535,-89.722,56.376,-89.981,56.216,-90.33,55.991,-90.68,55.767,-91.029,55.542,-91.378,55.317,-91.684,55.108,-91.989,54.899,-92.295,54.69,-92.6,54.481,-92.855,54.298,-93.11,54.114,-93.365,53.931,-93.62,53.748,-94.004,53.517,-94.387,53.285,-94.77,53.054,-95.154,52.822,-95.155,52.391,-95.156,51.96,-95.157,51.528,-95.158,51.097,-95.159,50.665,-95.159,50.234,-95.16,49.802,-95.155,49.37]},{"z":2,"b":[-102.002,60.001,-94.772,60.002],"l":[-102.002,60.001,-101.552,60.001,-101.102,60.001,-100.652,60.001,-100.202,60.001,-99.752,60.001,-99.302,60.001,-98.852,60.001,-98.402,60.001,-97.952,60.001,-97.502,60.001,-97.052,60.001,-96.603,60.001,-96.153,60.001,-95.703,60.001,-95.253,60.001,-94.772,60.002]},{"z":2,"b":[-102.002,48.993,-101.367,60.001],"l":[-102.002,60.001,-102.002,59.74,-102.002,59.478,-102.002,59.217,-102.002,58.955,-102.001,58.694,-102.001,58.432,-102.001,58.171,-102.001,57.909,-102.001,57.648,-102.001,57.386,-102.001,57.125,-102.001,56.863,-102.001,56.602,-102.001,56.34,-102.001,56.079,-102.001,55.818,-101.961,55.391,-101.921,54.965,-101.882,54.538,-101.842,54.112,-101.802,53.685,-101.762,53.259,-101.723,52.832,-101.683,52.406,-101.643,51.979,-101.603,51.553,-101.563,51.126,-101.524,50.7,-101.484,50.273,-101.444,49.847,-101.404,49.421,-101.367,48.993]},{"z":2,"b":[-123.221,45.591,-116.896,46.179],"l":[-116.896,46.002,-117.144,46.002,-117.41,46.002,-117.675,46.002,-117.941,46.002,-118.206,46.001,-118.472,46.001,-118.737,46.001,-119.002,46.001,-119.02,46,-119.05,45.981,-119.177,45.945,-119.429,45.934,-119.934,45.838,-120.156,45.762,-120.351,45.725,-120.716,45.69,-121.045,45.639,-121.184,45.644,-121.243,45.687,-121.402,45.712,-121.663,45.718,-121.882,45.689,-122.085,45.603,-122.15,45.594,-122.203,45.591,-122.561,45.625,-122.65,45.627,-122.726,45.674,-122.746,45.75,-122.843,45.978,-122.942,46.115,-123.042,46.16,-123.121,46.179,-123.221,46.154]},{"z":2,"b":[-117.039,46.002,-116.896,48.993],"l":[-116.896,46.002,-116.93,46.056,-116.948,46.124,-116.922,46.164,-116.943,46.232,-117.01,46.33,-117.039,46.397,-117.032,46.42,-117.025,46.429,-117.026,46.589,-117.027,46.75,-117.028,46.91,-117.028,47.07,-117.029,47.231,-117.03,47.391,-117.031,47.551,-117.032,47.712,-117.032,47.872,-117.033,48.032,-117.034,48.192,-117.035,48.353,-117.036,48.513,-117.037,48.673,-117.037,48.834,-117.039,48.993]},{"z":2,"b":[-117.202,42.001,-116.477,46.002],"l":[-117.017,42.001,-117.017,42.224,-117.018,42.447,-117.018,42.67,-117.018,42.893,-117.019,43.117,-117.019,43.34,-117.02,43.563,-117.02,43.808,-117.02,43.813,-116.958,43.964,-116.944,44.038,-116.961,44.079,-116.95,44.119,-116.911,44.157,-116.912,44.199,-116.953,44.245,-117.029,44.276,-117.14,44.29,-117.197,44.344,-117.202,44.436,-117.119,44.578,-116.949,44.77,-116.856,44.906,-116.84,44.985,-116.737,45.169,-116.549,45.457,-116.477,45.641,-116.521,45.722,-116.617,45.799,-116.765,45.873,-116.869,45.958,-116.896,46.002]},{"z":2,"b":[-114.725,32.715,-114.125,34.991],"l":[-114.611,34.991,-114.611,34.907,-114.557,34.795,-114.398,34.59,-114.379,34.541,-114.377,34.54,-114.371,34.488,-114.308,34.433,-114.159,34.355,-114.125,34.317,-114.125,34.314,-114.126,34.287,-114.164,34.253,-114.329,34.142,-114.418,34.051,-114.509,33.904,-114.495,33.785,-114.499,33.686,-114.522,33.608,-114.556,33.536,-114.603,33.47,-114.651,33.431,-114.702,33.418,-114.719,33.331,-114.704,33.17,-114.648,33.072,-114.551,33.037,-114.491,32.984,-114.469,32.912,-114.478,32.842,-114.516,32.773,-114.582,32.735,-114.721,32.724,-114.725,32.715]},{"z":2,"b":[-114.741,34.991,-114.04,37.004],"l":[-114.611,34.991,-114.611,34.998,-114.591,35.353,-114.649,35.476,-114.662,35.545,-114.645,35.631,-114.65,35.684,-114.677,35.73,-114.684,35.814,-114.687,35.917,-114.732,35.984,-114.741,36.014,-114.716,36.085,-114.669,36.122,-114.593,36.148,-114.505,36.156,-114.406,36.148,-114.332,36.116,-114.282,36.06,-114.232,36.032,-114.183,36.03,-114.119,36.077,-114.062,36.175,-114.043,36.182,-114.041,36.612,-114.04,37.004]},{"z":2,"b":[-106.668,31.768,-103,36.501],"l":[-103,36.501,-103.042,36.501,-103.044,36.219,-103.045,35.938,-103.047,35.657,-103.048,35.376,-103.049,35.095,-103.051,34.814,-103.052,34.533,-103.054,34.252,-103.055,33.971,-103.057,33.689,-103.058,33.408,-103.06,33.127,-103.061,32.846,-103.063,32.565,-103.064,32.284,-103.066,32.003,-103.29,32.003,-103.515,32.003,-103.74,32.002,-103.965,32.002,-104.19,32.002,-104.414,32.002,-104.639,32.002,-104.864,32.002,-105.089,32.002,-105.313,32.002,-105.538,32.002,-105.763,32.002,-105.988,32.002,-106.213,32.001,-106.437,32.001,-106.662,32.001,-106.668,32.001,-106.566,31.82,-106.445,31.768]},{"z":2,"b":[-95.347,39.124,-94.622,40.001],"l":[-95.347,40.001,-95.116,39.872,-95.056,39.871,-94.984,39.891,-94.93,39.861,-94.893,39.782,-94.899,39.741,-94.946,39.739,-95.005,39.692,-95.075,39.597,-95.058,39.509,-94.953,39.426,-94.882,39.343,-94.847,39.259,-94.778,39.194,-94.622,39.124]},{"z":2,"b":[-95.765,40.001,-95.347,40.602],"l":[-95.765,40.602,-95.74,40.571,-95.685,40.466,-95.662,40.363,-95.607,40.295,-95.521,40.265,-95.464,40.198,-95.434,40.096,-95.362,40.01,-95.347,40.001]},{"z":2,"b":[-96.481,40.602,-95.765,42.511],"l":[-96.481,42.511,-96.451,42.506,-96.384,42.418,-96.356,42.26,-96.295,42.133,-96.201,42.036,-96.154,41.965,-96.154,41.92,-96.131,41.872,-96.087,41.821,-96.069,41.734,-96.078,41.611,-96.059,41.546,-96.011,41.538,-95.991,41.52,-95.997,41.494,-95.982,41.474,-95.944,41.461,-95.933,41.425,-95.95,41.367,-95.936,41.331,-95.892,41.316,-95.884,41.288,-95.911,41.245,-95.907,41.212,-95.871,41.188,-95.847,41.08,-95.834,40.889,-95.838,40.778,-95.859,40.745,-95.826,40.676,-95.765,40.602]},{"z":2,"b":[-104.029,42.511,-96.481,43.001],"l":[-96.481,42.511,-96.558,42.525,-96.633,42.569,-96.676,42.638,-96.805,42.701,-97.02,42.76,-97.164,42.813,-97.239,42.861,-97.417,42.881,-97.698,42.875,-97.881,42.85,-97.965,42.806,-98.143,42.852,-98.442,43.001,-98.837,43.001,-99.183,43.001,-99.529,43.001,-99.875,43.001,-100.222,43.001,-100.568,43.001,-100.914,43.001,-101.26,43.001,-101.606,43.001,-101.952,43.001,-102.298,43.001,-102.644,43.001,-102.991,43.001,-103.337,43.001,-103.683,43.001,-104.029,43.001]},{"z":2,"b":[-91.656,31.002,-90.938,33.016],"l":[-91.15,33.016,-91.144,32.967,-91.096,32.938,-91.093,32.885,-91.136,32.809,-91.136,32.756,-91.092,32.726,-91.094,32.694,-91.142,32.661,-91.15,32.625,-91.116,32.586,-91.101,32.541,-91.104,32.49,-91.059,32.424,-90.99,32.381,-90.945,32.331,-90.94,32.303,-90.938,32.277,-90.948,32.24,-91.005,32.186,-91.048,32.146,-91.075,32.131,-91.089,32.104,-91.093,32.046,-91.157,31.971,-91.286,31.884,-91.373,31.774,-91.415,31.64,-91.459,31.555,-91.506,31.519,-91.529,31.449,-91.529,31.345,-91.559,31.288,-91.618,31.277,-91.631,31.218,-91.596,31.11,-91.611,31.065,-91.653,31.03,-91.656,31.002]},{"z":2,"b":[-91.215,33.016,-90.282,35.001],"l":[-90.294,35.001,-90.293,34.97,-90.282,34.945,-90.32,34.899,-90.416,34.852,-90.468,34.805,-90.475,34.76,-90.497,34.738,-90.539,34.728,-90.564,34.661,-90.584,34.504,-90.635,34.414,-90.718,34.392,-90.762,34.356,-90.768,34.308,-90.817,34.252,-90.91,34.189,-90.94,34.129,-90.909,34.072,-90.915,34.049,-90.943,34.03,-91.043,34.003,-91.06,33.989,-91.07,33.974,-91.05,33.945,-91.038,33.901,-91.053,33.828,-91.077,33.797,-91.1,33.767,-91.184,33.716,-91.207,33.676,-91.17,33.647,-91.173,33.617,-91.215,33.583,-91.203,33.547,-91.193,33.523,-91.154,33.5,-91.141,33.439,-91.168,33.359,-91.163,33.294,-91.117,33.241,-91.107,33.218,-91.153,33.041,-91.15,33.016]},{"z":2,"b":[-90.294,35.001,-89.689,36.002],"l":[-89.705,36.002,-89.709,35.983,-89.689,35.944,-89.689,35.921,-89.704,35.907,-89.738,35.899,-89.764,35.889,-89.769,35.872,-89.77,35.864,-89.768,35.845,-89.764,35.828,-89.78,35.806,-89.895,35.751,-89.944,35.679,-89.927,35.592,-89.967,35.503,-90.066,35.414,-90.108,35.314,-90.093,35.204,-90.142,35.114,-90.256,35.046,-90.294,35.004,-90.294,35.001]},{"z":2,"b":[-89.705,36.002,-89.541,36.501],"l":[-89.557,36.501,-89.541,36.427,-89.555,36.373,-89.604,36.352,-89.612,36.322,-89.584,36.288,-89.584,36.272,-89.595,36.259,-89.628,36.255,-89.654,36.248,-89.675,36.221,-89.634,36.168,-89.642,36.105,-89.698,36.03,-89.705,36.002]},{"z":2,"b":[-89.56,36.501,-89.487,36.555],"l":[-89.487,36.503,-89.497,36.529,-89.52,36.555,-89.536,36.555,-89.55,36.553,-89.555,36.541,-89.56,36.513,-89.557,36.501]},{"z":2,"b":[-89.487,36.489,-89.45,36.503],"l":[-89.45,36.5,-89.452,36.498,-89.471,36.489,-89.485,36.498,-89.487,36.503]},{"z":2,"b":[-89.45,36.5,-89.122,36.992],"l":[-89.154,36.992,-89.122,36.912,-89.13,36.811,-89.156,36.716,-89.203,36.626,-89.256,36.594,-89.28,36.6,-89.331,36.616,-89.351,36.611,-89.397,36.588,-89.45,36.5]},{"z":2,"b":[-91.502,36.992,-89.154,40.379],"l":[-91.442,40.379,-91.5,40.226,-91.502,40.098,-91.467,39.943,-91.426,39.827,-91.379,39.749,-91.21,39.603,-90.919,39.387,-90.737,39.182,-90.664,38.987,-90.577,38.91,-90.478,38.948,-90.354,38.93,-90.22,38.898,-90.15,38.86,-90.134,38.847,-90.127,38.833,-90.132,38.819,-90.151,38.781,-90.317,38.458,-90.374,38.31,-90.366,38.254,-90.299,38.174,-90.174,38.069,-90.064,38.001,-89.97,37.969,-89.823,37.884,-89.623,37.747,-89.523,37.645,-89.523,37.576,-89.503,37.506,-89.462,37.434,-89.462,37.366,-89.503,37.301,-89.487,37.213,-89.414,37.102,-89.352,37.036,-89.301,37.012,-89.29,37.013,-89.286,37.023,-89.293,37.038,-89.296,37.052,-89.292,37.071,-89.281,37.078,-89.272,37.08,-89.225,37.053,-89.154,36.992]},{"z":2,"b":[-91.442,40.379,-90.149,42.513],"l":[-90.651,42.513,-90.59,42.445,-90.485,42.383,-90.425,42.326,-90.41,42.272,-90.347,42.215,-90.233,42.154,-90.168,42.1,-90.149,42.054,-90.152,41.982,-90.177,41.884,-90.218,41.813,-90.276,41.77,-90.319,41.71,-90.348,41.632,-90.437,41.56,-90.587,41.495,-90.754,41.45,-90.939,41.426,-91.048,41.369,-91.082,41.28,-91.067,41.206,-91.001,41.146,-90.964,41.083,-90.954,41.016,-91,40.905,-91.102,40.75,-91.215,40.65,-91.339,40.604,-91.394,40.537,-91.381,40.448,-91.391,40.397,-91.442,40.379]},{"z":2,"b":[-91.245,42.513,-90.651,43.502],"l":[-91.245,43.502,-91.238,43.44,-91.219,43.395,-91.178,43.359,-91.117,43.331,-91.107,43.278,-91.148,43.2,-91.161,43.102,-91.148,42.986,-91.118,42.882,-91.07,42.789,-90.96,42.72,-90.789,42.677,-90.688,42.61,-90.657,42.521,-90.651,42.513]},{"z":2,"b":[-92.897,43.502,-89.958,47.309],"l":[-91.245,43.502,-91.278,43.797,-91.32,43.936,-91.383,43.991,-91.515,44.054,-91.716,44.126,-91.841,44.194,-91.912,44.289,-92.055,44.4,-92.149,44.445,-92.24,44.462,-92.313,44.498,-92.367,44.552,-92.442,44.587,-92.537,44.601,-92.644,44.645,-92.77,44.726,-92.773,44.732,-92.799,44.79,-92.785,44.823,-92.763,44.934,-92.765,44.969,-92.793,45.071,-92.759,45.111,-92.766,45.236,-92.754,45.278,-92.686,45.381,-92.683,45.433,-92.707,45.494,-92.757,45.543,-92.892,45.595,-92.897,45.659,-92.875,45.706,-92.786,45.796,-92.738,45.875,-92.694,45.909,-92.425,46.028,-92.367,46.036,-92.325,46.07,-92.293,46.084,-92.292,46.228,-92.292,46.373,-92.292,46.517,-92.292,46.661,-92.224,46.671,-92.195,46.707,-92.125,46.763,-92.093,46.762,-91.965,46.73,-91.841,46.779,-91.717,46.827,-91.593,46.876,-91.468,46.925,-91.265,47.021,-91.061,47.117,-90.858,47.213,-90.654,47.309,-90.48,47.304,-90.306,47.298,-90.132,47.293,-89.958,47.287]},{"z":2,"b":[-89.165,36.992,-88.042,37.789],"l":[-88.042,37.789,-88.073,37.771,-88.129,37.7,-88.14,37.621,-88.108,37.534,-88.191,37.465,-88.389,37.411,-88.495,37.364,-88.507,37.324,-88.493,37.262,-88.453,37.178,-88.446,37.12,-88.472,37.088,-88.614,37.11,-88.873,37.186,-89.046,37.183,-89.135,37.102,-89.165,37.042,-89.154,36.992]},{"z":2,"b":[-88.042,37.788,-84.79,39.092],"l":[-84.822,39.092,-84.837,39.083,-84.861,39.04,-84.835,38.996,-84.837,38.961,-84.864,38.933,-84.854,38.913,-84.807,38.9,-84.79,38.868,-84.801,38.819,-84.901,38.77,-85.094,38.714,-85.136,38.697,-85.17,38.69,-85.186,38.694,-85.211,38.704,-85.247,38.727,-85.307,38.737,-85.405,38.73,-85.445,38.683,-85.425,38.597,-85.459,38.53,-85.546,38.484,-85.609,38.423,-85.646,38.347,-85.706,38.301,-85.788,38.285,-85.855,38.215,-85.906,38.091,-85.961,38.014,-86.021,37.984,-86.103,37.992,-86.208,38.037,-86.268,38.083,-86.284,38.13,-86.315,38.164,-86.343,38.179,-86.361,38.179,-86.363,38.172,-86.355,38.159,-86.365,38.145,-86.43,38.14,-86.457,38.12,-86.448,38.086,-86.463,38.062,-86.5,38.048,-86.52,38.015,-86.522,37.964,-86.549,37.917,-86.599,37.875,-86.673,37.889,-86.77,37.96,-86.869,37.976,-86.969,37.938,-87.044,37.886,-87.094,37.821,-87.141,37.805,-87.209,37.855,-87.372,37.923,-87.457,37.938,-87.518,37.922,-87.561,37.929,-87.576,37.948,-87.587,37.956,-87.599,37.946,-87.601,37.887,-87.615,37.849,-87.643,37.833,-87.665,37.846,-87.679,37.886,-87.714,37.9,-87.77,37.885,-87.825,37.891,-87.874,37.916,-87.892,37.913,-87.906,37.902,-87.902,37.855,-87.912,37.818,-87.938,37.794,-87.975,37.788,-88.022,37.8,-88.042,37.789]},{"z":2,"b":[-84.822,38.448,-82.613,39.129],"l":[-82.613,38.448,-82.621,38.451,-82.758,38.541,-82.845,38.628,-82.88,38.71,-83,38.714,-83.203,38.639,-83.357,38.627,-83.461,38.68,-83.538,38.7,-83.616,38.683,-83.69,38.65,-83.752,38.669,-83.828,38.731,-83.948,38.774,-84.113,38.798,-84.227,38.865,-84.29,38.975,-84.358,39.05,-84.43,39.091,-84.509,39.103,-84.595,39.086,-84.661,39.094,-84.707,39.129,-84.766,39.125,-84.822,39.092]},{"z":2,"b":[-82.613,38.415,-80.519,40.647],"l":[-80.519,40.647,-80.638,40.603,-80.659,40.563,-80.623,40.512,-80.604,40.445,-80.602,40.364,-80.668,40.185,-80.804,39.909,-80.872,39.736,-80.874,39.664,-80.963,39.571,-81.139,39.456,-81.263,39.387,-81.333,39.363,-81.392,39.365,-81.44,39.393,-81.493,39.374,-81.552,39.308,-81.606,39.274,-81.657,39.272,-81.701,39.229,-81.737,39.147,-81.771,39.099,-81.803,39.086,-81.805,39.044,-81.778,38.973,-81.78,38.947,-81.795,38.946,-81.809,38.95,-81.844,38.934,-81.882,38.894,-81.897,38.894,-81.907,38.9,-81.919,38.952,-81.95,38.989,-82.001,39.011,-82.072,38.963,-82.163,38.845,-82.204,38.768,-82.188,38.715,-82.18,38.638,-82.202,38.606,-82.252,38.594,-82.291,38.552,-82.318,38.48,-82.387,38.434,-82.498,38.415,-82.613,38.448]},{"z":2,"b":[-88.095,37.789,-87.221,41.761],"l":[-88.042,37.789,-88.095,37.876,-88.091,37.897,-88.059,37.898,-88.042,37.937,-88.045,38.013,-88.026,38.06,-87.996,38.074,-87.99,38.089,-88.005,38.098,-88.006,38.113,-87.958,38.144,-87.949,38.178,-87.977,38.214,-87.98,38.249,-87.957,38.282,-87.921,38.298,-87.873,38.295,-87.827,38.336,-87.784,38.419,-87.735,38.47,-87.681,38.488,-87.636,38.543,-87.584,38.679,-87.529,38.742,-87.518,38.812,-87.53,38.909,-87.568,39.007,-87.632,39.107,-87.647,39.169,-87.615,39.193,-87.603,39.234,-87.611,39.291,-87.579,39.344,-87.533,39.375,-87.533,39.379,-87.531,39.662,-87.53,39.962,-87.528,40.262,-87.527,40.562,-87.525,40.861,-87.524,41.161,-87.523,41.461,-87.521,41.761,-87.371,41.761,-87.221,41.761]},{"z":2,"b":[-88.486,30.371,-88.085,34.999],"l":[-88.173,34.999,-88.085,34.933,-88.088,34.909,-88.089,34.893,-88.096,34.846,-88.12,34.661,-88.144,34.477,-88.169,34.292,-88.193,34.107,-88.218,33.923,-88.242,33.738,-88.267,33.553,-88.291,33.368,-88.315,33.184,-88.34,32.999,-88.364,32.814,-88.389,32.629,-88.413,32.445,-88.437,32.26,-88.462,32.075,-88.486,31.891,-88.476,31.703,-88.465,31.515,-88.455,31.327,-88.444,31.14,-88.434,30.952,-88.423,30.764,-88.413,30.577,-88.4,30.371]},{"z":2,"b":[-88.203,34.999,-85.624,35.025],"l":[-85.624,35.001,-85.784,35.002,-85.944,35.004,-86.105,35.005,-86.265,35.007,-86.426,35.008,-86.586,35.01,-86.747,35.011,-86.907,35.013,-87.067,35.014,-87.228,35.016,-87.388,35.017,-87.549,35.019,-87.709,35.02,-87.87,35.022,-88.03,35.023,-88.19,35.025,-88.203,35.024,-88.191,35.012,-88.173,34.999]},{"z":2,"b":[-89.45,36.5,-83.668,36.693],"l":[-83.668,36.605,-83.675,36.603,-83.683,36.6,-83.691,36.598,-83.699,36.596,-83.707,36.594,-83.715,36.591,-83.723,36.589,-83.73,36.587,-84.078,36.597,-84.425,36.606,-84.772,36.616,-85.119,36.626,-85.466,36.636,-85.813,36.645,-86.16,36.655,-86.508,36.665,-86.84,36.66,-87.173,36.654,-87.506,36.649,-87.839,36.644,-87.869,36.675,-87.956,36.682,-88.091,36.693,-88.096,36.693,-88.057,36.59,-88.056,36.501,-88.059,36.5,-88.216,36.5,-88.388,36.5,-88.561,36.5,-88.734,36.5,-88.906,36.5,-89.079,36.5,-89.252,36.5,-89.45,36.5]},{"z":2,"b":[-83.668,36.598,-81.659,36.619],"l":[-81.659,36.611,-81.801,36.615,-81.905,36.619,-81.953,36.598,-82.162,36.599,-82.329,36.6,-82.612,36.601,-82.956,36.602,-83.441,36.604,-83.668,36.605]},{"z":2,"b":[-81.659,36.551,-75.857,36.611],"l":[-75.857,36.551,-75.889,36.551,-75.966,36.551,-76.215,36.553,-76.577,36.556,-76.938,36.559,-77.3,36.561,-77.662,36.564,-78.024,36.567,-78.386,36.57,-78.747,36.573,-79.109,36.575,-79.471,36.578,-79.833,36.581,-80.194,36.584,-80.556,36.587,-80.918,36.589,-81.28,36.592,-81.642,36.595,-81.659,36.611]},{"z":2,"b":[-85.007,30.371,-81.504,31.002],"l":[-85.007,31.002,-84.966,30.923,-84.887,30.759,-84.884,30.721,-84.79,30.715,-84.631,30.706,-84.472,30.696,-84.312,30.687,-84.153,30.678,-83.993,30.668,-83.834,30.659,-83.675,30.65,-83.515,30.64,-83.356,30.631,-83.196,30.622,-83.037,30.612,-82.877,30.603,-82.718,30.594,-82.559,30.584,-82.399,30.575,-82.24,30.566,-82.239,30.533,-82.213,30.481,-82.206,30.422,-82.194,30.393,-82.163,30.376,-82.112,30.371,-82.062,30.405,-82.03,30.478,-82.022,30.56,-82.041,30.653,-82.038,30.732,-82.013,30.778,-81.983,30.789,-81.957,30.814,-81.936,30.82,-81.88,30.806,-81.673,30.74,-81.504,30.731]},{"z":2,"b":[-85.624,31.002,-84.922,35.001],"l":[-85.007,31.002,-85.067,31.118,-85.093,31.296,-85.055,31.573,-85.067,31.635,-85.125,31.777,-85.124,31.881,-85.064,32.051,-85.065,32.051,-85.035,32.147,-84.993,32.202,-84.93,32.247,-84.922,32.286,-84.968,32.32,-84.986,32.361,-84.974,32.409,-85.015,32.526,-85.157,32.804,-85.176,32.898,-85.201,32.956,-85.227,33.084,-85.254,33.212,-85.28,33.34,-85.307,33.467,-85.333,33.595,-85.359,33.723,-85.386,33.851,-85.412,33.979,-85.439,34.106,-85.465,34.234,-85.492,34.362,-85.518,34.49,-85.544,34.617,-85.571,34.745,-85.597,34.873,-85.624,35.001]},{"z":2,"b":[-83.356,32.03,-80.872,35.001],"l":[-83.115,35.001,-83.121,35.001,-83.17,34.933,-83.317,34.806,-83.356,34.708,-83.167,34.6,-83.166,34.599,-83.053,34.511,-82.976,34.476,-82.897,34.466,-82.848,34.437,-82.819,34.366,-82.589,34.017,-82.589,34.018,-82.352,33.838,-82.257,33.749,-82.208,33.664,-82.006,33.523,-81.936,33.447,-81.933,33.39,-81.909,33.349,-81.864,33.325,-81.821,33.274,-81.78,33.195,-81.699,33.127,-81.578,33.069,-81.49,32.936,-81.434,32.728,-81.377,32.607,-81.29,32.557,-81.172,32.38,-81.133,32.275,-81.135,32.182,-81.075,32.11,-80.872,32.03]},{"z":2,"b":[-77.122,38.807,-77.02,38.944],"l":[-77.122,38.944,-77.102,38.936,-77.053,38.915,-77.03,38.889,-77.02,38.807]},{"z":2,"b":[-77.727,38.944,-77.122,39.346],"l":[-77.727,39.346,-77.669,39.31,-77.534,39.266,-77.479,39.221,-77.503,39.175,-77.506,39.143,-77.481,39.113,-77.301,39.053,-77.19,38.969,-77.122,38.944]},{"z":2,"b":[-79.488,39.211,-77.727,39.686],"l":[-79.488,39.211,-79.36,39.285,-79.294,39.312,-79.075,39.476,-79.047,39.477,-78.971,39.454,-78.815,39.57,-78.796,39.613,-78.778,39.627,-78.758,39.625,-78.746,39.614,-78.753,39.59,-78.742,39.578,-78.678,39.55,-78.587,39.534,-78.575,39.533,-78.496,39.533,-78.46,39.556,-78.443,39.601,-78.407,39.628,-78.325,39.639,-78.181,39.686,-78.098,39.678,-78.027,39.631,-77.956,39.609,-77.883,39.611,-77.855,39.6,-77.872,39.576,-77.853,39.548,-77.798,39.517,-77.78,39.488,-77.797,39.461,-77.792,39.442,-77.762,39.429,-77.745,39.4,-77.739,39.354,-77.727,39.346]},{"z":2,"b":[-79.559,45.214,-74.34,51.545],"l":[-79.521,51.545,-79.52,51.226,-79.52,50.966,-79.52,50.919,-79.519,50.742,-79.519,50.5,-79.518,50.258,-79.518,50.017,-79.517,49.775,-79.517,49.533,-79.516,49.291,-79.516,49.049,-79.515,48.807,-79.515,48.565,-79.514,48.323,-79.514,48.082,-79.513,47.84,-79.513,47.556,-79.513,47.551,-79.559,47.483,-79.547,47.407,-79.479,47.319,-79.442,47.228,-79.435,47.134,-79.365,47.022,-79.166,46.827,-79.015,46.607,-78.903,46.486,-78.781,46.393,-78.46,46.307,-77.683,46.187,-77.373,46.064,-77.371,46.063,-77.293,45.977,-77.282,45.962,-77.267,45.929,-77.233,45.904,-77.101,45.833,-77.031,45.807,-76.977,45.798,-76.941,45.801,-76.926,45.82,-76.913,45.876,-76.89,45.89,-76.872,45.887,-76.824,45.88,-76.754,45.798,-76.667,45.643,-76.553,45.542,-76.413,45.496,-76.27,45.484,-76.123,45.505,-76.001,45.484,-75.904,45.42,-75.626,45.451,-75.167,45.575,-74.864,45.642,-74.717,45.651,-74.577,45.632,-74.397,45.568,-74.415,45.509,-74.453,45.394,-74.478,45.318,-74.413,45.269,-74.34,45.214]},{"z":4.7,"b":[47.949,52.78,50.256,54.516],"l":[50.077,54.516,50.119,54.437,50.206,54.418,50.256,54.305,50.197,54.038,49.979,53.966,49.957,53.907,49.827,53.887,49.624,53.815,49.414,53.868,49.299,53.84,49.029,53.857,48.874,53.716,48.491,53.771,48.456,53.666,48.331,53.624,48.376,53.471,48.057,53.476,47.949,53.359,48.035,53.315,48.144,53.162,48.149,53.059,48.222,52.999,48.39,52.97,48.522,52.978,48.593,52.919,48.54,52.83,48.437,52.78]},{"z":4.7,"b":[45.792,52.616,46.983,54.005],"l":[46.831,52.616,46.852,52.726,46.983,52.771,46.935,52.91,46.913,53.079,46.954,53.191,46.906,53.335,46.795,53.408,46.494,53.511,46.468,53.654,46.193,53.85,46.212,53.909,45.957,54.005,45.792,54.004]},{"z":4.7,"b":[50.072,56.1,51.436,56.67],"l":[50.072,56.623,50.35,56.67,50.497,56.511,50.586,56.367,50.706,56.33,50.851,56.39,50.908,56.296,50.837,56.248,51.073,56.131,51.224,56.1,51.436,56.144]},{"z":4.7,"b":[51.399,55.858,53.632,56.538],"l":[51.436,56.144,51.501,56.082,51.399,55.994,51.442,55.925,51.633,55.952,51.829,55.918,51.938,55.974,52.064,55.896,52.168,55.899,52.242,55.965,52.195,56.077,52.439,56.033,52.473,56.074,52.655,56.018,52.794,56.095,52.839,56.163,52.711,56.23,52.562,56.259,52.733,56.378,52.908,56.416,52.934,56.538,53.08,56.527,52.945,56.366,52.958,56.252,53.273,56.085,53.336,56.157,53.26,56.254,53.427,56.277,53.551,56.241,53.564,56.181,53.405,56.023,53.276,55.935,53.289,55.858,53.632,55.907]},{"z":4.7,"b":[47.242,54.678,48.412,55.829],"l":[47.242,54.678,47.37,54.753,47.379,54.832,47.487,54.858,47.729,54.831,47.803,54.945,47.996,54.968,48.075,55.108,48.017,55.276,47.913,55.316,47.763,55.214,47.703,55.328,47.834,55.342,47.974,55.5,48.101,55.545,48.025,55.63,48.205,55.694,48.412,55.829]},{"z":4.7,"b":[48.401,55.829,50.18,56.623],"l":[48.412,55.829,48.401,55.888,48.694,55.965,48.69,56.068,48.835,56.072,49.016,56.233,49.037,56.358,49.276,56.353,49.558,56.441,49.578,56.505,49.765,56.536,49.885,56.442,50.025,56.46,50.18,56.549,50.072,56.623]},{"z":4.7,"b":[50.077,54.33,52.539,54.642],"l":[52.539,54.377,52.482,54.467,52.223,54.445,52.099,54.388,51.933,54.436,51.936,54.528,51.668,54.567,51.513,54.639,51.391,54.601,51.296,54.642,51.169,54.637,51.093,54.554,51.004,54.539,50.948,54.348,50.697,54.427,50.519,54.33,50.384,54.486,50.077,54.516]},{"z":4.7,"b":[47.242,54.51,50.077,54.884],"l":[50.077,54.516,49.975,54.51,49.746,54.574,49.616,54.562,49.476,54.775,49.291,54.884,49.118,54.795,49.014,54.793,48.828,54.648,48.641,54.66,48.47,54.634,48.29,54.738,48.086,54.766,47.85,54.704,47.754,54.731,47.637,54.565,47.366,54.527,47.351,54.593,47.242,54.678]},{"z":4.7,"b":[42.094,52.403,43.237,53.812],"l":[42.134,53.812,42.094,53.54,42.312,53.44,42.354,53.368,42.748,53.022,42.905,52.97,43.095,52.822,43.237,52.661,43.042,52.561,43.011,52.482,43.08,52.403]},{"z":4.7,"b":[84.41,56.044,88.624,56.833],"l":[88.624,56.833,88.552,56.787,88.419,56.829,88.144,56.719,87.779,56.548,87.62,56.64,87.397,56.616,87.167,56.676,87.13,56.54,86.913,56.556,86.707,56.632,86.532,56.569,86.394,56.549,86.313,56.627,86.217,56.624,86.112,56.497,85.784,56.406,85.791,56.361,85.606,56.324,85.688,56.239,85.379,56.213,85.254,56.245,85.121,56.216,85.009,56.133,84.893,56.17,84.41,56.044]},{"z":4.7,"b":[38.895,53.342,40.16,53.588],"l":[38.895,53.565,39.041,53.55,39.162,53.395,39.415,53.414,39.524,53.462,39.512,53.525,39.74,53.588,39.907,53.499,39.918,53.416,40.16,53.342]},{"z":4.7,"b":[40.16,53.342,42.134,53.823],"l":[40.16,53.342,40.317,53.356,40.502,53.461,40.738,53.56,40.84,53.487,41.021,53.49,41.174,53.434,41.354,53.556,41.349,53.741,41.4,53.783,41.748,53.823,42.134,53.812]},{"z":4.7,"b":[37.727,52.017,38.038,53.009],"l":[37.747,52.017,37.778,52.161,37.948,52.246,38.038,52.345,37.911,52.473,37.783,52.508,37.727,52.597,37.813,52.648,37.777,52.719,37.871,52.772,37.79,52.845,37.839,52.891,37.841,53.009]},{"z":4.7,"b":[37.841,52.974,38.895,53.565],"l":[37.841,53.009,37.912,53.073,38.108,53.059,38.307,52.974,38.473,53.041,38.595,53.049,38.633,53.123,38.595,53.24,38.477,53.31,38.762,53.382,38.697,53.5,38.895,53.565]},{"z":4.7,"b":[75.159,58.65,84.26,61.047],"l":[84.26,60.856,83.997,60.826,83.616,60.982,83.504,61.047,83.144,61.028,82.407,60.723,82.362,60.601,82.139,60.532,81.83,60.645,81.498,60.615,81.141,60.637,81.017,60.757,80.723,60.797,80.414,60.761,80.165,60.664,79.853,60.692,79.503,60.687,79.36,60.664,79.285,60.726,79.314,60.792,79.169,60.837,78.806,60.78,78.675,60.828,78.474,60.777,78.164,60.803,77.941,60.749,77.702,60.825,77.408,60.811,77.136,60.856,77.068,60.726,76.97,60.649,77.054,60.578,77.013,60.527,76.78,60.452,76.749,60.347,76.848,60.266,76.699,60.117,76.758,60.057,76.741,59.758,76.656,59.693,76.645,59.585,76.44,59.545,76.172,59.543,76.007,59.428,75.895,59.426,75.843,59.323,75.628,59.238,75.687,59.04,75.642,58.982,75.373,58.786,75.159,58.65]},{"z":4.7,"b":[39.953,51.954,40.697,53.342],"l":[40.16,53.342,40.107,53.276,40.141,53.072,40.07,52.925,40.08,52.804,39.979,52.769,39.953,52.654,40.067,52.561,40.203,52.512,40.331,52.403,40.477,52.374,40.697,52.27,40.616,52.129,40.632,52.066,40.507,52.022,40.504,51.954]},{"z":4.7,"b":[46.341,58.075,47.602,59.611],"l":[47.114,59.611,47.056,59.379,47.252,59.355,47.239,59.22,47.571,59.06,47.602,58.907,47.452,58.921,47.282,58.893,47.313,58.807,47.26,58.756,47.12,58.775,46.947,58.607,46.687,58.567,46.554,58.488,46.541,58.397,46.424,58.347,46.407,58.209,46.341,58.075]},{"z":4.7,"b":[43.727,57.431,46.341,58.08],"l":[46.341,58.075,45.817,58.047,45.706,57.974,45.557,57.939,45.462,58.035,45.294,58.041,45.155,58.08,44.863,58.075,44.793,57.955,44.865,57.88,44.728,57.704,44.612,57.707,44.483,57.646,44.303,57.517,43.995,57.44,43.846,57.431,43.727,57.466]},{"z":4.7,"b":[35.931,53.618,37.259,54.836],"l":[37.259,54.836,37.208,54.688,37.089,54.549,36.805,54.61,36.848,54.472,36.965,54.396,36.891,54.283,36.714,54.317,36.461,54.288,36.354,54.223,36.237,54.229,36.201,54.098,36.111,53.961,35.989,53.916,35.931,53.786,36.01,53.618]},{"z":4.7,"b":[40.926,58.579,47.114,59.633],"l":[40.926,58.579,41.218,58.74,41.146,58.785,41.245,58.902,41.522,59.037,41.687,59.08,42.012,59.118,42.09,59.23,41.959,59.278,42.058,59.427,42.188,59.391,42.197,59.303,42.632,59.308,42.671,59.228,42.8,59.17,43.026,59.208,43.151,59.261,43.294,59.218,43.371,59.28,43.498,59.294,43.582,59.188,43.742,59.26,43.96,59.205,44.103,59.23,44.207,59.172,44.391,59.153,44.991,59.122,45.091,59.184,45.366,59.198,45.918,59.184,46.004,59.282,46.182,59.345,46.356,59.347,46.406,59.633,46.757,59.607,46.875,59.632,47.114,59.611]},{"z":4.7,"b":[35.37,55.194,36.904,55.319],"l":[35.37,55.239,35.465,55.22,35.579,55.29,35.755,55.287,35.856,55.238,36.238,55.194,36.423,55.319,36.586,55.309,36.904,55.229]},{"z":4.7,"b":[37.12,54.836,37.259,54.991],"l":[37.12,54.991,37.259,54.836]},{"z":4.7,"b":[36.904,54.991,37.12,55.229],"l":[36.904,55.229,37.084,55.096,37.12,54.991]},{"z":4.7,"b":[42.467,54.476,46.056,55.177],"l":[42.467,54.871,42.636,54.787,43.098,54.799,43.155,54.903,43.369,54.95,43.61,54.901,43.689,54.831,43.949,54.807,43.939,54.742,44.091,54.734,44.107,54.646,44.376,54.59,44.54,54.53,44.597,54.58,44.74,54.516,44.917,54.476,45.114,54.494,45.029,54.568,45.091,54.659,45.041,54.714,45.124,54.796,45.209,54.757,45.266,54.873,45.424,54.882,45.439,55.033,45.605,55.164,45.71,55.177,45.74,55.077,45.815,55,45.954,54.997,46.056,55.042]},{"z":4.7,"b":[46.056,54.772,46.441,55.042],"l":[46.056,55.042,46.415,54.895,46.441,54.772]},{"z":4.7,"b":[36.01,53.009,37.841,53.618],"l":[36.01,53.618,36.098,53.556,36.22,53.57,36.398,53.429,36.535,53.391,36.667,53.416,36.807,53.297,37.088,53.28,37.197,53.251,37.327,53.303,37.568,53.289,37.561,53.169,37.787,53.089,37.841,53.009]},{"z":4.7,"b":[42.206,53.847,42.699,54.871],"l":[42.308,53.847,42.437,54.012,42.319,54.099,42.535,54.083,42.596,54.177,42.463,54.254,42.273,54.171,42.206,54.231,42.381,54.264,42.527,54.367,42.625,54.373,42.557,54.492,42.699,54.582,42.524,54.723,42.419,54.736,42.391,54.807,42.467,54.871]},{"z":4.7,"b":[88.365,52.168,89.26,55.446],"l":[88.609,55.446,88.396,55.279,88.55,55.083,88.547,55.023,88.66,54.903,88.748,54.89,88.753,54.805,88.613,54.697,88.603,54.522,88.519,54.416,88.561,54.288,88.798,54.407,88.955,54.369,89.004,54.29,89.129,54.35,89.219,54.243,89.187,54.147,89.047,54.117,88.981,54.065,89.073,53.953,89.26,53.835,89.181,53.781,89.018,53.742,89.011,53.679,88.834,53.585,88.895,53.387,89.051,53.249,89.049,53.13,88.895,52.987,89.118,52.92,89.236,52.81,89.043,52.678,89.115,52.626,88.945,52.488,88.676,52.352,88.688,52.257,88.365,52.168]},{"z":4.7,"b":[35.104,53.272,36.01,53.644],"l":[35.104,53.311,35.205,53.272,35.356,53.284,35.422,53.404,35.637,53.543,35.766,53.519,35.911,53.644,36.01,53.618]},{"z":4.7,"b":[42.134,53.812,42.308,53.847],"l":[42.134,53.812,42.308,53.847]},{"z":4.7,"b":[42.308,53.664,45.792,54.017],"l":[42.308,53.847,42.513,53.737,42.631,53.808,42.94,53.801,43.17,53.839,43.106,53.909,43.182,54.001,43.31,54.017,43.425,53.989,43.528,54.015,43.752,53.963,43.933,53.862,43.877,53.81,44.099,53.736,44.206,53.76,44.314,53.701,44.428,53.723,44.534,53.664,44.729,53.7,44.615,53.831,44.617,53.892,44.883,53.969,45.176,53.979,45.339,53.885,45.646,53.93,45.792,54.004]},{"z":4.7,"b":[45.792,54.004,46.734,54.772],"l":[45.792,54.004,45.879,54.031,46.018,54.189,46.379,54.231,46.488,54.284,46.719,54.33,46.734,54.384,46.543,54.406,46.482,54.499,46.533,54.553,46.516,54.653,46.444,54.688,46.441,54.772]},{"z":4.7,"b":[46.441,54.632,47.242,54.772],"l":[46.441,54.772,46.869,54.632,47.063,54.69,47.242,54.678]},{"z":4.7,"b":[42.588,56.42,43.727,57.466],"l":[43.727,57.466,43.689,57.279,43.539,57.262,43.514,57.118,43.67,57.043,43.501,56.906,43.339,56.861,42.978,56.842,42.83,56.802,42.787,56.669,42.902,56.646,42.845,56.552,42.882,56.472,42.648,56.481,42.588,56.42]},{"z":4.7,"b":[46.341,56.966,47.762,58.075],"l":[46.341,58.075,46.43,58.02,47.354,58.026,47.452,57.987,47.703,57.958,47.762,57.876,47.572,57.81,47.521,57.586,47.471,57.529,47.249,57.567,47.054,57.498,46.75,57.517,46.695,57.32,46.79,57.281,46.742,57.205,46.763,57.03,46.715,56.966]},{"z":4.7,"b":[38.675,54.245,40.206,55.309],"l":[38.675,54.245,38.737,54.345,38.909,54.398,38.798,54.451,38.82,54.602,39.216,54.632,39.328,54.748,39.398,54.876,39.488,54.941,39.685,55.021,39.813,54.997,39.888,55.081,39.827,55.183,40.001,55.2,40.206,55.309]},{"z":4.7,"b":[41.783,54.871,42.467,55.119],"l":[41.783,55.119,41.886,55.059,42.167,54.949,42.44,55.013,42.467,54.871]},{"z":4.7,"b":[38.33,55.309,40.206,56.697],"l":[40.206,55.309,40.137,55.434,40.141,55.517,40.037,55.546,40.085,55.613,39.9,55.734,39.823,55.833,39.555,55.776,39.358,55.761,39.31,55.831,39.123,55.83,39.016,55.925,38.839,55.901,38.729,55.953,38.606,55.959,38.562,56.083,38.403,56.264,38.412,56.369,38.33,56.562,38.374,56.697]},{"z":4.7,"b":[38.672,53.565,38.909,54.245],"l":[38.895,53.565,38.909,53.753,38.837,53.76,38.795,53.872,38.841,53.919,38.714,54.001,38.694,54.075,38.764,54.135,38.672,54.177,38.675,54.245]},{"z":4.7,"b":[41.783,55.119,42.906,56.42],"l":[42.588,56.42,42.761,56.306,42.755,56.22,42.906,56.152,42.686,56.122,42.694,56.003,42.565,55.969,42.486,55.852,42.38,55.777,42.281,55.77,42.056,55.613,42.068,55.481,42.117,55.422,41.933,55.33,41.859,55.241,41.87,55.158,41.783,55.119]},{"z":4.7,"b":[40.206,55.109,41.783,55.34],"l":[41.783,55.119,41.658,55.109,41.497,55.157,41.412,55.131,41.21,55.221,41.047,55.183,40.965,55.26,40.729,55.204,40.612,55.207,40.517,55.328,40.345,55.34,40.206,55.309]},{"z":4.7,"b":[37.626,58.49,40.926,58.954],"l":[37.626,58.49,37.717,58.622,37.941,58.641,38.048,58.736,38.478,58.706,38.55,58.777,38.916,58.744,39,58.846,38.94,58.944,39.119,58.954,39.5,58.889,39.62,58.895,39.969,58.759,40.161,58.559,40.293,58.544,40.565,58.591,40.926,58.579]},{"z":4.7,"b":[39.417,56.368,42.588,56.822],"l":[39.417,56.772,39.679,56.822,39.859,56.78,39.879,56.646,39.943,56.53,40.156,56.434,40.491,56.584,40.715,56.598,40.934,56.502,41.053,56.475,41.934,56.517,41.958,56.391,42.066,56.368,42.289,56.471,42.469,56.421,42.588,56.42]},{"z":4.7,"b":[38.257,56.697,38.374,56.77],"l":[38.374,56.697,38.257,56.77]},{"z":4.7,"b":[37.35,56.77,38.306,58.49],"l":[38.257,56.77,38.154,56.832,38.199,56.894,38.25,57.129,38.143,57.197,38.306,57.276,38.27,57.371,38.148,57.337,37.984,57.359,37.794,57.443,37.81,57.608,37.391,57.828,37.367,57.977,37.484,57.981,37.549,58.053,37.673,58.083,37.562,58.165,37.35,58.239,37.449,58.408,37.644,58.429,37.626,58.49]},{"z":4.7,"b":[39.387,56.772,40.445,57.293],"l":[40.445,57.276,40.327,57.293,40.193,57.213,40.064,57.219,39.842,57.177,39.844,57.129,39.52,57.027,39.527,56.956,39.387,56.835,39.417,56.772]},{"z":4.7,"b":[38.374,56.538,39.417,56.772],"l":[39.417,56.772,39.31,56.684,39.337,56.609,39.272,56.538,39.121,56.594,39,56.543,38.847,56.598,38.629,56.581,38.374,56.697]},{"z":4.7,"b":[40.445,57.276,43.727,57.75],"l":[43.727,57.466,43.542,57.567,43.312,57.517,43.264,57.424,42.973,57.388,42.853,57.394,42.851,57.667,42.726,57.702,42.65,57.635,42.404,57.633,42.336,57.579,42.186,57.56,42.026,57.589,41.886,57.686,41.63,57.75,41.567,57.704,41.604,57.524,41.544,57.459,41.434,57.472,41.27,57.426,41.17,57.435,41.073,57.348,40.571,57.316,40.445,57.276]},{"z":4.7,"b":[40.385,57.276,41.164,58.579],"l":[40.445,57.276,40.448,57.441,40.385,57.498,40.499,57.58,40.663,57.581,40.623,57.655,40.626,57.94,40.744,58.052,40.885,58.126,40.889,58.198,41.164,58.361,41.065,58.411,41.034,58.522,40.926,58.579]},{"z":4.7,"b":[53.785,56.359,54.352,58.44],"l":[53.785,58.44,53.886,58.319,53.81,58.228,54.087,57.994,54.156,57.706,54.096,57.607,54.168,57.559,54.188,57.463,54.158,57.314,54.314,57.291,54.295,57.084,54.341,57.012,54.099,56.966,54.088,56.771,53.869,56.713,53.959,56.632,54.168,56.534,54.245,56.432,54.352,56.359]},{"z":4.7,"b":[51.774,60.242,59.349,61.682],"l":[59.349,61.682,59.114,61.615,59.023,61.552,58.692,61.503,57.204,61.511,57.086,61.488,56.702,61.525,56.545,61.435,56.391,61.42,56.257,61.206,55.841,61.25,55.69,61.086,55.316,61.123,55.214,61.019,55.062,61.009,54.98,60.864,53.853,60.98,53.791,60.848,53.354,60.893,53.349,61.037,52.876,61.094,52.79,60.949,52.442,60.98,52.355,60.844,51.89,60.876,51.774,60.606,52.134,60.547,52.138,60.473,52.324,60.432,52.298,60.242]},{"z":4.7,"b":[52.298,58.44,53.922,60.242],"l":[52.298,60.242,52.454,60.201,53.358,60.166,53.457,60.205,53.608,60.143,53.707,60.001,53.658,59.824,53.592,59.709,53.418,59.678,53.354,59.501,53.206,59.364,53.335,59.185,53.434,59.144,53.877,59.093,53.767,59.022,53.726,58.917,53.821,58.821,53.922,58.783,53.75,58.697,53.705,58.595,53.785,58.44]},{"z":4.7,"b":[51.12,56.144,53.785,58.537],"l":[53.785,58.44,53.498,58.45,53.34,58.389,53.165,58.456,53.13,58.537,52.921,58.534,52.87,58.411,52.329,58.45,52.167,58.49,51.971,58.464,51.796,58.361,51.791,58.281,51.673,58.166,51.766,58.141,51.793,58.043,51.885,57.986,51.852,57.842,51.924,57.817,51.862,57.647,51.748,57.546,51.64,57.544,51.601,57.457,51.397,57.478,51.181,57.447,51.12,57.265,51.193,57.203,51.227,57.067,51.359,56.927,51.54,56.883,51.4,56.767,51.379,56.68,51.206,56.67,51.145,56.515,51.273,56.44,51.32,56.329,51.42,56.284,51.436,56.144]},{"z":4.7,"b":[46.715,56.623,50.101,57.291],"l":[46.715,56.966,46.823,56.93,46.951,56.945,47.161,56.901,47.174,56.814,47.509,56.893,47.77,57.06,47.91,56.983,48.202,57.028,48.293,57.163,48.451,57.128,48.569,57.172,48.822,57.164,48.961,57.089,49.039,57.222,49.035,57.288,49.213,57.291,49.15,57.15,49.285,57.039,49.411,57.025,49.529,57.073,49.7,57.073,49.742,56.9,50.017,56.864,50.101,56.792,50.072,56.623]},{"z":4.7,"b":[48.419,59.208,52.298,60.854],"l":[48.419,60.854,48.538,60.564,48.519,60.476,48.755,60.361,48.497,60.218,48.456,60.115,48.493,59.989,48.508,59.715,48.562,59.699,49.073,59.66,49.039,59.5,49.151,59.506,49.363,59.416,49.525,59.397,49.527,59.235,49.664,59.208,49.795,59.233,49.834,59.638,50.04,59.682,49.999,59.777,50.212,59.754,50.386,59.84,50.83,59.871,51.075,60.061,51.307,60.028,51.421,59.943,51.521,59.946,51.802,60.115,51.9,60.244,52.061,60.317,52.298,60.242]},{"z":4.7,"b":[35.174,55.948,38.257,56.944],"l":[38.257,56.77,38.015,56.774,37.974,56.854,37.656,56.944,37.553,56.906,37.499,56.785,37.231,56.773,37.157,56.683,37.135,56.56,37.055,56.518,36.833,56.583,36.537,56.475,36.173,56.408,36.096,56.347,35.983,56.387,35.783,56.392,35.645,56.474,35.458,56.448,35.548,56.371,35.484,56.242,35.379,56.174,35.317,56.054,35.21,56.019,35.174,55.948]},{"z":4.7,"b":[36.904,54.991,37.843,55.904],"l":[37.12,54.991,37.216,55.084,37.425,55.184,37.463,55.379,37.566,55.358,37.659,55.569,37.813,55.649,37.843,55.763,37.801,55.838,37.611,55.904,37.431,55.878,37.348,55.744,37.414,55.66,37.211,55.602,37.175,55.448,36.914,55.426,37.014,55.301,36.904,55.229]},{"z":4.7,"b":[34.73,58.525,36.173,59.103],"l":[36.173,58.525,36.126,58.695,35.836,58.745,35.751,58.827,35.398,58.844,35.266,58.765,35.125,58.815,35.131,58.937,34.831,58.991,34.879,59.043,34.73,59.103]},{"z":4.7,"b":[31,56.931,36.173,58.525],"l":[36.173,58.525,35.941,58.455,35.935,58.423,35.552,58.448,35.394,58.431,35.279,58.359,35.269,58.271,35.035,58.2,34.786,58.242,34.684,58.197,34.651,58.081,34.488,58.146,34.142,58.208,33.869,58.144,33.719,58.158,33.545,58.047,33.686,58.003,33.701,57.927,33.581,57.858,33.537,57.72,33.325,57.655,33.309,57.566,33.216,57.454,33.046,57.437,32.807,57.46,32.668,57.346,32.272,57.195,32.133,57.184,31.967,57.117,31.722,57.106,31.678,57.013,31.546,56.94,31.307,56.969,31.118,56.931,31,56.965]},{"z":4.7,"b":[46.349,59.611,47.114,60.854],"l":[47.043,60.854,47.084,60.819,46.959,60.66,47.049,60.575,46.862,60.507,46.807,60.318,46.75,60.261,46.441,60.271,46.349,60.214,46.404,60.107,46.608,60.067,47.036,60.1,46.923,59.844,46.935,59.789,47.113,59.753,47.114,59.611]},{"z":4.7,"b":[36.173,58.49,37.626,58.866],"l":[37.626,58.49,37.188,58.771,37.122,58.866,36.864,58.843,36.816,58.758,36.669,58.685,36.521,58.558,36.324,58.5,36.173,58.525]},{"z":4.7,"b":[37.259,54.245,38.675,54.836],"l":[37.259,54.836,37.461,54.822,37.564,54.737,37.758,54.736,37.75,54.82,37.939,54.832,38.039,54.714,38.219,54.66,38.462,54.64,38.49,54.58,38.39,54.469,38.514,54.364,38.521,54.289,38.675,54.245]},{"z":4.7,"b":[46.214,55.829,48.412,56.335],"l":[46.214,56.05,46.402,56.119,46.658,56.12,47.056,56.288,47.277,56.335,47.385,56.33,47.437,56.188,47.529,56.154,47.829,56.145,47.905,56.114,48.024,55.937,48.285,55.846,48.412,55.829]},{"z":4.7,"b":[45.651,56.05,46.715,56.966],"l":[46.715,56.966,46.601,56.92,46.336,56.952,46.313,56.907,45.959,56.872,45.859,56.629,45.651,56.591,45.663,56.461,45.789,56.425,45.949,56.433,45.946,56.325,45.874,56.259,46.068,56.182,46.092,56.056,46.214,56.05]},{"z":4.7,"b":[45.92,55.042,46.384,56.05],"l":[46.214,56.05,46.099,55.941,46.094,55.808,45.92,55.688,46.048,55.617,46.14,55.524,46.244,55.524,46.384,55.463,46.254,55.385,46.229,55.273,46.098,55.204,46.056,55.042]},{"z":4.7,"b":[78.483,62.178,86.028,72.395],"l":[78.483,72.395,78.697,72.18,79.419,72.043,79.695,72.058,80.218,71.942,80.339,71.89,80.036,71.831,80.119,71.675,79.868,71.602,79.383,71.615,79.333,71.481,79.143,71.422,79.245,71.317,79.565,71.277,79.954,71.134,80.403,71.091,80.55,71.044,80.552,70.9,80.68,70.805,80.608,70.71,80.753,70.635,80.569,70.461,80.177,70.43,80.095,70.353,79.63,70.221,79.505,70.115,79.208,70.053,79.171,69.978,78.913,69.861,79.116,69.732,79.585,69.588,79.644,69.491,79.854,69.355,80.26,69.356,80.734,69.308,80.967,69.209,81.324,69.286,81.611,69.286,81.626,69.41,81.783,69.429,81.929,69.349,81.772,69.233,81.893,69.183,82.257,69.157,82.497,69.111,82.333,69.062,82.481,68.995,82.546,68.84,83.015,68.684,82.867,68.628,82.536,68.611,82.71,68.414,82.681,68.283,82.55,68.179,82.395,68.177,82.354,67.944,82.059,67.948,81.731,67.908,81.882,67.799,82.1,67.724,82.11,67.609,82.387,67.524,82.256,67.409,82.353,67.328,82.117,67.244,82.796,66.977,83.042,66.891,83.162,66.818,83.293,66.663,83.078,66.579,83.091,66.459,83.262,66.389,83.525,66.173,83.381,66.14,83.296,66.058,83.521,65.908,83.518,65.822,84.031,65.793,84.168,65.703,84.319,65.684,84.291,65.613,84.43,65.551,84.55,65.433,84.586,65.311,84.523,65.213,84.388,65.175,84.313,65.098,84.387,64.994,84.279,64.94,84.47,64.89,84.82,64.931,84.968,64.891,85.184,64.751,85.61,64.828,85.859,64.756,85.812,64.656,85.916,64.588,85.845,64.501,85.951,64.329,86.028,64.28,85.934,64.123,85.948,64.053,85.534,63.937,85.373,63.808,85.333,63.701,85.097,63.534,85.276,63.522,85.378,63.467,85.41,63.353,85.641,63.374,85.469,63.149,85.584,63.054,85.518,62.933,85.324,62.869,85.225,62.801,85.09,62.638,84.958,62.593,84.861,62.451,84.746,62.41,84.436,62.178]},{"z":4.7,"b":[62.018,62.178,84.436,65.717],"l":[84.436,62.178,84.289,62.276,84.237,62.377,84.096,62.375,83.798,62.56,83.533,62.525,83.311,62.457,83.034,62.53,83.01,62.595,82.741,62.68,82.733,62.757,82.288,62.775,82.073,62.8,81.84,62.692,81.6,62.773,81.349,62.821,81.195,62.95,81.075,63.117,80.584,63.083,80.574,63.019,80.073,62.826,79.915,62.792,79.82,62.604,78.793,62.615,78.379,62.568,77.967,62.478,77.889,62.557,77.327,62.71,77.132,62.813,77.026,62.975,76.623,63.038,76.289,62.95,76.24,63.019,75.803,63.114,75.7,63.091,75.364,63.108,74.915,63.032,74.507,63.043,74.241,63.172,73.368,63.207,73.284,63.315,73.104,63.436,72.907,63.393,72.655,63.28,72.161,63.299,71.964,63.197,71.805,63.219,71.608,63.185,71.569,63.551,71.388,63.675,70.914,63.692,70.813,63.822,70.638,63.903,70.57,64.015,70.755,64.1,70.769,64.194,70.672,64.237,70.473,64.252,70.307,64.344,70,64.316,69.825,64.341,69.693,64.4,69.39,64.389,69.244,64.466,69.015,64.472,68.859,64.436,68.99,64.33,68.811,64.221,68.425,64.296,68.179,64.176,67.936,64.118,67.809,64.032,67.679,64.079,67.281,64.081,67.171,64.163,66.819,64.226,66.796,64.321,66.892,64.383,66.936,64.487,66.682,64.557,66.434,64.515,66.146,64.586,65.883,64.569,65.4,64.571,65.176,64.446,64.995,64.466,64.761,64.427,64.622,64.439,64.444,64.356,64.016,64.357,63.896,64.287,63.611,64.268,63.417,64.324,63.503,64.371,63.467,64.451,63.215,64.526,62.699,64.492,62.532,64.56,62.6,64.645,62.803,64.726,62.799,64.847,62.679,64.878,62.801,65.051,62.817,65.292,62.517,65.339,62.374,65.449,62.411,65.515,62.278,65.623,62.018,65.717]},{"z":4.7,"b":[163.137,61.815,174.468,64.931],"l":[174.454,61.815,174.468,61.979,174.344,62.05,174.117,62.074,174.112,62.189,174.015,62.269,174.093,62.34,174.08,62.447,173.872,62.441,173.798,62.518,173.504,62.534,173.027,62.375,172.853,62.331,172.705,62.402,172.417,62.432,172.319,62.404,172.055,62.467,171.47,62.312,171.218,62.369,170.901,62.279,170.382,62.311,170.262,62.285,170.159,62.342,170.086,62.492,169.986,62.545,170.01,62.677,169.842,62.655,169.657,62.709,169.514,62.814,169.127,62.973,168.939,62.944,168.51,63.054,168.544,63.126,168.796,63.303,169.099,63.44,169.205,63.516,169.509,63.584,169.582,63.643,169.549,63.811,169.364,63.88,169.344,63.95,169.079,63.959,168.874,64.021,168.778,64.185,168.638,64.211,168.499,64.317,168.198,64.339,168.026,64.286,167.815,64.351,167.714,64.341,167.498,64.481,167.414,64.482,167.237,64.624,167,64.55,166.807,64.533,166.406,64.601,166.199,64.609,166.035,64.559,165.743,64.679,165.684,64.754,165.481,64.743,165.329,64.775,165.263,64.851,165.02,64.837,165.004,64.69,164.882,64.686,164.798,64.772,164.216,64.925,163.873,64.865,163.52,64.931,163.345,64.818,163.137,64.77]},{"z":4.7,"b":[157.692,66.359,162.847,69.677],"l":[158.529,66.359,158.368,66.459,158.47,66.488,158.637,66.619,158.716,66.731,158.905,66.803,158.703,67.055,158.299,67.148,157.958,67.258,157.793,67.358,157.876,67.413,157.692,67.545,157.806,67.705,158.329,67.754,158.27,67.823,158.093,67.84,158.277,68.076,158.633,68.142,158.934,68.144,159.111,68.191,159.693,68.239,159.85,68.277,160.997,68.301,161.226,68.384,161.406,68.412,161.711,68.373,162.178,68.355,162.418,68.309,162.535,68.363,162.581,68.501,162.723,68.605,162.606,68.714,162.594,68.796,162.755,68.802,162.847,68.869,162.554,68.991,162.524,69.118,162.774,69.203,162.521,69.325,162.315,69.547,162.432,69.677]},{"z":4.6,"b":[75.222,11.96,75.313,12.076],"l":[75.313,11.96,75.262,12.076,75.222,12.022]},{"z":4.6,"b":[79.705,10.81,79.847,10.986],"l":[79.847,10.81,79.767,10.889,79.705,10.91,79.737,10.986,79.847,10.98]},{"z":4.6,"b":[79.686,11.833,79.85,12],"l":[79.811,11.833,79.727,11.842,79.686,12,79.791,11.917,79.85,11.955]},{"z":4.6,"b":[74.893,11.667,76.416,12.756],"l":[76.416,11.667,76.389,11.744,76.317,11.747,76.177,11.857,76.115,11.857,76.056,11.964,75.961,11.929,75.846,11.951,75.776,12.01,75.761,12.069,75.629,12.102,75.537,12.169,75.426,12.292,75.389,12.295,75.312,12.469,75.102,12.679,74.893,12.756]},{"z":4.6,"b":[76.241,8.285,77.399,11.667],"l":[76.416,11.667,76.339,11.591,76.241,11.604,76.256,11.478,76.409,11.437,76.495,11.385,76.534,11.313,76.466,11.205,76.641,11.209,76.719,11.139,76.69,10.933,76.82,10.876,76.89,10.78,76.876,10.678,76.821,10.633,76.809,10.437,76.842,10.311,76.928,10.236,76.986,10.231,77.065,10.296,77.191,10.351,77.277,10.21,77.224,10.058,77.26,9.962,77.226,9.859,77.235,9.765,77.188,9.613,77.263,9.557,77.341,9.58,77.399,9.5,77.312,9.301,77.255,9.122,77.165,9.001,77.203,8.894,77.249,8.859,77.235,8.784,77.179,8.695,77.204,8.612,77.279,8.526,77.166,8.337,77.11,8.285]},{"z":4.6,"b":[77.257,15.953,77.621,18.308],"l":[77.551,18.308,77.6,18.115,77.563,18.071,77.621,17.987,77.62,17.926,77.5,17.812,77.525,17.746,77.464,17.713,77.412,17.631,77.493,17.578,77.583,17.579,77.599,17.487,77.374,17.223,77.465,17.043,77.433,16.935,77.411,16.721,77.432,16.594,77.371,16.524,77.257,16.449,77.294,16.407,77.533,16.361,77.491,16.262,77.484,16.002,77.446,15.953]},{"z":4.6,"b":[76.416,11.626,78.23,12.873],"l":[78.23,12.76,78.056,12.846,78.002,12.807,77.895,12.873,77.801,12.857,77.765,12.719,77.609,12.673,77.569,12.56,77.619,12.467,77.561,12.34,77.472,12.274,77.492,12.205,77.668,12.204,77.769,12.12,77.671,11.971,77.49,11.926,77.434,11.812,77.354,11.784,77.259,11.817,77.122,11.793,77.083,11.757,76.997,11.826,76.908,11.8,76.821,11.626,76.751,11.642,76.605,11.631,76.532,11.709,76.465,11.715,76.416,11.667]},{"z":4.6,"b":[73.68,15.614,74.101,15.772],"l":[74.101,15.64,73.99,15.614,73.943,15.632,73.908,15.721,73.813,15.772,73.68,15.713]},{"z":4.6,"b":[74.091,14.888,74.304,15.64],"l":[74.101,15.64,74.227,15.63,74.258,15.519,74.237,15.472,74.304,15.289,74.241,15.238,74.293,15.188,74.26,15.098,74.241,14.956,74.091,14.888]},{"z":4.6,"b":[77.503,18.308,80.27,19.866],"l":[80.27,18.714,80.091,18.697,80.002,18.786,79.96,18.867,79.934,19.053,79.862,19.081,79.944,19.169,79.931,19.212,79.965,19.402,79.931,19.483,79.865,19.509,79.796,19.594,79.735,19.603,79.539,19.534,79.42,19.536,79.266,19.59,79.198,19.462,79.059,19.547,79.024,19.542,78.873,19.65,78.848,19.725,78.78,19.777,78.712,19.774,78.534,19.825,78.478,19.799,78.383,19.866,78.322,19.863,78.384,19.685,78.333,19.643,78.303,19.483,78.24,19.46,78.174,19.375,78.168,19.264,78.064,19.264,77.992,19.323,77.92,19.332,77.855,19.228,77.834,19.121,77.752,19.046,77.912,18.835,77.839,18.81,77.781,18.704,77.703,18.669,77.68,18.57,77.592,18.548,77.503,18.382,77.551,18.308]},{"z":4.6,"b":[74.101,15.64,77.551,18.46],"l":[77.551,18.308,77.461,18.282,77.347,18.352,77.302,18.46,77.233,18.411,77.231,18.334,77.118,18.254,77.088,18.197,76.95,18.18,76.893,18.144,76.885,17.974,76.839,17.884,76.741,17.898,76.756,17.826,76.676,17.707,76.593,17.771,76.513,17.773,76.391,17.637,76.322,17.618,76.344,17.352,76.21,17.389,76.095,17.378,75.936,17.326,75.87,17.416,75.784,17.375,75.613,17.472,75.553,17.366,75.622,17.221,75.605,17.158,75.647,17.091,75.617,16.975,75.554,17.015,75.453,16.964,75.371,16.984,75.256,16.959,75.243,16.902,75.167,16.868,75.043,16.953,74.96,16.951,74.922,16.889,74.845,16.852,74.867,16.776,74.685,16.708,74.666,16.606,74.557,16.552,74.525,16.624,74.43,16.644,74.353,16.544,74.292,16.528,74.366,16.293,74.452,16.254,74.47,16.11,74.416,16.096,74.429,15.994,74.354,15.873,74.331,15.762,74.224,15.726,74.186,15.766,74.101,15.64]},{"z":4.6,"b":[74.129,21.075,80.679,22.023],"l":[74.129,21.952,74.205,21.925,74.284,21.945,74.408,22.023,74.486,21.946,74.512,21.727,74.708,21.611,74.894,21.612,75.048,21.554,75.13,21.438,75.312,21.381,75.535,21.372,75.814,21.383,75.878,21.4,76.083,21.368,76.143,21.193,76.109,21.153,76.187,21.086,76.287,21.075,76.437,21.108,76.498,21.18,76.614,21.202,76.654,21.251,76.633,21.341,76.73,21.413,76.786,21.502,76.794,21.577,76.917,21.618,77.029,21.694,77.116,21.724,77.219,21.701,77.333,21.774,77.461,21.77,77.552,21.697,77.555,21.542,77.457,21.549,77.433,21.499,77.482,21.424,77.578,21.384,77.713,21.38,77.788,21.423,77.871,21.398,78.029,21.438,78.193,21.551,78.413,21.606,78.428,21.508,78.514,21.527,78.696,21.475,78.904,21.509,78.922,21.572,79.216,21.655,79.24,21.713,79.413,21.69,79.533,21.619,79.552,21.55,79.65,21.582,79.798,21.588,79.84,21.534,80.005,21.548,80.187,21.639,80.31,21.58,80.418,21.447,80.626,21.326,80.679,21.351]},{"z":4.6,"b":[80.27,18.714,80.894,21.351],"l":[80.679,21.351,80.626,21.243,80.485,21.168,80.435,20.999,80.481,20.932,80.552,20.919,80.573,20.698,80.503,20.633,80.622,20.601,80.622,20.326,80.424,20.234,80.426,20.146,80.531,20.135,80.557,20.048,80.531,19.921,80.425,19.893,80.538,19.811,80.566,19.74,80.676,19.677,80.713,19.593,80.831,19.547,80.894,19.484,80.832,19.345,80.717,19.263,80.617,19.319,80.576,19.392,80.397,19.234,80.367,19.117,80.272,18.979,80.344,18.851,80.275,18.777,80.27,18.714]},{"z":4.7,"b":[41.713,44.976,45.712,46.24],"l":[45.706,44.976,45.712,45.014,45.586,45.158,45.353,45.243,44.706,45.506,44.421,45.527,44.207,45.61,44.148,45.653,44.097,45.774,43.878,45.945,43.742,45.979,43.568,45.981,43.393,46.016,43.237,46.112,42.991,46.18,42.905,46.24,42.835,46.156,42.671,46.088,42.533,45.998,42.317,45.981,42.34,46.107,42.177,46.099,42.15,45.949,41.713,45.996]},{"z":4.6,"b":[83.337,22.539,84.352,24.14],"l":[83.97,22.539,83.996,22.593,84.191,22.701,84.228,22.783,84.352,22.886,84.292,22.997,84.115,22.999,84.089,23.085,84.029,23.146,84.039,23.309,84.015,23.383,83.947,23.37,83.952,23.475,84.015,23.554,83.991,23.632,83.906,23.565,83.781,23.586,83.721,23.676,83.735,23.738,83.697,23.819,83.557,23.936,83.522,24.066,83.442,24.122,83.337,24.14]},{"z":4.7,"b":[84.26,56.833,89.391,60.856],"l":[88.624,56.833,88.721,57.055,88.53,57.096,88.648,57.212,88.739,57.233,88.861,57.432,89.077,57.515,89.167,57.616,89.38,57.637,89.354,57.796,89.391,57.878,89.328,57.95,88.851,57.961,88.687,58.037,88.156,58.107,88.014,58.268,87.918,58.5,87.932,58.526,88.383,58.909,88.797,59.01,88.826,59.035,88.62,59.199,88.586,59.299,87.936,59.266,87.878,59.289,87.554,59.641,87.492,59.674,87.214,59.685,87.084,59.883,86.625,59.95,85.968,59.956,85.496,59.892,84.692,59.905,84.531,59.975,84.611,60.059,84.632,60.206,84.781,60.356,84.708,60.459,84.355,60.791,84.26,60.856]},{"z":4.7,"b":[84.26,60.856,85.956,62.178],"l":[84.26,60.856,84.648,61.001,85.671,61.289,85.644,61.388,85.771,61.443,85.95,61.465,85.956,61.55,85.854,61.595,85.722,61.575,85.48,61.628,85.386,61.698,85.273,61.683,84.919,61.797,84.681,61.815,84.52,61.945,84.551,62.001,84.436,62.178]},{"z":4.7,"b":[88.609,52.183,91.846,55.446],"l":[90.477,52.183,90.7,52.458,91.038,52.66,91.242,52.747,91.396,52.868,91.408,52.947,91.351,53.077,91.66,53.158,91.841,53.288,91.846,53.352,91.744,53.447,91.47,53.514,91.472,53.567,91.333,53.758,91.327,53.817,91.465,53.966,91.464,54.057,91.21,54.338,91.159,54.488,90.855,54.627,90.915,54.791,90.84,54.839,90.715,54.831,90.685,54.905,90.458,54.871,90.436,55.016,90.262,55.05,90.129,55.105,89.904,55.086,89.658,55.003,89.55,55.046,89.201,54.99,89.147,55.076,88.966,55.185,88.95,55.289,88.76,55.344,88.609,55.446]},{"z":4.7,"b":[88.517,55.446,89.443,56.833],"l":[88.609,55.446,88.682,55.516,89.062,55.693,89.17,55.704,89.363,55.773,89.443,55.876,89.33,55.902,89.199,56.135,89.056,56.21,89.041,56.305,88.913,56.31,88.861,56.366,88.734,56.376,88.592,56.444,88.625,56.517,88.537,56.545,88.517,56.627,88.661,56.636,88.721,56.732,88.624,56.833]},{"z":4.7,"b":[37.773,60.65,47.043,61.522],"l":[47.043,60.854,46.753,60.907,46.737,60.986,46.527,61.051,46.285,60.968,45.878,61.016,45.767,61.055,45.792,61.154,45.723,61.179,45.332,61.189,45.229,60.904,44.455,60.947,44.206,60.972,44.158,61.082,43.776,61.021,43.715,60.885,43.536,60.853,43.511,60.767,43.15,60.806,43.049,60.74,42.823,60.855,42.65,60.863,42.395,60.769,42.024,60.836,41.801,60.899,41.519,60.899,41.394,60.858,41.412,60.791,41.34,60.676,41.059,60.65,40.819,60.711,40.757,60.774,40.534,60.725,39.923,60.652,39.764,60.737,39.648,60.746,39.338,60.684,39.068,60.714,38.948,60.803,38.789,60.713,38.633,60.723,38.574,60.799,38.43,60.805,38.418,60.874,38.213,60.924,38.166,61.133,37.963,61.16,37.857,61.285,37.773,61.522]},{"z":4.7,"b":[95.653,53.639,108.32,64.224],"l":[108.027,64.224,108.224,64.203,108.32,64.039,108.263,63.996,107.68,63.989,107.55,63.857,107.372,63.866,107.202,63.957,107.053,63.892,106.764,63.996,106.68,63.931,106.793,63.842,106.629,63.697,106.691,63.636,106.517,63.483,106.725,63.375,106.661,63.297,106.438,63.32,106.368,63.272,106.403,63.113,106.126,63.048,106.225,62.894,106.435,62.847,106.525,62.741,106.495,62.692,106.726,62.588,106.714,62.488,106.479,62.329,106.397,62.314,106.443,62.185,106.375,62.103,106.404,62.031,106.218,62.003,105.902,61.835,105.941,61.685,105.823,61.631,105.718,61.65,105.55,61.566,105.367,61.53,105.253,61.42,104.957,61.398,104.904,61.368,105.005,61.198,104.837,61.176,104.635,61.036,104.571,60.955,104.629,60.89,104.46,60.839,104.594,60.735,104.523,60.645,104.676,60.4,104.889,60.302,105.221,60.276,105.321,60.295,105.476,60.254,105.424,60.123,105.485,60.022,105.377,59.822,105.186,59.858,105.035,59.836,105.005,59.766,104.799,59.691,104.883,59.525,104.986,59.461,105.225,59.429,105.196,59.303,105.288,59.264,105.353,59.108,104.948,58.959,104.759,58.82,104.872,58.719,104.787,58.666,104.458,58.602,104.374,58.713,104.22,58.758,103.949,58.774,103.89,58.903,103.693,58.923,103.375,59.034,103.246,59.18,103.239,59.284,103.083,59.264,102.999,59.315,102.759,59.199,102.588,59.203,102.455,59.153,102.407,59.067,102.439,58.944,102.537,58.897,102.565,58.797,102.52,58.728,102.311,58.642,102.162,58.516,101.774,58.453,101.678,58.407,101.531,58.225,101.298,58.219,101.208,58.142,101.137,57.985,100.971,57.87,100.846,57.845,100.885,57.77,100.801,57.673,100.955,57.555,100.911,57.452,100.716,57.413,100.638,57.477,100.419,57.542,100.275,57.805,100.129,57.867,100.079,58.045,100.017,58.081,99.794,58.063,99.703,58.086,99.498,57.929,99.411,57.796,99.08,57.77,98.962,57.723,98.768,57.791,98.546,57.791,97.987,57.822,97.934,57.811,97.355,57.061,97.489,56.964,97.469,56.827,97.547,56.793,97.765,56.788,97.762,56.559,97.868,56.555,97.882,56.427,97.841,56.393,97.591,56.387,97.565,56.19,97.44,56.147,97.352,56.051,97.125,56.102,96.986,56.018,96.96,55.861,96.76,55.746,96.753,55.651,96.82,55.558,96.918,55.33,96.72,55.265,96.685,55.085,96.586,55.019,96.712,54.927,96.673,54.806,96.565,54.697,96.552,54.58,96.495,54.511,96.351,54.562,96.221,54.543,96.039,54.558,95.981,54.452,95.897,54.389,95.717,54.388,95.653,54.282,95.678,54.234,95.919,54.156,96.079,54.063,96.126,53.972,96.406,53.783,96.505,53.684,96.684,53.639]},{"z":4.7,"b":[45.442,60.854,50.467,66.119],"l":[49.006,66.119,48.989,65.269,49.021,65.238,49.567,65.292,49.663,65.255,50.063,64.89,50.16,64.847,50.467,64.792,50.348,64.538,50.273,64.513,49.627,64.592,49.45,64.463,48.95,64.491,48.84,64.357,48.518,64.356,48.408,64.224,48.196,64.239,48.116,64.348,46.611,64.327,46.323,64.265,45.825,64.284,45.442,64.188,45.593,64.064,45.884,63.964,46.11,63.946,46.436,63.888,46.819,63.747,46.97,63.64,47.026,63.531,46.937,63.352,47.091,63.246,47.366,63.167,47.555,63.148,47.516,63.037,47.4,62.955,47.083,62.84,47.238,62.636,47.235,62.351,47.386,62.32,47.413,62.193,47.621,62.185,47.65,62.297,47.751,62.322,48.291,62.322,48.237,62.473,48.366,62.729,48.629,62.706,48.714,62.816,48.875,62.829,49.609,62.77,49.369,62.149,49.213,62.091,49.052,61.691,49.097,61.658,49.417,61.618,49.461,61.586,49.302,61.176,49.098,61.175,48.889,61.141,48.509,61.048,48.446,60.984,48.419,60.854]},{"z":4.7,"b":[47.043,60.854,48.419,61.059],"l":[48.419,60.854,47.984,60.896,47.965,60.999,47.568,61.059,47.245,61.056,47.104,60.956,47.043,60.854]},{"z":4.7,"b":[108.027,58.223,119.132,64.282],"l":[119.108,58.223,119.07,58.341,119.132,58.472,118.98,58.543,118.821,58.574,118.893,58.816,118.795,58.935,118.84,59.016,118.707,59.064,118.689,59.191,118.84,59.3,118.734,59.42,118.407,59.512,118.336,59.596,118.058,59.583,117.906,59.444,117.779,59.528,117.598,59.472,117.352,59.497,117.089,59.588,117.19,59.646,117.228,59.798,117.065,59.908,117.296,60.019,117.077,60.029,116.954,60.169,116.722,60.266,116.582,60.36,116.075,60.407,115.999,60.457,115.842,60.468,115.686,60.526,115.395,60.487,115.112,60.395,115.007,60.269,114.836,60.19,114.697,60.219,114.54,60.102,114.545,59.985,114.309,59.879,114.161,59.748,114.03,59.681,113.849,59.682,113.779,59.609,113.592,59.565,113.605,59.499,113.435,59.402,113.465,59.266,113.264,59.157,113.152,59.164,112.728,59.083,112.554,59.033,112.612,58.954,112.451,58.924,112.465,59.168,112.631,59.31,112.435,59.326,112.218,59.5,111.933,59.272,111.776,59.276,111.625,59.207,111.47,59.267,111.316,59.257,111.164,59.189,110.713,59.259,110.58,59.191,110.589,59.131,110.471,59.034,110.287,58.985,110.119,58.98,109.636,59.066,109.499,59.294,109.268,59.316,109.269,59.449,109.517,59.63,109.504,59.764,109.65,59.871,109.764,60.027,109.694,60.129,109.741,60.233,109.917,60.451,110.116,60.587,110.057,60.674,110.268,60.702,110.24,60.831,110.458,60.988,110.517,61.091,110.481,61.159,110.169,61.164,110.094,61.263,109.792,61.325,109.846,61.424,109.851,61.557,109.62,61.718,109.598,61.87,109.819,62.008,110,62.164,109.996,62.278,109.918,62.406,109.557,62.431,109.306,62.484,109.512,62.777,109.614,62.874,109.465,62.95,109.453,63.149,109.215,63.442,109.053,63.549,108.857,63.537,108.547,63.599,108.16,63.563,108.112,63.617,108.265,63.673,108.279,63.796,108.687,63.795,108.766,63.86,108.735,63.977,108.495,64.116,108.533,64.228,108.468,64.282,108.027,64.224]},{"z":4.7,"b":[105.54,64.182,112.579,73.713],"l":[108.027,64.224,107.856,64.182,107.57,64.299,107.368,64.251,106.877,64.414,106.395,64.443,106.204,64.403,106.132,64.481,105.803,64.53,105.708,64.653,105.873,64.68,105.832,64.76,105.946,64.884,106.123,64.905,105.973,65.012,106.282,65.137,106.454,65.233,106.451,65.313,106.635,65.393,106.836,65.378,106.94,65.511,106.713,65.549,106.631,65.635,106.44,65.662,106.478,65.845,106.471,66.007,106.33,66.154,106.171,66.159,106.103,66.378,106.322,66.479,106.249,66.581,106.247,66.702,106.047,66.789,106.044,66.901,105.687,66.939,105.54,67.023,105.894,67.044,106.055,67.167,106.317,67.209,106.492,67.284,106.685,67.254,106.802,67.348,106.833,67.673,106.875,67.84,106.896,68.143,106.885,68.865,106.153,69.391,106.427,69.564,106.604,69.517,106.839,69.518,107.124,69.588,108.007,69.705,108.149,69.825,108.291,69.857,108.871,69.84,108.941,69.789,109.237,69.776,109.36,69.838,109.509,70.033,109.358,70.069,109.277,70.207,109.535,70.264,109.602,70.378,109.995,70.417,110.117,70.478,110.103,70.632,110.489,70.726,110.539,70.801,110.903,70.808,111.335,70.855,111.522,70.939,111.953,70.982,112.027,71.042,112.277,71.013,112.579,71.114,112.513,71.268,111.997,71.414,111.996,72.133,111.817,72.132,111.643,72.274,111.085,72.37,111.281,72.478,111.21,72.545,110.881,72.58,110.702,72.65,110.611,72.781,110.627,72.895,110.837,72.945,110.881,73.023,110.755,73.175,110.571,73.253,110.523,73.404,110.818,73.559,111.073,73.583,111.111,73.641,110.827,73.713]},{"z":4.7,"b":[110.801,73.759,111.303,73.883],"l":[110.801,73.759,111.303,73.883]},{"z":4.7,"b":[49.006,66.119,65.48,68.43],"l":[65.48,68.43,64.745,68.404,64.523,68.344,64.53,68.2,64.325,68.134,63.829,68.017,63.944,67.884,63.69,67.762,63.445,67.771,63.161,67.691,62.871,67.58,62.821,67.388,61.851,67.126,61.695,67.03,61.432,67,60.082,66.99,59.918,67.002,58.79,67,53.772,66.999,52.246,67.089,51.836,66.995,51.811,66.954,51.495,66.907,51.51,66.783,49.006,66.119]},{"z":4.7,"b":[44.214,65.808,49.006,66.408],"l":[49.006,66.119,48.156,66.13,47.753,65.941,47.764,65.861,47.661,65.808,47.407,65.836,47.11,65.821,46.347,66.028,45.832,66.088,45.741,66.068,44.578,66.307,44.476,66.37,44.214,66.408]},{"z":4.7,"b":[62.018,65.717,66.206,68.43],"l":[62.018,65.717,62.504,65.831,62.839,65.869,62.807,65.937,62.87,66.079,63.066,66.232,63.241,66.313,63.306,66.4,63.809,66.547,63.969,66.646,64.316,66.668,64.567,66.794,64.999,66.865,65.116,66.908,65.09,67.054,65.19,67.145,65.422,67.215,65.708,67.335,65.937,67.393,66.11,67.489,66.026,67.576,65.836,67.555,65.831,67.643,66.206,67.693,66.022,67.794,66.083,67.934,65.437,67.92,65.29,68.012,65.33,68.085,65.273,68.216,65.48,68.43]},{"z":4.7,"b":[64.545,68.43,65.654,69.271],"l":[65.48,68.43,65.654,68.556,65.517,68.588,65.326,68.73,65.315,68.803,65.064,68.814,64.904,68.871,64.554,68.871,64.545,69.032,64.99,69.205,65.038,69.271]},{"z":4.6,"b":[107.238,37.068,107.658,37.853],"l":[107.658,37.853,107.607,37.766,107.509,37.764,107.436,37.663,107.317,37.59,107.322,37.528,107.238,37.301,107.325,37.164,107.285,37.068]},{"z":4.6,"b":[110.237,34.616,111.225,39.385],"l":[111.11,39.385,111.225,39.311,111.118,39.067,110.995,39.004,110.98,38.817,110.872,38.621,110.899,38.545,110.86,38.468,110.774,38.426,110.671,38.317,110.58,38.287,110.511,38.202,110.515,37.955,110.589,37.919,110.653,37.813,110.753,37.746,110.789,37.561,110.746,37.48,110.63,37.397,110.687,37.357,110.665,37.282,110.46,37.051,110.389,37.022,110.398,36.781,110.483,36.593,110.496,36.484,110.459,36.349,110.473,36.264,110.447,36.142,110.511,35.887,110.56,35.849,110.576,35.713,110.62,35.597,110.425,35.294,110.335,35.198,110.26,34.945,110.237,34.801,110.252,34.663,110.336,34.616]},{"z":4.6,"b":[109.207,29.066,113.902,30.096],"l":[109.207,29.104,109.269,29.242,109.338,29.29,109.432,29.534,109.472,29.568,109.7,29.656,109.783,29.735,110.104,29.785,110.225,29.737,110.362,29.65,110.447,29.727,110.526,29.725,110.591,29.771,110.501,29.863,110.463,30.014,110.543,30.055,110.716,30.03,110.742,30.096,110.944,30.064,111.246,30.032,111.415,29.926,111.585,29.904,111.769,29.92,111.924,29.856,112.126,29.707,112.17,29.649,112.363,29.572,112.444,29.641,112.648,29.627,112.786,29.737,112.886,29.792,112.946,29.709,112.908,29.601,112.928,29.495,113.008,29.518,113.146,29.472,113.186,29.532,113.416,29.765,113.52,29.842,113.536,29.686,113.635,29.685,113.673,29.545,113.729,29.437,113.588,29.294,113.641,29.222,113.655,29.124,113.711,29.073,113.773,29.125,113.902,29.066]},{"z":4.6,"b":[113.562,25.448,114.227,29.066],"l":[113.902,29.066,113.905,28.956,114.012,28.913,114.143,28.789,114.113,28.616,114.057,28.564,114.177,28.507,114.227,28.409,114.184,28.293,114.092,28.258,114.028,28.184,114.014,28.106,113.916,28.021,113.737,27.982,113.706,27.938,113.701,27.819,113.611,27.699,113.562,27.591,113.6,27.368,113.667,27.33,113.783,27.371,113.851,27.346,113.824,27.229,113.754,27.142,113.892,26.945,113.832,26.827,113.865,26.656,114.085,26.577,114.068,26.454,114.015,26.385,113.999,26.283,113.939,26.209,114.063,26.178,114.162,26.221,114.214,26.17,114.041,26.069,114.018,25.935,113.907,25.75,113.918,25.66,113.982,25.582,113.928,25.448]},{"z":4.6,"b":[106.382,28.2,109.258,29.246],"l":[109.258,28.506,109.155,28.436,109.095,28.205,108.943,28.2,108.728,28.242,108.784,28.426,108.73,28.475,108.583,28.374,108.582,28.601,108.33,28.689,108.367,28.777,108.341,28.92,108.286,29.077,108.055,29.065,107.875,28.984,107.79,29.04,107.783,29.137,107.557,29.187,107.496,29.246,107.43,29.183,107.424,29.077,107.5,28.993,107.437,28.877,107.297,28.808,107.161,28.881,107.031,28.888,106.973,28.805,106.889,28.801,106.835,28.731,106.829,28.609,106.762,28.591,106.75,28.502,106.562,28.511,106.626,28.637,106.562,28.767,106.489,28.541,106.382,28.572]},{"z":4.6,"b":[108.791,26.039,109.507,28.506],"l":[109.258,28.506,109.264,28.322,109.354,28.265,109.299,28.095,109.363,28.03,109.31,27.97,109.328,27.808,109.413,27.739,109.447,27.65,109.418,27.56,109.318,27.501,109.277,27.439,109.206,27.445,109.015,27.262,108.9,27.189,108.897,27.147,108.791,27.083,108.874,27.018,108.95,27.027,109.008,27.094,109.095,27.134,109.156,27.08,109.231,27.152,109.438,27.131,109.499,27.062,109.507,26.976,109.444,26.901,109.505,26.825,109.397,26.755,109.294,26.727,109.377,26.646,109.403,26.537,109.315,26.426,109.297,26.313,109.399,26.27,109.428,26.099,109.457,26.039]},{"z":4.6,"b":[108.381,29.104,110.175,31.738],"l":[109.594,31.738,109.659,31.718,109.724,31.636,109.733,31.561,109.929,31.519,110.108,31.398,110.147,31.331,110.175,31.189,110.108,31.122,110.138,31,110.128,30.906,110.066,30.828,109.997,30.826,109.871,30.889,109.749,30.835,109.64,30.718,109.394,30.584,109.248,30.597,109.179,30.567,108.965,30.607,108.868,30.551,108.71,30.523,108.67,30.574,108.573,30.498,108.433,30.487,108.381,30.4,108.532,30.315,108.53,30.113,108.55,30.009,108.538,29.885,108.4,29.831,108.447,29.736,108.513,29.722,108.613,29.858,108.658,29.817,108.696,29.698,108.872,29.639,108.872,29.502,108.921,29.349,109.014,29.307,109.077,29.214,109.207,29.104]},{"z":4.6,"b":[109.207,28.506,109.285,29.104],"l":[109.207,29.104,109.275,29.049,109.246,28.931,109.247,28.786,109.281,28.729,109.213,28.629,109.285,28.573,109.258,28.506]},{"z":4.6,"b":[105.499,32.158,108.518,32.907],"l":[105.499,32.907,105.568,32.73,105.633,32.707,105.724,32.76,105.813,32.762,105.875,32.845,106.082,32.874,106.057,32.714,106.115,32.649,106.229,32.592,106.364,32.639,106.604,32.676,106.713,32.738,107.065,32.708,107.096,32.669,107.07,32.523,107.202,32.453,107.284,32.453,107.371,32.52,107.448,32.522,107.46,32.409,107.659,32.389,107.773,32.311,107.844,32.228,107.96,32.158,108.052,32.227,108.237,32.263,108.479,32.25,108.518,32.198]},{"z":4.6,"b":[108.518,31.705,109.594,32.198],"l":[108.518,32.198,108.789,32.045,109.056,31.941,109.189,31.84,109.253,31.744,109.336,31.705,109.594,31.738]},{"z":4.6,"b":[105.297,27.678,106.382,28.754],"l":[106.382,28.572,106.339,28.48,106.269,28.557,106.189,28.585,105.984,28.754,105.892,28.609,105.683,28.583,105.621,28.482,105.656,28.441,105.667,28.318,105.847,28.269,105.906,28.151,106.009,28.14,106.109,28.175,106.199,28.124,106.215,28.056,106.299,28,106.345,27.835,106.248,27.775,106.104,27.788,106.039,27.757,105.876,27.745,105.647,27.678,105.524,27.775,105.361,27.76,105.297,27.722]},{"z":4.6,"b":[97.361,32.403,101.778,34.005],"l":[97.716,32.544,97.664,32.56,97.431,32.7,97.384,32.779,97.361,32.897,97.469,32.983,97.504,33.046,97.476,33.12,97.501,33.194,97.616,33.328,97.724,33.406,97.539,33.454,97.477,33.562,97.391,33.611,97.384,33.87,97.61,33.931,97.696,34.005,97.745,33.865,97.819,33.864,98.035,33.96,98.212,33.939,98.302,33.846,98.458,33.84,98.646,33.676,98.633,33.608,98.736,33.49,98.773,33.303,98.852,33.175,99.101,33.072,99.225,32.999,99.289,32.887,99.509,32.835,99.699,32.745,99.729,32.758,99.769,32.922,99.841,32.957,100.047,32.935,100.137,32.848,100.159,32.782,100.119,32.671,100.223,32.637,100.255,32.726,100.399,32.758,100.498,32.669,100.561,32.563,100.473,32.485,100.531,32.403,100.603,32.451,100.643,32.594,100.697,32.686,100.902,32.63,101.049,32.676,101.171,32.688,101.237,32.808,101.232,32.858,101.131,32.938,101.164,33.128,101.22,33.171,101.445,33.236,101.644,33.128,101.727,33.268,101.665,33.32,101.691,33.42,101.778,33.53]},{"z":4.6,"b":[80.679,21.351,82.801,23.999],"l":[82.801,23.999,82.768,23.929,82.658,23.885,82.651,23.845,82.532,23.775,82.077,23.801,81.937,23.841,81.778,23.802,81.682,23.885,81.622,23.834,81.686,23.69,81.591,23.561,81.644,23.482,81.745,23.543,81.907,23.505,81.991,23.401,82.098,23.372,82.19,23.289,82.129,23.096,82.071,23.1,81.946,23.043,81.94,22.946,81.789,22.853,81.766,22.664,81.67,22.609,81.642,22.551,81.54,22.52,81.418,22.437,81.373,22.493,81.235,22.444,81.178,22.478,81.123,22.401,81.119,22.295,81.024,22.194,80.987,22.108,80.919,22.097,80.835,21.951,80.838,21.846,80.805,21.774,80.731,21.714,80.721,21.473,80.679,21.351]},{"z":4.6,"b":[105.291,28.572,108.518,32.198],"l":[108.518,32.198,108.389,32.184,108.419,32.1,108.345,32.074,108.268,31.973,108.287,31.922,108.501,31.791,108.516,31.708,108.407,31.566,108.323,31.499,108.223,31.468,108.194,31.331,108.033,31.222,108.007,31.039,107.932,30.949,107.93,30.873,107.85,30.81,107.769,30.827,107.736,30.883,107.621,30.837,107.508,30.822,107.457,30.761,107.478,30.61,107.372,30.5,107.229,30.238,107.033,30.058,106.77,30.037,106.717,30.059,106.618,30.269,106.532,30.338,106.449,30.324,106.385,30.264,106.239,30.22,106.125,30.342,105.999,30.385,105.792,30.423,105.709,30.274,105.557,30.148,105.739,30.033,105.71,29.868,105.558,29.804,105.551,29.748,105.412,29.703,105.291,29.585,105.315,29.47,105.405,29.43,105.422,29.334,105.506,29.291,105.645,29.27,105.701,29.224,105.745,28.995,105.795,28.937,105.878,28.92,106.008,28.973,106.26,28.869,106.257,28.794,106.298,28.672,106.382,28.572]},{"z":4.6,"b":[76.837,28.433,77.327,28.882],"l":[77.327,28.52,77.27,28.51,77.194,28.433,77.101,28.476,77.071,28.529,76.873,28.519,76.837,28.587,76.947,28.696,76.937,28.815,77.068,28.882,77.201,28.877]},{"z":4.6,"b":[76.815,30.681,76.827,30.789],"l":[76.815,30.789,76.827,30.681]},{"z":4.7,"b":[59.485,58.614,65.935,61.993],"l":[59.485,61.993,59.652,61.936,59.779,61.952,59.984,61.903,59.988,61.747,60.057,61.721,60.431,61.747,60.759,61.704,61.165,61.683,61.9,61.434,61.933,61.404,62.42,61.305,62.62,61.211,62.86,61.001,62.76,60.859,62.881,60.64,63.091,60.46,63.22,60.301,63.197,60.242,63.438,60.159,63.589,60.155,63.694,60.001,63.796,59.792,63.825,59.477,63.98,59.406,64.121,59.391,64.324,59.411,65.002,59.338,65.185,59.223,65.382,58.919,65.669,58.663,65.935,58.614]},{"z":4.7,"b":[59.232,61.993,62.018,65.717],"l":[59.485,61.993,59.404,62.108,59.507,62.307,59.565,62.335,59.644,62.51,59.527,62.541,59.394,62.731,59.483,62.892,59.278,62.97,59.232,63.089,59.33,63.274,59.292,63.342,59.494,63.611,59.521,63.791,59.584,63.938,59.761,63.996,59.838,64.08,59.588,64.233,59.633,64.344,59.589,64.467,59.484,64.497,59.704,64.668,59.648,64.772,59.75,64.859,59.889,64.901,60.147,65.065,60.305,65.071,60.677,64.912,60.831,65.051,61.003,65.06,61.107,65.161,61.241,65.192,61.263,65.307,61.511,65.5,61.882,65.702,62.018,65.717]},{"z":4.7,"b":[57.219,56.329,59.468,61.682],"l":[57.408,56.329,57.317,56.373,57.373,56.552,57.358,56.681,57.219,56.853,57.323,56.92,57.56,56.888,57.825,56.974,57.869,57.058,58.03,57.099,58.039,57.237,57.938,57.332,57.995,57.477,58.268,57.67,58.436,57.676,58.458,57.588,58.58,57.594,58.83,57.726,58.864,57.827,58.756,57.848,58.605,57.996,58.678,58.107,58.868,58.205,58.971,58.2,59.203,58.305,59.281,58.413,59.456,58.493,59.389,58.59,59.388,58.703,59.186,58.711,59.083,58.769,59.066,58.884,59.181,58.953,59.091,59.022,59.175,59.147,59.003,59.231,58.67,59.293,58.508,59.44,58.311,59.468,58.403,59.552,58.451,59.705,58.582,59.716,58.675,59.859,58.805,59.863,58.849,59.926,58.989,59.928,58.962,60.005,59.188,60.278,59.169,60.374,59.239,60.48,59.385,60.574,59.468,60.808,59.458,60.953,59.373,60.97,59.358,61.143,59.261,61.222,59.317,61.377,59.406,61.418,59.428,61.507,59.349,61.682]},{"z":4.7,"b":[59.345,61.682,59.485,61.993],"l":[59.349,61.682,59.389,61.744,59.345,61.857,59.485,61.993]},{"z":4.7,"b":[53.632,55.907,54.352,56.359],"l":[53.632,55.907,53.777,55.97,53.838,56.039,54.047,56.058,54.117,56.158,54.346,56.253,54.352,56.359]},{"z":4.7,"b":[54.352,56.108,57.408,56.515],"l":[54.352,56.359,54.43,56.343,54.532,56.515,54.918,56.392,54.968,56.32,55.448,56.341,55.548,56.4,55.876,56.437,56.073,56.289,56.275,56.305,56.402,56.387,56.655,56.22,56.73,56.224,56.864,56.108,57.213,56.182,57.274,56.305,57.408,56.329]},{"z":4.7,"b":[57.408,56.063,59.026,56.329],"l":[57.408,56.329,57.411,56.235,57.537,56.1,57.802,56.151,57.907,56.115,58.079,56.141,58.308,56.105,58.543,56.163,58.747,56.063,58.944,56.066,59.026,56.169]},{"z":4.7,"b":[53.141,54.046,54.177,55.907],"l":[53.47,54.046,53.348,54.382,53.426,54.499,53.414,54.552,53.515,54.616,53.607,54.728,53.579,54.822,53.422,54.982,53.269,55.012,53.141,55.099,53.147,55.147,53.327,55.166,53.418,55.222,53.591,55.212,53.723,55.338,53.891,55.381,53.997,55.544,54.175,55.624,54.177,55.707,53.976,55.853,53.759,55.866,53.632,55.907]},{"z":4.6,"b":[110.975,31.395,115.374,33.266],"l":[115.374,31.418,115.286,31.395,115.219,31.447,115.213,31.546,115.129,31.595,115.077,31.516,114.999,31.478,114.767,31.49,114.638,31.568,114.548,31.573,114.556,31.738,114.495,31.759,114.362,31.734,114.267,31.809,114.144,31.848,114.085,31.784,113.972,31.747,113.96,31.832,113.862,31.863,113.794,31.973,113.752,32.138,113.782,32.194,113.783,32.359,113.729,32.419,113.616,32.386,113.528,32.297,113.436,32.289,113.183,32.413,113.066,32.417,112.751,32.35,112.534,32.377,112.286,32.351,112.145,32.395,112.053,32.455,111.779,32.52,111.711,32.6,111.629,32.626,111.532,32.611,111.507,32.682,111.363,32.825,111.21,32.93,111.216,32.976,111.109,33.148,111.003,33.208,110.975,33.266]},{"z":4.6,"b":[115.374,29.789,116.147,31.418],"l":[115.374,31.418,115.55,31.183,115.669,31.217,115.768,31.144,115.877,31.129,115.946,31.034,116.036,30.989,115.859,30.861,115.862,30.785,115.775,30.755,115.768,30.688,115.833,30.594,115.947,30.488,115.877,30.39,115.912,30.316,116.04,30.221,116.081,30.047,116.078,29.969,116.126,29.905,116.147,29.789]},{"z":4.6,"b":[116.147,29.408,118.175,30.051],"l":[116.147,29.789,116.236,29.781,116.39,29.876,116.493,29.885,116.65,30.051,116.791,30.022,116.887,29.92,116.798,29.756,116.675,29.708,116.648,29.627,116.749,29.545,116.888,29.561,116.955,29.654,117.079,29.71,117.107,29.786,117.067,29.841,117.125,29.911,117.226,29.907,117.274,29.829,117.406,29.831,117.402,29.773,117.527,29.622,117.649,29.606,117.708,29.556,117.899,29.549,117.986,29.572,118.113,29.519,118.175,29.408]},{"z":4.6,"b":[114.88,31.418,116.641,34.567],"l":[116.151,34.567,116.157,34.447,116.401,34.275,116.552,34.287,116.575,34.192,116.543,34.121,116.641,33.953,116.445,33.845,116.421,33.791,116.182,33.718,116.08,33.778,115.971,33.905,115.961,34,115.841,34.007,115.733,34.066,115.654,34.048,115.586,33.98,115.561,33.898,115.63,33.839,115.574,33.75,115.635,33.592,115.443,33.548,115.346,33.453,115.316,33.374,115.345,33.251,115.251,33.121,115.143,33.084,114.969,33.124,114.887,33.083,114.88,32.993,114.925,32.958,115.185,32.859,115.199,32.591,115.367,32.56,115.457,32.507,115.565,32.402,115.647,32.402,115.694,32.492,115.79,32.471,115.888,32.394,115.915,32.25,115.928,32.025,115.901,31.794,115.663,31.784,115.532,31.736,115.39,31.512,115.374,31.418]},{"z":4.6,"b":[110.336,34.584,113.721,36.356],"l":[113.721,36.356,113.673,36.221,113.687,36.056,113.628,35.953,113.585,35.687,113.61,35.646,113.491,35.525,113.328,35.463,113.13,35.352,113.02,35.329,112.903,35.241,112.808,35.242,112.682,35.311,112.526,35.22,112.335,35.219,112.058,35.234,112.061,35.108,111.829,35.083,111.622,34.947,111.558,34.863,111.394,34.823,111.331,34.841,111.026,34.747,110.884,34.666,110.766,34.661,110.709,34.621,110.531,34.584,110.336,34.616]},{"z":4.6,"b":[113.902,29.066,116.147,29.864],"l":[113.902,29.066,114.063,29.203,114.247,29.249,114.243,29.309,114.308,29.355,114.47,29.318,114.709,29.39,114.818,29.394,114.895,29.52,114.973,29.56,115.272,29.615,115.417,29.712,115.486,29.864,115.705,29.861,115.856,29.739,115.999,29.744,116.147,29.789]},{"z":4.6,"b":[110.328,33.266,111.016,34.616],"l":[110.336,34.616,110.328,34.48,110.374,34.404,110.479,34.326,110.434,34.261,110.607,34.17,110.594,34.065,110.642,33.98,110.594,33.88,110.749,33.803,110.792,33.705,110.968,33.599,111.011,33.5,111.016,33.374,110.975,33.266]},{"z":4.6,"b":[109.487,31.738,110.975,33.266],"l":[110.975,33.266,110.9,33.205,110.823,33.199,110.711,33.108,110.604,33.154,110.547,33.245,110.432,33.173,110.234,33.156,110.094,33.213,109.96,33.214,109.875,33.243,109.66,33.264,109.516,33.237,109.491,33.145,109.757,33.08,109.781,33.045,109.74,32.93,109.836,32.896,109.899,32.914,110.096,32.841,110.155,32.73,110.168,32.622,110.074,32.617,110.001,32.552,109.88,32.592,109.635,32.591,109.579,32.541,109.561,32.405,109.487,32.336,109.548,32.234,109.619,32.189,109.666,32.043,109.61,31.927,109.594,31.738]},{"z":4.6,"b":[108.547,-7.665,108.895,-6.814],"l":[108.799,-7.665,108.734,-7.605,108.742,-7.537,108.705,-7.391,108.547,-7.279,108.595,-7.142,108.695,-7.14,108.789,-7.061,108.801,-6.935,108.895,-6.814]},{"z":4.6,"b":[106.401,-7.007,106.763,-6.301],"l":[106.401,-7.007,106.451,-6.836,106.506,-6.783,106.427,-6.697,106.405,-6.524,106.434,-6.439,106.421,-6.37,106.56,-6.355,106.631,-6.384,106.739,-6.386,106.763,-6.301]},{"z":4.6,"b":[121.068,-3.02,121.598,-2.873],"l":[121.598,-2.973,121.475,-3.02,121.278,-2.879,121.164,-2.873,121.068,-2.918]},{"z":4.6,"b":[110.043,-8.201,110.844,-7.569],"l":[110.844,-8.201,110.799,-8.122,110.787,-7.874,110.753,-7.833,110.579,-7.821,110.507,-7.783,110.441,-7.569,110.301,-7.669,110.137,-7.677,110.117,-7.762,110.043,-7.889]},{"z":4.6,"b":[119.412,-3.513,119.873,-1.955],"l":[119.467,-3.513,119.453,-3.281,119.412,-3.215,119.639,-3.121,119.543,-2.907,119.56,-2.746,119.661,-2.758,119.786,-2.695,119.739,-2.565,119.707,-2.354,119.624,-2.294,119.646,-2.176,119.753,-2.082,119.825,-2.046,119.873,-1.955]},{"z":4.6,"b":[119.873,-2.973,121.787,-1.86],"l":[119.873,-1.955,120.048,-1.861,120.157,-1.86,120.292,-1.9,120.448,-1.866,120.553,-2.025,120.625,-2.104,120.804,-2.239,121.35,-2.348,121.449,-2.388,121.638,-2.558,121.742,-2.61,121.787,-2.67,121.772,-2.801,121.598,-2.973]},{"z":4.6,"b":[77.201,28.52,77.338,28.877],"l":[77.327,28.52,77.338,28.626,77.307,28.721,77.227,28.784,77.201,28.877]},{"z":4.6,"b":[110.944,-8.226,111.673,-6.734],"l":[110.944,-8.226,110.965,-8.117,111.099,-8.009,111.228,-7.938,111.308,-7.838,111.265,-7.732,111.191,-7.707,111.217,-7.613,111.185,-7.529,111.197,-7.309,111.22,-7.251,111.444,-7.311,111.46,-7.274,111.608,-7.154,111.598,-6.961,111.629,-6.8,111.673,-6.734]},{"z":4.6,"b":[78.169,23.999,82.808,26.851],"l":[82.801,23.999,82.679,24.167,82.733,24.214,82.767,24.386,82.734,24.42,82.734,24.552,82.808,24.566,82.772,24.668,82.692,24.72,82.624,24.692,82.427,24.719,82.424,24.648,82.303,24.638,82.319,24.702,82.033,24.859,81.881,24.922,81.817,25.036,81.585,25.08,81.537,25.2,81.361,25.157,81.243,25.17,81.239,25.098,81.185,24.973,81.098,24.909,80.999,24.966,80.893,24.984,80.779,24.971,80.859,25.188,80.806,25.212,80.718,25.082,80.647,25.07,80.58,25.148,80.515,25.041,80.458,25.015,80.288,25.016,80.265,25.064,80.422,25.181,80.395,25.247,80.326,25.282,80.284,25.414,80.191,25.413,80.125,25.351,80.015,25.331,79.852,25.248,79.863,25.153,79.661,25.148,79.542,25.177,79.468,25.104,79.339,25.147,79.425,25.257,79.295,25.259,79.269,25.164,79.109,25.195,79.024,25.175,78.983,25.237,78.885,25.266,78.955,25.386,78.919,25.505,78.853,25.434,78.749,25.426,78.728,25.346,78.597,25.414,78.516,25.266,78.61,25.136,78.64,24.964,78.732,24.815,78.715,24.685,78.75,24.63,78.879,24.613,78.955,24.393,78.826,24.23,78.747,24.279,78.656,24.295,78.489,24.411,78.386,24.301,78.33,24.342,78.35,24.408,78.278,24.453,78.229,24.533,78.265,24.583,78.261,24.693,78.169,24.865,78.337,25.023,78.366,25.186,78.262,25.412,78.33,25.465,78.371,25.553,78.558,25.576,78.715,25.626,78.754,25.674,78.74,25.79,78.808,25.842,78.954,26.11,78.963,26.278,79.061,26.355,79.028,26.498,78.974,26.572,78.936,26.694,78.842,26.712,78.693,26.798,78.564,26.758,78.371,26.851,78.2,26.824]},{"z":4.6,"b":[77.281,27.809,77.529,28.52],"l":[77.281,27.809,77.424,27.891,77.48,27.898,77.526,27.974,77.468,28.047,77.529,28.253,77.467,28.315,77.497,28.393,77.327,28.52]},{"z":4.6,"b":[77.065,28.877,77.585,30.383],"l":[77.201,28.877,77.196,28.998,77.141,29.13,77.131,29.281,77.15,29.403,77.065,29.57,77.088,29.659,77.18,29.804,77.23,29.977,77.285,30.038,77.57,30.278,77.585,30.383]},{"z":4.6,"b":[83.337,24.14,83.523,24.53],"l":[83.523,24.53,83.402,24.498,83.454,24.362,83.337,24.14]},{"z":4.6,"b":[82.801,23.911,83.337,24.14],"l":[83.337,24.14,83.206,23.944,83.089,23.911,82.964,23.914,82.801,23.999]},{"z":4.6,"b":[83.523,24.307,87.784,25.324],"l":[87.784,25.214,87.726,25.252,87.599,25.267,87.523,25.324,87.407,25.181,87.292,25.174,87.266,25.072,87.186,25.062,87.117,24.971,87.119,24.851,87.078,24.745,87.05,24.597,86.925,24.614,86.89,24.532,86.755,24.588,86.702,24.548,86.586,24.561,86.494,24.48,86.439,24.34,86.276,24.435,86.29,24.552,86.155,24.556,86.098,24.69,86.033,24.712,85.912,24.698,85.831,24.771,85.746,24.77,85.647,24.649,85.662,24.569,85.578,24.558,85.526,24.502,85.391,24.509,85.281,24.487,84.961,24.348,84.889,24.373,84.798,24.497,84.588,24.394,84.519,24.308,84.472,24.307,84.312,24.451,84.305,24.531,84.096,24.504,84.044,24.624,83.918,24.544,83.788,24.537,83.696,24.506,83.523,24.53]},{"z":4.6,"b":[83.323,24.53,84.606,27.378],"l":[83.523,24.53,83.531,24.704,83.509,24.762,83.36,24.863,83.36,25.009,83.323,25.068,83.334,25.173,83.384,25.236,83.775,25.445,83.904,25.529,84.143,25.736,84.213,25.728,84.239,25.648,84.324,25.659,84.315,25.742,84.373,25.765,84.487,25.701,84.601,25.747,84.606,25.8,84.532,25.862,84.42,25.89,84.374,25.94,84.261,25.931,84.15,26.066,84.065,26.104,83.993,26.241,84.187,26.293,84.178,26.404,84.026,26.469,83.955,26.454,83.92,26.533,84.054,26.565,84.099,26.65,84.383,26.642,84.306,26.744,84.246,26.757,84.241,26.874,84.149,26.876,84.071,26.924,84.023,27.06,83.938,27.164,83.942,27.222,83.829,27.378]},{"z":4.6,"b":[76.768,30.383,77.585,30.908],"l":[77.585,30.383,77.303,30.46,77.148,30.539,77.15,30.664,77.115,30.722,77.002,30.777,76.906,30.899,76.768,30.908]},{"z":4.7,"b":[33.571,44.398,33.852,44.852],"l":[33.73,44.398,33.852,44.432,33.806,44.527,33.713,44.582,33.709,44.666,33.611,44.721,33.675,44.792,33.571,44.852]},{"z":4.6,"b":[103.592,-4.927,103.765,-4.805],"l":[103.592,-4.927,103.765,-4.805]},{"z":4.6,"b":[101.027,-2.48,101.336,-2.297],"l":[101.027,-2.48,101.171,-2.367,101.336,-2.297]},{"z":4.6,"b":[103.765,-4.947,105.828,-3.756],"l":[105.828,-4.158,105.709,-4.15,105.513,-3.868,105.31,-3.756,105.266,-3.886,105.129,-3.956,105.111,-4.06,105.009,-4.169,104.84,-4.221,104.674,-4.297,104.527,-4.32,104.397,-4.405,104.309,-4.44,104.293,-4.495,104.309,-4.628,104.265,-4.708,104.337,-4.814,104.298,-4.892,104.21,-4.893,104.081,-4.947,103.889,-4.934,103.855,-4.854,103.765,-4.805]},{"z":4.6,"b":[102.067,-4.805,103.765,-2.782],"l":[103.765,-4.805,103.723,-4.757,103.694,-4.639,103.622,-4.505,103.616,-4.418,103.391,-4.335,103.186,-4.243,103.142,-4.047,103.107,-4.02,102.981,-4.035,102.908,-3.991,102.845,-4,102.768,-3.961,102.63,-3.814,102.702,-3.791,102.897,-3.631,102.993,-3.584,103.017,-3.485,102.975,-3.396,102.87,-3.376,102.766,-3.279,102.631,-3.373,102.563,-3.344,102.462,-3.25,102.489,-3.151,102.431,-3.088,102.241,-3.059,102.224,-2.954,102.067,-2.782]},{"z":4.6,"b":[101.336,-2.782,102.067,-2.236],"l":[102.067,-2.782,101.963,-2.735,101.9,-2.74,101.737,-2.636,101.564,-2.45,101.517,-2.305,101.436,-2.236,101.336,-2.297]},{"z":4.6,"b":[101.114,-2.297,101.841,-1.038],"l":[101.336,-2.297,101.26,-2.081,101.142,-1.92,101.114,-1.705,101.226,-1.728,101.476,-1.702,101.519,-1.64,101.675,-1.5,101.678,-1.289,101.794,-1.239,101.841,-1.166,101.783,-1.132,101.77,-1.038]},{"z":4.6,"b":[123.075,0.306,123.5,0.941],"l":[123.075,0.941,123.205,0.837,123.247,0.733,123.325,0.664,123.439,0.611,123.481,0.557,123.5,0.43,123.457,0.306]},{"z":4.6,"b":[102.067,-2.782,104.532,-1.694],"l":[104.532,-1.734,104.428,-1.694,104.376,-1.714,104.322,-1.804,104.126,-1.831,103.958,-1.791,103.886,-1.795,103.535,-1.968,103.524,-2.225,103.46,-2.238,103.463,-2.352,103.419,-2.394,103.304,-2.285,103.272,-2.209,103.216,-2.186,103.17,-2.286,103.205,-2.361,103.123,-2.398,102.862,-2.354,102.864,-2.462,102.792,-2.568,102.619,-2.672,102.596,-2.71,102.487,-2.73,102.398,-2.71,102.26,-2.64,102.233,-2.684,102.067,-2.782]},{"z":4.6,"b":[100.163,-1.038,101.77,0.758],"l":[101.77,-1.038,101.674,-0.967,101.525,-0.932,101.434,-0.847,101.331,-0.787,101.236,-0.703,101.04,-0.474,101,-0.401,100.856,-0.322,100.79,-0.206,100.749,-0.064,100.846,-0.03,100.847,0.186,100.51,0.444,100.369,0.411,100.265,0.45,100.163,0.648,100.207,0.711,100.173,0.758]},{"z":4.6,"b":[100.071,0.758,100.441,2.555],"l":[100.173,0.758,100.21,0.866,100.21,1.054,100.225,1.146,100.148,1.225,100.13,1.3,100.071,1.359,100.105,1.415,100.35,1.525,100.441,1.599,100.435,1.667,100.376,1.814,100.304,1.881,100.319,2.046,100.313,2.177,100.279,2.355,100.314,2.555]},{"z":4.6,"b":[121.16,0.483,122.197,1.028],"l":[122.197,1.028,122.046,0.944,121.993,0.935,121.84,0.97,121.508,0.847,121.378,0.861,121.303,0.826,121.235,0.725,121.16,0.686,121.257,0.587,121.334,0.576,121.333,0.483]},{"z":4.6,"b":[101.77,-1.085,103.525,-0.739],"l":[103.525,-0.739,103.1,-0.772,102.917,-0.763,102.804,-0.793,102.682,-0.904,102.541,-1.085,102.452,-1.068,102.356,-1.01,102.261,-0.88,102.189,-0.856,102.013,-0.88,101.935,-0.927,101.882,-1,101.77,-1.038]},{"z":4.6,"b":[121.598,-3.246,122.349,-2.973],"l":[121.598,-2.973,121.781,-2.986,122.058,-3.093,122.207,-3.111,122.349,-3.246]},{"z":4.6,"b":[119.494,-1.955,119.873,-0.834],"l":[119.873,-1.955,119.823,-1.872,119.846,-1.792,119.822,-1.729,119.725,-1.613,119.693,-1.461,119.642,-1.394,119.494,-1.329,119.527,-1.224,119.602,-1.151,119.572,-1.052,119.566,-0.834]},{"z":4.6,"b":[106.763,-6.382,106.989,-6.027],"l":[106.763,-6.301,106.924,-6.382,106.985,-6.235,106.989,-6.027]},{"z":4.6,"b":[106.684,-6.301,106.763,-6.059],"l":[106.763,-6.301,106.702,-6.21,106.684,-6.102,106.731,-6.059]},{"z":4.6,"b":[120.883,31.012,121.341,31.491],"l":[120.909,31.012,120.883,31.14,121.035,31.144,121.059,31.248,121.126,31.277,121.136,31.374,121.187,31.458,121.341,31.491]},{"z":4.6,"b":[116.377,31.133,119.63,34.64],"l":[119.63,31.133,119.363,31.192,119.317,31.267,119.163,31.287,119.075,31.239,118.816,31.226,118.756,31.281,118.751,31.358,118.827,31.398,118.881,31.522,118.86,31.627,118.681,31.647,118.684,31.701,118.477,31.79,118.481,31.859,118.352,31.947,118.383,32.054,118.499,32.138,118.514,32.2,118.65,32.238,118.684,32.339,118.668,32.453,118.612,32.478,118.552,32.574,118.752,32.613,118.891,32.596,119.081,32.446,119.199,32.594,119.19,32.711,119.11,32.834,119.009,32.91,118.993,32.962,118.857,32.972,118.798,32.865,118.748,32.839,118.72,32.732,118.399,32.731,118.3,32.777,118.239,32.924,118.248,33.013,118.218,33.18,118.081,33.15,117.996,33.168,117.932,33.235,118.122,33.611,118.177,33.693,118.124,33.766,117.926,33.734,117.752,33.724,117.746,33.882,117.688,33.888,117.651,33.982,117.572,33.99,117.503,34.05,117.392,34.031,117.321,34.079,117.159,34.091,117.034,34.156,116.97,34.275,116.949,34.389,116.802,34.4,116.77,34.452,116.625,34.485,116.428,34.64,116.377,34.64]},{"z":4.6,"b":[119.63,30.764,120.909,31.176],"l":[119.63,31.133,119.74,31.176,119.932,31.15,119.943,31.087,120.129,30.943,120.213,30.921,120.362,30.939,120.447,30.89,120.491,30.764,120.578,30.846,120.709,30.891,120.692,30.977,120.909,31.012]},{"z":4.6,"b":[120.909,30.691,121.275,31.012],"l":[120.909,31.012,120.969,31.009,120.981,30.832,121.104,30.834,121.256,30.744,121.275,30.691]},{"z":4.6,"b":[118.001,28.289,118.476,29.408],"l":[118.446,28.289,118.476,28.332,118.458,28.517,118.416,28.604,118.426,28.695,118.372,28.799,118.303,28.836,118.229,28.955,118.064,29.051,118.001,29.144,118.052,29.275,118.132,29.298,118.175,29.408]},{"z":4.6,"b":[118.175,29.408,119.63,31.133],"l":[118.175,29.408,118.373,29.453,118.47,29.524,118.557,29.625,118.648,29.658,118.724,29.73,118.741,29.828,118.82,29.88,118.894,29.983,118.853,30.159,118.909,30.222,118.881,30.324,118.935,30.352,119.058,30.312,119.211,30.314,119.363,30.404,119.321,30.518,119.242,30.551,119.351,30.675,119.443,30.639,119.501,30.769,119.575,30.847,119.63,31.133]},{"z":4.6,"b":[115.843,24.917,118.446,28.289],"l":[115.888,24.917,115.888,25,115.843,25.195,115.908,25.238,115.979,25.351,116,25.497,116.045,25.575,116.028,25.636,116.124,25.764,116.135,25.866,116.18,25.907,116.323,25.955,116.397,26.061,116.419,26.153,116.399,26.288,116.502,26.407,116.594,26.395,116.619,26.492,116.542,26.563,116.505,26.689,116.545,26.868,116.67,26.987,116.923,27.021,117.04,27.11,117.154,27.276,117.101,27.347,117.125,27.43,117.112,27.568,117.225,27.723,117.287,27.773,117.284,27.857,117.544,27.967,117.605,27.866,117.746,27.813,117.823,27.937,117.946,27.973,118.061,27.979,118.131,28.041,118.368,28.1,118.349,28.22,118.446,28.289]},{"z":4.6,"b":[118.446,27.206,120.419,28.326],"l":[118.446,28.289,118.499,28.262,118.735,28.326,118.797,28.213,118.766,28.178,118.748,27.973,118.82,27.91,118.889,27.722,118.923,27.55,119.005,27.48,119.186,27.42,119.266,27.424,119.39,27.513,119.467,27.525,119.512,27.637,119.616,27.666,119.687,27.511,119.693,27.411,119.77,27.316,120.064,27.344,120.148,27.4,120.249,27.41,120.344,27.38,120.406,27.297,120.419,27.206]},{"z":4.6,"b":[81.759,17.909,84.778,19.161],"l":[81.759,17.909,81.924,18,82.042,18.043,82.132,18.039,82.24,17.993,82.33,18.044,82.353,18.155,82.31,18.195,82.361,18.292,82.368,18.414,82.449,18.522,82.503,18.511,82.511,18.422,82.597,18.325,82.587,18.244,82.781,18.358,82.78,18.417,82.878,18.409,82.904,18.355,83.039,18.39,83.037,18.469,83.084,18.525,83.028,18.641,83.127,18.758,83.202,18.742,83.394,18.869,83.327,18.93,83.341,19.005,83.424,18.964,83.49,19.052,83.573,19.07,83.613,19.13,83.706,19.001,83.802,19,83.854,18.86,83.902,18.806,84.071,18.818,84.153,18.788,84.318,18.809,84.463,18.946,84.525,19.033,84.641,19.101,84.695,19.161,84.778,19.116]},{"z":4.6,"b":[76.719,12.76,78.568,15.976],"l":[78.23,12.76,78.286,12.843,78.416,12.948,78.516,13.089,78.568,13.275,78.381,13.323,78.35,13.376,78.375,13.58,78.184,13.602,78.068,13.699,78.097,13.809,78.033,13.885,77.921,13.83,77.879,13.917,77.735,13.824,77.593,13.767,77.499,13.704,77.443,13.702,77.384,13.847,77.292,13.848,77.152,13.904,77.103,13.85,77.133,13.785,77.032,13.746,76.959,13.782,77.01,13.905,76.936,14.011,76.943,14.061,76.873,14.137,76.974,14.156,76.995,14.066,77.112,14.005,77.289,14.011,77.332,13.908,77.388,13.963,77.328,14.052,77.369,14.163,77.468,14.192,77.461,14.29,77.384,14.262,77.278,14.296,77.257,14.334,77.111,14.33,77.045,14.232,76.908,14.258,76.862,14.347,76.932,14.458,76.83,14.479,76.753,14.566,76.762,14.764,76.8,14.795,76.822,14.921,76.719,14.99,76.764,15.094,76.948,15.022,77.08,15.037,77.128,15.132,77.105,15.308,77.026,15.351,77,15.614,77.085,15.667,77.025,15.781,77.034,15.874,77.144,15.961,77.226,15.976,77.446,15.953]},{"z":4.6,"b":[77.446,15.833,81.776,17.909],"l":[77.446,15.953,77.641,15.901,77.85,15.894,77.888,15.919,78.087,15.833,78.235,15.95,78.256,16.006,78.4,16.083,78.5,16.094,78.734,16.03,78.839,16.067,78.839,16.125,79.022,16.247,79.161,16.219,79.215,16.244,79.233,16.331,79.213,16.493,79.266,16.57,79.406,16.587,79.504,16.642,79.721,16.689,79.752,16.724,79.882,16.703,79.958,16.63,80.043,16.739,80.073,16.823,80.002,16.871,80.078,16.973,80.22,17.033,80.281,16.997,80.351,16.862,80.506,16.792,80.586,16.934,80.474,16.937,80.384,16.976,80.502,17.108,80.591,17.14,80.67,17.08,80.83,17.046,80.893,17.156,80.99,17.218,81.144,17.25,81.214,17.324,81.408,17.371,81.47,17.42,81.535,17.638,81.629,17.763,81.776,17.833,81.759,17.909]},{"z":4.6,"b":[114.311,-3.434,115.68,-1.361],"l":[115.678,-1.426,115.68,-1.361,115.427,-1.434,115.404,-1.474,115.338,-1.765,115.358,-1.904,115.308,-1.992,115.33,-2.037,115.131,-2.208,114.905,-2.263,114.879,-2.326,114.908,-2.419,114.822,-2.562,114.785,-2.689,114.675,-2.759,114.631,-2.822,114.504,-2.881,114.471,-3.004,114.311,-3.434]},{"z":4.6,"b":[78.23,12.633,80.26,13.468],"l":[80.26,13.468,80.098,13.464,79.962,13.417,79.957,13.366,79.816,13.295,79.785,13.224,79.697,13.236,79.668,13.291,79.572,13.284,79.544,13.324,79.45,13.331,79.42,13.22,79.315,13.132,79.251,13.148,79.162,13.045,79.078,13.036,78.944,13.093,78.736,13.061,78.645,12.992,78.583,12.778,78.419,12.633,78.353,12.642,78.23,12.76]},{"z":4.6,"b":[80.278,13.513,80.288,13.527],"l":[80.288,13.527,80.278,13.513]},{"z":4.6,"b":[99.151,0.252,100.173,0.868],"l":[99.151,0.252,99.342,0.474,99.584,0.563,99.685,0.501,99.814,0.468,99.908,0.504,99.915,0.569,99.818,0.709,99.794,0.78,99.71,0.849,99.826,0.868,100.173,0.758]},{"z":4.6,"b":[97.784,2.189,98.268,4.295],"l":[98.101,2.189,98.128,2.327,98.063,2.444,98.025,2.576,98.069,2.677,98.057,2.812,97.923,2.909,97.896,3.083,97.936,3.141,97.864,3.257,97.99,3.33,97.907,3.433,97.914,3.484,97.844,3.58,97.784,3.745,97.893,3.848,97.89,3.915,98.001,3.98,98.063,4.24,98.188,4.295,98.268,4.288]},{"z":4.7,"b":[162.265,61.658,163.434,64.77],"l":[163.137,64.77,163.246,64.727,163.167,64.559,163.345,64.487,163.434,64.373,163.256,64.321,163.167,64.19,162.942,64.2,162.794,64.148,162.841,64.017,162.929,63.95,162.888,63.877,162.726,63.837,162.755,63.738,162.979,63.621,162.957,63.491,162.884,63.436,162.695,63.393,162.744,63.182,162.38,63.119,162.269,62.935,162.548,62.845,162.673,62.71,162.819,62.697,162.709,62.601,162.768,62.503,162.736,62.297,162.664,62.262,162.404,62.253,162.265,62.129,162.512,62.103,162.48,61.963,162.311,61.934,162.468,61.82,162.431,61.764,162.51,61.658]},{"z":4.6,"b":[72.947,20.04,73.166,20.127],"l":[73.166,20.115,73.161,20.047,72.997,20.04,72.947,20.127]},{"z":4.7,"b":[144.719,62.047,158.529,66.359],"l":[144.745,62.047,144.719,62.082,144.903,62.16,144.993,62.244,145.177,62.286,145.175,62.428,145.124,62.49,145.48,62.548,145.326,62.754,145.366,62.91,145.252,63.18,145.364,63.331,145.445,63.515,145.633,63.66,145.572,63.836,146.109,63.942,146.291,64.061,146.377,64.197,146.499,64.209,146.996,64.147,147.23,64.056,147.307,64.132,147.61,64.17,147.603,64.047,147.955,63.944,148.115,64.051,148.254,64.255,148.001,64.363,148.023,64.431,148.161,64.458,148.291,64.549,148.436,64.524,148.631,64.419,149.27,64.446,149.402,64.437,149.59,64.525,149.816,64.567,150.068,64.502,150.024,64.37,150.224,64.213,150.633,64.171,150.631,64.331,150.966,64.332,151.123,64.371,151.301,64.361,151.48,64.436,152.016,64.504,152.136,64.44,152.169,64.355,152.414,64.407,152.331,64.51,152.748,64.686,152.502,64.945,152.645,65.022,152.62,65.169,152.688,65.233,153.032,65.275,153.521,65.408,153.401,65.504,153.531,65.706,153.454,65.788,153.505,65.842,153.698,65.886,154.041,65.869,154.25,65.83,154.374,65.921,154.359,66.033,154.22,66.069,154.395,66.205,154.548,66.238,154.802,66.185,154.885,66.134,155.17,66.209,155.32,66.142,155.752,66.177,155.958,66.096,156.36,66.067,156.488,66.146,156.659,66.193,156.762,66.105,156.973,66.033,156.927,65.965,157.124,65.915,157.373,65.926,157.382,66,157.718,66.112,158.124,66.156,158.485,66.118,158.393,66.255,158.529,66.359]},{"z":4.7,"b":[158.529,64.649,163.137,66.359],"l":[158.529,66.359,158.776,66.284,159.166,66.234,159.203,66.164,159.052,66.048,158.934,65.903,158.922,65.743,159.377,65.658,159.834,65.529,159.997,65.523,160.17,65.451,160.174,65.376,160.418,65.251,160.537,65.162,160.688,65.149,161.01,65.17,161.281,65.141,161.417,65.069,161.645,65.013,161.784,64.838,162.293,64.742,162.608,64.734,162.736,64.661,162.997,64.649,163.117,64.693,163.137,64.77]},{"z":4.6,"b":[115.427,39.442,117.429,41.031],"l":[117.357,40.257,117.251,40.338,117.238,40.45,117.184,40.497,117.243,40.57,117.412,40.597,117.429,40.669,117.303,40.662,117.199,40.69,117.058,40.677,116.91,40.749,116.676,40.94,116.653,41.031,116.438,40.9,116.459,40.792,116.291,40.74,116.207,40.75,116.13,40.658,115.995,40.596,115.874,40.61,115.728,40.54,115.945,40.294,115.82,40.152,115.578,40.096,115.427,39.963,115.496,39.924,115.511,39.838,115.437,39.749,115.463,39.643,115.618,39.604,115.756,39.509,115.945,39.575,116.001,39.558,116.21,39.572,116.25,39.506,116.387,39.442,116.428,39.521,116.604,39.611,116.793,39.602]},{"z":4.6,"b":[116.793,39.602,116.876,39.686],"l":[116.793,39.602,116.876,39.686]},{"z":4.6,"b":[117.239,40.093,117.372,40.257],"l":[117.239,40.093,117.311,40.11,117.372,40.201,117.357,40.257]},{"z":4.6,"b":[113.454,36.356,114.541,40.743],"l":[113.721,36.356,113.631,36.438,113.535,36.483,113.547,36.549,113.459,36.636,113.454,36.737,113.608,36.769,113.679,36.872,113.776,36.891,113.754,37.115,113.858,37.209,113.887,37.296,114.053,37.501,114.096,37.6,114.1,37.698,114.026,37.751,113.987,37.839,113.891,37.949,113.791,38.153,113.558,38.243,113.518,38.378,113.534,38.537,113.609,38.644,113.669,38.66,113.825,38.81,113.76,38.909,113.853,39.072,114.013,39.119,114.089,39.074,114.216,39.069,114.394,39.169,114.42,39.3,114.541,39.531,114.402,39.658,114.383,39.861,114.233,39.876,114.086,39.917,113.936,40.016,113.996,40.132,114.085,40.185,114.45,40.294,114.434,40.361,114.286,40.379,114.258,40.55,114.152,40.674,114.154,40.743]},{"z":4.6,"b":[117.357,39.219,118.01,40.257],"l":[118.01,39.219,118.008,39.343,117.897,39.399,117.893,39.565,117.631,39.576,117.623,39.666,117.533,39.75,117.501,39.989,117.766,39.964,117.75,40.063,117.451,40.227,117.357,40.257]},{"z":4.6,"b":[115.276,36.158,117.837,38.271],"l":[115.46,36.158,115.449,36.249,115.343,36.352,115.276,36.474,115.342,36.6,115.478,36.754,115.626,36.794,115.773,36.946,115.852,37.057,115.907,37.206,115.975,37.322,116.115,37.368,116.256,37.387,116.22,37.462,116.35,37.621,116.411,37.485,116.609,37.639,116.744,37.799,116.84,37.834,117.421,37.847,117.569,38.049,117.687,38.073,117.78,38.164,117.837,38.271]},{"z":4.6,"b":[113.721,36.057,115.46,36.359],"l":[115.46,36.158,115.35,36.082,115.245,36.161,115.106,36.185,115.034,36.101,114.931,36.057,114.893,36.12,114.707,36.153,114.548,36.139,114.343,36.245,114.156,36.247,114.063,36.277,113.989,36.359,113.808,36.339,113.721,36.356]},{"z":4.6,"b":[116.151,34.567,116.377,34.64],"l":[116.377,34.64,116.267,34.577,116.151,34.567]},{"z":4.6,"b":[114.834,34.567,116.151,36.158],"l":[116.151,34.567,116.044,34.6,115.816,34.571,115.707,34.6,115.646,34.569,115.542,34.578,115.436,34.676,115.429,34.78,115.318,34.843,115.216,34.836,115.175,34.944,114.998,35.008,114.834,35.016,114.864,35.189,114.942,35.219,115.068,35.38,115.227,35.416,115.345,35.496,115.395,35.616,115.504,35.723,115.655,35.746,115.761,35.849,115.874,35.866,115.863,35.925,115.966,35.986,116.034,35.966,116.077,36.103,115.927,36.022,115.829,36.031,115.739,35.969,115.53,35.911,115.468,35.913,115.416,35.824,115.318,35.792,115.334,35.931,115.424,35.995,115.46,36.158]},{"z":4.6,"b":[81.394,17.807,83.996,22.539],"l":[81.394,17.807,81.444,17.88,81.543,18.262,81.644,18.292,81.741,18.358,81.792,18.438,81.913,18.545,81.904,18.622,81.969,18.686,82.139,18.747,82.169,18.879,82.224,18.922,82.225,19.015,82.145,19.244,82.169,19.387,82.029,19.518,82.029,19.776,81.824,19.913,81.868,20.047,81.94,20.105,82.046,20.018,82.096,20.051,82.228,19.959,82.29,19.836,82.381,19.886,82.682,19.823,82.695,20.001,82.611,19.988,82.416,20.067,82.434,20.159,82.422,20.428,82.34,20.556,82.367,20.657,82.342,20.861,82.454,20.847,82.562,20.954,82.668,21.155,82.965,21.179,83.078,21.115,83.198,21.173,83.279,21.359,83.361,21.337,83.381,21.4,83.328,21.499,83.352,21.577,83.444,21.719,83.473,21.807,83.557,21.835,83.571,21.923,83.515,21.964,83.546,22.102,83.605,22.196,83.733,22.262,83.841,22.349,83.946,22.371,83.996,22.441,83.97,22.539]},{"z":4.6,"b":[83.97,21.986,86.687,22.557],"l":[83.97,22.539,84.067,22.5,84.148,22.387,84.311,22.324,84.421,22.331,84.53,22.423,84.734,22.427,84.801,22.45,84.908,22.422,85.066,22.503,85.1,22.394,85.088,22.28,84.985,22.113,85.109,22.108,85.232,22.035,85.324,22.127,85.425,22.164,85.551,22.108,85.679,22.076,85.792,22.129,85.785,22.012,85.834,21.986,85.916,22.022,85.974,22.105,85.965,22.245,85.987,22.394,85.939,22.501,86.04,22.557,86.102,22.5,86.268,22.422,86.366,22.33,86.487,22.337,86.687,22.218]},{"z":4.6,"b":[116.377,34.399,119.296,35.106],"l":[116.377,34.64,116.42,34.834,116.47,34.898,116.632,34.938,116.828,34.958,116.941,34.88,116.993,34.774,117.061,34.709,117.182,34.45,117.235,34.461,117.274,34.564,117.337,34.589,117.469,34.475,117.591,34.472,117.653,34.526,117.773,34.527,117.79,34.651,117.915,34.677,118.05,34.651,118.165,34.399,118.391,34.444,118.423,34.578,118.477,34.689,118.596,34.707,118.681,34.684,118.759,34.749,118.777,34.834,118.842,34.935,118.867,35.036,119.076,35.05,119.176,35.106,119.296,35.071]},{"z":4.6,"b":[81.394,17.797,81.759,17.909],"l":[81.759,17.909,81.578,17.797,81.394,17.807]},{"z":4.6,"b":[25.013,-28.068,27.583,-26.738],"l":[25.013,-28.068,25.053,-28.058,25.129,-27.929,25.225,-27.878,25.241,-27.823,25.421,-27.71,25.581,-27.655,25.64,-27.673,25.799,-27.581,25.874,-27.621,26,-27.639,26.229,-27.489,26.25,-27.426,26.45,-27.353,26.486,-27.254,26.411,-27.177,26.49,-27.139,26.645,-26.999,26.914,-26.89,27.044,-26.929,27.178,-26.924,27.308,-26.862,27.408,-26.894,27.48,-26.874,27.519,-26.781,27.583,-26.738]},{"z":4.6,"b":[27.583,-26.894,28.256,-26.671],"l":[27.583,-26.738,27.644,-26.764,27.882,-26.724,27.97,-26.671,28.044,-26.818,28.256,-26.894]},{"z":4.6,"b":[29.016,-31.078,30.196,-29.979],"l":[30.196,-31.078,30.126,-30.934,30.064,-30.859,29.974,-30.832,29.794,-30.709,29.602,-30.688,29.476,-30.62,29.32,-30.655,29.22,-30.618,29.163,-30.555,29.2,-30.436,29.296,-30.374,29.298,-30.318,29.175,-30.286,29.102,-30.174,29.116,-30.078,29.016,-29.979]},{"z":4.6,"b":[25.467,-30.681,27.378,-30.312],"l":[27.378,-30.312,27.298,-30.488,27.132,-30.502,27.01,-30.533,26.922,-30.655,26.781,-30.643,26.703,-30.675,26.535,-30.651,26.442,-30.56,26.311,-30.582,26.205,-30.525,26.06,-30.514,25.725,-30.681,25.634,-30.657,25.611,-30.606,25.467,-30.613]},{"z":4.6,"b":[28.859,-28.773,29.771,-27.452],"l":[28.859,-28.773,28.973,-28.575,29.189,-28.535,29.235,-28.44,29.407,-28.357,29.488,-28.268,29.589,-28.25,29.675,-28.121,29.624,-28.048,29.722,-27.811,29.683,-27.668,29.766,-27.605,29.716,-27.518,29.771,-27.452]},{"z":4.6,"b":[29.771,-27.452,31.27,-27.236],"l":[29.771,-27.452,29.929,-27.364,30.133,-27.379,30.281,-27.302,30.423,-27.275,30.444,-27.309,30.575,-27.311,30.662,-27.277,30.833,-27.334,30.902,-27.311,30.98,-27.355,31.151,-27.324,31.27,-27.236]},{"z":4.6,"b":[92.995,24.033,93.312,24.399],"l":[93.312,24.033,93.249,24.057,93.106,24.045,92.995,24.095,93.018,24.399]},{"z":4.6,"b":[92.246,23.683,92.325,24.238],"l":[92.246,23.683,92.271,23.829,92.319,23.851,92.325,24.188,92.298,24.238]},{"z":4.6,"b":[118.855,39.986,119.85,41.273],"l":[119.85,39.986,119.738,40.1,119.743,40.205,119.635,40.209,119.58,40.373,119.6,40.477,119.564,40.54,119.294,40.531,119.19,40.62,119.191,40.7,119.068,40.651,118.855,40.821,118.965,41.075,119.048,41.094,119.164,41.176,119.231,41.273]},{"z":4.6,"b":[93.449,25.206,94.655,25.684],"l":[94.655,25.445,94.553,25.49,94.565,25.684,94.325,25.512,94.209,25.495,94.027,25.554,93.803,25.544,93.809,25.446,93.679,25.328,93.605,25.206,93.449,25.302]},{"z":4.6,"b":[123.696,40.859,125.766,43.472],"l":[125.704,40.859,125.576,40.913,125.692,41.004,125.766,41.139,125.738,41.245,125.651,41.286,125.47,41.575,125.45,41.68,125.306,41.68,125.328,41.747,125.286,41.826,125.295,41.957,125.458,42.105,125.481,42.151,125.284,42.234,125.269,42.309,125.179,42.333,125.209,42.406,125.102,42.494,125.096,42.577,125.019,42.647,124.942,42.804,124.86,42.888,124.888,43.087,124.766,43.106,124.673,42.999,124.52,42.872,124.376,42.986,124.419,43.082,124.296,43.157,124.282,43.226,124.006,43.31,123.807,43.454,123.749,43.472,123.696,43.353]},{"z":4.7,"b":[144.745,59.365,147.159,62.058],"l":[144.745,62.047,144.968,62.057,145.156,61.968,145.272,61.992,145.514,61.964,145.743,62.058,145.891,62.025,146.219,61.903,146.361,61.82,146.505,61.662,146.583,61.636,146.613,61.443,146.768,61.403,146.683,61.313,146.747,61.223,146.747,61.115,146.678,61.081,146.701,60.959,146.546,60.958,146.464,60.709,146.32,60.659,146.069,60.739,145.62,60.557,145.51,60.464,145.63,60.392,145.571,60.335,145.793,60.198,146.1,60.224,146.374,60.271,146.452,60.142,146.68,60.095,146.657,59.982,146.787,59.901,146.989,59.948,147.141,59.849,147.159,59.647,147.075,59.411,146.887,59.365]},{"z":4.7,"b":[133.972,46.637,139.012,48.445],"l":[139.012,47.396,138.856,47.466,138.755,47.438,138.629,47.451,138.595,47.548,138.618,47.629,138.517,47.669,138.61,47.726,138.707,47.735,138.826,47.861,138.581,48.031,138.722,48.136,138.71,48.183,138.558,48.181,138.48,48.303,138.489,48.402,138.306,48.445,138.179,48.379,138.145,48.316,137.995,48.297,137.843,48.209,137.579,48.228,137.449,48.202,137.315,48.084,137.417,47.978,137.597,47.91,137.601,47.784,137.787,47.697,137.896,47.724,138.048,47.668,138.087,47.517,138.006,47.455,137.755,47.329,137.598,47.3,137.452,47.198,137.328,47.214,137.094,47.297,137.013,47.236,136.934,47.057,136.974,47.012,136.835,46.944,136.728,46.841,136.577,46.777,136.451,46.81,136.384,46.769,136.264,46.765,136.062,46.85,135.942,46.79,135.85,46.811,135.746,46.949,135.526,46.953,135.42,46.911,135.363,46.95,135.433,47.019,135.205,47.153,135.078,47.193,135.015,47.128,134.851,47.154,134.653,47.092,134.549,46.967,134.443,46.942,134.412,46.866,134.465,46.786,134.412,46.678,134.199,46.644,133.972,46.637]},{"z":4.7,"b":[131.396,48.371,134.987,49.486],"l":[131.396,49.244,131.463,49.206,131.612,49.288,131.767,49.31,131.966,49.305,132.068,49.466,132.12,49.486,132.398,49.393,132.382,49.295,132.639,49.29,132.732,49.315,132.815,49.235,133.033,49.219,133.201,49.141,133.309,48.826,133.77,48.759,133.824,48.683,133.935,48.64,134.105,48.633,134.269,48.716,134.392,48.743,134.482,48.659,134.746,48.569,134.862,48.625,134.987,48.595,134.977,48.507,134.884,48.424,134.769,48.374,134.598,48.416,134.323,48.371]},{"z":4.7,"b":[130.862,55.679,144.745,62.573],"l":[130.923,55.679,130.932,55.749,130.862,55.889,130.924,55.942,130.964,56.129,131.093,56.206,131.088,56.345,131.184,56.468,131.394,56.479,131.502,56.55,131.67,56.55,131.676,56.648,131.797,56.717,131.751,56.843,131.445,57.036,131.474,57.079,131.179,57.137,131.183,57.235,131.509,57.251,131.599,57.296,131.565,57.365,131.617,57.453,131.729,57.5,131.693,57.561,131.975,57.586,132.035,57.691,131.888,57.809,131.757,57.853,131.806,57.906,131.61,57.995,131.477,58.168,131.705,58.174,132.039,58.02,132.017,58.19,132.196,58.248,132.207,58.35,132.129,58.489,132.372,58.524,132.564,58.64,132.599,58.891,132.808,58.88,132.871,58.987,132.862,59.064,133.126,59.204,133.384,59.24,133.543,59.294,133.625,59.232,133.767,59.223,134.047,59.256,134.219,59.209,134.555,59.181,134.725,59.211,134.752,59.153,135.209,59.122,135.26,59.22,135.422,59.387,135.619,59.495,135.791,59.534,136.053,59.438,136.759,59.355,136.836,59.398,137.182,59.446,137.349,59.549,137.456,59.551,137.458,59.647,137.616,59.74,137.833,59.761,138.044,59.741,138.174,59.68,138.253,59.713,138.174,59.837,138.263,59.938,138.2,60.231,138.315,60.318,138.327,60.406,138.243,60.493,138.404,60.611,138.429,60.702,138.284,60.782,138.186,60.895,138.205,60.952,138.346,61,138.383,61.1,138.703,61.193,138.651,61.282,138.719,61.337,138.941,61.321,139.12,61.449,139.247,61.433,139.435,61.491,139.548,61.482,139.667,61.641,139.919,61.802,139.988,61.972,140.114,61.971,140.256,62.038,140.3,62.119,140.294,62.274,140.404,62.422,140.635,62.425,140.705,62.573,140.881,62.499,141.055,62.482,141.067,62.417,141.302,62.431,141.545,62.179,141.838,62.041,142.008,62.027,142.187,62.081,142.29,61.963,142.438,61.87,142.653,61.962,142.826,61.949,142.928,61.901,143.085,62.018,143.126,62.101,143.362,62.024,143.62,62.016,143.571,61.948,143.954,61.957,143.927,61.766,144.002,61.727,144.245,61.76,144.54,61.856,144.586,61.978,144.745,62.047]},{"z":4.7,"b":[70.43,55.285,75.105,58.581],"l":[75.105,58.581,74.671,58.354,74.614,58.294,74.484,58.269,74.206,58.134,73.087,58.146,72.861,58.011,72.476,58.028,72.149,58.026,71.989,58.123,71.269,58.076,71.221,58.166,71.245,58.341,71.302,58.385,71.12,58.453,71.084,58.518,70.865,58.535,70.436,57.934,70.43,57.785,70.565,57.705,70.58,57.584,70.675,57.511,70.436,57.409,70.491,57.304,70.634,57.265,70.729,57.194,70.8,57.319,71.08,57.344,71.144,57.252,71.371,57.064,71.652,56.923,71.673,56.838,71.557,56.732,71.4,56.725,71.357,56.671,71.169,56.676,71.029,56.527,70.855,56.456,70.927,56.347,70.77,56.296,70.85,56.236,70.828,56.16,70.909,56.112,70.847,55.963,70.586,55.838,70.549,55.676,70.747,55.59,70.676,55.505,70.544,55.487,70.471,55.418,70.469,55.285]},{"z":4.7,"b":[61.98,54.163,63.329,56.134],"l":[63.329,54.163,63.25,54.247,63.128,54.306,63.13,54.448,63.238,54.478,63.295,54.564,63.192,54.686,62.903,54.694,62.711,54.626,62.424,54.723,62.379,54.672,62.147,54.644,62.025,54.717,62.123,54.815,61.98,54.934,61.989,55.035,62.244,55.079,62.165,55.242,62.049,55.311,62.234,55.341,62.304,55.403,62.579,55.517,62.516,55.564,62.517,55.738,62.343,55.905,62.305,55.979,62.132,56.065,62.072,56.134]},{"z":4.7,"b":[65.935,58.589,75.159,59.989],"l":[65.935,58.614,66.252,58.614,66.38,58.589,66.741,58.67,66.786,58.72,66.774,58.93,66.951,58.973,67.071,58.925,67.264,59.02,67.564,59.093,67.742,59.212,68.195,59.242,68.358,59.283,68.543,59.551,68.844,59.621,69.295,59.783,69.078,59.893,69.133,59.945,69.306,59.989,69.501,59.989,69.616,59.905,70.155,59.9,70.248,59.84,70.399,59.817,70.798,59.863,71.017,59.823,71.193,59.905,71.296,59.799,71.777,59.648,71.882,59.573,72,59.566,72.355,59.34,72.437,59.303,72.454,59.215,72.657,59.107,72.863,59.157,72.976,59.099,72.967,59,73.095,58.946,73.032,58.863,73.444,58.818,73.621,58.854,73.756,58.809,74,58.8,74.1,58.739,74.743,58.748,74.899,58.696,74.981,58.718,75.159,58.65]},{"z":4.7,"b":[75.105,58.581,75.159,58.65],"l":[75.159,58.65,75.105,58.581]},{"z":4.7,"b":[64.967,55.33,68.772,56.768],"l":[68.772,55.33,68.591,55.424,68.487,55.401,68.317,55.555,68.174,55.596,68.189,55.7,67.912,55.665,67.825,55.721,67.591,55.711,67.447,55.735,67.299,55.897,66.989,55.989,66.802,55.967,66.761,56.045,66.463,55.95,66.298,56.058,66.186,56.068,66.028,55.99,65.861,56.065,65.708,56.195,65.667,56.274,65.117,56.325,65.088,56.448,64.967,56.501,65.016,56.649,64.995,56.768]},{"z":4.7,"b":[64.839,56.768,66.197,58.614],"l":[64.995,56.768,65.14,56.77,65.227,56.86,65.101,56.892,64.994,57.042,64.992,57.221,64.918,57.296,64.842,57.446,64.937,57.549,64.839,57.641,65.428,57.834,65.54,57.78,65.702,57.901,66.197,58.033,66.002,58.297,65.935,58.614]},{"z":4.6,"b":[109.76,21.56,112.047,24.736],"l":[111.997,24.736,111.932,24.698,111.933,24.601,112.006,24.553,111.991,24.466,112.047,24.381,111.976,24.277,111.869,24.232,111.899,23.991,111.808,23.909,111.793,23.849,111.658,23.842,111.614,23.76,111.601,23.658,111.471,23.613,111.469,23.558,111.391,23.431,111.363,23.273,111.37,23.139,111.411,23.053,111.351,22.908,111.277,22.819,111.181,22.749,111.066,22.741,111.024,22.639,110.755,22.58,110.697,22.369,110.775,22.284,110.671,22.268,110.642,22.183,110.467,22.151,110.414,22.198,110.334,22.19,110.355,22.101,110.356,21.889,110.216,21.875,110.142,21.897,109.967,21.867,109.917,21.687,109.76,21.56]},{"z":4.6,"b":[134.255,-4.255,135.265,-2.838],"l":[134.873,-4.255,135.265,-3.76,134.258,-3.399,134.255,-3.308,134.332,-3.19,134.451,-3.078,134.445,-3.024,134.67,-2.838]},{"z":4.6,"b":[104.54,24.631,109.457,26.039],"l":[104.54,24.742,104.729,24.631,104.852,24.706,104.877,24.755,105.012,24.797,105.1,24.954,105.206,25.004,105.289,24.933,105.381,24.957,105.444,24.924,105.509,24.817,105.675,24.79,105.788,24.716,105.916,24.729,106.001,24.648,106.176,24.782,106.199,24.877,106.156,24.96,106.307,24.981,106.587,25.104,106.667,25.174,106.886,25.197,106.909,25.248,106.996,25.255,107.015,25.353,106.975,25.44,107.022,25.498,107.244,25.559,107.398,25.387,107.424,25.303,107.499,25.214,107.642,25.268,107.776,25.153,107.862,25.146,107.967,25.209,108.068,25.213,108.129,25.269,108.132,25.386,108.293,25.539,108.405,25.515,108.568,25.416,108.695,25.602,108.768,25.599,108.774,25.522,108.941,25.549,109.024,25.595,109.044,25.709,108.953,25.691,108.953,25.776,109.085,25.805,109.149,25.751,109.3,25.743,109.399,25.999,109.457,26.039]},{"z":4.6,"b":[109.457,24.643,111.997,26.382],"l":[109.457,26.039,109.603,26.053,109.672,25.985,109.677,25.889,109.772,25.908,109.781,26.024,109.838,26.045,109.912,26.181,110.062,26.15,110.034,26.036,110.138,26.052,110.247,25.978,110.342,26.113,110.485,26.172,110.537,26.225,110.577,26.351,110.689,26.332,110.724,26.274,110.879,26.275,110.935,26.382,111.123,26.306,111.252,26.28,111.257,26.21,111.202,25.93,111.256,25.869,111.408,25.908,111.411,25.803,111.324,25.73,111.305,25.639,111.316,25.503,111.254,25.399,111.164,25.365,111.114,25.239,110.993,25.151,110.955,25.025,111.006,24.935,111.079,24.944,111.126,25.044,111.274,25.148,111.427,25.106,111.462,25.019,111.43,24.943,111.474,24.793,111.428,24.681,111.517,24.643,111.616,24.707,111.667,24.78,111.797,24.76,111.926,24.775,111.997,24.736]},{"z":4.6,"b":[26.397,-25.317,28.294,-24.614],"l":[28.294,-25.317,28.231,-25.226,28.128,-25.2,28.124,-25.11,28.222,-25.078,28.098,-25.009,27.966,-24.985,27.906,-25.003,27.668,-24.971,27.587,-24.997,27.546,-25.093,27.488,-25.056,27.367,-25.059,27.289,-25.007,27.183,-24.991,27.055,-24.85,27.023,-24.724,26.944,-24.79,26.768,-24.866,26.626,-24.827,26.455,-24.812,26.41,-24.741,26.397,-24.614]},{"z":4.6,"b":[27.191,-26.738,28.294,-25.317],"l":[28.294,-25.317,28.235,-25.353,28.104,-25.372,28.004,-25.361,27.949,-25.41,28.033,-25.472,27.964,-25.593,27.943,-25.807,27.876,-25.853,27.728,-25.878,27.641,-25.804,27.459,-25.885,27.445,-26.007,27.395,-26.123,27.29,-26.146,27.324,-26.287,27.191,-26.382,27.23,-26.504,27.243,-26.64,27.335,-26.616,27.569,-26.65,27.583,-26.738]},{"z":4.6,"b":[28.355,-25.411,31.856,-23.963],"l":[31.856,-23.963,31.78,-23.965,31.712,-24.05,31.446,-24.023,31.413,-24.054,31.248,-24.101,31.203,-24.035,31.111,-24.097,31.051,-24.19,31.059,-24.321,31.195,-24.311,31.163,-24.379,31.26,-24.508,31.221,-24.557,30.975,-24.582,30.765,-24.465,30.734,-24.582,30.673,-24.618,30.72,-24.704,30.659,-24.819,30.492,-24.948,30.421,-24.851,30.35,-24.877,30.184,-24.855,30.185,-25.022,30.124,-25.123,30.035,-25.072,30.007,-25.15,30.004,-25.332,29.886,-25.344,29.77,-25.395,29.671,-25.37,29.528,-25.411,29.353,-25.404,29.268,-25.375,29.211,-25.408,29.166,-25.304,29.1,-25.318,29.038,-25.269,29.043,-25.142,28.969,-25.072,28.898,-24.956,28.732,-24.943,28.612,-25.002,28.629,-25.052,28.459,-25.117,28.355,-25.189,28.392,-25.224,28.606,-25.158]},{"z":4.6,"b":[28.294,-25.322,28.663,-25.158],"l":[28.606,-25.158,28.663,-25.247,28.527,-25.27,28.433,-25.322,28.294,-25.317]},{"z":4.6,"b":[28.256,-27.502,29.771,-26.894],"l":[28.256,-26.894,28.386,-26.933,28.426,-26.983,28.641,-27.01,28.652,-26.956,28.823,-27.039,28.959,-26.995,29.057,-27.03,29.126,-27.154,29.178,-27.138,29.442,-27.253,29.485,-27.342,29.615,-27.502,29.771,-27.452]},{"z":4.6,"b":[22.745,-33.98,24.182,-31.79],"l":[23.636,-33.98,23.687,-33.872,23.544,-33.796,23.367,-33.781,23.437,-33.712,23.618,-33.644,23.563,-33.496,23.437,-33.433,23.262,-33.399,23.029,-33.39,22.75,-33.402,22.745,-33.328,22.916,-33.096,22.916,-33.014,23.001,-32.889,23.195,-32.808,23.327,-32.808,23.37,-32.768,23.278,-32.668,23.237,-32.505,23.243,-32.425,23.325,-32.355,23.602,-32.325,23.674,-32.239,23.757,-32.196,23.885,-32.228,24.029,-32.175,24.09,-32.125,24.122,-32.003,24.182,-31.952,24.13,-31.858,24.147,-31.79]},{"z":4.7,"b":[42.489,51.608,43.08,52.403],"l":[42.489,51.608,42.49,51.693,42.643,51.804,42.694,51.966,42.806,52.012,42.751,52.078,42.752,52.174,42.996,52.365,43.08,52.403]},{"z":4.7,"b":[43.08,52.316,46.831,52.695],"l":[43.08,52.403,43.193,52.462,43.353,52.393,43.532,52.426,43.747,52.433,44.044,52.35,44.347,52.316,44.422,52.416,44.539,52.432,44.5,52.521,44.873,52.434,44.91,52.378,45.072,52.377,45.14,52.421,45.264,52.392,45.431,52.4,45.528,52.481,45.644,52.519,45.717,52.471,45.981,52.407,45.971,52.486,46.147,52.591,46.276,52.619,46.289,52.672,46.516,52.695,46.713,52.628,46.831,52.616]},{"z":4.7,"b":[53.47,51.575,58.857,54.046],"l":[53.47,54.046,53.598,53.883,53.656,53.752,53.878,53.658,54.108,53.417,54.247,53.375,54.302,53.41,54.52,53.36,54.559,53.27,54.664,53.236,54.802,53.271,54.927,53.22,54.955,53.11,55.162,52.823,55.264,52.862,55.407,52.837,55.412,52.658,55.545,52.542,55.546,52.385,55.778,52.344,55.889,52.399,55.919,52.499,56.078,52.604,56.365,52.523,56.445,52.42,56.451,52.309,56.194,52.189,56.217,52.132,56.495,52.162,56.627,52.118,56.685,52.045,56.608,51.829,56.748,51.773,56.715,51.721,56.78,51.592,56.89,51.625,56.876,51.699,56.999,51.728,57.036,51.66,57.207,51.575,57.374,51.639,57.432,51.726,57.621,51.759,57.653,51.863,57.784,51.826,57.882,51.846,57.981,51.786,58.092,51.795,58.236,51.738,58.494,51.762,58.576,51.793,58.639,52.016,58.694,52.049,58.606,52.181,58.691,52.265,58.857,52.263,58.828,52.433]},{"z":4.7,"b":[42.873,50.368,47.447,51.231],"l":[47.447,50.368,47.277,50.372,47.184,50.512,47.008,50.48,46.878,50.524,46.83,50.633,46.708,50.691,46.583,50.701,46.483,50.617,46.461,50.547,46.295,50.567,46.255,50.529,46.065,50.544,46.068,50.652,45.89,50.759,45.811,50.774,45.678,50.729,45.668,50.594,45.495,50.613,45.241,50.586,45.191,50.697,45.293,50.781,45.308,50.877,45.215,50.942,45.202,51.04,45.042,51.126,44.66,51.173,44.21,51.194,44.182,51.117,44.004,51.087,43.864,51.113,43.682,51.113,43.642,51.066,43.33,51.018,43.142,51.122,42.873,51.231]},{"z":4.7,"b":[50.796,51.729,52.539,54.377],"l":[50.796,51.729,51.415,52.103,51.453,52.219,51.413,52.35,51.563,52.447,51.471,52.569,51.545,52.681,51.732,52.668,51.772,52.783,51.715,52.84,51.862,52.926,52.061,53,52.069,53.154,52.128,53.174,52.143,53.297,52.193,53.375,52.079,53.501,52.253,53.654,52.413,53.927,52.378,53.966,52.476,54.077,52.339,54.174,52.271,54.327,52.506,54.322,52.539,54.377]},{"z":4.7,"b":[52.539,53.974,53.47,54.377],"l":[52.539,54.377,52.771,54.347,53.001,54.271,52.951,54.196,53.141,54.076,53.258,54.071,53.384,53.974,53.47,54.046]},{"z":4.7,"b":[59.026,56.134,62.072,56.376],"l":[59.026,56.169,59.135,56.206,59.122,56.317,59.194,56.352,59.349,56.318,59.701,56.306,59.789,56.233,59.921,56.265,60.24,56.209,60.315,56.238,60.585,56.214,60.751,56.237,60.909,56.188,61.15,56.217,61.084,56.326,61.15,56.376,61.267,56.331,61.385,56.367,61.537,56.297,61.642,56.319,61.816,56.239,61.864,56.181,62.072,56.134]},{"z":4.7,"b":[85.079,52.612,87.175,54.487],"l":[85.079,54.469,85.16,54.487,85.435,54.212,85.577,54.232,85.811,54.126,85.944,54.04,86.117,54,86.232,53.887,86.292,53.876,86.406,53.751,86.455,53.614,86.569,53.538,86.723,53.484,86.934,53.453,86.983,53.407,86.705,53.196,86.943,53.08,87.077,53.075,87.026,52.921,86.944,52.862,87.074,52.717,87.175,52.706,87.173,52.612]},{"z":4.7,"b":[58.828,51.975,60.149,52.495],"l":[60.064,51.975,59.914,52.1,59.975,52.182,60.14,52.267,60.149,52.421,59.946,52.428,59.781,52.492,59.509,52.435,59.398,52.495,59.249,52.487,59.175,52.277,58.828,52.433]},{"z":4.7,"b":[57.137,52.433,59.975,56.169],"l":[58.828,52.433,58.777,52.525,58.812,52.587,58.724,52.645,58.778,52.704,58.749,52.835,58.9,53.066,58.891,53.188,58.829,53.2,58.865,53.32,58.868,53.502,58.821,53.596,58.907,53.666,58.873,53.758,58.906,53.862,59.014,53.958,59.162,53.993,59.263,54.14,59.344,54.185,59.544,54.196,59.694,54.129,59.801,54.242,59.667,54.34,59.746,54.447,59.679,54.498,59.86,54.603,59.975,54.801,59.903,54.867,59.806,54.846,59.614,54.867,59.404,54.703,59.228,54.611,59.061,54.632,58.825,54.572,58.614,54.47,58.364,54.547,58.206,54.515,57.971,54.393,57.743,54.501,57.742,54.583,57.594,54.629,57.497,54.701,57.386,54.72,57.165,54.819,57.243,54.957,57.137,55.092,57.234,55.15,57.264,55.262,57.518,55.334,57.675,55.3,58.03,55.275,58.144,55.209,57.965,55.159,58.089,55.02,58.147,55.117,58.288,55.171,58.364,55.084,58.62,54.95,58.816,55.025,58.674,55.188,58.797,55.203,58.872,55.323,58.981,55.303,59.159,55.332,59.201,55.45,59.565,55.5,59.447,55.632,59.255,55.614,59.296,55.78,59.165,55.79,59.228,55.911,59.178,55.99,59.269,56.048,59.188,56.127,59.026,56.169]},{"z":4.7,"b":[84.41,54.469,85.079,56.044],"l":[84.41,56.044,84.529,56.042,84.567,55.961,84.506,55.868,84.606,55.841,84.566,55.709,84.709,55.662,84.771,55.54,84.731,55.391,84.875,55.389,84.823,55.246,84.916,55.165,84.883,54.985,85.076,54.868,84.955,54.744,85.043,54.662,84.948,54.604,85.079,54.469]},{"z":4.7,"b":[77.785,53.327,85.079,54.469],"l":[85.079,54.469,84.693,54.337,84.532,54.17,84.266,54.216,84.003,54.144,83.555,54.123,83.492,54.086,83.499,53.991,83.385,53.949,83.295,54.083,83.183,54.062,82.733,53.787,82.486,53.469,82.256,53.591,82.214,53.663,82.104,53.662,81.968,53.719,81.846,53.71,81.756,53.778,81.732,53.854,81.838,53.895,81.855,53.969,81.582,54.017,81.474,54.072,81.247,54.101,81.112,54.152,81.104,54.213,80.994,54.265,80.925,54.396,80.802,54.33,80.655,54.204,80.525,54.17,80.297,54.07,80.128,54.075,80.044,54.001,79.866,53.932,79.862,53.858,79.721,53.841,79.572,53.733,79.442,53.693,79.304,53.716,79.245,53.68,79.028,53.661,78.886,53.682,78.603,53.616,78.627,53.529,78.377,53.488,78.26,53.495,78.204,53.612,77.925,53.565,77.93,53.44,77.785,53.327]},{"z":4.7,"b":[75.097,54.113,76.309,57.25],"l":[76.109,57.25,76.196,56.969,76.249,56.913,76.284,56.726,76.009,56.548,76.108,56.328,76.309,56.224,76.305,56.183,76.006,56.179,75.939,56.129,75.776,56.144,75.696,56.068,75.566,56.054,75.318,55.896,75.342,55.764,75.134,55.699,75.227,55.652,75.178,55.569,75.311,55.5,75.191,55.462,75.097,55.382,75.268,55.25,75.255,55.137,75.394,55.037,75.363,54.908,75.53,54.891,75.64,54.812,75.691,54.713,75.766,54.706,75.83,54.602,75.697,54.544,75.683,54.482,75.691,54.113]},{"z":4.7,"b":[76.109,55.692,84.41,57.25],"l":[76.109,57.25,76.802,57.21,76.919,57.188,78.444,57.161,78.448,57.122,79.59,56.929,80.249,56.455,80.31,56.439,81.122,56.533,81.215,56.508,81.426,56.373,81.557,56.258,81.944,56.339,82.146,56.335,82.342,56.356,82.427,56.394,82.828,56.441,82.856,56.533,82.996,56.508,83.096,56.549,83.252,56.456,83.154,56.382,83.137,56.276,83.214,56.212,83.128,56.132,83.353,55.969,83.407,55.893,83.293,55.851,83.223,55.737,83.316,55.692,83.403,55.735,83.568,55.693,83.783,55.882,83.91,55.913,83.951,56.005,84.197,56.055,84.329,55.988,84.41,56.044]},{"z":4.7,"b":[62.072,56.134,64.995,56.841],"l":[62.072,56.134,62.083,56.291,62.253,56.363,62.333,56.469,62.542,56.578,62.59,56.524,63.001,56.598,63.098,56.569,63.359,56.563,63.448,56.61,63.766,56.508,64.173,56.468,64.237,56.519,64.219,56.601,64.357,56.653,64.337,56.753,64.484,56.76,64.542,56.808,64.826,56.841,64.995,56.768]},{"z":4.7,"b":[75.08,57.25,76.109,58.581],"l":[75.105,58.581,75.303,58.487,75.304,58.451,75.08,58.354,75.222,58.222,75.093,58.11,75.543,57.941,75.581,57.675,75.727,57.599,75.859,57.399,75.992,57.385,76.109,57.25]},{"z":4.6,"b":[113.928,24.5,115.888,25.448],"l":[113.928,25.448,114.012,25.435,114.026,25.268,114.115,25.308,114.279,25.292,114.384,25.335,114.429,25.386,114.569,25.407,114.58,25.36,114.707,25.29,114.683,25.163,114.729,25.119,114.548,25.054,114.414,24.98,114.384,24.885,114.302,24.759,114.192,24.695,114.289,24.596,114.425,24.5,114.498,24.556,114.642,24.585,114.712,24.555,114.74,24.625,114.863,24.588,114.945,24.67,115.047,24.707,115.139,24.684,115.24,24.746,115.421,24.785,115.541,24.685,115.599,24.609,115.706,24.546,115.799,24.573,115.749,24.748,115.787,24.861,115.888,24.917]},{"z":4.6,"b":[115.888,23.616,117.175,24.917],"l":[115.888,24.917,116.036,24.894,116.115,24.846,116.372,24.841,116.53,24.615,116.604,24.656,116.745,24.671,116.749,24.551,116.882,24.394,116.968,24.185,116.925,24.078,117.037,23.738,117.175,23.616]},{"z":4.6,"b":[74.323,23.036,78.2,26.824],"l":[78.2,26.824,78.101,26.795,78.079,26.683,78.013,26.696,77.903,26.664,77.831,26.576,77.715,26.506,77.617,26.483,77.431,26.374,77.323,26.352,77.21,26.242,77.125,26.238,77.082,26.185,76.908,26.099,76.727,25.916,76.652,25.923,76.546,25.848,76.478,25.72,76.551,25.46,76.652,25.379,76.78,25.332,76.913,25.309,77.081,25.353,77.202,25.356,77.255,25.434,77.359,25.407,77.363,25.262,77.399,25.212,77.341,25.114,77.187,25.128,76.999,25.088,76.861,25.034,76.883,24.897,76.781,24.837,76.848,24.76,76.967,24.737,77.037,24.67,77.035,24.538,76.943,24.5,76.881,24.562,76.806,24.556,76.815,24.36,76.892,24.241,76.87,24.182,76.767,24.16,76.573,24.273,76.543,24.215,76.456,24.265,76.23,24.278,76.133,24.329,76.117,24.134,75.999,24.051,75.998,23.984,75.899,23.918,75.776,23.883,75.7,23.793,75.597,23.818,75.578,23.876,75.49,23.94,75.539,24.041,75.622,23.995,75.73,24.001,75.805,24.119,75.748,24.19,75.795,24.252,75.734,24.424,75.792,24.494,75.875,24.514,75.869,24.599,75.759,24.777,75.649,24.738,75.448,24.703,75.204,24.725,75.172,24.763,75.318,24.955,75.235,25.04,75.133,25.04,75.084,24.889,74.981,24.863,74.876,24.919,74.815,24.917,74.838,24.797,74.974,24.786,74.965,24.694,74.87,24.666,74.807,24.736,74.712,24.524,74.847,24.436,74.766,24.359,74.759,24.257,74.87,24.236,74.893,24.134,74.968,24.033,74.908,23.897,74.907,23.791,74.939,23.687,74.823,23.5,74.648,23.431,74.57,23.368,74.536,23.251,74.658,23.226,74.716,23.179,74.612,23.113,74.476,23.062,74.323,23.036]},{"z":4.6,"b":[111.997,24.736,113.928,25.506],"l":[111.997,24.736,112.066,24.797,112.145,24.794,112.165,24.912,112.121,24.949,112.183,25.153,112.214,25.2,112.458,25.164,112.663,25.113,112.714,25,112.808,24.943,112.989,24.964,112.959,25.056,112.969,25.181,112.889,25.241,112.917,25.325,113.09,25.414,113.217,25.506,113.422,25.384,113.523,25.386,113.582,25.326,113.714,25.361,113.828,25.357,113.928,25.448]},{"z":4.6,"b":[74.466,27.679,77.281,29.947],"l":[74.517,29.947,74.547,29.861,74.466,29.799,74.506,29.737,74.603,29.726,74.575,29.59,74.602,29.47,74.537,29.441,74.613,29.333,74.759,29.364,74.821,29.403,74.907,29.378,74.97,29.284,75.107,29.238,75.25,29.256,75.328,29.296,75.392,29.253,75.393,29.106,75.452,29.024,75.515,28.996,75.486,28.927,75.594,28.643,75.699,28.521,75.887,28.404,76.026,28.262,76.032,28.188,75.962,28.139,75.993,28.051,75.932,27.918,76,27.843,76.215,27.842,76.18,27.91,76.163,28.058,76.267,28.077,76.368,28.156,76.453,28.163,76.553,27.971,76.658,28.017,76.647,28.086,76.857,28.229,76.941,28.123,76.921,28.034,76.941,27.852,76.901,27.734,76.973,27.679,77.041,27.807,77.281,27.809]},{"z":4.6,"b":[77.281,26.824,78.223,27.809],"l":[77.281,27.809,77.346,27.542,77.46,27.385,77.606,27.324,77.657,27.203,77.591,27.131,77.513,27.109,77.628,26.973,77.461,26.904,77.511,26.833,77.705,26.93,77.977,26.904,78.107,26.947,78.223,26.937,78.2,26.824]},{"z":4.6,"b":[76.704,30.677,76.827,30.789],"l":[76.815,30.789,76.704,30.781,76.776,30.677,76.827,30.681]},{"z":4.6,"b":[73.898,29.947,74.517,30.222],"l":[74.517,29.947,73.916,29.98,73.898,30.035,73.97,30.137,73.933,30.222]},{"z":4.6,"b":[75.602,30.908,76.768,32.517],"l":[75.847,32.517,75.912,32.431,75.693,32.272,75.623,32.259,75.639,32.147,75.602,32.069,75.707,32.05,75.88,31.972,75.964,31.849,75.923,31.806,76.071,31.564,76.158,31.339,76.275,31.31,76.374,31.419,76.434,31.318,76.561,31.277,76.645,30.999,76.768,30.908]},{"z":4.6,"b":[76.768,30.789,76.833,30.908],"l":[76.768,30.908,76.833,30.842,76.815,30.789]},{"z":4.6,"b":[74.517,29.562,76.905,30.681],"l":[76.827,30.681,76.887,30.634,76.905,30.384,76.753,30.431,76.714,30.337,76.606,30.274,76.625,30.143,76.531,30.084,76.43,30.14,76.267,30.114,76.178,29.955,76.226,29.869,75.957,29.739,75.887,29.749,75.786,29.831,75.718,29.778,75.604,29.765,75.446,29.806,75.312,29.656,75.295,29.575,75.235,29.562,75.172,29.688,75.238,29.749,75.188,29.835,75.105,29.821,75.073,29.891,74.996,29.873,74.903,29.955,74.823,29.985,74.711,29.968,74.651,29.914,74.517,29.947]},{"z":4.7,"b":[43.634,47.477,45.301,48.255],"l":[45.301,48.098,45.137,48.112,44.963,48.209,44.933,48.135,44.766,48.059,44.662,48.089,44.547,48.063,44.54,48.197,44.306,48.255,44.314,48.094,44.448,48.05,44.467,47.99,44.389,47.868,44.083,47.909,44.032,47.816,43.951,47.756,43.822,47.75,43.705,47.683,43.634,47.57,43.732,47.477]},{"z":4.6,"b":[28.256,-26.894,28.976,-25.115],"l":[28.256,-26.894,28.304,-26.829,28.408,-26.759,28.402,-26.659,28.506,-26.613,28.529,-26.565,28.644,-26.508,28.767,-26.516,28.796,-26.427,28.864,-26.401,28.807,-26.308,28.718,-26.338,28.532,-26.221,28.501,-26.08,28.581,-26.059,28.634,-25.973,28.778,-26.012,28.87,-26.005,28.881,-25.896,28.941,-25.87,28.976,-25.674,28.92,-25.552,28.804,-25.542,28.859,-25.632,28.764,-25.706,28.67,-25.664,28.628,-25.527,28.629,-25.333,28.783,-25.228,28.752,-25.115,28.606,-25.158]},{"z":4.7,"b":[45.301,45.546,47.528,48.098],"l":[47.528,45.546,47.47,45.635,47.354,45.684,47.296,45.646,46.922,45.578,46.906,45.637,47.04,45.966,47.17,46.078,47.089,46.118,46.846,46.076,46.856,46.244,46.504,46.274,46.531,46.338,46.664,46.429,46.94,46.432,46.93,46.556,47.022,46.635,47.15,46.668,47.213,46.742,47.126,46.851,46.988,46.863,46.912,46.98,46.801,47.004,46.726,47.089,46.511,47.406,46.526,47.435,46.788,47.557,46.685,47.628,46.449,47.478,46.297,47.437,46.116,47.502,45.843,47.719,45.707,47.908,45.741,48,45.677,48.032,45.547,47.997,45.405,48.092,45.301,48.098]},{"z":4.7,"b":[44.987,48.098,46.644,48.869],"l":[45.301,48.098,45.15,48.238,44.987,48.268,45.042,48.395,45.172,48.439,45.375,48.464,45.301,48.558,45.609,48.593,45.76,48.666,45.948,48.836,46.057,48.869,46.298,48.84,46.644,48.659]},{"z":4.7,"b":[46.831,52.57,48.437,52.81],"l":[46.831,52.616,46.917,52.637,47.062,52.57,47.435,52.57,47.531,52.611,47.731,52.587,47.777,52.68,47.917,52.675,47.998,52.754,48.252,52.81,48.437,52.78]},{"z":4.7,"b":[48.351,51.729,50.794,52.78],"l":[48.437,52.78,48.351,52.669,48.524,52.682,48.7,52.639,48.73,52.568,48.936,52.475,49.159,52.486,49.344,52.469,49.478,52.331,49.628,52.366,49.812,52.193,49.977,52.184,50.221,52.13,50.236,52.035,50.517,51.991,50.732,51.914,50.716,51.841,50.794,51.729]},{"z":4.6,"b":[93.018,24.399,93.449,25.302],"l":[93.449,25.302,93.394,25.235,93.189,24.811,93.124,24.81,93.087,24.707,93.046,24.445,93.018,24.399]},{"z":4.6,"b":[92.298,24.133,93.018,24.471],"l":[93.018,24.399,92.829,24.377,92.746,24.471,92.701,24.362,92.631,24.295,92.624,24.239,92.489,24.133,92.416,24.239,92.298,24.238]},{"z":4.7,"b":[29.418,59.61,30.756,60.266],"l":[29.697,59.95,29.68,59.869,29.85,59.828,30.113,59.663,30.259,59.61,30.502,59.631,30.556,59.686,30.748,59.726,30.756,59.777,30.512,59.862,30.527,59.97,30.374,60.036,30.39,60.123,30.179,60.148,29.633,60.266,29.418,60.169]},{"z":4.6,"b":[93.336,25.302,95.176,27.033],"l":[95.176,27.033,95.031,26.933,94.917,26.951,94.799,26.814,94.69,26.741,94.487,26.677,94.358,26.498,94.207,26.484,94.177,26.372,94.008,26.184,93.973,26.04,93.989,25.946,93.904,25.857,93.752,25.959,93.709,25.87,93.581,25.763,93.524,25.689,93.336,25.538,93.459,25.381,93.449,25.302]},{"z":4.7,"b":[34.73,59.103,35.686,61.188],"l":[35.604,61.188,35.686,61.121,35.489,60.959,35.44,60.862,35.281,60.85,35.154,60.787,35.259,60.585,35.231,60.341,35.147,60.269,35.157,60.067,35.238,59.985,35.404,59.974,35.382,59.875,35.424,59.701,35.497,59.647,35.496,59.554,35.324,59.507,35.449,59.428,35.301,59.301,35.135,59.254,34.901,59.238,34.73,59.103]},{"z":4.7,"b":[29.975,58.445,34.73,59.422],"l":[34.73,59.103,34.568,59.108,34.247,59.198,34.1,59.145,33.855,59.198,33.716,59.302,33.52,59.373,33.368,59.38,33.246,59.326,32.955,59.422,32.732,59.267,32.706,59.142,32.584,59.181,32.291,59.207,32.124,59.394,31.882,59.375,31.827,59.314,31.563,59.368,31.452,59.092,31.258,58.981,31.16,59.073,30.981,59.06,30.962,58.975,30.728,58.829,30.687,58.746,30.413,58.711,30.28,58.754,30.101,58.73,30.088,58.64,30.167,58.533,29.975,58.445]},{"z":4.7,"b":[35.604,61.188,37.773,61.594],"l":[37.773,61.522,37.278,61.501,36.996,61.594,36.873,61.545,36.578,61.529,36.467,61.407,35.604,61.188]},{"z":4.7,"b":[29.277,60.646,35.604,61.32],"l":[35.604,61.188,35.392,61.162,35.258,61.254,34.822,61.278,34.741,61.32,34.544,61.27,34.457,61.168,34.216,61.242,33.535,61.221,33.452,61.134,33.585,61.071,33.696,61.122,33.867,61.071,33.752,60.944,33.406,60.989,33.423,60.936,33.253,60.899,33.078,60.738,32.91,60.724,32.703,60.646,29.87,61.176,29.576,61.171,29.491,61.239,29.277,61.305]},{"z":4.7,"b":[29.362,66.356,33.05,66.684],"l":[33.05,66.576,32.594,66.582,32.531,66.489,32.363,66.467,32.329,66.359,31.902,66.356,31.785,66.503,31.652,66.537,31.555,66.655,31.073,66.684,29.362,66.627]},{"z":4.7,"b":[33.046,66.574,33.184,66.576],"l":[33.184,66.574,33.046,66.576]},{"z":4.7,"b":[35.882,61.522,37.927,63.971],"l":[36.523,63.971,36.555,63.764,36.375,63.645,35.945,63.62,35.882,63.573,36.058,63.46,36.22,63.416,36.262,63.236,36.32,63.161,36.259,63.032,36.501,62.826,36.76,62.718,37.081,62.656,37.352,62.684,37.505,62.645,37.588,62.473,37.573,62.361,37.43,62.237,37.719,62.203,37.808,61.998,37.927,61.968,37.857,61.698,37.762,61.648,37.773,61.522]},{"z":4.6,"b":[103.596,24.742,105.297,27.722],"l":[105.297,27.722,105.197,27.399,105.083,27.413,104.871,27.31,104.783,27.332,104.594,27.314,104.444,27.347,104.344,27.441,104.174,27.272,103.981,27.4,103.911,27.39,103.859,27.315,103.695,27.152,103.596,27.076,103.701,27.052,103.778,26.956,103.778,26.88,103.717,26.799,103.775,26.732,103.758,26.629,103.829,26.547,104.02,26.514,104.153,26.651,104.218,26.627,104.359,26.648,104.43,26.713,104.466,26.613,104.559,26.585,104.627,26.498,104.676,26.379,104.545,26.27,104.505,26.147,104.499,26.031,104.393,25.948,104.421,25.857,104.306,25.665,104.422,25.579,104.433,25.507,104.554,25.485,104.566,25.381,104.669,25.297,104.72,25.211,104.689,25.101,104.707,25.017,104.546,24.813,104.54,24.742]},{"z":4.6,"b":[104.495,23.152,106.194,24.742],"l":[104.54,24.742,104.495,24.698,104.502,24.578,104.599,24.403,104.667,24.34,104.729,24.364,104.753,24.468,104.961,24.416,105.061,24.431,105.176,24.31,105.17,24.167,105.253,24.086,105.32,24.118,105.447,24.038,105.532,24.126,105.608,24.14,105.641,24.082,105.788,24.021,105.872,24.025,105.971,24.088,106.157,23.974,106.194,23.869,106.134,23.797,106.152,23.75,106.128,23.541,105.983,23.469,105.848,23.519,105.702,23.359,105.629,23.347,105.619,23.285,105.512,23.152]},{"z":4.7,"b":[45.706,44.752,46.888,45.001],"l":[45.706,44.976,45.854,45.001,46.434,44.937,46.603,44.885,46.888,44.752]},{"z":4.6,"b":[99.099,26.035,105.297,29.224],"l":[99.109,29.224,99.121,28.965,99.099,28.856,99.118,28.709,99.169,28.602,99.163,28.438,99.275,28.277,99.382,28.185,99.416,28.255,99.389,28.334,99.393,28.543,99.503,28.579,99.534,28.681,99.629,28.815,99.705,28.845,99.732,28.745,99.828,28.673,99.846,28.604,99.934,28.569,100.001,28.456,100.143,28.342,100.162,28.236,100.047,28.187,100.059,28.066,100.202,27.899,100.296,27.853,100.349,27.747,100.41,27.829,100.533,27.816,100.625,27.897,100.661,27.875,100.872,27.562,100.867,27.495,100.941,27.435,100.992,27.331,101.004,27.198,101.173,27.191,101.141,27.045,101.342,26.871,101.371,26.775,101.503,26.766,101.495,26.706,101.412,26.57,101.544,26.495,101.644,26.349,101.58,26.307,101.595,26.209,101.677,26.228,101.764,26.176,101.776,26.107,101.85,26.035,101.922,26.099,102.113,26.084,102.252,26.229,102.358,26.263,102.401,26.312,102.568,26.379,102.626,26.345,102.61,26.26,102.72,26.238,102.878,26.365,102.951,26.345,103.003,26.4,102.996,26.485,103.058,26.557,102.975,26.831,102.901,26.925,102.878,27.032,102.914,27.112,102.889,27.291,102.977,27.389,103.121,27.412,103.218,27.567,103.415,27.743,103.509,27.865,103.514,27.971,103.435,28.068,103.459,28.135,103.572,28.243,103.666,28.263,103.709,28.216,103.88,28.326,103.795,28.531,103.837,28.605,104.057,28.63,104.414,28.64,104.44,28.605,104.25,28.531,104.268,28.334,104.377,28.282,104.445,28.1,104.334,28.047,104.375,27.962,104.482,27.891,104.605,27.846,104.702,27.883,104.897,27.901,104.958,27.957,104.966,28.048,105.056,28.088,105.172,28.062,105.203,28.002,105.282,28.001,105.228,27.856,105.297,27.722]},{"z":4.7,"b":[45.509,42.474,46.586,44.015],"l":[45.509,44.015,45.71,43.955,45.803,43.978,46.045,43.934,46.016,43.876,46.22,43.863,46.257,43.948,46.349,43.998,46.462,43.962,46.452,43.887,46.586,43.872,46.585,43.772,46.483,43.672,46.439,43.541,46.338,43.458,46.36,43.361,46.423,43.315,46.403,43.114,46.534,42.961,46.425,42.877,46.336,42.904,46.123,42.843,46.17,42.73,46.095,42.715,45.948,42.575,45.821,42.487,45.727,42.474]},{"z":4.7,"b":[45.124,44.015,45.706,44.976],"l":[45.509,44.015,45.506,44.19,45.124,44.202,45.131,44.268,45.404,44.318,45.444,44.402,45.398,44.444,45.237,44.46,45.271,44.591,45.435,44.617,45.614,44.766,45.621,44.899,45.706,44.976]},{"z":4.7,"b":[33.501,53.311,35.104,54.026],"l":[33.501,54.016,33.682,54.026,33.78,53.982,33.86,53.879,34.031,53.862,34.199,53.892,34.385,53.719,34.49,53.666,34.488,53.544,34.582,53.428,34.673,53.39,35.017,53.381,35.104,53.311]},{"z":4.7,"b":[34.805,52.353,35.314,53.311],"l":[35.104,53.311,35.122,53.186,35.182,53.102,35.314,53.066,35.157,52.969,34.967,52.97,34.805,52.868,34.869,52.784,34.948,52.783,35.092,52.698,35.011,52.614,34.99,52.511,34.833,52.424,34.897,52.353]},{"z":4.7,"b":[33.501,54.016,35.37,55.239],"l":[35.37,55.239,35.284,55.112,35.144,55.022,35.002,54.887,34.866,54.884,34.921,54.772,34.817,54.709,34.5,54.632,34.416,54.451,34.326,54.43,34.108,54.55,33.927,54.572,33.729,54.562,33.643,54.485,33.617,54.381,33.684,54.321,33.562,54.156,33.501,54.016]},{"z":4.7,"b":[32.706,53.42,33.501,54.016],"l":[33.501,54.016,33.475,53.972,33.287,53.954,33.289,53.822,32.899,53.569,32.908,53.493,32.706,53.42]},{"z":4.7,"b":[31.467,55.632,35.174,56.075],"l":[31.467,55.729,31.586,55.747,31.755,55.714,31.894,55.77,32.094,55.674,32.722,55.69,32.939,55.632,33.188,55.653,33.225,55.698,33.372,55.703,33.505,55.755,33.67,55.865,33.66,55.946,33.838,55.949,33.917,56.073,34.201,56.075,34.631,55.918,35.031,55.984,35.174,55.948]},{"z":4.7,"b":[35.174,55.239,35.397,55.948],"l":[35.174,55.948,35.354,55.779,35.316,55.727,35.397,55.617,35.303,55.571,35.301,55.299,35.37,55.239]},{"z":4.6,"b":[77.585,28.737,80.073,30.395],"l":[80.073,28.821,80.001,28.737,79.844,28.805,79.841,28.855,79.743,28.885,79.699,28.85,79.526,28.878,79.444,28.85,79.371,28.97,79.296,28.972,79.171,29.051,79.056,29.152,78.95,29.127,78.868,29.215,78.722,29.284,78.907,29.388,78.643,29.538,78.479,29.722,78.381,29.762,78.228,29.947,78.155,29.925,78.189,29.707,78.107,29.655,77.983,29.719,77.831,29.66,77.784,29.742,77.747,29.878,77.746,29.977,77.835,30.131,77.929,30.244,77.751,30.326,77.662,30.395,77.585,30.383]},{"z":4.6,"b":[77.585,30.383,78.946,31.29],"l":[77.585,30.383,77.813,30.521,77.706,30.791,77.789,30.931,77.806,31.051,77.941,31.173,78.04,31.172,78.284,31.29,78.369,31.29,78.501,31.22,78.582,31.231,78.793,31.207,78.87,31.122,78.946,31.138]},{"z":4.6,"b":[87.768,25.214,88.297,26.539],"l":[87.784,25.214,87.838,25.278,87.768,25.436,87.956,25.534,88.043,25.547,88.046,25.669,87.911,25.765,87.805,25.874,87.831,26.011,87.979,26.146,88.08,26.193,88.18,26.286,88.297,26.336,88.282,26.428,88.195,26.501,88.087,26.539]},{"z":4.6,"b":[110.899,-2.909,113.776,0.559],"l":[113.776,0.559,113.647,0.41,113.623,0.311,113.532,0.261,113.416,0.275,113.325,0.245,113.453,0.066,113.479,-0.048,113.429,-0.19,113.326,-0.252,113.249,-0.361,113.301,-0.423,113.256,-0.551,113.161,-0.47,113.084,-0.518,113.004,-0.519,112.755,-0.636,112.578,-0.704,112.497,-0.702,112.38,-0.755,112.119,-0.735,112.059,-0.796,111.969,-0.823,111.651,-1.015,111.587,-1.102,111.577,-1.187,111.416,-1.269,111.327,-1.373,111.242,-1.421,111.134,-1.53,111.012,-1.526,110.92,-1.613,111.017,-1.66,111.025,-1.867,110.995,-1.985,111.012,-2.127,111.138,-2.405,111.138,-2.733,111.067,-2.765,110.899,-2.909]},{"z":4.6,"b":[86.687,21.614,87.477,22.218],"l":[87.477,21.614,87.409,21.786,87.275,21.814,87.232,21.848,87.191,21.956,87.113,21.971,87.059,21.907,86.994,21.972,86.987,22.067,86.834,22.122,86.687,22.218]},{"z":4.6,"b":[85.829,22.218,87.895,25.214],"l":[86.687,22.218,86.78,22.287,86.79,22.416,86.718,22.469,86.749,22.545,86.611,22.604,86.587,22.681,86.399,22.785,86.415,22.943,86.45,23.004,86.142,23.028,86.02,23.153,85.888,23.163,85.83,23.261,85.895,23.416,85.829,23.483,85.996,23.512,86.056,23.603,86.135,23.559,86.121,23.501,86.214,23.449,86.336,23.508,86.348,23.568,86.459,23.639,86.646,23.697,86.8,23.726,86.81,23.818,86.877,23.913,87.08,23.815,87.166,23.81,87.241,23.851,87.302,24.057,87.338,24.002,87.489,24.047,87.487,24.124,87.572,24.118,87.662,24.186,87.631,24.269,87.721,24.318,87.8,24.501,87.791,24.562,87.877,24.589,87.895,24.67,87.853,24.758,87.877,24.939,87.844,25.042,87.781,25.09,87.784,25.214]},{"z":4.6,"b":[89.658,26.187,89.888,26.722],"l":[89.658,26.187,89.727,26.206,89.782,26.309,89.829,26.328,89.888,26.533,89.859,26.722]},{"z":4.6,"b":[88.001,27.085,88.756,27.248],"l":[88.756,27.149,88.664,27.173,88.518,27.176,88.448,27.114,88.357,27.085,88.233,27.137,88.118,27.138,88.001,27.248]},{"z":4.6,"b":[92.139,24.238,92.298,24.545],"l":[92.298,24.238,92.22,24.264,92.258,24.396,92.222,24.5,92.139,24.545]},{"z":4.6,"b":[89.809,25.005,92.806,26.089],"l":[92.385,25.005,92.565,25.152,92.806,25.228,92.79,25.349,92.588,25.499,92.593,25.587,92.382,25.764,92.167,25.69,92.165,25.809,92.215,25.919,92.184,25.993,92.261,26.055,92.164,26.088,91.922,26.038,91.825,26.089,91.752,26.048,91.718,25.968,91.582,26.027,91.475,25.896,91.361,25.843,91.251,25.738,91.208,25.736,91.179,25.87,91.097,25.84,90.975,25.944,90.753,25.962,90.644,25.931,90.618,25.97,90.485,26.012,90.136,25.96,89.974,25.802,89.942,25.691,90.002,25.587,89.897,25.541,89.875,25.46,89.809,25.441]},{"z":4.7,"b":[98.634,52.105,99.25,52.954],"l":[98.94,52.105,98.932,52.192,98.809,52.196,98.799,52.274,98.646,52.309,98.634,52.413,98.806,52.514,98.805,52.611,98.937,52.673,98.938,52.798,98.856,52.847,98.929,52.918,99.22,52.865,99.25,52.954]},{"z":4.7,"b":[99.25,51.132,116.17,57.24],"l":[99.25,52.954,99.366,53.054,99.379,53.113,99.536,53.105,99.69,53.132,99.805,53.19,99.878,53.299,100.08,53.3,100.26,53.375,100.401,53.234,100.363,53.137,100.606,52.944,100.827,52.856,100.852,52.811,101.031,52.769,101.32,52.641,101.436,52.631,101.476,52.571,101.688,52.461,101.743,52.402,101.91,52.317,101.963,52.222,102.046,52.212,102.188,52.276,102.32,52.231,102.395,52.259,102.542,52.228,102.574,52.149,102.673,52.098,102.777,51.985,102.945,51.907,103.065,51.883,103.222,51.788,103.181,51.621,103.335,51.434,103.576,51.479,103.69,51.46,103.819,51.388,103.814,51.132,104.098,51.218,104.212,51.281,104.494,51.309,104.609,51.268,104.67,51.399,104.629,51.443,104.65,51.622,104.899,51.669,105.123,51.731,105.461,51.863,105.752,52.031,105.992,52.221,106.198,52.44,108.041,53.193,108.061,53.205,108.423,53.783,109.007,54.425,109.022,54.494,108.671,54.547,108.64,54.601,108.716,54.785,108.69,55.062,108.782,55.17,108.773,55.237,108.651,55.313,108.622,55.456,108.771,55.623,108.854,55.866,108.96,55.941,109.18,55.913,109.105,56.022,108.991,56.04,108.929,56.148,108.799,56.257,108.571,56.294,108.557,56.346,108.719,56.499,108.699,56.583,108.93,56.588,109.143,56.676,109.314,56.672,109.41,56.735,109.665,56.694,109.78,56.711,109.887,56.891,109.989,56.969,110.248,56.925,110.399,56.921,110.532,56.847,110.662,56.833,110.753,56.874,110.883,56.834,110.998,56.858,111.134,56.815,111.363,56.895,111.337,56.95,111.462,57.102,111.62,57.095,111.628,57.016,111.854,56.913,111.916,56.945,112.11,56.944,112.235,56.878,112.444,56.863,112.662,56.96,112.762,56.753,112.886,56.697,113.356,56.626,113.547,56.672,113.803,56.602,113.87,56.533,114.01,56.545,114.127,56.676,114.289,56.692,114.498,56.671,114.795,56.71,114.851,56.837,115.05,56.991,115.086,57.119,115.45,57.097,115.698,57.137,115.852,57.24,116.156,57.229,116.17,57.141,116.048,57.082,115.805,57.031,115.711,56.964]},{"z":4.7,"b":[41.656,45.996,44.334,47.575],"l":[43.732,47.477,43.799,47.371,43.802,47.283,43.939,47.269,43.953,47.365,44.11,47.414,44.117,47.533,44.228,47.575,44.334,47.475,44.328,47.111,44.262,46.9,43.93,46.723,43.817,46.589,43.928,46.521,43.918,46.416,43.786,46.407,43.646,46.216,43.559,46.169,43.356,46.172,43.08,46.36,42.912,46.435,42.787,46.389,42.597,46.411,42.409,46.525,42.181,46.578,42.034,46.543,42.1,46.407,42.202,46.314,41.995,46.301,41.915,46.185,41.84,46.153,41.726,46.214,41.656,46.105,41.713,45.996]},{"z":4.7,"b":[41.162,45.947,41.713,45.996],"l":[41.713,45.996,41.658,45.976,41.42,45.995,41.375,45.947,41.162,45.954]},{"z":4.7,"b":[40.081,49.576,41.362,50.226],"l":[41.362,50.226,41.288,50.179,41.341,50.075,41.179,50.016,41.056,49.916,41.029,49.794,40.885,49.69,40.612,49.648,40.411,49.597,40.17,49.624,40.081,49.576]},{"z":4.7,"b":[41.362,47.45,43.732,50.226],"l":[41.362,50.226,41.492,50.084,41.595,50.045,41.617,49.962,41.876,49.9,42.115,49.75,42.178,49.668,42.072,49.502,42.053,49.402,42.092,49.289,42.037,49.267,42.095,49.141,42.344,49.152,42.411,49.047,42.644,48.977,42.74,48.786,42.651,48.705,42.701,48.624,42.634,48.552,42.442,48.505,42.277,48.498,42.165,48.464,42.074,48.392,42.147,48.287,42.051,48.23,42.096,48.154,42.03,48.029,42.283,48.016,42.343,48.045,42.492,48.014,42.728,47.854,42.785,47.731,42.955,47.51,43.054,47.573,43.263,47.45,43.482,47.484,43.732,47.477]},{"z":4.7,"b":[42.489,51.231,42.873,51.608],"l":[42.489,51.608,42.734,51.479,42.861,51.381,42.873,51.231]},{"z":4.7,"b":[41.168,50.226,42.873,51.239],"l":[42.873,51.231,42.715,51.239,42.573,51.169,42.255,51.132,42.067,51.16,41.977,51.203,41.855,51.189,41.737,51.026,41.444,50.917,41.37,50.831,41.168,50.781,41.314,50.725,41.4,50.625,41.526,50.599,41.424,50.482,41.509,50.392,41.362,50.226]},{"z":4.7,"b":[38.299,51.92,40.504,52.098],"l":[38.299,51.961,38.529,52.024,38.698,52.098,38.762,52.052,38.88,52.093,38.994,52.001,39.182,52.098,39.447,52.082,39.497,51.973,39.834,51.92,39.913,51.98,40.088,51.985,40.295,51.956,40.504,51.954]},{"z":4.7,"b":[40.504,51.6,42.489,51.954],"l":[40.504,51.954,40.735,51.94,40.938,51.89,40.892,51.743,41.066,51.71,41.268,51.735,41.311,51.794,41.556,51.767,41.952,51.656,42.248,51.694,42.274,51.6,42.403,51.633,42.489,51.608]},{"z":4.7,"b":[35.313,50.906,38.292,51.42],"l":[38.292,51.292,38.027,51.366,38.046,51.42,37.829,51.42,37.667,51.366,37.539,51.394,37.379,51.385,37.249,51.331,37.144,51.343,37.054,51.247,36.93,51.181,36.856,51.186,36.618,51.08,36.314,51.103,35.976,51.158,36,51.058,35.877,51.012,35.817,50.945,35.546,50.906,35.512,50.947,35.313,50.959]},{"z":4.7,"b":[38.292,49.856,39.234,51.292],"l":[38.292,51.292,38.446,51.257,38.501,51.189,38.396,51.157,38.397,51.077,38.516,51.062,38.618,50.998,38.738,51.074,38.846,51.036,38.874,50.911,38.737,50.901,38.724,50.79,38.966,50.647,39.072,50.636,39.098,50.506,38.99,50.433,39.085,50.349,39.099,50.173,39.234,50.056,39.175,49.856]},{"z":4.7,"b":[37.747,51.879,38.299,52.017],"l":[37.747,52.017,37.821,51.912,37.997,51.879,38.101,51.927,38.192,51.912,38.299,51.961]},{"z":4.7,"b":[38.207,51.292,38.41,51.961],"l":[38.299,51.961,38.41,51.92,38.41,51.688,38.22,51.708,38.207,51.629,38.276,51.526,38.323,51.341,38.292,51.292]},{"z":4.7,"b":[34.372,51.799,34.917,52.353],"l":[34.897,52.353,34.917,52.322,34.805,52.185,34.715,52.216,34.672,52.065,34.531,52.07,34.407,52.013,34.372,51.799]},{"z":4.7,"b":[34.897,51.936,37.747,52.414],"l":[34.897,52.353,35.124,52.335,35.241,52.39,35.396,52.414,35.572,52.387,35.637,52.332,35.893,52.274,36.001,52.313,36.146,52.319,36.357,52.366,36.552,52.215,36.729,52.176,36.82,52.12,37.158,52.072,37.354,51.936,37.481,52.009,37.652,51.971,37.747,52.017]},{"z":4.7,"b":[130.41,49.244,134.956,55.704],"l":[130.923,55.679,131.087,55.618,131.301,55.618,131.599,55.652,131.804,55.614,131.962,55.658,132.126,55.645,132.24,55.704,132.538,55.676,132.663,55.561,132.644,55.491,132.908,55.359,132.733,55.349,132.581,55.196,132.355,55.191,132.367,55.056,132.087,55.05,131.971,54.998,131.994,54.909,131.612,54.781,131.532,54.722,131.399,54.719,131.199,54.613,131.22,54.527,131.114,54.323,130.944,54.323,130.774,54.288,130.507,54.12,130.484,53.993,130.41,53.948,130.447,53.882,130.635,53.887,130.84,53.828,130.921,53.768,131.097,53.813,131.421,53.751,131.444,53.627,131.498,53.555,131.483,53.476,131.532,53.319,131.465,53.213,131.8,53.241,131.894,53.128,131.991,53.134,132.112,53.223,132.398,53.244,132.594,53.23,132.893,53.235,132.951,53.289,133.153,53.291,133.21,53.421,133.573,53.546,133.709,53.477,134.011,53.433,134.215,53.472,134.223,53.515,134.451,53.538,134.555,53.621,134.748,53.539,134.761,53.475,134.924,53.417,134.897,53.315,134.956,53.254,134.859,53.184,134.812,53.066,134.659,52.939,134.631,52.801,134.706,52.766,134.782,52.647,134.659,52.575,134.663,52.471,134.606,52.418,134.224,52.489,134.056,52.5,134.003,52.539,133.659,52.572,133.369,52.683,133.28,52.664,133.238,52.567,133.246,52.456,133.42,52.251,133.322,52.198,132.907,52.159,132.59,52.082,132.525,51.963,132.404,51.963,132.423,51.86,132.279,51.793,132.216,51.817,131.959,51.737,131.864,51.755,131.728,51.681,131.431,51.688,131.395,51.607,131.484,51.476,131.488,51.345,131.29,51.364,131.169,51.249,131.065,51.246,130.919,51.024,130.816,51.02,130.772,50.942,130.841,50.881,130.688,50.746,130.645,50.657,130.954,50.65,131.011,50.568,130.866,50.457,130.989,50.382,131.134,50.419,131.158,50.345,131.29,50.366,131.325,50.269,131.442,50.186,131.295,49.99,131.445,49.964,131.499,49.901,131.469,49.73,131.384,49.659,131.5,49.611,131.494,49.411,131.396,49.244]},{"z":4.7,"b":[130.554,48.86,131.396,49.25],"l":[131.396,49.244,131.182,49.25,131.007,49.051,130.983,48.972,130.829,48.983,130.554,48.86]},{"z":4.7,"b":[119.898,55.466,130.923,57.032],"l":[119.898,56.889,120.031,56.899,120.303,57.004,120.656,57.032,121.019,57.031,121.053,56.99,121.407,56.976,121.511,56.922,121.602,56.813,121.608,56.741,122.16,56.738,122.283,56.651,122.467,56.602,122.589,56.49,122.781,56.498,122.919,56.551,123.132,56.457,123.453,56.406,123.915,56.377,124.06,56.231,124.206,56.165,124.243,56.081,124.36,56.057,124.448,55.905,124.635,55.902,124.958,55.826,125.248,55.88,125.437,55.837,125.546,55.736,125.758,55.796,126.039,55.728,126.047,55.589,126.234,55.591,126.434,55.555,126.551,55.633,126.843,55.606,126.954,55.687,127.188,55.691,127.33,55.656,127.487,55.555,127.573,55.58,127.675,55.683,127.794,55.629,128.29,55.648,128.502,55.516,128.663,55.521,128.772,55.469,128.975,55.466,129.089,55.533,129.047,55.654,129.444,55.7,129.653,55.745,129.892,55.734,129.995,55.693,130.742,55.719,130.923,55.679]},{"z":4.7,"b":[123.162,43.436,131.253,46.272],"l":[131.253,43.469,131.149,43.436,130.995,43.508,130.917,43.45,130.589,43.628,130.414,43.669,130.333,43.928,130.28,43.964,130.104,43.845,130.033,43.856,130,43.984,129.913,44.022,129.802,43.965,129.752,43.874,129.535,43.87,129.249,43.786,129.216,43.738,129.229,43.608,129.015,43.54,128.883,43.556,128.764,43.709,128.747,43.802,128.632,43.912,128.529,44.08,128.446,44.154,128.451,44.316,128.419,44.45,128.359,44.502,128.207,44.44,128.155,44.353,128.043,44.341,128.052,44.126,127.85,44.056,127.744,44.086,127.715,44.161,127.61,44.22,127.618,44.272,127.493,44.411,127.559,44.574,127.375,44.649,127.233,44.616,127.147,44.655,127.045,44.598,127.032,44.731,126.978,44.826,127.077,44.934,126.953,45.134,126.789,45.149,126.559,45.247,126.421,45.228,126.15,45.141,125.841,45.235,125.705,45.36,125.699,45.511,125.521,45.471,125.432,45.477,125.307,45.417,125.104,45.395,125.028,45.498,124.872,45.449,124.612,45.448,124.563,45.415,124.396,45.451,124.332,45.547,124.224,45.636,124.141,45.632,124.018,45.782,124.062,45.872,123.979,45.978,124.034,46.018,123.919,46.272,123.356,46.235,123.258,46.265,123.162,46.22]},{"z":4.7,"b":[119.67,53.43,122.127,56.889],"l":[119.898,56.889,119.67,56.771,119.787,56.7,119.809,56.612,119.96,56.493,120.257,56.459,120.276,56.386,120.533,56.285,120.527,56.232,120.344,56.22,120.062,56.138,120.055,56.078,120.26,55.916,120.466,55.872,120.532,55.914,120.986,56.02,121.217,56.013,121.276,55.867,121.347,55.813,121.307,55.742,121.363,55.507,121.439,55.481,121.634,55.535,121.693,55.6,121.883,55.606,121.984,55.476,121.939,55.214,121.95,55.007,121.667,54.821,121.659,54.728,121.826,54.784,121.922,54.733,121.895,54.646,121.936,54.566,122.127,54.468,122.115,54.407,121.902,54.369,121.866,54.419,121.731,54.426,121.655,54.355,121.753,54.272,121.754,54.152,121.641,54.113,121.643,54.012,121.783,53.973,121.919,53.831,121.947,53.605,122.018,53.521,122.025,53.43]},{"z":4.7,"b":[115.711,56.726,119.108,58.398],"l":[115.711,56.964,115.86,56.932,115.957,56.865,116.122,56.813,116.362,56.847,116.629,56.749,116.815,56.726,117.012,56.806,117.141,56.809,117.18,56.866,117.571,56.851,117.576,56.935,117.673,56.978,117.677,57.066,117.601,57.15,117.723,57.187,117.794,57.287,117.597,57.321,117.427,57.264,117.357,57.337,117.321,57.457,117.257,57.509,117.282,57.617,117.14,57.668,117.173,57.736,117.128,57.842,117.313,57.847,117.368,57.934,117.375,58.061,117.438,58.152,117.588,58.184,117.471,58.304,117.684,58.398,118.142,58.386,118.317,58.353,118.405,58.262,118.63,58.175,118.784,58.213,119.108,58.223]},{"z":4.7,"b":[119.047,56.889,119.898,58.223],"l":[119.108,58.223,119.154,58.146,119.135,58.007,119.047,57.728,119.165,57.669,119.141,57.539,119.41,57.615,119.487,57.591,119.41,57.497,119.442,57.371,119.546,57.34,119.683,57.157,119.622,57.101,119.645,57.002,119.772,56.983,119.898,56.889]},{"z":4.7,"b":[90.477,51.781,96.684,53.639],"l":[90.477,52.183,90.582,52.112,90.744,52.063,90.839,51.912,91.101,51.943,91.18,51.89,91.543,51.901,91.616,51.869,91.894,51.853,91.987,51.794,92.43,51.802,92.505,51.781,92.673,51.84,92.844,51.832,92.901,51.895,93.075,51.945,93.36,52.129,93.678,52.224,93.768,52.371,93.774,52.446,93.989,52.585,94.139,52.761,94.141,52.832,94.29,52.9,94.512,52.911,94.604,53.055,94.604,53.176,94.69,53.224,94.594,53.316,94.74,53.383,94.976,53.405,95.041,53.376,95.26,53.456,95.381,53.372,95.622,53.449,95.768,53.52,96.048,53.454,96.186,53.515,96.241,53.613,96.418,53.543,96.642,53.548,96.684,53.639]},{"z":4.7,"b":[96.684,52.954,99.25,53.729],"l":[96.684,53.639,96.743,53.711,96.875,53.729,97.135,53.613,97.328,53.596,97.448,53.461,97.586,53.438,97.699,53.379,97.972,53.352,98.031,53.253,98.28,53.226,98.303,53.104,98.615,53.108,98.698,53.155,99.056,53.104,98.989,53.034,99.25,52.954]},{"z":4.7,"b":[107.745,49.967,116.927,56.964],"l":[115.711,56.964,115.589,56.902,115.571,56.732,115.434,56.69,115.49,56.62,115.688,56.595,115.673,56.54,115.801,56.466,115.684,56.425,115.688,56.327,115.774,56.206,115.765,56.086,115.889,55.965,115.844,55.857,115.845,55.705,115.892,55.641,115.895,55.525,116.072,55.415,116.178,55.391,116.376,55.409,116.458,55.34,116.476,55.253,116.716,55.145,116.774,55.148,116.893,54.977,116.927,54.826,116.884,54.738,116.774,54.69,116.777,54.627,116.673,54.548,116.458,54.517,116.19,54.538,115.934,54.504,115.629,54.442,115.526,54.404,115.491,54.329,115.269,54.257,115.164,54.162,115.023,54.098,114.966,54.023,114.86,53.981,114.588,53.927,114.501,53.845,114.378,53.844,114.326,53.79,113.983,53.744,113.927,53.681,113.793,53.63,113.878,53.562,113.842,53.382,113.936,53.358,113.978,53.24,114.201,53.187,114.132,53.1,114.325,53.068,114.233,52.961,114.235,52.822,114.088,52.695,113.965,52.633,113.744,52.574,113.573,52.453,113.236,52.469,112.95,52.343,112.68,52.331,112.545,52.347,112.405,52.208,112.185,52.288,112.062,52.277,111.796,52.09,111.724,52.006,111.586,51.922,111.454,51.878,111.125,51.818,110.845,51.66,110.862,51.595,110.677,51.504,110.423,51.523,110.389,51.577,110.049,51.616,109.629,51.449,109.247,51.366,109.047,51.5,108.942,51.44,108.757,51.449,108.649,51.501,108.532,51.423,108.385,51.237,108.555,51.157,108.484,51.057,108.353,51.055,108.308,51.011,108.25,50.844,108.067,50.753,108.187,50.658,108.472,50.59,108.554,50.465,108.315,50.403,108.213,50.411,108.132,50.318,107.99,50.335,107.926,50.215,107.77,50.147,107.796,50.026,107.745,49.967]},{"z":4.7,"b":[88.736,51.553,90.477,52.192],"l":[88.736,51.559,89.033,51.6,89.233,51.645,89.223,51.56,89.421,51.605,89.581,51.553,89.73,51.582,89.943,51.75,90.105,51.768,90.028,51.984,90.119,52.044,90.124,52.129,90.359,52.192,90.477,52.183]},{"z":4.6,"b":[80.323,35.246,89.678,36.449],"l":[89.678,36.082,89.224,36.264,88.958,36.287,88.828,36.374,88.712,36.368,88.522,36.414,88.459,36.449,88,36.428,87.925,36.383,87.571,36.334,87.346,36.374,87.194,36.304,87.001,36.274,86.787,36.26,86.714,36.231,86.459,36.219,86.4,36.17,86.212,36.116,86.087,35.876,85.93,35.762,85.717,35.759,85.62,35.675,85.389,35.757,85.26,35.73,85.15,35.75,84.924,35.689,84.778,35.6,84.563,35.559,84.215,35.389,84.133,35.378,83.984,35.416,83.871,35.375,83.565,35.361,83.453,35.394,83.246,35.421,83.099,35.413,82.972,35.49,82.978,35.601,82.936,35.674,82.785,35.685,82.71,35.65,82.428,35.69,82.319,35.634,82.289,35.545,82.086,35.448,82.002,35.319,81.711,35.246,81.545,35.252,81.462,35.31,81.357,35.332,81.21,35.317,81.061,35.389,80.979,35.293,80.858,35.347,80.766,35.343,80.656,35.384,80.486,35.404,80.323,35.507]},{"z":4.6,"b":[89.435,31.548,97.716,36.082],"l":[89.678,36.082,89.515,36.053,89.435,35.988,89.485,35.88,89.581,35.84,89.789,35.827,89.789,35.776,89.715,35.642,89.69,35.509,89.73,35.444,89.484,35.342,89.455,35.213,89.572,35.073,89.584,34.941,89.788,34.926,89.825,34.846,89.724,34.74,89.791,34.554,89.796,34.408,89.84,34.383,89.787,34.199,89.637,34.093,89.742,33.91,89.877,33.823,89.975,33.631,90.141,33.578,90.203,33.5,90.206,33.402,90.323,33.289,90.436,33.282,90.66,33.156,90.804,33.141,90.95,33.239,91.128,33.254,91.189,33.337,91.362,33.337,91.376,33.271,91.543,33.08,91.72,32.983,91.813,32.968,91.977,32.849,92.086,32.886,92.196,32.869,92.221,32.745,92.466,32.769,92.65,32.74,92.727,32.761,92.85,32.729,93.006,32.735,93.031,32.664,93.219,32.66,93.345,32.578,93.443,32.563,93.51,32.516,93.597,32.565,93.734,32.572,93.877,32.495,94.111,32.478,94.178,32.522,94.317,32.541,94.582,32.672,94.722,32.593,94.755,32.535,94.852,32.5,94.939,32.428,94.951,32.337,95.062,32.261,95.259,32.244,95.382,32.17,95.425,32.095,95.354,31.954,95.407,31.811,95.497,31.742,95.593,31.762,95.725,31.746,95.774,31.698,95.849,31.714,96.094,31.701,96.167,31.548,96.215,31.607,96.213,31.735,96.148,31.78,96.183,31.871,96.278,31.909,96.483,31.753,96.664,31.721,96.737,31.68,96.795,31.715,96.753,31.839,96.786,31.91,96.754,31.974,96.915,31.99,97.018,32.023,97.124,32.009,97.255,32.076,97.254,32.204,97.36,32.26,97.393,32.385,97.326,32.424,97.361,32.5,97.562,32.484,97.631,32.444,97.716,32.544]},{"z":4.6,"b":[92.772,39.153,96.366,42.723],"l":[96.366,42.723,96.091,42.587,96.044,42.5,96.018,42.139,96.036,41.995,95.864,41.855,95.772,41.83,95.492,41.856,95.058,41.789,94.581,41.585,94.011,41.104,93.76,40.823,93.669,40.683,93.577,40.588,93.286,40.469,93.037,40.49,92.926,40.423,92.92,40.118,92.777,39.906,92.772,39.811,92.948,39.452,92.936,39.153]},{"z":4.6,"b":[89.678,36.01,92.936,39.153],"l":[92.936,39.153,92.333,39.05,92.123,38.941,91.881,38.875,91.629,38.826,91.492,38.817,91.294,38.745,91.012,38.699,90.654,38.674,90.636,38.624,90.478,38.533,90.312,38.466,90.151,38.433,90.193,38.326,90.439,37.996,90.41,37.847,90.436,37.779,90.834,37.608,90.957,37.519,91.074,37.488,91.152,37.323,91.235,37.195,91.316,37.119,91.288,37.014,91.191,37.001,91.055,36.945,90.891,36.939,90.808,36.911,90.712,36.822,90.688,36.698,90.71,36.636,90.805,36.559,91.007,36.503,91.061,36.314,91.136,36.141,91.111,36.074,90.915,36.013,90.829,36.01,90.623,36.111,90.292,36.116,90.166,36.129,90.014,36.253,89.972,36.105,89.867,36.066,89.678,36.082]},{"z":4.6,"b":[75.348,33.197,76.74,34.573],"l":[76.74,33.197,76.647,33.352,76.488,33.502,76.246,33.574,76.236,33.729,76.166,33.873,76.024,33.982,75.88,33.964,75.742,34.066,75.663,34.199,75.585,34.21,75.469,34.28,75.375,34.371,75.348,34.573]},{"z":4.6,"b":[76.74,32.504,78.381,33.197],"l":[78.381,32.528,78.27,32.504,78.304,32.596,78.375,32.649,78.367,32.765,78.254,32.698,78.115,32.668,78.009,32.61,77.939,32.695,77.929,32.762,77.858,32.873,77.721,33.013,77.539,32.918,77.349,32.867,77.136,32.927,76.956,33.025,76.74,33.197]},{"z":4.6,"b":[113.776,0.559,114.211,1.463],"l":[113.776,0.559,113.918,0.736,113.948,0.862,113.863,0.902,113.864,0.976,114.018,1.079,114.192,1.214,114.211,1.463]},{"z":4.6,"b":[115.607,-2.363,116.573,-1.426],"l":[116.573,-2.363,116.39,-2.314,116.043,-2.288,115.994,-2.271,115.874,-2.351,115.814,-2.184,115.829,-2.047,115.749,-2.001,115.741,-1.784,115.689,-1.636,115.607,-1.467,115.678,-1.426]},{"z":4.6,"b":[113.776,-1.426,115.805,0.735],"l":[115.678,-1.426,115.72,-1.426,115.802,-1.294,115.805,-1.085,115.74,-1.104,115.629,-0.978,115.518,-0.938,115.46,-0.886,115.415,-0.774,115.381,-0.582,115.33,-0.511,115.274,-0.311,115.277,-0.22,115.347,0.002,115.275,0.031,115.187,0.005,115.086,-0.118,115.003,-0.178,114.967,-0.098,115.018,0.058,114.953,0.284,114.968,0.364,115.076,0.533,115.057,0.637,114.976,0.735,114.735,0.673,114.663,0.621,114.378,0.589,114.184,0.65,114.036,0.647,113.97,0.604,113.776,0.559]},{"z":4.6,"b":[73.166,20.115,74.292,21.952],"l":[74.129,21.952,74.061,21.94,73.918,21.867,73.809,21.836,73.829,21.651,73.752,21.611,73.821,21.508,73.996,21.555,74.192,21.546,74.292,21.514,74.109,21.452,74.053,21.466,73.96,21.394,73.919,21.292,73.8,21.281,73.777,21.217,73.695,21.123,73.868,20.975,73.903,20.884,73.909,20.776,73.811,20.677,73.792,20.606,73.7,20.562,73.476,20.679,73.404,20.67,73.459,20.538,73.361,20.369,73.394,20.27,73.364,20.178,73.272,20.193,73.243,20.139,73.166,20.115]},{"z":4.6,"b":[72.708,20.073,72.947,20.127],"l":[72.947,20.127,72.708,20.073]},{"z":4.6,"b":[72.897,20.115,73.166,20.33],"l":[73.166,20.115,73.042,20.158,73.047,20.221,73.112,20.255,73.05,20.33,72.897,20.222,72.947,20.127]},{"z":4.6,"b":[72.818,20.379,72.875,20.468],"l":[72.818,20.379,72.875,20.404,72.847,20.468]},{"z":4.6,"b":[70.967,20.729,71.006,20.747],"l":[71.006,20.737,70.983,20.747,70.967,20.729]},{"z":4.6,"b":[71.057,23.036,74.323,24.708],"l":[71.057,24.693,71.251,24.637,71.378,24.647,71.546,24.688,71.652,24.658,71.751,24.678,71.948,24.663,72.051,24.708,72.099,24.657,72.262,24.608,72.275,24.568,72.501,24.496,72.556,24.507,72.668,24.461,72.747,24.381,72.897,24.361,72.959,24.39,73,24.493,73.095,24.504,73.137,24.336,73.079,24.204,73.246,24.016,73.354,24.105,73.424,23.941,73.364,23.889,73.362,23.784,73.511,23.684,73.621,23.583,73.619,23.452,73.711,23.415,73.812,23.435,73.904,23.333,73.96,23.359,74.106,23.248,74.109,23.191,74.233,23.156,74.323,23.036]},{"z":4.6,"b":[74.026,21.952,74.458,23.036],"l":[74.323,23.036,74.365,22.875,74.427,22.872,74.458,22.804,74.376,22.651,74.273,22.61,74.161,22.517,74.063,22.521,74.026,22.48,74.113,22.411,74.222,22.409,74.224,22.349,74.056,22.294,74.071,22.198,74.142,22.063,74.129,21.952]},{"z":4.7,"b":[121.174,46.22,126.056,53.317],"l":[121.413,53.317,121.607,53.241,121.8,53.024,121.692,52.908,121.503,52.775,121.174,52.595,121.491,52.445,121.692,52.389,121.831,52.269,122.018,52.283,122.174,52.48,122.339,52.454,122.492,52.295,122.731,52.249,122.775,52.2,122.638,52.095,122.754,51.847,122.714,51.702,122.836,51.568,122.839,51.511,122.956,51.386,123.088,51.312,123.272,51.267,123.438,51.273,123.57,51.204,123.789,51.233,123.866,51.313,124.004,51.302,124.162,51.328,124.351,51.279,124.502,51.361,124.666,51.335,124.821,51.366,125.061,51.534,125.128,51.616,125.333,51.583,125.487,51.507,125.577,51.389,125.719,51.254,125.843,51.206,125.871,51.145,125.984,51.104,126.056,50.98,126.001,50.902,125.824,50.767,125.809,50.562,125.531,50.393,125.418,50.195,125.264,50.106,125.295,50.014,125.188,49.942,125.235,49.848,125.22,49.668,125.131,49.637,125.236,49.554,125.267,49.438,125.223,49.22,125.158,49.151,124.864,49.175,124.822,49.061,124.622,48.742,124.616,48.668,124.538,48.572,124.521,48.369,124.562,48.236,124.52,48.129,124.426,48.174,124.327,48.332,124.301,48.492,124.234,48.531,124.078,48.448,123.727,48.191,123.578,48.054,123.293,47.946,123.207,47.814,123.095,47.755,122.797,47.644,122.565,47.531,122.495,47.41,122.405,47.332,122.574,47.131,122.797,47.034,122.996,46.84,123.187,46.762,123.33,46.833,123.38,46.898,123.489,46.817,123.609,46.775,123.536,46.692,123.397,46.598,123.292,46.586,123.094,46.622,123.02,46.612,122.989,46.407,123.162,46.22]},{"z":4.6,"b":[24.147,-31.79,25.522,-30.613],"l":[25.467,-30.613,25.483,-30.756,25.522,-30.802,25.507,-30.934,25.446,-31.095,25.35,-31.228,25.215,-31.202,24.968,-31.284,24.765,-31.389,24.559,-31.402,24.513,-31.553,24.5,-31.705,24.335,-31.719,24.154,-31.758,24.147,-31.79]},{"z":4.6,"b":[17.767,-32.937,24.147,-30.471],"l":[24.147,-31.79,23.963,-31.727,23.863,-31.8,23.809,-31.728,23.699,-31.667,23.401,-31.681,23.293,-31.751,23.181,-31.89,23.07,-31.977,23.027,-31.886,22.943,-31.856,22.756,-31.834,22.606,-31.777,22.477,-31.653,22.291,-31.57,22.144,-31.854,22.089,-31.893,22.101,-32.057,21.971,-32.196,21.853,-32.243,21.716,-32.212,21.553,-32.241,21.423,-32.349,21.305,-32.377,21.192,-32.459,21.118,-32.607,20.865,-32.677,20.823,-32.716,20.784,-32.862,20.705,-32.918,20.546,-32.902,20.421,-32.937,20.209,-32.764,20.123,-32.596,20.082,-32.412,20.213,-32.284,20.139,-32.19,19.925,-32.357,19.801,-32.373,19.704,-32.438,19.61,-32.458,19.534,-32.638,19.473,-32.583,19.488,-32.466,19.425,-32.31,19.493,-32.232,19.471,-32.078,19.319,-32.022,19.211,-31.914,19.029,-31.893,19.072,-31.748,19.067,-31.577,19.017,-31.495,18.997,-31.29,18.937,-31.197,18.905,-31.01,18.928,-30.942,18.939,-30.724,18.843,-30.695,18.735,-30.577,18.561,-30.471,18.464,-30.526,18.411,-30.5,18.336,-30.654,18.247,-30.783,18.157,-30.822,18.024,-30.775,17.892,-30.934,17.767,-31.156]},{"z":4.6,"b":[22.621,-28.1,25.027,-25.987],"l":[22.621,-26.112,22.705,-26.13,22.702,-26.41,22.815,-26.448,22.851,-26.398,22.915,-26.155,22.986,-26.161,23.057,-26.1,23.099,-25.987,23.374,-26.016,23.351,-26.08,23.229,-26.059,23.222,-26.12,23.303,-26.144,23.222,-26.268,23.284,-26.343,23.271,-26.479,23.458,-26.529,23.581,-26.615,23.494,-26.737,23.846,-26.887,23.927,-26.866,23.988,-26.912,24.024,-27.016,23.962,-27.1,23.894,-27.258,24.085,-27.359,24.036,-27.475,24.043,-27.585,24.116,-27.628,24.112,-27.743,24.408,-27.684,24.445,-27.783,24.363,-27.899,24.447,-27.977,24.438,-28.038,24.52,-28.1,24.611,-28,24.621,-27.904,24.691,-27.826,24.671,-27.672,24.796,-27.611,24.815,-27.687,24.972,-27.685,25.027,-27.708,24.842,-27.906,24.932,-28.097,25.013,-28.068]},{"z":4.6,"b":[24.341,-30.613,25.467,-28.068],"l":[25.013,-28.068,25.01,-28.115,24.916,-28.317,24.845,-28.519,24.874,-28.65,24.655,-29.069,24.341,-29.649,24.409,-29.764,24.658,-29.896,24.67,-29.95,24.784,-29.988,24.907,-30.201,25.029,-30.283,25.043,-30.33,25.185,-30.456,25.467,-30.613]},{"z":4.6,"b":[95.176,26.655,95.246,27.033],"l":[95.246,26.655,95.235,26.791,95.202,26.867,95.225,26.974,95.176,27.033]},{"z":4.6,"b":[94.959,27.033,96.007,27.993],"l":[95.176,27.033,95.261,27.049,95.424,27.141,95.493,27.25,95.546,27.274,95.644,27.23,95.8,27.286,95.904,27.276,96.007,27.365,95.978,27.45,95.88,27.463,95.887,27.557,95.804,27.619,95.776,27.717,95.901,27.865,95.964,27.993,95.717,27.99,95.544,27.902,95.408,27.871,95.313,27.896,94.959,27.773]},{"z":4.6,"b":[75.805,32.517,76.74,33.231],"l":[76.74,33.197,76.626,33.189,76.559,33.231,76.397,33.198,76.294,33.148,76.216,33.051,76.101,33.013,75.972,32.911,75.826,32.938,75.805,32.899,75.909,32.773,75.923,32.617,75.847,32.517]},{"z":4.6,"b":[75.298,32.301,75.847,32.517],"l":[75.847,32.517,75.743,32.464,75.683,32.4,75.612,32.395,75.502,32.301,75.39,32.335,75.298,32.332]},{"z":4.6,"b":[97.716,29.224,99.109,32.544],"l":[97.716,32.544,98.014,32.465,98.081,32.405,98.206,32.356,98.22,32.252,98.31,32.129,98.453,31.984,98.421,31.878,98.445,31.804,98.559,31.677,98.592,31.599,98.885,31.354,98.768,31.23,98.683,31.316,98.611,31.256,98.62,31.188,98.803,30.991,98.775,30.902,98.954,30.748,98.905,30.664,98.933,30.491,98.961,30.46,98.987,30.144,99.034,30.055,99.056,29.915,99.005,29.82,98.987,29.651,99.042,29.562,99.063,29.308,99.109,29.224]},{"z":4.6,"b":[98.099,28.14,99.109,29.224],"l":[99.109,29.224,98.99,29.202,99.006,29.073,98.932,28.986,98.909,28.912,98.946,28.842,98.873,28.812,98.82,28.886,98.785,29,98.665,28.974,98.639,28.879,98.678,28.757,98.589,28.67,98.64,28.424,98.576,28.318,98.49,28.248,98.41,28.251,98.3,28.355,98.23,28.203,98.099,28.14]},{"z":4.7,"b":[88.736,49.903,89.869,51.559],"l":[88.736,51.559,88.955,51.438,88.946,51.228,89.032,51.184,89.052,51.065,89.384,50.858,89.674,50.579,89.78,50.567,89.869,50.485,89.844,50.43,89.508,50.362,89.39,50.319,89.336,50.21,89.501,50.207,89.553,50.052,89.644,49.903]},{"z":4.7,"b":[27.67,58.44,29.975,59.014],"l":[27.67,58.982,27.873,59.014,28.131,58.99,28.272,58.856,28.552,58.865,28.757,58.827,28.946,58.82,28.975,58.78,29.232,58.706,29.449,58.595,29.539,58.48,29.786,58.44,29.975,58.445]},{"z":4.7,"b":[87.173,52.168,88.418,52.612],"l":[87.173,52.612,87.334,52.598,87.37,52.518,87.579,52.487,87.636,52.45,87.924,52.548,88.201,52.416,88.413,52.455,88.418,52.351,88.363,52.299,88.365,52.168]},{"z":4.7,"b":[87.839,51.316,88.736,52.168],"l":[88.365,52.168,88.121,52.098,87.839,51.811,88.086,51.714,87.967,51.598,87.862,51.549,88.106,51.432,88.132,51.386,88.364,51.32,88.508,51.316,88.665,51.424,88.659,51.535,88.736,51.559]},{"z":4.6,"b":[92.936,33.512,103.04,39.322],"l":[101.778,33.53,101.613,33.512,101.577,33.631,101.496,33.705,101.4,33.645,101.16,33.664,101.166,33.756,101.129,33.851,101.009,33.881,100.948,33.93,100.792,34.155,100.815,34.295,100.935,34.386,101.036,34.339,101.256,34.3,101.324,34.26,101.612,34.191,101.647,34.136,101.78,34.068,101.863,34.141,102.012,34.178,102.077,34.267,102.212,34.352,102.15,34.495,102.013,34.536,101.932,34.591,101.914,34.661,101.793,34.632,101.755,34.704,101.923,34.787,101.913,34.845,102.009,34.942,102.267,35.056,102.376,35.204,102.318,35.275,102.289,35.407,102.426,35.435,102.535,35.536,102.702,35.524,102.721,35.619,102.68,35.757,102.7,35.839,102.838,35.861,102.976,35.833,103,35.991,102.933,36.086,103.04,36.216,102.894,36.309,102.819,36.326,102.77,36.498,102.698,36.609,102.611,36.657,102.615,36.729,102.675,36.802,102.46,36.956,102.496,37.089,102.551,37.164,102.427,37.278,102.246,37.381,102.142,37.407,102.033,37.479,102.067,37.548,101.996,37.615,101.937,37.731,101.766,37.525,101.647,37.612,101.293,37.799,101.05,37.964,100.636,38.105,100.554,38.252,100.497,38.289,100.358,38.307,100.141,38.486,100.083,38.395,100.156,38.303,100.079,38.278,99.8,38.366,99.557,38.515,99.496,38.601,99.099,38.89,99.095,38.958,98.962,38.982,98.902,39.036,98.782,39.068,98.602,38.948,98.445,38.957,98.324,39.023,98.263,38.932,98.069,38.802,98.021,38.836,97.698,38.981,97.675,39.005,97.363,39.15,97.137,39.203,97.026,39.196,96.963,39.103,96.991,38.997,96.971,38.864,96.981,38.744,96.942,38.639,96.971,38.567,96.945,38.358,96.845,38.356,96.51,38.47,96.503,38.549,96.315,38.627,96.055,38.679,96.008,38.752,95.892,38.784,95.788,38.862,95.722,38.875,95.566,39.015,95.461,39.047,95.305,39.183,95.057,39.147,94.912,39.198,94.725,39.237,94.631,39.295,94.479,39.318,94.117,39.322,93.909,39.286,93.722,39.307,93.502,39.272,93.405,39.217,93.276,39.189,93.14,39.199,93.036,39.152,92.936,39.153]},{"z":4.7,"b":[84.014,50.629,87.173,52.668],"l":[84.067,50.629,84.183,50.661,84.542,50.691,84.6,50.858,84.528,50.881,84.471,50.973,84.252,51.002,84.018,51.064,84.014,51.138,84.172,51.144,84.27,51.275,84.394,51.266,84.705,51.395,84.937,51.43,85.008,51.489,85.239,51.471,85.313,51.569,85.428,51.56,85.537,51.7,85.67,51.754,85.74,51.861,85.877,51.919,85.834,51.971,85.94,52.075,86.15,52.07,86.265,52.012,86.44,52.023,86.505,52.12,86.754,52.109,86.814,52.145,86.806,52.292,86.618,52.372,86.841,52.648,86.952,52.635,87.117,52.668,87.173,52.612]},{"z":4.6,"b":[105.499,32.885,108.704,37.068],"l":[107.285,37.068,107.3,36.907,107.501,36.891,107.549,36.84,107.695,36.825,107.887,36.755,108.042,36.598,108.356,36.546,108.441,36.461,108.607,36.432,108.704,36.359,108.65,36.226,108.7,36.126,108.677,36.005,108.493,35.881,108.523,35.776,108.51,35.701,108.604,35.548,108.617,35.393,108.493,35.272,108.277,35.263,108.155,35.291,107.979,35.223,107.923,35.267,107.742,35.318,107.67,35.228,107.83,34.977,107.71,34.951,107.57,34.965,107.497,34.926,107.321,34.942,107.211,34.892,107.052,35.038,106.915,35.089,106.565,35.08,106.491,35.03,106.501,34.926,106.55,34.863,106.492,34.741,106.318,34.583,106.368,34.52,106.498,34.52,106.614,34.459,106.674,34.385,106.656,34.254,106.554,34.281,106.587,34.137,106.514,34.107,106.43,33.942,106.48,33.868,106.455,33.803,106.482,33.701,106.559,33.599,106.496,33.544,106.393,33.619,106.171,33.562,106.073,33.618,105.993,33.611,105.952,33.553,105.832,33.498,105.785,33.406,105.718,33.389,105.747,33.294,105.913,33.234,105.911,33.032,105.886,32.978,105.639,32.885,105.499,32.907]},{"z":4.6,"b":[101.778,32.608,105.499,34.296],"l":[105.499,32.907,105.431,32.911,105.386,32.823,105.435,32.774,105.27,32.642,105.147,32.608,105.032,32.639,104.898,32.612,104.841,32.64,104.645,32.658,104.558,32.688,104.406,32.809,104.289,32.848,104.321,32.953,104.378,32.959,104.406,33.063,104.328,33.14,104.281,33.272,104.293,33.364,104.175,33.49,104.156,33.624,104.093,33.669,103.915,33.683,103.745,33.676,103.659,33.711,103.523,33.714,103.559,33.807,103.351,33.756,103.272,33.765,103.184,33.822,103.193,33.884,103.142,33.962,103.152,34.109,103.12,34.169,103.001,34.214,102.931,34.296,102.782,34.274,102.598,34.165,102.618,34.084,102.441,34.06,102.372,33.976,102.319,33.987,102.171,33.942,102.343,33.725,102.356,33.609,102.482,33.54,102.482,33.465,102.279,33.378,102.131,33.285,102.099,33.222,102.004,33.219,101.864,33.123,101.832,33.269,101.923,33.406,101.884,33.546,101.778,33.53]},{"z":4.6,"b":[97.191,37.39,104.359,42.787],"l":[104.359,37.401,104.262,37.39,104.109,37.462,103.864,37.626,103.679,37.775,103.438,37.842,103.387,37.999,103.388,38.102,103.522,38.14,103.471,38.436,103.794,38.591,103.98,38.759,104.043,38.874,104.172,38.96,104.225,39.091,104.06,39.308,104.024,39.441,103.749,39.425,103.464,39.357,103.284,39.293,102.965,39.119,102.831,39.13,102.593,39.178,102.393,39.237,101.825,39.064,102.023,38.893,102.02,38.857,101.831,38.69,101.58,38.688,101.414,38.749,101.32,38.758,101.328,38.826,101.248,38.87,101.18,39.021,101.049,39.001,100.889,39.105,100.852,39.167,100.833,39.38,100.79,39.408,100.543,39.409,100.455,39.496,100.307,39.55,100.287,39.627,100.189,39.695,100.043,39.728,99.686,39.876,99.431,39.879,99.533,40.001,99.627,40.069,99.899,40.205,100.014,40.402,100.14,40.518,100.204,40.617,100.176,40.735,100.075,40.859,100.011,40.896,99.665,40.901,99.496,40.843,99.157,40.836,98.826,40.727,98.646,40.568,98.515,40.534,98.235,40.542,98.327,40.856,98.294,40.917,98.204,40.951,97.951,41.12,97.708,41.349,97.648,41.456,97.68,41.502,97.847,41.619,97.191,42.787]},{"z":4.6,"b":[104.359,35.261,107.285,37.44],"l":[104.359,37.401,104.465,37.44,104.69,37.412,104.722,37.339,104.621,37.249,104.709,37.215,104.765,37.25,104.855,37.218,104.922,37.097,105.18,36.972,105.322,36.781,105.22,36.693,105.269,36.55,105.393,36.384,105.446,36.255,105.437,36.107,105.351,36.058,105.318,35.933,105.432,35.756,105.687,35.66,105.829,35.494,105.936,35.525,106.065,35.488,106.112,35.425,106.211,35.395,106.313,35.274,106.378,35.261,106.465,35.332,106.495,35.553,106.429,35.699,106.47,35.727,106.767,35.707,106.913,35.789,106.843,35.882,106.915,35.907,106.945,36.077,106.814,36.212,106.745,36.207,106.604,36.278,106.497,36.268,106.495,36.437,106.44,36.515,106.494,36.56,106.502,36.707,106.602,36.726,106.649,36.834,106.575,36.931,106.634,36.998,106.644,37.182,106.791,37.19,106.933,37.107,107.188,37.117,107.285,37.068]},{"z":4.6,"b":[107.658,37.633,111.137,39.565],"l":[111.11,39.385,111.041,39.423,111.137,39.565,111.013,39.565,110.846,39.465,110.685,39.264,110.603,39.267,110.494,39.374,110.338,39.318,110.225,39.424,110.109,39.428,110.177,39.285,109.963,39.181,109.821,39.054,109.674,39.011,109.635,38.906,109.538,38.785,109.424,38.769,109.313,38.62,109.16,38.542,108.986,38.335,108.953,38.199,109.028,38.111,109.001,37.988,108.954,37.926,108.863,37.988,108.791,37.942,108.764,37.682,108.496,37.669,108.333,37.634,108.039,37.633,107.991,37.726,107.885,37.802,107.658,37.853]},{"z":4.6,"b":[104.359,37.401,107.658,39.361],"l":[107.658,37.853,107.405,37.922,107.324,38.066,107.163,38.139,107.061,38.115,106.829,38.15,106.47,38.291,106.645,38.459,106.704,38.637,106.932,38.921,106.978,39.036,106.912,39.069,106.808,39.199,106.789,39.361,106.614,39.358,106.497,39.286,106.347,39.294,106.276,39.269,106.283,39.157,106.113,39.121,106.06,38.979,105.897,38.736,105.841,38.571,105.828,38.227,105.756,38.136,105.825,38.005,105.788,37.805,105.659,37.74,105.534,37.725,105.354,37.753,105.085,37.661,104.944,37.546,104.647,37.507,104.522,37.53,104.425,37.499,104.359,37.401]},{"z":4.6,"b":[113.881,40.743,119.231,42.591],"l":[119.231,41.273,119.153,41.312,118.728,41.347,118.478,41.355,118.37,41.314,118.314,41.57,118.244,41.592,118.159,41.719,118.278,41.753,118.322,41.864,118.268,42.074,118.134,42.022,118.086,42.182,117.989,42.222,118.04,42.289,118.024,42.383,117.8,42.569,117.743,42.591,117.594,42.552,117.45,42.549,117.412,42.458,117.244,42.482,117.056,42.463,116.887,42.395,116.827,42.308,116.879,42.22,116.858,42.02,116.6,41.924,116.466,41.948,116.378,42.01,116.207,41.884,116.13,41.873,116.038,41.801,115.937,41.927,115.859,41.927,115.528,41.772,115.34,41.721,115.347,41.624,115.226,41.586,115.097,41.616,114.889,41.61,114.876,41.812,114.931,41.839,114.92,41.937,114.805,42.178,114.752,42.14,114.596,42.141,114.481,42.075,114.42,41.956,114.256,41.86,114.214,41.767,114.197,41.594,114.027,41.531,113.928,41.418,113.909,41.294,113.985,41.241,113.964,41.153,113.881,41.099,113.908,41.028,114.019,40.924,114.06,40.814,114.154,40.743]},{"z":4.6,"b":[111.11,39.385,114.154,40.743],"l":[114.154,40.743,114.027,40.635,114.076,40.55,114.044,40.488,113.808,40.434,113.677,40.429,113.518,40.344,113.334,40.319,113.23,40.417,113.086,40.404,112.961,40.356,112.844,40.226,112.734,40.173,112.596,40.238,112.399,40.288,112.279,40.229,112.165,40.059,112.099,40.005,111.921,39.689,111.761,39.598,111.675,39.636,111.49,39.652,111.425,39.625,111.421,39.522,111.343,39.444,111.199,39.427,111.11,39.385]},{"z":4.6,"b":[121.666,43.353,123.696,46.22],"l":[123.162,46.22,123.069,46.109,122.799,46.089,122.801,45.913,122.773,45.788,122.647,45.725,122.498,45.825,122.399,45.949,122.24,45.813,121.996,45.98,121.774,46.01,121.815,45.914,121.666,45.716,121.785,45.676,121.934,45.69,121.964,45.568,122.027,45.485,122.136,45.449,122.234,45.287,122.192,45.201,122.011,45.118,122.038,44.934,122.023,44.847,122.046,44.695,122.088,44.62,122.282,44.429,122.254,44.246,122.377,44.22,122.715,44.336,122.955,44.447,123.045,44.513,123.124,44.494,123.117,44.406,123.168,44.367,123.258,44.204,123.359,44.151,123.322,44.042,123.522,43.697,123.49,43.591,123.361,43.54,123.378,43.453,123.606,43.365,123.696,43.353]},{"z":4.6,"b":[119.231,41.273,123.696,43.353],"l":[123.696,43.353,123.612,43.081,123.354,43.012,123.182,42.944,123.131,42.826,122.893,42.745,122.728,42.741,122.476,42.85,122.367,42.799,122.426,42.722,122.4,42.658,122.299,42.636,122.045,42.721,121.918,42.647,121.878,42.54,121.724,42.458,121.646,42.454,121.544,42.535,121.369,42.5,121.034,42.26,120.913,42.297,120.735,42.228,120.628,42.16,120.509,42.149,120.469,42.038,120.34,41.966,120.141,41.789,120.108,41.707,120.026,41.734,120.03,41.861,119.96,41.972,119.892,41.999,119.808,42.12,119.821,42.209,119.629,42.251,119.563,42.372,119.336,42.289,119.245,42.192,119.366,42.1,119.322,41.965,119.331,41.868,119.299,41.777,119.316,41.658,119.39,41.473,119.362,41.375,119.231,41.273]},{"z":4.7,"b":[44.839,42.691,45.172,43.584],"l":[44.839,43.584,45.047,43.494,45.121,43.204,45.169,43.127,45.137,43.075,45.161,42.946,45.137,42.835,45.172,42.792,45.076,42.691]},{"z":4.7,"b":[44.839,43.661,45.509,44.015],"l":[44.839,43.661,45,43.705,45.08,43.788,45.083,43.953,45.19,43.952,45.237,43.88,45.432,43.879,45.459,44,45.509,44.015]},{"z":4.7,"b":[44.839,43.584,44.839,43.661],"l":[44.839,43.661,44.839,43.584]},{"z":4.7,"b":[44.499,42.74,44.937,43.62],"l":[44.839,43.584,44.768,43.62,44.616,43.61,44.522,43.564,44.499,43.392,44.572,43.392,44.575,43.24,44.713,43.218,44.818,43.163,44.915,43.071,44.937,42.92,44.852,42.836,44.628,42.883,44.639,42.74]},{"z":4.7,"b":[43.386,42.887,44.433,43.819],"l":[44.285,43.819,44.328,43.664,44.433,43.654,44.38,43.49,44.181,43.357,44.108,43.431,44.036,43.365,44.084,43.274,43.898,43.254,43.848,43.31,43.735,43.11,43.566,42.939,43.437,42.95,43.386,42.887]},{"z":4.7,"b":[44.285,43.661,44.839,43.83],"l":[44.285,43.819,44.399,43.795,44.694,43.83,44.803,43.802,44.839,43.661]},{"z":4.7,"b":[42.363,43.235,42.683,43.807],"l":[42.683,43.807,42.671,43.696,42.46,43.615,42.479,43.5,42.405,43.395,42.404,43.284,42.363,43.235]},{"z":4.7,"b":[42.683,43.807,44.285,43.994],"l":[42.683,43.807,42.852,43.817,42.975,43.891,43.111,43.896,43.175,43.948,43.266,43.91,43.3,43.824,43.502,43.867,43.729,43.839,43.803,43.941,43.906,43.994,44.259,43.925,44.285,43.819]},{"z":4.7,"b":[38.728,43.751,40.625,45.193],"l":[39.752,45.127,39.642,45.193,39.54,45.156,39.324,45.001,39.042,44.966,38.921,45.017,38.728,45.012,38.76,44.939,39.064,44.81,39.553,44.807,39.632,44.858,39.489,44.967,39.495,45.031,39.639,45.034,39.75,44.935,39.884,45.007,40.02,44.724,39.915,44.699,39.915,44.583,39.874,44.473,39.793,44.357,39.908,44.325,39.971,44.256,40.078,44.244,39.988,44.078,39.905,44.077,39.8,44.19,39.718,44.118,39.754,44.061,39.73,43.918,39.931,43.898,40.279,43.751,40.336,43.769,40.455,43.981,40.383,44.033,40.366,44.198,40.437,44.284,40.408,44.343,40.406,44.499,40.327,44.564,40.394,44.725,40.554,44.72,40.625,44.767,40.528,44.915,40.416,45,40.174,45.11,39.964,45.075,39.752,45.127]},{"z":4.7,"b":[41.729,43.807,42.683,44.495],"l":[41.729,44.459,41.905,44.495,41.945,44.403,42.038,44.364,42.23,44.344,42.346,44.306,42.499,44.299,42.555,44.245,42.303,44.129,42.306,44.058,42.378,43.971,42.497,44.03,42.66,43.917,42.683,43.807]},{"z":4.7,"b":[40.841,44.459,41.729,45.954],"l":[41.162,45.954,41.261,45.872,41.274,45.754,41.172,45.702,40.87,45.696,40.841,45.539,40.925,45.43,41.008,45.385,41.038,45.218,41.26,45.228,41.36,45.212,41.347,45.122,41.469,44.987,41.642,44.981,41.651,44.897,41.538,44.736,41.417,44.708,41.567,44.546,41.702,44.501,41.729,44.459]},{"z":4.7,"b":[40.69,43.523,41.729,44.459],"l":[41.729,44.459,41.602,44.367,41.704,44.308,41.711,44.243,41.633,44.122,41.538,44.085,41.449,43.994,41.282,44.012,41.256,44.081,40.985,44.116,40.951,43.989,40.782,43.927,40.724,43.81,40.723,43.631,40.69,43.523]},{"z":4.7,"b":[30.907,55.578,31.467,55.769],"l":[31.467,55.729,31.187,55.737,31.047,55.769,30.907,55.578]},{"z":4.7,"b":[38.666,45.954,41.162,46.88],"l":[38.666,46.88,38.69,46.821,38.883,46.808,38.849,46.713,38.884,46.635,39.099,46.601,39.18,46.688,39.444,46.809,39.577,46.778,39.817,46.785,40.023,46.765,40.132,46.722,40.045,46.616,40.059,46.542,40.214,46.525,40.284,46.403,40.234,46.358,40.339,46.28,40.735,46.276,40.757,46.233,40.954,46.198,41,46.127,41.126,46.068,41.162,45.954]},{"z":4.7,"b":[29.658,56.953,31,58.445],"l":[29.975,58.445,29.658,58.244,29.679,58.077,29.82,58.083,29.95,57.944,30.168,57.912,30.53,57.887,30.52,57.742,30.472,57.682,30.609,57.617,30.493,57.488,30.516,57.318,30.773,57.258,30.647,57.191,30.645,56.982,30.8,56.953,31,56.965]},{"z":4.7,"b":[30.817,55.729,31.498,56.965],"l":[31,56.965,30.896,56.897,30.89,56.808,30.817,56.72,30.913,56.656,31.08,56.623,31.017,56.519,31.215,56.435,31.231,56.346,31.492,56.301,31.474,56.119,31.498,56.029,31.345,55.964,31.413,55.89,31.467,55.729]},{"z":4.6,"b":[80.27,17.776,81.394,18.714],"l":[81.394,17.807,81.245,17.804,81.148,17.837,81.045,17.776,80.986,17.84,80.979,17.964,80.938,18.086,80.852,18.196,80.772,18.233,80.696,18.412,80.583,18.541,80.503,18.6,80.409,18.589,80.32,18.642,80.27,18.714]},{"z":3,"b":[-50.237,-9.846,-48.159,-5.364],"l":[-50.237,-9.846,-50.235,-9.843,-50.175,-9.73,-50.084,-9.422,-49.979,-9.22,-49.83,-9.022,-49.708,-8.895,-49.613,-8.839,-49.503,-8.709,-49.379,-8.498,-49.283,-8.301,-49.227,-8.14,-49.19,-7.995,-49.172,-7.867,-49.22,-7.752,-49.334,-7.649,-49.34,-7.51,-49.24,-7.335,-49.195,-7.169,-49.21,-7.006,-49.08,-6.856,-48.805,-6.718,-48.653,-6.596,-48.622,-6.489,-48.58,-6.414,-48.527,-6.372,-48.471,-6.353,-48.41,-6.358,-48.391,-6.309,-48.414,-6.207,-48.39,-6.142,-48.319,-6.112,-48.297,-6.067,-48.321,-6.007,-48.307,-5.966,-48.254,-5.945,-48.245,-5.889,-48.28,-5.796,-48.259,-5.724,-48.181,-5.672,-48.159,-5.619,-48.193,-5.565,-48.231,-5.533,-48.275,-5.522,-48.317,-5.486,-48.357,-5.424,-48.422,-5.398,-48.511,-5.408,-48.598,-5.399,-48.699,-5.364]},{"z":3,"b":[-48.723,-10.316,-45.945,-5.17],"l":[-48.699,-5.364,-48.723,-5.355,-48.631,-5.33,-48.574,-5.288,-48.524,-5.22,-48.458,-5.181,-48.378,-5.17,-48.282,-5.189,-48.169,-5.241,-48.059,-5.263,-47.953,-5.256,-47.885,-5.286,-47.854,-5.351,-47.763,-5.407,-47.612,-5.455,-47.52,-5.506,-47.486,-5.561,-47.445,-5.75,-47.397,-6.072,-47.406,-6.421,-47.47,-6.796,-47.537,-7.027,-47.606,-7.115,-47.665,-7.167,-47.716,-7.185,-47.717,-7.222,-47.669,-7.28,-47.612,-7.302,-47.546,-7.288,-47.507,-7.306,-47.495,-7.356,-47.498,-7.45,-47.458,-7.535,-47.413,-7.53,-47.347,-7.66,-47.303,-7.663,-47.28,-7.731,-47.15,-7.856,-47.079,-7.977,-47.016,-8.044,-46.985,-8.039,-46.869,-7.959,-46.744,-7.923,-46.574,-7.905,-46.489,-7.963,-46.467,-8.07,-46.503,-8.173,-46.507,-8.276,-46.543,-8.32,-46.503,-8.396,-46.659,-8.396,-46.793,-8.436,-46.838,-8.486,-46.882,-8.584,-46.922,-8.744,-46.905,-8.829,-47.065,-8.977,-47.083,-9.035,-47.038,-9.066,-46.94,-9.07,-46.847,-9.169,-46.829,-9.307,-46.753,-9.41,-46.561,-9.499,-46.534,-9.557,-46.601,-9.651,-46.65,-9.664,-46.668,-9.744,-46.507,-9.856,-46.463,-9.95,-46.472,-10.003,-46.347,-10.169,-46.284,-10.186,-46.19,-10.177,-46.088,-10.209,-46.034,-10.271,-45.945,-10.316]},{"z":3,"b":[-50.998,-20.102,-50.965,-19.513],"l":[-50.965,-19.513,-50.998,-19.664,-50.994,-20.102]},{"z":3,"b":[-54.243,-24.047,-53.119,-22.642],"l":[-53.119,-22.642,-53.272,-22.752,-53.481,-22.852,-53.62,-23.001,-53.677,-23.174,-53.771,-23.322,-53.908,-23.407,-53.996,-23.57,-54.05,-23.823,-54.125,-23.978,-54.243,-24.047]},{"z":3,"b":[-44.833,-22.405,-41.864,-20.776],"l":[-44.833,-22.405,-44.73,-22.361,-44.663,-22.369,-44.601,-22.316,-44.534,-22.303,-44.445,-22.253,-44.293,-22.24,-44.217,-22.249,-44.092,-22.173,-43.896,-22.106,-43.815,-22.066,-43.565,-22.066,-43.463,-22.057,-43.342,-22.003,-43.132,-22.026,-43.063,-22.063,-42.28,-21.713,-42.284,-21.642,-42.369,-21.633,-42.364,-21.593,-42.302,-21.486,-42.297,-21.405,-42.222,-21.338,-42.181,-21.164,-42.097,-21.003,-42.141,-20.963,-42.097,-20.923,-41.998,-20.932,-41.963,-20.91,-41.927,-20.829,-41.864,-20.776]},{"z":3,"b":[-38.216,-9.513,-37.975,-9.351],"l":[-38.216,-9.351,-37.976,-9.512,-37.975,-9.513]},{"z":3,"b":[-37.975,-10.492,-36.411,-9.513],"l":[-37.975,-9.513,-37.938,-9.538,-37.426,-9.778,-37.196,-9.886,-37.019,-9.957,-36.956,-10.014,-36.94,-10.084,-36.88,-10.154,-36.777,-10.224,-36.699,-10.257,-36.648,-10.254,-36.607,-10.293,-36.58,-10.371,-36.538,-10.411,-36.483,-10.411,-36.482,-10.411,-36.481,-10.411,-36.48,-10.412,-36.479,-10.412,-36.478,-10.413,-36.477,-10.414,-36.476,-10.414,-36.475,-10.415,-36.474,-10.415,-36.473,-10.416,-36.472,-10.416,-36.471,-10.417,-36.47,-10.417,-36.469,-10.418,-36.467,-10.419,-36.466,-10.419,-36.465,-10.42,-36.464,-10.421,-36.463,-10.421,-36.462,-10.422,-36.46,-10.423,-36.459,-10.423,-36.458,-10.424,-36.457,-10.424,-36.456,-10.425,-36.455,-10.426,-36.454,-10.426,-36.453,-10.427,-36.452,-10.427,-36.451,-10.428,-36.45,-10.428,-36.449,-10.429,-36.448,-10.429,-36.447,-10.43,-36.446,-10.43,-36.446,-10.431,-36.445,-10.431,-36.444,-10.431,-36.444,-10.432,-36.443,-10.432,-36.411,-10.492]},{"z":3,"b":[-66.828,-9.838,-61.61,-7.999],"l":[-66.828,-9.838,-66.744,-9.749,-66.695,-9.749,-66.597,-9.664,-66.503,-9.633,-66.405,-9.526,-66.396,-9.405,-66.105,-9.419,-65.998,-9.401,-65.931,-9.414,-65.766,-9.566,-65.677,-9.535,-65.632,-9.45,-65.525,-9.414,-65.445,-9.445,-65.418,-9.392,-65.222,-9.258,-65.199,-9.267,-65.172,-9.374,-65.092,-9.436,-65.039,-9.401,-64.905,-9.213,-64.927,-9.119,-64.887,-9.061,-64.78,-8.986,-64.69,-9.021,-64.597,-9.026,-64.476,-8.95,-64.418,-8.972,-64.382,-8.941,-64.217,-8.95,-64.119,-8.936,-64.128,-8.686,-64.074,-8.713,-63.998,-8.682,-63.922,-8.535,-63.994,-8.441,-63.918,-8.334,-63.753,-8.285,-63.726,-8.191,-63.592,-8.164,-63.588,-8.079,-63.539,-7.999,-62.905,-8.008,-62.829,-8.017,-62.722,-8.061,-62.677,-8.115,-62.637,-8.222,-62.556,-8.289,-62.539,-8.361,-62.454,-8.347,-62.373,-8.383,-62.306,-8.57,-62.172,-8.611,-62.132,-8.767,-61.998,-8.811,-61.918,-8.874,-61.873,-8.856,-61.838,-8.744,-61.762,-8.727,-61.717,-8.686,-61.61,-8.722,-61.61,-8.767]},{"z":3,"b":[-53.211,-18.017,-50.485,-12.843],"l":[-53.052,-18.017,-53.115,-17.855,-53.211,-17.409,-53.203,-17.304,-53.158,-17.209,-53.078,-17.095,-53.033,-16.995,-53.024,-16.91,-52.943,-16.814,-52.789,-16.705,-52.68,-16.576,-52.614,-16.428,-52.681,-16.361,-52.668,-16.289,-52.534,-16.227,-52.538,-16.169,-52.417,-16.082,-52.362,-16.058,-52.308,-16.009,-52.257,-15.936,-52.142,-15.873,-51.962,-15.819,-51.847,-15.747,-51.799,-15.656,-51.779,-15.593,-51.788,-15.557,-51.773,-15.523,-51.732,-15.49,-51.696,-15.403,-51.665,-15.261,-51.59,-15.142,-51.471,-15.044,-51.286,-15.003,-51.246,-15.004,-51.189,-14.977,-51.114,-14.838,-50.978,-14.463,-50.941,-14.201,-50.909,-14.107,-50.868,-14.095,-50.852,-13.995,-50.861,-13.806,-50.8,-13.612,-50.667,-13.41,-50.598,-13.249,-50.595,-13.131,-50.566,-13.015,-50.485,-12.843]},{"z":4.6,"b":[78.954,34.326,80.327,35.523],"l":[80.327,35.503,80.276,35.523,80.249,35.479,80.258,35.408,80.207,35.313,80.102,35.173,80.017,35.096,80.039,35.047,80.036,34.98,79.99,34.963,79.906,34.946,79.879,34.902,79.906,34.855,79.948,34.768,79.96,34.658,79.897,34.58,79.825,34.466,79.768,34.424,79.671,34.439,79.56,34.474,79.458,34.471,79.343,34.431,79.202,34.441,79.049,34.389,78.954,34.326]},{"z":4.6,"b":[92.073,26.916,94.959,27.773],"l":[92.073,26.916,92.204,26.916,92.468,26.966,92.621,27.038,92.717,27.052,92.91,27.031,92.966,26.974,93.094,26.959,93.415,26.995,93.503,26.974,93.684,26.982,93.84,27.109,93.897,27.243,94.127,27.453,94.208,27.538,94.233,27.629,94.289,27.622,94.414,27.583,94.667,27.652,94.959,27.773]},{"z":4.6,"b":[116.744,39.686,117.239,40.093],"l":[116.876,39.686,116.922,39.774,116.781,39.89,116.744,39.959,116.829,40.037,116.988,40.034,117.239,40.093]},{"z":4.6,"b":[116.681,38.559,117.558,39.602],"l":[116.793,39.602,116.791,39.493,116.853,39.341,116.826,39.217,116.892,39.103,116.712,39.012,116.681,38.916,116.711,38.821,116.881,38.699,117.021,38.708,117.095,38.612,117.189,38.606,117.271,38.559,117.558,38.625]},{"z":4.6,"b":[116.876,39.618,117.239,40.093],"l":[117.239,40.093,117.148,39.948,117.203,39.762,117.153,39.618,116.876,39.686]}]} \ No newline at end of file diff --git a/src/maps/map_data.js b/src/maps/map_data.js new file mode 100644 index 0000000..d646834 --- /dev/null +++ b/src/maps/map_data.js @@ -0,0 +1,80 @@ +const fs = require("fs"); +const path = require("path"); + +const DATA_DIR = path.join(__dirname, "data"); +const loaded = new Map(); + +const load = (name) => { + if (loaded.has(name)) return loaded.get(name); + let data = { f: [] }; + try { data = JSON.parse(fs.readFileSync(path.join(DATA_DIR, `${name}.json`), "utf8")); } catch (_) {} + loaded.set(name, data); + return data; +}; + +const countries = (detail) => load(detail === "high" ? "countries_50m" : "countries_110m").f; +const coast = () => load("coast_50m").f; +const lakes = () => load("lakes_50m").f; +const rivers = () => load("rivers_50m").f; +const states = () => load("states_50m").f; +const places = (detail) => load(detail === "high" ? "places_50m" : "places_110m").f; + +const fold = (s) => String(s || "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim(); + +let placeIndex = null; +const buildPlaceIndex = () => { + if (placeIndex) return placeIndex; + const list = []; + for (const p of places("high")) { + list.push({ kind: "place", name: p.n, country: p.a, iso: p.i, pop: p.p || 0, capital: !!p.k, lat: p.la, lng: p.lo, key: fold(p.n) }); + } + for (const c of countries("low")) { + list.push({ kind: "country", name: c.n, country: "", iso: c.c, pop: Number.MAX_SAFE_INTEGER, capital: false, lat: c.ly, lng: c.lx, bbox: c.b, key: fold(c.n) }); + } + for (const c of countries("high")) { + if (list.some((e) => e.kind === "country" && e.key === fold(c.n))) continue; + list.push({ kind: "country", name: c.n, country: "", iso: c.c, pop: Number.MAX_SAFE_INTEGER, capital: false, lat: c.ly, lng: c.lx, bbox: c.b, key: fold(c.n) }); + } + placeIndex = list; + return list; +}; + +const searchPlaces = (query, limit = 5) => { + const q = fold(query); + if (!q) return []; + const index = buildPlaceIndex(); + const scored = []; + for (const e of index) { + let score = 0; + if (e.key === q) score = 3; + else if (e.key.startsWith(q)) score = 2; + else if (e.key.includes(q)) score = 1; + else if (e.iso && fold(e.iso) === q) score = 2; + if (!score) continue; + scored.push({ e, score }); + } + scored.sort((a, b) => b.score - a.score || b.e.pop - a.e.pop || a.e.key.localeCompare(b.e.key)); + return scored.slice(0, limit).map(({ e }) => ({ ...e, key: undefined })); +}; + +const toRad = (d) => d * Math.PI / 180; +const distanceKm = (lat1, lng1, lat2, lng2) => { + const dLat = toRad(lat2 - lat1); + const dLng = toRad(lng2 - lng1); + const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2; + return 6371 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +}; + +const nearestPlace = (lat, lng, maxKm = 300) => { + if (typeof lat !== "number" || typeof lng !== "number" || !isFinite(lat) || !isFinite(lng)) return null; + let best = null; + for (const p of places("high")) { + if (Math.abs(p.la - lat) > 4) continue; + const d = distanceKm(lat, lng, p.la, p.lo); + if (d > maxKm) continue; + if (!best || d < best.km) best = { name: p.n, country: p.a, iso: p.i, km: Math.round(d), lat: p.la, lng: p.lo }; + } + return best; +}; + +module.exports = { load, countries, coast, lakes, rivers, states, places, searchPlaces, nearestPlace, distanceKm, fold }; diff --git a/src/maps/map_renderer.js b/src/maps/map_renderer.js index d1e56e3..427799a 100644 --- a/src/maps/map_renderer.js +++ b/src/maps/map_renderer.js @@ -1,238 +1,608 @@ -const { execFileSync } = require("child_process"); -const path = require("path"); -const fs = require("fs"); -const crypto = require("crypto"); +const data = require("./map_data"); -const BASE_MAP = path.join(__dirname, "..", "client", "assets", "images", "worldmap-z2.png"); -const CACHE_DIR = path.join(__dirname, "cache"); -const TILES_DIR = path.join(__dirname, "tiles"); const MAP_W = 1024; -const MAP_H = 1024; +const MAP_H = 640; +const TILE = 256; +const MIN_ZOOM = 2; +const MAX_ZOOM = 9; +const TILE_MAX_ZOOM = 6; +const TILE_PATH = "/maptiles"; +const TILE_ENV = process.env.OASIS_MAP_TILES || ""; +const CRISP_ZOOM = 9; +const MAX_LAT = 85.0511; +const GRID_COLS = 24; +const GRID_ROWS = 15; +const CLUSTER_PX = 28; +const EDGE = 6; +const POPUP_W = 320; +const POPUP_H = 400; +const POPUP_GAP = 10; -const latLngToPx = (lat, lng) => { - const latRad = lat * Math.PI / 180; - const x = Math.round((lng + 180) / 360 * MAP_W); - const y = Math.round((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * MAP_H); - return { x: Math.max(12, Math.min(MAP_W - 12, x)), y: Math.max(12, Math.min(MAP_H - 12, y)) }; -}; +const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v)); +const num = (v) => { const n = typeof v === "number" ? v : parseFloat(v); return isFinite(n) ? n : null; }; +const r1 = (v) => Math.round(v * 10) / 10; +const r4 = (v) => Math.round(v * 10000) / 10000; +const esc = (s) => String(s == null ? "" : s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); -const pxToLatLng = (px, py) => { - const lng = px / MAP_W * 360 - 180; - const n = Math.PI - 2 * Math.PI * py / MAP_H; - const lat = 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); - return { lat: Math.round(lat * 100) / 100, lng: Math.round(lng * 100) / 100 }; -}; - -const getMaxTileZoom = () => { - try { - const dirs = fs.readdirSync(TILES_DIR).filter(d => /^\d+$/.test(d) && fs.existsSync(path.join(TILES_DIR, d, '0_0.png'))); - return dirs.length ? Math.max(...dirs.map(Number)) : 0; - } catch (_) { return 0; } -}; - -const getViewportBounds = (centerLat, centerLng, zoom) => { - const effectiveZ = Math.min(zoom, getMaxTileZoom()); - const n = Math.pow(2, effectiveZ); - const tileSize = 256; - const worldPx = n * tileSize; - const scale = Math.pow(2, zoom - effectiveZ); - const vw = MAP_W / scale; - const vh = MAP_H / scale; - - const latRad = centerLat * Math.PI / 180; - const cx = (centerLng + 180) / 360 * worldPx; - const cy = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * worldPx; - - const x0 = cx - vw / 2; - const y0 = cy - vh / 2; - const x1 = cx + vw / 2; - const y1 = cy + vh / 2; - - const pxToLng = (px) => px / worldPx * 360 - 180; - const pxToLat = (py) => { const nn = Math.PI - 2 * Math.PI * py / worldPx; return 180 / Math.PI * Math.atan(0.5 * (Math.exp(nn) - Math.exp(-nn))); }; +const worldPx = (zoom) => TILE * Math.pow(2, zoom); +const project = (lat, lng, zoom) => { + const w = worldPx(zoom); + const la = clamp(lat, -MAX_LAT, MAX_LAT) * Math.PI / 180; return { - latMin: Math.max(-85, pxToLat(Math.min(y1, worldPx - 1))), - latMax: Math.min(85, pxToLat(Math.max(y0, 0))), - lngMin: Math.max(-180, pxToLng(Math.max(x0, 0))), - lngMax: Math.min(180, pxToLng(Math.min(x1, worldPx - 1))) + x: (lng + 180) / 360 * w, + y: (1 - Math.log(Math.tan(Math.PI / 4 + la / 2)) / Math.PI) / 2 * w }; }; -const renderMapWithPins = (markers, mainIdx) => { - const pins = (Array.isArray(markers) ? markers : []) - .filter((m) => m && typeof m.lat === "number" && typeof m.lng === "number") - .map((m, i) => ({ ...latLngToPx(m.lat, m.lng), main: i === (mainIdx || 0) })); +const unproject = (x, y, zoom) => { + const w = worldPx(zoom); + const n = Math.PI - 2 * Math.PI * y / w; + return { lat: 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))), lng: x / w * 360 - 180 }; +}; - const hash = crypto.createHash("md5") - .update(pins.map((p) => `${p.x},${p.y},${p.main}`).join(";")) - .digest("hex") - .slice(0, 12); +const latLngToPx = (lat, lng) => { + const p = project(lat, lng, MIN_ZOOM); + return { x: Math.round(p.x), y: Math.round(p.y) }; +}; - const outFile = path.join(CACHE_DIR, `map_${hash}.png`); +const pxToLatLng = (px, py) => { + const p = unproject(px, py, MIN_ZOOM); + return { lat: Math.round(p.lat * 100) / 100, lng: Math.round(p.lng * 100) / 100 }; +}; - if (fs.existsSync(outFile)) return `map_${hash}.png`; +const normalizeZoom = (z) => clamp(Math.round(num(z) == null ? MIN_ZOOM : num(z)), MIN_ZOOM, MAX_ZOOM); - const script = ` -from PIL import Image, ImageDraw -import sys, json +const makeView = (centerLat, centerLng, zoom) => { + const z = normalizeZoom(zoom); + const w = worldPx(z); + const c = project(num(centerLat) == null ? 0 : num(centerLat), num(centerLng) == null ? 0 : num(centerLng), z); + const cx = w <= MAP_W ? w / 2 : clamp(c.x, MAP_W / 2, w - MAP_W / 2); + const cy = w <= MAP_H ? w / 2 : clamp(c.y, MAP_H / 2, w - MAP_H / 2); + const center = unproject(cx, cy, z); + return { zoom: z, lat: r4(center.lat), lng: r4(center.lng), x0: cx - MAP_W / 2, y0: cy - MAP_H / 2, world: w }; +}; -pins = json.loads(sys.argv[1]) -im = Image.open(sys.argv[2]).copy() -draw = ImageDraw.Draw(im) +const toScreen = (view, lat, lng) => { + const p = project(lat, lng, view.zoom); + return { x: p.x - view.x0, y: p.y - view.y0 }; +}; -def draw_pin(draw, x, y, main): - sw = 7 if main else 5 - sh = 34 if main else 26 - head = 11 if main else 8 - clr = '#e74c3c' if main else '#3498db' - dark = '#c0392b' if main else '#2980b9' - cy = y - sh - draw.polygon([(x, y + 2), (x - sw - 1, cy + sw), (x + sw + 1, cy + sw)], fill='white') - draw.polygon([(x, y), (x - sw, cy + sw), (x + sw, cy + sw)], fill=clr) - draw.ellipse([x - head - 2, cy - head - 2, x + head + 2, cy + head + 2], fill='white') - draw.ellipse([x - head, cy - head, x + head, cy + head], fill=clr, outline=dark, width=2) - draw.ellipse([x - head // 3, cy - head // 3, x + head // 3, cy + head // 3], fill='white') +const fromScreen = (view, x, y) => { + const p = unproject(view.x0 + x, view.y0 + y, view.zoom); + return { lat: r4(clamp(p.lat, -MAX_LAT, MAX_LAT)), lng: r4(clamp(p.lng, -180, 180)) }; +}; -for p in pins: - draw_pin(draw, p['x'], p['y'], p.get('main', False)) +const getViewportBounds = (centerLat, centerLng, zoom) => { + const view = makeView(centerLat, centerLng, zoom); + const nw = fromScreen(view, 0, 0); + const se = fromScreen(view, MAP_W, MAP_H); + return { latMin: se.lat, latMax: nw.lat, lngMin: nw.lng, lngMax: se.lng }; +}; -im.save(sys.argv[3], optimize=True) -`; - - try { - fs.mkdirSync(CACHE_DIR, { recursive: true }); - execFileSync("python3", [ - "-c", script, - JSON.stringify(pins), - BASE_MAP, - outFile - ], { timeout: 10000 }); - } catch (e) { - return null; +const fitView = (points, opts = {}) => { + const pts = (Array.isArray(points) ? points : []).filter((p) => p && num(p.lat) != null && num(p.lng) != null); + const single = normalizeZoom(opts.singleZoom || 8); + if (!pts.length) return { zoom: MIN_ZOOM, lat: 10, lng: 0 }; + if (pts.length === 1) return { zoom: single, lat: num(pts[0].lat), lng: num(pts[0].lng) }; + let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity; + for (const p of pts) { + const q = project(num(p.lat), num(p.lng), 0); + x0 = Math.min(x0, q.x); x1 = Math.max(x1, q.x); y0 = Math.min(y0, q.y); y1 = Math.max(y1, q.y); } + const pad = opts.pad == null ? 70 : opts.pad; + const dx = Math.max(x1 - x0, 1e-6); + const dy = Math.max(y1 - y0, 1e-6); + const z = Math.floor(Math.log2(Math.min((MAP_W - 2 * pad) / dx, (MAP_H - 2 * pad) / dy))); + const c = unproject((x0 + x1) / 2, (y0 + y1) / 2, 0); + return { zoom: clamp(z, MIN_ZOOM, Math.min(MAX_ZOOM, opts.maxZoom || MAX_ZOOM)), lat: r4(c.lat), lng: r4(c.lng) }; +}; - return `map_${hash}.png`; +const parseView = (raw) => { + const s = Array.isArray(raw) ? raw[raw.length - 1] : raw; + const m = /^\s*(\d{1,2})\/(-?\d+(?:\.\d+)?)\/(-?\d+(?:\.\d+)?)\s*$/.exec(String(s || "")); + if (!m) return null; + return { zoom: normalizeZoom(m[1]), lat: clamp(parseFloat(m[2]), -MAX_LAT, MAX_LAT), lng: clamp(parseFloat(m[3]), -180, 180) }; +}; + +const viewParam = (v) => `${normalizeZoom(v.zoom)}/${r4(v.lat)}/${r4(v.lng)}`; + +const parsePick = (raw) => { + const s = Array.isArray(raw) ? raw[raw.length - 1] : raw; + const m = /^\s*(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\s*$/.exec(String(s || "")); + if (!m) return null; + const lat = parseFloat(m[1]), lng = parseFloat(m[2]); + if (Math.abs(lat) > 90 || Math.abs(lng) > 180) return null; + return { lat: r4(lat), lng: r4(lng) }; +}; + +const resolveView = (params = {}, pins = [], opts = {}) => { + const explicit = parseView(params.view); + if (explicit) return makeView(explicit.lat, explicit.lng, explicit.zoom); + const clat = num(Array.isArray(params.clat) ? params.clat[0] : params.clat); + const clng = num(Array.isArray(params.clng) ? params.clng[0] : params.clng); + const zoomParam = num(Array.isArray(params.zoom) ? params.zoom[0] : params.zoom); + if (clat != null && clng != null) return makeView(clat, clng, zoomParam == null ? (opts.singleZoom || 8) : zoomParam); + const fit = fitView(pins, opts); + if (zoomParam != null && zoomParam >= MIN_ZOOM) return makeView(opts.focusLat != null ? opts.focusLat : fit.lat, opts.focusLng != null ? opts.focusLng : fit.lng, zoomParam); + return makeView(fit.lat, fit.lng, fit.zoom); +}; + +const metersPerPx = (view) => 156543.03392 * Math.cos(view.lat * Math.PI / 180) / Math.pow(2, view.zoom); + +const bboxHit = (b, vb) => b[0] <= vb[2] && b[2] >= vb[0] && b[1] <= vb[3] && b[3] >= vb[1]; + +const clipPolygon = (pts, x0, y0, x1, y1) => { + let out = pts; + const passes = [ + (p) => p.x >= x0, (p, q) => ({ x: x0, y: p.y + (q.y - p.y) * (x0 - p.x) / (q.x - p.x) }), + (p) => p.x <= x1, (p, q) => ({ x: x1, y: p.y + (q.y - p.y) * (x1 - p.x) / (q.x - p.x) }), + (p) => p.y >= y0, (p, q) => ({ x: p.x + (q.x - p.x) * (y0 - p.y) / (q.y - p.y), y: y0 }), + (p) => p.y <= y1, (p, q) => ({ x: p.x + (q.x - p.x) * (y1 - p.y) / (q.y - p.y), y: y1 }) + ]; + for (let i = 0; i < passes.length && out.length; i += 2) { + const inside = passes[i], cross = passes[i + 1]; + const res = []; + let prev = out[out.length - 1]; + for (const cur of out) { + const ci = inside(cur), pi = inside(prev); + if (ci) { + if (!pi) res.push(cross(prev, cur)); + res.push(cur); + } else if (pi) { + res.push(cross(prev, cur)); + } + prev = cur; + } + out = res; + } + return out; +}; + +const clipSegment = (a, b, x0, y0, x1, y1) => { + let t0 = 0, t1 = 1; + const dx = b.x - a.x, dy = b.y - a.y; + const checks = [[-dx, a.x - x0], [dx, x1 - a.x], [-dy, a.y - y0], [dy, y1 - a.y]]; + for (const [p, q] of checks) { + if (p === 0) { if (q < 0) return null; continue; } + const r = q / p; + if (p < 0) { if (r > t1) return null; if (r > t0) t0 = r; } + else { if (r < t0) return null; if (r < t1) t1 = r; } + } + return [{ x: a.x + t0 * dx, y: a.y + t0 * dy }, { x: a.x + t1 * dx, y: a.y + t1 * dy }]; +}; + +const simplify = (pts, tol) => { + if (pts.length <= 2) return pts; + const keep = new Uint8Array(pts.length); + keep[0] = 1; keep[pts.length - 1] = 1; + const stack = [[0, pts.length - 1]]; + const tol2 = tol * tol; + while (stack.length) { + const [a, b] = stack.pop(); + if (b - a < 2) continue; + const ax = pts[a].x, ay = pts[a].y, bx = pts[b].x, by = pts[b].y; + const dx = bx - ax, dy = by - ay; + const len2 = dx * dx + dy * dy; + let best = -1, bestD = 0; + for (let i = a + 1; i < b; i++) { + let d; + if (len2 === 0) { d = (pts[i].x - ax) ** 2 + (pts[i].y - ay) ** 2; } + else { + const t = clamp(((pts[i].x - ax) * dx + (pts[i].y - ay) * dy) / len2, 0, 1); + d = (pts[i].x - ax - t * dx) ** 2 + (pts[i].y - ay - t * dy) ** 2; + } + if (d > bestD) { bestD = d; best = i; } + } + if (best >= 0 && bestD > tol2) { keep[best] = 1; stack.push([a, best], [best, b]); } + } + const out = []; + for (let i = 0; i < pts.length; i++) if (keep[i]) out.push(pts[i]); + return out; +}; + +const projectFlat = (flat, view, tol) => { + const out = []; + let lx = NaN, ly = NaN; + for (let i = 0; i < flat.length; i += 2) { + const p = toScreen(view, flat[i + 1], flat[i]); + if (Math.abs(p.x - lx) + Math.abs(p.y - ly) < tol) continue; + out.push(p); + lx = p.x; ly = p.y; + } + return simplify(out, tol); +}; + +const pathOf = (pts, close) => { + if (pts.length < 2) return ""; + let d = `M${r1(pts[0].x)} ${r1(pts[0].y)}`; + for (let i = 1; i < pts.length; i++) d += `L${r1(pts[i].x)} ${r1(pts[i].y)}`; + return close ? d + "Z" : d; +}; + +const polygonPath = (rings, view, tol) => { + let d = ""; + for (const ring of rings) { + const pts = projectFlat(ring, view, tol); + if (pts.length < 3) continue; + const clipped = clipPolygon(pts, -EDGE, -EDGE, MAP_W + EDGE, MAP_H + EDGE); + if (clipped.length >= 3) d += pathOf(clipped, true); + } + return d; +}; + +const linePath = (flat, view, tol) => { + const pts = projectFlat(flat, view, tol); + let d = ""; + let run = []; + const flush = () => { if (run.length >= 2) d += pathOf(run, false); run = []; }; + for (let i = 1; i < pts.length; i++) { + const seg = clipSegment(pts[i - 1], pts[i], -EDGE, -EDGE, MAP_W + EDGE, MAP_H + EDGE); + if (!seg) { flush(); continue; } + if (!run.length || Math.abs(run[run.length - 1].x - seg[0].x) + Math.abs(run[run.length - 1].y - seg[0].y) > 0.05) { flush(); run.push(seg[0]); } + run.push(seg[1]); + } + flush(); + return d; +}; + +const viewBoxLatLng = (view) => { + const nw = fromScreen(view, -EDGE, -EDGE); + const se = fromScreen(view, MAP_W + EDGE, MAP_H + EDGE); + return [nw.lng, se.lat, se.lng, nw.lat]; +}; + +const baseCache = new Map(); +const cacheGet = (key) => baseCache.get(key); +const cacheSet = (key, val) => { + if (baseCache.size >= 64) baseCache.delete(baseCache.keys().next().value); + baseCache.set(key, val); +}; + +const tileZoom = (zoom, thumb) => clamp(Math.floor(zoom) - (thumb ? 2 : 0), 0, TILE_MAX_ZOOM); + +const tileCoverage = (view, opts = {}) => { + const tz = tileZoom(view.zoom, !!opts.thumb); + const size = TILE * Math.pow(2, view.zoom - tz); + const n = Math.pow(2, tz); + const tx0 = Math.max(0, Math.floor(view.x0 / size)), tx1 = Math.min(n - 1, Math.ceil((view.x0 + MAP_W) / size) - 1); + const ty0 = Math.max(0, Math.floor(view.y0 / size)), ty1 = Math.min(n - 1, Math.ceil((view.y0 + MAP_H) / size) - 1); + const out = []; + for (let ty = ty0; ty <= ty1; ty++) { + for (let tx = tx0; tx <= tx1; tx++) out.push({ z: tz, x: tx, y: ty, sx: tx * size - view.x0, sy: ty * size - view.y0, size }); + } + return out; +}; + +const tileHref = (base, t) => `${base}/${t.z}/${t.x}_${t.y}.jpg`; + +const tileBaseOf = (opts) => opts.tileBase || TILE_ENV || `${opts.publicBase || ""}${TILE_PATH}`; + +const renderRaster = (view, opts) => { + const tiles = tileCoverage(view, { thumb: !!opts.thumb }); + if (!tiles.length) return ""; + const base = tileBaseOf(opts); + const clipId = `${opts.id || "map"}-clip`; + let out = ``; + for (const t of tiles) { + out += ``; + } + return out + ""; +}; + +const graticuleStep = (zoom) => zoom <= 2 ? 30 : zoom === 3 ? 15 : zoom === 4 ? 10 : 0; + +const renderGraticule = (view, vb) => { + const step = graticuleStep(view.zoom); + if (!step) return ""; + let d = ""; + for (let lng = Math.ceil(vb[0] / step) * step; lng <= vb[2]; lng += step) { + const x = r1(toScreen(view, 0, lng).x); + d += `M${x} 0L${x} ${MAP_H}`; + } + for (let lat = Math.ceil(vb[1] / step) * step; lat <= vb[3]; lat += step) { + if (Math.abs(lat) >= 85) continue; + const y = r1(toScreen(view, lat, 0).y); + d += `M0 ${y}L${MAP_W} ${y}`; + } + return d ? `` : ""; +}; + +const renderBase = (view, detail) => { + const key = `${view.zoom}|${Math.round(view.x0)}|${Math.round(view.y0)}|${detail}`; + const hit = cacheGet(key); + if (hit) return hit; + const z = view.zoom; + const tol = detail === "low" ? 4 : z <= 3 ? 1.1 : 0.7; + const vb = viewBoxLatLng(view); + const high = z >= 5; + let out = detail === "low" ? "" : renderGraticule(view, vb); + let borders = ""; + for (const c of data.countries(high ? "high" : "low")) { + if (!bboxHit(c.b, vb)) continue; + for (const poly of c.p) { + const d = polygonPath(poly, view, tol); + if (d) borders += ``; + } + } + out += `${borders}`; + if (high && detail !== "low") { + let st = ""; + for (const s of data.states()) { + if (s.z > z || !bboxHit(s.b, vb)) continue; + const d = linePath(s.l, view, tol); + if (d) st += ``; + } + if (st) out += `${st}`; + } + if (z >= CRISP_ZOOM && detail !== "low") { + let co = ""; + for (const c of data.coast()) { + if (!bboxHit(c.b, vb)) continue; + const d = linePath(c.l, view, tol); + if (d) co += ``; + } + for (const l of data.lakes()) { + if (l.z > z || !bboxHit(l.b, vb)) continue; + for (const poly of l.p) { + const d = polygonPath(poly, view, tol); + if (d) co += ``; + } + } + if (co) out += `${co}`; + } + cacheSet(key, out); + return out; +}; + +const textWidth = (s, size) => String(s || "").length * size * 0.58; + +const collides = (boxes, b) => boxes.some((o) => b.x0 < o.x1 && b.x1 > o.x0 && b.y0 < o.y1 && b.y1 > o.y0); + +const renderLabels = (view, boxes) => { + const z = view.zoom; + let out = ""; + if (z >= 3 && z <= 7) { + const list = data.countries(z >= 5 ? "high" : "low").filter((c) => c.ml <= z && c.n).sort((a, b) => a.r - b.r); + for (const c of list) { + const p = toScreen(view, c.ly, c.lx); + if (p.x < 20 || p.x > MAP_W - 20 || p.y < 10 || p.y > MAP_H - 10) continue; + const w = textWidth(c.n, 14); + const b = { x0: p.x - w / 2 - 4, x1: p.x + w / 2 + 4, y0: p.y - 10, y1: p.y + 6 }; + if (collides(boxes, b)) continue; + boxes.push(b); + out += `${esc(c.n)}`; + } + } + if (z >= 2) { + const list = data.places(z >= 5 ? "high" : "low").filter((p) => p.z <= z).sort((a, b) => (b.k - a.k) || (b.p - a.p)); + for (const pl of list) { + const p = toScreen(view, pl.la, pl.lo); + if (p.x < 4 || p.x > MAP_W - 4 || p.y < 4 || p.y > MAP_H - 4) continue; + const w = textWidth(pl.n, 12); + const dot = { x0: p.x - 5, x1: p.x + 5, y0: p.y - 5, y1: p.y + 5 }; + if (collides(boxes, dot)) continue; + const lb = { x0: p.x + 5, x1: p.x + 9 + w, y0: p.y - 8, y1: p.y + 8 }; + const withLabel = !collides(boxes, lb) && lb.x1 <= MAP_W; + boxes.push(dot); + if (withLabel) boxes.push(lb); + const cls = pl.k ? "map-place-dot map-place-capital" : "map-place-dot"; + out += ``; + if (withLabel) out += `${esc(pl.n)}`; + } + } + return out ? `${out}` : ""; +}; + +const niceScale = (view) => { + const mpp = metersPerPx(view); + const target = 150 * mpp; + const pow = Math.pow(10, Math.floor(Math.log10(target))); + const nice = [1, 2, 5, 10].map((n) => n * pow).filter((n) => n <= target).pop() || pow; + const px = nice / mpp; + const label = nice >= 1000 ? `${Math.round(nice / 1000)} km` : `${Math.round(nice)} m`; + return { px, label }; +}; + +const renderScale = (view) => { + const { px, label } = niceScale(view); + const x = 14, y = MAP_H - 16; + return `${esc(label)} · z${view.zoom}`; +}; + +const renderAttribution = () => `Natural Earth`; + +const truncate = (s, n) => { + const line = String(s || "").split(/\r?\n/)[0].trim(); + return line.length > n ? line.slice(0, n - 1) + "…" : line; +}; + +const pinPath = (s) => `M0 0C${-7 * s} ${-9 * s} ${-11 * s} ${-14 * s} ${-11 * s} ${-21 * s}A${11 * s} ${11 * s} 0 1 1 ${11 * s} ${-21 * s}C${11 * s} ${-14 * s} ${7 * s} ${-9 * s} 0 0Z`; + +const clusterPins = (placed, view) => { + if (view.zoom >= MAX_ZOOM) return placed.map((p) => ({ ...p, members: [p] })); + const clusters = []; + for (const p of placed) { + const near = clusters.find((c) => Math.abs(c.x - p.x) <= CLUSTER_PX && Math.abs(c.y - p.y) <= CLUSTER_PX); + if (near) { + near.members.push(p); + near.x = near.members.reduce((s, m) => s + m.x, 0) / near.members.length; + near.y = near.members.reduce((s, m) => s + m.y, 0) / near.members.length; + } else { + clusters.push({ x: p.x, y: p.y, members: [p] }); + } + } + return clusters; +}; + +const renderPins = (view, pins, opts, boxes, hit = {}) => { + const placed = []; + pins.forEach((m, i) => { + const lat = num(m.lat), lng = num(m.lng); + if (lat == null || lng == null) return; + const p = toScreen(view, lat, lng); + if (p.x < -20 || p.x > MAP_W + 20 || p.y < -10 || p.y > MAP_H + 50) return; + placed.push({ ...m, lat, lng, x: p.x, y: p.y, idx: i }); + }); + if (!placed.length) return ""; + const clusters = clusterPins(placed.filter((p) => !p.pick), view); + for (const p of placed.filter((p) => p.pick)) clusters.push({ x: p.x, y: p.y, members: [p], pick: true }); + clusters.sort((a, b) => a.y - b.y); + const quiet = !!opts.popup; + let out = ""; + for (const c of clusters) { + const x = r1(c.x), y = r1(c.y); + if (c.members.length > 1) { + const n = c.members.length; + const center = fromScreen(view, c.x, c.y); + if (c.members.some((m) => m.focus)) hit.focus = { x: c.x, y: c.y, top: 20 }; + let gap = Infinity; + for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) gap = Math.min(gap, Math.max(Math.abs(c.members[i].x - c.members[j].x), Math.abs(c.members[i].y - c.members[j].y))); + const splitZoom = gap > 0 ? view.zoom + Math.ceil(Math.log2((CLUSTER_PX + 1) / gap)) : MAX_ZOOM; + const href = opts.viewHref ? opts.viewHref({ zoom: clamp(Math.max(splitZoom, view.zoom + 1), MIN_ZOOM, MAX_ZOOM), lat: center.lat, lng: center.lng }) : null; + const title = opts.clusterTitle ? `${n} ${opts.clusterTitle}` : String(n); + const glyph = `${esc(title)}${n}`; + out += href ? `${glyph}` : glyph; + boxes.push({ x0: c.x - 20, x1: c.x + 20, y0: c.y - 20, y1: c.y + 20 }); + continue; + } + const m = c.members[0]; + if (m.pick) { + out += ``; + continue; + } + const s = m.main ? 1.25 : 1; + const cls = ["map-pin", m.main ? "map-pin-main" : "", m.focus ? "map-pin-focus" : ""].filter(Boolean).join(" "); + let g = ``; + if (m.title) g += `${esc(m.title)}`; + g += ``; + const silent = quiet && m.focus; + if (silent) hit.focus = { x: c.x, y: c.y, top: 32 * s }; + const label = silent ? "" : truncate(m.label, 28); + if (label) { + const w = textWidth(label, 12) + 12; + const ly = -21 * s - 11 * s - 16; + g += `${esc(label)}`; + boxes.push({ x0: c.x - w / 2, x1: c.x + w / 2, y0: c.y + ly - 12, y1: c.y }); + } else { + boxes.push({ x0: c.x - 12, x1: c.x + 12, y0: c.y - 34, y1: c.y }); + } + if (m.image && !silent) { + const size = 38; + g += ``; + boxes.push({ x0: c.x + 13 * s, x1: c.x + 13 * s + size + 4, y0: c.y - 21 * s - size / 2 - 2, y1: c.y - 21 * s + size / 2 + 2 }); + } + g += ""; + out += m.href ? `${g}` : g; + } + return `${out}`; +}; + +const popupPlacement = (at) => { + const roomAbove = at.y - at.top - POPUP_GAP; + const roomBelow = MAP_H - at.y - POPUP_GAP; + const below = roomAbove < POPUP_H && roomBelow > roomAbove; + const h = Math.min(POPUP_H, Math.max(below ? roomBelow : roomAbove, 60)); + const x = clamp(at.x - POPUP_W / 2, 4, MAP_W - POPUP_W - 4); + const y = below ? at.y + POPUP_GAP : at.y - at.top - POPUP_GAP - h; + return { x, y, w: POPUP_W, h, below, tipX: clamp(at.x, x + 14, x + POPUP_W - 14) }; +}; + +const renderPopup = (at, html) => { + if (!at || !html) return ""; + const p = popupPlacement(at); + const pointer = p.below + ? `M${r1(p.tipX - 8)} ${r1(p.y)}L${r1(p.tipX)} ${r1(p.y - 8)}L${r1(p.tipX + 8)} ${r1(p.y)}Z` + : `M${r1(p.tipX - 8)} ${r1(p.y + p.h)}L${r1(p.tipX)} ${r1(p.y + p.h + 8)}L${r1(p.tipX + 8)} ${r1(p.y + p.h)}Z`; + return `` + + `` + + `
${html}
`; +}; + +const renderGrid = (view, opts) => { + const pick = opts.pick; + const legacy = opts.clickUrl; + if (!pick && !legacy) return ""; + const cells = []; + for (let gy = 0; gy < GRID_ROWS; gy++) { + for (let gx = 0; gx < GRID_COLS; gx++) { + const c = fromScreen(view, (gx + 0.5) / GRID_COLS * MAP_W, (gy + 0.5) / GRID_ROWS * MAP_H); + const val = `${c.lat},${c.lng}`; + if (pick) { + cells.push(``); + } else { + const latParam = opts.latParam || "lat", lngParam = opts.lngParam || "lng"; + cells.push(``); + } + } + } + return `
${cells.join("")}
`; +}; + +const renderControls = (view, opts) => { + const nav = opts.nav; + if (!nav) return ""; + const t = opts.text || {}; + const el = (target, label, cls, title) => { + const value = target ? viewParam(target) : ""; + if (nav.form) { + return ``; + } + return `${label}`; + }; + const pan = (dx, dy) => { + const c = fromScreen(view, MAP_W / 2 + dx * MAP_W * 0.4, MAP_H / 2 + dy * MAP_H * 0.4); + return { zoom: view.zoom, lat: c.lat, lng: c.lng }; + }; + const zoomIn = view.zoom < MAX_ZOOM ? el({ zoom: view.zoom + 1, lat: view.lat, lng: view.lng }, "+", "map-ctrl-in", t.zoomIn || "+") : `+`; + const zoomOut = view.zoom > MIN_ZOOM ? el({ zoom: view.zoom - 1, lat: view.lat, lng: view.lng }, "−", "map-ctrl-out", t.zoomOut || "−") : `−`; + const fit = el(null, "⌖", "map-ctrl-fit", t.fit || "⌖"); + return `
${zoomIn}${zoomOut}
` + + `
` + + `${el(pan(0, -1), "↑", "map-ctrl-up", t.pan || "↑")}` + + `${el(pan(-1, 0), "←", "map-ctrl-left", t.pan || "←")}${fit}${el(pan(1, 0), "→", "map-ctrl-right", t.pan || "→")}` + + `${el(pan(0, 1), "↓", "map-ctrl-down", t.pan || "↓")}` + + `
`; +}; + +const renderMapHtml = (view, pins, opts = {}) => { + const detail = opts.thumb ? "low" : "normal"; + const boxes = []; + const pinList = Array.isArray(pins) ? pins : []; + const grid = renderGrid(view, opts); + const hit = {}; + const pinsSvg = renderPins(view, pinList, opts, boxes, hit) + renderPopup(hit.focus, opts.popup); + const labels = opts.thumb ? "" : renderLabels(view, boxes); + const extras = opts.thumb ? "" : renderScale(view) + renderAttribution(); + const base = `` + renderRaster(view, opts) + renderBase(view, detail); + const svgOpen = ``; + let html = `
`; + if (grid || opts.nav) { + html += svgOpen + base + labels + extras + ""; + html += grid; + html += `${pinsSvg}`; + } else { + html += svgOpen + base + labels + pinsSvg + extras + ""; + } + html += renderControls(view, opts); + html += "
"; + return html; +}; + +const renderMapWithPins = (markers, mainIdx) => { + const pins = (Array.isArray(markers) ? markers : []).map((m, i) => ({ ...m, main: i === (mainIdx || 0) })); + const fit = fitView(pins); + return renderMapHtml(makeView(fit.lat, fit.lng, fit.zoom), pins, {}); }; const renderZoomedMapWithPins = (centerLat, centerLng, zoom, markers, mainIdx) => { - const maxZ = getMaxTileZoom(); - if (!maxZ || zoom <= 2) return renderMapWithPins(markers, mainIdx); - - const effectiveZ = Math.min(zoom, maxZ); - const scale = Math.pow(2, zoom - effectiveZ); - const n = Math.pow(2, effectiveZ); - const tileSize = 256; - const worldPx = n * tileSize; - - const latRad = centerLat * Math.PI / 180; - const cx = (centerLng + 180) / 360 * worldPx; - const cy = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * worldPx; - - const vw = MAP_W / scale; - const vh = MAP_H / scale; - const x0 = cx - vw / 2; - const y0 = cy - vh / 2; - - const pinData = (Array.isArray(markers) ? markers : []) - .filter((m) => m && typeof m.lat === "number" && typeof m.lng === "number") - .map((m, i) => { - const latR = m.lat * Math.PI / 180; - const wx = (m.lng + 180) / 360 * worldPx; - const wy = (1 - Math.log(Math.tan(latR) + 1 / Math.cos(latR)) / Math.PI) / 2 * worldPx; - return { px: (wx - x0) * scale, py: (wy - y0) * scale, main: i === (mainIdx || 0) }; - }); - - const hashInput = `z${zoom}_${Math.round(centerLat * 100)}_${Math.round(centerLng * 100)}_` + pinData.map(p => `${Math.round(p.px)},${Math.round(p.py)},${p.main}`).join(";"); - const hash = crypto.createHash("md5").update(hashInput).digest("hex").slice(0, 12); - const outFile = path.join(CACHE_DIR, `map_${hash}.png`); - - if (fs.existsSync(outFile)) return `map_${hash}.png`; - - const txMin = Math.max(0, Math.floor(x0 / tileSize)); - const txMax = Math.min(n - 1, Math.floor((x0 + vw) / tileSize)); - const tyMin = Math.max(0, Math.floor(y0 / tileSize)); - const tyMax = Math.min(n - 1, Math.floor((y0 + vh) / tileSize)); - - const tiles = []; - for (let tx = txMin; tx <= txMax; tx++) { - for (let ty = tyMin; ty <= tyMax; ty++) { - const tp = path.join(TILES_DIR, String(effectiveZ), `${tx}_${ty}.png`); - tiles.push({ tx, ty, tp, exists: fs.existsSync(tp) }); - } - } - - const script = ` -from PIL import Image, ImageDraw -import sys, json, os - -args = json.loads(sys.argv[1]) -out_file = sys.argv[2] - -tile_size = 256 -tx_min = args['txMin'] -ty_min = args['tyMin'] -tx_max = args['txMax'] -ty_max = args['tyMax'] -x0 = args['x0'] -y0 = args['y0'] -vw = args['vw'] -vh = args['vh'] -scale = args['scale'] -pins = args['pins'] -tiles = args['tiles'] - -canvas_w = (tx_max - tx_min + 1) * tile_size -canvas_h = (ty_max - ty_min + 1) * tile_size -canvas = Image.new('RGB', (canvas_w, canvas_h), (170, 211, 223)) - -for t in tiles: - if t['exists']: - try: - tile = Image.open(t['tp']).convert('RGB') - canvas.paste(tile, ((t['tx'] - tx_min) * tile_size, (t['ty'] - ty_min) * tile_size)) - except: - pass - -crop_x = x0 - tx_min * tile_size -crop_y = y0 - ty_min * tile_size -cropped = canvas.crop((int(crop_x), int(crop_y), int(crop_x + vw), int(crop_y + vh))) -result = cropped.resize((1024, 1024), Image.LANCZOS) - -draw = ImageDraw.Draw(result) -def draw_pin(draw, x, y, main): - sw = 7 if main else 5 - sh = 34 if main else 26 - head = 11 if main else 8 - clr = '#e74c3c' if main else '#3498db' - dark = '#c0392b' if main else '#2980b9' - cy = y - sh - draw.polygon([(x, y + 2), (x - sw - 1, cy + sw), (x + sw + 1, cy + sw)], fill='white') - draw.polygon([(x, y), (x - sw, cy + sw), (x + sw, cy + sw)], fill=clr) - draw.ellipse([x - head - 2, cy - head - 2, x + head + 2, cy + head + 2], fill='white') - draw.ellipse([x - head, cy - head, x + head, cy + head], fill=clr, outline=dark, width=2) - draw.ellipse([x - head // 3, cy - head // 3, x + head // 3, cy + head // 3], fill='white') - -for p in pins: - px, py, main = p['px'], p['py'], p.get('main', False) - if -40 <= px <= 1064 and -40 <= py <= 1064: - draw_pin(draw, px, py, main) - -result.save(out_file, optimize=True) -`; - - try { - fs.mkdirSync(CACHE_DIR, { recursive: true }); - execFileSync("python3", [ - "-c", script, - JSON.stringify({ txMin, tyMin, txMax, tyMax, x0, y0, vw, vh, scale, pins: pinData, tiles }), - outFile - ], { timeout: 15000 }); - } catch (e) { - return renderMapWithPins(markers, mainIdx); - } - - return `map_${hash}.png`; + const pins = (Array.isArray(markers) ? markers : []).map((m, i) => ({ ...m, main: i === (mainIdx || 0) })); + return renderMapHtml(makeView(centerLat, centerLng, zoom), pins, {}); }; -module.exports = { renderMapWithPins, renderZoomedMapWithPins, getViewportBounds, getMaxTileZoom, latLngToPx, pxToLatLng, MAP_W, MAP_H }; +module.exports = { + MAP_W, MAP_H, MIN_ZOOM, MAX_ZOOM, TILE_MAX_ZOOM, TILE_PATH, + project, unproject, latLngToPx, pxToLatLng, makeView, toScreen, fromScreen, fitView, resolveView, + parseView, viewParam, parsePick, getViewportBounds, metersPerPx, clusterPins, niceScale, tileCoverage, popupPlacement, + renderMapHtml, renderMapWithPins, renderZoomedMapWithPins +}; diff --git a/src/models/activity_model.js b/src/models/activity_model.js index b70963a..b4770b6 100644 --- a/src/models/activity_model.js +++ b/src/models/activity_model.js @@ -1,8 +1,10 @@ const pull = require('../server/node_modules/pull-stream'); +const longText = require('../backend/long_text'); const ssbRef = require('../server/node_modules/ssb-ref'); const { getConfig } = require('../configs/config-manager.js'); const { buildVoteTally } = require('../backend/vote_tally'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const safeFeedId = (v) => { @@ -57,6 +59,8 @@ function inferType(c = {}) { } const HIDDEN_ENVELOPE_TYPES = new Set([ + 'textChunk', + 'aiExchange', 'tribe-keys-distrib', 'tribe-keys', 'tribe-invite-msg', @@ -67,7 +71,6 @@ const HIDDEN_ENVELOPE_TYPES = new Set([ 'larpJoinHouse', 'larpLeaveLarp', 'larpTestAttempt', - 'larpHousePost', 'larpHouseInvite', 'larpHouseInviteRedeem', 'larpHouseTribeAnchor', @@ -75,19 +78,29 @@ const HIDDEN_ENVELOPE_TYPES = new Set([ 'larpAutoInvite', 'karmaScore', 'pubAvailability', + 'pam', 'calendarReminderSent', 'taskReminderSent', + 'ubiClaim', 'ubiClaimResult', + 'ubiRefuse', 'ubiAllocation', + 'bankClaim', 'courts-key' ]); -module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel }) => { +module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel, larpModel }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb }; let _feedCache = null; let _feedCacheInflight = null; + const logTip = async () => { + try { + const ssbClient = await openSsb(); + return await require('./typed_log').logTip(ssbClient); + } catch (_) { return null; } + }; const FEED_CACHE_MS = 15 * 1000; const buildAccessibleTribeIds = async () => { @@ -95,14 +108,14 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } if (!tribesModel) return set; try { const list = await tribesModel.listAll(); - for (const t of list) { - if (!t || !t.id) continue; + await Promise.all(list.map(async (t) => { + if (!t || !t.id) return; set.add(t.id); try { const chain = await tribesModel.getChainIds(t.id); for (const cid of chain) set.add(cid); } catch (_) {} - } + })); } catch (_) {} return set; }; @@ -143,9 +156,6 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } return t.length > max ? t.slice(0, max - 1) + '…' : t; }; - // Builds self-contained chatThread items (title + recent messages embedded) for - // general (non-tribe) OPEN chats that have activity. Emitted as one item per chat - // with ts = latest message, so it survives the recent/all/mine filters intact. const CHAT_THREAD_LIMIT = 6; const buildChatThreads = async (ssbClient, idToAction, rootOf) => { const replacedChatIds = new Set(); @@ -160,28 +170,31 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } const root = rootOf(a.id); const isTip = !replacedChatIds.has(a.id); const prev = stateByRoot.get(root); - // Resolve the chain tip by replaces (a non-replaced action wins), breaking ts - // ties correctly — the mock and real SSB can stamp equal timestamps. if (prev && !((isTip && !prev.isTip) || (isTip === prev.isTip && (a.ts || 0) >= prev.ts))) continue; const cc = a.content || {}; - stateByRoot.set(root, { ts: a.ts || 0, isTip, status: String(cc.status || '').toUpperCase(), tribeId: cc.tribeId || null, title: String(cc.title || ''), description: String(cc.description || ''), members: Array.isArray(cc.members) ? cc.members.length : 0 }); + stateByRoot.set(root, { ts: a.ts || 0, isTip, owner: cc.author || a.author || null, status: String(cc.status || '').toUpperCase(), tribeId: cc.tribeId || null, title: String(cc.title || ''), description: String(cc.description || ''), members: Array.isArray(cc.members) ? cc.members.length : 0 }); } const msgsByRoot = new Map(); for (const a of idToAction.values()) { - if (a.type !== 'chatMessage') continue; + const isPoll = a.type === 'poll'; + if (a.type !== 'chatMessage' && !isPoll) continue; const c = a.content || {}; - if (c.tribeId || c.encryptedText) continue; + if (c.tribeId || c.encryptedText || c.encryptedQuestion) continue; const root = c.chatId; - if (!root || typeof c.text !== 'string' || !c.text) continue; + if (!root) continue; + if (isPoll) { + if (!(typeof c.question === 'string' && c.question.trim())) continue; + } else if (!(typeof c.text === 'string' && c.text) && !c.image) continue; if (!msgsByRoot.has(root)) msgsByRoot.set(root, []); - msgsByRoot.get(root).push(a); + msgsByRoot.get(root).push(isPoll ? { ...a, content: { chatId: root, text: c.question.trim(), poll: true } } : a); } - for (const root of msgsByRoot.keys()) { - if (stateByRoot.has(root)) continue; - const val = await getMsg(ssbClient, root); + const missingRoots = [...msgsByRoot.keys()].filter(root => !stateByRoot.has(root)); + const rootMsgs = new Map(await Promise.all(missingRoots.map(async root => [root, await getMsg(ssbClient, root)]))); + for (const root of missingRoots) { + const val = rootMsgs.get(root); const cc = val && val.content; if (cc && typeof cc === 'object' && cc.type === 'chat') { - stateByRoot.set(root, { ts: 0, status: String(cc.status || '').toUpperCase(), tribeId: cc.tribeId || null, title: String(cc.title || ''), description: String(cc.description || ''), members: Array.isArray(cc.members) ? cc.members.length : 0 }); + stateByRoot.set(root, { ts: 0, owner: cc.author || val.author || null, status: String(cc.status || '').toUpperCase(), tribeId: cc.tribeId || null, title: String(cc.title || ''), description: String(cc.description || ''), members: Array.isArray(cc.members) ? cc.members.length : 0 }); } else { stateByRoot.set(root, null); } @@ -200,11 +213,29 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } content: { type: 'chatThread', chatRoot: root, + chatAuthor: info.owner || null, title: info.title || '', description: info.description || '', - members: info.members || 0, + members: Math.max(info.members || 0, new Set(asc.map(m => m.author)).size), messageCount: asc.length, - replies: asc.slice(-CHAT_THREAD_LIMIT).map(m => ({ id: m.id, author: m.author, ts: m.ts || 0, text: (m.content && m.content.text) || '' })) + replies: (() => { + const byId = new Map(asc.map(m => [m.id, m])); + return asc.slice(-CHAT_THREAD_LIMIT).map(m => { + const quotedId = (m.content && typeof m.content.replyTo === 'string') ? m.content.replyTo : null; + const quoted = quotedId ? byId.get(quotedId) : null; + return { + id: m.id, + author: m.author, + ts: m.ts || 0, + text: (m.content && m.content.text) || '', + poll: !!(m.content && m.content.poll), + image: (m.content && m.content.image) || null, + mimeType: (m.content && m.content.mimeType) || '', + replyTo: quotedId, + reply: quoted ? { id: quoted.id, author: quoted.author, text: String((quoted.content && quoted.content.text) || '').slice(0, 140) } : null + }; + }); + })() } }); } @@ -224,16 +255,18 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } if (entry && now - entry.ts < FEED_CACHE_MS) return entry.value; if (!_feedCacheInflight) _feedCacheInflight = new Map(); if (_feedCacheInflight.has(cacheKey)) return _feedCacheInflight.get(cacheKey); + const tipNow = await logTip(); + if (entry && tipNow && entry.tip === tipNow) { entry.ts = now; return entry.value; } const promise = (async () => { const ssbClient = await openSsb(); const userId = ssbClient.id; - const results = await new Promise((resolve, reject) => { + const results = (await new Promise((resolve, reject) => { pull( - ssbClient.createLogStream({ reverse: true, limit: logLimit }), + ssbClient.createLogStream({}), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) ); - }); + })).reverse(); if (typeof ssbClient.createUserStream === 'function') { const ownResults = await new Promise((resolve) => { @@ -249,6 +282,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } const tombstoned = buildValidatedTombstoneSet(results); + const chunkLookup = longText.lookupIn(longText.indexChunks(results)); const parentOf = new Map(); const idToAction = new Map(); const rawById = new Map(); @@ -270,6 +304,20 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } const fpIdx = tribeCrypto ? tribeCrypto.buildFingerprintIndex() : null; const accessibleTribeIds = await buildAccessibleTribeIds(); + let larpWall = null; + if (larpModel && results.some(m => m && m.value && m.value.content && m.value.content.type === 'larpHousePost')) { + try { + const s = await openSsb(); + larpWall = { + mine: await larpModel.getUserHouse(s.id).catch(() => null), + governing: typeof larpModel.getGoverningHouseKey === 'function' ? larpModel.getGoverningHouseKey() : null, + members: await larpModel.listAllMemberships().catch(() => new Map()) + }; + } catch (_) { larpWall = null; } + } + const larpPostVisible = (author, house) => !!larpWall && !!house + && (larpWall.members.get(author) || 'academia') === house + && (house === 'academia' || house === larpWall.governing || house === larpWall.mine); for (const msg of results) { const k = msg.key; @@ -277,7 +325,9 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } let c = v?.content; if (!c) continue; if (typeof c === 'string' && c.endsWith('.box')) continue; + if (c.private === true) continue; if (c.type && HIDDEN_ENVELOPE_TYPES.has(c.type)) continue; + if (c.type === 'larpHousePost' && !larpPostVisible(v.author, String(c.house || '').toLowerCase())) continue; if (typeof c.type === 'string' && /e2ee/i.test(c.type)) continue; if (c.type === 'contact') continue; if (tribeCrypto && tribeCrypto.isTribeMsg(c)) { @@ -307,6 +357,8 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } catch (_) {} } if (!isContentSane(c)) continue; + if ((c.type === 'post' || c.type === 'forum') && longText.hasChunks(c)) c = longText.resolveField(c, 'text', v?.author, chunkLookup); + if (c.type === 'about' && !c.name && !c.description && !c.image) continue; const ts = v?.timestamp || Number(c?.timestamp || 0) || (c?.updatedAt ? Date.parse(c.updatedAt) : 0) || 0; idToAction.set(k, { id: k, author: v?.author, ts, type: inferType(c), content: c }); rawById.set(k, msg); @@ -315,17 +367,23 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } const forgedParentOf = new Map(); const forgedFeedContent = new Map(); + const vg = versionGroups(Array.from(idToAction.values()).map(a => versionEntry({ key: a.id, value: { ...(rawById.get(a.id) || {}).value, content: a.content } }))); for (const [child, parent] of Array.from(parentOf.entries())) { const ca = idToAction.get(child); - const pa = idToAction.get(parent); - if (!pa) { parentOf.delete(child); continue; } - if (!ca || String(ca.author) !== String(pa.author)) { + if (vg.isOrphan(child)) { + parentOf.delete(child); + if (vg.isForeignCopy(child)) { idToAction.delete(child); rawById.delete(child); } + continue; + } + if (!ca || vg.isForeign(child)) { forgedParentOf.set(child, parent); if (ca && ca.type === 'feed') forgedFeedContent.set(child, ca.content || {}); parentOf.delete(child); idToAction.delete(child); rawById.delete(child); + continue; } + if (vg.isForeign(parent)) parentOf.set(child, vg.rootOf(child)); } const replacedIds = new Set(parentOf.values()); @@ -381,6 +439,10 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } const fetchedTargetCache = new Map(); + const missingTargets = [...new Set([...idToAction.values()].filter(a => a.type === 'spread').map(a => (a.content || {}).vote?.link || '').filter(link => link && !rawById.get(link)))]; + for (const [link, got] of await Promise.all(missingTargets.map(async link => [link, await getMsg(ssbClient, link)]))) { + fetchedTargetCache.set(link, got ? { key: link, value: got } : null); + } for (const a of idToAction.values()) { if (a.type !== 'spread') continue; @@ -540,7 +602,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } const v = msg && msg.value; const c = v && v.content; if (!c || typeof c !== 'object') continue; - const isOpinion = c.type === 'feedOpinion' && typeof c.target === 'string'; + const isOpinion = typeof c.type === 'string' && /Opinion$/.test(c.type) && c.type !== 'pollOpinion' && typeof c.target === 'string'; const isVoteAction = c.type === 'feed-action' && c.action === 'vote' && (typeof c.root === 'string' || typeof c.target === 'string'); if (!isOpinion && !isVoteAction) continue; const target = isOpinion ? c.target : String(c.root || c.target); @@ -570,6 +632,8 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } const latest = []; + const docUrls = [...new Set([...idToAction.values()].filter(a => a.type === 'document').map(a => (a.content || {}).url))]; + const docBlobOk = new Map(await Promise.all(docUrls.map(async url => [url, await hasBlob(ssbClient, url)]))); for (const a of idToAction.values()) { if (tombstoned.has(a.id)) continue; if (a.type === 'tribe' && parentOf.has(a.id)) continue; @@ -580,11 +644,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } if (c.key && tombstoned.has(c.key)) continue; if (c.branch && tombstoned.has(c.branch)) continue; if (c.target && tombstoned.has(c.target)) continue; - if (a.type === 'document') { - const url = c.url; - const ok = await hasBlob(ssbClient, url); - if (!ok) continue; - } + if (a.type === 'document' && !docBlobOk.get(c.url)) continue; if (a.type === 'forum' && c.root) { const rootId = typeof c.root === 'string' ? c.root : (c.root?.key || c.root?.id || ''); const rootAction = idToAction.get(rootId); @@ -600,6 +660,19 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } a.pollVoters = tally ? tally.voters.size : 0; } let content = voteAgg ? { ...c, ...voteAgg } : a.content; + if (a.type !== 'feed' && a.type !== 'votes' && (feedOpinionsByRoot.get(actionRoot) || []).length) { + const base = a.content || {}; + const opinions = { ...(base.opinions || {}) }; + const voters = Array.isArray(base.opinions_inhabitants) ? base.opinions_inhabitants.slice() : []; + const voterSet = new Set(voters); + for (const op of feedOpinionsByRoot.get(actionRoot)) { + if (!op.author || voterSet.has(op.author)) continue; + voterSet.add(op.author); + voters.push(op.author); + if (op.category) opinions[op.category] = (Number(opinions[op.category]) || 0) + 1; + } + content = { ...content, opinions, opinions_inhabitants: voters }; + } if (a.type === 'feed') { const base = a.content || {}; const opinions = { ...(base.opinions || {}) }; @@ -629,6 +702,8 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } let deduped = latest.filter(a => !a.tipId || a.tipId === a.id || (a.type === 'tribe' && !parentOf.has(a.id))); const mediaTypes = new Set(['image','video','audio','document','bookmark','map']); + const textTypes = new Set(['feed', 'post']); + const REPEAT_WINDOW_MS = 10 * 60 * 1000; const perAuthorUnique = new Set(); const byKey = new Map(); const norm = s => String(s || '').trim().toLowerCase(); @@ -668,6 +743,19 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } else if (prevHasImage === newHasImage && effTs > prev.__effTs) { byKey.set(key, { ...a, __effTs: effTs, __hasImage: newHasImage }); } + } else if (textTypes.has(a.type)) { + const t = norm(c.text); + const key = t ? `${a.type}:${a.author}:${t}` : null; + const prev = key ? byKey.get(key) : null; + if (!key) { + byKey.set(`id:${a.id}`, { ...a, __effTs: effTs }); + } else if (!prev) { + byKey.set(key, { ...a, __effTs: effTs }); + } else if (Math.abs(effTs - prev.__effTs) <= REPEAT_WINDOW_MS) { + if (effTs > prev.__effTs) byKey.set(key, { ...a, __effTs: effTs }); + } else { + byKey.set(`id:${a.id}`, { ...a, __effTs: effTs }); + } } else if (a.type === 'tribe') { const t = norm(c.title); if (t) { @@ -710,7 +798,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } } const tribeInternalTypes = new Set(['tribe-content', 'tribeParliamentCandidature', 'tribeParliamentTerm', 'tribeParliamentProposal', 'tribeParliamentRule', 'tribeParliamentLaw', 'tribeParliamentRevocation']); - const hiddenTypes = new Set(['padEntry', 'chatMessage', 'calendarDate', 'calendarNote', 'calendarReminderSent', 'taskReminderSent', 'feed-action', 'pubBalance', 'pubAvailability', 'log', 'logPublic', 'gameScore', 'pollVote', 'pollClose', 'pollOpinion', 'curriculum']); + const hiddenTypes = new Set(['wallet', 'bankWallet', 'padEntry', 'roomMember', 'chatMessage', 'calendarDate', 'calendarNote', 'calendarReminderSent', 'taskReminderSent', 'feed-action', 'pubBalance', 'pubAvailability', 'log', 'logPublic', 'gameScore', 'pollVote', 'pollClose', 'pollOpinion', 'curriculum', 'schoolLesson', 'schoolEnroll', 'schoolCertificate', 'schoolProgress', 'schoolExam', 'schoolExamResult', 'school-invite', 'schoolExamQuestion', 'schoolLessonMedia']); const chatThreadItems = await buildChatThreads(ssbClient, idToAction, rootOf, deduped); deduped = deduped.concat(chatThreadItems); const isAllowedTribeActivity = (a) => { @@ -732,14 +820,18 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } const c = a.content || {}; if (c.encryptedPayload) return false; if (a.type === 'pad' && c.status !== 'OPEN') return false; + if (a.type === 'room' && (c.status !== 'OPEN' || c.encrypted === true)) return false; if (a.type === 'chat' && c.status !== 'OPEN') return false; if (a.type === 'calendar' && c.status !== 'OPEN') return false; if (a.type === 'event' && String(c.isPublic || '').toLowerCase() === 'private' && c.organizer !== userId && !(Array.isArray(c.attendees) && c.attendees.includes(userId))) return false; if (a.type === 'task' && String(c.isPublic || '').toUpperCase() === 'PRIVATE' && a.author !== userId && !(Array.isArray(c.assignees) && c.assignees.includes(userId))) return false; if (a.type === 'forum' && c.isPrivate === true && a.author !== userId) return false; if (a.type === 'job' && String(c.visibility || '').toUpperCase() === 'HIDDEN' && a.author !== userId && !(Array.isArray(c.subscribers) && c.subscribers.includes(userId))) return false; + if (a.type === 'schoolCourse' && c.encryptedPayload) return false; + if (a.type === 'schoolCourse' && String(c.visibility || '').toUpperCase() === 'INVITE' && a.author !== userId && !(Array.isArray(c.invited) && c.invited.includes(userId))) return false; if (a.type === 'housing' && String(c.visibility || '').toUpperCase() === 'HIDDEN' && a.author !== userId) return false; if (a.type === 'market' && String(c.visibility || '').toUpperCase() === 'HIDDEN' && c.seller !== userId) return false; + if (a.type === 'market' && c.shopProductId) return false; if (a.type === 'shop' && String(c.visibility || '').toUpperCase() === 'CLOSED' && a.author !== userId) return false; if (a.type === 'curriculum' && String(c.visibility || '').toUpperCase() === 'HIDDEN' && a.author !== userId) return false; if (a.type === 'shopProduct' && c.shopVisibility && String(c.shopVisibility).toUpperCase() === 'CLOSED' && a.author !== userId) return false; @@ -774,7 +866,6 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } .map(a => ({ ...a, authorActions: perAuthor.get(a.author) || 0 })) .sort((x, y) => (y.authorActions - x.authorActions) || ((y.ts || 0) - (x.ts || 0))); } - else if (filter === 'banking') out = deduped.filter(a => a.type === 'bankWallet' || a.type === 'bankClaim' || a.type === 'ubiClaim'); else if (filter === 'tribe') out = deduped.filter(a => a.type === 'tribe' || String(a.type || '').startsWith('tribe')); else if (filter === 'spread') out = deduped.filter(a => a.type === 'spread'); else if (filter === 'parliament') @@ -793,6 +884,13 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } else if (filter === 'industry') out = deduped.filter(a => ['industry', 'industryBuild', 'industryBlueprint', 'industryAllocation'].includes(a.type) && isVisible(a)); else if (filter === 'pad') out = deduped.filter(a => a.type === 'pad' && (a.content || {}).status === 'OPEN'); + else if (filter === 'room') out = deduped.filter(a => a.type === 'room' && (a.content || {}).status === 'OPEN' && (a.content || {}).encrypted !== true); + else if (filter === 'wiki') out = deduped.filter(a => a.type === 'wikiPage' && isVisible(a)); + else if (filter === 'emergency') out = deduped.filter(a => (a.type === 'emergency' || a.type === 'emergencyUpdate') && isVisible(a)); + else if (filter === 'campaign') out = deduped.filter(a => (a.type === 'campaign' || a.type === 'campaignUpdate') && isVisible(a)); + else if (filter === 'mailing') out = deduped.filter(a => a.type === 'mailingList' && isVisible(a)); + else if (filter === 'logistics') out = deduped.filter(a => a.type === 'logisticsRoute' && isVisible(a)); + else if (filter === 'podcast') out = deduped.filter(a => (a.type === 'podcast' || a.type === 'podcastEpisode') && isVisible(a)); else if (filter === 'chat') out = deduped.filter(a => (a.type === 'chat' || a.type === 'chatThread') && isAllowedTribeActivity(a) && isVisible(a)); else if (filter === 'calendar') out = deduped.filter(a => a.type === 'calendar' && (a.content || {}).status === 'OPEN'); else if (filter === 'transfer') out = deduped.filter(a => a.type === 'transfer' && isVisible(a)); @@ -804,7 +902,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel } _feedCacheInflight.set(cacheKey, promise); try { const value = await promise; - _feedCache.set(cacheKey, { value, ts: Date.now() }); + _feedCache.set(cacheKey, { value, ts: Date.now(), tip: tipNow }); return value; } finally { _feedCacheInflight.delete(cacheKey); diff --git a/src/models/agenda_model.js b/src/models/agenda_model.js index 9090b89..e1b3f67 100644 --- a/src/models/agenda_model.js +++ b/src/models/agenda_model.js @@ -3,9 +3,11 @@ const path = require('path'); const pull = require('../server/node_modules/pull-stream'); const moment = require('../server/node_modules/moment'); -const agendaConfigPath = path.join(__dirname, '../configs/agenda-config.json'); +const agendaConfigPath = require('../configs/state-manager').statePath('agenda-config.json'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped } = require('./typed_log'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; function readAgendaConfig() { @@ -19,137 +21,117 @@ function writeAgendaConfig(cfg) { fs.writeFileSync(agendaConfigPath, JSON.stringify(cfg, null, 2)); } -module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel, jobsModel, projectsModel, industryModel, housingModel }) => { +module.exports = ({ cooler, campaignsModel = null, logisticsModel = null, calendarsModel, eventsModel, tasksModel, marketModel, jobsModel, projectsModel, industryModel, housingModel, schoolModel }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; const STATUS_ORDER = ['FOR SALE', 'OPEN', 'RESERVED', 'CLOSED', 'SOLD']; const sIdx = s => STATUS_ORDER.indexOf(String(s || '').toUpperCase()); - const fetchItems = (targetType) => - new Promise((resolve, reject) => { - openSsb().then((ssbClient) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => { - if (err) return reject(err); + const fetchItems = async (targetType) => { + const ssbClient = await openSsb(); + const msgs = await readTyped(ssbClient, [targetType, 'tombstone'], { limit: logLimit }); - const tomb = buildValidatedTombstoneSet(msgs); - const nodes = new Map(); - const parent = new Map(); - const child = new Map(); + const tomb = buildValidatedTombstoneSet(msgs); + const nodes = new Map(); + const versions = []; - for (const m of msgs) { - const k = m.key; - const v = m.value; - const c = v?.content; - if (!c) continue; - if (c.type === 'tombstone') continue; - if (c.type !== targetType) continue; - if (c.encryptedPayload) continue; - nodes.set(k, { key: k, ts: v.timestamp || 0, author: v.author, content: c }); - if (c.replaces) parent.set(k, c.replaces); - } + for (const m of msgs) { + const k = m.key; + const v = m.value; + const c = v?.content; + if (!c) continue; + if (c.type === 'tombstone') continue; + if (c.type !== targetType) continue; + if (c.encryptedPayload) continue; + nodes.set(k, { key: k, ts: v.timestamp || 0, author: v.author, content: c }); + versions.push(versionEntry(m)); + } - for (const [k, p] of Array.from(parent.entries())) { - const cn = nodes.get(k); - const pn = nodes.get(p); - if (!pn) { parent.delete(k); continue; } - if (!cn || String(cn.author) !== String(pn.author)) { parent.delete(k); nodes.delete(k); } - } - for (const [k, p] of parent.entries()) child.set(p, k); + const vg = versionGroups(versions); + const groups = new Map(); + for (const r of vg.roots) groups.set(r, new Set(vg.groups.get(r).filter(id => !vg.isForeign(id)))); - const rootOf = (id) => { let cur = id; while (parent.has(cur) && nodes.has(parent.get(cur))) cur = parent.get(cur); return cur; }; + const statusOrder = ['FOR SALE', 'OPEN', 'RESERVED', 'CLOSED', 'SOLD']; + const sIdx = s => statusOrder.indexOf(String(s || '').toUpperCase()); - const groups = new Map(); - for (const id of nodes.keys()) { - const r = rootOf(id); - if (!groups.has(r)) groups.set(r, new Set()); - groups.get(r).add(id); - } + const out = []; - const statusOrder = ['FOR SALE', 'OPEN', 'RESERVED', 'CLOSED', 'SOLD']; - const sIdx = s => statusOrder.indexOf(String(s || '').toUpperCase()); + for (const [root, ids] of groups.entries()) { + const items = Array.from(ids).map(id => nodes.get(id)).filter(n => n && !tomb.has(n.key)); + if (!items.length) continue; - const out = []; + const tip = nodes.get(vg.tipOf(root)); + if (tomb.has(tip.key)) continue; - for (const [root, ids] of groups.entries()) { - const items = Array.from(ids).map(id => nodes.get(id)).filter(n => n && !tomb.has(n.key)); - if (!items.length) continue; + if (targetType === 'market') { + let chosen = items[0]; + for (const n of items) { + const a = sIdx(n.content.status); + const b = sIdx(chosen.content.status); + if (a > b || (a === b && n.ts > chosen.ts)) chosen = n; + } + const c = chosen.content; + let status = c.status; + if (c.deadline) { + const dl = moment(c.deadline); + if (dl.isValid() && dl.isBefore(moment()) && String(status).toUpperCase() !== 'SOLD') status = 'DISCARDED'; + } + if (status === 'FOR SALE' && (c.stock || 0) === 0) continue; - let tipId = Array.from(ids).find(id => !child.has(id)); - let tip = tipId ? nodes.get(tipId) : items.reduce((a, b) => a.ts > b.ts ? a : b); + out.push({ + ...c, + status, + author: chosen.author, + id: chosen.key, + tipId: chosen.key, + createdAt: c.createdAt || chosen.ts + }); + continue; + } - if (targetType === 'market') { - let chosen = items[0]; - for (const n of items) { - const a = sIdx(n.content.status); - const b = sIdx(chosen.content.status); - if (a > b || (a === b && n.ts > chosen.ts)) chosen = n; - } - const c = chosen.content; - let status = c.status; - if (c.deadline) { - const dl = moment(c.deadline); - if (dl.isValid() && dl.isBefore(moment()) && String(status).toUpperCase() !== 'SOLD') status = 'DISCARDED'; - } - if (status === 'FOR SALE' && (c.stock || 0) === 0) continue; + if (targetType === 'job') { + const latest = items.sort((a, b) => b.ts - a.ts)[0]; + const withSubsNode = items + .filter(n => Array.isArray(n.content.subscribers)) + .sort((a, b) => b.ts - a.ts)[0]; + const subscribers = withSubsNode ? withSubsNode.content.subscribers : []; + const latestWithStatus = items + .filter(n => typeof n.content.status !== 'undefined') + .sort((a, b) => b.ts - a.ts)[0]; + const resolvedStatus = latestWithStatus + ? latestWithStatus.content.status + : latest.content.status; - out.push({ - ...c, - status, - author: chosen.author, - id: chosen.key, - tipId: chosen.key, - createdAt: c.createdAt || chosen.ts - }); - continue; - } + const c = { ...latest.content, status: resolvedStatus, subscribers }; - if (targetType === 'job') { - const latest = items.sort((a, b) => b.ts - a.ts)[0]; - const withSubsNode = items - .filter(n => Array.isArray(n.content.subscribers)) - .sort((a, b) => b.ts - a.ts)[0]; - const subscribers = withSubsNode ? withSubsNode.content.subscribers : []; - const latestWithStatus = items - .filter(n => typeof n.content.status !== 'undefined') - .sort((a, b) => b.ts - a.ts)[0]; - const resolvedStatus = latestWithStatus - ? latestWithStatus.content.status - : latest.content.status; + out.push({ + ...c, + author: latest.author, + id: latest.key, + tipId: latest.key, + createdAt: c.createdAt || latest.ts + }); + continue; + } - const c = { ...latest.content, status: resolvedStatus, subscribers }; + out.push({ + ...tip.content, + author: tip.author, + id: tip.key, + tipId: tip.key, + createdAt: tip.content.createdAt || tip.ts + }); + } - out.push({ - ...c, - author: latest.author, - id: latest.key, - tipId: latest.key, - createdAt: c.createdAt || latest.ts - }); - continue; - } - - out.push({ - ...tip.content, - author: tip.author, - id: tip.key, - tipId: tip.key, - createdAt: tip.content.createdAt || tip.ts - }); - } - - resolve(out); - }) - ); - }).catch(reject); - }); + return out; + }; return { async listAgenda(filter = 'all') { const agendaConfig = readAgendaConfig(); const discardedItems = agendaConfig.discardedItems || []; + const removedItems = agendaConfig.removedItems || []; const ssbClient = await openSsb(); const userId = ssbClient.id; @@ -192,7 +174,19 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel ? housingModel.listHousing('ALL', userId).then(normalize).catch(() => []) : []; - const [tasksAll, eventsAll, transfersAll, tribesAll, marketAll, reportsAll, jobsAll, projectsAll, calendarsAll, industryAll, housingAll] = await Promise.all([ + const schoolViaModel = schoolModel && typeof schoolModel.listCourses === 'function' + ? schoolModel.listCourses('ALL', userId, {}).then(normalize).catch(() => []) + : []; + + const campaignsViaModel = campaignsModel && typeof campaignsModel.listAll === 'function' + ? campaignsModel.listAll({ filter: 'all' }).catch(() => []) + : []; + + const logisticsViaModel = logisticsModel && typeof logisticsModel.listAll === 'function' + ? logisticsModel.listAll({ filter: 'all' }).catch(() => []) + : []; + + const [tasksAll, eventsAll, transfersAll, tribesAll, marketAll, reportsAll, jobsAll, projectsAll, calendarsAll, industryAll, housingAll, schoolAll, campaignsAll, logisticsAll] = await Promise.all([ tasksViaModel, eventsViaModel, fetchItems('transfer'), @@ -203,7 +197,10 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel projectsViaModel, calendarsViaModel, industryViaModel, - housingViaModel + housingViaModel, + schoolViaModel, + campaignsViaModel, + logisticsViaModel ]); const tasks = tasksAll.filter(c => Array.isArray(c.assignees) && c.assignees.includes(userId)).map(t => ({ ...t, type: 'task' })); @@ -215,26 +212,36 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel ).map(m => ({ ...m, type: 'market' })); const reports = reportsAll.filter(c => c.author === userId || (Array.isArray(c.confirmations) && c.confirmations.includes(userId))).map(r => ({ ...r, type: 'report' })); const jobs = jobsAll.filter(c => c.author === userId || (Array.isArray(c.subscribers) && c.subscribers.includes(userId))).map(j => ({ ...j, type: 'job', title: j.title })); - const projects = projectsAll.map(p => ({ ...p, type: 'project' })); + const projects = projectsAll.filter(p => p.author === userId || (Array.isArray(p.followers) && p.followers.includes(userId)) || (Array.isArray(p.backers) && p.backers.some(b => b && b.userId === userId)) || (Array.isArray(p.bounties) && p.bounties.some(b => b && b.claimedBy === userId))).map(p => ({ ...p, type: 'project' })); const industryBuilds = industryAll.map(b => ({ ...b, type: 'industry', title: b.title })); + const schoolCourses = schoolAll + .filter(c => c.author === userId || (Array.isArray(c.students) && c.students.includes(userId)) || (Array.isArray(c.pending) && c.pending.some(pn => pn.author === userId))) + .map(c => ({ ...c, type: 'schoolCourse', title: c.title, date: c.startDate || c.createdAt, teaching: c.author === userId })); + const campaigns = (Array.isArray(campaignsAll) ? campaignsAll : []) + .filter(c => c && c.deadline && (c.author === userId || (Array.isArray(c.signers) && c.signers.includes(userId)))) + .map(c => ({ ...c, type: 'campaign', date: c.deadline, status: c.closed ? 'CLOSED' : 'OPEN' })); + const routes = (Array.isArray(logisticsAll) ? logisticsAll : []) + .filter(r => r && r.date && (r.author === userId || !!r.myBooking)) + .map(r => ({ ...r, type: 'logisticsRoute', status: r.status === 'CLOSED' ? 'CLOSED' : 'OPEN' })); const housingPlaces = housingAll .filter(h => h.author === userId || (Array.isArray(h.requests) && h.requests.includes(userId))) .map(h => ({ ...h, type: 'housing', date: h.availableFrom || h.createdAt, requested: Array.isArray(h.requests) && h.requests.includes(userId) })); const myCalendars = calendarsAll .filter(c => c.author === userId || (Array.isArray(c.participants) && c.participants.includes(userId))); - const calendars = myCalendars.map(c => ({ ...c, type: 'calendar' })); + const calendars = myCalendars.map(c => ({ ...c, id: c.rootId || c.id || c.key, type: 'calendar' })); const calendarDates = []; if (calendarsModel && typeof calendarsModel.getDatesForCalendar === 'function') { - for (const cal of myCalendars) { + const datesByCal = await Promise.all(myCalendars.map(cal => calendarsModel.getDatesForCalendar(cal.rootId || cal.id || cal.key).catch(() => []))); + for (const [ci, cal] of myCalendars.entries()) { try { - const dates = await calendarsModel.getDatesForCalendar(cal.id || cal.key); + const dates = datesByCal[ci]; for (const d of (dates || [])) { calendarDates.push({ type: 'calendarDate', id: d.key || d.id, title: d.label || cal.title || 'Calendar date', calendarTitle: cal.title || '', - calendarId: cal.id || cal.key, + calendarId: cal.rootId || cal.id || cal.key, date: d.date || d.createdAt, label: d.label || '', author: cal.author, @@ -257,9 +264,12 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel ...projects, ...industryBuilds, ...housingPlaces, + ...schoolCourses, + ...campaigns, + ...routes, ...calendars, ...calendarDates - ]; + ].filter(i => !removedItems.includes(i.id)); let filtered; if (filter === 'discarded') { @@ -278,6 +288,9 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel else if (filter === 'projects') filtered = filtered.filter(i => i.type === 'project'); else if (filter === 'industry') filtered = filtered.filter(i => i.type === 'industry'); else if (filter === 'housing') filtered = filtered.filter(i => i.type === 'housing'); + else if (filter === 'school') filtered = filtered.filter(i => i.type === 'schoolCourse'); + else if (filter === 'campaigns') filtered = filtered.filter(i => i.type === 'campaign'); + else if (filter === 'logistics') filtered = filtered.filter(i => i.type === 'logisticsRoute'); else if (filter === 'calendars') filtered = filtered.filter(i => i.type === 'calendar' || i.type === 'calendarDate'); else if (filter === 'today') { const startOfDay = new Date(); startOfDay.setHours(0, 0, 0, 0); @@ -333,6 +346,9 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel projects: mainItems.filter(i => i.type === 'project').length, industry: mainItems.filter(i => i.type === 'industry').length, housing: mainItems.filter(i => i.type === 'housing').length, + school: mainItems.filter(i => i.type === 'schoolCourse').length, + campaigns: mainItems.filter(i => i.type === 'campaign').length, + logistics: mainItems.filter(i => i.type === 'logisticsRoute').length, calendars: mainItems.filter(i => i.type === 'calendar' || i.type === 'calendarDate').length, today: mainItems.filter(i => { const d = itemTs(i); return d >= startOfDay.getTime() && d <= endOfDay.getTime(); }).length, upcoming: mainItems.filter(i => itemTs(i) > now).length, @@ -354,6 +370,14 @@ module.exports = ({ cooler, calendarsModel, eventsModel, tasksModel, marketModel const agendaConfig = readAgendaConfig(); agendaConfig.discardedItems = agendaConfig.discardedItems.filter(id => id !== itemId); writeAgendaConfig(agendaConfig); + }, + + async removeItem(itemId) { + const agendaConfig = readAgendaConfig(); + agendaConfig.discardedItems = (agendaConfig.discardedItems || []).filter(id => id !== itemId); + agendaConfig.removedItems = agendaConfig.removedItems || []; + if (!agendaConfig.removedItems.includes(itemId)) agendaConfig.removedItems.push(itemId); + writeAgendaConfig(agendaConfig); } }; }; diff --git a/src/models/audios_model.js b/src/models/audios_model.js index 13f66d6..cf65107 100644 --- a/src/models/audios_model.js +++ b/src/models/audios_model.js @@ -1,6 +1,7 @@ const pull = require("../server/node_modules/pull-stream"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); const { getConfig } = require("../configs/config-manager.js"); const categories = require("../backend/opinion_categories"); @@ -31,10 +32,9 @@ module.exports = ({ cooler }) => { return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))); - }); + const AUDIO_TYPES = ["audio", "audioOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, AUDIO_TYPES, { limit: logLimit }); const getMsg = async (ssbClient, key) => new Promise((resolve, reject) => { @@ -44,9 +44,6 @@ module.exports = ({ cooler }) => { const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const opinionMsgs = []; for (const m of messages) { @@ -62,41 +59,16 @@ module.exports = ({ cooler }) => { const ts = v.timestamp || m.timestamp || 0; let sizeBytes = 0; try { sizeBytes = Buffer.byteLength(JSON.stringify(v), 'utf8'); } catch (_) { sizeBytes = 0; } - nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); } - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } - const forward = new Map(); - for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); - const resolveGroup = (root) => { const contentTip = contentTipOf(root); const contentNode = nodes.get(contentTip) || nodes.get(root); @@ -113,9 +85,9 @@ module.exports = ({ cooler }) => { }; const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); - return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildAudio = (node, rootId, viewerId, agg) => { @@ -132,9 +104,10 @@ module.exports = ({ cooler }) => { createdAt: c.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || null, tags: tagsArr, - author: c.author, + author: node.author, title: c.title || "", description: c.description || "", + license: c.license || "", mapUrl: c.mapUrl || "", opinions, opinions_inhabitants: voters, @@ -163,16 +136,12 @@ module.exports = ({ cooler }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(messages); - let tip = id; - while (idx.forward.has(tip)) tip = idx.forward.get(tip); - if (idx.tomb.has(tip)) throw new Error("Audio not found"); - - let root = tip; - while (idx.parent.has(root)) root = idx.parent.get(root); + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Audio not found"); return root; }, - async createAudio(blobMarkdown, tagsRaw, title, description, mapUrl) { + async createAudio(blobMarkdown, tagsRaw, title, description, mapUrl, license) { const ssbClient = await openSsb(); const blobId = parseBlobId(blobMarkdown); const tags = normalizeTags(tagsRaw) || []; @@ -189,7 +158,8 @@ module.exports = ({ cooler }) => { description: description || "", mapUrl: mapUrl || "", opinions: {}, - opinions_inhabitants: [] + opinions_inhabitants: [], + ...(license ? { license } : {}) }; return new Promise((resolve, reject) => { @@ -251,7 +221,7 @@ module.exports = ({ cooler }) => { }); }, - async updateAudioById(id, blobMarkdown, tagsRaw, title, description, mapUrl) { + async updateAudioById(id, blobMarkdown, tagsRaw, title, description, mapUrl, license) { const ssbClient = await openSsb(); const userId = ssbClient.id; const tipId = await this.resolveCurrentId(id); @@ -275,7 +245,8 @@ module.exports = ({ cooler }) => { description: description !== undefined ? description || "" : oldMsg.content.description || "", mapUrl: mapUrl !== undefined ? mapUrl || "" : oldMsg.content.mapUrl || "", createdAt: oldMsg.content.createdAt, - updatedAt: now + updatedAt: now, + ...(license !== undefined ? { license } : {}) }; const tombstone = { type: "tombstone", target: tipId, deletedAt: now, author: userId }; @@ -358,11 +329,11 @@ module.exports = ({ cooler }) => { const ssbClient = await openSsb(); const viewer = viewerId || ssbClient.id; const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); + const idx = memoIndex('audios', messages, buildIndex); const rootId = idx.rootOf(id); const agg = idx.resolveGroup(rootId); - if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Audio not found"); + if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Audio not found"); return buildAudio(agg.contentNode, rootId, viewer, agg); }, diff --git a/src/models/backup_model.js b/src/models/backup_model.js new file mode 100644 index 0000000..175e028 --- /dev/null +++ b/src/models/backup_model.js @@ -0,0 +1,779 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const zlib = require('zlib'); +const crypto = require('crypto'); +const { pipeline } = require('stream'); +const pull = require('../server/node_modules/pull-stream'); +const toPull = require('../server/node_modules/stream-to-pull-stream'); + +const KEY_MAGIC = Buffer.from('OASIS1'); +const BACKUP_MAGIC = Buffer.from('OASISBK1'); +const SNAPSHOT_MAGIC = Buffer.from('OASISSN1'); +const SNAPSHOT_MAX_BYTES = 2 * 1024 * 1024 * 1024; +const SALT_LEN = 16; +const IV_LEN = 12; +const TAG_LEN = 16; +const REC_META = 1; +const REC_MSG = 2; +const REC_BLOB = 3; +const REC_FILE = 4; +const REC_MAX_LEN = 256 * 1024 * 1024; + +const stateRoot = () => { + try { return path.join(require('../configs/state-manager').ssbDir(), 'oasis'); } catch (_) { return null; } +}; + +const NOT_BACKED_UP = new Set(['oasis-config.json', 'oasis-server-config.json', 'rooms']); +const listStateFiles = (dir, base = dir) => { + const out = []; + let entries = []; + try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return out; } + for (const entry of entries) { + if (dir === base && NOT_BACKED_UP.has(entry.name)) continue; + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...listStateFiles(full, base)); + else if (entry.isFile() && !entry.name.endsWith('.migrated') && !/^snapshot(-recent)?\.oasissn/.test(entry.name)) out.push({ rel: path.relative(base, full), full }); + } + return out; +}; +const BLOB_RE = /&[A-Za-z0-9+/=]{44}\.sha256/g; +const DEFAULT_REMINDER_DAYS = 30; +const REMINDER_OPTIONS = [7, 30, 90, 0]; + +const MODULE_GROUPS = [ + { key: 'feed', test: (t) => /^(post|feed|about|contact|pub|vote|subscription)$/.test(t) }, + { key: 'media', test: (t) => /^(audio|video|image|document|bookmark|torrent|file)/i.test(t) }, + { key: 'blogs', test: (t) => /^blog/i.test(t) }, + { key: 'forum', test: (t) => /^forum/i.test(t) }, + { key: 'chats', test: (t) => /^chat/i.test(t) }, + { key: 'pads', test: (t) => /^pad/i.test(t) }, + { key: 'rooms', test: (t) => /^room/i.test(t) }, + { key: 'wiki', test: (t) => /^wiki/i.test(t) }, + { key: 'maps', test: (t) => /^map/i.test(t) }, + { key: 'tribes', test: (t) => /^tribe/i.test(t) }, + { key: 'school', test: (t) => /^school/i.test(t) }, + { key: 'parliament', test: (t) => /^parliament/i.test(t) }, + { key: 'courts', test: (t) => /^courts/i.test(t) }, + { key: 'emergencies', test: (t) => /^emergency/i.test(t) }, + { key: 'votes', test: (t) => /^(votes?|poll)/i.test(t) }, + { key: 'events', test: (t) => /^event/i.test(t) }, + { key: 'calendars', test: (t) => /^calendar/i.test(t) }, + { key: 'tasks', test: (t) => /^task/i.test(t) }, + { key: 'reports', test: (t) => /^report/i.test(t) }, + { key: 'mailing', test: (t) => /^mailing/i.test(t) }, + { key: 'campaigns', test: (t) => /^campaign/i.test(t) }, + { key: 'market', test: (t) => /^market/i.test(t) }, + { key: 'shops', test: (t) => /^shop/i.test(t) }, + { key: 'jobs', test: (t) => /^(job|curriculum)/i.test(t) }, + { key: 'housing', test: (t) => /^housing/i.test(t) }, + { key: 'projects', test: (t) => /^project/i.test(t) }, + { key: 'industry', test: (t) => /^industry/i.test(t) }, + { key: 'banking', test: (t) => /^(bank|ubi|wallet)/i.test(t) }, + { key: 'transfers', test: (t) => /^transfer/i.test(t) }, + { key: 'logistics', test: (t) => /^logistics/i.test(t) }, + { key: 'podcasts', test: (t) => /^podcast/i.test(t) }, + { key: 'games', test: (t) => /^(game|larp|pixelia|melody)/i.test(t) }, + { key: 'logs', test: (t) => /^log/i.test(t) } +]; +const MODULE_KEYS = [...MODULE_GROUPS.map(g => g.key), 'inbox', 'other']; + +const moduleOf = (content) => { + if (typeof content === 'string') return 'inbox'; + const t = content && typeof content === 'object' ? String(content.type || '') : ''; + if (!t) return 'other'; + const g = MODULE_GROUPS.find(x => x.test(t)); + return g ? g.key : 'other'; +}; + +const normalizePassword = (password) => { + if (password && typeof password === 'object' && password.password) password = password.password; + return String(password || ''); +}; +const deriveKey = (password, salt) => crypto.scryptSync(password, salt, 32); + +const encryptBuffer = (plaintext, password) => { + const salt = crypto.randomBytes(SALT_LEN); + const iv = crypto.randomBytes(IV_LEN); + const cipher = crypto.createCipheriv('aes-256-gcm', deriveKey(password, salt), iv); + const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]); + return Buffer.concat([KEY_MAGIC, salt, iv, cipher.getAuthTag(), ciphertext]); +}; + +const decryptBuffer = (data, password) => { + if (!Buffer.isBuffer(data) || data.length < KEY_MAGIC.length + SALT_LEN + IV_LEN + TAG_LEN) throw new Error('Unrecognized or corrupt backup file.'); + if (!data.slice(0, KEY_MAGIC.length).equals(KEY_MAGIC)) throw new Error('Unrecognized or corrupt backup file.'); + let o = KEY_MAGIC.length; + const salt = data.slice(o, o += SALT_LEN); + const iv = data.slice(o, o += IV_LEN); + const tag = data.slice(o, o += TAG_LEN); + const decipher = crypto.createDecipheriv('aes-256-gcm', deriveKey(password, salt), iv); + decipher.setAuthTag(tag); + return Buffer.concat([decipher.update(data.slice(o)), decipher.final()]); +}; + +const ssbDirOf = () => require('../server/ssb_config').path; +const secretPathOf = () => path.join(ssbDirOf(), 'secret'); + +const stateFile = () => require('../configs/state-manager').statePath('oasis-backup.json'); +const readState = () => { + try { return JSON.parse(fs.readFileSync(stateFile(), 'utf8')) || {}; } catch (_) { return {}; } +}; +const writeState = (next) => { + try { + const p = stateFile(); + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, JSON.stringify(next, null, 2), { mode: 0o600 }); + } catch (_) {} + return next; +}; + + +const reminderState = () => { + const b = readState(); + const days = b.reminderDays === undefined ? DEFAULT_REMINDER_DAYS : Number(b.reminderDays); + const lastAt = b.lastAt || null; + const lastTs = lastAt ? Date.parse(lastAt) || 0 : 0; + const overdue = days > 0 && (!lastTs || Date.now() - lastTs > days * 86400000); + return { lastAt, reminderDays: days, overdue, daysSince: lastTs ? Math.floor((Date.now() - lastTs) / 86400000) : null }; +}; +const setReminderDays = (days) => { + const n = Number(days); + return writeState({ ...readState(), reminderDays: REMINDER_OPTIONS.includes(n) ? n : DEFAULT_REMINDER_DAYS }); +}; +const markBackup = () => writeState({ ...readState(), lastAt: new Date().toISOString() }); + +const blobRefsOf = (value) => { + let json; + try { json = JSON.stringify(value); } catch (_) { return []; } + return Array.from(new Set(json.match(BLOB_RE) || [])); +}; + +const record = (type, payload) => { + const head = Buffer.alloc(5); + head.writeUInt8(type, 0); + head.writeUInt32BE(payload.length, 1); + return Buffer.concat([head, payload]); +}; + +const writeChunk = (stream, chunk) => new Promise((resolve, reject) => { + const ok = stream.write(chunk, (err) => err ? reject(err) : null); + if (ok) resolve(); + else stream.once('drain', resolve); +}); + +module.exports = ({ cooler }) => { + let ssb; + let restoreJob = null; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + + const readLog = async (ssbClient) => new Promise((resolve, reject) => { + pull(ssbClient.createLogStream({ reverse: false }), pull.collect((err, arr) => err ? reject(err) : resolve(arr || []))); + }); + + const listBlobs = async (ssbClient) => { + if (!ssbClient.blobs || typeof ssbClient.blobs.ls !== 'function') return new Map(); + return new Promise((resolve) => { + pull(ssbClient.blobs.ls({ long: true }), pull.collect((err, arr) => { + if (err) return resolve(new Map()); + const out = new Map(); + for (const b of arr || []) { + if (!b) continue; + if (typeof b === 'string') out.set(b, 0); + else if (b.id) out.set(b.id, Number(b.size) || 0); + } + resolve(out); + })); + }); + }; + + const readBlob = (ssbClient, id) => new Promise((resolve) => { + pull(ssbClient.blobs.get(id), pull.collect((e, chunks) => { + if (e || !chunks || !chunks.length) return resolve(null); + resolve(Buffer.concat(chunks.map(c => Buffer.isBuffer(c) ? c : Buffer.from(c)))); + })); + }); + + const normalizeOptions = (opts = {}) => { + const scope = String(opts.scope || 'all').toLowerCase() === 'mine' ? 'mine' : 'all'; + const withBlobs = opts.blobs === undefined ? true : !(opts.blobs === false || String(opts.blobs) === '0' || String(opts.blobs) === 'false' || String(opts.blobs) === 'off'); + const rawMods = Array.isArray(opts.modules) ? opts.modules : (opts.modules ? String(opts.modules).split(',') : []); + const modules = rawMods.map(m => String(m || '').trim().toLowerCase()).filter(m => MODULE_KEYS.includes(m)); + const sinceTs = opts.since ? (Date.parse(opts.since) || 0) : 0; + return { scope, withBlobs, modules, sinceTs }; + }; + + const selectMessages = async (ssbClient, options) => { + const me = ssbClient.id; + const all = await readLog(ssbClient); + const picked = []; + const blobIds = new Set(); + for (const m of all) { + if (!m || !m.value) continue; + if (options.scope === 'mine' && m.value.author !== me) continue; + if (options.sinceTs && Number(m.value.timestamp || m.timestamp || 0) < options.sinceTs) continue; + if (options.modules.length && !options.modules.includes(moduleOf(m.value.content))) continue; + picked.push(m); + if (options.withBlobs) for (const ref of blobRefsOf(m.value.content)) blobIds.add(ref); + } + if (options.withBlobs && options.scope !== 'mine' && !options.sinceTs && !options.modules.length) { + const sizes = await listBlobs(ssbClient); + for (const id of sizes.keys()) blobIds.add(id); + } + return { picked, blobIds }; + }; + + return { + MODULE_KEYS, + REMINDER_OPTIONS, + moduleOf, + encryptBuffer, + decryptBuffer, + + async exportKeys(password) { + const pw = normalizePassword(password); + if (pw.length < 32) throw new Error('Password too short'); + const secretPath = secretPathOf(); + if (!fs.existsSync(secretPath)) throw new Error(".ssb/secret file doesn't exist"); + const original = fs.readFileSync(secretPath); + const encrypted = encryptBuffer(original, pw); + if (!decryptBuffer(encrypted, pw).equals(original)) throw new Error('Backup verification failed; nothing was exported.'); + return { filename: 'oasis.enc', data: encrypted }; + }, + + async importKeys({ filePath, password }) { + const pw = normalizePassword(password); + if (!fs.existsSync(filePath)) throw new Error('Encrypted file not found.'); + let decrypted; + try { decrypted = decryptBuffer(fs.readFileSync(filePath), pw); } catch (_) { throw new Error('Wrong password or corrupt backup file.'); } + let keys = null; + try { keys = JSON.parse(decrypted.toString('utf8').split('\n').filter(l => !l.trim().startsWith('#')).join('\n')); } catch (_) { keys = null; } + const ssbKeys = require('../server/node_modules/ssb-keys'); + const probe = Buffer.from('oasis-keys-check'); + const valid = (() => { + try { + if (!keys || keys.curve !== 'ed25519' || typeof keys.public !== 'string' || typeof keys.private !== 'string' || keys.id !== '@' + keys.public) return false; + return ssbKeys.verify(keys, ssbKeys.sign(keys, probe), probe) === true; + } catch (_) { return false; } + })(); + if (!valid) throw new Error('The file does not contain a valid Oasis identity.'); + const ssbDir = ssbDirOf(); + fs.mkdirSync(ssbDir, { recursive: true }); + const secretPath = path.join(ssbDir, 'secret'); + if (fs.existsSync(secretPath)) { + const bakPath = path.join(ssbDir, 'secret.bak-' + new Date().toISOString().replace(/[:.]/g, '-')); + fs.copyFileSync(secretPath, bakPath); + try { fs.chmodSync(bakPath, 0o600); } catch (_) {} + } + const tmpPath = path.join(ssbDir, 'secret.tmp-' + process.pid); + fs.writeFileSync(tmpPath, decrypted, { mode: 0o600 }); + fs.renameSync(tmpPath, secretPath); + try { fs.unlinkSync(filePath); } catch (_) {} + let id = ''; + try { id = JSON.parse(decrypted.toString('utf8').split('\n').filter(l => !l.trim().startsWith('#')).join('\n')).id || ''; } catch (_) {} + return { path: secretPath, id }; + }, + + recoveryKit() { + const secretPath = secretPathOf(); + if (!fs.existsSync(secretPath)) throw new Error(".ssb/secret file doesn't exist"); + const raw = fs.readFileSync(secretPath, 'utf8'); + const clean = raw.split('\n').filter(l => !l.trim().startsWith('#')).join('\n').trim(); + let id = ''; + try { id = JSON.parse(clean).id || ''; } catch (_) {} + return { secret: clean, id, createdAt: new Date().toISOString() }; + }, + + async estimate(opts = {}) { + const ssbClient = await openSsb(); + const options = normalizeOptions(opts); + const { picked, blobIds } = await selectMessages(ssbClient, options); + let bytes = 0; + for (const m of picked) bytes += Buffer.byteLength(JSON.stringify({ key: m.key, value: m.value, timestamp: m.timestamp }), 'utf8') + 5; + let blobBytes = 0; + let blobsFound = 0; + if (options.withBlobs && blobIds.size) { + const sizes = await listBlobs(ssbClient); + for (const id of blobIds) { + if (!sizes.has(id)) continue; + blobsFound += 1; + blobBytes += sizes.get(id) || 0; + } + } + return { options, messages: picked.length, blobs: blobsFound, blobsReferenced: blobIds.size, messageBytes: bytes, blobBytes, bytes: bytes + blobBytes }; + }, + + async createBackup(opts = {}, password, outPath) { + const pw = normalizePassword(password); + if (pw.length < 32) throw new Error('Password too short'); + const ssbClient = await openSsb(); + const options = normalizeOptions(opts); + const { picked, blobIds } = await selectMessages(ssbClient, options); + const salt = crypto.randomBytes(SALT_LEN); + const iv = crypto.randomBytes(IV_LEN); + const cipher = crypto.createCipheriv('aes-256-gcm', deriveKey(pw, salt), iv); + const gzip = zlib.createGzip(); + const out = fs.createWriteStream(outPath); + const done = new Promise((resolve, reject) => pipeline(gzip, cipher, out, (err) => err ? reject(err) : resolve())); + out.write(Buffer.concat([BACKUP_MAGIC, salt, iv])); + const meta = { version: 1, createdAt: new Date().toISOString(), author: ssbClient.id, options, messages: picked.length }; + await writeChunk(gzip, record(REC_META, Buffer.from(JSON.stringify(meta), 'utf8'))); + for (const m of picked) { + await writeChunk(gzip, record(REC_MSG, Buffer.from(JSON.stringify({ key: m.key, value: m.value, timestamp: m.timestamp }), 'utf8'))); + } + let files = 0; + const root = stateRoot(); + if (root) { + for (const f of listStateFiles(root)) { + let data = null; + try { data = fs.readFileSync(f.full); } catch (_) { continue; } + const head = Buffer.from(JSON.stringify({ rel: f.rel.split(path.sep).join('/'), size: data.length }), 'utf8'); + const lenBuf = Buffer.alloc(4); + lenBuf.writeUInt32BE(head.length, 0); + await writeChunk(gzip, record(REC_FILE, Buffer.concat([lenBuf, head, data]))); + files += 1; + } + } + let blobs = 0; + if (options.withBlobs) { + for (const id of blobIds) { + const data = await readBlob(ssbClient, id); + if (!data) continue; + const head = Buffer.from(JSON.stringify({ id, size: data.length }), 'utf8'); + const lenBuf = Buffer.alloc(4); + lenBuf.writeUInt32BE(head.length, 0); + await writeChunk(gzip, record(REC_BLOB, Buffer.concat([lenBuf, head, data]))); + blobs += 1; + } + } + gzip.end(); + await done; + fs.appendFileSync(outPath, cipher.getAuthTag()); + markBackup(); + return { path: outPath, messages: picked.length, blobs, bytes: fs.statSync(outPath).size, filename: `oasis-backup-${new Date().toISOString().slice(0, 10)}.oasisbk` }; + }, + + async createSnapshot(outPath, { sinceMs = 0, now = Date.now() } = {}) { + const ssbClient = await openSsb(); + const all = await readLog(ssbClient); + const byAuthor = new Map(); + for (const m of all) { + if (!m || !m.value || !m.value.author || m.value.content === undefined || m.value.content === null) continue; + const list = byAuthor.get(m.value.author) || []; + list.push(m); + byAuthor.set(m.value.author, list); + } + const latest = (list) => list.reduce((t, m) => Math.max(t, Number(m.timestamp) || 0, Number(m.value.timestamp) || 0), 0); + const picked = [...byAuthor.values()] + .filter(list => !(sinceMs > 0) || latest(list) >= now - sinceMs) + .sort((a, b) => latest(b) - latest(a)) + .flatMap(list => list.sort((a, b) => (a.value.sequence || 0) - (b.value.sequence || 0))); + const gzip = zlib.createGzip(); + const out = fs.createWriteStream(outPath); + const done = new Promise((resolve, reject) => pipeline(gzip, out, (err) => err ? reject(err) : resolve())); + out.write(SNAPSHOT_MAGIC); + const meta = { version: 1, kind: 'snapshot', createdAt: new Date().toISOString(), author: ssbClient.id, messages: picked.length, feeds: new Set(picked.map(m => m.value.author)).size, boxed: picked.filter(m => typeof m.value.content === 'string').length, sinceMs: sinceMs > 0 ? sinceMs : null }; + await writeChunk(gzip, record(REC_META, Buffer.from(JSON.stringify(meta), 'utf8'))); + for (const m of picked) { + await writeChunk(gzip, record(REC_MSG, Buffer.from(JSON.stringify({ key: m.key, value: m.value, timestamp: m.timestamp }), 'utf8'))); + } + gzip.end(); + await done; + return { path: outPath, messages: picked.length, feeds: meta.feeds, bytes: fs.statSync(outPath).size, createdAt: meta.createdAt }; + }, + + isSnapshotFile(filePath) { + try { + const fd = fs.openSync(filePath, 'r'); + const head = Buffer.alloc(SNAPSHOT_MAGIC.length); + fs.readSync(fd, head, 0, head.length, 0); + fs.closeSync(fd); + return head.equals(SNAPSHOT_MAGIC); + } catch (_) { return false; } + }, + + async fetchSnapshot({ address, connect = null, tier = 'full' }, outPath) { + const ssbClient = await openSsb(); + const doConnect = connect || ((addr, cb) => ssbClient.conn.connect(addr, cb)); + const discard = () => { try { fs.unlinkSync(outPath); } catch (_) {} return null; }; + try { + const rpc = await new Promise((resolve, reject) => doConnect(address, (err, r) => err ? reject(err) : resolve(r))); + if (!rpc || !rpc.snapshot || typeof rpc.snapshot.get !== 'function') return null; + const info = await new Promise((resolve) => rpc.snapshot.info({ tier }, (err, i) => resolve(err ? null : i))); + if (!info || !(info.bytes > 0) || info.bytes > SNAPSHOT_MAX_BYTES) return null; + const out = fs.createWriteStream(outPath); + let written = 0; + await new Promise((resolve, reject) => { + pull( + rpc.snapshot.get({ tier }), + pull.map(c => Buffer.isBuffer(c) ? c : Buffer.from(c)), + pull.through(c => { written += c.length; }), + toPull.sink(out, (err) => err ? reject(err) : resolve()) + ); + }); + if (written > SNAPSHOT_MAX_BYTES || !this.isSnapshotFile(outPath)) return discard(); + return { address, tier, path: outPath, bytes: written, createdAt: info.createdAt }; + } catch (_) { return discard(); } + }, + + startBootstrap({ address, connect = null, tiers = ['recent', 'full'] }) { + if (restoreJob && restoreJob.running) return restoreJob; + const empty = () => ({ messages: 0, skipped: 0, failed: 0, forked: 0, blobs: 0, blobsSkipped: 0, percent: 0 }); + const job = { running: true, source: 'snapshot', phase: null, tier: null, tiers: [], address, startedAt: new Date().toISOString(), finishedAt: null, progress: empty(), result: null, error: null }; + restoreJob = job; + const total = { ...empty(), files: 0, forks: [], errors: [], meta: null }; + const run = async () => { + for (const tier of tiers) { + const tmp = path.join(os.tmpdir(), `oasis-snapshot-${tier}-${Date.now()}-${Math.random().toString(36).slice(2)}.oasissn`); + job.tier = tier; + job.phase = 'download'; + try { + const got = await this.fetchSnapshot({ address, connect, tier }, tmp); + if (!got) continue; + job.phase = 'restore'; + const res = await this.restoreBackup({ filePath: tmp, password: '', stateFiles: false, onProgress: (p) => { job.progress = { ...total, ...p, messages: total.messages + p.messages, skipped: total.skipped + p.skipped, failed: total.failed + p.failed, forked: total.forked + p.forked }; } }); + for (const k of ['messages', 'skipped', 'failed', 'forked', 'blobs', 'blobsSkipped', 'files']) total[k] += Number(res[k]) || 0; + total.forks.push(...(res.forks || [])); + total.errors.push(...(res.errors || [])); + total.meta = res.meta || total.meta; + job.tiers.push({ tier, messages: res.messages, skipped: res.skipped, bytes: got.bytes }); + } finally { try { fs.unlinkSync(tmp); } catch (_) {} } + } + if (!job.tiers.length) throw new Error('snapshot not available'); + total.percent = 100; + return total; + }; + job.promise = run() + .then((res) => { job.result = res; job.progress = { ...job.progress, ...res }; return res; }) + .catch((err) => { job.error = err && err.message ? err.message : String(err); return null; }) + .finally(() => { job.running = false; job.phase = null; job.finishedAt = new Date().toISOString(); }); + return job; + }, + + async readBackup(filePath, password, onRecord) { + const size = fs.statSync(filePath).size; + const snapshot = this.isSnapshotFile(filePath); + const pw = snapshot ? '' : normalizePassword(password); + const headLen = snapshot ? SNAPSHOT_MAGIC.length : BACKUP_MAGIC.length + SALT_LEN + IV_LEN; + const tailLen = snapshot ? 0 : TAG_LEN; + if (size < headLen + tailLen) throw new Error('Unrecognized or corrupt backup file.'); + const fd = fs.openSync(filePath, 'r'); + const head = Buffer.alloc(headLen); + fs.readSync(fd, head, 0, headLen, 0); + const tag = Buffer.alloc(TAG_LEN); + if (!snapshot) fs.readSync(fd, tag, 0, TAG_LEN, size - TAG_LEN); + fs.closeSync(fd); + if (!snapshot && !head.slice(0, BACKUP_MAGIC.length).equals(BACKUP_MAGIC)) throw new Error('Unrecognized or corrupt backup file.'); + let decipher = null; + if (!snapshot) { + const salt = head.slice(BACKUP_MAGIC.length, BACKUP_MAGIC.length + SALT_LEN); + const iv = head.slice(BACKUP_MAGIC.length + SALT_LEN, headLen); + decipher = crypto.createDecipheriv('aes-256-gcm', deriveKey(pw, salt), iv); + decipher.setAuthTag(tag); + } + const gunzip = zlib.createGunzip(); + const total = size - headLen - tailLen; + const source = fs.createReadStream(filePath, { start: headLen, end: size - tailLen - 1 }); + let streamError = null; + if (decipher) pipeline(source, decipher, gunzip, (err) => { if (err) streamError = err; }); + else pipeline(source, gunzip, (err) => { if (err) streamError = err; }); + const chunks = []; + let pendingLen = 0; + let meta = null; + const compact = () => { + if (chunks.length < 2) return; + const all = Buffer.concat(chunks, pendingLen); + chunks.length = 0; + chunks.push(all); + }; + const nextRecord = () => { + if (pendingLen < 5) return null; + if (chunks[0].length < 5) compact(); + const type = chunks[0].readUInt8(0); + const len = chunks[0].readUInt32BE(1); + if (len > REC_MAX_LEN) throw new Error('Backup record too large.'); + if (pendingLen < 5 + len) return null; + compact(); + const all = chunks[0]; + chunks.length = 0; + pendingLen = all.length - 5 - len; + if (pendingLen) chunks.push(all.slice(5 + len)); + return { type, payload: all.slice(5, 5 + len) }; + }; + const handle = async (type, payload) => { + const read = Math.min(total, source.bytesRead || 0); + if (type === REC_META) { meta = JSON.parse(payload.toString('utf8')); await onRecord({ type: 'meta', meta, read, total }); } + else if (type === REC_MSG) await onRecord({ type: 'msg', msg: JSON.parse(payload.toString('utf8')), read, total }); + else if (type === REC_BLOB) { + const hl = payload.readUInt32BE(0); + const header = JSON.parse(payload.slice(4, 4 + hl).toString('utf8')); + await onRecord({ type: 'blob', id: header.id, data: payload.slice(4 + hl), read, total }); + } + else if (type === REC_FILE) { + const hl = payload.readUInt32BE(0); + const header = JSON.parse(payload.slice(4, 4 + hl).toString('utf8')); + await onRecord({ type: 'file', rel: header.rel, data: payload.slice(4 + hl), read, total }); + } + }; + try { + for await (const chunk of gunzip) { + chunks.push(chunk); + pendingLen += chunk.length; + let rec; + while ((rec = nextRecord())) await handle(rec.type, rec.payload); + } + } catch (e) { + source.destroy(); + if (streamError || /unsupported state|auth|bad decrypt|incorrect header|invalid/i.test(String(e && e.message))) throw new Error('Wrong password or corrupt backup file.'); + throw e; + } + if (streamError) throw new Error('Wrong password or corrupt backup file.'); + return meta; + }, + + async restoreBackup({ filePath, password, onProgress, stateFiles = true }) { + const ssbClient = await openSsb(); + let validate = null; + try { validate = require('../server/node_modules/ssb-validate'); } catch (_) {} + const seqKey = (v) => `${v.author}::${v.sequence}`; + const existing = new Map(); + for (const m of await readLog(ssbClient)) if (m && m.value) existing.set(seqKey(m.value), m.key); + const stats = { messages: 0, skipped: 0, failed: 0, forked: 0, blobs: 0, blobsSkipped: 0, files: 0, percent: 0, forks: [], errors: [], meta: null }; + const forkAuthors = new Map(); + const errorCounts = new Map(); + const noteError = (err) => { + const reason = String(err && err.message ? err.message : err || 'unknown error').replace(/, on feed:@[^\s]+/g, '').replace(/in state:\{.*?\}/g, '').trim(); + errorCounts.set(reason, (errorCounts.get(reason) || 0) + 1); + }; + const addMsg = (value) => new Promise((resolve) => { + if (typeof ssbClient.add !== 'function') return resolve(new Error('add is not available')); + ssbClient.add(value, (err) => resolve(err || null)); + }); + const addBlob = (data) => new Promise((resolve) => { + pull(pull.values([data]), ssbClient.blobs.add((err, ref) => resolve(err ? null : ref))); + }); + const getByKey = (key) => new Promise((resolve) => { + if (!key || typeof ssbClient.get !== 'function') return resolve(false); + try { ssbClient.get(key, (err, value) => resolve(!err && !!value)); } catch (_) { resolve(false); } + }); + const storedByKey = async (msg) => { + if (await getByKey(msg.key)) return true; + let computed = null; + try { computed = validate ? validate.id(msg.value) : null; } catch (_) { computed = null; } + return computed && computed !== msg.key ? getByKey(computed) : false; + }; + const supportsLs = !!(ssbClient.blobs && typeof ssbClient.blobs.ls === 'function'); + const present = supportsLs ? await listBlobs(ssbClient) : new Map(); + const hasBlob = (id) => supportsLs ? Promise.resolve(present.has(id)) : new Promise((resolve) => ssbClient.blobs.has(id, (err, has) => resolve(!err && !!has))); + const deferred = []; + const report = () => { if (typeof onProgress === 'function') onProgress({ ...stats }); }; + const sameMessage = (storedKey, msg) => { + if (storedKey === msg.key) return true; + try { return storedKey === validate.id(msg.value); } catch (_) { return false; } + }; + const tryAdd = async (msg) => { + const k = seqKey(msg.value); + if (existing.has(k)) { + if (sameMessage(existing.get(k), msg)) { stats.skipped += 1; return true; } + stats.forked += 1; + forkAuthors.set(msg.value.author, (forkAuthors.get(msg.value.author) || 0) + 1); + return true; + } + const err = await addMsg(msg.value); + if (err) { + if (!(await storedByKey(msg))) return err; + stats.skipped += 1; + existing.set(k, msg.key); + return true; + } + stats.messages += 1; + existing.set(k, msg.key); + return true; + }; + const meta = await this.readBackup(filePath, password, async (rec) => { + if (rec.total) stats.percent = Math.min(100, (rec.read / rec.total) * 100); + if (rec.type === 'meta') { stats.meta = rec.meta; return; } + if (rec.type === 'msg') { + if (!rec.msg || !rec.msg.value || !rec.msg.value.author) { stats.failed += 1; noteError('malformed message'); report(); return; } + const res = await tryAdd(rec.msg); + if (res !== true) deferred.push({ msg: rec.msg, err: res }); + report(); + return; + } + if (rec.type === 'file') { + if (!stateFiles) { stats.skipped += 1; report(); return; } + const root = stateRoot(); + const rel = typeof rec.rel === 'string' ? rec.rel.replace(/\\/g, '/') : ''; + const parts = rel.split('/').filter(Boolean); + const safe = !!root && parts.length > 0 && !NOT_BACKED_UP.has(parts[0]) && !path.isAbsolute(rel) && parts.every(p => p !== '.' && p !== '..' && !/[\0]/.test(p)); + const rootAbs = root ? path.resolve(root) : ''; + const target = safe ? path.resolve(rootAbs, ...parts) : ''; + if (!safe || !target.startsWith(rootAbs + path.sep)) { stats.failed += 1; noteError('state file rejected'); report(); return; } + try { + fs.mkdirSync(path.dirname(target), { recursive: true }); + if (fs.existsSync(target)) { try { fs.copyFileSync(target, `${target}.before-restore`); } catch (_) {} } + fs.writeFileSync(target, rec.data); + stats.files += 1; + } catch (_) { stats.failed += 1; noteError('state file could not be written'); } + report(); + return; + } + if (rec.type === 'blob') { + if (await hasBlob(rec.id)) { stats.blobsSkipped += 1; report(); return; } + const ref = await addBlob(rec.data); + if (ref) { stats.blobs += 1; present.set(ref, rec.data.length); } else { stats.failed += 1; noteError('blob could not be stored'); } + report(); + } + }); + deferred.sort((a, b) => a.msg.value.author === b.msg.value.author ? (a.msg.value.sequence || 0) - (b.msg.value.sequence || 0) : String(a.msg.value.author).localeCompare(String(b.msg.value.author))); + let pending = deferred; + while (pending.length) { + const next = []; + for (const item of pending) { + const res = await tryAdd(item.msg); + if (res !== true) next.push({ msg: item.msg, err: res }); + } + report(); + if (next.length === pending.length) { pending = next; break; } + pending = next; + } + for (const item of pending) { stats.failed += 1; noteError(item.err); } + stats.percent = 100; + stats.forks = Array.from(forkAuthors.entries()).map(([author, count]) => ({ author, count, mine: author === ssbClient.id })); + stats.errors = Array.from(errorCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([reason, count]) => ({ reason, count })); + try { fs.unlinkSync(filePath); } catch (_) {} + report(); + return { ...stats, meta: stats.meta || meta }; + }, + + startRestore({ filePath, password, source = 'file' }) { + if (restoreJob && restoreJob.running) return restoreJob; + const job = { running: true, source, startedAt: new Date().toISOString(), finishedAt: null, progress: { messages: 0, skipped: 0, failed: 0, forked: 0, blobs: 0, blobsSkipped: 0, percent: 0 }, result: null, error: null }; + restoreJob = job; + job.promise = this.restoreBackup({ filePath, password, onProgress: (p) => { job.progress = p; } }) + .then((res) => { job.result = res; return res; }) + .catch((err) => { job.error = err && err.message ? err.message : String(err); try { fs.unlinkSync(filePath); } catch (_) {} return null; }) + .finally(() => { job.running = false; job.finishedAt = new Date().toISOString(); }); + return job; + }, + + restoreStatus() { + if (!restoreJob) return null; + const { promise, ...rest } = restoreJob; + return rest; + }, + + reminderState() { return reminderState(); }, + + setReminderDays(days) { return setReminderDays(days); }, + + markBackup() { return markBackup(); }, + + async rebuildIndexes() { + const started = Date.now(); + const ssbClient = await openSsb(); + const indexDir = path.join(require('../configs/state-manager').ssbDir(), 'db2', 'indexes'); + const measure = (dir) => { + let files = 0, bytes = 0; + const walk = (d) => { + let entries = []; + try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch (_) { return; } + for (const entry of entries) { + const full = path.join(d, entry.name); + if (entry.isDirectory()) { walk(full); continue; } + try { bytes += fs.statSync(full).size; files += 1; } catch (_) {} + } + }; + walk(dir); + return { files, bytes }; + }; + const before = measure(indexDir); + let error = null; + try { + await new Promise((resolve, reject) => { + try { ssbClient.rebuild((err) => err ? reject(err) : resolve()); } catch (e) { reject(e); } + }); + } catch (e) { error = e && e.message ? e.message : String(e); } + const after = measure(indexDir); + let totalMessages = 0; + try { totalMessages = (await readLog(ssbClient)).length; } catch (_) {} + return { + checkedAt: new Date().toISOString(), + tookMs: Date.now() - started, + ok: !error, + error, + totalMessages, + indexes: { path: indexDir, files: after.files, bytes: after.bytes, deltaBytes: after.bytes - before.bytes } + }; + }, + async verify({ author = null } = {}) { + const started = Date.now(); + const ssbClient = await openSsb(); + const me = author || ssbClient.id; + const all = await readLog(ssbClient); + let validate = null; + try { validate = require('../server/node_modules/ssb-validate'); } catch (_) {} + const own = all.filter(m => m && m.value && m.value.author === me).sort((a, b) => (a.value.sequence || 0) - (b.value.sequence || 0)); + const feed = { messages: own.length, lastSequence: own.length ? own[own.length - 1].value.sequence : 0, gaps: [], brokenLinks: [], badHashes: [], badSignatures: [], duplicates: [] }; + let expected = 1; + let prevKey = null; + const seen = new Set(); + for (const m of own) { + const seq = Number(m.value.sequence) || 0; + if (seen.has(seq)) feed.duplicates.push(seq); + seen.add(seq); + if (seq !== expected) { feed.gaps.push({ expected, found: seq }); expected = seq; } + if (prevKey && m.value.previous !== prevKey) feed.brokenLinks.push(seq); + if (validate) { + try { if (validate.id(m.value) !== m.key) feed.badHashes.push(seq); } catch (_) { feed.badHashes.push(seq); } + try { if (validate.checkInvalidOOO(m.value, null)) feed.badSignatures.push(seq); } catch (_) { feed.badSignatures.push(seq); } + } + prevKey = m.key; + expected = seq + 1; + } + feed.ok = !feed.gaps.length && !feed.brokenLinks.length && !feed.badHashes.length && !feed.badSignatures.length && !feed.duplicates.length; + const bySeq = new Map(); + for (const m of all) { + if (!m || !m.value || !m.value.author) continue; + const k = `${m.value.author}::${m.value.sequence}`; + if (!bySeq.has(k)) bySeq.set(k, new Set()); + bySeq.get(k).add(m.key); + } + const forkMap = new Map(); + for (const [k, keys] of bySeq) { + if (keys.size < 2) continue; + const [feedId, seq] = k.split('::'); + if (!forkMap.has(feedId)) forkMap.set(feedId, []); + forkMap.get(feedId).push({ sequence: Number(seq), keys: Array.from(keys) }); + } + const forks = Array.from(forkMap.entries()).map(([feedId, points]) => ({ author: feedId, mine: feedId === me, points: points.sort((a, b) => a.sequence - b.sequence) })); + const referenced = new Set(); + const referencedOwn = new Set(); + for (const m of all) { + if (!m || !m.value) continue; + for (const ref of blobRefsOf(m.value.content)) { + referenced.add(ref); + if (m.value.author === me) referencedOwn.add(ref); + } + } + const present = await listBlobs(ssbClient); + const orphan = []; + let orphanBytes = 0; + for (const [id, size] of present) if (!referenced.has(id)) { orphan.push(id); orphanBytes += size || 0; } + const missing = []; + const missingOwn = []; + for (const id of referenced) { + if (!present.size || present.has(id)) continue; + missing.push(id); + if (referencedOwn.has(id)) missingOwn.push(id); + } + return { + author: me, checkedAt: new Date().toISOString(), tookMs: Date.now() - started, totalMessages: all.length, + feed, forks, forkCount: forks.length, + blobs: { present: present.size, referenced: referenced.size, orphan: orphan.length, orphanBytes, missing: missing.length, missingOwn: missingOwn.length, orphanIds: orphan.slice(0, 50), missingIds: missing.slice(0, 50), missingOwnIds: missingOwn.slice(0, 50), listed: present.size > 0 || referenced.size === 0 } + }; + } + }; +}; + +Object.assign(module.exports, { reminderState, setReminderDays, markBackup, REMINDER_OPTIONS, MODULE_KEYS }); diff --git a/src/models/banking_model.js b/src/models/banking_model.js index 46d93ff..eaf96b2 100644 --- a/src/models/banking_model.js +++ b/src/models/banking_model.js @@ -2,7 +2,8 @@ const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); const pull = require("../server/node_modules/pull-stream"); -const { getConfig } = require("../configs/config-manager.js"); +const { readTyped, CONTENT_TYPES, requestScope } = require("./typed_log"); +const { getConfig, defaultInvite } = require("../configs/config-manager.js"); const { config } = require("../server/SSB_server.js"); const sharedState = require("../configs/shared-state.js"); @@ -19,12 +20,17 @@ const DEFAULT_RULES = { graceDays: 30 }; -const STORAGE_DIR = process.env.OASIS_BANKING_DIR || path.join(__dirname, "..", "configs"); -const EPOCHS_PATH = path.join(STORAGE_DIR, "banking-epochs.json"); -const TRANSFERS_PATH = path.join(STORAGE_DIR, "banking-allocations.json"); -const ADDR_PATH = path.join(STORAGE_DIR, "wallet-addresses.json"); -const ECO_HISTORY_PATH = path.join(STORAGE_DIR, "banking-eco-history.json"); -const ECO_HISTORY_MAX = 500; +const { statePath, stateDir } = require("../configs/state-manager"); +const stateFile = (name) => process.env.OASIS_BANKING_DIR ? path.join(process.env.OASIS_BANKING_DIR, name) : statePath(name); +const STORAGE_DIR = process.env.OASIS_BANKING_DIR || stateDir("banking-epochs.json"); +const EPOCHS_PATH = stateFile("banking-epochs.json"); +const TRANSFERS_PATH = stateFile("banking-allocations.json"); +const ADDR_PATH = stateFile("wallet-addresses.json"); +const BOOK_PATH = stateFile("banking-address-book.json"); +const ECO_HISTORY_PATH = stateFile("banking-eco-history.json"); +const UBI_PAID_PATH = stateFile("banking-ubi-paid.json"); +const REBALANCE_PENDING_PATH = stateFile("banking-rebalance-pending.json"); +const FUNDS_HISTORY_PATH = stateFile("banking-funds-history.json"); const ECO_HISTORY_MIN_GAP_MS = 5 * 60 * 1000; const ECOIN_PER_GRAM_CO2 = 0.1; @@ -42,6 +48,8 @@ function ensureStoreFiles() { if (!fs.existsSync(TRANSFERS_PATH)) fs.writeFileSync(TRANSFERS_PATH, "[]"); if (!fs.existsSync(ADDR_PATH)) fs.writeFileSync(ADDR_PATH, "{}"); if (!fs.existsSync(ECO_HISTORY_PATH)) fs.writeFileSync(ECO_HISTORY_PATH, "[]"); + if (!fs.existsSync(UBI_PAID_PATH)) fs.writeFileSync(UBI_PAID_PATH, "{}"); + if (!fs.existsSync(FUNDS_HISTORY_PATH)) fs.writeFileSync(FUNDS_HISTORY_PATH, "[]"); } function readEcoHistory() { @@ -49,6 +57,59 @@ function readEcoHistory() { try { return JSON.parse(fs.readFileSync(ECO_HISTORY_PATH, "utf8")) || []; } catch (_) { return []; } } +const ECO_HISTORY_TIERS = [ + { olderThanMs: 2 * ONE_DAY_MS, keepEveryMs: 60 * 60 * 1000 }, + { olderThanMs: 60 * ONE_DAY_MS, keepEveryMs: ONE_DAY_MS } +]; +const ECO_HISTORY_HARD_MAX = 20000; + +function readFundsHistory() { + try { return JSON.parse(fs.readFileSync(FUNDS_HISTORY_PATH, "utf8")) || []; } catch (_) { return []; } +} + +function appendFundsHistory(sample) { + ensureStoreFiles(); + let arr = readFundsHistory(); + if (!Array.isArray(arr)) arr = []; + const last = arr[arr.length - 1]; + if (last && Number(sample.ts) - Number(last.ts) < ECO_HISTORY_MIN_GAP_MS) return arr; + arr.push(sample); + arr = compactEcoHistory(arr); + writeJson(FUNDS_HISTORY_PATH, arr); + return arr; +} + +function compactEcoHistory(arr) { + const now = Date.now(); + const out = []; + let lastKeptTs = -Infinity; + for (const s of arr) { + const ts = Number(s.ts) || 0; + const age = now - ts; + const tier = ECO_HISTORY_TIERS.slice().reverse().find(t => age > t.olderThanMs); + if (!tier || ts - lastKeptTs >= tier.keepEveryMs) { out.push(s); lastKeptTs = ts; } + } + return out.length > ECO_HISTORY_HARD_MAX ? out.slice(out.length - ECO_HISTORY_HARD_MAX) : out; +} + +const RANGE_MS = { today: ONE_DAY_MS, week: 7 * ONE_DAY_MS, month: 30 * ONE_DAY_MS, year: 365 * ONE_DAY_MS, "5y": 5 * 365 * ONE_DAY_MS, all: Infinity }; +const normalizeRange = (r) => (Object.prototype.hasOwnProperty.call(RANGE_MS, String(r || "")) ? String(r) : "today"); +const filterByRange = (arr, range, key = "ts") => { + const span = RANGE_MS[normalizeRange(range)]; + if (!Number.isFinite(span)) return arr; + const from = Date.now() - span; + return arr.filter(x => Number(x[key]) >= from); +}; +const RANGE_ORDER = ["today", "week", "month", "year", "5y", "all"]; +const widenRange = (arr, range, key = "ts") => { + const start = Math.max(0, RANGE_ORDER.indexOf(normalizeRange(range))); + for (let i = start; i < RANGE_ORDER.length; i++) { + const r = RANGE_ORDER[i]; + if (filterByRange(arr, r, key).length >= 2) return r; + } + return normalizeRange(range); +}; + function appendEcoHistory(sample) { ensureStoreFiles(); let arr = readEcoHistory(); @@ -56,7 +117,7 @@ function appendEcoHistory(sample) { const last = arr[arr.length - 1]; if (last && Number(sample.ts) - Number(last.ts) < ECO_HISTORY_MIN_GAP_MS) return arr; arr.push(sample); - if (arr.length > ECO_HISTORY_MAX) arr = arr.slice(arr.length - ECO_HISTORY_MAX); + arr = compactEcoHistory(arr); try { fs.writeFileSync(ECO_HISTORY_PATH, JSON.stringify(arr)); } catch (_) {} return arr; } @@ -133,7 +194,52 @@ function writeJson(p, v) { fs.writeFileSync(p, JSON.stringify(v, null, 2)); } -async function rpcCall(method, params, kind = "user") { +const ubiPaidKey = (epochId, claimantId) => `${epochId}:${claimantId}`; + +function readUbiPaidLedger() { + ensureStoreFiles(); + const raw = readJson(UBI_PAID_PATH, {}); + return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {}; +} + +function reserveUbiPayment(key) { + const ledger = readUbiPaidLedger(); + if (ledger[key]) return false; + ledger[key] = { startedAt: new Date().toISOString() }; + writeJson(UBI_PAID_PATH, ledger); + return true; +} + +function settleUbiPayment(key, patch) { + const ledger = readUbiPaidLedger(); + ledger[key] = { ...(ledger[key] || {}), ...patch }; + writeJson(UBI_PAID_PATH, ledger); +} + +function releaseUbiPayment(key) { + const ledger = readUbiPaidLedger(); + delete ledger[key]; + writeJson(UBI_PAID_PATH, ledger); +} + +function ubiAddressTaken(epochId, address, claimantId) { + const ledger = readUbiPaidLedger(); + const own = ubiPaidKey(epochId, claimantId); + return Object.keys(ledger).some(k => k !== own && k.startsWith(`${epochId}:`) && ledger[k] && ledger[k].address === address); +} + +const rpcFailureLog = new Map(); +function logRpcFailure(method, kind, reason) { + const key = `${kind}:${method}:${reason}`; + const now = Date.now(); + if (now - (rpcFailureLog.get(key) || 0) < 5 * 60 * 1000) return; + rpcFailureLog.set(key, now); + if (kind === "pub") console.warn(`[ECOin RPC] ${kind} ${method} failed: ${reason}`); +} + +const RPC_TIMEOUT_MS = { sendtoaddress: 120000, listtransactions: 30000, gettransaction: 15000 }; +const ENGINE_RPC_TIMEOUT_MS = 20000; +async function rpcCall(method, params, kind = "user", timeoutMs = 0) { const cfg = getWalletCfg(kind); if (!cfg?.url) { return null; @@ -145,7 +251,7 @@ async function rpcCall(method, params, kind = "user") { headers.authorization = "Basic " + Buffer.from(`${cfg.user}:${cfg.pass}`).toString("base64"); } const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), 1500); + const timer = setTimeout(() => controller.abort(), timeoutMs || RPC_TIMEOUT_MS[method] || 1500); try { const res = await fetch(cfg.url, { method: "POST", @@ -159,23 +265,78 @@ async function rpcCall(method, params, kind = "user") { signal: controller.signal, }); if (!res.ok) { + logRpcFailure(method, kind, `HTTP ${res.status}`); return null; } const data = await res.json(); if (data.error) { + logRpcFailure(method, kind, data.error.message || JSON.stringify(data.error)); return null; } return data.result; } catch (err) { + logRpcFailure(method, kind, err && err.name === "AbortError" ? "timeout" : (err && err.message) || String(err)); return null; } finally { clearTimeout(timer); } } -async function safeGetBalance(kind = "user") { +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function listWalletSends(kind = "pub") { + for (const account of ["*", ""]) { + const list = await rpcCall("listtransactions", [account, 1000, 0], kind); + if (Array.isArray(list)) return list.filter(t => t && t.category === "send"); + } + return null; +} + +const sameAmount = (a, b) => Math.abs(Math.abs(Number(a)) - Math.abs(Number(b))) <= 0.000001; + +function sendMatches(t, { address, amount, comment, sinceMs, amountIfNoComment }) { + if (!t || t.address !== address || typeof t.txid !== "string") return false; + const at = Number(t.time || t.timereceived || 0) * 1000; + if (sinceMs && at && at < sinceMs - 5 * 60 * 1000) return false; + if (amount !== undefined && amount !== null && !sameAmount(t.amount, amount)) return false; + if (comment) { + if (typeof t.comment === "string") { if (t.comment !== comment) return false; } + else if (amountIfNoComment !== undefined && amountIfNoComment !== null && !sameAmount(t.amount, amountIfNoComment)) return false; + } + return true; +} + +async function findWalletSend(match, kind = "pub", sends = null) { + const list = sends || await listWalletSends(kind); + if (!list) return undefined; + const hit = list.find(t => sendMatches(t, match) && !(match.exclude && match.exclude.has(t.txid))); + return hit ? hit.txid : null; +} + +async function sendVerified(address, amount, comment, kind = "pub") { + const sinceMs = Date.now(); + const txid = await rpcCall("sendtoaddress", [address, amount, comment], kind); + if (typeof txid === "string" && txid) return { txid }; + await sleep(3000); + const found = await findWalletSend({ address, amount, comment, sinceMs }, kind); + if (typeof found === "string") return { txid: found, recovered: true }; + return { txid: null, unknown: found === undefined }; +} + +async function tryGetBalance(kind = "user", timeoutMs = 0) { try { - const r = await rpcCall("getbalance", [], kind); + const r = await rpcCall("getbalance", [], kind, timeoutMs); + if (r === null || r === undefined) return null; + const n = Number(r); + return Number.isFinite(n) ? n : null; + } catch { + return null; + } +} + +async function safeGetBalance(kind = "user", timeoutMs = 0) { + try { + const r = await rpcCall("getbalance", [], kind, timeoutMs); return Number(r) || 0; } catch { return 0; @@ -193,6 +354,35 @@ function writeAddrMap(m) { writeJson(ADDR_PATH, m || {}); } +function readAddressBook() { + const raw = readJson(BOOK_PATH, []); + return Array.isArray(raw) ? raw : []; +} + +function listAddressBook() { + return readAddressBook().map(e => ({ entryId: e.id, id: e.userId || null, address: e.address, label: e.label || "", source: "book", createdAt: e.createdAt })); +} + +function addAddressBookEntry({ label, address, userId }) { + if (!isValidEcoinAddress(address)) return { status: "invalid" }; + const book = readAddressBook(); + const cleanLabel = String(label || "").trim().slice(0, 80); + const cleanUser = /^@[A-Za-z0-9+/]+={0,2}\.ed25519$/.test(String(userId || "")) ? String(userId) : null; + const dup = book.find(e => e.address === address && (e.label || "") === cleanLabel && (e.userId || null) === cleanUser); + if (dup) return { status: "exists" }; + book.push({ id: crypto.randomBytes(6).toString("hex"), label: cleanLabel, address, userId: cleanUser, createdAt: new Date().toISOString() }); + writeJson(BOOK_PATH, book); + return { status: "added" }; +} + +function removeAddressBookEntry(entryId) { + const book = readAddressBook(); + const next = book.filter(e => e.id !== String(entryId || "")); + if (next.length === book.length) return { status: "not_found" }; + writeJson(BOOK_PATH, next); + return { status: "deleted" }; +} + function getLogLimit() { return getConfig().ssbLogStream?.limit || 1000; } @@ -201,6 +391,11 @@ function isValidEcoinAddress(addr) { return typeof addr === "string" && /^E[1-9A-HJ-NP-Za-km-z]{32,34}$/.test(addr); } +function hasWalletCredentials(cfg = null) { + const w = (cfg || getConfig() || {}).wallet || {}; + return !!(String(w.url || "").trim() && String(w.user || "").trim() && String(w.pass || "").trim()); +} + function getWalletCfg(kind) { const cfg = getConfig() || {}; if (kind === "pub") { @@ -211,14 +406,36 @@ function getWalletCfg(kind) { } function isPubNode() { - const pubId = (getConfig() || {}).walletPub?.pubId || ""; - const myId = config?.keys?.id || ""; - return !!pubId && !!myId && pubId === myId; + const cfg = getConfig() || {}; + return config?.pub === true && !!(cfg.wallet && cfg.wallet.url) && !!config?.keys?.id; } -function getConfiguredPubId() { - return (getConfig() || {}).walletPub?.pubId || ""; +function getDefaultPubId() { + const m = String((defaultInvite() || {}).code || "").match(/(@[A-Za-z0-9+/]+={0,2}\.ed25519)/); + return m ? m[1] : ""; } +const PUB_DISCOVERY_TTL_MS = 60 * 1000; +const PUB_ANNOUNCE_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000; +const PUB_ANNOUNCE_REFRESH_MS = 12 * 60 * 60 * 1000; +const CLAIMANT_MIN_AGE_MS = 30 * 24 * 60 * 60 * 1000; +const REBALANCE_MIN_CLAIMANTS = 3; +const REBALANCE_PAYOUT_RATIO = 0.5; +const REBALANCE_TOLERANCE = 0.05; +const REBALANCE_SETTLE_MS = 6 * 60 * 60 * 1000; +const REBALANCE_CONCEPT = "OASIS UBI Rebalance"; +const UBI_PAYMENT_CONCEPT = "UBI - "; +const UBI_PAYMENT_PREFIXES = ["UBI - ", "OASIS UBI Payment"]; +const UBI_SEND_COMMENT = "OASIS UBI Payment"; +const UBI_TAKEOVER_AFTER_MS = 3 * 24 * 60 * 60 * 1000; +const UBI_TAKEOVER_MARGIN_MS = 12 * 60 * 60 * 1000; +const UBI_MAX_ATTEMPTS = 6; +const UBI_RETRY_AFTER_MS = 60 * 60 * 1000; +const isUbiPayoutConcept = (c) => UBI_PAYMENT_PREFIXES.some(p => String(c || "").startsWith(p)); +const TXID_RE = /\b([0-9a-f]{64})\b/i; +const OWN_SCORE_CAP = 200; +const RECEIVED_AUTHOR_MIN_AGE_MS = 30 * 24 * 60 * 60 * 1000; +const NEGATIVE_OPINIONS = new Set(["boring", "spam", "propaganda"]); +const ACTIONS_CACHE_TTL_MS = 60 * 1000; function resolveUserId(maybeId) { const s = String(maybeId || "").trim(); @@ -260,38 +477,53 @@ module.exports = ({ services } = {}) => { return ssbInstance; } + const BANKING_TYPES = ["wallet", "karmaScore", "ubiClaim", "ubiClaimResult", "ubiAllocation", "ubiRefuse", "pubAvailability", "tombstone"]; + async function scanLogStream() { const ssb = await openSsb(); if (!ssb) return []; - return new Promise((resolve, reject) => - pull( - ssb.createLogStream({ limit: getLogLimit(), reverse: true }), - pull.collect((err, arr) => err ? reject(err) : resolve(arr)) - ) - ); + return readTyped(ssb, BANKING_TYPES, { limit: getLogLimit(), withWindow: true }); + } + + const claimsInFlight = new Set(); + + const isWalletMessage = (c) => !!c && typeof c.address === "string" && isValidEcoinAddress(c.address) && + ((c.type === "wallet" && c.coin === "ECO") || c.type === "bankWallet"); + const isWalletUnset = (c) => !!c && typeof c.address === "string" && c.address === "" && + ((c.type === "wallet" && c.coin === "ECO") || c.type === "bankWallet"); + + async function scanWalletMessages() { + const ssb = await openSsb(); + if (!ssb) return []; + const msgs = await readTyped(ssb, ["wallet", "bankWallet"], { limit: Math.max(getLogLimit(), 20000) }); + return msgs.slice().sort((a, b) => (((b.value && b.value.timestamp) || 0) - ((a.value && a.value.timestamp) || 0)) || (((b.value && b.value.sequence) || 0) - ((a.value && a.value.sequence) || 0))); } async function getWalletFromSSB(userId) { - const msgs = await scanLogStream(); + const msgs = await scanWalletMessages(); for (const m of msgs) { const v = m.value || {}; - const c = v.content || {}; - if (v.author === userId && c && c.type === "wallet" && c.coin === "ECO" && typeof c.address === "string") { - return c.address; - } + if (v.author !== userId) continue; + if (isWalletMessage(v.content)) return v.content.address; + if (isWalletUnset(v.content)) return null; } return null; } + async function hasPublishedAddress(userId) { + return !!(await getWalletFromSSB(userId)); + } + async function scanAllWalletsSSB() { const latest = {}; - const msgs = await scanLogStream(); + const decided = new Set(); + const msgs = await scanWalletMessages(); for (const m of msgs) { const v = m.value || {}; const c = v.content || {}; - if (c && c.type === "wallet" && c.coin === "ECO" && typeof c.address === "string") { - if (!latest[v.author]) latest[v.author] = c.address; - } + if (!v.author || decided.has(v.author)) continue; + if (isWalletMessage(c)) { latest[v.author] = c.address; decided.add(v.author); } + else if (isWalletUnset(c)) decided.add(v.author); } return latest; } @@ -347,6 +579,7 @@ module.exports = ({ services } = {}) => { if (ssbAll[userId]) { m[userId] = "__removed__"; writeAddrMap(m); + if (idsEqual(userId, config.keys.id)) { try { await publishSelfAddress(""); } catch (_) {} } return { status: "deleted" }; } if (hadLocal) { @@ -453,30 +686,18 @@ module.exports = ({ services } = {}) => { return s; } + let actionsCache = null; async function listAllActions() { - if (services?.feed?.listAll) { - const arr = await services.feed.listAll(); - FEED_SRC = "services.feed.listAll"; - return normalizeFeedArray(arr); - } - if (services?.activity?.list) { - const arr = await services.activity.list(); - FEED_SRC = "services.activity.list"; - return normalizeFeedArray(arr); - } - if (typeof global.listFeed === "function") { - const arr = await global.listFeed("all"); - FEED_SRC = "global.listFeed('all')"; - return normalizeFeedArray(arr); - } const ssb = await openSsb(); - if (!ssb || !ssb.createLogStream) { + if (!ssb) { FEED_SRC = "none"; return []; } - const msgs = await scanLogStream(); - FEED_SRC = "ssb.createLogStream"; - return msgs.map(m => { + const ownSeq = await ownFeedSeq(ssb); + if (actionsCache && actionsCache.ownSeq === ownSeq && Date.now() - actionsCache.ts < ACTIONS_CACHE_TTL_MS) return actionsCache.value; + const msgs = await readTyped(ssb, CONTENT_TYPES, { limit: Math.max(getLogLimit(), 20000) }); + FEED_SRC = "messagesByType"; + const value = msgs.map(m => { const v = m.value || {}; const c = v.content || {}; return { @@ -484,25 +705,31 @@ module.exports = ({ services } = {}) => { author: v.author, type: (c.type || "").toLowerCase(), value: v, - content: c + content: c, + receivedAt: Number(m.timestamp) || 0 }; }); + actionsCache = { ts: Date.now(), ownSeq, value }; + return value; } - function normalizeFeedArray(arr) { - if (!Array.isArray(arr)) return []; - return arr.map(x => { - const value = x.value || {}; - const content = x.content || value.content || {}; - const author = x.author || value.author || content.author || null; - const type = (content.type || "").toLowerCase(); - return { id: x.id || value.key || x.key, author, type, value, content }; - }); - } + + +async function ownFeedSeq(ssb) { + return new Promise((res) => { + try { + pull( + ssb.createUserStream({ id: ssb.id, reverse: true, limit: 1 }), + pull.collect((err, msgs) => res(err || !msgs || !msgs.length ? 0 : (msgs[0].value && msgs[0].value.sequence) || 0)) + ); + } catch (_) { res(0); } + }); +} async function publishKarmaScore(userId, karmaScore) { const ssb = await openSsb(); if (!ssb || !ssb.publish) return false; + if (!(await ownFeedSeq(ssb))) return false; const timestamp = new Date().toISOString(); const content = { type: "karmaScore", karmaScore, userId, timestamp }; return new Promise((resolve, reject) => { @@ -524,79 +751,158 @@ async function fetchUserActions(userId) { }); } +function basePointsFor(action) { + const t = normalizeType(action); + const c = action.content || {}; + const rawType = String(c.type || "").toLowerCase(); + if (t === "post") return 10; + else if (t === "comment") return 5; + else if (t === "like") return 2; + else if (t === "image") return 8; + else if (t === "video") return 12; + else if (t === "audio") return 8; + else if (t === "document") return 6; + else if (t === "bookmark") return 2; + else if (t === "feed") return 6; + else if (t === "forum") return (c.root ? 5 : 10); + else if (t === "vote") return (3 + calculateOpinionScore(c)); + else if (t === "votes") return Math.min(10, Number(c.totalVotes || 0)); + else if (t === "market") return scoreMarket(c); + else if (t === "project") return scoreProject(c); + else if (t === "tribe") return (6 + Math.min(10, Array.isArray(c.members) ? c.members.length * 0.5 : 0)); + else if (t === "event") return (4 + Math.min(10, Array.isArray(c.attendees) ? c.attendees.length : 0)); + else if (t === "task") return (3 + priorityBump(c.priority)); + else if (t === "report") return (4 + (Array.isArray(c.confirmations) ? c.confirmations.length : 0) + severityBump(c.severity)); + else if (t === "curriculum") return 5; + else if (t === "wikipage") return (c.replaces ? 2 : 6); + else if (t === "emergency") return (c.replaces ? 1 : 5); + else if (t === "emergencyconfirm") return 1; + else if (t === "emergencyupdate") return 2; + else if (t === "mailinglist") return (c.replaces ? 1 : 3); + else if (t === "logisticsroute") return (c.replaces ? 1 : 4); + else if (t === "logisticsrating") return 1; + else if (t === "podcast") return (c.replaces ? 1 : 5); + else if (t === "podcastepisode") return (c.replaces ? 1 : 4); + else if (t === "podcastopinion" || t === "podcastplay" || t === "campaignopinion" || t === "logisticsopinion") return 1; + else if (t === "campaign") return (c.replaces ? 1 : 5); + else if (t === "campaignsignature") return 1; + else if (t === "campaignupdate") return 2; + else if (t === "aiexchange") return (Array.isArray(c.ctx) ? Math.min(10, c.ctx.length) : 0); + else if (t === "job") return (4 + (Array.isArray(c.subscribers) ? c.subscribers.length : 0)); + else if (t === "transfer") return 1; + else if (t === "about") return (1 + ((c.visibilityPrefs && c.visibilityPrefs.fediverseHandle) ? 8 : 0)); + else if (t === "contact") return 1; + else if (t === "pub") return 1; + else if (t === "parliamentcandidature" || rawType === "parliamentcandidature") return 12; + else if (t === "parliamentterm" || rawType === "parliamentterm") return 25; + else if (t === "parliamentproposal" || rawType === "parliamentproposal") return 8; + else if (t === "parliamentlaw" || rawType === "parliamentlaw") return 16; + else if (t === "parliamentrevocation" || rawType === "parliamentrevocation") return 10; + else if (t === "courts_case" || t === "courtscase" || rawType === "courts_case") return 4; + else if (t === "courts_evidence" || t === "courtsevidence" || rawType === "courts_evidence") return 3; + else if (t === "courts_answer" || t === "courtsanswer" || rawType === "courts_answer") return 4; + else if (t === "courts_verdict" || t === "courtsverdict" || rawType === "courts_verdict") return 10; + else if (t === "courts_settlement" || t === "courtssettlement" || rawType === "courts_settlement") return 8; + else if (t === "courts_nomination" || t === "courtsnomination" || rawType === "courts_nomination") return 6; + else if (t === "courts_nom_vote" || t === "courtsnomvote" || rawType === "courts_nom_vote") return 3; + else if (t === "courts_public_pref" || t === "courtspublicpref" || rawType === "courts_public_pref") return 1; + else if (t === "courts_mediators" || t === "courtsmediators" || rawType === "courts_mediators") return 6; + else if (t === "courts_open_support" || t === "courtsopensupport" || rawType === "courts_open_support") return 2; + else if (t === "courts_verdict_vote" || t === "courtsverdictvote" || rawType === "courts_verdict_vote") return 3; + else if (t === "courts_judge_assign" || t === "courtsjudgeassign" || rawType === "courts_judge_assign") return 5; + else if (t === "larpjoinhouse") return 15; + else if (t === "larphousepost") return 6; + else if (t === "larptestattempt") return 3; + else if (t === "torrent") return 6; + else if (t === "file") return 6; + else if (t === "shop" || t === "shopproduct") return 6; + else if (t === "shop-purchase") return 2; + else if (t === "pad" || t === "padentry") return 3; + else if (t === "room") return 3; + else if (t === "roommember") return 1; + else if (t === "calendar" || t === "calendarnote" || t === "calendardate") return 3; + else if (t === "chat") return 1; + else if (t === "gamescore") return 2; + else if (t === "pixelia") return 2; + else if (rawType === "industry") return 8; + else if (rawType === "industryblueprint") return 6; + else if (rawType === "industrybuild") return 8; + else if (rawType === "industrycontribution") return 4; + else if (rawType === "industryallocation") return 10; + else if (rawType === "industryvote") return 2; + else if (rawType === "industrymember") return 2; + else if (rawType === "industryopinion") return 1; + else if (rawType === "schoolcourse") return 10; + else if (rawType === "schoollesson") return 6; + else if (rawType === "schoolenroll") return 3; + else if (rawType === "schoolcertificate") return 12; + else if (rawType === "schoolexam") return 6; + else if (rawType === "schoolprogress") return 1; + else if (rawType === "schoolopinion") return 2; + return 0; +} + +function decayFor(ts, nowMs) { + const ageDays = ts ? (nowMs - ts) / 86400000 : Infinity; + return ageDays <= 30 ? 1.0 : ageDays <= 90 ? 0.5 : 0.25; +} + function scoreFromActions(actions) { let score = 0; const nowMs = Date.now(); + const perTypeDay = new Map(); for (const action of actions) { - const t = normalizeType(action); - const c = action.content || {}; - const rawType = String(c.type || "").toLowerCase(); + const pts = basePointsFor(action); + if (!pts) continue; const ts = action.value?.timestamp; - const ageDays = ts ? (nowMs - ts) / 86400000 : Infinity; - const decay = ageDays <= 30 ? 1.0 : ageDays <= 90 ? 0.5 : 0.25; - if (t === "post") score += 10 * decay; - else if (t === "comment") score += 5 * decay; - else if (t === "like") score += 2 * decay; - else if (t === "image") score += 8 * decay; - else if (t === "video") score += 12 * decay; - else if (t === "audio") score += 8 * decay; - else if (t === "document") score += 6 * decay; - else if (t === "bookmark") score += 2 * decay; - else if (t === "feed") score += 6 * decay; - else if (t === "forum") score += (c.root ? 5 : 10) * decay; - else if (t === "vote") score += (3 + calculateOpinionScore(c)) * decay; - else if (t === "votes") score += Math.min(10, Number(c.totalVotes || 0)) * decay; - else if (t === "market") score += scoreMarket(c) * decay; - else if (t === "project") score += scoreProject(c) * decay; - else if (t === "tribe") score += (6 + Math.min(10, Array.isArray(c.members) ? c.members.length * 0.5 : 0)) * decay; - else if (t === "event") score += (4 + Math.min(10, Array.isArray(c.attendees) ? c.attendees.length : 0)) * decay; - else if (t === "task") score += (3 + priorityBump(c.priority)) * decay; - else if (t === "report") score += (4 + (Array.isArray(c.confirmations) ? c.confirmations.length : 0) + severityBump(c.severity)) * decay; - else if (t === "curriculum") score += 5 * decay; - else if (t === "aiexchange") score += (Array.isArray(c.ctx) ? Math.min(10, c.ctx.length) : 0) * decay; - else if (t === "job") score += (4 + (Array.isArray(c.subscribers) ? c.subscribers.length : 0)) * decay; - else if (t === "bankclaim") score += Math.min(20, Math.log(1 + Math.max(0, Number(c.amount) || 0)) * 5) * decay; - else if (t === "bankwallet") score += 2 * decay; - else if (t === "transfer") score += 1 * decay; - else if (t === "about") score += (1 + ((c.visibilityPrefs && c.visibilityPrefs.fediverseHandle) ? 8 : 0)) * decay; - else if (t === "contact") score += 1 * decay; - else if (t === "pub") score += 1 * decay; - else if (t === "parliamentcandidature" || rawType === "parliamentcandidature") score += 12 * decay; - else if (t === "parliamentterm" || rawType === "parliamentterm") score += 25 * decay; - else if (t === "parliamentproposal" || rawType === "parliamentproposal") score += 8 * decay; - else if (t === "parliamentlaw" || rawType === "parliamentlaw") score += 16 * decay; - else if (t === "parliamentrevocation" || rawType === "parliamentrevocation") score += 10 * decay; - else if (t === "courts_case" || t === "courtscase" || rawType === "courts_case") score += 4 * decay; - else if (t === "courts_evidence" || t === "courtsevidence" || rawType === "courts_evidence") score += 3 * decay; - else if (t === "courts_answer" || t === "courtsanswer" || rawType === "courts_answer") score += 4 * decay; - else if (t === "courts_verdict" || t === "courtsverdict" || rawType === "courts_verdict") score += 10 * decay; - else if (t === "courts_settlement" || t === "courtssettlement" || rawType === "courts_settlement") score += 8 * decay; - else if (t === "courts_nomination" || t === "courtsnomination" || rawType === "courts_nomination") score += 6 * decay; - else if (t === "courts_nom_vote" || t === "courtsnomvote" || rawType === "courts_nom_vote") score += 3 * decay; - else if (t === "courts_public_pref" || t === "courtspublicpref" || rawType === "courts_public_pref") score += 1 * decay; - else if (t === "courts_mediators" || t === "courtsmediators" || rawType === "courts_mediators") score += 6 * decay; - else if (t === "courts_open_support" || t === "courtsopensupport" || rawType === "courts_open_support") score += 2 * decay; - else if (t === "courts_verdict_vote" || t === "courtsverdictvote" || rawType === "courts_verdict_vote") score += 3 * decay; - else if (t === "courts_judge_assign" || t === "courtsjudgeassign" || rawType === "courts_judge_assign") score += 5 * decay; - else if (t === "larpjoinhouse") score += 15 * decay; - else if (t === "larphousepost") score += 6 * decay; - else if (t === "larptestattempt") score += 3 * decay; - else if (t === "torrent") score += 6 * decay; - else if (t === "shop" || t === "shopproduct") score += 6 * decay; - else if (t === "shop-purchase") score += 2 * decay; - else if (t === "pad" || t === "padentry") score += 3 * decay; - else if (t === "calendar" || t === "calendarnote" || t === "calendardate") score += 3 * decay; - else if (t === "chat") score += 1 * decay; - else if (t === "gamescore") score += 2 * decay; - else if (t === "pixelia") score += 2 * decay; - else if (rawType === "industry") score += 8 * decay; - else if (rawType === "industryblueprint") score += 6 * decay; - else if (rawType === "industrybuild") score += 8 * decay; - else if (rawType === "industrycontribution") score += 4 * decay; - else if (rawType === "industryallocation") score += 10 * decay; - else if (rawType === "industryvote") score += 2 * decay; - else if (rawType === "industrymember") score += 2 * decay; - else if (rawType === "industryopinion") score += 1 * decay; + const dayKey = `${normalizeType(action)}:${ts ? new Date(ts).toISOString().slice(0, 10) : "?"}`; + const n = (perTypeDay.get(dayKey) || 0) + 1; + perTypeDay.set(dayKey, n); + score += pts * decayFor(ts, nowMs) / n; + } + return Math.max(0, Math.round(Math.min(OWN_SCORE_CAP, score))); +} + +function receivedScoreFor(userId, actions) { + const nowMs = Date.now(); + const mine = new Set(); + const firstTs = new Map(); + for (const a of actions) { + if (!a.author) continue; + const ts = Number(a.receivedAt) || 0; + if (ts && (!firstTs.has(a.author) || ts < firstTs.get(a.author))) firstTs.set(a.author, ts); + if (a.author === userId && a.id) mine.add(a.id); + } + const seen = new Set(); + const perTarget = new Map(); + let score = 0; + const add = (author, kind, target, pts, ts, capPerTarget = Infinity) => { + const k = `${kind}:${author}:${target}`; + if (seen.has(k)) return; + const n = perTarget.get(`${kind}:${target}`) || 0; + if (n >= capPerTarget) return; + seen.add(k); + perTarget.set(`${kind}:${target}`, n + 1); + score += pts * decayFor(ts, nowMs); + }; + for (const a of actions) { + const b = a.author; + if (!b || b === userId) continue; + if (!firstTs.has(b) || nowMs - firstTs.get(b) < RECEIVED_AUTHOR_MIN_AGE_MS) continue; + const c = a.content || {}; + const type = String(c.type || ""); + const ts = a.value?.timestamp; + if (/Opinion$/.test(type) && type !== "pollOpinion" && mine.has(c.target)) add(b, "opinion", c.target, NEGATIVE_OPINIONS.has(String(c.category || "").toLowerCase()) ? -3 : 3, ts); + else if (type === "vote" && c.vote && mine.has(c.vote.link)) add(b, "opinion", c.vote.link, Number(c.vote.value) > 0 ? 3 : -3, ts); + else if (type === "feed-action" && c.action === "vote" && mine.has(c.root || c.target)) add(b, "opinion", c.root || c.target, 3, ts); + else if (type === "feed-action" && c.action === "comment" && mine.has(c.root)) add(b, "comment", c.root, 2, ts, 10); + else if (type === "feed-action" && c.action === "refeed" && mine.has(c.root)) add(b, "refeed", c.root, 2, ts, 10); + else if (type === "post" && c.root && mine.has(c.root)) add(b, "comment", c.root, 2, ts, 10); + else if (type === "contact" && c.following === true && c.contact === userId) add(b, "follow", userId, 2, ts); + else if (type === "marketPurchase" && mine.has(c.target)) add(b, "purchase", c.target, 5, ts); + else if (type === "shop-purchase" && c.seller === userId) add(b, "purchase", c.productId || a.id, 5, ts); + else if (type === "schoolEnroll" && c.value === true && mine.has(c.courseId)) add(b, "enroll", c.courseId, 5, ts); + else if (type === "transferConfirm" && mine.has(c.target)) add(b, "confirm", c.target, 3, ts); } return Math.max(0, Math.round(score)); } @@ -632,7 +938,7 @@ async function getBytesForUser(userId) { } let _ecoTaxStatsCache = null; -const ECO_TAX_STATS_TTL_MS = 60 * 1000; +const ECO_TAX_STATS_TTL_MS = 10 * 60 * 1000; async function calculateEcoTaxStatsInternal() { const ssb = await (async () => { @@ -710,12 +1016,14 @@ async function calculateEcoTaxStatsInternal() { }); } +let _ecoTaxStatsPending = null; async function calculateEcoTaxStats() { const now = Date.now(); if (_ecoTaxStatsCache && (now - _ecoTaxStatsCache.ts) < ECO_TAX_STATS_TTL_MS) { return _ecoTaxStatsCache.value; } - const value = await calculateEcoTaxStatsInternal().catch(() => null); + if (!_ecoTaxStatsPending) _ecoTaxStatsPending = calculateEcoTaxStatsInternal().catch(() => null).finally(() => { _ecoTaxStatsPending = null; }); + const value = await _ecoTaxStatsPending; if (value) _ecoTaxStatsCache = { ts: now, value }; return value || { totalBlocks: 0, totalBytes: 0, totalGramsCO2: 0, @@ -744,7 +1052,7 @@ async function getUserFirstBlockTs(userId) { pull( ssb.createUserStream({ id: userId, limit: 1 }), pull.drain( - (m) => { if (m && m.value && m.value.timestamp && !first) first = Number(m.value.timestamp); }, + (m) => { if (m && m.value && !first) first = Number(m.timestamp) || 0; }, () => resolve(first || 0) ) ); @@ -791,11 +1099,47 @@ async function getIndustryBalance(userId) { }; } +async function getSchoolBalance(userId) { + const uid = resolveUserId(userId); + const ssb = await openSsb(); + const collect = (type) => new Promise((resolve) => { + if (!ssb.messagesByType) return resolve([]); + pull(ssb.messagesByType({ type }), pull.collect((err, msgs) => resolve(err ? [] : msgs))); + }); + const [transfers, confirms] = await Promise.all([collect("transfer"), collect("transferConfirm")]); + const confirmsByTarget = new Map(); + for (const m of confirms) { + const c = m.value && m.value.content; + if (!c || !c.target) continue; + if (!confirmsByTarget.has(c.target)) confirmsByTarget.set(c.target, new Set()); + if (m.value.author) confirmsByTarget.get(c.target).add(m.value.author); + } + const monthKey = new Date().toISOString().slice(0, 7); + let received = 0; + let lifetime = 0; + for (const m of transfers) { + const c = m.value && m.value.content; + if (!c || c.to !== uid) continue; + const tags = Array.isArray(c.tags) ? c.tags.map(t => String(t).toUpperCase()) : []; + if (!tags.includes("SCHOOL")) continue; + const signatures = new Set(Array.isArray(c.confirmedBy) ? c.confirmedBy : []); + if (c.from) signatures.add(c.from); + for (const a of (confirmsByTarget.get(m.key) || [])) signatures.add(a); + if (signatures.size < 2) continue; + const amount = parseFloat(c.amount); + if (!Number.isFinite(amount) || amount <= 0) continue; + lifetime += amount; + if (String(c.createdAt || "").slice(0, 7) === monthKey) received += amount; + } + return { received: Number(received.toFixed(6)), lifetime: Number(lifetime.toFixed(6)), net: Number(received.toFixed(6)) }; +} + async function getUserEngagementScore(userId) { const ssb = await openSsb(); const uid = resolveUserId(userId); const actions = await fetchUserActions(uid); - const rawKarma = scoreFromActions(actions); + const all = await listAllActions(); + const rawKarma = scoreFromActions(actions) + receivedScoreFor(uid, all); const carbonGrams = await getCarbonGramsForUser(uid).catch(() => 0); const karmaScore = Math.max(0, Math.round(rawKarma - carbonGrams)); @@ -878,10 +1222,11 @@ async function getLastPublishedTimestamp(userId) { } async function computeEpoch({ epochId, userId, rules = DEFAULT_RULES }) { - const pubBal = await safeGetBalance("pub"); + const pubBal = await safeGetBalance("pub", ENGINE_RPC_TIMEOUT_MS); const pv = computePoolVars(pubBal, rules); const addresses = await listAddressesMerged(); - const eligible = addresses.filter(a => a.address && isValidEcoinAddress(a.address)); + const pubIds = await knownPubIds(); + const eligible = addresses.filter(a => a.address && isValidEcoinAddress(a.address) && a.id !== config.keys.id && !pubIds.has(a.id)); const capUser = rules.caps?.cap_user_epoch ?? DEFAULT_RULES.caps.cap_user_epoch; const wMin = rules.caps?.w_min ?? DEFAULT_RULES.caps.w_min; const wMax = rules.caps?.w_max ?? DEFAULT_RULES.caps.w_max; @@ -936,7 +1281,10 @@ async function getLastPublishedTimestamp(userId) { from: config.keys.id, to: a.user, amount: a.amount, - concept: `UBI ${eid}`, + ecoTax: a.ecoTax, + archTax: a.archTax, + totalTax: a.totalTax, + concept: `${UBI_PAYMENT_CONCEPT}${eid}`, status: "UNCLAIMED", createdAt: new Date().toISOString(), deadline: new Date(Date.now() + ((rules.graceDays ?? DEFAULT_RULES.graceDays) * 86400000)).toISOString(), @@ -961,25 +1309,78 @@ async function getLastPublishedTimestamp(userId) { if (claimerId && allocation.to !== claimerId) throw new Error("This allocation is not for you."); const addr = await getUserAddress(allocation.to); if (!addr || !isValidEcoinAddress(addr)) throw new Error("No valid ECOin address registered."); - const txid = await rpcCall("sendtoaddress", [addr, allocation.amount, `UBI ${allocation.concept || "claim"}`], "pub"); - if (!txid) throw new Error("RPC sendtoaddress failed. Check PUB wallet configuration."); + const epochId = String((allocation.tags || []).find(t => String(t).startsWith("epoch:")) || "").replace(/^epoch:/, "") || epochIdNow(); + const paidKey = ubiPaidKey(epochId, allocation.to); + const pubBal = await safeGetBalance("pub", ENGINE_RPC_TIMEOUT_MS); + if (computePoolVars(pubBal, DEFAULT_RULES).available < Number(allocation.amount)) throw new Error("The PUB wallet is at its reserve."); + if (ubiAddressTaken(epochId, addr, allocation.to)) throw new Error("This ECOin address was already paid this epoch."); + if (!reserveUbiPayment(paidKey)) throw new Error("Invalid allocation or already claimed."); + settleUbiPayment(paidKey, { attempts: 1, lastAttemptAt: new Date().toISOString(), amount: allocation.amount, address: addr }); + const sent = await sendVerified(addr, allocation.amount, UBI_SEND_COMMENT, "pub"); + const txid = sent.txid; + if (!txid) { + if (sent.unknown) settleUbiPayment(paidKey, { error: "the wallet did not answer" }); + else releaseUbiPayment(paidKey); + throw new Error("RPC sendtoaddress failed. Check PUB wallet configuration."); + } + settleUbiPayment(paidKey, { txid, paidAt: new Date().toISOString() }); await transfersRepo.markClosed(transferId, txid); + console.log(`[UBI] paid ${allocation.amount} ECO to ${String(allocation.to).slice(0, 12)}… (${epochId}) tx ${txid}`); + try { await publishUbiClaimResult(transferId, epochId, txid, allocation.to, allocation.amount, addr); } catch (_) {} + try { await publishUbiTransfer({ to: allocation.to, amount: allocation.amount, epochId, txid }); } catch (_) {} return { txid }; } + async function publishUbiTransfer({ to, amount, epochId, txid }) { + const ssb = await openSsb(); + if (!ssb || !ssb.publish) return; + const now = new Date().toISOString(); + const content = { + type: "transfer", + from: config.keys.id, + to, + concept: `${UBI_PAYMENT_CONCEPT}${epochId}`, + amount: Number(amount).toFixed(6), + category: "ECONOMIC", + createdAt: now, + updatedAt: now, + deadline: null, + confirmedBy: [config.keys.id, to], + status: "CLOSED", + tags: ["UBI", `epoch:${epochId}`], + opinions: {}, + opinions_inhabitants: [], + txid + }; + const published = await new Promise((resolve, reject) => ssb.publish(content, (err, msg) => err ? reject(err) : resolve(msg))); + if (services && typeof services.notifyUbiPaid === "function") { + try { await services.notifyUbiPaid({ to, amount: Number(amount).toFixed(6), epochId, txid, transferKey: published && published.key ? published.key : null }); } catch (_) {} + } + return published; + } + async function claimUBI(userId) { const uid = resolveUserId(userId); const epochId = epochIdNow(); - const pubId = getConfiguredPubId(); - if (!pubId) throw new Error("no_pub_configured"); - const alreadyClaimed = await hasClaimedThisMonth(uid); - if (alreadyClaimed) throw new Error("already_claimed"); - const ssb = await openSsb(); - if (!ssb || !ssb.publish) throw new Error("ssb_unavailable"); - const now = new Date().toISOString(); - const claimContent = { type: "ubiClaim", pubId, epochId, claimedAt: now }; - await new Promise((resolve, reject) => ssb.publish(claimContent, (err, res) => err ? reject(err) : resolve(res))); - return { status: "claimed_pending", epochId }; + const lockKey = ubiPaidKey(epochId, uid); + if (isPubNode()) throw new Error("pub"); + if (claimsInFlight.has(lockKey)) throw new Error("already_claimed"); + claimsInFlight.add(lockKey); + try { + const pubId = await resolvePubId(); + if (!pubId) throw new Error("no_pub_configured"); + const alreadyClaimed = await hasClaimedThisMonth(uid); + if (alreadyClaimed) throw new Error("already_claimed"); + if (await hasRefusedThisMonth(uid)) throw new Error("already_refused"); + const ssb = await openSsb(); + if (!ssb || !ssb.publish) throw new Error("ssb_unavailable"); + const now = new Date().toISOString(); + const claimContent = { type: "ubiClaim", pubId, epochId, claimedAt: now }; + await new Promise((resolve, reject) => ssb.publish(claimContent, (err, res) => err ? reject(err) : resolve(res))); + return { status: "claimed_pending", epochId }; + } finally { + claimsInFlight.delete(lockKey); + } } async function updateAllocationStatus(allocationId, status, txid) { @@ -997,13 +1398,107 @@ async function getLastPublishedTimestamp(userId) { } } - async function hasClaimedThisMonth(userId) { + async function hasRefusedThisMonth(userId) { const epochId = epochIdNow(); const msgs = await scanLogStream(); + return msgs.some(m => m.value && m.value.author === userId && m.value.content && m.value.content.type === "ubiRefuse" && m.value.content.epochId === epochId); + } + + async function refuseUBI(userId) { + const uid = resolveUserId(userId); + const epochId = epochIdNow(); + if (await hasClaimedThisMonth(uid)) throw new Error("already_claimed"); + if (await hasRefusedThisMonth(uid)) throw new Error("already_refused"); + const pubId = await resolvePubId(); + const ssb = await openSsb(); + if (!ssb || !ssb.publish) throw new Error("ssb_unavailable"); + const content = { type: "ubiRefuse", pubId, epochId, refusedAt: new Date().toISOString() }; + await new Promise((resolve, reject) => ssb.publish(content, (err, res) => err ? reject(err) : resolve(res))); + return { status: "refused", epochId }; + } + + async function claimAvailability(userId) { + const uid = resolveUserId(userId); + if (isPubNode()) return { available: false, reason: "pub" }; + const pub = await discoverUbiPub(); + if (!pub.pubId || !pub.available) return { available: false, reason: "no_pub" }; + const address = await getUserAddress(uid); + const cfg = getWalletCfg("user") || {}; + if (!address || !isValidEcoinAddress(address) || !hasWalletCredentials()) return { available: false, reason: "no_wallet" }; + if (await hasClaimedThisMonth(uid)) return { available: false, reason: "claimed" }; + if (await hasRefusedThisMonth(uid)) return { available: false, reason: "refused" }; + return { available: true, epochId: epochIdNow(), pubId: pub.pubId }; + } + + function pubIdsFrom(msgs) { + const ids = new Set(); + const defaultPubId = getDefaultPubId(); + if (defaultPubId) ids.add(defaultPubId); + try { if (isPubNode() && config.keys && config.keys.id) ids.add(config.keys.id); } catch (_) {} + for (const m of msgs || []) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type === "pubAvailability" && c.coin === "ECO" && v.author) ids.add(v.author); + } + return ids; + } + + async function trustedPubIds() { + const ids = pubIdsFrom([]); + const me = config.keys && config.keys.id; + const ssb = await openSsb(); + if (!ssb || !me) return ids; + const msgs = await readTyped(ssb, ["pub"], { limit: Math.max(getLogLimit(), 20000) }).catch(() => []); for (const m of msgs) { const v = m.value || {}; const c = v.content || {}; - if (c.type === "ubiClaimResult" && c.userId === userId && c.epochId === epochId) return true; + const key = c.address && typeof c.address.key === "string" ? c.address.key : ""; + if (c.type !== "pub" || v.author !== me || !/^@[A-Za-z0-9+/]+={0,2}\.ed25519$/.test(key) || ids.has(key)) continue; + if (await isFollowing(ssb, me, key)) ids.add(key); + } + if (!isPubNode()) return ids; + const announcers = await scanUbiAnnouncements().catch(() => new Map()); + const contacts = await readTyped(ssb, ["contact"], { limit: Math.max(getLogLimit(), 20000) }).catch(() => []); + const latest = new Map(); + for (const m of contacts) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type !== "contact" || v.author !== me || !announcers.has(c.contact)) continue; + const prev = latest.get(c.contact); + if (!prev || (Number(v.sequence) || 0) > prev.seq) latest.set(c.contact, { seq: Number(v.sequence) || 0, c }); + } + for (const [id, { c }] of latest) if (c.following === true && c.pub !== true && c.blocking !== true) ids.add(id); + return ids; + } + + function claimAddressees(msgs) { + const addressed = new Set(); + for (const m of msgs || []) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type === "ubiClaim" && v.author && c.pubId) addressed.add(`${c.epochId}:${v.author}:${c.pubId}`); + } + return addressed; + } + + const resultHonoured = (v, c, trusted, addressed) => !!v.author && (trusted.has(v.author) || addressed.has(`${c.epochId}:${c.userId}:${v.author}`)); + + async function knownPubIds() { + const ids = pubIdsFrom([]); + try { for (const id of (await scanUbiAnnouncements()).keys()) ids.add(id); } catch (_) {} + try { for (const id of await trustedPubIds()) ids.add(id); } catch (_) {} + return ids; + } + + async function hasClaimedThisMonth(userId) { + const epochId = epochIdNow(); + const msgs = await scanLogStream(); + const trusted = await trustedPubIds(); + const addressed = claimAddressees(msgs); + for (const m of msgs) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type === "ubiClaimResult" && c.userId === userId && c.epochId === epochId && resultHonoured(v, c, trusted, addressed)) return true; if (c.type === "ubiClaim" && v.author === userId && c.epochId === epochId) return true; } return false; @@ -1011,13 +1506,15 @@ async function getLastPublishedTimestamp(userId) { async function getUbiClaimHistory(userId) { const msgs = await scanLogStream(); + const trusted = await trustedPubIds(); + const addressed = claimAddressees(msgs); let lastClaimedDate = null; let totalClaimed = 0; let claimCount = 0; for (const m of msgs) { const v = m.value || {}; const c = v.content || {}; - if (c.type === "ubiClaimResult" && c.userId === userId) { + if (c.type === "ubiClaimResult" && c.userId === userId && resultHonoured(v, c, trusted, addressed)) { totalClaimed += Number(c.amount) || 0; claimCount += 1; const d = c.processedAt || null; @@ -1028,57 +1525,382 @@ async function getLastPublishedTimestamp(userId) { } async function getUbiAllocationsFromSSB() { - const pubId = getConfiguredPubId(); + const pubId = isPubNode() ? config.keys.id : await resolvePubId(); if (!pubId) return []; const msgs = await scanLogStream(); + const pubIds = pubIdsFrom(msgs); + const trusted = await trustedPubIds(); + const addressed = claimAddressees(msgs); + const paid = new Map(); + for (const m of msgs) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type === "ubiClaimResult" && c.txid && c.epochId && c.userId && resultHonoured(v, c, trusted, addressed)) paid.set(ubiPaidKey(c.epochId, c.userId), c.txid); + } const out = []; + const seen = new Map(); for (const m of msgs) { const v = m.value || {}; const c = v.content || {}; if (v.author === pubId && c && c.type === "ubiAllocation") { - out.push({ + if (!c.to || c.to === pubId || pubIds.has(c.to)) continue; + const dupKey = ubiPaidKey(c.epochId, c.to); + const prev = seen.get(dupKey); + if (prev && String(prev.createdAt || "") >= String(c.createdAt || "")) continue; + const txid = paid.get(dupKey) || null; + if (prev) out.splice(out.indexOf(prev), 1); + const entry = { id: c.allocationId, from: pubId, to: c.to, amount: c.amount, concept: c.concept, epochId: c.epochId, - status: c.status || "UNCLAIMED", + status: txid ? "CLOSED" : (c.status || "UNCLAIMED"), + txid, createdAt: c.createdAt || new Date().toISOString() - }); + }; + seen.set(dupKey, entry); + out.push(entry); } } return out; } - async function publishPubAvailability() { - if (!isPubNode()) return; - const balance = await safeGetBalance("pub"); - const floor = Math.max(1, DEFAULT_RULES?.caps?.floor_user ?? 1); - const available = Number(balance) >= floor; - const ssb = await openSsb(); - if (!ssb || !ssb.publish) return; - const content = { type: "pubAvailability", available, coin: "ECO", timestamp: Date.now() }; - await new Promise((resolve, reject) => ssb.publish(content, (err, res) => err ? reject(err) : resolve(res))); - return available; - } - - async function getPubAvailabilityFromSSB() { - const pubId = getConfiguredPubId(); - if (!pubId) return false; - const msgs = await scanLogStream(); - let latest = null; + let lastAnnounce = null; + async function loadLastAnnounce(ssb) { + if (lastAnnounce) return lastAnnounce; + const msgs = await readTyped(ssb, ["pubAvailability"], { limit: Math.max(getLogLimit(), 5000) }); + let best = { available: null, timestamp: 0 }; for (const m of msgs) { const v = m.value || {}; const c = v.content || {}; - if (v.author === pubId && c && c.type === "pubAvailability" && c.coin === "ECO") { - if (!latest || (Number(c.timestamp) || 0) > (Number(latest.timestamp) || 0)) latest = c; - } + if (v.author !== config.keys.id || !c || c.type !== "pubAvailability") continue; + const ts = Number(c.timestamp) || 0; + if (ts > best.timestamp) best = { available: !!c.available, timestamp: ts, balance: Number(c.balance) || 0 }; } - return !!(latest && latest.available); + lastAnnounce = best; + return lastAnnounce; } - async function listBanking(filter = "overview", userId) { + async function scanUbiAnnouncements() { + const ssb = await openSsb(); + if (!ssb) return new Map(); + const msgs = await readTyped(ssb, ["pubAvailability"], { limit: Math.max(getLogLimit(), 5000) }); + const latestByPub = new Map(); + for (const m of msgs) { + const v = m.value || {}; + const c = v.content || {}; + if (!c || c.type !== "pubAvailability" || c.coin !== "ECO") continue; + const ts = Number(c.timestamp) || Number(v.timestamp) || 0; + const prev = latestByPub.get(v.author); + if (!prev || ts > prev.timestamp) latestByPub.set(v.author, { pubId: v.author, available: !!c.available, timestamp: ts, balance: Number(c.balance) || 0, balanceKnown: c.balance !== undefined && c.balance !== null && Number.isFinite(Number(c.balance)), pool: Number(c.pool) || 0, address: isValidEcoinAddress(c.address) ? String(c.address) : null }); + } + return latestByPub; + } + + async function networkPubIds() { + const trusted = await trustedPubIds(); + const ids = new Set(trusted); + const ssb = await openSsb(); + if (!ssb) return ids; + const contacts = await readTyped(ssb, ["contact"], { limit: Math.max(getLogLimit(), 20000) }).catch(() => []); + const latest = new Map(); + for (const m of contacts) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type !== "contact" || !trusted.has(v.author) || typeof c.contact !== "string") continue; + const k = `${v.author} ${c.contact}`; + const seq = Number(v.sequence) || 0; + const prev = latest.get(k); + if (!prev || seq > prev.seq) latest.set(k, { seq, contact: c.contact, federated: c.following === true && c.pub !== true && c.blocking !== true }); + } + for (const { contact, federated } of latest.values()) if (federated) ids.add(contact); + return ids; + } + + async function listNetworkUbiPubs() { + const latestByPub = await scanUbiAnnouncements(); + const known = await networkPubIds(); + const out = []; + for (const cand of latestByPub.values()) { + if (!known.has(cand.pubId) || Date.now() - cand.timestamp > PUB_ANNOUNCE_MAX_AGE_MS || !isValidEcoinAddress(cand.address) || !cand.balanceKnown) continue; + out.push(cand); + } + return out; + } + + async function listUbiPubsDetailed() { + const pubs = await listNetworkUbiPubs(); + if (!pubs.length) return []; + const ssb = await openSsb(); + const { transfers } = ssb ? await readUbiLedger(ssb) : { transfers: [] }; + const defaultPubId = getDefaultPubId(); + const connected = (await discoverUbiPub()).pubId; + const me = isPubNode() && config.keys ? config.keys.id : null; + return pubs.map(p => { + const payouts = transfers.filter(t => t.author === p.pubId && isUbiPayoutConcept(t.concept)); + const lastPayoutAt = payouts.reduce((acc, t) => Math.max(acc, Date.parse(t.createdAt || "") || 0), 0); + const paidOut = payouts.reduce((acc, t) => acc + (Number(t.amount) || 0), 0); + const address = p.address || null; + return { ...p, address, lastPayoutAt, paidOut: Number(paidOut.toFixed(6)), payouts: payouts.length, isDefault: p.pubId === defaultPubId, isConnected: p.pubId === connected, isThisNode: !!me && p.pubId === me }; + }).sort((a, b) => (Number(b.isThisNode || b.isConnected) - Number(a.isThisNode || a.isConnected)) || (b.balance - a.balance)); + } + + async function listUbiRebalanceSeries(range = "all", explicit = true) { + const ssb = await openSsb(); + if (!ssb) return { points: [], hasAnyData: false, range: normalizeRange(range), bucket: "month" }; + const { transfers } = await readUbiLedger(ssb); + const known = await networkPubIds(); + const events = transfers + .filter(t => String(t.concept || "").startsWith(REBALANCE_CONCEPT) && known.has(t.author)) + .map(t => ({ ts: Date.parse(t.createdAt || "") || 0, rebalanced: Number(t.amount) || 0 })) + .filter(e => e.ts > 0) + .sort((a, b) => a.ts - b.ts); + return bucketEvents(events, range, explicit, ["rebalanced"]); + } + + async function listUbiCharts(range = "all", explicit = true) { + const ssb = await openSsb(); + if (!ssb) return { monthly: [], pool: [] }; + const { transfers } = await readUbiLedger(ssb); + const byMonth = new Map(); + for (const t of transfers) { + if (!isUbiPayoutConcept(t.concept)) continue; + const month = (String(t.concept).match(/(\d{4}-\d{2})/) || [])[1] || String(t.createdAt || "").slice(0, 7); + if (!month) continue; + const cur = byMonth.get(month) || { month, amount: 0, count: 0 }; + cur.amount += Number(t.amount) || 0; + cur.count += 1; + byMonth.set(month, cur); + } + const monthly = Array.from(byMonth.values()).sort((a, b) => a.month.localeCompare(b.month)).map(m => ({ ...m, amount: Number(m.amount.toFixed(6)) })); + const msgs = await readTyped(ssb, ["pubAvailability"], { limit: Math.max(getLogLimit(), 5000) }); + const trusted = await trustedPubIds(); + const events = msgs + .filter(m => m.value && m.value.content && m.value.content.type === "pubAvailability" && m.value.content.coin === "ECO" && trusted.has(m.value.author) && Number.isFinite(Number(m.value.content.balance))) + .map(m => ({ ts: Number(m.value.content.timestamp) || Number(m.value.timestamp) || 0, pubId: m.value.author, balance: Number(m.value.content.balance) })) + .filter(x => x.ts > 0) + .sort((a, b) => a.ts - b.ts); + const latestByPub = new Map(); + const pool = []; + for (const e of events) { + latestByPub.set(e.pubId, e.balance); + let total = 0; + for (const v of latestByPub.values()) total += v; + const last = pool[pool.length - 1]; + if (last && last.ts === e.ts) last.balance = Number(total.toFixed(6)); + else pool.push({ ts: e.ts, balance: Number(total.toFixed(6)) }); + } + const fullPool = pool.some(p => p.balance > 0) ? pool : []; + const monthTs = (m) => Date.parse(`${m}-01T00:00:00Z`) || 0; + const monthlyTs = monthly.map(m => ({ ...m, ts: monthTs(m.month) })); + const poolRange = explicit ? normalizeRange(range) : widenRange(fullPool, range); + const monthlyRange = explicit ? normalizeRange(range) : widenRange(monthlyTs, range); + return { + monthly: filterByRange(monthlyTs, monthlyRange).slice(-120), + pool: filterByRange(fullPool, poolRange).slice(-120), + hasMonthly: monthlyTs.length >= 2, + hasPool: fullPool.length >= 2, + poolRange, + monthlyRange, + pubCount: latestByPub.size + }; + } + + function bucketEvents(events, range, explicit, fields) { + const effective = explicit ? normalizeRange(range) : widenRange(events, range); + const inRange = filterByRange(events, effective); + const bucketMs = effective === "today" ? 60 * 60 * 1000 : (effective === "week" || effective === "month") ? ONE_DAY_MS : 0; + const bucketOf = (ts) => { + if (bucketMs) return Math.floor(ts / bucketMs) * bucketMs; + const d = new Date(ts); + return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1); + }; + const buckets = new Map(); + for (const e of inRange) { + const b = bucketOf(e.ts); + const cur = buckets.get(b) || fields.reduce((acc, f) => ({ ...acc, [f]: 0 }), { ts: b }); + for (const f of fields) cur[f] += Number(e[f]) || 0; + buckets.set(b, cur); + } + let points = Array.from(buckets.values()).sort((a, b) => a.ts - b.ts) + .map(p => fields.reduce((acc, f) => ({ ...acc, [f]: Number(Number(p[f]).toFixed(6)) }), { ts: p.ts })) + .slice(-240); + if (points.length === 1) { + const span = RANGE_MS[effective]; + const baseTs = Number.isFinite(span) ? Date.now() - span : points[0].ts - (bucketMs || 30 * ONE_DAY_MS); + points = [fields.reduce((acc, f) => ({ ...acc, [f]: 0 }), { ts: Math.min(baseTs, points[0].ts - 1) }), ...points]; + } + return { points, range: effective, bucket: bucketMs === 60 * 60 * 1000 ? "hour" : bucketMs ? "day" : "month", hasAnyData: events.length >= 1 }; + } + + async function listUbiPaymentsSeries(range = "all", explicit = true) { + const ssb = await openSsb(); + if (!ssb) return { points: [], hasAnyData: false, range: normalizeRange(range), bucket: "month" }; + const { transfers } = await readUbiLedger(ssb); + const events = transfers + .filter(t => isUbiPayoutConcept(t.concept)) + .map(t => ({ ts: Date.parse(t.createdAt || "") || 0, distributed: Number(t.amount) || 0 })) + .filter(e => e.ts > 0) + .sort((a, b) => a.ts - b.ts); + return bucketEvents(events, range, explicit, ["distributed"]); + } + + async function listWealthSeries(range = "all", explicit = true) { + const ssb = await openSsb(); + if (!ssb) return { points: [], totals: { distributed: 0, taxes: 0 } }; + const { transfers } = await readUbiLedger(ssb); + const allocs = await readTyped(ssb, ["ubiAllocation"], { limit: Math.max(getLogLimit(), 20000) }); + const events = []; + for (const t of transfers) { + if (!isUbiPayoutConcept(t.concept)) continue; + const ts = Date.parse(t.createdAt || "") || 0; + if (ts) events.push({ ts, distributed: Number(t.amount) || 0, taxes: 0 }); + } + for (const m of allocs) { + const c = m.value && m.value.content; + if (!c || c.type !== "ubiAllocation") continue; + const ts = Date.parse(c.createdAt || "") || Number(m.value.timestamp) || 0; + if (ts) events.push({ ts, distributed: 0, taxes: Number(c.totalTax) || 0 }); + } + events.sort((a, b) => a.ts - b.ts); + const totals = events.reduce((acc, e) => ({ distributed: acc.distributed + e.distributed, taxes: acc.taxes + e.taxes }), { distributed: 0, taxes: 0 }); + totals.distributed = Number(totals.distributed.toFixed(6)); + totals.taxes = Number(totals.taxes.toFixed(6)); + return { ...bucketEvents(events, range, explicit, ["distributed", "taxes"]), totals }; + } + + async function listNetworkEpochs() { + const ssb = await openSsb(); + if (!ssb) return []; + const byEpoch = new Map(); + const bump = (id, amount, to) => { + if (!id) return; + const cur = byEpoch.get(id) || { id, pool: 0, recipients: new Set() }; + cur.pool += Number(amount) || 0; + if (to) cur.recipients.add(to); + byEpoch.set(id, cur); + }; + const allocs = await getUbiAllocationsFromSSB().catch(() => []); + for (const a of allocs) bump(String(a.epochId || ""), a.amount, a.to); + if (!byEpoch.size) { + const { transfers } = await readUbiLedger(ssb); + for (const t of transfers) { + if (!isUbiPayoutConcept(t.concept)) continue; + const tag = (t.tags || []).find(x => /^epoch:\d{4}-\d{2}$/i.test(String(x))); + const id = tag ? String(tag).slice(6) : ((String(t.concept || "").match(/(\d{4}-\d{2})/) || [])[1] || ""); + bump(id, t.amount, t.to); + } + } + return Array.from(byEpoch.values()) + .map(e => ({ id: e.id, pool: Number(e.pool.toFixed(6)), recipients: e.recipients.size })) + .sort((a, b) => String(b.id).localeCompare(String(a.id))); + } + + async function sampleUserFunds() { + if (!hasWalletCredentials()) return false; + const hist = readFundsHistory(); + const last = Array.isArray(hist) && hist.length ? hist[hist.length - 1] : null; + if (last && Date.now() - Number(last.ts) < ECO_HISTORY_MIN_GAP_MS) return false; + try { + const balance = Number(await rpcCall("getbalance", [], "user")); + if (!Number.isFinite(balance)) return false; + appendFundsHistory({ ts: Date.now(), balance }); + return true; + } catch (_) { return false; } + } + + function listFundsHistory(range = "all", explicit = true) { + const all = readFundsHistory() + .map(s => ({ ts: Number(s.ts) || 0, balance: Number(s.balance) || 0 })) + .filter(x => x.ts > 0) + .sort((a, b) => a.ts - b.ts); + const effective = explicit ? normalizeRange(range) : widenRange(all, range); + return { points: filterByRange(all, effective).slice(-240), hasAny: all.length >= 2, range: effective }; + } + + async function listKarmaHistory(userId, range = "all", explicit = true) { + const ssb = await openSsb(); + if (!ssb) return { points: [], hasAny: false, range: normalizeRange(range) }; + const uid = resolveUserId(userId); + const msgs = await readTyped(ssb, ["karmaScore"], { limit: Math.max(getLogLimit(), 5000) }); + const all = msgs + .filter(m => m.value && m.value.author === uid && m.value.content && m.value.content.type === "karmaScore") + .map(m => ({ ts: Date.parse(m.value.content.timestamp || "") || Number(m.value.timestamp) || 0, score: Number(m.value.content.karmaScore) || 0 })) + .filter(x => x.ts > 0) + .sort((a, b) => a.ts - b.ts); + const effective = explicit ? normalizeRange(range) : widenRange(all, range); + return { points: filterByRange(all, effective).slice(-120), hasAny: all.length >= 2, range: effective }; + } + + async function listUbiPubs() { + const latestByPub = await scanUbiAnnouncements(); + const trusted = await trustedPubIds(); + const out = []; + for (const cand of latestByPub.values()) { + if (!trusted.has(cand.pubId) || Date.now() - cand.timestamp > PUB_ANNOUNCE_MAX_AGE_MS) continue; + out.push(cand); + } + return out.sort((a, b) => b.timestamp - a.timestamp); + } + + async function publishPubAvailability() { + if (!isPubNode()) return; + const raw = await tryGetBalance("pub", ENGINE_RPC_TIMEOUT_MS); + const balance = raw === null ? 0 : raw; + const floor = Math.max(1, DEFAULT_RULES?.caps?.floor_user ?? 1); + const pv = computePoolVars(balance, DEFAULT_RULES); + const available = raw !== null && pv.available >= floor; + const ssb = await openSsb(); + if (!ssb || !ssb.publish) return; + const last = await loadLastAnnounce(ssb); + const stale = Date.now() - last.timestamp > PUB_ANNOUNCE_REFRESH_MS; + const announcedBalance = Number(pv.available.toFixed(6)); + const balanceMoved = raw !== null && Math.abs(announcedBalance - (Number(last.balance) || 0)) > Math.max(1, (Number(last.balance) || 0) * 0.2); + if (last.available === available && !stale && !balanceMoved) return available; + const address = await getAnyWalletAddress().catch(() => null); + const content = { type: "pubAvailability", available, coin: "ECO", ...(raw === null ? {} : { balance: announcedBalance, pool: Number(pv.pool.toFixed(6)) }), address: isValidEcoinAddress(address) ? address : undefined, timestamp: Date.now() }; + await new Promise((resolve, reject) => ssb.publish(content, (err, res) => err ? reject(err) : resolve(res))); + lastAnnounce = { available, timestamp: content.timestamp, balance: raw === null ? 0 : announcedBalance }; + console.log(`[UBI] announced ${available ? "available" : "unavailable"} (PUB wallet ${Number(balance).toFixed(6)} ECO)`); + return available; + } + + let discoveredPub = { pubId: "", available: false, timestamp: 0, checkedAt: 0 }; + async function discoverUbiPub() { + if (Date.now() - discoveredPub.checkedAt < PUB_DISCOVERY_TTL_MS) return discoveredPub; + const latestByPub = await scanUbiAnnouncements(); + const trusted = await trustedPubIds(); + const defaultPubId = getDefaultPubId(); + const fresh = (cand) => cand && Date.now() - cand.timestamp <= PUB_ANNOUNCE_MAX_AGE_MS; + const defaultCand = latestByPub.get(defaultPubId) || null; + let best = null; + if (fresh(defaultCand) && defaultCand.available) best = defaultCand; + else { + for (const cand of latestByPub.values()) { + if (!trusted.has(cand.pubId) || !fresh(cand) || !cand.available) continue; + if (!best || cand.timestamp > best.timestamp) best = cand; + } + } + if (!best && defaultPubId) best = { pubId: defaultPubId, available: false, timestamp: (defaultCand && defaultCand.timestamp) || 0 }; + discoveredPub = { pubId: (best && best.pubId) || "", available: !!(best && best.available), timestamp: (best && best.timestamp) || 0, balance: (best && best.balance) || 0, pool: (best && best.pool) || 0, checkedAt: Date.now() }; + return discoveredPub; + } + + async function resolvePubId() { + return (await discoverUbiPub()).pubId; + } + + async function getPubAvailabilityFromSSB() { + const pubId = await resolvePubId(); + if (!pubId) return false; + return (await discoverUbiPub()).available; + } + + async function listBanking(filter = "overview", userId, opts = {}) { + const explicitRange = !!opts.range; + const range = normalizeRange(opts.range); const uid = resolveUserId(userId); const epochId = epochIdNow(); let pubBalance = 0; @@ -1087,8 +1909,7 @@ async function getLastPublishedTimestamp(userId) { if (isPubNode()) { pubBalance = await safeGetBalance("pub"); const floor = Math.max(1, DEFAULT_RULES?.caps?.floor_user ?? 1); - ubiAvailable = Number(pubBalance) >= floor; - try { await publishPubAvailability(); } catch (_) {} + ubiAvailable = computePoolVars(Number(pubBalance) || 0, DEFAULT_RULES).available >= floor; const all = await transfersRepo.listByTag("UBI"); allocations = all.map(t => ({ id: t.id, concept: t.concept, from: t.from, to: t.to, amount: t.amount, status: t.status, @@ -1098,27 +1919,45 @@ async function getLastPublishedTimestamp(userId) { ubiAvailable = await getPubAvailabilityFromSSB(); allocations = await getUbiAllocationsFromSSB(); } - const userBalance = await safeGetBalance("user"); + let userBalance = 0; + if (hasWalletCredentials()) { + try { + userBalance = Number(await rpcCall("getbalance", [], "user")) || 0; + appendFundsHistory({ ts: Date.now(), balance: userBalance }); + } catch (_) { userBalance = 0; } + } const industryBal = await getIndustryBalance(uid).catch(() => ({ received: 0, sent: 0, net: 0, networkTotal: 0 })); - const epochs = await epochsRepo.list(); + const schoolBal = await getSchoolBalance(uid).catch(() => ({ received: 0, lifetime: 0, net: 0 })); + let epochs = await epochsRepo.list(); + if (!epochs.length) epochs = await listNetworkEpochs().catch(() => []); let computed = null; - try { computed = await computeEpoch({ epochId, userId: uid, rules: DEFAULT_RULES }); } catch {} + if (filter === "overview" || isPubNode()) { + try { computed = await computeEpoch({ epochId, userId: uid, rules: DEFAULT_RULES }); } catch {} + } const pv = computePoolVars(pubBalance, DEFAULT_RULES); const actions = await fetchUserActions(uid); const rawScore = scoreFromActions(actions); const carbonGramsForScore = await getCarbonGramsForUser(uid).catch(() => 0); const engagementScore = Math.max(0, Math.round(rawScore - carbonGramsForScore)); - const poolForEpoch = computed?.epoch?.pool || pv.pool || 0; + let poolForEpoch = computed?.epoch?.pool || pv.pool || 0; + if (!isPubNode()) { + const info = await discoverUbiPub(); + poolForEpoch = Number(info.pool) || computePoolVars((Number(info.balance) || 0) + (DEFAULT_RULES.reserveMin || 0), DEFAULT_RULES).pool || 0; + } const futureUBI = Number(((engagementScore / 100) * poolForEpoch).toFixed(6)); const addresses = await listAddressesMerged(); const alreadyClaimed = await hasClaimedThisMonth(uid); - const pubId = getConfiguredPubId(); + const pubId = isPubNode() ? config.keys.id : await resolvePubId(); + const pubLastSeen = isPubNode() ? (lastAnnounce ? lastAnnounce.timestamp : 0) : (await discoverUbiPub()).timestamp; const userAddress = await getUserAddress(uid); const userWalletCfg = getWalletCfg("user") || {}; - const hasValidWallet = !!(userAddress && isValidEcoinAddress(userAddress) && userWalletCfg.url); + const hasValidWallet = !!(userAddress && isValidEcoinAddress(userAddress) && hasWalletCredentials()); + const addressPublished = await hasPublishedAddress(uid).catch(() => false); const summary = { userBalance, industryBalance: industryBal.net, + schoolBalance: schoolBal.net, + schoolLifetime: schoolBal.lifetime, industryNetworkTotal: industryBal.networkTotal, industryReceived: industryBal.received, industrySent: industryBal.sent, @@ -1128,12 +1967,34 @@ async function getLastPublishedTimestamp(userId) { userEngagementScore: engagementScore, futureUBI, alreadyClaimed, + alreadyRefused: await hasRefusedThisMonth(uid).catch(() => false), pubId, + wealthTotals: (await listWealthSeries("all").catch(() => ({ totals: { distributed: 0, taxes: 0 } }))).totals, + pubLastSeen, + pubBalance: isPubNode() ? pubBalance : null, + addressPublished, hasValidWallet, ubiAvailability: ubiAvailable ? "OK" : "NO_FUNDS" }; const exchange = await calculateEcoinValue(); - const exchangeHistory = readEcoHistory(); + if (filter === 'exchange') { + const pubs = await listUbiPubs().catch(() => []); + exchange.pubsSupply = Number(pubs.reduce((acc, p) => acc + (Number(p.balance) || 0), 0).toFixed(6)); + const lastSample = readEcoHistory().filter(x => Number(x.currentSupply) > 0).pop(); + const generated = Number(exchange.currentSupply) > 0 ? Number(exchange.currentSupply) : (lastSample ? Number(lastSample.currentSupply) : 0); + exchange.existingSupply = generated > 0 ? Number(generated.toFixed(6)) : null; + exchange.holdingSupply = generated > 0 ? Number(Math.max(0, generated - exchange.pubsSupply).toFixed(6)) : null; + } + const fullHistory = readEcoHistory(); + const valueRange = explicitRange ? range : widenRange(fullHistory, range); + let exchangeHistory = filterByRange(fullHistory, valueRange); + if (exchangeHistory.length === 1 && fullHistory.length > 1) { + const firstTs = Number(exchangeHistory[0].ts) || 0; + const before = fullHistory.filter(x => Number(x.ts) < firstTs).pop(); + const span = RANGE_MS[normalizeRange(valueRange)]; + if (before) exchangeHistory = [{ ...before, ts: Number.isFinite(span) ? Math.max(Number(before.ts), Date.now() - span) : Number(before.ts) }, ...exchangeHistory]; + } + const valueHasAnyData = Array.isArray(fullHistory) && fullHistory.length >= 2; let taxStats = null; let userEcoinTax = 0; if (filter === 'taxes' || filter === 'exchange' || filter === 'overview') { @@ -1146,8 +2007,15 @@ async function getLastPublishedTimestamp(userId) { try { userArchTax = await getUserArchTax(uid); } catch (_) { userArchTax = 0; } } const userTotalTax = (userEcoinTax || 0) + (userArchTax || 0); + const ubiPubs = filter === 'ubi' ? await listUbiPubsDetailed().catch(() => []) : []; + const ubiCharts = filter === 'ubi' ? await listUbiCharts(range, explicitRange).catch(() => null) : null; + const karmaHistory = filter === 'overview' ? await listKarmaHistory(uid, range, explicitRange).catch(() => ({ points: [], hasAny: false, range })) : { points: [], hasAny: false, range }; + const fundsHistory = filter === 'overview' ? listFundsHistory(range, explicitRange) : { points: [], hasAny: false, range }; + const wealth = filter === 'exchange' || filter === 'overview' ? await listWealthSeries(range, explicitRange).catch(() => ({ points: [], totals: { distributed: 0, taxes: 0 } })) : { points: [], totals: { distributed: 0, taxes: 0 } }; + const ubiPayments = filter === 'ubi' || filter === 'overview' ? await listUbiPaymentsSeries(range, explicitRange).catch(() => ({ points: [], hasAnyData: false })) : { points: [], hasAnyData: false }; + const ubiRebalances = filter === 'ubi' ? await listUbiRebalanceSeries(range, explicitRange).catch(() => ({ points: [], hasAnyData: false })) : { points: [], hasAnyData: false }; return { - summary, allocations, epochs, rules: DEFAULT_RULES, taxRules, addresses, exchange, exchangeHistory, taxStats, + summary, allocations, epochs, rules: DEFAULT_RULES, taxRules, addresses, exchange, exchangeHistory, taxStats, ubiPubs, ubiCharts, karmaHistory, fundsHistory, wealth, ubiPayments, ubiRebalances, range, valueRange, valueHasAnyData, userEcoinTax: Number((userEcoinTax || 0).toFixed(6)), userArchTax: Number((userArchTax || 0).toFixed(6)), userTotalTax: Number(userTotalTax.toFixed(6)) @@ -1163,17 +2031,27 @@ async function getLastPublishedTimestamp(userId) { async function getEpochById(id) { const existing = await epochsRepo.get(id); if (existing) return existing; - const all = await transfersRepo.listAll(); - const filtered = all.filter(t => (t.tags || []).includes(`epoch:${id}`)); - const pool = filtered.reduce((s, t) => s + Number(t.amount || 0), 0); - return { id, pool, weightsSum: 0, rules: DEFAULT_RULES, hash: "-" }; + const allocations = await listEpochAllocations(id).catch(() => []); + if (!allocations.length) return null; + const pool = allocations.reduce((s, t) => s + Number(t.amount || 0), 0); + return { id, pool: Number(pool.toFixed(6)), recipients: allocations.length, rules: DEFAULT_RULES }; } async function listEpochAllocations(id) { const all = await transfersRepo.listAll(); - return all.filter(t => (t.tags || []).includes(`epoch:${id}`)).map(t => ({ + const local = all.filter(t => (t.tags || []).includes(`epoch:${id}`)).map(t => ({ id: t.id, concept: t.concept, from: t.from, to: t.to, amount: t.amount, status: t.status, createdAt: t.createdAt || new Date().toISOString(), txid: t.txid })); + if (local.length) return local; + const published = await getUbiAllocationsFromSSB().catch(() => []); + const fromNetwork = published.filter(a => String(a.epochId || "") === String(id)); + if (fromNetwork.length) return fromNetwork; + const ssb = await openSsb(); + if (!ssb) return []; + const { transfers } = await readUbiLedger(ssb); + return transfers + .filter(t => isUbiPayoutConcept(t.concept) && (String(t.concept || "").includes(id) || (t.tags || []).includes(`epoch:${id}`))) + .map(t => ({ id: t.key, concept: t.concept, from: t.from, to: t.to, amount: t.amount, status: t.status || "UNCONFIRMED", createdAt: t.createdAt || new Date().toISOString(), txid: t.txid })); } let genesisTimeCache = null; @@ -1198,6 +2076,63 @@ async function getLastPublishedTimestamp(userId) { } catch (_) { return 0; } } + async function getRecentBlockSeconds(blocks, window = 720) { + if (!blocks || blocks <= window + 1) return 0; + try { + const hCur = await rpcCall("getblockhash", [blocks]); + const hOld = await rpcCall("getblockhash", [blocks - window]); + if (!hCur || !hOld) return 0; + const bCur = await rpcCall("getblock", [hCur]); + const bOld = await rpcCall("getblock", [hOld]); + const elapsed = (bCur?.time || 0) - (bOld?.time || 0); + return elapsed > 0 ? elapsed / window : 0; + } catch (_) { return 0; } + } + + function observedIssuancePerHour(currentSupply) { + const hist = readEcoHistory(); + if (!Array.isArray(hist) || hist.length < 2 || !(currentSupply > 0)) return null; + const now = Date.now(); + const windowStart = now - 30 * ONE_DAY_MS; + const base = hist.find(s => Number(s.ts) >= windowStart && Number(s.currentSupply) > 0) || hist.find(s => Number(s.currentSupply) > 0); + if (!base) return null; + const spanMs = now - Number(base.ts); + if (spanMs < 6 * 60 * 60 * 1000) return null; + const minted = currentSupply - Number(base.currentSupply); + if (!Number.isFinite(minted) || minted < 0) return null; + return minted / (spanMs / (3600 * 1000)); + } + + function observedInflationFromHistory(currentSupply) { + const hist = readEcoHistory(); + if (!Array.isArray(hist) || hist.length < 2 || !(currentSupply > 0)) return null; + const now = Date.now(); + const minSpanMs = 6 * 60 * 60 * 1000; + const oldEnough = (s) => Number(s.currentSupply) > 0 && now - Number(s.ts) >= minSpanMs; + const base = hist.find(s => Number(s.ts) >= now - 7 * ONE_DAY_MS && oldEnough(s)) + || hist.find(s => Number(s.ts) >= now - 30 * ONE_DAY_MS && oldEnough(s)) + || hist.find(oldEnough); + if (!base) return null; + const spanMs = now - Number(base.ts); + const growth = (currentSupply - Number(base.currentSupply)) / Number(base.currentSupply); + return growth * (365 * ONE_DAY_MS / spanMs) * 100; + } + + async function ubiPerHourPerInhabitant() { + const pool = isPubNode() + ? computePoolVars(await safeGetBalance("pub"), DEFAULT_RULES).pool + : (Number((await discoverUbiPub()).pool) || 0); + const pubIds = await knownPubIds(); + const addresses = await listAddressesMerged().catch(() => []); + const eligible = addresses.filter(a => a.address && isValidEcoinAddress(a.address) && !pubIds.has(a.id) && !(isPubNode() && a.id === config.keys.id)).length; + const cap = DEFAULT_RULES.caps.cap_user_epoch; + const floor = DEFAULT_RULES.caps.floor_user ?? 1; + const perInhabitant = pool > 0 && eligible > 0 ? Math.max(floor, Math.min(cap, pool / eligible)) : 0; + const now = new Date(); + const hoursInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate() * 24; + return perInhabitant / hoursInMonth; + } + async function calculateEcoinValue() { const totalSupply = 25500000; let circulatingSupply = 0; @@ -1212,12 +2147,18 @@ async function getLastPublishedTimestamp(userId) { const mining = await rpcCall("getmininginfo", []); blockValueEco = (mining?.blockvalue || 0) / 1e8; } catch (_) {} - const avgSec = await getAvgBlockSeconds(blocks); - const ecoValuePerHour = avgSec > 0 ? (3600 / avgSec) * blockValueEco : 0; + const recentSec = await getRecentBlockSeconds(blocks); + const avgSec = recentSec > 0 ? recentSec : await getAvgBlockSeconds(blocks); + const nominalPerHour = avgSec > 0 ? (3600 / avgSec) * blockValueEco : 0; + const observedPerHour = isSynced ? observedIssuancePerHour(circulatingSupply) : null; + const issuancePerHour = observedPerHour !== null ? observedPerHour : nominalPerHour; + const ecoValuePerHour = await ubiPerHourPerInhabitant().catch(() => 0); const maturity = totalSupply > 0 ? circulatingSupply / totalSupply : 0; const ecoTimeMs = maturity * 3600 * 1000; - const annualIssuance = ecoValuePerHour * 24 * 365; - const inflationFactor = circulatingSupply > 0 ? (annualIssuance / circulatingSupply) * 100 : 0; + const annualIssuance = issuancePerHour * 24 * 365; + const inflationIssuance = circulatingSupply > 0 ? (annualIssuance / circulatingSupply) * 100 : 0; + const inflationObserved = isSynced ? observedInflationFromHistory(circulatingSupply) : null; + const inflationFactor = inflationObserved !== null && Number.isFinite(inflationObserved) ? inflationObserved : inflationIssuance; const inflationMonthly = inflationFactor / 12; const result = { ecoValue: Number(ecoValuePerHour.toFixed(6)), @@ -1225,6 +2166,8 @@ async function getLastPublishedTimestamp(userId) { totalSupply, inflationFactor: Number(inflationFactor.toFixed(2)), inflationMonthly: Number(inflationMonthly.toFixed(2)), + inflationIssuance: Number(inflationIssuance.toFixed(2)), + inflationObserved: inflationObserved === null ? null : Number(inflationObserved.toFixed(2)), currentSupply: circulatingSupply, isSynced }; @@ -1232,6 +2175,7 @@ async function getLastPublishedTimestamp(userId) { appendEcoHistory({ ts: Date.now(), ecoValue: result.ecoValue, + ecoTimeMs: result.ecoTimeMs, currentSupply: result.currentSupply, inflationFactor: result.inflationFactor, blockValueEco: Number(blockValueEco.toFixed(8)), @@ -1242,15 +2186,23 @@ async function getLastPublishedTimestamp(userId) { return result; } + const perRequest = (key, fn) => { + const store = requestScope.getStore(); + if (!store) return fn(); + const memo = store.bankingMemo || (store.bankingMemo = new Map()); + if (!memo.has(key)) memo.set(key, fn()); + return memo.get(key); + }; + async function getBankingData(userId) { - const ecoValue = await calculateEcoinValue(); + const ecoValue = await perRequest("ecoinValue", () => calculateEcoinValue()); const karmaScore = await getUserEngagementScore(userId); let estimatedUBI = 0; try { - const pubBal = isPubNode() ? await safeGetBalance("pub") : 0; + const pubBal = isPubNode() ? await perRequest("pubBalance", () => safeGetBalance("pub")) : 0; const pv = computePoolVars(pubBal, DEFAULT_RULES); const pool = pv.pool || 0; - const addresses = await listAddressesMerged(); + const addresses = await perRequest("addresses", () => listAddressesMerged()); const eligible = addresses.filter(a => a.address && isValidEcoinAddress(a.address)); const wMin = DEFAULT_RULES.caps?.w_min ?? 0.2; const wMax = DEFAULT_RULES.caps?.w_max ?? 6; @@ -1306,6 +2258,9 @@ async function getLastPublishedTimestamp(userId) { allocationId: allocation.id, to: allocation.to, amount: allocation.amount, + ecoTax: Number(allocation.ecoTax) || 0, + archTax: Number(allocation.archTax) || 0, + totalTax: Number(allocation.totalTax) || 0, concept: allocation.concept, epochId: epochTag ? epochTag.slice(6) : "", status: "UNCLAIMED", @@ -1321,84 +2276,363 @@ async function getLastPublishedTimestamp(userId) { return new Promise((resolve, reject) => ssb.publish(content, (err, res) => err ? reject(err) : resolve(res))); } - async function publishUbiClaimResult(allocationId, epochId, txid, userId, amount) { + async function publishUbiClaimResult(allocationId, epochId, txid, userId, amount, address) { const ssb = await openSsb(); if (!ssb) return; - const content = { type: "ubiClaimResult", allocationId, epochId, txid, userId, amount, processedAt: new Date().toISOString() }; + const content = { type: "ubiClaimResult", allocationId, epochId, txid, userId, amount, ...(address ? { address: String(address) } : {}), processedAt: new Date().toISOString() }; return new Promise((resolve, reject) => ssb.publish(content, (err, res) => err ? reject(err) : resolve(res))); } + async function isEligibleClaimant(userId) { + if ((isPubNode() && userId === config.keys.id) || (await knownPubIds()).has(userId)) return { ok: false, reason: "a PUB cannot claim the UBI" }; + const addr = await getUserAddress(userId); + if (!addr || !isValidEcoinAddress(addr)) return { ok: false, reason: "no published ECOin address" }; + const firstTs = await getUserFirstBlockTs(userId).catch(() => 0); + if (!firstTs || Date.now() - firstTs < CLAIMANT_MIN_AGE_MS) return { ok: false, reason: "feed younger than 30 days" }; + const score = await getUserEngagementScore(userId).catch(() => 0); + if (!(Number(score) > 0)) return { ok: false, reason: "no activity" }; + return { ok: true, address: addr, score: Number(score) }; + } + + function ubiAmountFor({ pool, userW, totalW, score, ecoTax, archTax }) { + const floorUbi = Math.max(1, DEFAULT_RULES.caps.floor_user ?? 1); + const capUser = DEFAULT_RULES.caps.cap_user_epoch; + const gross = Math.max(floorUbi, Math.min(pool * userW / totalW, capUser)); + const surplus = Math.max(0, gross - floorUbi); + const tax = (Number(ecoTax) || 0) + (Number(archTax) || 0); + return Number((floorUbi + Math.max(0, surplus - tax)).toFixed(6)); + } + + const previousEpochId = () => { + const d = new Date(); + d.setUTCDate(1); + d.setUTCMonth(d.getUTCMonth() - 1); + return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`; + }; + + const epochStartMs = (eid) => { + const m = /^(\d{4})-(\d{2})$/.exec(String(eid || "")); + return m ? Date.UTC(Number(m[1]), Number(m[2]) - 1, 1) : 0; + }; + + function claimPaidByMe(claim, me, defaultPubId, nowMs) { + const addressed = claim.pubId || defaultPubId; + const age = nowMs - (Number(claim._ts) || nowMs); + if (addressed === me) return me === defaultPubId || age < UBI_TAKEOVER_AFTER_MS; + if (!defaultPubId || me !== defaultPubId) return false; + const eid = claim.epochId || ""; + return age >= UBI_TAKEOVER_AFTER_MS + UBI_TAKEOVER_MARGIN_MS && (eid === epochIdNow() || eid === previousEpochId()); + } + async function processPendingClaims() { if (!isPubNode()) return; const ssb = await openSsb(); if (!ssb) return; const claims = []; const results = []; + const pubIds = await knownPubIds(); + const trusted = await trustedPubIds(); + const addressed = new Set(); + const seenClaims = new Set(); await new Promise((resolve, reject) => { pull(ssb.messagesByType({ type: "ubiClaim", reverse: false }), pull.drain(msg => { if (msg.value?.content?.type === "ubiClaim") { - claims.push({ ...msg.value.content, _author: msg.value.author }); + if (msg.value.content.pubId) addressed.add(`${msg.value.content.epochId}:${msg.value.author}:${msg.value.content.pubId}`); + const key = ubiPaidKey(msg.value.content.epochId || "", msg.value.author); + if (seenClaims.has(key)) return; + seenClaims.add(key); + claims.push({ ...msg.value.content, _author: msg.value.author, _ts: Number(msg.value.timestamp) || Date.parse(msg.value.content.claimedAt || "") || 0 }); } }, err => err ? reject(err) : resolve())); }); await new Promise((resolve, reject) => { pull(ssb.messagesByType({ type: "ubiClaimResult", reverse: false }), - pull.drain(msg => { if (msg.value?.content?.type === "ubiClaimResult") results.push(msg.value.content); }, + pull.drain(msg => { if (msg.value?.content?.type === "ubiClaimResult" && resultHonoured(msg.value, msg.value.content, trusted, addressed)) results.push(msg.value.content); }, err => err ? reject(err) : resolve())); }); const processedEpochUser = new Set(results.map(r => `${r.epochId}:${r.userId}`)); + const paidAddressBy = new Map(); + for (const r of results) if (r.address && r.epochId && r.userId) paidAddressBy.set(`${r.epochId}:${r.address}`, r.userId); + const refused = new Set(); + await new Promise((resolve, reject) => { + pull(ssb.messagesByType({ type: "ubiRefuse", reverse: false }), + pull.drain(msg => { const c = msg.value?.content; if (c && c.type === "ubiRefuse") refused.add(`${c.epochId}:${msg.value.author}`); }, + err => err ? reject(err) : resolve())); + }); const epochId = epochIdNow(); + const me = config.keys.id; + const defaultPubId = getDefaultPubId(); + const nowMs = Date.now(); + const attributed = new Set(results.map(r => r.txid).filter(Boolean)); + for (const e of Object.values(readUbiPaidLedger())) if (e && e.txid) attributed.add(e.txid); + let sends = null; + const walletSends = async () => { if (!sends) sends = await listWalletSends("pub"); return sends; }; + + const settlePaid = async ({ claim, claimantId, claimEpoch, paidKey, txid, amount, address, recovered }) => { + settleUbiPayment(paidKey, { txid, amount, address, paidAt: new Date().toISOString(), error: undefined }); + attributed.add(txid); + processedEpochUser.add(paidKey); + if (sends) sends.push({ category: "send", address, amount: -Number(amount), txid, comment: UBI_SEND_COMMENT, time: Math.floor(Date.now() / 1000) }); + try { + const open = (await transfersRepo.listByTag(`epoch:${claimEpoch}`)).find(a => a.to === claimantId && (a.status === "UNCLAIMED" || a.status === "UNCONFIRMED")); + if (open) await transfersRepo.markClosed(open.id, txid); + } catch (_) {} + console.log(`[UBI] ${recovered ? "found payment of" : "paid"} ${amount} ECO to ${claimantId.slice(0, 12)}… (${claimEpoch}) tx ${txid}`); + const allocationId = claim.allocationId || `claim:${claimEpoch}:${claimantId}`; + await publishUbiClaimResult(allocationId, claimEpoch, txid, claimantId, amount, address); + await publishBankClaim({ amount, epochId: claimEpoch, allocationId, txid }); + await publishUbiTransfer({ to: claimantId, amount, epochId: claimEpoch, txid }); + }; + for (const claim of claims) { const claimantId = claim._author; - if (!claimantId) continue; + if (!claimantId || claimantId === me || pubIds.has(claimantId)) continue; const claimEpoch = claim.epochId || epochId; - if (processedEpochUser.has(`${claimEpoch}:${claimantId}`)) continue; + if (!/^\d{4}-\d{2}$/.test(String(claimEpoch)) || (claimEpoch !== epochIdNow() && claimEpoch !== previousEpochId())) continue; + const paidKey = ubiPaidKey(claimEpoch, claimantId); + if (processedEpochUser.has(paidKey)) continue; + if (!claimPaidByMe(claim, me, defaultPubId, nowMs)) continue; + const ledgerEntry = readUbiPaidLedger()[paidKey]; + if (ledgerEntry && ledgerEntry.txid) continue; + if (refused.has(`${claimEpoch}:${claimantId}`)) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… skipped: refused by the inhabitant`); continue; } try { - const addr = await getUserAddress(claimantId); - if (!addr || !isValidEcoinAddress(addr)) continue; - const pubBal = await safeGetBalance("pub"); - if (pubBal <= 0) continue; + let attempts = 0; + if (ledgerEntry) { + attempts = Number(ledgerEntry.attempts) || 1; + const lastAt = Date.parse(ledgerEntry.lastAttemptAt || ledgerEntry.startedAt || "") || 0; + if (attempts >= UBI_MAX_ATTEMPTS) continue; + if (claimEpoch !== epochIdNow() && claimEpoch !== previousEpochId()) continue; + if (nowMs - lastAt < UBI_RETRY_AFTER_MS) continue; + const prevAddress = ledgerEntry.address || await getUserAddress(claimantId).catch(() => null); + if (prevAddress && ledgerEntry.amount) { + const list = await walletSends(); + if (!list) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… waiting: the wallet does not answer, the previous attempt cannot be checked`); continue; } + const found = await findWalletSend({ address: prevAddress, amount: ledgerEntry.amount, sinceMs: lastAt || epochStartMs(claimEpoch), exclude: attributed }, "pub", list); + if (found) { await settlePaid({ claim, claimantId, claimEpoch, paidKey, txid: found, amount: ledgerEntry.amount, address: prevAddress, recovered: true }); continue; } + } + } + const eligibility = await isEligibleClaimant(claimantId); + if (!eligibility.ok) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… skipped: ${eligibility.reason}`); continue; } + const addr = eligibility.address; + const pubBal = await safeGetBalance("pub", ENGINE_RPC_TIMEOUT_MS); + if (pubBal <= 0) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… skipped: PUB wallet balance is 0`); continue; } const pv = computePoolVars(pubBal, DEFAULT_RULES); const addresses = await listAddressesMerged(); - const eligible = addresses.filter(a => a.address && isValidEcoinAddress(a.address)); - const karmaScore = await getUserEngagementScore(claimantId); + const eligible = addresses.filter(a => a.address && isValidEcoinAddress(a.address) && a.id !== me && !pubIds.has(a.id)); + const karmaScore = eligibility.score; const wMin = DEFAULT_RULES.caps.w_min; const wMax = DEFAULT_RULES.caps.w_max; - const capUser = DEFAULT_RULES.caps.cap_user_epoch; const userW = clamp(1 + karmaScore / 100, wMin, wMax); const totalW = eligible.reduce((acc) => acc + clamp(1, wMin, wMax), 0) || 1; - const amount = Number(Math.max(1, Math.min(pv.pool * userW / totalW, capUser)).toFixed(6)); - const txid = await rpcCall("sendtoaddress", [addr, amount, `UBI ${claimEpoch}`], "pub"); - if (!txid) continue; - await publishUbiClaimResult(claim.allocationId || `claim:${claimEpoch}:${claimantId}`, claimEpoch, txid, claimantId, amount); - await publishBankClaim({ amount, epochId: claimEpoch, allocationId: claim.allocationId || `claim:${claimEpoch}:${claimantId}`, txid }); - const now = new Date().toISOString(); - await new Promise((resolve, reject) => ssb.publish({ - type: "transfer", - from: config.keys.id, - to: claimantId, - concept: `UBI ${claimEpoch} ${claimantId}`, - amount: String(amount), - createdAt: now, - updatedAt: now, - deadline: null, - confirmedBy: [config.keys.id], - status: "UNCONFIRMED", - tags: ["UBI"], - opinions: {}, - opinions_inhabitants: [], - txid - }, (err, msg) => err ? reject(err) : resolve(msg))); + const ecoTax = await getUserEcoinTax(claimantId).catch(() => 0); + const archTax = await getUserArchTax(claimantId).catch(() => 0); + const amount = ubiAmountFor({ pool: pv.pool, userW, totalW, score: karmaScore, ecoTax, archTax }); + const list = await walletSends(); + if (!list) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… waiting: the wallet does not answer`); continue; } + const earlier = await findWalletSend({ address: addr, comment: UBI_SEND_COMMENT, amountIfNoComment: amount, sinceMs: epochStartMs(claimEpoch), exclude: attributed }, "pub", list); + if (earlier) { await settlePaid({ claim, claimantId, claimEpoch, paidKey, txid: earlier, amount, address: addr, recovered: true }); continue; } + if (pv.available < amount) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… skipped: the PUB wallet is down to its reserve`); continue; } + if (ubiAddressTaken(claimEpoch, addr, claimantId) || (paidAddressBy.has(`${claimEpoch}:${addr}`) && paidAddressBy.get(`${claimEpoch}:${addr}`) !== claimantId)) { console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… skipped: this ECOin address was already paid this epoch`); continue; } + if (ledgerEntry) settleUbiPayment(paidKey, { attempts: attempts + 1, lastAttemptAt: new Date().toISOString(), amount, address: addr, error: undefined }); + else { + if (!reserveUbiPayment(paidKey)) continue; + settleUbiPayment(paidKey, { attempts: 1, lastAttemptAt: new Date().toISOString(), amount, address: addr }); + } + processedEpochUser.add(paidKey); + const sent = await sendVerified(addr, amount, UBI_SEND_COMMENT, "pub"); + if (!sent.txid) { + settleUbiPayment(paidKey, { error: sent.unknown ? "the wallet did not answer" : "sendtoaddress failed" }); + console.warn(`[UBI] claim ${claimEpoch} by ${claimantId.slice(0, 12)}… not paid yet: ${sent.unknown ? "the wallet did not answer, it will be checked again" : "sendtoaddress failed, it will be retried"}`); + continue; + } + await settlePaid({ claim, claimantId, claimEpoch, paidKey, txid: sent.txid, amount, address: addr, recovered: sent.recovered }); + } catch (err) { console.warn(`[UBI] claim by ${String(claimantId).slice(0, 12)}… failed after payment: ${(err && err.message) || err}`); } + } + } + + async function readUbiLedger(ssb) { + const msgs = await readTyped(ssb, ["transfer", "transferConfirm", "ubiClaim"], { limit: Math.max(getLogLimit(), 20000) }); + const transfers = []; + const confirms = new Map(); + const claims = []; + for (const m of msgs) { + const v = m.value || {}; + const c = v.content || {}; + if (!c || typeof c !== "object") continue; + if (c.type === "transfer" && Array.isArray(c.tags) && c.tags.some(t => String(t).toUpperCase() === "UBI")) transfers.push({ key: m.key, author: v.author, ...c }); + else if (c.type === "transferConfirm" && c.target) { if (!confirms.has(c.target)) confirms.set(c.target, new Set()); confirms.get(c.target).add(v.author); } + else if (c.type === "ubiClaim") claims.push({ key: m.key, author: v.author, pubId: c.pubId, epochId: c.epochId }); + } + return { transfers, confirms, claims }; + } + + function isFollowing(ssb, source, dest) { + return new Promise((resolve) => { + if (!ssb || !ssb.friends || typeof ssb.friends.isFollowing !== "function") return resolve(false); + ssb.friends.isFollowing({ source, dest }, (err, val) => resolve(!err && !!val)); + }); + } + + async function rebalanceUbiPools() { + if (!isPubNode()) return []; + const ssb = await openSsb(); + if (!ssb || !ssb.publish) return []; + const me = config.keys.id; + const epochId = epochIdNow(); + const pubs = (await listUbiPubs()).filter(p => p.pubId !== me && p.balanceKnown); + const { transfers, confirms, claims } = await readUbiLedger(ssb); + const confirmedBy = (t) => confirms.get(t.key) || new Set(); + const sum = (list) => list.reduce((acc, t) => acc + (Number(t.amount) || 0), 0); + const createdOf = (t) => Date.parse(t.createdAt || "") || 0; + const isRebalance = (t) => String(t.concept || "").startsWith(REBALANCE_CONCEPT); + const epochOf = (t) => { + const tag = (Array.isArray(t.tags) ? t.tags : []).find(x => String(x).startsWith("epoch:")); + return tag ? String(tag).slice(6) : ((String(t.concept || "").match(/(\d{4}-\d{2})\s*$/) || [])[1] || ""); + }; + const eligibleCache = new Map(); + const eligible = async (id) => { + if (!eligibleCache.has(id)) eligibleCache.set(id, !!(await isEligibleClaimant(id)).ok); + return eligibleCache.get(id); + }; + const servedBy = async (pubId) => { + const epochs = new Set([previousEpochId(), epochId]); + const people = new Set(); + for (const c of claims) { + if (!epochs.has(c.epochId) || people.has(c.author) || !(c.pubId === pubId || transfers.some(t => t.author === pubId && t.to === c.author && isUbiPayoutConcept(t.concept) && epochOf(t) === c.epochId))) continue; + if (await eligible(c.author)) people.add(c.author); + } + return people; + }; + const now = Date.now(); + const balanceOf = (p) => { + const announcedAt = Number(p.timestamp) || 0; + const inflows = transfers.filter(t => isRebalance(t) && t.to === p.pubId && createdOf(t) > announcedAt - REBALANCE_SETTLE_MS); + const outflows = transfers.filter(t => isRebalance(t) && t.author === p.pubId && createdOf(t) > announcedAt); + const payouts = transfers.filter(t => t.author === p.pubId && isUbiPayoutConcept(t.concept) && createdOf(t) > announcedAt); + return Math.max(0, (Number(p.balance) || 0) + sum(inflows) - sum(outflows) - sum(payouts)); + }; + const sent = []; + const recorded = new Set(transfers.map(t => t.txid).filter(Boolean)); + const pendingAll = () => { const raw = readJson(REBALANCE_PENDING_PATH, {}); return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {}; }; + const setPending = (key, value) => { const all = pendingAll(); if (value) all[key] = value; else delete all[key]; writeJson(REBALANCE_PENDING_PATH, all); }; + let left = 0; + const record = async (pub, amount, txid, served, ep = epochId, at = null) => { + const when = at || new Date().toISOString(); + const content = { type: "transfer", from: me, to: pub.pubId, concept: `${REBALANCE_CONCEPT} · ${ep}`, amount: Number(amount).toFixed(6), category: "ECONOMIC", createdAt: when, updatedAt: when, deadline: null, confirmedBy: [me], status: "UNCONFIRMED", tags: ["UBI", "REBALANCE", `epoch:${ep}`], opinions: {}, opinions_inhabitants: [], txid }; + await new Promise((resolve, reject) => ssb.publish(content, (err, msg) => err ? reject(err) : resolve(msg))); + recorded.add(txid); + transfers.push({ ...content, author: me }); + left -= Number(amount); + console.log(`[UBI] rebalanced ${amount} ECO to PUB ${pub.pubId.slice(0, 12)}…${served ? ` (${served} claimants)` : ""} tx ${txid}`); + sent.push({ pubId: pub.pubId, amount: Number(amount), txid }); + }; + for (const [key, pending] of Object.entries(pendingAll())) { + const cut = key.indexOf(":"); + const ep = key.slice(0, cut); + const pubId = key.slice(cut + 1); + const list = await listWalletSends("pub"); + if (!list) { console.warn(`[UBI] rebalance to ${pubId.slice(0, 12)}… waiting: the wallet does not answer, the previous attempt cannot be checked`); continue; } + const found = await findWalletSend({ address: pending.address, amount: pending.amount, comment: REBALANCE_CONCEPT, sinceMs: Date.parse(pending.startedAt || "") || 0, exclude: recorded }, "pub", list); + if (found) await record(pubs.find(p => p.pubId === pubId) || { pubId }, pending.amount, found, 0, ep, pending.startedAt || null); + setPending(key, null); + } + if (!pubs.length) return sent; + const myBalance = await tryGetBalance("pub", ENGINE_RPC_TIMEOUT_MS); + if (myBalance === null) return sent; + const mine = computePoolVars(myBalance, DEFAULT_RULES).available; + const balances = new Map([[me, mine], ...pubs.map(p => [p.pubId, balanceOf(p)])]); + const average = Array.from(balances.values()).reduce((a, b) => a + b, 0) / balances.size; + const tolerance = Math.max(1, average * REBALANCE_TOLERANCE); + const myExcess = mine - average; + if (myExcess < tolerance) return sent; + const totalExcess = Array.from(balances.values()).reduce((acc, b) => acc + Math.max(0, b - average), 0); + left = myExcess; + const poorer = pubs.map(p => ({ pub: p, gap: average - balances.get(p.pubId) })).filter(x => x.gap >= tolerance).sort((a, b) => b.gap - a.gap); + for (const { pub, gap } of poorer) { + if (left < 1) break; + const tag = pub.pubId.slice(0, 12); + try { + const pendingKey = `${epochId}:${pub.pubId}`; + if (Object.keys(pendingAll()).some(k => k.endsWith(`:${pub.pubId}`))) { console.warn(`[UBI] rebalance to ${tag}… waiting: an earlier transfer is still being checked`); continue; } + if (!(await isFollowing(ssb, me, pub.pubId))) { console.warn(`[UBI] rebalance to ${tag}… skipped: this PUB does not follow it`); continue; } + const address = pub.address || await getUserAddress(pub.pubId).catch(() => null); + if (!address || !isValidEcoinAddress(address)) { console.warn(`[UBI] rebalance to ${tag}… skipped: no ECOin address`); continue; } + const served = await servedBy(pub.pubId); + if (served.size < REBALANCE_MIN_CLAIMANTS) { console.warn(`[UBI] rebalance to ${tag}… skipped: ${served.size} eligible claimant(s), ${REBALANCE_MIN_CLAIMANTS} required`); continue; } + const receivedBefore = sum(transfers.filter(t => t.author === me && t.to === pub.pubId && isRebalance(t))); + const paidOut = sum(transfers.filter(t => t.author === pub.pubId && isUbiPayoutConcept(t.concept) && confirmedBy(t).has(t.to))); + if (receivedBefore > 0 && paidOut < receivedBefore * REBALANCE_PAYOUT_RATIO) { console.warn(`[UBI] rebalance to ${tag}… skipped: paid out ${paidOut.toFixed(6)} of ${receivedBefore.toFixed(6)} received`); continue; } + const amount = Number(Math.min(left, gap * myExcess / totalExcess).toFixed(6)); + if (!(amount >= 1)) { console.warn(`[UBI] rebalance to ${tag}… skipped: nothing needed`); continue; } + setPending(pendingKey, { address, amount, startedAt: new Date().toISOString() }); + const result = await sendVerified(address, amount, REBALANCE_CONCEPT, "pub"); + if (!result.txid) { + if (!result.unknown) setPending(pendingKey, null); + console.warn(`[UBI] rebalance to ${tag}… not sent: ${result.unknown ? "the wallet did not answer, it will be checked next time" : "sendtoaddress failed"}`); + continue; + } + await record(pub, amount, result.txid, served.size); + setPending(pendingKey, null); + } catch (err) { console.warn(`[UBI] rebalance to ${tag}… failed: ${(err && err.message) || err}`); } + } + return sent; + } + + async function confirmIncomingTransfers() { + if (!services || !services.transfers || typeof services.transfers.confirmTransferById !== "function" || typeof services.transfers.listAll !== "function") return []; + const kind = isPubNode() ? "pub" : "user"; + const cfg = getWalletCfg(kind) || {}; + if (!cfg.url) return []; + const ssb = await openSsb(); + if (!ssb) return []; + const me = config.keys.id; + const msgs = await readTyped(ssb, ["transfer", "transferConfirm"], { limit: Math.max(getLogLimit(), 20000) }); + const txidOf = (t) => (String(t.txid || "").match(TXID_RE) ? String(t.txid) : (String(t.concept || "").match(TXID_RE) || [])[1] || "").toLowerCase(); + const signed = new Map(); + const confirmedByMe = new Set(); + for (const m of msgs) { + const v = m.value || {}; + const c = v.content || {}; + if (c.type === "transferConfirm" && c.target && v.author === me) confirmedByMe.add(c.target); + else if (c.type === "transfer" && c.to === me && c.from === v.author) signed.set(m.key, c); + } + const used = new Set(); + for (const k of confirmedByMe) { const txid = signed.has(k) ? txidOf(signed.get(k)) : ""; if (txid) used.add(txid); } + const claimed = new Map(); + const incoming = []; + for (const t of (await services.transfers.listAll("all")) || []) { + if (t.to !== me || t.from === me || t.from !== t.author) continue; + const txid = txidOf(t); + if (!txid) continue; + if (t.status === "CLOSED" || (Array.isArray(t.confirmedBy) && t.confirmedBy.includes(me))) { used.add(txid); continue; } + if (t.status !== "UNCONFIRMED") continue; + claimed.set(txid, (claimed.get(txid) || 0) + 1); + incoming.push({ ...t, txid }); + } + const done = []; + for (const t of incoming) { + if (claimed.get(t.txid) !== 1 || used.has(t.txid)) continue; + const amount = Number(t.amount); + if (!Number.isFinite(amount) || amount <= 0) continue; + const tx = await rpcCall("gettransaction", [t.txid], kind); + if (!tx || !(Number(tx.confirmations) >= 1) || !Array.isArray(tx.details)) continue; + const address = signed.has(t.id) && typeof signed.get(t.id).address === "string" ? signed.get(t.id).address : ""; + if (!tx.details.some(d => d && d.category === "receive" && Number(d.amount) >= amount - 0.000001 && (!address || d.address === address))) continue; + try { + await services.transfers.confirmTransferById(t.id); + used.add(t.txid); + console.log(`[WALLET] confirmed incoming ${amount.toFixed(6)} ECO from ${String(t.from).slice(0, 12)}… (${t.concept}) tx ${t.txid}`); + done.push(t.id); } catch (_) {} } + return done; } return { DEFAULT_RULES, isPubNode, - getConfiguredPubId, computeEpoch, executeEpoch, getUserEngagementScore, @@ -1409,6 +2643,8 @@ async function getLastPublishedTimestamp(userId) { publishPubAvailability, getPubAvailabilityFromSSB, hasClaimedThisMonth, + hasWalletCredentials, + sampleUserFunds, getUbiClaimHistory, claimUBI, processPendingClaims, @@ -1420,10 +2656,34 @@ async function getLastPublishedTimestamp(userId) { listEpochAllocations, addAddress, removeAddress, + listAddressBook, + addAddressBookEntry, + removeAddressBookEntry, ensureSelfAddressPublished, getUserAddress, setUserAddress, listAddressesMerged, + scanAllWalletsSSB, + hasPublishedAddress, + discoverUbiPub, + resolvePubId, + listUbiPubs, + listUbiPubsDetailed, + listUbiCharts, + listKarmaHistory, + listWealthSeries, + listUbiPaymentsSeries, + listNetworkEpochs, + normalizeRange, + filterByRange, + compactEcoHistory, + isEligibleClaimant, + ubiAmountFor, + rebalanceUbiPools, + confirmIncomingTransfers, + refuseUBI, + hasRefusedThisMonth, + claimAvailability, calculateEcoinValue, calculateEcoTaxStats, getUserEcoinTax, diff --git a/src/models/blobcache_model.js b/src/models/blobcache_model.js new file mode 100644 index 0000000..0bfb001 --- /dev/null +++ b/src/models/blobcache_model.js @@ -0,0 +1,161 @@ +const fs = require('fs'); +const pull = require('../server/node_modules/pull-stream'); + +const BLOB_RE = /&[A-Za-z0-9+/=]{44}\.sha256/g; +const DEFAULT_MAX_MB = 0; +const PROTECT_MS = 24 * 60 * 60 * 1000; +const FLUSH_MS = 60 * 1000; + +const pinsPath = () => { + try { return require('../configs/state-manager').statePath('blob-pins.json'); } catch (_) { return null; } +}; + +const accessPath = () => { + try { return require('../configs/state-manager').statePath('blob-access.json'); } catch (_) { return null; } +}; + +module.exports = ({ cooler } = {}) => { + const openSsb = async () => cooler.open(); + let access = null; + let dirty = false; + let flushTimer = null; + + const loadAccess = () => { + if (access) return access; + access = {}; + const p = accessPath(); + if (p) { try { access = JSON.parse(fs.readFileSync(p, 'utf8')) || {}; } catch (_) { access = {}; } } + return access; + }; + + const flush = () => { + if (!dirty) return; + const p = accessPath(); + if (!p) return; + try { fs.writeFileSync(p, JSON.stringify(loadAccess())); dirty = false; } catch (_) {} + }; + + const normalizeId = (raw) => { + let id = String(raw || '').trim(); + try { id = decodeURIComponent(id); } catch (_) {} + if (!id) return null; + if (!id.startsWith('&')) id = `&${id}`; + return /^&[A-Za-z0-9+/=]{44}\.sha256$/.test(id) ? id : null; + }; + + const touch = (raw) => { + const id = normalizeId(raw); + if (!id) return; + loadAccess()[id] = Date.now(); + dirty = true; + if (!flushTimer) { + flushTimer = setTimeout(() => { flushTimer = null; flush(); }, FLUSH_MS); + if (typeof flushTimer.unref === 'function') flushTimer.unref(); + } + }; + + const listBlobs = async (ssb) => new Promise((resolve) => { + if (!ssb.blobs || typeof ssb.blobs.ls !== 'function') return resolve([]); + pull(ssb.blobs.ls({ long: true }), pull.collect((err, arr) => { + if (err) return resolve([]); + resolve((arr || []).filter(b => b && b.id).map(b => ({ id: b.id, size: Number(b.size) || 0, ts: Number(b.ts) || 0 }))); + })); + }); + + const loadPins = () => { + const p = pinsPath(); + if (!p) return []; + try { const arr = JSON.parse(fs.readFileSync(p, 'utf8')); return Array.isArray(arr) ? arr : []; } catch (_) { return []; } + }; + + const pin = (ids) => { + const p = pinsPath(); + if (!p) return; + const set = new Set(loadPins()); + for (const raw of (Array.isArray(ids) ? ids : [ids])) { const id = normalizeId(raw); if (id) set.add(id); } + try { fs.writeFileSync(p, JSON.stringify(Array.from(set))); } catch (_) {} + }; + + const getContent = (ssb, key) => new Promise((resolve) => { + try { ssb.get(key, (err, v) => resolve(!err && v && v.content && typeof v.content === 'object' ? v.content : null)); } catch (_) { resolve(null); } + }); + + const ownBlobIds = async (ssb) => { + const own = new Set(); + const msgs = await new Promise((resolve) => { + pull(ssb.createUserStream({ id: ssb.id, reverse: false }), pull.collect((err, arr) => resolve(err ? [] : (arr || [])))); + }); + const spreads = new Map(); + const tombstoned = new Set(); + for (const m of msgs) { + const content = m && m.value && m.value.content; + if (!content || typeof content !== 'object') continue; + if (content.type === 'spread' && typeof content.link === 'string') spreads.set(m.key, content.link); + else if (content.type === 'tombstone' && typeof content.target === 'string') tombstoned.add(content.target); + for (const id of (JSON.stringify(content).match(BLOB_RE) || [])) own.add(id); + } + if (ssb.private && typeof ssb.private.read === 'function') { + const boxed = await new Promise((resolve) => { + pull(ssb.private.read({ reverse: false }), pull.collect((err, arr) => resolve(err ? [] : (arr || [])))); + }); + for (const m of boxed) { + const content = m && m.value && m.value.content; + if (content && typeof content === 'object') for (const id of (JSON.stringify(content).match(BLOB_RE) || [])) own.add(id); + } + } + for (const id of loadPins()) own.add(id); + for (const [key, link] of spreads) { + if (tombstoned.has(key)) continue; + const seeded = await getContent(ssb, link); + if (!seeded || (seeded.type !== 'torrent' && seeded.type !== 'file')) continue; + for (const id of (JSON.stringify(seeded).match(BLOB_RE) || [])) own.add(id); + } + return own; + }; + + const usage = async () => { + const ssb = await openSsb(); + const blobs = await listBlobs(ssb); + return { count: blobs.length, bytes: blobs.reduce((n, b) => n + b.size, 0) }; + }; + + const collect = async ({ maxBytes, now = Date.now(), protectMs = PROTECT_MS } = {}) => { + const limit = Number(maxBytes); + const ssb = await openSsb(); + const blobs = await listBlobs(ssb); + const total = blobs.reduce((n, b) => n + b.size, 0); + const result = { before: total, after: total, deleted: 0, freed: 0, kept: blobs.length }; + if (!(limit > 0) || total <= limit) return result; + const own = await ownBlobIds(ssb); + const seen = loadAccess(); + const candidates = blobs + .filter(b => !own.has(b.id)) + .map(b => ({ ...b, last: Math.max(Number(seen[b.id]) || 0, b.ts) })) + .filter(b => now - b.last > protectMs) + .sort((a, b) => a.last - b.last); + let current = total; + for (const b of candidates) { + if (current <= limit) break; + const removed = await new Promise((resolve) => ssb.blobs.rm(b.id, (err) => resolve(!err))); + if (!removed) continue; + current -= b.size; + result.deleted += 1; + result.freed += b.size; + delete seen[b.id]; + dirty = true; + } + result.after = current; + result.kept = blobs.length - result.deleted; + flush(); + return result; + }; + + const maxBytesFor = (cfg, { isPublic = false } = {}) => { + const mb = Number(cfg && cfg.blobCache && (isPublic ? cfg.blobCache.pubMaxMB : cfg.blobCache.maxMB)); + if (!Number.isFinite(mb) || mb <= 0) return 0; + return Math.round(mb * 1024 * 1024); + }; + + return { touch, usage, collect, ownBlobIds, maxBytesFor, flush, pin, DEFAULT_MAX_MB }; +}; +module.exports.DEFAULT_MAX_MB = DEFAULT_MAX_MB; diff --git a/src/models/blockchain_model.js b/src/models/blockchain_model.js index 39fe868..b305269 100644 --- a/src/models/blockchain_model.js +++ b/src/models/blockchain_model.js @@ -2,6 +2,8 @@ const pull = require('../server/node_modules/pull-stream'); const config = require('../server/ssb_config'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readContentWindow, requestScope, logTip } = require('./typed_log'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; module.exports = ({ cooler, tribeCrypto, tribesModel }) => { @@ -11,6 +13,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { if (!ssb) ssb = await cooler.open(); return ssb; }; + const nameByFeedId = new Map(); const HIDDEN_ENVELOPE_TYPES = new Set([ 'tribe-keys-distrib', @@ -19,7 +22,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { ]); const isHiddenBoxedContent = (rawContent) => - typeof rawContent === 'string' && rawContent.endsWith('.box'); + typeof rawContent === 'string' && /\.box\d*$/.test(rawContent); const buildAccessibleTribeIds = async () => { const set = new Set(); @@ -41,16 +44,10 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const hasBlob = async (ssbClient, url) => new Promise(resolve => ssbClient.blobs.has(url, (err, has) => resolve(!err && has))); - const isClosedSold = s => - String(s || '').toUpperCase() === 'SOLD' || String(s || '').toUpperCase() === 'CLOSED'; - - const projectRank = (status) => { - const S = String(status || '').toUpperCase(); - if (S === 'COMPLETED') return 3; - if (S === 'ACTIVE') return 2; - if (S === 'PAUSED') return 1; - if (S === 'CANCELLED') return 0; - return -1; + const isReplacedBlock = (vg, tombstoned, block) => { + const c = block.content; + const rootDeleted = !c.replaces && c?.type === 'forum' && c.root && tombstoned.has(c.root); + return vg.tipOf(block.id) !== block.id || vg.isForeignCopy(block.id) || tombstoned.has(block.id) || !!rootDeleted; }; const safeDecode = (s) => { @@ -127,115 +124,83 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { return nameByFeedId; }; + let windowCache = null; + const readWindow = async (ssbClient) => { + const tip = await logTip(ssbClient).catch(() => null); + if (tip && windowCache && windowCache.tip === tip) return windowCache.msgs; + const msgs = await readContentWindow(ssbClient, logLimit); + if (tip) windowCache = { tip, msgs }; + return msgs; + }; + + async function buildBlocks(showLogs, me) { + const ssbClient = await openSsb(); + + const results = await readWindow(ssbClient); + + const tombstoned = buildValidatedTombstoneSet(results); + const idToBlock = new Map(); + const versions = []; + + const fpIdx = tribeCrypto ? tribeCrypto.buildFingerprintIndex() : null; + const accessibleTribeIds = await buildAccessibleTribeIds(); + for (const msg of results) { + const k = msg.key; + let c = msg.value?.content; + const author = msg.value?.author; + if (isHiddenBoxedContent(c) || msg.value?.private === true) continue; + if (showLogs && typeof c === 'string' && author === me) { + try { + const dec = ssbClient.private.unbox({ key: k, value: msg.value, timestamp: msg.timestamp || msg.value?.timestamp || 0 }); + c = dec?.value?.content; + } catch { c = null; } + } + if (!c?.type) continue; + if (HIDDEN_ENVELOPE_TYPES.has(c.type)) continue; + if (tribeCrypto && tribeCrypto.isTribeMsg(c)) { + const r = fpIdx ? tribeCrypto.unwrapMsg(c, fpIdx) : null; + if (!r || !r.body) continue; + const inner = r.body; + const innerType = inner.k === 'tribe' ? 'tribe' : (inner.k === 'tribe-content' ? `tribe-content:${inner.contentType || ''}` : inner.k || 'tribe-msg'); + c = { type: innerType, _decrypted: true, _rootId: r.rootId, ...inner }; + } else if (c.tribeId && !accessibleTribeIds.has(c.tribeId)) { + continue; + } + + if (c.type === 'about') { + const aboutId = String(c.about || author || '').trim(); + const nm = typeof c.name === 'string' ? c.name.trim() : ''; + if (aboutId && nm && !nameByFeedId.has(aboutId)) nameByFeedId.set(aboutId, nm); + } + + if (c.type === 'tombstone' && c.target) { + idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c, size: Buffer.byteLength(JSON.stringify(msg.value), 'utf8') }); + continue; + } + versions.push(versionEntry({ key: k, value: { ...msg.value, content: c } })); + idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c, size: Buffer.byteLength(JSON.stringify(msg.value), 'utf8') }); + } + + const vg = versionGroups(versions); + + const blockData = Array.from(idToBlock.values()).map(block => ({ + ...block, + isTombstoned: tombstoned.has(block.id), + isReplaced: isReplacedBlock(vg, tombstoned, block) + })); + + return blockData; + } + return { async listBlockchain(filter = 'all', userId, search = {}) { - const ssbClient = await openSsb(); - - const results = await new Promise((resolve, reject) => - pull( - ssbClient.createLogStream({ reverse: true, limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ) - ); - - const tombstoned = buildValidatedTombstoneSet(results); - const idToBlock = new Map(); - const referencedAsReplaces = new Set(); - - const nameByFeedId = new Map(); - const showLogs = (filter === 'logs' || filter === 'LOGS'); const me = userId || config.keys.id; - const fpIdx = tribeCrypto ? tribeCrypto.buildFingerprintIndex() : null; - const accessibleTribeIds = await buildAccessibleTribeIds(); - for (const msg of results) { - const k = msg.key; - let c = msg.value?.content; - const author = msg.value?.author; - if (isHiddenBoxedContent(c)) continue; - if (showLogs && typeof c === 'string' && author === me) { - try { - const dec = ssbClient.private.unbox({ key: k, value: msg.value, timestamp: msg.timestamp || msg.value?.timestamp || 0 }); - c = dec?.value?.content; - } catch { c = null; } - } - if (!c?.type) continue; - if (HIDDEN_ENVELOPE_TYPES.has(c.type)) continue; - if (tribeCrypto && tribeCrypto.isTribeMsg(c)) { - const r = fpIdx ? tribeCrypto.unwrapMsg(c, fpIdx) : null; - if (!r || !r.body) continue; - const inner = r.body; - const innerType = inner.k === 'tribe' ? 'tribe' : (inner.k === 'tribe-content' ? `tribe-content:${inner.contentType || ''}` : inner.k || 'tribe-msg'); - c = { type: innerType, _decrypted: true, _rootId: r.rootId, ...inner }; - } else if (c.tribeId && !accessibleTribeIds.has(c.tribeId)) { - continue; - } - - if (c.type === 'about') { - const aboutId = String(c.about || author || '').trim(); - const nm = typeof c.name === 'string' ? c.name.trim() : ''; - if (aboutId && nm && !nameByFeedId.has(aboutId)) nameByFeedId.set(aboutId, nm); - } - - if (c.type === 'tombstone' && c.target) { - idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c, size: Buffer.byteLength(JSON.stringify(msg.value), 'utf8') }); - continue; - } - if (c.replaces) referencedAsReplaces.add(c.replaces); - idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c, size: Buffer.byteLength(JSON.stringify(msg.value), 'utf8') }); - } - - const tipBlocks = []; - for (const [id, block] of idToBlock.entries()) { - if (!referencedAsReplaces.has(id) && block.content.replaces) tipBlocks.push(block); - } - for (const [id, block] of idToBlock.entries()) { - if (!block.content.replaces && !referencedAsReplaces.has(id)) tipBlocks.push(block); - } - - const groups = {}; - for (const block of tipBlocks) { - const ancestor = block.content.replaces || block.id; - if (!groups[ancestor]) groups[ancestor] = []; - groups[ancestor].push(block); - } - - const liveTipIds = new Set(); - for (const groupBlocks of Object.values(groups)) { - let best = groupBlocks[0]; - for (const block of groupBlocks) { - if (block.type === 'market') { - if (isClosedSold(block.content.status) && !isClosedSold(best.content.status)) { - best = block; - } else if ((block.content.status === best.content.status) && block.ts > best.ts) { - best = block; - } - } else if (block.type === 'project') { - const br = projectRank(best.content.status); - const cr = projectRank(block.content.status); - if (cr > br || (cr === br && block.ts > best.ts)) best = block; - } else if (block.type === 'job' || block.type === 'forum') { - if (block.ts > best.ts) best = block; - } else { - if (block.ts > best.ts) best = block; - } - } - liveTipIds.add(best.id); - } - - const blockData = Array.from(idToBlock.values()).map(block => { - const c = block.content; - const rootDeleted = c?.type === 'forum' && c.root && tombstoned.has(c.root); - return { - ...block, - isTombstoned: tombstoned.has(block.id), - isReplaced: c.replaces - ? (!liveTipIds.has(block.id) || tombstoned.has(block.id)) - : referencedAsReplaces.has(block.id) || tombstoned.has(block.id) || rootDeleted - }; - }); - - let filtered = blockData; + const store = requestScope.getStore(); + const memoKey = `blockchainBase:${showLogs}:${me}`; + if (store && !store[memoKey]) store[memoKey] = buildBlocks(showLogs, me); + const blockData = await (store ? store[memoKey] : buildBlocks(showLogs, me)); + let filtered = blockData.slice(); if (filter === 'RECENT' || filter === 'recent') { const now = Date.now(); @@ -267,7 +232,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const needsNameSearch = !!authorQ && !authorQ.toLowerCase().includes('.ed25519'); if (needsNameSearch) { - aboutIndex = await buildNameIndexFromAbout(ssbClient, 10000); + aboutIndex = await buildNameIndexFromAbout(await openSsb(), 10000); for (const [fid, nm] of aboutIndex.entries()) { if (!nameByFeedId.has(fid)) nameByFeedId.set(fid, nm); } @@ -293,17 +258,12 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { async getBlockById(id, userId) { const ssbClient = await openSsb(); - const results = await new Promise((resolve, reject) => - pull( - ssbClient.createLogStream({ reverse: true, limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ) - ); + const results = await readWindow(ssbClient); const me = userId || config.keys.id; const tombstoned = buildValidatedTombstoneSet(results); const idToBlock = new Map(); - const referencedAsReplaces = new Set(); + const versions = []; const fpIdx = tribeCrypto ? tribeCrypto.buildFingerprintIndex() : null; const accessibleTribeIds = await buildAccessibleTribeIds(); @@ -311,7 +271,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const k = msg.key; let c = msg.value?.content; const author = msg.value?.author; - if (isHiddenBoxedContent(c)) continue; + if (isHiddenBoxedContent(c) || msg.value?.private === true) continue; if (typeof c === 'string' && author === me) { try { const dec = ssbClient.private.unbox({ key: k, value: msg.value, timestamp: msg.timestamp || msg.value?.timestamp || 0 }); @@ -333,48 +293,10 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c, size: Buffer.byteLength(JSON.stringify(msg.value), 'utf8') }); continue; } - if (c.replaces) referencedAsReplaces.add(c.replaces); + versions.push(versionEntry({ key: k, value: { ...msg.value, content: c } })); idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c, size: Buffer.byteLength(JSON.stringify(msg.value), 'utf8') }); } - const tipBlocks = []; - for (const [bid, block] of idToBlock.entries()) { - if (!referencedAsReplaces.has(bid) && block.content.replaces) tipBlocks.push(block); - } - for (const [bid, block] of idToBlock.entries()) { - if (!block.content.replaces && !referencedAsReplaces.has(bid)) tipBlocks.push(block); - } - - const groups = {}; - for (const block of tipBlocks) { - const ancestor = block.content.replaces || block.id; - if (!groups[ancestor]) groups[ancestor] = []; - groups[ancestor].push(block); - } - - const liveTipIds = new Set(); - for (const groupBlocks of Object.values(groups)) { - let best = groupBlocks[0]; - for (const block of groupBlocks) { - if (block.type === 'market') { - if (isClosedSold(block.content.status) && !isClosedSold(best.content.status)) { - best = block; - } else if ((block.content.status === best.content.status) && block.ts > best.ts) { - best = block; - } - } else if (block.type === 'project') { - const br = projectRank(best.content.status); - const cr = projectRank(block.content.status); - if (cr > br || (cr === br && block.ts > best.ts)) best = block; - } else if (block.type === 'job' || block.type === 'forum') { - if (block.ts > best.ts) best = block; - } else { - if (block.ts > best.ts) best = block; - } - } - liveTipIds.add(best.id); - } - const block = idToBlock.get(id); if (!block) return null; @@ -383,12 +305,8 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { if (!valid) return null; } - const c = block.content; - const rootDeleted = c?.type === 'forum' && c.root && tombstoned.has(c.root); const isTombstoned = tombstoned.has(block.id); - const isReplaced = c.replaces - ? (!liveTipIds.has(block.id) || tombstoned.has(block.id)) - : referencedAsReplaces.has(block.id) || tombstoned.has(block.id) || rootDeleted; + const isReplaced = isReplacedBlock(versionGroups(versions), tombstoned, block); return { ...block, isTombstoned, isReplaced }; } diff --git a/src/models/blog_model.js b/src/models/blog_model.js index 8a7df90..61edcfb 100644 --- a/src/models/blog_model.js +++ b/src/models/blog_model.js @@ -1,6 +1,8 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, memoIndex } = require('./typed_log'); +const longText = require('../backend/long_text'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const OPINION_TYPE = 'blogOpinion'; @@ -9,13 +11,9 @@ module.exports = ({ cooler, isPublic = false }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const BLOG_TYPES = ['post', OPINION_TYPE, 'tombstone', longText.CHUNK_TYPE]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, BLOG_TYPES, { limit: logLimit }); const isRootPost = (c) => !!c && c.type === 'post' && typeof c.text === 'string' && @@ -23,6 +21,7 @@ module.exports = ({ cooler, isPublic = false }) => { const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); + const chunks = longText.indexChunks(messages); const posts = new Map(); const commentsByRoot = new Map(); const opinionsByRoot = new Map(); @@ -52,7 +51,7 @@ module.exports = ({ cooler, isPublic = false }) => { } } - return { tomb, posts, commentsByRoot, opinionsByRoot }; + return { tomb, posts, commentsByRoot, opinionsByRoot, chunks }; }; const buildBlog = (node, idx) => { @@ -63,7 +62,7 @@ module.exports = ({ cooler, isPublic = false }) => { key: node.key, author: node.author, subject: typeof c.contentWarning === 'string' ? c.contentWarning : '', - text: c.text || '', + text: longText.joinText(c.text || '', c.chunks, longText.lookupIn(idx.chunks), node.author), mentions: Array.isArray(c.mentions) ? c.mentions : [], allowComments: c.allowComments !== false, createdAt: new Date(node.ts).toISOString(), @@ -76,7 +75,7 @@ module.exports = ({ cooler, isPublic = false }) => { const collect = async () => { const ssbClient = await openSsb(); const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); + const idx = memoIndex('blog', messages, buildIndex); const list = []; for (const node of idx.posts.values()) list.push(buildBlog(node, idx)); return { list, idx, viewerId: ssbClient.id }; @@ -96,6 +95,7 @@ module.exports = ({ cooler, isPublic = false }) => { if (f === 'MINE') out = out.filter(b => String(b.author) === String(viewerId)); else if (f === 'FAVORITES') out = favorites ? out.filter(b => favorites.has(String(b.id))) : []; + else if (f === 'RECENT') out = out.filter(b => (Date.parse(b.createdAt || '') || 0) >= Date.now() - 86400000); const q = String(opts.q || '').trim().toLowerCase(); if (q) out = out.filter(b => @@ -123,14 +123,26 @@ module.exports = ({ cooler, isPublic = false }) => { const body = String(text || '').trim(); if (!body) throw new Error('Blog text is required'); const ssbClient = await openSsb(); - const content = { + const publish = (c) => new Promise((res, rej) => ssbClient.publish(c, (err, msg) => err ? rej(err) : res(msg))); + const content = await longText.chunkContent({ type: 'post', text: body, allowComments: allowComments !== false, ...(Array.isArray(mentions) && mentions.length ? { mentions } : {}), ...(String(subject || '').trim() ? { contentWarning: String(subject).trim() } : {}) - }; - return new Promise((res, rej) => ssbClient.publish(content, (err, msg) => err ? rej(err) : res(msg))); + }, 'text', { publish }); + return publish(content); + }, + + async deleteBlogById(id) { + if (isPublic) throw new Error('Not available in public mode'); + const ssbClient = await openSsb(); + const userId = ssbClient.id; + const msg = await new Promise((res, rej) => ssbClient.get(id, (err, m) => (err || !m || !m.content) ? rej(new Error('Blog not found')) : res(m))); + if (!isRootPost(msg.content)) throw new Error('Blog not found'); + if (msg.author !== userId) throw new Error('Not the author'); + const content = { type: 'tombstone', target: id, deletedAt: new Date().toISOString(), author: userId }; + return new Promise((res, rej) => ssbClient.publish(content, (err, result) => err ? rej(err) : res(result))); }, async createOpinion(id, category) { diff --git a/src/models/bookmarking_model.js b/src/models/bookmarking_model.js index 86a2bea..df6c3ac 100644 --- a/src/models/bookmarking_model.js +++ b/src/models/bookmarking_model.js @@ -3,7 +3,8 @@ const moment = require("../server/node_modules/moment"); const { getConfig } = require("../configs/config-manager.js"); const categories = require("../backend/opinion_categories"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -36,13 +37,9 @@ module.exports = ({ cooler }) => { return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const BOOKMARK_TYPES = ["bookmark", "bookmarkOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, BOOKMARK_TYPES, { limit: logLimit }); const getMsg = async (ssbClient, key) => new Promise((resolve, reject) => { @@ -52,9 +49,6 @@ module.exports = ({ cooler }) => { const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const opinionMsgs = []; for (const m of messages) { @@ -70,48 +64,16 @@ module.exports = ({ cooler }) => { const ts = v.timestamp || m.timestamp || 0; let sizeBytes = 0; try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; } - nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); } - for (const [k, t] of Array.from(parent.entries())) { - const cn = nodes.get(k); - const pn = nodes.get(t); - if (!pn) { parent.delete(k); if (child.get(t) === k) child.delete(t); continue; } - if (!cn || String(cn.author) !== String(pn.author)) { parent.delete(k); if (child.get(t) === k) child.delete(t); nodes.delete(k); } - } - - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } - const forward = new Map(); - for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); - const resolveGroup = (root) => { const contentTip = contentTipOf(root); const contentNode = nodes.get(contentTip) || nodes.get(root); @@ -128,9 +90,9 @@ module.exports = ({ cooler }) => { }; const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); - return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildBookmark = (node, rootId, viewerId, agg) => { @@ -172,12 +134,8 @@ module.exports = ({ cooler }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(messages); - let tip = id; - while (idx.forward.has(tip)) tip = idx.forward.get(tip); - if (idx.tomb.has(tip)) throw new Error("Bookmark not found"); - - let root = tip; - while (idx.parent.has(root)) root = idx.parent.get(root); + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Bookmark not found"); return root; }, @@ -318,10 +276,10 @@ module.exports = ({ cooler }) => { const viewer = viewerId || ssbClient.id; const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); + const idx = memoIndex('bookmarking', messages, buildIndex); const rootId = idx.rootOf(id); const agg = idx.resolveGroup(rootId); - if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Bookmark not found"); + if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Bookmark not found"); return buildBookmark(agg.contentNode, rootId, viewer, agg); }, diff --git a/src/models/calendars_model.js b/src/models/calendars_model.js index cb9089a..e21f286 100644 --- a/src/models/calendars_model.js +++ b/src/models/calendars_model.js @@ -2,6 +2,7 @@ const pull = require("../server/node_modules/pull-stream") const crypto = require("crypto") const { getConfig } = require("../configs/config-manager.js") const { buildValidatedTombstoneSet } = require('./tombstone_validator') +const { readTyped, memoIndex } = require('./typed_log') const { collabContent, openInviteOf } = require('../backend/collab_content') const calCollab = collabContent({ membersField: 'participants', undecField: 'encrypted', contentFields: ['title', 'deadline', 'status'], listFields: ['tags', 'invites'] }) const logLimit = getConfig().ssbLogStream?.limit || 1000 @@ -14,13 +15,27 @@ const normalizeTags = (raw) => { return String(raw).split(",").map(t => t.trim()).filter(Boolean) } const hasAnyInterval = (w, m, y) => !!(w || m || y) -const { expandRecurrence } = require('./recurrence') +const { expandRecurrence, truthy } = require('./recurrence') +const MAX_OCCURRENCES_PER_DATE = 520 +const MAX_DATES_PER_VIEW = 5000 const ts = (v) => { const t = new Date(v).getTime() return Number.isFinite(t) ? t : null } +const boundedOccurrences = (date, until, weekly, monthly, yearly) => { + const start = ts(date) + const end = ts(until) + if (start === null || end === null) return expandRecurrence(date, until, weekly, monthly, yearly).slice(0, MAX_OCCURRENCES_PER_DATE) + const limit = new Date(start) + if (truthy(weekly)) limit.setUTCDate(limit.getUTCDate() + 7 * MAX_OCCURRENCES_PER_DATE) + else if (truthy(monthly)) limit.setUTCMonth(limit.getUTCMonth() + MAX_OCCURRENCES_PER_DATE) + else limit.setUTCFullYear(limit.getUTCFullYear() + MAX_OCCURRENCES_PER_DATE) + const stop = Number.isFinite(limit.getTime()) ? Math.min(end, limit.getTime()) : end + return expandRecurrence(date, new Date(stop).toISOString(), weekly, monthly, yearly).slice(0, MAX_OCCURRENCES_PER_DATE) +} + const assertCalendarDates = ({ deadline, date, until }) => { const now = Date.now() const dl = deadline ? ts(deadline) : null @@ -48,6 +63,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) return (tribeCrypto && tribeCrypto.getKeys(rid)) || [] } const lookupGen = (rid) => ((ownCrypto && ownCrypto.getGen(rid)) || (tribeCrypto && tribeCrypto.getGen(rid)) || 0) + const keyProofFor = (keyHex, id) => { try { return crypto.createHmac("sha256", Buffer.from(keyHex, "hex")).update(String(id), "utf8").digest("hex") } catch (_) { return null } } const rotateCalendarKey = async (rootId, remainingMembers) => { if (!ownCrypto || !tribeCrypto || !rootId) return @@ -55,11 +71,10 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) if (!existing) return const newKey = ownCrypto.generateTribeKey() const newGen = ownCrypto.addNewKey(rootId, newKey) - if (!Array.isArray(remainingMembers) || !remainingMembers.length) return const ssbClient = await openSsb() const ssbKeys = require("../server/node_modules/ssb-keys") const memberKeys = {} - for (const m of remainingMembers) { + for (const m of new Set([ssbClient.id, ...(Array.isArray(remainingMembers) ? remainingMembers : [])])) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys) } catch (_) {} } if (Object.keys(memberKeys).length) { @@ -76,9 +91,12 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const ssbKeys = require("../server/node_modules/ssb-keys") const config = require("../server/ssb_config") const msgs = await readAll(ssbClient) + const authorOf = new Map() + for (const m of msgs) if (m && m.key && m.value) authorOf.set(m.key, m.value.author) for (const m of msgs) { const c = m.value && m.value.content if (!c || c.type !== "tribe-keys") continue + if (!c.tribeId || !authorOf.has(c.tribeId) || (m.value.author !== authorOf.get(c.tribeId) && m.value.author !== ssbClient.id)) continue const memberKeys = c.memberKeys if (!memberKeys || typeof memberKeys !== "object") continue const boxed = memberKeys[ssbClient.id] @@ -92,12 +110,45 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) } catch (_) {} } - const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))) - ) + const CALENDAR_TYPES = ["calendar", "calendarDate", "calendarNote", "calendarParticipant", "calendarReminderSent", "tribe-keys", "tombstone"] + + const readAll = async (ssbClient) => unwrapForIndex(await readTyped(ssbClient, CALENDAR_TYPES, { limit: logLimit, withWindow: true })) + + const ensureMemberKeys = async (ssbClient, messages, cals) => { + if (!tribeCrypto || !ownCrypto) return + const leavesByRoot = new Map() + for (const m of messages) { + const c = m.value && m.value.content + if (!c || c.type !== "calendarParticipant" || c.on !== false || !c.target) continue + if (!leavesByRoot.has(c.target)) leavesByRoot.set(c.target, []) + leavesByRoot.get(c.target).push({ member: m.value.author, ts: Number(m.timestamp || m.value.timestamp || 0) }) + } + const ssbKeys = require("../server/node_modules/ssb-keys") + for (const cal of (Array.isArray(cals) ? cals : [])) { + if (!cal || cal.tribeId || cal.encrypted || cal.author !== ssbClient.id) continue + const rootId = cal.rootId + if (!rootId || !lookupKey(rootId)) continue + const members = Array.isArray(cal.participants) ? cal.participants : [] + const plan = ownCrypto.keyPlan({ rootId, ownerId: cal.author, gen: lookupGen(rootId), members, messages, leaves: leavesByRoot.get(rootId) || [] }) + if (plan.rotate) { + await rotateCalendarKey(rootId, members) + continue + } + if (!plan.missing.length) continue + const key = lookupKey(rootId) + const memberKeys = {} + for (const m of plan.missing) { + try { memberKeys[m] = tribeCrypto.boxKeyForMember(key, m, ssbKeys) } catch (_) {} + } + if (!Object.keys(memberKeys).length) continue + await new Promise((resolve) => { + ssbClient.publish({ type: "tribe-keys", tribeId: rootId, generation: lookupGen(rootId) || 1, memberKeys }, () => resolve()) + }) + } + } const tribeHelpers = tribeCrypto ? tribeCrypto.createHelpers(tribesModel) : null + const unwrapForIndex = (msgs) => tribeHelpers ? tribeHelpers.unwrapMessagesForKind(msgs, CALENDAR_TYPES) : msgs const encryptIfTribe = tribeHelpers ? tribeHelpers.encryptIfTribe : async (c) => c const decryptIfTribe = tribeHelpers ? tribeHelpers.decryptIfTribe : async (c) => c const assertReadable = tribeHelpers ? tribeHelpers.assertReadable : () => {} @@ -111,7 +162,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const decryptScoped = async (content, rootId) => { if (!content) return content - if (content.tribeId) return await decryptIfTribe(content) + if (content.tribeId || (tribeCrypto && tribeCrypto.isTribeMsg(content))) return await decryptIfTribe(content) return decryptCalendarRoot(content, rootId) } @@ -185,9 +236,9 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const c = v.content if (!c) continue if (c.type === "tombstone" && c.target) { tombRequests.push({ target: c.target, author: v.author }); continue } - if (c.type === "calendarParticipant" && c.target) { participantMsgs.push({ target: c.target, author: v.author, on: c.on !== false, ts: v.timestamp || m.timestamp || 0 }); continue } + if (c.type === "calendarParticipant" && c.target) { participantMsgs.push({ target: c.target, author: v.author, on: c.on !== false, keyProof: typeof c.keyProof === "string" ? c.keyProof : "", code: typeof c.code === "string" ? c.code : "", ts: v.timestamp || m.timestamp || 0 }); continue } if (c.type === "calendar") { - nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) authorByKey.set(k, v.author) if (c.replaces) { parent.set(k, c.replaces); child.set(c.replaces, k) } } @@ -207,30 +258,31 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const rootOf = (id) => { let cur = id; while (parent.has(cur)) cur = parent.get(cur); return cur } const tipOf = (id) => { let cur = id; while (child.has(cur)) cur = child.get(cur); return cur } - const contentTipOf = (root) => { - const rn = nodes.get(root) - if (!rn) return root - let cur = root - let best = root - const seen = new Set() - while (child.has(cur) && !seen.has(cur)) { - seen.add(cur) - const next = child.get(cur) - const n = nodes.get(next) - if (!n) break - if (n.author === rn.author && !tomb.has(next)) best = next - cur = next - } - return best + const ownerTip = new Map() + for (const [k, n] of nodes.entries()) { + const r = rootOf(k) + const rn = nodes.get(r) + if (!rn || n.author !== rn.author || tomb.has(k)) continue + const prev = ownerTip.has(r) ? nodes.get(ownerTip.get(r)) : null + if (!prev || n.seq > prev.seq || (n.seq === prev.seq && n.ts >= prev.ts)) ownerTip.set(r, k) } + const contentTipOf = (root) => ownerTip.get(root) || root const roots = new Set() for (const id of nodes.keys()) roots.add(rootOf(id)) + const joinAllowed = (root, pm) => { + const rn = nodes.get(root) + if (!rn) return false + if (pm.author === rn.author || rn.c.tribeId) return true + if (!rn.c.encryptedPayload && String(rn.c.status || "OPEN").toUpperCase() === "OPEN") return true + return !!pm.keyProof && lookupKeys(root).some(k => keyProofFor(k, pm.author) === pm.keyProof) + } const participantsByRoot = new Map() for (const pm of participantMsgs) { if (!nodes.has(pm.target)) continue const r = rootOf(pm.target) + if (pm.on && !joinAllowed(r, pm)) continue if (!participantsByRoot.has(r)) participantsByRoot.set(r, new Map()) const m2 = participantsByRoot.get(r) const prev = m2.get(pm.author) @@ -267,17 +319,29 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) deadline: undec ? "" : (c.deadline || ""), tags: Array.isArray(c.tags) ? c.tags : [], mapUrl: typeof c.mapUrl === "string" ? c.mapUrl : "", - author: c.author || node.author, + author: node.author || c.author, participants: Array.isArray(participants) ? participants : (Array.isArray(c.participants) ? c.participants : []), invites: Array.isArray(c.invites) ? c.invites : [], createdAt: c.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || null, tribeId: c.tribeId || null, - encrypted: !!undec + encrypted: !!undec, + contentEncrypted: !!(c.encryptedPayload || c._decrypted === true) } } + const calendarMembers = async (cal) => { + const set = new Set([cal.author, ...(Array.isArray(cal.participants) ? cal.participants : [])].filter(Boolean)) + if (cal.tribeId && tribesModel) { + try { + const t = await tribesModel.getTribeById(cal.tribeId) + for (const m of (t && Array.isArray(t.members) ? t.members : [])) set.add(m) + } catch (_) {} + } + return set + } + const isClosed = (calendar) => { if (calendar.status === "CLOSED") return true if (!calendar.deadline) return false @@ -329,11 +393,8 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildIndex(messages) - let tip = id - while (idx.child.has(tip)) tip = idx.child.get(tip) - if (idx.tomb.has(tip)) throw new Error("Not found") - let root = tip - while (idx.parent.has(root)) root = idx.parent.get(root) + const root = idx.rootOf(id) + if (idx.tomb.has(idx.contentTipOf(root))) throw new Error("Not found") return root }, @@ -341,8 +402,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildIndex(messages) - let tip = id - while (idx.child.has(tip)) tip = idx.child.get(tip) + const tip = idx.contentTipOf(idx.rootOf(id)) if (idx.tomb.has(tip)) throw new Error("Not found") return tip }, @@ -476,11 +536,9 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) ssbClient.get(tipId, (err, it) => err ? reject(err) : resolve(it)) }) if (!item || !item.content) throw new Error("Calendar not found") - const oldDec = item.content.tribeId - ? await decryptIfTribe(item.content) - : decryptCalendarRoot(item.content, rootId) + const oldDec = await decryptScoped(item.content, rootId) assertReadable(oldDec, "Calendar") - if ((oldDec.author || item.content.author) !== userId) throw new Error("Not the author") + if (item.author !== userId) throw new Error("Not the author") const nextDeadline = data.deadline !== undefined ? data.deadline : (oldDec.deadline || "") if (nextDeadline !== (oldDec.deadline || "")) assertCalendarDates({ deadline: nextDeadline }) let updated = { @@ -493,12 +551,17 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) author: oldDec.author || userId, participants: oldDec.participants || [userId], invites: Array.isArray(oldDec.invites) ? oldDec.invites : [], - ...(item.content.tribeId ? { tribeId: item.content.tribeId } : {}), + ...(oldDec.tribeId ? { tribeId: oldDec.tribeId } : {}), createdAt: oldDec.createdAt, updatedAt: new Date().toISOString(), replaces: tipId } - if (item.content.tribeId) updated = await encryptIfTribe(updated) + const closing = !oldDec.tribeId && String(oldDec.status || "OPEN").toUpperCase() === "OPEN" && updated.status === "CLOSED" + if (closing) { + updated.invites = updated.invites.filter(inv => !(inv && typeof inv === "object" && inv.public === true)) + try { await rotateCalendarKey(rootId, (updated.participants || []).filter(p => p !== userId)) } catch (_) {} + } + if (updated.tribeId) updated = await encryptIfTribe(updated) else updated = encryptStandalone(updated, rootId) const result = await new Promise((resolve, reject) => ssbClient.publish(updated, (e, res) => e ? reject(e) : resolve(res))) const tombstone = { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId } @@ -515,8 +578,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const rootId = await this.resolveRootId(id) const dec = await decryptScoped(item.content, rootId) assertReadable(dec, "Calendar") - const contentAuthor = (dec && dec.author) || (typeof item.content === 'object' && item.content.author) - if (contentAuthor !== userId) throw new Error("Not the author") + if (item.author !== userId) throw new Error("Not the author") const tombstone = { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId } return new Promise((resolve, reject) => ssbClient.publish(tombstone, e => e ? reject(e) : resolve())) }, @@ -547,9 +609,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const participants = Array.isArray(cal.participants) ? cal.participants : [] if (!participants.includes(userId)) return const content = { type: "calendarParticipant", target: rootId, on: false, createdAt: new Date().toISOString() } - const result = await new Promise((resolve, reject) => ssbClient.publish(content, (e, res) => e ? reject(e) : resolve(res))) - try { await rotateCalendarKey(rootId, participants.filter(p => p !== userId)) } catch (_) {} - return result + return new Promise((resolve, reject) => ssbClient.publish(content, (e, res) => e ? reject(e) : resolve(res))) }, async findCalendarByLinkText(linkSubstring) { @@ -571,7 +631,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) async getCalendarById(id) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const idx = buildIndex(messages) + const idx = memoIndex('calendars', messages, buildIndex) await decryptIndexNodes(idx) let root = id while (idx.parent.has(root)) root = idx.parent.get(root) @@ -589,6 +649,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const ssbClient = await openSsb() const uid = viewerId || ssbClient.id let list = calCollab.visibleThenCollapsed(await collectCalendars(), uid) + try { await ensureMemberKeys(ssbClient, await readAll(ssbClient), list) } catch (_) {} if (filter === "mine") list = list.filter(c => c.author === uid) else if (filter === "recent") { const now = Date.now() @@ -615,6 +676,10 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) }) const ruleDeadline = hasInterval ? (intervalDeadline || cal.deadline || "") : "" + if (!(await calendarMembers(cal)).has(userId)) { + if (cal.tribeId || cal.status !== "OPEN") throw new Error("Only participants can add dates") + await this.joinCalendar(rootId) + } let dateContent = { type: "calendarDate", calendarId: rootId, @@ -640,6 +705,8 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const rootId = await this.resolveRootId(calendarId) const cal = await this.getCalendarById(rootId) const calDeadline = cal && cal.deadline ? cal.deadline : "" + const pubKey = cal ? tryDecryptPublicInviteKey(cal.invites) : null + const members = cal ? await calendarMembers(cal) : new Set() const ssbClient = await openSsb() const messages = await readAll(ssbClient) const authorByKey = new Map() @@ -659,6 +726,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const c = v.content if (!c || c.type !== "calendarDate") continue if (c.calendarId !== rootId) continue + if (!members.has(v.author)) continue let dec = c if (c.encryptedPayload && tribeCrypto) { if (c.tribeId && tribesModel) { @@ -666,25 +734,24 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) dec = r && !r._undecryptable ? r : c if (r && r._undecryptable) continue } else { - const keys = lookupKeys(c.calendarId) - if (keys && keys.length) { - const r = tribeCrypto.decryptContent(c, keys.map(k => [k])) - dec = r && !r._undecryptable ? r : c - if (r && r._undecryptable) continue - } + const keys = [...(lookupKeys(c.calendarId) || []), ...(pubKey ? [pubKey] : [])] + if (!keys.length) continue + const r = tribeCrypto.decryptContent(c, keys.map(k => [k])) + if (!r || r._undecryptable) continue + dec = r } } const baseEntry = { key: m.key, calendarId: dec.calendarId || c.calendarId, label: dec.label || "", - author: dec.author || v.author, + author: v.author || dec.author, createdAt: dec.createdAt || new Date(v.timestamp || 0).toISOString() } const hasInterval = !!(dec.intervalWeekly || dec.intervalMonthly || dec.intervalYearly) const ruleDeadline = dec.intervalDeadline || calDeadline if (hasInterval && ruleDeadline) { - const occurrences = expandRecurrence(dec.date, ruleDeadline, dec.intervalWeekly, dec.intervalMonthly, dec.intervalYearly) + const occurrences = boundedOccurrences(dec.date, ruleDeadline, dec.intervalWeekly, dec.intervalMonthly, dec.intervalYearly) for (const occ of occurrences) { dates.push({ ...baseEntry, date: occ.toISOString() }) } @@ -692,8 +759,10 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) dates.push({ ...baseEntry, date: dec.date }) } } - dates.sort((a, b) => new Date(a.date) - new Date(b.date)) - return dates + const owner = cal ? cal.author : null + const kept = [...dates.filter(d => d.author === owner), ...dates.filter(d => d.author !== owner)].slice(0, MAX_DATES_PER_VIEW) + kept.sort((a, b) => new Date(a.date) - new Date(b.date)) + return kept }, async deleteDate(dateId, calendarId) { @@ -771,9 +840,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const userId = ssbClient.id const item = await new Promise((resolve, reject) => ssbClient.get(noteId, (e, it) => e ? reject(e) : resolve(it))) if (!item || !item.content) throw new Error("Note not found") - const noteRoot = item.content.calendarId || null - const dec = await decryptScoped(item.content, noteRoot) - if (((dec && dec.author) || item.content.author) !== userId) throw new Error("Not the author") + if (item.author !== userId) throw new Error("Not the author") return new Promise((resolve, reject) => { ssbClient.publish({ type: "tombstone", target: noteId, deletedAt: new Date().toISOString(), author: userId }, (e, msg) => e ? reject(e) : resolve(msg)) }) @@ -794,15 +861,23 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) } } const notes = [] + let pubKey for (const m of messages) { const v = m.value || {} const c = v.content if (!c || c.type !== "calendarNote") continue if (tombstoned.has(m.key)) continue - if (c.calendarId !== rootId || c.dateId !== dateId) continue + if (c.calendarId !== rootId || (dateId != null && c.dateId !== dateId)) continue let dec = c if (c.encryptedPayload) { - const r = await decryptScoped(c, rootId) + let r = await decryptScoped(c, rootId) + if ((!r || r._undecryptable) && !c.tribeId && tribeCrypto) { + if (pubKey === undefined) { + const cal = await this.getCalendarById(rootId).catch(() => null) + pubKey = cal ? tryDecryptPublicInviteKey(cal.invites) : null + } + if (pubKey) r = tribeCrypto.decryptContent(c, [[pubKey]]) + } if (r && !r._undecryptable) dec = r else continue } @@ -811,7 +886,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) calendarId: dec.calendarId || c.calendarId, dateId: dec.dateId || c.dateId, text: dec.text || "", - author: dec.author || v.author, + author: v.author || dec.author, createdAt: dec.createdAt || new Date(v.timestamp || 0).toISOString() }) } @@ -844,33 +919,36 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) } } - const calendarDeadlines = new Map() + const calendarInfo = new Map() const dueByCalendar = new Map() - for (const m of messages) { - if (tombstoned.has(m.key)) continue + const dated = messages.filter(m => !tombstoned.has(m.key) && ((m.value || {}).content || {}).type === "calendarDate") + const decoded = await Promise.all(dated.map(async (m) => { + const c = m.value.content + if (!(c.encryptedPayload && tribeCrypto && tribesModel)) return c + const r = await tribeCrypto.decryptFromTribe(c, tribesModel) + return !r || r._undecryptable ? null : r + })) + for (let i = 0; i < dated.length; i++) { + const m = dated[i] const v = m.value || {} const c = v.content - if (!c || c.type !== "calendarDate") continue - let dec = c - if (c.encryptedPayload && tribeCrypto && tribesModel) { - const r = await tribeCrypto.decryptFromTribe(c, tribesModel) - if (!r || r._undecryptable) continue - dec = r - } - if (!dec.date) continue + const dec = decoded[i] + if (!dec || !dec.date) continue const calId = c.calendarId - let calDeadline = calendarDeadlines.get(calId) - if (calDeadline === undefined) { + let info = calendarInfo.get(calId) + if (info === undefined) { try { const cc = await this.getCalendarById(calId) - calDeadline = (cc && cc.deadline) || "" - } catch (_) { calDeadline = "" } - calendarDeadlines.set(calId, calDeadline) + info = cc ? { deadline: cc.deadline || "", members: await calendarMembers(cc) } : null + } catch (_) { info = null } + calendarInfo.set(calId, info) } + if (!info || !info.members.has(v.author)) continue + const calDeadline = info.deadline const hasInterval = !!(dec.intervalWeekly || dec.intervalMonthly || dec.intervalYearly) const ruleDeadline = dec.intervalDeadline || calDeadline const occurrences = (hasInterval && ruleDeadline) - ? expandRecurrence(dec.date, ruleDeadline, dec.intervalWeekly, dec.intervalMonthly, dec.intervalYearly) + ? boundedOccurrences(dec.date, ruleDeadline, dec.intervalWeekly, dec.intervalMonthly, dec.intervalYearly) : [new Date(dec.date)] for (const occ of occurrences) { if (occ.getTime() > now) continue @@ -934,18 +1012,19 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) if (cal.author !== userId) throw new Error("Only the author can generate invites") const code = crypto.randomBytes(INVITE_CODE_BYTES).toString("hex") let invite = code - const pubFlag = opts.public ? { public: true } : {} - if (tribeCrypto && !cal.tribeId) { + const calKey = !cal.tribeId ? lookupKey(cal.rootId) : null + if (tribeCrypto && calKey) { const inviteSalt = tribeCrypto.generateInviteSalt() - const ekChain = tribeCrypto.encryptChainForInvite([cal.rootId], code, inviteSalt) - if (ekChain) invite = { code, ekChain, salt: inviteSalt, gen: lookupGen(cal.rootId), ...pubFlag } + const ek = tribeCrypto.encryptForInvite(calKey, code, inviteSalt) + invite = opts.public + ? { code, ek, salt: inviteSalt, gen: lookupGen(cal.rootId), public: true } + : { ch: tribeCrypto.hashInviteCode(code, inviteSalt), ek, salt: inviteSalt, gen: lookupGen(cal.rootId) } } if (opts.public && typeof invite !== "object") invite = { code, public: true } const tipId = await this.resolveCurrentId(calendarId) const item = await new Promise((resolve, reject) => ssbClient.get(tipId, (e, it) => e ? reject(e) : resolve(it))) - const dec = item.content.tribeId - ? await decryptIfTribe(item.content) - : decryptCalendarRoot(item.content, cal.rootId) + const dec = await decryptScoped(item.content, cal.rootId) + assertReadable(dec, "Calendar") const invites = [...(Array.isArray(dec.invites) ? dec.invites : []), invite] let updated = { type: "calendar", @@ -957,12 +1036,12 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) author: dec.author, participants: Array.isArray(dec.participants) ? dec.participants : [userId], invites, - ...(item.content.tribeId ? { tribeId: item.content.tribeId } : {}), + ...(dec.tribeId ? { tribeId: dec.tribeId } : {}), createdAt: dec.createdAt, updatedAt: new Date().toISOString(), replaces: tipId } - if (item.content.tribeId) updated = await encryptIfTribe(updated) + if (updated.tribeId) updated = await encryptIfTribe(updated) else updated = encryptStandalone(updated, cal.rootId) await new Promise((resolve, reject) => ssbClient.publish(updated, (e, res) => e ? reject(e) : resolve(res))) const tombstone = { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId } @@ -990,9 +1069,8 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) if (cal.author !== userId) throw new Error("Only the author can remove invites") const tipId = await this.resolveCurrentId(calendarId) const item = await new Promise((resolve, reject) => ssbClient.get(tipId, (e, it) => e ? reject(e) : resolve(it))) - const dec = item.content.tribeId - ? await decryptIfTribe(item.content) - : decryptCalendarRoot(item.content, cal.rootId) + const dec = await decryptScoped(item.content, cal.rootId) + assertReadable(dec, "Calendar") const invites = (Array.isArray(dec.invites) ? dec.invites : []).filter(inv => !(typeof inv === "object" && inv.public === true)) let updated = { type: "calendar", @@ -1004,16 +1082,17 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) author: dec.author, participants: Array.isArray(dec.participants) ? dec.participants : [userId], invites, - ...(item.content.tribeId ? { tribeId: item.content.tribeId } : {}), + ...(dec.tribeId ? { tribeId: dec.tribeId } : {}), createdAt: dec.createdAt, updatedAt: new Date().toISOString(), replaces: tipId } - if (item.content.tribeId) updated = await encryptIfTribe(updated) + if (updated.tribeId) updated = await encryptIfTribe(updated) else updated = encryptStandalone(updated, cal.rootId) await new Promise((resolve, reject) => ssbClient.publish(updated, (e, res) => e ? reject(e) : resolve(res))) const tombstone = { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId } await new Promise((resolve, reject) => ssbClient.publish(tombstone, e => e ? reject(e) : resolve())) + if (!dec.tribeId) await rotateCalendarKey(cal.rootId, Array.isArray(cal.participants) ? cal.participants : []) }, async joinByInvite(code) { @@ -1026,12 +1105,22 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) const invs = Array.isArray(cal.invites) ? cal.invites : [] for (const inv of invs) { if (typeof inv === "string" && inv === code) { matched = cal; matchedInvite = inv; break } - if (typeof inv === "object" && inv.code === code) { matched = cal; matchedInvite = inv; break } + if (typeof inv === "object" && inv && (inv.code === code || (inv.ch && tribeCrypto && inv.ch === tribeCrypto.hashInviteCode(code, inv.salt)))) { matched = cal; matchedInvite = inv; break } } if (matched) break } if (!matched) throw new Error("Invalid or expired invite code") if (matched.participants.includes(userId)) throw new Error("Already a participant") + if (typeof matchedInvite === "object" && matchedInvite.ch && tribeCrypto && matchedInvite.ek) { + let probeKey = null + try { probeKey = tribeCrypto.decryptFromInvite(matchedInvite.ek, code, matchedInvite.salt) } catch (_) {} + const messages = await readAll(await openSsb()) + const spent = messages.some(m => { + const c = m.value && m.value.content + return c && c.type === "calendarParticipant" && c.on !== false && c.target === matched.rootId && c.code === matchedInvite.ch && m.value.author !== userId && (!probeKey || keyProofFor(probeKey, m.value.author) === c.keyProof) + }) + if (spent) throw new Error("Invite already used") + } let calKey = null if (tribeCrypto && typeof matchedInvite === "object") { if (matchedInvite.ekChain) { @@ -1053,9 +1142,8 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) } const tipId = await this.resolveCurrentId(matched.rootId) const item = await new Promise((resolve, reject) => ssbClient.get(tipId, (e, it) => e ? reject(e) : resolve(it))) - const dec = item.content.tribeId - ? await decryptIfTribe(item.content) - : decryptCalendarRoot(item.content, matched.rootId) + const dec = await decryptScoped(item.content, matched.rootId) + assertReadable(dec, "Calendar") const isPublicInvite = typeof matchedInvite === "object" && matchedInvite.public === true const invites = isPublicInvite ? (Array.isArray(dec.invites) ? dec.invites : []) @@ -1073,12 +1161,12 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) author: dec.author, participants: [...(Array.isArray(dec.participants) ? dec.participants : []), userId], invites, - ...(item.content.tribeId ? { tribeId: item.content.tribeId } : {}), + ...(dec.tribeId ? { tribeId: dec.tribeId } : {}), createdAt: dec.createdAt, updatedAt: new Date().toISOString(), replaces: tipId } - if (item.content.tribeId) updated = await encryptIfTribe(updated) + if (updated.tribeId) updated = await encryptIfTribe(updated) else updated = encryptStandalone(updated, matched.rootId) await new Promise((resolve, reject) => ssbClient.publish(updated, (e, res) => e ? reject(e) : resolve(res))) const tombstone = { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId } @@ -1098,7 +1186,7 @@ module.exports = ({ cooler, pmModel, tribeCrypto, calendarCrypto, tribesModel }) } } catch (_) {} } - await new Promise((resolve, reject) => ssbClient.publish({ type: "calendarParticipant", target: matched.rootId, on: true, createdAt: new Date().toISOString() }, (e, res) => e ? reject(e) : resolve(res))) + await new Promise((resolve, reject) => ssbClient.publish({ type: "calendarParticipant", target: matched.rootId, on: true, ...(calKey ? { keyProof: keyProofFor(calKey, userId) } : {}), ...(matchedInvite && typeof matchedInvite === "object" && matchedInvite.ch ? { code: matchedInvite.ch } : {}), createdAt: new Date().toISOString() }, (e, res) => e ? reject(e) : resolve(res))) return matched.rootId } } diff --git a/src/models/campaigns_model.js b/src/models/campaigns_model.js new file mode 100644 index 0000000..add3e31 --- /dev/null +++ b/src/models/campaigns_model.js @@ -0,0 +1,312 @@ +const { readTyped, memoIndex } = require('./typed_log'); +const categories = require('../backend/opinion_categories'); +const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { getConfig } = require('../configs/config-manager.js'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const CAMPAIGN_TYPE = 'campaign'; +const SIGNATURE_TYPE = 'campaignSignature'; +const UPDATE_TYPE = 'campaignUpdate'; +const OPINION_TYPE = 'campaignOpinion'; +const PROPOSAL_TYPE = 'parliamentProposal'; +const TYPES = [CAMPAIGN_TYPE, SIGNATURE_TYPE, UPDATE_TYPE, OPINION_TYPE, PROPOSAL_TYPE, 'tombstone']; +const CATEGORIES = ['ENVIRONMENT', 'RIGHTS', 'HEALTH', 'EDUCATION', 'INFRASTRUCTURE', 'CULTURE', 'ECONOMY', 'OTHER']; +const STATUSES = ['OPEN', 'ACHIEVED', 'CLOSED']; +const RECENT_MS = 24 * 60 * 60 * 1000; + +const safeText = (v) => String(v == null ? '' : v).trim(); +const normU = (v) => String(v || '').trim().toUpperCase(); +const normalizeCategory = (v) => (CATEGORIES.includes(normU(v)) ? normU(v) : 'OTHER'); +const normalizeTags = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(','); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(Boolean))); +}; +const toIso = (v) => { const t = Date.parse(v || ''); return Number.isFinite(t) ? new Date(t).toISOString() : ''; }; +const MEDIA_RE = /!?\[(image|video):[^\]]*\]\((&[^)]+)\)/; +const mediaOf = (text) => { const m = safeText(text).match(MEDIA_RE); return m ? { kind: m[1], blobId: m[2] } : null; }; +const goalOf = (v) => { const n = Math.floor(Number(v)); return Number.isFinite(n) && n > 0 ? n : 0; }; + +module.exports = ({ cooler }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + const publish = (ssbClient, content) => new Promise((resolve, reject) => ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res))); + + const buildIndex = (messages) => { + const tomb = buildValidatedTombstoneSet(messages); + const nodes = new Map(); + const parentOf = new Map(); + const signatures = []; + const updates = []; + const opinions = []; + const proposalByCampaign = new Map(); + for (const m of messages) { + const v = m.value || {}; + const c = v.content; + if (!c || typeof c !== 'object') continue; + if (c.type === CAMPAIGN_TYPE) { + nodes.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, c }); + if (typeof c.replaces === 'string') parentOf.set(m.key, c.replaces); + } else if (c.type === SIGNATURE_TYPE && typeof c.target === 'string') { + signatures.push({ key: m.key, target: c.target, author: v.author, ts: v.timestamp || 0, text: safeText(c.text), createdAt: c.createdAt || new Date(v.timestamp || 0).toISOString() }); + } else if (c.type === PROPOSAL_TYPE) { + const legacy = typeof c.campaignId === 'string' && c.campaignId ? null : String(c.description || '').match(/signatures: \/campaigns\/(%[^\s]+\.sha256)\s*$/); + const ref = typeof c.campaignId === 'string' && c.campaignId ? c.campaignId : (legacy ? legacy[1] : ''); + if (ref && !proposalByCampaign.has(ref)) proposalByCampaign.set(ref, m.key); + } else if (c.type === OPINION_TYPE && typeof c.target === 'string') { + opinions.push({ target: c.target, author: v.author, category: String(c.category || ''), ts: v.timestamp || 0 }); + } else if (c.type === UPDATE_TYPE && typeof c.target === 'string') { + updates.push({ key: m.key, target: c.target, author: v.author, ts: v.timestamp || 0, text: safeText(c.text), createdAt: c.createdAt || new Date(v.timestamp || 0).toISOString(), replaces: typeof c.replaces === 'string' ? c.replaces : null }); + } + } + const updateParent = new Map(updates.filter(u => u.replaces).map(u => [u.key, u.replaces])); + const updateRootOf = (key) => { + const seen = new Set(); + let cur = key; + while (updateParent.has(cur) && !seen.has(cur)) { seen.add(cur); cur = updateParent.get(cur); } + return cur; + }; + const updateVersions = new Map(); + for (const u of updates) { + const uroot = updateRootOf(u.key); + if (tomb.has(uroot)) continue; + if (!updateVersions.has(uroot)) updateVersions.set(uroot, []); + updateVersions.get(uroot).push(u); + } + const updateItems = []; + for (const [uroot, versions] of updateVersions) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const own = versions.filter(v => v.author === first.author); + const tip = own[own.length - 1]; + updateItems.push({ id: uroot, key: uroot, tipId: tip.key, target: first.target, author: first.author, ts: first.ts, text: tip.text, createdAt: first.createdAt, updatedAt: tip.createdAt, edited: tip.key !== uroot }); + } + const rootOf = (key) => { + const seen = new Set(); + let cur = key; + while (parentOf.has(cur) && !seen.has(cur)) { seen.add(cur); cur = parentOf.get(cur); } + return cur; + }; + const versionsByRoot = new Map(); + for (const node of nodes.values()) { + const root = rootOf(node.key); + if (tomb.has(root)) continue; + if (!versionsByRoot.has(root)) versionsByRoot.set(root, []); + versionsByRoot.get(root).push(node); + } + const sigByRoot = new Map(); + for (const s of signatures) { + if (!nodes.has(s.target)) continue; + const r = rootOf(s.target); + if (!sigByRoot.has(r)) sigByRoot.set(r, new Map()); + const prev = sigByRoot.get(r).get(s.author); + if (!prev || s.ts < prev.ts) sigByRoot.get(r).set(s.author, s); + } + const opinionsByRoot = new Map(); + for (const op of opinions) { + if (!nodes.has(op.target)) continue; + const r = rootOf(op.target); + if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); + opinionsByRoot.get(r).push(op); + } + const campaigns = new Map(); + const now = Date.now(); + for (const [root, versions] of versionsByRoot) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const own = versions.filter(v => v.author === first.author); + const tip = own[own.length - 1]; + const c = tip.c; + const sigs = Array.from((sigByRoot.get(root) || new Map()).values()).sort((a, b) => a.ts - b.ts); + const goal = goalOf(c.goal); + const deadline = toIso(c.deadline); + const deadlineTs = Date.parse(deadline) || 0; + const expired = !!deadlineTs && deadlineTs < now; + const achieved = goal > 0 && sigs.length >= goal; + const manual = normU(c.status) === 'CLOSED'; + const status = manual ? 'CLOSED' : (achieved ? 'ACHIEVED' : (expired ? 'CLOSED' : 'OPEN')); + const thread = updateItems.filter(u => nodes.has(u.target) && rootOf(u.target) === root && u.author === first.author).sort((a, b) => a.ts - b.ts); + const ops = {}; + const voters = []; + for (const op of (opinionsByRoot.get(root) || []).sort((a, b) => a.ts - b.ts)) { + if (voters.includes(op.author) || !categories.includes(op.category)) continue; + voters.push(op.author); + ops[op.category] = (ops[op.category] || 0) + 1; + } + campaigns.set(root, { + id: root, rootId: root, tipId: tip.key, + title: safeText(c.title), text: safeText(c.text), media: mediaOf(c.text), category: normalizeCategory(c.category), + goal, deadline, deadlineTs, expired, tags: normalizeTags(c.tags), mapUrl: safeText(c.mapUrl), + status, achieved, closed: status === 'CLOSED', proposalId: safeText(c.proposalId) || proposalByCampaign.get(root) || '', + author: first.author, createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt: c.updatedAt || new Date(tip.ts).toISOString(), ts: tip.ts, + signatures: sigs, signatureCount: sigs.length, signers: sigs.map(s => s.author), + progress: goal > 0 ? Math.min(100, Math.round((sigs.length / goal) * 100)) : 0, + updates: thread, + opinions: ops, opinions_inhabitants: voters, opinionCount: voters.length, + lastActivityTs: Math.max(tip.ts, ...sigs.map(s => s.ts), ...thread.map(u => u.ts)) + }); + } + return { campaigns, rootOf, nodes, updatesById: new Map(updateItems.map(u => [u.id, u])), updateRootOf }; + }; + + const load = async () => { + const ssbClient = await openSsb(); + const idx = memoIndex('campaigns', await readTyped(ssbClient, TYPES, { limit: logLimit }), buildIndex); + return { ssbClient, idx }; + }; + + const find = (idx, id) => { + const key = String(id || ''); + if (idx.campaigns.has(key)) return idx.campaigns.get(key); + if (idx.nodes.has(key)) return idx.campaigns.get(idx.rootOf(key)) || null; + return null; + }; + + const decorate = (cp, me) => ({ + ...cp, + isOwner: cp.author === me, + signed: cp.signers.includes(me), + canSign: !cp.closed && !cp.signers.includes(me), + canElevate: cp.author === me && cp.achieved && !cp.proposalId + }); + + const versionOf = (cp, me, data = {}) => ({ + type: CAMPAIGN_TYPE, + title: safeText(data.title) || cp.title, + text: data.text !== undefined ? safeText(data.text) : cp.text, + category: data.category !== undefined ? normalizeCategory(data.category) : cp.category, + goal: data.goal !== undefined && goalOf(data.goal) ? goalOf(data.goal) : cp.goal, + deadline: data.deadline !== undefined ? toIso(data.deadline) : cp.deadline, + tags: data.tags !== undefined ? normalizeTags(data.tags) : cp.tags, + mapUrl: data.mapUrl !== undefined ? safeText(data.mapUrl) : cp.mapUrl, + status: data.status !== undefined ? (normU(data.status) === 'CLOSED' ? 'CLOSED' : 'OPEN') : (cp.status === 'CLOSED' && !cp.expired ? 'CLOSED' : 'OPEN'), + proposalId: data.proposalId !== undefined ? safeText(data.proposalId) : cp.proposalId, + author: me, + createdAt: cp.createdAt, + updatedAt: new Date().toISOString(), + replaces: cp.tipId + }); + + return { + CATEGORIES, + STATUSES, + + async createCampaign({ title, text, category, goal, deadline, tags, mapUrl }) { + const ssbClient = await openSsb(); + const t = safeText(title); + if (!t) throw new Error('Title is required'); + const g = goalOf(goal); + if (!g) throw new Error('A signature goal is required'); + const now = new Date().toISOString(); + return publish(ssbClient, { + type: CAMPAIGN_TYPE, title: t, text: safeText(text), category: normalizeCategory(category), goal: g, + deadline: toIso(deadline), tags: normalizeTags(tags), mapUrl: safeText(mapUrl), status: 'OPEN', + author: ssbClient.id, createdAt: now, updatedAt: now + }); + }, + + async updateCampaign(id, data = {}) { + const { ssbClient, idx } = await load(); + const cp = find(idx, id); + if (!cp) throw new Error('Campaign not found'); + if (cp.author !== ssbClient.id) throw new Error('Only the promoter can update this campaign'); + const res = await publish(ssbClient, versionOf(cp, ssbClient.id, data)); + return { key: res.key, rootId: cp.id }; + }, + + async closeCampaign(id) { return this.updateCampaign(id, { status: 'CLOSED' }); }, + async reopenCampaign(id) { return this.updateCampaign(id, { status: 'OPEN' }); }, + + async deleteCampaign(id) { + const { ssbClient, idx } = await load(); + const cp = find(idx, id); + if (!cp) throw new Error('Campaign not found'); + if (cp.author !== ssbClient.id) throw new Error('Only the promoter can delete this campaign'); + await publish(ssbClient, { type: 'tombstone', target: cp.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + return { key: cp.id }; + }, + + async sign(id, text = '') { + const { ssbClient, idx } = await load(); + const cp = find(idx, id); + if (!cp) throw new Error('Campaign not found'); + if (cp.closed) throw new Error('This campaign is closed'); + if (cp.signers.includes(ssbClient.id)) throw new Error('You already signed this campaign'); + return publish(ssbClient, { type: SIGNATURE_TYPE, target: cp.id, text: safeText(text).slice(0, 500), createdAt: new Date().toISOString() }); + }, + + async addUpdate(id, text) { + const { ssbClient, idx } = await load(); + const cp = find(idx, id); + if (!cp) throw new Error('Campaign not found'); + if (cp.author !== ssbClient.id) throw new Error('Only the promoter can post updates'); + const body = safeText(text); + if (!body) throw new Error('Text is required'); + return publish(ssbClient, { type: UPDATE_TYPE, target: cp.id, text: body, createdAt: new Date().toISOString() }); + }, + + async editUpdate(updateId, text) { + const { ssbClient, idx } = await load(); + const update = idx.updatesById.get(idx.updateRootOf(String(updateId || ''))); + if (!update) throw new Error('Update not found'); + if (String(update.author) !== String(ssbClient.id)) throw new Error('Only the promoter can edit an update'); + const body = safeText(text); + if (!body) throw new Error('Text is required'); + const res = await publish(ssbClient, { type: UPDATE_TYPE, target: update.target, text: body, createdAt: new Date().toISOString(), replaces: update.tipId }); + return { key: res.key, rootId: update.id, campaignId: update.target }; + }, + + async deleteUpdate(updateId) { + const { ssbClient, idx } = await load(); + const update = idx.updatesById.get(idx.updateRootOf(String(updateId || ''))); + if (!update) throw new Error('Update not found'); + if (String(update.author) !== String(ssbClient.id)) throw new Error('Only the promoter can delete an update'); + await publish(ssbClient, { type: 'tombstone', target: update.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + return { key: update.id, campaignId: update.target }; + }, + + async createOpinion(id, category) { + if (!categories.includes(category)) throw new Error('Invalid voting category'); + const { ssbClient, idx } = await load(); + const cp = find(idx, id); + if (!cp) throw new Error('Campaign not found'); + if (cp.opinions_inhabitants.includes(ssbClient.id)) throw new Error('Already voted'); + return publish(ssbClient, { type: OPINION_TYPE, target: cp.id, category, createdAt: new Date().toISOString() }); + }, + + async recordProposal(id, proposalId) { + return this.updateCampaign(id, { proposalId: safeText(proposalId) }); + }, + + async getCampaignById(id, viewerId = null) { + const { ssbClient, idx } = await load(); + const cp = find(idx, id); + return cp ? decorate(cp, viewerId || ssbClient.id) : null; + }, + + async listAll({ filter = 'all', q = '', viewerId = null } = {}) { + const { ssbClient, idx } = await load(); + const me = viewerId || ssbClient.id; + const f = String(filter || 'all').toLowerCase(); + const needle = String(q || '').trim().toLowerCase(); + let out = Array.from(idx.campaigns.values()).map(cp => decorate(cp, me)); + if (f === 'mine') out = out.filter(cp => cp.isOwner); + else if (f === 'signed') out = out.filter(cp => cp.signed); + else if (f === 'recent') out = out.filter(cp => cp.lastActivityTs >= Date.now() - RECENT_MS); + else if (f === 'top') out = out.filter(cp => cp.signatureCount > 0); + else if (STATUSES.includes(f.toUpperCase())) out = out.filter(cp => cp.status === f.toUpperCase()); + else if (CATEGORIES.includes(f.toUpperCase())) out = out.filter(cp => cp.category === f.toUpperCase()); + if (needle) out = out.filter(cp => [cp.title, cp.text, cp.category, ...cp.tags].some(v => String(v || '').toLowerCase().includes(needle))); + if (f === 'top') out.sort((a, b) => b.signatureCount - a.signatureCount || b.lastActivityTs - a.lastActivityTs); + else out.sort((a, b) => b.lastActivityTs - a.lastActivityTs); + return out; + }, + + async resolveRootId(id) { + const { idx } = await load(); + const cp = find(idx, id); + return cp ? cp.id : String(id || ''); + } + }; +}; diff --git a/src/models/chats_model.js b/src/models/chats_model.js index 545e1fe..3d06479 100644 --- a/src/models/chats_model.js +++ b/src/models/chats_model.js @@ -2,6 +2,7 @@ const pull = require("../server/node_modules/pull-stream") const crypto = require("crypto") const { getConfig } = require("../configs/config-manager.js") const { buildValidatedTombstoneSet } = require('./tombstone_validator') +const { readTyped, memoIndex } = require("./typed_log") const { collabContent, openInviteOf } = require('../backend/collab_content') const chatCollab = collabContent({ membersField: 'members', undecField: 'undecryptable', contentFields: ['title', 'description', 'image', 'category', 'status'], listFields: ['tags', 'invites'] }) const logLimit = getConfig().ssbLogStream?.limit || 1000 @@ -16,6 +17,7 @@ const normalizeTags = (raw) => { const INVITE_CODE_BYTES = 16 const VALID_STATUS = ["OPEN", "INVITE-ONLY", "CLOSED"] +const REACTION_EMOJIS = ["up", "heart", "laugh", "down"] const DEFAULT_MESSAGES_PER_HOUR = 60 @@ -35,6 +37,17 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const ownCrypto = chatCrypto || tribeCrypto const lookupKey = (rid) => (ownCrypto && ownCrypto.getKey(rid)) || (tribeCrypto && tribeCrypto.getKey(rid)) || null + const hashCode = (code, salt) => tribeCrypto ? tribeCrypto.hashInviteCode(code, salt) : null + const inviteMatches = (inv, token) => { + if (!token) return false + if (typeof inv === "string") return inv === token + if (!inv || typeof inv !== "object") return false + if (typeof inv.code === "string" && inv.code === token) return true + if (typeof inv.ch === "string" && (inv.ch === token || hashCode(token, inv.salt) === inv.ch)) return true + return false + } + const inviteToken = (inv) => (inv && typeof inv === "object" && typeof inv.ch === "string") ? inv.ch : (typeof inv === "string" ? inv : (inv && inv.code) || "") + const keyProofFor = (keyHex, id) => { try { return crypto.createHmac("sha256", Buffer.from(keyHex, "hex")).update(String(id), "utf8").digest("hex") } catch (_) { return null } } const lookupKeys = (rid) => { const a = (ownCrypto && ownCrypto.getKeys(rid)) || [] if (a.length) return a @@ -48,11 +61,10 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (!existing) return const newKey = ownCrypto.generateTribeKey() const newGen = ownCrypto.addNewKey(rootId, newKey) - if (!Array.isArray(remainingMembers) || !remainingMembers.length) return const ssbClient = await openSsb() const ssbKeys = require("../server/node_modules/ssb-keys") const memberKeys = {} - for (const m of remainingMembers) { + for (const m of new Set([ssbClient.id, ...(Array.isArray(remainingMembers) ? remainingMembers : [])])) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys) } catch (_) {} } if (Object.keys(memberKeys).length) { @@ -69,9 +81,15 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const ssbKeys = require("../server/node_modules/ssb-keys") const config = require("../server/ssb_config") const msgs = await readAll(ssbClient) + const idx = buildIndex(msgs) + const trusted = (root, author) => { + const n = idx.nodes.get(root) + return !!author && !!n && (author === n.author || author === ssbClient.id) + } for (const m of msgs) { const c = m.value && m.value.content if (!c || c.type !== "tribe-keys") continue + if (!trusted(c.tribeId, m.value.author)) continue const memberKeys = c.memberKeys if (!memberKeys || typeof memberKeys !== "object") continue const boxed = memberKeys[ssbClient.id] @@ -98,10 +116,9 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { return ks.length ? ks[0] : null } - const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))) - ) + const CHAT_TYPES = ["chat", "chatMessage", "chatMember", "chatReaction", "chatPin", "tribe-keys", "tombstone"] + + const readAll = async (ssbClient) => readTyped(ssbClient, CHAT_TYPES, { limit: logLimit }) const buildIndex = (messages) => { const tomb = new Set() @@ -112,6 +129,8 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const authorByKey = new Map() const tombRequests = [] const memberMsgs = [] + const reactMsgs = [] + const pinMsgs = [] for (const m of messages) { const k = m.key @@ -120,11 +139,19 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (!c) continue if (c.type === "tombstone" && c.target) { tombRequests.push({ target: c.target, author: v.author }); continue } if (c.type === "chatMember" && c.target && c.member) { - memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, code: typeof c.code === "string" ? c.code : "", author: v.author, ts: v.timestamp || m.timestamp || 0 }) + memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, code: typeof c.code === "string" ? c.code : "", keyProof: typeof c.keyProof === "string" ? c.keyProof : "", author: v.author, ts: v.timestamp || m.timestamp || 0 }) + continue + } + if (c.type === "chatReaction" && c.target && REACTION_EMOJIS.includes(c.emoji)) { + reactMsgs.push({ target: c.target, emoji: c.emoji, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0 }) + continue + } + if (c.type === "chatPin" && c.target) { + pinMsgs.push({ target: c.target, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0 }) continue } if (c.type === "chat") { - nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) authorByKey.set(k, v.author) if (c.replaces) { parent.set(k, c.replaces); child.set(c.replaces, k) } } else if (c.type === "chatMessage") { @@ -133,24 +160,28 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { } } - const strictParent = new Map() - const strictChild = new Map() - for (const [c1, p1] of parent.entries()) { - const childNode = nodes.get(c1) - const parentNode = nodes.get(p1) - if (childNode && parentNode && childNode.author === parentNode.author) { - strictParent.set(c1, p1) - strictChild.set(p1, c1) - } - } - for (const t of tombRequests) { const targetAuthor = authorByKey.get(t.target) if (targetAuthor && t.author === targetAuthor) tomb.add(t.target) } - const rootOf = (id) => { let cur = id; while (strictParent.has(cur)) cur = strictParent.get(cur); return cur } - const tipOf = (id) => { let cur = id; while (strictChild.has(cur)) cur = strictChild.get(cur); return cur } + const rootMemo = new Map() + const rootOf = (id) => { + if (rootMemo.has(id)) return rootMemo.get(id) + let cur = id, g = 0 + while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur) + rootMemo.set(id, cur) + return cur + } + const ownerTip = new Map() + for (const [k, n] of nodes) { + const r = rootOf(k) + const rn = nodes.get(r) + if (!rn || n.author !== rn.author) continue + const prev = ownerTip.has(r) ? nodes.get(ownerTip.get(r)) : null + if (!prev || n.seq > prev.seq || (n.seq === prev.seq && n.ts >= prev.ts)) ownerTip.set(r, k) + } + const tipOf = (id) => { const r = rootOf(id); return ownerTip.get(r) || r } const roots = new Set() for (const id of nodes.keys()) roots.add(rootOf(id)) @@ -158,25 +189,72 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { for (const r of roots) tipByRoot.set(r, tipOf(r)) const memberByRoot = new Map() + const invitesOf = (root) => { + const out = [] + for (const id of [root, tipOf(root)]) { const n = nodes.get(id); if (n && Array.isArray(n.c.invites)) out.push(...n.c.invites) } + return out + } + const sealedRoot = (root) => { const oc = (nodes.get(root) || {}).c || {}; return !!(oc.encryptedPayload || oc.encrypted === true) } + const selfJoinAllowed = (root, mm) => { + if (!sealedRoot(root)) return true + const invites = invitesOf(root) + if (invites.some(inv => inv && typeof inv === "object" && inv.public === true)) return true + if (mm.keyProof && lookupKeys(root).some(k => keyProofFor(k, mm.member) === mm.keyProof)) return true + const tribeChat = !!((nodes.get(root) || {}).c || {}).tribeId + return !!mm.code && invites.some(inv => tribeChat ? inviteMatches(inv, mm.code) : (typeof inv === "string" ? inv === mm.code : !!(inv && typeof inv === "object" && typeof inv.code === "string" && inv.code === mm.code))) + } const consumedByRoot = new Map() + const codeClaimsByRoot = new Map() for (const mm of memberMsgs) { if (!nodes.has(mm.target)) continue const r = rootOf(mm.target) - if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) - const perMember = memberByRoot.get(r) - const prev = perMember.get(mm.member) - if (!prev || mm.ts >= prev.ts) perMember.set(mm.member, { on: mm.on, ts: mm.ts }) + const ownerAuthor = nodes.get(r) && nodes.get(r).author + const self = mm.member === mm.author + if (!self && mm.author !== ownerAuthor) continue + if (mm.on && mm.code) { + if (!codeClaimsByRoot.has(r)) codeClaimsByRoot.set(r, []) + codeClaimsByRoot.get(r).push({ code: mm.code, member: mm.member, keyProof: mm.keyProof, byOwner: mm.author === ownerAuthor }) + } + if (self && mm.author !== ownerAuthor && mm.on && !selfJoinAllowed(r, mm)) continue if (mm.on && mm.code) { if (!consumedByRoot.has(r)) consumedByRoot.set(r, new Set()) consumedByRoot.get(r).add(mm.code) } + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) + const perMember = memberByRoot.get(r) + const prev = perMember.get(mm.member) + if (!prev || mm.ts >= prev.ts) perMember.set(mm.member, { on: mm.on, ts: mm.ts }) } const isCodeConsumed = (rootId, code) => !!(code && consumedByRoot.has(rootId) && consumedByRoot.get(rootId).has(code)) + const codeClaims = (rootId, codes) => (codeClaimsByRoot.get(rootId) || []).filter(c => codes.includes(c.code)) const rawRootOf = (id) => { let cur = id, g = 0; while (parent.has(cur) && g++ < 100000) cur = parent.get(cur); return cur } - return { tomb, nodes, parent: strictParent, child: strictChild, rawParent: parent, rawChild: child, rawRootOf, rootOf, tipOf, tipByRoot, msgNodes, memberByRoot, consumedByRoot, isCodeConsumed } + for (const [f, fNode] of nodes) { + const r = rootOf(f) + if (!nodes.has(r) || fNode.author === nodes.get(r).author) continue + const opened = decryptChatContent(fNode.c, r) + if (opened.undecryptable || !Array.isArray(opened.c && opened.c.members)) continue + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) + const perMember = memberByRoot.get(r) + const prev = perMember.get(fNode.author) + if (!prev || fNode.ts >= prev.ts) perMember.set(fNode.author, { on: opened.c.members.includes(fNode.author), ts: fNode.ts }) + } + + const reactionByKey = new Map() + for (const r of reactMsgs) { + const rk = `${r.target}|${r.author}|${r.emoji}` + const prev = reactionByKey.get(rk) + if (!prev || r.ts >= prev.ts) reactionByKey.set(rk, r) + } + const pinByTarget = new Map() + for (const pm of pinMsgs) { + const prev = pinByTarget.get(pm.target) + if (!prev || pm.ts >= prev.ts) pinByTarget.set(pm.target, pm) + } + + return { tomb, nodes, rawRootOf, rootOf, tipOf, tipByRoot, msgNodes, memberByRoot, consumedByRoot, isCodeConsumed, codeClaims, reactionByKey, pinByTarget } } const resolveKeyChainSets = (chatRootId) => { @@ -248,13 +326,13 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const { c, undecryptable } = decryptChatContent(rawC, rootId) let ownerContent = c - let ownerAuthor = c.author || node.author + let ownerAuthor = node.author if (idx) { const ownerNode = idx.nodes.get(rootId) if (ownerNode) { const decOwner = decryptChatContent(ownerNode.c || {}, rootId) ownerContent = decOwner.c - ownerAuthor = decOwner.c.author || ownerNode.author + ownerAuthor = ownerNode.author } } const memberToggles = idx && idx.memberByRoot ? idx.memberByRoot.get(rootId) : null @@ -275,7 +353,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { tags: safeArr(c.tags), members, invites, - author: c.author || node.author, + author: node.author, createdAt: c.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || null, encrypted: !!c.encrypted, @@ -304,8 +382,9 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { chatId: c.chatId || "", text, image: c.image || null, - replyTo: c.replyTo || null, - author: c.author || node.author, + mimeType: typeof c.mimeType === "string" ? c.mimeType : null, + replyTo: typeof c.replyTo === "string" ? c.replyTo : null, + author: node.author, createdAt: c.createdAt || new Date(node.ts).toISOString() } } @@ -317,28 +396,27 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { }) const ensureMemberKeys = async (ssbClient, messages, chats) => { - if (!tribeCrypto) return - const distributed = new Map() + if (!tribeCrypto || !ownCrypto) return + const leavesByRoot = new Map() for (const m of messages) { const c = m.value && m.value.content - if (!c || c.type !== "tribe-keys" || !c.tribeId) continue - const mk = c.memberKeys - if (!mk || typeof mk !== "object") continue - if (!distributed.has(c.tribeId)) distributed.set(c.tribeId, new Set()) - for (const id of Object.keys(mk)) distributed.get(c.tribeId).add(id) + if (!c || c.type !== "chatMember" || c.on !== false || !c.target || !c.member) continue + if (!leavesByRoot.has(c.target)) leavesByRoot.set(c.target, []) + leavesByRoot.get(c.target).push({ member: c.member, author: m.value.author, ts: Number(m.timestamp || m.value.timestamp || 0) }) } const ssbKeys = require("../server/node_modules/ssb-keys") for (const chat of safeArr(chats)) { - if (!chat || chat.undecryptable) continue + if (!chat || chat.undecryptable || chat.author !== ssbClient.id) continue const rootId = chat.rootId - if (!rootId) continue + if (!rootId || !lookupKey(rootId)) continue + const members = safeArr(chat.members) + const leaves = (leavesByRoot.get(rootId) || []).filter(l => l.author === l.member || l.author === chat.author) + const plan = ownCrypto.keyPlan({ rootId, ownerId: chat.author, gen: lookupGen(rootId), members, messages, leaves }) + if (plan.rotate) { await rotateChatKey(rootId, members); continue } + if (!plan.missing.length) continue const key = lookupKey(rootId) - if (!key) continue - const have = distributed.get(rootId) || new Set() - const missing = safeArr(chat.members).filter(m => m && m !== ssbClient.id && !have.has(m)) - if (!missing.length) continue const memberKeys = {} - for (const m of missing) { + for (const m of plan.missing) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(key, m, ssbKeys) } catch (_) {} } if (!Object.keys(memberKeys).length) continue @@ -352,9 +430,22 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { new Promise((resolve, reject) => { const content = { type: "chatMember", target: rootId, member, on: !!on, createdAt: new Date().toISOString() } if (typeof code === "string" && code) content.code = code + const proofKey = on && member === ssbClient.id ? lookupKey(rootId) : null + if (proofKey) content.keyProof = keyProofFor(proofKey, member) ssbClient.publish(content, (e, res) => e ? reject(e) : resolve(res)) }) + const decryptTribeChats = async (idx) => { + if (!tribeCrypto || !tribesModel || typeof tribeCrypto.decryptFromTribe !== "function") return idx + for (const [k, n] of idx.nodes) { + const c = n.c || {} + if (!c.encryptedPayload || !c.tribeId) continue + const dec = await tribeCrypto.decryptFromTribe(c, tribesModel).catch(() => null) + if (dec && !dec._undecryptable) idx.nodes.set(k, { ...n, c: dec }) + } + return idx + } + const collectChats = (idx) => { const out = [] for (const [rootId, tipId] of idx.tipByRoot.entries()) { @@ -383,11 +474,8 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildIndex(messages) - let tip = id - while (idx.child.has(tip)) tip = idx.child.get(tip) - if (idx.tomb.has(tip)) throw new Error("Not found") - let root = tip - while (idx.parent.has(root)) root = idx.parent.get(root) + const root = idx.rootOf(id) + if (idx.tomb.has(idx.tipOf(root))) throw new Error("Not found") return root }, @@ -395,13 +483,12 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildIndex(messages) - let tip = id - while (idx.child.has(tip)) tip = idx.child.get(tip) + const tip = idx.tipOf(idx.rootOf(id)) if (idx.tomb.has(tip)) throw new Error("Not found") return tip }, - async createChat(title, description, image, category, status, tagsRaw, tribeId) { + async createChat(title, description, image, category, status, tagsRaw, tribeId, extra = {}) { const ssbClient = await openSsb() const userId = ssbClient.id const blobId = image ? String(image).trim() || null : null @@ -422,7 +509,8 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { author: userId, createdAt: now, updatedAt: now, - ...(tribeId ? { tribeId } : {}) + ...(tribeId ? { tribeId } : {}), + ...(extra && typeof extra === "object" && typeof extra.courseId === "string" && extra.courseId ? { courseId: extra.courseId } : {}) } if (!tribeCrypto) { @@ -478,13 +566,11 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { ssbClient.get(tipId, (err, item) => err || !item?.content ? reject(new Error("Chat not found")) : resolve(item)) }) const c = item.content - const rawAuthor = c.author || (c.encryptedPayload ? null : undefined) - if (rawAuthor && rawAuthor !== userId) throw new Error("Not the author") + if (item.author !== userId) throw new Error("Not the author") const messages = await readAll(ssbClient) const idx = buildIndex(messages) - let rootId = tipId - while (idx.parent.has(rootId)) rootId = idx.parent.get(rootId) + const rootId = idx.rootOf(tipId) const node = { key: tipId, c, author: item.author, ts: item.timestamp || 0 } const chat = buildChat(node, rootId, idx) if (!chat) throw new Error("Invalid chat") @@ -544,7 +630,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { return new Promise((resolve, reject) => { ssbClient.get(tipId, (err, item) => { if (err || !item?.content) return reject(new Error("Chat not found")) - if (item.content.author && item.content.author !== userId) return reject(new Error("Not the author")) + if (item.author !== userId) return reject(new Error("Not the author")) ssbClient.publish({ type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId }, (e) => e ? reject(e) : resolve()) }) }) @@ -555,11 +641,9 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const userId = ssbClient.id const messages = await readAll(ssbClient) const idx = buildIndex(messages) - let tip = id - while (idx.child.has(tip)) tip = idx.child.get(tip) + const root = idx.rootOf(id) + const tip = idx.tipOf(root) if (idx.tomb.has(tip)) throw new Error("Not found") - let root = tip - while (idx.parent.has(root)) root = idx.parent.get(root) const node = idx.nodes.get(tip) if (!node) throw new Error("Not found") @@ -592,18 +676,15 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { async getChatById(id) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const idx = buildIndex(messages) + const idx = await decryptTribeChats(memoIndex('chats', messages, buildIndex)) - let tip = id - while (idx.child.has(tip)) tip = idx.child.get(tip) + const root = idx.rootOf(id) + const tip = idx.tipOf(root) if (idx.tomb.has(tip)) return null const node = idx.nodes.get(tip) if (!node || node.c.type !== "chat") return null - let root = tip - while (idx.parent.has(root)) root = idx.parent.get(root) - const chat = buildChat(node, root, idx) if (!chat) return null return chatCollab.fold(chat, collectChats(idx)) @@ -613,7 +694,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const ssbClient = await openSsb() const uid = viewerId || ssbClient.id const messages = await readAll(ssbClient) - const idx = buildIndex(messages) + const idx = await decryptTribeChats(buildIndex(messages)) const now = Date.now() let list = chatCollab.visibleThenCollapsed(collectChats(idx), uid) @@ -631,7 +712,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { list = list.map(c => ({ ...c, lastMsgAt: lastMsgAt.get(c.rootId) || 0, lastMineAt: lastMineAt.get(c.rootId) || 0 })) if (filter === "mine") list = list.filter(c => c.author === uid) - else if (filter === "recent") list = list.filter(c => new Date(c.createdAt).getTime() >= now - 86400000) + else if (filter === "recent") list = list.filter(c => Math.max(new Date(c.createdAt).getTime() || 0, Number(c.lastMsgAt || 0)) >= now - 86400000) else if (filter === "open") list = list.filter(c => c.status === "OPEN" || c.status === "INVITE-ONLY") else if (filter === "closed") list = list.filter(c => c.status === "CLOSED") @@ -665,13 +746,16 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (tribeCrypto) { const inviteSalt = tribeCrypto.generateInviteSalt() const ekChain = tribeCrypto.encryptChainForInvite([chat.rootId], code, inviteSalt) + const ident = opts.public ? { code, ...pubFlag } : { ch: hashCode(code, inviteSalt) } if (ekChain) { - invite = { code, ekChain, salt: inviteSalt, gen: lookupGen(chat.rootId), ...pubFlag } + invite = { ...ident, ekChain, salt: inviteSalt, gen: lookupGen(chat.rootId) } } else { const chatKey = lookupKey(chat.rootId) if (chatKey) { const ek = tribeCrypto.encryptForInvite(chatKey, code, inviteSalt) - invite = { code, ek, salt: inviteSalt, gen: lookupGen(chat.rootId), ...pubFlag } + invite = { ...ident, ek, salt: inviteSalt, gen: lookupGen(chat.rootId) } + } else { + invite = { ...ident, salt: inviteSalt } } } } @@ -702,6 +786,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (chat.author !== userId) throw new Error("Only the author can remove invites") const invites = (Array.isArray(chat.invites) ? chat.invites : []).filter(inv => !(typeof inv === "object" && inv.public === true)) await this.updateChatById(chatId, { invites, members: chat.members, status: chat.status, title: chat.title, description: chat.description, image: chat.image, category: chat.category, tags: chat.tags }) + if (!chat.tribeId) await rotateChatKey(chat.rootId, chat.members) }, async joinByInvite(code) { @@ -721,12 +806,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (!chat || !chat.invites.length) continue for (const inv of chat.invites) { - if (typeof inv === "string" && inv === code) { - matchedChat = chat; matchedInvite = inv; break - } - if (typeof inv === "object" && inv.code === code) { - matchedChat = chat; matchedInvite = inv; break - } + if (inviteMatches(inv, code)) { matchedChat = chat; matchedInvite = inv; break } } if (matchedChat) break } @@ -735,29 +815,40 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (matchedChat.members.includes(userId)) throw new Error("Already a participant") const isPublic = typeof matchedInvite === "object" && matchedInvite.public === true - if (!isPublic && idx.isCodeConsumed(matchedChat.rootId, code)) throw new Error("Invite already used") + const token = inviteToken(matchedInvite) || code let chatKey = null + let chain = null if (tribeCrypto && typeof matchedInvite === "object") { if (matchedInvite.ekChain) { - const chain = tribeCrypto.decryptChainFromInvite(matchedInvite.ekChain, code, matchedInvite.salt, 3) - if (Array.isArray(chain) && chain.length) { - for (const entry of chain) { - if (Array.isArray(entry.keys) && entry.keys.length) { - tribeCrypto.setKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length) - } else if (entry.key) { - tribeCrypto.setKey(entry.rootId, entry.key, entry.gen || 1) - } - } - chatKey = chain[0].key - } + chain = tribeCrypto.decryptChainFromInvite(matchedInvite.ekChain, code, matchedInvite.salt, 3) + if (Array.isArray(chain) && chain.length) chatKey = chain[0].key + else chain = null } else if (matchedInvite.ek) { chatKey = tribeCrypto.decryptFromInvite(matchedInvite.ek, code, matchedInvite.salt) - ownCrypto.setKey(matchedChat.rootId, chatKey, matchedInvite.gen || 1) } } - await publishMemberToggle(ssbClient, matchedChat.rootId, userId, true, isPublic ? undefined : code) + if (!isPublic) { + if (idx.isCodeConsumed(matchedChat.rootId, token) || idx.isCodeConsumed(matchedChat.rootId, code)) throw new Error("Invite already used") + const claims = idx.codeClaims(matchedChat.rootId, [token, code]).filter(c => c.member !== userId) + const spent = chatKey ? claims.some(c => c.byOwner || keyProofFor(chatKey, c.member) === c.keyProof) : claims.length > 0 + if (spent) throw new Error("Invite already used") + } + + if (chain) { + for (const entry of chain) { + if (Array.isArray(entry.keys) && entry.keys.length) { + tribeCrypto.setKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length) + } else if (entry.key) { + tribeCrypto.setKey(entry.rootId, entry.key, entry.gen || 1) + } + } + } else if (chatKey) { + ownCrypto.setKey(matchedChat.rootId, chatKey, matchedInvite.gen || 1) + } + + await publishMemberToggle(ssbClient, matchedChat.rootId, userId, true, isPublic ? undefined : token) if (tribeCrypto && chatKey) { try { @@ -801,9 +892,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { const chat = await this.getChatById(chatId) if (!chat) throw new Error("Chat not found") if (chat.author === userId) throw new Error("Author cannot leave their own chat") - const members = chat.members.filter(m => m !== userId) await publishMemberToggle(ssbClient, chat.rootId, userId, false) - try { await rotateChatKey(chat.rootId, members) } catch (_) {} }, async ingestKeys() { await ingestOwnTribeKeys() }, @@ -825,7 +914,7 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { } catch (_) { return 0 } }, - async sendMessage(chatId, text, image = null, replyTo = null) { + async sendMessage(chatId, text, image = null, replyTo = null, mimeType = null) { const ssbClient = await openSsb() const userId = ssbClient.id const chat = await this.getChatById(chatId) @@ -857,7 +946,14 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { createdAt: now } if (image) content.image = image - if (replyTo && typeof replyTo === "string" && replyTo.startsWith("%")) content.replyTo = replyTo + if (image && mimeType) content.mimeType = String(mimeType) + if (replyTo) { + const idxAll = buildIndex(messages) + const target = idxAll.msgNodes.get(replyTo) + if (target && (target.c.chatId === chat.rootId || idxAll.rawRootOf(target.c.chatId) === idxAll.rawRootOf(chat.rootId))) { + content.replyTo = replyTo + } + } const chatIsEncrypted = !!(chat.tribeId) || !!lookupKey(chat.rootId) if (chatIsEncrypted && tribeCrypto) { @@ -896,10 +992,74 @@ module.exports = ({ cooler, tribeCrypto, chatCrypto, tribesModel }) => { if (msg) result.push(msg) } + const viewerId = ssbClient.id + let chatAuthor = null + if (chatNode) chatAuthor = chatNode.author + const reactsByTarget = new Map() + for (const r of idx.reactionByKey.values()) { + if (!r.on) continue + if (!reactsByTarget.has(r.target)) reactsByTarget.set(r.target, []) + reactsByTarget.get(r.target).push(r) + } + const byKey = new Map(result.map(m => [m.key, m])) + for (const m of result) { + const counts = {} + const mine = {} + for (const e of REACTION_EMOJIS) { counts[e] = 0; mine[e] = false } + for (const r of reactsByTarget.get(m.key) || []) { + counts[r.emoji] += 1 + if (r.author === viewerId) mine[r.emoji] = true + } + m.reactions = { counts, mine } + const pin = idx.pinByTarget.get(m.key) + m.pinned = !!(pin && pin.on && chatAuthor && pin.author === chatAuthor) + const quoted = m.replyTo ? byKey.get(m.replyTo) : null + m.reply = quoted ? { author: quoted.author, text: String(quoted.text || "").slice(0, 120) } : null + } + result.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) return result }, + async toggleReaction(chatId, targetKey, emoji) { + if (!REACTION_EMOJIS.includes(emoji)) throw new Error("Invalid reaction") + const ssbClient = await openSsb() + const userId = ssbClient.id + const chat = await this.getChatById(chatId) + if (!chat) throw new Error("Chat not found") + if (chat.status === "CLOSED") throw new Error("Chat is closed") + if (!chat.members.includes(userId)) { + if (chat.status === "OPEN") await this.joinChat(chatId) + else throw new Error("Not a participant") + } + const messages = await readAll(ssbClient) + const idx = buildIndex(messages) + const target = idx.msgNodes.get(targetKey) + if (!target || (target.c.chatId !== chat.rootId && idx.rawRootOf(target.c.chatId) !== idx.rawRootOf(chat.rootId))) throw new Error("Message not found") + const prev = idx.reactionByKey.get(`${targetKey}|${userId}|${emoji}`) + const on = !(prev && prev.on) + return new Promise((resolve, reject) => { + ssbClient.publish({ type: "chatReaction", chatId: chat.rootId, target: targetKey, emoji, on, author: userId, createdAt: new Date().toISOString() }, (e, res) => e ? reject(e) : resolve(res)) + }) + }, + + async togglePin(chatId, targetKey) { + const ssbClient = await openSsb() + const userId = ssbClient.id + const chat = await this.getChatById(chatId) + if (!chat) throw new Error("Chat not found") + if (chat.author !== userId) throw new Error("Only the chat author can pin") + const messages = await readAll(ssbClient) + const idx = buildIndex(messages) + const target = idx.msgNodes.get(targetKey) + if (!target || (target.c.chatId !== chat.rootId && idx.rawRootOf(target.c.chatId) !== idx.rawRootOf(chat.rootId))) throw new Error("Message not found") + const prev = idx.pinByTarget.get(targetKey) + const on = !(prev && prev.on && prev.author === userId) + return new Promise((resolve, reject) => { + ssbClient.publish({ type: "chatPin", chatId: chat.rootId, target: targetKey, on, author: userId, createdAt: new Date().toISOString() }, (e, res) => e ? reject(e) : resolve(res)) + }) + }, + async getParticipants(chatRootId) { const chat = await this.getChatById(chatRootId) if (!chat) return [] diff --git a/src/models/content_visibility.js b/src/models/content_visibility.js new file mode 100644 index 0000000..6afd0b7 --- /dev/null +++ b/src/models/content_visibility.js @@ -0,0 +1,30 @@ +const up = (v) => String(v == null ? '' : v).toUpperCase(); +const arr = (v) => (Array.isArray(v) ? v : []); +const isPrivateFlag = (v) => { + if (v === false) return true; + const s = String(v == null ? '' : v).toLowerCase(); + return s === 'private' || s === 'false' || s === '0'; +}; + +const isContentVisibleTo = (type, c, viewer, author = null) => { + if (!c || typeof c !== 'object') return false; + if (c.encrypted === true || c.encryptedPayload || c.encryptedQuestion || c.encryptedText) return false; + const owner = String(author || c.author || '') === String(viewer); + switch (type) { + case 'task': return !isPrivateFlag(c.isPublic) || owner || arr(c.assignees).includes(viewer); + case 'event': return !isPrivateFlag(c.isPublic) || owner || arr(c.attendees).includes(viewer); + case 'forum': return !(c.isPrivate === true || c.isPrivate === 'true' || c.isPrivate === 'on') || owner; + case 'job': return up(c.visibility) !== 'HIDDEN' || owner || arr(c.subscribers).includes(viewer); + case 'housing': + case 'market': return up(c.visibility) !== 'HIDDEN' || owner; + case 'shop': return up(c.visibility) !== 'CLOSED' || owner; + case 'schoolCourse': return up(c.visibility) !== 'INVITE' || owner || arr(c.students).includes(viewer) || arr(c.invited).includes(viewer); + case 'tribe': return c.isAnonymous === false || owner || arr(c.members).includes(viewer); + case 'poll': return !c.chatId && !c.tribeId; + case 'room': return !c.tribeId && c.encrypted !== true; + case 'curriculum': return up(c.visibility) !== 'HIDDEN' || owner; + default: return !c.tribeId; + } +}; + +module.exports = { isContentVisibleTo }; diff --git a/src/models/courts_model.js b/src/models/courts_model.js index 13b8fde..91f946c 100644 --- a/src/models/courts_model.js +++ b/src/models/courts_model.js @@ -2,6 +2,9 @@ const pull = require('../server/node_modules/pull-stream'); const moment = require('../server/node_modules/moment'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped } = require('./typed_log'); +const { buildVoteResults } = require('../backend/vote_tally'); +const { versionGroups } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const CASE_ANSWER_DAYS = 7; @@ -16,6 +19,13 @@ const EVIDENCE_FIELDS = ['text', 'link', 'imageUrl']; const ANSWER_FIELDS = ['stance', 'text']; const VERDICT_FIELDS = ['result', 'orders']; const SETTLEMENT_FIELDS = ['terms']; +const COURTS_TYPES = [ + 'courtsCase', 'courtsMediators', 'courtsJudge', 'courtsVoteLink', 'courtsVisibility', + 'courtsAnswer', 'courtsEvidence', 'courtsVerdict', 'courtsSettlementProposal', + 'courtsSettlementAccepted', 'courtsSupport', 'courtsNomination', 'courtsNominationVote', + 'courts-key', 'tombstone' +]; +const VOTE_LOG_TYPES = ['votes', 'votesVote', 'tombstone']; module.exports = ({ cooler, services = {}, tribeCrypto }) => { let ssb; @@ -73,12 +83,7 @@ module.exports = ({ cooler, services = {}, tribeCrypto }) => { async function readLog() { const ssbClient = await openSsb(); - return new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, arr) => (err ? reject(err) : resolve(arr))) - ); - }); + return readTyped(ssbClient, COURTS_TYPES, { limit: logLimit }); } async function listByType(type) { @@ -86,13 +91,19 @@ module.exports = ({ cooler, services = {}, tribeCrypto }) => { const tomb = buildValidatedTombstoneSet(msgs); const rep = new Map(); const map = new Map(); + const authorOf = new Map(); + for (const m of msgs) { + const c = m.value?.content || m.content; + if (c && c.type === type) authorOf.set(m.key || m.id, m.value?.author); + } for (const m of msgs) { const k = m.key || m.id; const c = m.value?.content || m.content; if (!c) continue; if (c.type === type) { + if (c.replaces && authorOf.has(c.replaces) && authorOf.get(c.replaces) !== m.value?.author) continue; if (c.replaces) rep.set(c.replaces, k); - map.set(k, { id: k, ...c }); + map.set(k, { ...c, id: k }); } } for (const oldId of rep.keys()) map.delete(oldId); @@ -252,7 +263,7 @@ if (c.type === type) { myPublicPreference = c.publicPrefRespondent; } const rootId = c.rootCaseId || c.id; - const tally = c.voteId ? await voteTally(c.voteId) : null; + const tally = c.voteId ? await voteTally(c.voteId, [c.accuser, c.respondentId]) : null; rows.push({ ...c, respondent: c.respondentId || c.respondent, @@ -284,27 +295,24 @@ if (c.type === type) { for (const m of msgs) { const c = m.value?.content || m.content; if (!c || c.type !== 'courtsCase') continue; - nodes.set(m.key || m.id, { key: m.key || m.id, author: m.value?.author, c }); + nodes.set(m.key || m.id, { key: m.key || m.id, author: m.value?.author, seq: m.value?.sequence || 0, ts: m.value?.timestamp || m.timestamp || 0, c }); } - const strictNext = new Map(), strictPrev = new Map(); - for (const [k, n] of nodes) { - const t = n.c.replaces; - if (t) { const o = nodes.get(t); if (o && o.author === n.author) { strictNext.set(t, k); strictPrev.set(k, t); } } - } - return { nodes, strictNext, strictPrev }; + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === 'string' ? n.c.replaces : null, claimedAuthor: typeof n.c.accuser === 'string' ? n.c.accuser : null }))); + return { nodes, vg }; } function strictCaseBase(msgs, id, idxIn) { const idx = idxIn || collectCaseNodes(msgs); - const { nodes, strictNext, strictPrev } = idx; + const { nodes, vg } = idx; const tomb = buildValidatedTombstoneSet(msgs); let startKey = nodes.has(id) ? id : null; if (!startKey) { for (const [k, n] of nodes) { if (String(n.c.rootCaseId || '') === id) { startKey = k; break; } } } if (!startKey) return null; - let root = startKey, g = 0; while (strictPrev.has(root) && g++ < 100000) root = strictPrev.get(root); - let tip = root; g = 0; while (strictNext.has(tip) && g++ < 100000) tip = strictNext.get(tip); + const root = vg.rootOf(startKey); + if (vg.isForeignCopy(root)) return null; + const tip = vg.tipOf(root); if (tomb.has(tip) || tomb.has(root)) return null; const node = nodes.get(tip); if (!node) return null; @@ -329,6 +337,7 @@ if (c.type === type) { let medATs = 0, medRTs = 0, judgeId = '', judgeTs = 0, voteId = String(base.voteId || ''), voteTs = 0; let prefA = base.publicPrefAccuser, prefATs = 0, prefR = base.publicPrefRespondent, prefRTs = 0; let answered = false, settled = false, verdict = null, verdictTs = 0, verdictAt = null, settledAt = null; + const verdictClaims = []; for (const m of msgs) { const c = m.value?.content || m.content; if (!c) continue; @@ -351,20 +360,20 @@ if (c.type === type) { answered = true; } else if (c.type === 'courtsSettlementAccepted' && matchC(c) && isParty(author)) { settled = true; settledAt = c.createdAt || new Date(ts).toISOString(); - } else if (c.type === 'courtsVerdict' && matchC(c) && ts >= verdictTs) { - verdict = author; verdictTs = ts; verdictAt = c.createdAt || new Date(ts).toISOString(); + } else if (c.type === 'courtsVerdict' && matchC(c)) { + verdictClaims.push({ author, ts, at: c.createdAt || new Date(ts).toISOString() }); } } - let verdictValid = false; - if (verdict) { - if (method === 'JUDGE') verdictValid = (verdict === judgeId); - else if (method === 'MEDIATION') verdictValid = (medA.includes(verdict) || medR.includes(verdict)); - else if (method === 'DICTATOR') verdictValid = (verdict === dictatorId); + const legit = await verdictAuthors({ method, judgeId, medA, medR, dictatorId, openedAt: base.openedAt || base.createdAt, accuser, respondentId }); + for (const v of verdictClaims) { + if (!legit.has(v.author) || v.ts < verdictTs) continue; + verdict = v.author; verdictTs = v.ts; verdictAt = v.at; } + const verdictValid = !!verdict; let voteDecided = false; if ((method === 'POPULAR' || method === 'KARMATOCRACY') && voteId) { - try { const t = await voteTally(voteId); if (t && t.closed && t.total > 0) voteDecided = true; } catch (_) {} + try { const t = await voteTally(voteId, [accuser, respondentId]); if (t && t.closed && t.total > 0 && (t.yes >= t.needed || t.no >= t.needed)) voteDecided = true; } catch (_) {} } base.mediatorsAccuser = medA; @@ -380,6 +389,33 @@ if (c.type === type) { return base; } + async function isRulingDictator(feedId, at) { + if (!feedId || !services.parliament || !services.parliament.listTerms) return false; + const atMs = new Date(at).getTime(); + if (!Number.isFinite(atMs)) return false; + try { + const terms = await services.parliament.listTerms('all'); + return (terms || []).some(t => + String(t.method || '').toUpperCase() === 'DICTATORSHIP' && + t.powerType === 'inhabitant' && + String(t.powerId || '') === String(feedId) && + new Date(t.startAt).getTime() <= atMs && + atMs < new Date(t.endAt).getTime() + ); + } catch (_) { return false; } + } + + async function verdictAuthors({ method, judgeId, medA, medR, dictatorId, openedAt, accuser, respondentId }) { + const out = new Set(); + if (method === 'JUDGE' && judgeId) out.add(String(judgeId)); + else if (method === 'MEDIATION') for (const x of [...ensureArray(medA), ...ensureArray(medR)]) out.add(String(x)); + else if (method === 'DICTATOR' && dictatorId && await isRulingDictator(dictatorId, openedAt)) out.add(String(dictatorId)); + out.delete(String(accuser || '')); + out.delete(String(respondentId || '')); + out.delete(''); + return out; + } + async function getCaseById(caseId) { const id = String(caseId || '').trim(); if (!id) return null; @@ -649,17 +685,22 @@ if (c.type === type) { return map; } - async function voteTally(voteId) { - if (!voteId || !services.votes || !services.votes.getVoteById) return null; + async function voteTally(voteId, parties) { + if (!voteId) return null; try { - const v = await services.votes.getVoteById(voteId); - const vm = v && v.votes ? v.votes : {}; + const ssbClient = await openSsb(); + const msgs = await readTyped(ssbClient, VOTE_LOG_TYPES, { limit: logLimit }); + const r = buildVoteResults(msgs).get(voteId); + if (!r) return null; + const tomb = buildValidatedTombstoneSet(msgs); + if (tomb.has(r.rootId) || tomb.has(voteId)) return null; + if (Array.isArray(parties) && !parties.map(x => String(x || '')).includes(String(r.creator || ''))) return null; + const vm = r.votes || {}; const yes = Number(vm.YES ?? vm.Yes ?? vm.yes ?? 0); const no = Number(vm.NO ?? vm.No ?? vm.no ?? 0); - const sum = Object.values(vm).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const deadline = v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); + const total = Number(r.totalVotes || 0); + const deadline = r.deadline || null; + const closed = !!(deadline && moment(deadline).isBefore(moment())); const needed = Math.floor(total / 2) + 1; return { yes, no, total, needed, closed, deadline }; } catch { return null; } @@ -868,8 +909,24 @@ if (c.type === type) { .filter(matchCase) .map(tryDecrypt) .sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0)); + const legit = await verdictAuthors({ + method: String(base.method || '').toUpperCase(), + judgeId, + medA: ma, + medR: mr, + dictatorId, + openedAt: base.openedAt || base.createdAt, + accuser: accuserId, + respondentId + }); + const legitVerdictKeys = new Set(); + for (const m of await readLog()) { + const c = m.value?.content; + if (c && c.type === 'courtsVerdict' && legit.has(String(m.value?.author || ''))) legitVerdictKeys.add(m.key || m.id); + } const verdicts = verdictsAll .filter(matchCase) + .filter(v => legitVerdictKeys.has(v.id)) .map(tryDecrypt) .sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0)); const verdict = verdicts.length ? verdicts[verdicts.length - 1] : null; @@ -884,7 +941,7 @@ if (c.type === type) { const hasVerdict = !!verdict; const supportsMap = await countSupportsByCase(); const supportCount = supportsMap.get(String(caseRootId)) || supportsMap.get(String(id)) || 0; - const tally = base.voteId ? await voteTally(base.voteId) : null; + const tally = base.voteId ? await voteTally(base.voteId, [accuserId, respondentId]) : null; return { ...base, id, @@ -919,12 +976,7 @@ if (c.type === type) { const ssbKeys = require('../server/node_modules/ssb-keys'); const ssbConfig = require('../server/ssb_config'); const ssbClient = await openSsb(); - const msgs = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, arr) => (err ? rej(err) : res(arr))) - ); - }); + const msgs = await readLog(); for (const m of msgs) { const c = m.value?.content; if (!c || c.type !== 'courts-key') continue; diff --git a/src/models/crypto.js b/src/models/crypto.js index 13ebaca..65b3dec 100644 --- a/src/models/crypto.js +++ b/src/models/crypto.js @@ -12,13 +12,17 @@ const SENSITIVE_FIELDS = [ const ENVELOPE_PRESERVE = new Set([ 'type', 'tribeId', 'contentType', 'replaces', 'target', 'author', 'createdAt', 'updatedAt', 'encryptedPayload', - 'mapId', 'calendarId', 'dateId', 'padId', 'roomId', 'parentId', + 'mapId', 'calendarId', 'dateId', 'padId', 'roomId', 'parentId', 'courseId', 'members', 'invites', 'participants', '_decrypted', '_undecryptable' ]); const INVITE_SALT_LEGACY = 'SolarNET.HuB'; -const INVITE_SCRYPT = { N: 131072, r: 8, p: 1, maxmem: 256 * 1024 * 1024 }; +const INVITE_SCRYPT = { N: 16384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }; +const INVITE_SCRYPT_LEGACY = { N: 131072, r: 8, p: 1, maxmem: 512 * 1024 * 1024 }; +const INVITE_CACHE_MAX = 512; +const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']); +const INVITE_FAIL_TTL_MS = 10 * 60 * 1000; const FP_INFO = Buffer.from('v1-fp', 'utf8'); const ENVELOPE_TYPE = 'tribe-msg'; @@ -27,7 +31,7 @@ const KEY_DISTRIB_TYPE = 'tribe-keys-distrib'; const KEY_DISTRIB_BATCH = 7; module.exports = (configPath, namespace = 'tribes') => { - const keysDir = path.join(configPath, 'keys'); + const keysDir = require('../configs/state-manager').keysDir(configPath); try { fs.mkdirSync(keysDir, { recursive: true, mode: 0o700 }); } catch (_) {} const keyringPath = path.join(keysDir, `${namespace}-keys.json`); @@ -62,6 +66,8 @@ module.exports = (configPath, namespace = 'tribes') => { }; const generateTribeKey = () => crypto.randomBytes(32).toString('hex'); + const HEX_KEY = /^[0-9a-f]{64}$/i; + const validKey = (k) => typeof k === 'string' && HEX_KEY.test(k); const getKey = (rid) => { const e = keyring[rid]; @@ -81,15 +87,17 @@ module.exports = (configPath, namespace = 'tribes') => { const getAllRootIds = () => Object.keys(keyring); const setKey = (rid, kHex, gen) => { + if (!validKey(kHex)) return; keyring[rid] = { keys: [kHex], gen: gen || 1 }; saveKeyring(); }; const setKeys = (rid, ks, topGen) => { - if (!Array.isArray(ks) || !ks.length) return; + if (!Array.isArray(ks)) return; const seen = new Set(); const dedup = []; - for (const k of ks) if (k && !seen.has(k)) { seen.add(k); dedup.push(k); } + for (const k of ks) if (validKey(k) && !seen.has(k)) { seen.add(k); dedup.push(k); } + if (!dedup.length) return; keyring[rid] = { keys: dedup, gen: topGen || dedup.length }; saveKeyring(); }; @@ -97,8 +105,9 @@ module.exports = (configPath, namespace = 'tribes') => { const mergeKeys = (rid, incoming, topGen) => { const e = keyring[rid] || { keys: [], gen: 0 }; const seen = new Set(e.keys); - const merged = [...e.keys]; - for (const k of incoming) if (k && !seen.has(k)) { seen.add(k); merged.push(k); } + const fresh = []; + for (const k of (Array.isArray(incoming) ? incoming : [])) if (validKey(k) && !seen.has(k)) { seen.add(k); fresh.push(k); } + const merged = [...fresh, ...e.keys]; keyring[rid] = { keys: merged, gen: Math.max(e.gen || 0, topGen || merged.length) }; saveKeyring(); return keyring[rid].gen; @@ -106,6 +115,7 @@ module.exports = (configPath, namespace = 'tribes') => { const addNewKey = (rid, kHex) => { const e = keyring[rid] || { keys: [], gen: 0 }; + if (!validKey(kHex)) return e.gen; if (e.keys.includes(kHex)) return e.gen; e.keys.unshift(kHex); e.gen = (e.gen || 0) + 1; @@ -114,6 +124,27 @@ module.exports = (configPath, namespace = 'tribes') => { return e.gen; }; + const keyPlan = ({ rootId, ownerId, gen, members, messages, leaves }) => { + const stamp = (m) => Number(m.timestamp || (m.value && m.value.timestamp) || 0); + const g = Number(gen) || 0; + let genStart = g > 1 ? null : 0; + const lastGot = new Map(); + for (const m of (Array.isArray(messages) ? messages : [])) { + const c = m && m.value && m.value.content; + if (!c || c.type !== 'tribe-keys' || c.tribeId !== rootId || m.value.author !== ownerId) continue; + if (!c.memberKeys || typeof c.memberKeys !== 'object') continue; + const t = stamp(m); + for (const id of Object.keys(c.memberKeys)) lastGot.set(id, Math.max(lastGot.get(id) || 0, t)); + if (g > 1 && Number(c.generation) === g) genStart = genStart === null ? t : Math.min(genStart, t); + } + if (genStart === null) genStart = 0; + const current = new Set((Array.isArray(members) ? members : []).filter(Boolean)); + const gone = (Array.isArray(leaves) ? leaves : []).filter((l) => l && l.member && l.member !== ownerId); + const rotate = g > 0 && gone.some((l) => !current.has(l.member) && l.ts > genStart); + const missing = rotate ? [] : [...current].filter((id) => id !== ownerId && (!lastGot.has(id) || gone.some((l) => l.member === id && l.ts > lastGot.get(id)))); + return { rotate, missing }; + }; + const dropKey = (rid) => { if (keyring[rid]) { delete keyring[rid]; @@ -121,8 +152,15 @@ module.exports = (configPath, namespace = 'tribes') => { } }; - const fingerprint = (kHex) => - crypto.createHmac('sha256', Buffer.from(kHex, 'hex')).update(FP_INFO).digest('hex').slice(0, 32); + const fingerprints = new Map(); + const fingerprint = (kHex) => { + let fp = fingerprints.get(kHex); + if (!fp) { + fp = crypto.createHmac('sha256', Buffer.from(kHex, 'hex')).update(FP_INFO).digest('hex').slice(0, 32); + fingerprints.set(kHex, fp); + } + return fp; + }; const buildFingerprintIndex = () => { const m = new Map(); @@ -200,9 +238,9 @@ module.exports = (configPath, namespace = 'tribes') => { const generateInviteSalt = () => crypto.randomBytes(16).toString('hex'); - const deriveInviteKey = (code, salt) => { + const deriveInviteKey = (code, salt, params = INVITE_SCRYPT) => { const s = (salt === undefined || salt === null || salt === '') ? INVITE_SALT_LEGACY : salt; - return crypto.scryptSync(code, s, 32, INVITE_SCRYPT); + return crypto.scryptSync(code, s, 32, params); }; const hashInviteCode = (code, salt) => { @@ -218,9 +256,44 @@ module.exports = (configPath, namespace = 'tribes') => { return encryptWithKey(tribeKeyHex, derived.toString('hex'), inviteAad(inviteCode, salt)); }; + const derivedCache = new Map(); + const failedCache = new Map(); + const remember = (map, k, v, max) => { + map.delete(k); + map.set(k, v); + while (map.size > max) map.delete(map.keys().next().value); + }; + const derivedKeyName = (code, salt, params) => `${params.N}|${salt == null ? '' : salt}|${code}`; + const openWithDerived = (payload, code, salt, params, aad) => { + const name = derivedKeyName(code, salt, params); + const cached = derivedCache.get(name); + const derived = cached || deriveInviteKey(code, salt, params); + const out = decryptWithKey(payload, derived.toString('hex'), aad); + if (out && !cached) remember(derivedCache, name, derived, INVITE_CACHE_MAX); + return out; + }; + const failureName = (payload, code, salt) => crypto.createHash('sha256').update(`${String(payload)}|${code}|${salt == null ? '' : salt}`).digest('hex'); + const recentlyFailed = (name) => { + const at = failedCache.get(name); + return !!at && Date.now() - at < INVITE_FAIL_TTL_MS; + }; + const decryptFromInvite = (encryptedKey, inviteCode, salt) => { - const derived = deriveInviteKey(inviteCode, salt); - return decryptWithKey(encryptedKey, derived.toString('hex'), inviteAad(inviteCode, salt)); + const failure = failureName(encryptedKey, inviteCode, salt); + if (recentlyFailed(failure)) throw new Error('Unsupported state or unable to authenticate data'); + const aad = inviteAad(inviteCode, salt); + try { + const out = openWithDerived(encryptedKey, inviteCode, salt, INVITE_SCRYPT, aad); + if (out) return out; + } catch (_) {} + for (let attempt = 0; attempt < 3; attempt++) { + try { + const out = openWithDerived(encryptedKey, inviteCode, salt, INVITE_SCRYPT_LEGACY, aad); + if (out) return out; + } catch (_) {} + } + remember(failedCache, failure, Date.now(), INVITE_CACHE_MAX); + throw new Error('Unsupported state or unable to authenticate data'); }; const INVITE_ENCRYPT_ATTEMPTS = 3; @@ -244,9 +317,15 @@ module.exports = (configPath, namespace = 'tribes') => { }; const decryptChainOnce = (encryptedPayload, code, salt) => { - const k = deriveInviteKey(code, salt); + let json = null + try { + json = openWithDerived(encryptedPayload, code, salt, INVITE_SCRYPT, inviteAad(code, salt)); + } catch (_) { + try { + json = openWithDerived(encryptedPayload, code, salt, INVITE_SCRYPT_LEGACY, inviteAad(code, salt)); + } catch (_) { return null; } + } try { - const json = decryptWithKey(encryptedPayload, k.toString('hex'), inviteAad(code, salt)); const parsed = JSON.parse(json); if (Array.isArray(parsed) && parsed.every(e => e && e.rootId && Array.isArray(e.keys) && e.keys.length)) { return parsed.map(e => ({ @@ -261,11 +340,14 @@ module.exports = (configPath, namespace = 'tribes') => { }; const decryptChainFromInvite = (encryptedPayload, code, salt, attempts = 1) => { + const failure = failureName(encryptedPayload, code, salt); + if (recentlyFailed(failure)) return null; const tries = Math.max(1, Number(attempts) || 1); for (let i = 0; i < tries; i++) { const chain = decryptChainOnce(encryptedPayload, code, salt); if (chain) return chain; } + remember(failedCache, failure, Date.now(), INVITE_CACHE_MAX); return null; }; @@ -365,7 +447,9 @@ module.exports = (configPath, namespace = 'tribes') => { const payload = JSON.parse(plaintext); const result = { ...content }; delete result.encryptedPayload; - Object.assign(result, payload); + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + for (const [k, v] of Object.entries(payload)) if (!UNSAFE_KEYS.has(k) && !(k in envelope)) result[k] = v; + } return result; } catch (_) {} } @@ -534,7 +618,7 @@ module.exports = (configPath, namespace = 'tribes') => { ENVELOPE_TYPE, ENVELOPE_VERSION, KEY_DISTRIB_TYPE, KEY_DISTRIB_BATCH, loadKeyring, saveKeyring, generateTribeKey, getKey, getKeys, getGen, getAllRootIds, - setKey, setKeys, mergeKeys, addNewKey, dropKey, + setKey, setKeys, mergeKeys, addNewKey, dropKey, keyPlan, fingerprint, buildFingerprintIndex, isTribeMsg, wrapMsg, unwrapMsg, encryptWithKey, decryptWithKey, diff --git a/src/models/cv_model.js b/src/models/cv_model.js index fabbc6c..173ac0b 100644 --- a/src/models/cv_model.js +++ b/src/models/cv_model.js @@ -1,8 +1,11 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped } = require('./typed_log'); const logLimit = getConfig().ssbLogStream?.limit || 1000; +const CV_TYPES = ['curriculum', 'tombstone']; + const extractBlobId = str => { if (!str || typeof str !== 'string') return null; const match = str.match(/\(([^)]+\.sha256)\)/); @@ -24,7 +27,7 @@ module.exports = ({ cooler }) => { return { type: 'curriculum', - async createCV(data, photoBlobId) { + async createCV(data, photoBlobId, pdfBlobId = null) { const ssbClient = await openSsb(); const userId = ssbClient.id; const content = { @@ -33,6 +36,7 @@ module.exports = ({ cooler }) => { name: data.name, description: data.description, photo: extractBlobId(photoBlobId) || null, + pdf: extractBlobId(pdfBlobId) || null, contact: userId, personalSkills: parseCSV(data.personalSkills), personalExperiences: data.personalExperiences || '', @@ -56,7 +60,7 @@ module.exports = ({ cooler }) => { }); }, - async updateCV(id, data, photoBlobId) { + async updateCV(id, data, photoBlobId, pdfBlobId = null) { const ssbClient = await openSsb(); const userId = ssbClient.id; @@ -88,6 +92,7 @@ module.exports = ({ cooler }) => { name: data.name, description: data.description, photo: extractBlobId(photoBlobId) || old.content.photo || null, + pdf: extractBlobId(pdfBlobId) || old.content.pdf || null, contact: userId, personalSkills: parseCSV(data.personalSkills), personalExperiences: data.personalExperiences || '', @@ -151,34 +156,27 @@ module.exports = ({ cooler }) => { const userId = ssbClient.id; const authorId = targetUserId || userId; - return new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => { - if (err) return reject(err); + const msgs = await readTyped(ssbClient, CV_TYPES, { limit: logLimit }); - const tombstoned = buildValidatedTombstoneSet(msgs); + const tombstoned = buildValidatedTombstoneSet(msgs); - const cvMsgs = msgs - .filter(m => - m.value?.content?.type === 'curriculum' && - m.value.author === authorId && - !tombstoned.has(m.key) - ) - .sort((a, b) => b.value.timestamp - a.value.timestamp); + const cvMsgs = msgs + .filter(m => + m.value?.content?.type === 'curriculum' && + m.value.author === authorId && + !tombstoned.has(m.key) + ) + .sort((a, b) => b.value.timestamp - a.value.timestamp); - if (!cvMsgs.length) { - return resolve(null); - } + if (!cvMsgs.length) { + return null; + } - const latest = cvMsgs[0]; - const c = latest.value.content; - const visibility = String(c.visibility || 'PUBLIC').toUpperCase() === 'HIDDEN' ? 'HIDDEN' : 'PUBLIC'; - if (visibility === 'HIDDEN' && authorId !== userId) return resolve(null); - resolve({ id: latest.key, ...c, visibility }); - }) - ); - }); + const latest = cvMsgs[0]; + const c = latest.value.content; + const visibility = String(c.visibility || 'PUBLIC').toUpperCase() === 'HIDDEN' ? 'HIDDEN' : 'PUBLIC'; + if (visibility === 'HIDDEN' && authorId !== userId) return null; + return { id: latest.key, ...c, visibility }; } }; }; diff --git a/src/models/data_model.js b/src/models/data_model.js index efa67dd..fec5bc3 100644 --- a/src/models/data_model.js +++ b/src/models/data_model.js @@ -1,6 +1,9 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped } = require('./typed_log'); +const { isContentVisibleTo } = require('./content_visibility'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -24,11 +27,20 @@ const KINDS = { documents: { type: 'document', href: (id) => `/documents/${encodeURIComponent(id)}` }, bookmarks: { type: 'bookmark', href: (id) => `/bookmarks/${encodeURIComponent(id)}` }, torrents: { type: 'torrent', href: (id) => `/torrents/${encodeURIComponent(id)}` }, + files: { type: 'file', href: (id) => `/files/${encodeURIComponent(id)}` }, chats: { type: 'chat', href: (id) => `/chats/${encodeURIComponent(id)}` }, pads: { type: 'pad', href: (id) => `/pads/${encodeURIComponent(id)}` }, + rooms: { type: 'room', href: (id) => `/rooms/${encodeURIComponent(id)}` }, maps: { type: 'map', href: (id) => `/maps/${encodeURIComponent(id)}` }, calendars: { type: 'calendar', href: (id) => `/calendars/${encodeURIComponent(id)}` }, - forum: { type: 'forum', href: (id) => `/forum/${encodeURIComponent(id)}` } + forum: { type: 'forum', href: (id) => `/forum/${encodeURIComponent(id)}` }, + school: { type: 'schoolCourse', href: (id) => `/school/course/${encodeURIComponent(id)}` }, + wiki: { type: 'wikiPage', href: (id) => `/wiki/${encodeURIComponent(id)}` }, + emergencies: { type: 'emergency', href: (id) => `/emergencies/${encodeURIComponent(id)}` }, + mailing: { type: 'mailingList', href: (id) => `/mailing/${encodeURIComponent(id)}` }, + logistics: { type: 'logisticsRoute', href: (id) => `/logistics/${encodeURIComponent(id)}` }, + podcasts: { type: 'podcast', href: (id) => `/podcasts/${encodeURIComponent(id)}` }, + campaigns: { type: 'campaign', href: (id) => `/campaigns/${encodeURIComponent(id)}` } }; const KIND_BY_TYPE = Object.fromEntries(Object.entries(KINDS).map(([k, v]) => [v.type, k])); @@ -49,7 +61,7 @@ const TITLE_STOPWORDS = new Set([ const titleTerms = (c) => String(c.title || c.name || c.question || c.concept || '') .toLowerCase() - .split(/[^0-9A-Za-zÀ-￿]+/) // sin \p{}: nodejs-mobile no lleva ICU completo + .split(/[^\w\u0080-\uFFFF]+/) .filter(w => w.length >= 3 && !TITLE_STOPWORDS.has(w) && !/^\d+$/.test(w)); const termsOf = (kind, c) => { @@ -68,9 +80,30 @@ const termsOf = (kind, c) => { return Array.from(new Set(out.map(norm).filter(t => t && !PLACEHOLDERS.has(t)))); }; +const DESC_TERMS_MAX = 40; +const descTerms = (c) => String(c.description || c.text || c.body || '') + .toLowerCase() + .split(/[^\w\u0080-\uFFFF]+/) + .filter(w => w.length >= 4 && !TITLE_STOPWORDS.has(w) && !/^\d+$/.test(w)) + .slice(0, DESC_TERMS_MAX); + +const FAV_KIND = Object.fromEntries(Object.keys(KINDS).map(k => [k, k === 'votes' ? 'polls' : k])); + +const ALIKE_MIN_AFFINITY = 0.3; +const RATED_MIN_OPINIONS = 3; +const REASON_ORDER = ['related', 'content', 'mutual', 'following', 'supportsYou', 'tribe', 'alike', 'cv', 'pinned', 'rated', 'near']; + +const opinionsTotalOf = (c) => { + const ops = c && typeof c.opinions === 'object' && c.opinions ? c.opinions : {}; + let total = 0; + for (const v of Object.values(ops)) total += Number(v) || 0; + return total; +}; + +const excerpt = (s) => String(s || '').replace(/!?\[[^\]]*\]\([^)]*\)/g, ' ').replace(/[#*_>`~|]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 90); const titleOf = (kind, c, author) => { - if (kind === 'inhabitants') return c.name || author || ''; - return c.title || c.name || c.question || c.concept || ''; + if (kind === 'inhabitants') return c.name || ''; + return c.title || c.name || c.question || c.concept || c.subject || excerpt(typeof c.text === 'string' ? c.text : '') || excerpt(typeof c.description === 'string' ? c.description : '') || ''; }; const jaccard = (a, b) => { @@ -80,17 +113,13 @@ const jaccard = (a, b) => { return { score: union > 0 ? common.length / union : 0, common }; }; -module.exports = ({ cooler }) => { +module.exports = ({ cooler, favoriteIdsFor = null }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const DATA_TYPES = [...Object.values(KINDS).map(v => v.type), 'tombstone', 'contact', 'about']; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, DATA_TYPES, { limit: logLimit }); const buildGraph = async () => { const ssbClient = await openSsb(); @@ -98,40 +127,74 @@ module.exports = ({ cooler }) => { const messages = await getAllMessages(ssbClient); const tomb = buildValidatedTombstoneSet(messages); - const latestByKey = new Map(); - const replaced = new Set(); + const candidates = new Map(); + const versions = []; + const follows = new Map(); + const followers = new Map(); + const aboutTerms = new Set(); for (const m of messages) { const v = m && m.value; const c = v && v.content; if (!c || typeof c !== 'object' || !c.type) continue; - if (tomb.has(m.key)) continue; - if (c.encryptedPayload || c.encryptedQuestion) continue; + if (c.type === 'contact') { + if (typeof c.contact !== 'string' || typeof c.following !== 'boolean') continue; + if (v.author === viewerId) follows.set(c.contact, c.following); + else if (c.contact === viewerId) followers.set(v.author, c.following); + continue; + } + if (c.type === 'about') { + if (v.author === viewerId && c.about === viewerId && typeof c.description === 'string') for (const t of descTerms(c)) aboutTerms.add(norm(t)); + continue; + } if (c.tribeId && c.type !== 'tribe') continue; const kind = KIND_BY_TYPE[c.type]; if (!kind) continue; - if (typeof c.replaces === 'string') replaced.add(c.replaces); - latestByKey.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, kind, c }); + versions.push(versionEntry(m)); + if (tomb.has(m.key)) continue; + if (!isContentVisibleTo(c.type, c, viewerId, v.author)) continue; + candidates.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, kind, c }); + } + + const vg = versionGroups(versions); + const latestByRoot = new Map(); + for (const root of vg.roots) { + const tip = candidates.get(vg.tipOf(root)); + if (!tip) continue; + if (tip.kind !== 'tribes') { latestByRoot.set(root, { ...tip, key: root }); continue; } + const members = new Set(Array.isArray(tip.c.members) ? tip.c.members : []); + const lastByAuthor = new Map(); + for (const k of vg.groups.get(root)) { + const n = candidates.get(k); + if (!n || n.author === tip.author || n.ts < tip.ts) continue; + const prev = lastByAuthor.get(n.author); + if (!prev || n.ts >= prev.ts) lastByAuthor.set(n.author, n); + } + for (const [a, n] of lastByAuthor) { if (Array.isArray(n.c.members) && n.c.members.includes(a)) members.add(a); else members.delete(a); } + latestByRoot.set(root, { ...tip, key: root, c: { ...tip.c, members: [...members] } }); } const nodes = []; const byAuthorCv = new Map(); - for (const node of latestByKey.values()) { - if (replaced.has(node.key)) continue; + for (const node of latestByRoot.values()) { const coreTerms = termsOf(node.kind, node.c); - const extra = node.kind === 'inhabitants' ? [] : titleTerms(node.c).map(norm).filter(t => t && !PLACEHOLDERS.has(t)); + const extra = node.kind === 'inhabitants' ? [] : [...titleTerms(node.c), ...descTerms(node.c)].map(norm).filter(t => t && !PLACEHOLDERS.has(t)); const terms = Array.from(new Set([...coreTerms, ...extra])); if (!terms.length) continue; const entry = { id: node.key, kind: node.kind, - author: node.c.author || node.author, - title: titleOf(node.kind, node.c, node.c.author || node.author), + ctype: KINDS[node.kind] ? KINDS[node.kind].type : '', + author: node.author, + title: titleOf(node.kind, node.c, node.author), terms, termSet: new Set(terms), coreTermSet: new Set(coreTerms), ts: node.ts, createdAt: node.c.createdAt || new Date(node.ts).toISOString(), - href: KINDS[node.kind].href(node.key, node.c) + href: KINDS[node.kind].href(node.key, node.c), + members: node.kind === 'tribes' && Array.isArray(node.c.members) ? node.c.members : null, + location: norm(node.c.location || ''), + opinionsTotal: opinionsTotalOf(node.c) }; if (node.kind === 'inhabitants') { const prev = byAuthorCv.get(entry.author); @@ -143,54 +206,168 @@ module.exports = ({ cooler }) => { } for (const cv of byAuthorCv.values()) nodes.push(cv); - return { viewerId, nodes, cvByAuthor: byAuthorCv }; + const following = new Set([...follows.entries()].filter(([, on]) => on).map(([id]) => id)); + const supporters = new Set([...followers.entries()].filter(([, on]) => on).map(([id]) => id)); + return { viewerId, nodes, cvByAuthor: byAuthorCv, following, supporters, aboutTerms }; + }; + + const favoriteIds = async () => { + if (typeof favoriteIdsFor !== 'function') return new Set(); + const out = new Set(); + await Promise.all(Object.values(FAV_KIND).map(async (k) => { + try { for (const id of await favoriteIdsFor(k)) out.add(String(id)); } catch (_) {} + })); + return out; + }; + + const diversify = (items) => { + const leaders = new Map(); + for (const it of items) if (!leaders.has(it.kind)) leaders.set(it.kind, it); + const lead = new Set(leaders.values()); + return [...leaders.values()].sort((x, y) => y.score - x.score || y.ts - x.ts) + .concat(items.filter(it => !lead.has(it))); }; const MAX_ENTITIES = 400; const MAX_PAIRS = 300; const strip = (n) => ({ - id: n.id, kind: n.kind, author: n.author, title: n.title, + id: n.id, kind: n.kind, ctype: n.ctype, author: n.author, title: n.title, href: n.href, createdAt: n.createdAt, ts: n.ts }); + const buildProfile = async (graph) => { + const { viewerId, nodes, cvByAuthor, aboutTerms } = graph; + const use = nodes.slice(0, MAX_ENTITIES); + const favs = await favoriteIds(); + + const myTermSet = new Set(); + const cvTerms = new Set(); + const contentTerms = new Set(); + const mineCv = cvByAuthor.get(viewerId); + if (mineCv) for (const t of mineCv.terms) { myTermSet.add(t); cvTerms.add(t); } + for (const t of aboutTerms) if (t && !PLACEHOLDERS.has(t)) myTermSet.add(t); + const tribeMates = new Set(); + const pinnedTerms = new Set(); + let myLocation = mineCv ? mineCv.location : ''; + for (const n of use) { + if (String(n.author) === String(viewerId)) { + for (const t of n.terms) { myTermSet.add(t); contentTerms.add(t); } + if (!myLocation && n.location) myLocation = n.location; + } + if (favs.has(String(n.id))) for (const t of n.terms) { myTermSet.add(t); pinnedTerms.add(t); } + if (n.members && (String(n.author) === String(viewerId) || n.members.includes(viewerId))) for (const m of n.members) tribeMates.add(m); + } + tribeMates.delete(viewerId); + + const df = new Map(); + for (const n of use) for (const t of n.termSet) df.set(t, (df.get(t) || 0) + 1); + const total = use.length || 1; + const weightOf = (t) => Math.log(1 + total / (df.get(t) || 1)); + return { use, myTermSet, cvTerms, contentTerms, pinnedTerms, tribeMates, myLocation, df, weightOf, total }; + }; + + const affinitiesFor = (viewerId, { use, myTermSet, pinnedTerms, tribeMates, weightOf }) => { + const termsByAuthor = new Map(); + for (const n of use) { + if (String(n.author) === String(viewerId)) continue; + if (!termsByAuthor.has(n.author)) termsByAuthor.set(n.author, new Set()); + const set = termsByAuthor.get(n.author); + for (const t of n.termSet) set.add(t); + } + let myW = 0; + for (const t of myTermSet) myW += weightOf(t); + const byAuthor = new Map(); + for (const [author, terms] of termsByAuthor) { + const common = [...terms].filter(t => myTermSet.has(t)); + let commonW = 0; + for (const t of common) commonW += weightOf(t); + let theirW = 0; + for (const t of terms) theirW += weightOf(t); + const floor = Math.min(myW, theirW); + const score = floor > 0 ? Math.min(1, commonW / floor) : 0; + common.sort((x, y) => weightOf(y) - weightOf(x) || x.localeCompare(y)); + const reasons = []; + if (tribeMates.has(String(author))) reasons.push('tribe'); + if (common.some(t => pinnedTerms.has(t))) reasons.push('pinned'); + byAuthor.set(author, { score, common, reasons }); + } + for (const m of tribeMates) if (!byAuthor.has(m)) byAuthor.set(m, { score: 0, common: [], reasons: ['tribe'] }); + return byAuthor; + }; + return { KINDS: Object.keys(KINDS), + async authorAffinities() { + const graph = await buildGraph(); + const profile = await buildProfile(graph); + return { viewerId: graph.viewerId, byAuthor: affinitiesFor(graph.viewerId, profile), hasProfile: profile.myTermSet.size > 0 }; + }, + async listMatches(filter = 'ALL', opts = {}) { - const { viewerId, nodes, cvByAuthor } = await buildGraph(); - const use = nodes.slice(0, MAX_ENTITIES); + const graph = await buildGraph(); + const { viewerId, following, supporters } = graph; + const profile = await buildProfile(graph); + const { use, myTermSet, cvTerms, contentTerms, pinnedTerms, tribeMates, myLocation, df, weightOf, total } = profile; + const affinity = affinitiesFor(viewerId, profile); const f = String(filter || 'ALL').toUpperCase(); - const myTermSet = new Set(); - const mineCv = cvByAuthor.get(viewerId); - if (mineCv) for (const t of mineCv.terms) myTermSet.add(t); + const expanded = new Set(); for (const n of use) { - if (String(n.author) === String(viewerId)) for (const t of n.terms) myTermSet.add(t); + if (String(n.author) === String(viewerId)) continue; + if (![...n.termSet].some(t => myTermSet.has(t))) continue; + for (const t of n.termSet) if (!myTermSet.has(t)) expanded.add(t); } - const df = new Map(); - for (const n of use) for (const t of n.termSet) df.set(t, (df.get(t) || 0) + 1); - const total = use.length || 1; - const weightOf = (t) => Math.log(1 + total / (df.get(t) || 1)); - let out = []; for (const n of use) { + if (!myTermSet.size) break; if (String(n.author) === String(viewerId)) continue; const common = [...n.termSet].filter(t => myTermSet.has(t)); - if (!common.length) continue; let commonW = 0; for (const t of common) commonW += weightOf(t); + let indirectW = 0; + for (const t of n.termSet) if (!myTermSet.has(t) && expanded.has(t)) indirectW += weightOf(t); let itemW = 0; for (const t of n.termSet) itemW += weightOf(t); - const score = itemW > 0 ? Math.min(1, commonW / itemW) : 0; - if (score <= 0) continue; + const direct = itemW > 0 ? Math.min(1, commonW / itemW) : 0; + const indirect = itemW > 0 ? Math.min(1, indirectW / itemW) : 0; + let dfSum = 0; + for (const t of n.termSet) dfSum += (df.get(t) || 1) / total; + const connectivity = n.termSet.size ? dfSum / n.termSet.size : 0; + const base = 0.8 * direct + 0.15 * indirect + 0.05 * connectivity; + const ageDays = Math.max(0, (Date.now() - (n.ts || 0)) / 86400000); + const recency = 0.8 + 0.2 * Math.exp(-ageDays / 60); + const social = (following.has(String(n.author)) ? 0.1 : 0) + (tribeMates.has(String(n.author)) ? 0.05 : 0); + const score = Math.min(1, base * recency + social); common.sort((x, y) => weightOf(y) - weightOf(x) || x.localeCompare(y)); - out.push({ ...strip(n), score, common, connections: common.length }); + const author = String(n.author); + const flags = new Set(); + if (!common.length && indirect > 0) flags.add('related'); + if (common.some(t => contentTerms.has(t))) flags.add('content'); + if (following.has(author) && supporters.has(author)) flags.add('mutual'); + else if (following.has(author)) flags.add('following'); + else if (supporters.has(author)) flags.add('supportsYou'); + if (tribeMates.has(author)) flags.add('tribe'); + const aff = affinity.get(n.author); + if (aff && aff.score >= ALIKE_MIN_AFFINITY) flags.add('alike'); + if (common.some(t => cvTerms.has(t))) flags.add('cv'); + if (common.some(t => pinnedTerms.has(t))) flags.add('pinned'); + if (n.opinionsTotal >= RATED_MIN_OPINIONS) flags.add('rated'); + if (myLocation && n.location && n.location === myLocation) flags.add('near'); + const reasons = REASON_ORDER.filter(r => flags.has(r)); + out.push({ ...strip(n), score, common, connections: common.length, reasons }); + } + + const anyMatches = out.length > 0; + const kindsAvail = {}; + for (const n of use) { + if (String(n.author) !== String(viewerId)) kindsAvail[n.kind] = true; } if (f === 'RECENT') { - const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000; + const cutoff = Date.now() - 24 * 60 * 60 * 1000; out = out.filter(s => s.ts >= cutoff); } else if (f !== 'ALL' && f !== 'TOP') { const kind = f.toLowerCase(); @@ -200,14 +377,25 @@ module.exports = ({ cooler }) => { const q = norm(opts.q); if (q) out = out.filter(s => norm(s.title).includes(q) || s.common.some(t => t.includes(q))); + const present = new Set(); + for (const s of out) for (const r of s.reasons) present.add(r); + const reasonsAvail = REASON_ORDER.filter(r => present.has(r)); + const reason = String(opts.reason || '').trim(); + if (reason && REASON_ORDER.includes(reason)) out = out.filter(s => s.reasons.includes(reason)); + if (f === 'RECENT') out.sort((x, y) => y.ts - x.ts || y.score - x.score); else out.sort((x, y) => y.score - x.score || y.ts - x.ts); + if (f === 'ALL' || f === 'TOP') out = diversify(out); return { matches: out.slice(0, MAX_PAIRS), total: out.length, hasProfile: myTermSet.size > 0, - myTerms: [...myTermSet] + myTerms: [...myTermSet], + kindsAvail, + anyMatches, + reasonsAvail, + reason: reasonsAvail.includes(reason) ? reason : '' }; }, diff --git a/src/models/documents_model.js b/src/models/documents_model.js index b6affb1..b044b9d 100644 --- a/src/models/documents_model.js +++ b/src/models/documents_model.js @@ -2,7 +2,8 @@ const pull = require("../server/node_modules/pull-stream"); const { getConfig } = require("../configs/config-manager.js"); const categories = require("../backend/opinion_categories"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require("./typed_log"); const contentFavorites = require("../backend/content_favorites"); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -34,20 +35,13 @@ module.exports = ({ cooler }) => { return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const DOCUMENT_TYPES = ["document", "documentOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, DOCUMENT_TYPES, { limit: logLimit }); const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const opinionMsgs = []; for (const m of messages) { @@ -63,50 +57,16 @@ module.exports = ({ cooler }) => { const ts = v.timestamp || m.timestamp || 0; let sizeBytes = 0; try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; } - nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); } - for (const [k, t] of Array.from(parent.entries())) { - const orig = nodes.get(t); - const node = nodes.get(k); - if (orig && node && orig.author === node.author) continue; - parent.delete(k); - if (child.get(t) === k) child.delete(t); - if (orig) nodes.delete(k); - } - - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } - const forward = new Map(); - for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); - const resolveGroup = (root) => { const contentTip = contentTipOf(root); const contentNode = nodes.get(contentTip) || nodes.get(root); @@ -123,9 +83,9 @@ module.exports = ({ cooler }) => { }; const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); - return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const pickDoc = (node, rootId, agg) => { @@ -140,6 +100,7 @@ module.exports = ({ cooler }) => { author: node.author, title: c.title || "", description: c.description || "", + license: c.license || "", opinions: agg ? agg.opinions : (c.opinions || {}), opinions_inhabitants: agg ? agg.voters : safeArr(c.opinions_inhabitants), sizeBytes: node.sizeBytes || 0 @@ -178,16 +139,12 @@ module.exports = ({ cooler }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(messages); - let tip = id; - while (idx.forward.has(tip)) tip = idx.forward.get(tip); - if (idx.tomb.has(tip)) throw new Error("Document not found"); - - let root = tip; - while (idx.parent.has(root)) root = idx.parent.get(root); + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Document not found"); return root; }, - async createDocument(blobMarkdown, tagsRaw, title, description) { + async createDocument(blobMarkdown, tagsRaw, title, description, license) { const ssbClient = await openSsb(); const blobId = parseBlobId(blobMarkdown); if (!blobId) throw new Error("Missing document blob"); @@ -203,7 +160,8 @@ module.exports = ({ cooler }) => { title: title || "", description: description || "", opinions: {}, - opinions_inhabitants: [] + opinions_inhabitants: [], + ...(license ? { license } : {}) }; return new Promise((resolve, reject) => { @@ -211,7 +169,7 @@ module.exports = ({ cooler }) => { }); }, - async updateDocumentById(id, blobMarkdown, tagsRaw, title, description) { + async updateDocumentById(id, blobMarkdown, tagsRaw, title, description, license) { const ssbClient = await openSsb(); const tipId = await this.resolveCurrentId(id); @@ -238,7 +196,8 @@ module.exports = ({ cooler }) => { tags, title: title !== undefined ? (title || "") : oldMsg.content.title || "", description: description !== undefined ? (description || "") : oldMsg.content.description || "", - updatedAt + updatedAt, + ...(license !== undefined ? { license } : {}) }; const tombstone = { type: "tombstone", target: tipId, deletedAt: updatedAt, author: userId }; @@ -327,12 +286,12 @@ module.exports = ({ cooler }) => { async getDocumentById(id) { const ssbClient = await openSsb(); const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); + const idx = memoIndex('documents', messages, buildIndex); const favorites = await favoritesSetForDocuments(); const rootId = idx.rootOf(id); const agg = idx.resolveGroup(rootId); - if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Document not found"); + if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Document not found"); const c = agg.contentNode.c || {}; return { @@ -345,6 +304,7 @@ module.exports = ({ cooler }) => { author: agg.contentNode.author, title: c.title || "", description: c.description || "", + license: c.license || "", opinions: agg.opinions, opinions_inhabitants: agg.voters, isFavorite: favorites.has(rootId || agg.contentTip) diff --git a/src/models/emergencies_model.js b/src/models/emergencies_model.js new file mode 100644 index 0000000..91929b3 --- /dev/null +++ b/src/models/emergencies_model.js @@ -0,0 +1,297 @@ +const { readTyped, memoIndex } = require('./typed_log'); +const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { getConfig } = require('../configs/config-manager.js'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const EMERGENCY_TYPES = ['emergency', 'emergencyConfirm', 'emergencyUpdate', 'tombstone']; +const CATEGORIES = ['WEATHER', 'INFRASTRUCTURE', 'HEALTH', 'SECURITY', 'LOST', 'NEIGHBORHOOD']; +const SEVERITY_THRESHOLDS = { LOW: 2, MEDIUM: 5, HIGH: 10 }; +const EXPIRES_OPTIONS = { '1d': 1, '3d': 3, '7d': 7, '30d': 30 }; +const MEDIA_RE = /!?\[(image|video):[^\]]*\]\((&[^)]+)\)/; +const RECENT_MS = 24 * 60 * 60 * 1000; + +const normU = (v) => String(v == null ? '' : v).trim().toUpperCase(); +const safeText = (v) => String(v == null ? '' : v).trim(); +const normalizeTags = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(','); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(Boolean))); +}; +const normalizeCategory = (v) => (CATEGORIES.includes(normU(v)) ? normU(v) : 'NEIGHBORHOOD'); +const severityOf = (confirmations) => { + const n = Number(confirmations) || 0; + if (n >= SEVERITY_THRESHOLDS.HIGH) return 'HIGH'; + if (n >= SEVERITY_THRESHOLDS.MEDIUM) return 'MEDIUM'; + if (n >= SEVERITY_THRESHOLDS.LOW) return 'LOW'; + return 'UNVERIFIED'; +}; +const expiresAtFrom = (expiresIn, from = Date.now()) => { + const days = EXPIRES_OPTIONS[String(expiresIn || '').toLowerCase()]; + return days ? new Date(from + days * 24 * 60 * 60 * 1000).toISOString() : null; +}; + +module.exports = ({ cooler }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + + const readAll = async (ssbClient) => readTyped(ssbClient, EMERGENCY_TYPES, { limit: logLimit }); + + const buildIndex = (messages) => { + const tomb = buildValidatedTombstoneSet(messages); + const nodes = new Map(); + const parentOf = new Map(); + const confirms = []; + const updates = []; + for (const m of messages) { + const v = m.value || {}; + const c = v.content; + if (!c || typeof c !== 'object') continue; + if (c.type === 'emergency') { + nodes.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, c }); + if (typeof c.replaces === 'string') parentOf.set(m.key, c.replaces); + } else if (c.type === 'emergencyConfirm' && typeof c.target === 'string') { + confirms.push({ key: m.key, target: c.target, author: v.author, ts: v.timestamp || 0 }); + } else if (c.type === 'emergencyUpdate' && typeof c.target === 'string') { + updates.push({ key: m.key, target: c.target, author: v.author, ts: v.timestamp || 0, text: safeText(c.text), createdAt: c.createdAt || new Date(v.timestamp || 0).toISOString(), replaces: typeof c.replaces === 'string' ? c.replaces : null }); + } + } + const rootOf = (key) => { + const seen = new Set(); + let cur = key; + while (parentOf.has(cur) && !seen.has(cur)) { seen.add(cur); cur = parentOf.get(cur); } + return cur; + }; + const updateParent = new Map(updates.filter(u => u.replaces).map(u => [u.key, u.replaces])); + const updateRootOf = (key) => { + const seen = new Set(); + let cur = key; + while (updateParent.has(cur) && !seen.has(cur)) { seen.add(cur); cur = updateParent.get(cur); } + return cur; + }; + const updateByKey = new Map(updates.map(u => [u.key, u])); + const updateVersions = new Map(); + for (const u of updates) { + const uroot = updateRootOf(u.key); + if (tomb.has(uroot)) continue; + if (!updateVersions.has(uroot)) updateVersions.set(uroot, []); + updateVersions.get(uroot).push(u); + } + const updateItems = []; + for (const [uroot, versions] of updateVersions) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const own = versions.filter(v => v.author === first.author); + const tip = own[own.length - 1]; + const confirmers = Array.from(new Set(confirms.filter(cf => updateByKey.has(cf.target) && updateRootOf(cf.target) === uroot && cf.author !== first.author).map(cf => cf.author))); + updateItems.push({ + id: uroot, key: uroot, tipId: tip.key, target: first.target, author: first.author, + ts: first.ts, editedTs: tip.ts, text: tip.text, createdAt: first.createdAt, updatedAt: tip.createdAt, edited: tip.key !== uroot, + confirmations: confirmers, confirmationCount: confirmers.length, + lastActivityTs: Math.max(tip.ts, ...confirms.filter(cf => updateByKey.has(cf.target) && updateRootOf(cf.target) === uroot).map(cf => cf.ts)) + }); + } + const versionsByRoot = new Map(); + for (const node of nodes.values()) { + const root = rootOf(node.key); + if (tomb.has(root)) continue; + if (!versionsByRoot.has(root)) versionsByRoot.set(root, []); + versionsByRoot.get(root).push(node); + } + const emergencies = new Map(); + for (const [root, versions] of versionsByRoot) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const own = versions.filter(v => v.author === first.author); + const tip = own[own.length - 1]; + const c = tip.c; + const thread = updateItems.filter(u => nodes.has(u.target) && rootOf(u.target) === root && u.author === first.author).sort((a, b) => a.ts - b.ts); + const confirmers = Array.from(new Set([ + ...confirms.filter(cf => nodes.has(cf.target) && rootOf(cf.target) === root && cf.author !== first.author).map(cf => cf.author), + ...thread.flatMap(u => u.confirmations) + ])); + const expiresAt = c.expiresAt || null; + const expired = !!(expiresAt && Date.parse(expiresAt) < Date.now()); + const status = normU(c.status) === 'RESOLVED' ? 'RESOLVED' : (expired ? 'EXPIRED' : 'ACTIVE'); + emergencies.set(root, { + id: root, rootId: root, tipId: tip.key, + title: safeText(c.title), text: safeText(c.text), + media: (() => { const m = safeText(c.text).match(MEDIA_RE); return m ? { kind: m[1], blobId: m[2] } : null; })(), + category: normalizeCategory(c.category), mapUrl: safeText(c.mapUrl), tags: normalizeTags(c.tags), + author: first.author, createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt: c.updatedAt || new Date(tip.ts).toISOString(), ts: tip.ts, lastActivityTs: Math.max(tip.ts, ...thread.map(u => u.lastActivityTs), ...confirms.filter(cf => nodes.has(cf.target) && rootOf(cf.target) === root).map(cf => cf.ts)), + expiresAt, expired, status, resolved: status === 'RESOLVED', + confirmations: confirmers, confirmationCount: confirmers.length, severity: severityOf(confirmers.length), + updates: thread + }); + } + const updatesById = new Map(updateItems.map(u => [u.id, u])); + return { emergencies, rootOf, nodes, updatesById, updateRootOf }; + }; + + const load = async () => { + const ssbClient = await openSsb(); + const idx = memoIndex('emergencies', await readAll(ssbClient), buildIndex); + return { ssbClient, idx }; + }; + + const find = (idx, id) => { + const key = String(id || ''); + if (idx.emergencies.has(key)) return idx.emergencies.get(key); + if (idx.nodes.has(key)) return idx.emergencies.get(idx.rootOf(key)) || null; + return null; + }; + + const publish = (ssbClient, content) => new Promise((resolve, reject) => ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res))); + + return { + CATEGORIES, + SEVERITY_THRESHOLDS, + EXPIRES_OPTIONS, + severityOf, + + async createEmergency({ title, text, category, mapUrl, tags, expiresIn }) { + const ssbClient = await openSsb(); + const cleanTitle = safeText(title); + if (!cleanTitle) throw new Error('Title required'); + const now = new Date().toISOString(); + const content = { + type: 'emergency', + title: cleanTitle, + text: safeText(text), + category: normalizeCategory(category), + mapUrl: safeText(mapUrl), + tags: normalizeTags(tags), + status: 'ACTIVE', + expiresAt: expiresAtFrom(expiresIn), + author: ssbClient.id, + createdAt: now, + updatedAt: now + }; + return publish(ssbClient, content); + }, + + async updateEmergency(id, data = {}) { + const { ssbClient, idx } = await load(); + const emergency = find(idx, id); + if (!emergency) throw new Error('Emergency not found'); + if (String(emergency.author) !== String(ssbClient.id)) throw new Error('Only the author can update'); + const editsContent = ['title', 'text', 'category', 'mapUrl', 'tags', 'expiresIn'].some(k => data[k] !== undefined); + if (editsContent && (emergency.confirmationCount || 0) > 0) throw new Error('Cannot edit an emergency after it has been confirmed'); + const content = { + type: 'emergency', + title: data.title !== undefined ? (safeText(data.title) || emergency.title) : emergency.title, + text: data.text !== undefined ? safeText(data.text) : emergency.text, + category: data.category !== undefined ? normalizeCategory(data.category) : emergency.category, + mapUrl: data.mapUrl !== undefined ? safeText(data.mapUrl) : emergency.mapUrl, + tags: data.tags !== undefined ? normalizeTags(data.tags) : emergency.tags, + status: data.status !== undefined ? (normU(data.status) === 'RESOLVED' ? 'RESOLVED' : 'ACTIVE') : (emergency.resolved ? 'RESOLVED' : 'ACTIVE'), + expiresAt: data.expiresIn !== undefined && data.expiresIn !== 'keep' ? expiresAtFrom(data.expiresIn) : emergency.expiresAt, + author: ssbClient.id, + createdAt: emergency.createdAt, + updatedAt: new Date().toISOString(), + replaces: emergency.tipId + }; + const res = await publish(ssbClient, content); + return { key: res.key, rootId: emergency.id }; + }, + + async resolveEmergency(id) { + return this.updateEmergency(id, { status: 'RESOLVED' }); + }, + + async confirmEmergency(id) { + const { ssbClient, idx } = await load(); + const emergency = find(idx, id); + if (!emergency) throw new Error('Emergency not found'); + if (String(emergency.author) === String(ssbClient.id)) throw new Error('Cannot confirm own emergency'); + if (emergency.confirmations.includes(ssbClient.id)) throw new Error('Already confirmed'); + return publish(ssbClient, { type: 'emergencyConfirm', target: emergency.id, createdAt: new Date().toISOString() }); + }, + + async confirmUpdate(updateId) { + const { ssbClient, idx } = await load(); + const update = idx.updatesById.get(idx.updateRootOf(String(updateId || ''))); + if (!update) throw new Error('Update not found'); + if (String(update.author) === String(ssbClient.id)) throw new Error('Cannot confirm own update'); + if (update.confirmations.includes(ssbClient.id)) throw new Error('Already confirmed'); + return publish(ssbClient, { type: 'emergencyConfirm', target: update.id, createdAt: new Date().toISOString() }); + }, + + async editUpdate(updateId, text) { + const { ssbClient, idx } = await load(); + const update = idx.updatesById.get(idx.updateRootOf(String(updateId || ''))); + if (!update) throw new Error('Update not found'); + if (String(update.author) !== String(ssbClient.id)) throw new Error('Only the author can edit an update'); + if (update.confirmationCount > 0) throw new Error('Cannot edit an update after it has been confirmed'); + const clean = safeText(text); + if (!clean) throw new Error('Text required'); + const res = await publish(ssbClient, { type: 'emergencyUpdate', target: update.target, text: clean, createdAt: new Date().toISOString(), replaces: update.tipId }); + return { key: res.key, rootId: update.id, emergencyId: update.target }; + }, + + async deleteUpdate(updateId) { + const { ssbClient, idx } = await load(); + const update = idx.updatesById.get(idx.updateRootOf(String(updateId || ''))); + if (!update) throw new Error('Update not found'); + if (String(update.author) !== String(ssbClient.id)) throw new Error('Only the author can delete an update'); + await publish(ssbClient, { type: 'tombstone', target: update.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + return { key: update.id, emergencyId: update.target }; + }, + + async addUpdate(id, text) { + const { ssbClient, idx } = await load(); + const emergency = find(idx, id); + if (!emergency) throw new Error('Emergency not found'); + if (String(emergency.author) !== String(ssbClient.id)) throw new Error('Only the author can post updates'); + const clean = safeText(text); + if (!clean) throw new Error('Text required'); + return publish(ssbClient, { type: 'emergencyUpdate', target: emergency.id, text: clean, createdAt: new Date().toISOString() }); + }, + + async deleteEmergency(id) { + const { ssbClient, idx } = await load(); + const emergency = find(idx, id); + if (!emergency) throw new Error('Emergency not found'); + if (String(emergency.author) !== String(ssbClient.id)) throw new Error('Only the author can delete'); + return publish(ssbClient, { type: 'tombstone', target: emergency.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + }, + + async getEmergencyById(id) { + const { idx } = await load(); + return find(idx, id); + }, + + async listAll({ filter = 'all', q = '', viewerId } = {}) { + const { ssbClient, idx } = await load(); + const me = viewerId || ssbClient.id; + let list = Array.from(idx.emergencies.values()); + const f = normU(filter || 'all'); + if (f === 'MINE') list = list.filter(a => String(a.author) === String(me)); + else if (f === 'RECENT') list = list.filter(a => a.lastActivityTs >= Date.now() - RECENT_MS); + else if (f === 'ACTIVE') list = list.filter(a => a.status === 'ACTIVE'); + else if (f === 'RESOLVED') list = list.filter(a => a.status === 'RESOLVED'); + else if (f === 'EXPIRED') list = list.filter(a => a.status === 'EXPIRED'); + else if (['UNVERIFIED', 'LOW', 'MEDIUM', 'HIGH'].includes(f)) list = list.filter(a => a.severity === f); + else if (CATEGORIES.includes(f)) list = list.filter(a => a.category === f); + const needle = safeText(q).toLowerCase(); + if (needle) list = list.filter(a => a.title.toLowerCase().includes(needle) || a.text.toLowerCase().includes(needle) || a.tags.some(t => t.toLowerCase().includes(needle))); + return list.sort((a, b) => b.lastActivityTs - a.lastActivityTs); + }, + + async featured() { + const { idx } = await load(); + const rank = { HIGH: 3, MEDIUM: 2, LOW: 1, UNVERIFIED: 0 }; + const candidates = Array.from(idx.emergencies.values()).filter(a => a.status === 'ACTIVE' && a.severity === 'HIGH'); + candidates.sort((a, b) => (rank[b.severity] - rank[a.severity]) || (b.confirmationCount - a.confirmationCount) || (b.lastActivityTs - a.lastActivityTs)); + const top = candidates[0]; + return top ? { id: top.id, title: top.title, severity: top.severity, category: top.category, confirmationCount: top.confirmationCount, href: `/emergencies/${encodeURIComponent(top.id)}` } : null; + }, + + async resolveRootId(id) { + const { idx } = await load(); + const emergency = find(idx, id); + if (!emergency) throw new Error('Not found'); + return emergency.id; + } + }; +}; diff --git a/src/models/events_model.js b/src/models/events_model.js index 9f5fd96..fb95fb2 100644 --- a/src/models/events_model.js +++ b/src/models/events_model.js @@ -5,9 +5,17 @@ const { truthy, hasAnyInterval, nextOccurrence, upcomingOccurrences } = require( const crypto = require('crypto'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); const { getConfig } = require('../configs/config-manager.js'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); const logLimit = getConfig().ssbLogStream?.limit || 1000; +const EVENT_TYPES = [ + 'event', 'eventOpinion', 'eventAttend', + 'event-invite', 'event-invite-tombstone', + 'event-open-invite', 'event-open-invite-tombstone', + 'tribe-keys', 'tombstone' +]; + module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; @@ -16,10 +24,53 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { const ownCrypto = eventCrypto || tribeCrypto; const lookupKey = (rid) => (ownCrypto && ownCrypto.getKey(rid)) || (tribeCrypto && tribeCrypto.getKey(rid)) || null; + const lookupGen = (rid) => ((ownCrypto && ownCrypto.getGen(rid)) || (tribeCrypto && tribeCrypto.getGen(rid)) || 0); + const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))) - ); + readTyped(ssbClient, EVENT_TYPES, { limit: logLimit }); + + const rotateEventKey = async (rid, attendees) => { + if (!ownCrypto || !tribeCrypto || !rid || !lookupKey(rid)) return; + const ssbClient = await openSsb(); + const ssbKeys = require("../server/node_modules/ssb-keys"); + const newKey = ownCrypto.generateTribeKey(); + const newGen = ownCrypto.addNewKey(rid, newKey); + const memberKeys = {}; + for (const m of new Set([ssbClient.id, ...(Array.isArray(attendees) ? attendees : [])])) { + try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys); } catch (_) {} + } + await new Promise((resolve) => { + ssbClient.publish({ type: "tribe-keys", tribeId: rid, generation: newGen, memberKeys }, () => resolve()); + }); + }; + + const ensureAttendeeKeys = async (ssbClient, messages, events) => { + if (!tribeCrypto || !ownCrypto) return; + const leavesByRoot = new Map(); + for (const m of messages) { + const c = m.value && m.value.content; + if (!c || c.type !== 'eventAttend' || c.on !== false || typeof c.target !== 'string') continue; + if (!leavesByRoot.has(c.target)) leavesByRoot.set(c.target, []); + leavesByRoot.get(c.target).push({ member: m.value.author, ts: Number(m.timestamp || m.value.timestamp || 0) }); + } + const ssbKeys = require("../server/node_modules/ssb-keys"); + for (const ev of events) { + if (!ev || !ev.rid || ev.organizer !== ssbClient.id || !lookupKey(ev.rid)) continue; + const members = Array.isArray(ev.attendees) ? ev.attendees : []; + const plan = ownCrypto.keyPlan({ rootId: ev.rid, ownerId: ev.organizer, gen: lookupGen(ev.rid), members, messages, leaves: leavesByRoot.get(ev.rid) || [] }); + if (plan.rotate) { await rotateEventKey(ev.rid, members); continue; } + if (!plan.missing.length) continue; + const key = lookupKey(ev.rid); + const memberKeys = {}; + for (const m of plan.missing) { + try { memberKeys[m] = tribeCrypto.boxKeyForMember(key, m, ssbKeys); } catch (_) {} + } + if (!Object.keys(memberKeys).length) continue; + await new Promise((resolve) => { + ssbClient.publish({ type: "tribe-keys", tribeId: ev.rid, generation: lookupGen(ev.rid) || 1, memberKeys }, () => resolve()); + }); + } + }; const ingestOwnTribeKeys = async () => { if (!ownCrypto) return; @@ -28,9 +79,11 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { const ssbKeys = require("../server/node_modules/ssb-keys"); const cfg = require("../server/ssb_config"); const msgs = await readAll(ssbClient); + const idx = buildEventIndex(msgs); for (const m of msgs) { const c = m.value && m.value.content; if (!c || c.type !== "tribe-keys") continue; + if (!c.tribeId || (m.value.author !== idx.eventAuthor.get(c.tribeId) && m.value.author !== ssbClient.id)) continue; const memberKeys = c.memberKeys; if (!memberKeys || typeof memberKeys !== "object") continue; const boxed = memberKeys[ssbClient.id]; @@ -98,45 +151,18 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { const buildEventIndex = (messages) => { const eventAuthor = new Map(); - const eventReplaces = new Map(); + const entries = []; for (const m of messages) { const c = m.value && m.value.content; if (!c || c.type !== 'event') continue; eventAuthor.set(m.key, m.value.author); - if (c.replaces) eventReplaces.set(m.key, c.replaces); + entries.push({ key: m.key, author: m.value.author, seq: m.value.sequence || 0, ts: m.value.timestamp || m.timestamp || 0, replaces: typeof c.replaces === 'string' ? c.replaces : null, claimedAuthor: typeof c.organizer === 'string' ? c.organizer : null }); } - for (const [key, target] of Array.from(eventReplaces.entries())) { - if (!eventAuthor.has(target)) { eventReplaces.delete(key); continue; } - if (eventAuthor.get(target) !== eventAuthor.get(key)) { eventReplaces.delete(key); eventAuthor.delete(key); } - } - const strictNext = new Map(); - for (const [key, target] of eventReplaces) { - if (eventAuthor.get(target) === eventAuthor.get(key)) strictNext.set(target, key); - } - const rootOf = (id) => { - let cur = id; - const seen = new Set(); - while (eventReplaces.has(cur) && eventAuthor.has(eventReplaces.get(cur))) { - if (seen.has(cur)) break; - seen.add(cur); - cur = eventReplaces.get(cur); - } - return cur; - }; - const contentTipOf = (root) => { - let cur = root; - const seen = new Set(); - while (strictNext.has(cur)) { - if (seen.has(cur)) break; - seen.add(cur); - cur = strictNext.get(cur); - } - return cur; - }; - return { eventAuthor, eventReplaces, strictNext, rootOf, contentTipOf }; + const vg = versionGroups(entries); + return { eventAuthor, vg, rootOf: vg.rootOf, contentTipOf: vg.tipOf }; }; - const collectCollab = (messages) => { + const collectCollab = (messages, rootOf = (id) => id) => { const opinions = new Map(); const attend = new Map(); for (const m of messages) { @@ -144,13 +170,14 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { if (!c || typeof c.target !== 'string') continue; const author = m.value.author; const ts = (m.value && m.value.timestamp) || 0; + const target = rootOf(c.target); if (c.type === 'eventOpinion' && typeof c.category === 'string') { - if (!opinions.has(c.target)) opinions.set(c.target, new Map()); - const byAuthor = opinions.get(c.target); + if (!opinions.has(target)) opinions.set(target, new Map()); + const byAuthor = opinions.get(target); if (!byAuthor.has(author)) byAuthor.set(author, c.category); } else if (c.type === 'eventAttend') { - if (!attend.has(c.target)) attend.set(c.target, new Map()); - const byAuthor = attend.get(c.target); + if (!attend.has(target)) attend.set(target, new Map()); + const byAuthor = attend.get(target); const prev = byAuthor.get(author); if (!prev || ts >= prev.ts) byAuthor.set(author, { on: c.on !== false, ts }); } @@ -285,23 +312,28 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { const ssbClient = await openSsb(); const rid = await this.resolveRootId(eventId).catch(() => eventId); const messages = await readAll(ssbClient); + const authorOf = new Map(); + for (const m of messages) if (m && m.key && m.value) authorOf.set(m.key, m.value.author); + const eventAuthor = authorOf.get(rid); + if (!eventAuthor) return null; const markerTomb = new Set(); const invTomb = new Set(); for (const m of messages) { const c = m.value && m.value.content; - if (!c) continue; - if (c.type === 'event-open-invite-tombstone' && typeof c.target === 'string') markerTomb.add(c.target); - if (c.type === 'event-invite-tombstone' && typeof c.target === 'string') invTomb.add(c.target); + if (!c || typeof c.target !== 'string' || authorOf.get(c.target) !== m.value.author) continue; + if (c.type === 'event-open-invite-tombstone') markerTomb.add(c.target); + if (c.type === 'event-invite-tombstone') invTomb.add(c.target); } let best = null; for (const m of messages) { const c = m.value && m.value.content; if (!c || c.type !== 'event-open-invite' || c.v !== 1) continue; if (c.target !== rid || typeof c.code !== 'string') continue; + if (m.value.author !== eventAuthor) continue; if (markerTomb.has(m.key)) continue; if (c.inviteKey && invTomb.has(c.inviteKey)) continue; const ts = (m.value && m.value.timestamp) || 0; - if (!best || ts > best.ts) best = { code: c.code, by: c.by || m.value.author, markerKey: m.key, inviteKey: c.inviteKey || null, ts }; + if (!best || ts > best.ts) best = { code: c.code, by: m.value.author, markerKey: m.key, inviteKey: c.inviteKey || null, ts }; } return best ? { code: best.code, by: best.by, markerKey: best.markerKey, inviteKey: best.inviteKey } : null; }, @@ -346,6 +378,16 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { if (rec.by !== userId && organizer !== userId) throw new Error("Not allowed to remove this invitation"); await new Promise((resolve, reject) => ssbClient.publish({ type: 'event-open-invite-tombstone', target: rec.markerKey, ts: new Date().toISOString() }, (err) => err ? reject(err) : resolve())); if (rec.inviteKey) await new Promise((resolve, reject) => ssbClient.publish({ type: 'event-invite-tombstone', target: rec.inviteKey, ts: new Date().toISOString() }, (err) => err ? reject(err) : resolve())); + if (organizer === userId) { + const rid = await this.resolveRootId(eventId); + const messages = await readAll(ssbClient); + const idx = buildEventIndex(messages); + const tip = idx.contentTipOf(rid); + const raw = messages.find(x => x.key === tip); + const rc = raw && raw.value && raw.value.content; + const dec = rc && rc.encryptedPayload ? decryptEventContent(rc, rid) : rc; + if (dec && !dec._undecryptable) await rotateEventKey(rid, aggregateCollab(dec, rid, collectCollab(messages, idx.rootOf)).attendees); + } }, async joinByInvite(code) { @@ -390,14 +432,13 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { const status = deriveStatus(c); if (status === 'CLOSED') throw new Error("Cannot attend a closed event"); - const collab = collectCollab(messages); + const collab = collectCollab(messages, idx.rootOf); const agg = aggregateCollab(c, rid, collab); const isAttending = agg.attendees.includes(userId); const isLeaving = isAttending; const on = !isAttending; const isPrivate = normalizePrivacy(c.isPublic) === 'private'; - const isOrganizer = c.organizer === userId; const result = await new Promise((resolve, reject) => { ssbClient.publish({ type: 'eventAttend', target: rid, on, createdAt: new Date().toISOString() }, (err2, res2) => err2 ? reject(err2) : resolve(res2)); @@ -419,36 +460,20 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { } catch (_) {} } - if (isPrivate && isLeaving && !isOrganizer && ownCrypto && tribeCrypto) { - try { - const remaining = agg.attendees.filter(a => a !== userId); - const newKey = ownCrypto.generateTribeKey(); - const newGen = ownCrypto.addNewKey(rid, newKey); - const ssbKeys = require("../server/node_modules/ssb-keys"); - const memberKeys = {}; - for (const m of remaining) { - try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys); } catch (_) {} - } - if (Object.keys(memberKeys).length) { - await new Promise((resolve) => { - ssbClient.publish({ type: "tribe-keys", tribeId: rid, generation: newGen, memberKeys }, () => resolve()); - }); - } - } catch (_) {} - } - return result; }, async deleteEventById(eventId) { const ssbClient = await openSsb(); const userId = await me(); - const ev = await new Promise((res, rej) => ssbClient.get(eventId, (err, ev) => err || !ev || !ev.content ? rej(new Error("Error retrieving event")) : res(ev))); - const rid = await this.resolveRootId(eventId); + const idx = buildEventIndex(await readAll(ssbClient)); + const rid = idx.rootOf(eventId); + const tipId = idx.contentTipOf(rid); + const ev = await new Promise((res, rej) => ssbClient.get(tipId, (err, ev) => err || !ev || !ev.content ? rej(new Error("Error retrieving event")) : res(ev))); const c = ev.content && ev.content.encryptedPayload ? decryptEventContent(ev.content, rid) : ev.content; if (c && c._undecryptable) throw new Error("Event is encrypted and cannot be decrypted"); - if (c.organizer !== userId) throw new Error("Only the organizer can delete this event"); - const tombstone = { type: 'tombstone', target: eventId, deletedAt: new Date().toISOString(), author: userId }; + if (c.organizer !== userId || ev.author !== userId) throw new Error("Only the organizer can delete this event"); + const tombstone = { type: 'tombstone', target: tipId, deletedAt: new Date().toISOString(), author: userId }; return new Promise((resolve, reject) => { ssbClient.publish(tombstone, (err, res) => err ? reject(err) : resolve(res)); }); @@ -457,15 +482,19 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { async getEventById(eventId) { const ssbClient = await openSsb(); const messages = await readAll(ssbClient); - const idx = buildEventIndex(messages); + const idx = memoIndex('events', messages, buildEventIndex); const rid = idx.rootOf(eventId); + if (idx.vg.isForeignCopy(rid)) throw new Error("Error retrieving event"); const contentTip = idx.contentTipOf(rid); + const tombstoned = buildValidatedTombstoneSet(messages); + if (tombstoned.has(rid) || tombstoned.has(contentTip)) throw new Error("Error retrieving event"); const msg = await new Promise((res, rej) => ssbClient.get(contentTip, (err, msg) => err || !msg || !msg.content ? rej(new Error("Error retrieving event")) : res(msg))); const c = msg.content && msg.content.encryptedPayload ? decryptEventContent(msg.content, rid) : msg.content; if (c && c._undecryptable) throw new Error("Event is encrypted and cannot be decrypted with available keys"); + if (c && c.organizer && c.organizer !== msg.author) throw new Error("Error retrieving event"); const status = deriveStatus(c); - const agg = aggregateCollab(c, rid, collectCollab(messages)); + const agg = aggregateCollab(c, rid, collectCollab(messages, idx.rootOf)); return { id: contentTip, @@ -502,11 +531,13 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { async updateEventById(eventId, updatedData) { const ssbClient = await openSsb(); const userId = await me(); - const ev = await new Promise((res, rej) => ssbClient.get(eventId, (err, ev) => err || !ev || !ev.content ? rej(new Error("Error retrieving event")) : res(ev))); - const rid = await this.resolveRootId(eventId); + const idx = buildEventIndex(await readAll(ssbClient)); + const rid = idx.rootOf(eventId); + const tipId = idx.contentTipOf(rid); + const ev = await new Promise((res, rej) => ssbClient.get(tipId, (err, ev) => err || !ev || !ev.content ? rej(new Error("Error retrieving event")) : res(ev))); const c = ev.content && ev.content.encryptedPayload ? decryptEventContent(ev.content, rid) : ev.content; if (c && c._undecryptable) throw new Error("Event is encrypted and cannot be decrypted"); - if (c.organizer !== userId) throw new Error("Only the organizer can update this event"); + if (c.organizer !== userId || ev.author !== userId) throw new Error("Only the organizer can update this event"); const status = deriveStatus(c); if (status === 'CLOSED') throw new Error("Cannot edit a closed event"); @@ -541,7 +572,7 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { intervalYearly: updatedData.intervalYearly !== undefined ? truthy(updatedData.intervalYearly) : truthy(c.intervalYearly), recurrenceUntil: updatedData.recurrenceUntil !== undefined ? String(updatedData.recurrenceUntil || '').trim() : (c.recurrenceUntil || ''), updatedAt: new Date().toISOString(), - replaces: eventId + replaces: tipId }; const wasPrivate = normalizePrivacy(c.isPublic) === 'private'; @@ -591,7 +622,7 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { const ev = await new Promise((res, rej) => ssbClient.get(contentTip, (err, m) => (err || !m || !m.content) ? rej(new Error('Event not found')) : res(m))); const c = ev.content && ev.content.encryptedPayload ? decryptEventContent(ev.content, rid) : ev.content; if (c && c._undecryptable) throw new Error('Event is encrypted and cannot be decrypted'); - const agg = aggregateCollab(c, rid, collectCollab(messages)); + const agg = aggregateCollab(c, rid, collectCollab(messages, idx.rootOf)); if (agg.opinions_inhabitants.includes(userId)) throw new Error('Already opined'); return new Promise((res, rej) => ssbClient.publish({ type: 'eventOpinion', target: rid, category, createdAt: new Date().toISOString() }, (err, result) => err ? rej(err) : res(result))); }, @@ -599,79 +630,67 @@ module.exports = ({ cooler, tribeCrypto, eventCrypto, tribesModel }) => { async listAll(author = null, filter = 'all') { const ssbClient = await openSsb(); const userId = await me(); - return new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, results) => { - if (err) return reject(new Error("Error listing events: " + err.message)); - const tombstoned = buildValidatedTombstoneSet(results); - const idx = buildEventIndex(results); - const collab = collectCollab(results); + const results = await readAll(ssbClient); + const tombstoned = buildValidatedTombstoneSet(results); + const idx = buildEventIndex(results); + const collab = collectCollab(results, idx.rootOf); - const roots = new Set(); - for (const r of results) { - const rawC = r.value && r.value.content; - if (!rawC || rawC.type !== 'event') continue; - if (!idx.eventAuthor.has(r.key)) continue; - roots.add(idx.rootOf(r.key)); - } + const byKey = new Map(results.map(x => [x.key, x])); + const byRoot = new Map(); + for (const rid of idx.vg.roots) { + if (tombstoned.has(rid)) continue; + const contentTip = idx.contentTipOf(rid); + if (tombstoned.has(contentTip)) continue; + const r = byKey.get(contentTip); + if (!r) continue; + const rawC = r.value && r.value.content; + if (!rawC) continue; + const c = rawC.encryptedPayload ? decryptEventContent(rawC, rid) : rawC; + if (!c || c._undecryptable) continue; + if (author && c.organizer !== author) continue; - const byRoot = new Map(); - for (const rid of roots) { - if (tombstoned.has(rid)) continue; - const contentTip = idx.contentTipOf(rid); - if (tombstoned.has(contentTip)) continue; - const r = results.find(x => x.key === contentTip); - if (!r) continue; - const rawC = r.value && r.value.content; - if (!rawC) continue; - const c = rawC.encryptedPayload ? decryptEventContent(rawC, rid) : rawC; - if (!c || c._undecryptable) continue; - if (author && c.organizer !== author) continue; + const status = deriveStatus(c); + const agg = aggregateCollab(c, rid, collab); - const status = deriveStatus(c); - const agg = aggregateCollab(c, rid, collab); + byRoot.set(rid, { + id: contentTip, + title: c.title || '', + description: c.description || '', + date: c.date || '', + location: c.location || '', + price: c.price || 0, + url: c.url || '', + attendees: agg.attendees, + tags: Array.isArray(c.tags) ? c.tags.filter(Boolean) : [], + createdAt: c.createdAt || new Date().toISOString(), + organizer: c.organizer || '', + status, + isPublic: normalizePrivacy(c.isPublic), + mapUrl: c.mapUrl || "", + images: normalizeImages(c.images), + video: normalizeVideo(c.video), + intervalWeekly: truthy(c.intervalWeekly), + intervalMonthly: truthy(c.intervalMonthly), + intervalYearly: truthy(c.intervalYearly), + recurrenceUntil: c.recurrenceUntil || '', + recurring: hasAnyInterval(truthy(c.intervalWeekly), truthy(c.intervalMonthly), truthy(c.intervalYearly)) && !!c.recurrenceUntil, + nextDate: effectiveDate(c), + encrypted: normalizePrivacy(c.isPublic) === 'private', + opinions: agg.opinions, + opinions_inhabitants: agg.opinions_inhabitants + }); + } - byRoot.set(rid, { - id: contentTip, - title: c.title || '', - description: c.description || '', - date: c.date || '', - location: c.location || '', - price: c.price || 0, - url: c.url || '', - attendees: agg.attendees, - tags: Array.isArray(c.tags) ? c.tags.filter(Boolean) : [], - createdAt: c.createdAt || new Date().toISOString(), - organizer: c.organizer || '', - status, - isPublic: normalizePrivacy(c.isPublic), - mapUrl: c.mapUrl || "", - images: normalizeImages(c.images), - video: normalizeVideo(c.video), - intervalWeekly: truthy(c.intervalWeekly), - intervalMonthly: truthy(c.intervalMonthly), - intervalYearly: truthy(c.intervalYearly), - recurrenceUntil: c.recurrenceUntil || '', - recurring: hasAnyInterval(truthy(c.intervalWeekly), truthy(c.intervalMonthly), truthy(c.intervalYearly)) && !!c.recurrenceUntil, - nextDate: effectiveDate(c), - encrypted: normalizePrivacy(c.isPublic) === 'private', - opinions: agg.opinions, - opinions_inhabitants: agg.opinions_inhabitants - }); - } + try { await ensureAttendeeKeys(ssbClient, results, [...byRoot].filter(([rid, e]) => e.encrypted && idx.eventAuthor.get(rid) === userId).map(([rid, e]) => ({ rid, organizer: userId, attendees: e.attendees }))); } catch (_) {} - let out = Array.from(byRoot.values()); - out = dedupeBy(out, e => e.title ? [norm(e.organizer), norm(e.title), norm(e.date)].join('|') : null); + let out = Array.from(byRoot.values()); + out = dedupeBy(out, e => e.title ? [norm(e.organizer), norm(e.title), norm(e.date)].join('|') : null); - if (filter === 'mine') out = out.filter(e => e.organizer === userId); - if (filter === 'open') out = out.filter(e => String(e.status).toUpperCase() === 'OPEN'); - if (filter === 'closed') out = out.filter(e => String(e.status).toUpperCase() === 'CLOSED'); + if (filter === 'mine') out = out.filter(e => e.organizer === userId); + if (filter === 'open') out = out.filter(e => String(e.status).toUpperCase() === 'OPEN'); + if (filter === 'closed') out = out.filter(e => String(e.status).toUpperCase() === 'CLOSED'); - resolve(out); - }) - ); - }); + return out; } }; }; diff --git a/src/models/exportmode_model.js b/src/models/exportmode_model.js index 52b20cb..d3d31ba 100644 --- a/src/models/exportmode_model.js +++ b/src/models/exportmode_model.js @@ -1,15 +1,13 @@ const os = require('os'); const fs = require('fs'); const path = require('path'); -const archiver = require('../server/node_modules/archiver'); module.exports = { exportSSB: async (outputPath) => { try { - // se exporta la cuenta activa, no siempre la de por defecto - const ssbPath = require('../server/ssb_config').path; - const output = fs.createWriteStream(outputPath); - const archive = archiver('zip', { + const ssbPath = require('../configs/state-manager').ssbDir(); + const output = fs.createWriteStream(outputPath, { mode: 0o600 }); + const archive = require('../server/node_modules/archiver')('zip', { zlib: { level: 9 } }); archive.pipe(output); @@ -22,7 +20,8 @@ module.exports = { const filePath = path.join(dirPath, file); const stat = fs.statSync(filePath); - if (file === 'secret') { + const rel = path.relative(ssbPath, filePath); + if (file === 'secret' || file.startsWith('secret.') || file === 'oasis-config.json' || rel === path.join('oasis', 'keys') || rel.startsWith(path.join('oasis', 'keys') + path.sep)) { return; } diff --git a/src/models/favorites_model.js b/src/models/favorites_model.js index 1eb5524..43d8334 100644 --- a/src/models/favorites_model.js +++ b/src/models/favorites_model.js @@ -15,7 +15,7 @@ const toTs = (d) => { return Number.isFinite(t) ? t : 0; }; -module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, videosModel, mapsModel, padsModel, chatsModel, calendarsModel, torrentsModel, marketModel, shopsModel, eventsModel, tasksModel, reportsModel, votesModel, jobsModel, housingModel, projectsModel, transfersModel, forumModel, blogsModel, pollsModel }) => { +module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, videosModel, mapsModel, padsModel, roomsModel, chatsModel, calendarsModel, torrentsModel, filesModel, marketModel, shopsModel, eventsModel, tasksModel, reportsModel, votesModel, jobsModel, housingModel, projectsModel, transfersModel, forumModel, blogsModel, pollsModel, schoolModel, wikiModel, emergenciesModel, mailingModel, logisticsModel, podcastsModel, campaignsModel }) => { const kindConfig = { audios: { base: "/audios/", @@ -45,6 +45,34 @@ module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, vi base: "/pads/", getById: getFn(padsModel, ["getPadById", "getById"]) }, + rooms: { + base: "/rooms/", + getById: getFn(roomsModel, ["getRoomById"]) + }, + wiki: { + base: "/wiki/", + getById: getFn(wikiModel, ["getPage"]) + }, + emergencies: { + base: "/emergencies/", + getById: getFn(emergenciesModel, ["getEmergencyById"]) + }, + mailing: { + base: "/mailing/", + getById: getFn(mailingModel, ["getListById"]) + }, + logistics: { + base: "/logistics/", + getById: getFn(logisticsModel, ["getRouteById"]) + }, + podcasts: { + base: "/podcasts/", + getById: getFn(podcastsModel, ["getAnyById"]) + }, + campaigns: { + base: "/campaigns/", + getById: getFn(campaignsModel, ["getCampaignById"]) + }, chats: { base: "/chats/", getById: getFn(chatsModel, ["getChatById", "getById"]) @@ -57,6 +85,10 @@ module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, vi base: "/torrents/", getById: getFn(torrentsModel, ["getTorrentById", "getById"]) }, + files: { + base: "/files/", + getById: getFn(filesModel, ["getFileById", "getById"]) + }, market: { base: "/market/", getById: getFn(marketModel, ["getItemById", "getById"]) @@ -77,6 +109,10 @@ module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, vi base: "/reports/", getById: getFn(reportsModel, ["getReportById", "getById"]) }, + school: { + base: "/school/course/", + getById: getFn(schoolModel, ["getCourseById", "getById"]) + }, votes: { base: "/votes/", getById: getFn(votesModel, ["getVoteById", "getById"]) @@ -115,7 +151,7 @@ module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, vi } }; - const kindOrder = ["audios", "blogs", "bookmarks", "calendars", "chats", "documents", "events", "forum", "housing", "images", "jobs", "maps", "market", "pads", "polls", "projects", "reports", "shopProducts", "shops", "tasks", "torrents", "transfers", "videos", "votes"]; + const kindOrder = ["audios", "blogs", "bookmarks", "calendars", "campaigns", "chats", "documents", "events", "files", "forum", "housing", "images", "jobs", "logistics", "mailing", "maps", "market", "pads", "podcasts", "polls", "projects", "reports", "rooms", "school", "shopProducts", "shops", "emergencies", "tasks", "torrents", "transfers", "videos", "votes", "wiki"]; const hydrateKind = async (kind, ids) => { const cfg = kindConfig[kind]; @@ -172,9 +208,11 @@ module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, vi for (const k of kindOrder) counts[k] = (byKind[k] || []).length; const recentFlat = flat - .slice() + .filter((x) => (toTs(x.updatedAt) || toTs(x.createdAt) || 0) >= Date.now() - 86400000) .sort((a, b) => (toTs(b.updatedAt) || toTs(b.createdAt)) - (toTs(a.updatedAt) || toTs(a.createdAt))); + counts.recent = recentFlat.length; + return { byKind, flat, recentFlat, counts }; }; diff --git a/src/models/fediverse_model.js b/src/models/fediverse_model.js index a8e1e0d..a61bee1 100644 --- a/src/models/fediverse_model.js +++ b/src/models/fediverse_model.js @@ -1,8 +1,9 @@ const fs = require('fs'); const path = require('path'); +const { Readable } = require('stream'); const { getConfig, saveConfig } = require('../configs/config-manager.js'); -const ACCOUNTS_PATH = path.join(__dirname, '..', 'configs', 'fediverse-accounts.json'); +const ACCOUNTS_PATH = require('../configs/state-manager').statePath('fediverse-accounts.json'); const FETCH_TIMEOUT_MS = 8000; const TIMELINE_CACHE_MS = 60 * 1000; const TIMELINE_LIMIT = 40; @@ -18,9 +19,10 @@ const spoofHeaders = (accept) => ({ }); const isPrivateHost = (hostname) => { - const h = String(hostname || '').toLowerCase(); + let h = String(hostname || '').toLowerCase().trim().replace(/^\[|\]$/g, ''); if (!h) return true; - if (h === 'localhost' || h.endsWith('.localhost') || h.endsWith('.local')) return true; + if (h.startsWith('::ffff:')) h = h.slice(7); + if (h === 'localhost' || h.endsWith('.localhost') || h.endsWith('.local') || h === '0.0.0.0' || h === '::') return true; if (h === '::1' || h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) return true; const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/); if (m) { @@ -34,6 +36,89 @@ const isPrivateHost = (hostname) => { return false; }; + +const TG_TIMEOUT_MS = 20000; +const TG_MEDIA_MAX_BYTES = 25 * 1024 * 1024; +const PROXY_MEDIA_MAX_BYTES = TG_MEDIA_MAX_BYTES; +const networkPaused = () => { + if (process.env.OASIS_NETWORK_PAUSED === '1') return true; + try { return require('../configs/config-manager.js').getConfig().networkPaused === true; } catch (_) { return true; } +}; +const netFetch = (url, opts) => networkPaused() ? Promise.reject(new Error('Network is paused')) : fetch(url, opts); +const publicOnlyLookup = (hostname, options, callback) => { + require('dns').lookup(hostname, { ...(options || {}), all: true }, (err, addresses) => { + if (err) return callback(err); + const list = Array.isArray(addresses) ? addresses : []; + if (!list.length || list.some(a => isPrivateHost(a.address))) return callback(Object.assign(new Error('Refusing a private address'), { code: 'EPRIVATE' })); + if (options && options.all) return callback(null, list); + callback(null, list[0].address, list[0].family); + }); +}; +let publicClient; +const publicFetch = (url, opts) => { + if (networkPaused()) return Promise.reject(new Error('Network is paused')); + if (publicClient === undefined) { + try { + const { Agent, fetch: clientFetch } = require('../server/node_modules/undici'); + publicClient = { fetch: clientFetch, dispatcher: new Agent({ connect: { lookup: publicOnlyLookup } }) }; + } catch (_) { publicClient = null; } + } + return publicClient ? publicClient.fetch(url, { ...opts, dispatcher: publicClient.dispatcher }) : fetch(url, opts); +}; +const readCapped = async (res, max) => { + if (Number(res.headers.get('content-length') || 0) > max) return null; + if (!res.body || typeof res.body.getReader !== 'function') { + const ab = await res.arrayBuffer(); + return ab.byteLength > max ? null : Buffer.from(ab); + } + const reader = res.body.getReader(); + const parts = []; + let total = 0; + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + total += value.length; + if (total > max) { try { await reader.cancel(); } catch (_) {} return null; } + parts.push(Buffer.from(value)); + } + return Buffer.concat(parts); +}; +const TG_MEDIA_CACHE_MAX = 60; +const TG_LOGIN_TTL_MS = 10 * 60 * 1000; +let gram = null; +const loadGram = () => { + if (gram) return gram; + const { TelegramClient, Api } = require('../server/node_modules/telegram'); + const { StringSession } = require('../server/node_modules/telegram/sessions'); + const { Logger, LogLevel } = require('../server/node_modules/telegram/extensions/Logger'); + gram = { TelegramClient, Api, StringSession, Logger, LogLevel }; + return gram; +}; +const withTimeout = (promise, ms = TG_TIMEOUT_MS) => new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error('telegramErrTimeout')), ms); + Promise.resolve(promise).then((v) => { clearTimeout(timer); resolve(v); }, (e) => { clearTimeout(timer); reject(e); }); +}); +const tgDisplayName = (u) => { + if (!u) return ''; + if (u.title) return String(u.title); + const full = [u.firstName, u.lastName].filter(Boolean).join(' ').trim(); + return full || (u.username ? `@${u.username}` : ''); +}; +const tgErrCode = (err) => { + if (err && err.message === 'peerErrPaused') return 'peerErrPaused'; + const m = String((err && (err.errorMessage || err.message)) || '').toUpperCase(); + if (m.includes('PHONE_CODE_INVALID') || m.includes('PHONE_CODE_EMPTY')) return 'telegramErrCode'; + if (m.includes('PHONE_CODE_EXPIRED')) return 'telegramErrCodeExpired'; + if (m.includes('PASSWORD_HASH_INVALID')) return 'telegramErrPassword'; + if (m.includes('PHONE_NUMBER_INVALID') || m.includes('PHONE_NUMBER_BANNED')) return 'telegramErrPhone'; + if (m.includes('API_ID_INVALID') || m.includes('API_ID_PUBLISHED_FLOOD')) return 'telegramErrApi'; + if (m.includes('FLOOD')) return 'telegramErrFlood'; + if (m.includes('TELEGRAMERRTIMEOUT')) return 'telegramErrTimeout'; + if (m.includes('AUTH_KEY') || m.includes('SESSION_REVOKED') || m.includes('USER_DEACTIVATED') || m.includes('AUTH_USER_CANCEL')) return 'telegramErrAuth'; + return 'telegramErrConnect'; +}; +const tgHardError = (code) => code === 'telegramErrPhone' || code === 'telegramErrApi' || code === 'telegramErrFlood' || code === 'telegramErrAuth' || code === 'telegramErrConnect' || code === 'telegramErrTimeout'; + module.exports = ({ isPublic } = {}) => { const cache = new Map(); let mediaHosts = new Set(); @@ -82,7 +167,7 @@ module.exports = ({ isPublic } = {}) => { const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const headers = Object.assign(spoofHeaders('application/json'), { Authorization: `Bearer ${token}` }, opts.headers || {}); - const res = await fetch(`${instance}${pathName}`, { ...opts, headers, signal: controller.signal }); + const res = await netFetch(`${instance}${pathName}`, { ...opts, headers, signal: controller.signal }); return res; } finally { clearTimeout(timer); @@ -287,8 +372,15 @@ module.exports = ({ isPublic } = {}) => { if (!file || !file.filepath) return null; let buf; try { buf = fs.readFileSync(file.filepath); } catch (_) { throw new Error('fediverseErrMedia'); } + return api.uploadMediaBuffer({ buffer: buf, mimetype: file.mimetype, filename: file.originalFilename }); + }, + + async uploadMediaBuffer({ buffer, mimetype, filename } = {}) { + const m = getMastodon(); + if (!m) throw new Error('fediverseErrAuth'); + if (!buffer || !buffer.length) return null; const fd = new FormData(); - fd.append('file', new Blob([buf], { type: file.mimetype || 'application/octet-stream' }), file.originalFilename || 'upload'); + fd.append('file', new Blob([buffer], { type: mimetype || 'application/octet-stream' }), filename || 'upload'); let res; try { res = await apiFetch(m.instance, m.token, '/api/v2/media', { method: 'POST', body: fd }); @@ -361,6 +453,656 @@ module.exports = ({ isPublic } = {}) => { favourite(id) { return this.action(id, 'favourite'); }, unfavourite(id) { return this.action(id, 'unfavourite'); }, + telegram: (() => { + const getTg = () => readStore().telegram || null; + let client = null; + let pending = null; + const entities = new Map(); + const messages = new Map(); + const mediaCache = new Map(); + const avatarCache = new Map(); + const dialogCache = { ts: 0, value: null }; + + const killClient = async (c) => { + if (!c) return; + try { await withTimeout(c.destroy(), 8000); } catch (_) { try { await c.disconnect(); } catch (__) {} } + }; + const newClient = (sessionStr, apiId, apiHash) => { + const { TelegramClient, StringSession, Logger, LogLevel } = loadGram(); + return new TelegramClient(new StringSession(sessionStr || ''), Number(apiId), String(apiHash), { + connectionRetries: 3, + baseLogger: new Logger(LogLevel.NONE) + }); + }; + const getClient = async () => { + const t = getTg(); + if (!t || !t.session) throw new Error('telegramErrAuth'); + if (networkPaused()) { + const c = client; + client = null; + await killClient(c); + throw new Error('peerErrPaused'); + } + if (client && client.connected) return client; + if (!client) client = newClient(t.session, t.apiId, t.apiHash); + await withTimeout(client.connect()); + return client; + }; + const rememberDialogs = (list) => { + for (const d of list) if (d && d.entity) entities.set(String(d.id), d.entity); + }; + const resolveEntity = async (c, id) => { + const key = String(id); + if (entities.has(key)) return entities.get(key); + try { rememberDialogs(await withTimeout(c.getDialogs({ limit: TIMELINE_LIMIT }))); } catch (_) {} + if (entities.has(key)) return entities.get(key); + const ent = await withTimeout(c.getEntity(Number(key))); + entities.set(key, ent); + return ent; + }; + const peerKind = (d) => (d.isUser ? 'user' : d.isChannel ? 'channel' : 'group'); + const kindOfEntity = (ent) => { + const { Api } = loadGram(); + if (ent instanceof Api.User) return 'user'; + if (ent instanceof Api.Channel) return ent.megagroup ? 'group' : 'channel'; + return 'group'; + }; + const mediaInfo = (msg) => { + const { Api } = loadGram(); + const m = msg && msg.media; + if (!m) return null; + if (m instanceof Api.MessageMediaPhoto) return { kind: 'photo', mime: 'image/jpeg', size: 0, name: '' }; + if (m instanceof Api.MessageMediaDocument && m.document && m.document.mimeType !== undefined) { + const mime = String(m.document.mimeType || 'application/octet-stream'); + const size = Number(m.document.size || 0); + const kind = mime.startsWith('image/') ? 'photo' : mime.startsWith('video/') ? 'video' : mime.startsWith('audio/') ? 'audio' : 'file'; + let name = ''; + for (const attr of (m.document.attributes || [])) if (attr && attr.fileName) name = String(attr.fileName); + return { kind, mime, size, name }; + } + return { kind: 'other', mime: '', size: 0, name: '' }; + }; + const toIso = (unix) => (unix ? new Date(Number(unix) * 1000).toISOString() : ''); + const mapMessage = (msg, chatId, senderNames) => { + const info = mediaInfo(msg); + const sid = msg.senderId ? String(msg.senderId) : ''; + return { + id: Number(msg.id), + chatId: String(chatId), + text: String(msg.message || ''), + date: toIso(msg.date), + out: msg.out === true, + senderId: sid, + senderName: senderNames.get(sid) || '', + media: info && info.kind !== 'other' && info.size <= TG_MEDIA_MAX_BYTES + ? { ...info, url: `/fediverse/telegram/media/${encodeURIComponent(String(chatId))}/${encodeURIComponent(String(msg.id))}` } + : null + }; + }; + const trimCache = (map) => { + while (map.size > TG_MEDIA_CACHE_MAX) map.delete(map.keys().next().value); + }; + const dropPending = async () => { + if (pending && pending.client && pending.client !== client) await killClient(pending.client); + pending = null; + }; + const waitStep = async (p, prevStep, ms = 25000) => { + const started = Date.now(); + while (Date.now() - started < ms) { + if (p.done || p.error || p.step !== prevStep || p.awaiting) return; + await sleep(200); + } + }; + + return { + hasAccount() { return !!(getTg() && getTg().session); }, + getAccount() { + const t = getTg(); + if (!t || !t.session) return null; + return { + network: 'telegram', + username: t.username || '', + displayName: t.displayName || t.username || t.phone || '', + phone: t.phone || '', + avatar: '/fediverse/telegram/avatar/me' + }; + }, + async getAccountStats() { + if (!this.hasAccount()) return null; + const data = await this.getDialogs(); + if (!data || data.error) return null; + return { + chats: data.dialogs.length, + unread: data.dialogs.reduce((sum, d) => sum + (Number(d.unread) || 0), 0) + }; + }, + loginState() { + if (!pending) return null; + if (Date.now() - pending.startedAt > TG_LOGIN_TTL_MS && !pending.done) { dropPending(); return null; } + return { step: pending.step, error: pending.error || '', done: pending.done === true }; + }, + async beginLogin({ apiId, apiHash, phone }) { + if (isPublic) throw new Error('fediverseErrPublic'); + const id = Number(String(apiId || '').trim()); + const hash = String(apiHash || '').trim(); + const ph = String(phone || '').trim(); + if (!id || !hash || !ph) throw new Error('telegramErrMissing'); + await dropPending(); + if (networkPaused()) throw new Error('peerErrPaused'); + const c = newClient('', id, hash); + try { await withTimeout(c.connect()); } catch (err) { throw new Error(tgErrCode(err)); } + const p = { client: c, apiId: id, apiHash: hash, phone: ph, step: 'sending', error: '', done: false, awaiting: false, startedAt: Date.now(), resolveCode: null, resolvePassword: null }; + pending = p; + p.promise = c.start({ + phoneNumber: async () => ph, + phoneCode: async () => new Promise((resolve) => { p.step = 'code'; p.awaiting = true; p.resolveCode = resolve; }), + password: async () => new Promise((resolve) => { p.step = 'password'; p.awaiting = true; p.resolvePassword = resolve; }), + onError: (err) => { + const code = tgErrCode(err); + p.error = code; + return tgHardError(code); + } + }).then(async () => { + const me = await withTimeout(c.getMe()); + const store = readStore(); + store.telegram = { + apiId: id, + apiHash: hash, + phone: ph, + session: c.session.save(), + userId: String(me.id || ''), + username: me.username || '', + displayName: tgDisplayName(me) + }; + writeStore(store); + if (client && client !== c) await killClient(client); + client = c; + entities.clear(); dialogCache.value = null; + p.done = true; p.step = 'done'; p.error = ''; + pending = null; + setModuleFlag('on'); + }).catch(async (err) => { + if (!p.error) p.error = tgErrCode(err); + p.done = true; p.step = 'error'; + await killClient(c); + }); + await waitStep(p, 'sending', 12000); + return this.loginState(); + }, + async submitCode(code) { + const p = pending; + if (!p || p.done || typeof p.resolveCode !== 'function') throw new Error('telegramErrNoLogin'); + const value = String(code || '').replace(/\s+/g, ''); + if (!value) throw new Error('telegramErrCode'); + const resolve = p.resolveCode; + p.resolveCode = null; p.awaiting = false; p.error = ''; + resolve(value); + await waitStep(p, 'code'); + return this.loginState(); + }, + async submitPassword(password) { + const p = pending; + if (!p || p.done || typeof p.resolvePassword !== 'function') throw new Error('telegramErrNoLogin'); + const value = String(password || ''); + if (!value) throw new Error('telegramErrPassword'); + const resolve = p.resolvePassword; + p.resolvePassword = null; p.awaiting = false; p.error = ''; + resolve(value); + await waitStep(p, 'password'); + return this.loginState(); + }, + async cancelLogin() { await dropPending(); }, + async goOffline() { + await dropPending(); + const c = client; + client = null; + await killClient(c); + }, + async disconnect() { + const { Api } = loadGram(); + try { + const c = await getClient(); + await withTimeout(c.invoke(new Api.auth.LogOut()), 8000); + } catch (_) {} + await killClient(client); + client = null; + const store = readStore(); + delete store.telegram; + writeStore(store); + entities.clear(); messages.clear(); mediaCache.clear(); avatarCache.clear(); dialogCache.value = null; + return true; + }, + invalidateCache() { dialogCache.value = null; }, + async getDialogs(force = false) { + if (!this.hasAccount()) return { connected: false, dialogs: [] }; + const now = Date.now(); + if (!force && dialogCache.value && now - dialogCache.ts < TIMELINE_CACHE_MS) return dialogCache.value; + let c; + try { c = await getClient(); } catch (err) { return { connected: true, account: this.getAccount(), dialogs: [], error: tgErrCode(err) }; } + let list; + try { list = await withTimeout(c.getDialogs({ limit: TIMELINE_LIMIT })); } catch (err) { return { connected: true, account: this.getAccount(), dialogs: [], error: tgErrCode(err) }; } + rememberDialogs(list); + const dialogs = list.map((d) => { + const id = String(d.id); + const last = d.message || null; + return { + id, + title: String(d.title || d.name || id), + kind: peerKind(d), + unread: Number(d.unreadCount || 0), + date: toIso(d.date || (last && last.date)), + last: last ? { text: String(last.message || ''), out: last.out === true, hasMedia: !!last.media } : null, + avatar: `/fediverse/telegram/avatar/${encodeURIComponent(id)}` + }; + }); + const value = { connected: true, account: this.getAccount(), dialogs }; + dialogCache.ts = now; dialogCache.value = value; + return value; + }, + async getChat(id) { + const c = await getClient(); + const entity = await resolveEntity(c, id); + const list = await withTimeout(c.getMessages(entity, { limit: TIMELINE_LIMIT })); + const senderNames = new Map(); + for (const msg of list) { + const sid = msg.senderId ? String(msg.senderId) : ''; + if (!sid || senderNames.has(sid)) continue; + try { senderNames.set(sid, tgDisplayName(await withTimeout(msg.getSender(), 8000))); } catch (_) { senderNames.set(sid, ''); } + } + for (const msg of list) messages.set(`${id}:${msg.id}`, msg); + trimCache(messages); + try { await withTimeout(c.markAsRead(entity), 8000); } catch (_) {} + dialogCache.value = null; + return { + id: String(id), + title: tgDisplayName(entity) || String(id), + kind: kindOfEntity(entity), + messages: list.slice().reverse().map((msg) => mapMessage(msg, id, senderNames)) + }; + }, + async sendMessage(id, { text, file } = {}) { + const c = await getClient(); + const entity = await resolveEntity(c, id); + const msg = String(text || '').trim(); + if (file && file.filepath) { + await withTimeout(c.sendFile(entity, { file: file.filepath, caption: msg, forceDocument: false }), 90000); + } else { + if (!msg) throw new Error('fediverseErrEmpty'); + await withTimeout(c.sendMessage(entity, { message: msg })); + } + dialogCache.value = null; + return true; + }, + async getMedia(chatId, msgId) { + const key = `${chatId}:${msgId}`; + if (mediaCache.has(key)) return mediaCache.get(key); + const c = await getClient(); + let msg = messages.get(key) || null; + if (!msg) { + const entity = await resolveEntity(c, chatId); + const arr = await withTimeout(c.getMessages(entity, { ids: [Number(msgId)] })); + msg = arr && arr[0] ? arr[0] : null; + } + if (!msg || !msg.media) return null; + const info = mediaInfo(msg); + if (!info || info.kind === 'other' || info.size > TG_MEDIA_MAX_BYTES) return null; + const buf = await withTimeout(c.downloadMedia(msg, {}), 90000); + if (!buf || !buf.length) return null; + const out = { contentType: info.mime || 'application/octet-stream', buffer: Buffer.from(buf), name: info.name }; + mediaCache.set(key, out); + trimCache(mediaCache); + return out; + }, + async getAvatar(id) { + const key = String(id); + if (avatarCache.has(key)) return avatarCache.get(key); + const c = await getClient(); + const entity = key === 'me' ? 'me' : await resolveEntity(c, key); + let buf = null; + try { buf = await withTimeout(c.downloadProfilePhoto(entity, { isBig: false }), 15000); } catch (_) { buf = null; } + const out = buf && buf.length ? { contentType: 'image/jpeg', buffer: Buffer.from(buf) } : null; + avatarCache.set(key, out); + trimCache(avatarCache); + return out; + } + }; + })(), + + + peertube: (() => { + const getPt = () => readStore().peertube || null; + const feedCache = new Map(); + const PT_UPLOAD_TIMEOUT_MS = 15 * 60 * 1000; + const PT_PRIVACY = { public: 1, unlisted: 2, private: 3 }; + + const fetchTimed = async (url, opts = {}, timeoutMs = FETCH_TIMEOUT_MS) => { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { return await netFetch(url, { ...opts, signal: controller.signal }); } finally { clearTimeout(timer); } + }; + const oauthClient = async (instance) => { + let res; + try { res = await fetchTimed(`${instance}/api/v1/oauth-clients/local`, { headers: spoofHeaders('application/json') }); } catch (_) { throw new Error('peertubeErrConnect'); } + if (!res.ok) throw new Error('peertubeErrConnect'); + const data = await res.json().catch(() => null); + if (!data || !data.client_id || !data.client_secret) throw new Error('peertubeErrConnect'); + return data; + }; + const tokenRequest = async (instance, params) => { + let res; + try { + res = await fetchTimed(`${instance}/api/v1/users/token`, { + method: 'POST', + headers: { ...spoofHeaders('application/json'), 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams(params).toString() + }); + } catch (_) { throw new Error('peertubeErrConnect'); } + if (res.status === 400 || res.status === 401 || res.status === 403) throw new Error('peertubeErrAuth'); + if (!res.ok) throw new Error('peertubeErrConnect'); + const data = await res.json().catch(() => null); + if (!data || !data.access_token) throw new Error('peertubeErrAuth'); + return data; + }; + const saveTokens = (pt, data) => { + const store = readStore(); + if (!store.peertube) return null; + store.peertube = { ...store.peertube, token: data.access_token, refreshToken: data.refresh_token || pt.refreshToken || '', expiresAt: Date.now() + (Number(data.expires_in) || 0) * 1000 }; + writeStore(store); + return store.peertube; + }; + const refreshAccess = async (pt) => { + if (!pt || !pt.refreshToken) return null; + try { + const data = await tokenRequest(pt.instance, { client_id: pt.clientId, client_secret: pt.clientSecret, grant_type: 'refresh_token', refresh_token: pt.refreshToken }); + return saveTokens(pt, data); + } catch (_) { return null; } + }; + const ptFetch = async (pathName, opts = {}, retry = true) => { + let pt = getPt(); + if (!pt) throw new Error('peertubeErrAuth'); + if (pt.expiresAt && Date.now() > pt.expiresAt - 60000 && retry) { const fresh = await refreshAccess(pt); if (fresh) pt = fresh; } + const headers = Object.assign(spoofHeaders('application/json'), { Authorization: `Bearer ${pt.token}` }, opts.headers || {}); + let res; + try { res = await fetchTimed(`${pt.instance}${pathName}`, { method: opts.method || 'GET', body: opts.body, headers }, opts.timeoutMs || FETCH_TIMEOUT_MS); } catch (_) { throw new Error('peertubeErrConnect'); } + if (res.status === 401 && retry) { + const fresh = await refreshAccess(pt); + if (fresh) return ptFetch(pathName, opts, false); + } + return res; + }; + const absUrl = (pt, p) => { + if (typeof p !== 'string' || !p) return ''; + if (/^https?:\/\//i.test(p)) return p; + return `${pt.instance}${p.startsWith('/') ? '' : '/'}${p}`; + }; + const rememberHost = (url) => { try { mediaHosts.add(new URL(url).host); } catch (_) {} }; + const avatarOf = (pt, obj) => { + const list = Array.isArray(obj && obj.avatars) ? obj.avatars.slice().sort((a, b) => (Number(b.width) || 0) - (Number(a.width) || 0)) : []; + const p = list.length ? list[0].path : (obj && obj.avatar && obj.avatar.path) || ''; + return p ? proxify(absUrl(pt, p)) : ''; + }; + const channelOf = (pt, ch) => { + if (!ch || typeof ch !== 'object') return { name: '', displayName: '', host: '', handle: '', avatar: '', url: '' }; + const host = ch.host || (() => { try { return new URL(pt.instance).host; } catch (_) { return ''; } })(); + return { name: ch.name || '', displayName: ch.displayName || ch.name || '', host, handle: ch.name ? `${ch.name}@${host}` : '', avatar: avatarOf(pt, ch), url: ch.url || (ch.name ? `${pt.instance}/c/${ch.name}` : '') }; + }; + const mapVideo = (pt, v) => { + if (!v || typeof v !== 'object') return null; + const id = String(v.uuid || v.shortUUID || v.id || ''); + return { + id, + numericId: v.id, + name: typeof v.name === 'string' ? v.name : '', + description: typeof v.description === 'string' ? v.description : '', + thumbnail: proxify(absUrl(pt, v.thumbnailPath || v.previewPath || '')), + duration: Number(v.duration) || 0, + views: Number(v.views) || 0, + likes: Number(v.likes) || 0, + dislikes: Number(v.dislikes) || 0, + publishedAt: v.publishedAt || v.createdAt || '', + channel: channelOf(pt, v.channel), + url: typeof v.url === 'string' && v.url ? v.url : `${pt.instance}/w/${id}`, + privacy: v.privacy && v.privacy.label ? String(v.privacy.label) : '', + privacyId: v.privacy && v.privacy.id ? Number(v.privacy.id) : 0, + state: v.state && v.state.label ? String(v.state.label) : '', + isLive: v.isLive === true, + nsfw: v.nsfw === true + }; + }; + const pickFile = (v) => { + const candidates = []; + for (const f of (Array.isArray(v.files) ? v.files : [])) if (f && f.fileUrl) candidates.push(f); + for (const pl of (Array.isArray(v.streamingPlaylists) ? v.streamingPlaylists : [])) for (const f of (Array.isArray(pl.files) ? pl.files : [])) if (f && f.fileUrl) candidates.push(f); + if (!candidates.length) return null; + const resOf = (f) => Number(f.resolution && f.resolution.id) || 0; + const under = candidates.filter(f => resOf(f) > 0 && resOf(f) <= 720).sort((a, b) => resOf(b) - resOf(a)); + const pick = under[0] || candidates.slice().sort((a, b) => resOf(a) - resOf(b))[0]; + return { url: pick.fileUrl, resolution: resOf(pick), size: Number(pick.size) || 0 }; + }; + const mapComment = (pt, c) => c && typeof c === 'object' ? { + id: String(c.id || ''), + html: typeof c.text === 'string' ? c.text : '', + createdAt: c.createdAt || '', + totalReplies: Number(c.totalReplies) || 0, + account: c.account ? { name: c.account.name || '', displayName: c.account.displayName || c.account.name || '', host: c.account.host || '', avatar: avatarOf(pt, c.account) } : { name: '', displayName: '', host: '', avatar: '' } + } : null; + const listOf = async (pathName) => { + const res = await ptFetch(pathName); + if (res.status === 401 || res.status === 403) return { error: 'peertubeErrAuth' }; + if (!res.ok) return { error: 'peertubeErrFetch' }; + const data = await res.json().catch(() => null); + return { total: Number(data && data.total) || 0, data: Array.isArray(data && data.data) ? data.data : [] }; + }; + const invalidatePt = () => feedCache.clear(); + + return { + hasAccount() { return !!(getPt() && getPt().token); }, + getAccount() { + const pt = getPt(); + if (!pt || !pt.token) return null; + let host = ''; + try { host = new URL(pt.instance).host; } catch (_) {} + return { + network: 'peertube', + instance: pt.instance, + host, + username: pt.username || '', + displayName: pt.displayName || pt.username || '', + handle: pt.username ? `${pt.username}@${host}` : host, + avatar: pt.avatar ? proxify(pt.avatar) : '', + channel: pt.channelName || '', + channelDisplayName: pt.channelDisplayName || pt.channelName || '', + channelUrl: pt.channelName ? `${pt.instance}/c/${pt.channelName}` : pt.instance, + profileUrl: pt.username ? `${pt.instance}/a/${pt.username}` : pt.instance + }; + }, + async connect({ instance, username, password }) { + if (isPublic) throw new Error('fediverseErrPublic'); + const origin = (() => { try { return normalizeInstance(instance); } catch (_) { throw new Error('peertubeErrInstance'); } })(); + const user = String(username || '').trim(); + const pass = String(password || ''); + if (!user || !pass) throw new Error('peertubeErrMissing'); + const client = await oauthClient(origin); + const tokens = await tokenRequest(origin, { client_id: client.client_id, client_secret: client.client_secret, grant_type: 'password', response_type: 'code', username: user, password: pass }); + let me; + try { + const res = await fetchTimed(`${origin}/api/v1/users/me`, { headers: Object.assign(spoofHeaders('application/json'), { Authorization: `Bearer ${tokens.access_token}` }) }); + if (!res.ok) throw new Error('peertubeErrAuth'); + me = await res.json(); + } catch (_) { throw new Error('peertubeErrConnect'); } + const channel = Array.isArray(me.videoChannels) && me.videoChannels.length ? me.videoChannels[0] : null; + const acc = me.account || {}; + const avatars = Array.isArray(acc.avatars) ? acc.avatars.slice().sort((a, b) => (Number(b.width) || 0) - (Number(a.width) || 0)) : []; + const avatarPath = avatars.length ? avatars[0].path : (acc.avatar && acc.avatar.path) || ''; + const store = readStore(); + store.peertube = { + instance: origin, + clientId: client.client_id, + clientSecret: client.client_secret, + token: tokens.access_token, + refreshToken: tokens.refresh_token || '', + expiresAt: Date.now() + (Number(tokens.expires_in) || 0) * 1000, + userId: String(me.id || ''), + username: me.username || user, + displayName: acc.displayName || me.username || user, + avatar: avatarPath ? `${origin}${avatarPath.startsWith('/') ? '' : '/'}${avatarPath}` : '', + accountName: acc.name || me.username || user, + channelId: channel ? String(channel.id) : '', + channelName: channel ? channel.name || '' : '', + channelDisplayName: channel ? channel.displayName || channel.name || '' : '' + }; + writeStore(store); + invalidatePt(); + try { mediaHosts.add(new URL(origin).host); } catch (_) {} + setModuleFlag('on'); + return this.getAccount(); + }, + async disconnect() { + const pt = getPt(); + if (pt && pt.token) { + try { await fetchTimed(`${pt.instance}/api/v1/users/revoke-token`, { method: 'POST', headers: Object.assign(spoofHeaders('application/json'), { Authorization: `Bearer ${pt.token}` }) }); } catch (_) {} + } + const store = readStore(); + delete store.peertube; + writeStore(store); + invalidatePt(); + return true; + }, + invalidateCache() { invalidatePt(); }, + async getAccountStats() { + const pt = getPt(); + if (!pt) return null; + let me = null; + try { const res = await ptFetch('/api/v1/users/me'); if (res.ok) me = await res.json().catch(() => null); } catch (_) {} + if (!me) return null; + const channel = Array.isArray(me.videoChannels) && me.videoChannels.length ? me.videoChannels[0] : null; + let videos = 0, subscriptions = 0; + try { const v = await listOf('/api/v1/users/me/videos?start=0&count=1'); if (!v.error) videos = v.total; } catch (_) {} + try { const sub = await listOf('/api/v1/users/me/subscriptions?start=0&count=1'); if (!sub.error) subscriptions = sub.total; } catch (_) {} + return { + videos, + subscriptions, + followers: channel ? Number(channel.followersCount) || 0 : 0, + quota: Number(me.videoQuota) || 0, + quotaUsed: Number(me.videoQuotaUsed) || 0 + }; + }, + async getFeed(kind = 'subscriptions', force = false) { + const pt = getPt(); + if (!pt) return { connected: false, videos: [] }; + try { mediaHosts.add(new URL(pt.instance).host); } catch (_) {} + const key = kind === 'mine' ? 'mine' : 'subscriptions'; + const now = Date.now(); + const hit = feedCache.get(key); + if (!force && hit && now - hit.ts < TIMELINE_CACHE_MS) return hit.value; + let result; + try { + result = await listOf(key === 'mine' ? `/api/v1/users/me/videos?start=0&count=${TIMELINE_LIMIT}&sort=-publishedAt` : `/api/v1/users/me/subscriptions/videos?start=0&count=${TIMELINE_LIMIT}&sort=-publishedAt`); + } catch (err) { + return { connected: true, account: this.getAccount(), videos: [], error: (err && err.message) || 'peertubeErrFetch' }; + } + if (result.error) return { connected: true, account: this.getAccount(), videos: [], error: result.error }; + const value = { connected: true, account: this.getAccount(), videos: result.data.map(v => mapVideo(pt, v)).filter(Boolean), total: result.total, kind: key }; + feedCache.set(key, { ts: now, value }); + return value; + }, + async getVideo(id) { + const pt = getPt(); + if (!pt) return null; + const vid = encodeURIComponent(String(id || '')); + let res; + try { res = await ptFetch(`/api/v1/videos/${vid}`); } catch (_) { return { error: 'peertubeErrFetch' }; } + if (!res.ok) return { error: 'peertubeErrFetch' }; + const raw = await res.json().catch(() => null); + const video = mapVideo(pt, raw); + if (!video) return { error: 'peertubeErrFetch' }; + const file = pickFile(raw || {}); + if (file) { rememberHost(file.url); video.stream = `/fediverse/peertube/stream?u=${encodeURIComponent(file.url)}`; video.resolution = file.resolution; video.fileSize = file.size; } + video.embedUrl = raw && raw.embedPath ? absUrl(pt, raw.embedPath) : ''; + video.tags = Array.isArray(raw && raw.tags) ? raw.tags.map(String) : []; + let comments = []; + try { + const c = await listOf(`/api/v1/videos/${vid}/comment-threads?start=0&count=${TIMELINE_LIMIT}&sort=-createdAt`); + if (!c.error) comments = c.data.map(x => mapComment(pt, x)).filter(Boolean); + } catch (_) {} + let myRating = 'none'; + try { const r = await ptFetch(`/api/v1/users/me/videos/${vid}/rating`); if (r.ok) { const d = await r.json().catch(() => null); if (d && d.rating) myRating = String(d.rating); } } catch (_) {} + return { video, comments, myRating }; + }, + async rate(id, rating) { + const allowed = { like: 1, dislike: 1, none: 1 }; + if (!allowed[rating]) throw new Error('peertubeErrFetch'); + let res; + try { res = await ptFetch(`/api/v1/videos/${encodeURIComponent(String(id || ''))}/rate`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rating }) }); } catch (_) { throw new Error('peertubeErrConnect'); } + if (res.status === 401 || res.status === 403) throw new Error('peertubeErrAuth'); + if (!res.ok && res.status !== 204) throw new Error('peertubeErrFetch'); + invalidatePt(); + return true; + }, + async comment(id, text) { + const body = String(text || '').trim(); + if (!body) throw new Error('fediverseErrEmpty'); + let res; + try { res = await ptFetch(`/api/v1/videos/${encodeURIComponent(String(id || ''))}/comment-threads`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: body }) }); } catch (_) { throw new Error('peertubeErrConnect'); } + if (res.status === 401 || res.status === 403) throw new Error('peertubeErrAuth'); + if (!res.ok) throw new Error('peertubeErrComment'); + return true; + }, + async upload({ file, name, description, privacy } = {}) { + const pt = getPt(); + if (!pt) throw new Error('peertubeErrAuth'); + if (!file || !file.filepath || !(Number(file.size) > 0)) throw new Error('peertubeErrNoFile'); + const title = String(name || '').trim(); + if (!title) throw new Error('peertubeErrTitle'); + let buf; + try { buf = fs.readFileSync(file.filepath); } catch (_) { throw new Error('peertubeErrUpload'); } + const fd = new FormData(); + fd.append('videofile', new Blob([buf], { type: file.mimetype || 'video/mp4' }), file.originalFilename || 'video.mp4'); + if (pt.channelId) fd.append('channelId', String(pt.channelId)); + fd.append('name', title.slice(0, 120)); + fd.append('privacy', String(PT_PRIVACY[String(privacy || 'public')] || 1)); + const desc = String(description || '').trim(); + if (desc) fd.append('description', desc.slice(0, 10000)); + let res; + try { res = await ptFetch('/api/v1/videos/upload', { method: 'POST', body: fd, timeoutMs: PT_UPLOAD_TIMEOUT_MS }); } catch (_) { throw new Error('peertubeErrUpload'); } + if (res.status === 401 || res.status === 403) throw new Error('peertubeErrAuth'); + if (!res.ok) throw new Error('peertubeErrUpload'); + const data = await res.json().catch(() => ({})); + invalidatePt(); + const v = data && data.video ? data.video : {}; + return { id: String(v.uuid || v.shortUUID || v.id || '') }; + }, + async openStream(remoteUrl, range) { + let u; + try { u = new URL(String(remoteUrl || '')); } catch (_) { return null; } + if (u.protocol !== 'https:' || isPrivateHost(u.hostname) || !api.isHostAllowed(u.host)) return null; + const headers = spoofHeaders('*/*'); + if (range) headers.Range = String(range); + let res = null; + let current = u; + for (let hop = 0; hop < 5; hop++) { + try { res = await publicFetch(current.href, { headers, redirect: 'manual' }); } catch (_) { return null; } + if (![301, 302, 303, 307, 308].includes(res.status)) break; + let next; + try { next = new URL(res.headers.get('location') || '', current); } catch (_) { return null; } + if (next.protocol !== 'https:' || isPrivateHost(next.hostname)) return null; + current = next; + res = null; + } + if (!res || (!res.ok && res.status !== 206)) return null; + const type = res.headers.get('content-type') || 'application/octet-stream'; + if (!/^(video|audio|application\/octet-stream)/i.test(type)) return null; + return { + status: res.status, + type, + length: res.headers.get('content-length') || '', + contentRange: res.headers.get('content-range') || '', + body: res.body ? Readable.fromWeb(res.body) : null + }; + } + }; + })(), + isHostAllowed(host) { if (mediaHosts.has(host)) return true; const m = getMastodon(); @@ -368,6 +1110,10 @@ module.exports = ({ isPublic } = {}) => { try { if (new URL(m.instance).host === host) return true; } catch (_) {} try { if (m.avatar && new URL(m.avatar).host === host) return true; } catch (_) {} } + const pt = readStore().peertube; + if (pt && pt.instance) { + try { if (new URL(pt.instance).host === host) return true; } catch (_) {} + } return false; }, @@ -382,7 +1128,7 @@ module.exports = ({ isPublic } = {}) => { try { let current = u, res = null; for (let hop = 0; hop < 5; hop++) { - res = await fetch(current.href, { signal: controller.signal, redirect: 'manual', headers: spoofHeaders('image/avif,image/webp,*/*') }); + res = await publicFetch(current.href, { signal: controller.signal, redirect: 'manual', headers: spoofHeaders('image/avif,image/webp,*/*') }); if (res.status < 300 || res.status >= 400) break; const loc = res.headers.get('location'); if (!loc) return null; @@ -395,8 +1141,8 @@ module.exports = ({ isPublic } = {}) => { if (!res || !res.ok) return null; const ct = res.headers.get('content-type') || 'application/octet-stream'; if (!/^(image|video|audio)\//i.test(ct)) return null; - const ab = await res.arrayBuffer(); - return { contentType: ct, buffer: Buffer.from(ab) }; + const buffer = await readCapped(res, PROXY_MEDIA_MAX_BYTES); + return buffer ? { contentType: ct, buffer } : null; } catch (_) { return null; } finally { @@ -407,3 +1153,5 @@ module.exports = ({ isPublic } = {}) => { return api; }; + +module.exports.isPrivateHost = isPrivateHost; diff --git a/src/models/feed_model.js b/src/models/feed_model.js index 80f894b..e37d5c9 100644 --- a/src/models/feed_model.js +++ b/src/models/feed_model.js @@ -2,7 +2,8 @@ const pull = require("../server/node_modules/pull-stream"); const { getConfig } = require("../configs/config-manager.js"); const categories = require("../backend/opinion_categories"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, mergeDuplicatesBy, norm } = require('../backend/dedupe'); +const { readTyped } = require('./typed_log'); +const { dedupeBy, mergeDuplicatesBy, norm, versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const FEED_TEXT_MIN = Number(getConfig().feed?.minLength ?? 1); @@ -15,10 +16,10 @@ module.exports = ({ cooler }) => { return ssb; }; - const cleanText = (t) => (typeof t === "string" ? t.trim() : ""); + const cleanText = (t) => (typeof t === "string" ? t.replace(/\r\n?/g, "\n").trim() : ""); const isValidFeedText = (t) => { - const s = cleanText(t); + const s = cleanText(t).replace(/!?\[[^\]\n]*\]\(\s*&[^)\s]+\.sha256\s*\)/g, "").trim(); return s.length >= FEED_TEXT_MIN && s.length <= FEED_TEXT_MAX; }; @@ -27,10 +28,9 @@ module.exports = ({ cooler }) => { ssbClient.get(id, (err, val) => (err ? reject(err) : resolve({ key: id, value: val }))); }); - const getAllMessages = (ssbClient) => - new Promise((resolve, reject) => { - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))); - }); + const FEED_TYPES = ["feed", "feedOpinion", "feed-action", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, FEED_TYPES, { limit: logLimit }); const extractTags = (text) => { const list = (String(text || "").match(/#[A-Za-z0-9_]{1,32}/g) || []).map((t) => t.slice(1).toLowerCase()); @@ -40,11 +40,8 @@ module.exports = ({ cooler }) => { const buildIndex = async (ssbClient) => { const messages = await getAllMessages(ssbClient); - const forward = new Map(); - const replacedIds = new Set(); const tombstoned = buildValidatedTombstoneSet(messages); const feedsById = new Map(); - const authorById = new Map(); const actions = []; const opinionMsgs = []; @@ -59,7 +56,6 @@ module.exports = ({ cooler }) => { } if (c.type === "feed") { feedsById.set(k, msg); - authorById.set(k, msg?.value?.author); continue; } if (c.type === "feed-action") { @@ -68,24 +64,8 @@ module.exports = ({ cooler }) => { } } - for (const [k, msg] of feedsById) { - const t = msg?.value?.content?.replaces; - if (!t) continue; - if (authorById.get(t) === authorById.get(k)) { - forward.set(t, k); - replacedIds.add(t); - } - } - - const resolve = (id) => { - let cur = id; - const seen = new Set(); - while (forward.has(cur) && !seen.has(cur)) { - seen.add(cur); - cur = forward.get(cur); - } - return cur; - }; + const vg = versionGroups(Array.from(feedsById.values()).map(versionEntry)); + const resolve = (id) => vg.tipOf(id); const actionsByRoot = new Map(); for (const a of actions) { @@ -105,7 +85,7 @@ module.exports = ({ cooler }) => { opinionsByTip.get(tip).push(op); } - return { resolve, tombstoned, feedsById, replacedIds, actionsByRoot, opinionsByTip }; + return { resolve, vg, tombstoned, feedsById, actionsByRoot, opinionsByTip }; }; const resolveCurrentId = async (id) => { @@ -114,7 +94,7 @@ module.exports = ({ cooler }) => { return idx.resolve(id); }; - const createFeed = async (text, mentions) => { + const createFeed = async (text, mentions, media = null) => { const ssbClient = await openSsb(); const userId = ssbClient.id; @@ -129,7 +109,7 @@ module.exports = ({ cooler }) => { const content = { type: "feed", - text: cleaned, + text: typeof media === "string" && media.trim() ? `${cleaned}\n${media.trim()}` : cleaned, author: userId, createdAt: new Date().toISOString(), tags: extractTags(cleaned), @@ -146,6 +126,7 @@ module.exports = ({ cooler }) => { const userId = ssbClient.id; const idx = await buildIndex(ssbClient); + if (idx.vg.isForeignCopy(idx.vg.rootOf(contentId))) throw new Error("Invalid feed"); const tipId = idx.resolve(contentId); let msg; @@ -216,6 +197,7 @@ module.exports = ({ cooler }) => { const userId = ssbClient.id; const idx = await buildIndex(ssbClient); + if (idx.vg.isForeignCopy(idx.vg.rootOf(contentId))) throw new Error("Invalid feed"); const tipId = idx.resolve(contentId); const msg = idx.feedsById.get(tipId); @@ -249,30 +231,22 @@ module.exports = ({ cooler }) => { return !!c && c.type === "feed" && isValidFeedText(c.text); }; - let tips = Array.from(idx.feedsById.values()).filter( + let tips = Array.from(idx.vg.roots).map((r) => idx.feedsById.get(idx.resolve(r))).filter( (m) => - !idx.replacedIds.has(m.key) && + !!m && !idx.tombstoned.has(m.key) && isValidFeedMsg(m) ); const textEditedEver = (m) => { - const seen = new Set(); - let cur = m; - let lastText = cur?.value?.content?.text; - while (cur?.value?.content?.replaces) { - const prevId = cur.value.content.replaces; - if (!prevId || seen.has(prevId)) break; - seen.add(prevId); - const prev = idx.feedsById.get(prevId); - if (!prev) break; - if (prev?.value?.author !== cur?.value?.author) break; - const prevText = prev?.value?.content?.text; - if (typeof lastText === "string" && typeof prevText === "string" && lastText !== prevText) return true; - cur = prev; - lastText = prevText; + const root = idx.vg.rootOf(m.key); + const owner = idx.feedsById.get(root)?.value?.author; + const texts = new Set(); + for (const k of (idx.vg.groups.get(root) || [])) { + const v = idx.feedsById.get(k)?.value; + if (v && v.author === owner && typeof v.content?.text === "string") texts.add(v.content.text); } - return false; + return texts.size > 1; }; const materialize = (feedMsg) => { @@ -300,7 +274,7 @@ module.exports = ({ cooler }) => { } if (ac.action === "comment") { - commentCount++; + if (!idx.tombstoned.has(a.key)) commentCount++; continue; } } @@ -367,7 +341,7 @@ module.exports = ({ cooler }) => { if (filter === "MINE") { feeds = feeds.filter((m) => (m.value?.author || m.value?.content?.author) === userId); - } else if (filter === "TODAY") { + } else if (filter === "RECENT" || filter === "TODAY") { feeds = feeds.filter((m) => now - getTs(m) < 86400000); } @@ -388,7 +362,7 @@ module.exports = ({ cooler }) => { const ssbClient = await openSsb(); const idx = await buildIndex(ssbClient); const currentId = idx.resolve(feedId); - if (idx.tombstoned.has(currentId)) return null; + if (idx.tombstoned.has(currentId) || idx.vg.isForeignCopy(idx.vg.rootOf(currentId))) return null; const msg = idx.feedsById.get(currentId); if (!msg) return null; const actions = idx.actionsByRoot.get(currentId) || []; @@ -402,7 +376,7 @@ module.exports = ({ cooler }) => { refeeds++; if (ac.author || a?.value?.author) refeedsInhabitants.push(ac.author || a.value.author); } - if (ac.type === "feed-action" && ac.action === "comment") { + if (ac.type === "feed-action" && ac.action === "comment" && !idx.tombstoned.has(a.key)) { commentCount++; } } @@ -417,7 +391,7 @@ module.exports = ({ cooler }) => { const currentId = idx.resolve(feedId); const actions = idx.actionsByRoot.get(currentId) || []; return actions - .filter(a => a?.value?.content?.type === "feed-action" && a?.value?.content?.action === "comment") + .filter(a => a?.value?.content?.type === "feed-action" && a?.value?.content?.action === "comment" && !idx.tombstoned.has(a.key)) .sort((a, b) => (a?.value?.timestamp || 0) - (b?.value?.timestamp || 0)); }; @@ -430,6 +404,15 @@ module.exports = ({ cooler }) => { }); }; - return { createFeed, createRefeed, addOpinion, listFeeds, resolveCurrentId, getFeedById, getComments, addComment }; + const deleteFeedById = async (id) => { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + const currentId = (await resolveCurrentId(id)) || id; + const item = await new Promise((res, rej) => ssbClient.get(currentId, (e, m) => (e || !m || !m.content) ? rej(new Error("Feed not found")) : res(m))); + if (item.author !== userId && item.content?.author !== userId) throw new Error("Not the author"); + return new Promise((res, rej) => ssbClient.publish({ type: "tombstone", target: currentId, deletedAt: new Date().toISOString(), author: userId }, (e, m) => e ? rej(e) : res(m))); + }; + + return { createFeed, createRefeed, addOpinion, listFeeds, resolveCurrentId, getFeedById, getComments, addComment, deleteFeedById }; }; diff --git a/src/models/files_model.js b/src/models/files_model.js new file mode 100644 index 0000000..94acd6a --- /dev/null +++ b/src/models/files_model.js @@ -0,0 +1,387 @@ +const pull = require("../server/node_modules/pull-stream"); +const { getConfig } = require("../configs/config-manager.js"); +const categories = require("../backend/opinion_categories"); +const { readTyped, memoIndex } = require("./typed_log"); + +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const safeArr = (v) => (Array.isArray(v) ? v : []); + +const normalizeTags = (raw) => { + if (raw === undefined || raw === null) return undefined; + if (Array.isArray(raw)) return raw.map((t) => String(t || "").trim()).filter(Boolean); + return String(raw).split(",").map((t) => t.trim()).filter(Boolean); +}; + +const fileExtra = (extra) => { + if (!extra || typeof extra !== "object") return {}; + const out = {}; + if (extra.mime) out.mime = String(extra.mime); + if (extra.fileName) out.fileName = String(extra.fileName); + if (typeof extra.torrentKey === "string" && extra.torrentKey.startsWith("%")) out.torrentKey = extra.torrentKey; + if (typeof extra.torrentUrl === "string" && extra.torrentUrl.startsWith("&")) out.torrentUrl = extra.torrentUrl; + if (extra.cipher && typeof extra.cipher === "object" && extra.cipher.key && extra.cipher.manifestBlobId) out.cipher = { v: Number(extra.cipher.v) || 1, key: String(extra.cipher.key), manifestBlobId: String(extra.cipher.manifestBlobId), chunkCount: Number(extra.cipher.chunkCount) || 0 }; + return out; +}; + +const parseBlobId = (blobMarkdown) => { + const s = String(blobMarkdown || ""); + const match = s.match(/\((&[^)]+\.sha256)\)/); + if (match) return match[1]; + const fallback = s.match(/\(([^)]+)\)/g); + return fallback ? fallback[fallback.length - 1].slice(1, -1) : s || null; +}; + +const voteSum = (opinions = {}) => + Object.values(opinions || {}).reduce((s, n) => s + (Number(n) || 0), 0); + +module.exports = ({ cooler, tribeCrypto, tribesModel }) => { + let ssb; + + const openSsb = async () => { + if (!ssb) ssb = await cooler.open(); + return ssb; + }; + + const tribeHelpers = tribeCrypto ? tribeCrypto.createHelpers(tribesModel) : null; + const encryptIfTribe = tribeHelpers ? tribeHelpers.encryptIfTribe : async (c) => c; + const decryptIfTribe = tribeHelpers ? tribeHelpers.decryptIfTribe : async (c) => c; + const assertReadable = tribeHelpers ? tribeHelpers.assertReadable : () => {}; + const decryptIndexNodes = tribeHelpers ? tribeHelpers.decryptIndexNodes : async () => {}; + const unwrapForIndex = (msgs) => tribeHelpers ? tribeHelpers.unwrapMessagesForKind(msgs, 'file') : msgs; + const tombFor = async (target, tribeId, author) => tribeHelpers ? tribeHelpers.encryptTombstone(target, tribeId, author) : { type: 'tombstone', target, deletedAt: new Date().toISOString(), author }; + + const FILE_TYPES = ["file", "fileOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, FILE_TYPES, { limit: logLimit, withWindow: true }); + + const getMsg = async (ssbClient, key) => + new Promise((resolve) => { + ssbClient.get(key, (err, msg) => (err ? resolve(null) : resolve(msg))); + }); + + const buildIndex = (messages) => { + const tomb = new Set(); + const nodes = new Map(); + const authorByKey = new Map(); + const tombRequests = []; + const opinionMsgs = []; + + for (const m of messages) { + const k = m.key; + const v = m.value || {}; + const c = v.content; + if (!c) continue; + + if (c.type === "tombstone" && c.target) { + tombRequests.push({ target: c.target, author: v.author }); + continue; + } + + if (c.type === "fileOpinion") { opinionMsgs.push({ target: c.target, author: v.author, category: c.category }); continue; } + + if (c.type !== "file") continue; + + const ts = v.timestamp || m.timestamp || 0; + let sizeBytes = 0; + try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); + authorByKey.set(k, v.author); + } + + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; + + for (const t of tombRequests) { + const targetAuthor = authorByKey.get(t.target); + if (targetAuthor && t.author === targetAuthor) tomb.add(t.target); + } + + const opinionsByRoot = new Map(); + for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } + + const tipByRoot = new Map(); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); + + const resolveGroup = (root) => { + const contentTip = contentTipOf(root); + const contentNode = nodes.get(contentTip) || nodes.get(root); + const lc = contentNode ? contentNode.c : {}; + const opinions = { ...(lc.opinions || {}) }; + const voters = safeArr(lc.opinions_inhabitants).slice(); + const voterSet = new Set(voters); + for (const op of (opinionsByRoot.get(root) || [])) { + if (voterSet.has(op.author)) continue; + voterSet.add(op.author); voters.push(op.author); + opinions[op.category] = (opinions[op.category] || 0) + 1; + } + return { contentTip, contentNode, opinions, voters }; + }; + + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; + }; + + const buildFile = (node, rootId, viewerId, agg) => { + const c = node.c || {}; + const undec = c.encryptedPayload && c._decrypted === false; + const opinions = agg ? agg.opinions : (c.opinions || {}); + const voters = agg ? agg.voters : safeArr(c.opinions_inhabitants); + return { + key: node.key, + rootId, + url: undec ? "" : c.url, + createdAt: c.createdAt || new Date(node.ts).toISOString(), + updatedAt: c.updatedAt || null, + tags: safeArr(c.tags), + author: node.author, + title: undec ? "" : (c.title || ""), + description: undec ? "" : (c.description || ""), + size: c.size || 0, + mime: undec ? "" : (c.mime || ""), + fileName: undec ? "" : (c.fileName || ""), + torrentKey: undec ? "" : (typeof c.torrentKey === "string" && c.torrentKey.startsWith("%") ? c.torrentKey : ""), + torrentUrl: undec ? "" : (typeof c.torrentUrl === "string" && c.torrentUrl.startsWith("&") ? c.torrentUrl : ""), + cipher: undec ? null : (c.cipher && typeof c.cipher === "object" && typeof c.cipher.key === "string" && typeof c.cipher.manifestBlobId === "string" ? { v: Number(c.cipher.v) || 1, key: c.cipher.key, manifestBlobId: c.cipher.manifestBlobId, chunkCount: Number(c.cipher.chunkCount) || 0 } : null), + opinions, + opinions_inhabitants: voters, + hasVoted: viewerId ? voters.includes(viewerId) : false, + tribeId: c.tribeId || null, + encrypted: !!undec, + sizeBytes: node.sizeBytes || 0 + }; + }; + + + return { + type: "file", + + async resolveCurrentId(id) { + const ssbClient = await openSsb(); + const messages = await getAllMessages(ssbClient); + const idx = buildIndex(unwrapForIndex(messages)); + + const tip = idx.tipByRoot.get(idx.rootOf(id)) || id; + if (idx.tomb.has(tip)) throw new Error("File not found"); + return tip; + }, + + async resolveRootId(id) { + const ssbClient = await openSsb(); + const messages = await getAllMessages(ssbClient); + const idx = buildIndex(unwrapForIndex(messages)); + + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("File not found"); + return root; + }, + + async createFile(blobMarkdown, tagsRaw, title, description, size, tribeId, extra = null) { + const ssbClient = await openSsb(); + const blobId = parseBlobId(blobMarkdown); + const tags = normalizeTags(tagsRaw) || []; + const now = new Date().toISOString(); + + let content = { + type: "file", + url: blobId, + createdAt: now, + updatedAt: null, + author: ssbClient.id, + tags, + title: title || "", + description: description || "", + size: Number(size) || 0, + opinions: {}, + opinions_inhabitants: [], + ...(tribeId ? { tribeId } : {}), + ...fileExtra(extra) + }; + + content = await encryptIfTribe(content); + + return new Promise((resolve, reject) => { + ssbClient.publish(content, (err, res) => (err ? reject(err) : resolve(res))); + }); + }, + + async createLink(url, tagsRaw, title, description, size, extra = null) { + const ssbClient = await openSsb(); + const link = String(url || '').trim(); + if (!/^https?:\/\//i.test(link)) throw new Error('Invalid link'); + const content = { + type: "file", + url: link, + createdAt: new Date().toISOString(), + updatedAt: null, + author: ssbClient.id, + tags: normalizeTags(tagsRaw) || [], + title: title || "", + description: description || "", + size: Number(size) || 0, + opinions: {}, + opinions_inhabitants: [], + ...fileExtra(extra) + }; + return new Promise((resolve, reject) => { + ssbClient.publish(content, (err, res) => (err ? reject(err) : resolve(res))); + }); + }, + + async updateFileById(id, blobMarkdown, tagsRaw, title, description, extra = null) { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + const tipId = await this.resolveCurrentId(id); + const oldMsg = await getMsg(ssbClient, tipId); + + if (!oldMsg) throw new Error("File not found"); + const oldDec = await decryptIfTribe(oldMsg.content); + if (!oldDec || oldDec.type !== "file") throw new Error("File not found"); + assertReadable(oldDec, "File"); + if (oldMsg.author !== userId) throw new Error("Not the author"); + const aggFile = await this.getFileById(id, userId); + if (aggFile && Object.keys(aggFile.opinions || {}).some(k => (aggFile.opinions[k] || 0) > 0)) throw new Error("Cannot edit file after it has received opinions."); + + const tags = tagsRaw !== undefined ? normalizeTags(tagsRaw) || [] : safeArr(oldDec.tags); + const blobId = blobMarkdown ? parseBlobId(blobMarkdown) : null; + const now = new Date().toISOString(); + + let updated = { + type: "file", + replaces: tipId, + url: blobId || oldDec.url, + tags, + title: title !== undefined ? title || "" : oldDec.title || "", + description: description !== undefined ? description || "" : oldDec.description || "", + size: extra && Number(extra.size) > 0 ? Number(extra.size) : (oldDec.size || 0), + ...(extra ? fileExtra(extra) : fileExtra(oldDec)), + opinions: oldDec.opinions || {}, + opinions_inhabitants: oldDec.opinions_inhabitants || [], + author: oldDec.author || userId, + ...((oldDec.tribeId || oldMsg.content.tribeId) ? { tribeId: oldDec.tribeId || oldMsg.content.tribeId } : {}), + createdAt: oldDec.createdAt, + updatedAt: now + }; + + updated = await encryptIfTribe(updated); + + const result = await new Promise((resolve, reject) => { + ssbClient.publish(updated, (err, res) => (err ? reject(err) : resolve(res))); + }); + const tombstone = await tombFor(tipId, oldDec.tribeId || oldMsg.content.tribeId, userId); + await new Promise((res, rej) => ssbClient.publish(tombstone, (e) => (e ? rej(e) : res()))); + return result; + }, + + async deleteFileById(id) { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + const tipId = await this.resolveCurrentId(id); + const msg = await getMsg(ssbClient, tipId); + + if (!msg) throw new Error("File not found"); + const dec = await decryptIfTribe(msg.content); + if (!dec || dec.type !== "file") throw new Error("File not found"); + if (msg.author !== userId) throw new Error("Not the author"); + + const tombstone = await tombFor(tipId, dec.tribeId || msg.content.tribeId, userId); + + return new Promise((resolve, reject) => { + ssbClient.publish(tombstone, (err, res) => (err ? reject(err) : resolve(res))); + }); + }, + + async listAll(filterOrOpts = "all", maybeOpts = {}) { + const ssbClient = await openSsb(); + + const opts = typeof filterOrOpts === "object" ? filterOrOpts : maybeOpts || {}; + const filter = (typeof filterOrOpts === "string" ? filterOrOpts : opts.filter || "all") || "all"; + const q = String(opts.q || "").trim().toLowerCase(); + const sort = String(opts.sort || "recent").trim(); + const viewerId = opts.viewerId || ssbClient.id; + + const messages = await getAllMessages(ssbClient); + const idx = buildIndex(unwrapForIndex(messages)); + await decryptIndexNodes(idx); + + const items = []; + for (const [rootId, tipId] of idx.tipByRoot.entries()) { + if (idx.tomb.has(tipId)) continue; + const node = idx.nodes.get(tipId); + if (!node) continue; + items.push(buildFile(node, rootId, viewerId, idx.resolveGroup(rootId))); + } + + let list = dedupeBy(items, x => x.url ? [norm(x.author), norm(x.url)].join('|') : null); + const now = Date.now(); + + if (filter === "mine") list = list.filter((a) => String(a.author) === String(viewerId)); + else if (filter === "recent") list = list.filter((a) => new Date(a.createdAt).getTime() >= now - 86400000); + else if (filter === "top") { + list = list.slice().sort((a, b) => voteSum(b.opinions) - voteSum(a.opinions) || new Date(b.createdAt) - new Date(a.createdAt)); + } + + if (q) { + list = list.filter((a) => { + const title = String(a.title || "").toLowerCase(); + const desc = String(a.description || "").toLowerCase(); + const tags = safeArr(a.tags).join(" ").toLowerCase(); + const author = String(a.author || "").toLowerCase(); + return title.includes(q) || desc.includes(q) || tags.includes(q) || author.includes(q); + }); + } + + if (sort === "top") { + list = list.slice().sort((a, b) => voteSum(b.opinions) - voteSum(a.opinions) || new Date(b.createdAt) - new Date(a.createdAt)); + } else if (sort === "oldest") { + list = list.slice().sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); + } else { + list = list.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + } + + return list; + }, + + async getFileById(id, viewerId = null) { + const ssbClient = await openSsb(); + const viewer = viewerId || ssbClient.id; + const messages = await getAllMessages(ssbClient); + const idx = await memoIndex('files', messages, async (m) => { const built = buildIndex(unwrapForIndex(m)); await decryptIndexNodes(built); return built; }); + + const root = idx.rootOf(id); + if (idx.nodes.has(root) && !idx.tipByRoot.has(root)) throw new Error("File not found"); + const agg = idx.resolveGroup(root); + if (agg.contentNode && !idx.tomb.has(agg.contentTip)) return buildFile(agg.contentNode, root, viewer, agg); + + const tip = idx.tipByRoot.get(root) || id; + if (idx.tomb.has(tip)) throw new Error("File not found"); + + const msg = await getMsg(ssbClient, tip); + if (!msg) throw new Error("File not found"); + let c = msg.content; + if (tribeCrypto && (c?.encryptedPayload || tribeCrypto.isTribeMsg(c)) && tribesModel) { + const dec = await tribeCrypto.decryptFromTribe(c, tribesModel); + c = dec && !dec._undecryptable ? { ...dec, _decrypted: true } : { ...c, _decrypted: false }; + } + if (!c || c.type !== "file") throw new Error("File not found"); + return buildFile({ key: tip, ts: msg.timestamp || 0, c, author: msg.author }, root, viewer, agg); + }, + + async createOpinion(id, category) { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + + if (!categories.includes(category)) throw new Error("Invalid voting category"); + + const file = await this.getFileById(id, userId); + if (!file) throw new Error("File not found"); + if (safeArr(file.opinions_inhabitants).includes(userId)) throw new Error("Already voted"); + + const content = { type: "fileOpinion", target: file.rootId, category, createdAt: new Date().toISOString() }; + return new Promise((resolve, reject) => { + ssbClient.publish(content, (err, res) => (err ? reject(err) : resolve(res))); + }); + } + }; +}; diff --git a/src/models/fileshare_model.js b/src/models/fileshare_model.js index a275fa6..c3da79b 100644 --- a/src/models/fileshare_model.js +++ b/src/models/fileshare_model.js @@ -5,6 +5,22 @@ const { Readable } = require('stream'); const fsc = require('../backend/fileshare_crypto'); const MAX_CHUNK_SIZE = 5 * 1024 * 1024; +const MAX_PREFETCH_CHUNKS = 64; +const BLOB_REF = /^&[A-Za-z0-9+/]{43}=\.sha256$/; +const DEFAULT_MAX_SHARE_BYTES = 1024 * 1024 * 1024; +const maxShareBytes = () => { + try { return Number(require('../configs/config-manager.js').getConfig().fileShare.maxSize) || DEFAULT_MAX_SHARE_BYTES; } catch (_) { return DEFAULT_MAX_SHARE_BYTES; } +}; +const checkManifest = (manifest) => { + const size = Number(manifest && manifest.size); + const chunkSize = Number(manifest && manifest.chunkSize); + const chunks = manifest && manifest.chunks; + if (!Number.isInteger(size) || size < 0 || size > maxShareBytes()) throw new Error('fileshare manifest invalid'); + if (!Number.isInteger(chunkSize) || chunkSize <= 0 || chunkSize > MAX_CHUNK_SIZE) throw new Error('fileshare manifest invalid'); + if (!Array.isArray(chunks) || chunks.length !== Math.max(1, Math.ceil(size / chunkSize)) || chunks.some(ref => !BLOB_REF.test(String(ref)))) throw new Error('fileshare manifest invalid'); + if (manifest.plainSha256 !== null && manifest.plainSha256 !== undefined && !/^[0-9a-f]{64}$/.test(String(manifest.plainSha256))) throw new Error('fileshare manifest invalid'); + return manifest; +}; module.exports = ({ cooler }) => { let ssb; @@ -117,7 +133,7 @@ module.exports = ({ cooler }) => { const key = fsc.keyFromHex(pointer.key); await wantBlob(client, pointer.manifestBlobId); const payload = await getBlob(client, pointer.manifestBlobId); - return fsc.decryptManifest(payload, key); + return checkManifest(fsc.decryptManifest(payload, key)); }; const CHUNK_WANT_TIMEOUT = 30000; @@ -128,11 +144,19 @@ module.exports = ({ cooler }) => { async function* gen() { const client = await openSsb(); if (!(await wantBlob(client, pointer.manifestBlobId, CHUNK_WANT_TIMEOUT))) throw new Error('fileshare blob unavailable'); - const manifest = fsc.decryptManifest(await getBlob(client, pointer.manifestBlobId), key); + const manifest = checkManifest(fsc.decryptManifest(await getBlob(client, pointer.manifestBlobId), key)); + const hash = crypto.createHash('sha256'); + let total = 0; for (const ref of manifest.chunks) { if (!(await wantBlob(client, ref, CHUNK_WANT_TIMEOUT))) throw new Error('fileshare blob unavailable'); - yield fsc.decryptChunk(await getBlob(client, ref), key); + const plain = fsc.decryptChunk(await getBlob(client, ref), key); + total += plain.length; + if (total > manifest.size) throw new Error('fileshare size mismatch'); + hash.update(plain); + yield plain; } + if (total !== manifest.size) throw new Error('fileshare size mismatch'); + if (manifest.plainSha256 && hash.digest('hex') !== manifest.plainSha256) throw new Error('fileshare content mismatch'); } return Readable.from(gen()); }; @@ -169,6 +193,16 @@ module.exports = ({ cooler }) => { } }; + const prefetch = async (pointer) => { + try { + const client = await openSsb(); + if (!(await hasBlob(client, pointer.manifestBlobId))) { wantBlob(client, pointer.manifestBlobId, 0); return; } + const manifest = fsc.decryptManifest(await getBlob(client, pointer.manifestBlobId), fsc.keyFromHex(pointer.key)); + if (!Array.isArray(manifest.chunks) || manifest.chunks.length > MAX_PREFETCH_CHUNKS || manifest.chunks.some(ref => !BLOB_REF.test(String(ref)))) return; + for (const ref of manifest.chunks) if (!(await hasBlob(client, ref))) wantBlob(client, ref, 0); + } catch (_) {} + }; + const removeLocalBlobs = async (pointer) => { const client = await openSsb(); let removed = 0; @@ -196,5 +230,5 @@ module.exports = ({ cooler }) => { return pruned; }; - return { createShareFromBuffer, createShareFromFile, openManifest, readShareStream, reassembleToBuffer, isAvailable, ensureAvailable, removeLocalBlobs, pruneExpired }; + return { createShareFromBuffer, createShareFromFile, openManifest, readShareStream, reassembleToBuffer, isAvailable, ensureAvailable, prefetch, removeLocalBlobs, pruneExpired }; }; diff --git a/src/models/forum_model.js b/src/models/forum_model.js index df27060..3869e60 100644 --- a/src/models/forum_model.js +++ b/src/models/forum_model.js @@ -2,6 +2,8 @@ const pull = require('../server/node_modules/pull-stream'); const crypto = require('crypto'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, requestScope, memoIndex } = require('./typed_log'); +const longText = require('../backend/long_text'); const logLimit = getConfig().ssbLogStream?.limit || 1000; module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { @@ -33,12 +35,13 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { const ssbClient = await openSsb(); const ssbKeys = require('../server/node_modules/ssb-keys'); const cfg = require('../server/ssb_config'); - const msgs = await new Promise((res, rej) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((e, m) => e ? rej(e) : res(m))) - ); + const msgs = await readForumLog(); + const authorOf = new Map(); + for (const m of msgs) if (m && m.key && m.value) authorOf.set(m.key, m.value.author); for (const m of msgs) { const c = m.value && m.value.content; if (!c || c.type !== 'tribe-keys') continue; + if (!c.tribeId || authorOf.get(c.tribeId) !== m.value.author) continue; const memberKeys = c.memberKeys; if (!memberKeys || typeof memberKeys !== 'object') continue; const boxed = memberKeys[ssbClient.id]; @@ -60,41 +63,63 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { return ssb; }; + const FORUM_TYPES = ['forum', 'forum-invite', 'forum-invite-tombstone', 'forum-open-invite', 'forum-open-invite-tombstone', 'tribe-keys', 'vote', 'tombstone', longText.CHUNK_TYPE]; + + const chunkIndexOf = (msgs) => longText.indexChunks( + msgs.filter(m => m.value && m.value.content && m.value.content.type === longText.CHUNK_TYPE), + (c) => { + if (!c || !c.encryptedPayload) return c; + const dec = decryptForumContent(c, null); + return dec && !dec._undecryptable ? dec : null; + } + ); + const fullText = (idx, c, author) => longText.joinText(c && c.text, c && c.chunks, longText.lookupIn(idx), author); + const readForumLog = async () => { const ssbClient = await openSsb(); - return new Promise((res, rej) => pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((e, m) => e ? rej(e) : res(m || [])))); + return readTyped(ssbClient, FORUM_TYPES, { limit: logLimit, withWindow: true }); }; - const scanForumOpenInvite = async (forumId) => { - const messages = await readForumLog(); + const authorsByKey = (messages) => { + const out = new Map(); + for (const m of messages) if (m && m.key && m.value) out.set(m.key, m.value.author); + return out; + }; + + const inviteTombstones = (messages, authorOf) => { const markerTomb = new Set(); const invTomb = new Set(); for (const m of messages) { const c = m.value && m.value.content; - if (!c) continue; - if (c.type === 'forum-open-invite-tombstone' && typeof c.target === 'string') markerTomb.add(c.target); - if (c.type === 'forum-invite-tombstone' && typeof c.target === 'string') invTomb.add(c.target); + if (!c || typeof c.target !== 'string' || authorOf.get(c.target) !== m.value.author) continue; + if (c.type === 'forum-open-invite-tombstone') markerTomb.add(c.target); + if (c.type === 'forum-invite-tombstone') invTomb.add(c.target); } + return { markerTomb, invTomb }; + }; + + const scanForumOpenInvite = async (forumId) => { + const messages = await readForumLog(); + const authorOf = authorsByKey(messages); + const forumAuthor = authorOf.get(forumId); + if (!forumAuthor) return null; + const { markerTomb, invTomb } = inviteTombstones(messages, authorOf); let best = null; for (const m of messages) { const c = m.value && m.value.content; if (!c || c.type !== 'forum-open-invite' || c.v !== 1) continue; if (c.target !== forumId || typeof c.code !== 'string') continue; + if (m.value.author !== forumAuthor) continue; if (markerTomb.has(m.key)) continue; if (c.inviteKey && invTomb.has(c.inviteKey)) continue; const ts = (m.value && m.value.timestamp) || 0; - if (!best || ts > best.ts) best = { code: c.code, by: c.by || m.value.author, markerKey: m.key, inviteKey: c.inviteKey || null, ts }; + if (!best || ts > best.ts) best = { code: c.code, by: m.value.author, markerKey: m.key, inviteKey: c.inviteKey || null, ts }; } return best; }; async function collectTombstones(ssbClient) { - return new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(buildValidatedTombstoneSet(msgs))) - ); - }); + return deletedOf(await readForumLog()); } async function findActiveVote(ssbClient, targetId, voter) { @@ -108,16 +133,38 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { }); } - async function aggregateVotes(ssbClient, targetId) { - const tombstoned = await collectTombstones(ssbClient); + const deletedOf = (msgs) => memoIndex('forum:deleted', msgs, buildValidatedTombstoneSet); + + function aggregateVotes(ssbClient, targetId, known = null) { + const store = requestScope.getStore(); + if (!store || store.method !== 'GET') return countVotes(ssbClient, targetId, known); + if (!store.forumVotes) store.forumVotes = new Map(); + let pending = store.forumVotes.get(targetId); + if (!pending) { pending = countVotes(ssbClient, targetId, known); store.forumVotes.set(targetId, pending); } + return pending; + } + + async function countVotes(ssbClient, targetId, known = null) { + const tombstoned = known || await collectTombstones(ssbClient); return new Promise((resolve, reject) => { - let positives = 0, negatives = 0; + const latest = new Map(); pull( ssbClient.links({ source: null, dest: targetId, rel: 'vote', values: true, keys: true }), pull.filter(link => link.value.content?.vote && !tombstoned.has(link.key)), pull.drain( - link => link.value.content.vote.value > 0 ? positives++ : negatives++, - err => err ? reject(err) : resolve({ positives, negatives }) + link => { + const author = link.value.author; + const ts = Number(link.value.timestamp) || 0; + const seq = Number(link.value.sequence) || 0; + const prev = latest.get(author); + if (!prev || ts > prev.ts || (ts === prev.ts && seq > prev.seq)) latest.set(author, { value: Number(link.value.content.vote.value) || 0, ts, seq }); + }, + err => { + if (err) return reject(err); + let positives = 0, negatives = 0; + for (const v of latest.values()) { if (v.value > 0) positives++; else if (v.value < 0) negatives++; } + resolve({ positives, negatives }); + } ) ); }); @@ -141,16 +188,89 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { } async function getMessageById(id) { - const ssbClient = await openSsb(); - const msgs = await new Promise((res, rej) => - pull(ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, data) => err ? rej(err) : res(data))) - ); + const msgs = await readForumLog(); const msg = msgs.find(m => m.key === id && m.value.content?.type === 'forum'); if (!msg) throw new Error('Message not found'); - return { key: msg.key, ...msg.value.content, timestamp: msg.value.timestamp }; + return { key: msg.key, ...msg.value.content, author: msg.value.author, timestamp: msg.value.timestamp }; } + const buildForums = async (ssbClient) => { + const msgs = await readForumLog(); + const deleted = deletedOf(msgs); + const chunkIdx = chunkIndexOf(msgs); + const decode = (m) => { + const c = m.value && m.value.content; + if (!c) return null; + if (c.encryptedPayload) { + const dec = decryptForumContent(c, m.value.content.root || m.key); + return (dec && !dec._undecryptable) ? dec : null; + } + return c; + }; + const forums = msgs + .map(m => ({ m, c: decode(m) })) + .filter(({ m, c }) => c && c.type === 'forum' && !c.root && !deleted.has(m.key)) + .map(({ m, c }) => ({ ...c, key: m.key, author: m.value.author, text: fullText(chunkIdx, c, m.value.author) })); + const forumsWithVotes = await Promise.all( + forums.map(async f => { + const { positives, negatives } = await aggregateVotes(ssbClient, f.key, deleted); + return { ...f, positiveVotes: positives, negativeVotes: negatives }; + }) + ); + const repliesByRoot = {}; + msgs.forEach(m => { + const cRaw = m.value && m.value.content; + if (!cRaw) return; + const root = cRaw.encryptedPayload ? null : cRaw.root; + if (!root) { + if (!cRaw.encryptedPayload) return; + const decReply = decryptForumContent(cRaw, null); + if (!decReply || decReply._undecryptable || decReply.type !== 'forum' || !decReply.root) return; + if (deleted.has(m.key)) return; + repliesByRoot[decReply.root] = repliesByRoot[decReply.root] || []; + repliesByRoot[decReply.root].push({ key: m.key, text: fullText(chunkIdx, decReply, m.value.author), author: m.value.author, timestamp: m.value.timestamp }); + return; + } + if (cRaw.type === 'forum' && root && !deleted.has(m.key)) { + repliesByRoot[root] = repliesByRoot[root] || []; + repliesByRoot[root].push({ key: m.key, text: fullText(chunkIdx, cRaw, m.value.author), author: m.value.author, timestamp: m.value.timestamp }); + } + }); + const final = await Promise.all( + forumsWithVotes.map(async f => { + const replies = repliesByRoot[f.key] || []; + await Promise.all(replies.map(async (r) => { + const { positives: rp, negatives: rn } = await aggregateVotes(ssbClient, r.key, deleted); + r.positiveVotes = rp; + r.negativeVotes = rn; + r.score = rp - rn; + })); + const replyPos = replies.reduce((sum, r) => sum + (r.positiveVotes || 0), 0); + const replyNeg = replies.reduce((sum, r) => sum + (r.negativeVotes || 0), 0); + const positiveVotes = f.positiveVotes + replyPos; + const negativeVotes = f.negativeVotes + replyNeg; + const score = positiveVotes - negativeVotes; + const participants = new Set(replies.map(r => r.author).concat(f.author)); + const messagesCount = replies.length + 1; + const lastMessage = + replies.length + ? replies.reduce((a, b) => (new Date(a.timestamp) > new Date(b.timestamp) ? a : b)) + : null; + return { + ...f, + positiveVotes, + negativeVotes, + score, + participants: Array.from(participants), + messagesCount, + lastMessage, + messages: replies + }; + }) + ); + return final; + }; + return { ingestKeys: async () => { await ingestOwnTribeKeys(); }, @@ -168,15 +288,14 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { votes_inhabitants: [], isPrivate: isPrivateFlag }; - let content = plainContent; let forumKey = null; - if (isPrivateFlag && ownCrypto && tribeCrypto) { - forumKey = ownCrypto.generateTribeKey(); - content = tribeCrypto.encryptContent(plainContent, [forumKey], true); - } - const result = await new Promise((resolve, reject) => - ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res)) + if (isPrivateFlag && ownCrypto && tribeCrypto) forumKey = ownCrypto.generateTribeKey(); + const seal = (c) => forumKey ? tribeCrypto.encryptContent(c, [forumKey], true) : c; + const publish = (c) => new Promise((resolve, reject) => + ssbClient.publish(seal(c), (err, res) => err ? reject(err) : resolve(res)) ); + const content = await longText.chunkContent(plainContent, 'text', { maxBytes: forumKey ? longText.CONTENT_BYTES.sealed : longText.CONTENT_BYTES.plain, publish }); + const result = await publish(content); if (forumKey) { ownCrypto.setKey(result.key, forumKey, 1); try { @@ -208,14 +327,17 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { votes_inhabitants: [] }; if (parentId) content.branch = parentId; + let key = null; if (isPrivate && ownCrypto && tribeCrypto) { - const key = lookupKey(forumId); + key = lookupKey(forumId); if (!key) throw new Error('Missing forum key — cannot reply to encrypted forum'); - content = tribeCrypto.encryptContent(content, [key], true); } - return new Promise((resolve, reject) => - ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res)) + const seal = (c) => key ? tribeCrypto.encryptContent(c, [key], true) : c; + const publish = (c) => new Promise((resolve, reject) => + ssbClient.publish(seal(c), (err, res) => err ? reject(err) : resolve(res)) ); + content = await longText.chunkContent(content, 'text', { maxBytes: key ? longText.CONTENT_BYTES.sealed : longText.CONTENT_BYTES.plain, publish }); + return publish(content); }, generateInvite: async (forumId) => { @@ -226,7 +348,7 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { const rawC = rawRoot.content; const dec = rawC && rawC.encryptedPayload ? decryptForumContent(rawC, forumId) : rawC; if (dec && dec._undecryptable) throw new Error('Forum is encrypted and cannot be decrypted'); - if (dec.author !== userId) throw new Error('Only the author can generate invites'); + if (rawRoot.author !== userId) throw new Error('Only the author can generate invites'); if (dec.isPrivate !== true) throw new Error('Only private forums use invitation codes'); const key = lookupKey(forumId); if (!key) throw new Error('Missing forum key'); @@ -252,7 +374,7 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { const rawC = rawRoot.content; const dec = rawC && rawC.encryptedPayload ? decryptForumContent(rawC, forumId) : rawC; if (dec && dec._undecryptable) throw new Error('Forum is encrypted and cannot be decrypted'); - if (dec.author !== userId) throw new Error('Only the author can generate invites'); + if (rawRoot.author !== userId) throw new Error('Only the author can generate invites'); if (dec.isPrivate !== true) throw new Error('Only private forums use invitation codes'); if (await scanForumOpenInvite(forumId)) throw new Error('An open invitation already exists'); const key = lookupKey(forumId); @@ -276,9 +398,7 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { let author = null; try { const rawRoot = await new Promise((res, rej) => ssbClient.get(forumId, (e, m) => e ? rej(e) : res(m))); - const rawC = rawRoot && rawRoot.content; - const dec = rawC && rawC.encryptedPayload ? decryptForumContent(rawC, forumId) : rawC; - author = dec && dec.author; + author = rawRoot && rawRoot.author; } catch (_) {} if (rec.by !== userId && author !== userId) throw new Error('Not allowed to remove this invitation'); await new Promise((resolve, reject) => ssbClient.publish({ type: 'forum-open-invite-tombstone', target: rec.markerKey, ts: new Date().toISOString() }, (err) => err ? reject(err) : resolve())); @@ -288,19 +408,15 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { joinByInvite: async (code) => { if (!ownCrypto || !tribeCrypto) throw new Error('Forum crypto unavailable'); const ssbClient = await openSsb(); - const messages = await new Promise((res, rej) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((e, m) => e ? rej(e) : res(m))) - ); - const invTomb = new Set(); - for (const m of messages) { - const c = m.value && m.value.content; - if (c && c.type === 'forum-invite-tombstone' && typeof c.target === 'string') invTomb.add(c.target); - } + const messages = await readForumLog(); + const authorOf = authorsByKey(messages); + const { invTomb } = inviteTombstones(messages, authorOf); let matched = null; for (const m of messages) { const c = m.value && m.value.content; if (!c || c.type !== 'forum-invite') continue; if (invTomb.has(m.key)) continue; + if (!authorOf.get(c.target) || authorOf.get(c.target) !== m.value.author) continue; try { const hash = tribeCrypto.hashInviteCode(code, c.salt); if (hash === c.codeHash) { matched = c; break; } @@ -356,81 +472,10 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { listAll: async filter => { const ssbClient = await openSsb(); - const msgs = await new Promise((res, rej) => - pull(ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, data) => err ? rej(err) : res(data))) - ); - const deleted = buildValidatedTombstoneSet(msgs); - const decode = (m) => { - const c = m.value && m.value.content; - if (!c) return null; - if (c.encryptedPayload) { - const dec = decryptForumContent(c, m.value.content.root || m.key); - return (dec && !dec._undecryptable) ? dec : null; - } - return c; - }; - const forums = msgs - .map(m => ({ m, c: decode(m) })) - .filter(({ m, c }) => c && c.type === 'forum' && !c.root && !deleted.has(m.key)) - .map(({ m, c }) => ({ ...c, key: m.key })); - const forumsWithVotes = await Promise.all( - forums.map(async f => { - const { positives, negatives } = await aggregateVotes(ssbClient, f.key); - return { ...f, positiveVotes: positives, negativeVotes: negatives }; - }) - ); - const repliesByRoot = {}; - msgs.forEach(m => { - const cRaw = m.value && m.value.content; - if (!cRaw) return; - const root = cRaw.encryptedPayload ? null : cRaw.root; - if (!root) { - if (!cRaw.encryptedPayload) return; - const decReply = decryptForumContent(cRaw, null); - if (!decReply || decReply._undecryptable || decReply.type !== 'forum' || !decReply.root) return; - if (deleted.has(m.key)) return; - repliesByRoot[decReply.root] = repliesByRoot[decReply.root] || []; - repliesByRoot[decReply.root].push({ key: m.key, text: decReply.text, author: decReply.author, timestamp: m.value.timestamp }); - return; - } - if (cRaw.type === 'forum' && root && !deleted.has(m.key)) { - repliesByRoot[root] = repliesByRoot[root] || []; - repliesByRoot[root].push({ key: m.key, text: cRaw.text, author: cRaw.author, timestamp: m.value.timestamp }); - } - }); - const final = await Promise.all( - forumsWithVotes.map(async f => { - const replies = repliesByRoot[f.key] || []; - for (let r of replies) { - const { positives: rp, negatives: rn } = await aggregateVotes(ssbClient, r.key); - r.positiveVotes = rp; - r.negativeVotes = rn; - r.score = rp - rn; - } - const replyPos = replies.reduce((sum, r) => sum + (r.positiveVotes || 0), 0); - const replyNeg = replies.reduce((sum, r) => sum + (r.negativeVotes || 0), 0); - const positiveVotes = f.positiveVotes + replyPos; - const negativeVotes = f.negativeVotes + replyNeg; - const score = positiveVotes - negativeVotes; - const participants = new Set(replies.map(r => r.author).concat(f.author)); - const messagesCount = replies.length + 1; - const lastMessage = - replies.length - ? replies.reduce((a, b) => (new Date(a.timestamp) > new Date(b.timestamp) ? a : b)) - : null; - return { - ...f, - positiveVotes, - negativeVotes, - score, - participants: Array.from(participants), - messagesCount, - lastMessage, - messages: replies - }; - }) - ); + const userId = ssbClient.id; + const store = requestScope.getStore(); + if (store && !store.forumAll) store.forumAll = buildForums(ssbClient); + const final = (await (store ? store.forumAll : buildForums(ssbClient))).slice(); const filtered = filter === 'mine' ? final.filter(f => f.author === userId) @@ -442,17 +487,14 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { getForumById: async id => { const ssbClient = await openSsb(); - const msgs = await new Promise((res, rej) => - pull(ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, data) => err ? rej(err) : res(data))) - ); - const deleted = buildValidatedTombstoneSet(msgs); + const msgs = await readForumLog(); + const deleted = deletedOf(msgs); const original = msgs.find(m => m.key === id && !deleted.has(m.key)); if (!original || original.value.content?.type !== 'forum') throw new Error('Forum not found'); const rawBase = original.value.content; const base = rawBase.encryptedPayload ? decryptForumContent(rawBase, id) : rawBase; if (base && base._undecryptable) throw new Error('Forum is encrypted and cannot be decrypted with available keys'); - const { positives, negatives } = await aggregateVotes(ssbClient, id); + const { positives, negatives } = await aggregateVotes(ssbClient, id, deleted); const replyAuthors = []; for (const m of msgs) { if (deleted.has(m.key)) continue; @@ -464,11 +506,13 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { if (!dec || dec._undecryptable) continue; rc = dec; } - if (rc.type === 'forum' && rc.root === id) replyAuthors.push(rc.author); + if (rc.type === 'forum' && rc.root === id) replyAuthors.push(m.value.author); } - const participants = Array.from(new Set(replyAuthors.concat(base.author).filter(Boolean))); + const participants = Array.from(new Set(replyAuthors.concat(original.value.author).filter(Boolean))); return { ...base, + author: original.value.author, + text: fullText(chunkIndexOf(msgs), base, original.value.author), key: id, positiveVotes: positives, negativeVotes: negatives, @@ -479,11 +523,9 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { getMessagesByForumId: async forumId => { const ssbClient = await openSsb(); - const msgs = await new Promise((res, rej) => - pull(ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, data) => err ? rej(err) : res(data))) - ); - const deleted = buildValidatedTombstoneSet(msgs); + const msgs = await readForumLog(); + const deleted = deletedOf(msgs); + const chunkIdx = chunkIndexOf(msgs); const decodeReply = (m) => { const c = m.value && m.value.content; if (!c || c.type !== 'forum') return null; @@ -500,18 +542,20 @@ module.exports = ({ cooler, tribeCrypto, forumCrypto }) => { .filter(r => r && !deleted.has(r.m.key)) .map(({ c, m }) => ({ key: m.key, - text: c.text, - author: c.author, + text: fullText(chunkIdx, c, m.value.author), + author: m.value.author, timestamp: m.value.timestamp, parent: c.branch || null })); - for (let r of replies) { - const { positives: rp, negatives: rn } = await aggregateVotes(ssbClient, r.key); - r.positiveVotes = rp; - r.negativeVotes = rn; - r.score = rp - rn; - } - const { positives: p, negatives: n } = await aggregateVotes(ssbClient, forumId); + const [, { positives: p, negatives: n }] = await Promise.all([ + Promise.all(replies.map(async (r) => { + const { positives: rp, negatives: rn } = await aggregateVotes(ssbClient, r.key, deleted); + r.positiveVotes = rp; + r.negativeVotes = rn; + r.score = rp - rn; + })), + aggregateVotes(ssbClient, forumId, deleted) + ]); const replyPos = replies.reduce((sum, r) => sum + (r.positiveVotes || 0), 0); const replyNeg = replies.reduce((sum, r) => sum + (r.negativeVotes || 0), 0); const positiveVotes = p + replyPos; diff --git a/src/models/games_model.js b/src/models/games_model.js index c148226..65b2eac 100644 --- a/src/models/games_model.js +++ b/src/models/games_model.js @@ -1,11 +1,14 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); +const { readTyped } = require('./typed_log'); const logLimit = getConfig().ssbLogStream?.limit || 5000; +const GAME_TYPES = ['gameScore']; + const VALID_GAMES = new Set([ 'cocoland', 'ecoinflow', 'neoninfiltrator', 'spaceinvaders', 'arkanoid', 'pingpong', 'asteroids', 'tiktaktoe', 'flipflop', - '8ball', 'artillery', 'labyrinth', 'cocoman', 'tetris' + '8ball', 'artillery', 'labyrinth', 'cocoman', 'tetris', 'minesweeper' ]); module.exports = ({ cooler }) => { @@ -16,12 +19,7 @@ module.exports = ({ cooler }) => { }; async function readAll(ssbClient) { - return new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, results) => (err ? reject(err) : resolve(results))) - ); - }); + return readTyped(ssbClient, GAME_TYPES, { limit: logLimit }); } return { diff --git a/src/models/housing_model.js b/src/models/housing_model.js index 0b7bbb3..1f06356 100644 --- a/src/models/housing_model.js +++ b/src/models/housing_model.js @@ -2,7 +2,9 @@ const pull = require("../server/node_modules/pull-stream") const moment = require("../server/node_modules/moment") const categories = require("../backend/opinion_categories") const { getConfig } = require("../configs/config-manager.js") -const { dedupeByPreferring } = require('../backend/dedupe') +const { dedupeByPreferring, versionGroups } = require('../backend/dedupe') +const { buildValidatedTombstoneSet } = require('./tombstone_validator') +const { readTyped, memoIndex } = require('./typed_log') const logLimit = getConfig().ssbLogStream?.limit || 1000 const HOUSING_TYPES = ["sale", "rent", "couchsurfing"] @@ -111,21 +113,14 @@ module.exports = ({ cooler, tribeCrypto }) => { }) }) - const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ) - ) + const HOUSING_MSG_TYPES = ["housing", "housingOpinion", "housingRequest", "tombstone"] + + const readAll = async (ssbClient) => readTyped(ssbClient, HOUSING_MSG_TYPES, { limit: logLimit, withWindow: true, withPrivate: true }) const buildIndex = (messages, ssbClient) => { - const tomb = new Set() + const tomb = buildValidatedTombstoneSet(messages) const nodes = new Map() - const parent = new Map() - const child = new Map() - const naiveReplaces = new Map() - const requestLatest = new Map() + const requestMsgs = [] const opinionMsgs = [] for (const m of messages) { @@ -134,11 +129,8 @@ module.exports = ({ cooler, tribeCrypto }) => { const c = v.content if (!c || typeof c !== "object") continue - if (c.type === "tombstone" && c.target) { tomb.add(c.target); continue } - if (c.type === "housing") { - nodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) - if (c.replaces) naiveReplaces.set(key, c.replaces) + nodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) continue } @@ -150,42 +142,11 @@ module.exports = ({ cooler, tribeCrypto }) => { if (c.type === "housingRequest" && c.housingId) { const author = v.author if (!author) continue - const ts = v.timestamp || m.timestamp || 0 - const k = `${c.housingId}::${author}` - const prev = requestLatest.get(k) - if (!prev || ts >= prev.ts) requestLatest.set(k, { ts, value: !!c.value, author, housingId: c.housingId }) + requestMsgs.push({ ts: v.timestamp || m.timestamp || 0, value: !!c.value, author, housingId: c.housingId }) continue } } - for (const [key, replacesId] of naiveReplaces.entries()) { - const node = nodes.get(key) - if (!node) continue - const orig = nodes.get(replacesId) - if (!orig) continue - if (String(orig.author) !== String(node.author)) { nodes.delete(key); continue } - parent.set(key, replacesId) - child.set(replacesId, key) - } - - const rootOf = (id) => { - let cur = id, guard = 0 - while (parent.has(cur) && guard++ < 100000) cur = parent.get(cur) - return cur - } - - const tipOf = (id) => { - let cur = id, guard = 0 - while (child.has(cur) && guard++ < 100000) cur = child.get(cur) - return cur - } - - const roots = new Set() - for (const id of nodes.keys()) roots.add(rootOf(id)) - - const tipByRoot = new Map() - for (const r of roots) tipByRoot.set(r, tipOf(r)) - if (ssbClient) { for (const m of messages) { if (typeof m.value?.content !== 'string') continue @@ -195,14 +156,26 @@ module.exports = ({ cooler, tribeCrypto }) => { if (!c || c.type !== 'housingRequest' || !c.housingId) continue const author = dec.value.author if (!author) continue - const ts = dec.value.timestamp || m.timestamp || 0 - const k = `${c.housingId}::${author}` - const prev = requestLatest.get(k) - if (!prev || ts >= prev.ts) requestLatest.set(k, { ts, value: !!c.value, author, housingId: c.housingId }) + requestMsgs.push({ ts: dec.value.timestamp || m.timestamp || 0, value: !!c.value, author, housingId: c.housingId }) } catch {} } } + const vg = versionGroups(Array.from(nodes.values()).map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === "string" ? n.c.replaces : null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))) + const rootOf = vg.rootOf + const tipOf = vg.tipOf + + const tipByRoot = new Map() + for (const r of vg.roots) tipByRoot.set(r, tipOf(r)) + + const requestLatest = new Map() + for (const req of requestMsgs) { + const housingId = rootOf(req.housingId) + const k = `${housingId}::${req.author}` + const prev = requestLatest.get(k) + if (!prev || req.ts >= prev.ts) requestLatest.set(k, { ...req, housingId }) + } + const requestsByRoot = new Map() const everRequestedByRoot = new Map() for (const { housingId, author, value } of requestLatest.values()) { @@ -236,7 +209,7 @@ module.exports = ({ cooler, tribeCrypto }) => { return { opinions, opinions_inhabitants: voters } } - return { tomb, nodes, parent, child, rootOf, tipOf, tipByRoot, requestsByRoot, everRequestedByRoot, aggregateFor } + return { tomb, nodes, vg, rootOf, tipOf, tipByRoot, requestsByRoot, everRequestedByRoot, aggregateFor } } const buildObject = (node, rootId, idx, viewer) => { @@ -355,7 +328,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const idx = buildIndex(await readAll(ssbClient), ssbClient) const tip = idx.tipOf(id) - if (idx.tomb.has(tip)) throw new Error("Housing not found") + if (idx.tomb.has(tip) || idx.vg.isForeignCopy(idx.rootOf(tip))) throw new Error("Housing not found") return tip }, @@ -363,7 +336,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const idx = buildIndex(await readAll(ssbClient), ssbClient) const tip = idx.tipOf(id) - if (idx.tomb.has(tip)) throw new Error("Housing not found") + if (idx.tomb.has(tip) || idx.vg.isForeignCopy(idx.rootOf(tip))) throw new Error("Housing not found") return idx.rootOf(tip) }, @@ -371,7 +344,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const idx = buildIndex(await readAll(ssbClient), ssbClient) const tipId = idx.tipOf(id) - if (idx.tomb.has(tipId)) throw new Error("Housing not found") + if (idx.tomb.has(tipId) || idx.vg.isForeignCopy(idx.rootOf(tipId))) throw new Error("Housing not found") const node = idx.nodes.get(tipId) if (!node || !node.c) throw new Error("Housing not found") if ((node.author || node.c.author) !== ssbClient.id) throw new Error("Unauthorized") @@ -433,7 +406,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const idx = buildIndex(await readAll(ssbClient), ssbClient) const tipId = idx.tipOf(id) - if (idx.tomb.has(tipId)) throw new Error("Housing not found") + if (idx.tomb.has(tipId) || idx.vg.isForeignCopy(idx.rootOf(tipId))) throw new Error("Housing not found") const node = idx.nodes.get(tipId) if (!node) throw new Error("Housing not found") if ((node.author || node.c.author) !== ssbClient.id) throw new Error("Unauthorized") @@ -468,7 +441,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const me = ssbClient.id const idx = buildIndex(await readAll(ssbClient), ssbClient) const tipId = idx.tipOf(id) - if (idx.tomb.has(tipId)) throw new Error("Housing not found") + if (idx.tomb.has(tipId) || idx.vg.isForeignCopy(idx.rootOf(tipId))) throw new Error("Housing not found") const node = idx.nodes.get(tipId) if (!node) throw new Error("Housing not found") const rootId = idx.rootOf(tipId) @@ -539,9 +512,9 @@ module.exports = ({ cooler, tribeCrypto }) => { async getHousingById(id, viewerId = null) { const ssbClient = await openSsb() const viewer = viewerId || ssbClient.id - const idx = buildIndex(await readAll(ssbClient), ssbClient) + const idx = memoIndex('housing', await readAll(ssbClient), (m) => buildIndex(m, ssbClient)) const tipId = idx.tipOf(id) - if (idx.tomb.has(tipId)) throw new Error("Housing not found") + if (idx.tomb.has(tipId) || idx.vg.isForeignCopy(idx.rootOf(tipId))) throw new Error("Housing not found") const node = idx.nodes.get(tipId) if (!node) throw new Error("Housing not found") const rootId = idx.rootOf(tipId) diff --git a/src/models/images_model.js b/src/models/images_model.js index 494bd88..e9a38e1 100644 --- a/src/models/images_model.js +++ b/src/models/images_model.js @@ -2,7 +2,8 @@ const pull = require("../server/node_modules/pull-stream"); const { getConfig } = require("../configs/config-manager.js"); const categories = require("../backend/opinion_categories"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -31,13 +32,9 @@ module.exports = ({ cooler }) => { return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const IMAGE_TYPES = ["image", "imageOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, IMAGE_TYPES, { limit: logLimit }); const getMsg = async (ssbClient, key) => new Promise((resolve, reject) => { @@ -47,9 +44,6 @@ module.exports = ({ cooler }) => { const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const opinionMsgs = []; for (const m of messages) { @@ -65,48 +59,16 @@ module.exports = ({ cooler }) => { const ts = v.timestamp || m.timestamp || 0; let sizeBytes = 0; try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; } - nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); } - for (const [k, t] of Array.from(parent.entries())) { - const cn = nodes.get(k); - const pn = nodes.get(t); - if (!pn) { parent.delete(k); if (child.get(t) === k) child.delete(t); continue; } - if (!cn || String(cn.author) !== String(pn.author)) { parent.delete(k); if (child.get(t) === k) child.delete(t); nodes.delete(k); } - } - - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } - const forward = new Map(); - for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); - const resolveGroup = (root) => { const contentTip = contentTipOf(root); const contentNode = nodes.get(contentTip) || nodes.get(root); @@ -123,9 +85,9 @@ module.exports = ({ cooler }) => { }; const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); - return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildImage = (node, rootId, viewerId, agg) => { @@ -141,6 +103,7 @@ module.exports = ({ cooler }) => { author: node.author, title: c.title || "", description: c.description || "", + license: c.license || "", mapUrl: c.mapUrl || "", opinions: agg ? agg.opinions : (c.opinions || {}), opinions_inhabitants: voters, @@ -167,16 +130,12 @@ module.exports = ({ cooler }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(messages); - let tip = id; - while (idx.forward.has(tip)) tip = idx.forward.get(tip); - if (idx.tomb.has(tip)) throw new Error("Image not found"); - - let root = tip; - while (idx.parent.has(root)) root = idx.parent.get(root); + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Image not found"); return root; }, - async createImage(blobMarkdown, tagsRaw, title, description, mapUrl) { + async createImage(blobMarkdown, tagsRaw, title, description, mapUrl, license) { const ssbClient = await openSsb(); const blobId = parseBlobId(blobMarkdown); const tags = normalizeTags(tagsRaw) || []; @@ -193,7 +152,8 @@ module.exports = ({ cooler }) => { description: description || "", mapUrl: mapUrl || "", opinions: {}, - opinions_inhabitants: [] + opinions_inhabitants: [], + ...(license ? { license } : {}) }; return new Promise((resolve, reject) => { @@ -201,7 +161,7 @@ module.exports = ({ cooler }) => { }); }, - async updateImageById(id, blobMarkdown, tagsRaw, title, description, mapUrl) { + async updateImageById(id, blobMarkdown, tagsRaw, title, description, mapUrl, license) { const ssbClient = await openSsb(); const userId = ssbClient.id; const tipId = await this.resolveCurrentId(id); @@ -225,7 +185,8 @@ module.exports = ({ cooler }) => { description: description !== undefined ? description || "" : oldMsg.content.description || "", mapUrl: mapUrl !== undefined ? mapUrl || "" : oldMsg.content.mapUrl || "", createdAt: oldMsg.content.createdAt, - updatedAt: now + updatedAt: now, + ...(license !== undefined ? { license } : {}) }; const tombstone = { type: "tombstone", target: tipId, deletedAt: now, author: userId }; @@ -311,10 +272,10 @@ module.exports = ({ cooler }) => { const viewer = viewerId || ssbClient.id; const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); + const idx = memoIndex('images', messages, buildIndex); const rootId = idx.rootOf(id); const agg = idx.resolveGroup(rootId); - if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Image not found"); + if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Image not found"); return buildImage(agg.contentNode, rootId, viewer, agg); }, diff --git a/src/models/industry_model.js b/src/models/industry_model.js index 5afb0fa..5593924 100644 --- a/src/models/industry_model.js +++ b/src/models/industry_model.js @@ -1,6 +1,7 @@ const pull = require("../server/node_modules/pull-stream") const { getConfig } = require("../configs/config-manager.js") const { buildValidatedTombstoneSet } = require('./tombstone_validator') +const { readTyped } = require('./typed_log') const logLimit = (getConfig().ssbLogStream && getConfig().ssbLogStream.limit) || 1000 module.exports = ({ cooler }) => { @@ -42,13 +43,10 @@ module.exports = ({ cooler }) => { return Number.isFinite(x) && x >= 0 ? x : 0 } + const INDUSTRY_TYPES = [TYPE, "industryMember", "industryInvite", "industryApply", "industryVote", "industryOpinion", "industryBlueprint", "industryBuild", "industryBuildStatus", "industryBuildDates", "industryBuildUpdate", "industryContribution", "industryAllocation", "tombstone"] + async function getAllMsgs(ssbClient) { - return new Promise((r, j) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((e, m) => (e ? j(e) : r(m))) - ) - }) + return readTyped(ssbClient, INDUSTRY_TYPES, { limit: logLimit }) } function chainMaps(nodeMap) { @@ -566,6 +564,7 @@ module.exports = ({ cooler }) => { } const f = String(filter || "ALL").toUpperCase() if (f === "MINE") list = list.filter(x => x.steward === uid) + else if (f === "RECENT") list = list.filter(x => (Date.parse(x.createdAt || "") || 0) >= Date.now() - 86400000) else if (f === "MEMBER") list = list.filter(x => Array.isArray(x.members) && x.members.includes(uid)) else if (f === "ACTIVE") list = list.filter(x => x.status === "ACTIVE") else if (f === "PAUSED") list = list.filter(x => x.status === "PAUSED") diff --git a/src/models/inhabitants_model.js b/src/models/inhabitants_model.js index ce98ee7..a594a75 100644 --- a/src/models/inhabitants_model.js +++ b/src/models/inhabitants_model.js @@ -1,11 +1,7 @@ const pull = require('../server/node_modules/pull-stream'); -const ssbClientGUI = require("../client/gui"); -const coolerInstance = ssbClientGUI({ offline: require('../server/ssb_config').offline }); +const { readTyped, CONTENT_TYPES, requestScope, authorActivity } = require('./typed_log'); +const { isContentVisibleTo } = require('./content_visibility'); const models = require("../models/main_models"); -const { about, friend } = models({ - cooler: coolerInstance, - isPublic: require('../server/ssb_config').public, -}); const { getConfig } = require('../configs/config-manager.js'); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -15,36 +11,48 @@ function toImageUrl(imgId, size=256){ return `/image/${size}/${encodeURIComponent(imgId)}`; } -module.exports = ({ cooler }) => { +const MIN_SUGGESTION_AFFINITY = 0.1; +const MAX_SUGGESTED = 12; + +module.exports = ({ cooler, tribesModel = null, dataModel = null }) => { + const { about, friend } = models({ cooler, isPublic: require('../server/ssb_config').public }); let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; - async function getLastKarmaScore(feedId) { + const bytesPerAuthor = (ssbClient) => readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }).then((msgs) => { + const acc = {}; + for (const m of msgs) { + const author = m && m.value && m.value.author; + if (!author) continue; + try { acc[author] = (acc[author] || 0) + Buffer.byteLength(JSON.stringify(m.value), 'utf8'); } catch (_) {} + } + return acc; + }).catch(() => ({})); + + async function latestKarmaByAuthor() { const ssbClient = await openSsb(); return new Promise(resolve => { + const out = new Map(); const src = ssbClient.messagesByType ? ssbClient.messagesByType({ type: "karmaScore", reverse: true }) : ssbClient.createLogStream && ssbClient.createLogStream({ reverse: true }); - if (!src) return resolve(0); + if (!src) return resolve(out); pull( src, - pull.filter(msg => { + pull.drain(msg => { const v = msg.value || msg; const c = v.content || {}; - return v.author === feedId && c.type === "karmaScore" && typeof c.karmaScore !== "undefined"; - }), - pull.take(1), - pull.collect((err, arr) => { - if (err || !arr || !arr.length) return resolve(0); - const v = arr[0].value || arr[0]; - resolve(v.content.karmaScore || 0); - }) + if (c.type !== "karmaScore" || typeof c.karmaScore === "undefined" || out.has(v.author)) return; + out.set(v.author, c.karmaScore || 0); + }, () => resolve(out)) ); }); } async function getLastActivityTimestamp(feedId) { const ssbClient = await openSsb(); + const activity = await authorActivity(ssbClient); + if (activity) return activity.get(feedId) || null; const norm = (t) => (t && t < 1e12 ? t * 1000 : t || 0); return new Promise((resolve) => { pull( @@ -82,21 +90,34 @@ module.exports = ({ cooler }) => { } }; - async function listAllBase(ssbClient) { - const authorsMsgs = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit, reverse: true }), - pull.filter(msg => !!msg.value?.author && msg.value?.content?.type !== 'tombstone'), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); - const uniqueFeedIds = Array.from(new Set(authorsMsgs.map(r => r.value.author).filter(Boolean))); + const publicMode = require('../server/ssb_config').public; + const imageUrlFrom = (raw, size = 256) => { + const img = raw == null || raw.link == null ? `&${"0".repeat(43)}=.sha256` : (typeof raw.link === 'string' ? raw.link : raw); + const id = typeof img === 'string' ? img : (img && (img.link || img.url)); + return toImageUrl(id, size); + }; + + async function listAllBase(ssbClient, onlyIds = null) { + const store = requestScope.getStore(); + const memoKey = onlyIds ? null : 'inhabitantsBase'; + if (memoKey && store && store[memoKey]) return store[memoKey]; + const pending = buildBase(ssbClient, onlyIds); + if (memoKey && store) store[memoKey] = pending; + return pending; + } + + async function buildBase(ssbClient, onlyIds) { + const only = onlyIds ? new Set(onlyIds) : null; + const authorsMsgs = (await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true })).filter(msg => !!msg.value?.author && msg.value?.content?.type !== 'tombstone').reverse(); + const uniqueFeedIds = Array.from(new Set(authorsMsgs.map(r => r.value.author).filter(Boolean))).filter(id => !only || only.has(id)); + const profiles = publicMode ? null : await about.profiles().catch(() => null); const users = await Promise.all( uniqueFeedIds.map(async (feedId) => { - const rawName = await about.name(feedId); + const p = profiles ? (profiles.get(feedId) || {}) : null; + const rawName = p ? (p.name || feedId.slice(1, 9)) : await about.name(feedId); const name = rawName || feedId.slice(0, 10); - const description = await about.description(feedId); - const photo = await fetchUserImageUrl(feedId, 256); + const description = p ? (p.description || "") : await about.description(feedId); + const photo = p ? imageUrlFrom(p.image, 256) : await fetchUserImageUrl(feedId, 256); const lastActivityTs = await getLastActivityTimestamp(feedId); const { bucket, range } = bucketLastActivity(lastActivityTs); return { id: feedId, name, description, photo, lastActivityTs, lastActivityBucket: bucket, lastActivityRange: range }; @@ -115,8 +136,14 @@ module.exports = ({ cooler }) => { } return { + async countInhabitants() { + const ssbClient = await openSsb(); + const msgs = await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }); + return new Set(msgs.filter(msg => !!msg.value?.author && msg.value?.content?.type !== 'tombstone').map(msg => msg.value.author)).size; + }, + async listInhabitants(options = {}) { - const { filter = 'all', search = '', location = '', language = '', skills = '', includeInactive = false } = options; + const { filter = 'all', search = '', location = '', language = '', skills = '', includeInactive = false, ids = null } = options; const ssbClient = await openSsb(); const userId = ssbClient.id; @@ -131,7 +158,7 @@ module.exports = ({ cooler }) => { } if (filter === 'all' || filter === 'TOP KARMA' || filter === 'TOP ACTIVITY' || filter === 'TOP INACTIVITY' || filter === 'TOP ECO') { - let users = await listAllBase(ssbClient); + let users = (await listAllBase(ssbClient, ids)).slice(); if (filter !== 'TOP ACTIVITY' && filter !== 'TOP INACTIVITY') { users = filterInactive(users); } @@ -143,23 +170,12 @@ module.exports = ({ cooler }) => { (u.id || '').toLowerCase().includes(q) ); } - const bytesByAuthor = await new Promise((res) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => { - if (err || !Array.isArray(msgs)) return res({}); - const acc = {}; - for (const m of msgs) { - const author = m && m.value && m.value.author; - if (!author) continue; - try { acc[author] = (acc[author] || 0) + Buffer.byteLength(JSON.stringify(m.value), 'utf8'); } catch (_) {} - } - res(acc); - }) - ); - }); + const store = requestScope.getStore(); + if (store && !store.inhabitantBytes) store.inhabitantBytes = bytesPerAuthor(ssbClient); + const bytesByAuthor = await (store ? store.inhabitantBytes : bytesPerAuthor(ssbClient)); + const karmaOf = await latestKarmaByAuthor(); const withMetrics = await Promise.all(users.map(async u => { - const karmaScore = await getLastKarmaScore(u.id); + const karmaScore = karmaOf.get(u.id) || 0; const bytes = (bytesByAuthor && bytesByAuthor[u.id]) || 0; const carbonGrams = (bytes / (1024 * 1024)) * 0.095; if (filter === 'TOP ECO') { @@ -199,80 +215,54 @@ module.exports = ({ cooler }) => { if (filter === 'SUGGESTED') { const base = await listAllBase(ssbClient); const active = filterInactive(base); - const cvRecords = await new Promise((res) => { - pull( - ssbClient.createLogStream({ limit: logLimit, reverse: true }), - pull.filter(msg => msg && msg.value && msg.value.content && msg.value.content.type === 'curriculum'), - pull.collect((err, msgs) => err ? res([]) : res(msgs)) - ); - }); - const cvByAuthor = new Map(); - for (const r of cvRecords) { - const c = r.value && r.value.content; - if (c && c.author && !cvByAuthor.has(c.author)) cvByAuthor.set(c.author, c); - } - const extractSkills = (cv) => cv ? [ - ...(cv.personalSkills || []), - ...(cv.oasisSkills || []), - ...(cv.educationalSkills || []), - ...(cv.professionalSkills || []) - ].map(s => String(s || '').toLowerCase()).filter(Boolean) : []; - const mecv = await this.getCVByUserId().catch(() => null); - const mySkills = extractSkills(mecv); + const affinities = dataModel ? await dataModel.authorAffinities().catch(() => null) : null; + const byAuthor = affinities ? affinities.byAuthor : new Map(); + const karmaOf = await latestKarmaByAuthor(); const rels = await Promise.all( active.map(async u => { if (u.id === userId) return null; const rel = await friend.getRelationship(u.id).catch(() => ({})); const n = normalizeRel(rel); if (n.iFollow || n.blocking || n.blockedBy) return null; - const karmaScore = await getLastKarmaScore(u.id); - const theirSkills = extractSkills(cvByAuthor.get(u.id)); - const commonSkills = mySkills.length && theirSkills.length - ? Array.from(new Set(mySkills.filter(s => theirSkills.includes(s)))) - : []; - const followsMeBonus = n.followsMe ? 20 : 0; - const karmaBonus = Math.min(20, Math.log10(1 + Math.max(0, karmaScore)) * 5); - const skillBonus = commonSkills.length * 4; - const activityBonus = u.lastActivityBucket === 'green' ? 5 : (u.lastActivityBucket === 'orange' ? 2 : 0); - const suggestionScore = followsMeBonus + karmaBonus + skillBonus + activityBonus; - return { user: u, rel: n, karmaScore, commonSkills, suggestionScore }; + const aff = byAuthor.get(u.id) || { score: 0, common: [], reasons: [] }; + const tribeMate = aff.reasons.includes('tribe'); + if (aff.score < MIN_SUGGESTION_AFFINITY && !n.followsMe && !tribeMate) return null; + const social = (n.followsMe ? 0.15 : 0) + (tribeMate ? 0.1 : 0); + const activityBonus = u.lastActivityBucket === 'green' ? 0.05 : (u.lastActivityBucket === 'orange' ? 0.02 : 0); + const suggestionScore = Math.min(1, aff.score + social + activityBonus); + const karmaScore = karmaOf.get(u.id) || 0; + return { user: u, rel: n, karmaScore, commonSkills: aff.common, reasons: aff.reasons, suggestionScore }; }) ); - const candidates = rels.filter(Boolean).filter(x => x.suggestionScore > 0); + const candidates = rels.filter(Boolean); const enriched = candidates.map(x => ({ ...x.user, karmaScore: x.karmaScore, followsYou: x.rel.followsMe, commonSkills: x.commonSkills, + reasons: x.reasons, mutualCount: x.rel.followsMe ? 1 : 0, - suggestionScore: x.suggestionScore + suggestionScore: x.suggestionScore, + affinity: x.suggestionScore })); const unique = Array.from(new Map(enriched.map(u => [u.id, u])).values()); return unique.sort((a, b) => (b.suggestionScore || 0) - (a.suggestionScore || 0) || (b.karmaScore || 0) - (a.karmaScore || 0) || (b.lastActivityTs || 0) - (a.lastActivityTs || 0) - ); + ).slice(0, MAX_SUGGESTED); } if (filter === 'CVs') { - const records = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit, reverse: true}), - pull.filter(msg => - msg.value.content?.type === 'curriculum' && - msg.value.content?.type !== 'tombstone' - ), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); + const records = (await readTyped(ssbClient, ['curriculum'], { limit: logLimit })).filter(msg => msg.value.content?.type === 'curriculum').reverse(); let cvs = records.map(r => r.value.content); cvs = Array.from(new Map(cvs.map(u => [u.author, u])).values()); if (filter === 'CVs') { + const profiles = publicMode ? null : await about.profiles().catch(() => null); let out = await Promise.all(cvs.map(async c => { - const photo = await fetchUserImageUrl(c.author, 256); + const photo = profiles ? imageUrlFrom((profiles.get(c.author) || {}).image, 256) : await fetchUserImageUrl(c.author, 256); const lastActivityTs = await getLastActivityTimestamp(c.author); const { bucket, range } = bucketLastActivity(lastActivityTs); const base = this._normalizeCurriculum(c, photo); @@ -308,6 +298,7 @@ module.exports = ({ cooler }) => { name: c.name, description: c.description, photo, + pdf: c.pdf || null, skills: [ ...(c.personalSkills || []), ...(c.oasisSkills || []), @@ -324,6 +315,14 @@ module.exports = ({ cooler }) => { }; }, + async listSupporters(feedId) { + const ssbClient = await openSsb(); + const graph = await new Promise((resolve) => ssbClient.friends.graph((err, g) => resolve(err ? {} : (g || {})))); + return Object.entries(graph) + .filter(([source, edges]) => source !== feedId && edges && (edges[feedId] === true || Number(edges[feedId]) > 0)) + .map(([source]) => source); + }, + async getLatestAboutById(id) { const ssbClient = await openSsb(); const records = await new Promise((res, rej) => { @@ -369,21 +368,8 @@ module.exports = ({ cooler }) => { const isOwner = viewer === target; const arr = (v) => Array.isArray(v) ? v : []; const up = (v) => String(v || '').toUpperCase(); - const COUNTED = new Set(['post','event','task','forum','tribe','market','job','housing','project','industry','shop','image','video','audio','document','bookmark','transfer','map']); - const accessible = (type, c) => { - if (c.encryptedPayload) return false; - switch (type) { - case 'task': return up(c.isPublic) !== 'PRIVATE' || isOwner || arr(c.assignees).includes(viewer); - case 'event': return String(c.isPublic || '').toLowerCase() !== 'private' || isOwner || arr(c.attendees).includes(viewer); - case 'forum': return c.isPrivate !== true || isOwner; - case 'job': return up(c.visibility) !== 'HIDDEN' || isOwner || arr(c.subscribers).includes(viewer); - case 'housing': return up(c.visibility) !== 'HIDDEN' || isOwner; - case 'market': return up(c.visibility) !== 'HIDDEN' || isOwner; - case 'shop': return up(c.visibility) !== 'CLOSED' || isOwner; - case 'tribe': { const st = up(c.status); return !(st === 'PRIVATE' || st === 'INVITE-ONLY') || isOwner || arr(c.members).includes(viewer); } - default: return true; - } - }; + const COUNTED = new Set(['post','event','task','forum','market','job','housing','project','industry','shop','image','video','audio','document','bookmark','transfer','map']); + const accessible = (type, c) => isOwner || isContentVisibleTo(type, c, viewer, target); const counts = {}; await new Promise((resolve) => { pull( @@ -399,6 +385,13 @@ module.exports = ({ cooler }) => { }, () => resolve()) ); }); + if (tribesModel) { + try { + const visible = await tribesModel.listTribesForViewer(viewer); + const n = visible.filter(t => String(t.author) === String(target)).length; + if (n > 0) counts.tribe = n; + } catch (_) {} + } return counts; }, @@ -435,16 +428,7 @@ module.exports = ({ cooler }) => { addAll(tokenize(job.requirements)); if (keywords.size === 0) return []; - const records = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit, reverse: true }), - pull.filter(msg => - msg.value?.content?.type === 'curriculum' && - msg.value?.content?.type !== 'tombstone' - ), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); + const records = (await readTyped(ssbClient, ['curriculum'], { limit: logLimit })).filter(msg => msg.value?.content?.type === 'curriculum').reverse(); let cvs = records.map(r => r.value.content); cvs = Array.from(new Map(cvs.map(u => [u.author, u])).values()); cvs = cvs.filter(c => String(c.visibility || 'PUBLIC').toUpperCase() !== 'HIDDEN'); diff --git a/src/models/jobs_model.js b/src/models/jobs_model.js index c1c4180..d396483 100644 --- a/src/models/jobs_model.js +++ b/src/models/jobs_model.js @@ -2,9 +2,12 @@ const pull = require("../server/node_modules/pull-stream") const moment = require("../server/node_modules/moment") const { getConfig } = require("../configs/config-manager.js") const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeByPreferring } = require('../backend/dedupe') +const { dedupeByPreferring, versionGroups } = require('../backend/dedupe') +const { readTyped, memoIndex } = require('./typed_log') const logLimit = getConfig().ssbLogStream?.limit || 1000 +const JOB_TYPES = ["job", "job_sub", "tombstone"] + const norm = (s) => String(s || "").trim().toLowerCase() const toNum = (v) => { const n = parseFloat(String(v ?? "").replace(",", ".")) @@ -40,20 +43,12 @@ module.exports = ({ cooler, tribeCrypto }) => { const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb } const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ) - ) + readTyped(ssbClient, JOB_TYPES, { limit: logLimit, withWindow: true, withPrivate: true }) const buildIndex = (messages, ssbClient) => { - const tomb = new Set() + const tomb = buildValidatedTombstoneSet(messages) const jobNodes = new Map() - const parent = new Map() - const child = new Map() - const naiveReplaces = new Map() - const jobSubLatest = new Map() + const subMsgs = [] for (const m of messages) { const key = m.key @@ -61,65 +56,19 @@ module.exports = ({ cooler, tribeCrypto }) => { const c = v.content if (!c) continue - if (c.type === "tombstone" && c.target) { - tomb.add(c.target) - continue - } - if (c.type === "job") { - jobNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) - if (c.replaces) naiveReplaces.set(key, c.replaces) + jobNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) continue } if (c.type === "job_sub" && c.jobId) { const author = v.author if (!author) continue - const ts = v.timestamp || m.timestamp || 0 - const jobId = c.jobId - const k = `${jobId}::${author}` - const prev = jobSubLatest.get(k) - if (!prev || ts >= prev.ts) jobSubLatest.set(k, { ts, value: !!c.value, author, jobId }) + subMsgs.push({ ts: v.timestamp || m.timestamp || 0, value: !!c.value, author, jobId: c.jobId }) continue } } - for (const [key, replacesId] of naiveReplaces.entries()) { - const node = jobNodes.get(key) - if (!node) continue - const orig = jobNodes.get(replacesId) - if (!orig) continue - if (String(orig.author) !== String(node.author)) { jobNodes.delete(key); continue } - parent.set(key, replacesId) - child.set(replacesId, key) - } - - const rootOf = (id) => { - let cur = id - while (parent.has(cur)) cur = parent.get(cur) - return cur - } - - const roots = new Set() - for (const id of jobNodes.keys()) roots.add(rootOf(id)) - - const tipOf = (id) => { - let cur = id - while (child.has(cur)) cur = child.get(cur) - return cur - } - - const tipByRoot = new Map() - for (const r of roots) tipByRoot.set(r, tipOf(r)) - - const subsByJob = new Map() - for (const { jobId, author, value } of jobSubLatest.values()) { - if (!subsByJob.has(jobId)) subsByJob.set(jobId, new Set()) - const set = subsByJob.get(jobId) - if (value) set.add(author) - else set.delete(author) - } - if (ssbClient) { for (const m of messages) { if (typeof m.value?.content !== 'string') continue @@ -130,16 +79,30 @@ module.exports = ({ cooler, tribeCrypto }) => { if (c.type !== 'job_sub' || !c.jobId) continue const author = dec.value.author if (!author) continue - const ts = dec.value.timestamp || m.timestamp || 0 - const jobId = c.jobId - const k = `${jobId}::${author}` - const prev = jobSubLatest.get(k) - if (!prev || ts >= prev.ts) jobSubLatest.set(k, { ts, value: !!c.value, author, jobId }) + subMsgs.push({ ts: dec.value.timestamp || m.timestamp || 0, value: !!c.value, author, jobId: c.jobId }) } catch {} } } - return { tomb, jobNodes, parent, child, rootOf, tipOf, tipByRoot, subsByJob } + const vg = versionGroups(Array.from(jobNodes.values()).map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === "string" ? n.c.replaces : null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))) + + const jobSubLatest = new Map() + for (const sub of subMsgs) { + const jobId = vg.rootOf(sub.jobId) + const k = `${jobId}::${sub.author}` + const prev = jobSubLatest.get(k) + if (!prev || sub.ts >= prev.ts) jobSubLatest.set(k, { ...sub, jobId }) + } + + const subsByJob = new Map() + for (const { jobId, author, value } of jobSubLatest.values()) { + if (!subsByJob.has(jobId)) subsByJob.set(jobId, new Set()) + const set = subsByJob.get(jobId) + if (value) set.add(author) + else set.delete(author) + } + + return { tomb, jobNodes, vg, subsByJob } } const buildJobObject = (node, rootId, subscribers) => { @@ -255,26 +218,20 @@ module.exports = ({ cooler, tribeCrypto }) => { async resolveCurrentId(jobId) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const { tomb, child } = buildIndex(messages, ssbClient) + const { tomb, vg } = buildIndex(messages, ssbClient) - let cur = jobId - while (child.has(cur)) cur = child.get(cur) - if (tomb.has(cur)) throw new Error("Job not found") + const cur = vg.tipOf(jobId) + if (tomb.has(cur) || vg.isForeignCopy(vg.rootOf(cur))) throw new Error("Job not found") return cur }, async resolveRootId(jobId) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const { tomb, parent, child } = buildIndex(messages, ssbClient) + const { tomb, vg } = buildIndex(messages, ssbClient) - let tip = jobId - while (child.has(tip)) tip = child.get(tip) - if (tomb.has(tip)) throw new Error("Job not found") - - let root = tip - while (parent.has(root)) root = parent.get(root) - return root + if (tomb.has(vg.tipOf(jobId)) || vg.isForeignCopy(vg.rootOf(jobId))) throw new Error("Job not found") + return vg.rootOf(jobId) }, async updateJob(id, jobData) { @@ -468,7 +425,8 @@ module.exports = ({ cooler, tribeCrypto }) => { const idx = buildIndex(messages, ssbClient) const jobs = [] - for (const [rootId, tipId] of idx.tipByRoot.entries()) { + for (const rootId of idx.vg.roots) { + const tipId = idx.vg.tipOf(rootId) if (idx.tomb.has(tipId)) continue const node = idx.jobNodes.get(tipId) if (!node) continue @@ -523,14 +481,11 @@ module.exports = ({ cooler, tribeCrypto }) => { const viewer = viewerId || ssbClient.id const messages = await readAll(ssbClient) - const idx = buildIndex(messages, ssbClient) + const idx = memoIndex('jobs', messages, (m) => buildIndex(m, ssbClient)) - let tipId = id - while (idx.child.has(tipId)) tipId = idx.child.get(tipId) - if (idx.tomb.has(tipId)) throw new Error("Job not found") - - let rootId = tipId - while (idx.parent.has(rootId)) rootId = idx.parent.get(rootId) + const tipId = idx.vg.tipOf(id) + const rootId = idx.vg.rootOf(id) + if (idx.tomb.has(tipId) || idx.vg.isForeignCopy(rootId)) throw new Error("Job not found") const gate = (job) => { if (job.visibility === "HIDDEN" && job.author !== viewer) throw new Error("Job not found") @@ -540,7 +495,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const node = idx.jobNodes.get(tipId) if (!node) { const msg = await new Promise((r, j) => ssbClient.get(tipId, (e, m) => e ? j(e) : r(m))) - if (!msg || !msg.content) throw new Error("Job not found") + if (!msg || !msg.content || msg.content.type !== "job" || (msg.content.author && msg.content.author !== msg.author)) throw new Error("Job not found") const tmpNode = { key: tipId, ts: msg.timestamp || 0, c: msg.content, author: msg.author } const subsSet = idx.subsByJob.get(rootId) || new Set() const subs = Array.from(subsSet) diff --git a/src/models/larp_model.js b/src/models/larp_model.js index e927fc9..3c72b0e 100644 --- a/src/models/larp_model.js +++ b/src/models/larp_model.js @@ -1,4 +1,5 @@ const pull = require('../server/node_modules/pull-stream'); +const { readTyped, requestScope } = require('./typed_log'); const fs = require('fs'); const path = require('path'); @@ -10,6 +11,7 @@ const HOUSE_KEYS = ['academia','solaris','arrakis','terraverde','unsystem','dogm const VALID_KEY = (k) => HOUSE_KEYS.includes(String(k || '').toLowerCase()); const TEST_COOLDOWN_MS = 30 * 24 * 60 * 60 * 1000; +const ACADEMIA_GRACE_MS = 10 * 60 * 1000; const PROFILE_QUESTIONS = [ { @@ -173,6 +175,12 @@ function getGoverningPeriodId(now = new Date()) { module.exports = ({ cooler, tribesModel, tribeCrypto }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + const startedAt = Date.now(); + let academiaRetry = null; + const followsSomeone = async (client, me) => { + const contacts = await readTyped(client, ['contact']).catch(() => []); + return contacts.some((m) => m && m.value && m.value.author === me && m.value.content && m.value.content.following === true); + }; const houseTribeTag = (houseKey) => { const h = HOUSES[houseKey]; @@ -192,7 +200,7 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { return tags.includes(tag) && members.includes(me); }); if (!candidates.length) return null; - candidates.sort((a, b) => new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime()); + candidates.sort((a, b) => (new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime()) || String(a.id || '').localeCompare(String(b.id || ''))); return candidates[0]; } @@ -206,78 +214,59 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { return tags.includes(tag); }); if (!candidates.length) return null; - candidates.sort((a, b) => new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime()); + candidates.sort((a, b) => (new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime()) || String(a.id || '').localeCompare(String(b.id || ''))); return candidates[0]; } + const ANCHOR_TYPES = ['larpHouseTribeAnchor', 'larpHouseTribeAnchorTombstone', 'tombstone']; + const authorOfMsg = (client, key) => new Promise((resolve) => client.get(key, (err, v) => resolve(err || !v ? null : v.author || null))); + async function findEarliestHouseAnchor(houseKey) { if (!VALID_KEY(houseKey)) return null; const client = await openSsb(); - return new Promise((resolve) => { - const anchors = []; - const anchorTombstones = []; - const tribeTombstones = new Map(); - const msgAuthorByKey = new Map(); - pull( - client.createLogStream(), - pull.drain((m) => { - if (!m || !m.value) return; - const author = m.value.author; - if (m.key && author) msgAuthorByKey.set(m.key, author); - const c = m.value.content; - if (!c || typeof c !== 'object') return; - if (c.type === 'larpHouseTribeAnchor') { - if (c.house !== houseKey) return; - if (typeof c.tribeRootId !== 'string') return; - const tribeTs = Number(Date.parse(c.tribeCreatedAt || '')) || m.value.timestamp || 0; - anchors.push({ tribeRootId: c.tribeRootId, anchorAuthor: author, tribeTs }); - } else if (c.type === 'larpHouseTribeAnchorTombstone') { - if (c.house !== houseKey) return; - if (typeof c.tribeRootId !== 'string') return; - anchorTombstones.push({ tribeRootId: c.tribeRootId, tombstoneAuthor: author }); - } else if (c.type === 'tombstone' && typeof c.target === 'string') { - tribeTombstones.set(c.target, author); - } - }, () => { - const killedAnchorPairs = new Set(); - for (const t of anchorTombstones) { - killedAnchorPairs.add(`${t.tribeRootId}|${t.tombstoneAuthor}`); - } - const deadTribes = new Set(); - for (const [target, tombAuthor] of tribeTombstones) { - const tribeAuthor = msgAuthorByKey.get(target); - if (tribeAuthor && tribeAuthor === tombAuthor) deadTribes.add(target); - } - const live = anchors.filter(a => - !killedAnchorPairs.has(`${a.tribeRootId}|${a.anchorAuthor}`) && - !deadTribes.has(a.tribeRootId) - ); - if (!live.length) return resolve(null); - live.sort((a, b) => a.tribeTs - b.tribeTs); - const first = live[0]; - resolve({ tribeRootId: first.tribeRootId, author: first.anchorAuthor, tribeTs: first.tribeTs }); - }) - ); - }); + const msgs = await readTyped(client, ANCHOR_TYPES); + const anchors = []; + const killedAnchorPairs = new Set(); + const tombAuthors = new Map(); + for (const m of msgs) { + const c = m && m.value && m.value.content; + if (!c || typeof c !== 'object') continue; + const author = m.value.author; + if (c.type === 'larpHouseTribeAnchor') { + if (c.house !== houseKey || typeof c.tribeRootId !== 'string') continue; + const tribeTs = Number(Date.parse(c.tribeCreatedAt || '')) || m.value.timestamp || 0; + anchors.push({ tribeRootId: c.tribeRootId, anchorAuthor: author, tribeTs }); + } else if (c.type === 'larpHouseTribeAnchorTombstone') { + if (c.house !== houseKey || typeof c.tribeRootId !== 'string') continue; + killedAnchorPairs.add(`${c.tribeRootId}|${author}`); + } else if (c.type === 'tombstone' && typeof c.target === 'string') { + if (!tombAuthors.has(c.target)) tombAuthors.set(c.target, new Set()); + tombAuthors.get(c.target).add(author); + } + } + const candidates = anchors.filter(a => !killedAnchorPairs.has(`${a.tribeRootId}|${a.anchorAuthor}`)); + const deadTribes = new Set(); + await Promise.all([...new Set(candidates.map(a => a.tribeRootId).filter(t => tombAuthors.has(t)))].map(async (t) => { + const tribeAuthor = await authorOfMsg(client, t); + if (tribeAuthor && tombAuthors.get(t).has(tribeAuthor)) deadTribes.add(t); + })); + const live = candidates.filter(a => !deadTribes.has(a.tribeRootId)); + if (!live.length) return null; + live.sort((a, b) => a.tribeTs - b.tribeTs); + const first = live[0]; + return { tribeRootId: first.tribeRootId, author: first.anchorAuthor, tribeTs: first.tribeTs }; } async function findHouseAnchorByTribe(houseKey, tribeRootId) { if (!VALID_KEY(houseKey) || !tribeRootId) return null; const client = await openSsb(); - return new Promise((resolve) => { - let hit = null; - pull( - client.createLogStream(), - pull.drain((m) => { - if (hit) return; - const c = m && m.value && m.value.content; - if (!c || c.type !== 'larpHouseTribeAnchor') return; - if (c.house !== houseKey) return; - if (c.tribeRootId !== tribeRootId) return; - hit = { author: m.value.author, ts: m.value.timestamp || 0 }; - }, () => resolve(hit)) - ); - }); + const msgs = await readTyped(client, ANCHOR_TYPES); + for (const m of msgs) { + const c = m && m.value && m.value.content; + if (!c || c.type !== 'larpHouseTribeAnchor' || c.house !== houseKey || c.tribeRootId !== tribeRootId) continue; + return { author: m.value.author, ts: m.value.timestamp || 0 }; + } + return null; } async function publishHouseTribeAnchor(houseKey, tribeRootId, tribeCreatedAt) { @@ -332,7 +321,7 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { const canonical = await findAnyHouseTribe(houseKey).catch(() => null); const canonRoot = canonical ? await tribesModel.getRootId(canonical.id).catch(() => canonical.id) : null; const myTribes = await listMyHouseTribes(houseKey); - myTribes.sort((a, b) => a.createdAtTs - b.createdAtTs); + myTribes.sort((a, b) => (a.createdAtTs - b.createdAtTs) || String(a.rootId || '').localeCompare(String(b.rootId || ''))); const keepRoot = canonRoot || (myTribes[0] && myTribes[0].rootId); for (const t of myTribes) { if (t.rootId === keepRoot) continue; @@ -345,6 +334,13 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { await redeemPendingAutoInvites().catch(() => {}); return await findMyHouseTribe(houseKey) || canonical; } + if (Date.now() - startedAt < ACADEMIA_GRACE_MS && await followsSomeone(client, me)) { + if (!academiaRetry) { + academiaRetry = setTimeout(() => { academiaRetry = null; enqueue(runCatchup); }, ACADEMIA_GRACE_MS - (Date.now() - startedAt)); + if (academiaRetry.unref) academiaRetry.unref(); + } + return null; + } const house = HOUSES[houseKey] || {}; const tag = houseTribeTag(houseKey); try { @@ -460,6 +456,7 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { joinedAt: new Date().toISOString() }, (err, msg) => err ? reject(err) : resolve(msg)); }); + forgetMemberships(); if (previousHouse && previousHouse !== houseKey) { await leaveMyHouseTribe(previousHouse).catch(() => {}); } @@ -471,53 +468,45 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { async function getUserHouse(feedId) { const client = await openSsb(); const target = feedId || client.id; - return new Promise((resolve) => { - let latest = null; - let latestTs = 0; - pull( - client.createUserStream({ id: target, reverse: true }), - pull.drain((m) => { - const c = m && m.value && m.value.content; - if (!c) return; - const ts = m.value.timestamp || 0; - if (c.type === 'larpJoinHouse' && VALID_KEY(c.house)) { - if (ts > latestTs) { latestTs = ts; latest = c.house; } - } else if (c.type === 'larpLeaveLarp') { - if (ts > latestTs) { latestTs = ts; latest = null; } - } - }, () => resolve(latest)) - ); - }); + return (await listAllMemberships()).get(target) || null; } - async function listAllMemberships() { + async function readMemberships() { const client = await openSsb(); - return new Promise((resolve) => { - const byAuthor = new Map(); - pull( - client.createLogStream({ reverse: true }), - pull.drain((m) => { - const author = m && m.value && m.value.author; - if (!author) return; - const c = m.value.content; - if (!c) return; - const ts = m.value.timestamp || 0; - if (c.type === 'larpJoinHouse' && VALID_KEY(c.house)) { - const prev = byAuthor.get(author); - if (!prev || ts > prev.ts) byAuthor.set(author, { house: c.house, ts }); - } else if (c.type === 'larpLeaveLarp') { - const prev = byAuthor.get(author); - if (!prev || ts > prev.ts) byAuthor.set(author, { house: null, ts }); - } - }, () => { - const result = new Map(); - for (const [a, v] of byAuthor.entries()) { - if (v.house) result.set(a, v.house); - } - resolve(result); - }) - ); - }); + const msgs = await readTyped(client, ['larpJoinHouse', 'larpLeaveLarp'], { limit: 0 }); + const byAuthor = new Map(); + for (const m of msgs) { + const author = m && m.value && m.value.author; + if (!author) continue; + const c = m.value.content; + if (!c) continue; + const ts = m.value.timestamp || 0; + const seq = Number(m.value.sequence) || 0; + const prev = byAuthor.get(author); + const newer = !prev || ts > prev.ts || (ts === prev.ts && seq > prev.seq); + if (c.type === 'larpJoinHouse' && VALID_KEY(c.house)) { + if (newer) byAuthor.set(author, { house: c.house, ts, seq }); + } else if (c.type === 'larpLeaveLarp') { + if (newer) byAuthor.set(author, { house: null, ts, seq }); + } + } + const result = new Map(); + for (const [a, v] of byAuthor.entries()) { + if (v.house) result.set(a, v.house); + } + return result; + } + + const forgetMemberships = () => { + const store = requestScope.getStore(); + if (store) delete store.larpMemberships; + }; + + async function listAllMemberships() { + const store = requestScope.getStore(); + if (!store) return readMemberships(); + if (!store.larpMemberships) store.larpMemberships = readMemberships(); + return store.larpMemberships; } async function publishLeaveLarp() { @@ -530,6 +519,7 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { leftAt: new Date().toISOString() }, (err, msg) => err ? reject(err) : resolve(msg)); }); + forgetMemberships(); if (previousHouse) { await leaveMyHouseTribe(previousHouse).catch(() => {}); } @@ -585,10 +575,16 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { const memberships = await listAllMemberships(); return new Promise((resolve) => { const posts = []; + const tombs = new Map(); pull( client.createLogStream({ reverse: true }), pull.drain((m) => { const c = m && m.value && m.value.content; + if (c && c.type === 'tombstone' && typeof c.target === 'string') { + if (!tombs.has(c.target)) tombs.set(c.target, new Set()); + tombs.get(c.target).add(m.value.author); + return; + } if (!c || c.type !== 'larpHousePost') return; if (c.house !== houseKey) return; const author = m.value.author; @@ -602,8 +598,9 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { ts: m.value.timestamp || 0 }); }, () => { - posts.sort((a, b) => b.ts - a.ts); - resolve(posts); + const shown = posts.filter(post => !(tombs.get(post.id) && tombs.get(post.id).has(post.author))); + shown.sort((a, b) => b.ts - a.ts); + resolve(shown); }) ); }); @@ -770,14 +767,20 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { const ssbKeys = require('../server/node_modules/ssb-keys'); const config = require('../server/ssb_config'); const codes = []; + const sealedOnly = client.private && typeof client.private.read === 'function'; await new Promise((resolve) => { pull( - client.createLogStream({ reverse: true, limit: 2000 }), + sealedOnly ? client.private.read({ reverse: true }) : client.createLogStream({ reverse: true, limit: 2000 }), pull.drain((m) => { const c = m && m.value && m.value.content; - if (typeof c !== 'string' || !c.endsWith('.box')) return; let decoded; - try { decoded = ssbKeys.unbox(c, config.keys); } catch (_) { return; } + if (typeof c === 'string') { + if (!c.endsWith('.box')) return; + try { decoded = ssbKeys.unbox(c, config.keys); } catch (_) { return; } + } else { + if (!m.value.private) return; + decoded = c; + } if (!decoded) return; if (typeof decoded === 'string') { try { decoded = JSON.parse(decoded); } catch (_) { return; } @@ -832,6 +835,13 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { await issueAutoInvitesForMyHouse().catch(() => {}); return; } + if (typeof c === 'object' && c.type === 'tribe' && Array.isArray(c.tags) && c.tags.includes(houseTribeTag('academia'))) { + if (m.value.author === me) return; + const myHouse = await getUserHouse(me).catch(() => null); + if (myHouse !== 'academia') return; + await ensureHouseTribe('academia').catch(() => {}); + return; + } if (typeof c === 'object' && c.type === 'larpHouseTribeAnchor' && VALID_KEY(c.house)) { if (m.value.author === me) return; const myHouse = await getUserHouse(me).catch(() => null); @@ -931,6 +941,7 @@ module.exports = ({ cooler, tribesModel, tribeCrypto }) => { publishJoin, publishLeaveLarp, getUserHouse, + listAllMemberships, listHousesWithCounts, getMembersOfHouse, publishHousePost, diff --git a/src/models/legacy_model.js b/src/models/legacy_model.js deleted file mode 100644 index b612890..0000000 --- a/src/models/legacy_model.js +++ /dev/null @@ -1,97 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); -const os = require('os'); - -const MAGIC = Buffer.from('OASIS1'); -const SALT_LEN = 16; -const IV_LEN = 12; -const TAG_LEN = 16; - -function normalizePassword(password) { - if (password && typeof password === 'object' && password.password) password = password.password; - return String(password || ''); -} - -function deriveKey(password, salt) { - return crypto.scryptSync(password, salt, 32); -} - -function encryptBuffer(plaintext, password) { - const salt = crypto.randomBytes(SALT_LEN); - const iv = crypto.randomBytes(IV_LEN); - const key = deriveKey(password, salt); - const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); - const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]); - const tag = cipher.getAuthTag(); - return Buffer.concat([MAGIC, salt, iv, tag, ciphertext]); -} - -function decryptBuffer(data, password) { - if (!Buffer.isBuffer(data) || data.length < MAGIC.length + SALT_LEN + IV_LEN + TAG_LEN) { - throw new Error('Unrecognized or corrupt backup file.'); - } - if (!data.slice(0, MAGIC.length).equals(MAGIC)) { - throw new Error('Unrecognized or corrupt backup file.'); - } - let o = MAGIC.length; - const salt = data.slice(o, o += SALT_LEN); - const iv = data.slice(o, o += IV_LEN); - const tag = data.slice(o, o += TAG_LEN); - const ciphertext = data.slice(o); - const key = deriveKey(password, salt); - const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); - decipher.setAuthTag(tag); - return Buffer.concat([decipher.update(ciphertext), decipher.final()]); -} - -module.exports = { - exportData: async (password) => { - try { - const pw = normalizePassword(password); - // identidad de la cuenta activa - const secretFilePath = path.join(require('../server/ssb_config').path, 'secret'); - if (!fs.existsSync(secretFilePath)) { - throw new Error(".ssb/secret file doesn't exist"); - } - const original = fs.readFileSync(secretFilePath); - const encrypted = encryptBuffer(original, pw); - const roundtrip = decryptBuffer(encrypted, pw); - if (!roundtrip.equals(original)) { - throw new Error('Backup verification failed; nothing was exported.'); - } - return { filename: 'oasis.enc', data: encrypted }; - } catch (error) { - throw new Error("Error exporting data: " + error.message); - } - }, - importData: async ({ filePath, password }) => { - try { - const pw = normalizePassword(password); - if (!fs.existsSync(filePath)) { - throw new Error('Encrypted file not found.'); - } - const data = fs.readFileSync(filePath); - let decrypted; - try { - decrypted = decryptBuffer(data, pw); - } catch (_) { - throw new Error('Wrong password or corrupt backup file.'); - } - // identidad de la cuenta activa - const ssbDir = require('../server/ssb_config').path; - fs.mkdirSync(ssbDir, { recursive: true }); - const secretPath = path.join(ssbDir, 'secret'); - if (fs.existsSync(secretPath)) { - fs.copyFileSync(secretPath, path.join(ssbDir, 'secret.bak-' + new Date().toISOString().replace(/[:.]/g, '-'))); - } - const tmpPath = path.join(ssbDir, 'secret.tmp-' + process.pid); - fs.writeFileSync(tmpPath, decrypted, { mode: 0o600 }); - fs.renameSync(tmpPath, secretPath); - try { fs.unlinkSync(filePath); } catch (_) {} - return secretPath; - } catch (error) { - throw new Error("Error importing data: " + error.message); - } - } -}; diff --git a/src/models/logistics_model.js b/src/models/logistics_model.js new file mode 100644 index 0000000..cec6bea --- /dev/null +++ b/src/models/logistics_model.js @@ -0,0 +1,390 @@ +const { readTyped, memoIndex } = require('./typed_log'); +const categories = require('../backend/opinion_categories'); +const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { getConfig } = require('../configs/config-manager.js'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const ROUTE_TYPE = 'logisticsRoute'; +const RATING_TYPE = 'logisticsRating'; +const BOOKING_TYPE = 'logistics-booking'; +const BOOKING_STATUS_TYPE = 'logistics-booking-status'; +const OPINION_TYPE = 'logisticsOpinion'; +const PUBLIC_TYPES = [ROUTE_TYPE, RATING_TYPE, OPINION_TYPE, 'tombstone']; +const KINDS = ['TRIP', 'SHIPMENT']; +const MODES = ['OFFER', 'REQUEST']; +const PRICE_TYPES = ['FREE', 'ECO', 'TIME']; +const RECURRENCES = ['NONE', 'DAILY', 'WEEKLY', 'MONTHLY']; +const STATUSES = ['OPEN', 'CLOSED']; +const BOOKING_STATUSES = ['PENDING', 'CONFIRMED', 'REJECTED', 'CANCELLED', 'DELIVERED']; +const OWNER_BOOKING_STATUSES = ['CONFIRMED', 'REJECTED', 'DELIVERED']; +const RECENT_MS = 24 * 60 * 60 * 1000; +const MAX_ZONES = 8; + +const safeText = (v) => String(v == null ? '' : v).trim(); +const normU = (v) => String(v || '').trim().toUpperCase(); +const pick = (list, v, fallback) => (list.includes(normU(v)) ? normU(v) : fallback); +const normalizeTags = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(','); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(Boolean))); +}; +const toIso = (v) => { const t = Date.parse(v || ''); return Number.isFinite(t) ? new Date(t).toISOString() : ''; }; +const num = (v, min = 0) => { const n = Number(v); return Number.isFinite(n) && n >= min ? n : 0; }; +const MEDIA_RE = /!?\[(image|video):[^\]]*\]\((&[^)]+)\)/; +const mediaOf = (text) => { const m = safeText(text).match(MEDIA_RE); return m ? { kind: m[1], blobId: m[2] } : null; }; +const zoneKey = (s) => safeText(s).toLowerCase(); + +module.exports = ({ cooler }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + + const publish = (ssbClient, content) => new Promise((resolve, reject) => ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res))); + const publishPrivate = (ssbClient, content, recps) => new Promise((resolve, reject) => ssbClient.private.publish(content, recps, (err, res) => err ? reject(err) : resolve(res))); + + const readPublic = async (ssbClient) => readTyped(ssbClient, PUBLIC_TYPES, { limit: logLimit }); + + const readPrivate = async (ssbClient) => { + const msgs = await readTyped(ssbClient, [BOOKING_TYPE, BOOKING_STATUS_TYPE], { limit: logLimit, withPrivate: true }); + const bookings = []; + const statuses = []; + for (const m of msgs) { + const v = (m && m.value) || {}; + if (v.private !== true) continue; + const c = v.content; + if (!c || typeof c !== 'object') continue; + const ts = v.timestamp || m.timestamp || 0; + if (c.type === BOOKING_TYPE && c.routeId) bookings.push({ id: m.key, author: v.author, ts, c }); + else if (c.type === BOOKING_STATUS_TYPE && c.bookingId) statuses.push({ key: m.key, author: v.author, ts, c }); + } + return { bookings, statuses }; + }; + + const buildIndex = (messages) => { + const tomb = buildValidatedTombstoneSet(messages); + const nodes = new Map(); + const parentOf = new Map(); + const ratings = []; + const opinions = []; + for (const m of messages) { + const v = m.value || {}; + const c = v.content; + if (!c || typeof c !== 'object') continue; + if (c.type === ROUTE_TYPE) { + nodes.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, c }); + if (typeof c.replaces === 'string') parentOf.set(m.key, c.replaces); + } else if (c.type === OPINION_TYPE && typeof c.target === 'string') { + opinions.push({ target: c.target, author: v.author, category: String(c.category || ''), ts: v.timestamp || 0 }); + } else if (c.type === RATING_TYPE && typeof c.target === 'string') { + ratings.push({ key: m.key, target: c.target, author: v.author, ts: v.timestamp || 0, score: Math.min(5, Math.max(1, num(c.score, 1) || 1)), text: safeText(c.text), createdAt: c.createdAt || new Date(v.timestamp || 0).toISOString() }); + } + } + const rootOf = (key) => { + const seen = new Set(); + let cur = key; + while (parentOf.has(cur) && !seen.has(cur)) { seen.add(cur); cur = parentOf.get(cur); } + return cur; + }; + const versionsByRoot = new Map(); + for (const node of nodes.values()) { + const root = rootOf(node.key); + if (tomb.has(root)) continue; + if (!versionsByRoot.has(root)) versionsByRoot.set(root, []); + versionsByRoot.get(root).push(node); + } + const routes = new Map(); + const now = Date.now(); + for (const [root, versions] of versionsByRoot) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const own = versions.filter(v => v.author === first.author); + const tip = own[own.length - 1]; + const c = tip.c; + const dateIso = toIso(c.date); + const dateTs = Date.parse(dateIso) || 0; + const recurrence = pick(RECURRENCES, c.recurrence, 'NONE'); + const past = !!dateTs && dateTs < now && recurrence === 'NONE'; + const status = pick(STATUSES, c.status, 'OPEN') === 'CLOSED' ? 'CLOSED' : (past ? 'PAST' : 'OPEN'); + const byAuthor = new Map(); + for (const r of ratings.filter(r => nodes.has(r.target) && rootOf(r.target) === root)) { + const prev = byAuthor.get(r.author); + if (!prev || r.ts >= prev.ts) byAuthor.set(r.author, r); + } + const routeRatings = Array.from(byAuthor.values()).sort((a, b) => b.ts - a.ts); + const ops = {}; + const voters = []; + for (const op of opinions.filter(o => nodes.has(o.target) && rootOf(o.target) === root).sort((a, b) => a.ts - b.ts)) { + if (voters.includes(op.author) || !categories.includes(op.category)) continue; + voters.push(op.author); + ops[op.category] = (ops[op.category] || 0) + 1; + } + const ratingAvg = routeRatings.length ? Math.round((routeRatings.reduce((s, r) => s + r.score, 0) / routeRatings.length) * 10) / 10 : 0; + routes.set(root, { + id: root, rootId: root, tipId: tip.key, + kind: pick(KINDS, c.kind, 'TRIP'), mode: pick(MODES, c.mode, 'OFFER'), + title: safeText(c.title), description: safeText(c.description), + origin: safeText(c.origin), destination: safeText(c.destination), mapUrl: safeText(c.mapUrl), + date: dateIso, dateTs, recurrence, + seats: num(c.seats), seatsTaken: num(c.seatsTaken), size: safeText(c.size), weight: safeText(c.weight), media: mediaOf(c.description), + priceType: pick(PRICE_TYPES, c.priceType, 'FREE'), price: num(c.price), + orderRef: safeText(c.orderRef), tags: normalizeTags(c.tags), + status, closed: status !== 'OPEN', + author: first.author, createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt: c.updatedAt || new Date(tip.ts).toISOString(), ts: tip.ts, + ratings: routeRatings, ratingCount: routeRatings.length, ratingAvg, + opinions: ops, opinions_inhabitants: voters, opinionCount: voters.length + }); + } + return { routes, rootOf, nodes }; + }; + + const bookingsFor = (priv, routeId, me) => { + const statusById = new Map(); + for (const s of priv.statuses) { + const prev = statusById.get(s.c.bookingId); + if (!prev || s.ts >= prev.ts) statusById.set(s.c.bookingId, { status: pick(BOOKING_STATUSES, s.c.status, 'PENDING'), ts: s.ts, by: s.author, receipt: safeText(s.c.receipt) }); + } + return priv.bookings + .filter(b => String(b.c.routeId) === String(routeId)) + .map(b => { + const st = statusById.get(b.id) || null; + return { + id: b.id, routeId: String(b.c.routeId), booker: b.author, owner: safeText(b.c.owner), + seats: num(b.c.seats), notes: safeText(b.c.notes), orderRef: safeText(b.c.orderRef), + createdAt: b.c.createdAt || new Date(b.ts).toISOString(), ts: b.ts, + status: st ? st.status : 'PENDING', statusAt: st ? new Date(st.ts).toISOString() : null, receipt: st ? st.receipt : '', + mine: b.author === me + }; + }) + .sort((a, b) => b.ts - a.ts); + }; + + const load = async () => { + const ssbClient = await openSsb(); + const [pub, priv] = await Promise.all([readPublic(ssbClient), readPrivate(ssbClient)]); + return { ssbClient, idx: memoIndex('logistics', pub, buildIndex), priv }; + }; + + const find = (idx, id) => { + const key = String(id || ''); + if (idx.routes.has(key)) return idx.routes.get(key); + if (idx.nodes.has(key)) return idx.routes.get(idx.rootOf(key)) || null; + return null; + }; + + const decorate = (route, priv, me) => { + const bookings = bookingsFor(priv, route.id, me); + const confirmed = bookings.filter(b => b.status === 'CONFIRMED' || b.status === 'DELIVERED'); + const taken = Math.max(route.seatsTaken || 0, confirmed.reduce((s, b) => s + (b.seats || 1), 0)); + const myBooking = bookings.find(b => b.mine && b.status !== 'CANCELLED' && b.status !== 'REJECTED') || null; + const isOwner = route.author === me; + const participated = isOwner || confirmed.some(b => b.mine); + const myRating = route.ratings.find(r => r.author === me) || null; + return { + ...route, + bookings: isOwner ? bookings : bookings.filter(b => b.mine), + bookingCount: bookings.filter(b => b.status !== 'CANCELLED' && b.status !== 'REJECTED').length, + confirmedCount: confirmed.length, seatsTaken: taken, + seatsLeft: route.kind === 'TRIP' && route.seats ? Math.max(0, route.seats - taken) : null, + myBooking, isOwner, participated, + canBook: !isOwner && !route.closed && !myBooking, + canRate: participated && route.closed && !myRating, + myRating, + lastActivityTs: Math.max(route.ts, ...bookings.map(b => b.ts), ...route.ratings.map(r => r.ts)) + }; + }; + + const routeContent = (userId, data, base = null) => ({ + type: ROUTE_TYPE, + kind: pick(KINDS, data.kind, base ? base.kind : 'TRIP'), + mode: pick(MODES, data.mode, base ? base.mode : 'OFFER'), + title: safeText(data.title) || (base ? base.title : ''), + description: data.description !== undefined ? safeText(data.description) : (base ? base.description : ''), + origin: data.origin !== undefined ? safeText(data.origin) : (base ? base.origin : ''), + destination: data.destination !== undefined ? safeText(data.destination) : (base ? base.destination : ''), + mapUrl: data.mapUrl !== undefined ? safeText(data.mapUrl) : (base ? base.mapUrl : ''), + date: data.date !== undefined ? toIso(data.date) : (base ? base.date : ''), + recurrence: pick(RECURRENCES, data.recurrence, base ? base.recurrence : 'NONE'), + seats: data.seats !== undefined ? num(data.seats) : (base ? base.seats : 0), + size: data.size !== undefined ? safeText(data.size) : (base ? base.size : ''), + weight: data.weight !== undefined ? safeText(data.weight) : (base ? base.weight : ''), + priceType: pick(PRICE_TYPES, data.priceType, base ? base.priceType : 'FREE'), + price: data.price !== undefined ? num(data.price) : (base ? base.price : 0), + orderRef: data.orderRef !== undefined ? safeText(data.orderRef) : (base ? base.orderRef : ''), + tags: data.tags !== undefined ? normalizeTags(data.tags) : (base ? base.tags : []), + status: data.status !== undefined ? pick(STATUSES, data.status, 'OPEN') : (base ? (base.status === 'CLOSED' ? 'CLOSED' : 'OPEN') : 'OPEN'), + seatsTaken: data.seatsTaken !== undefined ? num(data.seatsTaken) : (base ? base.seatsTaken : 0), + author: userId, + createdAt: base ? base.createdAt : new Date().toISOString(), + updatedAt: new Date().toISOString() + }); + + return { + KINDS, MODES, PRICE_TYPES, RECURRENCES, STATUSES, BOOKING_STATUSES, + + async createRoute(data = {}) { + const ssbClient = await openSsb(); + const content = routeContent(ssbClient.id, data); + if (!content.title) throw new Error('Title is required'); + if (!content.origin || !content.destination) throw new Error('Origin and destination are required'); + if (!content.date) throw new Error('A valid date is required'); + if (!(content.price > 0)) { content.price = 0; content.priceType = 'FREE'; } else if (content.priceType === 'FREE') content.priceType = 'ECO'; + return publish(ssbClient, content); + }, + + async updateRoute(id, data = {}) { + const { ssbClient, idx } = await load(); + const route = find(idx, id); + if (!route) throw new Error('Route not found'); + if (route.author !== ssbClient.id) throw new Error('Only the author can update this route'); + const content = { ...routeContent(ssbClient.id, data, route), replaces: route.tipId }; + if (!(content.price > 0)) { content.price = 0; content.priceType = 'FREE'; } else if (content.priceType === 'FREE') content.priceType = 'ECO'; + const res = await publish(ssbClient, content); + return { key: res.key, rootId: route.id }; + }, + + async createOpinion(id, category) { + if (!categories.includes(category)) throw new Error('Invalid voting category'); + const { ssbClient, idx } = await load(); + const route = find(idx, id); + if (!route) throw new Error('Route not found'); + if (route.opinions_inhabitants.includes(ssbClient.id)) throw new Error('Already voted'); + return publish(ssbClient, { type: OPINION_TYPE, target: route.id, category, createdAt: new Date().toISOString() }); + }, + + async closeRoute(id) { return this.updateRoute(id, { status: 'CLOSED' }); }, + async reopenRoute(id) { return this.updateRoute(id, { status: 'OPEN' }); }, + + async deleteRoute(id) { + const { ssbClient, idx } = await load(); + const route = find(idx, id); + if (!route) throw new Error('Route not found'); + if (route.author !== ssbClient.id) throw new Error('Only the author can delete this route'); + await publish(ssbClient, { type: 'tombstone', target: route.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + return { key: route.id }; + }, + + async book(id, { seats = 1, notes = '', orderRef = '' } = {}) { + const { ssbClient, idx, priv } = await load(); + const me = ssbClient.id; + const route = find(idx, id); + if (!route) throw new Error('Route not found'); + const view = decorate(route, priv, me); + if (view.isOwner) throw new Error('You cannot book your own route'); + if (view.closed) throw new Error('This route is closed'); + if (view.myBooking) throw new Error('You already booked this route'); + const n = route.kind === 'TRIP' ? Math.max(1, Math.floor(num(seats, 1) || 1)) : 1; + if (view.seatsLeft !== null && n > view.seatsLeft) throw new Error('Not enough seats left'); + const content = { type: BOOKING_TYPE, routeId: route.id, owner: route.author, seats: n, notes: safeText(notes), orderRef: safeText(orderRef), createdAt: new Date().toISOString() }; + const res = await publishPrivate(ssbClient, content, [me, route.author]); + return { ...res, routeId: route.id, owner: route.author, booker: me }; + }, + + async setBookingStatus(bookingId, status, { receipt = '' } = {}) { + const { ssbClient, priv } = await load(); + const me = ssbClient.id; + const st = pick(BOOKING_STATUSES, status, ''); + if (!st || st === 'PENDING') throw new Error('Invalid status'); + const b = priv.bookings.find(x => x.id === bookingId); + if (!b) throw new Error('Booking not found'); + const owner = safeText(b.c.owner); + if (OWNER_BOOKING_STATUSES.includes(st)) { if (me !== owner) throw new Error('Only the route owner can set this status'); } + else if (me !== b.author) throw new Error('Only the booker can cancel'); + const content = { type: BOOKING_STATUS_TYPE, bookingId, routeId: String(b.c.routeId), status: st, ...(st === 'DELIVERED' ? { receipt: safeText(receipt) || `Delivered ${new Date().toISOString()}` } : {}), updatedAt: new Date().toISOString() }; + const res = await publishPrivate(ssbClient, content, Array.from(new Set([me, b.author, owner].filter(Boolean)))); + if (me === owner) { + try { + const fresh = await load(); + const route = find(fresh.idx, b.c.routeId); + if (route && route.kind === 'TRIP' && route.seats) { + const taken = bookingsFor(fresh.priv, route.id, me).filter(x => x.status === 'CONFIRMED' || x.status === 'DELIVERED').reduce((sum, x) => sum + (x.seats || 1), 0); + if (taken !== route.seatsTaken) await publish(ssbClient, { ...routeContent(me, { seatsTaken: taken }, route), replaces: route.tipId }); + } + } catch (_) {} + } + return { ...res, routeId: String(b.c.routeId), owner, booker: b.author, status: st }; + }, + + async rate(id, { score, text = '' } = {}) { + const { ssbClient, idx, priv } = await load(); + const me = ssbClient.id; + const route = find(idx, id); + if (!route) throw new Error('Route not found'); + const view = decorate(route, priv, me); + if (!view.participated) throw new Error('Only participants can rate a route'); + if (!view.closed) throw new Error('Routes are rated once they are closed'); + if (view.myRating) throw new Error('You already rated this route'); + const s = Math.min(5, Math.max(1, Math.round(num(score, 1) || 1))); + return publish(ssbClient, { type: RATING_TYPE, target: route.id, score: s, text: safeText(text), createdAt: new Date().toISOString() }); + }, + + async getRouteById(id) { + const { ssbClient, idx, priv } = await load(); + const route = find(idx, id); + return route ? decorate(route, priv, ssbClient.id) : null; + }, + + async myBookings() { + const { ssbClient, idx, priv } = await load(); + const me = ssbClient.id; + const out = []; + for (const b of priv.bookings) { + if (b.author !== me) continue; + const route = find(idx, b.c.routeId); + if (!route) continue; + const view = decorate(route, priv, me); + const mine = view.bookings.find(x => x.id === b.id); + if (mine) out.push({ ...mine, route: view }); + } + return out.sort((a, b) => b.ts - a.ts); + }, + + zonesOf(list) { + const counts = new Map(); + for (const r of list || []) { + for (const z of [r.origin, r.destination]) { + const k = zoneKey(z); + if (!k) continue; + const prev = counts.get(k) || { label: safeText(z), count: 0 }; + prev.count += 1; + counts.set(k, prev); + } + } + return Array.from(counts.values()).sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)).slice(0, MAX_ZONES); + }, + + async listAll({ filter = 'all', q = '', zone = '' } = {}) { + const { ssbClient, idx, priv } = await load(); + const me = ssbClient.id; + const f = String(filter || 'all').toLowerCase(); + const needle = String(q || '').trim().toLowerCase(); + const z = zoneKey(zone); + let out = Array.from(idx.routes.values()).map(r => decorate(r, priv, me)); + if (f === 'mine') out = out.filter(r => r.isOwner); + else if (f === 'booked') out = out.filter(r => !!r.myBooking); + else if (f === 'recent') out = out.filter(r => r.lastActivityTs >= Date.now() - RECENT_MS); + else if (f === 'trips') out = out.filter(r => r.kind === 'TRIP'); + else if (f === 'shipments') out = out.filter(r => r.kind === 'SHIPMENT'); + else if (f === 'offers') out = out.filter(r => r.mode === 'OFFER'); + else if (f === 'requests') out = out.filter(r => r.mode === 'REQUEST'); + else if (f === 'upcoming') out = out.filter(r => r.status === 'OPEN'); + else if (f === 'past') out = out.filter(r => r.status === 'PAST'); + else if (f === 'closed') out = out.filter(r => r.status === 'CLOSED'); + else if (f === 'history') out = out.filter(r => r.closed && r.participated); + else if (f === 'free') out = out.filter(r => r.priceType === 'FREE'); + else if (f === 'eco') out = out.filter(r => r.priceType === 'ECO'); + else if (f === 'time') out = out.filter(r => r.priceType === 'TIME'); + else if (f === 'top') out = out.filter(r => r.ratingCount > 0); + if (z) out = out.filter(r => zoneKey(r.origin) === z || zoneKey(r.destination) === z); + if (needle) out = out.filter(r => [r.title, r.description, r.origin, r.destination, r.kind, r.mode, ...r.tags].some(v => String(v || '').toLowerCase().includes(needle))); + if (f === 'top') out.sort((a, b) => b.ratingAvg - a.ratingAvg || b.ratingCount - a.ratingCount); + else out.sort((a, b) => (a.closed === b.closed ? 0 : (a.closed ? 1 : -1)) || (a.dateTs && b.dateTs ? a.dateTs - b.dateTs : b.lastActivityTs - a.lastActivityTs)); + return out; + }, + + async resolveRootId(id) { + const { idx } = await load(); + const route = find(idx, id); + return route ? route.id : String(id || ''); + } + }; +}; diff --git a/src/models/logs_model.js b/src/models/logs_model.js index 7816900..741b44d 100644 --- a/src/models/logs_model.js +++ b/src/models/logs_model.js @@ -1,6 +1,6 @@ const pull = require('../server/node_modules/pull-stream'); -const util = require('../server/node_modules/util'); -const axios = require('../server/node_modules/axios'); +const { requestScope } = require('./typed_log'); +const util = require('util'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); @@ -22,8 +22,8 @@ const FILTER_WINDOWS = { const ACTION_TYPES = new Set([ 'post', 'about', 'contact', 'feed', 'bookmark', 'image', 'audio', 'video', - 'document', 'torrent', 'event', 'task', 'taskAssignment', - 'votes', 'vote', 'report', 'tribe', 'chat', 'chatMessage', 'pad', 'padEntry', + 'document', 'torrent', 'file', 'event', 'task', 'taskAssign', + 'votes', 'vote', 'report', 'tribe', 'chat', 'chatMessage', 'pad', 'padEntry', 'room', 'roomMember', 'forum', 'market', 'job', 'project', 'industry', 'industryBuild', 'pixelia', 'map', 'mapMarker', 'shop', 'shopProduct', 'curriculum', 'gameScore', 'calendar', 'calendarDate', 'calendarNote', @@ -33,7 +33,29 @@ const ACTION_TYPES = new Set([ 'courtsCase', 'courtsEvidence', 'courtsAnswer', 'courtsVerdict', 'courtsNomination', 'courtsNominationVote', 'courtsSettlementProposal', 'courtsSettlementAccepted', - 'tribeParliamentCandidature', 'tribeParliamentRule' + 'tribeParliamentCandidature', 'tribeParliamentRule', + 'wikiPage', 'emergency', 'emergencyConfirm', 'emergencyUpdate', 'mailingList', + 'logisticsRoute', 'logisticsRating', 'logisticsOpinion', + 'podcast', 'podcastEpisode', 'podcastOpinion', 'podcastPlay', + 'campaign', 'campaignSignature', 'campaignUpdate', 'campaignOpinion', + 'audioOpinion', 'blogOpinion', 'bookmarkOpinion', 'documentOpinion', 'eventOpinion', 'feedOpinion', + 'fileOpinion', 'imageOpinion', 'videoOpinion', 'torrentOpinion', 'housingOpinion', 'industryOpinion', + 'marketOpinion', 'projectOpinion', 'reportOpinion', 'schoolOpinion', 'shopOpinion', 'taskOpinion', + 'transferOpinion', 'votesOpinion', 'pollOpinion', + 'calendarParticipant', 'chatMember', 'chatPin', 'chatReaction', 'padMember', 'mapMember', + 'courtsJudge', 'courtsMediators', 'courtsSupport', 'courtsVisibility', 'courtsVoteLink', + 'eventAttend', 'feed-action', 'spread', 'subscription', 'job_sub', + 'housing', 'housingRequest', + 'industryAllocation', 'industryApply', 'industryBlueprint', 'industryBuildStatus', 'industryBuildUpdate', + 'industryContribution', 'industryMember', 'industryVote', + 'larpHousePost', 'larpJoinHouse', 'larpLeaveLarp', 'larpTestAttempt', + 'marketBid', 'marketPurchase', 'shopPurchase', + 'parliamentCandidatureVote', 'tribeParliamentTerm', + 'poll', 'pollVote', 'pollClose', 'votesVote', + 'projectClaim', 'projectFollow', 'projectPledge', 'projectPledgeConfirm', + 'reportConfirm', 'transferConfirm', 'ubiRefuse', + 'schoolCourse', 'schoolEnroll', 'schoolLesson', 'schoolLessonMedia', 'schoolExam', 'schoolExamQuestion', + 'schoolExamResult', 'schoolProgress', 'schoolCertificate', 'schoolCommentHide' ]); const ACTION_PHRASES = { @@ -47,9 +69,10 @@ const ACTION_PHRASES = { video: 'uploaded a video', document: 'uploaded a document', torrent: 'shared a torrent', + file: 'shared a file', event: 'created an event', task: 'created a task', - taskAssignment: 'updated a task assignment', + taskAssign: 'updated a task assignment', votes: 'participated in a vote', vote: 'cast a vote', report: 'submitted a report', @@ -58,6 +81,8 @@ const ACTION_PHRASES = { chatMessage: 'sent a chat message', pad: 'worked on a collaborative pad', padEntry: 'edited a pad entry', + room: 'opened a room', + roomMember: 'joined a room', market: 'posted in the market', forum: 'posted in the forum', job: 'posted a job opportunity', @@ -91,7 +116,100 @@ const ACTION_PHRASES = { courtsSettlementProposal: 'proposed a courts settlement', courtsSettlementAccepted: 'accepted a courts settlement', tribeParliamentCandidature: 'stood for a tribe parliament', - tribeParliamentRule: 'contributed a tribe parliament rule' + tribeParliamentRule: 'contributed a tribe parliament rule', + wikiPage: 'edited a wiki page', + emergency: 'reported an emergency', + emergencyConfirm: 'confirmed an emergency', + emergencyUpdate: 'posted an emergency update', + mailingList: 'created a mailing list', + logisticsRoute: 'published a logistics route', + logisticsRating: 'rated a logistics route', + logisticsOpinion: 'gave an opinion on a route', + podcast: 'created a podcast', + podcastEpisode: 'published a podcast episode', + podcastOpinion: 'gave an opinion on a podcast', + podcastPlay: 'listened to a podcast episode', + campaign: 'started a campaign', + campaignSignature: 'signed a campaign', + campaignUpdate: 'posted a campaign update', + campaignOpinion: 'gave an opinion on a campaign', + audioOpinion: 'gave an opinion on an audio', + blogOpinion: 'gave an opinion on a blog post', + bookmarkOpinion: 'gave an opinion on a bookmark', + documentOpinion: 'gave an opinion on a document', + eventOpinion: 'gave an opinion on an event', + feedOpinion: 'gave an opinion on a feed', + fileOpinion: 'gave an opinion on a file', + imageOpinion: 'gave an opinion on an image', + videoOpinion: 'gave an opinion on a video', + torrentOpinion: 'gave an opinion on a torrent', + housingOpinion: 'gave an opinion on a housing listing', + industryOpinion: 'gave an opinion on a facility', + marketOpinion: 'gave an opinion on a market item', + projectOpinion: 'gave an opinion on a project', + reportOpinion: 'gave an opinion on a report', + schoolOpinion: 'gave an opinion on a course', + shopOpinion: 'gave an opinion on a shop', + taskOpinion: 'gave an opinion on a task', + transferOpinion: 'gave an opinion on a transfer', + votesOpinion: 'gave an opinion on a votation', + pollOpinion: 'gave an opinion on a poll', + calendarParticipant: 'joined a calendar', + chatMember: 'joined a chat', + chatPin: 'pinned a chat message', + chatReaction: 'reacted to a chat message', + padMember: 'joined a pad', + mapMember: 'joined a map', + courtsJudge: 'chose a judge for a courts case', + courtsMediators: 'proposed mediators for a courts case', + courtsSupport: 'supported a courts case', + courtsVisibility: 'changed the visibility of a courts case', + courtsVoteLink: 'linked a vote to a courts case', + eventAttend: 'joined an event', + 'feed-action': 'refeeded a feed', + spread: 'spread some content', + subscription: 'subscribed to some content', + job_sub: 'subscribed to a job', + housing: 'published a housing listing', + housingRequest: 'requested a housing', + industryAllocation: 'shared out the output of a production build', + industryApply: 'applied to join a facility', + industryBlueprint: 'published a blueprint', + industryBuildStatus: 'updated the status of a production build', + industryBuildUpdate: 'posted a production build update', + industryContribution: 'contributed to a production build', + industryMember: 'joined a facility', + industryVote: 'voted in a facility', + larpHousePost: 'posted in a L.A.R.P. house', + larpJoinHouse: 'joined a L.A.R.P. house', + larpLeaveLarp: 'left L.A.R.P.', + larpTestAttempt: 'took a L.A.R.P. test', + marketBid: 'placed a bid in the market', + marketPurchase: 'bought in the market', + shopPurchase: 'bought in a shop', + parliamentCandidatureVote: 'voted for a parliamentary candidature', + tribeParliamentTerm: 'took part in a tribe parliament term', + poll: 'created a poll', + pollVote: 'voted in a poll', + pollClose: 'closed a poll', + votesVote: 'voted in a votation', + projectClaim: 'claimed a project bounty', + projectFollow: 'followed a project', + projectPledge: 'pledged to a project', + projectPledgeConfirm: 'confirmed a project pledge', + reportConfirm: 'confirmed a report', + transferConfirm: 'confirmed a transfer', + ubiRefuse: 'declined the UBI', + schoolCourse: 'created a course', + schoolEnroll: 'enrolled in a course', + schoolLesson: 'published a lesson', + schoolLessonMedia: 'added media to a lesson', + schoolExam: 'created an exam', + schoolExamQuestion: 'added an exam question', + schoolExamResult: 'completed an exam', + schoolProgress: 'progressed in a course', + schoolCertificate: 'issued a course certificate', + schoolCommentHide: 'hid a comment in a course' }; const compact = (s, n = 200) => String(s || '').replace(/\s+/g, ' ').trim().slice(0, n); @@ -111,7 +229,7 @@ module.exports = ({ cooler }) => { const ssbClient = await openSsb(); const msgs = await new Promise((resolve, reject) => pull( - ssbClient.createLogStream({ reverse: true, limit: logLimit }), + ssbClient.createUserStream({ id: ssbClient.id, reverse: true, limit: logLimit }), pull.collect((err, arr) => err ? reject(err) : resolve(arr)) ) ); @@ -132,10 +250,11 @@ module.exports = ({ cooler }) => { async function callAI(prompt) { if (!prompt) return ''; + try { if ((require('../configs/config-manager.js').getConfig().modules || {}).aiMod !== 'on') return ''; } catch (_) { return ''; } const tryOnce = async () => { try { - const res = await axios.post('http://localhost:4001/ai', { input: prompt, raw: true }, { timeout: 90000 }); - return String(res?.data?.answer || '').trim(); + const answer = await require('../AI/ai_client').ask({ system: 'You write one short first-person diary sentence. No IDs, hashes, quotes, lists or markdown.', input: prompt, maxTokens: 80 }); + return String(answer || '').trim(); } catch { return ''; } }; let out = await tryOnce(); @@ -272,9 +391,10 @@ module.exports = ({ cooler }) => { async function readAllLogMessages() { const ssbClient = await openSsb(); + const sealedOnly = ssbClient.private && typeof ssbClient.private.read === 'function'; const raw = await new Promise((resolve, reject) => pull( - ssbClient.createLogStream({ reverse: false, limit: logLimit }), + sealedOnly ? ssbClient.private.read({ reverse: true, limit: logLimit }) : ssbClient.createLogStream({ reverse: true, limit: logLimit }), pull.collect((err, arr) => err ? reject(err) : resolve(arr)) ) ); @@ -288,7 +408,7 @@ module.exports = ({ cooler }) => { const tsIn = m.timestamp || valueIn?.timestamp || Date.now(); let dec; try { - dec = ssbClient.private.unbox({ key: keyIn, value: valueIn, timestamp: tsIn }); + dec = typeof valueIn.content === 'string' ? ssbClient.private.unbox({ key: keyIn, value: valueIn, timestamp: tsIn }) : (valueIn.private === true ? m : null); } catch { continue; } const v = dec?.value; const c = v?.content; @@ -327,7 +447,9 @@ module.exports = ({ cooler }) => { } async function listLogs(filter = 'today') { - const items = await readAllLogMessages(); + const store = requestScope.getStore(); + if (store && !store.logsAll) store.logsAll = readAllLogMessages(); + const items = await (store ? store.logsAll : readAllLogMessages()); const win = FILTER_WINDOWS[filter]; if (win === null || win === undefined) return items; const cutoff = Date.now() - win; @@ -375,6 +497,7 @@ module.exports = ({ cooler }) => { getLogById, listLogs, countLogs, - isAImodOn + isAImodOn, + FILTER_WINDOWS }; }; diff --git a/src/models/mailing_model.js b/src/models/mailing_model.js new file mode 100644 index 0000000..eee9543 --- /dev/null +++ b/src/models/mailing_model.js @@ -0,0 +1,437 @@ +const pull = require('../server/node_modules/pull-stream'); +const crypto = require('crypto'); +const { readTyped } = require('./typed_log'); +const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const longText = require('../backend/long_text'); +const { getConfig } = require('../configs/config-manager.js'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const LIST_TYPE = 'mailingList'; +const LEAVE_TYPE = 'mailingLeave'; +const LIST_TYPES = ['OPEN', 'CLOSED']; +const STATUSES = ['ACTIVE', 'ARCHIVED']; +const BATCH = 6; +const RECENT_MS = 24 * 60 * 60 * 1000; + +const safeText = (v) => String(v == null ? '' : v).trim(); +const normU = (v) => String(v || '').trim().toUpperCase(); +const normalizeListType = (v) => (LIST_TYPES.includes(normU(v)) ? normU(v) : 'OPEN'); +const normalizeStatus = (v) => (STATUSES.includes(normU(v)) ? normU(v) : 'ACTIVE'); +const normalizeTags = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(','); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(Boolean))); +}; +const isFeedId = (v) => typeof v === 'string' && /^@[A-Za-z0-9+/=]+\.ed25519$/.test(v.trim()); +const normalizeMembers = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(/[\s,]+/); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(isFeedId))); +}; +const uniq = (list) => Array.from(new Set((list || []).filter(Boolean))); +const canonicalSubject = (s) => String(s || '').replace(/^\s*(RE:\s*)+/i, '').trim(); + +module.exports = ({ cooler, subscriptionsModel = null }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + + const publish = (ssbClient, content) => new Promise((resolve, reject) => ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res))); + const publishPrivate = (ssbClient, content, recps) => new Promise((resolve, reject) => ssbClient.private.publish(content, recps, (err, res) => err ? reject(err) : resolve(res))); + + const publishPrivateBatched = async (ssbClient, content, recipients) => { + const me = ssbClient.id; + const others = uniq(recipients).filter(id => id !== me); + const out = []; + if (!others.length) { out.push(await publishPrivate(ssbClient, content, [me])); return out; } + for (let i = 0; i < others.length; i += BATCH) { + out.push(await publishPrivate(ssbClient, content, uniq([me, ...others.slice(i, i + BATCH)]))); + } + return out; + }; + + const privateState = new WeakMap(); + const readPrivate = async (ssbClient) => { + const me = ssbClient.id; + let st = privateState.get(ssbClient); + if (!st || st.me !== me) { + st = { me, lastTs: 0, seen: new Set(), lists: [], leaves: [], posts: [], chunks: new Map(), tombClaims: new Map(), authorByKey: new Map() }; + privateState.set(ssbClient, st); + } + const raw = await new Promise((resolve, reject) => { + pull(ssbClient.createLogStream(st.lastTs > 0 ? { reverse: false, gte: st.lastTs } : { reverse: false }), pull.collect((err, arr) => err ? reject(err) : resolve(arr))); + }); + for (const m of raw) { + if (!m || !m.value) continue; + const recv = Number(m.timestamp) || 0; + if (recv > st.lastTs) st.lastTs = recv; + const content = m.value.content; + if (typeof content !== 'string') continue; + if (m.key) { + if (st.seen.has(m.key)) continue; + st.seen.add(m.key); + } + let dec; + try { dec = ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.timestamp || m.value.timestamp }); } catch (_) { continue; } + const v = (dec && dec.value) || {}; + const c = v.content; + const k = (dec && dec.key) || m.key; + if (!c || typeof c !== 'object' || !k) continue; + const ts = v.timestamp || m.timestamp || 0; + if (c.type === 'tombstone' && c.target) { + const set = st.tombClaims.get(c.target) || new Set(); + set.add(v.author); + st.tombClaims.set(c.target, set); + continue; + } + if (c.type === longText.CHUNK_TYPE) { + const ch = longText.chunkOf(v.author, c); + if (ch) st.chunks.set(k, ch); + continue; + } + st.authorByKey.set(k, v.author); + if (c.type === LIST_TYPE) st.lists.push({ key: k, author: v.author, ts, seq: v.sequence || 0, arrival: st.lists.length, c }); + else if (c.type === LEAVE_TYPE && c.list) st.leaves.push({ key: k, author: v.author, ts, list: String(c.list) }); + else if (c.type === 'post' && c.list) { + const to = Array.isArray(c.to) ? c.to : []; + if (v.author === me || to.includes(me)) st.posts.push({ key: k, author: v.author, ts, c }); + } + } + const tombed = new Set(); + for (const [target, authors] of st.tombClaims.entries()) { + const orig = st.authorByKey.get(target); + for (const a of authors) if (a === orig || a === me) { tombed.add(target); break; } + } + return { lists: st.lists.slice(), leaves: st.leaves.slice(), posts: st.posts.filter(p => !tombed.has(p.key)), tombed, chunks: st.chunks }; + }; + + const buildIndex = (publicMsgs, priv) => { + const tomb = buildValidatedTombstoneSet(publicMsgs); + const nodes = new Map(); + const parentOf = new Map(); + for (const m of publicMsgs) { + const v = m.value || {}; + const c = v.content; + if (!c || typeof c !== 'object' || c.type !== LIST_TYPE) continue; + nodes.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, arrival: Number(m.timestamp) || 0, c, closed: false }); + if (typeof c.replaces === 'string') parentOf.set(m.key, c.replaces); + } + const rootOf = (key) => { + const seen = new Set(); + let cur = key; + while (parentOf.has(cur) && !seen.has(cur)) { seen.add(cur); cur = parentOf.get(cur); } + return cur; + }; + const versionsById = new Map(); + for (const node of nodes.values()) { + const root = rootOf(node.key); + if (tomb.has(root)) continue; + if (!versionsById.has(root)) versionsById.set(root, []); + versionsById.get(root).push(node); + } + const privateById = new Map(); + for (const n of priv.lists) { + const id = n.c.listId ? String(n.c.listId) : null; + if (!id || nodes.has(id)) continue; + if (!privateById.has(id)) privateById.set(id, []); + privateById.get(id).push({ ...n, closed: true }); + } + for (const [id, versions] of privateById) versionsById.set(id, versions); + const ownerOf = (id, versions) => { + if (nodes.has(id)) return nodes.get(id).author; + const keys = new Set(versions.map(n => n.key)); + const heads = versions.filter(n => !(typeof n.c.replaces === 'string' && keys.has(n.c.replaces)) && (typeof n.c.author !== 'string' || n.c.author === n.author)); + heads.sort((a, b) => a.arrival - b.arrival); + return heads.length ? heads[0].author : null; + }; + const lists = new Map(); + for (const [id, versions] of versionsById) { + const owner = ownerOf(id, versions); + const own = versions.filter(v => v.author === owner).sort((a, b) => (a.seq - b.seq) || (a.ts - b.ts)); + if (!own.length || own.some(v => v.closed && normU(v.c.status) === 'DELETED')) continue; + const first = own[0]; + const tip = own[own.length - 1]; + const c = tip.c; + const listType = first.closed ? 'CLOSED' : normalizeListType(c.listType); + const updatedAt = c.updatedAt || new Date(tip.ts).toISOString(); + let members = first.closed ? uniq([owner, ...normalizeMembers(c.members)]) : []; + if (first.closed) { + const left = new Set(priv.leaves.filter(l => l.list === id && l.ts >= tip.ts).map(l => l.author)); + members = members.filter(m => m === owner || !left.has(m)); + } + lists.set(id, { + id, rootId: id, tipId: tip.key, + title: safeText(c.title), description: safeText(c.description), + listType, closed: listType === 'CLOSED', status: normalizeStatus(c.status), tags: normalizeTags(c.tags), + members, author: owner, + createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt, ts: tip.ts + }); + } + return { lists, rootOf, nodes }; + }; + + const historyFor = (priv, id, me) => { + const seen = new Map(); + for (const p of priv.posts) { + if (String(p.c.list) !== String(id)) continue; + const mid = p.c.mid ? String(p.c.mid) : p.key; + if (seen.has(mid)) continue; + seen.set(mid, p); + } + const out = Array.from(seen.values()).map(p => ({ + key: p.key, mid: p.c.mid ? String(p.c.mid) : p.key, list: String(p.c.list), + author: p.author, subject: safeText(p.c.subject), text: safeText(longText.joinText(p.c.text, p.c.chunks, longText.lookupIn(priv.chunks), p.author)), + thread: p.c.thread ? String(p.c.thread) : (p.c.mid ? String(p.c.mid) : p.key), + sentAt: p.c.sentAt || new Date(p.ts).toISOString(), ts: Date.parse(p.c.sentAt || '') || p.ts + })); + out.sort((a, b) => a.ts - b.ts); + return out; + }; + + const load = async () => { + const ssbClient = await openSsb(); + const [publicMsgs, priv] = await Promise.all([readTyped(ssbClient, [LIST_TYPE, 'tombstone'], { limit: logLimit }), readPrivate(ssbClient)]); + const idx = buildIndex(publicMsgs, priv); + return { ssbClient, idx, priv }; + }; + + const find = (idx, id) => { + const key = String(id || ''); + if (idx.lists.has(key)) return idx.lists.get(key); + if (idx.nodes.has(key)) return idx.lists.get(idx.rootOf(key)) || null; + return null; + }; + + const subscribersMap = async (ids) => { + if (!subscriptionsModel || !ids.length) return new Map(); + if (typeof subscriptionsModel.subscribersMap === 'function') { + try { return await subscriptionsModel.subscribersMap(ids); } catch (_) { return new Map(); } + } + const out = new Map(); + for (const id of ids) { + try { out.set(id, await subscriptionsModel.listSubscribers(id)); } catch (_) { out.set(id, []); } + } + return out; + }; + + const decorate = async (ssbClient, list, priv, subs) => { + const me = ssbClient.id; + const history = historyFor(priv, list.id, me); + const subscribers = list.closed ? [] : ((subs && subs.get(list.id)) || []); + const participants = list.closed ? list.members : uniq([list.author, ...subscribers]); + const isMember = participants.includes(me); + const lastTs = history.length ? history[history.length - 1].ts : list.ts; + return { + ...list, + subscribers, participants, participantCount: participants.length, + isMember, isOwner: list.author === me, canWrite: isMember && list.status === 'ACTIVE', + messageCount: history.length, threadCount: new Set(history.map(h => h.thread)).size, + lastActivityTs: lastTs, history + }; + }; + + const visibleTo = (list, me) => !list.closed || list.members.includes(me); + + const recipientsFor = async (ssbClient, list) => { + const me = ssbClient.id; + const subs = list.closed ? [] : ((await subscribersMap([list.id])).get(list.id) || []); + const all = list.closed ? list.members : uniq([list.author, ...subs]); + return { all, others: all.filter(id => id !== me) }; + }; + + const publishListVersion = async (ssbClient, content, list) => { + if (!list || !list.closed) return publish(ssbClient, content); + const res = await publishPrivateBatched(ssbClient, content, list.members); + return res[0]; + }; + + return { + LIST_TYPE, + LIST_TYPES, + STATUSES, + + async createList({ title, description, listType, members, tags }) { + const ssbClient = await openSsb(); + const me = ssbClient.id; + const now = new Date().toISOString(); + const t = safeText(title); + if (!t) throw new Error('Title is required'); + const type = normalizeListType(listType); + const content = { + type: LIST_TYPE, + title: t, + description: safeText(description), + listType: type, + status: 'ACTIVE', + tags: normalizeTags(tags), + author: me, + createdAt: now, + updatedAt: now + }; + if (type === 'CLOSED') { + const listId = crypto.randomBytes(16).toString('hex'); + const mem = uniq([me, ...normalizeMembers(members)]); + const res = await publishPrivateBatched(ssbClient, { ...content, listId, members: mem, private: true }, mem); + return { key: listId, rootId: listId, msgKey: res[0].key }; + } + const res = await publish(ssbClient, content); + return { key: res.key, rootId: res.key, msgKey: res.key }; + }, + + async updateList(id, data = {}) { + const { ssbClient, idx } = await load(); + const me = ssbClient.id; + const list = find(idx, id); + if (!list) throw new Error('Mailing list not found'); + if (list.author !== me) throw new Error('Only the author can update this mailing list'); + const now = new Date().toISOString(); + const nextMembers = list.closed && data.members !== undefined ? uniq([me, ...normalizeMembers(data.members)]) : list.members; + const content = { + type: LIST_TYPE, + title: data.title !== undefined && safeText(data.title) ? safeText(data.title) : list.title, + description: data.description !== undefined ? safeText(data.description) : list.description, + listType: list.listType, + status: data.status !== undefined ? normalizeStatus(data.status) : list.status, + tags: data.tags !== undefined ? normalizeTags(data.tags) : list.tags, + author: me, + createdAt: list.createdAt, + updatedAt: now, + replaces: list.tipId + }; + if (list.closed) { + const res = await publishPrivateBatched(ssbClient, { ...content, listId: list.id, members: nextMembers, private: true }, uniq([...list.members, ...nextMembers])); + return { key: res[0].key, rootId: list.id }; + } + const res = await publish(ssbClient, content); + return { key: res.key, rootId: list.id }; + }, + + async setStatus(id, status) { + return this.updateList(id, { status: normalizeStatus(status) }); + }, + + async deleteList(id) { + const { ssbClient, idx } = await load(); + const me = ssbClient.id; + const list = find(idx, id); + if (!list) throw new Error('Mailing list not found'); + if (list.author !== me) throw new Error('Only the author can delete this mailing list'); + if (list.closed) { + await publishPrivateBatched(ssbClient, { type: LIST_TYPE, listId: list.id, title: list.title, status: 'DELETED', author: me, createdAt: list.createdAt, updatedAt: new Date().toISOString(), replaces: list.tipId, private: true }, list.members); + return { key: list.id }; + } + await publish(ssbClient, { type: 'tombstone', target: list.id, deletedAt: new Date().toISOString(), author: me }); + return { key: list.id }; + }, + + async leaveList(id) { + const { ssbClient, idx } = await load(); + const me = ssbClient.id; + const list = find(idx, id); + if (!list) throw new Error('Mailing list not found'); + if (!list.closed) { + if (!subscriptionsModel) throw new Error('Subscriptions unavailable'); + return subscriptionsModel.setSubscription(list.id, 'mailing', false); + } + if (list.author === me) throw new Error('The author cannot leave the mailing list'); + if (!list.members.includes(me)) throw new Error('Not a member'); + return publishPrivateBatched(ssbClient, { type: LEAVE_TYPE, list: list.id, private: true, createdAt: new Date().toISOString() }, [list.author]); + }, + + async sendMessage(id, { subject, text, thread = '', replyTo = '' } = {}) { + const { ssbClient, idx, priv } = await load(); + const me = ssbClient.id; + const list = find(idx, id); + if (!list || !visibleTo(list, me)) throw new Error('Mailing list not found'); + if (list.status !== 'ACTIVE') throw new Error('This mailing list is archived'); + let { all, others } = await recipientsFor(ssbClient, list); + if (!all.includes(me) && !list.closed && subscriptionsModel) { + await subscriptionsModel.setSubscription(list.id, 'mailing', true); + ({ all, others } = await recipientsFor(ssbClient, list)); + if (!all.includes(me)) { all = uniq([...all, me]); others = all.filter(id => id !== me); } + } + if (!all.includes(me)) throw new Error('Only participants can write to this mailing list'); + const body = safeText(text); + if (!body) throw new Error('Text is required'); + const mid = crypto.randomBytes(12).toString('hex'); + let threadId = safeText(thread); + let subj = safeText(subject); + if (!threadId && replyTo) { + const parent = historyFor(priv, list.id, me).find(h => h.key === replyTo || h.mid === replyTo); + if (parent) { threadId = parent.thread; if (!subj) subj = `RE: ${canonicalSubject(parent.subject)}`; } + } + const content = { + type: 'post', + from: me, + to: [], + subject: subj || list.title, + text: body, + sentAt: new Date().toISOString(), + private: true, + list: list.id, + mid, + thread: threadId || mid + }; + const sendBatch = async (recps) => { + const sealed = await longText.chunkContent({ ...content, to: recps }, 'text', { maxBytes: longText.CONTENT_BYTES.boxed, extra: { private: true }, publish: (c) => publishPrivate(ssbClient, c, recps) }); + return publishPrivate(ssbClient, sealed, recps); + }; + const out = []; + if (!others.length) out.push(await sendBatch([me])); + for (let i = 0; i < others.length; i += BATCH) { + out.push(await sendBatch(uniq([me, ...others.slice(i, i + BATCH)]))); + } + return { key: out[0].key, mid, thread: content.thread, batches: out.length }; + }, + + async getListById(id) { + const { ssbClient, idx, priv } = await load(); + const list = find(idx, id); + if (!list || !visibleTo(list, ssbClient.id)) return null; + return decorate(ssbClient, list, priv, list.closed ? null : await subscribersMap([list.id])); + }, + + async listAll({ filter = 'all', q = '' } = {}) { + const { ssbClient, idx, priv } = await load(); + const me = ssbClient.id; + const f = String(filter || 'all').toLowerCase(); + const needle = String(q || '').trim().toLowerCase(); + const visible = Array.from(idx.lists.values()).filter(l => visibleTo(l, me)); + const subs = await subscribersMap(visible.filter(l => !l.closed).map(l => l.id)); + let out = []; + for (const list of visible) out.push(await decorate(ssbClient, list, priv, subs)); + if (f === 'mine') out = out.filter(l => l.author === me); + else if (f === 'subscribed') out = out.filter(l => l.isMember && l.author !== me); + else if (f === 'recent') out = out.filter(l => l.lastActivityTs >= Date.now() - RECENT_MS); + else if (f === 'open') out = out.filter(l => !l.closed); + else if (f === 'closed') out = out.filter(l => l.closed); + else if (f === 'active') out = out.filter(l => l.status === 'ACTIVE'); + else if (f === 'archived') out = out.filter(l => l.status === 'ARCHIVED'); + if (needle) out = out.filter(l => [l.title, l.description, ...l.tags].some(v => String(v || '').toLowerCase().includes(needle))); + out.sort((a, b) => b.lastActivityTs - a.lastActivityTs); + return out; + }, + + async composerEntries() { + const all = await this.listAll({ filter: 'all' }); + return all.filter(l => l.isMember && l.status === 'ACTIVE').map(l => ({ target: l.id, scope: 'mailing', title: l.title, owner: l.author, count: l.participantCount })); + }, + + async titlesFor(ids) { + const out = new Map(); + const wanted = new Set((ids || []).map(String)); + if (!wanted.size) return out; + const { ssbClient, idx } = await load(); + for (const list of idx.lists.values()) { + if (wanted.has(list.id) && visibleTo(list, ssbClient.id)) out.set(list.id, { title: list.title, status: list.status, closed: list.closed }); + } + return out; + }, + + async resolveRootId(id) { + const { idx } = await load(); + const list = find(idx, id); + return list ? list.id : String(id || ''); + } + }; +}; diff --git a/src/models/main_models.js b/src/models/main_models.js index a92f365..f74df52 100644 --- a/src/models/main_models.js +++ b/src/models/main_models.js @@ -1,6 +1,8 @@ "use strict"; const { buildValidatedTombstoneSet } = require("./tombstone_validator"); +const { readTyped, requestScope, authorActivity } = require("./typed_log"); +const longText = require("../backend/long_text"); const debug = require("../server/node_modules/debug")("oasis"); const { isRoot, isReply: isComment } = require("../server/node_modules/ssb-thread-schema"); const lodash = require("../server/node_modules/lodash"); @@ -12,13 +14,16 @@ const pullSort = require("../server/node_modules/pull-sort"); const path = require('path'); const fs = require('fs/promises'); -const os = require('os'); const ssbRef = require("../server/node_modules/ssb-ref"); const nameCache = require('../backend/nameCache'); +const sharedState = require('../configs/shared-state'); const { getConfig } = require('../configs/config-manager.js'); const logLimit = getConfig().ssbLogStream?.limit || 1000; +const LIFETIME_CONCURRENCY = 32; +const LIFETIME_TTL_MS = 60000; +const LIFETIME_CACHE_MAX = 20000; const isEncrypted = (message) => typeof message.value.content === "string"; const isNotEncrypted = (message) => isEncrypted(message) === false; @@ -65,9 +70,10 @@ const publicOnlyFilter = pull.filter(isNotPrivate); const configure = (...customOptions) => Object.assign({}, defaultOptions, ...customOptions); -// PEERS -const ebtDir = path.join(require('../server/ssb_config').path, 'ebt'); -const unfollowedPath = path.join(require('../server/ssb_config').path, 'gossip_unfollowed.json'); +const ssbConfig = require('../server/ssb_config'); +const peerHealth = require('./peer_health'); +const ebtDir = path.join(ssbConfig.path, 'ebt'); +const unfollowedPath = require('../configs/state-manager').statePath('gossip_unfollowed.json'); async function loadPeersFromEbt() { let result = []; @@ -83,13 +89,15 @@ async function loadPeersFromEbt() { try { const data = await fs.readFile(filePath, 'utf8'); const users = JSON.parse(data); + const touchedAt = (await fs.stat(filePath)).mtimeMs; const userList = Object.keys(users).map(u => ({ id: u, link: `/author/${encodeURIComponent(u)}` })); result.push({ pub: `@${core}.ed25519`, - users: userList + users: userList, + touchedAt }); } catch {} } @@ -119,7 +127,7 @@ const canonicalizePubId = (s) => { }; const parseRemote = (remote) => { - let m = /^net:([^:]+):\d+~shs:([^=]+)=/.exec(remote); + let m = /^(?:net|onion):([^:]+):\d+~shs:([^=]+)=/.exec(remote); if (m) return { host: m[1], pubId: canonicalizePubId(m[2]) }; m = /^wss?:\/\/([^:/]+)(?::\d+)?.*~shs:([^=]+)=/.exec(remote); if (m) return { host: m[1], pubId: canonicalizePubId(m[2]) }; @@ -160,10 +168,9 @@ function toLegacyInvite(s) { return `${m[1]}:${m[2]}:@${key}~${m[4]}`; } -// CORE MODEL module.exports = ({ cooler, isPublic }) => { const models = {}; - const getAbout = async ({ key, feedId }) => { + const readAboutOf = async (feedId) => { const ssb = await cooler.open(); const source = ssb.backlinks.read({ reverse: true, @@ -182,23 +189,31 @@ module.exports = ({ cooler, isPublic }) => { return new Promise((resolve, reject) => pull( source, - pull.find( - (message) => message.value.content[key] !== undefined, - (err, message) => { - if (err) { - reject(err); - } else { - if (message === null) { - resolve(null); - } else { - resolve(message.value.content[key]); - } - } + pull.collect((err, messages) => { + if (err) return reject(err); + const out = {}; + for (const message of messages || []) { + const content = (message && message.value && message.value.content) || {}; + for (const k of Object.keys(content)) if (out[k] === undefined && content[k] !== undefined) out[k] = content[k]; } - ) + resolve(out); + }) ) ); }; + const getAbout = async ({ key, feedId }) => { + const store = requestScope.getStore(); + let pending; + if (store) { + const memo = store.aboutOf || (store.aboutOf = new Map()); + if (!memo.has(feedId)) memo.set(feedId, readAboutOf(feedId)); + pending = memo.get(feedId); + } else { + pending = readAboutOf(feedId); + } + const all = await pending; + return all[key] === undefined ? null : all[key]; + }; const feeds_to_name = {}; let all_the_names = {}; let dirty = false; @@ -207,57 +222,48 @@ module.exports = ({ cooler, isPublic }) => { if (!dirty) return; if (running) return; running = true; - - all_the_names = {}; + dirty = false; const allFeeds = Object.keys(feeds_to_name); + sharedState.setSyncedPeerCount(allFeeds.length); console.log(`- Synced-peers: [ ${allFeeds.length} ]`); console.time("- Sync-time"); - const lookups = []; - for (const feed of allFeeds) { - const e = feeds_to_name[feed]; - let pair = { feed, name: e.name }; - lookups.push(enhanceFeedInfo(pair)); - } - Promise.all(lookups) - .then(() => { - dirty = false; + buildNameTable(allFeeds) + .then((table) => { + all_the_names = table; running = false; console.timeEnd("- Sync-time"); }) .catch((err) => { + dirty = true; running = false; console.warn("- Lookup Sync failed: ", err); }); }; - const enhanceFeedInfo = ({ feed, name }) => { - return new Promise((resolve, reject) => { - getAbout({ feedId: feed, key: "image" }) - .then((img) => { - if ( - img !== null && - typeof img !== "string" && - typeof img === "object" && - typeof img.link === "string" - ) { - img = img.link; - } else if (img === null) { - img = nullImage; - } - - models.friend - .getRelationship(feed) - .then((rel) => { - let feeds_named = all_the_names[name] || []; - feeds_named.push({ feed, name, rel, img }); - all_the_names[name.toLowerCase()] = feeds_named; - resolve(); - }) - .catch(reject); - }) - .catch(reject); - }); + const buildNameTable = async (allFeeds) => { + const ssb = await cooler.open(); + const [profiles, graph] = await Promise.all([ + models.about.profiles(), + new Promise((resolve) => ssb.friends.graph((err, g) => resolve(err || !g ? {} : g))) + ]); + const me = ssb.id; + const mine = graph[me] || {}; + const table = {}; + for (const feed of allFeeds) { + const e = feeds_to_name[feed]; + if (!e || typeof e.name !== "string") continue; + const p = profiles.get(feed) || {}; + const img = p.image && typeof p.image === "object" && typeof p.image.link === "string" + ? p.image.link + : (typeof p.image === "string" ? p.image : nullImage); + const rel = feed === me + ? { me: true, following: false, blocking: false, followsMe: false } + : { me: false, following: mine[feed] >= 0, blocking: Math.round(mine[feed]) === -1, followsMe: !!(graph[feed] && graph[feed][me] >= 0) }; + const k = e.name.toLowerCase(); + (table[k] || (table[k] = [])).push({ feed, name: e.name, rel, img }); + } + return table; }; async function enrichEntries(entries) { @@ -269,7 +275,8 @@ module.exports = ({ cooler, isPublic }) => { const { host, pubId } = parseRemote(remote); const effectiveKey = pubId || (data && data.key ? canonicalizePubId(data.key) : null); const name = host || (effectiveKey ? await models.about.name(effectiveKey).catch(() => (effectiveKey || '').slice(0, 10)) : remote); - const users = effectiveKey && ebtMap.has(effectiveKey) ? ebtMap.get(effectiveKey) : []; + const known = !!(effectiveKey && ebtMap.has(effectiveKey)); + const users = known ? ebtMap.get(effectiveKey) : []; const usersWithNames = await Promise.all( users.map(async (user) => { const userName = await models.about.name(user.id).catch(() => user.id); @@ -282,28 +289,29 @@ module.exports = ({ cooler, isPublic }) => { ...data, key: effectiveKey || remote, name, - users: usersWithNames + users: known ? usersWithNames : null } ]; }) ); }; -// ABOUT MODEL models.about = { publicWebHosting: async (feedId) => { const result = await getAbout({ key: "publicWebHosting", feedId, }); - return result === true; + if (result === true) return true; + const prefs = await getAbout({ key: "visibilityPrefs", feedId }).catch(() => null); + return !!(prefs && typeof prefs === 'object' && prefs.clearnet === true); }, deviceSource: async (feedId) => { const result = await getAbout({ key: "deviceSource", feedId }); return typeof result === 'string' && result.trim() ? result : null; }, - visibilityPrefs: async (feedId) => { - const result = await getAbout({ key: "visibilityPrefs", feedId }); + visibilityPrefs: async (feedId) => models.about.visibilityFrom(await getAbout({ key: "visibilityPrefs", feedId })), + visibilityFrom: (result) => { if (!result || typeof result !== 'object') return null; return { activity: result.activity === true, @@ -314,6 +322,9 @@ models.about = { ecoTax: result.ecoTax !== false, larpSign: result.larpSign === true, gpg: result.gpg !== false, + phone: result.phone === 'mutuals' || result.phone === 'off' ? result.phone : 'whole', + phoneDnd: result.phoneDnd === true, + clearnetSince: typeof result.clearnetSince === 'string' ? result.clearnetSince : '', clearnet: result.clearnet === true, fediverse: result.fediverse === true, fediverseHandle: typeof result.fediverseHandle === 'string' ? result.fediverseHandle : '', @@ -327,7 +338,13 @@ models.about = { clearnetImages: result.clearnetImages === true, clearnetDocuments: result.clearnetDocuments === true, clearnetTorrents: result.clearnetTorrents === true, + clearnetFiles: result.clearnetFiles === true, clearnetBookmarks: result.clearnetBookmarks === true, + clearnetPodcasts: result.clearnetPodcasts === true, + clearnetSchool: result.clearnetSchool === true, + clearnetMarket: result.clearnetMarket === true, + clearnetFeed: result.clearnetFeed === true, + clearnetWiki: result.clearnetWiki === true, profileShops: result.profileShops === true, profileJobs: result.profileJobs === true, profileEvents: result.profileEvents === true, @@ -338,9 +355,41 @@ models.about = { profileImages: result.profileImages === true, profileDocuments: result.profileDocuments === true, profileTorrents: result.profileTorrents === true, - profileBookmarks: result.profileBookmarks === true + profileFiles: result.profileFiles === true, + profileBookmarks: result.profileBookmarks === true, + profilePodcasts: result.profilePodcasts === true, + profileSchool: result.profileSchool === true, + profileMarket: result.profileMarket === true, + profileFeed: result.profileFeed === true, + profileWiki: result.profileWiki === true }; }, + profiles: async () => { + const store = requestScope.getStore(); + if (store && store.aboutProfiles) return store.aboutProfiles; + const pending = (async () => { + const ssb = await cooler.open(); + const out = new Map(); + await new Promise((resolve) => { + pull( + ssb.messagesByType({ type: "about", reverse: true }), + pull.drain((m) => { + const v = (m && m.value) || {}; + const c = v.content || {}; + if (!v.author || c.about !== v.author) return; + const cur = out.get(v.author) || {}; + for (const k of ["name", "description", "image", "visibilityPrefs", "gpgFingerprint", "deviceSource"]) if (cur[k] === undefined && c[k] !== undefined) cur[k] = c[k]; + out.set(v.author, cur); + }, () => resolve()) + ); + }); + const now = Date.now(); + for (const [id, p] of out) if (p.name) nameCache.set(id, p.name, now); + return out; + })(); + if (store) store.aboutProfiles = pending; + return pending; + }, name: async (feedId) => { if (isPublic && (await models.about.publicWebHosting(feedId)) === false) { return "Redacted"; @@ -479,16 +528,14 @@ models.about = { }, }; -// BLOBS MODEL function blobIdToHexPath(blobId) { - const homeDir = os.homedir(); const m = /^&([A-Za-z0-9+/=]+)\.sha256$/.exec(blobId); if (!m) throw new Error('Invalid blobId: ' + blobId); const b64 = m[1]; const buf = Buffer.from(b64, 'base64'); const hex = buf.toString('hex'); const prefix = hex.slice(0, 2); - return path.join(homeDir, '.ssb', 'blobs', 'sha256', prefix, hex); + return path.join(ssbConfig.path, 'blobs', 'sha256', prefix, hex); } async function checkLocalBlob(blobId) { @@ -496,11 +543,21 @@ async function checkLocalBlob(blobId) { try { const buf = await fs.readFile(filePath); if (buf && buf.length) return buf; - } catch (_) { /* not found */ } + } catch (_) {} return null; } models.blob = { + getLocal: async ({ blobId }) => { + const local = await checkLocalBlob(blobId); + if (local) return local; + const ssb = await cooler.open(); + const has = await new Promise((resolve) => ssb.blobs.has(blobId, (err, v) => resolve(!err && !!v))); + if (!has) return null; + return new Promise((resolve) => { + pull(ssb.blobs.get(blobId), pull.collect((err, bufs) => resolve(err || !bufs || !bufs.length ? null : Buffer.concat(bufs)))); + }); + }, getCached: async ({ blobId }) => { const local = await checkLocalBlob(blobId); if (local) return local; @@ -520,7 +577,7 @@ models.blob = { const filePath = blobIdToHexPath(blobId); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, buffer); - } catch (e) { /* ignore */ } + } catch (e) {} resolve(buffer); }) ); @@ -530,12 +587,13 @@ models.blob = { let buf = await checkLocalBlob(blobId); if (buf) return buf; const ssb = await cooler.open(); - await new Promise((resolve, reject) => { - ssb.blobs.want(blobId, (err) => { - if (err) reject(err); - else resolve(); - }); + const arrived = await new Promise((resolve) => { + const wait = setTimeout(() => resolve(false), timeout); + try { + ssb.blobs.want(blobId, (err) => { clearTimeout(wait); resolve(!err); }); + } catch (_) { clearTimeout(wait); resolve(false); } }); + if (!arrived) return null; return new Promise((resolve, reject) => { let timer = setTimeout(() => resolve(null), timeout); pull( @@ -548,7 +606,7 @@ models.blob = { const filePath = blobIdToHexPath(blobId); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, buffer); - } catch (e) { /* ignore */ } + } catch (e) {} resolve(buffer); }) ); @@ -565,7 +623,6 @@ models.blob = { } }; -// FRIENDS MODEL models.friend = { setRelationship: async ({ feedId, following, blocking }) => { if (following && blocking) { @@ -655,7 +712,6 @@ models.friend = { }, }; -// META MODEL models.meta = { myFeedId: async () => { const ssb = await cooler.open(); @@ -754,7 +810,7 @@ models.meta = { let addr = p.address; if (!addr && p.host && p.port) { const core = String(p.key).replace(/^@/, "").replace(/\.ed25519$/, ""); - addr = `net:${p.host}:${p.port}~shs:${core}`; + addr = `${/\.onion$/i.test(String(p.host)) ? 'onion' : 'net'}:${p.host}:${p.port}~shs:${core}`; } if (!addr) continue; seen.add(key); @@ -789,7 +845,7 @@ models.meta = { discovered: async () => { const ssb = await cooler.open(); const snapshot = await ssb.conn.dbPeers(); - const gossipPath = path.join(require('../server/ssb_config').path, 'gossip.json'); + const gossipPath = path.join(ssbConfig.path, 'gossip.json'); let gossipMap = new Map(); try { const gossipData = JSON.parse(await fs.readFile(gossipPath, 'utf8')); @@ -830,7 +886,7 @@ models.meta = { if (dbKeys.has(ck)) continue; let host = data.host, port = data.port; if ((!host || !port) && addr) { - const m = String(addr).match(/^net:([^:]+):(\d+)/); + const m = String(addr).match(/^(?:net|onion):([^:]+):(\d+)/); if (m) { host = host || m[1]; port = port || Number(m[2]); } } mergedSnapshot.push([addr, { key: data.key, host, port, source: data.type || 'staged', verified: data.verified }]); @@ -849,12 +905,19 @@ models.meta = { .map(([, d]) => d && d.key ? canonicalizePubId(d.key) : null) .filter(Boolean) ); - const discoveredPeers = allDbPeers.filter(([, d]) => !onlineKeys.has(canonicalizePubId(d.key))); + const isDeadPeer = (d) => { + const ck = canonicalizePubId(d.key); + const legacy = gossipMap.get(ck) || {}; + return peerHealth.isDead({ key: ck, failures: Math.max(Number(d.failure) || 0, Number(legacy.failure) || 0) }); + }; + const forgotten = await loadUnfollowedSet(); + const discoveredPeers = allDbPeers.filter(([, d]) => !onlineKeys.has(canonicalizePubId(d.key)) && !isDeadPeer(d) && !forgotten.has(canonicalizePubId(d.key))); const discoveredIds = new Set(allDbPeers.map(([, d]) => canonicalizePubId(d.key))); const ebtList = await loadPeersFromEbt(); const ebtMap = new Map(ebtList.map(e => [e.pub, e.users])); const unknownPeers = []; - for (const { pub } of ebtList) { + for (const { pub, touchedAt } of ebtList) { + if (Date.now() - (Number(touchedAt) || 0) >= peerHealth.DEAD_AFTER_MS || forgotten.has(pub)) continue; if (!discoveredIds.has(pub) && !onlineKeys.has(pub)) { const name = await models.about.name(pub).catch(() => pub); unknownPeers.push([pub, { key: pub, name, users: ebtMap.get(pub) || [] }]); @@ -862,68 +925,6 @@ models.meta = { } return { discoveredPeers, unknownPeers }; }, - connStop: async () => { - const ssb = await cooler.open(); - try { - const result = await ssb.conn.stop(); - return result; - } catch (e) { - const expectedName = "TypeError"; - const expectedMessage = "Cannot read property 'close' of null"; - if (e.name === expectedName && e.message === expectedMessage) { - debug("ssbConn is already stopped -- caught error"); - } else { - throw new Error(e); - } - } - }, - connStart: async () => { - const ssb = await cooler.open(); - const result = await ssb.conn.start(); - - return result; - }, - connRestart: async () => { - await models.meta.connStop(); - await models.meta.connStart(); - }, - sync: async () => { - const ssb = await cooler.open(); - - const progress = await ssb.progress(); - let previousTarget = progress.indexes.target; - - let keepGoing = true; - const timeoutInterval = setTimeout(() => { - keepGoing = false; - }, 5 * 60 * 1000); - - await ssb.conn.start(); - - const diff = async () => - new Promise((resolve) => { - setTimeout(async () => { - const currentProgress = await ssb.progress(); - const currentTarget = currentProgress.indexes.target; - const difference = currentTarget - previousTarget; - previousTarget = currentTarget; - debug(`Difference: ${difference} bytes`); - resolve(difference); - }, 5000); - }); - - debug("Starting sync, waiting for new messages..."); - while (keepGoing && (await diff()) === 0) { - debug("Received no new messages."); - } - debug("Finished waiting for first new message."); - while (keepGoing && (await diff()) > 0) { - debug(`Still receiving new messages...`); - } - debug("Finished waiting for last new message."); - clearInterval(timeoutInterval); - await ssb.conn.stop(); - }, acceptInvite: async (invite) => { const ssb = await cooler.open(); const code = toLegacyInvite(String(invite || '')); @@ -959,7 +960,7 @@ models.meta = { return conditions.every((x) => x === true); }; - const maxMessages = 30; // change it to control post overloading + const maxMessages = 30; const getMessages = async ({ myFeedId, @@ -968,23 +969,8 @@ models.meta = { query, filter = null, }) => { - const source = ssb.createLogStream({ reverse: true, limit: logLimit }); - - return new Promise((resolve, reject) => { - pull( - source, - pull.filter((msg) => { - return msg.value.content.type === "post"; - }), - pull.collect((err, collectedMessages) => { - if (err) { - reject(err); - } else { - resolve(collectedMessages); - } - }) - ); - }); + const collected = await readTyped(ssb, ["post"], { limit: logLimit }); + return collected.filter((msg) => msg.value && msg.value.content && msg.value.content.type === "post").reverse(); }; const socialFilter = async ({ @@ -1210,7 +1196,6 @@ models.meta = { return messages.length ? messages[0] : undefined; }; -// POST MODEL const post = { firstBy: async (feedId) => { return getLimitPost(feedId, false); @@ -1294,25 +1279,6 @@ const post = { return { messages, myFeedId }; }, - fromHashtag: async (hashtag, customOptions = {}) => { - const ssb = await cooler.open(); - const myFeedId = ssb.id; - const query = [ - { - $filter: { - dest: `#${hashtag}`, - }, - }, - ]; - const messages = await getMessages({ - myFeedId, - customOptions, - ssb, - query, - }); - - return messages; - }, topicComments: async (rootId, customOptions = {}) => { const ssb = await cooler.open(); const myFeedId = ssb.id; @@ -1347,7 +1313,16 @@ const post = { return msg; }) ); - return fullMessages; + const topic = fullMessages.filter((m) => m && m.value && m.value.content && m.value.content.type === "post" && m.value.content.root === rootId); + const tombstones = topic.length ? await readTyped(ssb, ["tombstone"], { limit: 0 }).catch(() => []) : []; + const gone = buildValidatedTombstoneSet([...topic.filter(m => m && m.key && m.value), ...tombstones]); + const chunkIdx = longText.indexChunks(await readTyped(ssb, [longText.CHUNK_TYPE], { limit: logLimit })); + const lookup = longText.lookupIn(chunkIdx); + return topic + .filter(m => !(m && gone.has(m.key))) + .map(m => m && m.value && longText.hasChunks(m.value.content) + ? { ...m, value: { ...m.value, content: longText.resolveField(m.value.content, "text", m.value.author, lookup) } } + : m); }, likes: async ({ feed }, customOptions = {}) => { const ssb = await cooler.open(); @@ -1425,356 +1400,6 @@ const post = { ); }); - return messages; - }, - latest: async () => { - const ssb = await cooler.open(); - - const myFeedId = ssb.id; - - const source = ssb.query.read( - configure({ - query: [ - { - $filter: { - value: { - timestamp: { $lte: Date.now() }, - content: { - type: { $in: ["post", "blog"] }, - }, - }, - }, - }, - ], - }) - ); - const followingFilter = await socialFilter({ following: true }); - - const messages = await new Promise((resolve, reject) => { - pull( - source, - followingFilter, - publicOnlyFilter, - pull.take(maxMessages), - pull.collect((err, collectedMessages) => { - if (err) { - reject(err); - } else { - resolve(transform(ssb, collectedMessages, myFeedId)); - } - }) - ); - }); - - return messages; - }, - latestExtended: async () => { - const ssb = await cooler.open(); - - const myFeedId = ssb.id; - - const source = ssb.query.read( - configure({ - query: [ - { - $filter: { - value: { - timestamp: { $lte: Date.now() }, - content: { - type: { $in: ["post", "blog"] }, - }, - }, - }, - }, - ], - }) - ); - - const extendedFilter = await socialFilter({ - following: false, - me: false, - }); - - const messages = await new Promise((resolve, reject) => { - pull( - source, - publicOnlyFilter, - extendedFilter, - pull.take(maxMessages), - pull.collect((err, collectedMessages) => { - if (err) { - reject(err); - } else { - resolve(transform(ssb, collectedMessages, myFeedId)); - } - }) - ); - }); - - return messages; - }, - latestTopics: async () => { - const ssb = await cooler.open(); - - const myFeedId = ssb.id; - - const source = ssb.query.read( - configure({ - query: [ - { - $filter: { - value: { - timestamp: { $lte: Date.now() }, - content: { - type: { $in: ["post", "blog"] }, - }, - }, - }, - }, - ], - }) - ); - - const extendedFilter = await socialFilter({ - following: true, - }); - - const messages = await new Promise((resolve, reject) => { - pull( - source, - publicOnlyFilter, - pull.filter(hasNoRoot), - extendedFilter, - pull.take(maxMessages), - pull.collect((err, collectedMessages) => { - if (err) { - reject(err); - } else { - resolve(transform(ssb, collectedMessages, myFeedId)); - } - }) - ); - }); - - return messages; - }, - latestSummaries: async () => { - const ssb = await cooler.open(); - - const myFeedId = ssb.id; - - const options = configure({ - type: "post", - private: false, - }); - - const source = ssb.messagesByType(options); - - const extendedFilter = await socialFilter({ - following: true, - }); - - const messages = await new Promise((resolve, reject) => { - pull( - source, - pull.filter((message) => isNotPrivate(message) && hasNoRoot(message)), - extendedFilter, - pull.take(maxMessages), - pullParallelMap(async (message, cb) => { - const thread = await post.fromThread(message.key); - lodash.set( - message, - "value.meta.thread", - await transform(ssb, thread, myFeedId) - ); - cb(null, message); - }), - pull.collect((err, collectedMessages) => { - if (err) { - reject(err); - } else { - resolve(transform(ssb, collectedMessages, myFeedId)); - } - }) - ); - }); - - return messages; - }, - latestThreads: async () => { - const ssb = await cooler.open(); - - const myFeedId = ssb.id; - - const source = ssb.query.read( - configure({ - query: [ - { - $filter: { - value: { - timestamp: { $lte: Date.now() }, - content: { - type: { $in: ["post", "blog"] }, - }, - }, - }, - }, - ], - }) - ); - const basicSocialFilter = await socialFilter(); - - const messages = await new Promise((resolve, reject) => { - pull( - source, - basicSocialFilter, - pull.filter((message) => isNotPrivate(message) && hasNoRoot(message)), - pull.take(maxMessages), - pullParallelMap(async (message, cb) => { - const thread = await post.fromThread(message.key); - lodash.set( - message, - "value.meta.thread", - await transform(ssb, thread, myFeedId) - ); - cb(null, message); - }), - pull.filter((message) => message.value.meta.thread.length > 1), - pull.collect((err, collectedMessages) => { - if (err) { - reject(err); - } else { - resolve(transform(ssb, collectedMessages, myFeedId)); - } - }) - ); - }); - - return messages; - }, - - popular: async ({ period }) => { - const ssb = await cooler.open(); - - const periodDict = { - day: 1, - week: 7, - month: 30.42, - year: 365, - }; - - if (period in periodDict === false) { - throw new Error("invalid period"); - } - - const myFeedId = ssb.id; - - const now = new Date(); - const earliest = Number(now) - 1000 * 60 * 60 * 24 * periodDict[period]; - const source = ssb.query.read( - configure({ - query: [ - { - $filter: { - value: { - timestamp: { $gte: earliest }, - content: { - type: "vote", - }, - }, - }, - }, - ], - }) - ); - const basicSocialFilter = await socialFilter(); - - const messages = await new Promise((resolve, reject) => { - pull( - source, - publicOnlyFilter, - pull.filter((msg) => { - return ( - isNotEncrypted(msg) && - typeof msg.value.content.vote === "object" && - typeof msg.value.content.vote.link === "string" && - typeof msg.value.content.vote.value === "number" - ); - }), - pull.reduce( - (acc, cur) => { - const author = cur.value.author; - const target = cur.value.content.vote.link; - const value = cur.value.content.vote.value; - - if (acc[author] == null) { - acc[author] = {}; - } - acc[author][target] = Math.max(-1, Math.min(1, value)); - - return acc; - }, - {}, - (err, obj) => { - if (err) { - return reject(err); - } - const adjustedObj = Object.entries(obj).reduce( - (acc, [author, values]) => { - if (author === myFeedId) { - return acc; - } - const entries = Object.entries(values); - const total = 1 + Math.log(entries.length); - - entries.forEach(([link, value]) => { - if (acc[link] == null) { - acc[link] = 0; - } - acc[link] += value / total; - }); - return acc; - }, - [] - ); - - const arr = Object.entries(adjustedObj); - const length = arr.length; - - pull( - pull.values(arr), - pullSort(([, aVal], [, bVal]) => bVal - aVal), - pull.take(Math.min(length, maxMessages)), - pull.map(([key]) => key), - pullParallelMap(async (key, cb) => { - try { - const msg = await post.get(key); - cb(null, msg); - } catch (e) { - cb(null, null); - } - }), - pull.filter( - (message) => - message && - isNotPrivate(message) && - (message.value.content.type === "post" || - message.value.content.type === "blog") - ), - basicSocialFilter, - pull.collect((collectErr, collectedMessages) => { - if (collectErr) { - reject(collectErr); - } else { - resolve(collectedMessages); - } - }) - ); - } - ) - ); - }); - return messages; }, fromThread: async (msgId, customOptions) => { @@ -1909,19 +1534,21 @@ const post = { }, publish: async (options) => { const ssb = await cooler.open(); - const body = { type: "post", ...options }; - return new Promise((resolve, reject) => { - ssb.publish(body, (err, msg) => { + const publish = (content) => new Promise((resolve, reject) => { + ssb.publish(content, (err, msg) => { if (err) reject(err); else resolve(msg); }); }); + const body = { type: "post", ...options }; + const content = typeof body.text === "string" && !body.recps ? await longText.chunkContent(body, "text", { publish }) : body; + return publish(content); }, publishFediverseHandle: async (handle) => { const ssb = await cooler.open(); const current = (await models.about.visibilityPrefs(ssb.id).catch(() => null)) || {}; const h = String(handle || ''); - const prefs = { ...current, fediverseHandle: h }; + const prefs = { ...current, fediverseHandle: current.fediverse === true ? h : '' }; if (h === '') prefs.fediverse = false; return new Promise((resolve, reject) => { ssb.publish({ type: "about", about: ssb.id, visibilityPrefs: prefs }, (err, msg) => err ? reject(err) : resolve(msg)); @@ -1940,6 +1567,9 @@ const post = { ecoTax: r.ecoTax !== false, larpSign: r.larpSign === true, gpg: r.gpg !== false, + phone: r.phone === 'mutuals' || r.phone === 'off' ? r.phone : 'whole', + phoneDnd: !!(getConfig().phone || {}).dnd, + ...(typeof r.clearnetSince === 'string' && r.clearnetSince ? { clearnetSince: r.clearnetSince } : {}), clearnet: r.clearnet === true, fediverse: r.fediverse === true, fediverseHandle: typeof r.fediverseHandle === 'string' ? r.fediverseHandle : '', @@ -1953,7 +1583,13 @@ const post = { clearnetImages: r.clearnetImages === true, clearnetDocuments: r.clearnetDocuments === true, clearnetTorrents: r.clearnetTorrents === true, + clearnetFiles: r.clearnetFiles === true, clearnetBookmarks: r.clearnetBookmarks === true, + clearnetPodcasts: r.clearnetPodcasts === true, + clearnetSchool: r.clearnetSchool === true, + clearnetMarket: r.clearnetMarket === true, + clearnetFeed: r.clearnetFeed === true, + clearnetWiki: r.clearnetWiki === true, profileShops: r.profileShops === true, profileJobs: r.profileJobs === true, profileEvents: r.profileEvents === true, @@ -1964,7 +1600,13 @@ const post = { profileImages: r.profileImages === true, profileDocuments: r.profileDocuments === true, profileTorrents: r.profileTorrents === true, - profileBookmarks: r.profileBookmarks === true + profileFiles: r.profileFiles === true, + profileBookmarks: r.profileBookmarks === true, + profilePodcasts: r.profilePodcasts === true, + profileSchool: r.profileSchool === true, + profileMarket: r.profileMarket === true, + profileFeed: r.profileFeed === true, + profileWiki: r.profileWiki === true }; }; const prefs = visibilityPrefs ? normalizePrefs(visibilityPrefs) : undefined; @@ -1985,9 +1627,9 @@ const post = { if (resolvedBlobId !== undefined) baseFields.gpgBlobId = String(resolvedBlobId || ""); if (image && image.length > 0) { const megabyte = Math.pow(2, 20); - const maxSize = 50 * megabyte; + const maxSize = 75 * megabyte; if (image.length > maxSize) { - throw new Error("File is too big, maximum size is 50 megabytes"); + throw new Error("File is too big, maximum size is 75 megabytes"); } return new Promise((resolve, reject) => { pull( @@ -2140,32 +1782,34 @@ const post = { inbox: async () => { const ssb = await cooler.open(); const myFeedId = ssb.id; - const rawMessages = await new Promise((resolve, reject) => { - pull( - ssb.createLogStream({ reverse: true, limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); - const decryptedMessages = rawMessages.map(msg => { - try { - return ssb.private.unbox(msg); - } catch { - return null; + let decryptedMessages = []; + try { + if (ssb.private && typeof ssb.private.read === 'function') { + decryptedMessages = await new Promise((resolve, reject) => pull(ssb.private.read({ reverse: true, limit: logLimit * 10 }), pull.collect((err, arr) => err ? reject(err) : resolve(arr || [])))); } - }).filter(Boolean); + } catch (_) { decryptedMessages = []; } + if (!decryptedMessages.length) { + const rawMessages = await readTyped(ssb, [], { limit: logLimit, withWindow: true }); + decryptedMessages = rawMessages.map(msg => { + try { + return ssb.private.unbox(msg); + } catch { + return null; + } + }).filter(Boolean); + } const tombstoneTargets = buildValidatedTombstoneSet(decryptedMessages); return decryptedMessages.filter(msg => { if (tombstoneTargets.has(msg.key)) return false; const content = msg.value?.content; const author = msg.value?.author; return content?.type === 'post' && content?.private === true && (author === myFeedId || content.to?.includes(myFeedId)); - }); + }).reverse(); } }; models.post = post; -// SPREAD MODEL models.vote = { publish: async ({ messageKey, value, recps }) => { const ssb = await cooler.open(); @@ -2212,9 +1856,11 @@ models.lifetime = (() => { if (days < FRESH_ORANGE_DAYS) return { bucket: 'orange', range: '2w–6m' }; return { bucket: 'red', range: '≥6m' }; }; - const lastAuthorTs = async (feedId) => { + const lastAuthorTsRaw = async (feedId) => { if (!feedId) return null; const ssbClient = await cooler.open(); + const activity = await authorActivity(ssbClient); + if (activity) return activity.get(feedId) || null; return new Promise((resolve) => { try { pull( @@ -2230,7 +1876,7 @@ models.lifetime = (() => { } catch (_) { resolve(null); } }); }; - const lastBacklinkTs = async (msgKey) => { + const lastBacklinkTsRaw = async (msgKey) => { if (!msgKey) return null; const ssbClient = await cooler.open(); return new Promise((resolve) => { @@ -2246,7 +1892,23 @@ models.lifetime = (() => { } catch (_) { resolve(null); } }); }; + const recent = new Map(); + const remembered = (bucket, key, fn) => { + if (!key) return fn(key); + let memo = recent.get(bucket); + if (!memo) recent.set(bucket, memo = new Map()); + const hit = memo.get(key); + if (hit && Date.now() - hit.at < LIFETIME_TTL_MS) return hit.value; + const value = fn(key); + memo.delete(key); + memo.set(key, { at: Date.now(), value }); + while (memo.size > LIFETIME_CACHE_MAX) memo.delete(memo.keys().next().value); + return value; + }; + const lastAuthorTs = (feedId) => remembered('author', feedId, lastAuthorTsRaw); + const lastBacklinkTs = (msgKey) => remembered('backlink', msgKey, lastBacklinkTsRaw); return { + forget: () => recent.clear(), bucket: bucketOf, lastAuthorTs, lastBacklinkTs, @@ -2259,30 +1921,40 @@ models.lifetime = (() => { const candidates = [authorTs, interactionTs, createdTs].filter(x => typeof x === 'number' && x > 0); const lastTs = candidates.length ? Math.max(...candidates) : null; const { bucket, range } = bucketOf(lastTs); - return { bucket, range, lastTs, authorTs, interactionTs, createdTs }; + const own = [interactionTs, createdTs].filter(x => typeof x === 'number' && x > 0); + const content = bucketOf(own.length ? Math.max(...own) : null); + return { bucket, range, contentBucket: content.bucket, contentRange: content.range, lastTs, authorTs, interactionTs, createdTs }; }, async enrichAndFilter(items, opts = {}) { const { includeDead = false, getKey = (x) => x.id || x.key, getAuthor = (x) => x.author, getCreatedAt = (x) => x.createdAt } = opts; - const authorCache = new Map(); + const list = Array.from(items || []); + const authors = [...new Set(list.map(getAuthor).filter(Boolean))]; + const mapLimit = async (items, fn) => { + const out = new Array(items.length); + let next = 0; + await Promise.all(Array.from({ length: Math.min(LIFETIME_CONCURRENCY, items.length) }, async () => { + while (next < items.length) { const k = next++; out[k] = await fn(items[k]); } + })); + return out; + }; + const authorTsList = await mapLimit(authors, (a) => lastAuthorTs(a)); + const authorCache = new Map(authors.map((a, i) => [a, authorTsList[i]])); + const interactionList = await mapLimit(list, (item) => { const key = getKey(item); return key ? lastBacklinkTs(key) : null; }); const out = []; - for (const item of items) { + for (let i = 0; i < list.length; i++) { + const item = list[i]; const author = getAuthor(item); - let authorTs; - if (author && authorCache.has(author)) { - authorTs = authorCache.get(author); - } else { - authorTs = author ? await lastAuthorTs(author) : null; - if (author) authorCache.set(author, authorTs); - } - const key = getKey(item); - const interactionTs = key ? await lastBacklinkTs(key) : null; + const authorTs = author ? authorCache.get(author) : null; + const interactionTs = interactionList[i]; const createdAt = getCreatedAt(item); const createdTs = createdAt ? new Date(createdAt).getTime() : null; const candidates = [authorTs, interactionTs, createdTs].filter(x => typeof x === 'number' && x > 0); const lastTs = candidates.length ? Math.max(...candidates) : null; const { bucket, range } = bucketOf(lastTs); if (!includeDead && bucket === 'red') continue; - out.push({ ...item, lifetime: { bucket, range, lastTs, authorTs, interactionTs, createdTs } }); + const own = [interactionTs, createdTs].filter(x => typeof x === 'number' && x > 0); + const content = bucketOf(own.length ? Math.max(...own) : null); + out.push({ ...item, lifetime: { bucket, range, contentBucket: content.bucket, contentRange: content.range, lastTs, authorTs, interactionTs, createdTs } }); } return out; } diff --git a/src/models/maps_model.js b/src/models/maps_model.js index 628507f..0cae474 100644 --- a/src/models/maps_model.js +++ b/src/models/maps_model.js @@ -3,6 +3,7 @@ const crypto = require("crypto"); const { getConfig } = require("../configs/config-manager.js"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); const { collabContent, openInviteOf } = require('../backend/collab_content'); +const { readTyped, memoIndex } = require("./typed_log"); const mapCollab = collabContent({ membersField: 'members', undecField: 'encrypted', contentFields: ['title', 'description', 'image'], listFields: ['tags', 'invites', 'markers'] }); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -34,6 +35,16 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { return (tribeCrypto && tribeCrypto.getKeys(rid)) || []; }; const lookupGen = (rid) => ((ownCrypto && ownCrypto.getGen(rid)) || (tribeCrypto && tribeCrypto.getGen(rid)) || 0); + const keyProofFor = (keyHex, id) => { try { return crypto.createHmac("sha256", Buffer.from(keyHex, "hex")).update(String(id), "utf8").digest("hex"); } catch (_) { return null; } }; + const hashCode = (code, salt) => (tribeCrypto ? tribeCrypto.hashInviteCode(code, salt) : null); + const inviteMatches = (inv, code) => { + if (!code) return false; + if (typeof inv === "string") return inv === code; + if (!inv || typeof inv !== "object") return false; + if (typeof inv.code === "string") return inv.code === code; + return typeof inv.ch === "string" && hashCode(code, inv.salt) === inv.ch; + }; + const inviteToken = (inv) => (inv && typeof inv === "object" && typeof inv.ch === "string") ? inv.ch : (typeof inv === "string" ? inv : (inv && inv.code) || ""); const rotateMapKey = async (rootId, remainingMembers) => { if (!ownCrypto || !tribeCrypto || !rootId) return; @@ -41,11 +52,10 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (!existing) return; const newKey = ownCrypto.generateTribeKey(); const newGen = ownCrypto.addNewKey(rootId, newKey); - if (!Array.isArray(remainingMembers) || !remainingMembers.length) return; const ssbClient = await openSsb(); const ssbKeys = require("../server/node_modules/ssb-keys"); const memberKeys = {}; - for (const m of remainingMembers) { + for (const m of new Set([ssbClient.id, ...(Array.isArray(remainingMembers) ? remainingMembers : [])])) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys); } catch (_) {} } if (Object.keys(memberKeys).length) { @@ -61,10 +71,13 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const ssbClient = await openSsb(); const ssbKeys = require("../server/node_modules/ssb-keys"); const config = require("../server/ssb_config"); - const msgs = await new Promise((resolve, reject) => pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((e, m) => e ? reject(e) : resolve(m)))); + const msgs = await getAllMessages(ssbClient); + const idx = buildIndex(unwrapForIndex(msgs)); + const trusted = (root, author) => { const n = idx.nodes.get(root); return !!author && !!n && (author === n.author || author === ssbClient.id); }; for (const m of msgs) { const c = m.value && m.value.content; if (!c || c.type !== "tribe-keys") continue; + if (!trusted(c.tribeId, m.value.author)) continue; const memberKeys = c.memberKeys; if (!memberKeys || typeof memberKeys !== "object") continue; const boxed = memberKeys[ssbClient.id]; @@ -148,13 +161,9 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { } }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const MAP_TYPES = ["map", "mapMarker", "mapMember", "tribe-msg", "tribe-keys", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, MAP_TYPES, { limit: logLimit }); const getMsg = async (ssbClient, key) => new Promise((resolve) => { @@ -185,7 +194,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { } if (c.type === "mapMember" && c.target) { - memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0, code: typeof c.code === "string" ? c.code : "" }); + memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0, code: typeof c.code === "string" ? c.code : "", keyProof: typeof c.keyProof === "string" ? c.keyProof : "" }); continue; } @@ -207,7 +216,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (c.type !== "map") continue; const ts = v.timestamp || m.timestamp || 0; - nodes.set(k, { key: k, ts, c, author: v.author }); + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, author: v.author }); authorByKey.set(k, v.author); if (c.replaces) { @@ -235,21 +244,15 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { return cur; }; - const contentTipOf = (root) => { - const rn = nodes.get(root); - if (!rn) return root; - let cur = root, best = root, g = 0; - const seen = new Set(); - while (child.has(cur) && !seen.has(cur) && g++ < 100000) { - seen.add(cur); - const next = child.get(cur); - const n = nodes.get(next); - if (!n) break; - if (n.author === rn.author && !tomb.has(next)) best = next; - cur = next; - } - return best; - }; + const ownerTip = new Map(); + for (const [k, n] of nodes) { + const r = rootOf(k); + const rn = nodes.get(r); + if (!rn || n.author !== rn.author) continue; + const prev = ownerTip.has(r) ? nodes.get(ownerTip.get(r)) : null; + if (!prev || n.seq > prev.seq || (n.seq === prev.seq && n.ts >= prev.ts)) ownerTip.set(r, k); + } + const contentTipOf = (root) => ownerTip.get(root) || root; for (const t of tombRequests) { const targetAuthor = authorByKey.get(t.target); @@ -268,8 +271,21 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const forward = new Map(); for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); + const invitesOf = (root) => { + const out = []; + for (const id of [root, contentTipOf(root)]) { const n = nodes.get(id); if (n && Array.isArray(n.c.invites)) out.push(...n.c.invites); } + return out; + }; + const selfJoinAllowed = (root, mm) => { + const oc = (nodes.get(root) || {}).c || {}; + if (!oc.encryptedPayload || oc.tribeId) return true; + if (invitesOf(root).some(inv => inv && typeof inv === "object" && inv.public === true)) return true; + return !!mm.keyProof && lookupKeys(root).some(k => keyProofFor(k, mm.member) === mm.keyProof); + }; + const memberByRoot = new Map(); const consumedByRoot = new Map(); + const codeClaimsByRoot = new Map(); for (const mm of memberMsgs) { if (!nodes.has(mm.target)) continue; const r = rootOf(mm.target); @@ -279,6 +295,11 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const byOwner = mm.author === ownerAuthor; if (!self && !byOwner) continue; if (!mm.member) continue; + if (mm.on && mm.code) { + if (!codeClaimsByRoot.has(r)) codeClaimsByRoot.set(r, []); + codeClaimsByRoot.get(r).push({ code: mm.code, member: mm.member, keyProof: mm.keyProof, byOwner }); + } + if (self && !byOwner && mm.on && !selfJoinAllowed(r, mm)) continue; if (mm.on && mm.code) { if (!consumedByRoot.has(r)) consumedByRoot.set(r, new Set()); consumedByRoot.get(r).add(mm.code); @@ -289,12 +310,24 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (!prev || mm.ts >= prev.ts) m2.set(mm.member, { on: mm.on, ts: mm.ts }); } + const codeClaims = (root, codes) => (codeClaimsByRoot.get(root) || []).filter(c => codes.includes(c.code)); + const isCodeConsumed = (root, code) => { if (!code) return false; const set = consumedByRoot.get(root); return !!(set && set.has(code)); }; + for (const [k, n] of nodes) { + const r = rootOf(k); + const rn = nodes.get(r); + if (!rn || n.author === rn.author || !Array.isArray(n.c.members) || !selfJoinAllowed(r, { member: n.author })) continue; + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()); + const m2 = memberByRoot.get(r); + const prev = m2.get(n.author); + if (!prev || n.ts >= prev.ts) m2.set(n.author, { on: n.c.members.includes(n.author), ts: n.ts }); + } + const resolveMembers = (root) => { const ownerNode = nodes.get(root); const oc = ownerNode ? ownerNode.c : {}; @@ -305,29 +338,31 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { return [...set]; }; - const ensureMemberKeys = async (ssbClient, messages, items) => { - if (!tribeCrypto) return; - const distributed = new Map(); + return { tomb, nodes, parent, child, strictChild, rootOf, tipOf, contentTipOf, tipByRoot, contentTipByRoot, forward, markers, rawMarkers, resolveMembers, isCodeConsumed, codeClaims }; + }; + + const ensureMemberKeys = async (ssbClient, messages, items) => { + if (!tribeCrypto || !ownCrypto) return; + const leavesByRoot = new Map(); for (const m of messages) { const c = m.value && m.value.content; - if (!c || c.type !== "tribe-keys" || !c.tribeId) continue; - const mk = c.memberKeys; - if (!mk || typeof mk !== "object") continue; - if (!distributed.has(c.tribeId)) distributed.set(c.tribeId, new Set()); - for (const id of Object.keys(mk)) distributed.get(c.tribeId).add(id); + if (!c || c.type !== "mapMember" || c.on !== false || !c.target || !c.member) continue; + if (!leavesByRoot.has(c.target)) leavesByRoot.set(c.target, []); + leavesByRoot.get(c.target).push({ member: c.member, author: m.value.author, ts: Number(m.timestamp || m.value.timestamp || 0) }); } const ssbKeys = require("../server/node_modules/ssb-keys"); for (const item of (Array.isArray(items) ? items : [])) { - if (!item || item.encrypted) continue; + if (!item || item.encrypted || item.author !== ssbClient.id) continue; const rootId = item.rootId; - if (!rootId) continue; + if (!rootId || !lookupKey(rootId)) continue; + const members = Array.isArray(item.members) ? item.members : []; + const leaves = (leavesByRoot.get(rootId) || []).filter(l => l.author === l.member || l.author === item.author); + const plan = ownCrypto.keyPlan({ rootId, ownerId: item.author, gen: lookupGen(rootId), members, messages, leaves }); + if (plan.rotate) { await rotateMapKey(rootId, members); continue; } + if (!plan.missing.length) continue; const key = lookupKey(rootId); - if (!key) continue; - const have = distributed.get(rootId) || new Set(); - const missing = (Array.isArray(item.members) ? item.members : []).filter(m => m && m !== ssbClient.id && !have.has(m)); - if (!missing.length) continue; const memberKeys = {}; - for (const m of missing) { + for (const m of plan.missing) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(key, m, ssbKeys) } catch (_) {} } if (!Object.keys(memberKeys).length) continue; @@ -337,13 +372,11 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { } } - return { tomb, nodes, parent, child, strictChild, rootOf, tipOf, contentTipOf, tipByRoot, contentTipByRoot, forward, markers, rawMarkers, resolveMembers, isCodeConsumed }; - }; - const expandMarkers = async (idx) => { for (const [mapId, raws] of idx.rawMarkers.entries()) { const list = []; for (const r of raws) { + if (idx.tomb.has(r.key)) continue; let c = r.c; if (c.encryptedPayload && tribeCrypto && tribesModel) { const dec = await tribeCrypto.decryptFromTribe(c, tribesModel); @@ -364,6 +397,15 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { } }; + const markersFor = (idx, tipId, rootId) => { + const seen = new Set(); + return safeArr(idx.markers.get(tipId)).concat(safeArr(idx.markers.get(rootId))).filter((mk) => { + if (seen.has(mk.key)) return false; + seen.add(mk.key); + return true; + }); + }; + const buildMap = (node, rootId, viewerId, markerList = [], members = null) => { const c = node.c || {}; @@ -384,6 +426,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { invites: Array.isArray(c.invites) ? c.invites : [], tribeId: c.tribeId || null, encrypted: !!undec, + contentEncrypted: !!(c.encryptedPayload || c._decrypted === true), createdAt: c.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || null, markers: markerList.filter((mk) => !mk.tombstoned) @@ -396,7 +439,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (idx.tomb.has(tipId)) continue; const node = idx.nodes.get(tipId); if (!node) continue; - const markerList = safeArr(idx.markers.get(tipId)).concat(safeArr(idx.markers.get(rootId))); + const markerList = markersFor(idx, tipId, rootId); items.push(buildMap(node, rootId, viewerId, markerList, idx.resolveMembers(rootId))); } return items; @@ -411,7 +454,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (!ownCrypto || typeof ownCrypto.getAllRootIds !== "function") return 0; try { const ssbClient = await openSsb(); - const messages = await new Promise((resolve, reject) => pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((e, m) => e ? reject(e) : resolve(m)))); + const messages = await getAllMessages(ssbClient); const tomb = buildValidatedTombstoneSet(messages); const all = ownCrypto.getAllRootIds(); let removed = 0; @@ -431,11 +474,9 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (!map) throw new Error("Map not found"); if (map.author === userId) throw new Error("Author cannot leave their own map"); if (!Array.isArray(map.members) || !map.members.includes(userId)) return; - const members = map.members.filter(m => m !== userId); const rootId = await this.resolveRootId(map.rootId || map.key); const content = { type: "mapMember", target: rootId, member: userId, on: false, createdAt: new Date().toISOString() }; const result = await new Promise((resolve, reject) => ssbClient.publish(content, (e, r) => e ? reject(e) : resolve(r))); - try { await rotateMapKey(rootId, members); } catch (_) {} return result; }, @@ -587,7 +628,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (effectiveTribeId) { updated = await encryptIfTribe(updated); - } else if (mType !== "SINGLE") { + } else if (mType !== "OPEN") { updated = encryptStandalone(updated, rootId); } @@ -623,9 +664,10 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(unwrapForIndex(messages)); + await decryptIndexNodes(idx); - let tipId = mapId; - while (idx.forward.has(tipId)) tipId = idx.forward.get(tipId); + const rootId = idx.rootOf(mapId); + const tipId = idx.contentTipOf(rootId); if (idx.tomb.has(tipId)) throw new Error("Map not found"); const node = idx.nodes.get(tipId); @@ -634,12 +676,11 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const mapDecrypted = await decryptIfTribe(node.c); assertReadable(mapDecrypted, "Map"); const mapType = mapDecrypted.mapType || node.c.mapType || "SINGLE"; - const mapAuthor = mapDecrypted.author || node.c.author; + const mapAuthor = node.author; if (mapType === "SINGLE") throw new Error("Map does not allow markers"); if (mapType === "CLOSED" && mapAuthor !== userId) throw new Error("Only the map creator can add markers"); const now = new Date().toISOString(); - const rootId = idx.rootOf(tipId); let content = { type: "mapMarker", mapId: rootId, @@ -664,6 +705,24 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { }); }, + async deleteMarker(markerId) { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + const msg = await getMsg(ssbClient, markerId); + if (!msg || !msg.content) throw new Error("Marker not found"); + let c = msg.content; + if (c.encryptedPayload && tribeCrypto && tribesModel) { + const dec = await tribeCrypto.decryptFromTribe(c, tribesModel); + if (dec && !dec._undecryptable) c = dec; + } + if (c.type !== "mapMarker" && !c.encryptedPayload) throw new Error("Marker not found"); + if (msg.author !== userId) throw new Error("Not the author"); + const tombstone = await tombFor(markerId, c.tribeId, userId); + return new Promise((resolve, reject) => { + ssbClient.publish(tombstone, (err, res) => (err ? reject(err) : resolve(res))); + }); + }, + async listAll(filterOrOpts = "all", maybeOpts = {}) { const ssbClient = await openSsb(); @@ -703,9 +762,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const viewer = viewerId || ssbClient.id; const messages = await getAllMessages(ssbClient); - const idx = buildIndex(unwrapForIndex(messages)); - await decryptIndexNodes(idx); - await expandMarkers(idx); + const idx = await memoIndex('maps', messages, async (m) => { const built = buildIndex(unwrapForIndex(m)); await decryptIndexNodes(built); await expandMarkers(built); return built; }); let root = id; while (idx.parent.has(root)) root = idx.parent.get(root); @@ -721,11 +778,11 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { const dec = await tribeCrypto.decryptFromTribe(c, tribesModel); c = dec && !dec._undecryptable ? { ...dec, _decrypted: true } : { ...c, _decrypted: false }; } - const markerList = safeArr(idx.markers.get(tip)).concat(safeArr(idx.markers.get(root))); + const markerList = markersFor(idx, tip, root); return buildMap({ key: tip, ts: msg.timestamp || 0, c, author: msg.author }, root, viewer, markerList, idx.resolveMembers(root)); } - const markerList = safeArr(idx.markers.get(tip)).concat(safeArr(idx.markers.get(root))); + const markerList = markersFor(idx, tip, root); const map = buildMap(node, root, viewer, markerList, idx.resolveMembers(root)); return mapCollab.fold(map, collectMaps(idx, viewer)); }, @@ -740,8 +797,11 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { let invite = code; if (tribeCrypto && !map.tribeId) { const inviteSalt = tribeCrypto.generateInviteSalt(); + const ident = opts.public ? { code, public: true } : { ch: hashCode(code, inviteSalt) }; const ekChain = tribeCrypto.encryptChainForInvite([map.rootId || map.key], code, inviteSalt); - if (ekChain) invite = { code, ekChain, salt: inviteSalt, gen: lookupGen(map.rootId || map.key) || 1, ...(opts.public ? { public: true } : {}) }; + const mapKey = ekChain ? null : lookupKey(map.rootId || map.key); + if (ekChain) invite = { ...ident, ekChain, salt: inviteSalt, gen: lookupGen(map.rootId || map.key) || 1 }; + else if (mapKey) invite = { ...ident, ek: tribeCrypto.encryptForInvite(mapKey, code, inviteSalt), salt: inviteSalt, gen: lookupGen(map.rootId || map.key) || 1 }; } if (opts.public && typeof invite !== "object") invite = { code, public: true }; const tipId = await this.resolveCurrentId(mapId); @@ -834,6 +894,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { await new Promise((resolve, reject) => ssbClient.publish(updated, (e, r) => e ? reject(e) : resolve(r))); const tomb1 = await tombFor(tipId, effectiveTribeId, userId); await new Promise((resolve, reject) => ssbClient.publish(tomb1, e => e ? reject(e) : resolve())); + if (!effectiveTribeId) await rotateMapKey(rootId, Array.isArray(map.members) ? map.members : []); }, async joinByInvite(code) { @@ -848,8 +909,7 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { for (const m of maps) { const invs = Array.isArray(m.invites) ? m.invites : []; for (const inv of invs) { - if (typeof inv === "string" && inv === code) { matched = m; matchedInvite = inv; break; } - if (typeof inv === "object" && inv.code === code) { matched = m; matchedInvite = inv; break; } + if (inviteMatches(inv, code)) { matched = m; matchedInvite = inv; break; } } if (matched) break; } @@ -857,30 +917,38 @@ module.exports = ({ cooler, tribeCrypto, mapCrypto, tribesModel }) => { if (Array.isArray(matched.members) && matched.members.includes(userId)) throw new Error("Already a member"); const isPublic = (typeof matchedInvite === "object" && matchedInvite.public === true); const rootId = await this.resolveRootId(matched.rootId || matched.key); - if (!isPublic) { - const idx = buildIndex(unwrapForIndex(await getAllMessages(ssbClient))); - if (idx.isCodeConsumed(rootId, code)) throw new Error("Invite already used"); - } + const token = inviteToken(matchedInvite) || code; let mapKey = null; + let chain = null; if (tribeCrypto && typeof matchedInvite === "object") { if (matchedInvite.ekChain) { - const chain = tribeCrypto.decryptChainFromInvite(matchedInvite.ekChain, code, matchedInvite.salt, 3); - if (Array.isArray(chain) && chain.length) { - for (const entry of chain) { - if (Array.isArray(entry.keys) && entry.keys.length) { - tribeCrypto.setKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length); - } else if (entry.key) { - tribeCrypto.setKey(entry.rootId, entry.key, entry.gen || 1); - } - } - mapKey = chain[0].key; - } + chain = tribeCrypto.decryptChainFromInvite(matchedInvite.ekChain, code, matchedInvite.salt, 3); + if (Array.isArray(chain) && chain.length) mapKey = chain[0].key || (Array.isArray(chain[0].keys) ? chain[0].keys[0] : null); + else chain = null; } else if (matchedInvite.ek) { mapKey = tribeCrypto.decryptFromInvite(matchedInvite.ek, code, matchedInvite.salt); - ownCrypto.setKey(matched.rootId || matched.key, mapKey, matchedInvite.gen || 1); } } - await new Promise((resolve, reject) => ssbClient.publish({ type: "mapMember", target: rootId, member: userId, on: true, createdAt: new Date().toISOString(), ...(isPublic ? {} : { code }) }, (e, r) => e ? reject(e) : resolve(r))); + if (!isPublic) { + const idx = buildIndex(unwrapForIndex(await getAllMessages(ssbClient))); + if (idx.isCodeConsumed(rootId, token) || idx.isCodeConsumed(rootId, code)) throw new Error("Invite already used"); + const claims = idx.codeClaims(rootId, [token, code]).filter(c => c.member !== userId); + const spent = mapKey ? claims.some(c => c.byOwner || keyProofFor(mapKey, c.member) === c.keyProof) : claims.length > 0; + if (spent) throw new Error("Invite already used"); + } + if (chain) { + for (const entry of chain) { + if (Array.isArray(entry.keys) && entry.keys.length) { + tribeCrypto.setKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length); + } else if (entry.key) { + tribeCrypto.setKey(entry.rootId, entry.key, entry.gen || 1); + } + } + } else if (mapKey) { + ownCrypto.setKey(matched.rootId || matched.key, mapKey, matchedInvite.gen || 1); + } + if (!mapKey && matched.contentEncrypted && !matched.tribeId) throw new Error("Invalid or expired invite code"); + await new Promise((resolve, reject) => ssbClient.publish({ type: "mapMember", target: rootId, member: userId, on: true, createdAt: new Date().toISOString(), ...(isPublic ? {} : { code: token }), ...(mapKey ? { keyProof: keyProofFor(mapKey, userId) } : {}) }, (e, r) => e ? reject(e) : resolve(r))); if (tribeCrypto && mapKey) { try { const ssbKeys = require("../server/node_modules/ssb-keys"); diff --git a/src/models/market_model.js b/src/models/market_model.js index ed2a038..9bbf051 100644 --- a/src/models/market_model.js +++ b/src/models/market_model.js @@ -3,7 +3,8 @@ const moment = require("../server/node_modules/moment") const { getConfig } = require("../configs/config-manager.js") const categories = require("../backend/opinion_categories") const { buildValidatedTombstoneSet } = require('./tombstone_validator') -const { dedupeByPreferring, norm } = require('../backend/dedupe') +const { readTyped, memoIndex } = require('./typed_log') +const { dedupeByPreferring, norm, versionGroups } = require('../backend/dedupe') const logLimit = getConfig().ssbLogStream?.limit || 1000 const N = (s) => String(s || "").toUpperCase().replace(/\s+/g, "_") @@ -63,11 +64,9 @@ module.exports = ({ cooler, tribeCrypto }) => { return ssb } - const readAll = async (ssbClient) => { - return new Promise((resolve, reject) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))) - ) - } + const MARKET_TYPES = ["market", "marketBid", "marketPurchase", "marketOpinion", "tombstone"] + + const readAll = async (ssbClient) => readTyped(ssbClient, MARKET_TYPES, { limit: logLimit }) const buildMarketIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages) @@ -83,31 +82,16 @@ module.exports = ({ cooler, tribeCrypto }) => { if (c.type === "marketPurchase") { purchases.push({ target: c.target, author: m.value.author, ts: (m.value && m.value.timestamp) || 0 }); continue } if (c.type === "marketOpinion" && c.target) { opinionMsgs.push({ target: c.target, author: m.value.author, category: c.category }); continue } if (c.type !== "market") continue - nodes.set(m.key, { key: m.key, ts: (m.value && m.value.timestamp) || m.timestamp || 0, c, author: m.value.author }) + nodes.set(m.key, { key: m.key, ts: (m.value && m.value.timestamp) || m.timestamp || 0, seq: m.value.sequence || 0, c, author: m.value.author }) } - const naivePrev = new Map(), strictNext = new Map() - for (const [key, n] of nodes) { - const t = n.c.replaces - if (!t) continue - naivePrev.set(key, t) - } - for (const [child, parent] of Array.from(naivePrev.entries())) { - const cn = nodes.get(child) - const pn = nodes.get(parent) - if (!pn) { naivePrev.delete(child); continue } - if (!cn || cn.author !== pn.author) { naivePrev.delete(child); nodes.delete(child); continue } - strictNext.set(parent, child) - } - return { tomb, nodes, bids, purchases, opinionMsgs, naivePrev, strictNext } + const vg = versionGroups(Array.from(nodes.values()).map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === "string" ? n.c.replaces : null, claimedAuthor: typeof n.c.seller === "string" ? n.c.seller : null }))) + return { tomb, nodes, bids, purchases, opinionMsgs, vg } } const resolveGroups = (idx) => { - const { tomb, nodes, bids, purchases, opinionMsgs, naivePrev, strictNext } = idx - const rootOf = (key) => { let x = key, g = 0; while (naivePrev.has(x) && nodes.has(naivePrev.get(x)) && g++ < 100000) x = naivePrev.get(x); return x } - const followStrict = (key) => { let x = key, g = 0; while (strictNext.has(x) && g++ < 100000) x = strictNext.get(x); return x } + const { tomb, nodes, bids, purchases, opinionMsgs, vg } = idx + const rootOf = vg.rootOf - const groups = new Map() - for (const key of nodes.keys()) { const r = rootOf(key); if (!groups.has(r)) groups.set(r, []); groups.get(r).push(key) } const bidsByRoot = new Map() for (const bd of bids) { if (!nodes.has(bd.target)) continue; const r = rootOf(bd.target); if (!bidsByRoot.has(r)) bidsByRoot.set(r, []); bidsByRoot.get(r).push(bd) } const soldByRoot = new Map() @@ -128,13 +112,12 @@ module.exports = ({ cooler, tribeCrypto }) => { } const out = new Map() - for (const [root, keys] of groups) { + for (const root of vg.roots) { + const keys = vg.groups.get(root) || [root] const rootNode = nodes.get(root) const sellerId = rootNode ? rootNode.author : null const sellerKeys = keys.filter(k => { const n = nodes.get(k); return n && n.author === sellerId }) - let tip = followStrict(root) - const tipNode0 = nodes.get(tip) - if (!tipNode0 || tipNode0.author !== sellerId) tip = root + const tip = vg.tipOf(root) if (tomb.has(tip)) continue let best = nodes.get(tip) if (!best) continue @@ -225,10 +208,9 @@ module.exports = ({ cooler, tribeCrypto }) => { async resolveCurrentId(itemId) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const { tomb, strictNext } = buildMarketIndex(messages) - let cur = itemId, g = 0 - while (strictNext.has(cur) && g++ < 100000) cur = strictNext.get(cur) - if (tomb.has(cur)) throw new Error("Item not found") + const { tomb, vg } = buildMarketIndex(messages) + const cur = vg.tipOf(itemId) + if (tomb.has(cur) || vg.isForeignCopy(vg.rootOf(cur))) throw new Error("Item not found") return cur }, @@ -431,9 +413,8 @@ module.exports = ({ cooler, tribeCrypto }) => { const userId = ssbClient.id const messages = await readAll(ssbClient) - const idx = buildMarketIndex(messages) - const rootOf = (key) => { let x = key, g = 0; while (idx.naivePrev.has(x) && idx.nodes.has(idx.naivePrev.get(x)) && g++ < 100000) x = idx.naivePrev.get(x); return x } - const grp = resolveGroups(idx).get(rootOf(itemId)) + const idx = memoIndex('market', messages, buildMarketIndex) + const grp = resolveGroups(idx).get(idx.vg.rootOf(itemId)) if (!grp) return null const { tip, rootId, best, statusN, poll, soldCount, buyers, opinions, voters } = grp @@ -495,6 +476,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const myId = ssbClient.id const now = moment() const list = Array.isArray(items) ? items : [] + let changed = 0 for (const item of list) { if (!item || !item.deadline) continue @@ -517,8 +499,10 @@ module.exports = ({ cooler, tribeCrypto }) => { try { await this.updateItemById(item.id, { status }) + changed++ } catch (_) {} } + return changed }, async getItemByShopProductId(shopProductId) { @@ -532,11 +516,8 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const userId = ssbClient.id const messages = await readAll(ssbClient) - const groups = resolveGroups(buildMarketIndex(messages)) - let group = null - for (const g of groups.values()) { - if (g.rootId === itemId || g.tip === itemId) { group = g; break } - } + const idx = buildMarketIndex(messages) + const group = resolveGroups(idx).get(idx.vg.rootOf(itemId)) if (!group) throw new Error("Item not found") if (group.best.author === userId) throw new Error("You cannot rate your own item") if ((group.voters || []).includes(userId)) throw new Error("Already voted") @@ -595,8 +576,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildMarketIndex(messages) - const rootOf = (key) => { let x = key, g = 0; while (idx.naivePrev.has(x) && idx.nodes.has(idx.naivePrev.get(x)) && g++ < 100000) x = idx.naivePrev.get(x); return x } - const grp = resolveGroups(idx).get(rootOf(itemId)) + const grp = resolveGroups(idx).get(idx.vg.rootOf(itemId)) if (!grp) throw new Error("Item not found") const c = grp.best.c diff --git a/src/models/melody_model.js b/src/models/melody_model.js index 072b763..57ebd20 100644 --- a/src/models/melody_model.js +++ b/src/models/melody_model.js @@ -22,12 +22,14 @@ const TYPE_TO_DEGREE = { bookmark: 4, feed: 5, pad: 6, + room: 6, chat: 7, audio: 8, video: 9, image: 10, document: 11, torrent: 0, + file: 0, map: 2, pixelia: 3, gameScore: 5, @@ -39,7 +41,35 @@ const TYPE_TO_DEGREE = { courts: 0, market: 1, aiExchange: 6, - tombstone: 4 + tombstone: 4, + wikiPage: 2, + emergency: 11, + emergencyConfirm: 11, + emergencyUpdate: 11, + mailingList: 5, + logisticsRoute: 3, + logisticsRating: 3, + podcast: 8, + podcastEpisode: 8, + podcastPlay: 8, + campaign: 9, + campaignSignature: 9, + campaignUpdate: 9 +}; + +const FAMILY_DEGREE = [ + ['tribeParliament', 11], ['parliament', 11], ['courts', 0], ['school', 1], ['housing', 4], ['industry', 6], + ['poll', 2], ['blog', 0], ['larp', 9], ['chat', 7], ['pad', 6], ['room', 6], ['wiki', 2], ['emergency', 11], + ['mailing', 5], ['logistics', 3], ['podcast', 8], ['campaign', 9], ['project', 6], ['market', 1], ['shop', 1], + ['job', 3], ['event', 8], ['task', 10], ['report', 10], ['votes', 7], ['calendar', 8], ['map', 2], + ['transfer', 11], ['bank', 11], ['ubi', 11], ['feed', 5], ['tribe', 9], ['forum', 7], ['bookmark', 4], + ['audio', 8], ['video', 9], ['image', 10], ['document', 11], ['torrent', 0], ['file', 0] +]; + +const degreeOf = (type) => { + if (TYPE_TO_DEGREE[type] != null) return TYPE_TO_DEGREE[type]; + const family = FAMILY_DEGREE.find(([prefix]) => type.startsWith(prefix)); + return family ? family[1] : (Math.abs(hashStr(type)) % 12); }; const NOTE_FREQS = (() => { @@ -59,7 +89,7 @@ function blockToNote(msg) { const c = msg && msg.value && msg.value.content; if (!c || typeof c !== 'object') return null; const type = String(c.type || '').trim() || 'unknown'; - const degree = TYPE_TO_DEGREE[type] != null ? TYPE_TO_DEGREE[type] : (Math.abs(hashStr(type)) % 12); + const degree = degreeOf(type); const size = Buffer.byteLength(JSON.stringify(msg.value), 'utf8'); const octIdx = size < 256 ? 0 : (size < 1024 ? 1 : 2); const octave = OCTAVES[octIdx]; diff --git a/src/models/mentions_model.js b/src/models/mentions_model.js index 3f1054b..85a9160 100644 --- a/src/models/mentions_model.js +++ b/src/models/mentions_model.js @@ -1,10 +1,11 @@ const fs = require('fs'); const pull = require('../server/node_modules/pull-stream'); +const { readTyped, CONTENT_TYPES } = require('./typed_log'); const SEEN_FILE = 'oasis-mentions-seen'; const seenPath = () => { - try { return require('../server/ssb_config').statePath(SEEN_FILE); } catch (_) { return null; } + try { return require('../configs/state-manager').statePath(SEEN_FILE); } catch (_) { return null; } }; const MAX_SEEN_KEYS = 500; @@ -32,6 +33,7 @@ const writeSeen = (keys, ts) => { }; const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const TEXT_FIELDS = ['text', 'description', 'title', 'concept', 'question', 'rules', 'message', 'body', 'subject']; @@ -45,13 +47,7 @@ module.exports = ({ cooler }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const getAllMessages = async (ssbClient) => readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }); const mentionsList = (content, feedId) => { const m = content && content.mentions; @@ -60,11 +56,24 @@ module.exports = ({ cooler }) => { return values.some(v => v && (v.link === feedId || v === feedId)); }; - const mentionsText = (content, feedId) => { + const escapeRe = (s) => String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const nameRes = (names) => names.map(n => new RegExp(`(^|[^\\w\\u0080-\\uFFFF\\[\\]/@])@${escapeRe(n)}(?=$|[^\\w\\u0080-\\uFFFF/+=])`, 'i')); + const myNamesOf = (messages, feedId) => { + let latest = null; + for (const m of messages) { + const v = m && m.value; + const c = v && v.content; + if (!c || c.type !== 'about' || String(v.author) !== String(feedId) || c.about !== feedId || typeof c.name !== 'string' || !c.name.trim()) continue; + const ts = Number(v.timestamp || m.timestamp || 0); + if (!latest || ts >= latest.ts) latest = { ts, name: c.name.trim() }; + } + return latest ? [latest.name] : []; + }; + const mentionsText = (content, feedId, byName = []) => { const bare = feedId.startsWith('@') ? feedId.slice(1) : feedId; for (const field of TEXT_FIELDS) { const v = content[field]; - if (typeof v === 'string' && (v.includes(feedId) || v.includes(bare))) return true; + if (typeof v === 'string' && (v.includes(feedId) || v.includes(bare) || byName.some(re => re.test(v)))) return true; } if (Array.isArray(content.tags)) { if (content.tags.some(t => typeof t === 'string' && (t.includes(feedId) || t.includes(bare)))) return true; @@ -80,6 +89,8 @@ module.exports = ({ cooler }) => { return ''; }; + const scanCache = { fingerprint: '', fresh: null }; + const titleOf = (content) => { for (const field of ['title', 'concept', 'question', 'subject']) { const v = content[field]; @@ -93,11 +104,17 @@ module.exports = ({ cooler }) => { const ssbClient = await openSsb(); const myFeedId = ssbClient.id; const messages = await getAllMessages(ssbClient); + const last = messages.length ? messages[messages.length - 1] : null; + const fingerprint = `${myFeedId}|${messages.length}|${last && last.key}`; + const fresh = (scanCache.fingerprint === fingerprint && scanCache.fresh) + ? scanCache.fresh + : (() => { const tomb = buildValidatedTombstoneSet(messages); + const vg = versionGroups(messages.filter(m => { const c = m && m.value && m.value.content; return c && typeof c === 'object' && c.type && !SKIP_TYPES.has(c.type); }).map(versionEntry)); const out = []; const seen = new Set(); - const replaced = new Set(); + const byName = nameRes(myNamesOf(messages, myFeedId)); for (const m of messages) { const v = m && m.value; const c = v && v.content; @@ -108,8 +125,8 @@ module.exports = ({ cooler }) => { if (Array.isArray(c.recps) || Array.isArray(v.content && v.content.recps)) continue; if (c.tribeId && c.type !== 'tribe') continue; if (String(v.author) === String(myFeedId)) continue; - if (typeof c.replaces === 'string') replaced.add(c.replaces); - if (!mentionsList(c, myFeedId) && !mentionsText(c, myFeedId)) continue; + if (vg.tipOf(m.key) !== m.key || !vg.roots.has(vg.rootOf(m.key))) continue; + if (!mentionsList(c, myFeedId) && !mentionsText(c, myFeedId, byName)) continue; if (seen.has(m.key)) continue; seen.add(m.key); out.push({ @@ -125,7 +142,10 @@ module.exports = ({ cooler }) => { }); } - const fresh = out.filter(x => !replaced.has(x.key)); + scanCache.fingerprint = fingerprint; + scanCache.fresh = out; + return out; + })(); const q = String(opts.q || '').trim().toLowerCase(); let list = q ? fresh.filter(x => x.text.toLowerCase().includes(q) || x.title.toLowerCase().includes(q) || x.type.toLowerCase().includes(q)) @@ -140,6 +160,28 @@ module.exports = ({ cooler }) => { seenAt: () => readSeen().ts, + readKeys: () => new Set(readSeen().keys.map(String)), + + markRead(key) { + if (!key) return false; + const seen = readSeen(); + const keys = [String(key)].concat(seen.keys.filter(k => String(k) !== String(key))); + return writeSeen(keys, seen.ts || Date.now()); + }, + + markReadMany(list) { + const seen = readSeen(); + const add = (Array.isArray(list) ? list : []).filter(Boolean).map(String); + const rest = seen.keys.filter(k => !add.includes(String(k))); + return writeSeen(add.concat(rest), seen.ts || Date.now()); + }, + + markUnread(key) { + if (!key) return false; + const seen = readSeen(); + return writeSeen(seen.keys.filter(k => String(k) !== String(key)), seen.ts); + }, + markSeen(list) { const items = Array.isArray(list) ? list : []; const keys = items.map(x => x && x.key).filter(Boolean); diff --git a/src/models/nextcloud_model.js b/src/models/nextcloud_model.js new file mode 100644 index 0000000..38a7b97 --- /dev/null +++ b/src/models/nextcloud_model.js @@ -0,0 +1,151 @@ +const fs = require('fs'); +const path = require('path'); +const { statePath } = require('../configs/state-manager'); + +const ACCOUNT_PATH = statePath('nextcloud-account.json'); +const CHUNK_BYTES = 10 * 1024 * 1024; +const REQUEST_TIMEOUT_MS = 15000; +const CHUNK_TIMEOUT_MS = 10 * 60 * 1000; +const DEFAULT_FOLDER = 'Oasis'; + +const readAccount = () => { + try { + const a = JSON.parse(fs.readFileSync(ACCOUNT_PATH, 'utf8')); + return a && a.server && a.user && a.password ? a : null; + } catch (_) { return null; } +}; +const writeAccount = (a) => { + fs.mkdirSync(path.dirname(ACCOUNT_PATH), { recursive: true }); + fs.writeFileSync(ACCOUNT_PATH, JSON.stringify(a), { mode: 0o600 }); +}; +const normalizeServer = (v) => { + let s = String(v || '').trim(); + if (!s) return ''; + if (!/^https?:\/\//i.test(s)) s = `https://${s}`; + try { + const u = new URL(s); + if (!/^https?:$/.test(u.protocol)) return ''; + return `${u.origin}${u.pathname.replace(/\/+$/, '')}`; + } catch (_) { return ''; } +}; +const cleanFolder = (v) => String(v || '').trim().replace(/^\/+|\/+$/g, '').split('/').map(s => s.trim()).filter(Boolean).join('/') || DEFAULT_FOLDER; +const encPath = (p) => p.split('/').map(encodeURIComponent).join('/'); +const safeName = (v) => String(v || '').replace(/[\\/:*?"<>|\r\n]+/g, ' ').trim().slice(0, 120) || 'file'; +const pad = (n) => String(n).padStart(15, '0'); +const readRange = async (file, start, end) => { + const fh = await fs.promises.open(file, 'r'); + try { + const buf = Buffer.alloc(end - start); + let got = 0; + while (got < buf.length) { + const { bytesRead } = await fh.read(buf, got, buf.length - got, start + got); + if (!bytesRead) break; + got += bytesRead; + } + return got === buf.length ? buf : buf.subarray(0, got); + } finally { await fh.close(); } +}; + +module.exports = ({ isPublic = false } = {}) => { + const auth = (a) => 'Basic ' + Buffer.from(`${a.user}:${a.password}`).toString('base64'); + const call = async (a, method, url, { headers = {}, body, timeout = REQUEST_TIMEOUT_MS } = {}) => { + const ctl = new AbortController(); + const t = setTimeout(() => ctl.abort(), timeout); + try { + return await fetch(url, { method, headers: { Authorization: auth(a), 'OCS-APIRequest': 'true', ...headers }, body, signal: ctl.signal, redirect: 'manual' }); + } finally { clearTimeout(t); } + }; + const davFiles = (a) => `${a.server}/remote.php/dav/files/${encodeURIComponent(a.user)}`; + const davUploads = (a) => `${a.server}/remote.php/dav/uploads/${encodeURIComponent(a.user)}`; + const targetUrl = (a, name) => `${davFiles(a)}/${encPath(a.folder)}/${encodeURIComponent(name)}`; + const whoami = async (a) => { + let res; + try { res = await call(a, 'GET', `${a.server}/ocs/v1.php/cloud/user?format=json`); } catch (_) { throw new Error('nextcloudErrConnect'); } + if (!res.ok) throw new Error('nextcloudErrConnect'); + let data = null; + try { data = await res.json(); } catch (_) {} + const d = data && data.ocs && data.ocs.data; + if (!d || !d.id) throw new Error('nextcloudErrConnect'); + return { id: String(d.id), displayName: String(d['display-name'] || d.displayname || d.id) }; + }; + const mkcol = async (a, url) => { + const res = await call(a, 'MKCOL', url); + if (!(res.ok || res.status === 405)) throw new Error('nextcloudErrUpload'); + }; + const ensureFolder = async (a) => { + let cur = davFiles(a); + for (const seg of a.folder.split('/')) { cur += `/${encodeURIComponent(seg)}`; await mkcol(a, cur); } + }; + const exists = async (a, url) => { + const res = await call(a, 'PROPFIND', url, { headers: { Depth: '0' } }); + return res.status === 207 || res.status === 200; + }; + const freeName = async (a, name) => { + if (!(await exists(a, targetUrl(a, name)))) return name; + const dot = name.lastIndexOf('.'); + const stamp = Date.now().toString(36); + return dot > 0 ? `${name.slice(0, dot)}-${stamp}${name.slice(dot)}` : `${name}-${stamp}`; + }; + const putWhole = async (a, filePath, size, name) => { + const res = await call(a, 'PUT', targetUrl(a, name), { body: await readRange(filePath, 0, size), headers: { 'Content-Type': 'application/octet-stream' }, timeout: CHUNK_TIMEOUT_MS }); + if (!res.ok) throw new Error('nextcloudErrUpload'); + }; + const putChunked = async (a, filePath, size, name) => { + const base = `${davUploads(a)}/oasis-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + await mkcol(a, base); + for (let start = 0; start < size; start += CHUNK_BYTES) { + const end = Math.min(size, start + CHUNK_BYTES); + const res = await call(a, 'PUT', `${base}/${pad(start)}-${pad(end - 1)}`, { body: await readRange(filePath, start, end), headers: { 'Content-Type': 'application/octet-stream' }, timeout: CHUNK_TIMEOUT_MS }); + if (!res.ok) throw new Error('nextcloudErrUpload'); + } + const res = await call(a, 'MOVE', `${base}/.file`, { headers: { Destination: targetUrl(a, name), 'OC-Total-Length': String(size) }, timeout: CHUNK_TIMEOUT_MS }); + if (!res.ok) throw new Error('nextcloudErrUpload'); + }; + const share = async (a, name) => { + const body = new URLSearchParams({ path: `/${a.folder}/${name}`, shareType: '3' }); + const res = await call(a, 'POST', `${a.server}/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json`, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() }); + if (!res.ok) throw new Error('nextcloudErrShare'); + let data = null; + try { data = await res.json(); } catch (_) {} + const url = data && data.ocs && data.ocs.data && data.ocs.data.url; + if (!url || !/^https?:\/\//i.test(String(url))) throw new Error('nextcloudErrShare'); + return String(url); + }; + + return { + getAccount() { + const a = readAccount(); + return a ? { server: a.server, user: a.user, folder: a.folder, displayName: a.displayName || a.user } : null; + }, + + async connect({ server, user, password, folder } = {}) { + if (isPublic) throw new Error('nextcloudErrConnect'); + const a = { server: normalizeServer(server), user: String(user || '').trim(), password: String(password || ''), folder: cleanFolder(folder) }; + if (!a.server || !a.user || !a.password) throw new Error('nextcloudErrConnect'); + const me = await whoami(a); + a.displayName = me.displayName; + await ensureFolder(a); + writeAccount(a); + return this.getAccount(); + }, + + disconnect() { + try { fs.unlinkSync(ACCOUNT_PATH); } catch (_) {} + }, + + async upload({ filePath, size, name } = {}) { + if (isPublic) throw new Error('nextcloudErrUpload'); + const a = readAccount(); + if (!a) throw new Error('nextcloudErrConnect'); + const bytes = Number(size) || 0; + if (!filePath || !(bytes > 0)) throw new Error('nextcloudErrUpload'); + const final = await freeName(a, safeName(name)); + if (bytes <= CHUNK_BYTES) await putWhole(a, filePath, bytes, final); + else await putChunked(a, filePath, bytes, final); + const url = await share(a, final); + return { url, name: final, size: bytes }; + } + }; +}; + +module.exports.CHUNK_BYTES = CHUNK_BYTES; diff --git a/src/models/onboarding_model.js b/src/models/onboarding_model.js index 8520a69..eac7b74 100644 --- a/src/models/onboarding_model.js +++ b/src/models/onboarding_model.js @@ -2,7 +2,8 @@ const fs = require('fs'); const path = require('path'); const pull = require('../server/node_modules/pull-stream'); -const STEPS = ['language', 'ux', 'profile', 'federation', 'larp', 'greeting', 'backup']; +const STEPS = ['profile', 'ux', 'workflow', 'theme', 'community', 'greeting']; +const MARKED_STEPS = ['profile', 'ux', 'workflow', 'theme', 'community']; const FLAG = 'oasis-first-contact'; const PENDING = 'welcome=pending'; const DISMISSED = 'welcome=dismissed'; @@ -13,7 +14,13 @@ const ssbPathOf = (given) => { try { return require('../server/ssb_config').path; } catch (_) { return null; } }; -const flagPath = (dir) => path.join(dir, FLAG); +const flagPath = (dir) => { + try { + const state = require('../configs/state-manager'); + if (!dir || path.resolve(dir) === state.ssbDir()) return state.statePath(FLAG); + } catch (_) {} + return path.join(dir, FLAG); +}; function readFlag(dir) { if (!dir) return { exists: false, id: '', markers: new Set() }; @@ -49,6 +56,33 @@ const isPending = (dir) => { const bannerVisible = (ssbPath) => isPending(ssbPathOf(ssbPath)); +const DEFAULT_LANGUAGE = 'en'; + +const browserLanguage = (header, supported) => { + const list = Array.isArray(supported) ? supported : []; + const wanted = String(header || '').slice(0, 1024).split(',').map((part, i) => { + const [tag, ...params] = part.trim().split(';'); + const qParam = params.map(p => p.trim()).find(p => /^q=/i.test(p)); + const q = qParam ? Number(qParam.slice(2)) : 1; + return { tag: String(tag || '').trim().toLowerCase().replace(/_/g, '-'), q: Number.isFinite(q) ? q : 0, i }; + }).filter(w => w.tag && w.tag !== '*' && w.q > 0).sort((a, b) => b.q - a.q || a.i - b.i); + for (const w of wanted) { + if (list.includes(w.tag)) return w.tag; + const base = w.tag.split('-')[0]; + if (list.includes(base)) return base; + } + return ''; +}; + +const firstRunLanguage = ({ fresh, isPublic, configured, cookie, header, supported } = {}) => { + const list = Array.isArray(supported) ? supported : []; + if (!fresh || isPublic) return { settled: true, language: '' }; + if (list.includes(configured) && configured !== DEFAULT_LANGUAGE) return { settled: true, language: '' }; + const picked = (list.includes(cookie) ? cookie : '') || browserLanguage(header, list); + if (!picked && !String(header || '').trim()) return { settled: false, language: '' }; + return { settled: true, language: picked }; +}; + module.exports = ({ cooler, ssbPath } = {}) => { const dir = () => ssbPathOf(ssbPath); @@ -56,7 +90,7 @@ module.exports = ({ cooler, ssbPath } = {}) => { const ssb = await cooler.open(); const mine = ssb.id; const all = await new Promise((resolve, reject) => - pull(ssb.createLogStream({ reverse: true, limit: 2000 }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs || []))) + pull(ssb.createUserStream({ id: mine, reverse: true, limit: 2000 }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs || []))) ); return { mine, messages: all.filter(m => m && m.value && m.value.author === mine) }; }; @@ -72,16 +106,6 @@ module.exports = ({ cooler, ssbPath } = {}) => { return c && typeof c === 'object' && c.type === 'feed' && String(c.text || '').trim().length > 0; }); - const hasJoinedLarp = (messages) => { - for (const m of messages) { - const c = m.value.content; - if (!c || typeof c !== 'object') continue; - if (c.type === 'larpJoinHouse' && c.house) return true; - if (c.type === 'larpLeaveLarp') return false; - } - return false; - }; - const hasFederated = (mine, messages) => { try { const gossip = JSON.parse(fs.readFileSync(path.join(dir(), 'gossip.json'), 'utf8')); @@ -120,9 +144,20 @@ module.exports = ({ cooler, ssbPath } = {}) => { } }, + adopt(feedId) { + const target = dir(); + if (!target || !feedId) return false; + try { + fs.mkdirSync(target, { recursive: true }); + fs.writeFileSync(flagPath(target), `${String(feedId)}\n${new Date().toISOString()}\n${DONE}\n`); + return true; + } catch (_) { + return false; + } + }, + markStep(step) { - if (!STEPS.includes(step)) return false; - if (step !== 'language' && step !== 'backup' && step !== 'ux') return true; + if (!MARKED_STEPS.includes(step)) return false; if (!isPending(dir())) return true; return appendMarker(dir(), `step=${step}`); }, @@ -143,12 +178,11 @@ module.exports = ({ cooler, ssbPath } = {}) => { let messages = []; try { ({ mine, messages } = await ownMessages()); } catch (_) { mine = ''; messages = []; } const steps = { - language: flag.markers.has('step=language'), - profile: hasProfile(mine, messages), - federation: hasFederated(mine, messages), - larp: hasJoinedLarp(messages), + profile: hasProfile(mine, messages) || flag.markers.has('step=profile'), ux: flag.markers.has('step=ux'), - backup: flag.markers.has('step=backup'), + workflow: flag.markers.has('step=workflow'), + theme: flag.markers.has('step=theme'), + community: hasFederated(mine, messages) || flag.markers.has('step=community'), greeting: hasGreeted(messages) }; const profile = { id: mine, name: '', description: '', image: '' }; @@ -180,3 +214,5 @@ module.exports = ({ cooler, ssbPath } = {}) => { module.exports.bannerVisible = bannerVisible; module.exports.STEPS = STEPS; +module.exports.browserLanguage = browserLanguage; +module.exports.firstRunLanguage = firstRunLanguage; diff --git a/src/models/opinions_model.js b/src/models/opinions_model.js index 1df58e1..a4a801a 100644 --- a/src/models/opinions_model.js +++ b/src/models/opinions_model.js @@ -2,7 +2,9 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const categories = require('../backend/opinion_categories'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, CONTENT_TYPES, requestScope } = require('./typed_log'); const { buildVoteTally } = require('../backend/vote_tally'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; module.exports = ({ cooler }) => { @@ -22,8 +24,9 @@ module.exports = ({ cooler }) => { const validTypes = [ 'bookmark', 'votes', 'transfer', - 'feed', 'image', 'audio', 'video', 'document', 'torrent', - 'industry', 'project', 'report', 'task', 'event', 'shopProduct', 'housing', 'market' + 'feed', 'image', 'audio', 'video', 'document', 'torrent', 'file', + 'industry', 'project', 'report', 'task', 'event', 'shopProduct', 'housing', 'market', 'schoolCourse', + 'podcast', 'podcastEpisode', 'campaign', 'logisticsRoute' ]; const getPreview = c => { @@ -31,25 +34,7 @@ module.exports = ({ cooler }) => { return c.text || c.description || c.title || c.name || ''; }; - const OPINION_MSG_TYPE = { - bookmark: 'bookmarkOpinion', - votes: 'votesOpinion', - transfer: 'transferOpinion', - feed: 'feedOpinion', - image: 'imageOpinion', - audio: 'audioOpinion', - video: 'videoOpinion', - document: 'documentOpinion', - torrent: 'torrentOpinion', - industry: 'industryOpinion', - project: 'projectOpinion', - report: 'reportOpinion', - task: 'taskOpinion', - event: 'eventOpinion', - shopProduct: 'shopOpinion', - housing: 'housingOpinion', - market: 'marketOpinion' - }; + const OPINION_MSG_TYPE = categories.messageTypes; const createVote = async (contentId, category) => { const ssbClient = await openSsb(); @@ -69,36 +54,23 @@ module.exports = ({ cooler }) => { ); }; - const listOpinions = async (filter = 'ALL', category = '') => { + const computeBase = async () => { const ssbClient = await openSsb(); - const userId = ssbClient.id; - const messages = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); + const messages = await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }); const tombstoned = buildValidatedTombstoneSet(messages); - const replaces = new Map(); const byId = new Map(); - const authorOf = new Map(); const opinionMsgs = []; for (const msg of messages) { const key = msg.key; const c = msg.value?.content; if (!c) continue; - if (c.type === 'tombstone') { - if (tombstoned.has(c.target)) byId.delete(c.target); - continue; - } - if (key) authorOf.set(key, msg.value.author); + if (c.type === 'tombstone') continue; if (typeof c.type === 'string' && c.type.endsWith('Opinion') && c.target) { opinionMsgs.push({ target: c.target, author: msg.value.author, category: c.category }); continue; } - if (c.opinions && !tombstoned.has(key) && !['task', 'event', 'report'].includes(c.type)) { - if (c.replaces) replaces.set(c.replaces, key); + if (c.opinions && !['task', 'event', 'report'].includes(c.type)) { byId.set(key, { key, value: { @@ -108,38 +80,19 @@ module.exports = ({ cooler }) => { } }); } - if (c.type === 'feed' && !tombstoned.has(key) && !byId.has(key)) { - if (c.replaces) replaces.set(c.replaces, key); + if (c.type === 'feed' && !byId.has(key)) { byId.set(key, { key, value: { ...msg.value, content: c, preview: getPreview(c) } }); } } - for (const [oldId, newId] of Array.from(replaces.entries())) { - const oldAuthor = authorOf.get(oldId); - const newM = byId.get(newId); - if (oldAuthor === undefined) { replaces.delete(oldId); continue; } - if (!newM || String(newM.value.author) !== String(oldAuthor)) { - replaces.delete(oldId); - byId.delete(newId); - } + const vg = versionGroups(Array.from(byId.values()).map(versionEntry)); + for (const key of Array.from(byId.keys())) { + if (!vg.roots.has(vg.rootOf(key)) || vg.tipOf(key) !== key || tombstoned.has(key)) byId.delete(key); } - for (const replacedId of replaces.keys()) { - byId.delete(replacedId); - } - - const tipToChain = new Map(); - for (const [oldId, newId] of replaces.entries()) { - let tip = newId; let g = 0; - while (replaces.has(tip) && g++ < 100000) tip = replaces.get(tip); - if (!tipToChain.has(tip)) tipToChain.set(tip, new Set([tip])); - tipToChain.get(tip).add(oldId); tipToChain.get(tip).add(newId); - } - const idToTip = new Map(); - for (const [tip, chain] of tipToChain.entries()) for (const id of chain) idToTip.set(id, tip); const opinionByTip = new Map(); for (const op of opinionMsgs) { - const tip = idToTip.get(op.target) || op.target; + const tip = vg.tipOf(op.target); if (!opinionByTip.has(tip)) opinionByTip.set(tip, { opinions: {}, voters: new Set() }); const agg = opinionByTip.get(tip); if (agg.voters.has(op.author)) continue; @@ -158,6 +111,12 @@ module.exports = ({ cooler }) => { } let filtered = Array.from(byId.values()).filter(m => validTypes.includes(m.value?.content?.type)); + filtered = filtered.filter(m => { + const c = m.value?.content; + if (c?.type !== 'schoolCourse') return true; + if (c.encryptedPayload) return false; + return String(c.visibility || 'PUBLIC').toUpperCase() !== 'INVITE'; + }); const blobTypes = ['document', 'image', 'audio', 'video']; const blobCheckCache = new Map(); @@ -237,10 +196,24 @@ module.exports = ({ cooler }) => { } filtered = Array.from(bySig.values()); - filtered = filtered.filter(m => { + return filtered.filter(m => { const ops = m.value?.content?.opinions || {}; return Object.values(ops).reduce((acc, x) => acc + (Number(x) || 0), 0) > 0; }); + }; + + const baseItems = () => { + const store = requestScope.getStore(); + if (store && store.opinionsBase) return store.opinionsBase; + const pending = computeBase(); + if (store) store.opinionsBase = pending; + return pending; + }; + + const listOpinions = async (filter = 'ALL', category = '') => { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + let filtered = (await baseItems()).slice(); if (filter === 'MINE') { filtered = filtered.filter(m => m.value.author === userId); diff --git a/src/models/pads_model.js b/src/models/pads_model.js index f6a42a8..3a9c310 100644 --- a/src/models/pads_model.js +++ b/src/models/pads_model.js @@ -3,6 +3,7 @@ const crypto = require("crypto") const fs = require("fs") const { buildValidatedTombstoneSet } = require('./tombstone_validator') const { collabContent, openInviteOf } = require('../backend/collab_content') +const { readTyped, memoIndex } = require('./typed_log') const padCollab = collabContent({ membersField: 'members', undecField: 'undecryptable', contentFields: ['title', 'deadline', 'status'], listFields: ['tags', 'invites'] }) const path = require("path") const { getConfig } = require("../configs/config-manager.js") @@ -59,6 +60,18 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) if (ownCrypto) return lookupKey(rootId) try { return JSON.parse(fs.readFileSync(getLegacyKeyringPath(), "utf8"))[rootId] || null } catch (_) { return null } } + const hashCode = (code, salt) => (ownCrypto && typeof ownCrypto.hashInviteCode === "function") ? ownCrypto.hashInviteCode(code, salt) : (tribeCrypto ? tribeCrypto.hashInviteCode(code, salt) : null) + const inviteMatches = (inv, token) => { + if (!token) return false + if (typeof inv === "string") return inv === token + if (!inv || typeof inv !== "object") return false + if (typeof inv.code === "string" && inv.code === token) return true + if (typeof inv.ch === "string" && (inv.ch === token || hashCode(token, inv.salt) === inv.ch)) return true + return false + } + const inviteToken = (inv) => (inv && typeof inv === "object" && typeof inv.ch === "string") ? inv.ch : (typeof inv === "string" ? inv : (inv && inv.code) || "") + const keyProofFor = (keyHex, id) => { try { return crypto.createHmac("sha256", Buffer.from(keyHex, "hex")).update(String(id), "utf8").digest("hex") } catch (_) { return null } } + const padKeys = (rootId) => (ownCrypto && typeof ownCrypto.getKeys === "function" ? ownCrypto.getKeys(rootId) : []).concat(tribeCrypto && typeof tribeCrypto.getKeys === "function" ? tribeCrypto.getKeys(rootId) : []) const setPadKey = (rootId, keyHex) => { migrateLegacyKeyring() if (ownCrypto) { ownCrypto.setKey(rootId, keyHex, 1); return } @@ -117,29 +130,37 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) let tagsRaw = "" try { deadline = c.deadline ? tryDecryptField(c.deadline, k) : "" } catch (_) {} try { tagsRaw = c.tags ? tryDecryptField(c.tags, k) : "" } catch (_) {} - const ensureMemberKeys = async (ssbClient, messages, items) => { - if (!tribeCrypto) return - const distributed = new Map() + return { title: safeText(title), deadline, tags: normalizeTags(tagsRaw) } + } catch (_) {} + } + return null + } + + const ensureMemberKeys = async (ssbClient, messages, items) => { + if (!tribeCrypto || !ownCrypto) return + const leavesByRoot = new Map() for (const m of messages) { const c = m.value && m.value.content - if (!c || c.type !== "tribe-keys" || !c.tribeId) continue - const mk = c.memberKeys - if (!mk || typeof mk !== "object") continue - if (!distributed.has(c.tribeId)) distributed.set(c.tribeId, new Set()) - for (const id of Object.keys(mk)) distributed.get(c.tribeId).add(id) + if (!c || c.type !== "padMember" || c.on !== false || !c.target || !c.member) continue + if (!leavesByRoot.has(c.target)) leavesByRoot.set(c.target, []) + leavesByRoot.get(c.target).push({ member: c.member, author: m.value.author, ts: Number(m.timestamp || m.value.timestamp || 0) }) } const ssbKeys = require("../server/node_modules/ssb-keys") for (const item of (Array.isArray(items) ? items : [])) { - if (!item || item.undecryptable) continue + if (!item || item.undecryptable || item.author !== ssbClient.id) continue const rootId = item.rootId - if (!rootId) continue + if (!rootId || !lookupKey(rootId)) continue + const members = Array.isArray(item.members) ? item.members : [] + const leaves = (leavesByRoot.get(rootId) || []).filter(l => l.author === l.member || l.author === item.author) + const plan = ownCrypto.keyPlan({ rootId, ownerId: item.author, gen: lookupGen(rootId), members, messages, leaves }) + if (plan.rotate) { + await rotatePadKey(rootId, members) + continue + } + if (!plan.missing.length) continue const key = lookupKey(rootId) - if (!key) continue - const have = distributed.get(rootId) || new Set() - const missing = (Array.isArray(item.members) ? item.members : []).filter(m => m && m !== ssbClient.id && !have.has(m)) - if (!missing.length) continue const memberKeys = {} - for (const m of missing) { + for (const m of plan.missing) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(key, m, ssbKeys) } catch (_) {} } if (!Object.keys(memberKeys).length) continue @@ -149,12 +170,6 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) } } - return { title: safeText(title), deadline, tags: normalizeTags(tagsRaw) } - } catch (_) {} - } - return null - } - const encryptForInvite = (padKeyHex, code, saltHex) => { const salt = saltHex ? Buffer.from(saltHex, "hex") : Buffer.from(INVITE_SALT) const derived = crypto.scryptSync(code, salt, 32) @@ -189,11 +204,10 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) if (!existing) return const newKey = crypto.randomBytes(32).toString("hex") const newGen = ownCrypto.addNewKey(rootId, newKey) - if (!Array.isArray(remainingMembers) || !remainingMembers.length) return const ssbClient = await openSsb() const ssbKeys = require("../server/node_modules/ssb-keys") const memberKeys = {} - for (const m of remainingMembers) { + for (const m of new Set([ssbClient.id, ...(Array.isArray(remainingMembers) ? remainingMembers : [])])) { try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys) } catch (_) {} } if (Object.keys(memberKeys).length) { @@ -210,9 +224,12 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const ssbKeys = require("../server/node_modules/ssb-keys") const config = require("../server/ssb_config") const msgs = await readAll(ssbClient) + const idx = buildIndex(msgs) + const trusted = (root, author) => { const n = idx.nodes.get(root); return !!author && !!n && (author === n.author || author === ssbClient.id) } for (const m of msgs) { const c = m.value && m.value.content if (!c || c.type !== "tribe-keys") continue + if (!trusted(c.tribeId, m.value.author)) continue const memberKeys = c.memberKeys if (!memberKeys || typeof memberKeys !== "object") continue const boxed = memberKeys[ssbClient.id] @@ -226,18 +243,15 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) } catch (_) {} } - const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))) - ) + const PAD_TYPES = ["pad", "padMember", "padEntry", "tribe-keys", "tombstone"] + + const readAll = async (ssbClient) => readTyped(ssbClient, PAD_TYPES, { limit: logLimit }) const buildIndex = (messages) => { const tomb = new Set() const nodes = new Map() const parent = new Map() const child = new Map() - const strictParent = new Map() - const strictChild = new Map() const authorByKey = new Map() const tombRequests = [] const memberMsgs = [] @@ -248,19 +262,14 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const c = v.content if (!c) continue if (c.type === "tombstone" && c.target) { tombRequests.push({ target: c.target, author: v.author }); continue } - if (c.type === "padMember" && c.target) { memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0, code: typeof c.code === "string" ? c.code : "" }); continue } + if (c.type === "padMember" && c.target) { memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0, code: typeof c.code === "string" ? c.code : "", keyProof: typeof c.keyProof === "string" ? c.keyProof : "" }); continue } if (c.type === "pad") { - nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) authorByKey.set(k, v.author) if (c.replaces) { parent.set(k, c.replaces); child.set(c.replaces, k) } } } - for (const [k, node] of nodes) { - const t = node.c.replaces - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) { strictParent.set(k, t); strictChild.set(t, k) } } - } - for (const t of tombRequests) { const targetAuthor = authorByKey.get(t.target) if (targetAuthor && t.author === targetAuthor) tomb.add(t.target) @@ -268,53 +277,86 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const rootOf = (id) => { let cur = id, g = 0; while (parent.has(cur) && g++ < 100000) cur = parent.get(cur); return cur } const tipOf = (id) => { let cur = id, g = 0; while (child.has(cur) && g++ < 100000) cur = child.get(cur); return cur } - const strictRootOf = (id) => { let cur = id, g = 0; while (strictParent.has(cur) && g++ < 100000) cur = strictParent.get(cur); return cur } - const contentTipOf = (root) => { - const rn = nodes.get(root) - if (!rn) return root - let cur = root, best = root, g = 0 - const seen = new Set() - while (child.has(cur) && !seen.has(cur) && g++ < 100000) { - seen.add(cur) - const next = child.get(cur) - const n = nodes.get(next) - if (!n) break - if (n.author === rn.author && !tomb.has(next)) best = next - cur = next - } - return best + const rootMemo = new Map() + const groupRootOf = (id) => { + if (rootMemo.has(id)) return rootMemo.get(id) + let cur = id, g = 0 + while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur) + rootMemo.set(id, cur) + return cur } + const ownerTip = new Map() + for (const [k, n] of nodes) { + const r = groupRootOf(k) + const rn = nodes.get(r) + if (!rn || n.author !== rn.author) continue + const prev = ownerTip.has(r) ? nodes.get(ownerTip.get(r)) : null + if (!prev || n.seq > prev.seq || (n.seq === prev.seq && n.ts >= prev.ts)) ownerTip.set(r, k) + } + const contentTipOf = (root) => ownerTip.get(root) || root const roots = new Set() - for (const id of nodes.keys()) roots.add(strictRootOf(id)) + for (const id of nodes.keys()) roots.add(groupRootOf(id)) + + const invitesOf = (root) => { + const out = [] + for (const id of [root, contentTipOf(root)]) { const n = nodes.get(id); if (n && Array.isArray(n.c.invites)) out.push(...n.c.invites) } + return out + } + const selfJoinAllowed = (root, mm) => { + const oc = (nodes.get(root) || {}).c || {} + if (oc.encrypted !== true || oc.tribeId) return true + const invites = invitesOf(root) + if (invites.some(inv => inv && typeof inv === "object" && inv.public === true)) return true + if (mm.keyProof && padKeys(root).some(k => keyProofFor(k, mm.member) === mm.keyProof)) return true + return !!mm.code && invites.some(inv => typeof inv === "string" ? inv === mm.code : !!(inv && typeof inv === "object" && typeof inv.code === "string" && inv.code === mm.code)) + } const memberByRoot = new Map() const consumedByRoot = new Map() + const codeClaimsByRoot = new Map() for (const mm of memberMsgs) { if (!nodes.has(mm.target)) continue - const r = strictRootOf(mm.target) + const r = groupRootOf(mm.target) const ownerAuthor = nodes.get(r) && nodes.get(r).author if (!ownerAuthor) continue const self = mm.member === mm.author const byOwner = mm.author === ownerAuthor if (!self && !byOwner) continue if (!mm.member) continue - if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) - const m2 = memberByRoot.get(r) - const p = m2.get(mm.member) - if (!p || mm.ts >= p.ts) m2.set(mm.member, { on: mm.on, ts: mm.ts }) + if (mm.on && mm.code) { + if (!codeClaimsByRoot.has(r)) codeClaimsByRoot.set(r, []) + codeClaimsByRoot.get(r).push({ code: mm.code, member: mm.member, keyProof: mm.keyProof, byOwner }) + } + if (self && !byOwner && mm.on && !selfJoinAllowed(r, mm)) continue if (mm.on && mm.code) { if (!consumedByRoot.has(r)) consumedByRoot.set(r, new Set()) consumedByRoot.get(r).add(mm.code) } + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) + const m2 = memberByRoot.get(r) + const p = m2.get(mm.member) + if (!p || mm.ts >= p.ts) m2.set(mm.member, { on: mm.on, ts: mm.ts }) } + const codeClaims = (root, codes) => (codeClaimsByRoot.get(root) || []).filter(c => codes.includes(c.code)) + const isCodeConsumed = (root, code) => { if (!code) return false const set = consumedByRoot.get(root) return !!(set && set.has(code)) } + for (const [k, n] of nodes) { + const r = groupRootOf(k) + const rn = nodes.get(r) + if (!rn || n.author === rn.author || !Array.isArray(n.c.members) || !selfJoinAllowed(r, { member: n.author })) continue + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) + const m2 = memberByRoot.get(r) + const p = m2.get(n.author) + if (!p || n.ts >= p.ts) m2.set(n.author, { on: n.c.members.includes(n.author), ts: n.ts }) + } + const resolveMembers = (root) => { const ownerNode = nodes.get(root) const oc = ownerNode ? ownerNode.c : {} @@ -326,7 +368,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const tipByRoot = new Map() for (const r of roots) tipByRoot.set(r, contentTipOf(r)) - return { tomb, nodes, parent, child, rootOf, tipOf, strictRootOf, contentTipOf, tipByRoot, resolveMembers, isCodeConsumed } + return { tomb, nodes, parent, child, rootOf, tipOf, groupRootOf, contentTipOf, tipByRoot, resolveMembers, isCodeConsumed, codeClaims } } const decryptPadFields = (c, rootId, tribeKeys) => { @@ -358,7 +400,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) status: c.status || "OPEN", deadline, tags, - author: c.author || node.author, + author: node.author, members: Array.isArray(members) ? members : (Array.isArray(c.members) ? c.members : []), invites: Array.isArray(c.invites) ? c.invites : [], createdAt: c.createdAt || new Date(node.ts).toISOString(), @@ -423,7 +465,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildIndex(messages) - const root = idx.strictRootOf(id) + const root = idx.groupRootOf(id) const tip = idx.contentTipOf(root) if (idx.tomb.has(tip)) throw new Error("Not found") return root @@ -433,7 +475,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const ssbClient = await openSsb() const messages = await readAll(ssbClient) const idx = buildIndex(messages) - const root = idx.strictRootOf(id) + const root = idx.groupRootOf(id) const tip = idx.contentTipOf(root) if (idx.tomb.has(tip)) throw new Error("Not found") return tip @@ -518,7 +560,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) return new Promise(async (resolve, reject) => { ssbClient.get(tipId, async (err, item) => { if (err || !item?.content) return reject(new Error("Pad not found")) - if (item.content.author !== userId) return reject(new Error("Not the author")) + if (item.author !== userId) return reject(new Error("Not the author")) const c = item.content const isEncrypted = c.encrypted === true let keyHex = null @@ -564,7 +606,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) return new Promise(async (resolve, reject) => { ssbClient.get(tipId, async (err, item) => { if (err || !item?.content) return reject(new Error("Pad not found")) - if (item.content.author !== userId) return reject(new Error("Not the author")) + if (item.author !== userId) return reject(new Error("Not the author")) const c = item.content let keyHex = null let usesTribeKey = false @@ -599,13 +641,11 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) if (!pad) throw new Error("Pad not found") if (pad.author === userId) throw new Error("Author cannot leave their own pad") if (!Array.isArray(pad.members) || !pad.members.includes(userId)) return - const members = pad.members.filter(m => m !== userId) const rootId = await this.resolveRootId(padId) await new Promise((resolve, reject) => { const content = { type: "padMember", target: rootId, member: userId, on: false, createdAt: new Date().toISOString() } ssbClient.publish(content, (e) => e ? reject(e) : resolve()) }) - try { await rotatePadKey(rootId, members) } catch (_) {} }, async ingestKeys() { await ingestOwnTribeKeys() }, @@ -640,7 +680,8 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const key = tryDecryptPublicInviteKey(c.invites) if (key) setPadKey(rootId, key) } - const content = { type: "padMember", target: rootId, member: feedId, on: true, createdAt: new Date().toISOString(), ...(typeof consumedCode === "string" && consumedCode ? { code: consumedCode } : {}) } + const proofKey = feedId === ssbClient.id && c.encrypted === true && !c.tribeId ? getPadKey(rootId) : null + const content = { type: "padMember", target: rootId, member: feedId, on: true, createdAt: new Date().toISOString(), ...(typeof consumedCode === "string" && consumedCode ? { code: consumedCode } : {}), ...(proofKey ? { keyProof: keyProofFor(proofKey, feedId) } : {}) } ssbClient.publish(content, (e, res) => e ? reject(e) : resolve(res)) }) }) @@ -653,7 +694,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) return new Promise((resolve, reject) => { ssbClient.get(tipId, (err, item) => { if (err || !item?.content) return reject(new Error("Pad not found")) - if (item.content.author !== userId) return reject(new Error("Not the author")) + if (item.author !== userId) return reject(new Error("Not the author")) const tombstone = { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId } ssbClient.publish(tombstone, (e) => e ? reject(e) : resolve()) }) @@ -663,8 +704,8 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) async getPadById(id) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const idx = buildIndex(messages) - const root = idx.strictRootOf(id) + const idx = memoIndex('pads', messages, buildIndex) + const root = idx.groupRootOf(id) const tip = idx.contentTipOf(root) if (idx.tomb.has(tip)) return null const node = idx.nodes.get(tip) @@ -703,12 +744,13 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) const code = crypto.randomBytes(INVITE_BYTES).toString("hex") let invite = code const pubFlag = opts.public ? { public: true } : {} + const inviteSalt = generateInviteSalt() if (keyHex) { - const inviteSalt = generateInviteSalt() const ek = encryptForInvite(keyHex, code, inviteSalt) - invite = { code, ek, salt: inviteSalt, ...pubFlag } + invite = opts.public ? { code, ek, salt: inviteSalt, ...pubFlag } : { ch: hashCode(code, inviteSalt), ek, salt: inviteSalt } + } else { + invite = opts.public ? { code, public: true } : { ch: hashCode(code, inviteSalt), salt: inviteSalt } } - if (opts.public && typeof invite !== "object") invite = { code, public: true } const invites = [...pad.invites, invite] await this.updatePadById(padId, { invites }) return code @@ -734,18 +776,18 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) if (pad.author !== userId) throw new Error("Only the author can remove invites") const invites = (Array.isArray(pad.invites) ? pad.invites : []).filter(inv => !(typeof inv === "object" && inv.public === true)) await this.updatePadById(padId, { invites }) + if (!pad.tribeId) await rotatePadKey(await this.resolveRootId(padId), pad.members) }, async joinByInvite(code) { const ssbClient = await openSsb() const userId = ssbClient.id - const pads = await this.listAll() + const pads = await collectPads(buildIndex(await readAll(ssbClient))) let matchedPad = null let matchedInvite = null for (const p of pads) { for (const inv of p.invites) { - if (typeof inv === "string" && inv === code) { matchedPad = p; matchedInvite = inv; break } - if (typeof inv === "object" && inv.code === code) { matchedPad = p; matchedInvite = inv; break } + if (inviteMatches(inv, code)) { matchedPad = p; matchedInvite = inv; break } } if (matchedPad) break } @@ -753,17 +795,19 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) if (matchedPad.members.includes(userId)) throw new Error("Already a member") const isPublic = typeof matchedInvite === "object" && matchedInvite.public === true let resolvedRootId = await this.resolveRootId(matchedPad.rootId) + const token = inviteToken(matchedInvite) || code + let padKey = null + if (typeof matchedInvite === "object" && matchedInvite.ek) padKey = decryptFromInvite(matchedInvite.ek, code, matchedInvite.salt) if (!isPublic) { const messages = await readAll(ssbClient) const idx = buildIndex(messages) - if (idx.isCodeConsumed(resolvedRootId, code)) throw new Error("Invite already used") + if (idx.isCodeConsumed(resolvedRootId, token) || idx.isCodeConsumed(resolvedRootId, code)) throw new Error("Invite already used") + const claims = idx.codeClaims(resolvedRootId, [token, code]).filter(c => c.member !== userId) + const spent = padKey ? claims.some(c => c.byOwner || keyProofFor(padKey, c.member) === c.keyProof) : claims.length > 0 + if (spent) throw new Error("Invite already used") } - let padKey = null - if (typeof matchedInvite === "object" && matchedInvite.ek) { - padKey = decryptFromInvite(matchedInvite.ek, code, matchedInvite.salt) - setPadKey(resolvedRootId, padKey) - } - await this.addMemberToPad(matchedPad.rootId, userId, isPublic ? "" : code) + if (padKey) setPadKey(resolvedRootId, padKey) + await this.addMemberToPad(matchedPad.rootId, userId, isPublic ? "" : token) if (tribeCrypto && padKey && resolvedRootId) { try { const ssbKeys = require("../server/node_modules/ssb-keys") @@ -854,7 +898,7 @@ module.exports = ({ cooler, cipherModel, tribeCrypto, padCrypto, tribesModel }) } entries.push({ key: m.key, - author: c.author || v.author, + author: v.author, text, createdAt: c.createdAt || new Date(v.timestamp || 0).toISOString() }) diff --git a/src/models/panicmode_model.js b/src/models/panicmode_model.js index 0c11a0d..d6edcdc 100644 --- a/src/models/panicmode_model.js +++ b/src/models/panicmode_model.js @@ -1,11 +1,9 @@ -const os = require('os'); const fs = require('fs'); const path = require('path'); module.exports = { removeSSB: async () => { try { - // ruta de la cuenta activa: con varias identidades, ~/.ssb fijo borraria otra const ssbPath = require('../server/ssb_config').path; await fs.promises.rm(ssbPath, { recursive: true, force: true }); } catch (error) { diff --git a/src/models/parliament_model.js b/src/models/parliament_model.js index 18e78f7..ec18860 100644 --- a/src/models/parliament_model.js +++ b/src/models/parliament_model.js @@ -2,8 +2,21 @@ const pull = require('../server/node_modules/pull-stream'); const moment = require('../server/node_modules/moment'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped } = require('./typed_log'); +const { buildVoteResults } = require('../backend/vote_tally'); const logLimit = getConfig().ssbLogStream?.limit || 1000; +const PARLIAMENT_TYPES = [ + 'parliamentCandidature', 'parliamentCandidatureVote', 'parliamentTerm', + 'parliamentProposal', 'parliamentRevocation', 'parliamentLaw', + 'tribe', 'about', 'tombstone' +]; +const VOTE_LOG_TYPES = ['votes', 'votesVote', 'tombstone']; +const PROPOSER_TYPES = new Set(['parliamentCandidature', 'parliamentProposal', 'parliamentRevocation']); +const TRIBE_PARLIAMENT_TYPES = [ + 'tribeParliamentTerm', 'tribeParliamentCandidature', 'tribeParliamentRule', + 'parliamentCandidature', 'tombstone' +]; const TERM_DAYS = 60; const PROPOSAL_DAYS = 7; const REVOCATION_DAYS = 15; @@ -27,12 +40,131 @@ const termWindowFor = (at = Date.now()) => { }; }; +const HEMICYCLE_MAX_SEATS = 400; + +const hemicycleQuorum = (n) => Math.max(PROPOSAL_QUORUM, Math.ceil(Number(n || 0) * QUORUM_RATIO)); + +const passThreshold = (method, n) => { + const m = String(method || '').toUpperCase(); + const total = Number(n || 0); + if (m === 'DEMOCRACY' || m === 'ANARCHY') return Math.floor(total / 2) + 1; + if (m === 'MAJORITY') return Math.ceil(total * 0.8); + if (m === 'MINORITY') return Math.ceil(total * 0.2); + return null; +}; + +const buildHemicycle = ({ census = [], candidatures = [], powerType = 'none', powerId = null, leaderId = null, method = 'ANARCHY', powerMembers = [], houses = {} } = {}) => { + const people = []; + const seen = new Set(); + for (const p of census) { + const id = typeof p === 'string' ? p : (p && p.id); + if (!id || seen.has(id)) continue; + seen.add(id); + people.push({ id, name: (p && typeof p === 'object' && p.name) ? String(p.name) : '' }); + } + const n = people.length; + const m = String(method || 'ANARCHY').toUpperCase(); + const power = new Set((Array.isArray(powerMembers) ? powerMembers : []).map(String)); + const leader = leaderId || (powerType === 'inhabitant' ? powerId : null); + + const cands = (Array.isArray(candidatures) ? candidatures : []).map(c => ({ + id: String(c.id || ''), + label: String(c.label || c.targetTitle || c.targetId || c.candidateId || ''), + href: c.href || null, + targetType: c.targetType || (c.candidateId ? 'inhabitant' : 'inhabitant'), + targetId: String(c.targetId || c.candidateId || ''), + method: String(c.method || '').toUpperCase(), + voters: Array.from(new Set((Array.isArray(c.voters) ? c.voters : []).map(String))).filter(v => v !== String(c.targetId || c.candidateId || '')) + })); + const isGovernment = (c) => powerType !== 'none' && powerId && c.targetId === String(powerId); + const government = cands.find(isGovernment) || null; + const opposition = cands.filter(c => c !== government && c.voters.some(v => seen.has(v))); + opposition.sort((a, b) => b.voters.length - a.voters.length || a.label.localeCompare(b.label)); + + const blocOf = new Map(); + if (leader && seen.has(String(leader)) && powerType !== 'none') blocOf.set(String(leader), 'gov'); + if (powerType === 'tribe') for (const id of power) if (seen.has(id)) blocOf.set(id, 'gov'); + if (government) for (const v of government.voters) if (seen.has(v) && !blocOf.has(v)) blocOf.set(v, 'gov'); + opposition.forEach((c, i) => { for (const v of c.voters) if (seen.has(v) && !blocOf.has(v)) blocOf.set(v, `opp${i}`); }); + + const seats = people.map(p => ({ + id: p.id, + name: p.name, + bloc: blocOf.get(p.id) || 'none', + leader: !!leader && p.id === String(leader), + inPower: power.has(p.id), + house: houses && houses[p.id] ? String(houses[p.id]) : null + })); + const order = { gov: 0 }; + opposition.forEach((c, i) => { order[`opp${i}`] = i + 1; }); + order.none = opposition.length + 1; + seats.sort((a, b) => (order[a.bloc] - order[b.bloc]) || (b.leader - a.leader) || a.id.localeCompare(b.id)); + + const count = (key) => seats.filter(s => s.bloc === key).length; + const blocs = []; + if (government) blocs.push({ key: 'gov', kind: 'government', label: government.label, href: government.href, targetId: government.targetId, method: government.method, count: count('gov') }); + opposition.forEach((c, i) => blocs.push({ key: `opp${i}`, kind: 'opposition', label: c.label, href: c.href, targetId: c.targetId, method: c.method, count: count(`opp${i}`) })); + blocs.push({ key: 'none', kind: 'abstention', label: '', href: null, count: count('none') }); + + const houseCounts = {}; + for (const s of seats) if (s.house) houseCounts[s.house] = (houseCounts[s.house] || 0) + 1; + + return { + population: n, + method: m, + powerType, + powerId: powerId || null, + leaderId: leader || null, + seats, + blocs: blocs.filter(b => b.count > 0 || b.kind === 'government'), + houses: houseCounts, + thresholds: { quorum: hemicycleQuorum(n), pass: passThreshold(m, n) }, + seatUnit: n > HEMICYCLE_MAX_SEATS ? Math.ceil(n / HEMICYCLE_MAX_SEATS) : 1 + }; +}; + +const seatPositions = (n, { width = 720, height = 380 } = {}) => { + if (n <= 0) return []; + const cx = width / 2; + const cy = height - 20; + const outer = Math.min(width / 2 - 16, height - 60); + let rows = 1; + const rowGap = () => outer / (rows + 1.5); + const capacity = (r) => { + let total = 0; + for (let i = 0; i < r; i++) { const radius = outer - i * rowGap(); total += Math.max(1, Math.floor((Math.PI * radius) / (rowGap() * 0.95))); } + return total; + }; + while (capacity(rows) < n && rows < 30) rows += 1; + const gap = rowGap(); + const radii = []; + for (let i = 0; i < rows; i++) radii.push(outer - i * gap); + const per = radii.map(r => Math.max(1, Math.floor((Math.PI * r) / (gap * 0.95)))); + const totalCap = per.reduce((a, b) => a + b, 0); + const alloc = per.map(c => Math.floor((c / totalCap) * n)); + let left = n - alloc.reduce((a, b) => a + b, 0); + for (let i = 0; left > 0; i = (i + 1) % rows) { if (alloc[i] < per[i]) { alloc[i] += 1; left -= 1; } } + const pts = []; + radii.forEach((r, row) => { + const k = alloc[row]; + for (let j = 0; j < k; j++) { + const angle = k === 1 ? Math.PI / 2 : Math.PI - (Math.PI * (j + 0.5)) / k; + pts.push({ angle, x: cx + r * Math.cos(angle), y: cy - r * Math.sin(angle), row }); + } + }); + pts.sort((a, b) => b.angle - a.angle || a.row - b.row); + const radius = Math.max(3, Math.min(11, gap * 0.4)); + return pts.map(p => ({ x: Number(p.x.toFixed(1)), y: Number(p.y.toFixed(1)), angle: p.angle, r: Number(radius.toFixed(1)) })); +}; + + module.exports = ({ cooler, services = {} }) => { let ssb; let userId; const CACHE_MS = 250; let logCache = { at: 0, arr: null }; + let voteCache = { at: 0, results: null, tomb: null }; let electionInFlight = null; let sweepInFlight = null; @@ -67,6 +199,7 @@ module.exports = ({ cooler, services = {} }) => { ssbClient.publish(content, (e, r) => (e ? reject(e) : resolve(r))) ); logCache = { at: 0, arr: null }; + voteCache = { at: 0, results: null, tomb: null }; return res; } @@ -74,49 +207,75 @@ module.exports = ({ cooler, services = {} }) => { const now = Date.now(); if (logCache.arr && now - logCache.at < CACHE_MS) return logCache.arr; const ssbClient = await openSsb(); - const arr = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, out) => (err ? rej(err) : res(out || []))) - ); - }); + const arr = await readTyped(ssbClient, PARLIAMENT_TYPES, { limit: logLimit }); logCache = { at: now, arr }; return arr; } - function listByTypeFromMsgs(msgs, type) { + function chainIndex(msgs, type) { const tomb = buildValidatedTombstoneSet(msgs); - const rep = new Map(); - const children = new Map(); - const map = new Map(); - + const nodes = new Map(); for (const m of msgs || []) { - const k = m.key; const v = m.value || {}; const c = v.content; - if (!c) continue; -if (c.type === type) { - if (c.replaces) { - const oldId = c.replaces; - const ts = normMs(v.timestamp || m.timestamp || Date.now()); - const prev = rep.get(oldId); - if (!prev || ts > prev.ts) rep.set(oldId, { id: k, ts }); - if (!children.has(oldId)) children.set(oldId, new Set()); - children.get(oldId).add(k); - } - map.set(k, { ...c, id: k }); - } + if (!c || c.type !== type) continue; + nodes.set(m.key, { key: m.key, author: v.author, ts: normMs(v.timestamp || m.timestamp || Date.now()), c }); } + const consistent = (n) => { + if (PROPOSER_TYPES.has(type)) return n.c.proposer === n.author; + if (type === 'parliamentTerm') return !n.c.createdBy || n.c.createdBy === n.author; + return true; + }; + const valid = new Map(); + const settle = (key) => { + const path = []; + const seen = new Set(); + let cur = key; + while (!valid.has(cur)) { + const n = nodes.get(cur); + if (!n || !consistent(n)) { valid.set(cur, false); break; } + const parent = n.c.replaces ? nodes.get(n.c.replaces) : null; + if (!parent) { valid.set(cur, true); break; } + if (parent.author !== n.author || seen.has(cur)) { valid.set(cur, false); break; } + seen.add(cur); + path.push(cur); + cur = parent.key; + } + for (let i = path.length - 1; i >= 0; i--) { + if (!valid.has(path[i])) valid.set(path[i], valid.get(nodes.get(path[i]).c.replaces) === true); + } + return valid.get(key) === true; + }; + const next = new Map(); + for (const n of nodes.values()) { + if (!n.c.replaces || !nodes.has(n.c.replaces) || !settle(n.key)) continue; + const prev = next.get(n.c.replaces); + if (!prev || n.ts > nodes.get(prev).ts) next.set(n.c.replaces, n.key); + } + const tipOf = (k) => { let x = k, g = 0; while (next.has(x) && g++ < 100000) x = next.get(x); return x; }; + const rootOf = (k) => { let x = k, g = 0; while (nodes.has(x) && nodes.has(nodes.get(x).c.replaces) && g++ < 100000) x = nodes.get(x).c.replaces; return x; }; + const items = []; + for (const n of nodes.values()) { + if ((n.c.replaces && nodes.has(n.c.replaces)) || !settle(n.key)) continue; + const tip = tipOf(n.key); + if (tomb.has(tip)) continue; + items.push({ ...nodes.get(tip).c, id: tip }); + } + const resolve = (k) => (nodes.has(k) && settle(k) ? tipOf(rootOf(k)) : null); + const authorOf = (k) => (nodes.has(k) ? nodes.get(k).author : null); + return { items, resolve, authorOf }; + } - for (const oldId of rep.keys()) map.delete(oldId); - for (const [oldId, kids] of children.entries()) { - const winner = rep.get(oldId)?.id || null; - for (const kid of kids) { - if (kid !== winner) map.delete(kid); - } - } - for (const tId of tomb) map.delete(tId); - return [...map.values()]; + function listByTypeFromMsgs(msgs, type) { + const items = chainIndex(msgs, type).items; + return type === 'parliamentProposal' ? items.map(withCampaignRef) : items; + } + + function withCampaignRef(p) { + if (!p || p.campaignId) return p; + const m = String(p.description || '').match(/\n*\s*(\d+) signatures: \/campaigns\/(%[^\s]+\.sha256)\s*$/); + if (!m) return p; + return { ...p, campaignId: m[2], signatures: Number(m[1]) || 0, description: String(p.description || '').slice(0, m.index).trim() }; } async function listByType(type) { @@ -212,11 +371,14 @@ if (c.type === type) { const s = String(candidateInput || '').trim(); if (!s) return null; const tribes = await listTribesAny(); - const t = tribes.find(tr => + let t = tribes.find(tr => tr.id === s || (tr.title && tr.title.toLowerCase() === s.toLowerCase()) || (tr.name && tr.name.toLowerCase() === s.toLowerCase()) ); + if (!t && s.startsWith('%') && services.tribes && services.tribes.getTribeById) { + try { t = await services.tribes.getTribeById(s); } catch (_) { t = null; } + } if (t) { return { type: 'tribe', id: t.id, title: t.title || t.name || t.id, members: ensureArray(t.members) }; } @@ -242,8 +404,9 @@ if (c.type === type) { let n = 0; try { if (services.inhabitants?.listInhabitants) { - const list = await services.inhabitants.listInhabitants({ filter: 'all', includeInactive: true }); - n = Array.isArray(list) ? list.length : 0; + n = typeof services.inhabitants.countInhabitants === 'function' + ? Number(await services.inhabitants.countInhabitants()) || 0 + : ((await services.inhabitants.listInhabitants({ filter: 'all', includeInactive: true })) || []).length; } } catch {} _inhCache = { at: now, n }; @@ -340,9 +503,186 @@ if (c.type === type) { return enriched; } + function supportFor(msgs) { + const cands = chainIndex(msgs, 'parliamentCandidature'); + const byId = new Map(cands.items.map(c => [c.id, c])); + const votes = []; + for (const m of msgs || []) { + const v = m.value || {}; + const c = v.content; + if (!c || c.type !== 'parliamentCandidatureVote' || typeof c.target !== 'string' || !v.author) continue; + const id = cands.resolve(c.target); + const cand = id ? byId.get(id) : null; + if (!cand) continue; + if (cand.targetType === 'inhabitant' && String(cand.targetId) === String(v.author)) continue; + votes.push({ cand, voter: v.author, ts: normMs(v.timestamp || m.timestamp || 0), seq: Number(v.sequence) || 0 }); + } + votes.sort((a, b) => (a.ts - b.ts) || (a.seq - b.seq)); + return (term) => { + const from = new Date(term.startAt).getTime(); + const to = new Date(term.endAt).getTime(); + const counted = new Map(); + for (const x of votes) { + if (counted.has(x.voter)) continue; + const created = new Date(x.cand.createdAt).getTime(); + if (!Number.isFinite(created) || created < from || created >= to) continue; + counted.set(x.voter, x.cand); + } + const perCand = new Map(); + for (const cand of counted.values()) perCand.set(cand, (perCand.get(cand) || 0) + 1); + let best = 0; + for (const [cand, n] of perCand) { + if (cand.targetType !== term.powerType || String(cand.targetId) !== String(term.powerId)) continue; + if (String(cand.method || '').toUpperCase() !== String(term.method || '').toUpperCase()) continue; + if (n > best) best = n; + } + return { votes: best, total: counted.size }; + }; + } + + function termShapeOk(t) { + const s = new Date(t && t.startAt).getTime(); + const e = new Date(t && t.endAt).getTime(); + if (!Number.isFinite(s) || !Number.isFinite(e) || e <= s || s > Date.now()) return false; + const window = termWindowFor(s); + if (new Date(window.startAt).getTime() === s && new Date(window.endAt).getTime() === e) return true; + return e <= Date.now() && Math.abs(e - s - TERM_SPAN_MS) <= 86400000; + } + + function validTermsFrom(msgs) { + const supportOf = supportFor(msgs); + const out = []; + for (const t of chainIndex(msgs, 'parliamentTerm').items) { + const method = String(t.method || '').toUpperCase(); + if (!METHODS.includes(method) || !termShapeOk(t)) continue; + const inhabitant = t.powerType === 'inhabitant' && FEED_ID_RE.test(String(t.powerId || '')); + const tribe = t.powerType === 'tribe' && !!t.powerId; + if (!inhabitant && !tribe) continue; + const support = supportOf({ ...t, method }); + if (support.votes < PROPOSAL_QUORUM) continue; + out.push({ ...t, method, winnerVotes: support.votes, totalVotes: support.total }); + } + return collapseOverlappingTerms(out, (t) => t.winnerVotes); + } + + async function listValidTerms() { + return validTermsFrom(await readLog()); + } + + async function readVoteResults() { + const now = Date.now(); + if (voteCache.results && now - voteCache.at < CACHE_MS) return voteCache; + const ssbClient = await openSsb(); + const arr = await readTyped(ssbClient, VOTE_LOG_TYPES, { limit: logLimit }); + voteCache = { at: now, results: buildVoteResults(arr), tomb: buildValidatedTombstoneSet(arr) }; + return voteCache; + } + + async function voteResult(voteId, creator) { + if (!voteId) return null; + const { results, tomb } = await readVoteResults(); + const r = results.get(voteId); + if (!r || tomb.has(r.rootId) || tomb.has(voteId)) return null; + if (creator && r.creator !== creator) return null; + const vm = r.votes || {}; + const due = r.deadline ? new Date(r.deadline).getTime() : NaN; + return { + YES: Number(vm.YES ?? vm.Yes ?? vm.yes ?? 0), + NO: Number(vm.NO ?? vm.No ?? vm.no ?? 0), + ABSTENTION: Number(vm.ABSTENTION ?? vm.Abstention ?? vm.abstention ?? 0), + total: Number(r.totalVotes || 0), + deadline: r.deadline || null, + closed: Number.isFinite(due) && due <= Date.now() + }; + } + + function termForId(termId, terms) { + const id = String(termId || ''); + const found = terms.find(t => t.id === id || t.startAt === id); + if (found) return found; + const m = id.match(/^anarchy:(-?\d+)$/); + return m ? virtualAnarchyTerm(termWindowFor(TERM_EPOCH + Number(m[1]) * TERM_SPAN_MS)) : null; + } + + async function outcomeContext() { + const msgs = await readLog(); + const terms = validTermsFrom(msgs); + const proposals = chainIndex(msgs, 'parliamentProposal'); + const revocations = chainIndex(msgs, 'parliamentRevocation'); + const voteOwner = new Map(); + const byAge = [...proposals.items, ...revocations.items].sort((a, b) => + ((new Date(a.createdAt).getTime() || 0) - (new Date(b.createdAt).getTime() || 0)) || String(a.id).localeCompare(String(b.id)) + ); + for (const x of byAge) if (x.voteId && !voteOwner.has(x.voteId)) voteOwner.set(x.voteId, x.id); + return { msgs, terms, proposals, revocations, voteOwner, members: new Map(), karma: new Map() }; + } + + async function mayPropose(term, feedId, ctx) { + if (!term || !feedId) return false; + if (term.virtual) return true; + if (term.powerType === 'inhabitant') return String(term.powerId) === String(feedId); + if (term.powerType === 'tribe') { + if (!ctx.members.has(term.powerId)) { + let members = []; + if (services.tribes) { + try { const tribe = await services.tribes.getTribeById(term.powerId); members = ensureArray(tribe && tribe.members); } catch {} + } + ctx.members.set(term.powerId, members); + } + return ctx.members.get(term.powerId).includes(feedId); + } + return false; + } + + async function karmaOf(feedId, ctx) { + if (!ctx.karma.has(feedId)) ctx.karma.set(feedId, await getInhabitantKarma(feedId)); + return ctx.karma.get(feedId); + } + + async function wonKarmatocracy(p, term, ctx) { + const spanOf = (x) => [new Date(x.createdAt).getTime() || 0, new Date(x.deadline).getTime()]; + const [ps, pe] = spanOf(p); + const rivals = []; + for (const q of ctx.proposals.items) { + if (q.termId !== p.termId || String(q.method || '').toUpperCase() !== 'KARMATOCRACY') continue; + const [qs, qe] = spanOf(q); + if (!Number.isFinite(qe) || qe > Date.now() || qs > pe || ps > qe) continue; + if (!(await mayPropose(term, q.proposer, ctx))) continue; + rivals.push({ id: q.id, proposer: q.proposer, karma: await karmaOf(q.proposer, ctx), createdAtMs: qs }); + } + rivals.sort((a, b) => (b.karma - a.karma) || (a.createdAtMs - b.createdAtMs) || String(a.proposer).localeCompare(String(b.proposer)) || String(a.id).localeCompare(String(b.id))); + return rivals.length > 0 && rivals[0].id === p.id; + } + + async function deriveOutcome(p, ctx, kind = 'proposal') { + if (!p || !p.proposer) return null; + const term = termForId(p.termId, ctx.terms); + if (!term) return null; + const method = String(p.method || '').toUpperCase(); + if (method !== String(term.method || '').toUpperCase()) return null; + if (!(await mayPropose(term, p.proposer, ctx))) return null; + const status = String(p.status || 'OPEN').toUpperCase(); + if (status === 'REJECTED' || status === 'DISCARDED') return status; + if (VOTE_METHODS.has(method)) { + if (!p.voteId || ctx.voteOwner.get(p.voteId) !== p.id) return null; + const v = await voteResult(p.voteId, p.proposer); + if (!v) return null; + if (!v.closed) return 'OPEN'; + return (await passesThreshold(method, v.total, v.YES)) ? 'APPROVED' : 'REJECTED'; + } + const due = new Date(p.deadline).getTime(); + const expired = Number.isFinite(due) && due <= Date.now(); + if (method === 'DICTATORSHIP') return (expired || status === 'APPROVED' || status === 'ENACTED') ? 'APPROVED' : 'OPEN'; + if (method === 'KARMATOCRACY') { + if (!expired) return 'OPEN'; + if (kind === 'revocation') return 'APPROVED'; + return (await wonKarmatocracy(p, term, ctx)) ? 'APPROVED' : 'REJECTED'; + } + return null; + } + async function listTermsBase(filter = 'all') { - const all = await listByType('parliamentTerm'); - const collapsed = collapseOverlappingTerms(all); + const collapsed = await listValidTerms(); const latestStart = collapsed.reduce((mx, t) => Math.max(mx, new Date(t.startAt).getTime() || 0), 0); let arr = collapsed.map(t => { const superseded = (new Date(t.startAt).getTime() || 0) < latestStart; @@ -354,8 +694,7 @@ if (c.type === type) { } async function getLatestTermAny() { - const terms = await listByType('parliamentTerm'); - const collapsed = collapseOverlappingTerms(terms); + const collapsed = await listValidTerms(); return collapsed[0] || null; } @@ -452,53 +791,28 @@ if (c.type === type) { } return x.termId === termId || (termStart && x.termId === termStart); }; - const proposals = await listByType('parliamentProposal'); - const mine = termId ? proposals.filter(matchesTerm) : []; - const proposed = mine.length; + const ctx = await outcomeContext(); + const mine = termId ? ctx.proposals.items.filter(matchesTerm) : []; + let proposed = 0; let approved = 0; let declined = 0; let discarded = 0; for (const p of mine) { - const baseStatus = String(p.status || 'OPEN').toUpperCase(); - let finalStatus = baseStatus; - let isDiscarded = false; - if (p.voteId && services.votes?.getVoteById) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - const dl = v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (dl && moment(dl).isBefore(moment())); - const reached = await passesThreshold(p.method, total, yes); - if (!closed) { - if (dl && moment(dl).isBefore(moment()) && !reached) isDiscarded = true; - else finalStatus = 'OPEN'; - } else { - finalStatus = reached ? 'APPROVED' : 'REJECTED'; - } - } catch {} - } else { - if (baseStatus === 'OPEN' && p.deadline && moment(p.deadline).isBefore(moment())) isDiscarded = true; - } - if (isDiscarded) { - discarded++; - continue; - } - if (finalStatus === 'ENACTED' || finalStatus === 'APPROVED') { - approved++; - continue; - } - if (finalStatus === 'REJECTED') { - declined++; - continue; + const outcome = await deriveOutcome(p, ctx); + if (!outcome) continue; + proposed++; + if (outcome === 'APPROVED') approved++; + else if (outcome === 'REJECTED') declined++; + else if (outcome === 'DISCARDED') discarded++; + } + let revocated = 0; + if (termId) { + for (const r of ctx.revocations.items) { + if (!matchesTerm(r)) continue; + const rTerm = termForId(r.termId, ctx.terms); + if (rTerm && !rTerm.virtual && isExpiredTerm(rTerm) && (await deriveOutcome(r, ctx, 'revocation')) === 'APPROVED') revocated++; } } - const revs = await listByType('parliamentRevocation'); - const revocated = termId - ? revs.filter(r => r.status === 'ENACTED' && matchesTerm(r)).length - : 0; return { proposed, approved, declined, discarded, revocated }; } @@ -552,84 +866,6 @@ if (c.type === type) { return nProp + nLaw; } - async function closeExpiredKarmatocracy(term) { - const termId = term.id || term.startAt; - const all = await listByType('parliamentProposal'); - const pending = all.filter(p => - p.termId === termId && - String(p.method).toUpperCase() === 'KARMATOCRACY' && - (p.status || 'OPEN') !== 'ENACTED' && - p.deadline && moment().isAfter(parseISO(p.deadline)) - ); - if (!pending.length) return; - const withKarma = await Promise.all(pending.map(async p => ({ - ...p, - karma: await getInhabitantKarma(p.proposer), - createdAtMs: new Date(p.createdAt).getTime() || 0 - }))); - withKarma.sort((a, b) => { - if (b.karma !== a.karma) return b.karma - a.karma; - if (a.createdAtMs !== b.createdAtMs) return a.createdAtMs - b.createdAtMs; - return String(a.proposer).localeCompare(String(b.proposer)); - }); - const winner = withKarma[0]; - const losers = withKarma.slice(1); - const approve = { ...stripId(winner), replaces: winner.id, status: 'APPROVED', updatedAt: nowISO() }; - await publishMsg(approve); - for (const lo of losers) { - const rej = { ...stripId(lo), replaces: lo.id, status: 'REJECTED', updatedAt: nowISO() }; - await publishMsg(rej); - } - } - - async function closeExpiredDictatorship(term) { - const termId = term.id || term.startAt; - const all = await listByType('parliamentProposal'); - const pending = all.filter(p => - p.termId === termId && - String(p.method).toUpperCase() === 'DICTATORSHIP' && - (p.status || 'OPEN') !== 'ENACTED' && - p.deadline && moment().isAfter(parseISO(p.deadline)) - ); - if (!pending.length) return; - for (const p of pending) { - const updated = { ...stripId(p), replaces: p.id, status: 'APPROVED', updatedAt: nowISO() }; - await publishMsg(updated); - } - } - - async function closeExpiredRevocationKarmatocracy(term) { - const termId = term.id || term.startAt; - const all = await listByType('parliamentRevocation'); - const pending = all.filter(p => - p.termId === termId && - String(p.method).toUpperCase() === 'KARMATOCRACY' && - (p.status || 'OPEN') !== 'ENACTED' && - p.deadline && moment().isAfter(parseISO(p.deadline)) - ); - if (!pending.length) return; - for (const p of pending) { - const updated = { ...stripId(p), replaces: p.id, status: 'APPROVED', updatedAt: nowISO() }; - await publishMsg(updated); - } - } - - async function closeExpiredRevocationDictatorship(term) { - const termId = term.id || term.startAt; - const all = await listByType('parliamentRevocation'); - const pending = all.filter(p => - p.termId === termId && - String(p.method).toUpperCase() === 'DICTATORSHIP' && - (p.status || 'OPEN') !== 'ENACTED' && - p.deadline && moment().isAfter(parseISO(p.deadline)) - ); - if (!pending.length) return; - for (const p of pending) { - const updated = { ...stripId(p), replaces: p.id, status: 'APPROVED', updatedAt: nowISO() }; - await publishMsg(updated); - } - } - async function createRevocation({ lawId, title, reasons }) { const term = await getCurrentTermBase(); if (!term) throw new Error('No active government'); @@ -637,7 +873,7 @@ if (c.type === type) { if (!allowed) throw new Error('You are not in the goverment, yet.'); const lawIdStr = String(lawId || '').trim(); if (!lawIdStr) throw new Error('Law required'); - const laws = await listByType('parliamentLaw'); + const laws = await listLaws(); const law = laws.find(l => l.id === lawIdStr); if (!law) throw new Error('Law not found'); const method = String(term.method || 'DEMOCRACY').toUpperCase(); @@ -678,36 +914,28 @@ if (c.type === type) { return await publishMsg(rev); } - async function closeRevocation(revId) { - const ssbClient = await openSsb(); - const msg = await new Promise((resolve, reject) => - ssbClient.get(revId, (e, m) => (e || !m) ? reject(new Error('Revocation not found')) : resolve(m)) - ); - if (msg.content?.type !== 'parliamentRevocation') throw new Error('Revocation not found'); - const p = msg.content; + async function settleOwn(kind, id, manual) { + const ctx = await outcomeContext(); + const index = kind === 'revocation' ? ctx.revocations : ctx.proposals; + const tipId = index.resolve(String(id || '')); + const p = tipId ? index.items.find(x => x.id === tipId) : null; + if (!p) throw new Error(kind === 'revocation' ? 'Revocation not found' : 'Proposal not found'); const currentStatus = String(p.status || 'OPEN').toUpperCase(); if (currentStatus === 'ENACTED' || currentStatus === 'REJECTED' || currentStatus === 'DISCARDED') return p; - const method = String(p.method || '').toUpperCase(); - if (method === 'DICTATORSHIP') { - if (currentStatus === 'APPROVED') return p; - const updated = { ...p, replaces: revId, status: 'APPROVED', updatedAt: nowISO() }; - await publishMsg(updated); - return updated; - } - if (method === 'KARMATOCRACY') return p; - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - const ok = await passesThreshold(method, total, yes); - const desiredStatus = ok ? 'APPROVED' : 'REJECTED'; - if (currentStatus === desiredStatus) return p; - const updated = { ...p, replaces: revId, status: desiredStatus, updatedAt: nowISO() }; + if (String(p.proposer) !== String(userId)) return p; + let outcome = await deriveOutcome(p, ctx, kind); + if (manual && outcome === 'OPEN' && String(p.method || '').toUpperCase() === 'DICTATORSHIP') outcome = 'APPROVED'; + if (outcome !== 'APPROVED' && outcome !== 'REJECTED') return p; + if (currentStatus === outcome) return p; + const updated = { ...stripId(p), replaces: p.id, status: outcome, updatedAt: nowISO() }; await publishMsg(updated); return updated; } + async function closeRevocation(revId) { + return await settleOwn('revocation', revId, true); + } + async function proposeCandidature({ candidateId, method }) { const m = String(method || '').toUpperCase(); if (!METHODS.includes(m)) throw new Error('Invalid method'); @@ -754,7 +982,13 @@ if (c.type === type) { return await publishMsg(content); } - async function createProposal({ title, description }) { + async function createProposal({ title, description, campaignId = '', signatures = 0, goal = 0 }) { + const campaignRef = String(campaignId || '').trim(); + if (campaignRef) { + const existing = await listByType('parliamentProposal'); + if (existing.some(p => String(p.campaignId || '') === campaignRef)) throw new Error('This campaign was already raised to Parliament'); + } + const extra = campaignRef ? { campaignId: campaignRef, signatures: Math.max(0, Math.floor(Number(signatures) || 0)), goal: Math.max(0, Math.floor(Number(goal) || 0)) } : {}; let term = await getCurrentTermBase(); if (!term) term = await resolveElection(); if (!term) throw new Error('No active government'); @@ -767,42 +1001,16 @@ if (c.type === type) { const method = String(term.method || 'DEMOCRACY').toUpperCase(); const deadline = moment().add(PROPOSAL_DAYS, 'days').toISOString(); if (method === 'DICTATORSHIP' || method === 'KARMATOCRACY') { - const proposal = { type: 'parliamentProposal', title, description: description || '', method, termId: term.id || term.startAt, proposer: userId, status: 'OPEN', deadline, createdAt: nowISO() }; + const proposal = { type: 'parliamentProposal', title, description: description || '', method, termId: term.id || term.startAt, proposer: userId, status: 'OPEN', deadline, createdAt: nowISO(), ...extra }; return await publishMsg(proposal); } const voteMsg = await services.votes.createVote(title, deadline, ['YES', 'NO', 'ABSTENTION'], [`gov:${term.id || term.startAt}`, `govMethod:${method}`, 'proposal']); - const proposal = { type: 'parliamentProposal', title, description: description || '', method, voteId: voteMsg.key || voteMsg.id, termId: term.id || term.startAt, proposer: userId, status: 'OPEN', createdAt: nowISO() }; + const proposal = { type: 'parliamentProposal', title, description: description || '', method, voteId: voteMsg.key || voteMsg.id, termId: term.id || term.startAt, proposer: userId, status: 'OPEN', createdAt: nowISO(), ...extra }; return await publishMsg(proposal); } async function closeProposal(proposalId) { - const ssbClient = await openSsb(); - const msg = await new Promise((resolve, reject) => - ssbClient.get(proposalId, (e, m) => (e || !m) ? reject(new Error('Proposal not found')) : resolve(m)) - ); - if (msg.content?.type !== 'parliamentProposal') throw new Error('Proposal not found'); - const p = msg.content; - const currentStatus = String(p.status || 'OPEN').toUpperCase(); - if (currentStatus === 'ENACTED' || currentStatus === 'REJECTED' || currentStatus === 'DISCARDED') return p; - const method = String(p.method || '').toUpperCase(); - if (method === 'DICTATORSHIP') { - if (currentStatus === 'APPROVED') return p; - const updated = { ...p, replaces: proposalId, status: 'APPROVED', updatedAt: nowISO() }; - await publishMsg(updated); - return updated; - } - if (method === 'KARMATOCRACY') return p; - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - const ok = await passesThreshold(method, total, yes); - const desiredStatus = ok ? 'APPROVED' : 'REJECTED'; - if (currentStatus === desiredStatus) return p; - const updated = { ...p, replaces: proposalId, status: desiredStatus, updatedAt: nowISO() }; - await publishMsg(updated); - return updated; + return await settleOwn('proposal', proposalId, true); } async function sweepProposals() { @@ -810,48 +1018,15 @@ if (c.type === type) { sweepInFlight = (async () => { const term = await getCurrentTermBase(); if (!term) return; - await closeExpiredKarmatocracy(term); - await closeExpiredDictatorship(term); - - const allProps = await listByType('parliamentProposal'); - const voteProps = allProps.filter(p => { - const m = String(p.method || '').toUpperCase(); - return (m === 'DEMOCRACY' || m === 'ANARCHY' || m === 'MAJORITY' || m === 'MINORITY') && p.voteId; - }); - - for (const p of voteProps) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - const deadline = v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - if (closed) { try { await closeProposal(p.id); } catch {} ; continue; } - } catch {} + const termId = term.id || term.startAt; + const ctx = await outcomeContext(); + for (const p of ctx.proposals.items) { + if (p.termId !== termId || String(p.proposer) !== String(userId)) continue; + try { await settleOwn('proposal', p.id, false); } catch {} } - - await closeExpiredRevocationKarmatocracy(term); - await closeExpiredRevocationDictatorship(term); - - const revs = await listByType('parliamentRevocation'); - const voteRevs = revs.filter(p => { - const m = String(p.method || '').toUpperCase(); - return (m === 'DEMOCRACY' || m === 'ANARCHY' || m === 'MAJORITY' || m === 'MINORITY') && p.voteId; - }); - - for (const p of voteRevs) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - const deadline = v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - if (closed) { try { await closeRevocation(p.id); } catch {} ; continue; } - } catch {} + for (const r of ctx.revocations.items) { + if (r.termId !== termId || String(r.proposer) !== String(userId)) continue; + try { await settleOwn('revocation', r.id, false); } catch {} } })().finally(() => { sweepInFlight = null; }); @@ -872,6 +1047,59 @@ if (c.type === type) { return await listTermsBase(filter); } + async function getHemicycle(termInput = null) { + const term = termInput || await getCurrentTerm().catch(() => null); + const window = term && term.startAt ? { startAt: term.startAt, endAt: term.endAt } : termWindowFor(Date.now()); + const from = new Date(window.startAt).getTime(); + const to = window.endAt ? new Date(window.endAt).getTime() : Number.MAX_SAFE_INTEGER; + const msgs = await readLog(); + const rootOf = new Map(); + const cands = new Map(); + for (const m of msgs || []) { + const c = m.value && m.value.content; + if (!c || c.type !== 'parliamentCandidature') continue; + const root = c.replaces ? (rootOf.get(c.replaces) || c.replaces) : m.key; + rootOf.set(m.key, root); + if (!cands.has(root)) { + cands.set(root, { id: root, targetType: c.targetType, targetId: c.targetId, targetTitle: c.targetTitle, method: c.method, createdAt: c.createdAt || new Date(normMs(m.value.timestamp || m.timestamp)).toISOString(), voters: new Set() }); + } + } + for (const m of msgs || []) { + const c = m.value && m.value.content; + if (!c || c.type !== 'parliamentCandidatureVote' || !c.target) continue; + const root = rootOf.get(c.target) || c.target; + const cand = cands.get(root); + if (cand) cand.voters.add(m.value.author); + } + const inWindow = [...cands.values()].filter(c => { const t = new Date(c.createdAt).getTime(); return Number.isFinite(t) ? (t >= from && t <= to) : false; }); + let census = []; + try { + if (services.inhabitants?.listInhabitants) census = await services.inhabitants.listInhabitants({ filter: 'all', includeInactive: true }); + } catch {} + let powerMembers = []; + if (term && term.powerType === 'tribe' && term.powerId && services.tribes?.getTribeById) { + try { const t = await services.tribes.getTribeById(term.powerId); powerMembers = Array.isArray(t && t.members) ? t.members : []; } catch {} + } + const candidatures = inWindow.map(c => ({ + id: c.id, + label: c.targetTitle || c.targetId, + href: c.targetType === 'tribe' ? `/tribe/${encodeURIComponent(c.targetId)}` : `/author/${encodeURIComponent(c.targetId)}`, + targetType: c.targetType, + targetId: c.targetId, + method: c.method, + voters: [...c.voters] + })); + return buildHemicycle({ + census: (census || []).map(u => ({ id: u.id, name: u.name || '' })), + candidatures, + powerType: term ? term.powerType : 'none', + powerId: term ? term.powerId : null, + leaderId: term && term.powerType === 'inhabitant' ? term.powerId : null, + method: term ? term.method : 'ANARCHY', + powerMembers + }); + } + async function getCurrentTerm() { const published = await getCurrentTermBase(); return published || virtualAnarchyTerm(); @@ -915,207 +1143,104 @@ if (c.type === type) { return rows; } - async function listProposalsCurrent() { - const all = await listByType('parliamentProposal'); - const rows = all.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + async function listCurrentOf(kind) { + const ctx = await outcomeContext(); + const index = kind === 'revocation' ? ctx.revocations : ctx.proposals; + const items = kind === 'revocation' ? index.items : index.items.map(withCampaignRef); + const rows = items.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + const needed = await proposalQuorum(); const out = []; for (const p of rows) { - let deadline = p.deadline || null; - let yes = 0; - let total = 0; - const baseStatus = String(p.status || 'OPEN').toUpperCase(); - let derivedStatus = baseStatus; - if (p.voteId && services.votes?.getVoteById) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - total = Number(v.totalVotes ?? v.total ?? sum); - yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - deadline = deadline || v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - const reached = await passesThreshold(p.method, total, yes); - if (closed) derivedStatus = reached ? 'APPROVED' : 'REJECTED'; - else derivedStatus = 'OPEN'; - } catch { - derivedStatus = baseStatus; - } - } else { - if (baseStatus === 'OPEN' && p.deadline && moment(p.deadline).isBefore(moment())) derivedStatus = 'DISCARDED'; - } - if (derivedStatus === 'ENACTED' || derivedStatus === 'REJECTED' || derivedStatus === 'DISCARDED') continue; - const needed = await proposalQuorum(); + if (String(p.status || 'OPEN').toUpperCase() === 'ENACTED') continue; + const derivedStatus = await deriveOutcome(p, ctx, kind); + if (!derivedStatus || derivedStatus === 'REJECTED' || derivedStatus === 'DISCARDED') continue; + const v = p.voteId ? await voteResult(p.voteId, p.proposer) : null; + const yes = v ? v.YES : 0; + const total = v ? v.total : 0; + const deadline = p.deadline || (v && v.deadline) || null; const onTrack = await passesThreshold(p.method, total, yes); out.push({ ...p, deadline, yes, total, needed, onTrack, derivedStatus }); } return out; } + async function listFutureOf(kind) { + const term = await getCurrentTermBase(); + if (!term) return []; + const termId = term.id || term.startAt; + const ctx = await outcomeContext(); + const index = kind === 'revocation' ? ctx.revocations : ctx.proposals; + const items = kind === 'revocation' ? index.items : index.items.map(withCampaignRef); + const rows = items + .filter(p => p.termId === termId && String(p.status || 'OPEN').toUpperCase() !== 'ENACTED') + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + const needed = await proposalQuorum(); + const out = []; + for (const p of rows) { + if ((await deriveOutcome(p, ctx, kind)) !== 'APPROVED') continue; + const v = p.voteId ? await voteResult(p.voteId, p.proposer) : null; + const deadline = p.deadline || (v && v.deadline) || null; + out.push({ ...p, deadline, yes: v ? v.YES : 0, total: v ? v.total : 0, needed }); + } + return out; + } + + async function listProposalsCurrent() { + return await listCurrentOf('proposal'); + } + async function deriveProposalStatus(method, voteId) { const m = String(method || '').toUpperCase(); - if (!voteId || !(m === 'DEMOCRACY' || m === 'ANARCHY' || m === 'MAJORITY' || m === 'MINORITY')) return null; + if (!voteId || !VOTE_METHODS.has(m)) return null; try { - const v = await services.votes.getVoteById(voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - const yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - const deadline = v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - if (!closed) return 'OPEN'; - return (await passesThreshold(m, total, yes)) ? 'APPROVED' : 'REJECTED'; + const v = await voteResult(voteId); + if (!v) return null; + if (!v.closed) return 'OPEN'; + return (await passesThreshold(m, v.total, v.YES)) ? 'APPROVED' : 'REJECTED'; } catch { return null; } } async function listFutureLawsCurrent() { - const term = await getCurrentTermBase(); - if (!term) return []; - const termId = term.id || term.startAt; - const all = await listByType('parliamentProposal'); - const rows = all - .filter(p => p.termId === termId) - .filter(p => p.status === 'APPROVED') - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - const out = []; - for (const p of rows) { - let yes = 0; - let total = 0; - let deadline = p.deadline || null; - let voteClosed = true; - if (p.voteId && services.votes?.getVoteById) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - total = Number(v.totalVotes ?? v.total ?? sum); - yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - deadline = deadline || v.deadline || v.endAt || v.expiresAt || null; - voteClosed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - if (!voteClosed) continue; - if (VOTE_METHODS.has(String(p.method || '').toUpperCase()) && !(await passesThreshold(p.method, total, yes))) continue; - } catch {} - } - const needed = await proposalQuorum(); - out.push({ ...p, deadline, yes, total, needed }); - } - return out; + return await listFutureOf('proposal'); } async function listRevocationsCurrent() { - const all = await listByType('parliamentRevocation'); - const rows = all.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - const out = []; - for (const p of rows) { - let deadline = p.deadline || null; - let yes = 0; - let total = 0; - const baseStatus = String(p.status || 'OPEN').toUpperCase(); - let derivedStatus = baseStatus; - if (p.voteId && services.votes?.getVoteById) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - total = Number(v.totalVotes ?? v.total ?? sum); - yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - deadline = deadline || v.deadline || v.endAt || v.expiresAt || null; - const closed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - const reached = await passesThreshold(p.method, total, yes); - if (closed) derivedStatus = reached ? 'APPROVED' : 'REJECTED'; - else derivedStatus = 'OPEN'; - } catch { - derivedStatus = baseStatus; - } - } else { - if (baseStatus === 'OPEN' && p.deadline && moment(p.deadline).isBefore(moment())) derivedStatus = 'DISCARDED'; - } - if (derivedStatus === 'ENACTED' || derivedStatus === 'REJECTED' || derivedStatus === 'DISCARDED') continue; - const needed = await proposalQuorum(); - const onTrack = await passesThreshold(p.method, total, yes); - out.push({ ...p, deadline, yes, total, needed, onTrack, derivedStatus }); - } - return out; + return await listCurrentOf('revocation'); } async function listFutureRevocationsCurrent() { - const term = await getCurrentTermBase(); - if (!term) return []; - const termId = term.id || term.startAt; - const all = await listByType('parliamentRevocation'); - const rows = all - .filter(p => p.termId === termId) - .filter(p => p.status === 'APPROVED') - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - const out = []; - for (const p of rows) { - let yes = 0; - let total = 0; - let deadline = p.deadline || null; - let voteClosed = true; - if (p.voteId && services.votes?.getVoteById) { - try { - const v = await services.votes.getVoteById(p.voteId); - const votesMap = v.votes || {}; - const sum = Object.values(votesMap).reduce((s, n) => s + Number(n || 0), 0); - total = Number(v.totalVotes ?? v.total ?? sum); - yes = Number(votesMap.YES ?? votesMap.Yes ?? votesMap.yes ?? 0); - deadline = deadline || v.deadline || v.endAt || v.expiresAt || null; - voteClosed = v.status === 'CLOSED' || (deadline && moment(deadline).isBefore(moment())); - if (!voteClosed) continue; - if (VOTE_METHODS.has(String(p.method || '').toUpperCase()) && !(await passesThreshold(p.method, total, yes))) continue; - } catch {} - } - const needed = await proposalQuorum(); - out.push({ ...p, deadline, yes, total, needed }); - } - return out; + return await listFutureOf('revocation'); } async function countRevocationsEnacted() { - const all = await listByType('parliamentRevocation'); - return all.filter(r => r.status === 'ENACTED').length; + const ctx = await outcomeContext(); + let n = 0; + for (const r of ctx.revocations.items) { + const term = termForId(r.termId, ctx.terms); + if (term && !term.virtual && isExpiredTerm(term) && (await deriveOutcome(r, ctx, 'revocation')) === 'APPROVED') n++; + } + return n; } - async function voteSnapshot(voteId) { - if (!voteId || !services.votes?.getVoteById) return null; - try { - const v = await services.votes.getVoteById(voteId); - const vm = v?.votes || {}; - const sum = Object.values(vm).reduce((s, n) => s + Number(n || 0), 0); - const total = Number(v.totalVotes ?? v.total ?? sum); - return { - YES: Number(vm.YES ?? vm.Yes ?? vm.yes ?? 0), - NO: Number(vm.NO ?? vm.No ?? vm.no ?? 0), - ABSTENTION: Number(vm.ABSTENTION ?? vm.Abstention ?? vm.abstention ?? 0), - total - }; - } catch { - return null; - } - } + const votesOf = (v) => (v ? { YES: v.YES, NO: v.NO, ABSTENTION: v.ABSTENTION, total: v.total } : null); async function enactApprovedChanges(expiringTerm) { if (!expiringTerm) return; const termId = expiringTerm.id || expiringTerm.startAt; + const ctx = await outcomeContext(); - const proposals = await listByType('parliamentProposal'); - const revocations = await listByType('parliamentRevocation'); - - const approvedProps = proposals.filter(p => p.termId === termId && p.status === 'APPROVED'); - for (const p of approvedProps) { - const snap = await voteSnapshot(p.voteId); - const votesFinal = - (p.votes && Object.keys(p.votes).length ? p.votes : null) || - snap || - { YES: 1, NO: 0, ABSTENTION: 0, total: 1 }; - - const totalFinal = Number(votesFinal.total ?? Object.entries(votesFinal).reduce((s, [k, n]) => s + (k === 'total' ? 0 : Number(n || 0)), 0)); - const yesFinal = Number(votesFinal.YES ?? votesFinal.Yes ?? votesFinal.yes ?? 0); - if (VOTE_METHODS.has(String(p.method || '').toUpperCase()) && !(await passesThreshold(p.method, totalFinal, yesFinal))) { + for (const p of ctx.proposals.items.map(withCampaignRef)) { + if (p.termId !== termId || String(p.proposer) !== String(userId)) continue; + const status = String(p.status || 'OPEN').toUpperCase(); + if (status === 'ENACTED' || status === 'REJECTED' || status === 'DISCARDED') continue; + const outcome = await deriveOutcome(p, ctx); + if (outcome === 'REJECTED') { const rej = { ...stripId(p), replaces: p.id, status: 'REJECTED', updatedAt: nowISO() }; await publishMsg(rej); continue; } + if (outcome !== 'APPROVED') continue; + const snap = p.voteId ? await voteResult(p.voteId, p.proposer) : null; const law = { type: 'parliamentLaw', @@ -1125,7 +1250,7 @@ if (c.type === type) { proposer: p.proposer, termId: p.termId, voteId: p.voteId || null, - votes: votesFinal, + votes: votesOf(snap) || { YES: 1, NO: 0, ABSTENTION: 0, total: 1 }, proposedAt: p.createdAt, proposalId: p.id, enactedAt: nowISO() @@ -1136,17 +1261,24 @@ if (c.type === type) { await publishMsg(updated); } - const approvedRevs = revocations.filter(r => r.termId === termId && r.status === 'APPROVED'); - for (const r of approvedRevs) { - const tomb = { type: 'tombstone', target: r.lawId, deletedAt: nowISO(), author: userId }; - await publishMsg(tomb); + for (const r of ctx.revocations.items) { + if (r.termId !== termId || String(r.proposer) !== String(userId)) continue; + const status = String(r.status || 'OPEN').toUpperCase(); + if (status === 'ENACTED' || status === 'REJECTED' || status === 'DISCARDED') continue; + if ((await deriveOutcome(r, ctx, 'revocation')) !== 'APPROVED') continue; + const lawMsg = (ctx.msgs || []).find(m => m.key === r.lawId); + const lawValue = lawMsg && lawMsg.value; + if (lawValue && lawValue.author === userId && lawValue.content && lawValue.content.type === 'parliamentLaw') { + const tomb = { type: 'tombstone', target: r.lawId, deletedAt: nowISO(), author: userId }; + await publishMsg(tomb); + } - const snap = await voteSnapshot(r.voteId); + const snap = r.voteId ? await voteResult(r.voteId, r.proposer) : null; const updated = { ...stripId(r), replaces: r.id, status: 'ENACTED', - votes: (r.votes && Object.keys(r.votes).length ? r.votes : null) || snap || undefined, + votes: votesOf(snap) || undefined, updatedAt: nowISO() }; await publishMsg(updated); @@ -1162,7 +1294,10 @@ if (c.type === type) { try { await enactApprovedChanges(latestAny); } catch (e) { console.error('enactApprovedChanges failed:', e); } } - const open = await listCandidaturesOpen(); + const open = (await listCandidaturesOpen()).filter(c => + METHODS.includes(String(c.method || '').toUpperCase()) && + ((c.targetType === 'inhabitant' && FEED_ID_RE.test(String(c.targetId || ''))) || (c.targetType === 'tribe' && !!c.targetId)) + ); let chosen = null; let totalVotes = 0; let winnerVotes = 0; @@ -1184,6 +1319,7 @@ if (c.type === type) { const startAt = window.startAt; const endAt = window.endAt; const population = await inhabitantsCount(); + if (chosen && supportFor(await readLog())({ ...window, powerType: chosen.targetType, powerId: chosen.targetId, method: chosen.method }).votes < PROPOSAL_QUORUM) chosen = null; if (!chosen) { await archiveAllCandidatures(); @@ -1231,7 +1367,7 @@ if (c.type === type) { } async function getGovernmentCard() { - const term = await getCurrentTermBase(); + const term = (await getCurrentTermBase()) || virtualAnarchyTerm(); if (!term) return null; return await computeGovernmentCard({ ...term, id: term.id || term.startAt }); } @@ -1276,7 +1412,36 @@ if (c.type === type) { } async function listLaws() { - const items = await listByType('parliamentLaw'); + const ctx = await outcomeContext(); + const proposalsById = new Map(ctx.proposals.items.map(p => [p.id, p])); + const laws = chainIndex(ctx.msgs, 'parliamentLaw'); + const chosen = new Map(); + const groupOfLaw = new Map(); + for (const l of laws.items) { + const pid = ctx.proposals.resolve(String(l.proposalId || '')); + const p = pid ? proposalsById.get(pid) : null; + if (!p || String(p.proposer) !== String(l.proposer) || p.termId !== l.termId) continue; + const voted = withCampaignRef(p); + if (l.question !== voted.title || String(l.description || '') !== String(voted.description || '') || String(l.method || '') !== String(voted.method || '')) continue; + const term = termForId(p.termId, ctx.terms); + if (!term || term.virtual || !isExpiredTerm(term)) continue; + if ((await deriveOutcome(p, ctx)) !== 'APPROVED') continue; + groupOfLaw.set(l.id, pid); + const own = laws.authorOf(l.id) === p.proposer; + const prev = chosen.get(pid); + const better = !prev + || (own && !prev.own) + || (own === prev.own && (new Date(l.enactedAt).getTime() || 0) < (new Date(prev.law.enactedAt).getTime() || 0)); + if (better) chosen.set(pid, { law: l, own }); + } + for (const r of ctx.revocations.items) { + const pid = groupOfLaw.get(String(r.lawId || '')); + if (!pid || !chosen.has(pid)) continue; + const term = termForId(r.termId, ctx.terms); + if (!term || term.virtual || !isExpiredTerm(term)) continue; + if ((await deriveOutcome(r, ctx, 'revocation')) === 'APPROVED') chosen.delete(pid); + } + const items = [...chosen.values()].map(x => x.law); return items.sort((a, b) => new Date(b.enactedAt) - new Date(a.enactedAt)); } @@ -1308,12 +1473,19 @@ if (c.type === type) { const tribeReadLog = async () => { const client = await openSsb(); - return new Promise((resolve, reject) => { - pull( - client.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs || [])) - ); - }); + return readTyped(client, TRIBE_PARLIAMENT_TYPES, { limit: logLimit }); + }; + + const tribeMembersOf = async (ids) => { + const out = new Set(); + for (const id of ids) { + try { + const t = services.tribes && services.tribes.getTribeById ? await services.tribes.getTribeById(id) : null; + if (t && t.author) out.add(t.author); + for (const m of (t && Array.isArray(t.members) ? t.members : [])) out.add(m); + } catch (_) {} + } + return out; }; const tribeListByType = async (type, tribeId) => { @@ -1323,24 +1495,91 @@ if (c.type === type) { ? await services.tribes.getChainIds(tribeId) : [tribeId]; } catch (_) { chainIds = [tribeId]; } - const tribeIdSet = new Set(Array.isArray(chainIds) && chainIds.length ? chainIds : [tribeId]); + const ids = Array.isArray(chainIds) && chainIds.length ? chainIds : [tribeId]; + const tribeIdSet = new Set(ids); + const members = await tribeMembersOf(ids); const msgs = await tribeReadLog(); const tomb = buildValidatedTombstoneSet(msgs); - const replaced = new Set(); - const items = new Map(); + const roots = new Map(); + const rewrites = []; for (const m of msgs) { const c = m.value?.content; if (!c) continue; - if (c.type === 'tombstone') continue; if (c.type !== type) continue; if (!tribeIdSet.has(c.tribeId)) continue; - if (c.replaces) replaced.add(c.replaces); - items.set(m.key, { ...c, id: m.key, _ts: m.value?.timestamp || 0 }); + const author = m.value?.author; + if (!members.has(author)) continue; + const item = { ...c, id: m.key, author, _ts: m.value?.timestamp || 0 }; + if (c.replaces) { rewrites.push(item); continue; } + if (type === 'tribeParliamentCandidature' && c.proposer !== author) continue; + if (type === 'tribeParliamentTerm') { + if (c.createdBy && c.createdBy !== author) continue; + const method = String(c.method || '').toUpperCase(); + if (!METHODS.includes(method) && method !== 'ANARCHY') continue; + if (c.leaderId && !members.has(c.leaderId)) continue; + } + roots.set(m.key, item); } - return [...items.values()].filter(it => !tomb.has(it.id) && !replaced.has(it.id)); + if (type === 'tribeParliamentCandidature') { + const byId = new Map(rewrites.map(r => [r.id, r])); + const rootOfRewrite = (id) => { let cur = id, g = 0; while (byId.has(cur) && g++ < 1000) cur = byId.get(cur).replaces; return cur; }; + const ballotsOf = new Map(); + for (const r of rewrites) { + const root = roots.get(rootOfRewrite(r.id)); + if (!root || r.author === root.candidateId) continue; + if (!ballotsOf.has(root.id)) ballotsOf.set(root.id, []); + ballotsOf.get(root.id).push({ author: r.author, ts: r._ts }); + } + return [...roots.values()].map(it => ({ ...it, _ballots: ballotsOf.get(it.id) || [], _closed: tomb.has(it.id) })); + } + const replacedBySameAuthor = new Map(); + for (const r of rewrites) { const orig = roots.get(r.replaces); if (orig && orig.author === r.author) replacedBySameAuthor.set(r.replaces, r); } + const out = []; + for (const it of roots.values()) { + let cur = it, g = 0; + while (replacedBySameAuthor.has(cur.id) && g++ < 1000) cur = replacedBySameAuthor.get(cur.id); + if (!tomb.has(cur.id) && !tomb.has(it.id)) out.push(cur); + } + return out; + }; + + const tallyCandidatures = (cands, fromTs, toTs) => { + const createdOf = (c) => Date.parse(c.createdAt) || c._ts || 0; + const inCycle = cands.filter(c => createdOf(c) >= fromTs && createdOf(c) < toTs); + const ballots = []; + for (const c of inCycle) for (const b of (c._ballots || [])) if (b.ts >= fromTs && b.ts < toTs) ballots.push({ ...b, root: c.id }); + ballots.sort((a, b) => a.ts - b.ts); + const seen = new Set(); + const votersOf = new Map(); + for (const b of ballots) { + if (seen.has(b.author)) continue; + seen.add(b.author); + if (!votersOf.has(b.root)) votersOf.set(b.root, []); + votersOf.get(b.root).push(b.author); + } + return inCycle.map(c => { const { _ballots, _closed, ...rest } = c; const voters = votersOf.get(c.id) || []; return { ...rest, voters, votes: voters.length, ...(_closed ? { _closed } : {}) }; }); + }; + + const tribeValidTerms = async (tribeId) => { + const terms = (await tribeListByType('tribeParliamentTerm', tribeId)).slice().sort((a, b) => (Date.parse(a.startAt) || 0) - (Date.parse(b.startAt) || 0)); + if (!terms.some(t => t.leaderId)) return terms; + const cands = await tribeListByType('tribeParliamentCandidature', tribeId); + const quorum = await tribeElectionQuorum(tribeId); + const out = []; + for (const t of terms) { + const start = Date.parse(t.startAt) || 0; + if (t.leaderId) { + const prevStart = out.length ? (Date.parse(out[out.length - 1].startAt) || 0) : 0; + const tally = tallyCandidatures(cands, prevStart, start); + const backing = tally.filter(c => c.candidateId === t.leaderId).reduce((n, c) => Math.max(n, c.votes), 0); + if (backing < quorum) continue; + } + out.push(t); + } + return out; }; const tribeGetCurrentTerm = async (tribeId) => { - const terms = await tribeListByType('tribeParliamentTerm', tribeId); + const terms = await tribeValidTerms(tribeId); if (terms.length === 0) return null; const now = moment(); const active = terms.find(t => moment(t.startAt).isSameOrBefore(now) && moment(t.endAt).isAfter(now)); @@ -1388,11 +1627,7 @@ if (c.type === type) { const latest = await tribeGetCurrentTerm(tribeId); if (latest && !isExpiredTerm(latest)) return latest; const opens = (await tribeListCandidatures(tribeId)) - .filter(c => (c.status || 'OPEN') === 'OPEN') - .map(c => { - const voters = ensureArray(c.voters).filter(v => String(v) !== String(c.candidateId)); - return { ...c, voters, votes: voters.length }; - }); + .filter(c => (c.status || 'OPEN') === 'OPEN'); let chosen = null, totalVotes = 0, winnerVotes = 0; if (opens.length) { opens.sort((a, b) => Number(b.votes || 0) - Number(a.votes || 0) || new Date(a.createdAt) - new Date(b.createdAt)); @@ -1437,7 +1672,11 @@ if (c.type === type) { return await tribeResolveElection(tribeId); } - const tribeListCandidatures = (tribeId) => tribeListByType('tribeParliamentCandidature', tribeId); + const tribeListCandidatures = async (tribeId) => { + const cands = await tribeListByType('tribeParliamentCandidature', tribeId); + const term = await tribeGetCurrentTerm(tribeId); + return tallyCandidatures(cands, term ? (Date.parse(term.startAt) || 0) : 0, Infinity).filter(c => !c._closed); + }; const tribeListRules = (tribeId) => tribeListByType('tribeParliamentRule', tribeId); const tribePublishCandidature = async ({ tribeId, candidateId, method }) => { @@ -1532,6 +1771,7 @@ if (c.type === type) { getPublishedTerm, getLatestTerm, getLatestGovernmentCard, + getHemicycle, listLeaders, sweepProposals, getActorMeta, @@ -1560,21 +1800,13 @@ if (c.type === type) { function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } -function compareTermsForWindow(a, b) { - const num = (t, field) => Number(t && t[field]) || 0; +function compareTermsForWindow(a, b, supportOf = () => 0) { + const support = (t) => Number(supportOf(t)) || 0; const anarchy = (t) => (String(t && t.method || '').toUpperCase() === 'ANARCHY' ? 1 : 0); - const populationA = num(a, 'population'); - const populationB = num(b, 'population'); - if (populationA !== populationB) return populationB - populationA; - - const turnoutA = num(a, 'totalVotes'); - const turnoutB = num(b, 'totalVotes'); - if (turnoutA !== turnoutB) return turnoutB - turnoutA; - - const votesA = num(a, 'winnerVotes'); - const votesB = num(b, 'winnerVotes'); - if (votesA !== votesB) return votesB - votesA; + const supportA = support(a); + const supportB = support(b); + if (supportA !== supportB) return supportB - supportA; const anarchyA = anarchy(a); const anarchyB = anarchy(b); @@ -1583,7 +1815,7 @@ function compareTermsForWindow(a, b) { return String(a && a.id || '').localeCompare(String(b && b.id || '')); } -function collapseOverlappingTerms(terms = []) { +function collapseOverlappingTerms(terms = [], supportOf) { if (!terms.length) return []; const sorted = [...terms].sort((a, b) => new Date(a.startAt) - new Date(b.startAt)); const groups = []; @@ -1603,7 +1835,7 @@ function collapseOverlappingTerms(terms = []) { if (!placed) groups.push({ items: [t], minStart: tStart, maxEnd: tEnd }); } const winners = groups.map(g => { - g.items.sort(compareTermsForWindow); + g.items.sort((a, b) => compareTermsForWindow(a, b, supportOf)); return g.items[0]; }); return winners.sort((a, b) => new Date(b.startAt) - new Date(a.startAt)); @@ -1613,3 +1845,5 @@ module.exports.termWindowFor = termWindowFor; module.exports.compareTermsForWindow = compareTermsForWindow; module.exports.collapseOverlappingTerms = collapseOverlappingTerms; module.exports.TERM_DAYS = TERM_DAYS; + +module.exports.hemicycle = { build: buildHemicycle, seatPositions, passThreshold }; diff --git a/src/models/peer_health.js b/src/models/peer_health.js new file mode 100644 index 0000000..48fda93 --- /dev/null +++ b/src/models/peer_health.js @@ -0,0 +1,103 @@ +const fs = require('fs'); + +const FAILURES_MIN = 3; +const DEAD_AFTER_MS = 7 * 24 * 60 * 60 * 1000; +const RETRY_CAP_MS = 3 * 60 * 60 * 1000; +const FEED_ID = /^@[A-Za-z0-9+/]{43}=\.ed25519$/; + +const healthPath = () => require('../configs/state-manager').statePath('peer-health.json'); +let cache = null; +let cachePath = null; + +const canonicalKey = (key) => { + let k = String(key || '').trim().replace(/-/g, '+').replace(/_/g, '/'); + if (!k) return null; + if (!k.startsWith('@')) k = '@' + k; + if (!k.endsWith('.ed25519')) k += '.ed25519'; + if (!/=\.ed25519$/.test(k)) k = k.replace(/\.ed25519$/, '=.ed25519'); + return FEED_ID.test(k) ? k : null; +}; + +const load = () => { + const p = healthPath(); + if (!cache || cachePath !== p) { + cachePath = p; + try { cache = JSON.parse(fs.readFileSync(p, 'utf8')) || {}; } catch (_) { cache = {}; } + if (typeof cache !== 'object' || Array.isArray(cache)) cache = {}; + } + return cache; +}; + +const save = () => { try { fs.writeFileSync(cachePath, JSON.stringify(cache)); } catch (_) {} }; + +const observe = ({ connected = [], failing = [], now = Date.now() } = {}) => { + const h = load(); + let changed = false; + const up = new Set(connected.map(canonicalKey).filter(Boolean)); + for (const id of up) { + if (h[id]) { delete h[id]; changed = true; } + } + for (const k of failing) { + const id = canonicalKey(k); + if (!id || up.has(id) || h[id]) continue; + h[id] = { failingSince: now }; + changed = true; + } + if (changed) save(); + return changed; +}; + +const isDead = ({ key, failures = 0, now = Date.now() } = {}) => { + if (!(Number(failures) >= FAILURES_MIN)) return false; + const e = load()[canonicalKey(key)] || {}; + const observed = Number(e.failingSince) ? now - Number(e.failingSince) : 0; + return Math.max(observed, Number(failures) * RETRY_CAP_MS) >= DEAD_AFTER_MS; +}; + +const addressOf = (item) => { + const m = Array.isArray(item) ? String(item[0] || '').match(/^(?:net|onion):([^:~]+):(\d+)/) : null; + if (m) return `${m[1]}:${m[2]}`; + const data = Array.isArray(item) ? item[1] : item; + return data && data.host ? `${data.host}:${Number(data.port) || 8008}` : null; +}; +const failuresByKey = (...sources) => { + const byKey = new Map(); + for (const list of sources) { + for (const item of list || []) { + const data = Array.isArray(item) ? item[1] : item; + const id = data && canonicalKey(data.key); + if (!id) continue; + const f = Number(data.failure) || 0; + const e = byKey.get(id) || { any: 0, addrs: new Map() }; + const where = addressOf(item); + if (where) e.addrs.set(where, Math.max(e.addrs.get(where) || 0, f)); + else e.any = Math.max(e.any, f); + byKey.set(id, e); + } + } + const out = new Map(); + for (const [id, e] of byKey) out.set(id, e.addrs.size ? Math.min(...[...e.addrs.values()].map(f => Math.max(f, e.any))) : e.any); + return out; +}; + +const classifyNetError = (err, address = '') => { + const msg = `${(err && err.code) || ''} ${(err && err.message) || err || ''}`.toLowerCase(); + const onion = /\.onion/i.test(String(address || '')); + if (msg.includes('network paused')) return 'paused'; + if (onion && /only know|could not connect to/.test(msg)) return 'tor'; + if (/127\.0\.0\.1:9[01]50/.test(msg) || (onion && /econnrefused|socks/.test(msg) && !/ttl|hostunreachable|host unreachable|general/.test(msg))) return 'tor'; + if (/shs\.client: (error when expecting server to accept challenge|server responded with invalid challenge)|application cap/.test(msg)) return 'keys'; + if (/shs\.client: (server hung up when we sent hello|the server's response accepting)/.test(msg)) return 'identity'; + if (/econnrefused/.test(msg)) return 'refused'; + if (/enotfound|eai_again|getaddrinfo/.test(msg)) return 'notfound'; + if (/enetunreach|ehostunreach|ehostdown|enetdown|hostunreachable|host unreachable|networkunreachable/.test(msg)) return 'unreachable'; + if (/timed? ?out|etimedout|ttl/.test(msg)) return 'timeout'; + if (/econnreset|epipe|hung up|aborted|closed/.test(msg)) return 'dropped'; + return 'other'; +}; +const NET_REASON_KEYS = { + paused: 'peerErrPaused', tor: 'peerErrTor', keys: 'peerErrKeys', identity: 'peerErrIdentity', refused: 'peerErrRefused', + notfound: 'peerErrNotFound', unreachable: 'peerErrUnreachable', timeout: 'peerErrTimeout', dropped: 'peerErrDropped', other: 'peerErrOther', silent: 'peerErrSilent' +}; + +module.exports = { canonicalKey, observe, isDead, failuresByKey, classifyNetError, NET_REASON_KEYS, DEAD_AFTER_MS }; diff --git a/src/models/phone_model.js b/src/models/phone_model.js new file mode 100644 index 0000000..4dabb6b --- /dev/null +++ b/src/models/phone_model.js @@ -0,0 +1,325 @@ +const fs = require('fs'); +const pull = require('../server/node_modules/pull-stream'); +const sharedState = require('../configs/shared-state'); +const { notify: desktopNotify, i18nNow, displayName } = require('../backend/desktopNotify'); +const { statePath } = require('../configs/state-manager'); + +const HISTORY_MAX = 200; +const HEARD_MAX = 1000; +const LATEST_MAX = 5; +const PUBS_TTL_MS = 5 * 60 * 1000; +const RECOUNT_DELAY_MS = 500; +const FEED_ID = /^@[A-Za-z0-9+/]{43}=\.ed25519$/; + +const readJson = (p, fallback) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return fallback; } }; +const writeJson = (p, data) => { try { fs.writeFileSync(p, JSON.stringify(data, null, 2)); } catch (_) {} }; +const asP = (fn, ...args) => new Promise((resolve, reject) => fn(...args, (err, v) => err ? reject(err) : resolve(v))); +const rpcValue = (fn, ...args) => new Promise((resolve) => { + try { + const r = fn(...args, (err, v) => resolve(err ? null : v)); + if (r && typeof r.then === 'function') r.then(resolve, () => resolve(null)); + else if (r !== undefined) resolve(r); + } catch (_) { resolve(null); } +}); + +module.exports = ({ cooler, pmModel, nameOf, isPublic, encryptFile, isAvailable, prefetch }) => { + const historyFile = statePath('phone-history.json'); + const seenFile = statePath('phone-seen.json'); + let ssb = null; + let subscribed = false; + let pubsAt = 0; + let pubsTimer = null; + + const open = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + const phone = async () => { const s = await open(); return s && s.phone ? s.phone : null; }; + const history = () => { const h = readJson(historyFile, []); return Array.isArray(h) ? h : []; }; + const seen = () => { + const s = readJson(seenFile, {}); + return { missedSeenAt: Number(s.missedSeenAt) || 0, heard: Array.isArray(s.heard) ? s.heard : [] }; + }; + + const refreshCount = async () => { + let n = 0; + const st = sharedState.getPhoneCall ? sharedState.getPhoneCall() : null; + if (st && st.dir === 'in' && st.phase === 'incoming') n += 1; + const sn = seen(); + n += history().filter(h => h && h.outcome === 'missed' && h.endedAt > sn.missedSeenAt).length; + try { + const me = (await open()).id; + const heard = new Set(sn.heard); + n += (await pmModel.listPams()).filter(m => m.value.author !== me && !heard.has(m.key)).length; + } catch (_) {} + sharedState.setPhoneCount(n); + return n; + }; + + const notify = async (peer, callId) => { + const i18n = i18nNow(); + let name = ''; + try { name = await nameOf(peer); } catch (_) {} + desktopNotify(i18n.phoneTitle, `${displayName(peer, name)} ${i18n.phoneIsCalling}`, callId ? `call:${callId}` : null, { + timeoutMs: require('../server/phone_module').RING_MS, + actions: [{ key: 'answer', label: i18n.phoneAnswer }, { key: 'reject', label: i18n.phoneReject }], + onAction: async (chosen) => { + try { + const ph = await phone(); + const st = ph ? await rpcValue(ph.state) : null; + if (!st || st.id !== callId || st.phase !== 'incoming') return; + await act(chosen === 'answer' ? 'accept' : 'reject'); + } catch (_) {} + } + }); + }; + + const ROOM_EVENT_TEXT = { join: 'roomEventJoined', leave: 'roomEventLeft', recStart: 'roomEventRecStart', recStop: 'roomEventRecStop', mute: 'roomEventMuted', unmute: 'roomEventUnmuted', hand: 'roomEventHand' }; + const roomNotices = { rid: null, since: 0, seen: new Set() }; + const announceRoom = async (room) => { + if (!room || !room.rid) { roomNotices.rid = null; roomNotices.seen = new Set(); return; } + if (roomNotices.rid !== room.rid) { roomNotices.rid = room.rid; roomNotices.since = Number(room.joinedAt) || Date.now(); roomNotices.seen = new Set(); } + if (!room.notify) { roomNotices.since = Date.now() + 1; return; } + const events = Array.isArray(room.events) ? room.events : []; + const keyOf = (e) => `${e.ts}:${e.t}:${e.id}`; + const fresh = events.filter(e => e && ROOM_EVENT_TEXT[e.t] && !roomNotices.seen.has(keyOf(e)) && Number(e.ts) >= roomNotices.since); + roomNotices.seen = new Set(events.filter(Boolean).map(keyOf)); + if (!fresh.length) return; + let me = null; + try { me = (await open()).id; } catch (_) {} + const i18n = i18nNow(); + for (const e of fresh) { + if (e.id === me) continue; + let name = ''; + try { name = await nameOf(e.id); } catch (_) {} + desktopNotify(i18n.roomsTitle, String(i18n[ROOM_EVENT_TEXT[e.t]] || '{name}').replace('{name}', displayName(e.id, name))); + } + }; + + const subscribe = async () => { + if (subscribed) return; + const ph = await phone(); + if (!ph || subscribed) return; + subscribed = true; + pull(ph.events(), pull.drain((ev) => { + if (!ev) return; + if (ev.type === 'state') { + sharedState.setPhoneCall(ev.state || null); + refreshCount(); + } else if (ev.type === 'room') { + sharedState.setPhoneRoom(ev.room || null); + announceRoom(ev.room).catch(() => {}); + } else if (ev.type === 'recordLimit') { + finishPam().catch(() => {}); + } else if (ev.type === 'incoming') { + notify(ev.peer, ev.id); + } else if (ev.type === 'ended' && ev.call) { + const h = history(); + h.unshift(ev.call); + writeJson(historyFile, h.slice(0, HISTORY_MAX)); + refreshCount(); + } + }, () => { + subscribed = false; + const retry = setTimeout(() => { subscribe().catch(() => {}); }, 5000); + if (retry.unref) retry.unref(); + })); + try { sharedState.setPhoneCall((await rpcValue(ph.state)) || null); } catch (_) {} + try { if (typeof ph.roomState === 'function') sharedState.setPhoneRoom((await rpcValue(ph.roomState)) || null); } catch (_) {} + }; + + let arrivalsWatched = false; + let recountTimer = null; + const scheduleRecount = () => { + if (recountTimer) return; + recountTimer = setTimeout(() => { recountTimer = null; refreshCount().catch(() => {}); }, RECOUNT_DELAY_MS); + if (recountTimer.unref) recountTimer.unref(); + }; + const watchArrivals = async () => { + if (arrivalsWatched) return; + const s = await open(); + if (!s || !s.db || typeof s.db.onMsgAdded !== 'function') return; + arrivalsWatched = true; + s.db.onMsgAdded((ev) => { + const v = ev && ev.kvt && ev.kvt.value; + if (!v || v.author === s.id || typeof v.content !== 'string') return; + scheduleRecount(); + }, false); + }; + + const act = async (method, ...args) => { + await subscribe(); + const ph = await phone(); + if (!ph) throw new Error('unavailable'); + return asP(ph[method], ...args); + }; + + const finishPam = async () => { + const ph = await phone(); + if (!ph) return false; + const out = await asP(ph.recordStop); + if (!out || !out.file) return false; + try { + const enc = await encryptFile(out.file); + await pmModel.sendPam(out.peer, enc.cipher, Number(out.durationMs) / 1000); + return true; + } finally { + fs.unlink(out.file, () => {}); + } + }; + + const findPam = async (key) => (await pmModel.listPams()).find(m => m.key === key) || null; + + const byType = (s, type) => new Promise((resolve) => { + if (!s || typeof s.messagesByType !== 'function') return resolve([]); + try { pull(s.messagesByType({ type }), pull.collect((err, msgs) => resolve(err ? [] : msgs))); } catch (_) { resolve([]); } + }); + const defaultPubId = () => { + const m = String((require('../configs/config-manager').defaultInvite() || {}).code || '').match(/(@[A-Za-z0-9+/]{43}=\.ed25519)/); + return m ? m[1] : ''; + }; + let announcedPubs = new Set(); + const refreshPubs = async (force = false) => { + const ids = new Set(); + const add = (id) => { if (FEED_ID.test(String(id || ''))) ids.add(String(id)); }; + try { + const s = await open(); + if (force || Date.now() - pubsAt >= PUBS_TTL_MS) { + pubsAt = Date.now(); + const found = new Set(); + for (const m of await byType(s, 'pub')) { const v = m && m.value; const k = v && v.content && v.content.address && v.content.address.key; if (FEED_ID.test(String(k || '')) && (v.author === k || v.author === s.id)) found.add(String(k)); } + for (const m of await byType(s, 'pubAvailability')) { const k = m && m.value && m.value.author; if (FEED_ID.test(String(k || ''))) found.add(String(k)); } + announcedPubs = found; + } + add(defaultPubId()); + for (const id of announcedPubs) add(id); + try { const ph = s.phone; if (ph && typeof ph.pubs === 'function') for (const id of (await rpcValue(ph.pubs)) || []) add(id); } catch (_) {} + ids.delete(s.id); + } catch (_) { add(defaultPubId()); } + sharedState.setPubIds(ids); + return ids; + }; + const isPub = (id) => sharedState.isPubId(id); + + return { + async start() { + await subscribe(); + await watchArrivals().catch(() => {}); + await refreshCount(); + await refreshPubs(true); + if (!pubsTimer) { pubsTimer = setInterval(() => { refreshPubs(true).catch(() => {}); }, PUBS_TTL_MS); if (pubsTimer.unref) pubsTimer.unref(); } + }, + refreshCount, + refreshPubs, + isPub, + async available() { + if (isPublic) return false; + const ph = await phone(); + return !!(ph && await rpcValue(ph.available)); + }, + async state() { + await subscribe(); + const ph = await phone(); + return ph ? rpcValue(ph.state) : null; + }, + async call(to, opts = {}) { + await refreshPubs(); + const list = Array.isArray(to) ? to : [to]; + if (list.some(isPub)) throw new Error('pub'); + return act('call', list.length === 1 ? list[0] : list, { legacy: Array.isArray(opts.legacy) ? opts.legacy : [] }); + }, + accept: () => act('accept'), + reject: () => act('reject'), + hangup: () => act('end'), + mute: (flag) => act('mute', !!flag), + camera: (flag) => act('camera', !!flag), + async videoFrames(peer) { + const ph = await phone(); + return ph && typeof ph.videoFrames === 'function' ? ph.videoFrames({ peer: String(peer || '') }) : null; + }, + silence: (id, flag) => act('silence', { id, on: !!flag }), + dismiss: () => act('dismiss'), + recordCancel: () => act('recordCancel'), + async sendPam() { await subscribe(); return finishPam(); }, + history, + async markAllHeard(keys) { + const s = seen(); + const set = new Set(s.heard); + for (const k of keys || []) if (typeof k === 'string' && k) set.add(k); + s.heard = [...set].slice(-HEARD_MAX); + writeJson(seenFile, s); + refreshCount(); + }, + deleteHistory(ids) { + const drop = new Set((ids || []).map(String)); + if (!drop.size) return; + writeJson(historyFile, history().filter(h => !drop.has(String(h && h.id)))); + }, + latestCalls(items) { + const me = ssb && ssb.id; + const out = []; + const seenPeers = new Set(); + for (const h of items || history()) { + for (const peer of (Array.isArray(h.peers) && h.peers.length ? h.peers : [h.peer])) { + if (!peer || peer === me || seenPeers.has(peer) || isPub(peer)) continue; + seenPeers.add(peer); + out.push({ id: peer, dir: h.dir, outcome: h.outcome, startedAt: h.startedAt }); + if (out.length >= LATEST_MAX) return out; + } + } + return out; + }, + async pams() { + const me = (await open()).id; + const heard = new Set(seen().heard); + const validShare = (s) => !!s && typeof s === 'object' && typeof s.key === 'string' && typeof s.manifestBlobId === 'string'; + const list = (await pmModel.listPams()) + .filter(m => m.value.author !== me && validShare(m.value.content && m.value.content.share)) + .map(m => ({ key: m.key, from: m.value.author, sentAt: m.value.content.sentAt || new Date(m.timestamp || 0).toISOString(), durationSec: Number(m.value.content.durationSec) || 0, heard: heard.has(m.key), share: m.value.content.share, ready: true })) + .sort((a, b) => String(b.sentAt).localeCompare(String(a.sentAt))); + if (typeof isAvailable === 'function') { + for (const item of list) { + item.ready = await isAvailable(item.share).catch(() => false); + if (!item.ready && typeof prefetch === 'function') prefetch(item.share).catch(() => {}); + } + } + return list.map(({ share, ...rest }) => rest); + }, + async pamCipher(key) { + const m = await findPam(key); + const share = m && m.value && m.value.content && m.value.content.share; + if (!share || typeof share.key !== 'string' || typeof share.manifestBlobId !== 'string') return null; + return share; + }, + markHeard(key) { + const s = seen(); + if (s.heard.includes(key)) return false; + s.heard.push(key); + s.heard = s.heard.slice(-HEARD_MAX); + writeJson(seenFile, s); + refreshCount(); + return true; + }, + async deletePam(key) { + const m = await findPam(key); + if (!m) return null; + return pmModel.deleteMessageById(key); + }, + async knownFeeds() { + const s = await open(); + await refreshPubs(); + const hops = await new Promise((resolve) => { + try { s.friends.hops({ start: s.id, max: 2 }, (err, h) => resolve(err ? {} : (h || {}))); } catch (_) { resolve({}); } + }); + const out = new Map(); + for (const [id, hop] of Object.entries(hops)) { + if (id === s.id || isPub(id) || !(Number(hop) >= 0)) continue; + out.set(id, Number(hop)); + } + return out; + }, + markMissedSeen() { + const s = seen(); + s.missedSeenAt = Date.now(); + writeJson(seenFile, s); + refreshCount(); + } + }; +}; diff --git a/src/models/phone_number.js b/src/models/phone_number.js new file mode 100644 index 0000000..c15a46e --- /dev/null +++ b/src/models/phone_number.js @@ -0,0 +1,28 @@ +const crypto = require('crypto'); + +const FEED_ID = /^@[A-Za-z0-9+/]{43}=\.ed25519$/; +const MSG_ID = /^%[A-Za-z0-9+/]{43}=?\.sha256$/; + +const digitsOf = (id) => { + const n = crypto.createHash('sha256').update(String(id)).digest().readUInt32BE(0) % 1000000; + const s = String(n).padStart(6, '0'); + return `${s.slice(0, 3)}-${s.slice(3)}`; +}; +const phoneNumberOf = (id) => (FEED_ID.test(String(id || '')) ? digitsOf(id) : null); +const roomNumberOf = (id) => (MSG_ID.test(String(id || '')) ? digitsOf(id) : null); + +const normalizeNumber = (value) => { + const digits = String(value || '').replace(/[^0-9A-Za-z]/g, ''); + return /^\d{6}$/.test(digits) ? `${digits.slice(0, 3)}-${digits.slice(3)}` : null; +}; + +const resolveNumber = (value, known) => { + const number = normalizeNumber(value); + if (!number) return []; + return [...(known || new Map())] + .filter(([id]) => phoneNumberOf(id) === number) + .map(([id, hop]) => ({ id, hop: Number(hop) })) + .sort((a, b) => a.hop - b.hop); +}; + +module.exports = { phoneNumberOf, roomNumberOf, normalizeNumber, resolveNumber }; diff --git a/src/models/pixelia_model.js b/src/models/pixelia_model.js index 1b14854..245cc18 100644 --- a/src/models/pixelia_model.js +++ b/src/models/pixelia_model.js @@ -1,8 +1,49 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped } = require('./typed_log'); const logLimit = getConfig().ssbLogStream?.limit || 1000; +const PIXELIA_TYPES = ['pixelia', 'tombstone']; +const COLOR_RE = /^#[0-9a-fA-F]{6}$/; + +const pixelCoordinate = (c) => { + if (!Number.isInteger(c.x) || !Number.isInteger(c.y) || c.x < 0 || c.x > 49 || c.y < 0 || c.y > 199) return null; + const key = `${c.x + 1}:${c.y + 1}`; + if (c.coordinateKey != null && c.coordinateKey !== key) return null; + return key; +}; + +const resolvePixels = (messages) => { + const tombstoned = buildValidatedTombstoneSet(messages); + const byKey = new Map(); + + for (const m of messages) { + const c = m.value?.content; + if (!c || c.type !== 'pixelia' || !m.key) continue; + const coordinateKey = pixelCoordinate(c); + if (!coordinateKey || typeof c.color !== 'string' || !COLOR_RE.test(c.color)) continue; + byKey.set(m.key, { m, coordinateKey }); + } + + const replaced = new Set(); + for (const { m, coordinateKey } of byKey.values()) { + const prev = byKey.get(m.value.content.replaces); + if (prev && prev.coordinateKey === coordinateKey) replaced.add(prev.m.key); + } + + const latest = new Map(); + for (const [k, { m, coordinateKey }] of byKey) { + if (replaced.has(k) || tombstoned.has(k)) continue; + const cur = latest.get(coordinateKey); + const ts = m.value.timestamp || 0; + const curTs = cur ? cur.value.timestamp || 0 : 0; + if (!cur || ts > curTs || (ts === curTs && k > cur.key)) latest.set(coordinateKey, m); + } + + return latest; +}; + module.exports = ({ cooler }) => { let ssb; @@ -13,52 +54,22 @@ module.exports = ({ cooler }) => { const getPixelByCoordinate = async (coordinateKey) => { const ssbClient = await openSsb(); - const messages = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); - - const tombstoned = buildValidatedTombstoneSet(messages); - - const replaces = new Map(); - const byId = new Map(); - - for (const m of messages) { - const c = m.value?.content; - const k = m.key; - if (!c || c.type !== 'pixelia' || c.coordinateKey !== coordinateKey) continue; - if (tombstoned.has(k)) continue; - if (c.replaces) replaces.set(c.replaces, k); - byId.set(k, m); - } - - for (const [replacedId, replacingId] of replaces.entries()) { - const orig = byId.get(replacedId); - if (!orig) continue; - const rep = byId.get(replacingId); - if (!rep || rep.value.author !== orig.value.author) { - byId.delete(replacingId); - continue; - } - byId.delete(replacedId); - } - - return [...byId.values()][0] || null; + const messages = await readTyped(ssbClient, PIXELIA_TYPES, { limit: logLimit }); + return resolvePixels(messages).get(coordinateKey) || null; }; const paintPixel = async (x, y, color) => { - if (x < 1 || x > 50 || y < 1 || y > 200) { + if (!Number.isInteger(x) || !Number.isInteger(y) || x < 1 || x > 50 || y < 1 || y > 200) { throw new Error('Coordinates out of bounds. Please use x (1-50) and y (1-200)'); } + if (typeof color !== 'string' || !COLOR_RE.test(color)) throw new Error('Invalid color'); const ssbClient = await openSsb(); const userId = ssbClient.id; const coordinateKey = `${x}:${y}`; const existingPixel = await getPixelByCoordinate(coordinateKey); - if (existingPixel) { + if (existingPixel && existingPixel.value.author === userId) { const tombstone = { type: 'tombstone', target: existingPixel.key, @@ -69,7 +80,7 @@ module.exports = ({ cooler }) => { ); } - const contributors = existingPixel?.value?.content?.contributors_inhabitants || []; + const contributors = Array.isArray(existingPixel?.value?.content?.contributors_inhabitants) ? existingPixel.value.content.contributors_inhabitants : []; const contributors_inhabitants = contributors.includes(userId) ? contributors : [...contributors, userId]; @@ -93,46 +104,14 @@ module.exports = ({ cooler }) => { const listPixels = async () => { const ssbClient = await openSsb(); - const messages = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); + const messages = await readTyped(ssbClient, PIXELIA_TYPES, { limit: logLimit }); - const tombstoned = buildValidatedTombstoneSet(messages); - const replaces = new Map(); - const byKey = new Map(); - - for (const m of messages) { - const c = m.value?.content; - const k = m.key; - if (!c) continue; - if (c.type === 'tombstone') continue; - if (c.type === 'pixelia') { - if (tombstoned.has(k)) continue; - if (c.replaces) replaces.set(c.replaces, k); - byKey.set(k, m); - } - } - - for (const [replacedId, replacingId] of replaces.entries()) { - const orig = byKey.get(replacedId); - if (!orig) continue; - const rep = byKey.get(replacingId); - if (!rep || rep.value.author !== orig.value.author) { - byKey.delete(replacingId); - continue; - } - byKey.delete(replacedId); - } - - return Array.from(byKey.values()).map(m => ({ + return Array.from(resolvePixels(messages).values()).map(m => ({ x: m.value.content.x + 1, y: m.value.content.y + 1, color: m.value.content.color, author: m.value.author, - contributors_inhabitants: m.value.content.contributors_inhabitants || [], + contributors_inhabitants: [...new Set([...(Array.isArray(m.value.content.contributors_inhabitants) ? m.value.content.contributors_inhabitants : []).filter(id => typeof id === 'string'), m.value.author])], timestamp: m.value.timestamp })); }; diff --git a/src/models/pm_model.js b/src/models/pm_model.js index 2fe5b46..3bbd6c1 100644 --- a/src/models/pm_model.js +++ b/src/models/pm_model.js @@ -1,5 +1,89 @@ const pull = require('../server/node_modules/pull-stream'); -const util = require('../server/node_modules/util'); +const util = require('util'); +const longText = require('../backend/long_text'); + +const fs = require('fs'); + +const INBOX_READ_FILE = 'oasis-inbox-read'; +const INBOX_ARCHIVE_FILE = 'oasis-inbox-archived'; +const flagPath = (name) => { + try { return require('../configs/state-manager').statePath(name); } catch (_) { return null; } +}; +const readFlagSet = (name) => { + const file = flagPath(name); + if (!file) return new Set(); + try { const arr = JSON.parse(fs.readFileSync(file, 'utf8')); return new Set(Array.isArray(arr) ? arr.map(String) : []); } catch (_) { return new Set(); } +}; +const writeFlagSet = (name, set) => { + const file = flagPath(name); + if (!file) return false; + try { fs.writeFileSync(file, JSON.stringify(Array.from(set))); return true; } catch (_) { return false; } +}; +const readKeys = () => readFlagSet(INBOX_READ_FILE); +const markRead = (key) => { if (!key) return false; const set = readKeys(); set.add(String(key)); return writeFlagSet(INBOX_READ_FILE, set); }; +const markUnread = (key) => { if (!key) return false; const set = readKeys(); set.delete(String(key)); return writeFlagSet(INBOX_READ_FILE, set); }; +const markReadMany = (keys) => { const set = readKeys(); for (const k of (keys || [])) { if (k) set.add(String(k)); } return writeFlagSet(INBOX_READ_FILE, set); }; +const archivedKeys = () => readFlagSet(INBOX_ARCHIVE_FILE); +const archive = (key) => { if (!key) return false; const set = archivedKeys(); set.add(String(key)); return writeFlagSet(INBOX_ARCHIVE_FILE, set); }; +const unarchive = (key) => { if (!key) return false; const set = archivedKeys(); set.delete(String(key)); return writeFlagSet(INBOX_ARCHIVE_FILE, set); }; + +const INBOX_BOTS = { + blogs: ['BLOG_NEW'], + jobs: ['JOB_MATCH', 'JOB_SUBSCRIBED', 'JOB_UNSUBSCRIBED'], + projects: ['PROJECT_FOLLOWED', 'PROJECT_UNFOLLOWED', 'PROJECT_PLEDGE'], + market: ['MARKET_SOLD'], + shops: ['SHOP_SOLD'], + political: ['LARP_RULING', 'PARLIAMENT_GOV', 'TRIBE_GOV'], + banking: ['BANKING_UBI_PAID', 'BANKING_UBI_AVAILABLE', 'BANKING_CONFIRM_PENDING', 'WALLET_PAYMENT'], + school: ['SCHOOL_ENROLLED', 'SCHOOL_INVITED', 'SCHOOL_ADMITTED', 'SCHOOL_CERTIFICATE', 'SCHOOL_PASSED', 'SCHOOL_LESSON_NEW'], + industry: ['INDUSTRY_ADMITTED', 'INDUSTRY_APPLICATION', 'INDUSTRY_INVITED', 'INDUSTRY_DISSOLVED', 'INDUSTRY_BUILD_APPROVED', 'INDUSTRY_DISTRIBUTED'], + housing: ['HOUSING_REQUESTED', 'HOUSING_CANCELLED', 'HOUSING_UNAVAILABLE'], + wiki: ['WIKI_EDITED', 'WIKI_RESTORED'], + emergencies: ['EMERGENCY_UPDATED', 'EMERGENCY_RESOLVED'], + podcasts: ['PODCAST_EPISODE'], + campaigns: ['CAMPAIGN_UPDATED', 'CAMPAIGN_ACHIEVED', 'CAMPAIGN_RAISED'], + logistics: ['LOGISTICS_UPDATED', 'LOGISTICS_CLOSED', 'LOGISTICS_BOOKED', 'LOGISTICS_CANCELLED', 'LOGISTICS_CONFIRMED', 'LOGISTICS_REJECTED', 'LOGISTICS_DELIVERED'] +}; +const BOT_BY_SUBJECT = new Map(); +for (const [bot, subjects] of Object.entries(INBOX_BOTS)) for (const s of subjects) BOT_BY_SUBJECT.set(s, bot); +const isReminderSubject = (s) => /^(Task Reminder:|Calendar Reminder:)/i.test(String(s || '')); +const botOf = (content) => { + const c = content || {}; + if (isReminderSubject(c.subject)) return 'reminders'; + if (c.meta && c.meta.type === 'project-pledge') return 'projects'; + return BOT_BY_SUBJECT.get(String(c.subject || '').toUpperCase()) || null; +}; +const mutedBots = (cfg) => { + const c = cfg || {}; + return new Set(Array.isArray(c.inboxMutedBots) ? c.inboxMutedBots.map(String) : []); +}; + +const privateCaches = new WeakMap(); +const privateCacheFor = (ssb, userId) => { + let byUser = privateCaches.get(ssb); + if (!byUser) { byUser = new Map(); privateCaches.set(ssb, byUser); } + let c = byUser.get(userId); + if (!c) { + c = { lastTs: 0, seen: new Set(), posts: new Map(), pams: new Map(), chunks: new Map(), tombClaims: new Map(), authorByKey: new Map(), recpsByKey: new Map(), version: 0 }; + byUser.set(userId, c); + } + return c; +}; + +const tombedKeys = (cache, userId) => { + const tombed = new Set(); + for (const [target, tombAuthors] of cache.tombClaims.entries()) { + const origAuthor = cache.authorByKey.get(target); + const origRecps = cache.recpsByKey.get(target) || []; + for (const tombAuthor of tombAuthors) { + if (tombAuthor === origAuthor || tombAuthor === userId || origRecps.includes(tombAuthor)) { + tombed.add(target); + break; + } + } + } + return tombed; +}; module.exports = ({ cooler }) => { let ssb; @@ -26,12 +110,35 @@ module.exports = ({ cooler }) => { } return { + readKeys, + markRead, + markUnread, + markReadMany, + archivedKeys, + archive, + unarchive, + botOf, + mutedBots, + INBOX_BOTS, type: 'post', + async sendToMany(recipients = [], subject = '', text = '', crypter = false, ref = '') { + const others = uniqueRecps(recipients).filter(id => id !== userId); + if (!others.length) return []; + const out = []; + for (let i = 0; i < others.length; i += 6) { + const batch = others.slice(i, i + 6); + const r = await this.sendMessage(batch, subject, text, crypter, ref).catch(() => null); + if (r) out.push(r); + } + return out; + }, + async sendMessage(recipients = [], subject = '', text = '', crypter = false, ref = '') { const ssbClient = await openSsb(); const recps = uniqueRecps([userId, ...recipients]); - const content = { + const publishAsync = util.promisify(ssbClient.private.publish); + const content = await longText.chunkContent({ type: 'post', from: userId, to: recps, @@ -41,8 +148,7 @@ module.exports = ({ cooler }) => { private: true, ...(ref ? { ref: String(ref) } : {}), ...(crypter ? { crypter: true } : {}) - }; - const publishAsync = util.promisify(ssbClient.private.publish); + }, 'text', { maxBytes: longText.CONTENT_BYTES.boxed, extra: { private: true }, publish: (c) => publishAsync(c, recps) }); return publishAsync(content, recps); }, @@ -119,20 +225,20 @@ module.exports = ({ cooler }) => { async listAllPrivate(opts = {}) { const includeDeleted = !!(opts && opts.includeDeleted); const ssbClient = await openSsb(); + const cache = privateCacheFor(ssbClient, userId); const raw = await new Promise((resolve, reject) => { pull( - ssbClient.createLogStream({ reverse: false }), + ssbClient.createLogStream(cache.lastTs > 0 ? { reverse: false, gte: cache.lastTs } : { reverse: false }), pull.collect((err, arr) => err ? reject(err) : resolve(arr)) ); }); - const posts = []; - const tombed = new Set(); - const tombClaims = new Map(); - const authorByKey = new Map(); - const recpsByKey = new Map(); for (const m of raw) { if (!m || !m.value) continue; const keyIn = m.key || m.value?.key || m.value?.hash || ''; + if (!keyIn || cache.seen.has(keyIn)) continue; + cache.seen.add(keyIn); + const recvTs = Number(m.timestamp) || 0; + if (recvTs > cache.lastTs) cache.lastTs = recvTs; const valueIn = m.value || m; const tsIn = m.timestamp || m.value?.timestamp || Date.now(); let dec; @@ -146,18 +252,23 @@ module.exports = ({ cooler }) => { const k = dec?.key || keyIn; if (!c || c.private !== true || !k) continue; if (c.type === 'tombstone' && c.target) { - const set = tombClaims.get(c.target) || new Set(); + const set = cache.tombClaims.get(c.target) || new Set(); set.add(v.author); - tombClaims.set(c.target, set); + cache.tombClaims.set(c.target, set); continue; } - authorByKey.set(k, v.author); - if (c.type === 'post') { + if (c.type === longText.CHUNK_TYPE) { + const ch = longText.chunkOf(v.author, c); + if (ch) cache.chunks.set(k, ch); + continue; + } + cache.authorByKey.set(k, v.author); + if (c.type === 'post' || c.type === 'pam') { const to = Array.isArray(c.to) ? c.to : []; - recpsByKey.set(k, to); + cache.recpsByKey.set(k, to); const author = v.author; if (author === userId || to.includes(userId)) { - posts.push({ + (c.type === 'pam' ? cache.pams : cache.posts).set(k, { key: k, value: { author, content: c }, timestamp: v.timestamp || tsIn @@ -165,17 +276,48 @@ module.exports = ({ cooler }) => { } } } - for (const [target, tombAuthors] of tombClaims.entries()) { - const origAuthor = authorByKey.get(target); - const origRecps = recpsByKey.get(target) || []; - for (const tombAuthor of tombAuthors) { - if (tombAuthor === origAuthor || tombAuthor === userId || origRecps.includes(tombAuthor)) { - tombed.add(target); - break; - } - } - } - return posts.filter(m => m && m.key && (includeDeleted || !tombed.has(m.key))); + cache.version += raw.length ? 1 : 0; + const tombed = tombedKeys(cache, userId); + const lookup = longText.lookupIn(cache.chunks); + return Array.from(cache.posts.values()) + .filter(m => m && m.key && (includeDeleted || !tombed.has(m.key))) + .map(m => longText.hasChunks(m.value.content) + ? { ...m, value: { ...m.value, content: longText.resolveField(m.value.content, 'text', m.value.author, lookup) } } + : m); + }, + + async listPams() { + await this.listAllPrivate(); + const cache = privateCacheFor(ssb, userId); + const tombed = tombedKeys(cache, userId); + return Array.from(cache.pams.values()).filter(m => m && m.key && !tombed.has(m.key)); + }, + + async sendPam(recipient, share, durationSec) { + const ssbClient = await openSsb(); + if (!share || typeof share !== 'object') throw new Error('Invalid audio'); + const recps = uniqueRecps([userId, recipient]); + const content = { + type: 'pam', + from: userId, + to: recps, + share, + durationSec: Math.max(0, Math.round(Number(durationSec) || 0)), + sentAt: new Date().toISOString(), + private: true + }; + const publishAsync = util.promisify(ssbClient.private.publish); + return publishAsync(content, recps); + }, + + privateCacheVersion() { + return ssb ? privateCacheFor(ssb, userId).version : 0; } }; }; + +module.exports.botOf = botOf; +module.exports.mutedBots = mutedBots; +module.exports.INBOX_BOTS = INBOX_BOTS; +module.exports.readKeys = readKeys; +module.exports.archivedKeys = archivedKeys; diff --git a/src/models/podcasts_model.js b/src/models/podcasts_model.js new file mode 100644 index 0000000..5d0d6a7 --- /dev/null +++ b/src/models/podcasts_model.js @@ -0,0 +1,329 @@ +const { readTyped, memoIndex } = require('./typed_log'); +const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { getConfig } = require('../configs/config-manager.js'); +const categories = require('../backend/opinion_categories'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const CHANNEL_TYPE = 'podcast'; +const EPISODE_TYPE = 'podcastEpisode'; +const OPINION_TYPE = 'podcastOpinion'; +const PLAY_TYPE = 'podcastPlay'; +const TYPES = [CHANNEL_TYPE, EPISODE_TYPE, OPINION_TYPE, PLAY_TYPE, 'tombstone']; +const CATEGORIES = ['NEWS', 'MUSIC', 'TALK', 'EDUCATION', 'TECH', 'CULTURE', 'COMMUNITY', 'OASIS', 'OTHER']; +const RECENT_MS = 24 * 60 * 60 * 1000; +const MEDIA_RE = /!?\[(image|audio|video):([^\]]*)\]\((&[^)]+)\)/; + +const safeText = (v) => String(v == null ? '' : v).trim(); +const normU = (v) => String(v || '').trim().toUpperCase(); +const normalizeCategory = (v) => (CATEGORIES.includes(normU(v)) ? normU(v) : 'OTHER'); +const normalizeTags = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(','); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(Boolean))); +}; +const num = (v) => { const n = Number(v); return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0; }; + +const parseMedia = (raw) => { + if (!raw) return null; + if (typeof raw === 'object' && raw.blobId) return { kind: String(raw.kind || 'audio'), name: safeText(raw.name), blobId: String(raw.blobId) }; + const m = String(raw).match(MEDIA_RE); + if (!m) return null; + return { kind: m[1], name: safeText(m[2]) || 'file', blobId: m[3] }; +}; + +module.exports = ({ cooler }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + const publish = (ssbClient, content) => new Promise((resolve, reject) => ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res))); + + const buildIndex = (messages) => { + const tomb = buildValidatedTombstoneSet(messages); + const nodes = new Map(); + const parentOf = new Map(); + const opinions = []; + const plays = []; + for (const m of messages) { + const v = m.value || {}; + const c = v.content; + if (!c || typeof c !== 'object') continue; + if (c.type === CHANNEL_TYPE || c.type === EPISODE_TYPE) { + nodes.set(m.key, { key: m.key, type: c.type, author: v.author, ts: v.timestamp || m.timestamp || 0, c }); + if (typeof c.replaces === 'string') parentOf.set(m.key, c.replaces); + } else if (c.type === OPINION_TYPE && typeof c.target === 'string') { + opinions.push({ target: c.target, author: v.author, category: String(c.category || ''), ts: v.timestamp || 0 }); + } else if (c.type === PLAY_TYPE && typeof c.target === 'string') { + plays.push({ target: c.target, author: v.author, ts: v.timestamp || 0 }); + } + } + const rootOf = (key) => { + const seen = new Set(); + let cur = key; + while (parentOf.has(cur) && !seen.has(cur)) { seen.add(cur); cur = parentOf.get(cur); } + return cur; + }; + const versionsByRoot = new Map(); + for (const node of nodes.values()) { + const root = rootOf(node.key); + if (tomb.has(root)) continue; + if (!versionsByRoot.has(root)) versionsByRoot.set(root, []); + versionsByRoot.get(root).push(node); + } + const tips = new Map(); + for (const [root, versions] of versionsByRoot) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const own = versions.filter(v => v.author === first.author && v.type === first.type); + tips.set(root, { root, first, tip: own[own.length - 1] }); + } + const episodes = new Map(); + const opinionsByRoot = new Map(); + for (const op of opinions) { + if (!nodes.has(op.target)) continue; + const r = rootOf(op.target); + if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); + opinionsByRoot.get(r).push(op); + } + const playsByRoot = new Map(); + for (const pl of plays) { + if (!nodes.has(pl.target)) continue; + const r = rootOf(pl.target); + if (!playsByRoot.has(r)) playsByRoot.set(r, new Set()); + playsByRoot.get(r).add(pl.author); + } + for (const { root, first, tip } of tips.values()) { + if (first.type !== EPISODE_TYPE) continue; + const c = tip.c; + const channelId = safeText(c.channel); + if (!channelId) continue; + const ops = {}; + const voters = []; + for (const op of (opinionsByRoot.get(root) || [])) { + if (voters.includes(op.author) || !categories.includes(op.category)) continue; + voters.push(op.author); + ops[op.category] = (ops[op.category] || 0) + 1; + } + const listeners = Array.from(playsByRoot.get(root) || []); + episodes.set(root, { + id: root, rootId: root, tipId: tip.key, channelId, + number: num(c.number), title: safeText(c.title), description: safeText(c.description), + media: parseMedia(c.media), tags: normalizeTags(c.tags), + author: first.author, createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt: c.updatedAt || new Date(tip.ts).toISOString(), ts: tip.ts, publishedTs: Date.parse(first.c.createdAt || '') || first.ts, + opinions: ops, opinions_inhabitants: voters, opinionCount: voters.length, + listeners, playCount: listeners.length + }); + } + const channels = new Map(); + for (const { root, first, tip } of tips.values()) { + if (first.type !== CHANNEL_TYPE) continue; + const c = tip.c; + const eps = Array.from(episodes.values()).filter(e => e.channelId === root && e.author === first.author).sort((a, b) => a.publishedTs - b.publishedTs || a.number - b.number); + eps.forEach((e, i) => { if (!e.number) e.number = i + 1; }); + const playCount = eps.reduce((s, e) => s + e.playCount, 0); + const ops = {}; + const voters = []; + for (const op of (opinionsByRoot.get(root) || [])) { + if (voters.includes(op.author) || !categories.includes(op.category)) continue; + voters.push(op.author); + ops[op.category] = (ops[op.category] || 0) + 1; + } + const opinionCount = voters.length + eps.reduce((s, e) => s + e.opinionCount, 0); + channels.set(root, { + id: root, rootId: root, tipId: tip.key, + title: safeText(c.title), description: safeText(c.description), category: normalizeCategory(c.category), + cover: parseMedia(c.cover), tags: normalizeTags(c.tags), + author: first.author, createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt: c.updatedAt || new Date(tip.ts).toISOString(), ts: tip.ts, + episodes: eps, episodeCount: eps.length, playCount, opinionCount, + opinions: ops, opinions_inhabitants: voters, + lastEpisodeTs: eps.length ? eps[eps.length - 1].publishedTs : 0, + lastActivityTs: Math.max(tip.ts, ...eps.map(e => e.ts)) + }); + } + return { channels, episodes, rootOf, nodes }; + }; + + const load = async () => { + const ssbClient = await openSsb(); + const idx = memoIndex('podcasts', await readTyped(ssbClient, TYPES, { limit: logLimit }), buildIndex); + return { ssbClient, idx }; + }; + + const findChannel = (idx, id) => { + const key = String(id || ''); + if (idx.channels.has(key)) return idx.channels.get(key); + if (idx.nodes.has(key)) return idx.channels.get(idx.rootOf(key)) || null; + return null; + }; + const findEpisode = (idx, id) => { + const key = String(id || ''); + if (idx.episodes.has(key)) return idx.episodes.get(key); + if (idx.nodes.has(key)) return idx.episodes.get(idx.rootOf(key)) || null; + return null; + }; + + return { + CATEGORIES, + parseMedia, + + async createChannel({ title, description, category, cover, tags }) { + const ssbClient = await openSsb(); + const t = safeText(title); + if (!t) throw new Error('Title is required'); + const now = new Date().toISOString(); + const media = parseMedia(cover); + return publish(ssbClient, { + type: CHANNEL_TYPE, title: t, description: safeText(description), category: normalizeCategory(category), + ...(media ? { cover: media } : {}), tags: normalizeTags(tags), author: ssbClient.id, createdAt: now, updatedAt: now + }); + }, + + async updateChannel(id, data = {}) { + const { ssbClient, idx } = await load(); + const ch = findChannel(idx, id); + if (!ch) throw new Error('Podcast not found'); + if (ch.author !== ssbClient.id) throw new Error('Only the author can update this podcast'); + const media = data.cover !== undefined && data.cover ? parseMedia(data.cover) : ch.cover; + const res = await publish(ssbClient, { + type: CHANNEL_TYPE, + title: safeText(data.title) || ch.title, + description: data.description !== undefined ? safeText(data.description) : ch.description, + category: data.category !== undefined ? normalizeCategory(data.category) : ch.category, + ...(media ? { cover: media } : {}), + tags: data.tags !== undefined ? normalizeTags(data.tags) : ch.tags, + author: ssbClient.id, createdAt: ch.createdAt, updatedAt: new Date().toISOString(), replaces: ch.tipId + }); + return { key: res.key, rootId: ch.id }; + }, + + async deleteChannel(id) { + const { ssbClient, idx } = await load(); + const ch = findChannel(idx, id); + if (!ch) throw new Error('Podcast not found'); + if (ch.author !== ssbClient.id) throw new Error('Only the author can delete this podcast'); + await publish(ssbClient, { type: 'tombstone', target: ch.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + return { key: ch.id }; + }, + + async addEpisode(channelId, { title, description, media, number, tags }) { + const { ssbClient, idx } = await load(); + const ch = findChannel(idx, channelId); + if (!ch) throw new Error('Podcast not found'); + if (ch.author !== ssbClient.id) throw new Error('Only the author can publish episodes'); + const m = parseMedia(media); + if (!m || (m.kind !== 'audio' && m.kind !== 'video')) throw new Error('An audio or video file is required'); + const t = safeText(title); + if (!t) throw new Error('Title is required'); + const now = new Date().toISOString(); + const res = await publish(ssbClient, { + type: EPISODE_TYPE, channel: ch.id, number: num(number) || ch.episodeCount + 1, + title: t, description: safeText(description), media: m, tags: normalizeTags(tags), + author: ssbClient.id, createdAt: now, updatedAt: now + }); + return { key: res.key, rootId: res.key, channelId: ch.id }; + }, + + async updateEpisode(id, data = {}) { + const { ssbClient, idx } = await load(); + const ep = findEpisode(idx, id); + if (!ep) throw new Error('Episode not found'); + if (ep.author !== ssbClient.id) throw new Error('Only the author can update this episode'); + const m = data.media ? parseMedia(data.media) : ep.media; + const res = await publish(ssbClient, { + type: EPISODE_TYPE, channel: ep.channelId, number: data.number !== undefined && num(data.number) ? num(data.number) : ep.number, + title: safeText(data.title) || ep.title, description: data.description !== undefined ? safeText(data.description) : ep.description, + media: m, tags: data.tags !== undefined ? normalizeTags(data.tags) : ep.tags, + author: ssbClient.id, createdAt: ep.createdAt, updatedAt: new Date().toISOString(), replaces: ep.tipId + }); + return { key: res.key, rootId: ep.id, channelId: ep.channelId }; + }, + + async deleteEpisode(id) { + const { ssbClient, idx } = await load(); + const ep = findEpisode(idx, id); + if (!ep) throw new Error('Episode not found'); + if (ep.author !== ssbClient.id) throw new Error('Only the author can delete this episode'); + await publish(ssbClient, { type: 'tombstone', target: ep.id, deletedAt: new Date().toISOString(), author: ssbClient.id }); + return { key: ep.id, channelId: ep.channelId }; + }, + + async markPlayed(id) { + const { ssbClient, idx } = await load(); + const ep = findEpisode(idx, id); + if (!ep) throw new Error('Episode not found'); + if (ep.listeners.includes(ssbClient.id)) return null; + return publish(ssbClient, { type: PLAY_TYPE, target: ep.id, createdAt: new Date().toISOString() }); + }, + + async createOpinion(id, category) { + if (!categories.includes(category)) throw new Error('Invalid voting category'); + const { ssbClient, idx } = await load(); + const target = findEpisode(idx, id) || findChannel(idx, id); + if (!target) throw new Error('Episode not found'); + if (target.opinions_inhabitants.includes(ssbClient.id)) throw new Error('Already voted'); + return publish(ssbClient, { type: OPINION_TYPE, target: target.id, category, createdAt: new Date().toISOString() }); + }, + + async getChannelById(id) { + const { idx } = await load(); + return findChannel(idx, id); + }, + + async getAnyById(id) { + const { idx } = await load(); + return findChannel(idx, id) || findEpisode(idx, id); + }, + + async getEpisodeById(id) { + const { idx } = await load(); + const ep = findEpisode(idx, id); + if (!ep) return null; + return { ...ep, channel: idx.channels.get(ep.channelId) || null }; + }, + + async listAll({ filter = 'all', q = '', category = '', viewerId = null } = {}) { + const { ssbClient, idx } = await load(); + const me = viewerId || ssbClient.id; + const f = String(filter || 'all').toLowerCase(); + const needle = String(q || '').trim().toLowerCase(); + const cat = normU(category); + let out = Array.from(idx.channels.values()); + if (f === 'mine') out = out.filter(c => c.author === me); + else if (f === 'recent') out = out.filter(c => c.lastActivityTs >= Date.now() - RECENT_MS); + else if (f === 'viewers') out = out.filter(c => c.playCount > 0); + else if (f === 'top') out = out.filter(c => c.opinionCount > 0 || (c.spreadCount || 0) > 0); + else if (CATEGORIES.includes(f.toUpperCase())) out = out.filter(c => c.category === f.toUpperCase()); + if (cat && CATEGORIES.includes(cat)) out = out.filter(c => c.category === cat); + if (needle) out = out.filter(c => [c.title, c.description, c.category, ...c.tags, ...c.episodes.map(e => e.title)].some(v => String(v || '').toLowerCase().includes(needle))); + if (f === 'viewers') out.sort((a, b) => b.playCount - a.playCount || b.lastActivityTs - a.lastActivityTs); + else if (f === 'top') out.sort((a, b) => b.opinionCount - a.opinionCount || b.playCount - a.playCount); + else out.sort((a, b) => b.lastActivityTs - a.lastActivityTs); + return out; + }, + + async listEpisodes({ filter = 'all', q = '' } = {}) { + const { ssbClient, idx } = await load(); + const me = ssbClient.id; + const f = String(filter || 'all').toLowerCase(); + const needle = String(q || '').trim().toLowerCase(); + let out = Array.from(idx.episodes.values()).filter(e => idx.channels.has(e.channelId)).map(e => ({ ...e, channel: idx.channels.get(e.channelId) })); + if (f === 'mine') out = out.filter(e => e.author === me); + else if (f === 'recent') out = out.filter(e => e.ts >= Date.now() - RECENT_MS); + else if (f === 'viewers') out = out.filter(e => e.playCount > 0); + else if (f === 'top') out = out.filter(e => e.opinionCount > 0); + if (needle) out = out.filter(e => [e.title, e.description, ...e.tags, e.channel.title].some(v => String(v || '').toLowerCase().includes(needle))); + if (f === 'viewers') out.sort((a, b) => b.playCount - a.playCount); + else if (f === 'top') out.sort((a, b) => b.opinionCount - a.opinionCount); + else out.sort((a, b) => b.publishedTs - a.publishedTs); + return out; + }, + + async resolveRootId(id) { + const { idx } = await load(); + const ch = findChannel(idx, id); + if (ch) return ch.id; + const ep = findEpisode(idx, id); + return ep ? ep.id : String(id || ''); + } + }; +}; diff --git a/src/models/polls_model.js b/src/models/polls_model.js index 31db862..c61a5c2 100644 --- a/src/models/polls_model.js +++ b/src/models/polls_model.js @@ -1,6 +1,8 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, memoIndex } = require('./typed_log'); +const { versionGroups } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -37,13 +39,9 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const POLLS_TYPES = [POLL_TYPE, VOTE_TYPE, OPINION_TYPE, 'pollClose', 'post', 'tombstone']; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, POLLS_TYPES, { limit: logLimit }); const decryptField = (value, keys) => { if (!tribeCrypto || typeof value !== 'string' || !value) return null; @@ -65,82 +63,61 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const replaced = new Map(); const votesByRoot = new Map(); const opinionsByRoot = new Map(); const closedRoots = new Set(); const commentsByRoot = new Map(); - const authorByKey = new Map(); + const others = []; for (const m of messages) { const v = m && m.value; const c = v && v.content; if (!c || typeof c !== 'object') continue; - authorByKey.set(m.key, v.author); + if (c.type === POLL_TYPE) nodes.set(m.key, { key: m.key, author: v.author, seq: v.sequence || 0, ts: v.timestamp || m.timestamp || 0, c }); + else others.push(m); + } - if (c.type === POLL_TYPE) { - nodes.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, c }); - if (typeof c.replaces === 'string') replaced.set(c.replaces, m.key); - continue; - } + const vg = versionGroups(Array.from(nodes.values()).map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === 'string' ? n.c.replaces : null, claimedAuthor: typeof n.c.author === 'string' ? n.c.author : null }))); + const rootOf = vg.rootOf; + const tipOf = vg.tipOf; + + for (const m of others) { + const v = m.value; + const c = v.content; if (c.type === VOTE_TYPE && typeof c.target === 'string') { - const entry = votesByRoot.get(c.target) || new Map(); - const prev = entry.get(v.author); - const ts = v.timestamp || m.timestamp || 0; - if (!prev || ts >= prev.ts) { - entry.set(v.author, { - ts, - choices: Array.isArray(c.choices) ? c.choices : [], - encryptedChoices: typeof c.encryptedChoices === 'string' ? c.encryptedChoices : null - }); - } - votesByRoot.set(c.target, entry); + const target = rootOf(c.target); + const entry = votesByRoot.get(target) || new Map(); + const list = entry.get(v.author) || []; + list.push({ + ts: v.timestamp || m.timestamp || 0, + choices: Array.isArray(c.choices) ? c.choices : [], + encryptedChoices: typeof c.encryptedChoices === 'string' ? c.encryptedChoices : null + }); + entry.set(v.author, list); + votesByRoot.set(target, entry); continue; } if (c.type === OPINION_TYPE && typeof c.target === 'string') { - const entry = opinionsByRoot.get(c.target) || { counts: {}, voters: [] }; + const target = rootOf(c.target); + const entry = opinionsByRoot.get(target) || { counts: {}, voters: [] }; if (!entry.voters.includes(v.author)) { entry.voters.push(v.author); entry.counts[c.category] = (entry.counts[c.category] || 0) + 1; } - opinionsByRoot.set(c.target, entry); + opinionsByRoot.set(target, entry); continue; } if (c.type === 'pollClose' && typeof c.target === 'string') { - closedRoots.add(`${v.author}|${c.target}`); + closedRoots.add(`${v.author}|${rootOf(c.target)}`); continue; } if (c.type === 'post' && typeof c.root === 'string') { - commentsByRoot.set(c.root, (commentsByRoot.get(c.root) || 0) + 1); + const target = rootOf(c.root); + commentsByRoot.set(target, (commentsByRoot.get(target) || 0) + 1); } } - const rootOf = (key) => { - let cur = key; - const seen = new Set(); - while (nodes.has(cur) && nodes.get(cur).c.replaces && !seen.has(cur)) { - seen.add(cur); - const next = nodes.get(cur).c.replaces; - if (!nodes.has(next)) break; - cur = next; - } - return cur; - }; - - const tipOf = (root) => { - let cur = root; - const seen = new Set(); - while (replaced.has(cur) && !seen.has(cur)) { - seen.add(cur); - const next = replaced.get(cur); - const node = nodes.get(next); - if (!node || node.author !== nodes.get(cur).author) break; - cur = next; - } - return cur; - }; - - return { tomb, nodes, replaced, votesByRoot, opinionsByRoot, closedRoots, commentsByRoot, rootOf, tipOf }; + return { tomb, nodes, vg, votesByRoot, opinionsByRoot, closedRoots, commentsByRoot, rootOf, tipOf }; }; const isExpired = (c) => { @@ -165,7 +142,13 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n try { rawOptions = o ? JSON.parse(o) : []; } catch (_) { rawOptions = []; } } const options = normalizeOptions(rawOptions); - const voteMap = idx.votesByRoot.get(rootId) || new Map(); + const opensAt = (idx.nodes.get(rootId) || {}).ts || 0; + const closesAt = c.deadline ? Date.parse(c.deadline) : NaN; + const voteMap = new Map(); + for (const [author, list] of (idx.votesByRoot.get(rootId) || new Map()).entries()) { + const inWindow = list.filter(b => b.ts >= opensAt && (!Number.isFinite(closesAt) || b.ts <= closesAt)); + if (inWindow.length) voteMap.set(author, inWindow.reduce((a, b) => (b.ts >= a.ts ? b : a))); + } const counts = {}; for (const o of options) counts[o] = 0; @@ -223,9 +206,7 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n const collect = async (viewerId) => { const ssbClient = await openSsb(); const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); - const roots = new Set(); - for (const key of idx.nodes.keys()) roots.add(idx.rootOf(key)); + const idx = memoIndex('polls', messages, buildIndex); const keyCache = new Map(); const keysFor = async (c) => { @@ -239,7 +220,7 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n }; const list = []; - for (const rootId of roots) { + for (const rootId of idx.vg.roots) { const tipId = idx.tipOf(rootId); if (idx.tomb.has(tipId) || idx.tomb.has(rootId)) continue; const node = idx.nodes.get(tipId); @@ -275,6 +256,7 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n else if (f === 'OPEN') out = out.filter(p => p.status === 'OPEN'); else if (f === 'CLOSED') out = out.filter(p => p.status === 'CLOSED'); else if (f === 'VOTED') out = out.filter(p => p.hasVoted); + else if (f === 'RECENT') out = out.filter(p => (Date.parse(p.createdAt || '') || 0) >= Date.now() - 86400000); else if (f === 'FAVORITES') { const fav = new Set((opts.favorites || []).map(String)); out = out.filter(p => fav.has(String(p.id))); diff --git a/src/models/projects_model.js b/src/models/projects_model.js index f5b8446..76d3b3a 100644 --- a/src/models/projects_model.js +++ b/src/models/projects_model.js @@ -3,6 +3,7 @@ const moment = require("../server/node_modules/moment") const { getConfig } = require("../configs/config-manager.js") const { buildValidatedTombstoneSet } = require('./tombstone_validator'); const { dedupeBy, norm } = require('../backend/dedupe') +const { readTyped } = require("./typed_log") const logLimit = (getConfig().ssbLogStream && getConfig().ssbLogStream.limit) || 1000 module.exports = ({ cooler }) => { @@ -20,13 +21,10 @@ module.exports = ({ cooler }) => { return Math.max(0, Math.min(100, x)) } + const PROJECT_TYPES = ["project", "projectOpinion", "projectFollow", "projectPledge", "projectClaim", "projectPledgeConfirm", "tombstone"] + async function getAllMsgs(ssbClient) { - return new Promise((r, j) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((e, m) => (e ? j(e) : r(m))) - ) - }) + return readTyped(ssbClient, PROJECT_TYPES, { limit: logLimit }) } function extractBlobId(possibleMarkdownImage) { diff --git a/src/models/pub_invites.js b/src/models/pub_invites.js new file mode 100644 index 0000000..8cc408a --- /dev/null +++ b/src/models/pub_invites.js @@ -0,0 +1,93 @@ +const ssbKeys = require('../server/node_modules/ssb-keys'); +const { readTyped } = require('./typed_log'); + +const ANNOUNCE_TYPE = 'pub-invites'; +const MAX_CODES = 40; +const FEED_ID = /@[A-Za-z0-9+/]{43}=\.ed25519/; +const SHS_KEY = /~shs:([A-Za-z0-9+/]{43}=)/; + +const LEGACY_ADDRESS = /^([^:~\s]+):(\d+):@/; +const MODERN_ADDRESS = /^(?:net|onion):([^:~\s]+):(\d+)~/; +const parseInvite = (code) => { + const s = String(code || '').trim(); + const legacy = s.match(FEED_ID); + const modern = s.match(SHS_KEY); + const pub = legacy ? legacy[0] : modern ? `@${modern[1]}.ed25519` : null; + const address = s.match(LEGACY_ADDRESS) || s.match(MODERN_ADDRESS); + const host = address ? address[1] : null; + const port = address ? Number(address[2]) : null; + let id = null; + try { + const seed = Buffer.from(s.split('~').pop().replace(/^invite:/, ''), 'base64'); + if (seed.length === 32) id = ssbKeys.generate('ed25519', seed).id; + } catch (_) {} + return { pub, id, host, port }; +}; + +const count = (n) => Math.max(0, Math.floor(Number(n) || 0)); + +const summarize = (rows) => { + const list = (Array.isArray(rows) ? rows : []) + .filter((r) => r && typeof r.id === 'string' && count(r.total) > 0) + .map((r) => ({ id: r.id, total: count(r.total), used: Math.min(count(r.used), count(r.total)) })); + const codes = list.filter((r) => r.total > 1) + .sort((a, b) => (b.total - b.used) - (a.total - a.used)) + .slice(0, MAX_CODES) + .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + return { + total: list.reduce((n, r) => n + r.total, 0), + used: list.reduce((n, r) => n + r.used, 0), + codes + }; +}; + +const latestByAuthor = (msgs) => { + const out = new Map(); + for (const m of msgs || []) { + const v = m && m.value; + const c = v && v.content; + if (!c || c.type !== ANNOUNCE_TYPE) continue; + const prev = out.get(v.author); + if (!prev || (Number(v.sequence) || 0) > (Number(prev.value.sequence) || 0)) out.set(v.author, m); + } + return out; +}; + +const listUses = (ssb) => new Promise((resolve) => { + if (!ssb || !ssb.invite || typeof ssb.invite.list !== 'function') return resolve(null); + try { ssb.invite.list((err, rows) => resolve(err ? null : rows)); } catch (_) { resolve(null); } +}); + +const announce = async (ssb) => { + const rows = await listUses(ssb); + if (!rows) return null; + const next = summarize(rows); + const last = latestByAuthor(await readTyped(ssb, [ANNOUNCE_TYPE])).get(ssb.id); + const c = last && last.value.content; + const prev = c ? { total: c.total, used: c.used, codes: c.codes } : null; + if (prev ? JSON.stringify(prev) === JSON.stringify(next) : !next.total) return null; + return new Promise((resolve, reject) => ssb.publish({ type: ANNOUNCE_TYPE, ...next, updatedAt: new Date().toISOString() }, (err, msg) => (err ? reject(err) : resolve(msg)))); +}; + +const statusFor = (entries, msgs) => { + const latest = latestByAuthor(msgs); + return (Array.isArray(entries) ? entries : []).map((e) => { + const { pub, id } = parseInvite(e && e.code); + const m = pub ? latest.get(pub) : null; + if (!m) return { ...e, invites: null, exhausted: false }; + const c = m.value.content; + const total = count(c.total); + const used = Math.min(count(c.used), total); + const own = id && Array.isArray(c.codes) ? c.codes.find((x) => x && x.id === id) : null; + const exhausted = (total > 0 && used >= total) || (!!own && count(own.used) >= count(own.total)); + return { ...e, invites: total > 0 ? { used, total } : null, exhausted }; + }); +}; + +const readStatus = async (ssb, entries) => { + let msgs = []; + try { msgs = await readTyped(ssb, [ANNOUNCE_TYPE]); } catch (_) { msgs = []; } + return statusFor(entries, msgs); +}; + +module.exports = { ANNOUNCE_TYPE, parseInvite, summarize, announce, readStatus }; diff --git a/src/models/reports_model.js b/src/models/reports_model.js index 09d6ea9..39631d9 100644 --- a/src/models/reports_model.js +++ b/src/models/reports_model.js @@ -2,7 +2,8 @@ const pull = require('../server/node_modules/pull-stream'); const { normalizeImages, normalizeVideo } = require('./media_gallery'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const normU = (v) => String(v || '').trim().toUpperCase(); @@ -50,20 +51,13 @@ module.exports = ({ cooler }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ); - }); + const REPORT_TYPES = ['report', 'reportConfirm', 'reportOpinion', 'tombstone']; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, REPORT_TYPES, { limit: logLimit }); const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const confirms = []; const opinionMsgs = []; @@ -79,46 +73,24 @@ module.exports = ({ cooler }) => { if (c.type !== 'report') continue; const ts = v.timestamp || m.timestamp || 0; - nodes.set(k, { key: k, ts, c, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, author: v.author }); } - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === 'string' ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const confirmsByRoot = new Map(), opinionsByRoot = new Map(); for (const cf of confirms) { if (!nodes.has(cf.target)) continue; const r = rootOf(cf.target); if (!confirmsByRoot.has(r)) confirmsByRoot.set(r, []); confirmsByRoot.get(r).push(cf); } for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); const resolveGroup = (root) => { const contentTip = contentTipOf(root); const contentNode = nodes.get(contentTip) || nodes.get(root); - if (!contentNode) return null; + if (!contentNode || !tipByRoot.has(root)) return null; const rootAuthor = nodes.get(root) ? nodes.get(root).author : null; const lc = contentNode.c || {}; @@ -126,8 +98,7 @@ module.exports = ({ cooler }) => { const voterSet = new Set(ensureArray(lc.opinions_inhabitants)); const confirmSet = new Set(ensureArray(lc.confirmations)); - const groupKeys = []; { let x = root, g = 0; groupKeys.push(x); while (child.has(x) && g++ < 100000) { x = child.get(x); groupKeys.push(x); } } - for (const k of groupKeys) { + for (const k of (vg.groups.get(root) || [root])) { const n = nodes.get(k); if (!n) continue; if (n.author !== rootAuthor) continue; const c = n.c || {}; for (const cb of ensureArray(c.confirmations)) confirmSet.add(cb); for (const vv of ensureArray(c.opinions_inhabitants)) voterSet.add(vv); @@ -151,7 +122,7 @@ module.exports = ({ cooler }) => { }; }; - return { tomb, nodes, parent, child, rootOf, tipByRoot, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildReport = (node, agg) => { @@ -304,7 +275,7 @@ module.exports = ({ cooler }) => { const ssb = await openSsb(); const messages = await getAllMessages(ssb); - const idx = buildIndex(messages); + const idx = memoIndex('reports', messages, buildIndex); const root = idx.rootOf(id); const agg = idx.resolveGroup(root); if (!agg || agg.tombstoned) throw new Error('Report not found'); diff --git a/src/models/rooms_model.js b/src/models/rooms_model.js new file mode 100644 index 0000000..f72abef --- /dev/null +++ b/src/models/rooms_model.js @@ -0,0 +1,821 @@ +const crypto = require("crypto") +const { buildValidatedTombstoneSet } = require('./tombstone_validator') +const { collabContent, openInviteOf } = require('../backend/collab_content') +const { readTyped, memoIndex } = require('./typed_log') +const { roomNumberOf, normalizeNumber } = require('./phone_number') +const { getConfig } = require("../configs/config-manager.js") +const roomCollab = collabContent({ membersField: 'members', undecField: 'undecryptable', contentFields: ['title', 'description', 'image', 'status'], listFields: ['tags', 'invites'] }) +const logLimit = getConfig().ssbLogStream?.limit || 1000 + +const ROOM_MAX = 50 +const ROOM_TYPES = ["room", "roomMember", "tribe-keys", "tombstone"] +const STATUSES = ["OPEN", "INVITE-ONLY"] +const INVITE_SALT = "SolarNET.HuB-rooms" +const INVITE_BYTES = 16 +const RECENT_MS = 86400000 +const RID = /^[0-9a-f]{32}$/ +const FEED_ID = /^@[A-Za-z0-9+/]{43}=\.ed25519$/ +const BLOB_ID = /^&[A-Za-z0-9+/]{43}=\.sha256$/ + +const safeText = (v) => String(v || "").trim() +const normalizeTags = (raw) => { + if (!raw) return [] + if (Array.isArray(raw)) return raw.map(t => String(t || "").trim()).filter(Boolean) + return String(raw).split(",").map(t => t.trim()).filter(Boolean) +} +const normalizeStatus = (v) => (STATUSES.includes(String(v || "").toUpperCase()) ? String(v).toUpperCase() : "OPEN") +const blobRef = (v) => (BLOB_ID.test(safeText(v)) ? safeText(v) : "") +const asP = (fn, ...args) => new Promise((resolve, reject) => fn(...args, (err, v) => err ? reject(err) : resolve(v))) +const rpcValue = (fn, ...args) => new Promise((resolve) => { + try { + const r = fn(...args, (err, v) => resolve(err ? null : v)) + if (r && typeof r.then === "function") r.then(resolve, () => resolve(null)) + else if (r !== undefined) resolve(r) + } catch (_) { resolve(null) } +}) + +module.exports = ({ cooler, tribeCrypto, roomCrypto, tribesModel }) => { + let ssb + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb } + const phone = async () => { const s = await openSsb(); return s && s.phone ? s.phone : null } + + const ownCrypto = roomCrypto || tribeCrypto + const lookupKey = (rid) => (ownCrypto && ownCrypto.getKey(rid)) || (tribeCrypto && tribeCrypto.getKey(rid)) || null + const lookupKeys = (rid) => { + const a = (ownCrypto && ownCrypto.getKeys(rid)) || [] + if (a.length) return a + return (tribeCrypto && tribeCrypto.getKeys(rid)) || [] + } + const lookupGen = (rid) => ((ownCrypto && ownCrypto.getGen(rid)) || (tribeCrypto && tribeCrypto.getGen(rid)) || 0) + const setRoomKey = (rootId, keyHex) => { if (ownCrypto) ownCrypto.setKey(rootId, keyHex, 1) } + const hashCode = (code, salt) => (ownCrypto && typeof ownCrypto.hashInviteCode === "function") ? ownCrypto.hashInviteCode(code, salt) : (tribeCrypto ? tribeCrypto.hashInviteCode(code, salt) : null) + const inviteMatches = (inv, token) => { + if (!token) return false + if (typeof inv === "string") return inv === token + if (!inv || typeof inv !== "object") return false + if (typeof inv.code === "string" && inv.code === token) return true + if (typeof inv.ch === "string" && (inv.ch === token || hashCode(token, inv.salt) === inv.ch)) return true + return false + } + const inviteToken = (inv) => (inv && typeof inv === "object" && typeof inv.ch === "string") ? inv.ch : (typeof inv === "string" ? inv : (inv && inv.code) || "") + const keyProofFor = (keyHex, id) => { try { return crypto.createHmac("sha256", Buffer.from(keyHex, "hex")).update(String(id), "utf8").digest("hex") } catch (_) { return null } } + const deriveRid = (keyHex, roomId) => crypto.createHmac("sha256", Buffer.from(String(keyHex), "hex")).update("oasis-room-rid|" + roomId).digest("hex").slice(0, 32) + const currentKeyFor = (rootId, tribeId, tribeKeys) => (tribeId && Array.isArray(tribeKeys) && tribeKeys.length ? tribeKeys[0] : lookupKeys(rootId)[0]) || null + + const encryptField = (text, keyHex) => { + const key = Buffer.from(keyHex, "hex") + const iv = crypto.randomBytes(12) + const cipher = crypto.createCipheriv("aes-256-gcm", key, iv) + const enc = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]) + const authTag = cipher.getAuthTag() + return iv.toString("hex") + authTag.toString("hex") + enc.toString("hex") + } + + const tryDecryptField = (encrypted, keyHex) => { + const key = Buffer.from(keyHex, "hex") + const iv = Buffer.from(encrypted.slice(0, 24), "hex") + const authTag = Buffer.from(encrypted.slice(24, 56), "hex") + const ciphertext = Buffer.from(encrypted.slice(56), "hex") + const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv) + decipher.setAuthTag(authTag) + return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8") + } + + const decryptField = (encrypted, keyHex) => { + try { return tryDecryptField(encrypted, keyHex) } catch (_) { return "" } + } + + const getTribeKeysFor = async (tribeId) => { + if (!tribeCrypto || !tribesModel || !tribeId) return [] + try { + const rootId = await tribesModel.getRootId(tribeId) + return tribeCrypto.getKeys(rootId) || [] + } catch (_) { return [] } + } + + const fieldsWith = (c, keyHex, strict) => { + const dec = strict ? tryDecryptField : decryptField + const title = c.title ? dec(c.title, keyHex) : "" + const opt = (v) => { try { return v ? dec(v, keyHex) : "" } catch (_) { return "" } } + return { + title: safeText(title), + description: safeText(opt(c.description)), + image: blobRef(opt(c.image)), + tags: normalizeTags(opt(c.tags)), + rid: RID.test(opt(c.rid)) ? opt(c.rid) : "" + } + } + + const decryptWithKeys = (c, keys) => { + if (!c.title || !keys.length) return null + for (const k of keys) { + try { return fieldsWith(c, k, true) } catch (_) {} + } + return null + } + + const encryptForInvite = (roomKeyHex, code, saltHex) => { + const salt = saltHex ? Buffer.from(saltHex, "hex") : Buffer.from(INVITE_SALT) + const derived = crypto.scryptSync(code, salt, 32) + return encryptField(roomKeyHex, derived.toString("hex")) + } + + const decryptFromInvite = (encryptedKey, code, saltHex) => { + const salt = saltHex ? Buffer.from(saltHex, "hex") : Buffer.from(INVITE_SALT) + const derived = crypto.scryptSync(code, salt, 32) + return decryptField(encryptedKey, derived.toString("hex")) + } + + const tryDecryptPublicInviteKey = (invites) => { + if (!Array.isArray(invites)) return null + for (const inv of invites) { + if (!inv || typeof inv !== "object" || inv.public !== true) continue + if (typeof inv.code !== "string" || typeof inv.ek !== "string") continue + try { + const key = decryptFromInvite(inv.ek, inv.code, inv.salt) + if (key) return key + } catch (_) {} + } + return null + } + + const generateInviteSalt = () => crypto.randomBytes(16).toString("hex") + + const rotateRoomKey = async (rootId, remainingMembers) => { + if (!ownCrypto || !tribeCrypto || !rootId) return + if (!lookupKey(rootId)) return + const newKey = crypto.randomBytes(32).toString("hex") + const newGen = ownCrypto.addNewKey(rootId, newKey) + const ssbClient = await openSsb() + const ssbKeys = require("../server/node_modules/ssb-keys") + const memberKeys = {} + for (const m of new Set([ssbClient.id, ...(Array.isArray(remainingMembers) ? remainingMembers : [])])) { + try { memberKeys[m] = tribeCrypto.boxKeyForMember(newKey, m, ssbKeys) } catch (_) {} + } + if (Object.keys(memberKeys).length) { + await new Promise((resolve) => { + ssbClient.publish({ type: "tribe-keys", tribeId: rootId, generation: newGen, memberKeys }, () => resolve()) + }) + } + } + + const readAll = async (ssbClient) => readTyped(ssbClient, ROOM_TYPES, { limit: logLimit }) + + const ensureMemberKeys = async (ssbClient, messages, rooms) => { + if (!tribeCrypto || !ownCrypto) return + const leavesByRoot = new Map() + for (const m of messages) { + const c = m.value && m.value.content + if (!c || c.type !== "roomMember" || c.on !== false || !c.target || !c.member) continue + if (!leavesByRoot.has(c.target)) leavesByRoot.set(c.target, []) + leavesByRoot.get(c.target).push({ member: c.member, author: m.value.author, ts: Number(m.timestamp || m.value.timestamp || 0) }) + } + const ssbKeys = require("../server/node_modules/ssb-keys") + for (const room of (Array.isArray(rooms) ? rooms : [])) { + if (!room || room.tribeId || room.author !== ssbClient.id) continue + const rootId = room.rootId + if (!rootId || !lookupKey(rootId)) continue + const members = Array.isArray(room.members) ? room.members : [] + const leaves = (leavesByRoot.get(rootId) || []).filter(l => l.author === l.member || l.author === room.author) + const plan = ownCrypto.keyPlan({ rootId, ownerId: room.author, gen: lookupGen(rootId), members, messages, leaves }) + if (plan.rotate) { + await rotateRoomKey(rootId, members) + continue + } + if (!plan.missing.length) continue + const key = lookupKey(rootId) + const memberKeys = {} + for (const m of plan.missing) { + try { memberKeys[m] = tribeCrypto.boxKeyForMember(key, m, ssbKeys) } catch (_) {} + } + if (!Object.keys(memberKeys).length) continue + await asP(ssbClient.publish, { type: "tribe-keys", tribeId: rootId, generation: lookupGen(rootId) || 1, memberKeys }) + } + } + + + const ingestOwnTribeKeys = async () => { + if (!ownCrypto) return + try { + const ssbClient = await openSsb() + const ssbKeys = require("../server/node_modules/ssb-keys") + const config = require("../server/ssb_config") + const rooms = new Set() + const msgs = await readAll(ssbClient) + for (const m of msgs) { + const c = m.value && m.value.content + if (c && c.type === "room") rooms.add(m.key) + } + const idx = buildIndex(msgs) + const trusted = (root, author) => { const owner = idx.nodes.get(root) && idx.nodes.get(root).author; return !!author && !!owner && (author === owner || author === ssbClient.id) } + for (const m of msgs) { + const c = m.value && m.value.content + if (!c || c.type !== "tribe-keys" || !rooms.has(c.tribeId)) continue + if (!trusted(c.tribeId, m.value.author)) continue + const boxed = c.memberKeys && typeof c.memberKeys === "object" ? c.memberKeys[ssbClient.id] : null + if (!boxed) continue + try { + const unboxed = ssbKeys.unbox(boxed, config.keys) + const key = typeof unboxed === "string" ? unboxed : (unboxed && unboxed.toString ? unboxed.toString() : null) + if (key) ownCrypto.addNewKey(c.tribeId, key) + } catch (_) {} + } + } catch (_) {} + } + + const buildIndex = (messages) => { + const tomb = new Set() + const nodes = new Map() + const parent = new Map() + const child = new Map() + const authorByKey = new Map() + const tombRequests = [] + const memberMsgs = [] + + for (const m of messages) { + const k = m.key + const v = m.value || {} + const c = v.content + if (!c) continue + if (c.type === "tombstone" && c.target) { tombRequests.push({ target: c.target, author: v.author }); continue } + if (c.type === "roomMember" && c.target) { memberMsgs.push({ target: c.target, member: c.member, on: c.on !== false, author: v.author, ts: v.timestamp || m.timestamp || 0, code: typeof c.code === "string" ? c.code : "", keyProof: typeof c.keyProof === "string" ? c.keyProof : "" }); continue } + if (c.type === "room") { + nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) + authorByKey.set(k, v.author) + if (c.replaces) { parent.set(k, c.replaces); child.set(c.replaces, k) } + } + } + + for (const t of tombRequests) { + const targetAuthor = authorByKey.get(t.target) + if (targetAuthor && t.author === targetAuthor) tomb.add(t.target) + } + + const rootOf = (id) => { let cur = id, g = 0; while (parent.has(cur) && g++ < 100000) cur = parent.get(cur); return cur } + const rootMemo = new Map() + const groupRootOf = (id) => { + if (rootMemo.has(id)) return rootMemo.get(id) + let cur = id, g = 0 + while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur) + rootMemo.set(id, cur) + return cur + } + const ownerTip = new Map() + for (const [k, n] of nodes) { + const r = groupRootOf(k) + const rn = nodes.get(r) + if (!rn || n.author !== rn.author) continue + const prev = ownerTip.has(r) ? nodes.get(ownerTip.get(r)) : null + if (!prev || n.seq > prev.seq || (n.seq === prev.seq && n.ts >= prev.ts)) ownerTip.set(r, k) + } + const contentTipOf = (root) => ownerTip.get(root) || root + + const roots = new Set() + for (const id of nodes.keys()) roots.add(groupRootOf(id)) + + const invitesOf = (root) => { + const out = [] + for (const id of [root, contentTipOf(root)]) { const n = nodes.get(id); if (n && Array.isArray(n.c.invites)) out.push(...n.c.invites) } + return out + } + const selfJoinAllowed = (root, mm) => { + const oc = (nodes.get(root) || {}).c || {} + if (oc.encrypted !== true || oc.tribeId) return true + const invites = invitesOf(root) + if (invites.some(inv => inv && typeof inv === "object" && inv.public === true)) return true + if (mm.keyProof && lookupKeys(root).some(k => keyProofFor(k, mm.member) === mm.keyProof)) return true + return !!mm.code && invites.some(inv => inviteMatches(inv, mm.code)) + } + + const memberByRoot = new Map() + const consumedByRoot = new Map() + for (const mm of memberMsgs) { + if (!nodes.has(mm.target)) continue + const r = groupRootOf(mm.target) + const ownerAuthor = nodes.get(r) && nodes.get(r).author + if (!ownerAuthor || !mm.member) continue + const self = mm.member === mm.author + if (!self && mm.author !== ownerAuthor) continue + if (self && mm.author !== ownerAuthor && mm.on && !selfJoinAllowed(r, mm)) continue + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) + const m2 = memberByRoot.get(r) + const p = m2.get(mm.member) + if (!p || mm.ts >= p.ts) m2.set(mm.member, { on: mm.on, ts: mm.ts }) + if (mm.on && mm.code) { + if (!consumedByRoot.has(r)) consumedByRoot.set(r, new Set()) + consumedByRoot.get(r).add(mm.code) + } + } + + const isCodeConsumed = (root, code) => { + if (!code) return false + const set = consumedByRoot.get(root) + return !!(set && set.has(code)) + } + + for (const [k, n] of nodes) { + const r = groupRootOf(k) + const rn = nodes.get(r) + if (!rn || n.author === rn.author || !Array.isArray(n.c.members) || !selfJoinAllowed(r, { member: n.author })) continue + if (!memberByRoot.has(r)) memberByRoot.set(r, new Map()) + const m2 = memberByRoot.get(r) + const p = m2.get(n.author) + if (!p || n.ts >= p.ts) m2.set(n.author, { on: n.c.members.includes(n.author), ts: n.ts }) + } + + const resolveMembers = (root) => { + const ownerNode = nodes.get(root) + const oc = ownerNode ? ownerNode.c : {} + const set = new Set(Array.isArray(oc.members) ? oc.members.filter(x => typeof x === "string" && x) : []) + for (const [mem, st] of (memberByRoot.get(root) || new Map())) { if (st.on) set.add(mem); else set.delete(mem) } + return [...set] + } + + const tipByRoot = new Map() + for (const r of roots) tipByRoot.set(r, contentTipOf(r)) + + return { tomb, nodes, parent, child, rootOf, groupRootOf, contentTipOf, tipByRoot, resolveMembers, isCodeConsumed } + } + + const decryptRoomFields = (c, rootId, tribeKeys) => { + if (c.encrypted !== true) { + return { title: safeText(c.title), description: safeText(c.description), image: blobRef(c.image), tags: normalizeTags(c.tags), rid: RID.test(String(c.rid || "")) ? String(c.rid) : "", _undec: false } + } + if (c.tribeId && Array.isArray(tribeKeys) && tribeKeys.length) { + const viaTribe = decryptWithKeys(c, tribeKeys) + if (viaTribe) return { ...viaTribe, _undec: false } + } + for (const k of [...lookupKeys(rootId), tryDecryptPublicInviteKey(c.invites)].filter(Boolean)) { + const out = decryptWithKeys(c, [k]) + if (out) return { ...out, _undec: false } + } + return { title: "", description: "", image: "", tags: [], rid: "", _undec: true } + } + + const buildRoom = (node, rootId, tribeKeys, members) => { + const c = node.c || {} + if (c.type !== "room") return null + const f = decryptRoomFields(c, rootId, tribeKeys) + const author = c.author || node.author + const roomId = RID.test(String(c.roomId || "")) ? String(c.roomId) : "" + const liveKey = roomId && c.encrypted === true && !f._undec ? currentKeyFor(rootId, c.tribeId, tribeKeys) : null + return { + key: node.key, + rootId, + roomId, + title: f.title, + description: f.description, + image: f.image, + tags: f.tags, + status: ["OPEN", "INVITE-ONLY", "CLOSED"].includes(c.status) ? c.status : "OPEN", + type: c.status === "INVITE-ONLY" || (c.status === "CLOSED" && c.encrypted === true && !c.tribeId) ? "INVITE-ONLY" : "OPEN", + author, + members: Array.isArray(members) ? members : (Array.isArray(c.members) ? c.members : []), + invites: Array.isArray(c.invites) ? c.invites : [], + createdAt: c.createdAt || new Date(node.ts).toISOString(), + updatedAt: c.updatedAt || null, + tribeId: c.tribeId || null, + encrypted: c.encrypted === true, + undecryptable: !!f._undec, + rid: roomId ? (liveKey ? deriveRid(liveKey, roomId) : "") : f.rid, + token: typeof c.token === "string" ? c.token : "", + hub: FEED_ID.test(String(c.hub || "")) ? c.hub : author, + hubAddress: typeof c.hubAddress === "string" ? c.hubAddress : "", + media: c.media === "video" ? "video" : "audio", + maxParticipants: ROOM_MAX, + number: roomNumberOf(rootId), + line: c.line === "SWITCHBOARD" ? "SWITCHBOARD" : "DND" + } + } + + const collectRooms = async (idx) => { + const tribeKeyCache = new Map() + const items = [] + for (const [rootId, tipId] of idx.tipByRoot.entries()) { + if (idx.tomb.has(tipId)) continue + const node = idx.nodes.get(tipId) + if (!node || node.c.type !== "room") continue + let tKeys = [] + if (node.c.tribeId) { + if (!tribeKeyCache.has(node.c.tribeId)) tribeKeyCache.set(node.c.tribeId, await getTribeKeysFor(node.c.tribeId)) + tKeys = tribeKeyCache.get(node.c.tribeId) + } + const room = buildRoom(node, rootId, tKeys, idx.resolveMembers(rootId)) + if (!room) continue + room.isClosed = room.status === "CLOSED" + items.push(room) + } + return items + } + + const pickHub = async () => { + const ph = await phone() + const me = (await openSsb()).id + const h = ph && typeof ph.roomHubFor === "function" ? await rpcValue(ph.roomHubFor) : null + if (h && FEED_ID.test(String(h.key || ""))) return { hub: h.key, hubAddress: String(h.address || "") } + return { hub: me, hubAddress: "" } + } + + const tokenFor = async (rid) => { + const ph = await phone() + const t = ph && typeof ph.roomToken === "function" ? await rpcValue(ph.roomToken, rid) : null + return typeof t === "string" ? t : "" + } + + const republish = async (id, mutate) => { + const self = api + const tipId = await self.resolveCurrentId(id) + const ssbClient = await openSsb() + const userId = ssbClient.id + const rootId = await self.resolveRootId(id) + const item = await asP(ssbClient.get, tipId).catch(() => null) + if (!item || !item.content) throw new Error("Room not found") + if (item.content.author !== userId) throw new Error("Not the author") + const c = item.content + let keyHex = null + let usesTribeKey = false + if (c.encrypted === true) { + if (c.tribeId) { + const tKeys = await getTribeKeysFor(c.tribeId) + if (tKeys.length) { keyHex = tKeys[0]; usesTribeKey = true } + } + if (!keyHex) keyHex = lookupKey(rootId) + if (!keyHex) throw new Error("Missing room key — cannot update room") + } + const enc = (text) => (c.encrypted === true ? encryptField(String(text), keyHex) : String(text)) + const updated = { ...mutate(c, enc), updatedAt: new Date().toISOString(), replaces: tipId } + await asP(ssbClient.publish, { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: userId }) + const res = await asP(ssbClient.publish, updated) + if (keyHex && !usesTribeKey) setRoomKey(res.key, keyHex) + return res + } + + const api = { + type: "room", + ROOM_MAX, + + async decryptContent(content, rootId) { + const tKeys = content && content.tribeId ? await getTribeKeysFor(content.tribeId) : [] + return decryptRoomFields(content, rootId, tKeys) + }, + + decryptContentPublicSync(content) { + if (!content) return null + if (content.encrypted !== true) return { title: safeText(content.title), description: safeText(content.description), tags: normalizeTags(content.tags) } + if (content.tribeId) return null + const keyHex = tryDecryptPublicInviteKey(content.invites) + if (!keyHex) return null + const f = decryptWithKeys(content, [keyHex]) + return f ? { title: f.title, description: f.description, tags: f.tags } : null + }, + + async resolveRootId(id) { + const ssbClient = await openSsb() + const idx = buildIndex(await readAll(ssbClient)) + const root = idx.groupRootOf(id) + if (idx.tomb.has(idx.contentTipOf(root))) throw new Error("Not found") + return root + }, + + async resolveCurrentId(id) { + const ssbClient = await openSsb() + const idx = buildIndex(await readAll(ssbClient)) + const tip = idx.contentTipOf(idx.groupRootOf(id)) + if (idx.tomb.has(tip)) throw new Error("Not found") + return tip + }, + + async createRoom({ title, description, image, status, tags, tribeId } = {}) { + const ssbClient = await openSsb() + const userId = ssbClient.id + const now = new Date().toISOString() + const type = tribeId ? "INVITE-ONLY" : normalizeStatus(status) + const tagsArr = normalizeTags(tags) + const rid = crypto.randomBytes(16).toString("hex") + const roomId = crypto.randomBytes(16).toString("hex") + const sealed = !!tribeId || type !== "OPEN" + const live = { token: await tokenFor(sealed ? roomId : rid), ...(await pickHub()), media: "audio" } + const base = { type: "room", status: tribeId ? "OPEN" : type, author: userId, members: [userId], invites: [], createdAt: now, updatedAt: now, ...live } + + if (type === "OPEN" && !tribeId) { + return asP(ssbClient.publish, { ...base, title: safeText(title), description: safeText(description), image: blobRef(image), tags: tagsArr, rid, encrypted: false }) + } + + let keyHex = null + let usesTribeKey = false + if (tribeId) { + const tKeys = await getTribeKeysFor(tribeId) + if (tKeys.length) { keyHex = tKeys[0]; usesTribeKey = true } + } + if (!keyHex) keyHex = crypto.randomBytes(32).toString("hex") + const enc = (text) => encryptField(String(text), keyHex) + const msg = await asP(ssbClient.publish, { + ...base, + title: enc(safeText(title)), + description: enc(safeText(description)), + image: enc(blobRef(image)), + tags: enc(tagsArr.join(",")), + roomId, + encrypted: true, + ...(tribeId ? { tribeId } : {}) + }) + if (!usesTribeKey) { + setRoomKey(msg.key, keyHex) + if (tribeCrypto) { + try { + const ssbKeys = require("../server/node_modules/ssb-keys") + await asP(ssbClient.publish, { type: "tribe-keys", tribeId: msg.key, generation: 1, memberKeys: { [userId]: tribeCrypto.boxKeyForMember(keyHex, userId, ssbKeys) } }) + } catch (_) {} + } + } + return msg + }, + + async updateRoomById(id, data = {}) { + const hub = data.refreshHub ? await pickHub() : null + return republish(id, (c, enc) => ({ + ...c, + title: data.title !== undefined ? enc(safeText(data.title)) : c.title, + description: data.description !== undefined ? enc(safeText(data.description)) : c.description, + image: data.image !== undefined ? enc(blobRef(data.image)) : c.image, + tags: data.tags !== undefined ? (c.encrypted === true ? enc(normalizeTags(data.tags).join(",")) : normalizeTags(data.tags)) : c.tags, + invites: data.invites !== undefined ? data.invites : c.invites, + ...(hub || {}) + })) + }, + + async rehomeHub(id) { + const ssbClient = await openSsb() + const picked = await pickHub() + if (!picked || picked.hub === ssbClient.id) return null + return republish(id, (c) => ({ ...c, ...picked })) + }, + + async closeRoomById(id) { + return republish(id, (c) => ({ ...c, status: "CLOSED" })) + }, + + async setRoomLine(id, line) { + return republish(id, (c) => ({ ...c, line: line === "SWITCHBOARD" ? "SWITCHBOARD" : "DND" })) + }, + + async deleteRoomById(id) { + const tipId = await this.resolveCurrentId(id) + const ssbClient = await openSsb() + const item = await asP(ssbClient.get, tipId).catch(() => null) + if (!item || !item.content) throw new Error("Room not found") + if (item.content.author !== ssbClient.id) throw new Error("Not the author") + await asP(ssbClient.publish, { type: "tombstone", target: tipId, deletedAt: new Date().toISOString(), author: ssbClient.id }) + }, + + async addMemberToRoom(roomId, feedId, consumedCode) { + const tipId = await this.resolveCurrentId(roomId) + const ssbClient = await openSsb() + const rootId = await this.resolveRootId(roomId) + const item = await asP(ssbClient.get, tipId).catch(() => null) + if (!item || !item.content) throw new Error("Room not found") + const c = item.content + if (c.encrypted === true && !c.tribeId && !lookupKey(rootId)) { + const key = tryDecryptPublicInviteKey(c.invites) + if (key) setRoomKey(rootId, key) + } + const proofKey = feedId === ssbClient.id && c.encrypted === true && !c.tribeId ? lookupKey(rootId) : null + return asP(ssbClient.publish, { type: "roomMember", target: rootId, member: feedId, on: true, createdAt: new Date().toISOString(), ...(typeof consumedCode === "string" && consumedCode ? { code: consumedCode } : {}), ...(proofKey ? { keyProof: keyProofFor(proofKey, feedId) } : {}) }) + }, + + async leaveRoomMembership(roomId) { + const ssbClient = await openSsb() + const userId = ssbClient.id + const room = await this.getRoomById(roomId) + if (!room) throw new Error("Room not found") + if (room.author === userId) throw new Error("Author cannot leave their own room") + if (!room.members.includes(userId)) return + await asP(ssbClient.publish, { type: "roomMember", target: room.rootId, member: userId, on: false, createdAt: new Date().toISOString() }) + }, + + async ingestKeys() { await ingestOwnTribeKeys() }, + + async pruneOrphanKeys() { + if (!ownCrypto || typeof ownCrypto.getAllRootIds !== "function") return 0 + try { + const ssbClient = await openSsb() + const tomb = buildValidatedTombstoneSet(await readAll(ssbClient)) + let removed = 0 + for (const rid of ownCrypto.getAllRootIds()) { + if (tomb.has(rid)) { try { ownCrypto.dropKey(rid); removed += 1 } catch (_) {} } + } + return removed + } catch (_) { return 0 } + }, + + async getRoomById(id) { + const ssbClient = await openSsb() + const idx = memoIndex('rooms', await readAll(ssbClient), buildIndex) + const root = idx.groupRootOf(id) + const tip = idx.contentTipOf(root) + if (idx.tomb.has(tip)) return null + const node = idx.nodes.get(tip) + if (!node || node.c.type !== "room") return null + const tKeys = node.c.tribeId ? await getTribeKeysFor(node.c.tribeId) : [] + const room = buildRoom(node, root, tKeys, idx.resolveMembers(root)) + if (!room) return null + room.isClosed = room.status === "CLOSED" + return roomCollab.fold(room, await collectRooms(idx)) + }, + + async listAll({ filter = "all", viewerId } = {}) { + const ssbClient = await openSsb() + const uid = viewerId || ssbClient.id + const messages = await readAll(ssbClient) + const idx = buildIndex(messages) + let list = roomCollab.visibleThenCollapsed(await collectRooms(idx), uid) + try { await ensureMemberKeys(ssbClient, messages, list) } catch (_) {} + if (filter === "mine") list = list.filter(r => r.author === uid) + else if (filter === "recent") list = list.filter(r => new Date(r.createdAt).getTime() >= Date.now() - RECENT_MS) + else if (filter === "open") list = list.filter(r => r.type === "OPEN" && !r.isClosed) + else if (filter === "invite") list = list.filter(r => r.type === "INVITE-ONLY" && !r.isClosed) + else if (filter === "closed") list = list.filter(r => r.isClosed) + return list.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + }, + + async generateInvite(roomId, opts = {}) { + const ssbClient = await openSsb() + const room = await this.getRoomById(roomId) + if (!room) throw new Error("Room not found") + if (room.author !== ssbClient.id) throw new Error("Only the author can generate invites") + const rootId = await this.resolveRootId(roomId) + const keyHex = lookupKey(rootId) + const code = crypto.randomBytes(INVITE_BYTES).toString("hex") + let invite = code + const pubFlag = opts.public ? { public: true } : {} + const inviteSalt = generateInviteSalt() + if (keyHex) { + invite = opts.public + ? { code, ek: encryptForInvite(keyHex, code, inviteSalt), salt: inviteSalt, ...pubFlag } + : { ch: hashCode(code, inviteSalt), ek: encryptForInvite(keyHex, code, inviteSalt), salt: inviteSalt } + } else { + invite = opts.public ? { code, public: true } : { ch: hashCode(code, inviteSalt), salt: inviteSalt } + } + await this.updateRoomById(roomId, { invites: [...room.invites, invite] }) + return code + }, + + async getOpenInvite(roomId) { + return openInviteOf(await this.getRoomById(roomId).catch(() => null)) + }, + + async generateOpenInvite(roomId) { + const room = await this.getRoomById(roomId) + if (!room) throw new Error("Room not found") + if ((Array.isArray(room.invites) ? room.invites : []).find(inv => typeof inv === "object" && inv.public === true)) throw new Error("An open invitation already exists") + return this.generateInvite(roomId, { public: true }) + }, + + async removeOpenInvite(roomId) { + const ssbClient = await openSsb() + const room = await this.getRoomById(roomId) + if (!room) throw new Error("Room not found") + if (room.author !== ssbClient.id) throw new Error("Only the author can remove invites") + await this.updateRoomById(roomId, { invites: (Array.isArray(room.invites) ? room.invites : []).filter(inv => !(typeof inv === "object" && inv.public === true)) }) + await rotateRoomKey(await this.resolveRootId(roomId), [...new Set([room.author, ...(Array.isArray(room.members) ? room.members : [])])]) + }, + + async joinByInvite(code) { + const ssbClient = await openSsb() + const userId = ssbClient.id + const rooms = await collectRooms(buildIndex(await readAll(ssbClient))) + let matchedRoom = null + let matchedInvite = null + for (const r of rooms) { + for (const inv of r.invites) { + if (inviteMatches(inv, code)) { matchedRoom = r; matchedInvite = inv; break } + } + if (matchedRoom) break + } + if (!matchedRoom) throw new Error("Invalid or expired invite code") + if (matchedRoom.members.includes(userId)) throw new Error("Already a member") + const isPublic = typeof matchedInvite === "object" && matchedInvite.public === true + const resolvedRootId = await this.resolveRootId(matchedRoom.rootId) + const token = inviteToken(matchedInvite) || code + if (!isPublic) { + const idx = buildIndex(await readAll(ssbClient)) + if (idx.isCodeConsumed(resolvedRootId, token) || idx.isCodeConsumed(resolvedRootId, code)) throw new Error("Invite already used") + } + let roomKey = null + if (typeof matchedInvite === "object" && matchedInvite.ek) { + roomKey = decryptFromInvite(matchedInvite.ek, code, matchedInvite.salt) + if (roomKey) setRoomKey(resolvedRootId, roomKey) + } + await this.addMemberToRoom(matchedRoom.rootId, userId, isPublic ? "" : token) + if (tribeCrypto && roomKey) { + try { + const ssbKeys = require("../server/node_modules/ssb-keys") + const memberKeys = {} + try { memberKeys[userId] = tribeCrypto.boxKeyForMember(roomKey, userId, ssbKeys) } catch (_) {} + if (matchedRoom.author && matchedRoom.author !== userId) { + try { memberKeys[matchedRoom.author] = tribeCrypto.boxKeyForMember(roomKey, matchedRoom.author, ssbKeys) } catch (_) {} + } + if (Object.keys(memberKeys).length) await asP(ssbClient.publish, { type: "tribe-keys", tribeId: resolvedRootId, generation: 1, memberKeys }) + } catch (_) {} + } + return matchedRoom.rootId + }, + + async liveParams(room) { + if (!room || !RID.test(String(room.rid || "")) || !room.token) return null + let secrets = null + if (room.tribeId) secrets = (await getTribeKeysFor(room.tribeId)).slice(0, 1) + else if (room.type === "INVITE-ONLY") secrets = lookupKeys(room.rootId).slice(0, 1) + return { + rid: room.rid, owner: room.author, token: room.token, hub: room.hub, address: room.hubAddress, + ref: room.rootId, title: room.title, secrets: Array.isArray(secrets) && secrets.length ? secrets : null, + ...(room.roomId ? { roomId: room.roomId } : {}) + } + }, + + async occupancy(room) { + const ph = await phone() + if (!ph || typeof ph.roomCount !== "function" || !room || !RID.test(String(room.rid || ""))) return null + try { return await asP(ph.roomCount, { rid: room.rid, hub: room.hub }) } catch (_) { return null } + }, + + async occupancies(rooms) { + const out = new Map() + await Promise.all((rooms || []).map(async (r) => { const o = await this.occupancy(r); if (o) out.set(r.rootId, o) })) + return out + }, + + async join(room) { + const ph = await phone() + if (!ph || typeof ph.roomJoin !== "function") throw new Error("unavailable") + const params = await this.liveParams(room) + if (!params) throw new Error("invalid") + return asP(ph.roomJoin, params) + }, + + async leave() { + const ph = await phone() + if (ph && typeof ph.roomLeave === "function") await asP(ph.roomLeave).catch(() => null) + }, + + async mute(flag) { + const ph = await phone() + if (!ph || typeof ph.roomMute !== "function") return null + return asP(ph.roomMute, !!flag) + }, + + async hand(flag) { + const ph = await phone() + if (!ph || typeof ph.roomHand !== "function") return null + return asP(ph.roomHand, !!flag) + }, + + async silence(id, flag) { + const ph = await phone() + if (!ph || typeof ph.silence !== "function") return null + return asP(ph.silence, { id, on: !!flag }) + }, + + async camera(flag) { + const ph = await phone() + if (!ph || typeof ph.camera !== "function") return null + return asP(ph.camera, !!flag) + }, + + async recStart() { + const ph = await phone() + if (!ph || typeof ph.roomRecStart !== "function") return null + return asP(ph.roomRecStart) + }, + + async recStop() { + const ph = await phone() + if (!ph || typeof ph.roomRecStop !== "function") return null + return asP(ph.roomRecStop) + }, + + async notify(flag) { + const ph = await phone() + if (!ph || typeof ph.roomNotify !== "function") return null + return asP(ph.roomNotify, !!flag) + }, + + async clearEvents() { + const ph = await phone() + if (!ph || typeof ph.roomClearEvents !== "function") return null + return asP(ph.roomClearEvents) + }, + + async liveState() { + const ph = await phone() + return ph && typeof ph.roomState === "function" ? rpcValue(ph.roomState) : null + }, + + async findByNumber(value, viewerId) { + const number = normalizeNumber(value) + if (!number) return [] + return (await this.listAll({ filter: "all", viewerId })).filter(r => r.number === number && !r.isClosed) + } + } + return api +} diff --git a/src/models/school_model.js b/src/models/school_model.js new file mode 100644 index 0000000..7e63678 --- /dev/null +++ b/src/models/school_model.js @@ -0,0 +1,1423 @@ +const pull = require("../server/node_modules/pull-stream") +const moment = require("../server/node_modules/moment") +const { getConfig } = require("../configs/config-manager.js") +const { readTyped, memoIndex } = require("./typed_log") +const { buildValidatedTombstoneSet } = require("./tombstone_validator") +const { versionGroups } = require("../backend/dedupe") +const opinionCategories = require("../backend/opinion_categories") +const logLimit = getConfig().ssbLogStream?.limit || 1000 + +const norm = (s) => String(s || "").trim().toLowerCase() +const toNum = (v) => { + const n = parseFloat(String(v ?? "").replace(",", ".")) + return Number.isFinite(n) ? n : NaN +} + +const normalizeTags = (raw) => { + if (raw === undefined || raw === null) return [] + if (Array.isArray(raw)) return raw.map(t => String(t || "").trim()).filter(Boolean) + return String(raw).split(",").map(t => t.trim()).filter(Boolean) +} + +const normalizeDate = (raw) => { + const t = Date.parse(String(raw || "")) + return Number.isFinite(t) ? new Date(t).toISOString() : null +} + +const normalizeIds = (raw) => { + if (raw === undefined || raw === null) return [] + const arr = Array.isArray(raw) ? raw : String(raw).split(/[\s,]+/) + return Array.from(new Set(arr.map(t => String(t || "").trim()).filter(t => t.startsWith("@")))) +} + +const matchSearch = (course, q) => { + const qq = norm(q) + if (!qq) return true + const hay = [ + course.title, + course.description, + Array.isArray(course.tags) ? course.tags.join(" ") : "" + ].map(x => norm(x)).join(" ") + return hay.includes(qq) +} + +module.exports = ({ cooler, transfersModel, schoolCrypto, chatsModel }) => { + const ssbKeys = require("../server/node_modules/ssb-keys") + const nodeCrypto = require("crypto") + let ssb + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb } + + const SCHOOL_TYPES = [ + "schoolCourse", "schoolLesson", "schoolLessonMedia", "schoolExam", "schoolExamQuestion", + "schoolExamResult", "schoolProgress", "schoolOpinion", "schoolCertificate", "schoolCommentHide", + "schoolEnroll", "school-invite", "tombstone", "tribe-keys", "transfer", "transferConfirm", "chatMember", "chat" + ] + + const readAll = async (ssbClient) => readTyped(ssbClient, SCHOOL_TYPES, { limit: logLimit, withWindow: true, withPrivate: true }) + + const indexOf = (messages, ssbClient) => memoIndex('school', messages, (m) => buildIndex(m, ssbClient)) + + const buildIndex = (messages, ssbClient) => { + const tomb = buildValidatedTombstoneSet(messages) + const courseNodes = new Map() + const lessonNodes = new Map() + const certNodes = new Map() + const lessonReplaces = new Map() + const enrollLatest = new Map() + const transferAgg = new Map() + const transferNodes = new Map() + const transferConfirms = [] + const tribeKeyMsgs = [] + const opinionMsgs = [] + const examNodes = new Map() + const questionNodes = new Map() + const progressLatest = new Map() + const examResultLatest = new Map() + const chatByCourse = new Map() + const chatMsgs = [] + + for (const m of messages) { + const key = m.key + const v = m.value || {} + const c = v.content + if (!c) continue + + if (c.type === "chat") { + if (typeof c.courseId === "string" && c.courseId) chatMsgs.push({ courseId: c.courseId, key, author: v.author }) + continue + } + + if (c.type === "tombstone") continue + + if (c.type === "schoolCourse") { + courseNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, seq: v.sequence || 0, c, author: v.author }) + continue + } + + if (c.type === "schoolLesson" && c.courseId) { + lessonNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + if (c.replaces) lessonReplaces.set(key, c.replaces) + continue + } + + if (c.type === "schoolCertificate" && c.courseId) { + certNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + continue + } + + if (c.type === "schoolEnroll" && c.courseId) { + const author = v.author + if (!author) continue + const ts = v.timestamp || m.timestamp || 0 + const k = `${c.courseId}::${author}` + const prev = enrollLatest.get(k) + if (!prev || ts >= prev.ts) enrollLatest.set(k, { ts, value: !!c.value, author, courseId: c.courseId, transferId: c.transferId || null, keyProof: c.keyProof || null }) + continue + } + + if (c.type === "transfer") { + if (!v.author || c.from !== v.author) continue + transferNodes.set(key, { author: v.author, to: c.to, amount: toNum(c.amount), concept: String(c.concept || ""), discarded: String(c.status || "").toUpperCase() === "DISCARDED", replaces: c.replaces || null }) + continue + } + + if (c.type === "transferConfirm" && c.target) { + if (v.author) transferConfirms.push({ target: c.target, author: v.author }) + continue + } + + if (c.type === "tribe-keys" && c.tribeId && c.memberKeys && typeof c.memberKeys === "object") { + tribeKeyMsgs.push({ tribeId: c.tribeId, memberKeys: c.memberKeys, author: v.author }) + continue + } + + if (c.type === "schoolOpinion" && c.target) { + opinionMsgs.push({ target: c.target, author: v.author, category: c.category }) + continue + } + + if (c.type === "schoolLessonMedia" && c.courseId && c.target) { + examNodes.set("media:" + key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author, isMedia: true }) + continue + } + + if (c.type === "schoolExamQuestion" && c.courseId && c.target) { + questionNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + continue + } + + if (c.type === "schoolExam" && c.courseId) { + examNodes.set(key, { key, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) + continue + } + + if (c.type === "schoolProgress" && c.courseId && c.lessonId) { + const ts = v.timestamp || m.timestamp || 0 + const k = `${c.courseId}::${c.lessonId}::${v.author}` + const prev = progressLatest.get(k) + if (!prev || ts >= prev.ts) progressLatest.set(k, { ts, value: c.value !== false, author: v.author, courseId: c.courseId, lessonId: c.lessonId }) + continue + } + } + + if (ssbClient && ssbClient.private && typeof ssbClient.private.unbox === "function") { + for (const m of messages) { + const sealed = typeof m.value?.content === "string" + if (!sealed && m.value?.private !== true) continue + try { + const dec = sealed ? ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.value?.timestamp || m.timestamp || 0 }) : m + const c = dec?.value?.content + const author = dec?.value?.author + if (!c || !author) continue + const ts = dec.value.timestamp || m.timestamp || 0 + if (c.type === "schoolEnroll" && c.courseId) { + const k = `${c.courseId}::${author}` + const prev = enrollLatest.get(k) + if (!prev || ts >= prev.ts) enrollLatest.set(k, { ts, value: !!c.value, author, courseId: c.courseId, transferId: c.transferId || null, keyProof: c.keyProof || null }) + continue + } + if (c.type === "schoolProgress" && c.courseId && c.lessonId) { + const k = `${c.courseId}::${c.lessonId}::${author}` + const prev = progressLatest.get(k) + if (!prev || ts >= prev.ts) progressLatest.set(k, { ts, value: c.value !== false, author, courseId: c.courseId, lessonId: c.lessonId }) + continue + } + if (c.type === "schoolExamResult" && c.courseId && c.examId) { + const k = `${c.examId}::${author}` + const prev = examResultLatest.get(k) + if (!prev || ts >= prev.ts) examResultLatest.set(k, { ts, author, courseId: c.courseId, examId: c.examId, score: c.score, total: c.total }) + continue + } + } catch {} + } + } + + const transferChild = new Map() + const transferForked = new Set() + for (const [key, t] of transferNodes.entries()) { + const orig = t.replaces ? transferNodes.get(t.replaces) : null + if (!orig || orig.author !== t.author) continue + if (transferChild.has(t.replaces)) transferForked.add(t.replaces) + transferChild.set(t.replaces, key) + } + const confirmersOf = new Map() + for (const cf of transferConfirms) { + if (!confirmersOf.has(cf.target)) confirmersOf.set(cf.target, new Set()) + confirmersOf.get(cf.target).add(cf.author) + } + for (const [key, t] of transferNodes.entries()) { + const orig = t.replaces ? transferNodes.get(t.replaces) : null + if (orig && orig.author === t.author) continue + const chain = [key] + while (transferChild.has(chain[chain.length - 1])) chain.push(transferChild.get(chain[chain.length - 1])) + const tipKey = chain[chain.length - 1] + const tip = transferNodes.get(tipKey) + const signers = new Set() + for (const ck of chain) for (const signer of (confirmersOf.get(ck) || [])) if (signer === tip.to) signers.add(signer) + const bill = { author: tip.author, to: tip.to, amount: tip.amount, concept: tip.concept, void: tip.discarded || tomb.has(tipKey) || chain.some(ck => transferForked.has(ck)), signers } + for (const ck of chain) transferAgg.set(ck, bill) + } + + const lessonParent = new Map() + for (const [key, target] of lessonReplaces.entries()) { + const node = lessonNodes.get(key) + const orig = lessonNodes.get(target) + if (node && orig && String(orig.author) === String(node.author)) lessonParent.set(key, target) + } + const lessonChildOf = new Map() + for (const [k, t] of lessonParent.entries()) lessonChildOf.set(t, k) + const lessonRootOf = (id) => { + let cur = id + while (lessonParent.has(cur)) cur = lessonParent.get(cur) + return cur + } + + const vg = versionGroups(Array.from(courseNodes.values()).map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === "string" ? n.c.replaces : null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))) + const rootOf = vg.rootOf + const tipOf = vg.tipOf + + const tipByRoot = new Map() + for (const r of vg.roots) tipByRoot.set(r, tipOf(r)) + + const courseAuthorOf = (id) => { const n = courseNodes.get(rootOf(id)); return n ? n.author : null } + for (const [key, node] of lessonNodes.entries()) if (courseAuthorOf(node.c.courseId) !== node.author) lessonNodes.delete(key) + for (const [key, node] of certNodes.entries()) if (courseAuthorOf(node.c.courseId) !== node.author) certNodes.delete(key) + for (const [key, node] of examNodes.entries()) if (courseAuthorOf(node.c.courseId) !== node.author) examNodes.delete(key) + for (const [key, node] of questionNodes.entries()) if (courseAuthorOf(node.c.courseId) !== node.author) questionNodes.delete(key) + for (const ch of chatMsgs) { + if (chatByCourse.has(ch.courseId) || courseAuthorOf(ch.courseId) !== ch.author) continue + chatByCourse.set(ch.courseId, ch.key) + } + + const enrollByCourse = new Map() + for (const { courseId, author, value, transferId, keyProof } of enrollLatest.values()) { + const root = rootOf(courseId) + if (!enrollByCourse.has(root)) enrollByCourse.set(root, new Map()) + const enrollments = enrollByCourse.get(root) + if (value) enrollments.set(author, { transferId, keyProof: keyProof || null }) + else enrollments.delete(author) + } + + const opinionsByCourse = new Map() + for (const op of opinionMsgs) { + const root = rootOf(op.target) + if (!courseNodes.has(root)) continue + if (!opinionsByCourse.has(root)) opinionsByCourse.set(root, { byAuthor: new Map() }) + const agg = opinionsByCourse.get(root) + if (!op.author || agg.byAuthor.has(op.author)) continue + agg.byAuthor.set(op.author, op.category) + } + + const progressByCourse = new Map() + for (const pr of progressLatest.values()) { + if (!pr.value) continue + const root = rootOf(pr.courseId) + if (!courseNodes.has(root)) continue + if (!progressByCourse.has(root)) progressByCourse.set(root, new Map()) + const perStudent = progressByCourse.get(root) + if (!perStudent.has(pr.author)) perStudent.set(pr.author, new Set()) + perStudent.get(pr.author).add(lessonRootOf(pr.lessonId)) + } + + const grantsByCourse = new Map() + const myId = ssbClient ? ssbClient.id : null + for (const tk of tribeKeyMsgs) { + const root = rootOf(tk.tribeId) + if (!courseNodes.has(root)) continue + if (courseNodes.get(root).author !== tk.author) continue + if (!grantsByCourse.has(root)) grantsByCourse.set(root, new Set()) + for (const member of Object.keys(tk.memberKeys)) grantsByCourse.get(root).add(member) + if (schoolCrypto && myId && tk.memberKeys[myId]) { + try { + const cfg = require("../server/ssb_config") + const unboxed = ssbKeys.unbox(tk.memberKeys[myId], cfg.keys) + if (typeof unboxed === "string" && unboxed) schoolCrypto.addNewKey(root, unboxed) + } catch {} + } + } + + return { tomb, courseNodes, lessonNodes, certNodes, examNodes, vg, rootOf, tipOf, tipByRoot, enrollByCourse, transferAgg, grantsByCourse, opinionsByCourse, progressByCourse, examResultLatest, lessonRootOf, lessonChildOf, questionNodes, chatByCourse } + } + + const keyProofFor = (keyHex, studentId) => { + try { return nodeCrypto.createHmac("sha256", Buffer.from(keyHex, "hex")).update(String(studentId), "utf8").digest("hex") } catch (_) { return null } + } + + const buildCourseObject = (node, rootId, enrollments, transferAgg, grants, opinionAgg, chatByCourse = new Map()) => { + let c = node.c || {} + const ringKeys = schoolCrypto ? (schoolCrypto.getKeys(rootId) || []) : [] + if (c.encryptedPayload) { + const dec = ringKeys.length && schoolCrypto ? schoolCrypto.decryptContent(c, ringKeys.map(k => [k])) : { ...c, _undecryptable: true } + if (dec._undecryptable) return { undecryptable: true, id: node.key, rootId, author: node.author } + c = dec + } + let blobId = c.image || null + if (blobId && /\(([^)]+)\)/.test(String(blobId))) blobId = String(blobId).match(/\(([^)]+)\)/)[1] + + const priceN = toNum(c.price) + const paid = Number.isFinite(priceN) && priceN > 0 + const students = [] + const pending = [] + const grantedViaInvite = [] + for (const [student, info] of (enrollments || new Map())) { + if (info.keyProof && ringKeys.some(k => keyProofFor(k, student) === info.keyProof)) grantedViaInvite.push(student) + if (paid) { + const bill = info.transferId ? (transferAgg && transferAgg.get(info.transferId)) : null + const settled = !!bill && !bill.void && bill.author === student && bill.to === node.author && bill.amount >= priceN && bill.concept.includes(rootId) && bill.signers.has(node.author) + if (settled) students.push(student) + else pending.push({ author: student, transferId: info.transferId || null }) + } else { + students.push(student) + } + } + const studentOpinions = { opinions: {}, voters: [] } + for (const [author, category] of ((opinionAgg && opinionAgg.byAuthor) || new Map())) { + if (!students.includes(author)) continue + studentOpinions.voters.push(author) + if (category) studentOpinions.opinions[category] = (studentOpinions.opinions[category] || 0) + 1 + } + return { + id: node.key, + rootId, + title: c.title, + description: c.description, + tags: Array.isArray(c.tags) ? c.tags : normalizeTags(c.tags), + image: blobId, + price: Number.isFinite(priceN) && priceN > 0 ? priceN.toFixed(6) : "0.000000", + visibility: String(c.visibility || "PUBLIC").toUpperCase() === "INVITE" ? "INVITE" : "PUBLIC", + status: String(c.status || "ONGOING").toUpperCase() === "CLOSED" ? "CLOSED" : "ONGOING", + invited: normalizeIds(c.invited).filter(id => !students.includes(id)), + startDate: c.startDate || null, + chatId: c.chatId || chatByCourse.get(rootId) || null, + inviteCode: c.inviteCode || null, + author: node.author, + createdAt: c.createdAt || new Date(node.ts).toISOString(), + updatedAt: c.updatedAt || null, + students, + pending, + granted: Array.from(new Set([...(grants ? Array.from(grants) : []), ...grantedViaInvite])), + opinions: studentOpinions.opinions, + opinions_inhabitants: studentOpinions.voters + } + } + + const isProtected = (course) => Number(course.price) > 0 || course.visibility === "INVITE" + + const addToChat = async (ssbClient, course, members) => { + if (!course.chatId || !members.length) return + for (const member of members) { + try { + await new Promise((res) => ssbClient.publish({ type: "chatMember", target: course.chatId, member, on: true, createdAt: new Date().toISOString() }, () => res())) + } catch {} + } + if (chatsModel && typeof chatsModel.listAll === "function") { + try { await chatsModel.listAll({ filter: "all", viewerId: ssbClient.id }) } catch {} + } + } + + const canView = (course, viewer) => + course.visibility === "PUBLIC" || + course.author === viewer || + course.invited.includes(viewer) || + course.students.includes(viewer) || + course.pending.some(p => p.author === viewer) + + const ensureCourseKey = async (ssbClient, rootId) => { + if (!schoolCrypto) return null + let key = schoolCrypto.getKey(rootId) + if (key) return key + key = schoolCrypto.generateTribeKey() + schoolCrypto.setKey(rootId, key, 1) + await publishKeyGrant(ssbClient, rootId, key, [ssbClient.id]) + return key + } + + const publishKeyGrant = (ssbClient, rootId, key, members) => { + const memberKeys = {} + for (const member of members) { + try { memberKeys[member] = schoolCrypto.boxKeyForMember(key, member, ssbKeys) } catch {} + } + if (!Object.keys(memberKeys).length) return Promise.resolve(null) + return new Promise((res) => ssbClient.publish({ type: "tribe-keys", tribeId: rootId, generation: 1, memberKeys }, () => res(null))) + } + + return { + type: "schoolCourse", + + async createCourse(data) { + const ssbClient = await openSsb() + + const title = String(data.title || "").trim() + const description = String(data.description || "").trim() + if (!title) throw new Error("Invalid title") + if (!description) throw new Error("Invalid description") + + let blobId = data.image || null + if (blobId && /\(([^)]+)\)/.test(String(blobId))) blobId = String(blobId).match(/\(([^)]+)\)/)[1] + + const priceN = toNum(data.price) + const protectedCourse = (Number.isFinite(priceN) && priceN > 0) || String(data.visibility || "PUBLIC").toUpperCase() === "INVITE" + const content = { + type: "schoolCourse", + title, + description, + tags: normalizeTags(data.tags), + image: blobId, + price: Number.isFinite(priceN) && priceN > 0 ? priceN.toFixed(6) : "0.000000", + visibility: String(data.visibility || "PUBLIC").toUpperCase() === "INVITE" ? "INVITE" : "PUBLIC", + status: "ONGOING", + invited: normalizeIds(data.invited), + startDate: (() => { + const d = normalizeDate(data.startDate) + if (d && d.slice(0, 10) < new Date().toISOString().slice(0, 10)) throw new Error("Start date cannot be in the past") + return d + })(), + chatId: null, + author: ssbClient.id, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + } + + let toPublish = content + let courseKey = null + if (content.visibility === "INVITE" && schoolCrypto) { + courseKey = schoolCrypto.generateTribeKey() + toPublish = schoolCrypto.encryptContent(content, [courseKey], true) + } + const published = await new Promise((res, rej) => ssbClient.publish(toPublish, (e, m) => e ? rej(e) : res(m))) + if (published && published.key && chatsModel) { + try { + await chatsModel.createChat(`Course: ${title}`, description, blobId, "school", protectedCourse ? "INVITE-ONLY" : "OPEN", normalizeTags(data.tags), null, { courseId: published.key }) + } catch {} + } + if (published && published.key) { + if (courseKey) { + schoolCrypto.setKey(published.key, courseKey, 1) + await publishKeyGrant(ssbClient, published.key, courseKey, [ssbClient.id]) + } else if (Number(content.price) > 0) { + await ensureCourseKey(ssbClient, published.key) + } + } + return published + }, + + async resolveCurrentId(courseId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const { tomb, vg } = indexOf(messages, ssbClient) + + const cur = vg.tipOf(courseId) + if (tomb.has(cur) || vg.isForeignCopy(vg.rootOf(cur))) throw new Error("Course not found") + return cur + }, + + async resolveRootId(courseId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const { tomb, vg } = indexOf(messages, ssbClient) + + if (tomb.has(vg.tipOf(courseId)) || vg.isForeignCopy(vg.rootOf(courseId))) throw new Error("Course not found") + return vg.rootOf(courseId) + }, + + async updateCourse(id, data) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const tipId = await this.resolveCurrentId(id) + const node = idx.courseNodes.get(tipId) + if (!node || !node.c) throw new Error("Course not found") + + let existingContent = node.c + if (existingContent.encryptedPayload) { + const rootForKeys = idx.rootOf(tipId) + const keys = schoolCrypto ? (schoolCrypto.getKeys(rootForKeys) || []) : [] + const dec = keys.length && schoolCrypto ? schoolCrypto.decryptContent(existingContent, keys.map(k => [k])) : { ...existingContent, _undecryptable: true } + if (dec._undecryptable) throw new Error("Course not found") + delete dec._decrypted + existingContent = dec + } + const author = existingContent.author + if (author !== ssbClient.id) throw new Error("Unauthorized") + + const patch = {} + + if (data.title !== undefined) { + const t = String(data.title || "").trim() + if (!t) throw new Error("Invalid title") + patch.title = t + } + + if (data.description !== undefined) { + const d = String(data.description || "").trim() + if (!d) throw new Error("Invalid description") + patch.description = d + } + + if (data.tags !== undefined) patch.tags = normalizeTags(data.tags) + + if (data.image !== undefined) { + let blobId = data.image + if (blobId && /\(([^)]+)\)/.test(String(blobId))) blobId = String(blobId).match(/\(([^)]+)\)/)[1] + patch.image = blobId || null + } + + if (data.price !== undefined) { + const p = toNum(data.price) + if (Number.isFinite(p) && p < 0) throw new Error("Invalid price") + patch.price = Number.isFinite(p) && p > 0 ? p.toFixed(6) : "0.000000" + } + + if (data.visibility !== undefined) { + patch.visibility = String(data.visibility || "PUBLIC").toUpperCase() === "INVITE" ? "INVITE" : "PUBLIC" + } + + if (data.status !== undefined) { + const s = String(data.status || "").toUpperCase() + if (!["ONGOING", "CLOSED"].includes(s)) throw new Error("Invalid status") + patch.status = s + } + + if (data.invited !== undefined) patch.invited = normalizeIds(data.invited) + + if (data.inviteCode !== undefined) patch.inviteCode = String(data.inviteCode || "") || null + + if (data.startDate !== undefined) patch.startDate = normalizeDate(data.startDate) + + const next = { + ...existingContent, + ...patch, + author, + createdAt: existingContent.createdAt, + updatedAt: new Date().toISOString(), + replaces: tipId, + type: "schoolCourse" + } + + const tombMsg = { + type: "tombstone", + target: tipId, + deletedAt: new Date().toISOString(), + author: ssbClient.id + } + + await new Promise((res, rej) => ssbClient.publish(tombMsg, (e) => e ? rej(e) : res())) + let toPublish = next + if (String(next.visibility || "").toUpperCase() === "INVITE" && schoolCrypto) { + const key = await ensureCourseKey(ssbClient, idx.rootOf(tipId)) + if (key) toPublish = schoolCrypto.encryptContent(next, [key], true) + } + const published = await new Promise((res, rej) => ssbClient.publish(toPublish, (e, m) => e ? rej(e) : res(m))) + const linkedChatId = existingContent.chatId || idx.chatByCourse.get(idx.rootOf(tipId)) || null + if (patch.image !== undefined && linkedChatId && chatsModel && typeof chatsModel.updateChatById === "function") { + try { await chatsModel.updateChatById(linkedChatId, { image: patch.image }) } catch {} + } + return published + }, + + async updateCourseStatus(id, status) { + return this.updateCourse(id, { status: String(status || "").toUpperCase() }) + }, + + async deleteCourse(id) { + const ssbClient = await openSsb() + const tipId = await this.resolveCurrentId(id) + const course = await this.getCourseById(tipId) + if (!course || course.author !== ssbClient.id) throw new Error("Unauthorized") + + const tombMsg = { + type: "tombstone", + target: tipId, + deletedAt: new Date().toISOString(), + author: ssbClient.id + } + + return new Promise((res, rej) => ssbClient.publish(tombMsg, (e, r) => e ? rej(e) : res(r))) + }, + + async inviteStudent(id, studentId) { + const ssbClient = await openSsb() + const course = await this.getCourseById(id) + const ids = normalizeIds(studentId) + if (!ids.length) throw new Error("Invalid student id") + const updated = await this.updateCourse(course.id, { invited: course.invited.concat(ids) }) + if (isProtected(course) && schoolCrypto) { + const key = await ensureCourseKey(ssbClient, course.rootId) + if (key) await publishKeyGrant(ssbClient, course.rootId, key, ids) + } + return updated + }, + + async uninviteStudent(id, studentId) { + const course = await this.getCourseById(id) + const drop = new Set(normalizeIds(studentId)) + return this.updateCourse(course.id, { invited: course.invited.filter(s => !drop.has(s)) }) + }, + + async enroll(id) { + const ssbClient = await openSsb() + const me = ssbClient.id + + const course = await this.getCourseById(id) + if (course.author === me) throw new Error("Cannot enroll in your own course") + if (course.status !== "ONGOING") throw new Error("Course is closed") + if (course.students.includes(me)) return { alreadyEnrolled: true } + if (course.pending.some(p => p.author === me)) return { alreadyPending: true } + if (course.visibility === "INVITE" && !course.invited.includes(me)) throw new Error("Invitation required") + + const rootId = course.rootId || (await this.resolveRootId(id)) + + let transferId = null + if (Number(course.price) > 0) { + if (!transfersModel) throw new Error("Transfers module unavailable") + const deadline = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() + const bill = await transfersModel.createTransfer(course.author, `SCHOOL ${rootId}`, course.price, deadline, ["SCHOOL"], "ECONOMIC") + transferId = bill && bill.key ? bill.key : null + } + + const msg = { + type: "schoolEnroll", + courseId: rootId, + value: true, + transferId, + createdAt: new Date().toISOString() + } + + if (isProtected(course)) { + return new Promise((res, rej) => ssbClient.private.publish(msg, [me, course.author], (e, m) => e ? rej(e) : res(m))) + } + const published = await new Promise((res, rej) => ssbClient.publish(msg, (e, m) => e ? rej(e) : res(m))) + if (course.chatId && chatsModel && typeof chatsModel.joinChat === "function") { + try { await chatsModel.joinChat(course.chatId) } catch {} + } + return published + }, + + async unenroll(id) { + const ssbClient = await openSsb() + const me = ssbClient.id + + const course = await this.getCourseById(id) + if (!course.students.includes(me) && !course.pending.some(pn => pn.author === me)) return { notEnrolled: true } + + const rootId = course.rootId || (await this.resolveRootId(id)) + + const msg = { + type: "schoolEnroll", + courseId: rootId, + value: false, + createdAt: new Date().toISOString() + } + + if (isProtected(course)) { + return new Promise((res, rej) => ssbClient.private.publish(msg, [me, course.author], (e, m) => e ? rej(e) : res(m))) + } + return new Promise((res, rej) => ssbClient.publish(msg, (e, m) => e ? rej(e) : res(m))) + }, + + async addLesson(courseId, data) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + + const title = String(data.title || "").trim() + const text = String(data.text || "").trim() + if (!title) throw new Error("Invalid title") + if (!text) throw new Error("Invalid text") + + const rootId = course.rootId || (await this.resolveRootId(courseId)) + + const orderN = toNum(data.order) + let content = { + type: "schoolLesson", + courseId: rootId, + title, + text, + unit: String(data.unit || "").trim() || null, + order: Number.isFinite(orderN) ? orderN : null, + sessionDate: (() => { + const d = normalizeDate(data.sessionDate) + if (d && d.slice(0, 10) < new Date().toISOString().slice(0, 10)) throw new Error("Session date cannot be in the past") + return d + })(), + author: ssbClient.id, + createdAt: new Date().toISOString() + } + + if (isProtected(course) && schoolCrypto) { + const key = await ensureCourseKey(ssbClient, rootId) + if (key) content = schoolCrypto.encryptContent(content, [key], true) + } + + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async createOpinion(courseId, category) { + if (!opinionCategories.includes(category)) throw new Error("Invalid voting category") + const ssbClient = await openSsb() + const me = ssbClient.id + const course = await this.getCourseById(courseId) + if (course.author === me) throw new Error("Cannot rate your own course") + if (!course.students.includes(me)) throw new Error("Only students can rate a course") + if (course.opinions_inhabitants.includes(me)) throw new Error("Already voted") + + const content = { type: "schoolOpinion", target: course.rootId, category, createdAt: new Date().toISOString() } + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async markLesson(courseId, lessonId, value = true) { + const ssbClient = await openSsb() + const me = ssbClient.id + const course = await this.getCourseById(courseId) + if (!course.students.includes(me)) throw new Error("Only students can track progress") + + const msg = { + type: "schoolProgress", + courseId: course.rootId, + lessonId, + value: value !== false, + createdAt: new Date().toISOString() + } + + if (isProtected(course)) { + return new Promise((res, rej) => ssbClient.private.publish(msg, [me, course.author], (e, m) => e ? rej(e) : res(m))) + } + return new Promise((res, rej) => ssbClient.publish(msg, (e, m) => e ? rej(e) : res(m))) + }, + + async progressForCourse(courseId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const rootId = idx.rootOf(courseId) + const perStudent = idx.progressByCourse.get(rootId) || new Map() + const out = {} + for (const [student, lessonSet] of perStudent) out[student] = lessonSet.size + return out + }, + + async createExam(courseId, title, opts = {}) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + if (!isProtected(course)) throw new Error("Exams are only for paid or invite courses") + if (!schoolCrypto) throw new Error("School crypto unavailable") + + const examTitle = String(title || "").trim() + if (!examTitle) throw new Error("Invalid title") + + const rootId = course.rootId + const key = await ensureCourseKey(ssbClient, rootId) + if (!key) throw new Error("Course key unavailable") + + let content = { + type: "schoolExam", + courseId: rootId, + title: examTitle, + lessonId: opts.lessonId ? String(opts.lessonId) : null, + author: ssbClient.id, + createdAt: new Date().toISOString() + } + content = schoolCrypto.encryptContent(content, [key], true) + + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async addExamQuestion(courseId, examId, data) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + if (!schoolCrypto) throw new Error("School crypto unavailable") + + const q = String(data.q || "").trim() + const options = [data.o1, data.o2, data.o3, data.o4].map(o => String(o || "").trim()) + const correct = parseInt(String(data.correct), 10) + if (!q) throw new Error("Invalid question") + if (options.some(o => !o)) throw new Error("Four options are required") + if (!Number.isFinite(correct) || correct < 0 || correct > 3) throw new Error("Invalid correct option") + + const key = await ensureCourseKey(ssbClient, course.rootId) + if (!key) throw new Error("Course key unavailable") + + let content = { + type: "schoolExamQuestion", + courseId: course.rootId, + target: examId, + q, + options, + correct, + author: ssbClient.id, + createdAt: new Date().toISOString() + } + content = schoolCrypto.encryptContent(content, [key], true) + + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async deleteExamQuestion(questionId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const node = idx.questionNodes.get(questionId) + if (!node || idx.tomb.has(questionId)) throw new Error("Question not found") + if (node.author !== ssbClient.id) throw new Error("Unauthorized") + const tombMsg = { type: "tombstone", target: questionId, deletedAt: new Date().toISOString(), author: ssbClient.id } + return new Promise((res, rej) => ssbClient.publish(tombMsg, (e, r) => e ? rej(e) : res(r))) + }, + + async deleteExam(examId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const node = idx.examNodes.get(examId) + if (!node || node.isMedia || idx.tomb.has(examId)) throw new Error("Exam not found") + if (node.author !== ssbClient.id) throw new Error("Unauthorized") + const tombMsg = { type: "tombstone", target: examId, deletedAt: new Date().toISOString(), author: ssbClient.id } + return new Promise((res, rej) => ssbClient.publish(tombMsg, (e, r) => e ? rej(e) : res(r))) + }, + + async listExams(courseId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const rootId = idx.rootOf(courseId) + const keys = schoolCrypto ? (schoolCrypto.getKeys(rootId) || []) : [] + + const exams = [] + for (const node of idx.examNodes.values()) { + if (node.isMedia) continue + if (idx.tomb.has(node.key)) continue + if (idx.rootOf(node.c.courseId) !== rootId) continue + let c = node.c + let locked = false + if (c.encryptedPayload) { + const dec = keys.length && schoolCrypto ? schoolCrypto.decryptContent(c, keys.map(k => [k])) : { ...c, _undecryptable: true } + if (dec._undecryptable) locked = true + else c = dec + } + const myResult = idx.examResultLatest.get(`${node.key}::${ssbClient.id}`) || null + const results = [] + for (const r of idx.examResultLatest.values()) { + if (r.examId === node.key) results.push({ author: r.author, score: r.score, total: r.total }) + } + const questions = [] + if (!locked) { + for (const qn of idx.questionNodes.values()) { + if (idx.tomb.has(qn.key)) continue + if (qn.c.target !== node.key) continue + let qc = qn.c + if (qc.encryptedPayload) { + const dec = keys.length && schoolCrypto ? schoolCrypto.decryptContent(qc, keys.map(k => [k])) : { ...qc, _undecryptable: true } + if (dec._undecryptable) continue + qc = dec + } + questions.push({ id: qn.key, q: qc.q, options: qc.options, correct: qc.correct, createdAt: qc.createdAt || new Date(qn.ts).toISOString() }) + } + questions.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) + } + const passMark = locked ? null : 10 + exams.push({ + id: node.key, + courseId: rootId, + locked, + title: locked ? null : c.title, + lessonId: locked ? null : (c.lessonId ? idx.lessonRootOf(c.lessonId) : null), + passMark, + questions, + author: node.author, + createdAt: c.createdAt || new Date(node.ts).toISOString(), + myResult: myResult ? { score: myResult.score, total: myResult.total, at: myResult.ts, passed: passMark != null && Number(myResult.score) >= passMark } : null, + results: results.map(r => ({ ...r, passed: passMark != null && Number(r.score) >= passMark })) + }) + } + return exams.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) + }, + + async takeExam(courseId, examId, answers) { + const ssbClient = await openSsb() + const me = ssbClient.id + const course = await this.getCourseById(courseId) + if (course.author === me) throw new Error("Cannot take your own exam") + if (!course.students.includes(me)) throw new Error("Only students can take exams") + + const exams = await this.listExams(courseId) + const exam = exams.find(x => x.id === examId) + if (!exam || exam.locked) throw new Error("Exam not found") + if (exam.myResult && exam.myResult.passed) throw new Error("Exam already passed") + if (exam.myResult && Date.now() - Number(exam.myResult.at || 0) < 24 * 60 * 60 * 1000) throw new Error("Retry available after 24 hours") + + if (!exam.questions.length) throw new Error("Exam has no questions") + + let correctCount = 0 + exam.questions.forEach((question, i) => { + const given = parseInt(String(Array.isArray(answers) ? answers[i] : (answers || {})[`q${i}`]), 10) + if (given === question.correct) correctCount += 1 + }) + const score = Math.round((correctCount / exam.questions.length) * 100) / 10 + + const msg = { + type: "schoolExamResult", + courseId: course.rootId, + examId, + score, + total: 10, + createdAt: new Date().toISOString() + } + + return new Promise((res, rej) => ssbClient.private.publish(msg, [me, course.author], (e, m) => e ? rej(e) : res(m))) + }, + + async addLessonMaterial(courseId, lessonId, mediaMarkdown, caption = "") { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + const media = String(mediaMarkdown || "").trim() + if (!media) throw new Error("Invalid material") + + let content = { + type: "schoolLessonMedia", + courseId: course.rootId, + target: lessonId, + media, + caption: String(caption || "").trim() || null, + author: ssbClient.id, + createdAt: new Date().toISOString() + } + + if (isProtected(course) && schoolCrypto) { + const key = await ensureCourseKey(ssbClient, course.rootId) + if (key) content = schoolCrypto.encryptContent(content, [key], true) + } + + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async deleteLessonMaterial(materialId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const node = idx.examNodes.get("media:" + materialId) + if (!node || idx.tomb.has(materialId)) throw new Error("Material not found") + if (node.author !== ssbClient.id) throw new Error("Unauthorized") + const tombMsg = { type: "tombstone", target: materialId, deletedAt: new Date().toISOString(), author: ssbClient.id } + return new Promise((res, rej) => ssbClient.publish(tombMsg, (e, r) => e ? rej(e) : res(r))) + }, + + async listLessonMaterials(courseId, lessonId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const rootId = idx.rootOf(courseId) + const keys = schoolCrypto ? (schoolCrypto.getKeys(rootId) || []) : [] + + const materials = [] + for (const node of idx.examNodes.values()) { + if (!node.isMedia) continue + if (idx.tomb.has(node.key)) continue + if (idx.rootOf(node.c.courseId) !== rootId) continue + if (idx.lessonRootOf(node.c.target) !== idx.lessonRootOf(lessonId)) continue + let c = node.c + let locked = false + if (c.encryptedPayload) { + const dec = keys.length && schoolCrypto ? schoolCrypto.decryptContent(c, keys.map(k => [k])) : { ...c, _undecryptable: true } + if (dec._undecryptable) locked = true + else c = dec + } + materials.push({ + id: node.key, + lessonId, + locked, + media: locked ? null : (c.media || ""), + caption: locked ? null : (c.caption || null), + author: node.author, + createdAt: c.createdAt || new Date(node.ts).toISOString() + }) + } + return materials.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) + }, + + async generateInvite(courseId) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + if (course.visibility !== "INVITE") throw new Error("Only invite courses use invitation codes") + if (!schoolCrypto) throw new Error("School crypto unavailable") + + if (course.inviteCode) { + const messages = await readAll(ssbClient) + const alive = messages.some(m => { + const c = m.value && m.value.content + if (!c || c.type !== "school-invite" || c.target !== course.rootId || m.value.author !== course.author) return false + try { return schoolCrypto.hashInviteCode(course.inviteCode, c.salt) === c.codeHash } catch { return false } + }) + if (alive) return { code: course.inviteCode, courseId: course.rootId } + const key = await ensureCourseKey(ssbClient, course.rootId) + if (!key) throw new Error("Course key unavailable") + const salt = schoolCrypto.generateInviteSalt() + const ek = schoolCrypto.encryptForInvite(key, course.inviteCode, salt) + await new Promise((res, rej) => ssbClient.publish({ + type: "school-invite", target: course.rootId, ek, salt, + codeHash: schoolCrypto.hashInviteCode(course.inviteCode, salt) + }, (e) => e ? rej(e) : res())) + return { code: course.inviteCode, courseId: course.rootId } + } + + const key = await ensureCourseKey(ssbClient, course.rootId) + if (!key) throw new Error("Course key unavailable") + + const code = nodeCrypto.randomBytes(16).toString("hex") + const salt = schoolCrypto.generateInviteSalt() + const ek = schoolCrypto.encryptForInvite(key, code, salt) + await new Promise((res, rej) => ssbClient.publish({ + type: "school-invite", target: course.rootId, ek, salt, + codeHash: schoolCrypto.hashInviteCode(code, salt) + }, (e) => e ? rej(e) : res())) + try { await this.updateCourse(courseId, { inviteCode: code }) } catch (_) {} + return { code, courseId: course.rootId } + }, + + async joinByInvite(rawCode) { + if (!schoolCrypto) throw new Error("School crypto unavailable") + const ssbClient = await openSsb() + const me = ssbClient.id + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const candidates = Array.from(new Set([String(rawCode || "").trim(), String(rawCode || "").trim().toLowerCase()])).filter(Boolean) + let matched = null + let code = null + for (const m of messages) { + const c = m.value && m.value.content + if (!c || c.type !== "school-invite") continue + const owner = idx.courseNodes.get(idx.rootOf(c.target)) + if (!owner || owner.author !== m.value.author) continue + for (const cand of candidates) { + try { + if (schoolCrypto.hashInviteCode(cand, c.salt) === c.codeHash) { matched = c; code = cand; break } + } catch {} + } + if (matched) break + } + if (!matched) throw new Error("Invalid or expired invite code") + + const courseKey = schoolCrypto.decryptFromInvite(matched.ek, code, matched.salt) + if (!courseKey) throw new Error("Could not decrypt invite") + schoolCrypto.addNewKey(matched.target, courseKey) + + const tipId = idx.tipOf(matched.target) + const node = idx.courseNodes.get(tipId) + if (!node) throw new Error("Course not found") + const teacherId = node.author + let cc = node.c + if (cc.encryptedPayload) { + const dec = schoolCrypto.decryptContent(cc, [[courseKey]]) + if (dec && !dec._undecryptable) cc = dec + } + const priceN = toNum(cc.price) + + if (teacherId !== me) { + const enrollments = idx.enrollByCourse.get(matched.target) || new Map() + if (!enrollments.has(me)) { + let transferId = null + if (Number.isFinite(priceN) && priceN > 0 && transfersModel) { + const deadline = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() + const bill = await transfersModel.createTransfer(teacherId, `SCHOOL ${matched.target}`, priceN.toFixed(6), deadline, ["SCHOOL"], "ECONOMIC") + transferId = bill && bill.key ? bill.key : null + } + const msg = { type: "schoolEnroll", courseId: matched.target, value: true, transferId, keyProof: keyProofFor(courseKey, me), createdAt: new Date().toISOString() } + await new Promise((res, rej) => ssbClient.private.publish(msg, [me, teacherId], (e, m) => e ? rej(e) : res(m))) + } + const enrollChatId = cc.chatId || idx.chatByCourse.get(matched.target) || null + if (enrollChatId) { + try { + await new Promise((res) => ssbClient.publish({ type: "chatMember", target: enrollChatId, member: me, on: true, createdAt: new Date().toISOString() }, () => res())) + } catch {} + } + } + return { ok: true, courseId: matched.target } + }, + + async grantAccess(courseId, studentId) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + if (!schoolCrypto) throw new Error("School crypto unavailable") + + const ids = normalizeIds(studentId) + if (!ids.length) throw new Error("Invalid student id") + for (const student of ids) { + const known = course.students.includes(student) || course.invited.includes(student) || course.pending.some(p => p.author === student) + if (!known) throw new Error("Not a student of this course") + } + + const key = await ensureCourseKey(ssbClient, course.rootId) + const grant = await publishKeyGrant(ssbClient, course.rootId, key, ids) + await addToChat(ssbClient, course, ids) + return grant + }, + + async updateLesson(courseId, lessonId, data) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + + const lessons = await this.listLessons(course.rootId) + const current = lessons.find(l => l.id === lessonId) + if (!current || current.locked) throw new Error("Lesson not found") + + const title = data.title !== undefined ? String(data.title || "").trim() : current.title + const text = data.text !== undefined ? String(data.text || "").trim() : current.text + if (!title || !text) throw new Error("Invalid lesson") + + const orderN = toNum(data.order) + let content = { + type: "schoolLesson", + courseId: course.rootId, + title, + text, + unit: data.unit !== undefined ? (String(data.unit || "").trim() || null) : current.unit, + order: data.order !== undefined ? (Number.isFinite(orderN) ? orderN : null) : current.order, + sessionDate: data.sessionDate !== undefined ? normalizeDate(data.sessionDate) : current.sessionDate, + author: ssbClient.id, + createdAt: current.createdAt, + updatedAt: new Date().toISOString(), + replaces: lessonId + } + + if (isProtected(course) && schoolCrypto) { + const key = await ensureCourseKey(ssbClient, course.rootId) + if (key) content = schoolCrypto.encryptContent(content, [key], true) + } + + const tombMsg = { type: "tombstone", target: lessonId, deletedAt: new Date().toISOString(), author: ssbClient.id } + await new Promise((res, rej) => ssbClient.publish(tombMsg, (e) => e ? rej(e) : res())) + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async deleteLesson(lessonId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const node = idx.lessonNodes.get(lessonId) + if (!node || idx.tomb.has(lessonId)) throw new Error("Lesson not found") + if (node.author !== ssbClient.id) throw new Error("Unauthorized") + + const tombMsg = { + type: "tombstone", + target: lessonId, + deletedAt: new Date().toISOString(), + author: ssbClient.id + } + + return new Promise((res, rej) => ssbClient.publish(tombMsg, (e, r) => e ? rej(e) : res(r))) + }, + + async listLessons(courseId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const rootId = idx.rootOf(courseId) + const keys = schoolCrypto ? (schoolCrypto.getKeys(rootId) || []) : [] + const lessons = [] + for (const node of idx.lessonNodes.values()) { + if (idx.tomb.has(node.key)) continue + if (idx.lessonChildOf.has(node.key)) continue + if (idx.rootOf(node.c.courseId) !== rootId) continue + let c = node.c + let locked = false + if (c.encryptedPayload) { + const dec = keys.length && schoolCrypto ? schoolCrypto.decryptContent(c, keys.map(k => [k])) : { ...c, _undecryptable: true } + if (dec._undecryptable) locked = true + else c = dec + } + lessons.push({ + id: node.key, + courseId: rootId, + locked, + title: locked ? null : c.title, + text: locked ? null : c.text, + unit: locked ? null : (c.unit || null), + order: locked ? null : (Number.isFinite(toNum(c.order)) ? toNum(c.order) : null), + sessionDate: locked ? null : (c.sessionDate || null), + author: node.author, + createdAt: c.createdAt || new Date(node.ts).toISOString() + }) + } + const myProgress = (idx.progressByCourse.get(rootId) || new Map()).get(ssbClient.id) || new Set() + for (const lesson of lessons) lesson.completed = myProgress.has(idx.lessonRootOf(lesson.id)) + return lessons.sort((a, b) => + ((a.order ?? Infinity) - (b.order ?? Infinity)) || (new Date(a.createdAt) - new Date(b.createdAt)) + ) + }, + + async hasPassedCourse(courseId, studentId) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + const student = normalizeIds(studentId)[0] || ssbClient.id + const lessons = await this.listLessons(course.rootId) + if (!lessons.length) return false + const exams = await this.listExams(course.rootId) + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + const progressSet = (idx.progressByCourse.get(course.rootId) || new Map()).get(student) || new Set() + for (const lesson of lessons) { + const lessonRoot = idx.lessonRootOf(lesson.id) + const lessonExams = exams.filter(x => !x.locked && x.questions.length && x.lessonId && x.lessonId === lessonRoot) + if (lessonExams.length) { + for (const x of lessonExams) { + const r = idx.examResultLatest.get(`${x.id}::${student}`) + if (!r || x.passMark == null || Number(r.score) < x.passMark) return false + } + } else if (!progressSet.has(lessonRoot)) { + return false + } + } + return true + }, + + async issueCertificate(courseId, studentId, text = "") { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Unauthorized") + + const student = normalizeIds(studentId)[0] + if (!student) throw new Error("Invalid student id") + if (!course.students.includes(student)) throw new Error("Student is not enrolled") + + const lessons = await this.listLessons(course.rootId) + if (lessons.length) { + const exams = await this.listExams(course.rootId) + const messages2 = await readAll(ssbClient) + const idx2 = indexOf(messages2, ssbClient) + const progressSet = (idx2.progressByCourse.get(course.rootId) || new Map()).get(student) || new Set() + for (const lesson of lessons) { + const lessonRoot = idx2.lessonRootOf(lesson.id) + const lessonExams = exams.filter(x => !x.locked && x.lessonId && x.lessonId === lessonRoot) + if (lessonExams.length) { + for (const x of lessonExams) { + const r = idx2.examResultLatest.get(`${x.id}::${student}`) + if (!r || x.passMark == null || Number(r.score) < x.passMark) throw new Error("Student has not passed all lessons") + } + } else if (!progressSet.has(lessonRoot)) { + throw new Error("Student has not completed all lessons") + } + } + } + + const rootId = course.rootId || (await this.resolveRootId(courseId)) + const existing = await this.listCertificates(rootId) + if (existing.some(cert => cert.student === student)) return { alreadyIssued: true } + + const content = { + type: "schoolCertificate", + courseId: rootId, + courseTitle: course.title, + student, + text: String(text || "").trim(), + author: ssbClient.id, + createdAt: new Date().toISOString() + } + + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async listCertificates(courseId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const rootId = idx.rootOf(courseId) + const certs = [] + for (const node of idx.certNodes.values()) { + if (idx.tomb.has(node.key)) continue + if (idx.rootOf(node.c.courseId) !== rootId) continue + certs.push({ + id: node.key, + courseId: rootId, + courseTitle: node.c.courseTitle, + student: node.c.student, + text: node.c.text || "", + author: node.author, + createdAt: node.c.createdAt || new Date(node.ts).toISOString() + }) + } + return certs.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + }, + + async listCertificatesForStudent(studentId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const certs = [] + for (const node of idx.certNodes.values()) { + if (idx.tomb.has(node.key)) continue + if (node.c.student !== studentId) continue + certs.push({ + id: node.key, + courseId: idx.rootOf(node.c.courseId), + courseTitle: node.c.courseTitle, + student: node.c.student, + text: node.c.text || "", + author: node.author, + createdAt: node.c.createdAt || new Date(node.ts).toISOString() + }) + } + return certs.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + }, + + async lessonRootOf(lessonId) { + const ssbClient = await openSsb() + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + return idx.lessonRootOf(lessonId) || lessonId + }, + + async listHiddenComments(courseId) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + const messages = await readAll(ssbClient) + const hidden = new Set() + for (const m of messages) { + const c = m.value?.content + if (!c || c.type !== "schoolCommentHide" || !c.target) continue + if (m.value?.author !== course.author) continue + if (c.courseId !== course.rootId) continue + hidden.add(c.target) + } + return hidden + }, + + async hideComment(courseId, commentId) { + const ssbClient = await openSsb() + const course = await this.getCourseById(courseId) + if (course.author !== ssbClient.id) throw new Error("Only the teacher can hide comments") + const content = { type: "schoolCommentHide", courseId: course.rootId, target: commentId, author: ssbClient.id, createdAt: new Date().toISOString() } + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) + }, + + async listCourses(filter = "ALL", viewerId = null, query = {}) { + const ssbClient = await openSsb() + const viewer = viewerId || ssbClient.id + + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const courses = [] + for (const [rootId, tipId] of idx.tipByRoot.entries()) { + if (idx.tomb.has(tipId)) continue + const node = idx.courseNodes.get(tipId) + if (!node) continue + const course = buildCourseObject(node, rootId, idx.enrollByCourse.get(rootId), idx.transferAgg, idx.grantsByCourse.get(rootId), idx.opinionsByCourse.get(rootId), idx.chatByCourse) + if (course.undecryptable) continue + if (!canView(course, viewer)) continue + courses.push(course) + } + + const F = String(filter || "ALL").toUpperCase() + let list = courses + + if (F === "MINE") list = list.filter(c => c.author === viewer) + else if (F === "APPLIED") list = list.filter(c => c.students.includes(viewer) || c.pending.some(p => p.author === viewer)) + else if (F === "OPEN") list = list.filter(c => !(Number(c.price) > 0)) + else if (F === "ONGOING") list = list.filter(c => c.status === "ONGOING") + else if (F === "CLOSED") list = list.filter(c => c.status === "CLOSED") + else if (F === "RECENT") list = list.filter(c => moment(c.createdAt).isAfter(moment().subtract(24, "hours"))) + else if (F === "TOP") list = [...list].sort((a, b) => b.students.length - a.students.length) + + const search = String(query.q || query.search || "").trim() + if (search) list = list.filter(c => matchSearch(c, search)) + + const sort = String(query.sort || "").trim() + const byRecent = () => list.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + if (sort === "students") list.sort((a, b) => b.students.length - a.students.length) + else if (sort === "title") list.sort((a, b) => String(a.title).localeCompare(String(b.title))) + else if (F !== "TOP") byRecent() + + return list + }, + + async getCourseById(id, viewerId = null) { + const ssbClient = await openSsb() + const viewer = viewerId || ssbClient.id + + const messages = await readAll(ssbClient) + const idx = indexOf(messages, ssbClient) + + const tipId = idx.tipOf(id) + const rootId = idx.rootOf(id) + if (idx.tomb.has(tipId) || idx.vg.isForeignCopy(rootId)) throw new Error("Course not found") + + const node = idx.courseNodes.get(tipId) + if (!node) throw new Error("Course not found") + + const course = buildCourseObject(node, rootId, idx.enrollByCourse.get(rootId), idx.transferAgg, idx.grantsByCourse.get(rootId), idx.opinionsByCourse.get(rootId), idx.chatByCourse) + if (course.undecryptable) throw new Error("Course not found") + if (!canView(course, viewer)) throw new Error("Course not found") + return course + } + } +} diff --git a/src/models/search_model.js b/src/models/search_model.js index 1f55297..1849c42 100644 --- a/src/models/search_model.js +++ b/src/models/search_model.js @@ -2,6 +2,9 @@ const pull = require('../server/node_modules/pull-stream'); const moment = require('../server/node_modules/moment'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, CONTENT_TYPES } = require('./typed_log'); +const longText = require('../backend/long_text'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { @@ -39,8 +42,8 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { const searchableTypes = [ 'post', 'about', 'curriculum', 'tribe', 'transfer', 'feed', 'votes', 'report', 'task', 'event', 'bookmark', 'document', - 'image', 'audio', 'video', 'torrent', 'market', 'bankWallet', 'bankClaim', - 'project', 'job', 'housing', 'industry', 'industryBlueprint', 'forum', 'vote', 'contact', 'pub', 'map', 'shop', 'shopProduct', 'chat', 'pad', 'poll' + 'image', 'audio', 'video', 'torrent', 'file', 'market', 'bankWallet', 'bankClaim', + 'project', 'job', 'housing', 'industry', 'industryBlueprint', 'forum', 'vote', 'contact', 'pub', 'map', 'shop', 'shopProduct', 'chat', 'pad', 'room', 'poll', 'schoolCourse', 'wikiPage', 'emergency', 'mailingList', 'logisticsRoute', 'podcast', 'podcastEpisode', 'campaign' ]; const getRelevantFields = (type, content) => { @@ -66,6 +69,7 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { case 'document': return [content?.url, content?.title, content?.description, ...(content?.tags || []), content?.key]; case 'torrent': + case 'file': return [content?.title, content?.description, ...(content?.tags || []), content?.url]; case 'market': return [content?.item_type, content?.title, content?.description, content?.price, ...(content?.tags || []), content?.status, content?.item_status, content?.deadline, content?.includesShipping, content?.seller, content?.image, content?.auctions_poll, content?.stock]; @@ -83,6 +87,8 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { return [content?.address]; case 'bankClaim': return [content?.amount, content?.epochId, content?.allocationId, content?.txid]; + case 'schoolCourse': + return [content?.title, content?.description, ...(content?.tags || []), content?.price, content?.status, content?.visibility]; case 'project': return [content?.title, content?.status, content?.progress, content?.goal, content?.pledged, content?.deadline, (content?.followers || []).length, (content?.backers || []).length, (content?.milestones || []).length, content?.bounty, content?.bountyAmount, content?.bounty_currency, content?.activity?.kind, content?.activityActor]; case 'poll': @@ -115,6 +121,22 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { return [content?.title, content?.description, content?.category, ...(content?.tags || []), content?.status, content?.author]; case 'pad': return [content?.title, content?.status, content?.deadline, ...(content?.tags || []), content?.author]; + case 'room': + return content?.encrypted === true ? [] : [content?.title, content?.description, content?.status, ...(content?.tags || []), content?.author]; + case 'wikiPage': + return [content?.title, content?.body, content?.slug, ...(content?.tags || []), ...(content?.aliases || []), content?.author]; + case 'emergency': + return [content?.title, content?.text, content?.category, content?.status, ...(content?.tags || []), content?.author]; + case 'mailingList': + return [content?.title, content?.description, content?.listType, content?.status, ...(content?.tags || []), content?.author]; + case 'logisticsRoute': + return [content?.title, content?.description, content?.origin, content?.destination, content?.kind, content?.mode, ...(content?.tags || []), content?.author]; + case 'podcast': + return [content?.title, content?.description, content?.category, ...(content?.tags || []), content?.author]; + case 'podcastEpisode': + return [content?.title, content?.description, ...(content?.tags || []), content?.author]; + case 'campaign': + return [content?.title, content?.text, content?.category, content?.status, ...(content?.tags || []), content?.author]; case 'gameScore': return [content?.game, content?.player]; default: @@ -144,6 +166,7 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { if (t === 'audio') return `audio:${c.url || `${author}|${norm(c.title)}|${norm(c.description)}` || msg.key}`; if (t === 'video') return `video:${c.url || `${author}|${norm(c.title)}|${norm(c.description)}` || msg.key}`; if (t === 'torrent') return `torrent:${c.url || `${author}|${norm(c.title)}|${norm(c.description)}` || msg.key}`; + if (t === 'file') return `file:${c.url || `${author}|${norm(c.title)}|${norm(c.description)}` || msg.key}`; if (t === 'bookmark') return `bookmark:${author}|${c.url || norm(c.description) || msg.key}`; if (t === 'tribe') { @@ -228,6 +251,9 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { ].join('|'); } + if (t === 'schoolCourse') { + return ['schoolCourse', author, norm(c.title), norm(c.createdAt)].join('|'); + } if (t === 'project') { return [ 'project', @@ -288,6 +314,30 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { return ['pad', author, norm(c.title), norm(c.deadline)].join('|'); } + if (t === 'room') { + return ['room', author, norm(c.rid || c.title), norm(c.createdAt)].join('|'); + } + + if (t === 'wikiPage') { + return ['wikiPage', norm(c.tribeId), norm(c.slug)].join('|'); + } + + if (t === 'emergency') { + return ['emergency', author, norm(c.title), norm(c.createdAt)].join('|'); + } + + if (t === 'mailingList') { + return ['mailingList', author, norm(c.createdAt)].join('|'); + } + + if (t === 'logisticsRoute') { + return ['logisticsRoute', author, norm(c.createdAt)].join('|'); + } + + if (t === 'podcast' || t === 'podcastEpisode' || t === 'campaign') { + return [t, author, norm(c.createdAt)].join('|'); + } + return `${t}:${msg.key}`; }; @@ -303,20 +353,14 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { return Array.from(map.values()); }; - const search = async ({ query, types = [], resultsPerPage = "10" }) => { + const search = async ({ query, types = [] }) => { const ssbClient = await openSsb(); const viewerId = ssbClient.id; const queryLower = String(query || '').toLowerCase(); - const messages = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); + const messages = await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }); const tombstoned = buildValidatedTombstoneSet(messages); - const replacesMap = new Map(); const latestByKey = new Map(); for (const msg of messages) { @@ -324,23 +368,12 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { const c = msg?.value?.content; const t = c?.type; if (!t || !searchableTypes.includes(t)) continue; - if (tombstoned.has(k)) continue; - if (c.replaces) replacesMap.set(c.replaces, k); latestByKey.set(k, msg); } - for (const [oldId, newId] of Array.from(replacesMap.entries())) { - const orig = latestByKey.get(oldId); - const repl = latestByKey.get(newId); - if (!orig) { replacesMap.delete(oldId); continue; } - if (!repl || String(repl?.value?.author) !== String(orig?.value?.author)) { - replacesMap.delete(oldId); - latestByKey.delete(newId); - } - } - - for (const oldId of replacesMap.keys()) { - latestByKey.delete(oldId); + const vg = versionGroups(Array.from(latestByKey.values()).map(versionEntry)); + for (const k of Array.from(latestByKey.keys())) { + if (!vg.roots.has(vg.rootOf(k)) || vg.tipOf(k) !== k || tombstoned.has(k)) latestByKey.delete(k); } const viewerTribeIds = new Set(); @@ -376,6 +409,14 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { } } + const chunkLookup = longText.lookupIn(longText.indexChunks(messages)); + for (const [k, msg] of Array.from(latestByKey.entries())) { + const c = msg?.value?.content; + if ((c?.type === 'post' || c?.type === 'forum') && longText.hasChunks(c)) { + latestByKey.set(k, { ...msg, value: { ...msg.value, content: longText.resolveField(c, 'text', msg.value.author, chunkLookup) } }); + } + } + if (padsModel) { for (const msg of latestByKey.values()) { const c = msg?.value?.content; @@ -422,11 +463,6 @@ module.exports = ({ cooler, padsModel, tribeCrypto, tribesModel }) => { return acc; }, {}); - if (resultsPerPage !== "all") { - const limit = parseInt(resultsPerPage, 10); - for (const key in grouped) grouped[key] = grouped[key].slice(0, limit); - } - return grouped; }; diff --git a/src/models/shops_model.js b/src/models/shops_model.js index 5dedbd2..54f79ae 100644 --- a/src/models/shops_model.js +++ b/src/models/shops_model.js @@ -3,6 +3,8 @@ const { getConfig } = require("../configs/config-manager.js") const categories = require("../backend/opinion_categories") const { buildValidatedTombstoneSet } = require('./tombstone_validator'); const { dedupeByPreferring, norm } = require('../backend/dedupe') +const { readTyped, memoIndex } = require('./typed_log') +const FileType = require('../server/node_modules/file-type') const logLimit = getConfig().ssbLogStream?.limit || 1000 const safeArr = (v) => (Array.isArray(v) ? v : []) @@ -18,31 +20,90 @@ module.exports = ({ cooler, tribeCrypto }) => { let ssb const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb } - const readAll = async (ssbClient) => - new Promise((resolve, reject) => - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))) - ) + const SHOP_TYPES = ["shop", "shopProduct", "shopOpinion", "shopPurchase", "shop-invite", "shop-invite-tombstone", "shop-open-invite", "shop-open-invite-tombstone", "tombstone"] + + const readAll = async (ssbClient) => readTyped(ssbClient, SHOP_TYPES, { limit: logLimit, withWindow: true, withPrivate: true }) + const mediaKinds = new Map() + const blobMediaKind = async (ssbClient, blobId) => { + if (mediaKinds.has(blobId)) return mediaKinds.get(blobId) + let kind + try { + const head = await new Promise((resolve, reject) => pull(ssbClient.blobs.get(blobId), pull.take(1), pull.collect((err, chunks) => err ? reject(err) : resolve(Buffer.concat(chunks))))) + const ft = await FileType.fromBuffer(head) + kind = ft && ft.mime ? ft.mime.split('/')[0] : 'other' + } catch (_) { return null } + mediaKinds.set(blobId, kind) + return kind + } + const withMediaKind = async (ssbClient, items) => { + for (const p of items) { + if (!p || !p.image || !String(p.image).startsWith('&')) continue + const kind = await blobMediaKind(ssbClient, p.image) + if (kind === 'video') p.image = `[video:](${p.image})` + else if (kind === 'audio') p.image = `[audio:](${p.image})` + } + return items + } + const openSealed = (ssbClient, m) => { + if (typeof m.value?.content === "string") return ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.value?.timestamp || m.timestamp || 0 }) + return m.value?.private === true ? m : null + } const SELLER_STATUSES = ["ACCEPTED", "REJECTED", "PAID", "SHIPPED"] const BUYER_STATUSES = ["RECEIVED"] const ORDER_STATUSES = [...SELLER_STATUSES, ...BUYER_STATUSES] + const ORDER_FLOW = { PENDING: ["ACCEPTED", "REJECTED"], ACCEPTED: ["PAID", "REJECTED"], PAID: ["SHIPPED"], SHIPPED: ["RECEIVED"] } const buildOrderStatusMap = (ssbClient, messages) => { const map = new Map() for (const m of messages) { - if (typeof m.value?.content !== "string") continue try { - const dec = ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.value?.timestamp || m.timestamp || 0 }) + const dec = openSealed(ssbClient, m) const dc = dec?.value?.content if (!dc || dc.type !== "shop-purchase-status" || !dc.orderId) continue - const ts = dec.value.timestamp || m.timestamp || 0 - const prev = map.get(dc.orderId) - if (!prev || ts >= prev.ts) map.set(dc.orderId, { status: String(dc.status || "").toUpperCase(), ts }) + if (!map.has(dc.orderId)) map.set(dc.orderId, []) + map.get(dc.orderId).push({ status: String(dc.status || "").toUpperCase(), author: dec.value.author, ts: dec.value.timestamp || m.timestamp || 0, price: dc.price !== undefined ? String(dc.price) : undefined, title: dc.title !== undefined ? String(dc.title) : undefined }) } catch (_) {} } return map } + const orderStatusOf = (updates, buyer, seller) => { + const signed = (updates || []).filter(u => (SELLER_STATUSES.includes(u.status) && u.author === seller) || (BUYER_STATUSES.includes(u.status) && u.author === buyer)).sort((a, b) => a.ts - b.ts) + let status = "PENDING", g = 0 + while (g++ < 100) { + const next = signed.find(u => (ORDER_FLOW[status] || []).includes(u.status)) + if (!next) break + status = next.status + } + return status + } + + const acceptedTermsOf = (updates, seller) => { + const accepted = (updates || []).filter(u => u.status === "ACCEPTED" && u.author === seller && u.price !== undefined).sort((a, b) => a.ts - b.ts)[0] + return accepted ? { price: accepted.price, ...(accepted.title ? { title: accepted.title } : {}) } : null + } + const withOrderStatus = (statusMap) => (o) => { + const updates = statusMap.get(o.id) + return { ...o, ...(acceptedTermsOf(updates, o.seller) || {}), status: orderStatusOf(updates, o.buyer, o.seller) } + } + + const resolveOrder = (idx, dc) => { + const productRoot = idx.rootOf(String(dc.productId || "")) + const productNode = idx.nodes.get(productRoot) + if (!productNode || productNode.c.type !== "shopProduct") return null + const shopRoot = idx.rootOf(productShopId(productNode.c)) + const shopNode = idx.nodes.get(shopRoot) + if (!shopNode || shopNode.c.type !== "shop" || shopNode.author !== productNode.author) return null + let ref = productRoot + let g = 0 + while (idx.strictChild.has(ref) && g++ < 100000) ref = idx.strictChild.get(ref) + const raw = idx.nodes.get(ref).c + const c = isEncrypted(raw) ? decryptShopContent(raw, shopRoot) : raw + const readable = !c._undecryptable + return { productId: productRoot, shopId: shopRoot, seller: shopNode.author, title: readable ? String(c.title || "") : "", price: readable ? (c.price || "") : "" } + } + const decryptBuyers = (val, key) => { if (Array.isArray(val)) return val if (typeof val === 'string' && tribeCrypto && key) { @@ -77,7 +138,7 @@ module.exports = ({ cooler, tribeCrypto }) => { } const buildIndex = (messages) => { - const tomb = new Set() + const tomb = buildValidatedTombstoneSet(messages) const nodes = new Map() const parent = new Map() const child = new Map() @@ -90,7 +151,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const v = m.value || {} const c = v.content if (!c) continue - if (c.type === "tombstone" && c.target) { tomb.add(c.target); continue } + if (c.type === "tombstone") continue if (c.type === "shopOpinion" && c.target) { opinionMsgs.push({ target: c.target, author: v.author, category: c.category }); continue } if (c.type === "shopPurchase" && c.target) { purchaseMsgs.push({ target: c.target, author: v.author }); continue } if (c.type === "shop" || c.type === "shopProduct") { @@ -162,7 +223,7 @@ module.exports = ({ cooler, tribeCrypto }) => { tags: safeArr(c.tags), visibility: c.visibility || (encrypted ? "CLOSED" : "OPEN"), clearnetPublic: !!c.clearnetPublic, - author: raw.author || c.author || node.author, + author: node.author, createdAt: c.createdAt || raw.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || raw.updatedAt || null, opinions: agg.opinions, @@ -182,7 +243,6 @@ module.exports = ({ cooler, tribeCrypto }) => { const undecryptable = !!c._undecryptable const agg = idx ? idx.aggregateFor(rootId, c) : { opinions: c.opinions || {}, opinions_inhabitants: safeArr(c.opinions_inhabitants) } const sellerStock = Number(c.stock) || 0 - const purchaseCount = idx ? idx.purchaseCountFor(rootId) : 0 const sellerBuyers = encrypted ? safeArr(c.buyers) : decryptBuyers(c.buyers, tribeCrypto ? tribeCrypto.getKey(rootId) : null) const buyers = Array.from(new Set(sellerBuyers.concat(idx ? idx.purchaseAuthorsFor(rootId) : []))) return { @@ -193,9 +253,9 @@ module.exports = ({ cooler, tribeCrypto }) => { description: c.description || "", image: c.image || null, price: c.price || "0.000000", - stock: Math.max(0, sellerStock - purchaseCount), + stock: Math.max(0, sellerStock), featured: !!c.featured, - author: raw.author || c.author || node.author, + author: node.author, createdAt: c.createdAt || raw.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || raw.updatedAt || null, opinions: agg.opinions, @@ -346,7 +406,7 @@ module.exports = ({ cooler, tribeCrypto }) => { async getShopById(id) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const idx = buildIndex(messages) + const idx = memoIndex('shops', messages, buildIndex) const tip = idx.strictTipOf(id) if (idx.tomb.has(tip)) return null @@ -521,7 +581,7 @@ module.exports = ({ cooler, tribeCrypto }) => { async getProductById(id) { const ssbClient = await openSsb() const messages = await readAll(ssbClient) - const idx = buildIndex(messages) + const idx = memoIndex('shops', messages, buildIndex) const tip = idx.strictTipOf(id) if (idx.tomb.has(tip)) return null @@ -531,7 +591,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const root = idx.rootOf(tip) - return buildProduct(node, root, idx) + return (await withMediaKind(ssbClient, [buildProduct(node, root, idx)]))[0] }, async listProducts(shopRootId) { @@ -549,7 +609,7 @@ module.exports = ({ cooler, tribeCrypto }) => { if (prod && !prod.undecryptable) items.push(prod) } - return items.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + return withMediaKind(ssbClient, items.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))) }, async listFeaturedProducts(shopRootId) { @@ -568,7 +628,7 @@ module.exports = ({ cooler, tribeCrypto }) => { if (prod && !prod.undecryptable) items.push(prod) } - return items.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, 4) + return withMediaKind(ssbClient, items.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, 4)) }, async listAllProducts({ filter = "all", sort = "recent" } = {}) { @@ -585,8 +645,10 @@ module.exports = ({ cooler, tribeCrypto }) => { if (prod && !prod.undecryptable) items.push(prod) } - if (filter === "top") return items.slice().sort((a, b) => voteSum(b.opinions) - voteSum(a.opinions) || new Date(b.createdAt) - new Date(a.createdAt)) - return items.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + const sorted = filter === "top" + ? items.slice().sort((a, b) => voteSum(b.opinions) - voteSum(a.opinions) || new Date(b.createdAt) - new Date(a.createdAt)) + : items.slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + return withMediaKind(ssbClient, sorted) }, async buyProduct(productId) { @@ -608,8 +670,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const shopRoot = productShopId(raw) const c = encrypted ? decryptShopContent(raw, shopRoot) : raw if (c._undecryptable) throw new Error("Cannot access product") - const stock = Math.max(0, (Number(c.stock) || 0) - idx.purchaseCountFor(rootId)) - if (stock <= 0) throw new Error("Out of stock") + if (!((Number(c.stock) || 0) > 0)) throw new Error("Out of stock") const content = { type: "shopPurchase", target: rootId, createdAt: new Date().toISOString() } return new Promise((res, rej) => ssbClient.publish(content, (e, m) => e ? rej(e) : res(m))) @@ -626,11 +687,14 @@ module.exports = ({ cooler, tribeCrypto }) => { const rootId = idx.rootOf(tipId) const node = idx.nodes.get(tipId) - if (!node) throw new Error("Product not found") + if (!node || node.c.type !== "shopProduct") throw new Error("Product not found") const raw = node.c - const shopOwner = raw.author - if (shopOwner === userId) throw new Error("Cannot buy your own product") const shopRoot = productShopId(raw) + const shopNode = idx.nodes.get(idx.rootOf(shopRoot)) + const productRootNode = idx.nodes.get(rootId) + if (!shopNode || shopNode.c.type !== "shop" || !productRootNode || productRootNode.author !== shopNode.author || node.author !== shopNode.author) throw new Error("Product not found") + const shopOwner = shopNode.author + if (shopOwner === userId) throw new Error("Cannot buy your own product") const c = isEncrypted(raw) ? decryptShopContent(raw, shopRoot) : raw const content = { @@ -655,31 +719,24 @@ module.exports = ({ cooler, tribeCrypto }) => { const ssbClient = await openSsb() const me = ssbClient.id const messages = await readAll(ssbClient) + const idx = buildIndex(messages) const out = [] for (const m of messages) { - if (typeof m.value?.content !== "string") continue try { - const dec = ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.value?.timestamp || m.timestamp || 0 }) + const dec = openSealed(ssbClient, m) if (!dec?.value?.content) continue const dc = dec.value.content if (dc.type !== "shop-purchase") continue if (dec.value.author !== me) continue - out.push({ id: m.key, ...dc, buyer: dec.value.author, ts: dec.value.timestamp || m.timestamp || 0 }) + const ts = dec.value.timestamp || m.timestamp || 0 + const resolved = resolveOrder(idx, dc) + if (!resolved) continue + out.push({ id: m.key, ...dc, ...resolved, buyer: dec.value.author, ts }) } catch (_) {} } const statusMap = buildOrderStatusMap(ssbClient, messages) - const sellerCache = new Map() - for (const o of out) { - if (!o.seller && o.shopId) { - if (!sellerCache.has(o.shopId)) { - const shop = await this.getShopById(o.shopId).catch(() => null) - sellerCache.set(o.shopId, (shop && shop.author) || null) - } - o.seller = sellerCache.get(o.shopId) || null - } - } return out - .map(o => ({ ...o, status: (statusMap.get(o.id) || {}).status || "PENDING" })) + .map(withOrderStatus(statusMap)) .sort((a, b) => b.ts - a.ts) }, @@ -689,23 +746,26 @@ module.exports = ({ cooler, tribeCrypto }) => { const shop = await this.getShopById(shopRootId).catch(() => null) if (!shop) throw new Error("Shop not found") if (shop.author !== me) throw new Error("Not the shop owner") + const rootId = shop.rootId || shopRootId const messages = await readAll(ssbClient) + const idx = buildIndex(messages) const out = [] for (const m of messages) { - if (typeof m.value?.content !== "string") continue try { - const dec = ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.value?.timestamp || m.timestamp || 0 }) + const dec = openSealed(ssbClient, m) if (!dec?.value?.content) continue const dc = dec.value.content if (dc.type !== "shop-purchase") continue - if (dc.shopId !== shopRootId) continue - out.push({ id: m.key, ...dc, buyer: dec.value.author, ts: dec.value.timestamp || m.timestamp || 0 }) + const ts = dec.value.timestamp || m.timestamp || 0 + const resolved = resolveOrder(idx, dc) + if (!resolved || resolved.shopId !== rootId || resolved.seller !== me) continue + out.push({ id: m.key, ...dc, ...resolved, buyer: dec.value.author, ts }) } catch (_) {} } const statusMap = buildOrderStatusMap(ssbClient, messages) return out - .map(o => ({ ...o, status: (statusMap.get(o.id) || {}).status || "PENDING" })) + .map(withOrderStatus(statusMap)) .sort((a, b) => b.ts - a.ts) }, @@ -715,27 +775,38 @@ module.exports = ({ cooler, tribeCrypto }) => { const st = String(status || "").toUpperCase() if (!ORDER_STATUSES.includes(st)) throw new Error("Invalid status") const messages = await readAll(ssbClient) + const idx = buildIndex(messages) let order = null for (const m of messages) { if (m.key !== orderId) continue - if (typeof m.value?.content !== "string") break try { - const dec = ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.value?.timestamp || m.timestamp || 0 }) + const dec = openSealed(ssbClient, m) const dc = dec?.value?.content - if (dc && dc.type === "shop-purchase") order = { ...dc, buyer: dec.value.author } + const resolved = dc && dc.type === "shop-purchase" ? resolveOrder(idx, dc) : null + if (resolved) order = { ...dc, ...resolved, buyer: dec.value.author } } catch (_) {} break } if (!order) throw new Error("Order not found") - const seller = order.seller || (await this.getShopById(order.shopId).catch(() => null) || {}).author || null + const seller = order.seller if (BUYER_STATUSES.includes(st)) { if (me !== order.buyer) throw new Error("Only the buyer can set this status") } else { if (me !== seller) throw new Error("Only the seller can set this status") } + const current = orderStatusOf(buildOrderStatusMap(ssbClient, messages).get(orderId), order.buyer, seller) + if (!(ORDER_FLOW[current] || []).includes(st)) throw new Error("Invalid status transition") const recps = Array.from(new Set([order.buyer, seller].filter(Boolean))) - const content = { type: "shop-purchase-status", orderId, shopId: order.shopId, status: st, updatedAt: new Date().toISOString() } - return new Promise((res, rej) => ssbClient.private.publish(content, recps, (e, m) => e ? rej(e) : res(m))) + const content = { type: "shop-purchase-status", orderId, shopId: order.shopId, status: st, ...(st === "ACCEPTED" ? { price: String(order.price || ""), title: String(order.title || "") } : {}), updatedAt: new Date().toISOString() } + const published = await new Promise((res, rej) => ssbClient.private.publish(content, recps, (e, m) => e ? rej(e) : res(m))) + const delta = st === "ACCEPTED" ? -1 : (st === "REJECTED" && current === "ACCEPTED" ? 1 : 0) + if (delta) { + try { + const product = await this.getProductById(order.productId) + await this.updateProductById(order.productId, { stock: String(Math.max(0, (Number(product.stock) || 0) + delta)) }) + } catch (_) {} + } + return published }, async countPendingOrders(shopRootId) { diff --git a/src/models/stats_model.js b/src/models/stats_model.js index ddbb322..ef20e57 100644 --- a/src/models/stats_model.js +++ b/src/models/stats_model.js @@ -1,16 +1,19 @@ const pull = require('../server/node_modules/pull-stream'); -const os = require('os'); const fs = require('fs'); const path = require('path'); +const ssbDirOf = () => require('../server/ssb_config').path; const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, CONTENT_TYPES } = require('./typed_log'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; +const DISK_USAGE_TTL_MS = 10 * 60 * 1000; -const STORAGE_DIR = path.join(__dirname, "..", "configs"); -const ADDR_FILE = path.join(STORAGE_DIR, "wallet_addresses.json"); +const addrFile = () => process.env.OASIS_BANKING_DIR ? path.join(process.env.OASIS_BANKING_DIR, "wallet-addresses.json") : require('../configs/state-manager').statePath("wallet-addresses.json"); function readAddrMap() { try { + const ADDR_FILE = addrFile(); if (!fs.existsSync(ADDR_FILE)) return {}; const raw = fs.readFileSync(ADDR_FILE, 'utf8'); const obj = JSON.parse(raw || '{}'); @@ -22,7 +25,7 @@ function readAddrMap() { const listPubsFromEbt = () => { try { - const ebtDir = path.join(require('../server/ssb_config').path, 'ebt'); + const ebtDir = path.join(ssbDirOf(), 'ebt'); const files = fs.readdirSync(ebtDir); return files.filter(f => f.endsWith('.ed25519')); } catch { @@ -59,25 +62,51 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const types = [ 'bookmark','event','task','votes','report','feed','project','industry','industryBlueprint', - 'image','torrent','audio','video','document','transfer','post','tribe', + 'image','torrent','file','audio','video','document','transfer','post','tribe', 'market','forum','job','aiExchange','map','shop','shopProduct','chat','chatMessage', - 'pad','padEntry','gameScore','calendar','calendarDate','calendarNote','log', + 'pad','padEntry','room','roomMember','wikiPage','emergency','emergencyConfirm','emergencyUpdate','mailingList','logisticsRoute','logisticsRating','podcast','podcastEpisode','podcastPlay','campaign','campaignSignature','campaignUpdate','gameScore','calendar','calendarDate','calendarNote','log', + 'schoolCourse','schoolLesson','schoolEnroll','schoolCertificate', 'parliamentCandidature','parliamentTerm','parliamentProposal','parliamentRevocation','parliamentLaw', 'courtsCase','courtsEvidence','courtsAnswer','courtsVerdict','courtsSettlement','courtsSettlementProposal','courtsSettlementAccepted','courtsNomination','courtsNominationVote' ]; - const getFolderSize = (folderPath) => { - let files; - try { files = fs.readdirSync(folderPath); } catch (_) { return 0; } - let totalSize = 0; - for (const file of files) { - const filePath = `${folderPath}/${file}`; + const walkBytes = async (dir) => { + let names; + try { names = await fs.promises.readdir(dir); } catch (_) { return 0; } + let total = 0; + for (const name of names) { + const filePath = path.join(dir, name); try { - const st = fs.statSync(filePath); - totalSize += st.isDirectory() ? getFolderSize(filePath) : st.size; + const st = await fs.promises.stat(filePath); + total += st.isDirectory() ? await walkBytes(filePath) : st.size; } catch (_) {} } - return totalSize; + return total; + }; + const diskUsage = new Map(); + const ssbDiskUsage = (root) => { + const hit = diskUsage.get(root); + if (hit && (hit.pending || Date.now() - hit.at < DISK_USAGE_TTL_MS)) return hit.pending || Promise.resolve(hit.value); + const pending = (async () => { + let names = []; + try { names = await fs.promises.readdir(root); } catch (_) {} + const usage = { total: 0, log: 0, blobs: 0 }; + for (const name of names) { + const filePath = path.join(root, name); + let bytes = 0; + try { + const st = await fs.promises.stat(filePath); + bytes = st.isDirectory() ? await walkBytes(filePath) : st.size; + } catch (_) {} + usage.total += bytes; + if (name === 'db2') usage.log = bytes; + if (name === 'blobs') usage.blobs = bytes; + } + return usage; + })(); + diskUsage.set(root, { pending, at: 0, value: null }); + pending.then((value) => diskUsage.set(root, { pending: null, at: Date.now(), value }), () => diskUsage.delete(root)); + return pending; }; const formatSize = (sizeInBytes) => { @@ -274,9 +303,31 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { else if (t === 'larphousepost') score += 6; else if (t === 'larptestattempt') score += 3; else if (t === 'torrent') score += 6; + else if (t === 'file') score += 6; else if (t === 'shop' || t === 'shopproduct') score += 6; + else if (t === 'schoolcourse' || rawType === 'schoolcourse') score += 10; + else if (t === 'schoollesson' || rawType === 'schoollesson') score += 6; + else if (t === 'schoolenroll' || rawType === 'schoolenroll') score += 3; + else if (t === 'schoolcertificate' || rawType === 'schoolcertificate') score += 12; + else if (t === 'schoolexam' || rawType === 'schoolexam') score += 6; + else if (t === 'schoolprogress' || rawType === 'schoolprogress') score += 1; + else if (t === 'schoolopinion' || rawType === 'schoolopinion') score += 2; else if (t === 'shop-purchase') score += 2; else if (t === 'pad' || t === 'padentry') score += 3; + else if (t === 'room') score += 3; + else if (t === 'roommember') score += 1; + else if (t === 'wikipage') score += 3; + else if (t === 'emergency') score += 4; + else if (t === 'emergencyconfirm' || t === 'emergencyupdate') score += 1; + else if (t === 'mailinglist') score += 3; + else if (t === 'logisticsroute') score += 3; + else if (t === 'logisticsrating') score += 1; + else if (t === 'podcast') score += 4; + else if (t === 'podcastepisode') score += 3; + else if (t === 'podcastopinion' || t === 'podcastplay') score += 1; + else if (t === 'campaign') score += 4; + else if (t === 'campaignsignature') score += 1; + else if (t === 'campaignupdate') score += 2; else if (t === 'calendar' || t === 'calendarnote' || t === 'calendardate') score += 3; else if (t === 'chat') score += 1; else if (t === 'gamescore') score += 2; @@ -289,6 +340,19 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { else if (rawType === 'industryvote') score += 2; else if (rawType === 'industrymember') score += 2; else if (rawType === 'industryopinion') score += 1; + else if (rawType === 'wikipage') score += c.replaces ? 2 : 6; + else if (rawType === 'emergency') score += c.replaces ? 1 : 5; + else if (rawType === 'emergencyconfirm') score += 1; + else if (rawType === 'emergencyupdate') score += 2; + else if (rawType === 'mailinglist') score += c.replaces ? 1 : 3; + else if (rawType === 'logisticsroute') score += c.replaces ? 1 : 4; + else if (rawType === 'logisticsrating' || rawType === 'logisticsopinion') score += 1; + else if (rawType === 'podcast') score += c.replaces ? 1 : 5; + else if (rawType === 'podcastepisode') score += c.replaces ? 1 : 4; + else if (rawType === 'podcastopinion' || rawType === 'podcastplay') score += 1; + else if (rawType === 'campaign') score += c.replaces ? 1 : 5; + else if (rawType === 'campaignsignature' || rawType === 'campaignopinion') score += 1; + else if (rawType === 'campaignupdate') score += 2; } return Math.max(0, Math.round(score)); }; @@ -297,12 +361,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const ssbClient = await openSsb(); const userId = ssbClient.id; - const messages = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit, reverse: true }), - pull.collect((err, msgs) => err ? rej(err) : res(msgs)) - ); - }); + const messages = (await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true })).reverse(); const allMsgs = messages.filter(m => { const c = m.value && m.value.content; @@ -316,15 +375,15 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const scopedMsgs = filter === 'MINE' ? allMsgs.filter(m => m.value.author === userId) : allMsgs; const byType = {}; - const parentOf = {}; + const versionsOf = {}; for (const t of types) { byType[t] = new Map(); - parentOf[t] = new Map(); + versionsOf[t] = []; } const fpIdx = tribeCrypto ? tribeCrypto.buildFingerprintIndex() : null; const accessibleTribeIds = await buildAccessibleTribeIds(); - for (const m of scopedMsgs) { + for (const m of allMsgs) { const k = m.key; let c = m.value.content; if (tribeCrypto && tribeCrypto.isTribeMsg(c)) { @@ -344,27 +403,20 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { let theType = c.type; if (!types.includes(theType)) continue; byType[theType].set(k, { key: k, ts: m.value.timestamp, content: c, author: m.value.author }); - if (c.replaces) parentOf[theType].set(k, c.replaces); + versionsOf[theType].push(versionEntry({ key: k, value: { ...m.value, content: c } })); } - const findRoot = (t, id) => { - let cur = id; - const pMap = parentOf[t]; - while (pMap.has(cur)) cur = pMap.get(cur); - return cur; - }; + const vgOf = {}; + for (const t of types) vgOf[t] = versionGroups(versionsOf[t]); + const findRoot = (t, id) => vgOf[t].rootOf(id); const tipOf = {}; for (const t of types) { tipOf[t] = new Map(); - const pMap = parentOf[t]; - const fwd = new Map(); - for (const [child, parent] of pMap.entries()) fwd.set(parent, child); const allMap = byType[t]; - const roots = new Set(Array.from(allMap.keys()).map(id => findRoot(t, id))); - for (const root of roots) { - let tip = root; - while (fwd.has(tip)) tip = fwd.get(tip); + for (const root of vgOf[t].roots) { + if (filter === 'MINE' && allMap.get(root).author !== userId) continue; + const tip = vgOf[t].tipOf(root); if (tombTargets.has(tip)) continue; const node = allMap.get(tip) || allMap.get(root); if (node) tipOf[t].set(root, node); @@ -395,6 +447,11 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { .filter(n => n.content?.isAnonymous !== false && Array.isArray(n.content?.members) && n.content.members.includes(userId)) .map(n => ({ id: findRoot('tribe', n.key), name: n.content.name || n.content.title || n.key })); + const opinionTargets = new Set(); + for (const m of allMsgs) { + const c = m && m.value && m.value.content; + if (c && typeof c.type === 'string' && c.type.endsWith('Opinion') && typeof c.target === 'string') opinionTargets.add(c.target); + } const content = {}; const opinions = {}; for (const t of types) { @@ -406,7 +463,9 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { if (t === 'forum') vals = vals.filter(c => !(c.root && tombTargets.has(c.root))); } content[t] = vals.length || 0; - opinions[t] = vals.filter(e => Array.isArray(e.opinions_inhabitants) && e.opinions_inhabitants.length > 0).length || 0; + const embedded = vals.filter(e => Array.isArray(e.opinions_inhabitants) && e.opinions_inhabitants.length > 0).length || 0; + const external = t === 'tribe' ? 0 : Array.from(tipOf[t].keys()).filter(root => vgOf[t].groups.get(root).some(id => opinionTargets.has(id))).length; + opinions[t] = Math.max(embedded, external); } if (filter === 'MINE') { @@ -428,12 +487,10 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const inhabitants = new Set(allMsgs.map(m => m.value.author)).size; - const secretStat = fs.statSync((require('../server/ssb_config').path + '/secret')); + const secretStat = fs.statSync(path.join(ssbDirOf(), 'secret')); const createdAt = secretStat.birthtime.toLocaleString(); - const folderSize = getFolderSize((require('../server/ssb_config').path + '')); - const flumeSize = getFolderSize((require('../server/ssb_config').path + '/flume')); - const blobsSize = getFolderSize((require('../server/ssb_config').path + '/blobs')); + const { total: folderSize, log: logSize, blobs: blobsSize } = await ssbDiskUsage(ssbDirOf()); const allTs = scopedMsgs.map(m => m.value.timestamp || 0).filter(Boolean); const lastTs = allTs.length ? Math.max(...allTs) : 0; @@ -572,7 +629,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { userTombstoneCount: scopedMsgs.filter(m => m.value.content.type === 'tombstone' && m.value.author === userId).length, networkTombstoneCount: validatedTombstoneCount, folderSize: formatSize(folderSize), - statsBlockchainSize: formatSize(flumeSize), + statsBlockchainSize: formatSize(logSize), statsBlobsSize: formatSize(blobsSize), pubsCount, activity: { diff --git a/src/models/subscriptions_model.js b/src/models/subscriptions_model.js new file mode 100644 index 0000000..bc94f6a --- /dev/null +++ b/src/models/subscriptions_model.js @@ -0,0 +1,104 @@ +const { getConfig } = require('../configs/config-manager.js'); +const { readTyped } = require('./typed_log'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +module.exports = ({ cooler }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + + const latestByTargetAuthor = (messages) => { + const latest = new Map(); + for (const m of messages) { + const c = m?.value?.content; + if (!c || c.type !== 'subscription' || typeof c.target !== 'string') continue; + const author = m.value.author; + const ts = m.value.timestamp || 0; + const k = `${c.target}::${author}`; + const prev = latest.get(k); + if (!prev || ts >= prev.ts) latest.set(k, { ts, target: c.target, scope: String(c.scope || ''), author, on: c.on !== false }); + } + return latest; + }; + + return { + async setSubscription(target, scope, on) { + const ssbClient = await openSsb(); + if (typeof target !== 'string' || !target.trim()) throw new Error('Invalid target'); + const content = { + type: 'subscription', + target: target.trim(), + scope: String(scope || '').trim().slice(0, 32), + on: on !== false, + updatedAt: new Date().toISOString() + }; + return new Promise((res, rej) => ssbClient.publish(content, (e, m) => (e ? rej(e) : res(m)))); + }, + + async myState(target) { + const ssbClient = await openSsb(); + const messages = await readTyped(ssbClient, ['subscription'], { limit: logLimit }); + const latest = latestByTargetAuthor(messages); + const mine = latest.get(`${target}::${ssbClient.id}`); + if (!mine) return null; + return mine.on ? 'on' : 'off'; + }, + + async isSubscribed(target) { + const ssbClient = await openSsb(); + const messages = await readTyped(ssbClient, ['subscription'], { limit: logLimit }); + const latest = latestByTargetAuthor(messages); + const mine = latest.get(`${target}::${ssbClient.id}`); + return !!(mine && mine.on); + }, + + async listSubscribers(target) { + const ssbClient = await openSsb(); + const messages = await readTyped(ssbClient, ['subscription'], { limit: logLimit }); + const latest = latestByTargetAuthor(messages); + const out = []; + for (const entry of latest.values()) { + if (entry.target === target && entry.on) out.push(entry.author); + } + return Array.from(new Set(out)); + }, + + async mySubscriptions() { + const ssbClient = await openSsb(); + const messages = await readTyped(ssbClient, ['subscription'], { limit: logLimit }); + const latest = latestByTargetAuthor(messages); + const out = []; + for (const entry of latest.values()) { + if (entry.author === ssbClient.id && entry.on) out.push({ target: entry.target, scope: entry.scope }); + } + return out; + }, + + async subscribersMap(targets) { + const ssbClient = await openSsb(); + const messages = await readTyped(ssbClient, ['subscription'], { limit: logLimit }); + const latest = latestByTargetAuthor(messages); + const out = new Map(); + for (const t of targets || []) out.set(t, []); + for (const entry of latest.values()) { + if (!out.has(entry.target) || !entry.on) continue; + out.get(entry.target).push(entry.author); + } + return out; + }, + + async subscriberCounts(targets) { + const ssbClient = await openSsb(); + const messages = await readTyped(ssbClient, ['subscription'], { limit: logLimit }); + const latest = latestByTargetAuthor(messages); + const counts = new Map(); + for (const t of targets || []) counts.set(t, 0); + const mine = new Set(); + for (const entry of latest.values()) { + if (!counts.has(entry.target) || !entry.on) continue; + counts.set(entry.target, counts.get(entry.target) + 1); + if (entry.author === ssbClient.id) mine.add(entry.target); + } + return { counts, mine }; + } + }; +}; diff --git a/src/models/tags_model.js b/src/models/tags_model.js index 3b1b2ac..45f7f38 100644 --- a/src/models/tags_model.js +++ b/src/models/tags_model.js @@ -1,6 +1,8 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, CONTENT_TYPES } = require('./typed_log'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; module.exports = ({ cooler, padsModel, tribesModel }) => { @@ -108,16 +110,10 @@ module.exports = ({ cooler, padsModel, tribesModel }) => { async listTags(filter = 'all', search = '') { const ssbClient = await openSsb(); - const messages = await new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ); - }); + const messages = await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }); const tombstoned = buildValidatedTombstoneSet(messages); - const replacesMap = new Map(); const latestByKey = new Map(); for (const msg of messages) { @@ -125,24 +121,15 @@ module.exports = ({ cooler, padsModel, tribesModel }) => { const c = msg?.value?.content; const t = c?.type; if (!k || !c || !t) continue; - if (tombstoned.has(k)) continue; if (t === 'tombstone') continue; - if (c.replaces) replacesMap.set(c.replaces, k); latestByKey.set(k, msg); } - for (const [oldId, newId] of Array.from(replacesMap.entries())) { - const oldMsg = latestByKey.get(oldId); - if (!oldMsg) { replacesMap.delete(oldId); continue; } - const newMsg = latestByKey.get(newId); - if (!newMsg || String(newMsg?.value?.author) !== String(oldMsg?.value?.author)) { - replacesMap.delete(oldId); - latestByKey.delete(newId); - } + const vg = versionGroups(Array.from(latestByKey.values()).map(versionEntry)); + for (const k of Array.from(latestByKey.keys())) { + if (!vg.roots.has(vg.rootOf(k)) || vg.tipOf(k) !== k || tombstoned.has(k)) latestByKey.delete(k); } - for (const oldId of replacesMap.keys()) latestByKey.delete(oldId); - const viewerId = ssbClient.id; const viewerVisibleTribeIds = new Set(); if (tribesModel) { @@ -216,7 +203,7 @@ module.exports = ({ cooler, padsModel, tribesModel }) => { if (filter === 'mine') { tags = tags.filter(t => (t.mine || 0) > 0).sort((a, b) => b.mine - a.mine || a.name.localeCompare(b.name)); } else if (filter === 'recent') { - const since = Date.now() - 7 * DAY; + const since = Date.now() - DAY; tags = tags.filter(t => (t.lastTs || 0) >= since).sort((a, b) => (b.lastTs || 0) - (a.lastTs || 0)); } else if (filter === 'top') { tags.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)); diff --git a/src/models/tasks_model.js b/src/models/tasks_model.js index 1fd0e29..c810f37 100644 --- a/src/models/tasks_model.js +++ b/src/models/tasks_model.js @@ -3,7 +3,8 @@ const moment = require('../server/node_modules/moment'); const { normalizeImages, normalizeVideo } = require('./media_gallery'); const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; module.exports = ({ cooler, pmModel }) => { @@ -23,13 +24,9 @@ module.exports = ({ cooler, pmModel }) => { return fallback; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const TASK_TYPES = ['task', 'taskOpinion', 'taskAssign', 'taskReminderSent', 'tombstone']; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, TASK_TYPES, { limit: logLimit }); const getMsg = async (ssbClient, key) => new Promise((resolve, reject) => { @@ -39,9 +36,6 @@ module.exports = ({ cooler, pmModel }) => { const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const opinionMsgs = []; const assignMsgs = []; @@ -57,34 +51,12 @@ module.exports = ({ cooler, pmModel }) => { if (c.type !== 'task') continue; const ts = v.timestamp || m.timestamp || 0; - nodes.set(k, { key: k, ts, c, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, author: v.author }); } - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === 'string' ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } @@ -117,9 +89,9 @@ module.exports = ({ cooler, pmModel }) => { }; const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); - return { tomb, nodes, parent, child, rootOf, tipByRoot, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildTask = (node, rootId, agg, now) => { @@ -141,6 +113,11 @@ module.exports = ({ cooler, pmModel }) => { }; }; + const currentTipId = async (ssb, id) => { + const idx = buildIndex(await getAllMessages(ssb)); + return idx.tipByRoot.get(idx.rootOf(id)) || id; + }; + return { async createTask(title, description, startTime, endTime, priority, location = '', tagsRaw = [], isPublic, media = {}) { const ssb = await openSsb(); @@ -185,9 +162,10 @@ module.exports = ({ cooler, pmModel }) => { async deleteTaskById(taskId) { const ssb = await openSsb(); const userId = ssb.id; - const task = await new Promise((res, rej) => ssb.get(taskId, (err, task) => err ? rej(new Error('Task not found')) : res(task))); + const tipId = await currentTipId(ssb, taskId); + const task = await new Promise((res, rej) => ssb.get(tipId, (err, task) => err ? rej(new Error('Task not found')) : res(task))); if (task.content.author !== userId) throw new Error('Not the author'); - const tombstone = { type: 'tombstone', target: taskId, deletedAt: new Date().toISOString(), author: userId }; + const tombstone = { type: 'tombstone', target: tipId, deletedAt: new Date().toISOString(), author: userId }; return new Promise((res, rej) => ssb.publish(tombstone, (err, result) => err ? rej(err) : res(result))); }, @@ -198,8 +176,9 @@ module.exports = ({ cooler, pmModel }) => { const keys = Object.keys(updatedData || {}).filter(k => updatedData[k] !== undefined); if (keys.length === 1 && keys[0] === 'assignees') return this.toggleAssignee(taskId); + const tipId = await currentTipId(ssb, taskId); const old = await new Promise((res, rej) => - ssb.get(taskId, (err, msg) => err || !msg ? rej(new Error('Task not found')) : res(msg)) + ssb.get(tipId, (err, msg) => err || !msg ? rej(new Error('Task not found')) : res(msg)) ); const c = old.content; @@ -262,7 +241,7 @@ module.exports = ({ cooler, pmModel }) => { video: updatedData.video !== undefined ? normalizeVideo(updatedData.video) : normalizeVideo(c.video), assignees: nextAssignees, updatedAt: new Date().toISOString(), - replaces: taskId + replaces: tipId }; return new Promise((res, rej) => ssb.publish(updated, (err, result) => err ? rej(err) : res(result))); @@ -278,10 +257,10 @@ module.exports = ({ cooler, pmModel }) => { const ssb = await openSsb(); const now = moment(); const messages = await getAllMessages(ssb); - const idx = buildIndex(messages); + const idx = memoIndex('tasks', messages, buildIndex); const rootId = idx.rootOf(taskId); const agg = idx.resolveGroup(rootId); - if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error('Task not found'); + if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error('Task not found'); return buildTask(agg.contentNode, rootId, agg, now); }, diff --git a/src/models/tombstone_validator.js b/src/models/tombstone_validator.js index a3a5b4d..e90cdb8 100644 --- a/src/models/tombstone_validator.js +++ b/src/models/tombstone_validator.js @@ -2,26 +2,27 @@ function buildValidatedTombstoneSet(messages) { if (!Array.isArray(messages)) return new Set(); const tombClaims = new Map(); const targetAuthors = new Map(); + const targetContentAuthors = new Map(); const targetRoots = new Map(); + const claims = []; for (const m of messages) { if (!m || !m.value) continue; const v = m.value; const c = v.content; if (!c) continue; if (typeof c === 'object' && c.type === 'tombstone' && typeof c.target === 'string') { - const ts = v.timestamp || 0; - const prev = tombClaims.get(c.target); - if (!prev || ts > prev.ts) tombClaims.set(c.target, { author: v.author, ts, rootId: c._rootId || null }); + claims.push({ target: c.target, author: v.author, rootId: c._rootId || null }); continue; } if (m.key) { targetAuthors.set(m.key, v.author); + if (typeof c === 'object' && c.type === 'feed' && typeof c.author === 'string') targetContentAuthors.set(m.key, c.author); if (typeof c === 'object' && c._rootId) targetRoots.set(m.key, c._rootId); } } const out = new Set(); - for (const [target, { author, rootId }] of tombClaims.entries()) { - if (targetAuthors.get(target) !== author) continue; + for (const { target, author, rootId } of claims) { + if (targetAuthors.get(target) !== author && targetContentAuthors.get(target) !== author) continue; if (rootId) { const targetRoot = targetRoots.get(target); if (targetRoot !== rootId) continue; diff --git a/src/models/torrent_downloads_model.js b/src/models/torrent_downloads_model.js new file mode 100644 index 0000000..7da185f --- /dev/null +++ b/src/models/torrent_downloads_model.js @@ -0,0 +1,169 @@ +const pull = require('../server/node_modules/pull-stream'); +const Abortable = require('../server/node_modules/pull-abortable'); + +const JOB_TTL_MS = 6 * 60 * 60 * 1000; +const PROBE_TIMEOUT_MS = 8000; +const RECHECK_MS = 15000; + +module.exports = ({ cooler }) => { + const jobs = new Map(); + let recheckTimer = null; + + const openSsb = () => cooler.open(); + const withTimeout = (p, ms) => Promise.race([p, new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms))]); + + const peerRpcs = (ssb) => { + const out = []; + const live = ssb && ssb.peers && typeof ssb.peers === 'object' ? ssb.peers : {}; + for (const id of Object.keys(live)) { + if (id === ssb.id) continue; + for (const rpc of (Array.isArray(live[id]) ? live[id] : [])) { + if (rpc && rpc.blobs && typeof rpc.blobs.get === 'function' && !rpc.closed) out.push({ id, rpc }); + } + } + return out; + }; + + const hasLocal = (ssb, blobId) => new Promise((resolve) => { + try { ssb.blobs.has(blobId, (err, has) => resolve(!err && !!has)); } catch (_) { resolve(false); } + }); + + const remoteHas = (peer, blobId) => withTimeout(new Promise((resolve) => { + try { peer.rpc.blobs.has(blobId, (err, has) => resolve(!err && !!has)); } catch (_) { resolve(false); } + }), PROBE_TIMEOUT_MS).catch(() => false); + + const findSeeds = async (ssb, blobId) => { + const peers = peerRpcs(ssb); + const flags = await Promise.all(peers.map((p) => remoteHas(p, blobId))); + return peers.filter((_, i) => flags[i]); + }; + + const fetchFrom = (ssb, seed, job) => new Promise((resolve, reject) => { + const abortable = Abortable(); + job.abort = () => abortable.abort(new Error('cancelled')); + let received = 0; + const cap = job.size > 0 ? job.size : 75 * 1024 * 1024; + const opts = { key: job.blobId, max: cap }; + pull( + seed.rpc.blobs.get(opts), + abortable, + pull.through((buf) => { received += buf.length; job.received = received; if (received > cap) abortable.abort(new Error('too large')); }), + ssb.blobs.add(job.blobId, (err) => { job.abort = null; err ? reject(err) : resolve(received); }) + ); + }); + + const finish = (job, state, error) => { + job.state = state; + job.error = error ? String(error.message || error) : ''; + job.finishedAt = Date.now(); + job.abort = null; + if (state === 'done' && job.size > 0) job.received = job.size; + }; + + const ensureRecheck = () => { + if (recheckTimer) return; + recheckTimer = setInterval(async () => { + const pending = Array.from(jobs.values()).filter((j) => j.state === 'nosource'); + if (!pending.length) { clearInterval(recheckTimer); recheckTimer = null; return; } + let ssb; + try { ssb = await openSsb(); } catch (_) { return; } + for (const job of pending) { + if (await hasLocal(ssb, job.blobId)) { finish(job, 'done'); continue; } + const seeds = await findSeeds(ssb, job.blobId); + if (seeds.length) run(job); + } + }, RECHECK_MS); + if (recheckTimer.unref) recheckTimer.unref(); + }; + + const run = async (job) => { + if (job.state === 'downloading' || job.state === 'probing') return; + let ssb; + try { ssb = await openSsb(); } catch (err) { finish(job, 'failed', err); return; } + if (await hasLocal(ssb, job.blobId)) { finish(job, 'done'); return; } + job.state = 'probing'; + job.error = ''; + const seeds = await findSeeds(ssb, job.blobId); + job.seeds = seeds.map((s) => s.id); + if (!seeds.length) { + job.state = 'nosource'; + try { ssb.blobs.want(job.blobId, () => {}); } catch (_) {} + ensureRecheck(); + return; + } + job.state = 'downloading'; + job.startedAt = job.startedAt || Date.now(); + let lastErr = null; + for (const seed of seeds) { + if (job.cancelled) { finish(job, 'cancelled'); return; } + job.peer = seed.id; + job.received = 0; + try { + await fetchFrom(ssb, seed, job); + finish(job, 'done'); + return; + } catch (err) { + lastErr = err; + if (job.cancelled) { finish(job, 'cancelled'); return; } + } + } + finish(job, 'failed', lastErr); + }; + + const sweep = () => { + const now = Date.now(); + for (const [key, job] of jobs) if (job.finishedAt && now - job.finishedAt > JOB_TTL_MS) jobs.delete(key); + }; + + const snapshot = (job) => ({ + blobId: job.blobId, + name: job.name, + size: job.size, + received: Math.min(job.received || 0, job.size || job.received || 0), + state: job.state, + seeds: Array.isArray(job.seeds) ? job.seeds.length : 0, + peer: job.peer || '', + error: job.error || '', + torrentKey: job.torrentKey || '', + startedAt: job.startedAt || 0, + finishedAt: job.finishedAt || 0, + active: job.state === 'queued' || job.state === 'probing' || job.state === 'downloading' || job.state === 'nosource' + }); + + return { + start({ blobId, size, name, torrentKey } = {}) { + const id = String(blobId || ''); + if (!id.startsWith('&')) throw new Error('torrentFromContentMissing'); + sweep(); + let job = jobs.get(id); + if (job && (job.state === 'downloading' || job.state === 'probing' || job.state === 'done')) return snapshot(job); + job = { blobId: id, size: Number(size) || 0, name: String(name || ''), torrentKey: torrentKey || '', received: 0, state: 'queued', seeds: [], error: '', cancelled: false, startedAt: 0, finishedAt: 0, abort: null, peer: '' }; + jobs.set(id, job); + run(job); + return snapshot(job); + }, + cancel(blobId) { + const job = jobs.get(String(blobId || '')); + if (!job) return false; + job.cancelled = true; + if (typeof job.abort === 'function') { try { job.abort(); } catch (_) {} } + else if (job.state !== 'done') finish(job, 'cancelled'); + return true; + }, + remove(blobId) { return jobs.delete(String(blobId || '')); }, + get(blobId) { + const job = jobs.get(String(blobId || '')); + return job ? snapshot(job) : null; + }, + list() { + sweep(); + return Array.from(jobs.values()).map(snapshot).sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0)); + }, + async hasLocal(blobId) { + try { return await hasLocal(await openSsb(), String(blobId || '')); } catch (_) { return false; } + }, + async seedsFor(blobId) { + try { return (await findSeeds(await openSsb(), String(blobId || ''))).map((s) => s.id); } catch (_) { return []; } + } + }; +}; diff --git a/src/models/torrents_model.js b/src/models/torrents_model.js index 9dc74cb..485e05d 100644 --- a/src/models/torrents_model.js +++ b/src/models/torrents_model.js @@ -1,8 +1,9 @@ const pull = require("../server/node_modules/pull-stream"); const { getConfig } = require("../configs/config-manager.js"); const categories = require("../backend/opinion_categories"); +const { readTyped, memoIndex } = require("./typed_log"); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const safeArr = (v) => (Array.isArray(v) ? v : []); @@ -13,6 +14,10 @@ const normalizeTags = (raw) => { return String(raw).split(",").map((t) => t.trim()).filter(Boolean); }; +const cipherField = (cipher) => (cipher && typeof cipher === "object" && cipher.key && cipher.manifestBlobId) + ? { cipher: { v: Number(cipher.v) || 1, key: String(cipher.key), manifestBlobId: String(cipher.manifestBlobId), chunkCount: Number(cipher.chunkCount) || 0 } } + : {}; + const parseBlobId = (blobMarkdown) => { const s = String(blobMarkdown || ""); const match = s.match(/\((&[^)]+\.sha256)\)/); @@ -40,10 +45,9 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const unwrapForIndex = (msgs) => tribeHelpers ? tribeHelpers.unwrapMessagesForKind(msgs, 'torrent') : msgs; const tombFor = async (target, tribeId, author) => tribeHelpers ? tribeHelpers.encryptTombstone(target, tribeId, author) : { type: 'tombstone', target, deletedAt: new Date().toISOString(), author }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull(ssbClient.createLogStream({ limit: logLimit }), pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))); - }); + const TORRENT_TYPES = ["torrent", "torrentOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, TORRENT_TYPES, { limit: logLimit, withWindow: true }); const getMsg = async (ssbClient, key) => new Promise((resolve) => { @@ -53,9 +57,6 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const buildIndex = (messages) => { const tomb = new Set(); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const authorByKey = new Map(); const tombRequests = []; const opinionMsgs = []; @@ -78,49 +79,24 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const ts = v.timestamp || m.timestamp || 0; let sizeBytes = 0; try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; } - nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author }); + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); authorByKey.set(k, v.author); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } } - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; for (const t of tombRequests) { const targetAuthor = authorByKey.get(t.target); if (targetAuthor && t.author === targetAuthor) tomb.add(t.target); } - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); - const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); - - const forward = new Map(); - for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); const resolveGroup = (root) => { const contentTip = contentTipOf(root); @@ -137,7 +113,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { return { contentTip, contentNode, opinions, voters }; }; - return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildTorrent = (node, rootId, viewerId, agg) => { @@ -156,6 +132,10 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { title: undec ? "" : (c.title || ""), description: undec ? "" : (c.description || ""), size: c.size || 0, + source: undec ? "" : (typeof c.source === "string" && c.source.startsWith("&") ? c.source : ""), + sourceName: undec ? "" : (c.sourceName || ""), + cipher: undec ? null : (c.cipher && typeof c.cipher === "object" && typeof c.cipher.key === "string" && typeof c.cipher.manifestBlobId === "string" ? { v: Number(c.cipher.v) || 1, key: c.cipher.key, manifestBlobId: c.cipher.manifestBlobId, chunkCount: Number(c.cipher.chunkCount) || 0 } : null), + sourceMime: undec ? "" : (c.sourceMime || ""), opinions, opinions_inhabitants: voters, hasVoted: viewerId ? voters.includes(viewerId) : false, @@ -184,16 +164,12 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(unwrapForIndex(messages)); - let tip = id; - while (idx.forward.has(tip)) tip = idx.forward.get(tip); - if (idx.tomb.has(tip)) throw new Error("Torrent not found"); - - let root = tip; - while (idx.parent.has(root)) root = idx.parent.get(root); + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Torrent not found"); return root; }, - async createTorrent(blobMarkdown, tagsRaw, title, description, size, tribeId) { + async createTorrent(blobMarkdown, tagsRaw, title, description, size, tribeId, source = null) { const ssbClient = await openSsb(); const blobId = parseBlobId(blobMarkdown); const tags = normalizeTags(tagsRaw) || []; @@ -211,7 +187,11 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { size: Number(size) || 0, opinions: {}, opinions_inhabitants: [], - ...(tribeId ? { tribeId } : {}) + ...(tribeId ? { tribeId } : {}), + ...(source && typeof source.source === "string" && source.source.startsWith("&") + ? { source: source.source, sourceName: String(source.sourceName || ""), sourceMime: String(source.sourceMime || "") } + : {}), + ...cipherField(source && source.cipher) }; content = await encryptIfTribe(content); @@ -221,7 +201,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { }); }, - async updateTorrentById(id, blobMarkdown, tagsRaw, title, description) { + async updateTorrentById(id, blobMarkdown, tagsRaw, title, description, extra = null) { const ssbClient = await openSsb(); const userId = ssbClient.id; const tipId = await this.resolveCurrentId(id); @@ -231,7 +211,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const oldDec = await decryptIfTribe(oldMsg.content); if (!oldDec || oldDec.type !== "torrent") throw new Error("Torrent not found"); assertReadable(oldDec, "Torrent"); - if ((oldDec.author || oldMsg.content.author) !== userId) throw new Error("Not the author"); + if (oldMsg.author !== userId) throw new Error("Not the author"); const aggTorrent = await this.getTorrentById(id, userId); if (aggTorrent && Object.keys(aggTorrent.opinions || {}).some(k => (aggTorrent.opinions[k] || 0) > 0)) throw new Error("Cannot edit torrent after it has received opinions."); @@ -243,6 +223,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { type: "torrent", replaces: tipId, url: blobId || oldDec.url, + ...(blobId ? cipherField(extra && extra.cipher) : cipherField(oldDec.cipher)), tags, title: title !== undefined ? title || "" : oldDec.title || "", description: description !== undefined ? description || "" : oldDec.description || "", @@ -250,7 +231,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { opinions: oldDec.opinions || {}, opinions_inhabitants: oldDec.opinions_inhabitants || [], author: oldDec.author || userId, - ...(oldMsg.content.tribeId ? { tribeId: oldMsg.content.tribeId } : {}), + ...((oldDec.tribeId || oldMsg.content.tribeId) ? { tribeId: oldDec.tribeId || oldMsg.content.tribeId } : {}), createdAt: oldDec.createdAt, updatedAt: now }; @@ -274,7 +255,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { if (!msg) throw new Error("Torrent not found"); const dec = await decryptIfTribe(msg.content); if (!dec || dec.type !== "torrent") throw new Error("Torrent not found"); - if ((dec.author || msg.content.author) !== userId) throw new Error("Not the author"); + if (msg.author !== userId) throw new Error("Not the author"); const tombstone = await tombFor(tipId, dec.tribeId || msg.content.tribeId, userId); @@ -338,10 +319,10 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const ssbClient = await openSsb(); const viewer = viewerId || ssbClient.id; const messages = await getAllMessages(ssbClient); - const idx = buildIndex(unwrapForIndex(messages)); - await decryptIndexNodes(idx); + const idx = await memoIndex('torrents', messages, async (m) => { const built = buildIndex(unwrapForIndex(m)); await decryptIndexNodes(built); return built; }); const root = idx.rootOf(id); + if (idx.nodes.has(root) && !idx.tipByRoot.has(root)) throw new Error("Torrent not found"); const agg = idx.resolveGroup(root); if (agg.contentNode && !idx.tomb.has(agg.contentTip)) return buildTorrent(agg.contentNode, root, viewer, agg); diff --git a/src/models/transfers_model.js b/src/models/transfers_model.js index 8e012e2..a4c2014 100644 --- a/src/models/transfers_model.js +++ b/src/models/transfers_model.js @@ -1,11 +1,14 @@ const pull = require("../server/node_modules/pull-stream") const moment = require("../server/node_modules/moment") -const { getConfig } = require("../configs/config-manager.js") +const { getConfig, defaultInvite } = require("../configs/config-manager.js") const categories = require("../backend/opinion_categories") const { buildValidatedTombstoneSet } = require('./tombstone_validator'); const { dedupeByPreferring, norm } = require('../backend/dedupe') +const { readTyped, memoIndex } = require('./typed_log') const logLimit = getConfig().ssbLogStream?.limit || 1000 +const TRANSFER_TYPES = ["transfer", "transferConfirm", "transferOpinion", "ubiClaim", "tombstone"] + const isValidId = (to) => /^@[A-Za-z0-9+/]+={0,2}\.ed25519$/.test(String(to || "")) const parseNum = (v) => { @@ -19,6 +22,11 @@ const normalizeTags = (raw) => { return String(raw).split(",").map(t => t.trim()).filter(Boolean) } +const defaultPubId = () => { + const m = String((defaultInvite() || {}).code || "").match(/(@[A-Za-z0-9+/]+={0,2}\.ed25519)/) + return m ? m[1] : "" +} + const CATEGORIES = ["ECONOMIC", "TIME", "TRUST"] const normalizeCategory = (raw) => { const c = String(raw || "ECONOMIC").trim().toUpperCase() @@ -28,14 +36,16 @@ const normalizeCategory = (raw) => { module.exports = ({ cooler }) => { let ssb const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb } + const ubiEpochOf = (c) => { + const tags = Array.isArray(c.tags) ? c.tags.map(t => String(t)) : [] + const fromTag = tags.find(t => /^epoch:\d{4}-\d{2}$/i.test(t)) + if (fromTag) return fromTag.slice(6) + const m = String(c.concept || "").match(/(\d{4}-\d{2})/) + return m ? m[1] : "" + } const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ) - }) + readTyped(ssbClient, TRANSFER_TYPES, { limit: logLimit }) const getMsg = async (ssbClient, key) => new Promise((resolve, reject) => { @@ -43,7 +53,7 @@ module.exports = ({ cooler }) => { }) const buildIndex = (messages) => { - const tomb = new Set() + const tomb = buildValidatedTombstoneSet(messages) const nodes = new Map() const parent = new Map() const child = new Map() @@ -51,7 +61,7 @@ module.exports = ({ cooler }) => { const confirms = [] const opinionMsgs = [] const ubiByPub = new Map() - const ubiByUser = new Map() + const claimedPubs = new Map() const ubiClaimNodes = [] for (const m of messages) { @@ -60,43 +70,43 @@ module.exports = ({ cooler }) => { const c = v.content if (!c) continue - if (c.type === "tombstone" && c.target) { - tomb.add(c.target) - continue - } - if (c.type === "transferConfirm") { confirms.push({ target: c.target, author: v.author, ts: v.timestamp || 0 }); continue } + if (c.type === "tombstone") continue + if (c.type === "transferConfirm") { confirms.push({ target: c.target, tip: typeof c.tip === "string" ? c.tip : null, author: v.author, ts: v.timestamp || 0 }); continue } if (c.type === "transferOpinion") { opinionMsgs.push({ target: c.target, author: v.author, category: c.category, ts: v.timestamp || 0 }); continue } if (c.type === "transfer") { + if (c.from !== v.author) continue nodes.set(k, { key: k, ts: v.timestamp || m.timestamp || 0, c, author: v.author }) if (c.replaces) { parent.set(k, c.replaces) child.set(c.replaces, k) } const tags = Array.isArray(c.tags) ? c.tags.map(t => String(t).toUpperCase()) : [] - if (tags.includes("UBI") && c.to && c.concept) { - const key = `${c.to}::${c.concept}` - if (v.author === c.from) ubiByPub.set(key, k) - else ubiByUser.set(key, k) + if (tags.includes("UBI") && !tags.includes("REBALANCE") && c.to && c.concept) { + const key = `${c.to}::${ubiEpochOf(c) || c.concept}` + if (!ubiByPub.has(key)) ubiByPub.set(key, new Set()) + ubiByPub.get(key).add(v.author) } } if (c.type === "ubiClaim") { ubiClaimNodes.push({ k, v, c, ts: v.timestamp || m.timestamp || 0 }) + if (c.pubId) { + if (!claimedPubs.has(v.author)) claimedPubs.set(v.author, new Set()) + claimedPubs.get(v.author).add(c.pubId) + } } } - for (const [key, userMsgKey] of ubiByUser.entries()) { - if (ubiByPub.has(key)) tomb.add(userMsgKey) - } - + const defaultPub = defaultPubId() for (const { k, v, c, ts } of ubiClaimNodes) { if (tomb.has(k)) continue const claimantId = v.author const epochId = c.epochId || "" - const concept = `UBI ${epochId} ${claimantId}`.trim() - const key = `${claimantId}::${concept}` - if (ubiByPub.has(key) || ubiByUser.has(key)) continue + const concept = `UBI - ${epochId}`.trim() + const key = `${claimantId}::${epochId || concept}` + const payers = ubiByPub.get(key) + if (payers && ((c.pubId && payers.has(c.pubId)) || (defaultPub && payers.has(defaultPub)))) continue const synthetic = { type: "transfer", from: c.pubId || "", @@ -106,7 +116,7 @@ module.exports = ({ cooler }) => { createdAt: c.claimedAt || new Date(ts).toISOString(), updatedAt: c.claimedAt || new Date(ts).toISOString(), deadline: null, - confirmedBy: [c.pubId || ""].filter(Boolean), + confirmedBy: [], status: "UNCONFIRMED", tags: ["UBI", "PENDING"], opinions: {}, @@ -153,42 +163,43 @@ module.exports = ({ cooler }) => { const resolveGroup = (root) => { const contentTip = contentTipOf(root) - const best = nodes.get(contentTip) || nodes.get(root) - if (!best) return null + const latest = nodes.get(contentTip) || nodes.get(root) + if (!latest) return null const rootAuthor = nodes.get(root) ? nodes.get(root).author : null - const groupKeys = []; { let x = root, g = 0; groupKeys.push(x); while (child.has(x) && g++ < 100000) { x = child.get(x); groupKeys.push(x) } } + const rootConfirms = confirmsByRoot.get(root) || [] + const seal = rootConfirms.filter(cf => { const n = cf.tip ? nodes.get(cf.tip) : null; return n && n.author === rootAuthor && rootOf(cf.tip) === root && cf.author === n.c.to }).sort((a, b) => a.ts - b.ts)[0] + const best = seal ? nodes.get(seal.tip) : latest const confirmedBy = new Set() const opinions = {}; const opinionSet = new Set() - for (const k of groupKeys) { - const n = nodes.get(k); if (!n) continue; if (n.author !== rootAuthor) continue; const c = n.c - for (const cb of (Array.isArray(c.confirmedBy) ? c.confirmedBy : [])) confirmedBy.add(cb) - for (const v of (Array.isArray(c.opinions_inhabitants) ? c.opinions_inhabitants : [])) opinionSet.add(v) - if (c.opinions && typeof c.opinions === "object") for (const kk of Object.keys(c.opinions)) opinions[kk] = Math.max(opinions[kk] || 0, Number(c.opinions[kk]) || 0) - } - if (best.c.from) confirmedBy.add(best.c.from) - for (const cf of (confirmsByRoot.get(root) || [])) confirmedBy.add(cf.author) + if (best.c.from && best.c.from === best.author) confirmedBy.add(best.c.from) + for (const cf of rootConfirms) { if (cf.author === best.c.to) confirmedBy.add(cf.author) } for (const op of (opinionsByRoot.get(root) || [])) { if (!opinionSet.has(op.author)) { opinionSet.add(op.author); opinions[op.category] = (opinions[op.category] || 0) + 1 } } - return { contentTip, best, confirmedBy: [...confirmedBy], opinions, opinions_inhabitants: [...opinionSet], tombstoned: tomb.has(contentTip) } + const ubiPayerTrusted = !!best.author && (best.author === defaultPub || !!(claimedPubs.get(best.c.to) && claimedPubs.get(best.c.to).has(best.author))) + return { contentTip, best, confirmedBy: [...confirmedBy], opinions, opinions_inhabitants: [...opinionSet], tombstoned: tomb.has(contentTip) || tomb.has(best.key), ubiPayerTrusted } } return { tomb, nodes, parent, child, rootOf, tipByRoot, resolveGroup } } - const deriveStatus = (t) => { - const status = String(t.status || "").toUpperCase() + const isSettledUbi = (t) => { + const tags = Array.isArray(t.tags) ? t.tags.map(x => String(x).toUpperCase()) : [] + return tags.includes("UBI") && !tags.includes("PENDING") && /^[0-9a-f]{64}$/i.test(String(t.txid || "")) + } + + const deriveStatus = (t, ubiPayerTrusted = false) => { const required = t.from === t.to ? 1 : 2 const confirmedCount = Array.isArray(t.confirmedBy) ? t.confirmedBy.length : 0 - if (status === "DISCARDED") return "DISCARDED" + if (ubiPayerTrusted && isSettledUbi(t)) return "CLOSED" if (confirmedCount >= required) return "CLOSED" const dl = t.deadline ? moment(t.deadline) : null if (dl && dl.isValid() && dl.isBefore(moment())) return "DISCARDED" - return status === "CLOSED" ? "CLOSED" : "UNCONFIRMED" + return "UNCONFIRMED" } const buildTransfer = (node, agg) => { const c = node.c || {} - const confirmedBy = agg ? agg.confirmedBy : (Array.isArray(c.confirmedBy) ? c.confirmedBy : []) + const confirmedBy = agg ? agg.confirmedBy : (c.from && c.from === node.author ? [c.from] : []) const opinions = agg ? agg.opinions : (c.opinions || {}) const opinions_inhabitants = agg ? agg.opinions_inhabitants : (Array.isArray(c.opinions_inhabitants) ? c.opinions_inhabitants : []) return { @@ -203,8 +214,9 @@ module.exports = ({ cooler }) => { updatedAt: c.updatedAt || null, deadline: c.deadline, confirmedBy, - status: deriveStatus({ ...c, confirmedBy }), + status: deriveStatus({ ...c, confirmedBy }, !!(agg && agg.ubiPayerTrusted)), tags: Array.isArray(c.tags) ? c.tags : [], + txid: typeof c.txid === "string" ? c.txid : null, opinions, opinions_inhabitants } @@ -269,6 +281,7 @@ module.exports = ({ cooler }) => { const old = await getMsg(ssbClient, tipId) if (!old?.content || old.content.type !== "transfer") throw new Error("Transfer not found") + if (old.author !== old.content.from) throw new Error("Not the author") const current = old.content const idxU = buildIndex(await getAllMessages(ssbClient)) @@ -331,6 +344,7 @@ module.exports = ({ cooler }) => { if (!msg?.content) throw new Error("Not found") if (msg.content.type !== "transfer") throw new Error("Not found") + if (msg.author !== msg.content.from) throw new Error("Not found") const idx = buildIndex(await getAllMessages(ssbClient)) const root = idx.rootOf(tipId) @@ -345,7 +359,7 @@ module.exports = ({ cooler }) => { if ((Array.isArray(t.confirmedBy) ? t.confirmedBy : []).includes(userId)) throw new Error("Already confirmed") - const content = { type: "transferConfirm", target: root, createdAt: new Date().toISOString() } + const content = { type: "transferConfirm", target: root, tip: tipId, createdAt: new Date().toISOString() } return new Promise((resolve, reject) => { ssbClient.publish(content, (e2, result) => e2 ? reject(e2) : resolve(result)) }) @@ -358,6 +372,7 @@ module.exports = ({ cooler }) => { const msg = await getMsg(ssbClient, tipId) if (!msg?.content || msg.content.type !== "transfer") throw new Error("Not found") + if (msg.author !== msg.content.from) throw new Error("Not the author") const idxD = buildIndex(await getAllMessages(ssbClient)) const gD = idxD.resolveGroup(idxD.rootOf(tipId)) @@ -396,7 +411,7 @@ module.exports = ({ cooler }) => { async getTransferById(id) { const ssbClient = await openSsb() const messages = await getAllMessages(ssbClient) - const idx = buildIndex(messages) + const idx = memoIndex('transfers', messages, buildIndex) const root = idx.rootOf(id) const g = idx.resolveGroup(root) @@ -405,6 +420,7 @@ module.exports = ({ cooler }) => { const msg = await getMsg(ssbClient, id) if (!msg?.content || msg.content.type !== "transfer") throw new Error("Not found") + if (msg.author !== msg.content.from) throw new Error("Not found") const tmpNode = { key: id, ts: msg.timestamp || 0, c: msg.content, author: msg.author } return buildTransfer(tmpNode) diff --git a/src/models/trending_model.js b/src/models/trending_model.js index 2c940ce..9b4929c 100644 --- a/src/models/trending_model.js +++ b/src/models/trending_model.js @@ -3,7 +3,9 @@ const { getConfig } = require('../configs/config-manager.js'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const opinionCategories = require('../backend/opinion_categories'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { readTyped, CONTENT_TYPES, requestScope } = require('./typed_log'); const { buildVoteTally } = require('../backend/vote_tally'); +const { versionGroups, versionEntry } = require('../backend/dedupe'); module.exports = ({ cooler }) => { let ssb; @@ -20,27 +22,19 @@ module.exports = ({ cooler }) => { const types = [ 'bookmark', 'votes', 'poll', 'feed', - 'image', 'audio', 'video', 'document', 'torrent', 'transfer', - 'industry', 'project', 'report', 'task', 'event', 'shopProduct', 'housing', 'market' + 'image', 'audio', 'video', 'document', 'torrent', 'file', 'transfer', + 'industry', 'project', 'report', 'task', 'event', 'shopProduct', 'housing', 'market', 'schoolCourse', + 'podcast', 'podcastEpisode', 'campaign', 'logisticsRoute' ]; const categories = opinionCategories; - const listTrending = async (filter = 'ALL') => { + const computeBase = async () => { const ssbClient = await openSsb(); - const userId = ssbClient.id; - - const messages = await new Promise((res, rej) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, xs) => err ? rej(err) : res(xs)) - ); - }); + const messages = await readTyped(ssbClient, CONTENT_TYPES, { limit: logLimit, withWindow: true }); const tombstoned = buildValidatedTombstoneSet(messages); - const replaces = new Map(); const itemsById = new Map(); - const authorOf = new Map(); const opinionMsgs = []; for (const m of messages) { @@ -48,54 +42,26 @@ module.exports = ({ cooler }) => { const c = m.value?.content; if (!c) continue; - if (c.type === 'tombstone' && c.target) { - tombstoned.add(c.target); - itemsById.delete(c.target); - continue; - } - - if (k) authorOf.set(k, m.value?.author); + if (c.type === 'tombstone') continue; if (typeof c.type === 'string' && c.type.endsWith('Opinion') && c.target) { opinionMsgs.push({ target: c.target, author: m.value?.author, category: c.category }); continue; } - if (c.opinions && !tombstoned.has(k) && !['task', 'event', 'report'].includes(c.type)) { - if (c.replaces) replaces.set(c.replaces, k); - itemsById.set(k, m); - } + if (c.opinions && !['task', 'event', 'report'].includes(c.type)) itemsById.set(k, m); - if (c.type === 'feed' && !tombstoned.has(k) && !itemsById.has(k)) { - if (c.replaces) replaces.set(c.replaces, k); - itemsById.set(k, m); - } + if (c.type === 'feed' && !itemsById.has(k)) itemsById.set(k, m); } - for (const [replacedId, newId] of Array.from(replaces.entries())) { - const oldAuthor = authorOf.get(replacedId); - const newMsg = itemsById.get(newId); - if (oldAuthor === undefined) { replaces.delete(replacedId); continue; } - if (!newMsg || String(newMsg.value?.author) !== String(oldAuthor)) { - replaces.delete(replacedId); - itemsById.delete(newId); - continue; - } - itemsById.delete(replacedId); + const vg = versionGroups(Array.from(itemsById.values()).map(versionEntry)); + for (const k of Array.from(itemsById.keys())) { + if (!vg.roots.has(vg.rootOf(k)) || vg.tipOf(k) !== k || tombstoned.has(k)) itemsById.delete(k); } - const tipToChain = new Map(); - for (const [oldId, newId] of replaces.entries()) { - let tip = newId; let g = 0; - while (replaces.has(tip) && g++ < 100000) tip = replaces.get(tip); - if (!tipToChain.has(tip)) tipToChain.set(tip, new Set([tip])); - tipToChain.get(tip).add(oldId); tipToChain.get(tip).add(newId); - } - const idToTip = new Map(); - for (const [tip, chain] of tipToChain.entries()) for (const id of chain) idToTip.set(id, tip); const opinionByTip = new Map(); for (const op of opinionMsgs) { - const tip = idToTip.get(op.target) || op.target; + const tip = vg.tipOf(op.target); if (!opinionByTip.has(tip)) opinionByTip.set(tip, { opinions: {}, voters: new Set() }); const agg = opinionByTip.get(tip); if (agg.voters.has(op.author)) continue; @@ -114,6 +80,12 @@ module.exports = ({ cooler }) => { } let rawItems = Array.from(itemsById.values()).filter(m => types.includes(m.value?.content?.type)); + rawItems = rawItems.filter(m => { + const c = m.value?.content; + if (c?.type !== 'schoolCourse') return true; + if (c.encryptedPayload) return false; + return String(c.visibility || 'PUBLIC').toUpperCase() !== 'INVITE'; + }); const blobTypes = ['document', 'image', 'audio', 'video']; let items = await Promise.all( @@ -176,7 +148,21 @@ module.exports = ({ cooler }) => { bySig.set(sig, m); } } - items = Array.from(bySig.values()); + return Array.from(bySig.values()); + }; + + const baseItems = () => { + const store = requestScope.getStore(); + if (store && store.trendingBase) return store.trendingBase; + const pending = computeBase(); + if (store) store.trendingBase = pending; + return pending; + }; + + const listTrending = async (filter = 'ALL') => { + const ssbClient = await openSsb(); + const userId = ssbClient.id; + let items = (await baseItems()).slice(); if (filter === 'MINE') { items = items.filter(m => m.value.author === userId); @@ -232,31 +218,12 @@ module.exports = ({ cooler }) => { const inhabitants = Array.isArray(msg.content.opinions_inhabitants) ? msg.content.opinions_inhabitants : []; if (inhabitants.includes(userId)) throw new Error('Already voted'); + const otype = categories.messageTypes[type]; + if (!otype) throw new Error('Voting not allowed on this content type'); - const tombstone = { - type: 'tombstone', - target: contentId, - deletedAt: new Date().toISOString(), - author: userId - }; - - const updated = { - ...msg.content, - opinions: { - ...(msg.content.opinions || {}), - [category]: ((msg.content.opinions && msg.content.opinions[category]) || 0) + 1 - }, - opinions_inhabitants: inhabitants.concat(userId), - updatedAt: new Date().toISOString(), - replaces: contentId - }; - - await new Promise((res, rej) => { - ssbClient.publish(tombstone, err => err ? rej(err) : res()); - }); - + const content = { type: otype, target: contentId, category, createdAt: new Date().toISOString() }; return new Promise((res, rej) => { - ssbClient.publish(updated, (err, result) => err ? rej(err) : res(result)); + ssbClient.publish(content, (err, result) => err ? rej(err) : res(result)); }); }; diff --git a/src/models/tribe_crypto.js b/src/models/tribe_crypto.js deleted file mode 100644 index f5576b7..0000000 --- a/src/models/tribe_crypto.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = (configPath) => require('./crypto')(configPath, 'tribes'); diff --git a/src/models/tribes_content_model.js b/src/models/tribes_content_model.js index 83fe943..9245de2 100644 --- a/src/models/tribes_content_model.js +++ b/src/models/tribes_content_model.js @@ -2,11 +2,30 @@ const pull = require('../server/node_modules/pull-stream'); const { getConfig } = require('../configs/config-manager.js'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const tribeLogLimit = Math.max(logLimit, 100000); +const { readTyped } = require('./typed_log'); +const longText = require('../backend/long_text'); +const { versionGroups } = require('../backend/dedupe'); +const TRIBE_LOG_TYPES = ['tribe', 'tribe-msg', 'tribe-keys-distrib', 'tribe-invite-msg', 'tribe-invite-tombstone', 'tribe-open-invite-tombstone', 'tombstone', 'contact']; const VALID_CONTENT_TYPES = ['event', 'task', 'report', 'votation', 'forum', 'forum-reply', 'market', 'job', 'project', 'media', 'feed', 'pixelia']; const categories = require('../backend/opinion_categories'); const VALID_STATUSES = ['OPEN', 'CLOSED', 'IN-PROGRESS']; const VALID_PRIORITIES = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']; +const EXPOSURE_TYPE = 'tribeExposure'; +const REACH_LEVELS = ['tribe', 'oasis', 'clearnet']; +const REACH_RANK = { tribe: 0, oasis: 1, clearnet: 2 }; +const SNAPSHOT_FIELDS = ['contentType', 'title', 'description', 'status', 'date', 'location', 'price', 'salary', 'priority', 'options', 'category', 'tags', 'image', 'mediaType', 'license', 'url', 'deadline', 'goal', 'dates', 'lat', 'lng', 'markers', 'createdAt', 'updatedAt']; +const snapshotOf = (item, replies = []) => { + const out = {}; + for (const f of SNAPSHOT_FIELDS) { + const v = item[f]; + if (v === undefined || v === null || v === '' || (Array.isArray(v) && !v.length)) continue; + out[f] = v; + } + out.author = item.author; + if (item.contentType === 'forum') out.replies = (replies || []).map(r => ({ author: r.author, description: r.description || '', createdAt: r.createdAt || null })); + return out; +}; module.exports = ({ cooler, tribeCrypto, tribesModel }) => { let ssb; @@ -14,12 +33,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const readLog = async () => { const client = await openSsb(); - return new Promise((resolve, reject) => - pull( - client.createLogStream({ limit: tribeLogLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ) - ); + return readTyped(client, TRIBE_LOG_TYPES, { limit: tribeLogLimit }); }; const fingerprintsForRoot = (rootId) => { @@ -38,6 +52,12 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { ); }; + const publishContent = async (rootId, body) => { + const publishChunk = (c) => wrapAndPublishContent(rootId, { k: longText.CHUNK_TYPE, rootId, text: c.text, index: c.index, total: c.total, group: c.group }); + const sealed = await longText.chunkContent(body, 'description', { maxBytes: longText.CONTENT_BYTES.sealed, extra: { rootId }, publish: publishChunk }); + return wrapAndPublishContent(rootId, sealed); + }; + const decodeContentMsgs = async (msgs, opts) => { const fpIdx = tribeCrypto.buildFingerprintIndex(); const targetRootId = opts && opts.rootId ? opts.rootId : null; @@ -45,6 +65,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const allowedFps = targetRootId ? fingerprintsForRoot(targetRootId) : null; const content = new Map(); + const chunks = new Map(); const tombRequests = []; const collabMsgs = []; @@ -62,36 +83,30 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { tombRequests.push({ target: b.target, author: m.value.author }); continue; } + if (b.k === longText.CHUNK_TYPE) { + const ch = longText.chunkOf(m.value.author, b); + if (ch) chunks.set(m.key, ch); + continue; + } if (b.k === 'tc-collab') { - collabMsgs.push({ sub: b.sub, target: b.target, author: m.value.author, ts: m.value.timestamp || 0, option: b.option, on: b.on, category: b.category }); + collabMsgs.push({ sub: b.sub, target: b.target, author: m.value.author, ts: m.value.timestamp || 0, seq: m.value.sequence || 0, option: b.option, on: b.on, category: b.category }); continue; } if (b.k !== 'tribe-content') continue; - content.set(m.key, { author: m.value.author, body: b, ts: m.value.timestamp }); + content.set(m.key, { author: m.value.author, body: b, ts: m.value.timestamp, seq: m.value.sequence || 0 }); } - const naiveNext = new Map(); - const strictNext = new Map(); - for (const [key, node] of content) { - const t = node.body.replaces; - if (!t) continue; - naiveNext.set(t, key); - const orig = content.get(t); - if (orig && orig.author === node.author) strictNext.set(t, key); + if (targetRootId && tribesModel && typeof tribesModel.departures === 'function') { + const gone = await tribesModel.departures(targetRootId).catch(() => new Map()); + const afterLeaving = (author, ts, seq) => { + const d = gone.get(author); + return !!d && ((d.seq !== undefined && seq > d.seq) || (d.ts !== undefined && (Number(ts) || 0) > d.ts)); + }; + for (const [key, node] of [...content]) if (afterLeaving(node.author, node.ts, node.seq)) content.delete(key); + for (let i = collabMsgs.length - 1; i >= 0; i--) if (afterLeaving(collabMsgs[i].author, collabMsgs[i].ts, collabMsgs[i].seq)) collabMsgs.splice(i, 1); } - for (const [t, key] of Array.from(naiveNext.entries())) { - const orig = content.get(t); - if (!orig) { naiveNext.delete(t); continue; } - const node = content.get(key); - if (!node || node.author !== orig.author) { naiveNext.delete(t); content.delete(key); } - } - const naivePrev = new Map(); - for (const [t, key] of naiveNext) naivePrev.set(key, t); - const followTo = (start, nextMap) => { let x = start, g = 0; while (nextMap.has(x) && g++ < 100000) x = nextMap.get(x); return x; }; - const rootOf = (key) => { let x = key, g = 0; while (naivePrev.has(x) && content.has(naivePrev.get(x)) && g++ < 100000) x = naivePrev.get(x); return x; }; - const roots = new Set(); - for (const [key, node] of content) { const t = node.body.replaces; if (!t || !content.has(t)) roots.add(key); } + const vg = versionGroups([...content].map(([key, node]) => ({ key, author: node.author, seq: node.seq, ts: node.ts, replaces: typeof node.body.replaces === 'string' ? node.body.replaces : null, claimedAuthor: typeof node.body.author === 'string' ? node.body.author : null }))); const tombstoned = new Set(); for (const t of tombRequests) { const orig = content.get(t.target); if (orig && t.author === orig.author) tombstoned.add(t.target); } @@ -100,7 +115,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const getCB = (root) => { if (!collabByRoot.has(root)) collabByRoot.set(root, { vote: new Map(), attend: new Map(), assign: new Map(), refeed: new Set(), opinion: new Map() }); return collabByRoot.get(root); }; for (const cm of collabMsgs) { if (!content.has(cm.target)) continue; - const a = getCB(rootOf(cm.target)); + const a = getCB(vg.rootOf(cm.target)); if (cm.sub === 'vote' && Number.isInteger(cm.option)) { if (!a.vote.has(cm.author)) a.vote.set(cm.author, cm.option); } else if (cm.sub === 'attend') { const p = a.attend.get(cm.author); if (!p || cm.ts >= p.ts) a.attend.set(cm.author, { on: cm.on !== false, ts: cm.ts }); } else if (cm.sub === 'assign') { const p = a.assign.get(cm.author); if (!p || cm.ts >= p.ts) a.assign.set(cm.author, { on: cm.on !== false, ts: cm.ts }); } @@ -112,15 +127,15 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const tipOf = new Map(); const EMPTY = { vote: new Map(), attend: new Map(), assign: new Map(), refeed: new Set(), opinion: new Map() }; - for (const root of roots) { - const contentTip = followTo(root, strictNext); + for (const root of vg.roots) { + const contentTip = vg.tipOf(root); const tipNode = content.get(contentTip); if (!tipNode) continue; const body = tipNode.body; const lb = tipNode.body; const a = collabByRoot.get(root) || EMPTY; - const item = { id: contentTip, ...body, author: tipNode.author, _ts: tipNode.ts }; + const item = { id: contentTip, ...body, description: longText.joinText(body.description, body.chunks, longText.lookupIn(chunks), tipNode.author), author: tipNode.author, _ts: tipNode.ts, originId: root }; const voteAuthors = new Set(); const votes = {}; const legacyVotes = lb.votes && typeof lb.votes === 'object' ? lb.votes : {}; @@ -145,8 +160,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { for (const [au, cat] of a.opinion) if (!opInh.has(au)) { opInh.add(au); opinions[cat] = (opinions[cat] || 0) + 1; } item.opinions_inhabitants = [...opInh]; item.opinions = opinions; - let cur = root, g = 0; tipOf.set(root, contentTip); - while (naiveNext.has(cur) && g++ < 100000) { cur = naiveNext.get(cur); tipOf.set(cur, contentTip); } + for (const k of (vg.groups.get(root) || [root])) tipOf.set(k, contentTip); if (tombstoned.has(contentTip)) continue; if (targetRootId && body.rootId !== targetRootId) continue; @@ -157,7 +171,23 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { return { items, tipOf, tombstoned }; }; + const chainIsPublic = async (tribeId) => { + let cur; + try { cur = await tribesModel.getTribeById(tribeId); } catch (_) { return false; } + const seen = new Set(); + while (cur) { + if (cur.isAnonymous) return false; + if (!cur.parentTribeId) return true; + if (seen.has(cur.parentTribeId)) return false; + seen.add(cur.parentTribeId); + try { cur = await tribesModel.getTribeById(cur.parentTribeId); } catch (_) { return false; } + } + return false; + }; + return { + chainIsPublic, + async create(tribeId, contentType, data) { if (!VALID_CONTENT_TYPES.includes(contentType)) throw new Error('Invalid content type'); if (data.status && !VALID_STATUSES.includes(data.status)) throw new Error('Invalid status. Must be OPEN, CLOSED, or IN-PROGRESS'); @@ -187,6 +217,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { tags: data.tags || [], image: data.image || null, mediaType: data.mediaType || null, + ...(data.license ? { license: data.license } : {}), url: data.url || null, attendees: data.attendees || [], deadline: data.deadline || null, @@ -200,7 +231,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { createdAt: now, updatedAt: now }; - return wrapAndPublishContent(rootId, body); + return publishContent(rootId, body); }, async update(contentId, data, existing) { @@ -215,7 +246,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { k: 'tribe-content', rootId, contentType: existing.contentType, - replaces: contentId, + replaces: existing.id || contentId, title: data.title !== undefined ? data.title : existing.title, description: data.description !== undefined ? data.description : existing.description, status: data.status !== undefined ? data.status : existing.status, @@ -232,6 +263,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { tags: data.tags !== undefined ? data.tags : existing.tags, image: data.image !== undefined ? data.image : existing.image, mediaType: data.mediaType !== undefined ? data.mediaType : existing.mediaType, + ...((data.license !== undefined ? data.license : existing.license) ? { license: data.license !== undefined ? data.license : existing.license } : {}), url: data.url !== undefined ? data.url : existing.url, attendees: data.attendees !== undefined ? data.attendees : existing.attendees, deadline: data.deadline !== undefined ? data.deadline : existing.deadline, @@ -245,7 +277,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { createdAt: existing.createdAt, updatedAt: now }; - return wrapAndPublishContent(rootId, body); + return publishContent(rootId, body); }, async deleteById(contentId) { @@ -256,7 +288,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { const body = { k: 'tombstone', rootId, - target: contentId, + target: existing.id || contentId, author: client.id, deletedAt: new Date().toISOString() }; @@ -347,6 +379,103 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => { return wrapAndPublishContent(item.rootId, { k: 'tc-collab', sub: 'opinion', rootId: item.rootId, target: item.id, category }); }, + reachLevels: REACH_LEVELS, + + async publishExposure(item, level, replies = []) { + if (!REACH_LEVELS.includes(level)) throw new Error('Invalid level'); + if (!item || !item.rootId) throw new Error('Content not found'); + const client = await openSsb(); + const publish = (c) => new Promise((resolve, reject) => client.publish(c, (err, r) => err ? reject(err) : resolve(r))); + const content = { + type: EXPOSURE_TYPE, + tribeId: item.rootId, + origin: item.originId || item.id, + contentType: item.contentType, + level, + item: null, + at: new Date().toISOString() + }; + if (level !== 'tribe') { + const { replies: snapReplies, ...snapshot } = snapshotOf(item, replies); + const budget = longText.CONTENT_BYTES.plain - longText.contentBytes(content); + const sealed = await longText.chunkContent(snapshot, 'description', { maxBytes: budget, publish }); + if (Array.isArray(snapReplies)) { + sealed.replies = snapReplies.slice(); + while (sealed.replies.length && longText.contentBytes({ ...content, item: sealed }) > longText.CONTENT_BYTES.plain) sealed.replies.pop(); + } + content.item = sealed; + } + return publish(content); + }, + + async exposureState({ ignorePrivacy = false } = {}) { + const client = await openSsb(); + const msgs = await readTyped(client, [EXPOSURE_TYPE, longText.CHUNK_TYPE], { limit: tribeLogLimit }); + const lookup = longText.lookupIn(longText.indexChunks(msgs)); + const byOrigin = new Map(); + for (const m of msgs) { + const c = m && m.value && m.value.content; + if (!c || c.type !== EXPOSURE_TYPE || typeof c.origin !== 'string' || typeof c.tribeId !== 'string' || !REACH_LEVELS.includes(c.level)) continue; + if (!byOrigin.has(c.origin)) byOrigin.set(c.origin, []); + byOrigin.get(c.origin).push({ by: m.value.author, ts: Number(m.value.timestamp) || 0, c }); + } + const memberCache = new Map(); + const isMember = async (author, tribeId) => { + const k = `${tribeId}|${author}`; + if (!memberCache.has(k)) memberCache.set(k, await tribesModel.isTribeMember(author, tribeId).catch(() => false)); + return memberCache.get(k); + }; + const everCache = new Map(); + const wasMember = async (author, tribeId) => { + const k = `${tribeId}|${author}`; + if (!everCache.has(k)) everCache.set(k, await tribesModel.wasEverMember(author, tribeId).catch(() => false)); + return everCache.get(k); + }; + const privacyCache = new Map(); + const isPrivate = async (tribeId) => { + if (!privacyCache.has(tribeId)) privacyCache.set(tribeId, !(await chainIsPublic(tribeId))); + return privacyCache.get(tribeId); + }; + const state = new Map(); + for (const [origin, list] of byOrigin) { + list.sort((a, b) => (b.ts - a.ts) || (REACH_RANK[a.c.level] - REACH_RANK[b.c.level])); + let chosen = null; + for (const e of list) { + if (await isMember(e.by, e.c.tribeId)) { chosen = e; break; } + if (e.c.level === 'tribe' && await wasMember(e.by, e.c.tribeId)) { chosen = e; break; } + } + if (!chosen) continue; + const priv = await isPrivate(chosen.c.tribeId); + const level = !ignorePrivacy && priv ? 'tribe' : chosen.c.level; + state.set(origin, { origin, tribeId: chosen.c.tribeId, contentType: chosen.c.contentType, level, item: level === 'tribe' || !chosen.c.item ? null : longText.resolveField(chosen.c.item, 'description', chosen.by, lookup), by: chosen.by, ts: chosen.ts }); + } + return state; + }, + + async levelOf(item, state = null) { + const st = state || await this.exposureState(); + const e = st.get(item && (item.originId || item.id)); + return e ? e.level : 'tribe'; + }, + + async listExposed(tribeId, contentType = null, minLevel = 'oasis', state = null) { + const rootId = await tribesModel.getRootId(tribeId).catch(() => tribeId); + const st = state || await this.exposureState(); + const out = []; + for (const e of st.values()) { + if (e.tribeId !== rootId || !e.item || REACH_RANK[e.level] < REACH_RANK[minLevel]) continue; + const base = { ...e.item, id: e.origin, originId: e.origin, rootId, reach: e.level, exposedBy: e.by, _ts: e.ts, attendees: [], assignees: [], votes: {}, opinions: {}, opinions_inhabitants: [], refeeds: 0, refeeds_inhabitants: [] }; + if (contentType === 'forum-reply') { + if (e.item.contentType !== 'forum') continue; + (e.item.replies || []).forEach((r, i) => out.push({ id: `${e.origin}:${i}`, originId: `${e.origin}:${i}`, rootId, contentType: 'forum-reply', parentId: e.origin, description: r.description, author: r.author, createdAt: r.createdAt, reach: e.level, attendees: [], assignees: [], votes: {}, opinions: {}, opinions_inhabitants: [], refeeds_inhabitants: [] })); + continue; + } + if (contentType && e.item.contentType !== contentType) continue; + out.push(base); + } + return out.sort((a, b) => (Date.parse(b.updatedAt || b.createdAt) || b._ts || 0) - (Date.parse(a.updatedAt || a.createdAt) || a._ts || 0)); + }, + async getThread(forumId) { const msgs = await readLog(); const { items } = await decodeContentMsgs(msgs, {}); diff --git a/src/models/tribes_model.js b/src/models/tribes_model.js index f94e6e1..8aa345b 100644 --- a/src/models/tribes_model.js +++ b/src/models/tribes_model.js @@ -4,6 +4,8 @@ const { getConfig } = require('../configs/config-manager.js'); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const tribeLogLimit = Math.max(logLimit, 100000); +const { readTyped } = require('./typed_log'); +const TRIBE_LOG_TYPES = ['tribe', 'tribe-msg', 'tribe-keys-distrib', 'tribe-invite-msg', 'tribe-invite-tombstone', 'tribe-open-invite', 'tribe-open-invite-tombstone', 'tombstone', 'contact']; const INVITE_CODE_BYTES = 16; const VALID_INVITE_MODES = ['strict', 'open']; @@ -25,13 +27,13 @@ module.exports = ({ cooler, tribeCrypto }) => { return true; }; - const validMembershipDelta = (prev, next, author) => { + const validMembershipDelta = (prev, next, author, selfJoinOk = () => true) => { const a = Array.isArray(prev) ? prev : []; const b = Array.isArray(next) ? next : []; const added = b.filter(m => !a.includes(m)); const removed = a.filter(m => !b.includes(m)); if (added.length === 0 && removed.length === 0) return true; - if (added.length === 1 && removed.length === 0 && added[0] === author) return true; + if (added.length === 1 && removed.length === 0 && added[0] === author) return selfJoinOk() === true; if (removed.length === 1 && added.length === 0 && removed[0] === author) return true; return false; }; @@ -49,19 +51,29 @@ module.exports = ({ cooler, tribeCrypto }) => { const x = a[f]; const y = b[f]; if (Array.isArray(x) || Array.isArray(y)) { if (!arraysEqual(x, y)) return false; continue; } - if (x !== y && !(x == null && y == null)) return false; + if (x !== y && !((x == null || x === '') && (y == null || y === ''))) return false; } return true; }; const streamLog = async () => { const client = await openSsb(); - return new Promise((resolve, reject) => { - pull( - client.createLogStream({ limit: tribeLogLimit }), - pull.collect((err, msgs) => err ? reject(err) : resolve(msgs)) - ); - }); + return readTyped(client, TRIBE_LOG_TYPES, { limit: tribeLogLimit }); + }; + const readKeyDistribs = async () => { + const client = await openSsb(); + const ssbKeys = require('../server/node_modules/ssb-keys'); + const config = require('../server/ssb_config'); + const msgs = await readTyped(client, TRIBE_LOG_TYPES, { limit: tribeLogLimit, withPrivate: true }); + const out = []; + for (const m of msgs) { + const v = m && m.value; + if (!v) continue; + const c = v.content; + const dec = (c && typeof c === 'object') ? (v.private === true && tribeCrypto.isKeyDistribContent(c) ? c : null) : tribeCrypto.tryUnboxKeyDistrib(c, config.keys, ssbKeys); + if (dec) out.push({ m, dec }); + } + return { msgs, distribs: out }; }; const buildTribeIndex = async () => { @@ -72,17 +84,31 @@ module.exports = ({ cooler, tribeCrypto }) => { const tribeMsgs = new Map(); const tombstones = new Map(); + const inviteAuthorByCh = new Map(); + const inviteAuthorByKey = new Map(); + const inviteTombstonesByAuthor = new Map(); + const sealedInvites = []; for (const m of msgs) { const c = m.value && m.value.content; if (!c) continue; const author = m.value.author; const ts = m.value.timestamp; + if (c.type === 'tribe-invite-msg' && typeof c.ch === 'string') { inviteAuthorByCh.set(c.ch, author); inviteAuthorByKey.set(m.key, author); continue; } + if (c.type === 'tribe-invite-tombstone' && typeof c.target === 'string') { + if (!inviteTombstonesByAuthor.has(author)) inviteTombstonesByAuthor.set(author, new Set()); + inviteTombstonesByAuthor.get(author).add(c.target); + continue; + } let body = null; if (tribeCrypto.isTribeMsg(c)) { const r = tribeCrypto.unwrapMsg(c, fpIdx); if (!r || !r.body || !r.body.k) continue; + if (r.body.k === 'open-invite' || r.body.k === 'open-invite-tombstone') { + sealedInvites.push({ key: m.key, author, body: r.body, keyRoot: r.rootId, ts }); + continue; + } body = r.body; } else if (c.type === 'tribe' && typeof c === 'object') { body = { @@ -104,7 +130,9 @@ module.exports = ({ cooler, tribeCrypto }) => { mapUrl: c.mapUrl, createdAt: c.createdAt, updatedAt: c.updatedAt, - author: c.author + author: c.author, + joinedWith: typeof c.joinedWith === 'string' ? c.joinedWith : undefined, + removedSeqs: c.removedSeqs && typeof c.removedSeqs === 'object' ? c.removedSeqs : undefined }; } else if (c.type === 'tombstone' && c.target) { tombstones.set(c.target, { author, ts }); @@ -125,6 +153,7 @@ module.exports = ({ cooler, tribeCrypto }) => { op: body.op || (body.rootId ? 'update' : 'create'), content: body, author, + seq: (m.value && m.value.sequence) || 0, _ts: ts }); } @@ -142,49 +171,58 @@ module.exports = ({ cooler, tribeCrypto }) => { } } - let progress = true; - while (progress) { - progress = false; - const candidatesByReplaces = new Map(); - for (const [k, entry] of tribeMsgs.entries()) { + const byReplaces = new Map(); + for (const [k, entry] of tribeMsgs.entries()) { + if (!entry.replaces) continue; + if (!byReplaces.has(entry.replaces)) byReplaces.set(entry.replaces, []); + byReplaces.get(entry.replaces).push([k, entry]); + } + const queue = [...tribes.keys()]; + for (let qi = 0; qi < queue.length; qi++) { + const replaces = queue[qi]; + if (child.has(replaces)) continue; + const parentEntry = tribes.get(replaces); + const root = rootByTip.get(replaces); + const rootEntry = tribes.get(root); + const rootAuthor = rootEntry && rootEntry.author; + const prevMembers = Array.isArray(parentEntry.content.members) ? parentEntry.content.members : []; + const candidates = []; + for (const [k, entry] of (byReplaces.get(replaces) || [])) { if (tribes.has(k)) continue; - const replaces = entry.replaces; - if (!replaces) continue; - const parentEntry = tribes.get(replaces); - if (!parentEntry) continue; - if (child.has(replaces)) continue; - const root = rootByTip.get(replaces); - const rootEntry = tribes.get(root); - const rootAuthor = rootEntry && rootEntry.author; const isRootAuthor = entry.author === rootAuthor; - const prevMembers = Array.isArray(parentEntry.content.members) ? parentEntry.content.members : []; if (!isRootAuthor) { if (!prevMembers.includes(entry.author) && !(entry.content.members || []).includes(entry.author)) continue; - if (!validMembershipDelta(prevMembers, entry.content.members, entry.author)) continue; + const mode = String(parentEntry.content.inviteMode || (rootEntry && rootEntry.content.inviteMode) || 'strict'); + const inviterOk = (inviter) => !!inviter && (inviter === rootAuthor || (mode === 'open' && prevMembers.includes(inviter))); + const selfJoinOk = () => { + const proof = entry.content.joinedWith; + if (proof && inviterOk(inviteAuthorByCh.get(proof))) return true; + const mine = inviteTombstonesByAuthor.get(entry.author); + if (mine) for (const target of mine) if (inviterOk(inviteAuthorByKey.get(target))) return true; + return false; + }; + if (!validMembershipDelta(prevMembers, entry.content.members, entry.author, selfJoinOk)) continue; if (!validInvitesDelta(parentEntry.content.invites, entry.content.invites, entry.author, rootAuthor)) continue; if (!structuralFieldsEqual(parentEntry.content, entry.content)) continue; } - if (!candidatesByReplaces.has(replaces)) candidatesByReplaces.set(replaces, []); - candidatesByReplaces.get(replaces).push({ k, entry, isRootAuthor, root }); + candidates.push({ k, entry, isRootAuthor, root }); } - for (const [replaces, candidates] of candidatesByReplaces.entries()) { - if (child.has(replaces)) continue; - let winner = candidates[0]; - for (let i = 1; i < candidates.length; i++) { - const c = candidates[i]; - if (c.isRootAuthor && !winner.isRootAuthor) { winner = c; continue; } - if (winner.isRootAuthor && !c.isRootAuthor) continue; - const wt = winner.entry._ts || 0; - const ct = c.entry._ts || 0; - if (ct < wt) winner = c; - else if (ct === wt && c.k < winner.k) winner = c; - } - parent.set(winner.k, replaces); - child.set(replaces, winner.k); - tribes.set(winner.k, winner.entry); - rootByTip.set(winner.k, winner.root); - progress = true; + if (!candidates.length) continue; + let winner = candidates[0]; + for (let i = 1; i < candidates.length; i++) { + const c = candidates[i]; + if (c.isRootAuthor && !winner.isRootAuthor) { winner = c; continue; } + if (winner.isRootAuthor && !c.isRootAuthor) continue; + const wt = winner.entry._ts || 0; + const ct = c.entry._ts || 0; + if (ct < wt) winner = c; + else if (ct === wt && c.k < winner.k) winner = c; } + parent.set(winner.k, replaces); + child.set(replaces, winner.k); + tribes.set(winner.k, winner.entry); + rootByTip.set(winner.k, winner.root); + queue.push(winner.k); } const tombstoned = new Set(); @@ -197,37 +235,34 @@ module.exports = ({ cooler, tribeCrypto }) => { } const rootOf = (id) => rootByTip.get(id) || id; - const tipOf = (id) => { let cur = id; while (child.has(cur)) cur = child.get(cur); return cur; }; const tipByRoot = new Map(); for (const k of tribes.keys()) { const root = rootOf(k); - const tip = tipOf(root); + if (tipByRoot.has(root)) continue; + let tip = root; + while (child.has(tip)) tip = child.get(tip); tipByRoot.set(root, tip); } const effectivelyTombstoned = new Set(tombstoned); - let cascade = true; - const seen = new Set(); - while (cascade) { - cascade = false; - for (const k of tribes.keys()) { - if (effectivelyTombstoned.has(k)) continue; - const root = rootOf(k); - if (effectivelyTombstoned.has(root)) { effectivelyTombstoned.add(k); cascade = true; continue; } + const deadMemo = new Map(); + const isDead = (k) => { + if (tombstoned.has(k)) return true; + if (deadMemo.has(k)) return deadMemo.get(k); + deadMemo.set(k, false); + const root = rootOf(k); + let dead = tombstoned.has(tipByRoot.get(root)) || (root !== k && isDead(root)); + if (!dead) { const e = tribes.get(k); const pid = e && e.content && e.content.parentTribeId; - if (!pid) continue; - if (seen.has(`${k}:${pid}`)) continue; - seen.add(`${k}:${pid}`); - const parentRoot = rootOf(pid); - if (effectivelyTombstoned.has(parentRoot) || effectivelyTombstoned.has(pid)) { - effectivelyTombstoned.add(k); - cascade = true; - } + if (pid) dead = isDead(rootOf(pid)) || isDead(pid); } - } + deadMemo.set(k, dead); + return dead; + }; + for (const k of tribes.keys()) if (isDead(k)) effectivelyTombstoned.add(k); - tribeIndex = { tribes, tombstoned, effectivelyTombstoned, parent, child, tipByRoot, rootByTip }; + tribeIndex = { tribes, tombstoned, effectivelyTombstoned, parent, child, tipByRoot, rootByTip, sealedInvites }; tribeIndexTs = Date.now(); return tribeIndex; }; @@ -253,8 +288,9 @@ module.exports = ({ cooler, tribeCrypto }) => { }; })(); - const normalizeTribe = (entry) => { + const normalizeTribe = (entry, rootEntry) => { const c = entry.content; + const root = rootEntry || entry; return { id: entry.id, title: c.title || '', @@ -269,9 +305,9 @@ module.exports = ({ cooler, tribeCrypto }) => { status: c.status || 'OPEN', parentTribeId: c.parentTribeId || null, mapUrl: c.mapUrl || '', - createdAt: c.createdAt, + createdAt: root.content.createdAt, updatedAt: c.updatedAt, - author: c.author || entry.author, + author: root.author, _ts: entry._ts }; }; @@ -290,28 +326,95 @@ module.exports = ({ cooler, tribeCrypto }) => { const scanOpenInviteMarkers = async () => { const msgs = await streamLog(); - const tombstoned = new Set(); - for (const m of msgs) { - const c = m.value && m.value.content; - if (!c) continue; - if (c.type === 'tribe-open-invite-tombstone' && typeof c.target === 'string') tombstoned.add(c.target); - if (c.type === 'tribe-invite-tombstone' && typeof c.target === 'string') tombstoned.add('inv:' + c.target); - } - const byRoot = new Map(); + const idx = await buildTribeIndex(); + const tombBy = new Map(); + const markTomb = (target, author) => { + if (!tombBy.has(target)) tombBy.set(target, new Set()); + tombBy.get(target).add(author); + }; + const markers = []; for (const m of msgs) { const v = m.value; const c = v && v.content; - if (!c || c.type !== 'tribe-open-invite' || c.v !== 1) continue; + if (!c) continue; + if (c.type === 'tribe-open-invite-tombstone' && typeof c.target === 'string') markTomb(c.target, v.author); + else if (c.type === 'tribe-invite-tombstone' && typeof c.target === 'string') markTomb('inv:' + c.target, v.author); + else if (c.type === 'tribe-open-invite' && c.v === 1) markers.push({ key: m.key, author: v.author, body: c, sealed: false, ts: v.timestamp || 0 }); + } + for (const e of idx.sealedInvites || []) { + if (e.body.k === 'open-invite-tombstone' && typeof e.body.target === 'string') markTomb(e.body.target, e.author); + else if (e.body.k === 'open-invite' && e.body.rootId === e.keyRoot) markers.push({ key: e.key, author: e.author, body: e.body, sealed: true, ts: e.ts || 0 }); + } + const killed = (target, allowed) => { + const by = tombBy.get(target); + return !!by && allowed.some((a) => a && by.has(a)); + }; + const byRoot = new Map(); + for (const mk of markers) { + const c = mk.body; if (typeof c.rootId !== 'string' || typeof c.code !== 'string') continue; - if (tombstoned.has(m.key)) continue; - if (c.inviteKey && tombstoned.has('inv:' + c.inviteKey)) continue; - const ts = (v && v.timestamp) || 0; + const rootEntry = idx.tribes.get(c.rootId); + if (!rootEntry || !mk.author) continue; + const tip = idx.tribes.get(idx.tipByRoot.get(c.rootId) || c.rootId) || rootEntry; + if (tip.content.isAnonymous !== false && !mk.sealed) continue; + const members = Array.isArray(tip.content.members) ? tip.content.members : []; + if (mk.author !== rootEntry.author && !members.includes(mk.author)) continue; + const allowed = [mk.author, rootEntry.author]; + if (killed(mk.key, allowed)) continue; + if (c.inviteKey && killed('inv:' + c.inviteKey, allowed)) continue; const prev = byRoot.get(c.rootId); - if (!prev || ts > prev.ts) byRoot.set(c.rootId, { code: c.code, by: c.by || v.author, markerKey: m.key, inviteKey: c.inviteKey || null, ts }); + if (!prev || mk.ts > prev.ts) byRoot.set(c.rootId, { code: c.code, by: c.by || mk.author, markerKey: mk.key, inviteKey: c.inviteKey || null, sealed: mk.sealed, ts: mk.ts }); } return byRoot; }; + const findInvite = async (code) => { + const msgs = await streamLog(); + const inviteTombstonedBy = new Map(); + const clearOpenInvites = new Set(); + for (const m of msgs) { + const v = m.value; + const c = v && v.content; + if (!c) continue; + if (c.type === 'tribe-open-invite' && typeof c.inviteKey === 'string') { clearOpenInvites.add(c.inviteKey); continue; } + if (c.type !== 'tribe-invite-tombstone' || typeof c.target !== 'string') continue; + if (!inviteTombstonedBy.has(c.target)) inviteTombstonedBy.set(c.target, new Set()); + inviteTombstonedBy.get(c.target).add(v.author); + } + let matched = null; + for (const m of msgs) { + const v = m.value; + const c = v && v.content; + if (!c || c.type !== 'tribe-invite-msg' || c.v !== 1) continue; + if (typeof c.ch !== 'string' || typeof c.s !== 'string' || typeof c.ek !== 'string') continue; + const multi = c.multi === 1 || c.multi === true; + if (!multi && inviteTombstonedBy.has(m.key)) continue; + if (tribeCrypto.hashInviteCode(code, c.s) !== c.ch) continue; + const chain = tribeCrypto.decryptChainFromInvite(c.ek, code, c.s, 3); + if (Array.isArray(chain) && chain.length) { + matched = { msgKey: m.key, codeHash: c.ch, chain, multi, inviteAuthor: v.author }; + break; + } + } + if (!matched) return null; + const fresh = matched.chain.filter(entry => entry && Array.isArray(entry.keys) && entry.keys.length && !tribeCrypto.getKeys(entry.rootId).length); + for (const entry of fresh) tribeCrypto.setKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length); + tribeIndex = null; + const undo = () => { for (const entry of fresh) tribeCrypto.dropKey(entry.rootId); tribeIndex = null; }; + const rootId = matched.chain[0].rootId; + const idx = await buildTribeIndex().catch(() => null); + const entry = idx && idx.tribes.get(idx.tipByRoot.get(rootId) || rootId); + const tribe = entry && !idx.effectivelyTombstoned.has(rootId) ? normalizeTribe(entry, idx.tribes.get(rootId)) : null; + if (!tribe) { undo(); return null; } + if (matched.multi) { + const by = inviteTombstonedBy.get(matched.msgKey); + const revoked = !!by && (by.has(matched.inviteAuthor) || by.has(tribe.author)); + const exposed = tribe.isAnonymous && clearOpenInvites.has(matched.msgKey); + if (revoked || exposed) { undo(); return null; } + } + return { matched, rootId, tribe, fresh, undo }; + }; + return { type: 'tribe', @@ -359,6 +462,27 @@ module.exports = ({ cooler, tribeCrypto }) => { return result; }, + async departures(tribeId) { + const idx = await buildTribeIndex(); + const root = idx.rootByTip.get(tribeId) || tribeId; + const out = new Map(); + const rootAuthor = idx.tribes.get(root) && idx.tribes.get(root).author; + let cur = root; + let prev = null; + let g = 0; + while (cur && g++ < 100000) { + const e = idx.tribes.get(cur); + if (!e) break; + const members = Array.isArray(e.content.members) ? e.content.members : []; + const noted = rootAuthor && e.author === rootAuthor && e.content.removedSeqs ? e.content.removedSeqs : {}; + if (prev) for (const m of prev) if (!members.includes(m)) out.set(m, e.author === m ? { seq: e.seq || 0 } : (Number.isFinite(Number(noted[m])) && noted[m] !== null ? { seq: Number(noted[m]) } : { ts: e._ts || 0 })); + for (const m of members) out.delete(m); + prev = members; + cur = idx.child.get(cur); + } + return out; + }, + async getRootId(tribeId) { const idx = await buildTribeIndex(); let cur = tribeId; @@ -400,7 +524,7 @@ module.exports = ({ cooler, tribeCrypto }) => { if (idx.tombstoned.has(latestId) || idx.effectivelyTombstoned.has(latestId)) throw new Error('Tribe not found'); const entry = idx.tribes.get(latestId); if (!entry) throw new Error('Tribe not found'); - return normalizeTribe(entry); + return normalizeTribe(entry, idx.tribes.get(idx.rootByTip.get(latestId) || latestId)); }, async listAll() { @@ -411,7 +535,7 @@ module.exports = ({ cooler, tribeCrypto }) => { if (idx.effectivelyTombstoned.has(root) || idx.effectivelyTombstoned.has(tip)) continue; const entry = idx.tribes.get(tip); if (!entry) continue; - items.push(normalizeTribe(entry)); + items.push(normalizeTribe(entry, idx.tribes.get(root))); } return items; }, @@ -452,6 +576,22 @@ module.exports = ({ cooler, tribeCrypto }) => { return out; }, + async wasEverMember(userId, tribeId) { + if (!userId || !tribeId) return false; + const idx = await buildTribeIndex(); + let root = tribeId; + while (idx.parent.has(root)) root = idx.parent.get(root); + for (const entry of idx.tribes.values()) { + let r = entry.id; + while (idx.parent.has(r)) r = idx.parent.get(r); + if (r !== root) continue; + if (entry.author === userId) return true; + const members = entry.content && Array.isArray(entry.content.members) ? entry.content.members : []; + if (members.includes(userId)) return true; + } + return false; + }, + async isTribeMember(userId, tribeId) { if (!userId || !tribeId) return false; try { @@ -512,7 +652,9 @@ module.exports = ({ cooler, tribeCrypto }) => { mapUrl: updatedContent.mapUrl !== undefined ? updatedContent.mapUrl : tribe.mapUrl, createdAt: tribe.createdAt, updatedAt: now, - author: tribe.author + author: tribe.author, + ...(typeof updatedContent.joinedWith === 'string' && updatedContent.joinedWith ? { joinedWith: updatedContent.joinedWith } : {}), + ...(updatedContent.removedSeqs && typeof updatedContent.removedSeqs === 'object' ? { removedSeqs: updatedContent.removedSeqs } : {}) }; if (fields.isAnonymous) { return wrapAndPublish(rootId, { k: 'tribe', op: 'update', rootId, replaces: tipId, ...fields }); @@ -533,7 +675,15 @@ module.exports = ({ cooler, tribeCrypto }) => { async updateTribeMembers(tribeId, members) { const tribe = await this.getTribeById(tribeId); const old = tribe.members || []; - await this.updateTribeById(tribeId, { members }); + const leaving = old.filter(m => !members.includes(m)); + const removedSeqs = {}; + if (leaving.length) { + const client = await openSsb(); + for (const m of leaving) { + removedSeqs[m] = await new Promise((resolve) => pull(client.createUserStream({ id: m, reverse: true, limit: 1 }), pull.collect((err, list) => resolve(!err && list && list[0] && list[0].value ? Number(list[0].value.sequence) || 0 : 0)))); + } + } + await this.updateTribeById(tribeId, { members, ...(leaving.length ? { removedSeqs } : {}) }); const removed = old.filter(m => !members.includes(m)); const added = members.filter(m => !old.includes(m)); if (removed.length > 0) { @@ -589,7 +739,7 @@ module.exports = ({ cooler, tribeCrypto }) => { const root = await this.getRootId(tribeId).catch(() => tribeId); const map = await scanOpenInviteMarkers(); const rec = map.get(root); - return rec ? { code: rec.code, by: rec.by, markerKey: rec.markerKey, inviteKey: rec.inviteKey } : null; + return rec ? { code: rec.code, by: rec.by, markerKey: rec.markerKey, inviteKey: rec.inviteKey, sealed: rec.sealed } : null; }, async enrichOpenInvites(tribes) { @@ -627,9 +777,14 @@ module.exports = ({ cooler, tribeCrypto }) => { const invitePub = await new Promise((resolve, reject) => client.publish({ type: 'tribe-invite-msg', v: 1, multi: 1, ch: codeHash, s: salt, ek: ekChain }, (err, r) => err ? reject(err) : resolve(r)) ); - await new Promise((resolve, reject) => - client.publish({ type: 'tribe-open-invite', v: 1, rootId: targetRoot, code, inviteKey: invitePub.key, by: userId, createdAt: new Date().toISOString() }, (err, r) => err ? reject(err) : resolve(r)) - ); + const marker = { v: 1, rootId: targetRoot, code, inviteKey: invitePub.key, by: userId, createdAt: new Date().toISOString() }; + if (tribe.isAnonymous) { + await wrapAndPublish(targetRoot, { k: 'open-invite', ...marker }); + } else { + await new Promise((resolve, reject) => + client.publish({ type: 'tribe-open-invite', ...marker }, (err, r) => err ? reject(err) : resolve(r)) + ); + } tribeIndex = null; return code; }, @@ -643,56 +798,61 @@ module.exports = ({ cooler, tribeCrypto }) => { if (rec.by !== userId && !(tribe && tribe.author === userId)) { throw new Error('Not allowed to remove this invitation'); } - await new Promise((resolve, reject) => - client.publish({ type: 'tribe-open-invite-tombstone', v: 1, target: rec.markerKey, ts: new Date().toISOString() }, (err, r) => err ? reject(err) : resolve(r)) - ); + const ts = new Date().toISOString(); + if (rec.sealed) { + await wrapAndPublish(await this.getRootId(tribeId), { k: 'open-invite-tombstone', target: rec.markerKey, ts }); + } else { + await new Promise((resolve, reject) => + client.publish({ type: 'tribe-open-invite-tombstone', v: 1, target: rec.markerKey, ts }, (err, r) => err ? reject(err) : resolve(r)) + ); + } if (rec.inviteKey) await this.publishInviteTombstone(rec.inviteKey).catch(() => {}); tribeIndex = null; }, + async previewInvite(rawCode) { + const found = await findInvite(String(rawCode || '').trim()); + if (!found) return null; + found.undo(); + return { rootId: found.rootId, title: found.tribe.title, tags: found.tribe.tags }; + }, + async joinByInvite(rawCode) { const code = String(rawCode || '').trim(); if (!code) throw new Error('Invalid or expired invite code'); const client = await openSsb(); const userId = client.id; - const msgs = await streamLog(); - const inviteTombstoned = new Set(); - for (const m of msgs) { - const v = m.value; - const c = v && v.content; - if (!c || c.type !== 'tribe-invite-tombstone' || typeof c.target !== 'string') continue; - inviteTombstoned.add(c.target); - } - let matched = null; - for (const m of msgs) { - const v = m.value; - const c = v && v.content; - if (!c || c.type !== 'tribe-invite-msg' || c.v !== 1) continue; - if (typeof c.ch !== 'string' || typeof c.s !== 'string' || typeof c.ek !== 'string') continue; - if (inviteTombstoned.has(m.key)) continue; - if (tribeCrypto.hashInviteCode(code, c.s) !== c.ch) continue; - const chain = tribeCrypto.decryptChainFromInvite(c.ek, code, c.s, 3); - if (Array.isArray(chain) && chain.length) { - matched = { msgKey: m.key, codeHash: c.ch, chain, multi: c.multi === 1 || c.multi === true }; - break; - } - } - if (!matched) throw new Error('Invalid or expired invite code'); - for (const entry of matched.chain) { - tribeCrypto.setKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length); - } - tribeIndex = null; - const rootId = matched.chain[0].rootId; + const found = await findInvite(code); + if (!found) throw new Error('Invalid or expired invite code'); + const { matched, rootId, fresh, undo } = found; let tribe; try { tribe = await this.getTribeById(rootId); } catch (_) { tribe = null; } - if (!tribe) throw new Error('Tribe not found after key import'); - if (tribe.members.includes(userId)) throw new Error('Already a member of this tribe'); + if (!tribe) { undo(); throw new Error('Tribe not found'); } + if (tribe.members.includes(userId)) { undo(); throw new Error('Already a member of this tribe'); } + const lineage = new Set([rootId]); + let cursor = tribe; + for (let hops = 0; cursor && cursor.parentTribeId && hops < 8; hops++) { + let parentTribe = null; + try { parentTribe = await this.getTribeById(cursor.parentTribeId); } catch (_) { parentTribe = null; } + if (!parentTribe) break; + const parentCircle = [parentTribe.author, ...(Array.isArray(parentTribe.members) ? parentTribe.members : [])]; + if (!parentCircle.includes(cursor.author)) break; + lineage.add(parentTribe.id); + lineage.add(cursor.parentTribeId); + cursor = parentTribe; + } + for (const entry of matched.chain) { + if (!entry || !Array.isArray(entry.keys) || !entry.keys.length) continue; + if (fresh.includes(entry)) { if (!lineage.has(entry.rootId)) tribeCrypto.dropKey(entry.rootId); continue; } + if (lineage.has(entry.rootId)) tribeCrypto.mergeKeys(entry.rootId, entry.keys, entry.gen || entry.keys.length); + } + tribeIndex = null; const members = [...tribe.members, userId]; if (matched.multi) { - await this.updateTribeById(tribe.id, { members }); + await this.updateTribeById(tribe.id, { members, joinedWith: matched.codeHash }); } else { const invites = (tribe.invites || []).filter(inv => inv.codeHash !== matched.codeHash); - await this.updateTribeById(tribe.id, { members, invites }); + await this.updateTribeById(tribe.id, { members, invites, joinedWith: matched.codeHash }); await this.publishInviteTombstone(matched.msgKey).catch(() => {}); } await this.ensureFollowTribeMembers(tribe.id).catch(() => {}); @@ -728,9 +888,6 @@ module.exports = ({ cooler, tribeCrypto }) => { return; } await this.updateTribeById(tribeId, { members }); - if (members.length > 0) { - await this.rotateTribeKey(tribeId, members).catch(() => {}); - } }, async distributeTribeKey(tribeId, toMembers) { @@ -741,13 +898,16 @@ module.exports = ({ cooler, tribeCrypto }) => { const gen = tribeCrypto.getGen(rootId); if (!keys.length) return; const payload = tribeCrypto.buildKeyDistribPayload(rootId, keys, gen); - const batch = tribeCrypto.KEY_DISTRIB_BATCH; - for (let i = 0; i < toMembers.length; i += batch) { - const recps = toMembers.slice(i, i + batch); + const others = [...new Set(toMembers)].filter(m => m && m !== client.id); + const batch = tribeCrypto.KEY_DISTRIB_BATCH - 1; + let i = 0; + do { + const recps = [client.id, ...others.slice(i, i + batch)]; await new Promise((resolve, reject) => client.publish({ ...payload, recps }, (err) => err ? reject(err) : resolve()) ); - } + i += batch; + } while (i < others.length); }, async rotateTribeKey(tribeId, remainingMembers) { @@ -770,38 +930,41 @@ module.exports = ({ cooler, tribeCrypto }) => { const rootId = await this.getRootId(tribeId); if (!tribeCrypto.getKey(rootId)) return; const others = (tribe.members || []).filter(m => m !== userId); - if (!others.length) return; - const ssbKeys = require('../server/node_modules/ssb-keys'); - const config = require('../server/ssb_config'); - const msgs = await streamLog(); + const { distribs } = await readKeyDistribs(); const distributed = new Set(); - for (const m of msgs) { - if (m.value && m.value.author !== userId) continue; - const c = m.value && m.value.content; - const dec = tribeCrypto.tryUnboxKeyDistrib(c, config.keys, ssbKeys); - if (!dec || dec.rootId !== rootId) continue; + const currentGen = tribeCrypto.getGen(rootId); + const holdersOfCurrent = new Set(); + for (const { m, dec } of distribs) { + if (m.value.author !== userId) continue; + if (dec.rootId !== rootId) continue; const recps = Array.isArray(dec.recps) ? dec.recps : []; for (const r of recps) distributed.add(r); + if (Number(dec.gen) === Number(currentGen)) for (const r of recps) holdersOfCurrent.add(r); } + const departed = [...holdersOfCurrent].filter(r => r !== userId && !(tribe.members || []).includes(r)); + if (departed.length > 0) { await this.rotateTribeKey(tribeId, others).catch(() => {}); return; } + if (!others.length) return; const missing = others.filter(m => !distributed.has(m)); if (missing.length > 0) await this.distributeTribeKey(tribeId, missing).catch(() => {}); }, async processIncomingKeys() { - const client = await openSsb(); - const ssbKeys = require('../server/node_modules/ssb-keys'); - const config = require('../server/ssb_config'); - const msgs = await streamLog(); + const { msgs, distribs } = await readKeyDistribs(); const byTribe = new Map(); - for (const m of msgs) { - const c = m.value && m.value.content; - const dec = tribeCrypto.tryUnboxKeyDistrib(c, config.keys, ssbKeys); - if (!dec) continue; + const authorOf = new Map(); + for (const m of msgs) if (m && m.key && m.value) authorOf.set(m.key, m.value.author); + for (const { m, dec } of distribs) { const list = byTribe.get(dec.rootId) || []; - list.push({ generation: dec.gen || dec.keys.length, keys: dec.keys, ts: m.value.timestamp }); + list.push({ generation: dec.gen || dec.keys.length, keys: dec.keys, ts: m.value.timestamp, author: m.value.author }); byTribe.set(dec.rootId, list); } - for (const [rootId, entries] of byTribe.entries()) { + for (const [rootId, all] of byTribe.entries()) { + const owner = authorOf.get(rootId); + if (!owner) continue; + let members = []; + try { const t = await this.getTribeById(rootId); members = Array.isArray(t && t.members) ? t.members : []; } catch (_) {} + const allowed = new Set([owner, ...members]); + const entries = all.filter(e => allowed.has(e.author)); entries.sort((a, b) => b.generation - a.generation); const top = entries[0]; if (top && Array.isArray(top.keys) && top.keys.length) { diff --git a/src/models/typed_log.js b/src/models/typed_log.js new file mode 100644 index 0000000..e7d4d60 --- /dev/null +++ b/src/models/typed_log.js @@ -0,0 +1,285 @@ +const pull = require('../server/node_modules/pull-stream'); +const { AsyncLocalStorage } = require('async_hooks'); + +const requestScope = new AsyncLocalStorage(); +const noteCapped = (limit, size) => { + if (!limit || !(size >= limit)) return; + const store = requestScope.getStore(); + if (store) { store.capped = true; store.limit = limit; } +}; + +const TAIL_PROBE = 50; +const LOG_TAIL_PROBE = 20; + +const collectStream = (stream) => + new Promise((resolve, reject) => { + pull(stream, pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))); + }); + +const isDeletion = (m) => { + const c = m && m.value && m.value.content; + return !!c && typeof c === 'object' && c.type === 'tombstone'; +}; + +const readContentWindow = (ssb, limit) => { + if (!limit) return collectStream(ssb.createLogStream({ reverse: true })); + let kept = 0; + return collectStream(pull( + ssb.createLogStream({ reverse: true }), + pull.take((m) => { if (!isDeletion(m)) kept++; return kept <= limit; }) + )); +}; + +const caches = new WeakMap(); + +const cacheFor = (ssb) => { + let c = caches.get(ssb); + if (!c) { + c = { types: new Map(), window: null, results: new Map() }; + caches.set(ssb, c); + } + return c; +}; + +const insert = (entry, msgs) => { + for (let i = msgs.length - 1; i >= 0; i--) { + const m = msgs[i]; + if (m && m.key && !entry.byKey.has(m.key)) entry.byKey.set(m.key, m); + } +}; + +const UNCAPPED_TYPES = new Set(['tombstone']); +const syncType = async (ssb, cache, type, requestedLimit) => { + const limit = UNCAPPED_TYPES.has(type) ? 0 : requestedLimit; + let entry = cache.types.get(type); + if (!entry) { + entry = { byKey: new Map(), warming: null, warm: false }; + cache.types.set(type, entry); + entry.warming = collectStream(ssb.messagesByType(limit ? { type, reverse: true, limit } : { type, reverse: true })) + .then((msgs) => { insert(entry, msgs); entry.warm = true; entry.capped = !!(limit && msgs.length >= limit); }) + .catch(() => { cache.types.delete(type); }); + await entry.warming; + return entry; + } + await entry.warming; + if (!entry.warm) return entry; + const tail = await collectStream(ssb.messagesByType({ type, reverse: true, limit: TAIL_PROBE })); + const fresh = tail.filter((m) => m && m.key && !entry.byKey.has(m.key)); + if (fresh.length === tail.length && tail.length === TAIL_PROBE) { + const all = await collectStream(ssb.messagesByType(limit ? { type, reverse: true, limit } : { type, reverse: true })); + insert(entry, all); + entry.capped = !!(limit && all.length >= limit); + } else { + insert(entry, fresh); + } + return entry; +}; + +const syncWindow = async (ssb, cache, limit) => { + let entry = cache.window; + if (!entry) { + entry = { byKey: new Map(), warming: null, warm: false }; + cache.window = entry; + entry.warming = readContentWindow(ssb, limit) + .then((msgs) => { insert(entry, msgs); entry.warm = true; }) + .catch(() => { cache.window = null; }); + await entry.warming; + return entry; + } + await entry.warming; + if (!entry.warm) return entry; + const tail = await collectStream(ssb.createLogStream({ reverse: true, limit: TAIL_PROBE })); + const fresh = tail.filter((m) => m && m.key && !entry.byKey.has(m.key)); + if (fresh.length === tail.length && tail.length === TAIL_PROBE) { + const all = await readContentWindow(ssb, limit); + insert(entry, all); + } else { + insert(entry, fresh); + } + return entry; +}; + +const syncPrivate = async (ssb, cache) => { + if (!ssb.private || typeof ssb.private.read !== 'function') return null; + let entry = cache.private; + if (!entry) { + entry = { byKey: new Map(), warming: null, warm: false }; + cache.private = entry; + entry.warming = collectStream(ssb.private.read({ reverse: true })) + .then((msgs) => { insert(entry, msgs); entry.warm = true; }) + .catch(() => { cache.private = null; }); + await entry.warming; + return entry; + } + await entry.warming; + if (!entry.warm) return entry; + const tail = await collectStream(ssb.private.read({ reverse: true, limit: TAIL_PROBE })); + const fresh = tail.filter((m) => m && m.key && !entry.byKey.has(m.key)); + if (fresh.length === tail.length && tail.length === TAIL_PROBE) { + insert(entry, await collectStream(ssb.private.read({ reverse: true }))); + } else { + insert(entry, fresh); + } + return entry; +}; + +const logTailOf = async (ssb, n) => { + if (ssb.activity && typeof ssb.activity.tail === 'function') { + const store = requestScope.getStore(); + const fresh = !(store && store.method === 'GET'); + const fast = await new Promise((resolve) => { + try { ssb.activity.tail(n, { fresh }, (err, arr) => resolve(err ? null : arr)); } catch (_) { resolve(null); } + }); + if (Array.isArray(fast)) return fast; + } + return collectStream(ssb.createLogStream({ reverse: true, limit: n })); +}; + +const readTyped = async (ssbClient, types, opts = {}) => { + const limit = opts.limit; + if (typeof ssbClient.messagesByType !== 'function') { + const all = await collectStream(ssbClient.createLogStream(limit ? { limit } : {})); + noteCapped(limit, all.length); + return all; + } + const cache = cacheFor(ssbClient); + const wanted = new Set(types); + + const logTail = (await logTailOf(ssbClient, LOG_TAIL_PROBE)).reverse(); + const tipKey = logTail.length ? logTail[logTail.length - 1].key : null; + const inTail = new Set(logTail.map((m) => m && m.key)); + const upToDate = (e) => !!e && e.warm && e.tip === tipKey; + const caughtUp = (e) => !!e && e.warm && !!e.tip && (e.tip === tipKey || inTail.has(e.tip)); + const allWarm = types.every((type) => upToDate(cache.types.get(type))) + && (!opts.withWindow || upToDate(cache.window)); + + const entries = await Promise.all(types.map((type) => { + const e = cache.types.get(type); + return caughtUp(e) ? e : syncType(ssbClient, cache, type, limit); + })); + const windowEntry = opts.withWindow + ? (caughtUp(cache.window) ? cache.window : await syncWindow(ssbClient, cache, limit)) + : null; + const withPrivate = opts.withPrivate === true; + const resultKey = `${types.join(',')}|${limit || 0}|${opts.withWindow ? 1 : 0}|${withPrivate ? 1 : 0}`; + if (allWarm && (!withPrivate || !wanted.size || upToDate(cache.private))) { + const hit = cache.results.get(resultKey); + if (hit && hit.tip === tipKey) { + if (hit.capped) noteCapped(limit, limit); + return hit.list.slice(); + } + } + const privateEntry = withPrivate && wanted.size + ? (!upToDate(cache.private) ? await syncPrivate(ssbClient, cache) : cache.private) + : null; + for (const e of [...entries, windowEntry, privateEntry]) if (e && e.warm) e.tip = tipKey; + + for (const m of logTail) { + if (!m || !m.key || !m.value) continue; + const t = m.value.content && m.value.content.type; + if (typeof t === 'string' && wanted.has(t)) { + const entry = cache.types.get(t); + if (entry && entry.warm && !entry.byKey.has(m.key)) entry.byKey.set(m.key, m); + } + if (windowEntry && windowEntry.warm && !windowEntry.byKey.has(m.key)) windowEntry.byKey.set(m.key, m); + } + + const union = new Map(); + let capped = false; + for (const entry of entries) { + if (!entry) continue; + if (entry.capped) { capped = true; noteCapped(limit, limit); } + for (const [k, m] of entry.byKey) if (!union.has(k)) union.set(k, m); + } + if (windowEntry) { + for (const [k, m] of windowEntry.byKey) if (!union.has(k)) union.set(k, m); + } + if (privateEntry && privateEntry.warm) { + for (const [k, m] of privateEntry.byKey) { + const t = m && m.value && m.value.content && m.value.content.type; + if (typeof t === 'string' && wanted.has(t)) union.set(k, m); + } + } + const sorted = Array.from(union.values()).sort((a, b) => { + const at = (a.value && a.value.timestamp) || 0; + const bt = (b.value && b.value.timestamp) || 0; + if (at !== bt) return at - bt; + const ar = a.timestamp || 0; + const br = b.timestamp || 0; + if (ar !== br) return ar - br; + if (a.value && b.value && a.value.author === b.value.author) return (a.value.sequence || 0) - (b.value.sequence || 0); + return 0; + }); + const complete = entries.every((e) => e && e.warm) + && (!windowEntry === !opts.withWindow) && (!windowEntry || windowEntry.warm) + && (!(withPrivate && wanted.size) || (privateEntry && privateEntry.warm)); + if (tipKey && complete) cache.results.set(resultKey, { tip: tipKey, capped, list: sorted }); + return sorted.slice(); +}; + +const NON_MESSAGE_LITERALS = new Set([ + 'hidden', 'submit', 'text', 'file', 'number', 'checkbox', 'radio', 'password', 'date', 'datetime-local', 'time', 'url', 'email', 'search', 'button', 'reset', + 'png', 'jpeg', 'jpg', 'gif', 'svg', 'webp', 'pdf', 'zip', 'json', 'html', 'error', 'meta', 'msg', 'blob', 'peer', 'inhabitant', 'chatThread', 'taskAssignment' +]); +const CORE_TYPES = ['post', 'about', 'contact', 'vote', 'pub', 'tombstone', 'file', 'textChunk']; + +const discoverContentTypes = () => { + const fs = require('fs'); + const path = require('path'); + const found = new Set(CORE_TYPES); + const sources = []; + try { + for (const f of fs.readdirSync(__dirname)) if (f.endsWith('.js') && f !== 'typed_log.js') sources.push(path.join(__dirname, f)); + sources.push(path.join(__dirname, '..', 'backend', 'backend.js')); + } catch (_) {} + for (const file of sources) { + let src = ''; + try { src = fs.readFileSync(file, 'utf8'); } catch (_) { continue; } + for (const m of src.matchAll(/\btype:\s*['"]([A-Za-z][\w-]*)['"]/g)) { + if (!NON_MESSAGE_LITERALS.has(m[1])) found.add(m[1]); + } + for (const m of src.matchAll(/\b(?:const|let)\s+(?:[A-Z][A-Z0-9_]*_)?TYPE\s*=\s*['"]([A-Za-z][\w-]*)['"]/g)) { + if (!NON_MESSAGE_LITERALS.has(m[1])) found.add(m[1]); + } + } + return Array.from(found).sort(); +}; + +const CONTENT_TYPES = discoverContentTypes(); + +const logTip = async (ssb) => { + const tail = await logTailOf(ssb, 1); + return tail[0] && tail[0].key ? tail[0].key : null; +}; + +const activityCache = new WeakMap(); +const ACTIVITY_TTL_MS = 5000; +const authorActivity = (ssb) => { + if (!ssb || !ssb.activity || typeof ssb.activity.latest !== 'function') return Promise.resolve(null); + const hit = activityCache.get(ssb); + if (hit && Date.now() - hit.at < ACTIVITY_TTL_MS) return hit.pending; + const store = requestScope.getStore(); + const fresh = !(store && store.method === 'GET'); + const pending = new Promise((resolve) => { + try { ssb.activity.latest(null, { fresh }, (err, all) => resolve(err || !all ? null : new Map(Object.entries(all)))); } catch (_) { resolve(null); } + }); + activityCache.set(ssb, { at: Date.now(), pending }); + return pending; +}; + +const memoIndex = (name, messages, build) => { + const store = requestScope.getStore(); + if (!store) return build(messages); + const list = Array.isArray(messages) ? messages : []; + const first = list[0]; + const last = list[list.length - 1]; + const sig = `${list.length}|${first && first.key}|${last && last.key}`; + if (!store.indexMemo) store.indexMemo = new Map(); + const hit = store.indexMemo.get(name); + if (hit && hit.sig === sig) return hit.idx; + const idx = build(messages); + store.indexMemo.set(name, { sig, idx }); + return idx; +}; + +module.exports = { readTyped, collectStream, readContentWindow, CONTENT_TYPES, discoverContentTypes, requestScope, memoIndex, logTip, authorActivity }; diff --git a/src/models/videos_model.js b/src/models/videos_model.js index 24b8a99..d9bc7a3 100644 --- a/src/models/videos_model.js +++ b/src/models/videos_model.js @@ -1,7 +1,8 @@ const pull = require("../server/node_modules/pull-stream"); const { getConfig } = require("../configs/config-manager.js"); const { buildValidatedTombstoneSet } = require('./tombstone_validator'); -const { dedupeBy, norm } = require('../backend/dedupe'); +const { dedupeBy, norm, versionGroups } = require('../backend/dedupe'); +const { readTyped, memoIndex } = require('./typed_log'); const categories = require("../backend/opinion_categories"); const logLimit = getConfig().ssbLogStream?.limit || 1000; @@ -31,13 +32,9 @@ module.exports = ({ cooler }) => { return ssb; }; - const getAllMessages = async (ssbClient) => - new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs))) - ); - }); + const VIDEO_TYPES = ["video", "videoOpinion", "tombstone"]; + + const getAllMessages = async (ssbClient) => readTyped(ssbClient, VIDEO_TYPES, { limit: logLimit }); const getMsg = async (ssbClient, key) => new Promise((resolve, reject) => { @@ -47,9 +44,6 @@ module.exports = ({ cooler }) => { const buildIndex = (messages) => { const tomb = buildValidatedTombstoneSet(messages); const nodes = new Map(); - const parent = new Map(); - const child = new Map(); - const strictChild = new Map(); const opinionMsgs = []; for (const m of messages) { @@ -65,41 +59,16 @@ module.exports = ({ cooler }) => { const ts = v.timestamp || m.timestamp || 0; let sizeBytes = 0; try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; } - nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author }); - - if (c.replaces) { - parent.set(k, c.replaces); - child.set(c.replaces, k); - } + nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author }); } - for (const [k, node] of nodes) { - const t = node.c.replaces; - if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); } - } - - const rootOf = (id) => { - let cur = id, g = 0; - while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur); - return cur; - }; - - const contentTipOf = (root) => { - let cur = root, g = 0; - while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur); - const n = nodes.get(cur), rn = nodes.get(root); - return (n && rn && n.author === rn.author) ? cur : root; - }; - - const roots = new Set(); - for (const id of nodes.keys()) roots.add(rootOf(id)); + const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null }))); + const rootOf = vg.rootOf; + const contentTipOf = vg.tipOf; const opinionsByRoot = new Map(); for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); } - const forward = new Map(); - for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId); - const resolveGroup = (root) => { const contentTip = contentTipOf(root); const contentNode = nodes.get(contentTip) || nodes.get(root); @@ -116,9 +85,9 @@ module.exports = ({ cooler }) => { }; const tipByRoot = new Map(); - for (const r of roots) tipByRoot.set(r, contentTipOf(r)); + for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r)); - return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup }; + return { tomb, nodes, rootOf, tipByRoot, resolveGroup }; }; const buildVideo = (node, rootId, viewerId, agg) => { @@ -132,9 +101,10 @@ module.exports = ({ cooler }) => { createdAt: c.createdAt || new Date(node.ts).toISOString(), updatedAt: c.updatedAt || null, tags: safeArr(c.tags), - author: c.author, + author: node.author, title: c.title || "", description: c.description || "", + license: c.license || "", mapUrl: c.mapUrl || "", opinions, opinions_inhabitants: voters, @@ -161,16 +131,12 @@ module.exports = ({ cooler }) => { const messages = await getAllMessages(ssbClient); const idx = buildIndex(messages); - let tip = id; - while (idx.forward.has(tip)) tip = idx.forward.get(tip); - if (idx.tomb.has(tip)) throw new Error("Video not found"); - - let root = tip; - while (idx.parent.has(root)) root = idx.parent.get(root); + const root = idx.rootOf(id); + if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Video not found"); return root; }, - async createVideo(blobMarkdown, tagsRaw, title, description, mapUrl) { + async createVideo(blobMarkdown, tagsRaw, title, description, mapUrl, license) { const ssbClient = await openSsb(); const blobId = parseBlobId(blobMarkdown); const tags = normalizeTags(tagsRaw) || []; @@ -187,7 +153,8 @@ module.exports = ({ cooler }) => { description: description || "", mapUrl: mapUrl || "", opinions: {}, - opinions_inhabitants: [] + opinions_inhabitants: [], + ...(license ? { license } : {}) }; return new Promise((resolve, reject) => { @@ -195,7 +162,7 @@ module.exports = ({ cooler }) => { }); }, - async updateVideoById(id, blobMarkdown, tagsRaw, title, description, mapUrl) { + async updateVideoById(id, blobMarkdown, tagsRaw, title, description, mapUrl, license) { const ssbClient = await openSsb(); const userId = ssbClient.id; const tipId = await this.resolveCurrentId(id); @@ -219,7 +186,8 @@ module.exports = ({ cooler }) => { description: description !== undefined ? description || "" : oldMsg.content.description || "", mapUrl: mapUrl !== undefined ? mapUrl || "" : oldMsg.content.mapUrl || "", createdAt: oldMsg.content.createdAt, - updatedAt: now + updatedAt: now, + ...(license !== undefined ? { license } : {}) }; const tombstone = { type: "tombstone", target: tipId, deletedAt: now, author: userId }; @@ -304,10 +272,10 @@ module.exports = ({ cooler }) => { const ssbClient = await openSsb(); const viewer = viewerId || ssbClient.id; const messages = await getAllMessages(ssbClient); - const idx = buildIndex(messages); + const idx = memoIndex('videos', messages, buildIndex); const rootId = idx.rootOf(id); const agg = idx.resolveGroup(rootId); - if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Video not found"); + if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Video not found"); return buildVideo(agg.contentNode, rootId, viewer, agg); }, diff --git a/src/models/viewer_filters.js b/src/models/viewer_filters.js index cef023e..98c9fec 100644 --- a/src/models/viewer_filters.js +++ b/src/models/viewer_filters.js @@ -3,7 +3,7 @@ const path = require('path'); const { getConfig } = require('../configs/config-manager.js'); const COOLDOWN_MS = 5 * 60 * 1000; -const statePath = path.join(__dirname, '../configs/follow_state.json'); +const statePath = require('../configs/state-manager').statePath('follow_state.json'); const readJson = (p, fallback) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { return fallback; } @@ -21,6 +21,87 @@ const loadState = () => { }; const saveState = (s) => writeJson(statePath, s); +const lanPeersPath = require('../configs/state-manager').statePath('lan-peers.json'); +const FEED_ID = /^@[A-Za-z0-9+/]{43}=\.ed25519$/; +let lanKnown = null; + +const canonicalFeedId = (key) => { + let k = String(key || '').trim(); + if (!k) return null; + if (!k.startsWith('@')) k = '@' + k; + if (!k.endsWith('.ed25519')) k += '.ed25519'; + return FEED_ID.test(k) ? k : null; +}; + +const isPrivateHost = (host) => { + const h = String(host || '').replace(/^\[|\]$/g, '').replace(/^::ffff:/i, '').toLowerCase(); + if (!h) return false; + if (/^(10|127)\./.test(h) || /^192\.168\./.test(h) || /^169\.254\./.test(h)) return true; + const m = h.match(/^172\.(\d+)\./); + if (m && Number(m[1]) >= 16 && Number(m[1]) <= 31) return true; + return h === '::1' || /^f[cd][0-9a-f]{0,2}:/.test(h) || /^fe[89ab][0-9a-f]?:/.test(h); +}; + +const hostOfAddress = (address) => { + const net = String(address || '').split('~')[0]; + if (!net.startsWith('net:')) return ''; + const hp = net.slice(4); + const i = hp.lastIndexOf(':'); + return i > 0 ? hp.slice(0, i) : hp; +}; + +const lanKeysFromConn = (entries) => { + const out = []; + for (const e of entries || []) { + if (!Array.isArray(e) || !e[1]) continue; + const [address, data] = e; + const key = canonicalFeedId(data.key); + if (!key) continue; + if (data.type === 'lan' || data.inferredType === 'lan' || isPrivateHost(hostOfAddress(address))) out.push(key); + } + return out; +}; + +const lanPeers = () => { + if (!lanKnown) { + const list = readJson(lanPeersPath, []); + lanKnown = new Set((Array.isArray(list) ? list : []).map(canonicalFeedId).filter(Boolean)); + } + return lanKnown; +}; + +const rememberLanPeers = (keys) => { + const set = lanPeers(); + let changed = false; + for (const k of keys || []) { + const id = canonicalFeedId(k); + if (id && !set.has(id)) { set.add(id); changed = true; } + } + if (changed) writeJson(lanPeersPath, [...set]); + return changed; +}; + +const MUTUAL_CONCURRENCY = 16; +const filterByWish = async (items, { wish, viewer, authorOf: author, isOwn, isMutual, lan } = {}) => { + if (!Array.isArray(items)) return items; + const own = (it) => !!(isOwn && isOwn(it, viewer)); + if (wish === 'local') return items.filter(own); + if (wish === 'only-lan') { + const near = lan || new Set(); + return items.filter(it => { const a = author(it); return !a || a === viewer || own(it) || near.has(a); }); + } + if (wish === 'mutuals' && typeof isMutual === 'function') { + const authors = [...new Set(items.map(author).filter(a => a && a !== viewer))]; + const verdict = new Map(); + let next = 0; + await Promise.all(Array.from({ length: Math.min(MUTUAL_CONCURRENCY, authors.length) }, async () => { + while (next < authors.length) { const a = authors[next++]; verdict.set(a, !!(await isMutual(a))); } + })); + return items.filter(it => { const a = author(it); return !a || a === viewer || verdict.get(a); }); + } + return items; +}; + const wishMutualsOnly = () => getConfig().wish === 'mutuals'; const pmMutualsOnly = () => getConfig().pmVisibility === 'mutuals'; const isFrictionActive = () => wishMutualsOnly() || pmMutualsOnly(); @@ -127,4 +208,9 @@ module.exports = { applyMutualSupportFilter, canSendPmTo, authorOf, + isPrivateHost, + lanKeysFromConn, + lanPeers, + rememberLanPeers, + filterByWish, }; diff --git a/src/models/votes_model.js b/src/models/votes_model.js index 75f2ca7..96deddb 100644 --- a/src/models/votes_model.js +++ b/src/models/votes_model.js @@ -5,9 +5,18 @@ const { getConfig } = require('../configs/config-manager.js'); const { dedupeBy, norm } = require('../backend/dedupe'); const { buildVoteTally } = require('../backend/vote_tally'); const categories = require('../backend/opinion_categories'); +const { readTyped, memoIndex } = require('./typed_log'); const logLimit = getConfig().ssbLogStream?.limit || 1000; const MIN_VOTE_DAYS = 7; +const INSTITUTIONAL_VOTE_TAG = /^(gov|govMethod|courtsCase|courtsMethod):/; +const isVoteLocked = (v) => !!v && ( + (parseInt(v.totalVotes || 0, 10) || 0) > 0 + || Object.values(v.opinions || {}).some(n => (Number(n) || 0) > 0) + || (Array.isArray(v.tags) && v.tags.some(t => INSTITUTIONAL_VOTE_TAG.test(String(t)))) +); +const lockedError = () => Object.assign(new Error('Vote locked'), { code: 'VOTE_LOCKED' }); + module.exports = ({ cooler }) => { let ssb; const openSsb = async () => { @@ -17,13 +26,10 @@ module.exports = ({ cooler }) => { const TYPE = 'votes'; + const VOTES_TYPES = [TYPE, 'votesVote', 'votesOpinion', 'tombstone']; + async function getAllMessages(ssbClient) { - return new Promise((resolve, reject) => { - pull( - ssbClient.createLogStream({ limit: logLimit }), - pull.collect((err, results) => (err ? reject(err) : resolve(results))) - ); - }); + return readTyped(ssbClient, VOTES_TYPES, { limit: logLimit }); } function buildIndex(messages) { @@ -220,6 +226,7 @@ module.exports = ({ cooler }) => { ssbClient.get(tipId, (err, msg) => (err || !msg ? rej(new Error('Vote not found')) : res(msg))) ); if (!vote.content || vote.content.createdBy !== userId) throw new Error('Not the author'); + if (isVoteLocked(vote.content) || isVoteLocked(await this.getVoteById(id).catch(() => null))) throw lockedError(); const tombstone = { type: 'tombstone', target: tipId, @@ -246,7 +253,7 @@ module.exports = ({ cooler }) => { if (c.createdBy !== userId) throw new Error('Not the author'); const agg = await this.getVoteById(id); const aggTotalVotes = agg ? (parseInt(agg.totalVotes || 0, 10) || 0) : 0; - if (agg && Object.keys(agg.opinions || {}).some(k => (agg.opinions[k] || 0) > 0)) throw new Error('Cannot edit vote after it has received opinions.'); + if (isVoteLocked(c) || isVoteLocked(agg)) throw lockedError(); let newDeadline = c.deadline; if (deadline != null && deadline !== '') { @@ -324,7 +331,7 @@ module.exports = ({ cooler }) => { async getVoteById(id) { const ssbClient = await openSsb(); const messages = await getAllMessages(ssbClient); - const index = buildIndex(messages); + const index = memoIndex('votes', messages, buildIndex); const activeList = computeActiveVotes(index); const byId = new Map(activeList.map(v => [v.id, v])); @@ -339,19 +346,7 @@ module.exports = ({ cooler }) => { return candidate; } - const msg = await new Promise((res, rej) => - ssbClient.get(id, (err, vote) => (err || !vote ? rej(new Error('Vote not found')) : res(vote))) - ); - - const content = msg.content || {}; - const status = statusFromContent(content, moment()); - - return { - id, - latestId: id, - ...content, - status - }; + throw new Error('Vote not found'); }, async listAll(filter = 'all') { @@ -363,6 +358,8 @@ module.exports = ({ cooler }) => { if (filter === 'mine') { list = list.filter(v => v.createdBy === userId); + } else if (filter === 'recent') { + list = list.filter(v => (Date.parse(v.createdAt || '') || 0) >= Date.now() - 86400000); } else if (filter === 'open') { list = list.filter(v => v.status === 'OPEN'); } else if (filter === 'closed') { @@ -389,4 +386,4 @@ module.exports = ({ cooler }) => { } }; }; - +module.exports.isVoteLocked = isVoteLocked; diff --git a/src/models/wallet_model.js b/src/models/wallet_model.js index cafa6cf..91ad69f 100644 --- a/src/models/wallet_model.js +++ b/src/models/wallet_model.js @@ -1,16 +1,19 @@ -const { - RequestManager, - HTTPTransport, - Client -} = require("../server/node_modules/@open-rpc/client-js"); +const RPC_TIMEOUT_MS = 30000; async function makeClient(url, user, pass) { - const headers = {}; + const headers = { 'Content-Type': 'application/json' }; if (user !== undefined || pass !== undefined) { headers['Authorization'] = 'Basic ' + Buffer.from(`${user || ''}:${pass || ''}`).toString('base64'); } - const transport = new HTTPTransport(url, { headers }); - return new Client(new RequestManager([transport])); + let nextId = 0; + return { + request: async ({ method, params = [] }) => { + const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ jsonrpc: '1.0', id: ++nextId, method, params }), signal: AbortSignal.timeout(RPC_TIMEOUT_MS) }); + const body = await res.json(); + if (!body || body.error) throw new Error((body && body.error && body.error.message) || `RPC ${res.status}`); + return body.result; + } + }; } module.exports = { @@ -50,8 +53,8 @@ module.exports = { const errors = []; const addrInfo = await module.exports.execute(url, user, pass, "validateaddress", [address]); const addressValid = !!addrInfo?.isvalid; - const amountValid = Number(amount) > 0; - const feeValid = Number(fee) >= 0; + const amountValid = Number.isFinite(Number(amount)) && Number(amount) > 0; + const feeValid = Number.isFinite(Number(fee)) && Number(fee) >= 0; if (!addressValid) errors.push("invalid_dest"); if (!amountValid) errors.push("invalid_amount"); if (!feeValid) errors.push("invalid_fee"); diff --git a/src/models/wiki_model.js b/src/models/wiki_model.js new file mode 100644 index 0000000..ee8ffe6 --- /dev/null +++ b/src/models/wiki_model.js @@ -0,0 +1,485 @@ +const pull = require('../server/node_modules/pull-stream'); +const { readTyped, memoIndex } = require('./typed_log'); +const { buildValidatedTombstoneSet } = require('./tombstone_validator'); +const { getConfig } = require('../configs/config-manager.js'); +const logLimit = getConfig().ssbLogStream?.limit || 1000; + +const WIKILINK_RE = /\[\[([^\]|\n]{1,120})(?:\|([^\]\n]{1,120}))?\]\]/g; + +const slugify = (value) => String(value == null ? '' : value) + .normalize('NFD') + .replace(/[̀-ͯ]/g, '') + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 80); + +const linkTarget = (raw) => { + const s = String(raw || '').trim(); + const isUrl = /^[a-z]+:\/\//i.test(s); + const wikiPath = (isUrl || s.startsWith('/')) ? s.match(/\/wiki\/([^?#]+)/) : null; + if (wikiPath) { try { return decodeURIComponent(wikiPath[1]).trim(); } catch (_) { return wikiPath[1].trim(); } } + if (isUrl) { + const seg = s.replace(/[?#].*$/, '').split('/').filter(Boolean).pop() || ''; + try { return decodeURIComponent(seg).trim(); } catch (_) { return seg.trim(); } + } + const namespaced = s.match(/^[a-z]+:(?=\S)(.+)$/i); + return namespaced ? namespaced[1].trim() : s; +}; + +const extractWikiLinks = (text) => { + const out = []; + const seen = new Set(); + for (const m of String(text || '').matchAll(WIKILINK_RE)) { + const slug = slugify(linkTarget(m[1])); + if (!slug || seen.has(slug)) continue; + seen.add(slug); + out.push(slug); + } + return out; +}; + +const WIKI_TYPE = 'wikiPage'; +const ENVELOPE_TYPE = 'tribe-msg'; +const EDIT_POLICIES = ['open', 'author', 'tribe']; +const RECENT_MS = 24 * 60 * 60 * 1000; +const MAX_CHANGES = 60; +const MAX_TITLE = 100; +const IMAGE_RE = /!\[image:[^\]\[\n]{0,160}\]\((&[^)\s]{1,120}\.sha256)\)/; +const BODY_INLINE_MAX = 6000; +const bodyCache = new Map(); +const addBlob = (ssbClient, buf) => new Promise((resolve, reject) => pull(pull.values([buf]), ssbClient.blobs.add((err, ref) => err ? reject(err) : resolve(ref)))); +const getBlob = (ssbClient, ref) => new Promise((resolve) => { + try { + pull(ssbClient.blobs.get(ref), pull.collect((err, chunks) => resolve(err || !chunks || !chunks.length ? null : Buffer.concat(chunks.map(c => Buffer.isBuffer(c) ? c : Buffer.from(c)))))); + } catch (_) { resolve(null); } +}); + +const safeText = (v) => String(v == null ? '' : v).trim(); +const normalizeList = (raw) => { + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : String(raw).split(','); + return Array.from(new Set(arr.map(t => String(t || '').trim()).filter(Boolean))); +}; +const normalizePolicy = (v) => { + const s = String(v || '').toLowerCase(); + if (s === 'closed') return 'author'; + return EDIT_POLICIES.includes(s) ? s : 'open'; +}; + +module.exports = ({ cooler, tribeCrypto = null, tribesModel = null }) => { + let ssb; + const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; + + const tribeRootId = async (tribeId) => { + if (!tribesModel || !tribeId) return tribeId; + try { return await tribesModel.getRootId(tribeId); } catch (_) { return tribeId; } + }; + + const tribeKeyFor = async (tribeId) => { + if (!tribeCrypto || !tribeId) return null; + const rootId = await tribeRootId(tribeId); + return tribeCrypto.getKey(rootId) || null; + }; + + const storeBody = async (ssbClient, body, tribeId) => { + const text = safeText(body); + if (Buffer.byteLength(text, 'utf8') <= BODY_INLINE_MAX) return { body: text }; + let payload = text; + if (tribeId) { + const key = await tribeKeyFor(tribeId); + if (!key) throw new Error('Missing tribe key — cannot publish encrypted wiki page'); + payload = JSON.stringify(tribeCrypto.encryptContent({ body: text }, [key], true)); + } + const ref = await addBlob(ssbClient, Buffer.from(payload, 'utf8')); + bodyCache.set(ref, text); + return { body: '', bodyBlob: ref }; + }; + + const resolveBody = async (ssbClient, c) => { + if (!c || typeof c.bodyBlob !== 'string' || !c.bodyBlob) return c; + if (bodyCache.has(c.bodyBlob)) return { ...c, body: bodyCache.get(c.bodyBlob) }; + const buf = await getBlob(ssbClient, c.bodyBlob); + if (!buf) return { ...c, body: safeText(c.body) }; + let text = buf.toString('utf8'); + if (c.tribeId) { + try { + const key = await tribeKeyFor(c.tribeId); + const r = tribeCrypto.decryptContent(JSON.parse(text), key ? [[key]] : []); + text = r && !r._undecryptable && typeof r.body === 'string' ? r.body : ''; + } catch (_) { text = ''; } + } + bodyCache.set(c.bodyBlob, text); + return { ...c, body: text }; + }; + + const readEntries = async (ssbClient) => { + const msgs = await readTyped(ssbClient, [WIKI_TYPE, 'tombstone', ENVELOPE_TYPE], { limit: logLimit }); + const fpIdx = tribeCrypto && typeof tribeCrypto.buildFingerprintIndex === 'function' ? tribeCrypto.buildFingerprintIndex() : null; + const out = []; + for (const m of msgs) { + const v = m && m.value; + const c = v && v.content; + if (!c || typeof c !== 'object') continue; + if (c.type === WIKI_TYPE || c.type === 'tombstone') { out.push(m); continue; } + if (!fpIdx || !tribeCrypto.isTribeMsg(c)) continue; + const r = tribeCrypto.unwrapMsg(c, fpIdx); + if (!r || !r.body || typeof r.body !== 'object') continue; + const body = r.body; + if (body.k === WIKI_TYPE) { + const flat = { ...body, type: WIKI_TYPE }; + delete flat.k; + out.push({ ...m, value: { ...v, content: flat } }); + } else if (body.k === 'tombstone' && typeof body.target === 'string') { + out.push({ ...m, value: { ...v, content: { type: 'tombstone', target: body.target, deletedAt: body.deletedAt, author: body.author || v.author } } }); + } + } + for (let i = 0; i < out.length; i++) { + const c = out[i].value && out[i].value.content; + if (c && c.type === WIKI_TYPE && c.bodyBlob) out[i] = { ...out[i], value: { ...out[i].value, content: await resolveBody(ssbClient, c) } }; + } + return out; + }; + + const tribeMembersFor = async (entries) => { + const out = new Map(); + if (!tribesModel) return out; + for (const m of entries) { + const c = m.value.content; + if (c.type !== WIKI_TYPE || !c.tribeId || out.has(String(c.tribeId))) continue; + let members = []; + try { const t = await tribesModel.getTribeById(c.tribeId); members = Array.isArray(t && t.members) ? t.members : []; } catch (_) {} + out.set(String(c.tribeId), members); + } + return out; + }; + + const buildIndex = (entries, membersOf = new Map()) => { + const tomb = buildValidatedTombstoneSet(entries); + const nodes = new Map(); + for (const m of entries) { + const c = m.value.content; + if (c.type !== WIKI_TYPE) continue; + nodes.set(m.key, { key: m.key, author: m.value.author, ts: m.value.timestamp || m.timestamp || 0, c }); + } + const mayEdit = (state, author) => { + if (author === state.owner) return true; + if (state.policy === 'author') return false; + if (state.tribeId) return (membersOf.get(state.tribeId) || []).includes(author); + return state.policy !== 'tribe'; + }; + const states = new Map(); + const stateOf = (key) => { + const path = []; + const seen = new Set(); + let cur = key; + while (!states.has(cur)) { + const node = nodes.get(cur); + if (!node) { states.set(cur, null); break; } + if (typeof node.c.replaces !== 'string') { + states.set(cur, { root: cur, owner: node.author, tribeId: String(node.c.tribeId || ''), policy: normalizePolicy(node.c.editPolicy), license: safeText(node.c.license) }); + break; + } + if (seen.has(cur)) { states.set(cur, null); break; } + seen.add(cur); + path.push(cur); + cur = node.c.replaces; + } + for (let i = path.length - 1; i >= 0; i--) { + const k = path[i]; + if (states.has(k)) continue; + const node = nodes.get(k); + const parent = states.get(node.c.replaces); + if (!parent || String(node.c.tribeId || '') !== parent.tribeId || !mayEdit(parent, node.author)) { states.set(k, null); continue; } + states.set(k, node.author === parent.owner ? { ...parent, policy: normalizePolicy(node.c.editPolicy), license: safeText(node.c.license) } : parent); + } + return states.get(key); + }; + const rootOf = (key) => { const s = stateOf(key); return s ? s.root : key; }; + const versionsByRoot = new Map(); + for (const node of nodes.values()) { + if (!stateOf(node.key)) continue; + const root = rootOf(node.key); + if (tomb.has(root)) continue; + if (!versionsByRoot.has(root)) versionsByRoot.set(root, []); + versionsByRoot.get(root).push(node); + } + const pages = []; + for (const [root, versions] of versionsByRoot) { + versions.sort((a, b) => a.ts - b.ts); + const first = versions[0]; + const tip = versions[versions.length - 1]; + const state = stateOf(tip.key); + const c = tip.c; + const body = safeText(c.body); + pages.push({ + id: root, + rootId: root, + tipId: tip.key, + slug: slugify(c.title) || safeText(c.slug), + title: safeText(c.title).slice(0, MAX_TITLE), + body, + image: (body.match(IMAGE_RE) || [])[1] || null, + tags: normalizeList(c.tags), + aliases: [...new Set([...normalizeList(c.aliases).map(slugify), safeText(c.slug)].filter(a => a && a !== slugify(c.title)))], + editPolicy: state.policy, + license: state.license, + tribeId: c.tribeId || null, + encrypted: !!c.tribeId, + author: state.owner, + lastAuthor: tip.author, + createdAt: first.c.createdAt || new Date(first.ts).toISOString(), + updatedAt: c.updatedAt || new Date(tip.ts).toISOString(), + ts: tip.ts, + versionCount: versions.length, + links: extractWikiLinks(body), + versions: versions.map(vn => ({ + key: vn.key, + author: vn.author, + ts: vn.ts, + createdAt: vn.c.updatedAt || vn.c.createdAt || new Date(vn.ts).toISOString(), + title: safeText(vn.c.title), + body: safeText(vn.c.body), + tags: normalizeList(vn.c.tags), + summary: safeText(vn.c.summary) + })) + }); + } + pages.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0); + return { pages, rootOf, nodes }; + }; + + const sameScope = (page, tribeId) => String(page.tribeId || '') === String(tribeId || ''); + + const scopePages = async (pages, tribeId) => { + if (tribeId === 'any') return pages; + if (!tribeId) return pages.filter(p => !p.tribeId); + const chain = tribesModel && typeof tribesModel.getChainIds === 'function' + ? await tribesModel.getChainIds(tribeId).catch(() => [tribeId]) + : [tribeId]; + const set = new Set(chain.map(String)); + return pages.filter(p => p.tribeId && set.has(String(p.tribeId))); + }; + + const decorate = (pages, scoped) => { + const bySlug = new Map(); + for (const p of scoped) { + const keys = [p.slug, ...p.aliases]; + for (const s of keys) if (s && !bySlug.has(s)) bySlug.set(s, p); + } + const inbound = new Map(); + for (const p of scoped) { + for (const s of p.links) { + const target = bySlug.get(s); + if (!target || target.id === p.id) continue; + if (!inbound.has(target.id)) inbound.set(target.id, new Set()); + inbound.get(target.id).add(p.id); + } + } + return scoped.map(p => ({ + ...p, + backlinkCount: (inbound.get(p.id) || new Set()).size, + missingLinks: p.links.filter(s => !bySlug.has(s)), + isLinked: (inbound.get(p.id) || new Set()).size > 0 + })); + }; + + const findInScope = (scoped, idOrSlug) => { + const needle = String(idOrSlug || '').trim(); + if (!needle) return null; + if (needle.startsWith('%')) return scoped.find(p => p.id === needle || p.tipId === needle || p.versions.some(v => v.key === needle)) || null; + const slug = slugify(needle); + return scoped.find(p => p.slug === slug) || scoped.find(p => p.aliases.includes(slug)) || null; + }; + + const publishContent = async (ssbClient, content) => { + let msg = content; + if (content.tribeId) { + const key = await tribeKeyFor(content.tribeId); + if (!key) throw new Error('Missing tribe key — cannot publish encrypted wiki page'); + msg = tribeCrypto.wrapMsg({ k: WIKI_TYPE, ...content }, key); + } + return new Promise((resolve, reject) => ssbClient.publish(msg, (err, res) => err ? reject(err) : resolve(res))); + }; + + const load = async () => { + const ssbClient = await openSsb(); + const entries = await readEntries(ssbClient); + const members = await tribeMembersFor(entries); + const idx = memoIndex('wiki', entries, (e) => buildIndex(e, members)); + return { ssbClient, idx }; + }; + + const canEdit = async (page, viewer) => { + if (!page) return false; + const owner = String(page.author) === String(viewer); + if (page.editPolicy === 'author') return owner; + if (page.tribeId) { + if (!tribesModel) return owner; + try { + const t = await tribesModel.getTribeById(page.tribeId); + return owner || (Array.isArray(t.members) && t.members.includes(viewer)); + } catch (_) { return owner; } + } + if (page.editPolicy === 'tribe') return owner; + return true; + }; + + return { + WIKI_TYPE, + EDIT_POLICIES, + slugify, + + async createPage({ title, body, tags, aliases, editPolicy, license, tribeId = null }) { + const { ssbClient, idx } = await load(); + const cleanTitle = safeText(title).slice(0, MAX_TITLE); + if (!cleanTitle) throw new Error('Title required'); + const slug = slugify(cleanTitle); + if (!slug) throw new Error('Title required'); + const scoped = await scopePages(idx.pages, tribeId); + const existing = scoped.find(p => p.slug === slug); + if (existing) return { key: existing.id, existing: true }; + const now = new Date().toISOString(); + const content = { + type: WIKI_TYPE, + title: cleanTitle, + slug, + ...(await storeBody(ssbClient, body, tribeId)), + tags: normalizeList(tags), + aliases: normalizeList(aliases).map(slugify).filter(Boolean), + editPolicy: normalizePolicy(editPolicy), + ...(license ? { license } : {}), + author: ssbClient.id, + createdAt: now, + updatedAt: now, + ...(tribeId ? { tribeId } : {}) + }; + const res = await publishContent(ssbClient, content); + return { key: res.key, existing: false }; + }, + + async updatePage(id, data = {}) { + const { ssbClient, idx } = await load(); + const page = findInScope(idx.pages, id); + if (!page) throw new Error('Page not found'); + if (!(await canEdit(page, ssbClient.id))) throw new Error('Not allowed to edit'); + const now = new Date().toISOString(); + const nextTitle = data.title !== undefined ? safeText(data.title).slice(0, MAX_TITLE) : page.title; + const nextLicense = data.license !== undefined && String(page.author) === String(ssbClient.id) ? safeText(data.license) : page.license; + const content = { + type: WIKI_TYPE, + title: nextTitle || page.title, + slug: page.slug, + ...(await storeBody(ssbClient, data.body !== undefined ? data.body : page.body, page.tribeId || null)), + tags: data.tags !== undefined ? normalizeList(data.tags) : page.tags, + aliases: data.aliases !== undefined ? normalizeList(data.aliases).map(slugify).filter(Boolean) : page.aliases, + editPolicy: data.editPolicy !== undefined ? normalizePolicy(data.editPolicy) : page.editPolicy, + ...(nextLicense ? { license: nextLicense } : {}), + summary: safeText(data.summary).slice(0, 200), + author: ssbClient.id, + createdAt: page.createdAt, + updatedAt: now, + replaces: page.tipId, + ...(page.tribeId ? { tribeId: page.tribeId } : {}) + }; + const res = await publishContent(ssbClient, content); + return { key: res.key, rootId: page.id, author: page.author }; + }, + + async restoreVersion(id, versionKey) { + const { idx } = await load(); + const page = findInScope(idx.pages, id); + if (!page) throw new Error('Page not found'); + const version = page.versions.find(v => v.key === versionKey); + if (!version) throw new Error('Version not found'); + return this.updatePage(page.id, { title: version.title, body: version.body, tags: version.tags, summary: `restore:${versionKey}` }); + }, + + async deletePage(id) { + const { ssbClient, idx } = await load(); + const page = findInScope(idx.pages, id); + if (!page) throw new Error('Page not found'); + if (String(page.author) !== String(ssbClient.id)) throw new Error('Only the creator can delete'); + const deletedAt = new Date().toISOString(); + const tomb = { type: 'tombstone', target: page.id, deletedAt, author: ssbClient.id }; + let msg = tomb; + if (page.tribeId) { + const key = await tribeKeyFor(page.tribeId); + if (!key) throw new Error('Missing tribe key — cannot delete encrypted wiki page'); + msg = tribeCrypto.wrapMsg({ k: 'tombstone', target: page.id, deletedAt, author: ssbClient.id }, key); + } + return new Promise((resolve, reject) => ssbClient.publish(msg, (err, res) => err ? reject(err) : resolve(res))); + }, + + async getPage(idOrSlug, { tribeId = null } = {}) { + const { ssbClient, idx } = await load(); + let scoped = await scopePages(idx.pages, tribeId); + let page = findInScope(scoped, idOrSlug); + if (!page && String(idOrSlug || '').startsWith('%')) { + page = findInScope(idx.pages, idOrSlug); + if (page) scoped = await scopePages(idx.pages, page.tribeId || null); + } + if (!page) return null; + const decorated = decorate(idx.pages, scoped); + const full = decorated.find(p => p.id === page.id) || page; + const bySlug = new Map(); + for (const p of scoped) for (const s of [p.slug, ...p.aliases]) if (s && !bySlug.has(s)) bySlug.set(s, p); + const backlinks = decorated.filter(p => p.id !== full.id && p.links.some(s => bySlug.get(s) && bySlug.get(s).id === full.id)); + const outbound = []; + const seen = new Set([full.id]); + for (const slug of full.links) { + const target = bySlug.get(slug); + if (!target || seen.has(target.id)) continue; + seen.add(target.id); + outbound.push(decorated.find(p => p.id === target.id) || target); + } + const linkedPages = [...backlinks]; + const inList = new Set(backlinks.map(p => p.id)); + for (const p of outbound) if (!inList.has(p.id)) linkedPages.push(p); + return { ...full, backlinks, outbound, linkedPages, canEdit: await canEdit(full, ssbClient.id), isOwner: String(full.author) === String(ssbClient.id) }; + }, + + async listPages({ tribeId = null, filter = 'all', q = '', viewerId } = {}) { + const { ssbClient, idx } = await load(); + const me = viewerId || ssbClient.id; + const scoped = decorate(idx.pages, await scopePages(idx.pages, tribeId)); + let list = scoped; + const f = String(filter || 'all').toLowerCase(); + if (f === 'mine') list = list.filter(p => String(p.author) === String(me) || p.versions.some(v => String(v.author) === String(me))); + else if (f === 'recent') list = list.filter(p => Date.now() - p.ts <= RECENT_MS); + else if (f === 'linked') list = list.filter(p => p.isLinked); + const needle = safeText(q).toLowerCase(); + if (needle) list = list.filter(p => p.title.toLowerCase().includes(needle) || p.body.toLowerCase().includes(needle) || p.tags.some(t => t.toLowerCase().includes(needle))); + return list.slice().sort((a, b) => b.ts - a.ts); + }, + + async recentChanges({ tribeId = null } = {}) { + const { idx } = await load(); + const scoped = await scopePages(idx.pages, tribeId); + const changes = []; + for (const p of scoped) { + p.versions.forEach((v, i) => changes.push({ + pageId: p.id, slug: p.slug, title: v.title || p.title, tribeId: p.tribeId || null, + versionKey: v.key, author: v.author, ts: v.ts, createdAt: v.createdAt, summary: v.summary, index: i + 1, isCreation: i === 0 + })); + } + return changes.sort((a, b) => b.ts - a.ts || b.index - a.index).slice(0, MAX_CHANGES); + }, + + async resolveRootId(id) { + const { idx } = await load(); + const page = findInScope(idx.pages, id); + if (!page) throw new Error('Not found'); + return page.id; + }, + + async canEditPage(id, viewer) { + const { ssbClient, idx } = await load(); + const page = findInScope(idx.pages, id); + return canEdit(page, viewer || ssbClient.id); + } + }; +}; + +Object.assign(module.exports, { WIKILINK_RE, slugify, linkTarget, extractWikiLinks }); diff --git a/src/server/SSB_server.js b/src/server/SSB_server.js index cd69637..95693c9 100644 --- a/src/server/SSB_server.js +++ b/src/server/SSB_server.js @@ -6,7 +6,7 @@ const fs = require('fs'); const path = require('path'); const SecretStack = require('secret-stack'); const caps = require('ssb-caps'); -const SSB = require('ssb-db'); +const db2Defaults = require('ssb-db2/defaults'); const config = require('./ssb_config'); const { printMetadata } = require('./ssb_metadata'); @@ -27,6 +27,7 @@ const { printMetadata } = require('./ssb_metadata'); return `${m[1].replace(/^::ffff:/, '')}:${m[2]}`; }; const logRejection = (peer) => { + if (!(process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true')) return; const ts = new Date().toISOString().replace('T', ' ').slice(0, 19); realErr.call(console, `[${ts}] REJECTED ${peer} (wrong SHS cap)`); }; @@ -51,35 +52,41 @@ const { printMetadata } = require('./ssb_metadata'); }; })(); -require('ssb-plugins').loadUserPlugins(SecretStack({ caps }), config); +try { + if (require('../configs/config-manager.js').getConfig().networkPaused === true || process.env.OASIS_NETWORK_PAUSED === '1') config.conn = { ...(config.conn || {}), autostart: false }; +} catch (_) {} const Server = SecretStack({ caps }) - .use(SSB) + .use(require('ssb-db2/core')) + .use(require('ssb-classic')) + .use(require('ssb-box')) + .use(require('ssb-db2/compat/publish')) + .use(require('ssb-db2/compat/post')) + .use(require('ssb-db2/compat/db')) + .use(require('ssb-db2/compat/log-stream')) + .use(require('ssb-db2/compat/history-stream')) + .use(require('ssb-db2/compat/ebt')) + .use(require('ssb-db2/compat/feedstate')) + .use(require('./db2_legacy')) .use(require('ssb-master')) - .use(require('ssb-gossip')) .use(require('ssb-ebt')) .use(require('ssb-friends')) .use(require('ssb-blobs')) - .use(require('ssb-meme')) - .use(require('ssb-plugins')) .use(require('ssb-conn')) - .use(require('ssb-box')) - .use(require('ssb-search')) - .use(require('ssb-private')) - .use(require('ssb-friend-pub')) .use(config.pub ? require('ssb-invite') : require('ssb-invite-client')) .use(require('ssb-logging')) .use(require('ssb-replication-scheduler')) - .use(require('ssb-partial-replication')) - .use(require('ssb-about')) .use(require('ssb-onion')) .use(require('ssb-unix-socket')) .use(require('ssb-no-auth')) - .use(require('ssb-backlinks')) - .use(require('ssb-links')) - .use(require('ssb-tangle')) - .use(require('ssb-query')); + .use(require('./snapshot_plugin')) + .use((() => { const pm = require('./phone_module'); try { if (process.env.OASIS_MOBILE === '1') { pm.setAudioFactory(require('./phone_audio_android')); pm.setVideoFactory(require('./phone_video_android')); } } catch (_) {} return pm; })()) + .use(require('./network_pause')); +if (!config.pub) { + Server.use(require('ssb-lan')); + Server.use(require('./lanRouter')); +} if (config.autofollow && typeof config.autofollow === 'object' && !Array.isArray(config.autofollow)) { if (config.autofollow.enabled === false) { @@ -104,20 +111,135 @@ const isLockError = (err) => { return /Resource temporarily unavailable/i.test(msg) && /\.ssb\/.*LOCK/i.test(msg); }; +const isCorruptStoreError = (err) => { + const msg = String((err && err.message) || ''); + const stack = String((err && err.stack) || ''); + return /JSON|Unexpected token|Unexpected end|bipf|Invalid|corrupt/i.test(msg) + && /flumelog-offset|aligned-block-file|flumeview|flumedb|async-append-only-log|jitdb|bipf|ssb-db2/i.test(stack); +}; + +const migration = { running: false, percent: 0, done: false, error: null }; +let migrationPromise = null; +const MIGRATED_MARKER = 'OASIS: this log was migrated to ssb-db2'; +const oldLogIsMarker = (file) => { + try { + const st = fs.statSync(file); + return st.size < 4096 && fs.readFileSync(file, 'utf8').startsWith(MIGRATED_MARKER); + } catch (_) { return false; } +}; +const writeMigratedMarker = () => { + try { + fs.mkdirSync(db2Defaults.flumePath(config.path), { recursive: true }); + fs.writeFileSync(db2Defaults.oldLogPath(config.path), `${MIGRATED_MARKER} (${config.path}/db2/log.bipf) on ${new Date().toISOString()}.\nThis file is a guard: an Oasis older than the ssb-db2 move cannot read it and refuses to start, instead of starting with an empty log and forking your feed. Do not delete it unless you are moving back to that version on purpose, and never run both versions on this folder.\n`); + } catch (_) {} +}; +const migrateFlume = () => { + if (migrationPromise) return migrationPromise; + migrationPromise = new Promise((resolve, reject) => { + const oldLog = db2Defaults.oldLogPath(config.path); + if (!fs.existsSync(oldLog) || oldLogIsMarker(oldLog)) { migration.done = true; return resolve(false); } + if (isDebug()) console.log('- Database: migrating the local log to ssb-db2'); + migration.running = true; + let mig; + try { + mig = SecretStack({ caps }) + .use(require('ssb-db2/migrate')) + .call(null, { ...config, connections: { incoming: {}, outgoing: {} }, db2: { ...(config.db2 || {}), automigrate: false, dangerouslyKillFlumeWhenMigrated: true } }); + } catch (err) { migration.running = false; migration.error = err; return reject(err); } + const pull = require('pull-stream'); + let shown = -1; + pull(mig.db2migrate.progress(), pull.drain((p) => { + const pct = Math.max(0, Math.min(100, Math.floor((Number(p) || 0) * 100))); + migration.percent = pct; + if (pct !== shown && isDebug()) { shown = pct; console.log(` migrating ${pct}%`); } + })); + const finish = () => { + const waitGone = (tries) => { + if (!fs.existsSync(db2Defaults.oldLogPath(config.path))) { + mig.close(() => { + writeMigratedMarker(); + migration.running = false; migration.done = true; migration.percent = 100; + if (isDebug()) console.log(' migrated 100%'); + resolve(true); + }); + return; + } + if (tries <= 0) { + try { fs.rmSync(db2Defaults.flumePath(config.path), { recursive: true, force: true }); } catch (_) {} + mig.close(() => { + if (!fs.existsSync(db2Defaults.oldLogPath(config.path))) writeMigratedMarker(); + else if (isDebug()) console.log(' the old log could not be removed; it is left in place'); + migration.running = false; migration.done = true; migration.percent = 100; + resolve(true); + }); + return; + } + setTimeout(() => waitGone(tries - 1), 200); + }; + waitGone(300); + }; + try { mig.db2migrate.start(); } catch (err) { migration.running = false; migration.error = err; return reject(err); } + mig.db2migrate.synchronized((synced) => { + if (synced !== true) return; + finish(); + return false; + }); + }); + return migrationPromise; +}; + +const isDebug = () => process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true'; + +const isPortInUseError = (err) => String((err && err.code) || '') === 'EADDRINUSE'; + const handleFatal = (err) => { + if (isPortInUseError(err)) { + console.log(''); + console.log('Another Oasis instance is already running on this device. Close the other instance (or kill the process) and try again.'); + if (isDebug()) console.log(`Detail: ${String((err && err.message) || err)}`); + console.log(''); + process.exit(1); + } if (isLockError(err)) { console.log(''); console.log('Another Oasis instance is already running on this device. Close the other instance (or kill the process) and try again.'); + if (isDebug()) { + console.log(`Detail: ${String((err && err.message) || err)}`); + console.log(String((err && err.stack) || '').split('\n').slice(1, 4).join('\n')); + } console.log(''); process.exit(1); } + if (isCorruptStoreError(err)) { + console.log(''); + console.log('Oasis could not read its local database (a record in ~/.ssb/db2 looks corrupted).'); + console.log('This usually happens after an unclean shutdown or a disk error.'); + console.log(''); + console.log('What you can try, in order:'); + console.log(' 1. Simply start Oasis again — transient read errors often clear on the next boot.'); + console.log(' 2. Check your disk health (e.g. run fsck on the partition holding ~/.ssb).'); + console.log(' 3. Rebuild the indexes from Settings once Oasis starts, or remove ~/.ssb/db2/indexes'); + console.log(' and ~/.ssb/db2/jit (NOT log.bipf, NOT secret) so they are regenerated from the log.'); + console.log(''); + if (isDebug()) console.log(`Technical detail: ${String((err && err.message) || err)}`); + process.exit(1); + } + if (server) { + console.error('[oasis] unexpected error, still running:', (err && err.stack) || err); + return; + } throw err; }; process.on('uncaughtException', handleFatal); -if (argv[0] === 'start') { +const tightenStateDir = () => { + try { if (config.path && fs.existsSync(config.path)) fs.chmodSync(config.path, 0o700); } catch (_) {} +}; + +const startServerOnly = () => { try { + tightenStateDir(); server = Server(config); } catch (err) { handleFatal(err); @@ -133,7 +255,7 @@ if (argv[0] === 'start') { manifest.config = 'sync'; server.config = cb => { - console.log(JSON.stringify(config, null, 2)); + console.log(JSON.stringify(config, (k, v) => (k === 'keys' || k === 'private' || k === 'secret') ? undefined : v, 2)); cb(); }; @@ -182,10 +304,51 @@ if (argv[0] === 'start') { } catch (_) {} }, 1000); + const ownFeedSeq = () => new Promise((res) => { + try { + const pull = require('pull-stream'); + pull( + server.createUserStream({ id: server.id, reverse: true, limit: 1 }), + pull.collect((err, msgs) => res(err || !msgs || !msgs.length ? 0 : (msgs[0].value && msgs[0].value.sequence) || 0)) + ); + } catch (_) { res(0); } + }); + + setTimeout(async () => { + try { + if (!(await ownFeedSeq())) return; + const pull = require('pull-stream'); + const version = String(require('./package.json').version || ''); + if (!version) return; + pull( + server.createUserStream({ id: server.id, reverse: true }), + pull.filter(m => m && m.value && m.value.content && m.value.content.type === 'oasisVersion'), + pull.take(1), + pull.collect((err, msgs) => { + const mine = !err && msgs && msgs.length ? msgs[0].value.content.version : null; + if (mine !== version) { + try { server.publish({ type: 'oasisVersion', version, updatedAt: new Date().toISOString() }, () => {}); } catch (_) {} + } + }) + ); + } catch (_) {} + }, 7000); + +}; + +if (argv[0] === 'start') { + migrateFlume().then(startServerOnly, handleFatal); } module.exports = { config, - server: server || Server(config), - open: async () => server || Server(config) + get server() { + return server; + }, + migrationStatus: () => ({ ...migration }), + open: async () => { + await migrateFlume(); + if (!server) { tightenStateDir(); server = Server(config); } + return server; + } }; diff --git a/src/server/db2_legacy.js b/src/server/db2_legacy.js new file mode 100644 index 0000000..8550072 --- /dev/null +++ b/src/server/db2_legacy.js @@ -0,0 +1,651 @@ +const pull = require('pull-stream'); +const cat = require('pull-cat'); +const defer = require('pull-defer'); +const pl = require('pull-level'); +const bipf = require('bipf'); +const ssbKeys = require('ssb-keys'); +const ssbMsgs = require('ssb-msgs'); +const { heads } = require('ssb-sort'); +const Plugin = require('ssb-db2/indexes/plugin'); +const { where, and, or, type, author, descending, sortByArrival, toPullStream, toCallback, startFrom, paginate, seqs, isDecrypted, batch, lt, lte } = require('ssb-db2/operators'); + +const BIPF_CONTENT = bipf.allocAndEncode('content'); +const BIPF_AUTHOR = bipf.allocAndEncode('author'); +const BIPF_TIMESTAMP = bipf.allocAndEncode('timestamp'); +const BIPF_TYPE = bipf.allocAndEncode('type'); +const LINK_RE = /^[@%&]/; +const SEARCH_LIMIT = 100; +const ARRIVAL_SLACK_MS = 10 * 60 * 1000; +const ACTIVITY_DRAIN_MS = 3000; +const RECENT_KEEP = 64; +const FIRST_PAGE = 1000; +const LAST_PAGE = 64000; +const OPS = ['$gt', '$gte', '$lt', '$lte', '$in', '$is', '$prefix', '$ne', '$truthy', '$eq']; + +class LinksIndex extends Plugin { + constructor(log, dir) { + super(log, dir, 'oasisLinks', 1, 'json', 'json'); + } + + processRecord(record, seq, pValue) { + const buf = record.value; + const pContent = bipf.seekKey2(buf, pValue, BIPF_CONTENT, 0); + if (pContent < 0) return; + const content = bipf.decode(buf, pContent); + if (!content || typeof content !== 'object') return; + const pAuthor = bipf.seekKey2(buf, pValue, BIPF_AUTHOR, 0); + const source = pAuthor < 0 ? null : bipf.decode(buf, pAuthor); + const seen = new Set(); + ssbMsgs.indexLinks(content, (link, rel) => { + const dest = link && link.link; + if (typeof dest !== 'string' || !LINK_RE.test(dest)) return; + const mark = `${dest}\u0000${rel}`; + if (seen.has(mark)) return; + seen.add(mark); + this.batch.push({ type: 'put', key: [dest, seq, rel], value: { s: seq, a: source, r: rel } }); + }); + } + + indexesContent() { + return true; + } + + getLinks(dest, cb) { + pull( + pl.read(this.level, { gte: [dest, ''], lte: [dest, undefined], keyEncoding: this.keyEncoding, valueEncoding: this.valueEncoding, keys: false }), + pull.collect((err, arr) => err ? cb(err) : cb(null, (arr || []).filter(Boolean))) + ); + } +} + +class ActivityIndex extends Plugin { + constructor(log, dir) { + super(log, dir, 'oasisActivity', 2, undefined, 'json'); + this.latest = new Map(); + this.dirty = new Set(); + this.reached = -1; + this.waiting = []; + this.recent = []; + const onRecord = this.onRecord; + this.onRecord = (record, isLive, pValue) => { + onRecord.call(this, record, isLive, pValue); + const last = this.recent.length ? this.recent[this.recent.length - 1] : -1; + if (record.value && record.offset > last) { + this.recent.push(record.offset); + if (this.recent.length > RECENT_KEEP) this.recent.shift(); + } + this.reach(record.offset); + }; + this.offset((offset) => this.reach(offset)); + } + + reach(offset) { + if (!(offset > this.reached)) return; + this.reached = offset; + if (!this.waiting.length) return; + const due = this.waiting.filter((w) => w.offset <= offset); + this.waiting = this.waiting.filter((w) => w.offset > offset); + for (const w of due) w.done(); + } + + whenReached(offset, ms, done) { + if (this.reached >= offset) return done(true); + let settled = false; + const finish = (ok) => { if (settled) return; settled = true; clearTimeout(timer); done(ok); }; + const timer = setTimeout(() => { this.waiting = this.waiting.filter((w) => w.done !== hit); finish(false); }, ms); + const hit = () => finish(true); + this.waiting.push({ offset, done: hit }); + } + + onLoaded(cb) { + pull( + pl.read(this.level, { gt: '\x00', valueEncoding: this.valueEncoding }), + pull.drain(({ key, value }) => { this.latest.set(key, value); }, (err) => cb(err && err !== true ? err : undefined)) + ); + } + + processRecord(record, seq, pValue) { + const buf = record.value; + const pAuthor = bipf.seekKey2(buf, pValue, BIPF_AUTHOR, 0); + if (pAuthor < 0) return; + const author = bipf.decode(buf, pAuthor); + if (typeof author !== 'string') return; + const pContent = bipf.seekKey2(buf, pValue, BIPF_CONTENT, 0); + if (pContent >= 0 && bipf.getEncodedType(buf, pContent) === bipf.types.object) { + const pType = bipf.seekKey2(buf, pContent, BIPF_TYPE, 0); + if (pType >= 0 && bipf.decode(buf, pType) === 'tombstone') return; + } + const pDeclared = bipf.seekKey2(buf, pValue, BIPF_TIMESTAMP, 0); + const pArrival = bipf.seekKey2(buf, 0, BIPF_TIMESTAMP, 0); + let ts = Number(pDeclared >= 0 ? bipf.decode(buf, pDeclared) : 0) || Number(pArrival >= 0 ? bipf.decode(buf, pArrival) : 0) || 0; + if (ts > 0 && ts < 1e12) ts *= 1000; + if (!(ts > (this.latest.get(author) || 0))) return; + this.latest.set(author, ts); + this.dirty.add(author); + } + + onFlush(cb) { + for (const author of this.dirty) this.batch.push({ type: 'put', key: author, value: this.latest.get(author) }); + this.dirty.clear(); + cb(); + } + + indexesContent() { + return false; + } + + reset() { + this.latest.clear(); + this.dirty.clear(); + this.recent = []; + } +} + +const linksOf = (content) => { + const out = []; + if (!content || typeof content !== 'object') return out; + ssbMsgs.indexLinks(content, (link, rel) => { + const dest = link && link.link; + if (typeof dest === 'string' && LINK_RE.test(dest)) out.push({ dest, rel }); + }); + return out; +}; + +const plainOf = (kvt, keys, opts) => { + if (!kvt || !kvt.value) return null; + const base = { key: kvt.key, value: kvt.value, timestamp: kvt.timestamp }; + const c = kvt.value.content; + if (typeof c !== 'string') return opts.decryptedOnly ? null : base; + if (!opts.private && !opts.decryptedOnly) return base; + let plain = null; + try { plain = ssbKeys.unbox(c, keys.private); } catch (_) { plain = null; } + if (!plain) return opts.decryptedOnly ? null : base; + return legacy({ key: kvt.key, value: { ...kvt.value, content: plain }, timestamp: kvt.timestamp, meta: { private: true, originalContent: c } }, { private: opts.private }); +}; + +const liveTap = (sbot, keys, opts, match) => { + const items = []; + let waiting = null; + let ended = null; + const remove = sbot.db.onMsgAdded((ev) => { + if (ended) return false; + const m = plainOf(ev && ev.kvt, keys, opts); + if (!m || (match && !match(m))) return; + items.push(m); + if (waiting) { const w = waiting; waiting = null; w(null, items.shift()); } + }, false); + return (abort, cb) => { + if (abort) { + if (!ended) { ended = abort; items.length = 0; remove(); } + return cb(abort); + } + if (items.length) return cb(null, items.shift()); + if (ended) return cb(ended); + waiting = cb; + }; +}; + +const withSync = (old$, live$, opts = {}) => { + const seen = new Set(); + const parts = []; + if (opts.old !== false) parts.push(pull(old$, pull.through((m) => { if (m && m.key) seen.add(m.key); }))); + if (opts.sync !== false) parts.push(pull.values([{ sync: true }])); + parts.push(pull(live$, pull.filter((m) => !(m && m.key && seen.has(m.key))))); + return cat(parts); +}; + +const legacy = (msg, opts = {}) => { + if (!msg || !msg.value || msg.sync) return msg; + const priv = msg.meta && msg.meta.private === true; + if (!priv) return { key: msg.key, value: msg.value, timestamp: msg.timestamp }; + if (!opts.private) return { key: msg.key, value: { ...msg.value, content: msg.meta.originalContent }, timestamp: msg.timestamp }; + return { + key: msg.key, + value: { ...msg.value, private: true, meta: { ...(msg.value.meta || {}), private: true, original: { content: msg.meta.originalContent } } }, + timestamp: msg.timestamp + }; +}; + +const isOpObject = (f) => f && typeof f === 'object' && !Array.isArray(f) && Object.keys(f).some(k => OPS.includes(k)); + +const matchValue = (v, f) => { + if (isOpObject(f)) { + for (const op of Object.keys(f)) { + const arg = f[op]; + if (op === '$gt' && !(v > arg)) return false; + if (op === '$gte' && !(v >= arg)) return false; + if (op === '$lt' && !(v < arg)) return false; + if (op === '$lte' && !(v <= arg)) return false; + if (op === '$in' && !(Array.isArray(arg) && arg.includes(v))) return false; + if (op === '$is' && typeof v !== arg) return false; + if (op === '$prefix' && !(typeof v === 'string' && v.startsWith(arg))) return false; + if (op === '$ne' && v === arg) return false; + if (op === '$truthy' && !v) return false; + if (op === '$eq' && v !== arg) return false; + } + return true; + } + if (f && typeof f === 'object' && !Array.isArray(f)) { + if (!v || typeof v !== 'object') return false; + return Object.keys(f).every(k => matchValue(v[k], f[k])); + } + if (Array.isArray(f)) return JSON.stringify(v) === JSON.stringify(f); + return v === f; +}; + +const formatter = (opts = {}) => { + if (opts.keys === false && opts.values !== false) return (m) => (m && m.sync) ? m : m.value; + if (opts.values === false) return (m) => (m && m.sync) ? m : m.key; + return (m) => m; +}; + +const matchFilter = (msg, filter) => { + if (!filter) return true; + return Object.keys(filter).every(k => k === 'dest' ? true : matchValue(msg[k], filter[k])); +}; + +const opsFor = (filter) => { + const ops = []; + const value = filter && filter.value; + const content = value && value.content; + const t = content && content.type; + if (typeof t === 'string') ops.push(type(t)); + else if (t && Array.isArray(t.$in) && t.$in.length) ops.push(or(...t.$in.map(x => type(x)))); + if (value && typeof value.author === 'string') ops.push(author(value.author)); + if (!ops.length) return null; + return ops.length === 1 ? ops[0] : and(...ops); +}; + +const filterOf = (filter) => filter ? (m) => matchFilter(m, filter) : null; + +module.exports = [ + { + manifest: { + createUserStream: 'source', + messagesByType: 'source', + links: 'source', + createLogStream: 'source', + get: 'async', + whoami: 'sync', + status: 'sync', + progress: 'sync', + rebuild: 'async' + }, + init(sbot, config) { + sbot.db.registerIndex(LinksIndex); + const linksIndex = () => sbot.db.getIndex('oasisLinks'); + + const pages = (ops, extra) => { + let seq = 0; + let size = FIRST_PAGE; + let total = Infinity; + let ended = false; + const next = (end, cb) => { + if (end) return cb(end); + if (ended || seq >= total) return cb(true); + sbot.db.query(...[ops ? where(ops) : null, ...extra, startFrom(seq), paginate(size), toCallback((err, answer) => { + if (err) return cb(err); + if (!answer || !answer.total || !answer.results || !answer.results.length) { ended = true; return cb(true); } + total = answer.total; + seq = answer.nextSeq; + size = Math.min(size * 2, LAST_PAGE); + cb(null, answer.results); + })].filter(Boolean)); + }; + return pull(next, pull.map((page) => pull.values(page)), pull.flatten()); + }; + + const query = (ops, extra, size) => size + ? sbot.db.query(...[ops ? where(ops) : null, ...extra, batch(size), toPullStream()].filter(Boolean)) + : pages(ops, extra); + + const stream = (ops, opts = {}) => { + const shape = () => pull.map(m => legacy(m, { private: opts.private })); + const boxed = () => opts.skipPrivate ? pull.filter(m => m.sync || typeof (m.value && m.value.content) !== 'string') : pull.through(); + const pass = () => opts.filter ? pull.filter(opts.filter) : pull.through(); + const limit = typeof opts.limit === 'number' && opts.limit >= 0 ? opts.limit : -1; + const order = [opts.arrival ? sortByArrival() : null, opts.reverse ? descending() : null].filter(Boolean); + const fmt = formatter(opts); + const size = limit >= 0 ? Math.max(1, Math.min(limit, FIRST_PAGE)) : undefined; + if (!opts.live) return pull(query(ops, order, size), shape(), boxed(), pass(), limit >= 0 ? pull.take(limit) : pull.through(), pull.map(fmt)); + const match = (m) => (!opts.skipPrivate || typeof (m.value && m.value.content) !== 'string') && (!opts.match || opts.match(m)) && (!opts.filter || opts.filter(m)); + const live$ = liveTap(sbot, config.keys, { private: opts.private, decryptedOnly: opts.decryptedOnly }, match); + const old$ = pull(query(ops, order, size), shape(), boxed(), pass(), limit >= 0 ? pull.take(limit) : pull.through()); + return pull(withSync(old$, live$, opts), pull.map(fmt)); + }; + + const bySeqs = (list, opts = {}) => { + const unique = [...new Set(list)]; + if (!unique.length) return pull.empty(); + return pull(query(seqs(unique), []), pull.map(m => legacy(m, { private: opts.private }))); + }; + + const linked = (dest, opts, cb) => { + sbot.db.onDrain('oasisLinks', () => { + const idx = linksIndex(); + if (!idx) return cb(null, []); + idx.getLinks(dest, (err, entries) => { + if (err) return cb(err); + const wanted = entries.filter(e => (!opts.rel || e.r === opts.rel) && (!opts.source || e.a === opts.source)); + pull(bySeqs(wanted.map(e => e.s), opts), pull.collect(cb)); + }); + }); + }; + + const liveLinked = (dest, opts) => liveTap(sbot, config.keys, { private: opts.private }, (m) => + linksOf(m.value && m.value.content).some(l => l.dest === dest && (!opts.rel || l.rel === opts.rel)) && (!opts.source || m.value.author === opts.source)); + + const sortLegacy = (arr, byTs, reverse) => { + const claimed = (m) => Number(m.value && m.value.timestamp) || 0; + const arrival = (m) => Number(m.timestamp) || 0; + const seq = (m) => Number(m.value && m.value.sequence) || 0; + arr.sort((a, b) => (byTs ? (claimed(a) - claimed(b)) : 0) || (arrival(a) - arrival(b)) || (seq(a) - seq(b))); + if (reverse) arr.reverse(); + return arr; + }; + + let drained = false; + try { sbot.db.onDrain('base', () => { drained = true; }); } catch (_) { drained = true; } + const status = () => { + const st = (sbot.db.getStatus && sbot.db.getStatus().value) || { log: 0, jit: {}, indexes: {}, progress: 1 }; + return { + ready: drained, + sync: { since: Number(st.log) || 0, plugins: { ...(st.indexes || {}) } }, + jit: { ...(st.jit || {}) }, + progress: typeof st.progress === 'number' ? st.progress : 1, + db: 'ssb-db2' + }; + }; + + const progress = () => { + const st = status(); + const target = st.sync.since; + return { indexes: { start: 0, current: Math.min(target, Math.round(st.progress * target)), target } }; + }; + + const rebuild = (cb) => { + if (typeof cb === 'function') return sbot.db.reset(cb); + return new Promise((resolve, reject) => sbot.db.reset((err) => err ? reject(err) : resolve())); + }; + + const createUserStream = (opts = {}) => { + const id = typeof opts === 'string' ? opts : opts.id; + if (!id) return pull.error(new Error('createUserStream: missing id')); + const seqOf = (m) => Number(m.value && m.value.sequence) || 0; + const filter = (opts.gt !== undefined || opts.gte !== undefined || opts.lt !== undefined || opts.lte !== undefined) + ? (m) => (opts.gt === undefined || seqOf(m) > opts.gt) && (opts.gte === undefined || seqOf(m) >= opts.gte) && (opts.lt === undefined || seqOf(m) < opts.lt) && (opts.lte === undefined || seqOf(m) <= opts.lte) + : null; + return stream(author(id), { reverse: opts.reverse === true, limit: opts.limit, live: opts.live === true, old: opts.old, sync: opts.sync, filter, match: (m) => m.value && m.value.author === id, private: opts.private === true, arrival: true, keys: opts.keys, values: opts.values }); + }; + + const messagesByType = (opts = {}) => { + const t = typeof opts === 'string' ? opts : opts.type; + if (!t) return pull.error(new Error('messagesByType: missing type')); + const o = typeof opts === 'string' ? {} : opts; + return stream(type(t), { reverse: o.reverse === true, limit: o.limit, live: o.live === true, old: o.old, sync: o.sync, skipPrivate: true, match: (m) => !!(m.value && m.value.content && m.value.content.type === t), arrival: true, keys: o.keys, values: o.values }); + }; + + const links = (opts = {}) => { + const src = defer.source(); + const toLink = (m) => opts.values ? m : { source: m.value.author, dest: opts.dest, rel: opts.rel, key: m.key }; + if (opts.dest) { + const live$ = opts.live ? liveLinked(opts.dest, { rel: opts.rel, source: opts.source }) : null; + linked(opts.dest, { rel: opts.rel, source: opts.source }, (err, arr) => { + if (err) { if (live$) live$(err, () => {}); return src.abort(err); } + const out = sortLegacy(arr, false, opts.reverse === true); + if (!live$) return src.resolve(pull(pull.values(out), pull.map(toLink))); + src.resolve(pull(withSync(pull.values(out), live$, opts), pull.map((m) => m.sync ? m : toLink(m)))); + }); + return src; + } + const ops = opts.source ? author(opts.source) : null; + src.resolve(pull( + stream(ops, { reverse: opts.reverse === true, arrival: true }), + pull.map(m => linksOf(m.value && m.value.content).filter(l => !opts.rel || l.rel === opts.rel).map(l => opts.values ? m : { source: m.value.author, dest: l.dest, rel: l.rel, key: m.key })), + pull.flatten() + )); + return src; + }; + + const createLogStream = (opts = {}) => { + const bound = (k) => opts[k] !== undefined && opts[k] !== null && Number.isFinite(Number(opts[k])); + const upper = [bound('lt') ? lt(Number(opts.lt), 'timestamp') : null, bound('lte') ? lte(Number(opts.lte), 'timestamp') : null].filter(Boolean); + const upperOps = upper.length ? (upper.length === 1 ? upper[0] : and(...upper)) : null; + const hasRange = ['gt', 'gte', 'lt', 'lte'].some(k => opts[k] !== undefined && opts[k] !== null); + const ts = (m) => Number(m && m.timestamp) || 0; + const inRange = (m) => !m || m.sync || typeof m !== 'object' || ( + (opts.gt === undefined || opts.gt === null || ts(m) > opts.gt) && + (opts.gte === undefined || opts.gte === null || ts(m) >= opts.gte) && + (opts.lt === undefined || opts.lt === null || ts(m) < opts.lt) && + (opts.lte === undefined || opts.lte === null || ts(m) <= opts.lte) + ); + const lower = Math.max(bound('gt') ? Number(opts.gt) : -Infinity, bound('gte') ? Number(opts.gte) : -Infinity); + if (lower > -Infinity && opts.live !== true) { + const floor = lower - ARRIVAL_SLACK_MS; + const src = defer.source(); + pull( + stream(upperOps, { reverse: true, arrival: true }), + pull.take((m) => !m || typeof m !== 'object' || ts(m) >= floor), + pull.filter(inRange), + pull.collect((err, arr) => { + if (err) return src.resolve(pull.error(err)); + const ordered = opts.reverse === true ? arr : arr.reverse(); + const limited = opts.limit > 0 ? ordered.slice(0, opts.limit) : ordered; + src.resolve(pull.values(limited)); + }) + ); + return src; + } + const shaped = stream(upperOps, { reverse: opts.reverse === true, limit: hasRange ? undefined : opts.limit, live: opts.live === true, old: opts.old, sync: opts.sync, arrival: true, keys: opts.keys, values: opts.values }); + if (!hasRange) return shaped; + const filtered = pull(shaped, pull.filter(inRange)); + return opts.limit > 0 ? pull(filtered, pull.take(opts.limit)) : filtered; + }; + + const get = (idOrObject, cb) => { + const isObj = !!(idOrObject && typeof idOrObject === 'object'); + const id = isObj ? idOrObject.id : idOrObject; + sbot.db.getMsg(id, (err, msg) => { + if (err) return cb(err); + const shaped = legacy(msg, { private: isObj && idOrObject.private === true }); + cb(null, isObj && idOrObject.meta ? shaped : shaped.value); + }); + }; + + const whoami = () => ({ id: sbot.id }); + + sbot._oasisDb2 = { stream, linked, liveLinked, bySeqs, sortLegacy, legacy, matchFilter, opsFor, filterOf, linksOf }; + return { createUserStream, messagesByType, links, createLogStream, get, whoami, status, progress, rebuild }; + } + }, + { + name: 'private', + version: '1.0.0', + manifest: { publish: 'async', unbox: 'sync', read: 'source' }, + init(sbot, config) { + return { + publish(content, recps, cb) { + const list = (Array.isArray(recps) ? recps : [recps]).map(r => typeof r === 'string' ? r : (r && r.link)).filter(Boolean); + if (!list.length) return cb(new Error('private.publish: no recipients')); + sbot.db.create({ content, recps: list, encryptionFormat: 'box' }, (err, kvt) => err ? cb(err) : cb(null, legacy(kvt))); + }, + unbox(msgOrData) { + if (typeof msgOrData === 'string') return ssbKeys.unbox(msgOrData, config.keys.private); + if (!msgOrData || !msgOrData.value || typeof msgOrData.value.content !== 'string') return undefined; + let plain = null; + try { plain = ssbKeys.unbox(msgOrData.value.content, config.keys.private); } catch (_) { plain = null; } + if (!plain) return undefined; + const v = msgOrData.value; + return { + key: msgOrData.key, + value: { previous: v.previous, author: v.author, sequence: v.sequence, timestamp: v.timestamp, hash: v.hash, content: plain, private: true }, + timestamp: msgOrData.timestamp + }; + }, + read(opts = {}) { + return sbot._oasisDb2.stream(isDecrypted('box'), { reverse: opts.reverse === true, limit: opts.limit, live: opts.live === true, old: opts.old, sync: opts.sync, private: true, decryptedOnly: true, arrival: true, keys: opts.keys, values: opts.values }); + } + }; + } + }, + { + name: 'activity', + version: '1.0.0', + manifest: { latest: 'async', tail: 'async' }, + init(sbot) { + sbot.db.registerIndex(ActivityIndex); + return { + latest(ids, opts, cb) { + if (typeof ids === 'function') { cb = ids; ids = null; opts = {}; } + if (typeof opts === 'function') { cb = opts; opts = {}; } + const idx = sbot.db.getIndex('oasisActivity'); + if (!idx) return cb(null, null); + let settled = false; + const settle = (ok) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (!ok && !(idx.offset.value >= 0)) return cb(null, null); + const out = {}; + if (Array.isArray(ids)) { for (const id of ids) if (idx.latest.has(id)) out[id] = idx.latest.get(id); } + else for (const [id, ts] of idx.latest) out[id] = ts; + cb(null, out); + }; + const timer = setTimeout(() => settle(false), ACTIVITY_DRAIN_MS); + const log = sbot.db.getLog(); + const read = () => { + const since = log.since.value; + if (!Number.isFinite(since)) return settle(idx.offset.value >= 0); + idx.whenReached(since, ACTIVITY_DRAIN_MS, settle); + }; + if (opts && opts.fresh === false) read(); + else log.onDrain(read); + }, + tail(limit, opts, cb) { + if (typeof limit === 'function') { cb = limit; limit = 1; opts = {}; } + if (typeof opts === 'function') { cb = opts; opts = {}; } + const want = Math.max(1, Math.min(Number(limit) || 1, RECENT_KEEP)); + const idx = sbot.db.getIndex('oasisActivity'); + if (!idx) return cb(null, null); + const log = sbot.db.getLog(); + const read = () => { + const since = log.since.value; + if (!Number.isFinite(since)) return cb(null, []); + idx.whenReached(since, ACTIVITY_DRAIN_MS, (ok) => { + const offsets = idx.recent; + if (!ok || !offsets.length || offsets[offsets.length - 1] !== since) return cb(null, null); + if (offsets.length < want && offsets[0] !== 0) return cb(null, null); + const picked = offsets.slice(-want).reverse(); + const out = new Array(picked.length); + let left = picked.length; + let failed = false; + picked.forEach((offset, i) => log.get(offset, (err, buf) => { + if (failed) return; + let m = null; + try { m = !err && buf ? bipf.decode(buf, 0) : null; } catch (_) { m = null; } + if (!m || !m.key || !m.value) { failed = true; return cb(null, null); } + out[i] = legacy(m); + if (--left === 0) cb(null, out); + })); + }); + }; + if (opts && opts.fresh === false) read(); + else log.onDrain(read); + } + }; + } + }, + { + name: 'backlinks', + version: '1.0.0', + manifest: { read: 'source' }, + init(sbot) { + return { + read(opts = {}) { + const { stream, linked, liveLinked, sortLegacy, matchFilter, opsFor } = sbot._oasisDb2; + const filter = (opts.query && opts.query[0] && opts.query[0].$filter) || {}; + const priv = opts.private === true; + const pass = (m) => matchFilter(m, filter); + if (!filter.dest) { + return stream(opsFor(filter), { reverse: opts.reverse === true, limit: opts.limit, live: opts.live === true, old: opts.old, sync: opts.sync, filter: pass, private: priv }); + } + const src = defer.source(); + const limit = typeof opts.limit === 'number' && opts.limit >= 0 ? opts.limit : -1; + const fmt = formatter(opts); + const live$ = opts.live ? pull(liveLinked(filter.dest, { private: priv }), pull.filter(pass)) : null; + const finish = (arr) => { + let out = sortLegacy(arr.filter(pass), opts.index === 'DTA', opts.reverse === true); + if (limit >= 0) out = out.slice(0, limit); + if (!live$) return src.resolve(pull(pull.values(out), pull.map(fmt))); + src.resolve(pull(withSync(pull.values(out), live$, opts), pull.map(fmt))); + }; + if (opts.old === false && opts.live) return finish([]), src; + linked(filter.dest, { private: priv }, (err, arr) => { + if (!err) return finish(arr); + if (live$) live$(err, () => {}); + src.abort(err); + }); + return src; + } + }; + } + }, + { + name: 'query', + version: '1.0.0', + manifest: { read: 'source' }, + init(sbot) { + return { + read(opts = {}) { + const { stream, opsFor, filterOf } = sbot._oasisDb2; + const filter = (opts.query && opts.query[0] && opts.query[0].$filter) || null; + return stream(opsFor(filter), { reverse: opts.reverse === true, limit: opts.limit, live: opts.live === true, old: opts.old, sync: opts.sync, filter: filterOf(filter), skipPrivate: true, keys: opts.keys, values: opts.values }); + } + }; + } + }, + { + name: 'search', + version: '1.0.0', + manifest: { query: 'source' }, + init(sbot) { + const textOf = (c) => ['text', 'title', 'description', 'body', 'name', 'summary'].map(k => c && typeof c[k] === 'string' ? c[k] : '').join('\n').toLowerCase(); + return { + query(opts = {}) { + const needle = String(opts.query || '').trim().toLowerCase(); + if (!needle) return pull.empty(); + const terms = needle.split(/\s+/).filter(Boolean); + const limit = typeof opts.limit === 'number' && opts.limit > 0 ? opts.limit : SEARCH_LIMIT; + return sbot._oasisDb2.stream(null, { + reverse: opts.reverse !== false, + limit, + skipPrivate: true, + arrival: true, + filter: (m) => { const c = m.value && m.value.content; if (!c || typeof c !== 'object') return false; const hay = textOf(c); return terms.every(t => hay.includes(t)); } + }); + } + }; + } + }, + { + name: 'tangle', + version: '1.0.0', + manifest: { branch: 'async' }, + init(sbot) { + return { + branch(rootKey, cb) { + if (typeof rootKey !== 'string' || !rootKey.startsWith('%')) return cb(new Error('tangle.branch requires a message key')); + sbot._oasisDb2.linked(rootKey, {}, (err, arr) => { + if (err) return cb(err); + const thread = arr.filter(m => m.value && m.value.content && typeof m.value.content === 'object' && m.value.content.root === rootKey); + cb(null, heads([{ key: rootKey }, ...thread])); + }); + } + }; + } + } +]; + +module.exports.LinksIndex = LinksIndex; +module.exports.legacy = legacy; +module.exports.matchFilter = matchFilter; diff --git a/src/server/lanRouter.js b/src/server/lanRouter.js new file mode 100644 index 0000000..a4c6079 --- /dev/null +++ b/src/server/lanRouter.js @@ -0,0 +1,97 @@ +const pull = require('./node_modules/pull-stream'); +const Ref = require('./node_modules/ssb-ref'); +const fs = require('fs'); +const path = require('path'); + +const staged = new Set(); + +function readOasisConfig() { + try { + const dir = process.env.OASIS_STATE_DIR || process.env.ssb_path || path.join(require('os').homedir(), '.ssb'); + const p = [path.join(dir, 'oasis', 'oasis-config.json'), path.join(dir, 'oasis-config.json')].find(f => fs.existsSync(f)); + return JSON.parse(fs.readFileSync(p || path.join(__dirname, '..', 'configs', 'oasis-config.json'), 'utf8')) || {}; + } catch (_) { return {}; } +} + +function stagePeer(ssb, address, key, eagerReplicate) { + if (!address || !key || key === ssb.id) return; + if (staged.has(address)) return; + staged.add(address); + let routed = false; + try { + if (ssb.conn && typeof ssb.conn.stage === 'function') { + ssb.conn.stage(address, { type: 'lan', key }); + routed = true; + } + } catch (_) {} + try { require('../models/viewer_filters').rememberLanPeers([key]); } catch (_) {} + if (!routed) { + try { + if (ssb.gossip && typeof ssb.gossip.add === 'function') { + ssb.gossip.add(address, 'local'); + } + } catch (_) {} + } + if (eagerReplicate) { + try { + if (ssb.ebt && typeof ssb.ebt.request === 'function') { + ssb.ebt.request(key, true); + } + } catch (_) {} + try { + if (ssb.replicate && typeof ssb.replicate.request === 'function') { + ssb.replicate.request(key, true); + } + } catch (_) {} + } +} + +function handleDiscovery(ssb, d, opts) { + if (!d || !d.address) return; + if (!d.verified && !opts.acceptUnverified) return; + let key = null; + try { key = Ref.getKeyFromAddress(d.address); } catch (_) {} + if (key) stagePeer(ssb, d.address, key, opts.eagerReplicate); +} + +function guardLan(ssb, isEnabled) { + const lan = ssb && ssb.lan; + if (!lan || typeof lan.start !== 'function' || lan.oasisGuarded) return false; + const start = lan.start.bind(lan); + lan.start = (...args) => { + if (!isEnabled()) return undefined; + try { return start(...args); } catch (_) { return undefined; } + }; + lan.oasisGuarded = true; + if (!isEnabled() && typeof lan.stop === 'function') { try { lan.stop(); } catch (_) {} } + return true; +} + +function startRouter(ssb, opts, isEnabled) { + if (!ssb.lan || typeof ssb.lan.discoveredPeers !== 'function') return; + if (isEnabled()) { try { ssb.lan.start(); } catch (_) {} } + else if (typeof ssb.lan.stop === 'function') { try { ssb.lan.stop(); } catch (_) {} } + pull( + ssb.lan.discoveredPeers(), + pull.drain(d => handleDiscovery(ssb, d, opts), () => {}) + ); +} + +module.exports = { + name: 'lanRouter', + version: '1.1.0', + manifest: {}, + init(ssb, config) { + const lanCfg = (config && config.lan) || {}; + const opts = { + acceptUnverified: lanCfg.acceptUnverified === true, + eagerReplicate: lanCfg.eagerReplicate === true + }; + const isEnabled = () => { const c = readOasisConfig(); return c.lanBroadcasting !== false && c.networkPaused !== true; }; + guardLan(ssb, isEnabled); + setImmediate(() => startRouter(ssb, opts, isEnabled)); + return {}; + } +}; + +module.exports.guardLan = guardLan; diff --git a/src/server/network_pause.js b/src/server/network_pause.js new file mode 100644 index 0000000..9b92677 --- /dev/null +++ b/src/server/network_pause.js @@ -0,0 +1,210 @@ +const fs = require('fs'); +const path = require('path'); + +const readOasisConfig = () => { + try { + const dir = process.env.OASIS_STATE_DIR || process.env.ssb_path || path.join(require('os').homedir(), '.ssb'); + const p = [path.join(dir, 'oasis', 'oasis-config.json'), path.join(dir, 'oasis-config.json')].find(f => fs.existsSync(f)); + return JSON.parse(fs.readFileSync(p || path.join(__dirname, '..', 'configs', 'oasis-config.json'), 'utf8')) || {}; + } catch (_) { return {}; } +}; + +module.exports = { + name: 'networkPause', + version: '1.0.0', + manifest: { pause: 'sync', resume: 'sync', paused: 'sync', lastErrors: 'sync' }, + init(server, config) { + server.oasisNetworkPaused = readOasisConfig().networkPaused === true || process.env.OASIS_NETWORK_PAUSED === '1'; + const refused = () => new Error('network paused'); + + if (server.auth && typeof server.auth.hook === 'function') { + server.auth.hook(function (fn, args) { + const cb = args[args.length - 1]; + if (server.oasisNetworkPaused && args[0] !== server.id && typeof cb === 'function') return cb(refused()); + return fn.apply(this, args); + }); + } + if (server.connect && typeof server.connect.hook === 'function') { + server.connect.hook(function (fn, args) { + const cb = args[args.length - 1]; + if (server.oasisNetworkPaused && typeof cb === 'function') return cb(refused()); + return fn.apply(this, args); + }); + } + + const closeAll = () => { + try { for (const [addr] of server.conn.query().peersInConnection()) server.conn.disconnect(addr, () => {}); } catch (_) {} + for (const id of Object.keys(server.peers || {})) { + if (id === server.id) continue; + for (const rpc of (server.peers[id] || []).slice()) { try { rpc.close(true, () => {}); } catch (_) {} } + } + }; + + const lastErrors = new Map(); + const keyOf = (ev) => { + const { canonicalKey } = require('../models/peer_health'); + const m = String((ev && ev.address) || '').match(/~shs:([A-Za-z0-9+/=_-]{43,44})/); + return canonicalKey((ev && ev.key) || (m ? m[1] : '')); + }; + const majorMinor = (v) => String(v || '').split('.').slice(0, 2).map(n => parseInt(n, 10) || 0); + const ownVersion = (() => { try { return String(require('./package.json').version || ''); } catch (_) { return ''; } })(); + const upToDate = (v) => { + const [a1, b1] = majorMinor(v), [a2, b2] = majorMinor(ownVersion); + return a1 > a2 || (a1 === a2 && b1 >= b2); + }; + const rank = { versions: new Map(), graph: {} }; + const refreshRank = () => { + const pull = require('pull-stream'); + const latest = new Map(); + const done = () => { + rank.versions = new Map([...latest].map(([k, m]) => [k, m.version])); + try { server.friends.graph((err, g) => { if (!err && g) rank.graph = g; }); } catch (_) {} + }; + try { + if (typeof server.messagesByType !== 'function') return done(); + pull(server.messagesByType({ type: 'oasisVersion' }), pull.drain((m) => { + const author = m && m.value && m.value.author; + const c = m && m.value && m.value.content; + if (!author || !c || typeof c.version !== 'string') return; + const ts = Number(m.value.timestamp) || 0; + const prev = latest.get(author); + if (!prev || prev.ts <= ts) latest.set(author, { version: c.version, ts }); + }, done)); + } catch (_) { done(); } + }; + const peerRank = (peer) => { + const key = peer && peer[1] && peer[1].key; + if (!key) return 0; + const follows = rank.graph[key] || {}; + const mine = rank.graph[server.id] || {}; + let total = 0, shared = 0; + for (const [k, v] of Object.entries(follows)) { + if (!(v >= 0) || k === key) continue; + total++; + if (mine[k] >= 0) shared++; + } + const v = rank.versions.get(key); + const tier = v ? (upToDate(v) ? 2 : 0) : (total > 0 ? 0 : 1); + return tier * 1e9 + Math.min(shared, 99999) * 1e4 + Math.min(total, 9999); + }; + server.oasisPeerRank = peerRank; + setImmediate(refreshRank); + const rankTimer = setInterval(refreshRank, 10 * 60 * 1000); + if (rankTimer.unref) rankTimer.unref(); + const fallbackAt = new Map(); + const tryOtherAddress = (key, failed, reason) => { + if (reason === 'paused' || server.oasisNetworkPaused) return; + if (Array.isArray(server.peers && server.peers[key]) && server.peers[key].length) return; + const now = Date.now(); + if (now - (fallbackAt.get(key) || 0) < 10 * 60 * 1000) return; + const { canonicalKey } = require('../models/peer_health'); + let entries = []; + try { entries = server.conn.dbPeers(); } catch (_) { return; } + const failedOnion = /^onion:/.test(String(failed || '')); + const alt = entries.find(([addr, data]) => addr && addr !== failed && data && canonicalKey(data.key) === key && /^onion:/.test(addr) !== failedOnion); + if (!alt) return; + fallbackAt.set(key, now); + try { server.conn.connect(alt[0], { ...(alt[1] || {}) }, () => {}); } catch (_) {} + }; + let listening = false; + const listen = () => { + if (listening) return; + try { + const { classifyNetError } = require('../models/peer_health'); + const hub = server.conn && typeof server.conn.hub === 'function' ? server.conn.hub() : null; + const stream = hub && typeof hub.listen === 'function' ? hub.listen() : null; + if (!stream) return; + listening = true; + require('pull-stream')(stream, require('pull-stream').drain((ev) => { + const key = ev && keyOf(ev); + if (!key) return; + if (ev.type === 'connected') lastErrors.delete(key); + else if (ev.type === 'connecting-failed') { + const reason = classifyNetError(ev.details, ev.address); + lastErrors.set(key, { reason, at: Date.now() }); + if (lastErrors.size > 500) lastErrors.delete(lastErrors.keys().next().value); + tryOtherAddress(key, ev.address, reason); + } + }, () => { listening = false; })); + } catch (_) { listening = false; } + }; + const listenTimer = setInterval(() => { listen(); if (listening) clearInterval(listenTimer); }, 1000); + if (listenTimer.unref) listenTimer.unref(); + + const readList = (file) => { try { const a = JSON.parse(fs.readFileSync(file, 'utf8')); return Array.isArray(a) ? a : []; } catch (_) { return []; } }; + const syncPeerBook = () => { + if (!server.conn || typeof server.conn.dbPeers !== 'function' || !config || !config.path) return; + const { canonicalKey } = require('../models/peer_health'); + let forgottenFile = null; + try { forgottenFile = require('../configs/state-manager').statePath('gossip_unfollowed.json'); } catch (_) {} + const forgotten = new Set((forgottenFile ? readList(forgottenFile) : []).map(e => e && canonicalKey(e.key)).filter(Boolean)); + let entries = []; + try { entries = server.conn.dbPeers(); } catch (_) { return; } + for (const [addr, data] of entries) { + const m = String(addr).match(/^(.*~shs:)([A-Za-z0-9+/]{43})$/); + if (!m) continue; + const padded = `${m[1]}${m[2]}=`; + try { + if (!server.conn.db().has(padded)) server.conn.remember(padded, { ...(data || {}) }); + else if (data && data.autoconnect === true && typeof server.conn.db().update === 'function') server.conn.db().update(padded, { autoconnect: true }); + server.conn.forget(addr); + } catch (_) {} + } + try { entries = server.conn.dbPeers(); } catch (_) { return; } + for (const [addr, data] of entries) { + const key = data && canonicalKey(data.key); + if (key && forgotten.has(key)) { try { server.conn.forget(addr); } catch (_) {} } + } + const chosen = new Map(); + for (const p of readList(path.join(config.path, 'gossip.json'))) { + const key = p && canonicalKey(p.key); + const host = p ? String(p.host || '').trim() : ''; + if (!key || key === server.id || forgotten.has(key) || !host) continue; + const onion = /\.onion$/i.test(host); + const prev = chosen.get(key); + if (!prev || (prev.onion && !onion)) chosen.set(key, { host, port: Number(p.port) || 8008, onion }); + } + for (const [key, p] of chosen) { + const addr = `${p.onion ? 'onion' : 'net'}:${p.host}:${p.port}~shs:${key.slice(1, -8)}`; + try { if (!server.conn.db().has(addr)) server.conn.remember(addr, { key, type: 'pub', autoconnect: true }); } catch (_) {} + } + const seeds = [].concat(config.seeds || [], (config.connections && config.connections.seeds) || []); + for (const seed of seeds) { + const addr = String(seed || '').trim(); + const m = addr.match(/~shs:([A-Za-z0-9+/]{43}=?)$/); + const key = m && canonicalKey(m[1]); + if (!key || key === server.id || forgotten.has(key)) continue; + try { if (!server.conn.db().has(addr)) server.conn.remember(addr, { key, type: 'pub', autoconnect: true }); } catch (_) {} + } + if (config.pub && typeof server.conn.db().update === 'function') { + try { entries = server.conn.dbPeers(); } catch (_) { return; } + for (const [addr, data] of entries) { + const key = data && canonicalKey(data.key); + if (!key || key === server.id || forgotten.has(key) || data.type !== 'pub' || data.autoconnect !== false) continue; + try { server.conn.db().update(addr, { autoconnect: true }); } catch (_) {} + } + } + }; + for (const ms of [5000, 30000]) { const t = setTimeout(syncPeerBook, ms); if (t.unref) t.unref(); } + const bookTimer = setInterval(syncPeerBook, 10 * 60 * 1000); + if (bookTimer.unref) bookTimer.unref(); + + return { + lastErrors() { return Object.fromEntries(lastErrors); }, + pause() { + server.oasisNetworkPaused = true; + try { server.conn.stop(); } catch (_) {} + try { if (server.lan && typeof server.lan.stop === 'function') server.lan.stop(); } catch (_) {} + closeAll(); + return true; + }, + resume() { + server.oasisNetworkPaused = false; + try { server.conn.start(); } catch (_) {} + try { if (server.lan && typeof server.lan.start === 'function') server.lan.start(); } catch (_) {} + return false; + }, + paused() { return !!server.oasisNetworkPaused; } + }; + } +}; diff --git a/src/server/package-lock.json b/src/server/package-lock.json index 099f6b7..cdd8f3f 100644 --- a/src/server/package-lock.json +++ b/src/server/package-lock.json @@ -1,32 +1,27 @@ { "name": "@krakenslab/oasis", - "version": "0.8.9", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@krakenslab/oasis", - "version": "0.7.8", + "version": "1.2.3", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { "@koa/router": "^13.1.0", - "@open-rpc/client-js": "^1.8.1", - "@xenova/transformers": "^2.17.2", - "abstract-level": "^2.0.1", "archiver": "^7.0.1", "axios": "^1.10.0", - "cors": "^2.8.5", + "bencode": "^2.0.3", + "bipf": "^1.9.0", + "chloride": "^2.4.1", "debug": "^4.3.1", "dompurify": "^3.3.1", "env-paths": "^2.2.1", - "epidemic-broadcast-trees": "^9.0.4", - "express": "^5.1.0", "file-type": "^16.5.4", "highlight.js": "11.0.0", "hyperaxe": "^2.0.1", - "ip": "https://registry.npmjs.org/neoip/-/neoip-3.0.0.tgz", - "is-svg": "^4.4.0", "jsdom": "^28.0.0", "koa": "^2.7.0", "koa-body": "^6.0.1", @@ -36,90 +31,58 @@ "minimist": "^1.2.8", "module-alias": "^2.2.3", "moment": "^2.30.1", - "multiblob": "^1.13.0", - "multiserver": "^3.3.1", - "multiserver-address": "^1.0.1", - "muxrpc": "^8.0.0", - "muxrpc-validation": "^3.0.2", - "muxrpcli": "^3.1.2", - "node-llama-cpp": "^3.10.0", - "non-private-ip": "^2.2.0", "open": "^8.4.2", "openpgp": "^6.3.0", - "packet-stream": "^2.0.6", - "packet-stream-codec": "^1.2.0", - "pdfjs-dist": "^5.2.133", + "opusscript": "^0.1.1", "pretty-ms": "^7.0.1", "pull-abortable": "^4.1.1", "pull-cat": "~1.1.5", - "pull-file": "^1.0.0", - "pull-many": "~1.0.6", + "pull-defer": "^0.2.3", + "pull-level": "^2.0.4", "pull-paramap": "^1.2.2", "pull-pushable": "^2.2.0", "pull-sort": "^1.0.2", "pull-stream": "^3.7.0", - "punycode.js": "^2.3.1", "qrcode": "^1.5.4", "secret-stack": "^6.3.1", - "ssb-about": "^2.0.1", "ssb-autofollow": "^1.1.0", - "ssb-backlinks": "^2.1.1", "ssb-blobs": "^2.0.1", "ssb-box": "^1.0.1", "ssb-caps": "^1.0.1", + "ssb-classic": "^1.1.0", "ssb-client": "^4.9.0", "ssb-config": "^3.4.4", "ssb-conn": "6.0.3", - "ssb-conn-db": "^1.0.5", - "ssb-conn-hub": "^1.2.0", - "ssb-conn-staging": "^1.0.0", - "ssb-db": "^20.4.1", - "ssb-device-address": "^1.1.6", + "ssb-db2": "^6.3.3", "ssb-ebt": "^9.1.2", - "ssb-friend-pub": "^1.0.7", "ssb-friends": "^5.0.0", - "ssb-gossip": "^1.1.1", "ssb-invite": "^3.0.3", "ssb-invite-client": "^1.3.3", "ssb-keys": "^8.0.0", "ssb-lan": "^1.0.0", - "ssb-legacy-conn": "^1.0.17", - "ssb-links": "^3.0.10", "ssb-logging": "^1.0.0", - "ssb-markdown": "^3.6.0", "ssb-master": "^1.0.3", - "ssb-meme": "^1.1.0", "ssb-mentions": "^0.5.2", "ssb-msgs": "^5.2.0", "ssb-no-auth": "^1.0.0", "ssb-onion": "^1.0.0", - "ssb-partial-replication": "^3.0.1", - "ssb-plugins": "^1.0.2", - "ssb-private": "^1.1.0", - "ssb-query": "^2.4.5", "ssb-ref": "^2.16.0", "ssb-replication-scheduler": "^3.0.0", - "ssb-search": "^1.3.0", - "ssb-server": "file:packages/ssb-server", - "ssb-tangle": "^1.0.1", + "ssb-sort": "^1.1.3", "ssb-thread-schema": "^1.1.1", - "ssb-threads": "^10.0.4", "ssb-unix-socket": "^1.0.0", - "ssb-ws": "^6.2.3", - "util": "^0.12.5", + "ssb-validate": "^4.1.4", + "stream-to-pull-stream": "^1.7.3", + "telegram": "^2.26.22", "yargs": "^17.7.2" }, "bin": { "oasis": "npm run start" }, - "devDependencies": { - "nodemon": "^3.1.0" - }, "engines": { "node": "^10.0.0 || >=12.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2", "sharp": "^0.33.5" } }, @@ -131,6 +94,8 @@ }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "license": "MIT", "engines": { "node": ">=10" @@ -189,177 +154,11 @@ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "license": "MIT" }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } + "node_modules/@cryptography/aes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@cryptography/aes/-/aes-0.1.1.tgz", + "integrity": "sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg==", + "license": "GPL-3.0-or-later" }, "node_modules/@csstools/color-helpers": { "version": "6.0.1", @@ -506,36 +305,71 @@ }, "node_modules/@hapi/bourne": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", "license": "BSD-3-Clause" }, - "node_modules/@huggingface/jinja": { - "version": "0.5.5", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { + "node_modules/@img/sharp-libvips-linux-arm64": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", "cpu": [ - "x64" + "arm64" ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], + "libc": [ + "glibc" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "libc": [ + "musl" + ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", "cpu": [ "x64" ], "license": "LGPL-3.0-or-later", - "optional": true, "os": [ "linux" ], @@ -543,13 +377,39 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, "node_modules/@img/sharp-linux-x64": { "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", "cpu": [ "x64" ], "license": "Apache-2.0", - "optional": true, "os": [ "linux" ], @@ -563,13 +423,39 @@ "@img/sharp-libvips-linux-x64": "1.0.4" } }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "libc": [ + "musl" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, "node_modules/@img/sharp-linuxmusl-x64": { "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", "cpu": [ "x64" ], "license": "Apache-2.0", - "optional": true, "os": [ "linux" ], @@ -585,6 +471,8 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -600,6 +488,8 @@ }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", "engines": { "node": ">=12" @@ -610,6 +500,8 @@ }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -621,59 +513,10 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@koa/router": { "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", + "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", "license": "MIT", "dependencies": { "http-errors": "^2.0.0", @@ -684,91 +527,10 @@ "node": ">= 18" } }, - "node_modules/@kwsites/file-exists": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1" - } - }, - "node_modules/@kwsites/promise-deferred": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", - "license": "MIT" - }, - "node_modules/@ljharb/resumer": { - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@ljharb/through": "^2.3.9" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/@ljharb/through": { - "version": "2.3.14", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/@napi-rs/canvas": { - "version": "0.1.70", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.70", - "@napi-rs/canvas-darwin-arm64": "0.1.70", - "@napi-rs/canvas-darwin-x64": "0.1.70", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.70", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.70", - "@napi-rs/canvas-linux-arm64-musl": "0.1.70", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.70", - "@napi-rs/canvas-linux-x64-gnu": "0.1.70", - "@napi-rs/canvas-linux-x64-musl": "0.1.70", - "@napi-rs/canvas-win32-x64-msvc": "0.1.70" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.70", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.70", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -777,521 +539,10 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@node-llama-cpp/linux-arm64": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/linux-arm64/-/linux-arm64-3.16.2.tgz", - "integrity": "sha512-CxzgPsS84wL3W5sZRgxP3c9iJKEW+USrak1SmX6EAJxW/v9QGzehvT6W/aR1FyfidiIyQtOp3ga0Gg/9xfJPGw==", - "cpu": [ - "arm64", - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/linux-armv7l": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/linux-armv7l/-/linux-armv7l-3.16.2.tgz", - "integrity": "sha512-9G6W/MkQ/DLwGmpcj143NQ50QJg5gQZfzVf5RYx77VczBqhgwkgYHILekYrOs4xanOeqeJ8jnOnQQSp1YaJZUg==", - "cpu": [ - "arm", - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/linux-x64": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/linux-x64/-/linux-x64-3.16.2.tgz", - "integrity": "sha512-OXYf8rVfoDyvN+YrfKk8F9An9a5GOxVIM8OcR1U911tc0oRNf8yfJrQ8KrM75R26gwq0Y6YZwVTP0vRCInwWOw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/linux-x64-cuda": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/linux-x64-cuda/-/linux-x64-cuda-3.16.2.tgz", - "integrity": "sha512-LTBQFqjin7tyrLNJz0XWTB5QAHDsZV71/qiiRRjXdBKSZHVVaPLfdgxypGu7ggPeBNsv+MckRXdlH5C7yMtE4A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/linux-x64-cuda-ext": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/linux-x64-cuda-ext/-/linux-x64-cuda-ext-3.16.2.tgz", - "integrity": "sha512-47d9myCJauZyzAlN7IK1eIt/4CcBMslF+yHy4q+yJotD/RV/S6qRpK2kGn+ybtdVjkPGNCoPkHKcyla9iIVjbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/linux-x64-vulkan": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/linux-x64-vulkan/-/linux-x64-vulkan-3.16.2.tgz", - "integrity": "sha512-HDLAw4ZhwJuhKuF6n4x520yZXAQZahUOXtCGvPubjfpmIOElKrfDvCVlRsthAP0JwcwINzIQlVys3boMIXfBgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/mac-arm64-metal": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/mac-arm64-metal/-/mac-arm64-metal-3.16.2.tgz", - "integrity": "sha512-nEZ74qB0lUohF88yR741YUrUqz/qD+FJFzUTHj0FwxAynSZCjvwtzEDtavRlh3qd3yLD/0ChNn00/RQ54ISImw==", - "cpu": [ - "arm64", - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/mac-x64": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/mac-x64/-/mac-x64-3.16.2.tgz", - "integrity": "sha512-BjA+DgeDt+kRxVMV6kChb9XVXm7U5b90jUif7Z/s6ZXtOOnV6exrTM2W09kbSqAiNhZmctcVY83h2dwNTZ/yIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/win-arm64": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/win-arm64/-/win-arm64-3.16.2.tgz", - "integrity": "sha512-XHNFQzUjYODtkZjIn4NbQVrBtGB9RI9TpisiALryqfrIqagQmjBh6dmxZWlt5uduKAfT7M2/2vrABGR490FACA==", - "cpu": [ - "arm64", - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/win-x64": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/win-x64/-/win-x64-3.16.2.tgz", - "integrity": "sha512-etrivzbyLNVhZlUosFW8JSL0OSiuKQf9qcI3dNdehD907sHquQbBJrG7lXcdL6IecvXySp3oAwCkM87VJ0b3Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/win-x64-cuda": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/win-x64-cuda/-/win-x64-cuda-3.16.2.tgz", - "integrity": "sha512-jStDELHrU3rKQMOk5Hs5bWEazyjE2hzHwpNf6SblOpaGkajM/HJtxEZoL0mLHJx5qeXs4oOVkr7AzuLy0WPpNA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/win-x64-cuda-ext": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/win-x64-cuda-ext/-/win-x64-cuda-ext-3.16.2.tgz", - "integrity": "sha512-sdv4Kzn9bOQWNBRvw6B/zcn8dQRfZhjIHv5AfDBIOfRlSCgjebFpBeYUoU4wZPpjr3ISwcqO5MEWsw+AbUdV3Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@node-llama-cpp/win-x64-vulkan": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@node-llama-cpp/win-x64-vulkan/-/win-x64-vulkan-3.16.2.tgz", - "integrity": "sha512-9xuHFCOhCQjZgQSFrk79EuSKn9nGWt/SAq/3wujQSQLtgp8jGdtZgwcmuDUoemInf10en2dcOmEt7t8dQdC3XA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@octokit/app": { - "version": "16.1.2", - "license": "MIT", - "dependencies": { - "@octokit/auth-app": "^8.1.2", - "@octokit/auth-unauthenticated": "^7.0.3", - "@octokit/core": "^7.0.6", - "@octokit/oauth-app": "^8.0.3", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/types": "^16.0.0", - "@octokit/webhooks": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-app": { - "version": "8.2.0", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "toad-cache": "^3.7.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "9.0.3", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "8.0.3", - "license": "MIT", - "dependencies": { - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-token": { - "version": "6.0.0", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "7.0.3", - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/core": { - "version": "7.0.6", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/endpoint": { - "version": "11.0.2", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/graphql": { - "version": "9.0.3", - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "8.0.3", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.2", - "@octokit/auth-oauth-user": "^6.0.1", - "@octokit/auth-unauthenticated": "^7.0.2", - "@octokit/core": "^7.0.5", - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/oauth-methods": "^6.0.1", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "8.0.0", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "license": "MIT" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "12.1.0", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "6.0.0", - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "17.0.0", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "8.0.3", - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=7" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "11.0.3", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": "^7.0.0" - } - }, - "node_modules/@octokit/request": { - "version": "10.0.7", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.2", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "fast-content-type-parse": "^3.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/request-error": { - "version": "7.1.0", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/types": { - "version": "16.0.0", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/webhooks": { - "version": "14.2.0", - "license": "MIT", - "dependencies": { - "@octokit/openapi-webhooks-types": "12.1.0", - "@octokit/request-error": "^7.0.0", - "@octokit/webhooks-methods": "^6.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/webhooks-methods": { - "version": "6.0.0", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@open-rpc/client-js": { - "version": "1.8.1", - "license": "Apache-2.0", - "dependencies": { - "isomorphic-fetch": "^3.0.0", - "isomorphic-ws": "^5.0.0", - "strict-event-emitter-types": "^2.0.0", - "ws": "^7.0.0" - } - }, "node_modules/@paralleldrive/cuid2": { "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", "license": "MIT", "dependencies": { "@noble/hashes": "^1.1.5" @@ -1299,130 +550,17 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", - "optional": true, "engines": { "node": ">=14" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause" - }, - "node_modules/@reflink/reflink": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@reflink/reflink/-/reflink-0.1.19.tgz", - "integrity": "sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@reflink/reflink-darwin-arm64": "0.1.19", - "@reflink/reflink-darwin-x64": "0.1.19", - "@reflink/reflink-linux-arm64-gnu": "0.1.19", - "@reflink/reflink-linux-arm64-musl": "0.1.19", - "@reflink/reflink-linux-x64-gnu": "0.1.19", - "@reflink/reflink-linux-x64-musl": "0.1.19", - "@reflink/reflink-win32-arm64-msvc": "0.1.19", - "@reflink/reflink-win32-x64-msvc": "0.1.19" - } - }, - "node_modules/@reflink/reflink-linux-x64-gnu": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@reflink/reflink-linux-x64-gnu/-/reflink-linux-x64-gnu-0.1.19.tgz", - "integrity": "sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@reflink/reflink-linux-x64-musl": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@reflink/reflink-linux-x64-musl/-/reflink-linux-x64-musl-0.1.19.tgz", - "integrity": "sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@sammacbeth/random-access-idb-mutable-file": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@sammacbeth/random-access-idb-mutable-file/-/random-access-idb-mutable-file-0.1.1.tgz", + "integrity": "sha512-jHnpuu2qtFgwCmhgrpCCk3/hU3XqXTqhidh4XmcTijkVsGwh1c2T0+r2hkHs1PRfsxeimx8qDAotphpRoYB2eg==", "license": "MIT", "dependencies": { "buffer": "5.1.0", @@ -1431,6 +569,8 @@ }, "node_modules/@sammacbeth/random-access-idb-mutable-file/node_modules/buffer": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", + "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", "license": "MIT", "dependencies": { "base64-js": "^1.0.2", @@ -1439,28 +579,17 @@ }, "node_modules/@sammacbeth/random-access-idb-mutable-file/node_modules/random-access-storage": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-1.3.0.tgz", + "integrity": "sha512-pdS9Mcb9TB7oICypPRALlheaSuszuAKmLVEPKJMuYor7R/zDuHh5ALuQoS+ox31XRwQUL+tDwWH2GPdyspwelA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3" } }, - "node_modules/@simple-git/args-pathspec": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", - "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", - "license": "MIT" - }, - "node_modules/@simple-git/argv-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", - "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", - "license": "MIT", - "dependencies": { - "@simple-git/args-pathspec": "^1.0.3" - } - }, "node_modules/@tangle/graph": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tangle/graph/-/graph-3.2.0.tgz", + "integrity": "sha512-I92Prubc9XVRmMZkshMr2u+REuwqhDQYcFtZuHmY8yP6TVpeDmWRUZY5AKCWYdeA58igTPUbPCpvLT9iRXiRBA==", "license": "LGPL-3.0-only", "dependencies": { "lodash.clone": "^4.5.0", @@ -1469,6 +598,8 @@ }, "node_modules/@tangle/overwrite-fields": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tangle/overwrite-fields/-/overwrite-fields-2.0.3.tgz", + "integrity": "sha512-0VKHs1czriZOpVgc1aZve3lwpwgw+1+/P/wis2hH3FbACbfIYByIU1EgykVKeu/HbE3nRWLip83llFbr6Rjq4A==", "license": "LGPL-3.0-or-later", "dependencies": { "is-my-json-valid": "^2.20.5", @@ -1477,6 +608,8 @@ }, "node_modules/@tangle/reduce": { "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@tangle/reduce/-/reduce-5.0.5.tgz", + "integrity": "sha512-+GRGgvzt57fZjyGCamEc3uyh8onHlHYxuFF4HhRY4WDk9aky+WUBa8YSeFKANnGpBrptdzQuo3+VFIYucbpS3w==", "license": "LGPL-3.0-only", "dependencies": { "@tangle/graph": "^3.2.0" @@ -1484,42 +617,33 @@ }, "node_modules/@tangle/strategy": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tangle/strategy/-/strategy-4.1.2.tgz", + "integrity": "sha512-P5qGsPuAMqZx/zJA20nwpqD3A5aGDnmjEbp221jRTClI+NSU/spcqkryOP7iH8UCRWiuzjWHA9To5ymw/xOsCA==", "license": "LGPL-3.0-only", "dependencies": { "is-my-json-valid": "^2.20.5", "lodash.isequal": "^4.5.0" } }, - "node_modules/@tinyhttp/content-disposition": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.4.tgz", - "integrity": "sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==", - "license": "MIT", - "engines": { - "node": ">=12.17.0" - }, - "funding": { - "type": "individual", - "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" - } - }, "node_modules/@tokenizer/token": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, "node_modules/@types/accepts": { "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", "license": "MIT", "dependencies": { "@types/node": "*" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.160", - "license": "MIT" - }, "node_modules/@types/body-parser": { "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -1528,6 +652,8 @@ }, "node_modules/@types/co-body": { "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", + "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -1536,6 +662,8 @@ }, "node_modules/@types/connect": { "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -1543,10 +671,14 @@ }, "node_modules/@types/content-disposition": { "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", "license": "MIT" }, "node_modules/@types/cookies": { "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -1557,6 +689,8 @@ }, "node_modules/@types/express": { "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -1566,6 +700,8 @@ }, "node_modules/@types/express-serve-static-core": { "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -1576,6 +712,8 @@ }, "node_modules/@types/formidable": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", + "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -1583,18 +721,26 @@ }, "node_modules/@types/http-assert": { "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, "node_modules/@types/keygrip": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", "license": "MIT" }, "node_modules/@types/koa": { "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", "license": "MIT", "dependencies": { "@types/accepts": "*", @@ -1609,23 +755,23 @@ }, "node_modules/@types/koa-compose": { "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", "license": "MIT", "dependencies": { "@types/koa": "*" } }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "license": "MIT" }, "node_modules/@types/node": { "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1633,14 +779,20 @@ }, "node_modules/@types/qs": { "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -1649,6 +801,8 @@ }, "node_modules/@types/serve-static": { "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -1660,63 +814,12 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, - "node_modules/@xenova/transformers": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz", - "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==", - "license": "Apache-2.0", - "dependencies": { - "@huggingface/jinja": "^0.2.2", - "onnxruntime-web": "1.14.0", - "sharp": "^0.32.0" - }, - "optionalDependencies": { - "onnxruntime-node": "1.14.0" - } - }, - "node_modules/@xenova/transformers/node_modules/@huggingface/jinja": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz", - "integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@xenova/transformers/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "license": "MIT" }, - "node_modules/@xenova/transformers/node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/abort-controller": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -1725,23 +828,10 @@ "node": ">=6.5" } }, - "node_modules/abstract-level": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "is-buffer": "^2.0.5", - "level-supports": "^6.0.0", - "level-transcoder": "^1.0.1", - "maybe-combine-errors": "^1.0.0", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/abstract-leveldown": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -1756,6 +846,8 @@ }, "node_modules/abstract-leveldown/node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -1778,6 +870,8 @@ }, "node_modules/abstract-leveldown/node_modules/level-supports": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", "license": "MIT", "dependencies": { "xtend": "^4.0.2" @@ -1788,6 +882,8 @@ }, "node_modules/accepts": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -1808,6 +904,8 @@ }, "node_modules/aligned-block-file": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/aligned-block-file/-/aligned-block-file-1.2.2.tgz", + "integrity": "sha512-2Sy0hWhifVb8ycNFJgicL8fDPL2Ct1r62XOVxXnykn36z22MPZwnQlCmB2viQlY/lwfuO67GaQjUZ0rJgdVP7Q==", "license": "MIT", "dependencies": { "hashlru": "^2.1.0", @@ -1820,6 +918,8 @@ }, "node_modules/aligned-block-file/node_modules/mkdirp": { "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -1828,20 +928,10 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" @@ -1849,6 +939,8 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1860,34 +952,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "license": "ISC", - "dependencies": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/append-batch": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/append-batch/-/append-batch-0.0.2.tgz", + "integrity": "sha512-v1lD0bKqM4MTtGzapIx6qCGcwbVJyKaTmbzIzA6kpnVwTY1E+VNMeYoOvAywl+hXEEF4CcqkrH/4iFMbzM8XXw==", "license": "MIT" }, "node_modules/archiver": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "license": "MIT", "dependencies": { "archiver-utils": "^5.0.2", @@ -1904,6 +978,8 @@ }, "node_modules/archiver-utils": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "license": "MIT", "dependencies": { "glob": "^10.0.0", @@ -1918,38 +994,10 @@ "node": ">= 14" } }, - "node_modules/arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -1962,48 +1010,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -2021,34 +1031,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asap": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/async": { "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, "node_modules/async-append-only-log": { "version": "4.3.10", + "resolved": "https://registry.npmjs.org/async-append-only-log/-/async-append-only-log-4.3.10.tgz", + "integrity": "sha512-OYibcLkDBG9J8FFX4Nyf8RJOhO1EJTfCfb3rWou+qjcmuuJwBu7EuVVDpRtzJ+mPGVmwlXVPKtaw3X1lfHhxaQ==", "license": "LGPL-3.0", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -2065,71 +1063,40 @@ "push-stream-to-pull-stream": "^1.0.3" } }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "node_modules/async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", "license": "MIT", "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/async-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" + "tslib": "^2.3.1" } }, "node_modules/async-single": { "version": "1.0.5", - "license": "MIT" - }, - "node_modules/async-write": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/async-write/-/async-write-2.1.0.tgz", - "integrity": "sha512-eKEYj1+jDgQ15jazjmelcPGXO77pa5epzlyXyEQcwwLtnACtBTCrxaW+96uXF2j4rzIikmUtVlzXwejx6iIhWw==", + "resolved": "https://registry.npmjs.org/async-single/-/async-single-1.0.5.tgz", + "integrity": "sha512-I0SMZyvxwnr00advlzQbcfztaSEBv3Dw7Vg5i7pJVrXfjqHJSP1wljuOFKanBec5WKd+V4CIF4F9nhsukBikHA==", "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/atomic-file": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomic-file/-/atomic-file-2.1.1.tgz", + "integrity": "sha512-Eh6pW+fRC2/1RxPq3hO8+PkZKv+wujzKky2MP/n69eC8yMkbNFfuEb/riZHqf13M7gr6Hvglpk/kISgBSBb6bQ==", "license": "MIT", "dependencies": { "flumecodec": "0.0.1", @@ -2139,6 +1106,8 @@ }, "node_modules/atomic-file-rw": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/atomic-file-rw/-/atomic-file-rw-0.3.0.tgz", + "integrity": "sha512-XMFpe/ub9Mwdlyq3DUdOc/g2sdutDMdYw0XNFSvNGQpYdmxqVmLbeklu5KhemUYyrAj1kIrwoHOx6IxA3a7w3A==", "license": "MIT", "dependencies": { "idb-kv-store": "^4.5.0", @@ -2147,6 +1116,8 @@ }, "node_modules/atomic-file/node_modules/flumecodec": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.1.tgz", + "integrity": "sha512-JT0xivzdV7uTucjsLMw6JhK2e1K5TmU4fGmoQqnrJbydgY/V6+m71QoxX5ZtRR1pKoD48uhPDWWE6G5MlNoGkg==", "license": "MIT", "dependencies": { "level-codec": "^6.2.0" @@ -2154,19 +1125,14 @@ }, "node_modules/atomic-file/node_modules/level-codec": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", + "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", "license": "MIT" }, - "node_modules/attach-ware": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/attach-ware/-/attach-ware-1.1.1.tgz", - "integrity": "sha512-OpavlXWZkyE7m28fpCWF/RmxCukC1edukJp9IKjEpZs/O11H3896DkLpK7lMiL8ZDx2yxo9FrZQaeHkyJGcIuQ==", - "license": "MIT", - "dependencies": { - "unherit": "^1.0.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -2191,20 +1157,14 @@ }, "node_modules/b4a": { "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", "license": "Apache-2.0" }, - "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "node_modules/bare-events": { @@ -2221,129 +1181,10 @@ } } }, - "node_modules/bare-fs": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", - "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", - "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", - "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.25.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-abort-controller": "*", - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - }, - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", - "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "license": "MIT", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -2362,6 +1203,8 @@ }, "node_modules/base64-url": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-2.3.3.tgz", + "integrity": "sha512-dLMhIsK7OplcDauDH/tZLvK7JmUZK3A7KiQpjNzsBrM6Etw7hzNI1tLEywqJk9NnwkgWuFKSlx/IUO7vF6Mo8Q==", "license": "ISC", "engines": { "node": ">=6" @@ -2369,14 +1212,14 @@ }, "node_modules/bash-color": { "version": "0.0.4", + "resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.4.tgz", + "integrity": "sha512-ZNB4525U7BxT6v9C8LEtywyCgB4Pjnm7/bh+ru/Z9Ecxvg3fDjaJ6z305z9a61orQdbB1zqYHh5JbUqx4s4K0g==", "license": "MIT" }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "license": "Apache-2.0" - }, "node_modules/bencode": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", + "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==", "license": "MIT" }, "node_modules/bidi-js": { @@ -2388,52 +1231,572 @@ "require-from-string": "^2.0.2" } }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "license": "MIT", + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", "engines": { - "node": ">=0.10.0" + "node": ">=0.6" } }, - "node_modules/binary-search": { - "version": "1.3.6", - "license": "CC0-1.0" - }, "node_modules/binary-search-bounds": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==", "license": "MIT" }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bipf": { "version": "1.9.0", + "resolved": "https://registry.npmjs.org/bipf/-/bipf-1.9.0.tgz", + "integrity": "sha512-B/d8IADy5Y4v/CTMRWxLD8ONd2qRkF+2DbZLeIUql7PukfAiBhlGlw5qJcIU03l21qMEyvbi4PdntatH+j40vA==", "license": "MIT", "dependencies": { "fast-varint": "^1.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", + "node_modules/blake2b": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.4.tgz", + "integrity": "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==", + "license": "ISC", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "blake2b-wasm": "^2.4.0", + "nanoassert": "^2.0.0" } }, - "node_modules/bl/node_modules/buffer": { + "node_modules/blake2b-wasm": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz", + "integrity": "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==", + "license": "MIT", + "dependencies": { + "b4a": "^1.0.1", + "nanoassert": "^2.0.0" + } + }, + "node_modules/blake2s": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blake2s/-/blake2s-1.1.0.tgz", + "integrity": "sha512-lvCxvg+up7AmujO8vijTi4GsbIOuusWa+b/nN5+VAanFjzbauq0er5VzgjTC6pevhEO8SYTzY784zS2KQauO0A==", + "license": "Public Domain" + }, + "node_modules/blake3": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/blake3/-/blake3-2.1.7.tgz", + "integrity": "sha512-5d+TdKJvju96IyEaGJ0eO6CHbckWi+NBrCezGYM/WsnI3R03aLL2TWfsuZSh1rs0fTv/L3ps/r0vykjYurcIwA==", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browser-split": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/browser-split/-/browser-split-0.0.0.tgz", + "integrity": "sha512-CNXO3AXAS1H/kOGQkPjucm1161/XoF3aVkMfujqwk85XN/D/MkQMvoB81lXyX/2rerZS+hPAYYRR3mAW05awjQ==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-2.0.2.tgz", + "integrity": "sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/butt64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/butt64/-/butt64-2.0.1.tgz", + "integrity": "sha512-rTxg4F4IH+/u6BN2/SJjJnQsOQwXeV7Hsm9ataUsxagoqXZ2xbt+vDVDYTbS1GEDpUE/38LEuG9Mk9pgoLUzpw==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "license": "MIT", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chacha20-universal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chacha20-universal/-/chacha20-universal-1.0.4.tgz", + "integrity": "sha512-/IOxdWWNa7nRabfe7+oF+jVkGjlr2xUL4J8l/OvzZhj+c9RpMqoo3Dq+5nU1j/BflRV4BKnaQ4+4oH1yBpQG1Q==", + "license": "ISC", + "dependencies": { + "nanoassert": "^2.0.0" + } + }, + "node_modules/charwise": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/charwise/-/charwise-3.0.1.tgz", + "integrity": "sha512-RcdumNsM6fJZ5HHbYunqj2bpurVRGsXour3OR+SlLEHFhG6ALm54i6Osnh+OvO7kEoSBzwExpblYFH8zKQiEPw==", + "license": "MIT" + }, + "node_modules/chloride": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chloride/-/chloride-2.4.1.tgz", + "integrity": "sha512-ZiID87W2o2llvuF4C7Fvt9GJisazSdMsSkjAq4WaMed9zn77nlkcy08ZfrPtOGAXyaxTDj0VjnuyD97EdJLz3g==", + "license": "MIT", + "dependencies": { + "sodium-browserify": "^1.2.7", + "sodium-browserify-tweetnacl": "^0.2.5", + "sodium-chloride": "^1.1.2" + }, + "optionalDependencies": { + "sodium-native": "^3.0.0" + } + }, + "node_modules/chloride-test": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/chloride-test/-/chloride-test-1.2.4.tgz", + "integrity": "sha512-9vhoi1qXSBPn6//ZxIgSe3M2QhKHzIPZQzmrZgmPADsqW0Jxpe3db1e7aGSRUMXbxAQ04SfypdT8dGaSvIvKDw==", + "license": "MIT", + "dependencies": { + "json-buffer": "^2.0.11" + } + }, + "node_modules/chloride-test/node_modules/json-buffer": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-2.0.11.tgz", + "integrity": "sha512-Wu4/hxSZX7Krzjor+sZHWaRau6Be4WQHlrkl3v8cmxRBBewF2TotlgHUedKQJyFiUyFxnK/ZlRYnR9UNVZ7pkg==", + "license": "MIT" + }, + "node_modules/clarify-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clarify-error/-/clarify-error-1.0.0.tgz", + "integrity": "sha512-f96oT3/Cdwz1eB+7RaH/XRR42lwGqVPnDl9NAm9ugT+BwAFoUS/pVnkgXUo/5UaUmwMMs6/GNFP8A8gCgmgvog==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/class-list": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/class-list/-/class-list-0.1.1.tgz", + "integrity": "sha512-zqR0uW+VsLtyQhixBhkdQ+z6B8+Y8HTh28kdSVjJ4zTTKM7Xz2asAQSya9VI6m/34F6N6Ktm0mrchKB+E5a8Xw==", + "dependencies": { + "indexof": "0.0.1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz", + "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==", + "license": "MIT", + "dependencies": { + "@hapi/bourne": "^3.0.0", + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/cont": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cont/-/cont-1.0.3.tgz", + "integrity": "sha512-lZzUsnNnzBW4EN+Mll8J54cdHn6JzKyHpa9ftxxsPwkWfhdYRIHph9dFdmi0ibfqhnvR103tQ61eAF57FF87IA==", + "license": "MIT", + "dependencies": { + "continuable": "~1.2.0", + "continuable-para": "~1.2.0", + "continuable-series": "~1.2.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/continuable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/continuable/-/continuable-1.2.0.tgz", + "integrity": "sha512-DMksZyrS34yEX+DTGyt9h8/6ONHRBH+PpQ5ev3YdCy5J41r0nMNv+psAujoeoT1mJf+fzKhzacR9o1M1ys2tBQ==" + }, + "node_modules/continuable-hash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/continuable-hash/-/continuable-hash-0.1.4.tgz", + "integrity": "sha512-Gu5kdDzwmCymZ2tROViH0VYSd0iXDDaOjn8iG51FyZreS6OuqEJAE+a3sJ6/Ky6roKOHYHDYD8Ttxj+a1tyYrg==", + "dependencies": { + "continuable": "~1.1.6" + } + }, + "node_modules/continuable-hash/node_modules/continuable": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/continuable/-/continuable-1.1.8.tgz", + "integrity": "sha512-DrVON8s+uwIH+OJa/HdbLE7ViLKpDeF0+flOhLf7hKtffIvyI2l38i7jq6G5i2GFqodpkz7RJPq60VkmwxfVnA==" + }, + "node_modules/continuable-list": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/continuable-list/-/continuable-list-0.1.6.tgz", + "integrity": "sha512-dqfYFtC/YRR4fUuZyJ8A+VKX/oV8E+atSCCcOvkuHsB6MFkiyI9LrDlUXCnHYowuuDu5vYqcAiRU2hiGXtcb7w==", + "dependencies": { + "continuable": "~1.1.6" + } + }, + "node_modules/continuable-list/node_modules/continuable": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/continuable/-/continuable-1.1.8.tgz", + "integrity": "sha512-DrVON8s+uwIH+OJa/HdbLE7ViLKpDeF0+flOhLf7hKtffIvyI2l38i7jq6G5i2GFqodpkz7RJPq60VkmwxfVnA==" + }, + "node_modules/continuable-para": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/continuable-para/-/continuable-para-1.2.0.tgz", + "integrity": "sha512-XjPo974RGEJqnvFVT9PWxG97d/NCg58mwmWDbKXqTZBZuUnMIjkx0vde+xerkUuymk8DR+YUAcllK8J5/O4Leg==", + "license": "MIT", + "dependencies": { + "continuable-hash": "~0.1.4", + "continuable-list": "~0.1.5" + } + }, + "node_modules/continuable-series": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/continuable-series/-/continuable-series-1.2.0.tgz", + "integrity": "sha512-xMAlREs3m/IM0VCzuh0TRBXpjd6tl6zi3cMgiwcywIPFcCmNlcFNA6XCtA2cEx6Kq7EE6c1JVDVl3WGepp6a5g==", + "license": "MIT" + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cpu-percentage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cpu-percentage/-/cpu-percentage-1.0.3.tgz", + "integrity": "sha512-LP7N2+hS7optwpkMisZoDfswnLeCzyETNOogkjw7v2YteQSPDd0vEv3nSbU8FVu58fXCkVAWtXaeSXEedT1a5g==", + "license": "MIT", + "engines": { + "node": ">=6.1" + } + }, + "node_modules/crc": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.6.0.tgz", + "integrity": "sha512-K9CVP4+ugmpRvZidsumVVL1swX7t0cqYrLhT+5rAp7/S3TjiEtpovD8TISzy+3moanm6v6/cbXOzE6JbzB+siw==", + "license": "MIT", + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc/node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", @@ -2457,1108 +1820,10 @@ "ieee754": "^1.1.13" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/blake2b": { - "version": "2.1.4", - "license": "ISC", - "dependencies": { - "blake2b-wasm": "^2.4.0", - "nanoassert": "^2.0.0" - } - }, - "node_modules/blake2b-wasm": { - "version": "2.4.0", - "license": "MIT", - "dependencies": { - "b4a": "^1.0.1", - "nanoassert": "^2.0.0" - } - }, - "node_modules/blake2s": { - "version": "1.1.0", - "license": "Public Domain" - }, - "node_modules/blake3": { - "version": "2.1.7", - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.2.2", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.7.2", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/media-typer": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/mime-db": { - "version": "1.54.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/mime-types": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/type-is": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/bottleneck": { - "version": "2.19.5", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", - "license": "MIT", - "dependencies": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/broadcast-stream": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/broadcast-stream/-/broadcast-stream-0.2.2.tgz", - "integrity": "sha512-d6iHc4P5YC2RvXv6qZxH3aoue88BiWhzCPki+i1Dj2igeZyNtUoMInloINRfnyrB7Fm+2f1q1bmQYhXQeKWwkQ==", - "license": "MIT", - "bin": { - "broadcast-stream": "bin.js" - } - }, - "node_modules/browser-split": { - "version": "0.0.0", - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/buffer-crc32": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/buffer-xor": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.1" - } - }, - "node_modules/butt64": { - "version": "2.0.1", - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "license": "MIT", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cache-base/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cache-content-type": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cat-names": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cat-names/-/cat-names-2.0.0.tgz", - "integrity": "sha512-yjcTHwA7tcro823P5yIfmTvXT8p5YxJd4eASWHAHvqaekuJoGu1qjWoldgfV2mH4tgLp+FNAf13uiDEo/O7LGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "meow": "^5.0.0", - "unique-random-array": "^1.0.0" - }, - "bin": { - "cat-names": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chacha20-universal": { - "version": "1.0.4", - "license": "ISC", - "dependencies": { - "nanoassert": "^2.0.0" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", - "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/charwise": { - "version": "3.0.1", - "license": "MIT" - }, - "node_modules/chloride": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "sodium-browserify": "^1.2.7", - "sodium-browserify-tweetnacl": "^0.2.5", - "sodium-chloride": "^1.1.2" - }, - "optionalDependencies": { - "sodium-native": "^3.0.0" - } - }, - "node_modules/chloride-test": { - "version": "1.2.4", - "license": "MIT", - "dependencies": { - "json-buffer": "^2.0.11" - } - }, - "node_modules/chloride-test/node_modules/json-buffer": { - "version": "2.0.11", - "license": "MIT" - }, - "node_modules/chmodrp": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==", - "license": "MIT", - "dependencies": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" - }, - "optionalDependencies": { - "fsevents": "^1.0.0" - } - }, - "node_modules/chokidar/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/clarify-error": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/class-list": { - "version": "0.1.1", - "dependencies": { - "indexof": "0.0.1" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/class-utils/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/cmake-js": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-8.0.0.tgz", - "integrity": "sha512-YbUP88RDwCvoQkZhRtGURYm9RIpWdtvZuhT87fKNoLjk8kIFIFeARpKfuZQGdwfH99GZpUmqSfcDrK62X7lTgg==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "fs-extra": "^11.3.3", - "node-api-headers": "^1.8.0", - "rc": "1.2.8", - "semver": "^7.7.3", - "tar": "^7.5.6", - "url-join": "^4.0.1", - "which": "^6.0.0", - "yargs": "^17.7.2" - }, - "bin": { - "cmake-js": "bin/cmake-js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/cmake-js/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/cmake-js/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/co-body": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "@hapi/bourne": "^3.0.0", - "inflation": "^2.0.0", - "qs": "^6.5.2", - "raw-body": "^2.3.3", - "type-is": "^1.6.16" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "license": "MIT", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "4.2.3", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/compare-at-paths": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "libnested": "^1.3.2", - "tape": "^4.9.1", - "typewiselite": "^1.0.0" - } - }, - "node_modules/compatibility": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/compatibility/-/compatibility-1.0.1.tgz", - "integrity": "sha512-0OzQu7V1TlJvKiEGC4b4TEXypGBHzcRX8ZSJ9svyMIKnE73AFrBAmVyPIabrFKhKc0L0iDLw6voyB3vA3hEXHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "npm-install-package": "^2.1.0", - "semver": "^6.1.0" - }, - "bin": { - "compatibility": "bin.js" - } - }, - "node_modules/compatibility/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/compress-commons": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/cont": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "continuable": "~1.2.0", - "continuable-para": "~1.2.0", - "continuable-series": "~1.2.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/continuable": { - "version": "1.2.0" - }, - "node_modules/continuable-hash": { - "version": "0.1.4", - "dependencies": { - "continuable": "~1.1.6" - } - }, - "node_modules/continuable-hash/node_modules/continuable": { - "version": "1.1.8" - }, - "node_modules/continuable-list": { - "version": "0.1.6", - "dependencies": { - "continuable": "~1.1.6" - } - }, - "node_modules/continuable-list/node_modules/continuable": { - "version": "1.1.8" - }, - "node_modules/continuable-para": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "continuable-hash": "~0.1.4", - "continuable-list": "~0.1.5" - } - }, - "node_modules/continuable-series": { - "version": "1.2.0", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cookies": { - "version": "0.9.1", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "keygrip": "~1.1.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cpu-percentage": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">=6.1" - } - }, - "node_modules/crc": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "buffer": "^5.1.0" - } - }, - "node_modules/crc-32": { - "version": "1.2.2", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc/node_modules/buffer": { - "version": "5.7.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/crc32-stream": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "license": "MIT", "dependencies": { "crc-32": "^1.2.0", @@ -3570,6 +1835,8 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3617,17 +1884,17 @@ "node": "20 || >=22" } }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", - "dev": true, - "license": "MIT", + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", "dependencies": { - "array-find-index": "^1.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.12" } }, "node_modules/data-urls": { @@ -3680,6 +1947,8 @@ }, "node_modules/data-view-buffer": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3695,6 +1964,8 @@ }, "node_modules/data-view-byte-length": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3710,6 +1981,8 @@ }, "node_modules/data-view-byte-offset": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3725,6 +1998,8 @@ }, "node_modules/debug": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3740,33 +2015,8 @@ }, "node_modules/decamelize": { "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3778,36 +2028,16 @@ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-equal": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", "license": "MIT" }, "node_modules/deep-extend": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", "engines": { "node": ">=4.0.0" @@ -3815,6 +2045,8 @@ }, "node_modules/deferred-leveldown": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", "license": "MIT", "dependencies": { "abstract-leveldown": "~6.2.1", @@ -3826,6 +2058,8 @@ }, "node_modules/define-data-property": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -3841,6 +2075,8 @@ }, "node_modules/define-lazy-prop": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "license": "MIT", "engines": { "node": ">=8" @@ -3848,6 +2084,8 @@ }, "node_modules/define-properties": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -3861,37 +2099,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defined": { - "version": "1.0.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3899,10 +2110,14 @@ }, "node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3910,23 +2125,18 @@ }, "node_modules/destroy": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detab": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/detab/-/detab-1.0.2.tgz", - "integrity": "sha512-J7vnPWu/Rq5Hi8xPPnwsv55CLkJmzZZCJOs0sAc6QcC7uf1X2iOrAXyGB6bXJBl5s4anllkMBKkHz13/BenuUQ==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.5.2" - } - }, "node_modules/detect-libc": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3934,6 +2144,8 @@ }, "node_modules/dezalgo": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "license": "ISC", "dependencies": { "asap": "^2.0.0", @@ -3942,6 +2154,8 @@ }, "node_modules/dgram-broadcast": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dgram-broadcast/-/dgram-broadcast-1.0.0.tgz", + "integrity": "sha512-7IPzhuQZVnU2iGo+Eq+dRNpYvlCkT7/5yD6BHx5P6MtT/cDxajR7Qh4+ve2WamsXaXVRLvj16ah8YIF7qcXOLw==", "license": "MIT", "bin": { "dgram-broadcast": "bin.js" @@ -3952,260 +2166,64 @@ }, "node_modules/dijkstrajs": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, "node_modules/discontinuous-range": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", "license": "MIT" }, - "node_modules/dog-names": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dog-names/-/dog-names-1.0.2.tgz", - "integrity": "sha512-3+2tu1P9L+J3od+yTWQpfXkcUlukfylLYpsSgAMFaUvxk8Ah7kStNdUDcg9D4hpH1D8b+uMGHv5LKlB4uaH4ng==", - "dev": true, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "license": "MIT", "dependencies": { - "meow": "^3.0.0", - "unique-random-array": "^1.0.0" + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" }, - "bin": { - "dog-names": "cli.js" - }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/dog-names/node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/parse-json": { + "node_modules/dom-serializer/node_modules/entities": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/dog-names/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/pify": { + "node_modules/domelementtype": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" }, - "node_modules/dog-names/node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "license": "MIT", + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "domelementtype": "^2.2.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "node": ">= 4" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-stdin": "^4.0.1" - }, - "bin": { - "strip-indent": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dog-names/node_modules/trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, "node_modules/dompurify": { @@ -4217,18 +2235,24 @@ "@types/trusted-types": "^2.0.7" } }, - "node_modules/dotignore": { - "version": "0.1.2", - "license": "MIT", + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", "dependencies": { - "minimatch": "^3.0.4" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" }, - "bin": { - "ignored": "bin/ignored" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dunder-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4241,6 +2265,8 @@ }, "node_modules/dynamic-dijkstra": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dynamic-dijkstra/-/dynamic-dijkstra-1.0.2.tgz", + "integrity": "sha512-1N+eCCrepIeK1+qtWrMEO1CV68Hn+TLbiR9c70VB3xnut3DmUxT+3T7sRHhb0mpK2F/74IfP+loQDriU2W9lkA==", "license": "MIT", "dependencies": { "heap": "^0.2.6", @@ -4249,10 +2275,14 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, "node_modules/ed2curve": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.1.4.tgz", + "integrity": "sha512-hDZWhCHZ1wu4P2g2RVsM2MjDmmJzhvcsXr5qHUSBJZXvuhJSunhbVsWoBXdIe0/yTa3RV4UaWpOmFmrVsKr0wA==", "license": "Public domain", "dependencies": { "tweetnacl": "0.x.x" @@ -4260,50 +2290,29 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "license": "MIT" - }, - "node_modules/elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/emoji-named-characters": { - "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, - "node_modules/emoji-server": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "emoji-named-characters": "~1.0.2" - } - }, "node_modules/encodeurl": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/encoding-down": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", "license": "MIT", "dependencies": { "abstract-leveldown": "^6.2.1", @@ -4315,15 +2324,6 @@ "node": ">=6" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -4338,20 +2338,17 @@ }, "node_modules/env-paths": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/env-var": { - "version": "7.5.0", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/envelope-js": { "version": "1.3.2", + "resolved": "https://registry.npmjs.org/envelope-js/-/envelope-js-1.3.2.tgz", + "integrity": "sha512-U3MQnyczN9QvwgXWlxDzAiEbNjwGD1aG+l1fNdwSZijOYboiu1aLHT8vIPSITShLiD4+n9p+EEwR79rp0vsKdw==", "license": "MIT", "dependencies": { "buffer-xor": "^2.0.2", @@ -4363,6 +2360,8 @@ }, "node_modules/envelope-spec": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/envelope-spec/-/envelope-spec-1.1.1.tgz", + "integrity": "sha512-7eIn5kw7eIt2H2YEPE5oWT8ATx9AYkVFKQi4GG+3qRxgcBKmPWt71OOI3DE4Do/hryZohh9eamoxGhSgZGQpIw==", "license": "LGPL-3.0-only", "dependencies": { "ssb-bfe": "^1.0.1" @@ -4370,6 +2369,8 @@ }, "node_modules/envelope-spec/node_modules/ssb-bfe": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ssb-bfe/-/ssb-bfe-1.1.0.tgz", + "integrity": "sha512-viY/ZzQENnO+gvahYQ35tf3OPV6ChQ+CxnyPpiloDZu7bzQNL0E24wjmoGefF+ftaCK/meeWKP3Hxm7pG50cFA==", "license": "LGPL-3.0", "dependencies": { "is-canonical-base64": "^1.1.1", @@ -4378,6 +2379,8 @@ }, "node_modules/epidemic-broadcast-trees": { "version": "9.0.4", + "resolved": "https://registry.npmjs.org/epidemic-broadcast-trees/-/epidemic-broadcast-trees-9.0.4.tgz", + "integrity": "sha512-f1Y2ayalcLQNn2I6umep29UDkTo+8nF4D//KulmwjDGP4ZPOO+ch8/LuxpXaMoeV6RO5arvvaEL53yzkKGSHTg==", "license": "MIT", "dependencies": { "push-stream": "^11.0.0" @@ -4385,6 +2388,8 @@ }, "node_modules/errno": { "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "license": "MIT", "dependencies": { "prr": "~1.0.1" @@ -4393,18 +2398,10 @@ "errno": "cli.js" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -4471,6 +2468,8 @@ }, "node_modules/es-define-property": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4478,6 +2477,8 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4485,6 +2486,8 @@ }, "node_modules/es-get-iterator": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -4503,6 +2506,8 @@ }, "node_modules/es-map": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/es-map/-/es-map-1.0.6.tgz", + "integrity": "sha512-JWn54TeYJe93WHhduTh9MUqEkjhbCjU8vqq1q9FyCrOle/utr1i8PmzK2vXnP6BeLsBLvb1zV4fSkpVnT4LW1A==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -4526,6 +2531,8 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4536,6 +2543,8 @@ }, "node_modules/es-set": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-set/-/es-set-1.1.2.tgz", + "integrity": "sha512-tGLC1yArXhXwZTm3D6xbu1JL4S3cUtRRggbFWHhqTxpPc975fmcLL1VB0QOnAY8q5TBdul7YTjfEOT2VpUGcCg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -4564,6 +2573,8 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4577,6 +2588,8 @@ }, "node_modules/es-to-primitive": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "license": "MIT", "dependencies": { "is-callable": "^1.2.7", @@ -4590,8 +2603,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -4599,39 +2654,48 @@ }, "node_modules/escape-html": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, "engines": { - "node": ">=0.8.0" + "node": ">=0.10" } }, - "node_modules/etag": { - "version": "1.8.1", + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" } }, "node_modules/event-target-shim": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" - }, "node_modules/events": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", "engines": { "node": ">=0.8.x" @@ -4646,272 +2710,55 @@ "bare-events": "^2.7.0" } }, - "node_modules/exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", - "license": "MIT", - "dependencies": { - "is-posix-bracket": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/explain-error": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/explain-error/-/explain-error-1.0.4.tgz", + "integrity": "sha512-/wSgNMxFusiYRy1rd19LT2SQlIXDppHpumpWo06wxjflD1OYxDLbl6rMVw+U3bxD5Nuhex4TKqv9Aem4D0lVzQ==", "license": "MIT" }, - "node_modules/express": { - "version": "5.1.0", - "license": "MIT", + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "type": "^2.7.2" } }, - "node_modules/express/node_modules/accepts": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/fresh": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/mime-db": { - "version": "1.54.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/negotiator": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend.js": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/extend.js/-/extend.js-0.0.2.tgz", - "integrity": "sha512-kSK5oO9X2i9qUptwhkilKqBfLG322xXY2ZO6/dlPY/ozt0fc+Ac9Qo6hZE/RiRTau5XUvVv2y6z1G6lNZ8f1WA==" - }, - "node_modules/extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-content-type-parse": { - "version": "3.0.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-fifo": { "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "license": "MIT" }, "node_modules/fast-varint": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-varint/-/fast-varint-1.0.1.tgz", + "integrity": "sha512-Vi9Db6dVe0GbWHtZxYzlpLbIq4o1FnVIaVud3RMYF/E7d4l0r37aepMY16qZBNfniw/o02BP9LAqCYCKjFTy+Q==", "license": "MIT" }, - "node_modules/fast-xml-parser": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", - "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastintcompression": { "version": "0.0.4", + "resolved": "https://registry.npmjs.org/fastintcompression/-/fastintcompression-0.0.4.tgz", + "integrity": "sha512-sRtejNHcWjJl0Fr+gsbybbgOKKj0sQYvv7rA787EbeFu1fMpmzjHuBW8fLBImSZeqDuR++TZU2n60mRrpAUR9g==", "license": "Apache-2.0" }, "node_modules/fastpriorityqueue": { "version": "0.7.5", + "resolved": "https://registry.npmjs.org/fastpriorityqueue/-/fastpriorityqueue-0.7.5.tgz", + "integrity": "sha512-3Pa0n9gwy8yIbEsT3m2j/E9DXgWvvjfiZjjqcJ+AdNKTAlVMIuFYrYG5Y3RHEM8O6cwv9hOpOWY/NaMfywoQVA==", "license": "Apache-2.0" }, "node_modules/file-type": { "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", "license": "MIT", "dependencies": { "readable-web-to-node-stream": "^3.0.0", @@ -4925,108 +2772,10 @@ "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT", - "optional": true - }, - "node_modules/filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/filename-reserved-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", - "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/filenamify": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", - "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", - "license": "MIT", - "dependencies": { - "filename-reserved-regex": "^3.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "license": "MIT", - "dependencies": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flatbuffers": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz", - "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==", - "license": "SEE LICENSE IN LICENSE.txt" - }, "node_modules/flumecodec": { "version": "0.0.0", + "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.0.tgz", + "integrity": "sha512-1UhNtCE8Al1mMrEqvRGxKU2GoZHXPKLWmAm6Oe2J9x+oQeMinCW9jpegJaEuGgUtro94MogbObFjEBFJHc36fA==", "license": "MIT", "dependencies": { "level-codec": "^6.2.0" @@ -5034,27 +2783,14 @@ }, "node_modules/flumecodec/node_modules/level-codec": { "version": "6.2.0", - "license": "MIT" - }, - "node_modules/flumedb": { - "version": "2.1.8", - "license": "MIT", - "dependencies": { - "cont": "^1.0.3", - "explain-error": "^1.0.3", - "obz": "1.0.2", - "pull-abortable": "^4.1.1", - "pull-cont": "^0.1.1", - "pull-looper": "^1.0.0", - "pull-stream": "^3.6.14" - } - }, - "node_modules/flumedb/node_modules/obz": { - "version": "1.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", + "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", "license": "MIT" }, "node_modules/flumelog-offset": { "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flumelog-offset/-/flumelog-offset-3.4.4.tgz", + "integrity": "sha512-sakCD+dVx541h3VeVq3Ti2lWPRrJf8PBRmnbm9EMBVLJnZkS3UD2lAlClZROxgKbh/JkMPyffvhDGv4VHNCVbA==", "license": "MIT", "dependencies": { "aligned-block-file": "^1.2.0", @@ -5069,96 +2805,10 @@ "uint48be": "^2.0.1" } }, - "node_modules/flumeview-hashtable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flumeview-hashtable/-/flumeview-hashtable-1.1.1.tgz", - "integrity": "sha512-73LAN0qF4zVMHMExYhK0z1swDo6FEh/bt1HF5/UnWkgI5JsECXOK2bTokWVU1levtr9bd+IxYChnJKJNnEzD0A==", - "license": "MIT", - "dependencies": { - "async-single": "^1.0.5", - "atomic-file": "^1.1.3", - "obv": "0.0.1", - "pull-stream": "^3.6.0" - } - }, - "node_modules/flumeview-hashtable/node_modules/atomic-file": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/atomic-file/-/atomic-file-1.1.5.tgz", - "integrity": "sha512-TG+5YFiaKQ6CZiSQsosGMJ/IJzwMZ4V/rSdEXlD6+DwKyv8OyeUcprq34kp4yuS6bfQYXhxBC2Vm8PWo+iKBGQ==", - "license": "MIT" - }, - "node_modules/flumeview-level": { - "version": "4.0.4", - "license": "MIT", - "dependencies": { - "charwise": "^3.0.1", - "explain-error": "^1.0.4", - "level": "^6.0.1", - "ltgt": "^2.1.3", - "mkdirp": "^1.0.4", - "obz": "^1.0.2", - "pull-level": "^2.0.3", - "pull-paramap": "^1.2.1", - "pull-stream": "^3.6.14", - "pull-write": "^1.1.1" - } - }, - "node_modules/flumeview-links": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "deep-equal": "^2.0.3", - "flumeview-level": "^4.0.3", - "map-filter-reduce": "^3.2.2", - "pull-flatmap": "0.0.1", - "pull-stream": "^3.6.14" - } - }, - "node_modules/flumeview-links/node_modules/deep-equal": { - "version": "2.2.3", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/flumeview-query": { - "version": "8.0.0", - "license": "MIT", - "dependencies": { - "deep-equal": "^1.0.1", - "flumeview-level": "^4.0.3", - "map-filter-reduce": "^3.2.0", - "pull-flatmap": "0.0.1", - "pull-paramap": "^1.1.3", - "pull-sink-through": "0.0.0", - "pull-stream": "^3.4.0" - } - }, "node_modules/flumeview-reduce": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/flumeview-reduce/-/flumeview-reduce-1.4.0.tgz", + "integrity": "sha512-ShCMtY5YZl3icnUAWTfwzRXSGN1AUo4Isj1wTRLpMMNgM9p1T0gc6N+r1ajampf1GO19SkqrgZHdPPOaEitvhg==", "license": "MIT", "dependencies": { "async-single": "^1.0.5", @@ -5170,16 +2820,6 @@ "pull-stream": "^3.5.0" } }, - "node_modules/flumeview-search": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "flumeview-level": "^4.0.3", - "pull-paramap": "^1.2.2", - "pull-pushable": "^2.2.0", - "pull-stream": "^3.0.1" - } - }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -5202,6 +2842,8 @@ }, "node_modules/for-each": { "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -5213,29 +2855,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/foreground-child": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -5250,6 +2873,8 @@ }, "node_modules/form-data": { "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5264,6 +2889,8 @@ }, "node_modules/formidable": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", "license": "MIT", "dependencies": { "@paralleldrive/cuid2": "^2.2.2", @@ -5275,72 +2902,25 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "license": "MIT", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fresh": { "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5348,6 +2928,8 @@ }, "node_modules/function.prototype.name": { "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5366,6 +2948,8 @@ }, "node_modules/functions-have-names": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5373,6 +2957,8 @@ }, "node_modules/futoin-hkdf": { "version": "1.5.3", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", + "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -5380,6 +2966,8 @@ }, "node_modules/generate-function": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "license": "MIT", "dependencies": { "is-property": "^1.0.2" @@ -5387,6 +2975,8 @@ }, "node_modules/generate-object-property": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ==", "license": "MIT", "dependencies": { "is-property": "^1.0.0" @@ -5394,25 +2984,17 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5435,6 +3017,8 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5444,18 +3028,10 @@ "node": ">= 0.4" } }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/get-symbol-description": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5469,23 +3045,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -5502,28 +3065,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", - "license": "MIT", - "dependencies": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", - "license": "ISC", - "dependencies": { - "is-glob": "^2.0.0" - } - }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -5539,18 +3080,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/globalthis": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -5563,51 +3096,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz", - "integrity": "sha512-JPDtMSr0bt25W64q792rvlrSwIaZwqUAhqdYKSr57Wh/xBcQ5JDWLM85ndn+Q1WdBQXLb9YGCl0QN/T0HpqU0A==", - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^6.0.1", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby/node_modules/glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gopd": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5616,56 +3108,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gossip-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gossip-query/-/gossip-query-2.0.2.tgz", - "integrity": "sha512-17MflOq79BYcK01RrqHhtG5Dl88ztSu7gfD+4i1GrpzkbvLDKkJ7LoKiFKzBWzzd+Jd5VhIiLtQRvjRiKa6PUg==", - "license": "MIT", - "dependencies": { - "obv": "0.0.1", - "pull-stream": "^3.6.0" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/guid-typescript": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", - "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", - "license": "ISC" - }, - "node_modules/has": { - "version": "1.0.4", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5674,26 +3126,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-network": { - "version": "0.0.1", - "license": "MIT" - }, "node_modules/has-network2": { "version": "0.0.3", + "resolved": "https://registry.npmjs.org/has-network2/-/has-network2-0.0.3.tgz", + "integrity": "sha512-EvEZguA+LkyiS8G/Qks5I6imKnM2Z3NPN3eoQhviUQ7O6/d8nyZ7sDozBk6kTIA+Qj/S/V8ubRA1rqJcxc3qBQ==", "license": "MIT" }, "node_modules/has-property-descriptors": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -5704,6 +3146,8 @@ }, "node_modules/has-proto": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" @@ -5717,6 +3161,8 @@ }, "node_modules/has-symbols": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5727,6 +3173,8 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5738,90 +3186,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "license": "MIT", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-value/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hashlru": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", + "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", "license": "MIT" }, "node_modules/hasown": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5830,31 +3204,16 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/he/-/he-0.5.0.tgz", - "integrity": "sha512-DoufbNNOFzwRPy8uecq+j+VCPQ+JyDelHTmSgygrA5TsR8Cbw4Qcir5sGtWiusB4BdT89nmlaVDhSJOqC/33vw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/heap": { "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "license": "MIT" }, - "node_modules/hexpp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hexpp/-/hexpp-2.0.0.tgz", - "integrity": "sha512-5qB1Uar+ka6iQm++gOs3rZDH7q3d7gIxgwYRz00KF5OI4saTKEnGMsv5ebqW9XRkBXnkMUghd9b9b5gJpVROtA==", - "dev": true, - "license": "MIT", - "bin": { - "hexpp": "bin.js" - } - }, "node_modules/highlight.js": { "version": "11.0.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.0.0.tgz", + "integrity": "sha512-ByaTMfsSuoqerTwemOgpIhfULEIaK52JJYhky/sK7/Yqc0+t7Uh5DHay9vIC94YXSupnQ1Vqfc9VXrYP4eXW3Q==", "license": "BSD-3-Clause", "engines": { "node": ">=12.0.0" @@ -5862,17 +3221,14 @@ }, "node_modules/hoox": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/hoox/-/hoox-0.0.1.tgz", + "integrity": "sha512-4tKFjXcp8AWuw5lLTL7Xnixj1w88r+y1j9HKE8GoSeqDfsv6fLNMLjnrkB/H9tH+LqLp4+7eLss5IFS3Qra4lw==", "license": "MIT" }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/html-element": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/html-element/-/html-element-2.3.1.tgz", + "integrity": "sha512-xnFt2ZkbFcjc+JoAtg3Hl89VeEZDjododu4VCPkRvFmBTHHA9U1Nt6hLUWfW2O+6Sl/rT1hHK/PivleX3PdBJQ==", "license": "MIT", "dependencies": { "class-list": "~0.1.1" @@ -5895,6 +3251,8 @@ }, "node_modules/html-tags": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "license": "MIT", "engines": { "node": ">=8" @@ -5903,8 +3261,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-assert": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "license": "MIT", "dependencies": { "deep-equal": "~1.0.1", @@ -5916,6 +3304,8 @@ }, "node_modules/http-assert/node_modules/depd": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5923,6 +3313,8 @@ }, "node_modules/http-assert/node_modules/http-errors": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "license": "MIT", "dependencies": { "depd": "~1.1.2", @@ -5937,6 +3329,8 @@ }, "node_modules/http-assert/node_modules/statuses": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5944,6 +3338,8 @@ }, "node_modules/http-errors": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -5984,6 +3380,8 @@ }, "node_modules/hyperaxe": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hyperaxe/-/hyperaxe-2.0.1.tgz", + "integrity": "sha512-vp/x8kZnGoCQ5fwHtqQEFsv7sGoWFENNqSCKk46XjottHTL0feSW2LHSr6uK/JkeA8QydjoYPRjAl15mVHSM3g==", "license": "ISC", "dependencies": { "html-tags": "^3.0.0", @@ -5995,6 +3393,8 @@ }, "node_modules/hyperscript": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hyperscript/-/hyperscript-2.0.2.tgz", + "integrity": "sha512-uggBAYfHFC5WyZQXlJ61BNZbPmJbschcvfYNhYdZWCp+0J8KYb5Du8nQuk8Ru+ThoCNb01B0tPtnTRqnrFBkVg==", "license": "MIT", "dependencies": { "browser-split": "0.0.0", @@ -6002,20 +3402,10 @@ "html-element": "^2.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/idb-kv-store": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/idb-kv-store/-/idb-kv-store-4.5.0.tgz", + "integrity": "sha512-snvtAQRforYUI+C2+45L2LBJy/0/uQUffxv8/uwiS98fSUoXHVrFPClgzWZWxT0drwkLHJRm9inZcYzTR42GLA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -6024,6 +3414,8 @@ }, "node_modules/ieee754": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "funding": [ { "type": "github", @@ -6040,43 +3432,36 @@ ], "license": "BSD-3-Clause" }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, "node_modules/immediate": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", "license": "MIT" }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/increment-buffer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/increment-buffer/-/increment-buffer-1.0.1.tgz", + "integrity": "sha512-djREfebnCm+rsaMKhxQ3iQI0uUicnKoWUt3eHBLAMqqh9PRJYb+gc2Sty7HYpXrE0JdgdND/wNZIqOyUcMdejw==", "license": "MIT" }, - "node_modules/indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/indexof": { - "version": "0.0.1" - }, - "node_modules/infer-partial-order": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/infer-partial-order/-/infer-partial-order-0.1.1.tgz", - "integrity": "sha512-ahwueME5/IpvDbTWjG+ZawATtmvp7p04/ctMBPhwpvq8myV2FXXbJTJhvB8lgU2+1E2Nzipk+jbOmqZ+VZ3Dpw==", - "dev": true, - "license": "MIT" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" }, "node_modules/inflation": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -6084,6 +3469,8 @@ }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -6092,30 +3479,26 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, "node_modules/int53": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/int53/-/int53-1.0.0.tgz", + "integrity": "sha512-u8BMiMa05OPBgd32CKTead0CVTsFVgwFk23nNXo1teKPF6Sxcu0lXxEzP//zTcaKzXbGgPDXGmj/woyv+I4C5w==", "license": "BSD-3-Clause" }, - "node_modules/interleavings": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/interleavings/-/interleavings-1.0.0.tgz", - "integrity": "sha512-DAzMdwFFmBZcVvi3cXiEXDY3SMZvSedK6qFICOk0A6ietIUMKyZSgXlyvssakingGkSOLN6SaGnQzqrI6KWIYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "infer-partial-order": "~0.1.1", - "rng": "~0.2.2" - } - }, "node_modules/internal-slot": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6135,6 +3518,8 @@ }, "node_modules/ip-address": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "license": "MIT", "dependencies": { "jsbn": "1.1.0", @@ -6146,205 +3531,14 @@ }, "node_modules/ip-address/node_modules/sprintf-js": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ipull": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/ipull/-/ipull-3.9.5.tgz", - "integrity": "sha512-5w/yZB5lXmTfsvNawmvkCjYo4SJNuKQz/av8TC1UiOyfOHyaM+DReqbpU2XpWYfmY+NIUbRRH8PUAWsxaS+IfA==", - "license": "MIT", - "dependencies": { - "@tinyhttp/content-disposition": "^2.2.0", - "async-retry": "^1.3.3", - "chalk": "^5.3.0", - "ci-info": "^4.0.0", - "cli-spinners": "^2.9.2", - "commander": "^10.0.0", - "eventemitter3": "^5.0.1", - "filenamify": "^6.0.0", - "fs-extra": "^11.1.1", - "is-unicode-supported": "^2.0.0", - "lifecycle-utils": "^2.0.1", - "lodash.debounce": "^4.0.8", - "lowdb": "^7.0.1", - "pretty-bytes": "^6.1.0", - "pretty-ms": "^8.0.0", - "sleep-promise": "^9.1.0", - "slice-ansi": "^7.1.0", - "stdout-update": "^4.0.1", - "strip-ansi": "^7.1.0" - }, - "bin": { - "ipull": "dist/cli/cli.js" - }, - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/ido-pluto/ipull?sponsor=1" - }, - "optionalDependencies": { - "@reflink/reflink": "^0.1.16" - } - }, - "node_modules/ipull/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ipull/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ipull/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ipull/node_modules/lifecycle-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lifecycle-utils/-/lifecycle-utils-2.1.0.tgz", - "integrity": "sha512-AnrXnE2/OF9PHCyFg0RSqsnQTzV991XaZA/buhFDoc58xU7rhSCDgCz/09Lqpsn4MpoPHt7TRAXV1kWZypFVsA==", - "license": "MIT" - }, - "node_modules/ipull/node_modules/parse-ms": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", - "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ipull/node_modules/pretty-ms": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", - "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", - "license": "MIT", - "dependencies": { - "parse-ms": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ipull/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/ipull/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/irregular-plurals": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", - "integrity": "sha512-kniTIJmaZYiwa17eTtWIfm0K342seyugl6vuC8DiiyiRAJWAVlLkqGCI0Im0neo0TkXw+pRcKaBPRdcKHnQJ6Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", - "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-arguments": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6359,6 +3553,8 @@ }, "node_modules/is-array-buffer": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -6372,15 +3568,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/is-async-function": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "license": "MIT", "dependencies": { "async-function": "^1.0.0", @@ -6398,6 +3589,8 @@ }, "node_modules/is-bigint": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" @@ -6409,20 +3602,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6435,33 +3618,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/is-buffer-zero": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-buffer-zero/-/is-buffer-zero-1.0.0.tgz", + "integrity": "sha512-eqgpqrTMGaAd5dQxg0dcZ79C8wTlDVYrM+zvB8kUXXSBzOqG5JeKWne1Zv9LDV3ePovx06fHLbp372OFbp/cIA==", "license": "MIT" }, "node_modules/is-callable": { "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6472,10 +3638,14 @@ }, "node_modules/is-canonical-base64": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-canonical-base64/-/is-canonical-base64-1.1.1.tgz", + "integrity": "sha512-o6t/DwgEapC0bsloqtegAQyZzQXaQ5+8fzsyf2KmLqupC2ifLFq/lMQiFCJeGpdSrK1o6GL+WW2lRU050lLlFg==", "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -6487,20 +3657,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", - "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/is-data-view": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6516,6 +3676,8 @@ }, "node_modules/is-date-object": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6528,31 +3690,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/is-docker": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", "bin": { "is-docker": "cli.js" @@ -6564,47 +3705,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", - "license": "MIT", - "dependencies": { - "is-primitive": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-finalizationregistry": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -6616,21 +3720,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -6638,6 +3731,8 @@ }, "node_modules/is-generator-function": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6652,42 +3747,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-map": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6698,10 +3761,14 @@ }, "node_modules/is-my-ip-valid": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz", + "integrity": "sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg==", "license": "MIT" }, "node_modules/is-my-json-valid": { "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", "license": "MIT", "dependencies": { "generate-function": "^2.0.0", @@ -6713,6 +3780,8 @@ }, "node_modules/is-my-ssb-valid": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-my-ssb-valid/-/is-my-ssb-valid-1.2.2.tgz", + "integrity": "sha512-13B1KdYuXwfVASWWWAKcbtKHtXhFNzLtg16oKh/3UTAWVh9GBdf/zjvY9BFY3xYsYFQYC1WhZEuifbYRzeiz7w==", "license": "LGPL-3.0-only", "dependencies": { "is-my-json-valid": "^2.20.0", @@ -6721,6 +3790,8 @@ }, "node_modules/is-negative-zero": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6729,20 +3800,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-number-object": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6757,76 +3818,29 @@ }, "node_modules/is-options": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", + "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", "license": "MIT", "dependencies": { "b4a": "^1.1.1" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "license": "MIT" }, - "node_modules/is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "license": "MIT" - }, "node_modules/is-property": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "license": "MIT" }, "node_modules/is-regex": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6843,6 +3857,8 @@ }, "node_modules/is-set": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6853,6 +3869,8 @@ }, "node_modules/is-shared-array-buffer": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -6866,6 +3884,8 @@ }, "node_modules/is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" @@ -6876,6 +3896,8 @@ }, "node_modules/is-string": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6888,21 +3910,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-svg": { - "version": "4.4.0", - "license": "MIT", - "dependencies": { - "fast-xml-parser": "^4.1.3" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-symbol": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6918,6 +3929,8 @@ }, "node_modules/is-typed-array": { "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -6931,29 +3944,14 @@ }, "node_modules/is-typedarray": { "version": "1.0.0", - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true, + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "license": "MIT" }, "node_modules/is-weakmap": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6964,6 +3962,8 @@ }, "node_modules/is-weakref": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -6977,6 +3977,8 @@ }, "node_modules/is-weakset": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6989,17 +3991,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-wsl": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -7010,86 +4005,20 @@ }, "node_modules/isarray": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "license": "MIT", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isobject/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "node_modules/isomorphic-ws": { - "version": "5.0.0", - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/iterate-iterator": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz", + "integrity": "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7097,6 +4026,8 @@ }, "node_modules/iterate-value": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", "license": "MIT", "dependencies": { "es-get-iterator": "^1.0.2", @@ -7108,6 +4039,8 @@ }, "node_modules/jackspeak": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -7121,6 +4054,8 @@ }, "node_modules/jitdb": { "version": "7.0.7", + "resolved": "https://registry.npmjs.org/jitdb/-/jitdb-7.0.7.tgz", + "integrity": "sha512-D/b1uyZzEvQewg9gDamgkCjvCji3ZYnirG86p3VHm/UzCohASd6ojLZrRPZHlsDRWsO5qhZxBzWoH7fSRtztdA==", "license": "LGPL-3.0", "dependencies": { "atomic-file-rw": "^0.3.0", @@ -7152,15 +4087,10 @@ "async-append-only-log": "^4.3.2" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/jsbn": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "license": "MIT" }, "node_modules/jsdom": { @@ -7239,6 +4169,8 @@ }, "node_modules/jsesc": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -7247,27 +4179,10 @@ "node": ">=6" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsonpointer": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7275,6 +4190,8 @@ }, "node_modules/key-value-file-store": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/key-value-file-store/-/key-value-file-store-1.1.1.tgz", + "integrity": "sha512-j2Vq2ftRSt99InoUUvMBgLX6BLrHZSa8nedntZyhqj2njzBaeL3IiUpT3DYx2p2+Fbf4vcmn74rJcds0uGpc0g==", "license": "MIT", "dependencies": { "atomic-file-rw": "^0.3.0" @@ -7282,6 +4199,8 @@ }, "node_modules/keygrip": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", "license": "MIT", "dependencies": { "tsscmp": "1.0.6" @@ -7290,24 +4209,6 @@ "node": ">= 0.6" } }, - "node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kind-of/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, "node_modules/koa": { "version": "2.16.4", "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.4.tgz", @@ -7344,6 +4245,8 @@ }, "node_modules/koa-body": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-6.0.1.tgz", + "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==", "license": "MIT", "dependencies": { "@types/co-body": "^6.1.0", @@ -7356,10 +4259,14 @@ }, "node_modules/koa-compose": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", "license": "MIT" }, "node_modules/koa-convert": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", "license": "MIT", "dependencies": { "co": "^4.6.0", @@ -7371,6 +4278,8 @@ }, "node_modules/koa-mount": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.2.0.tgz", + "integrity": "sha512-2iHQc7vbA9qLeVq5gKAYh3m5DOMMlMfIKjW/REPAS18Mf63daCJHHVXY9nbu7ivrnYn5PiPC4CE523Tf5qvjeQ==", "license": "MIT", "dependencies": { "debug": "^4.0.1", @@ -7382,6 +4291,8 @@ }, "node_modules/koa-send": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", "license": "MIT", "dependencies": { "debug": "^4.1.1", @@ -7394,6 +4305,8 @@ }, "node_modules/koa-send/node_modules/depd": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7401,6 +4314,8 @@ }, "node_modules/koa-send/node_modules/http-errors": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "license": "MIT", "dependencies": { "depd": "~1.1.2", @@ -7415,6 +4330,8 @@ }, "node_modules/koa-send/node_modules/statuses": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7422,6 +4339,8 @@ }, "node_modules/koa-static": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", "license": "MIT", "dependencies": { "debug": "^3.1.0", @@ -7433,6 +4352,8 @@ }, "node_modules/koa/node_modules/http-errors": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "license": "MIT", "dependencies": { "depd": "~1.1.2", @@ -7447,6 +4368,8 @@ }, "node_modules/koa/node_modules/http-errors/node_modules/depd": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7454,6 +4377,8 @@ }, "node_modules/koa/node_modules/statuses": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7461,6 +4386,8 @@ }, "node_modules/layered-graph": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/layered-graph/-/layered-graph-1.2.0.tgz", + "integrity": "sha512-VnjZHUyaXXsOR6KZ8JjTbkyjtq8VaU5ncGJ8cKBvg2bSLqVHE4bcGJ+20dDE7T84sss9d/BCHRj34Yh2fz9sbg==", "license": "MIT", "dependencies": { "dynamic-dijkstra": "^1.0.0", @@ -7471,6 +4398,8 @@ }, "node_modules/lazystream": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "license": "MIT", "dependencies": { "readable-stream": "^2.0.5" @@ -7481,10 +4410,14 @@ }, "node_modules/lazystream/node_modules/isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, "node_modules/lazystream/node_modules/readable-stream": { "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -7498,10 +4431,14 @@ }, "node_modules/lazystream/node_modules/safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -7509,6 +4446,8 @@ }, "node_modules/level": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", "license": "MIT", "dependencies": { "level-js": "^5.0.0", @@ -7525,6 +4464,8 @@ }, "node_modules/level-codec": { "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", "license": "MIT", "dependencies": { "buffer": "^5.6.0" @@ -7535,6 +4476,8 @@ }, "node_modules/level-codec/node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -7557,6 +4500,8 @@ }, "node_modules/level-concat-iterator": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", "license": "MIT", "engines": { "node": ">=6" @@ -7564,6 +4509,8 @@ }, "node_modules/level-errors": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", "license": "MIT", "dependencies": { "errno": "~0.1.1" @@ -7574,6 +4521,8 @@ }, "node_modules/level-iterator-stream": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", "license": "MIT", "dependencies": { "inherits": "^2.0.4", @@ -7586,6 +4535,8 @@ }, "node_modules/level-iterator-stream/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -7598,6 +4549,8 @@ }, "node_modules/level-js": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", "license": "MIT", "dependencies": { "abstract-leveldown": "~6.2.3", @@ -7608,6 +4561,8 @@ }, "node_modules/level-js/node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -7630,6 +4585,8 @@ }, "node_modules/level-packager": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", "license": "MIT", "dependencies": { "encoding-down": "^6.3.0", @@ -7641,31 +4598,17 @@ }, "node_modules/level-post": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/level-post/-/level-post-1.0.7.tgz", + "integrity": "sha512-PWYqG4Q00asOrLhX7BejSajByB4EmG2GaKHfj3h5UmmZ2duciXLPGYWIjBzLECFWUGOZWlm5B20h/n3Gs3HKew==", "license": "MIT", "dependencies": { "ltgt": "^2.1.2" } }, - "node_modules/level-supports": { - "version": "6.2.0", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/level-transcoder": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/leveldown": { "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -7679,6 +4622,8 @@ }, "node_modules/leveldown/node_modules/node-gyp-build": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -7688,6 +4633,8 @@ }, "node_modules/levelup": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", "license": "MIT", "dependencies": { "deferred-leveldown": "~5.3.0", @@ -7702,6 +4649,8 @@ }, "node_modules/levelup/node_modules/level-supports": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", "license": "MIT", "dependencies": { "xtend": "^4.0.2" @@ -7710,67 +4659,21 @@ "node": ">=6" } }, - "node_modules/libnested": { - "version": "1.5.2", - "license": "MIT" - }, "node_modules/libsodium": { "version": "0.7.15", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz", + "integrity": "sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==", "license": "ISC" }, "node_modules/libsodium-wrappers": { "version": "0.7.15", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.15.tgz", + "integrity": "sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==", "license": "ISC", "dependencies": { "libsodium": "^0.7.15" } }, - "node_modules/lifecycle-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lifecycle-utils/-/lifecycle-utils-3.1.1.tgz", - "integrity": "sha512-gNd3OvhFNjHykJE3uGntz7UuPzWlK9phrIdXxU9Adis0+ExkwnZibfxCJWiWWZ+a6VbKiZrb+9D9hCQWd4vjTg==", - "license": "MIT" - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -7779,30 +4682,38 @@ }, "node_modules/lodash.chunk": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", + "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==", "license": "MIT" }, "node_modules/lodash.clone": { "version": "4.5.0", - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==", "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, "node_modules/lodash.find": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==", "license": "MIT" }, "node_modules/lodash.get": { "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "license": "MIT" }, "node_modules/lodash.set": { @@ -7812,248 +4723,39 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha512-4vSow8gbiGnwdDNrpy1dyNaXWKSCIPop0EHdE8GrnngHoJujM3QhvHUN/igsYCgPoHo7pFOezlJ61Hlln0KHyA==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^1.0.0", - "cli-cursor": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-update/node_modules/onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==", - "license": "MIT", - "dependencies": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/longest-streak": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-1.0.0.tgz", - "integrity": "sha512-84jGpz/1j02Xm/L4y4uEXGxFFPHFabKjMHQ+rEPi0gPQbD5p0J3aZomvk0ZpUPpTtcVqhtSEq+4WNQbJjWiZ1Q==", - "license": "MIT" - }, "node_modules/looper": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-4.0.0.tgz", + "integrity": "sha512-NjGRcX4vCwyfbujv03omakGfAYh6St5kVsZFKfU23MFO1Z9/mZT8ypTZMEnvVC7nJeYtbqkRPFV4GoJBPdJgYw==", "license": "MIT" }, - "node_modules/lossy-store": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lossy-store/-/lossy-store-1.2.4.tgz", - "integrity": "sha512-rvqTCHZxWLvHAJv2w5vdCr5xICAW/FiXQ9wZGaAfBlNGG2FuLX0URNcj/zXC3qr5zQ8lBCbW8EEmDCKi7Dsp1g==", - "license": "MIT", - "dependencies": { - "mkdirp": "^0.5.1", - "tape": "^4.6.3" - } - }, - "node_modules/lossy-store/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loud-rejection/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/lowdb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", - "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", - "license": "MIT", - "dependencies": { - "steno": "^4.0.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, "node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "node_modules/ltgt": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", "license": "MIT" }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-filter-reduce": { - "version": "3.2.2", - "license": "MIT", - "dependencies": { - "binary-search": "^1.2.0", - "compare-at-paths": "^1.0.0", - "pull-sink-through": "0.0.0", - "pull-sort": "^1.0.1", - "pull-stream": "^3.4.3", - "typewiselite": "^1.0.0" - } - }, "node_modules/map-merge": { "version": "1.1.0", - "license": "MIT" - }, - "node_modules/map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "license": "MIT", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/markdown-table": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz", - "integrity": "sha512-9i/E3ZtVAoaDulRQjoPseX2X5pBNdeR8MInQb57JFvCAq4glz/w2q31eL0NHMKOntzy2D6X3plZDH4+OGuz5Fw==", + "resolved": "https://registry.npmjs.org/map-merge/-/map-merge-1.1.0.tgz", + "integrity": "sha512-TGNNg3WqoLsS5HnlK6GHIXFvM/0wYMtlyflc1nAQUhgptr9wIu7JwQ2YsqnpFSqbSYFv2U6rgXQTHJmWJOtlzQ==", "license": "MIT" }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "license": "MIT" - }, - "node_modules/maybe-combine-errors": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/mdmanifest": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/mdmanifest/-/mdmanifest-1.0.8.tgz", - "integrity": "sha512-x7QEK3/RbANozWnUhmSPzT01yL/PsO2zBDWzDRr+tMRs0o/HQxbDzGb34JiQ2bGnSf88eKuMgrAzMR1+hyRBLw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0", - "remark": "^3.2.2", - "remark-html": "^2.0.2", - "word-wrap": "^1.1.0" - }, - "bin": { - "mdm": "mdm.js" - } - }, "node_modules/mdn-data": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", @@ -8062,90 +4764,29 @@ }, "node_modules/media-typer": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", - "dev": true, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "license": "MIT", - "dependencies": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=6" - } - }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^4.1.0" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", - "license": "MIT", - "dependencies": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" + "node": ">=10.0.0" } }, "node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8153,6 +4794,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8161,30 +4804,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -8209,71 +4828,26 @@ }, "node_modules/minimist": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/minipass": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "license": "MIT", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mkdirp": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -8284,39 +4858,20 @@ }, "node_modules/mkdirp-classic": { "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, - "node_modules/mock-property": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.1", - "functions-have-names": "^1.2.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "hasown": "^2.0.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/module-alias": { "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", "license": "MIT" }, - "node_modules/module-error": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/moment": { "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", "engines": { "node": "*" @@ -8324,18 +4879,26 @@ }, "node_modules/monotonic-timestamp": { "version": "0.0.9", + "resolved": "https://registry.npmjs.org/monotonic-timestamp/-/monotonic-timestamp-0.0.9.tgz", + "integrity": "sha512-PQcys7iTcXiMyW8cgK5B/fhKGteB/WLqWN1f+s7IJHyyRd80JnJYoNo03kTQcGZhVtsKz5kEWiS7W94gfY6qcQ==", "license": "MIT" }, "node_modules/moo": { "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", "license": "BSD-3-Clause" }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/multiblob": { "version": "1.13.8", + "resolved": "https://registry.npmjs.org/multiblob/-/multiblob-1.13.8.tgz", + "integrity": "sha512-YXNUflHc7I7iUiLctHMTvwDQOjMbCv+Pdbf+1lScFOsjP/CSZgwblRknPntcD+KEPGFudXPBtJR10TIrun3zmQ==", "license": "MIT", "dependencies": { "blake2s": "^1.0.1", @@ -8355,24 +4918,15 @@ "stream-to-pull-stream": "^1.7.2" } }, - "node_modules/multiblob-http": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "pull-many": "^1.0.9", - "pull-stream": "^3.6.14", - "range-parser": "^1.2.1", - "stream-to-pull-stream": "^1.7.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/multicb": { - "version": "1.2.2" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/multicb/-/multicb-1.2.2.tgz", + "integrity": "sha512-PZM4dhYFmCF6uZGWpEmoPMUqJBywS9IcAgybT2GmSpYI1BvGvoWSdbio+ik+q/YD2vodhvslESWIS3NnkKYdqQ==" }, "node_modules/multiserver": { "version": "3.8.2", + "resolved": "https://registry.npmjs.org/multiserver/-/multiserver-3.8.2.tgz", + "integrity": "sha512-gmR/5dY+N81EN0yDaziSJXAJhJjG+3Rv3lJc74OSA1ySRKOMqZwEuGJjqG6ZcZiaqcHvhQZkZavDJsAwtmnvoA==", "license": "MIT", "dependencies": { "debug": "^4.1.1", @@ -8391,6 +4945,8 @@ }, "node_modules/multiserver-address": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/multiserver-address/-/multiserver-address-1.0.1.tgz", + "integrity": "sha512-IfZMAGs9onCLkYNSnNBri3JxuvhQYllMyh3W9ry86iEDcfW9uPVsHTHDsjDxQtL+dPq3byshmA+Y4LN2wLHwNw==", "license": "MIT", "dependencies": { "nearley": "^2.15.1" @@ -8398,6 +4954,8 @@ }, "node_modules/multiserver-scopes": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/multiserver-scopes/-/multiserver-scopes-2.0.0.tgz", + "integrity": "sha512-XWv9J617i3mWtZIZQNTpYI9iq4goUpsKy3GdUEDls23z1VaMzuRp2rL3S3IKrheVdgmrf0zHbErcXokGxqQfzw==", "license": "MIT", "dependencies": { "non-private-ip": "^2.0.0" @@ -8405,197 +4963,45 @@ }, "node_modules/mutexify": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", + "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", "license": "MIT", "dependencies": { "queue-tick": "^1.0.0" } }, - "node_modules/muxrpc": { - "version": "8.0.0", - "license": "MIT", - "dependencies": { - "clarify-error": "^1.0.0", - "debug": "^4.3.3", - "packet-stream": "~2.0.0", - "packet-stream-codec": "^1.2.0", - "pull-goodbye": "0.0.3", - "pull-stream": "^3.6.10" - } - }, - "node_modules/muxrpc-usage": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "right-pad": "^1.0.1" - } - }, "node_modules/muxrpc-validation": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/muxrpc-validation/-/muxrpc-validation-3.0.2.tgz", + "integrity": "sha512-iWo/23xFnl+IGeX+LlfwoVKtyY4volPSodf3nwPScPgxjws4k2ZUozPG98OouMA0yn0JamqApjRw7eqLrzyV2A==", "license": "MIT", "dependencies": { "pull-stream": "^3.6.11", "zerr": "^1.0.4" } }, - "node_modules/muxrpcli": { - "version": "3.1.2", - "license": "MIT", - "dependencies": { - "cont": "^1.0.3", - "minimist": "^1.2.0", - "muxrpc-usage": "^2.0.1", - "pull-stream": "^3.6.9", - "stream-to-pull-stream": "^1.7.3", - "word-wrap": "^1.2.3" - } - }, - "node_modules/mv": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/mv/node_modules/glob": { - "version": "6.0.4", - "license": "ISC", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mv/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mv/node_modules/rimraf": { - "version": "2.4.5", - "license": "ISC", - "dependencies": { - "glob": "^6.0.1" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/nan": { - "version": "2.26.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", - "integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==", - "license": "MIT", - "optional": true - }, "node_modules/nano-equal": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/nano-equal/-/nano-equal-2.0.2.tgz", + "integrity": "sha512-RtAioZtSSppEWRruYU8oz2AKPJWTW74TpzcqP79n7Kh4nHW+gmrJGcTuELmid9tvGR4Xp2cCoTtYIIDy9iWO4g==", "license": "MIT" }, "node_modules/nanoassert": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", + "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", "license": "ISC" }, - "node_modules/nanoid": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", - "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/napi-macros": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", "license": "MIT" }, - "node_modules/ncp": { - "version": "2.0.0", - "license": "MIT", - "bin": { - "ncp": "bin/ncp" - } - }, "node_modules/nearley": { "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", "license": "MIT", "dependencies": { "commander": "^2.19.0", @@ -8616,10 +5022,14 @@ }, "node_modules/nearley/node_modules/commander": { "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8627,57 +5037,14 @@ }, "node_modules/next-tick": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "license": "ISC" }, - "node_modules/nice-try": { - "version": "1.0.5", - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "8.5.0", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-api-headers": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.8.0.tgz", - "integrity": "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==", - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-gyp-build": { "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -8685,409 +5052,22 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-llama-cpp": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/node-llama-cpp/-/node-llama-cpp-3.16.2.tgz", - "integrity": "sha512-ovhuTaXSWfcoyfI8ljWxO2Rg63mNxqQQAbDGkXRhlgsL7UjPqm2Nsy1bTNa0ZaQRg3vezG4agnCJTImrICY/0A==", - "hasInstallScript": true, + "node_modules/node-localstorage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz", + "integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==", "license": "MIT", "dependencies": { - "@huggingface/jinja": "^0.5.5", - "async-retry": "^1.3.3", - "bytes": "^3.1.2", - "chalk": "^5.6.2", - "chmodrp": "^1.0.2", - "cmake-js": "^8.0.0", - "cross-spawn": "^7.0.6", - "env-var": "^7.5.0", - "filenamify": "^6.0.0", - "fs-extra": "^11.3.0", - "ignore": "^7.0.4", - "ipull": "^3.9.5", - "is-unicode-supported": "^2.1.0", - "lifecycle-utils": "^3.1.1", - "log-symbols": "^7.0.1", - "nanoid": "^5.1.6", - "node-addon-api": "^8.5.0", - "octokit": "^5.0.5", - "ora": "^9.3.0", - "pretty-ms": "^9.3.0", - "proper-lockfile": "^4.1.2", - "semver": "^7.7.1", - "simple-git": "^3.31.1", - "slice-ansi": "^8.0.0", - "stdout-update": "^4.0.1", - "strip-ansi": "^7.1.2", - "validate-npm-package-name": "^7.0.2", - "which": "^6.0.1", - "yargs": "^17.7.2" - }, - "bin": { - "nlc": "dist/cli/cli.js", - "node-llama-cpp": "dist/cli/cli.js" + "write-file-atomic": "^1.1.4" }, "engines": { - "node": ">=20.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/giladgd" - }, - "optionalDependencies": { - "@node-llama-cpp/linux-arm64": "3.16.2", - "@node-llama-cpp/linux-armv7l": "3.16.2", - "@node-llama-cpp/linux-x64": "3.16.2", - "@node-llama-cpp/linux-x64-cuda": "3.16.2", - "@node-llama-cpp/linux-x64-cuda-ext": "3.16.2", - "@node-llama-cpp/linux-x64-vulkan": "3.16.2", - "@node-llama-cpp/mac-arm64-metal": "3.16.2", - "@node-llama-cpp/mac-x64": "3.16.2", - "@node-llama-cpp/win-arm64": "3.16.2", - "@node-llama-cpp/win-x64": "3.16.2", - "@node-llama-cpp/win-x64-cuda": "3.16.2", - "@node-llama-cpp/win-x64-cuda-ext": "3.16.2", - "@node-llama-cpp/win-x64-vulkan": "3.16.2" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/node-llama-cpp/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/node-llama-cpp/node_modules/ignore": { - "version": "7.0.5", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/node-llama-cpp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/node-llama-cpp/node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-llama-cpp/node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-llama-cpp/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/node-llama-cpp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/nodemon": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", - "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^10.2.1", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/nodemon/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/nodemon/node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/nodemon/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nodemon/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/nodemon/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nodemon/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/nodemon/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nodemon/node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nodemon/node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nodemon/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nodemon/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "node": ">=0.12" } }, "node_modules/non-private-ip": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/non-private-ip/-/non-private-ip-2.2.0.tgz", + "integrity": "sha512-NZ3Upr3K2whD6vdZ9k8gHsijsrQl5O6IARLIUDyvQwSuO/owM1kOMu8wDMMsIR8ujlLvhPNjlTZC2SXzWwWByQ==", "license": "MIT", "dependencies": { "ip": "^1.1.5" @@ -9096,1417 +5076,19 @@ "non-private-ip": "bin.js" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-path": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/normalize-uri": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/normalize-uri/-/normalize-uri-1.1.3.tgz", - "integrity": "sha512-ECNgiM5IAeZNuXYu5kF4JV8t+MSFixHsvjexQtf/bLTOsL+KycDv3pod1a88N8uHtzsktYLRX6cAawg4aHeLVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/npm-install-package": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", - "integrity": "sha512-F37OfFN2Fk6ixI3bn8CNllSGMjpRxTqPcnN5j2zFlqNiaf/LGG6fCq3DeBGKRinyVMR5ROS34nWKQmn7tABuJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/npm-prefix": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/npm-prefix/-/npm-prefix-1.2.0.tgz", - "integrity": "sha512-EkGZ7jtA2onsULFpnZ/P5S0DPy8w9qH1TVytPhY54s+dmtLXBmp1evt8W9nfg5JEay24K3bX9WWTIHR8WQcOJA==", - "license": "MIT", - "dependencies": { - "rc": "^1.1.0", - "shellsubstitute": "^1.1.0", - "untildify": "^2.1.0" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", - "integrity": "sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==", - "bundleDependencies": [ - "archy", - "arrify", - "caching-transform", - "convert-source-map", - "find-cache-dir", - "find-up", - "foreground-child", - "glob", - "istanbul-lib-coverage", - "istanbul-lib-hook", - "istanbul-lib-report", - "istanbul-lib-source-maps", - "istanbul-reports", - "make-dir", - "merge-source-map", - "resolve-from", - "rimraf", - "signal-exit", - "spawn-wrap", - "test-exclude", - "uuid", - "yargs", - "yargs-parser" - ], - "dev": true, - "license": "ISC", - "dependencies": { - "archy": "^1.0.0", - "arrify": "^1.0.1", - "caching-transform": "^3.0.1", - "convert-source-map": "^1.6.0", - "find-cache-dir": "^2.0.0", - "find-up": "^3.0.0", - "foreground-child": "^1.5.6", - "glob": "^7.1.3", - "istanbul-lib-coverage": "^2.0.3", - "istanbul-lib-hook": "^2.0.3", - "istanbul-lib-instrument": "^3.1.0", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.2", - "istanbul-reports": "^2.1.1", - "make-dir": "^1.3.0", - "merge-source-map": "^1.1.0", - "resolve-from": "^4.0.0", - "rimraf": "^2.6.3", - "signal-exit": "^3.0.2", - "spawn-wrap": "^1.4.2", - "test-exclude": "^5.1.0", - "uuid": "^3.3.2", - "yargs": "^12.0.5", - "yargs-parser": "^11.1.1" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/ansi-regex": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/append-transform": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "default-require-extensions": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/archy": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/arrify": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/async": { - "version": "2.6.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.11" - } - }, - "node_modules/nyc/node_modules/balanced-match": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/nyc/node_modules/caching-transform": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "hasha": "^3.0.0", - "make-dir": "^1.3.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/camelcase": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "4.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/nyc/node_modules/code-point-at": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/commander": { - "version": "2.17.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/commondir": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/convert-source-map": { - "version": "1.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/nyc/node_modules/cross-spawn": { - "version": "4.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "node_modules/nyc/node_modules/debug": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nyc/node_modules/decamelize": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/default-require-extensions": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/end-of-stream": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/nyc/node_modules/error-ex": { - "version": "1.3.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/nyc/node_modules/es6-error": { - "version": "4.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/execa": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/nyc/node_modules/find-cache-dir": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/foreground-child": { - "version": "1.5.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" - } - }, - "node_modules/nyc/node_modules/fs.realpath": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/get-caller-file": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/get-stream": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/glob": { - "version": "7.1.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nyc/node_modules/graceful-fs": { - "version": "4.1.15", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/handlebars": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/nyc/node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/has-flag": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/hasha": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-stream": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/hosted-git-info": { - "version": "2.7.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/nyc/node_modules/inflight": { - "version": "1.0.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/nyc/node_modules/inherits": { - "version": "2.0.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/invert-kv": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/is-arrayish": { - "version": "0.2.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/is-stream": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/istanbul-lib-coverage": { - "version": "2.0.3", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-hook": { - "version": "2.0.3", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "append-transform": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-report": { - "version": "2.0.4", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^2.0.3", - "make-dir": "^1.3.0", - "supports-color": "^6.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-source-maps": { - "version": "3.0.2", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.3", - "make-dir": "^1.3.0", - "rimraf": "^2.6.2", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/istanbul-reports": { - "version": "2.1.1", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "handlebars": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/json-parse-better-errors": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/lcid": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "invert-kv": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/load-json-file": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/lodash": { - "version": "4.17.11", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/lodash.flattendeep": { - "version": "4.4.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/lru-cache": { - "version": "4.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/nyc/node_modules/make-dir": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/map-age-cleaner": { - "version": "0.1.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-defer": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/mem": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/merge-source-map": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/nyc/node_modules/merge-source-map/node_modules/source-map": { - "version": "0.6.1", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/mimic-fn": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/minimatch": { - "version": "3.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nyc/node_modules/minimist": { - "version": "0.0.10", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/mkdirp": { - "version": "0.5.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/nyc/node_modules/mkdirp/node_modules/minimist": { - "version": "0.0.8", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/ms": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/nice-try": { - "version": "1.0.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/normalize-package-data": { - "version": "2.5.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/nyc/node_modules/npm-run-path": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/number-is-nan": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/once": { - "version": "1.4.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/nyc/node_modules/optimist": { - "version": "0.6.1", - "inBundle": true, - "license": "MIT/X11", - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "node_modules/nyc/node_modules/os-homedir": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/os-locale": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/p-defer": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/p-finally": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/p-is-promise": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/p-try": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/package-hash": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/parse-json": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/path-exists": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/path-is-absolute": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/path-key": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/path-parse": { - "version": "1.0.6", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/path-type": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/pify": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/pkg-dir": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/pseudomap": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/pump": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/nyc/node_modules/read-pkg": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/read-pkg-up": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/release-zalgo": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/require-directory": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/require-main-filename": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/resolve": { - "version": "1.10.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-parse": "^1.0.6" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/rimraf": { - "version": "2.6.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/nyc/node_modules/safe-buffer": { - "version": "5.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/nyc/node_modules/semver": { - "version": "5.6.0", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nyc/node_modules/set-blocking": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/shebang-command": { - "version": "1.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/shebang-regex": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/signal-exit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/spawn-wrap": { - "version": "1.4.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", - "signal-exit": "^3.0.2", - "which": "^1.3.0" - } - }, - "node_modules/nyc/node_modules/spdx-correct": { - "version": "3.1.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/nyc/node_modules/spdx-exceptions": { - "version": "2.2.0", - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/nyc/node_modules/spdx-expression-parse": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/nyc/node_modules/spdx-license-ids": { - "version": "3.0.3", - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/nyc/node_modules/string-width": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/strip-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/strip-bom": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nyc/node_modules/strip-eof": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/test-exclude": { - "version": "5.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "arrify": "^1.0.1", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/uglify-js": { - "version": "3.4.9", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/nyc/node_modules/uglify-js/node_modules/source-map": { - "version": "0.6.1", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/uuid": { - "version": "3.3.2", - "inBundle": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/nyc/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/nyc/node_modules/which": { - "version": "1.3.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/nyc/node_modules/which-module": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/wordwrap": { - "version": "0.0.3", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "2.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/wrappy": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/write-file-atomic": { - "version": "2.4.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/yallist": { - "version": "2.1.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/yargs": { - "version": "12.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "11.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "license": "MIT", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/object-inspect": { "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -10515,50 +5097,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.6", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-visit/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.assign": { "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10577,6 +5128,8 @@ }, "node_modules/object.entries": { "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -10588,92 +5141,28 @@ "node": ">= 0.4" } }, - "node_modules/object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", - "license": "MIT", - "dependencies": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/observ": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/observ/-/observ-0.2.0.tgz", - "integrity": "sha512-7SKbYTiFXMvqbeinO/GwTGtXSvbRq5AHKszmq5cx3fV7bKEjNCK7AdkhGzsuQAoT+IPf3mlxDmPrVW8wgshpcQ==" - }, - "node_modules/observ-debounce": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/observ-debounce/-/observ-debounce-1.1.1.tgz", - "integrity": "sha512-vP3EdCDE0+6tftznrvmmfI66qiOEkSV+zJ/TLo/3x5BZjm0a3I9A12V16iPck1v2cwAgGRPtWVF3vUmFN9vtPw==", - "license": "MIT", - "dependencies": { - "observ": "~0.2.0" - } - }, "node_modules/obv": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/obv/-/obv-0.0.1.tgz", + "integrity": "sha512-Iq/Q3TXAfHgABGL7mKlG3ILJnT8jgetOt0sH9a6SGLLCvjh58bsH7RzixLJWkVO6aQK6hI4rxVYHF0rk9KmIwA==", "license": "MIT" }, "node_modules/obz": { "version": "1.1.1", - "license": "MIT" - }, - "node_modules/octokit": { - "version": "5.0.5", - "license": "MIT", - "dependencies": { - "@octokit/app": "^16.1.2", - "@octokit/core": "^7.0.6", - "@octokit/oauth-app": "^8.0.3", - "@octokit/plugin-paginate-graphql": "^6.0.0", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "@octokit/plugin-retry": "^8.0.3", - "@octokit/plugin-throttling": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "@octokit/webhooks": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/on-change-network": { - "version": "0.0.2", + "resolved": "https://registry.npmjs.org/obz/-/obz-1.1.1.tgz", + "integrity": "sha512-eUiAX663dASOUIEWuRzUycf2ERZJFLbdjfzYRtju9z2eBfDPywcWjfyHV5JNbVoBjuHGVodyfeyy8Qu09+JCXw==", "license": "MIT" }, "node_modules/on-change-network-strict": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/on-change-network-strict/-/on-change-network-strict-1.0.0.tgz", + "integrity": "sha512-ldHCpTJWgr5KUJy3/TVoSGNwBUA8BP9UFmd0iQqe4aGaXY4PJyzQPiVBIo8VBSlSoKyaJY3vcpW0hixZb6gPaA==", "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -10684,79 +5173,28 @@ }, "node_modules/on-wakeup": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-wakeup/-/on-wakeup-1.0.1.tgz", + "integrity": "sha512-3ufOvnTvh39ah2/TT++HpLailHVmEVVrKtzKLKifAUyWbulKLGGJGOF7ywKC4k/iQGmn9KooV6WmQl/6BVwklA==", "license": "MIT" }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/only": { - "version": "0.0.2" - }, - "node_modules/onnx-proto": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz", - "integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==", - "license": "MIT", - "dependencies": { - "protobufjs": "^6.8.8" - } - }, - "node_modules/onnxruntime-common": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz", - "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==", - "license": "MIT" - }, - "node_modules/onnxruntime-node": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz", - "integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==", - "license": "MIT", - "optional": true, - "os": [ - "win32", - "darwin", - "linux" - ], - "dependencies": { - "onnxruntime-common": "~1.14.0" - } - }, - "node_modules/onnxruntime-web": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz", - "integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==", - "license": "MIT", - "dependencies": { - "flatbuffers": "^1.12.0", - "guid-typescript": "^1.0.9", - "long": "^4.0.0", - "onnx-proto": "^4.0.4", - "onnxruntime-common": "~1.14.0", - "platform": "^1.3.6" - } + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" }, "node_modules/open": { "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", @@ -10770,13 +5208,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "license": "MIT", - "bin": { - "opencollective-postinstall": "index.js" - } - }, "node_modules/openpgp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/openpgp/-/openpgp-6.3.0.tgz", @@ -10786,107 +5217,25 @@ "node": ">= 18.0.0" } }, - "node_modules/ora": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", - "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.1", - "string-width": "^8.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/cli-spinners": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", - "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", - "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } + "node_modules/opusscript": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.1.1.tgz", + "integrity": "sha512-mL0fZZOUnXdZ78woRXp18lApwpp0lF5tozJOD1Wut0dgrA9WuQTgSels/CSmFleaAZrJi/nci5KOVtbuxeWoQA==", + "license": "MIT" }, "node_modules/os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/own-keys": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", @@ -10902,49 +5251,17 @@ }, "node_modules/p-defer": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/p-try": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "license": "MIT", "engines": { "node": ">=6" @@ -10952,10 +5269,14 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, "node_modules/packet-stream": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/packet-stream/-/packet-stream-2.0.6.tgz", + "integrity": "sha512-kSxHpoTqlgNEetMp77snCTVILwLw4dJX6pB/z1g1PRG5xylH8cf9upIPygt+epBC3l14XrcZH4/kQYSrzp2Ijg==", "license": "MIT", "engines": { "node": ">=8" @@ -10963,6 +5284,8 @@ }, "node_modules/packet-stream-codec": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/packet-stream-codec/-/packet-stream-codec-1.2.0.tgz", + "integrity": "sha512-3xoTsSVqCPd+0mPsQGlfYm2ecvJK9tS1HOxrjnKEiB1Ynq0fOJHEXcZV/hxW6BkOSGBsGX7dTN8bjdNTU3nKBA==", "license": "MIT", "dependencies": { "pull-reader": "^1.3.1", @@ -10972,51 +5295,26 @@ "node": ">=12" } }, - "node_modules/parse-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", - "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", - "license": "MIT", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "node_modules/parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", - "license": "MIT", - "dependencies": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } + "node_modules/pako": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", + "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" }, "node_modules/parse-ms": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", "license": "MIT", "engines": { "node": ">=6" @@ -11036,22 +5334,23 @@ }, "node_modules/parseurl": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "license": "MIT", "engines": { "node": ">=8" @@ -11059,6 +5358,8 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11066,6 +5367,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -11073,10 +5376,14 @@ }, "node_modules/path-parse": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -11091,33 +5398,14 @@ }, "node_modules/path-to-regexp": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "license": "MIT" }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pdfjs-dist": { - "version": "5.3.31", - "license": "Apache-2.0", - "engines": { - "node": ">=20.16.0 || >=22.3.0" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^0.1.67" - } - }, "node_modules/peek-readable": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", "license": "MIT", "engines": { "node": ">=8" @@ -11127,77 +5415,10 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "license": "MIT" - }, - "node_modules/plur": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", - "integrity": "sha512-WhcHk576xg9y/iv6RWOuroZgsqvCbJN+XGvAypCJwLAYs2iWDp5LUmvaCdV6JR2O0SMBf8l6p7A94AyLCFVMlQ==", - "license": "MIT", - "dependencies": { - "irregular-plurals": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pngjs": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -11205,126 +5426,27 @@ }, "node_modules/polyraf": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/polyraf/-/polyraf-1.1.0.tgz", + "integrity": "sha512-wKRyhZQE6AC70nJyMCWbwd/dX4S6UsFz+58qAF8HTpCn6C7pvr63FiH4vR9vT6VrhZrJKuT7A81ea/lIslS0bA==", "license": "MIT", "dependencies": { "random-access-file": "^2.1.0", "random-access-web": "^2.0.1" } }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/prebuild-install/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-bytes": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", - "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", - "license": "MIT", - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-ms": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", "license": "MIT", "dependencies": { "parse-ms": "^2.1.0" @@ -11338,10 +5460,14 @@ }, "node_modules/print-tree": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/print-tree/-/print-tree-0.1.5.tgz", + "integrity": "sha512-t5eFtuheMgWGR3kj1vtxsvfWrrecEEWow3IgEtbW0EPbYRufHAvB+ShK1DgRjWxEYYEqO4B6DNlkoPmh7E3LGQ==", "license": "MIT" }, "node_modules/private-box": { "version": "0.3.1", + "resolved": "https://registry.npmjs.org/private-box/-/private-box-0.3.1.tgz", + "integrity": "sha512-abAuk3ZDyQvPLY6MygtwaDTUBIZ0C5wMMuX1jXa0svazV+keTwn7cPobRv4WYA9ctsDUztm/9CYu4y2TPL08xw==", "license": "MIT", "dependencies": { "chloride": "^2.2.9" @@ -11349,6 +5475,8 @@ }, "node_modules/private-group-spec": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/private-group-spec/-/private-group-spec-1.2.0.tgz", + "integrity": "sha512-O7SfG+vZIZgqDXy/wjsuTRI5LaozW4rxaZBpGmwlcDfjIvxvYWNboyNm1PoQUU6j4dQ02V1tOQVLDq9u2RzolA==", "license": "LGPL-3.0-only", "dependencies": { "is-my-ssb-valid": "^1.2.0" @@ -11356,6 +5484,8 @@ }, "node_modules/process": { "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -11363,10 +5493,14 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, "node_modules/promisify-4loc": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/promisify-4loc/-/promisify-4loc-1.0.0.tgz", + "integrity": "sha512-u/XtndUyqqDXAuhFEgFgkpjHG8IizREoj80j5dL4t41eE9yH0gzFPyOD21/VnikdPJtRziuqf6ryTu1HoTjyog==", "license": "MIT", "engines": { "node": ">=6" @@ -11374,6 +5508,8 @@ }, "node_modules/promisify-tuple": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/promisify-tuple/-/promisify-tuple-1.2.0.tgz", + "integrity": "sha512-DRI8QrLUzbQxgwLiwKhtVCpSqtAUnnyPaCi3cad2+0avb2o5UzobLWHkXUOAYQB8e4fSJVef22eVm77c/8n//g==", "license": "MIT", "engines": { "node": ">=6" @@ -11381,58 +5517,10 @@ }, "node_modules/promisize": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/promisize/-/promisize-1.1.2.tgz", + "integrity": "sha512-6/X05CD1iri6YyLy6TW7a23HY0igsrb/qetltYKfJznLfzmspWtN/cY/UR0By3M5i13hBDWfmM2P42ovKl3GAw==", "license": "MIT" }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "license": "ISC" - }, - "node_modules/protobufjs": { - "version": "6.11.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz", - "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -11444,42 +5532,35 @@ }, "node_modules/prr": { "version": "1.0.1", - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "license": "MIT" }, "node_modules/pull-abortable": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/pull-abortable/-/pull-abortable-4.1.1.tgz", + "integrity": "sha512-/zz8tW+T5tvtDc4NQVZTp3lJZoGEpUb2VDV3R5Q2/9lavNTwHK+hC5yF/3Q3LMmdn5AluFy23RFoqXIoySPl1w==", "license": "MIT" }, "node_modules/pull-async": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pull-async/-/pull-async-1.0.0.tgz", + "integrity": "sha512-mm0mZe9tb3yjIhfXt384c8EXGFAtMbau20At2yNqZDPXHevjrfom5b82sk8BMQfGhj6gXimnOde1lhUKkF/v9w==", "license": "Apache-2.0" }, "node_modules/pull-awaitable": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pull-awaitable/-/pull-awaitable-1.0.0.tgz", + "integrity": "sha512-tfufzU9wMd1rM38tcgunaKMUNGXMj1Qms1ygEw+ZerkMmLAwilEtMsyzrDSLhNlnfhSzHXovOarieSNrTD1ovQ==", "license": "MIT", "dependencies": { "pull-thenable": "~1.0.0" } }, - "node_modules/pull-bitflipper": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pull-bitflipper/-/pull-bitflipper-0.1.1.tgz", - "integrity": "sha512-MloQIUQkooVrlc4qom1rDeUsnx9Fol4ieMhlJEjpT/v2x37+ydL164jbRkvDFCpWBp+9qLKPgWoQum/zh8iHkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pull-stream": "^3.6.11" - } - }, "node_modules/pull-box-stream": { "version": "1.0.13", + "resolved": "https://registry.npmjs.org/pull-box-stream/-/pull-box-stream-1.0.13.tgz", + "integrity": "sha512-OPg5cTnBCH9jpTCNZfm+BrtrAzmu5NsWDyDadaWiB06oWQujSMfRJp58VqhuJOCEgxVkeKFhXTaSKE+StNKRHw==", "license": "MIT", "dependencies": { "chloride": "^2.2.7", @@ -11492,18 +5573,26 @@ }, "node_modules/pull-cat": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/pull-cat/-/pull-cat-1.1.11.tgz", + "integrity": "sha512-i3w+xZ3DCtTVz8S62hBOuNLRHqVDsHMNZmgrZsjPnsxXUgbWtXEee84lo1XswE7W2a3WHyqsNuDJTjVLAQR8xg==", "license": "MIT" }, "node_modules/pull-catch": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pull-catch/-/pull-catch-1.1.0.tgz", + "integrity": "sha512-x6DkWZfBVvYrjUIcFroe3GtBkZcRINi8PsYgx3qBYluIeawLn4Z5oJI++WE5AJPbIZBmxHFx+zIvQmhRYaSeFA==", "license": "ISC" }, "node_modules/pull-cont": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pull-cont/-/pull-cont-0.1.1.tgz", + "integrity": "sha512-OAJRFBoEW00F0++qkeNuIWdaUwqrlZGj/6JAm+094iWKDnuzBXUWi2IipDqNa+yzIJrOE8BjwugiyGtLvPAk4w==", "license": "MIT" }, "node_modules/pull-cursor": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pull-cursor/-/pull-cursor-3.0.0.tgz", + "integrity": "sha512-95lZVSF2eSEdOmUtlOBaD9p5YOvlYeCr5FBv2ySqcj/4rpaXI6d8OH+zPHHjKAf58R8QXJRZuyfHkcCX8TZbAg==", "license": "MIT", "dependencies": { "looper": "^4.0.0", @@ -11513,10 +5602,14 @@ }, "node_modules/pull-defer": { "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pull-defer/-/pull-defer-0.2.3.tgz", + "integrity": "sha512-/An3KE7mVjZCqNhZsr22k1Tx8MACnUnHZZNPSJ0S62td8JtYr/AiRG42Vz7Syu31SoTLUzVIe61jtT/pNdjVYA==", "license": "MIT" }, "node_modules/pull-drain-gently": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pull-drain-gently/-/pull-drain-gently-1.1.0.tgz", + "integrity": "sha512-ZUPsNrn8jkU6Y2B4w8Jz3gXAmjSpb+qn4AQhAL8qTWUHULglH16ANr+6qnfOEa1kUoUGVCQZaORTd2NSQFAnhA==", "license": "MIT", "dependencies": { "cpu-percentage": "~1.0.3", @@ -11525,6 +5618,8 @@ }, "node_modules/pull-file": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pull-file/-/pull-file-1.1.0.tgz", + "integrity": "sha512-yP3hNN3C0PrxA5isqyzHvsvFxd7Ohjvp4aLreeHm8mhnipxUfbY08Mk7o/KDsnR8N4WT9W3CqMDysU+ZufMr6w==", "license": "MIT", "dependencies": { "pull-utf8-decoder": "^1.0.2" @@ -11532,6 +5627,8 @@ }, "node_modules/pull-flat-merge": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pull-flat-merge/-/pull-flat-merge-2.1.0.tgz", + "integrity": "sha512-V/HzK88m+N867KfDkDA0u9lHrQIcLMBsSllVReO4mU3hJ2PaPOPxtTXQuQc+eIWMHavWqaWSkim8O8s2P4dTLg==", "license": "ISC", "dependencies": { "pull-many": "^1.0.8", @@ -11540,10 +5637,14 @@ }, "node_modules/pull-flatmap": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pull-flatmap/-/pull-flatmap-0.0.1.tgz", + "integrity": "sha512-9BgwZPZ6J22kPf9ExoVI3m2yMHdCK8uPf58p6L63t36IgH7NrCX+p3QV8cQ4JmYjwvXDZzimVuJ7IW7iLxm7cA==", "license": "MIT" }, "node_modules/pull-fs": { "version": "1.1.6", + "resolved": "https://registry.npmjs.org/pull-fs/-/pull-fs-1.1.6.tgz", + "integrity": "sha512-xO+d44h/FdlSW61RpxUdqLhNz5vHKnjqEyGaXIVvZa5TxkOvIzhkWd7BXqH8oakXCxb8NozN6F/w1BtfP8Z64Q==", "license": "MIT", "dependencies": { "pull-file": "^0.5.0", @@ -11554,6 +5655,8 @@ }, "node_modules/pull-fs/node_modules/pull-file": { "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pull-file/-/pull-file-0.5.0.tgz", + "integrity": "sha512-4s/IfBLebH/eWUlhp3F3M29mk+B1cryp3W99t/k9J/c033f+HyLLVB2EAMGoN02j8ff7ObkLF1tMaHok3nGS6Q==", "license": "MIT", "dependencies": { "pull-utf8-decoder": "^1.0.2" @@ -11561,6 +5664,8 @@ }, "node_modules/pull-glob": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/pull-glob/-/pull-glob-1.0.7.tgz", + "integrity": "sha512-nQrhcraiOUfMYiFoJyGLICb5AzlmWm5Vzwhqt+wNuimCG1kqrjdWycw5Dk9xQbHYbq5U55hs3Hee0qZgpjZ3bA==", "license": "MIT", "dependencies": { "pull-fs": "~1.1.6", @@ -11572,6 +5677,8 @@ }, "node_modules/pull-goodbye": { "version": "0.0.3", + "resolved": "https://registry.npmjs.org/pull-goodbye/-/pull-goodbye-0.0.3.tgz", + "integrity": "sha512-fl3RcIHKsxFaygdU3dcwSznLr73HYGOEU9IshpiatYSV+PW3TOUEjtfdu1L8uIsUoDajJz3HM/+mG0mFD4+v5A==", "license": "MIT", "dependencies": { "pull-stream": "^3.6.14" @@ -11579,6 +5686,8 @@ }, "node_modules/pull-handshake": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pull-handshake/-/pull-handshake-1.1.4.tgz", + "integrity": "sha512-+dT8auWatbSNt1o43GKygECvOM2znXdyWwhEQaxztJSvxrNtEqo/wgZttknLfyxxbkbS6qUten6LsXTOwYj0yg==", "license": "MIT", "dependencies": { "pull-cat": "^1.1.9", @@ -11589,10 +5698,14 @@ }, "node_modules/pull-hash": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pull-hash/-/pull-hash-1.0.0.tgz", + "integrity": "sha512-m+6RcvwRdpYLxWgSmviXo3tmZHdpniWDJj3ryC39RYH4oc6AXhkoBHkQuA4yhmkM0WQh+OP+vD9F5oCozhhxIg==", "license": "Fair" }, "node_modules/pull-inactivity": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/pull-inactivity/-/pull-inactivity-2.1.4.tgz", + "integrity": "sha512-W2Q+6Jk0oRMICYXBXom3/ipz2U5YPUSQUfLvVgUqL/daHop7QQB3Jz5XpnnxsekCPoM61lGvXc7kFDZt0uWzMg==", "license": "MIT", "dependencies": { "pull-abortable": "^4.1.1", @@ -11601,6 +5714,8 @@ }, "node_modules/pull-level": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pull-level/-/pull-level-2.0.4.tgz", + "integrity": "sha512-fW6pljDeUThpq5KXwKbRG3X7Ogk3vc75d5OQU/TvXXui65ykm+Bn+fiktg+MOx2jJ85cd+sheufPL+rw9QSVZg==", "license": "MIT", "dependencies": { "level-post": "^1.0.7", @@ -11614,6 +5729,8 @@ }, "node_modules/pull-live": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pull-live/-/pull-live-1.0.1.tgz", + "integrity": "sha512-tkNz1QT5gId8aPhV5+dmwoIiA1nmfDOzJDlOOUpU5DNusj6neNd3EePybJ5+sITr2FwyCs/FVpx74YMCfc8YeA==", "license": "MIT", "dependencies": { "pull-cat": "^1.1.9", @@ -11622,6 +5739,8 @@ }, "node_modules/pull-looper": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pull-looper/-/pull-looper-1.0.0.tgz", + "integrity": "sha512-djlD60A6NGe5goLdP5pgbqzMEiWmk1bInuAzBp0QOH4vDrVwh05YDz6UP8+pOXveKEk8wHVP+rB2jBrK31QMPA==", "license": "Fair", "dependencies": { "looper": "^4.0.0" @@ -11629,19 +5748,17 @@ }, "node_modules/pull-many": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/pull-many/-/pull-many-1.0.9.tgz", + "integrity": "sha512-+jUydDVlj/HsvtDqxWMSsiRq3B0HVo7RhBV4C0p2nZRS3mFTUEu9SPEBN+B5PMaW8KTnblYhTIaKg7oXgGnj4Q==", "license": "MIT", "dependencies": { "pull-stream": "^3.4.5" } }, - "node_modules/pull-next": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pull-next/-/pull-next-1.0.1.tgz", - "integrity": "sha512-CAOxYSfmFmQ+jxuwnmaXmovYOmVD+XC7X+5uyZh4hVg06DUK7O9uccsIQkGu493eGkAqASlmB5zekEH7oxS0AQ==", - "license": "MIT" - }, "node_modules/pull-notify": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pull-notify/-/pull-notify-0.1.2.tgz", + "integrity": "sha512-oooAxYEUGNbOVsUrmqqTWWsAUMRIs4sYglnxgleiVcWyvrWgOuk/WUoZDajPTsYix2/rd+z5xSclzHLA7QygcQ==", "license": "MIT", "dependencies": { "pull-pushable": "^2.0.0" @@ -11649,10 +5766,14 @@ }, "node_modules/pull-pair": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pull-pair/-/pull-pair-1.1.0.tgz", + "integrity": "sha512-7VEFLxWnj2AKaN3AvtyaM508d1+56/ulSjDLu4j6kcd9DnEM6LfG8b1R9gCLqzIjJo5stFrWynVc6e451OW4LQ==", "license": "MIT" }, "node_modules/pull-paramap": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pull-paramap/-/pull-paramap-1.2.2.tgz", + "integrity": "sha512-0ALwLcASif3KmKCXO4liySCKsTfC/mGYhRFch+xLvfJJrYOIfXPthrykDUZpeZtbjEXglPySzfUmYcnBqzTj5w==", "license": "MIT", "dependencies": { "looper": "^4.0.0" @@ -11660,10 +5781,14 @@ }, "node_modules/pull-pause": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/pull-pause/-/pull-pause-0.0.2.tgz", + "integrity": "sha512-yQs63NDgD/FeAsdF7INpy4uDolkstkS4Gx1Z3BvMR2gQS8RREJd2tsy6d/K6T+/CDStGnCQI7JLrkjz10YNiSA==", "license": "MIT" }, "node_modules/pull-ping": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pull-ping/-/pull-ping-2.0.3.tgz", + "integrity": "sha512-nbY4yHnMesJBrvkbhMim4VXUC9k1VCkgrkQu49pf8mxFbmb/U2KQrsuePvSmLjRL+VgkBVRSUXUoOY7DtSvhKw==", "license": "MIT", "dependencies": { "pull-pushable": "^2.0.0", @@ -11673,10 +5798,14 @@ }, "node_modules/pull-pushable": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pull-pushable/-/pull-pushable-2.2.0.tgz", + "integrity": "sha512-M7dp95enQ2kaHvfCt2+DJfyzgCSpWVR2h2kWYnVsW6ZpxQBx5wOu0QWOvQPVoPnBLUZYitYP2y7HyHkLQNeGXg==", "license": "MIT" }, "node_modules/pull-rate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pull-rate/-/pull-rate-1.0.2.tgz", + "integrity": "sha512-HH4fXHM7gu2uCO6u2SqTuGGDqPZNVHKXjTdyP9igg8jAROnvSph9plMAQqmv4elYFCm+Bfah2ASGWWe/+NdzMQ==", "license": "MIT", "dependencies": { "pull-stream": "^3.6.0" @@ -11684,14 +5813,14 @@ }, "node_modules/pull-reader": { "version": "1.3.1", - "license": "MIT" - }, - "node_modules/pull-sink-through": { - "version": "0.0.0", + "resolved": "https://registry.npmjs.org/pull-reader/-/pull-reader-1.3.1.tgz", + "integrity": "sha512-CBkejkE5nX50SiSEzu0Qoz4POTJMS/mw8G6aj3h3M/RJoKgggLxyF0IyTZ0mmpXFlXRcLmLmIEW4xeYn7AeDYw==", "license": "MIT" }, "node_modules/pull-sort": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pull-sort/-/pull-sort-1.0.2.tgz", + "integrity": "sha512-jGcAHMP+0Le+bEIhSODlbNNd3jW+S6XrXOlhVzfcKU5HQZjP92OzQSgHHSlwvWRsiTWi+UGgbFpL/5gGgmFoVQ==", "license": "MIT", "dependencies": { "pull-defer": "^0.2.3", @@ -11700,10 +5829,14 @@ }, "node_modules/pull-stream": { "version": "3.7.0", + "resolved": "https://registry.npmjs.org/pull-stream/-/pull-stream-3.7.0.tgz", + "integrity": "sha512-Eco+/R004UaCK2qEDE8vGklcTG2OeZSVm1kTUQNrykEjDwcFXDZhygFDsW49DbXyJMEhHeRL3z5cRVqPAhXlIw==", "license": "MIT" }, "node_modules/pull-thenable": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pull-thenable/-/pull-thenable-1.0.0.tgz", + "integrity": "sha512-gio2Yanuj5lcN8X+VXtx6F2iWxnhxfaHSKaSJHUzJMNNboEz48/+udzZFDaya8kAwj3DbuFFt1pbb8m1tIS6PQ==", "license": "MIT", "dependencies": { "quicktask": "~1.0.0" @@ -11711,6 +5844,8 @@ }, "node_modules/pull-through": { "version": "1.0.18", + "resolved": "https://registry.npmjs.org/pull-through/-/pull-through-1.0.18.tgz", + "integrity": "sha512-t8BBRdKstI/JoWS3FAXRnBvo1ahoLcKo8nm8ZUzr4OUgIf47RodD6HjnPIpw/d6HQZaMu+TotLAYCNwLTnvcNw==", "license": "MIT", "dependencies": { "looper": "~3.0.0" @@ -11718,18 +5853,26 @@ }, "node_modules/pull-through/node_modules/looper": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", + "integrity": "sha512-LJ9wplN/uSn72oJRsXTx+snxPet5c8XiZmOKCm906NVYu+ag6SB6vUcnJcWxgnl2NfbIyeobAn7Bwv6xRj2XJg==", "license": "MIT" }, "node_modules/pull-traverse": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pull-traverse/-/pull-traverse-1.0.3.tgz", + "integrity": "sha512-tdvTsDPh7PwuBEJ3o7DI6h/bQ5ocm9W0TYWNWEQ37ZS9Mt9aRfJqfvmAIdrpg3DgahU9PSITX0fYg8xTbqEdjQ==", "license": "MIT" }, "node_modules/pull-utf8-decoder": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pull-utf8-decoder/-/pull-utf8-decoder-1.0.2.tgz", + "integrity": "sha512-hpGheDlDMMEOkiBwXpiUqaFVR0SlGb23vZyJuHe1s8CWGJVcDEzhy9HHUEIuPjTEajYb90ikKsDmt6lkmTtk5g==", "license": "MIT" }, "node_modules/pull-websocket": { "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pull-websocket/-/pull-websocket-3.4.2.tgz", + "integrity": "sha512-hGFWC4/fzRdO2FEsyR9woVzgv/yG4PIk3RXPN4azBpomGzGQFRUORwKQDS3j7RAIy8tjvN2W+qjU8jNn2NWeNQ==", "license": "ISC", "dependencies": { "relative-url": "^1.0.2", @@ -11739,6 +5882,8 @@ }, "node_modules/pull-window": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/pull-window/-/pull-window-2.1.4.tgz", + "integrity": "sha512-cbDzN76BMlcGG46OImrgpkMf/VkCnupj8JhsrpBw3aWBM9ye345aYnqitmZCgauBkc0HbbRRn9hCnsa3k2FNUg==", "license": "MIT", "dependencies": { "looper": "^2.0.0" @@ -11746,40 +5891,20 @@ }, "node_modules/pull-window/node_modules/looper": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-2.0.0.tgz", + "integrity": "sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ==", "license": "MIT" }, - "node_modules/pull-write": { - "version": "1.1.4", - "license": "MIT", - "dependencies": { - "looper": "^4.0.0", - "pull-cat": "^1.1.11", - "pull-stream": "^3.4.5" - } - }, "node_modules/pull-write-file": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/pull-write-file/-/pull-write-file-0.2.4.tgz", + "integrity": "sha512-XqtjwmjObiJYj/ojJskRayIzlmP6ufzG6Ec5oSd/qhlUWr4zMsAcJoiSPjUi079QFqvlb54HaWCBkbpg2zZiKQ==", "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -11787,10 +5912,14 @@ }, "node_modules/push-stream": { "version": "11.2.0", + "resolved": "https://registry.npmjs.org/push-stream/-/push-stream-11.2.0.tgz", + "integrity": "sha512-MbiU+tFKDFv6IRwxfU2pltftFiIsW6mhLT3q91EJQwZYd2A9aaytdKAnRGYCx9KAuWQ11/cClK/FvHY996WwxA==", "license": "MIT" }, "node_modules/push-stream-to-pull-stream": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/push-stream-to-pull-stream/-/push-stream-to-pull-stream-1.0.5.tgz", + "integrity": "sha512-oQfzDroAv+SySQIXFiBVkShIh8Vgpr+hd7TrwyUna1kVrbv3i6D+QQC+31QdI7D6Jow61QLQW+uWToxv4cXI2w==", "license": "MIT", "dependencies": { "pull-looper": "^1.0.0", @@ -11799,6 +5928,8 @@ }, "node_modules/qrcode": { "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", "license": "MIT", "dependencies": { "dijkstrajs": "^1.0.1", @@ -11814,6 +5945,8 @@ }, "node_modules/qrcode/node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "license": "MIT", "engines": { "node": ">=6" @@ -11821,6 +5954,8 @@ }, "node_modules/qrcode/node_modules/cliui": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -11830,10 +5965,14 @@ }, "node_modules/qrcode/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/qrcode/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -11845,6 +5984,8 @@ }, "node_modules/qrcode/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -11855,6 +5996,8 @@ }, "node_modules/qrcode/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -11868,6 +6011,8 @@ }, "node_modules/qrcode/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -11878,6 +6023,8 @@ }, "node_modules/qrcode/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11890,6 +6037,8 @@ }, "node_modules/qrcode/node_modules/wrap-ansi": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -11902,10 +6051,14 @@ }, "node_modules/qrcode/node_modules/y18n": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "license": "ISC" }, "node_modules/qrcode/node_modules/yargs": { "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -11926,6 +6079,8 @@ }, "node_modules/qrcode/node_modules/yargs-parser": { "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -11952,28 +6107,26 @@ }, "node_modules/queue-tick": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "license": "MIT" }, - "node_modules/quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/quicktask": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/quicktask/-/quicktask-1.0.1.tgz", + "integrity": "sha512-+jhR01aSsi6Iw9wbYumKusRK72QK1ub2oE0kOnRyMkMdD/rXMMGW/TVl5edjcLjuuIKP1ezkr+xQzUMD5/4JHw==", "license": "MIT" }, "node_modules/railroad-diagrams": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", "license": "CC0-1.0" }, "node_modules/randexp": { "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", "license": "MIT", "dependencies": { "discontinuous-range": "1.0.0", @@ -11985,6 +6138,8 @@ }, "node_modules/random-access-chrome-file": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/random-access-chrome-file/-/random-access-chrome-file-1.2.0.tgz", + "integrity": "sha512-M1NOdkHEcjRB+acKrdQkwf8aMTnZUIGboiH6i2PMNkjfChBIJiB4j4MuhpOn+u+XU2n7GqpocPN4bzfv0jrBsg==", "license": "MIT", "dependencies": { "random-access-storage": "^1.3.0" @@ -11992,6 +6147,8 @@ }, "node_modules/random-access-file": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-2.2.1.tgz", + "integrity": "sha512-RGU0xmDqdOyEiynob1KYSeh8+9c9Td1MJ74GT1viMEYAn8SJ9oBtWCXLsYZukCF46yududHOdM449uRYbzBrZQ==", "license": "MIT", "dependencies": { "mkdirp-classic": "^0.5.2", @@ -12000,6 +6157,8 @@ }, "node_modules/random-access-idb": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/random-access-idb/-/random-access-idb-1.2.2.tgz", + "integrity": "sha512-NroFuBNVh5wVIHKN/jEYrgkkffppkfxNWFX9OEwC2VP7dYc3sa+Qxv7tMa1Gi9Jp/ObVfLeCZBt/8Sbn1WU1Xg==", "license": "BSD", "dependencies": { "buffer-alloc": "^1.1.0", @@ -12012,6 +6171,8 @@ }, "node_modules/random-access-idb-mutable-file": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/random-access-idb-mutable-file/-/random-access-idb-mutable-file-0.3.0.tgz", + "integrity": "sha512-CdVAoFNNDn5uAgYOJ8J3ICSaFzaMOa95XnYcX+taj4jirJuRASiTyQSOGR+Z0K8ZkBGuj0A8ivyeRAWuxRCgQA==", "license": "MIT", "dependencies": { "buffer": "5.1.0", @@ -12020,6 +6181,8 @@ }, "node_modules/random-access-idb-mutable-file/node_modules/buffer": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", + "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", "license": "MIT", "dependencies": { "base64-js": "^1.0.2", @@ -12028,6 +6191,8 @@ }, "node_modules/random-access-idb-mutable-file/node_modules/random-access-storage": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-1.3.0.tgz", + "integrity": "sha512-pdS9Mcb9TB7oICypPRALlheaSuszuAKmLVEPKJMuYor7R/zDuHh5ALuQoS+ox31XRwQUL+tDwWH2GPdyspwelA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3" @@ -12035,10 +6200,14 @@ }, "node_modules/random-access-idb/node_modules/buffer-from": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", + "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==", "license": "MIT" }, "node_modules/random-access-memory": { "version": "3.1.4", + "resolved": "https://registry.npmjs.org/random-access-memory/-/random-access-memory-3.1.4.tgz", + "integrity": "sha512-rqgqd/8ec65gbpKaYHnDOW391OR39d+eXn8NI87G+f3sUKrtGib9jC+/5/9MBFBwwHAZIS8RLJ8yyB4etzbYTA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -12048,6 +6217,8 @@ }, "node_modules/random-access-storage": { "version": "1.4.3", + "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-1.4.3.tgz", + "integrity": "sha512-D5e2iIC5dNENWyBxsjhEnNOMCwZZ64TARK6dyMN+3g4OTC4MJxyjh9hKLjTGoNhDOPrgjI+YlFEHFnrp/cSnzQ==", "license": "MIT", "dependencies": { "events": "^3.3.0", @@ -12057,6 +6228,8 @@ }, "node_modules/random-access-web": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/random-access-web/-/random-access-web-2.0.3.tgz", + "integrity": "sha512-nN3AAgl4/lTOYMk5Qm44SzFsglOmaG2d0Kh0603umh35+rk9QXYLFf0nFJ0GOv9INBsP9iT1lub24r8PjyCtvA==", "license": "MIT", "dependencies": { "@sammacbeth/random-access-idb-mutable-file": "^0.1.1", @@ -12067,47 +6240,10 @@ "random-access-storage": "^1.3.0" } }, - "node_modules/randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "license": "MIT", - "dependencies": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/randomatic/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/randomatic/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/raw-body": { "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -12121,6 +6257,8 @@ }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -12131,6 +6269,8 @@ }, "node_modules/rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", @@ -12144,42 +6284,17 @@ }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/readable-stream": { "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", @@ -12194,6 +6309,8 @@ }, "node_modules/readable-web-to-node-stream": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", "license": "MIT", "dependencies": { "readable-stream": "^4.7.0" @@ -12208,6 +6325,8 @@ }, "node_modules/readdir-glob": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "license": "Apache-2.0", "dependencies": { "minimatch": "^5.1.0" @@ -12225,320 +6344,16 @@ "node": ">=10" } }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/readdirp/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "license": "MIT", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/readdirp/node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "license": "MIT", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "node_modules/real-cancellable-promise": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/real-cancellable-promise/-/real-cancellable-promise-1.2.3.tgz", + "integrity": "sha512-hBI5Gy/55VEeeMtImMgEirD7eq5UmqJf1J8dFZtbJZA/3rB0pYFZ7PayMGueb6v4UtUtpKpP+05L0VwyE1hI9Q==", "license": "MIT" }, - "node_modules/readdirp/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/readdirp/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/readdirp/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -12557,33 +6372,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "license": "MIT", - "dependencies": { - "is-equal-shallow": "^0.1.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -12602,182 +6394,14 @@ }, "node_modules/relative-url": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/relative-url/-/relative-url-1.0.2.tgz", + "integrity": "sha512-jI1AmBVFFMXwQ3I6tIYVmVOjy8f+ogHbqkeb8/LL9tszEQiTV8I0l8XT4oEomUOoxfm698f92gYlNKeA/9LJVQ==", "license": "MIT" }, - "node_modules/remark": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/remark/-/remark-3.2.3.tgz", - "integrity": "sha512-E+WibagMmLjs7y5d6qWRiwbL2/z0zbkLwCqvmNlxBTx2Y1ufZ06sir+ZdyQXTxDiEbkbsrX6kG883ItjrEkaSQ==", - "license": "MIT", - "dependencies": { - "camelcase": "^2.0.0", - "ccount": "^1.0.0", - "chalk": "^1.0.0", - "chokidar": "^1.0.5", - "collapse-white-space": "^1.0.0", - "commander": "^2.0.0", - "concat-stream": "^1.0.0", - "debug": "^2.0.0", - "elegant-spinner": "^1.0.0", - "extend.js": "0.0.2", - "glob": "^6.0.1", - "globby": "^4.0.0", - "he": "^0.5.0", - "log-update": "^1.0.1", - "longest-streak": "^1.0.0", - "markdown-table": "^0.4.0", - "minimatch": "^3.0.0", - "npm-prefix": "^1.0.1", - "parse-entities": "^1.0.0", - "repeat-string": "^1.5.0", - "stringify-entities": "^1.0.0", - "to-vfile": "^1.0.0", - "trim": "^0.0.1", - "trim-trailing-lines": "^1.0.0", - "unified": "^2.0.0", - "user-home": "^2.0.0", - "vfile": "^1.1.0", - "vfile-find-down": "^1.0.0", - "vfile-find-up": "^1.0.0", - "vfile-reporter": "^1.5.0", - "ware": "^1.3.0" - }, - "bin": { - "remark": "bin/remark" - } - }, - "node_modules/remark-html": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-2.0.2.tgz", - "integrity": "sha512-2yj8Oad44uo/IWOssZs3Y4AvfrA9nGZqiHsL7ePyZuntPKjtljHZDcroljlQQ+sJryO3Bjnnj30e7wi8aTp/hQ==", - "license": "MIT", - "dependencies": { - "collapse-white-space": "^1.0.0", - "detab": "^1.0.0", - "normalize-uri": "^1.0.0", - "object-assign": "^4.0.1", - "trim": "0.0.1", - "trim-lines": "^1.0.0", - "unist-util-visit": "^1.0.0" - } - }, - "node_modules/remark/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remark/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remark/node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remark/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remark/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, - "node_modules/remark/node_modules/glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/remark/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "license": "ISC" - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12794,10 +6418,14 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, "node_modules/resolve": { "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -12816,6 +6444,8 @@ }, "node_modules/resolve-path": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", "license": "MIT", "dependencies": { "http-errors": "~1.6.2", @@ -12827,6 +6457,8 @@ }, "node_modules/resolve-path/node_modules/depd": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -12834,6 +6466,8 @@ }, "node_modules/resolve-path/node_modules/http-errors": { "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", "license": "MIT", "dependencies": { "depd": "~1.1.2", @@ -12847,65 +6481,38 @@ }, "node_modules/resolve-path/node_modules/inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "license": "ISC" }, "node_modules/resolve-path/node_modules/setprototypeof": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "license": "ISC" }, "node_modules/resolve-path/node_modules/statuses": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "license": "MIT" - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ret": { "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "license": "MIT", "engines": { "node": ">=0.12" } }, - "node_modules/retry": { - "version": "0.12.0", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/right-pad": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -12919,6 +6526,8 @@ }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12937,59 +6546,20 @@ }, "node_modules/rng": { "version": "0.2.2", - "license": "MIT" - }, - "node_modules/router": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", - "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "resolved": "https://registry.npmjs.org/rng/-/rng-0.2.2.tgz", + "integrity": "sha512-3F/A3swXdqb4JLpF22zwlCllyXlUWEE3T35GzJ+Vvtx4HnUYVAXovaK2TyUC04UlpxdVxB+E7HaKdDrGeOcJuA==", "license": "MIT" }, "node_modules/rwlock": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rwlock/-/rwlock-5.0.0.tgz", + "integrity": "sha512-XgzRqLMfCcm9QfZuPav9cV3Xin5TRcIlp4X/SH3CvB+x5D2AakdlEepfJKDd8ByncvfpcxNWdRZVUl38PS6ZJg==", "license": "MIT" }, "node_modules/safe-array-concat": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -13007,6 +6577,8 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -13025,6 +6597,8 @@ }, "node_modules/safe-push-apply": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13037,17 +6611,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "license": "MIT", - "dependencies": { - "ret": "~0.1.10" - } - }, "node_modules/safe-regex-test": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13063,10 +6630,14 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/sanitize-filename": { "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", "license": "WTFPL OR ISC", "dependencies": { "truncate-utf8-bytes": "^1.0.0" @@ -13086,6 +6657,8 @@ }, "node_modules/secret-handshake": { "version": "1.1.21", + "resolved": "https://registry.npmjs.org/secret-handshake/-/secret-handshake-1.1.21.tgz", + "integrity": "sha512-e02+IddZv40tNJmaRZNZ6aYje95h9MvmpmbOg2PVjLKRA5p9QnrQypJlVE2dU516TyYc9jikBjqhXV1IlkwNUw==", "license": "MIT", "dependencies": { "chloride": "^2.2.8", @@ -13097,6 +6670,8 @@ }, "node_modules/secret-stack": { "version": "6.4.2", + "resolved": "https://registry.npmjs.org/secret-stack/-/secret-stack-6.4.2.tgz", + "integrity": "sha512-aUJNQ1DmZo0loxcnHOU7pvu9N2aOE0H63a1hpd77+I1GbmDBkK5hI9vO16IFf4OYDgPBiBIY3NGAHF23sdJC8g==", "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -13115,10 +6690,14 @@ }, "node_modules/secret-stack-decorators": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/secret-stack-decorators/-/secret-stack-decorators-1.1.0.tgz", + "integrity": "sha512-wYl0Mcul/fuEbZwn9tN62c+W4LP2RPus/ilt3wdBNQqfBFSMlDXTLaIsrA4SEElb7JEBH4xzdQbOCnTvYHeWCA==", "license": "MIT" }, "node_modules/secret-stack/node_modules/muxrpc": { "version": "6.7.3", + "resolved": "https://registry.npmjs.org/muxrpc/-/muxrpc-6.7.3.tgz", + "integrity": "sha512-yVr66BigJxHIvHOLsdJzuvrHt46Vq9DiDCBTZiS6q+49mXh4mkDjRiXo9FnGXQv1WJOqnRJk5fcfwV6TaSgNvA==", "license": "MIT", "dependencies": { "clarify-error": "^1.0.0", @@ -13141,87 +6720,22 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/fresh": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/separator-escape": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/separator-escape/-/separator-escape-0.0.1.tgz", + "integrity": "sha512-daCzTzZVoowYzjW7x9xMH6zr+lt/zsGxV1rtXaoTnlues7ZDx6Qu0l5W3jCdgnXGE1ONAGL+XPWY+IRDxnJ9EQ==", "license": "MIT" }, - "node_modules/serve-static": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "license": "ISC" }, "node_modules/set-function-length": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -13237,6 +6751,8 @@ }, "node_modules/set-function-name": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -13250,6 +6766,8 @@ }, "node_modules/set-proto": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -13260,35 +6778,10 @@ "node": ">= 0.4" } }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/set.prototype.difference": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/set.prototype.difference/-/set.prototype.difference-1.1.7.tgz", + "integrity": "sha512-u6NOoxMovg0cqKd9wa5JoZ9F/5SF5wCdO0w3zKNjKps3VJ594mCQJDGXYtKaJUpwlIZFiZGEbtsNzkWlcr6GLA==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -13309,6 +6802,8 @@ }, "node_modules/set.prototype.issubsetof": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/set.prototype.issubsetof/-/set.prototype.issubsetof-1.1.4.tgz", + "integrity": "sha512-m7paZD4cLQAXj5TLpdvpyX6gQ62onRV65zxjUH4qpZX5BouTBcL89X8BQ3QDhcFKwO2eZql8yTtNKsEmCOb1Tw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -13328,10 +6823,14 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/sha.js": { "version": "2.4.5", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.5.tgz", + "integrity": "sha512-459AP5kwdIhtMLLE1+h2q5a6E7hyY7Ri88GUIhFsyrQinKkm+7qj1ARHELow7GkFS7oimWmIwhXTSxBsuNuOsw==", "license": "MIT", "dependencies": { "inherits": "^2.0.1" @@ -13342,6 +6841,8 @@ }, "node_modules/sha256-universal": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sha256-universal/-/sha256-universal-1.2.1.tgz", + "integrity": "sha512-ghn3muhdn1ailCQqqceNxRgkOeZSVfSE13RQWEg6njB+itsFzGVSJv+O//2hvNXZuxVIRyNzrgsZ37SPDdGJJw==", "license": "ISC", "dependencies": { "b4a": "^1.0.1", @@ -13350,6 +6851,8 @@ }, "node_modules/sha256-wasm": { "version": "2.2.2", + "resolved": "https://registry.npmjs.org/sha256-wasm/-/sha256-wasm-2.2.2.tgz", + "integrity": "sha512-qKSGARvao+JQlFiA+sjJZhJ/61gmW/3aNLblB2rsgIxDlDxsJPHo8a1seXj12oKtuHVgJSJJ7QEGBUYQN741lQ==", "license": "ISC", "dependencies": { "b4a": "^1.0.1", @@ -13358,6 +6861,8 @@ }, "node_modules/sha512-universal": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sha512-universal/-/sha512-universal-1.2.1.tgz", + "integrity": "sha512-kehYuigMoRkIngCv7rhgruLJNNHDnitGTBdkcYbCbooL8Cidj/bS78MDxByIjcc69M915WxcQTgZetZ1JbeQTQ==", "license": "ISC", "dependencies": { "b4a": "^1.0.1", @@ -13366,6 +6871,8 @@ }, "node_modules/sha512-wasm": { "version": "2.3.4", + "resolved": "https://registry.npmjs.org/sha512-wasm/-/sha512-wasm-2.3.4.tgz", + "integrity": "sha512-akWoxJPGCB3aZCrZ+fm6VIFhJ/p8idBv7AWGFng/CZIrQo51oQNsvDbTSRXWAzIiZJvpy16oIDiCCPqTe21sKg==", "license": "ISC", "dependencies": { "b4a": "^1.0.1", @@ -13374,9 +6881,10 @@ }, "node_modules/sharp": { "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", @@ -13412,6 +6920,8 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -13422,19 +6932,17 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/shellsubstitute": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shellsubstitute/-/shellsubstitute-1.2.0.tgz", - "integrity": "sha512-CI1ViFC5a3ub86aaBmBVQ7kqg8eFypZLgBh+Bmq+ehHy9g7vu9kqCj5hS82cPzLwfdJRgiPB2hNHnd6oetiakQ==", - "license": "ISC" - }, "node_modules/side-channel": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13452,6 +6960,8 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13466,6 +6976,8 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13482,6 +6994,8 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13499,6 +7013,8 @@ }, "node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", "engines": { "node": ">=14" @@ -13507,70 +7023,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-git": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", - "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", - "license": "MIT", - "dependencies": { - "@kwsites/file-exists": "^1.1.1", - "@kwsites/promise-deferred": "^1.1.1", - "@simple-git/args-pathspec": "^1.0.3", - "@simple-git/argv-parser": "^1.1.0", - "debug": "^4.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/steveukx/git-js?sponsor=1" - } - }, "node_modules/simple-swizzle": { "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" @@ -13578,190 +7034,42 @@ }, "node_modules/simple-swizzle/node_modules/is-arrayish": { "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/siphash24": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/siphash24/-/siphash24-1.3.1.tgz", + "integrity": "sha512-moemC3ZKiTzH29nbFo3Iw8fbemWWod4vNs/WgKbQ54oEs6mE6XVlguxvinYjB+UmaE0PThgyED9fUkWvirT8hA==", "license": "MIT", "dependencies": { "nanoassert": "^2.0.0" } }, - "node_modules/sleep-promise": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/sleep-promise/-/sleep-promise-9.1.0.tgz", - "integrity": "sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==", - "license": "MIT" - }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "license": "ISC", "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, "node_modules/smart-buffer": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" } }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "license": "MIT", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "license": "MIT", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/socks": { "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", "license": "MIT", "dependencies": { "ip-address": "^9.0.5", @@ -13774,6 +7082,8 @@ }, "node_modules/sodium-browserify": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sodium-browserify/-/sodium-browserify-1.3.0.tgz", + "integrity": "sha512-1KRS6Oew3X13AIZhbmGF0YBdt2pQdafJMfv83OZHWbzxG92YBBnN8HYx/VKmYB4xCe90eidNaDJWBEFw/o3ahw==", "license": "MIT", "dependencies": { "libsodium-wrappers": "^0.7.4", @@ -13784,6 +7094,8 @@ }, "node_modules/sodium-browserify-tweetnacl": { "version": "0.2.6", + "resolved": "https://registry.npmjs.org/sodium-browserify-tweetnacl/-/sodium-browserify-tweetnacl-0.2.6.tgz", + "integrity": "sha512-ZnEI26hdluilpYY28Xc4rc1ALfmEp2TWihkJX6Mdtw0z9RfHfpZJU7P8DoKbN1HcBdU9aJmguFZs7igE8nLJPg==", "license": "MIT", "dependencies": { "chloride-test": "^1.1.0", @@ -13795,6 +7107,8 @@ }, "node_modules/sodium-browserify-tweetnacl/node_modules/sha.js": { "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.4", @@ -13813,14 +7127,20 @@ }, "node_modules/sodium-browserify-tweetnacl/node_modules/tweetnacl": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "license": "Unlicense" }, "node_modules/sodium-chloride": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sodium-chloride/-/sodium-chloride-1.1.2.tgz", + "integrity": "sha512-8AVzr9VHueXqfzfkzUA0aXe/Q4XG3UTmhlP6Pt+HQc5bbAPIJFo7ZIMh9tvn+99QuiMcyDJdYumegGAczl0N+g==", "license": "MIT" }, "node_modules/sodium-javascript": { "version": "0.8.0", + "resolved": "https://registry.npmjs.org/sodium-javascript/-/sodium-javascript-0.8.0.tgz", + "integrity": "sha512-rEBzR5mPxPES+UjyMDvKPIXy9ImF17KOJ32nJNi9uIquWpS/nfj+h6m05J5yLJaGXjgM72LmQoUbWZVxh/rmGg==", "license": "MIT", "dependencies": { "blake2b": "^2.1.1", @@ -13834,6 +7154,8 @@ }, "node_modules/sodium-native": { "version": "3.4.1", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.4.1.tgz", + "integrity": "sha512-PaNN/roiFWzVVTL6OqjzYct38NSXewdl2wz8SRB51Br/MLIJPrbM3XexhVWkq7D3UWMysfrhKVf1v1phZq6MeQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -13842,6 +7164,8 @@ }, "node_modules/sodium-universal": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-3.1.0.tgz", + "integrity": "sha512-N2gxk68Kg2qZLSJ4h0NffEhp4BjgWHCHXVlDi1aG1hA3y+ZeWEmHqnpml8Hy47QzfL1xLy5nwr9LcsWAg2Ep0A==", "license": "MIT", "dependencies": { "blake2b": "^2.1.1", @@ -13856,15 +7180,6 @@ "xsalsa20": "^1.0.0" } }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -13874,88 +7189,16 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "license": "MIT", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "license": "MIT" - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/split-buffer": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split-buffer/-/split-buffer-1.0.0.tgz", + "integrity": "sha512-cbHOF413jAsBL7JLpU1RWX07Xs+dPb25DbeCvwDzSN3+4bNmRPJa2QHpDdtREgGKgIfx6Tzj/jiqyOS3kNLPnQ==", "license": "MIT" }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssb-about": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "ssb-social-index": "^1.0.0" - } - }, "node_modules/ssb-about-self": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ssb-about-self/-/ssb-about-self-1.1.0.tgz", + "integrity": "sha512-AG07Dg2XG9CJvK52qrpwz54cXu+G6sPKwPiOb2LX5K+ORGhA+4MJUVViwuMEcOCI1a1wmXvOKFJbeTaFtosSAw==", "license": "LGPL-3.0", "dependencies": { "bipf": "^1.6.3", @@ -13974,24 +7217,18 @@ }, "node_modules/ssb-autofollow": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ssb-autofollow/-/ssb-autofollow-1.1.0.tgz", + "integrity": "sha512-2YBiFhVB9vKSN4pxGg11xwGpP4eVRWf5H3YthKodIfP84/IB/sUNFa+Z7VuL7IAczB3v3DQSWSTc+nK4WOc7ww==", "license": "MIT", "dependencies": { "debug": "^3.1.0", "pull-stream": "^3.6.9" } }, - "node_modules/ssb-backlinks": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "base64-url": "^2.3.3", - "flumeview-links": "^2.1.0", - "pull-stream": "^3.6.14", - "ssb-ref": "^2.14.0" - } - }, "node_modules/ssb-bendy-butt": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ssb-bendy-butt/-/ssb-bendy-butt-1.0.2.tgz", + "integrity": "sha512-PGIfYKLcpyKAGO6AS3BHctjxL58xK/DN6qmDBYVlxxqT+DI+482XYBCNKGtYfnCUlyBK+qO7lmbJp/DQhR8jmQ==", "license": "LGPL-3.0", "dependencies": { "bencode": "^2.0.1", @@ -14002,6 +7239,8 @@ }, "node_modules/ssb-bfe": { "version": "3.7.0", + "resolved": "https://registry.npmjs.org/ssb-bfe/-/ssb-bfe-3.7.0.tgz", + "integrity": "sha512-uJVMPAQhnm8Vz+5H7mP36kGhu0ZIloiDkNrkiO/xGWcr8gMv+GXX2DyNNPD7j0xZmL7HfFqgTvPXFeuni2w4IQ==", "license": "LGPL-3.0", "dependencies": { "is-canonical-base64": "^1.1.1", @@ -14012,10 +7251,14 @@ }, "node_modules/ssb-bfe-spec": { "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ssb-bfe-spec/-/ssb-bfe-spec-0.8.0.tgz", + "integrity": "sha512-0NuyOS0CK1LAoJY+c3d9VmhGFuT1ylFKp+3T7Cttgq7LHhh0zMYZdzeJCUtTYgjsW/grvwRzUuNtYffJSYT0aA==", "license": "CC0-1.0" }, "node_modules/ssb-blobs": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ssb-blobs/-/ssb-blobs-2.0.1.tgz", + "integrity": "sha512-WuF96Yc8bd0l62wGxpQNT2OGf2XEhdMLEYsWJeXilYtEJLW+IZoO4YUjPanmp4fkhU2KunN+a/cQr6vqDqLphA==", "license": "MIT", "dependencies": { "level": "^6.0.0", @@ -14030,6 +7273,8 @@ }, "node_modules/ssb-box": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ssb-box/-/ssb-box-1.0.1.tgz", + "integrity": "sha512-/lZOqFr5glmzKTmpXZ8/QbvYA6/mVQ6dDPT/+zsYQkKKEHaloMkMPRRXlK86xG+/Bsuw2HGjcm78W09aWshS+g==", "license": "LGPL-3.0", "dependencies": { "chloride": "^2.4.1", @@ -14043,6 +7288,8 @@ }, "node_modules/ssb-box2": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ssb-box2/-/ssb-box2-3.0.1.tgz", + "integrity": "sha512-jQhrsEyrpqnUciEb1qzqc/SJCAx3hTm48BMzMy1bE//xQthWRahigTffflOM7pdRieGlxwGlHc8PpagBBZOkhA==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.0", @@ -14056,6 +7303,8 @@ }, "node_modules/ssb-buttwoo": { "version": "0.3.3", + "resolved": "https://registry.npmjs.org/ssb-buttwoo/-/ssb-buttwoo-0.3.3.tgz", + "integrity": "sha512-TA4xIs+4abz8Zn40LfrM2qM/hkpn5gYmy76yFGo9KkbDYoiVc5+Wk589yUf1ahcCzfDZN/lltRgBUzyeEY36uw==", "license": "LGPL-3.0", "dependencies": { "base64-url": "^2.3.3", @@ -14070,10 +7319,14 @@ }, "node_modules/ssb-caps": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ssb-caps/-/ssb-caps-1.1.0.tgz", + "integrity": "sha512-qe3qpvchJ+gnH8M/ge4rpL+7eRbSmsEAzNwHkDdrW06OBcziQ6/KuAdmcR6joxCbNeoAXAZF+inkefgE16okXA==", "license": "CC0-1.0" }, "node_modules/ssb-classic": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ssb-classic/-/ssb-classic-1.1.0.tgz", + "integrity": "sha512-aR1nifEXIpyNP+tRmQ8Sw2ra9nhPGrJnhH6xGTmSn4GXSwy5Gm4JCrpzcKvzUKzKMsfmqw5HVug+2/IVkvEVXQ==", "license": "LGPL-3.0", "dependencies": { "bipf": "^1.9.0", @@ -14087,6 +7340,8 @@ }, "node_modules/ssb-client": { "version": "4.9.0", + "resolved": "https://registry.npmjs.org/ssb-client/-/ssb-client-4.9.0.tgz", + "integrity": "sha512-5wPrVA82Uk3deV2oiCZEbm1kMp9r/5xTT2VvwwMLZS3fDD9FnLOi0kjUUh3OTP+jGBK7bVHRmnvXIVy6ETnSpA==", "license": "MIT", "dependencies": { "explain-error": "^1.0.1", @@ -14101,6 +7356,8 @@ }, "node_modules/ssb-client/node_modules/mkdirp": { "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -14111,6 +7368,8 @@ }, "node_modules/ssb-client/node_modules/muxrpc": { "version": "6.7.3", + "resolved": "https://registry.npmjs.org/muxrpc/-/muxrpc-6.7.3.tgz", + "integrity": "sha512-yVr66BigJxHIvHOLsdJzuvrHt46Vq9DiDCBTZiS6q+49mXh4mkDjRiXo9FnGXQv1WJOqnRJk5fcfwV6TaSgNvA==", "license": "MIT", "dependencies": { "clarify-error": "^1.0.0", @@ -14123,6 +7382,8 @@ }, "node_modules/ssb-client/node_modules/ssb-keys": { "version": "7.2.2", + "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.2.tgz", + "integrity": "sha512-FPeyYU/3LpxcagnbmVWE+Q/qzg6keqeOBPbD7sEH9UKixUASeufPKiORDgh8nVX7J9Z+0vUaHt/WG999kGjvVQ==", "license": "MIT", "dependencies": { "chloride": "^2.2.8", @@ -14132,6 +7393,8 @@ }, "node_modules/ssb-config": { "version": "3.4.6", + "resolved": "https://registry.npmjs.org/ssb-config/-/ssb-config-3.4.6.tgz", + "integrity": "sha512-arKSuP3S5qlsqY7w5g0VGCidi7oullM5KBEqy/0M9OgedN1uW5SKyIgEjCE85PbkHBfCpAcCNps4exZzP317FA==", "license": "MIT", "dependencies": { "deep-extend": "^0.6.0", @@ -14145,6 +7408,8 @@ }, "node_modules/ssb-conn": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ssb-conn/-/ssb-conn-6.0.3.tgz", + "integrity": "sha512-XPZgTlLlfetsYqRtJlO0+tUE2OBGFf7BrdUkNPmSbqXHNZK1NDFPgohMYevywBt08xdTWfbSyljIsZs1buId3g==", "license": "MIT", "dependencies": { "debug": "^4.3.1", @@ -14172,6 +7437,8 @@ }, "node_modules/ssb-conn-db": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ssb-conn-db/-/ssb-conn-db-1.0.5.tgz", + "integrity": "sha512-2rbqjvlDMTke2/qgauA415QSQ4hUJziXHqcXuC2Yh1uPrCDXHXrp0dhCchn0b/taEeL0Ppqo/Tsy6szQdqlEDw==", "license": "MIT", "dependencies": { "atomic-file-rw": "^0.3.0", @@ -14183,6 +7450,8 @@ }, "node_modules/ssb-conn-hub": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ssb-conn-hub/-/ssb-conn-hub-1.2.1.tgz", + "integrity": "sha512-EazMSgPJSOZTp/NdhUOq4kduAliT9k0GNJvao3Muvqk4Y17V4yIwCKt8SENHI5BMz2ATrDmFLj9uf5z/83Txww==", "license": "MIT", "dependencies": { "debug": "^4.3.1", @@ -14198,6 +7467,8 @@ }, "node_modules/ssb-conn-query": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ssb-conn-query/-/ssb-conn-query-1.2.2.tgz", + "integrity": "sha512-pvCU8oxpX9sBap6oBrLUwDGAYnHSxkhYAYnj6RxGNotPKDIRZUBZB04yQ4nA1rHm7oHOnBgcuK0GRWls78pnkQ==", "license": "MIT", "dependencies": { "ssb-conn-db": "~1.0.3", @@ -14207,6 +7478,8 @@ }, "node_modules/ssb-conn-staging": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ssb-conn-staging/-/ssb-conn-staging-1.0.0.tgz", + "integrity": "sha512-NOy1qZoBkhH0XNzLteaaKKePXigEJSCntD4RPRU6vWLMlQ10+SzlIz4TWKgvEVx0LfCe6tEx/vhQf6vBWO/Ylw==", "license": "MIT", "dependencies": { "debug": "^4.1.1", @@ -14216,54 +7489,10 @@ "pull-stream": "^3.6.9" } }, - "node_modules/ssb-db": { - "version": "20.4.1", - "license": "MIT", - "dependencies": { - "flumedb": "^2.1.8", - "flumelog-offset": "^3.4.2", - "flumeview-level": "^4.0.4", - "flumeview-reduce": "^1.4.0", - "hashlru": "^2.3.0", - "lodash.clonedeep": "^4.5.0", - "ltgt": "^2.2.0", - "mkdirp": "^1.0.4", - "monotonic-timestamp": "~0.0.8", - "muxrpc-validation": "^3.0.0", - "obv": "0.0.1", - "pull-cat": "^1.1.11", - "pull-cont": "^0.1.1", - "pull-notify": "^0.1.1", - "pull-stream": "^3.4.0", - "rimraf": "^3.0.0", - "ssb-keys": "^7.1.3", - "ssb-msgs": "^5.0.0", - "ssb-ref": "^2.14.0", - "ssb-validate": "^4.0.0", - "zerr": "^1.0.0" - } - }, - "node_modules/ssb-db/node_modules/ssb-keys": { - "version": "7.2.2", - "license": "MIT", - "dependencies": { - "chloride": "^2.2.8", - "mkdirp": "~0.5.0", - "private-box": "^0.3.0" - } - }, - "node_modules/ssb-db/node_modules/ssb-keys/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ssb-db2": { "version": "6.3.3", + "resolved": "https://registry.npmjs.org/ssb-db2/-/ssb-db2-6.3.3.tgz", + "integrity": "sha512-H5WztPw1lJmUR1e12McRABT4OStiwygDutirHhx1P520ssu5pTmuKrcUM/uSaEPbEPsLif17HpdKKd1fK2uxYg==", "license": "LGPL-3.0", "dependencies": { "async-append-only-log": "^4.3.7", @@ -14306,6 +7535,8 @@ }, "node_modules/ssb-db2/node_modules/flumecodec": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.1.tgz", + "integrity": "sha512-JT0xivzdV7uTucjsLMw6JhK2e1K5TmU4fGmoQqnrJbydgY/V6+m71QoxX5ZtRR1pKoD48uhPDWWE6G5MlNoGkg==", "license": "MIT", "dependencies": { "level-codec": "^6.2.0" @@ -14313,18 +7544,14 @@ }, "node_modules/ssb-db2/node_modules/flumecodec/node_modules/level-codec": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", + "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", "license": "MIT" }, - "node_modules/ssb-device-address": { - "version": "1.1.6", - "license": "MIT", - "dependencies": { - "pull-stream": "^3.6.9", - "ssb-ref": "^2.13.3" - } - }, "node_modules/ssb-ebt": { "version": "9.1.2", + "resolved": "https://registry.npmjs.org/ssb-ebt/-/ssb-ebt-9.1.2.tgz", + "integrity": "sha512-nNR26/3da8e+39fARLFDhrNdbkycxvmXqoK7QxrHGJaY/opBGtZ7GaHjNKkaXcF4Fl04ee+NhkYaiq18pGI0RQ==", "license": "MIT", "dependencies": { "base64-url": "^2.2.0", @@ -14343,56 +7570,10 @@ "node": ">=10" } }, - "node_modules/ssb-feed": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ssb-feed/-/ssb-feed-2.3.0.tgz", - "integrity": "sha512-PKdrDl+KpqpLmIAJA/l7+8njHsDkJ8siDnMDxcGJhr63uwdib74H9BMaV9qbW9kRI7S0UJHvuPcre0pB3tcxgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cont": "~1.0.3", - "monotonic-timestamp": "~0.0.9", - "pull-stream": "^3.4.2", - "ssb-keys": "^7.0.0", - "ssb-ref": "^2.0.0" - } - }, - "node_modules/ssb-feed/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ssb-feed/node_modules/ssb-keys": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.2.tgz", - "integrity": "sha512-FPeyYU/3LpxcagnbmVWE+Q/qzg6keqeOBPbD7sEH9UKixUASeufPKiORDgh8nVX7J9Z+0vUaHt/WG999kGjvVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chloride": "^2.2.8", - "mkdirp": "~0.5.0", - "private-box": "^0.3.0" - } - }, - "node_modules/ssb-friend-pub": { - "version": "1.0.7", - "license": "Beerware", - "dependencies": { - "pull-stream": "^3.6.9", - "ssb-ref": "^2.13.9", - "ssb-sort": "^1.1.3" - } - }, "node_modules/ssb-friends": { "version": "5.1.7", + "resolved": "https://registry.npmjs.org/ssb-friends/-/ssb-friends-5.1.7.tgz", + "integrity": "sha512-oBoMmcw5URa16CXM7LV1YG+YAGdwsQHkHevkh1VVKqhxxi8UAQ1qseb0iQ9Xdzwgadh9lRaW/aGz+175xrcxig==", "license": "MIT", "dependencies": { "bipf": "^1.5.1", @@ -14414,6 +7595,8 @@ }, "node_modules/ssb-friends/node_modules/flumecodec": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.1.tgz", + "integrity": "sha512-JT0xivzdV7uTucjsLMw6JhK2e1K5TmU4fGmoQqnrJbydgY/V6+m71QoxX5ZtRR1pKoD48uhPDWWE6G5MlNoGkg==", "license": "MIT", "dependencies": { "level-codec": "^6.2.0" @@ -14421,43 +7604,14 @@ }, "node_modules/ssb-friends/node_modules/level-codec": { "version": "6.2.0", - "license": "MIT" - }, - "node_modules/ssb-generate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ssb-generate/-/ssb-generate-1.0.1.tgz", - "integrity": "sha512-Ube4EVI2QCHLwtxPwi3julUGW1j45oZFCA/keST5JtWLAmt7Tegq7VWwlTHxXRInTZKcT8Q6AJF+KtipR+FC1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "pull-paramap": "^1.2.2", - "pull-stream": "^3.6.0" - } - }, - "node_modules/ssb-gossip": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "atomic-file": "^1.1.5", - "deep-equal": "^1.0.1", - "has-network": "0.0.1", - "ip": "^1.1.5", - "muxrpc-validation": "^3.0.0", - "on-change-network": "0.0.2", - "on-wakeup": "^1.0.1", - "pull-notify": "^0.1.1", - "pull-ping": "^2.0.2", - "pull-stream": "^3.6.9", - "ssb-ref": "^2.13.9", - "statistics": "^3.3.0" - } - }, - "node_modules/ssb-gossip/node_modules/atomic-file": { - "version": "1.1.5", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", + "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", "license": "MIT" }, "node_modules/ssb-index-feeds": { "version": "0.10.2", + "resolved": "https://registry.npmjs.org/ssb-index-feeds/-/ssb-index-feeds-0.10.2.tgz", + "integrity": "sha512-j4j24OeD5CMGWcfjlk9ZUiQda6Obx/1z4X/SjYmBJBsX6mjVLJ0Kz0MbO607oVCls7AXdlgBCs6UsCGoO/VJiA==", "license": "LGPL-3.0", "dependencies": { "debug": "^4.3.2", @@ -14474,6 +7628,8 @@ }, "node_modules/ssb-invite": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ssb-invite/-/ssb-invite-3.0.3.tgz", + "integrity": "sha512-zf0i9SL1phZJjXQE0poeguw+WOl3YdHV+Ajqnwka2AbDXaounA9RpoSl/SsxgLnCF/p8lOhNGMGU5Nkm/t7gVA==", "license": "MIT", "dependencies": { "cont": "^1.0.3", @@ -14488,6 +7644,8 @@ }, "node_modules/ssb-invite-client": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ssb-invite-client/-/ssb-invite-client-1.3.3.tgz", + "integrity": "sha512-JBj5GxtWAquFp9L4TISSmCq1bsTEwpp5KUeALFuRBWuG+y6Nib55qaLJz8Q6r+HbxiVz3Soq68RaG5uW9FCcrQ==", "license": "MIT", "dependencies": { "clarify-error": "^1.0.0", @@ -14501,6 +7659,8 @@ }, "node_modules/ssb-keyring": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ssb-keyring/-/ssb-keyring-2.2.0.tgz", + "integrity": "sha512-pXNzYsQbtdWoWAjEPT5lVrZs/Px4ypJU2m4S34tTN6KivZukHPcflj1/IbZSDj7kHJGQhJRcyqGQ3MxgQdFBrA==", "license": "LGPL-3.0", "dependencies": { "charwise": "^3.0.1", @@ -14516,6 +7676,8 @@ }, "node_modules/ssb-keyring/node_modules/ssb-private-group-keys": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ssb-private-group-keys/-/ssb-private-group-keys-1.1.2.tgz", + "integrity": "sha512-0UPPmxy61qmbDmP71J2vhX6UPfCtXa/CNehxYTgk2+AaLXsnA0perGZAiOWm9niGEU50TYYC5/jsIfjz4IiD9A==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.2", @@ -14527,6 +7689,8 @@ }, "node_modules/ssb-keys": { "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-8.5.0.tgz", + "integrity": "sha512-Fdgnz5QQ/oK/bMf5Hkaqss/INqiKHFHG4RKDk3StWrQC4fUKMSL/GfnkxFlcLFGhVomzxUmhtAnhJQx2WecEKQ==", "license": "MIT", "dependencies": { "chloride": "~2.4.1", @@ -14540,6 +7704,8 @@ }, "node_modules/ssb-keys/node_modules/mkdirp": { "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -14550,6 +7716,8 @@ }, "node_modules/ssb-lan": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/ssb-lan/-/ssb-lan-1.2.3.tgz", + "integrity": "sha512-HJdc9oLIYO4u2eE9F3w8aXDP+idUiJ+ODcJKCKY8VKAlyn8psP4TC+Ol9VpT8NzFdOKQ5LnaAW9yThawfi2VUg==", "license": "MIT", "dependencies": { "debug": "^4.3.1", @@ -14565,237 +7733,19 @@ "secret-stack": ">=6.2.0" } }, - "node_modules/ssb-legacy-conn": { - "version": "1.0.25", - "license": "MIT", - "dependencies": { - "debug": "~4.1.1", - "has-network": "0.0.1", - "ip": "^1.1.5", - "multiserver-address": "~1.0.1", - "on-change-network": "0.0.2", - "on-wakeup": "^1.0.1", - "pull-notify": "^0.1.1", - "pull-ping": "^2.0.2", - "pull-stream": "^3.6.9", - "ssb-conn-db": "0.1.0", - "ssb-conn-hub": "0.0.3", - "ssb-conn-staging": "0.0.1", - "ssb-ref": "^2.13.9", - "statistics": "^3.3.0" - } - }, - "node_modules/ssb-legacy-conn/node_modules/atomic-file": { - "version": "1.1.5", - "license": "MIT" - }, - "node_modules/ssb-legacy-conn/node_modules/ssb-conn-db": { - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "atomic-file": "^1.1.5", - "debug": "~4.1.1", - "multiserver-address": "~1.0.1", - "pull-notify": "~0.1.1", - "ssb-ref": "~2.13.9" - } - }, - "node_modules/ssb-legacy-conn/node_modules/ssb-conn-hub": { - "version": "0.0.3", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "multiserver-address": "~1.0.1", - "promisify-tuple": "^1.0.0", - "pull-notify": "~0.1.1", - "ssb-ref": "~2.13.9" - } - }, - "node_modules/ssb-legacy-conn/node_modules/ssb-conn-staging": { - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "multiserver-address": "~1.0.1", - "pull-notify": "~0.1.1", - "pull-stream": "^3.6.9", - "ssb-conn-hub": "~0.0.3" - } - }, - "node_modules/ssb-links": { - "version": "3.0.10", - "license": "MIT", - "dependencies": { - "flumeview-links": "^1.0.1", - "map-filter-reduce": "^2.0.0", - "ssb-msgs": "^5.2.0" - } - }, - "node_modules/ssb-links/node_modules/abstract-leveldown": { - "version": "6.0.3", - "license": "MIT", - "dependencies": { - "level-concat-iterator": "~2.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ssb-links/node_modules/deep-equal": { - "version": "2.2.3", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ssb-links/node_modules/flumeview-level": { - "version": "3.0.14", - "license": "MIT", - "dependencies": { - "charwise": "^3.0.1", - "explain-error": "^1.0.4", - "level": "^5.0.0", - "ltgt": "^2.1.3", - "mkdirp": "^0.5.1", - "obv": "0.0.1", - "pull-level": "^2.0.3", - "pull-paramap": "^1.2.1", - "pull-stream": "^3.5.0", - "pull-write": "^1.1.1" - } - }, - "node_modules/ssb-links/node_modules/flumeview-links": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "deep-equal": "^2.0.3", - "flumeview-level": "^3.0.14", - "map-filter-reduce": "^3.2.2", - "pull-flatmap": "0.0.1", - "pull-stream": "^3.6.14" - } - }, - "node_modules/ssb-links/node_modules/flumeview-links/node_modules/map-filter-reduce": { - "version": "3.2.2", - "license": "MIT", - "dependencies": { - "binary-search": "^1.2.0", - "compare-at-paths": "^1.0.0", - "pull-sink-through": "0.0.0", - "pull-sort": "^1.0.1", - "pull-stream": "^3.4.3", - "typewiselite": "^1.0.0" - } - }, - "node_modules/ssb-links/node_modules/immediate": { - "version": "3.2.3", - "license": "MIT" - }, - "node_modules/ssb-links/node_modules/level": { - "version": "5.0.1", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "level-js": "^4.0.0", - "level-packager": "^5.0.0", - "leveldown": "^5.0.0", - "opencollective-postinstall": "^2.0.0" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/ssb-links/node_modules/level-js": { - "version": "4.0.2", - "license": "MIT", - "dependencies": { - "abstract-leveldown": "~6.0.1", - "immediate": "~3.2.3", - "inherits": "^2.0.3", - "ltgt": "^2.1.2", - "typedarray-to-buffer": "~3.1.5" - } - }, - "node_modules/ssb-links/node_modules/map-filter-reduce": { - "version": "2.2.1", - "license": "MIT", - "dependencies": { - "binary-search": "^1.2.0", - "pull-sink-through": "0.0.0", - "pull-stream": "^3.3.0", - "typewiselite": "^1.0.0" - } - }, - "node_modules/ssb-links/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ssb-links/node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/ssb-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ssb-local/-/ssb-local-1.0.0.tgz", - "integrity": "sha512-aIkz+tDsAQFLbcN5d9itipHG71EG62UdIwojz78BQBxD7kGTwMezZofkMplURp9/njwdqMiqxYEUMvOh8Emh/w==", - "license": "MIT", - "dependencies": { - "broadcast-stream": "^0.2.2", - "ssb-ref": "^2.13.9" - } - }, "node_modules/ssb-logging": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ssb-logging/-/ssb-logging-1.0.0.tgz", + "integrity": "sha512-6apTG47+VgLAD3MYkpRTbO27DMDh0YLJIvWfcJUEo5FdrjnLsqDl1kkzQ4B5MbH08z5Vklx5907t4by9rhlROQ==", "license": "MIT", "dependencies": { "bash-color": "0.0.4" } }, - "node_modules/ssb-markdown": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "emoji-named-characters": "^1.0.2", - "ssb-marked": "^0.7.3", - "ssb-msgs": "^5.2.0", - "ssb-ref": "^2.3.0" - } - }, "node_modules/ssb-marked": { "version": "0.7.4", + "resolved": "https://registry.npmjs.org/ssb-marked/-/ssb-marked-0.7.4.tgz", + "integrity": "sha512-7Dnl4iTmugLXchTXN5mVi1t9in/9GsKYMD9Pm0o1JKGftyWP9ztSTl6Lukix6FoNRfLJRhjGzfXj+Aw27TN4pQ==", "license": "MIT", "bin": { "ssb-marked": "bin/marked" @@ -14803,19 +7753,14 @@ }, "node_modules/ssb-master": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ssb-master/-/ssb-master-1.0.3.tgz", + "integrity": "sha512-N1Cxm9WscGD9VEZrWbF2amyQai2U2g9gtq57W5zTqbhlQTLUUvl84U9A6fg6GPkECnUXadulnTw+mMYVkLLHjQ==", "license": "MIT" }, - "node_modules/ssb-meme": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "flumeview-search": "^2.0.0", - "is-my-json-valid": "^2.17.2", - "ssb-ref": "^2.11.1" - } - }, "node_modules/ssb-mentions": { "version": "0.5.2", + "resolved": "https://registry.npmjs.org/ssb-mentions/-/ssb-mentions-0.5.2.tgz", + "integrity": "sha512-+1lslVCkfnmJOlswJluia+70K5tTLzwRxbLmluvV0rf2KW5T4DIXBPuAhB3vBsP26emAcTHEOj7QtD8iQ9VWtA==", "license": "MIT", "dependencies": { "lodash.isequal": "^4.5.0", @@ -14825,6 +7770,8 @@ }, "node_modules/ssb-meta-feeds": { "version": "0.39.0", + "resolved": "https://registry.npmjs.org/ssb-meta-feeds/-/ssb-meta-feeds-0.39.0.tgz", + "integrity": "sha512-vnfGA5FT0iHVMTE3Ore10qEi2Bpi2TqIiD4FvHbU+rdBOJyKKE0E3KwDFWhlqGDB5YwSt9YP364WbEjn1VTLQw==", "license": "LGPL-3.0", "dependencies": { "bencode": "^2.0.2", @@ -14850,10 +7797,14 @@ }, "node_modules/ssb-msg-content": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ssb-msg-content/-/ssb-msg-content-1.0.1.tgz", + "integrity": "sha512-M6W0Ef+jif829USmGvh6XeS4lYb/F2lgFhfEoCE/md7ESILNOGidp8frJE2uVOzSr2wVRA265tPrnVb7rYHkug==", "license": "MIT" }, "node_modules/ssb-msgs": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ssb-msgs/-/ssb-msgs-5.2.0.tgz", + "integrity": "sha512-a5SF3fnXhXhcEIT406QNPftf+9YbPWFYFABNudreB19RrE0N3JsJL7bm/eKWgVd0fXNpARZJF3f5BYYMUv6lFw==", "license": "MIT", "dependencies": { "ssb-ref": "^2.0.0" @@ -14861,10 +7812,14 @@ }, "node_modules/ssb-network-errors": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ssb-network-errors/-/ssb-network-errors-1.0.1.tgz", + "integrity": "sha512-Re6q7yZL4GreJXbfa7QWk24Nr/I7bs3cIszV2usiSqVYyDphkcwT8iOXFyqjWxG9gVA3Lfjj+6uwX2WI7SorPw==", "license": "LGPL-3.0" }, "node_modules/ssb-no-auth": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ssb-no-auth/-/ssb-no-auth-1.0.0.tgz", + "integrity": "sha512-+o9WnY566shLyiJp6l21+SHZfgv40SEr35PtoF3uuFYI24vFhgcfv77pXvWpDINVyX2uQ152o2eQHg1amlp6lQ==", "license": "MIT", "dependencies": { "multiserver": "^3.3.3" @@ -14872,183 +7827,17 @@ }, "node_modules/ssb-onion": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ssb-onion/-/ssb-onion-1.0.0.tgz", + "integrity": "sha512-iRXjeI0sbRbgMwaKg+poqhHSH7GvUVgJ+8ysi8q7Hjj4wHkczLGAOyVjnyhjF+ErPDFn9kXN+46joneKqSgEDg==", "license": "MIT", "dependencies": { "multiserver": "^3.3.3" } }, - "node_modules/ssb-ooo": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/ssb-ooo/-/ssb-ooo-1.3.3.tgz", - "integrity": "sha512-6yI5XBpBHkwFdAELpKwlf1C1V32Z0/AKEJYsoU7lk+Eh88blv8LKydo4a1DnjMiPy4ywn4lRU5oayQaPE5MRtQ==", - "license": "MIT", - "dependencies": { - "flumecodec": "0.0.1", - "flumelog-offset": "^3.4.3", - "flumeview-hashtable": "^1.1.1", - "gossip-query": "^2.0.2", - "mkdirp": "^0.5.1", - "pull-stream": "^3.6.14", - "ssb-keys": "^7.2.0", - "ssb-ref": "^2.13.3", - "ssb-validate": "^4.1.0" - } - }, - "node_modules/ssb-ooo/node_modules/flumecodec": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.1.tgz", - "integrity": "sha512-JT0xivzdV7uTucjsLMw6JhK2e1K5TmU4fGmoQqnrJbydgY/V6+m71QoxX5ZtRR1pKoD48uhPDWWE6G5MlNoGkg==", - "license": "MIT", - "dependencies": { - "level-codec": "^6.2.0" - } - }, - "node_modules/ssb-ooo/node_modules/level-codec": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", - "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", - "deprecated": "Superseded by level-transcoder (https://github.com/Level/community#faq)", - "license": "MIT" - }, - "node_modules/ssb-ooo/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ssb-ooo/node_modules/ssb-keys": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.2.tgz", - "integrity": "sha512-FPeyYU/3LpxcagnbmVWE+Q/qzg6keqeOBPbD7sEH9UKixUASeufPKiORDgh8nVX7J9Z+0vUaHt/WG999kGjvVQ==", - "license": "MIT", - "dependencies": { - "chloride": "^2.2.8", - "mkdirp": "~0.5.0", - "private-box": "^0.3.0" - } - }, - "node_modules/ssb-partial-replication": { - "version": "3.0.1", - "license": "Beerware", - "dependencies": { - "pull-cont": "^0.1.1", - "pull-stream": "^3.6.14", - "ssb-sort": "^1.1.3" - } - }, - "node_modules/ssb-plugins": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.5", - "explain-error": "^1.0.4", - "mkdirp": "^0.5.1", - "muxrpc": "^6.4.6", - "muxrpc-validation": "^3.0.0", - "mv": "^2.1.1", - "osenv": "^0.1.5", - "pull-cat": "^1.1.11", - "pull-many": "^1.0.8", - "pull-pushable": "^2.2.0", - "pull-stream": "^3.6.9", - "stream-to-pull-stream": "^1.7.2" - } - }, - "node_modules/ssb-plugins/node_modules/cross-spawn": { - "version": "6.0.6", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/ssb-plugins/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ssb-plugins/node_modules/muxrpc": { - "version": "6.7.3", - "license": "MIT", - "dependencies": { - "clarify-error": "^1.0.0", - "debug": "^4.3.3", - "packet-stream": "~2.0.0", - "packet-stream-codec": "^1.2.0", - "pull-goodbye": "0.0.3", - "pull-stream": "^3.6.10" - } - }, - "node_modules/ssb-plugins/node_modules/path-key": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ssb-plugins/node_modules/semver": { - "version": "5.7.2", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/ssb-plugins/node_modules/shebang-command": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssb-plugins/node_modules/shebang-regex": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssb-plugins/node_modules/which": { - "version": "1.3.1", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/ssb-private": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "base64-url": "^2.2.0", - "explain-error": "^1.0.4", - "flumeview-links": "^2.0.1", - "ssb-keys": "^7.0.14" - } - }, "node_modules/ssb-private-group-keys": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/ssb-private-group-keys/-/ssb-private-group-keys-0.4.1.tgz", + "integrity": "sha512-Npl9NpUZvHho10h18/tphMfuyMlj+6lLW0mm+u8ooIRgVh5+hyoOtgJL22VmTEyD8KQVJG6SOjiv6MiGXzIL3g==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.1.2", @@ -15058,36 +7847,10 @@ "ssb-bfe": "^3.1.1" } }, - "node_modules/ssb-private/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ssb-private/node_modules/ssb-keys": { - "version": "7.2.2", - "license": "MIT", - "dependencies": { - "chloride": "^2.2.8", - "mkdirp": "~0.5.0", - "private-box": "^0.3.0" - } - }, - "node_modules/ssb-query": { - "version": "2.4.5", - "license": "MIT", - "dependencies": { - "explain-error": "^1.0.1", - "flumeview-query": "^8.0.0", - "pull-stream": "^3.6.2" - } - }, "node_modules/ssb-ref": { "version": "2.16.0", + "resolved": "https://registry.npmjs.org/ssb-ref/-/ssb-ref-2.16.0.tgz", + "integrity": "sha512-ylyrfz9NLxwTCbeDDAdLo++O3elhNs6/gUqMhZ22F+gSOIjwXy2X7dpg5Q1YTH7uALOSu307Rpo1UfK9sj7Sjw==", "license": "MIT", "dependencies": { "ip": "^1.1.3", @@ -15098,62 +7861,17 @@ }, "node_modules/ssb-ref/node_modules/is-valid-domain": { "version": "0.0.20", + "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.0.20.tgz", + "integrity": "sha512-Yd9oD7sgCycVvH8CHy5U4fLXibPwxVw2+diudYbT8ZfAiQDtW1H9WvPRR4+rtN9qOll+r+KAfO4SjO28OPpitA==", "license": "MIT", "dependencies": { "punycode": "^1.4.1" } }, - "node_modules/ssb-replicate": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ssb-replicate/-/ssb-replicate-1.3.5.tgz", - "integrity": "sha512-XJcsI8xMEpkEF4VF3eUd0edkakBEokakmIPLk2kR6ryNNYo72M+WPB6q+t2JSNkpGIaKFBJTCEMTkNJnLrM9EQ==", - "license": "MIT", - "dependencies": { - "deep-equal": "^2.2.3", - "observ-debounce": "^1.1.1", - "pull-cat": "^1.1.11", - "pull-next": "^1.0.1", - "pull-notify": "^0.1.2", - "pull-paramap": "^1.2.2", - "pull-pushable": "^2.2.0", - "pull-stream": "^3.7.0", - "ssb-ref": "^2.16.0" - } - }, - "node_modules/ssb-replicate/node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/ssb-replication-scheduler": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/ssb-replication-scheduler/-/ssb-replication-scheduler-3.1.1.tgz", + "integrity": "sha512-mLGSs8RjY9LpyVa4uJ+FIFvlqv6A4aBb3YAKHziAY8IRhkbIQmS/HbHf5p2IKBSf9wo0/Ac+EzPDQdeO8hCCpA==", "license": "LGPL-3.0", "dependencies": { "debug": "^4.3.2", @@ -15174,6 +7892,8 @@ }, "node_modules/ssb-replication-scheduler/node_modules/ssb-box2": { "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ssb-box2/-/ssb-box2-7.5.0.tgz", + "integrity": "sha512-cYhq4CdjHXlQCQZqqzbkBygD9Hyf5C86i4xHHNqoMW1FPS20m0uEjwTTpMFJOZfhXs/wgeCuAJ1h4UG1Es+slg==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.2", @@ -15188,6 +7908,8 @@ }, "node_modules/ssb-replication-scheduler/node_modules/ssb-keyring": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ssb-keyring/-/ssb-keyring-7.0.0.tgz", + "integrity": "sha512-HBeAFCdjqSLCiors10s+AHfaFm78ac97oqeWgDg9Es4gK2LUs/ZQ1DjoYH+BS4EnTh5yE+Y9E8ska4WWc0p4Dw==", "license": "LGPL-3.0", "dependencies": { "charwise": "^3.0.1", @@ -15204,6 +7926,8 @@ }, "node_modules/ssb-replication-scheduler/node_modules/ssb-keyring/node_modules/private-group-spec": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/private-group-spec/-/private-group-spec-8.1.0.tgz", + "integrity": "sha512-vxChFtmkWW85VJjh3VmByB1zeOp40JZmJ+0YBCibxlxBHuaW5aFa6BZ/zamkH2sBtXAjwZnou4f5Ty7CUj3NvA==", "license": "LGPL-3.0-only", "dependencies": { "is-my-ssb-valid": "^1.2.0" @@ -15211,6 +7935,8 @@ }, "node_modules/ssb-replication-scheduler/node_modules/ssb-private-group-keys": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ssb-private-group-keys/-/ssb-private-group-keys-1.1.2.tgz", + "integrity": "sha512-0UPPmxy61qmbDmP71J2vhX6UPfCtXa/CNehxYTgk2+AaLXsnA0perGZAiOWm9niGEU50TYYC5/jsIfjz4IiD9A==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.2", @@ -15222,37 +7948,17 @@ }, "node_modules/ssb-schema-definitions": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ssb-schema-definitions/-/ssb-schema-definitions-1.3.0.tgz", + "integrity": "sha512-UUNBnXpaISaM3djBDH8HB5RZtXBS+/ThehppCLQ6LJru7IMubu/hOPbD3EYahfidw13pvdCQXSuGkIeKIcOv8g==", "license": "MIT", "dependencies": { "ssb-ref": "^2.13.6" } }, - "node_modules/ssb-search": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "flumeview-search": "^2.0.0", - "pull-cont": "^0.1.1", - "pull-stream": "^3.6.7", - "ssb-msgs": "^5.2.0" - } - }, - "node_modules/ssb-server": { - "resolved": "packages/ssb-server", - "link": true - }, - "node_modules/ssb-social-index": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "bipf": "^1.4.0", - "pull-defer": "^0.2.3", - "pull-stream": "^3.6.9", - "ssb-ref": "^2.7.1" - } - }, "node_modules/ssb-sort": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ssb-sort/-/ssb-sort-1.1.3.tgz", + "integrity": "sha512-oPsF8lGgcHcIb4F1GddV3CbZTJZ0OzxI9fHXH0Zc7ZjqjFlYdqMDxFSuvqJnmtDydJcswyGANiziP1ghd69jOw==", "license": "MIT", "dependencies": { "ssb-ref": "^2.3.0" @@ -15260,6 +7966,8 @@ }, "node_modules/ssb-subset-ql": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ssb-subset-ql/-/ssb-subset-ql-1.0.1.tgz", + "integrity": "sha512-msZA4Oc1rgEEAVLyzMiQNxJf85+XBrw7IEQLHQpMDv+o/p4GGX9Ygijc2RtItnWSu1H5fFcZChL8pf/q+j3q+w==", "license": "LGPL-3.0", "dependencies": { "nano-equal": "^2.0.2", @@ -15267,18 +7975,10 @@ "ssb-ref": "^2.13.0" } }, - "node_modules/ssb-tangle": { - "version": "1.0.1", - "license": "AGPL-3.0", - "dependencies": { - "is-my-json-valid": "^2.20.0", - "pull-stream": "^3.6.11", - "ssb-ref": "^2.13.9", - "ssb-sort": "^1.1.3" - } - }, "node_modules/ssb-thread-schema": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ssb-thread-schema/-/ssb-thread-schema-1.1.1.tgz", + "integrity": "sha512-93RcHfnev5vj7J5UZI47lu7l71bCevUdO80xUDZSgIPGQw+FrHo0LGSxDTfzNngJaJTlat5mEkRwrrY+wYoahg==", "license": "AGPL-3.0", "dependencies": { "is-my-json-valid": "^2.20.0", @@ -15286,26 +7986,10 @@ "ssb-schema-definitions": "^1.1.5" } }, - "node_modules/ssb-threads": { - "version": "10.8.0", - "license": "MIT", - "dependencies": { - "bipf": "^1.9.0", - "pull-cat": "^1.1.11", - "pull-level": "^2.0.4", - "pull-stream": "^3.6.2", - "secret-stack-decorators": "1.1.0", - "ssb-db2": ">=3.4.1", - "ssb-ref": "^2.13.0", - "ssb-sort": "1.1.x", - "ssb-typescript": "^2.5.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ssb-tribes2": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ssb-tribes2/-/ssb-tribes2-1.4.0.tgz", + "integrity": "sha512-3Cv1HNUheBtTgfi4CobjJLd5s9xcz4d1pgNcyEUPK+mQOnD185ZkMHa/+RIbOjTSBDTlGgjt50Ymixbi+LpJtQ==", "license": "LGPL-3.0-only", "dependencies": { "@tangle/overwrite-fields": "^2.0.3", @@ -15345,6 +8029,8 @@ }, "node_modules/ssb-tribes2/node_modules/flumecodec": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.1.tgz", + "integrity": "sha512-JT0xivzdV7uTucjsLMw6JhK2e1K5TmU4fGmoQqnrJbydgY/V6+m71QoxX5ZtRR1pKoD48uhPDWWE6G5MlNoGkg==", "license": "MIT", "dependencies": { "level-codec": "^6.2.0" @@ -15352,10 +8038,14 @@ }, "node_modules/ssb-tribes2/node_modules/flumecodec/node_modules/level-codec": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", + "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", "license": "MIT" }, "node_modules/ssb-tribes2/node_modules/private-group-spec": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/private-group-spec/-/private-group-spec-8.1.0.tgz", + "integrity": "sha512-vxChFtmkWW85VJjh3VmByB1zeOp40JZmJ+0YBCibxlxBHuaW5aFa6BZ/zamkH2sBtXAjwZnou4f5Ty7CUj3NvA==", "license": "LGPL-3.0-only", "dependencies": { "is-my-ssb-valid": "^1.2.0" @@ -15363,6 +8053,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-box2": { "version": "7.5.0", + "resolved": "https://registry.npmjs.org/ssb-box2/-/ssb-box2-7.5.0.tgz", + "integrity": "sha512-cYhq4CdjHXlQCQZqqzbkBygD9Hyf5C86i4xHHNqoMW1FPS20m0uEjwTTpMFJOZfhXs/wgeCuAJ1h4UG1Es+slg==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.2", @@ -15377,6 +8069,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-db2": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ssb-db2/-/ssb-db2-7.2.0.tgz", + "integrity": "sha512-XxqzmccNgE2YmUZ+lXhw08BVlLEGA8LnIXBeeem4+084/fOPdBdGbXy8rb1G3UoiKVYFLVBc64LhbRQ/JfaS6w==", "license": "LGPL-3.0-only", "dependencies": { "async-append-only-log": "^4.3.7", @@ -15421,6 +8115,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-db2/node_modules/private-group-spec": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/private-group-spec/-/private-group-spec-1.2.0.tgz", + "integrity": "sha512-O7SfG+vZIZgqDXy/wjsuTRI5LaozW4rxaZBpGmwlcDfjIvxvYWNboyNm1PoQUU6j4dQ02V1tOQVLDq9u2RzolA==", "license": "LGPL-3.0-only", "dependencies": { "is-my-ssb-valid": "^1.2.0" @@ -15428,6 +8124,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-db2/node_modules/ssb-box2": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ssb-box2/-/ssb-box2-3.0.1.tgz", + "integrity": "sha512-jQhrsEyrpqnUciEb1qzqc/SJCAx3hTm48BMzMy1bE//xQthWRahigTffflOM7pdRieGlxwGlHc8PpagBBZOkhA==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.0", @@ -15441,6 +8139,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-db2/node_modules/ssb-box2/node_modules/ssb-private-group-keys": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/ssb-private-group-keys/-/ssb-private-group-keys-0.4.1.tgz", + "integrity": "sha512-Npl9NpUZvHho10h18/tphMfuyMlj+6lLW0mm+u8ooIRgVh5+hyoOtgJL22VmTEyD8KQVJG6SOjiv6MiGXzIL3g==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.1.2", @@ -15452,6 +8152,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-db2/node_modules/ssb-keyring": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ssb-keyring/-/ssb-keyring-2.2.0.tgz", + "integrity": "sha512-pXNzYsQbtdWoWAjEPT5lVrZs/Px4ypJU2m4S34tTN6KivZukHPcflj1/IbZSDj7kHJGQhJRcyqGQ3MxgQdFBrA==", "license": "LGPL-3.0", "dependencies": { "charwise": "^3.0.1", @@ -15467,6 +8169,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-keyring": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ssb-keyring/-/ssb-keyring-7.0.0.tgz", + "integrity": "sha512-HBeAFCdjqSLCiors10s+AHfaFm78ac97oqeWgDg9Es4gK2LUs/ZQ1DjoYH+BS4EnTh5yE+Y9E8ska4WWc0p4Dw==", "license": "LGPL-3.0", "dependencies": { "charwise": "^3.0.1", @@ -15483,6 +8187,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-private-group-keys": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ssb-private-group-keys/-/ssb-private-group-keys-1.1.2.tgz", + "integrity": "sha512-0UPPmxy61qmbDmP71J2vhX6UPfCtXa/CNehxYTgk2+AaLXsnA0perGZAiOWm9niGEU50TYYC5/jsIfjz4IiD9A==", "license": "LGPL-3.0-only", "dependencies": { "envelope-js": "^1.3.2", @@ -15494,6 +8200,8 @@ }, "node_modules/ssb-tribes2/node_modules/ssb-private-group-keys/node_modules/private-group-spec": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/private-group-spec/-/private-group-spec-1.2.0.tgz", + "integrity": "sha512-O7SfG+vZIZgqDXy/wjsuTRI5LaozW4rxaZBpGmwlcDfjIvxvYWNboyNm1PoQUU6j4dQ02V1tOQVLDq9u2RzolA==", "license": "LGPL-3.0-only", "dependencies": { "is-my-ssb-valid": "^1.2.0" @@ -15501,10 +8209,14 @@ }, "node_modules/ssb-typescript": { "version": "2.8.0", + "resolved": "https://registry.npmjs.org/ssb-typescript/-/ssb-typescript-2.8.0.tgz", + "integrity": "sha512-akqVsc2HNM0x5q4iN8v/t0jDuNOyU8E8UfyObqU4qWALIXLUptUQd1n03bWOcDn/+sjb2CLpjq584gDoQ4gEjw==", "license": "MIT" }, "node_modules/ssb-unix-socket": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ssb-unix-socket/-/ssb-unix-socket-1.0.0.tgz", + "integrity": "sha512-Z4jBj917W+dKAiDglwxCpWm8vINOMtkpHQIgk50NQTb5jHqHI5Rcyiy7EO0uRcWwRWqXi1ZwOTEFVyLyyuittA==", "license": "MIT", "dependencies": { "multiserver": "^3.3.3" @@ -15512,6 +8224,8 @@ }, "node_modules/ssb-uri2": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/ssb-uri2/-/ssb-uri2-2.4.1.tgz", + "integrity": "sha512-3Oia/V8Q7kWj2/Wcw67yKSkMcxQIAIHGDZ4CsQxqVrEjqApYkQ5Drj0nlyFCA/qX8HF0ILFwIrHuQesk0s2PkQ==", "license": "LGPL-3.0", "dependencies": { "ssb-typescript": "^2.5.0" @@ -15522,6 +8236,8 @@ }, "node_modules/ssb-validate": { "version": "4.1.4", + "resolved": "https://registry.npmjs.org/ssb-validate/-/ssb-validate-4.1.4.tgz", + "integrity": "sha512-nzj5EQnhm5fBGXgtzuuWgxv45dW+CJJm4eCLZKiOxyG1NE/WJZwju2DmqZfiE9zr9bC2T2hPHkckDP0CCP8v8w==", "license": "MIT", "dependencies": { "is-canonical-base64": "^1.1.1", @@ -15530,176 +8246,24 @@ "ssb-ref": "^2.6.2" } }, - "node_modules/ssb-ws": { - "version": "6.2.3", - "license": "MIT", - "dependencies": { - "emoji-server": "^1.0.0", - "multiblob-http": "^1.0.0", - "multiserver": "^3.0.2", - "pull-box-stream": "^1.0.13", - "pull-stream": "^3.6.9", - "ssb-ref": "^2.3.0", - "stack": "^0.1.0" - } - }, - "node_modules/stack": { - "version": "0.1.0", - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "license": "MIT", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/statistics": { - "version": "3.3.0" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/statistics/-/statistics-3.3.0.tgz", + "integrity": "sha512-9+dmo0XcSK1AU6/uNuSUV/9/KnGUT+7ZKeL+J4IWcjcczflFTWwYQaInBBSo2zyEFVkFd59bGVxzZ8SSBWC66g==" }, "node_modules/statuses": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/stdin-discarder": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.1.tgz", - "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stdout-update": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/stdout-update/-/stdout-update-4.0.1.tgz", - "integrity": "sha512-wiS21Jthlvl1to+oorePvcyrIkiG/6M3D3VTmDUlJm7Cy6SbFhKkAvX+YBuHLxck/tO3mrdpC/cNesigQc3+UQ==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^6.2.0", - "ansi-styles": "^6.2.1", - "string-width": "^7.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/stdout-update/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/stdout-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/stdout-update/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/stdout-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stdout-update/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/steno": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", - "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -15709,8 +8273,16 @@ "node": ">= 0.4" } }, + "node_modules/store2": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz", + "integrity": "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==", + "license": "MIT" + }, "node_modules/stream-to-pull-stream": { "version": "1.7.3", + "resolved": "https://registry.npmjs.org/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz", + "integrity": "sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==", "license": "MIT", "dependencies": { "looper": "^3.0.0", @@ -15719,6 +8291,8 @@ }, "node_modules/stream-to-pull-stream/node_modules/looper": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", + "integrity": "sha512-LJ9wplN/uSn72oJRsXTx+snxPet5c8XiZmOKCm906NVYu+ag6SB6vUcnJcWxgnl2NfbIyeobAn7Bwv6xRj2XJg==", "license": "MIT" }, "node_modules/streamx": { @@ -15732,19 +8306,10 @@ "text-decoder": "^1.1.0" } }, - "node_modules/strict-event-emitter-types": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -15759,8 +8324,10 @@ } }, "node_modules/string-width-cjs": { - "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "name": "string-width", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -15773,10 +8340,14 @@ }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", "engines": { "node": ">=12" @@ -15787,6 +8358,8 @@ }, "node_modules/string-width/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -15800,6 +8373,8 @@ }, "node_modules/string.prototype.trim": { "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -15819,6 +8394,8 @@ }, "node_modules/string.prototype.trimend": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -15835,6 +8412,8 @@ }, "node_modules/string.prototype.trimstart": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -15848,20 +8427,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stringify-entities": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "character-entities-html4": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-hexadecimal": "^1.0.0" + "safe-buffer": "~5.2.0" } }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15871,8 +8449,10 @@ } }, "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "name": "strip-ansi", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15881,38 +8461,10 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strnum": { - "version": "1.1.2", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/strtok3": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", @@ -15926,17 +8478,10 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -15951,123 +8496,10 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, - "node_modules/tape": { - "version": "4.17.0", - "license": "MIT", - "dependencies": { - "@ljharb/resumer": "~0.0.1", - "@ljharb/through": "~2.3.9", - "call-bind": "~1.0.2", - "deep-equal": "~1.1.1", - "defined": "~1.0.1", - "dotignore": "~0.1.2", - "for-each": "~0.3.3", - "glob": "~7.2.3", - "has": "~1.0.3", - "inherits": "~2.0.4", - "is-regex": "~1.1.4", - "minimist": "~1.2.8", - "mock-property": "~1.0.0", - "object-inspect": "~1.12.3", - "resolve": "~1.22.6", - "string.prototype.trim": "~1.2.8" - }, - "bin": { - "tape": "bin/tape" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tape/node_modules/deep-equal": { - "version": "1.1.2", - "license": "MIT", - "dependencies": { - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.5.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tape/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tape/node_modules/is-regex": { - "version": "1.1.4", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tape/node_modules/object-inspect": { - "version": "1.12.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tar": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", - "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, "node_modules/tar-stream": { "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "license": "MIT", "dependencies": { "b4a": "^1.6.4", @@ -16075,29 +8507,42 @@ "streamx": "^2.15.0" } }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "node_modules/telegram": { + "version": "2.26.22", + "resolved": "https://registry.npmjs.org/telegram/-/telegram-2.26.22.tgz", + "integrity": "sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg==", + "deprecated": "This package is archived and no longer maintained. Development continues in teleproto (https://npmjs.com/package/teleproto), a largely compatible, actively maintained fork. See the migration guide at https://docs.teleproto.dev/migrating-from-gramjs", "license": "MIT", - "optional": true, "dependencies": { - "streamx": "^2.12.5" + "@cryptography/aes": "^0.1.1", + "async-mutex": "^0.3.0", + "big-integer": "^1.6.48", + "buffer": "^6.0.3", + "htmlparser2": "^6.1.0", + "mime": "^3.0.0", + "node-localstorage": "^2.2.1", + "pako": "^2.0.3", + "path-browserify": "^1.0.1", + "real-cancellable-promise": "^1.1.1", + "socks": "^2.6.2", + "store2": "^2.13.0", + "ts-custom-error": "^3.2.0", + "websocket": "^1.0.34" + }, + "optionalDependencies": { + "bufferutil": "^4.0.3", + "utf-8-validate": "^5.0.5" } }, "node_modules/text-decoder": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "license": "MIT" - }, "node_modules/tldts": { "version": "7.0.23", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", @@ -16118,6 +8563,8 @@ }, "node_modules/to-buffer": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", "license": "MIT", "dependencies": { "isarray": "^2.0.5", @@ -16130,6 +8577,8 @@ }, "node_modules/to-camel-case": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz", + "integrity": "sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==", "license": "MIT", "dependencies": { "to-space-case": "^1.0.0" @@ -16137,85 +8586,23 @@ }, "node_modules/to-no-case": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", + "integrity": "sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==", "license": "MIT" }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "license": "MIT", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/to-space-case": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz", + "integrity": "sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==", "license": "MIT", "dependencies": { "to-no-case": "^1.0.0" } }, - "node_modules/to-vfile": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-1.0.0.tgz", - "integrity": "sha512-BHc+hdHwULe8x6xmQhSuTsiiPHyTCCf7dtH7l6WkBoYBR2rDfYtoJufKLDDAYGMfA+1XoRq44HfyjoB9vMBr1w==", - "license": "MIT", - "dependencies": { - "vfile": "^1.0.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.0", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -16223,6 +8610,8 @@ }, "node_modules/token-types": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", @@ -16238,21 +8627,13 @@ }, "node_modules/too-hot": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/too-hot/-/too-hot-1.0.1.tgz", + "integrity": "sha512-ymyiJ8bM8e2wKfJpxbq4Fi7GwLy9w7P8PKHnsWlDgbRIampbMtjv8eFrvpMIC0bK5dKybYrYqvbLUVu5/sDQbw==", "license": "MIT", "dependencies": { "cpu-percentage": "~1.0.3" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -16265,12 +8646,10 @@ "node": ">=16" } }, - "node_modules/tr46": { - "version": "0.0.3", - "license": "MIT" - }, "node_modules/traverse": { "version": "0.6.11", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", + "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", "license": "MIT", "dependencies": { "gopd": "^1.2.0", @@ -16284,81 +8663,64 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/trim": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.3.tgz", - "integrity": "sha512-h82ywcYhHK7veeelXrCScdH7HkWfbIT1D/CgYO+nmDarz3SGNssVBMws6jU16Ga60AJCRAvPV6w6RLuNerQqjg==", - "deprecated": "Use String.prototype.trim() instead" - }, - "node_modules/trim-lines": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.3.tgz", - "integrity": "sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/trim-trailing-lines": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", - "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", "license": "WTFPL", "dependencies": { "utf8-byte-length": "^1.0.1" } }, + "node_modules/ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tsscmp": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", "license": "MIT", "engines": { "node": ">=0.6.x" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/tweetnacl": { "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, "node_modules/tweetnacl-auth": { "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tweetnacl-auth/-/tweetnacl-auth-0.3.1.tgz", + "integrity": "sha512-9/c8c6qRMTfWuv54ETFhihgYoofi0M9HUovMSGJ1rLRUj6O5A0vuCg2L/qKfvmcjLVhaTgAJCLy2EqCLJK2QLw==", "license": "Unlicense", "dependencies": { "tweetnacl": "0.x.x" } }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-is": { "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -16370,6 +8732,8 @@ }, "node_modules/typed-array-buffer": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -16382,6 +8746,8 @@ }, "node_modules/typed-array-byte-length": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -16399,6 +8765,8 @@ }, "node_modules/typed-array-byte-offset": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -16418,6 +8786,8 @@ }, "node_modules/typed-array-length": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -16434,14 +8804,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, "node_modules/typedarray-to-buffer": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz", + "integrity": "sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ==", "funding": [ { "type": "github", @@ -16460,6 +8826,8 @@ }, "node_modules/typedarray.prototype.slice": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", + "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -16480,31 +8848,20 @@ }, "node_modules/typedfastbitset": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/typedfastbitset/-/typedfastbitset-0.2.1.tgz", + "integrity": "sha512-B2B+wo5gC7VRAqcFEiUCjS6CJ1vmeYZ7uzY3Jsu6UNStHiF+W0vkhZAmQaj5m9sC2KVrpyHGRzGuhz3M6+n/8A==", "license": "Apache-2.0" }, - "node_modules/typescript": { - "version": "5.8.3", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typewiselite": { - "version": "1.0.0", - "license": "MIT" - }, "node_modules/uint48be": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uint48be/-/uint48be-2.0.1.tgz", + "integrity": "sha512-LQvWofTo3RCz+XaQR3VNch+dDFwpIvWr/98imhQne++vFhpQP16YAC/a8w9N00Heqqra00ACjHT18cgvn5H+bg==", "license": "MIT" }, "node_modules/unbox-primitive": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -16519,13 +8876,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, "node_modules/undici": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.2.tgz", @@ -16537,408 +8887,53 @@ }, "node_modules/undici-types": { "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/unified": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/unified/-/unified-2.1.4.tgz", - "integrity": "sha512-qa4nA26ms49OczPueTt7G46r89TOlwAJ4pEk2U4mwkV1wNhjttItF03SE/YnfkgWg14tzmAHXGhJp2GhDYwn1A==", - "license": "MIT", - "dependencies": { - "attach-ware": "^1.0.0", - "bail": "^1.0.0", - "extend": "^3.0.0", - "unherit": "^1.0.4", - "vfile": "^1.0.0", - "ware": "^1.3.0" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-random": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-random/-/unique-random-1.0.0.tgz", - "integrity": "sha512-K1sUkPf9EXCZFNIlMCoX4icAqcvkR4FMPH4Z61HbyiWhQl1ZGo0zYeV2bJmocK8Cp6tnKYrCnpkeKGebXZoRTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-random-array": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unique-random-array/-/unique-random-array-1.0.1.tgz", - "integrity": "sha512-z9J/SV8CUIhIRROcHe9YUoAT6XthUJt0oUyLGgobiXJprDP9O9dsErNevvSaAv5BkhwFEVPn6nIEOKeNE6Ck1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "unique-random": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unist-util-is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", - "license": "MIT" - }, - "node_modules/unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", - "license": "MIT", - "dependencies": { - "unist-util-visit-parents": "^2.0.0" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", - "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", - "license": "MIT", - "dependencies": { - "unist-util-is": "^3.0.0" - } - }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.2", - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "license": "ISC" - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, "license": "MIT", "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "node-gyp-build": "^4.3.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "license": "MIT", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "license": "MIT", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/unset-value/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/untildify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==", - "license": "MIT", - "dependencies": { - "os-homedir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "license": "MIT" - }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "license": "MIT" - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==", - "license": "MIT", - "dependencies": { - "os-homedir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "node": ">=6.14.2" } }, "node_modules/utf8-byte-length": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", "license": "(WTFPL OR MIT)" }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", - "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/vfile": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz", - "integrity": "sha512-7Fz639rwERslMqQCuf1/0H4Tqe2q484Xl6X/jsKqrP7IjFcDODFURhv0GekMnImpbj9pTOojtqL7r39LJJkjGA==", - "license": "MIT" - }, - "node_modules/vfile-find-down": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vfile-find-down/-/vfile-find-down-1.0.0.tgz", - "integrity": "sha512-AOXiJrVKizToYfRosXd1p9Fq8b0u0qchvSwVF1/ue3JE7o7KuQ/UH24bNAPLDUG/RIM1DZ6zWtDsiLShcma4WA==", - "license": "MIT", - "dependencies": { - "to-vfile": "^1.0.0" - } - }, - "node_modules/vfile-find-up": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vfile-find-up/-/vfile-find-up-1.0.0.tgz", - "integrity": "sha512-t97P/jQswvX0n//+RB74Wj43VOg3tel2InzaJYryaBewd4uN4pNXuoH/F00PkI3U1fBp+w/SIyrTjzIzPwDODg==", - "license": "MIT", - "dependencies": { - "to-vfile": "^1.0.0" - } - }, - "node_modules/vfile-reporter": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-1.5.0.tgz", - "integrity": "sha512-VFF1LK0O8/nLmrPcc+5VMEnyP21BTzdVoq1rbxTaVt6cmSVk5MQs1POhkfY/cctndmZheNgirTcAMoiKj3aJYA==", - "license": "MIT", - "dependencies": { - "chalk": "^1.1.0", - "log-symbols": "^1.0.2", - "plur": "^2.0.0", - "repeat-string": "^1.5.0", - "string-width": "^1.0.0", - "text-table": "^0.2.0", - "vfile-sort": "^1.0.0" - } - }, - "node_modules/vfile-reporter/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-reporter/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-reporter/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-reporter/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-reporter/node_modules/log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==", - "license": "MIT", - "dependencies": { - "chalk": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-reporter/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-reporter/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vfile-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-1.0.0.tgz", - "integrity": "sha512-6qIalNEKUt2YyVFzyJptdEo9sm/pMrSKvOJ35lH4us9YeW08zRs3E9VbdJ0O0n2Thxc1TWINP5QVhucy/YiGPA==", - "license": "MIT" - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -16951,22 +8946,31 @@ "node": ">=18" } }, - "node_modules/ware": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", - "integrity": "sha512-Y2HUDMktriUm+SR2gZWxlrszcgtXExlhQYZ8QJNYbl22jum00KIUcHJ/h/sdAXhWTJcbSkiMYN9Z2tWbWYSrrw==", - "license": "MIT", + "node_modules/websocket": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "license": "Apache-2.0", "dependencies": { - "wrap-fn": "^0.1.0" + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "license": "MIT" + "node_modules/websocket/node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } }, "node_modules/whatwg-mimetype": { "version": "5.0.0", @@ -16977,16 +8981,10 @@ "node": ">=20" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -17000,6 +8998,8 @@ }, "node_modules/which-boxed-primitive": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", @@ -17017,6 +9017,8 @@ }, "node_modules/which-builtin-type": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -17042,6 +9044,8 @@ }, "node_modules/which-collection": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "license": "MIT", "dependencies": { "is-map": "^2.0.3", @@ -17058,10 +9062,14 @@ }, "node_modules/which-module": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "license": "ISC" }, "node_modules/which-typed-array": { "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -17079,15 +9087,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -17102,8 +9105,10 @@ } }, "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "name": "wrap-ansi", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -17119,10 +9124,14 @@ }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -17135,6 +9144,8 @@ }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", "engines": { "node": ">=12" @@ -17145,6 +9156,8 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "license": "MIT", "engines": { "node": ">=12" @@ -17155,6 +9168,8 @@ }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -17166,27 +9181,27 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrap-fn": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", - "integrity": "sha512-xDLdGx0M8JQw9QDAC9s5NUxtg9MI09F6Vbxa2LYoSoCvzJnx2n81YMIfykmXEGsUvuLaxnblJTzhSOjUOX37ag==", - "license": "MIT", - "dependencies": { - "co": "3.1.0" - } - }, - "node_modules/wrap-fn/node_modules/co": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", - "integrity": "sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA==", - "license": "MIT" - }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, "node_modules/ws": { "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -17221,10 +9236,14 @@ }, "node_modules/xsalsa20": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/xsalsa20/-/xsalsa20-1.2.0.tgz", + "integrity": "sha512-FIr/DEeoHfj7ftfylnoFt3rAIRoWXpx2AoDfrT2qD2wtp7Dp+COajvs/Icb7uHqRW9m60f5iXZwdsJJO3kvb7w==", "license": "MIT" }, "node_modules/xtend": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "license": "MIT", "engines": { "node": ">=0.4" @@ -17232,22 +9251,27 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" } }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=0.10.32" } }, "node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -17264,6 +9288,8 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" @@ -17271,10 +9297,14 @@ }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -17287,31 +9317,29 @@ }, "node_modules/ylru": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", "license": "MIT", "engines": { "node": ">= 4.0.0" } }, - "node_modules/yoctocolors": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zerr": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/zerr/-/zerr-1.0.4.tgz", + "integrity": "sha512-u3WW9JA9xNmhy3cK6iSi3uWe4XmuqPCZ+jOrngEIwYz//ZUhY6bexVFrWLcxrIDUbJeXnyJORqVYPi2YU6gA+A==", "license": "MIT" }, "node_modules/ziii": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ziii/-/ziii-1.0.2.tgz", + "integrity": "sha512-q1FogtBIchy1W0fkxUpe6A4n4WUvAM+hAHN1J6LjBNCV42ZegeC5JSz0mcNv4qxnI0V4cL4FNeEhPMm97Ed0kA==", "license": "MIT" }, "node_modules/zip-stream": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "license": "MIT", "dependencies": { "archiver-utils": "^5.0.0", @@ -17324,490 +9352,12 @@ }, "node_modules/zod": { "version": "3.25.56", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.56.tgz", + "integrity": "sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "packages/ssb-server": { - "version": "15.3.0", - "license": "MIT", - "dependencies": { - "bash-color": "~0.0.3", - "broadcast-stream": "^0.2.1", - "cont": "~1.0.3", - "cross-spawn": "^6.0.5", - "debug": "^4.1.1", - "deep-equal": "^1.0.1", - "explain-error": "^1.0.3", - "has-network": "0.0.1", - "ip": "^1.1.5", - "mdmanifest": "^1.0.8", - "minimist": "^1.1.3", - "mkdirp": "~0.5.0", - "multiblob": "^1.13.0", - "multiserver": "^3.3.1", - "multiserver-address": "^1.0.1", - "muxrpc-validation": "^3", - "muxrpcli": "3", - "mv": "^2.1.1", - "osenv": "^0.1.5", - "pull-cat": "~1.1.5", - "pull-file": "^1.0.0", - "pull-many": "~1.0.6", - "pull-pushable": "^2.2.0", - "pull-stream": "^3.6.2", - "rimraf": "^2.4.2", - "secret-stack": "^6.3.1", - "ssb-blobs": "1.2.2", - "ssb-caps": "^1.0.1", - "ssb-client": "^4.7.9", - "ssb-config": "^3.2.5", - "ssb-db": "19.2.0", - "ssb-ebt": "^5.6.1", - "ssb-friends": "^4.1.0", - "ssb-gossip": "^1.1.0", - "ssb-invite": "^2.1.2", - "ssb-keys": "^7.1.1", - "ssb-links": "^3.0.10", - "ssb-local": "^1.0.0", - "ssb-logging": "^1.0.0", - "ssb-master": "^1.0.2", - "ssb-no-auth": "^1.0.0", - "ssb-onion": "^1.0.0", - "ssb-ooo": "^1.3.0", - "ssb-plugins": "1.0.0", - "ssb-query": "^2.4.0", - "ssb-ref": "^2.13.9", - "ssb-replicate": "^1.3.0", - "ssb-unix-socket": "^1.0.0", - "ssb-ws": "^6.2.1", - "stream-to-pull-stream": "^1.6.10", - "zerr": "^1.0.0" - }, - "bin": { - "sbot": "bin.js", - "ssb-server": "bin.js" - }, - "devDependencies": { - "cat-names": "^2.0.0", - "compatibility": "^1.0.0", - "dog-names": "~1.0.2", - "hexpp": "^2.0.0", - "interleavings": "^1.0.0", - "npm-install-package": "^2.1.0", - "nyc": "^13.2.0", - "pull-abortable": "^4.1.1", - "pull-bitflipper": "^0.1.0", - "pull-paramap": "^1.2.2", - "rng": "^0.2.2", - "run-series": "^1.1.8", - "semver": "^5.6.0", - "ssb-feed": "^2.3.0", - "ssb-generate": "^1.0.1", - "ssb-validate": "^4", - "tape": "^4.9.1", - "typewiselite": "^1.0.0" - } - }, - "packages/ssb-server/node_modules/abstract-leveldown": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.0.3.tgz", - "integrity": "sha512-jzewKKpZbaYUa6HTThnrl+GrJhzjEAeuc7hTVpZdzg7kupXZFoqQDFwyOwLNbmJKJlmzw8yiipMPkDiuKkT06Q==", - "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "license": "MIT", - "dependencies": { - "level-concat-iterator": "~2.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "packages/ssb-server/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "packages/ssb-server/node_modules/epidemic-broadcast-trees": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/epidemic-broadcast-trees/-/epidemic-broadcast-trees-7.0.0.tgz", - "integrity": "sha512-nm1o1ncxcJvXtLJVoPl4AJFX7Bh0BhPsTC9XhCRWwdRJXp1Ud1DcGpi/6c3sUA/URmyoIc1WONNdub+/AE5vTQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "push-stream": "^10.0.0" - } - }, - "packages/ssb-server/node_modules/flumecodec": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/flumecodec/-/flumecodec-0.0.1.tgz", - "integrity": "sha512-JT0xivzdV7uTucjsLMw6JhK2e1K5TmU4fGmoQqnrJbydgY/V6+m71QoxX5ZtRR1pKoD48uhPDWWE6G5MlNoGkg==", - "license": "MIT", - "dependencies": { - "level-codec": "^6.2.0" - } - }, - "packages/ssb-server/node_modules/flumedb": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/flumedb/-/flumedb-1.1.0.tgz", - "integrity": "sha512-Bwol+72GU5z2DxZlnaxUA9A8qaRcQcdTprmRcgfqn2ldn147ByVh9Zyp90hVGPlo/oEN/yjOBUXcNkK3SYjbgA==", - "license": "MIT", - "dependencies": { - "cont": "^1.0.3", - "explain-error": "^1.0.3", - "obv": "0.0.1", - "pull-cont": "^0.1.1", - "pull-looper": "^1.0.0", - "pull-stream": "^3.5.0" - } - }, - "packages/ssb-server/node_modules/flumeview-level": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/flumeview-level/-/flumeview-level-3.0.14.tgz", - "integrity": "sha512-Nl0gJOgrDGpJGZDkP6gvo6s1Q9WmRynbHUvI/JY3eQ81YgzUUa2FKLlfu6OHV5ho5NeXP+00F+0K1yBVaEgJOQ==", - "license": "MIT", - "dependencies": { - "charwise": "^3.0.1", - "explain-error": "^1.0.4", - "level": "^5.0.0", - "ltgt": "^2.1.3", - "mkdirp": "^0.5.1", - "obv": "0.0.1", - "pull-level": "^2.0.3", - "pull-paramap": "^1.2.1", - "pull-stream": "^3.5.0", - "pull-write": "^1.1.1" - } - }, - "packages/ssb-server/node_modules/flumeview-level/node_modules/level": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/level/-/level-5.0.1.tgz", - "integrity": "sha512-wcak5OQeA4rURGacqS62R/xNHjCYnJSQDBOlm4KNUGJVE9bWv2B04TclqReYejN+oD65PzD4FsqeWoI5wNC5Lg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "level-js": "^4.0.0", - "level-packager": "^5.0.0", - "leveldown": "^5.0.0", - "opencollective-postinstall": "^2.0.0" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "packages/ssb-server/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/ssb-server/node_modules/immediate": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", - "integrity": "sha512-RrGCXRm/fRVqMIhqXrGEX9rRADavPiDFSoMb/k64i9XMk8uH4r/Omi5Ctierj6XzNecwDbO4WuFbDD1zmpl3Tg==", - "license": "MIT" - }, - "packages/ssb-server/node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "license": "MIT" - }, - "packages/ssb-server/node_modules/level-codec": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-6.2.0.tgz", - "integrity": "sha512-J437PvCMZZKNT88+VRh6dkmh1ndZzwGwDzb5ZZl3QEsl+U9wIlt8hG+Y1gXVOhH75gf8JyceKGiG6WFUBbxyGQ==", - "deprecated": "Superseded by level-transcoder (https://github.com/Level/community#faq)", - "license": "MIT" - }, - "packages/ssb-server/node_modules/level-js": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-4.0.2.tgz", - "integrity": "sha512-PeGjZsyMG4O89KHiez1zoMJxStnkM+oBIqgACjoo5PJqFiSUUm3GNod/KcbqN5ktyZa8jkG7I1T0P2u6HN9lIg==", - "deprecated": "Superseded by browser-level (https://github.com/Level/community#faq)", - "license": "MIT", - "dependencies": { - "abstract-leveldown": "~6.0.1", - "immediate": "~3.2.3", - "inherits": "^2.0.3", - "ltgt": "^2.1.2", - "typedarray-to-buffer": "~3.1.5" - } - }, - "packages/ssb-server/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "packages/ssb-server/node_modules/muxrpc": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/muxrpc/-/muxrpc-6.7.3.tgz", - "integrity": "sha512-yVr66BigJxHIvHOLsdJzuvrHt46Vq9DiDCBTZiS6q+49mXh4mkDjRiXo9FnGXQv1WJOqnRJk5fcfwV6TaSgNvA==", - "license": "MIT", - "dependencies": { - "clarify-error": "^1.0.0", - "debug": "^4.3.3", - "packet-stream": "~2.0.0", - "packet-stream-codec": "^1.2.0", - "pull-goodbye": "0.0.3", - "pull-stream": "^3.6.10" - } - }, - "packages/ssb-server/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "packages/ssb-server/node_modules/push-stream": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/push-stream/-/push-stream-10.1.2.tgz", - "integrity": "sha512-wCDN1KkAWOMXsiV0XxH/CggHqOYeXvyn00t9Zjp5RKsLZ2rzg/lLJIMAVxYLyh79T168W3fBYcG5TRRJAlQr6g==", - "license": "MIT" - }, - "packages/ssb-server/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "packages/ssb-server/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "packages/ssb-server/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/ssb-server/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "packages/ssb-server/node_modules/ssb-blobs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ssb-blobs/-/ssb-blobs-1.2.2.tgz", - "integrity": "sha512-N+X46lE/KaIH9y1w3LQ9pPnt2tQr5VCSj1dAo/pJGYnSwnHz8GhIiboq7UGzrCZwCWgVUs22/2YiytCXSC9ASg==", - "license": "MIT", - "dependencies": { - "cont": "^1.0.3", - "debug": "^4.1.1", - "level": "^5.0.1", - "multiblob": "^1.12.0", - "pull-level": "^2.0.4", - "pull-notify": "^0.1.0", - "pull-stream": "^3.3.0", - "ssb-ref": "^2.3.0" - } - }, - "packages/ssb-server/node_modules/ssb-blobs/node_modules/level": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/level/-/level-5.0.1.tgz", - "integrity": "sha512-wcak5OQeA4rURGacqS62R/xNHjCYnJSQDBOlm4KNUGJVE9bWv2B04TclqReYejN+oD65PzD4FsqeWoI5wNC5Lg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "level-js": "^4.0.0", - "level-packager": "^5.0.0", - "leveldown": "^5.0.0", - "opencollective-postinstall": "^2.0.0" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "packages/ssb-server/node_modules/ssb-db": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/ssb-db/-/ssb-db-19.2.0.tgz", - "integrity": "sha512-pJAFizB6OcuJLX4RJJuU9HWyPwM2CqLi/vs08lhVIR3TGxacxpavvK5LzbxT+Y3iWkBchOTKS5hHCigA5aaung==", - "license": "MIT", - "dependencies": { - "async-write": "^2.1.0", - "cont": "~1.0.0", - "explain-error": "~1.0.1", - "flumedb": "^1.0.0", - "flumelog-offset": "^3.3.1", - "flumeview-hashtable": "^1.0.3", - "flumeview-level": "^3.0.5", - "flumeview-reduce": "^1.3.9", - "ltgt": "^2.2.0", - "mdmanifest": "^1.0.8", - "mkdirp": "^0.5.1", - "monotonic-timestamp": "~0.0.8", - "muxrpc-validation": "^3.0.0", - "obv": "0.0.1", - "osenv": "^0.1.5", - "pull-cont": "^0.1.1", - "pull-stream": "^3.4.0", - "rimraf": "^2.6.2", - "ssb-keys": "^7.1.3", - "ssb-msgs": "^5.0.0", - "ssb-ref": "^2.12.0", - "ssb-validate": "^4.0.0", - "typewiselite": "^1.0.0", - "zerr": "^1.0.0" - } - }, - "packages/ssb-server/node_modules/ssb-ebt": { - "version": "5.6.7", - "resolved": "https://registry.npmjs.org/ssb-ebt/-/ssb-ebt-5.6.7.tgz", - "integrity": "sha512-ifPgPNmDE8EKuuoqtXibwgYNtDZNry7sJL1epSUb3XgQr62bUV31N9R5LHKDsI2kx96OgWRwWY2PfZ7vf/hU8Q==", - "license": "MIT", - "dependencies": { - "base64-url": "^2.2.0", - "epidemic-broadcast-trees": "7", - "lossy-store": "^1.2.3", - "pull-stream": "^3.5.0", - "push-stream-to-pull-stream": "^1.0.0", - "ssb-ref": "^2.9.1" - } - }, - "packages/ssb-server/node_modules/ssb-friends": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/ssb-friends/-/ssb-friends-4.4.10.tgz", - "integrity": "sha512-APTTt47PRVv5nzo+8VLWymAxSZPiAA3p5wPu9VlCqr4qQVaDkCF7Xn5Lk9j8VkjEnclNt7aeE/YgnXCld/BPNA==", - "license": "MIT", - "dependencies": { - "bipf": "^1.9.0", - "flumecodec": "0.0.1", - "flumeview-reduce": "^1.4.0", - "layered-graph": "^1.2.0", - "pull-flatmap": "0.0.1", - "pull-level": "^2.0.4", - "pull-notify": "^0.1.2", - "pull-stream": "^3.7.0", - "ssb-ref": "^2.16.0" - } - }, - "packages/ssb-server/node_modules/ssb-invite": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/ssb-invite/-/ssb-invite-2.1.8.tgz", - "integrity": "sha512-fuBu0jtZzfsejLWUQXVzNq6s6IekJGZqVpY6EZLx3J2mM0+3rXuwDI/1LtqL2VOfzrPbC4jRJi6I21PosBGveg==", - "license": "MIT", - "dependencies": { - "cont": "^1.0.3", - "explain-error": "^1.0.4", - "ip": "^1.1.8", - "level": "^6.0.1", - "muxrpc-validation": "^3.0.2", - "ssb-client": "^4.9.0", - "ssb-keys": "^7.2.2", - "ssb-ref": "^2.16.0" - } - }, - "packages/ssb-server/node_modules/ssb-keys": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.2.tgz", - "integrity": "sha512-FPeyYU/3LpxcagnbmVWE+Q/qzg6keqeOBPbD7sEH9UKixUASeufPKiORDgh8nVX7J9Z+0vUaHt/WG999kGjvVQ==", - "license": "MIT", - "dependencies": { - "chloride": "^2.2.8", - "mkdirp": "~0.5.0", - "private-box": "^0.3.0" - } - }, - "packages/ssb-server/node_modules/ssb-plugins": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ssb-plugins/-/ssb-plugins-1.0.0.tgz", - "integrity": "sha512-eM8vid+K8MhwZwzk/CDUhSNhUoS6wYgq9clJrrKaP0/Otdd3zZzcBQw54Xvm0olMcOgpTSlY3m2rT4iqjZPIBw==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.5", - "explain-error": "^1.0.4", - "mdmanifest": "^1.0.8", - "mkdirp": "^0.5.1", - "muxrpc": "^6.4.0", - "muxrpc-validation": "^3.0.0", - "mv": "^2.1.1", - "osenv": "^0.1.5", - "pull-cat": "^1.1.11", - "pull-many": "^1.0.8", - "pull-pushable": "^2.2.0", - "pull-stream": "^3.6.9", - "stream-to-pull-stream": "^1.7.2" - } - }, - "packages/ssb-server/node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "packages/ssb-server/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } } } } diff --git a/src/server/package.json b/src/server/package.json index 7e26715..f8fc610 100644 --- a/src/server/package.json +++ b/src/server/package.json @@ -1,6 +1,6 @@ { "name": "@krakenslab/oasis", - "version": "0.9.5", + "version": "1.3.1", "description": "Oasis - Social Networking Utopia", "repository": { "type": "git", @@ -17,27 +17,22 @@ "start:backend": "node ../backend/backend.js", "start:ssb": "node SSB_server.js start &", "dev": "npm run dev:backend", - "dev:backend": "nodemon", + "dev:backend": "npx --yes nodemon@3.1.14", "postinstall": "node ../../scripts/patch-node-modules.js" }, "dependencies": { "@koa/router": "^13.1.0", - "@open-rpc/client-js": "^1.8.1", - "@xenova/transformers": "^2.17.2", - "abstract-level": "^2.0.1", "archiver": "^7.0.1", "axios": "^1.10.0", - "cors": "^2.8.5", + "bencode": "^2.0.3", + "bipf": "^1.9.0", + "chloride": "^2.4.1", "debug": "^4.3.1", "dompurify": "^3.3.1", "env-paths": "^2.2.1", - "epidemic-broadcast-trees": "^9.0.4", - "express": "^5.1.0", "file-type": "^16.5.4", "highlight.js": "11.0.0", "hyperaxe": "^2.0.1", - "ip": "https://registry.npmjs.org/neoip/-/neoip-3.0.0.tgz", - "is-svg": "^4.4.0", "jsdom": "^28.0.0", "koa": "^2.7.0", "koa-body": "^6.0.1", @@ -47,77 +42,49 @@ "minimist": "^1.2.8", "module-alias": "^2.2.3", "moment": "^2.30.1", - "multiblob": "^1.13.0", - "multiserver": "^3.3.1", - "multiserver-address": "^1.0.1", - "muxrpc": "^8.0.0", - "muxrpc-validation": "^3.0.2", - "muxrpcli": "^3.1.2", - "node-llama-cpp": "^3.10.0", - "non-private-ip": "^2.2.0", "open": "^8.4.2", "openpgp": "^6.3.0", - "packet-stream": "^2.0.6", - "packet-stream-codec": "^1.2.0", - "pdfjs-dist": "^5.2.133", + "opusscript": "^0.1.1", "pretty-ms": "^7.0.1", "pull-abortable": "^4.1.1", "pull-cat": "~1.1.5", - "pull-file": "^1.0.0", - "pull-many": "~1.0.6", + "pull-defer": "^0.2.3", + "pull-level": "^2.0.4", "pull-paramap": "^1.2.2", "pull-pushable": "^2.2.0", "pull-sort": "^1.0.2", "pull-stream": "^3.7.0", - "punycode.js": "^2.3.1", "qrcode": "^1.5.4", "secret-stack": "^6.3.1", - "ssb-about": "^2.0.1", "ssb-autofollow": "^1.1.0", - "ssb-backlinks": "^2.1.1", "ssb-blobs": "^2.0.1", "ssb-box": "^1.0.1", "ssb-caps": "^1.0.1", + "ssb-classic": "^1.1.0", "ssb-client": "^4.9.0", "ssb-config": "^3.4.4", "ssb-conn": "6.0.3", - "ssb-conn-db": "^1.0.5", - "ssb-conn-hub": "^1.2.0", - "ssb-conn-staging": "^1.0.0", - "ssb-db": "^20.4.1", - "ssb-device-address": "^1.1.6", + "ssb-db2": "^6.3.3", "ssb-ebt": "^9.1.2", - "ssb-friend-pub": "^1.0.7", "ssb-friends": "^5.0.0", - "ssb-gossip": "^1.1.1", "ssb-invite": "^3.0.3", "ssb-invite-client": "^1.3.3", "ssb-keys": "^8.0.0", "ssb-lan": "^1.0.0", - "ssb-legacy-conn": "^1.0.17", - "ssb-links": "^3.0.10", "ssb-logging": "^1.0.0", - "ssb-markdown": "^3.6.0", "ssb-master": "^1.0.3", - "ssb-meme": "^1.1.0", "ssb-mentions": "^0.5.2", "ssb-msgs": "^5.2.0", "ssb-no-auth": "^1.0.0", "ssb-onion": "^1.0.0", - "ssb-partial-replication": "^3.0.1", - "ssb-plugins": "^1.0.2", - "ssb-private": "^1.1.0", - "ssb-query": "^2.4.5", "ssb-ref": "^2.16.0", "ssb-replication-scheduler": "^3.0.0", - "ssb-search": "^1.3.0", - "ssb-server": "file:packages/ssb-server", - "ssb-tangle": "^1.0.1", + "ssb-sort": "^1.1.3", "ssb-thread-schema": "^1.1.1", - "ssb-threads": "^10.0.4", "ssb-unix-socket": "^1.0.0", - "ssb-ws": "^6.2.3", - "util": "^0.12.5", + "ssb-validate": "^4.1.4", + "stream-to-pull-stream": "^1.7.3", + "telegram": "^2.26.22", "yargs": "^17.7.2" }, "overrides": { @@ -135,15 +102,14 @@ "ssb-ref": "^2.16.0", "secret-stack": "^6.3.1", "ip": "https://registry.npmjs.org/neoip/-/neoip-3.0.0.tgz", - "lodash.set": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + "lodash.set": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "onnxruntime-node": "1.19.2", + "onnxruntime-common": "1.19.2", + "onnxruntime-web": "1.19.2" }, "optionalDependencies": { - "fsevents": "^2.3.2", "sharp": "^0.33.5" }, - "devDependencies": { - "nodemon": "^3.1.0" - }, "bugs": { "url": "https://github.com/epsylon/oasis/issues" }, @@ -153,7 +119,7 @@ }, "keywords": [], "engines": { - "node": "^10.0.0 || >=12.0.0" + "node": ">=22.0.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/server/phone_audio_android.js b/src/server/phone_audio_android.js new file mode 100644 index 0000000..eb45a2e --- /dev/null +++ b/src/server/phone_audio_android.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const net = require('net'); +const path = require('path'); + +const MARKER = path.join(__dirname, '..', '..', '.audio_bridge'); +const MAX_BACKLOG = 4000; + +const bridgePort = () => { + try { + const port = Number(String(fs.readFileSync(MARKER, 'utf8')).trim()); + return Number.isFinite(port) && port > 0 ? port : 0; + } catch (_) { + return 0; + } +}; + +module.exports = () => { + const port = bridgePort(); + if (!port) return null; + const open = (mode) => { + const sock = net.connect(port, '127.0.0.1'); + sock.on('error', () => {}); + sock.setNoDelay(true); + sock.write(mode); + return sock; + }; + return { + capture(onPcm) { + const sock = open('R'); + sock.on('data', onPcm); + return { stop() { try { sock.destroy(); } catch (_) {} } }; + }, + player() { + const sock = open('P'); + return { + write(buf) { + if (sock.destroyed || sock.writableLength > MAX_BACKLOG) return false; + sock.write(buf); + return true; + }, + stop() { try { sock.end(); sock.destroy(); } catch (_) {} } + }; + } + }; +}; diff --git a/src/server/phone_module.js b/src/server/phone_module.js new file mode 100644 index 0000000..fdec57d --- /dev/null +++ b/src/server/phone_module.js @@ -0,0 +1,1855 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { spawn } = require('child_process'); +const nodeCrypto = require('crypto'); +const pull = require('./node_modules/pull-stream'); +const Pushable = require('./node_modules/pull-pushable'); +const sodium = require('./node_modules/chloride'); +const ssbKeys = require('./node_modules/ssb-keys'); + +const RING_MS = 20000; +const PAM_MIN_MS = 500; +const dbg = process.env.OASIS_DEBUG ? (...a) => console.log('[phone]', ...a) : () => {}; +const sid = (id) => String(id || '').slice(0, 9); +const PAM_WARN_MS = 10000; +const VOICEMAIL_MAX_MS = 60000; +const RATE = 8000; +const FRAME_BYTES = 320; +const FRAME_MS = 20; +const MAX_BACKLOG = 4000; +const CLOCK_SKEW_MS = 5 * 60 * 1000; +const RING_WINDOW_MS = 60 * 1000; +const RING_PER_WINDOW = 20; +const SEEN_MAX = 5000; +const HUB_ROOMS_MAX = 256; +const HUB_RIDS_PER_ROOM = 4; +const GROUP_MAX = 7; +const MIX_QUEUE = 10; +const ROOM_MAX = 50; +const ROOM_QUIET_AT = 10; +const ROOM_JOIN_MS = 10000; +const ROOM_PEEK_MS = 1500; +const ROOM_FRAME_MAX = 2048; +const ROOM_RATE_MAX = 120; +const HUB_LAG_MAX = 2000; +const ROOM_ACK_EVERY = 100; +const ROUTES_MAX = 4096; +const MUTE_BEACON_MS = 2000; +const MUTE_TTL_MS = 6000; +const ROOM_EVENTS_MAX = 50; +const REC_TOGGLE_MIN_MS = 250; +const CHIMES_AT_ONCE = 3; +const ROOM_REC_MAX_BYTES = 4 * 3600 * 8000 * 2; +const CHIMES = { + join: [[660, 90], [0, 20], [990, 140]], + leave: [[990, 90], [0, 20], [660, 190]], + recStart: [[880, 70], [0, 60], [880, 70], [0, 60], [1320, 160]], + recStop: [[1320, 70], [0, 60], [660, 200]], + mute: [[523, 80], [0, 30], [392, 160]], + unmute: [[392, 80], [0, 30], [523, 180]], + hand: [[784, 60], [0, 40], [988, 60], [0, 40], [1175, 110]] +}; +const chimeMs = (kind) => (CHIMES[kind] || []).reduce((n, [, ms]) => n + ms, 0); +const K_CTRL = 0; +const K_AUDIO = 1; +const K_DATA = 2; +const K_KEY = 3; +const K_VIDEO = 4; +const VIDEO_CHUNK = 8 * 1024; +const VIDEO_PAD = 1024; +const VIDEO_HDR = 6; +const VIDEO_FRAME_MAX = 256 * 1024; +const VIDEO_FPS = 8; +const VIDEO_SIZE = { w: 320, h: 240 }; +const VIDEO_HOLD_MS = 3000; +const ROOM_VIDEO_FRAME_MAX = 10 * 1024; +const ROOM_VIDEO_BYTES_PER_S = 200 * 1024; +const VAD_MIN = 200; +const VAD_FLOOR_MAX = 1500; +const VAD_HANG = 15; +const RID = /^[0-9a-f]{32}$/; + +const BIAS = 0x84; +const CLIP = 32635; +const ulawEncodeSample = (sample) => { + let s = sample; + const sign = (s >> 8) & 0x80; + if (sign) s = -s; + if (s > CLIP) s = CLIP; + s += BIAS; + let exponent = 7; + for (let mask = 0x4000; (s & mask) === 0 && exponent > 0; exponent--, mask >>= 1); + const mantissa = (s >> (exponent + 3)) & 0x0f; + return ~(sign | (exponent << 4) | mantissa) & 0xff; +}; +const ULAW_DECODE = new Int16Array(256); +for (let i = 0; i < 256; i++) { + const u = ~i & 0xff; + const t = (((u & 0x0f) << 3) + BIAS) << ((u & 0x70) >> 4); + ULAW_DECODE[i] = (u & 0x80) ? BIAS - t : t - BIAS; +} +const ulawEncode = (pcm) => { + const out = Buffer.alloc(pcm.length >> 1); + for (let i = 0; i < out.length; i++) out[i] = ulawEncodeSample(pcm.readInt16LE(i * 2)); + return out; +}; +const ulawDecode = (ulaw) => { + const out = Buffer.alloc(ulaw.length * 2); + for (let i = 0; i < ulaw.length; i++) out.writeInt16LE(ULAW_DECODE[ulaw[i]], i * 2); + return out; +}; + +const wavFile = (pcm) => { + const h = Buffer.alloc(44); + h.write('RIFF', 0); h.writeUInt32LE(36 + pcm.length, 4); h.write('WAVE', 8); + h.write('fmt ', 12); h.writeUInt32LE(16, 16); h.writeUInt16LE(1, 20); h.writeUInt16LE(1, 22); + h.writeUInt32LE(RATE, 24); h.writeUInt32LE(RATE * 2, 28); h.writeUInt16LE(2, 32); h.writeUInt16LE(16, 34); + h.write('data', 36); h.writeUInt32LE(pcm.length, 40); + return Buffer.concat([h, pcm]); +}; + +const connectingCycle = () => { + const n = RATE * 2; + const buf = Buffer.alloc(n * 2); + let seed = 0x2545f491; + let lp = 0; + for (let i = 0; i < n; i++) { + seed = (seed * 1103515245 + 12345) >>> 0; + lp += (((seed >>> 8) / 0x800000) - 1 - lp) * 0.08; + const env = Math.sin(Math.PI * i / n); + buf.writeInt16LE(Math.round(lp * 9000 * env * env), i * 2); + } + return buf; +}; +const toneCycle = (kind) => { + if (kind === 'connecting') return connectingCycle(); + const segs = kind === 'ring' + ? [{ ms: 1000, freq: (k) => (Math.floor(k / (RATE / 20)) % 2 ? 1250 : 1000) }, { ms: 2000 }] + : [{ ms: 1500, freq: () => 425 }, { ms: 3000 }]; + const total = segs.reduce((a, s) => a + s.ms, 0) * RATE / 1000; + const buf = Buffer.alloc(total * 2); + let i = 0; + let phase = 0; + for (const s of segs) { + const n = s.ms * RATE / 1000; + for (let k = 0; k < n; k++, i++) { + let v = 0; + if (s.freq) { phase += 2 * Math.PI * s.freq(k) / RATE; v = Math.round(Math.sin(phase) * 6000); } + buf.writeInt16LE(v, i * 2); + } + } + return buf; +}; + +const onPath = (bin) => (process.env.PATH || '').split(path.delimiter).some(d => d && fs.existsSync(path.join(d, bin))); +const BACKENDS = [ + { rec: ['parec', ['--raw', `--rate=${RATE}`, '--channels=1', '--format=s16le', '--latency-msec=20']], play: ['pacat', ['--playback', '--raw', `--rate=${RATE}`, '--channels=1', '--format=s16le', '--latency-msec=60']] }, + { rec: ['pw-record', ['--rate', String(RATE), '--channels', '1', '--format', 's16', '-']], play: ['pw-play', ['--rate', String(RATE), '--channels', '1', '--format', 's16', '-']] }, + { rec: ['arecord', ['-q', '-t', 'raw', '-f', 'S16_LE', '-r', String(RATE), '-c', '1']], play: ['aplay', ['-q', '-t', 'raw', '-f', 'S16_LE', '-r', String(RATE), '-c', '1']] } +]; +const systemAudio = () => { + const backend = BACKENDS.find(b => onPath(b.rec[0]) && onPath(b.play[0])); + if (!backend) return null; + return { + capture(onPcm) { + const p = spawn(backend.rec[0], backend.rec[1], { stdio: ['ignore', 'pipe', 'ignore'] }); + p.on('error', () => {}); + p.stdout.on('data', onPcm); + return { stop() { try { p.kill('SIGTERM'); } catch (_) {} } }; + }, + player() { + const p = spawn(backend.play[0], backend.play[1], { stdio: ['pipe', 'ignore', 'ignore'] }); + p.on('error', () => {}); + p.stdin.on('error', () => {}); + return { + write(buf) { + if (!p.stdin.writable || p.stdin.writableLength > MAX_BACKLOG) return false; + p.stdin.write(buf); + return true; + }, + end() { try { p.stdin.end(); } catch (_) {} }, + stop() { try { p.stdin.end(); p.kill('SIGTERM'); } catch (_) {} } + }; + } + }; +}; +let audioFactory = systemAudio; + +const JPEG_SOI = Buffer.from([0xff, 0xd8, 0xff]); +const JPEG_EOI = Buffer.from([0xff, 0xd9]); +const jpegSplitter = (onJpeg) => { + let pending = Buffer.alloc(0); + return (chunk) => { + pending = pending.length ? Buffer.concat([pending, chunk]) : Buffer.from(chunk); + for (;;) { + const start = pending.indexOf(JPEG_SOI); + if (start < 0) { pending = Buffer.alloc(0); return; } + const end = pending.indexOf(JPEG_EOI, start + 3); + if (end < 0) { pending = pending.length - start > VIDEO_FRAME_MAX ? Buffer.alloc(0) : pending.subarray(start); return; } + onJpeg(Buffer.from(pending.subarray(start, end + 2))); + pending = pending.subarray(end + 2); + } + }; +}; +const VIDEO_BACKENDS = [ + { bin: 'v4l2-ctl', args: (dev) => ['--device', dev, `--set-fmt-video=width=${VIDEO_SIZE.w},height=${VIDEO_SIZE.h},pixelformat=MJPG`, `--set-parm=${VIDEO_FPS}`, '--stream-mmap', '--stream-to=-'] }, + { bin: 'gst-launch-1.0', args: (dev) => ['-q', 'v4l2src', `device=${dev}`, '!', `video/x-raw,width=${VIDEO_SIZE.w},height=${VIDEO_SIZE.h}`, '!', 'videorate', '!', `video/x-raw,framerate=${VIDEO_FPS}/1`, '!', 'videoconvert', '!', 'jpegenc', 'quality=60', '!', 'fdsink', 'fd=1'] }, + { bin: 'ffmpeg', args: (dev) => ['-loglevel', 'quiet', '-f', 'v4l2', '-video_size', `${VIDEO_SIZE.w}x${VIDEO_SIZE.h}`, '-i', dev, '-vf', `fps=${VIDEO_FPS}`, '-c:v', 'mjpeg', '-q:v', '8', '-f', 'image2pipe', '-'] } +]; +const videoDevices = () => { + try { return fs.readdirSync('/dev').filter(n => /^video\d+$/.test(n)).sort((a, b) => Number(a.slice(5)) - Number(b.slice(5))).map(n => `/dev/${n}`); } catch (_) { return []; } +}; +const systemVideo = (server, device) => { + const backend = VIDEO_BACKENDS.find(b => onPath(b.bin)); + const dev = device || videoDevices()[0]; + if (!backend || !dev) return null; + return { + capture(onJpeg) { + const p = spawn(backend.bin, backend.args(dev), { stdio: ['ignore', 'pipe', 'ignore'] }); + p.on('error', () => {}); + p.stdout.on('data', jpegSplitter(onJpeg)); + return { stop() { try { p.kill('SIGTERM'); } catch (_) {} } }; + } + }; +}; +let videoFactory = systemVideo; + +const videoChunks = (seq, jpeg) => { + const n = Math.ceil(jpeg.length / VIDEO_CHUNK); + const out = []; + for (let i = 0; i < n; i++) { + const data = jpeg.subarray(i * VIDEO_CHUNK, (i + 1) * VIDEO_CHUNK); + const plain = Buffer.alloc(Math.ceil((VIDEO_HDR + data.length) / VIDEO_PAD) * VIDEO_PAD); + plain.writeUInt16BE(seq & 0xffff, 0); + plain[2] = i; + plain[3] = n; + plain.writeUInt16BE(data.length, 4); + data.copy(plain, VIDEO_HDR); + nodeCrypto.randomFillSync(plain, VIDEO_HDR + data.length); + out.push(plain); + } + return out; +}; +const videoOff = () => { const plain = Buffer.alloc(VIDEO_PAD); nodeCrypto.randomFillSync(plain, VIDEO_HDR); return plain; }; +const videoAssembler = () => { + let cur = null; + return (plain) => { + if (!Buffer.isBuffer(plain) || plain.length < VIDEO_HDR) return null; + const seq = plain.readUInt16BE(0), idx = plain[2], n = plain[3], len = plain.readUInt16BE(4); + if (!n) { cur = null; return 'off'; } + if (idx >= n || VIDEO_HDR + len > plain.length || n * VIDEO_CHUNK > VIDEO_FRAME_MAX) return null; + if (!cur || cur.seq !== seq) cur = { seq, n, parts: new Array(n), got: 0 }; + if (cur.parts[idx]) return null; + cur.parts[idx] = Buffer.from(plain.subarray(VIDEO_HDR, VIDEO_HDR + len)); + cur.got++; + if (cur.got < n) return null; + const jpeg = Buffer.concat(cur.parts); + cur = null; + return jpeg; + }; +}; + +const BEEP_MS = 700; +const playBeep = (audio, count = 1) => { + const on = (count > 1 ? 150 : BEEP_MS) * RATE / 1000; + const gap = 150 * RATE / 1000; + const pcm = Buffer.alloc((on * count + gap * (count - 1)) * 2); + let phase = 0; + for (let b = 0; b < count; b++) { + const start = b * (on + gap); + for (let k = 0; k < on; k++) { + phase += 2 * Math.PI * 1000 / RATE; + pcm.writeInt16LE(Math.round(Math.sin(phase) * 9000), (start + k) * 2); + } + } + const player = audio.player(); + player.write(pcm); + if (typeof player.end === 'function') player.end(); + else setTimeout(() => player.stop(), pcm.length / (RATE * 2) * 1000 + 300); +}; + +const chimeBusy = new WeakMap(); +const playChime = (audio, kind) => { + const notes = CHIMES[kind]; + if (!audio || !notes) return; + const now = Date.now(); + const playing = (chimeBusy.get(audio) || []).filter(t => t > now); + if (playing.length >= CHIMES_AT_ONCE) return; + playing.push(now + chimeMs(kind)); + chimeBusy.set(audio, playing); + const pcm = Buffer.alloc(Math.round(chimeMs(kind) * RATE / 1000) * 2); + let at = 0, phase = 0; + for (const [freq, ms] of notes) { + const n = Math.round(ms * RATE / 1000); + for (let k = 0; k < n && (at + k) * 2 + 1 < pcm.length; k++) { + if (freq > 0) { + phase += 2 * Math.PI * freq / RATE; + const env = Math.min(1, k / 40, (n - k) / 40); + pcm.writeInt16LE(Math.round(Math.sin(phase) * 7000 * env), (at + k) * 2); + } + } + at += n; + } + const player = audio.player(); + player.write(pcm); + if (typeof player.end === 'function') player.end(); + else setTimeout(() => player.stop(), pcm.length / (RATE * 2) * 1000 + 300); +}; +const recordingPrefix = (ref) => nodeCrypto.createHash('sha256').update(String(ref || '')).digest('hex').slice(0, 16); + +const startTone = (audio, kind) => { + const cycle = toneCycle(kind); + const player = audio.player(); + const chunk = RATE * 2 / 10; + let pos = 0; + const tick = () => { + const end = Math.min(pos + chunk, cycle.length); + player.write(cycle.subarray(pos, end)); + pos = end >= cycle.length ? 0 : end; + }; + tick(); + const t = setInterval(tick, 100); + return { stop() { clearInterval(t); player.stop(); } }; +}; + +const mixInto = (acc, pcm) => { for (let i = 0; i < acc.length && i * 2 + 1 < pcm.length; i++) acc[i] += pcm.readInt16LE(i * 2); }; +const clampPcm = (acc) => { + const out = Buffer.alloc(acc.length * 2); + for (let i = 0; i < acc.length; i++) out.writeInt16LE(Math.max(-32768, Math.min(32767, acc[i])), i * 2); + return out; +}; + +let OpusScript = null; +try { OpusScript = require('opusscript'); } catch (_) {} +let sharpLib = null; +try { sharpLib = require('./node_modules/sharp'); } catch (_) {} +const OPUS_TAG = 0xf0; +const OPUS_BITRATE = 16000; +const makeEncoder = (codec) => { + if (codec !== 'opus' || !OpusScript) return { encode: ulawEncode, free() {} }; + let enc = null; + return { + encode(pcm) { + try { + if (!enc) { enc = new OpusScript(RATE, 1, OpusScript.Application.VOIP); try { enc.encoderCTL(4002, OPUS_BITRATE); } catch (_) {} } + return Buffer.concat([Buffer.from([OPUS_TAG]), enc.encode(pcm, pcm.length >> 1)]); + } catch (_) { return ulawEncode(pcm); } + }, + free() { if (enc) { try { enc.delete(); } catch (_) {} enc = null; } } + }; +}; +const makeDecoder = () => { + let dec = null; + return { + decode(buf) { + if (buf.length === FRAME_BYTES / 2 || buf[0] !== OPUS_TAG || !OpusScript) return ulawDecode(buf); + try { + if (!dec) dec = new OpusScript(RATE, 1, OpusScript.Application.VOIP); + return Buffer.from(dec.decode(buf.subarray(1))); + } catch (_) { return Buffer.alloc(FRAME_BYTES); } + }, + free() { if (dec) { try { dec.delete(); } catch (_) {} dec = null; } } + }; +}; +const ctrlFrame = (obj) => Buffer.concat([Buffer.from([K_CTRL]), Buffer.from(JSON.stringify(obj))]); +const levelOf = (pcm) => { + const n = pcm.length >> 1; + let sum = 0; + for (let i = 0; i < n; i++) sum += Math.abs(pcm.readInt16LE(i * 2)); + return n ? sum / n : 0; +}; +const voiceGate = () => { + let floor = 0; + let hang = 0; + return (pcm) => { + const lv = levelOf(pcm); + floor = lv < floor ? lv : floor + (lv - floor) * 0.002; + if (lv > Math.max(VAD_MIN, Math.min(floor, VAD_FLOOR_MAX) * 3)) hang = VAD_HANG; + else if (hang > 0) hang--; + return hang > 0; + }; +}; + +const b64 = (buf) => Buffer.from(buf).toString('base64'); +const isFeedId = (id) => typeof id === 'string' && /^@[A-Za-z0-9+/]{43}=\.ed25519$/.test(id); +const pkOf = (id) => Buffer.from(String(id).slice(1, -8), 'base64'); +const signParts = (keys, parts) => b64(sodium.crypto_sign_detached(Buffer.from(parts.join('|')), Buffer.from(String(keys.private).replace(/\.ed25519$/, ''), 'base64'))); +const verifyParts = (id, parts, sig) => { + try { return !!sodium.crypto_sign_verify_detached(Buffer.from(String(sig), 'base64'), Buffer.from(parts.join('|')), pkOf(id)); } catch (_) { return false; } +}; +const ephKey = (v) => { + try { const b = Buffer.from(String(v), 'base64'); return b.length === 32 ? b : null; } catch (_) { return null; } +}; +const deriveKeys = (mySecret, theirPublic, callId, callerPk, calleePk) => { + const shared = sodium.crypto_scalarmult(mySecret, theirPublic); + const label = (l) => sodium.crypto_hash_sha256(Buffer.concat([Buffer.from(shared), Buffer.from(l), Buffer.from(callId, 'hex'), callerPk, calleePk])); + return { a2b: label('oasis-voices-a2b'), b2a: label('oasis-voices-b2a'), va2b: label('oasis-video-a2b'), vb2a: label('oasis-video-b2a') }; +}; +const sealer = (key) => { + const prefix = nodeCrypto.randomBytes(16); + let counter = 0n; + return (plain) => { + const nonce = Buffer.alloc(24); + prefix.copy(nonce, 0); + nonce.writeBigUInt64BE(++counter, 16); + return Buffer.concat([nonce, Buffer.from(sodium.crypto_secretbox_easy(plain, nonce, key))]); + }; +}; +const opener = (key) => { + let prefix = null; + let last = 0n; + return (frame) => { + if (!Buffer.isBuffer(frame) || frame.length < 40) return null; + const nonce = frame.subarray(0, 24); + const p = nonce.subarray(0, 16).toString('hex'); + const counter = nonce.readBigUInt64BE(16); + if ((prefix && p !== prefix) || counter <= last) return null; + const plain = sodium.crypto_secretbox_open_easy(frame.subarray(24), nonce, key); + if (!plain) return null; + prefix = p; + last = counter; + return Buffer.from(plain); + }; +}; + +module.exports = { + name: 'phone', + version: '1.0.0', + manifest: { + ring: 'async', answer: 'async', hangup: 'async', audio: 'duplex', relay: 'async', relayAudio: 'duplex', video: 'duplex', relayVideo: 'duplex', + call: 'async', accept: 'async', reject: 'async', end: 'async', mute: 'async', silence: 'async', dismiss: 'async', camera: 'async', videoFrames: 'source', + recordStop: 'async', recordCancel: 'async', + roomHub: 'duplex', roomInfo: 'async', roomAdmits: 'async', roomJoin: 'async', roomLeave: 'async', roomMute: 'async', roomHand: 'async', roomCount: 'async', + roomRecStart: 'async', roomRecStop: 'async', roomNotify: 'async', roomClearEvents: 'async', + roomState: 'sync', roomToken: 'sync', roomHubFor: 'async', + state: 'sync', available: 'sync', pubs: 'sync', events: 'source' + }, + permissions: { anonymous: { allow: ['ring', 'answer', 'hangup', 'audio', 'relay', 'relayAudio', 'video', 'relayVideo', 'roomHub', 'roomInfo', 'roomAdmits'] } }, + init(server, config) { + const keys = config.keys; + const audio = audioFactory(server); + const recBase = String((config && config.path) || os.tmpdir()); + const recDir = path.join(recBase, 'oasis', 'rooms'); + try { if (!fs.existsSync(recDir) && fs.existsSync(path.join(recBase, 'rooms-recordings'))) { fs.mkdirSync(path.dirname(recDir), { recursive: true }); fs.renameSync(path.join(recBase, 'rooms-recordings'), recDir); } } catch (_) {} + const subscribers = new Set(); + const seenCalls = new Map(); + const routes = new Map(); + const hubRooms = new Map(); + let call = null; + let room = null; + const silenced = new Set(); + + const policy = () => { + if (config.phone && typeof config.phone === 'object') return config.phone; + try { + const cfg = require('../configs/config-manager.js').getConfig(); + return { ...(cfg.phone || {}), off: !!(cfg.modules && cfg.modules.phoneMod === 'off') }; + } catch (_) { return {}; } + }; + const ringMs = Number(policy().ringMs) > 0 ? Number(policy().ringMs) : RING_MS; + const voicemailMaxMs = Number(policy().voicemailMaxMs) > 0 ? Number(policy().voicemailMaxMs) : VOICEMAIL_MAX_MS; + const pamWarnMs = Number(policy().pamWarnMs) > 0 ? Number(policy().pamWarnMs) : PAM_WARN_MS; + const roomMax = Number(policy().roomMax) > 0 ? Math.min(ROOM_MAX, Number(policy().roomMax)) : ROOM_MAX; + const roomQuietAt = Number(policy().roomQuietAt) > 0 ? Number(policy().roomQuietAt) : ROOM_QUIET_AT; + const video = videoFactory(server, policy().videoDevice); + const videoAllowed = () => policy().video !== false; + const videoOn = () => !!video && videoAllowed(); + const codecsOffered = () => [...(OpusScript && policy().opus !== false ? ['opus'] : []), ...(videoOn() ? ['mjpeg'] : [])]; + const pickCodec = (offer) => (codecsOffered().includes('opus') && Array.isArray(offer) && offer.includes('opus') ? 'opus' : 'ulaw'); + const pickVideo = (offer, mine = false) => (videoAllowed() && ((Array.isArray(offer) && offer.includes('mjpeg')) || mine) ? 'mjpeg' : null); + const frameWatchers = new Map(); + const publishFrame = (target, jpeg) => { const set = frameWatchers.get(target); if (set) for (const p of set) p.push(jpeg); }; + const endFrames = (target) => { + const set = frameWatchers.get(target); + if (!set) return; + frameWatchers.delete(target); + for (const p of set) { try { p.end(); } catch (_) {} } + }; + let cam = null; + let camSeq = 0; + const startCam = (onJpeg) => { + if (cam || !videoOn()) return false; + const cap = video.capture((jpeg) => { + if (!cam || jpeg.length > VIDEO_FRAME_MAX) return; + camSeq = (camSeq + 1) & 0xffff; + publishFrame('me', jpeg); + onJpeg(jpeg, camSeq); + }); + cam = { stop() { cap.stop(); } }; + return true; + }; + const stopCam = () => { if (!cam) return; const c = cam; cam = null; c.stop(); endFrames('me'); }; + const relation = (method, source, dest, done) => { + const friends = server.friends; + if (!friends || typeof friends[method] !== 'function') return done(false); + friends[method]({ source, dest }, (err, yes) => done(!err && !!yes)); + }; + const allowedCaller = (from, done) => { + const p = policy(); + if (p.off || p.dnd === true) return done(false); + relation('isBlocking', server.id, from, (blocked) => { + if (blocked) return done(false); + if (p.visibility !== 'mutuals') return done(true); + relation('isFollowing', server.id, from, (a) => { + if (!a) return done(false); + relation('isFollowing', from, server.id, (b) => done(!!b)); + }); + }); + }; + const relayAllowed = (a, b, done) => { + const p = policy(); + if (p.relay === false) return done(false); + if (p.relayOpen === true) return done(true); + relation('isFollowing', server.id, a, (x) => x ? done(true) : relation('isFollowing', server.id, b, (y) => done(!!y))); + }; + const seal = (payload, to) => { try { return ssbKeys.box(payload, [to]); } catch (_) { return null; } }; + const unseal = (sealed) => { try { return ssbKeys.unbox(String(sealed), keys) || null; } catch (_) { return null; } }; + const opened = (req) => (req && typeof req.sealed === 'string' ? unseal(req.sealed) : req); + const pubKeys = () => { + const set = new Set(); + const add = (list) => { for (const e of list || []) if (Array.isArray(e) && e[1] && e[1].type === 'pub' && e[1].key) set.add(e[1].key); }; + try { add(server.conn.dbPeers()); } catch (_) {} + try { add(server.conn.query().peersAll()); } catch (_) {} + return set; + }; + const knownPubs = () => { + const connected = Object.keys(server.peers || {}).filter(id => id !== server.id && liveRpc(id)); + if (!server.conn || typeof server.conn.query !== 'function') return connected; + const set = pubKeys(); + return connected.filter(id => set.has(id)); + }; + let routesPrunedAt = 0; + const learn = (cid, target, from, forwarded) => { + const now = Date.now(); + if (now - routesPrunedAt > 1000) { + routesPrunedAt = now; + for (const [k, v] of routes) if (now - v.ts > CLOCK_SKEW_MS * 2) routes.delete(k); + } + if (!routes.has(cid) && routes.size >= ROUTES_MAX) return { id: target, via: liveRpc(target) ? target : null }; + const r = routes.get(cid) || { sides: [], ts: now }; + let t = r.sides.find(x => x.id === target) || r.sides.find(x => x.id === null); + if (!t) { t = { id: target, via: null }; r.sides.push(t); } + t.id = target; + if (liveRpc(target)) t.via = target; + let o = r.sides.find(x => x !== t); + if (!o) { o = { id: forwarded ? null : from, via: from }; r.sides.push(o); } + else if (!o.via) o.via = from; + r.ts = now; + routes.set(cid, r); + return t; + }; + let seenPrunedAt = 0; + const directRings = new Map(); + const ringBudget = new Map(); + const trimOldest = (map, max) => { while (map.size > max) map.delete(map.keys().next().value); }; + const firstSeen = (callId) => { + const now = Date.now(); + if (now - seenPrunedAt > 1000) { + seenPrunedAt = now; + for (const [k, t] of seenCalls) if (now - t > CLOCK_SKEW_MS * 2) seenCalls.delete(k); + for (const [k, t] of directRings) if (now - t > CLOCK_SKEW_MS * 2) directRings.delete(k); + for (const [k, list] of ringBudget) { const live = list.filter(t => now - t < RING_WINDOW_MS); if (live.length) ringBudget.set(k, live); else ringBudget.delete(k); } + } + if (seenCalls.has(callId)) return false; + seenCalls.set(callId, now); + trimOldest(seenCalls, SEEN_MAX); + trimOldest(directRings, SEEN_MAX); + return true; + }; + const callerAllowed = (from) => { + const now = Date.now(); + const list = (ringBudget.get(from) || []).filter(t => now - t < RING_WINDOW_MS); + if (list.length >= RING_PER_WINDOW) { ringBudget.set(from, list); return false; } + list.push(now); + ringBudget.set(from, list); + trimOldest(ringBudget, SEEN_MAX); + return true; + }; + + const MOBILE_STATE = path.join(__dirname, '..', '..', '.phone_state.json'); + const writeMobileState = (s) => { + if (process.env.OASIS_MOBILE !== '1') return; + try { fs.writeFileSync(MOBILE_STATE, JSON.stringify(s || null)); } catch (_) {} + }; + writeMobileState(null); + const emit = (ev) => { for (const p of subscribers) p.push(ev); }; + const snapshot = () => call ? { + id: call.id, peer: call.peer, dir: call.dir, phase: call.phase, + startedAt: call.startedAt, answeredAt: call.answeredAt || 0, ringUntil: call.ringUntil || 0, + muted: !!call.muted, silenced: silenced.has(call.peer), recordingStartedAt: call.recordingStartedAt || 0, + reached: call.group ? call.legs.some(l => l.reached) : !!call.reached, why: call.why || '', codec: call.codec || '', + video: !!call.video, cam: !!call.cam, peerCam: !!call.peerCam, videoAvailable: videoOn(), + ...(call.group ? { group: true, peers: call.legs.map(l => ({ id: l.peer, phase: l.phase, reached: !!l.reached, silenced: silenced.has(l.peer) })) } : {}) + } : null; + const changed = () => { const s = snapshot(); writeMobileState(s); emit({ type: 'state', state: s }); }; + const stopTone = (c) => { if (c.tone) { c.tone.stop(); c.tone = null; } }; + const stopRecorder = (c) => { clearTimeout(c.recDelay); clearTimeout(c.recWarn); if (c.recorder) { c.recorder.stop(); c.recorder = null; } clearTimeout(c.recTimer); }; + const startRecording = (c) => { + const max = voicemailMaxMs * RATE * 2 / 1000; + c.rec = { chunks: [], bytes: 0 }; + c.phase = 'recording'; + c.recordingStartedAt = Date.now() + BEEP_MS + 400; + playBeep(audio); + c.recDelay = setTimeout(() => { + if (call !== c || c.phase !== 'recording') return; + c.recordingStartedAt = Date.now(); + c.recorder = audio.capture((pcm) => { + if (!c.rec || c.rec.bytes >= max) return; + const take = Buffer.from(pcm.subarray(0, Math.max(0, max - c.rec.bytes))); + c.rec.chunks.push(take); + c.rec.bytes += take.length; + }); + if (voicemailMaxMs > pamWarnMs) c.recWarn = setTimeout(() => { if (call === c && c.phase === 'recording') playBeep(audio, 2); }, voicemailMaxMs - pamWarnMs); + c.recTimer = setTimeout(() => { + if (call !== c || c.phase !== 'recording') return; + stopRecorder(c); + c.phase = 'recorded'; + changed(); + emit({ type: 'recordLimit', id: c.id }); + }, voicemailMaxMs); + changed(); + }, BEEP_MS + 400); + }; + const finish = (outcome) => { + const c = call; + if (!c) return; + call = null; + clearTimeout(c.timer); + stopTone(c); + stopRecorder(c); + if (c.media) c.media.stop(); + stopCallVideo(c); + if (c.group) { + for (const leg of c.legs) { clearTimeout(leg.timer); if (leg.out) { try { leg.out.end(); } catch (_) {} } } + if (c.mixer) c.mixer.stop(); + for (const leg of c.legs) { if (leg.enc) leg.enc.free(); if (leg.dec) leg.dec.free(); } + } + emit({ type: 'ended', call: { id: c.id, peer: c.peer, ...(c.group ? { peers: c.legs.map(l => l.peer) } : {}), dir: c.dir, startedAt: c.startedAt, answeredAt: c.answeredAt || 0, endedAt: Date.now(), outcome } }); + changed(); + }; + const liveRpc = (id) => { + const list = server.peers && server.peers[id]; + return Array.isArray(list) ? list.find(r => r && !r.closed && r.phone) || null : null; + }; + const sendVia = (via, to, method, payload, cb = () => {}) => { + try { + if (via) { + const r = liveRpc(via); + const sealed = seal(payload, to); + if (!r || !sealed) return cb(new Error('no-connection')); + return r.phone.relay({ to, method, cid: payload.callId, sealed }, cb); + } + const r = liveRpc(to); + if (!r) return cb(new Error('no-connection')); + return r.phone[method](payload, cb); + } catch (err) { cb(err); } + }; + const notifyHangup = (c) => { + const ts = Date.now(); + const payload = { callId: c.id, from: server.id, ts, sig: signParts(keys, ['hangup', c.id, c.peer, ts]) }; + if (c.phase === 'calling' && c.dir === 'out') { + for (const via of c.paths || []) sendVia(via, c.peer, 'hangup', payload, (err) => dbg('hangup sent', sid(c.peer), via ? 'via ' + sid(via) : 'direct', err ? 'error ' + err.message : 'ok')); + } else { + sendVia(c.via || null, c.peer, 'hangup', payload, (err) => dbg('hangup sent', sid(c.peer), c.via ? 'via ' + sid(c.via) : 'direct', err ? 'error ' + err.message : 'ok')); + } + }; + const senderOf = (rpcCtx, payload) => { + const direct = rpcCtx && rpcCtx.id; + const claimed = payload && isFeedId(payload.from) ? payload.from : direct; + return { sender: claimed, via: claimed && direct && claimed !== direct ? direct : null }; + }; + const netFirst = (entries) => entries.slice().sort((x, y) => (/^onion:/.test(String(x && x[0])) ? 1 : 0) - (/^onion:/.test(String(y && y[0])) ? 1 : 0)); + const reach = (id, done) => { + const now = liveRpc(id); + if (now) return done(now); + let entries = []; + try { entries = netFirst(server.conn.query().peersAll()); } catch (_) {} + const hit = entries.find(e => Array.isArray(e) && e[0] && e[1] && e[1].key === id); + if (!hit) return done(null); + try { + server.conn.connect(hit[0], (err, rpc) => done(!err && rpc && rpc.phone ? rpc : liveRpc(id))); + } catch (_) { done(null); } + }; + const relayCandidates = (target) => knownPubs().filter(id => id !== target); + + const calleePubs = (target, done) => { + if (!server.friends || typeof server.friends.hops !== 'function' || !server.conn || typeof server.conn.query !== 'function') return done([]); + try { + server.friends.hops({ start: target, max: 1 }, (err, hops) => { + if (err || !hops) return done([]); + let entries = []; + try { entries = netFirst(server.conn.dbPeers().concat(server.conn.query().peersAll())); } catch (_) {} + const seen = new Set(); + done(entries + .filter(e => Array.isArray(e) && e[0] && e[1] && e[1].type === 'pub' && hops[e[1].key] === 1 && !liveRpc(e[1].key) && !seen.has(e[1].key) && seen.add(e[1].key)) + .slice(0, 5) + .map(e => ({ address: e[0], key: e[1].key }))); + }); + } catch (_) { done([]); } + }; + + const ringPaths = (holder, target, req, alive, onReached, legacy = false) => { + dbg('ring', sid(target), 'legacy=' + legacy, 'live-pubs=' + knownPubs().map(sid).join(',')); + reach(target, (rpc) => { + dbg('direct', sid(target), rpc ? 'reachable' : 'unreachable'); + if (!alive() || !rpc) return; + holder.paths.push(null); + if (legacy) onReached(); + sendVia(null, target, 'ring', req, (err) => { dbg('direct ring', sid(target), err ? 'error ' + err.message : 'delivered'); if (!err && alive()) onReached(); }); + }); + for (const via of relayCandidates(target)) { + holder.paths.push(via); + sendVia(via, target, 'ring', req, (err) => dbg('relay via', sid(via), err ? 'error ' + err.message : 'accepted')); + } + calleePubs(target, (pubs) => { + dbg('callee pubs', pubs.map(p => sid(p.key) + '@' + p.address).join(' ') || 'none'); + for (const entry of pubs) { + try { + server.conn.connect(entry.address, (err) => { + dbg('connect callee pub', sid(entry.key), err ? 'failed ' + err.message : 'ok'); + if (err || !alive()) return; + holder.paths.push(entry.key); + sendVia(entry.key, target, 'ring', req, (e2) => dbg('relay via callee pub', sid(entry.key), e2 ? 'error ' + e2.message : 'accepted')); + }); + } catch (_) {} + } + }); + }; + const ringRequest = (callId, eph, target) => { + const ts = Date.now(); + const ephPk = b64(eph.publicKey); + return { callId, from: server.id, to: target, ephPk, ts, sig: signParts(keys, ['ring', callId, ephPk, target, ts]), codecs: codecsOffered() }; + }; + + const legOf = (c, callId) => (c && c.group ? c.legs.find(l => l.id === callId) || null : null); + const toVoicemail = (c, why) => { + if (call !== c || c.phase !== 'calling') return; + dbg('voicemail', sid(c.peer), why, 'reached=' + !!c.reached); + clearTimeout(c.timer); + stopTone(c); + if (why !== 'busy') notifyHangup(c); + c.why = why; + startRecording(c); + changed(); + }; + const reachedCall = (c) => { + if (call !== c || c.phase !== 'calling' || c.reached) return; + dbg('reached', sid(c.peer), 'now ringing there'); + c.reached = true; + c.ringUntil = Date.now() + ringMs; + stopTone(c); + c.tone = startTone(audio, 'ringback'); + clearTimeout(c.timer); + c.timer = setTimeout(() => toVoicemail(c, 'noanswer'), ringMs); + changed(); + }; + const reachedLeg = (c, leg) => { + if (call !== c || leg.phase !== 'calling' || leg.reached) return; + leg.reached = true; + clearTimeout(leg.timer); + leg.timer = setTimeout(() => { if (call === c && leg.phase === 'calling') dropLeg(c, leg, true); }, ringMs); + if (c.phase === 'calling' && !c.ringing) { stopTone(c); c.ringing = true; c.tone = startTone(audio, 'ringback'); c.ringUntil = Date.now() + ringMs; } + changed(); + }; + const ringingAck = (c, req, from, cb) => { + const target = c && c.group ? legOf(c, req.callId) : c; + if (!target || c.dir !== 'out' || target.phase !== 'calling' || from !== target.peer || req.callId !== target.id) return cb(new Error('no-call')); + if (Math.abs(Date.now() - Number(req.ts)) > CLOCK_SKEW_MS || !verifyParts(from, ['ringing', req.callId, req.ringing, server.id, req.ts], req.sig)) return cb(new Error('rejected')); + cb(null, true); + if (req.ringing === 'busy') { + if (c.group) { target.reached = true; return dropLeg(c, target, false); } + c.reached = true; + return toVoicemail(c, 'busy'); + } + if (c.group) reachedLeg(c, target); + else reachedCall(c); + }; + const hangupLeg = (leg) => { + const ts = Date.now(); + const payload = { callId: leg.id, from: server.id, ts, sig: signParts(keys, ['hangup', leg.id, leg.peer, ts]) }; + if (leg.phase === 'calling') for (const via of leg.paths) sendVia(via, leg.peer, 'hangup', payload); + else sendVia(leg.via || null, leg.peer, 'hangup', payload); + }; + const settleGroup = (c) => { + if (call !== c) return; + if (c.legs.some(l => l.phase === 'connected' || l.phase === 'calling')) return changed(); + finish(c.answeredAt ? 'ended' : 'noanswer'); + }; + const dropLeg = (c, leg, notify) => { + if (leg.phase === 'gone') return; + if (notify) hangupLeg(leg); + clearTimeout(leg.timer); + leg.phase = 'gone'; + if (leg.out) { try { leg.out.end(); } catch (_) {} leg.out = null; } + if (leg.enc) leg.enc.free(); + if (leg.dec) leg.dec.free(); + leg.queue = []; + settleGroup(c); + }; + const startMixer = (c) => { + if (c.mixer) return; + const player = audio.player(); + const samples = FRAME_BYTES / 2; + let pending = Buffer.alloc(0); + const capture = audio.capture((pcm) => { + if (call !== c) return; + pending = pending.length ? Buffer.concat([pending, pcm]) : Buffer.from(pcm); + while (pending.length >= FRAME_BYTES) { + const mic = pending.subarray(0, FRAME_BYTES); + pending = pending.subarray(FRAME_BYTES); + const legs = c.legs.filter(l => l.phase === 'connected' && l.out); + const heard = legs.map(l => l.queue.shift() || null); + const all = new Int32Array(samples); + heard.forEach((f, i) => { if (f && !silenced.has(legs[i].peer)) mixInto(all, f); }); + player.write(clampPcm(all)); + legs.forEach((leg, i) => { + const acc = new Int32Array(samples); + if (!c.muted) mixInto(acc, mic); + heard.forEach((f, j) => { if (f && j !== i) mixInto(acc, f); }); + leg.out.push(leg.seal(leg.enc.encode(clampPcm(acc)))); + }); + } + }); + c.mixer = { stop() { capture.stop(); player.stop(); } }; + }; + const startLegMedia = (c, leg, duplex) => { + const open = opener(leg.keys.b2a); + leg.seal = sealer(leg.keys.a2b); + leg.enc = makeEncoder(leg.codec); + leg.dec = makeDecoder(); + leg.out = Pushable(() => {}); + leg.queue = []; + pull(leg.out, duplex.sink); + pull(duplex.source, pull.drain((frame) => { + const plain = open(Buffer.isBuffer(frame) ? frame : Buffer.from(frame || [])); + if (!plain || leg.phase !== 'connected') return; + leg.queue.push(leg.dec.decode(plain)); + if (leg.queue.length > MIX_QUEUE) leg.queue.shift(); + }, () => { if (call === c) dropLeg(c, leg, false); })); + startMixer(c); + }; + const groupCall = (targets, legacy, cb) => { + const startedAt = Date.now(); + const c = { id: nodeCrypto.randomBytes(16).toString('hex'), peer: targets[0], dir: 'out', group: true, phase: 'calling', startedAt, ringUntil: startedAt + ringMs, legs: [] }; + call = c; + c.tone = startTone(audio, 'connecting'); + for (const peer of targets) { + const leg = { id: nodeCrypto.randomBytes(16).toString('hex'), peer, phase: 'calling', eph: sodium.crypto_box_keypair(), paths: [], via: null, queue: [] }; + c.legs.push(leg); + leg.timer = setTimeout(() => { if (call === c && leg.phase === 'calling') dropLeg(c, leg, true); }, ringMs); + ringPaths(leg, peer, ringRequest(leg.id, leg.eph, peer), () => call === c && leg.phase === 'calling', () => reachedLeg(c, leg), legacy.has(peer)); + } + changed(); + cb(null, snapshot()); + }; + const groupAnswer = (c, req, from, via, cb) => { + const leg = legOf(c, req && req.callId); + if (!leg || leg.phase !== 'calling' || from !== leg.peer) return cb(new Error('no-call')); + const calleePk = ephKey(req.ephPk); + if (!calleePk || !verifyParts(from, ['answer', req.callId, req.ephPk, server.id, req.ts], req.sig)) return cb(new Error('rejected')); + const path = via ? liveRpc(via) : liveRpc(from); + if (!path) return cb(new Error('no-connection')); + leg.keys = deriveKeys(leg.eph.secretKey, calleePk, leg.id, Buffer.from(leg.eph.publicKey), calleePk); + leg.codec = pickCodec([req.codec]); + clearTimeout(leg.timer); + leg.via = via; + leg.phase = 'connected'; + leg.answeredAt = Date.now(); + if (c.phase === 'calling') { stopTone(c); c.phase = 'connected'; c.answeredAt = leg.answeredAt; } + cb(null, true); + changed(); + const quiet = () => {}; + startLegMedia(c, leg, via ? path.phone.relayAudio({ to: leg.peer, callId: leg.id }, quiet) : path.phone.audio({ callId: leg.id }, quiet)); + }; + + const startMedia = (c, duplex, role) => { + const seal = sealer(role === 'out' ? c.keys.a2b : c.keys.b2a); + const open = opener(role === 'out' ? c.keys.b2a : c.keys.a2b); + const out = Pushable(() => {}); + const player = audio.player(); + const enc = makeEncoder(c.codec); + const dec = makeDecoder(); + let pending = Buffer.alloc(0); + const capture = audio.capture((pcm) => { + if (call !== c || c.phase !== 'connected') return; + pending = pending.length ? Buffer.concat([pending, pcm]) : Buffer.from(pcm); + while (pending.length >= FRAME_BYTES) { + const frame = pending.subarray(0, FRAME_BYTES); + pending = pending.subarray(FRAME_BYTES); + if (!c.muted) out.push(seal(enc.encode(frame))); + } + }); + const incoming = []; + c.audioLag = () => incoming.length * FRAME_MS; + const pace = setInterval(() => { + if (call !== c) return; + if (incoming.length > MIX_QUEUE) incoming.splice(0, incoming.length - MIX_QUEUE); + const next = incoming.shift(); + if (next) player.write(next); + }, FRAME_MS); + if (pace.unref) pace.unref(); + const sink = pull.drain((frame) => { + const plain = open(Buffer.isBuffer(frame) ? frame : Buffer.from(frame || [])); + if (plain && !silenced.has(c.peer)) incoming.push(dec.decode(plain)); + }, () => { if (call === c) finish('ended'); }); + c.media = { stop() { clearInterval(pace); try { out.end(); } catch (_) {} capture.stop(); player.stop(); enc.free(); dec.free(); } }; + if (!duplex) return { source: out, sink }; + pull(out, duplex.sink); + pull(duplex.source, sink); + return null; + }; + + const peerFrameSeen = (c) => { + clearTimeout(c.peerTimer); + c.peerTimer = setTimeout(() => { if (call === c && c.peerCam) { c.peerCam = false; endFrames(c.peer); changed(); } }, VIDEO_HOLD_MS); + if (c.peerTimer.unref) c.peerTimer.unref(); + if (!c.peerCam) { c.peerCam = true; changed(); } + }; + const startVideo = (c, duplex, role) => { + const seal = sealer(role === 'out' ? c.keys.va2b : c.keys.vb2a); + const open = opener(role === 'out' ? c.keys.vb2a : c.keys.va2b); + const out = Pushable(() => {}); + const assemble = videoAssembler(); + const sink = pull.drain((frame) => { + const plain = open(Buffer.isBuffer(frame) ? frame : Buffer.from(frame || [])); + if (!plain || call !== c) return; + const got = assemble(plain); + if (got === 'off') { clearTimeout(c.peerTimer); if (c.peerCam) { c.peerCam = false; endFrames(c.peer); changed(); } return; } + if (!got) return; + peerFrameSeen(c); + const hold = setTimeout(() => { if (call === c && c.peerCam) publishFrame(c.peer, got); }, typeof c.audioLag === 'function' ? c.audioLag() : 0); + if (hold.unref) hold.unref(); + }, () => { if (call === c) stopCallVideo(c); }); + c.vmedia = { out, seal, stop() { try { out.end(); } catch (_) {} } }; + if (!duplex) return { source: out, sink }; + pull(out, duplex.sink); + pull(duplex.source, sink); + return null; + }; + const stopCallVideo = (c) => { + clearTimeout(c.peerTimer); + if (c.cam) { c.cam = false; stopCam(); } + if (c.vmedia) { c.vmedia.stop(); c.vmedia = null; } + endFrames(c.peer); + c.peerCam = false; + }; + const sendCallVideo = (c, plain) => { if (c.vmedia) c.vmedia.out.push(c.vmedia.seal(plain)); }; + + const deliver = (m, frame, droppable) => { + if (droppable && m.sent - m.acked > HUB_LAG_MAX) return; + m.sent++; + m.out.push(frame); + }; + const hubBroadcast = (r, from, frame, droppable) => { + for (const [slot, m] of r.members) { + if (slot !== from) deliver(m, frame, droppable); + } + }; + const hubAttach = (rid, id, out, scope = null) => { + let r = hubRooms.get(rid); + if (!r) { + if (hubRooms.size >= HUB_ROOMS_MAX) return null; + if (scope && [...hubRooms.values()].filter(h => h.scope === scope).length >= HUB_RIDS_PER_ROOM) return null; + r = { members: new Map(), scope }; + hubRooms.set(rid, r); + } + if (scope && r.scope !== scope) return null; + for (const old of [...r.members.values()]) if (old.id === id) old.kick(); + if (!hubRooms.has(rid)) hubRooms.set(rid, r); + if (r.members.size >= roomMax) return null; + let slot = 0; + while (r.members.has(slot)) slot++; + const peers = [...r.members].map(([s, m]) => ({ slot: s, id: m.id })); + const m = { id, out, sent: 0, acked: 0 }; + m.leave = () => { + if (r.members.get(slot) !== m) return; + r.members.delete(slot); + if (r.members.size) hubBroadcast(r, slot, ctrlFrame({ t: 'leave', slot }), false); + else if (hubRooms.get(rid) === r) hubRooms.delete(rid); + }; + m.kick = () => { m.leave(); try { out.end(); } catch (_) {} }; + m.handle = (frame) => { + if (r.members.get(slot) !== m || frame.length < 2) return; + const kind = frame[0]; + if (kind === K_VIDEO) { + if (frame.length > ROOM_VIDEO_FRAME_MAX || policy().video === false) return; + const at = Date.now(); + if (at - (m.vwindowAt || 0) >= 1000) { m.vwindowAt = at; m.vbytes = 0; } + m.vbytes = (m.vbytes || 0) + frame.length; + if (m.vbytes > ROOM_VIDEO_BYTES_PER_S) return; + hubBroadcast(r, slot, Buffer.concat([Buffer.from([K_VIDEO, slot]), frame.subarray(1)]), true); + return; + } + if (frame.length > ROOM_FRAME_MAX) return; + if (kind === K_CTRL) { + let msg = null; + try { msg = JSON.parse(frame.subarray(1).toString('utf8')); } catch (_) {} + if (msg && msg.t === 'bye') return m.kick(); + const n = msg && msg.t === 'ack' ? Number(msg.n) : NaN; + if (Number.isFinite(n) && n > m.acked && n <= m.sent) m.acked = n; + return; + } + const now = Date.now(); + if (now - (m.windowAt || 0) >= 1000) { m.windowAt = now; m.windowCount = 0; } + if (++m.windowCount > ROOM_RATE_MAX) return; + if (kind === K_AUDIO || kind === K_DATA) { + hubBroadcast(r, slot, Buffer.concat([Buffer.from([kind, slot]), frame.subarray(1)]), true); + } else if (kind === K_KEY && frame.length > 2 && frame[1] !== slot) { + const target = r.members.get(frame[1]); + if (target) deliver(target, Buffer.concat([Buffer.from([K_KEY, slot]), frame.subarray(2)]), false); + } + }; + r.members.set(slot, m); + deliver(m, ctrlFrame({ t: 'hello', slot, peers, max: roomMax, video: policy().video !== false }), false); + hubBroadcast(r, slot, ctrlFrame({ t: 'join', slot, id }), false); + return m; + }; + const hubDuplex = (from, opts) => { + let member = null; + let closed = false; + const out = Pushable(() => { closed = true; if (member) member.leave(); }); + const sink = pull.drain((frame) => { + if (member) member.handle(Buffer.isBuffer(frame) ? frame : Buffer.from(frame || [])); + }, () => { closed = true; if (member) member.leave(); try { out.end(); } catch (_) {} }); + const refuse = (t) => { out.push(ctrlFrame({ t })); out.end(); }; + const rid = String((opts && opts.rid) || ''); + const roomId = String((opts && opts.roomId) || ''); + const tokenOk = roomId ? (RID.test(roomId) && verifyParts(opts.owner, ['room', roomId], opts.token)) : verifyParts(opts.owner, ['room', rid], opts.token); + if (!from || !RID.test(rid) || !isFeedId(opts.owner) || !tokenOk) { + refuse('refused'); + return { source: out, sink }; + } + const admit = () => { + if (closed) return; + member = hubAttach(rid, from, out, roomId ? `${opts.owner}|${roomId}` : null); + if (!member) refuse('full'); + }; + if (opts.owner === server.id || from === server.id) admit(); + else relayAllowed(opts.owner, from, (ok) => (ok ? admit() : refuse('refused'))); + return { source: out, sink }; + }; + + const roomSnapshot = () => { + const r = room; + if (!r) return null; + const now = Date.now(); + return { + rid: r.rid, ref: r.ref, title: r.title, phase: r.phase, joinedAt: r.joinedAt, muted: !!r.muted, hand: !!r.hand, handSince: r.hand ? r.handAt : 0, + secure: !!r.kid, count: r.peers.size + 1, max: r.max, speaking: now - r.sentAt < 1000, + cam: !!r.cam, videoAvailable: videoOn() && r.hubVideo === true, grid: !!sharpLib, gridOrder: gridIds(r), + notify: !!r.notify, recording: !!r.rec, recordingSince: r.rec ? r.rec.at : 0, recordingBy: [...r.recPeers], + events: r.notify ? r.events.slice(-ROOM_EVENTS_MAX) : [], + peers: [...r.peers.values()].map(p => ({ id: p.id, speaking: now - p.heardAt < 1000, muted: now - p.mutedAt < MUTE_TTL_MS, hand: now - (p.handBeat || 0) < MUTE_TTL_MS, handSince: p.handSince || 0, silenced: silenced.has(p.id), recording: r.recPeers.has(p.id), cam: now - (p.frameAt || 0) < VIDEO_HOLD_MS })) + }; + }; + const roomChanged = () => emit({ type: 'room', room: roomSnapshot() }); + const pushEvent = (r, t, id) => { + r.events.push({ t, id, ts: Date.now() }); + if (r.events.length > ROOM_EVENTS_MAX) r.events.splice(0, r.events.length - ROOM_EVENTS_MAX); + roomChanged(); + }; + const startRoomRec = (r) => { + if (!r || r.rec || r.phase !== 'live') return false; + try { + fs.mkdirSync(recDir, { recursive: true, mode: 0o700 }); + try { fs.chmodSync(recDir, 0o700); } catch (_) {} + const file = path.join(recDir, `${recordingPrefix(r.ref)}-${Date.now()}.wav`); + const fd = fs.openSync(file, 'w', 0o600); + fs.writeSync(fd, wavFile(Buffer.alloc(0))); + r.rec = { fd, file, bytes: 0, at: Date.now() }; + } catch (_) { return false; } + sendRoomData(r, { t: 'rec', on: true }); + playChime(audio, 'recStart'); + pushEvent(r, 'recStart', server.id); + return true; + }; + const stopRoomRec = (r) => { + if (!r || !r.rec) return false; + const rec = r.rec; + r.rec = null; + try { + const h = Buffer.alloc(4); + h.writeUInt32LE(36 + rec.bytes, 0); fs.writeSync(rec.fd, h, 0, 4, 4); + h.writeUInt32LE(rec.bytes, 0); fs.writeSync(rec.fd, h, 0, 4, 40); + fs.closeSync(rec.fd); + if (!rec.bytes) fs.unlinkSync(rec.file); + } catch (_) {} + if (room === r) { + sendRoomData(r, { t: 'rec', on: false }); + playChime(audio, 'recStop'); + pushEvent(r, 'recStop', server.id); + } + if (rec.bytes) emit({ type: 'roomRecorded', room: { ref: r.ref, title: r.title }, file: rec.file, bytes: rec.bytes, durationMs: Math.round(rec.bytes / (RATE * 2) * 1000) }); + return true; + }; + const keeperSlot = (r) => r.order[0]; + const trimKeys = (r) => { while (r.keys.size > 3) r.keys.delete(r.keys.keys().next().value); }; + const useKey = (r, kid, key) => { + if (r.kid === kid && r.sealer && r.keys.get(kid) && r.keys.get(kid).equals(key)) return; + r.keys.set(kid, key); + trimKeys(r); + r.kid = kid; + r.sealer = sealer(key); + sendCodecs(r); + }; + const newRoomKey = (r) => useKey(r, nodeCrypto.randomBytes(4).toString('hex'), nodeCrypto.randomBytes(32)); + const sendRoomKey = (r, slot) => { + const p = r.peers.get(slot); + const key = r.kid && r.keys.get(r.kid); + if (!p || !key) return; + const ts = Date.now(); + const sealed = seal({ rid: r.rid, kid: r.kid, key: b64(key), from: server.id, ts, sig: signParts(keys, ['roomkey', r.rid, r.kid, p.id, ts]) }, p.id); + if (sealed) r.out.push(Buffer.concat([Buffer.from([K_KEY, slot]), Buffer.from(sealed)])); + }; + const peerEntry = (id) => ({ id, queue: [], openers: new Map(), heardAt: 0, mutedAt: 0, dec: null, opus: false, greeted: false }); + const dropPeer = (r, slot) => { const p = r.peers.get(slot); if (p) { if (p.dec) p.dec.free(); clearTimeout(p.vtimer); endFrames(p.id); gridNote(p.id, null); } r.peers.delete(slot); }; + const sendCodecs = (r) => { if (r.out && r.sealer && codecsOffered().length) sendRoomData(r, { t: 'codecs', codecs: codecsOffered() }); }; + const onRoomCtrl = (r, msg) => { + if (msg.t === 'hello' && r.slot === null) { + r.slot = Number(msg.slot); + r.max = Number(msg.max) > 0 ? Number(msg.max) : ROOM_MAX; + r.hubVideo = msg.video === true; + for (const p of Array.isArray(msg.peers) ? msg.peers : []) { + if (!p || !isFeedId(p.id)) continue; + r.peers.set(Number(p.slot), peerEntry(p.id)); + r.order.push(Number(p.slot)); + } + r.order.push(r.slot); + if (r.peers.size >= roomQuietAt) r.muted = true; + if (!r.static && keeperSlot(r) === r.slot) newRoomKey(r); + r.phase = 'live'; + if (r.ready) return r.ready(null); + } else if (msg.t === 'join' && isFeedId(msg.id)) { + const slot = Number(msg.slot); + dropPeer(r, slot); + r.peers.set(slot, peerEntry(msg.id)); + r.order = r.order.filter(s => s !== slot).concat(slot); + if (!r.static && keeperSlot(r) === r.slot) sendRoomKey(r, slot); + if (r.notify) playChime(audio, 'join'); + pushEvent(r, 'join', msg.id); + } else if (msg.t === 'leave') { + const slot = Number(msg.slot); + const wasKeeper = keeperSlot(r) === slot; + const gone = r.peers.get(slot); + dropPeer(r, slot); + r.order = r.order.filter(s => s !== slot); + if (gone) { + if (r.recPeers.delete(gone.id)) pushEvent(r, 'recStop', gone.id); + if (r.notify) playChime(audio, 'leave'); + pushEvent(r, 'leave', gone.id); + } + if (!r.static && keeperSlot(r) === r.slot && (wasKeeper || !r.kid)) { + newRoomKey(r); + for (const s of r.peers.keys()) sendRoomKey(r, s); + } + } else if (msg.t === 'full' || msg.t === 'refused') { + if (r.ready) return r.ready(new Error(msg.t)); + } + roomChanged(); + }; + const onRoomKey = (r, slot, payload) => { + const p = r.peers.get(slot); + if (r.static || !p || keeperSlot(r) !== slot) return; + const msg = unseal(payload.toString('utf8')); + if (!msg || msg.rid !== r.rid || msg.from !== p.id || !/^[0-9a-f]{8}$/.test(String(msg.kid))) return; + if (!verifyParts(p.id, ['roomkey', r.rid, msg.kid, server.id, msg.ts], msg.sig)) return; + const key = Buffer.from(String(msg.key || ''), 'base64'); + if (key.length !== 32) return; + useKey(r, msg.kid, key); + roomChanged(); + }; + const openRoomFrame = (r, p, payload) => { + if (!p || payload.length < 44) return null; + const kid = payload.subarray(0, 4).toString('hex'); + const key = r.keys.get(kid); + if (!key) return null; + let open = p.openers.get(kid); + if (!open) { open = opener(key); p.openers.set(kid, open); } + return open(payload.subarray(4)); + }; + const sendRoomData = (r, msg) => { + if (!r.sealer) return; + r.out.push(Buffer.concat([Buffer.from([K_DATA]), Buffer.from(r.kid, 'hex'), r.sealer(Buffer.from(JSON.stringify(msg)))])); + r.beaconAt = Date.now(); + }; + const onRoomAudio = (r, slot, payload) => { + const p = r.peers.get(slot); + const plain = openRoomFrame(r, p, payload); + if (!plain) return; + if (!p.dec) p.dec = makeDecoder(); + p.queue.push(p.dec.decode(plain)); + if (p.queue.length > MIX_QUEUE) p.queue.shift(); + p.heardAt = Date.now(); + p.mutedAt = 0; + noteMute(r, p, false); + }; + const noteMute = (r, p, on, quiet = false) => { + if (!!p.muted === on) return; + p.muted = on; + if (quiet) return roomChanged(); + const now = Date.now(); + const announce = now - (p.muteToggleAt || 0) >= REC_TOGGLE_MIN_MS; + p.muteToggleAt = now; + if (!announce) return roomChanged(); + if (r.notify) playChime(audio, on ? 'mute' : 'unmute'); + pushEvent(r, on ? 'mute' : 'unmute', p.id); + }; + const noteHand = (r, p, on, at, quiet = false) => { + if (!!p.hand === on) return; + p.hand = on; + const now = Date.now(); + p.handSince = on ? (quiet && Number.isFinite(at) && at > 0 ? Math.min(at, now) : now) : 0; + if (!on || quiet) return roomChanged(); + const announce = now - (p.handToggleAt || 0) >= REC_TOGGLE_MIN_MS; + p.handToggleAt = now; + if (!announce) return roomChanged(); + if (r.notify) playChime(audio, 'hand'); + pushEvent(r, 'hand', p.id); + }; + const onRoomData = (r, slot, payload) => { + const p = r.peers.get(slot); + const plain = openRoomFrame(r, p, payload); + if (!plain) return; + let msg = null; + try { msg = JSON.parse(plain.toString('utf8')); } catch (_) {} + if (msg && msg.t === 'codecs') { + p.opus = Array.isArray(msg.codecs) && msg.codecs.includes('opus'); + if (!p.greeted) { p.greeted = true; sendCodecs(r); } + return; + } + if (msg && msg.t === 'rec') { + const was = r.recPeers.has(p.id); + if (!!msg.on === was) return; + const now = Date.now(); + const announce = now - (p.recToggleAt || 0) >= REC_TOGGLE_MIN_MS; + p.recToggleAt = now; + if (msg.on) r.recPeers.add(p.id); else r.recPeers.delete(p.id); + if (announce) { playChime(audio, msg.on ? 'recStart' : 'recStop'); pushEvent(r, msg.on ? 'recStart' : 'recStop', p.id); } + else roomChanged(); + return; + } + if (msg && msg.t === 'hand') { + p.handBeat = msg.on ? Date.now() : 0; + noteHand(r, p, !!msg.on, Number(msg.at), msg.beacon === true); + return; + } + if (!msg || msg.t !== 'mute') return; + p.mutedAt = msg.on ? Date.now() : 0; + noteMute(r, p, !!msg.on, msg.beacon === true); + }; + const gridIds = (r) => { + const now = Date.now(); + const ids = r.cam ? [server.id] : []; + for (const slot of r.order) { const p = r.peers.get(slot); if (p && now - (p.frameAt || 0) < VIDEO_HOLD_MS) ids.push(p.id); } + return ids; + }; + const gridFrames = new Map(); + let gridBusy = false; + let gridDirty = false; + let gridTimer = null; + const composeGrid = async (r) => { + const ids = gridIds(r).filter(id => gridFrames.has(id)); + if (!ids.length) return; + const cols = Math.ceil(Math.sqrt(ids.length)); + const rows = Math.ceil(ids.length / cols); + const tiles = []; + for (let i = 0; i < ids.length; i++) { + try { + let tile = sharpLib(gridFrames.get(ids[i])).resize(VIDEO_SIZE.w, VIDEO_SIZE.h, { fit: 'cover' }); + if (ids[i] === server.id) tile = tile.flop(); + tiles.push({ input: await tile.toBuffer(), left: (i % cols) * VIDEO_SIZE.w, top: Math.floor(i / cols) * VIDEO_SIZE.h }); + } catch (_) {} + } + if (!tiles.length) return; + const jpeg = await sharpLib({ create: { width: cols * VIDEO_SIZE.w, height: rows * VIDEO_SIZE.h, channels: 3, background: '#000000' } }).composite(tiles).jpeg({ quality: 70 }).toBuffer(); + if (room === r) publishFrame('grid', jpeg); + }; + const gridTick = () => { + const r = room; + if (!r || !sharpLib || gridBusy || !gridDirty || !frameWatchers.has('grid')) return; + gridDirty = false; + gridBusy = true; + composeGrid(r).catch(() => {}).then(() => { gridBusy = false; }); + }; + const gridNote = (id, jpeg) => { if (jpeg) gridFrames.set(id, jpeg); else gridFrames.delete(id); gridDirty = true; }; + const stopGrid = () => { clearInterval(gridTimer); gridTimer = null; gridFrames.clear(); gridDirty = false; endFrames('grid'); }; + const onRoomVideo = (r, slot, payload) => { + const p = r.peers.get(slot); + if (!videoAllowed()) return; + const plain = openRoomFrame(r, p, payload); + if (!plain) return; + if (!p.vasm) p.vasm = videoAssembler(); + const got = p.vasm(plain); + if (got === 'off') { clearTimeout(p.vtimer); if (p.frameAt) { p.frameAt = 0; endFrames(p.id); gridNote(p.id, null); roomChanged(); } return; } + if (!got) return; + const fresh = !(Date.now() - (p.frameAt || 0) < VIDEO_HOLD_MS); + p.frameAt = Date.now(); + clearTimeout(p.vtimer); + p.vtimer = setTimeout(() => { if (room === r) { endFrames(p.id); gridNote(p.id, null); roomChanged(); } }, VIDEO_HOLD_MS); + if (p.vtimer.unref) p.vtimer.unref(); + const hold = setTimeout(() => { if (room !== r || !(Date.now() - (p.frameAt || 0) < VIDEO_HOLD_MS)) return; publishFrame(p.id, got); gridNote(p.id, got); }, p.queue.length * FRAME_MS); + if (hold.unref) hold.unref(); + if (fresh) roomChanged(); + }; + const sendRoomVideo = (r, plain) => { + if (!r.sealer || !r.hubVideo) return; + r.out.push(Buffer.concat([Buffer.from([K_VIDEO]), Buffer.from(r.kid, 'hex'), r.sealer(plain)])); + }; + const onRoomFrame = (r, frame) => { + if (room !== r) return; + const buf = Buffer.isBuffer(frame) ? frame : Buffer.from(frame || []); + if (!buf.length) return; + r.received = (r.received || 0) + 1; + if (r.received - (r.ackedAt || 0) >= ROOM_ACK_EVERY) { r.ackedAt = r.received; r.out.push(ctrlFrame({ t: 'ack', n: r.received })); } + if (buf[0] === K_CTRL) { + let msg = null; + try { msg = JSON.parse(buf.subarray(1).toString('utf8')); } catch (_) {} + if (msg && typeof msg === 'object') onRoomCtrl(r, msg); + return; + } + if (buf.length < 3) return; + if (buf[0] === K_AUDIO) onRoomAudio(r, buf[1], buf.subarray(2)); + else if (buf[0] === K_VIDEO) onRoomVideo(r, buf[1], buf.subarray(2)); + else if (buf[0] === K_DATA) onRoomData(r, buf[1], buf.subarray(2)); + else if (buf[0] === K_KEY) onRoomKey(r, buf[1], buf.subarray(2)); + }; + const startRoomMedia = (r) => { + const player = audio.player(); + const gate = voiceGate(); + const samples = FRAME_BYTES / 2; + let pending = Buffer.alloc(0); + const capture = audio.capture((pcm) => { + if (room !== r) return; + pending = pending.length ? Buffer.concat([pending, pcm]) : Buffer.from(pcm); + while (pending.length >= FRAME_BYTES) { + const mic = pending.subarray(0, FRAME_BYTES); + pending = pending.subarray(FRAME_BYTES); + const acc = new Int32Array(samples); + for (const p of r.peers.values()) { const f = p.queue.shift(); if (f && !silenced.has(p.id)) mixInto(acc, f); } + player.write(clampPcm(acc)); + if (r.rec) { + const both = Int32Array.from(acc); + if (!r.muted) mixInto(both, mic); + const take = clampPcm(both); + try { fs.writeSync(r.rec.fd, take); r.rec.bytes += take.length; } catch (_) { stopRoomRec(r); } + if (r.rec && r.rec.bytes >= ROOM_REC_MAX_BYTES) stopRoomRec(r); + if (r.rec && r.sealer && Date.now() - (r.recBeaconAt || 0) >= MUTE_BEACON_MS) { sendRoomData(r, { t: 'rec', on: true }); r.recBeaconAt = Date.now(); } + } + const voiced = gate(mic); + if (r.muted && r.sealer && Date.now() - (r.beaconAt || 0) >= MUTE_BEACON_MS) sendRoomData(r, { t: 'mute', on: true, beacon: true }); + if (r.hand && r.sealer && Date.now() - (r.handBeaconAt || 0) >= MUTE_BEACON_MS) { sendRoomData(r, { t: 'hand', on: true, at: r.handAt, beacon: true }); r.handBeaconAt = Date.now(); } + if (r.muted || !r.sealer || !voiced) continue; + r.sentAt = Date.now(); + const opus = codecsOffered().includes('opus') && r.peers.size > 0 && [...r.peers.values()].every(p => p.opus); + if (opus && !r.enc) r.enc = makeEncoder('opus'); + r.out.push(Buffer.concat([Buffer.from([K_AUDIO]), Buffer.from(r.kid, 'hex'), r.sealer(opus ? r.enc.encode(mic) : ulawEncode(mic))])); + } + }); + r.media = { stop() { capture.stop(); player.stop(); } }; + }; + const closeDuplex = (duplex) => { + if (!duplex) return; + try { duplex.source(true, () => {}); } catch (_) {} + try { pull(pull.empty(), duplex.sink); } catch (_) {} + }; + const leaveRoom = (r, outcome) => { + if (!r || room !== r) return; + if (r.ready) { r.ready(new Error('left')); return; } + stopRoomRec(r); + room = null; + clearTimeout(r.timer); + if (r.cam) { r.cam = false; stopCam(); } + for (const p of r.peers.values()) { clearTimeout(p.vtimer); endFrames(p.id); } + stopGrid(); + if (r.media) r.media.stop(); + if (r.enc) { r.enc.free(); r.enc = null; } + for (const p of r.peers.values()) if (p.dec) p.dec.free(); + try { r.out.push(ctrlFrame({ t: 'bye' })); } catch (_) {} + try { r.out.end(); } catch (_) {} + if (outcome) emit({ type: 'roomEnded', room: { rid: r.rid, ref: r.ref, title: r.title, joinedAt: r.joinedAt, endedAt: Date.now(), outcome } }); + roomChanged(); + }; + const staticKey = (rid, secret) => { + const key = Buffer.from(sodium.crypto_hash_sha256(Buffer.concat([Buffer.from('oasis-room|'), Buffer.from(rid, 'hex'), Buffer.from(String(secret))]))); + return { kid: Buffer.from(sodium.crypto_hash_sha256(key)).subarray(0, 4).toString('hex'), key }; + }; + + if (server.close && typeof server.close.hook === 'function') { + server.close.hook(function (fn, args) { finish('ended'); leaveRoom(room, 'ended'); stopCam(); return fn.apply(this, args); }); + } + + return { + ring(raw, cb) { + const reply = typeof cb === 'function' ? cb : () => {}; + const req = opened(raw); + const { sender: from, via } = senderOf(this, req); + reply(null, true); + if (!from || from === server.id || !req || typeof req !== 'object') return; + const { callId, to, ephPk, ts, sig } = req; + dbg('incoming ring from', sid(from), via ? 'via ' + sid(via) : 'direct'); + if (!/^[0-9a-f]{32}$/.test(String(callId)) || to !== server.id || Math.abs(Date.now() - Number(ts)) > CLOCK_SKEW_MS) return; + const remotePk = ephKey(ephPk); + if (!remotePk || !verifyParts(from, ['ring', callId, ephPk, to, ts], sig)) return; + if (!seenCalls.has(callId) && !callerAllowed(from)) return; + if (!via) directRings.set(callId, Date.now()); + if (call && call.id === callId && call.dir === 'in') { + if (!via && call.phase === 'incoming' && call.peer === from) call.via = null; + return; + } + if (!firstSeen(callId)) return; + const ack = (state) => { + const at = Date.now(); + sendVia(directRings.has(callId) ? null : via, from, 'answer', { callId, from: server.id, ringing: state, ts: at, sig: signParts(keys, ['ringing', callId, state, from, at]) }); + }; + allowedCaller(from, (ok) => { + if (!ok || !audio) return ack('ringing'); + if (call || room) { + ack('busy'); + const at = Date.now(); + emit({ type: 'ended', call: { id: callId, peer: from, dir: 'in', startedAt: at, answeredAt: 0, endedAt: at, outcome: 'missed' } }); + return; + } + const startedAt = Date.now(); + call = { id: callId, peer: from, dir: 'in', phase: 'incoming', startedAt, ringUntil: startedAt + ringMs, remotePk, via: directRings.has(callId) ? null : via, codec: pickCodec(req.codecs), video: pickVideo(req.codecs, videoOn()) }; + call.tone = startTone(audio, 'ring'); + call.timer = setTimeout(() => { if (call && call.id === callId && call.phase === 'incoming') finish('missed'); }, ringMs); + changed(); + emit({ type: 'incoming', id: callId, peer: from }); + ack('ringing'); + }); + }, + answer(raw, cb) { + const req = opened(raw); + const { sender: from, via } = senderOf(this, req); + const c = call; + if (req && (req.ringing === 'ringing' || req.ringing === 'busy')) return ringingAck(c, req, from, cb); + if (c && c.group) return groupAnswer(c, req, from, via, cb); + if (!c || c.dir !== 'out' || c.phase !== 'calling' || from !== c.peer || !req || req.callId !== c.id) return cb(new Error('no-call')); + const calleePk = ephKey(req.ephPk); + if (!calleePk || !verifyParts(from, ['answer', req.callId, req.ephPk, server.id, req.ts], req.sig)) return cb(new Error('rejected')); + const path = via ? liveRpc(via) : liveRpc(from); + if (!path) return cb(new Error('no-connection')); + c.keys = deriveKeys(c.eph.secretKey, calleePk, c.id, Buffer.from(c.eph.publicKey), calleePk); + c.codec = pickCodec([req.codec]); + c.video = pickVideo([req.video]); + clearTimeout(c.timer); + stopTone(c); + c.via = via; + c.phase = 'connected'; + c.answeredAt = Date.now(); + cb(null, true); + changed(); + const quiet = () => {}; + startMedia(c, via ? path.phone.relayAudio({ to: c.peer, callId: c.id }, quiet) : path.phone.audio({ callId: c.id }, quiet), 'out'); + if (c.video) startVideo(c, via ? path.phone.relayVideo({ to: c.peer, callId: c.id }, quiet) : path.phone.video({ callId: c.id }, quiet), 'out'); + }, + hangup(raw, cb) { + const reply = typeof cb === 'function' ? cb : () => {}; + const req = opened(raw); + const { sender: from } = senderOf(this, req); + const c = call; + dbg('hangup received from', sid(from), c ? 'call ' + sid(c.peer) + ' ' + c.phase : 'no call'); + if (c && c.group) { + const leg = legOf(c, req && req.callId); + if (leg && from === leg.peer && verifyParts(from, ['hangup', req.callId, server.id, req.ts], req.sig)) dropLeg(c, leg, false); + return reply(null, true); + } + if (c && req && req.callId === c.id && from === c.peer && verifyParts(from, ['hangup', req.callId, server.id, req.ts], req.sig)) { + if (c.dir === 'in') finish(c.phase === 'incoming' ? 'missed' : 'ended'); + else if (c.phase === 'calling' || c.phase === 'connected') finish(c.phase === 'connected' ? 'ended' : 'noanswer'); + } + reply(null, true); + }, + audio(opts) { + const direct = this && this.id; + const from = opts && isFeedId(opts.from) ? opts.from : direct; + const c = call; + const viaOk = direct === c?.peer || (c?.via && direct === c.via); + if (!c || c.dir !== 'in' || !c.keys || c.media || from !== c.peer || !viaOk || !opts || opts.callId !== c.id || (c.phase !== 'connecting' && c.phase !== 'connected')) { + return { source: pull.error(new Error('no-call')), sink: pull.drain(null, () => {}) }; + } + c.phase = 'connected'; + return startMedia(c, null, 'in'); + }, + relay(req, cb) { + const reply = typeof cb === 'function' ? cb : () => {}; + const from = this && this.id; + reply(null, true); + if (!from || policy().relay === false || !req || typeof req !== 'object' || !isFeedId(req.to) || req.to === from) return; + if (!['ring', 'answer', 'hangup'].includes(req.method) || typeof req.sealed !== 'string' || !/^[0-9a-f]{32}$/.test(String(req.cid))) return; + const forwarded = req.hop === 1; + dbg('relay', req.method, 'from', sid(from), 'to', sid(req.to), forwarded ? 'forwarded' : 'origin', 'target-live=' + !!liveRpc(req.to)); + if (forwarded && !knownPubs().includes(from)) return; + const msg = { to: req.to, method: req.method, cid: req.cid, sealed: req.sealed, hop: 1 }; + const side = learn(req.cid, req.to, from, forwarded); + if (side.via === req.to) { + relayAllowed(req.to, forwarded ? req.to : from, (ok) => { + const target = ok ? liveRpc(req.to) : null; + dbg('relay deliver', sid(req.to), ok ? (target ? 'delivering' : 'not connected here') : 'policy refuses'); + if (!target) return; + try { target.phone[req.method]({ sealed: req.sealed }, () => {}); } catch (_) {} + }); + return; + } + if (side.via && side.via !== from && liveRpc(side.via)) { + try { liveRpc(side.via).phone.relay(msg, () => {}); } catch (_) {} + return; + } + if (forwarded || req.method !== 'ring') return; + relayAllowed(from, from, (ok) => { + if (!ok) return; + for (const pub of knownPubs().filter(id => id !== from)) { + try { liveRpc(pub).phone.relay(msg, () => {}); } catch (_) {} + } + }); + }, + relayAudio(opts) { + const from = this && this.id; + const refuse = { source: pull.error(new Error('no-route')), sink: pull.drain(null, () => {}) }; + if (!from || !opts || !isFeedId(opts.to) || opts.to === from || policy().relay === false) return refuse; + const forwarded = knownPubs().includes(from) && isFeedId(opts.from); + const caller = forwarded ? opts.from : from; + const r = routes.get(String(opts.callId || '')); + const side = r && r.sides.find(x => x.id === opts.to); + const direct = liveRpc(opts.to); + const next = direct || (side && side.via && side.via !== from ? liveRpc(side.via) : null); + if (!next) return refuse; + const remote = direct + ? next.phone.audio({ callId: opts.callId, from: caller }, () => {}) + : next.phone.relayAudio({ to: opts.to, callId: opts.callId, from: caller }, () => {}); + return { source: remote.source, sink: remote.sink }; + }, + video(opts) { + const direct = this && this.id; + const from = opts && isFeedId(opts.from) ? opts.from : direct; + const c = call; + const viaOk = direct === c?.peer || (c?.via && direct === c.via); + if (!c || c.dir !== 'in' || !c.keys || !c.video || c.vmedia || from !== c.peer || !viaOk || !opts || opts.callId !== c.id || (c.phase !== 'connecting' && c.phase !== 'connected')) { + return { source: pull.error(new Error('no-call')), sink: pull.drain(null, () => {}) }; + } + return startVideo(c, null, 'in'); + }, + relayVideo(opts) { + const from = this && this.id; + const refuse = { source: pull.error(new Error('no-route')), sink: pull.drain(null, () => {}) }; + if (!from || !opts || !isFeedId(opts.to) || opts.to === from || policy().relay === false || policy().video === false) return refuse; + const forwarded = knownPubs().includes(from) && isFeedId(opts.from); + const caller = forwarded ? opts.from : from; + const r = routes.get(String(opts.callId || '')); + const side = r && r.sides.find(x => x.id === opts.to); + const direct = liveRpc(opts.to); + const next = direct || (side && side.via && side.via !== from ? liveRpc(side.via) : null); + if (!next) return refuse; + const remote = direct + ? next.phone.video({ callId: opts.callId, from: caller }, () => {}) + : next.phone.relayVideo({ to: opts.to, callId: opts.callId, from: caller }, () => {}); + return { source: remote.source, sink: remote.sink }; + }, + camera(flag, cb) { + const on = !!flag; + const c = call; + if (c && c.vmedia && !c.group) { + if (on === !!c.cam) return cb(null, snapshot()); + if (on) { + if (!startCam((jpeg, seq) => { if (call === c) for (const plain of videoChunks(seq, jpeg)) sendCallVideo(c, plain); })) return cb(new Error('unavailable')); + c.cam = true; + } else { + c.cam = false; + stopCam(); + sendCallVideo(c, videoOff()); + } + changed(); + return cb(null, snapshot()); + } + const r = room; + if (r && r.phase === 'live') { + if (on === !!r.cam) return cb(null, roomSnapshot()); + if (on) { + if (!r.hubVideo || !startCam((jpeg, seq) => { if (room !== r) return; gridNote(server.id, jpeg); for (const plain of videoChunks(seq, jpeg)) sendRoomVideo(r, plain); })) return cb(new Error('unavailable')); + r.cam = true; + } else { + r.cam = false; + stopCam(); + gridNote(server.id, null); + sendRoomVideo(r, videoOff()); + } + roomChanged(); + return cb(null, roomSnapshot()); + } + cb(new Error('no-call')); + }, + videoFrames(opts) { + const target = String((opts && opts.peer) || ''); + const p = Pushable(() => { const set = frameWatchers.get(target); if (set) { set.delete(p); if (!set.size) frameWatchers.delete(target); } }); + const now = Date.now(); + const live = target === 'me' + ? !!cam + : target === 'grid' ? !!(room && sharpLib && gridIds(room).length) + : call ? (call.peer === target && !!call.peerCam) + : room ? [...room.peers.values()].some(x => x.id === target && now - (x.frameAt || 0) < VIDEO_HOLD_MS) + : false; + if (!live) { p.end(); return p; } + if (!frameWatchers.has(target)) frameWatchers.set(target, new Set()); + frameWatchers.get(target).add(p); + return p; + }, + + call(target, opts, cb) { + if (typeof opts === 'function') { cb = opts; opts = {}; } + const legacy = new Set(Array.isArray(opts && opts.legacy) ? opts.legacy : []); + if (!audio) return cb(new Error('unavailable')); + const targets = [...new Set(Array.isArray(target) ? target : [target])]; + if (!targets.length || targets.length > GROUP_MAX || targets.some(t => !isFeedId(t) || t === server.id)) return cb(new Error('invalid')); + if (call || room) return cb(new Error('busy')); + if (targets.length > 1) return groupCall(targets, legacy, cb); + target = targets[0]; + const id = nodeCrypto.randomBytes(16).toString('hex'); + const eph = sodium.crypto_box_keypair(); + const startedAt = Date.now(); + const c = { id, peer: target, dir: 'out', phase: 'calling', startedAt, ringUntil: startedAt + ringMs, eph, paths: [], via: null }; + call = c; + c.tone = startTone(audio, 'connecting'); + c.timer = setTimeout(() => toVoicemail(c, 'unreachable'), ringMs); + changed(); + ringPaths(c, target, ringRequest(id, eph, target), () => call === c && c.phase === 'calling', () => reachedCall(c), legacy.has(target)); + cb(null, snapshot()); + }, + accept(cb) { + const c = call; + if (!c || c.dir !== 'in' || c.phase !== 'incoming') return cb(new Error('no-call')); + const eph = sodium.crypto_box_keypair(); + c.keys = deriveKeys(eph.secretKey, c.remotePk, c.id, c.remotePk, Buffer.from(eph.publicKey)); + stopTone(c); + clearTimeout(c.timer); + c.phase = 'connecting'; + c.answeredAt = Date.now(); + changed(); + const ts = Date.now(); + const ephPk = b64(eph.publicKey); + sendVia(c.via || null, c.peer, 'answer', { callId: c.id, from: server.id, ephPk, ts, sig: signParts(keys, ['answer', c.id, ephPk, c.peer, ts]), codec: c.codec, video: c.video }, (err) => { + if (call !== c) return; + if (err) return finish('failed'); + if (c.phase === 'connecting') { c.phase = 'connected'; changed(); } + }); + cb(null, snapshot()); + }, + reject(cb) { + const c = call; + if (c && c.dir === 'in' && c.phase === 'incoming') finish('rejected'); + cb(null, snapshot()); + }, + end(cb) { + const c = call; + if (!c) return cb(null, null); + if (c.group) { + for (const leg of c.legs) if (leg.phase === 'calling' || leg.phase === 'connected') hangupLeg(leg); + finish(c.answeredAt ? 'ended' : 'cancelled'); + return cb(null, null); + } + if (c.dir === 'in' && c.phase === 'incoming') { finish('rejected'); return cb(null, null); } + if (c.phase === 'calling' || c.phase === 'connecting' || c.phase === 'connected') notifyHangup(c); + finish(c.phase === 'connected' || c.phase === 'connecting' ? 'ended' : (c.phase === 'calling' ? 'cancelled' : 'noanswer')); + cb(null, null); + }, + mute(flag, cb) { + if (call) { call.muted = !!flag; changed(); } + cb(null, snapshot()); + }, + silence(opts, cb) { + const id = opts && opts.id; + if (!isFeedId(id) || id === server.id) return cb(new Error('invalid')); + if (opts.on) silenced.add(id); else silenced.delete(id); + if (call) changed(); + if (room) roomChanged(); + cb(null, { id, on: silenced.has(id) }); + }, + dismiss(cb) { + const c = call; + if (c && c.dir === 'out' && (c.phase === 'noanswer' || c.phase === 'recorded')) finish('noanswer'); + cb(null, snapshot()); + }, + recordStop(cb) { + const c = call; + if (!c || c.dir !== 'out' || (c.phase !== 'recording' && c.phase !== 'recorded') || !c.rec) return cb(new Error('no-recording')); + stopRecorder(c); + const pcm = Buffer.concat(c.rec.chunks); + c.rec = null; + if (pcm.length < RATE * 2 * PAM_MIN_MS / 1000) { finish('noanswer'); return cb(new Error('empty')); } + const file = path.join(os.tmpdir(), `oasis-voice-${c.id}.wav`); + try { fs.writeFileSync(file, wavFile(pcm), { mode: 0o600 }); } catch (err) { return cb(err); } + const out = { file, durationMs: Math.round(pcm.length / (RATE * 2) * 1000), peer: c.peer, callId: c.id }; + finish('pam'); + cb(null, out); + }, + recordCancel(cb) { + const c = call; + if (c && (c.phase === 'recording' || c.phase === 'recorded')) { + stopRecorder(c); + c.rec = null; + finish('noanswer'); + } + cb(null, snapshot()); + }, + roomHub(opts) { return hubDuplex(this && this.id, opts); }, + roomInfo(opts, cb) { + const r = hubRooms.get(String((opts && opts.rid) || '')); + cb(null, { count: r ? r.members.size : 0, max: roomMax, video: policy().video !== false }); + }, + roomAdmits(opts, cb) { + const from = this && this.id; + if (!from) return cb(null, false); + relayAllowed(from, from, (ok) => cb(null, !!ok)); + }, + roomJoin(opts, cb) { + if (!audio) return cb(new Error('unavailable')); + if (call || room) return cb(new Error('busy')); + const rid = String((opts && opts.rid) || ''); + if (!RID.test(rid) || !isFeedId(opts.owner) || !isFeedId(opts.hub) || typeof opts.token !== 'string') return cb(new Error('invalid')); + const secrets = (Array.isArray(opts.secrets) ? opts.secrets : []).filter(x => typeof x === 'string' && x); + const r = { + rid, ref: String(opts.ref || ''), title: String(opts.title || '').slice(0, 200), hub: opts.hub, + phase: 'joining', joinedAt: Date.now(), muted: false, slot: null, max: ROOM_MAX, peers: new Map(), order: [], + keys: new Map(), kid: null, sealer: null, static: secrets.length > 0, sentAt: 0, + events: [], notify: true, rec: null, recPeers: new Set(), hand: false, handAt: 0 + }; + if (r.static) { + for (const secret of secrets.slice().reverse()) { const k = staticKey(rid, secret); r.keys.set(k.kid, k.key); } + const first = staticKey(rid, secrets[0]); + r.kid = first.kid; + r.sealer = sealer(first.key); + } + room = r; + r.out = Pushable(() => {}); + r.ready = (err) => { + r.ready = null; + clearTimeout(r.timer); + if (err) { leaveRoom(r, null); return cb(err); } + startRoomMedia(r); + if (sharpLib) { gridTimer = setInterval(gridTick, Math.round(1000 / VIDEO_FPS)); if (gridTimer.unref) gridTimer.unref(); } + roomChanged(); + cb(null, roomSnapshot()); + }; + r.timer = setTimeout(() => { if (r.ready) r.ready(new Error('unreachable')); }, ROOM_JOIN_MS); + const attach = (duplex) => { + if (room !== r || !r.ready) return closeDuplex(duplex); + if (!duplex) return r.ready(new Error('unreachable')); + pull(r.out, duplex.sink); + pull(duplex.source, pull.drain((frame) => onRoomFrame(r, frame), () => { + if (room !== r) return; + if (r.ready) r.ready(new Error('unreachable')); + else leaveRoom(r, 'lost'); + })); + }; + const req = { rid, owner: opts.owner, token: opts.token, ...(RID.test(String(opts.roomId || '')) ? { roomId: String(opts.roomId) } : {}) }; + if (opts.hub === server.id) return attach(hubDuplex(server.id, req)); + const viaRpc = (rpc) => attach(rpc && rpc.phone && typeof rpc.phone.roomHub === 'function' ? rpc.phone.roomHub(req, () => {}) : null); + const live = liveRpc(opts.hub); + if (live) return viaRpc(live); + if (typeof opts.address === 'string' && opts.address) { + try { + return server.conn.connect(opts.address, (err, rpc) => { + const got = !err && rpc && rpc.phone ? rpc : liveRpc(opts.hub); + return got ? viaRpc(got) : reach(opts.hub, viaRpc); + }); + } catch (_) {} + } + reach(opts.hub, viaRpc); + }, + roomLeave(cb) { + leaveRoom(room, 'left'); + cb(null, null); + }, + roomRecStart(cb) { cb(null, startRoomRec(room) ? roomSnapshot() : null); }, + roomRecStop(cb) { cb(null, stopRoomRec(room) ? roomSnapshot() : null); }, + roomNotify(flag, cb) { + if (room) { room.notify = !!flag; roomChanged(); } + cb(null, roomSnapshot()); + }, + roomClearEvents(cb) { + if (room) { room.events = []; roomChanged(); } + cb(null, roomSnapshot()); + }, + roomHand(flag, cb) { + if (room) { + room.hand = !!flag; + room.handAt = room.hand ? Date.now() : 0; + sendRoomData(room, { t: 'hand', on: room.hand, at: room.handAt }); + room.handBeaconAt = Date.now(); + roomChanged(); + } + cb(null, roomSnapshot()); + }, + roomMute(flag, cb) { + if (room) { room.muted = !!flag; sendRoomData(room, { t: 'mute', on: room.muted }); roomChanged(); } + cb(null, roomSnapshot()); + }, + roomCount(opts, cb) { + const rid = String((opts && opts.rid) || ''); + if (!RID.test(rid) || !isFeedId(opts.hub)) return cb(null, null); + if (room && room.rid === rid && room.phase === 'live') return cb(null, { count: room.peers.size + 1, max: room.max }); + if (opts.hub === server.id) { const h = hubRooms.get(rid); return cb(null, { count: h ? h.members.size : 0, max: roomMax }); } + const rpc = liveRpc(opts.hub); + if (!rpc || typeof rpc.phone.roomInfo !== 'function') return cb(null, null); + let done = false; + const finishPeek = (v) => { if (done) return; done = true; clearTimeout(t); cb(null, v); }; + const t = setTimeout(() => finishPeek(null), ROOM_PEEK_MS); + try { + rpc.phone.roomInfo({ rid }, (err, info) => finishPeek(!err && info && Number.isFinite(Number(info.count)) ? { count: Number(info.count), max: Number(info.max) > 0 ? Math.min(ROOM_MAX, Number(info.max)) : ROOM_MAX } : null)); + } catch (_) { finishPeek(null); } + }, + roomState() { return roomSnapshot(); }, + roomToken(rid) { return RID.test(String(rid || '')) ? signParts(keys, ['room', String(rid)]) : null; }, + roomHubFor(cb) { + const done = typeof cb === 'function' ? cb : () => {}; + let entries = []; + try { entries = netFirst(server.conn.query().peersAll()); } catch (_) {} + const ranked = typeof server.oasisPeerRank === 'function' ? (id) => server.oasisPeerRank([null, { key: id }]) : () => 0; + const candidates = knownPubs().filter(id => { const rpc = liveRpc(id); return rpc && rpc.phone && typeof rpc.phone.roomHub === 'function'; }).sort((x, y) => ranked(y) - ranked(x)); + const admits = (id, next) => { + const rpc = liveRpc(id); + const byGraph = () => relation('isFollowing', id, server.id, next); + if (!rpc || typeof rpc.phone.roomAdmits !== 'function') return byGraph(); + let settled = false; + const finishAsk = (fn) => { if (settled) return; settled = true; clearTimeout(t); fn(); }; + const t = setTimeout(() => finishAsk(() => next(false)), ROOM_PEEK_MS); + try { rpc.phone.roomAdmits({}, (err, ok) => finishAsk(err ? byGraph : () => next(!!ok))); } catch (_) { finishAsk(byGraph); } + }; + const pick = (i) => { + if (i >= candidates.length) return done(null, { key: server.id, address: '' }); + admits(candidates[i], (ok) => { + if (!ok) return pick(i + 1); + const hit = entries.find(e => Array.isArray(e) && e[0] && e[1] && e[1].key === candidates[i]); + done(null, { key: candidates[i], address: hit ? hit[0] : '' }); + }); + }; + pick(0); + }, + state() { return snapshot(); }, + available() { return !!audio; }, + pubs() { return [...pubKeys()]; }, + events() { + const p = Pushable(() => subscribers.delete(p)); + subscribers.add(p); + p.push({ type: 'state', state: snapshot() }); + return p; + } + }; + } +}; + +module.exports.setAudioFactory = (factory) => { audioFactory = typeof factory === 'function' ? factory : systemAudio; }; +module.exports.setVideoFactory = (factory) => { videoFactory = typeof factory === 'function' ? factory : systemVideo; }; +module.exports.jpegSplitter = jpegSplitter; +module.exports.VIDEO_CHUNK = VIDEO_CHUNK; +module.exports.VIDEO_HOLD_MS = VIDEO_HOLD_MS; +module.exports.ROOM_VIDEO_BYTES_PER_S = ROOM_VIDEO_BYTES_PER_S; +module.exports.ulawEncode = ulawEncode; +module.exports.ulawDecode = ulawDecode; +module.exports.RING_MS = RING_MS; +module.exports.GROUP_MAX = GROUP_MAX; +module.exports.ROOM_MAX = ROOM_MAX; +module.exports.RING_PER_WINDOW = RING_PER_WINDOW; +module.exports.chimeMs = chimeMs; +module.exports.recordingPrefix = recordingPrefix; +module.exports.VOICEMAIL_MAX_MS = VOICEMAIL_MAX_MS; diff --git a/src/server/phone_video_android.js b/src/server/phone_video_android.js new file mode 100644 index 0000000..deb6354 --- /dev/null +++ b/src/server/phone_video_android.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const net = require('net'); +const path = require('path'); +const { jpegSplitter } = require('./phone_module'); + +const MARKER = path.join(__dirname, '..', '..', '.audio_bridge'); + +const bridgePort = () => { + try { + const port = Number(String(fs.readFileSync(MARKER, 'utf8')).trim()); + return Number.isFinite(port) && port > 0 ? port : 0; + } catch (_) { + return 0; + } +}; + +module.exports = () => { + const port = bridgePort(); + if (!port) return null; + return { + capture(onJpeg) { + const sock = net.connect(port, '127.0.0.1'); + sock.on('error', () => {}); + sock.setNoDelay(true); + sock.write('V'); + sock.on('data', jpegSplitter(onJpeg)); + return { stop() { try { sock.destroy(); } catch (_) {} } }; + } + }; +}; diff --git a/src/server/snapshot_plugin.js b/src/server/snapshot_plugin.js new file mode 100644 index 0000000..9c865ef --- /dev/null +++ b/src/server/snapshot_plugin.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const path = require('path'); +const pull = require('./node_modules/pull-stream'); +const defer = require('./node_modules/pull-defer'); +const toPull = require('./node_modules/stream-to-pull-stream'); + +module.exports = { + name: 'snapshot', + version: '1.0.0', + manifest: { info: 'async', get: 'source' }, + permissions: { anonymous: { allow: ['info', 'get'] } }, + init(server, config) { + const file = (opts) => path.join(config.path, 'oasis', 'content', opts && opts.tier === 'recent' ? 'snapshot-recent.oasissn' : 'snapshot.oasissn'); + const stat = (opts) => { + try { const st = fs.statSync(file(opts)); return { tier: opts && opts.tier === 'recent' ? 'recent' : 'full', bytes: st.size, createdAt: new Date(st.mtimeMs).toISOString() }; } catch (_) { return null; } + }; + const allowed = (rpc, cb) => { + const id = rpc && rpc.id; + if (!id) return cb(null, false); + if (id === server.id) return cb(null, true); + if (!server.friends || typeof server.friends.isFollowing !== 'function') return cb(null, false); + server.friends.isFollowing({ source: server.id, dest: id }, (err, yes) => cb(null, !err && !!yes)); + }; + return { + info(opts, cb) { + if (typeof opts === 'function') { cb = opts; opts = {}; } + allowed(this, (_, ok) => { + if (!ok) return cb(new Error('snapshot: not allowed')); + const s = stat(opts); + if (!s) return cb(new Error('snapshot: not available')); + cb(null, s); + }); + }, + get(opts) { + const source = defer.source(); + allowed(this, (_, ok) => { + if (!ok) return source.resolve(pull.error(new Error('snapshot: not allowed'))); + if (!stat(opts)) return source.resolve(pull.error(new Error('snapshot: not available'))); + source.resolve(toPull.source(fs.createReadStream(file(opts)))); + }); + return source; + } + }; + } +}; diff --git a/src/server/ssb_config.js b/src/server/ssb_config.js index 551da1a..1f1a47f 100644 --- a/src/server/ssb_config.js +++ b/src/server/ssb_config.js @@ -3,8 +3,7 @@ const path = require('path'); const Config = require('ssb-config/inject'); const minimist = require('minimist'); -const configPath = path.resolve(__dirname, '../configs', 'server-config.json'); -const configData = JSON.parse(fs.readFileSync(configPath, 'utf8')); +const configData = require('../configs/config-manager').readServerConfig(); const argv = process.argv.slice(2); const i = argv.indexOf('--'); @@ -43,7 +42,15 @@ if (debug) { const megabyte = Math.pow(2, 20); config.blobs = config.blobs || {}; -config.blobs.max = 50 * megabyte; +config.blobs.max = 75 * megabyte; + +config.db2 = { automigrate: false, dangerouslyKillFlumeWhenMigrated: false, ...(config.db2 || {}) }; + +const lanOn = (() => { try { return require('../configs/config-manager').getConfig().lanBroadcasting !== false; } catch (_) { return true; } })(); +const bindHost = config.pub || lanOn ? '0.0.0.0' : '127.0.0.1'; +for (const entry of (config.connections && config.connections.incoming && config.connections.incoming.net) || []) { + if (entry && typeof entry === 'object' && !entry.host) entry.host = bindHost; +} config.account = account; diff --git a/src/server/ssb_metadata.js b/src/server/ssb_metadata.js index 37ca6c1..a25dd62 100644 --- a/src/server/ssb_metadata.js +++ b/src/server/ssb_metadata.js @@ -5,6 +5,23 @@ const pkg = require('./package.json'); const config = require('./ssb_config'); const updater = require('../backend/updater.js'); +const formatMB = (bytes, decimals = 0) => `${(bytes / (1024 * 1024)).toFixed(decimals)} MB`; + +const blobUsageOnDisk = (ssbPath) => { + const out = { bytes: 0, files: 0 }; + const walk = (dir) => { + let entries = []; + try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; } + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) walk(full); + else if (e.isFile()) { try { out.bytes += fs.statSync(full).size; out.files += 1; } catch (_) {} } + } + }; + if (ssbPath) walk(path.join(ssbPath, 'blobs', 'sha256')); + return out; +}; + let printed = false; let checkedForUpdate = false; let pendingClearnetModules = null; @@ -90,40 +107,90 @@ async function printMetadata(mode, modeColor = colors.cyan, httpPort = 3000, htt const httpUrl = hasHttp ? `http://${httpHost}:${httpPort}` : ''; const oscLink = hasHttp ? `\x1b]8;;${httpUrl}\x07${httpUrl}\x1b]8;;\x07` : ''; const ssbPort = config.connections?.incoming?.net?.[0]?.port || config.port || 8008; - const localDiscovery = config.local === true; + const localDiscovery = (() => { try { return require('../configs/config-manager.js').getConfig().lanBroadcasting !== false; } catch (_) { return false; } })(); const hops = config.conn?.hops ?? config.friends?.hops ?? 2; console.log("========================="); console.log(`Running mode: ${modeColor}${mode}${colors.reset}`); console.log("========================="); + const walletId = (() => { + try { + const w = (require('../configs/config-manager.js').getConfig() || {}).wallet || {}; + if (!(String(w.url || '').trim() && String(w.user || '').trim() && String(w.pass || '').trim())) return ''; + const file = process.env.OASIS_BANKING_DIR ? path.join(process.env.OASIS_BANKING_DIR, 'wallet-addresses.json') : require('../configs/state-manager').statePath('wallet-addresses.json'); + const map = JSON.parse(fs.readFileSync(file, 'utf8')); + const v = map[`@${publicKey}`]; + const addr = typeof v === 'string' ? v : (v && v.address) || ''; + return /^E[1-9A-HJ-NP-Za-km-z]{32,34}$/.test(addr) ? addr : ''; + } catch (_) { return ''; } + })(); + console.log(`- OASIS ID: [ ${colors.orange}@${publicKey}${colors.reset} ]`); console.log(`- Package: ${colors.blue}${name} ${colors.yellow}[Version: ${version}]${colors.reset}`); - if (hasHttp) console.log(`- URL: ${colors.cyan}${oscLink}${colors.reset}`); - console.log(`- Oasis ID: [ ${colors.orange}@${publicKey}${colors.reset} ]`); + if (hasHttp) console.log(`- GUI: ${colors.cyan}${oscLink}${colors.reset}`); + const phoneEnabled = (() => { + try { return ((require('../configs/config-manager.js').getConfig() || {}).modules || {}).phoneMod !== 'off'; } catch (_) { return false; } + })(); + const phoneNumber = phoneEnabled ? (() => { try { return require('../models/phone_number').phoneNumberOf(`@${publicKey}`); } catch (_) { return null; } })() : null; + console.log(`- VoIP ID: ${phoneEnabled ? (phoneNumber ? `[ ${colors.orange}${phoneNumber}${colors.reset} ]` : 'enabled') : 'disabled'}`); + console.log(walletId ? `- ECOin ID: [ ${colors.orange}${walletId}${colors.reset} ]` : `- ECOin ID: disabled`); console.log("- Logging Level:", logLevel); + console.log("- Engine: db2-legacy"); const ifaces = os.networkInterfaces(); const isOnline = Object.values(ifaces).some(list => list && list.some(i => !i.internal && i.family === 'IPv4') ); console.log(`- Protocol (port): ${ssbPort}`); - console.log(`- Mode: ${isOnline ? 'online' : 'offline'}`); + const networkPaused = (() => { try { return require('../configs/config-manager.js').getConfig().networkPaused === true || process.env.OASIS_NETWORK_PAUSED === '1'; } catch (_) { return false; } })(); + console.log(`- Mode: ${networkPaused ? 'paused' : (isOnline ? 'online' : 'offline')}`); console.log(`- Replication (hops): ${hops}`); + const oasisCfg = (() => { try { return require('../configs/config-manager.js').getConfig() || {}; } catch (_) { return {}; } })(); + console.log(`- Blockchain backlog: ${Number(oasisCfg.ssbLogStream && oasisCfg.ssbLogStream.limit) || 1000} blocks`); + console.log(`- Blob size limit: ${formatMB(Number(config.blobs && config.blobs.max) || 0)}`); + const cacheMb = Number(oasisCfg.blobCache && (isPublic ? oasisCfg.blobCache.pubMaxMB : oasisCfg.blobCache.maxMB)) || 0; + const cacheUsage = blobUsageOnDisk(config.path); + console.log(`- Media cache limit: ${cacheMb > 0 ? formatMB(cacheMb * 1024 * 1024) : 'Unlimited'}`); + console.log(`- Current usage: ${formatMB(cacheUsage.bytes, 1)} · ${cacheUsage.files} files`); console.log(`- LAN Broadcasting (UDP): ${localDiscovery ? 'enabled' : 'disabled'}`); const clearnetModules = await waitForClearnet(); const clearnetStatus = (clearnetModules && clearnetModules.length > 0) ? clearnetModules.join(', ') : 'disabled'; - let fediverseConnected = false; - try { - const acc = JSON.parse(fs.readFileSync(path.join(__dirname, '../configs/fediverse-accounts.json'), 'utf8')); - fediverseConnected = !!(acc && acc.mastodon); - } catch (_) {} + const multiverse = (() => { + if ((oasisCfg.modules || {}).fediverseMod === 'off') return []; + try { + const acc = JSON.parse(fs.readFileSync(require('../configs/state-manager').statePath('fediverse-accounts.json'), 'utf8')) || {}; + return [ + acc.mastodon && acc.mastodon.token ? 'Mastodon' : null, + acc.peertube && acc.peertube.token ? 'PeerTube' : null, + acc.telegram && acc.telegram.session ? 'Telegram' : null + ].filter(Boolean); + } catch (_) { return []; } + })(); console.log(`- Internet Broadcasting:`); console.log(` - Clearnet: ${clearnetStatus}`); - console.log(` - Fediverse: ${fediverseConnected ? 'enabled' : 'disabled'}`); + console.log(` - Multiverse: ${multiverse.length ? multiverse.join(', ') : 'disabled'}`); + if (hasHttp) { + const en = (() => { try { return require('../client/assets/translations/i18n').en || {}; } catch (_) { return {}; } })(); + const uxNames = { blocks: en.uxModeMenus, ainav: en.uxModeAINav, chats: en.uxModeChats, feed: en.uxModeFeed, phone: en.phoneTitle }; + const wishNames = { whole: en.settingsWishWhole, mutuals: en.settingsWishMutuals, 'only-lan': en.settingsWishOnlyLan, local: en.settingsWishLocal }; + const workflow = (() => { + try { + const wf = require('../models/workflows_model'); + const key = wf.currentWorkflow(oasisCfg); + if (key) return en[`workflow_${key}`] || key; + const mods = oasisCfg.modules || {}; + return wf.ALL_MODULES.every(m => mods[`${m}Mod`] === 'on') ? (en.welcomeWorkflow_full || 'Default') : 'custom'; + } catch (_) { return 'custom'; } + })(); + const ux = (oasisCfg.ux && oasisCfg.ux.current) || 'blocks'; + const wish = oasisCfg.wish || 'whole'; + console.log(`- Workflow: ${workflow}`); + console.log(`- UX mode: ${uxNames[ux] || ux}`); + console.log(`- Wish: ${wishNames[wish] || wish}`); + } console.log(""); console.log("========================="); console.log("Modules loaded: [", modules.length, "]"); console.log("========================="); - // Check for updates await checkForUpdate(); console.log("========================="); diff --git a/src/views/AI_view.js b/src/views/AI_view.js index 1529237..3d0bb6f 100644 --- a/src/views/AI_view.js +++ b/src/views/AI_view.js @@ -1,95 +1,102 @@ -const { div, h2, p, section, button, form, textarea, br, span, input, label, select, option } = require("../server/node_modules/hyperaxe"); -const { template, i18n } = require('./main_views'); -const { renderUrl } = require('../backend/renderUrl'); +const { div, h2, p, section, button, form, textarea, span, input, br } = require("../server/node_modules/hyperaxe"); +const moment = require("../server/node_modules/moment"); +const { template, i18n, userLinkLabel } = require('./main_views'); +const config = require('../server/ssb_config'); +const { renderStyledText } = require('../backend/renderStyledText'); -exports.aiView = (history = [], userPrompt = '') => { +const statusBanner = (status) => { + if (!status) return null; + if (status.installed === false || /model_missing/.test(String(status.error || ''))) { + return div({ class: 'ai-status ai-status-missing' }, i18n.aiStatusMissingModel); + } + if (status.error && !/model_missing/.test(String(status.error))) { + return div({ class: 'ai-status ai-status-loading' }, `${i18n.aiServerError} (${status.error})`); + } + return null; +}; + +const renderAnswerText = (text) => { + const lines = String(text || '').split('\n').map(l => l.trim()).filter(Boolean); + return lines.flatMap((line, i) => (i > 0 ? [br()] : []).concat(renderStyledText(line))); +}; + +const myNick = () => { try { return userLinkLabel(config.keys.id); } catch (_) { return '@me'; } }; + +const ratingRow = (entry) => { + if (entry.source !== 'model') return null; + const rating = Math.max(0, Math.min(5, Number(entry.rating) || 0)); + if (entry.trainStatus === 'approved') { + return div({ class: 'ai-rating ai-rating-done', title: i18n.aiTrainApproved }, + span({ class: 'ai-stars-static' }, '★'.repeat(rating || 5) + '☆'.repeat(5 - (rating || 5))), + span({ class: 'ai-rating-label' }, i18n.aiTrainApproved) + ); + } + return form({ method: 'POST', action: '/ai/approve', class: 'ai-rating ai-stars', title: i18n.aiApproveTrain }, + input({ type: 'hidden', name: 'ts', value: String(entry.timestamp) }), + ...[5, 4, 3, 2, 1].map(n => button({ type: 'submit', name: 'rating', value: String(n), class: 'ai-star-btn', title: `${n} ★` }, '★')) + ); +}; + +const SPLIT_MARKER = ''; + +const stateChip = (kind) => span({ class: `pm-exposition-chip ai-chip ai-chip-${kind}` }, + span({ class: 'pm-exposition-icon' }, kind === 'thinking' ? '⏳' : '🤖'), + span({ class: 'pm-exposition-text' }, kind === 'thinking' ? i18n.aiThinking : i18n.aiWaiting) +); + + +const renderEntry = (entry, { split = false } = {}) => { + const time = entry.timestamp ? moment(entry.timestamp).format("YYYY/MM/DD HH:mm") : ''; + const thinking = entry.trainStatus === 'thinking'; + return [ + div({ class: 'chat-bubble-row ai-row' }, + div({ class: 'chat-message ai-bubble-user' }, + span({ class: 'chat-bubble-sender' }, myNick()), + div({ class: 'chat-message-text' }, ...renderStyledText(String(entry.question || ''))), + span({ class: 'chat-bubble-time' }, time) + ) + ), + thinking ? stateChip('thinking') : null, + split ? span({ id: 'oasis-ai-split' }) : null, + thinking ? null : div({ class: 'chat-bubble-row chat-bubble-row-self ai-row' }, + div({ class: `chat-message chat-message-self ai-bubble-42 ai-bubble-${entry.source || 'model'}` }, + span({ class: 'chat-bubble-sender chat-bubble-sender-owner' }, '🤖 42'), + div({ class: 'chat-message-text' }, ...renderAnswerText(entry.answer)), + span({ class: 'chat-bubble-time' }, time), + ratingRow(entry) + ) + ) + ]; +}; + +exports.aiView = (history = [], userPrompt = '', { status = null, split = false } = {}) => { + const ordered = [...history].reverse(); + const thinking = ordered.length ? ordered[ordered.length - 1].trainStatus === 'thinking' : false; return template( i18n.aiTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.aiTitle), - p(i18n.aiDescription), - userPrompt ? div({ class: 'user-prompt' }, - `${i18n.aiPromptUsed || 'System Prompt'}: `, - span({ class: 'user-prompt-text' }, `"${userPrompt}"`) - ) : null, - form({ method: 'POST', action: '/ai', class: 'ai-prompt-form' }, - textarea({ name: 'input', rows: 4, placeholder: i18n.aiInputPlaceholder, required: true }), - br(), + p(i18n.aiDescription) + ), + statusBanner(status), + div({ class: 'chat-workspace ai-workspace' }, + ordered.length ? div({ class: 'chat-messages-list ai-messages-list' }, ...ordered.flatMap((e, i) => renderEntry(e, { split: split && i === ordered.length - 1 }))) : null, + thinking ? null : div({ class: 'chat-message-form ai-message-form' }, + form({ id: 'ai-prompt-form', method: 'POST', action: '/ai', class: 'ai-prompt-form' }, + textarea({ maxlength: "5000", name: 'input', rows: 3, placeholder: i18n.aiInputPlaceholder, required: true }) + ), div({ class: 'ai-submit-row' }, - button({ type: 'submit' }, i18n.aiSubmitButton) - ) - ), - div({ class: 'ai-clear-row' }, - form({ method: 'POST', action: '/ai/clear', class: 'ai-clear-form' }, - button({ type: 'submit', class: 'ai-clear-btn' }, i18n.aiClearHistory) - ) - ), - br(), - ...history.map(entry => - div({ class: 'chat-entry' }, - entry.timestamp - ? span({ class: 'chat-entry-timestamp' }, new Date(entry.timestamp).toLocaleString()) - : null, - br(), br(), - div({ class: 'user-question' }, - h2(`${i18n.aiUserQuestion}:`), - p(...renderUrl(entry.question)) - ), - div({ class: 'ai-response' }, - h2(`${i18n.aiResponseTitle}:`), - ...String(entry.answer || '') - .split('\n\n') - .flatMap(paragraph => - paragraph - .split('\n') - .map(line => - p(...renderUrl(line.trim())) - ) - ) - ), - div({ class: 'ai-train-bar' }, - Array.isArray(entry.snippets) && entry.snippets.length - ? span({ class: 'ai-snippets-used' }, `${i18n.aiSnippetsUsed}: ${entry.snippets.length}`) - : null, - h2(`${i18n.statsAITraining}:`), - entry.trainStatus === 'approved' - ? span({ class: 'ai-train-approved' }, i18n.aiTrainApproved) - : entry.trainStatus === 'rejected' - ? span({ class: 'ai-train-rejected' }, i18n.aiTrainRejected) - : null, - entry.trainStatus === 'approved' || entry.trainStatus === 'rejected' - ? null - : div({ class: 'ai-approve-block' }, - form({ method: 'POST', action: '/ai/approve', class: 'ai-approve-form' }, - input({ type: 'hidden', name: 'ts', value: String(entry.timestamp) }), - div({ class: 'ai-approve-meta' }, - label({ class: 'ai-approve-meta-label' }, i18n.aiApproveTagsLabel || 'Tags (comma-separated)'), - input({ type: 'text', name: 'tags', placeholder: i18n.aiApproveTagsPlaceholder || 'e.g. oasis, governance, ecology', maxlength: '160' }), - label({ class: 'ai-approve-meta-label' }, i18n.aiApproveRatingLabel || 'Rating'), - select({ name: 'rating' }, - option({ value: '0' }, '—'), - option({ value: '1' }, '★'), - option({ value: '2' }, '★★'), - option({ value: '3' }, '★★★'), - option({ value: '4' }, '★★★★'), - option({ value: '5' }, '★★★★★') - ) - ), - textarea({ name: 'custom', rows: 3, placeholder: i18n.aiCustomAnswerPlaceholder, class: 'ai-approve-custom' }), - div({ class: 'ai-approve-actions' }, - button({ type: 'submit', class: 'approve-btn' }, i18n.aiApproveTrain) - ) - ), - form({ method: 'POST', action: '/ai/reject', class: 'ai-approve-reject' }, - input({ type: 'hidden', name: 'ts', value: String(entry.timestamp) }), - button({ type: 'submit', class: 'reject-btn' }, i18n.aiRejectTrain) - ) - ) - ) + stateChip('waiting'), + button({ type: 'submit', form: 'ai-prompt-form' }, i18n.aiSubmitButton), + ordered.length ? form({ method: 'POST', action: '/ai/clear', class: 'ai-clear-form' }, + button({ type: 'submit', class: 'ai-clear-btn' }, i18n.aiClearHistory) + ) : null ) ) ) ) ); }; + +exports.SPLIT_MARKER = SPLIT_MARKER; diff --git a/src/views/activity_view.js b/src/views/activity_view.js index f922d5c..04a92a0 100644 --- a/src/views/activity_view.js +++ b/src/views/activity_view.js @@ -1,35 +1,52 @@ const { div, h2, p, section, button, form, a, input, img, textarea, br, span, video: videoHyperaxe, audio: audioHyperaxe, table, tr, td, th, details, summary } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, userLinkLabel, renderSpreadButton, renderContentActions } = require('./main_views'); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const { template, i18n, paged, userLink, userLinkLabel, renderStateChip, renderPhoneChip, renderSpreadButton, renderContentActions, renderVotesSummary, renderModuleStats, renderCardMetaRow, renderTorrentDownload, renderTorrentSourceDownload, renderFileDownloads, torrentDownloadHref, CONTENT_FAV_KIND, CONTENT_SPREADABLE, CONTENT_TORRENTABLE, contentDeleteAction } = require('./main_views'); const opinionCategories = require('../backend/opinion_categories'); +const { roomNumberOf } = require('../models/phone_number'); -const OPINION_TYPES = new Set(['bookmark','votes','feed','image','audio','video','document','torrent']); +const OPINION_TYPES = new Set(['bookmark','votes','feed','image','audio','video','document','torrent','file']); +const TORRENTABLE_TYPES = CONTENT_TORRENTABLE; const OPINION_ROUTES = { feed: (id) => `/feed/opinions/${encodeURIComponent(id)}`, bookmark: (id) => `/bookmarks/opinions/${encodeURIComponent(id)}`, image: (id) => `/images/opinions/${encodeURIComponent(id)}`, audio: (id) => `/audios/opinions/${encodeURIComponent(id)}`, torrent: (id) => `/torrents/opinions/${encodeURIComponent(id)}`, + file: (id) => `/files/opinions/${encodeURIComponent(id)}`, video: (id) => `/videos/opinions/${encodeURIComponent(id)}`, document: (id) => `/documents/opinions/${encodeURIComponent(id)}`, votes: (id) => `/votes/opinions/${encodeURIComponent(id)}` }; const moment = require("../server/node_modules/moment"); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledText, escapeHtml, safeExternalHref } = require('../backend/renderStyledText'); const { letterOf } = require('./polls_view'); const { getConfig } = require("../configs/config-manager.js"); const { sanitizeHtml } = require('../backend/sanitizeHtml'); +const { renderZoomableImage } = require('./gallery_view'); +const { renderTribeOriginChip } = require('./clearnet_view'); + +const MEDIA_MD_RE = /!?\[(?:image|video|audio|pdf|torrent)[^\]]*\]\(\s*&[^)\s]+\s*\)/g +const stripMediaMarkdown = (text) => String(text || '').replace(MEDIA_MD_RE, '').replace(/\n{3,}/g, '\n\n').trim() +const renderMediaObject = (m, href = null) => { + if (!m || typeof m !== 'object' || !m.blobId) return null + const src = `/blob/${encodeURIComponent(String(m.blobId))}` + if (href && m.kind !== 'video' && m.kind !== 'audio') return div({ class: 'card-field image-container media-left' }, a({ href, class: 'podcast-cover-link' }, img({ src, class: 'post-image', alt: '' }))) + if (m.kind === 'video') return div({ class: 'card-field video-container' }, videoHyperaxe({ controls: true, class: 'post-video', src, preload: 'metadata' })) + if (m.kind === 'audio') return div({ class: 'card-field audio-container' }, audioHyperaxe({ controls: true, class: 'post-audio', src, preload: 'metadata' })) + return div({ class: 'card-field image-container media-left' }, renderZoomableImage(src, { imgClass: 'post-image' })) +} const renderMediaBlob = (value, fallbackSrc = null) => { if (!value) return fallbackSrc ? img({ src: fallbackSrc, class: 'post-image' }) : null const s = String(value).trim() if (!s) return fallbackSrc ? img({ src: fallbackSrc, class: 'post-image' }) : null - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, class: 'post-image' }) + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: 'post-image' }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return videoHyperaxe({ controls: true, class: 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return videoHyperaxe({ controls: true, class: 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) if (mAudio) return audioHyperaxe({ controls: true, class: 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: 'post-image' }) + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: 'post-image' }) return fallbackSrc ? img({ src: fallbackSrc, class: 'post-image' }) : null } @@ -41,7 +58,7 @@ function cleanFeedText(t) { } function isValidFeedText(t) { - const s = cleanFeedText(t); + const s = cleanFeedText(t).replace(/!?\[[^\]\n]*\]\(\s*&[^)\s]+\.sha256\s*\)/g, '').trim(); return s.length >= FEED_TEXT_MIN && s.length <= FEED_TEXT_MAX; } @@ -109,25 +126,7 @@ const decodeMaybe = (s) => { try { return decodeURIComponent(String(s || '')); } catch { return String(s || ''); } }; -const rewriteHashtagLinks = (html) => { - const s = String(html || ''); - return s.replace(/href=(["'])(?:https?:\/\/[^"']+)?\/hashtag\/([^"'?#]+)([^"']*)\1/g, (m, q, tag, rest) => { - const t = decodeMaybe(tag).replace(/^#/, '').trim().toLowerCase(); - const href = `/search?query=%23${encodeURIComponent(t)}`; - return `href=${q}${href}${q}`; - }); -}; -function renderUrlPreserveNewlines(text) { - const s = String(text || ''); - const lines = s.split(/\r\n|\r|\n/); - const out = []; - for (let i = 0; i < lines.length; i++) { - if (i) out.push(br()); - out.push(...renderUrl(lines[i])); - } - return out; -} function getThreadIdFromPost(action) { const c = action.value?.content || action.content || {}; @@ -199,6 +198,8 @@ function buildActivityItemsWithPostThreads(deduped, allActions) { .slice() .sort((a, b) => (a.ts || 0) - (b.ts || 0)); + if (!rootAction) continue; + const rootType = rootAction.type || 'post'; out.push({ id: `thread:${threadId}`, type: 'postThread', @@ -206,18 +207,21 @@ function buildActivityItemsWithPostThreads(deduped, allActions) { ts: latest.ts, content: { threadId, + rootType, + rootHref: rootType === 'post' ? `/blogs/${encodeURIComponent(threadId)}` : getViewDetailsAction(rootType, rootAction), root: rootAction ? { id: safeMsgId(rootAction), author: rootAction.author, - text: excerptPostText(rootAction.value?.content || rootAction.content || {}, 240) + subject: String((rootAction.value?.content || rootAction.content || {}).contentWarning || '').trim(), + text: excerptPostText(rootAction.value?.content || rootAction.content || {}, 600) } : null, replies: replies.map(p => ({ id: safeMsgId(p), author: p.author, ts: p.ts, - text: excerptPostText(p.value?.content || p.content || {}, 200) + text: excerptPostText(p.value?.content || p.content || {}, 600) })) } }); @@ -228,12 +232,48 @@ function buildActivityItemsWithPostThreads(deduped, allActions) { } exports.renderActionCards = renderActionCards; -const SPREADABLE_TYPES = new Set([ - 'post', 'audio', 'video', 'image', 'document', 'torrent', 'bookmark', - 'event', 'calendar', 'task', 'votes', 'vote', 'market', 'shop', 'shopProduct', - 'project', 'transfer', 'housing', 'job', 'report', 'industry', 'industryBuild', 'industryBlueprint', - 'chat', 'chatMessage', 'pad', 'padEntry', 'forum', 'map', 'poll', 'blog' -]); +const SPREADABLE_TYPES = CONTENT_SPREADABLE; + +const TRIBE_OPENED_TYPE = { event: 'typeEvent', task: 'typeTask', votation: 'typeVotes', forum: 'typeForum', feed: 'typeFeed', report: 'typeReport', image: 'typeImage', audio: 'typeAudio', video: 'typeVideo', document: 'typeDocument', bookmark: 'typeBookmark', torrent: 'typeTorrent' }; +const TRIBE_OPENED_FILTER = { event: 'event', task: 'task', votation: 'votes', forum: 'forum', feed: 'feed', report: 'report', image: 'image', audio: 'audio', video: 'video', document: 'document', bookmark: 'bookmark', torrent: 'torrent' }; +const tribeOpenedKey = (action) => { + const c = (action && action.content) || {}; + return c.contentType === 'media' ? String(c.mediaType || '') : String(c.contentType || ''); +}; +const openedIn = (action, filter) => !!action && action.type === 'tribeOpened' && TRIBE_OPENED_FILTER[tribeOpenedKey(action)] === filter; + +const renderTribeOpenedCard = (action, date, authorName) => { + const c = action.content || {}; + const origin = c.tribeOrigin || {}; + const key = tribeOpenedKey(action); + const label = String(i18n[TRIBE_OPENED_TYPE[key]] || key || '').toUpperCase(); + const blob = typeof c.image === 'string' && c.image.startsWith('&') ? `/blob/${encodeURIComponent(c.image)}` : null; + const media = !blob ? null + : c.mediaType === 'video' ? videoHyperaxe({ controls: true, class: 'post-video', src: blob, preload: 'metadata' }) + : c.mediaType === 'audio' ? audioHyperaxe({ controls: true, class: 'post-audio', src: blob }) + : c.mediaType === 'image' ? renderZoomableImage(blob, { imgClass: 'post-image' }) + : null; + const bookmarkHref = c.mediaType === 'bookmark' ? safeExternalHref(c.url) : ''; + return div({ class: 'trending-card' }, + div({ class: 'card-header activity-card-header' }, + div({ class: 'card-chips-row' }, + span({ class: 'pm-exposition-chip pm-exposition-whole' }, span({ class: 'pm-exposition-text' }, label)), + renderTribeOriginChip(origin) + ) + ), + div({ class: 'card-section' }, + c.title ? h2(origin.href ? a({ href: origin.href }, c.title) : c.title) : null, + c.description ? p({ class: 'post-text post-text-pre' }, ...renderStyledText(String(c.description).slice(0, 600))) : null, + c.date || c.location ? p({ class: 'post-text' }, [c.date, c.location].filter(Boolean).join(' · ')) : null, + media, + bookmarkHref ? a({ href: bookmarkHref, target: '_blank', rel: 'noopener noreferrer' }, c.url) : null + ), + p({ class: 'card-footer' }, + span({ class: 'date-link' }, `${date}`), + userLink(action.author, authorName) + ) + ); +}; function renderActionCards(actions, userId, allActions, spreadMap = new Map(), extras = {}) { const all = Array.isArray(allActions) ? allActions : actions; @@ -296,7 +336,8 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e } const seenDocumentTitles = new Set(); - const items = buildActivityItemsWithPostThreads(deduped, all); + const grouped = buildActivityItemsWithPostThreads(deduped, all); + const items = extras && extras.paged ? paged(grouped) : grouped; const spreadOrdinalById = new Map(); const spreadsByLink = new Map(); @@ -330,8 +371,9 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e } const cards = items.map(action => { - const date = action.ts ? new Date(action.ts).toLocaleString() : ""; + const date = action.ts ? moment(action.ts).format("YYYY/MM/DD HH:mm") : ""; const type = action.type || 'unknown'; + if (type === 'tribeOpened') return renderTribeOpenedCard(action, date, (action.authorNames && action.authorNames[action.author]) || getProfile(action.author).name); let skip = false; let headerText; if (type.startsWith('parliament')) { @@ -349,14 +391,18 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e headerText = `[${String(i18n.typeTask || 'TASK').toUpperCase()} · ASSIGNMENT]`; } else if (type === 'shopProduct') { headerText = `[SHOP · PRODUCT]`; - } else if (type === 'chat') { - headerText = `[CHAT \u00b7 ${String(i18n.chatThreadsLabel).toUpperCase()}]`; } else if (type === 'pad') { headerText = `[PAD · ${String(i18n.padNew || 'NEW').toUpperCase()}]`; - } else if (type === 'ubiClaim') { - headerText = `[UBI · CLAIM]`; - } else if (type === 'ubiclaimresult') { - headerText = `[UBI · RESULT]`; + } else if (type === 'wikiPage') { + headerText = `[${String(i18n.typeWiki || 'WIKI').toUpperCase()}]`; + } else if (type === 'emergencyUpdate') { + headerText = `[${String(i18n.typeEmergency || 'EMERGENCY').toUpperCase()} · ${String(i18n.emergencyLastUpdate || 'LAST UPDATE').toUpperCase()}]`; + } else if (type === 'campaignUpdate') { + headerText = `[${String(i18n.typeCampaign || 'CAMPAIGN').toUpperCase()} · ${String(i18n.emergencyLastUpdate || 'LAST UPDATE').toUpperCase()}]`; + } else if (type === 'podcastEpisode') { + headerText = `[${String(i18n.typePodcast || 'PODCAST').toUpperCase()} · ${String(i18n.typePodcastEpisode || 'EPISODE').toUpperCase()}]`; + } else if (type === 'larpHousePost') { + headerText = `[${String(i18n.typeLarp || 'L.A.R.P.').toUpperCase()}]`; } else { const typeLabel = i18n[`type${capitalize(type)}`] || type; headerText = `[${String(typeLabel).toUpperCase()}]`; @@ -378,7 +424,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const voteOutcome = (() => { const totalNum = Number(totalVotes || 0); - const noResult = { text: i18n.voteNoQuorum || 'NO QUORUM', color: '#ffcc00' }; + const noResult = { text: i18n.voteNoQuorum || 'NO QUORUM', tone: 'none' }; if (totalNum < 2) return noResult; const entries = Object.entries(votes || {}).filter(([o]) => o !== 'FOLLOW_MAJORITY'); const maxCount = entries.reduce((m, [, c]) => Math.max(m, Number(c) || 0), 0); @@ -386,7 +432,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e if (maxCount === 0 || top.length > 1) return noResult; const w = top[0][0]; const label = i18n['vote' + w.split('_').map(x => x.charAt(0) + x.slice(1).toLowerCase()).join('')] || w; - return { text: label, color: w === 'YES' ? '#4caf50' : w === 'NO' ? '#e53935' : '#ffcc00' }; + return { text: label, tone: w === 'YES' ? 'yes' : w === 'NO' ? 'no' : 'none' }; })(); cardBody.push( @@ -399,7 +445,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div( { class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), - span({ class: 'card-value' }, deadline ? new Date(deadline).toLocaleString() : '') + span({ class: 'card-value' }, deadline ? moment(deadline).format("YYYY/MM/DD HH:mm") : '') ), div( { class: 'card-field' }, @@ -409,7 +455,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div( { class: 'card-field' }, span({ class: 'card-label' }, (i18n.voteResults || 'Results') + ':'), - span({ class: 'card-value', style: `color:${voteOutcome.color};font-weight:bold;` }, voteOutcome.text) + span({ class: `card-value vote-outcome vote-outcome-${voteOutcome.tone}` }, voteOutcome.text) ), div( { class: 'card-field' }, @@ -430,7 +476,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div({ class: 'card-section transfer' }, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.concept + ':'), span({ class: 'card-value' }, concept)), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.amount + ':'), span({ class: 'card-value' }, amount)), - div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, deadline ? new Date(deadline).toLocaleString() : '')), + deadline ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, moment(deadline).format("YYYY/MM/DD HH:mm"))) : null, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.status + ':'), span({ class: 'card-value' }, status)) ) ); @@ -448,90 +494,8 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ); } - if (type === 'bankClaim') { - const { amount, epochId, allocationId, txid } = content; - const amt = Number(amount || 0); - cardBody.push( - div({ class: 'card-section banking-claim' }, - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiReceived + ':' ), - span({ class: 'card-value' }, `${amt.toFixed(6)} ECO`) - ), - epochId ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankEpochShort + ':' ), - span({ class: 'card-value' }, epochId) - ) : "", - allocationId ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankAllocId + ':' ), - span({ class: 'card-value' }, allocationId) - ) : "", - txid ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankTx + ':' ), - a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${txid}`, target: '_blank' }, txid) - ) : "" - ) - ); - } - if (type === 'ubiClaim') { - const { pubId, amount, epochId, claimedAt } = content; - const amt = Number(amount || 0); - const inhabitantId = action.author || ''; - cardBody.push( - div({ class: 'card-section banking-ubi' }, - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiInhabitant + ':'), - span({ class: 'card-value' }, userLink(inhabitantId)) - ), - pubId ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiPub + ':'), - span({ class: 'card-value' }, userLink(pubId)) - ) : "", - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiClaimedAmount + ':'), - span({ class: 'card-value' }, `${amt.toFixed(6)} ECO`) - ), - epochId ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankEpochShort + ':'), - span({ class: 'card-value' }, epochId) - ) : "", - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.status + ':'), - span({ class: 'card-value' }, 'UNCONFIRMED') - ), - claimedAt ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.date + ':'), - span({ class: 'card-value' }, moment(claimedAt).format('YYYY-MM-DD HH:mm:ss')) - ) : "" - ) - ); - } - if (type === 'ubiclaimresult') { - const { txid, userId: inhabitantId, amount, epochId } = content; - const pubAuthor = action.author || action.value?.author || ''; - const amt = Number(amount || 0); - cardBody.push( - div({ class: 'card-section banking-ubi' }, - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiPub + ':'), - span({ class: 'card-value' }, pubAuthor) - ), - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiInhabitant + ':'), - span({ class: 'card-value' }, inhabitantId || '') - ), - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankUbiClaimedAmount + ':'), - span({ class: 'card-value' }, `${amt.toFixed(6)} ECO`) - ), - txid ? div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.bankTx + ':'), - a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${txid}`, target: '_blank' }, txid) - ) : "" - ) - ); - } if (type === 'pixelia') { const { author } = content; @@ -552,7 +516,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e img({ src: `/assets/larp/images/${encodeURIComponent(String(house).toLowerCase())}.jpg`, alt: house, class: 'larp-house-mini' }), span(`L.A.R.P · ${String(house).toUpperCase()}`) ) : null, - text ? p({ class: 'post-text post-text-pre' }, ...renderUrlPreserveNewlines(String(text))) : null + text ? p({ class: 'post-text post-text-pre' }, ...renderStyledText(String(text))) : null ) ); } @@ -567,10 +531,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ), image ? (/^(\/|https?:)/.test(String(image)) - ? img({ src: image, class: 'feed-image tribe-image' }) + ? renderZoomableImage(image, { imgClass: 'feed-image tribe-image' }) : renderMediaBlob(image, null)) : null, - p({ class: 'tribe-description' }, ...renderUrl(description || '')) + p({ class: 'tribe-description' }, ...renderStyledText(description || '')) ) ); } @@ -602,11 +566,11 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e photo ? [ br(), - img({ class: "cv-photo", src: `/blob/${encodeURIComponent(photo)}` }), + renderZoomableImage(`/blob/${encodeURIComponent(photo)}`, { imgClass: 'cv-photo', linkClass: 'zoom-inline' }), br() ] : "", - p(...renderUrl(description || "")), + p(...renderStyledText(description || "")), personalSkills && personalSkills.length ? div({ class: 'card-tags' }, personalSkills.map(skill => a({ href: `/search?query=%23${encodeURIComponent(skill)}`, class: "tag-link" }, `#${skill}`) @@ -631,7 +595,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const { url } = content; cardBody.push( div({ class: 'card-section image' }, - img({ src: `/blob/${encodeURIComponent(url)}`, class: 'post-image' }) + renderZoomableImage(`/blob/${encodeURIComponent(url)}`, { imgClass: 'post-image' }) ) ); } @@ -641,7 +605,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const mapKey = action.id || action.key || ''; cardBody.push( div({ class: 'card-section map' }, - title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.mapTitle || 'Map') + ':'), span({ class: 'card-value' }, mapKey ? a({ href: `/maps/${encodeURIComponent(mapKey)}`, class: 'user-link' }, title) : title)) : "", + title ? div({ class: 'card-field' }, span({ class: 'card-value' }, mapKey ? a({ href: `/maps/${encodeURIComponent(mapKey)}`, class: 'user-link' }, title) : title)) : "", div({ class: 'map-card-info' }, span({ class: 'map-type-badge' }, mapType || 'SINGLE'), span({ class: 'map-coords' }, `${(parseFloat(lat) || 0).toFixed(4)}, ${(parseFloat(lng) || 0).toFixed(4)}`) @@ -702,17 +666,28 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e } if (type === 'torrent') { - const { title } = content; + const { title, url, source, sourceName } = content; cardBody.push( div({ class: 'card-section' }, - title ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.torrentTitleLabel || 'Title') + ':'), - span({ class: 'card-value' }, title) + title?.trim() ? h2({ class: 'torrent-title' }, title) : "", + url ? div({ class: 'torrent-card-actions' }, + renderTorrentSourceDownload(action.tipId || action.id || action.key, source), + renderTorrentDownload(torrentDownloadHref(url, title)) ) : null ) ); } + if (type === 'file') { + const { title, url, fileName, torrentUrl } = content; + cardBody.push( + div({ class: 'card-section' }, + title?.trim() ? h2({ class: 'torrent-title' }, title) : "", + url ? renderFileDownloads(action.tipId || action.id || action.key, torrentUrl, fileName || title) : null + ) + ); + } + if (type === 'document') { const { url, title, key } = content; const docKey = title ? `${action.author || ''}::${title.trim()}` : null; @@ -736,7 +711,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const { url } = content; cardBody.push( div({ class: 'card-section bookmark' }, - h2(url ? p(a({ href: url, target: '_blank', class: "bookmark-url" }, url)) : "") + url ? div({ class: 'card-field' }, a({ href: safeExternalHref(url), target: '_blank', rel: 'noreferrer noopener', class: 'bookmark-url' }, url)) : '' ) ); } @@ -746,10 +721,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e cardBody.push( div({ class: 'card-section event' }, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.title + ':'), span({ class: 'card-value' }, title)), - date ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.date + ':'), span({ class: 'card-value' }, new Date(date).toLocaleString())) : "", + date ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.date + ':'), span({ class: 'card-value' }, moment(date).format("YYYY/MM/DD HH:mm"))) : "", location ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.location || 'Location') + ':'), span({ class: 'card-value' }, location)) : "", typeof isPublic === 'boolean' ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.isPublic || 'Public') + ':'), span({ class: 'card-value' }, isPublic ? 'Yes' : 'No')) : "", - price ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.price || 'Price') + ':'), span({ class: 'card-value' }, price + " ECO")) : "", + Number(price) > 0 ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.price || 'Price') + ':'), span({ class: 'card-value' }, price + " ECO")) : "", br(), Array.isArray(attendees) ? h2({ class: 'card-label' }, (i18n.attendees || 'Attendees') + ': ' + attendees.length) : "" ) @@ -762,8 +737,8 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div({ class: 'card-section task' }, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.title + ':'), span({ class: 'card-value' }, title)), priority ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.priority || 'Priority') + ':'), span({ class: 'card-value' }, priority)) : "", - startTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.taskStartTimeLabel || 'Start') + ':'), span({ class: 'card-value' }, new Date(startTime).toLocaleString())) : "", - endTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.taskEndTimeLabel || 'End') + ':'), span({ class: 'card-value' }, new Date(endTime).toLocaleString())) : "", + startTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.taskStartTimeLabel || 'Start') + ':'), span({ class: 'card-value' }, moment(startTime).format("YYYY/MM/DD HH:mm"))) : "", + endTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.taskEndTimeLabel || 'End') + ':'), span({ class: 'card-value' }, moment(endTime).format("YYYY/MM/DD HH:mm"))) : "", status ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.status + ':'), span({ class: 'card-value' }, status)) : "" ) ); @@ -798,15 +773,15 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e } if (type === 'feed') { - const { renderTextWithStyles } = require('../backend/renderTextWithStyles'); + const { renderStyledHtml } = require('../backend/renderStyledText'); const { text, refeeds } = content; if (!isValidFeedText(text)) return null; const safeText = cleanFeedText(text); - const htmlText = safeText ? rewriteHashtagLinks(renderTextWithStyles(safeText)) : ''; + const textNodes = safeText ? renderStyledText(safeText, { zoomImages: true }) : []; const refeedsNum = Number(refeeds || 0) || 0; cardBody.push( div({ class: 'card-section feed' }, - div({ class: 'feed-text', innerHTML: sanitizeHtml(htmlText) }), + div({ class: 'feed-text' }, ...textNodes), refeedsNum > 0 ? h2({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeFeedRefeeds + ': '), @@ -820,7 +795,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e if (type === 'post') { const { contentWarning, text } = content || {}; const rawText = text || ''; - const POST_TRUNCATE_LEN = 300; + const POST_TRUNCATE_LEN = 1000; const isTruncated = rawText.length > POST_TRUNCATE_LEN; const displayText = isTruncated ? rawText.slice(0, POST_TRUNCATE_LEN) + '…' : rawText; const isHtml = typeof displayText === 'string' && /<\/?[a-z][\s\S]*>/i.test(displayText); @@ -834,9 +809,11 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e (url) => `${url}` ); - bodyNode = div({ class: 'post-text post-text-clamped', innerHTML: sanitizeHtml(linkified) }); + bodyNode = div({ class: 'feed-text post-text post-text-clamped', [RAW_HTML]: sanitizeHtml(linkified) }); } else { - bodyNode = p({ class: 'post-text post-text-pre post-text-clamped' }, ...renderUrlPreserveNewlines(displayText)); + bodyNode = div({ class: 'feed-text' }, + p({ class: 'post-text post-text-pre post-text-clamped' }, ...renderStyledText(displayText)) + ); } const threadId = getThreadIdFromPost(action); const replyToId = getReplyToIdFromPost(action, byIdAll); @@ -856,16 +833,11 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e a({ href: ctxHref, class: 'tag-link' }, i18n.inReplyTo || 'IN REPLY TO'), parentAuthor ? span(' ', userLink(parentAuthor, parentName)) : '' ), - parentText ? p({ class: 'post-text reply-context-text post-text-pre' }, ...renderUrlPreserveNewlines(parentText)) : '' + parentText ? p({ class: 'post-text reply-context-text post-text-pre' }, ...renderStyledText(parentText)) : '' ) : '', contentWarning ? h2({ class: 'content-warning' }, contentWarning) : '', - bodyNode, - isTruncated && threadId - ? div({ class: 'card-section-action' }, - a({ href: `/thread/${encodeURIComponent(threadId)}#${encodeURIComponent(action.id || threadId)}`, class: 'filter-btn' }, i18n.keepReading || 'Keep reading...') - ) - : '' + bodyNode ) ); } @@ -873,53 +845,52 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e if (type === 'postThread') { const c = action.content || {}; const threadId = c.threadId; - const href = `/thread/${encodeURIComponent(threadId)}#${encodeURIComponent(threadId)}`; + const rootType = c.rootType || 'post'; + const isBlogThread = rootType === 'post'; + const baseHref = c.rootHref || `/blogs/${encodeURIComponent(threadId)}`; + const href = isBlogThread ? baseHref : `${baseHref}${baseHref.includes('?') ? '&' : '?'}comments=open#comments-latest`; + const rootTypeLabel = isBlogThread + ? (i18n.typePost || 'BLOG') + : (i18n[`type${rootType.charAt(0).toUpperCase()}${rootType.slice(1)}`] || rootType); const root = c.root; const replies = Array.isArray(c.replies) ? c.replies : []; const repliesAsc = replies.slice().sort((a, b) => (a.ts || 0) - (b.ts || 0)); - const limit = 5; - const overflow = repliesAsc.length > limit; - const show = repliesAsc.slice(Math.max(0, repliesAsc.length - limit)); - const lastId = repliesAsc.length ? repliesAsc[repliesAsc.length - 1].id : threadId; - const viewMoreHref = `/thread/${encodeURIComponent(threadId)}#${encodeURIComponent(lastId)}`; + const latest = repliesAsc.length ? repliesAsc[repliesAsc.length - 1] : null; + const titleSource = root ? (root.subject || root.text || '') : ''; + const titleText = titleSource + ? (titleSource.length > 90 ? `${titleSource.slice(0, 90)}…` : titleSource) + : threadId; return div({ class: 'trending-card post-thread' + (String(action.author) === String(userId) ? ' own-content' : '') }, div({ class: 'card-header activity-card-header' }, div({ class: 'card-chips-row' }, span({ class: 'pm-exposition-chip pm-exposition-whole' }, - span({ class: 'pm-exposition-text' }, `${String(i18n.typePost || 'POST').toUpperCase()} · THREAD`) + span({ class: 'pm-exposition-text' }, `${String(rootTypeLabel).toUpperCase()} · ${String(i18n.activityUpdateLabel || 'UPDATE').toUpperCase()}`) ) ), - renderContentActions(threadId, href, { - author: action.author, + renderContentActions(threadId, baseHref, { + author: c.rootAuthor || (root && root.author) || action.author, + reportTitle: titleText, spread: spreadMap.get(threadId) || null, - ...favOptsFor('post', threadId, extras) + ...favOptsFor(rootType, threadId, extras) }) ), div({ class: 'card-body' }, - root && root.text - ? div({ class: 'card-section' }, - p({ class: 'post-text post-text-pre' }, ...renderUrlPreserveNewlines(root.text)) - ) - : '', - div({ class: 'card-section' }, - show.map(r => { - const rDate = r.ts ? new Date(r.ts).toLocaleString() : ''; - return div({ class: 'thread-reply-item' }, - div({ class: 'thread-reply' }, - r.text ? p({ class: 'post-text post-text-pre' }, ...renderUrlPreserveNewlines(r.text)) : '' - ), - div({ class: 'card-footer thread-reply-footer' }, - span({ class: 'date-link' }, rDate), - userLink(r.author, action.authorNames && action.authorNames[r.author]) - ) - ); - }), + div({ class: 'card-section' }, + div({ class: 'card-field activity-update-title' }, + a({ href, class: 'card-value user-link' }, titleText), + span({ class: 'card-label activity-update-counts' }, `💬 ${replies.length}`) + ), + latest && latest.text + ? div({ class: 'feed-text activity-update-msg' }, + p({ class: 'post-text post-text-pre' }, ...renderStyledText(latest.text)) + ) + : '' + ) + ), + p({ class: 'card-footer' }, + span({ class: 'date-link' }, `${action.ts ? moment(action.ts).format("YYYY/MM/DD HH:mm") : ''}`), + userLink(action.author, action.authorNames && action.authorNames[action.author]) ) - ), - p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${action.ts ? new Date(action.ts).toLocaleString() : ''} ${i18n.performed} `), - userLink(action.author, action.authorNames && action.authorNames[action.author]) - ) ); } @@ -927,21 +898,21 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const c = action.content || {}; const chatRoot = c.chatRoot; const href = `/chats/${encodeURIComponent(chatRoot)}`; + const latestHref = `${href}#chat-latest`; const chatTitle = c.title || chatRoot; - const chatDesc = c.description || ''; const replies = Array.isArray(c.replies) ? c.replies : []; const repliesAsc = replies.slice().sort((a, b) => (a.ts || 0) - (b.ts || 0)); - const limit = 6; + const limit = 2; const show = repliesAsc.slice(Math.max(0, repliesAsc.length - limit)); return div({ class: 'trending-card post-thread chat-thread' + (String(action.author) === String(userId) ? ' own-content' : '') }, div({ class: 'card-header activity-card-header' }, div({ class: 'card-chips-row' }, span({ class: 'pm-exposition-chip pm-exposition-whole' }, - span({ class: 'pm-exposition-text' }, `${String(i18n.typeChat || 'CHAT').toUpperCase()} \u00b7 ${String(i18n.chatThreadsLabel).toUpperCase()}`) + span({ class: 'pm-exposition-text' }, `${String(i18n.typeChat || 'CHAT').toUpperCase()} \u00b7 ${String(i18n.activityUpdateLabel || 'UPDATE').toUpperCase()}`) ) ), renderContentActions(chatRoot, href, { - author: action.author, + author: c.chatAuthor || action.author, reportTitle: chatTitle, spread: spreadMap.get(chatRoot) || null, ...favOptsFor('chat', chatRoot, extras) @@ -949,37 +920,48 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ), div({ class: 'card-body' }, div({ class: 'card-section chat' }, - div({ class: 'card-field' }, a({ href, class: 'card-value user-link' }, chatTitle)), - chatDesc ? div({ class: 'card-field' }, span({ class: 'card-value' }, chatDesc)) : '', - div({ class: 'card-field chat-thread-meta' }, - span({ class: 'card-label' }, `${i18n.chatParticipants}: `), - span({ class: 'card-value' }, String(c.members || 0)), - span({ class: 'card-label' }, ` · ${i18n.chatMessagesLabel}: `), - span({ class: 'card-value' }, String(c.messageCount || show.length)) - ) - ), - show.length - ? details({ class: 'chat-thread-details' }, - summary({ class: 'comments-summary chat-thread-summary' }, i18n.keepReading || 'Keep reading...'), - div({ class: 'card-section' }, - show.map(r => { - const rDate = r.ts ? new Date(r.ts).toLocaleString() : ''; - return div({ class: 'thread-reply-item' }, - div({ class: 'thread-reply' }, - r.text ? p({ class: 'post-text thread-reply-text' }, ...renderUrlPreserveNewlines(r.text)) : '' - ), - div({ class: 'card-footer thread-reply-footer' }, - span({ class: 'date-link' }, rDate), - userLink(r.author, action.authorNames && action.authorNames[r.author]) - ) - ); - }) - ) - ) - : null + div({ class: 'card-field activity-update-title' }, + a({ href: latestHref, class: 'card-value user-link' }, chatTitle), + span({ class: 'card-label activity-update-counts' }, `👥: ${c.members || 0} · 💬 ${c.messageCount || show.length}`) + ), + (() => { + const latest = show.length ? show[show.length - 1] : null; + if (!latest) return ''; + const rawBlob = String(latest.image || '').trim(); + const attSrc = rawBlob.startsWith('&') ? `/blob/${encodeURIComponent(rawBlob)}` : null; + const mime = String(latest.mimeType || ''); + const attNode = attSrc + ? (mime.startsWith('video/') + ? videoHyperaxe({ controls: true, class: 'post-video', src: attSrc, type: mime, preload: 'metadata' }) + : mime.startsWith('audio/') + ? audioHyperaxe({ controls: true, class: 'post-audio', src: attSrc, type: mime, preload: 'metadata' }) + : mime === 'application/pdf' + ? a({ href: attSrc, target: '_blank', rel: 'noopener', class: 'filter-btn' }, '📄 PDF') + : mime.includes('bittorrent') || mime === 'application/x-torrent' + ? renderTorrentDownload(attSrc) + : renderZoomableImage(attSrc, { imgClass: 'post-image' })) + : renderMediaBlob(latest.image); + const textNode = latest.text + ? p({ class: 'post-text post-text-pre' }, ...(latest.poll ? [span({ class: 'card-label' }, `${String(i18n.pollInChat || 'Poll').toUpperCase()}: `)] : []), ...renderStyledText(latest.text)) + : null; + const quoted = latest.reply || null; + const replyNode = quoted + ? div({ class: 'reply-context' }, + span({ class: 'reply-context-meta' }, + a({ href: `${href}#msg-${String(quoted.id || '').replace(/[^a-zA-Z0-9]/g, '')}`, class: 'tag-link' }, i18n.inReplyTo || 'IN REPLY TO'), + quoted.author ? span(' ', userLink(quoted.author, action.authorNames && action.authorNames[quoted.author])) : '' + ), + quoted.text ? p({ class: 'post-text reply-context-text' }, quoted.text) : '' + ) + : null; + return (textNode || attNode || replyNode) + ? div({ class: 'feed-text activity-update-msg' }, ...[replyNode, textNode, attNode].filter(Boolean)) + : ''; + })() + ) ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${action.ts ? new Date(action.ts).toLocaleString() : ''} ${i18n.performed} `), + span({ class: 'date-link' }, `${action.ts ? moment(action.ts).format("YYYY/MM/DD HH:mm") : ''}`), userLink(action.author, action.authorNames && action.authorNames[action.author]) ) ); @@ -1004,21 +986,23 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const parentForum = byIdAll.get(rootId) || actions.find(a => a.type === 'forum' && !a.content?.root && (a.id === rootId || a.content?.key === rootId)); const parentContent = parentForum ? (parentForum.value?.content || parentForum.content || {}) : {}; const parentTitle = (parentContent?.title && String(parentContent.title).trim()) ? parentContent.title : ((rootTitle && String(rootTitle).trim()) ? rootTitle : ''); - const parentAuthor = parentForum?.author || ''; - const parentName = parentForum?.authorName || ''; + const branchId = typeof content.branch === 'string' && content.branch !== rootId && content.branch !== rootKey ? content.branch : ''; + const branchMsg = branchId ? (byIdAll.get(branchId) || actions.find(a => a.type === 'forum' && (a.id === branchId || a.content?.key === branchId))) : null; + const repliedAuthor = branchMsg ? (branchMsg.author || '') : ''; + const repliedName = branchMsg ? (branchMsg.authorName || '') : ''; const hrefKey = rootKey || rootId; cardBody.push( div({ class: 'card-section forum' }, div( { class: 'reply-context' }, span({ class: 'reply-context-meta' }, - a({ href: `/forum/${encodeURIComponent(hrefKey)}`, class: 'tag-link' }, i18n.inReplyTo || 'IN REPLY TO'), - parentAuthor ? span(' ', userLink(parentAuthor, parentName)) : '' + a({ href: `/forum/${encodeURIComponent(hrefKey)}#msg-${String(action.tipId || action.id || '').replace(/[^a-zA-Z0-9]/g, '')}`, class: 'tag-link' }, repliedAuthor ? i18n.inReplyTo : i18n.forumInThread), + repliedAuthor ? span(' ', userLink(repliedAuthor, repliedName)) : '' ), parentTitle ? p({ class: 'post-text reply-context-text' }, parentTitle) : '' ), div({ class: 'card-field forum-reply-body' }, - p({ class: 'forum-reply-text' }, ...renderUrl(text)) + p({ class: 'forum-reply-text' }, ...renderStyledText(text)) ) ) ); @@ -1061,11 +1045,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e spreadTitle ? h2({ class: 'post-title activity-spread-title' }, spreadTitle) : '', spreadContentWarning ? h2({ class: 'content-warning' }, spreadContentWarning) : '', spreadExcerpt - ? div({ class: 'post-text activity-spread-text post-text-pre activity-spread-clamped' }, ...renderUrlPreserveNewlines(spreadExcerpt)) + ? div({ class: 'post-text activity-spread-text post-text-pre activity-spread-clamped' }, ...renderStyledText(spreadExcerpt)) : div({ class: 'post-text activity-spread-text activity-spread-missing' }, i18n.spreadContentUnavailable || 'Content not yet available (pending replication)'), spreadOriginalAuthor ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.spreadBy || 'By') + ': '), span({ class: 'card-value' }, userLink(spreadOriginalAuthor)) ) : '', @@ -1100,30 +1083,23 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const imgId = image ? (typeof image === 'string' ? image : (image.link || '')) : ''; cardBody.push( div({ class: 'card-section about' }, - imgId - ? img({ src: `/blob/${encodeURIComponent(imgId)}`, alt: name, class: 'activity-avatar' }) - : img({ src: '/assets/images/default-avatar.png', alt: name, class: 'activity-avatar' }), - h2(userLink(about, name)), - description ? p({ class: 'tribe-side-description' }, ...renderUrlPreserveNewlines(String(description))) : null + div({ class: 'about-card-cols' }, + div({ class: 'about-card-col-img' }, + imgId + ? renderZoomableImage(`/blob/${encodeURIComponent(imgId)}`, { imgClass: 'activity-avatar', alt: name, linkClass: 'zoom-inline' }) + : img({ src: '/assets/images/default-avatar.png', alt: name, class: 'activity-avatar' }) + ), + div({ class: 'about-card-col-desc' }, + h2({ class: 'about-card-header' }, userLink(about, name)), + description + ? p({ class: 'tribe-side-description' }, ...renderStyledText(String(description))) + : null + ) + ) ) ); } - if (type === 'pub') { - const { address } = content || {}; - const { key } = address || {}; - const pr = getProfile(key || ''); - const src = pr.image ? `/blob/${encodeURIComponent(pr.image)}` : '/assets/images/default-avatar.png'; - cardBody.push( - div({ class: 'card-section pub activity-pub' }, - br(), - userLink(pr.id, pr.name), - br(), - img({ src, alt: pr.name || pr.id, class: 'activity-avatar' }) - ) - ); - } - if (type === 'market') { const { item_type, title, price, status, deadline, stock, image, auctions_poll, seller } = content; const isSeller = seller && userId && seller === userId; @@ -1132,16 +1108,11 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemTitle + ':'), span({ class: 'card-value' }, title)), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemType + ':'), span({ class: 'card-value' }, item_type.toUpperCase())), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemStatus + ": " ), span({ class: 'card-value' }, status.toUpperCase())), - div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, deadline ? new Date(deadline).toLocaleString() : "")), + div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, deadline ? moment(deadline).format("YYYY/MM/DD HH:mm") : "")), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemStock + ':'), span({ class: 'card-value' }, stock)), + div({ class: "price-chip" }, `${price} ECO`), br(), - image - ? renderMediaBlob(image, '/assets/images/default-market.png') - : img({ src: '/assets/images/default-market.png', alt: title, class: 'post-image' }), - br(), - div({ class: "market-card price" }, - p(`${i18n.marketItemPrice}: ${price} ECO`) - ), + image ? renderMediaBlob(image, '/assets/images/default-market.png') : null, item_type === 'auction' && status !== 'SOLD' && status !== 'DISCARDED' && !isSeller ? div({ class: "auction-info" }, auctions_poll && auctions_poll.length > 0 @@ -1161,16 +1132,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ); }) ) - : p(i18n.marketNoBids), - form({ method: "POST", action: `/market/bid/${encodeURIComponent(action.id)}` }, - input({ type: "number", name: "bidAmount", step: "0.000001", min: "0.000001", placeholder: i18n.marketYourBid, required: true }), - br(), - button({ class: "buy-btn", type: "submit" }, i18n.marketPlaceBidButton) - ) - ) : "", - item_type === 'exchange' && status !== 'SOLD' && status !== 'DISCARDED' && !isSeller - ? form({ method: "POST", action: `/market/buy/${encodeURIComponent(action.id)}` }, - button({ class: "buy-btn", type: "submit" }, i18n.marketActionsBuy) + : p(i18n.marketNoBids) ) : "" ) ); @@ -1182,9 +1144,8 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const displayDesc = shortDescription || (description ? (description.length > 140 ? description.slice(0, 140) + "\u2026" : description) : ""); cardBody.push( div({ class: 'card-section shop' }, - div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopTitle || 'Shop') + ':'), span({ class: 'card-value' }, shopKey ? a({ href: `/shops/${encodeURIComponent(shopKey)}`, class: 'user-link' }, title || shopKey) : (title || ''))), - displayDesc ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.description || 'Description') + ':'), span({ class: 'card-value' }, displayDesc)) : "", - visibility ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopVisibility || 'Visibility') + ':'), span({ class: 'card-value' }, visibility)) : "", + div({ class: 'card-field' }, span({ class: 'card-value' }, shopKey ? a({ href: `/shops/${encodeURIComponent(shopKey)}`, class: 'user-link' }, title || shopKey) : (title || ''))), + displayDesc ? div({ class: 'card-field' }, span({ class: 'card-value' }, displayDesc)) : "", location ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopLocation || 'Location') + ':'), span({ class: 'card-value' }, location)) : "" ) ); @@ -1197,10 +1158,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e const prodImageNode = renderMediaBlob(prodImage); cardBody.push( div({ class: 'card-section shop' }, - shopId ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopTitle || 'Shop') + ':'), span({ class: 'card-value' }, a({ href: `/shops/${encodeURIComponent(shopId)}`, class: 'user-link' }, shopLabel))) : '', - div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopProductTitle || 'Product') + ':'), span({ class: 'card-value' }, title || '')), + shopId ? div({ class: 'card-field' }, span({ class: 'card-value' }, a({ href: `/shops/${encodeURIComponent(shopId)}`, class: 'user-link' }, shopLabel))) : '', + div({ class: 'card-field' }, span({ class: 'card-value' }, title || '')), + div({ class: 'price-chip' }, `${Number(price || 0).toFixed(6)} ECO`), prodImageNode ? div({ class: 'card-field' }, prodImageNode) : '', - div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopProductPrice || 'Price') + ':'), span({ class: 'card-value' }, `${Number(price || 0).toFixed(6)} ECO`)), div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.shopProductStock || 'Stock') + ':'), span({ class: 'card-value' }, String(stock || 0))) ) ); @@ -1229,10 +1190,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ), image ? (/^(\/|https?:)/.test(String(image)) - ? img({ src: image, class: 'feed-image tribe-image' }) + ? renderZoomableImage(image, { imgClass: 'feed-image tribe-image' }) : renderMediaBlob(image, null)) : null, - p({ class: 'tribe-description' }, ...renderUrl(description || '')), + p({ class: 'tribe-description' }, ...renderStyledText(description || '')), validTags.length ? div({ class: 'card-tags' }, validTags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`))) @@ -1252,16 +1213,16 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ), (buildStatus || startDate || endDate) ? div({ class: 'card-field-row' }, buildStatus ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.industryStatusLabel || 'Status').toUpperCase() + ':'), span({ class: 'card-value' }, String(i18n['industryBuildStatus_' + buildStatus] || buildStatus).toUpperCase())) : "", - startDate ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.industryBuildStart || 'Start date').toUpperCase() + ':'), span({ class: 'card-value' }, moment(startDate).format('YYYY-MM-DD'))) : "", + startDate ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.industryBuildStart || 'Start date').toUpperCase() + ':'), span({ class: 'card-value' }, moment(startDate).format("YYYY/MM/DD"))) : "", endDate ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.industryBuildEnd || 'End date').toUpperCase() + ':'), span({ class: 'card-value' }, moment(endDate).format('YYYY-MM-DD'))) : "", daysLeft != null ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.industryTimeLeft || 'Time left').toUpperCase() + ':'), span({ class: 'card-value' }, `${daysLeft}d`)) : "" ) : null, image ? (/^(\/|https?:)/.test(String(image)) - ? img({ src: image, class: 'feed-image tribe-image' }) + ? renderZoomableImage(image, { imgClass: 'feed-image tribe-image' }) : renderMediaBlob(image, null)) : null, - notes ? p({ class: 'tribe-description' }, ...renderUrl(notes)) : "" + notes ? p({ class: 'tribe-description' }, ...renderStyledText(notes)) : "" ) ); } @@ -1282,10 +1243,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ), image ? (/^(\/|https?:)/.test(String(image)) - ? img({ src: image, class: 'feed-image tribe-image' }) + ? renderZoomableImage(image, { imgClass: 'feed-image tribe-image' }) : renderMediaBlob(image, null)) : null, - description ? p({ class: 'tribe-description' }, ...renderUrl(description)) : "" + description ? p({ class: 'tribe-description' }, ...renderStyledText(description)) : "" ) ); } @@ -1309,7 +1270,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div({ class: 'card-section chat' }, div({ class: 'card-field' }, chatKey - ? a({ href: `/chats/${encodeURIComponent(chatKey)}`, class: 'card-value user-link' }, title || chatKey) + ? a({ href: `/chats/${encodeURIComponent(chatKey)}#chat-latest`, class: 'card-value user-link' }, title || chatKey) : span({ class: 'card-value' }, title || '')), displayDesc ? div({ class: 'card-field' }, span({ class: 'card-value' }, displayDesc)) : '', category ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.chatCategoryLabel || 'Category') + ':'), span({ class: 'card-value' }, category)) : '' @@ -1328,6 +1289,139 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ); } + if (type === 'room') { + const roomKey = action.id || action.key || ''; + const roomTitle = content.title || action.title || ''; + const roomNumber = roomNumberOf(action.rootId || roomKey); + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'card-field activity-room-title' }, + roomKey ? a({ href: `/rooms/${encodeURIComponent(roomKey)}`, class: 'card-value user-link' }, roomTitle || roomKey) : span({ class: 'card-value' }, roomTitle || ''), + roomNumber ? renderPhoneChip(roomNumber, content.line === 'SWITCHBOARD' ? i18n.roomNumberHint : i18n.roomLineDndError) : '' + ), + content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, String(content.description).slice(0, 200))) : '' + ) + ); + } + + if (type === 'emergency') { + const emergencyKey = action.id || action.key || ''; + const emergencyTitle = content.title || action.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'card-field' }, emergencyKey ? a({ href: `/emergencies/${encodeURIComponent(emergencyKey)}`, class: 'card-value user-link' }, emergencyTitle || emergencyKey) : span({ class: 'card-value' }, emergencyTitle || '')), + content.status ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.statusLabel || 'Status') + ':'), span({ class: 'card-value' }, String(content.status).toUpperCase())) : '', + content.category ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.emergencyCategoryLabel || 'Category') + ':'), span({ class: 'card-value' }, String(content.category).toUpperCase())) : '', + renderMediaBlob(content.text, null), + stripMediaMarkdown(content.text) ? p({ class: 'tribe-description' }, ...renderStyledText(stripMediaMarkdown(content.text))) : '' + ) + ); + } + + if (type === 'mailingList') { + const listKey = action.id || action.key || ''; + const listTitle = content.title || action.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'card-field' }, listKey ? a({ href: `/mailing/${encodeURIComponent(listKey)}`, class: 'card-value user-link' }, listTitle || listKey) : span({ class: 'card-value' }, listTitle || '')) + ) + ); + } + + if (type === 'logisticsRoute') { + const routeKey = action.id || action.key || ''; + const routeTitle = content.title || action.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'card-field' }, routeKey ? a({ href: `/logistics/${encodeURIComponent(routeKey)}`, class: 'card-value user-link' }, routeTitle || routeKey) : span({ class: 'card-value' }, routeTitle || '')), + content.origin || content.destination ? div({ class: 'card-field' }, span({ class: 'card-value' }, `${content.origin || ''} → ${content.destination || ''}`)) : '', + content.kind ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.logisticsKindLabel || 'Type') + ':'), span({ class: 'card-value' }, `${String(content.kind).toUpperCase()} · ${String(content.mode || '').toUpperCase()}`)) : '', + content.date ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.logisticsDateLabel || 'Date') + ':'), span({ class: 'card-value' }, moment(content.date).format('YYYY/MM/DD HH:mm'))) : '', + renderMediaBlob(content.description, null), + stripMediaMarkdown(content.description) ? p({ class: 'tribe-description' }, ...renderStyledText(stripMediaMarkdown(content.description))) : '' + ) + ); + } + + if (type === 'podcast' || type === 'podcastEpisode') { + const pKey = action.id || action.key || ''; + const pTitle = content.title || action.title || ''; + const pHref = type === 'podcast' ? `/podcasts/${encodeURIComponent(pKey)}` : `/podcasts/episode/${encodeURIComponent(pKey)}`; + const pDesc = stripMediaMarkdown(content.description); + const pShortDesc = pDesc ? (pDesc.length > 140 ? pDesc.slice(0, 140) + "\u2026" : pDesc) : ''; + cardBody.push( + div({ class: 'card-section' }, + type === 'podcastEpisode' ? '' : div({ class: 'card-field' }, pKey ? a({ href: pHref, class: 'card-value user-link' }, pTitle || pKey) : span({ class: 'card-value' }, pTitle || '')), + pShortDesc ? div({ class: 'card-field' }, span({ class: 'card-value' }, pShortDesc)) : '', + type === 'podcast' && content.category ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.podcastCategoryLabel || 'Category') + ':'), span({ class: 'card-value' }, String(content.category).toUpperCase())) : '', + renderMediaObject(type === 'podcast' ? content.cover : content.media, type === 'podcast' ? pHref : null) + ) + ); + } + + if (type === 'campaign') { + const cpKey = action.id || action.key || ''; + const cpTitle = content.title || action.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'card-field' }, cpKey ? a({ href: `/campaigns/${encodeURIComponent(cpKey)}`, class: 'card-value user-link' }, cpTitle || cpKey) : span({ class: 'card-value' }, cpTitle || '')), + content.category ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.campaignCategoryLabel || 'Category') + ':'), span({ class: 'card-value' }, String(content.category).toUpperCase())) : '', + content.status ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.statusLabel || 'Status') + ':'), span({ class: 'card-value' }, String(content.status).toUpperCase())) : '', + renderMediaBlob(content.text, null), + stripMediaMarkdown(content.text) ? p({ class: 'tribe-description' }, ...renderStyledText(stripMediaMarkdown(content.text))) : '', + content.goal ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.campaignSignaturesLabel || 'Signatures').toUpperCase() + ':'), span({ class: 'card-value' }, `${new Set(all.filter(x => x && x.type === 'campaignSignature' && x.content && (x.content.target === cpKey || x.content.target === (action.rootId || cpKey))).map(x => x.author)).size} / ${content.goal}`)) : '' + ) + ); + } + + if (type === 'campaignUpdate') { + const targetId = String(content.target || ''); + const parent = all.find(x => x && (x.id === targetId || x.key === targetId || x.rootId === targetId || x.tipId === targetId)) || null; + const parentContent = parent ? (parent.value?.content || parent.content || {}) : {}; + const parentTitle = parentContent.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'reply-context' }, + span({ class: 'reply-context-meta' }, + a({ href: `/campaigns/${encodeURIComponent(targetId)}`, class: 'tag-link' }, i18n.emergencyLastUpdate || 'LAST UPDATE'), + parentTitle ? span(' ', a({ href: `/campaigns/${encodeURIComponent(targetId)}`, class: 'user-link' }, parentTitle)) : '' + ) + ), + renderMediaBlob(content.text, null), + stripMediaMarkdown(content.text) ? div({ class: 'card-field' }, p({ class: 'forum-reply-text' }, ...renderStyledText(stripMediaMarkdown(content.text)))) : '' + ) + ); + } + + if (type === 'emergencyUpdate') { + const targetId = String(content.target || ''); + const parent = all.find(x => x && (x.id === targetId || x.key === targetId || x.rootId === targetId || x.tipId === targetId)) || null; + const parentContent = parent ? (parent.value?.content || parent.content || {}) : {}; + const parentTitle = parentContent.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'reply-context' }, + span({ class: 'reply-context-meta' }, + a({ href: `/emergencies/${encodeURIComponent(targetId)}`, class: 'tag-link' }, i18n.emergencyLastUpdate || 'LAST UPDATE'), + parentTitle ? span(' ', a({ href: `/emergencies/${encodeURIComponent(targetId)}`, class: 'user-link' }, parentTitle)) : '' + ) + ), + renderMediaBlob(content.text, null), + stripMediaMarkdown(content.text) ? div({ class: 'card-field' }, p({ class: 'forum-reply-text' }, ...renderStyledText(stripMediaMarkdown(content.text)))) : '' + ) + ); + } + + if (type === 'wikiPage') { + const wikiKey = action.id || action.key || ''; + const wikiTitle = content.title || action.title || ''; + cardBody.push( + div({ class: 'card-section' }, + div({ class: 'card-field' }, wikiKey ? a({ href: `/wiki/${encodeURIComponent(wikiKey)}`, class: 'card-value user-link' }, wikiTitle || wikiKey) : span({ class: 'card-value' }, wikiTitle || '')) + ) + ); + } + if (type === 'calendar') { const calKey = action.id || action.key || ''; const calTitle = content.title || action.title || ''; @@ -1419,10 +1513,12 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e : 'ACTION'; const actorId = activity.activityActor || ''; + const actorLink = `${escapeHtml(userLinkLabel(actorId))}`; + const projectLink = `${escapeHtml(title || '')}`; const msgHtml = tmpl - .replace('%OASIS%', `${userLinkLabel(actorId)}`) - .replace('%PROJECT%', `${title || ''}`) - .replace('%ACTION%', `${actionWord}`); + .replace('%OASIS%', () => actorLink) + .replace('%PROJECT%', () => projectLink) + .replace('%ACTION%', () => `${escapeHtml(actionWord)}`); return div({ class: 'card card-rpg' }, div({ class: 'card-header' }, @@ -1432,10 +1528,10 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ) ), div( - p({ innerHTML: sanitizeHtml(msgHtml) }) + p({ [RAW_HTML]: sanitizeHtml(msgHtml) }) ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${action.ts ? new Date(action.ts).toLocaleString() : ''} ${i18n.performed} `), + span({ class: 'date-link' }, `${action.ts ? moment(action.ts).format("YYYY/MM/DD HH:mm") : ''}`), userLink(action.author, action.authorNames && action.authorNames[action.author]) ) ); @@ -1491,38 +1587,6 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ); } - if (type === 'aiExchange') { - const { ctx, lang, tags, rating } = content; - const helpful = Number(action.helpfulVotes || 0); - cardBody.push( - div({ class: 'card-section ai-exchange' }, - Array.isArray(ctx) && ctx.length - ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.aiSnippetsLearned || 'Snippets learned') + ':'), span({ class: 'card-value' }, String(ctx.length))) - : null, - lang - ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.aiExchangeLang || 'Language') + ':'), span({ class: 'card-value' }, String(lang).toUpperCase())) - : null, - Array.isArray(tags) && tags.length - ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.aiExchangeTags || 'Tags') + ':'), - span({ class: 'card-value' }, tags.map(t => span({ class: 'ai-exchange-tag' }, '#' + t)))) - : null, - rating > 0 - ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.aiExchangeRating || 'Rating') + ':'), span({ class: 'card-value' }, '★'.repeat(rating) + '☆'.repeat(Math.max(0, 5 - rating)))) - : null, - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.aiExchangeHelpful || 'Helpful') + ':'), - span({ class: 'card-value' }, String(helpful)), - form({ method: 'POST', action: '/ai/exchange/vote', class: 'ai-exchange-vote-form' }, - input({ type: 'hidden', name: 'target', value: action.id }), - input({ type: 'hidden', name: 'helpful', value: 'yes' }), - input({ type: 'hidden', name: 'returnTo', value: '/activity' }), - button({ type: 'submit', class: 'filter-btn' }, i18n.aiExchangeMarkHelpful || '+1 helpful') - ) - ) - ) - ); - } - if (type === 'karmaScore') { const { karmaScore } = content; cardBody.push( @@ -1580,6 +1644,30 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ); } + if (type === 'schoolCourse') { + const { title, description, price, visibility, startDate } = content; + const coursePaid = Number(price) > 0; + cardBody.push( + div({ class: 'card-section report' }, + div({ class: 'card-field' }, + span({ class: 'card-label' }, i18n.title + ':'), + span({ class: 'card-value' }, title) + ), + description ? div({ class: 'card-field' }, + span({ class: 'card-value' }, String(description).length > 220 ? String(description).slice(0, 220) + '…' : description) + ) : null, + coursePaid ? div({ class: 'card-field' }, + span({ class: 'card-label' }, (i18n.schoolPrice || 'Price (ECO)') + ':'), + span({ class: 'card-value' }, `${Number(price).toFixed(2)} ECO`) + ) : null, + startDate ? div({ class: 'card-field' }, + span({ class: 'card-label' }, (i18n.schoolStartDate || 'Start date') + ':'), + span({ class: 'card-value' }, moment(startDate).format("YYYY/MM/DD HH:mm")) + ) : null + ) + ); + } + if (type === 'job') { const { title, job_type, tasks, location, vacants, salary, status, subscribers } = content; cardBody.push( @@ -1650,15 +1738,21 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e cardBody.push( div({ class: 'card-section parliament' }, - startAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentElectionsStart.toUpperCase() || 'Elections start') + ':'), span({ class: 'card-value' }, new Date(startAt).toLocaleString())) : '', - endAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentElectionsEnd.toUpperCase() || 'Elections end') + ':'), span({ class: 'card-value' }, new Date(endAt).toLocaleString())) : '', + startAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentElectionsStart.toUpperCase() || 'Elections start') + ':'), span({ class: 'card-value' }, moment(startAt).format("YYYY/MM/DD HH:mm"))) : '', + endAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentElectionsEnd.toUpperCase() || 'Elections end') + ':'), span({ class: 'card-value' }, moment(endAt).format("YYYY/MM/DD HH:mm"))) : '', div({ class: 'card-field' }, span({ class: 'card-value' }, winnerLink)) ) ); } if (type === 'parliamentProposal') { - const { title, description, method, status, voteId, createdAt } = content; + const { title, method, status, voteId, createdAt } = content; + const legacy = !content.campaignId ? String(content.description || '').match(/\n*\s*(\d+) signatures: \/campaigns\/(%[^\s]+\.sha256)\s*$/) : null; + const description = legacy ? String(content.description || '').slice(0, legacy.index).trim() : content.description; + const campaignId = content.campaignId || (legacy ? legacy[2] : ''); + const liveSignatures = campaignId ? new Set(all.filter(x => x && x.type === 'campaignSignature' && x.content && x.content.target === campaignId).map(x => x.author)).size : 0; + const signatures = Math.max(liveSignatures, content.campaignId ? (Number(content.signatures) || 0) : (legacy ? Number(legacy[1]) || 0 : 0)); + const goal = Number(content.goal) || 0; const methodUpper = String( i18n['parliamentMethod' + String(method || '').toUpperCase()] || method @@ -1667,9 +1761,12 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e cardBody.push( div({ class: 'card-section parliament' }, title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentProposalTitle.toUpperCase() || 'Title') + ':'), span({ class: 'card-value' }, title)) : '', - description ? p({ class: 'card-section-text' }, description) : '', + renderMediaBlob(description, null), + stripMediaMarkdown(description) ? p({ class: 'card-section-text' }, ...renderStyledText(stripMediaMarkdown(description))) : '', + campaignId ? div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.campaignSignaturesLabel || 'Signatures').toUpperCase() + ':'), span({ class: 'card-value' }, `${signatures}${goal > 0 ? ` / ${goal}` : ''}`)) : '', + campaignId ? div({ class: 'card-field' }, a({ href: `/campaigns/${encodeURIComponent(campaignId)}`, class: 'card-value user-link' }, title || campaignId)) : '', div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentGovMethod || 'Method') + ':'), span({ class: 'card-value' }, methodUpper)), - createdAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.createdAt.toUpperCase() || 'Created at') + ':'), span({ class: 'card-value' }, new Date(createdAt).toLocaleString())) : '', + createdAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.createdAt.toUpperCase() || 'Created at') + ':'), span({ class: 'card-value' }, moment(createdAt).format("YYYY/MM/DD HH:mm"))) : '', voteId ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentOpenVote.toUpperCase() || 'Open vote') + ':'), a({ href: `/votes/${encodeURIComponent(voteId)}`, class: 'tag-link' }, i18n.viewDetails || 'View details')) : '', status ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentStatus.toUpperCase() || 'Status') + ':'), span({ class: 'card-value' }, status)) : '' ) @@ -1687,7 +1784,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentProposalTitle.toUpperCase() || 'Title') + ':'), span({ class: 'card-value' }, title)) : '', reasons ? p({ class: 'card-section-text' }, reasons) : '', div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentGovMethod || 'Method') + ':'), span({ class: 'card-value' }, methodUpper)), - createdAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.createdAt.toUpperCase() || 'Created at') + ':'), span({ class: 'card-value' }, new Date(createdAt).toLocaleString())) : '', + createdAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.createdAt.toUpperCase() || 'Created at') + ':'), span({ class: 'card-value' }, moment(createdAt).format("YYYY/MM/DD HH:mm"))) : '', voteId ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentOpenVote.toUpperCase() || 'Open vote') + ':'), a({ href: `/votes/${encodeURIComponent(voteId)}`, class: 'tag-link' }, i18n.viewDetails || 'View details')) : '', status ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentStatus.toUpperCase() || 'Status') + ':'), span({ class: 'card-value' }, status)) : '' ) @@ -1709,7 +1806,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e description ? p({ class: 'card-section-text' }, description) : '', div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentLawMethod || 'Method') + ':'), span({ class: 'card-value' }, methodUpper)), proposer ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentLawProposer || 'Proposer') + ':'), span({ class: 'card-value' }, userLink(proposer))) : '', - enactedAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentLawEnacted || 'Enacted at') + ':'), span({ class: 'card-value' }, new Date(enactedAt).toLocaleString())) : '', + enactedAt ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentLawEnacted || 'Enacted at') + ':'), span({ class: 'card-value' }, moment(enactedAt).format("YYYY/MM/DD HH:mm"))) : '', (total || yes) ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.parliamentLawVotes || 'Votes') + ':'), span({ class: 'card-value' }, `${yes}/${total}`)) : '' ) ); @@ -1723,9 +1820,9 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsCaseTitle.toUpperCase() + ':'), span({ class: 'card-value' }, title)) : '', status ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThStatus.toUpperCase() + ':'), span({ class: 'card-value' }, status)) : '', method ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsMethod.toUpperCase() + ':'), span({ class: 'card-value' }, String(i18n['courtsMethod' + String(method).toUpperCase()] || method).toUpperCase())) : '', - answerBy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThAnswerBy + ':'), span({ class: 'card-value' }, new Date(answerBy).toLocaleString())) : '', - evidenceBy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThEvidenceBy + ':'), span({ class: 'card-value' }, new Date(evidenceBy).toLocaleString())) : '', - decisionBy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThDecisionBy + ':'), span({ class: 'card-value' }, new Date(decisionBy).toLocaleString())) : '', + answerBy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThAnswerBy + ':'), span({ class: 'card-value' }, moment(answerBy).format("YYYY/MM/DD HH:mm"))) : '', + evidenceBy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThEvidenceBy + ':'), span({ class: 'card-value' }, moment(evidenceBy).format("YYYY/MM/DD HH:mm"))) : '', + decisionBy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThDecisionBy + ':'), span({ class: 'card-value' }, moment(decisionBy).format("YYYY/MM/DD HH:mm"))) : '', accuser ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsAccuser + ':'), span({ class: 'card-value' }, userLink(accuser))) : '', typeof needed !== 'undefined' ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsVotesNeeded + ':'), span({ class: 'card-value' }, String(needed))) : '', (typeof yes !== 'undefined' || typeof total !== 'undefined') ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsVotesSlashTotal + ':'), span({ class: 'card-value' }, `${Number(yes || 0)}/${Number(total || 0)}`)) : '', @@ -1768,7 +1865,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e return null; } - const detailHref = (type !== 'aiExchange' && type !== 'bankWallet') + const detailHref = (type !== 'bankWallet') ? (isParliamentTarget ? `/parliament?filter=${encodeURIComponent(parliamentFilter)}` : isCourtsTarget @@ -1781,24 +1878,47 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e div({ class: 'card-header activity-card-header' }, div({ class: 'card-chips-row' }, span({ class: 'pm-exposition-chip pm-exposition-whole' }, - span({ class: 'pm-exposition-text' }, String(type || '').toUpperCase()) + span({ class: 'pm-exposition-text' }, String(headerText || type || '').replace(/^\[|\]$/g, '')) ) ), renderContentActions(msgId, detailHref, { author: action.author, reportTitle: (content && (content.title || content.question || content.concept || content.name)) || '', spread: SPREADABLE_TYPES.has(type) ? (spreadMap.get(action.id) || null) : undefined, - ...favOptsFor(type, msgId, extras) + ...(type === 'torrent' || type === 'file' ? { spreadTitle: i18n.seedAction } : {}), + ...(TORRENTABLE_TYPES.has(type) && content && typeof content.url === 'string' && content.url.startsWith('&') + ? { torrentFrom: { blobId: content.url, name: content.title || content.fileName || '' } } + : {}), + ...favOptsFor(type, msgId, extras), + deleteAction: isOwn ? activityDeleteAction(type, action, content, msgId, userId) : undefined }) ), ...cardBody, (() => { - if (!OPINION_TYPES.has(type)) return null; - const routeFn = OPINION_ROUTES[type]; - if (!routeFn) return null; + const footerAuthorId = action.author || (content && content.proposer) || ''; + const footerNode = p({ class: 'card-footer' }, + span({ class: 'date-link' }, `${date}`), + userLink(footerAuthorId, (action.authorNames && action.authorNames[footerAuthorId]) || getProfile(footerAuthorId).name) + ); + const commentTotal = Number(action.commentCount) || 0; + const commentsHref = detailHref + ? `${detailHref}${detailHref.includes('?') ? '&' : '?'}comments=open${commentTotal ? '#comments-latest' : ''}` + : ''; + const commentsNode = detailHref && type !== 'about' + ? a({ + href: commentsHref, + class: commentTotal ? 'comments-summary-link engage-on' : 'comments-summary-link', + title: i18n.commentsGoToLast || i18n.voteNewCommentLabel || 'Comments' + }, + span({ class: 'comments-summary-icon' }, '✑'), + span({ class: 'comments-summary-count' }, `(${commentTotal})`) + ) + : null; + const routeFn = OPINION_TYPES.has(type) ? OPINION_ROUTES[type] : null; + if (!routeFn) return commentsNode ? [renderCardMetaRow(commentsNode, footerNode)] : footerNode; const ops = (action.value?.content?.opinions) || (action.content?.opinions) || {}; const opsTotal = Object.values(ops).reduce((s, n) => s + (Number(n) || 0), 0); - return details({ class: 'opinions-voting-collapse' }, + const votingNode = details({ class: 'opinions-voting-collapse' }, summary({ class: 'opinions-summary' }, span({ class: 'opinions-summary-icon' }, 'ꔍ'), span({ class: 'opinions-summary-count' }, `(${opsTotal})`)), @@ -1810,13 +1930,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e ) ) ); - })(), - (() => { - const footerAuthorId = action.author || (content && content.proposer) || ''; - return p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${date} ${i18n.performed} `), - userLink(footerAuthorId, (action.authorNames && action.authorNames[footerAuthorId]) || getProfile(footerAuthorId).name) - ); + return [renderVotesSummary(ops), renderCardMetaRow(votingNode, commentsNode, footerNode)]; })() ); }); @@ -1828,13 +1942,19 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map(), e return filteredCards; } -const FAV_KIND_BY_TYPE = { - image: 'images', audio: 'audios', video: 'videos', document: 'documents', - bookmark: 'bookmarks', torrent: 'torrents', event: 'events', task: 'tasks', - report: 'reports', votes: 'votes', poll: 'polls', market: 'market', - housing: 'housing', job: 'jobs', project: 'projects', shop: 'shops', - chat: 'chats', chatThread: 'chats', pad: 'pads', calendar: 'calendars', - map: 'maps', forum: 'forum', transfer: 'transfers', post: 'blogs' +const FAV_KIND_BY_TYPE = CONTENT_FAV_KIND; + +const activityDeleteAction = (type, action, content, msgId, userId) => { + const c = content || {}; + if (c.author && String(c.author) !== String(userId)) return undefined; + if (type === 'transfer') { + const required = c.from === c.to ? 1 : 2; + const confirmed = Array.isArray(c.confirmedBy) ? c.confirmedBy.length : 0; + const dl = c.deadline ? moment(c.deadline) : null; + const expired = dl && dl.isValid() ? dl.isBefore(moment()) : false; + if (String(c.status || '').toUpperCase() !== 'UNCONFIRMED' || expired || confirmed >= required) return undefined; + } + return contentDeleteAction(type, type === 'forum' ? safeMsgId(c.key || action.tipId || action.id) : msgId); }; const favOptsFor = (type, id, extras = {}) => { @@ -1891,31 +2011,45 @@ function getViewDetailsAction(type, action) { case 'audio': return `/audios/${id}`; case 'video': return `/videos/${id}`; case 'torrent': return `/torrents/${id}`; - case 'forum': return `/forum/${encodeURIComponent(action.content?.key || action.tipId || action.id)}`; + case 'file': return `/files/${id}`; + case 'forum': { + const root = action.content?.root; + const rootId = typeof root === 'string' ? root : (root && (root.key || root.id)) || ''; + if (rootId) return `/forum/${encodeURIComponent(action.content?.rootKey || rootId)}#msg-${String(action.tipId || action.id || '').replace(/[^a-zA-Z0-9]/g, '')}`; + return `/forum/${encodeURIComponent(action.content?.key || action.tipId || action.id)}`; + } case 'document': return `/documents/${id}`; case 'bookmark': return `/bookmarks/${id}`; case 'event': return `/events/${id}`; case 'task': return `/tasks/${id}`; case 'taskAssignment': return `/tasks/${encodeURIComponent(action.content?.taskId || action.tipId || action.id)}`; case 'about': return `/author/${encodeURIComponent(action.author)}`; - case 'pub': return `/invites`; case 'market': return `/market/${id}`; case 'shop': return `/shops/${id}`; case 'shopProduct': return `/shops/product/${id}`; case 'chat': return `/chats/${id}`; case 'pad': return `/pads/${id}`; + case 'room': return `/rooms/${id}`; + case 'wikiPage': return `/wiki/${id}`; + case 'emergency': return `/emergencies/${id}`; + case 'emergencyUpdate': return action.content && action.content.target ? `/emergencies/${encodeURIComponent(action.content.target)}` : '/emergencies'; + case 'campaignUpdate': return action.content && action.content.target ? `/campaigns/${encodeURIComponent(action.content.target)}` : '/campaigns'; + case 'mailingList': return `/mailing/${id}`; + case 'logisticsRoute': return `/logistics/${id}`; + case 'podcast': return `/podcasts/${id}`; + case 'podcastEpisode': return `/podcasts/episode/${id}`; + case 'campaign': return `/campaigns/${id}`; case 'calendar': return `/calendars/${id}`; case 'housing': return `/housing/${id}`; case 'job': return `/jobs/${id}`; case 'project': return `/projects/${id}`; + case 'schoolCourse': return `/school/course/${id}`; case 'industry': return `/industry/${id}`; case 'industryBuild': return `/industry/build/${id}`; case 'industryBlueprint': return `/industry/blueprint/${id}`; case 'industryAllocation': return action.content?.target ? `/industry/build/${encodeURIComponent(action.content.target)}` : '/industry?filter=BUILDS'; case 'report': return `/reports/${id}`; case 'bankWallet': return `/wallet`; - case 'bankClaim': return `/banking${action.content?.epochId ? `/epoch/${encodeURIComponent(action.content.epochId)}` : ''}`; - case 'ubiClaim': return action.content?.transferId ? `/transfers/${encodeURIComponent(action.content.transferId)}` : `/transfers?filter=ubi`; default: return `/activity`; } } @@ -1929,8 +2063,8 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { const activityTypes = [ { type: 'all', label: i18n.allButton }, - { type: 'mine', label: i18n.mineButton }, { type: 'recent', label: i18n.typeRecent }, + { type: 'mine', label: i18n.mineButton }, { type: 'top', label: i18n.typeTop }, { type: 'inhabitants', label: i18n.typeInhabitants }, { type: 'tribe', label: i18n.typeTribe }, @@ -1946,21 +2080,28 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { { type: 'feed', label: i18n.typeFeed }, { type: 'chat', label: i18n.typeChat }, { type: 'pad', label: i18n.typePad }, + { type: 'room', label: i18n.typeRoom }, + { type: 'wiki', label: i18n.typeWiki }, + { type: 'emergency', label: i18n.typeEmergency }, + { type: 'mailing', label: i18n.typeMailingList }, + { type: 'logistics', label: i18n.typeLogisticsRoute }, + { type: 'podcast', label: i18n.typePodcast }, + { type: 'campaign', label: i18n.typeCampaign }, { type: 'forum', label: i18n.typeForum }, { type: 'map', label: i18n.typeMap }, - { type: 'banking', label: i18n.typeBanking }, { type: 'market', label: i18n.typeMarket }, + { type: 'schoolCourse', label: i18n.typeSchool }, { type: 'project', label: i18n.typeProject }, { type: 'industry', label: i18n.typeIndustry }, { type: 'housing', label: i18n.typeHousing }, { type: 'job', label: i18n.typeJob }, { type: 'shop', label: i18n.typeShop }, - { type: 'transfer', label: i18n.typeTransfer }, { type: 'audio', label: i18n.typeAudio }, { type: 'bookmark', label: i18n.typeBookmark }, { type: 'document', label: i18n.typeDocument }, { type: 'image', label: i18n.typeImage }, { type: 'torrent', label: i18n.typeTorrent }, + { type: 'file', label: i18n.typeFile }, { type: 'video', label: i18n.typeVideo } ]; @@ -1968,21 +2109,27 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { const GROUP_SUBTYPES = { parliament: ['parliamentCandidature', 'parliamentTerm', 'parliamentProposal', 'parliamentRevocation', 'parliamentLaw'], courts: ['courtsCase', 'courtsNomination', 'courtsNominationVote'], - banking: ['bankWallet', 'bankClaim', 'ubiClaim'], task: ['task', 'taskAssignment'], votes: ['votes', 'poll'], shop: ['shop', 'shopProduct'], larp: ['larpHousePost'], inhabitants:['about'], chat: ['chat', 'chatThread'], + wiki: ['wikiPage'], + mailing: ['mailingList'], + emergency: ['emergency', 'emergencyUpdate'], + logistics: ['logisticsRoute'], + podcast: ['podcast', 'podcastEpisode'], + campaign: ['campaign', 'campaignUpdate'], industry: ['industry', 'industryBuild', 'industryBlueprint', 'industryAllocation'] }; - const ALLOWED_TYPES = new Set(); + const ALLOWED_TYPES = new Set(['tribeOpened']); for (const { type } of activityTypes) { if (FILTER_META.has(type)) continue; if (GROUP_SUBTYPES[type]) GROUP_SUBTYPES[type].forEach(t => ALLOWED_TYPES.add(t)); else ALLOWED_TYPES.add(type); } + const rawActions = actions; actions = actions.filter(action => ALLOWED_TYPES.has(action.type)); let filteredActions; @@ -1992,9 +2139,9 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { const now = Date.now(); filteredActions = actions.filter(action => action.type !== 'tombstone' && action.ts && now - action.ts < 24 * 60 * 60 * 1000); } else if (filter === 'banking') { - filteredActions = actions.filter(action => action.type !== 'tombstone' && (action.type === 'bankWallet' || action.type === 'bankClaim' || action.type === 'ubiClaim')); + filteredActions = []; } else if (filter === 'tribe') { - filteredActions = actions.filter(action => action.type === 'tribe'); + filteredActions = actions.filter(action => action.type === 'tribe' || action.type === 'tribeOpened'); } else if (filter === 'larp') { filteredActions = actions.filter(action => action.type === 'larpHousePost'); } else if (filter === 'inhabitants') { @@ -2007,17 +2154,34 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { return t === 'courtscase' || t === 'courtsnomination' || t === 'courtsnominationvote'; }); } else if (filter === 'task') { - filteredActions = actions.filter(action => action.type !== 'tombstone' && (action.type === 'task' || action.type === 'taskAssignment')); + filteredActions = actions.filter(action => action.type !== 'tombstone' && (action.type === 'task' || action.type === 'taskAssignment' || openedIn(action, 'task'))); } else if (filter === 'torrent') { - filteredActions = actions.filter(action => action.type === 'torrent'); + filteredActions = actions.filter(action => action.type === 'torrent' || openedIn(action, 'torrent')); + } else if (filter === 'file') { + filteredActions = actions.filter(action => action.type === 'file'); } else if (filter === 'chat') { filteredActions = actions.filter(action => (action.type === 'chat' || action.type === 'chatThread') && action.type !== 'tombstone'); + } else if (filter === 'wiki') { + filteredActions = actions.filter(action => action.type === 'wikiPage'); + } else if (filter === 'emergency') { + filteredActions = actions.filter(action => action.type === 'emergency' || action.type === 'emergencyUpdate'); + } else if (filter === 'mailing') { + filteredActions = actions.filter(action => action.type === 'mailingList'); + } else if (filter === 'room') { + filteredActions = actions.filter(action => action.type === 'room'); + } else if (filter === 'logistics') { + filteredActions = actions.filter(action => action.type === 'logisticsRoute'); + } else if (filter === 'podcast') { + filteredActions = actions.filter(action => action.type === 'podcast' || action.type === 'podcastEpisode'); + } else if (filter === 'campaign') { + filteredActions = actions.filter(action => action.type === 'campaign' || action.type === 'campaignUpdate'); } else if (filter === 'industry') { filteredActions = actions.filter(action => ['industry', 'industryBuild', 'industryBlueprint', 'industryAllocation'].includes(action.type) && action.type !== 'tombstone'); } else { filteredActions = actions.filter(action => (action.type === filter || filter === 'all' + || openedIn(action, filter) || (filter === 'shop' && action.type === 'shopProduct') || (filter === 'votes' && action.type === 'poll')) && action.type !== 'tombstone'); @@ -2084,73 +2248,72 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { } const MODULE_SUB_FILTERS = { - audio: { url: '/audios', filters: ['all', 'mine', 'recent', 'top', 'favorites'] }, - video: { url: '/videos', filters: ['all', 'mine', 'recent', 'top', 'favorites'] }, - image: { url: '/images', filters: ['all', 'mine', 'recent', 'top', 'favorites'] }, - document:{ url: '/documents', filters: ['all', 'mine', 'recent', 'top', 'favorites'] }, - bookmark:{ url: '/bookmarks', filters: ['all', 'mine', 'recent', 'top', 'favorites'] }, - torrent: { url: '/torrents', filters: ['all', 'mine', 'recent', 'top', 'favorites'] }, - map: { url: '/maps', filters: ['all', 'mine', 'recent'] }, - forum: { url: '/forum', filters: ['all', 'mine', 'recent', 'top'] }, - event: { url: '/events', filters: ['all', 'mine', 'recent', 'top'] }, + audio: { url: '/audios', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + video: { url: '/videos', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + image: { url: '/images', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + document:{ url: '/documents', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + bookmark:{ url: '/bookmarks', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + torrent: { url: '/torrents', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + file: { url: '/files', filters: ['all', 'recent', 'mine', 'top', 'favorites'] }, + map: { url: '/maps', filters: ['all', 'recent', 'mine'] }, + forum: { url: '/forum', filters: ['all', 'recent', 'mine', 'top'] }, + event: { url: '/events', filters: ['all', 'recent', 'mine', 'top'] }, task: { url: '/tasks', filters: ['all', 'mine', 'assigned', 'open', 'closed'] }, - votes: { url: '/votes', filters: ['all', 'mine', 'recent', 'top'] }, + votes: { url: '/votes', filters: ['all', 'recent', 'mine', 'top'] }, transfer:{ url: '/transfers', filters: ['all', 'mine', 'pending', 'unconfirmed', 'closed'] }, market: { url: '/market', filters: ['all', 'mine', 'exchange', 'auctions', 'for sale', 'sold'] }, - shop: { url: '/shops', filters: ['all', 'mine', 'recent'] }, + shop: { url: '/shops', filters: ['all', 'recent', 'mine'] }, job: { url: '/jobs', filters: ['ALL', 'MINE', 'REMOTE', 'PRESENCIAL', 'OPEN', 'CLOSED'] }, project: { url: '/projects', filters: ['all', 'mine', 'active', 'completed'] }, industry:{ url: '/industry', filters: ['ALL', 'MINE', 'ACTIVE', 'PAUSED', 'DISSOLVED', 'BLUEPRINTS', 'BUILDS', 'MEMBER', 'RULES'] }, chat: { url: '/chats', filters: ['all', 'mine'] }, pad: { url: '/pads', filters: ['all', 'mine'] }, calendar:{ url: '/calendars', filters: ['all', 'mine'] }, - report: { url: '/reports', filters: ['all', 'mine', 'recent'] }, + report: { url: '/reports', filters: ['all', 'recent', 'mine'] }, curriculum:{ url: '/cv', filters: ['view', 'edit'] } }; const sub = MODULE_SUB_FILTERS[filter]; + const emptyAct = actions.length === 0 && !qs; let html = template( title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header activity-header-line' }, h2(i18n.activityList), p(desc) ), - div({ class: 'activity-filter-grid' }, - ...(() => { - const COLUMNS = [ - ['all', 'mine', 'recent', 'top'], - ['inhabitants', 'tribe', 'larp', 'parliament', 'courts'], - ['votes', 'event', 'calendar', 'task', 'report'], - ['banking', 'market', 'housing', 'project', 'industry', 'job', 'shop', 'transfer'], - ['post', 'feed', 'chat', 'pad', 'forum', 'map'], - ['audio', 'bookmark', 'document', 'image', 'torrent', 'video'] + emptyAct ? null : div({ class: 'activity-filter-chips' }, + (() => { + const ORDER = [ + 'all', 'recent', 'mine', 'top', + 'inhabitants', 'tribe', 'larp', 'schoolCourse', 'parliament', 'courts', 'emergency', + 'votes', 'event', 'calendar', 'task', 'report', 'campaign', + 'banking', 'market', 'housing', 'project', 'industry', 'job', 'shop', 'logistics', + 'post', 'feed', 'chat', 'room', 'pad', 'wiki', 'mailing', 'forum', 'map', + 'audio', 'bookmark', 'document', 'file', 'image', 'torrent', 'video', 'podcast' ]; const byType = new Map(activityTypes.map(t => [t.type, t])); - const placed = new Set(COLUMNS.flat()); - const leftovers = activityTypes.filter(t => !placed.has(t.type)); - return COLUMNS - .map(col => col.map(type => byType.get(type)).filter(Boolean)) - .concat(leftovers.length ? [leftovers] : []) - .filter(col => col.length); - })().map(col => - div({ class: 'activity-filter-col' }, - col.map(({ type, label }) => - form({ method: 'GET', action: '/activity' }, - input({ type: 'hidden', name: 'filter', value: type }), - button({ type: 'submit', class: filter === type ? 'filter-btn active' : 'filter-btn' }, label) - ) - ) - ) - ) + const placed = new Set(ORDER); + const presentActionTypes = new Set(actions.map(a2 => a2.type)); + const metaKeep = new Set(['all', 'mine', 'recent', 'top']); + const hasContentFor = (type) => + metaKeep.has(type) || (GROUP_SUBTYPES[type] || [type]).some(t2 => presentActionTypes.has(t2)); + const ordered = ORDER.map(type => byType.get(type)).filter(Boolean) + .concat(activityTypes.filter(t => !placed.has(t.type))) + .filter(({ type }) => hasContentFor(type)); + return ordered.map(({ type, label }) => + a({ + href: `/activity?filter=${encodeURIComponent(type)}`, + class: filter === type ? 'activity-chip active' : 'activity-chip' + }, label) + ); + })() ), - sub - ? div({ class: 'activity-sub-filter' }, - sub.filters.map(f => a({ href: `${sub.url}?filter=${encodeURIComponent(f)}`, class: 'filter-btn' }, String(f).toUpperCase())) - ) - : null, - div({ class: 'filters' }, + emptyAct ? null : div({ class: 'activity-filter-chips activity-toolbar-row' }, + renderModuleStats(filteredActions.length, [], null, { showInhabitants: true, showTribes: true }), + sub ? span({ class: 'activity-subchip-label' }, '\u21b3') : '', + sub ? sub.filters.map(f => a({ href: `${sub.url}?filter=${encodeURIComponent(f)}`, class: 'activity-chip' }, String(f).toUpperCase())) : '', form({ method: 'GET', action: '/activity', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ type: 'text', name: 'q', value: qs, placeholder: i18n.activitySearchPlaceholder, class: 'filter-box__input' }), @@ -2159,15 +2322,14 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => { ) ) ), - section({ class: 'feed-container' }, renderActionCards(filteredActions, userId, actions, spreadMap, extras)) + section({ class: 'feed-container' }, renderActionCards(filteredActions, userId, rawActions, spreadMap, { ...extras, paged: true })) ) ); const hasDocument = actions.some(a => a && a.type === 'document'); if (hasDocument) { html += ` - - + `; } return html; diff --git a/src/views/agenda_view.js b/src/views/agenda_view.js index bdfdc56..e7e5512 100644 --- a/src/views/agenda_view.js +++ b/src/views/agenda_view.js @@ -1,19 +1,11 @@ const { div, h2, p, section, button, form, img, input, textarea, a, br, h1, span } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderContentActions } = require('./main_views'); +const { safeExternalHref } = require("../backend/renderStyledText"); +const { template, i18n, userLink, renderContentActions, renderModuleStats, renderStateChip, CONTENT_FAV_KIND, CONTENT_SPREADABLE, contentDeleteAction, paged } = require('./main_views'); const moment = require('../server/node_modules/moment'); const { config } = require('../server/SSB_server.js'); const userId = config.keys.id; -const renderCardField = (labelText, value) => - div({ class: 'card-field' }, - span({ class: 'card-label' }, labelText), - span( - { class: 'card-value' }, - ...(Array.isArray(value) ? value : [value ?? '']) - ) - ); - function getViewDetailsAction(item) { switch (item.type) { case 'transfer': return `/transfers/${encodeURIComponent(item.id)}`; @@ -26,144 +18,114 @@ function getViewDetailsAction(item) { case 'project': return `/projects/${encodeURIComponent(item.id)}`; case 'industry': return `/industry/build/${encodeURIComponent(item.id)}`; case 'housing': return `/housing/${encodeURIComponent(item.id)}`; + case 'schoolCourse': return `/school/course/${encodeURIComponent(item.id)}`; + case 'campaign': return `/campaigns/${encodeURIComponent(item.id)}`; + case 'logisticsRoute': return `/logistics/${encodeURIComponent(item.id)}`; case 'calendar': return `/calendars/${encodeURIComponent(item.id)}`; + case 'calendarDate': return `/calendars/${encodeURIComponent(item.calendarId)}`; default: return `/messages/${encodeURIComponent(item.id)}`; } } -const renderAgendaItem = (item, userId, filter) => { - const fmt = d => moment(d).format('YYYY/MM/DD HH:mm:ss'); +const agendaDeleteAction = (item) => { + if (item.type === 'transfer') { + const required = item.from === item.to ? 1 : 2; + const confirmed = Array.isArray(item.confirmedBy) ? item.confirmedBy.length : 0; + const dl = item.deadline ? moment(item.deadline) : null; + const expired = dl && dl.isValid() ? dl.isBefore(moment()) : false; + return String(item.status || '').toUpperCase() === 'UNCONFIRMED' && !expired && confirmed < required ? contentDeleteAction('transfer', item.id) : undefined; + } + return contentDeleteAction(item.type, item.id); +}; + +const chip = (text, kind = "whole", icon = null) => (text === null || text === undefined || text === "") ? null : renderStateChip(kind, icon, String(text).toUpperCase()); +const timeChip = (value, fmt = "YYYY/MM/DD HH:mm") => value ? p({ class: "time-chip" }, moment(value).format(fmt)) : null; +const metaLine = (text) => text && String(text).trim() ? p({ class: "job-meta-line" }, String(text)) : null; +const priceChip = (text) => text ? div({ class: "price-chip" }, text) : null; +const countLine = (labelText, value) => div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${labelText}: ${value}`)); +const statusKind = (status) => String(status || "").toUpperCase() === "CLOSED" ? "closed" : "mutuals"; + +const renderAgendaItem = (item, userId, filter, extras = {}) => { const author = item.seller || item.organizer || item.from || item.author || ''; - - const commonFields = [ - p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${item.createdAt ? moment(item.createdAt).format('YYYY/MM/DD HH:mm:ss') : ''} ${i18n.performed} `), - author ? userLink(author) : '' - ) - ]; - - let details = []; + const chips = []; + const body = []; let actionButton = null; if (filter === 'discarded') { - actionButton = form({ method: 'POST', action: `/agenda/restore/${encodeURIComponent(item.id)}` }, - button({ type: 'submit', class: 'restore-btn' }, i18n.agendaRestoreButton) + actionButton = form({ method: 'POST', action: `/agenda/restore/${encodeURIComponent(item.id)}`, class: 'phone-action-form' }, + button({ type: 'submit', class: 'tribe-action-btn' }, String(i18n.agendaRestoreButton).toUpperCase()) ); } else { - actionButton = form({ method: 'POST', action: `/agenda/discard/${encodeURIComponent(item.id)}` }, - button({ type: 'submit', class: 'discard-btn' }, i18n.agendaDiscardButton) + actionButton = form({ method: 'POST', action: `/agenda/discard/${encodeURIComponent(item.id)}`, class: 'phone-action-form' }, + button({ type: 'submit', class: 'tribe-action-btn' }, String(i18n.agendaDiscardButton).toUpperCase()) ); } + const extraActions = []; + if (filter === 'discarded') extraActions.push(form({ method: 'POST', action: `/agenda/remove/${encodeURIComponent(item.id)}`, class: 'phone-action-form' }, + button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, String(i18n.agendaRemoveButton).toUpperCase()) + )); if (item.type === 'market') { - details = [ - renderCardField(i18n.marketItemType + ":", String(item.item_type || '').toUpperCase()), - renderCardField(i18n.marketItemStatus + ":", item.status), - renderCardField(i18n.marketItemStock + ":", item.stock), - renderCardField(i18n.marketItemPrice + ":", `${item.price} ECO`), - renderCardField(i18n.marketItemIncludesShipping + ":", item.includesShipping ? i18n.agendaYes : i18n.agendaNo), - renderCardField(i18n.deadline + ":", item.deadline ? new Date(item.deadline).toLocaleString() : '') - ]; + chips.push(chip(item.item_type), chip(item.status, statusKind(item.status)), chip(`${i18n.marketItemStock}: ${item.stock}`), item.includesShipping ? chip(i18n.marketItemIncludesShipping, 'mutuals', '✓') : null); if (String(item.item_type || '').toLowerCase() === 'auction') { const bids = Array.isArray(item.auctions_poll) ? item.auctions_poll.map(bid => parseFloat(String(bid).split(':')[1])).filter(n => !isNaN(n)) : []; const maxBid = bids.length ? Math.max(...bids) : 0; - details.push(renderCardField(i18n.marketItemHighestBid + ":", `${maxBid} ECO`)); + chips.push(chip(`${i18n.marketItemHighestBid}: ${maxBid} ECO`, 'whole', '▲')); } - const seller = author ? p(userLink(author)) : ''; - details.push(br(), div({ class: 'members-list' }, i18n.marketItemSeller + ': ', seller)); + body.push(priceChip(`${item.price} ECO`), timeChip(item.deadline)); } if (item.type === 'tribe') { - details = [ - renderCardField(i18n.agendaAnonymousLabel + ":", item.isAnonymous ? i18n.agendaYes : i18n.agendaNo), - renderCardField(i18n.agendaInviteModeLabel + ":", (item.inviteMode ? String(item.inviteMode).toUpperCase() : i18n.noInviteMode)), - renderCardField(i18n.agendaLocationLabel + ":", item.location || i18n.noLocation), - renderCardField(i18n.agendaMembersCount + ":", Array.isArray(item.members) ? item.members.length : 0), - br() - ]; - const membersList = Array.isArray(item.members) ? item.members.map(member => p(userLink(member))) : []; - details.push(div({ class: 'members-list' }, `${i18n.agendaMembersLabel}:`, membersList)); + chips.push(item.isAnonymous ? chip(i18n.agendaAnonymousLabel, 'hidden') : null, chip(item.inviteMode ? item.inviteMode : i18n.noInviteMode)); + body.push(metaLine(item.location || i18n.noLocation), countLine(i18n.agendaMembersLabel, Array.isArray(item.members) ? item.members.length : 0)); } if (item.type === 'report') { - details = [ - renderCardField(i18n.agendareportStatus + ":", item.status || i18n.noStatus), - renderCardField(i18n.agendareportCategory + ":", item.category || i18n.noCategory), - renderCardField(i18n.agendareportSeverity + ":", (item.severity ? String(item.severity).toUpperCase() : i18n.noSeverity)) - ]; + chips.push(chip(item.status || i18n.noStatus, statusKind(item.status)), chip(item.category || i18n.noCategory), chip(item.severity || i18n.noSeverity, 'closed')); } if (item.type === 'event') { - details = [ - renderCardField(i18n.eventDateLabel + ":", item.date ? fmt(item.date) : ''), - renderCardField(i18n.eventLocationLabel + ":", item.location || ''), - renderCardField(i18n.eventPriceLabel + ":", `${item.price} ECO`), - renderCardField( - i18n.eventUrlLabel + ":", - item.url ? p(a({ href: item.url, target: "_blank" }, item.url)) : p(i18n.noUrl) - ) - ]; - actionButton = actionButton || form({ method: 'POST', action: `/events/attend/${encodeURIComponent(item.id)}` }, - button({ type: 'submit', class: 'assign-btn' }, `${i18n.eventAttendButton}`) - ); + body.push(timeChip(item.date), metaLine(item.location), parseFloat(item.price || 0) > 0 ? priceChip(`${item.price} ECO`) : null, + item.url ? p({ class: 'job-meta-line' }, a({ href: safeExternalHref(item.url), target: "_blank", rel: "noopener noreferrer" }, item.url)) : null); } if (item.type === 'task') { - details = [ - renderCardField(i18n.taskStatus + ":", item.status), - renderCardField(i18n.taskPriorityLabel + ":", item.priority), - renderCardField(i18n.taskStartTimeLabel + ":", item.startTime ? new Date(item.startTime).toLocaleString() : ''), - renderCardField(i18n.taskEndTimeLabel + ":", item.endTime ? new Date(item.endTime).toLocaleString() : ''), - renderCardField(i18n.taskLocationLabel + ":", item.location || '') - ]; - const assigned = Array.isArray(item.assignees) && item.assignees.includes(userId); - actionButton = actionButton || form({ method: 'POST', action: `/tasks/assign/${encodeURIComponent(item.id)}` }, - button({ type: 'submit', class: 'assign-btn' }, assigned ? i18n.taskUnassignButton : i18n.taskAssignButton) - ); + chips.push(chip(item.status, statusKind(item.status)), chip(item.priority, 'closed')); + body.push(timeChip(item.startTime), timeChip(item.endTime), metaLine(item.location)); } if (item.type === 'transfer') { - details = [ - renderCardField(i18n.agendaTransferConcept + ":", item.concept), - renderCardField(i18n.agendaTransferAmount + ":", item.amount), - renderCardField(i18n.agendaTransferDeadline + ":", item.deadline ? fmt(item.deadline) : ''), - br() - ]; - const membersList = item.to ? p(userLink(item.to)) : ''; - details.push(div({ class: 'members-list' }, i18n.to + ': ', membersList)); + body.push(metaLine(item.concept), priceChip(`${item.amount} ECO`), timeChip(item.deadline), + item.to && process.env.OASIS_MOBILE !== '1' ? p({ class: 'job-meta-line' }, `${i18n.to}: `, userLink(item.to)) : null); } - + if (item.type === 'project') { - details = [ - renderCardField(i18n.projectStatus + ":", item.status || i18n.noStatus), - renderCardField(i18n.projectProgress + ":", `${item.progress || 0}%`), - renderCardField(i18n.projectGoal + ":", `${item.goal} ECO`), - renderCardField(i18n.projectPledged + ":", `${item.pledged || 0} ECO`), - renderCardField(i18n.projectDeadline + ":", item.deadline ? new Date(item.deadline).toLocaleString() : i18n.noDeadline) - ]; + chips.push(chip(item.status || i18n.noStatus, statusKind(item.status)), chip(`${item.progress || 0}%`, 'mutuals')); + body.push(priceChip(`${item.pledged || 0} / ${item.goal} ECO`), item.deadline ? timeChip(item.deadline) : metaLine(i18n.noDeadline)); } if (item.type === 'calendar') { - details = [ - renderCardField((i18n.calendarStatusLabel || 'Status') + ':', item.isClosed ? (i18n.calendarStatusClosed || 'CLOSED') : (i18n.calendarStatusOpen || 'OPEN')), - renderCardField((i18n.calendarDeadlineLabel || 'Deadline') + ':', item.deadline ? moment(item.deadline).format('YYYY/MM/DD HH:mm') : ''), - renderCardField((i18n.calendarParticipantsLabel || 'Participants') + ':', Array.isArray(item.participants) ? item.participants.length : 0) - ]; + chips.push(chip(item.isClosed ? (i18n.calendarStatusClosed || 'CLOSED') : (i18n.calendarStatusOpen || 'OPEN'), item.isClosed ? 'closed' : 'mutuals')); + body.push(timeChip(item.deadline), countLine(i18n.calendarParticipantsLabel || 'Participants', Array.isArray(item.participants) ? item.participants.length : 0)); + } + + if (item.type === 'campaign') { + chips.push(chip(`${i18n.campaignSignaturesLabel}: ${item.signatureCount || 0} / ${item.goal || 0}`, 'mutuals', '✍')); + body.push(timeChip(item.deadline)); + } + + if (item.type === 'logisticsRoute') { + chips.push(chip(item.kind), chip(item.mode)); + body.push(metaLine(`${item.origin || ''} → ${item.destination || ''}`), timeChip(item.date)); } if (item.type === 'housing') { const isOwner = String(item.author) === String(userId); const requestCount = Number(item.requestCount) || 0; - details = [ - renderCardField(i18n.housingType + ":", (i18n["housingType" + String(item.housing_type || '').toUpperCase()] || item.housing_type || '').toUpperCase()), - renderCardField(i18n.housingStatus + ":", String(item.status || '').toUpperCase() === 'CLOSED' ? i18n.housingStatusCLOSED : i18n.housingStatusOPEN), - item.place ? renderCardField(i18n.housingPlace + ":", item.place) : null, - renderCardField(i18n.housingPrice + ":", String(item.housing_type) === 'couchsurfing' ? (i18n.housingFree || 'FREE') : `${item.price} ECO`), - item.availableFrom ? renderCardField(i18n.housingAvailableFrom + ":", moment(item.availableFrom).format('YYYY/MM/DD')) : null, - isOwner - ? renderCardField(i18n.housingRequests + ":", String(requestCount)) - : renderCardField(i18n.housingRequests + ":", i18n.housingRequestedBadge || 'REQUESTED') - ].filter(Boolean); + chips.push(chip(i18n["housingType" + String(item.housing_type || '').toUpperCase()] || item.housing_type), + chip(String(item.status || '').toUpperCase() === 'CLOSED' ? i18n.housingStatusCLOSED : i18n.housingStatusOPEN, statusKind(item.status)), + isOwner ? chip(`${i18n.housingRequests}: ${requestCount}`) : chip(i18n.housingRequestedBadge || 'REQUESTED', 'mutuals', '✓')); + body.push(metaLine(item.place), priceChip(String(item.housing_type) === 'couchsurfing' ? (i18n.housingFree || 'FREE') : `${item.price} ECO`), timeChip(item.availableFrom, 'YYYY/MM/DD')); } if (item.type === 'job') { @@ -171,114 +133,95 @@ const renderAgendaItem = (item, userId, filter) => { ? item.subscribers : (typeof item.subscribers === 'string' ? item.subscribers.split(',').map(s => s.trim()).filter(Boolean) - : (item.subscribers && typeof item.subscribers.length === 'number' - ? Array.from(item.subscribers) - : [])); - - const subsInterleaved = subs - .map((id, i) => [i > 0 ? ', ' : '', userLink(id)]) - .flat(); - - details = [ - renderCardField(i18n.jobStatus + ":", item.status), - renderCardField(i18n.jobLocation + ":", (item.location || '').toUpperCase()), - renderCardField(i18n.jobType + ":", (item.job_type || '').toUpperCase()), - renderCardField(i18n.jobSalary + ":", `${item.salary} ECO`), - renderCardField(i18n.jobVacants + ":", item.vacants), - renderCardField(i18n.jobLanguages + ":", (item.languages || '').toUpperCase()), - br(), - div( - { class: 'members-list' }, - i18n.jobSubscribers + ': ',br(),br(), - ...(subs.length ? subsInterleaved : [i18n.noSubscribers.toUpperCase()]) - ), - ]; - - const subscribed = subs.includes(userId); - if (!subscribed && String(item.status).toUpperCase() !== 'CLOSED' && item.author !== userId) { - actionButton = form({ method: 'POST', action: `/jobs/subscribe/${encodeURIComponent(item.id)}` }, - button({ type: 'submit', class: 'subscribe-btn' }, i18n.jobSubscribeButton) - ); - } + : (item.subscribers && typeof item.subscribers.length === 'number' ? Array.from(item.subscribers) : [])); + chips.push(chip(item.status, statusKind(item.status)), chip(item.job_type), chip(item.languages), chip(`${i18n.jobVacants}: ${item.vacants}`)); + body.push(metaLine(item.location), priceChip(`${item.salary} ECO`), countLine(i18n.jobSubscribers, subs.length)); } if (item.type === 'industry') { const st = String(item.status || 'PROPOSED').toUpperCase(); - details = [ - renderCardField((i18n.industryFacility || 'Facility') + ":", item.facilityName || ''), - renderCardField((i18n.industryStatusLabel || 'Status') + ":", i18n['industryBuildStatus_' + st] || st) - ]; + chips.push(chip(i18n['industryBuildStatus_' + st] || st, statusKind(st))); + body.push(metaLine(item.facilityName)); } const isOwn = author && String(author) === String(userId); + const favKind = CONTENT_FAV_KIND[item.type]; + const favIndex = extras.favIndex instanceof Map ? extras.favIndex : null; + const spreadMap = extras.spreadMap instanceof Map ? extras.spreadMap : null; + const href = getViewDetailsAction(item); + const title = item.title || item.name || item.concept || ''; return div({ class: 'trending-card agenda-card' + (isOwn ? ' own-content' : '') }, div({ class: 'card-header activity-card-header' }, span({ class: 'pm-exposition-chip pm-exposition-whole' }, span({ class: 'pm-exposition-text' }, String(item.type || '').toUpperCase()) ), - renderContentActions(item.id, getViewDetailsAction(item)) + renderContentActions(item.id, href, { + author, + reportTitle: title, + spread: CONTENT_SPREADABLE.has(item.type) ? ((spreadMap && spreadMap.get(item.id)) || null) : undefined, + ...(favKind ? { favKind, isFavorite: item.isFavorite === true || (!!favIndex && [item.rootId, item.id].some(id => id && favIndex.get(String(id)) === favKind)) } : {}), + returnTo: `/agenda?filter=${encodeURIComponent(filter || 'all')}`, + deleteAction: isOwn ? agendaDeleteAction(item) : undefined + }) ), div({ class: 'card-section agenda-card-body' }, - actionButton, - br(), - h2(item.title || item.name || item.concept || ''), - ...details, - ...commonFields + div({ class: 'shop-title-row' }, h2({ class: 'tribe-card-title' }, a({ href }, title))), + chips.filter(Boolean).length ? div({ class: 'card-chips-row' }, ...chips.filter(Boolean)) : null, + ...body.filter(Boolean), + p({ class: 'card-footer' }, + span({ class: 'date-link' }, `${item.createdAt ? moment(item.createdAt).format('YYYY/MM/DD HH:mm') : ''}`), + author ? userLink(author) : '' + ), + div({ class: 'agenda-card-actions' }, actionButton, ...extraActions) ) ); }; -exports.agendaView = async (data, filter, q = '') => { +exports.agendaView = async (data, filter, q = '', extras = {}) => { const { items = [], counts: _c = {} } = data || {}; const counts = { all: 0, open: 0, closed: 0, events: 0, tasks: 0, reports: 0, tribes: 0, jobs: 0, market: 0, projects: 0, transfers: 0, calendars: 0, housing: 0, discarded: 0, ..._c }; + const emptyAgenda = Number(counts.all || 0) === 0 && !String(q || '').trim(); return template( i18n.agendaTitle, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.agendaTitle), p(i18n.agendaDescription) ), - div({ class: 'filters' }, - form({ method: 'GET', action: '/agenda' }, - button({ type: 'submit', name: 'filter', value: 'all', class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterAll} (${counts.all})`), - button({ type: 'submit', name: 'filter', value: 'today', class: filter === 'today' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterToday || 'TODAY'} (${counts.today || 0})`), - button({ type: 'submit', name: 'filter', value: 'upcoming', class: filter === 'upcoming' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterUpcoming || 'UPCOMING'} (${counts.upcoming || 0})`), - button({ type: 'submit', name: 'filter', value: 'overdue', class: filter === 'overdue' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterOverdue || 'OVERDUE'} (${counts.overdue || 0})`), - button({ type: 'submit', name: 'filter', value: 'open', class: filter === 'open' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterOpen} (${counts.open})`), - button({ type: 'submit', name: 'filter', value: 'closed', class: filter === 'closed' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterClosed} (${counts.closed})`), - button({ type: 'submit', name: 'filter', value: 'events', class: filter === 'events' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterEvents} (${counts.events})`), - button({ type: 'submit', name: 'filter', value: 'tasks', class: filter === 'tasks' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterTasks} (${counts.tasks})`), - button({ type: 'submit', name: 'filter', value: 'reports', class: filter === 'reports' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterReports} (${counts.reports})`), - button({ type: 'submit', name: 'filter', value: 'tribes', class: filter === 'tribes' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterTribes} (${counts.tribes})`), - button({ type: 'submit', name: 'filter', value: 'jobs', class: filter === 'jobs' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterJobs} (${counts.jobs})`), - button({ type: 'submit', name: 'filter', value: 'market', class: filter === 'market' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterMarket} (${counts.market})`), - button({ type: 'submit', name: 'filter', value: 'projects', class: filter === 'projects' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterProjects} (${counts.projects})`), - button({ type: 'submit', name: 'filter', value: 'industry', class: filter === 'industry' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterIndustry || 'INDUSTRY'} (${counts.industry || 0})`), - button({ type: 'submit', name: 'filter', value: 'housing', class: filter === 'housing' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterHousing || 'HOUSING'} (${counts.housing || 0})`), - button({ type: 'submit', name: 'filter', value: 'calendars', class: filter === 'calendars' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterCalendars || 'CALENDARS'} (${counts.calendars})`), - button({ type: 'submit', name: 'filter', value: 'transfers', class: filter === 'transfers' ? 'filter-btn active' : 'filter-btn' }, - `${i18n.agendaFilterTransfers} (${counts.transfers})`), - button({ type: 'submit', name: 'filter', value: 'discarded', class: filter === 'discarded' ? 'filter-btn active' : 'filter-btn' }, - `DISCARDED (${counts.discarded})`) - ) + emptyAgenda ? null : div({ class: 'mode-buttons-row' }, + ...[ + ['all', i18n.agendaFilterAll], + ['today', i18n.agendaFilterToday || 'TODAY'], + ['upcoming', i18n.agendaFilterUpcoming || 'UPCOMING'], + ['overdue', i18n.agendaFilterOverdue || 'OVERDUE'], + ['open', i18n.agendaFilterOpen], + ['closed', i18n.agendaFilterClosed], + ['events', i18n.agendaFilterEvents], + ['tasks', i18n.agendaFilterTasks], + ['reports', i18n.agendaFilterReports], + ['tribes', i18n.agendaFilterTribes], + ['jobs', i18n.agendaFilterJobs], + ['market', i18n.agendaFilterMarket], + ['projects', i18n.agendaFilterProjects], + ['industry', i18n.agendaFilterIndustry || 'INDUSTRY'], + ['housing', i18n.agendaFilterHousing || 'HOUSING'], + ['school', i18n.agendaFilterSchool || 'SCHOOL'], + ['campaigns', i18n.agendaFilterCampaigns || 'CAMPAIGNS'], + ['logistics', i18n.agendaFilterLogistics || 'LOGISTICS'], + ['calendars', i18n.agendaFilterCalendars || 'CALENDARS'], + ['transfers', i18n.agendaFilterTransfers], + ['discarded', 'DISCARDED'] + ].filter(([value]) => value === 'all' || filter === value || Number(counts[value] || 0) > 0) + .map(([value, labelText]) => + form({ method: 'GET', action: '/agenda' }, + input({ type: 'hidden', name: 'filter', value }), + button({ type: 'submit', class: filter === value ? 'filter-btn active' : 'filter-btn' }, String(labelText).toUpperCase()))) ), - div({ class: 'filters' }, + emptyAgenda ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStats(items.length, [ + ['today', i18n.agendaFilterToday || 'TODAY'], ['upcoming', i18n.agendaFilterUpcoming || 'UPCOMING'], ['overdue', i18n.agendaFilterOverdue || 'OVERDUE'], + ['open', i18n.agendaFilterOpen], ['closed', i18n.agendaFilterClosed], ['discarded', 'DISCARDED'] + ].map(([value, label]) => ({ label: String(label).toUpperCase(), count: Number(counts[value] || 0) }))), form({ method: 'GET', action: '/agenda', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ type: 'text', name: 'q', value: q, placeholder: i18n.agendaSearchPlaceholder, class: 'filter-box__input' }), @@ -287,11 +230,9 @@ exports.agendaView = async (data, filter, q = '') => { ) ) ), - div({ class: 'agenda-list' }, - items.length - ? items.map(item => renderAgendaItem(item, userId, filter)) - : p(i18n.agendaNoItems) - ) + items.length + ? div({ class: 'jobs-grid agenda-list' }, ...paged(items).map(item => renderAgendaItem(item, userId, filter, extras || {}))) + : div({ class: 'no-content-box' }, p({ class: 'no-content' }, i18n.agendaNoItems)) ) ); }; diff --git a/src/views/audio_view.js b/src/views/audio_view.js index 5502f7a..cbe2e75 100644 --- a/src/views/audio_view.js +++ b/src/views/audio_view.js @@ -17,11 +17,12 @@ const { } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection, renderCommentsLink } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions , renderSpreadEditWarning } = require("./main_views"); +const { renderLicenseChip, renderLicenseSelect, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require('./clearnet_view'); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions , renderSpreadEditWarning, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl") -const { renderMapLocationVisitLabel } = require("./maps_view"); +const { renderStyledText } = require("../backend/renderStyledText") +const { renderMapLocationVisitLabel, renderMapEmbed } = require("./maps_view"); const userId = config.keys.id; @@ -82,13 +83,6 @@ const renderAudioOwnerActions = (filter, audioObj, params = {}) => { ) ); } - items.push( - form( - { method: "POST", action: `/audios/delete/${encodeURIComponent(audioObj.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.audioDeleteButton) - ) - ); return items; }; @@ -110,22 +104,32 @@ const renderAudioList = exports.renderAudioList = (audios, filter, params = {}) const title = safeText(audioObj.title); const isOwn = audioObj.author && String(audioObj.author) === String(userId); + const headerActions = typeof params.headerActions === "function" + ? params.headerActions(audioObj) + : audioObj.tribeOrigin + ? renderContentActions(null, audioObj.tribeOrigin.href) + : renderContentActions(audioObj.key, `/audios/${encodeURIComponent(audioObj.key)}`, { spread: (params.spreadMap && params.spreadMap.get(audioObj.key)) || params.spreads || null, author: audioObj.author, favKind: 'audios', torrentFrom: { blobId: audioObj.url, name: audioObj.title }, isFavorite: audioObj.isFavorite, reportTitle: audioObj.title, deleteAction: isOwn ? contentDeleteAction('audio', audioObj.key) : undefined, returnTo }); + const titleHref = typeof params.headerActions === "function" ? null : audioObj.tribeOrigin ? audioObj.tribeOrigin.href : `/audios/${encodeURIComponent(audioObj.key)}`; + const engagement = typeof params.engagement === "function" + ? params.engagement(audioObj) + : audioObj.tribeOrigin ? null : renderEngagement(audioObj.key, + renderOpinionsVoting('/audios/opinions', audioObj.key, audioObj.opinions, returnTo, audioObj.opinions_inhabitants), + renderCommentsLink({ href: `/audios/${encodeURIComponent(audioObj.key)}`, count: commentCount }) + ); return div( { class: "trending-card audio-card" + (isOwn ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(audioObj.key, `/audios/${encodeURIComponent(audioObj.key)}`, { spread: (params.spreadMap && params.spreadMap.get(audioObj.key)) || params.spreads || null, author: audioObj.author, favKind: 'audios', isFavorite: audioObj.isFavorite, reportTitle: audioObj.title }) + headerActions ), div( { class: "card-section audio-card-body" }, - title ? h2(title) : null, + div({ class: "shop-title-row" }, title ? h2(titleHref ? a({ href: titleHref, class: "card-title-link" }, title) : title) : null, audioObj.tribeOrigin ? renderTribeOriginChip(audioObj.tribeOrigin) : audioObj.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('audios', audioObj.title, audioObj.key)) : null, renderLicenseChip(audioObj.license), ...(typeof params.titleChips === "function" ? params.titleChips(audioObj) : [])), audioObj.lifetime ? div({ class: "card-chips-row" }, renderLifespanChip(audioObj.lifetime, i18n)) : null, renderAudioPlayer(audioObj), - renderEngagement(audioObj.key, - renderOpinionsVoting('/audios/opinions', audioObj.key, audioObj.opinions, returnTo, audioObj.opinions_inhabitants), - renderCommentsLink({ href: `/audios/${encodeURIComponent(audioObj.key)}`, count: commentCount }) - ), + ...(typeof params.bodyExtra === "function" ? params.bodyExtra(audioObj) : []), + engagement, renderMapLocationVisitLabel(audioObj.mapUrl), br(), (() => { @@ -135,7 +139,7 @@ const renderAudioList = exports.renderAudioList = (audios, filter, params = {}) return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(audioObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(audioObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(audioObj.author), showUpdated ? span( @@ -165,7 +169,7 @@ const renderAudioForm = (filter, audioId, audioToEdit, params = {}) => { input({ type: "hidden", name: "returnTo", value: returnTo }), span(i18n.audioFileLabel), br(), - input({ type: "file", name: "audio", required: filter !== "edit" }), + input({ type: "file", name: "audio", accept: "audio/*", required: filter !== "edit" }), br(), br(), span(i18n.audioTitleLabel), @@ -174,7 +178,7 @@ const renderAudioForm = (filter, audioId, audioToEdit, params = {}) => { br(), span(i18n.audioDescriptionLabel), br(), - textarea({ name: "description", placeholder: i18n.audioDescriptionPlaceholder, rows: "4" }, audioToEdit?.description || ""), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.audioDescriptionPlaceholder, rows: "4" }, audioToEdit?.description || ""), br(), span(i18n.mapLocationTitle || "Map Location"), br(), @@ -189,12 +193,26 @@ const renderAudioForm = (filter, audioId, audioToEdit, params = {}) => { value: safeArr(audioToEdit?.tags).join(", ") }), br(), + ...renderLicenseSelect(audioToEdit?.license, i18n), + br(), + renderClearnetSelector(filter === "edit" ? !!audioToEdit?.clearnet : false, i18n), br(), button({ type: "submit" }, filter === "edit" ? i18n.audioUpdateButton : i18n.audioCreateButton) ) ); }; +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.some((x) => !x.tribeOrigin); + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => !x.tribeOrigin && String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + exports.audioView = async (audios, filter = "all", audioId = null, params = {}) => { if (filter === "edit") params = { ...params, spreadWarning: await renderSpreadEditWarning(audioId) }; const title = i18n.audioTitle; @@ -203,36 +221,34 @@ exports.audioView = async (audios, filter = "all", audioId = null, params = {}) const sort = safeText(params.sort || "recent"); const list = safeArr(audios); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); const audioToEdit = audioId ? list.find((a) => a.key === audioId) : null; return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.audioDescription) ), - (() => { - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetAudios); - return div({ class: "shop-title-row" }, renderReachChip(isClearnet, i18n)); - })(), - br(), div( { class: "filters" }, form( { method: "GET", action: "/audios", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterRecent).toUpperCase()), - button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterFavorites).toUpperCase() - ), - button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterTop).toUpperCase()), - button({ type: "submit", name: "filter", value: "bcs", class: filter === "bcs" ? "filter-btn active" : "filter-btn" }, i18n.audioFilterBcs || "BCS"), + )] : []), + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterTop).toUpperCase())] : []), + ...(mediaChip("bcs") ? [button({ type: "submit", name: "filter", value: "bcs", class: filter === "bcs" ? "filter-btn active" : "filter-btn" }, i18n.audioFilterBcs || "BCS")] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.audioCreateButton) ) ) @@ -241,8 +257,9 @@ exports.audioView = async (audios, filter = "all", audioId = null, params = {}) filter === "create" || filter === "edit" ? renderAudioForm(filter, audioId, audioToEdit, { ...params, filter }) : section( - div( - { class: "audios-search" }, + emptyMod ? null : div( + { class: "audios-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form( { method: "GET", action: "/audios", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -265,21 +282,21 @@ exports.audioView = async (audios, filter = "all", audioId = null, params = {}) ) ) ), - div({ class: "audios-list" }, renderAudioList(list, filter, { q, sort })) + div({ class: "audios-list" }, renderAudioList(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) ) ) ); }; exports.singleAudioView = async (audioObj, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); const q = safeText(params.q || ""); const sort = safeText(params.sort || "recent"); const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q, sort }); const title = safeText(audioObj.title); const isAuthor = String(audioObj.author) === String(userId); - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetAudios); + const isClearnet = !!audioObj.clearnet; const chips = [ renderLifespanChip(audioObj.lifetime, i18n), @@ -288,16 +305,9 @@ exports.singleAudioView = async (audioObj, filter = "all", comments = [], params const ownerActions = renderAudioOwnerActions(filter, audioObj, { q, sort }); const sideActions = []; - if (audioObj.author && String(audioObj.author) !== String(userId)) { - sideActions.push(form( - { method: "GET", action: "/pm" }, - input({ type: "hidden", name: "recipients", value: audioObj.author }), - button({ type: "submit", class: "filter-btn" }, i18n.audioMessageAuthorButton) - )); - } if (audioObj.isBcs) { sideActions.push(form( - { method: "GET", action: `/melody/transcode/${encodeURIComponent(audioObj.key)}` }, + { method: "GET", action: `/melody/transcode/${encodeURIComponent(audioObj.key)}`, class: "audio-transcode-form" }, button({ type: "submit", class: "filter-btn" }, i18n.audioTranscodeButton || "TRANSCODE") )); } @@ -306,27 +316,30 @@ exports.singleAudioView = async (audioObj, filter = "all", comments = [], params const tagsNode = renderTags(audioObj.tags); const detailActions = div({ class: "card-header activity-card-header" }, - renderContentActions(audioObj.key, null, { + renderContentActions(audioObj.key, `/audios/${encodeURIComponent(audioObj.key)}`, { author: audioObj.author, - favKind: 'audios', + favKind: 'audios', torrentFrom: { blobId: audioObj.url, name: audioObj.title }, isFavorite: audioObj.isFavorite, spread: params.spreads || null, returnTo, - reportTitle: audioObj.title + reportTitle: audioObj.title, + deleteAction: isAuthor ? contentDeleteAction('audio', audioObj.key) : undefined }) ); const audioSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, title ? h2({ class: "tribe-card-title" }, title) : null, - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('audios', audioObj.title, audioObj.key) : null), + isAuthor ? renderClearnetSwitch('audios', audioObj.rootId || audioObj.key, isClearnet) : null, + renderLicenseChip(audioObj.license) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, safeText(audioObj.description) - ? p({ class: "tribe-side-description" }, ...renderUrl(audioObj.description)) + ? p({ class: "tribe-side-description" }, ...renderStyledText(audioObj.description)) : null, tagsNode, - renderMapLocationVisitLabel(audioObj.mapUrl), + renderMapEmbed(params.mapData, audioObj.mapUrl), sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null ); @@ -340,7 +353,7 @@ exports.singleAudioView = async (audioObj, filter = "all", comments = [], params return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(audioObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(audioObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(audioObj.author), showUpdated ? span( @@ -359,7 +372,7 @@ exports.singleAudioView = async (audioObj, filter = "all", comments = [], params return template( i18n.audioTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.audioAllSectionTitle || i18n.audioTitle), p(i18n.audioDescription) ), @@ -370,14 +383,14 @@ exports.singleAudioView = async (audioObj, filter = "all", comments = [], params input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterRecent).toUpperCase()), - button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterFavorites).toUpperCase() - ), + )] : []), button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.audioFilterTop).toUpperCase()), - button({ type: "submit", name: "filter", value: "bcs", class: filter === "bcs" ? "filter-btn active" : "filter-btn" }, i18n.audioFilterBcs || "BCS"), + ...(mediaChip("bcs") ? [button({ type: "submit", name: "filter", value: "bcs", class: filter === "bcs" ? "filter-btn active" : "filter-btn" }, i18n.audioFilterBcs || "BCS")] : []), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.audioCreateButton) ) ), @@ -397,7 +410,7 @@ exports.audioTranscodeDetailView = async ({ audio, decoded = false, stegoPayload return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.audioTranscodeDetailDescription || "Decode the embedded payload and the original blockchain composition map.") ), @@ -419,9 +432,8 @@ exports.audioTranscodeDetailView = async ({ audio, decoded = false, stegoPayload ), safeText(audio.description) ? p({ class: "melody-bcs-desc" }, audio.description) : null, renderTags(audio.tags), - br(), - form({ method: "POST", action: `/melody/transcode/${encodeURIComponent(audio.key)}`, class: "audio-transcode-run-form" }, - button({ type: "submit", class: "filter-btn" }, i18n.audioTranscodeButton || "TRANSCODE") + form({ method: "POST", action: `/melody/transcode/${encodeURIComponent(audio.key)}`, class: "audio-transcode-run-form audio-transcode-form" }, + button({ type: "submit", class: "create-button" }, i18n.audioTranscodeButton || "TRANSCODE") ), br(), decoded @@ -433,7 +445,6 @@ exports.audioTranscodeDetailView = async ({ audio, decoded = false, stegoPayload span({ class: "card-value" }, stegoDate || (i18n.audioTranscodeStegoUnknown || "—")) ), div({ class: "transcode-stego-field" }, - span({ class: "card-label" }, (i18n.audioTranscodeStegoOasisId || "By") + ": "), stegoPayload.id ? userLink(stegoPayload.id) : span({ class: "card-value" }, i18n.audioTranscodeStegoUnknown || "—") ), div({ class: "transcode-stego-field transcode-stego-msg" }, diff --git a/src/views/backup_view.js b/src/views/backup_view.js new file mode 100644 index 0000000..f3e401c --- /dev/null +++ b/src/views/backup_view.js @@ -0,0 +1,197 @@ +const { div, h2, h3, p, section, button, form, a, span, br, hr, input, label, select, option, pre, img, ul, li, strong, progress } = require("../server/node_modules/hyperaxe"); +const { template, i18n } = require("./main_views"); +const moment = require("../server/node_modules/moment"); +const crypto = require("crypto"); + +const generateRandomPassword = (length = 32) => crypto.randomBytes(length).toString("hex").slice(0, length); +const fmt = (d) => moment(d).format("YYYY/MM/DD HH:mm"); +const humanBytes = (n) => { + const b = Number(n) || 0; + if (b < 1024) return `${b} B`; + if (b < 1024 * 1024) return `${(b / 1024).toFixed(1)} KB`; + if (b < 1024 * 1024 * 1024) return `${(b / (1024 * 1024)).toFixed(1)} MB`; + return `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`; +}; + +const BACKUP_TYPES = ["RECOVERY", "KEYS", "INSTANT", "FULL", "RESTORE"]; +const typeLabel = (t) => String(i18n[`backupType${t.charAt(0) + t.slice(1).toLowerCase()}`] || t).toUpperCase(); +const normalizeType = (t) => (BACKUP_TYPES.includes(String(t || "").toUpperCase()) ? String(t).toUpperCase() : "RECOVERY"); + +const renderTypeFilters = (type) => + div({ class: "filters" }, + form({ method: "GET", action: "/backup", class: "ui-toolbar ui-toolbar--filters" }, + ...BACKUP_TYPES.map(t => button({ type: "submit", name: "type", value: t, class: type === t ? "filter-btn active" : "filter-btn" }, typeLabel(t))) + ) + ); + +const renderRandomPassword = () => p({ id: "randomPassword" }, h2({ class: "generated-password" }, generateRandomPassword())); + +const renderRecovery = (kit) => kit + ? div({ class: "backup-kit" }, + p({ class: "backup-kit-line" }, strong(`${i18n.backupKitId}: `), span({ class: "backup-kit-id" }, kit.id || "—")), + p({ class: "backup-kit-line" }, strong(`${i18n.backupKitDate}: `), fmt(kit.createdAt)), + pre({ class: "backup-kit-secret" }, kit.secret), + div({ class: "backup-kit-qr" }, img({ src: "/backup/recovery-kit/qr.png", alt: i18n.backupKitQrAlt, class: "backup-kit-qr-img" })), + ul({ class: "backup-kit-steps" }, + li(i18n.backupKitStep1), + li(i18n.backupKitStep2), + li(strong(i18n.backupKitStep3)) + ), + div({ class: "doc-export-actions doc-export-left" }, + form({ method: "GET", action: "/backup/recovery-kit/pdf" }, button({ type: "submit", class: "filter-btn" }, i18n.backupKitPdfButton)) + ) + ) + : p({ class: "backup-warning" }, i18n.backupKitPageDescription); + +const renderKeysExport = () => + div({ class: "backup-section" }, + renderRandomPassword(), + p(i18n.exportDescription), + form({ action: "/backup/keys/export", method: process.env.OASIS_MOBILE === '1' ? "GET" : "POST", id: "exportForm" }, + label(i18n.exportPasswordLabel), br(), + input({ type: "password", name: "password", id: "password", required: true, placeholder: i18n.exportPasswordPlaceholder, minlength: 32 }), br(), + p({ class: "file-info" }, i18n.fileInfo), + button({ type: "submit" }, i18n.backupKeysExportButton) + ) + ); + +const renderFullBackup = (options) => { + const opt = options || {}; + const sinceValue = opt.sinceTs ? moment(opt.sinceTs).format("YYYY-MM-DD") : ""; + return div({ class: "backup-section" }, + renderRandomPassword(), + form({ method: process.env.OASIS_MOBILE === '1' ? "GET" : "POST", action: "/backup/export" }, + label(i18n.backupScopeLabel), br(), + select({ name: "scope" }, + option({ value: "all", ...(opt.scope !== "mine" ? { selected: true } : {}) }, i18n.backupScopeAll), + option({ value: "mine", ...(opt.scope === "mine" ? { selected: true } : {}) }, i18n.backupScopeMine) + ), br(), br(), + label(i18n.backupSinceLabel), br(), + input({ type: "date", name: "since", value: sinceValue }), br(), br(), + label(i18n.exportPasswordLabel), br(), + input({ type: "password", name: "password", required: true, placeholder: i18n.exportPasswordPlaceholder, minlength: 32 }), br(), + p({ class: "file-info" }, i18n.backupFileInfo), + button({ type: "submit" }, i18n.backupExportButton) + ) + ); +}; + +const restoreSummary = (r) => + `${i18n.backupRestoredMessages}: ${r.messages} · ${i18n.backupRestoredSkipped}: ${r.skipped} · ${i18n.backupRestoredBlobs}: ${r.blobs}${r.files ? ` · ${i18n.backupRestoredState}: ${r.files}` : ""}${r.forked ? ` · ${i18n.backupRestoredForked}: ${r.forked}` : ""}${r.failed ? ` · ${i18n.backupRestoredFailed}: ${r.failed}` : ""}`; + +const renderRestoreStatus = (job) => { + if (!job) return null; + if (job.running) { + const elapsed = Math.max(0, Math.round((Date.now() - Date.parse(job.startedAt)) / 1000)); + const pct = Math.max(0, Math.min(100, Number((job.progress || {}).percent) || 0)); + return div({ class: "backup-restored" }, + p({ class: "backup-warning" }, `⏳ ${i18n.backupRestoreRunning}`), + div({ class: "indexing-progress-block" }, + progress({ value: String(pct), max: "100", class: "indexing-progress" }), + p({ class: "indexing-percent" }, strong(`${pct.toFixed(1)} %`)) + ), + p(`${restoreSummary(job.progress || {})} · ${i18n.backupRestoreElapsed}: ${elapsed}s`) + ); + } + if (job.error) return div({ class: "backup-restored" }, p({ class: "backup-warning" }, `✗ ${i18n.backupRestoreFailedLine} ${job.error}`)); + const r = job.result || {}; + const mine = (r.forks || []).some(f => f.mine); + return div({ class: "backup-restored" }, + p(`✓ ${i18n.backupRestoredLine} ${restoreSummary(r)}`), + mine ? p({ class: "backup-warning" }, `⚠ ${i18n.backupRestoreForkMine}`) : null + ); +}; + +const renderRestore = (job) => + div({ class: "backup-section" }, + renderRestoreStatus(job), + job && job.running ? null : form({ method: "POST", action: "/backup/import", enctype: "multipart/form-data" }, + input({ type: "file", name: "uploadedFile", required: true, accept: ".oasisbk" }), br(), br(), + p(i18n.backupRestorePassword), + input({ type: "password", name: "importPassword", required: true, placeholder: i18n.importPasswordPlaceholder, minlength: 32 }), br(), + button({ type: "submit" }, i18n.backupRestoreButton) + ), + br(), + p(i18n.importDescription), + form({ action: "/backup/keys/import", method: "POST", enctype: "multipart/form-data" }, + input({ type: "file", name: "uploadedFile", required: true }), br(), br(), + p(i18n.passwordImport), + input({ type: "password", name: "importPassword", required: true, placeholder: i18n.importPasswordPlaceholder, minlength: 32 }), br(), + button({ type: "submit" }, i18n.backupKeysImportButton) + ) + ); + +const renderInstantBackup = () => + div({ class: "backup-form" }, + p({ class: "backup-hint" }, i18n.backupInstantDescription, strong(i18n.backupInstantRisk)), + form({ action: "/export/create", method: "POST" }, + button({ type: "submit" }, i18n.exportDataButton) + ) + ); + +exports.backupView = async ({ type = "RECOVERY", options = null, restoreJob = null, kit = null } = {}) => { + const t = normalizeType(type); + const body = t === "RECOVERY" ? renderRecovery(kit) + : t === "KEYS" ? renderKeysExport() + : t === "FULL" ? renderFullBackup(options) + : t === "INSTANT" ? renderInstantBackup() + : renderRestore(restoreJob); + const html = template( + i18n.backupTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.backupTitle), p(i18n.backupDescription)), + renderTypeFilters(t) + ), + section( + div({ class: "div-center backup-wrap" }, body) + ) + ); + return restoreJob && restoreJob.running ? html.replace('', '') : html; +}; + +exports.renderRestoreStatus = renderRestoreStatus; +exports.recoveryKitView = async (kit) => exports.backupView({ type: "RECOVERY", kit }); + +exports.renderRebuildReport = (report) => { + if (!report) return null; + const idx = report.indexes || {}; + const line = (ok, text) => li({ class: ok ? "backup-check-ok" : "backup-check-bad" }, `${ok ? "✓" : "✗"} ${text}`); + return div({ class: "backup-verification" }, + p({ class: "backup-hint" }, `${fmt(report.checkedAt)} · ${report.tookMs} ms · ${i18n.verificationTotalMessages}: ${report.totalMessages}`), + ul( + report.ok + ? line(true, `${i18n.indexesRebuilt}`) + : line(false, `${i18n.indexesRebuildFailed}: ${report.error || ''}`), + li({ class: "backup-check-ok" }, `· ${i18n.indexesFiles}: ${idx.files || 0} (${humanBytes(idx.bytes || 0)})`) + ) + ); +}; + +exports.renderVerificationReport = (report) => { + if (!report) return null; + const feed = report.feed || {}; + const blobs = report.blobs || {}; + const line = (ok, text) => li({ class: ok ? "backup-check-ok" : "backup-check-bad" }, `${ok ? "✓" : "✗"} ${text}`); + return div({ class: "backup-verification" }, + p({ class: "backup-hint" }, `${fmt(report.checkedAt)} · ${report.tookMs} ms · ${i18n.verificationTotalMessages}: ${report.totalMessages}`), + ul( + line(feed.ok, `${i18n.verificationFeed}: ${feed.messages} ${i18n.verificationMessages}, ${i18n.verificationLastSequence} ${feed.lastSequence}`), + feed.gaps && feed.gaps.length ? li({ class: "backup-check-bad" }, `✗ ${i18n.verificationGaps}: ${feed.gaps.map(g => `${g.expected}→${g.found}`).join(", ")}`) : null, + feed.brokenLinks && feed.brokenLinks.length ? li({ class: "backup-check-bad" }, `✗ ${i18n.verificationBrokenLinks}: ${feed.brokenLinks.join(", ")}`) : null, + feed.badHashes && feed.badHashes.length ? li({ class: "backup-check-bad" }, `✗ ${i18n.verificationBadHashes}: ${feed.badHashes.join(", ")}`) : null, + feed.badSignatures && feed.badSignatures.length ? li({ class: "backup-check-bad" }, `✗ ${i18n.verificationBadSignatures}: ${feed.badSignatures.join(", ")}`) : null, + feed.duplicates && feed.duplicates.length ? li({ class: "backup-check-bad" }, `✗ ${i18n.verificationDuplicates}: ${feed.duplicates.join(", ")}`) : null, + line(!report.forkCount, `${i18n.verificationForks}: ${report.forkCount}`), + ...(report.forks || []).map(f => li({ class: "backup-check-bad backup-fork" }, `${f.mine ? "⚠ " : ""}${f.author} · ${f.points.map(pt => `#${pt.sequence} (${pt.keys.length})`).join(", ")}`)), + blobs.listed + ? line(!blobs.missingOwn, `${i18n.verificationBlobs}: ${blobs.present} ${i18n.verificationBlobsPresent}, ${blobs.referenced} ${i18n.verificationBlobsReferenced}, ${blobs.orphan} ${i18n.verificationBlobsOrphan} (${humanBytes(blobs.orphanBytes)})`) + : li(`· ${i18n.verificationBlobsUnavailable}`), + blobs.listed && blobs.missingOwn + ? li({ class: "backup-check-bad" }, `✗ ${i18n.verificationBlobsMissingOwn}: ${blobs.missingOwn}`) + : null, + blobs.listed && (blobs.missing - (blobs.missingOwn || 0)) > 0 + ? li({ class: "backup-check-warn" }, `⚠ ${i18n.verificationBlobsMissingOthers}: ${blobs.missing - (blobs.missingOwn || 0)}`) + : null + ) + ); +}; diff --git a/src/views/banking_views.js b/src/views/banking_views.js index 69f9ffc..44337f0 100644 --- a/src/views/banking_views.js +++ b/src/views/banking_views.js @@ -1,9 +1,11 @@ -const { div, h2, h3, p, section, button, form, a, input, span, pre, table, thead, tbody, tr, td, th, br, strong, label } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, formatCarbon } = require("../views/main_views"); +const { div, h2, h3, p, section, button, form, a, input, span, pre, table, thead, tbody, tr, td, th, br, strong, label, ul, li } = require("../server/node_modules/hyperaxe"); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const { template, i18n, userLink, formatCarbon, renderStateChip, renderModuleStats, renderWalletChip, paged } = require("../views/main_views"); const moment = require("../server/node_modules/moment"); const FILTER_LABELS = { overview: i18n.bankOverview, + ubi: i18n.bankUbiTab, exchange: i18n.bankExchange, taxes: i18n.bankTaxes || "Taxes", mine: i18n.mine, @@ -63,79 +65,254 @@ const escAttr = (s) => String(s) .replace(/"/g, '"') .replace(/'/g, '''); -const buildEcoValueChartSvg = (history, labels) => { - const arr = Array.isArray(history) ? history.slice(-120) : []; - const W = 720, H = 320; - const padL = 56, padR = 16, padT = 16, padB = 70; +const CHART_LINE_CLASSES = ["value", "supply", "inflation"]; +const legendLayout = (labels, x0, maxX) => { + const rows = [[]]; + let x = x0; + for (const label of labels) { + const width = 18 + String(label).length * 6.6 + 24; + if (x + width > maxX && rows[rows.length - 1].length) { rows.push([]); x = x0; } + rows[rows.length - 1].push({ label, x }); + x += width; + } + return rows; +}; + +const axisDecimals = (max, fallback) => { + const m = Math.abs(Number(max) || 0); + if (m >= 10000) return 0; + if (m >= 100) return 2; + return fallback; +}; + +const buildSeriesChartSvg = ({ series = [], xLabels = [], empty = "", yDecimals = 4, yDomain = null }) => { + const valid = series.filter(s => Array.isArray(s.points) && s.points.length >= 2); + const W = 720; + const padR = 16, padT = 16; + const first = valid[0] ? valid[0].points.map(v => Number(v) || 0) : [0]; + const yMax = yDomain ? Number(yDomain.max) : Math.max(...first); + const yMin = yDomain ? Number(yDomain.min) : Math.min(...first); + const decimals = axisDecimals(Math.max(Math.abs(yMax), Math.abs(yMin)), yDecimals); + const widestLabel = Math.max(...[yMax, yMin].map(v => v.toFixed(decimals).length)); + const padL = Math.max(56, Math.round(widestLabel * 6.2) + 14); + const legendRows = legendLayout(valid.map(s => s.label || ""), padL, W - padR); + const padB = 54 + 16 * Math.max(1, legendRows.length); + const H = 250 + padB; const plotW = W - padL - padR; const plotH = H - padT - padB; - if (arr.length < 2) { + if (!valid.length) { return `` + `` - + `${escAttr(labels.empty || 'Not enough samples yet')}` + + `${escAttr(empty || 'Not enough samples yet')}` + ``; } - const values = arr.map(s => Number(s.ecoValue || 0)); - const supplies = arr.map(s => Number(s.currentSupply || 0)); - const inflations = arr.map(s => Number(s.inflationFactor || 0)); - const minV = Math.min(...values); - const maxV = Math.max(...values); - const rangeV = maxV - minV || 1; - const minS = Math.min(...supplies); - const maxS = Math.max(...supplies); - const rangeS = maxS - minS || 1; - const minI = Math.min(...inflations); - const maxI = Math.max(...inflations); - const rangeI = maxI - minI || 1; - const stepX = arr.length > 1 ? plotW / (arr.length - 1) : plotW; - const xy = (i, v, minR, rangeR) => { - const x = padL + i * stepX; - const y = padT + plotH - ((v - minR) / rangeR) * plotH; - return `${x.toFixed(2)},${y.toFixed(2)}`; - }; - const pointsValue = values.map((v, i) => xy(i, v, minV, rangeV)).join(' '); - const pointsSupply = supplies.map((v, i) => xy(i, v, minS, rangeS)).join(' '); - const pointsInfl = inflations.map((v, i) => xy(i, v, minI, rangeI)).join(' '); - const tsStart = moment(arr[0].ts).format('YYYY-MM-DD HH:mm'); - const tsEnd = moment(arr[arr.length - 1].ts).format('YYYY-MM-DD HH:mm'); - const tsMid = moment(arr[Math.floor(arr.length / 2)].ts).format('YYYY-MM-DD HH:mm'); + const lines = valid.map((s, idx) => { + const vals = s.points.map(v => Number(v) || 0); + const min = yDomain ? yMin : Math.min(...vals); + const max = yDomain ? yMax : Math.max(...vals); + const range = max - min || 1; + const stepX = plotW / (vals.length - 1); + const pts = vals.map((v, i) => `${(padL + i * stepX).toFixed(2)},${(padT + plotH - ((v - min) / range) * plotH).toFixed(2)}`).join(" "); + return { cls: CHART_LINE_CLASSES[idx % CHART_LINE_CLASSES.length], label: s.label || "", pts, min, max, range }; + }); const yTicks = 4; const grid = []; for (let i = 0; i <= yTicks; i++) { const y = padT + (plotH / yTicks) * i; grid.push(``); - const val = maxV - (rangeV / yTicks) * i; - grid.push(`${val.toFixed(4)}`); + const val = lines[0].max - (lines[0].range / yTicks) * i; + grid.push(`${val.toFixed(decimals)}`); } const xLabelY = padT + plotH + 16; - const xLabels = `${escAttr(tsStart)}` - + `${escAttr(tsMid)}` - + `${escAttr(tsEnd)}`; - const legendY = padT + plotH + 44; - const legendBaseX = padL; - const legend = `` - + `` - + `${escAttr(labels.value || 'Value')}` - + `` - + `${escAttr(labels.supply || 'Supply')}` - + `` - + `${escAttr(labels.inflation || 'Inflation')}` - + ``; + const [xs = "", xm = "", xe = ""] = xLabels; + const xLabelsSvg = `${escAttr(xs)}` + + `${escAttr(xm)}` + + `${escAttr(xe)}`; + const legendY0 = padT + plotH + 44; + const legend = `` + legendRows.map((row, r) => row.map((item, i) => { + const idx = legendRows.slice(0, r).reduce((acc, rr) => acc + rr.length, 0) + i; + const l = lines[idx]; + const y = legendY0 + r * 16; + return `` + + `${escAttr(l.label)}`; + }).join("")).join("") + ``; return `` + `` - + grid.join('') - + `` - + `` - + `` - + xLabels + + grid.join("") + + lines.slice().reverse().map(l => ``).join("") + + xLabelsSvg + legend + ``; }; -const renderExchange = (ex, history, taxStats) => { +const tsLabels = (arr, key) => { + if (!arr.length) return ["", "", ""]; + const f = (x) => moment(x[key]).format("YYYY-MM-DD HH:mm"); + return [f(arr[0]), f(arr[Math.floor(arr.length / 2)]), f(arr[arr.length - 1])]; +}; + +const buildEcoValueChartSvg = (history, labels) => { + const arr = Array.isArray(history) ? history.slice(-120) : []; + return buildSeriesChartSvg({ + series: [ + { label: labels.value || "Value", points: arr.map(s => Number(s.ecoValue || 0)) }, + { label: labels.supply || "Supply", points: arr.map(s => Number(s.currentSupply || 0)) }, + { label: labels.inflation || "Inflation", points: arr.map(s => Number(s.inflationFactor || 0)) } + ], + xLabels: tsLabels(arr, "ts"), + empty: labels.empty + }); +}; + +const renderChartBlock = (id, title, svg) => + div({ class: "bank-eco-chart-block" }, + h2({ class: "bank-eco-chart-title" }, title), + a({ href: `#${id}`, id: `${id}-src`, class: "bank-eco-chart-zoom-link", title: i18n.bankChartZoomHint }, + div({ class: "bank-eco-chart-canvas", [RAW_HTML]: svg }) + ), + div({ id, class: "lightbox bank-eco-chart-lightbox" }, + a({ href: `#${id}-src`, class: "lightbox-close" }, "\u00d7"), + div({ class: "bank-eco-chart-canvas-zoom", [RAW_HTML]: svg }) + ) + ); + +const renderRangedChart = (id, title, filterName, range, hasAnyData, svg) => { + if (!hasAnyData) return null; + return div({ class: "bank-eco-chart-block", id: `${id}-block` }, + h2({ class: "bank-eco-chart-title" }, title), + renderRangeTabs(filterName, range, `${id}-block`), + svg ? a({ href: `#${id}`, id: `${id}-src`, class: "bank-eco-chart-zoom-link", title: i18n.bankChartZoomHint }, div({ class: "bank-eco-chart-canvas", [RAW_HTML]: svg })) : p({ class: "bank-chart-empty-note" }, i18n.bankChartNoDataYet), + svg ? div({ id, class: "lightbox bank-eco-chart-lightbox" }, a({ href: `#${id}-src`, class: "lightbox-close" }, "\u00d7"), div({ class: "bank-eco-chart-canvas-zoom", [RAW_HTML]: svg })) : null + ); +}; + +const bucketLabel = (bucket) => bucket === "month" ? i18n.bankChartPerMonth : bucket === "day" ? i18n.bankChartPerDay : i18n.bankChartPerHour; +const bucketXLabels = (pts, bucket) => { + const fmt = (x) => bucket === "month" ? moment(x.ts).format("YYYY-MM") : bucket === "day" ? moment(x.ts).format("YYYY-MM-DD") : moment(x.ts).format("YYYY-MM-DD HH:mm"); + return pts.length ? [fmt(pts[0]), fmt(pts[Math.floor(pts.length / 2)]), fmt(pts[pts.length - 1])] : ["", "", ""]; +}; + +const renderBucketChart = (id, title, filterName, data, field, label, decimals = 2) => { + const pts = Array.isArray(data && data.points) ? data.points : []; + const svg = pts.length >= 2 ? buildSeriesChartSvg({ + series: [{ label: `${label} ${bucketLabel(data.bucket)}`, points: pts.map(p => Number(p[field]) || 0) }], + xLabels: bucketXLabels(pts, data.bucket), + empty: i18n.bankExchangeChartEmpty, yDecimals: decimals + }) : null; + return renderRangedChart(id, title, filterName, (data && data.range) || "today", !!(data && data.hasAnyData), svg); +}; + +const renderSupplyChart = (history, range, hasAnyData, totalSupply = 0) => { + const arr = Array.isArray(history) ? history.slice(-240) : []; + const total = Number(totalSupply) || 0; + const svg = arr.length >= 2 ? buildSeriesChartSvg({ + series: [{ label: i18n.bankCurrentSupply, points: arr.map(s => Number(s.currentSupply) || 0) }], + xLabels: tsLabels(arr, "ts"), + empty: i18n.bankExchangeChartEmpty, yDecimals: 0 + }) : null; + return renderRangedChart("eco-supply-chart", i18n.bankChartSupplyTitle, "exchange", range, !!hasAnyData, svg); +}; + +const renderInflationChart = (history, range, hasAnyData) => { + const arr = Array.isArray(history) ? history.slice(-240) : []; + const svg = arr.length >= 2 ? buildSeriesChartSvg({ + series: [{ label: i18n.bankChartInflationSeries, points: arr.map(s => Number(s.inflationFactor) || 0) }], + xLabels: tsLabels(arr, "ts"), + empty: i18n.bankExchangeChartEmpty, yDecimals: 2 + }) : null; + return renderRangedChart("eco-inflation-chart", i18n.bankChartInflationTitle, "exchange", range, !!hasAnyData, svg); +}; + +const renderUbiCharts = (charts) => { + const pool = Array.isArray(charts && charts.pool) ? charts.pool : []; + const empty = i18n.bankExchangeChartEmpty; + const poolSvg = pool.length >= 2 ? buildSeriesChartSvg({ + series: [{ label: i18n.bankChartPool, points: pool.map(x => x.balance) }], + xLabels: tsLabels(pool, "ts"), + empty, yDecimals: 2 + }) : null; + const out = [ + renderBucketChart("ubi-chart-network", i18n.bankChartNetworkUbiTitle, "ubi", charts && charts.payments, "distributed", i18n.bankChartDistributed, 6), + renderRangedChart("ubi-chart-pool", i18n.bankChartPoolTitle, "ubi", (charts && charts.poolRange) || "today", !!(charts && charts.hasPool), poolSvg), + renderBucketChart("ubi-chart-rebalance", i18n.bankChartRebalanceTitle, "ubi", charts && charts.rebalances, "rebalanced", i18n.bankChartRebalanced, 6) + ].filter(Boolean); + return out.length ? div(...out) : null; +}; + +const RANGES = ["today", "week", "month", "year", "5y", "all"]; +const rangeLabel = (r) => ({ today: i18n.bankRangeToday, week: i18n.bankRangeWeek, month: i18n.bankRangeMonth, year: i18n.bankRangeYear, "5y": i18n.bankRangeFiveYears, all: i18n.bankRangeAll })[r] || r; +const renderRangeTabs = (filter, current, anchor) => + div({ class: "bank-range-tabs" }, + ...RANGES.map(r => a({ href: `/banking?filter=${filter}&range=${r}#${anchor}`, class: r === current ? "activity-chip active" : "activity-chip" }, String(rangeLabel(r)).toUpperCase())) + ); + +const renderValueChart = (history, range, hasAnyData) => { + const arr = Array.isArray(history) ? history.slice(-240) : []; + if (!hasAnyData) return null; + const hasTime = arr.some(s => Number(s.ecoTimeMs) > 0); + const hasInflation = arr.some(s => Number(s.inflationFactor) > 0); + const svg = arr.length >= 2 ? buildSeriesChartSvg({ + series: [ + { label: i18n.bankExchangeChartValue || "Value (ECO/h)", points: arr.map(s => Number(s.ecoValue || 0)) }, + ...(hasTime ? [{ label: i18n.bankChartValueHours, points: arr.map(s => Number(s.ecoTimeMs || 0) / 3600000) }] : []), + ...(hasInflation ? [{ label: i18n.bankExchangeChartInflation || "Inflation %", points: arr.map(s => Number(s.inflationFactor || 0)) }] : []) + ], + xLabels: tsLabels(arr, "ts"), + empty: i18n.bankExchangeChartEmpty + }) : null; + return renderRangedChart("eco-value-chart", i18n.bankChartValueTitle, "exchange", range, !!hasAnyData, svg); +}; + +const renderWealthChart = (wealth, range) => { + const pts = Array.isArray(wealth && wealth.points) ? wealth.points : []; + if (!(wealth && wealth.hasAnyData)) return null; + if (pts.length < 2) return div({ class: "bank-eco-chart-block", id: "wealth-chart-block" }, + h2({ class: "bank-eco-chart-title" }, i18n.bankChartWealthTitle), + renderRangeTabs("exchange", range, "wealth-chart-block"), + p({ class: "bank-chart-empty-note" }, i18n.bankChartNoDataYet) + ); + const fmtBucket = (x) => wealth.bucket === "month" ? moment(x.ts).format("YYYY-MM") : wealth.bucket === "day" ? moment(x.ts).format("YYYY-MM-DD") : moment(x.ts).format("YYYY-MM-DD HH:mm"); + const per = wealth.bucket === "month" ? i18n.bankChartPerMonth : wealth.bucket === "day" ? i18n.bankChartPerDay : i18n.bankChartPerHour; + const svg = buildSeriesChartSvg({ + series: [ + { label: `${i18n.bankChartDistributed} ${per}`, points: pts.map(p => p.distributed) }, + { label: `${i18n.bankChartTaxes} ${per}`, points: pts.map(p => p.taxes) } + ], + xLabels: [fmtBucket(pts[0]), fmtBucket(pts[Math.floor(pts.length / 2)]), fmtBucket(pts[pts.length - 1])], + empty: i18n.bankExchangeChartEmpty, yDecimals: 2 + }); + const enough = pts.length >= 2; + return div({ class: "bank-eco-chart-block", id: "wealth-chart-block" }, + h2({ class: "bank-eco-chart-title" }, i18n.bankChartWealthTitle), + renderRangeTabs("exchange", range, "wealth-chart-block"), + enough ? a({ href: "#wealth-chart", id: "wealth-chart-src", class: "bank-eco-chart-zoom-link", title: i18n.bankChartZoomHint }, div({ class: "bank-eco-chart-canvas", [RAW_HTML]: svg })) : p({ class: "bank-chart-empty-note" }, i18n.bankChartNoDataRange), + enough ? div({ id: "wealth-chart", class: "lightbox bank-eco-chart-lightbox" }, a({ href: "#wealth-chart-src", class: "lightbox-close" }, "\u00d7"), div({ class: "bank-eco-chart-canvas-zoom", [RAW_HTML]: svg })) : null + ); +}; + +const renderFundsChart = (history) => { + const arr = Array.isArray(history && history.points) ? history.points : []; + const svg = arr.length >= 2 ? buildSeriesChartSvg({ + series: [{ label: "ECO", points: arr.map(x => x.balance) }], + xLabels: tsLabels(arr, "ts"), + empty: i18n.bankExchangeChartEmpty, yDecimals: 2 + }) : null; + return renderRangedChart("funds-chart", i18n.bankChartFundsTitle, "overview", (history && history.range) || "today", !!(history && history.hasAny), svg); +}; + +const renderKarmaChart = (history) => { + const arr = Array.isArray(history && history.points) ? history.points : []; + const svg = arr.length >= 2 ? buildSeriesChartSvg({ + series: [{ label: i18n.bankChartKarma, points: arr.map(x => x.score) }], + xLabels: tsLabels(arr, "ts"), + empty: i18n.bankExchangeChartEmpty, yDecimals: 0 + }) : null; + return renderRangedChart("karma-chart", i18n.bankChartKarmaTitle, "overview", (history && history.range) || "today", !!(history && history.hasAny), svg); +}; + +const renderExchange = (ex, history, taxStats, wealth, range, valueRange = range, valueHasAnyData = false) => { if (!ex) return div(p(i18n.bankExchangeNoData)); const syncStatus = ex.isSynced ? i18n.bankingSyncStatusSynced : i18n.bankingSyncStatusOutdated; - const syncStatusClass = ex.isSynced ? 'synced' : 'outdated'; const ecoTimeLabel = ex.isSynced ? fmtEcoTime(ex.ecoTimeMs) : fmtEcoTime(0); const chartLabels = { value: i18n.bankExchangeChartValue || 'Value (ECO/h)', @@ -154,24 +331,30 @@ const renderExchange = (ex, history, taxStats) => { table({ class: "bank-info-table" }, tbody( kvRow(i18n.bankingSyncStatus, - span({ class: syncStatusClass }, syncStatus) + span({ class: ex.isSynced ? "ubi-tick-ok" : "ubi-tick-bad", title: syncStatus }, ex.isSynced ? "✓" : "✗") ), - kvRow(i18n.bankExchangeCurrentValue, `${fmtIndex(ex.ecoValue)} ECO`), - kvRow(i18n.bankCurrentSupply, `${Number(ex.currentSupply || 0).toFixed(6)} ECO`), - kvRow(i18n.bankTotalSupply, `${Number(ex.totalSupply || 0).toFixed(6)} ECO`), - kvRow(i18n.bankEcoinHours, ecoTimeLabel), - kvRow(i18n.bankInflation, `${ex.inflationFactor.toFixed(2)}%`), - kvRow(i18n.bankInflationMonthly, `${Number(ex.inflationMonthly || 0).toFixed(2)}%`), - ...taxRows + kvRow(strong(i18n.bankTotalSupply), strong(`${Number(ex.totalSupply || 0).toFixed(6)} ECO`)), + ...(ex.isSynced ? [kvRow(strong(i18n.bankCurrentSupply), strong(`${Number(ex.currentSupply || 0).toFixed(6)} ECO`))] : []), + ...(ex.holdingSupply != null ? [kvRow(strong(i18n.bankHoldingSupply), strong(`${Number(ex.holdingSupply).toFixed(6)} ECO`))] : []), + kvRow(strong(i18n.bankPubsSupply), strong(`${Number(ex.pubsSupply || 0).toFixed(6)} ECO`)), + ...(ex.isSynced ? [ + kvRow(strong(i18n.bankInflation), strong(`${Number(ex.inflationFactor || 0).toFixed(2)}%`)), + kvRow(i18n.bankInflationMonthly, `${Number(ex.inflationMonthly || 0).toFixed(2)}%`) + ] : []), + kvRow(strong(i18n.bankTotalUbiDistributed), strong(`${Number((wealth && wealth.totals && wealth.totals.distributed) || 0).toFixed(6)} ECO`)), + kvRow(strong(i18n.bankTotalTaxesWithheld), strong(`${Number((wealth && wealth.totals && wealth.totals.taxes) || 0).toFixed(6)} ECO`)), + ...taxRows, + ...(ex.isSynced ? [ + kvRow(strong(i18n.bankExchangeCurrentValue), strong(`${fmtIndex(ex.ecoValue)} ECO`)), + kvRow(strong(i18n.bankEcoinHours), strong(ecoTimeLabel)) + ] : []) ) ) ), - hasEnoughSamples - ? div({ class: "bank-eco-chart-block" }, - h2({ class: "bank-eco-chart-title" }, i18n.bankExchangeChartTitle || 'ECOin value over time'), - div({ class: "bank-eco-chart-canvas", innerHTML: buildEcoValueChartSvg(history, chartLabels) }) - ) - : null + ex.isSynced ? renderValueChart(history, valueRange, !!valueHasAnyData) : null, + ex.isSynced ? renderWealthChart(wealth, (wealth && wealth.range) || range) : null, + renderSupplyChart(history, range, hasEnoughSamples, ex.totalSupply), + renderInflationChart(history, range, hasEnoughSamples) ); }; @@ -248,7 +431,7 @@ const renderTaxes = (data, lookup) => { (i18n.bankTaxesUserNoteOutro || ', and their ongoing contribution may be required by networking consensus.') ), p({ class: "bank-taxes-user-note" }, i18n.bankTaxesUserNoteChips || 'The ECO Tax chip shown next to each item changes color (green / yellow / red) depending on the item size relative to the largest block ever seen on the network, softened by the number of active inhabitants — a larger solar-punk network distributes the load and pushes more items into the green band.'), - p({ class: "bank-taxes-user-note" }, i18n.bankTaxesUserNoteChipsClick || 'Click any chip to inspect its block in the blockexplorer.'), + process.env.OASIS_MOBILE === '1' ? null : p({ class: "bank-taxes-user-note" }, i18n.bankTaxesUserNoteChipsClick || 'Click any chip to inspect its block in the blockexplorer.'), (() => { const firstBlock = data.firstBlock || null; const sampleSize = Number(data.firstBlockSize || 0); @@ -258,17 +441,17 @@ const renderTaxes = (data, lookup) => { const sampleValue = sampleSize > 0 ? formatCarbon(sampleSize) : '— CO₂'; const label = i18n.ecoTaxLabel || 'ECO Tax'; return p({ class: "bank-taxes-example-chips" }, - a({ href: sampleHref, class: 'eco-tax-chip eco-tax-chip-low', title: label + ' · low' }, + (process.env.OASIS_MOBILE === '1' ? span : a)({ ...(process.env.OASIS_MOBILE === '1' ? {} : { href: sampleHref }), class: 'eco-tax-chip eco-tax-chip-low', title: label + ' · low' }, span({ class: 'eco-tax-chip-label' }, label + ': '), span({ class: 'eco-tax-chip-value' }, sampleValue) ), ' ', - a({ href: sampleHref, class: 'eco-tax-chip eco-tax-chip-mid', title: label + ' · mid' }, + (process.env.OASIS_MOBILE === '1' ? span : a)({ ...(process.env.OASIS_MOBILE === '1' ? {} : { href: sampleHref }), class: 'eco-tax-chip eco-tax-chip-mid', title: label + ' · mid' }, span({ class: 'eco-tax-chip-label' }, label + ': '), span({ class: 'eco-tax-chip-value' }, sampleValue) ), ' ', - a({ href: sampleHref, class: 'eco-tax-chip eco-tax-chip-high', title: label + ' · high' }, + (process.env.OASIS_MOBILE === '1' ? span : a)({ ...(process.env.OASIS_MOBILE === '1' ? {} : { href: sampleHref }), class: 'eco-tax-chip eco-tax-chip-high', title: label + ' · high' }, span({ class: 'eco-tax-chip-label' }, label + ': '), span({ class: 'eco-tax-chip-value' }, sampleValue) ) @@ -297,6 +480,33 @@ const renderTaxes = (data, lookup) => { ); }; +const renderUbiPubs = (pubs, hasValidWallet) => { + const fmt = (ts) => Number(ts) > 0 ? moment(Number(ts)).format("YYYY/MM/DD HH:mm") : "—"; + if (!pubs.length) return div({ class: "bank-summary" }, p(i18n.bankUbiPubNone)); + return div({ class: "addr-cards ubi-pub-cards" }, + ...pubs.map(p => div({ class: "addr-card ubi-pub-card" + (p.isThisNode || p.isConnected ? " own-content" : "") }, + div({ class: "addr-card-head" }, + userLink(p.pubId), + p.isThisNode || p.isConnected ? span({ class: "status you" }, p.isThisNode ? i18n.bankUbiPubThisNode : i18n.bankUbiPubConnected) : null, + hasValidWallet && p.address + ? div({ class: "content-actions" }, form({ method: "GET", action: "/banking/fund", class: "content-action-form" }, input({ type: "hidden", name: "pub", value: p.pubId }), button({ type: "submit", class: "btn-singleview btn-donate", title: i18n.bankDonateEco }, "\u2744"))) + : null + ), + div({ class: "bank-summary ubi-pub-summary" }, + table({ class: "bank-info-table" }, + tbody( + kvRow(i18n.bankUbiAvailability, span({ class: p.available ? "ubi-tick-ok" : "ubi-tick-bad" }, p.available ? "✓" : "✗")), + kvRow(i18n.bankUbiPool, strong(`${Number(p.balance || 0).toFixed(6)} ECO`)), + kvRow(i18n.bankUbiLastSeen, fmt(p.timestamp)), + kvRow(i18n.bankUbiLastPayout, fmt(p.lastPayoutAt)), + kvRow(i18n.bankUbiPaidOut, strong(`${Number(p.paidOut || 0).toFixed(6)} ECO`)) + ) + ) + ) + )) + ); +}; + const renderOverviewSummaryTable = (s, rules, userEcoinTax, isPub = false) => { const ubiWired = !!s.pubId || isPub; const score = Number(s.userEngagementScore || 0); @@ -308,39 +518,48 @@ const renderOverviewSummaryTable = (s, rules, userEcoinTax, isPub = false) => { const gross = Math.max(floor, Math.min(pool * (w / W), cap)); const surplus = Math.max(0, gross - floor); const tax = Number(userEcoinTax || 0); - const future = floor + Math.max(0, surplus - tax); - const availClass = s.ubiAvailability === "OK" ? "ubi-available" : "ubi-unavailable"; - const availLabel = s.ubiAvailability === "OK" ? i18n.bankUbiAvailableOk : i18n.bankUbiAvailableNo; + const ubiOn = ubiWired && s.ubiAvailability === "OK"; + const future = ubiOn ? floor + Math.max(0, surplus - tax) : 0; return div({ class: "bank-summary" }, table({ class: "bank-info-table" }, tbody( - kvRow(i18n.bankUserBalance, `${Number(s.userBalance || 0).toFixed(6)} ECO`), - kvRow(i18n.bankIndustryBalance || 'Industry Production (estimated)', a({ href: '/industry' }, `${Number(s.industryNetworkTotal || 0).toFixed(6)} ECO`)), - ubiWired ? kvRow(i18n.bankUbiAvailability, span({ class: availClass }, availLabel)) : null, - s.pubId ? kvRow(i18n.pubIdLabel, userLink(s.pubId)) : null, + kvRow(strong(i18n.bankUserBalance), strong(`${Number(s.userBalance || 0).toFixed(6)} ECO`)), + Number(s.industryNetworkTotal || 0) > 0 ? kvRow(strong(i18n.bankIndustryBalance || "Industry Production"), a({ href: '/industry' }, strong(`${Number(s.industryNetworkTotal || 0).toFixed(6)} ECO`))) : null, kvRow(i18n.bankEpoch, String(s.epochId || "-")), kvRow(i18n.bankPool, `${pool.toFixed(6)} ECO`), kvRow(i18n.bankWeightsSum, String(W.toFixed(6))), - kvRow(i18n.bankingUserEngagementScore, String(score)), + kvRow(strong(i18n.bankingUserEngagementScore), strong(String(score))), ubiWired ? kvRow(i18n.bankYourUbiMonth || 'Your UBI (this month)', `${future.toFixed(6)} ECO`) : null, - kvRow(i18n.bankYourIndustryBalance || 'Your Industry Share', a({ href: '/industry?filter=MEMBER' }, `${Number(s.industryBalance || 0).toFixed(6)} ECO`)), - kvRow(i18n.bankOverviewYourTaxes || 'Your taxes', a({ href: '/banking?filter=taxes' }, `${tax.toFixed(6)} ECO`)), - kvRow(i18n.bankYourFundsMonth || 'Your Funds (this month)', `${((ubiWired ? future : 0) + Number(s.industryBalance || 0) - tax).toFixed(6)} ECO`) + Number(s.industryBalance || 0) > 0 ? kvRow(i18n.bankYourIndustryBalance || 'Your Industry Share', a({ href: '/industry?filter=MEMBER' }, `${Number(s.industryBalance || 0).toFixed(6)} ECO`)) : null, + Number(s.schoolBalance || 0) > 0 ? kvRow(i18n.bankYourSchoolBalance || 'Your School Earnings', a({ href: '/school?filter=mine' }, `${Number(s.schoolBalance || 0).toFixed(6)} ECO`)) : null, + kvRow(i18n.bankTotalUbiDistributed, `${Number((s.wealthTotals && s.wealthTotals.distributed) || 0).toFixed(6)} ECO`), + kvRow(i18n.bankTotalTaxesWithheld, `${Number((s.wealthTotals && s.wealthTotals.taxes) || 0).toFixed(6)} ECO`), + kvRow(strong(i18n.bankOverviewYourTaxes || 'Total Taxes'), a({ href: '/banking?filter=taxes' }, strong(`${tax.toFixed(6)} ECO`))), + kvRow(strong(i18n.bankYourFundsMonth || 'Total Funds'), strong(`${((ubiWired ? future : 0) + Number(s.industryBalance || 0) + Number(s.schoolBalance || 0)).toFixed(6)} ECO`)) ) ) ); }; -const renderClaimUBIBlock = (pendingAllocation, isPub, alreadyClaimed, pubId, hasValidWallet, ubiAvailability) => { +const renderClaimUBIBlock = (pendingAllocation, isPub, alreadyClaimed, pubId, hasValidWallet, ubiAvailability, alreadyRefused = false, addressPublished = false, ecoinSynced = false) => { + if (!ecoinSynced) return ""; if (alreadyClaimed) return ""; + if (alreadyRefused) return div({ class: "bank-claim-ubi" }, div({ class: "bank-claim-card" }, p(i18n.bankUbiRefusedThisMonth))); if (!pubId && !isPub) return ""; - if (!isPub && !hasValidWallet) return ""; + if (!isPub && !(hasValidWallet && addressPublished)) return ""; if (!isPub && ubiAvailability !== "OK") return ""; - if (!pendingAllocation && !isPub) { + if (!isPub) { return div({ class: "bank-claim-ubi" }, div({ class: "bank-claim-card" }, - form({ method: "POST", action: "/banking/claim-ubi" }, - button({ type: "submit", class: "create-button bank-claim-btn" }, i18n.bankClaimUBI) + pendingAllocation ? p(`${i18n.bankUbiThisMonth}: `, span({ class: "accent" }, `${Number(pendingAllocation.amount || 0).toFixed(6)} ECO`)) : null, + pendingAllocation ? p(`${i18n.bankEpoch}: `, span(pendingAllocation.concept || "")) : null, + div({ class: "bank-claim-actions" }, + form({ method: "POST", action: "/banking/claim-ubi" }, + button({ type: "submit", class: "create-button bank-claim-btn" }, i18n.bankClaimUBI) + ), + form({ method: "POST", action: "/banking/refuse-ubi" }, + button({ type: "submit", class: "delete-btn bank-claim-btn" }, i18n.bankRefuseUBI) + ) ) ) ); @@ -359,7 +578,7 @@ const renderClaimUBIBlock = (pendingAllocation, isPub, alreadyClaimed, pubId, ha const filterAllocations = (allocs, filter, userId) => { if (filter === "mine") return allocs.filter(a => a.to === userId && (a.status === "UNCLAIMED" || a.status === "UNCONFIRMED")); - if (filter === "pending") return allocs.filter(a => a.status === "UNCLAIMED" || a.status === "UNCONFIRMED"); + if (filter === "pending") return allocs.filter(a => a.status !== "EXPIRED"); if (filter === "closed") return allocs.filter(a => a.status === "CLOSED"); if (filter === "claimed") return allocs.filter(a => a.status === "CLAIMED"); if (filter === "expired") return allocs.filter(a => a.status === "EXPIRED"); @@ -378,27 +597,23 @@ const allocationsTable = (rows = [], userId) => th(i18n.bankAllocFrom), th(i18n.bankAllocTo), th(i18n.bankAllocAmount), - th(i18n.bankAllocStatus), - th("") + th(i18n.bankAllocStatus) ) ), tbody( ...rows.map(r => tr( - td(new Date(r.createdAt).toLocaleString()), + td(moment(r.createdAt).format("YYYY/MM/DD HH:mm")), td(r.concept || ""), td(userLink(r.from)), td(userLink(r.to)), td(String(Number(r.amount || 0).toFixed(6))), - td(r.status), td( - (r.status === "UNCLAIMED" || r.status === "UNCONFIRMED") && r.to === userId - ? form({ method: "POST", action: `/banking/claim/${encodeURIComponent(r.id)}` }, - button({ type: "submit", class: "filter-btn" }, i18n.bankClaimNow) - ) - : r.status === "CLOSED" && r.txid - ? a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${encodeURIComponent(r.txid)}`, target: "_blank", class: "btn-singleview" }, i18n.bankViewTx) - : null + r.status === "CLOSED" || r.status === "CLAIMED" + ? span({ class: "ubi-tick-ok", title: r.status }, "✓") + : r.status === "UNCLAIMED" || r.status === "UNCONFIRMED" + ? span({ class: "ubi-tick-bad", title: r.status }, "✗") + : r.status ) ) ) @@ -410,16 +625,15 @@ const renderEpochList = (epochs = []) => ? div(p(i18n.bankNoEpochs)) : table( { class: "bank-epochs" }, - thead(tr(th(i18n.bankEpochId), th(i18n.bankPool), th(i18n.bankWeightsSum), th(i18n.bankRuleHash), th(""))), + thead(tr(th(i18n.bankEpochId), th(i18n.bankPool), th(i18n.bankEpochAllocations), th(""))), tbody( - ...epochs - .sort((a, b) => String(b.id).localeCompare(String(a.id))) + ...paged(epochs + .sort((a, b) => String(b.id).localeCompare(String(a.id)))) .map(e => tr( td(e.id), td(String(Number(e.pool || 0).toFixed(6))), - td(String(Number(e.weightsSum || 0).toFixed(6))), - td(e.hash || "-"), + td(String(Number(e.recipients || 0))), td( form({ method: "GET", action: `/banking/epoch/${encodeURIComponent(e.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.bankViewEpoch) @@ -436,6 +650,9 @@ const rulesBlock = (rules, taxRules) => { const tr = taxRules || {}; const fmt = (n, d = 4) => (Number.isFinite(Number(n)) ? Number(n).toFixed(d) : "—"); return div({ class: "bank-rules" }, + h2(i18n.bankRulesEligibilityTitle), + ul({ class: "bank-rules-list" }, ...[i18n.bankRulesEligibility1, i18n.bankRulesEligibility2, i18n.bankRulesEligibility3, i18n.bankRulesEligibility4, i18n.bankRulesEligibility5, i18n.bankRulesEligibility6].map(x => li(x))), + h2(i18n.bankRulesPoolTitle || "Monthly pool"), table({ class: "bank-info-table" }, tbody( @@ -516,118 +733,100 @@ const flashText = (key) => { if (key === "claimed_pending") return i18n.bankClaimedPending; if (key === "already_claimed") return i18n.bankAlreadyClaimedThisMonth; if (key === "no_pub_configured") return i18n.bankNoPubConfigured; + if (key === "no_pub_address") return i18n.bankNoPubAddress; + if (key === "refused") return i18n.bankUbiRefusedThisMonth; + if (key === "already_refused") return i18n.bankUbiRefusedThisMonth; if (key === "no_funds") return i18n.bankUbiAvailableNo; if (key === "forbidden") return i18n.bankAddressForbidden; return ""; }; -const flashBanner = (msgKey) => - !msgKey ? null : div({ class: "flash-banner" }, p(flashText(msgKey) || msgKey)); - -const addressesToolbar = (rows = [], search = "") => - div({ class: "addr-toolbar" }, - div({ class: "addr-counter accent-pill" }, - span({ class: "acc-title accent" }, i18n.bankAddressTotal + ":"), - span({ class: "acc-badge" }, String(rows.length)) - ), - form({ method: "GET", action: "/banking", class: "addr-search" }, - input({ type: "hidden", name: "filter", value: "addresses" }), - input({ type: "text", name: "q", placeholder: i18n.bankAddressSearch, value: search || "" }), - br(), - button({ type: "submit", class: "filter-btn" }, i18n.search) - ) - ); - const renderAddresses = (data, userId) => { const rows = data.addresses || []; const search = data.search || ""; + const hasContent = rows.length > 0 || !!search; return div( - data.flash ? flashBanner(data.flash) : null, - addressesToolbar(rows, search), - div({ class: "bank-addresses-stack" }, - div({ class: "addr-form-card wide" }, - h2(i18n.bankAddAddressTitle), - form({ method: "POST", action: "/banking/addresses", class: "addr-form" }, - div({ class: "form-row" }, - span({ class: "form-label accent" }, i18n.bankAddAddressUser + ":"), - input({ - class: "form-input xl", - type: "text", - name: "userId", - required: true, - pattern: "^@[A-Za-z0-9+/]+={0,2}\\.ed25519$", - placeholder: "@...=.ed25519", - id: "addr-user-id" - }) - ), - div({ class: "form-row" }, - span({ class: "form-label accent" }, i18n.bankAddAddressAddress + ":"), - input({ - class: "form-input xl", - type: "text", - name: "address", - required: true, - pattern: "^[A-Za-z0-9]{20,64}$", - placeholder: "ETQ17sBv8QFoiCPGKDQzNcDJeXmB2317HX" - }) - ), - div({ class: "form-actions" }, - button({ type: "submit", class: "filter-btn" }, i18n.bankAddAddressSave) + div({ class: "bank-summary" }, + h2(i18n.bankAddAddressTitle), + form({ method: "POST", action: "/banking/addresses", class: "bank-form" }, + label({ for: "addr-label" }, i18n.bankAddAddressLabel), br(), + input({ type: "text", id: "addr-label", name: "label", maxlength: "80", placeholder: i18n.bankAddAddressLabelPlaceholder }), br(), + label({ for: "addr-user-id" }, i18n.bankAddAddressUserOptional), br(), + input({ type: "text", id: "addr-user-id", name: "userId", pattern: "^@[A-Za-z0-9+/]+={0,2}\\.ed25519$", placeholder: "@...=.ed25519" }), br(), + label({ for: "addr-address" }, i18n.bankAddAddressAddress), br(), + input({ type: "text", id: "addr-address", name: "address", required: true, pattern: "^[A-Za-z0-9]{20,64}$", placeholder: "ETQ17sBv8QFoiCPGKDQzNcDJeXmB2317HX" }), br(), + button({ type: "submit", class: "create-button" }, i18n.bankAddAddressSave) + ) + ), + hasContent + ? form({ method: "GET", action: "/banking", class: "filter-box bank-addr-search" }, + input({ type: "hidden", name: "filter", value: "addresses" }), + input({ type: "text", name: "q", placeholder: i18n.bankAddressSearch, value: search || "", class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + : null, + rows.length === 0 + ? div({ class: "no-content-box" }, p(i18n.bankNoAddresses)) + : div({ class: "bank-summary" }, + renderModuleStats(rows.length, [ + { label: i18n.bankLocal, count: rows.filter(r => r.source !== "ssb").length }, + { label: i18n.bankFromOasis, count: rows.filter(r => r.source === "ssb").length } + ]), + table({ class: "bank-addresses" }, + thead(tr(th(i18n.bankAddAddressLabel.replace(/\s*\(.*\)\s*$/, "")), th(i18n.bankAddAddressUser), th(i18n.bankAddress), th(i18n.bankAddressSource), th(""))), + tbody( + ...paged(rows).map(r => tr( + td(r.label || (r.id ? "" : i18n.bankAddressUnnamed)), + td(r.id ? userLink(r.id) : ""), + td(strong({ class: "bank-address-code" }, r.address)), + td(r.source === "ssb" ? i18n.bankFromOasis : i18n.bankLocal), + td(r.source === "ssb" && String(r.id) !== String(userId) + ? "" + : form({ method: "POST", action: "/banking/addresses/delete", class: "addr-del" }, + input({ type: "hidden", name: "userId", value: r.id || "" }), + input({ type: "hidden", name: "source", value: r.source || "local" }), + input({ type: "hidden", name: "entryId", value: r.entryId || "" }), + button({ type: "submit", class: "delete-btn" }, i18n.bankAddressDelete) + )) + )) + ) ) ) - ), - div({ class: "addr-list-card" }, - rows.length === 0 - ? div(p(i18n.bankNoAddresses)) - : table( - { class: "bank-addresses" }, - thead( - tr( - th(i18n.bankUser), - th(i18n.bankAddress), - th(i18n.bankAddressSource), - th(i18n.bankAddressActions) - ) - ), - tbody( - ...rows.map(r => - tr( - td(userLink(r.id)), - td(r.address), - td(r.source === "local" ? i18n.bankLocal : i18n.bankFromOasis), - td( - div({ class: "row-actions" }, - form({ method: "POST", action: "/banking/addresses/delete", class: "addr-del" }, - input({ type: "hidden", name: "userId", value: r.id }), - input({ type: "hidden", name: "source", value: r.source || "local" }), - button({ type: "submit", class: "delete-btn", onclick: `return confirm(${JSON.stringify(i18n.bankAddressDeleteConfirm)})` }, i18n.bankAddressDelete) - ) - ) - ) - ) - ) - ) - ) - ) - ) ); }; +const bankingFilters = (data, filter, userId) => { + const allocs = data.allocations || []; + const subsets = ["mine", "pending", "closed", "claimed", "expired"].filter(f => filterAllocations(allocs, f, userId).length > 0 || filter === f); + return [ + "overview", "exchange", "ubi", "taxes", + ...subsets, + ...((data.epochs || []).length || filter === "epochs" || String(filter || "").startsWith("epoch:") ? ["epochs"] : []), + "addresses", "rules" + ]; +}; + const renderBankingView = (data, filter, userId, isPub) => template( i18n.banking, section( - div({ class: "tags-header" }, h2(i18n.banking), p(i18n.bankingDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.banking), p(i18n.bankingDescription), renderWalletChip()), data.flash ? div({ class: "flash-banner" }, p(flashText(data.flash) || data.flash)) : null, - generateFilterButtons(["overview","exchange","taxes","mine","pending","closed","claimed","expired","epochs","rules","addresses"], filter, "/banking"), + generateFilterButtons(bankingFilters(data, filter, userId), filter, "/banking"), filter === "overview" ? div( renderOverviewSummaryTable(data.summary || {}, data.rules, data.userTotalTax || data.userEcoinTax, isPub), - renderClaimUBIBlock(data.pendingUBI || null, isPub, data.alreadyClaimed, (data.summary || {}).pubId, (data.summary || {}).hasValidWallet, (data.summary || {}).ubiAvailability), - allocationsTable((data.allocations || []).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)), userId) + renderKarmaChart(data.karmaHistory), + renderFundsChart(data.fundsHistory) + ) + : filter === "ubi" + ? div( + renderClaimUBIBlock(data.pendingUBI || null, isPub, data.alreadyClaimed, (data.summary || {}).pubId, (data.summary || {}).hasValidWallet, (data.summary || {}).ubiAvailability, (data.summary || {}).alreadyRefused, (data.summary || {}).addressPublished, !!(data.exchange && data.exchange.isSynced)), + renderUbiPubs(data.ubiPubs || [], !!(data.summary || {}).hasValidWallet), + renderUbiCharts({ ...(data.ubiCharts || {}), payments: data.ubiPayments, rebalances: data.ubiRebalances }) ) : filter === "exchange" - ? renderExchange(data.exchange, data.exchangeHistory, data.taxStats) + ? renderExchange(data.exchange, data.exchangeHistory, data.taxStats, data.wealth, data.range, data.valueRange || data.range, !!data.valueHasAnyData) : filter === "taxes" ? renderTaxes(data, data.lookup || null) : filter === "epochs" @@ -637,7 +836,7 @@ const renderBankingView = (data, filter, userId, isPub) => : filter === "addresses" ? renderAddresses(data, userId) : allocationsTable( - filterAllocations((data.allocations || []).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)), filter, userId), + paged(filterAllocations((data.allocations || []).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)), filter, userId)), userId ) ) @@ -659,7 +858,6 @@ const renderSingleAllocationView = (alloc, userId) => { kvRow(i18n.bankAllocAmount, `${Number(alloc.amount || 0).toFixed(6)} ECO`), kvRow(i18n.bankAllocStatus, alloc.status || "-"), kvRow(i18n.bankAllocDate, alloc.createdAt ? fmtDate(alloc.createdAt) : "-"), - alloc.txid ? kvRow("TxID", a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${encodeURIComponent(alloc.txid)}`, target: "_blank" }, alloc.txid)) : null ) ) ), @@ -673,26 +871,36 @@ const renderSingleAllocationView = (alloc, userId) => { ); }; -const renderEpochView = (epoch, allocations) => { - if (!epoch) return template(i18n.banking, section(div(p(i18n.bankNoEpochs)))); +const bankingHeader = () => + div({ class: "tags-header module-header-line" }, h2(i18n.banking), p(i18n.bankingDescription), renderWalletChip()); + +const renderEpochView = (epoch, allocations, userId = "", data = {}) => { + const rows = allocations || []; return template( i18n.banking, section( - div({ class: "tags-header" }, h2(`${i18n.bankEpoch}: ${epoch.id}`)), - div({ class: "bank-summary" }, - table({ class: "bank-info-table" }, - tbody( - kvRow(i18n.bankEpochId, epoch.id || "-"), - kvRow(i18n.bankPool, `${Number(epoch.pool || 0).toFixed(6)} ECO`), - kvRow(i18n.bankWeightsSum, String(Number(epoch.weightsSum || 0).toFixed(6))), - kvRow(i18n.bankRuleHash, epoch.hash || "-") + bankingHeader(), + generateFilterButtons(bankingFilters({ ...data, allocations: rows, epochs: data.epochs || [{}] }, "epochs", userId), "epochs", "/banking"), + !epoch + ? div({ class: "no-content-box" }, p(i18n.bankNoEpochs)) + : div( + h2(`${i18n.bankEpoch}: ${epoch.id}`), + div({ class: "bank-summary" }, + table({ class: "bank-info-table" }, + tbody( + kvRow(i18n.bankEpochId, epoch.id || "-"), + kvRow(i18n.bankPool, `${Number(epoch.pool || 0).toFixed(6)} ECO`), + Number.isFinite(Number(epoch.weightsSum)) && Number(epoch.weightsSum) > 0 ? kvRow(i18n.bankWeightsSum, String(Number(epoch.weightsSum).toFixed(6))) : null, + kvRow(i18n.bankEpochAllocations, String(rows.length)), + epoch.hash && epoch.hash !== "-" ? kvRow(i18n.bankRuleHash, epoch.hash) : null + ) + ) + ), + h2(i18n.bankEpochAllocations), + allocationsTable(paged(rows), userId) ) - ) - ), - h2(i18n.bankEpochAllocations), - allocationsTable(allocations || [], "") ) ); }; -module.exports = { renderBankingView, renderSingleAllocationView, renderEpochView }; +module.exports = { renderBankingView, renderSingleAllocationView, renderEpochView, bankingFlashText: flashText }; diff --git a/src/views/blockchain_view.js b/src/views/blockchain_view.js index f39792c..f3d1318 100644 --- a/src/views/blockchain_view.js +++ b/src/views/blockchain_view.js @@ -1,5 +1,5 @@ const { div, h2, h3, p, section, button, form, a, input, span, pre, table, tr, td, strong } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink } = require("../views/main_views"); +const { template, i18n, userLink, paged } = require("../views/main_views"); const moment = require("../server/node_modules/moment"); const FILTER_LABELS = { @@ -13,15 +13,16 @@ const FILTER_LABELS = { project: i18n.typeProject, industry: i18n.typeIndustry, industryBlueprint: i18n.industryBlueprints, banking: i18n.typeBanking, bankWallet: i18n.typeBankWallet, bankClaim: i18n.typeBankClaim, aiExchange: i18n.typeAiExchange, parliament: i18n.typeParliament, courts: i18n.typeCourts, map: i18n.typeMap, shop: i18n.typeShop, shopProduct: i18n.typeShopProduct || 'Shop Product', - pad: i18n.typePad || 'PAD', chat: i18n.typeChat || 'CHAT', gameScore: i18n.typeGameScore || 'GAME SCORE', - calendar: i18n.typeCalendar || 'CALENDAR', torrent: i18n.typeTorrent + pad: i18n.typePad || 'PAD', room: i18n.typeRoom || 'ROOM', roomMember: i18n.typeRoomMember || 'ROOM PARTICIPANT', wikiPage: i18n.typeWiki || 'WIKI', emergency: i18n.typeEmergency || 'EMERGENCY', emergencyConfirm: i18n.typeEmergencyConfirm || 'EMERGENCY CONFIRM', emergencyUpdate: i18n.typeEmergencyUpdate || 'EMERGENCY UPDATE', mailingList: i18n.typeMailingList || 'MAILING LIST', logisticsRoute: i18n.typeLogisticsRoute || 'ROUTE', logisticsRating: i18n.typeLogisticsRating || 'ROUTE RATING', podcast: i18n.typePodcast || 'PODCAST', podcastEpisode: i18n.typePodcastEpisode || 'EPISODE', podcastOpinion: i18n.typePodcastOpinion || 'EPISODE OPINION', podcastPlay: i18n.typePodcastPlay || 'EPISODE PLAY', campaign: i18n.typeCampaign || 'CAMPAIGN', campaignSignature: i18n.typeCampaignSignature || 'SIGNATURE', campaignUpdate: i18n.typeCampaignUpdate || 'CAMPAIGN UPDATE', chat: i18n.typeChat || 'CHAT', gameScore: i18n.typeGameScore || 'GAME SCORE', + calendar: i18n.typeCalendar || 'CALENDAR', torrent: i18n.typeTorrent, file: i18n.typeFile, + school: i18n.typeSchool || 'SCHOOL' }; -const BASE_FILTERS = ['recent', 'all', 'mine', 'tombstone', 'logs']; -const CAT_BLOCK1 = ['votes', 'event', 'task', 'report', 'calendar', 'parliament', 'courts']; +const BASE_FILTERS = ['all', 'recent', 'mine', 'tombstone', 'logs']; +const CAT_BLOCK1 = ['votes', 'event', 'task', 'report', 'calendar', 'school', 'parliament', 'courts']; const CAT_BLOCK2 = ['pub', 'tribe', 'about', 'contact', 'curriculum', 'vote', 'aiExchange']; const CAT_BLOCK3 = ['banking', 'job', 'housing', 'market', 'project', 'industry', 'transfer', 'feed', 'post', 'pixelia', 'shop', 'gameScore']; -const CAT_BLOCK4 = ['forum', 'pad', 'chat', 'bookmark', 'image', 'video', 'audio', 'document', 'map', 'torrent']; +const CAT_BLOCK4 = ['forum', 'pad', 'chat', 'room', 'bookmark', 'image', 'video', 'audio', 'document', 'map', 'torrent', 'file']; const SEARCH_FIELDS = ['author','id','from','to']; @@ -75,6 +76,7 @@ const filterBlocks = (blocks, filter, userId) => { return blocks.filter(b => cset.has(b.type)); } if (filter === 'shop') return blocks.filter(b => b.type === 'shop' || b.type === 'shopProduct'); + if (filter === 'school') return blocks.filter(b => ['schoolCourse', 'schoolLesson', 'schoolEnroll', 'schoolCertificate'].includes(b.type)); if (filter === 'logs') return blocks.filter(b => b.type === 'log' && b.author === userId); return blocks.filter(b => b.type === filter); }; @@ -181,6 +183,7 @@ const getViewDetailsAction = (type, block) => { case 'job': return `/jobs/${encodeURIComponent(block.id)}`; case 'housing': return `/housing/${encodeURIComponent(block.id)}`; case 'project': return `/projects/${encodeURIComponent(block.id)}`; + case 'schoolCourse': return `/school/course/${encodeURIComponent(block.id)}`; case 'industry': return `/industry/${encodeURIComponent(block.id)}`; case 'industryBlueprint': return `/industry/blueprint/${encodeURIComponent(block.id)}`; case 'report': return `/reports/${encodeURIComponent(block.id)}`; @@ -203,10 +206,13 @@ const getViewDetailsAction = (type, block) => { case 'courtsNominationVote': return `/courts`; case 'map': return `/maps/${encodeURIComponent(block.id)}`; case 'torrent': return `/torrents/${encodeURIComponent(block.id)}`; + case 'file': return `/files/${encodeURIComponent(block.id)}`; case 'mapMarker': return block.content?.mapId ? `/maps/${encodeURIComponent(block.content.mapId)}` : `/maps`; case 'shop': return `/shops/${encodeURIComponent(block.id)}`; case 'shopProduct': return `/shops/product/${encodeURIComponent(block.id)}`; case 'pad': return `/pads/${encodeURIComponent(block.id)}`; + case 'room': return `/rooms/${encodeURIComponent(block.id)}`; + case 'roomMember': return block.content?.target ? `/rooms/${encodeURIComponent(block.content.target)}` : `/rooms`; case 'chat': return `/chats/${encodeURIComponent(block.id)}`; case 'gameScore': return `/games?filter=scoring`; case 'log': return `/logs/view/${encodeURIComponent(block.id)}`; @@ -224,7 +230,7 @@ const TYPE_COLORS = { pub:'#2ecc71', tribe:'#e67e22', event:'#e74c3c', task:'#f39c12', report:'#c0392b', image:'#2980b9', audio:'#8e44ad', video:'#d35400', document:'#27ae60', bookmark:'#f1c40f', forum:'#1abc9c', feed:'#95a5a6', transfer:'#e74c3c', market:'#e67e22', job:'#3498db', housing:'#16a085', - project:'#2ecc71', banking:'#f39c12', bankWallet:'#f39c12', bankClaim:'#f39c12', + project:'#2ecc71', schoolCourse:'#1abc9c', schoolLesson:'#1abc9c', schoolEnroll:'#1abc9c', schoolCertificate:'#1abc9c', banking:'#f39c12', bankWallet:'#f39c12', bankClaim:'#f39c12', pixelia:'#9b59b6', curriculum:'#1abc9c', aiExchange:'#3498db', tombstone:'#7f8c8d', parliamentTerm:'#8e44ad', parliamentProposal:'#8e44ad', parliamentLaw:'#8e44ad', parliamentCandidature:'#8e44ad', parliamentRevocation:'#8e44ad', @@ -232,7 +238,7 @@ const TYPE_COLORS = { courtsVerdict:'#c0392b', courtsSettlement:'#c0392b', courtsNomination:'#c0392b', map:'#27ae60', mapMarker:'#27ae60', shop:'#e67e22', shopProduct:'#e67e22', - pad:'#2ecc71', chat:'#3498db', gameScore:'#f39c12', + pad:'#2ecc71', room:'#27ae60', roomMember:'#27ae60', wikiPage:'#1abc9c', emergency:'#e74c3c', emergencyConfirm:'#e74c3c', emergencyUpdate:'#e74c3c', mailingList:'#8e44ad', logisticsRoute:'#16a085', logisticsRating:'#16a085', podcast:'#d35400', podcastEpisode:'#d35400', podcastOpinion:'#d35400', podcastPlay:'#d35400', campaign:'#c0392b', campaignSignature:'#c0392b', campaignUpdate:'#c0392b', chat:'#3498db', gameScore:'#f39c12', calendar:'#e74c3c' }; @@ -307,7 +313,7 @@ const renderSingleBlockView = (block, filter = 'recent', userId, search = {}, vi return template( i18n.blockchain, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.blockchain), p(i18n.blockchainDescription) ), @@ -393,7 +399,7 @@ const renderSingleBlockView = (block, filter = 'recent', userId, search = {}, vi return template( i18n.blockchain, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.blockchain), p(i18n.blockchainDescription) ), @@ -440,25 +446,28 @@ const renderBlockchainView = (blocks, filter, userId, search = {}, extras = {}) const shown = filterBlocks(blocks, filter, userId); const qs = toQueryString(filter, s); + const censusBlocks = Array.isArray(extras && extras.censusBlocks) ? extras.censusBlocks : blocks; + const chipVisible = (mode) => mode === filter || filterBlocks(censusBlocks, mode, userId).length > 0; + const baseChips = ['all', 'recent', 'mine', ...(chipVisible('tombstone') ? ['tombstone'] : []), ...(chipVisible('logs') ? ['logs'] : [])]; return template( i18n.blockchain, section( - div({ class:'tags-header' }, + div({ class:'tags-header module-header-line' }, h2(i18n.blockchain), p(i18n.blockchainDescription) ), div({ class:'mode-buttons-row' }, div({ class: 'filter-column' }, - generateFilterButtons(BASE_FILTERS, filter, '/blockexplorer', s) + generateFilterButtons(baseChips, filter, '/blockexplorer', s) ), div({ class: 'filter-column' }, - generateFilterButtons(CAT_BLOCK1, filter, '/blockexplorer', s), - generateFilterButtons(CAT_BLOCK2, filter, '/blockexplorer', s) + generateFilterButtons(CAT_BLOCK1.filter(chipVisible), filter, '/blockexplorer', s), + generateFilterButtons(CAT_BLOCK2.filter(chipVisible), filter, '/blockexplorer', s) ), div({ class: 'filter-column' }, - generateFilterButtons(CAT_BLOCK3, filter, '/blockexplorer', s), - generateFilterButtons(CAT_BLOCK4, filter, '/blockexplorer', s) + generateFilterButtons(CAT_BLOCK3.filter(chipVisible), filter, '/blockexplorer', s), + generateFilterButtons(CAT_BLOCK4.filter(chipVisible), filter, '/blockexplorer', s) ) ), div({ class: 'blockexplorer-search' }, @@ -512,7 +521,7 @@ const renderBlockchainView = (blocks, filter, userId, search = {}, extras = {}) h2({ class: 'block-diagram-title' }, 'Blockchain Blocks'), shown.length === 0 ? div(p(i18n.blockchainNoBlocks)) - : shown + : paged(shown .sort((a,b)=>{ const ta = a.type==='market'&&a.content.updatedAt ? new Date(a.content.updatedAt).getTime() @@ -521,7 +530,7 @@ const renderBlockchainView = (blocks, filter, userId, search = {}, extras = {}) ? new Date(b.content.updatedAt).getTime() : b.ts; return tb - ta; - }) + })) .map(block=> div({ class:'block' }, div({ class:'block-buttons' }, diff --git a/src/views/blog_view.js b/src/views/blog_view.js index fb6401c..df19bcb 100644 --- a/src/views/blog_view.js +++ b/src/views/blog_view.js @@ -1,18 +1,20 @@ const { div, h2, p, section, button, form, a, input, label, span, textarea, br, table, tr, td } = require("../server/node_modules/hyperaxe"); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const { TEXT_CAP } = require('../backend/long_text'); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, userLink, renderOpinionsVoting, renderEngagement, renderSpreadButton, renderContentActions } = require("./main_views"); +const { clearnetItemHref, template, i18n, userLink, renderOpinionsVoting, renderEngagement, renderSpreadButton, renderContentActions, contentDeleteAction, renderSubscriptionBox, renderModuleStats, moduleIsEmpty, paged } = require("./main_views"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); -const { renderTextWithStyles } = require("../backend/renderTextWithStyles"); +const { renderStyledHtml } = require("../backend/renderStyledText"); const { sanitizeHtml } = require("../backend/sanitizeHtml"); const userId = config.keys.id; const FILTERS = [ { key: "ALL", i18n: "blogFilterAll" }, - { key: "MINE", i18n: "blogFilterMine" }, { key: "RECENT", i18n: "blogFilterRecent" }, + { key: "MINE", i18n: "blogFilterMine" }, { key: "FAVORITES", i18n: "blogFilterFavorites" }, { key: "TOP", i18n: "blogFilterTop" } ]; @@ -24,18 +26,30 @@ const excerpt = (text, max = 420) => { return s.length > max ? s.slice(0, max) + "…" : s; }; -const renderFilterBar = (filter, q, showSearch = true) => - section( +const renderFilterBar = (filter, q, showSearch = true, total = null, censusList = null) => { + const emptyMod = total !== null && moduleIsEmpty({ length: Number(total) || 0 }, filter, "ALL", q); + const censusB = Array.isArray(censusList) ? censusList : []; + const blogChip = (x) => { + const m = x.key; + if (m === filter || m === "ALL") return true; + if (m === "TOP") return censusB.length > 0; + if (m === "MINE") return censusB.some(b => String(b.author) === String(userId)); + if (m === "RECENT") return censusB.length > 0; + if (m === "FAVORITES") return censusB.some(b => b.isFavorite); + return true; + }; + return section( div({ class: "filters" }, form({ method: "GET", action: "/blogs", class: "ui-toolbar ui-toolbar--filters" }, - ...FILTERS.map(f => + ...(emptyMod ? [] : FILTERS.filter(blogChip).map(f => button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, String(i18n[f.i18n]).toUpperCase()) - ), + )), button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.blogCreateButton) ) ), - showSearch - ? div({ class: "filters" }, + showSearch && !emptyMod + ? div({ class: "filters activity-filter-chips activity-toolbar-row" }, + total != null ? renderModuleStats(total) : null, form({ method: "GET", action: "/blogs", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: q || "", placeholder: i18n.blogSearchPlaceholder, class: "filter-box__input" }), @@ -46,13 +60,14 @@ const renderFilterBar = (filter, q, showSearch = true) => ) : null ); +}; const renderBlogCard = (blog, filter, spreadInfo) => { const href = `/blogs/${encodeURIComponent(blog.id)}`; const isOwn = String(blog.author) === String(userId); return div({ class: "trending-card blog-card" + (isOwn ? " own-content" : "") }, div({ class: "card-header activity-card-header" }, - renderContentActions(blog.id, href, { spread: spreadInfo || null, author: blog.author, favKind: 'blogs', isFavorite: blog.isFavorite, reportTitle: blog.subject || blog.text }) + renderContentActions(blog.id, href, { spread: spreadInfo || null, author: blog.author, favKind: 'blogs', isFavorite: blog.isFavorite, reportTitle: blog.subject || blog.text, deleteAction: isOwn ? contentDeleteAction('blog', blog.id) : undefined }) ), div({ class: "card-section blog-card-body" }, blog.subject @@ -60,28 +75,40 @@ const renderBlogCard = (blog, filter, spreadInfo) => { h2({ class: "tribe-card-title" }, a({ href }, blog.subject)) ) : null, - div({ class: "blog-card-text", innerHTML: sanitizeHtml(renderTextWithStyles(excerpt(blog.text))) }), + blog.clearnet === true ? div({ class: "card-chips-row" }, renderReachChip(true, i18n, clearnetItemHref("blog", blog.subject || blog.text, blog.id))) : null, + div({ class: "blog-card-text", [RAW_HTML]: sanitizeHtml(renderStyledHtml(excerpt(blog.text))) }), p({ class: "card-footer" }, - span({ class: "date-link" }, moment(blog.createdAt).format("YYYY/MM/DD HH:mm")), - span(" · "), + span({ class: "date-link" }, `${moment(blog.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(blog.author) ) ) ); }; -const renderCreateForm = () => - section( +const renderCreateForm = (draft = null) => { + const subjectValue = draft ? safeText(draft.subject) : ""; + const textValue = draft ? String(draft.text || "") : ""; + const allowComments = draft ? draft.allowComments !== false : true; + return section( + draft + ? section({ class: "post-preview blog-preview" }, + div({ class: "preview-content" }, + h2(i18n.messagePreview), + subjectValue ? h2({ class: "tribe-card-title" }, subjectValue) : null, + div({ class: "blog-detail-text", [RAW_HTML]: sanitizeHtml(renderStyledHtml(textValue)) }) + ) + ) + : null, div({ class: "publish-form" }, form({ action: "/blogs/create", method: "POST", enctype: "multipart/form-data" }, label({ for: "subject" }, i18n.blogSubject), br(), - input({ type: "text", name: "subject", id: "subject", maxlength: "150", placeholder: i18n.contentWarningPlaceholder }), + input({ type: "text", name: "subject", id: "subject", maxlength: "150", placeholder: i18n.contentWarningPlaceholder, value: subjectValue }), br(), br(), label({ for: "text" }, i18n.blogMessage), br(), - textarea({ required: true, name: "text", id: "text", rows: "8", class: "publish-textarea", maxlength: "7000", placeholder: i18n.publishWarningPlaceholder }), + textarea({ required: true, name: "text", id: "text", rows: "8", class: "publish-textarea", maxlength: String(TEXT_CAP), placeholder: i18n.publishWarningPlaceholder }, textValue), br(), br(), label({ for: "blob" }, i18n.blogMedia), @@ -92,16 +119,20 @@ const renderCreateForm = () => div({ class: "blog-allow-comments" }, input({ type: "hidden", name: "allowComments", value: "0" }), label( - input({ type: "checkbox", name: "allowComments", value: "1", checked: true }), + input({ type: "checkbox", name: "allowComments", value: "1", ...(allowComments ? { checked: true } : {}) }), " ", i18n.blogAllowComments ) ), + renderClearnetSelector(!!(draft && draft.clearnet === true), i18n), br(), - button({ type: "submit" }, i18n.blogPublish) + button({ type: "submit", class: "filter-btn", formaction: "/blogs/preview", attrs: { formmethod: "POST" } }, i18n.preview), + " ", + button({ type: "submit", class: "create-button" }, i18n.blogPublish) ) ) ); +}; const renderCommentsSection = (blog, comments = []) => { const href = `/blogs/${encodeURIComponent(blog.id)}`; @@ -119,18 +150,18 @@ exports.blogView = async (blogs = [], filter = "ALL", params = {}) => { return template( i18n.blogTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.blogTitle), p(i18n.blogDescription) ) ), - renderFilterBar(showForm ? "ALL" : filter, params.q, !showForm), + renderFilterBar(showForm ? "CREATE" : filter, params.q, !showForm, Array.isArray(blogs) ? blogs.length : 0, params.censusList), showForm - ? renderCreateForm() + ? renderCreateForm(params.draft || null) : section( blogs.length - ? div({ class: "jobs-grid" }, ...blogs.map(b => renderBlogCard(b, filter, spreadMap.get(b.id)))) - : p({ class: "no-content" }, i18n.blogNoItems) + ? div({ class: "blogs-grid" }, ...paged(blogs).map(b => renderBlogCard(b, filter, spreadMap.get(b.id)))) + : div({ class: "no-content-box" }, p({ class: "no-content" }, i18n.blogNoItems)) ) ); }; @@ -138,14 +169,17 @@ exports.blogView = async (blogs = [], filter = "ALL", params = {}) => { exports.singleBlogView = async (blog, comments = [], params = {}) => { const href = `/blogs/${encodeURIComponent(blog.id)}`; const isAuthor = String(blog.author) === String(userId); + const isClearnet = !!blog.clearnet; const blogSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(blog.id, null, { spread: params.spreads || null, author: blog.author, favKind: 'blogs', isFavorite: blog.isFavorite, reportTitle: blog.subject || blog.text }) + renderContentActions(blog.id, href, { spread: params.spreads || null, author: blog.author, favKind: 'blogs', isFavorite: blog.isFavorite, reportTitle: blog.subject || blog.text, returnTo: href, deleteAction: isAuthor ? contentDeleteAction('blog', blog.id) : undefined }) ), div({ class: "shop-title-row" }, - h2({ class: "tribe-card-title" }, blog.subject || i18n.blogTitle) + h2({ class: "tribe-card-title" }, blog.subject || i18n.blogTitle), + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref("blog", blog.subject || blog.text, blog.id) : null) ), + isAuthor ? renderClearnetSwitch("posts", blog.rootId || blog.id, isClearnet) : null, table({ class: "tribe-info-table jobs-info-table" }, tr( td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), @@ -158,11 +192,21 @@ exports.singleBlogView = async (blog, comments = [], params = {}) => { div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.blogComments}: ${blog.commentCount || 0}`) ), + params.subscription + ? renderSubscriptionBox({ + target: blog.author, + scope: "blogs", + subscribed: params.subscription.subscribed === true, + count: params.subscription.count, + isOwner: isAuthor, + returnTo: href + }) + : null, ); const blogMain = div({ class: "tribe-main" }, div({ class: "job-section" }, - div({ class: "blog-detail-text", innerHTML: sanitizeHtml(renderTextWithStyles(blog.text || "")) }) + div({ class: "blog-detail-text", [RAW_HTML]: sanitizeHtml(renderStyledHtml(blog.text || "")) }) ), renderEngagement(blog.id, renderOpinionsVoting('/blogs/opinions', blog.id, blog.opinions, href, blog.opinions_inhabitants), @@ -172,8 +216,8 @@ exports.singleBlogView = async (blog, comments = [], params = {}) => { return template( blog.subject || i18n.blogTitle, - section(div({ class: "tags-header" }, h2(i18n.blogTitle), p(i18n.blogDescription))), - renderFilterBar("ALL", params.q, false), + section(div({ class: "tags-header module-header-line" }, h2(i18n.blogTitle), p(i18n.blogDescription))), + renderFilterBar("ALL", params.q, false, null, params.censusList), section(div({ class: "tribe-details" }, blogSide, blogMain)) ); }; diff --git a/src/views/bookmark_view.js b/src/views/bookmark_view.js index 671b4cf..e979039 100644 --- a/src/views/bookmark_view.js +++ b/src/views/bookmark_view.js @@ -2,10 +2,11 @@ const { form, button, div, h2, p, section, input, label, textarea, br, a, span, require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection, renderCommentsLink } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions, renderSpreadEditWarning } = require("./main_views"); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions, renderSpreadEditWarning, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require("./clearnet_view"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText, safeExternalHref } = require("../backend/renderStyledText"); const userId = config.keys.id; @@ -34,30 +35,6 @@ const renderPMButton = (recipient, className = "filter-btn") => { ); }; -const renderBookmarkActions = (filter, bookmark, params = {}) => { - const returnTo = buildReturnTo(filter, params); - const isAuthor = String(bookmark.author) === String(userId); - const hasOpinions = Object.keys(bookmark.opinions || {}).length > 0; - - return isAuthor - ? div( - { class: "bookmark-actions" }, - !hasOpinions - ? form( - { method: "GET", action: `/bookmarks/edit/${encodeURIComponent(bookmark.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "update-btn", type: "submit" }, i18n.bookmarkUpdateButton) - ) - : null, - form( - { method: "POST", action: `/bookmarks/delete/${encodeURIComponent(bookmark.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.bookmarkDeleteButton) - ) - ) - : null; -}; - const renderBookmarkCommentsSection = (bookmarkId, rootId, comments = [], returnTo = null) => { return renderSharedCommentsSection({ action: `/bookmarks/${encodeURIComponent(bookmarkId)}/comments`, @@ -83,7 +60,7 @@ const renderTags = (tags) => { : null; }; -const renderBookmarkList = (filteredBookmarks, filter, params = {}) => { +const renderBookmarkList = exports.renderBookmarkList = (filteredBookmarks, filter, params = {}) => { const returnTo = buildReturnTo(filter, params); return filteredBookmarks.length @@ -97,28 +74,40 @@ const renderBookmarkList = (filteredBookmarks, filter, params = {}) => { : i18n.noLastVisit; const urlLink = bookmark.url - ? a({ href: bookmark.url, target: "_blank", rel: "noreferrer noopener", class: "bookmark-url" }, bookmark.url) + ? a({ href: safeExternalHref(bookmark.url), target: "_blank", rel: "noreferrer noopener", class: "bookmark-url" }, bookmark.url) : i18n.noUrl; const isOwn = bookmark.author && String(bookmark.author) === String(userId); + const reachChip = bookmark.tribeOrigin ? renderTribeOriginChip(bookmark.tribeOrigin) : bookmark.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref("bookmarks", bookmark.title || bookmark.url, bookmark.id)) : null; + const extraChips = typeof params.titleChips === "function" ? params.titleChips(bookmark).filter(Boolean) : []; + const headerActions = typeof params.headerActions === "function" + ? params.headerActions(bookmark) + : bookmark.tribeOrigin + ? renderContentActions(null, bookmark.tribeOrigin.href) + : renderContentActions(bookmark.id, `/bookmarks/${encodeURIComponent(bookmark.id)}`, { spread: (params.spreadMap && params.spreadMap.get(bookmark.id)) || params.spreads || null, author: bookmark.author, favKind: 'bookmarks', isFavorite: bookmark.isFavorite, reportTitle: bookmark.title, deleteAction: isOwn ? contentDeleteAction('bookmark', bookmark.id) : undefined, returnTo }); + const titleHref = typeof params.headerActions === "function" ? null : bookmark.tribeOrigin ? bookmark.tribeOrigin.href : `/bookmarks/${encodeURIComponent(bookmark.id)}`; + const engagement = typeof params.engagement === "function" + ? params.engagement(bookmark) + : bookmark.tribeOrigin ? null : renderEngagement(bookmark.id, + renderOpinionsVoting('/bookmarks/opinions', bookmark.id, bookmark.opinions, returnTo, bookmark.opinions_inhabitants), + renderCommentsLink({ href: `/bookmarks/${encodeURIComponent(bookmark.id)}`, count: commentCount }) + ); return div( { class: "trending-card bookmark-card" + (isOwn ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(bookmark.id, `/bookmarks/${encodeURIComponent(bookmark.id)}`, { spread: (params.spreadMap && params.spreadMap.get(bookmark.id)) || params.spreads || null, author: bookmark.author, favKind: 'bookmarks', isFavorite: bookmark.isFavorite, reportTitle: bookmark.title }) + headerActions ), div( { class: "card-section bookmark-card-body" }, - h2({ class: "bookmark-title" }, bookmark.url ? urlLink : (bookmark.title || "")), - bookmark.lifetime ? div({ class: "card-chips-row" }, renderLifespanChip(bookmark.lifetime, i18n)) : null, - bookmark.title && bookmark.url ? p({ class: "bookmark-subtitle" }, bookmark.title) : null, - renderCardField(i18n.bookmarkLastVisitLabel + ":", lastVisitTxt), + h2({ class: "bookmark-title" }, titleHref ? a({ href: titleHref, class: "card-title-link" }, bookmark.title || bookmark.url || "") : (bookmark.url ? urlLink : (bookmark.title || ""))), + bookmark.lifetime || reachChip || extraChips.length ? div({ class: "card-chips-row" }, renderLifespanChip(bookmark.lifetime, i18n), reachChip, ...extraChips) : null, + titleHref ? (bookmark.title && bookmark.url ? p({ class: "bookmark-subtitle" }, urlLink) : null) : (bookmark.title && bookmark.url ? p({ class: "bookmark-subtitle" }, bookmark.title) : null), + params.hideLastVisit ? null : renderCardField(i18n.bookmarkLastVisitLabel + ":", lastVisitTxt), + ...(typeof params.bodyExtra === "function" ? params.bodyExtra(bookmark) : []), br, - renderEngagement(bookmark.id, - renderOpinionsVoting('/bookmarks/opinions', bookmark.id, bookmark.opinions, returnTo, bookmark.opinions_inhabitants), - renderCommentsLink({ href: `/bookmarks/${encodeURIComponent(bookmark.id)}`, count: commentCount }) - ), + engagement, (() => { const createdTs = bookmark.createdAt ? new Date(bookmark.createdAt).getTime() : NaN; const updatedTs = bookmark.updatedAt ? new Date(bookmark.updatedAt).getTime() : NaN; @@ -126,7 +115,7 @@ const renderBookmarkList = (filteredBookmarks, filter, params = {}) => { return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(bookmark.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(bookmark.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(bookmark.author), showUpdated ? span( @@ -199,11 +188,24 @@ const renderBookmarkForm = (filter, bookmarkId, bookmarkToEdit, tags, params = { }), br(), br(), + renderClearnetSelector(filter === "edit" ? !!bookmarkToEdit.clearnet : false, i18n), + br(), button({ type: "submit" }, filter === "edit" ? i18n.bookmarkUpdateButton : i18n.bookmarkCreateButton) ) ); }; +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.some((x) => !x.tribeOrigin); + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + exports.bookmarkView = async (bookmarks, filter = "all", bookmarkId = null, params = {}) => { const bookmarkEditWarning = filter === "edit" ? await renderSpreadEditWarning(bookmarkId) : null; const title = i18n.bookmarkTitle; @@ -212,30 +214,28 @@ exports.bookmarkView = async (bookmarks, filter = "all", bookmarkId = null, para const sort = safeText(params.sort || "recent"); const list = safeArr(bookmarks); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); const bookmarkToEdit = bookmarkId ? list.find((b) => b.id === bookmarkId) : null; const tags = bookmarkToEdit && Array.isArray(bookmarkToEdit.tags) ? bookmarkToEdit.tags : []; return template( title, section( - div({ class: "tags-header" }, h2(title), p(i18n.bookmarkDescription)), - (() => { - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetBookmarks); - return div({ class: "shop-title-row" }, renderReachChip(isClearnet, i18n)); - })(), - br(), + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.bookmarkDescription)), div( { class: "filters" }, form( { method: "GET", action: "/bookmarks", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterRecent).toUpperCase()), - button({ type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterFavorites).toUpperCase()), - button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterTop).toUpperCase()), + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button({ type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterFavorites).toUpperCase())] : []), + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterTop).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.bookmarkCreateButton) ) ) @@ -244,8 +244,9 @@ exports.bookmarkView = async (bookmarks, filter = "all", bookmarkId = null, para filter === "edit" || filter === "create" ? renderBookmarkForm(filter, bookmarkId, bookmarkToEdit || {}, tags, { ...params, filter, spreadWarning: bookmarkEditWarning }) : section( - div( - { class: "bookmarks-search" }, + emptyMod ? null : div( + { class: "bookmarks-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form( { method: "GET", action: "/bookmarks", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -262,21 +263,21 @@ exports.bookmarkView = async (bookmarks, filter = "all", bookmarkId = null, para ) ) ), - div({ class: "bookmark-list" }, renderBookmarkList(list, filter, { q, sort })) + div({ class: "bookmark-list" }, renderBookmarkList(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) ) ) ); }; exports.singleBookmarkView = async (bookmark, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); const q = safeText(params.q || ""); const sort = safeText(params.sort || "recent"); const returnTo = params.returnTo || buildReturnTo(filter, { q, sort }); const isAuthor = String(bookmark.author) === String(userId); const hasOpinions = Object.keys(bookmark.opinions || {}).length > 0; - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetBookmarks); + const isClearnet = !!bookmark.clearnet; const lastVisit = bookmark.lastVisit ? moment(bookmark.lastVisit) : null; const lastVisitTxt = @@ -285,7 +286,7 @@ exports.singleBookmarkView = async (bookmark, filter = "all", comments = [], par : i18n.noLastVisit; const urlLink = bookmark.url - ? a({ href: bookmark.url, target: "_blank", rel: "noreferrer noopener", class: "bookmark-url" }, bookmark.url) + ? a({ href: safeExternalHref(bookmark.url), target: "_blank", rel: "noreferrer noopener", class: "bookmark-url" }, bookmark.url) : i18n.noUrl; const chips = [ @@ -302,36 +303,31 @@ exports.singleBookmarkView = async (bookmark, filter = "all", comments = [], par button({ class: "update-btn", type: "submit" }, i18n.bookmarkUpdateButton) )); } - if (isAuthor) { - sideActions.push(form( - { method: "POST", action: `/bookmarks/delete/${encodeURIComponent(bookmark.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.bookmarkDeleteButton) - )); - } const tagsNode = renderTags(bookmark.tags); const detailActions = div({ class: "card-header activity-card-header" }, - renderContentActions(bookmark.id, null, { + renderContentActions(bookmark.id, `/bookmarks/${encodeURIComponent(bookmark.id)}`, { author: bookmark.author, favKind: 'bookmarks', isFavorite: bookmark.isFavorite, spread: params.spreads || null, returnTo, - reportTitle: bookmark.title + reportTitle: bookmark.title, + deleteAction: isAuthor ? contentDeleteAction('bookmark', bookmark.id) : undefined }) ); const bookmarkSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, bookmark.url ? urlLink : (bookmark.title || "")), - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref("bookmarks", bookmark.title || bookmark.url, bookmark.id) : null) ), + isAuthor ? renderClearnetSwitch("bookmarks", bookmark.rootId || bookmark.id, isClearnet) : null, bookmark.title && bookmark.url ? p({ class: "bookmark-subtitle" }, bookmark.title) : null, chips.length ? div({ class: "card-chips-row" }, ...chips) : null, safeText(bookmark.description) - ? p({ class: "tribe-side-description" }, ...renderUrl(bookmark.description)) + ? p({ class: "tribe-side-description" }, ...renderStyledText(bookmark.description)) : null, tagsNode, sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null @@ -346,7 +342,7 @@ exports.singleBookmarkView = async (bookmark, filter = "all", comments = [], par const showUpdated = Number.isFinite(updatedTs) && (!Number.isFinite(createdTs) || updatedTs !== createdTs); return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(bookmark.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(bookmark.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(bookmark.author), showUpdated ? span( @@ -365,7 +361,7 @@ exports.singleBookmarkView = async (bookmark, filter = "all", comments = [], par return template( i18n.bookmarkTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.bookmarkAllSectionTitle || i18n.bookmarkTitle), p(i18n.bookmarkDescription) ), @@ -376,9 +372,9 @@ exports.singleBookmarkView = async (bookmark, filter = "all", comments = [], par input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterRecent).toUpperCase()), - button({ type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterFavorites).toUpperCase()), + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button({ type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterFavorites).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.bookmarkFilterTop).toUpperCase()), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.bookmarkCreateButton) ) diff --git a/src/views/calendars_view.js b/src/views/calendars_view.js index 30a5cc4..f1990c4 100644 --- a/src/views/calendars_view.js +++ b/src/views/calendars_view.js @@ -1,35 +1,31 @@ -const { div, h2, h3, h4, p, section, button, form, a, span, br, textarea, input, label, select, option, table, tr, td, ul, li } = require("../server/node_modules/hyperaxe") -const { template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton , renderSpreadEditWarning, renderContentActions, renderDocumentActions } = require("./main_views") -const { renderMapLocationVisitLabel } = require("./maps_view") -const { renderEncryptedChip } = require("./clearnet_view") +const { div, h2, h3, h4, p, section, button, form, a, span, br, hr, textarea, input, label, select, option, table, tr, td, ul, li } = require("../server/node_modules/hyperaxe") +const { renderStyledText } = require("../backend/renderStyledText") +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton , renderSpreadEditWarning, renderContentActions, renderDocumentActions, renderInviteQrCard, renderSubscriptionBox, renderModuleStatsBy, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views") +const { renderMapEmbed } = require("./maps_view") +const { renderEncryptedChip, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require("./clearnet_view") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") const userId = config.keys.id +const CAL_REACH = ["OPEN", "CLOSED"] +const calReachLabel = (s) => s === "CLOSED" ? (i18n.calendarStatusClosed || "CLOSED") : (i18n.calendarStatusOpen || "OPEN") +const hasPublicInvite = (cal) => Array.isArray(cal.invites) && cal.invites.some(i => i && typeof i === "object" && i.public === true) +const clearnetEligible = (cal) => !!cal && !cal.tribeId && !cal.encrypted && String(cal.status || "").toUpperCase() === "OPEN" && !cal.isClosed && (!cal.contentEncrypted || hasPublicInvite(cal)) +const calClearnetHref = (cal) => clearnetItemHref("calendars", cal.title, cal.rootId) -const renderNoteText = (text) => { - if (!text) return [] - const urlRegex = /(https?:\/\/[^\s]+)/g - const result = [] - let last = 0 - text.replace(urlRegex, (match, _g, offset) => { - if (offset > last) result.push(text.slice(last, offset)) - result.push(a({ href: match, target: "_blank" }, match)) - last = offset + match.length - }) - if (last < text.length) result.push(text.slice(last)) - return result -} +const renderNoteText = (text) => renderStyledText(String(text || "")) -const renderModeButtons = (currentFilter) => +const renderModeButtons = (currentFilter, emptyMod = false, modesAvail = null) => div({ class: "tribe-mode-buttons" }, - ["all", "mine", "recent", "favorites", "open", "closed"].map(f => + ...(emptyMod ? [] : [ + ["all", "recent", "mine", "favorites", "open", "closed"].filter(f => f === "all" || f === currentFilter || (modesAvail && modesAvail[f] !== false)).map(f => form({ method: "GET", action: "/calendars" }, input({ type: "hidden", name: "filter", value: f }), button({ type: "submit", class: currentFilter === f ? "filter-btn active" : "filter-btn" }, i18n[`calendarFilter${f.charAt(0).toUpperCase() + f.slice(1)}`] || f.toUpperCase()) ) ), + ]), form({ method: "GET", action: "/calendars" }, input({ type: "hidden", name: "filter", value: "create" }), button({ type: "submit", class: "create-button" }, i18n.calendarCreate || "Create Calendar") @@ -49,16 +45,23 @@ const renderCalendarStatusChip = (cal) => { return renderStateChip(variant, icon, label) } -const renderCalendarCard = (cal, spreadInfo) => { - const href = `/calendars/${encodeURIComponent(cal.rootId)}` +const renderCalendarCard = exports.renderCalendarCard = (cal, spreadInfo) => { + const origin = cal.tribeOrigin || null + const href = origin ? origin.href : `/calendars/${encodeURIComponent(cal.rootId)}` const chips = [ renderCalendarStatusChip(cal), - renderEncryptedChip(i18n), - renderLifespanChip(cal.lifetime, i18n) + origin ? renderTribeOriginChip(origin) : renderEncryptedChip(i18n), + renderLifespanChip(cal.lifetime, i18n), + !origin && cal.clearnet === true && clearnetEligible(cal) ? renderReachChip(true, i18n, calClearnetHref(cal)) : null, + cal.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (cal.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null) ].filter(Boolean) return div({ class: "tribe-card" }, div({ class: "card-header activity-card-header" }, - renderContentActions(cal.rootId, href, { spread: spreadInfo || null, author: cal.author, favKind: 'calendars', isFavorite: cal.isFavorite, reportTitle: cal.title }) + renderContentActions(cal.rootId, href, origin + ? { author: cal.author, reportTitle: cal.title } + : { spread: spreadInfo || null, author: cal.author, favKind: 'calendars', isFavorite: cal.isFavorite, reportTitle: cal.title, deleteAction: String(cal.author) === String(userId) ? contentDeleteAction('calendar', cal.rootId) : undefined }) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, @@ -66,11 +69,19 @@ const renderCalendarCard = (cal, spreadInfo) => { ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, cal.deadline - ? p({ class: "job-meta-line" }, `${i18n.calendarDeadlineLabel || "Deadline"}: ${moment(cal.deadline).format("YYYY/MM/DD HH:mm")}`) + ? div({ class: "card-field card-field-mb" }, + span({ class: "card-label" }, `${String(i18n.calendarDeadlineLabel).toUpperCase()}: `), + span({ class: "card-value" }, moment(cal.deadline).format("YYYY/MM/DD HH:mm")) + ) : null, - div({ class: "tribe-card-members" }, - span({ class: "tribe-members-count calendar-participants-count" }, `${i18n.calendarParticipantsLabel || "Participants"}: ${cal.participants.length}`) - ) + origin && Array.isArray(cal.dates) && cal.dates.length + ? div(...cal.dates.map(d => p({ class: "job-meta-line" }, `📅 ${moment(d.date).format("YYYY/MM/DD HH:mm")}${d.label ? ` · ${d.label}` : ""}`))) + : null, + origin + ? null + : div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count calendar-participants-count" }, `${i18n.calendarParticipantsLabel || "Participants"}: ${cal.participants.length}`) + ) ) ) } @@ -100,6 +111,11 @@ const renderIntervalBlock = (bounds = {}, current = "", until = "") => { const renderCreateForm = (calendarToEdit, params) => { const isEdit = !!calendarToEdit const tribeId = (params && params.tribeId) || "" + const draft = isEdit ? calendarToEdit : ((params && params.draft) || {}) + const reachRaw = String((params && params.reach) || draft.status || "").toUpperCase() + const reach = CAL_REACH.includes(reachRaw) ? reachRaw : "OPEN" + const showClearnet = reach === "OPEN" && !tribeId && clearnetEligible({ ...(isEdit ? calendarToEdit : {}), status: reach, isClosed: false }) + const asLocal = (v) => v ? moment(v).format("YYYY-MM-DDTHH:mm") : "" const now = moment().add(1, "minute").format("YYYY-MM-DDTHH:mm") const deadlineMax = calendarToEdit && calendarToEdit.deadline ? moment(calendarToEdit.deadline).format("YYYY-MM-DDTHH:mm") @@ -109,48 +125,58 @@ const renderCreateForm = (calendarToEdit, params) => { return div({ class: "div-center audio-form" }, h2(sectionTitle), (params && params.spreadWarning) || null, + form({ method: "GET", action: "/calendars" }, + input({ type: "hidden", name: "filter", value: isEdit ? "edit" : "create" }), + isEdit ? input({ type: "hidden", name: "id", value: calendarToEdit.rootId }) : null, + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + label(i18n.calendarTypeLabel), br(), + div({ class: "apply-row" }, + select({ name: "status", class: "report-category-select" }, + ...CAL_REACH.map(s => option({ value: s, ...(reach === s ? { selected: true } : {}) }, calReachLabel(s))) + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, calReachLabel(reach)), form({ method: "POST", action }, tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + input({ type: "hidden", name: "status", value: reach }), span(i18n.calendarTitleLabel || "Title"), br(), - input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.calendarTitlePlaceholder || "Calendar title...", value: calendarToEdit ? calendarToEdit.title : "" }), - br(), br(), - span(i18n.calendarStatusLabel || "Status"), br(), - select({ name: "status", required: true }, - option({ value: "OPEN", ...((!calendarToEdit || calendarToEdit.status === "OPEN") ? { selected: true } : {}) }, i18n.calendarStatusOpen || "OPEN"), - option({ value: "CLOSED", ...((calendarToEdit && calendarToEdit.status === "CLOSED") ? { selected: true } : {}) }, i18n.calendarStatusClosed || "CLOSED") - ), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.calendarTitlePlaceholder || "Calendar title...", value: draft.title || "" }), br(), br(), span(i18n.calendarDeadlineLabel || "Deadline"), br(), - input({ type: "datetime-local", name: "deadline", required: true, min: now, value: calendarToEdit && calendarToEdit.deadline ? moment(calendarToEdit.deadline).format("YYYY-MM-DDTHH:mm") : "" }), + input({ type: "datetime-local", name: "deadline", required: true, min: now, value: asLocal(draft.deadline) }), br(), br(), span(i18n.calendarTagsLabel || "Tags"), br(), - input({ type: "text", name: "tags", placeholder: i18n.calendarTagsPlaceholder || "tag1, tag2...", value: calendarToEdit && Array.isArray(calendarToEdit.tags) ? calendarToEdit.tags.join(", ") : "" }), + input({ type: "text", name: "tags", placeholder: i18n.calendarTagsPlaceholder || "Enter tags separated by commas", value: Array.isArray(draft.tags) ? draft.tags.join(", ") : "" }), br(), br(), span(i18n.mapLocationTitle || "Map Location"), br(), - input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: (calendarToEdit && calendarToEdit.mapUrl) || "" }), + input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: draft.mapUrl || "" }), br(), br(), !isEdit ? [ span(i18n.calendarFirstDateLabel || "Date"), br(), - input({ type: "datetime-local", name: "firstDate", required: true, min: now, max: deadlineMax || undefined }), + input({ type: "datetime-local", name: "firstDate", required: true, min: now, max: deadlineMax || undefined, ...(draft.firstDate ? { value: asLocal(draft.firstDate) } : {}) }), br(), br(), span(i18n.calendarFormDescription || "Description"), br(), - input({ type: "text", name: "firstDateLabel", placeholder: i18n.calendarDatePlaceholder || "Describe this date..." }), + input({ type: "text", name: "firstDateLabel", placeholder: i18n.calendarDatePlaceholder || "Describe this date...", value: draft.firstDateLabel || "" }), br(), br(), span(i18n.calendarFirstNoteLabel || "Notes"), br(), - textarea({ name: "firstNote", rows: "3", placeholder: i18n.calendarNotePlaceholder || "Add a note..." }), + textarea({ maxlength: "5000", name: "firstNote", rows: "3", placeholder: i18n.calendarNotePlaceholder || "Add a note..." }, draft.firstNote || ""), br(), br(), - renderIntervalBlock({ min: now, max: deadlineMax || undefined }), + renderIntervalBlock({ min: now, max: deadlineMax || undefined }, draft.interval || "", asLocal(draft.intervalDeadline)), br(), br() ] : null, + showClearnet ? renderClearnetSelector(draft.clearnet === true || String(draft.clearnet || "") === "1", i18n) : null, button({ type: "submit", class: "create-button" }, isEdit ? (i18n.calendarUpdate || "Update") : (i18n.calendarCreate || "Create Calendar")) ) ) } const renderMonthGrid = (year, month, datesMap, calendarId) => { - const DAY_NAMES = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"] + const DAY_NAMES = String(i18n.calendarDayInitials || "M,T,W,T,F,S,S").split(",") const firstDay = new Date(year, month, 1) const daysInMonth = new Date(year, month + 1, 0).getDate() const startPad = (firstDay.getDay() + 6) % 7 @@ -193,19 +219,21 @@ exports.calendarsView = async (calendars, filter, calendarToEdit, params) => { if (calendarToEdit) params = { ...(params || {}), spreadWarning: await renderSpreadEditWarning(calendarToEdit.id || calendarToEdit.key || calendarToEdit.rootId) }; const q = (params && params.q) || "" const showForm = filter === "create" || filter === "edit" || !!calendarToEdit + const emptyMod = moduleIsEmpty(Array.isArray(calendars) ? calendars : [], filter || "all", "all", q) const headerText = i18n.calendarsTitle || "Calendars" return template( i18n.calendarsTitle || "Calendars", section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(headerText), p(i18n.calendarsDescription || "Discover and manage calendars.") ), - renderModeButtons(filter), - showForm + renderModeButtons(filter, emptyMod, (params && params.modesAvail) || null), + showForm || emptyMod ? null - : div({ class: "filters" }, + : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(calendars, c => c.isClosed ? 'CLOSED' : String(c.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.calendarStatusOpen }, { value: 'CLOSED', label: i18n.calendarStatusClosed }]), form({ method: "GET", action: "/calendars", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: q, placeholder: i18n.calendarSearchPlaceholder || "Search calendars...", class: "filter-box__input" }), @@ -219,8 +247,8 @@ exports.calendarsView = async (calendars, filter, calendarToEdit, params) => { showForm ? renderCreateForm(calendarToEdit, params) : (calendars.length > 0 - ? div({ class: "tribe-grid" }, ...calendars.map(c => renderCalendarCard(c, params && params.spreadMap && params.spreadMap.get(c.rootId)))) - : p({ class: "no-content" }, i18n.calendarsNoItems || "No calendars found.")) + ? div({ class: "tribe-grid" }, ...paged(calendars).map(c => renderCalendarCard(c, params && params.spreadMap && params.spreadMap.get(c.rootId)))) + : div({ class: "no-content-box" }, p({ class: "no-content" }, i18n.calendarsNoItems || "No calendars found."))) ) ) } @@ -247,13 +275,21 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { } const tags = Array.isArray(calendar.tags) && calendar.tags.length > 0 - ? div({ class: "tribe-side-tags" }, ...calendar.tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}` }, `#${t} `))) + ? div({ class: "tribe-side-tags" }, ...calendar.tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) : null + const subscriptionIn = isAuthor || (calendar.subscription && calendar.subscription.subscribed === true) + const canClearnet = clearnetEligible(calendar) + const isClearnet = canClearnet && calendar.clearnet === true const detailChips = [ renderCalendarStatusChip(calendar), renderEncryptedChip(i18n), - renderLifespanChip(calendar.lifetime, i18n) + renderLifespanChip(calendar.lifetime, i18n), + canClearnet ? renderReachChip(isClearnet, i18n, isClearnet ? calClearnetHref(calendar) : null) : null, + isAuthor && canClearnet ? renderClearnetSwitch("calendars", calendar.rootId, isClearnet) : null, + (isAuthor || isParticipant) + ? renderStateChip(subscriptionIn ? "mutuals" : "closed", "✉", subscriptionIn ? i18n.subscriptionOn : i18n.subscriptionOff) + : null ].filter(Boolean) const calSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, @@ -267,6 +303,7 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { tr(td({ class: "tribe-info-value", colspan: "4" }, userLink(calendar.author))) ), tags, + renderMapEmbed(params && params.mapData, calendar.mapUrl), div({ class: "tribe-card-members" }, span({ class: "tribe-members-count calendar-participants-count" }, `${i18n.calendarParticipantsLabel || "Participants"}: ${calendar.participants.length}`) ), @@ -282,7 +319,9 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { const openInvite = invs.find(inv => typeof inv === "object" && inv && inv.public === true && inv.code) if (openInvite) return div({ class: "calendar-open-invite-block" }, div({ class: "tribe-open-invite" }, - span({ class: "tribe-open-invite-code" }, openInvite.code) + span({ class: "card-label" }, i18n.tribeInviteCodeText), + span({ class: "tribe-open-invite-code" }, openInvite.code), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/calendars/${encodeURIComponent(openInvite.code)}` }) ), form({ method: "POST", action: `/calendars/open-invite/remove/${encodeURIComponent(calendar.rootId)}` }, button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.tribeRemoveInvitation) @@ -298,7 +337,7 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { ) : null, !isAuthor && !isParticipant && calendar.status !== "OPEN" - ? a({ class: "tribe-action-btn", href: "/invites#invites-calendars" }, i18n.tribeEnterInvite) + ? a({ class: "tribe-action-btn", href: "/invites?filter=others" }, i18n.tribeEnterInvite) : null, !isAuthor && isParticipant ? form({ method: "POST", action: `/calendars/leave/${encodeURIComponent(calendar.rootId)}` }, @@ -306,15 +345,22 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { ) : null ), + (calendar.subscription && (isAuthor || isParticipant)) + ? renderSubscriptionBox({ + target: calendar.rootId || calendar.key, + scope: "calendars", + subscribed: calendar.subscription.subscribed === true, + count: calendar.subscription.count, + isOwner: isAuthor, + returnTo: shareUrl + }) + : null, isAuthor ? div({ class: "tribe-side-actions calendar-owner-actions" }, form({ method: "GET", action: "/calendars" }, input({ type: "hidden", name: "filter", value: "edit" }), input({ type: "hidden", name: "id", value: calendar.rootId }), button({ type: "submit", class: "tribe-action-btn" }, i18n.calendarUpdate || "Update") - ), - form({ method: "POST", action: `/calendars/delete/${encodeURIComponent(calendar.rootId)}` }, - button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.calendarDelete || "Delete") ) ) : null, @@ -337,7 +383,7 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { isParticipant ? [ span(i18n.calendarNoteLabel), br(), - textarea({ name: "text", rows: "3", placeholder: i18n.calendarNotePlaceholder || "Add a note..." }), + textarea({ maxlength: "5000", name: "text", rows: "3", placeholder: i18n.calendarNotePlaceholder || "Add a note..." }), br(), br() ] : null, @@ -348,11 +394,12 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { ) : null - const monthLabel = currentMonth.format("MMMM YYYY") + const monthNames = String(i18n.calendarMonthNames || "").split(",") + const monthLabel = `${monthNames[currentMonth.month()] || currentMonth.format("MMMM")} ${currentMonth.year()}` const calNav = div({ class: "calendar-nav" }, - a({ href: `${shareUrl}?month=${prevMonth}`, class: "filter-btn" }, i18n.calendarMonthPrev || "\u2190 Prev"), + a({ href: `${shareUrl}?month=${prevMonth}`, class: "filter-btn", title: i18n.calendarMonthPrev || "Prev" }, "\u2190"), span({ class: "tribe-info-label" }, monthLabel), - a({ href: `${shareUrl}?month=${nextMonth}`, class: "filter-btn" }, i18n.calendarMonthNext || "Next \u2192") + a({ href: `${shareUrl}?month=${nextMonth}`, class: "filter-btn", title: i18n.calendarMonthNext || "Next" }, "\u2192") ) const grid = renderMonthGrid(year, month, datesMap, calendar.rootId) @@ -410,13 +457,14 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { const calMain = div({ class: "tribe-main" }, div({ class: "card-header activity-card-header" }, - renderContentActions(calendar.rootId, null, { + renderContentActions(calendar.rootId, shareUrl, { author: calendar.author, favKind: 'calendars', isFavorite: calendar.isFavorite, spread: (params && params.spreads) || null, returnTo: shareUrl, - reportTitle: calendar.title + reportTitle: calendar.title, + deleteAction: isAuthor ? contentDeleteAction('calendar', calendar.rootId) : undefined }) ), @@ -429,14 +477,65 @@ exports.singleCalendarView = async (calendar, dates, notesByDate, params) => { return template( calendar.title || i18n.calendarsTitle || "Calendar", section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.calendarsTitle || "Calendars"), p(i18n.calendarsDescription || "Discover and manage calendars.") ), - renderModeButtons("all") + renderModeButtons("all", false, (params && params.modesAvail) || null) ), - section(div({ class: "tribe-details" }, calSide, calMain)) + section(div({ class: "tribe-details calendar-details" }, calSide, calMain)) ) } +exports.clearnetCalendarView = async (calendar, dates, notesByDate = {}) => { + const { escapeHtml: esc, renderRichText, renderKindTag, renderTagChips, renderClearnetPage } = require("./clearnet_view") + const name = calendar.title || i18n.calendarTitle + const fmt = (v) => esc(moment(v).format("YYYY/MM/DD HH:mm")) + const entries = [] + const byKey = new Map() + for (const d of Array.isArray(dates) ? dates : []) { + if (!byKey.has(d.key)) { + const entry = { key: d.key, label: d.label || "", when: [] } + byKey.set(d.key, entry) + entries.push(entry) + } + byKey.get(d.key).when.push(d.date) + } + const entriesHtml = entries.map(e => { + const notes = (notesByDate[e.key] || []).filter(n => n && String(n.text || "").trim()) + return `
+ ${e.label ? `` : ""} +
${e.when.map(w => `📅 ${fmt(w)}`).join("")}
+ ${notes.map(n => `

${renderRichText(n.text)}

`).join("")} +
` + }).join("") + const extraCss = ` +.cn-cal-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700;word-break:break-word} +.cn-cal-meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:16px} +.cn-cal-entry{border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:12px 0;background:var(--bg-elev)} +.cn-cal-entry-label{color:var(--fg);font-weight:600;font-size:16px;margin-bottom:8px;word-break:break-word} +.cn-cal-dates{display:flex;flex-wrap:wrap;gap:6px} +.cn-cal-note{color:var(--fg-soft);line-height:1.5;margin:10px 0 0 0;word-break:break-word} +.cn-cal-empty{color:var(--fg-dim)} +` + const body = ` +

${esc(name)}

+
+ ${renderKindTag("calendar")} + ${calendar.deadline ? `⏳ ${esc(i18n.calendarDeadlineLabel || "Deadline")}: ${fmt(calendar.deadline)}` : ""} +
+ ${renderTagChips(calendar.tags)} +
+ ${entriesHtml || `

${esc(i18n.calendarNoDates || "No dates added yet.")}

`} +` + return renderClearnetPage({ + title: `${name} | Oasis`, + ogTitle: name, + ogDescription: "", + extraCss, + body, + hubFeedId: calendar.author || null + }) +} + exports.renderIntervalBlock = renderIntervalBlock diff --git a/src/views/campaigns_view.js b/src/views/campaigns_view.js new file mode 100644 index 0000000..9b0cc88 --- /dev/null +++ b/src/views/campaigns_view.js @@ -0,0 +1,336 @@ +const { div, h2, h3, p, section, button, form, a, span, br, textarea, input, label, select, option, img, progress, table, tr, td, video: videoHyperaxe } = require("../server/node_modules/hyperaxe"); +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderContentActions, renderSubscriptionBox, renderModuleStats, renderOpinionsVoting, renderEngagement, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view"); +const { renderCommentsSection } = require("./comments_view"); +const { renderMapEmbed } = require("./maps_view"); +const { renderStyledText } = require("../backend/renderStyledText"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); +const sharedState = require("../configs/shared-state"); + +const userId = config.keys.id; +const BASE_FILTERS = ["ALL", "RECENT", "MINE", "SIGNED", "TOP"]; +const STATUSES = ["OPEN", "ACHIEVED", "CLOSED"]; +const CATEGORIES = ["ENVIRONMENT", "RIGHTS", "HEALTH", "EDUCATION", "INFRASTRUCTURE", "CULTURE", "ECONOMY", "OTHER"]; + +const cap = (s) => s.charAt(0) + s.slice(1).toLowerCase(); +const filterLabel = (f) => String(i18n[`campaignFilter${cap(f)}`] || f).toUpperCase(); +const statusLabel = (s) => String(i18n[`campaignStatus${cap(s)}`] || s).toUpperCase(); +const catLabel = (c) => String(i18n[`campaignCategory${cap(c)}`] || c).toUpperCase(); +const campaignHref = (cp) => `/campaigns/${encodeURIComponent(cp.id)}`; +const fmt = (d) => moment(d).format("YYYY/MM/DD HH:mm"); + +const statusChip = (cp) => renderStateChip(cp.closed ? "closed" : "mutuals", cp.closed ? "✗" : "✓", statusLabel(cp.closed ? "CLOSED" : "OPEN")); +const outcomeChip = (cp) => cp.closed ? renderStateChip(cp.achieved ? "mutuals" : "closed", cp.achieved ? "✓" : "✗", cp.achieved ? statusLabel("ACHIEVED") : String(i18n.campaignStatusUnachieved || "UNACHIEVED").toUpperCase()) : null; +const categoryChip = (cp) => renderStateChip("neutral", "", catLabel(cp.category)); +const clearnetEligible = (cp) => String(cp.status || "OPEN").toUpperCase() !== "CLOSED" && !cp.tribeId && !cp.encrypted; + +const renderTags = (tags) => (Array.isArray(tags) && tags.length) + ? div({ class: "card-tags" }, ...tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) + : null; + +const renderProgress = (cp) => + div({ class: "tribe-card-members campaign-progress" }, + span({ class: "tribe-members-count" }, `${i18n.campaignSignaturesLabel}: ${cp.signatureCount} / ${cp.goal} (${cp.progress}%)`), + progress({ class: "confirmations-progress", value: String(cp.signatureCount), max: String(cp.goal || 1) }) + ); + +const renderCover = (cp) => { + if (!cp.media || !cp.media.blobId) return null; + if (cp.media.kind === "video") return videoHyperaxe({ class: "campaign-card-cover", src: `/blob/${encodeURIComponent(cp.media.blobId)}`, controls: true, preload: "metadata" }); + return a({ href: campaignHref(cp) }, img({ loading: 'lazy', class: "campaign-card-cover", src: `/blob/${encodeURIComponent(cp.media.blobId)}`, alt: cp.title || "" })); +}; + +const campaignChipFor = (mode, census) => { + if (mode === "ALL") return true; + if (!Array.isArray(census)) return true; + if (mode === "MINE") return census.some(c => String(c.author) === String(userId)); + if (mode === "SIGNED") return census.some(c => Array.isArray(c.signers) && c.signers.includes(userId)); + if (mode === "RECENT") return census.length > 0; + if (mode === "TOP") return census.some(c => c.signatureCount > 0); + if (STATUSES.includes(mode)) return census.some(c => c.status === mode); + if (CATEGORIES.includes(mode)) return census.some(c => c.category === mode); + return census.length > 0; +}; +exports.campaignChipFor = campaignChipFor; + +const renderCampaignCard = (cp, params = {}) => + div({ class: `tribe-card campaign-card campaign-${cp.status.toLowerCase()}` }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(cp.id, campaignHref(cp), { author: cp.author, favKind: "campaigns", isFavorite: cp.isFavorite, reportTitle: cp.title, spread: (params.spreadMap && params.spreadMap.get(cp.id)) || null, deleteAction: cp.isOwner ? contentDeleteAction("campaign", cp.id) : undefined }) + ), + div({ class: "tribe-card-body" }, + renderCover(cp), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: campaignHref(cp) }, cp.title || "—"))), + div({ class: "card-chips-row" }, statusChip(cp), outcomeChip(cp), categoryChip(cp), cp.clearnet === true && clearnetEligible(cp) ? renderReachChip(true, i18n, clearnetItemHref("campaigns", cp.title, cp.id)) : null), + renderProgress(cp), + cp.deadline ? p({ class: "job-meta-line" }, `${i18n.campaignDeadlineLabel}: ${fmt(cp.deadline)}`) : null + ) + ); + +const chipButtons = (modes, filter, census, labelOf) => + modes.filter(m => m === filter || campaignChipFor(m, census)).map(m => button({ type: "submit", name: "filter", value: m, class: filter === m ? "filter-btn active" : "filter-btn" }, labelOf(m))); + +const renderFilters = (filter, q, census, emptyMod) => + section( + div({ class: "filters" }, + form({ method: "GET", action: "/campaigns", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q || "" }), + ...(emptyMod ? [] : [ + ...chipButtons(BASE_FILTERS, filter, census, filterLabel), + ...chipButtons(STATUSES, filter, census, statusLabel), + ...chipButtons(CATEGORIES, filter, census, catLabel) + ]), + button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.campaignCreate) + ) + ) + ); + +const renderForm = (cp) => + div({ class: "report-form" }, + h2(cp ? i18n.campaignUpdateSectionTitle : i18n.campaignCreateSectionTitle), + form({ method: "POST", action: cp ? `/campaigns/update/${encodeURIComponent(cp.id)}` : "/campaigns/create", enctype: "multipart/form-data" }, + label(i18n.campaignTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "120", required: true, placeholder: i18n.campaignTitlePlaceholder, value: cp ? cp.title : "" }), br(), + label(i18n.campaignTextLabel), br(), + textarea({ name: "text", rows: 10, maxlength: "8000", placeholder: i18n.campaignTextPlaceholder }, cp ? cp.text : ""), br(), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + label(i18n.campaignCategoryLabel), br(), + select({ name: "category" }, ...CATEGORIES.map(c => option({ value: c, ...((cp ? cp.category : "OTHER") === c ? { selected: true } : {}) }, catLabel(c)))), br(), br(), + label(i18n.campaignGoalLabel), br(), + input({ type: "number", name: "goal", min: "1", step: "1", required: true, placeholder: String((sharedState.getInhabitantCount && sharedState.getInhabitantCount()) || 100), value: cp ? String(cp.goal) : "" }), br(), br(), + label(i18n.campaignDeadlineLabel), br(), + input({ type: "datetime-local", name: "deadline", min: moment().format("YYYY-MM-DDTHH:mm"), value: cp && cp.deadline ? moment(cp.deadline).format("YYYY-MM-DDTHH:mm") : "" }), br(), br(), + label(i18n.mapLocationTitle || "Map Location"), br(), + input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: cp ? cp.mapUrl : "" }), br(), + label(i18n.campaignTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.campaignTagsPlaceholder, value: cp ? cp.tags.join(", ") : "" }), br(), br(), + ...(!cp || clearnetEligible(cp) ? [renderClearnetSelector(!!cp && cp.clearnet === true, i18n), br()] : []), + button({ type: "submit", class: "create-button" }, cp ? i18n.campaignUpdate : i18n.campaignCreate) + ) + ); + +exports.campaignsView = async (campaigns, filter = "ALL", params = {}) => { + const list = Array.isArray(campaigns) ? campaigns : []; + const q = String(params.q || "").trim(); + const census = Array.isArray(params.censusList) ? params.censusList : list; + const f = String(filter || "ALL").toUpperCase(); + const isForm = f === "CREATE" || f === "EDIT"; + const emptyMod = !isForm && moduleIsEmpty(census, "ALL", "ALL", q); + return template( + i18n.campaignsTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.campaignsTitle), p(i18n.campaignsDescription)), + renderFilters(isForm ? "ALL" : f, q, isForm ? [] : census, emptyMod) + ), + section( + isForm + ? renderForm(f === "EDIT" ? params.campaign : null) + : [ + emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length, STATUSES.map(s => ({ label: statusLabel(s), count: list.filter(c => c.status === s).length }))), + form({ method: "GET", action: "/campaigns", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: f }), + input({ type: "text", name: "q", value: q, placeholder: i18n.campaignSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ), + div({ class: "tribe-grid" }, list.length ? paged(list).map(c => renderCampaignCard(c, params)) : p(i18n.campaignNoItems)) + ] + ) + ); +}; + +const renderSignatures = (cp) => + div({ class: "card-section campaign-signatures" }, + h3(`${i18n.campaignSignaturesTitle} (${cp.signatureCount})`), + cp.signatureCount + ? div({ class: "campaign-signature-list" }, ...cp.signatures.slice().reverse().map(s => div({ class: "campaign-signature" }, + div({ class: "campaign-signature-head" }, userLink(s.author), span({ class: "date-link" }, fmt(s.createdAt))), + s.text ? p({ class: "campaign-signature-text" }, s.text) : null + ))) + : p(i18n.campaignNoSignatures) + ); + +const renderUpdate = (cp, u, href, editing) => + div({ class: "campaign-update", id: `update-${encodeURIComponent(u.id)}` }, + div({ class: "emergency-update-head campaign-update-head" }, + div({ class: "card-chips-row" }, + u.edited ? renderStateChip("neutral", "", i18n.campaignUpdateEdited) : null + ), + cp.isOwner + ? div({ class: "tribe-side-actions emergency-update-actions" }, + editing ? null : form({ method: "GET", action: `${href}#update-${encodeURIComponent(u.id)}` }, input({ type: "hidden", name: "editUpdate", value: u.id }), button({ type: "submit", class: "update-btn" }, i18n.campaignUpdate)), + form({ method: "POST", action: `/campaigns/updates/${encodeURIComponent(u.id)}/delete` }, button({ type: "submit", class: "delete-btn" }, i18n.campaignDelete)) + ) + : null + ), + editing + ? form({ method: "POST", action: `/campaigns/updates/${encodeURIComponent(u.id)}/edit`, enctype: "multipart/form-data", class: "campaign-update-form" }, + textarea({ name: "text", rows: 3, maxlength: "3000", required: true, autofocus: true }, u.text), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.campaignUpdate) + ) + : div({ class: "campaign-update-text" }, ...renderStyledText(u.text)), + p({ class: "card-footer" }, span({ class: "date-link" }, `${fmt(u.createdAt)}`), userLink(u.author)) + ); + +const renderUpdates = (cp, href, editUpdate) => + div({ class: "card-section campaign-updates" }, + h3(i18n.campaignUpdatesTitle), + cp.updates.length + ? div({ class: "campaign-updates-list" }, ...cp.updates.slice().reverse().map(u => renderUpdate(cp, u, href, editUpdate === u.id))) + : p(i18n.campaignNoUpdates), + cp.isOwner + ? form({ method: "POST", action: `/campaigns/updates/${encodeURIComponent(cp.id)}`, enctype: "multipart/form-data", class: "campaign-update-form" }, + textarea({ name: "text", rows: 3, maxlength: "3000", required: true, placeholder: i18n.campaignUpdatePlaceholder }), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.campaignAddUpdate) + ) + : null + ); + +exports.singleCampaignView = async (cp, params = {}) => { + const href = campaignHref(cp); + const canClearnet = clearnetEligible(cp); + const isClearnet = canClearnet && !!cp.clearnet; + const census = Array.isArray(params.censusList) ? params.censusList : []; + const sideActions = []; + const ownerActions = cp.isOwner + ? [ + form({ method: "GET", action: "/campaigns" }, input({ type: "hidden", name: "filter", value: "edit" }), input({ type: "hidden", name: "id", value: cp.id }), button({ type: "submit", class: "update-btn" }, i18n.campaignUpdate)) + ].filter(Boolean) + : []; + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(cp.id, href, { author: cp.author, favKind: "campaigns", isFavorite: cp.isFavorite, reportTitle: cp.title, spread: params.spread || null, returnTo: href, deleteAction: cp.isOwner ? contentDeleteAction("campaign", cp.id) : undefined }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, cp.title)), + div({ class: "card-chips-row" }, + statusChip(cp), outcomeChip(cp), categoryChip(cp), cp.proposalId ? renderStateChip("neutral", "⚖", String(i18n.campaignElevated).toUpperCase()) : null, + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref("campaigns", cp.title, cp.id) : null), + cp.isOwner && canClearnet ? renderClearnetSwitch("campaigns", cp.rootId || cp.id, isClearnet) : null + ), + table({ class: "tribe-info-table jobs-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), + td({ class: "tribe-info-value", colspan: "3" }, fmt(cp.createdAt)) + ), + cp.deadline + ? tr( + td({ class: "tribe-info-label" }, i18n.campaignDeadlineLabel), + td({ class: "tribe-info-value", colspan: "3" }, fmt(cp.deadline)) + ) + : null, + tr( + td({ class: "tribe-info-value", colspan: "4" }, userLink(cp.author)) + ) + ), + div({ class: "campaign-qr" }, + img({ src: `${href}/qr.png`, alt: i18n.campaignQrAlt, class: "campaign-qr-img" }), + p({ class: "campaign-qr-caption" }, i18n.campaignQrCaption) + ), + renderProgress(cp), + renderMapEmbed(params.mapData, cp.mapUrl), + sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, + div({ class: "campaign-signed" }, + cp.signed ? renderStateChip("mutuals", "✓", i18n.campaignSigned) : renderStateChip("closed", "✗", i18n.campaignNotSigned) + ), + cp.canElevate + ? div({ class: "tribe-side-actions campaign-elevate" }, form({ method: "POST", action: `/campaigns/elevate/${encodeURIComponent(cp.id)}` }, button({ type: "submit", class: "tribe-action-btn" }, String(i18n.campaignElevate).toUpperCase()))) + : null, + cp.isOwner && !cp.expired + ? div({ class: "tribe-side-actions housing-status-row" }, + span({ class: "card-label" }, `${i18n.statusLabel || "Status"}: `), + statusChip(cp), + form({ method: "POST", action: `/campaigns/status/${encodeURIComponent(cp.id)}`, class: "inline-form" }, + button({ class: "tribe-action-btn", type: "submit", name: "status", value: cp.status === "CLOSED" ? "OPEN" : "CLOSED" }, + String(cp.status === "CLOSED" ? i18n.campaignReopen : i18n.campaignClose).toUpperCase()) + ) + ) + : null, + div({ class: "doc-export-actions" }, + form({ method: "GET", action: `${href}/pdf` }, button({ type: "submit", class: "filter-btn" }, i18n.campaignGenerateReport)), + form({ method: "POST", action: `${href}/share` }, button({ type: "submit", class: "filter-btn" }, i18n.sharePm)) + ), + renderTags(cp.tags), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null, + params.subscription + ? renderSubscriptionBox({ target: cp.id, scope: "campaigns", subscribed: params.subscription.subscribed, count: params.subscription.count, isOwner: cp.isOwner, returnTo: href, canWrite: cp.isOwner }) + : null + ); + const main = div({ class: "tribe-main" }, + cp.text ? div({ class: "campaign-body" }, ...renderStyledText(cp.text)) : null, + cp.canSign + ? div({ class: "card-section campaign-sign" }, + h3(i18n.campaignSignTitle), + form({ method: "POST", action: `/campaigns/sign/${encodeURIComponent(cp.id)}`, class: "campaign-sign-form" }, + textarea({ name: "text", rows: 3, maxlength: "500", placeholder: i18n.campaignSignPlaceholder }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.campaignSign) + ) + ) + : null, + renderUpdates(cp, href, String(params.editUpdate || "")), + renderSignatures(cp), + renderEngagement(cp.id, + renderOpinionsVoting("/campaigns/opinions", cp.id, cp.opinions, href, cp.opinions_inhabitants), + renderCommentsSection({ action: `/campaigns/${encodeURIComponent(cp.id)}/comments`, comments: params.comments || [], returnTo: href }) + ) + ); + return template( + cp.title, + section(div({ class: "tags-header module-header-line" }, h2(i18n.campaignsTitle), p(i18n.campaignsDescription)), renderFilters("ALL", "", census, census.length === 0)), + section(div({ class: "tribe-details" }, side, main)) + ); +}; + +exports.clearnetCampaignView = async (cp, params = {}) => { + const { escapeHtml: esc, renderRichText, renderKindTag, renderTagChips, blobUrl: cnBlob, renderClearnetPage } = require("./clearnet_view"); + const title = esc(cp.title || i18n.campaignLabel); + const desc = renderRichText(cp.text || ""); + const point = params.mapPoint || null; + const cover = cp.media && cp.media.kind === "image" ? cnBlob(cp.media.blobId) : null; + const goal = Number(cp.goal) || 0; + const signed = Number(cp.signatureCount) || 0; + const pct = Math.max(0, Math.min(100, Number(cp.progress) || 0)); + const updates = (Array.isArray(cp.updates) ? cp.updates : []).filter(u => String(u.author) === String(cp.author)); + const extraCss = ` +.cn-campaign-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700} +.cn-campaign-meta{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px} +.cn-campaign-meta-item{background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;padding:8px 14px;font-size:14px;color:var(--fg-soft);display:inline-flex;align-items:center;gap:6px} +.cn-campaign-progress{margin:0 0 20px 0} +.cn-campaign-progress-label{color:var(--fg);font-weight:700;font-size:15px;margin-bottom:8px} +.cn-campaign-progress progress{width:100%;height:14px;accent-color:var(--fg)} +.cn-campaign-desc{color:var(--fg-soft);line-height:1.6;font-size:15px;margin:0 0 20px 0;word-break:break-word} +.cn-campaign-update{border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin:12px 0;background:var(--bg-elev)} +.cn-campaign-update-date{color:var(--fg-dim);font-size:12px;margin-bottom:6px} +.cn-campaign-update-text{color:var(--fg-soft);line-height:1.5;word-break:break-word} +`; + const body = ` +

${title}

+
+ ${renderKindTag("campaign")} + ${esc(statusLabel(cp.achieved ? "ACHIEVED" : "OPEN"))} + ${esc(catLabel(String(cp.category || "OTHER")))} + ${cp.createdAt ? `📅 ${esc(fmt(cp.createdAt))}` : ""} + ${cp.deadline ? `⏳ ${esc(i18n.campaignDeadlineLabel)}: ${esc(fmt(cp.deadline))}` : ""} + ${point ? `📍 ${point.title ? `${esc(point.title)} · ` : ""}${point.lat}, ${point.lng}` : ""} +
+ ${goal > 0 ? `
${esc(i18n.campaignSignaturesLabel)}: ${signed} / ${goal} (${pct}%)
` : ""} +
+ ${desc ? `
${desc}
` : ""} + ${updates.length ? `

${esc(i18n.campaignUpdatesTitle)}

${updates.slice().reverse().map(u => `
${esc(fmt(u.updatedAt || u.createdAt))}
${renderRichText(u.text || "")}
`).join("")}` : ""} + ${renderTagChips(cp.tags)} +`; + return renderClearnetPage({ + title: `${cp.title || i18n.campaignLabel} | Oasis`, + ogTitle: cp.title || i18n.campaignLabel, + ogDescription: String(cp.text || "").replace(/!?\[[^\]]*\]\(\s*&[^)\s]+\s*\)/g, "").trim(), + ogImage: cover, + extraCss, + body, + hubFeedId: cp.author || null + }); +}; diff --git a/src/views/chats_view.js b/src/views/chats_view.js index c122134..afafbda 100644 --- a/src/views/chats_view.js +++ b/src/views/chats_view.js @@ -1,10 +1,10 @@ -const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, img, table, tr, td, ul, li, details, summary } = require("../server/node_modules/hyperaxe") -const { template, i18n, userLink, userLinkLabel, renderStateChip, renderLifespanChip, renderSpreadButton, renderContentActions } = require("./main_views") +const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, img, table, tr, td, ul, li, details, summary, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe") +const { template, i18n, userLink, userLinkLabel, renderStateChip, renderLifespanChip, renderSpreadButton, renderContentActions, renderInviteQrCard, renderSubscriptionBox, renderModuleStatsBy, moduleIsEmpty, renderTorrentDownload, torrentDownloadHref, contentDeleteAction, paged } = require("./main_views") const { renderEncryptedChip } = require("./clearnet_view") const { renderResults, renderBallot, outcomeOf } = require("./polls_view") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const { renderZoomableImage } = require("./gallery_view") const userId = config.keys.id @@ -30,9 +30,9 @@ const renderMediaBlob = (value, fallbackSrc = null, attrs = {}) => { if (!value) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null const s = String(value).trim() if (!s) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null - if (s.startsWith("&")) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith("&")) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(s)}`, ...attrs }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, ...attrs }) + if (mImg) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(mImg[1])}`, ...attrs }) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null } @@ -44,14 +44,16 @@ const buildReturnTo = (filter, params = {}) => { return `/chats?${parts.join("&")}` } -const renderModeButtons = (currentFilter) => +const renderModeButtons = (currentFilter, emptyMod = false, modesAvail = null) => div({ class: "tribe-mode-buttons" }, - ["all", "mine", "recent", "favorites", "open", "closed"].map(f => + ...(emptyMod ? [] : [ + ["all", "recent", "mine", "favorites", "open", "closed"].filter(f => f === "all" || f === currentFilter || (modesAvail && modesAvail[f] !== false)).map(f => form({ method: "GET", action: "/chats" }, input({ type: "hidden", name: "filter", value: f }), button({ type: "submit", class: currentFilter === f ? "filter-btn active" : "filter-btn" }, i18n[`chatFilter${f.charAt(0).toUpperCase() + f.slice(1)}`] || f.toUpperCase()) ) ), + ]), form({ method: "GET", action: "/chats" }, input({ type: "hidden", name: "filter", value: "create" }), button({ type: "submit", class: "create-button" }, i18n.chatCreate) @@ -68,11 +70,14 @@ const renderChatStatusChip = (status) => { return renderStateChip(variant, icon, label) } -const renderChatCard = (chat, filter, params = {}) => { +const renderChatCard = exports.renderChatCard = (chat, filter, params = {}) => { const chips = [ renderChatStatusChip(chat.status), renderEncryptedChip(i18n), - renderLifespanChip(chat.lifetime, i18n) + renderLifespanChip(chat.lifetime, i18n), + chat.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (chat.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null) ].filter(Boolean) const href = `/chats/${encodeURIComponent(chat.key)}` @@ -84,18 +89,19 @@ const renderChatCard = (chat, filter, params = {}) => { favKind: 'chats', isFavorite: chat.isFavorite, returnTo: buildReturnTo(filter, params), - reportTitle: chat.title + reportTitle: chat.title, + deleteAction: String(chat.author) === String(userId) ? contentDeleteAction('chat', chat.key) : undefined }) ), div({ class: "tribe-card-image-wrapper" }, - a({ href: `/chats/${encodeURIComponent(chat.key)}` }, + a({ href: `/chats/${encodeURIComponent(chat.key)}#chat-latest` }, renderMediaBlob(chat.image, "/assets/images/default-avatar.png", { class: "tribe-card-hero-image" }) ) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, - a({ href: `/chats/${encodeURIComponent(chat.key)}` }, chat.title || i18n.chatUntitled) + a({ href: `/chats/${encodeURIComponent(chat.key)}#chat-latest` }, chat.title || i18n.chatUntitled) ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, @@ -119,7 +125,7 @@ const renderChatForm = (filter, chat = {}, params = {}) => { span(i18n.title || "Title"), br(), input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.chatTitlePlaceholder, value: chat.title || "" }), br(), br(), span(i18n.chatDescription), br(), - textarea({ name: "description", rows: 4, placeholder: i18n.chatDescriptionPlaceholder }, chat.description || ""), br(), br(), + textarea({ name: "description", rows: 4, maxlength: "1000", placeholder: i18n.chatDescriptionPlaceholder }, chat.description || ""), br(), br(), span(i18n.uploadMedia), br(), input({ type: "file", name: "image", accept: "image/*" }), br(), br(), span(i18n.chatCategory), br(), @@ -128,7 +134,7 @@ const renderChatForm = (filter, chat = {}, params = {}) => { option({ value: cat, ...((chat.category || "GENERAL") === cat ? { selected: true } : {}) }, catLabel(cat)) ) ), br(), br(), - span(i18n.chatStatusLabel || "Status"), br(), + span(i18n.chatTypeLabel), br(), select({ name: "status" }, option({ value: "OPEN", ...((!chat.status || chat.status === "OPEN") ? { selected: true } : {}) }, i18n.chatStatusOpen), option({ value: "INVITE-ONLY", ...(chat.status === "INVITE-ONLY" ? { selected: true } : {}) }, i18n.chatStatusInviteOnly) @@ -140,17 +146,7 @@ const renderChatForm = (filter, chat = {}, params = {}) => { ) } -const renderMessageText = (text) => { - if (!text) return span({ class: "chat-message-text" }, "") - const lines = String(text).split("\n") - const nodes = [] - lines.forEach((line, idx) => { - const rendered = renderUrl(line) - nodes.push(...rendered) - if (idx < lines.length - 1) nodes.push(br()) - }) - return span({ class: "chat-message-text" }, ...nodes) -} +const renderMessageText = (text) => span({ class: "chat-message-text" }, ...renderStyledText(String(text || ""))) const chatActivityTs = (c) => Math.max( Number(c.lastMsgAt || 0), @@ -167,9 +163,18 @@ const renderChatTopics = (chats, activeKey) => ...safeArr(chats).slice().sort((a, b) => chatActivityTs(b) - chatActivityTs(a)).map(c => { const key = c.rootId || c.key const active = activeKey && String(key) === String(activeKey) - return a({ href: `/chats/${encodeURIComponent(key)}`, class: active ? "chat-topic chat-topic-active" : "chat-topic" }, + return a({ href: `/chats/${encodeURIComponent(key)}#chat-latest`, class: active ? "chat-topic chat-topic-active" : "chat-topic" }, + renderMediaBlob(c.image, "/assets/images/default-avatar.png", { class: "chat-topic-thumb" }), span({ class: "chat-topic-title" }, c.title || i18n.chatUntitled), - span({ class: "chat-topic-meta" }, `${i18n.chatParticipants}: ${safeArr(c.members).length}`) + span({ class: "chat-topic-meta" }, `\u2689 ${safeArr(c.members).length}`), + c.lastMsg && (c.lastMsg.text || c.lastMsg.author) ? (() => { + const who = userLinkLabel(c.lastMsg.author) || '' + const shortWho = who.length > 20 ? who.slice(0, 10) + '\u2026' : who + return span({ class: "chat-topic-last" }, + span({ class: "chat-topic-last-author" }, shortWho + ': '), + String(c.lastMsg.text || '').replace(/\s+/g, ' ').slice(0, 80) + ) + })() : null ) }) ) @@ -197,15 +202,21 @@ const renderChatPoll = (poll, chat) => { : [ p({ class: "chat-poll-question" }, poll.question), showResults ? renderResults(poll) : null, - div({ class: "chat-poll-actions" }, - renderBallot(poll, `/chats/${encodeURIComponent(chat.key)}`, "/polls"), - String(poll.author) === String(userId) && poll.status === "OPEN" - ? form({ method: "POST", action: `/polls/close/${encodeURIComponent(poll.id)}` }, - input({ type: "hidden", name: "returnTo", value: `/chats/${encodeURIComponent(chat.key)}` }), - button({ type: "submit", class: "filter-btn" }, i18n.pollCloseButton) - ) - : null - ), + (() => { + const canClose = String(poll.author) === String(userId) && poll.status === "OPEN"; + const closeFormId = `poll-close-${String(poll.id).replace(/[^a-zA-Z0-9]/g, "")}`; + const ballot = renderBallot(poll, `/chats/${encodeURIComponent(chat.key)}`, "/polls", + canClose ? [button({ type: "submit", form: closeFormId, class: "danger-btn" }, i18n.pollCloseButton)] : null); + return div({ class: "chat-poll-actions" }, + ballot, + canClose + ? form({ id: closeFormId, method: "POST", action: `/polls/close/${encodeURIComponent(poll.id)}`, class: "chat-poll-close-form" }, + input({ type: "hidden", name: "returnTo", value: `/chats/${encodeURIComponent(chat.key)}` }), + ballot ? null : button({ type: "submit", class: "danger-btn" }, i18n.pollCloseButton) + ) + : null + ); + })(), div({ class: "chat-poll-meta" }, span({ class: "card-label" }, `${i18n.pollVoters}: `), span({ class: "card-value" }, String(poll.totalVoters)), @@ -218,32 +229,85 @@ const renderChatPoll = (poll, chat) => { ) } -const renderMessage = (msg, chatAuthor, ctx = {}) => { - const isAuthor = String(msg.author) === String(chatAuthor) +const REACTIONS = [ + ["up", "👍"], + ["heart", "❤️"], + ["laugh", "😂"], + ["down", "👎"] +] + +const anchorIdOf = (key) => "msg-" + String(key || "").replace(/[^a-zA-Z0-9]/g, "") + +const renderMessage = (msg, chat, opts = {}) => { + const isAuthor = String(msg.author) === String(chat.author) const isSelf = String(msg.author) === String(userId) - // cita del mensaje respondido: se resuelve con el indice que arma la vista - const quoted = (msg.replyTo && ctx.msgById) ? ctx.msgById.get(String(msg.replyTo)) : null - const replyHref = (ctx.chatKey && msg.key) - ? `/chats/${encodeURIComponent(ctx.chatKey)}?replyTo=${encodeURIComponent(msg.key)}#chat-compose` - : null const imageSrc = blobSrcOf(msg.image) + const mime = String(msg.mimeType || "") const imageNode = imageSrc - ? renderZoomableImage(imageSrc, { imgClass: "chat-message-image" }) + ? (mime.startsWith("video/") + ? videoHyperaxe({ controls: true, class: "post-video chat-message-media", src: imageSrc, type: mime, preload: "metadata" }) + : mime.startsWith("audio/") + ? audioHyperaxe({ controls: true, class: "post-audio chat-message-media", src: imageSrc, type: mime, preload: "metadata" }) + : mime === "application/pdf" + ? a({ href: imageSrc, target: "_blank", rel: "noopener", class: "filter-btn chat-message-file" }, "📄 PDF") + : mime.includes("bittorrent") || mime === "application/x-torrent" + ? renderTorrentDownload(imageSrc, { class: "filter-btn chat-message-file" }) + : renderZoomableImage(imageSrc, { imgClass: "chat-message-image" })) : (msg.image ? renderMediaBlob(msg.image, null, { class: "chat-message-image" }) : null) - return div({ class: isSelf ? "chat-bubble-row chat-bubble-row-self" : "chat-bubble-row" }, - div({ class: isSelf ? "chat-message chat-message-self" : isAuthor ? "chat-message chat-message-author" : "chat-message" }, + const counts = (msg.reactions && msg.reactions.counts) || {} + const mine = (msg.reactions && msg.reactions.mine) || {} + const reactionNodes = REACTIONS.map(([code, icon]) => { + const n = Number(counts[code] || 0) + const label = n > 0 ? `${icon} ${n}` : icon + if (!opts.canWrite) return n > 0 ? span({ class: "chat-react-btn" }, label) : null + return form({ method: "POST", action: `/chats/${encodeURIComponent(chat.key)}/react`, class: "chat-react-form" }, + input({ type: "hidden", name: "target", value: msg.key }), + input({ type: "hidden", name: "emoji", value: code }), + button({ type: "submit", class: mine[code] ? "chat-react-btn chat-react-active" : "chat-react-btn" }, label) + ) + }).filter(Boolean) + + const replyLink = opts.canWrite + ? a({ href: `/chats/${encodeURIComponent(chat.key)}?reply=${encodeURIComponent(msg.key)}#chat-message-form`, class: "chat-react-btn chat-reply-btn", title: i18n.chatReply }, "↩") + : null + + const pinBtn = opts.isChatAuthor + ? form({ method: "POST", action: `/chats/${encodeURIComponent(chat.key)}/pin`, class: "chat-react-form chat-pin-toggle" }, + input({ type: "hidden", name: "target", value: msg.key }), + button({ type: "submit", class: msg.pinned ? "chat-react-btn chat-react-active" : "chat-react-btn", title: msg.pinned ? i18n.chatUnpin : i18n.chatPin }, "📌") + ) + : null + + const quoteText = String((msg.reply && msg.reply.text) || "").trim() + const quote = msg.reply + ? a({ href: `#${anchorIdOf(msg.replyTo)}`, class: "chat-reply-quote" }, + span({ class: "chat-reply-arrow" }, "↩ "), + span({ class: "chat-reply-text" }, quoteText ? `${quoteText}${quoteText.length >= 120 ? "…" : ""}` : userLinkLabel(msg.reply.author)) + ) + : null + + const timeNode = span({ class: "chat-bubble-time" }, moment(msg.createdAt).format("HH:mm")) + const actionsRow = (reactionNodes.length || replyLink) + ? div({ class: "chat-msg-actions" }, ...reactionNodes, replyLink, + span({ class: replyLink ? "chat-bubble-time chat-time-inline" : "chat-bubble-time chat-time-inline chat-time-push" }, moment(msg.createdAt).format("HH:mm"))) + : null + + const bubbleClass = [ + "chat-message", + isSelf ? "chat-message-self" : (isAuthor ? "chat-message-author" : ""), + msg.pinned ? "chat-message-pinned" : "", + pinBtn ? "chat-has-pin-toggle" : "" + ].filter(Boolean).join(" ") + + return div({ id: anchorIdOf(msg.key), class: isSelf ? "chat-bubble-row chat-bubble-row-self" : "chat-bubble-row" }, + div({ class: bubbleClass }, + pinBtn, isSelf ? null : renderSenderLink(msg.author, isAuthor), - quoted - ? div({ class: "chat-quote" }, - span({ class: "chat-quote-author" }, String(quoted.author || "").slice(0, 10)), - span({ class: "chat-quote-text" }, String(quoted.text || "").slice(0, 120)) - ) - : null, + quote, renderMessageText(msg.text || ""), imageNode ? div({ class: "chat-message-image-wrap" }, imageNode) : null, - span({ class: "chat-bubble-time" }, moment(msg.createdAt).format("HH:mm")), - replyHref ? a({ class: "chat-reply-btn", href: replyHref, title: i18n.chatReply || "Reply" }, "↩") : null + actionsRow || timeNode ) ) } @@ -263,6 +327,7 @@ exports.renderChatInvitePage = (code) => { exports.chatsView = async (chats, filter, chatToEdit = null, params = {}) => { const q = safeText(params.q || "") const list = safeArr(chats) + const emptyMod = moduleIsEmpty(list, filter || "all", "all", q) const isForm = filter === "create" || filter === "edit" @@ -271,7 +336,7 @@ exports.chatsView = async (chats, filter, chatToEdit = null, params = {}) => { if (params.workspace && !isForm) { return template( i18n.chatsTitle, - section({ class: "chat-workspace-section" }, + section({ class: "chat-workspace-section chat-workspace-list" }, div({ class: "chat-workspace" }, renderChatTopics(list, null), div({ class: "chat-workspace-main chat-workspace-empty" }, @@ -285,15 +350,16 @@ exports.chatsView = async (chats, filter, chatToEdit = null, params = {}) => { return template( i18n.chatsTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(headerText), p(i18n.modulesChatsDescription) ) ), - section(renderModeButtons(filter)), - !isForm + section(renderModeButtons(filter, emptyMod, (params && params.modesAvail) || null)), + !isForm && !emptyMod ? section( - div({ class: "filters" }, + div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(list, c => String(c.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.chatStatusOpen }, { value: 'INVITE-ONLY', label: i18n.chatStatusInviteOnly }, { value: 'CLOSED', label: i18n.chatStatusClosed }]), form({ method: "GET", action: "/chats", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", placeholder: i18n.chatSearchPlaceholder, value: q, class: "filter-box__input" }), @@ -309,7 +375,7 @@ exports.chatsView = async (chats, filter, chatToEdit = null, params = {}) => { ? renderChatForm(filter, filter === "edit" ? (chatToEdit || {}) : {}, params) : div({ class: "tribe-grid" }, list.length - ? list.map(chat => renderChatCard(chat, filter, { q, spread: params.spreadMap && params.spreadMap.get(chat.key) })) + ? paged(list).map(chat => renderChatCard(chat, filter, { q, spread: params.spreadMap && params.spreadMap.get(chat.key) })) : p(i18n.chatNoItems) ) ) @@ -326,23 +392,40 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { const statusLabel = chat.status === "CLOSED" ? i18n.chatStatusClosed : chat.status === "INVITE-ONLY" ? i18n.chatStatusInviteOnly : i18n.chatStatusOpen + const chatShares = isAuthor || isMember const detailChips = [ renderChatStatusChip(chat.status), renderEncryptedChip(i18n), - renderLifespanChip(chat.lifetime, i18n) + renderLifespanChip(chat.lifetime, i18n), + chat.subscription && chatShares + ? ((isAuthor || chat.subscription.subscribed === true) + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : renderStateChip("closed", "✉", i18n.subscriptionOff)) + : null ].filter(Boolean) const chatSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(chat.key, null, { spread: params.spreads || null, author: chat.author, favKind: 'chats', isFavorite: chat.isFavorite, returnTo: params.returnTo, reportTitle: chat.title }) + renderContentActions(chat.key, `/chats/${encodeURIComponent(chat.key)}`, { spread: params.spreads || null, author: chat.author, favKind: 'chats', isFavorite: chat.isFavorite, returnTo: params.returnTo, reportTitle: chat.title, deleteAction: isAuthor ? contentDeleteAction('chat', chat.key) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, chat.title || i18n.chatUntitled) ), + chat.description ? p({ class: "tribe-card-description" }, chat.description) : null, detailChips.length ? div({ class: "card-chips-row" }, ...detailChips) : null, renderMediaBlob(chat.image, "/assets/images/default-avatar.png", { class: "tribe-detail-image" }), div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.chatParticipants}: ${safeArr(chat.members).length}`) ), + chat.subscription && chatShares + ? renderSubscriptionBox({ + target: chat.rootId || chat.key, + scope: "chats", + subscribed: chat.subscription.subscribed === true, + count: chat.subscription.count, + isOwner: isAuthor, + returnTo + }) + : null, table({ class: "tribe-info-table" }, tr( td({ class: "tribe-info-label" }, i18n.chatCreatedAt), @@ -373,7 +456,8 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { if (openInvite) return [ div({ class: "tribe-open-invite" }, span({ class: "card-label" }, i18n.tribeInviteCodeText), - span({ class: "tribe-open-invite-code" }, openInvite.code) + span({ class: "tribe-open-invite-code" }, openInvite.code), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/chats/${encodeURIComponent(openInvite.code)}` }) ), form({ method: "POST", action: `/chats/open-invite/remove` }, input({ type: "hidden", name: "chatId", value: chat.key }), @@ -409,14 +493,11 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { !isAuthor ? null : div({ class: "tribe-side-actions owner-actions" }, form({ method: "GET", action: `/chats/edit/${encodeURIComponent(chat.key)}` }, button({ type: "submit", class: "tribe-action-btn" }, i18n.chatUpdate) - ), - form({ method: "POST", action: `/chats/delete/${encodeURIComponent(chat.key)}` }, - button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.chatDelete) ) ), !isMember && chat.status === "INVITE-ONLY" ? div({ class: "chat-join-section" }, - a({ class: "tribe-action-btn", href: "/invites#invites-chats" }, i18n.tribeEnterInvite) + a({ class: "tribe-action-btn", href: "/invites?filter=others" }, i18n.tribeEnterInvite) ) : null, !isRestrictedInviteOnly && safeArr(chat.tags).length @@ -427,19 +508,21 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { ) const msgList = safeArr(messages) - // indice por clave para resolver las citas; se arma antes de mezclar con encuestas - const msgById = new Map(msgList.filter(m => m && m.key).map(m => [String(m.key), m])) - const replyingTo = params.replyTo ? msgById.get(String(params.replyTo)) : null const canWrite = (isMember || chat.status === "OPEN") && chat.status !== "CLOSED" + const pinnedMsgs = msgList.filter(m => m.pinned).sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) + const replyMsg = params.reply ? msgList.find(m => String(m.key) === String(params.reply)) : null + const chatMain = isRestrictedInviteOnly ? div({ class: "tribe-main chat-full-width" }, p({ class: "access-denied-msg" }, i18n.chatAccessDenied)) : div({ class: "tribe-main chat-full-width" }, - msgList.length - ? div({ class: "chat-jump-row" }, - a({ href: "#chat-latest", class: "filter-btn chat-jump-latest" }, i18n.chatJumpLatest) - ) - : null, + div({ class: "chat-jump-row" }, + a({ href: "/chats", class: "filter-btn chat-back-btn" }, "\u00ab " + (i18n.chatsTitle || "Chats")), + msgList.length && pinnedMsgs.length + ? a({ href: `#${anchorIdOf(pinnedMsgs[0].key)}`, class: "filter-btn chat-pinned-link" }, `${i18n.chatPinned} (${pinnedMsgs.length})`) + : null, + msgList.length ? a({ href: "#chat-latest", class: "filter-btn chat-jump-latest" }, i18n.chatJumpLatest) : null + ), div({ class: "chat-messages-list" }, (() => { const visible = msgList @@ -461,27 +544,27 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { const last = i === stream.length - 1 const node = entry.kind === 'poll' ? renderChatPoll(entry.poll, chat) - : renderMessage(entry.msg, chat.author, { msgById, chatKey: chat.key }) + : renderMessage(entry.msg, chat, { canWrite, isChatAuthor: isAuthor }) nodes.push(last ? div({ id: "chat-latest", class: "chat-latest-anchor" }, node) : node) }) return nodes })() ), canWrite - ? div({ class: "chat-message-form" }, + ? div({ id: "chat-message-form", class: "chat-message-form" }, + replyMsg + ? div({ class: "chat-replying-banner" }, + span({ class: "chat-reply-arrow" }, "↩ "), + span({ class: "chat-reply-text" }, String(replyMsg.text || "").trim() ? ` ${String(replyMsg.text).trim().slice(0, 120)}` : userLinkLabel(replyMsg.author)), + a({ href: `/chats/${encodeURIComponent(chat.key)}#chat-message-form`, class: "chat-reply-cancel", title: i18n.chatReplyCancel || "✕" }, "✕") + ) + : null, form({ method: "POST", action: `/chats/${encodeURIComponent(chat.key)}/message`, enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: `/chats/${encodeURIComponent(chat.key)}#chat-latest` }), - replyingTo - ? div({ class: "chat-replying-to", id: "chat-compose" }, - span({ class: "chat-quote-author" }, String(replyingTo.author || "").slice(0, 10)), - span({ class: "chat-quote-text" }, String(replyingTo.text || "").slice(0, 120)), - a({ class: "chat-reply-cancel", href: `/chats/${encodeURIComponent(chat.key)}#chat-latest` }, "✕") - ) - : null, - replyingTo ? input({ type: "hidden", name: "replyTo", value: String(params.replyTo) }) : null, - textarea({ name: "text", rows: 3, placeholder: i18n.chatMessagePlaceholder }), br(), + replyMsg ? input({ type: "hidden", name: "replyTo", value: replyMsg.key }) : null, + textarea({ name: "text", rows: 3, maxlength: "3000", placeholder: i18n.chatMessagePlaceholder }), br(), span(i18n.uploadMedia), br(), - input({ type: "file", name: "image", accept: "image/*,video/*" }), br(), br(), + input({ type: "file", name: "image", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), button({ type: "submit", class: "filter-btn" }, i18n.chatSendMessage) ) ) @@ -494,7 +577,7 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { span(i18n.pollQuestion), br(), input({ type: "text", name: "question", required: true, maxlength: "300", placeholder: i18n.pollQuestionPlaceholder }), br(), br(), span(i18n.pollOptions), br(), - textarea({ name: "options", rows: 4, required: true, placeholder: i18n.pollOptionsPlaceholder }), br(), br(), + textarea({ maxlength: "1000", name: "options", rows: 4, required: true, placeholder: i18n.pollOptionsPlaceholder }), br(), br(), div({ class: "poll-switch" }, input({ type: "hidden", name: "anonymous", value: "0" }), label(input({ type: "checkbox", name: "anonymous", value: "1" }), " ", i18n.pollAnonymousLabel) @@ -514,7 +597,6 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { chat.title || i18n.chatUntitled, section({ class: "chat-workspace-section" }, div({ class: "chat-workspace" }, - renderChatTopics(safeArr(params.allChats), chat.rootId || chat.key), div({ class: "chat-workspace-main" }, div({ class: "tribe-details chat-workspace-details" }, chatSide, @@ -529,11 +611,11 @@ exports.singleChatView = async (chat, filter, messages = [], params = {}) => { return template( chat.title || i18n.chatUntitled, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.chatsTitle), p(i18n.modulesChatsDescription) ), - renderModeButtons(filter || "all") + renderModeButtons(filter || "all", false, (params && params.modesAvail) || null) ), section( div({ class: "tribe-details" }, diff --git a/src/views/cipher_view.js b/src/views/cipher_view.js index 6e3ecb0..b79b48d 100644 --- a/src/views/cipher_view.js +++ b/src/views/cipher_view.js @@ -9,7 +9,7 @@ function generateRandomPassword(length = 32) { const cipherView = async (encryptedText = "", decryptedText = "", iv = "", password = "") => { const randomPassword = generateRandomPassword(); - const header = div({ class: "tags-header" }, + const header = div({ class: "tags-header module-header-line" }, h2(i18n.cipherTitle), p(i18n.cipherDescription) ); @@ -83,9 +83,7 @@ const cipherView = async (encryptedText = "", decryptedText = "", iv = "", passw i18n.cipherTitle, section( header, - div({ id: "randomPassword" }, - h2({ class: "generated-password" }, randomPassword) - ), + p({ id: "randomPassword" }, h2({ class: "generated-password" }, randomPassword)), div({ class: "div-center" }, encryptForm, br(), diff --git a/src/views/clearnet_view.js b/src/views/clearnet_view.js index 2ba743a..a46c7e3 100644 --- a/src/views/clearnet_view.js +++ b/src/views/clearnet_view.js @@ -1,4 +1,31 @@ -const { a, br, div, input, span, strong } = require("../server/node_modules/hyperaxe"); +const { a, br, div, input, option, select, span, strong, form, button, label } = require("../server/node_modules/hyperaxe"); +const { renderStyledHtml } = require('../backend/renderStyledText'); +const sharedState = require('../configs/shared-state'); +const cnPkg = (() => { try { return require('../server/package.json'); } catch (_) { return {}; } })(); +const mv = () => require('./main_views'); +const cnScope = () => { try { return require('../models/typed_log').requestScope.getStore() || {}; } catch (_) { return {}; } }; +const cnWithLang = (href, lang) => { + if (!lang || !/^\/c(?:[/?#]|$)/.test(href) || /[?&]lang=/.test(href)) return href; + const [pathAndQuery, hash] = href.split('#'); + const joined = `${pathAndQuery}${pathAndQuery.includes('?') ? '&' : '?'}lang=${lang}`; + return hash != null ? `${joined}#${hash}` : joined; +}; +const propagateLang = (html, lang) => !lang ? html : String(html) + .replace(/href="(\/c(?:[/?#][^"]*)?)"/g, (m, href) => `href="${cnWithLang(href, lang)}"`) + .replace(/(]*action="\/c(?:\/[^"]*)?"[^>]*>)/g, (m) => `${m}`); +const renderLangSelector = (current) => { + const langs = Object.keys(require('../client/assets/translations/i18n')); + const scope = cnScope(); + const base = String(scope.path || '/c'); + const params = new URLSearchParams(String(scope.query || '')); + params.delete('lang'); + const others = langs.filter(l => l !== current).map(l => { + const q = new URLSearchParams(params); q.set('lang', l); + return `${l.toUpperCase()}`; + }).join(''); + return `
${escapeHtml(String(current).toUpperCase())}
${others}
`; +}; +const cnText = (key, fallback) => { const v = mv().i18n[key]; return typeof v === 'string' && v ? v : fallback; }; const STAT_TYPE_KEYS = { post:'statsPost', event:'statsEvent', task:'statsTask', forum:'statsForum', tribe:'statsTribe', market:'statsMarket', job:'statsJob', project:'statsProject', shop:'statsShop', image:'statsImage', video:'statsVideo', audio:'statsAudio', document:'statsDocument', bookmark:'statsBookmark', transfer:'statsTransfer', map:'statsMap' }; const STAT_ORDER = ['post','event','task','forum','tribe','market','job','project','shop','image','video','audio','document','bookmark','transfer','map']; @@ -6,7 +33,7 @@ const renderContentStats = (stats, i18nObj = {}) => { if (!stats || typeof stats !== 'object') return null; const chips = STAT_ORDER .filter(t => (stats[t] || 0) > 0) - .map(t => span({ class: 'inhabitant-stat' }, + .map(t => span({ class: 'inhabitant-stat', title: `${i18nObj[STAT_TYPE_KEYS[t]] || t}: ${stats[t]}` }, span({ class: 'inhabitant-stat-label' }, i18nObj[STAT_TYPE_KEYS[t]] || t), strong({ class: 'inhabitant-stat-value' }, String(stats[t])) )); @@ -23,6 +50,31 @@ const escapeHtml = (s) => String(s || '') .replace(/"/g, '"') .replace(/'/g, '''); +const renderTagChips = (tags) => { + const list = (Array.isArray(tags) ? tags : []) + .map(t => String(t || '').trim()) + .filter(Boolean) + .slice(0, 12); + if (!list.length) return ''; + const chips = list.map(raw => String(raw).replace(/^#+/, '')).filter(Boolean).map(t => `#${escapeHtml(t)}`).join(''); + return `
${chips}
`; +}; + +const KIND_KEYS = { audio: 'cnKindAudio', blog: 'cnKindBlog', bookmark: 'cnKindBookmark', document: 'cnKindDocument', event: 'cnKindEvent', feed: 'cnKindFeed', image: 'cnKindImage', job: 'cnKindJob', market: 'cnKindMarket', podcast: 'cnKindPodcast', project: 'cnKindProject', course: 'cnKindCourse', shop: 'cnKindShop', torrent: 'cnKindTorrent', file: 'cnKindFile', video: 'cnKindVideo', wiki: 'cnKindWiki', emergency: 'emergencyLabel', campaign: 'campaignLabel', housing: 'housingTitle', room: 'cnKindRoom', map: 'cnKindMap', calendar: 'calendarTitle' }; +const kindLabel = (kind) => cnText(KIND_KEYS[String(kind || '').toLowerCase()], String(kind || '')); +const renderKindTag = (kind) => `[${escapeHtml(kindLabel(kind).toUpperCase())}]`; + +const renderRichText = (s, { links = true, wikiLinks = null } = {}) => renderStyledHtml(s, { + blobPrefix: '/c/blob/', + internalLinks: false, + links, + plainUrlClass: 'cn-url', + hashtagHref: links ? (tag) => `/c?q=%23${encodeURIComponent(tag)}` : null, + wikiLink: wikiLinks ? (target, slug, label) => wikiLinks.has(slug) + ? a({ href: wikiLinks.get(slug), class: 'cn-wiki-link' }, label) + : label : null +}).replace(/\n/g, '
'); + const blobIdOf = (v) => { if (!v) return null; const s = String(v).trim(); @@ -38,6 +90,22 @@ const blobUrl = (v) => { return id ? `/c/blob/${encodeURIComponent(id)}` : null; }; +const renderClearnetSelector = (on = false, i18nObj = {}) => + div({ class: 'clearnet-choice' }, + div({ class: 'clearnet-choice-options' }, + label({ class: 'clearnet-choice-option' }, input({ type: 'radio', name: 'clearnet', value: '0', ...(on ? {} : { checked: true }) }), renderReachChip(false, i18nObj)), + label({ class: 'clearnet-choice-option' }, input({ type: 'radio', name: 'clearnet', value: '1', ...(on ? { checked: true } : {}) }), renderReachChip(true, i18nObj)) + ) + ); + +const renderClearnetSwitch = (kind, id, on = false) => id + ? form({ method: 'POST', action: `/clearnet/item/${encodeURIComponent(id)}`, class: 'clearnet-switch' }, + input({ type: 'hidden', name: 'kind', value: kind }), + button({ type: 'submit', name: 'on', value: '0', class: on ? 'clearnet-switch-btn' : 'clearnet-switch-btn active', ...(on ? {} : { disabled: true }) }, 'OASIS'), + button({ type: 'submit', name: 'on', value: '1', class: on ? 'clearnet-switch-btn active' : 'clearnet-switch-btn', ...(on ? { disabled: true } : {}) }, 'CLEARNET') + ) + : null; + const renderReachChip = (isClearnet, i18nObj = {}, href = null) => { const icon = isClearnet ? '🌐' : '🏝'; const label = isClearnet @@ -53,6 +121,15 @@ const renderReachChip = (isClearnet, i18nObj = {}, href = null) => { return chip; }; +const renderTribeOriginChip = (origin) => origin && origin.title + ? a({ href: origin.tribeHref || origin.href, class: 'pm-exposition-chip-link' }, + span({ class: 'pm-exposition-chip pm-exposition-tribe' }, + span({ class: 'pm-exposition-icon' }, 'ꖥ'), + span({ class: 'pm-exposition-text' }, origin.title) + ) + ) + : null; + const fediverseProfileUrl = (handle) => { const h = String(handle || '').trim().replace(/^@/, ''); const at = h.indexOf('@'); @@ -78,6 +155,52 @@ const renderFediverseReach = (prefs, i18nObj = {}) => { ); }; +const LICENSES = [ + { id: 'CC0-1.0', label: 'Public Domain', name: 'Public Domain' }, + { id: 'CC-BY-4.0', label: 'CC BY 4.0', name: 'Creative Commons Attribution 4.0' }, + { id: 'CC-BY-SA-4.0', label: 'CC BY-SA 4.0', name: 'Creative Commons Attribution-ShareAlike 4.0' }, + { id: 'GFDL-1.3-or-later', label: 'GNU FDL 1.3', name: 'GNU Free Documentation License 1.3' }, + { id: 'LAL-1.3', label: 'Free Art License 1.3', name: 'Free Art License 1.3' } +]; +const DEFAULT_LICENSE = LICENSES[0].id; +const LICENSED_KINDS = new Set(['wiki', 'audio', 'video', 'image', 'document']); +const licenseInfo = (id) => { + const v = String(id || '').trim() || DEFAULT_LICENSE; + return LICENSES.find(l => l.id === v) || null; +}; +const normalizeLicense = (id) => (LICENSES.find(l => l.id === String(id || '').trim()) || LICENSES[0]).id; + +const renderLicenseChip = (id) => { + const l = licenseInfo(id); + if (!l) return null; + return span({ class: 'pm-exposition-chip pm-exposition-license', title: l.name }, + span({ class: 'pm-exposition-icon' }, '\u2696'), + span({ class: 'pm-exposition-text' }, l.label) + ); +}; + +const renderLicenseSelect = (current, i18nObj = {}) => { + const selectedId = normalizeLicense(current); + return [ + span(i18nObj.licenseLabel || 'License'), + br(), + select({ name: 'license' }, ...LICENSES.map(l => option({ value: l.id, ...(l.id === selectedId ? { selected: true } : {}) }, l.label))), + br() + ]; +}; + +const renderClearnetLicense = (id) => { + const l = licenseInfo(id); + return l ? `\u2696 ${escapeHtml(l.label)}` : ''; +}; + +const renderTransportChip = (i18nObj = {}) => { + return span({ class: 'pm-exposition-chip pm-exposition-transport', title: 'Secret Handshake + boxstream · SHA-256' }, + span({ class: 'pm-exposition-icon' }, '\u21C4'), + span({ class: 'pm-exposition-text' }, i18nObj.transportChipLabel || 'Encrypted in transit') + ); +}; + const renderEncryptedChip = (i18nObj = {}) => { return span({ class: 'pm-exposition-chip pm-exposition-encrypted' }, span({ class: 'pm-exposition-icon' }, '🔒'), @@ -92,16 +215,7 @@ const renderDoubleEncryptionChip = (i18nObj = {}) => { ); }; -const INTERNAL_OASIS_PATHS = [ - 'author','thread','hashtag','inbox','pm','profile','settings','banking','wallet', - 'jobs','events','projects','shops','audios','videos','images','documents','torrents', - 'tribes','tribe','forum','votes','votations','reports','tasks','maps','chats','pads', - 'calendars','trending','opinions','feed','pixelia','cv','invites','peers','stats', - 'blockexplorer','modules','publish','search','tags','mentions','popular','threads', - 'topics','latest','summaries','multiverse','legacy','cipher','graphos','agenda', - 'favorites','logs','games','parliament','courts','market','ai','public','spread', - 'follow','unfollow','block','like','unlike' -]; +const { INTERNAL_PATHS: INTERNAL_OASIS_PATHS } = require('../backend/renderStyledText'); const stripInternalAnchors = (html) => { if (typeof html !== 'string' || !html) return html; @@ -111,10 +225,10 @@ const stripInternalAnchors = (html) => { return html.replace(hrefedClosed, '$1').replace(hrefedBare, '$1'); }; -const renderClearnetSearchForm = ({ authorFeedId = '', query = '', placeholder = 'Search…' }) => { +const renderClearnetSearchForm = ({ authorFeedId = '', query = '', placeholder = '' }) => { if (!authorFeedId) return ''; const safeQuery = escapeHtml(query || ''); - const safePh = escapeHtml(placeholder); + const safePh = escapeHtml(placeholder || cnText('cnSearchPlaceholder', 'Search…')); return ``; }; @@ -124,6 +238,40 @@ const renderClearnetUrlBlock = ({ baseUrl = '', path, i18nObj = {} }) => { ); }; +const CLEARNET_TEXT_CSS = ` +.cn-tags{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0} +.cn-hub-priceline{margin:10px 0 0 0} +.cn-hub-details{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0 0 0} +.cn-detail{display:inline-flex;align-items:center;border:1px solid var(--border);border-radius:5px;padding:3px 8px;font-size:11px;letter-spacing:1px;text-transform:uppercase;color:var(--fg-soft);background:var(--bg-sub);white-space:nowrap} +.cn-tag{border:1px solid var(--border);border-radius:5px;padding:3px 8px;font-size:11px;color:var(--fg-soft);background:var(--bg-sub);text-decoration:none;white-space:nowrap} +.cn-tag:hover{color:var(--fg);border-color:var(--fg)} +.cn-license{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--border);border-radius:5px;padding:3px 8px;font-size:11px;letter-spacing:1px;color:var(--fg-soft);background:var(--bg-sub);text-decoration:none;white-space:nowrap} +a.tag-link{color:var(--accent);text-decoration:none} +a.cn-wiki-link{color:var(--accent);text-decoration:underline;text-underline-offset:2px} +a.cn-wiki-link:hover{color:var(--fg)} +.cn-price{display:inline-flex;align-items:center;color:var(--fg);background:var(--bg-sub);border:1px solid var(--fg);border-radius:4px;padding:4px 10px;font-weight:bold;font-size:14px} +a.tag-link:hover{text-decoration:underline} +.rt-header{display:block;font-weight:bold;margin:10px 0 4px;line-height:1.3} +.rt-header-1{font-size:1.5em} +.rt-header-2{font-size:1.3em} +.rt-header-3{font-size:1.15em} +.rt-item{display:block;padding-left:1.4em;text-indent:-0.7em} +.rt-item::before{content:"\u2022 ";opacity:.7} +.rt-item-2{padding-left:3em} +.rt-item-3{padding-left:4.6em} +.rt-item-4{padding-left:6.2em} +.rt-item-number{text-indent:-1.2em} +.rt-item-number::before{content:none} +.rt-quote{display:block;border-left:3px solid currentColor;padding-left:10px;margin:6px 0;opacity:.85} +.rt-rule{display:block;border-top:1px solid currentColor;opacity:.4;margin:10px 0} +.rt-code{font-family:monospace;background:rgba(128,128,128,.18);padding:1px 4px;border-radius:3px;word-break:break-word} +.rt-code-block{display:block;font-family:monospace;white-space:pre-wrap;background:rgba(128,128,128,.18);padding:10px;border-radius:5px;margin:8px 0;overflow-x:auto;word-break:break-word} +.post-image{display:block;max-width:100%;height:auto;margin:12px 0;border:1px solid var(--border);border-radius:6px} +.post-video,.post-audio{display:block;width:100%;max-width:100%;margin:12px 0;border-radius:6px;background:#000} +.post-pdf{display:inline-block;margin:8px 0;padding:8px 14px;background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;color:var(--fg);text-decoration:none} +.post-pdf:hover{border-color:var(--fg)} +`; + const CLEARNET_SEARCH_CSS = ` .cn-search{margin:0} .cn-search input[type=text]{width:240px;max-width:100%;background:var(--bg-sub);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:8px 12px;font-size:14px;font-family:inherit} @@ -189,10 +337,13 @@ header.cn-header{display:flex;align-items:center;gap:16px;padding-bottom:16px;ma .cn-brand{font-size:20px;font-weight:700;color:var(--fg);letter-spacing:1px} .cn-brand-sub{color:var(--fg-dim);font-size:12px;text-transform:uppercase;letter-spacing:2px;margin-top:2px} .cn-header-extra{flex:1 1 auto;display:flex;justify-content:flex-end;align-items:center;min-width:0} +.cn-kind-tag{font-weight:700;color:var(--fg);letter-spacing:1px} +.cn-sep{border:0;border-top:1px solid var(--border);margin:24px 0} h2.cn-section{color:var(--fg);font-size:18px;text-transform:uppercase;letter-spacing:2px;margin:32px 0 16px 0;padding-bottom:8px;border-bottom:1px solid var(--border)} footer.cn-footer{margin-top:48px;padding-top:20px;border-top:1px solid var(--border);font-size:12px;color:var(--fg-dim);text-align:center;letter-spacing:0.5px} footer.cn-footer a{color:var(--fg-soft)} footer.cn-footer .cn-footer-logo{width:56px;height:auto;display:block;margin:0 auto 10px auto;border-radius:6px} +footer.cn-footer .cn-footer-line{margin:3px 0} `; const renderClearnetPage = ({ title, ogTitle, ogDescription = '', ogImage = null, extraCss = '', body, headerExtra = '', hubFeedId = null }) => { @@ -201,12 +352,12 @@ const renderClearnetPage = ({ title, ogTitle, ogDescription = '', ogImage = null const safeOgDesc = escapeHtml(ogDescription || ''); const palette = getCurrentPalette(); const baseCss = buildBaseCss(palette); - const brandInner = `
⛱ Oasis HUB
Libre · P2P · Federated
`; - const brandBlock = hubFeedId - ? `${brandInner}` - : `
${brandInner}
`; + const brandInner = `
⛱ Oasis HUB
${escapeHtml(cnText('cnBrandSub', 'Libre · P2P · Federated'))}
`; + const lang = escapeHtml(mv().getLanguage ? mv().getLanguage() : 'en'); + const langOverride = cnScope().cnLang || ''; + const brandBlock = `${brandInner}`; return ` - + @@ -216,21 +367,33 @@ const renderClearnetPage = ({ title, ogTitle, ogDescription = '', ogImage = null ${ogImage ? `` : ''} -
${brandBlock} - ${headerExtra ? `
${headerExtra}
` : ''} +
${renderLangSelector(lang)}${propagateLang(headerExtra || '', langOverride)}
- ${stripInternalAnchors(body)} + ${propagateLang(stripInternalAnchors(body), langOverride)}
- - Powered by Oasis + + + +
`; @@ -242,14 +405,44 @@ const renderClearnetNotFound = () => { ogTitle: 'Oasis', ogDescription: '', extraCss: `.cn-notfound{color:var(--fg-soft);font-size:16px;max-width:480px;margin:80px auto 40px auto;text-align:center;line-height:1.5}`, - body: `

The content is not accessible at this moment.

` + body: `

${escapeHtml(cnText('cnNotAccessible', 'The content is not accessible at this moment.'))}

` }); }; +const CLEARNET_PAGER_CSS = ` +.cn-pager{display:flex;justify-content:flex-end;align-items:center;flex-wrap:wrap;gap:8px;margin:24px 0 8px 0} +.cn-pager-info{color:var(--fg-dim);font-size:12px;margin-right:4px} +.cn-pager-sizes{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-right:auto} +.cn-pager-btn{display:inline-block;padding:6px 14px;background:var(--bg-elev);color:var(--fg-soft);border:1px solid var(--border);border-radius:14px;font-size:13px;text-decoration:none;transition:border-color .15s ease,color .15s ease,background .15s ease} +.cn-pager-btn:hover,.cn-pager-btn.active{border-color:var(--fg);color:var(--fg);text-decoration:none} +`; +const paginateClearnet = (list, page) => mv().slicePage(list, page, mv().listPerPage(cnScope().query)); +const renderClearnetPager = ({ base, params = {}, page, pages, per, total, pageParam = 'page' }) => { + const query = String(cnScope().query || ''); + const sizes = !!per && mv().showPageSizes(total, query); + if (!(pages > 1) && !sizes) return ''; + const chosen = new URLSearchParams(query).has('perPage'); + const href = (n, size) => { + const q = new URLSearchParams(); + for (const [k, v] of Object.entries(params)) if (v) q.set(k, String(v)); + if (size || chosen) q.set('perPage', size || per); + if (n > 1) q.set(pageParam, String(n)); + const s = q.toString(); + return escapeHtml(s ? `${base}?${s}` : base); + }; + const sizeLinks = sizes + ? `${escapeHtml(cnText('searchPerPageLabel', 'Results per page'))}${mv().LIST_PAGE_SIZES.map(s => `${escapeHtml(mv().pageSizeLabel(s))}`).join('')}` + : ''; + const info = pages > 1 ? `${escapeHtml(cnText('cnPageOf', 'Page {page} of {pages}').replace('{page}', String(page)).replace('{pages}', String(pages)))}` : ''; + const prev = page > 1 ? `${escapeHtml(cnText('cnPrevPage', '← Previous'))}` : ''; + const next = page < pages ? `${escapeHtml(cnText('cnNextPage', 'Next →'))}` : ''; + return `
${sizeLinks}${info}${prev}${next}
`; +}; + const renderClearnetMediaView = ({ kind, item }) => { const blob = blobUrl(item.url); - const title = escapeHtml(item.title || 'Untitled'); - const desc = escapeHtml(item.description || ''); + const title = escapeHtml(item.title || cnText('cnUntitled', 'Untitled')); + const desc = renderRichText(item.description || '', { wikiLinks: item.wikiLinks instanceof Map ? item.wikiLinks : null }); const dateStr = item.createdAt ? escapeHtml(new Date(item.createdAt).toISOString().slice(0, 10)) : ''; const extraCss = ` .cn-media-meta{color:var(--fg-dim);font-size:13px;margin-bottom:16px;display:flex;gap:14px;flex-wrap:wrap;align-items:baseline} @@ -259,9 +452,11 @@ const renderClearnetMediaView = ({ kind, item }) => { .cn-media-frame{margin:16px 0} .cn-media-frame img{max-width:100%;height:auto;border-radius:6px;border:1px solid var(--border);display:block} .cn-media-frame audio,.cn-media-frame video{width:100%;max-width:100%;display:block;border-radius:6px;background:#000} -.cn-media-frame .cn-media-doc{display:inline-block;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;padding:10px 18px;color:var(--fg);text-decoration:none} +.cn-media-frame .cn-media-doc{display:inline-block;max-width:100%;box-sizing:border-box;overflow-wrap:anywhere;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;padding:10px 18px;color:var(--fg);text-decoration:none} .cn-media-frame .cn-media-doc:hover{border-color:var(--fg)} +.cn-media-frame .cn-media-docs{display:flex;flex-direction:column;align-items:flex-start;gap:10px} `; + const fileKind = kind === 'document' || kind === 'torrent' || kind === 'file'; let mediaHtml = ''; if (blob) { if (kind === 'image') { @@ -270,21 +465,38 @@ const renderClearnetMediaView = ({ kind, item }) => { mediaHtml = ``; } else if (kind === 'video') { mediaHtml = ``; - } else if (kind === 'document' || kind === 'torrent') { + } else if (kind === 'file' || kind === 'torrent') { + const dlName = (v) => String(v || '').replace(/[\\/:*?"<>|\r\n]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120); + const dlLink = (href, name) => `⇩ ${escapeHtml(name)}`; + const original = kind === 'file' ? blob : blobUrl(item.source); + const originalName = dlName(kind === 'file' ? item.fileName : item.sourceName) || dlName(item.title) || 'file'; + const torrentBlob = kind === 'file' ? blobUrl(item.torrentUrl) : blob; + const torrentName = `${kind === 'file' ? originalName : (dlName(item.title) || 'download').replace(/\.torrent$/i, '')}.torrent`; + mediaHtml = `
${[original ? dlLink(original, originalName) : '', torrentBlob ? dlLink(torrentBlob, torrentName) : ''].filter(Boolean).join('')}
`; + } else if (kind === 'document') { mediaHtml = `⇩ ${title}`; + } else { + mediaHtml = `${title}`; } } const body = `
+ ${renderKindTag(kind)} ${dateStr ? `📅 ${dateStr}` : ''} + ${(Array.isArray(item.details) ? item.details : []).map(d => `${escapeHtml(String(d))}`).join('')} + ${item.price ? `${escapeHtml(String(item.price))} ECO` : ''} + ${LICENSED_KINDS.has(kind) ? renderClearnetLicense(item.license) : ''}
-

${title}

- ${mediaHtml ? `
${mediaHtml}
` : ''} + ${item.title ? `

${title}

` : ''} +
+ ${mediaHtml && !fileKind ? `
${mediaHtml}
` : ''} ${desc ? `

${desc}

` : ''} + ${mediaHtml && fileKind ? `
${mediaHtml}
` : ''} + ${renderTagChips(item.tags)} `; return renderClearnetPage({ - title: `${title} — Oasis`, - ogTitle: item.title || 'Oasis', + title: `${item.title || (kind ? kindLabel(kind) : 'Oasis')} | Oasis`, + ogTitle: item.title || (kind ? kindLabel(kind) : 'Oasis'), ogDescription: item.description || '', ogImage: (kind === 'image') ? blob : null, extraCss, @@ -293,18 +505,80 @@ const renderClearnetMediaView = ({ kind, item }) => { }); }; +const renderClearnetPodcastView = ({ channel }) => { + const title = escapeHtml(channel.title || cnText('cnUntitled', 'Untitled')); + const desc = renderRichText(channel.description || ''); + const cover = channel.cover && channel.cover.blobId ? blobUrl(channel.cover.blobId) : null; + const coverIsVideo = !!(channel.cover && channel.cover.kind === 'video'); + const dateStr = channel.createdAt ? escapeHtml(new Date(channel.createdAt).toISOString().slice(0, 10)) : ''; + const extraCss = ` +.cn-media-meta{color:var(--fg-dim);font-size:13px;margin-bottom:16px;display:flex;gap:14px;flex-wrap:wrap;align-items:baseline} +.cn-media-title{color:var(--fg);font-size:26px;font-weight:700;margin:0 0 12px 0} +.cn-media-desc{color:var(--fg-soft);white-space:pre-wrap;line-height:1.6;margin:16px 0} +.cn-podcast-cover{max-width:240px;width:100%;border-radius:6px;border:1px solid var(--border);display:block;margin:12px 0} +.cn-episode{border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:12px 0;background:var(--bg-elev)} +.cn-episode h2{margin:0 0 8px 0;font-size:17px;color:var(--fg)} +.cn-episode .cn-episode-meta{color:var(--fg-dim);font-size:12px;margin-bottom:8px} +.cn-episode audio,.cn-episode video{width:100%;max-width:100%;display:block;border-radius:6px;background:#000} +.cn-episode p{color:var(--fg-soft);white-space:pre-wrap;line-height:1.5;margin:8px 0 0 0} +`; + const episodes = Array.isArray(channel.episodes) ? channel.episodes.slice().reverse() : []; + const epHtml = episodes.map(ep => { + const src = ep.media && ep.media.blobId ? blobUrl(ep.media.blobId) : null; + const player = !src ? '' : (ep.media.kind === 'video' + ? `` + : ``); + const epDate = ep.createdAt ? escapeHtml(new Date(ep.createdAt).toISOString().slice(0, 10)) : ''; + return `

#${Number(ep.number) || 0} · ${escapeHtml(ep.title || '')}

${epDate}
${player}${ep.description ? `

${renderRichText(ep.description)}

` : ''}
`; + }).join(''); + const body = ` +
${renderKindTag('podcast')}${dateStr ? `📅 ${dateStr}` : ''}🎧 ${episodes.length}
+

${title}

+ ${cover ? (coverIsVideo ? `` : `${title}`) : ''} + ${desc ? `

${desc}

` : ''} + ${epHtml ? `
${epHtml}` : ''} +`; + return renderClearnetPage({ + title: `${channel.title || cnText('cnUntitled', 'Untitled')} | Oasis`, + ogTitle: channel.title || 'Oasis', + ogDescription: channel.description || '', + ogImage: cover && !coverIsVideo ? cover : null, + extraCss, + body, + hubFeedId: channel.author || null + }); +}; + module.exports = { + kindLabel, + renderTagChips, + renderClearnetPodcastView, escapeHtml, + renderRichText, + renderKindTag, blobIdOf, blobUrl, renderReachChip, + renderTribeOriginChip, + renderClearnetSelector, + renderClearnetSwitch, renderFediverseReach, renderContentStats, renderEncryptedChip, + renderTransportChip, + LICENSES, + licenseInfo, + normalizeLicense, + renderLicenseChip, + renderLicenseSelect, + renderClearnetLicense, renderDoubleEncryptionChip, renderClearnetUrlBlock, renderClearnetSearchForm, renderClearnetPage, renderClearnetNotFound, + CLEARNET_PAGER_CSS, + paginateClearnet, + renderClearnetPager, renderClearnetMediaView }; diff --git a/src/views/comments_view.js b/src/views/comments_view.js index 85489fc..9d2b6d4 100644 --- a/src/views/comments_view.js +++ b/src/views/comments_view.js @@ -1,16 +1,21 @@ const { div, p, h2, span, a, form, input, textarea, button, label, br, details, summary } = require("../server/node_modules/hyperaxe"); +const { TEXT_CAP } = require('../backend/long_text'); const moment = require("../server/node_modules/moment"); const { i18n, userLink } = require("./main_views"); -const { renderUrl } = require("../backend/renderUrl"); +const { config } = require("../server/SSB_server.js"); +const { renderStyledText } = require("../backend/renderStyledText"); const COMMENT_ICON = "✑"; +let commentsOpenByDefault = false; +const setCommentsOpen = (value) => { commentsOpenByDefault = !!value; }; + const visibleComments = (comments) => (Array.isArray(comments) ? comments : []).filter(c => { const t = c && c.value && c.value.content && c.value.content.text; return t && String(t).trim(); }); -const renderCommentCard = (c) => { +const renderCommentCard = (c, extra = null) => { const author = (c && c.value && c.value.author) || ""; const ts = (c && c.value && c.value.timestamp) || (c && c.timestamp); const absDate = ts ? moment(ts).format("YYYY/MM/DD HH:mm") : ""; @@ -18,43 +23,51 @@ const renderCommentCard = (c) => { const content = (c && c.value && c.value.content) || {}; const rootId = content.fork || content.root || null; const text = content.text || ""; + const own = !!author && !!(config && config.keys) && author === config.keys.id; - return div({ class: "votations-comment-card", id: c && c.key ? c.key : undefined }, + return div({ class: own ? "votations-comment-card own-comment" : "votations-comment-card", id: c && c.key ? c.key : undefined }, + own && c.key + ? form({ method: "POST", action: `/comments/delete/${encodeURIComponent(c.key)}`, class: "comment-delete-form" }, + button({ type: "submit", class: "btn-singleview btn-delete", title: i18n.chatDelete }, "✕")) + : null, span({ class: "created-at" }, - span(i18n.createdBy), author ? userLink(author) : span("(unknown)"), absDate ? span(" | ") : "", absDate ? span({ class: "votations-comment-date" }, absDate) : "", relDate ? span({ class: "votations-comment-date" }, " | ", i18n.sendTime) : "", relDate && rootId ? a({ href: `/thread/${encodeURIComponent(rootId)}#${encodeURIComponent(c.key)}` }, relDate) : "" ), - p({ class: "votations-comment-text" }, ...renderUrl(String(text))) + p({ class: "votations-comment-text" }, ...renderStyledText(String(text), { zoomImages: true })), + extra ); }; -const renderCommentsSection = ({ action, comments = [], returnTo = null, closedNote = null, extraClass = "" } = {}) => { +const renderCommentsSection = ({ action, comments = [], returnTo = null, closedNote = null, extraClass = "", open = false, commentExtra = null } = {}) => { const list = visibleComments(comments); - return details({ class: "comments-collapse" + (extraClass ? ` ${extraClass}` : "") }, + return details({ class: "comments-collapse" + (extraClass ? ` ${extraClass}` : ""), ...((open || commentsOpenByDefault) ? { open: true } : {}), ...(process.env.OASIS_MOBILE === '1' ? { id: "comments" } : {}) }, summary({ class: list.length > 0 ? "comments-summary engage-on" : "comments-summary" }, span({ class: "comments-summary-icon" }, COMMENT_ICON), span({ class: "comments-summary-count" }, `(${list.length})`) ), div({ class: "vote-comments-section" }, + list.length + ? div({ class: "comments-list" }, ...list.map((c, i) => { + const card = renderCommentCard(c, typeof commentExtra === "function" ? commentExtra(c) : null); + return i === list.length - 1 ? div({ id: "comments-latest", class: "comments-latest-anchor" }, card) : card; + })) + : (closedNote ? null : p({ class: "votations-no-comments" }, i18n.voteNoCommentsYet)), closedNote ? p({ class: "muted" }, closedNote) : div({ class: "comment-form-wrapper" }, h2({ class: "comment-form-title" }, i18n.voteNewCommentLabel), form({ method: "POST", action, class: "comment-form", enctype: "multipart/form-data" }, returnTo ? input({ type: "hidden", name: "returnTo", value: returnTo }) : null, - textarea({ name: "text", rows: 4, class: "comment-textarea", placeholder: i18n.voteNewCommentPlaceholder }), + textarea({ name: "text", rows: 4, maxlength: String(TEXT_CAP), class: "comment-textarea", placeholder: i18n.voteNewCommentPlaceholder, required: true }), div({ class: "comment-file-upload" }, label(i18n.uploadMedia), input({ type: "file", name: "blob" })), br(), button({ type: "submit", class: "comment-submit-btn" }, i18n.voteNewCommentButton) ) - ), - list.length - ? div({ class: "comments-list" }, ...list.map(renderCommentCard)) - : (closedNote ? null : p({ class: "votations-no-comments" }, i18n.voteNoCommentsYet)) + ) ) ); }; @@ -62,7 +75,8 @@ const renderCommentsSection = ({ action, comments = [], returnTo = null, closedN const renderCommentsLink = ({ href, count = 0 } = {}) => { if (!href) return null; const total = Number(count) || 0; - return a({ href, class: total > 0 ? "comments-summary engage-on comments-link" : "comments-summary comments-link" }, + const target = process.env.OASIS_MOBILE === '1' ? `${href}#comments` : href; + return a({ href: target, class: total > 0 ? "comments-summary engage-on comments-link" : "comments-summary comments-link" }, span({ class: "comments-summary-icon" }, COMMENT_ICON), span({ class: "comments-summary-count" }, `(${total})`) ); @@ -73,5 +87,6 @@ module.exports = { renderCommentsLink, visibleComments, renderCommentCard, - renderCommentsSection + renderCommentsSection, + setCommentsOpen }; diff --git a/src/views/courts_view.js b/src/views/courts_view.js index 9fb4138..5f9fa9d 100644 --- a/src/views/courts_view.js +++ b/src/views/courts_view.js @@ -1,6 +1,7 @@ const { form, button, div, h2, p, section, input, label, br, a, span, table, thead, tbody, tr, th, td, textarea, select, option, ul, li, img } = require('../server/node_modules/hyperaxe'); const moment = require('../server/node_modules/moment'); -const { template, i18n, userLink, renderStateChip } = require('./main_views'); +const { template, i18n, userLink, renderStateChip, renderModuleStatsBy, paged } = require('./main_views'); +const { renderStyledText, safeExternalHref } = require('../backend/renderStyledText'); const CourtsE2EChip = () => renderStateChip('encrypted', '🔒', i18n.encryptedChipLabel || 'E2E'); @@ -31,6 +32,19 @@ const CASE_TITLE_PRESETS = [ 'Governance dispute' ]; +const PRESET_KEYS = { + 'Minor conflict': 'courtsPresetMinorConflict', + 'Moderate conflict': 'courtsPresetModerateConflict', + 'Severe conflict': 'courtsPresetSevereConflict', + 'Harassment or abuse': 'courtsPresetHarassment', + 'Content moderation': 'courtsPresetContentModeration', + 'Ban or restriction': 'courtsPresetBanRestriction', + 'Governance dispute': 'courtsPresetGovernanceDispute' +}; +const presetLabel = (t) => i18n[PRESET_KEYS[t]] || t; +const caseTitle = (title) => CASE_TITLE_PRESETS.reduce((acc, t) => acc.split(t).join(presetLabel(t)), String(title || '')); +const statusLabel = (status) => String(i18n[`courtsStatus${String(status || '').toUpperCase()}`] || status || '').toUpperCase(); + const FILTERS = [ { value: 'cases', key: 'courtsFilterCases' }, { value: 'mycases', key: 'courtsFilterMyCases' }, @@ -41,12 +55,12 @@ const FILTERS = [ { value: 'open', key: 'courtsFilterOpenCase' } ]; -const Tabs = (active) => +const Tabs = (active, emptyCourts = false) => div( { class: 'filters' }, form( { method: 'GET', action: '/courts' }, - FILTERS.map((f) => { + FILTERS.filter((f) => !emptyCourts || f.value === 'open' || f.value === 'rules' || (f.value === active && active !== 'cases')).map((f) => { const isOpen = f.value === 'open'; const cls = isOpen @@ -82,15 +96,15 @@ const CaseForm = (prefill = {}) => type: 'text', name: 'titleSuffix', required: true, - placeholder: 'Subject or short description', + placeholder: i18n.courtsCaseSubjectPlaceholder, value: prefill.titleSuffix || '' }), br(), - label('Case type'), + label(i18n.courtsCaseType), br(), select( { name: 'titlePreset', required: true }, - CASE_TITLE_PRESETS.map((t) => option({ value: t, ...(prefill.titlePreset === t ? { selected: true } : {}) }, t)) + CASE_TITLE_PRESETS.map((t) => option({ value: t, ...(prefill.titlePreset === t ? { selected: true } : {}) }, presetLabel(t))) ), br(), br(), @@ -164,7 +178,7 @@ const EvidenceForm = (caseId) => }), br(), br(), - label(i18n.uploadMedia || 'Upload media (max-size: 50MB)'), + label(i18n.uploadMedia || 'Upload media (max-size: 75MB)'), br(), input({ type: 'file', name: 'image' }), br(), @@ -325,31 +339,6 @@ const AcceptSettlementForm = (caseId) => button({ type: 'submit', class: 'create-button' }, i18n.courtsSettlementAcceptBtn) ); -const VerdictVoteForm = (caseId) => - div( - { class: 'div-center' }, - h2(i18n.courtsVerdictVoteTitle), - form( - { - method: 'POST', - action: `/courts/cases/${encodeURIComponent(caseId)}/verdict/vote` - }, - label(i18n.courtsVerdictVoteLabel), - br(), - select( - { name: 'decision' }, - option({ value: 'ACCEPT' }, i18n.courtsVerdictVoteAccept), - option({ value: 'REJECT' }, i18n.courtsVerdictVoteReject) - ), - br(), - br(), - button( - { type: 'submit', class: 'create-button' }, - i18n.courtsVerdictVoteSubmit - ) - ) - ); - const shortId = (id) => { const s = String(id || ''); if (!s) return ''; @@ -361,60 +350,9 @@ const UserLinkCompact = (id) => id ? userLink(id) : span(''); const UserLinkFull = (id) => id ? userLink(id) : span(''); const renderRichTextNodes = (raw) => { - const text = String(raw || ''); + const text = String(raw || '').trim(); if (!text) return []; - const nodes = []; - let remaining = text; - const imgRegex = /!\[[^\]]*]\(([^)]+)\)/; - const linkRegex = /\[([^\]]+)]\((https?:\/\/[^)]+)\)/; - while (remaining.length) { - const imgMatch = imgRegex.exec(remaining); - const linkMatch = linkRegex.exec(remaining); - let next = null; - let type = null; - if (imgMatch && (!linkMatch || imgMatch.index < linkMatch.index)) { - next = imgMatch; - type = 'img'; - } else if (linkMatch) { - next = linkMatch; - type = 'link'; - } - if (!next) { - if (remaining.trim()) nodes.push(p(remaining)); - break; - } - const idx = next.index; - if (idx > 0) { - const before = remaining.slice(0, idx); - if (before.trim()) nodes.push(p(before)); - } - if (type === 'img') { - const ref = next[1]; - nodes.push( - img({ - class: 'evidence-image', - src: `/blob/${encodeURIComponent(ref)}`, - alt: 'evidence' - }) - ); - } else { - const labelText = next[1]; - const url = next[2]; - nodes.push( - a( - { - class: 'evidence-link', - href: url, - target: '_blank', - rel: 'noopener noreferrer' - }, - labelText - ) - ); - } - remaining = remaining.slice(idx + next[0].length); - } - return nodes; + return [p(...renderStyledText(text))]; }; const RichTextBlock = (raw) => { @@ -529,7 +467,7 @@ const CaseCard = (c) => { { class: 'card-header' }, div( { class: 'card-header__meta' }, - h2(c.title || ''), + h2(caseTitle(c.title)), div({ class: 'card-chips-row' }, CourtsE2EChip()), div( { class: 'card-field' }, @@ -539,7 +477,7 @@ const CaseCard = (c) => { div( { class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThStatus + ': '), - span({ class: 'card-value' }, String(c.status || '')) + span({ class: 'card-value' }, statusLabel(c.status)) ), c.answerBy ? div( @@ -747,13 +685,13 @@ const MyCaseCard = (c) => { div( { class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThCase + ': '), - span({ class: 'card-value' }, c.title || '') + span({ class: 'card-value' }, caseTitle(c.title)) ), div({ class: 'card-chips-row' }, CourtsE2EChip()), div( { class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThStatus + ': '), - span({ class: 'card-value' }, String(c.status || '')) + span({ class: 'card-value' }, statusLabel(c.status)) ), div( { class: 'card-field' }, @@ -901,8 +839,8 @@ const CasesTable = (rows = [], opts = {}) => { const bodyRows = rows.map((c) => { const role = showRole ? roleTextForCase(c) : ''; return tr( - td(c.title || ''), - td( + td({ 'data-label': i18n.courtsThCase }, caseTitle(c.title)), + td({ 'data-label': i18n.courtsAccuser }, c.accuser ? a( { @@ -913,10 +851,10 @@ const CasesTable = (rows = [], opts = {}) => { ) : '' ), - td(methodLabel(c.method)), - td(c.createdAt ? fmt(c.createdAt) : ''), - showRole ? td(role) : null, - td( + td({ 'data-label': i18n.courtsCaseMethod }, methodLabel(c.method)), + td({ 'data-label': i18n.courtsThCreatedAt }, c.createdAt ? fmt(c.createdAt) : ''), + showRole ? td({ 'data-label': i18n.courtsThRole || 'Role' }, role) : null, + td({ class: 'table-row-action' }, c.id ? form( { @@ -960,10 +898,10 @@ const NominationsTable = (nominations = [], currentUserId = '') => { currentUserId && String(n.judgeId || '') === String(currentUserId || ''); return tr( - td(userLink(n.judgeId)), - td(String(n.supports || 0)), - td(fmt(n.createdAt)), - td( + td({ 'data-label': i18n.courtsThJudge }, userLink(n.judgeId)), + td({ 'data-label': i18n.courtsThSupports }, String(n.supports || 0)), + td({ 'data-label': i18n.courtsThDate }, fmt(n.createdAt)), + td({ class: 'table-row-action' }, isSelf ? span('') : form( @@ -1024,7 +962,7 @@ const HistoryList = (rows = []) => { span({ class: 'card-label' }, i18n.courtsThStatus + ': '), span({ class: 'card-value' }, String(hh.status || '')) ), - h2(hh.title || ''), + h2(caseTitle(hh.title)), canShowDescription ? RichTextBlock(hh.description || '') : null, form( { @@ -1065,9 +1003,10 @@ const RulesContent = () => { ); }; -const CaseSearch = (filter, search = '') => +const CaseSearch = (filter, search = '', items = null) => div( - { class: 'filters' }, + { class: 'filters activity-filter-chips activity-toolbar-row' }, + items ? renderModuleStatsBy(items, c => c.status === 'OPEN' ? 'OPEN' : c.status === 'IN_PROGRESS' ? 'IN_PROGRESS' : 'DONE', [{ value: 'OPEN', label: i18n.taskStatusOpen }, { value: 'IN_PROGRESS', label: i18n.taskStatusInProgress }, { value: 'DONE', label: i18n.taskStatusClosed }]) : null, form( { method: 'GET', action: '/courts', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), @@ -1136,7 +1075,7 @@ const CaseDetailsBlock = (c) => { { class: 'card-header' }, div( { class: 'card-header__meta' }, - h2(c.title || ''), + h2(caseTitle(c.title)), div({ class: 'card-chips-row' }, CourtsE2EChip()), c.method ? div( @@ -1153,7 +1092,7 @@ const CaseDetailsBlock = (c) => { div( { class: 'card-field' }, span({ class: 'card-label' }, i18n.courtsThStatus + ': '), - span({ class: 'card-value' }, String(c.status || '')) + span({ class: 'card-value' }, statusLabel(c.status)) ), c.createdAt ? div( @@ -1244,8 +1183,8 @@ const CaseDetailsBlock = (c) => { a( { class: 'evidence-link', - href: e.link, - target: '_blank', + href: safeExternalHref(e.link), + target: '_blank', rel: 'noopener noreferrer', rel: 'noopener noreferrer' }, e.link @@ -1363,27 +1302,27 @@ const courtsView = async (state) => { i18n.courtsTitle, section( div( - { class: 'tags-header' }, + { class: 'tags-header module-header-line' }, h2(i18n.courtsTitle), p(i18n.courtsDescription) ), - Tabs(filter), - filter === 'cases' ? CaseSearch(filter, search) : null + Tabs(filter, cases.length === 0 && myCases.length === 0 && history.length === 0 && nominations.length === 0 && !String(search || '').trim()), + filter === 'cases' && ((Array.isArray(cases) && cases.length > 0) || String(search || '').trim()) ? CaseSearch(filter, search, cases) : null ), section( - filter === 'cases' ? CasesTable(cases) : null, + filter === 'cases' ? CasesTable(paged(cases)) : null, filter === 'mycases' ? myCases.length - ? CasesTable(myCases, { showRole: true }) + ? CasesTable(paged(myCases), { showRole: true }) : div({ class: 'empty' }, p(i18n.courtsNoMyCases)) : null, filter === 'actions' ? myCases.length - ? MyCasesList(myCases) + ? MyCasesList(paged(myCases)) : div({ class: 'empty' }, p(i18n.courtsNoMyCases)) : null, - filter === 'judges' ? JudgesSection(nominations, userId) : null, - filter === 'history' ? HistoryList(history) : null, + filter === 'judges' ? JudgesSection(paged(nominations), userId) : null, + filter === 'history' ? HistoryList(paged(history)) : null, filter === 'rules' ? RulesContent() : null, filter === 'open' ? CaseForm(state.prefill || {}) : null ) @@ -1396,7 +1335,7 @@ const courtsCaseView = async (state) => { i18n.courtsTitle, section( div( - { class: 'tags-header' }, + { class: 'tags-header module-header-line' }, h2(i18n.courtsTitle), p(i18n.courtsDescription) ), diff --git a/src/views/cv_view.js b/src/views/cv_view.js index b076e4b..b6c8ff5 100644 --- a/src/views/cv_view.js +++ b/src/views/cv_view.js @@ -1,6 +1,7 @@ const { form, button, div, h2, p, section, textarea, label, input, br, img, a, select, option, span, table, tr, td } = require("../server/node_modules/hyperaxe"); +const moment = require("../server/node_modules/moment"); const { template, i18n, userLink, renderVisibilityChip, renderDocumentActions } = require('./main_views'); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledText } = require('../backend/renderStyledText'); const generateCVBox = (label, content, className) => { return div({ class: `cv-box ${className}` }, @@ -15,7 +16,7 @@ exports.createCVView = async (cv = {}, editMode = false) => { return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.cvDescription) ), @@ -44,34 +45,36 @@ exports.createCVView = async (cv = {}, editMode = false) => { label(i18n.cvNameLabel), br(), input({ type: "text", name: "name", required: true, value: cv.name || "" }), br(), label(i18n.cvDescriptionLabel), br(), - textarea({ name: "description", required: true, rows: 4 }, cv.description || ""), br(), + textarea({ maxlength: "5000", name: "description", required: true, rows: 4 }, cv.description || ""), br(), label(i18n.cvLanguagesLabel), br(), input({ type: "text", name: "languages", value: cv.languages || "" }), br(), label(i18n.cvPhotoLabel), br(), input({ type: "file", name: "image" }), br(), br(), + label(i18n.cvPdfLabel), br(), + input({ type: "file", name: "cvPdf", accept: "application/pdf" }), br(), br(), label(i18n.cvPersonalExperiencesLabel), br(), - textarea({ name: "personalExperiences", rows: 4 }, cv.personalExperiences || ""), br(), + textarea({ maxlength: "5000", name: "personalExperiences", rows: 4 }, cv.personalExperiences || ""), br(), label(i18n.cvPersonalSkillsLabel), br(), input({ type: "text", name: "personalSkills", required: true, value: (cv.personalSkills || []).join(", ") }), br() ], "personal"), generateCVBox(i18n.cvOasis, [ label(i18n.cvOasisExperiencesLabel), br(), - textarea({ name: "oasisExperiences", rows: 4 }, cv.oasisExperiences || ""), br(), + textarea({ maxlength: "5000", name: "oasisExperiences", rows: 4 }, cv.oasisExperiences || ""), br(), label(i18n.cvOasisSkillsLabel), br(), input({ type: "text", name: "oasisSkills", value: (cv.oasisSkills || []).join(", ") }), br() ], "oasis"), generateCVBox(i18n.cvEducational, [ label(i18n.cvEducationExperiencesLabel), br(), - textarea({ name: "educationExperiences", rows: 4 }, cv.educationExperiences || ""), br(), + textarea({ maxlength: "5000", name: "educationExperiences", rows: 4 }, cv.educationExperiences || ""), br(), label(i18n.cvEducationalSkillsLabel), br(), input({ type: "text", name: "educationalSkills", value: (cv.educationalSkills || []).join(", ") }), br() ], "education"), generateCVBox(i18n.cvProfessional, [ label(i18n.cvProfessionalExperiencesLabel), br(), - textarea({ name: "professionalExperiences", rows: 4 }, cv.professionalExperiences || ""), br(), + textarea({ maxlength: "5000", name: "professionalExperiences", rows: 4 }, cv.professionalExperiences || ""), br(), label(i18n.cvProfessionalSkillsLabel), br(), input({ type: "text", name: "professionalSkills", value: (cv.professionalSkills || []).join(", ") }), br() ], "professional"), @@ -104,23 +107,38 @@ exports.createCVView = async (cv = {}, editMode = false) => { ) }; -exports.cvView = async (cv) => { +exports.cvView = async (cv, certificates = []) => { const title = i18n.cvTitle; if (!cv) { return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.cvDescription) ), - div({ class: "no-cv" }, - p(i18n.cvNoCV), + div({ class: "filters" }, form({ method: "GET", action: "/cv/create" }, - button({ type: "submit" }, i18n.cvCreateButton) + button({ type: "submit", class: "create-button" }, i18n.cvCreateButton) ) - ) + ), + div({ class: "no-content-box" }, p(i18n.cvNoCV)), + Array.isArray(certificates) && certificates.length + ? div({ class: "cv-section-block school-certificates" }, + h2(i18n.schoolCertificates), + certificates.map(cert => + div({ class: "school-certificate" }, + span("🎓 "), + a({ href: `/school/course/${encodeURIComponent(cert.courseId)}` }, cert.courseTitle || cert.courseId), + span(" — "), + userLink(cert.author), + span({ class: "school-certificate-date" }, ` (${moment(cert.createdAt).format("YYYY/MM/DD")})`), + a({ href: `/school/certificate/pdf/${encodeURIComponent(cert.courseId)}/${encodeURIComponent(cert.id)}`, class: "filter-btn school-cert-pdf" }, `⬇ ${i18n.schoolCertificatePdf || "PDF"}`) + ) + ) + ) + : null ) ) } @@ -144,7 +162,7 @@ exports.cvView = async (cv) => { const renderBlock = (heading, body, list) => (body || skills(list).length) ? div({ class: "cv-box" }, h2({ class: "job-section-title" }, heading), - body ? p({ class: "tribe-side-description" }, ...renderUrl(String(body))) : null, + body ? p({ class: "tribe-side-description" }, ...renderStyledText(String(body))) : null, renderSkillTags(list) ) : null; @@ -163,15 +181,15 @@ exports.cvView = async (cv) => { if (cv.preferences) pushRow(i18n.cvPreferencesLabel, cv.preferences); if (cv.languages) pushRow(i18n.cvLanguagesLabel, String(cv.languages).toUpperCase()); pushRow(i18n.cvAiManaged, cv.aiManaged === false ? i18n.switchOff : `${i18n.switchOn} · ${cv.matchThreshold != null ? cv.matchThreshold : 80}%`); - pushRow(i18n.cvCreatedAt, new Date(cv.createdAt).toLocaleString()); - if (cv.updatedAt) pushRow(i18n.cvUpdatedAt, new Date(cv.updatedAt).toLocaleString()); + pushRow(i18n.cvCreatedAt, moment(cv.createdAt).format("YYYY/MM/DD HH:mm")); + if (cv.updatedAt) pushRow(i18n.cvUpdatedAt, moment(cv.updatedAt).format("YYYY/MM/DD HH:mm")); const cvSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, cv.name || i18n.unnamed || 'Anonymous') ), cv.photo - ? img({ src: `/blob/${encodeURIComponent(cv.photo)}`, class: "cv-photo tribe-detail-image" }) + ? img({ loading: 'lazy', src: `/blob/${encodeURIComponent(cv.photo)}`, class: "cv-photo tribe-detail-image" }) : null, (cv.contact || cv.author) ? div({ class: 'profile-qr' }, @@ -179,6 +197,9 @@ exports.cvView = async (cv) => { img({ class: 'profile-qr-img', src: `/qr/${encodeURIComponent(cv.contact || cv.author)}?size=240`, alt: 'QR' }))) : null, cv.contact ? p(userLink(cv.contact)) : null, + cv.pdf + ? p(a({ href: `/blob/${encodeURIComponent(cv.pdf)}`, target: "_blank", rel: "noopener", class: "filter-btn" }, "📄 " + i18n.cvPdfLabel)) + : null, table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), uniqueSkills.length ? div({ class: "tribe-card-members" }, @@ -210,19 +231,34 @@ exports.cvView = async (cv) => { cv.description ? div({ class: "job-section" }, h2({ class: "job-section-title" }, i18n.cvDescriptionLabel), - p({ class: "tribe-side-description" }, ...renderUrl(String(cv.description))) + p({ class: "tribe-side-description" }, ...renderStyledText(String(cv.description))) ) : null, renderBlock(i18n.cvPersonal, cv.personalExperiences, cv.personalSkills), renderBlock(i18n.cvEducationalView, cv.educationExperiences, cv.educationalSkills), renderBlock(i18n.cvProfessionalView, cv.professionalExperiences, cv.professionalSkills), - renderBlock(i18n.cvOasisContributorView, cv.oasisExperiences, cv.oasisSkills) + renderBlock(i18n.cvOasisContributorView, cv.oasisExperiences, cv.oasisSkills), + Array.isArray(certificates) && certificates.length + ? div({ class: "cv-section-block school-certificates" }, + h2(i18n.schoolCertificates), + certificates.map(cert => + div({ class: "school-certificate" }, + span("🎓 "), + a({ href: `/school/course/${encodeURIComponent(cert.courseId)}` }, cert.courseTitle || cert.courseId), + span(" — "), + userLink(cert.author), + span({ class: "school-certificate-date" }, ` (${moment(cert.createdAt).format("YYYY/MM/DD")})`), + a({ href: `/school/certificate/pdf/${encodeURIComponent(cert.courseId)}/${encodeURIComponent(cert.id)}`, class: "filter-btn school-cert-pdf" }, `⬇ ${i18n.schoolCertificatePdf || "PDF"}`) + ) + ) + ) + : null ); return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.cvDescription) ), diff --git a/src/views/data_view.js b/src/views/data_view.js index 9878c3a..5b37b61 100644 --- a/src/views/data_view.js +++ b/src/views/data_view.js @@ -1,5 +1,5 @@ const { div, h2, p, section, button, form, a, input, span, table, tr, td, ul, li } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink } = require("./main_views"); +const { template, i18n, userLink, renderModuleStats, paged } = require("./main_views"); const KIND_LABEL = { inhabitants: () => i18n.dataKindInhabitants, @@ -19,53 +19,81 @@ const KIND_LABEL = { documents: () => i18n.documentTitle, bookmarks: () => i18n.bookmarkTitle, torrents: () => i18n.typeTorrent, + files: () => i18n.typeFile, chats: () => i18n.chatsTitle, pads: () => i18n.padsTitle, + rooms: () => i18n.roomsTitle, maps: () => i18n.mapTitle, calendars: () => i18n.calendarsTitle, - forum: () => i18n.forumTitle + forum: () => i18n.forumTitle, + school: () => i18n.schoolTitle, + wiki: () => i18n.wikiTitle, + emergencies: () => i18n.emergenciesTitle, + mailing: () => i18n.mailingTitle, + logistics: () => i18n.logisticsTitle, + podcasts: () => i18n.podcastsTitle, + campaigns: () => i18n.campaignsTitle +}; + +const REASON_LABEL = { + mutual: () => i18n.dataReasonMutual, + following: () => i18n.dataReasonFollowing, + supportsYou: () => i18n.dataReasonSupportsYou, + tribe: () => i18n.dataReasonTribe, + alike: () => i18n.dataReasonAlike, + cv: () => i18n.dataReasonCv, + content: () => i18n.dataReasonContent, + pinned: () => i18n.dataReasonPinned, + related: () => i18n.dataReasonRelated, + rated: () => i18n.dataReasonRated, + near: () => i18n.dataReasonNear }; const FILTER_COLUMNS = [ ["ALL", "RECENT", "TOP"], ["INHABITANTS", "TRIBES"], - ["VOTES", "EVENTS", "CALENDARS", "TASKS", "REPORTS"], - ["MARKET", "HOUSING", "JOBS", "PROJECTS", "INDUSTRY"], - ["FORUM", "CHATS", "PADS", "MAPS"], - ["AUDIOS", "BOOKMARKS", "DOCUMENTS", "IMAGES", "TORRENTS", "VIDEOS"] + ["VOTES", "EVENTS", "CALENDARS", "TASKS", "REPORTS", "EMERGENCIES", "MAILING", "CAMPAIGNS"], + ["MARKET", "HOUSING", "JOBS", "PROJECTS", "INDUSTRY", "LOGISTICS"], + ["FORUM", "CHATS", "ROOMS", "PADS", "WIKI", "MAPS", "SCHOOL"], + ["AUDIOS", "BOOKMARKS", "DOCUMENTS", "FILES", "IMAGES", "TORRENTS", "VIDEOS", "PODCASTS"] ]; const kindLabel = (kind) => (KIND_LABEL[kind] ? KIND_LABEL[kind]() : String(kind || '').toUpperCase()); -const filterLabel = (key) => { - const map = { - ALL: i18n.dataFilterAll, MINE: i18n.dataFilterMine, - RECENT: i18n.dataFilterRecent, TOP: i18n.dataFilterTop - }; - return map[key] || kindLabel(key.toLowerCase()); -}; - -const filterButton = (mode, current, q) => +const reasonButton = (value, label, current, q) => form({ method: "GET", action: "/data" }, input({ type: "hidden", name: "q", value: q || "" }), - button({ - type: "submit", name: "filter", value: mode, - class: current === mode ? "filter-btn active" : "filter-btn" - }, String(filterLabel(mode)).toUpperCase()) + value ? input({ type: "hidden", name: "reason", value }) : null, + button({ type: "submit", class: current === value ? "filter-btn active" : "filter-btn" }, String(label).toUpperCase()) ); -const renderFilters = (current, q) => - section( - div({ class: "activity-filter-grid" }, - ...FILTER_COLUMNS.map(col => +const sectionId = (kind) => `data-${String(kind || '').toLowerCase()}`; + +const renderFilters = (q, payload = {}, kindsPresent = []) => { + const matches = Array.isArray(payload.matches) ? payload.matches : []; + const reason = String(payload.reason || ""); + if (!matches.length && !String(q || "").trim() && !reason) return null; + const avail = (Array.isArray(payload.reasonsAvail) ? payload.reasonsAvail : []).filter(r => REASON_LABEL[r]); + const present = new Set(kindsPresent); + const columns = FILTER_COLUMNS.slice(1) + .map(col => col.filter(k => present.has(k.toLowerCase()))) + .filter(col => col.length); + return section( + div({ class: "activity-sub-filter" }, + reasonButton("", i18n.dataFilterAll, reason, q), + ...avail.map(r => reasonButton(r, REASON_LABEL[r](), reason, q)) + ), + reason || !columns.length ? null : div({ class: "activity-filter-grid" }, + ...columns.map(col => div({ class: "activity-filter-col" }, - ...col.map(mode => filterButton(mode, current, q)) + ...col.map(k => a({ href: `#${sectionId(k)}`, class: "filter-btn" }, String(kindLabel(k.toLowerCase())).toUpperCase())) ) ) ), - div({ class: "data-search" }, + div({ class: "data-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(matches.length), form({ method: "GET", action: "/data", class: "filter-box" }, - input({ type: "hidden", name: "filter", value: current }), + reason ? input({ type: "hidden", name: "reason", value: reason }) : null, input({ type: "text", name: "q", value: q || "", placeholder: i18n.dataSearchPlaceholder, class: "filter-box__input" }), div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton) @@ -73,15 +101,16 @@ const renderFilters = (current, q) => ) ) ); +}; -const scorePct = (score) => ((Number(score) || 0) * 100).toFixed(1).replace(/\.0$/, ''); +const scorePct = (score) => ((Number(score) || 0) * 100).toFixed(2).replace(/\.?0+$/, ''); const scaleOf = (pct) => Math.min(4, Math.floor((Number(pct) || 0) / 20)); const renderCohesion = (c) => { const measurable = c.entities >= 2; const pct = measurable ? Number(c.percent) || 0 : 0; return section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.dataCohesionTitle), p(i18n.dataCohesionHint) ), @@ -117,6 +146,12 @@ const renderCohesion = (c) => { ); }; +const TITLE_MAX = 120; +const shortTitle = (title) => { + const t = String(title || "").replace(/\s+/g, " ").trim(); + return t.length > TITLE_MAX ? `${t.slice(0, TITLE_MAX - 1)}…` : t; +}; + const shortId = (id) => { const value = String(id || ""); return value.length > 14 ? `${value.slice(0, 12)}…` : value; @@ -124,6 +159,7 @@ const shortId = (id) => { const renderMatchRow = (m, isBest) => { const common = Array.isArray(m.common) ? m.common : []; + const reasons = (Array.isArray(m.reasons) ? m.reasons : []).filter(r => REASON_LABEL[r]); return div({ class: isBest ? "data-card data-best-card" : "data-card" }, div({ class: "data-side-top" }, span({ class: "data-kind-chip" }, String(kindLabel(m.kind)).toUpperCase()), @@ -131,7 +167,7 @@ const renderMatchRow = (m, isBest) => { span({ class: isBest ? "data-score-value data-best-score" : "data-score-value" }, `${scorePct(m.score)}%`), a({ href: m.href, class: "btn-singleview btn-content data-side-visit", title: i18n.visitContent }, "↗") ), - a({ href: m.href, class: "data-card-title" }, m.title || shortId(m.id)), + a({ href: m.href, class: "data-card-title" }, shortTitle(m.title) || shortId(m.id)), span({ class: "data-card-author" }, userLink(m.author)), common.length ? div({ class: "data-common-row" }, @@ -139,34 +175,65 @@ const renderMatchRow = (m, isBest) => { ...common.slice(0, 8).map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`)) ) + : null, + reasons.length + ? div({ class: "data-reasons-row" }, + ...reasons.map(r => span({ class: "data-kind-chip data-reason-chip" }, REASON_LABEL[r]())) + ) : null ); }; +const groupByKind = (matches) => { + const groups = new Map(); + for (const m of matches) { + if (!groups.has(m.kind)) groups.set(m.kind, []); + groups.get(m.kind).push(m); + } + const ordered = [...groups.entries()].map(([kind, items]) => { + items.sort((x, y) => (y.score || 0) - (x.score || 0) || (y.ts || 0) - (x.ts || 0)); + return { kind, items }; + }); + ordered.sort((x, y) => (y.items[0].score || 0) - (x.items[0].score || 0)); + return ordered; +}; + exports.dataView = async (payload = {}) => { - const filter = String(payload.filter || 'ALL').toUpperCase(); const q = payload.q || ''; + const reason = String(payload.reason || ''); const matches = Array.isArray(payload.matches) ? payload.matches : []; + const emptyData = matches.length === 0 && payload.hasProfile === false && !String(q || '').trim() && !reason; + const groups = groupByKind(matches); + const best = groups.length ? groups[0].items[0] : null; return template( i18n.dataTitle, - section(div({ class: "tags-header" }, h2(i18n.dataTitle), p(i18n.dataDescription))), - renderFilters(filter, q), - payload.cohesion ? renderCohesion(payload.cohesion) : null, + section(div({ class: "tags-header module-header-line" }, h2(i18n.dataTitle), p(i18n.dataDescription))), + renderFilters(q, { ...payload, matches }, groups.map(g => g.kind)), + emptyData ? null : (payload.cohesion ? renderCohesion(payload.cohesion) : null), section( - div({ class: "tags-header" }, + emptyData ? null : div({ class: "tags-header" }, h2(q ? `${i18n.dataTopicTitle} #${q}` : i18n.dataMatchesTitle), - p(q ? i18n.dataTopicHint : i18n.dataMatchesHint), - typeof payload.total === "number" && payload.total > matches.length - ? p({ class: "data-cohesion-hint" }, `${matches.length} / ${payload.total}`) - : null + p(q ? i18n.dataTopicHint : i18n.dataMatchesHint) + ), + best + ? div({ class: "data-section data-top-best" }, + h2({ class: "data-section-title" }, String(i18n.dataBestMatch).toUpperCase()), + div({ class: "data-list" }, renderMatchRow(best, true)) + ) + : null, + ...groupByKind(paged(groups.flatMap(g => g.items))).map(g => + div({ id: sectionId(g.kind), class: "data-section" }, + h2({ class: "data-section-title" }, `${String(kindLabel(g.kind)).toUpperCase()} (${(groups.find(x => x.kind === g.kind) || g).items.length})`), + div({ class: "data-list" }, ...g.items.map((m, idx) => renderMatchRow(m, idx === 0))) + ) ), matches.length - ? div({ class: "data-list" }, ...matches.map((m, idx) => renderMatchRow(m, idx === 0))) - : p({ class: "no-content" }, payload.hasProfile === false + ? null + : div({ class: "no-content-box" }, p({ class: "no-content" }, payload.hasProfile === false ? i18n.dataNoProfile - : i18n.dataNoMatches) + : i18n.dataNoMatches)) ) ); }; diff --git a/src/views/dev_view.js b/src/views/dev_view.js index 90d705f..ccc1468 100644 --- a/src/views/dev_view.js +++ b/src/views/dev_view.js @@ -1,4 +1,4 @@ -const { form, button, div, h2, p, section, input, a, span, ul, li, table, thead, tbody, tr, td, th, pre, code, select, option } = require("../server/node_modules/hyperaxe") +const { form, button, div, h2, p, section, input, a, span, strong, ul, li, table, thead, tbody, tr, td, th, pre, code, select, option } = require("../server/node_modules/hyperaxe") const { template, i18n } = require("./main_views") const FILTERS = [ @@ -41,7 +41,7 @@ const fileHref = (path, from) => `/dev/file?path=${encodeURIComponent(path)}${fr const dirHref = (path) => (path ? `/dev?path=${encodeURIComponent(path)}` : "/dev") const renderHeader = (active) => [ - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.devTitle || "Developer"), p(i18n.devDescription || "Explore the source code running on this node.") ), @@ -68,20 +68,16 @@ const renderPathRow = (parents, currentIsFile, group) => (group : renderBreadcrumb(parents, currentIsFile)) const renderStats = (stats) => { - const bit = (labelKey, fallback, value) => [ - span({ class: "dev-label" }, `${i18n[labelKey] || fallback}: `), - span(String(value)) - ] - return div({ class: "dev-stats" }, - p( - ...bit("devVersion", "Version", stats.version || "-"), span({ class: "dev-sep" }, " · "), - ...bit("devNodeVersion", "Node", stats.node || "-"), span({ class: "dev-sep" }, " · "), - ...bit("devStatsFiles", "Files", fmtCount(stats.files)), span({ class: "dev-sep" }, " · "), - ...bit("devStatsLines", "Lines", fmtCount(stats.lines)), span({ class: "dev-sep" }, " · "), - ...bit("devStatsModules", "Modules", fmtCount(stats.modules)), span({ class: "dev-sep" }, " · "), - ...bit("devStatsLanguages", "Languages", fmtCount(stats.languages)), span({ class: "dev-sep" }, " · "), - ...bit("devStatsTests", "Test suites", fmtCount(stats.testMods)) - ) + const bit = (labelKey, fallback, value) => + span(`${i18n[labelKey] || fallback}: `, strong(String(value))) + return div({ class: "module-stats-line" }, + bit("devVersion", "Version", stats.version || "-"), + bit("devNodeVersion", "Node", stats.node || "-"), + bit("devStatsFiles", "Files", fmtCount(stats.files)), + bit("devStatsLines", "Lines", fmtCount(stats.lines)), + bit("devStatsModules", "Modules", fmtCount(stats.modules)), + bit("devStatsLanguages", "Languages", fmtCount(stats.languages)), + bit("devStatsTests", "Tests", fmtCount(stats.testMods)) ) } @@ -95,7 +91,8 @@ const renderBreadcrumb = (parents, currentIsFile) => div({ class: "dev-breadcrum ]) ) -const renderSearchForm = (query, ext) => div({ class: "dev-search" }, +const renderSearchForm = (query, ext, stats = null) => div({ class: "dev-search activity-filter-chips activity-toolbar-row" }, + stats ? renderStats(stats) : null, form({ method: "GET", action: "/dev/search", class: "filter-box" }, input({ type: "text", name: "q", value: safeText(query), placeholder: i18n.devSearchPlaceholder || "Text to find in the code", minlength: "2", required: true, class: "filter-box__input" }), div({ class: "filter-box__controls" }, @@ -103,7 +100,7 @@ const renderSearchForm = (query, ext) => div({ class: "dev-search" }, option({ value: "", ...(ext ? {} : { selected: true }) }, i18n.devAnyExtension || "Any file"), [".js", ".json", ".css", ".md", ".sh"].map(x => option({ value: x, ...(ext === x ? { selected: true } : {}) }, x)) ), - button({ type: "submit", class: "filter-btn" }, i18n.devFilterSearch || "SEARCH") + button({ type: "submit", class: "filter-box__button" }, i18n.searchButton || "Search") ) ) ) @@ -154,8 +151,7 @@ exports.devTreeView = async (tree, stats, active) => { i18n.devTitle || "Developer", section( ...renderHeader(active || (tree.group ? tree.group : "tree")), - renderStats(stats), - renderSearchForm("", ""), + renderSearchForm("", "", stats), renderPathRow(tree.parents, false, tree.group), renderDesktop(tree) ) diff --git a/src/views/document_view.js b/src/views/document_view.js index 70bcca7..fd7eddc 100644 --- a/src/views/document_view.js +++ b/src/views/document_view.js @@ -3,9 +3,10 @@ const { form, button, div, h2, p, section, input, label, br, a, span, textarea, const { renderCommentsSection: renderSharedCommentsSection, renderCommentsLink } = require("./comments_view"); const moment = require("../server/node_modules/moment"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions , renderSpreadEditWarning } = require("./main_views"); +const { renderLicenseChip, renderLicenseSelect, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require('./clearnet_view'); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions , renderSpreadEditWarning, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const userId = config.keys.id; @@ -28,36 +29,12 @@ const renderTags = (tags) => { const list = safeArr(tags).map((t) => String(t || "").trim()).filter(Boolean); return list.length ? div( - { class: "card-tags" }, + { class: "tribe-side-tags" }, list.map((tag) => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)) ) : null; }; -const renderDocumentActions = (filter, doc, params = {}) => { - const returnTo = buildReturnTo(filter, params); - const isAuthor = String(doc.author) === String(userId); - const hasOpinions = Object.keys(doc.opinions || {}).length > 0; - - return isAuthor - ? div( - { class: "bookmark-actions" }, - !hasOpinions - ? form( - { method: "GET", action: `/documents/edit/${encodeURIComponent(doc.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "update-btn", type: "submit" }, i18n.documentUpdateButton) - ) - : null, - form( - { method: "POST", action: `/documents/delete/${encodeURIComponent(doc.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.documentDeleteButton) - ) - ) - : null; -}; - const renderDocumentCommentsSection = (documentKey, rootId, comments = [], returnTo = null) => { return renderSharedCommentsSection({ action: `/documents/${encodeURIComponent(documentKey)}/comments`, @@ -75,34 +52,35 @@ const renderDocumentList = exports.renderDocumentList = (documents, filter, para const title = safeText(doc.title); const pdfId = safeDomId("pdf-container-", doc.key); - const topbarLeft = - doc.author && String(doc.author) !== String(userId) - ? form( - { method: "GET", action: "/pm" }, - input({ type: "hidden", name: "recipients", value: doc.author }), - button({ type: "submit", class: "filter-btn" }, i18n.documentMessageAuthorButton) - ) - : null; - const isOwn = doc.author && String(doc.author) === String(userId); + const headerActions = typeof params.headerActions === "function" + ? params.headerActions(doc) + : doc.tribeOrigin + ? renderContentActions(null, doc.tribeOrigin.href) + : renderContentActions(doc.key, `/documents/${encodeURIComponent(doc.key)}`, { spread: (params.spreadMap && params.spreadMap.get(doc.key)) || params.spreads || null, author: doc.author, favKind: 'documents', torrentFrom: { blobId: doc.url, name: doc.title }, isFavorite: doc.isFavorite, reportTitle: doc.title, deleteAction: isOwn ? contentDeleteAction('document', doc.key) : undefined, returnTo }); + const titleHref = typeof params.headerActions === "function" ? null : doc.tribeOrigin ? doc.tribeOrigin.href : `/documents/${encodeURIComponent(doc.key)}`; + const engagement = typeof params.engagement === "function" + ? params.engagement(doc) + : doc.tribeOrigin ? null : renderEngagement(doc.key, + renderOpinionsVoting('/documents/opinions', doc.key, doc.opinions, returnTo, doc.opinions_inhabitants), + renderCommentsLink({ href: `/documents/${encodeURIComponent(doc.key)}`, count: commentCount }) + ); return div( { class: "trending-card document-card" + (isOwn ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(doc.key, `/documents/${encodeURIComponent(doc.key)}`, { spread: (params.spreadMap && params.spreadMap.get(doc.key)) || params.spreads || null, author: doc.author, favKind: 'documents', isFavorite: doc.isFavorite, reportTitle: doc.title }) + headerActions ), div( { class: "card-section document-card-body" }, - title ? h2(title) : null, + div({ class: "shop-title-row" }, title ? h2(titleHref ? a({ href: titleHref, class: "card-title-link" }, title) : title) : null, doc.tribeOrigin ? renderTribeOriginChip(doc.tribeOrigin) : doc.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('documents', doc.title, doc.key)) : null, renderLicenseChip(doc.license), ...(typeof params.titleChips === "function" ? params.titleChips(doc) : [])), doc.lifetime ? div({ class: "card-chips-row" }, renderLifespanChip(doc.lifetime, i18n)) : null, doc?.url ? div({ id: pdfId, class: "pdf-viewer-container", "data-pdf-url": `/blob/${encodeURIComponent(doc.url)}` }) : p(i18n.documentNoFile), - renderEngagement(doc.key, - renderOpinionsVoting('/documents/opinions', doc.key, doc.opinions, returnTo, doc.opinions_inhabitants), - renderCommentsLink({ href: `/documents/${encodeURIComponent(doc.key)}`, count: commentCount }) - ), + ...(typeof params.bodyExtra === "function" ? params.bodyExtra(doc) : []), + engagement, br(), (() => { const createdTs = doc.createdAt ? new Date(doc.createdAt).getTime() : NaN; @@ -111,7 +89,7 @@ const renderDocumentList = exports.renderDocumentList = (documents, filter, para return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(doc.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(doc.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(doc.author), showUpdated ? span( @@ -153,18 +131,32 @@ const renderDocumentForm = (filter, documentId, docToEdit, params = {}) => { br(), label(i18n.documentDescriptionLabel), br(), - textarea({ name: "description", placeholder: i18n.documentDescriptionPlaceholder, rows: "4" }, docToEdit?.description || ""), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.documentDescriptionPlaceholder, rows: "4" }, docToEdit?.description || ""), br(), label(i18n.documentTagsLabel), br(), input({ type: "text", name: "tags", placeholder: i18n.documentTagsPlaceholder, value: tagsValue }), br(), + ...renderLicenseSelect(docToEdit?.license, i18n), + br(), + renderClearnetSelector(filter === "edit" ? !!docToEdit?.clearnet : false, i18n), br(), button({ type: "submit" }, filter === "edit" ? i18n.documentUpdateButton : i18n.documentCreateButton) ) ); }; +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.some((x) => !x.tribeOrigin); + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + exports.documentView = async (documents, filter = "all", documentId = null, params = {}) => { if (filter === "edit") params = { ...params, spreadWarning: await renderSpreadEditWarning(documentId) }; const title = i18n.documentTitle; @@ -173,35 +165,33 @@ exports.documentView = async (documents, filter = "all", documentId = null, para const sort = safeText(params.sort || "recent"); const list = safeArr(documents); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); const docToEdit = documentId ? list.find((d) => d.key === documentId) : null; const tpl = template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.documentDescription) ), - (() => { - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetDocuments); - return div({ class: "shop-title-row" }, renderReachChip(isClearnet, i18n)); - })(), - br(), div( { class: "filters" }, form( { method: "GET", action: "/documents", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterRecent).toUpperCase()), - button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterFavorites).toUpperCase() - ), - button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterTop).toUpperCase()), + )] : []), + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterTop).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.documentCreateButton) ) ) @@ -210,8 +200,9 @@ exports.documentView = async (documents, filter = "all", documentId = null, para filter === "create" || filter === "edit" ? renderDocumentForm(filter, documentId, docToEdit || {}, { ...params, filter }) : section( - div( - { class: "documents-search" }, + emptyMod ? null : div( + { class: "documents-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form( { method: "GET", action: "/documents", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -228,15 +219,16 @@ exports.documentView = async (documents, filter = "all", documentId = null, para ) ) ), - div({ class: "documents-list" }, renderDocumentList(list, filter, { q, sort })) + div({ class: "documents-list" }, renderDocumentList(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) ) ) ); - return `${tpl}`; + return `${tpl}`; }; exports.singleDocumentView = async (doc, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); const q = safeText(params.q || ""); const sort = safeText(params.sort || "recent"); const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q, sort }); @@ -245,8 +237,7 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = const pdfId = safeDomId("pdf-container-", doc.key); const isAuthor = String(doc.author) === String(userId); const hasOpinions = Object.keys(doc.opinions || {}).length > 0; - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetDocuments); + const isClearnet = !!doc.clearnet; const chips = [ renderLifespanChip(doc.lifetime, i18n), @@ -254,13 +245,6 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = ].filter(Boolean); const sideActions = []; - if (doc.author && String(doc.author) !== String(userId)) { - sideActions.push(form( - { method: "GET", action: "/pm" }, - input({ type: "hidden", name: "recipients", value: doc.author }), - button({ type: "submit", class: "filter-btn" }, i18n.documentMessageAuthorButton) - )); - } if (isAuthor && !hasOpinions) { sideActions.push(form( { method: "GET", action: `/documents/edit/${encodeURIComponent(doc.key)}` }, @@ -268,35 +252,31 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = button({ class: "update-btn", type: "submit" }, i18n.documentUpdateButton) )); } - if (isAuthor) { - sideActions.push(form( - { method: "POST", action: `/documents/delete/${encodeURIComponent(doc.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.documentDeleteButton) - )); - } const tagsNode = renderTags(doc.tags); const detailActions = div({ class: "card-header activity-card-header" }, - renderContentActions(doc.key, null, { + renderContentActions(doc.key, `/documents/${encodeURIComponent(doc.key)}`, { author: doc.author, - favKind: 'documents', + favKind: 'documents', torrentFrom: { blobId: doc.url, name: doc.title }, isFavorite: doc.isFavorite, spread: params.spreads || null, - returnTo, - reportTitle: doc.title + returnTo: buildReturnTo(filter, { q, sort }), + reportTitle: doc.title, + deleteAction: isAuthor ? contentDeleteAction('document', doc.key) : undefined }) ); const docSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, title ? h2({ class: "tribe-card-title" }, title) : null, - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('documents', doc.title, doc.key) : null), + isAuthor ? renderClearnetSwitch('documents', doc.rootId || doc.key, isClearnet) : null, + renderLicenseChip(doc.license) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, safeText(doc.description) - ? p({ class: "tribe-side-description" }, ...renderUrl(doc.description)) + ? p({ class: "tribe-side-description" }, ...renderStyledText(doc.description)) : null, tagsNode, sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null @@ -314,7 +294,7 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(doc.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(doc.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(doc.author), showUpdated ? span( @@ -333,7 +313,7 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = const tpl = template( i18n.documentTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.documentAllSectionTitle || i18n.documentTitle), p(i18n.documentDescription) ), @@ -344,12 +324,12 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterRecent).toUpperCase()), - button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterFavorites).toUpperCase() - ), + )] : []), button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.documentFilterTop).toUpperCase()), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.documentCreateButton) ) @@ -358,6 +338,6 @@ exports.singleDocumentView = async (doc, filter = "all", comments = [], params = ) ); - return `${tpl}`; + return `${tpl}`; }; diff --git a/src/views/emergencies_view.js b/src/views/emergencies_view.js new file mode 100644 index 0000000..22a1ba4 --- /dev/null +++ b/src/views/emergencies_view.js @@ -0,0 +1,308 @@ +const { div, h2, h3, p, section, button, form, a, span, br, textarea, input, label, select, option, img, video: videoHyperaxe, table, tr, td } = require("../server/node_modules/hyperaxe"); +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderContentActions, renderSubscriptionBox, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view"); +const { renderCommentsSection } = require("./comments_view"); +const { renderMapEmbed } = require("./maps_view"); +const { renderStyledText } = require("../backend/renderStyledText"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); + +const userId = config.keys.id; +const CATEGORIES = ["WEATHER", "INFRASTRUCTURE", "HEALTH", "SECURITY", "LOST", "NEIGHBORHOOD"]; +const SEVERITIES = ["UNVERIFIED", "LOW", "MEDIUM", "HIGH"]; +const STATUSES = ["ACTIVE", "RESOLVED", "EXPIRED"]; +const BASE_FILTERS = ["ALL", "RECENT", "MINE"]; +const EXPIRES = ["1d", "3d", "7d", "30d"]; + +const catLabel = (c) => String(i18n[`emergencyCategory${c.charAt(0) + c.slice(1).toLowerCase()}`] || c).toUpperCase(); +const sevLabel = (s) => String(i18n[`emergencySeverity${s.charAt(0) + s.slice(1).toLowerCase()}`] || s).toUpperCase(); +const statusLabel = (s) => String(i18n[`emergencyStatus${s.charAt(0) + s.slice(1).toLowerCase()}`] || s).toUpperCase(); +const baseLabel = (f) => String(i18n[`emergencyFilter${f.charAt(0) + f.slice(1).toLowerCase()}`] || f).toUpperCase(); + +const severityChip = (emergency) => span({ class: `emergency-severity emergency-severity-${String(emergency.severity || "UNVERIFIED").toLowerCase()}` }, `${sevLabel(emergency.severity)} · ${emergency.confirmationCount || 0}`); +const statusChip = (emergency) => renderStateChip(emergency.status === "ACTIVE" ? "mutuals" : "closed", "", statusLabel(emergency.status)); +const categoryChip = (emergency) => renderStateChip("neutral", "", catLabel(emergency.category)); +const clearnetEligible = (emergency) => String(emergency.status || "ACTIVE").toUpperCase() === "ACTIVE" && !emergency.tribeId && !emergency.encrypted; + +const renderTags = (tags) => (Array.isArray(tags) && tags.length) + ? div({ class: "card-tags" }, ...tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) + : null; + +const emergencyHref = (emergency) => `/emergencies/${encodeURIComponent(emergency.id)}`; + +const renderEmergencyCard = (emergency, params = {}) => + div({ class: `tribe-card emergency-card emergency-card-${String(emergency.severity || "UNVERIFIED").toLowerCase()}` }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(emergency.id, emergencyHref(emergency), { author: emergency.author, favKind: "emergencies", isFavorite: emergency.isFavorite, reportTitle: emergency.title, spread: (params.spreadMap && params.spreadMap.get(emergency.id)) || null, deleteAction: String(emergency.author) === String(userId) ? contentDeleteAction("emergency", emergency.id) : undefined }) + ), + div({ class: "tribe-card-body" }, + emergency.media && emergency.media.kind === "image" ? a({ href: emergencyHref(emergency) }, img({ loading: 'lazy', class: "emergency-card-cover", src: `/blob/${encodeURIComponent(emergency.media.blobId)}`, alt: emergency.title || "" })) : null, + emergency.media && emergency.media.kind === "video" ? videoHyperaxe({ class: "emergency-card-cover", src: `/blob/${encodeURIComponent(emergency.media.blobId)}`, controls: true, preload: "metadata" }) : null, + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: emergencyHref(emergency) }, emergency.title || "—"))), + div({ class: "card-chips-row" }, severityChip(emergency), statusChip(emergency), categoryChip(emergency), emergency.clearnet === true && clearnetEligible(emergency) ? renderReachChip(true, i18n, clearnetItemHref("emergencies", emergency.title, emergency.id)) : null), + emergency.expiresAt ? p({ class: "job-meta-line" }, `${i18n.emergencyExpiresAt}: ${moment(emergency.expiresAt).format("YYYY/MM/DD HH:mm")}`) : null + ) + ); + +const chipVisible = (mode, filter, census) => { + if (mode === filter || mode === "ALL") return true; + if (mode === "MINE") return census.some(al => String(al.author) === String(userId)); + if (mode === "RECENT") return census.length > 0; + if (STATUSES.includes(mode)) return census.some(al => al.status === mode); + if (SEVERITIES.includes(mode)) return census.some(al => al.severity === mode); + if (CATEGORIES.includes(mode)) return census.some(al => al.category === mode); + return census.length > 0; +}; + +const chipButtons = (modes, filter, census, labelOf) => + modes.filter(m => chipVisible(m, filter, census)).map(m => button({ type: "submit", name: "filter", value: m, class: filter === m ? "filter-btn active" : "filter-btn" }, labelOf(m))); + +const renderFilters = (filter, q, census, emptyMod) => + section( + div({ class: "filters" }, + form({ method: "GET", action: "/emergencies", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q || "" }), + ...(emptyMod ? [] : [ + ...chipButtons(BASE_FILTERS, filter, census, baseLabel), + ...chipButtons(SEVERITIES, filter, census, sevLabel), + ...chipButtons(STATUSES, filter, census, statusLabel), + ...chipButtons(CATEGORIES, filter, census, catLabel) + ]), + button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.emergencyCreate) + ) + ) + ); + +const renderForm = (emergency, params = {}) => + div({ class: "div-center audio-form" }, + h2(emergency ? i18n.emergencyUpdateSectionTitle : i18n.emergencyCreateSectionTitle), + form({ method: "POST", action: emergency ? `/emergencies/update/${encodeURIComponent(emergency.id)}` : "/emergencies/create", enctype: "multipart/form-data" }, + label(i18n.emergencyTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.emergencyTitlePlaceholder, value: emergency ? emergency.title : "" }), br(), br(), + label(i18n.emergencyCategoryLabel), br(), + select({ name: "category" }, ...CATEGORIES.map(c => option({ value: c, ...((emergency ? emergency.category : "NEIGHBORHOOD") === c ? { selected: true } : {}) }, catLabel(c)))), br(), br(), + label(i18n.emergencyTextLabel), br(), + textarea({ name: "text", rows: 8, maxlength: "5000", placeholder: i18n.emergencyTextPlaceholder }, emergency ? emergency.text : ""), br(), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + label(i18n.mapLocationTitle || "Map Location"), br(), + input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: emergency ? emergency.mapUrl : "" }), br(), + label(i18n.emergencyExpiresLabel), br(), + select({ name: "expiresIn" }, + emergency && emergency.expiresAt ? option({ value: "keep", selected: true }, i18n.emergencyExpiresKeep) : null, + option({ value: "", ...(!(emergency && emergency.expiresAt) ? { selected: true } : {}) }, i18n.emergencyExpiresNone), + ...EXPIRES.map(e => option({ value: e }, i18n[`emergencyExpires${e}`] || e)) + ), br(), br(), + label(i18n.emergencyTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.emergencyTagsPlaceholder, value: emergency ? emergency.tags.join(", ") : "" }), br(), br(), + ...(!emergency || clearnetEligible(emergency) ? [renderClearnetSelector(!!emergency && emergency.clearnet === true, i18n), br()] : []), + button({ type: "submit", class: "create-button" }, emergency ? i18n.emergencyUpdate : i18n.emergencyCreate) + ) + ); + +const renderHeader = () => div({ class: "tags-header module-header-line" }, h2(i18n.emergenciesTitle), p(i18n.emergenciesDescription)); + +exports.emergenciesView = async (emergencies, filter = "ALL", params = {}) => { + const list = Array.isArray(emergencies) ? emergencies : []; + const q = String(params.q || "").trim(); + const census = Array.isArray(params.censusList) ? params.censusList : list; + const f = String(filter || "ALL").toUpperCase(); + const isForm = f === "CREATE" || f === "EDIT"; + const emptyMod = !isForm && moduleIsEmpty(census, "ALL", "ALL", q); + return template( + i18n.emergenciesTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.emergenciesTitle), p(i18n.emergenciesDescription)), + renderFilters(isForm ? "ALL" : f, q, isForm ? [] : census, emptyMod) + ), + section( + isForm + ? renderForm(f === "EDIT" ? params.emergency : null, params) + : [ + emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length, SEVERITIES.map(s => ({ label: sevLabel(s), count: list.filter(al => al.severity === s).length }))), + form({ method: "GET", action: "/emergencies", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: f }), + input({ type: "text", name: "q", value: q, placeholder: i18n.emergencySearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ), + div({ class: "tribe-grid" }, list.length ? paged(list).map(al => renderEmergencyCard(al, params)) : p(i18n.emergencyNoItems)) + ] + ) + ); +}; + +exports.singleEmergencyView = async (emergency, params = {}) => { + const isAuthor = String(emergency.author) === String(userId); + const canClearnet = clearnetEligible(emergency); + const isClearnet = canClearnet && !!emergency.clearnet; + const confirmed = Array.isArray(emergency.confirmations) && emergency.confirmations.includes(userId); + const href = emergencyHref(emergency); + const census = Array.isArray(params.censusList) ? params.censusList : []; + const sideActions = [ + !isAuthor && !confirmed && emergency.status === "ACTIVE" + ? form({ method: "POST", action: `/emergencies/confirm/${encodeURIComponent(emergency.id)}` }, button({ type: "submit", class: "tribe-action-btn" }, `✓ ${String(i18n.emergencyConfirmButton).toUpperCase()}`)) + : null, + confirmed ? span({ class: "status supporting" }, i18n.emergencyConfirmed) : null + ].filter(Boolean); + const ownerActions = isAuthor + ? [ + (emergency.confirmationCount || 0) > 0 + ? null + : form({ method: "GET", action: "/emergencies" }, input({ type: "hidden", name: "filter", value: "edit" }), input({ type: "hidden", name: "id", value: emergency.id }), button({ type: "submit", class: "update-btn" }, i18n.emergencyUpdate)) + ].filter(Boolean) + : []; + const updates = Array.isArray(emergency.updates) ? emergency.updates : []; + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(emergency.id, href, { author: emergency.author, favKind: "emergencies", isFavorite: emergency.isFavorite, reportTitle: emergency.title, spread: params.spread || null, returnTo: href, deleteAction: isAuthor ? contentDeleteAction("emergency", emergency.id) : undefined }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, emergency.title)), + div({ class: "card-chips-row" }, + severityChip(emergency), statusChip(emergency), categoryChip(emergency), + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref("emergencies", emergency.title, emergency.id) : null), + isAuthor && canClearnet ? renderClearnetSwitch("emergencies", emergency.rootId || emergency.id, isClearnet) : null + ), + table({ class: "tribe-info-table jobs-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), + td({ class: "tribe-info-value", colspan: "3" }, moment(emergency.createdAt).format("YYYY/MM/DD HH:mm")) + ), + emergency.expiresAt + ? tr( + td({ class: "tribe-info-label" }, i18n.emergencyExpiresAt), + td({ class: "tribe-info-value", colspan: "3" }, moment(emergency.expiresAt).format("YYYY/MM/DD HH:mm")) + ) + : null, + tr( + td({ class: "tribe-info-value", colspan: "4" }, userLink(emergency.author)) + ) + ), + div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.emergencyConfirmations}: ${emergency.confirmationCount || 0}`) + ), + renderMapEmbed(params.mapData, emergency.mapUrl), + sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, + isAuthor && emergency.status !== "EXPIRED" + ? div({ class: "tribe-side-actions housing-status-row" }, + span({ class: "card-label" }, `${i18n.statusLabel || "Status"}: `), + statusChip(emergency), + form({ method: "POST", action: `/emergencies/status/${encodeURIComponent(emergency.id)}`, class: "inline-form" }, + button({ class: "tribe-action-btn", type: "submit", name: "status", value: emergency.status === "ACTIVE" ? "RESOLVED" : "ACTIVE" }, + String(emergency.status === "ACTIVE" ? i18n.emergencyResolveButton : i18n.emergencyReopenButton).toUpperCase()) + ) + ) + : null, + div({ class: "doc-export-actions" }, + form({ method: "GET", action: `/emergencies/${encodeURIComponent(emergency.id)}/pdf` }, button({ type: "submit", class: "filter-btn" }, i18n.emergencyGenerateReport)), + form({ method: "POST", action: `/emergencies/${encodeURIComponent(emergency.id)}/share` }, button({ type: "submit", class: "filter-btn" }, i18n.sharePm)) + ), + renderTags(emergency.tags), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null, + params.subscription + ? renderSubscriptionBox({ target: emergency.id, scope: "emergencies", subscribed: params.subscription.subscribed, count: params.subscription.count, isOwner: isAuthor, returnTo: href }) + : null + ); + const main = div({ class: "tribe-main" }, + emergency.text ? div({ class: "emergency-body" }, ...renderStyledText(emergency.text)) : null, + div({ class: "card-section emergency-updates" }, + h3(i18n.emergencyUpdatesTitle), + updates.length + ? div({ class: "emergency-updates-list" }, ...updates.map(u => { + const mineUpdate = String(u.author) === String(userId); + const confirmedUpdate = Array.isArray(u.confirmations) && u.confirmations.includes(userId); + const editing = params.editUpdate && params.editUpdate === u.id; + return div({ class: "emergency-update", id: `update-${encodeURIComponent(u.id)}` }, + div({ class: "emergency-update-head" }, + div({ class: "card-chips-row" }, + renderStateChip(u.confirmationCount > 0 ? "mutuals" : "neutral", "✓", `${i18n.emergencyConfirmations} · ${u.confirmationCount || 0}`), + u.edited ? renderStateChip("neutral", "", i18n.emergencyUpdateEdited) : null + ), + div({ class: "tribe-side-actions emergency-update-actions" }, + !mineUpdate && !confirmedUpdate && emergency.status === "ACTIVE" + ? form({ method: "POST", action: `/emergencies/updates/${encodeURIComponent(u.id)}/confirm` }, input({ type: "hidden", name: "emergency", value: emergency.id }), button({ type: "submit", class: "tribe-action-btn" }, `✓ ${String(i18n.emergencyConfirmButton).toUpperCase()}`)) + : null, + confirmedUpdate ? span({ class: "status supporting" }, i18n.emergencyConfirmed) : null, + mineUpdate && !editing && u.confirmationCount === 0 + ? form({ method: "GET", action: `${href}#update-${encodeURIComponent(u.id)}` }, input({ type: "hidden", name: "editUpdate", value: u.id }), button({ type: "submit", class: "update-btn" }, i18n.emergencyUpdate)) + : null, + mineUpdate + ? form({ method: "POST", action: `/emergencies/updates/${encodeURIComponent(u.id)}/delete` }, button({ type: "submit", class: "delete-btn" }, i18n.emergencyDelete)) + : null + ), + ), + editing + ? form({ method: "POST", action: `/emergencies/updates/${encodeURIComponent(u.id)}/edit`, enctype: "multipart/form-data", class: "emergency-update-form" }, + textarea({ name: "text", rows: 3, maxlength: "2000", required: true, autofocus: true }, u.text), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.emergencyUpdate) + ) + : div({ class: "emergency-update-text" }, ...renderStyledText(u.text)), + p({ class: "card-footer" }, span({ class: "date-link" }, `${moment(u.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(u.author)) + ); + })) + : p(i18n.emergencyNoUpdates), + isAuthor && emergency.status === "ACTIVE" + ? form({ method: "POST", action: `/emergencies/updates/${encodeURIComponent(emergency.id)}`, enctype: "multipart/form-data", class: "emergency-update-form" }, + textarea({ name: "text", rows: 3, maxlength: "2000", required: true, placeholder: i18n.emergencyUpdatePlaceholder }), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.emergencyAddUpdate) + ) + : null + ), + renderCommentsSection({ action: `/emergencies/${encodeURIComponent(emergency.id)}/comments`, comments: params.comments || [], returnTo: href }) + ); + return template( + emergency.title, + section(renderHeader(), renderFilters("ALL", "", census, census.length === 0)), + section(div({ class: "tribe-details" }, side, main)) + ); +}; + +exports.clearnetEmergencyView = async (emergency, params = {}) => { + const { escapeHtml: esc, renderRichText, renderKindTag, renderTagChips, blobUrl: cnBlob, renderClearnetPage } = require("./clearnet_view"); + const title = esc(emergency.title || i18n.emergencyLabel); + const desc = renderRichText(emergency.text || ""); + const severity = String(emergency.severity || "UNVERIFIED").toUpperCase(); + const point = params.mapPoint || null; + const cover = emergency.media && emergency.media.kind === "image" ? cnBlob(emergency.media.blobId) : null; + const updates = (Array.isArray(emergency.updates) ? emergency.updates : []).filter(u => String(u.author) === String(emergency.author)); + const fmtDate = (v) => esc(moment(v).format("YYYY/MM/DD HH:mm")); + const extraCss = ` +.cn-emergency-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700} +.cn-emergency-meta{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px} +.cn-emergency-meta-item{background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;padding:8px 14px;font-size:14px;color:var(--fg-soft);display:inline-flex;align-items:center;gap:6px} +.cn-emergency-severity-medium,.cn-emergency-severity-high{border-color:var(--fg);color:var(--fg);font-weight:700} +.cn-emergency-desc{color:var(--fg-soft);line-height:1.6;font-size:15px;margin:0 0 20px 0;word-break:break-word} +.cn-emergency-update{border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin:12px 0;background:var(--bg-elev)} +.cn-emergency-update-date{color:var(--fg-dim);font-size:12px;margin-bottom:6px} +.cn-emergency-update-text{color:var(--fg-soft);line-height:1.5;word-break:break-word} +`; + const body = ` +

${title}

+
+ ${renderKindTag("emergency")} + ⚠ ${esc(sevLabel(severity))} + ✓ ${esc(i18n.emergencyConfirmations)}: ${Number(emergency.confirmationCount) || 0} + ${esc(catLabel(String(emergency.category || "NEIGHBORHOOD")))} + ${emergency.createdAt ? `📅 ${fmtDate(emergency.createdAt)}` : ""} + ${emergency.expiresAt ? `⏳ ${esc(i18n.emergencyExpiresAt)}: ${fmtDate(emergency.expiresAt)}` : ""} + ${point ? `📍 ${point.title ? `${esc(point.title)} · ` : ""}${point.lat}, ${point.lng}` : ""} +
+
+ ${desc ? `
${desc}
` : ""} + ${updates.length ? `

${esc(String(i18n.emergencyUpdatesTitle || "").replace(/[::]\s*$/, ""))}

${updates.map(u => `
${fmtDate(u.updatedAt || u.createdAt)}
${renderRichText(u.text || "")}
`).join("")}` : ""} + ${renderTagChips(emergency.tags)} +`; + return renderClearnetPage({ + title: `${emergency.title || i18n.emergencyLabel} | Oasis`, + ogTitle: emergency.title || i18n.emergencyLabel, + ogDescription: String(emergency.text || "").replace(/!?\[[^\]]*\]\(\s*&[^)\s]+\s*\)/g, "").trim(), + ogImage: cover, + extraCss, + body, + hubFeedId: emergency.author || null + }); +}; diff --git a/src/views/event_view.js b/src/views/event_view.js index 6b16f48..220277c 100644 --- a/src/views/event_view.js +++ b/src/views/event_view.js @@ -1,12 +1,13 @@ -const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, table, tr, td, details, summary, ul, li } = require("../server/node_modules/hyperaxe"); +const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, table, tr, td, details, summary, ul, li, hr } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderOpenClosedChip, renderPrivacyChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderDocumentActions } = require("./main_views"); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderOpenClosedChip, renderPrivacyChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderDocumentActions, renderInviteQrCard, renderSubscriptionBox, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, contentDeleteAction, paged, renderCreatedRows } = require("./main_views"); const { renderPhotoGallery, renderGalleryFields } = require("./gallery_view"); const { renderIntervalBlock } = require("./calendars_view"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText, safeExternalHref } = require("../backend/renderStyledText"); const { renderMapLocationUrl, renderMapEmbed, renderMapLocationVisitLabel } = require("./maps_view"); +const { renderReachChip, renderEncryptedChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require("./clearnet_view"); const userId = config.keys.id; @@ -32,12 +33,6 @@ const normalizePrivacy = (v) => { const privacyLabel = (v) => (normalizePrivacy(v) === "private" ? i18n.eventPrivate : i18n.eventPublic); -const safeExternalHref = (url) => { - const s = String(url || "").trim(); - const lower = s.toLowerCase(); - if (lower.startsWith("http://") || lower.startsWith("https://") || lower.startsWith("mailto:")) return s; - return ""; -}; const normalizeEventStatus = (v) => { const up = String(v || "").toUpperCase(); @@ -45,6 +40,8 @@ const normalizeEventStatus = (v) => { return up || "OPEN"; }; +const clearnetEligible = (e) => normalizeEventStatus(e.status) !== "CLOSED" && normalizePrivacy(e.isPublic) === "public" && !e.encrypted; + const eventStatusLabel = (v) => { const st = normalizeEventStatus(v); if (st === "OPEN") return i18n.eventStatusOpen; @@ -62,11 +59,6 @@ const renderEventOwnerActions = (e, returnTo) => { { method: "GET", action: `/events/edit/${encodeURIComponent(e.id)}` }, input({ type: "hidden", name: "returnTo", value: returnTo }), button({ type: "submit", class: "update-btn" }, i18n.eventUpdateButton) - ), - form( - { method: "POST", action: `/events/delete/${encodeURIComponent(e.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ type: "submit", class: "delete-btn" }, i18n.eventDeleteButton) ) ]; if (normalizePrivacy(e.isPublic) === "private") { @@ -77,7 +69,8 @@ const renderEventOwnerActions = (e, returnTo) => { if (e.openInviteCode) { actions.push(div({ class: "tribe-open-invite" }, span({ class: "card-label" }, i18n.tribeInviteCodeText), - span({ class: "tribe-open-invite-code" }, e.openInviteCode) + span({ class: "tribe-open-invite-code" }, e.openInviteCode), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/events/${encodeURIComponent(e.openInviteCode)}` }) )); actions.push(form( { method: "POST", action: `/events/open-invite/remove/${encodeURIComponent(e.id)}` }, @@ -98,7 +91,7 @@ const renderEventAttendAction = (e, isAttending, returnTo) => { if (st !== "OPEN") return null; if (e.organizer === userId) return null; if (normalizePrivacy(e.isPublic) === "private" && !isAttending) { - return a({ class: "tribe-action-btn", href: "/invites#invites-events" }, i18n.tribeEnterInvite); + return a({ class: "tribe-action-btn", href: "/invites?filter=others" }, i18n.tribeEnterInvite); } return form( { method: "POST", action: `/events/attend/${encodeURIComponent(e.id)}` }, @@ -121,56 +114,83 @@ const renderEventStatusChip = (status) => { return renderOpenClosedChip(status, { statusChipOPEN: localized, statusChipCLOSED: localized }); }; -const renderEventItem = exports.renderEventItem = (e, filter, spreadInfo) => { +const renderEventItem = exports.renderEventItem = (e, filter, spreadInfo, opts = {}) => { const currentFilter = filter || "all"; const attendees = safeArray(e.attendees); const isPrivate = normalizePrivacy(e.isPublic) === "private"; const isAttending = attendees.includes(userId); const price = parseFloat(e.price || 0); + const origin = e.tribeOrigin || null; + const href = opts.href !== undefined ? opts.href : (origin ? origin.href : `/events/${encodeURIComponent(e.id)}`); const chips = [ - renderEventStatusChip(e.status), renderPrivacyChip(isPrivate, i18n), + renderEventStatusChip(e.status), e.encrypted ? renderStateChip("encrypted", "🔒", i18n.encryptedChipLabel || "E2E") : null, isAttending ? renderStateChip("whole", "★", i18n.eventAttended) : null, - renderLifespanChip(e.lifetime, i18n) + renderLifespanChip(e.lifetime, i18n), + !origin && e.clearnet === true && clearnetEligible(e) ? renderReachChip(true, i18n, clearnetItemHref('events', e.title, e.id)) : null, + origin ? null : e.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (e.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null), + origin ? renderTribeOriginChip(origin) : null, + ...(Array.isArray(opts.extraChips) ? opts.extraChips : []) ].filter(Boolean); const dateText = e.date ? moment(e.date).format("YYYY/MM/DD HH:mm") : ""; const isOwn = e.organizer && String(e.organizer) === String(userId); + const headerActions = opts.headerActions !== undefined + ? opts.headerActions + : origin ? null : renderContentActions(e.id, `/events/${encodeURIComponent(e.id)}?filter=${encodeURIComponent(currentFilter)}`, { spread: spreadInfo || null, author: e.organizer || e.author, favKind: 'events', isFavorite: e.isFavorite, reportTitle: e.title, deleteAction: isOwn ? contentDeleteAction('event', e.id) : undefined, returnTo: `/events?filter=${encodeURIComponent(currentFilter)}` }); return div({ class: "trending-card event-card" + (isOwn ? " own-content" : "") }, - div( + headerActions ? div( { class: "card-header activity-card-header" }, span(), - renderContentActions(e.id, `/events/${encodeURIComponent(e.id)}?filter=${encodeURIComponent(currentFilter)}`, { spread: spreadInfo || null, author: e.organizer || e.author, favKind: 'events', isFavorite: e.isFavorite, reportTitle: e.title }) - ), + headerActions + ) : null, div({ class: "card-section event-card-body" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, - a({ href: `/events/${encodeURIComponent(e.id)}` }, e.title || i18n.eventsTitle) + href ? a({ href }, e.title || i18n.eventsTitle) : (e.title || i18n.eventsTitle) ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - dateText ? p({ class: "card-date-highlight" }, dateText) : null, e.location && String(e.location).trim() - ? p({ class: "job-meta-line" }, String(e.location)) + ? div({ class: "card-field card-field-mb" }, + span({ class: "card-label" }, `${String(i18n.eventLocation).toUpperCase()}: `), + span({ class: "card-value" }, String(e.location)) + ) : null, + dateText ? p({ class: "time-chip" }, dateText) : null, price > 0 - ? div({ class: "job-price-line card-salary" }, `${price.toFixed(6)} ECO`) + ? div({ class: "price-chip" }, `${price.toFixed(6)} ECO`) : null, - div({ class: "tribe-card-members" }, + origin ? null : div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.eventAttendees}: ${attendees.length}`) - ) + ), + ...(Array.isArray(opts.bodyExtra) ? opts.bodyExtra.filter(Boolean) : []) ) ); }; +const eventChipFor = (currentFilter, censusEvents) => (mode) => { + if (mode === currentFilter) return true; + if (!Array.isArray(censusEvents)) return true; + const pubE = (e) => normalizePrivacy(e.isPublic) === "public"; + if (mode === "mine") return censusEvents.some((e) => e.organizer === userId); + if (mode === "recent") return censusEvents.length > 0; + if (mode === "today") return censusEvents.some((e) => pubE(e) && moment(e.date).isSame(moment(), "day")); + if (mode === "week") return censusEvents.some((e) => pubE(e) && moment(e.date).isBetween(moment(), moment().add(7, "days"), null, "[]")); + if (mode === "month") return censusEvents.some((e) => pubE(e) && moment(e.date).isBetween(moment(), moment().add(1, "month"), null, "[]")); + if (mode === "year") return censusEvents.some((e) => pubE(e) && moment(e.date).isBetween(moment(), moment().add(1, "year"), null, "[]")); + if (mode === "archived") return censusEvents.some((e) => pubE(e) && normalizeEventStatus(e.status) === "CLOSED"); + return true; +}; + exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { const list = Array.isArray(events) ? events : [events]; const currentFilter = filter || "all"; - const { renderReachChip: renderReachChipEvents } = require('./clearnet_view'); - const viewerClearnetEvents = !!(params.viewerPrefs && params.viewerPrefs.clearnetEvents); const title = i18n.eventsTitle; @@ -187,10 +207,13 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { }; const visible = list.filter(canSee); + const censusEvents = (Array.isArray(params.censusList) ? params.censusList : list).filter(canSee); let filtered; if (currentFilter === "all") { filtered = visible.filter((e) => normalizePrivacy(e.isPublic) === "public"); + } else if (currentFilter === "recent") { + filtered = visible.filter((e) => normalizePrivacy(e.isPublic) === "public" && (Date.parse(e.createdAt || "") || 0) >= Date.now() - 86400000); } else if (currentFilter === "mine") { filtered = visible.filter((e) => e.organizer === userId); } else if (currentFilter === "today") { @@ -212,7 +235,12 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { const minCreate = moment().add(1, "minute").format("YYYY-MM-DDTHH:mm"); const ret = typeof returnTo === "string" && returnTo.startsWith("/events") ? returnTo : "/events?filter=mine"; - const editPrivacy = normalizePrivacy(formData.isPublic); + const emptyMod = moduleIsEmpty(censusEvents, currentFilter, "all", params.q); + const pubE = (e) => normalizePrivacy(e.isPublic) === "public"; + const eventChipVisible = eventChipFor(currentFilter, censusEvents); + const reachRaw = String(params.reach || "").toLowerCase(); + const editPrivacy = reachRaw === "public" || reachRaw === "private" ? reachRaw : normalizePrivacy(formData.isPublic); + const showClearnet = editPrivacy === "public" && clearnetEligible({ ...formData, isPublic: editPrivacy }); const editInterval = formData.interval || (formData.intervalWeekly ? "weekly" : formData.intervalMonthly ? "monthly" : formData.intervalYearly ? "yearly" : ""); const editIntervalUntil = (formData.recurrenceUntil || formData.intervalDeadline) @@ -222,29 +250,31 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.eventsTitle), p(i18n.eventsDescription) ), - div({ class: "shop-title-row" }, renderReachChipEvents(viewerClearnetEvents, i18n)), - br(), div( { class: "filters" }, form( { method: "GET", action: "/events" }, + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: currentFilter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "today", class: currentFilter === "today" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterToday).toUpperCase()), - button({ type: "submit", name: "filter", value: "week", class: currentFilter === "week" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterWeek).toUpperCase()), - button({ type: "submit", name: "filter", value: "month", class: currentFilter === "month" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMonth).toUpperCase()), - button({ type: "submit", name: "filter", value: "year", class: currentFilter === "year" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterYear).toUpperCase()), - button({ type: "submit", name: "filter", value: "archived", class: currentFilter === "archived" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterArchived).toUpperCase()), + ...(eventChipVisible("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: currentFilter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterRecent).toUpperCase())] : []), + ...(eventChipVisible("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMine).toUpperCase())] : []), + ...(eventChipVisible("today") ? [button({ type: "submit", name: "filter", value: "today", class: currentFilter === "today" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterToday).toUpperCase())] : []), + ...(eventChipVisible("week") ? [button({ type: "submit", name: "filter", value: "week", class: currentFilter === "week" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterWeek).toUpperCase())] : []), + ...(eventChipVisible("month") ? [button({ type: "submit", name: "filter", value: "month", class: currentFilter === "month" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMonth).toUpperCase())] : []), + ...(eventChipVisible("year") ? [button({ type: "submit", name: "filter", value: "year", class: currentFilter === "year" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterYear).toUpperCase())] : []), + ...(eventChipVisible("archived") ? [button({ type: "submit", name: "filter", value: "archived", class: currentFilter === "archived" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterArchived).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.eventCreateButton) ) ), currentFilter === "edit" || currentFilter === "create" ? null - : div({ class: "filters" }, + : emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filtered, e => normalizeEventStatus(e.status), [{ value: 'OPEN', label: i18n.eventStatusOpen }, { value: 'CLOSED', label: i18n.eventStatusClosed }]), form({ method: "GET", action: "/events", class: "filter-box" }, input({ type: "hidden", name: "filter", value: currentFilter }), input({ type: "text", name: "q", value: params.q || "", placeholder: i18n.eventSearchPlaceholder, class: "filter-box__input" }), @@ -259,6 +289,23 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { ? div( { class: "event-form" }, spreadWarning, + form( + { method: "GET", action: currentFilter === "edit" ? `/events/edit/${encodeURIComponent(eventId)}` : "/events" }, + currentFilter === "edit" ? null : input({ type: "hidden", name: "filter", value: "create" }), + typeof returnTo === "string" && returnTo.startsWith("/events") ? input({ type: "hidden", name: "returnTo", value: returnTo }) : null, + label(i18n.eventPrivacyLabel), + br(), + div({ class: "apply-row" }, + select( + { name: "isPublic", class: "report-category-select" }, + opt("public", editPrivacy !== "private", i18n.eventPublic), + opt("private", editPrivacy === "private", i18n.eventPrivate) + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, privacyLabel(editPrivacy)), form( { action: currentFilter === "edit" ? `/events/update/${encodeURIComponent(eventId)}` : "/events/create", @@ -266,6 +313,7 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: ret }), + input({ type: "hidden", name: "isPublic", value: editPrivacy }), label(i18n.eventTitleLabel), br(), input({ @@ -284,6 +332,8 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { ), br(), ...renderGalleryFields(formData, currentFilter === "edit"), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob" }), br(), br(), br(), label(i18n.eventDateLabel), br(), @@ -292,7 +342,7 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { name: "date", id: "date", required: true, - min: currentFilter === "create" ? minCreate : undefined, + min: minCreate, value: formData.date ? moment(formData.date).format("YYYY-MM-DDTHH:mm") : "" }), br(), @@ -327,11 +377,12 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { step: "0.000001", value: parseFloat(formData.price || 0).toFixed(6) }), + renderEcoValueChip(), br(), br(), label(i18n.eventTagsLabel), br(), - input({ type: "text", name: "tags", id: "tags", value: editTags.join(", ") }), + input({ type: "text", name: "tags", id: "tags", placeholder: i18n.tagsPlaceholder, value: editTags.join(", ") }), br(), br(), div({ class: "event-recurrence-block" }, @@ -340,15 +391,7 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { renderIntervalBlock({ min: minCreate }, editInterval, editIntervalUntil) ), br(), - label(i18n.eventPrivacyLabel), - br(), - select( - { name: "isPublic", id: "isPublic" }, - opt("public", editPrivacy !== "private", i18n.eventPublic), - opt("private", editPrivacy === "private", i18n.eventPrivate) - ), - br(), - br(), + showClearnet ? renderClearnetSelector(formData.clearnet === true || String(formData.clearnet || "") === "1", i18n) : null, ...(currentFilter === "create" ? [ input({ type: "hidden", name: "addToCalendar", value: "0" }), label(i18n.eventAddToCalendar || "Add to Calendar"), @@ -361,13 +404,14 @@ exports.eventView = async (events, filter, eventId, returnTo, params = {}) => { ) ) : filtered.length > 0 - ? div({ class: "jobs-grid" }, filtered.map((e) => renderEventItem(e, currentFilter, params.spreadMap && params.spreadMap.get(e.id)))) - : p(i18n.noevents) + ? div({ class: "jobs-grid" }, paged(filtered).map((e) => renderEventItem(e, currentFilter, params.spreadMap && params.spreadMap.get(e.id)))) + : div({ class: "no-content-box" }, p(i18n.noevents)) ) ); }; exports.singleEventView = async (event, filter, comments = [], params = {}) => { + const eventChipVisible = (mode) => eventChipFor(currentFilter, Array.isArray(params.censusList) ? params.censusList : null)(mode); const currentFilter = filter || "all"; const commentCount = typeof event.commentCount === "number" ? event.commentCount : 0; const attendees = safeArray(event.attendees); @@ -382,12 +426,13 @@ exports.singleEventView = async (event, filter, comments = [], params = {}) => { form( { method: "GET", action: "/events" }, button({ type: "submit", name: "filter", value: "all", class: currentFilter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "today", class: currentFilter === "today" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterToday).toUpperCase()), - button({ type: "submit", name: "filter", value: "week", class: currentFilter === "week" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterWeek).toUpperCase()), - button({ type: "submit", name: "filter", value: "month", class: currentFilter === "month" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMonth).toUpperCase()), - button({ type: "submit", name: "filter", value: "year", class: currentFilter === "year" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterYear).toUpperCase()), - button({ type: "submit", name: "filter", value: "archived", class: currentFilter === "archived" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterArchived).toUpperCase()), + ...(eventChipVisible("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: currentFilter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterRecent).toUpperCase())] : []), + ...(eventChipVisible("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMine).toUpperCase())] : []), + ...(eventChipVisible("today") ? [button({ type: "submit", name: "filter", value: "today", class: currentFilter === "today" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterToday).toUpperCase())] : []), + ...(eventChipVisible("week") ? [button({ type: "submit", name: "filter", value: "week", class: currentFilter === "week" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterWeek).toUpperCase())] : []), + ...(eventChipVisible("month") ? [button({ type: "submit", name: "filter", value: "month", class: currentFilter === "month" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterMonth).toUpperCase())] : []), + ...(eventChipVisible("year") ? [button({ type: "submit", name: "filter", value: "year", class: currentFilter === "year" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterYear).toUpperCase())] : []), + ...(eventChipVisible("archived") ? [button({ type: "submit", name: "filter", value: "archived", class: currentFilter === "archived" ? "filter-btn active" : "filter-btn" }, String(i18n.eventFilterArchived).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.eventCreateButton) ) ); @@ -399,21 +444,28 @@ exports.singleEventView = async (event, filter, comments = [], params = {}) => { ); } - const { renderReachChip, renderEncryptedChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetEvents && normalizeEventStatus(event.status) !== 'CLOSED' && normalizePrivacy(event.isPublic) === 'public'); + const canClearnet = clearnetEligible(event); + const isClearnet = canClearnet && !!event.clearnet; const isPrivate = normalizePrivacy(event.isPublic) === 'private'; const isEncrypted = !!event.encrypted || isPrivate; const isAttending = attendees.includes(userId); const isOrganizer = String(event.organizer) === String(userId); + const isEventOwner = String(event.organizer || event.author) === String(userId); + const sharesEvent = isEventOwner || attendees.includes(userId); + const eventSubIn = isEventOwner || (event.subscription && event.subscription.subscribed === true); const returnToSelf = `/events/${encodeURIComponent(event.id)}?filter=${encodeURIComponent(currentFilter)}`; const chips = [ - renderEventStatusChip(event.status), renderPrivacyChip(isPrivate, i18n), - isEncrypted ? renderEncryptedChip(i18n) : renderReachChip(isClearnet, i18n), + renderEventStatusChip(event.status), + isEncrypted ? renderEncryptedChip(i18n) : renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('events', event.title, event.id) : null), + isOrganizer && canClearnet ? renderClearnetSwitch('events', event.rootId || event.id, isClearnet) : null, isAttending ? renderStateChip("whole", "★", i18n.eventAttended) : null, renderLifespanChip(event.lifetime, i18n), - renderEcoTax(event.msgSize, event.id) + renderEcoTax(event.msgSize, event.id), + (event.subscription && sharesEvent) + ? renderStateChip(eventSubIn ? "mutuals" : "closed", "✉", eventSubIn ? i18n.subscriptionOn : i18n.subscriptionOff) + : null ].filter(Boolean); const sideActions = []; @@ -463,14 +515,14 @@ exports.singleEventView = async (event, filter, comments = [], params = {}) => { const eventSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(event.id, null, { spread: params.spreads || null, author: event.organizer || event.author, favKind: 'events', isFavorite: event.isFavorite, reportTitle: event.title }) + renderContentActions(event.id, returnToSelf, { spread: params.spreads || null, author: event.organizer || event.author, favKind: 'events', isFavorite: event.isFavorite, reportTitle: event.title, deleteAction: isOrganizer ? contentDeleteAction('event', event.id) : undefined, returnTo: `/events?filter=${encodeURIComponent(currentFilter)}` }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, event.title) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, renderPhotoGallery(event, 'event'), - table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), + table({ class: "tribe-info-table jobs-info-table" }, ...infoRows, ...renderCreatedRows(event.createdAt, event.organizer)), event.recurring && Array.isArray(event.occurrences) && event.occurrences.length > 1 ? details({ class: "event-occurrences" }, summary({ class: "chat-thread-summary" }, `${i18n.eventUpcomingDates} (${event.occurrences.length})`), @@ -486,6 +538,16 @@ exports.singleEventView = async (event, filter, comments = [], params = {}) => { attendeesListNode, sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null, + (event.subscription && sharesEvent) + ? renderSubscriptionBox({ + target: event.id, + scope: "events", + subscribed: event.subscription.subscribed === true, + count: event.subscription.count, + isOwner: isEventOwner, + returnTo: returnToSelf + }) + : null, renderDocumentActions('events', event.id) ); @@ -496,21 +558,17 @@ exports.singleEventView = async (event, filter, comments = [], params = {}) => { event.description ? div({ class: "job-section" }, h2({ class: "job-section-title" }, i18n.eventDescriptionLabel), - p({ class: "tribe-side-description" }, ...renderUrl(event.description)) + p({ class: "tribe-side-description" }, ...renderStyledText(event.description)) ) : null, event.mapUrl ? div({ class: "job-section" }, renderMapEmbed(params.mapData, event.mapUrl)) : null, - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(event.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(event.organizer) - ), renderEngagement(event.id, opinionsBar, renderEventCommentsSection(event.id, comments, currentFilter)) ); return template( event.title, section( - div({ class: "tags-header" }, h2(i18n.eventsTitle), p(i18n.eventsDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.eventsTitle), p(i18n.eventsDescription)), filterBar, div({ class: "tribe-details" }, eventSide, eventMain) ) @@ -518,9 +576,9 @@ exports.singleEventView = async (event, filter, comments = [], params = {}) => { }; exports.clearnetEventView = async (event) => { - const { escapeHtml: esc, renderClearnetPage } = require('./clearnet_view'); - const title = esc(event.title || 'Event'); - const desc = esc(event.description || ''); + const { escapeHtml: esc, renderRichText, renderKindTag, renderClearnetPage } = require('./clearnet_view'); + const title = esc(event.title || i18n.cnKindEvent); + const desc = renderRichText(event.description || ''); const dateStr = event.date ? esc(moment(event.date).format("YYYY/MM/DD HH:mm")) : ''; const loc = esc(event.location || ''); const price = parseFloat(event.price || 0); @@ -537,15 +595,17 @@ exports.clearnetEventView = async (event) => { const body = `

${title}

+ ${renderKindTag('event')} ${dateStr ? `📅 ${dateStr}` : ''} ${loc ? `📍 ${loc}` : ''} ${priceStr ? `💰 ${priceStr}` : ''}
+
${desc ? `

${desc}

` : ''} - ${urlHref ? `More info →` : ''} + ${urlHref ? `${esc(i18n.cnMoreInfo)}` : ''} `; return renderClearnetPage({ - title: `${event.title || 'Event'} — Oasis`, + title: `${event.title || 'Event'} | Oasis`, ogTitle: event.title || 'Event', ogDescription: event.description || '', extraCss, diff --git a/src/views/favorites_view.js b/src/views/favorites_view.js index 0260a3c..4642562 100644 --- a/src/views/favorites_view.js +++ b/src/views/favorites_view.js @@ -1,15 +1,17 @@ const { form, button, div, h2, p, section, input, a, span, img } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderContentActions } = require("./main_views"); +const { template, i18n, userLink, renderContentActions, renderModuleStats, CONTENT_FAV_KIND, CONTENT_TORRENTABLE, paged } = require("./main_views"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText, safeExternalHref } = require("../backend/renderStyledText"); const userId = config.keys.id; const safeArr = (v) => (Array.isArray(v) ? v : []); const safeText = (v) => String(v || "").trim(); +const TORRENTABLE_KINDS = new Set(Object.keys(CONTENT_FAV_KIND).filter((t) => CONTENT_TORRENTABLE.has(t)).map((t) => CONTENT_FAV_KIND[t])); + const FILTER_KINDS = [ { value: "audios", label: () => i18n.favoritesFilterAudios }, { value: "bookmarks", label: () => i18n.favoritesFilterBookmarks }, @@ -17,6 +19,7 @@ const FILTER_KINDS = [ { value: "chats", label: () => i18n.favoritesFilterChats }, { value: "documents", label: () => i18n.favoritesFilterDocuments }, { value: "events", label: () => i18n.favoritesFilterEvents }, + { value: "files", label: () => i18n.favoritesFilterFiles }, { value: "forum", label: () => i18n.favoritesFilterForum }, { value: "housing", label: () => i18n.favoritesFilterHousing }, { value: "images", label: () => i18n.favoritesFilterImages }, @@ -26,6 +29,7 @@ const FILTER_KINDS = [ { value: "pads", label: () => i18n.favoritesFilterPads }, { value: "projects", label: () => i18n.favoritesFilterProjects }, { value: "reports", label: () => i18n.favoritesFilterReports }, + { value: "rooms", label: () => i18n.favoritesFilterRooms }, { value: "shopProducts", label: () => i18n.favoritesFilterShopProducts }, { value: "tasks", label: () => i18n.favoritesFilterTasks }, { value: "torrents", label: () => i18n.favoritesFilterTorrents }, @@ -95,7 +99,7 @@ const renderBookmarkUrl = (item) => { if (!item.url) return null; return p( a( - { href: item.url, target: "_blank", rel: "noreferrer noopener", class: "bookmark-url" }, + { href: safeExternalHref(item.url), target: "_blank", rel: "noreferrer noopener", class: "bookmark-url" }, item.url ) ); @@ -119,7 +123,7 @@ const renderImagePreview = (item) => { ); }; -const renderFavoriteCard = (item, filter) => { +const renderFavoriteCard = (item, filter, spreadMap = null) => { const returnTo = buildReturnTo(filter); const title = safeText(item.title) || safeText(item.name) || safeText(item.category) || safeText(item.url) || ""; @@ -137,49 +141,48 @@ const renderFavoriteCard = (item, filter) => { span({ class: 'pm-exposition-text' }, String(item.kind || '').toUpperCase()) ) ), - renderContentActions(item.favId, item.viewHref) + renderContentActions(item.favId, item.viewHref, { + author: item.author, + spread: (spreadMap && spreadMap.get(item.favId)) || null, + reportTitle: title, + ...(TORRENTABLE_KINDS.has(item.kind) && typeof item.url === "string" && item.url.startsWith("&") ? { torrentFrom: { blobId: item.url, name: item.title } } : {}) + }) ), div( { class: "card-section favorites-card-body" }, - div( - { class: "bookmark-topbar" }, - div( - { class: "bookmark-topbar-left" }, - form( - { - method: "POST", - action: `/favorites/remove/${encodeURIComponent(item.kind)}/${encodeURIComponent(item.favId)}`, - class: "bookmark-favorite-form" - }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ type: "submit", class: "filter-btn" }, i18n.favoritesRemoveButton) - ) - ) - ), - title ? h2(title) : null, + title ? div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, item.viewHref ? a({ href: item.viewHref }, title) : title)) : null, renderImagePreview(item), renderBookmarkUrl(item), - safeText(item.description) ? p(...renderUrl(item.description)) : null, + safeText(item.description) ? p(...renderStyledText(item.description)) : null, renderDetailFields(item), renderTags(item.tags), p( - { class: "card-footer" }, - absDate ? span({ class: "date-link" }, `${absDate} ${i18n.performed} `) : "", + { class: "card-footer favorites-meta" }, + absDate ? span({ class: "date-link" }, `${absDate}`) : "", item.author ? userLink(item.author) : "" + ), + p( + { class: "card-footer" }, + form( + { method: "POST", action: `/favorites/remove/${encodeURIComponent(item.kind)}/${encodeURIComponent(item.favId)}`, class: "phone-action-form" }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ type: "submit", class: "tribe-action-btn" }, String(i18n.agendaDiscardButton).toUpperCase()) + ) ) ) ); }; -exports.favoritesView = async (items, filter = "all", counts = {}, q = "") => { +exports.favoritesView = async (items, filter = "all", counts = {}, q = "", params = {}) => { const c = counts || {}; const total = typeof c.all === "number" ? c.all : safeArr(items).length; + const emptyFav = total === 0 && !String(q || "").trim(); return template( i18n.favoritesTitle, section( - div({ class: "tags-header" }, h2(i18n.favoritesTitle), p(i18n.favoritesDescription)), - div( + div({ class: "tags-header module-header-line" }, h2(i18n.favoritesTitle), p(i18n.favoritesDescription)), + emptyFav ? null : div( { class: "filters" }, form( { method: "GET", action: "/favorites", class: "ui-toolbar ui-toolbar--filters" }, @@ -187,11 +190,13 @@ exports.favoritesView = async (items, filter = "all", counts = {}, q = "") => { { type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, `${i18n.favoritesFilterAll} (${total})` ), - button( - { type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, - `${i18n.favoritesFilterRecent} (${total})` - ), - ...FILTER_KINDS.map((k) => + (c.recent || 0) > 0 || filter === "recent" + ? button( + { type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, + `${i18n.favoritesFilterRecent} (${c.recent || 0})` + ) + : null, + ...FILTER_KINDS.filter((k) => (c[k.value] || 0) > 0).map((k) => button( { type: "submit", name: "filter", value: k.value, class: filter === k.value ? "filter-btn active" : "filter-btn" }, `${k.label()} (${c[k.value] || 0})` @@ -199,8 +204,9 @@ exports.favoritesView = async (items, filter = "all", counts = {}, q = "") => { ) ) ), - div( - { class: "filters" }, + emptyFav ? null : div( + { class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(items.length), form( { method: "GET", action: "/favorites", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -210,7 +216,7 @@ exports.favoritesView = async (items, filter = "all", counts = {}, q = "") => { ) ) ), - div({ class: "bookmark-list" }, safeArr(items).length ? safeArr(items).map((it) => renderFavoriteCard(it, filter)) : p(i18n.favoritesNoItems)) + div({ class: "bookmark-list" }, safeArr(items).length ? paged(safeArr(items)).map((it) => renderFavoriteCard(it, filter, params.spreadMap)) : p(i18n.favoritesNoItems)) ) ); }; diff --git a/src/views/fediverse_view.js b/src/views/fediverse_view.js index dcbd33d..580eab7 100644 --- a/src/views/fediverse_view.js +++ b/src/views/fediverse_view.js @@ -1,5 +1,7 @@ -const { div, h2, h3, p, section, button, form, a, span, strong, input, label, img, textarea, br, hr, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe"); +const { div, h2, h3, h4, p, section, button, form, a, span, strong, input, label, img, textarea, br, hr, select, option, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe"); +const RAW_HTML = Symbol.for('oasis.rawHtml'); const { template, i18n } = require("./main_views"); +const { renderZoomableImage } = require("./gallery_view"); const { sanitizeHtml } = require('../backend/sanitizeHtml'); const moment = require("../server/node_modules/moment"); @@ -17,7 +19,7 @@ const renderMedia = (m) => { if (m.type === 'audio') { return audioHyperaxe({ class: "post-audio", src: m.url, controls: true }); } - return img({ class: "post-image", src: m.url, alt: m.description || "" }); + return renderZoomableImage(m.url, { imgClass: "post-image", alt: m.description || "" }); }; const renderComposeForm = (opts = {}) => { @@ -26,7 +28,7 @@ const renderComposeForm = (opts = {}) => { form( { method: "POST", action, enctype: "multipart/form-data" }, replyToId ? input({ type: "hidden", name: "inReplyToId", value: replyToId }) : "", - textarea({ name: "text", id: "text", rows: "6", class: "publish-textarea", placeholder: placeholder || i18n.fediverseComposePlaceholder }), + textarea({ maxlength: "5000", name: "text", id: "text", rows: "6", class: "publish-textarea", placeholder: placeholder || i18n.fediverseComposePlaceholder }), br(), label({ for: "fediverse_media" }, i18n.fediverseAttach), br(), @@ -67,7 +69,7 @@ const renderPost = (post, returnTo, opts = {}) => { ), span({ class: "fediverse-date" }, fmtDate(post.createdAt)) ), - div({ class: "feed-text", innerHTML: sanitizeHtml(post.html) }), + div({ class: "feed-text", [RAW_HTML]: sanitizeHtml(post.html) }), post.media && post.media.length ? div({ class: "fediverse-media" }, post.media.map(renderMedia)) : "", opts.noActions ? "" : renderActions(post, returnTo) ); @@ -90,7 +92,7 @@ const mastodonBox = (account, stats, actions, showLabel) => { ), actions && actions.length ? div({ class: "fediverse-compact-actions" }, actions) : "" ), - (stats && stats.bio) ? div({ class: "fediverse-bio", innerHTML: sanitizeHtml(stats.bio) }) : "", + (stats && stats.bio) ? div({ class: "fediverse-bio", [RAW_HTML]: sanitizeHtml(stats.bio) }) : "", stats ? div({ class: "fediverse-stats" }, stat(i18n.fediverseFollowers, stats.followers), @@ -102,7 +104,7 @@ const mastodonBox = (account, stats, actions, showLabel) => { (stats && Array.isArray(stats.fields) && stats.fields.length) ? div({ class: "fediverse-fields" }, stats.fields.map(f => div({ class: "fediverse-field" }, span({ class: "fediverse-field-name" }, f.name, f.verified ? span({ class: "fediverse-field-verified", title: "verified" }, " ✓") : ""), - span({ class: "fediverse-field-value", innerHTML: sanitizeHtml(f.value) }) + span({ class: "fediverse-field-value", [RAW_HTML]: sanitizeHtml(f.value) }) ))) : "" ) @@ -111,7 +113,7 @@ const mastodonBox = (account, stats, actions, showLabel) => { const disconnectedBlock = () => { const text = String(i18n.fediverseDisconnected || ""); - const settingsLink = a({ href: "/settings#multiverse" }, i18n.fediverseEmptyLink || "settings"); + const settingsLink = a({ href: "/settings?filter=multiverse" }, i18n.fediverseEmptyLink || "settings"); const parts = text.split("%LINK%"); const para = parts.length > 1 ? p(parts[0], settingsLink, parts.slice(1).join("%LINK%")) @@ -119,16 +121,322 @@ const disconnectedBlock = () => { return div({ class: "fediverse-disconnected" }, h2(i18n.fediverseStatus), para); }; -exports.fediverseOverviewView = ({ account, stats } = {}) => { - const box = account - ? mastodonBox(account, stats, [ - form({ method: "GET", action: "/fediverse/mastodon" }, button({ type: "submit", class: "filter-btn" }, i18n.fediverseManage)) - ], true) - : null; + +const telegramBox = (account, stats, actions, showLabel) => { + const stat = (lbl, val) => div({ class: "fediverse-stat" }, span({ class: "fediverse-stat-label" }, lbl), strong(String(val))); + const handle = account.username ? `@${account.username}` : (account.phone || ""); + return div({ class: "fediverse-network" }, + showLabel ? h3("Telegram") : "", + div({ class: "fediverse-profile" }, + div({ class: "fediverse-namerow" }, + account.avatar ? img({ class: "fediverse-avatar", src: account.avatar, alt: account.displayName }) : "", + div({ class: "fediverse-profile-id" }, + div({ class: "fediverse-name" }, account.displayName || handle), + span({ class: "fediverse-acct" }, handle) + ), + actions && actions.length ? div({ class: "fediverse-compact-actions" }, actions) : "" + ), + stats + ? div({ class: "fediverse-stats" }, + stat(i18n.telegramChats, stats.chats), + stat(i18n.telegramUnread, stats.unread) + ) + : "" + ) + ); +}; + +const telegramKindLabel = (kind) => kind === 'channel' ? i18n.telegramChannel : kind === 'group' ? i18n.telegramGroup : i18n.telegramPrivate; + +const renderTelegramMedia = (m) => { + if (!m || !m.url) return ""; + if (m.kind === 'photo') return renderZoomableImage(m.url, { imgClass: "post-image", alt: m.name || "" }); + if (m.kind === 'video') return videoHyperaxe({ class: "post-video", src: m.url, controls: true, preload: "metadata" }); + if (m.kind === 'audio') return audioHyperaxe({ class: "post-audio", src: m.url, controls: true }); + return a({ href: m.url, class: "filter-btn" }, `📎 ${m.name || m.mime || "file"}`); +}; + +const renderTelegramDialog = (d) => { + const href = `/fediverse/telegram/chat/${encodeURIComponent(d.id)}#telegram-latest`; + const preview = d.last ? ((d.last.out ? "→ " : "") + (d.last.text || (d.last.hasMedia ? "📎" : ""))) : ""; + return div({ class: "feed-card fediverse-card telegram-dialog" + (d.unread > 0 ? " telegram-unread" : "") }, + div({ class: "fediverse-head" }, + img({ class: "fediverse-avatar", src: d.avatar, alt: "" }), + div({ class: "fediverse-author" }, + a({ class: "fediverse-name", href }, d.title), + span({ class: "fediverse-acct" }, telegramKindLabel(d.kind)) + ), + d.unread > 0 ? span({ class: "telegram-unread-badge" }, String(d.unread)) : "", + span({ class: "fediverse-date" }, fmtDate(d.date)) + ), + preview ? p({ class: "telegram-preview" }, preview.length > 160 ? `${preview.slice(0, 160)}…` : preview) : "", + div({ class: "fediverse-actions" }, + form({ method: "GET", action: href }, button({ type: "submit", class: "filter-btn" }, i18n.telegramOpen)) + ) + ); +}; + +const renderTelegramMessage = (m) => div({ class: "feed-card fediverse-card telegram-message" + (m.out ? " telegram-out" : "") }, + div({ class: "fediverse-head" }, + span({ class: "fediverse-name" }, m.out ? i18n.telegramYou : (m.senderName || "")), + span({ class: "fediverse-date" }, fmtDate(m.date)) + ), + m.text ? div({ class: "feed-text telegram-text" }, m.text) : "", + m.media ? div({ class: "fediverse-media" }, renderTelegramMedia(m.media)) : "" +); + +exports.telegramDialogsView = ({ account, stats, dialogs, error } = {}) => { + if (!account) return exports.fediverseOverviewView({ account: null }); + const body = []; + body.push(div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription))); + body.push(telegramBox(account, stats, [ + a({ href: "/fediverse", class: "filter-btn" }, `← ${i18n.fediverse}`), + a({ href: "/fediverse/telegram?refresh=1", class: "filter-btn" }, i18n.fediverseRefresh) + ], false)); + if (error) body.push(div({ class: "fediverse-error" }, i18n[error] || i18n.telegramErrConnect)); + const list = Array.isArray(dialogs) ? dialogs : []; + if (!list.length && !error) body.push(p({ class: "muted" }, i18n.telegramNoChats)); + else body.push(section({ class: "feed-container" }, list.map(renderTelegramDialog))); + return template(i18n.fediverse, section(...body)); +}; + +exports.telegramChatView = ({ account, stats, chat, error } = {}) => { + if (!account) return exports.fediverseOverviewView({ account: null }); + const header = section( + div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), + telegramBox(account, stats, [ + a({ href: "/fediverse/telegram", class: "filter-btn" }, `← ${i18n.telegramChats}`) + ], false) + ); + if (!chat) return template(i18n.fediverse, header, section(div({ class: "fediverse-error" }, i18n[error] || i18n.telegramErrFetch))); + const body = []; + body.push(div({ class: "telegram-chat-title" }, + h3(chat.title), + span({ class: "fediverse-acct" }, telegramKindLabel(chat.kind)), + a({ href: "#telegram-latest", class: "filter-btn chat-jump-latest" }, i18n.chatJumpLatest) + )); + if (error) body.push(div({ class: "fediverse-error" }, i18n[error] || i18n.telegramErrConnect)); + const list = Array.isArray(chat.messages) ? chat.messages : []; + if (!list.length) body.push(p({ class: "muted" }, i18n.telegramNoMessages)); + else body.push(section({ class: "feed-container" }, + list.map((m, idx) => { + const node = renderTelegramMessage(m); + return idx === list.length - 1 ? div({ id: "telegram-latest", class: "chat-latest-anchor" }, node) : node; + }) + )); + body.push(br(), hr(), br()); + body.push(div({ class: "publish-form" }, + form({ method: "POST", action: `/fediverse/telegram/chat/${encodeURIComponent(chat.id)}/send`, enctype: "multipart/form-data" }, + textarea({ maxlength: "4096", name: "text", rows: "4", class: "publish-textarea", placeholder: i18n.telegramComposePlaceholder }), + br(), + label({ for: "telegram_media" }, i18n.fediverseAttach), br(), + input({ type: "file", id: "telegram_media", name: "media", accept: "image/*,video/*,audio/*,application/pdf" }), + br(), br(), + button({ type: "submit", class: "filter-btn" }, i18n.telegramSend) + ) + )); + return template(i18n.fediverse, header, section(...body)); +}; + + +const fmtDuration = (sec) => { + const n = Math.max(0, Math.floor(Number(sec) || 0)); + const h = Math.floor(n / 3600), m = Math.floor((n % 3600) / 60), sInt = n % 60; + const mm = String(m).padStart(h ? 2 : 1, "0"), ss = String(sInt).padStart(2, "0"); + return h ? `${h}:${mm}:${ss}` : `${mm}:${ss}`; +}; + +const fmtBytes = (n) => { + const b = Number(n) || 0; + if (b >= 1024 ** 3) return `${(b / 1024 ** 3).toFixed(1)} GB`; + if (b >= 1024 ** 2) return `${(b / 1024 ** 2).toFixed(1)} MB`; + if (b >= 1024) return `${(b / 1024).toFixed(0)} KB`; + return `${b} B`; +}; + +const peertubeBox = (account, stats, actions, showLabel) => { + const stat = (lbl, val) => div({ class: "fediverse-stat" }, span({ class: "fediverse-stat-label" }, lbl), strong(String(val))); + return div({ class: "fediverse-network" }, + showLabel ? h3("PeerTube") : "", + div({ class: "fediverse-profile" }, + div({ class: "fediverse-namerow" }, + account.avatar ? img({ class: "fediverse-avatar", src: account.avatar, alt: account.displayName }) : "", + div({ class: "fediverse-profile-id" }, + div({ class: "fediverse-name" }, account.displayName || account.handle), + a({ class: "fediverse-acct", href: account.channelUrl || account.profileUrl, target: "_blank", rel: "noopener noreferrer" }, account.handle) + ), + actions && actions.length ? div({ class: "fediverse-compact-actions" }, actions) : "" + ), + stats + ? div({ class: "fediverse-stats" }, + stat(i18n.peertubeVideos, stats.videos), + stat(i18n.peertubeSubscriptions, stats.subscriptions), + stat(i18n.peertubeFollowers, stats.followers), + stats.quota > 0 ? stat(i18n.peertubeQuota, `${fmtBytes(stats.quotaUsed)} / ${fmtBytes(stats.quota)}`) : "" + ) + : "" + ) + ); +}; + +const peertubeHeader = (account, stats, actions) => section( + div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), + peertubeBox(account, stats, actions, false) +); + +const renderPeertubeVideo = (v) => { + const href = `/fediverse/peertube/video/${encodeURIComponent(v.id)}`; + const ch = v.channel || {}; + const chips = []; + if (v.privacyId && v.privacyId !== 1 && v.privacy) chips.push(span({ class: "card-label" }, v.privacy)); + if (v.state && !/published/i.test(v.state)) chips.push(span({ class: "card-label" }, v.state)); + return div({ class: "feed-card fediverse-card peertube-card" }, + a({ href, class: "peertube-thumb" }, + v.thumbnail ? img({ src: v.thumbnail, alt: v.name, loading: "lazy" }) : div({ class: "peertube-thumb-empty" }), + v.duration ? span({ class: "peertube-duration" }, fmtDuration(v.duration)) : "" + ), + div({ class: "peertube-card-body" }, + a({ href, class: "peertube-title" }, v.name || v.id), + div({ class: "peertube-meta" }, + ch.avatar ? img({ class: "peertube-channel-avatar", src: ch.avatar, alt: "" }) : "", + span({ class: "fediverse-acct" }, ch.displayName || ch.handle || ""), + span({ class: "fediverse-date" }, fmtDate(v.publishedAt)), + span(`${v.views} ${i18n.peertubeViews} · ${v.likes} ${i18n.peertubeLikes}`) + ), + chips.length ? div({ class: "card-chips-row" }, ...chips) : "" + ) + ); +}; + +exports.peertubeFeedView = ({ account, stats, videos, kind, error, notice } = {}) => { + if (!account) return exports.fediverseOverviewView({ account: null }); + const current = kind === "mine" ? "mine" : "subscriptions"; + const body = []; + if (error) body.push(div({ class: "fediverse-error" }, i18n[error] || i18n.peertubeErrConnect)); + if (notice) body.push(div({ class: "feed-success-msg" }, p(i18n[notice] || String(notice)))); + body.push(div({ class: "mode-buttons-row" }, + form({ method: "GET", action: "/fediverse/peertube" }, + button({ type: "submit", name: "filter", value: "subscriptions", class: current === "subscriptions" ? "filter-btn active" : "filter-btn" }, String(i18n.peertubeSubscriptions).toUpperCase()), + button({ type: "submit", name: "filter", value: "mine", class: current === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.peertubeMyVideos).toUpperCase()) + ) + )); + const list = Array.isArray(videos) ? videos : []; + if (!list.length && !error) body.push(p({ class: "muted" }, i18n.peertubeNoVideos)); + else body.push(section({ class: "peertube-grid" }, list.map(renderPeertubeVideo))); + return template(i18n.fediverse, peertubeHeader(account, stats, [ + a({ href: "/fediverse", class: "filter-btn" }, `← ${i18n.fediverse}`), + a({ href: `/fediverse/peertube?filter=${current}&refresh=1`, class: "filter-btn" }, i18n.fediverseRefresh), + a({ href: "/fediverse/peertube/upload", class: "filter-btn" }, i18n.peertubeUpload) + ]), section(...body)); +}; + +const renderPeertubeComment = (c) => { + const acc = c.account || {}; + return div({ class: "feed-card fediverse-card" }, + div({ class: "fediverse-head" }, + acc.avatar ? img({ class: "fediverse-avatar", src: acc.avatar, alt: acc.displayName }) : "", + div({ class: "fediverse-author" }, + span({ class: "fediverse-name" }, acc.displayName || acc.name), + span({ class: "fediverse-acct" }, acc.name ? `@${acc.name}${acc.host ? "@" + acc.host : ""}` : "") + ), + span({ class: "fediverse-date" }, fmtDate(c.createdAt)) + ), + div({ class: "feed-text", [RAW_HTML]: sanitizeHtml(c.html) }) + ); +}; + +exports.peertubeVideoView = ({ account, stats, video, comments, myRating, error } = {}) => { + if (!account) return exports.fediverseOverviewView({ account: null }); + const header = peertubeHeader(account, stats, [a({ href: "/fediverse/peertube", class: "filter-btn" }, `← ${i18n.peertubeVideos}`)]); + if (!video) return template(i18n.fediverse, header, section(div({ class: "fediverse-error" }, i18n[error] || i18n.peertubeErrFetch))); + const id = encodeURIComponent(video.id); + const ch = video.channel || {}; + const liked = myRating === "like"; + const body = []; + if (error) body.push(div({ class: "fediverse-error" }, i18n[error] || i18n.peertubeErrConnect)); + body.push(div({ class: "feed-card fediverse-card peertube-watch" }, + video.stream + ? videoHyperaxe({ class: "peertube-player", src: video.stream, controls: true, preload: "metadata", poster: video.thumbnail || undefined }) + : div({ class: "peertube-nostream" }, video.thumbnail ? img({ src: video.thumbnail, alt: video.name, class: "post-image" }) : "", p(i18n.peertubeNoStream)), + h3({ class: "peertube-title" }, video.name), + div({ class: "peertube-meta" }, + ch.avatar ? img({ class: "peertube-channel-avatar", src: ch.avatar, alt: "" }) : "", + ch.url ? a({ href: ch.url, target: "_blank", rel: "noopener noreferrer", class: "fediverse-acct" }, ch.displayName || ch.handle) : span({ class: "fediverse-acct" }, ch.displayName || ""), + span({ class: "fediverse-date" }, fmtDate(video.publishedAt)), + span(`${video.views} ${i18n.peertubeViews}`), + video.duration ? span(fmtDuration(video.duration)) : "" + ), + div({ class: "fediverse-actions" }, + form({ method: "POST", action: `/fediverse/peertube/video/${id}/rate` }, + input({ type: "hidden", name: "rating", value: liked ? "none" : "like" }), + button({ type: "submit", class: liked ? "filter-btn active" : "filter-btn" }, `${liked ? "★" : "☆"} ${video.likes}`) + ), + a({ href: video.url, target: "_blank", rel: "noopener noreferrer", class: "filter-btn" }, i18n.peertubeOpen) + ), + video.description ? div({ class: "feed-text peertube-description" }, video.description) : "", + Array.isArray(video.tags) && video.tags.length ? div({ class: "card-chips-row" }, video.tags.map(t => span({ class: "tag-link" }, `#${t}`))) : "" + )); + body.push(h4(`${i18n.peertubeComments} · ${Array.isArray(comments) ? comments.length : 0}`)); + const list = Array.isArray(comments) ? comments : []; + if (!list.length) body.push(p({ class: "muted" }, i18n.peertubeNoComments)); + else body.push(section({ class: "feed-container" }, list.map(renderPeertubeComment))); + body.push(div({ class: "publish-form" }, + form({ method: "POST", action: `/fediverse/peertube/video/${id}/comment` }, + textarea({ maxlength: "4000", name: "text", rows: "4", class: "publish-textarea", placeholder: i18n.peertubeCommentPlaceholder, required: true }), + br(), + button({ type: "submit", class: "filter-btn" }, i18n.peertubeComment) + ) + )); + return template(i18n.fediverse, header, section(...body)); +}; + +exports.peertubeUploadView = ({ account, stats, error } = {}) => { + if (!account) return exports.fediverseOverviewView({ account: null }); + const header = peertubeHeader(account, stats, [a({ href: "/fediverse/peertube", class: "filter-btn" }, `← ${i18n.peertubeVideos}`)]); + return template(i18n.fediverse, header, section( + error ? div({ class: "fediverse-error" }, i18n[error] || i18n.peertubeErrUpload) : "", + div({ class: "publish-form" }, + form({ method: "POST", action: "/fediverse/peertube/upload", enctype: "multipart/form-data" }, + p(i18n.peertubeUploadHelp), + label({ for: "peertube_video" }, i18n.fediverseAttach), br(), + input({ type: "file", id: "peertube_video", name: "video", accept: "video/*", required: true }), br(), br(), + label({ for: "peertube_name" }, i18n.peertubeTitleLabel), br(), + input({ type: "text", id: "peertube_name", name: "name", maxlength: "120", required: true }), br(), + label({ for: "peertube_description" }, i18n.peertubeDescriptionLabel), br(), + textarea({ id: "peertube_description", name: "description", rows: "5", maxlength: "10000", class: "publish-textarea" }), br(), + label({ for: "peertube_privacy" }, i18n.peertubePrivacyLabel), br(), + select({ id: "peertube_privacy", name: "privacy" }, + option({ value: "public" }, i18n.peertubePrivacyPublic), + option({ value: "unlisted" }, i18n.peertubePrivacyUnlisted), + option({ value: "private" }, i18n.peertubePrivacyPrivate) + ), br(), br(), + button({ type: "submit", class: "filter-btn" }, i18n.peertubeUpload) + ) + ) + )); +}; + +exports.fediverseOverviewView = ({ account, stats, telegram, telegramStats, peertube, peertubeStats } = {}) => { + const boxes = []; + if (account) { + boxes.push(mastodonBox(account, stats, [ + form({ method: "GET", action: "/fediverse/mastodon" }, button({ type: "submit", class: "filter-btn" }, i18n.fediverseManage)) + ], true)); + } + if (telegram) { + boxes.push(telegramBox(telegram, telegramStats, [ + form({ method: "GET", action: "/fediverse/telegram" }, button({ type: "submit", class: "filter-btn" }, i18n.fediverseManage)) + ], true)); + } + if (peertube) { + boxes.push(peertubeBox(peertube, peertubeStats, [ + form({ method: "GET", action: "/fediverse/peertube" }, button({ type: "submit", class: "filter-btn" }, i18n.fediverseManage)) + ], true)); + } return template(i18n.fediverse, section( - div({ class: "tags-header" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), - account ? box : disconnectedBlock() + div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), + boxes.length ? boxes : disconnectedBlock() )); }; @@ -136,7 +444,7 @@ exports.fediverseView = ({ account, posts, error, stats } = {}) => { if (!account) return exports.fediverseOverviewView({ account: null }); const body = []; - body.push(div({ class: "tags-header" }, h2(i18n.fediverse), p(i18n.fediverseDescription))); + body.push(div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription))); body.push(mastodonBox(account, stats, [ a({ href: "/fediverse", class: "filter-btn" }, `← ${i18n.fediverse}`), a({ href: "/fediverse/mastodon?refresh=1", class: "filter-btn" }, i18n.fediverseRefresh), @@ -156,7 +464,7 @@ exports.fediverseView = ({ account, posts, error, stats } = {}) => { exports.fediverseThreadView = ({ account, stats, thread, error } = {}) => { const header = section( - div({ class: "tags-header" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), account ? mastodonBox(account, stats, [ a({ href: "/fediverse/mastodon", class: "filter-btn" }, `← ${i18n.fediverse}`) ], true) : "" @@ -198,7 +506,7 @@ exports.fediversePreviewView = ({ account, stats, text, media, error, replyToId, return template(i18n.fediverse, section( - div({ class: "tags-header" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.fediverse), p(i18n.fediverseDescription)), account ? mastodonBox(account, stats, [ a({ href: backHref, class: "filter-btn" }, `← ${i18n.fediverse}`) ], true) : "" @@ -209,7 +517,7 @@ exports.fediversePreviewView = ({ account, stats, text, media, error, replyToId, div({ class: "publish-form" }, form( { action: postAction, method: "POST", enctype: "multipart/form-data" }, - textarea({ name: "text", id: "text", rows: "6", class: "publish-textarea", placeholder: isReply ? i18n.fediverseReplyPlaceholder : i18n.fediverseComposePlaceholder }, text || ""), + textarea({ maxlength: "5000", name: "text", id: "text", rows: "6", class: "publish-textarea", placeholder: isReply ? i18n.fediverseReplyPlaceholder : i18n.fediverseComposePlaceholder }, text || ""), ...mediaList.map(m => input({ type: "hidden", name: "tmp", value: m.name })), br(), label({ for: "fediverse_media" }, i18n.fediverseAttach), diff --git a/src/views/feed_view.js b/src/views/feed_view.js index 359f9f2..95c59ab 100644 --- a/src/views/feed_view.js +++ b/src/views/feed_view.js @@ -1,11 +1,11 @@ -const { div, h2, p, section, button, form, a, span, textarea, br, input, h1, label } = require("../server/node_modules/hyperaxe"); +const { div, h2, p, section, button, form, a, span, textarea, br, input, h1, label, img } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection, renderCommentsLink } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, userLink, renderContentActions, renderEngagement } = require("./main_views"); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, userLink, renderContentActions, renderEngagement, renderModuleStats, moduleIsEmpty, paged } = require("./main_views"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require("./clearnet_view"); const { config } = require("../server/SSB_server.js"); -const { renderTextWithStyles } = require("../backend/renderTextWithStyles"); +const { renderStyledHtml, renderStyledText } = require("../backend/renderStyledText"); const moment = require("../server/node_modules/moment"); const { sanitizeHtml } = require('../backend/sanitizeHtml'); -const { renderUrl } = require("../backend/renderUrl"); const FEED_TEXT_MIN = Number(config?.feed?.minLength ?? 1); const FEED_TEXT_MAX = Number(config?.feed?.maxLength ?? 280); @@ -23,7 +23,7 @@ const normalizeOptions = (opts) => { const formatDate = (feed) => { const ts = feed?.value?.timestamp || Date.parse(feed?.value?.content?.createdAt || "") || 0; - return ts ? new Date(ts).toLocaleString() : ""; + return ts ? moment(ts).format("YYYY/MM/DD HH:mm") : ""; }; const extractTags = (text) => { @@ -31,19 +31,6 @@ const extractTags = (text) => { return Array.from(new Set(list)); }; -const rewriteHashtagLinks = (html) => { - return String(html || '').replace( - /href=(["'])\/hashtag\/([^"'?#\s<]+)\1/gi, - (m, q, rawTag) => { - let t = String(rawTag || ''); - try { t = decodeURIComponent(t); } catch {} - t = t.replace(/[^A-Za-z0-9_]/g, ''); - const tag = t.toLowerCase(); - const query = encodeURIComponent(`#${tag}`); - return `href=${q}/search?query=${query}${q}`; - } - ); -}; const generateFilterButtons = (filters, currentFilter, action, extra = {}) => { const cur = String(currentFilter || "").toUpperCase(); @@ -62,16 +49,6 @@ const generateFilterButtons = (filters, currentFilter, action, extra = {}) => { ); }; -const renderVotesSummary = (opinions = {}) => { - const entries = Object.entries(opinions).filter(([, v]) => Number(v) > 0); - if (!entries.length) return null; - entries.sort((a, b) => Number(b[1]) - Number(a[1]) || String(a[0]).localeCompare(String(b[0]))); - return div( - { class: "votes" }, - entries.map(([category, count]) => span({ class: "vote-category" }, `${category}: ${count}`)) - ); -}; - const renderCardField = (labelText, value) => div( { class: "card-field" }, @@ -83,11 +60,56 @@ const renderFeedCommentsSection = (feedKey, comments = []) => { return renderSharedCommentsSection({ action: `/feed/${encodeURIComponent(feedKey)}/comments`, comments: comments, - returnTo: null + returnTo: null, + open: true }); }; -const renderFeedCard = (feed) => { + +const FEED_MEDIA_RE = /^\n?!?\[[^\]\n]{0,200}\]\(&[A-Za-z0-9+/=]{44}\.sha256\)$/; +const renderMastodonToggle = (on = false) => + div({ class: "clearnet-choice" }, + div({ class: "clearnet-choice-options" }, + label({ class: "clearnet-choice-option" }, input({ type: "checkbox", name: "mastodon", value: "1", ...(on ? { checked: true } : {}) }), + span({ class: "pm-exposition-chip pm-exposition-whole" }, span({ class: "pm-exposition-icon" }, "⌬"), span({ class: "pm-exposition-text" }, "Mastodon")) + ) + ) + ); + +const renderFeedComposer = ({ text = "", media = "", rows = 5, clearnet = false, mastodon = false, mastodonOn = false } = {}) => { + const cleanMedia = FEED_MEDIA_RE.test(String(media || "")) ? String(media).trim() : ""; + const draft = String(text || ""); + const hasPreview = !!(draft.trim() || cleanMedia); + const previewNodes = hasPreview ? renderStyledText(cleanMedia ? `${draft.trim()}\n${cleanMedia}` : draft.trim(), { zoomImages: true }) : []; + return form( + { method: "POST", action: "/feed/create", enctype: "multipart/form-data" }, + textarea({ + name: "text", + required: true, + minlength: String(FEED_TEXT_MIN), + maxlength: String(FEED_TEXT_MAX), + rows: String(rows), + cols: 50, + placeholder: i18n.feedPlaceholder + }, draft), + cleanMedia ? input({ type: "hidden", name: "media", value: cleanMedia }) : null, + div({ class: "comment-file-upload" }, label(i18n.uploadMedia), input({ type: "file", name: "blob" })), + hasPreview + ? div({ class: "feed-preview" }, + h2({ class: "feed-preview-title" }, i18n.messagePreview || "Preview"), + div({ class: "trending-card feed-card" }, div({ class: "feed-text" }, ...previewNodes)) + ) + : null, + renderClearnetSelector(clearnet, i18n), + mastodon ? renderMastodonToggle(mastodonOn) : null, + div({ class: "feed-compose-actions" }, + button({ type: "submit", class: "filter-btn", formaction: "/feed/preview" }, i18n.preview || "Preview"), + button({ type: "submit", class: "create-button" }, i18n.createFeedButton || "Send Feed!") + ) + ); +}; + +const renderFeedCard = exports.renderFeedCard = (feed, spreadMap = null, opts = {}) => { const content = feed.value.content || {}; const rawText = typeof content.text === "string" ? content.text : ""; const safeText = rawText.trim(); @@ -100,71 +122,146 @@ const renderFeedCard = (feed) => { const alreadyRefeeded = Array.isArray(content.refeeds_inhabitants) && me ? content.refeeds_inhabitants.includes(me) : false; - const authorId = feed.value.author || content.author || ""; + const authorId = content.author || feed.value.author || ""; + const signerId = feed.value.author || ""; const refeedsNum = Number(content.refeeds || 0) || 0; const commentCount = Number(content.commentCount || 0); - const styledHtml = rewriteHashtagLinks(renderTextWithStyles(safeText)); + const styledNodes = renderStyledText(safeText, { zoomImages: true }); + const refeedAction = opts.refeedAction || `/feed/refeed/${encodeURIComponent(feed.key)}`; + const canRefeed = opts.canRefeed !== false && !(authorId && String(authorId) === String(me)); + const headerActions = opts.headerActions !== undefined + ? opts.headerActions + : feed.tribeOrigin + ? renderContentActions(null, feed.tribeOrigin.href) + : renderContentActions(feed.key, `/feed/${encodeURIComponent(feed.key)}`, { + author: authorId, + spread: (spreadMap && spreadMap.get(feed.key)) || null, + reportTitle: safeText, + ...(((signerId && String(signerId) === String(me)) || (authorId && String(authorId) === String(me))) ? { deleteAction: `/feed/delete/${encodeURIComponent(feed.key)}` } : {}) + }); + const chipsRow = opts.chips !== undefined + ? opts.chips + : feed.tribeOrigin ? div({ class: "card-chips-row" }, renderTribeOriginChip(feed.tribeOrigin)) : feed.clearnet === true ? div({ class: "card-chips-row" }, renderReachChip(true, i18n, clearnetItemHref("feed", "", feed.key))) : null; + const engagement = opts.engagement !== undefined + ? opts.engagement + : feed.tribeOrigin ? null : renderEngagement(feed.key, + renderOpinionsVoting('/feed/opinions', feed.key, content.opinions, null, content.opinions_inhabitants), + renderCommentsLink({ href: `/feed/${encodeURIComponent(feed.key)}`, count: commentCount }) + ); + + if (process.env.OASIS_MOBILE === '1') { + return div( + { class: "trending-card feed-card" + (authorId && String(authorId) === String(me) ? " own-content" : "") }, + div({ class: "card-header activity-card-header" }, span(), headerActions), + div( + { class: "card-section feed-card-body" }, + div({ class: "feed-text" }, ...styledNodes), + chipsRow, + feed.tribeOrigin ? null : div( + { class: "refeed-column" }, + h1(String(refeedsNum)), + canRefeed + ? form( + { method: "POST", action: refeedAction }, + button({ class: alreadyRefeeded ? "refeed-btn active" : "refeed-btn", type: "submit", title: i18n.refeedButton, "aria-label": i18n.refeedButton, ...(alreadyRefeeded ? { disabled: true } : {}) }, "\u25b2") + ) + : null, + alreadyRefeeded ? p({ class: "muted" }, i18n.alreadyRefeeded) : null + ), + engagement, + p( + { class: "card-footer" }, + span({ class: "date-link" }, `${createdAt}`), + userLink(authorId), + content._textEdited ? span({ class: "edited-badge" }, ` \u00b7 ${i18n.edited || "edited"}`) : null + ) + ) + ); + } return div( { class: "trending-card feed-card" + (authorId && String(authorId) === String(me) ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(feed.key, `/feed/${encodeURIComponent(feed.key)}`, { author: authorId, reportTitle: safeText }) + headerActions ), div( { class: "card-section feed-card-body" }, div( { class: "feed-row" }, - div( + feed.tribeOrigin ? null : div( { class: "refeed-column" }, h1(String(refeedsNum)), - form( - { method: "POST", action: `/feed/refeed/${encodeURIComponent(feed.key)}` }, - button({ class: alreadyRefeeded ? "refeed-btn active" : "refeed-btn", type: "submit", ...(alreadyRefeeded ? { disabled: true } : {}) }, i18n.refeedButton) - ), - alreadyRefeeded ? p({ class: "muted" }, i18n.alreadyRefeeded) : null + canRefeed + ? form( + { method: "POST", action: refeedAction }, + button({ class: alreadyRefeeded ? "refeed-btn active" : "refeed-btn", type: "submit", title: i18n.refeedButton, "aria-label": i18n.refeedButton, ...(alreadyRefeeded ? { disabled: true } : {}) }, "▲") + ) + : null, ), div( { class: "feed-main" }, - div({ class: "feed-text", innerHTML: sanitizeHtml(styledHtml) }), - h2( - `${i18n.totalOpinions}: ${totalCount}`, - ...(() => { - const entries = voteEntries.filter(([, v]) => Number(v) > 0); - if (!entries.length) return []; - const maxVal = Math.max(...entries.map(([, v]) => Number(v))); - const dominant = entries.filter(([, v]) => Number(v) === maxVal).map(([k]) => i18n['vote' + k.charAt(0).toUpperCase() + k.slice(1)] || k); - return [ - span({ class: 'feed-vote-sep' }, '|'), - span({ class: 'feed-vote-dominant' }, `${i18n.moreVoted || 'More Voted'}: ${dominant.join(' + ')}`) - ]; - })() - ), + div({ class: "feed-text" }, ...styledNodes), + chipsRow, p( { class: "card-footer" }, - span({ class: "date-link" }, `${createdAt} ${i18n.performed} `), + span({ class: "date-link" }, `${createdAt}`), userLink(authorId), content._textEdited ? span({ class: "edited-badge" }, ` · ${i18n.edited || "edited"}`) : null ) ) ), - renderEngagement(feed.key, - renderOpinionsVoting('/feed/opinions', feed.key, content.opinions, null, content.opinions_inhabitants), - renderCommentsLink({ href: `/feed/${encodeURIComponent(feed.key)}`, count: commentCount }) - ) + engagement ) ); }; +const renderFeedSideTags = (trendingTags) => + div({ class: "feed-side feed-side-right" }, + div({ class: "feed-side-box" }, + h2({ class: "feed-side-title" }, i18n.feedTrendingTitle || "Trending Tags"), + trendingTags.length + ? div({ class: "feed-side-tags" }, + trendingTags.map(t => a({ href: `/feed?tag=${encodeURIComponent(t.name || t)}`, class: "tag-link" }, `#${t.name || t}`))) + : p({ class: "muted" }, "—") + ) + ); + +const renderFeedSideUsers = (activeUsers) => + div({ class: "feed-side feed-side-left" }, + div({ class: "feed-side-box" }, + h2({ class: "feed-side-title" }, i18n.feedActiveTitle || "Active Inhabitants"), + activeUsers.length + ? div({ class: "feed-side-users" }, + activeUsers.map(u => div({ class: "feed-side-user" }, + img({ src: u.avatarUrl || "/assets/images/default-avatar.png", class: "feed-side-avatar" }), + userLink(u.id) + ))) + : p({ class: "muted" }, "—") + ) + ); + +const feedChipFor = (filter, censusF) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusF)) return true; + if (mode === "MINE") return censusF.some((f) => String(f && f.value && f.value.author) === String(config.keys.id)); + if (mode === "RECENT") return censusF.length > 0; + return true; +}; + exports.feedView = (feeds, opts = "ALL") => { const { filter, q, tag, msg } = normalizeOptions(opts); + const workspace = !!(opts && typeof opts === "object" && opts.workspace) && require("../configs/config-manager.js").getConfig().ux?.current === "feed"; + const spreadMap = (opts && typeof opts === "object" && opts.spreadMap instanceof Map) ? opts.spreadMap : null; + const trendingTags = (opts && typeof opts === "object" && Array.isArray(opts.trendingTags)) ? opts.trendingTags : []; + const activeUsers = (opts && typeof opts === "object" && Array.isArray(opts.activeUsers)) ? opts.activeUsers : []; const title = filter === "MINE" ? i18n.MINEButton - : filter === "TODAY" - ? i18n.TODAYButton + : filter === "RECENT" + ? i18n.RECENTButton : filter === "TOP" ? i18n.TOPButton : filter === "CREATE" @@ -175,57 +272,67 @@ exports.feedView = (feeds, opts = "ALL") => { ? `${i18n.searchTitle || "Search"}: “${q}”` : i18n.feedTitle; - const header = div({ class: "tags-header" }, h2(title), p(i18n.FeedshareYourOpinions)); + const header = div({ class: "tags-header module-header-line" }, h2(title), p(i18n.FeedshareYourOpinions)); const successBanner = msg === 'feedPublished' ? div({ class: 'feed-success-msg' }, p('✓ ' + (i18n.feedPublishedSuccess || 'Feed published successfully!'))) : null; const extra = { q, tag }; + const emptyMod = moduleIsEmpty(feeds, filter, "ALL", q || tag); + const censusF = Array.isArray(opts.censusList) ? opts.censusList : feeds; + const feedChip = feedChipFor(filter, censusF); - return template( - title, - section( - header, - successBanner, - div( - { class: "mode-buttons-row" }, - ...generateFilterButtons(["ALL", "MINE", "TODAY", "TOP"], filter, "/feed", extra), - form({ method: "GET", action: "/feed/create" }, button({ type: "submit", class: "create-button filter-btn" }, i18n.createFeedTitle || "Create Feed")) - ), - div( - { class: "feed-tools-row" }, - form( - { method: "GET", action: "/feed", class: "filter-box" }, - input({ type: "hidden", name: "filter", value: filter }), - tag ? input({ type: "hidden", name: "tag", value: tag }) : null, - input({ type: "text", name: "q", value: q, placeholder: i18n.searchPlaceholder || "Search", class: "filter-box__input" }), - div({ class: "filter-box__controls" }, - button({ type: "submit", class: "filter-box__button" }, i18n.searchButton || "Search") - ) + const centerContent = section( + header, + successBanner, + div( + { class: "mode-buttons-row" }, + ...(emptyMod ? [] : generateFilterButtons(["ALL", "RECENT", "MINE", "TOP"].filter(feedChip), filter, "/feed", extra)), + form({ method: "GET", action: "/feed/create" }, button({ type: "submit", class: "create-button filter-btn" }, i18n.createFeedTitle || "Create Feed")) + ), + emptyMod ? null : div( + { class: "feed-tools-row activity-filter-chips activity-toolbar-row" }, + renderModuleStats(feeds.length), + form( + { method: "GET", action: "/feed", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: filter }), + tag ? input({ type: "hidden", name: "tag", value: tag }) : null, + input({ type: "text", name: "q", value: q, placeholder: i18n.feedSearchPlaceholder || i18n.searchPlaceholder || "Search", class: "filter-box__input" }), + div({ class: "filter-box__controls" }, + button({ type: "submit", class: "filter-box__button" }, i18n.searchButton || "Search") ) - ), - section( - filter === "CREATE" - ? form( - { method: "POST", action: "/feed/create" }, - textarea({ - name: "text", - placeholder: i18n.feedPlaceholder, - required: true, - minlength: String(FEED_TEXT_MIN), - maxlength: String(FEED_TEXT_MAX), - rows: 4, - cols: 50 - }), - br(), - button({ type: "submit", class: "create-button" }, i18n.createFeedButton) - ) - : feeds && feeds.length > 0 - ? div({ class: "feed-container" }, feeds.map((feed) => renderFeedCard(feed)).filter(Boolean)) - : div({ class: "no-results" }, p(i18n.noFeedsFound)) ) + ), + (process.env.OASIS_MOBILE === '1' && workspace) ? div({ class: "feed-sides-row" }, renderFeedSideUsers(activeUsers), renderFeedSideTags(trendingTags)) : null, + section( + filter === "CREATE" + ? renderFeedComposer({ rows: 4, mastodon: opts.mastodon === true }) + : feeds && feeds.length > 0 + ? div({ class: "feed-container" }, paged(feeds.filter((feed) => String((feed.value.content || {}).text || "").trim())).map((feed) => renderFeedCard(feed, spreadMap)).filter(Boolean)) + : div({ class: "no-results" }, p(i18n.noFeedsFound)) ) ); + + if (workspace) { + if (process.env.OASIS_MOBILE === '1') { + return template( + title, + div({ class: "feed-workspace" }, + div({ class: "feed-workspace-center" }, centerContent) + ) + ); + } + return template( + title, + div({ class: "feed-workspace" }, + renderFeedSideUsers(activeUsers), + div({ class: "feed-workspace-center" }, centerContent), + renderFeedSideTags(trendingTags) + ) + ); + } + + return template(title, centerContent); }; exports.feedCreateView = (opts = {}) => { @@ -234,49 +341,39 @@ exports.feedCreateView = (opts = {}) => { return template( i18n.createFeedTitle, section( - div({ class: "tags-header" }, h2(i18n.createFeedTitle), p(i18n.FeedshareYourOpinions)), - div({ class: "mode-buttons-row" }, ...generateFilterButtons(["ALL", "MINE", "TODAY", "TOP"], "CREATE", "/feed", { q, tag })), - form( - { method: "POST", action: "/feed/create" }, - textarea({ - name: "text", - required: true, - minlength: String(FEED_TEXT_MIN), - maxlength: String(FEED_TEXT_MAX), - rows: 5, - cols: 50, - placeholder: i18n.feedPlaceholder - }), - br(), - button({ type: "submit", class: "create-button" }, i18n.createFeedButton || "Send Feed!") - ) + div({ class: "tags-header module-header-line" }, h2(i18n.createFeedTitle), p(i18n.FeedshareYourOpinions)), + div({ class: "mode-buttons-row" }, ...generateFilterButtons(["ALL"], "CREATE", "/feed", { q, tag })), + renderFeedComposer({ text: opts.text, media: opts.media, clearnet: opts.clearnet === true, mastodon: opts.mastodon === true, mastodonOn: opts.mastodonOn === true }) ) ); }; exports.singleFeedView = (feed, comments = [], params = {}) => { + const feedChip = feedChipFor("ALL", Array.isArray(params.censusList) ? params.censusList : null); const content = feed.value?.content || {}; const rawText = typeof content.text === "string" ? content.text : ""; const safeText = rawText.trim(); - const authorId = feed.value?.author || content.author || ""; + const authorId = content.author || feed.value?.author || ""; + const signerId = feed.value?.author || ""; const createdAt = formatDate(feed); - const styledHtml = rewriteHashtagLinks(renderTextWithStyles(safeText)); + const styledNodes = renderStyledText(safeText, { zoomImages: true }); const me = config?.keys?.id; const alreadyRefeeded = Array.isArray(content.refeeds_inhabitants) && me ? content.refeeds_inhabitants.includes(me) : false; const refeedsNum = Number(content.refeeds || 0) || 0; const tags = extractTags(safeText); + const isClearnet = !!feed.clearnet; return template( i18n.feedDetailTitle || "Feed", - section(div({ class: "tags-header" }, h2(i18n.feedTitle), p(i18n.FeedshareYourOpinions))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.feedTitle), p(i18n.FeedshareYourOpinions))), section( div( { class: "filters" }, form( { method: "GET", action: "/feed", class: "ui-toolbar ui-toolbar--filters" }, button({ type: "submit", name: "filter", value: "ALL", class: "filter-btn" }, i18n.ALLButton || "ALL"), - button({ type: "submit", name: "filter", value: "MINE", class: "filter-btn" }, i18n.MINEButton || "MINE"), - button({ type: "submit", name: "filter", value: "TODAY", class: "filter-btn" }, i18n.TODAYButton || "TODAY"), + ...(feedChip("RECENT") ? [button({ type: "submit", name: "filter", value: "RECENT", class: "filter-btn" }, i18n.RECENTButton || "RECENT")] : []), + ...(feedChip("MINE") ? [button({ type: "submit", name: "filter", value: "MINE", class: "filter-btn" }, i18n.MINEButton || "MINE")] : []), button({ type: "submit", name: "filter", value: "TOP", class: "filter-btn" }, i18n.TOPButton || "TOP"), form({ method: "GET", action: "/feed/create" }, button({ type: "submit", class: "create-button" }, i18n.createFeedTitle || "Create Feed")) ) @@ -285,7 +382,12 @@ exports.singleFeedView = (feed, comments = [], params = {}) => { { class: "bookmark-item card feed-detail-card" }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(feed.key, null, { spread: params.spreads || null, author: authorId, reportTitle: safeText }) + renderContentActions(feed.key, `/feed/${encodeURIComponent(feed.key)}`, { + spread: params.spreads || null, + author: authorId, + reportTitle: safeText, + ...(((signerId && String(signerId) === String(me)) || (authorId && String(authorId) === String(me))) ? { deleteAction: `/feed/delete/${encodeURIComponent(feed.key)}` } : {}) + }) ), br, div( @@ -293,25 +395,28 @@ exports.singleFeedView = (feed, comments = [], params = {}) => { div( { class: "refeed-column" }, h1(String(refeedsNum)), - form( - { method: "POST", action: `/feed/refeed/${encodeURIComponent(feed.key)}` }, - button({ class: alreadyRefeeded ? "refeed-btn active" : "refeed-btn", type: "submit", ...(alreadyRefeeded ? { disabled: true } : {}) }, i18n.refeedButton) - ), - alreadyRefeeded ? p({ class: "muted" }, i18n.alreadyRefeeded) : null + (authorId && String(authorId) === String(me)) + ? null + : form( + { method: "POST", action: `/feed/refeed/${encodeURIComponent(feed.key)}` }, + button({ class: alreadyRefeeded ? "refeed-btn active" : "refeed-btn", type: "submit", title: i18n.refeedButton, "aria-label": i18n.refeedButton, ...(alreadyRefeeded ? { disabled: true } : {}) }, "▲") + ), ), div( { class: "feed-main" }, - div({ class: "feed-text", innerHTML: sanitizeHtml(styledHtml) }), + div({ class: "feed-text" }, ...styledNodes), tags.length ? div( { class: "card-tags" }, tags.map((tag) => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)) ) : null, + div({ class: "card-chips-row" }, renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref("feed", "", feed.key) : null)), + authorId && String(authorId) === String(me) ? renderClearnetSwitch("feed", feed.rootId || feed.key, isClearnet) : null, br, p( { class: "card-footer" }, - span({ class: "date-link" }, `${createdAt} ${i18n.performed} `), + span({ class: "date-link" }, `${createdAt}`), userLink(authorId), content._textEdited ? span({ class: "edited-badge" }, ` · ${i18n.edited || "edited"}`) : null ) diff --git a/src/views/files_view.js b/src/views/files_view.js new file mode 100644 index 0000000..f36f6d3 --- /dev/null +++ b/src/views/files_view.js @@ -0,0 +1,425 @@ +const { form, button, div, h2, h3, p, section, input, br, a, span, textarea, select, label, option, table, tr, th, td, progress, strong } = require("../server/node_modules/hyperaxe"); +const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require('./clearnet_view'); + +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip , renderSpreadEditWarning, renderContentActions, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); +const { renderStyledText, safeExternalHref } = require("../backend/renderStyledText"); + +const userId = config.keys.id; + +const safeArr = (v) => (Array.isArray(v) ? v : []); +const safeText = (v) => String(v || "").trim(); + +const buildReturnTo = (filter, params = {}) => { + const f = safeText(filter || "all"); + const q = safeText(params.q || ""); + const sort = safeText(params.sort || "recent"); + const parts = [`filter=${encodeURIComponent(f)}`]; + if (q) parts.push(`q=${encodeURIComponent(q)}`); + if (sort) parts.push(`sort=${encodeURIComponent(sort)}`); + return `/files?${parts.join("&")}`; +}; + +const renderTags = (tags) => { + const list = safeArr(tags).map((t) => String(t || "").trim()).filter(Boolean); + return list.length + ? div( + { class: "card-tags" }, + list.map((tag) => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)) + ) + : null; +}; + +const renderFileOwnerActions = (filter, fileObj, params = {}) => { + const returnTo = buildReturnTo(filter, params); + const isAuthor = String(fileObj.author) === String(userId); + const hasOpinions = Object.keys(fileObj.opinions || {}).length > 0; + + if (!isAuthor || hasOpinions) return []; + + return [ + form( + { method: "GET", action: `/files/edit/${encodeURIComponent(fileObj.key)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ class: "update-btn", type: "submit" }, i18n.fileUpdateButton) + ) + ]; +}; + +const renderFileCommentsSection = (fileId, comments = [], returnTo = null) => { + return renderSharedCommentsSection({ + action: `/files/${encodeURIComponent(fileId)}/comments`, + comments: comments, + returnTo: returnTo + }); +}; + +const blobSha256Hex = (blobId) => { + const m = /^&([A-Za-z0-9+/=]+)\.sha256$/.exec(String(blobId || "")); + if (!m) return ""; + try { return Buffer.from(m[1], "base64").toString("hex"); } catch (_) { return ""; } +}; + +const formatSize = (bytes) => { + const n = Number(bytes) || 0; + if (n === 0) return "—"; + if (n < 1024) return n + " B"; + if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB"; + return (n / (1024 * 1024)).toFixed(1) + " MB"; +}; + +const spreadInfoOf = (t, spreadMap) => (spreadMap instanceof Map && spreadMap.get(t.key)) || null; +const renderFileSeeds = (t, spreadMap) => { + const info = spreadInfoOf(t, spreadMap); + return String(info && typeof info.count === "number" ? info.count : (info && Array.isArray(info.voters) ? info.voters.length : 0)); +}; +const renderFileSpread = (t, spreadMap) => String(t.author) === String(userId) ? "" : renderSpreadButton(t.key, spreadInfoOf(t, spreadMap), i18n.seedAction, false); + +const renderFileTable = exports.renderFileTable = (files, filter, params = {}) => { + const returnTo = buildReturnTo(filter, params); + + if (!files.length) return p(params.q ? i18n.fileNoMatch : i18n.noFiles); + + return table( + { border: "1", class: "file-table" }, + tr( + th(i18n.createdAt || "DATE"), + th(i18n.authorLabel || "AUTHOR"), + th(i18n.fileTitleLabel || "TITLE"), + th(i18n.fileSizeLabel || "SIZE"), + th(i18n.seedsLabel), + th(i18n.seedAction), + th("") + ), + files.map((t) => + tr( + td({ class: "row-chips" }, t.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('files', t.title, t.key)) : null), + td({ "data-label": i18n.createdAt || "DATE" }, moment(t.createdAt).format("YYYY/MM/DD HH:mm")), + td({ class: "row-author" }, userLink(t.author)), + td({ class: "row-title", "data-label": i18n.fileTitleLabel || "TITLE" }, t.title || ""), + td({ "data-label": i18n.fileSizeLabel || "SIZE" }, formatSize(t.size)), + td({ class: "torrent-spread-cell", "data-label": i18n.seedsLabel }, renderFileSeeds(t, params.spreadMap)), + td({ class: "torrent-spread-cell", "data-label": i18n.seedAction }, renderFileSpread(t, params.spreadMap)), + td( + form( + { method: "GET", action: `/files/${encodeURIComponent(t.key)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + input({ type: "hidden", name: "filter", value: filter || "all" }), + params.q ? input({ type: "hidden", name: "q", value: params.q }) : null, + params.sort ? input({ type: "hidden", name: "sort", value: params.sort }) : null, + button({ type: "submit", class: "filter-btn" }, i18n.fileDetailsButton) + ) + ) + ) + ) + ); +}; + +const isExternalUrl = (u) => /^https?:\/\//i.test(String(u || "")); + +const renderNextcloudForm = (returnTo) => div( + { class: "div-center audio-form" }, + h2("Nextcloud"), + form( + { action: "/files/nextcloud", method: "POST", enctype: "multipart/form-data" }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + span(i18n.nextcloudFileLabel), + br(), + input({ type: "file", name: "file", required: true }), + br(), + br(), + span(i18n.fileTitleLabel), + br(), + input({ type: "text", name: "title", maxlength: "100", placeholder: i18n.fileTitlePlaceholder, required: true }), + br(), + span(i18n.fileDescriptionLabel), + br(), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.fileDescriptionPlaceholder, rows: "4" }), + br(), + span(i18n.fileTagsLabel), + br(), + input({ type: "text", name: "tags", placeholder: i18n.fileTagsPlaceholder }), + br(), + br(), + renderClearnetSelector(false, i18n), br(), + button({ type: "submit" }, i18n.nextcloudUploadButton) + ) +); + +const renderFileForm = (filter, fileId, fileToEdit, params = {}) => { + const returnTo = safeText(params.returnTo) || buildReturnTo("all", params); + const tribeId = safeText(params.tribeId || ""); + const clearnetAllowed = !tribeId && !fileToEdit?.tribeId && !fileToEdit?.encrypted && !fileToEdit?.cipher; + return div( + { class: "div-center audio-form" }, + params.spreadWarning || null, + form( + { + action: filter === "edit" ? `/files/update/${encodeURIComponent(fileId)}` : "/files/create", + method: "POST", + enctype: "multipart/form-data" + }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + span(i18n.fileFileLabel), + br(), + input({ type: "file", name: "file", required: filter !== "edit" }), + br(), + br(), + span(i18n.fileTitleLabel), + br(), + input({ type: "text", name: "title", maxlength: "100", placeholder: i18n.fileTitlePlaceholder, value: fileToEdit?.title || "", required: true }), + br(), + span(i18n.fileDescriptionLabel), + br(), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.fileDescriptionPlaceholder, rows: "4" }, fileToEdit?.description || ""), + br(), + span(i18n.fileTagsLabel), + br(), + input({ + type: "text", + name: "tags", + placeholder: i18n.fileTagsPlaceholder, + value: safeArr(fileToEdit?.tags).join(", ") + }), + br(), + br(), + ...(clearnetAllowed ? [renderClearnetSelector(filter === "edit" ? !!fileToEdit?.clearnet : false, i18n), br()] : []), + button({ type: "submit" }, filter === "edit" ? i18n.fileUpdateButton : i18n.fileCreateButton) + ) + ); +}; + +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.length > 0; + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + +exports.filesView = async (files, filter = "all", fileId = null, params = {}) => { + if (filter === "edit") params = { ...params, spreadWarning: await renderSpreadEditWarning(fileId) }; + const title = i18n.filesTitle; + + const q = safeText(params.q || ""); + const sort = safeText(params.sort || "recent"); + + const list = safeArr(files); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); + const fileToEdit = fileId ? list.find((t) => t.key === fileId) : null; + + return template( + title, + section( + div({ class: "tags-header module-header-line" }, + h2(title), + p(i18n.filesDescription) + ), + div( + { class: "filters" }, + form( + { method: "GET", action: "/files", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q }), + input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ + button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterAll).toUpperCase()), + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( + { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, + String(i18n.fileFilterFavorites).toUpperCase() + )] : []), + + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterTop).toUpperCase())] : []), + ]), + button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.fileCreateButton) + ) + ) + ), + section( + filter === "create" || filter === "edit" + ? [renderFileForm(filter, fileId, fileToEdit, { ...params, filter }), filter !== "edit" && params.nextcloud && !params.tribeId ? renderNextcloudForm(safeText(params.returnTo) || buildReturnTo("all", params)) : null] + : section( + emptyMod ? null : div( + { class: "audios-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), + form( + { method: "GET", action: "/files", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: filter }), + input({ + type: "text", + name: "q", + value: q, + placeholder: i18n.fileSearchPlaceholder, + class: "filter-box__input" + }), + div( + { class: "filter-box__controls" }, + select( + { name: "sort", class: "filter-box__select" }, + option({ value: "recent", ...(sort === "recent" ? { selected: true } : {})}, i18n.fileSortRecent), + option({ value: "oldest", ...(sort === "oldest" ? { selected: true } : {})}, i18n.fileSortOldest), + option({ value: "top", ...(sort === "top" ? { selected: true } : {})}, i18n.fileSortTop) + ), + button({ type: "submit", class: "filter-box__button" }, i18n.fileSearchButton) + ) + ) + ), + div({ class: "audios-list" }, renderFileTable(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) + ) + ) + ); +}; + +exports.singleFileView = async (fileObj, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); + const q = safeText(params.q || ""); + const sort = safeText(params.sort || "recent"); + const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q, sort }); + + const title = safeText(fileObj.title); + const { renderEncryptedChip, renderTransportChip } = require('./clearnet_view'); + const isAuthor = String(fileObj.author) === String(userId); + const clearnetAllowed = !fileObj.tribeId && !fileObj.encrypted && !fileObj.cipher; + const isClearnet = !!fileObj.clearnet; + + const chips = [ + renderLifespanChip(fileObj.lifetime, i18n), + fileObj.sizeBytes ? renderEcoTax(fileObj.sizeBytes, fileObj.key) : null + ].filter(Boolean); + + const ownerActions = renderFileOwnerActions(filter, fileObj, { q, sort }); + const sideActions = []; + for (const a of ownerActions) sideActions.push(a); + + const tagsNode = renderTags(fileObj.tags); + + const detailActions = div({ class: "card-header activity-card-header" }, + renderContentActions(fileObj.key, `/files/${encodeURIComponent(fileObj.key)}`, { + spreadTitle: i18n.seedAction, + author: fileObj.author, + deleteAction: String(fileObj.author) === String(userId) ? contentDeleteAction("file", fileObj.key) : null, + favKind: 'files', + ...(fileObj.url && fileObj.url.startsWith("&") ? { torrentFrom: { blobId: fileObj.url, name: fileObj.fileName || fileObj.title } } : {}), + isFavorite: fileObj.isFavorite, + spread: params.spreads || null, + returnTo, + reportTitle: fileObj.title + }) + ); + + const fileSide = div({ class: "tribe-side" }, + div({ class: "shop-title-row" }, + title ? h2({ class: "tribe-card-title" }, title) : null, + fileObj.tribeId && fileObj.cipher ? renderEncryptedChip(i18n) : renderTransportChip(i18n), + clearnetAllowed ? renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('files', fileObj.title, fileObj.key) : null) : null, + clearnetAllowed && isAuthor ? renderClearnetSwitch('files', fileObj.rootId || fileObj.key, isClearnet) : null + ), + chips.length ? div({ class: "card-chips-row" }, ...chips) : null, + safeText(fileObj.description) + ? p({ class: "tribe-side-description" }, ...renderStyledText(fileObj.description)) + : null, + tagsNode, + sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null + ); + + const fileMain = div({ class: "tribe-main" }, + detailActions, + fileObj.url && fileObj.url.startsWith("&") + ? div({ class: "torrent-download" }, + div({ class: "torrent-oasis torrent-detail-section" }, + h3({ class: "torrent-section-title" }, i18n.torrentOriginalSection || "Original"), + table({ class: "tribe-info-table torrent-file-info" }, + tr(td({ class: "tribe-info-label" }, i18n.fileShareFileLabel), td({ class: "tribe-info-value" }, fileObj.fileName || fileObj.title || "download")), + tr(td({ class: "tribe-info-label" }, i18n.fileSizeLabel), td({ class: "tribe-info-value" }, formatSize(fileObj.size))), + fileObj.mime ? tr(td({ class: "tribe-info-label" }, i18n.fileTypeLabel), td({ class: "tribe-info-value" }, fileObj.mime)) : null, + fileObj.cipher + ? tr(td({ class: "tribe-info-label" }, i18n.encryptedChipLabel || "E2E"), td({ class: "tribe-info-value" }, "AES-256-GCM")) + : tr(td({ class: "tribe-info-label" }, "SHA-256"), td({ class: "tribe-info-value" }, span({ class: "bank-address-code" }, blobSha256Hex(fileObj.url)))) + ), + a({ href: `/files/${encodeURIComponent(fileObj.key)}/get`, class: "filter-btn" }, `\u2B07 ${i18n.torrentOasisButton || "OASIS"}`) + ), + fileObj.torrentUrl + ? div({ class: "torrent-detail-section" }, + h3({ class: "torrent-section-title" }, "Torrent"), + table({ class: "tribe-info-table torrent-file-info" }, + tr(td({ class: "tribe-info-label" }, i18n.fileShareFileLabel), td({ class: "tribe-info-value" }, `${String(fileObj.fileName || fileObj.title || "download").replace(/\.torrent$/i, "")}.torrent`)), + tr(td({ class: "tribe-info-label" }, "SHA-256"), td({ class: "tribe-info-value" }, span({ class: "bank-address-code" }, blobSha256Hex(fileObj.torrentUrl)))) + ), + a({ href: `/blob/${encodeURIComponent(fileObj.torrentUrl)}?name=${encodeURIComponent(`${String(fileObj.fileName || fileObj.title || "download").replace(/\.torrent$/i, "")}.torrent`)}`, class: "filter-btn" }, `\u2B07 ${i18n.torrentExternalButton || "EXTERNAL"}`) + ) + : null + ) + : isExternalUrl(fileObj.url) + ? div({ class: "torrent-download" }, + div({ class: "torrent-oasis torrent-detail-section" }, + h3({ class: "torrent-section-title" }, i18n.fileExternalLink), + table({ class: "tribe-info-table torrent-file-info" }, + tr(td({ class: "tribe-info-label" }, i18n.fileShareFileLabel), td({ class: "tribe-info-value" }, fileObj.fileName || fileObj.title || "download")), + tr(td({ class: "tribe-info-label" }, i18n.fileSizeLabel), td({ class: "tribe-info-value" }, formatSize(fileObj.size))), + fileObj.mime ? tr(td({ class: "tribe-info-label" }, i18n.fileTypeLabel), td({ class: "tribe-info-value" }, fileObj.mime)) : null, + tr(td({ class: "tribe-info-label" }, "URL"), td({ class: "tribe-info-value" }, a({ href: safeExternalHref(fileObj.url), target: "_blank", rel: "noopener noreferrer" }, fileObj.url))) + ), + a({ href: safeExternalHref(fileObj.url), target: "_blank", rel: "noopener noreferrer", class: "filter-btn" }, `\u2B07 ${i18n.fileExternalLink}`) + ) + ) + : p(i18n.fileNoFile), + (() => { + const createdTs = fileObj.createdAt ? new Date(fileObj.createdAt).getTime() : NaN; + const updatedTs = fileObj.updatedAt ? new Date(fileObj.updatedAt).getTime() : NaN; + const showUpdated = Number.isFinite(updatedTs) && (!Number.isFinite(createdTs) || updatedTs !== createdTs); + + return p( + { class: "card-footer" }, + span({ class: "date-link" }, `${moment(fileObj.createdAt).format("YYYY/MM/DD HH:mm")}`), + userLink(fileObj.author), + showUpdated + ? span( + { class: "votations-comment-date" }, + ` | ${i18n.fileUpdatedAt}: ${moment(fileObj.updatedAt).format("YYYY/MM/DD HH:mm")}` + ) + : null + ); + })(), + renderEngagement(fileObj.key, + renderOpinionsVoting('/files/opinions', fileObj.key, fileObj.opinions, returnTo, fileObj.opinions_inhabitants), + renderFileCommentsSection(fileObj.key, comments, returnTo) + ) + ); + + return template( + i18n.filesTitle, + section( + div({ class: "tags-header module-header-line" }, + h2(i18n.fileAllSectionTitle || i18n.filesTitle), + p(i18n.filesDescription) + ), + div( + { class: "filters" }, + form( + { method: "GET", action: "/files", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q }), + input({ type: "hidden", name: "sort", value: sort }), + button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterAll).toUpperCase()), + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( + { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, + String(i18n.fileFilterFavorites).toUpperCase() + )] : []), + + button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.fileFilterTop).toUpperCase()), + button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.fileCreateButton) + ) + ), + div({ class: "tribe-details" }, fileSide, fileMain) + ) + ); +}; diff --git a/src/views/forum_view.js b/src/views/forum_view.js index a452797..0f7be4b 100644 --- a/src/views/forum_view.js +++ b/src/views/forum_view.js @@ -1,14 +1,16 @@ const { - div, a, span, form, button, section, p, - input, label, br, select, option, h2, textarea + div, a, span, form, button, section, p, ul, li, + input, label, br, select, option, h2, textarea, details, summary } = require("../server/node_modules/hyperaxe"); +const RAW_HTML = Symbol.for('oasis.rawHtml'); const moment = require("../server/node_modules/moment"); -const { template, i18n, userLink, renderSpreadButton, renderPrivacyChip, renderLifespanChip, renderContentActions } = require('./main_views'); -const { renderEncryptedChip: renderForumEncryptedChip } = require('./clearnet_view'); +const { template, i18n, userLink, renderSpreadButton, renderPrivacyChip, renderLifespanChip, renderContentActions, renderInviteQrCard, renderStateChip, renderSubscriptionBox, renderModuleStats, moduleIsEmpty, paged } = require('./main_views'); +const { renderEncryptedChip: renderForumEncryptedChip, renderTribeOriginChip } = require('./clearnet_view'); const { config } = require('../server/SSB_server.js'); -const { renderUrl } = require('../backend/renderUrl'); -const { renderTextWithStyles } = require('../backend/renderTextWithStyles'); +const { renderStyledText } = require('../backend/renderStyledText'); +const { renderStyledHtml } = require('../backend/renderStyledText'); const { sanitizeHtml } = require('../backend/sanitizeHtml'); +const { TEXT_CAP } = require('../backend/long_text'); const userId = config.keys.id; @@ -22,7 +24,7 @@ exports.renderForumInvitePage = (code) => { return template(i18n.invitesForumsTitle || 'Forums', section(pageContent)); }; -const BASE_FILTERS = ['hot','all','mine','recent','top']; +const BASE_FILTERS = ['hot','all','recent','mine','top']; const CAT_BLOCK1 = ['GENERAL','OASIS','L.A.R.P.']; const CAT_BLOCK2 = ['POLITICS','TECH','SCIENCE','MUSIC','ART','GAMING','BOOKS','FILMS']; const CAT_BLOCK3 = ['PHILOSOPHY','SOCIETY','PRIVACY','CYBERWARFARE','SURVIVALISM']; @@ -30,6 +32,7 @@ const ALL_CATS = [...CAT_BLOCK1, ...CAT_BLOCK2, ...CAT_BLOCK3]; const catKey = (c) => 'forumCat' + String(c || '').replace(/\./g,'').replace(/[\s-]/g,'').toUpperCase(); const catLabel = (c) => i18n[catKey(c)] || c; +const tribeCatLabel = (c) => i18n['tribeForumCat' + String(c || '').charAt(0) + String(c || '').slice(1).toLowerCase()] || c; const Z = 1.96; function wilsonScore(pos, neg) { @@ -43,9 +46,9 @@ function getFilteredForums(filter, forums) { const now = Date.now(); if (filter === 'mine') return forums.filter(f => f.author === userId); if (filter === 'recent') return forums.filter(f => new Date(f.createdAt).getTime() >= now - 86400000); - if (filter === 'top') return forums.slice().sort((a,b) => b.score - a.score); + if (filter === 'top') return forums.filter(f => !f.tribeOrigin).sort((a,b) => b.score - a.score); if (filter === 'hot') return forums - .filter(f => new Date(f.createdAt).getTime() >= now - 86400000) + .filter(f => !f.tribeOrigin && new Date(f.createdAt).getTime() >= now - 86400000) .sort((a,b) => b.score - a.score); if (ALL_CATS.includes(filter)) return forums.filter(f => f.category === filter); @@ -84,57 +87,71 @@ const renderVotes = (target, score, forumId) => ) ); -const renderForumForm = () => +const renderForumForm = (draft = null) => div({ class: 'forum-form' }, - form({ action: '/forum/create', method: 'POST' }, + form({ action: '/forum/create', method: 'POST', enctype: 'multipart/form-data' }, label(i18n.forumCategoryLabel), br(), select({ name: 'category', required: true }, - ALL_CATS.map(cat => option({ value: cat }, catLabel(cat))) + ALL_CATS.map(cat => option({ value: cat, ...(draft && draft.category === cat ? { selected: true } : {}) }, catLabel(cat))) ), br(), br(), label(i18n.eventPrivacyLabel), br(), select({ name: 'isPublic', id: 'isPublic' }, - option({ value: 'public', selected: true }, i18n.eventPublic), - option({ value: 'private' }, i18n.eventPrivate) + option({ value: 'public', ...(draft && draft.isPrivate ? {} : { selected: true }) }, i18n.eventPublic), + option({ value: 'private', ...(draft && draft.isPrivate ? { selected: true } : {}) }, i18n.eventPrivate) ), br(), br(), label(i18n.forumTitleLabel), br(), input({ type: 'text', name: 'title', maxlength: '100', required: true, - placeholder: i18n.forumTitlePlaceholder + placeholder: i18n.forumTitlePlaceholder, + value: draft ? String(draft.title || '') : '' }), br(), br(), label(i18n.forumMessageLabel), br(), - textarea({ + textarea({ maxlength: String(TEXT_CAP), name: 'text', required: true, - rows: 4, + rows: 8, placeholder: i18n.forumMessagePlaceholder - }), br(), br(), + }, draft ? String(draft.text || '') : ''), br(), br(), + label(i18n.uploadMedia), br(), + input({ type: 'file', name: 'blob' }), br(), br(), button({ type: 'submit' }, i18n.forumCreateButton) ) ); -const renderThread = (nodes, level = 0, forumId) => { +const bySupport = (a, b) => + wilsonScore(b.positiveVotes, b.negativeVotes) - wilsonScore(a.positiveVotes, a.negativeVotes); + +const bestReplyKey = (nodes) => { + const flat = []; + const walk = (list) => { for (const m of Array.isArray(list) ? list : []) { if (m && m.text && String(m.text).trim()) flat.push(m); walk(m && m.children); } }; + walk(nodes); + if (!flat.length) return null; + const sorted = flat.slice().sort(bySupport); + const top = sorted[0]; + if (wilsonScore(top.positiveVotes, top.negativeVotes) > 0) return top.key; + const first = (Array.isArray(nodes) ? nodes : []).filter(m => m && m.text && String(m.text).trim()).sort(bySupport)[0]; + return first ? first.key : null; +}; + +const renderThread = (nodes, level = 0, forumId, bestKey = null) => { if (!Array.isArray(nodes)) return []; return [...nodes] .filter(m => (m && m.text && String(m.text).trim()) || (m && Array.isArray(m.children) && m.children.length)) - .sort((a, b) => - wilsonScore(b.positiveVotes, b.negativeVotes) - - wilsonScore(a.positiveVotes, a.negativeVotes) - ) - .flatMap((m, i) => { - const isTopLevelWinner = level === 0 && i === 0; + .sort(bySupport) + .flatMap((m) => { const classList = [ 'forum-comment', `level-${level}`, - isTopLevelWinner ? 'highlighted-reply' : '' + bestKey && m.key === bestKey ? 'highlighted-reply' : '' ].filter(Boolean).join(' '); const commentBox = div( - { class: classList }, + { class: classList, id: `msg-${String(m.key || '').replace(/[^a-zA-Z0-9]/g, '')}` }, div({ class: 'comment-header' }, span({ class: 'date-link' }, - `${moment(m.timestamp).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed}`), + `${moment(m.timestamp).format('YYYY/MM/DD HH:mm')}`), userLink(m.author), div({ class: 'comment-votes' }, span({ class: 'votes-count' }, `▲: ${m.positiveVotes || 0}`), @@ -151,123 +168,119 @@ const renderThread = (nodes, level = 0, forumId) => { ...(m.text || '').split('\n') .map(l => l.trim()) .filter(l => l) - .map(l => p(...renderUrl(l))) + .map(l => p(...renderStyledText(l, { zoomImages: true }))) ) ) ), - div({ class: 'new-reply' }, + details({ class: 'new-reply forum-inline-reply', ...(draftFor(m.key) ? { open: true } : {}) }, + summary({ class: 'forum-inline-reply-toggle' }, i18n.tribeForumReply), form({ method: 'POST', action: `/forum/${encodeURIComponent(forumId)}/message`, - class: 'comment-form' + class: 'comment-form', + enctype: 'multipart/form-data' }, input({ type: 'hidden', name: 'parentId', value: m.key }), - textarea({ + textarea({ maxlength: String(TEXT_CAP), name: 'message', - rows: 2, + rows: 4, required: true, placeholder: i18n.forumMessagePlaceholder, class: 'comment-textarea' - }), - button({ type: 'submit', class: 'forum-send-btn' }, 'Reply') + }, draftFor(m.key)), + div({ class: 'comment-file-upload' }, input({ type: 'file', name: 'blob' })), + button({ type: 'submit', class: 'forum-send-btn' }, i18n.tribeForumReply) ) ) ); - return [ commentBox, ...renderThread(m.children || [], level + 1, forumId) ]; + return [ commentBox, ...renderThread(m.children || [], level + 1, forumId, bestKey) ]; }); }; +const forumHref = (f) => `/forum/${encodeURIComponent(f.key)}`; + +const forumCounts = (f) => span({ class: 'card-label activity-update-counts mailing-counts' }, + `👥: ${f.participants?.length || 1} · 💬 ${Math.max(0, (f.messagesCount || 1) - 1)} · ▲ ${f.positiveVotes || 0} · ▼ ${f.negativeVotes || 0}`); + const renderForumList = (forums, currentFilter, spreadMap = new Map()) => { const visibleForums = (Array.isArray(forums) ? forums : []).filter(f => (f && f.title && String(f.title).trim()) || (f && f.text && String(f.text).trim()) ) - return div({ class: 'forum-list' }, - visibleForums.length - ? visibleForums.map(f => - div({ class: 'forum-card' }, - div({ class: 'forum-score-col' }, - renderVotes(f.key, f.score, f.key) - ), - div({ class: 'forum-main-col' }, - div({ class: 'card-header activity-card-header' }, - renderContentActions(f.key, `/forum/${encodeURIComponent(f.key)}`, { spread: spreadMap.get(f.key) || null, author: f.author, favKind: 'forum', isFavorite: f.isFavorite, reportTitle: f.title }) - ), - div({ class: 'forum-header-row' }, - a({ - class: 'forum-category', - href: `/forum?filter=${encodeURIComponent(f.category)}` - }, `[${catLabel(f.category)}]`), - a({ - class: 'forum-title', - href: `/forum/${encodeURIComponent(f.key)}` - }, f.title), - f.isPrivate ? renderPrivacyChip(true, i18n) : null, - f.isPrivate ? renderForumEncryptedChip(i18n) : null, - renderLifespanChip(f.lifetime, i18n) - ), - div({ - class: 'forum-body', - innerHTML: sanitizeHtml(renderTextWithStyles(f.text || '')) - }), - div({ class: 'forum-meta' }, - span({ class: 'forum-positive-votes' }, - `▲: ${f.positiveVotes || 0}`), - span({ class: 'forum-negative-votes' }, - `▼: ${f.negativeVotes || 0}`), - span({ class: 'forum-participants' }, - `${i18n.forumParticipants.toUpperCase()}: ${f.participants?.length || 1}`), - span({ class: 'forum-messages' }, - `${i18n.forumMessages.toUpperCase()}: ${(f.messagesCount || 1) - 1}`) - ), - div({ class: 'forum-footer' }, - span({ class: 'date-link' }, - `${moment(f.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed}`), - userLink(f.author) - ), - currentFilter === 'mine' && f.author === userId - ? div({ class: 'forum-owner-actions' }, - form({ - method: 'POST', - action: `/forum/delete/${encodeURIComponent(f.key)}`, - class: 'forum-delete-form' - }, - button({ type: 'submit', class: 'delete-btn' }, - i18n.forumDeleteButton) - ) - ) - : null + if (!visibleForums.length) return div({ class: 'tribe-grid' }, p(i18n.noForums)); + return ul({ class: 'mailing-archive' }, + ...paged(visibleForums).map(f => { + if (f.tribeOrigin) { + return li({ class: 'mailing-archive-item' }, + div({ class: 'emergency-update-head mailing-archive-head' }, + div({ class: 'mailing-archive-meta' }, + a({ href: f.tribeOrigin.href, class: 'user-link' }, f.title || '—'), + ALL_CATS.includes(f.category) + ? a({ class: 'forum-category', href: `/forum?filter=${encodeURIComponent(f.category)}` }, `[${catLabel(f.category)}]`) + : (f.category ? span({ class: 'forum-category' }, `[${tribeCatLabel(f.category)}]`) : null), + span({ class: 'card-label activity-update-counts mailing-counts' }, `👥: ${f.participants?.length || 1} · 💬 ${Math.max(0, (f.messagesCount || 1) - 1)}`), + renderLifespanChip(f.lifetime, i18n), + renderTribeOriginChip(f.tribeOrigin) + ) ) + ); + } + return li({ class: 'mailing-archive-item' }, + div({ class: 'emergency-update-head mailing-archive-head' }, + div({ class: 'mailing-archive-meta' }, + a({ href: forumHref(f), class: 'user-link' }, f.title || '—'), + a({ class: 'forum-category', href: `/forum?filter=${encodeURIComponent(f.category)}` }, `[${catLabel(f.category)}]`), + forumCounts(f), + f.isPrivate ? renderPrivacyChip(true, i18n) : null, + f.isPrivate ? renderForumEncryptedChip(i18n) : null, + renderLifespanChip(f.lifetime, i18n), + f.subscriptionIn === true + ? renderStateChip('mutuals', '✉', i18n.subscriptionOn) + : (f.subscriptionIn === false ? renderStateChip('closed', '✉', i18n.subscriptionOff) : null) + ), + renderContentActions(f.key, forumHref(f), { spread: spreadMap.get(f.key) || null, author: f.author, favKind: 'forum', isFavorite: f.isFavorite, reportTitle: f.title, deleteAction: f.author === userId ? `/forum/delete/${encodeURIComponent(f.key)}` : null }) ) - ) - : p(i18n.noForums) + ); + }) ); } exports.forumView = async (forums, currentFilter, params = {}) => { const CAT_I18N_MAP_UP = ALL_CATS.reduce((m,c)=>{ m[c]=(catLabel(c)||c).toUpperCase(); return m; },{}); + const emptyModForum = moduleIsEmpty(getFilteredForums(currentFilter || 'all', forums), currentFilter || 'all', 'all', params.q); + const censusForums = Array.isArray(params.censusList) ? params.censusList : (Array.isArray(forums) ? forums : []); + const presentCats = new Set(censusForums.map(f => f && f.category).filter(Boolean)); + const baseChipVisible = (mode) => { + if (mode === currentFilter || mode === 'all') return true; + if (mode === 'mine') return censusForums.some(f => String(f.author) === String(userId)); + if (mode === 'recent') return censusForums.length > 0; + return censusForums.length > 0; + }; return template(i18n.forumTitle, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.forumTitle), p(i18n.forumDescription) ), - div({ class: 'mode-buttons-cols' }, - generateFilterButtons(BASE_FILTERS, currentFilter, '/forum', { + div({ class: 'mode-buttons-row' }, + ...(emptyModForum ? [] : [ + generateFilterButtons(BASE_FILTERS.filter(baseChipVisible), currentFilter, '/forum', { hot: i18n.forumFilterHot, all: i18n.forumFilterAll, mine: i18n.forumFilterMine, recent: i18n.forumFilterRecent, top: i18n.forumFilterTop }), - generateFilterButtons(CAT_BLOCK1, currentFilter, '/forum', CAT_I18N_MAP_UP), - generateFilterButtons(CAT_BLOCK2, currentFilter, '/forum', CAT_I18N_MAP_UP), - generateFilterButtons(CAT_BLOCK3, currentFilter, '/forum', CAT_I18N_MAP_UP), + generateFilterButtons(CAT_BLOCK1.filter(c => presentCats.has(c) || c === currentFilter), currentFilter, '/forum', CAT_I18N_MAP_UP), + generateFilterButtons(CAT_BLOCK2.filter(c => presentCats.has(c) || c === currentFilter), currentFilter, '/forum', CAT_I18N_MAP_UP), + generateFilterButtons(CAT_BLOCK3.filter(c => presentCats.has(c) || c === currentFilter), currentFilter, '/forum', CAT_I18N_MAP_UP) + ]), renderCreateForumButton() ), currentFilter === 'create' ? null - : div({ class: 'filters' }, + : emptyModForum ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStats(getFilteredForums(currentFilter || 'all', forums).length), form({ method: 'GET', action: '/forum', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: currentFilter || 'all' }), input({ type: 'text', name: 'q', value: params.q || '', placeholder: i18n.forumSearchPlaceholder, class: 'filter-box__input' }), @@ -277,7 +290,7 @@ exports.forumView = async (forums, currentFilter, params = {}) => { ) ), currentFilter === 'create' - ? renderForumForm() + ? renderForumForm(params.draft || null) : renderForumList( getFilteredForums(currentFilter || 'all', forums), currentFilter, @@ -287,15 +300,22 @@ exports.forumView = async (forums, currentFilter, params = {}) => { ); }; -exports.singleForumView = async (forum, messagesData, currentFilter) => { +let replyDraft = null; +const draftFor = (parentId) => replyDraft && String(replyDraft.parentId || '') === String(parentId || '') ? String(replyDraft.message || '') : ''; + +exports.singleForumView = async (forum, messagesData, currentFilter, replyId = null, params = {}) => { + replyDraft = params.draft || null; const CAT_I18N_MAP_UP = ALL_CATS.reduce((m,c)=>{ m[c]=(catLabel(c)||c).toUpperCase(); return m; },{}); + const isForumOwner = String(forum.author) === String(userId); + const sharesForum = isForumOwner || (forum.participants || []).includes(userId); + const forumSubIn = isForumOwner || (forum.subscription && forum.subscription.subscribed === true); return template(forum.title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.forumTitle), p(i18n.forumDescription) ), - div({ class: 'mode-buttons-cols' }, + div({ class: 'mode-buttons-row' }, generateFilterButtons(BASE_FILTERS, currentFilter, '/forum', { hot: i18n.forumFilterHot, all: i18n.forumFilterAll, @@ -318,12 +338,13 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => { )), div({ class: 'forum-main-col' }, div({ class: 'card-header activity-card-header' }, - renderContentActions(forum.key, null, { + renderContentActions(forum.key, forumHref(forum), { author: forum.author, favKind: 'forum', isFavorite: forum.isFavorite, - spread: null, - reportTitle: forum.title + spread: params.spreads || null, + reportTitle: forum.title, + deleteAction: isForumOwner ? `/forum/delete/${encodeURIComponent(forum.key)}` : null }) ), div({ class: 'forum-header-row' }, @@ -338,6 +359,9 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => { forum.isPrivate ? renderPrivacyChip(true, i18n) : null, forum.isPrivate ? renderForumEncryptedChip(i18n) : null, renderLifespanChip(forum.lifetime, i18n), + (forum.subscription && sharesForum) + ? renderStateChip(forumSubIn ? 'mutuals' : 'closed', '✉', forumSubIn ? i18n.subscriptionOn : i18n.subscriptionOff) + : null, (forum.isPrivate && forum.author === userId) ? form({ method: 'POST', action: `/forum/generate-invite/${encodeURIComponent(forum.key)}`, class: 'forum-invite-form' }, button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeGenerateInvite)) @@ -347,25 +371,32 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => { button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeOpenInvitation)) : null, (forum.isPrivate && forum.author === userId && forum.openInviteCode) - ? span({ class: 'tribe-open-invite' }, + ? div({ class: 'tribe-open-invite' }, span({ class: 'card-label' }, i18n.tribeInviteCodeText), - span({ class: 'tribe-open-invite-code' }, forum.openInviteCode)) + span({ class: 'tribe-open-invite-code' }, forum.openInviteCode), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/forum/${encodeURIComponent(forum.openInviteCode)}` })) : null, (forum.isPrivate && forum.author === userId && forum.openInviteCode) ? form({ method: 'POST', action: `/forum/open-invite/remove/${encodeURIComponent(forum.key)}`, class: 'forum-invite-form' }, button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeRemoveInvitation)) : null, (forum.isPrivate && forum.author !== userId) - ? a({ class: 'tribe-action-btn', href: '/invites#invites-forums' }, i18n.tribeEnterInvite) - : null, - (forum.author === userId) - ? form({ method: 'POST', action: `/forum/delete/${encodeURIComponent(forum.key)}`, class: 'forum-delete-form' }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.forumDeleteButton)) + ? a({ class: 'tribe-action-btn', href: '/invites?filter=others' }, i18n.tribeEnterInvite) : null ), + (forum.subscription && sharesForum) + ? renderSubscriptionBox({ + target: forum.rootId || forum.key, + scope: 'forum', + subscribed: forum.subscription.subscribed === true, + count: forum.subscription.count, + isOwner: isForumOwner, + returnTo: `/forum/${encodeURIComponent(forum.key)}` + }) + : null, div({ class: 'forum-body', - innerHTML: sanitizeHtml(renderTextWithStyles(forum.text || '')) + [RAW_HTML]: sanitizeHtml(renderStyledHtml(forum.text || '')) }), div({ class: 'forum-meta' }, span({ class: 'forum-positive-votes' }, @@ -379,7 +410,7 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => { ), div({ class: 'forum-footer' }, span({ class: 'date-link' }, - `${moment(forum.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed}`), + `${moment(forum.createdAt).format('YYYY/MM/DD HH:mm')}`), userLink(forum.author) ) ) @@ -388,19 +419,22 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => { form({ method: 'POST', action: `/forum/${encodeURIComponent(forum.key)}/message`, - class: 'new-message-form' + class: 'new-message-form', + enctype: 'multipart/form-data' }, - textarea({ + textarea({ maxlength: String(TEXT_CAP), name: 'message', - rows: 4, + rows: 8, required: true, placeholder: i18n.forumMessagePlaceholder, class: 'new-message-textarea' - }), br(), + }, draftFor(null)), br(), + label(i18n.uploadMedia), br(), + input({ type: 'file', name: 'blob' }), br(), button({ type: 'submit', class: 'forum-send-btn' }, i18n.forumSendButton) ) ), - ...renderThread(messagesData.messages, 0, forum.key) + ...renderThread(messagesData.messages, 0, forum.key, bestReplyKey(messagesData.messages)) ) ); }; diff --git a/src/views/gallery_view.js b/src/views/gallery_view.js index 3f9663e..741661c 100644 --- a/src/views/gallery_view.js +++ b/src/views/gallery_view.js @@ -4,6 +4,7 @@ const { MAX_IMAGES } = require("../models/media_gallery") const safeArr = (v) => (Array.isArray(v) ? v : []) const safeText = (v) => String(v == null ? "" : v).trim() +const downloadHref = (src) => `${src}${String(src).includes("?") ? "&" : "?"}download=1` const blobIdOf = (entry) => { const value = safeText(entry) @@ -26,20 +27,21 @@ const imagesOf = (item) => { const videoOf = (item) => safeText(item && item.video) const renderMediaThumb = (entry, alt = "") => isVideoEntry(entry) - ? video({ controls: true, class: "gallery-image", src: blobUrl(blobIdOf(entry)) }) + ? video({ controls: true, class: "gallery-image", src: blobUrl(blobIdOf(entry)), preload: 'metadata' }) : img({ src: blobUrl(blobIdOf(entry), 256), class: "gallery-image", alt }) const lightboxId = (scope, itemId, index) => `${scope}-photo-${encodeURIComponent(itemId)}-${index}` let zoomSeq = 0 -const renderZoomableImage = (src, { alt = "", imgClass = "" } = {}) => { +const renderZoomableImage = (src, { alt = "", imgClass = "", linkClass = "" } = {}) => { if (!src) return null zoomSeq = (zoomSeq + 1) % 1000000 const id = `zoom-${zoomSeq}-${String(src).replace(/[^a-zA-Z0-9]/g, "").slice(-10)}` return [ - a({ href: `#${id}`, class: "zoom-link" }, img({ src, class: imgClass, alt })), + a({ href: `#${id}`, id: `${id}-src`, class: linkClass ? `zoom-link ${linkClass}` : "zoom-link" }, img({ src, class: imgClass, alt })), div({ id, class: "lightbox" }, - a({ href: "#", class: "lightbox-close" }, "×"), + a({ href: downloadHref(src), class: "lightbox-download", title: i18n.fileShareDownload || "Download" }, "⤓"), + a({ href: `#${id}-src`, class: "lightbox-close" }, "×"), img({ src, class: "lightbox-image", alt }) ) ] @@ -56,19 +58,20 @@ const renderPhotoGallery = (item, scope = "media") => { list.map((entry, i) => isVideoEntry(entry) ? span({ class: "gallery-item" }, renderMediaThumb(entry)) - : a({ href: `#${lightboxId(scope, id, i)}`, class: "gallery-item" }, renderMediaThumb(entry)) + : a({ href: `#${lightboxId(scope, id, i)}`, id: `${lightboxId(scope, id, i)}-src`, class: "gallery-item" }, renderMediaThumb(entry)) ) ) : null, list.filter(entry => !isVideoEntry(entry)).map((entry) => div({ id: lightboxId(scope, id, list.indexOf(entry)), class: "lightbox" }, - a({ href: "#", class: "lightbox-close" }, "×"), + a({ href: downloadHref(blobUrl(blobIdOf(entry))), class: "lightbox-download", title: i18n.fileShareDownload || "Download" }, "⤓"), + a({ href: `#${lightboxId(scope, id, list.indexOf(entry))}-src`, class: "lightbox-close" }, "×"), img({ src: blobUrl(blobIdOf(entry)), class: "lightbox-image", alt: "" }) ) ), clip ? div({ class: "media-video media-video-centered" }, - video({ controls: true, class: "media-video-player", src: blobUrl(blobIdOf(clip)) }) + video({ controls: true, class: "media-video-player", src: blobUrl(blobIdOf(clip)), preload: 'metadata' }) ) : null ) diff --git a/src/views/games_view.js b/src/views/games_view.js index e4c6135..88bbb0a 100644 --- a/src/views/games_view.js +++ b/src/views/games_view.js @@ -1,5 +1,5 @@ const { div, h2, h3, p, section, form, input, button, a, img, table, tr, td, th, span, iframe } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink} = require('./main_views'); +const { template, i18n, userLink, renderModuleStats } = require('./main_views'); const moment = require("../server/node_modules/moment"); const getGames = () => [ @@ -7,6 +7,7 @@ const getGames = () => [ { id: 'ecoinflow', title: () => i18n.gamesTheFlowTitle, desc: () => i18n.gamesTheFlowDesc }, { id: 'neoninfiltrator', title: () => i18n.gamesNeonInfiltratorTitle, desc: () => i18n.gamesNeonInfiltratorDesc }, { id: 'audiopendulum', title: () => i18n.gamesAudioPendulumTitle, desc: () => i18n.gamesAudioPendulumDesc }, + { id: 'visualdj', title: () => i18n.gamesVisualDjTitle, desc: () => i18n.gamesVisualDjDesc }, { id: 'spaceinvaders', title: () => i18n.gamesSpaceInvadersTitle, desc: () => i18n.gamesSpaceInvadersDesc }, { id: 'arkanoid', title: () => i18n.gamesArkanoidTitle, desc: () => i18n.gamesArkanoidDesc }, { id: 'pingpong', title: () => i18n.gamesPingPongTitle, desc: () => i18n.gamesPingPongDesc }, @@ -18,16 +19,16 @@ const getGames = () => [ { id: 'artillery', title: () => i18n.gamesArtilleryTitle, desc: () => i18n.gamesArtilleryDesc }, { id: 'labyrinth', title: () => i18n.gamesLabyrinthTitle, desc: () => i18n.gamesLabyrinthDesc }, { id: 'cocoman', title: () => i18n.gamesCocomanTitle, desc: () => i18n.gamesCocomanDesc }, - { id: 'tetris', title: () => i18n.gamesTetrisTitle, desc: () => i18n.gamesTetrisDesc } + { id: 'tetris', title: () => i18n.gamesTetrisTitle, desc: () => i18n.gamesTetrisDesc }, + { id: 'minesweeper', title: () => i18n.gamesMinesweeperTitle, desc: () => i18n.gamesMinesweeperDesc } ]; const shortId = (feedId) => feedId ? '@' + feedId.slice(1, 9) + '...' : '?'; -const renderHallOfFame = (hall) => { - const games = getGames(); +const renderHallOfFame = (hall, games) => { const gamesWithScores = games.filter(g => hall[g.id] && hall[g.id].length > 0); if (gamesWithScores.length === 0) { - return p({ class: 'no-content' }, i18n.gamesNoScores || 'No scores yet.'); + return div({ class: 'no-content-box' }, p({ class: 'no-content' }, i18n.gamesNoScores || 'No scores yet.')); } return div({ class: 'games-scoring-list' }, gamesWithScores.map(game => @@ -55,7 +56,7 @@ const renderHallOfFame = (hall) => { ); }; -const VALID_GAME_IDS = new Set(['cocoland','ecoinflow','neoninfiltrator','audiopendulum','spaceinvaders','arkanoid','pingpong','asteroids','rockpaperscissors','tiktaktoe','flipflop','8ball','artillery','labyrinth','cocoman','tetris']); +const VALID_GAME_IDS = new Set(['cocoland','ecoinflow','neoninfiltrator','audiopendulum','visualdj','spaceinvaders','arkanoid','pingpong','asteroids','rockpaperscissors','tiktaktoe','flipflop','8ball','artillery','labyrinth','cocoman','tetris','minesweeper']); exports.gameShellView = (name) => { if (!VALID_GAME_IDS.has(name)) { @@ -75,7 +76,7 @@ exports.gameShellView = (name) => { return template( game ? game.title() : name, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.gamesTitle), p(i18n.gamesDescription || 'Discover and play some mini-games in your network.') ), @@ -92,8 +93,9 @@ exports.gameShellView = (name) => { ); }; -exports.gamesView = (filter = 'all', hall = null) => { - const games = getGames(); +exports.gamesView = (filter = 'all', hall = null, q = '') => { + const ql = String(q || '').trim().toLowerCase(); + const games = getGames().filter(g => !ql || `${g.title()} ${g.desc()}`.toLowerCase().includes(ql)); const filterBar = div({ class: 'filter-group' }, form({ method: 'GET', action: '/games' }, @@ -110,8 +112,19 @@ exports.gamesView = (filter = 'all', hall = null) => { ) ); + const searchBox = div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStats(games.length), + form({ method: 'GET', action: '/games', class: 'filter-box' }, + input({ type: 'hidden', name: 'filter', value: filter }), + input({ type: 'text', name: 'q', value: q, placeholder: i18n.gamesSearchPlaceholder, class: 'filter-box__input' }), + div({ class: 'filter-box__controls' }, + button({ type: 'submit', class: 'filter-box__button' }, i18n.searchButton) + ) + ) + ); + const content = filter === 'scoring' && hall - ? renderHallOfFame(hall) + ? renderHallOfFame(hall, games) : div({ class: 'games-single-col' }, games.map(game => { const topScore = hall && hall[game.id] && hall[game.id].length > 0 ? hall[game.id][0] : null; @@ -140,12 +153,13 @@ exports.gamesView = (filter = 'all', hall = null) => { return template( i18n.gamesTitle, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.gamesTitle), p(filter === 'scoring' ? i18n.gamesHallOfFame : (i18n.gamesDescription || 'Discover and play some mini-games in your network.')) ), filterBar ), + section(searchBox), section(content) ); }; diff --git a/src/views/graphos_view.js b/src/views/graphos_view.js index 0a3ba2b..afcc211 100644 --- a/src/views/graphos_view.js +++ b/src/views/graphos_view.js @@ -1,6 +1,7 @@ const h = require("../server/node_modules/hyperaxe"); -const { div, h2, p, section, button, form, input, span, a } = h; -const { template, i18n } = require('./main_views'); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const { div, h2, p, section, button, form, input, span, a, table, tr, td } = h; +const { template, i18n, renderStateChip } = require('./main_views'); const TAU = Math.PI * 2; @@ -26,7 +27,7 @@ const adaptiveLayout = (n) => { return { rRatio: 0.44, nodeR: 14, labelGap: 12 }; }; -const buildGraphSvg = (me, peers) => { +const buildGraphSvg = (me, peers, links = []) => { const W = 900; const H = 600; const cx = W / 2; @@ -48,6 +49,11 @@ const buildGraphSvg = (me, peers) => { const edges = positions.map(({ peer, x, y }) => `` ).join(''); + const at = new Map(positions.map(({ peer, x, y }) => [peer.key, { x, y }])); + const chords = links.filter(l => at.has(l.a) && at.has(l.b)).map(l => { + const A = at.get(l.a), B = at.get(l.b); + return ``; + }).join(''); const nodes = positions.map(({ peer, x, y }) => { const xs = x.toFixed(2); @@ -84,12 +90,26 @@ const buildGraphSvg = (me, peers) => { + ``; return `` - + edges + nodes + center + + chords + edges + nodes + center + ``; }; +const zoomableGraph = (id, svg) => [ + div({ class: 'graphos-zoom-row' }, a({ href: process.env.OASIS_MOBILE === '1' ? `#${id}-focus` : `#${id}`, id: `${id}-src`, class: 'tribe-action-btn graphos-zoom-btn' }, `\u2922 ${i18n.graphosZoom || 'Enlarge'}`)), + div({ class: 'graphos-canvas', [RAW_HTML]: svg }), + div({ id, class: 'lightbox graphos-lightbox' }, + a({ href: `#${id}-src`, class: 'lightbox-close' }, '\u00d7'), + div({ class: 'graphos-canvas-zoom' }, + div({ class: 'graphos-zoom-stage' }, + div({ class: 'graphos-zoom-svg', [RAW_HTML]: svg }), + span({ id: `${id}-focus`, class: 'graphos-zoom-focus' }) + ) + ) + ) +]; + const kpi = (label, value) => div({ class: 'stats-kpi stats-kpi-inline' }, - span({ class: 'stats-kpi-label' }, `${String(label).toUpperCase()}: `), + span({ class: 'stats-kpi-label' }, String(label).toUpperCase()), span({ class: 'stats-kpi-value' }, String(value)) ); @@ -99,16 +119,16 @@ const legendItem = (kind, label) => span(label) ); -exports.graphosView = ({ filter, me, peers, kpis, focus = null, focusId = null, shown = null, total = null }) => { +exports.graphosView = ({ filter, me, peers, links = [], kpis, focus = null, focusId = null, shown = null, total = null }) => { const title = i18n.graphos || 'Graphos'; const description = i18n.graphosDescription || 'Interactive map of the network around you.'; - const modes = ['ALL', 'MINE']; + const modes = ['ALL', 'MINE', 'FEDERATION']; const capped = Number.isFinite(shown) && Number.isFinite(total) && total > shown; return template( title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(title), p(description) ), @@ -126,7 +146,7 @@ exports.graphosView = ({ filter, me, peers, kpis, focus = null, focusId = null, modes.map(m => form({ method: 'GET', action: '/graphos' }, input({ type: 'hidden', name: 'filter', value: m }), - button({ type: 'submit', class: filter === m ? 'filter-btn active' : 'filter-btn' }, String(i18n[m + 'Button']).toUpperCase()) + button({ type: 'submit', class: filter === m ? 'filter-btn active' : 'filter-btn' }, String(m === 'FEDERATION' ? i18n.graphosFederation : i18n[m + 'Button']).toUpperCase()) ) ) ), @@ -141,13 +161,151 @@ exports.graphosView = ({ filter, me, peers, kpis, focus = null, focusId = null, filter !== 'MINE' ? kpi(i18n.unknown || 'Unknown', kpis.unknown) : null ) ), - div({ class: 'graphos-canvas', innerHTML: buildGraphSvg(me, peers) }), + ...zoomableGraph('graphos-zoom', buildGraphSvg(me, peers, links)), div({ class: 'graphos-legend' }, legendItem('me', i18n.graphosYou || 'You'), legendItem('online', i18n.online || 'Online'), filter !== 'MINE' ? legendItem('discovered', i18n.discovered || 'Discovered') : null, - filter !== 'MINE' ? legendItem('unknown', i18n.unknown || 'Unknown') : null + filter !== 'MINE' ? legendItem('unknown', i18n.unknown || 'Unknown') : null, + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-line graphos-fed-line-mutual' }), span(i18n.graphosFedMutual)), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-line graphos-fed-line-oneway' }), span(i18n.graphosFedOneWay)) ) ) ); }; + +const majorMinor = (v) => String(v || '').split('.').slice(0, 2).map(n => parseInt(n, 10) || 0); +const olderThan = (v, own) => { + if (!v || !own) return false; + const [a1, b1] = majorMinor(v), [a2, b2] = majorMinor(own); + return a1 < a2 || (a1 === a2 && b1 < b2); +}; +const NET_REASON_KEYS = require('../models/peer_health').NET_REASON_KEYS; + +const pubLabel = (row) => { + const n = String(row.name || '').trim(); + if (n && n !== String(row.key).replace(/^@/, '').slice(0, 8)) return n.length > 22 ? n.slice(0, 21) + '…' : n; + if (row.host) return row.host.length > 22 ? row.host.slice(0, 21) + '…' : row.host; + return '@' + String(row.key).replace(/^@/, '').slice(0, 8) + '…'; +}; + +const buildFederationSvg = (myName, rows, links, ownVersion) => { + const W = 900; + const H = 640; + const cx = W / 2; + const cy = H / 2; + const N = Math.max(1, rows.length); + const r = Math.min(W, H) * (N <= 6 ? 0.34 : 0.42); + const pos = new Map(rows.map((row, i) => { + const angle = (i / N) * TAU - Math.PI / 2; + return [row.key, { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) }]; + })); + const radius = (row) => 12 + Math.min(14, Math.sqrt(row.inhabitants || 0) * 1.6); + const fed = links.map(l => { + const A = pos.get(l.a), B = pos.get(l.b); + return ``; + }).join(''); + const mine = rows.filter(row => row.state === 'connected' || row.following).map(row => { + const P = pos.get(row.key); + return ``; + }).join(''); + const nodes = rows.map(row => { + const P = pos.get(row.key); + const rr = radius(row); + const old = olderThan(row.version, ownVersion) || (!row.version && row.known); + const name = escText(pubLabel(row)); + const enc = escAttr(encodeURIComponent(row.key)); + const below = (P.y + rr + 14).toFixed(2); + return `` + + `${escText(row.name || row.host || row.key)}${row.version ? ' · ' + escText(row.version) : ''}` + + `` + + (old ? `` : '') + + (row.isolated ? `` : '') + + `` + + `${name}` + + (row.transport === 'tor' ? `TOR` : '') + + ``; + }).join(''); + const me = `${escText(myName)}` + + `` + + `${escText(myName)}`; + return `` + + fed + mine + nodes + me + + ``; +}; + +exports.graphosFederationView = ({ myName, rows = [], links = [], ownVersion = '' }) => { + const title = i18n.graphos || 'Graphos'; + const ORDER = { connected: 0, available: 1, unreachable: 2, unknown: 3 }; + const sorted = rows.slice().sort((x, y) => (ORDER[x.state] - ORDER[y.state]) || (y.inhabitants - x.inhabitants) || pubLabel(x).localeCompare(pubLabel(y))); + const outdated = (row) => olderThan(row.version, ownVersion) || (!row.version && row.known); + const count = (fn) => sorted.filter(fn).length; + const stateChip = (row) => { + if (row.state === 'connected') return renderStateChip('mutuals', null, String(i18n.peerStateConnected).toUpperCase()); + if (row.state === 'available') return renderStateChip(outdated(row) ? 'whole' : 'mutuals', null, String(i18n.peerStateStaged).toUpperCase()); + if (row.state === 'unreachable') return renderStateChip('hidden', null, String(i18n[NET_REASON_KEYS[row.reason] || 'peerErrSilent'] || i18n.peerErrOther).toUpperCase()); + return renderStateChip('hidden', null, String(i18n.graphosFedNoData).toUpperCase()); + }; + const versionChip = (row) => row.version + ? renderStateChip(outdated(row) ? 'closed' : 'mutuals', null, row.version) + : (row.known ? span({ title: i18n.peerVersionUnannounced }, renderStateChip('closed', null, String(i18n.peerOutdated).toUpperCase())) : '—'); + const transportChip = (row) => row.transport === 'unknown' ? '—' + : renderStateChip(row.transport === 'tor' ? 'fediverse' : 'transport', null, String(row.transport === 'tor' ? i18n.peerErrTor : row.transport === 'both' ? i18n.graphosFedBoth : i18n.graphosFedClearnet).toUpperCase()); + const linksCell = (row) => !row.known ? '—' : row.isolated ? renderStateChip('closed', null, String(i18n.graphosFedIsolated).toUpperCase()) : String(row.links); + return template( + title, + section( + div({ class: 'tags-header module-header-line' }, + h2(title), + p(i18n.graphosDescription || '') + ), + div({ class: 'mode-buttons stats-mode-row' }, + ['ALL', 'MINE', 'FEDERATION'].map(m => + form({ method: 'GET', action: '/graphos' }, + input({ type: 'hidden', name: 'filter', value: m }), + button({ type: 'submit', class: m === 'FEDERATION' ? 'filter-btn active' : 'filter-btn' }, String(m === 'FEDERATION' ? i18n.graphosFederation : i18n[m + 'Button']).toUpperCase()) + ) + ) + ), + div({ class: 'stats-block graphos-stats' }, + div({ class: 'stats-grid' }, + kpi(i18n.graphosFedPubs, sorted.length), + kpi(i18n.peerStateConnected, count(r => r.state === 'connected')), + kpi(i18n.graphosFedUnreachable, count(r => r.state === 'unreachable')), + kpi(i18n.peerOutdated, count(outdated)), + kpi(i18n.peerErrTor, count(r => r.transport === 'tor')), + kpi(i18n.graphosFedLinks, links.filter(l => l.mutual).length), + kpi(i18n.graphosFedIsolated, count(r => r.isolated)) + ) + ), + ...(sorted.length ? zoomableGraph('graphos-fed-zoom', buildFederationSvg(myName || '', sorted, links, ownVersion)) : [p(i18n.graphosFedEmpty)]), + div({ class: 'graphos-legend' }, + legendItem('me', i18n.graphosYou || 'You'), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-dot graphos-fed-dot-connected' }), span(i18n.peerStateConnected)), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-dot graphos-fed-dot-available' }), span(i18n.peerStateStaged)), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-dot graphos-fed-dot-unreachable' }), span(i18n.graphosFedUnreachable)), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-dot graphos-fed-dot-outdated' }), span(i18n.peerOutdated)), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-line graphos-fed-line-mutual' }), span(i18n.graphosFedMutual)), + span({ class: 'graphos-legend-item' }, span({ class: 'graphos-legend-line graphos-fed-line-oneway' }), span(i18n.graphosFedOneWay)) + ), + sorted.length ? table({ class: 'block-info-table graphos-fed-table' }, + tr( + td({ class: 'card-label' }, 'PUB'), + td({ class: 'card-label' }, i18n.peersOasisVersion), + td({ class: 'card-label' }, i18n.graphosFedTransport), + td({ class: 'card-label' }, i18n.peerStateLabel), + td({ class: 'card-label' }, i18n.graphosFedWith), + td({ class: 'card-label' }, i18n.graphosFedInhabitants) + ), + ...sorted.map(row => tr( + td({ 'data-label': 'PUB' }, a({ href: `/author/${encodeURIComponent(row.key)}`, class: 'user-link', title: row.host || row.key }, pubLabel(row))), + td({ 'data-label': i18n.peersOasisVersion }, versionChip(row)), + td({ 'data-label': i18n.graphosFedTransport }, transportChip(row)), + td({ 'data-label': i18n.peerStateLabel }, stateChip(row)), + td({ 'data-label': i18n.graphosFedWith }, linksCell(row)), + td({ 'data-label': i18n.graphosFedInhabitants }, row.known ? String(row.inhabitants) : '—') + )) + ) : null + ) + ); +}; diff --git a/src/views/housing_view.js b/src/views/housing_view.js index 92c5ab3..a0b5d45 100644 --- a/src/views/housing_view.js +++ b/src/views/housing_view.js @@ -1,10 +1,11 @@ -const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, img, video, table, tr, td } = require("../server/node_modules/hyperaxe") +const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, img, video, table, tr, td, hr } = require("../server/node_modules/hyperaxe") const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, userLink, renderOpenClosedChip, renderStateChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderOpinionsVoting, renderEngagement, renderSpreadEditWarning } = require("./main_views") +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view") +const { clearnetItemHref, template, i18n, renderOpenClosedChip, renderStateChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderOpinionsVoting, renderEngagement, renderSpreadEditWarning, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, contentDeleteAction, paged, renderCreatedRows } = require("./main_views") const { blobUrl, blobIdOf, isVideoEntry, imagesOf, renderMediaThumb, renderPhotoGallery, renderGalleryFields } = require("./gallery_view") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const opinionCategories = require("../backend/opinion_categories") const { renderMapEmbed, renderMapLocationVisitLabel } = require("./maps_view") @@ -27,6 +28,10 @@ const MAX_IMAGES = 8 const safeArr = (v) => (Array.isArray(v) ? v : []) const safeText = (v) => String(v || "").trim() +const clearnetEligible = (item) => String(item.status || "OPEN").toUpperCase() === "OPEN" && String(item.visibility || "PUBLIC").toUpperCase() === "PUBLIC" && !item.encrypted && !item.tribeId +const housingClearnetHref = (item) => clearnetItemHref("housing", item.title, item.rootId || item.id) +const HOUSING_REACH = ["PUBLIC", "HIDDEN"] +const housingReachLabel = (v) => v === "HIDDEN" ? (i18n.visibilityHidden || "Hidden") : (i18n.visibilityPublic || "Public") const parseNum = (v) => { const n = parseFloat(String(v ?? "").replace(",", ".")) @@ -110,7 +115,7 @@ const renderInfoTable = (item) => { const mapNode = renderMapLocationVisitLabel(item.mapUrl) if (mapNode) pushRow(i18n.mapLocationTitle || "Map", mapNode) } - return table({ class: "tribe-info-table housing-info-table" }, ...rows) + return table({ class: "tribe-info-table housing-info-table" }, ...rows, ...renderCreatedRows(item.createdAt, item.author)) } const today = () => moment().format("YYYY-MM-DD") @@ -135,10 +140,6 @@ const renderOwnerActions = (item, returnTo) => { form({ method: "GET", action: `/housing/edit/${encodeURIComponent(item.id)}` }, input({ type: "hidden", name: "returnTo", value: returnTo }), button({ class: "update-btn", type: "submit" }, i18n.housingUpdateButton) - ), - form({ method: "POST", action: `/housing/delete/${encodeURIComponent(item.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.housingDeleteButton) ) ] } @@ -161,6 +162,7 @@ const renderRequestToggle = (item, returnTo) => { const renderHousingList = (items, filter, params = {}) => { const list = safeArr(items) if (!list.length) return p(i18n.housingNoItems) + const returnTo = buildReturnTo(filter, params) return div({ class: "housing-grid" }, list.map((item) => { @@ -169,22 +171,23 @@ const renderHousingList = (items, filter, params = {}) => { const isOwn = item.author && String(item.author) === String(userId) const requested = safeArr(item.requests).includes(userId) const chips = [ + item.visibility === "HIDDEN" ? renderVisibilityChip("HIDDEN", i18n) : null, renderTypeChip(item), renderStatusChip(item.status), - item.visibility === "HIDDEN" ? renderVisibilityChip("HIDDEN", i18n) : null, requested ? renderRequestedChip() : null, - renderLifespanChip(item.lifetime, i18n) + renderLifespanChip(item.lifetime, i18n), + item.clearnet === true && clearnetEligible(item) ? renderReachChip(true, i18n, housingClearnetHref(item)) : null ].filter(Boolean) return div({ class: "trending-card housing-card" + (isOwn ? " own-content" : "") }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(item.id, `/housing/${encodeURIComponent(item.id)}`, { spread: params.spreadMap && params.spreadMap.get(item.id) || null, author: item.author, favKind: 'housing', isFavorite: item.isFavorite, reportTitle: item.title }) + renderContentActions(item.id, `/housing/${encodeURIComponent(item.id)}`, { spread: params.spreadMap && params.spreadMap.get(item.id) || null, author: item.author, favKind: 'housing', isFavorite: item.isFavorite, reportTitle: item.title, returnTo, deleteAction: isOwn ? contentDeleteAction("housing", item.id) : null }) ), div({ class: "card-section housing-card-body" }, clip || (cover && isVideoEntry(cover)) ? div({ class: "tribe-card-image-wrapper housing-card-video" }, - video({ controls: true, class: "housing-card-hero-video", src: blobUrl(blobIdOf(clip || cover)) }) + video({ controls: true, class: "housing-card-hero-video", src: blobUrl(blobIdOf(clip || cover)), preload: 'metadata' }) ) : cover ? div({ class: "tribe-card-image-wrapper" }, @@ -201,10 +204,10 @@ const renderHousingList = (items, filter, params = {}) => { ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, renderStarRating(item.opinions, safeArr(item.opinions_inhabitants).length), - div({ class: "card-date-highlight" }, priceLabel(item)), + div({ class: "price-chip" }, priceLabel(item)), safeText(item.place) - ? div({ class: "card-field" }, - span({ class: "card-label" }, `${i18n.housingPlace}: `), + ? div({ class: "card-field card-field-mb" }, + span({ class: "card-label" }, `${String(i18n.housingPlace).toUpperCase()}: `), span({ class: "card-value" }, safeText(item.place)) ) : null, @@ -224,17 +227,36 @@ const renderHousingList = (items, filter, params = {}) => { ) } -const renderHousingForm = (item = {}, mode = "create", maxImages = MAX_IMAGES, spreadWarning = null) => { +const renderHousingForm = (item = {}, mode = "create", maxImages = MAX_IMAGES, spreadWarning = null, params = {}) => { const isEdit = mode === "edit" const type = String(item.housing_type || "").toLowerCase() + const reachRaw = String(params.reach || "").toUpperCase() + const ownReach = String(item.visibility || "").toUpperCase() + const reach = HOUSING_REACH.includes(reachRaw) ? reachRaw : (HOUSING_REACH.includes(ownReach) ? ownReach : "PUBLIC") + const showClearnet = reach === "PUBLIC" && clearnetEligible({ ...item, visibility: reach }) return div({ class: "div-center housing-form" }, + isEdit ? spreadWarning : null, + form({ method: "GET", action: isEdit ? `/housing/edit/${encodeURIComponent(item.id)}` : "/housing" }, + isEdit ? null : input({ type: "hidden", name: "filter", value: "CREATE" }), + label(i18n.visibilityLabel || "Visibility"), + br(), + div({ class: "apply-row" }, + select({ name: "visibility", class: "report-category-select" }, + option({ value: "PUBLIC", ...(reach === "PUBLIC" ? { selected: true } : {}) }, i18n.visibilityPublic || "Public"), + option({ value: "HIDDEN", ...(reach === "HIDDEN" ? { selected: true } : {}) }, i18n.visibilityHidden || "Hidden") + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, housingReachLabel(reach)), form({ action: isEdit ? `/housing/update/${encodeURIComponent(item.id)}` : "/housing/create", method: "POST", enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: "/housing?filter=MINE" }), - isEdit ? spreadWarning : null, + input({ type: "hidden", name: "visibility", value: reach }), label(i18n.housingType), br(), select({ name: "housing_type", required: true }, @@ -263,12 +285,12 @@ const renderHousingForm = (item = {}, mode = "create", maxImages = MAX_IMAGES, s ...renderGalleryFields(item, isEdit, maxImages), label(i18n.housingDescription), br(), - textarea({ name: "description", rows: "6", required: true, placeholder: i18n.housingDescriptionPlaceholder }, item.description || ""), + textarea({ maxlength: "5000", name: "description", rows: "6", required: true, placeholder: i18n.housingDescriptionPlaceholder }, item.description || ""), br(), br(), label(i18n.housingRules), br(), - textarea({ name: "rules", rows: "4", placeholder: i18n.housingRulesPlaceholder }, item.rules || ""), + textarea({ maxlength: "5000", name: "rules", rows: "4", placeholder: i18n.housingRulesPlaceholder }, item.rules || ""), br(), br(), label(i18n.housingPlace), @@ -284,6 +306,7 @@ const renderHousingForm = (item = {}, mode = "create", maxImages = MAX_IMAGES, s label(i18n.housingPriceLabel), br(), input({ type: "number", name: "price", step: "0.01", min: "0", value: item.price || "" }), + renderEcoValueChip(), br(), br(), label(i18n.housingRooms), @@ -303,7 +326,7 @@ const renderHousingForm = (item = {}, mode = "create", maxImages = MAX_IMAGES, s br(), label(i18n.housingAvailableFrom), br(), - input({ type: "date", name: "availableFrom", required: true, min: isEdit ? undefined : today(), value: item.availableFrom ? String(item.availableFrom).slice(0, 10) : "" }), + input({ type: "date", name: "availableFrom", required: true, min: today(), value: item.availableFrom ? String(item.availableFrom).slice(0, 10) : "" }), br(), br(), label(i18n.housingAvailableTo), @@ -313,23 +336,28 @@ const renderHousingForm = (item = {}, mode = "create", maxImages = MAX_IMAGES, s br(), label(i18n.housingTags), br(), - input({ type: "text", name: "tags", value: Array.isArray(item.tags) ? item.tags.join(", ") : (item.tags || "") }), - br(), - br(), - label(i18n.visibilityLabel || "Visibility"), - br(), - select({ name: "visibility" }, - option({ value: "PUBLIC", ...((item.visibility || "PUBLIC") === "PUBLIC" ? { selected: true } : {})}, i18n.visibilityPublic || "Public"), - option({ value: "HIDDEN", ...(item.visibility === "HIDDEN" ? { selected: true } : {})}, i18n.visibilityHidden || "Hidden") - ), + input({ type: "text", name: "tags", placeholder: i18n.tagsPlaceholder, value: Array.isArray(item.tags) ? item.tags.join(", ") : (item.tags || "") }), br(), br(), + ...(showClearnet ? [renderClearnetSelector(item.clearnet === true || String(item.clearnet || "") === "1", i18n), br()] : []), button({ type: "submit" }, isEdit ? i18n.housingUpdateButton : i18n.housingCreateButton) ) ) } -const renderFiltersBar = (filter, params = {}) => +const housingChip = (censusH, filter, x) => { + const m = x.key + if (m === filter) return true + if (m === "TOP") return censusH.length > 0 + if (m === "MINE") return censusH.some(h => String(h.author) === String(userId)) + if (m === "RECENT") return censusH.length > 0 + if (m === "REQUESTED") return censusH.some(h => safeArr(h.requests).includes(userId)) + if (m === "SALE" || m === "RENT" || m === "COUCHSURFING") return censusH.some(h => String(h.housing_type || "").toUpperCase() === m) + if (m === "OPEN" || m === "CLOSED") return censusH.some(h => String(h.status || "OPEN").toUpperCase() === m) + return true +} + +const renderFiltersBar = (filter, params = {}, emptyMod = false, censusH = null) => div({ class: "filters" }, form({ method: "GET", action: "/housing", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "search", value: safeText(params.search || "") }), @@ -337,9 +365,9 @@ const renderFiltersBar = (filter, params = {}) => input({ type: "hidden", name: "maxPrice", value: String(params.maxPrice ?? "") }), input({ type: "hidden", name: "place", value: safeText(params.place || "") }), input({ type: "hidden", name: "sort", value: safeText(params.sort || "") }), - ...FILTERS.map(f => + ...(emptyMod ? [] : (censusH ? FILTERS.filter(x => housingChip(censusH, filter, x)) : FILTERS).map(f => button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, String(i18n[f.i18n]).toUpperCase()) - ), + )), button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.housingCreateButton) ) ) @@ -352,42 +380,39 @@ exports.housingView = async (items, filter = "ALL", params = {}) => { const sort = safeText(params.sort || "recent") const isForm = filter === "CREATE" || filter === "EDIT" + const emptyMod = moduleIsEmpty(Array.isArray(items) ? items : [], filter, "ALL", search || place || String(minPrice || "") || String(maxPrice || "")) return template( i18n.housingTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.housingTitle), p(i18n.housingDescriptionText) ), - renderFiltersBar(filter, { search, minPrice, maxPrice, place, sort }) + renderFiltersBar(filter, { search, minPrice, maxPrice, place, sort }, emptyMod, Array.isArray(params.censusList) ? params.censusList : (Array.isArray(items) ? items : [])) ), section( isForm - ? renderHousingForm(filter === "EDIT" ? (Array.isArray(items) ? items[0] : items) || {} : (params.draft || {}), filter === "EDIT" ? "edit" : "create", Number(params.maxImages) > 0 ? Number(params.maxImages) : MAX_IMAGES, await renderSpreadEditWarning(filter === "EDIT" ? ((Array.isArray(items) ? items[0] : items) || {}).id : null)) + ? renderHousingForm(filter === "EDIT" ? (Array.isArray(items) ? items[0] : items) || {} : (params.draft || {}), filter === "EDIT" ? "edit" : "create", Number(params.maxImages) > 0 ? Number(params.maxImages) : MAX_IMAGES, await renderSpreadEditWarning(filter === "EDIT" ? ((Array.isArray(items) ? items[0] : items) || {}).id : null), params) : section( - div({ class: "housing-search" }, + emptyMod ? null : div({ class: "housing-search activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(items, it => String(it.status || '').toUpperCase(), [{ value: 'OPEN', label: i18n.housingFilterOpen }, { value: 'CLOSED', label: i18n.housingFilterClosed }]), form({ method: "GET", action: "/housing", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter || "ALL" }), input({ type: "text", name: "search", value: search, placeholder: i18n.housingSearchPlaceholder, class: "filter-box__input" }), - div({ class: "filter-box__controls" }, - input({ type: "text", name: "place", value: place, placeholder: i18n.housingPlacePlaceholder, class: "filter-box__input housing-place-input" }), - div({ class: "transfer-range" }, - input({ type: "number", name: "minPrice", step: "0.01", min: "0", value: String(minPrice ?? ""), placeholder: i18n.housingMinPrice, class: "filter-box__number transfer-amount-input" }), - input({ type: "number", name: "maxPrice", step: "0.01", min: "0", value: String(maxPrice ?? ""), placeholder: i18n.housingMaxPrice, class: "filter-box__number transfer-amount-input" }) - ), - select({ name: "sort", class: "filter-box__select" }, + input({ type: "text", name: "place", value: place, placeholder: i18n.housingPlacePlaceholder, class: "filter-box__input housing-place-input" }), + input({ type: "number", name: "minPrice", step: "0.01", min: "0", value: String(minPrice ?? ""), placeholder: i18n.housingMinPrice, class: "filter-box__number transfer-amount-input" }), + input({ type: "number", name: "maxPrice", step: "0.01", min: "0", value: String(maxPrice ?? ""), placeholder: i18n.housingMaxPrice, class: "filter-box__number transfer-amount-input" }), + select({ name: "sort", class: "filter-box__select" }, option({ value: "recent", ...(sort === "recent" ? { selected: true } : {})}, i18n.housingSortRecent), option({ value: "price", ...(sort === "price" ? { selected: true } : {})}, i18n.housingSortPrice), option({ value: "requests", ...(sort === "requests" ? { selected: true } : {})}, i18n.housingSortRequests), option({ value: "rating", ...(sort === "rating" ? { selected: true } : {})}, i18n.housingSortRating) ), button({ type: "submit", class: "filter-box__button" }, i18n.housingSearchButton) - ) ) ), - br(), - div({ class: "housing-list" }, renderHousingList(items, filter, params)) + div({ class: "housing-list" }, renderHousingList(paged(items), filter, params)) ) ) ) @@ -406,14 +431,18 @@ exports.singleHousingView = async (item, filter = "ALL", comments = [], params = const isAuthor = String(item.author) === String(userId) const requested = safeArr(item.requests).includes(userId) const voters = safeArr(item.opinions_inhabitants) + const canClearnet = clearnetEligible(item) + const isClearnet = canClearnet && !!item.clearnet const chips = [ + item.visibility === "HIDDEN" ? renderVisibilityChip("HIDDEN", i18n) : null, renderTypeChip(item), renderStatusChip(item.status), - item.visibility === "HIDDEN" ? renderVisibilityChip("HIDDEN", i18n) : null, requested ? renderRequestedChip() : null, renderLifespanChip(item.lifetime, i18n), - renderEcoTax(item.msgSize, item.id) + renderEcoTax(item.msgSize, item.id), + renderReachChip(isClearnet, i18n, isClearnet ? housingClearnetHref(item) : null), + isAuthor && canClearnet ? renderClearnetSwitch("housing", item.rootId || item.id, isClearnet) : null ].filter(Boolean) const sideActions = [] @@ -441,7 +470,7 @@ exports.singleHousingView = async (item, filter = "ALL", comments = [], params = const cover = imagesOf(item)[0] const housingSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(item.id, null, { spread: params.spreads || null, author: item.author, favKind: 'housing', isFavorite: item.isFavorite, reportTitle: item.title }) + renderContentActions(item.id, `/housing/${encodeURIComponent(item.id)}`, { spread: params.spreads || null, author: item.author, favKind: 'housing', isFavorite: item.isFavorite, reportTitle: item.title, returnTo, deleteAction: isAuthor ? contentDeleteAction("housing", item.id) : null }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, safeText(item.title) || i18n.housingTitle) @@ -464,7 +493,7 @@ exports.singleHousingView = async (item, filter = "ALL", comments = [], params = safeText(bodyText) ? div({ class: "job-section" }, h2({ class: "job-section-title" }, titleText), - p({ class: "tribe-side-description" }, ...renderUrl(bodyText)) + p({ class: "tribe-side-description" }, ...renderStyledText(bodyText)) ) : null @@ -473,10 +502,6 @@ exports.singleHousingView = async (item, filter = "ALL", comments = [], params = renderSection(i18n.housingDescription, item.description), renderSection(i18n.housingRules, item.rules), item.mapUrl ? div({ class: "job-section" }, renderMapEmbed(params.mapData, item.mapUrl)) : null, - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(item.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(item.author) - ), renderEngagement(item.id, !isAuthor && item.everRequestedByViewer ? renderOpinionsVoting("/housing/opinions", item.id, item.opinions, returnTo, voters) @@ -488,9 +513,67 @@ exports.singleHousingView = async (item, filter = "ALL", comments = [], params = return template( i18n.housingTitle, section( - div({ class: "tags-header" }, h2(i18n.housingTitle), p(i18n.housingDescriptionText)), + div({ class: "tags-header module-header-line" }, h2(i18n.housingTitle), p(i18n.housingDescriptionText)), renderFiltersBar(filter, params), div({ class: "tribe-details" }, housingSide, housingMain) ) ) } + +exports.clearnetHousingView = async (item, params = {}) => { + const { escapeHtml: esc, renderRichText, renderKindTag, renderTagChips, blobUrl: cnBlob, renderClearnetPage } = require("./clearnet_view") + const title = esc(item.title || i18n.housingTitle) + const point = params.mapPoint || null + const media = imagesOf(item).map(entry => ({ src: cnBlob(entry), video: isVideoEntry(entry) })).filter(m => m.src) + const clip = cnBlob(item.video) + if (clip && !media.some(m => m.src === clip)) media.push({ src: clip, video: true }) + const cover = (media.find(m => !m.video) || {}).src || null + const day = (v) => moment(v).format("YYYY/MM/DD") + const rows = [ + [i18n.housingProperty, i18n["housingProperty" + String(item.property_type || "").toUpperCase()] || ""], + [i18n.housingRooms, item.rooms > 0 ? String(item.rooms) : ""], + [i18n.housingSize, item.size > 0 ? `${item.size} m²` : ""], + [i18n.housingCapacity, item.capacity > 0 ? String(item.capacity) : ""], + [i18n.housingAvailableFrom, item.availableFrom ? day(item.availableFrom) : ""], + [i18n.housingAvailableTo, item.availableTo ? day(item.availableTo) : ""] + ].filter(([, v]) => v) + const textSection = (heading, text) => safeText(text) ? `

${esc(heading)}

${renderRichText(text)}
` : "" + const extraCss = ` +.cn-housing-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700} +.cn-housing-meta{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px} +.cn-housing-meta-item{background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;padding:8px 14px;font-size:14px;color:var(--fg-soft);display:inline-flex;align-items:center;gap:6px} +.cn-housing-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin:0 0 20px 0} +.cn-housing-gallery img,.cn-housing-gallery video{width:100%;height:auto;display:block;border:1px solid var(--border);border-radius:6px;background:#000} +.cn-housing-info{border-collapse:collapse;margin:0 0 20px 0} +.cn-housing-info td{padding:6px 14px 6px 0;font-size:14px;color:var(--fg-soft);border-bottom:1px solid var(--border)} +.cn-housing-info td:first-child{color:var(--fg-dim);text-transform:uppercase;letter-spacing:1px;font-size:12px} +.cn-housing-section h2{color:var(--fg);font-size:18px;text-transform:uppercase;letter-spacing:2px;margin:24px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)} +.cn-housing-text{color:var(--fg-soft);line-height:1.6;font-size:15px;word-break:break-word} +` + const body = ` +

${title}

+
+ ${renderKindTag("housing")} + ${esc(i18n["housingType" + String(item.housing_type || "").toUpperCase()] || item.housing_type || "")} + ${safeText(item.place) ? `📍 ${esc(item.place)}` : ""} + ${point ? `⌖ ${point.title ? `${esc(point.title)} · ` : ""}${point.lat}, ${point.lng}` : ""} + ${item.createdAt ? `📅 ${esc(day(item.createdAt))}` : ""} + ${esc(priceLabel(item))} +
+
+ ${media.length ? `` : ""} + ${rows.length ? `${rows.map(([k, v]) => ``).join("")}
${esc(k)}${esc(v)}
` : ""} + ${textSection(i18n.housingDescription, item.description)} + ${textSection(i18n.housingRules, item.rules)} + ${renderTagChips(item.tags)} +` + return renderClearnetPage({ + title: `${item.title || i18n.housingTitle} | Oasis`, + ogTitle: item.title || i18n.housingTitle, + ogDescription: item.description || "", + ogImage: cover, + extraCss, + body, + hubFeedId: item.author || null + }) +} diff --git a/src/views/image_view.js b/src/views/image_view.js index 8071fac..18b144e 100644 --- a/src/views/image_view.js +++ b/src/views/image_view.js @@ -3,10 +3,12 @@ const { form, button, div, h2, p, section, input, label, br, a, img, span, texta const { renderCommentsSection: renderSharedCommentsSection, renderCommentsLink } = require("./comments_view"); const moment = require("../server/node_modules/moment"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderStateChip, renderContentActions , renderSpreadEditWarning } = require("./main_views"); +const { renderLicenseChip, renderLicenseSelect, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require('./clearnet_view'); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderStateChip, renderContentActions , renderSpreadEditWarning, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl") -const { renderMapLocationVisitLabel } = require("./maps_view"); +const { renderStyledText } = require("../backend/renderStyledText") +const { renderMapLocationVisitLabel, renderMapEmbed } = require("./maps_view"); +const { renderZoomableImage } = require("./gallery_view"); const userId = config.keys.id; @@ -39,14 +41,7 @@ const renderImageMedia = (imgObj, filter, params = {}) => { return imgObj?.url ? div( { class: "image-container image-container-row" }, - a( - { - href: `/images/${encodeURIComponent(imgObj.key)}?filter=${encodeURIComponent(filter || "all")}${ - params.q ? `&q=${encodeURIComponent(params.q)}` : "" - }${params.sort ? `&sort=${encodeURIComponent(params.sort)}` : ""}` - }, - img({ src, alt: imgObj.title || "", class: "media-preview", loading: "lazy" }) - ) + renderZoomableImage(src, { alt: imgObj.title || "", imgClass: "post-image" }) ) : p(i18n.imageNoFile); }; @@ -56,27 +51,15 @@ const renderImageOwnerActions = (filter, imgObj, params = {}) => { const isAuthor = String(imgObj.author) === String(userId); const hasOpinions = Object.keys(imgObj.opinions || {}).length > 0; - if (!isAuthor) return []; + if (!isAuthor || hasOpinions) return []; - const items = []; - if (!hasOpinions) { - items.push( - form( - { method: "GET", action: `/images/edit/${encodeURIComponent(imgObj.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "update-btn", type: "submit" }, i18n.imageUpdateButton) - ) - ); - } - items.push( + return [ form( - { method: "POST", action: `/images/delete/${encodeURIComponent(imgObj.key)}` }, + { method: "GET", action: `/images/edit/${encodeURIComponent(imgObj.key)}` }, input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.imageDeleteButton) + button({ class: "update-btn", type: "submit" }, i18n.imageUpdateButton) ) - ); - - return items; + ]; }; const renderImageList = exports.renderImageList = (images, filter, params = {}) => { @@ -87,24 +70,34 @@ const renderImageList = exports.renderImageList = (images, filter, params = {}) const title = safeText(imgObj.title); const isOwn = imgObj.author && String(imgObj.author) === String(userId); + const headerActions = typeof params.headerActions === "function" + ? params.headerActions(imgObj) + : imgObj.tribeOrigin + ? renderContentActions(null, imgObj.tribeOrigin.href) + : renderContentActions(imgObj.key, `/images/${encodeURIComponent(imgObj.key)}`, { spread: (params.spreadMap && params.spreadMap.get(imgObj.key)) || params.spreads || null, author: imgObj.author, favKind: 'images', torrentFrom: { blobId: imgObj.url, name: imgObj.title }, isFavorite: imgObj.isFavorite, reportTitle: imgObj.title, returnTo, deleteAction: isOwn ? contentDeleteAction("image", imgObj.key) : null }); + const titleHref = typeof params.headerActions === "function" ? null : imgObj.tribeOrigin ? imgObj.tribeOrigin.href : `/images/${encodeURIComponent(imgObj.key)}`; + const engagement = typeof params.engagement === "function" + ? params.engagement(imgObj) + : imgObj.tribeOrigin ? null : renderEngagement(imgObj.key, + renderOpinionsVoting('/images/opinions', imgObj.key, imgObj.opinions, returnTo, imgObj.opinions_inhabitants), + renderCommentsLink({ href: `/images/${encodeURIComponent(imgObj.key)}`, count: commentCount }) + ); return div( { class: "trending-card image-card" + (isOwn ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(imgObj.key, `/images/${encodeURIComponent(imgObj.key)}`, { spread: (params.spreadMap && params.spreadMap.get(imgObj.key)) || params.spreads || null, author: imgObj.author, favKind: 'images', isFavorite: imgObj.isFavorite, reportTitle: imgObj.title }) + headerActions ), div( { class: "card-section image-card-body" }, - title ? h2(title) : null, + div({ class: "shop-title-row" }, title ? h2(titleHref ? a({ href: titleHref, class: "card-title-link" }, title) : title) : null, imgObj.tribeOrigin ? renderTribeOriginChip(imgObj.tribeOrigin) : imgObj.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('images', imgObj.title, imgObj.key)) : null, renderLicenseChip(imgObj.license), ...(typeof params.titleChips === "function" ? params.titleChips(imgObj) : [])), imgObj.lifetime ? div({ class: "card-chips-row" }, imgObj.lifetime ? renderLifespanChip(imgObj.lifetime, i18n) : null ) : null, renderImageMedia(imgObj, filter, params), - renderEngagement(imgObj.key, - renderOpinionsVoting('/images/opinions', imgObj.key, imgObj.opinions, returnTo, imgObj.opinions_inhabitants), - renderCommentsLink({ href: `/images/${encodeURIComponent(imgObj.key)}`, count: commentCount }) - ), + ...(typeof params.bodyExtra === "function" ? params.bodyExtra(imgObj) : []), + engagement, renderMapLocationVisitLabel(imgObj.mapUrl), br(), (() => { @@ -114,7 +107,7 @@ const renderImageList = exports.renderImageList = (images, filter, params = {}) return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(imgObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(imgObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(imgObj.author), showUpdated ? span( @@ -151,7 +144,7 @@ const renderImageForm = (filter, imageId, imageToEdit, params = {}) => { br(), br(), imageToEdit?.url - ? img({ src: `/blob/${encodeURIComponent(imageToEdit.url)}`, class: "media-preview", alt: imageToEdit?.title || "" }) + ? img({ loading: 'lazy', src: `/blob/${encodeURIComponent(imageToEdit.url)}`, class: "media-preview", alt: imageToEdit?.title || "" }) : null, label(i18n.imageTitleLabel), br(), @@ -159,7 +152,7 @@ const renderImageForm = (filter, imageId, imageToEdit, params = {}) => { br(), label(i18n.imageDescriptionLabel), br(), - textarea({ name: "description", placeholder: i18n.imageDescriptionPlaceholder, rows: "4" }, imageToEdit?.description || ""), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.imageDescriptionPlaceholder, rows: "4" }, imageToEdit?.description || ""), br(), label(i18n.mapLocationTitle || "Map Location"), br(), @@ -169,6 +162,9 @@ const renderImageForm = (filter, imageId, imageToEdit, params = {}) => { br(), input({ type: "text", name: "tags", placeholder: i18n.imageTagsPlaceholder, value: tagsValue }), br(), + ...renderLicenseSelect(imageToEdit?.license, i18n), + br(), + renderClearnetSelector(filter === "edit" ? !!imageToEdit?.clearnet : false, i18n), br(), button({ type: "submit" }, filter === "edit" ? i18n.imageUpdateButton : i18n.imageCreateButton) ) @@ -183,7 +179,7 @@ const renderGallery = (images) => { images.map((imgObj) => { const src = imgObj.url ? `/image/256/${encodeURIComponent(imgObj.url)}` : ""; return a( - { href: `#img-${encodeURIComponent(imgObj.key)}`, class: "gallery-item" }, + { href: `#img-${encodeURIComponent(imgObj.key)}`, id: `img-${encodeURIComponent(imgObj.key)}-src`, class: "gallery-item" }, img({ src, alt: imgObj.title || "", class: "gallery-image", loading: "lazy" }) ); }) @@ -195,7 +191,7 @@ const renderLightbox = (images) => const src = imgObj.url ? `/blob/${encodeURIComponent(imgObj.url)}` : ""; return div( { id: `img-${encodeURIComponent(imgObj.key)}`, class: "lightbox" }, - a({ href: "#", class: "lightbox-close" }, "×"), + a({ href: `#img-${encodeURIComponent(imgObj.key)}-src`, class: "lightbox-close" }, "×"), img({ src, class: "lightbox-image", alt: imgObj.title || "" }) ); }); @@ -208,6 +204,18 @@ const renderImageCommentsSection = (imageKey, comments = [], returnTo = null) => }); }; +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.some((x) => !x.tribeOrigin); + if (mode === "gallery") return censusM.length > 0; + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + exports.imageView = async (images, filter = "all", imageId = null, params = {}) => { if (filter === "edit") params = { ...params, spreadWarning: await renderSpreadEditWarning(imageId) }; const title = i18n.imageTitle; @@ -216,39 +224,38 @@ exports.imageView = async (images, filter = "all", imageId = null, params = {}) const sort = safeText(params.sort || "recent"); const list = safeArr(images); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); const imageToEdit = imageId ? list.find((im) => im.key === imageId) : null; return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.imageDescription) ), - (() => { - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetImages); - return div({ class: "shop-title-row" }, renderReachChip(isClearnet, i18n)); - })(), - br(), div( { class: "filters" }, form( { method: "GET", action: "/images", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterRecent).toUpperCase()), button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterFavorites).toUpperCase() - ), + )] : []), - button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterTop).toUpperCase()), - button( + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterTop).toUpperCase())] : []), + ...(mediaChip("gallery") ? [button( { type: "submit", name: "filter", value: "gallery", class: filter === "gallery" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterGallery).toUpperCase() - ), + )] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.imageCreateButton) ) ) @@ -257,8 +264,9 @@ exports.imageView = async (images, filter = "all", imageId = null, params = {}) filter === "create" || filter === "edit" ? renderImageForm(filter, imageId, imageToEdit, { ...params, filter }) : section( - div( - { class: "images-search" }, + emptyMod ? null : div( + { class: "images-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form( { method: "GET", action: "/images", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -281,21 +289,22 @@ exports.imageView = async (images, filter = "all", imageId = null, params = {}) ) ) ), - filter === "gallery" ? renderGallery(list) : div({ class: "images-list" }, renderImageList(list, filter, { q, sort })) + filter === "gallery" ? renderGallery(paged(list)) : div({ class: "images-list" }, renderImageList(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) ) ), - ...(filter === "gallery" ? renderLightbox(list) : []) + ...(filter === "gallery" ? renderLightbox(paged(list)) : []) ); }; exports.singleImageView = async (imageObj, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); const q = safeText(params.q || ""); const sort = safeText(params.sort || "recent"); const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q, sort }); const title = safeText(imageObj.title); - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetImages); + const isAuthor = String(imageObj.author) === String(userId); + const isClearnet = !!imageObj.clearnet; const chips = [ renderLifespanChip(imageObj.lifetime, i18n), @@ -309,9 +318,10 @@ exports.singleImageView = async (imageObj, filter = "all", comments = [], params const tagsNode = renderTags(imageObj.tags); const detailActions = div({ class: "card-header activity-card-header" }, - renderContentActions(imageObj.key, null, { + renderContentActions(imageObj.key, `/images/${encodeURIComponent(imageObj.key)}`, { author: imageObj.author, - favKind: 'images', + deleteAction: String(imageObj.author) === String(userId) ? contentDeleteAction("image", imageObj.key) : null, + favKind: 'images', torrentFrom: { blobId: imageObj.url, name: imageObj.title }, isFavorite: imageObj.isFavorite, spread: params.spreads || null, returnTo, @@ -322,14 +332,16 @@ exports.singleImageView = async (imageObj, filter = "all", comments = [], params const imageSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, title ? h2({ class: "tribe-card-title" }, title) : null, - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('images', imageObj.title, imageObj.key) : null), + isAuthor ? renderClearnetSwitch('images', imageObj.rootId || imageObj.key, isClearnet) : null, + renderLicenseChip(imageObj.license) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, safeText(imageObj.description) - ? p({ class: "tribe-side-description" }, ...renderUrl(imageObj.description)) + ? p({ class: "tribe-side-description" }, ...renderStyledText(imageObj.description)) : null, tagsNode, - renderMapLocationVisitLabel(imageObj.mapUrl), + renderMapEmbed(params.mapData, imageObj.mapUrl), sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null ); @@ -353,7 +365,7 @@ exports.singleImageView = async (imageObj, filter = "all", comments = [], params return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(imageObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(imageObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(imageObj.author), showUpdated ? span( @@ -372,7 +384,7 @@ exports.singleImageView = async (imageObj, filter = "all", comments = [], params return template( i18n.imageTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.imageAllSectionTitle || i18n.imageTitle), p(i18n.imageDescription) ), @@ -382,18 +394,18 @@ exports.singleImageView = async (imageObj, filter = "all", comments = [], params { method: "GET", action: "/images", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), - button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterRecent).toUpperCase()), button( + button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterAll).toUpperCase()), ...(mediaChip("favorites") ? [button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterMine).toUpperCase())] : []), { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterFavorites).toUpperCase() - ), + )] : []), button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterTop).toUpperCase()), - button( + ...(mediaChip("gallery") ? [button( { type: "submit", name: "filter", value: "gallery", class: filter === "gallery" ? "filter-btn active" : "filter-btn" }, String(i18n.imageFilterGallery).toUpperCase() - ), + )] : []), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.imageCreateButton) ) ), diff --git a/src/views/indexing_view.js b/src/views/indexing_view.js index c2d758b..56d3d65 100644 --- a/src/views/indexing_view.js +++ b/src/views/indexing_view.js @@ -4,7 +4,7 @@ const { getConfig } = require('../configs/config-manager.js'); const doctypeString = ''; -exports.indexingView = ({ percent }) => { +exports.indexingView = ({ percent, refresh = 10 }) => { let metaRefresh; try { const cfg = getConfig() || {}; @@ -12,28 +12,23 @@ exports.indexingView = ({ percent }) => { void theme; } catch (_) {} + const known = percent !== null && percent !== undefined && Number.isFinite(Number(percent)); const pct = Math.max(0, Math.min(100, Number(percent) || 0)); const headingText = i18n.indexingTitle || 'Synchronizing'; - const message = i18n.indexingMessage || 'Oasis is trying to syncronize a huge network of inhabitants. Just wait!'; const refreshNote = i18n.indexingRefreshNote || 'This page refreshes every 10 seconds.'; - const editProfileLabel = i18n.indexingEditProfileLink || 'Set up my avatar'; return template( headingText, section( - div({ class: 'tags-header' }, - h2(`❤ ${headingText}`), - p(message) + div({ class: 'tags-header module-header-line' }, + h2(`❤ ${headingText}`) ), div({ class: 'indexing-progress-block' }, - progress({ value: String(pct), max: '100', class: 'indexing-progress' }), - p({ class: 'indexing-percent' }, strong(`${pct.toFixed(1)} %`)), - p({ class: 'indexing-note' }, refreshNote) - ), - div({ class: 'indexing-actions' }, - a({ href: '/profile/edit', class: 'filter-btn welcome-action-primary' }, editProfileLabel), - a({ href: '/modules', class: 'filter-btn' }, i18n.modulesTitle || 'Modules') + p({ class: 'indexing-message' }, `\ud83c\udf34 ${i18n.indexingMessage || 'Oasis is synchronizing the distributed network of nodes. Please wait.'}`), + known ? progress({ value: String(pct), max: '100', class: 'indexing-progress' }) : progress({ max: '100', class: 'indexing-progress' }), + known ? p({ class: 'indexing-percent' }, strong(`${pct.toFixed(1)} %`)) : null, + p({ class: 'indexing-note' }, String(refreshNote).replace('10', String(Number(refresh) || 10))) ) ) - ).replace('', ''); + ).replace('', ``); }; diff --git a/src/views/industry_view.js b/src/views/industry_view.js index c04be4d..13c7a65 100644 --- a/src/views/industry_view.js +++ b/src/views/industry_view.js @@ -1,9 +1,10 @@ const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, ul, li, img, video, audio, table, thead, tbody, tr, td, th } = require("../server/node_modules/hyperaxe") -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions , renderSpreadEditWarning } = require("./main_views") +const { renderZoomableImage } = require("./gallery_view") +const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderContentActions , renderSpreadEditWarning, renderSubscriptionBox, renderModuleStatsBy, renderCardMetaRow, moduleIsEmpty, paged } = require("./main_views") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") const { renderMapEmbedWithZoom } = require("./maps_view") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const userId = config.keys.id @@ -11,13 +12,13 @@ const renderMediaBlob = (value, attrs = {}) => { if (!value) return null const s = String(value).trim() if (!s) return null - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: attrs.class || 'post-image' }) + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) return null } @@ -26,6 +27,7 @@ const POLICIES = ["open", "vote", "invite"] const FILTERS = [ { key: "ALL", i18n: "industryFilterAll", title: "industryAllTitle" }, + { key: "RECENT", i18n: "industryFilterRecent", title: "industryRecentTitle" }, { key: "MINE", i18n: "industryFilterMine", title: "industryMineTitle" }, { key: "ACTIVE", i18n: "industryFilterActive", title: "industryActiveTitle" }, { key: "PAUSED", i18n: "industryFilterPaused", title: "industryPausedTitle" }, @@ -109,18 +111,22 @@ const renderFacilityList = exports.renderFacilityList = (facilities, filter, spr list.map((fc) => { const isOwn = fc.steward && String(fc.steward) === String(userId) const isMember = safeArr(fc.members).includes(userId) + const memberCount = fc.memberCount != null ? fc.memberCount : safeArr(fc.members).length const href = `/industry/${encodeURIComponent(fc.id)}` const chips = [ renderStatusChip(fc.status), renderStateChip("whole", "🏭", sectorLabel(fc.sector)), renderStateChip("half", "⚖", policyLabel(fc.membershipPolicy)), isMember ? renderStateChip("whole", "★", i18n.industryMemberBadge || "MEMBER") : null, - renderLifespanChip(fc.lifetime, i18n) + renderLifespanChip(fc.lifetime, i18n), + fc.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (fc.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null) ].filter(Boolean) return div({ class: "trending-card tribes-card industry-card" + (isOwn ? " own-content" : "") }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(fc.id || fc.key, href, { spread: spreadMap.get(fc.id || fc.key) || null }) + renderContentActions(fc.id || fc.key, href, { spread: spreadMap.get(fc.id || fc.key) || null, author: fc.steward, reportTitle: fc.name, deleteAction: isOwn && memberCount <= 1 ? `/industry/delete/${encodeURIComponent(fc.id)}` : null }) ), div({ class: "card-section tribes-card-body" }, div({ class: "tribe-card-image-wrapper" }, @@ -138,7 +144,7 @@ const renderFacilityList = exports.renderFacilityList = (facilities, filter, spr div({ class: "card-chips-row" }, ...chips), fc.description ? p({ class: "tribe-card-description" }, safeText(fc.description).slice(0, 220)) : null, div({ class: "tribe-card-members" }, - span({ class: "tribe-members-count" }, `${i18n.industryMembers || "Members"}: ${fc.memberCount != null ? fc.memberCount : safeArr(fc.members).length}`) + span({ class: "tribe-members-count" }, `${i18n.industryMembers || "Members"}: ${memberCount}`) ), ) ) @@ -169,9 +175,13 @@ const renderFacilityForm = (facility, mode, spreadWarning = null) => { br(), textarea({ name: "description", rows: "5", maxlength: "2000", placeholder: i18n.industryDescriptionPlaceholder || "What does this facility produce?" }, fc.description || ""), br(), - label(i18n.uploadMedia || "Upload media (max-size: 50MB)"), + label(i18n.uploadMedia || "Upload media (max-size: 75MB)"), br(), input({ type: "file", name: "image" }), + br(), + label(i18n.uploadMedia), + br(), + input({ type: "file", name: "blob" }), fc.image ? div({ class: "industry-form-media" }, renderMediaBlob(fc.image, { class: "industry-hero-image" })) : null, br(), label(i18n.industrySector || "Sector"), @@ -186,7 +196,7 @@ const renderFacilityForm = (facility, mode, spreadWarning = null) => { br(), label(i18n.shopTags || "Tags"), br(), - input({ type: "text", name: "tags", placeholder: i18n.shopTagsPlaceholder || "tag1, tag2, tag3", value: safeArr(fc.tags).join(", ") }), + input({ type: "text", name: "tags", placeholder: i18n.shopTagsPlaceholder || "Enter tags separated by commas", value: safeArr(fc.tags).join(", ") }), br(), label(i18n.industryMembershipPolicy || "Membership policy"), br(), @@ -216,17 +226,16 @@ const renderGlobalBlueprints = (blueprints, spreadMap = new Map()) => { if (!list.length) return p(i18n.industryNoBlueprints || "No blueprints yet.") return div({ class: "industry-blueprints" }, list.map((bp) => div({ class: "industry-card-wrap" }, - div({ class: "card-header activity-card-header" }, span(), renderContentActions(bp.id, "/industry/blueprint/" + encodeURIComponent(bp.id))), + div({ class: "card-header activity-card-header" }, span(), renderContentActions(bp.id, "/industry/blueprint/" + encodeURIComponent(bp.id), { spread: spreadMap.get(bp.id) || null, author: bp.author, reportTitle: bp.name })), div({ class: "industry-blueprint-card" + (bp.author === userId ? " own-content" : "") }, div({ class: "card-chips-row" }, - renderSpreadButton(bp.id, spreadMap.get(bp.id)), renderStateChip("half", bp.outKind === "digital" ? "💾" : "📦", i18n["industryKind_" + bp.outKind] || bp.outKind), renderStateChip("half", "⚖", String(bp.license || "copyleft").toUpperCase()) ), p({ class: "industry-facility-line" }, span({ class: "industry-meta-label" }, `${i18n.industryFacility || "Facility"}: `), a({ href: `/industry/${encodeURIComponent(bp.facilityId)}` }, safeText(bp.facilityName))), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: `/industry/blueprint/${encodeURIComponent(bp.id)}` }, safeText(bp.name)))), bp.image ? renderMediaBlob(bp.image, { class: "post-image" }) : null, - bp.description ? p({ class: "tribe-card-description" }, ...renderUrl(safeText(bp.description))) : null, + bp.description ? p({ class: "tribe-card-description" }, ...renderStyledText(safeText(bp.description))) : null, renderBlueprintLaborHours(bp), safeArr(bp.materials).length ? div({ class: "industry-materials" }, ul({ class: "industry-materials-list" }, bp.materials.map(m => li(`${m.qty || 0} × ${safeText(m.item)}${m.price != null ? ` = ${m.price} ECO` : ""}`)))) : null, renderBlueprintEstimate(bp, i18n.industryBuildingPrice || "Building price"), @@ -271,7 +280,6 @@ const renderBuildCard = (b, opts = {}) => { const href = `/industry/build/${encodeURIComponent(b.id)}` const card = div({ class: "industry-blueprint-card" + (b.proposer === userId ? " own-content" : "") }, div({ class: "card-chips-row" }, - renderSpreadButton(b.id, opts.spread), renderBuildStatusChip(b.status), ...((b.blueprintKind && opts.withBlueprintChips !== false) ? [ renderStateChip("half", b.blueprintKind === "digital" ? "💾" : "📦", i18n["industryKind_" + b.blueprintKind] || b.blueprintKind), @@ -282,7 +290,7 @@ const renderBuildCard = (b, opts = {}) => { opts.withProposer !== false ? p(span({ class: "industry-meta-label" }, `${i18n.industryProposer || "Proposer"}: `), userLink(b.proposer)) : null, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href }, safeText(b.title)))), b.blueprintImage ? renderMediaBlob(b.blueprintImage, { class: "post-image" }) : null, - b.notes ? p({ class: "tribe-card-description" }, ...renderUrl(safeText(b.notes))) : null, + b.notes ? p({ class: "tribe-card-description" }, ...renderStyledText(safeText(b.notes))) : null, div({ class: "industry-blueprint-meta industry-dates-row" }, b.startDate ? p(span({ class: "industry-meta-label" }, `${i18n.industryBuildStart || "Start date"}: `), moment(b.startDate).format("YYYY/MM/DD")) : null, b.endDate ? p(span({ class: "industry-meta-label" }, `${i18n.industryBuildEnd || "End date"}: `), moment(b.endDate).format("YYYY/MM/DD")) : null, @@ -293,7 +301,7 @@ const renderBuildCard = (b, opts = {}) => { ) if (!opts.withActions) return card return div({ class: "industry-card-wrap" }, - div({ class: "card-header activity-card-header" }, span(), renderContentActions(b.id, href)), + div({ class: "card-header activity-card-header" }, span(), renderContentActions(b.id, href, { spread: opts.spread || null, author: b.proposer, reportTitle: b.title })), card ) } @@ -313,19 +321,30 @@ exports.industryView = async (facilitiesOrForm, filter, params = {}) => { const sectorSel = safeText(params.sector) const isForm = f === "CREATE" || f === "EDIT" const isRules = f === "RULES" + const emptyMod = moduleIsEmpty(safeArr(facilitiesOrForm), f, "ALL", search || sectorSel) + const censusI = Array.isArray(params.censusList) ? params.censusList : safeArr(facilitiesOrForm) + const indChip = (x) => { + const m = x.key + if (m === f) return true + if (m === "MINE") return censusI.some(fc => String(fc.steward) === String(userId) || safeArr(fc.members).includes(userId)) + if (m === "ACTIVE" || m === "PAUSED" || m === "DISSOLVED") return censusI.some(fc => String(fc.status || "ACTIVE").toUpperCase() === m) + if (m === "MEMBER") return censusI.some(fc => safeArr(fc.members).includes(userId)) + if (m === "RECENT") return censusI.length > 0 + if (m === "BLUEPRINTS" || m === "BUILDS") return censusI.length > 0 + return true + } return template( i18n.industryTitle || "Industry", section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.") ), - br(), div({ class: "filters" }, form({ method: "GET", action: "/industry", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "search", value: search }), input({ type: "hidden", name: "sector", value: sectorSel }), - FILTERS.map((x) => button({ type: "submit", name: "filter", value: x.key, class: f === x.key ? "filter-btn active" : "filter-btn" }, String(i18n[x.i18n] || x.key).toUpperCase())) + (emptyMod ? [] : FILTERS.filter(indChip).map((x) => button({ type: "submit", name: "filter", value: x.key, class: f === x.key ? "filter-btn active" : "filter-btn" }, String(i18n[x.i18n] || x.key).toUpperCase()))) .concat(button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.industryCreateFacility || "Create facility")) ) ), @@ -334,11 +353,12 @@ exports.industryView = async (facilitiesOrForm, filter, params = {}) => { : isForm ? renderFacilityForm(f === "EDIT" ? (safeArr(facilitiesOrForm)[0] || {}) : {}, f === "EDIT" ? "edit" : "create", facilityEditWarning) : f === "BLUEPRINTS" - ? div({ class: "industry-list" }, renderGlobalBlueprints(facilitiesOrForm, params.spreadMap || new Map())) + ? div({ class: "industry-list" }, renderGlobalBlueprints(paged(facilitiesOrForm), params.spreadMap || new Map())) : f === "BUILDS" - ? div({ class: "industry-list" }, renderGlobalBuilds(facilitiesOrForm, params.spreadMap || new Map())) + ? div({ class: "industry-list" }, renderGlobalBuilds(paged(facilitiesOrForm), params.spreadMap || new Map())) : section( - div({ class: "industry-search" }, + emptyMod ? null : div({ class: "industry-search activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(facilitiesOrForm, fc => String(fc.status || 'ACTIVE').toUpperCase(), [{ value: 'ACTIVE', label: i18n.industryStatusActive }, { value: 'PAUSED', label: i18n.industryStatusPaused }, { value: 'DISSOLVED', label: i18n.industryStatusDissolved }]), form({ method: "GET", action: "/industry", class: "filter-box" }, input({ type: "hidden", name: "filter", value: f || "ALL" }), input({ type: "text", name: "search", value: search, placeholder: i18n.industrySearchPlaceholder || "Search facilities…", class: "filter-box__input" }), @@ -351,8 +371,7 @@ exports.industryView = async (facilitiesOrForm, filter, params = {}) => { ) ) ), - br(), - div({ class: "industry-list" }, renderFacilityList(facilitiesOrForm, f, params.spreadMap)) + div({ class: "industry-list" }, renderFacilityList(paged(facilitiesOrForm), f, params.spreadMap)) ) ) ) @@ -508,7 +527,7 @@ const renderBlueprintsSection = (fc, blueprints, isMember, spreadMap = new Map() const cards = list.map((bp) => { const actions = renderBlueprintGovernActions(fc.id, bp, isMember, `/industry/${encodeURIComponent(fc.id)}`) return div({ class: "industry-card-wrap" }, - div({ class: "card-header activity-card-header" }, span(), renderContentActions(bp.id, `/industry/blueprint/${encodeURIComponent(bp.id)}`, { spread: spreadMap.get(bp.id) || null })), + div({ class: "card-header activity-card-header" }, span(), renderContentActions(bp.id, `/industry/blueprint/${encodeURIComponent(bp.id)}`, { spread: spreadMap.get(bp.id) || null, author: bp.author, reportTitle: bp.name })), div({ class: "industry-blueprint-card" }, div({ class: "card-chips-row" }, renderStateChip("half", bp.outKind === "digital" ? "💾" : "📦", i18n["industryKind_" + bp.outKind] || bp.outKind), @@ -516,7 +535,7 @@ const renderBlueprintsSection = (fc, blueprints, isMember, spreadMap = new Map() ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, safeText(bp.name))), bp.image ? renderMediaBlob(bp.image, { class: "post-image" }) : null, - bp.description ? p({ class: "tribe-card-description" }, ...renderUrl(safeText(bp.description))) : (bp.outItem ? p(span({ class: "industry-meta-label" }, `${i18n.industryOutput || "Output"}: `), `${bp.outQty || 0} × ${safeText(bp.outItem)}`) : null), + bp.description ? p({ class: "tribe-card-description" }, ...renderStyledText(safeText(bp.description))) : (bp.outItem ? p(span({ class: "industry-meta-label" }, `${i18n.industryOutput || "Output"}: `), `${bp.outQty || 0} × ${safeText(bp.outItem)}`) : null), renderBlueprintLaborHours(bp), safeArr(bp.materials).length ? div({ class: "industry-materials" }, ul({ class: "industry-materials-list" }, bp.materials.map(m => li(`${m.qty || 0} × ${safeText(m.item)}${m.price != null ? ` = ${m.price} ECO` : ""}`)))) : null, renderBlueprintEstimate(bp, i18n.industryBuildingPrice || "Building price"), @@ -544,14 +563,14 @@ const renderBlueprintForm = (fc, bp, mode, spreadWarning = null) => { br(), input({ type: "text", name: "name", required: true, maxlength: "80", value: bp.name || "" }), br(), label(i18n.industryDescriptionLabel || "Description"), br(), textarea({ name: "description", rows: "4", maxlength: "2000", placeholder: i18n.industryBlueprintDescPlaceholder || "Specs, dimensions, weight, docs…" }, bp.description || ""), br(), - label(i18n.uploadMedia || "Upload media (max-size: 50MB)"), + label(i18n.uploadMedia || "Upload media (max-size: 75MB)"), br(), input({ type: "file", name: "image" }), bp.image ? div({ class: "industry-form-media" }, renderMediaBlob(bp.image, { class: "post-image" })) : null, br(), label(i18n.industryOutputKind || "Product type"), br(), select({ name: "outKind" }, ["physical", "digital"].map(k => option({ value: k, ...(k === curKind ? { selected: true } : {}) }, i18n["industryKind_" + k] || k))), br(), label(i18n.industryMaterials || "Materials"), - br(), textarea({ name: "materialsText", rows: "3", placeholder: i18n.industryMaterialsPlaceholder || "solar-panel:2:120\nbattery:1:80\nkit:1:15" }, materialsText), br(), + br(), textarea({ maxlength: "5000", name: "materialsText", rows: "3", placeholder: i18n.industryMaterialsPlaceholder || "solar-panel:2:120\nbattery:1:80\nkit:1:15" }, materialsText), br(), label(i18n.industryLaborHours || "Labor hours"), br(), input({ type: "number", name: "laborHours", min: "0", step: "0.01", value: bp.laborHours != null ? bp.laborHours : "" }), br(), button({ type: "submit" }, isEdit ? (i18n.industryUpdateButton || "Update") : (i18n.industryCreateBlueprintButton || "Create blueprint")) @@ -562,7 +581,7 @@ const renderBlueprintForm = (fc, bp, mode, spreadWarning = null) => { const renderBuildForm = (fc, blueprints, build, mode, spreadWarning = null) => { const b = build || {} const isEdit = mode === "edit" - const today = new Date().toISOString().slice(0, 10) + const today = moment().format("YYYY-MM-DD") return div({ class: "industry-form industry-build-form" }, h2(isEdit ? (i18n.industryEditBuild || "Edit build") : (i18n.industryNewBuild || "Propose a build")), isEdit ? spreadWarning : null, @@ -574,7 +593,7 @@ const renderBuildForm = (fc, blueprints, build, mode, spreadWarning = null) => { ? input({ type: "text", disabled: true, value: safeText(b.blueprintName) }) : select({ name: "blueprintId", required: true }, safeArr(blueprints).map(bp => option({ value: bp.id }, safeText(bp.name)))), br(), label(i18n.industryBuildStart || "Start date"), - br(), input({ type: "date", name: "startDate", required: true, value: b.startDate || "", ...(isEdit ? {} : { min: today }) }), br(), + br(), input({ type: "date", name: "startDate", required: true, value: b.startDate || "", min: today }), br(), label(i18n.industryBuildEnd || "End date"), br(), input({ type: "date", name: "endDate", required: true, value: b.endDate || "", min: today }), br(), label(i18n.industryBuildNotes || "Notes"), @@ -612,40 +631,38 @@ const renderFacilityJobsSection = (fc, jobs) => { const renderFacilitySide = (fc, returnTo, params = {}) => { const isSteward = fc.steward === userId + const isMember = safeArr(fc.members).includes(userId) + const isOwner = String(fc.steward || fc.author) === String(userId) const memberCount = fc.memberCount != null ? fc.memberCount : safeArr(fc.members).length const chips = [ renderStateChip("whole", "🏭", sectorLabel(fc.sector)), renderStateChip("half", "⚖", policyLabel(fc.membershipPolicy)), renderLifespanChip(fc.lifetime, i18n), - renderEcoTax(fc.msgSize, fc.id || fc.key) + renderEcoTax(fc.msgSize, fc.id || fc.key), + (isSteward || isMember) + ? ((isOwner || (fc.subscription && fc.subscription.subscribed === true)) + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : renderStateChip("closed", "✉", i18n.subscriptionOff)) + : null ].filter(Boolean) const sideActions = [] for (const act of membershipActions(fc, returnTo)) sideActions.push(act) if (!isSteward && fc.steward) { - sideActions.push(form({ method: "GET", action: "/pm" }, - input({ type: "hidden", name: "recipients", value: fc.steward }), - button({ type: "submit", class: "filter-btn" }, i18n.privateMessage) - )) sideActions.push(a({ href: `/reports?filter=create&category=ABUSE&title=${encodeURIComponent(`${i18n.industryTitle || "Industry"}: ${safeText(fc.name)}`)}`, class: "filter-btn" }, i18n.industryReportButton || "Report")) } if (isSteward) { sideActions.push(form({ method: "GET", action: `/industry/edit/${encodeURIComponent(fc.id)}` }, button({ type: "submit", class: "tribe-action-btn" }, i18n.industryUpdateButton || "Update") )) - if (memberCount <= 1) { - sideActions.push(form({ method: "POST", action: `/industry/delete/${encodeURIComponent(fc.id)}` }, - button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.industryDeleteButton || "Delete") - )) - } } return div({ class: "tribe-side" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: `/industry/${encodeURIComponent(fc.id)}` }, safeText(fc.name) || (i18n.industryTitle || "Industry")))), div({ class: "card-chips-row" }, ...chips), fc.image ? renderMediaBlob(fc.image, { class: "tribe-detail-image" }) : null, - fc.description ? p({ class: "tribe-side-description" }, ...renderUrl(safeText(fc.description))) : null, + fc.description ? p({ class: "tribe-side-description" }, ...renderStyledText(safeText(fc.description))) : null, table({ class: "tribe-info-table" }, tr(td({ class: "tribe-info-label" }, i18n.industryLaborRate || "Labor rate"), td({ class: "tribe-info-value", colspan: "3" }, `${fc.laborRate || 0} ECO/h`)), tr(td({ class: "tribe-info-label" }, i18n.industryQuorum || "Quorum"), td({ class: "tribe-info-value", colspan: "3" }, String(fc.quorum))), @@ -660,6 +677,16 @@ const renderFacilitySide = (fc, returnTo, params = {}) => { h2({ class: "tribe-members-count" }, `${i18n.industryMembers || "Members"}: ${memberCount}`), renderInviteSection(fc, returnTo), sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, + (fc.subscription && (isSteward || isMember)) + ? renderSubscriptionBox({ + target: fc.rootId || fc.id, + scope: "industry", + subscribed: fc.subscription.subscribed === true, + count: fc.subscription.count, + isOwner: isOwner, + returnTo: returnTo + }) + : null, renderDissolveBlock(fc, returnTo) ) } @@ -669,13 +696,14 @@ exports.singleFacilityView = async (facility, filter, params = {}) => { const f = String(filter || "ALL").toUpperCase() const returnTo = `/industry/${encodeURIComponent(fc.id)}?filter=${encodeURIComponent(f)}` const isMember = safeArr(fc.members).includes(userId) + const memberCount = fc.memberCount != null ? fc.memberCount : safeArr(fc.members).length const facilitySide = renderFacilitySide(fc, returnTo, params) const facilityMain = div({ class: "tribe-main" }, renderBlueprintsSection(fc, params.blueprints, isMember, params.childSpreadMap || new Map()), renderBuildsSection(fc, params.builds, params.blueprints, isMember, params.childSpreadMap || new Map()), renderFacilityJobsSection(fc, params.facilityJobs), - renderOpinionsVoting('/industry/opinions', fc.id || fc.key, fc.opinions, null, fc.opinions_inhabitants), + renderCardMetaRow(renderOpinionsVoting('/industry/opinions', fc.id || fc.key, fc.opinions, null, fc.opinions_inhabitants)), div({ class: "industry-section" }, h2(i18n.industryMembers || "Members"), ul({ class: "industry-members-list" }, safeArr(fc.members).map((mid) => li( @@ -690,9 +718,9 @@ exports.singleFacilityView = async (facility, filter, params = {}) => { i18n.industryTitle || "Industry", section( div({ class: "card-header activity-card-header" }, - renderContentActions(fc.id || fc.key, null, { spread: params.spreads || null }) + renderContentActions(fc.id || fc.key, `/industry/${encodeURIComponent(fc.id)}`, { spread: params.spreads || null, author: fc.steward, reportTitle: fc.name, deleteAction: fc.steward === userId && memberCount <= 1 ? `/industry/delete/${encodeURIComponent(fc.id)}` : null }) ), - div({ class: "tags-header" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.")), + div({ class: "tags-header module-header-line" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.")), div({ class: "filters" }, form({ method: "GET", action: "/industry", class: "ui-toolbar ui-toolbar--filters" }, FILTERS.map((x) => button({ type: "submit", name: "filter", value: x.key, class: f === x.key ? "filter-btn active" : "filter-btn" }, String(i18n[x.i18n] || x.key).toUpperCase())) @@ -708,9 +736,9 @@ exports.blueprintEditView = async (bp, fc) => template( i18n.industryTitle || "Industry", section( div({ class: "card-header activity-card-header" }, - renderContentActions(fc.id || fc.key, `/industry/${encodeURIComponent(fc.id)}`) + renderContentActions(bp.id, `/industry/blueprint/${encodeURIComponent(bp.id)}`, { author: bp.author, reportTitle: bp.name }) ), - div({ class: "tags-header" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.")), + div({ class: "tags-header module-header-line" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.")), div({ class: "filters" }, form({ method: "GET", action: "/industry", class: "ui-toolbar ui-toolbar--filters" }, FILTERS.map((x) => button({ type: "submit", name: "filter", value: x.key, class: "filter-btn" }, String(i18n[x.i18n] || x.key).toUpperCase())) @@ -725,9 +753,9 @@ exports.buildEditView = async (b, fc) => template( i18n.industryTitle || "Industry", section( div({ class: "card-header activity-card-header" }, - renderContentActions(b.id, `/industry/build/${encodeURIComponent(b.id)}`) + renderContentActions(b.id, `/industry/build/${encodeURIComponent(b.id)}`, { author: b.proposer, reportTitle: b.title }) ), - div({ class: "tags-header" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.")), + div({ class: "tags-header module-header-line" }, h2(i18n.industryTitle || "Industry"), p(i18n.industryDescription || "Network-owned production facilities.")), div({ class: "filters" }, form({ method: "GET", action: "/industry", class: "ui-toolbar ui-toolbar--filters" }, FILTERS.map((x) => button({ type: "submit", name: "filter", value: x.key, class: "filter-btn" }, String(i18n[x.i18n] || x.key).toUpperCase())) @@ -747,7 +775,7 @@ exports.singleBlueprintView = async (blueprint, params = {}) => { return template( i18n.industryTitle || "Industry", - section(div({ class: "tags-header" }, h2(i18n.industryTitle), p(i18n.industryDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.industryTitle), p(i18n.industryDescription))), section( div({ class: "filters" }, form({ method: "GET", action: "/industry", class: "ui-toolbar ui-toolbar--filters" }, @@ -760,7 +788,7 @@ exports.singleBlueprintView = async (blueprint, params = {}) => { div({ class: "shop-detail" }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(bp.id, null, { spread: params.spreads || null, author: bp.author, reportTitle: bp.name || bp.title }) + renderContentActions(bp.id, returnTo, { spread: params.spreads || null, author: bp.author, reportTitle: bp.name || bp.title }) ), div({ class: "card-chips-row" }, renderStateChip("half", bp.outKind === "digital" ? "💾" : "📦", i18n["industryKind_" + bp.outKind] || bp.outKind), @@ -769,7 +797,7 @@ exports.singleBlueprintView = async (blueprint, params = {}) => { p({ class: "industry-facility-line" }, span({ class: "industry-meta-label" }, `${i18n.industryFacility || "Facility"}: `), a({ href: facilityHref }, safeText(bp.facilityName))), h2(safeText(bp.name)), bp.image ? div({ class: "shop-detail-media" }, renderMediaBlob(bp.image, { class: "post-image" })) : null, - bp.description ? p({ class: "tribe-card-description" }, ...renderUrl(safeText(bp.description))) : null, + bp.description ? p({ class: "tribe-card-description" }, ...renderStyledText(safeText(bp.description))) : null, div({ class: "industry-meta" }, p(span({ class: "industry-meta-label" }, `${i18n.industryAuthor || "Author"}: `), userLink(bp.author)) ), @@ -932,7 +960,7 @@ exports.singleBuildView = async (build, params = {}) => { return template( i18n.industryTitle || "Industry", - section(div({ class: "tags-header" }, h2(i18n.industryTitle), p(i18n.industryDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.industryTitle), p(i18n.industryDescription))), section( div({ class: "filters" }, form({ method: "GET", action: "/industry", class: "ui-toolbar ui-toolbar--filters" }, @@ -945,7 +973,7 @@ exports.singleBuildView = async (build, params = {}) => { div({ class: "shop-detail" }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(b.id, null, { spread: params.spreads || null, author: b.proposer, reportTitle: b.title }) + renderContentActions(b.id, returnTo, { spread: params.spreads || null, author: b.proposer, reportTitle: b.title }) ), b.image ? div({ class: "shop-detail-media" }, renderMediaBlob(b.image, { class: "post-image" })) : null, h2(safeText(b.title) || (i18n.industryBuild || "Build")), diff --git a/src/views/inhabitants_view.js b/src/views/inhabitants_view.js index fa7b2c9..4828b54 100644 --- a/src/views/inhabitants_view.js +++ b/src/views/inhabitants_view.js @@ -1,8 +1,9 @@ const { div, h2, p, section, button, form, img, a, textarea, input, span, strong } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderUserSensors, renderContentActions, renderRelationshipBlock } = require('./main_views'); +const moment = require("../server/node_modules/moment"); +const { renderCallButton, template, i18n, userLink, renderUserSensors, renderContentActions, renderRelationshipBlock, renderModuleStats, paged } = require('./main_views'); const { renderZoomableImage } = require('./gallery_view'); const { renderContentStats } = require('./clearnet_view'); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledText } = require('../backend/renderStyledText'); const { getConfig } = require('../configs/config-manager'); const DEFAULT_HASH_ENC = "%260000000000000000000000000000000000000000000%3D.sha256"; @@ -71,9 +72,13 @@ const generateFilterButtons = (filters, currentFilter, labelOf = filterLabel) => const MAIN_FILTERS = ['all', 'TOP', 'contacts', 'blocked', 'CVs', 'SUGGESTED', 'GALLERY']; -const renderMainFilters = (filter, isTop) => +const renderMainFilters = (filter, isTop, census = {}) => div({ class: 'inhabitant-action' }, - ...MAIN_FILTERS.map(mode => + ...MAIN_FILTERS.filter(mode => { + if (mode === 'all' || mode === 'TOP') return true; + if (mode === 'TOP' ? isTop : filter === mode) return true; + return census[mode] !== false; + }).map(mode => form({ method: 'GET', action: '/inhabitants' }, input({ type: 'hidden', name: 'filter', value: mode === 'TOP' ? 'TOP ACTIVITY' : mode }), button({ @@ -126,7 +131,10 @@ const renderCvFields = (user) => { cvField(i18n.skillsLabel, skills.length ? skills.join(', ') : ''), cvField(i18n.statusLabel || 'Status', user.status), cvField(i18n.preferencesLabel || 'Preferences', user.preferences), - cvField(i18n.createdAtLabel || 'Created at', user.createdAt ? new Date(user.createdAt).toLocaleString() : '') + cvField(i18n.createdAtLabel || 'Created at', user.createdAt ? moment(user.createdAt).format("YYYY/MM/DD HH:mm") : ''), + user.pdf + ? div({ class: 'card-field' }, a({ href: `/blob/${encodeURIComponent(user.pdf)}`, target: '_blank', rel: 'noopener', class: 'filter-btn' }, '📄 ' + i18n.cvPdfLabel)) + : null ].filter(Boolean); return fields.length ? div({ class: 'cv-card-fields' }, ...fields) : null; }; @@ -134,7 +142,7 @@ const renderCvFields = (user) => { const renderInhabitantCard = (user, filter, currentUserId, fediverseConfigured) => { const isMe = user.id === currentUserId; const raw = user.visibilityPrefs || {}; - const clearnetSubKeys = ['clearnetShops','clearnetJobs','clearnetEvents','clearnetProjects','clearnetPosts','clearnetAudios','clearnetVideos','clearnetImages','clearnetDocuments','clearnetTorrents','clearnetBookmarks']; + const clearnetSubKeys = ['clearnetShops','clearnetJobs','clearnetEvents','clearnetProjects','clearnetPosts','clearnetAudios','clearnetVideos','clearnetImages','clearnetDocuments','clearnetTorrents','clearnetFiles','clearnetBookmarks','clearnetPodcasts']; const hasClearnet = raw.clearnet === true || clearnetSubKeys.some(k => raw[k] === true); const prefs = { activity: raw.activity === true, @@ -145,6 +153,8 @@ const renderInhabitantCard = (user, filter, currentUserId, fediverseConfigured) ecoTax: raw.ecoTax !== false, larpSign: raw.larpSign === true, gpg: raw.gpg === true, + phone: raw.phone === 'mutuals' || raw.phone === 'off' ? raw.phone : 'whole', + phoneDnd: raw.phoneDnd === true, clearnet: hasClearnet, fediverse: raw.fediverse === true, fediverseHandle: typeof raw.fediverseHandle === 'string' ? raw.fediverseHandle : '' @@ -162,14 +172,17 @@ const renderInhabitantCard = (user, filter, currentUserId, fediverseConfigured) { href: `/author/${encodeURIComponent(user.id)}` }, img({ class: 'inhabitant-photo-details', src: resolvePhoto(user.photo, 256), alt: user.name || 'Anonymous' }) ), + user.name ? p({ class: 'inhabitant-left-name' }, userLink(user.id, user.name)) : null, ...renderUserSensors({ isMe, fediverseConfigured, prefs, id: user.id, karmaScore: user.karmaScore, carbonGrams: user.carbonGrams, deviceSource: user.deviceSource, activityBucket: user.lastActivityBucket, gpgFingerprint: user.gpgFingerprint, ecoAddress: user.ecoAddress, estimatedUBI: user.estimatedUBI, lastClaimedDate: user.lastClaimedDate, totalClaimed: user.totalClaimed, - larpHouse: user.larpHouse, stats: user.stats - }, { relationshipNode: isMe ? span({ class: 'status you' }, i18n.relationshipYou) : null, excludeContent: true }), + larpHouse: user.larpHouse, stats: user.stats, + mutual: !!(user.relationship && user.relationship.following && user.relationship.followsMe) + }, { excludeContent: true, sensorsOnly: true, dndToggle: false }), + isMe ? p({ class: 'inhabitant-left-name' }, span({ class: 'status you' }, i18n.relationshipYou)) : null, filter === 'CVs' ? div( { class: 'cv-actions doc-export-actions' }, @@ -182,32 +195,21 @@ const renderInhabitantCard = (user, filter, currentUserId, fediverseConfigured) button({ type: 'submit', class: 'filter-btn' }, i18n.sharePm) ) ) - : null, - !isMe - ? div( - { class: 'inhabitant-user-actions' }, - div({ class: 'inhabitant-relationship' }, renderRelationshipBlock(user.relationship || {}, user.id)) - ) : null ), - div({ class: 'inhabitant-details' }, - h2(user.name || 'Anonymous'), - user.description ? p(...renderUrl(user.description)) : null, - filter === 'CVs' ? renderCvFields(user) : null, - filter === 'SUGGESTED' && (user.followsYou || user.commonSkills?.length || user.mutualCount) - ? div({ class: 'suggested-meta' }, - user.followsYou ? span({ class: 'suggested-badge' }, i18n.suggestedFollowsYou || 'Follows you') : null, - user.commonSkills?.length - ? p(`${i18n.commonSkills || 'Common skills'}: ${user.commonSkills.join(', ')}`) - : null, - user.mutualCount ? p(`${i18n.mutualFollowers}: ${user.mutualCount}`) : null - ) - : null, - filter === 'blocked' && user.isBlocked - ? p(i18n.blockedLabel) : null, - p(userLink(user.id)), - renderContentStats(user.stats, i18n) - ) + (() => { + const detailNodes = [ + filter === 'CVs' ? renderCvFields(user) : null, + filter === 'SUGGESTED' && user.commonSkills?.length + ? div({ class: 'suggested-meta' }, + p(`${i18n.commonSkills || 'Common skills'}: ${user.commonSkills.slice(0, 8).join(', ')}`) + ) + : null, + filter === 'blocked' && user.isBlocked + ? p(i18n.blockedLabel) : null + ].filter(Boolean); + return detailNodes.length ? div({ class: 'inhabitant-details' }, ...detailNodes) : null; + })() ) ) ); @@ -218,7 +220,7 @@ const renderGalleryInhabitants = inhabitants => { class: "gallery" }, inhabitants.length ? inhabitants.map(u => - a({ href: `#${lightboxId(u.id)}`, class: "gallery-item" }, + a({ href: `#${lightboxId(u.id)}`, id: `${lightboxId(u.id)}-src`, class: "gallery-item" }, img({ src: resolvePhoto(u.photo, 256), alt: u.name || "Anonymous", class: "gallery-image" }) ) ) @@ -229,7 +231,7 @@ const renderLightbox = inhabitants => inhabitants.map(u => div( { id: lightboxId(u.id), class: "lightbox" }, - a({ href: "#", class: "lightbox-close" }, "×"), + a({ href: `#${lightboxId(u.id)}-src`, class: "lightbox-close" }, "×"), img({ src: resolvePhoto(u.photo, 256), class: "lightbox-image", alt: u.name || "Anonymous" }) ) ); @@ -249,21 +251,37 @@ function msgIdOf(m) { return m && (m.key || m.value?.key || m.value?.content?.root || m.value?.content?.branch || null); } -exports.inhabitantsView = (inhabitants, filter, query, currentUserId, fediverseConfigured) => { +exports.inhabitantsView = (inhabitants, filter, query, currentUserId, fediverseConfigured, filterCensus = {}) => { const title = i18n.allInhabitants; const showCVFilters = filter === 'CVs'; + const bucketCounts = inhabitants.reduce((acc, u) => { + const b = u && u.lastActivityBucket; + if (b === 'green' || b === 'orange' || b === 'red') acc[b]++; + return acc; + }, { green: 0, orange: 0, red: 0 }); const TOP_FILTERS = ['TOP ACTIVITY', 'TOP INACTIVITY', 'TOP KARMA', 'TOP ECO']; const isTop = TOP_FILTERS.includes(filter); return template( title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(title), p(i18n.discoverPeople) ), - div({ class: 'filters' }, + renderMainFilters(filter, isTop, filterCensus), + isTop + ? div({ class: 'inhabitant-action inhabitant-subfilters' }, + ...generateFilterButtons(TOP_FILTERS, filter, (mode) => filterLabel(mode).replace(/^TOP\s+/, '')) + ) + : null, + div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStats(inhabitants.length, [ + { label: '<2w', count: bucketCounts.green, labelClass: 'stat-label-green' }, + { label: '2w–6m', count: bucketCounts.orange, labelClass: 'stat-label-orange' }, + { label: '>6m', count: bucketCounts.red, labelClass: 'stat-label-red' } + ]), form({ method: 'GET', action: '/inhabitants', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ @@ -285,20 +303,14 @@ exports.inhabitantsView = (inhabitants, filter, query, currentUserId, fediverseC ) ) ), - renderMainFilters(filter, isTop), - isTop - ? div({ class: 'inhabitant-action inhabitant-subfilters' }, - ...generateFilterButtons(TOP_FILTERS, filter, (mode) => filterLabel(mode).replace(/^TOP\s+/, '')) - ) - : null, filter === 'GALLERY' - ? renderGalleryInhabitants(inhabitants) - : div({ class: 'inhabitants-list' }, + ? renderGalleryInhabitants(paged(inhabitants)) + : div({ class: filter === 'CVs' ? 'inhabitants-list' : 'inhabitants-list inhabitants-grid' }, inhabitants.length - ? inhabitants.map(user => renderInhabitantCard(user, filter, currentUserId, fediverseConfigured)) + ? paged(inhabitants).map(user => renderInhabitantCard(user, filter, currentUserId, fediverseConfigured)) : p({ class: 'no-results' }, i18n.noInhabitantsFound) ), - ...renderLightbox(inhabitants) + ...renderLightbox(paged(inhabitants)) ) ); }; @@ -338,7 +350,7 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = ]; const status = (cv && cv.status) || ''; const preferences = (cv && cv.preferences) || ''; - const createdAt = (cv && cv.createdAt) ? new Date(cv.createdAt).toLocaleString() : ''; + const createdAt = (cv && cv.createdAt) ? moment(cv.createdAt).format("YYYY/MM/DD HH:mm") : ''; const isMe = id && id === currentUserId; const title = i18n.inhabitantProfileTitle || i18n.inhabitantviewDetails; const karmaScore = typeof safe.karmaScore === 'number' ? safe.karmaScore : 0; @@ -347,7 +359,7 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = const totalClaimed = typeof safe.totalClaimed === 'number' ? safe.totalClaimed : 0; const ecoAddress = typeof safe.ecoAddress === 'string' ? safe.ecoAddress : null; const rawPrefs = safe.visibilityPrefs || {}; - const clearnetSubKeys = ['clearnetShops','clearnetJobs','clearnetEvents','clearnetProjects','clearnetPosts','clearnetAudios','clearnetVideos','clearnetImages','clearnetDocuments','clearnetTorrents','clearnetBookmarks']; + const clearnetSubKeys = ['clearnetShops','clearnetJobs','clearnetEvents','clearnetProjects','clearnetPosts','clearnetAudios','clearnetVideos','clearnetImages','clearnetDocuments','clearnetTorrents','clearnetFiles','clearnetBookmarks','clearnetPodcasts']; const prefs = { activity: rawPrefs.activity === true, device: rawPrefs.device === true, @@ -357,6 +369,8 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = ecoTax: rawPrefs.ecoTax !== false, larpSign: rawPrefs.larpSign === true, gpg: rawPrefs.gpg === true, + phone: rawPrefs.phone === 'mutuals' || rawPrefs.phone === 'off' ? rawPrefs.phone : 'whole', + phoneDnd: rawPrefs.phoneDnd === true, clearnet: rawPrefs.clearnet === true || clearnetSubKeys.some(k => rawPrefs[k] === true), fediverse: rawPrefs.fediverse === true, fediverseHandle: typeof rawPrefs.fediverseHandle === 'string' ? rawPrefs.fediverseHandle : '' @@ -378,14 +392,14 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = ].filter(Boolean); const detailNodes = [ - description ? p(...renderUrl(description)) : null, + description ? p(...renderStyledText(description)) : null, fieldNodes.length ? div({ class: 'cv-card-fields' }, ...fieldNodes) : null ].filter(Boolean); return template( name, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(title), p(i18n.discoverPeople) ), @@ -399,15 +413,17 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = isMe, fediverseConfigured, prefs, id: id || viewedId, karmaScore, carbonGrams, deviceSource: safe.deviceSource, activityBucket: providedBucket, gpgFingerprint, ecoAddress, estimatedUBI, lastClaimedDate, totalClaimed, - larpHouse, stats: safe.stats + larpHouse, stats: safe.stats, + mutual: !!(safe.relationship && safe.relationship.following && safe.relationship.followsMe) }, { excludeContent: true }), (!isMe && (id || viewedId)) ? div({ class: 'cv-actions doc-export-actions' }, form( { method: 'GET', action: '/pm' }, input({ type: 'hidden', name: 'recipients', value: id || viewedId }), - button({ type: 'submit', class: 'filter-btn' }, i18n.pmCreateButton) - ) + button({ type: 'submit', class: 'filter-btn' }, `✉ ${i18n.pmCreateButton}`) + ), + renderCallButton(id || viewedId, { pam: true }) ) : null, (!isMe && (id || viewedId) && safe.relationship) @@ -422,14 +438,15 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = const raw = (m.value?.content?.text || '').replace(//g, ''); const parts = stripAndCollectImgs(raw); const tid = msgIdOf(m); - const visitBtn = tid - ? form({ method: 'GET', action: `/thread/${encodeURIComponent(tid)}#${encodeURIComponent(tid)}` }, - button({ type:'submit', class:'filter-btn' }, i18n.visitContent) + const actions = tid + ? div({ class: 'card-header activity-card-header' }, + span(), + renderContentActions(tid, `/thread/${encodeURIComponent(tid)}#${encodeURIComponent(tid)}`, { author: m.value?.author, reportTitle: parts.clean }) ) : null; return div({ class: 'post' }, - visitBtn, - parts.clean && parts.clean.trim() ? p(...renderUrl(parts.clean)) : null, + actions, + parts.clean && parts.clean.trim() ? p(...renderStyledText(parts.clean)) : null, ...(parts.imgs || []).map(src => renderZoomableImage(src, { imgClass: 'post-image', alt: 'image' })) ); }) @@ -446,3 +463,4 @@ exports.inhabitantsProfileView = (payload, currentUserId, fediverseConfigured) = exports.lastActivityBadge = lastActivityBadge; exports.resolvePhoto = resolvePhoto; +exports.renderInhabitantCard = renderInhabitantCard; diff --git a/src/views/invites_view.js b/src/views/invites_view.js index 15a2dcf..8f64cb2 100644 --- a/src/views/invites_view.js +++ b/src/views/invites_view.js @@ -1,12 +1,13 @@ -const { form, button, div, h2, h3, p, section, ul, li, a, br, hr, input, label, span, table, tr, td, textarea } = require("../server/node_modules/hyperaxe"); +const { form, button, div, h2, h3, p, section, a, br, input, label, table, tr, td, textarea } = require("../server/node_modules/hyperaxe"); +const moment = require("../server/node_modules/moment"); const path = require("path"); const fs = require('fs'); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const { template, i18n } = require('./main_views'); -const homedir = require('os').homedir(); -const gossipPath = path.join(homedir, ".ssb", "gossip.json"); -const unfollowedPath = path.join(homedir, ".ssb", "gossip_unfollowed.json"); +const gossipPath = path.join(require("../server/ssb_config").path, "gossip.json"); +const unfollowedPath = require("../configs/state-manager").statePath("gossip_unfollowed.json"); +const peerHealth = require("../models/peer_health"); const encodePubLink = (key) => { let core = String(key).replace(/^@/, '').replace(/\.ed25519$/, '').replace(/-/g, '+').replace(/_/g, '/'); @@ -14,12 +15,21 @@ const encodePubLink = (key) => { return `/author/${encodeURIComponent('@' + core)}.ed25519`; }; -const snhInvitePath = path.join(__dirname, '..', 'configs', 'snh-invite-code.json'); - -let snhInvite = null; -try { - snhInvite = JSON.parse(fs.readFileSync(snhInvitePath, 'utf8')); -} catch {} +const INVITE_KIND_LABEL = { + tribes: 'invitesTribesTitle', + houses: 'invitesHousesTitle', + chats: 'invitesChatsTitle', + pads: 'invitesPadsTitle', + rooms: 'invitesRoomsTitle', + calendars: 'invitesCalendarsTitle', + events: 'invitesEventsTitle', + forum: 'invitesForumsTitle', + school: 'invitesSchoolTitle', + maps: 'invitesMapsTitle', + shops: 'invitesShopsTitle', + inhabitants: 'invitesInhabitantsTitle', + pubs: 'invitesPubsTitle' +}; const deduplicateByHost = (list) => { const seen = new Set(); @@ -31,7 +41,25 @@ const deduplicateByHost = (list) => { }); }; -const invitesView = ({ invitesEnabled, flash }) => { +const renderSuggestedPub = (entry, canJoin) => div({ class: 'snh-invite-box' }, + h3({ class: 'snh-invite-name' }, entry.name), + p({ class: 'snh-invite-name' }, entry.description), + p({ class: 'snh-invite-name' }, renderStyledText(entry.url)), + entry.createdAt ? p({ class: 'snh-invite-date' }, `${i18n.statsCreatedAt || 'Created'}: ${moment(entry.createdAt).format("YYYY/MM/DD")}`) : null, + p({ class: 'snh-invite-name snh-invite-code' }, entry.code), + entry.invites ? p({ class: 'snh-invite-date' }, `${i18n.invitesPubUses}: ${entry.invites.used}/${entry.invites.total}`) : null, + entry.exhausted + ? p({ class: 'snh-invite-name' }, i18n.invitesPubExhausted) + : canJoin + ? form({ action: '/settings/invite/accept', method: 'post' }, + input({ type: 'hidden', name: 'invite', value: entry.code }), + button({ type: 'submit' }, i18n.invitesAcceptInvite) + ) + : null +); + +const invitesView = (opts = {}) => { + const canJoin = opts.canJoin !== false; let pubs = []; let pubsValue = "false"; let unfollowed = []; @@ -60,15 +88,8 @@ const invitesView = ({ invitesEnabled, flash }) => { ? deduplicateByHost(pubs.filter(pubItem => !unfollowed.find(u => u.key === pubItem.key))) : []; - const hasError = (pubItem) => pubItem && (pubItem.error || (typeof pubItem.failure === 'number' && pubItem.failure > 0)); - - const sanitizeError = (err) => { - if (!err) return i18n.genericError || 'Unknown error'; - const firstLine = String(err).split('\n')[0].replace(/^Error:\s*/, '').trim(); - return firstLine || (i18n.genericError || 'Unknown error'); - }; - - const unreachableLabel = i18n.currentlyUnreachable || i18n.currentlyUnrecheable || 'ERROR!'; + const deadKeys = opts.deadKeys instanceof Set ? opts.deadKeys : new Set(); + const hasError = (pubItem) => !!(pubItem && deadKeys.has(peerHealth.canonicalKey(pubItem.key))); const pubTableHeader = () => tr( td({ class: 'card-label' }, 'PUB'), @@ -78,7 +99,9 @@ const invitesView = ({ invitesEnabled, flash }) => { ); const activePubs = filteredPubs.filter(pubItem => !hasError(pubItem)); - const unreachablePubs = pubs.filter(hasError); + const filter = opts.filter === 'others' ? 'others' : 'pubs'; + const suggested = Array.isArray(opts.inviteCodes) ? opts.inviteCodes : []; + const used = (Array.isArray(opts.invitesUsed) ? opts.invitesUsed : []).filter((e) => e && typeof e.href === 'string' && e.href.startsWith('/')); const renderPubTable = (items, actionFn) => table({ class: 'block-info-table invites-pubs-table' }, pubTableHeader(), @@ -96,56 +119,44 @@ const invitesView = ({ invitesEnabled, flash }) => { return template( title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(title), p(description) + ), + div({ class: 'mode-buttons-row' }, + a({ class: filter === 'pubs' ? 'filter-btn active' : 'filter-btn', href: '/invites?filter=pubs' }, String(i18n.invitesPubsTitle).toUpperCase()), + a({ class: filter === 'others' ? 'filter-btn active' : 'filter-btn', href: '/invites?filter=others' }, String(i18n.invitesFilterOthers).toUpperCase()) ) ), - flash === 'alreadyFederated' - ? section(div({ class: 'message-banner' }, p(i18n.invitesAlreadyFederated || 'You are already federated with this pub.'))) - : null, - section( + filter === 'pubs' && suggested.length ? section( + div({ class: 'pubs-section' }, + h2(`${i18n.invitesSuggestedPubs} (${suggested.length})`), + ...suggested.map((entry) => renderSuggestedPub(entry, canJoin)) + ) + ) : null, + filter === 'pubs' ? section( div({ class: 'pubs-section' }, h2(i18n.invitesPubsTitle), p(i18n.invitesPubsHint), - form( - { action: '/settings/invite/accept', method: 'post' }, - input({ name: 'invite', type: 'text', placeholder: i18n.invitesPubInviteCodePlaceholder, autofocus: true, required: true }), - br(), - button({ type: 'submit' }, i18n.invitesAcceptInvite) - ), - br(), - snhInvite ? div({ class: 'snh-invite-box' }, - h3({ class: 'snh-invite-name' }, snhInvite.name), - p({ class: 'snh-invite-name' }, snhInvite.description), - p({ class: 'snh-invite-name' }, renderUrl(snhInvite.url)), - snhInvite.createdAt ? p({ class: 'snh-invite-date' }, `${i18n.statsCreatedAt || 'Created'}: ${new Date(snhInvite.createdAt).toLocaleDateString()}`) : null, - form({ action: '/settings/invite/accept', method: 'post' }, - input({ type: 'hidden', name: 'invite', value: snhInvite.code }), - button({ type: 'submit', class: 'filter-btn' }, snhInvite.code) - ) - ) : null + canJoin + ? form( + { action: '/settings/invite/accept', method: 'post' }, + input({ name: 'invite', type: 'text', placeholder: i18n.invitesPubInviteCodePlaceholder, autofocus: true, required: true }), + br(), + button({ type: 'submit' }, i18n.invitesAcceptInvite) + ) + : p({ class: 'invites-join-note' }, opts.publicMode === true ? i18n.invitesJoinPubMode : i18n.invitesJoinLocalOnly) ) - ), - section( + ) : null, + filter === 'pubs' ? section( div({ class: 'federations-section' }, h2(i18n.invitesFederationsTitle || 'Federations'), p(i18n.invitesFederationsHint), div({ class: 'conn-actions invites-pubs-actions' }, form({ action: '/invites/refresh-pubs', method: 'post' }, button({ type: 'submit' }, i18n.invitesPubsRefresh || 'Refresh')), - form({ action: '/invites/clear-unreachable', method: 'post' }, button({ type: 'submit' }, i18n.invitesPubsClearUnreachable || 'Remove unreachable')), + form({ action: '/invites/clear-unreachable', method: 'post' }, button({ type: 'submit' }, i18n.invitesPubsClearUnreachable || 'Clean')), form({ action: '/invites/export-pubs', method: 'get' }, button({ type: 'submit' }, i18n.invitesPubsExport || 'Export')) ), - form( - { action: '/invites/import-pubs', method: 'post', enctype: 'multipart/form-data', class: 'peers-import-form' }, - label({ class: 'peers-import-label' }, i18n.invitesPubsImportTitle || 'Import pubs'), - br(), - textarea({ name: 'peerList', rows: '4', placeholder: i18n.invitesPubsImportPlaceholder || 'Paste one multiserver address or invite code per line…' }), - br(), - input({ type: 'file', name: 'peerFile', accept: '.txt,text/plain' }), - br(), - button({ type: 'submit', class: 'filter-btn' }, i18n.invitesPubsImport || 'Import') - ), h2(`${i18n.invitesAcceptedInvites} (${activePubs.length})`), activePubs.length ? renderPubTable(activePubs, pubItem => @@ -155,7 +166,6 @@ const invitesView = ({ invitesEnabled, flash }) => { ) ) : p(i18n.invitesNoFederatedPubs), - hr(), h2(`${i18n.invitesUnfollowedInvites} (${unfollowed.length})`), unfollowed.length ? renderPubTable(unfollowed, pubItem => @@ -167,145 +177,51 @@ const invitesView = ({ invitesEnabled, flash }) => { ) ) : p(i18n.invitesNoUnfollowed), - hr(), - h2(`${i18n.invitesUnreachablePubs} (${unreachablePubs.length})`), - unreachablePubs.length - ? renderPubTable(unreachablePubs, pubItem => - div({ class: 'error-box' }, - p({ class: 'error-title' }, i18n.errorDetails), - p({ class: 'error-pre' }, sanitizeError(pubItem.error)) - ) + form( + { action: '/invites/import-pubs', method: 'post', enctype: 'multipart/form-data', class: 'peers-import-form' }, + label({ class: 'peers-import-label' }, i18n.invitesPubsImportTitle || 'Import pubs'), + br(), + textarea({ name: 'peerList', rows: '4', placeholder: i18n.invitesPubsImportPlaceholder || 'Paste one multiserver address or invite code per line…' }), + br(), + input({ type: 'file', name: 'peerFile', accept: '.txt,text/plain' }), + br(), + button({ type: 'submit' }, i18n.invitesPubsImport || 'Import') + ) + ) + ) : null, + filter === 'others' ? section( + div( + h2(i18n.invitesJoinTitle), + p(i18n.invitesJoinHint), + form( + { action: '/invites/join', method: 'post' }, + input({ name: 'code', type: 'text', placeholder: i18n.invitesJoinPlaceholder, required: true }), + br(), + button({ type: 'submit' }, i18n.invitesJoinButton) + ), + h2(`${i18n.invitesUsedTitle} (${used.length})`), + used.length + ? table({ class: 'block-info-table invites-used-table' }, + tr( + td({ class: 'card-label' }, i18n.invitesUsedType), + td({ class: 'card-label' }, i18n.invitesUsedName), + td({ class: 'card-label' }, i18n.invitesUsedDate) + ), + used.map((entry) => tr( + td(INVITE_KIND_LABEL[entry.kind] ? i18n[INVITE_KIND_LABEL[entry.kind]] : entry.kind), + td(a({ href: entry.href }, entry.title || entry.href)), + td(moment(entry.at).format('YYYY/MM/DD HH:mm')) + )) ) - : p(i18n.invitesNoUnreachablePubs) + : p(i18n.invitesUsedEmpty) ) - ), - section( - div({ class: 'invites-houses', id: 'invites-houses' }, - h2(i18n.invitesHousesTitle || 'Houses'), - p(i18n.invitesHousesHint), - form( - { action: '/larp/invite/redeem', method: 'post' }, - input({ type: 'hidden', name: 'returnTo', value: '/invites' }), - input({ name: 'code', type: 'text', placeholder: i18n.invitesHouseInviteCodePlaceholder || 'House invitation code', required: true, maxlength: 32 }), - br(), - button({ type: 'submit' }, i18n.invitesHouseJoinButton || 'Join House') - ) - ) - ), - section( - div({ class: 'invites-tribes', id: 'invites-tribes' }, - h2(i18n.invitesTribesTitle), - p(i18n.invitesTribesHint), - form( - { action: '/tribes/join-code', method: 'post' }, - input({ name: 'inviteCode', type: 'text', placeholder: i18n.invitesTribeInviteCodePlaceholder, required: true }), - br(), - button({ type: 'submit' }, i18n.invitesTribeJoinButton) - ) - ) - ), - section( - div({ class: 'invites-chats', id: 'invites-chats' }, - h2(i18n.invitesChatsTitle || 'Chats'), - p(i18n.invitesChatsHint), - form( - { action: '/chats/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesChatInviteCodePlaceholder || 'Enter chat invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesChatJoinButton || 'Join Chat') - ) - ) - ), - section( - div({ class: 'invites-pads', id: 'invites-pads' }, - h2(i18n.invitesPadsTitle || 'Pads'), - p(i18n.invitesPadsHint), - form( - { action: '/pads/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesPadInviteCodePlaceholder || 'Enter pad invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesPadJoinButton || 'Join Pad') - ) - ) - ), - section( - div({ class: 'invites-calendars', id: 'invites-calendars' }, - h2(i18n.invitesCalendarsTitle || 'Calendars'), - p(i18n.invitesCalendarsHint), - form( - { action: '/calendars/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesCalendarInviteCodePlaceholder || 'Enter calendar invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesCalendarJoinButton || 'Join Calendar') - ) - ) - ), - section( - div({ class: 'invites-events', id: 'invites-events' }, - h2(i18n.invitesEventsTitle || 'Events'), - p(i18n.invitesEventsHint), - form( - { action: '/events/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesEventInviteCodePlaceholder || 'Enter event invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesEventJoinButton || 'Join Event') - ) - ) - ), - section( - div({ class: 'invites-forums', id: 'invites-forums' }, - h2(i18n.invitesForumsTitle || 'Forums'), - p(i18n.invitesForumsHint), - form( - { action: '/forum/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesForumInviteCodePlaceholder || 'Enter forum invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesForumJoinButton || 'Join Forum') - ) - ) - ), - section( - div({ class: 'invites-maps', id: 'invites-maps' }, - h2(i18n.invitesMapsTitle || 'Maps'), - p(i18n.invitesMapsHint), - form( - { action: '/maps/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesMapInviteCodePlaceholder || 'Enter map invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesMapJoinButton || 'Join Map') - ) - ) - ), - section( - div({ class: 'invites-shops', id: 'invites-shops' }, - h2(i18n.invitesShopsTitle || 'Shops'), - p(i18n.invitesShopsHint), - form( - { action: '/shops/join-code', method: 'post' }, - input({ name: 'code', type: 'text', placeholder: i18n.invitesShopInviteCodePlaceholder || 'Enter shop invite code', required: true }), - br(), - button({ type: 'submit' }, i18n.invitesShopJoinButton || 'Join Shop') - ) - ) - ), - section( - div({ class: 'invites-inhabitants', id: 'invites-inhabitants' }, - h2(i18n.invitesInhabitantsTitle || 'Inhabitants'), - p(i18n.invitesInhabitantsHint), - form( - { action: '/invites/inhabitant/follow', method: 'post' }, - input({ name: 'feedId', id: 'inh_oasis_id', type: 'text', placeholder: '@...=.ed25519', pattern: '@[A-Za-z0-9+/_\\-]{43}=\\.ed25519', required: true, maxlength: 56 }), - br(), - button({ type: 'submit' }, i18n.invitesInhabitantsFollow || 'Give Support') - ) - ) - ), - section( - div({ class: 'invites-peers', id: 'invites-peers' }, + ) : null, + filter === 'others' ? section( + div( h2(i18n.peers || 'Peers'), p(i18n.directConnectDescription), form({ action: '/peers/connect', method: 'post' }, - input({ type: 'text', id: 'peer_host', name: 'host', required: true, placeholder: '192.168.1.100', pattern: '(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|[a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?)*)', title: i18n.peerHostValidation || 'Valid IPv4 (e.g. 192.168.1.100) or hostname (e.g. pub.example.com)', maxlength: 253 }), + input({ type: 'text', id: 'peer_host', name: 'host', required: true, placeholder: '192.168.1.100', pattern: '(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|[a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9\\-]*[a-zA-Z0-9])?)*)', title: i18n.peerHostValidation || 'Valid IPv4 (e.g. 192.168.1.100) or hostname (e.g. pub.solarnethub.com)', maxlength: 253 }), br(), label({ for: 'peer_port' }, i18n.peerPort), br(), @@ -318,9 +234,10 @@ const invitesView = ({ invitesEnabled, flash }) => { button({ type: 'submit' }, i18n.connectAndFollow) ) ) - ) + ) : null ); }; exports.invitesView = invitesView; +exports.renderSuggestedPub = renderSuggestedPub; diff --git a/src/views/jobs_view.js b/src/views/jobs_view.js index 0ea989f..9d89c07 100644 --- a/src/views/jobs_view.js +++ b/src/views/jobs_view.js @@ -1,23 +1,24 @@ -const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, img, progress, video, audio, table, tr, td } = require("../server/node_modules/hyperaxe") +const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, img, progress, video, audio, table, tr, td, hr } = require("../server/node_modules/hyperaxe") +const { renderZoomableImage } = require("./gallery_view") const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, userLink, renderStateChip, renderOpenClosedChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning } = require("./main_views") -const moment = require("../server/node_modules/moment") +const { renderCallButton, clearnetItemHref, template, i18n, userLink, renderStateChip, renderOpenClosedChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, contentDeleteAction, paged, renderCreatedRows } = require("./main_views") const { config } = require("../server/SSB_server.js") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const { renderMapLocationUrl, renderMapEmbed, renderMapLocationVisitLabel } = require("./maps_view") const { resolvePhoto } = require("./inhabitants_view") +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view") const renderMediaBlob = (value, attrs = {}) => { if (!value) return null const s = String(value).trim() if (!s) return null - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: attrs.class || 'post-image' }) + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) return null } @@ -25,8 +26,8 @@ const userId = config.keys.id const FILTERS = [ { key: "ALL", i18n: "jobsFilterAll", title: "jobsAllTitle" }, - { key: "MINE", i18n: "jobsFilterMine", title: "jobsMineTitle" }, { key: "RECENT", i18n: "jobsFilterRecent", title: "jobsRecentTitle" }, + { key: "MINE", i18n: "jobsFilterMine", title: "jobsMineTitle" }, { key: "TOP", i18n: "jobsFilterTop", title: "jobsTopTitle" }, { key: "APPLIED", i18n: "jobsFilterApplied", title: "jobsAppliedTitle" }, { key: "REMOTE", i18n: "jobsFilterRemote", title: "jobsRemoteTitle" }, @@ -41,6 +42,9 @@ const FILTERS = [ const safeArr = (v) => (Array.isArray(v) ? v : []) const safeText = (v) => String(v || "").trim() +const clearnetEligible = (job) => String(job.status || "").toUpperCase() !== "CLOSED" && String(job.visibility || "PUBLIC").toUpperCase() !== "HIDDEN" +const JOB_REACH = ["PUBLIC", "HIDDEN"] +const jobReachLabel = (v) => v === "HIDDEN" ? (i18n.visibilityHidden || "Hidden") : (i18n.visibilityPublic || "Public") const parseNum = (v) => { const n = parseFloat(String(v ?? "").replace(",", ".")) @@ -68,11 +72,11 @@ const buildReturnTo = (filter, params = {}) => { const renderPmButton = (recipientId) => recipientId && String(recipientId) !== String(userId) - ? form( + ? [form( { method: "GET", action: "/pm" }, input({ type: "hidden", name: "recipients", value: recipientId }), button({ type: "submit", class: "filter-btn" }, i18n.privateMessage) - ) + ), renderCallButton(recipientId, { pam: true })] : null const renderTags = (tags = []) => { @@ -100,15 +104,6 @@ const renderApplicantsProgress = (subsCount, vacants) => { ) } -const renderUpdatedLabel = (createdAt, updatedAt) => { - const createdTs = createdAt ? new Date(createdAt).getTime() : NaN - const updatedTs = updatedAt ? new Date(updatedAt).getTime() : NaN - const showUpdated = Number.isFinite(updatedTs) && (!Number.isFinite(createdTs) || updatedTs !== createdTs) - return showUpdated - ? span({ class: "votations-comment-date" }, ` | ${i18n.jobsUpdatedAt}: ${moment(updatedAt).format("YYYY/MM/DD HH:mm")}`) - : null -} - const renderJobOwnerActions = (job, returnTo) => { const isAuthor = String(job.author) === String(userId) if (!isAuthor) return [] @@ -123,11 +118,6 @@ const renderJobOwnerActions = (job, returnTo) => { { method: "GET", action: `/jobs/edit/${encodeURIComponent(job.id)}` }, input({ type: "hidden", name: "returnTo", value: returnTo }), button({ class: "update-btn", type: "submit" }, i18n.jobsUpdateButton) - ), - form( - { method: "POST", action: `/jobs/delete/${encodeURIComponent(job.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.jobsDeleteButton) ) ] } @@ -204,13 +194,14 @@ const renderJobExtraDetails = (job) => { pushRow(i18n.jobsHoursRequested || "Hours requested", `${Number(job.hoursRequested) || 0} h`) if (skill) pushRow(i18n.jobsExchangeSkill || "Skill", skill) } - return table({ class: "tribe-info-table jobs-info-table" }, ...rows) + return table({ class: "tribe-info-table jobs-info-table" }, ...rows, ...(opts.includeCreated ? renderCreatedRows(job.createdAt, job.author, job.updatedAt) : [])) } const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => { const list = safeArr(jobs) - if (!list.length) return p(i18n.noJobsMatch || i18n.noJobsFound) + if (!list.length) return p((params.search || params.q || params.minSalary || params.maxSalary) ? (i18n.noJobsMatch || i18n.noJobsFound) : (i18n.noJobsFound || i18n.noJobsMatch)) + const returnTo = buildReturnTo(filter, params) return div({ class: "jobs-grid" }, list.map((job) => { @@ -229,7 +220,8 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => { isHidden ? renderJobHiddenChip() : null, isSubscribed ? renderJobAppliedChip() : null, job.industry ? a({ href: `/industry/${encodeURIComponent(job.industry)}` }, renderStateChip("whole", "🏭", String(i18n.industryTitle || "Industry").toUpperCase())) : null, - renderLifespanChip(job.lifetime, i18n) + renderLifespanChip(job.lifetime, i18n), + job.clearnet === true && clearnetEligible(job) ? renderReachChip(true, i18n, clearnetItemHref('jobs', job.title, job.id)) : null ].filter(Boolean) const isExchange = String(job.job_type || "").toLowerCase() === "exchange" const compensationText = isExchange @@ -241,7 +233,7 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => { div( { class: "card-header activity-card-header" }, span(), - renderContentActions(job.id, `/jobs/${encodeURIComponent(job.id)}`, { spread: params.spreadMap && params.spreadMap.get(job.id) || null, author: job.author, favKind: 'jobs', isFavorite: job.isFavorite, reportTitle: job.title }) + renderContentActions(job.id, `/jobs/${encodeURIComponent(job.id)}`, { spread: params.spreadMap && params.spreadMap.get(job.id) || null, author: job.author, favKind: 'jobs', isFavorite: job.isFavorite, reportTitle: job.title, returnTo, deleteAction: isOwn ? contentDeleteAction("job", job.id) : null }) ), div({ class: "card-section job-card-body" }, heroNode, @@ -252,7 +244,7 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => { ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - div({ class: "card-date-highlight" }, compensationText), + div({ class: "price-chip" }, compensationText), div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.jobSubscribers}: ${subs.length}`) ) @@ -263,11 +255,32 @@ const renderJobList = exports.renderJobList = (jobs, filter, params = {}) => { ) } -const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => { +const renderJobForm = (job = {}, mode = "create", spreadWarning = null, params = {}) => { const isEdit = mode === "edit" + const reachRaw = String(params.reach || "").toUpperCase() + const ownReach = String(job.visibility || "").toUpperCase() + const reach = JOB_REACH.includes(reachRaw) ? reachRaw : (JOB_REACH.includes(ownReach) ? ownReach : "PUBLIC") + const prefillKeys = ["industry", "title", "description", "tasks", "salary"] return div( { class: "div-center job-form" }, isEdit ? spreadWarning : null, + form( + { method: "GET", action: isEdit ? `/jobs/edit/${encodeURIComponent(job.id)}` : "/jobs" }, + isEdit ? null : input({ type: "hidden", name: "filter", value: "CREATE" }), + ...(isEdit ? [] : prefillKeys.filter((k) => params[k]).map((k) => input({ type: "hidden", name: k, value: params[k] }))), + label(i18n.visibilityLabel || "Visibility"), + br(), + div({ class: "apply-row" }, + select( + { name: "visibility", class: "report-category-select" }, + option({ value: "PUBLIC", ...(reach === "PUBLIC" ? { selected: true } : {})}, i18n.visibilityPublic || "Public"), + option({ value: "HIDDEN", ...(reach === "HIDDEN" ? { selected: true } : {})}, i18n.visibilityHidden || "Hidden") + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, jobReachLabel(reach)), form( { action: isEdit ? `/jobs/update/${encodeURIComponent(job.id)}` : "/jobs/create", @@ -275,6 +288,7 @@ const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => { enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: "/jobs?filter=MINE" }), + input({ type: "hidden", name: "visibility", value: reach }), job.industry ? input({ type: "hidden", name: "industry", value: job.industry }) : null, label(i18n.jobType), br(), @@ -299,17 +313,17 @@ const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => { br(), label(i18n.jobDescription), br(), - textarea({ name: "description", rows: "6", required: true, placeholder: i18n.jobDescriptionPlaceholder }, job.description || ""), + textarea({ maxlength: "5000", name: "description", rows: "6", required: true, placeholder: i18n.jobDescriptionPlaceholder }, job.description || ""), br(), br(), label(i18n.jobRequirements), br(), - textarea({ name: "requirements", rows: "6", placeholder: i18n.jobRequirementsPlaceholder }, job.requirements || ""), + textarea({ maxlength: "5000", name: "requirements", rows: "6", placeholder: i18n.jobRequirementsPlaceholder }, job.requirements || ""), br(), br(), label(i18n.jobsTagsLabel), br(), - input({ type: "text", name: "tags", value: Array.isArray(job.tags) ? job.tags.join(", ") : (job.tags || "") }), + input({ type: "text", name: "tags", placeholder: i18n.tagsPlaceholder, value: Array.isArray(job.tags) ? job.tags.join(", ") : (job.tags || "") }), br(), br(), label(i18n.jobLanguages), @@ -328,7 +342,7 @@ const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => { br(), label(i18n.jobTasks), br(), - textarea({ name: "tasks", rows: "6", placeholder: i18n.jobTasksPlaceholder }, job.tasks || ""), + textarea({ maxlength: "5000", name: "tasks", rows: "6", placeholder: i18n.jobTasksPlaceholder }, job.tasks || ""), br(), br(), label(i18n.jobLocation), @@ -345,15 +359,7 @@ const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => { input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: job.mapUrl || "" }), br(), br(), - label(i18n.visibilityLabel || "Visibility"), - br(), - select( - { name: "visibility" }, - option({ value: "PUBLIC", ...((job.visibility || "PUBLIC") === "PUBLIC" ? { selected: true } : {})}, i18n.visibilityPublic || "Public"), - option({ value: "HIDDEN", ...(job.visibility === "HIDDEN" ? { selected: true } : {})}, i18n.visibilityHidden || "Hidden") - ), - br(), - br(), + reach === "PUBLIC" && clearnetEligible({ ...job, visibility: reach }) ? renderClearnetSelector(job.clearnet === true || String(job.clearnet || "") === "1", i18n) : null, label(i18n.jobVacants), br(), input({ type: "number", name: "vacants", min: "1", placeholder: i18n.jobVacantsPlaceholder, value: job.vacants || 1, required: true }), @@ -364,6 +370,7 @@ const renderJobForm = (job = {}, mode = "create", spreadWarning = null) => { label(i18n.jobSalary), br(), input({ type: "number", name: "salary", step: "0.000001", min: "0", value: job.salary || "" }), + renderEcoValueChip(), br(), br() ] @@ -413,7 +420,7 @@ const renderCVList = (inhabitants) => ), div( { class: "inhabitant-details" }, - user.description ? p(...renderUrl(user.description)) : null, + user.description ? p(...renderStyledText(user.description)) : null, p(userLink(user.id)), user.skills && user.skills.length ? div({ class: "card-tags" }, @@ -430,6 +437,7 @@ const renderCVList = (inhabitants) => div( { class: "cv-actions" }, form({ method: "GET", action: `/inhabitant/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.cvVisitButton)), + user.pdf ? a({ href: `/blob/${encodeURIComponent(user.pdf)}`, target: "_blank", rel: "noopener", class: "filter-btn" }, "📄 " + i18n.cvPdfLabel) : null, form({ method: "GET", action: `/cv/pdf/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.generatePdf)), form({ method: "POST", action: `/cv/share/${encodeURIComponent(user.id)}` }, button({ type: "submit", class: "filter-btn" }, i18n.sharePm)), !isMe ? renderPmButton(user.id) : null @@ -450,20 +458,32 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => { const maxSalary = params.maxSalary ?? "" const sort = safeText(params.sort || "recent") + const emptyMod = moduleIsEmpty(Array.isArray(jobsOrCVs) ? jobsOrCVs : [], filter, "ALL", search || String(minSalary || "") || String(maxSalary || "")); + const censusJ = Array.isArray(params.censusList) ? params.censusList : (Array.isArray(jobsOrCVs) ? jobsOrCVs : []); + const jobChip = (x) => { + const m = x.key; + if (m === filter) return true; + if (m === "TOP") return censusJ.length > 0; + if (m === "MINE") return censusJ.some(j => String(j.author) === String(userId)); + if (m === "RECENT") return censusJ.length > 0; + if (m === "APPLIED") return censusJ.some(j => safeArr(j.subscribers).includes(userId)); + if (m === "REMOTE") return censusJ.some(j => String(j.location || "").toLowerCase() === "remote"); + if (m === "PRESENCIAL") return censusJ.some(j => String(j.location || "").toLowerCase() === "presencial"); + if (m === "FREELANCER" || m === "EMPLOYEE" || m === "EXCHANGE") return censusJ.some(j => String(j.job_type || "").toUpperCase() === m); + if (m === "OPEN" || m === "CLOSED") return censusJ.some(j => String(j.status || "OPEN").toUpperCase() === m); + if (m === "CV") return params.anyCVs === true; + return true; + }; const filterObj = FILTERS.find((f) => f.key === filter) || FILTERS[0] const sectionTitle = i18n[filterObj.title] || i18n.jobsTitle - const { renderReachChip: renderReachChipJobs } = require('./clearnet_view'); - const viewerClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetJobs) return template( i18n.jobsTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(sectionTitle), p(i18n.jobsDescription) ), - div({ class: "shop-title-row" }, renderReachChipJobs(viewerClearnet, i18n)), - br(), div( { class: "filters" }, form( @@ -472,9 +492,9 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => { input({ type: "hidden", name: "minSalary", value: String(minSalary ?? "") }), input({ type: "hidden", name: "maxSalary", value: String(maxSalary ?? "") }), input({ type: "hidden", name: "sort", value: sort }), - ...FILTERS.map((f) => + ...(emptyMod ? [] : FILTERS.filter(jobChip).map((f) => button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, String(i18n[f.i18n]).toUpperCase()) - ), + )), button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.jobsCreateJob) ) ) @@ -493,7 +513,7 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => { ) ), br(), - renderCVList(jobsOrCVs) + renderCVList(paged(jobsOrCVs)) ) : filter === "CREATE" || filter === "EDIT" ? (() => { @@ -504,36 +524,31 @@ exports.jobsView = async (jobsOrCVs, filter = "ALL", params = {}) => { ...(params.title ? { title: params.title } : {}), ...(params.description ? { description: params.description } : {}), ...(params.tasks ? { tasks: params.tasks } : {}), - ...(params.salary ? { salary: params.salary } : {}) + ...(params.salary ? { salary: params.salary } : {}), + ...(params.draft || {}) } - return renderJobForm(jobToEdit, filter === "EDIT" ? "edit" : "create", params.spreadWarning) + return renderJobForm(jobToEdit, filter === "EDIT" ? "edit" : "create", params.spreadWarning, params) })() : section( - div( - { class: "jobs-search" }, + emptyMod ? null : div( + { class: "jobs-search activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(jobsOrCVs, j => String(j.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.jobsFilterOpen }, { value: 'CLOSED', label: i18n.jobsFilterClosed }]), form( { method: "GET", action: "/jobs", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter || "ALL" }), input({ type: "text", name: "search", value: search, placeholder: i18n.jobsSearchPlaceholder, class: "filter-box__input" }), - div( - { class: "filter-box__controls" }, - div( - { class: "transfer-range" }, - input({ type: "number", name: "minSalary", step: "0.000001", min: "0", value: String(minSalary ?? ""), placeholder: i18n.jobsMinSalaryLabel, class: "filter-box__number transfer-amount-input" }), - input({ type: "number", name: "maxSalary", step: "0.000001", min: "0", value: String(maxSalary ?? ""), placeholder: i18n.jobsMaxSalaryLabel, class: "filter-box__number transfer-amount-input" }) - ), - select( - { name: "sort", class: "filter-box__select" }, - option({ value: "recent", ...(sort === "recent" ? { selected: true } : {})}, i18n.jobsSortRecent), - option({ value: "salary", ...(sort === "salary" ? { selected: true } : {})}, i18n.jobsSortSalary), - option({ value: "subscribers", ...(sort === "subscribers" ? { selected: true } : {})}, i18n.jobsSortSubscribers) - ), - button({ type: "submit", class: "filter-box__button" }, i18n.jobsSearchButton) - ) + input({ type: "number", name: "minSalary", step: "0.000001", min: "0", value: String(minSalary ?? ""), placeholder: i18n.jobsMinSalaryLabel, class: "filter-box__number transfer-amount-input" }), + input({ type: "number", name: "maxSalary", step: "0.000001", min: "0", value: String(maxSalary ?? ""), placeholder: i18n.jobsMaxSalaryLabel, class: "filter-box__number transfer-amount-input" }), + select( + { name: "sort", class: "filter-box__select" }, + option({ value: "recent", ...(sort === "recent" ? { selected: true } : {})}, i18n.jobsSortRecent), + option({ value: "salary", ...(sort === "salary" ? { selected: true } : {})}, i18n.jobsSortSalary), + option({ value: "subscribers", ...(sort === "subscribers" ? { selected: true } : {})}, i18n.jobsSortSubscribers) + ), + button({ type: "submit", class: "filter-box__button" }, i18n.jobsSearchButton) ) ), - br(), - div({ class: "jobs-list" }, renderJobList(jobsOrCVs, filter, { ...params, search, minSalary, maxSalary, sort })) + div({ class: "jobs-list" }, renderJobList(paged(jobsOrCVs), filter, { ...params, search, minSalary, maxSalary, sort })) ) ) ) @@ -570,7 +585,7 @@ const renderCandidates = (candidates, jobId) => { ), div( { class: "inhabitant-details" }, - c.description ? p(...renderUrl(c.description)) : null, + c.description ? p(...renderStyledText(c.description)) : null, p(userLink(c.id)), div({ class: "matchskills" }, p(`${i18n.matchScore || 'Match score'}: ${Math.round(c.matchScore * 100)}%`), @@ -588,7 +603,8 @@ const renderCandidates = (candidates, jobId) => { input({ type: 'hidden', name: 'subject', value: `${i18n.jobsTitle || 'Job'}: ${job.title || ''}`.slice(0, 150) }), input({ type: 'hidden', name: 'text', value: `${i18n.jobsCandidatesPmBody || 'Hi, I think your profile matches my job opening'}: /jobs/${jobId}` }), button({ type: 'submit', class: 'filter-btn' }, i18n.pmCreateButton || 'Send PM') - ) + ), + renderCallButton(c.id, { pam: true }) ) ) )) @@ -602,8 +618,8 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) const tagsNode = renderTags(job.tags) const isAuthor = String(job.author) === String(userId) const isSubscribed = subs.includes(userId) - const { renderReachChip } = require('./clearnet_view') - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetJobs && String(job.status || '').toUpperCase() !== 'CLOSED' && String(job.visibility || 'PUBLIC').toUpperCase() !== 'HIDDEN') + const canClearnet = clearnetEligible(job) + const isClearnet = canClearnet && !!job.clearnet const candidatesBlock = isAuthor ? renderCandidates(params.candidates || [], job.id) : null const ownerActions = renderJobOwnerActions(job, returnTo) @@ -616,7 +632,8 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) job.industry ? a({ href: `/industry/${encodeURIComponent(job.industry)}` }, renderStateChip("whole", "🏭", String(i18n.industryTitle || "Industry").toUpperCase())) : null, renderLifespanChip(job.lifetime, i18n), renderEcoTax(job.msgSize, job.id), - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('jobs', job.title, job.id) : null), + isAuthor && canClearnet ? renderClearnetSwitch('jobs', job.rootId || job.id, isClearnet) : null ].filter(Boolean) const nextVisibility = visibility === 'PUBLIC' ? 'HIDDEN' : 'PUBLIC' @@ -639,7 +656,7 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) const jobSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(job.id, null, { spread: params.spreads || null, author: job.author, favKind: 'jobs', isFavorite: job.isFavorite, reportTitle: job.title }) + renderContentActions(job.id, `/jobs/${encodeURIComponent(job.id)}`, { spread: params.spreads || null, author: job.author, favKind: 'jobs', isFavorite: job.isFavorite, reportTitle: job.title, returnTo, deleteAction: isAuthor ? contentDeleteAction("job", job.id) : null }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, safeText(job.title) || i18n.jobsTitle) @@ -648,7 +665,7 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) job.image ? renderMediaBlob(job.image, { class: "tribe-detail-image" }) : null, - renderJobInfoTable(job, { includeVacants: true, includeSubscribers: false }), + renderJobInfoTable(job, { includeVacants: true, includeSubscribers: false, includeCreated: true }), div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.jobSubscribers}: ${subs.length}`) ), @@ -663,7 +680,7 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) safeText(bodyText) ? div({ class: "job-section" }, h2({ class: "job-section-title" }, titleText), - p({ class: "tribe-side-description" }, ...renderUrl(bodyText)) + p({ class: "tribe-side-description" }, ...renderStyledText(bodyText)) ) : null @@ -674,18 +691,13 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) extraDetailsTable ? div({ class: "job-section" }, extraDetailsTable) : null, renderJobSection(i18n.jobTasks, job.tasks), job.mapUrl ? div({ class: "job-section" }, renderMapEmbed(params.mapData, job.mapUrl)) : null, - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(job.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(job.author), - renderUpdatedLabel(job.createdAt, job.updatedAt) - ), candidatesBlock ? candidatesBlock : null, renderJobCommentsSection(job.id, returnTo, comments) ) return template( i18n.jobsTitle, - section(div({ class: "tags-header" }, h2(i18n.jobsTitle), p(i18n.jobsDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.jobsTitle), p(i18n.jobsDescription))), section( div( { class: "filters" }, @@ -707,17 +719,19 @@ exports.singleJobsView = async (job, filter = "ALL", comments = [], params = {}) } exports.clearnetJobView = async (job) => { - const { escapeHtml: esc, blobUrl: cnBlob, renderClearnetPage } = require('./clearnet_view'); - const title = esc(job.title || 'Job'); - const desc = esc(job.description || ''); + const { escapeHtml: esc, renderRichText, renderKindTag, blobUrl: cnBlob, renderClearnetPage } = require('./clearnet_view'); + const title = esc(job.title || i18n.cnKindJob); + const desc = renderRichText(job.description || ''); const req = esc(job.requirements || ''); const lang = esc(String(job.languages || '').toUpperCase()); const loc = esc(String(job.location || '').toUpperCase()); const jobType = String(job.job_type || '').toLowerCase(); - const jobTypeLabel = jobType === 'exchange' ? 'Hour exchange' : jobType === 'employee' ? 'Employee' : 'Freelancer'; + const jobTime = String(job.job_time || '').toLowerCase(); + const jobTimeLabel = jobTime === 'partial' ? i18n.jobTimePartial : jobTime === 'complete' ? i18n.jobTimeComplete : ''; + const jobTypeLabel = jobType === 'exchange' ? i18n.jobTypeExchange : jobType === 'employee' ? i18n.jobTypeSalary : i18n.jobTypeFreelance; let compensation = ''; if (jobType === 'exchange') { - compensation = `${Number(job.hoursOffered || 0)}h offered · ${Number(job.hoursRequested || 0)}h requested`; + compensation = `${esc(i18n.jobsHoursOffered)}: ${Number(job.hoursOffered || 0)}h · ${esc(i18n.jobsHoursRequested)}: ${Number(job.hoursRequested || 0)}h`; if (job.exchangeSkill) compensation += ` · ${esc(job.exchangeSkill)}`; } else { compensation = `${parseFloat(job.salary || 0).toFixed(2)} ECO`; @@ -727,7 +741,6 @@ exports.clearnetJobView = async (job) => { .cn-job-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700} .cn-job-meta{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px} .cn-job-meta-item{background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;padding:8px 14px;font-size:14px;color:var(--fg-soft);display:inline-flex;align-items:center;gap:6px} -.cn-job-comp{background:var(--bg-sub);border:1px solid var(--fg);color:var(--fg);padding:8px 16px;border-radius:6px;font-weight:600;display:inline-block;margin-bottom:20px} .cn-job-img{display:block;max-width:100%;border:1px solid var(--border);border-radius:8px;margin-bottom:20px} .cn-job-section h2{color:var(--fg);font-size:18px;text-transform:uppercase;letter-spacing:2px;margin:24px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)} .cn-job-section p{color:var(--fg-soft);white-space:pre-wrap;line-height:1.6;font-size:15px} @@ -735,18 +748,21 @@ exports.clearnetJobView = async (job) => { const body = `

${title}

- 💼 ${jobTypeLabel} + ${renderKindTag('job')} + 💼 ${esc(jobTypeLabel)} + ${jobTimeLabel ? `⏱ ${esc(jobTimeLabel)}` : ''} ${job.createdAt ? `📅 ${esc(new Date(job.createdAt).toISOString().slice(0,10))}` : ''} ${loc ? `📍 ${loc}` : ''} ${lang ? `🗣 ${lang}` : ''} + ${compensation ? `${compensation}` : ''}
-
${compensation}
+
${jobImg ? `${title}` : ''} - ${desc ? `

Description

${desc}

` : ''} - ${req ? `

Requirements

${req}

` : ''} + ${desc ? `

${esc(i18n.description)}

${desc}

` : ''} + ${req ? `

${esc(i18n.jobRequirements)}

${req}

` : ''} `; return renderClearnetPage({ - title: `${job.title || 'Job'} — Oasis`, + title: `${job.title || 'Job'} | Oasis`, ogTitle: job.title || 'Job', ogDescription: job.description || '', ogImage: jobImg, diff --git a/src/views/larp_view.js b/src/views/larp_view.js index 970ae31..8722ff7 100644 --- a/src/views/larp_view.js +++ b/src/views/larp_view.js @@ -1,5 +1,6 @@ const { div, h1, h2, h3, p, section, button, form, input, span, img, a, br, table, tr, td, textarea, label, strong, details, summary } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink } = require("./main_views"); +const { renderStyledText } = require("../backend/renderStyledText"); +const { template, i18n, userLink, paged } = require("./main_views"); const { config } = require("../server/SSB_server.js"); const userId = config.keys.id; @@ -205,27 +206,21 @@ const renderPostsBlock = (posts, house, canPost) => { stream.length === 0 ? p({ class: 'empty' }, i18n.larpPostsEmpty || 'No posts yet.') : div({ class: 'larp-posts-list' }, - stream.map(entry => div({ class: 'larp-post' }, + paged(stream).map(entry => div({ class: entry.post.author === userId ? 'larp-post own-comment' : 'larp-post' }, + entry.post.author === userId && entry.post.id + ? form({ method: 'POST', action: `/larp/post/delete/${encodeURIComponent(entry.post.id)}`, class: 'comment-delete-form' }, + button({ type: 'submit', class: 'btn-singleview btn-delete', title: i18n.chatDelete }, '✕')) + : null, div({ class: 'larp-post-head' }, userLink(entry.post.author), span({ class: 'larp-post-time' }, moment(entry.post.createdAt).format('YYYY/MM/DD HH:mm')) ), - p({ class: 'larp-post-text' }, entry.post.text) + p({ class: 'larp-post-text' }, ...renderStyledText(String(entry.post.text || ''))) )) ) ); }; -const renderHouseSearch = (q) => div({ class: 'filters' }, - form({ method: 'GET', action: '/larp', class: 'filter-box' }, - input({ type: 'hidden', name: 'filter', value: 'houses' }), - input({ type: 'text', name: 'q', value: q || '', placeholder: i18n.larpSearchPlaceholder, class: 'filter-box__input' }), - div({ class: 'filter-box__controls' }, - button({ type: 'submit', class: 'filter-box__button' }, i18n.searchButton) - ) - ) -); - const matchesHouse = (house, q) => { const needle = String(q || '').trim().toLowerCase(); if (!needle) return true; @@ -317,18 +312,17 @@ exports.larpListView = ({ filter, houses, myHouseKey, cycle, governingKey, gover return template( title, section( - div({ class: 'tags-header' }, - h1(title), + div({ class: 'tags-header module-header-line' }, + h2(title), p(description) ), + renderModeButtons(mode), renderCycleBanner(cycle), renderHouseBadges({ myHouse, governingHouse, houses }), - renderModeButtons(mode), - renderHouseSearch(search), mode === 'houses' ? (matched.length ? renderHousesGrid(matched, myHouseKey, governingKey) - : p({ class: 'no-content' }, i18n.larpNoHousesMatch)) + : div({ class: 'no-content-box' }, p({ class: 'no-content' }, i18n.larpNoHousesMatch))) : mode === 'rules' ? renderRules() : [ @@ -384,7 +378,7 @@ exports.larpHouseView = ({ house, members, myHouseKey, cycle, governingKey, hous p({ class: 'larp-invite-banner-code' }, inviteCode) ) : null, - isAcademia && viewerInAcademia ? renderAcademiaJoinPanel(houses, testStatus, housesById, questions, myHouseKey) : null, + null, showWall ? renderPostsBlock(posts, house, canWriteWall) : null diff --git a/src/views/legacy_view.js b/src/views/legacy_view.js deleted file mode 100644 index 80f06f5..0000000 --- a/src/views/legacy_view.js +++ /dev/null @@ -1,66 +0,0 @@ -const { form, button, div, h2, p, section, input, label, br } = require("../server/node_modules/hyperaxe"); -const { template, i18n } = require('./main_views'); - -const crypto = require('crypto'); - -function generateRandomPassword(length = 32) { - return crypto.randomBytes(length).toString('hex').slice(0, length); -} - -const legacyView = async () => { - const randomPassword = generateRandomPassword(); - - const header = div({ class: 'tags-header' }, - h2(i18n.legacyTitle), - p(i18n.legacyDescription) - ); - - return template( - i18n.legacyTitle, - section( - header, - p({ id: "randomPassword" }, h2({ class: "generated-password" }, randomPassword)), - div({ class: "div-center legacy-section" }, - p(i18n.exportDescription), - form( - { - action: "/legacy/export", - method: "POST", - id: "exportForm" - }, - label(i18n.exportPasswordLabel), - br(), - input({ - type: "password", - name: "password", - id: "password", - required: true, - placeholder: i18n.exportPasswordPlaceholder, - minlength: 32 - }), - p({ class: "file-info" }, i18n.fileInfo), - button({ type: "submit" }, i18n.legacyExportButton) - ), - br(), - p(i18n.importDescription), - form( - { action: "/legacy/import", method: "POST", enctype: "multipart/form-data" }, - input({ type: "file", name: "uploadedFile", required: true }), - br(), - p(i18n.passwordImport), - input({ - type: "password", - name: "importPassword", - required: true, - placeholder: i18n.importPasswordPlaceholder, - minlength: 32 - }), - br(), - button({ type: "submit" }, i18n.legacyImportButton) - ) - ) - ) - ); -}; - -exports.legacyView = legacyView; diff --git a/src/views/logistics_view.js b/src/views/logistics_view.js new file mode 100644 index 0000000..80567ea --- /dev/null +++ b/src/views/logistics_view.js @@ -0,0 +1,400 @@ +const { hr, div, h2, h3, p, section, button, form, a, span, br, textarea, input, label, select, option, img, table, tr, td, video: videoHyperaxe } = require("../server/node_modules/hyperaxe"); +const { template, i18n, userLink, renderStateChip, renderContentActions, renderModuleStats, renderOpinionsVoting, renderEngagement, moduleIsEmpty, renderEcoValueChip, contentDeleteAction, paged } = require("./main_views"); +const { renderCommentsSection } = require("./comments_view"); +const { renderMapEmbed } = require("./maps_view"); +const { renderStyledText } = require("../backend/renderStyledText"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); + +const userId = config.keys.id; +const BASE_FILTERS = ["ALL", "RECENT", "MINE", "BOOKED", "HISTORY", "TOP"]; +const KIND_FILTERS = ["TRIPS", "SHIPMENTS"]; +const MODE_FILTERS = ["OFFERS", "REQUESTS"]; +const DATE_FILTERS = ["UPCOMING", "PAST", "CLOSED"]; +const PRICE_FILTERS = ["FREE", "ECO", "TIME"]; +const KINDS = ["TRIP", "SHIPMENT"]; +const MODES = ["OFFER", "REQUEST"]; +const RECURRENCES = ["NONE", "DAILY", "WEEKLY", "MONTHLY"]; + +const cap = (s) => s.charAt(0) + s.slice(1).toLowerCase(); +const lbl = (prefix, v) => String(i18n[`logistics${prefix}${cap(v)}`] || v).toUpperCase(); +const filterLabel = (f) => lbl("Filter", f); +const kindLabel = (k) => lbl("Kind", k); +const modeLabel = (m) => lbl("Mode", m); +const statusLabel = (s) => lbl("Status", s); +const priceLabel = (t) => lbl("Price", t); +const recurrenceLabel = (r) => lbl("Recurrence", r); +const bookingLabel = (s) => lbl("Booking", s); + +const routeHref = (route) => `/logistics/${encodeURIComponent(route.id)}`; +const fmt = (d) => moment(d).format("YYYY/MM/DD HH:mm"); +const stars = (n) => "★".repeat(Math.round(n)) + "☆".repeat(5 - Math.round(n)); + +const priceText = (route) => { + if (route.priceType === "ECO") return `${route.price} ECO`; + if (route.priceType === "TIME") return `${route.price} ${i18n.logisticsHours}`; + return priceLabel("FREE"); +}; + +const kindChip = (route) => renderStateChip("neutral", route.kind === "TRIP" ? "🚗" : "📦", kindLabel(route.kind)); +const modeChip = (route) => renderStateChip(route.mode === "OFFER" ? "mutuals" : "whole", "", modeLabel(route.mode)); +const statusChip = (route) => renderStateChip(route.status === "OPEN" ? "mutuals" : (route.status === "PAST" ? "whole" : "closed"), route.status === "OPEN" ? "✓" : (route.status === "CLOSED" ? "✗" : ""), statusLabel(route.status)); +const priceChip = (route) => renderStateChip("neutral", "", priceText(route)); + +const renderTags = (tags) => (Array.isArray(tags) && tags.length) + ? div({ class: "card-tags" }, ...tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) + : null; + +const routeLine = (route) => p({ class: "logistics-route-line" }, span({ class: "logistics-zone" }, route.origin), " → ", span({ class: "logistics-zone" }, route.destination)); +const whenLine = (route) => p({ class: "job-meta-line" }, `📅 ${fmt(route.date)}${route.recurrence !== "NONE" ? ` · ${recurrenceLabel(route.recurrence)}` : ""}`); +const capacityLine = (route) => { + if (route.kind === "TRIP") return route.seats ? p({ class: "job-meta-line" }, `💺 ${route.seatsLeft} / ${route.seats}`) : null; + const parts = [route.size ? `${i18n.logisticsSizeLabel}: ${route.size}` : null, route.weight ? `${i18n.logisticsWeightLabel}: ${route.weight}` : null].filter(Boolean); + return parts.length ? p({ class: "job-meta-line" }, `📦 ${parts.join(" · ")}`) : null; +}; +const countersBlock = (route) => + div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.logisticsRatingsTitle}: ${route.ratingCount || 0}${route.ratingCount ? ` · ${stars(route.ratingAvg)} ${route.ratingAvg}` : ""}`) + ); +const bookingsBlock = (route) => route.isOwner + ? div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.logisticsBookingsLabel}: ${route.bookingCount || 0}`)) + : null; +const renderCover = (route) => { + if (!route.media || !route.media.blobId) return null; + if (route.media.kind === "video") return videoHyperaxe({ class: "logistics-card-cover", src: `/blob/${encodeURIComponent(route.media.blobId)}`, controls: true, preload: "metadata" }); + return a({ href: routeHref(route) }, img({ loading: 'lazy', class: "logistics-card-cover", src: `/blob/${encodeURIComponent(route.media.blobId)}`, alt: route.title || "" })); +}; + +const logisticsChipFor = (mode, census) => { + if (mode === "ALL") return true; + if (!Array.isArray(census)) return true; + if (mode === "MINE") return census.some(r => String(r.author) === String(userId)); + if (mode === "BOOKED") return census.some(r => !!r.myBooking); + if (mode === "RECENT") return census.length > 0; + if (mode === "HISTORY") return census.some(r => r.closed && r.participated); + if (mode === "TOP") return census.some(r => r.ratingCount > 0); + if (mode === "TRIPS") return census.some(r => r.kind === "TRIP"); + if (mode === "SHIPMENTS") return census.some(r => r.kind === "SHIPMENT"); + if (mode === "OFFERS") return census.some(r => r.mode === "OFFER"); + if (mode === "REQUESTS") return census.some(r => r.mode === "REQUEST"); + if (mode === "UPCOMING") return census.some(r => r.status === "OPEN"); + if (mode === "PAST") return census.some(r => r.status === "PAST"); + if (mode === "CLOSED") return census.some(r => r.status === "CLOSED"); + if (PRICE_FILTERS.includes(mode)) return census.some(r => r.priceType === mode); + return census.length > 0; +}; +exports.logisticsChipFor = logisticsChipFor; + +const renderRouteCard = (route, params = {}) => + div({ class: `tribe-card logistics-card logistics-${route.kind.toLowerCase()}` }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(route.id, routeHref(route), { author: route.author, favKind: "logistics", isFavorite: route.isFavorite, reportTitle: route.title, spread: (params.spreadMap && params.spreadMap.get(route.id)) || null, deleteAction: route.isOwner ? contentDeleteAction("logisticsRoute", route.id) : null }) + ), + div({ class: "tribe-card-body" }, + renderCover(route), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: routeHref(route) }, route.title || "—"))), + div({ class: "card-chips-row" }, kindChip(route), modeChip(route), statusChip(route), priceChip(route)), + routeLine(route), + whenLine(route), + capacityLine(route), + countersBlock(route) + ) + ); + +const chipForm = (mode, filter, q, zone, labelText, extra = {}) => + form({ class: "sub-filter-form", method: "GET", action: "/logistics" }, + input({ type: "hidden", name: "q", value: q || "" }), + input({ type: "hidden", name: "filter", value: extra.filter !== undefined ? extra.filter : mode }), + input({ type: "hidden", name: "zone", value: extra.zone !== undefined ? extra.zone : (zone || "") }), + button({ type: "submit", class: extra.active ? "filter-btn active" : "filter-btn" }, labelText) + ); + +const chipButtons = (modes, filter, census, labelOf) => + modes.filter(m => m === filter || logisticsChipFor(m, census)).map(m => button({ type: "submit", name: "filter", value: m, class: filter === m ? "filter-btn active" : "filter-btn" }, labelOf(m))); + +const zoneRow = (zones, filter, q, zone) => { + if (!Array.isArray(zones) || !zones.length) return null; + const current = String(zone || "").toLowerCase(); + return div({ class: "activity-sub-filter logistics-zones" }, + span({ class: "inbox-filters-label" }, `${i18n.logisticsZoneLabel}:`), + ...zones.map(z => chipForm(filter, filter, q, zone, z.label, { filter, zone: current === z.label.toLowerCase() ? "" : z.label, active: current === z.label.toLowerCase() })) + ); +}; + +const renderFilters = (filter, q, zone, census, zones, emptyMod) => + section( + div({ class: "filters" }, + form({ method: "GET", action: "/logistics", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q || "" }), + input({ type: "hidden", name: "zone", value: zone || "" }), + ...(emptyMod ? [] : [ + ...chipButtons(BASE_FILTERS, filter, census, filterLabel), + ...chipButtons(KIND_FILTERS, filter, census, kindLabel), + ...chipButtons(MODE_FILTERS, filter, census, modeLabel), + ...chipButtons(DATE_FILTERS, filter, census, statusLabel), + ...chipButtons(PRICE_FILTERS, filter, census, priceLabel) + ]), + button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.logisticsCreate) + ) + ), + emptyMod ? null : zoneRow(zones, filter, q, zone) + ); + +const renderForm = (route, params = {}) => { + const kind = route ? route.kind : (KINDS.includes(String(params.kind || "").toUpperCase()) ? String(params.kind).toUpperCase() : "TRIP"); + const isTrip = kind === "TRIP"; + const formId = route ? "logistics-edit-form" : "logistics-create-form"; + return div({ class: "report-form" }, + route + ? null + : form({ id: "logistics-kind-form", method: "GET", action: "/logistics" }, + input({ type: "hidden", name: "filter", value: "CREATE" }), + label(i18n.logisticsKindLabel), br(), + div({ class: "apply-row" }, + select({ name: "kind", class: "report-category-select" }, ...KINDS.map(k => option({ value: k, ...(k === kind ? { selected: true } : {}) }, kindLabel(k)))), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + route ? null : hr({ class: "form-sep" }), + route ? null : h2({ class: "report-category-fixed" }, kindLabel(kind)), + form({ id: formId, method: "POST", action: route ? `/logistics/update/${encodeURIComponent(route.id)}` : "/logistics/create", enctype: "multipart/form-data" }, + input({ type: "hidden", name: "kind", value: kind }), + label(i18n.logisticsTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.logisticsTitlePlaceholder, value: route ? route.title : "" }), br(), + label(i18n.logisticsModeLabel), br(), + select({ name: "mode" }, ...MODES.map(m => option({ value: m, ...((route ? route.mode : "OFFER") === m ? { selected: true } : {}) }, modeLabel(m)))), br(), br(), + label(i18n.logisticsDescriptionLabel), br(), + textarea({ name: "description", rows: 5, maxlength: "3000", placeholder: i18n.logisticsDescriptionPlaceholder }, route ? route.description : ""), br(), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + label(i18n.logisticsOriginLabel), br(), + input({ type: "text", name: "origin", maxlength: "100", required: true, placeholder: i18n.logisticsOriginPlaceholder, value: route ? route.origin : "" }), br(), + label(i18n.logisticsDestinationLabel), br(), + input({ type: "text", name: "destination", maxlength: "100", required: true, placeholder: i18n.logisticsDestinationPlaceholder, value: route ? route.destination : "" }), br(), + label(i18n.mapLocationTitle || "Map Location"), br(), + input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: route ? route.mapUrl : "" }), br(), + label(i18n.logisticsDateLabel), br(), + input({ type: "datetime-local", name: "date", required: true, min: moment().format("YYYY-MM-DDTHH:mm"), value: route && route.date ? moment(route.date).format("YYYY-MM-DDTHH:mm") : "" }), br(), br(), + label(i18n.logisticsRecurrenceLabel), br(), + select({ name: "recurrence" }, ...RECURRENCES.map(r => option({ value: r, ...((route ? route.recurrence : "NONE") === r ? { selected: true } : {}) }, recurrenceLabel(r)))), br(), br(), + isTrip + ? [ + label(i18n.logisticsSeatsLabel), br(), + input({ type: "number", name: "seats", min: "1", max: "99", step: "1", required: true, class: "logistics-short", value: route ? String(route.seats || 1) : "1" }), br(), br() + ] + : [ + label(i18n.logisticsSizeLabel), br(), + input({ type: "text", name: "size", maxlength: "60", placeholder: i18n.logisticsSizePlaceholder, value: route ? route.size : "" }), br(), + label(i18n.logisticsWeightLabel), br(), + input({ type: "text", name: "weight", maxlength: "60", placeholder: i18n.logisticsWeightPlaceholder, value: route ? route.weight : "" }), br() + ], + label(i18n.logisticsPriceLabel), br(), + input({ type: "number", name: "price", min: "0", step: "0.01", placeholder: "0", class: "logistics-short", value: route && route.price ? String(route.price) : "" }), + select({ name: "priceType", class: "logistics-price-unit" }, ...["ECO", "TIME"].map(t => option({ value: t, ...((route && route.priceType === "TIME" ? "TIME" : "ECO") === t ? { selected: true } : {}) }, priceLabel(t)))), renderEcoValueChip(), br(), br(), + label(i18n.logisticsOrderRefLabel), br(), + input({ type: "text", name: "orderRef", maxlength: "200", placeholder: i18n.logisticsOrderRefPlaceholder, value: route ? route.orderRef : "" }), br(), + label(i18n.logisticsTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.logisticsTagsPlaceholder, value: route ? route.tags.join(", ") : "" }), br(), br(), + button({ type: "submit", class: "create-button" }, route ? i18n.logisticsUpdate : i18n.logisticsCreate) + ) + ); +}; + +const renderHeader = () => div({ class: "tags-header module-header-line" }, h2(i18n.logisticsTitle), p(i18n.logisticsDescription)); + +exports.logisticsView = async (routes, filter = "ALL", params = {}) => { + const list = Array.isArray(routes) ? routes : []; + const q = String(params.q || "").trim(); + const zone = String(params.zone || "").trim(); + const census = Array.isArray(params.censusList) ? params.censusList : list; + const zones = Array.isArray(params.zones) ? params.zones : []; + const f = String(filter || "ALL").toUpperCase(); + const isForm = f === "CREATE" || f === "EDIT"; + const emptyMod = !isForm && moduleIsEmpty(census, "ALL", "ALL", q) && !zone; + return template( + i18n.logisticsTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.logisticsTitle), p(i18n.logisticsDescription)), + renderFilters(isForm ? "ALL" : f, q, isForm ? "" : zone, isForm ? [] : census, isForm ? [] : zones, emptyMod) + ), + section( + isForm + ? renderForm(f === "EDIT" ? params.route : null, params) + : [ + emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length, [ + { label: kindLabel("TRIP"), count: list.filter(r => r.kind === "TRIP").length }, + { label: kindLabel("SHIPMENT"), count: list.filter(r => r.kind === "SHIPMENT").length }, + { label: statusLabel("OPEN"), count: list.filter(r => r.status === "OPEN").length } + ]), + form({ method: "GET", action: "/logistics", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: f }), + input({ type: "hidden", name: "zone", value: zone }), + input({ type: "text", name: "q", value: q, placeholder: i18n.logisticsSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ), + div({ class: "tribe-grid" }, list.length ? paged(list).map(r => renderRouteCard(r, params)) : p(i18n.logisticsNoItems)) + ] + ) + ); +}; + +const bookingStatusChip = (b) => renderStateChip(b.status === "CONFIRMED" || b.status === "DELIVERED" ? "mutuals" : (b.status === "PENDING" ? "whole" : "closed"), "", bookingLabel(b.status)); + +const bookingStatusForm = (route, b, status, cls, text) => + form({ method: "POST", action: `/logistics/booking/${encodeURIComponent(b.id)}/status` }, + input({ type: "hidden", name: "status", value: status }), + input({ type: "hidden", name: "route", value: route.id }), + button({ type: "submit", class: cls }, String(text).toUpperCase()) + ); + +const renderBooking = (route, b) => + div({ class: "logistics-booking" }, + div({ class: "emergency-update-head logistics-booking-head" }, + div({ class: "card-chips-row" }, bookingStatusChip(b), route.kind === "TRIP" ? renderStateChip("neutral", "💺", String(b.seats)) : null), + div({ class: "tribe-side-actions emergency-update-actions" }, + route.isOwner && b.status === "PENDING" ? bookingStatusForm(route, b, "CONFIRMED", "tribe-action-btn", i18n.logisticsConfirm) : null, + route.isOwner && b.status === "PENDING" ? bookingStatusForm(route, b, "REJECTED", "delete-btn", i18n.logisticsReject) : null, + b.mine && (b.status === "PENDING" || b.status === "CONFIRMED") ? bookingStatusForm(route, b, "CANCELLED", "delete-btn", i18n.logisticsCancelBooking) : null + ) + ), + b.notes ? p({ class: "logistics-booking-notes" }, b.notes) : null, + b.orderRef ? p({ class: "job-meta-line" }, a({ href: b.orderRef, class: "user-link" }, `🛍 ${i18n.logisticsOrderLink}`)) : null, + b.receipt ? p({ class: "logistics-receipt" }, `🧾 ${i18n.logisticsReceiptLabel}: ${b.receipt}`) : null, + route.isOwner && b.status === "CONFIRMED" + ? form({ method: "POST", action: `/logistics/booking/${encodeURIComponent(b.id)}/status`, class: "logistics-receipt-form" }, + input({ type: "hidden", name: "status", value: "DELIVERED" }), + input({ type: "hidden", name: "route", value: route.id }), + input({ type: "text", name: "receipt", maxlength: "200", placeholder: i18n.logisticsReceiptPlaceholder }), + button({ type: "submit", class: "tribe-action-btn" }, String(i18n.logisticsDeliver).toUpperCase()) + ) + : null, + p({ class: "card-footer" }, span({ class: "date-link" }, `${fmt(b.createdAt)}`), userLink(b.booker)) + ); + +const renderBookForm = (route) => + form({ method: "POST", action: `/logistics/${encodeURIComponent(route.id)}/book`, class: "logistics-book-form" }, + h3(i18n.logisticsBookTitle), + route.kind === "TRIP" ? [label(i18n.logisticsSeatsLabel), br(), input({ type: "number", name: "seats", min: "1", max: String(route.seatsLeft || 99), step: "1", value: "1" }), br(), br()] : null, + label(i18n.logisticsNotesLabel), br(), + textarea({ name: "notes", rows: 3, maxlength: "1000", placeholder: i18n.logisticsNotesPlaceholder }), br(), br(), + label(i18n.logisticsOrderRefLabel), br(), + input({ type: "text", name: "orderRef", maxlength: "200", placeholder: i18n.logisticsOrderRefPlaceholder }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.logisticsBook) + ); + +const renderRatings = (route) => + div({ class: "card-section logistics-ratings" }, + h3(i18n.logisticsRatingsTitle), + route.ratings.length + ? div({ class: "logistics-ratings-list" }, ...route.ratings.map(r => div({ class: "logistics-rating" }, + div({ class: "emergency-update-head logistics-rating-head" }, div({ class: "card-chips-row" }, renderStateChip("neutral", "", stars(r.score)))), + r.text ? p(r.text) : null, + p({ class: "card-footer" }, span({ class: "date-link" }, `${fmt(r.createdAt)}`), userLink(r.author)) + ))) + : p(route.closed ? i18n.logisticsNoRatings : i18n.logisticsRatingsWhenClosed), + route.canRate + ? form({ method: "POST", action: `/logistics/${encodeURIComponent(route.id)}/rate`, class: "logistics-rate-form" }, + label(i18n.logisticsScoreLabel), br(), + select({ name: "score" }, ...[5, 4, 3, 2, 1].map(n => option({ value: String(n) }, stars(n)))), br(), br(), + textarea({ name: "text", rows: 3, maxlength: "1000", placeholder: i18n.logisticsRatingPlaceholder }), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.logisticsRate) + ) + : null + ); + +exports.singleLogisticsView = async (route, params = {}) => { + const href = routeHref(route); + const census = Array.isArray(params.censusList) ? params.censusList : []; + const zones = Array.isArray(params.zones) ? params.zones : []; + const ownerActions = route.isOwner + ? [ + form({ method: "GET", action: "/logistics" }, input({ type: "hidden", name: "filter", value: "edit" }), input({ type: "hidden", name: "id", value: route.id }), button({ type: "submit", class: "update-btn" }, i18n.logisticsUpdate)) + ] + : []; + const capacity = route.kind === "TRIP" + ? (route.seats ? [{ label: i18n.logisticsSeatsLabel, value: `${route.seatsLeft} / ${route.seats}` }] : []) + : [route.size ? { label: i18n.logisticsSizeLabel, value: route.size } : null, route.weight ? { label: i18n.logisticsWeightLabel, value: route.weight } : null].filter(Boolean); + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(route.id, href, { author: route.author, favKind: "logistics", isFavorite: route.isFavorite, reportTitle: route.title, spread: params.spread || null, deleteAction: route.isOwner ? contentDeleteAction("logisticsRoute", route.id) : null }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, route.title)), + div({ class: "card-chips-row" }, kindChip(route), modeChip(route), statusChip(route), priceChip(route)), + table({ class: "tribe-info-table jobs-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), + td({ class: "tribe-info-value", colspan: "3" }, fmt(route.createdAt)) + ), + tr( + td({ class: "tribe-info-label" }, i18n.logisticsDateLabel), + td({ class: "tribe-info-value", colspan: "3" }, fmt(route.date)) + ), + route.recurrence !== "NONE" + ? tr( + td({ class: "tribe-info-label" }, i18n.logisticsFrequencyLabel), + td({ class: "tribe-info-value", colspan: "3" }, recurrenceLabel(route.recurrence)) + ) + : null, + tr( + td({ class: "tribe-info-label" }, i18n.logisticsOriginLabel), + td({ class: "tribe-info-value", colspan: "3" }, route.origin) + ), + tr( + td({ class: "tribe-info-label" }, i18n.logisticsDestinationLabel), + td({ class: "tribe-info-value", colspan: "3" }, route.destination) + ), + ...capacity.map(c => tr( + td({ class: "tribe-info-label" }, c.label), + td({ class: "tribe-info-value", colspan: "3" }, c.value) + )), + tr( + td({ class: "tribe-info-value", colspan: "4" }, userLink(route.author)) + ) + ), + countersBlock(route), + renderMapEmbed(params.mapData, route.mapUrl), + route.orderRef + ? div({ class: "tribe-side-actions" }, form({ method: "GET", action: route.orderRef }, button({ type: "submit", class: "tribe-action-btn" }, `🛍 ${String(i18n.logisticsOrderLink).toUpperCase()}`))) + : null, + route.isOwner + ? div({ class: "tribe-side-actions housing-status-row" }, + span({ class: "card-label" }, `${i18n.statusLabel || "Status"}: `), + statusChip(route), + form({ method: "POST", action: `/logistics/status/${encodeURIComponent(route.id)}`, class: "inline-form" }, + button({ class: "tribe-action-btn", type: "submit", name: "status", value: route.status === "CLOSED" ? "OPEN" : "CLOSED" }, + String(route.status === "CLOSED" ? i18n.logisticsReopen : i18n.logisticsClose).toUpperCase()) + ) + ) + : null, + bookingsBlock(route), + div({ class: "doc-export-actions" }, + form({ method: "GET", action: `${href}/pdf` }, button({ type: "submit", class: "filter-btn" }, i18n.logisticsGenerateReport)), + form({ method: "POST", action: `${href}/share` }, button({ type: "submit", class: "filter-btn" }, i18n.sharePm)) + ), + renderTags(route.tags), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null + ); + const main = div({ class: "tribe-main" }, + route.description ? div({ class: "logistics-body" }, ...renderStyledText(route.description)) : null, + route.canBook ? div({ class: "card-section" }, renderBookForm(route)) : null, + route.bookings.length + ? div({ class: "card-section logistics-bookings" }, + h3(route.isOwner ? `${i18n.logisticsBookingsTitle} (${route.bookings.length})` : i18n.logisticsMyBookingTitle), + ...route.bookings.map(b => renderBooking(route, b)) + ) + : null, + renderRatings(route), + renderEngagement(route.id, + renderOpinionsVoting("/logistics/opinions", route.id, route.opinions, href, route.opinions_inhabitants), + renderCommentsSection({ action: `/logistics/${encodeURIComponent(route.id)}/comments`, comments: params.comments || [], returnTo: href }) + ) + ); + return template( + route.title, + section(renderHeader(), renderFilters("ALL", "", "", census, zones, census.length === 0)), + section(div({ class: "tribe-details" }, side, main)) + ); +}; diff --git a/src/views/logs_view.js b/src/views/logs_view.js index 8aa7653..b43b790 100644 --- a/src/views/logs_view.js +++ b/src/views/logs_view.js @@ -1,8 +1,8 @@ const { div, h2, p, section, button, form, span, table, thead, tbody, tr, th, td, input, textarea, br, option, select, a, label } = require("../server/node_modules/hyperaxe"); -const { template, i18n } = require("./main_views"); +const { template, i18n, renderModuleStatsBy, paged } = require("./main_views"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const userId = config.keys.id; @@ -21,10 +21,10 @@ const filterLabel = (f) => { return map[f] || f.toUpperCase(); }; -const renderFilterBar = (current, hasItems = true) => +const renderFilterBar = (current, hasItems = true, emptyLogs = false, avail = null) => div({ class: "activity-sub-filter" }, - form({ method: "GET", action: "/logs", class: "sub-filter-form" }, - FILTERS.map(f => + emptyLogs ? null : form({ method: "GET", action: "/logs", class: "sub-filter-form" }, + FILTERS.filter(f => f === current || f === "always" || !avail || avail[f] !== false).map(f => button({ type: "submit", name: "filter", value: f, class: current === f ? "filter-btn active" : "filter-btn" @@ -42,9 +42,10 @@ const renderFilterBar = (current, hasItems = true) => : null ); -const renderSearchBox = (current, search) => { +const renderSearchBox = (current, search, items = null) => { const q = search || {}; - return div({ class: "logs-search" }, + return div({ class: "logs-search activity-filter-chips activity-toolbar-row" }, + items ? renderModuleStatsBy(items, e => (e && e.mode === 'ai' ? 'ai' : 'manual'), [{ value: 'manual', label: i18n.logsModeManual }, { value: 'ai', label: i18n.logsModeAI }]) : null, form({ method: "GET", action: "/logs", class: "filter-box" }, input({ type: "hidden", name: "filter", value: current || 'today' }), input({ @@ -68,11 +69,18 @@ const renderSearchBox = (current, search) => { ); }; -const renderToolbar = (current, search, hasItems) => - div({ class: "logs-toolbar-wrap" }, - renderFilterBar(current, hasItems), - renderSearchBox(current, search) - ); +const renderToolbar = (current, search, hasItems, items = null, emptyLogs = false, avail = null) => + emptyLogs + ? div({ class: "filters" }, + form({ method: "GET", action: "/logs", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "view", value: "create" }), + button({ type: "submit", class: "create-button" }, i18n.logsCreate || 'Create Log') + ) + ) + : div({ class: "logs-toolbar-wrap" }, + renderFilterBar(current, hasItems, emptyLogs, avail), + renderSearchBox(current, search, items) + ); const truncate = (value, max = 160) => { const text = String(value == null ? '' : value).trim(); @@ -81,7 +89,7 @@ const truncate = (value, max = 160) => { const renderLogPreview = (item) => { const text = truncate(item.text); - return div({ class: "logs-entry-text" }, ...renderUrl(text)); + return div({ class: "logs-entry-text" }, ...renderStyledText(text)); }; const renderTable = (items) => { @@ -160,7 +168,7 @@ const renderCreateForm = (mode, aiModOn) => { : div({ class: "div-center audio-form" }, form({ method: "POST", action: "/logs/create" }, input({ type: "hidden", name: "mode", value: "manual" }), - textarea({ name: "text", rows: "8", required: true, placeholder: i18n.logsTextPlaceholder || 'Describe your experiences...' }), + textarea({ maxlength: "5000", name: "text", rows: "8", required: true, placeholder: i18n.logsTextPlaceholder || 'Describe your experiences...' }), br(), br(), button({ type: "submit", class: "create-button" }, i18n.logsWriteButton || 'Write') ) @@ -172,7 +180,7 @@ const renderEditForm = (entry) => { return div({ class: "div-center audio-form" }, h2(i18n.logsEditTitle || 'Update Log'), form({ method: "POST", action: `/logs/update/${encodeURIComponent(entry.key)}` }, - textarea({ name: "text", rows: "8", required: true }, entry.text || ''), + textarea({ maxlength: "5000", name: "text", rows: "8", required: true }, entry.text || ''), input({ type: "hidden", name: "label", value: entry.label || '' }), br(), br(), button({ type: "submit", class: "create-button" }, i18n.logsUpdateButton || 'Update') @@ -185,7 +193,7 @@ const renderDetail = (entry) => { return div({ class: "div-center audio-form logs-detail" }, h2(headerLine), entry.label ? div({ class: "logs-entry-label" }, entry.label) : null, - div({ class: "logs-detail-text" }, ...renderUrl(String(entry.text || ''))), + div({ class: "logs-detail-text" }, ...renderStyledText(String(entry.text || ''))), div({ class: "tribe-side-actions logs-detail-actions" }, form({ method: "GET", action: "/logs" }, button({ type: "submit", class: "tribe-action-btn" }, i18n.walletBack || 'Back') @@ -214,8 +222,12 @@ exports.logsView = (items, filter, mode, opts = {}) => { const screen = (...blocks) => template( listTitle, section( - div({ class: "tags-header" }, h2(listTitle), p(description)), - renderFilterBar(filter), + div({ class: "tags-header module-header-line" }, h2(listTitle), p(description)), + div({ class: "activity-sub-filter" }, + form({ method: "GET", action: "/logs", class: "sub-filter-form" }, + button({ type: "submit", name: "filter", value: filter || "today", class: "filter-btn active" }, String(filterLabel(filter || "today")).toUpperCase()) + ) + ), ...blocks ) ); @@ -229,10 +241,12 @@ exports.logsView = (items, filter, mode, opts = {}) => { if (view === 'detail' && opts.entry) { return screen(renderDetail(opts.entry)); } + const sr = opts.search || {}; + const emptyLogs = Number(opts.total ?? items.length) === 0 && !String(sr.q || '').trim() && !String(sr.type || '').trim() && !String(sr.date || '').trim(); const body = section( - div({ class: "tags-header" }, h2(listTitle), p(description)), - renderToolbar(filter, opts.search || {}, hasItems), - div({ class: "logs-list" }, renderTable(items)) + div({ class: "tags-header module-header-line" }, h2(listTitle), p(description)), + renderToolbar(filter, opts.search || {}, hasItems, items, emptyLogs, opts.avail || null), + div({ class: "logs-list" }, renderTable(paged(items))) ); return template(listTitle, body); }; diff --git a/src/views/mailing_view.js b/src/views/mailing_view.js new file mode 100644 index 0000000..e666dbb --- /dev/null +++ b/src/views/mailing_view.js @@ -0,0 +1,322 @@ +const { div, h2, h3, p, section, button, form, a, span, br, textarea, input, label, select, option, details, summary, ul, li, table, tr, td } = require("../server/node_modules/hyperaxe"); +const { TEXT_CAP } = require('../backend/long_text'); +const { template, i18n, userLink, renderStateChip, renderOpenClosedChip, renderContentActions, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const { renderStyledText } = require("../backend/renderStyledText"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); + +const userId = config.keys.id; +const BASE_FILTERS = ["ALL", "RECENT", "MINE", "SUBSCRIBED"]; +const TYPE_FILTERS = ["OPEN", "CLOSED"]; +const STATUS_FILTERS = ["ACTIVE", "ARCHIVED"]; +const HISTORY_MODES = ["DATE", "THREADS", "INHABITANTS"]; + +const cap = (s) => s.charAt(0) + s.slice(1).toLowerCase(); +const baseLabel = (f) => String(i18n[`mailingFilter${cap(f)}`] || f).toUpperCase(); +const typeLabel = (t) => String(i18n[`mailingType${cap(t)}`] || t).toUpperCase(); +const statusLabel = (s) => String(i18n[`mailingStatus${cap(s)}`] || s).toUpperCase(); +const historyLabel = (m) => String(i18n[`mailingHistory${cap(m)}`] || m).toUpperCase(); + +const listHref = (list) => `/mailing/${encodeURIComponent(list.id)}`; +const fmt = (d) => moment(d).format("YYYY/MM/DD HH:mm"); + +const statusChip = (list) => renderStateChip(list.status === "ACTIVE" ? "mutuals" : "closed", list.status === "ACTIVE" ? "✓" : "✗", statusLabel(list.status)); +const countsLine = (list) => span({ class: "card-label activity-update-counts mailing-counts" }, `👥: ${list.participantCount || 0} · 💬 ${list.messageCount || 0}`); + +const subscribeForm = (list, returnTo) => { + if (list.isOwner) return null; + if (list.closed) { + return list.isMember + ? form({ method: "POST", action: `/mailing/leave/${encodeURIComponent(list.id)}` }, button({ type: "submit", class: "tribe-action-btn danger-btn" }, String(i18n.subscriptionUnsubscribe).toUpperCase())) + : null; + } + return form({ method: "POST", action: "/subscriptions/toggle" }, + input({ type: "hidden", name: "target", value: list.id }), + input({ type: "hidden", name: "scope", value: "mailing" }), + input({ type: "hidden", name: "on", value: list.isMember ? "0" : "1" }), + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ type: "submit", class: list.isMember ? "tribe-action-btn danger-btn" : "tribe-action-btn" }, String(list.isMember ? i18n.subscriptionUnsubscribe : i18n.subscriptionSubscribe).toUpperCase()) + ); +}; + +const writeForm = (list) => (list.status === "ACTIVE" && (!list.closed || list.isMember)) + ? form({ method: "GET", action: `${listHref(list)}#compose` }, input({ type: "hidden", name: "write", value: "1" }), button({ type: "submit", class: "tribe-action-btn" }, String(i18n.mailingWriteButton).toUpperCase())) + : null; + +const renderTags = (tags) => (Array.isArray(tags) && tags.length) + ? div({ class: "card-tags" }, ...tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) + : null; + +const mailingChipFor = (mode, census) => { + if (mode === "ALL") return true; + if (!Array.isArray(census)) return true; + if (mode === "MINE") return census.some(l => String(l.author) === String(userId)); + if (mode === "SUBSCRIBED") return census.some(l => l.isMember && String(l.author) !== String(userId)); + if (mode === "RECENT") return census.length > 0; + if (mode === "OPEN") return census.some(l => !l.closed); + if (mode === "CLOSED") return census.some(l => l.closed); + if (STATUS_FILTERS.includes(mode)) return census.some(l => l.status === mode); + return census.length > 0; +}; +exports.mailingChipFor = mailingChipFor; + +const renderArchiveItem = (list, params = {}) => + li({ class: "mailing-archive-item" }, + div({ class: "emergency-update-head mailing-archive-head" }, + div({ class: "mailing-archive-meta" }, + a({ href: listHref(list), class: "user-link" }, list.title || "—"), + countsLine(list), + renderOpenClosedChip(list.listType, { statusChipOPEN: typeLabel("OPEN"), statusChipCLOSED: typeLabel("CLOSED") }), + statusChip(list), + list.isMember && !list.isOwner ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) : null + ), + renderContentActions(list.id, listHref(list), { author: list.author, favKind: "mailing", isFavorite: list.isFavorite, reportTitle: list.title, spread: (params.spreadMap && params.spreadMap.get(list.id)) || null, deleteAction: String(list.author) === String(userId) ? contentDeleteAction("mailingList", list.id) : null }) + ) + ); + +const chipButtons = (modes, filter, census, labelOf) => + modes.filter(m => m === filter || mailingChipFor(m, census)).map(m => button({ type: "submit", name: "filter", value: m, class: filter === m ? "filter-btn active" : "filter-btn" }, labelOf(m))); + +const renderFilters = (filter, q, census, emptyMod) => + section( + div({ class: "filters" }, + form({ method: "GET", action: "/mailing", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q || "" }), + ...(emptyMod ? [] : [ + ...chipButtons(BASE_FILTERS, filter, census, baseLabel), + ...chipButtons(TYPE_FILTERS, filter, census, typeLabel), + ...chipButtons(STATUS_FILTERS, filter, census, statusLabel) + ]), + button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.mailingCreate) + ) + ) + ); + +const renderForm = (list) => + div({ class: "report-form" }, + h2(list ? i18n.mailingUpdateSectionTitle : i18n.mailingCreateSectionTitle), + form({ method: "POST", action: list ? `/mailing/update/${encodeURIComponent(list.id)}` : "/mailing/create" }, + label(i18n.mailingTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.mailingTitlePlaceholder, value: list ? list.title : "" }), br(), + label(i18n.mailingDescriptionLabel), br(), + textarea({ name: "description", rows: 4, maxlength: "1000", placeholder: i18n.mailingDescriptionPlaceholder }, list ? list.description : ""), br(), + list + ? [ + label(i18n.mailingStatusLabel), br(), + select({ name: "status" }, ...STATUS_FILTERS.map(st => option({ value: st, ...(st === list.status ? { selected: true } : {}) }, statusLabel(st)))), br(), br() + ] + : [ + label(i18n.mailingTypeLabel), br(), + select({ name: "listType" }, ...TYPE_FILTERS.map(t => option({ value: t }, typeLabel(t)))), br(), br() + ], + (!list || list.closed) + ? [ + label(i18n.mailingMembersLabel), br(), + textarea({ name: "members", rows: 3, maxlength: "4000", placeholder: i18n.mailingMembersPlaceholder }, list ? list.members.filter(m => m !== list.author).join(", ") : ""), br() + ] + : null, + label(i18n.mailingTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.mailingTagsPlaceholder, value: list ? list.tags.join(", ") : "" }), br(), br(), + button({ type: "submit", class: "create-button" }, list ? i18n.mailingUpdate : i18n.mailingCreate) + ) + ); + +const renderHeader = () => div({ class: "tags-header module-header-line" }, h2(i18n.mailingTitle), p(i18n.mailingDescription)); + +exports.mailingView = async (lists, filter = "ALL", params = {}) => { + const list = Array.isArray(lists) ? lists : []; + const q = String(params.q || "").trim(); + const census = Array.isArray(params.censusList) ? params.censusList : list; + const f = String(filter || "ALL").toUpperCase(); + const isForm = f === "CREATE" || f === "EDIT"; + const emptyMod = !isForm && moduleIsEmpty(census, "ALL", "ALL", q); + return template( + i18n.mailingTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.mailingTitle), p(i18n.mailingDescription)), + renderFilters(isForm ? "ALL" : f, q, isForm ? [] : census, emptyMod) + ), + section( + isForm + ? renderForm(f === "EDIT" ? params.list : null) + : [ + emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length, [ + { label: typeLabel("OPEN"), count: list.filter(l => !l.closed).length }, + { label: typeLabel("CLOSED"), count: list.filter(l => l.closed).length }, + { label: statusLabel("ARCHIVED"), count: list.filter(l => l.status === "ARCHIVED").length } + ]), + form({ method: "GET", action: "/mailing", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: f }), + input({ type: "text", name: "q", value: q, placeholder: i18n.mailingSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ), + list.length ? ul({ class: "mailing-archive" }, ...paged(list.slice().sort((x, y) => (y.lastActivityTs || 0) - (x.lastActivityTs || 0))).map(l => renderArchiveItem(l, params))) : div({ class: "tribe-grid" }, p(i18n.mailingNoItems)) + ] + ) + ); +}; + +const renderMessage = (list, m, opts = {}) => + div({ class: "mailing-message" }, + div({ class: "emergency-update-head mailing-message-head" }, + span({ class: "mailing-message-subject" }, m.subject || i18n.pmNoSubject), + list.canWrite && !opts.noReply + ? div({ class: "tribe-side-actions emergency-update-actions" }, + form({ method: "GET", action: `${listHref(list)}#compose`, class: "mailing-reply-form" }, + input({ type: "hidden", name: "write", value: "1" }), + input({ type: "hidden", name: "reply", value: m.mid }), + input({ type: "hidden", name: "history", value: opts.mode || "DATE" }), + button({ type: "submit", class: "tribe-action-btn" }, String(i18n.pmReply || "Reply").toUpperCase()) + ) + ) + : null + ), + div({ class: "mailing-message-text" }, ...renderStyledText(m.text || "")), + p({ class: "card-footer" }, span({ class: "date-link" }, `${fmt(m.sentAt)}`), userLink(m.author)) + ); + +const renderHistory = (list, history, mode) => { + if (!history.length) return p(i18n.mailingNoMessages); + if (mode === "THREADS") { + const groups = new Map(); + for (const m of history) { + if (!groups.has(m.thread)) groups.set(m.thread, []); + groups.get(m.thread).push(m); + } + const ordered = Array.from(groups.values()).sort((x, y) => y[y.length - 1].ts - x[x.length - 1].ts); + return div({ class: "mailing-threads" }, ...ordered.map(msgs => { + const root = msgs[0]; + const replies = msgs.slice(1); + return div({ class: "mailing-thread" }, + renderMessage(list, root, { mode }), + replies.length + ? details({ class: "mailing-thread-details" }, + summary(`${replies.length} ${replies.length === 1 ? (i18n.pmReply || "reply") : (i18n.pmReplies || "replies")}`), + ...replies.map(r => renderMessage(list, r, { mode })) + ) + : null + ); + })); + } + if (mode === "INHABITANTS") { + const groups = new Map(); + for (const m of history) { + if (!groups.has(m.author)) groups.set(m.author, []); + groups.get(m.author).push(m); + } + const ordered = Array.from(groups.entries()).sort((x, y) => y[1].length - x[1].length); + return div({ class: "mailing-by-author" }, ...ordered.map(([author, msgs]) => + div({ class: "mailing-author-group" }, + h3({ class: "mailing-author-title" }, userLink(author), span({ class: "mailing-author-count" }, ` · ${msgs.length}`)), + ...msgs.slice().sort((x, y) => y.ts - x.ts).map(m => renderMessage(list, m, { mode })) + ) + )); + } + return div({ class: "mailing-by-date" }, ...history.slice().sort((x, y) => y.ts - x.ts).map(m => renderMessage(list, m, { mode }))); +}; + +const renderCompose = (list, params) => { + const parent = params.replyTo ? (list.history || []).find(m => m.mid === params.replyTo || m.key === params.replyTo) : null; + const draft = params.draft || null; + const subject = draft ? String(draft.subject || "") : parent ? (/^\s*RE:/i.test(parent.subject) ? parent.subject : `RE: ${parent.subject}`) : ""; + const thread = parent ? parent.thread : draft ? String(draft.thread || "") : ""; + return div({ class: "div-center audio-form mailing-compose", id: "compose" }, + h2(parent ? i18n.mailingReplyTitle : i18n.mailingWriteTitle), + form({ method: "POST", action: `/mailing/${encodeURIComponent(list.id)}/message` }, + thread ? input({ type: "hidden", name: "thread", value: thread }) : null, + params.returnTo ? input({ type: "hidden", name: "returnTo", value: params.returnTo }) : null, + label(i18n.pmSubject), br(), + input({ type: "text", name: "subject", maxlength: "150", placeholder: i18n.pmSubjectHint, value: subject }), br(), br(), + label(i18n.pmText), br(), + textarea({ name: "text", rows: 8, maxlength: String(TEXT_CAP), required: true, autofocus: true, placeholder: parent ? parent.text.split("\n").map(l => `> ${l}`).join("\n") : i18n.mailingTextPlaceholder }, draft ? String(draft.text || "") : ""), br(), br(), + button({ type: "submit", class: "create-button" }, i18n.mailingSend) + ) + ); +}; + +exports.singleMailingView = async (list, params = {}) => { + const isAuthor = String(list.author) === String(userId); + const href = listHref(list); + const census = Array.isArray(params.censusList) ? params.censusList : []; + const history = Array.isArray(list.history) ? list.history : []; + const mode = HISTORY_MODES.includes(String(params.history || "").toUpperCase()) ? String(params.history).toUpperCase() : "THREADS"; + const q = String(params.q || "").trim(); + const shown = q ? history.filter(m => [m.subject, m.text, m.author].some(v => String(v || "").toLowerCase().includes(q.toLowerCase()))) : history; + const historyChips = history.length + ? div({ class: "activity-sub-filter" }, + ...HISTORY_MODES.map(m => form({ class: "sub-filter-form", method: "GET", action: `${href}#history` }, + input({ type: "hidden", name: "history", value: m }), + input({ type: "hidden", name: "q", value: q }), + button({ type: "submit", class: mode === m ? "filter-btn active" : "filter-btn" }, historyLabel(m)) + )) + ) + : null; + const sideActions = [subscribeForm(list, href)].filter(Boolean); + const ownerActions = isAuthor + ? [ + form({ method: "GET", action: "/mailing" }, input({ type: "hidden", name: "filter", value: "edit" }), input({ type: "hidden", name: "id", value: list.id }), button({ type: "submit", class: "update-btn" }, i18n.mailingUpdate)) + ] + : []; + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(list.id, href, { author: list.author, favKind: "mailing", isFavorite: list.isFavorite, reportTitle: list.title, spread: params.spread || null, deleteAction: isAuthor ? contentDeleteAction("mailingList", list.id) : null }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, list.title)), + div({ class: "card-chips-row" }, renderOpenClosedChip(list.listType, { statusChipOPEN: typeLabel("OPEN"), statusChipCLOSED: typeLabel("CLOSED") }), statusChip(list)), + list.description ? p({ class: "tribe-side-description" }, ...renderStyledText(list.description)) : null, + table({ class: "tribe-info-table jobs-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), + td({ class: "tribe-info-value", colspan: "3" }, fmt(list.createdAt)) + ), + tr( + td({ class: "tribe-info-value", colspan: "4" }, userLink(list.author)) + ) + ), + div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.mailingSubscribers}: ${list.participantCount || 0}`) + ), + sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, + isAuthor + ? div({ class: "tribe-side-actions housing-status-row" }, + span({ class: "card-label" }, `${i18n.statusLabel || "Status"}: `), + statusChip(list), + form({ method: "POST", action: `/mailing/status/${encodeURIComponent(list.id)}`, class: "inline-form" }, + button({ class: "tribe-action-btn", type: "submit", name: "status", value: list.status === "ACTIVE" ? "ARCHIVED" : "ACTIVE" }, + String(list.status === "ACTIVE" ? i18n.mailingArchive : i18n.mailingActivate).toUpperCase()) + ) + ) + : null, + div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.mailingMessagesLabel}: ${list.messageCount || 0}`) + ), + writeForm(list) ? div({ class: "tribe-side-actions" }, writeForm(list)) : null, + div({ class: "doc-export-actions" }, + form({ method: "GET", action: `${href}/pdf` }, button({ type: "submit", class: "filter-btn" }, i18n.mailingGenerateReport)), + form({ method: "POST", action: `${href}/share` }, button({ type: "submit", class: "filter-btn" }, i18n.sharePm)) + ), + renderTags(list.tags), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null + ); + const main = div({ class: "tribe-main" }, + params.write && list.status === "ACTIVE" && (!list.closed || list.isMember) ? renderCompose(list, params) : null, + div({ class: "card-section mailing-history", id: "history" }, + historyChips, + history.length + ? form({ method: "GET", action: `${href}#history`, class: "filter-box" }, + input({ type: "hidden", name: "history", value: mode }), + input({ type: "text", name: "q", value: q, placeholder: i18n.mailingHistorySearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + : null, + renderHistory(list, shown, mode) + ) + ); + return template( + list.title, + section(renderHeader(), renderFilters("ALL", "", census, census.length === 0)), + section(div({ class: "tribe-details" }, side, main)) + ); +}; diff --git a/src/views/main_views.js b/src/views/main_views.js index 2920101..b9abd53 100644 --- a/src/views/main_views.js +++ b/src/views/main_views.js @@ -1,15 +1,16 @@ "use strict"; const path = require("path"); +const RAW_HTML = Symbol.for('oasis.rawHtml'); const fs = require("fs"); const envPaths = require("../server/node_modules/env-paths"); const debug = require("../server/node_modules/debug")("oasis"); -const highlightJs = require("../server/node_modules/highlight.js"); +const highlightJs = require("../server/node_modules/highlight.js/lib/core"); +highlightJs.registerLanguage("json", require("../server/node_modules/highlight.js/lib/languages/json")); const prettyMs = require("../server/node_modules/pretty-ms"); const moment = require('../server/node_modules/moment'); -const QRCode = require('../server/node_modules/qrcode'); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledText, renderStyledHtml, plainText, safeExternalHref } = require('../backend/renderStyledText'); const ssbClientGUI = require("../client/gui"); const config = require("../server/ssb_config"); const cooler = ssbClientGUI({ offline: config.offline }); @@ -18,15 +19,17 @@ const sharedState = require('../configs/shared-state'); let ssb, userId; const getUserId = async () => { + if (userId) return userId; + if (config && config.keys && config.keys.id) { userId = config.keys.id; return userId; } if (!ssb) ssb = await cooler.open(); - if (!userId) userId = ssb.id; + userId = ssb.id; return userId; }; +exports.getUserId = getUserId; const { a, article, br, body, button, details, div, em, footer, form, h1, h2, h3, head, header, hr, html, img, input, label, li, link, main, meta, nav, option, p, pre, section, select, span, summary, table, td, textarea, title, tr, ul, strong, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe"); const lodash = require("../server/node_modules/lodash"); -const markdown = require("./markdown"); const { sanitizeHtml } = require('../backend/sanitizeHtml'); const nameCache = require('../backend/nameCache'); @@ -46,18 +49,128 @@ const userLink = (feedId, knownName) => { }; exports.userLink = userLink; + +const renderCreatedRows = (createdAt, author, updatedAt) => { + const createdTs = createdAt ? new Date(createdAt).getTime() : NaN; + const updatedTs = updatedAt ? new Date(updatedAt).getTime() : NaN; + const showUpdated = Number.isFinite(updatedTs) && (!Number.isFinite(createdTs) || updatedTs !== createdTs); + return [ + createdAt ? tr(td({ class: 'tribe-info-label' }, i18n.createdAtLabel), td({ class: 'tribe-info-value', colspan: '3' }, moment(createdAt).format('YYYY/MM/DD HH:mm'))) : null, + showUpdated ? tr(td({ class: 'tribe-info-label' }, i18n.jobsUpdatedAt), td({ class: 'tribe-info-value', colspan: '3' }, moment(updatedAt).format('YYYY/MM/DD HH:mm'))) : null, + author ? tr(td({ class: 'tribe-info-value', colspan: '4' }, userLink(author))) : null + ]; +}; +exports.renderCreatedRows = renderCreatedRows; + +const inboxBadges = () => { + const inboxCount = Number(sharedState.getInboxCount()) || 0; + return [inboxCount > 0 ? span({ class: 'inbox-badge' }, String(inboxCount)) : '']; +}; +const phoneOn = () => !config.public && getConfig().modules.phoneMod !== 'off'; +const PHONE_PAM_MAX_MS = require('../server/phone_module').VOICEMAIL_MAX_MS; +const renderCallButton = (feedId, { upper = false, cls = null, pam = false } = {}) => { + if (!phoneOn() || !feedId || String(feedId) === String(config.keys.id) || sharedState.isPubId(feedId)) return null; + if (pam) return a({ href: `/phone?filter=create&to=${encodeURIComponent(feedId)}`, class: cls || "filter-btn" }, `✆ ${i18n.phoneCallTooltip}`); + const label = upper ? String(i18n.phoneCallButton).toUpperCase() : i18n.phoneCallButton; + return form({ method: "POST", action: "/phone/call", class: "phone-action-form" }, + input({ type: "hidden", name: "to", value: feedId }), + button({ type: "submit", class: cls || "filter-btn", title: i18n.phoneCallButton }, `✆ ${label}`) + ); +}; +exports.renderCallButton = renderCallButton; +const pad2 = (n) => String(n).padStart(2, '0'); +const liveClock = (fromMs, { max = 0 } = {}) => { + const from = Number(fromMs) || Date.now(); + const e = Math.max(0, Math.floor((Date.now() - from) / 1000)); + const roll = (cls, labels) => span({ class: 'live-clock-win' }, span({ class: `live-clock-roll ${cls}` }, ...labels.map(l => span(l)))); + if (max > 0) { + const cap = Math.floor(max / 1000); + return span({ class: 'live-clock' }, roll('live-clock-once', Array.from({ length: 60 }, (_, i) => { const v = Math.min(cap, e + i); return `${pad2(Math.floor(v / 60))}:${pad2(v % 60)}`; }))); + } + const m = Math.floor(e / 60), sec = e % 60; + return span({ class: 'live-clock' }, + roll(`live-clock-min lc-d${sec}`, Array.from({ length: 60 }, (_, i) => `${pad2(m + i)}:`)), + roll('live-clock-sec', Array.from({ length: 60 }, (_, i) => pad2((sec + i) % 60))) + ); +}; +exports.liveClock = liveClock; +const renderPhoneTopLink = () => { + if (!phoneOn()) return null; + const n = Number(sharedState.getPhoneCount()) || 0; + return li(a({ href: "/phone" }, span({ class: "emoji" }, "✆"), nbsp, i18n.phoneTitle, n > 0 ? span({ class: 'inbox-badge' }, String(n)) : '')); +}; exports.userLinkLabel = userLinkLabel; const renderInviteQrCard = ({ qrDataUrl }) => qrDataUrl ? div({ class: 'invite-qr-card' }, img({ src: qrDataUrl, alt: 'QR', class: 'invite-qr-img' })) : null; exports.renderInviteQrCard = renderInviteQrCard; +const renderLiveFeed = (src, imgClass) => { + const id = `live-${String(src).replace(/[^a-zA-Z0-9]/g, '').slice(-24)}`; + return [ + a({ href: `#${id}`, id: `${id}-src`, class: 'zoom-link zoom-inline' }, img({ src, alt: '', class: imgClass })), + div({ id, class: 'lightbox live-lightbox' }, + a({ href: `#${id}-src`, class: 'lightbox-close' }, '\u00d7'), + a({ href: `#${id}-src`, class: 'lightbox-image-link' }, img({ src, alt: '', class: 'lightbox-image', loading: 'lazy' })) + ) + ]; +}; +exports.renderLiveFeed = renderLiveFeed; + +const renderVideoGrid = (peers, me) => { + const tiles = [ + me ? div({ class: 'video-tile video-tile-me' }, ...renderLiveFeed('/phone/video/me', 'video-feed'), span({ class: 'video-tile-name' }, i18n.roomYou)) : null, + ...(Array.isArray(peers) ? peers : []).filter(p => p && p.id && p.on).map(p => div({ class: 'video-tile' }, ...renderLiveFeed(`/phone/video/${encodeURIComponent(p.id)}`, 'video-feed'), userLink(p.id))) + ].filter(Boolean); + return tiles.length ? div({ class: 'video-grid' }, ...tiles) : null; +}; +exports.renderVideoGrid = renderVideoGrid; + +const renderSubscriptionBox = ({ target, scope, subscribed, count, isOwner, returnTo, canWrite, inline, compact }) => { + if (!target) return null; + if (isOwner && (Number(count) || 0) <= 1) return null; + const showPm = canWrite !== undefined ? canWrite : (isOwner || subscribed); + const total = Number(count) || 0; + const countSuffix = total ? ` (${total})` : ''; + const parts = [ + total && !compact ? span({ class: 'card-label' }, `${i18n.subscriptionTitle} (${total})${isOwner ? '' : ':'}`) : null, + compact && isOwner && total ? renderStateChip('mutuals', '🔔', `${i18n.subscriptionTitle}${countSuffix}`) : null, + span({ class: 'subscription-actions' }, + isOwner + ? null + : form({ method: 'POST', action: '/subscriptions/toggle', class: 'subscription-form' }, + input({ type: 'hidden', name: 'target', value: target }), + input({ type: 'hidden', name: 'scope', value: String(scope || '') }), + input({ type: 'hidden', name: 'on', value: subscribed ? '0' : '1' }), + returnTo ? input({ type: 'hidden', name: 'returnTo', value: returnTo }) : null, + compact + ? button({ type: 'submit', class: subscribed ? 'delete-btn' : 'update-btn' }, `${subscribed ? i18n.subscriptionUnsubscribe : i18n.subscriptionSubscribe}${countSuffix}`) + : button({ type: 'submit', class: subscribed ? 'tribe-action-btn danger-btn' : 'tribe-action-btn' }, `${subscribed ? '🔕' : '🔔'} ${String(subscribed ? i18n.subscriptionUnsubscribe : i18n.subscriptionSubscribe).toUpperCase()}`) + ), + showPm && total + ? a({ href: `/pm?list=${encodeURIComponent(target)}`, class: 'btn-singleview btn-pm', title: i18n.pmCreateButton || 'Write a PM' }, '✉') + : null + ) + ]; + const nodes = parts.filter(Boolean); + return inline ? nodes : div({ class: 'tribe-side-actions shop-visibility-row stacked-action-row subscription-box-block' }, ...nodes); +}; +exports.renderSubscriptionBox = renderSubscriptionBox; + const renderStateChip = (variant, icon, text) => span({ class: `pm-exposition-chip pm-exposition-${variant}` }, icon ? span({ class: "pm-exposition-icon" }, icon) : null, span({ class: "pm-exposition-text" }, String(text || "")) ); +const renderPhoneCallForm = (to, inner, title) => !phoneOn() || !to + ? inner + : form({ method: 'POST', action: '/phone/call', class: 'phone-chip-form' }, + input({ type: 'hidden', name: 'to', value: String(to) }), + button({ type: 'submit', class: 'phone-chip-btn', title: title || i18n.phoneCallButton }, inner)); +const renderPhoneChip = (number, title) => number ? renderPhoneCallForm(number, renderStateChip('whole', '✆', number), title) : null; +exports.renderPhoneChip = renderPhoneChip; + const renderOpenClosedChip = (status, i18nObj) => { const s = String(status || "").toUpperCase(); const isOpen = s === "OPEN"; @@ -87,9 +200,10 @@ const renderModeChip = (mode, i18nObj) => { const renderLifespanChip = (lifetime, i18nObj) => { const lt = lifetime || null; if (!lt || !lt.bucket) return null; - const range = lt.range || ""; + const range = lt.contentRange || lt.range || ""; + const bucket = lt.contentBucket || lt.bucket; if (range) { - return span({ class: `pm-exposition-chip pm-exposition-lifespan-${lt.bucket}` }, + return span({ class: `pm-exposition-chip pm-exposition-lifespan-${bucket}` }, span({ class: "pm-exposition-text" }, range) ); } @@ -108,10 +222,86 @@ exports.DATE_FORMAT = DATE_FORMAT; exports.fmtDateTime = fmtDateTime; exports.fmtDay = fmtDay; +const clearnetShortId = (id) => String(id || '').replace(/^[%&@]/, '').replace(/[^A-Za-z0-9]/g, '').slice(0, 8).toLowerCase(); +const clearnetSlugFor = (title, id) => { + const base = String(title || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60).replace(/-+$/, ''); + const short = clearnetShortId(id); + return base ? `${base}-${short}` : short; +}; +exports.clearnetShortId = clearnetShortId; +exports.clearnetSlugFor = clearnetSlugFor; +const clearnetItemHref = (modulePath, title, id) => `/c/${modulePath}/${encodeURIComponent(clearnetSlugFor(title, id))}`; +exports.clearnetItemHref = clearnetItemHref; +const CLEARNET_PATHS = { blogs: 'blog', wiki: 'wiki', market: 'market', audios: 'audios', videos: 'videos', images: 'images', documents: 'documents', bookmarks: 'bookmarks', events: 'events', feed: 'feed', jobs: 'jobs', podcasts: 'podcasts', projects: 'projects', torrents: 'torrents', files: 'files', shops: 'shops', emergencies: 'emergencies', campaigns: 'campaigns', housing: 'housing' }; +let clearnetBaseCache = null; +const clearnetBase = () => { + if (clearnetBaseCache !== null) return clearnetBaseCache; + clearnetBaseCache = String((require('../configs/config-manager.js').defaultInvite() || {}).url || '').replace(/\/+$/, ''); + return clearnetBaseCache; +}; +const clearnetHrefFor = (viewHref, blockId, title) => { + const m = String(viewHref || '').match(/^\/(school\/course|[a-z]+)\/([^/?#]+)/); + if (!m) return null; + const seg = m[1] === 'school/course' ? 'school' : CLEARNET_PATHS[m[1]]; + if (!seg) return null; + const base = clearnetBase(); + const id = blockId ? encodeURIComponent(clearnetSlugFor(title, blockId)) : m[2]; + return base ? `${base}/c/${seg}/${id}` : null; +}; +let linkBoxSeq = 0; +const renderLinkBox = (blockId, viewHref, title) => { + linkBoxSeq = (linkBoxSeq + 1) % 1000000; + const id = `linkbox-${linkBoxSeq}-${String(blockId).replace(/[^a-zA-Z0-9]/g, '').slice(-10)}`; + const clearnetHref = clearnetHrefFor(viewHref, blockId, title); + return [ + a({ href: `#${id}`, id: `${id}-src`, class: 'btn-singleview btn-link', title: i18n.copyLinkTitle }, '⚯'), + span({ id, class: 'lightbox link-lightbox' }, + a({ href: `#${id}-src`, class: 'lightbox-close' }, '\u00d7'), + span({ class: 'link-box' }, + span({ class: 'link-box-row' }, span({ class: 'link-box-label' }, i18n.linkBoxOasis), input({ type: 'text', readonly: true, class: 'link-box-input', value: viewHref })), + clearnetHref ? span({ class: 'link-box-row' }, span({ class: 'link-box-label' }, i18n.linkBoxClearnet), input({ type: 'text', readonly: true, class: 'link-box-input', value: clearnetHref })) : null + ) + ) + ]; +}; + +const renderWalletChip = () => { + try { if (getConfig().modules.walletMod !== 'on') return null; } catch (_) { return null; } + const ready = sharedState.getWalletReady ? sharedState.getWalletReady() : false; + const chip = renderStateChip(ready ? 'mutuals' : 'closed', '❄', ready ? i18n.bankWalletReady : i18n.bankWalletNotConfigured); + return ready ? chip : a({ href: '/wallet', class: 'wallet-chip-link' }, chip); +}; +exports.renderWalletChip = renderWalletChip; + +const torrentFileName = (title) => `${String(title || 'download').replace(/\.torrent$/i, '')}.torrent`; +const torrentDownloadHref = (blobId, title) => `/blob/${encodeURIComponent(blobId)}?name=${encodeURIComponent(torrentFileName(title))}`; +const renderTorrentDownload = (href, opts = {}) => + a({ href, class: opts.class || 'filter-btn' }, `\u2B07 ${i18n.torrentExternalButton || 'EXTERNAL'}`); +exports.torrentDownloadHref = torrentDownloadHref; +exports.renderTorrentDownload = renderTorrentDownload; +const renderTorrentSourceDownload = (torrentKey, source, opts = {}) => { + if (typeof source !== 'string' || !source.startsWith('&') || !torrentKey) return null; + return a({ href: `/torrents/${encodeURIComponent(torrentKey)}/get`, class: opts.class || 'filter-btn' }, `\u2B07 ${i18n.torrentOasisButton || 'OASIS'}`); +}; +exports.renderTorrentSourceDownload = renderTorrentSourceDownload; +const renderFileDownloads = (fileKey, torrentUrl, name, opts = {}) => { + if (!fileKey) return null; + const btnClass = opts.class || 'filter-btn'; + const torrentName = `${String(name || 'download').replace(/\.torrent$/i, '')}.torrent`; + return div({ class: 'torrent-card-actions' }, + a({ href: `/files/${encodeURIComponent(fileKey)}/get`, class: btnClass }, `\u2B07 ${i18n.torrentOasisButton || 'OASIS'}`), + typeof torrentUrl === 'string' && torrentUrl.startsWith('&') + ? a({ href: `/blob/${encodeURIComponent(torrentUrl)}?name=${encodeURIComponent(torrentName)}`, class: btnClass }, `\u2B07 ${i18n.torrentExternalButton || 'EXTERNAL'}`) + : null + ); +}; +exports.renderFileDownloads = renderFileDownloads; + const renderContentActions = (msgId, viewHref, opts = {}) => { const o = (opts && typeof opts === 'object') ? opts : {}; const blockId = (typeof msgId === 'string' && msgId.startsWith('%')) ? msgId : null; const myId = (config.keys && config.keys.id) ? config.keys.id : ''; + const linkBtn = blockId && viewHref ? renderLinkBox(blockId, viewHref, o.reportTitle) : null; const pinBtn = o.favKind && blockId ? form({ method: 'POST', action: `/${o.favKind}/favorites/${o.isFavorite ? 'remove' : 'add'}/${encodeURIComponent(blockId)}`, class: 'content-action-form' }, @@ -120,31 +310,94 @@ const renderContentActions = (msgId, viewHref, opts = {}) => { ) : null; - const spreadBtn = o.spread !== undefined && blockId ? renderSpreadButton(blockId, o.spread) : null; + const spreadBtn = o.spread !== undefined && blockId && !(o.author && String(o.author) === String(myId)) ? renderSpreadButton(blockId, o.spread, o.spreadTitle || null) : null; - const chainBtn = blockId + const torrentBtn = (() => { + if (process.env.OASIS_MOBILE === '1') return null; + const src = o.torrentFrom && typeof o.torrentFrom === 'object' ? o.torrentFrom : null; + if (!src || typeof src.blobId !== 'string' || !src.blobId.startsWith('&')) return null; + try { if (getConfig().modules.torrentsMod !== 'on') return null; } catch (_) { return null; } + const name = String(src.name || o.reportTitle || '').slice(0, 100); + return a({ href: `/torrents?filter=create&fromBlob=${encodeURIComponent(src.blobId)}&name=${encodeURIComponent(name)}`, class: 'btn-singleview btn-torrent', title: i18n.torrentFromContent }, '\u21C5'); + })(); + + const chainBtn = blockId && process.env.OASIS_MOBILE !== '1' ? a({ href: `/blockexplorer/block/${encodeURIComponent(blockId)}`, class: 'btn-singleview', title: i18n.blockchainViewBlockexplorer }, '⦿') : null; - const contentBtn = viewHref + const herePath = (listScope() || {}).path; + const onItself = (() => { try { return !!herePath && decodeURIComponent(String(viewHref || '').split(/[?#]/)[0]) === decodeURIComponent(String(herePath)); } catch (_) { return false; } })(); + const contentBtn = viewHref && !onItself ? a({ href: viewHref, class: 'btn-singleview btn-content', title: i18n.visitContent }, '↗') : null; + const donateBtn = (() => { + try { if (getConfig().modules.walletMod !== 'on') return null; } catch (_) { return null; } + if (!o.author || String(o.author) === String(myId)) return null; + const donatable = sharedState.getDonatableAuthors ? sharedState.getDonatableAuthors() : null; + if (!donatable || !donatable.has(String(o.author))) return null; + const ref = viewHref ? `?returnTo=${encodeURIComponent(viewHref)}` : ''; + return a({ href: `/wallet/donate/${encodeURIComponent(o.author)}${ref}`, class: 'btn-singleview btn-donate', title: i18n.donateTooltip }, '\u2744'); + })(); + const reportHref = blockId ? `/reports?filter=create&category=CONTENT&title=${encodeURIComponent(String(o.reportTitle || blockId).slice(0, 120))}&description=${encodeURIComponent(`${viewHref || blockId}`)}` : null; - const reportBtn = o.report !== false && reportHref + const reportBtn = o.report !== false && reportHref && !(o.author && String(o.author) === String(myId)) ? a({ href: reportHref, class: 'btn-singleview btn-report', title: i18n.reportContent }, '⚑') : null; const pmBtn = o.author && String(o.author) !== String(myId) ? a({ href: `/pm?recipients=${encodeURIComponent(o.author)}`, class: 'btn-singleview btn-pm', title: i18n.pmContentTooltip }, '✉') : null; + const callBtn = o.author && String(o.author) !== String(myId) && phoneOn() && !sharedState.isPubId(o.author) + ? a({ href: `/phone?filter=create&to=${encodeURIComponent(o.author)}`, class: 'btn-singleview btn-call', title: i18n.phoneCallTooltip }, '✆') + : null; - if (!pinBtn && !spreadBtn && !chainBtn && !contentBtn && !reportBtn && !pmBtn) return null; - return div({ class: 'content-actions' }, spreadBtn, pinBtn, reportBtn, pmBtn, chainBtn, contentBtn); + const deleteBtn = o.deleteAction + ? form({ method: 'POST', action: o.deleteAction, class: 'content-action-form content-action-delete' }, + o.returnTo && o.returnTo !== viewHref ? input({ type: 'hidden', name: 'returnTo', value: o.returnTo }) : null, + button({ type: 'submit', class: 'btn-singleview btn-delete', title: i18n.chatDelete }, '✕') + ) + : null; + + if (!pinBtn && !spreadBtn && !chainBtn && !contentBtn && !reportBtn && !pmBtn && !callBtn && !deleteBtn && !donateBtn && !torrentBtn) return null; + return div({ class: 'content-actions' }, deleteBtn, spreadBtn, pinBtn, reportBtn, donateBtn, pmBtn, callBtn, torrentBtn, linkBtn, chainBtn, contentBtn); }; exports.renderContentActions = renderContentActions; +const CONTENT_FAV_KIND = { + image: 'images', audio: 'audios', video: 'videos', document: 'documents', + bookmark: 'bookmarks', torrent: 'torrents', file: 'files', event: 'events', task: 'tasks', + report: 'reports', votes: 'votes', poll: 'polls', market: 'market', + housing: 'housing', job: 'jobs', project: 'projects', shop: 'shops', shopProduct: 'shopProducts', + chat: 'chats', chatThread: 'chats', pad: 'pads', room: 'rooms', calendar: 'calendars', + map: 'maps', forum: 'forum', transfer: 'transfers', post: 'blogs', schoolCourse: 'school', + wikiPage: 'wiki', emergency: 'emergencies', mailingList: 'mailing', logisticsRoute: 'logistics', + podcast: 'podcasts', podcastEpisode: 'podcasts', campaign: 'campaigns' +}; +const CONTENT_SPREADABLE = new Set([ + 'post', 'audio', 'video', 'image', 'document', 'torrent', 'file', 'bookmark', + 'event', 'calendar', 'task', 'votes', 'vote', 'market', 'shop', 'shopProduct', + 'project', 'transfer', 'housing', 'job', 'report', 'industry', 'industryBuild', 'industryBlueprint', + 'chat', 'chatMessage', 'pad', 'padEntry', 'room', 'wikiPage', 'emergency', 'mailingList', 'logisticsRoute', 'podcast', 'podcastEpisode', 'campaign', 'forum', 'map', 'poll', 'blog', 'schoolCourse', 'feed' +]); +const CONTENT_TORRENTABLE = new Set(['audio', 'video', 'image', 'document', 'file']); +const CONTENT_DELETE_ROUTE = { + audio: 'audios', video: 'videos', image: 'images', document: 'documents', bookmark: 'bookmarks', + torrent: 'torrents', file: 'files', event: 'events', task: 'tasks', report: 'reports', votes: 'votes', + poll: 'polls', market: 'market', housing: 'housing', job: 'jobs', project: 'projects', shop: 'shops', + shopProduct: 'shops/product', chat: 'chats', pad: 'pads', room: 'rooms', calendar: 'calendars', map: 'maps', mapMarker: 'maps/marker', + forum: 'forum', transfer: 'transfers', emergency: 'emergencies', mailingList: 'mailing', + logisticsRoute: 'logistics', podcast: 'podcasts', podcastEpisode: 'podcasts/episode', campaign: 'campaigns', + feed: 'feed', schoolCourse: 'school', tribe: 'tribes', blog: 'blogs' +}; +const contentDeleteAction = (type, id) => CONTENT_DELETE_ROUTE[type] && id ? `/${CONTENT_DELETE_ROUTE[type]}/delete/${encodeURIComponent(id)}` : undefined; +exports.CONTENT_FAV_KIND = CONTENT_FAV_KIND; +exports.CONTENT_SPREADABLE = CONTENT_SPREADABLE; +exports.CONTENT_TORRENTABLE = CONTENT_TORRENTABLE; +exports.contentDeleteAction = contentDeleteAction; +exports.THEMES = [['Dark-SNH', 'Dark-SNH'], ['Clear-SNH', 'Clear-SNH'], ['Purple-SNH', 'Purple-SNH'], ['Matrix-SNH', 'Matrix-SNH'], ['OasisMobile', 'Oasis-Mobile']]; +exports.WIZARD_THEMES = exports.THEMES.filter(([value]) => value !== 'OasisMobile'); const renderDocumentActions = (kind, id, leadingNodes = []) => { const base = id ? `/${kind}/${encodeURIComponent(id)}` : `/${kind}`; @@ -164,9 +417,10 @@ const renderRelationshipBlock = (relationship, feedId) => { const rel = relationship || {}; if (rel.me) return span({ class: 'status you' }, i18n.relationshipYou); const actions = []; + const icons = { follow: '♥', unfollow: '✕', block: '⊘', unblock: '↺' }; const addAction = (action) => actions.push( form({ action: `/${action}/${encodeURIComponent(feedId)}`, method: 'post' }, - button({ type: 'submit', class: 'filter-btn' }, i18n[action]) + button({ type: 'submit', class: 'filter-btn' }, `${icons[action]} ${i18n[action]}`) ) ); if (rel.following) addAction('unfollow'); @@ -234,7 +488,7 @@ const renderEcoTax = (sizeBytes, blockId) => { ? ` · reducer ×${reducer.toFixed(2)} (${inhabitants} inhabitants)` : ''; const title = `${label} · ${formatCarbon(sizeBytes)}${reducerNote}`; - return a({ href, class: `eco-tax-chip eco-tax-chip-${band}`, title }, + return (process.env.OASIS_MOBILE === '1' ? span : a)({ ...(process.env.OASIS_MOBILE === '1' ? {} : { href }), class: `eco-tax-chip eco-tax-chip-${band}`, title }, span({ class: 'eco-tax-chip-label' }, label + ': '), span({ class: 'eco-tax-chip-value' }, formatCarbon(sizeBytes)) ); @@ -243,6 +497,101 @@ const renderEcoTax = (sizeBytes, blockId) => { exports.formatCarbon = formatCarbon; exports.renderEcoTax = renderEcoTax; +const renderEcoValueChip = () => { + const v = Number(sharedState.getEcoValue ? sharedState.getEcoValue() : NaN); + if (!Number.isFinite(v) || v <= 0) return null; + const title = (i18n && i18n.bankExchangeCurrentValue) ? i18n.bankExchangeCurrentValue : 'ECOin Value (1h)'; + return a({ href: '/banking?filter=exchange', class: 'eco-tax-chip eco-tax-chip-mid eco-value-chip', title }, + span({ class: 'eco-tax-chip-label' }, 'ECO/h: '), + span({ class: 'eco-tax-chip-value' }, v.toFixed(4)) + ); +}; +exports.renderEcoValueChip = renderEcoValueChip; + +const renderMobileCounters = () => { + if (process.env.OASIS_MOBILE === '1') return null; + try { + const theme = (getConfig().themes || {}).current; + if (theme !== 'OasisMobile' && process.env.OASIS_MOBILE !== '1') return null; + } catch (_) { return null; } + const inbox = Number(sharedState.getInboxCount()) || 0; + const mentions = Number(sharedState.getMentionsCount()) || 0; + const phone = phoneOn() ? (Number(sharedState.getPhoneCount()) || 0) : 0; + if (inbox <= 0 && mentions <= 0 && phone <= 0) return null; + return div({ class: 'mobile-counters' }, + inbox > 0 ? a({ href: '/inbox', class: 'mobile-counter', title: i18n.inbox }, '☂ ', String(inbox)) : null, + phone > 0 ? a({ href: '/phone', class: 'mobile-counter', title: i18n.phoneTitle }, '✆ ', String(phone)) : null, + mentions > 0 ? a({ href: '/mentions', class: 'mobile-counter', title: i18n.mentions }, '✺ ', String(mentions)) : null + ); +}; + +const LIST_PAGE_SIZE = 50; +const LIST_PAGE_SIZES = ['10', '50', '100', 'all']; +const listScope = () => { try { return require('../models/typed_log').requestScope.getStore() || null; } catch (_) { return null; } }; +const listPerPage = (query) => { + const v = String(new URLSearchParams(String(query || '')).get('perPage') || '').toLowerCase(); + return LIST_PAGE_SIZES.includes(v) ? v : String(LIST_PAGE_SIZE); +}; +const slicePage = (list, page, per) => { + const all = Array.isArray(list) ? list : []; + const size = per === 'all' ? Math.max(1, all.length) : Number(per) || LIST_PAGE_SIZE; + const pages = Math.max(1, Math.ceil(all.length / size)); + const current = Math.min(pages, Math.max(1, parseInt(page, 10) || 1)); + return { items: all.slice((current - 1) * size, current * size), page: current, pages, total: all.length, per: String(per) }; +}; +const showPageSizes = (total, query) => total > Number(LIST_PAGE_SIZES[0]) && (total > LIST_PAGE_SIZE || new URLSearchParams(String(query || '')).has('perPage')); +const pageSizeLabel = (s) => s === 'all' ? String(i18n.all || 'All').toUpperCase() : s; +exports.LIST_PAGE_SIZE = LIST_PAGE_SIZE; +exports.LIST_PAGE_SIZES = LIST_PAGE_SIZES; +exports.listPerPage = listPerPage; +exports.slicePage = slicePage; +exports.showPageSizes = showPageSizes; +exports.pageSizeLabel = pageSizeLabel; +const paged = (list) => { + const store = listScope(); + const query = store ? store.query : ''; + const { items, ...pager } = slicePage(list, new URLSearchParams(String(query || '')).get('page'), listPerPage(query)); + if (store) { store.pager = pager; store.pageItems = items; } + return items; +}; +exports.paged = paged; +exports.currentPerPage = () => { const store = listScope(); return listPerPage(store ? store.query : ''); }; +const renderListPager = () => { + const store = listScope(); + const pg = store && store.pager; + if (!pg) return null; + const sizes = showPageSizes(pg.total, store.query); + if (!(pg.pages > 1) && !sizes) return null; + const href = (n, per) => { + const q = new URLSearchParams(String(store.query || '')); + for (const k of ['page', 'error', 'errsig']) q.delete(k); + if (per) q.set('perPage', per); + if (n > 1) q.set('page', String(n)); + const s = q.toString(); + return s ? `${store.path}?${s}` : store.path; + }; + return nav({ class: 'oasis-pager' }, + sizes ? span({ class: 'oasis-pager-sizes' }, + span({ class: 'oasis-pager-info' }, i18n.searchPerPageLabel || 'Results per page'), + ...LIST_PAGE_SIZES.map(s => a({ href: href(1, s), class: s === pg.per ? 'oasis-pager-btn active' : 'oasis-pager-btn' }, pageSizeLabel(s))) + ) : null, + pg.pages > 1 ? span({ class: 'oasis-pager-info' }, String(i18n.cnPageOf || 'Page {page} of {pages}').replace('{page}', String(pg.page)).replace('{pages}', String(pg.pages))) : null, + pg.page > 1 ? a({ href: href(pg.page - 1), class: 'oasis-pager-btn' }, i18n.cnPrevPage || '← Previous') : null, + pg.page < pg.pages ? a({ href: href(pg.page + 1), class: 'oasis-pager-btn' }, i18n.cnNextPage || 'Next →') : null + ); +}; + +const renderLogWindowNote = () => { + let store = null; + try { store = require('../models/typed_log').requestScope.getStore(); } catch (_) { store = null; } + if (!store || !store.capped || !(store.limit > 0)) return null; + return div({ class: 'log-window-note' }, + a({ href: '/settings#logstream', class: 'eco-tax-chip eco-tax-chip-low log-window-chip', title: i18n.ssbLogStreamDescription }, + span({ class: 'eco-tax-chip-value' }, String(i18n.logWindowChip).replace('{n}', String(store.limit))) + ) + ); +}; + const errorView = ({ title, message, backHref }) => { const heading = title || i18n.errorPageTitle || 'Error'; return exports.template( @@ -259,7 +608,75 @@ const errorView = ({ title, message, backHref }) => { }; exports.errorView = errorView; -const renderSpreadButton = (msgKey, opts) => { +const keepTextView = ({ title, action, fields = [], hidden = [], enctype = null, backHref = null }) => { + const formAttrs = { method: 'POST', action }; + if (enctype) formAttrs.enctype = enctype; + return exports.template( + title || i18n.errorPageTitle || 'Error', + section( + div({ class: 'tags-header' }, + h2(title || ''), + form(formAttrs, + ...hidden.map(h => input({ type: 'hidden', name: h.name, value: String(h.value == null ? '' : h.value) })), + ...fields.map(f => { + const attrs = { name: f.name, id: f.name }; + if (f.maxlength) attrs.maxlength = String(f.maxlength); + if (f.required) attrs.required = true; + const value = String(f.value == null ? '' : f.value); + const control = f.type === 'textarea' + ? textarea({ ...attrs, rows: f.rows || 10 }, value) + : input({ ...attrs, type: 'text', value }); + return [label({ for: f.name }, f.label || f.name), br(), control, br(), br()]; + }).flat(), + button({ type: 'submit', class: 'create-button' }, i18n.keepTextRetry), + ' ', + backHref ? a({ href: backHref, class: 'filter-btn' }, i18n.goBack || 'Go back') : null + ) + ) + ) + ); +}; +exports.keepTextView = keepTextView; + +const confirmView = ({ title, message, action, hidden = [], backHref = null }) => exports.template( + title || i18n.confirmActionTitle, + section( + div({ class: 'tags-header' }, + h2(title || i18n.confirmActionTitle), + message ? p({ class: 'error-page-message' }, String(message)) : null, + form({ method: 'POST', action, class: 'confirm-actions' }, + ...hidden.map(h => input({ type: 'hidden', name: h.name, value: String(h.value == null ? '' : h.value) })), + button({ type: 'submit', class: 'create-button' }, i18n.confirmActionButton), + ' ', + a({ href: backHref || '/', class: 'filter-btn' }, i18n.goBack || 'Go back') + ) + ) + ) +); +exports.confirmView = confirmView; + +exports.renderInlineError = (message, dismissHref) => section({ class: 'inline-error' }, + div({ class: 'tags-header inline-error-box' }, + p({ class: 'error-page-message' }, String(message || '')), + dismissHref ? a({ href: dismissHref, class: 'inline-error-dismiss' }, i18n.errorDismiss || 'OK') : null + ) +).outerHTML; + +const renderVotesSummary = (opinions = {}) => { + const entries = Object.entries(opinions).filter(([, v]) => Number(v) > 0); + if (!entries.length) return null; + entries.sort((a, b) => Number(b[1]) - Number(a[1]) || String(a[0]).localeCompare(String(b[0]))); + return div( + { class: "votes feed-votes-summary" }, + entries.map(([category, count]) => { + const label = i18n['vote' + category.charAt(0).toUpperCase() + category.slice(1)] || category; + return span({ class: "vote-category" }, `${label} ${count}`); + }) + ); +}; +exports.renderVotesSummary = renderVotesSummary; + +const renderSpreadButton = (msgKey, opts, title = null, showCount = true) => { if (!msgKey || typeof msgKey !== 'string' || !msgKey.startsWith('%') || !/\.sha256$/.test(msgKey)) return null; const o = (opts && typeof opts === 'object') ? opts : {}; const voters = Array.isArray(o.voters) ? o.voters : []; @@ -268,8 +685,8 @@ const renderSpreadButton = (msgKey, opts) => { return form( { method: 'POST', action: `/spread/${encodeURIComponent(msgKey)}`, class: 'spread-form' }, button( - { type: 'submit', class: alreadySpread ? 'btn-singleview btn-spread-on' : 'btn-singleview', title: i18n.spreadContent }, - `⟳ ${count}` + { type: 'submit', class: alreadySpread ? 'btn-singleview btn-spread-on' : 'btn-singleview', title: title || i18n.spreadContent }, + showCount ? `⟳ ${count}` : '⟳' ) ); }; @@ -322,11 +739,15 @@ const i18nBase = require("../client/assets/translations/i18n"); let selectedLanguage = "en"; let i18n = {}; Object.assign(i18n, i18nBase[selectedLanguage]); +exports.getLanguage = () => (i18nBase[selectedLanguage] ? selectedLanguage : 'en'); exports.setLanguage = (language) => { + if (language === selectedLanguage) return; selectedLanguage = language; const newLang = i18nBase[selectedLanguage] || i18nBase['en']; Object.keys(i18n).forEach(k => delete i18n[k]); Object.assign(i18n, newLang); + const momentLocale = moment.locale(selectedLanguage === 'zh' ? 'zh-cn' : (i18nBase[selectedLanguage] ? selectedLanguage : 'en')); + if (moment.localeData(momentLocale).postformat('1') !== '1') moment.updateLocale(momentLocale, { preparse: (str) => str, postformat: (str) => str }); }; exports.i18n = i18n; Object.defineProperty(exports, 'selectedLanguage', { get: () => selectedLanguage }); @@ -339,18 +760,57 @@ const renderEngagement = (id, opinionsNode, commentsNode) => { }; exports.renderEngagement = renderEngagement; -const renderOpinionsVoting = (basePath, id, opinions, returnTo, voters) => { +const renderModuleStats = (total, segments = [], totalLabel = null, opts = {}) => { + if (!(Number(total) > 0)) return null; + const inh = opts.showInhabitants && sharedState.getInhabitantCount ? sharedState.getInhabitantCount() : 0; + const trb = opts.showTribes && sharedState.getTribesCount ? sharedState.getTribesCount() : 0; + const network = [ + inh > 0 ? { label: i18n.inhabitants, count: inh } : null, + trb > 0 ? { label: i18n.tribesTitle, count: trb } : null + ]; + const toNode = s => span(s.labelClass ? span({ class: s.labelClass }, `${s.label}: `) : `${s.label}: `, strong(String(s.count))); + return div({ class: 'module-stats-line' }, + ...network.filter(Boolean).map(toNode), + span(`${totalLabel || i18n.statsTotalLabel}: `, strong(String(total))), + ...segments.filter(s => s && Number(s.count) > 0).map(toNode) + ); +}; +exports.renderModuleStats = renderModuleStats; + +const renderModuleStatsBy = (items, field, specs, totalLabel = null) => { + const arr = Array.isArray(items) ? items : []; + const counts = {}; + for (const it of arr) { const v = typeof field === 'function' ? field(it) : (it && it[field]); counts[v] = (counts[v] || 0) + 1; } + return renderModuleStats(arr.length, specs.map(s => ({ label: s.label, count: counts[s.value] || 0 })), totalLabel); +}; +exports.renderModuleStatsBy = renderModuleStatsBy; + +const moduleIsEmpty = (items, filter, defaultFilter = 'all', q = '') => + (!items || items.length === 0) && + String(filter || defaultFilter).toUpperCase() === String(defaultFilter).toUpperCase() && + !String(q || '').trim(); +exports.moduleIsEmpty = moduleIsEmpty; + +const renderCardMetaRow = (...nodes) => { + const flat = nodes.flat(Infinity).filter(Boolean); + if (!flat.length) return null; + return div({ class: 'card-meta-row' }, ...flat); +}; +exports.renderCardMetaRow = renderCardMetaRow; + +const renderOpinionsVoting = (basePath, id, opinions, returnTo, voters, pathFor = null) => { const ops = opinions || {}; const total = Object.values(ops).reduce((s, n) => s + (Number(n) || 0), 0); const myId = (config.keys && config.keys.id) ? config.keys.id : ''; const alreadyVoted = Array.isArray(voters) && myId ? voters.includes(myId) : false; - return details({ class: 'opinions-voting-collapse' }, + const actionFor = (category) => typeof pathFor === 'function' ? pathFor(id, category) : `${basePath}/${encodeURIComponent(id)}/${category}`; + const votingDetails = details({ class: 'opinions-voting-collapse' }, summary({ class: total > 0 ? 'opinions-summary engage-on' : 'opinions-summary' }, span({ class: 'opinions-summary-icon' }, 'ꔍ'), span({ class: 'opinions-summary-count' }, `(${total})`)), div({ class: 'voting-buttons' }, opinionCategoriesList.map((category) => - form({ method: 'POST', action: `${basePath}/${encodeURIComponent(id)}/${category}` }, + form({ method: 'POST', action: actionFor(category) }, returnTo ? input({ type: 'hidden', name: 'returnTo', value: returnTo }) : null, button({ class: alreadyVoted ? 'vote-btn disabled' : 'vote-btn', type: 'submit', ...(alreadyVoted ? { disabled: true } : {}) }, `${String(i18n['vote' + category.charAt(0).toUpperCase() + category.slice(1)] || category).toUpperCase()} [${ops[category] || 0}]`) @@ -359,6 +819,7 @@ const renderOpinionsVoting = (basePath, id, opinions, returnTo, voters) => { ), alreadyVoted ? p({ class: 'muted' }, i18n.alreadyVoted) : null ); + return [renderVotesSummary(ops), votingDetails].filter(Boolean); }; exports.renderOpinionsVoting = renderOpinionsVoting; @@ -384,7 +845,6 @@ const renderSpreadEditWarning = async (msgId) => { exports.renderSpreadEditWarning = renderSpreadEditWarning; -// markdown const markdownUrl = "https://commonmark.org/help/"; const doctypeString = ""; @@ -399,12 +859,37 @@ const nbsp = "\xa0"; const { getConfig } = require('../configs/config-manager.js'); -// menu INIT -const assetVersion = () => { - try { return encodeURIComponent(String(readPkg()?.version || '0')); } catch (_) { return '0'; } +const shortCache = (ttl, fn) => { + let at = 0; + let value; + return () => { + const now = Date.now(); + if (!at || now - at > ttl) { value = fn(); at = now; } + return value; + }; }; -const readPkg = () => { +const computeAssetVersion = () => { + try { + const base = path.resolve(__dirname, "..", "client", "assets"); + let latest = 0; + for (const rel of ["styles", "themes"]) { + const dir = path.join(base, rel); + for (const f of fs.readdirSync(dir)) { + const t = fs.statSync(path.join(dir, f)).mtimeMs; + if (t > latest) latest = t; + } + } + const v = String(readPkg()?.version || '0'); + return encodeURIComponent(latest ? `${v}-${Math.floor(latest / 1000).toString(36)}` : v); + } catch (_) { + try { return encodeURIComponent(String(readPkg()?.version || '0')); } catch (_) { return '0'; } + } +}; + +const assetVersion = shortCache(5000, () => computeAssetVersion()); + +const readPkgFile = () => { const file = path.resolve(__dirname, "..", "server", "package.json"); try { const txt = fs.readFileSync(file, "utf8"); @@ -414,6 +899,7 @@ const readPkg = () => { return {}; } }; +const readPkg = shortCache(30000, readPkgFile); const renderFooter = () => { const pkg = readPkg(); @@ -425,6 +911,7 @@ const renderFooter = () => { const hcH = sharedState.getCarbonHcH(); const peersOnline = sharedState.getOnlinePeerCount ? sharedState.getOnlinePeerCount() : null; + const syncedPeers = sharedState.getSyncedPeerCount ? sharedState.getSyncedPeerCount() : null; const inboxUnread = sharedState.getInboxUnreadCount ? sharedState.getInboxUnreadCount() : null; const lastSyncTs = sharedState.getLastSyncTs ? sharedState.getLastSyncTs() : null; const lastSyncLabel = lastSyncTs ? moment(lastSyncTs).fromNow() : '–'; @@ -457,6 +944,12 @@ const renderFooter = () => { ]; })(), br(), + span({ class: "oasis-footer-peers" }, + span("Synced-peers: [ "), + a({ href: "/peers" }, String(syncedPeers || 0)), + span(" ]") + ), + br(), span({ class: "oasis-footer-carbon" }, span("HcT: "), a({ href: "/stats?filter=ALL#carbon" }, hcT != null ? String(hcT) : '–'), @@ -497,30 +990,43 @@ const navLink = ({ href, emoji, text, current, class: extraClass }) => ) ); -const customCSS = (filename) => { - const customStyleFile = path.join( - envPaths("oasis", { suffix: "" }).config, - filename - ); - try { - if (fs.existsSync(customStyleFile)) { - return link({ rel: "stylesheet", href: filename }); - } - } catch (error) { - return ""; +const currentNavPath = () => { + let store = null; + try { store = require('../models/typed_log').requestScope.getStore(); } catch (_) { store = null; } + return store && typeof store.path === 'string' ? store.path : ''; +}; + +const firstHref = (node) => { + if (!node || typeof node !== 'object') return ''; + const own = typeof node.href === 'string' && node.href ? node.href : (typeof node.getAttribute === 'function' ? node.getAttribute('href') : null); + if (own) return String(own); + for (const child of node.childNodes || []) { + const found = firstHref(child); + if (found) return found; } + return ''; +}; + +const navGroupHasPath = (items, path) => { + if (!path) return false; + return items.flat(Infinity).some(item => { + const href = firstHref(item).split(/[?#]/)[0]; + if (!href || href === '/') return false; + return path === href || path.startsWith(href + '/'); + }); }; const navGroup = ({ id, emoji, title, defaultOpen = false }, ...items) => { const active = items.filter(Boolean); if (!active.length) return null; + const open = process.env.OASIS_MOBILE !== '1' && (defaultOpen || navGroupHasPath(active, currentNavPath())); return li( { class: "oasis-nav-group" }, input({ type: "checkbox", id: `oasis-nav-group-${id}`, class: "oasis-nav-toggle", - ...(defaultOpen ? { checked: true } : {}) + ...(open ? { checked: true } : {}) }), label( { for: `oasis-nav-group-${id}`, class: "oasis-nav-header" }, @@ -615,15 +1121,15 @@ const renderDevLink = () => { return ""; }; -const renderLegacyLink = () => { - const legacyMod = getConfig().modules.legacyMod === "on"; - if (legacyMod) { +const renderBackupLink = () => { + const backupMod = getConfig().modules.backupMod === "on"; + if (backupMod) { return [ navLink({ - href: "/legacy", + href: "/backup", emoji: "ꖤ", - text: i18n.legacy, - class: "legacy-link enabled" + text: i18n.backupTitle, + class: "backup-link enabled" }) ]; } @@ -678,7 +1184,7 @@ const renderImagesLink = () => { return [ navLink({ href: "/images", - emoji: "ꕥ", + emoji: "▦", text: i18n.imagesLabel, class: "images-link enabled" }) @@ -687,6 +1193,21 @@ const renderImagesLink = () => { return ""; }; +const renderFilesLink = () => { + const filesMod = getConfig().modules.filesMod === "on"; + if (filesMod) { + return [ + navLink({ + href: "/files", + emoji: "▣", + text: i18n.filesLabel, + class: "files-link enabled" + }) + ]; + } + return ""; +}; + const renderTorrentsLink = () => { const torrentsMod = getConfig().modules.torrentsMod === "on"; if (torrentsMod) { @@ -792,9 +1313,18 @@ const renderTagsLink = () => { }; +const hasMultiverseAccount = () => { + try { + const store = JSON.parse(fs.readFileSync(require('../configs/state-manager').statePath('fediverse-accounts.json'), 'utf8')); + return !!(store && ((store.mastodon && store.mastodon.token) || (store.peertube && store.peertube.token) || (store.telegram && store.telegram.session))); + } catch (_) { + return false; + } +}; + const renderMastodonLink = () => { const fediverseMod = getConfig().modules.fediverseMod === "on"; - return fediverseMod + return fediverseMod && hasMultiverseAccount() ? navLink({ href: "/fediverse", emoji: "ꗵ", @@ -912,7 +1442,7 @@ const renderLarpLink = () => { ? [ navLink({ href: "/larp", - emoji: "✦", + emoji: "♜", text: i18n.larpTitle || "L.A.R.P.", class: "larp-link enabled" }) @@ -934,6 +1464,18 @@ const renderParliamentLink = () => { : ""; }; +const renderSchoolLink = () => { + const schoolMod = getConfig().modules.schoolMod === "on"; + return schoolMod + ? navLink({ + href: "/school", + emoji: "ꖂ", + text: i18n.schoolTitle, + class: "school-link enabled" + }) + : ""; +}; + const renderCourtsLink = () => { const courtsMod = getConfig().modules.courtsMod === "on"; return courtsMod @@ -946,6 +1488,78 @@ const renderCourtsLink = () => { : ""; }; +const renderEmergenciesLink = () => { + const emergenciesMod = getConfig().modules.emergenciesMod === "on"; + return emergenciesMod + ? navLink({ + href: "/emergencies", + emoji: "ꕥ", + text: i18n.emergenciesTitle, + class: "emergencies-link enabled" + }) + : ""; +}; + +const renderCampaignsLink = () => { + const campaignsMod = getConfig().modules.campaignsMod === "on"; + return campaignsMod + ? navLink({ + href: "/campaigns", + emoji: "✍\ufe0e", + text: i18n.campaignsTitle, + class: "campaigns-link enabled" + }) + : ""; +}; + +const renderPodcastsLink = () => { + const podcastsMod = getConfig().modules.podcastsMod === "on"; + return podcastsMod + ? navLink({ + href: "/podcasts", + emoji: "ꖜ", + text: i18n.podcastsTitle, + class: "podcasts-link enabled" + }) + : ""; +}; + +const renderLogisticsLink = () => { + const logisticsMod = getConfig().modules.logisticsMod === "on"; + return logisticsMod + ? navLink({ + href: "/logistics", + emoji: "⇄", + text: i18n.logisticsTitle, + class: "logistics-link enabled" + }) + : ""; +}; + +const renderRoomsLink = () => { + const roomsMod = getConfig().modules.roomsMod === "on"; + return roomsMod + ? navLink({ + href: "/rooms", + emoji: "ꘒ", + text: i18n.roomsTitle, + class: "rooms-link enabled" + }) + : ""; +}; + +const renderMailingLink = () => { + const mailingMod = getConfig().modules.mailingMod === "on"; + return mailingMod + ? navLink({ + href: "/mailing", + emoji: "✉", + text: i18n.mailingTitle, + class: "mailing-link enabled" + }) + : ""; +}; + const renderVotationsLink = () => { const votesMod = getConfig().modules.votesMod === "on"; return votesMod @@ -980,7 +1594,7 @@ const renderReportsLink = () => { ? [ navLink({ href: "/reports", - emoji: "ꕥ", + emoji: "⚐", text: i18n.reportsTitle, class: "reports-link enabled" }) @@ -1016,6 +1630,20 @@ const renderPadsLink = () => { : ""; }; +const renderWikiLink = () => { + const wikiMod = getConfig().modules.wikiMod === "on"; + return wikiMod + ? [ + navLink({ + href: "/wiki", + emoji: "¶", + text: i18n.wikiTitle, + class: "wiki-link enabled" + }) + ] + : ""; +}; + const renderTransfersLink = () => { const transfersMod = getConfig().modules.transfersMod === "on"; return transfersMod @@ -1200,17 +1828,131 @@ exports.PINNABLE_MODULES = forkNav.PINNABLE_MODULES; exports.renderHiveNav = renderHiveNav; // se resuelve al llamarse, cuando template ya esta definido mas abajo exports.bottomBarPickerView = () => forkNav.bottomBarPickerView(template); +const renderEmergencyBanner = (featured, opts = {}) => { + if (!featured || !featured.href) return null; + const compact = !!opts.compact; + const cap = compact ? 42 : 80; + const sev = String(featured.severity || "UNVERIFIED"); + const sevLabel = String(i18n[`emergencySeverity${sev.charAt(0) + sev.slice(1).toLowerCase()}`] || sev).toUpperCase(); + const t = String(featured.title || "").trim(); + const label = `${t.length > cap ? t.slice(0, cap) + "…" : t} (${sevLabel} · ${featured.confirmationCount || 0})`; + return div( + { class: (compact ? "ai-suggestion-banner ai-suggestion-inline" : "update-banner ai-suggestion-banner") + ` emergency-banner emergency-banner-${sev.toLowerCase()}` }, + span({ class: "update-banner-icon" }, "🚨"), + compact ? null : span({ class: "update-banner-text" }, i18n.emergencyBannerLabel), + a({ href: featured.href, class: "update-banner-link" }, label), + form({ method: "POST", action: "/emergencies/banner/dismiss", class: "welcome-banner-close" }, + button({ type: "submit", class: "welcome-banner-close-btn" }, "✕")) + ); +}; const template = (titlePrefix, ...elements) => { + const scope = listScope(); + if (scope && scope.dryRun) return ''; const currentConfig = getConfig(); const theme = currentConfig.themes.current || "Dark-SNH"; - const uxMode = currentConfig.ux?.current === "ainav" ? "ainav" : currentConfig.ux?.current === "chats" ? "chats" : "blocks"; + const buildAiSuggestion = (compact) => { + try { + const { getConfig } = require('../configs/config-manager.js'); + if (getConfig().ai?.suggestions === false) return null; + } catch (_) {} + let here = ''; + try { here = (require('../models/typed_log').requestScope.getStore() || {}).path || ''; } catch (_) {} + const own = sharedState.getSectionMatch ? sharedState.getSectionMatch(here.split('/')[1] || '') : null; + const suggestion = (own && own.href !== here) ? own : (sharedState.getBestMatch ? sharedState.getBestMatch() : null); + if (!suggestion || !suggestion.href) return null; + if (sharedState.isSuggestionDismissed && sharedState.isSuggestionDismissed(suggestion.href)) return null; + if (sharedState.areSuggestionsQuiet && sharedState.areSuggestionsQuiet()) return null; + const cap = compact ? 52 : 96; + const t = String(suggestion.title || '').trim(); + const label = t.length > cap ? t.slice(0, cap) + '…' : t; + return div( + { class: compact ? "ai-suggestion-banner ai-suggestion-inline" : "update-banner ai-suggestion-banner" }, + span({ class: "update-banner-icon" }, "🤖"), + a({ href: suggestion.href, class: "update-banner-link" }, label), + form( + { method: "POST", action: "/ai/suggestion/dismiss", class: "welcome-banner-close" }, + input({ type: "hidden", name: "href", value: suggestion.href }), + button({ type: "submit", class: "welcome-banner-close-btn" }, "✕") + ) + ); + }; + const bannerAct = (action, label, kls, fields = {}) => form({ method: "POST", action, class: "phone-action-form" }, + ...Object.entries(fields).map(([k, v]) => input({ type: "hidden", name: k, value: String(v) })), + button({ type: "submit", class: kls }, String(label).toUpperCase())); + const bannerCls = () => "update-banner phone-banner"; + const bannerChip = (icon, value) => span({ class: "pm-exposition-chip pm-exposition-whole phone-banner-chip" }, + span({ class: "pm-exposition-icon" }, icon), + span({ class: "pm-exposition-text" }, value)); + const buildRoomBanner = (here) => { + if (config.public || getConfig().modules.roomsMod !== 'on') return null; + const room = sharedState.getPhoneRoom(); + if (room && room.ref) { + const href = `/rooms/${encodeURIComponent(room.ref)}`; + if (here === href) return null; + return div({ class: bannerCls() }, + span({ class: "phone-banner-dot" }), + a({ href, class: "update-banner-link" }, room.title), + bannerChip("👥", `${room.count}/${room.max}`), + room.joinedAt ? bannerChip("◷", liveClock(room.joinedAt)) : null, + room.hand ? bannerChip("✋", String((Array.isArray(room.peers) ? room.peers : []).filter(p => p && p.hand && (Number(p.handSince) || 0) < (Number(room.handSince) || 0)).length + 1)) : null, + bannerAct("/rooms/mute", room.muted ? i18n.phoneUnmute : i18n.phoneMute, "tribe-action-btn", { mute: room.muted ? 0 : 1 }), + bannerAct("/rooms/hand", `✋ ${room.hand ? i18n.roomLowerHand : i18n.roomRaiseHand}`, room.hand ? "tribe-action-btn room-hand-btn-on" : "tribe-action-btn", { hand: room.hand ? 0 : 1 }), + bannerAct("/rooms/leave", i18n.roomLeave, "tribe-action-btn danger-btn") + ); + } + return null; + }; + const buildPhoneBanner = () => { + let here = ''; + try { here = (require('../models/typed_log').requestScope.getStore() || {}).path || ''; } catch (_) {} + const st = phoneOn() ? sharedState.getPhoneCall() : null; + if (!st) return buildRoomBanner(here); + if (here === '/phone') return null; + const nameOf = (id) => nameCache.get(id) || String(id).slice(0, 10); + const name = st.group && Array.isArray(st.peers) ? st.peers.filter(p => p.phase !== 'gone').map(p => nameOf(p.id)).join(', ') : nameOf(st.peer); + const shell = (text, ...rest) => div({ class: bannerCls() }, + span({ class: "phone-banner-dot" }), + a({ href: "/phone", class: "update-banner-link" }, text), + ...rest + ); + if (st.dir === 'in' && st.phase === 'incoming') { + return shell(`${name} ${i18n.phoneIsCalling}`, + bannerAct("/phone/accept", i18n.phoneAnswer, "tribe-action-btn"), + bannerAct("/phone/reject", i18n.phoneReject, "tribe-action-btn danger-btn")); + } + if (st.phase === 'calling') { + return shell(`${st.reached ? i18n.phoneCalling : i18n.phoneConnecting} ${name}`, + bannerChip("◷", liveClock(st.startedAt)), + bannerAct("/phone/hangup", i18n.phoneHangup, "tribe-action-btn danger-btn")); + } + if (st.phase === 'recording' || st.phase === 'recorded') { + return shell(`${i18n.phoneRecording} ${name}`, + st.phase === 'recorded' ? null : bannerChip("●", liveClock(st.recordingStartedAt, { max: PHONE_PAM_MAX_MS })), + bannerAct("/phone/pam/send", i18n.phoneSendPam, "tribe-action-btn"), + bannerAct("/phone/pam/cancel", i18n.phoneCancel, "tribe-action-btn danger-btn")); + } + return shell(name, + bannerChip("◷", liveClock(st.answeredAt || st.startedAt)), + bannerAct("/phone/mute", st.muted ? i18n.phoneUnmute : i18n.phoneMute, "tribe-action-btn", { mute: st.muted ? 0 : 1 }), + bannerAct("/phone/hangup", i18n.phoneHangup, "tribe-action-btn danger-btn")); + }; + const buildEmergencyBanner = (compact) => { + try { + if (getConfig().modules.emergenciesMod !== "on") return null; + const featured = sharedState.getFeaturedEmergency ? sharedState.getFeaturedEmergency() : null; + if (!featured || !featured.id) return null; + if (sharedState.getDismissedEmergency && sharedState.getDismissedEmergency() === featured.id) return null; + return renderEmergencyBanner(featured, { compact }); + } catch (_) { return null; } + }; + const uxMode = currentConfig.ux?.current === "ainav" ? "ainav" : currentConfig.ux?.current === "chats" ? "chats" : currentConfig.ux?.current === "feed" ? "feed" : currentConfig.ux?.current === "phone" ? "phone" : "blocks"; const themeLink = link({ rel: "stylesheet", href: `/assets/themes/${theme}.css?v=${assetVersion()}` }); const nodes = html( - { lang: "en" }, + { lang: i18nBase[selectedLanguage] ? selectedLanguage : "en" }, head( title(titlePrefix, " | Oasis"), link({ rel: "stylesheet", href: `/assets/styles/style.css?v=${assetVersion()}` }), @@ -1234,6 +1976,11 @@ const template = (titlePrefix, ...elements) => { }) ), body( + ...(uxMode === "blocks" ? [ + input({ type: "radio", name: "oasis-nav-side", id: "nav-none-toggle", class: "oasis-nav-side-toggle", checked: true }), + input({ type: "radio", name: "oasis-nav-side", id: "nav-left-toggle", class: "oasis-nav-side-toggle" }), + input({ type: "radio", name: "oasis-nav-side", id: "nav-right-toggle", class: "oasis-nav-side-toggle" }) + ] : []), div( { class: uxMode === "ainav" ? "header ainav-only" : "header" }, renderMobileTopbar(), @@ -1249,23 +1996,33 @@ const template = (titlePrefix, ...elements) => { alt: "Oasis Logo" }) ), + (process.env.OASIS_MOBILE === '1' && uxMode === "blocks" && phoneOn()) ? (() => { + const dnd = !!(getConfig().phone || {}).dnd; + return form({ method: 'POST', action: '/phone/dnd', class: 'oasis-phone-state' }, + input({ type: 'hidden', name: 'dnd', value: dnd ? '0' : '1' }), + button({ type: 'submit', class: `oasis-phone-state-btn pm-exposition-chip pm-exposition-${dnd ? 'hidden' : 'mutuals'}`, title: String(dnd ? i18n.phoneDnd : i18n.phoneOpen), 'aria-label': String(dnd ? i18n.phoneDnd : i18n.phoneOpen) }, + span({ class: 'pm-exposition-icon' }, dnd ? '⊘' : '✆') + ) + ); + })() : null, uxMode === "ainav" ? nav( ul( (() => { - const inboxCount = sharedState.getInboxCount(); - const badge = inboxCount > 0 ? span({ class: 'inbox-badge' }, String(inboxCount)) : ''; return li( a({ href: "/inbox" }, - span({ class: "emoji" }, "☂"), nbsp, i18n.inbox, badge + span({ class: "emoji" }, "☂"), nbsp, i18n.inbox, ...inboxBadges() ) ); })(), + renderPhoneTopLink(), navLink({ href: "/settings", emoji: "⚙", text: i18n.settings }), navLink({ href: "/invites", emoji: "ꔮ", text: i18n.invites }) ) ) : nav( ul( (() => { + const mentionsTotal = sharedState.getMentionsTotal ? sharedState.getMentionsTotal() : 0; + if (!(mentionsTotal > 0) && process.env.OASIS_MOBILE !== '1') return null; const mentionsCount = sharedState.getMentionsCount(); const badge = mentionsCount > 0 ? span({ class: 'inbox-badge' }, String(mentionsCount)) : ''; return li( @@ -1275,29 +2032,39 @@ const template = (titlePrefix, ...elements) => { ); })(), (() => { - const inboxCount = sharedState.getInboxCount(); - const badge = inboxCount > 0 ? span({ class: 'inbox-badge' }, String(inboxCount)) : ''; return li( a({ href: "/inbox" }, - span({ class: "emoji" }, "☂"), nbsp, i18n.inbox, badge + span({ class: "emoji" }, "☂"), nbsp, i18n.inbox, ...inboxBadges() ) ); })(), - uxMode === "chats" + renderPhoneTopLink(), + (uxMode === "chats" || uxMode === "feed" || uxMode === "phone") ? navLink({ href: "/settings", emoji: "⚙", text: i18n.settings }) - : navLink({ - href: "/pm", - emoji: "ꕕ", - text: i18n.privateMessage - }) + : null ) ) ), + uxMode === "blocks" ? div( + { class: "oasis-mobile-menus" }, + span({ class: "oasis-menu-slot oasis-menu-slot-left" }, + label({ for: "nav-left-toggle", class: "oasis-menu-btn oasis-menu-open" }, + span({ class: "oasis-menu-ico" }, "☰"), span(i18n.menuPersonal || "Personal")), + label({ for: "nav-none-toggle", class: "oasis-menu-btn oasis-menu-close" }, + span({ class: "oasis-menu-ico" }, "☰"), span(i18n.menuPersonal || "Personal"))), + span({ class: "oasis-menu-slot oasis-menu-slot-right" }, + label({ for: "nav-right-toggle", class: "oasis-menu-btn oasis-menu-open" }, + span({ class: "oasis-menu-ico" }, "☰"), span(i18n.menuNetwork || "Community")), + label({ for: "nav-none-toggle", class: "oasis-menu-btn oasis-menu-close" }, + span({ class: "oasis-menu-ico" }, "☰"), span(i18n.menuNetwork || "Community"))) + ) : null, (() => { const aiNavOn = getConfig().modules.aiNavMod === 'on'; if (!aiNavOn && uxMode !== 'ainav') return null; return div( { class: uxMode === 'ainav' ? "top-bar-center top-bar-center-ainav" : "top-bar-center" }, + div({ class: 'ai-ask-row' }, + nav({ class: 'ai-ask-nav' }, ul(...(renderAILink() || []))), form( { method: 'POST', action: '/ai/ask', class: 'ai-ask-form' }, input({ @@ -1310,7 +2077,10 @@ const template = (titlePrefix, ...elements) => { autofocus: uxMode === 'ainav' ? 'autofocus' : undefined }), button({ type: 'submit', class: 'ai-ask-btn' }, '➤') - ) + ), + buildAiSuggestion(true) + ), + buildEmergencyBanner(true) ); })(), uxMode === "ainav" ? div( @@ -1322,33 +2092,35 @@ const template = (titlePrefix, ...elements) => { navLink({ href: "/peers", emoji: "⧖", text: i18n.peers }) ) ) - ) : uxMode === "chats" ? div( + ) : (uxMode === "chats" || uxMode === "feed" || uxMode === "phone") ? div( { class: "top-bar-right" }, nav( ul( navLink({ href: "/activity", emoji: "ꔙ", text: i18n.activityTitle }), renderTrendingLink(), - navLink({ href: "/search", emoji: "ꔅ", text: i18n.searchTitle }) + navLink({ href: "/search", emoji: "⌕", text: i18n.searchTitle }) ) ) ) : div( { class: "top-bar-right" }, nav( ul( + ...((getConfig().modules.aiNavMod === 'on' || uxMode === 'ainav') ? [] : (renderAILink() || [])), navLink({ href: "/data", emoji: "⚯", text: i18n.dataTitle }), renderTagsLink(), - navLink({ href: "/search", emoji: "ꔅ", text: i18n.searchTitle }) + navLink({ href: "/search", emoji: "⌕", text: i18n.searchTitle }), + process.env.OASIS_MOBILE === '1' ? navLink({ href: "/settings", emoji: "⚙", text: i18n.settings }) : null ) ) ) ), (() => { - const updateFlagPath = path.join(__dirname, '../server/.update_required'); - if (fs.existsSync(updateFlagPath)) { + const updateInfo = (() => { try { return require('../backend/updater').readUpdateInfo(); } catch (_) { return null; } })(); + if (updateInfo) { return div( { class: "update-banner" }, span({ class: "update-banner-icon" }, "🛠️"), - span({ class: "update-banner-text" }, i18n.updateBannerText), + span({ class: "update-banner-text" }, updateInfo.version ? `${String(i18n.updateBannerText).replace(/[.。।!]\s*$/, '')}: v${updateInfo.version}` : i18n.updateBannerText), a({ href: "/settings", class: "update-banner-link" }, i18n.updateBannerAction) ); } @@ -1358,11 +2130,14 @@ const template = (titlePrefix, ...elements) => { try { const onboarding = require('../models/onboarding_model'); if (!onboarding.bannerVisible(config && config.path)) return null; + let here = ''; + try { here = (require('../models/typed_log').requestScope.getStore() || {}).path || ''; } catch (_) {} + if (here === '/welcome' || here.startsWith('/welcome/')) return null; return div( { class: "update-banner welcome-banner" }, span({ class: "update-banner-icon" }, "🌴"), span({ class: "update-banner-text" }, i18n.welcomeBannerText), - a({ href: "/welcome", class: "update-banner-link" }, i18n.welcomeBannerAction), + a({ href: "/welcome", class: "create-button welcome-banner-start" }, i18n.welcomeBannerAction), form( { method: "POST", action: "/welcome/dismiss", class: "welcome-banner-close" }, button({ type: "submit", class: "welcome-banner-close-btn" }, "✕") @@ -1372,27 +2147,11 @@ const template = (titlePrefix, ...elements) => { return null; } })(), - (() => { - try { - const { getConfig } = require('../configs/config-manager.js'); - if (getConfig().ai?.suggestions === false) return null; - } catch (_) {} - const suggestion = sharedState.getBestMatch ? sharedState.getBestMatch() : null; - if (!suggestion || !suggestion.href) return null; - if (sharedState.getDismissedSuggestion && sharedState.getDismissedSuggestion() === suggestion.href) return null; - return div( - { class: "update-banner ai-suggestion-banner" }, - span({ class: "update-banner-icon" }, "🤖"), - span({ class: "update-banner-text" }, i18n.aiSuggestionBanner), - a({ href: suggestion.href, class: "update-banner-link" }, `${suggestion.title} (${(((Number(suggestion.score) || 0) * 100).toFixed(1)).replace(/\.0$/, '')}%)`), - form( - { method: "POST", action: "/ai/suggestion/dismiss", class: "welcome-banner-close" }, - button({ type: "submit", class: "welcome-banner-close-btn" }, "✕") - ) - ); - })(), + (getConfig().modules.aiNavMod === 'on' || uxMode === 'ainav') ? null : buildAiSuggestion(false), + buildPhoneBanner(), + buildEmergencyBanner(false), div( - { class: uxMode === "ainav" ? "main-content ainav-only" : uxMode === "chats" ? "main-content chatsux-only" : "main-content" }, + { class: uxMode === "ainav" ? "main-content ainav-only" : uxMode === "chats" ? "main-content chatsux-only" : uxMode === "feed" || uxMode === "phone" ? "main-content chatsux-only" : "main-content" }, uxMode !== "blocks" ? null : div( { class: "sidebar-left" }, nav( @@ -1439,10 +2198,12 @@ const template = (titlePrefix, ...elements) => { emoji: "ꖘ", text: i18n.inhabitantsLabel }), - renderTribesLink(), - renderLarpLink(), + renderEmergenciesLink(), + renderSchoolLink(), renderParliamentLink(), - renderCourtsLink() + renderCourtsLink(), + renderLarpLink(), + renderTribesLink() ), navGroup( { @@ -1450,14 +2211,15 @@ const template = (titlePrefix, ...elements) => { emoji: "¤", title: i18n.menuEconomy }, - renderBankingLink(), renderWalletLink(), + renderBankingLink(), renderMarketLink(), - renderHousingLink(), - renderProjectsLink(), - renderIndustryLink(), - renderJobsLink(), renderShopsLink(), + renderIndustryLink(), + renderLogisticsLink(), + renderProjectsLink(), + renderJobsLink(), + renderHousingLink(), renderTransfersLink() ), navGroup( @@ -1468,10 +2230,12 @@ const template = (titlePrefix, ...elements) => { }, renderVotationsLink(), renderPollsLink(), + renderCampaignsLink(), renderEventsLink(), renderCalendarsLink(), renderTasksLink(), - renderReportsLink() + renderReportsLink(), + renderMailingLink() ), navGroup( { @@ -1479,8 +2243,7 @@ const template = (titlePrefix, ...elements) => { emoji: "⚒", title: i18n.menuTools }, - renderAILink(), - navLink({ + process.env.OASIS_MOBILE === '1' ? null : navLink({ href: "/blockexplorer", emoji: "ꖸ", text: i18n.blockchain @@ -1489,17 +2252,16 @@ const template = (titlePrefix, ...elements) => { navLink({ href: "/peers", emoji: "⧖", text: i18n.peers }), renderDevLink(), renderCipherLink(), - renderLegacyLink(), navLink({ href: "/stats", emoji: "ꕷ", text: i18n.statistics - }) + }), + renderBackupLink() ) ) ) ), - main({ id: "content", class: "main-column" }, elements), uxMode !== "blocks" ? null : div( { class: "sidebar-right" }, nav( @@ -1515,13 +2277,15 @@ const template = (titlePrefix, ...elements) => { emoji: "ꔙ", text: i18n.activityTitle }), - renderFeedLink(), - renderBlogsLink(), renderTrendingLink(), renderOpinionsLink(), - renderPadsLink(), + renderFeedLink(), + renderBlogsLink(), renderForumLink(), + renderPadsLink(), + renderWikiLink(), renderMapsLink(), + renderRoomsLink(), renderChatsLink(), renderKarvanLink() ), @@ -1535,8 +2299,10 @@ const template = (titlePrefix, ...elements) => { renderBookmarksLink(), renderDocsLink(), renderImagesLink(), + renderFilesLink(), renderTorrentsLink(), - renderVideosLink() + renderVideosLink(), + renderPodcastsLink() ), navGroup( { @@ -1558,7 +2324,8 @@ const template = (titlePrefix, ...elements) => { ) ) ) - ) + ), + main({ id: "content", class: "main-column" }, elements, renderListPager(), renderLogWindowNote(), renderMobileCounters()) ), renderBottomBar(), renderFooter() @@ -1566,7 +2333,6 @@ const template = (titlePrefix, ...elements) => { ); return doctypeString + nodes.outerHTML; }; -// menu END exports.template = template; @@ -1575,7 +2341,7 @@ exports.ainavHomeView = ({ recentTags = [] } = {}) => { const theme = currentConfig.themes.current || "Dark-SNH"; const placeholder = i18n.aiNavPlaceholder || 'Where do you want to go?'; const nodes = html( - { lang: "en" }, + { lang: i18nBase[selectedLanguage] ? selectedLanguage : "en" }, head( title(placeholder, " | Oasis"), link({ rel: "stylesheet", href: `/assets/styles/style.css?v=${assetVersion()}` }), @@ -1599,14 +2365,13 @@ exports.ainavHomeView = ({ recentTags = [] } = {}) => { nav( ul( (() => { - const inboxCount = sharedState.getInboxCount(); - const badge = inboxCount > 0 ? span({ class: 'inbox-badge' }, String(inboxCount)) : ''; return li( a({ href: "/inbox" }, - span({ class: "emoji" }, "☂"), nbsp, i18n.inbox, badge + span({ class: "emoji" }, "☂"), nbsp, i18n.inbox, ...inboxBadges() ) ); })(), + renderPhoneTopLink(), navLink({ href: "/settings", emoji: "⚙", text: i18n.settings }), navLink({ href: "/invites", emoji: "ꔮ", text: i18n.invites }) ) @@ -1986,7 +2751,7 @@ const post = ({ msg, aside = false, preview = false, spreadInfo = null }) => { const u = safeStr(c.url); if (u) { nodes.push( - renderField((i18n.url || 'URL') + ':', a({ href: u, target: '_blank', rel: 'noopener noreferrer' }, u)) + renderField((i18n.url || 'URL') + ':', a({ href: safeExternalHref(u), target: '_blank', rel: 'noopener noreferrer' }, u)) ); } } else if (t === 'image') { @@ -1994,7 +2759,7 @@ const post = ({ msg, aside = false, preview = false, spreadInfo = null }) => { if (u && isMsgId(u)) { nodes.push( div({ class: 'card-field card-field-mt' }, - img({ src: `/blob/${encodeURIComponent(u)}`, class: 'feed-image img-content' }) + img({ loading: 'lazy', src: `/blob/${encodeURIComponent(u)}`, class: 'feed-image img-content' }) ) ); } @@ -2012,7 +2777,7 @@ const post = ({ msg, aside = false, preview = false, spreadInfo = null }) => { if (u && isMsgId(u)) { nodes.push( div({ class: 'card-field card-field-mt' }, - videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(u)}` }) + videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(u)}`, preload: 'metadata' }) ) ); } @@ -2081,7 +2846,7 @@ const post = ({ msg, aside = false, preview = false, spreadInfo = null }) => { details( summary(i18n.viewJson || 'View JSON'), pre({ - innerHTML: highlightJs.highlight( + [RAW_HTML]: highlightJs.highlight( JSON.stringify(msg, null, 2), { language: "json", ignoreIllegals: true } ).value, @@ -2096,11 +2861,11 @@ const post = ({ msg, aside = false, preview = false, spreadInfo = null }) => { (u) => `${u}` ); } - articleElement = article({ class: "content", innerHTML: sanitizeHtml(html) }); + articleElement = article({ class: "content", [RAW_HTML]: sanitizeHtml(html) }); } else { articleElement = article( { class: "content" }, - p({ class: "post-text" }, ...renderUrl(rawText)) + p({ class: "post-text" }, ...renderStyledText(rawText)) ); } @@ -2192,7 +2957,6 @@ const post = ({ msg, aside = false, preview = false, spreadInfo = null }) => { ), span( { class: "created-at" }, - `${i18n.createdBy} `, userLink(authorIdForName, msg.value?.meta?.author?.name), ` | ${timeAbsolute} | ${i18n.sendTime} `, a({ href: url.link }, timeAgo) @@ -2241,6 +3005,7 @@ exports.editProfileView = ({ name, description, visibilityPrefs = {}, feedId = ' ubi: visibilityPrefs.ubi === true, wallet: visibilityPrefs.wallet === true, clearnetShops: visibilityPrefs.clearnetShops === true, + clearnetSchool: visibilityPrefs.clearnetSchool === true, clearnetJobs: visibilityPrefs.clearnetJobs === true, clearnetEvents: visibilityPrefs.clearnetEvents === true, clearnetProjects: visibilityPrefs.clearnetProjects === true, @@ -2250,7 +3015,12 @@ exports.editProfileView = ({ name, description, visibilityPrefs = {}, feedId = ' clearnetImages: visibilityPrefs.clearnetImages === true, clearnetDocuments: visibilityPrefs.clearnetDocuments === true, clearnetTorrents: visibilityPrefs.clearnetTorrents === true, + clearnetFiles: visibilityPrefs.clearnetFiles === true, clearnetBookmarks: visibilityPrefs.clearnetBookmarks === true, + clearnetPodcasts: visibilityPrefs.clearnetPodcasts === true, + clearnetMarket: visibilityPrefs.clearnetMarket === true, + clearnetFeed: visibilityPrefs.clearnetFeed === true, + clearnetWiki: visibilityPrefs.clearnetWiki === true, profileShops: visibilityPrefs.profileShops === true, profileJobs: visibilityPrefs.profileJobs === true, profileEvents: visibilityPrefs.profileEvents === true, @@ -2261,14 +3031,23 @@ exports.editProfileView = ({ name, description, visibilityPrefs = {}, feedId = ' profileImages: visibilityPrefs.profileImages === true, profileDocuments: visibilityPrefs.profileDocuments === true, profileTorrents: visibilityPrefs.profileTorrents === true, + profileFiles: visibilityPrefs.profileFiles === true, profileBookmarks: visibilityPrefs.profileBookmarks === true, + profilePodcasts: visibilityPrefs.profilePodcasts === true, + profileSchool: visibilityPrefs.profileSchool === true, + profileMarket: visibilityPrefs.profileMarket === true, + profileFeed: visibilityPrefs.profileFeed === true, + profileWiki: visibilityPrefs.profileWiki === true, ecoTax: visibilityPrefs.ecoTax !== false, larpSign: visibilityPrefs.larpSign === true, fediverse: visibilityPrefs.fediverse === true, gpg: visibilityPrefs.gpg === true }; const fediverseHandleValue = typeof visibilityPrefs.fediverseHandle === 'string' ? visibilityPrefs.fediverseHandle : ''; - prefs.clearnet = prefs.clearnetShops || prefs.clearnetJobs || prefs.clearnetEvents || prefs.clearnetProjects || prefs.clearnetPosts || prefs.clearnetAudios || prefs.clearnetVideos || prefs.clearnetImages || prefs.clearnetDocuments || prefs.clearnetTorrents || prefs.clearnetBookmarks; + prefs.clearnet = prefs.clearnetShops || prefs.clearnetSchool || prefs.clearnetJobs || prefs.clearnetEvents || prefs.clearnetProjects || prefs.clearnetPosts || prefs.clearnetAudios || prefs.clearnetVideos || prefs.clearnetImages || prefs.clearnetDocuments || prefs.clearnetTorrents || prefs.clearnetFiles || prefs.clearnetBookmarks || prefs.clearnetPodcasts || prefs.clearnetMarket || prefs.clearnetFeed || prefs.clearnetWiki; + const pillRows = (entries) => div({ class: "pref-pill-row" }, + ...entries.slice().sort((a, b) => String(a[1]).localeCompare(String(b[1]))).map(([key, labelText]) => togglePill(key, labelText)) + ); const togglePill = (key, labelText, forced = false) => label( { class: forced ? "pref-pill pref-pill-forced" : "pref-pill", for: forced ? undefined : `vis_${key}`, title: forced ? (i18n.profileDeviceLockedHint || 'On mobile devices this sensor is mandatory and cannot be disabled.') : undefined }, forced ? input({ type: "hidden", name: `vis_${key}`, value: "1" }) : null, @@ -2323,64 +3102,51 @@ exports.editProfileView = ({ name, description, visibilityPrefs = {}, feedId = ' ), br(), br(), div({ class: "prefs-card" }, - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.profileContentSectionTitle || 'Avatar Content'), p({ class: "prefs-help" }, i18n.profileContentHelp || 'Choose which of your modules will be displayed on your profile.') ), - div({ class: "pref-pill-row" }, - togglePill('profileShops', i18n.profileClearnetShopsLabel || 'Shops'), - togglePill('profileJobs', i18n.profileClearnetJobsLabel || 'Jobs'), - togglePill('profileEvents', i18n.profileClearnetEventsLabel || 'Events'), - togglePill('profileProjects', i18n.profileClearnetProjectsLabel || 'Projects'), - togglePill('profilePosts', i18n.profileClearnetPostsLabel || 'Blogs'), - togglePill('profileAudios', i18n.profileClearnetAudiosLabel || 'Audios'), - togglePill('profileVideos', i18n.profileClearnetVideosLabel || 'Videos'), - togglePill('profileImages', i18n.profileClearnetImagesLabel || 'Images'), - togglePill('profileDocuments', i18n.profileClearnetDocumentsLabel || 'Documents'), - togglePill('profileTorrents', i18n.profileClearnetTorrentsLabel || 'Torrents'), - togglePill('profileBookmarks', i18n.profileClearnetBookmarksLabel || 'Bookmarks') - ) + pillRows([ + ['profileAudios', i18n.profileClearnetAudiosLabel || 'Audios'], + ['profilePosts', i18n.profileClearnetPostsLabel || 'Blogs'], + ['profileBookmarks', i18n.profileClearnetBookmarksLabel || 'Bookmarks'], + ['profileDocuments', i18n.profileClearnetDocumentsLabel || 'Documents'], + ['profileEvents', i18n.profileClearnetEventsLabel || 'Events'], + ['profileFeed', i18n.profileClearnetFeedLabel || 'Feed'], + ['profileImages', i18n.profileClearnetImagesLabel || 'Images'], + ['profileJobs', i18n.profileClearnetJobsLabel || 'Jobs'], + ['profileMarket', i18n.profileClearnetMarketLabel || 'Market'], + ['profilePodcasts', i18n.profileClearnetPodcastsLabel || 'Podcasts'], + ['profileProjects', i18n.profileClearnetProjectsLabel || 'Projects'], + ['profileSchool', i18n.profileClearnetSchoolLabel || 'School'], + ['profileShops', i18n.profileClearnetShopsLabel || 'Shops'], + ['profileTorrents', i18n.profileClearnetTorrentsLabel || 'Torrents'], + ['profileFiles', i18n.profileClearnetFilesLabel || 'Files'], + ['profileVideos', i18n.profileClearnetVideosLabel || 'Videos'], + ['profileWiki', i18n.profileClearnetWikiLabel || 'Wikis'] + ]) ), br(), div({ class: "prefs-card" }, - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.profileSensorsSectionTitle || 'Sensors'), p({ class: "prefs-help" }, i18n.profileSensorsHelp || 'Optional metrics shown on your profile.') ), - div({ class: "pref-pill-row" }, - togglePill('karma', i18n.profileVisibilityKarma || 'KARMA Scoring'), - togglePill('activity', i18n.profileVisibilityActivity || 'Activity Level'), - togglePill('fediverse', i18n.profileVisibilityFediverse || 'Multiverse'), - togglePill('device', i18n.profileVisibilityDevice || 'Device', process.env.OASIS_MOBILE === '1'), - togglePill('larpSign', i18n.profileVisibilityLarpSign || 'L.A.R.P. Sign'), - togglePill('gpg', i18n.profileVisibilityGpg || 'GPG Key'), - togglePill('wallet', i18n.profileVisibilityWallet || 'ECOIN Wallet'), - togglePill('ubi', i18n.profileVisibilityUbi || 'UBI'), - togglePill('ecoTax', i18n.profileVisibilityEcoTax || 'ECO Tax') - ), + pillRows([ + ['activity', i18n.profileVisibilityActivity || 'Activity Level'], + ['ecoTax', i18n.profileVisibilityEcoTax || 'ECO Tax'], + ['wallet', i18n.profileVisibilityWallet || 'ECOIN Wallet'], + ['gpg', i18n.profileVisibilityGpg || 'GPG Key'], + ['karma', i18n.profileVisibilityKarma || 'KARMA Scoring'], + ['larpSign', i18n.profileVisibilityLarpSign || 'L.A.R.P. Sign'], + ['fediverse', i18n.profileVisibilityFediverse || 'Multiverse'] + ]), + process.env.OASIS_MOBILE === '1' + ? div({ class: "pref-pill-row" }, togglePill('device', i18n.profileVisibilityDevice || 'Device', true)) + : null, input({ type: "hidden", name: "fediverseHandle", value: fediverseHandleValue }) ), br(), - div({ class: "prefs-card" }, - div({ class: "tags-header" }, - h2(i18n.clearnetSectionTitle || 'Clearnet'), - p({ class: "prefs-help" }, i18n.profileClearnetHelp || 'Modules that can be accessed from outside Oasis.') - ), - div({ class: "pref-pill-row" }, - togglePill('clearnetShops', i18n.profileClearnetShopsLabel || 'Shops'), - togglePill('clearnetJobs', i18n.profileClearnetJobsLabel || 'Jobs'), - togglePill('clearnetEvents', i18n.profileClearnetEventsLabel || 'Events'), - togglePill('clearnetProjects', i18n.profileClearnetProjectsLabel || 'Projects'), - togglePill('clearnetPosts', i18n.profileClearnetPostsLabel || 'Blogs'), - togglePill('clearnetAudios', i18n.profileClearnetAudiosLabel || 'Audios'), - togglePill('clearnetVideos', i18n.profileClearnetVideosLabel || 'Videos'), - togglePill('clearnetImages', i18n.profileClearnetImagesLabel || 'Images'), - togglePill('clearnetDocuments', i18n.profileClearnetDocumentsLabel || 'Documents'), - togglePill('clearnetTorrents', i18n.profileClearnetTorrentsLabel || 'Torrents'), - togglePill('clearnetBookmarks', i18n.profileClearnetBookmarksLabel || 'Bookmarks') - ) - ), - br(), button( { type: "submit", @@ -2393,40 +3159,39 @@ exports.editProfileView = ({ name, description, visibilityPrefs = {}, feedId = ' }; exports.clearnetBlogView = async ({ msgKey, text, author, authorName, contentWarning, sentAt }) => { - const { escapeHtml: esc, renderClearnetPage } = require('./clearnet_view'); + const { escapeHtml: esc, renderKindTag, renderClearnetPage, renderRichText } = require('./clearnet_view'); const rawText = String(text || ''); - const renderedHtml = sanitizeHtml(markdown(rawText)) - .replace(/(["'])\/blob\//g, '$1/c/blob/'); + const renderedHtml = sanitizeHtml(renderRichText(rawText)); const plainPreview = rawText.replace(/!\[[^\]]*\]\([^)]*\)/g, '').replace(/<[^>]+>/g, '').slice(0, 200); const authorEsc = esc(authorName || (author || '').slice(1, 9)); const dateStr = sentAt ? esc(new Date(sentAt).toISOString().slice(0, 10)) : ''; const cw = esc(contentWarning || ''); const firstLine = rawText.replace(/!\[[^\]]*\]\([^)]*\)/g, '').replace(/<[^>]+>/g, '').split('\n').map(s => s.trim()).find(Boolean) || ''; - const titleText = cw || firstLine.slice(0, 100) || 'Post'; + const titleText = cw || firstLine.slice(0, 100) || i18n.cnKindBlog; const extraCss = ` .cn-blog-meta{color:var(--fg-dim);font-size:13px;margin-bottom:16px;display:flex;gap:14px;flex-wrap:wrap} .cn-blog-cw{background:#663d00;color:#ffd700;border:1px solid #ff7300;padding:8px 14px;border-radius:6px;margin-bottom:16px;font-weight:600} .cn-blog-body{color:var(--fg-soft);line-height:1.7;font-size:16px;margin:0;word-wrap:break-word} -.cn-blog-body p{margin:0 0 14px 0} .cn-blog-body img{max-width:100%;height:auto;border-radius:6px;border:1px solid var(--border);display:block;margin:10px 0} .cn-blog-body a{color:var(--fg);text-decoration:underline} .cn-blog-body a:hover{color:var(--accent)} -.cn-blog-body pre,.cn-blog-body code{background:var(--bg-sub);color:var(--fg);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-family:monospace;font-size:13px} -.cn-blog-body pre{padding:10px 14px;overflow-x:auto;white-space:pre-wrap;word-break:break-word} -.cn-blog-body blockquote{margin:10px 0;padding:6px 14px;border-left:3px solid var(--fg);color:var(--fg-soft);background:var(--bg-sub);border-radius:0 4px 4px 0} -.cn-blog-body h1,.cn-blog-body h2,.cn-blog-body h3{color:var(--fg);margin:18px 0 10px 0} -.cn-blog-body ul,.cn-blog-body ol{padding-left:24px;margin:8px 0} +.cn-blog-body .rt-code{background:var(--bg-sub);color:var(--fg);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-family:monospace;font-size:13px} +.cn-blog-body .rt-code-block{background:var(--bg-sub);color:var(--fg);border:1px solid var(--border);border-radius:4px;padding:10px 14px;font-family:monospace;font-size:13px} +.cn-blog-body .rt-quote{padding:6px 14px;border-left:3px solid var(--fg);color:var(--fg-soft);background:var(--bg-sub);border-radius:0 4px 4px 0} +.cn-blog-body .rt-header{color:var(--fg);margin:18px 0 10px 0} .cn-blog-body video,.cn-blog-body audio{max-width:100%;display:block;margin:10px 0} `; const body = `
+ ${renderKindTag('blog')} ${dateStr ? `📅 ${dateStr}` : ''}
${cw ? `
${cw}
` : ''} +
${renderedHtml}
`; return renderClearnetPage({ - title: `${esc(titleText)} — Oasis`, + title: `${titleText} | Oasis`, ogTitle: titleText, ogDescription: plainPreview, extraCss, @@ -2435,48 +3200,216 @@ exports.clearnetBlogView = async ({ msgKey, text, author, authorName, contentWar }); }; -exports.clearnetInhabitantView = async ({ feedId, name, description, image, prefs, items = {}, query = '', filterType = '' }) => { - const { blobUrl: cnBlob, escapeHtml: esc, renderClearnetPage } = require('./clearnet_view'); - const blobAvatarUrl = cnBlob(image); - const avatarSrc = blobAvatarUrl || '/assets/images/default-avatar.png'; - const qrSrc = feedId ? `/qr/${encodeURIComponent(feedId)}` : null; - const displayName = esc(name || 'Anonymous'); - const desc = esc(description || ''); +const cnModuleLabel = (m) => (m && i18n[m.labelKey]) || (m && m.label) || ''; +exports.cnModuleLabel = cnModuleLabel; +const CLEARNET_MODULES = [ + { key: 'audios', label: 'Audios', kind: 'audio', labelKey: 'audiosLabel', prefKey: 'clearnetAudios' }, + { key: 'posts', label: 'Blogs', kind: 'blog', labelKey: 'blogTitle', prefKey: 'clearnetPosts', modulePath: 'blog' }, + { key: 'bookmarks', label: 'Bookmarks', kind: 'bookmark', labelKey: 'bookmarksLabel', prefKey: 'clearnetBookmarks' }, + { key: 'calendars', label: 'Calendars', kind: 'calendar', labelKey: 'calendarsTitle', prefKey: 'clearnetCalendars' }, + { key: 'campaigns', label: 'Campaigns', kind: 'campaign', labelKey: 'campaignsTitle', prefKey: 'clearnetCampaigns' }, + { key: 'documents', label: 'Documents', kind: 'document', labelKey: 'docsLabel', prefKey: 'clearnetDocuments' }, + { key: 'emergencies', label: 'Emergencies', kind: 'emergency', labelKey: 'emergenciesTitle', prefKey: 'clearnetEmergencies' }, + { key: 'events', label: 'Events', kind: 'event', labelKey: 'eventsLabel', prefKey: 'clearnetEvents' }, + { key: 'feed', label: 'Feed', kind: 'feed', labelKey: 'feedTitle', prefKey: 'clearnetFeed' }, + { key: 'housing', label: 'Housing', kind: 'housing', labelKey: 'housingTitle', prefKey: 'clearnetHousing' }, + { key: 'images', label: 'Images', kind: 'image', labelKey: 'imagesLabel', prefKey: 'clearnetImages' }, + { key: 'jobs', label: 'Jobs', kind: 'job', labelKey: 'modulesJobsLabel', prefKey: 'clearnetJobs' }, + { key: 'maps', label: 'Maps', kind: 'map', labelKey: 'mapTitle', prefKey: 'clearnetMaps' }, + { key: 'market', label: 'Market', kind: 'market', labelKey: 'marketTitle', prefKey: 'clearnetMarket' }, + { key: 'podcasts', label: 'Podcasts', kind: 'podcast', labelKey: 'podcastsTitle', prefKey: 'clearnetPodcasts' }, + { key: 'projects', label: 'Projects', kind: 'project', labelKey: 'projectsTitle', prefKey: 'clearnetProjects' }, + { key: 'rooms', label: 'Rooms', kind: 'room', labelKey: 'roomsTitle', prefKey: 'clearnetRooms' }, + { key: 'school', label: 'School', kind: 'course', labelKey: 'schoolTitle', prefKey: 'clearnetSchool' }, + { key: 'shops', label: 'Shops', kind: 'shop', labelKey: 'shopsTitle', prefKey: 'clearnetShops' }, + { key: 'torrents', label: 'Torrents', kind: 'torrent', labelKey: 'torrentsLabel', prefKey: 'clearnetTorrents' }, + { key: 'files', label: 'Files', kind: 'file', labelKey: 'filesLabel', prefKey: 'clearnetFiles' }, + { key: 'videos', label: 'Videos', kind: 'video', labelKey: 'videosLabel', prefKey: 'clearnetVideos' }, + { key: 'wiki', label: 'Wikis', kind: 'wiki', labelKey: 'wikiTitle', prefKey: 'clearnetWiki' } +]; + +exports.CLEARNET_MODULES = CLEARNET_MODULES; + +const CLEARNET_HUB_CSS = ` +.cn-filter-row{display:flex;flex-wrap:wrap;gap:8px;margin:24px 0 12px 0} +.cn-filter-btn{display:inline-block;padding:6px 14px;background:var(--bg-elev);color:var(--fg-soft);border:1px solid var(--border);border-radius:14px;font-size:13px;text-decoration:none;transition:border-color .15s ease,color .15s ease,background .15s ease} +.cn-filter-btn:hover{border-color:var(--fg);color:var(--fg);text-decoration:none} +.cn-filter-btn.active{background:var(--bg-sub);border-color:var(--fg);color:var(--fg);font-weight:600} +.cn-hub-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;margin-top:16px} +.cn-hub-kind{color:var(--fg-dim);font-size:10px;text-transform:uppercase;letter-spacing:2px;font-weight:600} +.cn-hub-card{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;overflow:hidden;transition:border-color .15s ease;color:var(--fg);text-decoration:none} +.cn-hub-card:hover{border-color:var(--fg);text-decoration:none} +.cn-hub-card a{color:var(--fg);text-decoration:none} +.cn-hub-card a:hover{text-decoration:underline} +.cn-hub-player{width:100%;display:block;margin:6px 0;border-radius:6px;background:#000} +video.cn-hub-player{max-height:160px;object-fit:cover} +img.cn-hub-player{max-height:200px;object-fit:cover} +audio.cn-hub-player{background:transparent;height:36px} +.cn-hub-thumb{width:100%;height:140px;object-fit:cover;background:#000;border-bottom:1px solid var(--border)} +.cn-pop-row{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:14px;margin-top:16px} +.cn-pop-thumb{width:100%;height:90px;object-fit:cover;background:#000;border-bottom:1px solid var(--border)} +.cn-pop-card .cn-hub-body{padding:8px 10px;gap:4px} +.cn-pop-card .cn-hub-title{font-size:13px} +@media (max-width:700px){.cn-pop-row{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:42%;overflow-x:auto}} +.cn-hub-body{padding:12px 14px;display:flex;flex-direction:column;gap:6px;min-width:0} +.cn-hub-title{color:var(--fg);font-weight:600;font-size:15px;word-break:break-word} +.cn-hub-snippet{color:var(--fg-soft);font-size:13px;line-height:1.4;word-break:break-word;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden} +.cn-hub-snippet .cn-url{text-decoration:underline} +.cn-hub-card{position:relative} +.cn-hub-cover{position:absolute;inset:0;z-index:1} +.cn-hub-player,.cn-hub-card a:not(.cn-hub-cover){position:relative;z-index:2} +.cn-hub-meta{color:var(--fg-dim);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-top:auto} +.cn-hub-author{color:var(--fg-dim);font-size:11px;word-break:break-all} +.cn-empty-content{background:var(--bg-elev);border:1px dashed var(--border);border-radius:8px;padding:24px;text-align:center;color:var(--fg-dim);font-size:14px} +`; + +const buildClearnetHub = ({ items = {}, filterBase, filterType = '', query = '', showAuthor = false, page = 1 }) => { + const { blobUrl: cnBlob, escapeHtml: esc, renderRichText, renderTagChips, kindLabel, paginateClearnet, renderClearnetPager } = require('./clearnet_view'); const renderHubItem = (modulePath, it) => { const blob = cnBlob(it.image); - const title = esc(it.title || 'Untitled'); - const snippet = esc((it.snippet || '').slice(0, 160)); + const href = clearnetItemHref(modulePath, it.title, it.id); + const title = esc(it.title || i18n.cnUntitled); + const raw = String(it.snippet || ''); + const snippet = renderRichText(raw.slice(0, 300)); const meta = esc(it.meta || ''); const kind = esc(it.kind || ''); - return ` + const mediaSrc = it.media && it.media.blobId ? cnBlob(it.media.blobId) : null; + const player = mediaSrc + ? (it.media.kind === 'video' + ? `` + : it.media.kind === 'image' + ? `` + : ``) + : ''; + const chipRow = [ + meta ? `📅 ${meta}` : '', + ...(Array.isArray(it.details) ? it.details : []).map(d => `${esc(String(d))}`), + it.price ? `${esc(String(it.price))} ECO` : '' + ].filter(Boolean).join(''); + const author = showAuthor && it.feedId + ? `${esc(it.authorName || String(it.feedId).slice(1, 9))}` + : ''; + return `
+ ${blob ? `` : ''}
${kind ? `
${kind}
` : ''} -
${title}
- ${snippet ? `
${snippet}${(it.snippet || '').length > 160 ? '…' : ''}
` : ''} - ${meta ? `
${meta}
` : ''} + ${it.title ? `
${title}
` : ''} + ${author} + ${player} + ${snippet ? `
${snippet}${raw.length > 300 ? '…' : ''}
` : ''} + ${chipRow ? `
${chipRow}
` : ''} + ${renderTagChips(it.tags)}
- `; +
`; }; - const moduleDef = [ - { key: 'shops', label: 'Shops', kind: 'Shop', prefKey: 'clearnetShops' }, - { key: 'jobs', label: 'Jobs', kind: 'Job', prefKey: 'clearnetJobs' }, - { key: 'events', label: 'Events', kind: 'Event', prefKey: 'clearnetEvents' }, - { key: 'projects', label: 'Projects', kind: 'Project', prefKey: 'clearnetProjects' }, - { key: 'posts', label: 'Blogs', kind: 'Blog', prefKey: 'clearnetPosts', modulePath: 'blog' }, - { key: 'audios', label: 'Audios', kind: 'Audio', prefKey: 'clearnetAudios' }, - { key: 'videos', label: 'Videos', kind: 'Video', prefKey: 'clearnetVideos' }, - { key: 'images', label: 'Images', kind: 'Image', prefKey: 'clearnetImages' }, - { key: 'documents', label: 'Documents', kind: 'Document', prefKey: 'clearnetDocuments' }, - { key: 'torrents', label: 'Torrents', kind: 'Torrent', prefKey: 'clearnetTorrents' }, - { key: 'bookmarks', label: 'Bookmarks', kind: 'Bookmark', prefKey: 'clearnetBookmarks' } - ]; + const q = String(query || '').trim().toLowerCase(); + const matches = (it) => !q || [it.title, it.snippet, it.meta, ...(Array.isArray(it.tags) ? it.tags.map(t => `#${t}`) : [])].some(v => String(v || '').toLowerCase().includes(q)); + const allItems = []; + for (const m of CLEARNET_MODULES) { + for (const it of (items[m.key] || [])) { + if (!matches(it)) continue; + allItems.push({ ...it, modulePath: m.modulePath || m.key, kind: kindLabel(m.kind), _moduleKey: m.key }); + } + } + allItems.sort((a, b) => (Number(b.ts) || 0) - (Number(a.ts) || 0)); + const activeFilter = String(filterType || '').toLowerCase(); + const visibleItems = activeFilter ? allItems.filter(it => it._moduleKey === activeFilter) : allItems; + const qs = q ? `&q=${encodeURIComponent(query)}` : ''; + const filterButtons = `
+ ${esc(i18n.all)} (${allItems.length}) + ${CLEARNET_MODULES.filter(m => allItems.some(it => it._moduleKey === m.key)).map(m => { + const count = allItems.filter(it => it._moduleKey === m.key).length; + return `${esc(cnModuleLabel(m))} (${count})`; + }).join('')} +
`; + const paged = paginateClearnet(visibleItems, page); + const pager = renderClearnetPager({ base: filterBase, params: { type: activeFilter, q: query }, page: paged.page, pages: paged.pages, per: paged.per, total: paged.total }); + const sections = visibleItems.length + ? `${filterButtons}

${esc(i18n.cnNetworkContent)} (${visibleItems.length})

${paged.items.map(it => renderHubItem(it.modulePath, it)).join('')}
${pager}` + : (allItems.length ? `${filterButtons}
${esc(i18n.cnCategoryEmpty)}
` : ''); + return { sections, total: allItems.length, visible: visibleItems.length, page: paged.page, pages: paged.pages }; +}; + +exports.CLEARNET_HUB_CSS = CLEARNET_HUB_CSS; + +exports.clearnetHubView = async ({ authors = [], items = {}, tribes = [], filterType = '', query = '', page = 1, tribesPage = 1 }) => { + const { escapeHtml: esc, renderClearnetPage, blobUrl: cnBlob, CLEARNET_PAGER_CSS, renderClearnetPager } = require('./clearnet_view'); + const hub = buildClearnetHub({ items, filterBase: '/c', filterType, query, page }); + const extraCss = CLEARNET_HUB_CSS + CLEARNET_PAGER_CSS + ` +`; + const q = String(query || '').trim().toLowerCase(); + const shownTribes = (filterType || hub.page > 1) ? [] : (tribes || []).filter(t => !q || [t.title, t.description].some(v => String(v || '').toLowerCase().includes(q))); + const ranked = shownTribes.slice().sort((x, y) => (Number(y.content) || 0) - (Number(x.content) || 0)); + const tribePage = slicePage(ranked, tribesPage, 10); + const tribeCard = (t) => { + const img = cnBlob(t.image) || '/c/assets/images/default-tribe.png'; + const href = `/c/tribe/${encodeURIComponent(clearnetSlugFor(t.title, t.id))}`; + return `
${esc(t.title || i18n.cnUntitled)}
${esc(i18n.cnPublicContent)}: ${Number(t.content) || 0}
`; + }; + const tribesMore = renderClearnetPager({ base: '/c', params: { q: query }, page: tribePage.page, pages: tribePage.pages, total: tribePage.total, pageParam: 'tpage' }); + const tribesBlock = ranked.length + ? `

${esc(i18n.cnTribesContent)} (${ranked.length})

${tribePage.items.map(tribeCard).join('')}
${tribesMore}` + : ''; + const body = ` + ${tribesBlock} + ${hub.total ? hub.sections : (tribesBlock ? '' : `
${esc(i18n.cnHubEmpty)}
`)} +`; + return renderClearnetPage({ + title: `${i18n.cnHubTitle} | Oasis`, + ogTitle: `${i18n.cnHubTitle} | Oasis`, + ogDescription: i18n.cnHubDescription, + extraCss, + body, + headerExtra: `` + }); +}; + +exports.clearnetInhabitantView = async ({ feedId, name, description, image, prefs, items = {}, query = '', filterType = '', page = 1 }) => { + const { blobUrl: cnBlob, escapeHtml: esc, renderRichText, renderTagChips, renderClearnetPage, kindLabel, paginateClearnet, renderClearnetPager, CLEARNET_PAGER_CSS } = require('./clearnet_view'); + const blobAvatarUrl = cnBlob(image); + const avatarSrc = blobAvatarUrl || '/assets/images/default-avatar.png'; + const qrSrc = feedId ? `/c/qr/${encodeURIComponent(feedId)}` : null; + const displayName = esc(name && name !== 'Redacted' ? name : feedId); + const desc = renderRichText(description && description !== 'Redacted' ? description : ''); + const renderHubItem = (modulePath, it) => { + const blob = cnBlob(it.image); + const title = esc(it.title || i18n.cnUntitled); + const raw = String(it.snippet || ''); + const snippet = renderRichText(raw.slice(0, 300)); + const meta = esc(it.meta || ''); + const kind = esc(it.kind || ''); + const mediaSrc = it.media && it.media.blobId ? cnBlob(it.media.blobId) : null; + const preview = mediaSrc && it.media.kind === 'image' + ? `` + : ''; + const href = clearnetItemHref(modulePath, it.title, it.id); + const chipRow = [ + meta ? `📅 ${meta}` : '', + ...(Array.isArray(it.details) ? it.details : []).map(d => `${esc(String(d))}`), + it.price ? `${esc(String(it.price))} ECO` : '' + ].filter(Boolean).join(''); + return `
+ + ${blob ? `` : ''} +
+ ${kind ? `
${kind}
` : ''} + ${it.title ? `
${title}
` : ''} + ${preview} + ${snippet ? `
${snippet}${raw.length > 300 ? '…' : ''}
` : ''} + ${chipRow ? `
${chipRow}
` : ''} + ${renderTagChips(it.tags)} +
+
`; + }; + const moduleDef = CLEARNET_MODULES; const allItems = []; for (const m of moduleDef) { for (const it of (items[m.key] || [])) { - allItems.push({ ...it, modulePath: m.modulePath || m.key, kind: m.kind, _moduleKey: m.key }); + allItems.push({ ...it, modulePath: m.modulePath || m.key, kind: kindLabel(m.kind), _moduleKey: m.key }); } } + allItems.sort((a, b) => (Number(b.ts) || 0) - (Number(a.ts) || 0)); const activeFilter = (filterType || '').toLowerCase(); const visibleItems = activeFilter ? allItems.filter(it => it._moduleKey === activeFilter) @@ -2484,18 +3417,20 @@ exports.clearnetInhabitantView = async ({ feedId, name, description, image, pref const totalCount = visibleItems.length; const filterBase = `/c/inhabitant/${encodeURIComponent(feedId)}`; const filterButtons = `
- All (${allItems.length}) - ${moduleDef.filter(m => prefs && prefs[m.prefKey] && (items[m.key] || []).length).map(m => { + ${esc(i18n.all)} (${allItems.length}) + ${moduleDef.filter(m => (items[m.key] || []).length).map(m => { const isActive = activeFilter === m.key; const count = (items[m.key] || []).length; - return `${esc(m.label)} (${count})`; + return `${esc(cnModuleLabel(m))} (${count})`; }).join('')}
`; + const paged = paginateClearnet(visibleItems, page); + const pager = renderClearnetPager({ base: filterBase, params: { type: activeFilter }, page: paged.page, pages: paged.pages, per: paged.per, total: paged.total }); const sections = totalCount - ? `${filterButtons}

Public Content (${totalCount})

${visibleItems.map(it => renderHubItem(it.modulePath, it)).join('')}
` - : (allItems.length ? `${filterButtons}
No content in this category.
` : ''); - const noResults = '
This inhabitant has not published content to Clearnet yet.
'; - const extraCss = ` + ? `${filterButtons}

${esc(i18n.cnPublicContent)} (${totalCount})

${paged.items.map(it => renderHubItem(it.modulePath, it)).join('')}
${pager}` + : (allItems.length ? `${filterButtons}
${esc(i18n.cnCategoryEmpty)}
` : ''); + const noResults = `
${esc(i18n.cnInhabitantEmpty)}
`; + const extraCss = CLEARNET_PAGER_CSS + ` .cn-profile{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start;margin-bottom:24px} .cn-avatar{width:160px;height:160px;border-radius:8px;border:3px solid var(--fg);object-fit:cover;background:#000;flex:0 0 auto} .cn-profile-body{flex:1 1 280px;min-width:0} @@ -2512,16 +3447,26 @@ exports.clearnetInhabitantView = async ({ feedId, name, description, image, pref .cn-hub-kind{color:var(--fg-dim);font-size:10px;text-transform:uppercase;letter-spacing:2px;font-weight:600} .cn-hub-card{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;overflow:hidden;transition:border-color .15s ease;color:var(--fg);text-decoration:none} .cn-hub-card:hover{border-color:var(--fg);text-decoration:none} +.cn-hub-card a{color:var(--fg);text-decoration:none} +.cn-hub-card a:hover{text-decoration:underline} +.cn-hub-player{width:100%;display:block;margin:6px 0;border-radius:6px;background:#000} +video.cn-hub-player{max-height:160px;object-fit:cover} +img.cn-hub-player{max-height:200px;object-fit:cover} +audio.cn-hub-player{background:transparent;height:36px} .cn-hub-thumb{width:100%;height:140px;object-fit:cover;background:#000;border-bottom:1px solid var(--border)} .cn-hub-body{padding:12px 14px;display:flex;flex-direction:column;gap:6px;min-width:0} .cn-hub-title{color:var(--fg);font-weight:600;font-size:15px;word-break:break-word} .cn-hub-snippet{color:var(--fg-soft);font-size:13px;line-height:1.4;word-break:break-word;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden} +.cn-hub-snippet .cn-url{text-decoration:underline} +.cn-hub-card{position:relative} +.cn-hub-cover{position:absolute;inset:0;z-index:1} +.cn-hub-player,.cn-hub-card a:not(.cn-hub-cover){position:relative;z-index:2} .cn-hub-meta{color:var(--fg-dim);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-top:auto} .cn-empty-content{background:var(--bg-elev);border:1px dashed var(--border);border-radius:8px;padding:24px;text-align:center;color:var(--fg-dim);font-size:14px} `; const body = `
- ${displayName} + ${displayName}

${displayName}

${esc(feedId)}
@@ -2532,7 +3477,7 @@ exports.clearnetInhabitantView = async ({ feedId, name, description, image, pref ${totalCount > 0 ? sections : noResults} `; return renderClearnetPage({ - title: `${name || 'Inhabitant'} — Oasis`, + title: `${name || 'Inhabitant'} | Oasis`, ogTitle: name || 'Oasis', ogDescription: description || '', ogImage: blobAvatarUrl, @@ -2587,16 +3532,32 @@ const renderUserSensors = (u, opts = {}) => { items.push(span({ class: 'gpg-line' }, `${i18n.profileGpgChip || 'GPG'}: `, gpgNode)); } if (show('wallet') && (u.ecoAddress || isMe)) { - const wt = u.ecoAddress || (i18n.statsEcoWalletNotConfigured || 'Not configured!'); - const wn = isMe ? a({ href: '/wallet' }, strong(wt)) : strong(wt); - items.push(span({ class: 'ubi-line' }, `${i18n.statsEcoWalletLabel || 'ECOin Wallet'}: `, wn)); + const addressNode = u.ecoAddress + ? span({ class: 'wallet-address', title: i18n.statsEcoWalletLabel }, u.ecoAddress) + : (isMe ? a({ href: '/wallet' }, strong(i18n.statsEcoWalletNotConfigured || 'Not configured!')) : strong(i18n.statsEcoWalletNotConfigured || 'Not configured!')); + items.push(div({ class: 'wallet-line' }, + span({ class: 'wallet-line-head', title: i18n.statsEcoWalletLabel }, span({ class: 'wallet-icon' }, '❄')), + isMe && u.ecoAddress ? a({ href: '/wallet', class: 'wallet-line-link' }, addressNode) : addressNode + )); } - if (show('ubi')) { - items.push(span({ class: 'ubi-line' }, `${i18n.bankUbiThisMonth || 'UBI'}: `, strong(`${Number(u.estimatedUBI || 0).toFixed(6)} ECO`))); - items.push(span({ class: 'ubi-line' }, `${i18n.bankUbiLastClaimed || 'Last claimed'}: `, u.lastClaimedDate ? a({ href: '/transfers?filter=ubi', class: 'user-link' }, new Date(u.lastClaimedDate).toLocaleDateString()) : strong(i18n.bankUbiNeverClaimed || 'Never claimed'))); - items.push(span({ class: 'ubi-line' }, `${i18n.bankUbiTotalClaimed || 'Total claimed'}: `, strong(`${Number(u.totalClaimed || 0).toFixed(6)} ECO`))); + const phoneVisible = isMe || prefs.phone === 'whole' || prefs.phone === undefined || (prefs.phone === 'mutuals' && u.mutual === true); + if (phoneOn() && prefs.phone !== 'off' && phoneVisible && u.id && !sharedState.isPubId(u.id)) { + const number = require('../models/phone_number').phoneNumberOf(u.id); + const dnd = isMe ? !!(getConfig().phone || {}).dnd : prefs.phoneDnd === true; + if (number) items.push(div({ class: 'wallet-line phone-number-line' }, + span({ class: 'wallet-line-head', title: i18n.profileVisibilityPhone }, span({ class: 'phone-number-icon' }, '✆')), + isMe || dnd + ? span({ class: 'wallet-address phone-number-value', title: isMe ? i18n.profileVisibilityPhone : i18n.phoneRecipientRefuses }, number) + : renderPhoneCallForm(u.id, span({ class: 'wallet-address phone-number-value' }, number), i18n.phoneCallButton), + renderStateChip(dnd ? 'hidden' : 'mutuals', null, String(dnd ? i18n.phoneDnd : i18n.phoneOpen).toUpperCase()), + isMe && opts.dndToggle !== false && process.env.OASIS_MOBILE !== '1' ? form({ method: 'POST', action: '/phone/dnd', class: 'phone-dnd-form' }, + input({ type: 'hidden', name: 'dnd', value: dnd ? '0' : '1' }), + button({ type: 'submit', class: 'tribe-action-btn' }, String(dnd ? i18n.phoneOpen : i18n.phoneDnd).toUpperCase()) + ) : null + )); } const sensorsBox = items.length ? div({ class: 'profile-sensors-box' }, ...items) : null; + if (opts.sensorsOnly) return sensorsBox ? [sensorsBox] : []; const larpNode = (show('larpSign') && u.larpHouse && u.larpHouse.key) ? a({ href: `/larp/${u.larpHouse.key}`, class: 'larp-sign-block', title: u.larpHouse.name }, img({ src: u.larpHouse.image || '/assets/larp/images/default.jpg', alt: u.larpHouse.name, class: 'larp-sign-large' })) : null; @@ -2605,13 +3566,12 @@ const renderUserSensors = (u, opts = {}) => { const path = `/c/inhabitant/${encodeURIComponent(u.id)}`; return div({ class: 'profile-reach' }, renderReachChip(true, i18n, path), - renderInviteQrCard({ qrDataUrl: `/qr-clearnet/${encodeURIComponent(u.id)}` }), - isMe ? form({ method: 'POST', action: '/profile/clearnet-toggle', class: 'profile-reach-toggle' }, button({ type: 'submit', class: 'btn' }, i18n.profileSwitchToOasis || 'Return to Oasis')) : null + renderInviteQrCard({ qrDataUrl: `/qr-clearnet/${encodeURIComponent(u.id)}` }) ); })() : null; const contentNode = opts.excludeContent ? null : renderContentStats(u.stats, i18n); - return [fediverseNode, opts.relationshipNode || null, sensorsBox, larpNode, reachNode, contentNode].filter(Boolean); + return [opts.relationshipNode || null, sensorsBox, fediverseNode, larpNode, reachNode, contentNode].filter(Boolean); }; exports.renderUserSensors = renderUserSensors; @@ -2658,9 +3618,11 @@ exports.authorView = async ({ clearnet: rawPrefs.clearnet === true, fediverse: rawPrefs.fediverse === true, fediverseHandle: typeof rawPrefs.fediverseHandle === 'string' ? rawPrefs.fediverseHandle : '', - gpg: rawPrefs.gpg === true + gpg: rawPrefs.gpg === true, + phone: rawPrefs.phone === 'mutuals' || rawPrefs.phone === 'off' ? rawPrefs.phone : 'whole', + phoneDnd: rawPrefs.phoneDnd === true }; - const clearnetSubKeys = ['clearnetShops','clearnetJobs','clearnetEvents','clearnetProjects','clearnetPosts','clearnetAudios','clearnetVideos','clearnetImages','clearnetDocuments','clearnetTorrents','clearnetBookmarks']; + const clearnetSubKeys = CLEARNET_MODULES.map(m => m.prefKey); const anySubClearnet = clearnetSubKeys.some(k => rawPrefs[k] === true); prefs.clearnet = prefs.clearnet || anySubClearnet; const showField = (key) => prefs[key]; @@ -2676,7 +3638,8 @@ exports.authorView = async ({ isMe: isOwnProfile, fediverseConfigured, prefs, id: feedId, karmaScore, carbonGrams, deviceSource, activityBucket: lastActivityBucket, gpgFingerprint, ecoAddress, estimatedUBI, lastClaimedDate, totalClaimed, - larpHouse, stats + larpHouse, stats, + mutual: !!(relationship && relationship.following && relationship.followsMe) }, { relationshipNode: div({ class: "profile-side-relationship" }, relationshipBlock) }); const sideColumn = div({ class: "tribe-side profile-side" }, @@ -2691,13 +3654,15 @@ exports.authorView = async ({ ) : null, feedId ? renderInviteQrCard({ qrDataUrl: `/qr/${encodeURIComponent(feedId)}` }) : null, description !== "" - ? div({ class: "profile-side-description", innerHTML: sanitizeHtml(markdown(description)) }) + ? div({ class: "profile-side-description", [RAW_HTML]: sanitizeHtml(renderStyledHtml(description)) }) : null, ...userSensors, div({ class: "profile-side-actions" }, - isOwnProfile ? a({ href: `/profile/edit`, class: "filter-btn" }, i18n.editProfile) : null, - a({ href: `/likes/${encodeURIComponent(feedId)}`, class: "filter-btn" }, i18n.viewLikes), - !isOwnProfile ? a({ href: `/pm?recipients=${encodeURIComponent(feedId)}`, class: "filter-btn" }, i18n.pmCreateButton) : null + isOwnProfile ? a({ href: `/profile/edit`, class: "filter-btn" }, `✎ ${i18n.editProfile}`) : null, + isOwnProfile ? a({ href: "/supporters", class: "filter-btn" }, `♥ ${i18n.viewSupporters}`) : null, + a({ href: `/likes/${encodeURIComponent(feedId)}`, class: "filter-btn" }, `⟳ ${i18n.viewLikes}`), + !isOwnProfile ? a({ href: `/pm?recipients=${encodeURIComponent(feedId)}`, class: "filter-btn" }, `✉ ${i18n.pmCreateButton}`) : null, + !isOwnProfile ? renderCallButton(feedId, { pam: true }) : null ) ); @@ -2714,25 +3679,40 @@ exports.authorView = async ({ videos: new Set(['video']), images: new Set(['image']), documents: new Set(['document']), - torrents: new Set(['torrent']) + torrents: new Set(['torrent']), + files: new Set(['file']), + bookmarks: new Set(['bookmark']), + podcasts: new Set(['podcast']), + school: new Set(['schoolCourse']), + market: new Set(['market']), + feed: new Set(['feed']), + wiki: new Set(['wikiPage']) }; const moduleDef = [ - { key: 'shops', label: i18n.profileClearnetShopsLabel || 'Shops' }, - { key: 'jobs', label: i18n.profileClearnetJobsLabel || 'Jobs' }, - { key: 'events', label: i18n.profileClearnetEventsLabel || 'Events' }, - { key: 'projects', label: i18n.profileClearnetProjectsLabel || 'Projects' }, - { key: 'posts', label: i18n.profileClearnetPostsLabel || 'Blogs' }, { key: 'audios', label: i18n.profileClearnetAudiosLabel || 'Audios' }, - { key: 'videos', label: i18n.profileClearnetVideosLabel || 'Videos' }, - { key: 'images', label: i18n.profileClearnetImagesLabel || 'Images' }, + { key: 'posts', label: i18n.profileClearnetPostsLabel || 'Blogs' }, + { key: 'bookmarks', label: i18n.profileClearnetBookmarksLabel || 'Bookmarks' }, { key: 'documents', label: i18n.profileClearnetDocumentsLabel || 'Documents' }, - { key: 'torrents', label: i18n.profileClearnetTorrentsLabel || 'Torrents' } + { key: 'events', label: i18n.profileClearnetEventsLabel || 'Events' }, + { key: 'feed', label: i18n.profileClearnetFeedLabel || 'Feed' }, + { key: 'images', label: i18n.profileClearnetImagesLabel || 'Images' }, + { key: 'jobs', label: i18n.profileClearnetJobsLabel || 'Jobs' }, + { key: 'market', label: i18n.profileClearnetMarketLabel || 'Market' }, + { key: 'podcasts', label: i18n.profileClearnetPodcastsLabel || 'Podcasts' }, + { key: 'projects', label: i18n.profileClearnetProjectsLabel || 'Projects' }, + { key: 'school', label: i18n.profileClearnetSchoolLabel || 'School' }, + { key: 'shops', label: i18n.profileClearnetShopsLabel || 'Shops' }, + { key: 'torrents', label: i18n.profileClearnetTorrentsLabel || 'Torrents' }, + { key: 'files', label: i18n.profileClearnetFilesLabel || 'Files' }, + { key: 'videos', label: i18n.profileClearnetVideosLabel || 'Videos' }, + { key: 'wiki', label: i18n.profileClearnetWikiLabel || 'Wikis' } ]; const enabledKeys = moduleDef.filter(m => rawPrefs[`profile${m.key.charAt(0).toUpperCase() + m.key.slice(1)}`] === true).map(m => m.key); if (enabledKeys.length > 0) { const allowedTypes = new Set(); for (const k of enabledKeys) for (const t of keyToTypes[k]) allowedTypes.add(t); - const authorActions = allActions.filter(a => a && a.author === feedId && allowedTypes.has(a.type)); + const channelsWithEpisodes = new Set(allActions.filter(a => a && a.type === 'podcastEpisode' && a.content && a.content.channel).map(a => String(a.content.channel))); + const authorActions = allActions.filter(a => a && a.author === feedId && allowedTypes.has(a.type) && (a.type !== 'podcast' || channelsWithEpisodes.has(String(a.rootId || a.id)) || channelsWithEpisodes.has(String(a.id)))); const counts = {}; for (const k of enabledKeys) counts[k] = 0; for (const a of authorActions) { @@ -2757,11 +3737,10 @@ exports.authorView = async ({ ? authorActions.filter(a => keyToTypes[activeFilter].has(a.type)) : authorActions; visible.sort((a, b) => (b.ts || 0) - (a.ts || 0)); - const limited = visible.slice(0, 50); const { renderActionCards } = require('./activity_view'); mainColumnContent.push(filterRow); mainColumnContent.push(div({ class: 'feed-container profile-module-section' }, - renderActionCards(limited, (config.keys && config.keys.id) ? config.keys.id : '', allActions || limited, spreadMap instanceof Map ? spreadMap : new Map()) + renderActionCards(visible, (config.keys && config.keys.id) ? config.keys.id : '', allActions || visible, spreadMap instanceof Map ? spreadMap : new Map(), { paged: true }) )); } } @@ -2779,8 +3758,7 @@ exports.authorView = async ({ const hasDocument = Array.isArray(allActions) && allActions.some(a => a && a.author === feedId && a.type === 'document'); if (hasDocument) { html += ` - - + `; } return html; @@ -2911,7 +3889,7 @@ exports.commentView = async ( br(), label( { for: "blob" }, - i18n.blogImage || "Upload media (max-size: 50MB)" + i18n.blogImage || "Upload media (max-size: 75MB)" ), input({ type: "file", id: "blob", name: "blob" }), br(), @@ -2925,7 +3903,7 @@ exports.commentView = async ( const renderMessage = (msg) => { const content = lodash.get(msg, "value.content", {}); const authorId = msg.value.author || "Anonymous"; - const createdAt = new Date(msg.value.timestamp).toLocaleString(); + const createdAt = moment(msg.value.timestamp).format("YYYY/MM/DD HH:mm"); const mentionsText = content.text || ''; const isTribe = content.type === 'tribe-content'; const visitUrl = isTribe @@ -2952,11 +3930,11 @@ const renderMessage = (msg) => { div({ class: 'card-body' }, div({ class: 'card-section' }, badge, - p({ class: 'post-text' }, ...renderUrl(mentionsText || '[No content]')) + p({ class: 'post-text' }, ...renderStyledText(mentionsText || '[No content]')) ) ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${createdAt} ${i18n.performed || ''} `), + span({ class: 'date-link' }, `${createdAt}`), userLink(authorId) ) ); @@ -2964,18 +3942,50 @@ const renderMessage = (msg) => { -exports.privateView = async (messagesInput, filter, decrypted = null, notice = '') => { - const noticeText = notice === 'unavailable' - ? (i18n.fileShareUnavailable || 'This file is not available right now. Try again later.') - : notice === 'badkey' - ? (i18n.pmCrypterBadKey || 'Your shared key is incorrect!') - : '' +const { botOf: inboxBotOf, INBOX_BOTS } = require('../models/pm_model'); +const INBOX_BOT_ORDER = Object.keys(INBOX_BOTS).concat(['reminders']); +const inboxBotLabel = (bot) => ({ + blogs: i18n.pmBotBlogs, jobs: i18n.pmBotJobs, projects: i18n.pmBotProjects, market: i18n.pmBotMarket, shops: i18n.pmBotShops, + political: i18n.pmBotPolitical, banking: i18n.pmBotBanking, school: i18n.pmBotSchool, industry: i18n.pmBotIndustry, + housing: i18n.pmBotHousing, wiki: i18n.pmBotWiki, emergencies: i18n.pmBotEmergencies, podcasts: i18n.pmBotPodcasts, + campaigns: i18n.pmBotCampaigns, logistics: i18n.pmBotLogistics, reminders: i18n.privateReminders +})[bot] || bot; +exports.INBOX_BOT_ORDER = INBOX_BOT_ORDER; +exports.inboxBotLabel = inboxBotLabel; + +exports.privateView = async (messagesInput, filter, decrypted = null, notice = '', q = '') => { const messagesRaw = Array.isArray(messagesInput) ? messagesInput : messagesInput.messages - const messages = (messagesRaw || []).filter(m => m && m.key && m.value && m.value.content && m.value.content.type === 'post' && m.value.content.private === true) + const listTitles = (!Array.isArray(messagesInput) && messagesInput.listTitles && typeof messagesInput.listTitles === 'object') ? messagesInput.listTitles : {} + const readKeys = new Set((!Array.isArray(messagesInput) && Array.isArray(messagesInput.readKeys)) ? messagesInput.readKeys.map(String) : []) + const archivedKeys = new Set((!Array.isArray(messagesInput) && Array.isArray(messagesInput.archivedKeys)) ? messagesInput.archivedKeys.map(String) : []) + const mutedBots = new Set((!Array.isArray(messagesInput) && Array.isArray(messagesInput.mutedBots)) ? messagesInput.mutedBots.map(String) : []) + const botFilter = (!Array.isArray(messagesInput) && typeof messagesInput.bot === 'string' && INBOX_BOT_ORDER.includes(messagesInput.bot)) ? messagesInput.bot : '' + const sortMode = (!Array.isArray(messagesInput) && messagesInput.sort === 'recent') ? 'recent' : 'thread' + const isMutedMsg = m => { const b = inboxBotOf(m?.value?.content); return !!(b && mutedBots.has(b)) } + const messages = (messagesRaw || []).filter(m => m && m.key && m.value && m.value.content && m.value.content.type === 'post' && m.value.content.private === true).filter(m => !isMutedMsg(m)) + const qNorm = String(q || '').trim().toLowerCase() + const qMatch = (m) => { + if (!qNorm) return true + const c = m.value.content || {} + const ids = [c.from, m.value.author, ...(Array.isArray(c.to) ? c.to : [])].filter(Boolean) + return [c.subject, c.text, ...ids, ...ids.map(id => userLinkLabel(id))] + .some(v => String(v || '').toLowerCase().includes(qNorm)) + } const userId = await getUserId() const isSent = m => (m?.value?.author === userId) || (m?.value?.content?.from === userId) + const isList = m => !!(m?.value?.content?.list) const isToUser = m => Array.isArray(m?.value?.content?.to) && m.value.content.to.includes(userId) + const isReminder = m => { + const s = String(m?.value?.content?.subject || '') + return /^(Task Reminder:|Calendar Reminder:)/i.test(s) + } + const isNotification = m => !!inboxBotOf(m?.value?.content) + const isSelfNotice = m => isSent(m) && isToUser(m) && isNotification(m) + const isArchived = m => archivedKeys.has(String(m?.key)) + const toUserKeys = new Set(messages.filter(isToUser).map(m => String(m.key))) + const isUnreadKey = (key) => toUserKeys.has(String(key)) && !readKeys.has(String(key)) && !archivedKeys.has(String(key)) + const firstHref = (str) => { const m = String(str || '').match(/\]\((\/[^)\s]+)\)/); return m ? m[1] : null } const linkAuthor = (id) => userLink(id) @@ -2986,15 +3996,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' shopProduct: (id) => `/shops/product/${encodeURIComponent(id)}` } - const clickableCardProps = (href, extraClass = '') => { - const props = { class: `pm-card ${extraClass}` } - if (href) { - props.onclick = `window.location='${href}'` - props.tabindex = 0 - props.onkeypress = `if(event.key==='Enter') window.location='${href}'` - } - return props - } + const titleLink = (href, text) => href ? a({ href, class: 'pm-title-link' }, text) : text const chip = (txt) => span({ class: 'chip' }, txt) @@ -3033,7 +4035,10 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' ), tr( td({ class: 'card-label' }, i18n.pmSubjectLabel || 'Subject:'), - td({ class: 'card-value' }, subject || i18n.pmNoSubject || '(no subject)') + td({ class: 'card-value pm-subject-cell' }, + span(subject || i18n.pmNoSubject || '(no subject)'), + isUnreadKey(msgKey) ? span({ class: 'pm-exposition-chip pm-unread-chip' }, span({ class: 'pm-exposition-text' }, i18n.inboxUnreadChip)) : null + ) ), ecoChip ? tr( @@ -3053,18 +4058,24 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' } function actions({ key, replyId, subjectRaw, text, extra = null }) { - const stop = { onclick: 'event.stopPropagation()' } const subjectReply = /^(\s*RE:\s*)/i.test(subjectRaw || '') ? (subjectRaw || '') : `RE: ${subjectRaw || ''}` const isSelf = replyId === userId return div({ class: 'pm-actions' }, - isSelf ? null : form({ method: 'GET', action: '/pm', class: 'pm-action-form', ...stop }, + isSelf ? null : form({ method: 'GET', action: '/pm', class: 'pm-action-form' }, input({ type: 'hidden', name: 'recipients', value: replyId }), input({ type: 'hidden', name: 'subject', value: subjectReply }), input({ type: 'hidden', name: 'quote', value: text || '' }), button({ type: 'submit', class: 'pm-btn reply-btn' }, i18n.pmReply.toUpperCase()) ), + isSelf ? null : renderCallButton(replyId, { upper: true, cls: 'pm-btn' }), extra || null, - form({ method: 'POST', action: `/inbox/delete/${encodeURIComponent(key)}`, class: 'pm-action-form', ...stop }, + toUserKeys.has(String(key)) && !archivedKeys.has(String(key)) ? form({ method: 'POST', action: `/inbox/${readKeys.has(String(key)) ? 'unread' : 'read'}/${encodeURIComponent(key)}`, class: 'pm-action-form' }, + button({ type: 'submit', class: 'pm-btn read-btn' }, String(readKeys.has(String(key)) ? i18n.inboxMarkUnread : i18n.inboxMarkRead).toUpperCase()) + ) : null, + toUserKeys.has(String(key)) ? form({ method: 'POST', action: `/inbox/${archivedKeys.has(String(key)) ? 'unarchive' : 'archive'}/${encodeURIComponent(key)}`, class: 'pm-action-form' }, + button({ type: 'submit', class: 'pm-btn archive-btn' }, String(archivedKeys.has(String(key)) ? i18n.inboxUnarchive : i18n.inboxArchive).toUpperCase()) + ) : null, + form({ method: 'POST', action: `/inbox/delete/${encodeURIComponent(key)}`, class: 'pm-action-form' }, button({ type: 'submit', class: 'pm-btn delete-btn danger-btn' }, i18n.privateDelete.toUpperCase()) ) ) @@ -3081,7 +4092,9 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' } function threadId(m) { - return canonicalSubject(m?.value?.content?.subject || '') + '||' + participantsKey(m) + const c = m?.value?.content || {} + if (c.list) return canonicalSubject(c.subject || '') + '||list:' + String(c.thread || c.list) + return canonicalSubject(c.subject || '') + '||' + participantsKey(m) } function threadLevel(s) { @@ -3135,7 +4148,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' flushQuote() const mdLinks = [] const masked = parts.join('
') - .replace(/\[([^\]\n]{1,120})\]\((https?:\/\/[^)\s]+|\/[^)\s]+)\)/g, (match, label, href) => { + .replace(/\[([^\]\n]{1,120})\]\((https?:\/\/[^)\s]+|\/(?![\/\\])[^)\s]*)\)/g, (match, label, href) => { const idx = mdLinks.length const safeHref = String(href).replace(/"/g, '%22') const ext = /^https?:/.test(href) ? ' target="_blank" rel="noopener noreferrer"' : '' @@ -3143,7 +4156,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' return `\u0000MD${idx}\u0000` }) return masked - .replace(/(@[a-zA-Z0-9/+._=-]+\.ed25519)/g, (match, id) => `${userLinkLabel(id)}`) + .replace(/(@[a-zA-Z0-9/+._=-]+\.ed25519)/g, (match, id) => `${escapeHtml(userLinkLabel(id))}`) .replace(/\/jobs\/([%A-Za-z0-9/+._=-]+\.sha256)/g, (match, id) => `${match}`) .replace(/\/projects\/([%A-Za-z0-9/+._=-]+\.sha256)/g, (match, id) => `${match}`) .replace(/\/market\/([%A-Za-z0-9/+._=-]+\.sha256)/g, (match, id) => `${match}`) @@ -3151,7 +4164,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' .replace(/\/ai\/ask\?[^\s<"]+/g, (match) => `${match}`) .replace(/\/tribe\/([%A-Za-z0-9/+._=-]+\.sha256)(\?section=[a-zA-Z]+)?/g, (match) => `${match}`) .replace(/\/larp\/([a-zA-Z]+)/g, (match) => `${match}`) - .replace(/(? `${match}`) + .replace(/(? `${match}`) .replace(/(https?:\/\/[^\s<"]+)/g, (match) => `${match}`) .replace(/\u0000MD(\d+)\u0000/g, (m, i) => mdLinks[Number(i)] !== undefined ? mdLinks[Number(i)] : m) } @@ -3166,24 +4179,39 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' const inboxSet = new Set() for (const arr of Object.values(threads)) { for (const m of arr) { - if (!isSent(m) && isToUser(m)) inboxSet.add(m) + if ((!isSent(m) || isSelfNotice(m)) && isToUser(m) && !isArchived(m)) inboxSet.add(m) } } - const isReminder = m => { - const s = String(m?.value?.content?.subject || '') - return /^(Task Reminder:|Calendar Reminder:)/i.test(s) - } + const received = Array.from(inboxSet) + const archivedMsgs = messages.filter(isArchived) + const notifications = messages.filter(m => isNotification(m) && !isArchived(m) && (!isSent(m) || isReminder(m) || isSelfNotice(m))) + const mailing = messages.filter(m => isList(m) && !isArchived(m)) + const botCounts = {} + for (const m of notifications) { const b = inboxBotOf(m.value.content); if (b) botCounts[b] = (botCounts[b] || 0) + 1 } + const botsPresent = INBOX_BOT_ORDER.filter(b => botCounts[b] > 0) - const data = - filter === 'sent' ? messages.filter(m => isSent(m) && !isReminder(m)) : - filter === 'reminders' ? messages.filter(isReminder) : - filter === 'inbox' ? Array.from(inboxSet).filter(m => !isReminder(m)) : - messages + const data = ( + filter === 'sent' ? messages.filter(m => isSent(m) && !isReminder(m) && !isSelfNotice(m) && !isArchived(m)) : + filter === 'reminders' ? messages.filter(m => isReminder(m) && !isArchived(m)) : + filter === 'pms' ? received.filter(m => !isNotification(m) && !isList(m)) : + filter === 'notifications' ? (botFilter ? notifications.filter(m => inboxBotOf(m.value.content) === botFilter) : notifications) : + filter === 'mailing' ? mailing : + filter === 'archived' ? archivedMsgs : + filter === 'inbox' ? received.filter(m => !isReminder(m)) : + messages.filter(m => !isArchived(m)) + ).filter(qMatch) - const inboxCount = Array.from(inboxSet).filter(m => !isReminder(m)).length - const sentCount = messages.filter(m => isSent(m) && !isReminder(m)).length - const reminderCount = messages.filter(isReminder).length + const inboxCount = received.filter(m => !isReminder(m)).length + const sentCount = messages.filter(m => isSent(m) && !isReminder(m) && !isSelfNotice(m) && !isArchived(m)).length + const pmCount = received.filter(m => !isNotification(m) && !isList(m)).length + const notifCount = notifications.length + const mailingCount = mailing.length + const archivedCount = archivedMsgs.length + const emptyInbox = messages.length === 0 && !qNorm + const shownKeys = data.map(m => String(m.key)) + const unreadShown = shownKeys.filter(k => isUnreadKey(k)) + const bulkDeletable = filter === 'notifications' || filter === 'reminders' || filter === 'archived' const sorted = [...data].sort((a, b) => { const ta = threadId(a) @@ -3203,9 +4231,9 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' const jobId = pickLink(text, 'job') const href = jobId ? hrefFor.job(jobId) : null return div( - clickableCardProps(href, `job-notification thread-level-0`), + { class: 'pm-card job-notification thread-level-0' }, headerLine({ sentAt, from, toLinks, subject: titleH, msgKey: key, msgSize }), - h2({ class: 'pm-title' }, `${icon} ${i18n.pmBotJobs} · ${titleH}`), + h2({ class: 'pm-title' }, `${icon} ${i18n.pmBotJobs} · `, titleLink(href, titleH)), p( i18n.pmInhabitantWithId, ' ', linkAuthor(from), ' ', @@ -3227,9 +4255,9 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' const projectId = pickLink(text, 'project') const href = projectId ? hrefFor.project(projectId) : null return div( - clickableCardProps(href, `project-${isFollow ? 'follow' : 'unfollow'}-notification thread-level-0`), + { class: `pm-card project-${isFollow ? 'follow' : 'unfollow'}-notification thread-level-0` }, headerLine({ sentAt, from, toLinks, subject: titleH, msgKey: key, msgSize }), - h2({ class: 'pm-title' }, `${icon} ${i18n.pmBotProjects} · ${titleH}`), + h2({ class: 'pm-title' }, `${icon} ${i18n.pmBotProjects} · `, titleLink(href, titleH)), p( i18n.pmInhabitantWithId, ' ', userLink(from), @@ -3254,9 +4282,9 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' const botLabel = isShop ? i18n.pmBotShops : i18n.pmBotMarket const soldTitle = isShop ? (i18n.inboxShopSoldTitle || 'Product Sold') : i18n.inboxMarketItemSoldTitle return div( - clickableCardProps(href, (isShop ? 'shop-sold-notification' : 'market-sold-notification') + ' thread-level-0'), + { class: `pm-card ${isShop ? 'shop-sold-notification' : 'market-sold-notification'} thread-level-0` }, headerLine({ sentAt, from, toLinks, subject: soldTitle, msgKey: key, msgSize }), - h2({ class: 'pm-title' }, `${icon} ${botLabel} · ${soldTitle}`), + h2({ class: 'pm-title' }, `${icon} ${botLabel} · `, titleLink(href, soldTitle)), p( i18n.pmYourItem, ' ', href ? a({ class: isShop ? 'shop-link' : 'market-link', href }, `"${itemTitle}"`) : `"${itemTitle}"`, @@ -3275,9 +4303,9 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' const projectId = content.meta?.projectId ?? pickLink(text, 'project') const href = projectId ? hrefFor.project(projectId) : null return div( - clickableCardProps(href, 'project-pledge-notification thread-level-0'), + { class: 'pm-card project-pledge-notification thread-level-0' }, headerLine({ sentAt, from, toLinks, subject: i18n.inboxProjectPledgedTitle, msgKey: key, msgSize }), - h2({ class: 'pm-title' }, `💚 ${i18n.pmBotProjects} · ${i18n.inboxProjectPledgedTitle}`), + h2({ class: 'pm-title' }, `💚 ${i18n.pmBotProjects} · `, titleLink(href, i18n.inboxProjectPledgedTitle)), p( i18n.pmInhabitantWithId, ' ', linkAuthor(from), ' ', @@ -3297,11 +4325,12 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' TRIBE_GOV: i18n.politicalBotTribeTitle || 'A new government cycle has begun in one of your Tribes.' } const title = titleMap[subjectU] || (i18n.pmBotPolitical || 'PoliticalBot') + const href = firstHref(text) return div( { class: 'pm-card political-bot-notification thread-level-0' }, headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), - h2({ class: 'pm-title' }, `🏛️ ${i18n.pmBotPolitical} · ${title}`), - div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(text || '')) }), + h2({ class: 'pm-title' }, `🏛️ ${i18n.pmBotPolitical} · `, href ? a({ href, class: 'pm-title-link' }, title) : title), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), actions({ key, replyId: from, subjectRaw: title, text }) ) } @@ -3320,7 +4349,157 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' { class: 'pm-card industry-bot-notification thread-level-0' }, headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), h2({ class: 'pm-title' }, `🏭 ${i18n.pmBotIndustry || 'IndustryBot'} · ${title}`), - div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(text || '')) }), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function BankingBotCard({ subjectU, sentAt, from, toLinks, text, key, msgSize }) { + const title = subjectU === 'BANKING_UBI_PAID' + ? (i18n.bankingBotUbiPaidTitle || 'You have received your UBI.') + : subjectU === 'BANKING_UBI_AVAILABLE' + ? (i18n.bankingBotUbiAvailableTitle || 'You can claim your UBI.') + : subjectU === 'BANKING_CONFIRM_PENDING' + ? (i18n.bankingBotConfirmTitle || 'You have transfers awaiting your confirmation.') + : (i18n.bankingBotPaymentTitle || 'You have received a payment.') + const href = firstHref(text) + return div( + { class: 'pm-card banking-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `💰 ${i18n.pmBotBanking || 'BankingBot'} · `, href ? a({ href, class: 'pm-title-link' }, title) : title), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function SchoolBotCard({ subjectU, sentAt, from, toLinks, text, key, msgSize }) { + const titleMap = { + SCHOOL_ENROLLED: i18n.schoolBotEnrolledTitle || 'A new student has enrolled in your course.', + SCHOOL_INVITED: i18n.schoolBotInvitedTitle || 'You have been invited to a course.', + SCHOOL_ADMITTED: i18n.schoolBotAdmittedTitle || 'You have been admitted to a course.', + SCHOOL_CERTIFICATE: i18n.schoolBotCertificateTitle || 'You have received a certificate.', + SCHOOL_PASSED: i18n.schoolBotPassedTitle || 'A student has passed your course.', + SCHOOL_LESSON_NEW: i18n.schoolBotLessonTitle || 'A new lesson has been published.' + } + const title = titleMap[subjectU] || (i18n.pmBotSchool || 'EducaBot') + return div( + { class: 'pm-card school-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `🎓 ${i18n.pmBotSchool || 'EducaBot'} · ${title}`), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function EmergencyBotCard({ subjectU, sentAt, from, toLinks, text, key, msgSize }) { + const titleMap = { + EMERGENCY_UPDATED: i18n.emergencyBotUpdatedTitle || 'An emergency you follow has an update.', + EMERGENCY_RESOLVED: i18n.emergencyBotResolvedTitle || 'An emergency you follow has been resolved.' + } + const title = titleMap[subjectU] || (i18n.pmBotEmergencies || 'EmergencyBot') + return div( + { class: 'pm-card emergency-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `🚨 ${i18n.pmBotEmergencies || 'EmergencyBot'} · ${title}`), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function MailingCard({ sentAt, from, toLinks, subject, text, key, msgSize, content }) { + const listId = String(content.list || '') + const info = listTitles[listId] || null + const listHref = `/mailing/${encodeURIComponent(listId)}` + const listName = info && info.title ? info.title : (i18n.mailingTitle || 'Mailing list') + const subjectReply = /^(\s*RE:\s*)/i.test(subject || '') ? (subject || '') : `RE: ${subject || ''}` + const canReply = !!info && info.status === 'ACTIVE' + const replyForm = canReply + ? form({ method: 'POST', action: `${listHref}/message`, class: 'pm-action-form mailing-inbox-reply' }, + input({ type: 'hidden', name: 'thread', value: String(content.thread || content.mid || '') }), + input({ type: 'hidden', name: 'subject', value: subjectReply }), + input({ type: 'hidden', name: 'returnTo', value: '/inbox?filter=mailing' }), + textarea({ name: 'text', rows: 2, maxlength: String(require('../backend/long_text').TEXT_CAP), required: true, placeholder: i18n.mailingReplyPlaceholder || 'Reply to the list...', class: 'mailing-inbox-reply-text' }), + button({ type: 'submit', class: 'pm-btn reply-btn' }, String(i18n.mailingReplyToList || 'Reply to list').toUpperCase()) + ) + : null + return div( + { class: 'pm-card mailing-pm' }, + headerLine({ sentAt, from, toLinks, subject, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, '✉ ', a({ href: listHref, class: 'mailing-inbox-list' }, listName)), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: userId, subjectRaw: subject, text, extra: replyForm }) + ) + } + + function CampaignBotCard({ subjectU, sentAt, from, toLinks, text, key, msgSize }) { + const titleMap = { + CAMPAIGN_UPDATED: i18n.campaignBotUpdatedTitle || 'A campaign you follow has an update.', + CAMPAIGN_ACHIEVED: i18n.campaignBotAchievedTitle || 'A campaign you follow has reached its goal.', + CAMPAIGN_RAISED: i18n.campaignBotRaisedTitle || 'A campaign you signed has been raised to Parliament.' + } + const title = titleMap[subjectU] || (i18n.pmBotCampaigns || 'CampaignBot') + return div( + { class: 'pm-card campaign-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `✍ ${i18n.pmBotCampaigns || 'CampaignBot'} · ${title}`), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function LogisticsBotCard({ subjectU, sentAt, from, toLinks, text, key, msgSize }) { + const titleMap = { + LOGISTICS_UPDATED: i18n.logisticsBotUpdatedTitle || 'A route you booked has changed.', + LOGISTICS_CLOSED: i18n.logisticsBotClosedTitle || 'A route you booked has been closed.', + LOGISTICS_BOOKED: i18n.logisticsBotBookedTitle || 'Someone booked your route.', + LOGISTICS_CANCELLED: i18n.logisticsBotCancelledTitle || 'A booking on your route was cancelled.', + LOGISTICS_CONFIRMED: i18n.logisticsBotConfirmedTitle || 'Your booking was confirmed: the package is on its way.', + LOGISTICS_REJECTED: i18n.logisticsBotRejectedTitle || 'Your booking was rejected.', + LOGISTICS_DELIVERED: i18n.logisticsBotDeliveredTitle || 'Your package has arrived.' + } + const title = titleMap[subjectU] || (i18n.pmBotLogistics || 'LogisticsBot') + return div( + { class: 'pm-card logistics-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `${i18n.pmBotLogistics || 'LogisticsBot'} · ${title}`), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function BlogBotCard({ sentAt, from, toLinks, text, key, msgSize }) { + const title = i18n.blogBotNewTitle + return div( + { class: 'pm-card blog-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `📝 ${i18n.pmBotBlogs} · `, titleLink(firstHref(text), title)), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function PodcastBotCard({ sentAt, from, toLinks, text, key, msgSize }) { + const title = i18n.podcastBotEpisodeTitle || 'A podcast you follow has a new episode.' + return div( + { class: 'pm-card podcast-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `${i18n.pmBotPodcasts || 'PodcastBot'} · ${title}`), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), + actions({ key, replyId: from, subjectRaw: title, text }) + ) + } + + function WikiBotCard({ subjectU, sentAt, from, toLinks, text, key, msgSize }) { + const titleMap = { + WIKI_EDITED: i18n.wikiBotEditedTitle || 'A wiki page you follow has been edited.', + WIKI_RESTORED: i18n.wikiBotRestoredTitle || 'An earlier version of a wiki page you follow has been restored.' + } + const title = titleMap[subjectU] || (i18n.pmBotWiki || 'WikiBot') + return div( + { class: 'pm-card wiki-bot-notification thread-level-0' }, + headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), + h2({ class: 'pm-title' }, `📖 ${i18n.pmBotWiki || 'WikiBot'} · ${title}`), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), actions({ key, replyId: from, subjectRaw: title, text }) ) } @@ -3331,7 +4510,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' { class: 'pm-card jobs-bot-notification thread-level-0' }, headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), h2({ class: 'pm-title' }, `💼 ${i18n.pmBotJobs} · ${title}`), - div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(text || '')) }), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), actions({ key, replyId: from, subjectRaw: title, text }) ) } @@ -3355,7 +4534,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' { class: 'pm-card housing-bot-notification thread-level-0' }, headerLine({ sentAt, from, toLinks, subject: title, msgKey: key, msgSize }), h2({ class: 'pm-title' }, `🏠 ${i18n.pmBotHousing || 'HousingBot'} · ${title}`), - div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(text || '')) }), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text || '')) }), actions({ key, replyId: from, subjectRaw: title, text }) ) } @@ -3364,61 +4543,98 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' return template( i18n.private, section( - div({ class: 'tags-header' }, - div({ class: 'title-with-chip' }, h2(i18n.private), renderEncryptedChip(i18n)), - p(i18n.privateDescription) + div({ class: 'tags-header module-header-line' }, + h2(i18n.private), + p(i18n.privateDescription), + renderEncryptedChip(i18n) ), - noticeText ? div({ class: 'pm-form-error-msg' }, p('✗ ' + noticeText)) : null, - (() => { - const pmVis = getConfig().pmVisibility === 'mutuals' ? 'mutuals' : 'whole' - const pmVisLabel = pmVis === 'mutuals' ? i18n.settingsPmVisibilityMutuals : i18n.settingsPmVisibilityWhole - const pmVisIcon = pmVis === 'mutuals' ? '🤝' : '🌐' - const nextVis = pmVis === 'mutuals' ? 'whole' : 'mutuals' - const nextLabel = nextVis === 'mutuals' - ? (i18n.inboxToggleToMutuals || 'Switch to mutuals') - : (i18n.inboxToggleToWhole || 'Switch to whole') - return div({ class: 'pm-exposition inbox-exposition' }, - span({ class: 'inbox-filters-label' }, i18n.inboxFiltersLabel || 'Filters:'), - span({ class: `pm-exposition-chip pm-exposition-${pmVis}` }, - span({ class: 'pm-exposition-icon' }, pmVisIcon), - span({ class: 'pm-exposition-text' }, pmVisLabel) - ), - form({ method: 'POST', action: '/settings/pm-visibility?returnTo=/inbox', class: 'inbox-vis-toggle' }, - input({ type: 'hidden', name: 'pmVisibility', value: nextVis }), - button({ type: 'submit', class: 'btn' }, nextLabel) - ) - ) - })(), div({ class: 'filters' }, form({ method: 'GET', action: '/inbox' }, [ + ...(emptyInbox ? [] : [ button({ type: 'submit', name: 'filter', value: 'inbox', class: filter === 'inbox' ? 'filter-btn active' : 'filter-btn' - }, `${i18n.privateInbox} (${inboxCount})`), - button({ + }, `${String(i18n.allButton || 'ALL').toUpperCase()} (${inboxCount})`), + (pmCount > 0 || filter === 'pms') ? button({ type: 'submit', name: 'filter', - value: 'reminders', - class: filter === 'reminders' ? 'filter-btn active' : 'filter-btn' - }, `${i18n.privateReminders || 'Reminders'} (${reminderCount})`), - button({ + value: 'pms', + class: filter === 'pms' ? 'filter-btn active' : 'filter-btn' + }, `${String(i18n.inboxFilterPms || 'PMs').toUpperCase()} (${pmCount})`) : null, + (mailingCount > 0 || filter === 'mailing') ? button({ + type: 'submit', + name: 'filter', + value: 'mailing', + class: filter === 'mailing' ? 'filter-btn active' : 'filter-btn' + }, `${String(i18n.inboxFilterMailing || 'Mailing lists').toUpperCase()} (${mailingCount})`) : null, + (sentCount > 0 || filter === 'sent') ? button({ type: 'submit', name: 'filter', value: 'sent', class: filter === 'sent' ? 'filter-btn active' : 'filter-btn' - }, `${i18n.privateSent} (${sentCount})`), + }, `${String(i18n.privateSent || 'SENT').toUpperCase()} (${sentCount})`) : null, + (notifCount > 0 || filter === 'notifications' || filter === 'reminders') ? button({ + type: 'submit', + name: 'filter', + value: 'notifications', + class: (filter === 'notifications' || filter === 'reminders') ? 'filter-btn active' : 'filter-btn' + }, `${String(i18n.inboxFilterNotifications || 'Notifications').toUpperCase()} (${notifCount})`) : null, + (archivedCount > 0 || filter === 'archived') ? button({ + type: 'submit', + name: 'filter', + value: 'archived', + class: filter === 'archived' ? 'filter-btn active' : 'filter-btn' + }, `${String(i18n.inboxFilterArchived).toUpperCase()} (${archivedCount})`) : null, + ]), button({ type: 'submit', name: 'filter', value: 'create', class: 'create-button', formaction: '/pm', - formmethod: 'GET' + attrs: { formmethod: 'GET' } }, i18n.pmCreateButton) ]) ), + (filter === 'notifications' && botsPresent.length) ? div({ class: 'filters inbox-bot-filters' }, + form({ method: 'GET', action: '/inbox' }, + input({ type: 'hidden', name: 'filter', value: 'notifications' }), + sortMode === 'recent' ? input({ type: 'hidden', name: 'sort', value: 'recent' }) : null, + button({ type: 'submit', name: 'bot', value: '', class: botFilter ? 'filter-btn' : 'filter-btn active' }, `${String(i18n.inboxAllBots).toUpperCase()} (${notifCount})`), + ...botsPresent.map(b => button({ type: 'submit', name: 'bot', value: b, class: botFilter === b ? 'filter-btn active' : 'filter-btn' }, `${String(inboxBotLabel(b)).toUpperCase()} (${botCounts[b]})`)) + ) + ) : null, + emptyInbox ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + (() => { + return div({ class: 'pm-exposition inbox-exposition' }, + span({ class: 'inbox-filters-label' }, i18n.inboxFiltersLabel || 'Filters:'), + form({ method: 'GET', action: '/inbox', class: 'inbox-vis-toggle' }, + input({ type: 'hidden', name: 'filter', value: filter || 'inbox' }), + botFilter ? input({ type: 'hidden', name: 'bot', value: botFilter }) : null, + qNorm ? input({ type: 'hidden', name: 'q', value: String(q || '') }) : null, + sortMode === 'recent' ? null : input({ type: 'hidden', name: 'sort', value: 'recent' }), + button({ type: 'submit', class: 'btn' }, sortMode === 'recent' ? i18n.inboxSortThread : i18n.inboxSortRecent) + ), + unreadShown.length ? form({ method: 'POST', action: '/inbox/read-all', class: 'inbox-vis-toggle' }, + ...unreadShown.map(k => input({ type: 'hidden', name: 'keys', value: k })), + button({ type: 'submit', class: 'btn' }, `${i18n.inboxMarkAllRead} (${unreadShown.length})`) + ) : null, + (bulkDeletable && shownKeys.length) ? form({ method: 'POST', action: '/inbox/delete-many', class: 'inbox-vis-toggle inbox-bulk-delete' }, + ...shownKeys.map(k => input({ type: 'hidden', name: 'keys', value: k })), + button({ type: 'submit', class: 'btn delete-btn' }, `${String(i18n.inboxDeleteShown).toUpperCase()} (${shownKeys.length})`) + ) : null + ) + })(), + form({ method: 'GET', action: '/inbox', class: 'filter-box' }, + input({ type: 'hidden', name: 'filter', value: filter || 'inbox' }), + botFilter ? input({ type: 'hidden', name: 'bot', value: botFilter }) : null, + sortMode === 'recent' ? input({ type: 'hidden', name: 'sort', value: 'recent' }) : null, + input({ type: 'text', name: 'q', value: String(q || ''), placeholder: i18n.inboxSearchPlaceholder || 'Search in Inbox...', class: 'filter-box__input' }), + button({ type: 'submit', class: 'filter-box__button' }, i18n.searchButton) + ) + ), div({ class: 'message-list' }, (() => { function renderMsg(msg) { @@ -3428,7 +4644,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' const subjectU = subjectRaw.toUpperCase() const text = content.text || '' const sentAt = new Date(content.sentAt || msg.timestamp) - const fromResolved = content.from || author + const fromResolved = author const toLinks = Array.isArray(content.to) ? content.to.map(addr => linkAuthor(addr)) : [] const level = threadLevel(subjectRaw) const msgSize = msgSizeBytes(msg) @@ -3455,7 +4671,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' key: msg.key, replyId: fromResolved, subjectRaw, text: '', extra: fsp.crypter ? null - : form({ method: 'GET', action: `/inbox/file/${encodeURIComponent(msg.key)}`, class: 'pm-action-form', onclick: 'event.stopPropagation()' }, + : form({ method: 'GET', action: `/inbox/file/${encodeURIComponent(msg.key)}`, class: 'pm-action-form' }, button({ type: 'submit', class: 'pm-btn pm-fileshare-download' }, (i18n.fileShareDownload || 'Download').toUpperCase()) ) }) @@ -3480,15 +4696,42 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' if (subjectU === 'LARP_RULING' || subjectU === 'PARLIAMENT_GOV' || subjectU === 'TRIBE_GOV') { return PoliticalBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) } + if (subjectU === 'SCHOOL_ENROLLED' || subjectU === 'SCHOOL_INVITED' || subjectU === 'SCHOOL_ADMITTED' || subjectU === 'SCHOOL_CERTIFICATE' || subjectU === 'SCHOOL_PASSED' || subjectU === 'SCHOOL_LESSON_NEW') { + return SchoolBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } + if (subjectU === 'BANKING_UBI_PAID' || subjectU === 'BANKING_UBI_AVAILABLE' || subjectU === 'BANKING_CONFIRM_PENDING' || subjectU === 'WALLET_PAYMENT') { + return BankingBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } if (subjectU === 'INDUSTRY_ADMITTED' || subjectU === 'INDUSTRY_APPLICATION' || subjectU === 'INDUSTRY_INVITED' || subjectU === 'INDUSTRY_DISSOLVED' || subjectU === 'INDUSTRY_BUILD_APPROVED' || subjectU === 'INDUSTRY_DISTRIBUTED') { return IndustryBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) } if (subjectU === 'HOUSING_REQUESTED' || subjectU === 'HOUSING_CANCELLED' || subjectU === 'HOUSING_UNAVAILABLE') { return HousingBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) } + if (subjectU === 'WIKI_EDITED' || subjectU === 'WIKI_RESTORED') { + return WikiBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } + if (subjectU === 'EMERGENCY_UPDATED' || subjectU === 'EMERGENCY_RESOLVED') { + return EmergencyBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } + if (subjectU === 'BLOG_NEW') { + return BlogBotCard({ sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } + if (subjectU === 'PODCAST_EPISODE') { + return PodcastBotCard({ sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } + if (subjectU === 'CAMPAIGN_UPDATED' || subjectU === 'CAMPAIGN_ACHIEVED' || subjectU === 'CAMPAIGN_RAISED') { + return CampaignBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } + if (subjectU.startsWith('LOGISTICS_')) { + return LogisticsBotCard({ subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize }) + } if (subjectU === 'PROJECT_PLEDGE' || content.meta?.type === 'project-pledge') { return ProjectPledgeCard({ sentAt, from: fromResolved, toLinks, content, text, key: msg.key, msgSize }) } + if (content.list) { + return MailingCard({ sentAt, from: fromResolved, toLinks, subject: subjectRaw, text, key: msg.key, msgSize, content }) + } if (content.crypter === true) { const dec = (decrypted && decrypted.key === msg.key) ? decrypted : null @@ -3497,10 +4740,8 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' { class: 'pm-card normal-pm pm-crypter-card' }, headerLine({ sentAt, from: fromResolved, toLinks, subject: subjectRaw, msgKey: msg.key, msgSize, crypter: true }), dec && typeof dec.text === 'string' - ? div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(dec.text)) }) - : dec && dec.error - ? div({ class: 'pm-form-error-msg' }, p('✗ ' + i18n.pmCrypterBadKey)) - : null, + ? div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(dec.text)) }) + : null, dec && typeof dec.text === 'string' ? null : form({ method: 'POST', action: '/inbox/decrypt', class: 'pm-crypter-decrypt-form' }, @@ -3516,11 +4757,17 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' return div( { class: 'pm-card normal-pm' }, headerLine({ sentAt, from: fromResolved, toLinks, subject: subjectRaw, msgKey: msg.key, msgSize }), - div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(text)) }), + div({ class: 'message-text', [RAW_HTML]: sanitizeHtml(clickableLinks(text)) }), actions({ key: msg.key, replyId: fromResolved, subjectRaw, text }) ) } + const msgTs = (m) => new Date(m?.value?.content?.sentAt || m.timestamp || 0).getTime() + if (sortMode === 'recent') { + if (!sorted.length) return p({ class: 'empty' }, i18n.noPrivateMessages) + return paged([...sorted].sort((a, b) => msgTs(b) - msgTs(a))).map(renderMsg) + } + const threadGroups = {} const threadOrder = [] for (const msg of sorted) { @@ -3540,26 +4787,26 @@ exports.privateView = async (messagesInput, filter, decrypted = null, notice = ' }, 0) threadOrder.sort((a, b) => threadTs(b) - threadTs(a)) - return threadOrder.map(tid => { + return paged(threadOrder).map(tid => { const msgs = threadGroups[tid] - const original = msgs[0] - const replies = msgs.slice(1) + const latest = msgs[msgs.length - 1] + const earlier = msgs.slice(0, -1) - if (!replies.length) { - return renderMsg(original) + if (!earlier.length) { + return renderMsg(latest) } - const replyLabel = `${replies.length} ${replies.length === 1 ? (i18n.pmReply || 'reply') : (i18n.pmReplies || 'replies')}` + const earlierLabel = `${earlier.length} ${i18n.inboxEarlierMessages}` return div({ class: 'pm-thread' }, - renderMsg(original), + renderMsg(latest), details({ class: 'pm-thread-details' }, summary({ class: 'pm-thread-toggle' }, span({ class: 'pm-thread-icon' }, '▶'), - span(replyLabel) + span(earlierLabel) ), div({ class: 'pm-thread-replies' }, - ...replies.map(renderMsg) + ...earlier.map(renderMsg) ) ) ) @@ -3587,18 +4834,33 @@ exports.threadView = ({ messages, spreadMap = null }) => { return `${tpl}${ needsPdfViewer - ? ` - ` + ? `` : "" }`; }; +exports.supportersView = async ({ supporters, feed, name }) => { + const list = Array.isArray(supporters) ? supporters : []; + return template( + i18n.viewSupporters, + section( + div({ class: "tags-header module-header-line" }, + h2(`${i18n.viewSupporters} (${list.length})`), + p(userLink(feed, name)) + ) + ), + list.length + ? section(div({ class: "supporters-list" }, ...list.map(id => span({ class: "pad-member-chip" }, userLink(id))))) + : p({ class: "no-content" }, i18n.no_results) + ); +}; + exports.likesView = async ({ messages, feed, name, spreadMap = null }) => { const list = Array.isArray(messages) ? messages : []; return template( i18n.viewLikes, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.viewLikes), p(userLink(feed, name)) ) @@ -3610,7 +4872,6 @@ exports.likesView = async ({ messages, feed, name, spreadMap = null }) => { }; -//generate preview const ensureAt = (id) => { const s = String(id || "").trim() if (!s) return "" @@ -3668,51 +4929,6 @@ const injectResolvedMentions = (text, mentions) => { return out } -const markdownMentionsToHtml = (markdownText) => { - const escaped = escapeHtml(String(markdownText || "")) - const withBr = escaped.replace(/\r\n|\r|\n/g, "
") - - const unescapeBlob = (b) => b.replace(/&/g, '&') - - const escAttr = (s) => String(s).replace(/"/g, '"').replace(/'/g, ''') - - const withImages = withBr.replace( - /!\[([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, - (_m, alt, blob) => `${escAttr(alt)}` - ) - - const withVideos = withImages.replace( - /\[video:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, - (_m, _name, blob) => `` - ) - - const withAudios = withVideos.replace( - /\[audio:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, - (_m, _name, blob) => `` - ) - - const withPdfs = withAudios.replace( - /\[pdf:([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/g, - (_m, name, blob) => `${escapeHtml(name || i18n.pdfFallbackLabel || 'PDF')}` - ) - - const withMentions = withPdfs.replace( - /\[@([^\]]+)\]\(\s*@?([^) \t\r\n]+\.ed25519)\s*\)/g, - (_m, label, feed) => { - const href = authorHref(feed) - const shown = `@${String(label || "").replace(/^@+/, "")}` - return `${escapeHtml(shown)}` - } - ) - - const withLinks = withMentions.replace( - /(https?:\/\/[^\s"'<>]+)/g, - (u) => `${escAttr(u)}` - ) - - return withLinks -} - const generatePreview = ({ previewData, contentWarning, action }) => { const mentions = previewData && previewData.mentions && typeof previewData.mentions === "object" @@ -3724,7 +4940,7 @@ const generatePreview = ({ previewData, contentWarning, action }) => { const injected = injectResolvedMentions(normalized, mentions) const publishText = normalizeMentionLinks(injected) - const previewHtml = markdownMentionsToHtml(publishText) + const previewHtml = renderStyledHtml(publishText) const mentionCards = Object.entries(mentions) .map(([_token, matches]) => { @@ -3790,7 +5006,7 @@ const generatePreview = ({ previewData, contentWarning, action }) => { { class: "preview-content" }, h2(i18n.messagePreview), contentWarning ? div({ class: "content-warning-preview" }, escapeHtml(contentWarning)) : null, - div({ class: "preview-rendered", innerHTML: previewHtml }) + div({ class: "preview-rendered", [RAW_HTML]: previewHtml }) ) ), section( @@ -3811,47 +5027,6 @@ const generatePreview = ({ previewData, contentWarning, action }) => { } -const messageListView = ({ - messages, - viewTitle = null, - viewDescription = null, - viewElements = null, - aside = null, - spreadMap = null, -}) => { - const hasHeader = !!viewElements; - const titleBlock = hasHeader - ? viewElements - : div({ class: "tags-header" }, - h2(viewTitle), - p(viewDescription) - ); - const getSpread = (key) => (spreadMap instanceof Map ? spreadMap.get(key) : null) || null; - return template( - viewTitle, - section(titleBlock), - messages.map((msg) => post({ msg, aside, spreadInfo: getSpread(msg.key) })) - ); -}; - - - - - - -exports.spreadedView = ({ messages }) => { - const header = div({ class: "tags-header" }, - h2(i18n.spreaded), - p(i18n.spreadedDescription) - ); - return spreadedListView({ - messages, - viewTitle: i18n.spreaded, - viewElements: header - }); -}; - - exports.previewSubtopicView = async ({ previewData, messages, @@ -3936,7 +5111,7 @@ exports.subtopicView = async ( br(), label( { for: "blob" }, - i18n.blogImage || "Upload media (max-size: 50MB)" + i18n.blogImage || "Upload media (max-size: 75MB)" ), input({ type: "file", id: "blob", name: "blob" }), br(), diff --git a/src/views/maps_view.js b/src/views/maps_view.js index 7f2b570..36f248b 100644 --- a/src/views/maps_view.js +++ b/src/views/maps_view.js @@ -1,16 +1,23 @@ -const { form, button, div, h2, h3, p, section, input, label, br, a, span, textarea, select, option, img, strong, table, tr, td } = +const { form, button, div, h2, h3, p, section, input, label, br, hr, a, span, textarea, select, option, img, strong, table, tr, td } = require("../server/node_modules/hyperaxe"); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const { renderStyledText, renderStyledHtml, escapeHtml } = require("../backend/renderStyledText"); const moment = require("../server/node_modules/moment"); -const { template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton, renderContentActions, renderSpreadEditWarning } = require("./main_views"); -const { renderEncryptedChip } = require("./clearnet_view"); +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderDocumentActions, renderInviteQrCard, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const { renderEncryptedChip, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require("./clearnet_view"); const { config } = require("../server/SSB_server.js"); -const { renderMapWithPins, renderZoomedMapWithPins, getViewportBounds, latLngToPx, pxToLatLng, MAP_W, MAP_H, getMaxTileZoom } = require("../maps/map_renderer"); -const { sanitizeHtml } = require('../backend/sanitizeHtml'); +const { renderMapHtml, resolveView, makeView, fitView, parseView, viewParam, parsePick, MIN_ZOOM, MAX_ZOOM } = require("../maps/map_renderer"); +const { searchPlaces, nearestPlace } = require("../maps/map_data"); const userId = config.keys.id; const safeArr = (v) => (Array.isArray(v) ? v : []); +const localHref = (u) => (typeof u === 'string' && /^\/(?![\/\\])\S*$/.test(u) ? u : null); const safeText = (v) => String(v || "").trim(); +const MAP_REACH = ["SINGLE", "OPEN", "CLOSED"]; +const mapReachLabel = (t) => t === "OPEN" ? i18n.mapTypeOpen : t === "CLOSED" ? i18n.mapTypeClosed : i18n.mapTypeSingle; +const clearnetEligible = (m) => !!m && !m.tribeId && !m.encrypted && !m.contentEncrypted && String(m.mapType || "").toUpperCase() === "OPEN"; +const mapClearnetHref = (m) => clearnetItemHref("maps", m.title, m.rootId || m.key); const buildReturnTo = (filter, params = {}) => { const f = safeText(filter || "all"); @@ -28,120 +35,103 @@ const renderTags = (tags) => { }; let areaCounter = 0; +const lastOf = (v) => (Array.isArray(v) ? v[v.length - 1] : v); +const numOrNull = (v) => { const n = parseFloat(lastOf(v)); return isFinite(n) ? n : null; }; +const isBlob = (v) => !!v && String(v).startsWith("&"); +const blobSrc = (image, base) => (isBlob(image) ? `${base}/blob/${encodeURIComponent(image)}` : ""); -const buildAreas = (clickUrl, latParam = "lat", lngParam = "lng", viewport = null) => { - const GRID = 16; - const cellW = MAP_W / GRID; - const cellH = MAP_H / GRID; - const areas = []; - for (let gy = 0; gy < GRID; gy++) { - for (let gx = 0; gx < GRID; gx++) { - let c; - if (viewport) { - const lat = viewport.latMax - (gy + 0.5) / GRID * (viewport.latMax - viewport.latMin); - const lng = viewport.lngMin + (gx + 0.5) / GRID * (viewport.lngMax - viewport.lngMin); - c = { lat: Math.round(lat * 10000) / 10000, lng: Math.round(lng * 10000) / 10000 }; - } else { - const cx = Math.round(gx * cellW + cellW / 2); - const cy = Math.round(gy * cellH + cellH / 2); - c = pxToLatLng(cx, cy); - } - const x1 = Math.round(gx * cellW); - const y1 = Math.round(gy * cellH); - const x2 = Math.round((gx + 1) * cellW); - const y2 = Math.round((gy + 1) * cellH); - areas.push(`${c.lat},${c.lng}`); - } +const buildQuery = (params) => Object.entries(params || {}) + .filter(([, v]) => v !== undefined && v !== null && String(v) !== "") + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) + .join("&"); + +const viewHrefFor = (basePath, baseParams, anchor = "#map") => (target, extra = {}) => { + const q = buildQuery({ ...baseParams, ...extra, view: target ? viewParam(target) : undefined }); + return `${basePath}${q ? "?" + q : ""}${anchor}`; +}; + +const navText = () => ({ zoomIn: i18n.mapZoomIn, zoomOut: i18n.mapZoomOut, fit: i18n.mapFitMarkers, pan: i18n.mapPanLabel }); + +const viewForPlace = (hit) => { + if (!hit) return null; + if (hit.kind === "country" && Array.isArray(hit.bbox)) { + const b = hit.bbox; + const f = fitView([{ lat: b[1], lng: b[0] }, { lat: b[3], lng: b[2] }], { maxZoom: 7, pad: 40 }); + return makeView(f.lat, f.lng, f.zoom); } - return areas; + return makeView(hit.lat, hit.lng, MAX_ZOOM); +}; + +const placeLabel = (hit) => hit.country ? `${hit.name}, ${hit.country}` : hit.name; + +const nearText = (lat, lng) => { + const near = nearestPlace(lat, lng); + return near ? `${i18n.mapNear} ${near.name}, ${near.country}` : ""; +}; + +const pickedSpot = (lat, lng) => { + const la = numOrNull(lat), lo = numOrNull(lng); + if (la === null || lo === null) return p({ class: "map-pick-hint" }, i18n.mapPickHint); + const near = nearText(la, lo); + return div({ class: "map-picked" }, + span({ class: "map-coord-pin" }, "📍"), + strong(`${la.toFixed(4)}, ${lo.toFixed(4)}`), + near ? span({ class: "map-picked-near" }, near) : null); }; const renderMap = (markers, clickUrl, mainIdx, opts = {}) => { areaCounter++; - const mapName = `m${areaCounter}`; - const latParam = opts.latParam || "lat"; - const lngParam = opts.lngParam || "lng"; const pinLabels = opts.pinLabels || []; const pinImages = opts.pinImages || []; - const pfx = opts.pinPrefix || `pin${areaCounter}`; - const zoom = parseInt(opts.zoom) || 2; - const centerLat = typeof opts.centerLat === "number" ? opts.centerLat : 0; - const centerLng = typeof opts.centerLng === "number" ? opts.centerLng : 0; - - const pinList = safeArr(markers).filter((m) => m && typeof m.lat === "number" && typeof m.lng === "number"); - const useZoom = zoom > 2; - const mapFile = useZoom - ? renderZoomedMapWithPins(centerLat, centerLng, zoom, pinList, mainIdx) - : (pinList.length > 0 ? renderMapWithPins(pinList, mainIdx) : null); - const imgSrc = mapFile ? `/mapcache/${mapFile}` : "/assets/images/worldmap-z2.png"; - const viewport = useZoom && clickUrl ? getViewportBounds(centerLat, centerLng, zoom) : null; - - const useMap = clickUrl || pinLabels.length > 0; - const mapTag = useMap ? mapName : ""; - - let gridAreasHtml = ""; - if (clickUrl) { - const clickUrlWithZoom = zoom > 2 ? `${clickUrl}zoom=${zoom}&` : clickUrl; - gridAreasHtml = buildAreas(clickUrlWithZoom, latParam, lngParam, viewport).join(""); + const pinHrefs = opts.pinHrefs || []; + const base = opts.publicBase || ""; + const focus = Number.isInteger(opts.focus) ? opts.focus : -1; + const main = mainIdx || 0; + const pins = safeArr(markers) + .filter((m) => m && numOrNull(m.lat) !== null && numOrNull(m.lng) !== null) + .map((m, i) => ({ + lat: numOrNull(m.lat), + lng: numOrNull(m.lng), + label: pinLabels[i] !== undefined ? pinLabels[i] : (m.label || ""), + image: blobSrc(pinImages[i] !== undefined ? pinImages[i] : m.image, base), + href: pinHrefs[i] || m.href || null, + title: m.title || "", + main: i === main, + focus: i === focus + })); + if (opts.pick && numOrNull(opts.pick.lat) !== null && numOrNull(opts.pick.lng) !== null) { + pins.push({ lat: numOrNull(opts.pick.lat), lng: numOrNull(opts.pick.lng), pick: true }); } - let popupAreasHtml = ""; - let popupsHtml = ""; - if (pinLabels.length > 0) { - const vp = useZoom ? getViewportBounds(centerLat, centerLng, zoom) : null; - pinList.forEach((m, i) => { - const lbl = pinLabels[i] || ""; - let px; - if (vp) { - px = { - x: ((m.lng - vp.lngMin) / (vp.lngMax - vp.lngMin)) * MAP_W, - y: ((vp.latMax - m.lat) / (vp.latMax - vp.latMin)) * MAP_H - }; - } else { - px = latLngToPx(m.lat, m.lng); - } - const escaped = lbl.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); - const withLinks = escaped.replace(/https?:\/\/[^\s&"<>]+/g, (url) => { - const clean = url.replace(/&/g, "&"); - return `${url}`; - }).replace(/\n/g, "
"); - const scale = Math.pow(2, Math.max(0, zoom - getMaxTileZoom())); - const halfW = Math.max(26, 26 * scale); - const pinTop = Math.max(48, 48 * scale); - const pinFoot = Math.max(10, 10 * scale); - const x1 = Math.max(0, px.x - halfW); - const y1 = Math.max(0, px.y - pinTop); - const x2 = Math.min(MAP_W, px.x + halfW); - const y2 = Math.min(MAP_H, px.y + pinFoot); - const popupId = `${pfx}_${i}`; - const latStr = typeof m.lat === "number" ? m.lat.toFixed(4) : ""; - const lngStr = typeof m.lng === "number" ? m.lng.toFixed(4) : ""; - const imgBlobId = pinImages[i] && String(pinImages[i]).startsWith("&") ? pinImages[i] : ""; - const imgHtml = imgBlobId ? `` : ""; - popupAreasHtml += `${escaped}`; - popupsHtml += `
✕${imgHtml}
${sanitizeHtml(withLinks)}
${latStr}, ${lngStr}
`; - }); - } - const mapHtml = useMap ? `${popupAreasHtml}${gridAreasHtml}` : ""; - const useAttr = useMap ? ` usemap="#${mapTag}"` : ""; - - const mapWrapHtml = `
map${mapHtml}
`; - const viewerEl = div({ class: "map-viewer" }, { innerHTML: mapWrapHtml }); - if (!popupsHtml) return viewerEl; - return div({ class: "map-zone" }, viewerEl, div({ class: "map-popup-container", innerHTML: popupsHtml })); + const view = opts.view || resolveView({ zoom: opts.zoom, clat: opts.centerLat, clng: opts.centerLng }, pins.filter((x) => !x.pick), { singleZoom: opts.singleZoom || 8 }); + const html = renderMapHtml(view, pins, { + id: opts.id || `map${areaCounter}`, + thumb: !!opts.thumb, + publicBase: base, + nav: opts.nav || null, + pick: opts.pickForm ? { form: opts.pickForm, action: opts.pickAction, name: opts.pickName || "pick" } : null, + clickUrl: clickUrl || null, + latParam: opts.latParam, + lngParam: opts.lngParam, + anchor: typeof opts.anchor === "string" ? opts.anchor : "", + viewHref: opts.viewHref || null, + popup: opts.popup || null, + clusterTitle: i18n.mapClusterTitle, + text: navText() + }); + return div({ class: opts.thumb ? "map-viewer map-viewer-thumb" : "map-viewer", [RAW_HTML]: html }); }; -const renderCoordPreview = (lat, lng) => { - if (!lat && !lng) return null; - return span({ class: "map-coord-inline" }, - span({ class: "map-coord-pin" }, "📍"), - strong(`${lat}, ${lng}`)); +const renderPlaceResults = (hits, toEl) => { + if (!hits) return null; + if (!hits.length) return span({ class: "map-place-none" }, i18n.mapNoPlaces); + return div({ class: "map-place-results" }, hits.map((h) => toEl(h))); }; const renderLocalEmbed = (lat, lng) => { const la = parseFloat(lat) || 0; const lo = parseFloat(lng) || 0; if (!la && !lo) return null; - return renderMap([{ lat: la, lng: lo }], null, 0); + return renderMap([{ lat: la, lng: lo }], null, 0, { zoom: MIN_ZOOM, centerLat: la, centerLng: lo }); }; const renderMapUrl = (mapObj) => @@ -156,10 +146,7 @@ const renderMapOwnerActions = (filter, mapObj, params = {}) => { const actions = [ form({ method: "GET", action: `/maps/edit/${encodeURIComponent(mapObj.key)}` }, input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "update-btn", type: "submit" }, i18n.mapUpdateButton)), - form({ method: "POST", action: `/maps/delete/${encodeURIComponent(mapObj.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.mapDeleteButton)) + button({ class: "update-btn", type: "submit" }, i18n.mapUpdateButton)) ]; const invitable = !mapObj.tribeId && mapObj.mapType === "CLOSED"; if (invitable) { @@ -171,7 +158,8 @@ const renderMapOwnerActions = (filter, mapObj, params = {}) => { if (openInvite) { actions.push(div({ class: 'tribe-open-invite' }, span({ class: 'card-label' }, i18n.tribeInviteCodeText), - span({ class: 'tribe-open-invite-code' }, openInvite.code) + span({ class: 'tribe-open-invite-code' }, openInvite.code), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/maps/${encodeURIComponent(openInvite.code)}` }) )); actions.push(form({ method: "POST", action: `/maps/open-invite/remove/${encodeURIComponent(mapObj.key)}` }, button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.tribeRemoveInvitation))); @@ -183,59 +171,89 @@ const renderMapOwnerActions = (filter, mapObj, params = {}) => { return actions; }; -const renderFilters = (filter, q) => - div({ class: "filters" }, +const renderFilters = (filter, q, emptyMod = false, chip = null, search = null) => + div({ class: search ? "filters activity-filter-chips activity-toolbar-row" : "filters" }, form({ method: "GET", action: "/maps", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q || "" }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterRecent).toUpperCase()), - button({ type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterFavorites).toUpperCase()), - button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.mapUploadButton))); + ...(!chip || chip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterRecent).toUpperCase())] : []), + ...(!chip || chip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterMine).toUpperCase())] : []), + ...(!chip || chip("favorites") ? [button({ type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.mapFilterFavorites).toUpperCase())] : []), + ]), + button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.mapCreateButton)), + search); const renderMapForm = (filter, mapId, mapToEdit, params = {}) => { const returnFilter = filter === "create" ? "all" : params.filter || "all"; const returnTo = safeText(params.returnTo) || buildReturnTo(returnFilter, params); - const latVal = params.lat !== undefined ? String(params.lat) : String(mapToEdit?.lat || ""); - const lngVal = params.lng !== undefined ? String(params.lng) : String(mapToEdit?.lng || ""); + const picked = parsePick(params.pick); + const latVal = picked ? String(picked.lat) : (params.lat !== undefined ? String(lastOf(params.lat)) : String(mapToEdit?.lat || "")); + const lngVal = picked ? String(picked.lng) : (params.lng !== undefined ? String(lastOf(params.lng)) : String(mapToEdit?.lng || "")); const titleVal = params.title || mapToEdit?.title || ""; const descVal = params.description || mapToEdit?.description || ""; const markerLabelVal = params.markerLabel !== undefined ? params.markerLabel : (mapToEdit?.markerLabel || ""); const tagsValue = params.tags !== undefined ? params.tags : safeArr(mapToEdit?.tags).join(", "); - const mapTypeVal = params.mapType || mapToEdit?.mapType || "SINGLE"; - const maxTileZoom = getMaxTileZoom(); - const zoomVal = parseInt(params.zoom) || 2; - const cleanUrl = `/maps?filter=create${params.tribeId ? '&tribeId=' + encodeURIComponent(params.tribeId) : ''}`; + const isEdit = filter === "edit"; + const reachRaw = String(params.reach || params.mapType || "").toUpperCase(); + const mapTypeVal = MAP_REACH.includes(reachRaw) ? reachRaw : (mapToEdit?.mapType || "SINGLE"); + const showClearnet = mapTypeVal === "OPEN" && !params.tribeId && clearnetEligible({ ...(mapToEdit || {}), mapType: mapTypeVal }); + const clearnetOn = params.clearnet !== undefined ? String(params.clearnet) === "1" : mapToEdit?.clearnet === true; + const stepAction = isEdit ? `/maps/edit/${encodeURIComponent(mapId)}` : "/maps"; + const cleanUrl = isEdit + ? `${stepAction}?mapType=${encodeURIComponent(mapTypeVal)}` + : `/maps?filter=create&mapType=${encodeURIComponent(mapTypeVal)}${params.tribeId ? '&tribeId=' + encodeURIComponent(params.tribeId) : ''}`; const pickerMarkers = latVal && lngVal ? [{ lat: parseFloat(latVal), lng: parseFloat(lngVal) }] : []; + const placeQ = safeText(lastOf(params.place)); + const placeHits = placeQ ? searchPlaces(placeQ, 5) : null; + const explicitView = parseView(params.view); + const prevView = Array.isArray(params.view) ? parseView(params.view[0]) : null; + const view = explicitView + ? makeView(explicitView.lat, explicitView.lng, explicitView.zoom) + : (placeHits && placeHits.length ? viewForPlace(placeHits[0]) : resolveView({ zoom: params.zoom, clat: prevView ? prevView.lat : undefined, clng: prevView ? prevView.lng : undefined }, pickerMarkers, { singleZoom: 8 })); + const zoomVal = view.zoom; + const formId = "map-main-form"; + const nav = { form: formId, action: stepAction }; + const keptKeys = ["lat", "lng", "zoom", "view", "title", "description", "markerLabel", "tags"]; return div({ class: "div-center audio-form" }, params.spreadWarning || null, - h2(filter === "edit" ? i18n.mapUpdateButton : i18n.mapCreateButton), + h2(isEdit ? i18n.mapUpdateButton : i18n.mapCreateButton), + form({ method: "GET", action: stepAction }, + isEdit ? null : input({ type: "hidden", name: "filter", value: "create" }), + params.tribeId ? input({ type: "hidden", name: "tribeId", value: params.tribeId }) : null, + isEdit && safeText(params.returnTo) ? input({ type: "hidden", name: "returnTo", value: safeText(params.returnTo) }) : null, + ...keptKeys.filter((k) => params[k] !== undefined && String(lastOf(params[k])) !== "").map((k) => input({ type: "hidden", name: k, value: String(lastOf(params[k])) })), + label(i18n.mapTypeLabel), br(), + div({ class: "apply-row" }, + select({ name: "mapType", class: "report-category-select" }, + ...MAP_REACH.map((t) => option({ value: t, ...(mapTypeVal === t ? { selected: true } : {}) }, mapReachLabel(t)))), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply"))), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, mapReachLabel(mapTypeVal)), form({ - action: filter === "edit" ? `/maps/update/${encodeURIComponent(mapId)}` : "/maps/create", + id: formId, + action: isEdit ? `/maps/update/${encodeURIComponent(mapId)}` : "/maps/create", method: "POST", enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: returnTo }), - input({ type: "hidden", name: "filter", value: "create" }), + isEdit ? null : input({ type: "hidden", name: "filter", value: "create" }), + input({ type: "hidden", name: "mapType", value: mapTypeVal }), + input({ type: "hidden", name: "view", value: viewParam(view) }), params.tribeId ? input({ type: "hidden", name: "tribeId", value: params.tribeId }) : null, label(i18n.title || "Title"), br(), input({ type: "text", name: "title", maxlength: "100", placeholder: i18n.mapTitlePlaceholder || "Map title", value: titleVal }), br(), br(), label(i18n.mapDescriptionLabel), br(), - textarea({ name: "description", placeholder: i18n.mapDescriptionPlaceholder, rows: "3" }, descVal), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.mapDescriptionPlaceholder, rows: "3" }, descVal), br(), br(), label(i18n.mapTagsLabel), br(), input({ type: "text", name: "tags", placeholder: i18n.mapTagsPlaceholder, value: tagsValue }), br(), br(), - label(i18n.mapTypeLabel), br(), - select({ name: "mapType" }, - option({ value: "SINGLE", ...(mapTypeVal === "SINGLE" ? { selected: true } : {}) }, i18n.mapTypeSingle), - option({ value: "OPEN", ...(mapTypeVal === "OPEN" ? { selected: true } : {}) }, i18n.mapTypeOpen), - option({ value: "CLOSED", ...(mapTypeVal === "CLOSED" ? { selected: true } : {}) }, i18n.mapTypeClosed)), - br(), br(), + showClearnet ? renderClearnetSelector(clearnetOn, i18n) : null, label(i18n.mapMarkerLabelField), br(), - textarea({ name: "markerLabel", placeholder: i18n.mapMarkerLabelPlaceholder, rows: "3" }, markerLabelVal), + textarea({ maxlength: "5000", name: "markerLabel", placeholder: i18n.mapMarkerLabelPlaceholder, rows: "3" }, markerLabelVal), br(), br(), label(i18n.markerImageLabel || "Marker Image"), br(), input({ type: "file", name: "image", accept: "image/*" }), @@ -247,117 +265,164 @@ const renderMapForm = (filter, mapId, mapToEdit, params = {}) => { input({ type: "text", name: "lng", placeholder: i18n.mapLngPlaceholder, value: lngVal }), br(), br(), div({ class: "map-form-row" }, - button({ type: "submit", attrs: { formmethod: "GET" }, formaction: "/maps", class: "filter-btn" }, i18n.mapAddMarkerButton || "Add Marker"), + button({ type: "submit", attrs: { formmethod: "GET" }, formaction: stepAction, class: "filter-btn" }, i18n.mapAddMarkerButton || "Add Marker"), a({ href: cleanUrl, class: "filter-btn" }, i18n.mapCleanMarkerButton || "Clean Marker")), - renderCoordPreview(latVal, lngVal), - br(), - label(i18n.mapZoomLabel || "Zoom"), br(), - select({ name: "zoom" }, - [2, 3, 4, 5, 6, 7, 8].map(z => - option({ value: String(z), ...(zoomVal === z ? { selected: true } : {}) }, String(z)))), - br(), br(), - button({ type: "submit", attrs: { formmethod: "GET" }, formaction: "/maps", class: "filter-btn" }, i18n.mapApplyZoom || "Apply Zoom"), + pickedSpot(latVal, lngVal), + div({ class: "map-toolbar" }, + div({ class: "map-place-search" }, + input({ type: "text", name: "place", value: placeQ, placeholder: i18n.mapPlaceSearchPlaceholder, class: "filter-box__input" }), + button({ type: "submit", attrs: { formmethod: "GET" }, formaction: stepAction, name: "view", value: "", class: "filter-btn" }, i18n.mapSearchButton))), + renderPlaceResults(placeHits, (h) => { + const v = viewForPlace(h); + return button({ type: "submit", attrs: { formmethod: "GET" }, formaction: stepAction, name: "view", value: viewParam(v), class: "map-place-result" }, + span(h.name), h.country ? span({ class: "map-place-result-country" }, h.country) : null); + }), div({ class: "map-form-map-slot" }, - renderMap(pickerMarkers, null, 0, { zoom: zoomVal, centerLat: parseFloat(latVal) || 0, centerLng: parseFloat(lngVal) || 0 })), - button({ type: "submit", class: "create-button" }, filter === "edit" ? i18n.mapUpdateButton : i18n.mapCreateButton))); + renderMap(pickerMarkers, null, 0, { view, id: "map", nav, pickForm: formId, pickAction: stepAction })), + button({ type: "submit", class: "create-button" }, isEdit ? i18n.mapUpdateButton : i18n.mapCreateButton))); }; -const renderMarkerForm = (mapObj, returnTo, params = {}, tribeMembers = []) => { - if (mapObj.mapType === "SINGLE") return null; - if (mapObj.mapType === "CLOSED" && String(mapObj.author) !== String(userId)) return null; - if (mapObj.mapType === "OPEN" && mapObj.tribeId && !tribeMembers.includes(userId)) return null; - const mkLat = params.mkLat || ""; - const mkLng = params.mkLng || ""; - const zoomVal = parseInt(params.zoom) || 2; +const MARKER_FORM_ID = "add-marker-form"; - const existingMarkers = [{ lat: mapObj.lat, lng: mapObj.lng }].concat( - safeArr(mapObj.markers).map((m) => ({ lat: m.lat, lng: m.lng }))); - if (mkLat && mkLng) existingMarkers.push({ lat: parseFloat(mkLat), lng: parseFloat(mkLng) }); +const canAddMarkers = (mapObj, tribeMembers = []) => { + if (mapObj.mapType === "SINGLE") return false; + if (mapObj.mapType === "CLOSED" && String(mapObj.author) !== String(userId)) return false; + if (mapObj.mapType === "OPEN" && mapObj.tribeId && !safeArr(tribeMembers).includes(userId)) return false; + return true; +}; - const pinLabels = [mapObj.markerLabel || mapObj.description || mapObj.title || ""].concat( - safeArr(mapObj.markers).map((m) => m.label || "")); +const allMarkersOf = (mapObj) => [{ + key: mapObj.key, + lat: mapObj.lat, + lng: mapObj.lng, + label: mapObj.markerLabel || mapObj.description || mapObj.title || i18n.mapMarkerDefault, + image: mapObj.image || "", + author: mapObj.author, + createdAt: mapObj.createdAt, + root: true +}].concat(safeArr(mapObj.markers).map((m) => ({ ...m, root: false }))); - const mkCleanUrl = `/maps/${encodeURIComponent(mapObj.key)}?filter=${encodeURIComponent(params.filter || "all")}`; - const clickUrl = `/maps/${encodeURIComponent(mapObj.key)}?filter=${encodeURIComponent(params.filter || "all")}&zoom=${zoomVal}&`; +const firstLine = (s, n = 140) => { + const line = String(s || "").split(/\r?\n/)[0].trim(); + return line.length > n ? line.slice(0, n - 1) + "…" : line; +}; + +const renderMarkerForm = (mapObj, returnTo, params = {}, tribeMembers = [], ctx = {}) => { + if (!canAddMarkers(mapObj, tribeMembers)) return null; + const mkLat = ctx.mkLat || ""; + const mkLng = ctx.mkLng || ""; + const base = `/maps/${encodeURIComponent(mapObj.key)}`; + const filter = params.filter || "all"; + const viewVal = ctx.view ? viewParam(ctx.view) : ""; + const mkCleanUrl = `${base}?${buildQuery({ filter, view: viewVal, focus: ctx.focus >= 0 ? ctx.focus : undefined })}#add-marker`; return div({ class: "map-marker-form", id: "add-marker" }, h3(i18n.mapAddMarkerTitle), - form({ method: "POST", action: `/maps/${encodeURIComponent(mapObj.key)}/marker`, class: "map-form", enctype: "multipart/form-data" }, - returnTo ? input({ type: "hidden", name: "returnTo", value: returnTo }) : null, + form({ id: MARKER_FORM_ID, method: "POST", action: `${base}/marker`, class: "map-form", enctype: "multipart/form-data" }, + returnTo ? input({ type: "hidden", name: "returnTo", value: returnTo }) : null, + input({ type: "hidden", name: "filter", value: filter }), + viewVal ? input({ type: "hidden", name: "view", value: viewVal }) : null, + ctx.focus >= 0 ? input({ type: "hidden", name: "focus", value: String(ctx.focus) }) : null, + pickedSpot(mkLat, mkLng), label(i18n.mapMarkerLabelField), - textarea({ name: "label", placeholder: i18n.mapMarkerLabelPlaceholder, rows: "3" }, params.mkMarkerLabel || ""), + textarea({ maxlength: "5000", name: "label", placeholder: i18n.mapMarkerLabelPlaceholder, rows: "3" }, params.mkMarkerLabel || ""), label(i18n.markerImageLabel || "Marker Image"), input({ type: "file", name: "image", accept: "image/*" }), - br(),br(), + br(), br(), label(i18n.mapMarkerLatLabel), input({ type: "text", name: "mkLat", placeholder: i18n.mapLatPlaceholder, value: String(mkLat) }), label(i18n.mapMarkerLngLabel), input({ type: "text", name: "mkLng", placeholder: i18n.mapLngPlaceholder, value: String(mkLng) }), - div({ class: "map-form-row" }, - button({ type: "submit", attrs: { formmethod: "GET" }, formaction: `/maps/${encodeURIComponent(mapObj.key)}`, class: "filter-btn" }, i18n.mapAddMarkerButton || "Add Marker"), - a({ href: mkCleanUrl, class: "filter-btn" }, i18n.mapCleanMarkerButton || "Clean Marker")), - renderCoordPreview(mkLat, mkLng), - label(i18n.mapZoomLabel || "Zoom"), - select({ name: "zoom" }, - [2, 3, 4, 5, 6, 7, 8].map(z => - option({ value: String(z), ...(zoomVal === z ? { selected: true } : {}) }, String(z)))), - br(),br(), - button({ type: "submit", attrs: { formmethod: "GET" }, formaction: `/maps/${encodeURIComponent(mapObj.key)}`, class: "filter-btn" }, i18n.mapApplyZoom || "Apply Zoom"), - div({ class: "map-form-map-slot" }, - renderMap(existingMarkers, clickUrl, 0, { latParam: "mkLat", lngParam: "mkLng", pinLabels, pinPrefix: `mk${areaCounter}`, zoom: zoomVal, centerLat: parseFloat(mkLat) || parseFloat(mapObj.lat) || 0, centerLng: parseFloat(mkLng) || parseFloat(mapObj.lng) || 0 })), - button({ type: "submit", class: "create-button" }, i18n.mapAddMarkerButton))); + div({ class: "map-form-row map-form-actions" }, + button({ type: "submit", class: "create-button" }, i18n.mapAddMarkerButton), + a({ href: mkCleanUrl, class: "filter-btn" }, i18n.mapClearPick)))); }; -const renderMarkersList = (markers, mapObj) => { - const allMarkers = []; - if (mapObj) { - allMarkers.push({ - lat: mapObj.lat, - lng: mapObj.lng, - label: mapObj.markerLabel || mapObj.description || mapObj.title || i18n.mapMarkerDefault, - author: mapObj.author, - createdAt: mapObj.createdAt - }); - } - allMarkers.push(...safeArr(markers)); +const popupNotes = (s, n = 600) => { + const text = String(s || "").trim(); + if (text.length <= n) return text; + const cut = text.slice(0, n); + return cut.slice(0, Math.max(cut.lastIndexOf(" "), n - 40)) + "…"; +}; + +const renderPinPopup = (mk, i, ctx = {}) => { + const lat = numOrNull(mk.lat) === null ? 0 : numOrNull(mk.lat); + const lng = numOrNull(mk.lng) === null ? 0 : numOrNull(mk.lng); + const base = ctx.publicBase || ""; + const notes = ctx.notesHtml ? ctx.notesHtml(popupNotes(mk.label)) : renderStyledHtml(popupNotes(mk.label)); + const own = !ctx.readOnly && !mk.root && mk.key && String(mk.author) === String(userId); + const parts = [ + `
${mk.root ? "★" : i}` + + (ctx.closeHref ? `×` : "") + `
`, + isBlob(mk.image) ? `` : "", + notes ? `
${notes}
` : "", + `${lat.toFixed(4)}, ${lng.toFixed(4)}`, + `
` + (ctx.readOnly ? "" : userLink(mk.author).outerHTML) + + (mk.createdAt ? `${escapeHtml(moment(mk.createdAt).fromNow())}` : "") + `
`, + `
` + + (ctx.centerHref ? `${escapeHtml(i18n.mapCenterHere)}` : "") + + (own ? form({ method: "POST", action: contentDeleteAction('mapMarker', mk.key) }, + ctx.returnTo ? input({ type: "hidden", name: "returnTo", value: ctx.returnTo }) : null, + button({ type: "submit", class: "btn-singleview btn-delete", title: i18n.mapDeleteMarker }, "✕")).outerHTML : "") + `
` + ]; + return parts.join(""); +}; + +const renderMarkersList = (markers, mapObj, ctx = {}) => { + const allMarkers = mapObj ? allMarkersOf({ ...mapObj, markers }) : safeArr(markers); if (!allMarkers.length) return null; + const focus = Number.isInteger(ctx.focus) ? ctx.focus : -1; + const base = ctx.publicBase || ""; return div({ class: "map-markers-list" }, - h3(i18n.mapMarkersTitle), - br(), - div(allMarkers.flatMap((mk, i) => [ - ...(i > 0 ? [br()] : []), - div({ class: "map-marker-info" }, - span({ class: "map-marker-dot" }, "ꔌ"), - span({ class: "map-marker-coords" }, `${(typeof mk.lat === 'number' ? mk.lat : 0).toFixed(4)}, ${(typeof mk.lng === 'number' ? mk.lng : 0).toFixed(4)}`), - span({ class: "map-marker-meta" }, - userLink(mk.author), - ` · ${moment(mk.createdAt).fromNow()}`)) - ]))); + h3(`${i18n.mapMarkersTitle} (${allMarkers.length})`), + div(allMarkers.map((mk, i) => { + const lat = numOrNull(mk.lat) === null ? 0 : numOrNull(mk.lat); + const lng = numOrNull(mk.lng) === null ? 0 : numOrNull(mk.lng); + const centerHref = ctx.viewHref ? ctx.viewHref({ zoom: MAX_ZOOM, lat, lng }, { focus: i }) : null; + const own = !ctx.readOnly && !mk.root && mk.key && String(mk.author) === String(userId); + return div({ class: "map-marker-row" + (i === focus ? " map-marker-row--focus" : ""), id: `marker-${i}` }, + span({ class: "map-marker-idx" + (mk.root ? " map-marker-idx--main" : "") }, mk.root ? "★" : String(i)), + div({ class: "map-marker-body" }, + div({ class: "map-marker-text" }, + firstLine(mk.label) ? span({ class: "map-marker-label" }, firstLine(mk.label)) : null, + span({ class: "map-marker-coords" }, `${lat.toFixed(4)}, ${lng.toFixed(4)}`), + span({ class: "map-marker-meta" }, + ctx.readOnly ? null : userLink(mk.author), + mk.createdAt ? span({ class: "map-marker-date" }, moment(mk.createdAt).fromNow()) : null)), + isBlob(mk.image) ? img({ src: blobSrc(mk.image, base), class: "map-marker-thumb", alt: "" }) : null), + div({ class: "map-marker-actions" }, + centerHref ? a({ href: centerHref, class: "map-center-link" }, i18n.mapCenterHere) : null, + own ? form({ method: "POST", action: contentDeleteAction('mapMarker', mk.key) }, + ctx.returnTo ? input({ type: "hidden", name: "returnTo", value: ctx.returnTo }) : null, + button({ type: "submit", class: "btn-singleview btn-delete", title: i18n.mapDeleteMarker }, "✕")) : null)); + }))); }; -const renderMapCard = (mapObj, filter, params = {}) => { - const returnTo = buildReturnTo(filter, params); +const renderMapCard = exports.renderMapCard = (mapObj, filter, params = {}) => { + const returnTo = params.returnTo || buildReturnTo(filter, params); const markerCount = safeArr(mapObj.markers).length + 1; const thumbMarkers = [{ lat: mapObj.lat, lng: mapObj.lng }].concat( safeArr(mapObj.markers).map((m) => ({ lat: m.lat, lng: m.lng }))); - const thumbFile = renderMapWithPins(thumbMarkers, 0); - const thumbSrc = thumbFile ? `/mapcache/${thumbFile}` : "/assets/images/worldmap-z2.png"; + const origin = mapObj.tribeOrigin || null; const chips = [ renderStateChip("whole", "🗺", String(mapObj.mapType || "").toUpperCase()), - renderEncryptedChip(i18n), - renderLifespanChip(mapObj.lifetime, i18n) + origin ? renderTribeOriginChip(origin) : renderEncryptedChip(i18n), + renderLifespanChip(mapObj.lifetime, i18n), + !origin && mapObj.clearnet === true && clearnetEligible(mapObj) ? renderReachChip(true, i18n, mapClearnetHref(mapObj)) : null ].filter(Boolean); const isOwn = mapObj.author && String(mapObj.author) === String(userId); - const href = `/maps/${encodeURIComponent(mapObj.key)}?filter=${encodeURIComponent(filter)}`; + const href = origin ? origin.href : `/maps/${encodeURIComponent(mapObj.key)}?filter=${encodeURIComponent(filter)}`; return div({ class: "tribe-card" + (isOwn ? " own-content" : "") }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(mapObj.key, href, { spread: params.spreadMap && params.spreadMap.get(mapObj.key) || null, author: mapObj.author, favKind: 'maps', isFavorite: mapObj.isFavorite, reportTitle: mapObj.title }) + renderContentActions(mapObj.key, href, origin + ? { author: mapObj.author, reportTitle: mapObj.title } + : { spread: params.spreadMap && params.spreadMap.get(mapObj.key) || null, author: mapObj.author, favKind: 'maps', isFavorite: mapObj.isFavorite, reportTitle: mapObj.title, returnTo, deleteAction: isOwn ? contentDeleteAction('map', mapObj.key) : undefined }) ), - div({ class: "tribe-card-image-wrapper" }, - a({ href }, img({ src: thumbSrc, class: "tribe-card-hero-image", alt: "map" })) + div({ class: "tribe-card-image-wrapper map-card-thumb" }, + a({ href }, renderMap(thumbMarkers, null, 0, { thumb: true, singleZoom: 5 })) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, @@ -365,7 +430,7 @@ const renderMapCard = (mapObj, filter, params = {}) => { ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, p({ class: "job-meta-line" }, `📍 ${mapObj.lat.toFixed(4)}, ${mapObj.lng.toFixed(4)}`), - safeText(mapObj.description) ? p({ class: "tribe-card-description" }, mapObj.description) : null, + safeText(mapObj.description) ? p({ class: "tribe-card-description" }, ...renderStyledText(safeText(mapObj.description))) : null, div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.mapMarkersTitle || "Markers"}: ${markerCount}`) ) @@ -397,21 +462,31 @@ exports.mapsView = async (maps, filter = "all", mapId = null, params = {}) => { const list = safeArr(maps); const mapToEdit = mapId ? list.find((m) => m.key === mapId) : null; const allMarkers = list.map((m) => ({ lat: m.lat, lng: m.lng, href: `/maps/${encodeURIComponent(m.key)}` })); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const censusMp = Array.isArray(params.censusList) ? params.censusList : list; + const mapsChip = (mode) => { + if (mode === filter) return true; + if (mode === "mine") return censusMp.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusMp.length > 0; + if (mode === "favorites") return censusMp.some((x) => x.isFavorite); + return true; + }; return template(title, section( - div({ class: "tags-header" }, h2(title), p(i18n.mapDescription)), - renderFilters(filter, q)), + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.mapDescription)), + renderFilters(filter, q, emptyMod, mapsChip)), section( filter === "create" || filter === "edit" ? renderMapForm(filter, mapId, mapToEdit, { ...params, filter }) : section( - div({ class: "maps-search" }, + emptyMod ? null : div({ class: "maps-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form({ method: "GET", action: "/maps", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: q, placeholder: i18n.mapSearchPlaceholder, class: "filter-box__input" }), div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.mapSearchButton)))), - div({ class: "jobs-grid" }, renderMapList(list, filter, { q }))))); + div({ class: "jobs-grid" }, renderMapList(paged(list), filter, { q, spreadMap: params.spreadMap }))))); }; exports.singleMapView = async (mapObj, filter = "all", params = {}) => { @@ -419,24 +494,43 @@ exports.singleMapView = async (mapObj, filter = "all", params = {}) => { const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q }); const ownerActions = renderMapOwnerActions(filter, mapObj, { q }); const tribeMembers = safeArr(params.tribeMembers); - const zoomVal = parseInt(params.zoom) || 8; + const canClearnet = clearnetEligible(mapObj); + const isClearnet = canClearnet && mapObj.clearnet === true; - const allMarkers = [{ lat: mapObj.lat, lng: mapObj.lng }].concat( - safeArr(mapObj.markers).map((m) => ({ lat: m.lat, lng: m.lng }))); + const base = `/maps/${encodeURIComponent(mapObj.key)}`; + const allMarkers = allMarkersOf(mapObj); + const focusRaw = parseInt(lastOf(params.focus)); + const focus = Number.isInteger(focusRaw) && focusRaw >= 0 && focusRaw < allMarkers.length ? focusRaw : -1; + const picked = parsePick(params.pick); + const mkLat = picked ? String(picked.lat) : safeText(lastOf(params.mkLat)); + const mkLng = picked ? String(picked.lng) : safeText(lastOf(params.mkLng)); + const placeQ = safeText(lastOf(params.place)); + const placeHits = placeQ ? searchPlaces(placeQ, 5) : null; + const explicitView = parseView(params.view); + const view = explicitView + ? makeView(explicitView.lat, explicitView.lng, explicitView.zoom) + : (placeHits && placeHits.length ? viewForPlace(placeHits[0]) : resolveView({ zoom: params.zoom, clat: params.clat, clng: params.clng }, allMarkers, { singleZoom: 8 })); + const baseParams = { filter, q, mkLat: mkLat || undefined, mkLng: mkLng || undefined, focus: focus >= 0 ? focus : undefined }; + const viewHref = viewHrefFor(base, baseParams, "#map"); + const pinHrefs = allMarkers.map((m, i) => viewHref({ zoom: view.zoom, lat: m.lat, lng: m.lng }, { focus: i })); + const canAdd = canAddMarkers(mapObj, tribeMembers); + const pageReturnTo = viewHref(view); + const popup = focus >= 0 ? renderPinPopup(allMarkers[focus], focus, { + closeHref: viewHrefFor(base, { ...baseParams, focus: undefined }, "#map")(view), + centerHref: viewHref({ zoom: MAX_ZOOM, lat: allMarkers[focus].lat, lng: allMarkers[focus].lng }, { focus }), + returnTo: pageReturnTo + }) : null; - const pinLabels = [mapObj.markerLabel || mapObj.description || mapObj.title || ""].concat( - safeArr(mapObj.markers).map((m) => m.label || "")); - const pinImages = [mapObj.image || ""].concat(safeArr(mapObj.markers).map((m) => m.image || "")); - - const mapSide = div({ class: "tribe-side" }, + const mapSide = div({ class: "tribe-side map-detail-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(mapObj.key, null, { + renderContentActions(mapObj.key, `/maps/${encodeURIComponent(mapObj.key)}`, { author: mapObj.author, favKind: 'maps', isFavorite: mapObj.isFavorite, spread: params.spreads || null, returnTo, - reportTitle: mapObj.title + reportTitle: mapObj.title, + deleteAction: String(mapObj.author) === String(userId) ? contentDeleteAction('map', mapObj.key) : undefined }) ), div({ class: "shop-title-row" }, @@ -445,9 +539,11 @@ exports.singleMapView = async (mapObj, filter = "all", params = {}) => { div({ class: "card-chips-row" }, renderStateChip("whole", "🗺", String(mapObj.mapType || "").toUpperCase()), renderEncryptedChip(i18n), - renderLifespanChip(mapObj.lifetime, i18n) + renderLifespanChip(mapObj.lifetime, i18n), + canClearnet ? renderReachChip(isClearnet, i18n, isClearnet ? mapClearnetHref(mapObj) : null) : null, + canClearnet && String(mapObj.author) === String(userId) ? renderClearnetSwitch("maps", mapObj.rootId || mapObj.key, isClearnet) : null ), - safeText(mapObj.description) ? p({ class: "tribe-side-description" }, mapObj.description) : null, + safeText(mapObj.description) ? p({ class: "tribe-side-description" }, ...renderStyledText(safeText(mapObj.description))) : null, table({ class: "tribe-info-table jobs-info-table" }, tr( td({ class: "tribe-info-label" }, i18n.mapLatLabel), @@ -471,47 +567,204 @@ exports.singleMapView = async (mapObj, filter = "all", params = {}) => { ), (String(mapObj.author) !== String(userId) && !mapObj.tribeId && mapObj.mapType !== "OPEN") ? div({ class: "tribe-side-actions" }, - a({ class: "tribe-action-btn", href: "/invites#invites-maps" }, i18n.tribeEnterInvite) + a({ class: "tribe-action-btn", href: "/invites?filter=others" }, i18n.tribeEnterInvite) ) : null, + renderDocumentActions('maps', mapObj.key), ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null ); - const mapMain = div({ class: "tribe-main" }, + const hiddenState = (withView) => [ + input({ type: "hidden", name: "filter", value: filter }), + q ? input({ type: "hidden", name: "q", value: q }) : null, + mkLat && mkLng ? input({ type: "hidden", name: "mkLat", value: mkLat }) : null, + mkLat && mkLng ? input({ type: "hidden", name: "mkLng", value: mkLng }) : null, + focus >= 0 ? input({ type: "hidden", name: "focus", value: String(focus) }) : null, + withView ? input({ type: "hidden", name: "clat", value: String(view.lat) }) : null, + withView ? input({ type: "hidden", name: "clng", value: String(view.lng) }) : null + ]; + + const placeSearch = form({ method: "GET", action: base, class: "filter-box" }, + ...hiddenState(false), + input({ type: "text", name: "place", value: placeQ, placeholder: i18n.mapPlaceSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.mapSearchButton))); + + const mapHero = div({ class: "map-hero" }, + renderPlaceResults(placeHits, (h) => a({ href: viewHref(viewForPlace(h)), class: "map-place-result" }, + span(h.name), h.country ? span({ class: "map-place-result-country" }, h.country) : null)), renderMapUrl(mapObj), - renderMap(allMarkers, null, 0, { pinLabels, pinImages, pinPrefix: `detail${areaCounter}`, zoom: zoomVal, centerLat: parseFloat(mapObj.lat) || 0, centerLng: parseFloat(mapObj.lng) || 0 }), - form({ method: "GET", action: `/maps/${encodeURIComponent(mapObj.key)}` }, - label(i18n.mapZoomLabel || "Zoom"), - br(), - select({ name: "zoom" }, - [2, 3, 4, 5, 6, 7, 8].map(z => - option({ value: String(z), ...(zoomVal === z ? { selected: true } : {}) }, String(z)))), - br(), br(), - button({ type: "submit", class: "filter-btn" }, i18n.mapApplyZoom || "Apply Zoom")), - renderMarkersList(mapObj.markers, mapObj), + renderMap(allMarkers, null, 0, { + id: "map", + view, + focus, + pinHrefs, + popup, + nav: { href: viewHref }, + viewHref, + pick: mkLat && mkLng ? { lat: mkLat, lng: mkLng } : null, + pickForm: canAdd ? MARKER_FORM_ID : null, + pickAction: base + })); + + const mapMain = div({ class: "tribe-main map-detail-main" }, + renderMarkersList(mapObj.markers, mapObj, { focus, view, viewHref, returnTo: pageReturnTo }), p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(mapObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(mapObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(mapObj.author), mapObj.updatedAt && mapObj.updatedAt !== mapObj.createdAt ? span({ class: "votations-comment-date" }, ` · ${i18n.mapUpdatedAt}: ${moment(mapObj.updatedAt).format("YYYY/MM/DD HH:mm")}`) : null), - renderMarkerForm(mapObj, returnTo, params, tribeMembers) + renderMarkerForm(mapObj, returnTo, { ...params, filter }, tribeMembers, { mkLat, mkLng, view, focus }) ); return template(mapObj.title || i18n.mapTitle, - section(div({ class: "tags-header" }, h2(i18n.mapTitle), p(i18n.mapDescription))), - section(renderFilters(filter, q)), - section(div({ class: "tribe-details" }, mapSide, mapMain))); + section(div({ class: "tags-header module-header-line" }, h2(i18n.mapTitle), p(i18n.mapDescription))), + section(renderFilters(filter, q, false, null, placeSearch)), + section(mapHero, div({ class: "tribe-details map-detail-grid" }, mapSide, mapMain))); }; -exports.renderMapLocationUrl = (mapUrl) => { +exports.clearnetMapView = async (mapObj, params = {}) => { + const { escapeHtml: esc, renderRichText, renderKindTag, renderTagChips, renderClearnetPage } = require("./clearnet_view"); + const base = clearnetItemHref("maps", mapObj.title, mapObj.rootId || mapObj.key); + const allMarkers = allMarkersOf(mapObj); + const focusRaw = parseInt(lastOf(params.focus)); + const focus = Number.isInteger(focusRaw) && focusRaw >= 0 && focusRaw < allMarkers.length ? focusRaw : -1; + const explicitView = parseView(params.view); + const view = explicitView + ? makeView(explicitView.lat, explicitView.lng, explicitView.zoom) + : resolveView({ zoom: params.zoom, clat: params.clat, clng: params.clng }, allMarkers, { singleZoom: 8 }); + const viewHref = viewHrefFor(base, { focus: focus >= 0 ? focus : undefined }, "#map"); + const pinHrefs = allMarkers.map((m, i) => viewHref({ zoom: view.zoom, lat: m.lat, lng: m.lng }, { focus: i })); + const popup = focus >= 0 ? renderPinPopup(allMarkers[focus], focus, { + readOnly: true, + publicBase: "/c", + notesHtml: renderRichText, + closeHref: viewHrefFor(base, {}, "#map")(view), + centerHref: viewHref({ zoom: MAX_ZOOM, lat: allMarkers[focus].lat, lng: allMarkers[focus].lng }, { focus }) + }) : null; + const mapHtml = renderMap(allMarkers, null, 0, { id: "map", view, focus, pinHrefs, popup, nav: { href: viewHref }, viewHref, publicBase: "/c" }).outerHTML; + const name = mapObj.title || i18n.cnKindMap; + const desc = renderRichText(mapObj.description || ""); + const coord = (v) => (typeof v === "number" ? v : parseFloat(v) || 0).toFixed(4); + const extraCss = ` +.cn-map-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700;word-break:break-word} +.cn-map-meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:16px} +.cn-map-desc{color:var(--fg-soft);line-height:1.6;margin:16px 0} +.map-viewer{width:100%} +.map-stage{position:relative;width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;overflow:hidden;line-height:0} +.map-svg{display:block;width:100%;height:auto;font-family:inherit} +.map-pins-layer{position:absolute;top:0;left:0;width:100%;height:100%;z-index:4;pointer-events:none} +.map-pins-layer a{pointer-events:auto} +.map-ocean{fill:#a7c6de} +.map-tile{image-rendering:auto} +.map-grat{fill:none;stroke:rgba(255,255,255,.4);stroke-width:.6} +.map-border{fill:none;stroke:rgba(72,52,32,.65);stroke-width:.8;stroke-linejoin:round} +.map-lake{fill:none;stroke:rgba(36,86,128,.7);stroke-width:.7;stroke-linejoin:round} +.map-state{fill:none;stroke:rgba(72,52,32,.5);stroke-width:.6;stroke-dasharray:3 2} +.map-coast{fill:none;stroke:rgba(36,86,128,.75);stroke-width:.8;stroke-linejoin:round} +.map-label-country{fill:#262c31;font-size:14px;font-weight:600;letter-spacing:.6px;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.85);stroke-width:3px;stroke-linejoin:round} +.map-place-dot{fill:#1f252a;stroke:#fff;stroke-width:1} +.map-place-capital{fill:#d35400} +.map-place-label{fill:#1f252a;font-size:12px;paint-order:stroke;stroke:rgba(255,255,255,.85);stroke-width:3px;stroke-linejoin:round} +.map-place-label-capital{font-weight:700} +.map-pin-body{fill:#3498db;stroke:#fff;stroke-width:1.5} +.map-pin-main .map-pin-body{fill:#e74c3c} +.map-pin-focus .map-pin-body{fill:var(--accent);stroke-width:2.2} +.map-pin-eye{fill:#fff} +.map-pin-label-bg{fill:var(--bg-elev);stroke:var(--border);stroke-width:.8} +.map-pin-focus .map-pin-label-bg{stroke:var(--accent);stroke-width:1.2} +.map-pin-label{fill:var(--fg);font-size:12px;font-weight:700;text-anchor:middle} +.map-pin-img-frame{fill:#fff} +.map-pins-layer a:hover .map-pin-body,.map-pins a:hover .map-pin-body{fill:var(--accent)} +.map-cluster-ring{fill:var(--bg-elev);opacity:.7} +.map-cluster-dot{fill:var(--accent);stroke:#fff;stroke-width:1.6} +.map-cluster-count{fill:#000;font-size:13px;font-weight:700;text-anchor:middle} +.map-scale-line{fill:none;stroke:#1f252a;stroke-width:1.6} +.map-scale-text{fill:#1f252a;font-size:11px;font-weight:600;paint-order:stroke;stroke:rgba(255,255,255,.85);stroke-width:3px} +.map-attrib{fill:#3b434a;font-size:9px;text-anchor:end;paint-order:stroke;stroke:rgba(255,255,255,.75);stroke-width:2px} +.map-controls{position:absolute;z-index:3;display:flex;flex-direction:column;gap:4px;line-height:1} +.map-controls-zoom{top:10px;left:10px} +.map-controls-pan{top:10px;right:10px;display:grid;grid-template-columns:repeat(3,30px);grid-template-rows:repeat(3,30px);gap:2px} +.map-ctrl{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-elev);color:var(--accent);border:1px solid var(--border);border-radius:6px;text-decoration:none;font-size:17px;font-weight:700;font-family:inherit;box-sizing:border-box} +.map-ctrl:hover{background:var(--accent);color:var(--bg);border-color:var(--accent);text-decoration:none} +.map-ctrl-off{opacity:.35} +.map-ctrl-off:hover{background:var(--bg-elev);color:var(--accent);border-color:var(--border)} +.cn-map-marker{display:flex;flex-wrap:wrap;gap:10px;align-items:center;padding:8px 6px;border-bottom:1px solid var(--border);color:var(--fg-soft)} +.cn-map-marker--focus{background:var(--bg-sub);box-shadow:inset 3px 0 0 var(--accent)} +.cn-map-marker-idx{min-width:24px;height:24px;border-radius:50%;background:#3498db;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:700} +.cn-map-marker-idx--main{background:#e74c3c} +.cn-map-marker--focus .cn-map-marker-idx{background:var(--accent);color:#000} +.cn-map-marker-thumb{width:48px;height:48px;object-fit:cover;border-radius:4px} +.cn-map-marker-text{flex:1;min-width:160px;display:flex;flex-direction:column;gap:2px} +.cn-map-marker-coords{font-family:monospace;color:var(--fg);font-size:13px} +.cn-map-marker-label{word-break:break-word;color:var(--fg)} +.cn-map-marker-date{font-size:12px;color:var(--fg-dim)} +.cn-map-center{color:var(--accent);text-decoration:none;font-size:13px;border:1px solid var(--border);border-radius:6px;padding:4px 8px;white-space:nowrap;margin-left:auto} +.cn-map-center:hover{background:var(--accent);color:var(--bg);text-decoration:none} +.map-popup{pointer-events:auto} +.map-popup-pointer{fill:var(--bg-elev);stroke:var(--border);stroke-width:1} +.map-popup-wrap{height:100%;display:flex;flex-direction:column;justify-content:flex-end;line-height:1.35} +.map-popup--below .map-popup-wrap{justify-content:flex-start} +.map-popup-card{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--fg);font-size:13px;box-sizing:border-box;max-height:100%;overflow:hidden;display:flex;flex-direction:column;gap:6px;text-align:left} +.map-popup-card>*{flex-shrink:0} +.map-popup-head{display:flex;justify-content:space-between;align-items:center;gap:8px} +.map-popup-head .map-marker-idx{min-width:24px;height:24px;border-radius:50%;background:#3498db;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:700} +.map-popup-head .map-marker-idx--main{background:#e74c3c} +.map-popup-close{color:var(--fg-dim);text-decoration:none;font-size:20px;line-height:1} +.map-popup-close:hover{color:var(--accent);text-decoration:none} +.map-popup-img{width:100%;max-height:120px;object-fit:cover;border-radius:4px;display:block} +.map-popup-text{color:var(--fg);word-break:break-word;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden} +.map-popup-text img{max-width:100%;max-height:100px} +.map-popup-coords{font-family:monospace;font-size:12px;color:var(--fg-soft)} +.map-popup-meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12px;color:var(--fg-dim)} +.map-popup-actions{display:flex;gap:6px;align-items:center} +.map-popup-actions .cn-map-center,.map-popup-actions .map-center-link{margin-left:0;color:var(--accent);text-decoration:none;font-size:13px;border:1px solid var(--border);border-radius:6px;padding:4px 8px} +`; + const markerRow = (mk, i) => { + const centerHref = viewHref({ zoom: MAX_ZOOM, lat: numOrNull(mk.lat) || 0, lng: numOrNull(mk.lng) || 0 }, { focus: i }); + const label = firstLine(mk.label); + return `
` + + `${mk.root ? "★" : i}` + + (isBlob(mk.image) ? `` : "") + + `${label ? `${esc(label)}` : ""}` + + `${coord(mk.lat)}, ${coord(mk.lng)}` + + (mk.createdAt ? `${esc(moment(mk.createdAt).format("YYYY/MM/DD"))}` : "") + + `${esc(i18n.mapCenterHere)}
`; + }; + const body = ` +

${esc(name)}

+
+ ${renderKindTag("map")} + ${mapObj.createdAt ? `📅 ${esc(moment(mapObj.createdAt).format("YYYY/MM/DD"))}` : ""} + 📍 ${coord(mapObj.lat)}, ${coord(mapObj.lng)} +
+ ${desc ? `

${desc}

` : ""} + ${renderTagChips(mapObj.tags)} +
+ ${mapHtml} +

${esc(i18n.mapMarkersTitle || "Markers")} (${allMarkers.length})

+ ${allMarkers.map(markerRow).join("")} +`; + return renderClearnetPage({ + title: `${name} | Oasis`, + ogTitle: name, + ogDescription: mapObj.description || "", + extraCss, + body, + hubFeedId: mapObj.author || null + }); +}; + +exports.renderMapLocationUrl = (rawMapUrl) => { + const mapUrl = localHref(rawMapUrl); if (!mapUrl) return null; return span({ class: "map-location-inline" }, span({ class: "map-location-icon" }, "ꔌ"), a({ href: mapUrl, class: "map-location-link" }, mapUrl)); }; -exports.renderMapLocationVisitLabel = (mapUrl) => { +exports.renderMapLocationVisitLabel = (rawMapUrl) => { + const mapUrl = localHref(rawMapUrl); if (!mapUrl) return null; return div({ class: "card-field" }, span({ class: "card-label" }, (i18n.mapLocationTitle || "Map Location") + ":"), @@ -519,7 +772,8 @@ exports.renderMapLocationVisitLabel = (mapUrl) => { a({ href: mapUrl, class: "map-location-link" }, i18n.mapVisitLabel || "Visit map"))); }; -exports.renderMapEmbed = (mapData, mapUrl) => { +exports.renderMapEmbed = (mapData, rawMapUrl) => { + const mapUrl = localHref(rawMapUrl); if (!mapData || (parseFloat(mapData.lat) === 0 && parseFloat(mapData.lng) === 0)) return exports.renderMapLocationVisitLabel(mapUrl); return div({ class: "map-embed-section" }, @@ -530,7 +784,8 @@ exports.renderMapEmbed = (mapData, mapUrl) => { a({ href: mapUrl, class: "map-location-link" }, mapUrl)) : null); }; -exports.renderMapEmbedWithZoom = (mapData, mapUrl, detailUrl, zoom) => { +exports.renderMapEmbedWithZoom = (mapData, rawMapUrl, detailUrl, zoom) => { + const mapUrl = localHref(rawMapUrl); if (!mapData || (parseFloat(mapData.lat) === 0 && parseFloat(mapData.lng) === 0)) return exports.renderMapLocationVisitLabel(mapUrl); const zoomVal = parseInt(zoom) || 2; @@ -539,14 +794,6 @@ exports.renderMapEmbedWithZoom = (mapData, mapUrl, detailUrl, zoom) => { return div({ class: "map-embed-section" }, span({ class: "card-label" }, (i18n.mapLocationTitle || "Map Location") + ":"), renderMap([{ lat: la, lng: lo }], null, 0, { zoom: zoomVal, centerLat: la, centerLng: lo }), - form({ method: "GET", action: detailUrl }, - label(i18n.mapZoomLabel || "Zoom"), - br(), - select({ name: "zoom" }, - [2, 3, 4, 5, 6, 7, 8].map(z => - option({ value: String(z), ...(zoomVal === z ? { selected: true } : {}) }, String(z)))), - br(), br(), - button({ type: "submit", class: "filter-btn" }, i18n.mapApplyZoom || "Apply Zoom")), mapUrl ? div({ class: "map-embed-url" }, a({ href: mapUrl, class: "map-location-link" }, mapUrl)) : null); }; @@ -554,5 +801,5 @@ exports.renderMapEmbedWithZoom = (mapData, mapUrl, detailUrl, zoom) => { exports.renderMapLocationGrid = (lat, lng) => { if (lat === undefined || lng === undefined) return null; return div({ class: "map-location-embed" }, - renderMap([{ lat: parseFloat(lat) || 0, lng: parseFloat(lng) || 0 }], null, 0)); + renderMap([{ lat: parseFloat(lat) || 0, lng: parseFloat(lng) || 0 }], null, 0, { zoom: MIN_ZOOM, centerLat: parseFloat(lat) || 0, centerLng: parseFloat(lng) || 0 })); }; diff --git a/src/views/markdown.js b/src/views/markdown.js deleted file mode 100644 index 4e5d040..0000000 --- a/src/views/markdown.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; - -const md = require("../server/node_modules/ssb-markdown"); -const ssbMessages = require("../server/node_modules/ssb-msgs"); -const ssbRef = require("../server/node_modules/ssb-ref"); -const { span } = require("../server/node_modules/hyperaxe"); - -const toUrl = (mentions) => { - const mentionNames = []; - - const handleLink = ({ name, link }) => { - if (typeof name === "string") { - const atName = name.charAt(0) === "@" ? name : `@${name}`; - mentionNames.push({ name: atName, link }); - } - }; - - ssbMessages.links(mentions, "feed").forEach(handleLink); - - const urlHandler = (ref) => { - const found = mentionNames.find(({ name }) => name === ref); - if (found) return `/author/${encodeURIComponent(found.link)}`; - - if (ssbRef.isFeedId(ref)) return `/author/${encodeURIComponent(ref)}`; - if (ssbRef.isMsgId(ref)) return `/thread/${encodeURIComponent(ref)}`; - - const splitIndex = ref.indexOf("?"); - const blobRef = splitIndex === -1 ? ref : ref.slice(0, splitIndex); - - if (ssbRef.isBlobId(blobRef)) return `/blob/${encodeURIComponent(blobRef)}`; - if (ref && ref[0] === "#") return `/hashtag/${encodeURIComponent(ref.substr(1))}`; - - return ""; - }; - - return urlHandler; -}; - -module.exports = (input, mentions = []) => - md.block(input, { - toUrl: toUrl(mentions), - emoji: (character) => span({ class: "emoji" }, character).outerHTML, - }); diff --git a/src/views/market_view.js b/src/views/market_view.js index e9c70e2..1ef97fa 100644 --- a/src/views/market_view.js +++ b/src/views/market_view.js @@ -1,27 +1,31 @@ -const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, img, table, tr, th, td, progress, video, audio } = require("../server/node_modules/hyperaxe") +const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, img, table, tr, th, td, progress, video, audio, hr } = require("../server/node_modules/hyperaxe") +const { renderZoomableImage } = require("./gallery_view") const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, userLink, renderStateChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderSpreadEditWarning, renderOpinionsVoting, renderEngagement , renderContentActions } = require("./main_views") +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderVisibilityChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderSpreadEditWarning, renderOpinionsVoting, renderEngagement , renderContentActions, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, renderWalletChip, contentDeleteAction, paged } = require("./main_views") +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view") const opinionCategories = require("../backend/opinion_categories") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const { renderMapLocationUrl, renderMapEmbed, renderMapLocationVisitLabel, renderMapEmbedWithZoom } = require("./maps_view") const renderMediaBlob = (value, fallbackSrc = null, attrs = {}) => { if (!value) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null const s = String(value).trim() if (!s) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: attrs.class || 'post-image' }) + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null } const userId = config.keys.id +const MARKET_REACH = ["PUBLIC", "HIDDEN"] +const marketReachLabel = (v) => v === "HIDDEN" ? (i18n.visibilityHidden || "Hidden") : (i18n.visibilityPublic || "Public") const parseBidEntry = (raw) => { const s = String(raw || "").trim() @@ -109,16 +113,11 @@ const renderStarRating = (opinions, voterCount) => { } const renderCardField = (labelText, value = "") => - div({ class: "card-field" }, span({ class: "card-label" }, labelText), span({ class: "card-value" }, ...renderUrl(String(value)))) + div({ class: "card-field" }, span({ class: "card-label" }, labelText), span({ class: "card-value" }, ...renderStyledText(String(value)))) const renderCardFieldRich = (labelText, parts) => div({ class: "card-field" }, span({ class: "card-label" }, labelText), span({ class: "card-value" }, ...(Array.isArray(parts) ? parts : [parts]))) -const renderPmButton = (recipientId) => - recipientId && String(recipientId) !== String(userId) - ? form({ method: "GET", action: "/pm" }, input({ type: "hidden", name: "recipients", value: recipientId }), button({ type: "submit", class: "filter-btn" }, i18n.privateMessage)) - : null - const renderStockBar = (stockValue, maxValue) => { const s = Math.max(0, Number(stockValue || 0)) const m = Math.max(1, Number(maxValue || s || 1)) @@ -163,7 +162,7 @@ const auctionCountdownParts = (deadline) => { const renderCountdownField = (item) => { const cd = item && (item.item_type === "auction" || item.item_type === "exchange") ? auctionCountdownParts(item.deadline) : null if (!cd) return null - return renderCardFieldRich(`${cd.label}:`, [span({ class: "countdown-strong" }, cd.rel)]) + return renderCardFieldRich(`${cd.label}:`, [span({ class: "time-chip" }, cd.rel)]) } const normStatus = (s) => String(s || "").toUpperCase().replace(/_/g, " ").replace(/\s+/g, " ").trim() @@ -183,13 +182,6 @@ const renderMarketOwnerActions = (item, returnTo) => { ) ) } - out.push( - form( - { method: "POST", action: `/market/delete/${encodeURIComponent(item.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.marketActionsDelete) - ) - ) if (canChange) { out.push( form( @@ -208,16 +200,22 @@ const renderMarketOwnerActions = (item, returnTo) => { return out } -const renderMarketTopbar = (item, returnTo) => { - const left = [renderPmButton(item && item.seller)].filter(Boolean) - const right = item && String(item.seller) === String(userId) ? renderMarketOwnerActions(item, returnTo) : [] - const leftNode = left.length ? div({ class: "bookmark-topbar-left transfer-topbar-left" }, ...left) : null - const rightNode = right.length ? div({ class: "bookmark-actions transfer-actions" }, ...right) : null - const children = [] - if (leftNode) children.push(leftNode) - if (rightNode) children.push(rightNode) - return children.length ? div({ class: "bookmark-topbar transfer-topbar-single" }, ...children) : null -} +const marketChipFor = (filter, list) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(list)) return true; + if (mode === "recent") return list.length > 0; + if (mode === "mine") return list.some((e) => String(e.seller) === String(userId)); + if (mode === "exchange") return list.some((e) => e.item_type === "exchange" && e.status === "FOR SALE"); + if (mode === "auctions") return list.some((e) => e.item_type === "auction" && e.status === "FOR SALE"); + if (mode === "mybids") return list.some(isMyBidItem); + if (mode === "new") return list.some((e) => e.item_status === "NEW" && e.status === "FOR SALE"); + if (mode === "used") return list.some((e) => e.item_status === "USED" && e.status === "FOR SALE"); + if (mode === "broken") return list.some((e) => e.item_status === "BROKEN" && e.status === "FOR SALE"); + if (mode === "for sale") return list.some((e) => normStatus(e.status || "FOR SALE") === "FOR SALE"); + if (mode === "sold") return list.some((e) => normStatus(e.status || "") === "SOLD"); + if (mode === "discarded") return list.some((e) => normStatus(e.status || "") === "DISCARDED"); + return true; +}; exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { const list = Array.isArray(items) ? items : [] @@ -278,6 +276,8 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { filtered = filtered.filter((it) => matchesQuery(it, q)).filter((it) => withinPrice(it, minP, maxP)) filtered = sortItems(filtered, sort) + const emptyMod = moduleIsEmpty(list, filter || "all", "all", q || minPrice || maxPrice); + const marketChipVisible = marketChipFor(filter, list); const returnTo = buildReturnTo(filter, q, minPrice, maxPrice, sort) const itemEdit = itemToEdit || {} @@ -290,43 +290,46 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { const isFormMode = filter === "create" || filter === "edit" const spreadWarning = filter === "edit" ? await renderSpreadEditWarning(itemEdit && (itemEdit.id || itemEdit.key)) : null + const reachRaw = String(params.reach || "").toUpperCase() + const ownReach = String(itemEdit.visibility || "").toUpperCase() + const reach = MARKET_REACH.includes(reachRaw) ? reachRaw : (MARKET_REACH.includes(ownReach) ? ownReach : "PUBLIC") + const prefillKeys = ["industry", "title", "description", "price", "tags", "stock"] return template( title, section( - div({ class: "tags-header" }, h2(i18n.marketTitle), p(i18n.marketDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.marketTitle), p(i18n.marketDescription), renderWalletChip()), div( { class: "filters" }, form( { method: "GET", action: "/market", class: "ui-toolbar ui-toolbar--filters" }, ...hiddenCtx, + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "exchange", class: filter === "exchange" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterItems).toUpperCase()), - button({ type: "submit", name: "filter", value: "auctions", class: filter === "auctions" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterAuctions).toUpperCase()), - button({ type: "submit", name: "filter", value: "mybids", class: filter === "mybids" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMyBids).toUpperCase()), - button({ type: "submit", name: "filter", value: "new", class: filter === "new" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterNew).toUpperCase()), - button({ type: "submit", name: "filter", value: "used", class: filter === "used" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterUsed).toUpperCase()), - button({ type: "submit", name: "filter", value: "broken", class: filter === "broken" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterBroken).toUpperCase()), - button({ type: "submit", name: "filter", value: "for sale", class: filter === "for sale" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterForSale).toUpperCase()), - button({ type: "submit", name: "filter", value: "sold", class: filter === "sold" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterSold).toUpperCase()), - button({ type: "submit", name: "filter", value: "discarded", class: filter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterDiscarded).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterRecent).toUpperCase()), + ...(marketChipVisible("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterRecent).toUpperCase())] : []), + ...(marketChipVisible("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMine).toUpperCase())] : []), + ...(marketChipVisible("exchange") ? [button({ type: "submit", name: "filter", value: "exchange", class: filter === "exchange" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterItems).toUpperCase())] : []), + ...(marketChipVisible("auctions") ? [button({ type: "submit", name: "filter", value: "auctions", class: filter === "auctions" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterAuctions).toUpperCase())] : []), + ...(marketChipVisible("mybids") ? [button({ type: "submit", name: "filter", value: "mybids", class: filter === "mybids" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMyBids).toUpperCase())] : []), + ...(marketChipVisible("new") ? [button({ type: "submit", name: "filter", value: "new", class: filter === "new" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterNew).toUpperCase())] : []), + ...(marketChipVisible("used") ? [button({ type: "submit", name: "filter", value: "used", class: filter === "used" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterUsed).toUpperCase())] : []), + ...(marketChipVisible("broken") ? [button({ type: "submit", name: "filter", value: "broken", class: filter === "broken" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterBroken).toUpperCase())] : []), + ...(marketChipVisible("for sale") ? [button({ type: "submit", name: "filter", value: "for sale", class: filter === "for sale" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterForSale).toUpperCase())] : []), + ...(marketChipVisible("sold") ? [button({ type: "submit", name: "filter", value: "sold", class: filter === "sold" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterSold).toUpperCase())] : []), + ...(marketChipVisible("discarded") ? [button({ type: "submit", name: "filter", value: "discarded", class: filter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterDiscarded).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.marketCreateButton) ) ), - !isFormMode + !isFormMode && !emptyMod ? div( - { class: "market-search" }, + { class: "market-search activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filtered, e => normStatus(e.status || 'FOR SALE'), [{ value: 'FOR SALE', label: i18n.marketFilterForSale }, { value: 'SOLD', label: i18n.marketFilterSold }, { value: 'DISCARDED', label: i18n.marketFilterDiscarded }]), form( { method: "GET", action: "/market", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter || "all" }), input({ type: "text", name: "q", value: q, placeholder: i18n.marketSearchPlaceholder, class: "filter-box__input" }), - div( - { class: "filter-box__controls" }, - div( - { class: "transfer-range" }, - input({ + input({ type: "number", name: "minPrice", step: "0.000001", @@ -343,8 +346,8 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { value: String(maxPrice ?? ""), placeholder: i18n.marketMaxPriceLabel, class: "filter-box__number transfer-amount-input" - }) - ), + }), + select( { name: "sort", class: "filter-box__select" }, option({ value: "recent", ...(sort === "recent" ? { selected: true } : {})}, i18n.marketSortRecent), @@ -352,7 +355,7 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { option({ value: "deadline", ...(sort === "deadline" ? { selected: true } : {})}, i18n.marketSortDeadline) ), button({ type: "submit", class: "filter-box__button" }, i18n.marketSearchButton) - ) + ) ) : null @@ -362,9 +365,27 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { ? div( { class: "market-form" }, spreadWarning, + form( + { method: "GET", action: filter === "edit" ? `/market/edit/${encodeURIComponent(itemEdit.id)}` : "/market" }, + filter === "edit" ? null : input({ type: "hidden", name: "filter", value: "create" }), + ...(filter === "edit" ? [] : prefillKeys.filter((k) => params[k]).map((k) => input({ type: "hidden", name: k, value: params[k] }))), + label(i18n.visibilityLabel || "Visibility"), + br(), + div({ class: "apply-row" }, + select( + { name: "visibility", class: "report-category-select" }, + option({ value: "PUBLIC", ...(reach === "PUBLIC" ? { selected: true } : {})}, i18n.visibilityPublic || "Public"), + option({ value: "HIDDEN", ...(reach === "HIDDEN" ? { selected: true } : {})}, i18n.visibilityHidden || "Hidden") + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, marketReachLabel(reach)), form( { action: filter === "edit" ? `/market/update/${encodeURIComponent(itemEdit.id)}` : "/market/create", method: "POST", enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: "/market?filter=mine" }), + input({ type: "hidden", name: "visibility", value: reach }), ((itemEdit && itemEdit.industry) || params.industry) ? input({ type: "hidden", name: "industry", value: (itemEdit && itemEdit.industry) || params.industry }) : null, label(i18n.marketItemType), br(), @@ -382,7 +403,7 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { br(), label(i18n.marketItemDescription), br(), - textarea({ name: "description", id: "description", placeholder: i18n.marketItemDescriptionPlaceholder, rows: "6", required: true }, (itemEdit && itemEdit.description) || params.description || ""), + textarea({ maxlength: "5000", name: "description", id: "description", placeholder: i18n.marketItemDescriptionPlaceholder, rows: "6", required: true }, (itemEdit && itemEdit.description) || params.description || ""), br(), br(), label(i18n.marketCreateFormImageLabel), @@ -410,18 +431,11 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: itemEdit?.mapUrl || "" }), br(), br(), - label(i18n.visibilityLabel || "Visibility"), - br(), - select( - { name: "visibility" }, - option({ value: "PUBLIC", ...((itemEdit?.visibility || "PUBLIC") === "PUBLIC" ? { selected: true } : {})}, i18n.visibilityPublic || "Public"), - option({ value: "HIDDEN", ...(itemEdit?.visibility === "HIDDEN" ? { selected: true } : {})}, i18n.visibilityHidden || "Hidden") - ), - br(), - br(), + ...(reach === "PUBLIC" ? [renderClearnetSelector(itemEdit.clearnet === true || String(itemEdit.clearnet || "") === "1", i18n), br()] : []), label(i18n.marketItemPrice), br(), input({ type: "number", name: "price", id: "price", value: (itemEdit && itemEdit.price) || params.price || "", required: true, step: "0.000001", min: "0.000001" }), + renderEcoValueChip(), br(), br(), label(i18n.marketItemTags), @@ -450,7 +464,7 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { name: "includesShipping", value: "1", class: "meme-checkbox", - ...(itemEdit && itemEdit.includesShipping ? { checked: true } : {}) + ...(itemEdit && [true, "1", "on", "true"].includes([].concat(itemEdit.includesShipping).pop()) ? { checked: true } : {}) }), br(),br(), button({ type: "submit" }, filter === "edit" ? i18n.marketUpdateButton : i18n.marketCreateButton) @@ -459,36 +473,9 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { : div( { class: "market-grid" }, filtered.length > 0 - ? filtered.map((item) => { - const polls = Array.isArray(item.auctions_poll) ? item.auctions_poll : [] - const parsedBids = polls.map(parseBidEntry).filter(Boolean).sort((a, b) => new Date(b.time) - new Date(a.time)) - const myBid = item.item_type === "auction" ? parsedBids.some((b) => b.bidder === userId) : false - const maxStock = item.initialStock || item.stockMax || item.stock || 1 - const stockLeft = Number(item.stock || 0) + ? paged(filtered).map((item) => { const isOwner = String(item.seller) === String(userId) - const actionNodesRaw = isOwner - ? renderMarketOwnerActions(item, "/market?filter=mine") - : [ - item.status !== "SOLD" && item.status !== "DISCARDED" && item.item_type === "auction" - ? form( - { method: "POST", action: `/market/bid/${encodeURIComponent(item.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - input({ type: "number", name: "bidAmount", step: "0.000001", min: "0.000001", placeholder: i18n.marketYourBid, required: true }), - br(), - button({ class: "buy-btn", type: "submit" }, i18n.marketPlaceBidButton) - ) - : null, - item.status === "FOR SALE" && item.item_type !== "auction" && !isOwner && stockLeft > 0 - ? form( - { method: "POST", action: `/market/buy/${encodeURIComponent(item.id)}` }, - input({ type: "hidden", name: "returnTo", value: "/inbox?filter=sent" }), - input({ type: "hidden", name: "buyerId", value: userId }), - button({ class: "buy-btn", type: "submit" }, i18n.marketActionsBuy) - ) - : null - ].filter(Boolean) - return div({ class: "tribe-card market-tribe-card" }, div({ class: "tribe-card-image-wrapper" }, a({ href: `/market/${encodeURIComponent(item.id)}` }, @@ -497,7 +484,7 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { ), div({ class: "card-header activity-card-header" }, span(), - renderContentActions(item.id, `/market/${encodeURIComponent(item.id)}`, { spread: params.spreads || null, author: item.seller, favKind: 'market', isFavorite: item.isFavorite, reportTitle: item.title }) + renderContentActions(item.id, `/market/${encodeURIComponent(item.id)}`, { spread: (params.spreadMap && params.spreadMap.get(item.id)) || null, author: item.seller, favKind: 'market', isFavorite: item.isFavorite, reportTitle: item.title, returnTo, deleteAction: isOwner ? contentDeleteAction('market', item.id) : undefined }) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, @@ -505,24 +492,26 @@ exports.marketView = async (items, filter, itemToEdit = null, params = {}) => { ), renderStarRating(item.opinions, Array.isArray(item.opinions_inhabitants) ? item.opinions_inhabitants.length : 0), div({ class: "card-chips-row" }, + String(item.visibility || "PUBLIC").toUpperCase() === "HIDDEN" ? renderVisibilityChip("HIDDEN", i18n) : null, renderStateChip("encrypted", "", String(item.item_type || "").toUpperCase()), item.item_status ? renderStateChip("whole", "", String(item.item_status).toUpperCase()) : null, - String(item.visibility || "PUBLIC").toUpperCase() === "HIDDEN" ? renderVisibilityChip("HIDDEN", i18n) : null, item.includesShipping ? renderStateChip("mutuals", "📦", String(i18n.marketItemIncludesShipping || "Shipping").replace(/\?$/, "").toUpperCase()) : null, item.industry ? a({ href: `/industry/${encodeURIComponent(item.industry)}` }, renderStateChip("whole", "🏭", String(i18n.industryTitle || "Industry").toUpperCase())) : null, - renderLifespanChip(item.lifetime, i18n) + renderLifespanChip(item.lifetime, i18n), + item.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref("market", item.title, item.id)) : null ), - div({ class: "market-card-price card-date-highlight" }, `${item.price} ECO`) + div({ class: "price-chip" }, `${item.price} ECO`) ) ) }) - : p(i18n.marketNoItemsMatch || i18n.marketNoItems) + : p(q || String(minPrice ?? "") || String(maxPrice ?? "") ? (i18n.marketNoItemsMatch || i18n.marketNoItems) : (i18n.marketNoItems || i18n.marketNoItemsMatch)) ) ) ) } exports.singleMarketView = async (item, filter, comments = [], params = {}) => { + const marketChipVisible = (mode) => marketChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null)(mode); const polls = Array.isArray(item.auctions_poll) ? item.auctions_poll : [] const parsedBids = polls.map(parseBidEntry).filter(Boolean).sort((a, b) => new Date(b.time) - new Date(a.time)) const q = params.q || "" @@ -530,14 +519,13 @@ exports.singleMarketView = async (item, filter, comments = [], params = {}) => { const maxPrice = params.maxPrice const sort = params.sort || "recent" const returnTo = params.returnTo || buildReturnTo(filter, q, minPrice, maxPrice, sort) - const topbar = renderMarketTopbar(item, returnTo) const stockLeft = Number(item.stock || 0) const showBuy = item.status === "FOR SALE" && item.item_type !== "auction" && String(item.seller) !== String(userId) && stockLeft > 0 const maxStock = item.initialStock || item.stockMax || item.stock || 1 return template( item.title, - section(div({ class: "tags-header" }, h2(i18n.marketTitle), p(i18n.marketDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.marketTitle), p(i18n.marketDescription), renderWalletChip())), section( div( { class: "filters" }, @@ -548,27 +536,29 @@ exports.singleMarketView = async (item, filter, comments = [], params = {}) => { input({ type: "hidden", name: "maxPrice", value: maxPrice ?? "" }), input({ type: "hidden", name: "sort", value: sort }), button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "exchange", class: filter === "exchange" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterItems).toUpperCase()), - button({ type: "submit", name: "filter", value: "auctions", class: filter === "auctions" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterAuctions).toUpperCase()), - button({ type: "submit", name: "filter", value: "mybids", class: filter === "mybids" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMyBids).toUpperCase()), - button({ type: "submit", name: "filter", value: "new", class: filter === "new" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterNew).toUpperCase()), - button({ type: "submit", name: "filter", value: "used", class: filter === "used" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterUsed).toUpperCase()), - button({ type: "submit", name: "filter", value: "broken", class: filter === "broken" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterBroken).toUpperCase()), - button({ type: "submit", name: "filter", value: "for sale", class: filter === "for sale" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterForSale).toUpperCase()), - button({ type: "submit", name: "filter", value: "sold", class: filter === "sold" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterSold).toUpperCase()), - button({ type: "submit", name: "filter", value: "discarded", class: filter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterDiscarded).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterRecent).toUpperCase()), + ...(marketChipVisible("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterRecent).toUpperCase())] : []), + ...(marketChipVisible("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMine).toUpperCase())] : []), + ...(marketChipVisible("exchange") ? [button({ type: "submit", name: "filter", value: "exchange", class: filter === "exchange" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterItems).toUpperCase())] : []), + ...(marketChipVisible("auctions") ? [button({ type: "submit", name: "filter", value: "auctions", class: filter === "auctions" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterAuctions).toUpperCase())] : []), + ...(marketChipVisible("mybids") ? [button({ type: "submit", name: "filter", value: "mybids", class: filter === "mybids" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterMyBids).toUpperCase())] : []), + ...(marketChipVisible("new") ? [button({ type: "submit", name: "filter", value: "new", class: filter === "new" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterNew).toUpperCase())] : []), + ...(marketChipVisible("used") ? [button({ type: "submit", name: "filter", value: "used", class: filter === "used" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterUsed).toUpperCase())] : []), + ...(marketChipVisible("broken") ? [button({ type: "submit", name: "filter", value: "broken", class: filter === "broken" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterBroken).toUpperCase())] : []), + ...(marketChipVisible("for sale") ? [button({ type: "submit", name: "filter", value: "for sale", class: filter === "for sale" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterForSale).toUpperCase())] : []), + ...(marketChipVisible("sold") ? [button({ type: "submit", name: "filter", value: "sold", class: filter === "sold" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterSold).toUpperCase())] : []), + ...(marketChipVisible("discarded") ? [button({ type: "submit", name: "filter", value: "discarded", class: filter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.marketFilterDiscarded).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.marketCreateButton) ) ), (() => { const isHidden = String(item.visibility || 'PUBLIC').toUpperCase() === 'HIDDEN' + const isClearnet = !!item.clearnet const chips = [ + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref("market", item.title, item.id) : null), + isHidden ? renderVisibilityChip("HIDDEN", i18n) : null, renderStateChip("encrypted", "", String(item.item_type || "").toUpperCase()), item.item_status ? renderStateChip("whole", "", String(item.item_status).toUpperCase()) : null, item.includesShipping ? renderStateChip("mutuals", "📦", String(i18n.marketItemIncludesShipping || "Shipping").replace(/\?$/, "").toUpperCase()) : null, - isHidden ? renderVisibilityChip("HIDDEN", i18n) : null, item.industry ? a({ href: `/industry/${encodeURIComponent(item.industry)}` }, renderStateChip("whole", "🏭", String(i18n.industryTitle || "Industry").toUpperCase())) : null, renderLifespanChip(item.lifetime, i18n), renderEcoTax(item.msgSize, item.id) @@ -605,27 +595,20 @@ exports.singleMarketView = async (item, filter, comments = [], params = {}) => { })() : null - const marketActions = [] - if (String(item.seller) === String(userId)) { - marketActions.push(form({ method: "GET", action: `/market/edit/${encodeURIComponent(item.id)}` }, - button({ type: "submit", class: "update-btn" }, i18n.marketUpdateButton || "Update") - )) - marketActions.push(form({ method: "POST", action: `/market/delete/${encodeURIComponent(item.id)}` }, - button({ type: "submit", class: "delete-btn" }, i18n.marketDeleteButton || "Delete") - )) - } + const marketActions = String(item.seller) === String(userId) ? renderMarketOwnerActions(item, `/market/${encodeURIComponent(item.id)}`) : [] const itemSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(item.id, null, { spread: params.spreads || null, author: item.seller, favKind: 'market', isFavorite: item.isFavorite, reportTitle: item.title }) + renderContentActions(item.id, `/market/${encodeURIComponent(item.id)}`, { spread: params.spreads || null, author: item.seller, favKind: 'market', isFavorite: item.isFavorite, reportTitle: item.title, returnTo: `/market/${encodeURIComponent(item.id)}`, deleteAction: String(item.seller) === String(userId) ? contentDeleteAction('market', item.id) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, item.title) ), renderStarRating(item.opinions, Array.isArray(item.opinions_inhabitants) ? item.opinions_inhabitants.length : 0), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, + String(item.seller) === String(userId) ? renderClearnetSwitch("market", item.rootId || item.id, isClearnet) : null, renderMediaBlob(item.image, "/assets/images/default-market.png"), - div({ class: "card-date-highlight" }, `${item.price} ECO`), + div({ class: "price-chip" }, `${item.price} ECO`), renderStockBar(item.stock, maxStock), table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), tagsNode, @@ -637,7 +620,7 @@ exports.singleMarketView = async (item, filter, comments = [], params = {}) => { item.description ? div({ class: "job-section" }, h2({ class: "job-section-title" }, i18n.marketItemDescription), - p({ class: "tribe-side-description" }, ...renderUrl(item.description)) + p({ class: "tribe-side-description" }, ...renderStyledText(item.description)) ) : null, renderCountdownField(item), @@ -657,7 +640,7 @@ exports.singleMarketView = async (item, filter, comments = [], params = {}) => { : p({ class: "tribe-side-description" }, i18n.marketNoBids || "No bids yet"), item.status !== "SOLD" && item.status !== "DISCARDED" && String(item.seller) !== String(userId) ? form( - { method: "POST", action: `/market/bid/${encodeURIComponent(item.id)}` }, + { method: "POST", action: `/market/bid/${encodeURIComponent(item.id)}`, class: "market-bid-form" }, input({ type: "hidden", name: "returnTo", value: returnTo }), input({ type: "number", name: "bidAmount", step: "0.000001", min: "0.000001", placeholder: i18n.marketYourBid, required: true }), br(), @@ -670,7 +653,7 @@ exports.singleMarketView = async (item, filter, comments = [], params = {}) => { ? div( { class: "market-item actions" }, form( - { method: "POST", action: `/market/buy/${encodeURIComponent(item.id)}` }, + { method: "POST", action: `/market/buy/${encodeURIComponent(item.id)}`, class: "market-buy-form" }, input({ type: "hidden", name: "returnTo", value: "/inbox?filter=sent" }), button({ class: "buy-btn", type: "submit" }, i18n.marketActionsBuy) ) diff --git a/src/views/melody_view.js b/src/views/melody_view.js index 5d7677a..fc82391 100644 --- a/src/views/melody_view.js +++ b/src/views/melody_view.js @@ -2,7 +2,7 @@ const { div, h2, p, section, audio, source, span, a, form, input, button, br, textarea, label } = require("../server/node_modules/hyperaxe"); const moment = require("../server/node_modules/moment"); -const { template, i18n, userLink, renderEcoTax } = require("./main_views"); +const { template, i18n, userLink, renderEcoTax, renderContentActions, paged } = require("./main_views"); const renderSequenceList = (sequence, availableIds = null) => { if (!sequence || sequence.length === 0) { @@ -26,7 +26,7 @@ const renderSequenceList = (sequence, availableIds = null) => { span({ class: "melody-note-name" }, noteName), span({ class: "melody-note-type" }, noteType) ]; - if (reachable) { + if (reachable && process.env.OASIS_MOBILE !== '1') { return a({ href: `/blockexplorer/block/${encodeURIComponent(blockId)}`, class: "melody-note-chip melody-note-chip-link", title: titleAttr }, ...inner); } const cls = checkAvailability && blockId && !reachable @@ -97,27 +97,31 @@ const renderBcsList = (bcsAudios) => { return p({ class: "empty" }, i18n.melodyAllEmpty || "No BCS compositions from other inhabitants yet."); } return div({ class: "melody-bcs-list" }, - bcsAudios.map(a => div({ class: "melody-bcs-card" }, + bcsAudios.map(item => div({ class: "melody-bcs-card" }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(item.key, `/audios/${encodeURIComponent(item.key)}`, { author: item.author, favKind: 'audios', isFavorite: item.isFavorite, torrentFrom: { blobId: item.url, name: item.title }, reportTitle: item.title }) + ), div({ class: "melody-bcs-head" }, - a.title ? h2({ class: "melody-bcs-title" }, a.title) : null, + item.title ? h2({ class: "melody-bcs-title" }, item.title) : null, div({ class: "melody-bcs-meta" }, - span({ class: "card-label" }, `${i18n.melodyByLabel || "By"}: `), - userLink(a.author), - span({ class: "melody-meta-sep" }, " · "), - span({ class: "card-value" }, moment(a.createdAt).format("YYYY/MM/DD HH:mm")) + userLink(item.author), + span({ class: "melody-bcs-date" }, moment(item.createdAt).format("YYYY/MM/DD HH:mm")) ) ), - a.url + item.url ? div({ class: "audio-container melody-bcs-player" }, - audio({ controls: true, preload: "metadata", src: `/blob/${encodeURIComponent(a.url)}` }) + audio({ controls: true, preload: "metadata", src: `/blob/${encodeURIComponent(item.url)}` }), + form({ method: "GET", action: `/melody/transcode/${encodeURIComponent(item.key)}`, class: "audio-transcode-form" }, + button({ type: "submit", class: "filter-btn" }, i18n.audioTranscodeButton || "TRANSCODE") + ) ) - : null, - a.description ? p({ class: "melody-bcs-desc" }, a.description) : null, - div({ class: "melody-bcs-actions" }, - form({ method: "GET", action: `/melody/transcode/${encodeURIComponent(a.key)}` }, - button({ type: "submit", class: "filter-btn" }, i18n.audioTranscodeButton || "TRANSCODE") - ) - ) + : div({ class: "melody-bcs-actions" }, + form({ method: "GET", action: `/melody/transcode/${encodeURIComponent(item.key)}`, class: "audio-transcode-form" }, + button({ type: "submit", class: "filter-btn" }, i18n.audioTranscodeButton || "TRANSCODE") + ) + ), + item.description ? p({ class: "melody-bcs-desc" }, item.description) : null )) ); }; @@ -127,13 +131,13 @@ exports.melodyView = ({ feedId, total, sequence, filter, bcsAudios }) => { const description = i18n.melodyDescription || "Play the melody of your blockchain — each block becomes a note."; const activeFilter = filter === "all" ? "all" : "mine"; const body = activeFilter === "all" - ? renderBcsList(bcsAudios) + ? renderBcsList(paged(bcsAudios || [])) : renderOwnPanel({ feedId, total, sequence }); return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(description) ), diff --git a/src/views/mentions_view.js b/src/views/mentions_view.js index 5aa82c6..3ef4313 100644 --- a/src/views/mentions_view.js +++ b/src/views/mentions_view.js @@ -1,9 +1,9 @@ const { div, h2, p, section, button, form, input, span } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderContentActions } = require("./main_views"); +const { template, i18n, userLink, renderContentActions, renderModuleStats, CONTENT_SPREADABLE, CONTENT_TORRENTABLE, paged } = require("./main_views"); const { getViewDetailsAction } = require("./activity_view"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const userId = config.keys.id; @@ -14,27 +14,41 @@ const excerpt = (text, max = 320) => { const typeLabel = (type) => i18n['type' + String(type || '').charAt(0).toUpperCase() + String(type || '').slice(1)] || String(type || '').toUpperCase(); -const renderMentionCard = (item) => { +const renderMentionCard = (item, readKeys, params = {}) => { const href = getViewDetailsAction(item.type, { id: item.id, key: item.id, author: item.author, content: item.content }); const isOwn = String(item.author) === String(userId); + const read = readKeys.has(String(item.key)); return div({ class: "trending-card mention-card" + (isOwn ? " own-content" : "") }, div({ class: "card-header activity-card-header" }, div({ class: 'card-chips-row' }, span({ class: 'pm-exposition-chip pm-exposition-whole' }, span({ class: 'pm-exposition-text' }, typeLabel(item.type)) - ) + ), + read ? null : span({ class: 'pm-exposition-chip pm-unread-chip' }, span({ class: 'pm-exposition-text' }, i18n.inboxUnreadChip)) ), - renderContentActions(item.id, href, { author: item.author, reportTitle: item.title || item.text }) + renderContentActions(item.id, href, { + author: item.author, + reportTitle: item.title || item.text, + spread: CONTENT_SPREADABLE.has(item.type) ? ((params.spreadMap && params.spreadMap.get(item.id)) || null) : undefined, + ...(item.type === 'torrent' || item.type === 'file' ? { spreadTitle: i18n.seedAction } : {}), + ...(CONTENT_TORRENTABLE.has(item.type) && item.content && typeof item.content.url === 'string' && item.content.url.startsWith('&') + ? { torrentFrom: { blobId: item.content.url, name: item.content.title || item.content.fileName || '' } } + : {}) + }) ), div({ class: "card-section mention-card-body" }, item.title ? div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, item.title)) : null, - item.text ? p({ class: "mention-text" }, ...renderUrl(excerpt(item.text))) : null, + item.text ? p({ class: "mention-text" }, ...renderStyledText(excerpt(item.text))) : null, p({ class: "card-footer" }, - span({ class: "date-link" }, moment(item.createdAt).format("YYYY/MM/DD HH:mm")), - span(" · "), + span({ class: "date-link" }, `${moment(item.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(item.author) + ), + div({ class: "pm-actions mention-actions" }, + form({ method: "POST", action: `/mentions/${read ? 'unread' : 'read'}/${encodeURIComponent(item.key)}`, class: "pm-action-form" }, + button({ type: "submit", class: "pm-btn read-btn" }, String(read ? i18n.inboxMarkUnread : i18n.inboxMarkRead).toUpperCase()) + ) ) ) ); @@ -44,11 +58,14 @@ exports.mentionsView = async (items = [], filter = 'ALL', params = {}) => { const counts = params.counts || {}; const types = Object.keys(counts).sort((a, b) => (counts[b] - counts[a]) || a.localeCompare(b)); const q = params.q || ''; + const readKeys = new Set(Array.isArray(params.readKeys) ? params.readKeys.map(String) : []); + const unreadShown = items.filter(x => x && x.key && !readKeys.has(String(x.key))).map(x => String(x.key)); + const emptyMentions = (!Array.isArray(items) || items.length === 0) && String(filter || 'ALL').toUpperCase() === 'ALL' && !q.trim(); return template( i18n.mentions, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.mentions), p(i18n.mentionsDescription) ), @@ -62,7 +79,12 @@ exports.mentionsView = async (items = [], filter = 'ALL', params = {}) => { ) ) : null, - div({ class: "filters" }, + emptyMentions ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(items.length), + unreadShown.length ? form({ method: "POST", action: "/mentions/read-all", class: "inbox-vis-toggle" }, + ...unreadShown.map(k => input({ type: "hidden", name: "keys", value: k })), + button({ type: "submit", class: "btn" }, `${i18n.inboxMarkAllRead} (${unreadShown.length})`) + ) : null, form({ method: "GET", action: "/mentions", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: q, placeholder: i18n.mentionsSearchPlaceholder, class: "filter-box__input" }), @@ -74,8 +96,8 @@ exports.mentionsView = async (items = [], filter = 'ALL', params = {}) => { ), section( items.length - ? div({ class: "mentions-list" }, ...items.map(renderMentionCard)) - : p({ class: "empty" }, i18n.noMentions) + ? div({ class: "mentions-list" }, ...paged(items).map(item => renderMentionCard(item, readKeys, params))) + : div({ class: "no-content-box" }, p({ class: "empty" }, i18n.noMentions)) ) ); }; diff --git a/src/views/modules_view.js b/src/views/modules_view.js index 0d64193..362d54f 100644 --- a/src/views/modules_view.js +++ b/src/views/modules_view.js @@ -1,7 +1,7 @@ const { form, button, div, h2, p, section, table, tr, td, input, a, br, ul, li, span, strong } = require("../server/node_modules/hyperaxe"); const { template, i18n } = require('./main_views'); const { getConfig } = require('../configs/config-manager.js'); -const { MOBILE_MODULES, ALL_MODULES } = require('../models/workflows_model'); +const { PRESETS: PRESET_LIST } = require('../models/workflows_model'); const modulesView = () => { const config = getConfig().modules; @@ -9,6 +9,13 @@ const modulesView = () => { { name: 'agenda', label: i18n.modulesAgendaLabel, description: i18n.modulesAgendaDescription }, { name: 'ai', label: i18n.modulesAILabel, description: i18n.modulesAIDescription }, { name: 'aiNav', label: i18n.modulesAINavLabel, description: i18n.modulesAINavDescription }, + { name: 'emergencies', label: i18n.modulesEmergenciesLabel, description: i18n.modulesEmergenciesDescription }, + { name: 'mailing', label: i18n.modulesMailingLabel, description: i18n.modulesMailingDescription }, + { name: 'rooms', label: i18n.modulesRoomsLabel, description: i18n.modulesRoomsDescription }, + { name: 'phone', label: i18n.modulesPhoneLabel, description: i18n.modulesPhoneDescription }, + { name: 'logistics', label: i18n.modulesLogisticsLabel, description: i18n.modulesLogisticsDescription }, + { name: 'podcasts', label: i18n.modulesPodcastsLabel, description: i18n.modulesPodcastsDescription }, + { name: 'campaigns', label: i18n.modulesCampaignsLabel, description: i18n.modulesCampaignsDescription }, { name: 'audios', label: i18n.modulesAudiosLabel, description: i18n.modulesAudiosDescription }, { name: 'banking', label: i18n.modulesBankingLabel, description: i18n.modulesBankingDescription }, { name: 'bookmarks', label: i18n.modulesBookmarksLabel, description: i18n.modulesBookmarksDescription }, @@ -23,16 +30,18 @@ const modulesView = () => { { name: 'blogs', label: i18n.modulesBlogsLabel, description: i18n.modulesBlogsDescription }, { name: 'fediverse', label: i18n.modulesFediverseLabel, description: i18n.modulesFediverseDescription }, { name: 'feed', label: i18n.modulesFeedLabel, description: i18n.modulesFeedDescription }, + { name: 'files', label: i18n.modulesFilesLabel, description: i18n.modulesFilesDescription }, { name: 'forum', label: i18n.modulesForumLabel, description: i18n.modulesForumDescription }, { name: 'games', label: i18n.modulesGamesLabel, description: i18n.modulesGamesDescription }, { name: 'graphos', label: i18n.modulesGraphosLabel, description: i18n.modulesGraphosDescription }, + { name: 'housing', label: i18n.modulesHousingLabel, description: i18n.modulesHousingDescription }, { name: 'images', label: i18n.modulesImagesLabel, description: i18n.modulesImagesDescription }, { name: 'industry', label: i18n.modulesIndustryLabel, description: i18n.modulesIndustryDescription }, { name: 'invites', label: i18n.modulesInvitesLabel, description: i18n.modulesInvitesDescription }, { name: 'jobs', label: i18n.modulesJobsLabel, description: i18n.modulesJobsDescription }, { name: 'karvan', label: i18n.modulesKarvanLabel, description: i18n.modulesKarvanDescription }, { name: 'larp', label: i18n.modulesLarpLabel, description: i18n.modulesLarpDescription }, - { name: 'legacy', label: i18n.modulesLegacyLabel, description: i18n.modulesLegacyDescription }, + { name: 'backup', label: i18n.modulesBackupLabel, description: i18n.modulesBackupDescription }, { name: 'logs', label: i18n.modulesLogsLabel, description: i18n.modulesLogsDescription }, { name: 'maps', label: i18n.modulesMapLabel, description: i18n.modulesMapDescription }, { name: 'market', label: i18n.modulesMarketLabel, description: i18n.modulesMarketDescription }, @@ -44,6 +53,7 @@ const modulesView = () => { { name: 'polls', label: i18n.modulesPollsLabel, description: i18n.modulesPollsDescription }, { name: 'projects', label: i18n.modulesProjectsLabel, description: i18n.modulesProjectsDescription }, { name: 'reports', label: i18n.modulesReportsLabel, description: i18n.modulesReportsDescription }, + { name: 'school', label: i18n.modulesSchoolLabel, description: i18n.modulesSchoolDescription }, { name: 'shops', label: i18n.modulesShopsLabel, description: i18n.modulesShopsDescription }, { name: 'tags', label: i18n.modulesTagsLabel, description: i18n.modulesTagsDescription }, { name: 'tasks', label: i18n.modulesTasksLabel, description: i18n.modulesTasksDescription }, @@ -53,49 +63,49 @@ const modulesView = () => { { name: 'tribes', label: i18n.modulesTribesLabel, description: i18n.modulesTribesDescription }, { name: 'videos', label: i18n.modulesVideosLabel, description: i18n.modulesVideosDescription }, { name: 'votes', label: i18n.modulesVotationsLabel, description: i18n.modulesVotationsDescription }, - { name: 'wallet', label: i18n.modulesWalletLabel, description: i18n.modulesWalletDescription } - ]; + { name: 'wallet', label: i18n.modulesWalletLabel, description: i18n.modulesWalletDescription }, + { name: 'wiki', label: i18n.modulesWikiLabel, description: i18n.modulesWikiDescription } + ].sort((x, y) => String(x.label || x.name).localeCompare(String(y.label || y.name), undefined, { sensitivity: 'base' })); - const moduleStates = modules.reduce((acc, mod) => { + const MOBILE_UNAVAILABLE = ['ai', 'aiNav', 'dev', 'games', 'logs', 'maps', 'pixelia']; + const isMobile = process.env.OASIS_MOBILE === '1'; + const visibleModules = isMobile ? modules.filter(mod => !MOBILE_UNAVAILABLE.includes(mod.name)) : modules; + + const moduleStates = visibleModules.reduce((acc, mod) => { acc[`${mod.name}Mod`] = config[`${mod.name}Mod`] === 'on' ? 'on' : 'off'; return acc; }, {}); - const activeModulesCount = modules.filter(mod => moduleStates[`${mod.name}Mod`] === 'on').length; - const totalModulesCount = modules.length; + const activeModulesCount = visibleModules.filter(mod => moduleStates[`${mod.name}Mod`] === 'on').length; + const totalModulesCount = visibleModules.length; const disabledModulesCount = totalModulesCount - activeModulesCount; - const header = div({ class: 'tags-header' }, + const header = [div({ class: 'tags-header module-header-line' }, h2(i18n.modulesViewTitle), - p(i18n.modulesViewDescription), - div({ class: 'modules-counts' }, - `${i18n.modulesTotalModulesLabel}: `, - strong({ class: 'modules-count-value' }, totalModulesCount), - ul({ class: 'modules-count-list' }, - li({ class: 'modules-count-item' }, - `${i18n.modulesEnabledModulesLabel}: `, - span({ class: 'modules-count-value' }, activeModulesCount) - ), - li({ class: 'modules-count-item' }, - `${i18n.modulesDisabledModulesLabel}: `, - span({ class: 'modules-count-value' }, disabledModulesCount) - ) - ) - ) - ); + p(i18n.modulesViewDescription) + )]; - const PRESETS = { - minimal: ['feed', 'forum', 'games', 'images', 'videos', 'audios', 'bookmarks', 'tags', 'trending', 'blogs', 'polls', 'opinions', 'cipher', 'legacy'], - social: ['agenda', 'audios', 'bookmarks', 'calendars', 'chats', 'cipher', 'courts', 'docs', 'events', 'favorites', 'fediverse', 'feed', 'forum', 'games', 'images', 'invites', 'larp', 'legacy', 'logs', 'maps', 'blogs', 'polls', 'opinions', 'pads', 'parliament', 'pixelia', 'melody', 'projects', 'reports', 'tags', 'tasks', 'trending', 'tribes', 'videos', 'votes'], - economy: ['agenda', 'audios', 'bookmarks', 'calendars', 'chats', 'cipher', 'courts', 'docs', 'events', 'favorites', 'fediverse', 'feed', 'forum', 'games', 'images', 'invites', 'larp', 'legacy', 'logs', 'maps', 'blogs', 'polls', 'opinions', 'pads', 'parliament', 'pixelia', 'melody', 'projects', 'reports', 'tags', 'tasks', 'trending', 'tribes', 'videos', 'votes', 'banking', 'wallet', 'transfers', 'market', 'jobs', 'shops', 'industry'], - mobile: MOBILE_MODULES, - full: ALL_MODULES - }; + const counts = div({ class: 'modules-counts' }, + span({ class: 'modules-count-item' }, + `${i18n.modulesTotalModulesLabel}: `, + strong({ class: 'modules-count-value' }, totalModulesCount) + ), + span({ class: 'modules-count-item' }, + `${i18n.modulesEnabledModulesLabel}: `, + strong({ class: 'modules-count-value' }, activeModulesCount) + ), + span({ class: 'modules-count-item' }, + `${i18n.modulesDisabledModulesLabel}: `, + strong({ class: 'modules-count-value' }, disabledModulesCount) + ) + ); + + const PRESETS = PRESET_LIST; const presetButtons = div({ class: 'preset-group' }, Object.entries(PRESETS).map(([key, mods]) => { const presetLabel = (i18n[`modulesPreset_${key}`] || key).toUpperCase(); - const isActive = modules.every(m => mods.includes(m.name) === (moduleStates[`${m.name}Mod`] === 'on')); + const isActive = visibleModules.every(m => mods.includes(m.name) === (moduleStates[`${m.name}Mod`] === 'on')); return form({ action: "/modules/preset", method: "post", class: "modules-preset-form" }, input({ type: "hidden", name: "preset", value: key }), button({ @@ -110,8 +120,8 @@ const modulesView = () => { i18n.modules, section(header), section( - h2(i18n.modulesPresetTitle || "Common Configurations"), - presetButtons, + ...(isMobile ? [] : [h2(i18n.modulesPresetTitle || "Common Configurations"), presetButtons]), + counts, form( { action: "/save-modules", method: "post" }, table( @@ -121,7 +131,7 @@ const modulesView = () => { td(i18n.modulesModuleDescription), td({ class: 'module-status-cell' }, i18n.modulesModuleStatus) ), - modules.map(mod => + visibleModules.map(mod => tr( td(a({ href: `/${mod.name}` }, mod.label)), td(p(mod.description)), diff --git a/src/views/opinions_view.js b/src/views/opinions_view.js index 3f86bfe..2726bfe 100644 --- a/src/views/opinions_view.js +++ b/src/views/opinions_view.js @@ -1,12 +1,13 @@ const { div, h2, p, section, button, form, a, img, video: videoHyperaxe, audio: audioHyperaxe, input, table, tr, th, td, br, span, details, summary } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderSpreadButton, renderContentActions } = require('./main_views'); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const moment = require("../server/node_modules/moment"); +const { template, i18n, userLink, renderSpreadButton, renderContentActions, renderVotesSummary, renderModuleStats, renderCardMetaRow, CONTENT_FAV_KIND, CONTENT_TORRENTABLE, paged } = require('./main_views'); +const { renderZoomableImage } = require('./gallery_view'); const { config } = require('../server/SSB_server.js'); -const { renderTextWithStyles } = require('../backend/renderTextWithStyles'); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledText, renderStyledHtml, safeExternalHref } = require('../backend/renderStyledText'); const opinionCategories = require('../backend/opinion_categories'); const { sanitizeHtml } = require('../backend/sanitizeHtml'); -const seenDocumentTitles = new Set(); const detailHref = (type, key) => { switch (type) { @@ -15,6 +16,7 @@ const detailHref = (type, key) => { case 'video': return `/videos/${encodeURIComponent(key)}`; case 'audio': return `/audios/${encodeURIComponent(key)}`; case 'torrent': return `/torrents/${encodeURIComponent(key)}`; + case 'file': return `/files/${encodeURIComponent(key)}`; case 'document': return `/documents/${encodeURIComponent(key)}`; case 'feed': return `/feed/${encodeURIComponent(key)}`; case 'votes': return `/votes/${encodeURIComponent(key)}`; @@ -27,6 +29,11 @@ const detailHref = (type, key) => { case 'shopProduct': return `/shops/product/${encodeURIComponent(key)}`; case 'housing': return `/housing/${encodeURIComponent(key)}`; case 'market': return `/market/${encodeURIComponent(key)}`; + case 'schoolCourse': return `/school/course/${encodeURIComponent(key)}`; + case 'podcast': return `/podcasts/${encodeURIComponent(key)}`; + case 'podcastEpisode': return `/podcasts/episode/${encodeURIComponent(key)}`; + case 'campaign': return `/campaigns/${encodeURIComponent(key)}`; + case 'logisticsRoute': return `/logistics/${encodeURIComponent(key)}`; default: return null; } }; @@ -48,7 +55,7 @@ const renderContentHtml = (content, key) => { span({ class: 'card-label' }, i18n.industryMembershipPolicy + ':'), span({ class: 'card-value' }, String(i18n['industryPolicy_' + content.membershipPolicy] || content.membershipPolicy).toUpperCase()) ) : "", - content.description ? p(...renderUrl(content.description)) : null, + content.description ? p(...renderStyledText(content.description)) : null, Array.isArray(content.tags) && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`))) @@ -70,7 +77,7 @@ const renderContentHtml = (content, key) => { span({ class: 'card-label' }, i18n.industryLaborHours + ':'), span({ class: 'card-value' }, String(content.laborHours)) ) : "", - content.description ? p(...renderUrl(content.description)) : null + content.description ? p(...renderStyledText(content.description)) : null ) ); case 'housing': { @@ -109,7 +116,7 @@ const renderContentHtml = (content, key) => { span({ class: 'card-label' }, i18n.housingCapacity + ':'), span({ class: 'card-value' }, String(content.capacity)) ) : null, - content.description ? p(...renderUrl(content.description)) : null, + content.description ? p(...renderStyledText(content.description)) : null, Array.isArray(content.tags) && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`))) @@ -132,11 +139,8 @@ const renderContentHtml = (content, key) => { span({ class: 'card-label' }, (i18n.marketItemCondition || i18n.status) + ':'), span({ class: 'card-value' }, String(content.item_status).toUpperCase()) ) : null, - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.marketItemPrice || i18n.price) + ':'), - span({ class: 'card-value' }, `${content.price} ECO`) - ), - content.description ? p(...renderUrl(content.description)) : null, + div({ class: 'price-chip' }, `${content.price} ECO`), + content.description ? p(...renderStyledText(content.description)) : null, Array.isArray(content.tags) && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`))) @@ -147,16 +151,16 @@ const renderContentHtml = (content, key) => { return div({ class: 'opinion-bookmark' }, div({ class: 'card-section bookmark' }, h2(content.url ? div({ class: 'card-field' }, - span({ class: 'card-label' }, p(a({ href: content.url, target: '_blank', class: "bookmark-url" }, content.url))) + span({ class: 'card-label' }, p(a({ href: safeExternalHref(content.url), target: '_blank', rel: 'noopener noreferrer', class: "bookmark-url" }, content.url))) ) : ""), content.lastVisit ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkLastVisitLabel + ':'), - span({ class: 'card-value' }, new Date(content.lastVisit).toLocaleString()) + span({ class: 'card-value' }, moment(content.lastVisit).format("YYYY/MM/DD HH:mm")) ) : "", content.description ? [ span({ class: 'card-label' }, i18n.bookmarkDescriptionLabel + ":"), - p(...renderUrl(content.description)) + p(...renderStyledText(content.description)) ] : null ) @@ -171,12 +175,12 @@ const renderContentHtml = (content, key) => { content.description ? [ span({ class: 'card-label' }, i18n.imageDescriptionLabel + ":"), - p(...renderUrl(content.description)) + p(...renderStyledText(content.description)) ] : null, br(), - div({ class: 'card-field' }, - img({ src: `/blob/${encodeURIComponent(content.url)}`, class: 'feed-image' }) + div({ class: 'card-field image-container' }, + renderZoomableImage(`/blob/${encodeURIComponent(content.url)}`, { imgClass: 'post-image' }) ) ) ); @@ -190,16 +194,16 @@ const renderContentHtml = (content, key) => { content.description ? [ span({ class: 'card-label' }, i18n.videoDescriptionLabel + ":"), - p(...renderUrl(content.description)) + p(...renderStyledText(content.description)) ] : null, div({ class: 'card-field' }, videoHyperaxe({ controls: true, + class: 'post-video', src: `/blob/${encodeURIComponent(content.url)}`, type: content.mimeType || 'video/mp4', - width: '640', - height: '360' + preload: 'metadata' }) ) ) @@ -214,12 +218,13 @@ const renderContentHtml = (content, key) => { content.description ? [ span({ class: 'card-label' }, i18n.audioDescriptionLabel + ":"), - p(...renderUrl(content.description)) + p(...renderStyledText(content.description)) ] : null, - div({ class: 'card-field' }, + div({ class: 'card-field audio-container' }, audioHyperaxe({ controls: true, + class: 'post-audio', src: `/blob/${encodeURIComponent(content.url)}`, type: content.mimeType, preload: 'metadata' @@ -227,6 +232,12 @@ const renderContentHtml = (content, key) => { ) ) ); + case 'file': + return div({ class: 'opinion-file' }, + div({ class: 'card-section' }, + content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.fileTitleLabel || 'Title') + ':'), span({ class: 'card-value' }, content.title)) : "" + ) + ); case 'torrent': return div({ class: 'opinion-torrent' }, div({ class: 'card-section' }, @@ -235,8 +246,6 @@ const renderContentHtml = (content, key) => { ); case 'document': { const t = content.title?.trim(); - if (t && seenDocumentTitles.has(t)) return null; - if (t) seenDocumentTitles.add(t); return div({ class: 'opinion-document' }, div({ class: 'card-section document' }, t ? div({ class: 'card-field' }, @@ -246,7 +255,7 @@ const renderContentHtml = (content, key) => { content.description ? [ span({ class: 'card-label' }, i18n.documentDescriptionLabel + ":"), - p(...renderUrl(content.description)) + p(...renderStyledText(content.description)) ] : null, div({ class: 'card-field' }, @@ -258,7 +267,7 @@ const renderContentHtml = (content, key) => { case 'feed': return div({ class: 'opinion-feed' }, div({ class: 'card-section feed' }, - div({ class: 'feed-text', innerHTML: sanitizeHtml(renderTextWithStyles(content.text)) }), + div({ class: 'feed-text', [RAW_HTML]: sanitizeHtml(renderStyledHtml(content.text)) }), content.refeeds ? h2({ class: 'card-field' }, span({ class: 'card-label' }, `${i18n.tribeFeedRefeeds}: `), span({ class: 'card-value' }, content.refeeds)) : "" @@ -276,7 +285,7 @@ const renderContentHtml = (content, key) => { ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteDeadline + ':'), - span({ class: 'card-value' }, content.deadline ? new Date(content.deadline).toLocaleString() : '') + span({ class: 'card-value' }, content.deadline ? moment(content.deadline).format("YYYY/MM/DD HH:mm") : '') ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteTotalVotes + ':'), @@ -298,7 +307,7 @@ const renderContentHtml = (content, key) => { ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), - span({ class: 'card-value' }, content.deadline ? new Date(content.deadline).toLocaleString() : '') + span({ class: 'card-value' }, content.deadline ? moment(content.deadline).format("YYYY/MM/DD HH:mm") : '') ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.status + ':'), @@ -312,7 +321,7 @@ const renderContentHtml = (content, key) => { span({ class: 'card-label' }, i18n.from + ':'), span({ class: 'card-value' }, userLink(content.from)) ), - div({ class: 'card-field' }, + process.env.OASIS_MOBILE === '1' ? null : div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.to + ':'), span({ class: 'card-value' }, userLink(content.to)) ), @@ -326,15 +335,19 @@ const renderContentHtml = (content, key) => { return div({ class: 'styled-text' }, div({ class: 'card-section styled-text-content' }, div({ class: 'card-field' }, - span({ class: 'card-value', innerHTML: sanitizeHtml(content.title || content.name || content.text || content.description || '[no content]') }) + span({ class: 'card-value', [RAW_HTML]: sanitizeHtml(renderStyledHtml(content.title || content.name || content.text || content.description || '[no content]')) }) ) ) ); } }; -exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { - seenDocumentTitles.clear(); +exports.opinionsView = (items, filter, spreadMap = new Map(), q = '', allItems = null, params = {}) => { + const presentCats = new Set(); + for (const it of (Array.isArray(allItems) ? allItems : items)) { + const ops = (it.value?.content || {}).opinions || {}; + for (const [cat, n] of Object.entries(ops)) if (Number(n) > 0) presentCats.add(cat); + } items = items .filter(item => { const c = item.value?.content || item.content; @@ -350,9 +363,19 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { }); const title = i18n.opinionsTitle; - const baseFilters = ['ALL', 'MINE', 'RECENT', 'TOP']; + const baseFilters = ['ALL', 'RECENT', 'MINE', 'TOP']; + const emptyOps = (Array.isArray(allItems) ? allItems : items).length === 0 && !String(q || '').trim(); - const cards = items + const seenDocs = new Set(); + const shownItems = items.filter(item => { + const c = item.value.content || {}; + const t = c.type === 'document' ? String(c.title || '').trim() : ''; + if (!t) return true; + if (seenDocs.has(t)) return false; + seenDocs.add(t); + return true; + }); + const cards = paged(shownItems) .map(item => { const c = item.value.content; const key = item.key; @@ -361,7 +384,7 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { const voteEntries = Object.entries(c.opinions || {}); const total = voteEntries.reduce((sum, [, v]) => sum + v, 0); const voted = c.opinions_inhabitants?.includes(config.keys.id); - const created = new Date(item.value.timestamp).toLocaleString(); + const created = moment(item.value.timestamp).format("YYYY/MM/DD HH:mm"); const allCats = opinionCategories; const isOwn = item.value.author && String(item.value.author) === String(config.keys.id); @@ -371,32 +394,24 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { span({ class: 'pm-exposition-chip pm-exposition-whole' }, span({ class: 'pm-exposition-text' }, String(c.type || '').toUpperCase()) ), - renderContentActions(key, detailHref(c.type, key), { spread: spreadMap.get(key) || null }) + renderContentActions(key, detailHref(c.type, key), { + spread: spreadMap.get(key) || null, + author: item.value.author, + reportTitle: c.title || c.question || c.concept || c.name || '', + ...(CONTENT_TORRENTABLE.has(c.type) && typeof c.url === 'string' && c.url.startsWith('&') + ? { torrentFrom: { blobId: c.url, name: c.title || c.fileName || '' } } + : {}), + ...(CONTENT_FAV_KIND[c.type] + ? { favKind: CONTENT_FAV_KIND[c.type], isFavorite: params.favIndex instanceof Map ? params.favIndex.get(String(key)) === CONTENT_FAV_KIND[c.type] : false, returnTo: `/opinions?filter=${encodeURIComponent(filter)}` } + : {}) + }) ), div( { class: 'card-section opinions-card-body' }, - contentHtml, - p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${created} ${i18n.performed} `), - userLink(item.value.author) - ), - (() => { - const entries = voteEntries.filter(([, v]) => v > 0); - const dominantPart = (() => { - if (!entries.length) return null; - const maxVal = Math.max(...entries.map(([, v]) => v)); - const dominant = entries.filter(([, v]) => v === maxVal).map(([k]) => i18n['vote' + k.charAt(0).toUpperCase() + k.slice(1)] || k); - return [ - span({ class: 'trending-dominant-sep' }, '|'), - span({ class: 'trending-dominant-text' }, `${i18n.moreVoted || 'More Voted'}: ${dominant.join(' + ')}`) - ]; - })(); - return h2( - `${i18n.totalOpinions || i18n.opinionsTotalCount}: `, - span({ class: 'trending-total-count' }, String(total)), - ...(dominantPart || []) - ); - })(), + contentHtml + ), + renderVotesSummary(c.opinions), + renderCardMetaRow( details({ class: 'opinions-voting-collapse' }, summary({ class: 'opinions-summary' }, span({ class: 'opinions-summary-icon' }, 'ꔍ'), @@ -412,6 +427,10 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { ); }) ) + ), + p({ class: 'card-footer' }, + span({ class: 'date-link' }, `${created}`), + userLink(item.value.author) ) ) ); @@ -419,7 +438,7 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { .filter(Boolean); const hasDocuments = items.some(item => item.value.content?.type === 'document'); - const header = div({ class: 'tags-header' }, + const header = div({ class: 'tags-header module-header-line' }, h2(title), p(i18n.shareYourOpinions) ); @@ -428,7 +447,7 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { title, section( header, - div({ class: 'mode-buttons' }, + emptyOps ? null : div({ class: 'mode-buttons' }, div({ class: 'column' }, baseFilters.map(mode => form({ method: 'GET', action: '/opinions' }, @@ -438,7 +457,7 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { ) ), div({ class: 'column' }, - opinionCategories.positive.slice(0, 5).map(mode => + opinionCategories.positive.slice(0, 5).filter(mode => presentCats.has(mode)).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) @@ -446,7 +465,7 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { ) ), div({ class: 'column' }, - opinionCategories.positive.slice(5, 10).map(mode => + opinionCategories.positive.slice(5, 10).filter(mode => presentCats.has(mode)).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) @@ -454,7 +473,40 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { ) ), div({ class: 'column' }, - opinionCategories.positive.slice(10, 15).map(mode => + opinionCategories.positive.slice(10, 15).filter(mode => presentCats.has(mode)).map(mode => + form({ method: 'GET', action: '/opinions' }, + input({ type: 'hidden', name: 'filter', value: mode }), + button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) + ) + ) + ) + , + div({ class: 'column' }, + opinionCategories.constructive.slice(0, 5).filter(mode => presentCats.has(mode)).map(mode => + form({ method: 'GET', action: '/opinions' }, + input({ type: 'hidden', name: 'filter', value: mode }), + button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) + ) + ) + ), + div({ class: 'column' }, + opinionCategories.constructive.slice(5, 11).filter(mode => presentCats.has(mode)).map(mode => + form({ method: 'GET', action: '/opinions' }, + input({ type: 'hidden', name: 'filter', value: mode }), + button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) + ) + ) + ), + div({ class: 'column' }, + opinionCategories.moderation.slice(0, 5).filter(mode => presentCats.has(mode)).map(mode => + form({ method: 'GET', action: '/opinions' }, + input({ type: 'hidden', name: 'filter', value: mode }), + button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) + ) + ) + ), + div({ class: 'column' }, + opinionCategories.moderation.slice(5, 10).filter(mode => presentCats.has(mode)).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) @@ -462,41 +514,8 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { ) ) ), - div({ class: 'mode-buttons' }, - div({ class: 'column' }, - opinionCategories.constructive.slice(0, 5).map(mode => - form({ method: 'GET', action: '/opinions' }, - input({ type: 'hidden', name: 'filter', value: mode }), - button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) - ) - ) - ), - div({ class: 'column' }, - opinionCategories.constructive.slice(5, 11).map(mode => - form({ method: 'GET', action: '/opinions' }, - input({ type: 'hidden', name: 'filter', value: mode }), - button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) - ) - ) - ), - div({ class: 'column' }, - opinionCategories.moderation.slice(0, 5).map(mode => - form({ method: 'GET', action: '/opinions' }, - input({ type: 'hidden', name: 'filter', value: mode }), - button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) - ) - ) - ), - div({ class: 'column' }, - opinionCategories.moderation.slice(5, 10).map(mode => - form({ method: 'GET', action: '/opinions' }, - input({ type: 'hidden', name: 'filter', value: mode }), - button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, String(i18n[mode + 'Button'] || mode).toUpperCase()) - ) - ) - ) - ), - div({ class: 'filters' }, + emptyOps ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStats(shownItems.length), form({ method: 'GET', action: '/opinions', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ type: 'text', name: 'q', value: q, placeholder: i18n.opinionsSearchPlaceholder, class: 'filter-box__input' }), @@ -514,8 +533,7 @@ exports.opinionsView = (items, filter, spreadMap = new Map(), q = '') => { ); return `${html}${hasDocuments - ? ` - ` + ? `` : ''}`; }; diff --git a/src/views/pads_view.js b/src/views/pads_view.js index c38c2f1..26eae79 100644 --- a/src/views/pads_view.js +++ b/src/views/pads_view.js @@ -1,5 +1,5 @@ -const { div, h2, h3, h4, p, section, button, form, a, span, br, textarea, input, label, select, option, table, tr, td } = require("../server/node_modules/hyperaxe") -const { template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton , renderContentActions } = require("./main_views") +const { div, h2, h3, h4, p, section, button, form, a, span, br, textarea, input, label, select, option, table, tr, td, details, summary } = require("../server/node_modules/hyperaxe") +const { template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton , renderContentActions, renderInviteQrCard, renderSubscriptionBox, renderModuleStatsBy, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views") const { renderEncryptedChip } = require("./clearnet_view") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") @@ -82,15 +82,17 @@ const renderPadStatusChip = (status, isClosed) => { return renderStateChip(variant, icon, label) } -const renderModeButtons = (currentFilter) => +const renderModeButtons = (currentFilter, emptyMod = false, modesAvail = null) => div({ class: "tribe-mode-buttons" }, - ["all", "mine", "recent", "open", "closed"].map(f => + ...(emptyMod ? [] : [ + ["all", "recent", "mine", "open", "closed"].filter(f => f === "all" || f === currentFilter || (modesAvail && modesAvail[f] !== false)).map(f => form({ method: "GET", action: "/pads" }, input({ type: "hidden", name: "filter", value: f }), button({ type: "submit", class: currentFilter === f ? "filter-btn active" : "filter-btn" }, i18n[`padFilter${f.charAt(0).toUpperCase() + f.slice(1)}`] || f.toUpperCase()) ) ), + ]), form({ method: "GET", action: "/pads" }, input({ type: "hidden", name: "filter", value: "create" }), button({ type: "submit", class: "create-button" }, i18n.padCreate || "Create Pad") @@ -98,16 +100,19 @@ const renderModeButtons = (currentFilter) => ) -const renderPadCard = (pad, filter, spreadInfo) => { +const renderPadCard = exports.renderPadCard = (pad, filter, spreadInfo) => { const chips = [ renderPadStatusChip(pad.status, pad.isClosed), renderEncryptedChip(i18n), - renderLifespanChip(pad.lifetime, i18n) + renderLifespanChip(pad.lifetime, i18n), + pad.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (pad.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null) ].filter(Boolean) return div({ class: "tribe-card" }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(pad.rootId || pad.key, `/pads/${encodeURIComponent(pad.rootId)}`, { spread: spreadInfo || null, author: pad.author, favKind: 'pads', isFavorite: pad.isFavorite, reportTitle: pad.title }) + renderContentActions(pad.rootId || pad.key, `/pads/${encodeURIComponent(pad.rootId)}`, { spread: spreadInfo || null, author: pad.author, favKind: 'pads', isFavorite: pad.isFavorite, reportTitle: pad.title, deleteAction: String(pad.author) === String(userId) ? contentDeleteAction('pad', pad.rootId || pad.key) : undefined }) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, @@ -138,7 +143,7 @@ const renderCreateForm = (padToEdit, params) => { span(i18n.padTitleLabel || "Title"), require("../server/node_modules/hyperaxe").br(), input({ type: "text", name: "title", maxlength: "100", value: padToEdit ? padToEdit.title : "", placeholder: i18n.padTitlePlaceholder || "Enter pad title...", required: true }), require("../server/node_modules/hyperaxe").br(), require("../server/node_modules/hyperaxe").br(), - span(i18n.padStatusLabel || "Status"), require("../server/node_modules/hyperaxe").br(), + span(i18n.padTypeLabel), require("../server/node_modules/hyperaxe").br(), select({ name: "status" }, ["OPEN", "INVITE-ONLY"].map(s => option({ value: s, ...(padToEdit && padToEdit.status === s ? { selected: true } : {}) }, s) @@ -154,7 +159,7 @@ const renderCreateForm = (padToEdit, params) => { }), require("../server/node_modules/hyperaxe").br(), require("../server/node_modules/hyperaxe").br(), span(i18n.padTagsLabel || "Tags"), require("../server/node_modules/hyperaxe").br(), - input({ type: "text", name: "tags", value: padToEdit ? padToEdit.tags.join(", ") : "", placeholder: i18n.padTagsPlaceholder || "tag1, tag2, ..." }), + input({ type: "text", name: "tags", value: padToEdit ? padToEdit.tags.join(", ") : "", placeholder: i18n.padTagsPlaceholder || "Enter tags separated by commas" }), require("../server/node_modules/hyperaxe").br(), require("../server/node_modules/hyperaxe").br(), button({ type: "submit", class: "create-button" }, padToEdit ? (i18n.padUpdate || "Update Pad") : (i18n.padCreate || "Create Pad")) ) @@ -176,19 +181,21 @@ exports.padsView = async (pads, filter, padToEdit, params) => { const q = String((params && params.q) || "").trim() const isForm = filter === "create" || filter === "edit" const headerText = i18n.padsTitle + const emptyMod = moduleIsEmpty(Array.isArray(pads) ? pads : [], filter || "all", "all", q) const filteredPads = q ? pads.filter(pd => String(pd.title || "").toLowerCase().includes(q.toLowerCase())) : pads - const body = div({ class: "main-column" }, - div({ class: "tags-header" }, + const body = section( + div({ class: "tags-header module-header-line" }, h2(headerText), p(i18n.padsDescription || "Manage collaborative encrypted text editors in your network.") ), - renderModeButtons(filter), - !isForm - ? div({ class: "filters" }, + renderModeButtons(filter, emptyMod, (params && params.modesAvail) || null), + !isForm && !emptyMod + ? div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filteredPads, pd => pd.isClosed ? 'CLOSED' : String(pd.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.padStatusOpen }, { value: 'INVITE-ONLY', label: i18n.padStatusInviteOnly }, { value: 'CLOSED', label: i18n.padStatusClosed }]), form({ method: "GET", action: "/pads", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", placeholder: i18n.padSearchPlaceholder || "Search pads...", value: q, class: "filter-box__input" }), @@ -203,7 +210,7 @@ exports.padsView = async (pads, filter, padToEdit, params) => { : div( filteredPads.length === 0 ? p({ class: "no-content" }, i18n.padsNoItems || "No pads found.") - : div({ class: "tribe-grid pads-grid" }, ...filteredPads.map(pd => renderPadCard(pd, filter, params && params.spreadMap && params.spreadMap.get(pd.rootId)))) + : div({ class: "tribe-grid pads-grid" }, ...paged(filteredPads).map(pd => renderPadCard(pd, filter, params && params.spreadMap && params.spreadMap.get(pd.rootId)))) ) ) @@ -218,13 +225,18 @@ exports.singlePadView = async (pad, entries, params) => { const isRestrictedInviteOnly = !isMember && !isAuthor && pad.status === "INVITE-ONLY" const tags = !isRestrictedInviteOnly && Array.isArray(pad.tags) && pad.tags.length > 0 - ? div({ class: "tribe-side-tags" }, ...pad.tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}` }, `#${t}`))) + ? div({ class: "tribe-side-tags" }, ...pad.tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) : null + const sharesPad = isAuthor || (pad.members || []).includes(userId) + const subscriptionIn = isAuthor || (pad.subscription && pad.subscription.subscribed === true) const detailChips = [ renderPadStatusChip(pad.status, padClosed), renderEncryptedChip(i18n), - renderLifespanChip(pad.lifetime, i18n) + renderLifespanChip(pad.lifetime, i18n), + sharesPad + ? renderStateChip(subscriptionIn ? "mutuals" : "closed", "✉", subscriptionIn ? i18n.subscriptionOn : i18n.subscriptionOff) + : null ].filter(Boolean) const inviteActions = [ isAuthor && pad.status === "INVITE-ONLY" @@ -239,7 +251,8 @@ exports.singlePadView = async (pad, entries, params) => { if (openInvite) return [ div({ class: "tribe-open-invite" }, span({ class: "card-label" }, i18n.tribeInviteCodeText), - span({ class: "tribe-open-invite-code" }, openInvite.code) + span({ class: "tribe-open-invite-code" }, openInvite.code), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/pads/${encodeURIComponent(openInvite.code)}` }) ), form({ method: "POST", action: `/pads/open-invite/remove/${encodeURIComponent(pad.rootId)}` }, button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.tribeRemoveInvitation) @@ -251,58 +264,78 @@ exports.singlePadView = async (pad, entries, params) => { })() ].filter(Boolean) + const topButtons = isRestrictedInviteOnly ? [] : [ + !isAuthor && !isMember && pad.status === "OPEN" && !padClosed + ? form({ method: "POST", action: `/pads/join/${encodeURIComponent(pad.rootId)}` }, + button({ type: "submit", class: "update-btn" }, i18n.padStartEditing || "EDIT") + ) + : null, + form({ method: "GET", action: `/pads/${encodeURIComponent(pad.rootId)}/pdf` }, + button({ type: "submit", class: "update-btn" }, i18n.generatePdf || "Generate PDF") + ), + isAuthor && pad.status !== "CLOSED" && !padClosed + ? form({ method: "POST", action: `/pads/close/${encodeURIComponent(pad.rootId)}` }, + button({ type: "submit", class: "update-btn" }, i18n.padClose || "Close") + ) + : null, + isAuthor + ? form({ method: "GET", action: "/pads" }, + input({ type: "hidden", name: "filter", value: "edit" }), + input({ type: "hidden", name: "id", value: pad.rootId }), + button({ type: "submit", class: "update-btn" }, i18n.padUpdate || "Update") + ) + : null + ].filter(Boolean) + const padSide = div({ class: "tribe-side" }, - div({ class: "card-header activity-card-header" }, - renderContentActions(pad.rootId, null, { - author: pad.author, - favKind: 'pads', - isFavorite: pad.isFavorite, - spread: (params && params.spreads) || null, - returnTo, - reportTitle: pad.title - }) + div({ class: "tribe-side-actions wiki-actions-top pad-actions-top" }, + span({ class: "wiki-actions-left" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(pad.rootId, returnTo, { + author: pad.author, + favKind: 'pads', + isFavorite: pad.isFavorite, + spread: (params && params.spreads) || null, + returnTo, + reportTitle: pad.title, + deleteAction: isAuthor ? contentDeleteAction('pad', pad.rootId) : undefined + }) + ) + ), + ...topButtons ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, pad.title || "\u2014") ), detailChips.length ? div({ class: "card-chips-row" }, ...detailChips) : null, - table({ class: "tribe-info-table jobs-info-table" }, - tr(td({ class: "tribe-info-label" }, i18n.padCreated || "Created"), td({ class: "tribe-info-value", colspan: "3" }, moment(pad.createdAt).format("YYYY/MM/DD HH:mm"))), - (isRestrictedInviteOnly || !pad.deadline) ? null : tr(td({ class: "tribe-info-label" }, i18n.padDeadlineLabel || "Deadline"), td({ class: "tribe-info-value", colspan: "3" }, moment(pad.deadline).format("YYYY/MM/DD HH:mm"))), - isRestrictedInviteOnly ? null : tr(td({ class: "tribe-info-value pad-author-cell", colspan: "4" }, userLink(pad.author))) + div({ class: "pad-meta" }, + span({ class: "card-label" }, `${i18n.padCreated || "Created"}: `), + span(moment(pad.createdAt).format("YYYY/MM/DD HH:mm")), + (isRestrictedInviteOnly || !pad.deadline) ? null : span({ class: "card-label" }, `${i18n.padDeadlineLabel || "Deadline"}: `), + (isRestrictedInviteOnly || !pad.deadline) ? null : span(moment(pad.deadline).format("YYYY/MM/DD HH:mm")), + isRestrictedInviteOnly ? null : userLink(pad.author) ), tags, - div({ class: "tribe-card-members" }, - span({ class: "tribe-members-count" }, `${i18n.padMembersLabel || "Members"}: ${pad.members.length}`) - ), - isRestrictedInviteOnly ? null : div({ class: "tribe-side-actions housing-status-row" }, - span({ class: "card-label" }, `${i18n.padStatusLabel || "Status"}: `), - renderPadStatusChip(pad.status, padClosed), - isAuthor && pad.status !== "CLOSED" && !padClosed - ? form({ method: "POST", action: `/pads/close/${encodeURIComponent(pad.rootId)}` }, - button({ type: "submit", class: "status-btn project-control-btn" }, i18n.padClose || "Close") - ) - : null - ), (isRestrictedInviteOnly || !inviteActions.length) ? null : div({ class: "tribe-side-actions" }, ...inviteActions), + (pad.subscription && sharesPad) + ? renderSubscriptionBox({ + target: pad.rootId || pad.key, + scope: "pads", + subscribed: pad.subscription.subscribed === true, + count: pad.subscription.count, + isOwner: isAuthor, + returnTo + }) + : null, !isAuthor && pad.status === "INVITE-ONLY" && !isMember ? div({ class: "pad-invite-section" }, - a({ class: "tribe-action-btn", href: "/invites#invites-pads" }, i18n.tribeEnterInvite) + a({ class: "tribe-action-btn", href: "/invites?filter=others" }, i18n.tribeEnterInvite) ) : null, - !isRestrictedInviteOnly && !isAuthor && !isMember && pad.status === "OPEN" && !padClosed - ? form({ method: "POST", action: `/pads/join/${encodeURIComponent(pad.rootId)}` }, - button({ type: "submit", class: "create-button" }, i18n.padStartEditing || "START EDITING!") - ) - : null, - isRestrictedInviteOnly || !isAuthor ? null : div({ class: "tribe-side-actions" }, - form({ method: "GET", action: "/pads" }, - input({ type: "hidden", name: "filter", value: "edit" }), - input({ type: "hidden", name: "id", value: pad.rootId }), - button({ type: "submit", class: "tribe-action-btn" }, i18n.padUpdate || "Update") - ), - form({ method: "POST", action: `/pads/delete/${encodeURIComponent(pad.rootId)}` }, - button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.padDelete || "Delete") + details({ class: "pad-members-details", ...(pad.members.length <= 8 ? { open: true } : {}) }, + summary({ class: "tribe-members-count" }, `${i18n.padMembersLabel || "Members"}: ${pad.members.length}`), + div({ class: "pad-members-chips" }, + ...pad.members.map(m => span({ class: "pad-member-chip" }, span({ class: "pad-author-swatch " + memberColorClass(pad.members, m) }), userLink(m))) ) ) ) @@ -316,49 +349,83 @@ exports.singlePadView = async (pad, entries, params) => { const lastEntry = canonicalEntries.length > 0 ? canonicalEntries[canonicalEntries.length - 1] : null const currentText = lastEntry ? lastEntry.text : "" - const coloredView = chunks.length > 0 - ? div({ class: "pad-readonly-colored" }, - ...chunks.map(c => - span({ class: "pad-author-span " + memberColorClass(pad.members, c.author) }, c.text) - ) + const linesOfChunks = (list) => { + const rows = [[]] + for (const c of list) { + const parts = String(c.text || "").split("\n") + parts.forEach((part, i) => { + if (i > 0) rows.push([]) + if (part) rows[rows.length - 1].push({ text: part, author: c.author }) + }) + } + return rows + } + const renderNumbered = (list, minRows = 0) => { + const rows = linesOfChunks(list) + while (rows.length < minRows) rows.push([]) + return table({ class: "pad-lines" }, + ...rows.map((segs, i) => tr({ id: `L${i + 1}` }, + td({ class: "pad-line-num" }, a({ href: `#L${i + 1}` }, String(i + 1))), + td({ class: "pad-line-text" }, segs.length + ? segs.map(sg => span({ class: "pad-author-span " + memberColorClass(pad.members, sg.author) }, sg.text)) + : (i === 0 && !list.length ? span({ class: "pad-line-placeholder" }, i18n.padNoEntries || "No entries yet.") : " ")) + )) + ) + } + const documentView = div({ class: "pad-document" }, renderNumbered(chunks, 24)) + const attributionView = chunks.length > 0 + ? details({ class: "pad-attribution" }, + summary(i18n.padAttributionView || "By author"), + div({ class: "pad-readonly-colored" }, renderNumbered(chunks)) ) - : p(i18n.padNoEntries || "No entries yet.") + : null + const lineCount = String(currentText || "").split("\n").length + const editorRows = Math.max(24, lineCount + 12) + const gutter = div({ class: "pad-gutter" }, Array.from({ length: editorRows }, (_, i) => String(i + 1)).join("\n")) const visibleEntries = entries.filter(e => e.text && String(e.text).trim()) - const versionList = visibleEntries.length > 0 - ? div({ class: "pad-version-list" }, + const selectedKey = params.selectedVersion ? params.selectedVersion.key : null + const historyPanel = visibleEntries.length > 0 + ? div({ class: "pad-history" }, h4(i18n.padVersionHistory || "Version History"), ...visibleEntries.slice().reverse().map((e, idx) => - div({ class: "pad-version-item" }, - span({ class: "pad-version-date" }, moment(e.createdAt).format("YYYY/MM/DD HH:mm")), - span({ class: "pad-version-author" }, - span({ class: "pad-author-swatch " + memberColorClass(pad.members, e.author) }), - userLink(e.author) - ), - a({ href: `/pads/${encodeURIComponent(pad.rootId)}?version=${encodeURIComponent(e.key || idx)}`, class: "pad-version-link" }, i18n.padVersionView || "View") + div({ class: "pad-history-item" }, + a({ href: `/pads/${encodeURIComponent(pad.rootId)}?version=${encodeURIComponent(e.key || idx)}`, class: "pad-version-link" + (selectedKey && e.key === selectedKey ? " active" : ""), title: i18n.padVersionView || "View" }, moment(e.createdAt).format("YYYY/MM/DD HH:mm")), + span({ class: "pad-author-swatch " + memberColorClass(pad.members, e.author) }), + userLink(e.author) ) ) ) : null + const sidePanels = [historyPanel].filter(Boolean) const editorArea = isMember && !padClosed && !params.selectedVersion - ? div({ class: "pad-editor-area" }, - coloredView, - form({ method: "POST", action: `/pads/entry/${encodeURIComponent(pad.rootId)}` }, - textarea({ name: "text", rows: "12", class: "pad-editor-white", placeholder: i18n.padEditorPlaceholder || "Start writing..." }, currentText), - button({ type: "submit", class: "create-button" }, i18n.padSubmitEntry || "Submit") - ), - versionList ? div({ class: "pad-version-section" }, versionList) : null - ) - : div({ class: "pad-editor-area" }, - params.selectedVersion - ? div({ class: "pad-viewer-back" }, - a({ href: `/pads/${encodeURIComponent(pad.rootId)}`, class: "filter-btn" }, - "\u2190 " + (i18n.padBackToEditor || "Back to editor")) + ? div({ class: "pad-workspace" }, + div({ class: "pad-workspace-main" }, + form({ method: "POST", action: `/pads/entry/${encodeURIComponent(pad.rootId)}` }, + div({ class: "pad-editor-wrap" }, + gutter, + textarea({ maxlength: "3000", name: "text", rows: String(editorRows), class: "pad-editor", placeholder: i18n.padEditorPlaceholder || "Start writing...", wrap: "off" }, currentText) + ), + div({ class: "pad-editor-bar" }, + button({ type: "submit", class: "create-button" }, i18n.padSubmitEntry || "Submit") ) - : null, - coloredView, - versionList ? div({ class: "pad-version-section" }, versionList) : null + ), + attributionView + ), + sidePanels.length ? div({ class: "pad-workspace-side" }, ...sidePanels) : null + ) + : div({ class: "pad-workspace" }, + div({ class: "pad-workspace-main" }, + params.selectedVersion + ? div({ class: "pad-viewer-back" }, + a({ href: `/pads/${encodeURIComponent(pad.rootId)}`, class: "filter-btn" }, + "\u2190 " + (i18n.padBackToEditor || "Back to editor")) + ) + : null, + documentView + ), + sidePanels.length ? div({ class: "pad-workspace-side" }, ...sidePanels) : null ) const padMain = isRestrictedInviteOnly @@ -368,12 +435,12 @@ exports.singlePadView = async (pad, entries, params) => { return template( pad.title || i18n.padsTitle || "Pad", section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.padsTitle || "Pads"), p(i18n.padsDescription || "Manage collaborative encrypted text editors in your network.") ), - renderModeButtons("all") + renderModeButtons("all", false, (params && params.modesAvail) || null) ), - section(div({ class: "tribe-details" }, padSide, padMain)) + section(div({ class: "tribe-details pad-page" }, padSide, padMain)) ) } diff --git a/src/views/parliament_view.js b/src/views/parliament_view.js index e3e3628..0558668 100644 --- a/src/views/parliament_view.js +++ b/src/views/parliament_view.js @@ -1,6 +1,153 @@ const { form, button, div, h2, p, section, input, label, br, a, span, table, thead, tbody, tr, th, td, textarea, select, option, ul, li, img } = require('../server/node_modules/hyperaxe'); +const RAW_HTML = Symbol.for('oasis.rawHtml'); const moment = require("../server/node_modules/moment"); -const { template, i18n, userLink} = require('./main_views'); +const { template, i18n, userLink, paged } = require('./main_views'); +const { renderStyledText } = require('../backend/renderStyledText'); + +const { seatPositions } = require('../models/parliament_model').hemicycle; + +const HEMI_W = 720; +const HEMI_H = 380; + +const hemiEsc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + +const blocClass = (key) => { + const m = /^opp(\d+)$/.exec(String(key || '')); + return m ? `hemi-bloc-opp${Number(m[1]) % 8}` : `hemi-bloc-${key}`; +}; + +const seatClass = (seat, mode) => { + const cls = ['hemi-seat']; + if (mode === 'houses') cls.push(seat.house ? `hemi-house-${seat.house}` : 'hemi-bloc-none'); + else cls.push(blocClass(seat.bloc)); + if (seat.leader) cls.push('hemi-leader'); + if (seat.inPower) cls.push('hemi-power'); + return cls.join(' '); +}; + +const thresholdMarker = (positions, count, cls, label) => { + if (!count || count <= 0 || count > positions.length) return null; + const p = positions[count - 1]; + const q = positions[Math.min(positions.length - 1, count)]; + const angle = (p.angle + q.angle) / 2; + const cx = HEMI_W / 2; + const cy = HEMI_H - 20; + const inner = 40; + const outer = Math.min(HEMI_W / 2 - 16, HEMI_H - 60) + 14; + return { + angle, cls, text: `${label}: ${count}`, + x1: cx + inner * Math.cos(angle), y1: cy - inner * Math.sin(angle), + x2: cx + outer * Math.cos(angle), y2: cy - outer * Math.sin(angle), + tx: cx + (outer + 12) * Math.cos(angle), ty: cy - (outer + 12) * Math.sin(angle), + anchor: angle > Math.PI / 2 + 0.2 ? 'end' : (angle < Math.PI / 2 - 0.2 ? 'start' : 'middle') + }; +}; + +const LABEL_LINE = 14; +const renderMarkers = (markers) => { + const list = markers.filter(Boolean); + const placed = []; + for (const m of list.slice().sort((a, b) => a.ty - b.ty)) { + let ty = m.ty; + while (placed.some(o => Math.abs(o.ty - ty) < LABEL_LINE && Math.abs(o.tx - m.tx) < 160)) ty += LABEL_LINE; + placed.push({ ...m, ty }); + } + const drawn = new Set(); + return placed.map(m => { + const key = `${m.x2.toFixed(1)},${m.y2.toFixed(1)}`; + const line = drawn.has(key) ? '' : ``; + drawn.add(key); + return `${line}${hemiEsc(m.text)}`; + }).join(''); +}; + +const hemicycleSvg = (data, mode) => { + const seats = Array.isArray(data.seats) ? data.seats : []; + const unit = Math.max(1, Number(data.seatUnit) || 1); + const shown = unit > 1 ? seats.filter((_, i) => i % unit === 0) : seats; + const positions = seatPositions(shown.length, { width: HEMI_W, height: HEMI_H }); + const circles = shown.map((seat, i) => { + const pos = positions[i]; + if (!pos) return ''; + const title = hemiEsc(seat.name ? `${seat.name} (${seat.id})` : seat.id); + const circle = `${title}`; + return unit > 1 ? circle : `${circle}`; + }).join(''); + const t = data.thresholds || {}; + const scale = (n) => Math.ceil(Number(n || 0) / unit); + const markers = mode === 'houses' ? '' : renderMarkers([ + thresholdMarker(positions, scale(t.quorum), 'hemi-threshold-quorum', i18n.parliamentSeatsQuorum), + thresholdMarker(positions, scale(t.pass), 'hemi-threshold-pass', i18n.parliamentSeatsPass) + ]); + return `${circles}${markers}`; +}; + +const pct = (n, total) => total > 0 ? `${((n / total) * 100).toFixed(1)}%` : '0%'; + +const blocLabel = (b) => { + if (b.kind === 'government') return `${i18n.parliamentSeatsGovernment} · ${b.label}`; + if (b.kind === 'opposition') return `${i18n.parliamentSeatsOpposition} · ${i18n.parliamentSeatsVotesFor} ${b.label}`; + return i18n.parliamentSeatsAbstention; +}; + +const legendElection = (data) => ul({ class: 'hemi-legend' }, + ...(data.blocs || []).map(b => li({ class: 'hemi-legend-item' }, + span({ class: `hemi-swatch ${blocClass(b.key)}` }), + b.href ? a({ href: b.href, class: 'hemi-legend-link' }, blocLabel(b)) : span(blocLabel(b)), + span({ class: 'hemi-legend-count' }, `${b.count} ${i18n.parliamentSeatsSeats} · ${pct(b.count, data.population)}`) + )) +); + +const legendHouses = (data, houseNames = {}) => { + const entries = Object.entries(data.houses || {}).sort((a, b) => b[1] - a[1]); + const noHouse = data.population - entries.reduce((s, [, n]) => s + n, 0); + return ul({ class: 'hemi-legend' }, + ...entries.map(([key, n]) => li({ class: 'hemi-legend-item' }, + span({ class: `hemi-swatch hemi-house-${key}` }), + a({ href: `/larp/${encodeURIComponent(key)}`, class: 'hemi-legend-link' }, houseNames[key] || key), + span({ class: 'hemi-legend-count' }, `${n} ${i18n.parliamentSeatsSeats} · ${pct(n, data.population)}`) + )), + noHouse > 0 ? li({ class: 'hemi-legend-item' }, span({ class: 'hemi-swatch hemi-bloc-none' }), span(i18n.parliamentSeatsNoHouse), span({ class: 'hemi-legend-count' }, `${noHouse} ${i18n.parliamentSeatsSeats} · ${pct(noHouse, data.population)}`)) : null + ); +}; + +const renderHemicycle = (data, { mode = 'election', baseHref = '/parliament?filter=government', houseNames = {}, showHouses = true } = {}) => { + if (!data || !Array.isArray(data.seats) || !data.seats.length) return null; + const seatsMode = mode === 'houses' ? 'houses' : 'election'; + const join = baseHref.includes('?') ? '&' : '?'; + const toggle = showHouses ? div({ class: 'filters hemi-modes' }, + a({ href: baseHref, class: seatsMode === 'election' ? 'filter-btn active' : 'filter-btn' }, i18n.parliamentSeatsByElection), + a({ href: `${baseHref}${join}seats=houses`, class: seatsMode === 'houses' ? 'filter-btn active' : 'filter-btn' }, i18n.parliamentSeatsByHouses) + ) : null; + const unit = Math.max(1, Number(data.seatUnit) || 1); + return div({ class: 'hemicycle' }, + toggle, + (() => { + const svg = hemicycleSvg(data, seatsMode); + return div({ class: 'hemicycle-zoom-wrap' }, + div({ class: 'hemicycle-canvas' }, + div({ [RAW_HTML]: svg }), + a({ href: '#hemi-zoom', id: 'hemi-zoom-src', class: 'hemi-zoom-link', title: i18n.bankChartZoomHint }) + ), + div({ id: 'hemi-zoom', class: 'lightbox bank-eco-chart-lightbox' }, + a({ href: '#hemi-zoom-src', class: 'lightbox-close' }, '\u00d7'), + div({ class: 'bank-eco-chart-canvas-zoom hemi-zoom-canvas', [RAW_HTML]: svg }) + ) + ); + })(), + unit > 1 ? p({ class: 'hemi-note' }, `1 ${i18n.parliamentSeatsSeat} = ${unit}`) : null, + seatsMode === 'houses' ? legendHouses(data, houseNames) : legendElection(data), + data.leaderId ? p({ class: 'hemi-note' }, `${i18n.parliamentSeatsLeader}: `, a({ href: `/author/${encodeURIComponent(data.leaderId)}`, class: 'hemi-legend-link' }, (data.seats.find(s => s.id === data.leaderId) || {}).name || data.leaderId)) : null + ); +}; + + +const campaignBlock = (pItem) => pItem && pItem.campaignId + ? [ + div({ class: 'card-field' }, span({ class: 'card-label' }, String(i18n.campaignSignaturesLabel || 'Signatures').toUpperCase() + ': '), span({ class: 'card-value' }, `${Number(pItem.signatures) || 0}${Number(pItem.goal) > 0 ? ` / ${Number(pItem.goal)}` : ''}`)), + div({ class: 'doc-export-actions doc-export-left' }, form({ method: 'GET', action: `/campaigns/${encodeURIComponent(pItem.campaignId)}/pdf` }, button({ type: 'submit', class: 'filter-btn' }, i18n.parliamentViewReport))) + ] + : []; const TERM_DAYS = 60; @@ -29,26 +176,6 @@ const showVoteMetrics = (method) => { }; const applyEl = (fn, attrs, kids) => fn.apply(null, [attrs || {}].concat(kids || [])); const methodImageSrc = (method) => `assets/images/${String(method || '').toUpperCase().toLowerCase()}.png`; -const MethodBadge = (method) => { - const m = String(method || '').toUpperCase(); - const labelTxt = String(i18n[`parliamentMethod${m}`] || m).toUpperCase(); - return span( - { class: 'method-badge' }, - labelTxt, - br(),br(), - img({ src: methodImageSrc(m), alt: labelTxt, class: 'method-badge__icon' }) - ); -}; -const MethodHero = (method) => { - const m = String(method || '').toUpperCase(); - const labelTxt = String(i18n[`parliamentMethod${m}`] || m).toUpperCase(); - return span( - { class: 'method-hero' }, - labelTxt, - br(),br(), - img({ src: methodImageSrc(m), alt: labelTxt, class: 'method-hero__icon' }) - ); -}; const KPI = (labelTxt, value) => div({ class: 'kpi' }, span({ class: 'kpi__label' }, labelTxt), @@ -64,12 +191,14 @@ const CycleInfo = (start, end, labels = { KPI((labels.end + ': ').toUpperCase(), fmt(end)), KPI((labels.remaining + ': ').toUpperCase(), timeLeft(end)) ); -const Tabs = (active) => +const Tabs = (active, avail = null) => div( { class: 'filters' }, form( { method: 'GET', action: '/parliament' }, - ['government', 'candidatures', 'proposals', 'laws', 'revocations', 'historical', 'leaders', 'rules'].map(f => + ['government', 'candidatures', 'proposals', 'laws', 'revocations', 'historical', 'leaders', 'rules'] + .filter(f => !avail || f === 'government' || f === 'rules' || f === active || avail[f]) + .map(f => button({ type: 'submit', name: 'filter', value: f, class: active === f ? 'filter-btn active' : 'filter-btn' }, i18n[`parliamentFilter${f.charAt(0).toUpperCase()+f.slice(1)}`]) ) ) @@ -118,7 +247,7 @@ const GovHeader = (g) => { ); }; -const GovernmentCard = (g, meta) => { +const GovernmentCard = (g, meta, hemicycle = null, seatsMode = 'election', houseNames = {}) => { const termStart = g && g.since ? g.since : null; const termEnd = g && g.end ? g.end : null; const actorLabel = @@ -154,7 +283,9 @@ const GovernmentCard = (g, meta) => { { class: 'card' }, h2(i18n.parliamentGovernmentCard), GovHeader(g), - div({ class: 'method-image-centered' }, img({ src: methodImageSrc(methodKey), alt: methodLabel })), + hemicycle + ? renderHemicycle(hemicycle, { mode: seatsMode, baseHref: '/parliament?filter=government', houseNames }) + : div({ class: 'method-image-centered' }, img({ src: methodImageSrc(methodKey), alt: methodLabel })), div( { class: 'table-wrap' }, applyEl(table, { class: 'table table--centered gov-overview' }, [ @@ -275,14 +406,14 @@ const CandidatureStats = (cands, govCard, leaderMeta, electionQuorum = 2) => { hasQuorum ? div( { class: 'table-wrap mt-2' }, - applyEl(table, { class: 'parliament-gov-table' }, [ + applyEl(table, { class: 'parliament-gov-table parliament-rows-table' }, [ thead(tr( th(i18n.parliamentThLeader), th({ class: 'parliament-method-col' }, i18n.parliamentGovMethod), th({ class: 'parliament-votes-col' }, i18n.parliamentVotesReceived) )), tbody(tr( - td( + td({ 'data-label': i18n.parliamentThLeader }, div({ class: 'parliament-avatar-cell' }, avatarHref ? a({ href: avatarHref }, img({ src: avatarSrc, class: 'parliament-leader-avatar' })) @@ -290,10 +421,10 @@ const CandidatureStats = (cands, govCard, leaderMeta, electionQuorum = 2) => { idLink ? div({ class: 'parliament-avatar-name' }, idLink) : null ) ), - td({ class: 'parliament-method-col' }, + td({ class: 'parliament-method-col', 'data-label': i18n.parliamentGovMethod }, img({ src: methodImageSrc(methodKey), alt: methodLabel, class: 'parliament-method-img' }) ), - td({ class: 'parliament-votes-col' }, span({ class: 'votes-value' }, votes)) + td({ class: 'parliament-votes-col', 'data-label': i18n.parliamentVotesReceived }, span({ class: 'votes-value' }, votes)) )) ]) ) @@ -307,23 +438,19 @@ const CandidaturesTable = (candidatures) => { c.targetType === 'inhabitant' ? p(userLink(c.targetId)) : p(a({ class: 'tag-link', href: `/tribe/${encodeURIComponent(c.targetId)}?` }, c.targetTitle || c.targetId)); - const voters = Array.isArray(c.voters) ? c.voters : []; return tr( - td(idLink), - td(fmt(c.createdAt)), - td({ class: 'nowrap' }, c.method), - td(c.targetType === 'inhabitant' ? String(c.karma || 0) : '-'), - td( - span({ class: 'candidature-votes-count' }, String(c.votes || 0)), - voters.length ? div({ class: 'candidature-voters' }, ...voters.map(v => div({ class: 'candidature-voter' }, userLink(v)))) : null - ), - td(form({ method: 'POST', action: `/parliament/candidatures/${encodeURIComponent(c.id)}/vote` }, button({ class: 'vote-btn' }, i18n.parliamentVoteBtn))) + td({ 'data-label': i18n.parliamentThId }, idLink), + td({ 'data-label': i18n.parliamentThProposalDate }, fmt(c.createdAt)), + td({ class: 'nowrap', 'data-label': i18n.parliamentThMethod }, c.method), + td({ 'data-label': i18n.parliamentThKarma }, c.targetType === 'inhabitant' ? String(c.karma || 0) : '-'), + td({ 'data-label': i18n.parliamentThSupports }, span({ class: 'candidature-votes-count' }, String(c.votes || 0))), + td({ 'data-label': '' }, form({ method: 'POST', action: `/parliament/candidatures/${encodeURIComponent(c.id)}/vote` }, button({ class: 'vote-btn' }, i18n.parliamentVoteBtn))) ); }); return div( { class: 'table-wrap' }, h2(i18n.parliamentCandidaturesListTitle), - applyEl(table, { class: 'table table--centered' }, [ + applyEl(table, { class: 'table table--centered parliament-rows-table' }, [ thead(tr( th(i18n.parliamentThId), th(i18n.parliamentThProposalDate), @@ -378,11 +505,11 @@ const ProposalsList = (proposals) => { span({ class: 'card-label' }, i18n.parliamentGovMethod.toUpperCase() + ': '), span({ class: 'card-value' }, pItem.method) ), - br(), div( h2(titleNode), - p(pItem.description || '') + p(...renderStyledText(pItem.description || '')) ), + ...campaignBlock(pItem), pItem.deadline ? div( { class: 'card-field' }, @@ -421,7 +548,7 @@ const ProposalsList = (proposals) => { pItem && pItem.voteId ? form( { method: 'GET', action: `/votes/${encodeURIComponent(pItem.voteId)}` }, - button({ type: 'submit', class: 'vote-btn' }, i18n.parliamentVoteAction) + button({ type: 'submit', class: 'buy-btn' }, i18n.parliamentVoteAction) ) : null ); @@ -443,7 +570,8 @@ const FutureLawsList = (rows) => { div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.parliamentThProposalDate.toUpperCase() + ': '), span({ class: 'card-value' }, fmt(pItem.createdAt))), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.parliamentLawProposer.toUpperCase() + ': '), span({ class: 'card-value' }, userLink(pItem.proposer))), h2(pItem.title || ''), - p(pItem.description || '') + p(...renderStyledText(pItem.description || '')), + ...campaignBlock(pItem) ) ); return div( @@ -553,7 +681,7 @@ const RevocationsList = (revocations) => { pItem && pItem.voteId ? form( { method: 'GET', action: `/votes/${encodeURIComponent(pItem.voteId)}` }, - button({ type: 'submit', class: 'vote-btn' }, i18n.parliamentVoteAction) + button({ type: 'submit', class: 'buy-btn' }, i18n.parliamentVoteAction) ) : null ); @@ -594,7 +722,7 @@ const LawsStats = (laws = [], revocatedCount = 0) => { return div( { class: 'table-wrap' }, h2(i18n.parliamentPoliciesTitle || 'POLICIES'), - applyEl(table, { class: 'table table--centered' }, [ + applyEl(table, { class: 'table table--centered parliament-rows-table' }, [ thead(tr( th(i18n.parliamentThProposed), th(i18n.parliamentThApproved), @@ -604,11 +732,11 @@ const LawsStats = (laws = [], revocatedCount = 0) => { )), tbody( tr( - td(String(proposed)), - td(String(approved)), - td(String(declined)), - td(String(discarded)), - td(String(revocated)) + td({ 'data-label': i18n.parliamentThProposed }, String(proposed)), + td({ 'data-label': i18n.parliamentThApproved }, String(approved)), + td({ 'data-label': i18n.parliamentThDeclined }, String(declined)), + td({ 'data-label': i18n.parliamentThDiscarded }, String(discarded)), + td({ 'data-label': i18n.parliamentPoliciesRevocated }, String(revocated)) ) ) ]) @@ -657,19 +785,19 @@ const HistoricalGovsSummary = (rows = []) => { const entries = Array.from(byMethod.entries()).sort((a,b) => String(a[0]).localeCompare(String(b[0]))); const lines = entries.map(([method, rec]) => tr( - td(method), - td(String(rec.proposed)), - td(String(rec.approved)), - td(String(rec.declined)), - td(String(rec.discarded)), - td(String(rec.revocated)), - td(String(rec.cycles)) + td({ 'data-label': String(i18n.parliamentGovMethod).toUpperCase() }, method), + td({ 'data-label': i18n.parliamentPoliciesProposal }, String(rec.proposed)), + td({ 'data-label': i18n.parliamentPoliciesApproved }, String(rec.approved)), + td({ 'data-label': i18n.parliamentPoliciesDeclined }, String(rec.declined)), + td({ 'data-label': i18n.parliamentPoliciesDiscarded }, String(rec.discarded)), + td({ 'data-label': i18n.parliamentPoliciesRevocated }, String(rec.revocated)), + td({ 'data-label': i18n.parliamentThCycles }, String(rec.cycles)) ) ); return div( { class: 'table-wrap' }, h2(i18n.parliamentHistoricalGovernmentsTitle || 'Governments'), - applyEl(table, { class: 'table table--centered' }, [ + applyEl(table, { class: 'table table--centered parliament-rows-table' }, [ thead(tr( th(String(i18n.parliamentGovMethod).toUpperCase()), th(i18n.parliamentPoliciesProposal), @@ -782,7 +910,7 @@ const LeadersSummary = (leaders = [], candidatures = [], allGovCards = [], total return div( { class: 'table-wrap' }, h2(i18n.parliamentHistoricalLawsTitle || 'Actions'), - applyEl(table, { class: 'table table--centered' }, [ + applyEl(table, { class: 'table table--centered parliament-rows-table' }, [ thead(tr( th(i18n.parliamentThTotalCandidatures), th(i18n.parliamentThTimesInPower), @@ -795,14 +923,14 @@ const LeadersSummary = (leaders = [], candidatures = [], allGovCards = [], total )), tbody( tr( - td(String(presented)), - td(String(timesInPower)), - td(String(totals.proposed)), - td(String(totals.approved)), - td(String(totals.declined)), - td(String(totals.discarded)), - td(String(totals.revocated)), - td(`${efficiencyPct} %`) + td({ 'data-label': i18n.parliamentThTotalCandidatures }, String(presented)), + td({ 'data-label': i18n.parliamentThTimesInPower }, String(timesInPower)), + td({ 'data-label': i18n.parliamentThProposed }, String(totals.proposed)), + td({ 'data-label': i18n.parliamentThApproved }, String(totals.approved)), + td({ 'data-label': i18n.parliamentThDeclined }, String(totals.declined)), + td({ 'data-label': i18n.parliamentThDiscarded }, String(totals.discarded)), + td({ 'data-label': i18n.parliamentPoliciesRevocated }, String(totals.revocated)), + td({ 'data-label': i18n.parliamentEfficiency }, `${efficiencyPct} %`) ) ) ]) @@ -825,13 +953,13 @@ const LeadersList = (leaders, metas = {}, candidatures = []) => { : userLink(l.powerId, l.powerTitle); const leaderCell = div({ class: 'leader-cell parliament-avatar-cell' }, avatar, div({ class: 'parliament-avatar-name' }, link)); return tr( - td(leaderCell), - td(String(l.proposed || 0)), - td(String(l.approved || 0)), - td(String(l.declined || 0)), - td(String(l.discarded || 0)), - td(String(l.revocated || 0)), - td(`${(l.efficiency != null ? Math.round(l.efficiency * 100) : (l.proposed > 0 ? Math.round((l.approved / l.proposed) * 100) : 0))} %`) + td({ 'data-label': i18n.parliamentThLeader }, leaderCell), + td({ 'data-label': i18n.parliamentPoliciesProposal }, String(l.proposed || 0)), + td({ 'data-label': i18n.parliamentPoliciesApproved }, String(l.approved || 0)), + td({ 'data-label': i18n.parliamentPoliciesDeclined }, String(l.declined || 0)), + td({ 'data-label': i18n.parliamentPoliciesDiscarded }, String(l.discarded || 0)), + td({ 'data-label': i18n.parliamentPoliciesRevocated }, String(l.revocated || 0)), + td({ 'data-label': i18n.parliamentEfficiency }, `${(l.efficiency != null ? Math.round(l.efficiency * 100) : (l.proposed > 0 ? Math.round((l.approved / l.proposed) * 100) : 0))} %`) ); }); return div( @@ -856,6 +984,7 @@ const RulesContent = () => { const points = [ i18n.parliamentRulesIntro, i18n.parliamentRulesTerm, + i18n.parliamentRulesHemicycle, i18n.parliamentRulesMethods, i18n.parliamentRulesAnarchy, i18n.parliamentRulesCandidates, @@ -888,7 +1017,7 @@ const CandidaturesSection = (governmentCard, candidatures, leaderMeta, electionQ GovHeader(governmentCard || {}), CandidatureStats(candidatures || [], governmentCard || null, leaderMeta || null, electionQuorum), CandidatureForm(), - candidatures && candidatures.length ? CandidaturesTable(candidatures) : null + candidatures && candidatures.length ? CandidaturesTable(paged(candidatures)) : null ); }; @@ -897,7 +1026,7 @@ const ProposalsSection = (governmentCard, proposals, futureLaws, canPropose) => const fl = FutureLawsList(futureLaws || []); if (!has && canPropose) return div(h2(i18n.parliamentGovernmentCard), GovHeader(governmentCard || {}), ProposalForm(), fl); if (!has && !canPropose) return div(h2(i18n.parliamentGovernmentCard), GovHeader(governmentCard || {}), NoProposals(), fl); - return div(h2(i18n.parliamentGovernmentCard), GovHeader(governmentCard || {}), ProposalForm(), ProposalsList(proposals), fl); + return div(h2(i18n.parliamentGovernmentCard), GovHeader(governmentCard || {}), ProposalForm(), ProposalsList(paged(proposals)), fl); }; const RevocationsSection = (governmentCard, laws, revocations, futureRevocations) => @@ -905,7 +1034,7 @@ const RevocationsSection = (governmentCard, laws, revocations, futureRevocations h2(i18n.parliamentGovernmentCard), GovHeader(governmentCard || {}), RevocationForm(laws || []), - RevocationsList(revocations || []) || '', + RevocationsList(paged(revocations || [])) || '', FutureRevocationsList(futureRevocations || []) || '' ); @@ -930,6 +1059,9 @@ const parliamentView = async (state) => { leaders, leaderMeta, powerMeta, + hemicycle, + seatsMode, + houseNames, revocations, futureRevocations, revocationsEnactedCount, @@ -964,24 +1096,30 @@ const parliamentView = async (state) => { const LawsSectionWrap = () => div( LawsStats(laws || [], revocationsEnactedCount || 0), - LawsList(laws || []) + LawsList(paged(laws || [])) ); return template( i18n.parliamentTitle, - section(div({ class: 'tags-header' }, h2(i18n.parliamentTitle), p(i18n.parliamentDescription)), Tabs(filter)), + section(div({ class: 'tags-header module-header-line' }, h2(i18n.parliamentTitle), p(i18n.parliamentDescription)), Tabs(filter, { + candidatures: true, + proposals: true, + laws: (laws || []).length > 0 || (futureLaws || []).length > 0, + revocations: (revocations || []).length > 0 || (futureRevocations || []).length > 0, + historical: (historical || []).length > 0, + leaders: (leaders || []).length > 0 + })), section( - filter === 'government' ? GovernmentCard(gov, powerMeta) : null, + filter === 'government' ? GovernmentCard(gov, powerMeta, hemicycle || null, seatsMode || 'election', houseNames || {}) : null, filter === 'candidatures' ? CandidaturesSection(gov, candidatures, leaderMeta, electionQuorum) : null, filter === 'proposals' ? ProposalsSection(gov, proposals, futureLaws, canPropose) : null, filter === 'laws' ? LawsSectionWrap() : null, filter === 'revocations' ? RevocationsSection(gov, laws, revocations, futureRevocations) : null, - filter === 'historical' ? div(HistoricalGovsSummary(historical || []), HistoricalList(historical || [], historicalMetas)) : null, - filter === 'leaders' ? div(LeadersSummary(leaders || [], candidatures || [], leadersGovCards, leadersTotalCandidatures), LeadersList(leaders || [], leadersMetas, candidatures || [])) : null, + filter === 'historical' ? div(HistoricalGovsSummary(historical || []), HistoricalList(paged(historical || []), historicalMetas)) : null, + filter === 'leaders' ? div(LeadersSummary(leaders || [], candidatures || [], leadersGovCards, leadersTotalCandidatures), LeadersList(paged(leaders || []), leadersMetas, candidatures || [])) : null, filter === 'rules' ? RulesContent() : null ) ); }; -module.exports = { parliamentView, pickLeader }; - +module.exports = { parliamentView, pickLeader, renderHemicycle }; diff --git a/src/views/peers_view.js b/src/views/peers_view.js index 4f9e793..179d0c2 100644 --- a/src/views/peers_view.js +++ b/src/views/peers_view.js @@ -1,12 +1,33 @@ -const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadcastActive = false, technicalPeers = [] }) => { - const { form, button, div, h2, p, section, a, hr, input, label, br, span, table, tr, td, textarea } = require("../server/node_modules/hyperaxe"); - const { template, i18n } = require('./main_views'); +const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadcastActive = false, technicalPeers = [], versions = {}, staleKeys = [], onionKeys = [], ownVersion = '', lastErrors = {}, paused = false }) => { + const { form, button, div, h2, p, section, a, hr, input, label, br, span, table, tr, td, textarea, wbr } = require("../server/node_modules/hyperaxe"); + const { template, i18n, renderStateChip } = require('./main_views'); + const { NET_REASON_KEYS, canonicalKey } = require('../models/peer_health'); + const majorMinor = (v) => String(v || '').split('.').slice(0, 2).map(n => parseInt(n, 10) || 0); + const isOutdated = (v) => { + if (!v || !ownVersion) return false; + const [a1, b1] = majorMinor(v), [a2, b2] = majorMinor(ownVersion); + return a1 < a2 || (a1 === a2 && b1 < b2); + }; + const stale = new Set(staleKeys || []); + const outdatedKey = (k) => isOutdated((versions || {})[k]) || (!(versions || {})[k] && stale.has(k)); + const versionCell = (k) => { + const v = (versions || {})[k]; + if (!v) return [stale.has(k) ? span({ title: i18n.peerVersionUnannounced }, renderStateChip('closed', null, String(i18n.peerOutdated).toUpperCase())) : '—']; + const old = isOutdated(v); + return [span({ title: old ? i18n.peerOutdated : '' }, renderStateChip(old ? 'closed' : 'mutuals', null, String(v)))]; + }; + const stateCell = (tp) => { + const st = String(tp.state || ''); + if (st === 'connected') return renderStateChip('mutuals', null, String(i18n.peerStateConnected).toUpperCase()); + if (st === 'connecting') return renderStateChip('whole', null, String(i18n.peerStateConnecting).toUpperCase()); + if (st === 'staged') return renderStateChip(outdatedKey(tp.key) ? 'whole' : 'mutuals', null, String(i18n.peerStateStaged).toUpperCase()); + const err = (lastErrors || {})[canonicalKey(tp.key)]; + return renderStateChip('hidden', null, String(i18n[err ? NET_REASON_KEYS[err.reason] : 'peerErrSilent'] || i18n.peerErrOther).toUpperCase()); + }; - const startButton = form({ action: "/settings/conn/start", method: "post" }, button({ type: "submit" }, i18n.startNetworking)); - const restartButton = form({ action: "/settings/conn/restart", method: "post" }, button({ type: "submit" }, i18n.restartNetworking)); - const stopButton = form({ action: "/settings/conn/stop", method: "post" }, button({ type: "submit" }, i18n.stopNetworking)); - const syncButton = form({ action: "/settings/conn/sync", method: "post" }, button({ type: "submit" }, i18n.sync)); - const connButtons = [startButton, restartButton, stopButton, syncButton]; + const pauseButton = paused + ? form({ action: "/peers/resume", method: "post" }, button({ type: "submit", class: "tribe-action-btn success-btn" }, String(i18n.peersResume || 'Resume').toUpperCase())) + : form({ action: "/peers/pause", method: "post" }, button({ type: "submit", class: "tribe-action-btn danger-btn" }, String(i18n.peersPause || 'Pause').toUpperCase())); const deduplicatePeers = (peers) => { const seen = new Set(); @@ -28,16 +49,28 @@ const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadc return null; }; + const shortHost = (h) => { + const s = String(h || ''); + const m = s.match(/^(?:onion:)?([a-z2-7]{16,56})\.onion(.*)$/i); + return m ? `${m[1].slice(0, 10)}….onion${m[2]}` : s; + }; + const onion = new Set(onionKeys || []); + const overTor = (peerData) => /^onion:/i.test(String(peerData[0] || '')) || /\.onion$/i.test(String((peerData[1] || {}).host || '')) || onion.has(canonicalKey((peerData[1] || {}).key)); const renderPeerRow = (peerData) => { const peer = peerData[1]; const { name, users, key } = peer; + const tor = overTor(peerData); const peerUrl = `/author/${encodeURIComponent(key)}`; const filteredUsers = (users || []).filter(u => u.id !== key); - const userCount = filteredUsers.length; + const userCount = Array.isArray(users) ? filteredUsers.length : null; return tr( - td({ 'data-label': i18n.peerHost || 'Pub' }, a({ href: peerUrl, class: "user-link" }, name || key.slice(0, 20) + '…')), - td({ 'data-label': 'Key' }, a({ href: peerUrl, class: 'user-link peer-key' }, key)), - td({ 'data-label': i18n.peersReplicatedFeeds || 'Feeds' }, String(userCount)) + td({ 'data-label': i18n.peerHost || 'Pub' }, a({ href: peerUrl, class: "user-link", title: name || key }, name ? shortHost(name) : key.slice(0, 20) + '…')), + td({ 'data-label': i18n.peersOasisId || 'Oasis ID' }, a({ href: peerUrl, class: 'user-link peer-key' }, key)), + td({ 'data-label': i18n.peersOasisVersion || 'Version' }, ...versionCell(key)), + td({ 'data-label': i18n.peersTorColumn }, tor + ? span({ class: 'ubi-tick-ok', title: i18n.peersTorYes }, '✓') + : span({ class: 'peer-tor-no', title: i18n.peersTorNo }, '—')), + td({ 'data-label': i18n.peersReplicatedFeeds || 'Replicated' }, userCount == null ? '—' : String(userCount)) ); }; @@ -54,8 +87,10 @@ const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadc return table({ class: 'block-info-table' }, tr( td({ class: 'card-label' }, i18n.peerHost || 'Pub'), - td({ class: 'card-label' }, 'Key'), - td({ class: 'card-label' }, i18n.peersReplicatedFeeds || 'Replicated feeds') + td({ class: 'card-label' }, i18n.peersOasisId || 'Oasis ID'), + td({ class: 'card-label' }, i18n.peersOasisVersion || 'Version'), + td({ class: 'card-label' }, i18n.peersTorColumn), + td({ class: 'card-label' }, i18n.peersReplicatedFeeds || 'Replicated') ), ...peers.map(renderPeerRow) ); @@ -67,12 +102,15 @@ const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadc const action = connected ? 'disconnect' : 'connect'; const btnLabel = connected ? (i18n.peerDisconnect || 'Disconnect') : (i18n.peerConnect || 'Connect'); return tr( - td({ 'data-label': 'Key' }, a({ href: `/author/${encodeURIComponent(k)}`, class: 'user-link peer-key' }, k ? k.slice(0, 20) + '…' : '—')), - td({ 'data-label': 'Host' }, String(tp.host || '—')), - td({ 'data-label': i18n.peerPort || 'Port' }, String(tp.port || '—')), - td({ 'data-label': 'State' }, String(tp.state || tp.source || '—')), - td({ 'data-label': i18n.peerLastChange || 'Last change' }, String(tp.stateChange ? new Date(tp.stateChange).toISOString().slice(0, 16).replace('T', ' ') : '—')), - td({ 'data-label': '' }, + td({ 'data-label': i18n.peersOasisId }, a({ href: `/author/${encodeURIComponent(k)}`, class: 'user-link peer-key' }, k ? k.slice(0, 20) + '…' : '—')), + onion.has(canonicalKey(tp.key)) || /\.onion$/i.test(String(tp.host || '')) + ? td({ 'data-label': i18n.peerAddressLabel, class: 'peer-host' }, renderStateChip('fediverse', null, 'TOR')) + : td({ 'data-label': i18n.peerAddressLabel, title: String(tp.host || ''), class: 'peer-host' }, tp.host ? shortHost(tp.host).split(/(?<=\.)/).reduce((acc, part, i) => i ? [...acc, wbr(), part] : [part], []) : '—'), + td({ 'data-label': i18n.peerPort, class: 'peer-port' }, String(tp.port || '—')), + td({ 'data-label': i18n.peersOasisVersion || 'Version' }, ...versionCell(k)), + td({ 'data-label': i18n.peerStateLabel }, stateCell(tp)), + td({ 'data-label': i18n.peerLastChangeLabel }, String(tp.stateChange ? new Date(tp.stateChange).toISOString().slice(0, 16).replace('T', ' ') : '—')), + td( form({ method: "POST", action: `/peers/${action}`, class: "inline-form" }, input({ type: "hidden", name: "key", value: k }), input({ type: "hidden", name: "host", value: String(tp.host || '') }), @@ -82,9 +120,9 @@ const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadc ) ); }); - const refreshButton = form({ action: "/peers/refresh", method: "post" }, button({ type: "submit" }, i18n.peerRefresh || 'Refresh')); - const pruneButton = form({ action: "/peers/prune", method: "post" }, button({ type: "submit" }, i18n.peerPruneIdle || 'Remove idle')); - const exportButton = form({ action: "/peers/export", method: "get" }, button({ type: "submit" }, i18n.peerExport || 'Export')); + const refreshButton = form({ action: "/peers/refresh", method: "post" }, button({ type: "submit", class: "filter-btn" }, i18n.peerRefresh || 'Refresh')); + const pruneButton = form({ action: "/peers/prune", method: "post" }, button({ type: "submit", class: "filter-btn" }, i18n.peerPruneIdle || 'Remove idle')); + const exportButton = form({ action: "/peers/export", method: "get" }, button({ type: "submit", class: "filter-btn" }, i18n.peerExport || 'Export')); const importForm = form( { action: "/peers/import", method: "post", enctype: "multipart/form-data", class: "peers-import-form" }, label({ class: 'peers-import-label' }, i18n.peerImportTitle || 'Import peer list'), @@ -98,15 +136,17 @@ const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadc const peersTechnicalBlock = div({ class: 'tags-header peers-technical-block' }, h2(i18n.peerConnectionsTitle || 'Connections'), - div({ class: "conn-actions peers-conn-actions" }, refreshButton, pruneButton, exportButton), + div({ class: "peers-pause-row" }, pauseButton, p({ class: paused ? "peers-paused-note" : "peers-pause-hint" }, paused ? i18n.peersPausedNote : i18n.peersPauseHint)), + div({ class: "conn-actions peers-conn-actions filters" }, refreshButton, pruneButton, exportButton), technicalPeers.length ? table({ class: 'block-info-table' }, tr( - td({ class: 'card-label' }, 'Key'), - td({ class: 'card-label' }, 'Host'), - td({ class: 'card-label' }, i18n.peerPort || 'Port'), - td({ class: 'card-label' }, 'State'), - td({ class: 'card-label' }, 'Last change'), + td({ class: 'card-label' }, i18n.peersOasisId), + td({ class: 'card-label' }, i18n.peerAddressLabel), + td({ class: 'card-label peer-port' }, i18n.peerPort), + td({ class: 'card-label' }, i18n.peersOasisVersion || 'Version'), + td({ class: 'card-label' }, i18n.peerStateLabel), + td({ class: 'card-label' }, i18n.peerLastChangeLabel), td({ class: 'card-label' }, '') ), ...technicalRows @@ -118,11 +158,10 @@ const peersView = async ({ onlinePeers, discoveredPeers, unknownPeers, lanBroadc return template( i18n.peers, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.peers), p(i18n.peerConnectionsIntro) ), - div({ class: "conn-actions" }, ...connButtons), (onlineCount + discoveredCount + unknownCount) > 0 ? div({ class: "peers-list" }, div({ class: "tags-header" }, h2(`${i18n.online} (${onlineCount})`)), diff --git a/src/views/phone_view.js b/src/views/phone_view.js new file mode 100644 index 0000000..373d5dc --- /dev/null +++ b/src/views/phone_view.js @@ -0,0 +1,298 @@ +const { div, h2, p, section, button, form, input, span, audio, table, tr, td, th, br, a } = require("../server/node_modules/hyperaxe"); +const { template, i18n, userLink, userLinkLabel, renderCallButton, liveClock, renderStateChip, renderVideoGrid } = require("./main_views"); +const { renderEncryptedChip } = require("./clearnet_view"); +const { phoneNumberOf } = require("../models/phone_number"); +const moment = require("../server/node_modules/moment"); + +const PAM_MAX_MS = require('../server/phone_module').VOICEMAIL_MAX_MS; + +const clock = (ms) => { + const s = Math.max(0, Math.floor((Number(ms) || 0) / 1000)); + return `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`; +}; + +const postButton = (action, label, danger = false, fields = {}) => + form({ method: 'POST', action, class: 'phone-action-form' }, + ...Object.entries(fields).map(([name, value]) => input({ type: 'hidden', name, value: String(value) })), + button({ type: 'submit', class: danger ? 'tribe-action-btn danger-btn' : 'tribe-action-btn' }, String(label).toUpperCase()) + ); + +const linkList = (ids) => ids.reduce((acc, id, i) => i > 0 ? [...acc, ', ', userLink(id)] : [userLink(id)], []); +const peersOf = (h) => (Array.isArray(h.peers) && h.peers.length ? h.peers : [h.peer]).filter(Boolean); + +const OUTCOME_LABEL = { + ended: () => i18n.phoneOutcomeEnded, + missed: () => i18n.phoneOutcomeMissed, + rejected: () => i18n.phoneOutcomeRejected, + noanswer: () => i18n.phoneOutcomeNoanswer, + cancelled: () => i18n.phoneOutcomeCancelled, + pam: () => i18n.phoneOutcomePam, + failed: () => i18n.phoneOutcomeFailed +}; + +const renderCallPanel = (st, now) => { + if (!st) return null; + const who = st.group ? linkList(st.peers.map(x => x.id)) : [userLink(st.peer)]; + const line = (...parts) => div({ class: 'phone-call-who' }, span({ class: 'phone-call-icon' }, '✆'), ...parts); + const actions = (...btns) => div({ class: 'phone-call-actions' }, ...btns); + let body; + if (st.dir === 'in' && st.phase === 'incoming') { + body = [line(...who, ' ', i18n.phoneIsCalling), actions(process.env.OASIS_MOBILE === '1' ? postButton('/phone/accept', '\u2706 ' + i18n.phoneEarpiece) : postButton('/phone/accept', i18n.phoneAnswer), process.env.OASIS_MOBILE === '1' ? a({ href: '/phone/accept-speaker', class: 'tribe-action-btn phone-speaker-btn' }, span({ class: 'phone-ico' }, '\ud83d\udd0a'), ' ' + String(i18n.phoneSpeaker).toUpperCase()) : null, postButton('/phone/reject', i18n.phoneReject, true))]; + } else if (st.phase === 'calling') { + body = [line(st.reached ? i18n.phoneCalling : i18n.phoneConnecting, ' ', ...who, ' ', span({ class: 'phone-call-clock' }, liveClock(st.startedAt))), actions(postButton('/phone/hangup', i18n.phoneHangup, true))]; + } else if (st.phase === 'connecting' || st.phase === 'connected') { + const inCall = st.group ? linkList(st.peers.filter(x => x.phase === 'connected').map(x => x.id)) : who; + const ringing = st.group ? st.peers.filter(x => x.phase === 'calling').map(x => x.id) : []; + const silenceButton = (id, on) => on + ? postButton('/phone/silence', i18n.phoneUnsilence, true, { id, on: 0 }) + : postButton('/phone/silence', i18n.phoneSilence, false, { id, on: 1 }); + const peerRow = (pr) => div({ class: 'phone-contact room-peer' }, + span({ class: 'phone-call-icon' }, '✆'), + userLink(pr.id), + silenceButton(pr.id, pr.silenced) + ); + body = [ + line(...inCall, ' ', span({ class: 'phone-call-clock' }, liveClock(st.answeredAt || st.startedAt))), + ringing.length ? div({ class: 'phone-call-ringing' }, i18n.phoneCalling, ' ', ...linkList(ringing)) : null, + st.group ? div({ class: 'phone-contacts phone-call-peers' }, ...st.peers.filter(x => x.phase === 'connected').map(peerRow)) : null, + st.video ? renderVideoGrid([{ id: st.peer, on: st.peerCam }], st.cam) : null, + actions( + st.muted ? postButton('/phone/mute', i18n.phoneUnmute, false, { mute: 0 }) : postButton('/phone/mute', i18n.phoneMute, false, { mute: 1 }), + st.video ? (st.cam ? postButton('/phone/camera', i18n.phoneCameraOff, false, { cam: 0 }) : postButton('/phone/camera', i18n.phoneCameraOn, false, { cam: 1 })) : null, + st.group ? null : silenceButton(st.peer, st.silenced), + process.env.OASIS_MOBILE === '1' ? (() => { let on = false; try { on = require('fs').readFileSync(require('path').join(__dirname, '..', '..', '.audio_speaker'), 'utf8').trim() === '1'; } catch (_) {} return on ? a({ href: '/phone/speaker?on=0', class: 'tribe-action-btn phone-speaker-btn' }, '\u2706 ' + String(i18n.phoneEarpiece).toUpperCase()) : a({ href: '/phone/speaker?on=1', class: 'tribe-action-btn phone-speaker-btn' }, span({ class: 'phone-ico' }, '\ud83d\udd0a'), ' ' + String(i18n.phoneSpeaker).toUpperCase()); })() : null, + postButton('/phone/hangup', i18n.phoneHangup, true) + ) + ]; + } else if (st.phase === 'recording' || st.phase === 'recorded') { + const elapsed = st.phase === 'recorded' ? clock(PAM_MAX_MS) : liveClock(st.recordingStartedAt || now, { max: PAM_MAX_MS }); + body = [line(i18n.phoneRecording, ' ', ...who, ' ', span({ class: 'phone-call-clock' }, elapsed, ` / ${clock(PAM_MAX_MS)}`)), p({ class: 'phone-pam-hint' }, st.why === 'busy' ? i18n.phoneLeaveMessageBusy : st.why === 'unreachable' ? i18n.phoneLeaveMessageUnreachable : i18n.phoneLeaveMessage), actions(postButton('/phone/pam/send', i18n.phoneSendPam), postButton('/phone/pam/cancel', i18n.phoneCancel, true))]; + } else { + return null; + } + return div({ class: 'phone-call-panel' }, + div({ class: 'room-live-head' }, + div({ class: 'room-live-head-main' }, + div({ class: 'card-chips-row' }, renderEncryptedChip(i18n), st.group ? span({ class: 'pm-exposition-chip phone-joint-chip' }, span({ class: 'pm-exposition-text' }, i18n.phoneJointCall)) : null) + ), + (process.env.OASIS_MOBILE === '1' && (st.phase === 'incoming' || st.phase === 'connecting' || st.phase === 'connected')) ? null : a({ href: '/phone', class: 'tribe-action-btn room-refresh-btn' }, String(i18n.liveRefresh).toUpperCase()) + ), + ...body + ); +}; + +const dndChip = () => renderStateChip('hidden', null, String(i18n.phoneDnd).toUpperCase()); + +const renderNumberChooser = (compose) => + div({ class: 'phone-number-chooser' }, + p(String(i18n.phoneNumberChoose || '').replace('{number}', compose.number || '')), + div({ class: 'phone-contacts' }, + ...(compose.candidates || []).map(c => div({ class: 'phone-contact' }, + userLink(c.id), + span({ class: 'date-link' }, c.id), + c.dnd ? dndChip() : postButton('/phone/call', `✆ ${i18n.phoneCallButton}`, false, { to: c.to, compose: 1 }) + )), + ...(compose.rooms || []).map(r => div({ class: 'phone-contact' }, + a({ href: `/rooms/${encodeURIComponent(r.id)}`, class: 'user-link' }, r.title || i18n.roomsTitle), + span({ class: 'date-link' }, i18n.roomsTitle), + postButton(`/rooms/join/${encodeURIComponent(r.id)}`, `ꘒ ${i18n.roomJoin}`) + )) + ) + ); + +const DIAL_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9']; +const NUMBER_TOKEN = /^\d{3}-\d{3}$/; + +const renderCompose = (compose) => { + const recipients = Array.isArray(compose.recipients) ? compose.recipients : []; + const max = Number(compose.max) || 7; + const dial = String(compose.dial || ''); + const full = recipients.length >= max; + const keep = () => [input({ type: 'hidden', name: 'filter', value: 'create' }), input({ type: 'hidden', name: 'to', value: compose.to || '' })]; + const key = (value, label, cls = 'phone-key', extra = {}) => button({ type: 'submit', name: 'key', value, class: cls, ...extra }, label); + return section( + section({ class: 'phone-flash-anchor' }), + div({ class: 'pm-form phone-compose' }, + compose.choose && ((compose.candidates || []).length || (compose.rooms || []).length) ? renderNumberChooser(compose) : null, + form({ method: 'GET', action: '/phone', class: 'phone-dialer', id: 'phone-dialer' }, + ...keep(), + key('add', `+ ${i18n.phoneDialAdd}`, 'phone-dial-add', full ? { disabled: true } : {}), + div({ class: 'phone-screen' }, + input({ type: 'text', name: 'dial', value: dial, placeholder: i18n.phoneDialPlaceholder, maxlength: '64', autocomplete: 'off', ...(dial ? {} : { autofocus: true }) }) + ), + div({ class: 'phone-dialpad' }, + ...DIAL_KEYS.map(d => key(d, d)), + key('back', '⌫', 'phone-key phone-key-fn', { title: i18n.phoneDialBack }), + key('0', '0'), + key('clear', '✕', 'phone-key phone-key-fn', { title: i18n.phoneDialClear }) + ) + ), + p({ class: 'phone-empty phone-dial-hint' }, i18n.phoneLimitsHint), + div({ class: 'phone-recipients' }, + span({ class: 'card-label' }, `${i18n.phoneDialInhabitants}: ${recipients.length}/${max}`), + recipients.length ? div({ class: 'phone-recipient-list' }, + ...recipients.map((r, i) => span({ class: 'phone-recipient' }, + NUMBER_TOKEN.test(r) ? span(`✆ ${r}`) : [userLink(r), span(`✆ ${phoneNumberOf(r)}`)], + form({ method: 'GET', action: '/phone' }, + ...keep(), + input({ type: 'hidden', name: 'dial', value: dial }), + button({ type: 'submit', name: 'key', value: `remove:${i}`, class: 'phone-recipient-remove', title: i18n.phoneDialRemove }, '✕') + ) + )) + ) : null + ), + div({ class: 'phone-dial-call' }, + button({ type: 'submit', form: 'phone-dialer', formaction: '/phone/call', attrs: { formmethod: 'post' }, name: 'compose', value: '1', class: 'pm-btn' }, `✆ ${String(i18n.phoneCallButton).toUpperCase()}`) + ) + ) + ); +}; + +const renderRecords = (pams) => + div({ class: 'phone-block' }, + h2(i18n.phonePamTitle), + pams.length + ? div({ class: 'message-list' }, + ...pams.map(m => div({ class: 'pm-card normal-pm phone-record' }, + table({ class: 'pm-info-table' }, + tr(td({ class: 'card-label' }, i18n.pmFromLabel || 'From:'), td({ class: 'card-value' }, userLink(m.from))), + tr(td({ class: 'card-label' }, i18n.privateDate || 'Date'), td({ class: 'card-value' }, moment(m.sentAt).format('YYYY/MM/DD HH:mm:ss'))), + tr( + td({ class: 'card-label' }, i18n.phoneDuration), + td({ class: 'card-value pm-subject-cell' }, + span(clock(m.durationSec * 1000)), + m.heard ? null : span({ class: 'pm-exposition-chip pm-unread-chip' }, span({ class: 'pm-exposition-text' }, i18n.phoneNew)) + ) + ), + tr(td({ class: 'card-label' }, i18n.pmEncryptionLabel || 'Encryption'), td({ class: 'card-value pm-encryption-cell' }, renderEncryptedChip(i18n))) + ), + m.ready === false + ? div({ class: 'phone-record-pending' }, span({ class: 'pm-exposition-chip pm-unread-chip' }, span({ class: 'pm-exposition-text' }, i18n.torrentStateDownloading))) + : audio({ controls: true, preload: 'metadata', src: `/phone/pam/${encodeURIComponent(m.key)}/audio` }), + div({ class: 'pm-actions' }, + renderCallButton(m.from, { upper: true, cls: 'pm-btn' }), + form({ method: 'POST', action: `/phone/pam/${encodeURIComponent(m.key)}/delete`, class: 'pm-action-form' }, + button({ type: 'submit', class: 'pm-btn delete-btn danger-btn' }, String(i18n.phoneDelete).toUpperCase()) + ) + ) + )) + ) + : p({ class: 'phone-empty' }, i18n.phoneNoPams) + ); + +const deleteCalls = (ids) => ids.length + ? form({ method: 'POST', action: '/phone/delete-shown', class: 'content-action-form phone-row-delete' }, + ...ids.map(id => input({ type: 'hidden', name: 'calls', value: id })), + button({ type: 'submit', class: 'btn-singleview btn-delete', title: i18n.phoneDelete }, '✕') + ) + : null; + +const renderLatest = (latest, canCall, history = []) => + div({ class: 'phone-block' }, + h2(i18n.phoneLatestTitle), + latest.length + ? div({ class: 'phone-contacts' }, + ...latest.map(c => div({ class: 'phone-contact phone-latest-row' }, + span({ class: 'phone-dir', title: c.dir === 'out' ? i18n.phoneOutgoing : i18n.phoneIncoming }, c.dir === 'out' ? '↗' : '↙'), + userLink(c.id), + span({ class: 'date-link' }, moment(c.startedAt).format('YYYY/MM/DD HH:mm')), + c.dnd ? dndChip() : canCall ? renderCallButton(c.id) : null, + deleteCalls(history.filter(h => peersOf(h).includes(c.id)).map(h => h.id)) + )) + ) + : p({ class: 'phone-empty' }, i18n.phoneNoHistory) + ); + +const renderHistory = (items) => + div({ class: 'phone-block' }, + h2(i18n.phoneHistoryTitle), + items.length + ? table({ class: 'phone-history' }, + tr(th(''), th(i18n.phoneWith), th(i18n.phoneResult), th(i18n.phoneWhen), th(i18n.phoneDuration), th('')), + ...items.map(h => tr( + td({ class: 'phone-dir', title: h.dir === 'out' ? i18n.phoneOutgoing : i18n.phoneIncoming }, h.dir === 'out' ? '↗' : '↙'), + td({ 'data-label': i18n.phoneWith }, ...linkList(peersOf(h))), + td({ 'data-label': i18n.phoneResult }, (OUTCOME_LABEL[h.outcome] || OUTCOME_LABEL.failed)()), + td({ 'data-label': i18n.phoneWhen }, moment(h.startedAt).format('YYYY/MM/DD HH:mm')), + td({ 'data-label': i18n.phoneDuration }, h.answeredAt ? clock(h.endedAt - h.answeredAt) : '—'), + td({ class: 'phone-row-actions' }, deleteCalls([h.id])) + )) + ) + : p({ class: 'phone-empty' }, i18n.phoneNoHistory) + ); + +exports.phoneView = ({ available = false, state = null, pams = [], history = [], latest = [], q = '', filter = 'all', compose = {}, now = Date.now(), refused = [] } = {}) => { + const qNorm = String(q || '').trim().toLowerCase(); + const matchId = (id) => !qNorm || [id, userLinkLabel(id)].some(v => String(v || '').toLowerCase().includes(qNorm)); + const isMissed = (h) => h.dir === 'in' && h.outcome === 'missed'; + const counts = { + all: pams.length + history.length, + records: pams.length, + missed: history.filter(isMissed).length, + incoming: history.filter(h => h.dir === 'in').length, + outgoing: history.filter(h => h.dir === 'out').length + }; + const records = pams.filter(m => matchId(m.from)); + const calls = history.filter(h => peersOf(h).some(matchId)); + const shownCalls = filter === 'missed' ? calls.filter(isMissed) + : filter === 'incoming' ? calls.filter(h => h.dir === 'in') + : filter === 'outgoing' ? calls.filter(h => h.dir === 'out') + : calls; + const showRecords = filter === 'all' || filter === 'records'; + const showCalls = filter === 'all' || filter === 'missed' || filter === 'incoming' || filter === 'outgoing'; + const unheard = showRecords ? records.filter(m => !m.heard).map(m => m.key) : []; + const deletable = [...(showRecords ? records.map(m => m.key) : []), ...(showCalls ? shownCalls.map(h => h.id) : [])]; + const canCall = available && !state; + const empty = counts.all === 0; + const filterBtn = (value, label, count) => + !empty && (value === 'all' || count > 0 || filter === value) + ? button({ type: 'submit', name: 'filter', value, class: filter === value ? 'filter-btn active' : 'filter-btn' }, `${String(label).toUpperCase()} (${count})`) + : null; + + return template( + i18n.phoneTitle, + section( + div({ class: 'tags-header module-header-line' }, + h2(i18n.phoneTitle), + p(i18n.phoneDescription), + renderEncryptedChip(i18n) + ), + available ? null : div({ class: 'pm-form-error-msg' }, p('✗ ' + i18n.phoneUnavailable)), + refused.length ? div({ class: 'phone-refused' }, span(i18n.phoneRefusedNotice), ' ', ...linkList(refused)) : null, + state ? renderCallPanel(state, now) : null, + div({ class: 'filters' }, + form({ method: 'GET', action: '/phone' }, + filterBtn('all', i18n.allButton || 'ALL', counts.all), + filterBtn('records', i18n.phonePamTitle, counts.records), + filterBtn('missed', i18n.phoneOutcomeMissed, counts.missed), + filterBtn('incoming', i18n.phoneIncoming, counts.incoming), + filterBtn('outgoing', i18n.phoneOutgoing, counts.outgoing), + canCall ? button({ type: 'submit', name: 'filter', value: 'create', class: 'create-button' }, i18n.phoneCreateButton) : null + ) + ), + (filter === 'create' || empty) ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + (unheard.length || deletable.length) ? div({ class: 'pm-exposition inbox-exposition' }, + span({ class: 'inbox-filters-label' }, i18n.inboxFiltersLabel || 'Filters:'), + unheard.length ? form({ method: 'POST', action: '/phone/heard-all', class: 'inbox-vis-toggle' }, + ...unheard.map(k => input({ type: 'hidden', name: 'keys', value: k })), + button({ type: 'submit', class: 'btn' }, `${i18n.inboxMarkAllRead} (${unheard.length})`) + ) : null, + deletable.length ? form({ method: 'POST', action: '/phone/delete-shown', class: 'inbox-vis-toggle inbox-bulk-delete' }, + ...(showRecords ? records.map(m => input({ type: 'hidden', name: 'keys', value: m.key })) : []), + ...(showCalls ? shownCalls.map(h => input({ type: 'hidden', name: 'calls', value: h.id })) : []), + button({ type: 'submit', class: 'btn delete-btn' }, `${String(i18n.inboxDeleteShown).toUpperCase()} (${deletable.length})`) + ) : null + ) : null, + form({ method: 'GET', action: '/phone', class: 'filter-box' }, + input({ type: 'hidden', name: 'filter', value: filter }), + input({ type: 'text', name: 'q', value: String(q || ''), placeholder: i18n.phoneSearchPlaceholder, class: 'filter-box__input' }), + button({ type: 'submit', class: 'filter-box__button' }, i18n.searchButton) + ) + ), + filter === 'create' && canCall ? renderCompose(compose) : null, + showRecords ? renderRecords(records) : null, + filter === 'all' ? renderLatest(latest.filter(c => matchId(c.id)), canCall, history) : null, + showCalls ? renderHistory(shownCalls) : null + ) + ); +}; diff --git a/src/views/pixelia_view.js b/src/views/pixelia_view.js index f2f5c85..d42de5e 100644 --- a/src/views/pixelia_view.js +++ b/src/views/pixelia_view.js @@ -1,7 +1,7 @@ const { div, h2, p, section, form, input, label, select, option, button, table, tr, td, hr, ul, li, a, br } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink} = require('./main_views'); +const { template, i18n, userLink, renderDocumentActions } = require('./main_views'); -exports.pixeliaView = (pixelArt, errorMessage) => { +exports.pixeliaView = (pixelArt) => { const title = i18n.pixeliaTitle; const description = i18n.pixeliaDescription; const gridWidth = 50; @@ -18,7 +18,7 @@ exports.pixeliaView = (pixelArt, errorMessage) => { return td( { id: cellId, - title: pixel ? `By: ${pixel.author}` : "", + title: pixel ? `${pixel.author}` : "", class: `pixel-cell ${colorClass}` }, "" @@ -33,7 +33,7 @@ exports.pixeliaView = (pixelArt, errorMessage) => { return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(description) ) @@ -64,10 +64,9 @@ exports.pixeliaView = (pixelArt, errorMessage) => { option({ value: "#d3d3d3", class: "pixelia-swatch pixelia-swatch-d3d3d3" }, "Light Grey"), option({ value: "#ff6347", class: "pixelia-swatch pixelia-swatch-ff6347" }, "Tomato") ), - button({ type: "submit" }, i18n.paintButton) + button({ type: "submit", class: "filter-btn" }, String(i18n.paintButton).toUpperCase()) ) ), - errorMessage ? div({ class: "error-message" }, errorMessage) : null, div({ class: "total-pixels" }, h2(`${i18n.totalPixels}: ${pixelArt.length}`) ) @@ -76,6 +75,7 @@ exports.pixeliaView = (pixelArt, errorMessage) => { section( div({ class: "main_content" }, div({ class: "pixelia-grid-wrap" }, grid), + pixelArt.length > 0 ? renderDocumentActions('pixelia', null) : null, pixelArt.length > 0 ? div({ class: "contributors" }, h2(i18n.contributorsTitle), diff --git a/src/views/pm_view.js b/src/views/pm_view.js index 69300ad..e31a20c 100644 --- a/src/views/pm_view.js +++ b/src/views/pm_view.js @@ -1,35 +1,25 @@ -const { div, h2, p, section, button, form, input, textarea, br, label, pre, span, strong, a } = require("../server/node_modules/hyperaxe"); +const { div, h2, p, section, button, form, input, textarea, br, label, pre, span, strong, a, select, option, datalist } = require("../server/node_modules/hyperaxe"); +const { TEXT_CAP } = require('../backend/long_text'); const { template, i18n } = require('./main_views'); const { getConfig } = require('../configs/config-manager.js'); -exports.pmView = async (initialRecipients = '', initialSubject = '', initialText = '', showPreview = false, sentKey = '', crypterError = false, crypterPreview = null, recipientError = false, fileError = '', fileSharePreview = null) => { +exports.pmView = async (initialRecipients = '', initialSubject = '', initialText = '', showPreview = false, sentKey = '', crypterError = false, crypterPreview = null, recipientError = false, fileError = '', fileSharePreview = null, opts = {}) => { + const mailingLists = Array.isArray(opts.lists) ? opts.lists : []; + const selectedList = String(opts.selectedList || ''); const title = i18n.pmSendTitle; const description = i18n.pmDescription; const textLen = (initialText || '').length; - const fileErrorText = { - recipient: i18n.pmInvalidRecipients || 'Invalid Oasis ID', - mutual: i18n.fileShareMutualError || 'You can only share files with habitants with mutual support.', - nofile: i18n.fileShareNoFile || 'No file selected.', - size: i18n.fileShareTooLarge || 'The file exceeds the allowed size.', - failed: i18n.fileShareFailed || 'Could not prepare the file.', - send: i18n.fileShareSendError || 'Could not send the file.' - }[fileError] || ''; const { renderEncryptedChip, renderDoubleEncryptionChip } = require('./clearnet_view'); return template( title, section( - div({ class: "tags-header" }, - div({ class: "title-with-chip" }, h2(title), renderEncryptedChip(i18n)), - p(description) + div({ class: "tags-header module-header-line" }, + h2(title), + p(description), + renderEncryptedChip(i18n) ), - crypterError - ? div({ class: "pm-form-error-msg" }, p('✗ ' + i18n.pmCrypterTooLong)) - : null, - recipientError - ? div({ class: "pm-form-error-msg" }, p('✗ ' + i18n.pmInvalidRecipients)) - : null, sentKey ? div({ class: "pm-sent-key" }, input({ type: "text", readonly: true, value: sentKey, class: "pm-sent-key-value" }) @@ -43,18 +33,38 @@ exports.pmView = async (initialRecipients = '', initialSubject = '', initialText type: "text", name: "recipients", placeholder: `${i18n.pmRecipientsHint}. ${i18n.pmLimitsHint}`, - required: true, + ...(mailingLists.length ? {} : { required: true }), value: initialRecipients, maxlength: "511" }), br(), + mailingLists.length + ? div({ class: "pm-list-row" }, + label({ for: "pm-list" }, i18n.pmListLabel), + br(), + mailingLists.length <= 12 + ? select({ name: "list", id: "pm-list" }, + option({ value: "" }, i18n.pmListNone), + mailingLists.map(l => + option({ value: l.target, ...(selectedList === l.target ? { selected: true } : {}) }, `${l.label} (${l.count})`) + ) + ) + : div( + input({ type: "text", name: "list", id: "pm-list", list: "pm-list-options", value: (mailingLists.find(l => l.target === selectedList) || {}).label || selectedList, placeholder: i18n.pmListNone }), + datalist({ id: "pm-list-options" }, + mailingLists.map(l => option({ value: l.label }, `(${l.count})`)) + ) + ), + br() + ) + : null, label({ for: "subject" }, i18n.pmSubject), br(), input({ type: "text", name: "subject", placeholder: i18n.pmSubjectHint, value: initialSubject, maxlength: "150" }), br(), label({ for: "text" }, i18n.pmText), br(), - textarea({ name: "text", rows: "6", cols: "50", id: "pm-text", maxlength: "7000", placeholder: i18n.pmTextPlaceholder || '' }, initialText), + textarea({ name: "text", rows: "6", cols: "50", id: "pm-text", maxlength: String(TEXT_CAP), placeholder: i18n.pmTextPlaceholder || '' }, initialText), div({ class: "pm-crypter-row" }, label({ for: "pm-crypter" }, input({ type: "checkbox", name: "crypter", value: "1", id: "pm-crypter", ...(crypterPreview ? { checked: true } : {}) }), @@ -83,6 +93,7 @@ exports.pmView = async (initialRecipients = '', initialSubject = '', initialText div({ class: "pm-preview-content" }, pre({ class: "pm-pre" }, crypterPreview.cipher || '')), form({ method: "POST", action: "/pm", class: "pm-crypter-send-form" }, input({ type: "hidden", name: "recipients", value: initialRecipients }), + input({ type: "hidden", name: "list", value: selectedList }), input({ type: "hidden", name: "subject", value: initialSubject }), input({ type: "hidden", name: "text", value: initialText }), input({ type: "hidden", name: "crypter", value: "1" }), @@ -102,6 +113,7 @@ exports.pmView = async (initialRecipients = '', initialSubject = '', initialText ), form({ method: "POST", action: "/pm", class: "pm-send-form" }, input({ type: "hidden", name: "recipients", value: initialRecipients }), + input({ type: "hidden", name: "list", value: selectedList }), input({ type: "hidden", name: "subject", value: initialSubject }), input({ type: "hidden", name: "text", value: initialText }), div({ class: "pm-actions" }, @@ -116,7 +128,6 @@ exports.pmView = async (initialRecipients = '', initialSubject = '', initialText section({ id: "fileshare" }, div({ class: "pm-form pm-fileshare-form-wrap" }, h2({ class: "pm-section-title" }, i18n.fileShareTitle || 'Share a file'), - fileErrorText ? div({ class: "pm-form-error-msg" }, p('✗ ' + fileErrorText)) : null, form({ method: "POST", action: "/pm/file/preview#fileshare", enctype: "multipart/form-data", class: "pm-fileshare-form" }, input({ id: "fs-recipient", type: "text", name: "recipient", placeholder: i18n.fileShareRecipientPlaceholder || 'Enter Oasis ID (@....ed25519)', required: true, value: initialRecipients, maxlength: "120" }), br(), diff --git a/src/views/podcasts_view.js b/src/views/podcasts_view.js new file mode 100644 index 0000000..8b686e2 --- /dev/null +++ b/src/views/podcasts_view.js @@ -0,0 +1,322 @@ +const { div, h2, h3, p, section, button, form, a, span, br, textarea, input, label, select, option, img, table, tr, td, audio: audioHyperaxe, video: videoHyperaxe } = require("../server/node_modules/hyperaxe"); +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderContentActions, renderSubscriptionBox, renderModuleStats, renderOpinionsVoting, renderEngagement, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); +const { renderCommentsSection } = require("./comments_view"); +const { renderStyledText } = require("../backend/renderStyledText"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); + + +const userId = config.keys.id; +const BASE_FILTERS = ["ALL", "RECENT", "MINE", "TOP", "VIEWERS"]; +const CATEGORIES = ["NEWS", "MUSIC", "TALK", "EDUCATION", "TECH", "CULTURE", "COMMUNITY", "OASIS", "OTHER"]; + +const cap = (s) => s.charAt(0) + s.slice(1).toLowerCase(); +const filterLabel = (f) => String(i18n[`podcastFilter${cap(f)}`] || f).toUpperCase(); +const catLabel = (c) => String(i18n[`podcastCategory${cap(c)}`] || c).toUpperCase(); +const channelHref = (ch) => `/podcasts/${encodeURIComponent(ch.id)}`; +const episodeHref = (ep) => `/podcasts/episode/${encodeURIComponent(ep.id)}`; +const fmt = (d) => moment(d).format("YYYY/MM/DD HH:mm"); +const blobSrc = (m) => `/blob/${encodeURIComponent(m.blobId)}`; + +const renderTags = (tags) => (Array.isArray(tags) && tags.length) + ? div({ class: "card-tags" }, ...tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) + : null; + +const renderCover = (ch, cls = "podcast-cover") => { + if (!ch.cover || !ch.cover.blobId) return null; + if (ch.cover.kind === "video") return videoHyperaxe({ class: cls, src: blobSrc(ch.cover), controls: true, preload: "metadata", muted: true }); + return img({ class: cls, src: blobSrc(ch.cover), alt: ch.title || "" }); +}; + +const countsLine = (ch) => div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.podcastPlaysLabel}: ${ch.playCount || 0}`) +); +const infoTable = (item) => table({ class: "tribe-info-table jobs-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), + td({ class: "tribe-info-value", colspan: "3" }, fmt(item.createdAt)) + ), + tr( + td({ class: "tribe-info-value", colspan: "4" }, userLink(item.author)) + ) +); + +const podcastChipFor = (mode, census) => { + if (mode === "ALL") return true; + if (!Array.isArray(census)) return true; + if (mode === "MINE") return census.some(c => String(c.author) === String(userId)); + if (mode === "RECENT") return census.length > 0; + if (mode === "TOP") return census.some(c => (c.opinionCount || 0) > 0 || (c.spreadCount || 0) > 0); + if (mode === "VIEWERS") return census.some(c => (c.playCount || 0) > 0); + if (CATEGORIES.includes(mode)) return census.some(c => c.category === mode); + return census.length > 0; +}; +exports.podcastChipFor = podcastChipFor; + +const renderChannelCard = (ch, params = {}) => + div({ class: "tribe-card podcast-card" }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(ch.id, channelHref(ch), { author: ch.author, favKind: "podcasts", isFavorite: ch.isFavorite, reportTitle: ch.title, spread: (params.spreadMap && params.spreadMap.get(ch.id)) || null, deleteAction: String(ch.author) === String(userId) ? contentDeleteAction("podcast", ch.id) : undefined }) + ), + div({ class: "tribe-card-body" }, + ch.cover && ch.cover.blobId ? a({ href: channelHref(ch) }, renderCover(ch, "podcast-card-cover")) : null, + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: channelHref(ch) }, ch.title || "—"))), + div({ class: "card-chips-row" }, renderStateChip("neutral", "", catLabel(ch.category)), ch.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('podcasts', ch.title, ch.id)) : null), + countsLine(ch) + ) + ); + +const chipButtons = (modes, filter, census, labelOf) => + modes.filter(m => m === filter || podcastChipFor(m, census)).map(m => button({ type: "submit", name: "filter", value: m, class: filter === m ? "filter-btn active" : "filter-btn" }, labelOf(m))); + +const renderFilters = (filter, q, census, emptyMod) => + section( + div({ class: "filters" }, + form({ method: "GET", action: "/podcasts", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q || "" }), + ...(emptyMod ? [] : [ + ...chipButtons(BASE_FILTERS, filter, census, filterLabel), + ...chipButtons(CATEGORIES, filter, census, catLabel) + ]), + button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.podcastCreate) + ) + ) + ); + +const renderChannelForm = (ch) => + div({ class: "div-center audio-form" }, + h2(ch ? i18n.podcastUpdateSectionTitle : i18n.podcastCreateSectionTitle), + form({ method: "POST", action: ch ? `/podcasts/update/${encodeURIComponent(ch.id)}` : "/podcasts/create", enctype: "multipart/form-data" }, + label(i18n.podcastTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.podcastTitlePlaceholder, value: ch ? ch.title : "" }), br(), + label(i18n.podcastDescriptionLabel), br(), + textarea({ name: "description", rows: 5, maxlength: "3000", placeholder: i18n.podcastDescriptionPlaceholder }, ch ? ch.description : ""), br(), + label(i18n.podcastCoverLabel), br(), + input({ type: "file", name: "cover", accept: "image/*,video/*" }), br(), br(), + label(i18n.podcastCategoryLabel), br(), + select({ name: "category" }, ...CATEGORIES.map(c => option({ value: c, ...((ch ? ch.category : "TALK") === c ? { selected: true } : {}) }, catLabel(c)))), br(), br(), + label(i18n.podcastTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.podcastTagsPlaceholder, value: ch ? ch.tags.join(", ") : "" }), br(), br(), + renderClearnetSelector(ch ? !!ch.clearnet : false, i18n), br(), + button({ type: "submit", class: "create-button" }, ch ? i18n.podcastUpdate : i18n.podcastCreate) + ) + ); + +const renderEpisodeForm = (ch, ep) => + div({ class: "div-center audio-form" }, + h2(ep ? i18n.podcastEpisodeUpdateTitle : i18n.podcastEpisodeCreateTitle), + form({ method: "POST", action: ep ? `/podcasts/episode/update/${encodeURIComponent(ep.id)}` : `/podcasts/${encodeURIComponent(ch.id)}/episodes`, enctype: "multipart/form-data" }, + label(i18n.podcastEpisodeTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "120", required: true, placeholder: i18n.podcastEpisodeTitlePlaceholder, value: ep ? ep.title : "" }), br(), + label(i18n.podcastEpisodeDescriptionLabel), br(), + textarea({ name: "description", rows: 6, maxlength: "5000", placeholder: i18n.podcastEpisodeDescriptionPlaceholder }, ep ? ep.description : ""), br(), + label(i18n.podcastEpisodeAudioLabel), br(), + input({ type: "file", name: "media", accept: "audio/*,video/*", ...(ep ? {} : { required: true }) }), br(), br(), + label(i18n.podcastTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.podcastTagsPlaceholder, value: ep ? ep.tags.join(", ") : "" }), br(), br(), + button({ type: "submit", class: "create-button" }, ep ? i18n.podcastUpdate : i18n.podcastEpisodePublish) + ) + ); + +exports.podcastsView = async (channels, filter = "ALL", params = {}) => { + const list = Array.isArray(channels) ? channels : []; + const q = String(params.q || "").trim(); + const census = Array.isArray(params.censusList) ? params.censusList : list; + const f = String(filter || "ALL").toUpperCase(); + const isForm = f === "CREATE" || f === "EDIT"; + const emptyMod = !isForm && moduleIsEmpty(census, "ALL", "ALL", q); + return template( + i18n.podcastsTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.podcastsTitle), p(i18n.podcastsDescription)), + renderFilters(isForm ? "ALL" : f, q, isForm ? [] : census, emptyMod) + ), + section( + isForm + ? renderChannelForm(f === "EDIT" ? params.channel : null) + : [ + emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length, [ + { label: i18n.podcastEpisodesLabel, count: list.reduce((s, c) => s + (c.episodeCount || 0), 0) }, + { label: i18n.podcastPlaysLabel, count: list.reduce((s, c) => s + (c.playCount || 0), 0) } + ]), + form({ method: "GET", action: "/podcasts", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: f }), + input({ type: "text", name: "q", value: q, placeholder: i18n.podcastSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ), + div({ class: "tribe-grid" }, list.length ? paged(list).map(c => renderChannelCard(c, params)) : p(i18n.podcastNoItems)) + ] + ) + ); +}; + +const episodeChips = (ep) => + div({ class: "card-chips-row" }, + renderStateChip("neutral", "", `${i18n.podcastPlaysLabel}: ${ep.playCount || 0}`), + ep.opinionCount > 0 ? renderStateChip("mutuals", "ꔍ", String(ep.opinionCount)) : null + ); + +const episodeActions = (ep, params = {}, returnTo) => + renderContentActions(ep.id, episodeHref(ep), { + author: ep.author, + favKind: "podcasts", + isFavorite: ep.isFavorite, + reportTitle: ep.title, + spread: (params.spreadMap && params.spreadMap.get(ep.id)) || null, + returnTo, + torrentFrom: { blobId: ep.media && ep.media.blobId, name: ep.title }, + deleteAction: String(ep.author) === String(userId) ? contentDeleteAction("podcastEpisode", ep.id) : undefined + }); + +const episodeRow = (ch, ep, params = {}, returnTo) => + div({ class: "podcast-episode-row" }, + span({ class: "podcast-episode-number" }, `#${ep.number}`), + div({ class: "podcast-episode-main" }, + div({ class: "emergency-update-head podcast-episode-head" }, + div({ class: "podcast-episode-head-left" }, + a({ href: episodeHref(ep), class: "podcast-episode-title" }, ep.title), + episodeChips(ep) + ), + episodeActions(ep, params, returnTo) + ), + ep.description ? p({ class: "tribe-side-description" }, ...renderStyledText(ep.description)) : null, + p({ class: "card-footer" }, span({ class: "date-link" }, fmt(ep.createdAt))) + ) + ); + +exports.singleChannelView = async (ch, params = {}) => { + const isAuthor = String(ch.author) === String(userId); + const href = channelHref(ch); + const census = Array.isArray(params.censusList) ? params.censusList : []; + const mode = String(params.mode || "").toUpperCase(); + const ownerActions = isAuthor + ? [ + form({ method: "GET", action: "/podcasts" }, input({ type: "hidden", name: "filter", value: "edit" }), input({ type: "hidden", name: "id", value: ch.id }), button({ type: "submit", class: "update-btn" }, i18n.podcastUpdate)) + ] + : []; + const allEpisodes = Array.isArray(ch.episodes) ? ch.episodes.slice().reverse() : []; + const episodeFilter = String(params.episodes || "").toUpperCase() === "VIEWERS" && allEpisodes.some(e => (e.playCount || 0) > 0) ? "VIEWERS" : "RECENT"; + const episodes = episodeFilter === "VIEWERS" ? allEpisodes.slice().sort((x, y) => (y.playCount || 0) - (x.playCount || 0) || y.publishedTs - x.publishedTs) : allEpisodes; + const episodeModes = ["RECENT", ...(allEpisodes.some(e => (e.playCount || 0) > 0) ? ["VIEWERS"] : [])]; + const episodeChipsRow = allEpisodes.length + ? div({ class: "activity-sub-filter" }, + ...episodeModes.map(m => form({ class: "sub-filter-form", method: "GET", action: `${href}#episodes` }, + input({ type: "hidden", name: "episodes", value: m }), + button({ type: "submit", class: episodeFilter === m ? "filter-btn active" : "filter-btn" }, filterLabel(m)) + )) + ) + : null; + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(ch.id, href, { author: ch.author, favKind: "podcasts", isFavorite: ch.isFavorite, reportTitle: ch.title, spread: params.spread || null, returnTo: href, deleteAction: isAuthor ? contentDeleteAction("podcast", ch.id) : undefined }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, ch.title), renderReachChip(!!ch.clearnet, i18n, ch.clearnet ? clearnetItemHref('podcasts', ch.title, ch.id) : null)), + isAuthor ? renderClearnetSwitch("podcasts", ch.rootId || ch.id, !!ch.clearnet) : null, + div({ class: "card-chips-row" }, renderStateChip("neutral", "", catLabel(ch.category))), + ch.cover && ch.cover.blobId ? a({ href: channelHref(ch), class: "podcast-cover-link" }, renderCover(ch, "podcast-cover podcast-cover-large")) : null, + ch.description ? p({ class: "tribe-side-description" }, ...renderStyledText(ch.description)) : null, + infoTable(ch), + countsLine(ch), + isAuthor + ? div({ class: "tribe-side-actions podcast-episode-add" }, + form({ method: "GET", action: href }, input({ type: "hidden", name: "mode", value: "episode" }), button({ type: "submit", class: "tribe-action-btn" }, String(i18n.podcastEpisodeAdd).toUpperCase())) + ) + : null, + renderTags(ch.tags), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null, + params.subscription + ? renderSubscriptionBox({ target: ch.id, scope: "podcasts", subscribed: params.subscription.subscribed, count: params.subscription.count, isOwner: isAuthor, returnTo: href, canWrite: isAuthor }) + : null + ); + const main = div({ class: "tribe-main" }, + isAuthor && mode === "EPISODE" ? renderEpisodeForm(ch, null) : null, + allEpisodes.length + ? div({ class: "podcast-featured" }, + div({ class: "emergency-update-head podcast-episode-head" }, + div({ class: "podcast-episode-head-left" }, + h2({ class: "tribe-card-title" }, a({ href: episodeHref(allEpisodes[0]) }, `#${allEpisodes[0].number} · ${allEpisodes[0].title}`)), + episodeChips(allEpisodes[0]) + ), + episodeActions(allEpisodes[0], params, href) + ), + renderPlayer(allEpisodes[0]), + allEpisodes[0].description ? p({ class: "tribe-side-description" }, ...renderStyledText(allEpisodes[0].description)) : null, + p({ class: "card-footer" }, span({ class: "date-link" }, fmt(allEpisodes[0].createdAt))) + ) + : null, + div({ class: "card-section podcast-episodes", id: "episodes" }, + h3(`${i18n.podcastEpisodesTitle} (${episodes.length})`), + episodeChipsRow, + episodes.length ? div({ class: "podcast-episode-list" }, ...episodes.map(ep => episodeRow(ch, ep, params, href))) : p(i18n.podcastNoEpisodes) + ), + renderEngagement(ch.id, + renderOpinionsVoting("/podcasts/opinions", ch.id, ch.opinions, href, ch.opinions_inhabitants), + renderCommentsSection({ action: `/podcasts/${encodeURIComponent(ch.id)}/comments`, comments: params.comments || [], returnTo: href }) + ) + ); + return template( + ch.title, + section(div({ class: "tags-header module-header-line" }, h2(i18n.podcastsTitle), p(i18n.podcastsDescription)), renderFilters("ALL", "", census, census.length === 0)), + section(div({ class: "tribe-details" }, side, main)) + ); +}; + +const renderPlayer = (ep) => { + if (!ep.media || !ep.media.blobId) return p(i18n.audioNoFile); + if (ep.media.kind === "video") return div({ class: "podcast-player video-container video-container-row" }, videoHyperaxe({ controls: true, src: blobSrc(ep.media), preload: "metadata", class: "podcast-video" })); + return div({ class: "podcast-player audio-container" }, audioHyperaxe({ controls: true, src: blobSrc(ep.media), preload: "metadata" })); +}; + +exports.singleEpisodeView = async (ep, params = {}) => { + const ch = ep.channel || { id: ep.channelId, title: "", author: ep.author, episodes: [] }; + const isAuthor = String(ep.author) === String(userId); + const href = episodeHref(ep); + const census = Array.isArray(params.censusList) ? params.censusList : []; + const played = Array.isArray(ep.listeners) && ep.listeners.includes(userId); + const mode = String(params.mode || "").toUpperCase(); + const headActions = [ + played + ? span({ class: "status supporting" }, `✓ ${i18n.podcastPlayed}`) + : form({ method: "POST", action: `/podcasts/episode/${encodeURIComponent(ep.id)}/play` }, button({ type: "submit", class: "tribe-action-btn" }, String(i18n.podcastMarkPlayed).toUpperCase())) + ].filter(Boolean); + const ownerActions = isAuthor + ? [ + form({ method: "GET", action: href }, input({ type: "hidden", name: "mode", value: "edit" }), button({ type: "submit", class: "update-btn" }, i18n.podcastUpdate)) + ] + : []; + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(ep.id, href, { author: ep.author, favKind: "podcasts", isFavorite: ep.isFavorite, reportTitle: ep.title, spread: params.spread || null, returnTo: href, torrentFrom: { blobId: ep.media && ep.media.blobId, name: ep.title }, deleteAction: isAuthor ? contentDeleteAction("podcastEpisode", ep.id) : undefined }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href: channelHref(ch), class: "user-link" }, ch.title || i18n.podcastsTitle)), renderReachChip(!!ch.clearnet, i18n, ch.clearnet ? clearnetItemHref('podcasts', ch.title, ch.id) : null)), + ch.cover && ch.cover.blobId ? a({ href: channelHref(ch), class: "podcast-cover-link" }, renderCover(ch, "podcast-cover podcast-cover-large")) : null, + ep.description ? p({ class: "tribe-side-description" }, ...renderStyledText(ep.description)) : null, + infoTable(ep), + renderTags(ep.tags), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null + ); + const main = div({ class: "tribe-main" }, + div({ class: "emergency-update-head podcast-episode-head" }, + div({ class: "podcast-episode-head-left" }, + h2({ class: "tribe-card-title" }, `#${ep.number} · ${ep.title}`), + episodeChips(ep) + ), + headActions.length ? div({ class: "tribe-side-actions emergency-update-actions" }, ...headActions) : null + ), + renderPlayer(ep), + renderEngagement(ep.id, + renderOpinionsVoting("/podcasts/opinions", ep.id, ep.opinions, href, ep.opinions_inhabitants), + renderCommentsSection({ action: `/podcasts/episode/${encodeURIComponent(ep.id)}/comments`, comments: params.comments || [], returnTo: href }) + ), + isAuthor && mode === "EDIT" ? renderEpisodeForm(ch, ep) : null + ); + return template( + ep.title, + section(div({ class: "tags-header module-header-line" }, h2(i18n.podcastsTitle), p(i18n.podcastsDescription)), renderFilters("ALL", "", census, census.length === 0)), + section(div({ class: "tribe-details" }, side, main)) + ); +}; diff --git a/src/views/polls_view.js b/src/views/polls_view.js index b2cf414..58d1a76 100644 --- a/src/views/polls_view.js +++ b/src/views/polls_view.js @@ -1,17 +1,16 @@ const { div, h2, p, section, button, form, a, input, label, span, textarea, br, table, tr, td } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, userLink, renderOpinionsVoting, renderEngagement, renderSpreadButton, renderContentActions, renderStateChip, renderLifespanChip, renderSpreadEditWarning } = require("./main_views"); +const { template, i18n, userLink, renderOpinionsVoting, renderEngagement, renderSpreadButton, renderContentActions, renderStateChip, renderLifespanChip, renderSpreadEditWarning, renderModuleStatsBy, moduleIsEmpty, contentDeleteAction, paged, renderCreatedRows } = require("./main_views"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); const { MAX_OPTIONS, MIN_OPTIONS, MAX_OPTION_LENGTH } = require("../models/polls_model_limits"); const userId = config.keys.id; const FILTERS = [ { key: "ALL", i18n: "pollFilterAll" }, - { key: "MINE", i18n: "pollFilterMine" }, { key: "RECENT", i18n: "pollFilterRecent" }, + { key: "MINE", i18n: "pollFilterMine" }, { key: "TOP", i18n: "pollFilterTop" }, { key: "VOTED", i18n: "pollFilterVoted" }, { key: "OPEN", i18n: "pollFilterOpen" }, @@ -68,7 +67,7 @@ const renderResults = (poll) => ) ); -const renderBallot = (poll, returnTo, basePath) => { +const renderBallot = (poll, returnTo, basePath, extra = null) => { if (poll.status === "CLOSED" || poll.hasVoted) return null; const inputType = poll.multiple ? "checkbox" : "radio"; return form({ method: "POST", action: `${basePath}/vote/${encodeURIComponent(poll.id)}`, class: "poll-ballot" }, @@ -85,7 +84,10 @@ const renderBallot = (poll, returnTo, basePath) => { ) ) ), - button({ type: "submit", class: "filter-btn" }, i18n.pollVoteButton) + div({ class: "poll-ballot-actions" }, + button({ type: "submit", class: "filter-btn" }, i18n.pollVoteButton), + ...(Array.isArray(extra) ? extra.filter(Boolean) : []) + ) ); }; @@ -103,7 +105,7 @@ const renderPollCard = (poll, filter, spreadInfo, basePath = "/polls") => { return div({ class: "tribe-card poll-card" + (isOwn ? " own-content" : "") }, div({ class: "card-header activity-card-header" }, span(), - renderContentActions(poll.id, href, { spread: spreadInfo || null, author: poll.author, favKind: 'polls', isFavorite: poll.isFavorite, reportTitle: poll.question }) + renderContentActions(poll.id, href, { spread: spreadInfo || null, author: poll.author, favKind: 'polls', isFavorite: poll.isFavorite, reportTitle: poll.question, deleteAction: isOwn ? contentDeleteAction('poll', poll.id) : undefined }) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, @@ -180,17 +182,30 @@ const renderComments = (poll, comments, basePath) => { }); }; -const renderFilterBar = (filter, q, showSearch = true) => - section( +const renderFilterBar = (filter, q, showSearch = true, items = null, censusList = null) => { + const emptyMod = items !== null && moduleIsEmpty(items, filter, "ALL", q); + const censusPl = Array.isArray(censusList) ? censusList : (Array.isArray(items) ? items : []); + const pollChip = (x) => { + const m = x.key; + if (m === filter) return true; + if (m === "TOP") return censusPl.length > 0; + if (m === "MINE") return censusPl.some(pl => String(pl.author || pl.createdBy) === String(userId)); + if (m === "RECENT") return censusPl.length > 0; + if (m === "VOTED") return censusPl.some(pl => pl.hasVoted || safeArr(pl.myChoices).length > 0); + if (m === "OPEN" || m === "CLOSED") return censusPl.some(pl => String(pl.status || "OPEN").toUpperCase() === m); + return true; + }; + return section( div({ class: "filters" }, form({ method: "GET", action: "/polls", class: "ui-toolbar ui-toolbar--filters" }, - ...FILTERS.map(f => + ...(emptyMod ? [] : FILTERS.filter(pollChip).map(f => button({ type: "submit", name: "filter", value: f.key, class: filter === f.key ? "filter-btn active" : "filter-btn" }, String(i18n[f.i18n]).toUpperCase()) - ), + )), button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.pollCreateButton) ) ), - showSearch ? div({ class: "filters" }, + showSearch && !emptyMod ? div({ class: "filters activity-filter-chips activity-toolbar-row" }, + items ? renderModuleStatsBy(items, pl => String(pl.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.pollStatusOpen }, { value: 'CLOSED', label: i18n.pollStatusClosed }]) : null, form({ method: "GET", action: "/polls", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: q || "", placeholder: i18n.pollSearchPlaceholder, class: "filter-box__input" }), @@ -200,6 +215,7 @@ const renderFilterBar = (filter, q, showSearch = true) => ) ) : null ); +}; exports.pollsView = async (polls = [], filter = "ALL", params = {}) => { const mode = String(filter).toUpperCase(); @@ -209,19 +225,19 @@ exports.pollsView = async (polls = [], filter = "ALL", params = {}) => { if (editing) params = { ...params, spreadWarning: await renderSpreadEditWarning(editing.id) }; return template( i18n.pollsTitle, - section(div({ class: "tags-header" }, h2(i18n.pollsTitle), p(i18n.pollsDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.pollsTitle), p(i18n.pollsDescription))), renderFilterBar(mode, params.q, false), renderCreateForm(editing, params) ); } return template( i18n.pollsTitle, - section(div({ class: "tags-header" }, h2(i18n.pollsTitle), p(i18n.pollsDescription))), - renderFilterBar(mode, params.q), + section(div({ class: "tags-header module-header-line" }, h2(i18n.pollsTitle), p(i18n.pollsDescription))), + renderFilterBar(mode, params.q, true, polls, params.censusList), section( polls.length - ? div({ class: "jobs-grid" }, ...polls.map(pl => renderPollCard(pl, mode, spreadMap.get(pl.id)))) - : p({ class: "no-content" }, i18n.pollsNoItems) + ? div({ class: "jobs-grid" }, ...paged(polls).map(pl => renderPollCard(pl, mode, spreadMap.get(pl.id)))) + : div({ class: "no-content-box" }, p({ class: "no-content" }, i18n.pollsNoItems)) ) ); }; @@ -246,28 +262,23 @@ exports.singlePollView = async (poll, comments = [], params = {}) => { a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`))) : null; - const ownerActions = isOwn + const ownerActions = isOwn && poll.totalVoters === 0 && poll.status === "OPEN" ? div({ class: "tribe-side-actions" }, - poll.totalVoters === 0 && poll.status === "OPEN" - ? form({ method: "GET", action: "/polls" }, - input({ type: "hidden", name: "filter", value: "EDIT" }), - input({ type: "hidden", name: "id", value: poll.id }), - button({ type: "submit", class: "update-btn" }, i18n.pollUpdateButton) - ) - : null, - form({ method: "POST", action: `/polls/delete/${encodeURIComponent(poll.id)}` }, - button({ type: "submit", class: "delete-btn" }, i18n.pollDeleteButton) + form({ method: "GET", action: "/polls" }, + input({ type: "hidden", name: "filter", value: "EDIT" }), + input({ type: "hidden", name: "id", value: poll.id }), + button({ type: "submit", class: "update-btn" }, i18n.pollUpdateButton) ) ) : null; const pollSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(poll.id, null, { spread: params.spreads || null, author: poll.author, favKind: 'polls', isFavorite: poll.isFavorite, returnTo: href, reportTitle: poll.question }) + renderContentActions(poll.id, href, { spread: params.spreads || null, author: poll.author, favKind: 'polls', isFavorite: poll.isFavorite, returnTo: href, reportTitle: poll.question, deleteAction: isOwn ? contentDeleteAction('poll', poll.id) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, poll.question)), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), + table({ class: "tribe-info-table jobs-info-table" }, ...infoRows, ...renderCreatedRows(poll.createdAt, poll.author)), tagsNode, div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.pollVoters}: ${poll.totalVoters}`) @@ -297,11 +308,6 @@ exports.singlePollView = async (poll, comments = [], params = {}) => { : (poll.voters.length ? div({ class: "card-assigned-list" }, ...poll.voters.map(v => userLink(v))) : null), - p({ class: "card-footer" }, - span({ class: "date-link" }, moment(poll.createdAt).format("YYYY/MM/DD HH:mm")), - span(" · "), - userLink(poll.author) - ), renderEngagement(poll.id, renderOpinionsVoting('/polls/opinions', poll.id, poll.opinions, href, poll.opinions_inhabitants), renderComments(poll, comments, basePath) @@ -310,7 +316,7 @@ exports.singlePollView = async (poll, comments = [], params = {}) => { return template( poll.question, - section(div({ class: "tags-header" }, h2(i18n.pollsTitle), p(i18n.pollsDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.pollsTitle), p(i18n.pollsDescription))), renderFilterBar(String(params.filter || "ALL").toUpperCase(), params.q, false), section(div({ class: "tribe-details" }, pollSide, pollMain)) ); diff --git a/src/views/projects_view.js b/src/views/projects_view.js index 5a46005..df75a33 100644 --- a/src/views/projects_view.js +++ b/src/views/projects_view.js @@ -1,22 +1,24 @@ const { form, button, div, h2, p, section, input, label, textarea, br, a, span, select, option, img, ul, li, table, thead, tbody, tr, th, td, progress, video, audio } = require("../server/node_modules/hyperaxe") +const { renderZoomableImage } = require("./gallery_view") const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning } = require("./main_views") +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderSubscriptionBox, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, contentDeleteAction, paged, renderCreatedRows } = require("./main_views") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const { renderMapLocationUrl, renderMapEmbed, renderMapLocationVisitLabel, renderMapEmbedWithZoom } = require("./maps_view") +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view") const renderMediaBlob = (value, attrs = {}) => { if (!value) return null const s = String(value).trim() if (!s) return null - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: attrs.class || 'post-image' }) + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }) return null } @@ -24,19 +26,20 @@ const userId = config.keys.id const FILTERS = [ { key: "ALL", i18n: "projectFilterAll", title: "projectAllTitle" }, + { key: "RECENT", i18n: "projectFilterRecent", title: "projectRecentTitle" }, { key: "MINE", i18n: "projectFilterMine", title: "projectMineTitle" }, { key: "APPLIED", i18n: "projectFilterApplied", title: "projectAppliedTitle" }, { key: "ACTIVE", i18n: "projectFilterActive", title: "projectActiveTitle" }, { key: "PAUSED", i18n: "projectFilterPaused", title: "projectPausedTitle" }, { key: "COMPLETED", i18n: "projectFilterCompleted", title: "projectCompletedTitle" }, { key: "FOLLOWING", i18n: "projectFilterFollowing", title: "projectFollowingTitle" }, - { key: "RECENT", i18n: "projectFilterRecent", title: "projectRecentTitle" }, { key: "TOP", i18n: "projectFilterTop", title: "projectTopTitle" }, { key: "BACKERS", i18n: "projectFilterBackers", title: "projectBackersLeaderboardTitle" } ] const safeArr = (v) => (Array.isArray(v) ? v : []) const safeText = (v) => String(v || "").trim() +const clearnetEligible = (pr) => String(pr.status || "").toUpperCase() !== "CANCELLED" const toNum = (v) => { const s = v === null || v === undefined ? "" : String(v) @@ -78,7 +81,7 @@ const renderCardFieldRich = (labelText, children) => const renderProgressBlock = (labelText, valueText, value, maxValue) => div( - { class: "confirmations-block progress-block" }, + { class: "confirmations-block progress-block project-progress-block" }, div( { class: "card-field" }, span({ class: "card-label" }, labelText), @@ -215,6 +218,7 @@ const renderPledgeBox = (project, filter, isAuthor) => { { method: "POST", action: `/projects/pledge/${encodeURIComponent(project.id)}` }, input({ type: "hidden", name: "returnTo", value: rt }), input({ type: "number", name: "amount", min: "0.01", step: "0.01", required: true, placeholder: i18n.projectPledgePlaceholder }), + renderEcoValueChip(), select( { name: "milestoneOrBounty" }, option({ value: "" }, i18n.projectSelectMilestoneOrBounty), @@ -260,7 +264,7 @@ const renderMilestonesAndBounties = (project, filter, editable) => { { class: "milestone-head" }, span({ class: "milestone-title" }, m.title), m.dueDate ? span({ class: "chip chip-due" }, `${i18n.projectMilestoneDue}: ${moment(m.dueDate).format("YYYY/MM/DD HH:mm")}`) : null, - safeText(m.description) ? p(...renderUrl(m.description)) : null, + safeText(m.description) ? p(...renderStyledText(m.description)) : null, editable && !m.done ? form( { method: "POST", action: `/projects/milestones/complete/${encodeURIComponent(project.id)}/${idx}` }, @@ -284,7 +288,7 @@ const renderMilestonesAndBounties = (project, filter, editable) => { span({ class: "bounty-title" }, b.title), span({ class: "bounty-amount" }, `${b.amount} ECO`) ), - safeText(b.description) ? p(...renderUrl(b.description)) : null, + safeText(b.description) ? p(...renderStyledText(b.description)) : null, renderCardField(i18n.projectBountyStatus + ":", statusText), b.claimedBy ? renderCardField(i18n.projectBountyClaimedBy + ":", userLink(b.claimedBy)) : null, !editable && !b.done && !b.claimedBy && project.author !== userId @@ -343,7 +347,7 @@ const renderMilestonesAndBounties = (project, filter, editable) => { span({ class: "bounty-title" }, b.title), span({ class: "bounty-amount" }, `${b.amount} ECO`) ), - safeText(b.description) ? p(...renderUrl(b.description)) : null, + safeText(b.description) ? p(...renderStyledText(b.description)) : null, renderCardField(i18n.projectBountyStatus + ":", statusText), b.claimedBy ? renderCardField(i18n.projectBountyClaimedBy + ":", userLink(b.claimedBy)) : null, !editable && !b.done && !b.claimedBy && project.author !== userId @@ -401,7 +405,11 @@ const renderProjectList = exports.renderProjectList = (projects, filter, spreadM : null const chips = [ renderProjectStatusChip(pr.status), - renderLifespanChip(pr.lifetime, i18n) + renderLifespanChip(pr.lifetime, i18n), + pr.clearnet === true && clearnetEligible(pr) ? renderReachChip(true, i18n, clearnetItemHref('projects', pr.title, pr.id || pr.key)) : null, + pr.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (pr.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null) ].filter(Boolean) const isOwn = pr.author && String(pr.author) === String(userId) @@ -409,7 +417,7 @@ const renderProjectList = exports.renderProjectList = (projects, filter, spreadM div( { class: "card-header activity-card-header" }, span(), - renderContentActions(pr.id || pr.key, `/projects/${encodeURIComponent(pr.id)}`, { spread: spreadMap.get(pr.id || pr.key) || null, author: pr.author, favKind: 'projects', isFavorite: pr.isFavorite, reportTitle: pr.title }) + renderContentActions(pr.id || pr.key, `/projects/${encodeURIComponent(pr.id)}`, { spread: spreadMap.get(pr.id || pr.key) || null, author: pr.author, favKind: 'projects', isFavorite: pr.isFavorite, reportTitle: pr.title, deleteAction: isOwn ? contentDeleteAction('project', pr.id || pr.key) : undefined }) ), div({ class: "card-section project-card-body" }, heroNode, @@ -420,7 +428,7 @@ const renderProjectList = exports.renderProjectList = (projects, filter, spreadM ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, goal > 0 - ? div({ class: "card-date-highlight" }, `${pr.goal} ECO`) + ? div({ class: "price-chip" }, `${pr.goal} ECO`) : null, goal > 0 ? renderProgressBlock(i18n.projectFunding + ":", `${fundingPct}%`, fundingPct, 100) @@ -458,17 +466,21 @@ const renderProjectForm = (project, mode, spreadWarning = null) => { br(), label(i18n.projectDescription), br(), - textarea({ name: "description", rows: "6", required: true, placeholder: i18n.projectDescriptionPlaceholder }, pr.description || ""), + textarea({ maxlength: "5000", name: "description", rows: "6", required: true, placeholder: i18n.projectDescriptionPlaceholder }, pr.description || ""), br(), label(i18n.projectImage), br(), input({ type: "file", name: "image" }), br(), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob" }), + br(), pr.image ? renderMediaBlob(pr.image) : null, br(), label(i18n.projectGoal), br(), input({ type: "number", step: "0.01", min: "0.01", name: "goal", required: true, placeholder: i18n.projectGoalPlaceholder, value: pr.goal || "" }), + renderEcoValueChip(), br(), br(), label(i18n.mapLocationTitle || "Map Location"), @@ -488,7 +500,7 @@ const renderProjectForm = (project, mode, spreadWarning = null) => { br(), label(i18n.projectMilestoneDescription), br(), - textarea({ name: "milestoneDescription", rows: "3", placeholder: i18n.projectMilestoneDescriptionPlaceholder }), + textarea({ maxlength: "5000", name: "milestoneDescription", rows: "3", placeholder: i18n.projectMilestoneDescriptionPlaceholder }), br(), label(i18n.projectMilestoneTargetPercent), br(), @@ -500,6 +512,7 @@ const renderProjectForm = (project, mode, spreadWarning = null) => { input({ type: "datetime-local", name: "milestoneDueDate", min: nowLocal, max: milestoneMax }), br(), br(), + !isEdit || clearnetEligible(pr) ? renderClearnetSelector(isEdit ? !!pr.clearnet : false, i18n) : null, button({ type: "submit" }, isEdit ? i18n.projectUpdateButton : i18n.projectCreateButton) ) ) @@ -513,29 +526,40 @@ exports.projectsView = async (projectsOrForm, filter, _unused, params = {}) => { const f = String(filter || "ALL").toUpperCase() const filterObj = FILTERS.find((x) => x.key === f) || FILTERS[0] const sectionTitle = i18n[filterObj.title] || i18n.projectAllTitle - const { renderReachChip: renderReachChipProjects } = require('./clearnet_view'); - const viewerClearnetProjects = !!(params.viewerPrefs && params.viewerPrefs.clearnetProjects); + const emptyMod = moduleIsEmpty(Array.isArray(projectsOrForm) ? projectsOrForm : [], f, "ALL", params.q); + const censusP = Array.isArray(params.censusList) ? params.censusList : (Array.isArray(projectsOrForm) ? projectsOrForm : []); + const projChip = (x) => { + const m = x.key; + if (m === f) return true; + if (m === "TOP") return censusP.length > 0; + if (m === "MINE") return censusP.some(pr => String(pr.author) === String(userId)); + if (m === "APPLIED") return censusP.some(pr => String(pr.author) !== String(userId) && [pr.members, pr.participants, pr.applicants].some(l => Array.isArray(l) && l.includes(userId))); + if (m === "ACTIVE" || m === "PAUSED" || m === "COMPLETED") return censusP.some(pr => String(pr.status || "ACTIVE").toUpperCase() === m); + if (m === "FOLLOWING") return censusP.some(pr => Array.isArray(pr.followers) && pr.followers.includes(userId)); + if (m === "RECENT") return censusP.length > 0; + if (m === "BACKERS") return censusP.some(pr => safeArr(pr.backers).length > 0); + return true; + }; return template( i18n.projectsTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(sectionTitle), p(i18n.projectsDescription) ), - div({ class: "shop-title-row" }, renderReachChipProjects(viewerClearnetProjects, i18n)), - br(), div( { class: "filters" }, form( { method: "GET", action: "/projects", class: "ui-toolbar ui-toolbar--filters" }, - FILTERS.map((x) => button({ type: "submit", name: "filter", value: x.key, class: f === x.key ? "filter-btn active" : "filter-btn" }, String(i18n[x.i18n]).toUpperCase())) + (emptyMod ? [] : FILTERS.filter(projChip).map((x) => button({ type: "submit", name: "filter", value: x.key, class: f === x.key ? "filter-btn active" : "filter-btn" }, String(i18n[x.i18n]).toUpperCase()))) .concat(button({ type: "submit", name: "filter", value: "CREATE", class: "create-button" }, i18n.projectCreateProject)) ) ), f === "CREATE" || f === "EDIT" ? null - : div({ class: "filters" }, + : emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(projectsOrForm, pr => String(pr.status || 'ACTIVE').toUpperCase(), [{ value: 'ACTIVE', label: i18n.projectStatusACTIVE }, { value: 'PAUSED', label: i18n.projectStatusPAUSED }, { value: 'COMPLETED', label: i18n.projectStatusCOMPLETED }, { value: 'CANCELLED', label: i18n.projectStatusCANCELLED }]), form({ method: "GET", action: "/projects", class: "filter-box" }, input({ type: "hidden", name: "filter", value: f }), input({ type: "text", name: "q", value: safeText(params.q), placeholder: i18n.projectSearchPlaceholder, class: "filter-box__input" }), @@ -551,7 +575,7 @@ exports.projectsView = async (projectsOrForm, filter, _unused, params = {}) => { })() : (f === "BACKERS" ? renderBackersLeaderboard(projectsOrForm) - : div({ class: "projects-list" }, renderProjectList(projectsOrForm, f, params.spreadMap)) + : div({ class: "projects-list" }, renderProjectList(paged(projectsOrForm), f, params.spreadMap)) ) ) ) @@ -564,6 +588,8 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { const isFollower = safeArr(pr.followers).includes(userId) const statusUpper = String(pr.status || "ACTIVE").toUpperCase() + const canClearnet = clearnetEligible(pr) + const isClearnet = canClearnet && !!pr.clearnet const pctRaw = toNum(pr.progress) const pct = clamp(Math.round(Number.isFinite(pctRaw) ? pctRaw : 0), 0, 100) const goal = Math.max(0, toNum(pr.goal) || 0) @@ -576,7 +602,14 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { renderProjectStatusChip(pr.status), isFollower ? renderStateChip("whole", "★", i18n.projectFollowing || "FOLLOWING") : null, renderLifespanChip(pr.lifetime, i18n), - renderEcoTax(pr.msgSize, pr.id || pr.key) + renderEcoTax(pr.msgSize, pr.id || pr.key), + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('projects', pr.title, pr.id || pr.key) : null), + isAuthor && canClearnet ? renderClearnetSwitch('projects', pr.rootId || pr.id || pr.key, isClearnet) : null, + pr.subscription + ? ((isAuthor || pr.subscription.subscribed === true) + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : renderStateChip("closed", "✉", i18n.subscriptionOff)) + : null ].filter(Boolean) const sideActions = [] @@ -596,10 +629,6 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { sideActions.push(form({ method: "GET", action: `/projects/edit/${encodeURIComponent(pr.id)}` }, button({ class: "tribe-action-btn", type: "submit" }, i18n.projectUpdateButton) )) - sideActions.push(form({ method: "POST", action: `/projects/delete/${encodeURIComponent(pr.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.projectDeleteButton) - )) sideActions.push(form( { method: "POST", action: `/projects/status/${encodeURIComponent(pr.id)}`, class: "project-control-form project-control-form--status" }, input({ type: "hidden", name: "returnTo", value: returnTo }), @@ -622,18 +651,30 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { const projectSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(pr.id || pr.key, null, { spread: params.spreads || null, author: pr.author, favKind: 'projects', isFavorite: pr.isFavorite, reportTitle: pr.title }) + renderContentActions(pr.id || pr.key, `/projects/${encodeURIComponent(pr.id)}`, { spread: params.spreads || null, author: pr.author, favKind: 'projects', isFavorite: pr.isFavorite, reportTitle: pr.title, returnTo: `/projects/${encodeURIComponent(pr.id)}`, deleteAction: isAuthor ? contentDeleteAction('project', pr.id || pr.key) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, pr.title) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, pr.image ? renderMediaBlob(pr.image, { class: "tribe-detail-image" }) : null, - div({ class: "job-price-line card-salary" }, `${pr.goal || 0} ECO`), + div({ class: "price-chip" }, `${pr.goal || 0} ECO`), div({ class: "job-price-line card-salary" }, `${i18n.projectFollowers}: ${followersCount(pr)}`), renderProgressBlock(i18n.projectProgress + ":", `${pct}%`, pct, 100), goal > 0 ? renderProgressBlock(i18n.projectFunding + ":", `${fundingPct}%`, fundingPct, 100) : null, - sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null + table({ class: "tribe-info-table" }, ...renderCreatedRows(pr.createdAt, pr.author)), + sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, + pr.subscription + ? renderSubscriptionBox({ + target: pr.rootId || pr.id, + scope: "projects", + subscribed: pr.subscription.subscribed === true, + count: pr.subscription.count, + isOwner: isAuthor, + canWrite: isAuthor, + returnTo + }) + : null ) const projectMain = div({ class: "tribe-main" }, @@ -641,19 +682,15 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { safeText(pr.description) ? div({ class: "job-section" }, h2({ class: "job-section-title" }, i18n.projectDescription), - p({ class: "tribe-side-description" }, ...renderUrl(pr.description)) + p({ class: "tribe-side-description" }, ...renderStyledText(pr.description)) ) : null, pr.mapUrl ? div({ class: "job-section" }, renderMapEmbedWithZoom(params.mapData, pr.mapUrl, `/projects/${encodeURIComponent(pr.id || pr.key)}`, params.zoom)) : null, renderBudget(pr), renderBackers(pr, f), renderMilestonesAndBounties(pr, f, isAuthor), - renderFollowers(pr), renderPledgeBox(pr, f, isAuthor), - div({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(pr.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(pr.author) - ), + renderFollowers(pr), renderEngagement(pr.id || pr.key, renderOpinionsVoting('/projects/opinions', pr.id || pr.key, pr.opinions, null, pr.opinions_inhabitants), renderSharedCommentsSection({ @@ -667,7 +704,7 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { return template( i18n.projectsTitle, section( - div({ class: "tags-header" }, h2(i18n.projectsTitle), p(i18n.projectsDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.projectsTitle), p(i18n.projectsDescription)), div( { class: "filters" }, form( @@ -682,10 +719,10 @@ exports.singleProjectView = async (project, filter, comments, params = {}) => { } exports.clearnetProjectView = async (project) => { - const { escapeHtml: esc, blobUrl: cnBlob, renderClearnetPage } = require('./clearnet_view'); + const { escapeHtml: esc, renderRichText, renderKindTag, blobUrl: cnBlob, renderClearnetPage } = require('./clearnet_view'); const pr = project || {}; - const title = esc(pr.title || 'Project'); - const desc = esc(pr.description || ''); + const title = esc(pr.title || i18n.cnKindProject); + const desc = renderRichText(pr.description || ''); const goal = Math.max(0, toNum(pr.goal) || 0); const pledged = Math.max(0, toNum(pr.pledged) || 0); const fundingPct = goal > 0 ? Math.min(100, Math.round((pledged / goal) * 100)) : 0; @@ -694,11 +731,11 @@ exports.clearnetProjectView = async (project) => { const deadline = pr.deadline ? new Date(pr.deadline).toISOString().slice(0, 10) : ''; const milestones = Array.isArray(pr.milestones) ? pr.milestones.slice(0, 10) : []; const milestonesBlock = milestones.length - ? `

Milestones

    ${milestones.map(m => `
  1. ${esc(m.title || '')}${m.targetPercent ? ` — ${m.targetPercent}%` : ''}
  2. `).join('')}
` + ? `

${esc(i18n.projectMilestones)}

    ${milestones.map(m => `
  1. ${esc(m.title || '')}${m.targetPercent ? ` — ${m.targetPercent}%` : ''}
  2. `).join('')}
` : ''; const extraCss = ` .cn-prj-title{color:var(--fg);margin:0 0 12px 0;font-size:32px;font-weight:700} -.cn-prj-status{display:inline-block;background:var(--bg-sub);border:1px solid var(--fg);color:var(--fg);padding:6px 12px;border-radius:6px;font-weight:600;text-transform:uppercase;letter-spacing:1px;font-size:12px;margin-bottom:16px} +.cn-prj-status{display:inline-flex;align-items:center;background:var(--bg-sub);border:1px solid var(--fg);color:var(--fg);padding:6px 12px;border-radius:6px;font-weight:600;text-transform:uppercase;letter-spacing:1px;font-size:12px;margin:0} .cn-prj-img{display:block;max-width:100%;border:1px solid var(--border);border-radius:8px;margin-bottom:20px} .cn-prj-funding{background:var(--bg-sub);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:20px} .cn-prj-funding-label{color:var(--fg-dim);font-size:12px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px} @@ -719,22 +756,24 @@ ${Array.from({ length: 21 }, (_, i) => `.cn-prj-bar-fill-${i * 5}{width:${i * 5} const body = `

${title}

- ${esc(status)} + ${renderKindTag('project')} + ${esc(i18n['projectStatus' + status] || status)} ${pr.createdAt ? `📅 ${esc(new Date(pr.createdAt).toISOString().slice(0,10))}` : ''}
+
${projectImg ? `${title}` : ''} ${goal > 0 ? `
-
Funding
+
${esc(i18n.projectFunding)}
${pledged.toFixed(2)} / ${goal.toFixed(2)} ECO · ${fundingPct}%
- ${deadline ? `
Deadline: ${deadline}
` : ''} + ${deadline ? `
${esc(i18n.deadline)}: ${deadline}
` : ''}
` : ''} - ${desc ? `

Description

${desc}

` : ''} + ${desc ? `

${esc(i18n.description)}

${desc}

` : ''} ${milestonesBlock} `; return renderClearnetPage({ - title: `${pr.title || 'Project'} — Oasis`, + title: `${pr.title || 'Project'} | Oasis`, ogTitle: pr.title || 'Project', ogDescription: pr.description || '', ogImage: projectImg, diff --git a/src/views/report_view.js b/src/views/report_view.js index bdabbed..3dc904f 100644 --- a/src/views/report_view.js +++ b/src/views/report_view.js @@ -1,22 +1,22 @@ -const { div, h2, p, section, button, form, a, textarea, br, input, img, span, label, select, option, video, audio, table, tr, td } = require("../server/node_modules/hyperaxe"); +const { hr, div, h2, p, section, button, form, a, textarea, br, input, img, span, label, select, option, video, audio, table, tr, td } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderSpreadEditWarning, renderContentActions, renderDocumentActions } = require("./main_views"); -const { renderPhotoGallery, renderGalleryFields } = require("./gallery_view"); +const { template, i18n, renderOpinionsVoting, renderEngagement, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderSpreadEditWarning, renderContentActions, renderDocumentActions, renderModuleStatsBy, moduleIsEmpty, contentDeleteAction, paged, renderCreatedRows } = require("./main_views"); +const { renderPhotoGallery, renderGalleryFields, renderZoomableImage } = require("./gallery_view"); +const { renderTribeOriginChip } = require("./clearnet_view"); const { config } = require("../server/SSB_server.js"); -const moment = require("../server/node_modules/moment"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const renderMediaBlob = (value, attrs = {}) => { if (!value) return null; const s = String(value).trim(); if (!s) return null; - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }); + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }); const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }); + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }); const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }); const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: attrs.class || 'post-image' }); + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }); return null; }; @@ -40,6 +40,13 @@ const STATUS_BY_FILTER = { closed: "CLOSED" }; +const SEVERITY_BY_FILTER = { + sev_low: "LOW", + sev_medium: "MEDIUM", + sev_high: "HIGH", + sev_critical: "CRITICAL" +}; + const opt = (value, isSelected, text) => option(Object.assign({ value }, isSelected ? { ...("selected" ? { selected: true } : {})} : {}), text); @@ -52,7 +59,7 @@ const renderCardField = (labelText, value = "") => div( { class: "card-field" }, span({ class: "card-label" }, labelText), - span({ class: "card-value" }, ...renderUrl(String(value ?? ""))) + span({ class: "card-value" }, ...renderStyledText(String(value ?? ""))) ); const renderStackedTextField = (lbl, val) => @@ -60,7 +67,7 @@ const renderStackedTextField = (lbl, val) => ? div( { class: "card-field card-field-stacked" }, span({ class: "card-label" }, lbl), - span({ class: "card-value" }, ...renderUrl(String(val))) + span({ class: "card-value" }, ...renderStyledText(String(val))) ) : null; @@ -155,23 +162,19 @@ const renderTemplateForCategory = (category, templateData = {}) => { h2({ class: "report-template-title" }, i18n.reportsBugTemplateTitle), label(i18n.reportsStepsToReproduceLabel), br(), - textarea({ name: "stepsToReproduce", rows: "4", placeholder: i18n.reportsStepsToReproducePlaceholder }, tval("stepsToReproduce")), - br(), + textarea({ maxlength: "5000", name: "stepsToReproduce", rows: "4", placeholder: i18n.reportsStepsToReproducePlaceholder }, tval("stepsToReproduce")), br(), label(i18n.reportsExpectedBehaviorLabel), br(), - textarea({ name: "expectedBehavior", rows: "3", placeholder: i18n.reportsExpectedBehaviorPlaceholder }, tval("expectedBehavior")), - br(), + textarea({ maxlength: "5000", name: "expectedBehavior", rows: "3", placeholder: i18n.reportsExpectedBehaviorPlaceholder }, tval("expectedBehavior")), br(), label(i18n.reportsActualBehaviorLabel), br(), - textarea({ name: "actualBehavior", rows: "3", placeholder: i18n.reportsActualBehaviorPlaceholder }, tval("actualBehavior")), - br(), + textarea({ maxlength: "5000", name: "actualBehavior", rows: "3", placeholder: i18n.reportsActualBehaviorPlaceholder }, tval("actualBehavior")), br(), label(i18n.reportsEnvironmentLabel), br(), - textarea({ name: "environment", rows: "3", placeholder: i18n.reportsEnvironmentPlaceholder }, tval("environment")), - br(), + textarea({ maxlength: "5000", name: "environment", rows: "3", placeholder: i18n.reportsEnvironmentPlaceholder }, tval("environment")), br(), label(i18n.reportsReproduceRateLabel), br(), @@ -193,17 +196,15 @@ const renderTemplateForCategory = (category, templateData = {}) => { h2({ class: "report-template-title" }, i18n.reportsContentTemplateTitle), label(i18n.reportsContentLocationLabel), br(), - textarea({ name: "contentLocation", rows: "3", placeholder: i18n.reportsContentLocationPlaceholder }, tval("contentLocation")), - br(), + textarea({ maxlength: "5000", name: "contentLocation", rows: "3", placeholder: i18n.reportsContentLocationPlaceholder }, tval("contentLocation")), br(), label(i18n.reportsWhyInappropriateLabel), br(), - textarea({ name: "whyInappropriate", rows: "4", placeholder: i18n.reportsWhyInappropriatePlaceholder }, tval("whyInappropriate")), - br(), + textarea({ maxlength: "5000", name: "whyInappropriate", rows: "4", placeholder: i18n.reportsWhyInappropriatePlaceholder }, tval("whyInappropriate")), br(), label(i18n.reportsEvidenceLinksLabel), br(), - textarea({ name: "evidenceLinks", rows: "3", placeholder: i18n.reportsEvidenceLinksPlaceholder }, tval("evidenceLinks")) + textarea({ maxlength: "5000", name: "evidenceLinks", rows: "3", placeholder: i18n.reportsEvidenceLinksPlaceholder }, tval("evidenceLinks")) ); } @@ -212,17 +213,15 @@ const renderTemplateForCategory = (category, templateData = {}) => { h2({ class: "report-template-title" }, i18n.reportsFeatureTemplateTitle), label(i18n.reportsProblemStatementLabel), br(), - textarea({ name: "problemStatement", rows: "4", placeholder: i18n.reportsProblemStatementPlaceholder }, tval("problemStatement")), - br(), + textarea({ maxlength: "5000", name: "problemStatement", rows: "4", placeholder: i18n.reportsProblemStatementPlaceholder }, tval("problemStatement")), br(), label(i18n.reportsUserStoryLabel), br(), - textarea({ name: "userStory", rows: "3", placeholder: i18n.reportsUserStoryPlaceholder }, tval("userStory")), - br(), + textarea({ maxlength: "5000", name: "userStory", rows: "3", placeholder: i18n.reportsUserStoryPlaceholder }, tval("userStory")), br(), label(i18n.reportsAcceptanceCriteriaLabel), br(), - textarea({ name: "acceptanceCriteria", rows: "4", placeholder: i18n.reportsAcceptanceCriteriaPlaceholder }, tval("acceptanceCriteria")) + textarea({ maxlength: "5000", name: "acceptanceCriteria", rows: "4", placeholder: i18n.reportsAcceptanceCriteriaPlaceholder }, tval("acceptanceCriteria")) ); }; @@ -273,51 +272,83 @@ const renderReportCategoryChip = (category) => { const renderReportCard = (report, userId, currentFilter = "all", spreadInfo) => { const confirmations = Array.isArray(report.confirmations) ? report.confirmations : []; + const origin = report.tribeOrigin || null; const chips = [ renderReportStatusChip(report.status), renderReportSeverityChip(report.severity), - renderReportCategoryChip(report.category), - renderLifespanChip(report.lifetime, i18n) + origin && !report.category ? null : renderReportCategoryChip(report.category), + renderLifespanChip(report.lifetime, i18n), + origin ? renderTribeOriginChip(origin) : null ].filter(Boolean); return div({ class: "tribe-card report-card" }, - div({ class: "card-header activity-card-header" }, + origin ? null : div({ class: "card-header activity-card-header" }, span(), - renderContentActions(report.id, `/reports/${encodeURIComponent(report.id)}`, { spread: spreadInfo || null, author: report.author, favKind: 'reports', isFavorite: report.isFavorite, reportTitle: report.title, report: false }) + renderContentActions(report.id, `/reports/${encodeURIComponent(report.id)}`, { spread: spreadInfo || null, author: report.author, favKind: 'reports', isFavorite: report.isFavorite, reportTitle: report.title, report: false, deleteAction: String(report.author) === String(userId) ? contentDeleteAction('report', report.id) : undefined }) ), div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, - a({ href: `/reports/${encodeURIComponent(report.id)}` }, report.title || i18n.reportsTitle) + a({ href: origin ? origin.href : `/reports/${encodeURIComponent(report.id)}` }, report.title || i18n.reportsTitle) ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - div({ class: "tribe-card-members" }, + origin ? null : div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.reportsConfirmations}: ${confirmations.length}`) ) ) ); }; +const reportChipFor = (filter, censusReports) => (mode) => { + if (mode === filter || mode === "all") return true; + if (!Array.isArray(censusReports)) return true; + if (mode === "top") return censusReports.length > 0; + if (mode === "mine") return censusReports.some((r) => r.author === userId); + if (mode === "recent") return censusReports.length > 0; + if (mode === "confirmed") return censusReports.some((r) => Array.isArray(r.confirmations) && r.confirmations.includes(userId)); + if (CATEGORY_BY_FILTER[mode]) return censusReports.some((r) => normU(r.category) === CATEGORY_BY_FILTER[mode]); + if (STATUS_BY_FILTER[mode]) return censusReports.some((r) => normalizeStatus(r.status) === STATUS_BY_FILTER[mode]); + if (SEVERITY_BY_FILTER[mode]) return censusReports.some((r) => normU(r.severity) === SEVERITY_BY_FILTER[mode]); + return true; +}; + exports.reportView = async (reports, filter, reportId, createCategory, params = {}) => { const title = i18n.reportsTitle; let filtered = Array.isArray(reports) ? reports : []; + const dayAgoReports = Date.now() - 86400000; + const confirmationsOf = (r) => (Array.isArray(r.confirmations) ? r.confirmations.length : 0); if (filter === "mine") { filtered = filtered.filter((r) => r.author === userId); + } else if (filter === "recent") { + filtered = filtered.filter((r) => new Date(r.createdAt).getTime() >= dayAgoReports); } else if (filter === "confirmed") { filtered = filtered.filter((r) => Array.isArray(r.confirmations) && r.confirmations.includes(userId)); + } else if (filter === "top") { + filtered = filtered.filter((r) => !r.tribeOrigin); } else if (CATEGORY_BY_FILTER[filter]) { const wanted = CATEGORY_BY_FILTER[filter]; filtered = filtered.filter((r) => normU(r.category) === wanted); } else if (STATUS_BY_FILTER[filter]) { const wanted = STATUS_BY_FILTER[filter]; filtered = filtered.filter((r) => normalizeStatus(r.status) === wanted); + } else if (SEVERITY_BY_FILTER[filter]) { + const wanted = SEVERITY_BY_FILTER[filter]; + filtered = filtered.filter((r) => normU(r.severity) === wanted); } - filtered = filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + filtered = filter === "top" + ? filtered.sort((a, b) => confirmationsOf(b) - confirmationsOf(a) || new Date(b.createdAt) - new Date(a.createdAt)) + : filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + const emptyMod = moduleIsEmpty(filtered, filter, "all", params.q); + const censusReports = Array.isArray(reports) ? reports : []; + const reportChip = reportChipFor(filter, censusReports); + const chipBtn = (mode, label) => reportChip(mode) + ? [button({ type: "submit", name: "filter", value: mode, class: btnClass(mode) }, String(label).toUpperCase())] + : []; const reportToEdit = filter === "edit" ? (Array.isArray(reports) ? reports.find((r) => r.id === reportId) : null) @@ -342,39 +373,42 @@ exports.reportView = async (reports, filter, reportId, createCategory, params = title, section( div( - { class: "tags-header" }, + { class: "tags-header module-header-line" }, h2(i18n.reportsTitle), p(i18n.reportsDescription) ), div( { class: "filters" }, - form( + ...(emptyMod ? [] : [form( { method: "GET", action: "/reports", class: "ui-toolbar ui-toolbar--filters" }, - button({ type: "submit", name: "filter", value: "all", class: btnClass("all") }, String(i18n.reportsFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: btnClass("mine") }, String(i18n.reportsFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: btnClass("recent") }, String(i18n.reportsFilterRecent).toUpperCase()), - button({ type: "submit", name: "filter", value: "top", class: btnClass("top") }, String(i18n.reportsFilterTop).toUpperCase()), - button({ type: "submit", name: "filter", value: "features", class: btnClass("features") }, String(i18n.reportsFilterFeatures).toUpperCase()), - button({ type: "submit", name: "filter", value: "bugs", class: btnClass("bugs") }, String(i18n.reportsFilterBugs).toUpperCase()), - button({ type: "submit", name: "filter", value: "content", class: btnClass("content") }, String(i18n.reportsFilterContent).toUpperCase()), - button({ type: "submit", name: "filter", value: "confirmed", class: btnClass("confirmed") }, String(i18n.reportsFilterConfirmed).toUpperCase()), - button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.reportsCreateButton) - ), + ...chipBtn("all", i18n.reportsFilterAll), + ...chipBtn("recent", i18n.reportsFilterRecent), + ...chipBtn("mine", i18n.reportsFilterMine), + ...chipBtn("top", i18n.reportsFilterTop), + ...chipBtn("features", i18n.reportsFilterFeatures), + ...chipBtn("bugs", i18n.reportsFilterBugs), + ...chipBtn("content", i18n.reportsFilterContent), + ...chipBtn("confirmed", i18n.reportsFilterConfirmed), + )]), form( { method: "GET", action: "/reports", class: "ui-toolbar ui-toolbar--filters reports-subfilters" }, - button({ type: "submit", name: "filter", value: "open", class: btnClass("open") }, String(i18n.reportsFilterOpen).toUpperCase()), - button({ type: "submit", name: "filter", value: "under_review", class: btnClass("under_review") }, String(i18n.reportsFilterUnderReview).toUpperCase()), - button({ type: "submit", name: "filter", value: "resolved", class: btnClass("resolved") }, String(i18n.reportsFilterResolved).toUpperCase()), - button({ type: "submit", name: "filter", value: "invalid", class: btnClass("invalid") }, String(i18n.reportsFilterInvalid).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_low", class: btnClass("sev_low") }, String(i18n.reportsSeverityLow).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_medium", class: btnClass("sev_medium") }, String(i18n.reportsSeverityMedium).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_high", class: btnClass("sev_high") }, String(i18n.reportsSeverityHigh).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_critical", class: btnClass("sev_critical") }, String(i18n.reportsSeverityCritical).toUpperCase()) + ...(emptyMod ? [] : [ + ...chipBtn("open", i18n.reportsFilterOpen), + ...chipBtn("under_review", i18n.reportsFilterUnderReview), + ...chipBtn("resolved", i18n.reportsFilterResolved), + ...chipBtn("invalid", i18n.reportsFilterInvalid), + ...chipBtn("sev_low", i18n.reportsSeverityLow), + ...chipBtn("sev_medium", i18n.reportsSeverityMedium), + ...chipBtn("sev_high", i18n.reportsSeverityHigh), + ...chipBtn("sev_critical", i18n.reportsSeverityCritical), + ]), + button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.reportsCreateButton) ) ), filter === "edit" || filter === "create" ? null - : div({ class: "filters" }, + : emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filtered, r => normalizeStatus(r.status || 'OPEN'), [{ value: 'OPEN', label: i18n.reportsStatusOpen }, { value: 'UNDER_REVIEW', label: i18n.reportsStatusUnderReview }, { value: 'RESOLVED', label: i18n.reportsStatusResolved }, { value: 'CLOSED', label: i18n.reportsStatusClosed }, { value: 'INVALID', label: i18n.reportsStatusInvalid }]), form({ method: "GET", action: "/reports", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: params.q || "", placeholder: i18n.reportsSearchPlaceholder, class: "filter-box__input" }), @@ -390,42 +424,38 @@ exports.reportView = async (reports, filter, reportId, createCategory, params = { class: "report-form" }, filter === "edit" ? await renderSpreadEditWarning(reportToEdit && (reportToEdit.id || reportToEdit.key)) : null, filter === "create" - ? div( - label(i18n.reportsTitleLabel), - br(), - input({ type: "text", name: "title", maxlength: "100", required: true, value: params.prefillTitle || "", form: "report-create-form" }), - br(), - br(), + ? [ form( { id: "report-category-form", method: "GET", action: "/reports" }, input({ type: "hidden", name: "filter", value: "create" }), - label(i18n.reportsCategory), - br(), - select( - { name: "category", class: "report-category-select" }, - opt("FEATURES", selectedCategory === "FEATURES", i18n.reportsCategoryFeatures), - opt("BUGS", selectedCategory === "BUGS", i18n.reportsCategoryBugs), - opt("CONTENT", selectedCategory === "CONTENT", i18n.reportsCategoryContent) - ), - br(), - br(), - button({ type: "submit", class: "create-button" }, applyLabel) + div({ class: "apply-row" }, + select( + { name: "category", class: "report-category-select" }, + opt("FEATURES", selectedCategory === "FEATURES", i18n.reportsCategoryFeatures), + opt("BUGS", selectedCategory === "BUGS", i18n.reportsCategoryBugs), + opt("CONTENT", selectedCategory === "CONTENT", i18n.reportsCategoryContent) + ), + button({ type: "submit", class: "create-button" }, applyLabel) + ) ), - br(), + hr({ class: "form-sep" }), h2({ class: "report-category-fixed" }, selectedCategory), - br(), form( { id: "report-create-form", action: "/reports/create", method: "POST", enctype: "multipart/form-data" }, input({ type: "hidden", name: "category", value: selectedCategory }), input({ type: "hidden", name: "description", value: "" }), - h2({ class: "report-template-main-title" }, i18n.reportsTemplateSectionTitle), - renderTemplateForCategory(selectedCategory, params.draft || {}), - ...renderGalleryFields(formData, false, 8), + label(i18n.reportsTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, value: params.prefillTitle || "" }), + br(), + renderTemplateForCategory(selectedCategory, params.draft || {}), + br(), + ...renderGalleryFields(formData, false, 8), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob" }), br(), br(), label("Tags"), br(), - input({ type: "text", name: "tags", value: Array.isArray(formData.tags) ? formData.tags.join(", ") : (formData.tags || "") }), - br(), + input({ type: "text", name: "tags", placeholder: i18n.tagsPlaceholder, value: Array.isArray(formData.tags) ? formData.tags.join(", ") : (formData.tags || "") }), br(), label(i18n.reportsSeverity), br(), @@ -440,15 +470,14 @@ exports.reportView = async (reports, filter, reportId, createCategory, params = br(), button({ type: "submit", class: "create-button" }, i18n.reportsCreateButton) ) - ) - : div( + ] + : [ form( { id: "report-edit-form", action: `/reports/update/${encodeURIComponent(reportId)}`, method: "POST", enctype: "multipart/form-data" }, label(i18n.reportsTitleLabel), br(), input({ type: "text", name: "title", maxlength: "100", required: true, value: reportToEdit?.title || "" }), br(), - br(), input({ type: "hidden", name: "description", value: hiddenDescription }), label(i18n.reportsCategory), br(), @@ -459,17 +488,12 @@ exports.reportView = async (reports, filter, reportId, createCategory, params = opt("CONTENT", selectedCategory === "CONTENT", i18n.reportsCategoryContent) ), br(), - br(), - h2({ class: "report-template-main-title" }, i18n.reportsTemplateSectionTitle), renderTemplateForCategory(selectedCategory, selectedTemplate), br(), - br(), ...renderGalleryFields(reportToEdit || {}, true, 8), - br(), label("Tags"), br(), - input({ type: "text", name: "tags", value: reportToEdit?.tags?.join(", ") || "" }), - br(), + input({ type: "text", name: "tags", placeholder: i18n.tagsPlaceholder, value: reportToEdit?.tags?.join(", ") || "" }), br(), label(i18n.reportsSeverity), br(), @@ -484,16 +508,17 @@ exports.reportView = async (reports, filter, reportId, createCategory, params = br(), button({ type: "submit" }, i18n.reportsUpdateButton) ) - ) + ] ) : filtered.length > 0 - ? div({ class: "jobs-grid" }, filtered.map((r) => renderReportCard(r, userId, filter, params.spreadMap && params.spreadMap.get(r.id)))) - : p(i18n.reportsNoItems) + ? div({ class: "jobs-grid" }, paged(filtered).map((r) => renderReportCard(r, userId, filter, params.spreadMap && params.spreadMap.get(r.id)))) + : div({ class: "no-content-box" }, p(i18n.reportsNoItems)) ) ); }; exports.singleReportView = async (report, filter, comments = [], params = {}) => { + const reportChip = (mode) => reportChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null)(mode); const btnClass = (v) => (filter === v ? "filter-btn active" : "filter-btn"); const confirmations = Array.isArray(report.confirmations) ? report.confirmations : []; const isAuthor = String(report.author) === String(userId); @@ -516,9 +541,6 @@ exports.singleReportView = async (report, filter, comments = [], params = {}) => ownerActions.push(form({ method: "GET", action: `/reports/edit/${encodeURIComponent(report.id)}` }, button({ type: "submit", class: "tribe-action-btn" }, i18n.reportsUpdateButton) )); - ownerActions.push(form({ method: "POST", action: `/reports/delete/${encodeURIComponent(report.id)}` }, - button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.reportsDeleteButton) - )); } const tagsNode = report.tags && report.tags.length @@ -539,13 +561,13 @@ exports.singleReportView = async (report, filter, comments = [], params = {}) => const reportSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(report.id, null, { spread: params.spreads || null, author: report.author, favKind: 'reports', isFavorite: report.isFavorite, reportTitle: report.title, report: false }) + renderContentActions(report.id, `/reports/${encodeURIComponent(report.id)}`, { spread: params.spreads || null, author: report.author, favKind: 'reports', isFavorite: report.isFavorite, reportTitle: report.title, report: false, returnTo: `/reports/${encodeURIComponent(report.id)}`, deleteAction: isAuthor ? contentDeleteAction('report', report.id) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, report.title) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), + table({ class: "tribe-info-table jobs-info-table" }, ...infoRows, ...renderCreatedRows(report.createdAt, report.author)), tagsNode, div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.reportsConfirmations}: ${confirmations.length}`) @@ -569,41 +591,37 @@ exports.singleReportView = async (report, filter, comments = [], params = {}) => const reportMain = div({ class: "tribe-main" }, details ? div({ class: "job-section" }, details) : null, renderPhotoGallery(report, 'report'), - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(report.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(report.author) - ), renderEngagement(report.id, opinionsBar, renderReportCommentsSection(report.id, comments)) ); return template( report.title, section( - div({ class: "tags-header" }, h2(i18n.reportsTitle), p(i18n.reportsDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.reportsTitle), p(i18n.reportsDescription)), div( { class: "filters" }, form( { method: "GET", action: "/reports", class: "ui-toolbar ui-toolbar--filters" }, button({ type: "submit", name: "filter", value: "all", class: btnClass("all") }, String(i18n.reportsFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: btnClass("mine") }, String(i18n.reportsFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: btnClass("recent") }, String(i18n.reportsFilterRecent).toUpperCase()), + ...(reportChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: btnClass("recent") }, String(i18n.reportsFilterRecent).toUpperCase())] : []), + ...(reportChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: btnClass("mine") }, String(i18n.reportsFilterMine).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "top", class: btnClass("top") }, String(i18n.reportsFilterTop).toUpperCase()), - button({ type: "submit", name: "filter", value: "features", class: btnClass("features") }, String(i18n.reportsFilterFeatures).toUpperCase()), - button({ type: "submit", name: "filter", value: "bugs", class: btnClass("bugs") }, String(i18n.reportsFilterBugs).toUpperCase()), - button({ type: "submit", name: "filter", value: "content", class: btnClass("content") }, String(i18n.reportsFilterContent).toUpperCase()), - button({ type: "submit", name: "filter", value: "confirmed", class: btnClass("confirmed") }, String(i18n.reportsFilterConfirmed).toUpperCase()), + ...(reportChip("features") ? [button({ type: "submit", name: "filter", value: "features", class: btnClass("features") }, String(i18n.reportsFilterFeatures).toUpperCase())] : []), + ...(reportChip("bugs") ? [button({ type: "submit", name: "filter", value: "bugs", class: btnClass("bugs") }, String(i18n.reportsFilterBugs).toUpperCase())] : []), + ...(reportChip("content") ? [button({ type: "submit", name: "filter", value: "content", class: btnClass("content") }, String(i18n.reportsFilterContent).toUpperCase())] : []), + ...(reportChip("confirmed") ? [button({ type: "submit", name: "filter", value: "confirmed", class: btnClass("confirmed") }, String(i18n.reportsFilterConfirmed).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.reportsCreateButton) ), form( { method: "GET", action: "/reports", class: "ui-toolbar ui-toolbar--filters reports-subfilters" }, - button({ type: "submit", name: "filter", value: "open", class: btnClass("open") }, String(i18n.reportsFilterOpen).toUpperCase()), - button({ type: "submit", name: "filter", value: "under_review", class: btnClass("under_review") }, String(i18n.reportsFilterUnderReview).toUpperCase()), - button({ type: "submit", name: "filter", value: "resolved", class: btnClass("resolved") }, String(i18n.reportsFilterResolved).toUpperCase()), - button({ type: "submit", name: "filter", value: "invalid", class: btnClass("invalid") }, String(i18n.reportsFilterInvalid).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_low", class: btnClass("sev_low") }, String(i18n.reportsSeverityLow).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_medium", class: btnClass("sev_medium") }, String(i18n.reportsSeverityMedium).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_high", class: btnClass("sev_high") }, String(i18n.reportsSeverityHigh).toUpperCase()), - button({ type: "submit", name: "filter", value: "sev_critical", class: btnClass("sev_critical") }, String(i18n.reportsSeverityCritical).toUpperCase()) + ...(reportChip("open") ? [button({ type: "submit", name: "filter", value: "open", class: btnClass("open") }, String(i18n.reportsFilterOpen).toUpperCase())] : []), + ...(reportChip("under_review") ? [button({ type: "submit", name: "filter", value: "under_review", class: btnClass("under_review") }, String(i18n.reportsFilterUnderReview).toUpperCase())] : []), + ...(reportChip("resolved") ? [button({ type: "submit", name: "filter", value: "resolved", class: btnClass("resolved") }, String(i18n.reportsFilterResolved).toUpperCase())] : []), + ...(reportChip("invalid") ? [button({ type: "submit", name: "filter", value: "invalid", class: btnClass("invalid") }, String(i18n.reportsFilterInvalid).toUpperCase())] : []), + ...(reportChip("sev_low") ? [button({ type: "submit", name: "filter", value: "sev_low", class: btnClass("sev_low") }, String(i18n.reportsSeverityLow).toUpperCase())] : []), + ...(reportChip("sev_medium") ? [button({ type: "submit", name: "filter", value: "sev_medium", class: btnClass("sev_medium") }, String(i18n.reportsSeverityMedium).toUpperCase())] : []), + ...(reportChip("sev_high") ? [button({ type: "submit", name: "filter", value: "sev_high", class: btnClass("sev_high") }, String(i18n.reportsSeverityHigh).toUpperCase())] : []), + ...(reportChip("sev_critical") ? [button({ type: "submit", name: "filter", value: "sev_critical", class: btnClass("sev_critical") }, String(i18n.reportsSeverityCritical).toUpperCase())] : []) ) ), div({ class: "tribe-details" }, reportSide, reportMain) diff --git a/src/views/rooms_view.js b/src/views/rooms_view.js new file mode 100644 index 0000000..1c6bafb --- /dev/null +++ b/src/views/rooms_view.js @@ -0,0 +1,463 @@ +const { div, h2, p, section, button, form, a, span, br, hr, label, textarea, input, select, option, img, table, tr, td, details, summary, ul, li } = require("../server/node_modules/hyperaxe") +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderLifespanChip, renderContentActions, renderInviteQrCard, renderModuleStatsBy, moduleIsEmpty, liveClock, renderPhoneChip, contentDeleteAction, paged, renderVideoGrid, renderLiveFeed } = require("./main_views") +const { renderEncryptedChip, renderTransportChip, renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view") +const { renderStyledText } = require("../backend/renderStyledText") +const moment = require("../server/node_modules/moment") +const { config } = require("../server/SSB_server.js") + +const userId = config.keys.id +const ROOM_MAX = 50 +const MODES = ["all", "recent", "mine", "live", "open", "invite", "closed"] + +const safeArr = (v) => (Array.isArray(v) ? v : []) +const roomHref = (room) => `/rooms/${encodeURIComponent(room.rootId)}` +const occOf = (params, room) => (params && params.occupancy && params.occupancy.get(room.rootId)) || null +const modeLabel = (f) => String(i18n[`roomFilter${f.charAt(0).toUpperCase() + f.slice(1)}`] || f).toUpperCase() +const ROOM_REACH = ["OPEN", "INVITE-ONLY"] +const roomReachLabel = (s) => s === "INVITE-ONLY" ? i18n.roomStatusInviteOnly : i18n.roomStatusOpen +const clearnetEligible = (room) => !!room && !room.tribeId && !room.encrypted && room.type === "OPEN" && String(room.status || "").toUpperCase() === "OPEN" && !room.isClosed +const roomClearnetHref = (room) => clearnetItemHref("rooms", room.title, room.rootId) + +const renderRoomStatusChip = (room) => { + const s = room.isClosed ? "CLOSED" : room.type + const variant = s === "CLOSED" ? "closed" : s === "INVITE-ONLY" ? "whole" : "mutuals" + const icon = s === "CLOSED" ? "✗" : s === "INVITE-ONLY" ? "🔑" : "✓" + const label = s === "CLOSED" ? i18n.roomStatusClosed : s === "INVITE-ONLY" ? i18n.roomStatusInviteOnly : i18n.roomStatusOpen + return renderStateChip(variant, icon, label) +} + +const renderRoomNumberChip = (room, occ, inside) => { + if (!room.number) return null + if (room.line === "SWITCHBOARD" && !inside && occ && Number(occ.count) > 0) return renderPhoneChip(room.number, i18n.roomNumberHint) + return span({ title: room.line !== "SWITCHBOARD" ? i18n.roomLineDndError : inside ? i18n.roomYouAreIn : i18n.roomNobodyInside }, renderStateChip("whole", "✆", room.number)) +} +const renderRoomLineChip = (room) => room.isClosed || String(room.author) === String(userId) + ? null + : renderStateChip(room.line === "SWITCHBOARD" ? "mutuals" : "hidden", null, String(room.line === "SWITCHBOARD" ? i18n.roomLineSwitchboard : i18n.phoneDnd).toUpperCase()) + +const renderLiveChip = (occ) => { + if (!occ) return null + const count = Number(occ.count) || 0 + const max = occ.max || ROOM_MAX + return count > 0 ? renderStateChip("mutuals", "●", `${i18n.roomLiveChip} ${count}/${max}`) : renderStateChip("neutral", "●", `${count}/${max}`) +} + +const roomChips = (room, occ, inside = false) => [ + renderRoomStatusChip(room), + renderEncryptedChip(i18n), + room.type === "OPEN" && !room.tribeId ? renderTransportChip(i18n) : null, + renderRoomNumberChip(room, occ, inside), + renderRoomLineChip(room), + renderLiveChip(occ), + renderLifespanChip(room.lifetime, i18n) +].filter(Boolean) + +const renderModeButtons = (currentFilter, emptyMod = false, modesAvail = null) => + div({ class: "tribe-mode-buttons" }, + ...(emptyMod ? [] : [ + MODES.filter(f => f === "all" || f === currentFilter || (modesAvail && modesAvail[f] === true)).map(f => + form({ method: "GET", action: "/rooms" }, + input({ type: "hidden", name: "filter", value: f }), + button({ type: "submit", class: currentFilter === f ? "filter-btn active" : "filter-btn" }, modeLabel(f)) + ) + ) + ]), + form({ method: "GET", action: "/rooms" }, + input({ type: "hidden", name: "filter", value: "create" }), + button({ type: "submit", class: "create-button" }, i18n.roomCreate) + ) + ) + +const renderCover = (room, cls) => room.image ? img({ loading: "lazy", src: `/blob/${encodeURIComponent(room.image)}`, class: cls, alt: "" }) : null + +const roomIsFull = (occ) => !!occ && occ.count >= ((occ && occ.max) || ROOM_MAX) + +const joinForm = (room, occ, cls = "tribe-action-btn") => { + const max = (occ && occ.max) || ROOM_MAX + const count = occ ? occ.count : null + const full = count !== null && count >= max + return form({ method: "POST", action: `/rooms/join/${encodeURIComponent(room.rootId)}`, class: "room-join-form" }, + button({ type: "submit", class: cls, ...(full ? { disabled: true } : {}) }, + String(full ? i18n.roomFull : i18n.roomJoin).toUpperCase()) + ) +} + +const renderRoomItem = (room, params = {}) => { + const inside = params.live && params.live.ref === room.rootId + const occ = inside && Number.isFinite(Number(params.live.count)) ? { count: Number(params.live.count), max: params.live.max } : occOf(params, room) + const canJoin = !room.isClosed && !inside && (room.type === "OPEN" || room.tribeId || String(room.author) === String(userId) || safeArr(room.members).includes(userId)) + return li({ class: "mailing-archive-item room-item" }, + div({ class: "emergency-update-head mailing-archive-head" }, + div({ class: "mailing-archive-meta" }, + a({ href: roomHref(room), class: "user-link" }, room.title || "—"), + span({ class: "card-label activity-update-counts mailing-counts" }, `👥: ${safeArr(room.members).length}`), + ...roomChips(room, occ, inside), + room.clearnet === true && clearnetEligible(room) ? renderReachChip(true, i18n, roomClearnetHref(room)) : null, + inside ? renderStateChip("whole", "ꘒ", String(i18n.roomYouAreIn).toUpperCase()) : null + ), + renderContentActions(room.rootId, roomHref(room), { spread: (params.spreadMap && params.spreadMap.get(room.rootId)) || null, author: room.author, favKind: "rooms", isFavorite: room.isFavorite, reportTitle: room.title, deleteAction: String(room.author) === String(userId) ? contentDeleteAction("room", room.rootId) : undefined }) + ) + ) +} + +const renderRoomForm = (room, params = {}) => { + const tribeId = String(params.tribeId || "") + const isEdit = !!room + const draft = isEdit ? room : (params.draft || {}) + const reachRaw = String(params.reach || draft.status || "").toUpperCase() + const reach = ROOM_REACH.includes(reachRaw) ? reachRaw : "OPEN" + const withReach = !isEdit && !tribeId + const showClearnet = isEdit ? clearnetEligible(room) : (withReach && reach === "OPEN") + return div({ class: "div-center audio-form" }, + h2(isEdit ? i18n.roomUpdateSectionTitle : i18n.roomCreateSectionTitle), + withReach + ? [ + form({ method: "GET", action: "/rooms" }, + input({ type: "hidden", name: "filter", value: "create" }), + label(i18n.roomTypeLabel), br(), + div({ class: "apply-row" }, + select({ name: "status", class: "report-category-select" }, + ...ROOM_REACH.map(s => option({ value: s, ...(reach === s ? { selected: true } : {}) }, roomReachLabel(s))) + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, roomReachLabel(reach)) + ] + : null, + form({ method: "POST", action: isEdit ? `/rooms/update/${encodeURIComponent(room.rootId)}` : "/rooms/create", enctype: "multipart/form-data" }, + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + withReach ? input({ type: "hidden", name: "status", value: reach }) : null, + span(i18n.title || "Title"), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.roomTitlePlaceholder, value: draft.title || "" }), br(), + span(i18n.roomDescriptionLabel), br(), + textarea({ name: "description", rows: 4, maxlength: "1000", placeholder: i18n.roomDescriptionPlaceholder }, draft.description || ""), br(), + span(i18n.uploadMedia), br(), + input({ type: "file", name: "image", accept: "image/*" }), br(), br(), + span(i18n.roomTagsLabel), br(), + input({ type: "text", name: "tags", placeholder: i18n.tagsPlaceholder, value: safeArr(draft.tags).join(", ") }), br(), + showClearnet ? renderClearnetSelector(draft.clearnet === true || String(draft.clearnet || "") === "1", i18n) : null, + button({ type: "submit", class: "create-button" }, isEdit ? i18n.roomUpdate : i18n.roomCreate) + ) + ) +} + +exports.renderRoomInvitePage = (code) => + template(i18n.roomInviteMode || i18n.tribeInviteCodeText, + section(div({ class: "invite-page" }, + h2(i18n.tribeInviteCodeText, code), + form({ method: "GET", action: "/rooms" }, + input({ type: "hidden", name: "filter", value: "all" }), + button({ type: "submit", class: "filter-btn" }, i18n.walletBack) + ) + )) + ) + +exports.roomsView = async (rooms, filter, roomToEdit, params = {}) => { + const list = safeArr(rooms) + const q = String(params.q || "").trim() + const isForm = filter === "create" || filter === "edit" + const emptyMod = moduleIsEmpty(list, filter || "all", "all", q) + const shown = q + ? list.filter(r => [r.title, r.description, r.number, ...safeArr(r.tags)].some(v => String(v || "").toLowerCase().includes(q.toLowerCase()))) + : list + return template(i18n.roomsTitle, + section( + div({ class: "tags-header module-header-line" }, h2(i18n.roomsTitle), p(i18n.roomsDescription)), + renderModeButtons(filter, emptyMod, params.modesAvail || null), + !isForm && !emptyMod + ? div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(shown, r => r.isClosed ? "CLOSED" : r.type, [{ value: "OPEN", label: i18n.roomStatusOpen }, { value: "INVITE-ONLY", label: i18n.roomStatusInviteOnly }, { value: "CLOSED", label: i18n.roomStatusClosed }]), + form({ method: "GET", action: "/rooms", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: filter }), + input({ type: "text", name: "q", placeholder: i18n.roomSearchPlaceholder, value: q, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ) + : null, + isForm + ? renderRoomForm(filter === "edit" ? roomToEdit : null, params) + : (shown.length + ? ul({ class: "mailing-archive" }, ...paged(shown).map(r => renderRoomItem(r, params))) + : div({ class: "tribe-grid" }, p(i18n.roomsNoItems))) + ) + ) +} + +const eventLine = (ev) => { + const key = ev.t === "join" ? "roomEventJoined" : ev.t === "leave" ? "roomEventLeft" : ev.t === "recStart" ? "roomEventRecStart" : ev.t === "mute" ? "roomEventMuted" : ev.t === "unmute" ? "roomEventUnmuted" : ev.t === "hand" ? "roomEventHand" : "roomEventRecStop" + const [before, after] = String(i18n[key] || "{name}").split("{name}") + return [before, userLink(ev.id), after] +} +const renderRecChip = () => span({ class: "room-rec-chip" }, "● " + String(i18n.roomRecording).toUpperCase()) +const fmtDuration = (ms) => { const s = Math.max(0, Math.floor((Number(ms) || 0) / 1000)); return `${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}` } +const refreshLink = (room) => a({ href: roomHref(room), class: "tribe-action-btn room-refresh-btn" }, String(i18n.liveRefresh).toUpperCase()) +const renderLiveControls = (room, live) => div({ class: "room-live-controls" }, + form({ method: "POST", action: "/rooms/notify", class: "phone-action-form" }, + input({ type: "hidden", name: "on", value: live.notify ? "0" : "1" }), + button({ type: "submit", class: live.notify ? "tribe-action-btn" : "tribe-action-btn room-notify-off" }, "🔔 " + String(live.notify ? i18n.roomNotificationsOn : i18n.roomNotificationsOff).toUpperCase()) + ), + form({ method: "POST", action: "/rooms/hand", class: "phone-action-form" }, + input({ type: "hidden", name: "hand", value: live.hand ? "0" : "1" }), + input({ type: "hidden", name: "returnTo", value: roomHref(room) }), + button({ type: "submit", class: live.hand ? "tribe-action-btn room-hand-btn-on" : "tribe-action-btn" }, "✋ " + String(live.hand ? i18n.roomLowerHand : i18n.roomRaiseHand).toUpperCase()) + ), + live.notify && safeArr(live.events).length + ? form({ method: "POST", action: "/rooms/notify/clear", class: "phone-action-form" }, button({ type: "submit", class: "tribe-action-btn" }, String(i18n.roomClearNotices).toUpperCase())) + : null, + form({ method: "POST", action: "/rooms/mute", class: "phone-action-form" }, + input({ type: "hidden", name: "mute", value: live.muted ? "0" : "1" }), + input({ type: "hidden", name: "returnTo", value: roomHref(room) }), + button({ type: "submit", class: "tribe-action-btn" }, String(live.muted ? i18n.phoneUnmute : i18n.phoneMute).toUpperCase()) + ), + live.videoAvailable + ? form({ method: "POST", action: "/rooms/camera", class: "phone-action-form" }, + input({ type: "hidden", name: "cam", value: live.cam ? "0" : "1" }), + input({ type: "hidden", name: "returnTo", value: roomHref(room) }), + button({ type: "submit", class: live.cam ? "tribe-action-btn room-cam-btn-on" : "tribe-action-btn" }, String(live.cam ? i18n.phoneCameraOff : i18n.phoneCameraOn).toUpperCase()) + ) + : null, + live.recording + ? form({ method: "POST", action: "/rooms/rec/stop", class: "phone-action-form" }, button({ type: "submit", class: "tribe-action-btn danger-btn" }, String(i18n.roomRecStop).toUpperCase())) + : form({ method: "POST", action: "/rooms/rec/start", class: "phone-action-form" }, button({ type: "submit", class: "tribe-action-btn" }, String(i18n.roomRec).toUpperCase())), + live.recording || safeArr(live.recordingBy).length ? renderRecChip() : null, + refreshLink(room), + form({ method: "POST", action: "/rooms/leave", class: "phone-action-form" }, + input({ type: "hidden", name: "returnTo", value: roomHref(room) }), + button({ type: "submit", class: "tribe-action-btn danger-btn" }, String(i18n.roomLeave).toUpperCase()) + ) +) +const handOrder = (live) => { + const all = [{ you: true, hand: !!live.hand, since: Number(live.handSince) || 0 }, ...safeArr(live.peers).map(peer => ({ peer, hand: !!peer.hand, since: Number(peer.handSince) || 0 }))] + const raised = all.filter(e => e.hand).sort((a, b) => a.since - b.since) + raised.forEach((e, i) => { e.turn = i + 1 }) + return [...raised, ...all.filter(e => !e.hand)] +} +const renderRoomEvents = (live) => live.notify && safeArr(live.events).length + ? ul({ class: "room-events" }, ...safeArr(live.events).slice().reverse().map(ev => li({ class: "room-event" }, span({ class: "date-link" }, moment(ev.ts).format("HH:mm:ss")), span({ class: "room-event-text" }, ...eventLine(ev))))) + : null +const renderRecordings = (recordings) => safeArr(recordings).length + ? div({ class: "phone-block room-recordings-block" }, + h2(i18n.roomRecordings), + ul({ class: "room-recordings" }, ...recordings.map(r => li({ class: "room-recording" }, + span({ class: "date-link" }, moment(r.at).format("YYYY/MM/DD HH:mm")), + span({ class: "phone-call-clock" }, fmtDuration(r.durationMs)), + a({ href: `/rooms/recordings/${encodeURIComponent(r.name)}`, class: "tribe-action-btn", download: r.name }, String(i18n.fileDownload).toUpperCase()), + form({ method: "POST", action: `/rooms/recordings/${encodeURIComponent(r.name)}/delete`, class: "phone-action-form" }, + button({ type: "submit", class: "btn-singleview btn-delete", title: i18n.delete }, "✕")) + ))) + ) + : null +const renderLivePanel = (room, live, occ, canJoin = false) => { + const head = (left, controls = null) => div({ class: "room-live-head" }, + div({ class: "room-live-head-main" }, left), + controls || refreshLink(room) + ) + if (live) { + const silenceForm = (id, on) => form({ method: "POST", action: "/rooms/silence", class: "phone-action-form" }, + input({ type: "hidden", name: "id", value: id }), + input({ type: "hidden", name: "on", value: on ? "0" : "1" }), + button({ type: "submit", class: on ? "tribe-action-btn danger-btn" : "tribe-action-btn" }, String(on ? i18n.phoneUnsilence : i18n.phoneSilence).toUpperCase()) + ) + const who = (id, muted, you, silenced = false, recording = false, turn = 0, cam = false) => div({ class: "phone-contact room-peer" + (turn ? " room-peer-raised" : "") }, + turn ? span({ class: "room-peer-hand" }, `✋ ${turn}`) : null, + userLink(id), + you ? span({ class: "room-peer-you" }, i18n.roomYou) : null, + muted ? span({ class: "room-peer-muted" }, String(i18n.roomMuted).toUpperCase()) : null, + cam ? span({ class: "room-peer-cam" }, "CAM") : null, + recording ? renderRecChip() : null, + you ? null : silenceForm(id, silenced) + ) + return div({ class: "phone-call-panel room-live-panel" }, + head(div({ class: "card-chips-row" }, renderEncryptedChip(i18n), renderLiveChip({ count: live.count, max: live.max }), live.joinedAt ? span({ class: "phone-call-clock" }, liveClock(live.joinedAt)) : null), renderLiveControls(room, live)), + live.grid + ? (safeArr(live.gridOrder).length + ? div({ class: "video-grid video-grid-one" }, + ...renderLiveFeed("/phone/video/grid", "video-feed video-feed-grid"), + div({ class: "video-grid-names" }, ...safeArr(live.gridOrder).map(id => id === userId ? span({ class: "video-tile-name" }, i18n.roomYou) : userLink(id))) + ) + : null) + : renderVideoGrid(safeArr(live.peers).map(p => ({ id: p.id, on: !!p.cam })), !!live.cam), + div({ class: "phone-contacts" }, ...handOrder(live).map(e => e.you + ? who(userId, live.muted, true, false, !!live.recording, e.turn, !!live.cam) + : who(e.peer.id, e.peer.muted, false, !!e.peer.silenced, !!e.peer.recording, e.turn, !!e.peer.cam))), + renderRoomEvents(live) + ) + } + const count = occ ? occ.count : 0 + return div({ class: "phone-block room-live-panel" }, + head(p({ class: "phone-empty" }, count > 0 ? String(i18n.roomSomeone).replace("{n}", String(count)) : i18n.roomNobody), canJoin ? div({ class: "room-live-controls" }, refreshLink(room), joinForm(room, occ)) : null) + ) +} + +exports.singleRoomView = async (room, params = {}) => { + const isAuthor = String(room.author) === String(userId) + const isMember = safeArr(room.members).includes(userId) || (!!room.tribeId && !!room.isTribeMember) + const restricted = !isMember && !isAuthor && room.type === "INVITE-ONLY" + const live = params.live && params.live.ref === room.rootId ? params.live : null + const occ = params.occ || null + const canJoin = !restricted && !room.isClosed && params.available !== false + const canClearnet = clearnetEligible(room) + const isClearnet = canClearnet && room.clearnet === true + + const inviteActions = isAuthor && room.type === "INVITE-ONLY" && !room.tribeId && !room.isClosed + ? [ + form({ method: "POST", action: `/rooms/generate-invite/${encodeURIComponent(room.rootId)}` }, + button({ type: "submit", class: "tribe-action-btn" }, i18n.tribeGenerateInvite) + ), + (() => { + const openInvite = safeArr(room.invites).find(inv => typeof inv === "object" && inv && inv.public === true && inv.code) + if (openInvite) return [ + div({ class: "tribe-open-invite" }, + span({ class: "card-label" }, i18n.tribeInviteCodeText), + span({ class: "tribe-open-invite-code" }, openInvite.code), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/rooms/${encodeURIComponent(openInvite.code)}` }) + ), + form({ method: "POST", action: `/rooms/open-invite/remove/${encodeURIComponent(room.rootId)}` }, + button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.tribeRemoveInvitation) + ) + ] + return form({ method: "POST", action: `/rooms/open-invite/create/${encodeURIComponent(room.rootId)}` }, + button({ type: "submit", class: "tribe-action-btn" }, i18n.tribeOpenInvitation) + ) + })() + ].flat().filter(Boolean) + : [] + + const ownerActions = isAuthor + ? [ + room.isClosed ? null : form({ method: "POST", action: `/rooms/close/${encodeURIComponent(room.rootId)}` }, + button({ type: "submit", class: "update-btn" }, i18n.roomClose) + ), + form({ method: "GET", action: "/rooms" }, + input({ type: "hidden", name: "filter", value: "edit" }), + input({ type: "hidden", name: "id", value: room.rootId }), + button({ type: "submit", class: "update-btn" }, i18n.roomUpdate) + ) + ].filter(Boolean) + : [] + + const side = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(room.rootId, roomHref(room), { spread: params.spreads || null, author: room.author, favKind: "rooms", isFavorite: room.isFavorite, returnTo: roomHref(room), reportTitle: room.title, deleteAction: isAuthor ? contentDeleteAction("room", room.rootId) : undefined }) + ), + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, restricted ? i18n.roomStatusInviteOnly : (room.title || "—"))), + div({ class: "card-chips-row" }, + ...roomChips(room, live ? { count: live.count, max: live.max } : occ, !!live), + canClearnet ? renderReachChip(isClearnet, i18n, isClearnet ? roomClearnetHref(room) : null) : null, + isAuthor && canClearnet ? renderClearnetSwitch("rooms", room.rootId, isClearnet) : null + ), + restricted ? null : renderCover(room, "tribe-detail-image"), + restricted || !room.description ? null : p({ class: "tribe-side-description" }, ...renderStyledText(room.description)), + table({ class: "tribe-info-table jobs-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.createdAtLabel || "Created at"), + td({ class: "tribe-info-value", colspan: "3" }, moment(room.createdAt).format("YYYY/MM/DD HH:mm")) + ), + restricted ? null : tr(td({ class: "tribe-info-value", colspan: "4" }, userLink(room.author))) + ), + live || restricted + ? div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.roomParticipants}: ${safeArr(room.members).length}`) + ) + : null, + inviteActions.length ? div({ class: "tribe-side-actions" }, ...inviteActions) : null, + isAuthor && !room.isClosed + ? div({ class: "tribe-side-actions housing-status-row" }, + span({ class: "card-label" }, `${i18n.statusLabel || "Status"}: `), + renderStateChip(room.line === "SWITCHBOARD" ? "mutuals" : "hidden", "✆", String(room.line === "SWITCHBOARD" ? i18n.roomLineSwitchboard : i18n.phoneDnd).toUpperCase()), + form({ method: "POST", action: `/rooms/line/${encodeURIComponent(room.rootId)}`, class: "inline-form" }, + button({ class: "tribe-action-btn", type: "submit", name: "line", value: room.line === "SWITCHBOARD" ? "DND" : "SWITCHBOARD" }, + String(room.line === "SWITCHBOARD" ? i18n.phoneDnd : i18n.roomLineSwitchboard).toUpperCase()) + ) + ) + : null, + restricted + ? div({ class: "tribe-side-actions" }, a({ class: "tribe-action-btn", href: "/invites?filter=others" }, i18n.tribeEnterInvite)) + : null, + restricted || !safeArr(room.tags).length + ? null + : div({ class: "card-tags" }, ...room.tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))), + ownerActions.length ? div({ class: "tribe-side-actions owner-actions" }, ...ownerActions) : null + ) + + const main = div({ class: "tribe-main" }, + restricted + ? p({ class: "access-denied-msg" }, i18n.roomAccessDenied) + : [ + params.available === false ? div({ class: "pm-form-error-msg" }, p("✗ " + i18n.phoneUnavailable)) : null, + live + ? renderLivePanel(room, live, occ) + : [ + renderLivePanel(room, null, occ, canJoin), + details({ class: "pad-members-details", ...(safeArr(room.members).length <= 8 ? { open: true } : {}) }, + summary({ class: "tribe-members-count" }, `${i18n.roomParticipants}: ${safeArr(room.members).length}`), + div({ class: "pad-members-chips" }, ...safeArr(room.members).map(m => span({ class: "pad-member-chip" }, userLink(m)))) + ) + ], + renderRecordings(params.recordings) + ] + ) + + return template(room.title || i18n.roomsTitle, + section(div({ class: "tags-header module-header-line" }, h2(i18n.roomsTitle), p(i18n.roomsDescription)), renderModeButtons("all", false, params.modesAvail || null)), + section(div({ class: "tribe-details" }, side, main)) + ) +} + +exports.renderTribeRoomsSection = (tribe, rooms, occupancy, live, toolbar = null) => { + const items = safeArr(rooms) + const createBtn = form({ method: "GET", action: "/rooms" }, + input({ type: "hidden", name: "filter", value: "create" }), + input({ type: "hidden", name: "tribeId", value: tribe.id }), + button({ type: "submit", class: "create-button" }, i18n.tribeRoomCreate)) + const head = toolbar || div({ class: "tribe-content-header" }, h2(i18n.tribeSectionRooms), createBtn) + if (!items.length) return div({ class: "tribe-content-list" }, head, div({ class: "no-content-box" }, p(i18n.tribeRoomsEmpty))) + return div({ class: "tribe-content-list" }, + head, + ul({ class: "mailing-archive" }, ...paged(items).map(r => renderRoomItem(r, { occupancy, live }))) + ) +} + +exports.renderRoomItem = renderRoomItem +exports.roomStatsBy = (rooms) => renderModuleStatsBy(safeArr(rooms), r => r.isClosed ? "CLOSED" : r.type, [{ value: "OPEN", label: i18n.roomStatusOpen }, { value: "INVITE-ONLY", label: i18n.roomStatusInviteOnly }, { value: "CLOSED", label: i18n.roomStatusClosed }]) + +exports.clearnetRoomView = async (room) => { + const { escapeHtml: esc, renderKindTag, renderClearnetPage } = require("./clearnet_view") + const name = room.title || i18n.cnKindRoom + const extraCss = ` +.cn-room-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700;word-break:break-word} +.cn-room-meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center} +.cn-room-number{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--fg);border-radius:10px;padding:4px 12px;font-size:16px;font-weight:600;color:var(--fg);background:var(--bg-sub)} +.cn-room-number .pm-exposition-icon{font-size:18px;line-height:1} +.cn-room-number .pm-exposition-text{line-height:1;letter-spacing:1px;font-family:monospace} +` + const body = ` +

${esc(name)}

+
+ ${renderKindTag("room")} + ${room.number ? `✆${esc(room.number)}` : ""} +
+` + return renderClearnetPage({ + title: `${name} | Oasis`, + ogTitle: name, + ogDescription: "", + extraCss, + body, + hubFeedId: room.author || null + }) +} + +exports.roomModesFromCensus = (census, me, occupancy) => { + const list = safeArr(census) + return { + mine: list.some(x => String(x.author) === String(me)), + recent: list.length > 0, + live: list.some(x => { const o = occupancy && occupancy.get(x.rootId); return !!(o && o.count > 0) }), + open: list.some(x => x.type === "OPEN" && !x.isClosed), + invite: list.some(x => x.type === "INVITE-ONLY" && !x.isClosed), + closed: list.some(x => x.isClosed) + } +} diff --git a/src/views/school_view.js b/src/views/school_view.js new file mode 100644 index 0000000..eb6c031 --- /dev/null +++ b/src/views/school_view.js @@ -0,0 +1,945 @@ +const { div, h2, h3, p, section, button, form, a, span, textarea, br, input, label, select, option, table, tr, td, th, details, summary, datalist, progress, hr } = require("../server/node_modules/hyperaxe") +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const moment = require("../server/node_modules/moment"); +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderContentActions, renderOpinionsVoting, renderEngagement, renderInviteQrCard, renderSubscriptionBox, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, renderWalletChip, contentDeleteAction, paged } = require("./main_views") +const opinionCategories = require("../backend/opinion_categories") +const { config } = require("../server/SSB_server.js") +const { renderStyledText, renderStyledHtml } = require("../backend/renderStyledText") +const nameCache = require("../backend/nameCache") +const { sanitizeHtml } = require("../backend/sanitizeHtml") +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch } = require("./clearnet_view") +const renderMd = (text) => div({ class: "styled-text", [RAW_HTML]: sanitizeHtml(renderStyledHtml(String(text || ""))) }) + +const userId = config.keys.id +const safeArr = (v) => (Array.isArray(v) ? v : []) +const safeText = (v) => String(v || "").trim() +const isFree = (course) => !(Number(course.price) > 0) +const isProtected = (course) => !isFree(course) || course.visibility === "INVITE" +const clearnetEligible = (course) => course.visibility === "PUBLIC" && isFree(course) && !course.encrypted +const COURSE_TYPES = ["OPEN", "PAID", "INVITE"] +const courseTypeOf = (course) => COURSE_TYPES.includes(String(course.courseType || "").toUpperCase()) ? String(course.courseType).toUpperCase() : (course.visibility === "INVITE" ? "INVITE" : (Number(course.price) > 0 ? "PAID" : "OPEN")) +const courseTypeLabel = (v) => v === "INVITE" ? i18n.schoolTypeInvite : (v === "PAID" ? i18n.schoolTypePaid : i18n.schoolTypeOpen) +const sumCats = (opinions = {}, cats = []) => (cats || []).reduce((sum, cat) => sum + (Number((opinions || {})[cat]) || 0), 0) +const renderStarRating = (opinions, voterCount) => { + const pos = sumCats(opinions, opinionCategories.positive) + const neg = sumCats(opinions, opinionCategories.constructive) + sumCats(opinions, opinionCategories.moderation) + const totalVotes = pos + neg + const full = totalVotes > 0 ? Math.round((pos / totalVotes) * 5) : 0 + const stars = "\u2605".repeat(full) + "\u2606".repeat(5 - full) + return span({ class: "shop-product-stars" }, `${stars} (${voterCount})`) +} + +const renderMediaBlob = (value, fallbackSrc = null, attrs = {}) => { + if (!value) return fallbackSrc ? require("../server/node_modules/hyperaxe").img({ src: fallbackSrc, ...attrs }) : null + const { img } = require("../server/node_modules/hyperaxe") + const s = String(value).trim() + if (s.startsWith('&')) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null +} + +const buildReturnTo = (filter, params = {}) => { + const f = safeText(filter || "all") + const q = safeText(params.q || "") + const sort = safeText(params.sort || "recent") + const parts = [`filter=${encodeURIComponent(f)}`] + if (q) parts.push(`q=${encodeURIComponent(q)}`) + if (sort) parts.push(`sort=${encodeURIComponent(sort)}`) + return `/school?${parts.join("&")}` +} + +const renderModeButtons = (currentFilter, emptyMod = false, modesAvail = null) => + div({ class: "tribe-mode-buttons" }, + ...(emptyMod ? [] : [ + ["all", "recent", "mine", "top", "applied", "open", "favorites"].filter(f => f === "all" || f === currentFilter || (modesAvail && modesAvail[f] !== false)).map(f => + form({ method: "GET", action: "/school" }, + input({ type: "hidden", name: "filter", value: f }), + button({ type: "submit", class: currentFilter === f ? "filter-btn active" : "filter-btn" }, i18n[`schoolFilter${f.charAt(0).toUpperCase() + f.slice(1)}`] || f.toUpperCase()) + ) + ), + ]), + form({ method: "GET", action: "/school" }, + input({ type: "hidden", name: "filter", value: "create" }), + button({ type: "submit", class: "create-button" }, i18n.schoolCreateButton) + ) + ) + +const renderPriceChip = (course) => + isFree(course) + ? renderStateChip("mutuals", "✓", i18n.schoolFree) + : span({ class: "price-chip" }, `${Number(course.price).toFixed(2)} ECO`) + +const renderCourseChips = (course, subscription = null) => + div({ class: "card-chips-row" }, + course.visibility === "INVITE" ? renderStateChip("whole", "\uD83D\uDD11", i18n.chatStatusInviteOnly) : null, + course.status === "CLOSED" + ? renderStateChip("closed", "✗", i18n.schoolClosed) + : renderStateChip("mutuals", "✓", i18n.schoolOngoing), + renderPriceChip(course), + course.clearnet === true && clearnetEligible(course) ? renderReachChip(true, i18n, clearnetItemHref('school', course.title, course.id)) : null, + subscription + ? (subscription.in + ? renderStateChip("mutuals", "\u2709", i18n.subscriptionOn) + : renderStateChip("closed", "\u2709", i18n.subscriptionOff)) + : null + ) + +const renderCourseCard = (course, filter, params = {}) => { + const url = `/school/course/${encodeURIComponent(course.id)}` + const isOwn = String(course.author) === String(userId) + return div({ class: "tribe-card" }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(course.id, url, { + spread: (params.spreadMap && params.spreadMap.get(course.id)) || null, + author: course.author, + favKind: 'school', + isFavorite: course.isFavorite, + reportTitle: course.title, + returnTo: buildReturnTo(filter, params), + deleteAction: isOwn ? contentDeleteAction('schoolCourse', course.id) : undefined + }) + ), + div({ class: "tribe-card-image-wrapper" }, + a({ href: url }, + renderMediaBlob(course.image, '/assets/images/default-avatar.png', { class: 'tribe-card-hero-image' }) + ) + ), + div({ class: "tribe-card-body" }, + h2({ class: "tribe-card-title" }, a({ href: url }, course.title)), + renderStarRating(course.opinions, safeArr(course.opinions_inhabitants).length), + renderCourseChips(course, params.subscriptions && (String(course.author) === String(userId) || safeArr(course.students).includes(userId)) ? { in: params.subscriptions.mine.has(course.rootId || course.id) || String(course.author) === String(userId) } : null), + course.description ? p({ class: "tribe-card-description" }, ...renderStyledText(course.description)) : null, + div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.schoolStudents}: ${course.students.length}`) + ), + course.startDate ? div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.schoolStartDate}: ${moment(course.startDate).format("YYYY/MM/DD HH:mm")}`) + ) : null + ) + ) +} + +const renderCourseForm = (filter, course = {}, params = {}) => { + const isEdit = filter === "edit" + const reachRaw = String(params.reach || "").toUpperCase() + const reach = COURSE_TYPES.includes(reachRaw) ? reachRaw : courseTypeOf(course) + return div({ class: "create-tribe-form" }, + h2(isEdit ? i18n.schoolUpdateSectionTitle : i18n.schoolCreateSectionTitle), + form({ method: "GET", action: "/school" }, + input({ type: "hidden", name: "filter", value: isEdit ? "edit" : "create" }), + isEdit ? input({ type: "hidden", name: "courseId", value: course.id || "" }) : null, + label(i18n.schoolCourseType), br(), + div({ class: "apply-row" }, + select({ name: "courseType", class: "report-category-select" }, + option({ value: "OPEN", ...(reach === "OPEN" ? { selected: true } : {}) }, i18n.schoolTypeOpen), + option({ value: "PAID", ...(reach === "PAID" ? { selected: true } : {}) }, i18n.schoolTypePaid), + option({ value: "INVITE", ...(reach === "INVITE" ? { selected: true } : {}) }, i18n.schoolTypeInvite) + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, courseTypeLabel(reach)), + form({ action: isEdit ? `/school/update/${encodeURIComponent(course.id || "")}` : "/school/create", method: "POST", enctype: "multipart/form-data" }, + input({ type: "hidden", name: "courseType", value: reach }), + label(i18n.title), br, + input({ type: "text", name: "title", maxlength: "120", required: true, placeholder: i18n.schoolCourseTitlePlaceholder, value: course.title || "" }), br(), + label(i18n.description), br, + textarea({ name: "description", rows: 5, required: true, maxlength: "2000", placeholder: i18n.schoolCourseDescriptionPlaceholder }, course.description || ""), br, + label(i18n.blogImage), br, + input({ type: "file", name: "image", accept: "image/*" }), br(), br(), + label(i18n.schoolTags), br, + input({ type: "text", name: "tags", placeholder: i18n.schoolTagsPlaceholder, value: Array.isArray(course.tags) ? course.tags.join(", ") : (course.tags || "") }), br, + ...(reach === "PAID" + ? [ + label(i18n.schoolPrice), br, + input({ type: "number", name: "price", step: "0.000001", min: "0.000001", required: true, value: course.price && Number(course.price) > 0 ? course.price : "" }), renderEcoValueChip(), br(), br() + ] + : reach === "INVITE" + ? [ + label(i18n.schoolPrice), br, + input({ type: "number", name: "price", step: "0.000001", min: "0", value: course.price && Number(course.price) > 0 ? course.price : "0" }), renderEcoValueChip(), br(), br() + ] + : [input({ type: "hidden", name: "price", value: "0" })]), + label(i18n.schoolStartDate), br, + input({ type: "datetime-local", name: "startDate", min: moment().format("YYYY-MM-DDTHH:mm"), value: course.startDate ? moment(course.startDate).format("YYYY-MM-DDTHH:mm") : "" }), br(), + isEdit + ? div( + label(i18n.schoolStatus), br, + select({ name: "status" }, + option({ value: "ONGOING", ...((course.status || "ONGOING") === "ONGOING" ? { selected: true } : {}) }, i18n.schoolOngoing), + option({ value: "CLOSED", ...(course.status === "CLOSED" ? { selected: true } : {}) }, i18n.schoolClosed) + ), br() + ) + : null, + reach === "OPEN" && clearnetEligible({ ...course, visibility: "PUBLIC", price: "0" }) ? renderClearnetSelector(course.clearnet === true || String(course.clearnet || "") === "1", i18n) : null, + br(), + button({ type: "submit" }, isEdit ? i18n.schoolUpdate : i18n.schoolCreate) + ) + ) +} + +exports.schoolView = async (courses, filter, courseToEdit = null, params = {}) => { + const q = safeText(params.q || "") + const sort = safeText(params.sort || "recent") + const list = safeArr(courses) + const emptyMod = moduleIsEmpty(list, filter || "all", "all", q) + const title = i18n.schoolTitle + const isForm = filter === "create" || filter === "edit" + + return template( + title, + section(div({ class: "tags-header module-header-line" }, h2(title), p(i18n.schoolDescription), renderWalletChip())), + section(renderModeButtons(filter, emptyMod, (params && params.modesAvail) || null)), + !isForm && !emptyMod + ? section( + div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(list, c => String(c.status || 'ONGOING').toUpperCase(), [{ value: 'ONGOING', label: i18n.schoolOngoing }, { value: 'CLOSED', label: i18n.schoolClosed }]), + form({ method: "GET", action: "/school", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: filter }), + input({ type: "text", name: "q", value: q, placeholder: i18n.schoolSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, + select({ name: "sort", class: "filter-box__select" }, + option({ value: "recent", ...(sort === "recent" ? { selected: true } : {}) }, i18n.documentSortRecent), + option({ value: "students", ...(sort === "students" ? { selected: true } : {}) }, i18n.documentSortTop), + option({ value: "title", ...(sort === "title" ? { selected: true } : {}) }, i18n.schoolSortTitle) + ), + button({ type: "submit", class: "filter-box__button" }, i18n.searchButton) + ) + ) + ) + ) + : null, + section( + isForm + ? renderCourseForm(filter, filter === "edit" ? (courseToEdit || {}) : (params.draft || {}), params) + : div({ class: "tribe-grid" }, + list.length + ? paged(list).map(course => renderCourseCard(course, filter, params)) + : p(i18n.schoolNoCourses) + ) + ) + ) +} + +const renderLesson = (lesson, course, isTeacher, returnTo, isStudent = false, isNext = false) => + lesson.locked + ? div({ class: "school-lesson school-lesson-locked" }, + h3("🔒"), + p(i18n.schoolLessonLocked), + div({ class: "school-lesson-meta" }, span(moment(lesson.createdAt).format("YYYY/MM/DD"))) + ) + : div({ class: isNext ? "school-lesson school-lesson-next" : "school-lesson" }, + details({ class: "school-lesson-details", ...(isNext ? { open: true } : {}) }, + summary({ class: "school-lesson-summary" }, + span({ class: "school-lesson-summary-title" }, (lesson.completed ? "✓ " : isNext ? "▶ " : "") + lesson.title), + span({ class: "school-lesson-summary-meta" }, + lesson.sessionDate ? `${moment(lesson.sessionDate).format("YYYY/MM/DD HH:mm")} · ` : "", + moment(lesson.createdAt).format("YYYY/MM/DD") + ) + ), + div({ class: "school-lesson-body" }, + renderMd(lesson.text), + div({ class: "school-lesson-actions" }, + a({ href: `/school/lesson/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}`, class: "filter-btn" }, i18n.schoolLessonOpen || "Open lesson"), + isTeacher + ? a({ href: `/school/lesson/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}?edit=1`, class: "filter-btn" }, i18n.chatUpdate) + : null, + isStudent + ? form({ method: "POST", action: `/school/lesson/complete/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}`, class: "inline-form" }, + input({ type: "hidden", name: "value", value: lesson.completed ? "false" : "true" }), + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ type: "submit", class: lesson.completed ? "tribe-action-btn school-complete-btn school-complete-btn--done" : "tribe-action-btn school-complete-btn" }, lesson.completed ? `✓ ${i18n.schoolProgressDone}` : i18n.schoolProgressMark) + ) + : null + ) + ) + ) + ) + +const renderTeacherPanel = (course, certificates, returnTo, lessons = [], exams = [], progress = {}, certStudent = "") => + div({ class: "create-tribe-form school-teacher-panel" }, + h2(i18n.schoolTeacherPanel), + div({ class: "school-panel-box" }, + h3(i18n.schoolAddLesson), + form({ method: "POST", action: `/school/lesson/add/${encodeURIComponent(course.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + input({ type: "text", name: "title", maxlength: "120", required: true, placeholder: i18n.schoolLessonTitlePlaceholder }), br(), + textarea({ name: "text", rows: 5, required: true, maxlength: "4000", placeholder: i18n.schoolLessonTextPlaceholder }), br, + label(i18n.schoolLessonUnit), br, + input({ type: "text", name: "unit", maxlength: "60", placeholder: i18n.schoolLessonUnitPlaceholder }), br(), + label(i18n.schoolLessonOrder), br, + input({ type: "number", name: "order", min: "0", step: "1" }), br(), br(), + label(i18n.schoolSessionDate), br, + input({ type: "datetime-local", name: "sessionDate", min: moment().format("YYYY-MM-DDTHH:mm") }), br(), br(), + button({ type: "submit" }, i18n.schoolAddLesson) + ) + ), + isProtected(course) && safeArr(lessons).length + ? div({ class: "school-panel-box" }, + h3(i18n.schoolExamCreate), + form({ method: "POST", action: `/school/exam/create/${encodeURIComponent(course.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + input({ type: "text", name: "title", maxlength: "120", required: true, placeholder: i18n.schoolExamTitlePlaceholder }), br(), + label(i18n.schoolLessons), br, + select({ name: "lessonId" }, + safeArr(lessons).map(lesson => option({ value: lesson.id }, lesson.title || lesson.id)) + ), br(), br(), + button({ type: "submit" }, i18n.schoolExamCreate) + ) + ) + : null, + (() => { + const openExams = safeArr(exams).filter(x => !x.locked) + return openExams.length + ? div({ class: "school-panel-box" }, + h3(i18n.schoolExamCreateQuestion), + form({ method: "POST", action: `/school/exam/question/add/${encodeURIComponent(course.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + select({ name: "examId" }, + openExams.map(x => option({ value: x.id }, x.title || x.id)) + ), br(), br(), + label(i18n.schoolExamAddQuestion), br, + input({ type: "text", name: "q", required: true, maxlength: "300" }), br(), + input({ type: "text", name: "o1", required: true, maxlength: "200", placeholder: `${i18n.schoolExamOption} 1` }), br(), + input({ type: "text", name: "o2", required: true, maxlength: "200", placeholder: `${i18n.schoolExamOption} 2` }), br(), + input({ type: "text", name: "o3", required: true, maxlength: "200", placeholder: `${i18n.schoolExamOption} 3` }), br(), + input({ type: "text", name: "o4", required: true, maxlength: "200", placeholder: `${i18n.schoolExamOption} 4` }), br(), + label(i18n.schoolExamCorrect), br, + select({ name: "correct" }, + option({ value: "0" }, `${i18n.schoolExamOption} 1`), + option({ value: "1" }, `${i18n.schoolExamOption} 2`), + option({ value: "2" }, `${i18n.schoolExamOption} 3`), + option({ value: "3" }, `${i18n.schoolExamOption} 4`) + ), br(), br(), + button({ type: "submit" }, i18n.schoolExamAddQuestion) + ) + ) + : null + })(), + course.students.length + ? div({ class: "school-panel-box", id: "school-cert" }, + h3(i18n.schoolIssueCertificate), + form({ method: "POST", action: `/school/certificate/${encodeURIComponent(course.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + input({ type: "text", name: "student", required: true, list: "school-cert-students", placeholder: "@…", maxlength: "120", value: (() => { + const id = safeText(certStudent) + if (!id) return "" + const nick = String(nameCache.get(id) || "").trim() + return nick ? `${id} (${nick})` : id + })() }), + datalist({ id: "school-cert-students" }, + (() => { + const readyExams = safeArr(exams).filter(x => !x.locked && safeArr(x.questions).length) + return course.students + .filter(s => !safeArr(certificates).some(cert => cert.student === s)) + .filter(s => (Number((progress || {})[s]) || 0) >= safeArr(lessons).length) + .filter(s => readyExams.filter(x => safeArr(x.results).some(r => r.author === s && r.passed)).length >= readyExams.length) + .map(s => { + const nick = String(nameCache.get(s) || "").trim() + return option({ value: nick ? `${s} (${nick})` : s }) + }) + })() + ), br(), + textarea({ name: "text", rows: 3, maxlength: "500", placeholder: i18n.schoolCertificateTextPlaceholder }), br, + button({ type: "submit" }, i18n.schoolIssueCertificate) + ) + ) + : null, + safeArr(course.pending).length + ? div({ class: "school-pending-list" }, + p(`${i18n.schoolPendingStudents}: `), + course.pending.map(pen => + p( + userLink(pen.author), + pen.transferId ? span(" — ") : null, + pen.transferId ? a({ href: `/transfers/${encodeURIComponent(pen.transferId)}` }, i18n.schoolViewBill) : null + ) + ), + br() + ) + : null, + !isFree(course) || course.visibility === "INVITE" + ? (() => { + const granted = safeArr(course.granted) + const ungrantedStudents = safeArr(course.students).filter(st => !granted.includes(st)) + return ungrantedStudents.length + ? div({ class: "school-grant-list" }, + p(`${i18n.schoolGrantAccess}: `), + ungrantedStudents.map(st => + form({ method: "POST", action: `/school/grant/${encodeURIComponent(course.id)}` }, + input({ type: "hidden", name: "student", value: st }), + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ type: "submit", class: "filter-btn" }, `🔑 ${st.slice(0, 10)}...`) + ) + ), + br() + ) + : null + })() + : null, + null + ) + +exports.singleCourseView = async (course, lessons = [], certificates = [], params = {}) => { + const courseApproved = params.approved === true + const returnTo = `/school/course/${encodeURIComponent(course.id)}` + const isTeacher = String(course.author) === String(userId) + const isStudent = course.students.includes(userId) + const myPending = safeArr(course.pending).find(p => p.author === userId) || null + const isPaid = !isFree(course) + const canClearnet = clearnetEligible(course) + const isClearnet = canClearnet && !!course.clearnet + lessons = safeArr(lessons) + certificates = safeArr(certificates) + const myCompleted = lessons.filter(lesson => lesson.completed).length + const nextLesson = isStudent ? lessons.find(l => !l.locked && !l.completed) : null + + const courseSide = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(course.id, returnTo, { spread: params.spreads || null, author: course.author, favKind: 'school', isFavorite: course.isFavorite, returnTo, reportTitle: course.title, deleteAction: isTeacher ? contentDeleteAction('schoolCourse', course.id) : undefined }) + ), + h2({ class: "tribe-card-title" }, course.title), + renderStarRating(course.opinions, safeArr(course.opinions_inhabitants).length), + div({ class: "card-chips-row" }, + course.visibility === "INVITE" ? renderStateChip("whole", "\uD83D\uDD11", i18n.chatStatusInviteOnly) : null, + course.status === "CLOSED" + ? renderStateChip("closed", "✗", i18n.schoolClosed) + : renderStateChip("mutuals", "✓", i18n.schoolOngoing), + renderPriceChip(course), + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('school', course.title, course.id) : null), + isTeacher && canClearnet ? renderClearnetSwitch('school', course.rootId || course.id, isClearnet) : null, + courseApproved ? renderStateChip("mutuals", "🎓", i18n.schoolApproved) : null, + params.subscription && (isTeacher || isStudent) + ? ((isTeacher || params.subscription.subscribed === true) + ? renderStateChip("mutuals", "\u2709", i18n.subscriptionOn) + : renderStateChip("closed", "\u2709", i18n.subscriptionOff)) + : null + ), + renderMediaBlob(course.image, '/assets/images/default-avatar.png', { class: 'tribe-detail-image' }), + course.description ? p({ class: "tribe-side-description" }, ...renderStyledText(course.description)) : null, + table({ class: "tribe-info-table" }, + tr( + td({ class: "tribe-info-label" }, i18n.schoolCreatedAt), + td({ class: "tribe-info-value", colspan: "3" }, moment(course.createdAt).format("YYYY/MM/DD HH:mm")) + ), + course.startDate ? tr( + td({ class: "tribe-info-label" }, i18n.schoolStartDate), + td({ class: "tribe-info-value", colspan: "3" }, moment(course.startDate).format("YYYY/MM/DD HH:mm")) + ) : null, + tr( + td({ class: "tribe-info-label" }, i18n.schoolTeacher), + td({ class: "tribe-info-value", colspan: "3" }, userLink(course.author)) + ) + ), + div({ class: "tribe-card-members" }, + span({ class: "tribe-members-count" }, `${i18n.schoolStudents}: ${course.students.length}`) + ), + course.chatId && (isTeacher || isStudent || (course.visibility === "PUBLIC" && isFree(course))) + ? div({ class: "tribe-side-actions shop-visibility-row stacked-action-row" }, + span({ class: "card-label" }, `${i18n.schoolCourseChat}:`), + a({ href: `/chats/${encodeURIComponent(course.chatId)}#chat-latest`, class: "tribe-action-btn" }, i18n.chatVisitChat) + ) + : null, + isStudent && lessons.length + ? div({ class: "tribe-side-actions shop-visibility-row school-progress-side" }, + span({ class: "card-label" }, `${i18n.schoolProgress}:`), + progress({ class: "school-progress-bar", value: String(myCompleted), max: String(lessons.length) }), + span({ class: "school-progress-count" }, `${myCompleted}/${lessons.length}`) + ) + : null, + !isTeacher + ? (myPending + ? div({ class: "school-pending-block" }, + div({ class: "tribe-side-actions school-pending-row" }, + renderStateChip("closed", "⧖", i18n.schoolPendingPayment) + ), + myPending.transferId + ? div({ class: "doc-export-actions" }, + form({ method: "GET", action: `/transfers/contract/${encodeURIComponent(myPending.transferId)}`, class: "transfer-contract-form" }, + button({ type: "submit", class: "filter-btn" }, i18n.transfersExportContract) + ), + form({ method: "POST", action: `/transfers/${encodeURIComponent(myPending.transferId)}/share` }, + button({ type: "submit", class: "filter-btn" }, i18n.sharePm) + ) + ) + : null + ) + : !isStudent && course.status === "ONGOING" && (course.visibility === "PUBLIC" || course.invited.includes(userId)) + ? div({ class: "tribe-side-actions shop-visibility-row stacked-action-row" }, + span({ class: "card-label" }, `${i18n.schoolEnrollLabel}:`), + form({ method: "POST", action: `/school/enroll/${encodeURIComponent(course.id)}` }, + button({ type: "submit", class: "tribe-action-btn" }, isPaid ? `${i18n.schoolEnrollButton} (${Number(course.price).toFixed(2)} ECO)` : i18n.schoolEnrollButton) + ) + ) + : !isStudent + ? p(i18n.schoolInviteRequired) + : null) + : null, + isTeacher && course.visibility === "INVITE" && !course.inviteCode + ? div({ class: "tribe-side-actions shop-visibility-row" }, + span({ class: "card-label" }, `${i18n.schoolVisibilityInvite}: `), + form({ method: "POST", action: `/school/generate-invite/${encodeURIComponent(course.id)}` }, + button({ type: "submit", class: "tribe-action-btn" }, i18n.tribeGenerateInvite) + ) + ) + : null, + isTeacher && course.visibility === "INVITE" && course.inviteCode + ? div({ class: "tribe-side-actions school-invite-code-block" }, + div({ class: "tribe-open-invite" }, + span({ class: "card-label" }, i18n.tribeInviteCodeText), + span({ class: "tribe-open-invite-code" }, course.inviteCode), + renderInviteQrCard({ qrDataUrl: `/qr-invite-code/school/${encodeURIComponent(course.inviteCode)}` }) + ) + ) + : null, + isTeacher && course.visibility === "INVITE" + ? div({ class: "tribe-side-actions stacked-action-row school-invite-students" }, + form({ method: "POST", action: `/school/invite/${encodeURIComponent(course.id)}` }, + label({ class: "card-label" }, `${i18n.schoolInviteStudents}:`), br, + input({ type: "text", name: "students", required: true, placeholder: i18n.schoolInvitePlaceholder }), br(), + button({ type: "submit", class: "tribe-action-btn" }, i18n.schoolInviteButton) + ), + course.invited.length + ? div({ class: "school-invited-list" }, + p(`${i18n.schoolInvited}: `), + course.invited.map(s => p(userLink(s))) + ) + : null + ) + : null, + params.subscription && (isTeacher || isStudent) + ? renderSubscriptionBox({ + target: course.rootId || course.id, + scope: "school", + subscribed: params.subscription.subscribed === true, + count: params.subscription.count, + isOwner: isTeacher, + returnTo + }) + : null, + safeArr(course.tags).length + ? div({ class: "tribe-side-tags" }, safeArr(course.tags).map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`))) + : null, + isTeacher + ? div({ class: "tribe-side-actions shop-visibility-row" }, + span({ class: "card-label" }, `${i18n.schoolStatus}: `), + course.status === "CLOSED" + ? renderStateChip("closed", "✗", i18n.schoolClosed) + : renderStateChip("mutuals", "✓", i18n.schoolOngoing), + form({ method: "POST", action: `/school/status/${encodeURIComponent(course.id)}`, class: "inline-form" }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + input({ type: "hidden", name: "status", value: course.status === "CLOSED" ? "ONGOING" : "CLOSED" }), + button({ type: "submit", class: "tribe-action-btn" }, course.status === "CLOSED" ? i18n.schoolReopenCourse : i18n.schoolCloseCourse) + ) + ) + : null, + isTeacher + ? div({ class: "tribe-side-actions owner-actions" }, + form({ method: "GET", action: "/school" }, + input({ type: "hidden", name: "filter", value: "edit" }), + input({ type: "hidden", name: "courseId", value: course.id }), + button({ type: "submit", class: "tribe-action-btn" }, i18n.chatUpdate) + ) + ) + : null, + !isTeacher && (isStudent || myPending) + ? div({ class: "tribe-side-actions owner-actions" }, + form({ method: "POST", action: `/school/unenroll/${encodeURIComponent(course.id)}` }, + button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.schoolUnenrollButton) + ) + ) + : null + ) + + const exams = safeArr(params.exams) + const readyExams = exams.filter(x => !x.locked && safeArr(x.questions).length) + + const teacherProgress = isTeacher && safeArr(course.students).length + ? div({ class: "school-progress-list" }, + h2(`${i18n.schoolStudents} (${course.students.length})`), + table({ class: "school-students-table" }, + tr( + th(i18n.schoolStudents), + th(i18n.schoolLessons), + th(i18n.schoolExams), + th(i18n.schoolCertified) + ), + course.students.map(student => { + const done = Number((params.progress || {})[student]) || 0 + const passedExams = readyExams.filter(x => safeArr(x.results).some(r => r.author === student && r.passed)).length + const failedExams = readyExams.filter(x => safeArr(x.results).some(r => r.author === student && !r.passed) && !safeArr(x.results).some(r => r.author === student && r.passed)) + const cert = safeArr(certificates).find(c => c.student === student) || null + const eligible = done >= lessons.length && passedExams >= readyExams.length + return tr({ class: eligible ? "school-row-passed" : "" }, + td(userLink(student)), + td(`${done}/${lessons.length}`), + td( + readyExams.length ? `${passedExams}/${readyExams.length}` : "—", + failedExams.length + ? span({ class: "school-progress-failed", title: failedExams.map(x => x.title).join(", ") }, ` ✗${failedExams.length}`) + : null + ), + td(cert + ? a({ href: `/school/certificate/pdf/${encodeURIComponent(course.id)}/${encodeURIComponent(cert.id)}`, class: "filter-btn school-cert-pdf" }, `⬇ ${i18n.schoolCertificatePdf || "PDF"}`) + : eligible + ? a({ href: `${returnTo}?cert=${encodeURIComponent(student)}#school-cert`, class: "filter-btn school-cert-pdf" }, i18n.schoolIssueCertificate) + : span({ class: "school-progress-failed" }, "✗")) + ) + }) + ) + ) + : null + + const studentPanel = isStudent && !isTeacher + ? (() => { + const myPassed = readyExams.filter(x => x.myResult && x.myResult.passed).length + const myFailed = readyExams.filter(x => x.myResult && !x.myResult.passed) + return div({ class: "school-progress-list" }, + h2(i18n.schoolStudentPanel), + table({ class: "school-students-table" }, + tr( + th(i18n.schoolLessons), + th(i18n.schoolExams), + th(i18n.schoolCertified) + ), + tr( + td(`${myCompleted}/${lessons.length}`), + td( + readyExams.length ? `${myPassed}/${readyExams.length}` : "—", + myFailed.length + ? span({ class: "school-progress-failed", title: myFailed.map(x => x.title).join(", ") }, ` ✗${myFailed.length}`) + : null + ), + td((() => { + const cert = safeArr(certificates).find(c => c.student === userId) || null + return cert + ? a({ href: `/school/certificate/pdf/${encodeURIComponent(course.id)}/${encodeURIComponent(cert.id)}`, class: "filter-btn school-cert-pdf" }, `⬇ ${i18n.schoolCertificatePdf || "PDF"}`) + : span({ class: "school-progress-failed" }, "✗") + })()) + ) + ) + ) + })() + : null + + const examsBlock = (isTeacher || isStudent) && exams.length + ? div({ class: "school-exams" }, + h2(`${i18n.schoolExams} (${isTeacher ? exams.length : readyExams.length})`), + (isTeacher ? exams : readyExams).map(exam => + exam.locked + ? null + : div({ class: "school-lesson school-exam" }, + isTeacher + ? details({ class: "school-lesson-details" }, + summary({ class: "school-lesson-summary" }, + span({ class: "school-lesson-summary-title" }, `${exam.title} (${safeArr(exam.questions).length})`) + ), + div({ class: "school-lesson-body" }, + exam.questions.map((question, qi) => + div({ class: "school-exam-question" }, + p(`${qi + 1}. ${question.q}`), + question.options.map((opt, oi) => + p({ class: oi === question.correct ? "school-exam-correct" : "school-exam-option-row" }, `${oi === question.correct ? "✓" : "·"} ${opt}`) + ), + form({ method: "POST", action: `/school/exam/question/delete/${encodeURIComponent(course.id)}/${encodeURIComponent(exam.id)}/${encodeURIComponent(question.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.chatDelete) + ) + ) + ), + form({ method: "POST", action: `/school/exam/delete/${encodeURIComponent(course.id)}/${encodeURIComponent(exam.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.chatDelete) + ) + ) + ) + : (() => { + const res = exam.myResult + const blocked = res && (res.passed || Date.now() - Number(res.at || 0) < 24 * 60 * 60 * 1000) + const chip = res && res.passed + ? renderStateChip("mutuals", "✓", i18n.schoolExamPassed) + : blocked + ? renderStateChip("closed", "✗", i18n.schoolExamFailed) + : span({ class: "state-chip" }, i18n.schoolExamPending) + return details({ class: "school-lesson-details" }, + summary({ class: "school-lesson-summary" }, + span({ class: "school-lesson-summary-title" }, `${exam.title} (${safeArr(exam.questions).length})`), + chip + ), + div({ class: "school-lesson-body" }, + blocked + ? div( + p(`${i18n.schoolExamScore}: ${res.score}/10 — ${res.passed ? i18n.schoolExamPassed : i18n.schoolExamFailed}`), + !res.passed ? p({ class: "school-exam-retry" }, i18n.schoolExamRetryLater) : null + ) + : form({ method: "POST", action: `/school/exam/take/${encodeURIComponent(course.id)}/${encodeURIComponent(exam.id)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + exam.questions.map((question, qi) => + div({ class: "school-exam-question" }, + p(question.q), + question.options.map((opt, oi) => + label({ class: "school-exam-option" }, + input({ type: "radio", name: `q${qi}`, value: String(oi), required: true }), + ` ${opt}` + ) + ) + ) + ), + button({ type: "submit" }, i18n.schoolExamSubmit) + ) + ) + ) + })() + ) + ), + null + ) + : null + + const lessonItem = (lesson) => renderLesson(lesson, course, isTeacher, returnTo, isStudent, nextLesson && lesson.id === nextLesson.id) + const unitGroups = (() => { + const groups = new Map() + for (const lesson of lessons) { + const unit = safeText(lesson.unit) + if (!groups.has(unit)) groups.set(unit, []) + groups.get(unit).push(lesson) + } + return [...groups.entries()] + })() + + const lessonsBlock = div({ class: "school-lessons" }, + h2(`${i18n.schoolLessons} (${lessons.length})`), + isTeacher || isStudent || isFree(course) + ? (lessons.length + ? unitGroups.map(([unit, items]) => + unit + ? details({ class: "school-unit", ...(isTeacher ? {} : { open: true }) }, + summary({ class: "school-unit-summary" }, `${unit} (${items.length})`), + items.map(lessonItem) + ) + : items.map(lessonItem) + ) + : p(i18n.schoolNoLessons)) + : p(i18n.schoolEnrollToSee) + ) + + return template( + course.title, + section(div({ class: "tags-header module-header-line" }, h2(i18n.schoolTitle), p(i18n.schoolDescription), renderWalletChip())), + section(renderModeButtons("all", false, (params && params.modesAvail) || null)), + section( + div({ class: "tribe-details" }, + courseSide, + div({ class: "tribe-main" }, + lessonsBlock, + examsBlock, + teacherProgress, + studentPanel, + isTeacher ? renderTeacherPanel(course, certificates, returnTo, lessons, exams, params.progress, params.certStudent) : null, + renderEngagement(course.id, + isStudent && !isTeacher + ? renderOpinionsVoting('/school/opinions', course.id, course.opinions, returnTo, course.opinions_inhabitants) + : null, + null + ) + ) + ) + ) + ) +} + +exports.clearnetCourseView = async (course, lessons = []) => { + const { escapeHtml: esc, blobUrl: cnBlob, renderRichText, renderKindTag, renderClearnetPage } = require("./clearnet_view") + const title = esc(course.title || i18n.cnKindCourse) + const desc = renderRichText(course.description || "") + const courseImg = cnBlob(course.image) + const visibleLessons = safeArr(lessons).filter(lesson => !lesson.locked) + const extraCss = ` +.cn-course-title{color:var(--fg);margin:0 0 16px 0;font-size:32px;font-weight:700} +.cn-course-meta{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px} +.cn-course-meta-item{background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;padding:8px 14px;font-size:14px;color:var(--fg-soft);display:inline-flex;align-items:center;gap:6px} +.cn-course-img{display:block;max-width:100%;border:1px solid var(--border);border-radius:8px;margin-bottom:20px} +.cn-course-section h2{color:var(--fg);font-size:18px;text-transform:uppercase;letter-spacing:2px;margin:24px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)} +.cn-course-section p{color:var(--fg-soft);white-space:pre-wrap;line-height:1.6;font-size:15px} +.cn-lesson{background:var(--bg-sub);border:1px solid var(--border);border-radius:8px;padding:14px 18px;margin-bottom:12px} +.cn-lesson h3{color:var(--fg);margin:0 0 8px 0;font-size:16px} +.cn-lesson p{color:var(--fg-soft);white-space:pre-wrap;line-height:1.6;font-size:14px;margin:0} +.cn-lesson-date{color:var(--fg-dim);font-size:12px;margin-top:8px} +` + const body = ` +

${title}

+
+ ${renderKindTag('course')} + 🎓 ${esc(i18n.cnOpenCourse)} + ${course.startDate ? `📅 ${esc(new Date(course.startDate).toISOString().slice(0, 10))}` : ""} + 👥 ${safeArr(course.students).length} ${esc(i18n.schoolStudents)} +
+
+ ${courseImg ? `${title}` : ""} + ${desc ? `

${esc(i18n.description)}

${desc}

` : ""} + ${visibleLessons.length ? `

${esc(i18n.schoolLessons)} (${visibleLessons.length})

${visibleLessons.map(lesson => ` +
+

${esc(lesson.title || "")}

+

${esc(lesson.text || "")}

+ ${lesson.sessionDate ? `
📅 ${esc(new Date(lesson.sessionDate).toISOString().slice(0, 10))}
` : ""} +
`).join("")}
` : ""} +` + return renderClearnetPage({ + title: `${course.title || "Course"} | Oasis`, + ogTitle: course.title || "Course", + ogDescription: course.description || "", + ogImage: courseImg, + extraCss, + body, + hubFeedId: course.author || null + }) +} + +const renderMaterial = (material, course, isTeacher) => { + const { img, video, audio } = require("../server/node_modules/hyperaxe") + const m = String(material.media || "") + const ref = m.match(/\[(image|audio|video|pdf|torrent):([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)/) || m.match(/^!?\[([^\]]*)\]\(\s*(&[^)\s]+\.sha256)\s*\)$/m) + let body + if (material.locked) body = p(i18n.schoolLessonLocked) + else if (ref && ref.length === 4) { + const kind = ref[1], name = ref[2], blobId = ref[3] + const url = `/blob/${encodeURIComponent(blobId)}` + if (kind === "pdf") body = div({ class: "pdf-viewer-container", id: `pdf-${material.id.slice(1, 9)}`, "data-pdf-url": url }, a({ href: url }, `📎 ${name || "PDF"}`)) + else if (kind === "image") body = img({ src: url, alt: name, class: "school-material-image" }) + else if (kind === "video") body = video({ controls: true, src: url, class: "post-video", preload: 'metadata' }) + else if (kind === "audio") body = audio({ controls: true, src: url }) + else body = p(a({ href: url }, `📎 ${name || blobId}`)) + } else if (ref) { + body = p(a({ href: `/blob/${encodeURIComponent(ref[2])}` }, `📎 ${ref[1] || ref[2]}`)) + } else { + body = renderMd(m) + } + return div({ class: "school-material" }, + body, + material.caption ? p({ class: "school-material-caption" }, material.caption) : null, + isTeacher + ? form({ method: "POST", action: `/school/lesson/media/delete/${encodeURIComponent(course.id)}/${encodeURIComponent(material.lessonId)}/${encodeURIComponent(material.id)}` }, + button({ type: "submit", class: "tribe-action-btn danger-btn" }, "DELETE") + ) + : null + ) +} + +exports.singleLessonView = async (course, lesson, materials = [], params = {}) => { + const isTeacher = String(course.author) === String(userId) + const isStudent = course.students.includes(userId) + const courseUrl = `/school/course/${encodeURIComponent(course.id)}` + const lessonUrl = `/school/lesson/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}` + + const lessonSide = div({ class: "tribe-side" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(lesson.id, lessonUrl, { + author: lesson.author || course.author, + reportTitle: lesson.title || course.title, + returnTo: courseUrl, + deleteAction: String(lesson.author || course.author) === String(userId) ? `/school/lesson/delete/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}` : undefined + }) + ), + h2({ class: "tribe-card-title" }, a({ href: courseUrl }, course.title)), + renderCourseChips(course), + renderMediaBlob(course.image, '/assets/images/default-avatar.png', { class: 'tribe-detail-image' }), + table({ class: "tribe-info-table" }, + lesson.unit ? tr( + td({ class: "tribe-info-label" }, i18n.schoolLessonUnit), + td({ class: "tribe-info-value", colspan: "3" }, lesson.unit) + ) : null, + lesson.sessionDate ? tr( + td({ class: "tribe-info-label" }, i18n.schoolSessionDate), + td({ class: "tribe-info-value", colspan: "3" }, moment(lesson.sessionDate).format("YYYY/MM/DD HH:mm")) + ) : null, + tr( + td({ class: "tribe-info-label" }, i18n.schoolTeacher), + td({ class: "tribe-info-value", colspan: "3" }, userLink(course.author)) + ) + ), + isStudent + ? div({ class: "tribe-side-actions" }, + form({ method: "POST", action: `/school/lesson/complete/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}` }, + input({ type: "hidden", name: "value", value: lesson.completed ? "false" : "true" }), + input({ type: "hidden", name: "returnTo", value: lessonUrl }), + button({ type: "submit", class: "tribe-action-btn" }, lesson.completed ? `✓ ${i18n.schoolProgressDone}` : i18n.schoolProgressMark) + ) + ) + : null, + isTeacher + ? div({ class: "tribe-side-actions owner-actions" }, + form({ method: "GET", action: lessonUrl }, + input({ type: "hidden", name: "edit", value: "1" }), + button({ type: "submit", class: "tribe-action-btn" }, i18n.chatUpdate) + ) + ) + : null + ) + + const lessonMain = div({ class: "tribe-main" }, + params.edit && isTeacher + ? div({ class: "create-tribe-form" }, + h2(i18n.chatUpdate), + form({ method: "POST", action: `/school/lesson/update/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}` }, + label(i18n.title), br, + input({ type: "text", name: "title", maxlength: "120", required: true, value: lesson.title || "" }), br(), + textarea({ name: "text", rows: 5, required: true, maxlength: "4000" }, lesson.text || ""), br, + label(i18n.schoolLessonUnit), br, + input({ type: "text", name: "unit", maxlength: "60", value: lesson.unit || "" }), br(), + label(i18n.schoolLessonOrder), br, + input({ type: "number", name: "order", min: "0", step: "1", value: lesson.order != null ? String(lesson.order) : "" }), br(), br(), + label(i18n.schoolSessionDate), br, + input({ type: "datetime-local", name: "sessionDate", min: moment().format("YYYY-MM-DDTHH:mm"), value: lesson.sessionDate ? moment(lesson.sessionDate).format("YYYY-MM-DDTHH:mm") : "" }), br(), br(), + button({ type: "submit" }, i18n.chatUpdate) + ) + ) + : div({ class: "school-lesson" }, + h2(lesson.title), + renderMd(lesson.text) + ), + div({ class: "school-materials" }, + h2(`${i18n.schoolMaterials} (${safeArr(materials).length})`), + safeArr(materials).length + ? materials.map(material => renderMaterial(material, course, isTeacher)) + : p(i18n.schoolNoMaterials) + ), + isTeacher + ? div({ class: "create-tribe-form" }, + h2(i18n.schoolAddMaterial), + form({ method: "POST", action: `/school/lesson/media/${encodeURIComponent(course.id)}/${encodeURIComponent(lesson.id)}`, enctype: "multipart/form-data" }, + label(i18n.blogImage), br, + input({ type: "file", name: "files", multiple: true, accept: "image/*,video/*,audio/*,application/pdf,.md,.txt,.epub,.zip" }), br(), br(), + label(i18n.title), br, + input({ type: "text", name: "caption", maxlength: "120" }), br(), br(), + label(i18n.description), br, + textarea({ name: "text", rows: 3, maxlength: "2000", placeholder: i18n.schoolMaterialTextPlaceholder }), br, + button({ type: "submit" }, i18n.schoolAddMaterial) + ) + ) + : null + ) + + const tpl = template( + lesson.title, + section(div({ class: "tags-header module-header-line" }, h2(i18n.schoolTitle), p(i18n.schoolDescription), renderWalletChip())), + section(renderModeButtons("all", false, (params && params.modesAvail) || null)), + section(div({ class: "tribe-details" }, lessonSide, lessonMain)) + ) + const hasPdf = safeArr(materials).some(m => !m.locked && /\[pdf:/.test(String(m.media || ""))) + return hasPdf ? `${tpl}` : tpl +} + +exports.schoolInvitePage = (code) => { + return template( + i18n.schoolTitle, + section( + div({ class: "invite-page" }, + h2(i18n.tribeInviteCodeText, " ", code), + form({ method: "GET", action: "/school" }, + button({ type: "submit", class: "filter-btn" }, i18n.walletBack) + ) + ) + ) + ) +} diff --git a/src/views/search_view.js b/src/views/search_view.js index af63a40..b7743f6 100644 --- a/src/views/search_view.js +++ b/src/views/search_view.js @@ -1,16 +1,14 @@ const { form, button, div, h2, p, section, input, label, select, option, img, audio: audioHyperaxe, video: videoHyperaxe, table, hr, hd, br, td, tr, th, a, span } = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderContentActions } = require('./main_views'); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const { template, i18n, userLink, renderContentActions, CONTENT_FAV_KIND, CONTENT_SPREADABLE, CONTENT_TORRENTABLE, paged, currentPerPage, LIST_PAGE_SIZES, pageSizeLabel } = require('./main_views'); const moment = require("../server/node_modules/moment"); -const { renderTextWithStyles } = require('../backend/renderTextWithStyles'); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledHtml, safeExternalHref } = require('../backend/renderStyledText'); +const { renderStyledText } = require('../backend/renderStyledText'); const { sanitizeHtml } = require('../backend/sanitizeHtml'); const { renderZoomableImage } = require('./gallery_view'); const { config } = require("../server/SSB_server.js"); const userId = config.keys.id; -const decodeMaybe = (s) => { - try { return decodeURIComponent(String(s || '')); } catch { return String(s || ''); } -}; const industryStatusLabel = (status) => { const s = String(status || 'ACTIVE').toUpperCase(); @@ -19,16 +17,8 @@ const industryStatusLabel = (status) => { return i18n.industryStatusActive || 'WORKING'; }; -const rewriteHashtagLinks = (html) => { - const s = String(html || ''); - return s.replace(/href=(["'])(?:https?:\/\/[^"']+)?\/hashtag\/([^"'?#]+)([^"']*)\1/g, (m, q, tag, rest) => { - const t = decodeMaybe(tag).replace(/^#/, '').trim().toLowerCase(); - const href = `/search?query=%23${encodeURIComponent(t)}`; - return `href=${q}${href}${q}`; - }); -}; -const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = [], hashtag = null, results = {}, resultCount = "10" }) => { +const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = [], hashtag = null, results = {}, spreadMap = null, favIndex = null }) => { const searchInput = input({ id: "search_query", name: "query", @@ -41,8 +31,8 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = const contentTypes = [ "post", "about", "curriculum", "tribe", "market", "transfer", "feed", "votes", - "report", "task", "event", "bookmark", "image", "audio", "video", "document", "torrent", - "bankWallet", "bankClaim", "project", "job", "industry", "industryBlueprint", "forum", "vote", "contact", "map", "shop", "shopProduct", "chat", "pad", "all" + "report", "task", "event", "bookmark", "image", "audio", "video", "document", "torrent", "file", + "bankWallet", "bankClaim", "project", "job", "industry", "industryBlueprint", "forum", "vote", "contact", "map", "shop", "shopProduct", "chat", "pad", "room", "schoolCourse", "wikiPage", "emergency", "mailingList", "logisticsRoute", "podcast", "podcastEpisode", "campaign", "all" ]; const filterSelect = select( @@ -59,16 +49,14 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = ) ); + const resultCount = currentPerPage(); const resultsPerPageSelect = select( { id: "results-per-page", - name: "resultsPerPage", + name: "perPage", class: "input-select search-select" }, - option({ value: "100", selected: resultCount === "100" ? "selected" : undefined }, "100"), - option({ value: "50", selected: resultCount === "50" ? "selected" : undefined }, "50"), - option({ value: "10", selected: resultCount === "10" ? "selected" : undefined }, "10"), - option({ value: "all", selected: resultCount === "all" ? "selected" : undefined }, i18n.allTypesLabel) + LIST_PAGE_SIZES.map(s => option({ value: s, selected: resultCount === s ? "selected" : undefined }, pageSizeLabel(s))) ); const getViewDetailsActionForSearch = (type, contentId, content) => { @@ -76,8 +64,10 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = case 'votes': return `/votes/${encodeURIComponent(contentId)}`; case 'transfer': return `/transfers/${encodeURIComponent(contentId)}`; case 'tribe': return `/tribe/${encodeURIComponent(contentId)}`; - case 'about': return content && (content.about || content.author) ? `/inhabitant/${encodeURIComponent(content.about || content.author)}` : '#'; - case 'curriculum': return content && content.author ? `/inhabitant/${encodeURIComponent(content.author)}` : '#'; + case 'about': return content && (content.about || content.author) ? `/inhabitant/${encodeURIComponent(content.about || content.author)}` : null; + case 'curriculum': return content && content.author ? `/inhabitant/${encodeURIComponent(content.author)}` : null; + case 'feed': return `/feed/${encodeURIComponent(contentId)}`; + case 'poll': return `/polls/${encodeURIComponent(contentId)}`; case 'image': return `/images/${encodeURIComponent(contentId)}`; case 'audio': return `/audios/${encodeURIComponent(contentId)}`; case 'video': return `/videos/${encodeURIComponent(contentId)}`; @@ -89,14 +79,15 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = case 'market': return `/market/${encodeURIComponent(contentId)}`; case 'report': return `/reports/${encodeURIComponent(contentId)}`; case 'project': return `/projects/${encodeURIComponent(contentId)}`; + case 'schoolCourse': return `/school/course/${encodeURIComponent(contentId)}`; case 'housing': return `/housing/${encodeURIComponent(contentId)}`; case 'job': return `/jobs/${encodeURIComponent(contentId)}`; case 'industry': return `/industry/${encodeURIComponent(contentId)}`; case 'industryBlueprint': return `/industry/blueprint/${encodeURIComponent(contentId)}`; - case 'forum': return `/forum/${encodeURIComponent(contentId)}`; - case 'vote': return content && content.vote && content.vote.link ? `/thread/${encodeURIComponent(content.vote.link)}#${encodeURIComponent(content.vote.link)}` : '#'; - case 'contact': return content && content.contact ? `/author/${encodeURIComponent(content.contact)}` : '#'; - case 'pub': return '#'; + case 'forum': return content && content.root ? `/forum/${encodeURIComponent(typeof content.root === 'string' ? content.root : (content.root.key || content.root.id || ''))}#msg-${String(contentId || '').replace(/[^a-zA-Z0-9]/g, '')}` : `/forum/${encodeURIComponent(contentId)}`; + case 'vote': return content && content.vote && content.vote.link ? `/thread/${encodeURIComponent(content.vote.link)}#${encodeURIComponent(content.vote.link)}` : null; + case 'contact': return content && content.contact ? `/author/${encodeURIComponent(content.contact)}` : null; + case 'pub': return null; case 'bankWallet': return `/banking`; case 'bankClaim': return `/banking`; case 'map': return `/maps/${encodeURIComponent(contentId)}`; @@ -104,9 +95,18 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = case 'shopProduct': return `/shops/product/${encodeURIComponent(contentId)}`; case 'chat': return `/chats/${encodeURIComponent(contentId)}`; case 'pad': return `/pads/${encodeURIComponent(contentId)}`; + case 'room': return `/rooms/${encodeURIComponent(contentId)}`; + case 'wikiPage': return `/wiki/${encodeURIComponent(contentId)}`; + case 'emergency': return `/emergencies/${encodeURIComponent(contentId)}`; + case 'mailingList': return `/mailing/${encodeURIComponent(contentId)}`; + case 'logisticsRoute': return `/logistics/${encodeURIComponent(contentId)}`; + case 'podcast': return `/podcasts/${encodeURIComponent(contentId)}`; + case 'podcastEpisode': return `/podcasts/episode/${encodeURIComponent(contentId)}`; + case 'campaign': return `/campaigns/${encodeURIComponent(contentId)}`; case 'torrent': return `/torrents/${encodeURIComponent(contentId)}`; + case 'file': return `/files/${encodeURIComponent(contentId)}`; case 'gameScore': return content && content.game ? `/games/${encodeURIComponent(content.game)}` : '/games'; - default: return '#'; + default: return null; } }; @@ -125,20 +125,28 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = case 'post': return div({ class: 'search-post' }, content.contentWarning ? h2({ class: 'card-field' }, span({ class: 'card-value' }, content.contentWarning)) : null, - content.text ? div({ class: 'card-field' }, span({ class: 'card-value', innerHTML: sanitizeHtml(rewriteHashtagLinks(renderTextWithStyles(content.text))) })) : null + content.text ? div({ class: 'card-field' }, span({ class: 'card-value', [RAW_HTML]: sanitizeHtml(renderStyledHtml(content.text)) })) : null ); case 'about': - return div({ class: 'search-about' }, - content.name ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.name + ':'), span({ class: 'card-value' }, content.name)) : null, - content.description ? div({ class: 'card-field card-field-stacked' }, span({ class: 'card-value' }, content.description)) : null, - content.image ? img({ src: `/image/64/${encodeURIComponent(content.image)}` }) : null + return div({ class: 'card-section about' }, + div({ class: 'about-card-cols' }, + div({ class: 'about-card-col-img' }, + content.image + ? renderZoomableImage(`/blob/${encodeURIComponent(content.image)}`, { imgClass: 'activity-avatar', alt: content.name || '', linkClass: 'zoom-inline' }) + : img({ src: '/assets/images/default-avatar.png', alt: content.name || '', class: 'activity-avatar' }) + ), + div({ class: 'about-card-col-desc' }, + h2({ class: 'about-card-header' }, userLink(content.about, content.name)), + content.description ? p({ class: 'tribe-side-description' }, ...renderStyledText(String(content.description))) : null + ) + ) ); case 'feed': { const rawText = typeof content.text === 'string' ? content.text.trim() : ''; - const htmlText = rawText ? rewriteHashtagLinks(renderTextWithStyles(rawText)) : ''; + const htmlText = rawText ? renderStyledHtml(rawText) : ''; const refeedsNum = Number(content.refeeds || 0) || 0; return div({ class: 'search-feed' }, - rawText ? div({ class: 'card-field' }, span({ class: 'card-value', innerHTML: sanitizeHtml(htmlText) })) : null, + rawText ? div({ class: 'card-field' }, span({ class: 'card-value', [RAW_HTML]: sanitizeHtml(htmlText) })) : null, refeedsNum > 0 ? h2({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeFeedRefeeds + ':'), @@ -155,11 +163,11 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventTitleLabel + ':'), span({ class: 'card-value' }, content.title)) : null, cleanEvDesc ? div({ class: 'card-field' }, span({ class: 'card-value' }, cleanEvDesc)) : null, blobImg(content.image || blobInEvDesc), - content.date ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventDate + ':'), span({ class: 'card-value' }, new Date(content.date).toLocaleString())) : null, + content.date ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventDate + ':'), span({ class: 'card-value' }, moment(content.date).format("YYYY/MM/DD HH:mm"))) : null, content.location ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventLocation + ':'), span({ class: 'card-value' }, content.location)) : null, content.isPublic ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventPrivacyLabel + ':'), span({ class: 'card-value' }, content.isPublic)) : null, content.status ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventStatus + ':'), span({ class: 'card-value' }, content.status)) : null, - content.eventUrl ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventUrlLabel + ':'), span({ class: 'card-value' }, a({ href: content.eventUrl, target: '_blank' }, content.eventUrl))) : null, + content.eventUrl ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventUrlLabel + ':'), span({ class: 'card-value' }, a({ href: safeExternalHref(content.eventUrl), target: '_blank', rel: 'noopener noreferrer' }, content.eventUrl))) : null, content.price ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.eventPrice + ':'), span({ class: 'card-value' }, content.price)) : null, content.tags && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => @@ -184,7 +192,7 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = ) : null, content.deadline ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteDeadline + ':' ), - span({ class: 'card-value' }, content.deadline ? new Date(content.deadline).toLocaleString() : '') + span({ class: 'card-value' }, content.deadline ? moment(content.deadline).format("YYYY/MM/DD HH:mm") : '') ) : null, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteTotalVotes + ':' ), @@ -201,9 +209,9 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = case 'tribe': return div({ class: 'search-tribe' }, content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.title + ':'), span({ class: 'card-value' }, content.title)) : null, - (() => { const s = String(content.image || '').trim().replace(/&/g, '&'); const m = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); const src = m ? m[1] : s; return src.startsWith('&') ? img({ src: `/blob/${encodeURIComponent(src)}`, class: 'feed-image' }) : img({ src: '/assets/images/default-tribe.png', class: 'feed-image' }); })(), - content.description ? div({ class: 'card-field card-field-stacked' }, span({ class: 'card-value' }, ...renderUrl(content.description))) : null, - content.location ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeLocationLabel + ':'), span({ class: 'card-value' }, ...renderUrl(content.location))) : null, + (() => { const s = String(content.image || '').trim().replace(/&/g, '&'); const m = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); const src = m ? m[1] : s; return src.startsWith('&') ? img({ loading: 'lazy', src: `/blob/${encodeURIComponent(src)}`, class: 'feed-image' }) : img({ src: '/assets/images/default-tribe.png', class: 'feed-image' }); })(), + content.description ? div({ class: 'card-field card-field-stacked' }, span({ class: 'card-value' }, ...renderStyledText(content.description))) : null, + content.location ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeLocationLabel + ':'), span({ class: 'card-value' }, ...renderStyledText(content.location))) : null, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeIsAnonymousLabel + ':'), span({ class: 'card-value' }, content.isAnonymous ? i18n.tribePrivate : i18n.tribePublic)), content.inviteMode ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeModeLabel + ':'), span({ class: 'card-value' }, String(content.inviteMode).toUpperCase())) : null, Array.isArray(content.members) @@ -245,9 +253,9 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = return content.url ? div({ class: 'search-video' }, content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.videoTitleLabel + ':'), span({ class: 'card-value' }, content.title)) : null, content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.description)) : null, - br(), - videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(content.url)}`, type: content.mimeType || 'video/mp4', width: '640', height: '360' }), - br(), + div({ class: 'video-container' }, + videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(content.url)}`, type: content.mimeType || 'video/mp4', width: '640', height: '360', preload: 'metadata' }) + ), content.tags && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`) @@ -272,6 +280,12 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = )) : null ); + case 'file': + return div({ class: 'search-file' }, + content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.fileTitleLabel || i18n.title) + ':'), span({ class: 'card-value' }, content.title)) : null, + content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.description)) : null, + content.size ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.fileSizeLabel || 'Size') + ':'), span({ class: 'card-value' }, String(content.size))) : null + ); case 'torrent': return div({ class: 'search-torrent' }, content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.torrentTitleLabel || i18n.title) + ':'), span({ class: 'card-value' }, content.title)) : null, @@ -289,13 +303,13 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.description)) : null, content.item_type ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemType + ':'), span({ class: 'card-value' }, content.item_type.toUpperCase())) : null, content.status ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemCondition + ':'), span({ class: 'card-value' }, content.status)) : null, - content.deadline ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemDeadline + ':'), span({ class: 'card-value' }, new Date(content.deadline).toLocaleString())) : null, + content.deadline ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemDeadline + ':'), span({ class: 'card-value' }, moment(content.deadline).format("YYYY/MM/DD HH:mm"))) : null, br(), blobImg(content.image), br(), content.seller ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemSeller + ':'), span({ class: 'card-value' }, userLink(content.seller))) : null, content.stock ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemStock + ':'), span({ class: 'card-value' }, content.stock || 'N/A')) : null, - content.price ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.searchPriceLabel + ':'), span({ class: 'card-value' }, `${content.price} ECO`)) : null, + content.price ? div({ class: 'price-chip' }, `${content.price} ECO`) : null, content.condition ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.condition)) : null, content.includesShipping ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemIncludesShipping + ':'), span({ class: 'card-value' }, `${content.includesShipping ? i18n.YESLabel : i18n.NOLabel}`)) : null, content.auctions_poll && content.auctions_poll.length > 0 @@ -326,9 +340,9 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = ); case 'bookmark': return div({ class: 'search-bookmark' }, - content.url ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkUrlLabel + ':'), span({ class: 'card-value' }, a({ href: content.url, target: '_blank' }, content.url))) : null, + content.url ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkUrlLabel + ':'), span({ class: 'card-value' }, a({ href: safeExternalHref(content.url), target: '_blank', rel: 'noopener noreferrer' }, content.url))) : null, content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.description)) : null, - content.lastVisit ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkLastVisit + ':'), span({ class: 'card-value' }, new Date(content.lastVisit).toLocaleString())) : null, + content.lastVisit ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkLastVisit + ':'), span({ class: 'card-value' }, moment(content.lastVisit).format("YYYY/MM/DD HH:mm"))) : null, content.tags && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`) @@ -343,8 +357,8 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = content.status ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.searchStatusLabel + ':'), span({ class: 'card-value' }, content.status)) : null, content.priority ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.searchPriorityLabel + ':'), span({ class: 'card-value' }, content.priority)) : null, typeof content.isPublic === 'boolean' ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.searchIsPublicLabel + ':'), span({ class: 'card-value' }, content.isPublic ? i18n.YESLabel : i18n.NOLabel)) : null, - content.startTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.taskStartTimeLabel + ':'), span({ class: 'card-value' }, new Date(content.startTime).toLocaleString())) : null, - content.endTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.taskEndTimeLabel + ':'), span({ class: 'card-value' }, new Date(content.endTime).toLocaleString())) : null, + content.startTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.taskStartTimeLabel + ':'), span({ class: 'card-value' }, moment(content.startTime).format("YYYY/MM/DD HH:mm"))) : null, + content.endTime ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.taskEndTimeLabel + ':'), span({ class: 'card-value' }, moment(content.endTime).format("YYYY/MM/DD HH:mm"))) : null, Array.isArray(content.assignees) ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.taskAssignees + ':'), span({ class: 'card-value' }, content.assignees.length)) : null, content.tags && content.tags.length ? div({ class: 'card-tags' }, content.tags.map(tag => @@ -377,7 +391,7 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = content.amount ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.transfersAmount + ':'), span({ class: 'card-value' }, content.amount)) : null, br(), content.from ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.transfersFrom + ':'), span({ class: 'card-value' }, userLink(content.from))) : null, - content.to ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.transfersTo + ':'), span({ class: 'card-value' }, userLink(content.to))) : null, + content.to && process.env.OASIS_MOBILE !== '1' ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.transfersTo + ':'), span({ class: 'card-value' }, userLink(content.to))) : null, br(), content.confirmedBy && content.confirmedBy.length ? h2({ class: 'card-field' }, span({ class: 'card-label' }, i18n.transfersConfirmations + ':'), span({ class: 'card-value' }, content.confirmedBy.length)) @@ -442,7 +456,18 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = ) : null, content.txid ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bankTx + ':' ), - a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${content.txid}`, target: '_blank' }, content.txid) + span({ class: 'card-value bank-address-code' }, content.txid) + ) : null + ); + case 'schoolCourse': + return div({ class: 'search-school' }, + content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.title + ':'), span({ class: 'card-value' }, content.title)) : null, + div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.schoolCourseType || 'Course type') + ':'), span({ class: 'card-value' }, String(content.visibility || '').toUpperCase() === 'INVITE' ? 'INVITE-ONLY' : (Number(content.price) > 0 ? 'PAID' : 'OPEN'))), + Number(content.price) > 0 ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.schoolPrice || 'Price (ECO)') + ':'), span({ class: 'card-value' }, `${Number(content.price).toFixed(2)} ECO`)) : null, + content.startDate ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.schoolStartDate || 'Start date') + ':'), span({ class: 'card-value' }, moment(content.startDate).format("YYYY/MM/DD HH:mm"))) : null, + content.description ? div({ class: 'card-field card-field-stacked' }, span({ class: 'card-value' }, String(content.description).length > 220 ? String(content.description).slice(0, 220) + '…' : content.description)) : null, + Array.isArray(content.tags) && content.tags.length ? div({ class: 'card-tags' }, + content.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`)) ) : null ); case 'housing': @@ -510,7 +535,7 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.title + ':'), span({ class: 'card-value' }, content.title)) : null, blobImg(content.image), content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.description)) : null, - content.price ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.searchPriceLabel || 'PRICE') + ':'), span({ class: 'card-value' }, `${content.price} ECO`)) : null, + content.price ? div({ class: 'price-chip' }, `${content.price} ECO`) : null, content.stock !== undefined ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemStock + ':'), span({ class: 'card-value' }, content.stock)) : null ); case 'chat': { @@ -538,6 +563,11 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = content.deadline ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.padDeadlineLabel || 'Deadline') + ':'), span({ class: 'card-value' }, content.deadline)) : null ); } + case 'room': + return div({ class: 'search-room' }, + content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.roomsTitle || 'Rooms') + ':'), span({ class: 'card-value' }, content.title)) : null, + content.description ? div({ class: 'card-field' }, span({ class: 'card-value' }, content.description)) : null + ); case 'gameScore': return div({ class: 'search-game' }, content.game ? div({ class: 'game-row' }, @@ -562,17 +592,19 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = : null ); default: - return div({ class: 'styled-text', innerHTML: sanitizeHtml(renderTextWithStyles(content.text || content.description || content.title || '[no content]')) }); + return div({ class: 'styled-text', [RAW_HTML]: sanitizeHtml(renderStyledHtml(content.text || content.description || content.title || '[no content]')) }); } }; + const pageGroups = paged(Object.entries(results).flatMap(([key, msgs]) => msgs.map((msg) => [key, msg]))) + .reduce((acc, [key, msg]) => { (acc[key] = acc[key] || []).push(msg); return acc; }, {}); const resultSection = Object.entries(results).length > 0 - ? Object.entries(results).map(([key, msgs]) => + ? Object.entries(pageGroups).map(([key, msgs]) => div( { class: "search-result-group" }, ...msgs.map((msg) => { const content = msg.value.content || {}; - const created = new Date(msg.timestamp).toLocaleString(); + const created = moment(msg.timestamp).format("YYYY/MM/DD HH:mm"); if (content.type === 'document') hasDocument = true; const contentHtml = renderContentHtml(content); let author; @@ -613,13 +645,23 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = span({ class: 'pm-exposition-text' }, String(content.type || '').toUpperCase()) ) ), - renderContentActions(contentId, getViewDetailsActionForSearch(content.type, contentId, content)) + renderContentActions(contentId, getViewDetailsActionForSearch(content.type, contentId, content), { + author: msg.value.author, + reportTitle: content.title || content.question || content.concept || '', + spread: CONTENT_SPREADABLE.has(content.type) ? ((spreadMap instanceof Map && spreadMap.get(contentId)) || null) : undefined, + ...(CONTENT_TORRENTABLE.has(content.type) && typeof content.url === 'string' && content.url.startsWith('&') + ? { torrentFrom: { blobId: content.url, name: content.title || content.fileName || '' } } + : {}), + ...(CONTENT_FAV_KIND[content.type] + ? { favKind: CONTENT_FAV_KIND[content.type], isFavorite: favIndex instanceof Map ? favIndex.get(String(contentId)) === CONTENT_FAV_KIND[content.type] : false, returnTo: `/search?query=${encodeURIComponent(hashtag ? `#${hashtag}` : String(query || ''))}` } + : {}) + }) ), div({ class: 'card-section search-card-body' }, contentHtml, author ? p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${created} ${i18n.performed} `), + span({ class: 'date-link' }, `${created}`), (authorUrl && authorUrl !== '#' && authorUrl.startsWith('/author/')) ? userLink(decodeURIComponent(authorUrl.replace(/^\/author\//, ''))) : a({ href: authorUrl, class: 'user-link' }, `${author}`) @@ -629,17 +671,19 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = }) ) ) - : div({ class: 'no-results' }, p(i18n.noResultsFound)); + : (String(query || '').trim() || hashtag + ? div({ class: 'no-results' }, p(i18n.noResultsFound)) + : null); let html = template( hashtag ? `#${hashtag}` : i18n.searchTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(hashtag ? `#${hashtag}` : i18n.searchTitle), p(hashtag ? i18n.hashtagDescription : i18n.searchDescriptionLabel) ), form( - { action: "/search", method: "POST", class: "search-form" }, + { action: "/search", method: "GET", class: "search-form" }, div({ class: "search-filters-row" }, table({ class: "search-filters-table" }, tr( @@ -676,8 +720,7 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types = if (hasDocument) { html += ` - - + `; } diff --git a/src/views/settings_view.js b/src/views/settings_view.js index ef5e69e..24ebbd9 100644 --- a/src/views/settings_view.js +++ b/src/views/settings_view.js @@ -1,17 +1,27 @@ -const { form, button, div, h2, h3, p, section, select, option, input, br, a, label, span, img } = require("../server/node_modules/hyperaxe"); +const { form, button, div, h2, h3, p, section, select, option, input, br, a, label, span, img, strong, table, tr, td, ul, li } = require("../server/node_modules/hyperaxe"); const fs = require('fs'); const path = require('path'); const { getConfig } = require('../configs/config-manager.js'); -const { template, selectedLanguage, i18n, setLanguage } = require('./main_views'); +const { template, selectedLanguage, i18n, setLanguage, INBOX_BOT_ORDER, inboxBotLabel, THEMES } = require('./main_views'); const i18nBase = require("../client/assets/translations/i18n"); const { WORKFLOWS, currentWorkflow } = require('../models/workflows_model'); const html = require("../server/node_modules/hyperaxe"); // selector de identidades: la implementacion vive en ./identities_view.js const { renderIdentities } = require('./identities_view')({ i18n, html }); +const { renderVerificationReport, renderRebuildReport } = require('./backup_view'); const snhUrl = "https://wiki.solarnethub.com/socialnet/overview"; +const BLOB_CACHE_OPTIONS = [512, 1024, 2048, 5120, 10240, 0]; +const sizeLabel = (bytes) => { + let value = Number(bytes) || 0; + const units = ['B', 'KB', 'MB', 'GB']; + let unit = 0; + while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1; } + return `${unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`; +}; +const blobCacheOptionLabel = (mb) => mb === 0 ? i18n.settingsBlobCacheUnlimited : (mb >= 1024 ? `${mb / 1024} GB` : `${mb} MB`); -const themeFilePath = path.join(__dirname, '../configs/oasis-config.json'); +const themeFilePath = require('../configs/config-manager').configFilePath; const getThemeConfig = () => { try { const configData = fs.readFileSync(themeFilePath); @@ -22,31 +32,28 @@ const getThemeConfig = () => { } }; -const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) => { +const settingsView = (params) => { + const { version, aiPrompt, aiExportCount = 0, blobCache = null, fediverseAccount, telegramAccount = null, telegramLogin = null, peertubeAccount = null, nextcloudAccount = null, verification = null, rebuild = null } = params; const currentThemeConfig = getThemeConfig(); const theme = currentThemeConfig.themes?.current || "Dark-SNH"; const currentConfig = getConfig(); let serverConfig = {}; - try { serverConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '../configs/server-config.json'), 'utf8')); } catch (_) {} + try { serverConfig = require('../configs/config-manager.js').readServerConfig(); } catch (_) {} const currentHops = (serverConfig.friends && Number.isFinite(serverConfig.friends.hops)) ? serverConfig.friends.hops : 2; const walletUrl = currentConfig.wallet.url; const walletUser = currentConfig.wallet.user; const walletFee = currentConfig.wallet.fee; - const currentWish = currentConfig.wish === 'mutuals' ? 'mutuals' : 'whole'; + const currentWish = ['mutuals', 'only-lan', 'local'].includes(currentConfig.wish) ? currentConfig.wish : 'whole'; const currentPmVisibility = currentConfig.pmVisibility === 'mutuals' ? 'mutuals' : 'whole'; + const currentPhone = currentConfig.phone && typeof currentConfig.phone === 'object' ? currentConfig.phone : {}; - const themeElements = [ - option({ value: "Dark-SNH", ...(theme === "Dark-SNH" ? true : undefined ? { selected: true } : {})}, "Dark-SNH"), - option({ value: "Clear-SNH", ...(theme === "Clear-SNH" ? true : undefined ? { selected: true } : {})}, "Clear-SNH"), - option({ value: "Purple-SNH", ...(theme === "Purple-SNH" ? true : undefined ? { selected: true } : {})}, "Purple-SNH"), - option({ value: "Matrix-SNH", ...(theme === "Matrix-SNH" ? true : undefined ? { selected: true } : {})}, "Matrix-SNH"), - option({ value: "OasisMobile", ...(theme === "OasisMobile" ? true : undefined ? { selected: true } : {})}, "Oasis-Mobile") - ]; + const themeElements = THEMES.map(([value, name]) => option({ value, ...(theme === value ? { selected: true } : {}) }, name)); const activeWorkflow = currentWorkflow(currentConfig) || ''; const modOn = (name) => (currentConfig.modules || {})[`${name}Mod`] === 'on'; + const isMobile = process.env.OASIS_MOBILE === '1'; - const workflowSection = section( + const workflowSection = section({ id: "workflows" }, div({ class: "tags-header" }, h2(i18n.workflowsTitle), p(i18n.workflowsDescription), @@ -74,26 +81,45 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = button({ type: "submit" }, i18n.rebuildName) ); - const updateFlagPath = path.join(__dirname, '../server/.update_required'); - let updateButton = null; - if (fs.existsSync(updateFlagPath)) { - updateButton = form( - { action: "/update", method: "post" }, - button({ type: "submit" }, i18n.updateit) + const updateInfo = (() => { try { return require('../backend/updater').readUpdateInfo(); } catch (_) { return null; } })(); + let updatePanel = null; + if (updateInfo) { + const d = updateInfo.details || {}; + const day = (iso) => String(iso || '').slice(0, 10); + const versionLine = (v, c) => `${v || '?'}${c ? ` · ${c.hash} · ${day(c.date)}` : ''}`; + const row = (name, ...value) => tr(td({ class: "tribe-info-label" }, name), td({ class: "tribe-info-value" }, ...value)); + updatePanel = section({ id: "update" }, + div({ class: "torrent-download update-panel" }, + div({ class: "torrent-oasis torrent-detail-section" }, + h3({ class: "torrent-section-title" }, i18n.notifyUpdateLabel), + table({ class: "tribe-info-table torrent-file-info" }, + row(i18n.updateInstalledLabel, versionLine(updateInfo.installedVersion, d.installed)), + row(i18n.updateAvailableLabel, versionLine(updateInfo.version, d.available)), + Array.isArray(d.commits) && d.commits.length + ? row(i18n.updateChangesLabel, ul({ class: "update-changes" }, + ...d.commits.map(c => li(`${day(c.date)} · ${c.subject}`)), + d.count > d.commits.length ? li(`+${d.count - d.commits.length}`) : null)) + : null, + d.files ? row(i18n.updateFilesLabel, `${d.files} · +${d.insertions} −${d.deletions}`) : null, + d.origin ? row(i18n.updateOriginLabel, span({ class: "bank-address-code" }, d.origin)) : null + ), + isMobile + ? ((updateInfo.apk || updateInfo.page) + ? a({ href: updateInfo.apk || updateInfo.page, class: "filter-btn update-download-link" }, `\u2B07 OASIS${updateInfo.version ? ` ${updateInfo.version}` : ''}`) + : null) + : form({ action: "/update", method: "post" }, + button({ type: "submit", class: "filter-btn" }, `\u2B07 OASIS${updateInfo.version ? ` ${updateInfo.version}` : ''}`) + ) + ) + ) ); } - return template( - i18n.settings, - section( - div({ class: "tags-header" }, - h2(i18n.settings), - p(a({ href: snhUrl, target: "_blank" }, i18n.settingsIntro({ version }))), - updateButton - ) - ), + const filter = params.filter === 'multiverse' && modOn('fediverse') ? 'multiverse' : 'oasis'; + const oasisParts = [ + updatePanel, renderIdentities(require('../server/ssb_config').account), - section( + section({ id: "language" }, div({ class: "tags-header" }, h2(i18n.language), p(i18n.languageDescription), @@ -104,6 +130,8 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = languageOption("Español", "es"), languageOption("Français", "fr"), languageOption("Euskara", "eu"), + languageOption("Català", "ca"), + languageOption("Galego", "gl"), languageOption("Deutsch", "de"), languageOption("Italiano", "it"), languageOption("Português", "pt"), @@ -118,7 +146,7 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - section( + section({ id: "ux" }, div({ class: "tags-header" }, h2(i18n.uxModeTitle || "UX"), p(i18n.uxModeDescription || "Select which UX navigation mode you want for your GUI."), @@ -127,7 +155,8 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = (() => { const aiNavEnabled = currentConfig.modules && currentConfig.modules.aiNavMod === 'on'; const chatsEnabled = currentConfig.modules && currentConfig.modules.chatsMod === 'on'; - const cur = currentConfig.ux?.current === "ainav" ? "ainav" : currentConfig.ux?.current === "chats" ? "chats" : "blocks"; + const phoneEnabled = !!(currentConfig.modules && currentConfig.modules.phoneMod !== 'off'); + const cur = currentConfig.ux?.current === "ainav" ? "ainav" : currentConfig.ux?.current === "chats" ? "chats" : currentConfig.ux?.current === "feed" ? "feed" : currentConfig.ux?.current === "phone" ? "phone" : "blocks"; const uxCard = (value, title, image) => label({ class: "welcome-ux-option" }, input({ type: "radio", name: "ux", value, ...(cur === value ? { checked: true } : {}) }), img({ src: image, class: "welcome-ux-shot", alt: title }), @@ -135,16 +164,17 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ); return div({ class: "welcome-ux-grid" }, uxCard("blocks", i18n.uxModeMenus || "Blocks", "/assets/images/ux-blocks.png"), - chatsEnabled ? uxCard("chats", i18n.chatsTitle || "Chats", "/assets/images/ux-chats.png") : null, - aiNavEnabled ? uxCard("ainav", i18n.uxModeAINav || "AI", "/assets/images/ux-ainav.png") : null + aiNavEnabled ? uxCard("ainav", i18n.uxModeAINav || "AI", "/assets/images/ux-ainav.png") : null, + chatsEnabled ? uxCard("chats", i18n.uxModeChats || "Conversations", "/assets/images/ux-chats.png") : null, + uxCard("feed", i18n.uxModeFeed || "Microblogging", "/assets/images/ux-feed.png"), + phoneEnabled ? uxCard("phone", i18n.phoneTitle || "Phone", "/assets/images/ux-phone.png") : null ); })(), button({ type: "submit" }, i18n.saveSettings) ) ) ), - workflowSection, - section( + section({ id: "theme" }, div({ class: "tags-header" }, h2(i18n.theme), p(i18n.themeIntro), @@ -157,7 +187,24 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - section( + section({ id: "wish" }, + div({ class: "tags-header" }, + h2(i18n.settingsWishTitle), + p(i18n.settingsWishDesc), + form( + { action: "/settings/wish", method: "POST" }, + select({ name: "wish" }, + option({ value: "whole", ...(currentWish === "whole" ? { selected: true } : {})}, i18n.settingsWishWhole), + option({ value: "mutuals", ...(currentWish === "mutuals" ? { selected: true } : {})}, i18n.settingsWishMutuals), + option({ value: "only-lan", ...(currentWish === "only-lan" ? { selected: true } : {})}, i18n.settingsWishOnlyLan || "Only LAN"), + option({ value: "local", ...(currentWish === "local" ? { selected: true } : {})}, i18n.settingsWishLocal || "Local") + ), br(), br(), + button({ type: "submit" }, i18n.saveSettings) + ) + ) + ), + workflowSection, + section({ id: "home-page" }, div({ class: "tags-header" }, h2(i18n.homePageTitle), p(i18n.homePageDescription), @@ -185,7 +232,7 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - modOn('ai') ? section( + modOn('ai') ? section({ id: "ai" }, div({ class: "tags-header" }, h2(i18n.aiTitle), p(i18n.aiSettingsDescription), @@ -212,11 +259,12 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = span({ class: "lan-checkbox-text" }, i18n.aiSuggestionsEnable) ), br(), - button({ type: "submit" }, i18n.saveSettings) + button({ type: "submit" }, i18n.saveSettings), + Number(aiExportCount) > 0 ? button({ type: "submit", formaction: "/ai/export", attrs: { formmethod: "GET" }, class: "ai-export-btn" }, `${i18n.aiExportFineTuning} (${aiExportCount})`) : null ) ) ) : null, - section( + section({ id: "logstream" }, div({ class: "tags-header" }, h2(i18n.ssbLogStream), p(i18n.ssbLogStreamDescription), @@ -234,7 +282,24 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - section( + blobCache ? section({ id: "blobcache" }, + div({ class: "tags-header" }, + h2(i18n.settingsBlobCacheTitle), + p(i18n.settingsBlobCacheDesc), + blobCache.usage ? p({ class: "blob-cache-usage" }, strong(`${i18n.settingsBlobCacheUsage}: `), `${sizeLabel(blobCache.usage.bytes)} · ${blobCache.usage.count} ${i18n.settingsBlobCacheFiles}`) : null, + blobCache.cleaned !== null ? p({ class: "blob-cache-notice" }, String(i18n.settingsBlobCacheCleaned).replace('{n}', String(blobCache.cleaned)).replace('{size}', sizeLabel(blobCache.freed))) : null, + form( + { action: "/settings/blob-cache", method: "POST" }, + label({ for: "blob_cache_mb" }, i18n.settingsBlobCacheLimit), + br(), + select({ id: "blob_cache_mb", name: "blob_cache_mb" }, ...BLOB_CACHE_OPTIONS.map(mb => option({ value: String(mb), ...(mb === blobCache.maxMB ? { selected: true } : {}) }, blobCacheOptionLabel(mb)))), + br(), br(), + button({ type: "submit" }, i18n.saveSettings), + blobCache.maxMB > 0 ? button({ type: "submit", formaction: "/settings/blob-cache/collect", class: "blob-cache-clean-btn" }, i18n.settingsBlobCacheCleanNow) : null + ) + ) + ) : null, + section({ id: "replication" }, div({ class: "tags-header" }, h2(i18n.settingsReplicationTitle || 'Replication'), p(i18n.settingsReplicationDesc || 'Configure the number of hops your peer follows out from your own feed when replicating content.'), @@ -255,7 +320,7 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - section( + section({ id: "lan" }, div({ class: "tags-header" }, h2(i18n.settingsLanTitle || 'LAN Broadcasting'), p(i18n.settingsLanDesc || 'Periodically announce this peer to other Oasis instances on the same local network. Disable to stop UDP broadcasts.'), @@ -277,98 +342,105 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - section( - div({ class: "tags-header" }, - h2(i18n.settingsWishTitle), - p(i18n.settingsWishDesc), - form( - { action: "/settings/wish", method: "POST" }, - select({ name: "wish" }, - option({ value: "whole", ...(currentWish === "whole" ? true : undefined ? { selected: true } : {})}, i18n.settingsWishWhole), - option({ value: "mutuals", ...(currentWish === "mutuals" ? true : undefined ? { selected: true } : {})}, i18n.settingsWishMutuals), - option({ value: "only-lan", ...(currentWish === "only-lan" ? true : undefined ? { selected: true } : {})}, i18n.settingsWishOnlyLan || "Only LAN") - ), br(), br(), - button({ type: "submit" }, i18n.saveSettings) - ) - ) - ), - section( + section({ id: "pm-visibility" }, div({ class: "tags-header" }, h2(i18n.settingsPmVisibilityTitle), p(i18n.settingsPmVisibilityDesc), form( { action: "/settings/pm-visibility", method: "POST" }, select({ name: "pmVisibility" }, - option({ value: "whole", ...(currentPmVisibility === "whole" ? true : undefined ? { selected: true } : {})}, i18n.settingsPmVisibilityWhole), - option({ value: "mutuals", ...(currentPmVisibility === "mutuals" ? true : undefined ? { selected: true } : {})}, i18n.settingsPmVisibilityMutuals) + option({ value: "whole", ...(currentPmVisibility === "whole" ? { selected: true } : {})}, i18n.settingsPmVisibilityWhole), + option({ value: "mutuals", ...(currentPmVisibility === "mutuals" ? { selected: true } : {})}, i18n.settingsPmVisibilityMutuals) ), br(), br(), button({ type: "submit" }, i18n.saveSettings) ) ) ), + modOn('phone') ? section({ id: "phone" }, + div({ class: "tags-header" }, + h2(i18n.phoneTitle), + p(i18n.phoneSettingsDesc), + form( + { action: "/settings/phone", method: "POST" }, + select({ name: "visibility" }, + option({ value: "whole", ...(!currentPhone.dnd && currentPhone.visibility !== "mutuals" ? { selected: true } : {})}, i18n.settingsPmVisibilityWhole), + option({ value: "mutuals", ...(!currentPhone.dnd && currentPhone.visibility === "mutuals" ? { selected: true } : {})}, i18n.settingsPmVisibilityMutuals), + option({ value: "dnd", ...(currentPhone.dnd ? { selected: true } : {})}, i18n.phoneDnd) + ), br(), br(), + label(i18n.settingsPhoneVideo), br(), + select({ name: "video" }, + option({ value: "on", ...(currentPhone.video !== false ? { selected: true } : {}) }, i18n.settingsPhoneVideoOn), + option({ value: "off", ...(currentPhone.video === false ? { selected: true } : {}) }, i18n.settingsPhoneVideoOff) + ), br(), br(), + button({ type: "submit" }, i18n.saveSettings) + ) + ) + ) : null, + modOn('inbox') ? section({ id: "inbox-bots" }, + div({ class: "tags-header" }, + h2(i18n.settingsInboxBotsTitle), + p(i18n.settingsInboxBotsDesc), + form( + { action: "/settings/inbox-bots", method: "POST" }, + ...INBOX_BOT_ORDER.map(bot => + label({ for: `inbox-bot-${bot}`, class: "lan-checkbox-label inbox-bot-label" }, + input({ + type: "checkbox", + id: `inbox-bot-${bot}`, + name: "bots", + value: bot, + class: "lan-checkbox-input", + checked: (Array.isArray(currentConfig.inboxMutedBots) && currentConfig.inboxMutedBots.includes(bot)) ? undefined : true + }), + span({ class: "lan-checkbox-text" }, inboxBotLabel(bot)) + ) + ), + br(), + button({ type: "submit" }, i18n.saveSettings) + ) + ) + ) : null, modOn('wallet') ? section( { id: "wallet" }, div({ class: "tags-header" }, h2(i18n.wallet), p( - i18n.walletSettingsDescription, " ", - a({ href: "docs/ecoin.md", target: "_blank", rel: "noopener" }, `[${i18n.walletSettingsDocLink}]`) + isMobile ? i18n.walletSettingsMobileDescription : i18n.walletSettingsDescription ), + isMobile ? a({ href: require('../backend/updater.js').ecoinWalletDownloadUrl(), class: "update-download-link" }, i18n.walletDownloadEcoin) : null, form( { action: "/settings/wallet", method: "POST" }, - label({ for: "wallet_url" }, i18n.walletAddress), br(), - input({ type: "text", id: "wallet_url", name: "wallet_url", placeholder: walletUrl, value: walletUrl }), br(), + label({ for: "wallet_url" }, i18n.walletRpcUrl), br(), + input({ type: "text", id: "wallet_url", name: "wallet_url", placeholder: "http://localhost:7474", value: walletUrl || "http://localhost:7474" }), br(), label({ for: "wallet_user" }, i18n.walletUser), br(), - input({ type: "text", id: "wallet_user", name: "wallet_user", placeholder: walletUser, value: walletUser }), br(), + input({ type: "text", id: "wallet_user", name: "wallet_user", placeholder: "ecoinrpc", value: walletUser }), br(), label({ for: "wallet_pass" }, i18n.walletPass), br(), input({ type: "password", id: "wallet_pass", name: "wallet_pass" }), br(), label({ for: "wallet_fee" }, i18n.walletFee), br(), - input({ type: "text", id: "wallet_fee", name: "wallet_fee", placeholder: walletFee, value: walletFee }), br(), - button({ type: "submit" }, i18n.walletConfiguration) + input({ type: "text", id: "wallet_fee", name: "wallet_fee", placeholder: "5", value: walletFee || "5" }), br(), + button({ type: "submit" }, i18n.walletConfiguration), + walletUser ? " " : null, + walletUser ? button({ type: "submit", class: "delete-btn", formaction: "/settings/wallet/disconnect", attrs: { formmethod: "POST" } }, i18n.walletDisconnectButton) : null ) ) ) : null, - modOn('fediverse') ? section( - { id: "multiverse" }, + section({ id: "verification" }, div({ class: "tags-header" }, - h2(i18n.fediverseSettingsTitle), - div({ class: "fediverse-network" }, - h3("Mastodon"), - p(i18n.fediverseTokenHelp), - fediverseError ? p({ class: "fediverse-error" }, i18n[fediverseError] || i18n.fediverseError) : "", - fediverseAccount - ? (() => { - const host = String(fediverseAccount.instance || "").replace(/^https?:\/\//, ""); - const profileUrl = `${fediverseAccount.instance}/@${fediverseAccount.acct}`; - const link = (txt) => a({ href: profileUrl, target: "_blank", rel: "noopener noreferrer" }, txt); - return form( - { action: "/settings/fediverse/disconnect", method: "POST" }, - p( - `${i18n.fediverseConnectedAs}: `, - link(`${fediverseAccount.acct}@${host}`) - ), - button({ type: "submit" }, i18n.fediverseDisconnect) - ); - })() - : form( - { action: "/settings/fediverse", method: "POST" }, - label({ for: "fediverse_instance" }, i18n.fediverseInstanceLabel), br(), - input({ type: "text", id: "fediverse_instance", name: "instance", placeholder: "mastodon.social", required: true }), br(), - label({ for: "fediverse_token" }, i18n.fediverseTokenLabel), br(), - input({ type: "password", id: "fediverse_token", name: "token", autocomplete: "off", required: true }), br(), - button({ type: "submit" }, i18n.fediverseConnect) - ) - ) + h2(i18n.verificationTitle), + p(i18n.verificationDescription), + form({ action: "/settings/verify", method: "post" }, button({ type: "submit" }, i18n.verificationRun)), + verification && verification.error ? p({ class: "backup-check-bad" }, verification.error) : renderVerificationReport(verification) ) - ) : null, - section( + ), + section({ id: "indexes" }, div({ class: "tags-header" }, h2(i18n.indexes), p(i18n.indexesDescription), - rebuildButton + rebuildButton, + renderRebuildReport(rebuild) ) ), - section( + section({ id: "export" }, div({ class: "tags-header" }, h2(i18n.exportDataTitle), p(i18n.exportDataDescription), @@ -378,7 +450,7 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ), - section( + section({ id: "panic" }, div({ class: "tags-header" }, h2(i18n.panicMode), p(i18n.removeDataDescription), @@ -388,6 +460,136 @@ const settingsView = ({ version, aiPrompt, fediverseAccount, fediverseError }) = ) ) ) + ]; + const multiverseParts = modOn('fediverse') ? [ + section({ id: "mastodon" }, + div({ class: "tags-header" }, + h2("Mastodon"), + fediverseAccount + ? (() => { + const host = String(fediverseAccount.instance || "").replace(/^https?:\/\//, ""); + const profileUrl = `${fediverseAccount.instance}/@${fediverseAccount.acct}`; + const link = (txt) => a({ href: profileUrl, target: "_blank", rel: "noopener noreferrer" }, txt); + return form( + { action: "/settings/fediverse/disconnect", method: "POST" }, + p( + `${i18n.fediverseConnectedAs}: `, + link(`${fediverseAccount.acct}@${host}`) + ), + button({ type: "submit" }, i18n.fediverseDisconnect) + ); + })() + : form( + { action: "/settings/fediverse", method: "POST" }, + label({ for: "fediverse_instance" }, i18n.fediverseInstanceLabel), br(), + input({ type: "text", id: "fediverse_instance", name: "instance", placeholder: "mastodon.social", required: true }), br(), + label({ for: "fediverse_token" }, i18n.fediverseTokenLabel), br(), + input({ type: "password", id: "fediverse_token", name: "token", autocomplete: "off", required: true }), br(), + button({ type: "submit" }, i18n.fediverseConnect) + ) + ) + ), + section({ id: "telegram" }, + div({ class: "tags-header" }, + h2("Telegram"), + telegramAccount + ? form( + { action: "/settings/telegram/disconnect", method: "POST" }, + p(`${i18n.fediverseConnectedAs}: `, strong(telegramAccount.displayName || ""), telegramAccount.username ? span(` (@${telegramAccount.username})`) : ""), + br(), + button({ type: "submit" }, i18n.fediverseDisconnect) + ) + : telegramLogin && telegramLogin.step === "code" + ? form( + { action: "/settings/telegram/code", method: "POST" }, + br(), + p(i18n.telegramCodeHelp), + input({ type: "text", id: "telegram_code", name: "code", autocomplete: "off", inputmode: "numeric", placeholder: "12345", required: true }), br(), br(), + button({ type: "submit" }, i18n.telegramVerify), + " ", + button({ type: "submit", formaction: "/settings/telegram/cancel", class: "delete-btn" }, i18n.telegramCancel) + ) + : telegramLogin && telegramLogin.step === "password" + ? form( + { action: "/settings/telegram/password", method: "POST" }, + br(), + p(i18n.telegramPasswordHelp), + input({ type: "password", id: "telegram_password", name: "password", autocomplete: "off", required: true }), br(), br(), + button({ type: "submit" }, i18n.telegramVerify), + " ", + button({ type: "submit", formaction: "/settings/telegram/cancel", class: "delete-btn" }, i18n.telegramCancel) + ) + : form( + { action: "/settings/telegram/start", method: "POST" }, + label({ for: "telegram_api_id" }, i18n.telegramApiIdLabel), br(), + input({ type: "text", id: "telegram_api_id", name: "apiId", inputmode: "numeric", placeholder: "12345678", required: true }), br(), + label({ for: "telegram_api_hash" }, i18n.fediverseTokenLabel), br(), + input({ type: "password", id: "telegram_api_hash", name: "apiHash", autocomplete: "off", placeholder: "0123456789abcdef0123456789abcdef", required: true }), br(), + label({ for: "telegram_phone" }, i18n.telegramPhoneLabel), br(), + input({ type: "tel", id: "telegram_phone", name: "phone", placeholder: "+34 60000000", required: true }), br(), br(), + button({ type: "submit" }, i18n.fediverseConnect) + ) + ) + ), + section({ id: "peertube" }, + div({ class: "tags-header" }, + h2("PeerTube"), + peertubeAccount + ? form( + { action: "/settings/peertube/disconnect", method: "POST" }, + p(`${i18n.fediverseConnectedAs}: `, a({ href: peertubeAccount.channelUrl || peertubeAccount.profileUrl, target: "_blank", rel: "noopener noreferrer" }, peertubeAccount.handle)), + button({ type: "submit" }, i18n.fediverseDisconnect) + ) + : form( + { action: "/settings/peertube", method: "POST" }, + label({ for: "peertube_instance" }, i18n.fediverseInstanceLabel), br(), + input({ type: "text", id: "peertube_instance", name: "instance", placeholder: "peertube.example.org", required: true }), br(), + label({ for: "peertube_username" }, i18n.peertubeUsernameLabel), br(), + input({ type: "text", id: "peertube_username", name: "username", autocomplete: "off", required: true }), br(), + label({ for: "peertube_password" }, i18n.peertubePasswordLabel), br(), + input({ type: "password", id: "peertube_password", name: "password", autocomplete: "off", required: true }), br(), + button({ type: "submit" }, i18n.fediverseConnect) + ) + ) + ) + , + section({ id: "nextcloud" }, + div({ class: "tags-header" }, + h2("Nextcloud"), + nextcloudAccount + ? form( + { action: "/settings/nextcloud/disconnect", method: "POST" }, + p(`${i18n.fediverseConnectedAs}: `, a({ href: nextcloudAccount.server, target: "_blank", rel: "noopener noreferrer" }, `${nextcloudAccount.user}@${String(nextcloudAccount.server).replace(/^https?:\/\//, "")}`), ` (${nextcloudAccount.folder})`), + button({ type: "submit" }, i18n.fediverseDisconnect) + ) + : form( + { action: "/settings/nextcloud", method: "POST" }, + label({ for: "nextcloud_server" }, i18n.nextcloudServerLabel), br(), + input({ type: "text", id: "nextcloud_server", name: "server", placeholder: "cloud.example.org", required: true }), br(), + label({ for: "nextcloud_user" }, i18n.nextcloudUserLabel), br(), + input({ type: "text", id: "nextcloud_user", name: "user", autocomplete: "off", required: true }), br(), + label({ for: "nextcloud_password" }, i18n.peertubePasswordLabel), br(), + input({ type: "password", id: "nextcloud_password", name: "password", autocomplete: "off", required: true }), br(), + label({ for: "nextcloud_folder" }, i18n.nextcloudFolderLabel), br(), + input({ type: "text", id: "nextcloud_folder", name: "folder", placeholder: "Oasis" }), br(), + button({ type: "submit" }, i18n.fediverseConnect) + ) + ) + ) + ] : []; + return template( + i18n.settings, + section( + div({ class: "tags-header module-header-line" }, + h2(i18n.settings), + p(i18n.settingsDescription) + ), + div({ class: "mode-buttons-row" }, + a({ class: filter === 'oasis' ? 'filter-btn active' : 'filter-btn', href: '/settings?filter=oasis' }, String(i18n.linkBoxOasis).toUpperCase()), + modOn('fediverse') ? a({ class: filter === 'multiverse' ? 'filter-btn active' : 'filter-btn', href: '/settings?filter=multiverse' }, String(i18n.multiverse).toUpperCase()) : null + ) + ), + ...(filter === 'multiverse' ? multiverseParts : oasisParts) ); }; diff --git a/src/views/shops_view.js b/src/views/shops_view.js index d392be5..ce324ef 100644 --- a/src/views/shops_view.js +++ b/src/views/shops_view.js @@ -1,16 +1,19 @@ -const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, img, progress, video, table, tr, td } = require("../server/node_modules/hyperaxe") +const { div, h2, p, section, button, form, a, span, textarea, br, input, label, select, option, img, progress, video, audio, table, tr, td, hr } = require("../server/node_modules/hyperaxe") const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton, renderOpinionsVoting, renderEngagement, renderInviteQrCard , renderSpreadEditWarning, renderContentActions } = require("./main_views") +const { renderCallButton, clearnetItemHref, template, i18n, userLink, renderStateChip, renderLifespanChip, renderSpreadButton, renderOpinionsVoting, renderEngagement, renderInviteQrCard , renderSpreadEditWarning, renderContentActions, renderSubscriptionBox, renderModuleStats, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, renderWalletChip, contentDeleteAction, paged } = require("./main_views") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") -const { renderUrl } = require("../backend/renderUrl") +const { renderStyledText } = require("../backend/renderStyledText") const { renderMapLocationUrl, renderMapEmbed, renderMapLocationVisitLabel } = require("./maps_view") const opinionCategories = require("../backend/opinion_categories") -const { renderReachChip, renderClearnetUrlBlock, renderClearnetPage, renderClearnetSearchForm, renderEncryptedChip, blobUrl: cnBlobUrl, escapeHtml: cnEscapeHtml } = require("./clearnet_view") +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderClearnetUrlBlock, renderClearnetPage, renderClearnetSearchForm, renderEncryptedChip, blobUrl: cnBlobUrl, escapeHtml: cnEscapeHtml, renderRichText: cnRichText, renderKindTag: cnKindTag } = require("./clearnet_view") const userId = config.keys.id const safeArr = (v) => (Array.isArray(v) ? v : []) const safeText = (v) => String(v || "").trim() +const clearnetEligible = (shop) => String(shop.visibility || "").toUpperCase() !== "CLOSED" && !shop.encrypted +const SHOP_REACH = ["OPEN", "CLOSED"] +const shopReachLabel = (v) => v === "CLOSED" ? i18n.shopClosed : i18n.shopOpen const voteSum = (opinions = {}) => Object.values(opinions || {}).reduce((s, n) => s + (Number(n) || 0), 0) const sumCats = (opinions = {}, cats = []) => (cats || []).reduce((s, c) => s + (Number((opinions || {})[c]) || 0), 0) const renderStarRating = (opinions, voterCount) => { @@ -26,11 +29,13 @@ const renderMediaBlob = (value, fallbackSrc = null, attrs = {}) => { if (!value) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null const s = String(value).trim() if (!s) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith('&')) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(s)}`, ...attrs }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) + const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) + if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}`, preload: 'metadata' }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, ...attrs }) + if (mImg) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(mImg[1])}`, ...attrs }) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null } @@ -44,17 +49,19 @@ const buildReturnTo = (filter, params = {}) => { return `/shops?${parts.join("&")}` } -const renderModeButtons = (currentFilter) => +const renderModeButtons = (currentFilter, emptyMod = false, modesAvail = null) => div({ class: "tribe-mode-buttons" }, - ["all", "recent", "mine", "top", "products", "prices", "favorites"].map(f => + ...(emptyMod ? [] : [ + ["all", "recent", "mine", "top", "products", "prices", "favorites"].filter(f => f === "all" || f === currentFilter || (modesAvail && modesAvail[f] !== false)).map(f => form({ method: "GET", action: "/shops" }, input({ type: "hidden", name: "filter", value: f }), button({ type: "submit", class: currentFilter === f ? "filter-btn active" : "filter-btn" }, i18n[`shopFilter${f.charAt(0).toUpperCase() + f.slice(1)}`] || f.toUpperCase()) ) ), - form({ method: "GET", action: "/shops/purchases" }, + ...((currentFilter === "purchases" || !modesAvail || modesAvail.purchases !== false) ? [form({ method: "GET", action: "/shops/purchases" }, button({ type: "submit", class: currentFilter === "purchases" ? "filter-btn active" : "filter-btn" }, (i18n.shopPurchasesButton || "Purchases").toUpperCase()) - ), + )] : []), + ]), form({ method: "GET", action: "/shops" }, input({ type: "hidden", name: "filter", value: "create" }), button({ type: "submit", class: "create-button" }, i18n.shopUpload) @@ -67,7 +74,7 @@ const renderShopCard = exports.renderShopCard = (shop, filter, params = {}) => { return div({ class: "tribe-card" }, div({ class: "card-header activity-card-header" }, - renderContentActions(shop.key, `/shops/${encodeURIComponent(shop.key)}`, { spread: params.spreadMap && params.spreadMap.get(shop.key) || null, author: shop.author, favKind: 'shops', isFavorite: shop.isFavorite, reportTitle: shop.title }) + renderContentActions(shop.key, `/shops/${encodeURIComponent(shop.key)}`, { spread: params.spreadMap && params.spreadMap.get(shop.key) || null, author: shop.author, favKind: 'shops', isFavorite: shop.isFavorite, reportTitle: shop.title, returnTo, deleteAction: isAuthor ? contentDeleteAction('shop', shop.key) : undefined }) ), div({ class: "tribe-card-image-wrapper" }, a({ href: `/shops/${encodeURIComponent(shop.key)}` }, @@ -82,7 +89,11 @@ const renderShopCard = exports.renderShopCard = (shop, filter, params = {}) => { shop.visibility === "CLOSED" ? renderStateChip("closed", "✗", i18n.shopClosed) : renderStateChip("mutuals", "✓", i18n.shopOpen), - renderLifespanChip(shop.lifetime, i18n) + renderLifespanChip(shop.lifetime, i18n), + shop.clearnet === true && clearnetEligible(shop) ? renderReachChip(true, i18n, clearnetItemHref('shops', shop.title, shop.key)) : null, + shop.subscriptionIn === true + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : (shop.subscriptionIn === false ? renderStateChip("closed", "✉", i18n.subscriptionOff) : null) ), div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.shopProducts}: ${shop.productCount || 0}`) @@ -91,20 +102,23 @@ const renderShopCard = exports.renderShopCard = (shop, filter, params = {}) => { ) } -const renderProductCard = (product, shopId, returnTo) => { +const renderProductCard = (product, shopId, returnTo, params = {}) => { const isAuthor = String(product.author) === String(userId) const stock = Number(product.stock) || 0 const voterCount = safeArr(product.opinions_inhabitants).length const productUrl = `/shops/product/${encodeURIComponent(product.key)}?shopId=${encodeURIComponent(shopId)}` return div({ class: "shop-product-card" }, + div({ class: "card-header activity-card-header" }, + renderContentActions(product.key, `/shops/product/${encodeURIComponent(product.key)}`, { spread: params.spreadMap && params.spreadMap.get(product.key) || null, author: product.author, favKind: 'shopProducts', isFavorite: product.isFavorite, reportTitle: product.title, returnTo, deleteAction: isAuthor ? contentDeleteAction('shopProduct', product.key) : undefined }) + ), product.image ? div({ class: "shop-product-media" }, a({ href: productUrl }, renderMediaBlob(product.image))) : null, div({ class: "shop-product-body" }, product.shopTitle ? p(a({ href: `/shops/${encodeURIComponent(shopId)}`, class: "user-link" }, product.shopTitle)) : null, h2(a({ href: productUrl }, product.title || i18n.shopProductUntitled)), renderStarRating(product.opinions, voterCount), - product.description ? p(...renderUrl(product.description)) : null, - div({ class: "shop-product-price" }, `${Number(product.price || 0).toFixed(6)} ECO`), + product.description ? p(...renderStyledText(product.description)) : null, + div({ class: "price-chip" }, `${Number(product.price || 0).toFixed(6)} ECO`), div({ class: "confirmations-block stock-block" }, div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.shopProductStock}: `), @@ -134,18 +148,38 @@ const renderCommentsSection = (parentId, returnTo, comments = []) => { const renderShopForm = (filter, shop = {}, params = {}) => { const isEdit = filter === "edit" const returnTo = safeText(params.returnTo) || buildReturnTo("all") + const reachRaw = String(params.reach || "").toUpperCase() + const ownReach = String(shop.visibility || "").toUpperCase() + const reach = SHOP_REACH.includes(reachRaw) ? reachRaw : (SHOP_REACH.includes(ownReach) ? ownReach : "OPEN") + const showClearnet = isEdit ? clearnetEligible(shop) : reach === "OPEN" && clearnetEligible({ ...shop, visibility: reach }) return div({ class: "create-tribe-form" }, h2(isEdit ? i18n.shopUpdateSectionTitle : i18n.shopCreateSectionTitle), isEdit ? (params.spreadWarning || null) : null, + ...(isEdit ? [] : [ + form({ method: "GET", action: "/shops" }, + input({ type: "hidden", name: "filter", value: "create" }), + label(i18n.shopVisibility), br(), + div({ class: "apply-row" }, + select({ name: "visibility", class: "report-category-select" }, + option({ value: "OPEN", ...(reach === "OPEN" ? { selected: true } : {}) }, i18n.shopOpen), + option({ value: "CLOSED", ...(reach === "CLOSED" ? { selected: true } : {}) }, i18n.shopClosed) + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") + ) + ), + hr({ class: "form-sep" }), + h2({ class: "report-category-fixed" }, shopReachLabel(reach)) + ]), form({ action: isEdit ? `/shops/update/${encodeURIComponent(shop.key || "")}` : "/shops/create", method: "POST", enctype: "multipart/form-data" }, input({ type: "hidden", name: "returnTo", value: returnTo }), + isEdit ? null : input({ type: "hidden", name: "visibility", value: reach }), label(i18n.title || "Title"), br, input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.shopTitlePlaceholder || "Name of your shop", value: shop.title || "" }), br(), label(i18n.shopShortDescription), br, input({ type: "text", name: "shortDescription", required: true, maxlength: 160, placeholder: i18n.shopShortDescriptionPlaceholder || "Brief description of your shop", value: shop.shortDescription || "" }), br(), label(i18n.description || "Description"), br, - textarea({ name: "description", rows: 4, placeholder: i18n.shopDescriptionPlaceholder || "Detailed description of your shop" }, shop.description || ""), br, - label(i18n.blogImage || "Upload media (max-size: 50MB)"), br, + textarea({ maxlength: "5000", name: "description", rows: 4, placeholder: i18n.shopDescriptionPlaceholder || "Detailed description of your shop" }, shop.description || ""), br, + label(i18n.blogImage || "Upload media (max-size: 75MB)"), br, input({ type: "file", name: "image", accept: "image/*,video/*" }), br(), br(), label(i18n.shopUrl), br, input({ type: "text", name: "url", placeholder: "https://", value: shop.url || "" }), br, @@ -154,14 +188,8 @@ const renderShopForm = (filter, shop = {}, params = {}) => { label(i18n.mapLocationTitle || "Map Location"), br, input({ type: "text", name: "mapUrl", placeholder: i18n.mapUrlPlaceholder || "/maps/MAP_ID", value: shop.mapUrl || "" }), br, label(i18n.shopTags), br, - input({ type: "text", name: "tags", placeholder: i18n.shopTagsPlaceholder || "tag1, tag2, tag3", value: safeArr(shop.tags).join(", ") }), br, - isEdit ? null : label(i18n.shopVisibility), - isEdit ? null : br, - isEdit ? null : select({ name: "visibility" }, - option({ value: "OPEN", ...((shop.visibility || "OPEN") === "OPEN" ? { selected: true } : {})}, i18n.shopOpen), - option({ value: "CLOSED", ...(shop.visibility === "CLOSED" ? { selected: true } : {})}, i18n.shopClosed) - ), - isEdit ? null : br(), + input({ type: "text", name: "tags", placeholder: i18n.shopTagsPlaceholder || "Enter tags separated by commas", value: Array.isArray(shop.tags) ? shop.tags.join(", ") : (shop.tags || "") }), br, + showClearnet ? renderClearnetSelector(shop.clearnet === true || String(shop.clearnet || "") === "1", i18n) : null, br(), button({ type: "submit" }, isEdit ? i18n.shopUpdate : i18n.shopCreate) ) @@ -178,12 +206,12 @@ const renderProductForm = (shopId, product = {}, isEdit = false, returnTo = "", label(i18n.title || "Title"), br, input({ type: "text", name: "title", maxlength: "100", required: true, value: product.title || "" }), br(), label(i18n.description || "Description"), br, - textarea({ name: "description", rows: 4 }, product.description || ""), br, + textarea({ maxlength: "5000", name: "description", rows: 4 }, product.description || ""), br, label(i18n.shopProductPrice), br, - input({ type: "number", name: "price", step: "0.000001", min: "0.000001", required: true, value: product.price || "" }), br(), br(), + input({ type: "number", name: "price", step: "0.000001", min: "0.000001", required: true, value: product.price || "" }), renderEcoValueChip(), br(), br(), label(i18n.shopProductStock), br, input({ type: "number", name: "stock", min: "0", value: product.stock !== undefined ? product.stock : 1 }), br(), br(), - label(i18n.blogImage || "Upload media (max-size: 50MB)"), br, + label(i18n.blogImage || "Upload media (max-size: 75MB)"), br, input({ type: "file", name: "image", accept: "image/*,video/*" }), br(), br(), input({ type: "hidden", name: "featured", value: "0" }), label(i18n.shopProductFeatured), @@ -207,17 +235,19 @@ exports.shopsView = async (shops, filter, shopToEdit = null, params = {}) => { const q = safeText(params.q || "") const sort = safeText(params.sort || "recent") const list = safeArr(shops) + const emptyMod = moduleIsEmpty(list, filter || "all", "all", q) const title = i18n.shopsTitle const isForm = filter === "create" || filter === "edit" const isProducts = filter === "products" || filter === "prices" return template( title, - section(div({ class: "tags-header" }, h2(title), p(i18n.shopDescription))), - section(renderModeButtons(filter)), - !isForm + section(div({ class: "tags-header module-header-line" }, h2(title), p(i18n.shopDescription), renderWalletChip())), + section(renderModeButtons(filter, emptyMod, (params && params.modesAvail) || null)), + !isForm && !emptyMod ? section( - div({ class: "filters" }, + div({ class: "filters activity-filter-chips activity-toolbar-row" }, + isProducts ? renderModuleStats(list.length) : renderModuleStatsBy(list, s => String(s.visibility || '').toUpperCase(), [{ value: 'OPEN', label: i18n.shopOpen }, { value: 'CLOSED', label: i18n.shopClosed }]), form({ method: "GET", action: "/shops", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), input({ type: "text", name: "q", value: q, placeholder: i18n.shopSearchPlaceholder, class: "filter-box__input" }), @@ -234,16 +264,16 @@ exports.shopsView = async (shops, filter, shopToEdit = null, params = {}) => { : null, section( isForm - ? renderShopForm(filter, filter === "edit" ? (shopToEdit || {}) : {}, params) + ? renderShopForm(filter, filter === "edit" ? (shopToEdit || {}) : (params.draft || {}), params) : isProducts ? div({ class: "shop-products-grid" }, list.length - ? list.map(prod => renderProductCard(prod, prod.shopId, buildReturnTo(filter, { q }))) + ? paged(list).map(prod => renderProductCard(prod, prod.shopId, buildReturnTo(filter, { q }), params)) : p(i18n.shopNoProducts) ) : div({ class: "tribe-grid" }, list.length - ? list.map(shopItem => renderShopCard(shopItem, filter, params)) + ? paged(list).map(shopItem => renderShopCard(shopItem, filter, params)) : p(i18n.shopNoItems) ) ) @@ -254,12 +284,13 @@ exports.singleShopView = async (shop, filter, products = [], comments = [], para const q = safeText(params.q || "") const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q }) const isAuthor = String(shop.author) === String(userId) - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetShops === true) + const canClearnet = clearnetEligible(shop) + const isClearnet = canClearnet && !!shop.clearnet products = safeArr(products) const shopSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(shop.key, null, { spread: params.spreads || null, author: shop.author, favKind: 'shops', isFavorite: shop.isFavorite, reportTitle: shop.title }) + renderContentActions(shop.key, `/shops/${encodeURIComponent(shop.key)}`, { spread: params.spreads || null, author: shop.author, favKind: 'shops', isFavorite: shop.isFavorite, reportTitle: shop.title, deleteAction: isAuthor ? contentDeleteAction('shop', shop.key) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, shop.title || i18n.shopUntitled) @@ -270,18 +301,24 @@ exports.singleShopView = async (shop, filter, products = [], comments = [], para : renderStateChip("mutuals", "✓", i18n.shopOpen), shop.encrypted ? renderStateChip("encrypted", "🔒", i18n.encryptedChipLabel || "E2E") : null, renderLifespanChip(shop.lifetime, i18n), - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('shops', shop.title, shop.key) : null), + isAuthor && canClearnet ? renderClearnetSwitch('shops', shop.rootId || shop.key, isClearnet) : null, + shop.subscription + ? ((isAuthor || shop.subscription.subscribed === true) + ? renderStateChip("mutuals", "✉", i18n.subscriptionOn) + : renderStateChip("closed", "✉", i18n.subscriptionOff)) + : null ), renderMediaBlob(shop.image, '/assets/images/default-avatar.png', { class: 'tribe-detail-image' }), - shop.description ? p({ class: "tribe-side-description" }, ...renderUrl(shop.description)) : null, + shop.description ? p({ class: "tribe-side-description" }, ...renderStyledText(shop.description)) : null, table({ class: "tribe-info-table" }, tr( td({ class: "tribe-info-label" }, i18n.shopCreatedAt || "CREATED"), - td({ class: "tribe-info-value", colspan: "3" }, new Date(shop.createdAt).toLocaleString()) + td({ class: "tribe-info-value", colspan: "3" }, moment(shop.createdAt).format("YYYY/MM/DD HH:mm")) ), shop.location ? tr( td({ class: "tribe-info-label" }, i18n.shopLocation), - td({ class: "tribe-info-value", colspan: "3" }, ...renderUrl(shop.location)) + td({ class: "tribe-info-value", colspan: "3" }, ...renderStyledText(shop.location)) ) : null, tr( td({ class: "tribe-info-label" }, i18n.shopStatus || "STATUS"), @@ -289,7 +326,7 @@ exports.singleShopView = async (shop, filter, products = [], comments = [], para ), shop.url ? tr( td({ class: "tribe-info-label" }, i18n.shopUrl), - td({ class: "tribe-info-value", colspan: "3" }, ...renderUrl(shop.url)) + td({ class: "tribe-info-value", colspan: "3" }, ...renderStyledText(shop.url)) ) : null, tr( td({ class: "tribe-info-value", colspan: "4" }, userLink(shop.author)) @@ -332,6 +369,17 @@ exports.singleShopView = async (shop, filter, products = [], comments = [], para shop.encrypted ? i18n.shopMakePublic : i18n.shopMakePrivate)) ) : null, + shop.subscription + ? renderSubscriptionBox({ + target: shop.rootId || shop.key, + scope: "shops", + subscribed: shop.subscription.subscribed === true, + count: shop.subscription.count, + isOwner: isAuthor, + canWrite: isAuthor, + returnTo + }) + : null, isAuthor ? div({ class: "tribe-side-actions" }, form({ method: "GET", action: `/shops/${encodeURIComponent(shop.key)}/orders` }, @@ -346,9 +394,6 @@ exports.singleShopView = async (shop, filter, products = [], comments = [], para ? div({ class: "tribe-side-actions owner-actions" }, form({ method: "GET", action: `/shops/edit/${encodeURIComponent(shop.key)}` }, button({ type: "submit", class: "tribe-action-btn" }, i18n.shopUpdate) - ), - form({ method: "POST", action: `/shops/delete/${encodeURIComponent(shop.key)}` }, - button({ type: "submit", class: "tribe-action-btn danger-btn" }, i18n.shopDelete) ) ) : null, @@ -365,15 +410,15 @@ exports.singleShopView = async (shop, filter, products = [], comments = [], para : null, div({ class: "shop-products-grid" }, products.length - ? products.map(prod => renderProductCard(prod, shop.rootId || shop.key, returnTo)) + ? paged(products).map(prod => renderProductCard(prod, shop.rootId || shop.key, `/shops/${encodeURIComponent(shop.key)}`, params)) : p(i18n.shopNoProducts) ) ) return template( shop.title || i18n.shopTitle, - section(div({ class: "tags-header" }, h2(i18n.shopsTitle), p(i18n.shopDescription))), - section(renderModeButtons(filter)), + section(div({ class: "tags-header module-header-line" }, h2(i18n.shopsTitle), p(i18n.shopDescription), renderWalletChip())), + section(renderModeButtons(filter, false, (params && params.modesAvail) || null)), section( div({ class: "tribe-details" }, shopSide, @@ -390,14 +435,14 @@ exports.singleProductView = async (product, shop, comments = [], params = {}) => const productSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(product.key, null, { spread: params.spreads || null, author: product.author, favKind: 'shopProducts', isFavorite: product.isFavorite, returnTo, reportTitle: product.title }) + renderContentActions(product.key, `/shops/product/${encodeURIComponent(product.key)}`, { spread: params.spreads || null, author: product.author, favKind: 'shopProducts', isFavorite: product.isFavorite, returnTo, reportTitle: product.title, deleteAction: isAuthor ? contentDeleteAction('shopProduct', product.key) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, product.title || i18n.shopProductUntitled) ), renderStarRating(product.opinions, safeArr(product.opinions_inhabitants).length), product.image ? renderMediaBlob(product.image, null, { class: "tribe-detail-image" }) : null, - div({ class: "card-date-highlight" }, `${Number(product.price || 0).toFixed(6)} ECO`), + div({ class: "price-chip" }, `${Number(product.price || 0).toFixed(6)} ECO`), div({ class: "confirmations-block stock-block" }, div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.shopProductStock}: `), @@ -425,10 +470,6 @@ exports.singleProductView = async (product, shop, comments = [], params = {}) => input({ type: "hidden", name: "shopId", value: product.shopId }), input({ type: "hidden", name: "returnTo", value: returnTo }), button({ class: "tribe-action-btn", type: "submit" }, i18n.shopUpdate) - ), - form({ method: "POST", action: `/shops/product/delete/${encodeURIComponent(product.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "tribe-action-btn danger-btn", type: "submit" }, i18n.shopDelete) ) ) : null @@ -438,7 +479,7 @@ exports.singleProductView = async (product, shop, comments = [], params = {}) => product.description ? div({ class: "job-section" }, h2({ class: "job-section-title" }, i18n.marketItemDescription || i18n.shopDescription), - p({ class: "tribe-side-description" }, ...renderUrl(product.description)) + p({ class: "tribe-side-description" }, ...renderStyledText(product.description)) ) : null, !isAuthor && stock > 0 @@ -448,7 +489,7 @@ exports.singleProductView = async (product, shop, comments = [], params = {}) => p({ class: "shop-buy-form-note" }, i18n.shopBuyEncryptedNote || "Your delivery details are sent encrypted only to the shop owner."), label(i18n.shopBuyDeliveryAddress || "Delivery address"), br(), - textarea({ name: "deliveryAddress", required: true, rows: 3, placeholder: i18n.shopBuyDeliveryAddressPlaceholder || "" }), + textarea({ maxlength: "5000", name: "deliveryAddress", required: true, rows: 3, placeholder: i18n.shopBuyDeliveryAddressPlaceholder || "" }), br(), br(), label(i18n.shopBuyContact || "Contact"), @@ -458,7 +499,7 @@ exports.singleProductView = async (product, shop, comments = [], params = {}) => br(), label(i18n.shopBuyNotes || "Notes"), br(), - textarea({ name: "notes", rows: 2, placeholder: i18n.shopBuyNotesPlaceholder || "" }), + textarea({ maxlength: "5000", name: "notes", rows: 2, placeholder: i18n.shopBuyNotesPlaceholder || "" }), br(), br(), button({ type: "submit", class: "buy-btn" }, i18n.marketActionsBuy || i18n.shopBuy) @@ -475,8 +516,8 @@ exports.singleProductView = async (product, shop, comments = [], params = {}) => return template( product.title || i18n.shopProductTitle, - section(div({ class: "tags-header" }, h2(i18n.shopsTitle), p(i18n.shopDescription))), - section(renderModeButtons("products")), + section(div({ class: "tags-header module-header-line" }, h2(i18n.shopsTitle), p(i18n.shopDescription), renderWalletChip())), + section(renderModeButtons("products", false, (params && params.modesAvail) || null)), section(div({ class: "tribe-details" }, productSide, productMain)) ) } @@ -531,9 +572,10 @@ exports.shopOrdersView = async (shop, orders) => { acts.push(statusForm(o.id, "SHIPPED", i18n.shopOrderMarkShipped || "Mark shipped")) } acts.push(a({ href: `/pm?recipients=${encodeURIComponent(o.buyer)}`, class: "tribe-action-btn" }, i18n.shopOrderPmBuyer || i18n.privateMessage || "PM")) + { const cb = renderCallButton(o.buyer, { cls: "tribe-action-btn", pam: true }); if (cb) acts.push(cb); } return div({ class: "tribe-side-actions" }, ...acts) } - const rows = (orders || []).map(o => div({ class: "shop-order-card card-section" }, + const rows = paged(orders || []).map(o => div({ class: "shop-order-card card-section" }, div({ class: "card-chips-row" }, renderEncryptedChip(i18n), orderStatusChip(o.status)), div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.shopOrderProduct || "Product"}:`), span({ class: "card-value" }, String(o.title || o.productId || ""))), div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.shopOrderPrice || "Price"}:`), span({ class: "card-value" }, `${Number(o.price || 0).toFixed(6)} ECO`)), @@ -547,7 +589,7 @@ exports.shopOrdersView = async (shop, orders) => { return template( title, section( - div({ class: "tags-header" }, h2(title), p(i18n.shopOrdersDescription || "Encrypted purchase orders received by this shop.")), + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.shopOrdersDescription || "Encrypted purchase orders received by this shop.")), a({ href: `/shops/${encodeURIComponent(shop.key || shop.id || "")}`, class: "filter-btn" }, i18n.goBack || "Go back") ), section( @@ -556,11 +598,12 @@ exports.shopOrdersView = async (shop, orders) => { ) } -exports.myPurchasesView = async (purchases) => { - const title = i18n.shopMyOrdersTitle || "My orders" +exports.myPurchasesView = async (purchases, params = {}) => { + const title = i18n.shopsTitle const buyerActions = (o) => { const acts = [] if (o.seller) acts.push(a({ href: `/pm?recipients=${encodeURIComponent(o.seller)}`, class: "tribe-action-btn" }, i18n.shopOrderPmSeller || i18n.privateMessage || "PM")) + if (o.seller) { const cb = renderCallButton(o.seller, { cls: "tribe-action-btn", pam: true }); if (cb) acts.push(cb); } if (String(o.status || "PENDING").toUpperCase() === "SHIPPED") { acts.push(form({ method: "POST", action: `/shops/orders/${encodeURIComponent(o.id)}/status`, class: "inline-form" }, input({ type: "hidden", name: "status", value: "RECEIVED" }), @@ -570,7 +613,7 @@ exports.myPurchasesView = async (purchases) => { } return acts.length ? div({ class: "tribe-side-actions" }, ...acts) : null } - const rows = (purchases || []).map(o => div({ class: "shop-order-card card-section" }, + const rows = paged(purchases || []).map(o => div({ class: "shop-order-card card-section" }, div({ class: "card-chips-row" }, renderEncryptedChip(i18n), orderStatusChip(o.status)), div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.shopOrderProduct || "Product"}:`), span({ class: "card-value" }, String(o.title || o.productId || ""))), div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.shopOrderPrice || "Price"}:`), span({ class: "card-value" }, `${Number(o.price || 0).toFixed(6)} ECO`)), @@ -584,11 +627,11 @@ exports.myPurchasesView = async (purchases) => { return template( title, section( - div({ class: "tags-header" }, h2(title), p(i18n.shopMyOrdersDescription || "Your encrypted purchase orders.")) + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.shopMyOrdersDescription || "Your encrypted purchase orders.")) ), - section(renderModeButtons("purchases")), + section(renderModeButtons("purchases", false, params.modesAvail || null)), section( - rows.length ? div({ class: "shop-orders-list" }, ...rows) : p(i18n.shopMyOrdersEmpty || "You have no purchases yet.") + rows.length ? div({ class: "shop-orders-list" }, ...rows) : div({ class: "tribe-grid" }, p(i18n.shopMyOrdersEmpty || "You have no purchases yet.")) ) ) } @@ -600,17 +643,17 @@ exports.clearnetShopView = async (shop, products = []) => { }; const productCards = (products || []).filter(p => Number(p.stock) > 0 || p.featured).map(prod => { const pImg = cnBlobUrl(prod.image); + const pKind = /^\[video:/.test(String(prod.image || '')) ? 'video' : /^\[audio:/.test(String(prod.image || '')) ? 'audio' : 'img'; return `
- ${pImg ? `` : ''} + ${pImg ? (pKind === 'img' ? `` : `<${pKind} class="cn-product-img" src="${pImg}" controls preload="metadata">`) : ''}

${cnEscapeHtml(prod.title || '')}

- ${prod.description ? `

${cnEscapeHtml(prod.description)}

` : ''} + ${prod.description ? `

${cnRichText(prod.description)}

` : ''}

${fmtPrice(prod.price)} ECO

- ${Number(prod.stock) > 0 ? `

Stock: ${prod.stock}

` : ''}
`; }).join('\n'); const shopBlobUrl = cnBlobUrl(shop.image); const shopImg = shopBlobUrl ? `${cnEscapeHtml(shop.title || '')}` : ''; - const desc = cnEscapeHtml(shop.shortDescription || shop.description || ''); + const desc = cnRichText(shop.shortDescription || shop.description || ''); const extraCss = ` .cn-hero{display:flex;gap:24px;margin-bottom:24px;flex-wrap:wrap;align-items:flex-start} .cn-shop-img{display:block;max-width:280px;width:100%;border:3px solid var(--fg);border-radius:8px;background:#000} @@ -636,16 +679,17 @@ exports.clearnetShopView = async (shop, products = []) => {

${cnEscapeHtml(shop.title || '')}

${desc ? `

${desc}

` : ''}
+ ${cnKindTag('shop')} ${shop.createdAt ? `📅 ${new Date(shop.createdAt).toISOString().slice(0,10)}` : ''} - ${shop.location ? `📍 ${cnEscapeHtml(shop.location)}` : ''}
-

Products

- ${productCards ? `
${productCards}
` : '
No products available.
'} +
+

${cnEscapeHtml(i18n.shopProducts)}

+ ${productCards ? `
${productCards}
` : `
${cnEscapeHtml(i18n.shopNoProducts)}
`} `; return renderClearnetPage({ - title: `${shop.title || 'Shop'} — Oasis`, + title: `${shop.title || 'Shop'} | Oasis`, ogTitle: shop.title || 'Oasis', ogDescription: shop.shortDescription || shop.description || '', ogImage: shopBlobUrl, diff --git a/src/views/stats_view.js b/src/views/stats_view.js index 2cccdee..337b0ce 100644 --- a/src/views/stats_view.js +++ b/src/views/stats_view.js @@ -1,5 +1,6 @@ const { div, h2, p, section, button, form, input, ul, li, a, h3, span, strong, table, thead, tbody, tr, td, th } = require("../server/node_modules/hyperaxe"); const { template, i18n, userLink } = require('./main_views'); +const sharedState = require('../configs/shared-state'); Object.assign(i18n, { statsChat: "Chats", @@ -37,9 +38,10 @@ exports.statsView = (stats, filter) => { const modes = ['ALL', 'MINE', 'TOMBSTONE']; const types = [ 'bookmark', 'event', 'task', 'votes', 'report', 'feed', 'project', 'industry', 'industryBlueprint', - 'image', 'torrent', 'audio', 'video', 'document', 'transfer', 'post', 'tribe', + 'image', 'torrent', 'file', 'audio', 'video', 'document', 'transfer', 'post', 'tribe', 'market', 'forum', 'job', 'aiExchange', 'map', 'shop', 'shopProduct', - 'chat', 'chatMessage', 'pad', 'padEntry', 'gameScore', 'calendar', 'calendarDate', 'calendarNote', + 'chat', 'chatMessage', 'pad', 'padEntry', 'room', 'roomMember', 'wikiPage', 'emergency', 'emergencyConfirm', 'emergencyUpdate', 'mailingList', 'logisticsRoute', 'logisticsRating', 'podcast', 'podcastEpisode', 'podcastPlay', 'campaign', 'campaignSignature', 'campaignUpdate', 'gameScore', 'calendar', 'calendarDate', 'calendarNote', + 'schoolCourse', 'schoolLesson', 'schoolEnroll', 'schoolCertificate', 'parliamentCandidature','parliamentTerm','parliamentProposal','parliamentRevocation','parliamentLaw', 'courtsCase','courtsEvidence','courtsAnswer','courtsVerdict','courtsSettlement','courtsSettlementProposal','courtsSettlementAccepted','courtsNomination','courtsNominationVote' ]; @@ -55,6 +57,7 @@ exports.statsView = (stats, filter) => { industryBlueprint: i18n.industryBlueprints, image: i18n.statsImage, torrent: i18n.statsTorrent, + file: i18n.statsFile, audio: i18n.statsAudio, video: i18n.statsVideo, document: i18n.statsDocument, @@ -72,7 +75,26 @@ exports.statsView = (stats, filter) => { chatMessage: i18n.statsChatMessage, pad: i18n.statsPad, padEntry: i18n.statsPadEntry, + room: i18n.statsRoom, + roomMember: i18n.statsRoomMember, + wikiPage: i18n.statsWiki, + emergency: i18n.statsEmergency, + emergencyConfirm: i18n.statsEmergencyConfirm, + emergencyUpdate: i18n.statsEmergencyUpdate, + mailingList: i18n.statsMailingList, + logisticsRoute: i18n.statsLogisticsRoute, + logisticsRating: i18n.statsLogisticsRating, + podcast: i18n.statsPodcast, + podcastEpisode: i18n.statsPodcastEpisode, + podcastPlay: i18n.statsPodcastPlay, + campaign: i18n.statsCampaign, + campaignSignature: i18n.statsCampaignSignature, + campaignUpdate: i18n.statsCampaignUpdate, gameScore: i18n.statsGameScore, + schoolCourse: i18n.statsSchoolCourse, + schoolLesson: i18n.statsSchoolLesson, + schoolEnroll: i18n.statsSchoolEnroll, + schoolCertificate: i18n.statsSchoolCertificate, calendar: i18n.statsCalendar, calendarDate: i18n.statsCalendarDate, calendarNote: i18n.statsCalendarNote, @@ -171,7 +193,8 @@ exports.statsView = (stats, filter) => { const networkCO2 = parseFloat((totalMB * kWhPerMB * gCO2PerKWh).toFixed(2)); const inhabitants = stats.usersKPIs?.totalInhabitants || stats.inhabitants || 1; const userCO2 = parseFloat((networkCO2 / Math.max(1, inhabitants)).toFixed(2)); - const maxAnnualCO2 = 500; + const gCO2PerInhabitantYear = 50; + const maxAnnualCO2 = Math.max(500, Math.max(1, inhabitants) * gCO2PerInhabitantYear); if (filter === 'MINE') { const pct = networkCO2 > 0 ? Math.min(100, (userCO2 / networkCO2) * 100) : 0; @@ -240,7 +263,7 @@ exports.statsView = (stats, filter) => { div({ class: `carbon-bar-fill carbon-bar-network ${wClass(pct)}` }) ), p({ class: 'carbon-bar-note' }, strong(`${pct.toFixed(1)}%`), ` ${i18n.statsCarbonOfEstMax || 'of estimated max capacity'} · `, strong(`${yearPct.toFixed(1)}%`), ` ${i18n.statsCarbonYearProgress || 'of the year elapsed'}`), - p({ class: 'carbon-bar-formula' }, 'Based on local data storage weight ', strong('(0.0002 kWh/MB × 475 g CO₂/kWh)')) + p({ class: 'carbon-bar-formula' }, 'Based on local data storage weight ', strong('(0.0002 kWh/MB × 475 g CO₂/kWh)'), ` · max: ${gCO2PerInhabitantYear} g CO₂ × ${Math.max(1, inhabitants)} inhabitants`) ); })(); @@ -273,40 +296,64 @@ exports.statsView = (stats, filter) => { const totalInhabitants = stats.usersKPIs?.totalInhabitants || stats.inhabitants || 0; const networkKPIs = stats.networkKPIs || {}; - const networkStrip = div({ class: 'stats-block' }, - h3({ class: 'stats-section-h' }, i18n.statsNetworkTitle || 'Network'), - kpiGrid( - kpi(i18n.statsUsersTitle, totalInhabitants), - kpi(i18n.statsTotalMsgs || 'Total messages', networkKPIs.totalMsgs || 0), - kpi(i18n.statsLogsTitle || 'Logs', stats?.logsCount || 0), - kpi(i18n.statsAITraining, C(stats, 'aiExchange') || 0), - kpi(i18n.statsPUBs, stats.pubsCount || 0) - ) - ); + const kvTable = (rows) => { + const shown = rows.filter(([, v]) => !isZero(v)); + if (!shown.length) return p({ class: 'no-content' }, i18n.no_results || 'No data'); + return table({ class: 'tag-table' }, + thead(tr( + th(i18n.statsContentTypeColumn || 'Type'), + th(i18n.statsContentCountColumn || 'Count') + )), + tbody(...shown.map(([label, value]) => tr(td(label), td(String(value))))) + ); + }; + + const blockWithTiles = (cls, title, tiles) => { + const shown = (Array.isArray(tiles) ? tiles : []).filter(Boolean); + if (!shown.length) return null; + return div({ class: cls }, h3({ class: 'stats-section-h' }, title), kpiGrid(...shown)); + }; + + const networkRows = [ + [i18n.statsUsersTitle, totalInhabitants], + [i18n.statsTotalMsgs || 'Total messages', networkKPIs.totalMsgs || 0], + [i18n.statsLogsTitle || 'Logs', stats?.logsCount || 0], + [i18n.statsAITraining, C(stats, 'aiExchange') || 0], + [i18n.statsPUBs, stats.pubsCount || 0], + [i18n.statsSyncedPeers, sharedState.getSyncedPeerCount() || 0] + ]; + const networkStrip = networkRows.some(([, v]) => !isZero(v)) + ? div({ class: 'stats-block' }, + h3({ class: 'stats-section-h' }, i18n.statsNetworkTitle || 'Network'), + kvTable(networkRows) + ) + : null; const carbonCard = div({ id: 'carbon', class: 'stats-card' }, h3({ class: 'stats-section-h' }, i18n.statsCarbonFootprintTitle || 'Carbon Footprint'), carbonChart ); - const networkBlock = div({ class: 'stats-block' }, - h3({ class: 'stats-section-h' }, i18n.statsAveragesTitle || 'Averages'), - kpiGrid( - filter === 'MINE' - ? kpi(i18n.statsMyShare || 'Your share of the network', `${fmtNum(networkKPIs.myShare || 0)}%`) - : null, - kpi(i18n.statsAvgPerInhabitant || 'Avg per inhabitant', fmtNum(networkKPIs.avgMsgsPerInhabitant || 0)), - kpi(i18n.statsMsgsPerDay || 'Messages/day (lifetime)', fmtNum(networkKPIs.networkMsgsPerDay || 0)), - kpi(i18n.statsNetworkSpan || 'Network span', `${fmtNum(networkKPIs.networkSpanDays || 0)} d`), - kpi(i18n.statsTombstoneRatioLabel || 'Tombstone ratio', `${fmtNum(stats.tombstoneKPIs?.ratio || 0)}%`) - ) - ); + const networkBlock = blockWithTiles('stats-block stats-block-boxed', i18n.statsAveragesTitle || 'Averages', [ + filter === 'MINE' + ? kpi(i18n.statsMyShare || 'Your share of the network', `${fmtNum(networkKPIs.myShare || 0)}%`) + : null, + kpi(i18n.statsAvgPerInhabitant || 'Avg per inhabitant', fmtNum(networkKPIs.avgMsgsPerInhabitant || 0)), + kpi(i18n.statsMsgsPerDay || 'Messages/day (lifetime)', fmtNum(networkKPIs.networkMsgsPerDay || 0)), + kpi(i18n.statsNetworkSpan || 'Network span', `${fmtNum(networkKPIs.networkSpanDays || 0)} d`), + kpi(i18n.statsTombstoneRatioLabel || 'Tombstone ratio', `${fmtNum(stats.tombstoneKPIs?.ratio || 0)}%`) + ]); const activityBlock = (() => { const rows = Array.isArray(stats.activity?.daily7) ? stats.activity.daily7 : []; const max = Math.max(1, ...rows.map(r => Number(r.count) || 0)); + if (!rows.length && isZero(stats.activity?.daily7Total || 0) && isZero(stats.activity?.daily30Total || 0)) return null; return div({ class: 'stats-block' }, h2(i18n.statsActivity7d), + kpiGrid( + kpi(i18n.statsActivity7dTotal, stats.activity?.daily7Total || 0), + kpi(i18n.statsActivity30dTotal, stats.activity?.daily30Total || 0) + ), rows.length ? ul({ class: 'stats-toplist' }, ...rows.map(row => { @@ -321,11 +368,7 @@ exports.statsView = (stats, filter) => { ); }) ) - : p({ class: 'no-content' }, i18n.no_results || 'No data'), - div({ class: 'stats-activity-totals' }, - span(`${i18n.statsActivity7dTotal}: `, strong(String(stats.activity?.daily7Total || 0))), - span(`${i18n.statsActivity30dTotal}: `, strong(String(stats.activity?.daily30Total || 0))) - ) + : p({ class: 'no-content' }, i18n.no_results || 'No data') ); })(); @@ -401,8 +444,17 @@ exports.statsView = (stats, filter) => { ); }; - const buildOpinionTiles = () => - types.map(t => O(stats, t) > 0 ? kpi(labels[t], O(stats, t)) : null).filter(Boolean); + const buildOpinionTable = () => { + const rows = types.map(t => [labels[t], O(stats, t)]).filter(([, n]) => n > 0).sort((a, b) => b[1] - a[1]); + if (!rows.length) return p({ class: 'no-content' }, i18n.no_results || 'No data'); + return table({ class: 'tag-table' }, + thead(tr( + th(i18n.statsContentTypeColumn || 'Type'), + th(i18n.statsContentCountColumn || 'Count') + )), + tbody(...rows.map(([label, count]) => tr(td(label), td(String(count))))) + ); + }; const tribeListBlock = (label, list) => div({ class: 'stats-block' }, h2(`${label}: ${list.length}`), @@ -415,16 +467,15 @@ exports.statsView = (stats, filter) => { const allMode = filter === 'ALL' ? div({ class: 'stats-container' }, [ - networkBlock, activityBlock, totalOpinions > 0 - ? div({ class: 'stats-block' }, + ? div({ class: 'stats-block stats-block-boxed' }, h2(`${i18n.statsNetworkOpinions}: ${totalOpinions}`), - kpiGrid(...buildOpinionTiles()) + buildOpinionTable() ) : null, totalContent > 0 - ? div({ class: 'stats-block' }, + ? div({ class: 'stats-block stats-block-boxed' }, h2(`${i18n.statsNetworkContent}: ${totalContent}`), buildContentTable() ) @@ -434,16 +485,15 @@ exports.statsView = (stats, filter) => { const mineMode = filter === 'MINE' ? div({ class: 'stats-container' }, [ - networkBlock, activityBlock, totalOpinions > 0 - ? div({ class: 'stats-block' }, + ? div({ class: 'stats-block stats-block-boxed' }, h2(`${i18n.statsYourOpinions}: ${totalOpinions}`), - kpiGrid(...buildOpinionTiles()) + buildOpinionTable() ) : null, totalContent > 0 - ? div({ class: 'stats-block' }, + ? div({ class: 'stats-block stats-block-boxed' }, h2(`${i18n.statsYourContent}: ${totalContent}`), buildContentTable() ) @@ -456,21 +506,16 @@ exports.statsView = (stats, filter) => { const userTomb = Number(stats.userTombstoneCount || 0); const netTomb = Number(stats.tombstoneKPIs?.networkTombstoneCount || 0); if (userTomb === 0 && netTomb === 0) { - return div({ class: 'stats-container' }, - div({ class: 'stats-block' }, - p({ class: 'no-content' }, i18n.statsTombstoneEmpty || 'No tombstones in the network yet.') - ) + return div({ class: 'stats-block' }, + p({ class: 'no-content' }, i18n.statsTombstoneEmpty || 'No tombstones in the network yet.') ); } - return div({ class: 'stats-container' }, [ - div({ class: 'stats-block' }, - h3({ class: 'stats-section-h' }, i18n.TOMBSTONEButton), - kpiGrid( - kpi(i18n.TOMBSTONEButton, userTomb), - kpi(i18n.statsTombstoneRatio, `${(stats.tombstoneKPIs?.ratio || 0).toFixed(2)}%`) - ) + return div({ class: 'stats-block stats-block-boxed' }, + kpiGrid( + kpi(i18n.TOMBSTONEButton, userTomb), + kpi(i18n.statsTombstoneRatio, `${(stats.tombstoneKPIs?.ratio || 0).toFixed(2)}%`) ) - ]); + ); })() : null; @@ -493,7 +538,7 @@ exports.statsView = (stats, filter) => { return template( title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(title), p(description) ), @@ -506,11 +551,12 @@ exports.statsView = (stats, filter) => { ) ), section( - filter === 'ALL' ? networkStrip : null, filter === 'MINE' ? accountCard : null, filter === 'ALL' ? storageCard : null, + filter === 'ALL' ? networkStrip : null, tombMode, - carbonCard, + filter === 'TOMBSTONE' ? null : networkBlock, + filter === 'TOMBSTONE' && !(Number(stats.userTombstoneCount || 0) || Number(stats.tombstoneKPIs?.networkTombstoneCount || 0)) ? null : carbonCard, filter !== 'TOMBSTONE' ? ecoTaxBlock : null, allMode, mineMode diff --git a/src/views/tags_view.js b/src/views/tags_view.js index 32c9a17..eb7069d 100644 --- a/src/views/tags_view.js +++ b/src/views/tags_view.js @@ -1,5 +1,5 @@ const { form, button, div, h2, p, section, table, thead, tr, th, td, a, tbody, input } = require("../server/node_modules/hyperaxe"); -const { template, i18n } = require('./main_views'); +const { template, i18n, renderModuleStats, paged } = require('./main_views'); const getFilteredTags = (filter, tags) => { const filteredTags = Array.isArray(tags) ? [...tags] : []; @@ -31,26 +31,11 @@ const renderTagsTable = (filteredTags) => { const renderTagsCloud = (mergedTags) => { const maxCount = Math.max(...mergedTags.map(t => t.count), 1); return div({ class: 'tag-cloud-wrap' }, - mergedTags.map(tag => { - const angle = Math.random() * 2 * Math.PI; - const radius = 10 + Math.random() * 40; - const x = 50 + Math.cos(angle) * radius; - const y = 50 + Math.sin(angle) * radius; - const weight = tag.count / maxCount; - const fontSize = 12 + Math.round(weight * 32); - const hue = 200 + Math.round(weight * 120); - - return a({ - href: `/search?query=%23${encodeURIComponent(tag.name)}`, - class: 'tag-cloud-item', - style: ` - left: ${x}%; - top: ${y}%; - font-size: ${fontSize}px; - color: hsl(${hue},70%,60%); - ` - }, tag.name); - }) + mergedTags.map(tag => a({ + href: `/search?query=%23${encodeURIComponent(tag.name)}`, + class: 'tag-cloud-item', + 'data-weight': String(1 + Math.round((tag.count / maxCount) * 4)) + }, tag.name)) ); }; @@ -60,26 +45,28 @@ exports.tagsView = async (tags, filter, search = '') => { const title = i18n.tagsTitle; + const emptyTags = filteredTags.length === 0 && ['top', 'all'].includes(String(filter || 'top')) && !query; return template( title, section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.tagsTitle), p(i18n.tagsDescription) ), - div({ class: 'filters' }, + emptyTags ? null : div({ class: 'filters' }, form({ method: 'GET', action: '/tags', class: 'ui-toolbar ui-toolbar--filters' }, input({ type: 'hidden', name: 'search', value: query }), - button({ type: 'submit', name: 'filter', value: 'all', class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterAll).toUpperCase()), - button({ type: 'submit', name: 'filter', value: 'mine', class: filter === 'mine' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterMine).toUpperCase()), - button({ type: 'submit', name: 'filter', value: 'recent', class: filter === 'recent' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterRecent).toUpperCase()), button({ type: 'submit', name: 'filter', value: 'top', class: filter === 'top' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterTop).toUpperCase()), + button({ type: 'submit', name: 'filter', value: 'all', class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterAll).toUpperCase()), + button({ type: 'submit', name: 'filter', value: 'recent', class: filter === 'recent' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterRecent).toUpperCase()), + button({ type: 'submit', name: 'filter', value: 'mine', class: filter === 'mine' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterMine).toUpperCase()), button({ type: 'submit', name: 'filter', value: 'cloud', class: filter === 'cloud' ? 'filter-btn active' : 'filter-btn' }, String(i18n.tagsFilterCloud).toUpperCase()) ) ), - div({ class: 'tags-search' }, + emptyTags ? null : div({ class: 'tags-search activity-filter-chips activity-toolbar-row' }, + renderModuleStats(filteredTags.length), form({ method: 'GET', action: '/tags', class: 'filter-box' }, - input({ type: 'hidden', name: 'filter', value: filter || 'all' }), + input({ type: 'hidden', name: 'filter', value: filter || 'top' }), input({ type: 'text', name: 'search', value: query, placeholder: i18n.tagsSearchPlaceholder, class: 'filter-box__input' }), div({ class: 'filter-box__controls' }, button({ type: 'submit', class: 'filter-box__button' }, i18n.searchButton)) ) @@ -88,7 +75,7 @@ exports.tagsView = async (tags, filter, search = '') => { filteredTags.length === 0 ? p(query ? i18n.noResultsFound : i18n.tagsNoItems) : filter !== 'cloud' - ? renderTagsTable(filteredTags) + ? renderTagsTable(paged(filteredTags)) : renderTagsCloud(filteredTags) ) ) diff --git a/src/views/task_view.js b/src/views/task_view.js index 3beb4e6..089e534 100644 --- a/src/views/task_view.js +++ b/src/views/task_view.js @@ -1,20 +1,21 @@ const { div, h2, p, section, button, form, input, select, option, a, br, textarea, label, span, table, tr, td, img, video } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); const moment = require("../server/node_modules/moment"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderPrivacyChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderSpreadEditWarning, renderContentActions, renderDocumentActions } = require("./main_views"); -const { renderPhotoGallery, renderGalleryFields, imagesOf } = require("./gallery_view"); +const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderPrivacyChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderSpreadEditWarning, renderContentActions, renderDocumentActions, renderModuleStatsBy, moduleIsEmpty, contentDeleteAction, paged, renderCreatedRows } = require("./main_views"); +const { renderPhotoGallery, renderGalleryFields, imagesOf, renderZoomableImage } = require("./gallery_view"); +const { renderTribeOriginChip } = require("./clearnet_view"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const renderTaskMediaBlob = (value, attrs = {}) => { if (!value) return null; const s = String(value).trim(); if (!s) return null; - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }); + if (s.startsWith('&')) return renderZoomableImage(`/blob/${encodeURIComponent(s)}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }); const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, class: attrs.class || 'post-image' }); + if (mImg) return renderZoomableImage(`/blob/${encodeURIComponent(mImg[1])}`, { imgClass: attrs.class || 'post-image', alt: attrs.alt || '' }); const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/); - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }); + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }); return null; }; @@ -54,21 +55,16 @@ const renderTaskOwnerActions = (task, returnTo) => { input({ type: "hidden", name: "returnTo", value: returnTo }), button({ type: "submit", class: "update-btn" }, i18n.taskUpdateButton) ), - form( - { method: "POST", action: `/tasks/delete/${encodeURIComponent(task.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ type: "submit", class: "delete-btn" }, i18n.taskDeleteButton) - ), ]; }; -const renderTaskStatusRow = (task, returnTo) => { +const renderTaskStatusRow = exports.renderTaskStatusRow = (task, returnTo, action = null) => { const st = normalizeStatus(task.status || "OPEN"); return div({ class: "tribe-side-actions housing-status-row" }, span({ class: "card-label" }, `${i18n.taskStatus}: `), renderStateChip(st === "CLOSED" ? "hidden" : "mutuals", st === "CLOSED" ? "🔒" : "👁", statusLabel(task.status)), form( - { method: "POST", action: `/tasks/status/${encodeURIComponent(task.id)}`, class: "project-control-form project-control-form--status" }, + { method: "POST", action: action || `/tasks/status/${encodeURIComponent(task.id)}`, class: "project-control-form project-control-form--status" }, input({ type: "hidden", name: "returnTo", value: returnTo }), select( { name: "status", class: "project-control-select" }, @@ -81,7 +77,7 @@ const renderTaskStatusRow = (task, returnTo) => { ); }; -const renderTaskAssignAction = (task, isAssignedToMe, returnTo) => { +const renderTaskAssignAction = exports.renderTaskAssignAction = (task, isAssignedToMe, returnTo, action = null) => { const st = normalizeStatus(task.status || "OPEN"); if (st === "CLOSED") return null; return [ @@ -89,7 +85,7 @@ const renderTaskAssignAction = (task, isAssignedToMe, returnTo) => { renderStateChip(isAssignedToMe ? "mutuals" : "whole", isAssignedToMe ? "👤" : "○", String(isAssignedToMe ? i18n.taskAssignedChip : i18n.taskUnassignedChip).toUpperCase()), form( - { method: "POST", action: `/tasks/assign/${encodeURIComponent(task.id)}`, class: "project-control-form" }, + { method: "POST", action: action || `/tasks/assign/${encodeURIComponent(task.id)}`, class: "project-control-form" }, input({ type: "hidden", name: "returnTo", value: returnTo }), button({ type: "submit", class: "status-btn project-control-btn" }, isAssignedToMe ? i18n.taskUnassignButton : i18n.taskAssignButton) ) @@ -116,11 +112,12 @@ const renderTaskPriorityChip = (priority) => { const p = String(priority || "").toUpperCase(); if (!p) return null; const variant = - p === "URGENT" ? "closed" : + p === "URGENT" || p === "CRITICAL" ? "closed" : p === "HIGH" ? "lifespan-orange" : p === "MEDIUM" ? "whole" : "mutuals"; const localized = + p === "CRITICAL" ? i18n.tribePriorityCritical : p === "URGENT" ? i18n.taskPriorityUrgent : p === "HIGH" ? i18n.taskPriorityHigh : p === "MEDIUM" ? i18n.taskPriorityMedium : @@ -128,12 +125,15 @@ const renderTaskPriorityChip = (priority) => { return renderStateChip(variant, "⚑", localized || p); }; -const renderTaskItem = (task, filter, spreadInfo) => { +const renderTaskItem = exports.renderTaskItem = (task, filter, spreadInfo, opts = {}) => { const currentFilter = filter || "all"; const assignees = safeArray(task.assignees); const isPrivate = String(task.isPublic || "").toUpperCase() === "PRIVATE"; + const isAuthor = String(task.author) === String(userId); + const origin = task.tribeOrigin || null; + const href = opts.href !== undefined ? opts.href : (origin ? origin.href : `/tasks/${encodeURIComponent(task.id)}`); - const cover = imagesOf(task)[0] + const cover = origin ? null : imagesOf(task)[0] const heroNode = cover ? div({ class: "tribe-card-image-wrapper" }, a({ href: `/tasks/${encodeURIComponent(task.id)}` }, @@ -149,36 +149,49 @@ const renderTaskItem = (task, filter, spreadInfo) => { : null; const chips = [ - renderTaskStatusChip(task.status), isPrivate ? renderPrivacyChip(true, i18n) : null, + renderTaskStatusChip(task.status), renderTaskPriorityChip(task.priority), - renderLifespanChip(task.lifetime, i18n) + renderLifespanChip(task.lifetime, i18n), + origin ? renderTribeOriginChip(origin) : null, + ...(Array.isArray(opts.extraChips) ? opts.extraChips : []) ].filter(Boolean); - const start = task.startTime ? moment(task.startTime).format("YYYY/MM/DD HH:mm") : ""; - const end = task.endTime ? moment(task.endTime).format("YYYY/MM/DD HH:mm") : ""; + const headerActions = opts.headerActions !== undefined + ? opts.headerActions + : origin ? null : renderContentActions(task.id, `/tasks/${encodeURIComponent(task.id)}`, { spread: spreadInfo || null, author: task.author, favKind: 'tasks', isFavorite: task.isFavorite, reportTitle: task.title, returnTo: `/tasks?filter=${encodeURIComponent(currentFilter)}`, deleteAction: isAuthor ? contentDeleteAction('task', task.id) : undefined }); - return div({ class: "tribe-card task-card" }, - div({ class: "card-header activity-card-header" }, - span(), - renderContentActions(task.id, `/tasks/${encodeURIComponent(task.id)}`, { spread: spreadInfo || null, author: task.author, favKind: 'tasks', isFavorite: task.isFavorite, reportTitle: task.title }) - ), + return div({ class: "tribe-card task-card" + (isAuthor ? " own-content" : "") }, + headerActions ? div({ class: "card-header activity-card-header" }, span(), headerActions) : null, heroNode, div({ class: "tribe-card-body" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, - a({ href: `/tasks/${encodeURIComponent(task.id)}` }, task.title || i18n.tasksTitle) + href ? a({ href }, task.title || i18n.tasksTitle) : (task.title || i18n.tasksTitle) ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - (start || end) ? p({ class: "card-date-highlight" }, start && end ? `${start} → ${end}` : (start || end)) : null, - div({ class: "tribe-card-members" }, + origin ? null : div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.taskAssignedTo}: ${assignees.length}`) - ) + ), + ...(Array.isArray(opts.bodyExtra) ? opts.bodyExtra.filter(Boolean) : []) ) ); }; +const taskChipFor = (currentFilter, visible) => (mode) => { + if (mode === currentFilter) return true; + if (!Array.isArray(visible)) return true; + if (mode === "mine") return visible.some((t) => t.author === userId); + if (mode === "recent") return visible.length > 0; + if (mode === "assigned") return visible.some((t) => safeArray(t.assignees).includes(userId)); + if (mode === "open") return visible.some((t) => normalizeStatus(t.status) === "OPEN"); + if (mode === "in-progress") return visible.some((t) => normalizeStatus(t.status) === "IN-PROGRESS"); + if (mode === "closed") return visible.some((t) => normalizeStatus(t.status) === "CLOSED"); + if (mode.indexOf("priority-") === 0) { const pr = mode.slice(9).toUpperCase(); return visible.some((t) => String(t.priority).toUpperCase() === pr); } + return true; +}; + exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { const list = Array.isArray(tasks) ? tasks : [tasks]; const currentFilter = filter || "all"; @@ -195,7 +208,8 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { const visible = list.filter(canSee); let filtered; - if (currentFilter === "mine") filtered = visible.filter((t) => t.author === userId); + if (currentFilter === "recent") filtered = visible.filter((t) => (Date.parse(t.createdAt || "") || 0) >= Date.now() - 86400000); + else if (currentFilter === "mine") filtered = visible.filter((t) => t.author === userId); else if (currentFilter === "assigned") filtered = visible.filter((t) => safeArray(t.assignees).includes(userId)); else if (currentFilter === "open") filtered = visible.filter((t) => normalizeStatus(t.status) === "OPEN"); else if (currentFilter === "in-progress") filtered = visible.filter((t) => normalizeStatus(t.status) === "IN-PROGRESS"); @@ -216,12 +230,14 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { const ret = typeof returnTo === "string" && returnTo.startsWith("/tasks") ? returnTo : "/tasks?filter=mine"; + const emptyMod = moduleIsEmpty(filtered, currentFilter, "all", params.q); + const taskChipVisible = taskChipFor(currentFilter, visible); return template( title, section( div( - { class: "tags-header" }, + { class: "tags-header module-header-line" }, h2(i18n.tasksTitle), p(i18n.tasksDescription) ), @@ -229,22 +245,26 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { { class: "filters" }, form( { method: "GET", action: "/tasks" }, + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: currentFilter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "assigned", class: currentFilter === "assigned" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterAssigned).toUpperCase()), - button({ type: "submit", name: "filter", value: "open", class: currentFilter === "open" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterOpen).toUpperCase()), - button({ type: "submit", name: "filter", value: "in-progress", class: currentFilter === "in-progress" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterInProgress).toUpperCase()), - button({ type: "submit", name: "filter", value: "closed", class: currentFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterClosed).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-low", class: currentFilter === "priority-low" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterLow).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-medium", class: currentFilter === "priority-medium" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMedium).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-high", class: currentFilter === "priority-high" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterHigh).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-urgent", class: currentFilter === "priority-urgent" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterUrgent).toUpperCase()), + ...(taskChipVisible("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: currentFilter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterRecent).toUpperCase())] : []), + ...(taskChipVisible("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMine).toUpperCase())] : []), + ...(taskChipVisible("assigned") ? [button({ type: "submit", name: "filter", value: "assigned", class: currentFilter === "assigned" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterAssigned).toUpperCase())] : []), + ...(taskChipVisible("open") ? [button({ type: "submit", name: "filter", value: "open", class: currentFilter === "open" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterOpen).toUpperCase())] : []), + ...(taskChipVisible("in-progress") ? [button({ type: "submit", name: "filter", value: "in-progress", class: currentFilter === "in-progress" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterInProgress).toUpperCase())] : []), + ...(taskChipVisible("closed") ? [button({ type: "submit", name: "filter", value: "closed", class: currentFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterClosed).toUpperCase())] : []), + ...(taskChipVisible("priority-low") ? [button({ type: "submit", name: "filter", value: "priority-low", class: currentFilter === "priority-low" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterLow).toUpperCase())] : []), + ...(taskChipVisible("priority-medium") ? [button({ type: "submit", name: "filter", value: "priority-medium", class: currentFilter === "priority-medium" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMedium).toUpperCase())] : []), + ...(taskChipVisible("priority-high") ? [button({ type: "submit", name: "filter", value: "priority-high", class: currentFilter === "priority-high" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterHigh).toUpperCase())] : []), + ...(taskChipVisible("priority-urgent") ? [button({ type: "submit", name: "filter", value: "priority-urgent", class: currentFilter === "priority-urgent" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterUrgent).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.taskCreateButton) ) ), currentFilter === "edit" || currentFilter === "create" ? null - : div({ class: "filters" }, + : emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filtered, t => normalizeStatus(t.status || 'OPEN'), [{ value: 'OPEN', label: i18n.taskStatusOpen }, { value: 'IN-PROGRESS', label: i18n.taskStatusInProgress }, { value: 'CLOSED', label: i18n.taskStatusClosed }]), form({ method: "GET", action: "/tasks", class: "filter-box" }, input({ type: "hidden", name: "filter", value: currentFilter }), input({ type: "text", name: "q", value: params.q || "", placeholder: i18n.taskSearchPlaceholder, class: "filter-box__input" }), @@ -265,7 +285,7 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { label(i18n.taskTitleLabel), br(), input({ type: "text", name: "title", maxlength: "100", required: true, value: formData.title || params.prefillTitle || "" }), br(), label(i18n.taskDescriptionLabel), br(), - textarea({ name: "description", required: true, placeholder: i18n.taskDescriptionPlaceholder, rows: "4" }, formData.description || params.prefillDescription || ""), br(), + textarea({ maxlength: "5000", name: "description", required: true, placeholder: i18n.taskDescriptionPlaceholder, rows: "4" }, formData.description || params.prefillDescription || ""), br(), ...renderGalleryFields(formData, currentFilter === "edit"), br(), label(i18n.taskStartTimeLabel), br(), @@ -273,7 +293,7 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { type: "datetime-local", name: "startTime", required: true, - min: currentFilter === "create" ? minCreate : undefined, + min: minCreate, value: formData.startTime ? moment(formData.startTime).format("YYYY-MM-DDTHH:mm") : "" }), br(), br(), label(i18n.taskEndTimeLabel), br(), @@ -281,7 +301,7 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { type: "datetime-local", name: "endTime", required: true, - min: currentFilter === "create" ? minCreate : undefined, + min: minCreate, value: formData.endTime ? moment(formData.endTime).format("YYYY-MM-DDTHH:mm") : "" }), br(), br(), label(i18n.taskPriorityLabel), br(), @@ -306,13 +326,14 @@ exports.taskView = async (tasks, filter, taskId, returnTo, params = {}) => { ) ) : filtered.length > 0 - ? div({ class: "jobs-grid" }, filtered.map((t) => renderTaskItem(t, currentFilter, params.spreadMap && params.spreadMap.get(t.id)))) - : p(i18n.notasks) + ? div({ class: "jobs-grid" }, paged(filtered).map((t) => renderTaskItem(t, currentFilter, params.spreadMap && params.spreadMap.get(t.id)))) + : div({ class: "no-content-box" }, p(i18n.notasks)) ) ); }; exports.singleTaskView = async (task, filter, comments = [], params = {}) => { + const taskChipVisible = (mode) => taskChipFor(currentFilter, Array.isArray(params.censusList) ? params.censusList : null)(mode); const currentFilter = filter || "all"; const assignees = safeArray(task.assignees); const commentCount = typeof task.commentCount === "number" ? task.commentCount : 0; @@ -326,15 +347,16 @@ exports.singleTaskView = async (task, filter, comments = [], params = {}) => { form( { method: "GET", action: "/tasks" }, button({ type: "submit", name: "filter", value: "all", class: currentFilter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "assigned", class: currentFilter === "assigned" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterAssigned).toUpperCase()), - button({ type: "submit", name: "filter", value: "open", class: currentFilter === "open" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterOpen).toUpperCase()), - button({ type: "submit", name: "filter", value: "in-progress", class: currentFilter === "in-progress" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterInProgress).toUpperCase()), - button({ type: "submit", name: "filter", value: "closed", class: currentFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterClosed).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-low", class: currentFilter === "priority-low" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterLow).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-medium", class: currentFilter === "priority-medium" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMedium).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-high", class: currentFilter === "priority-high" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterHigh).toUpperCase()), - button({ type: "submit", name: "filter", value: "priority-urgent", class: currentFilter === "priority-urgent" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterUrgent).toUpperCase()), + ...(taskChipVisible("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: currentFilter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterRecent).toUpperCase())] : []), + ...(taskChipVisible("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: currentFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMine).toUpperCase())] : []), + ...(taskChipVisible("assigned") ? [button({ type: "submit", name: "filter", value: "assigned", class: currentFilter === "assigned" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterAssigned).toUpperCase())] : []), + ...(taskChipVisible("open") ? [button({ type: "submit", name: "filter", value: "open", class: currentFilter === "open" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterOpen).toUpperCase())] : []), + ...(taskChipVisible("in-progress") ? [button({ type: "submit", name: "filter", value: "in-progress", class: currentFilter === "in-progress" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterInProgress).toUpperCase())] : []), + ...(taskChipVisible("closed") ? [button({ type: "submit", name: "filter", value: "closed", class: currentFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterClosed).toUpperCase())] : []), + ...(taskChipVisible("priority-low") ? [button({ type: "submit", name: "filter", value: "priority-low", class: currentFilter === "priority-low" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterLow).toUpperCase())] : []), + ...(taskChipVisible("priority-medium") ? [button({ type: "submit", name: "filter", value: "priority-medium", class: currentFilter === "priority-medium" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterMedium).toUpperCase())] : []), + ...(taskChipVisible("priority-high") ? [button({ type: "submit", name: "filter", value: "priority-high", class: currentFilter === "priority-high" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterHigh).toUpperCase())] : []), + ...(taskChipVisible("priority-urgent") ? [button({ type: "submit", name: "filter", value: "priority-urgent", class: currentFilter === "priority-urgent" ? "filter-btn active" : "filter-btn" }, String(i18n.taskFilterUrgent).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.taskCreateButton) ) ); @@ -355,8 +377,8 @@ exports.singleTaskView = async (task, filter, comments = [], params = {}) => { const ecoTaxChipNode = renderEcoTax(task.msgSize, task.id); const chips = [ - renderTaskStatusChip(task.status), renderPrivacyChip(isPrivate, i18n), + renderTaskStatusChip(task.status), renderTaskPriorityChip(task.priority), lifespanChipNode, ecoTaxChipNode @@ -392,14 +414,14 @@ exports.singleTaskView = async (task, filter, comments = [], params = {}) => { const taskSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(task.id, null, { spread: params.spreads || null, author: task.author, favKind: 'tasks', isFavorite: task.isFavorite, reportTitle: task.title }) + renderContentActions(task.id, `/tasks/${encodeURIComponent(task.id)}`, { spread: params.spreads || null, author: task.author, favKind: 'tasks', isFavorite: task.isFavorite, reportTitle: task.title, deleteAction: isAuthor ? contentDeleteAction('task', task.id) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, task.title) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, renderPhotoGallery(task, 'task'), - table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), + table({ class: "tribe-info-table jobs-info-table" }, ...infoRows, ...renderCreatedRows(task.createdAt, task.author)), assignNode ? div({ class: "tribe-side-actions housing-status-row task-assign-row" }, ...assignNode) : null, isAuthor ? renderTaskStatusRow(task, returnToSelf) : null, tagsNode, @@ -419,20 +441,16 @@ exports.singleTaskView = async (task, filter, comments = [], params = {}) => { task.description ? div({ class: "job-section" }, h2({ class: "job-section-title" }, i18n.taskDescriptionLabel), - p({ class: "tribe-side-description" }, ...renderUrl(task.description)) + p({ class: "tribe-side-description" }, ...renderStyledText(task.description)) ) : null, - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(task.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(task.author) - ), renderEngagement(task.id, opinionsBar, renderTaskCommentsSection(task.id, comments, currentFilter)) ); return template( task.title, section( - div({ class: "tags-header" }, h2(i18n.tasksTitle), p(i18n.tasksDescription)), + div({ class: "tags-header module-header-line" }, h2(i18n.tasksTitle), p(i18n.tasksDescription)), filterBar, div({ class: "tribe-details" }, taskSide, taskMain) ) diff --git a/src/views/torrents_view.js b/src/views/torrents_view.js index cef7a56..f948fea 100644 --- a/src/views/torrents_view.js +++ b/src/views/torrents_view.js @@ -1,29 +1,11 @@ -const { - form, - button, - div, - h2, - p, - section, - input, - br, - a, - span, - textarea, - select, - label, - option, - table, - tr, - th, - td -} = require("../server/node_modules/hyperaxe"); +const { form, button, div, h2, h3, p, section, input, br, a, span, textarea, select, label, option, table, tr, th, td, progress, strong } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); +const { renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require('./clearnet_view'); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip , renderSpreadEditWarning, renderContentActions } = require("./main_views"); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip , renderSpreadEditWarning, renderContentActions, renderModuleStats, moduleIsEmpty, renderTorrentDownload, torrentDownloadHref, renderTorrentSourceDownload, contentDeleteAction, paged } = require("./main_views"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderStyledText } = require("../backend/renderStyledText"); const userId = config.keys.id; @@ -67,13 +49,6 @@ const renderTorrentOwnerActions = (filter, torrentObj, params = {}) => { ) ); } - items.push( - form( - { method: "POST", action: `/torrents/delete/${encodeURIComponent(torrentObj.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.torrentDeleteButton) - ) - ); return items; }; @@ -86,6 +61,12 @@ const renderTorrentCommentsSection = (torrentId, comments = [], returnTo = null) }); }; +const blobSha256Hex = (blobId) => { + const m = /^&([A-Za-z0-9+/=]+)\.sha256$/.exec(String(blobId || "")); + if (!m) return ""; + try { return Buffer.from(m[1], "base64").toString("hex"); } catch (_) { return ""; } +}; + const formatSize = (bytes) => { const n = Number(bytes) || 0; if (n === 0) return "—"; @@ -94,6 +75,13 @@ const formatSize = (bytes) => { return (n / (1024 * 1024)).toFixed(1) + " MB"; }; +const spreadInfoOf = (t, spreadMap) => (spreadMap instanceof Map && spreadMap.get(t.key)) || null; +const renderTorrentSeeds = (t, spreadMap) => { + const info = spreadInfoOf(t, spreadMap); + return String(info && typeof info.count === "number" ? info.count : (info && Array.isArray(info.voters) ? info.voters.length : 0)); +}; +const renderTorrentSpread = (t, spreadMap) => String(t.author) === String(userId) ? "" : renderSpreadButton(t.key, spreadInfoOf(t, spreadMap), i18n.seedAction, false); + const renderTorrentTable = exports.renderTorrentTable = (torrents, filter, params = {}) => { const returnTo = buildReturnTo(filter, params); @@ -106,38 +94,66 @@ const renderTorrentTable = exports.renderTorrentTable = (torrents, filter, param th(i18n.authorLabel || "AUTHOR"), th(i18n.torrentTitleLabel || "TITLE"), th(i18n.torrentSizeLabel || "SIZE"), + th(i18n.seedsLabel), + th(i18n.seedAction), + th(""), th(""), th("") ), - torrents.map((t) => - tr( - td(moment(t.createdAt).format("YYYY/MM/DD HH:mm")), - td(userLink(t.author)), - td(t.title || ""), - td(formatSize(t.size)), + torrents.map((t) => { + const row = typeof params.rowFor === "function" ? params.rowFor(t) : null; + return tr( + td({ class: "row-chips" }, t.tribeOrigin ? renderTribeOriginChip(t.tribeOrigin) : t.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('torrents', t.title, t.key)) : null, ...(row && Array.isArray(row.titleChips) ? row.titleChips.filter(Boolean) : [])), + td({ "data-label": i18n.createdAt || "DATE" }, moment(t.createdAt).format("YYYY/MM/DD HH:mm")), + td({ class: "row-author" }, userLink(t.author)), + td({ class: "row-title", "data-label": i18n.torrentTitleLabel || "TITLE" }, t.title || ""), + td({ "data-label": i18n.torrentSizeLabel || "SIZE" }, formatSize(t.size)), + td({ class: "torrent-spread-cell", "data-label": i18n.seedsLabel }, t.tribeOrigin || row ? "" : renderTorrentSeeds(t, params.spreadMap)), + td({ class: "torrent-spread-cell", "data-label": i18n.seedAction }, t.tribeOrigin || row ? "" : renderTorrentSpread(t, params.spreadMap)), td( - form( - { method: "GET", action: `/torrents/${encodeURIComponent(t.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - input({ type: "hidden", name: "filter", value: filter || "all" }), - params.q ? input({ type: "hidden", name: "q", value: params.q }) : null, - params.sort ? input({ type: "hidden", name: "sort", value: params.sort }) : null, - button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) - ) + row + ? (row.details || "") + : t.tribeOrigin + ? a({ href: t.tribeOrigin.href, class: "filter-btn" }, i18n.torrentDetailsButton) + : form( + { method: "GET", action: `/torrents/${encodeURIComponent(t.key)}` }, + input({ type: "hidden", name: "returnTo", value: returnTo }), + input({ type: "hidden", name: "filter", value: filter || "all" }), + params.q ? input({ type: "hidden", name: "q", value: params.q }) : null, + params.sort ? input({ type: "hidden", name: "sort", value: params.sort }) : null, + button({ type: "submit", class: "filter-btn" }, i18n.torrentDetailsButton) + ) ), td( - t.url && t.url.startsWith("&") - ? a({ href: `/blob/${encodeURIComponent(t.url)}`, class: "filter-btn" }, i18n.torrentDownloadButton || "DOWNLOAD IT!") + row + ? (row.download || "") + : t.url && t.url.startsWith("&") + ? div({ class: "torrent-card-actions" }, renderTorrentSourceDownload(t.key, t.source), renderTorrentDownload(torrentDownloadHref(t.url, t.title))) : "" + ), + td({ class: "row-actions" }, + row + ? (row.actions || "") + : t.tribeOrigin ? "" : renderContentActions(t.key, `/torrents/${encodeURIComponent(t.key)}`, { + author: t.author, + favKind: 'torrents', + isFavorite: t.isFavorite, + returnTo, + reportTitle: t.title, + deleteAction: String(t.author) === String(userId) ? contentDeleteAction('torrent', t.key) : undefined + }) ) - ) - ) + ); + }) ); }; const renderTorrentForm = (filter, torrentId, torrentToEdit, params = {}) => { const returnTo = safeText(params.returnTo) || buildReturnTo("all", params); const tribeId = safeText(params.tribeId || ""); + const fromBlob = filter === "edit" ? "" : safeText(params.fromBlob || ""); + const fromName = safeText(params.fromName || ""); + const clearnetAllowed = !tribeId && !torrentToEdit?.tribeId && !torrentToEdit?.encrypted && !torrentToEdit?.cipher; return div( { class: "div-center audio-form" }, params.spreadWarning || null, @@ -149,18 +165,29 @@ const renderTorrentForm = (filter, torrentId, torrentToEdit, params = {}) => { }, input({ type: "hidden", name: "returnTo", value: returnTo }), tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, - span(i18n.torrentFileLabel), - br(), - input({ type: "file", name: "torrent", accept: ".torrent", required: filter !== "edit" }), - br(), - br(), + fromBlob + ? div({ class: "torrent-from-notice" }, + input({ type: "hidden", name: "fromBlob", value: fromBlob }), + input({ type: "hidden", name: "fromName", value: fromName }), + table({ class: "tribe-info-table torrent-file-info" }, + tr(td({ class: "tribe-info-label" }, i18n.fileShareFileLabel), td({ class: "tribe-info-value" }, fromName || fromBlob.slice(0, 16))), + tr(td({ class: "tribe-info-label" }, i18n.torrentSizeLabel), td({ class: "tribe-info-value" }, formatSize(Number(params.fromSize) || 0))), + tr(td({ class: "tribe-info-label" }, "SHA-256"), td({ class: "tribe-info-value" }, span({ class: "bank-address-code" }, blobSha256Hex(fromBlob)))) + ) + ) + : null, + fromBlob ? null : span(i18n.torrentFileLabel), + fromBlob ? null : br(), + fromBlob ? null : input({ type: "file", name: "torrent", accept: ".torrent", required: filter !== "edit" }), + fromBlob ? null : br(), + fromBlob ? null : br(), span(i18n.torrentTitleLabel), br(), - input({ type: "text", name: "title", maxlength: "100", placeholder: i18n.torrentTitlePlaceholder, value: torrentToEdit?.title || "", required: true }), + input({ type: "text", name: "title", maxlength: "100", placeholder: i18n.torrentTitlePlaceholder, value: torrentToEdit?.title || fromName || "", required: true }), br(), span(i18n.torrentDescriptionLabel), br(), - textarea({ name: "description", placeholder: i18n.torrentDescriptionPlaceholder, rows: "4" }, torrentToEdit?.description || ""), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.torrentDescriptionPlaceholder, rows: "4" }, torrentToEdit?.description || ""), br(), span(i18n.torrentTagsLabel), br(), @@ -172,11 +199,92 @@ const renderTorrentForm = (filter, torrentId, torrentToEdit, params = {}) => { }), br(), br(), + ...(clearnetAllowed ? [renderClearnetSelector(filter === "edit" ? !!torrentToEdit?.clearnet : false, i18n), br()] : []), button({ type: "submit" }, filter === "edit" ? i18n.torrentUpdateButton : i18n.torrentCreateButton) ) ); }; +const torrentStateLabel = (state) => { + const map = { queued: i18n.torrentStateQueued, probing: i18n.torrentStateProbing, downloading: i18n.torrentStateDownloading, done: i18n.torrentStateDone, failed: i18n.torrentStateFailed, nosource: i18n.torrentStateNoSource, cancelled: i18n.torrentStateCancelled }; + return map[state] || String(state || ""); +}; + +const sourceSaveHref = (blobId, name) => `/blob/${encodeURIComponent(blobId)}?download=1&name=${encodeURIComponent(String(name || "download"))}`; + +const renderDownloadRow = (d) => { + const pct = d.size > 0 ? Math.min(100, Math.floor((d.received / d.size) * 100)) : (d.state === "done" ? 100 : 0); + const titleNode = d.torrentKey ? a({ href: `/torrents/${encodeURIComponent(d.torrentKey)}` }, d.name || d.blobId.slice(0, 12)) : span(d.name || d.blobId.slice(0, 12)); + const actions = d.state === "done" + ? a({ href: sourceSaveHref(d.blobId, d.name), class: "filter-btn" }, i18n.torrentSaveFile) + : d.active + ? form({ method: "POST", action: `/torrents/downloads/${encodeURIComponent(d.blobId)}/cancel` }, button({ type: "submit", class: "filter-btn" }, i18n.torrentCancel)) + : form({ method: "POST", action: `/torrents/downloads/${encodeURIComponent(d.blobId)}/remove` }, button({ type: "submit", class: "filter-btn" }, "✕")); + return tr({ class: "torrent-download-row torrent-state-" + d.state }, + td(titleNode), + td(d.size ? formatSize(d.size) : "—"), + td(String(d.seeds || 0)), + td({ class: "torrent-progress-cell" }, progress({ value: String(pct), max: "100", class: "torrent-progress" }), span({ class: "torrent-progress-text" }, ` ${pct}%`)), + td(torrentStateLabel(d.state)), + td(actions) + ); +}; + +const renderDownloads = (downloads) => { + const list = safeArr(downloads); + if (!list.length) return p(i18n.torrentNoDownloads); + return table( + { border: "1", class: "torrent-table torrent-downloads" }, + tr( + th(i18n.torrentSourceFile), + th(i18n.torrentSizeLabel), + th(i18n.torrentSeeds), + th(""), + th(""), + th("") + ), + ...list.map(renderDownloadRow) + ); +}; + +const renderOasisDownload = (torrentObj, params = {}) => { + if (!torrentObj.source || params.isPublic) return null; + const d = params.download || null; + const rows = [ + tr(td({ class: "tribe-info-label" }, i18n.fileShareFileLabel), td({ class: "tribe-info-value" }, torrentObj.sourceName || torrentObj.source.slice(0, 16))), + tr(td({ class: "tribe-info-label" }, i18n.torrentSizeLabel), td({ class: "tribe-info-value" }, formatSize(torrentObj.size))), + tr(td({ class: "tribe-info-label" }, "SHA-256"), td({ class: "tribe-info-value" }, span({ class: "bank-address-code" }, blobSha256Hex(torrentObj.source)))) + ]; + let action; + if (d && d.active) { + const pct = d.size > 0 ? Math.min(100, Math.floor((d.received / d.size) * 100)) : 0; + action = div({ class: "torrent-progress-cell" }, + progress({ value: String(pct), max: "100", class: "torrent-progress" }), + span({ class: "torrent-progress-text" }, ` ${pct}% · ${torrentStateLabel(d.state)} · ${d.seeds || 0} ${i18n.torrentSeeds}`), + br(), + a({ href: "/torrents?filter=downloads", class: "filter-btn" }, i18n.torrentDownloads) + ); + } else { + action = renderTorrentSourceDownload(torrentObj.key, torrentObj.source); + } + return div({ class: "torrent-oasis torrent-detail-section" }, + h3({ class: "torrent-section-title" }, i18n.torrentOriginalSection || "Original"), + table({ class: "tribe-info-table torrent-file-info" }, ...rows), + action + ); +}; + +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.some((x) => !x.tribeOrigin); + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + exports.torrentsView = async (torrents, filter = "all", torrentId = null, params = {}) => { if (filter === "edit") params = { ...params, spreadWarning: await renderSpreadEditWarning(torrentId) }; const title = i18n.torrentsTitle; @@ -185,35 +293,35 @@ exports.torrentsView = async (torrents, filter = "all", torrentId = null, params const sort = safeText(params.sort || "recent"); const list = safeArr(torrents); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); const torrentToEdit = torrentId ? list.find((t) => t.key === torrentId) : null; return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.torrentsDescription) ), - (() => { - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetTorrents); - return div({ class: "shop-title-row" }, renderReachChip(isClearnet, i18n)); - })(), - br(), div( { class: "filters" }, form( { method: "GET", action: "/torrents", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterRecent).toUpperCase()), button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterFavorites).toUpperCase() - ), + )] : []), - button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterTop).toUpperCase()), + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterTop).toUpperCase())] : []), + ...(params.isPublic || filter === "create" ? [] : [button({ type: "submit", name: "filter", value: "downloads", class: filter === "downloads" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentDownloads).toUpperCase())]), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.torrentCreateButton) ) ) @@ -221,9 +329,12 @@ exports.torrentsView = async (torrents, filter = "all", torrentId = null, params section( filter === "create" || filter === "edit" ? renderTorrentForm(filter, torrentId, torrentToEdit, { ...params, filter }) + : filter === "downloads" + ? section(renderDownloads(params.downloads)) : section( - div( - { class: "audios-search" }, + emptyMod ? null : div( + { class: "audios-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form( { method: "GET", action: "/torrents", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -246,20 +357,23 @@ exports.torrentsView = async (torrents, filter = "all", torrentId = null, params ) ) ), - div({ class: "audios-list" }, renderTorrentTable(list, filter, { q, sort })) + div({ class: "audios-list" }, renderTorrentTable(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) ) ) ); }; exports.singleTorrentView = async (torrentObj, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); const q = safeText(params.q || ""); const sort = safeText(params.sort || "recent"); const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q, sort }); const title = safeText(torrentObj.title); - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetTorrents); + const { renderEncryptedChip, renderTransportChip } = require('./clearnet_view'); + const isAuthor = String(torrentObj.author) === String(userId); + const clearnetAllowed = !torrentObj.tribeId && !torrentObj.encrypted && !torrentObj.cipher; + const isClearnet = !!torrentObj.clearnet; const chips = [ renderLifespanChip(torrentObj.lifetime, i18n), @@ -273,24 +387,28 @@ exports.singleTorrentView = async (torrentObj, filter = "all", comments = [], pa const tagsNode = renderTags(torrentObj.tags); const detailActions = div({ class: "card-header activity-card-header" }, - renderContentActions(torrentObj.key, null, { + renderContentActions(torrentObj.key, `/torrents/${encodeURIComponent(torrentObj.key)}`, { + spreadTitle: i18n.seedAction, author: torrentObj.author, favKind: 'torrents', isFavorite: torrentObj.isFavorite, spread: params.spreads || null, returnTo, - reportTitle: torrentObj.title + reportTitle: torrentObj.title, + deleteAction: String(torrentObj.author) === String(userId) ? contentDeleteAction('torrent', torrentObj.key) : undefined }) ); const torrentSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, title ? h2({ class: "tribe-card-title" }, title) : null, - renderReachChip(isClearnet, i18n) + torrentObj.tribeId && torrentObj.cipher ? renderEncryptedChip(i18n) : renderTransportChip(i18n), + clearnetAllowed ? renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('torrents', torrentObj.title, torrentObj.key) : null) : null, + clearnetAllowed && isAuthor ? renderClearnetSwitch('torrents', torrentObj.rootId || torrentObj.key, isClearnet) : null ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, safeText(torrentObj.description) - ? p({ class: "tribe-side-description" }, ...renderUrl(torrentObj.description)) + ? p({ class: "tribe-side-description" }, ...renderStyledText(torrentObj.description)) : null, tagsNode, sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null @@ -300,7 +418,18 @@ exports.singleTorrentView = async (torrentObj, filter = "all", comments = [], pa detailActions, torrentObj.url && torrentObj.url.startsWith("&") ? div({ class: "torrent-download" }, - a({ href: `/blob/${encodeURIComponent(torrentObj.url)}?name=${encodeURIComponent((torrentObj.title || 'download').replace(/\.torrent$/i, '') + '.torrent')}` , class: "filter-btn" }, i18n.torrentDownloadButton || "DOWNLOAD IT!") + renderOasisDownload(torrentObj, params), + div({ class: "torrent-detail-section" }, + h3({ class: "torrent-section-title" }, "Torrent"), + table({ class: "tribe-info-table torrent-file-info" }, + tr(td({ class: "tribe-info-label" }, i18n.fileShareFileLabel), td({ class: "tribe-info-value" }, `${String(torrentObj.title || "download").replace(/\.torrent$/i, "")}.torrent`)), + torrentObj.source ? null : tr(td({ class: "tribe-info-label" }, i18n.torrentSizeLabel), td({ class: "tribe-info-value" }, formatSize(torrentObj.size))), + torrentObj.cipher + ? tr(td({ class: "tribe-info-label" }, i18n.encryptedChipLabel || "E2E"), td({ class: "tribe-info-value" }, "AES-256-GCM")) + : tr(td({ class: "tribe-info-label" }, "SHA-256"), td({ class: "tribe-info-value" }, span({ class: "bank-address-code" }, blobSha256Hex(torrentObj.url)))) + ), + renderTorrentDownload(torrentObj.cipher ? `/torrents/${encodeURIComponent(torrentObj.key)}/file` : torrentDownloadHref(torrentObj.url, torrentObj.title)) + ) ) : p(i18n.torrentNoFile), (() => { @@ -310,7 +439,7 @@ exports.singleTorrentView = async (torrentObj, filter = "all", comments = [], pa return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(torrentObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(torrentObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(torrentObj.author), showUpdated ? span( @@ -329,9 +458,9 @@ exports.singleTorrentView = async (torrentObj, filter = "all", comments = [], pa return template( i18n.torrentsTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.torrentAllSectionTitle || i18n.torrentsTitle), - p(i18n.torrentDescription) + p(i18n.torrentsDescription) ), div( { class: "filters" }, @@ -339,12 +468,12 @@ exports.singleTorrentView = async (torrentObj, filter = "all", comments = [], pa { method: "GET", action: "/torrents", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), - button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterRecent).toUpperCase()), button( + button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterAll).toUpperCase()), ...(mediaChip("favorites") ? [button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterMine).toUpperCase())] : []), { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterFavorites).toUpperCase() - ), + )] : []), button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.torrentFilterTop).toUpperCase()), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.torrentCreateButton) diff --git a/src/views/transfer_view.js b/src/views/transfer_view.js index 9b1d02e..32e0871 100644 --- a/src/views/transfer_view.js +++ b/src/views/transfer_view.js @@ -1,5 +1,6 @@ -const { div, h2, p, section, button, form, a, input, br, span, label, select, option, progress, table, tr, td } = require("../server/node_modules/hyperaxe") -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions } = require("./main_views") +const { hr, div, h2, p, section, button, form, a, input, br, span, label, select, option, progress, table, tr, td } = require("../server/node_modules/hyperaxe") +const { getConfig } = require('../configs/config-manager.js'); +const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderStateChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderModuleStatsBy, moduleIsEmpty, renderEcoValueChip, renderWalletChip, contentDeleteAction, paged, renderCreatedRows } = require("./main_views") const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view") const moment = require("../server/node_modules/moment") const { config } = require("../server/SSB_server.js") @@ -90,7 +91,7 @@ const renderBlockInfoCard = (block) => { div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.blockchainBlockID || "Block ID"}: `), span({ class: "card-value" }, - a({ href: `/blockexplorer/block/${encodeURIComponent(block.id)}` }, block.id) + process.env.OASIS_MOBILE === '1' ? block.id : a({ href: `/blockexplorer/block/${encodeURIComponent(block.id)}` }, block.id) ) ) ) @@ -117,25 +118,10 @@ const renderOwnerActions = (transfer, returnTo) => { { method: "GET", action: `/transfers/edit/${encodeURIComponent(transfer.id)}` }, input({ type: "hidden", name: "returnTo", value: returnTo }), button({ type: "submit", class: "update-btn" }, i18n.transfersUpdateButton) - ), - form( - { method: "POST", action: `/transfers/delete/${encodeURIComponent(transfer.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ type: "submit", class: "delete-btn" }, i18n.transfersDeleteButton) ) ] } -const renderUpdatedLabel = (createdAt, updatedAt) => { - const createdTs = createdAt ? new Date(createdAt).getTime() : NaN - const updatedTs = updatedAt ? new Date(updatedAt).getTime() : NaN - const showUpdated = Number.isFinite(updatedTs) && (!Number.isFinite(createdTs) || updatedTs !== createdTs) - return showUpdated - ? span({ class: "votations-comment-date" }, ` | ${i18n.transfersUpdatedAt}: ${moment(updatedAt).format("YYYY/MM/DD HH:mm")}`) - : null -} - - const renderTransferStatusChip = (status) => { const up = String(status || "").toUpperCase() const variant = @@ -146,12 +132,25 @@ const renderTransferStatusChip = (status) => { return renderStateChip(variant, icon, i18n[statusKey(up)] || up) } +const ubiEpochLabel = (t) => { + const tags = safeArr(t.tags).map(x => String(x)) + const fromTag = tags.find(x => /^epoch:\d{4}-\d{2}$/i.test(x)) + const epoch = fromTag ? fromTag.slice(6) : ((String(t.concept || "").match(/(\d{4}-\d{2})/) || [])[1] || "") + return epoch ? `UBI - ${epoch}` : "UBI" +} +const transferTitle = (t) => { + const tags = safeArr(t.tags).map(x => String(x).toUpperCase()) + if (tags.includes("UBI") && /UBI Payment|^UBI\b/.test(String(t.concept || ""))) return ubiEpochLabel(t) + return t.concept || i18n.transfersTitle +} + const renderTransferCategoryChip = (cat) => renderStateChip("encrypted", "", categoryLabel(cat)) const generateTransferCard = (transfer, filter, params = {}) => { const confirmedBy = safeArr(transfer.confirmedBy) - const required = transfer.from === transfer.to ? 1 : 2 + const settledUbiTx = (Array.isArray(transfer.tags) ? transfer.tags : []).some(t => String(t).toUpperCase() === "UBI") && /^[0-9a-f]{64}$/i.test(String(transfer.txid || "")) + const required = (transfer.from === transfer.to || settledUbiTx) ? Math.max(1, Math.min(confirmedBy.length, 2)) : 2 const confirmedCount = confirmedBy.length const dl = transfer.deadline ? moment(transfer.deadline) : null const isExpired = dl && dl.isValid() ? dl.isBefore(moment()) : false @@ -161,7 +160,7 @@ const generateTransferCard = (transfer, filter, params = {}) => { const chips = [ renderTransferStatusChip(transfer.status), - isUbi ? renderStateChip("mutuals", "🎁", "UBI") : null, + isUbi ? renderStateChip("mutuals", "", "UBI") : null, isExpired ? renderStateChip("closed", "⏰", i18n.transfersExpiredBadge) : null, renderLifespanChip(transfer.lifetime, i18n) ].filter(Boolean) @@ -171,32 +170,52 @@ const generateTransferCard = (transfer, filter, params = {}) => { const returnTo = buildReturnTo(filter, params) const isOwn = transfer.from && String(transfer.from) === String(userId) + const canDelete = isOwn && String(transfer.status || "").toUpperCase() === "UNCONFIRMED" && !isExpired && confirmedCount < required return div({ class: "trending-card transfer-card" + (isOwn ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(transfer.id, `/transfers/${encodeURIComponent(transfer.id)}`, { spread: params.spreadMap && params.spreadMap.get(transfer.id) || null, author: transfer.from, favKind: 'transfers', isFavorite: transfer.isFavorite, reportTitle: transfer.concept }) + renderContentActions(transfer.id, `/transfers/${encodeURIComponent(transfer.id)}`, { spread: params.spreadMap && params.spreadMap.get(transfer.id) || null, author: transfer.from, favKind: 'transfers', isFavorite: transfer.isFavorite, reportTitle: transfer.concept, returnTo, deleteAction: canDelete ? contentDeleteAction('transfer', transfer.id) : undefined }) ), div({ class: "card-section transfer-card-body" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, - a({ href: `/transfers/${encodeURIComponent(transfer.id)}` }, transfer.concept || i18n.transfersTitle) + a({ href: `/transfers/${encodeURIComponent(transfer.id)}` }, transferTitle(transfer)) ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, !isUbi && dl && dl.isValid() - ? p({ class: "card-date-highlight" }, dl.format("YYYY/MM/DD HH:mm")) + ? p({ class: "time-chip" }, dl.format("YYYY/MM/DD HH:mm")) : null, cat !== "TRUST" - ? div({ class: "job-price-line card-salary transfer-amount-centered" }, fmtAmountWithUnit(transfer)) - : null, - div({ class: "tribe-card-members" }, - span({ class: "tribe-members-count" }, `${i18n.transfersConfirmations}: ${confirmedCount}/${required}`) - ) + ? div({ class: "tribe-card-members" }, span({ class: "price-chip" }, fmtAmountWithUnit(transfer))) + : null ) ) } +const isUbiTagged = (t) => safeArr(t.tags).some(tag => String(tag).toUpperCase() === "UBI") + +const trChipFor = (normalizedFilter, censusT) => (mode) => { + if (mode === normalizedFilter) return true + if (!Array.isArray(censusT)) return true; + const stT = (t) => String(t.status || "").toUpperCase() + if (mode === "mine") return censusT.some(t => t.from === userId || t.to === userId) + if (mode === "recent") return censusT.length > 0 + if (mode === "mine-pending") return censusT.some(t => (t.from === userId || t.to === userId) && String(t.status || "").toUpperCase() === "UNCONFIRMED") + if (mode === "mine-confirmed") return censusT.some(t => (t.from === userId || t.to === userId) && String(t.status || "").toUpperCase() === "CLOSED") + if (mode === "ubi") return censusT.some(t => safeArr(t.tags).some(tag => String(tag).toUpperCase() === "UBI")) + if (mode === "economic") return censusT.some(t => categoryOf(t) === "ECONOMIC") + if (mode === "time") return censusT.some(t => categoryOf(t) === "TIME") + if (mode === "trust") return censusT.some(t => categoryOf(t) === "TRUST") + if (mode === "market") return censusT.some(t => !isUbiTagged(t)) + if (mode === "pending") return censusT.some(t => stT(t) === "UNCONFIRMED" && t.to === userId && !safeArr(t.confirmedBy).includes(userId)) + if (mode === "unconfirmed") return censusT.some(t => stT(t) === "UNCONFIRMED") + if (mode === "closed" || mode === "top") return censusT.some(t => stT(t) === "CLOSED") + if (mode === "discarded") return censusT.some(t => stT(t) === "DISCARDED") + return true +} + exports.transferView = async (transfers, filter, transferId, params = {}) => { const normalizedFilter = filter === "favs" ? "all" : (filter || "all") @@ -212,7 +231,10 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { const list = safeArr(transfers) let filtered = + normalizedFilter === "recent" ? list.filter(t => (Date.parse(t.createdAt || "") || 0) >= Date.now() - 86400000) : normalizedFilter === "mine" ? list.filter(t => t.from === userId || t.to === userId) : + normalizedFilter === "mine-pending" ? list.filter(t => (t.from === userId || t.to === userId) && String(t.status || "").toUpperCase() === "UNCONFIRMED") : + normalizedFilter === "mine-confirmed" ? list.filter(t => (t.from === userId || t.to === userId) && String(t.status || "").toUpperCase() === "CLOSED") : normalizedFilter === "ubi" ? list.filter(t => safeArr(t.tags).some(tag => String(tag).toUpperCase() === "UBI")) : normalizedFilter === "pending" ? list.filter(t => String(t.status || "").toUpperCase() === "UNCONFIRMED" && t.to === userId && !safeArr(t.confirmedBy).includes(userId)) : normalizedFilter === "top" ? list.filter(t => String(t.status || "").toUpperCase() === "CLOSED") : @@ -222,7 +244,7 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { normalizedFilter === "economic" ? list.filter(t => categoryOf(t) === "ECONOMIC") : normalizedFilter === "time" ? list.filter(t => categoryOf(t) === "TIME") : normalizedFilter === "trust" ? list.filter(t => categoryOf(t) === "TRUST") : - normalizedFilter === "market" ? list : + normalizedFilter === "market" ? list.filter(t => !isUbiTagged(t)) : list if (q) { @@ -261,10 +283,14 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { selectedCategory === "TRUST" ? (i18n.transfersUnitTrust || "trust") : (i18n.transfersUnitEco || "ECO") + const emptyMod = moduleIsEmpty(list, normalizedFilter, "all", q || String(minAmountRaw || "") || String(maxAmountRaw || "")); + const censusT = Array.isArray(params.censusList) ? params.censusList : list + const stT = (t) => String(t.status || "").toUpperCase() + const trChip = trChipFor(normalizedFilter, censusT) return template( title, section( - div({ class: "tags-header" }, h2(title), p(i18n.transfersDescription)), + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.transfersDescription), renderWalletChip()), div( { class: "filters" }, form( @@ -273,20 +299,30 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { input({ type: "hidden", name: "minAmount", value: String(minAmountRaw ?? "") }), input({ type: "hidden", name: "maxAmount", value: String(maxAmountRaw ?? "") }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: normalizedFilter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: normalizedFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "ubi", class: normalizedFilter === "ubi" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUBI).toUpperCase()), - button({ type: "submit", name: "filter", value: "economic", class: normalizedFilter === "economic" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterEconomic || (i18n.transfersCategoryEconomic || "ECONOMIC")), - button({ type: "submit", name: "filter", value: "time", class: normalizedFilter === "time" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTime || (i18n.transfersCategoryTime || "TIME")), - button({ type: "submit", name: "filter", value: "trust", class: normalizedFilter === "trust" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTrust || (i18n.transfersCategoryTrust || "TRUST")), - button({ type: "submit", name: "filter", value: "market", class: normalizedFilter === "market" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMarket).toUpperCase()), - button({ type: "submit", name: "filter", value: "pending", class: normalizedFilter === "pending" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterPending).toUpperCase()), - button({ type: "submit", name: "filter", value: "unconfirmed", class: normalizedFilter === "unconfirmed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUnconfirmed).toUpperCase()), - button({ type: "submit", name: "filter", value: "closed", class: normalizedFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterClosed).toUpperCase()), - button({ type: "submit", name: "filter", value: "discarded", class: normalizedFilter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterDiscarded).toUpperCase()), - button({ type: "submit", name: "filter", value: "top", class: normalizedFilter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterTop).toUpperCase()), + ...(trChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: normalizedFilter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterRecent).toUpperCase())] : []), + ...(trChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: normalizedFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMine).toUpperCase())] : []), + ...(trChip("ubi") ? [button({ type: "submit", name: "filter", value: "ubi", class: normalizedFilter === "ubi" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUBI).toUpperCase())] : []), + ...(trChip("economic") ? [button({ type: "submit", name: "filter", value: "economic", class: normalizedFilter === "economic" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterEconomic || (i18n.transfersCategoryEconomic || "ECONOMIC"))] : []), + ...(trChip("time") ? [button({ type: "submit", name: "filter", value: "time", class: normalizedFilter === "time" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTime || (i18n.transfersCategoryTime || "TIME"))] : []), + ...(trChip("trust") ? [button({ type: "submit", name: "filter", value: "trust", class: normalizedFilter === "trust" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTrust || (i18n.transfersCategoryTrust || "TRUST"))] : []), + ...(trChip("market") ? [button({ type: "submit", name: "filter", value: "market", class: normalizedFilter === "market" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMarket).toUpperCase())] : []), + ...(trChip("pending") ? [button({ type: "submit", name: "filter", value: "pending", class: normalizedFilter === "pending" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterPending).toUpperCase())] : []), + ...(trChip("unconfirmed") ? [button({ type: "submit", name: "filter", value: "unconfirmed", class: normalizedFilter === "unconfirmed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUnconfirmed).toUpperCase())] : []), + ...(trChip("closed") ? [button({ type: "submit", name: "filter", value: "closed", class: normalizedFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterClosed).toUpperCase())] : []), + ...(trChip("discarded") ? [button({ type: "submit", name: "filter", value: "discarded", class: normalizedFilter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterDiscarded).toUpperCase())] : []), + ...(trChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: normalizedFilter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterTop).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.transfersCreateButton) - ) + ), + ["mine", "mine-pending", "mine-confirmed"].includes(normalizedFilter) + ? div({ class: "transfers-subfilters" }, + span({ class: "activity-subchip-label" }, "\u21b3"), + ...[["mine", i18n.transfersFilterAll], ["mine-pending", i18n.transfersFilterMinePending], ["mine-confirmed", i18n.transfersFilterMineConfirmed]] + .map(([mode, label]) => a({ href: `/transfers?filter=${mode}`, class: normalizedFilter === mode ? "activity-chip active" : "activity-chip" }, String(label).toUpperCase())) + ) + : null ) ), section( @@ -299,15 +335,16 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { input({ type: "hidden", name: "filter", value: "create" }), label(i18n.transfersCategory || "Category"), br(), - select( - { name: "category", class: "transfer-category-select" }, - option({ value: "ECONOMIC", selected: selectedCategory === "ECONOMIC" ? "selected" : undefined }, i18n.transfersCategoryEconomic || "Economic"), - option({ value: "TIME", selected: selectedCategory === "TIME" ? "selected" : undefined }, i18n.transfersCategoryTime || "Time"), - option({ value: "TRUST", selected: selectedCategory === "TRUST" ? "selected" : undefined }, i18n.transfersCategoryTrust || "Trust") + div({ class: "apply-row" }, + select( + { name: "category", class: "transfer-category-select" }, + option({ value: "ECONOMIC", selected: selectedCategory === "ECONOMIC" ? "selected" : undefined }, i18n.transfersCategoryEconomic || "Economic"), + option({ value: "TIME", selected: selectedCategory === "TIME" ? "selected" : undefined }, i18n.transfersCategoryTime || "Time"), + option({ value: "TRUST", selected: selectedCategory === "TRUST" ? "selected" : undefined }, i18n.transfersCategoryTrust || "Trust") + ), + button({ type: "submit", class: "create-button" }, i18n.apply || "Apply") ), - " ", - button({ type: "submit", class: "filter-btn" }, i18n.transfersCategoryApply || "Apply"), - br(), br() + hr({ class: "form-sep" }) ) : null, form( @@ -341,6 +378,7 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { label(`${i18n.transfersAmount} (${amountUnitLabel})`), br(), input({ type: "number", name: "amount", step: "0.000001", required: true, min: "0.000001", value: transferToEdit.amount || "" }), + renderEcoValueChip(), br(), br() ], label(i18n.transfersDeadline), @@ -357,19 +395,16 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { ) ) : section( - div( - { class: "transfers-search" }, + emptyMod ? null : div( + { class: "transfers-search activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filtered, t => String(t.status || '').toUpperCase(), [{ value: 'UNCONFIRMED', label: i18n.transfersFilterUnconfirmed }, { value: 'CLOSED', label: i18n.transfersFilterClosed }, { value: 'DISCARDED', label: i18n.transfersFilterDiscarded }]), form( { method: "GET", action: "/transfers", class: "filter-box" }, input({ type: "hidden", name: "filter", value: normalizedFilter || "all" }), input({ type: "text", name: "q", value: q, placeholder: i18n.transfersSearchPlaceholder, class: "filter-box__input" }), - div( - { class: "filter-box__controls" }, - div( - { class: "transfer-range" }, - input({ type: "number", name: "minAmount", step: "0.000001", min: "0", value: String(minAmountRaw ?? ""), placeholder: i18n.transfersMinAmountLabel, class: "filter-box__number transfer-amount-input" }), - input({ type: "number", name: "maxAmount", step: "0.000001", min: "0", value: String(maxAmountRaw ?? ""), placeholder: i18n.transfersMaxAmountLabel, class: "filter-box__number transfer-amount-input" }) - ), + input({ type: "number", name: "minAmount", step: "0.000001", min: "0", value: String(minAmountRaw ?? ""), placeholder: i18n.transfersMinAmountLabel, class: "filter-box__number transfer-amount-input" }), + input({ type: "number", name: "maxAmount", step: "0.000001", min: "0", value: String(maxAmountRaw ?? ""), placeholder: i18n.transfersMaxAmountLabel, class: "filter-box__number transfer-amount-input" }), + select( { name: "sort", class: "filter-box__select" }, option({ value: "recent", selected: sort === "recent" ? "selected" : undefined }, i18n.transfersSortRecent), @@ -377,52 +412,52 @@ exports.transferView = async (transfers, filter, transferId, params = {}) => { option({ value: "deadline", selected: sort === "deadline" ? "selected" : undefined }, i18n.transfersSortDeadline) ), button({ type: "submit", class: "filter-box__button" }, i18n.transfersSearchButton) - ) + ) ), - br(), filtered.length - ? div({ class: "jobs-grid" }, - filtered.map(t => generateTransferCard(t, normalizedFilter, { q, minAmount: minAmountRaw, maxAmount: maxAmountRaw, sort, spreadMap: params.spreadMap })) + ? div({ class: "jobs-grid transfers-grid" }, + paged(filtered).map(t => generateTransferCard(t, normalizedFilter, { q, minAmount: minAmountRaw, maxAmount: maxAmountRaw, sort, spreadMap: params.spreadMap })) ) - : p(q || String(minAmountRaw) || String(maxAmountRaw) ? i18n.transfersNoMatch : i18n.transfersNoItems) + : div({ class: "no-content-box" }, p(q || String(minAmountRaw) || String(maxAmountRaw) ? i18n.transfersNoMatch : i18n.transfersNoItems)) ) ) ) } exports.singleTransferView = async (transfer, filter, params = {}) => { + const trChip = (mode) => trChipFor(normalizedFilter, Array.isArray(params.censusList) ? params.censusList : null)(mode) const normalizedFilter = filter === "favs" ? "all" : (filter || "all") const q = safeText(params.q || "") const sort = safeText(params.sort || "recent") const returnTo = safeText(params.returnTo) || buildReturnTo(normalizedFilter, { ...params, q, sort }) const confirmedBy = safeArr(transfer.confirmedBy) - const required = transfer.from === transfer.to ? 1 : 2 + const settledUbiTx = (Array.isArray(transfer.tags) ? transfer.tags : []).some(t => String(t).toUpperCase() === "UBI") && /^[0-9a-f]{64}$/i.test(String(transfer.txid || "")) + const required = (transfer.from === transfer.to || settledUbiTx) ? Math.max(1, Math.min(confirmedBy.length, 2)) : 2 const confirmedCount = confirmedBy.length const isUnconfirmed = String(transfer.status || "").toUpperCase() === "UNCONFIRMED" const dl = transfer.deadline ? moment(transfer.deadline) : null const isExpired = dl && dl.isValid() ? dl.isBefore(moment()) : false const tags = Array.isArray(transfer.tags) ? transfer.tags.map(t => String(t).toUpperCase()) : [] const isUbi = tags.includes("UBI") - const showConfirm = isUnconfirmed && transfer.to === userId && !confirmedBy.includes(userId) && !isExpired && !tags.includes("PENDING") + const settledUbi = isUbi && /^[0-9a-f]{64}$/i.test(String(transfer.txid || "")) + const showConfirm = isUnconfirmed && transfer.to === userId && !confirmedBy.includes(userId) && !isExpired && !tags.includes("PENDING") && !settledUbi const tagsNode = renderTags(transfer.tags) const cat = categoryOf(transfer) const otherParty = transfer.from === userId ? transfer.to : transfer.from const chips = [ renderTransferStatusChip(transfer.status), - isUbi ? renderStateChip("mutuals", "🎁", "UBI") : null, + isUbi ? renderStateChip("mutuals", "", "UBI") : null, isExpired ? renderStateChip("closed", "⏰", i18n.transfersExpiredBadge) : null, renderLifespanChip(transfer.lifetime, i18n) ].filter(Boolean) const sideActions = [] - if (otherParty && String(otherParty) !== String(userId)) { - sideActions.push(form({ method: "GET", action: "/pm" }, - input({ type: "hidden", name: "recipients", value: otherParty }), - button({ type: "submit", class: "filter-btn" }, i18n.privateMessage) - )) + const isPayer = String(transfer.from) === String(userId) + if (isPayer && String(transfer.status || "").toUpperCase() === "UNCONFIRMED" && getConfig().modules.walletMod === "on") { + sideActions.push(a({ href: `/wallet/send?transfer=${encodeURIComponent(transfer.id)}`, class: "filter-btn" }, i18n.transfersPayWithWallet)) } if (showConfirm) { sideActions.push(form({ method: "POST", action: `/transfers/confirm/${encodeURIComponent(transfer.id)}` }, @@ -436,11 +471,8 @@ exports.singleTransferView = async (transfer, filter, params = {}) => { input({ type: "hidden", name: "returnTo", value: returnTo }), button({ type: "submit", class: "update-btn" }, i18n.transfersUpdateButton) )) - sideActions.push(form({ method: "POST", action: `/transfers/delete/${encodeURIComponent(transfer.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ type: "submit", class: "delete-btn" }, i18n.transfersDeleteButton) - )) } + const canDelete = canEdit && !isExpired && confirmedCount < required const exportActions = div({ class: "doc-export-actions" }, form({ method: "GET", action: `/transfers/contract/${encodeURIComponent(transfer.id)}`, class: "transfer-contract-form" }, button({ type: "submit", class: "filter-btn" }, i18n.transfersExportContract) @@ -465,19 +497,21 @@ exports.singleTransferView = async (transfer, filter, params = {}) => { const transferSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(transfer.id, null, { + renderContentActions(transfer.id, `/transfers/${encodeURIComponent(transfer.id)}`, { spread: params.spreads || null, author: transfer.from, favKind: 'transfers', isFavorite: transfer.isFavorite, returnTo, - reportTitle: transfer.concept + reportTitle: transfer.concept, + deleteAction: canDelete ? contentDeleteAction('transfer', transfer.id) : undefined }) ), div({ class: "shop-title-row" }, - h2({ class: "tribe-card-title" }, transfer.concept || i18n.transfersTitle) + h2({ class: "tribe-card-title" }, transferTitle(transfer)) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, + table({ class: "tribe-info-table" }, ...renderCreatedRows(transfer.createdAt, transfer.from, transfer.updatedAt)), tagsNode, div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.transfersConfirmations}: ${confirmedCount}/${required}`) @@ -485,7 +519,7 @@ exports.singleTransferView = async (transfer, filter, params = {}) => { params.block && params.block.id ? div({ class: "card-field" }, span({ class: "card-label" }, `${i18n.blockchainBlockID || "Block ID"}: `), - span({ class: "card-value" }, a({ class: "user-link", href: `/blockexplorer/block/${encodeURIComponent(params.block.id)}` }, params.block.id)) + span({ class: "card-value" }, process.env.OASIS_MOBILE === '1' ? params.block.id : a({ class: "user-link", href: `/blockexplorer/block/${encodeURIComponent(params.block.id)}` }, params.block.id)) ) : null, sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null, @@ -506,11 +540,6 @@ exports.singleTransferView = async (transfer, filter, params = {}) => { ? div({ class: "card-assigned-list" }, ...confirmedList.map(id => userLink(id))) : null ), - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(transfer.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(transfer.from), - renderUpdatedLabel(transfer.createdAt, transfer.updatedAt) - ), renderEngagement(transfer.id, renderOpinionsVoting('/transfers/opinions', transfer.id, transfer.opinions, returnTo, transfer.opinions_inhabitants), renderSharedCommentsSection({ @@ -523,7 +552,7 @@ exports.singleTransferView = async (transfer, filter, params = {}) => { return template( transfer.concept, - section(div({ class: "tags-header" }, h2(i18n.transfersTitle), p(i18n.transfersDescription))), + section(div({ class: "tags-header module-header-line" }, h2(i18n.transfersTitle), p(i18n.transfersDescription), renderWalletChip())), section( div( { class: "filters" }, @@ -534,16 +563,17 @@ exports.singleTransferView = async (transfer, filter, params = {}) => { input({ type: "hidden", name: "maxAmount", value: String(params.maxAmount ?? "") }), input({ type: "hidden", name: "sort", value: sort }), button({ type: "submit", name: "filter", value: "all", class: normalizedFilter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: normalizedFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "ubi", class: normalizedFilter === "ubi" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUBI).toUpperCase()), - button({ type: "submit", name: "filter", value: "economic", class: normalizedFilter === "economic" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterEconomic || (i18n.transfersCategoryEconomic || "ECONOMIC")), - button({ type: "submit", name: "filter", value: "time", class: normalizedFilter === "time" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTime || (i18n.transfersCategoryTime || "TIME")), - button({ type: "submit", name: "filter", value: "trust", class: normalizedFilter === "trust" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTrust || (i18n.transfersCategoryTrust || "TRUST")), - button({ type: "submit", name: "filter", value: "market", class: normalizedFilter === "market" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMarket).toUpperCase()), - button({ type: "submit", name: "filter", value: "pending", class: normalizedFilter === "pending" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterPending).toUpperCase()), - button({ type: "submit", name: "filter", value: "unconfirmed", class: normalizedFilter === "unconfirmed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUnconfirmed).toUpperCase()), - button({ type: "submit", name: "filter", value: "closed", class: normalizedFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterClosed).toUpperCase()), - button({ type: "submit", name: "filter", value: "discarded", class: normalizedFilter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterDiscarded).toUpperCase()), + ...(trChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: normalizedFilter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterRecent).toUpperCase())] : []), + ...(trChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: normalizedFilter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMine).toUpperCase())] : []), + ...(trChip("ubi") ? [button({ type: "submit", name: "filter", value: "ubi", class: normalizedFilter === "ubi" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUBI).toUpperCase())] : []), + ...(trChip("economic") ? [button({ type: "submit", name: "filter", value: "economic", class: normalizedFilter === "economic" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterEconomic || (i18n.transfersCategoryEconomic || "ECONOMIC"))] : []), + ...(trChip("time") ? [button({ type: "submit", name: "filter", value: "time", class: normalizedFilter === "time" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTime || (i18n.transfersCategoryTime || "TIME"))] : []), + ...(trChip("trust") ? [button({ type: "submit", name: "filter", value: "trust", class: normalizedFilter === "trust" ? "filter-btn active" : "filter-btn" }, i18n.transfersFilterTrust || (i18n.transfersCategoryTrust || "TRUST"))] : []), + ...(trChip("market") ? [button({ type: "submit", name: "filter", value: "market", class: normalizedFilter === "market" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterMarket).toUpperCase())] : []), + ...(trChip("pending") ? [button({ type: "submit", name: "filter", value: "pending", class: normalizedFilter === "pending" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterPending).toUpperCase())] : []), + ...(trChip("unconfirmed") ? [button({ type: "submit", name: "filter", value: "unconfirmed", class: normalizedFilter === "unconfirmed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterUnconfirmed).toUpperCase())] : []), + ...(trChip("closed") ? [button({ type: "submit", name: "filter", value: "closed", class: normalizedFilter === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterClosed).toUpperCase())] : []), + ...(trChip("discarded") ? [button({ type: "submit", name: "filter", value: "discarded", class: normalizedFilter === "discarded" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterDiscarded).toUpperCase())] : []), button({ type: "submit", name: "filter", value: "top", class: normalizedFilter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.transfersFilterTop).toUpperCase()), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.transfersCreateButton) ) diff --git a/src/views/trending_view.js b/src/views/trending_view.js index 320a8c1..0a4c7be 100644 --- a/src/views/trending_view.js +++ b/src/views/trending_view.js @@ -1,8 +1,11 @@ const { div, h2, p, section, button, form, a, textarea, br, input, table, tr, th, td, img, video: videoHyperaxe, audio: audioHyperaxe, span, details, summary} = require("../server/node_modules/hyperaxe"); -const { template, i18n, userLink, renderSpreadButton, renderContentActions} = require('./main_views'); -const { renderTextWithStyles } = require('../backend/renderTextWithStyles'); +const RAW_HTML = Symbol.for('oasis.rawHtml'); +const moment = require("../server/node_modules/moment"); +const { template, i18n, userLink, renderSpreadButton, renderContentActions, renderVotesSummary, renderModuleStats, renderCardMetaRow, renderTorrentDownload, renderTorrentSourceDownload, renderFileDownloads, torrentDownloadHref, CONTENT_FAV_KIND, CONTENT_TORRENTABLE, paged } = require('./main_views'); +const { renderStyledHtml, safeExternalHref } = require('../backend/renderStyledText'); +const { renderZoomableImage } = require('./gallery_view'); const { config } = require('../server/SSB_server.js'); -const { renderUrl } = require('../backend/renderUrl'); +const { renderStyledText } = require('../backend/renderStyledText'); const opinionCategories = require('../backend/opinion_categories'); const { sanitizeHtml } = require('../backend/sanitizeHtml'); @@ -20,9 +23,9 @@ const filterButton = (mode, currentFilter) => const voteLabelFor = (cat) => i18n['vote' + cat.charAt(0).toUpperCase() + cat.slice(1)] || cat; -const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new Map()) => { +const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new Map(), params = {}) => { const c = item.value.content; - const created = new Date(item.value.timestamp).toLocaleString(); + const created = moment(item.value.timestamp).format("YYYY/MM/DD HH:mm"); let contentHtml; @@ -30,15 +33,15 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new const { url, description, lastVisit } = c; contentHtml = div({ class: 'trending-bookmark' }, div({ class: 'card-section bookmark' }, - url ? h2(p(a({ href: url, target: '_blank', class: "bookmark-url" }, url))) : "", + url ? h2(p(a({ href: safeExternalHref(url), target: '_blank', rel: 'noopener noreferrer', class: "bookmark-url" }, url))) : "", lastVisit ? div( { class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkLastVisitLabel + ':'), - span({ class: 'card-value' }, new Date(lastVisit).toLocaleString()) + span({ class: 'card-value' }, moment(lastVisit).format("YYYY/MM/DD HH:mm")) ) : "", - description ? [span({ class: 'card-label' }, i18n.bookmarkDescriptionLabel + ":"), p(...renderUrl(description))] : null + description ? [span({ class: 'card-label' }, i18n.bookmarkDescriptionLabel + ":"), p(...renderStyledText(description))] : null ) ); } else if (c.type === 'image') { @@ -46,8 +49,8 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new contentHtml = div({ class: 'trending-image' }, div({ class: 'card-section image' }, title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.imageTitleLabel + ':'), span({ class: 'card-value' }, title)) : "", - description ? [span({ class: 'card-label' }, i18n.imageDescriptionLabel + ":"), p(...renderUrl(description))] : null, - div({ class: 'card-field' }, img({ src: `/blob/${encodeURIComponent(url)}`, class: 'feed-image' })) + description ? [span({ class: 'card-label' }, i18n.imageDescriptionLabel + ":"), p(...renderStyledText(description))] : null, + div({ class: 'card-field image-container' }, renderZoomableImage(`/blob/${encodeURIComponent(url)}`, { imgClass: 'post-image' })) ) ); } else if (c.type === 'audio') { @@ -55,9 +58,9 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new contentHtml = div({ class: 'trending-audio' }, div({ class: 'card-section audio' }, title?.trim() ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.audioTitleLabel + ':'), span({ class: 'card-value' }, title)) : "", - description ? [span({ class: 'card-label' }, i18n.audioDescriptionLabel + ":"), p(...renderUrl(description))] : null, + description ? [span({ class: 'card-label' }, i18n.audioDescriptionLabel + ":"), p(...renderStyledText(description))] : null, url - ? div({ class: 'card-field audio-container' }, audioHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(url)}`, type: mimeType })) + ? div({ class: 'card-field audio-container' }, audioHyperaxe({ controls: true, class: 'post-audio', src: `/blob/${encodeURIComponent(url)}`, type: mimeType })) : div({ class: 'card-field' }, p(i18n.audioNoFile)) ) ); @@ -66,21 +69,32 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new contentHtml = div({ class: 'trending-video' }, div({ class: 'card-section video' }, title?.trim() ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.videoTitleLabel + ':'), span({ class: 'card-value' }, title)) : "", - description ? [span({ class: 'card-label' }, i18n.videoDescriptionLabel + ":"), p(...renderUrl(description))] : null, + description ? [span({ class: 'card-label' }, i18n.videoDescriptionLabel + ":"), p(...renderStyledText(description))] : null, br(), url - ? div({ class: 'card-field video-container' }, videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(url)}`, type: mimeType, preload: 'metadata', width: '640', height: '360' })) + ? div({ class: 'card-field video-container' }, videoHyperaxe({ controls: true, class: 'post-video', src: `/blob/${encodeURIComponent(url)}`, type: mimeType, preload: 'metadata' })) : div({ class: 'card-field' }, p(i18n.videoNoFile)) ) ); + } else if (c.type === 'file') { + const { url, title, description, fileName, torrentUrl } = c; + contentHtml = div({ class: 'trending-file' }, + div({ class: 'card-section file' }, + title?.trim() ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.fileTitleLabel || 'Title') + ':'), span({ class: 'card-value' }, title)) : "", + description ? [span({ class: 'card-label' }, (i18n.fileDescriptionLabel || 'Description') + ":"), p(...renderStyledText(description))] : null, + url && url.startsWith("&") + ? div({ class: 'card-field' }, renderFileDownloads(item.key, torrentUrl, fileName || title)) + : div({ class: 'card-field' }, p(i18n.fileNoFile || 'No file')) + ) + ); } else if (c.type === 'torrent') { - const { url, title, description } = c; + const { url, title, description, source, sourceName } = c; contentHtml = div({ class: 'trending-torrent' }, div({ class: 'card-section torrent' }, title?.trim() ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.torrentTitleLabel || 'Title') + ':'), span({ class: 'card-value' }, title)) : "", - description ? [span({ class: 'card-label' }, (i18n.torrentDescriptionLabel || 'Description') + ":"), p(...renderUrl(description))] : null, + description ? [span({ class: 'card-label' }, (i18n.torrentDescriptionLabel || 'Description') + ":"), p(...renderStyledText(description))] : null, url && url.startsWith("&") - ? div({ class: 'card-field' }, a({ href: `/blob/${encodeURIComponent(url)}`, class: 'filter-btn' }, i18n.torrentDownload || 'Download')) + ? div({ class: 'card-field torrent-card-actions' }, renderTorrentSourceDownload(item.key, source), renderTorrentDownload(torrentDownloadHref(url, title))) : div({ class: 'card-field' }, p(i18n.torrentNoFile || 'No file')) ) ); @@ -93,7 +107,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new contentHtml = div({ class: 'trending-document' }, div({ class: 'card-section document' }, t ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.documentTitleLabel + ':'), span({ class: 'card-value' }, t)) : "", - description ? [span({ class: 'card-label' }, i18n.documentDescriptionLabel + ":"), p(...renderUrl(description))] : null, + description ? [span({ class: 'card-label' }, i18n.documentDescriptionLabel + ":"), p(...renderStyledText(description))] : null, div({ id: `pdf-container-${item.key}`, class: 'pdf-viewer-container', 'data-pdf-url': `/blob/${encodeURIComponent(url)}` }) ) ); @@ -103,7 +117,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.industryName + ':'), span({ class: 'card-value' }, c.name || '')), c.sector ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.industrySector + ':'), span({ class: 'card-value' }, String(c.sector).toUpperCase())) : "", c.membershipPolicy ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.industryMembershipPolicy + ':'), span({ class: 'card-value' }, String(i18n['industryPolicy_' + c.membershipPolicy] || c.membershipPolicy).toUpperCase())) : "", - c.description ? p(...renderUrl(c.description)) : null, + c.description ? p(...renderStyledText(c.description)) : null, Array.isArray(c.tags) && c.tags.length ? div({ class: 'card-tags' }, c.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`))) : null @@ -115,7 +129,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.industryBlueprint + ':'), span({ class: 'card-value' }, c.name || '')), c.outKind ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.industryOutputKind + ':'), span({ class: 'card-value' }, String(i18n['industryKind_' + c.outKind] || c.outKind).toUpperCase())) : "", c.laborHours ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.industryLaborHours + ':'), span({ class: 'card-value' }, String(c.laborHours))) : "", - c.description ? p(...renderUrl(c.description)) : null + c.description ? p(...renderStyledText(c.description)) : null ) ); } else if (c.type === 'housing') { @@ -129,7 +143,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new c.place ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.housingPlace + ':'), span({ class: 'card-value' }, c.place)) : "", Number(c.rooms) > 0 ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.housingRooms + ':'), span({ class: 'card-value' }, String(c.rooms))) : "", Number(c.size) > 0 ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.housingSize + ':'), span({ class: 'card-value' }, `${c.size} m²`)) : "", - c.description ? p(...renderUrl(c.description)) : null, + c.description ? p(...renderStyledText(c.description)) : null, Array.isArray(c.tags) && c.tags.length ? div({ class: 'card-tags' }, c.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`))) : null @@ -141,8 +155,8 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.title + ':'), span({ class: 'card-value' }, c.title || '')), c.item_type ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.marketItemType + ':'), span({ class: 'card-value' }, String(c.item_type).toUpperCase())) : "", c.item_status ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.marketItemCondition || i18n.status) + ':'), span({ class: 'card-value' }, String(c.item_status).toUpperCase())) : "", - div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.marketItemPrice || i18n.price) + ':'), span({ class: 'card-value' }, `${c.price} ECO`)), - c.description ? p(...renderUrl(c.description)) : null, + div({ class: 'price-chip' }, `${c.price} ECO`), + c.description ? p(...renderStyledText(c.description)) : null, Array.isArray(c.tags) && c.tags.length ? div({ class: 'card-tags' }, c.tags.map(tag => a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' }, `#${tag}`))) : null @@ -152,7 +166,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new const { text, refeeds } = c; contentHtml = div({ class: 'trending-feed' }, div({ class: 'card-section feed' }, - div({ class: 'feed-text', innerHTML: sanitizeHtml(renderTextWithStyles(text)) }), + div({ class: 'feed-text' }, ...renderStyledText(String(text || ''), { zoomImages: true })), refeeds ? h2({ class: 'card-field' }, span({ class: 'card-label' }, i18n.tribeFeedRefeeds + ': '), span({ class: 'card-value' }, refeeds)) : "" @@ -166,7 +180,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new contentHtml = div({ class: 'trending-votes' }, div({ class: 'card-section votes' }, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteQuestionLabel + ':'), span({ class: 'card-value' }, question)), - div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteDeadline + ':'), span({ class: 'card-value' }, deadline ? new Date(deadline).toLocaleString() : '')), + div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteDeadline + ':'), span({ class: 'card-value' }, deadline ? moment(deadline).format("YYYY/MM/DD HH:mm") : '')), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteTotalVotes + ':'), span({ class: 'card-value' }, totalVotes)), table( tr(...votesList.map(v => th(i18n[v.option] || v.option))), @@ -179,11 +193,11 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new contentHtml = div({ class: 'trending-transfer' }, div({ class: 'card-section transfer' }, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.concept + ':'), span({ class: 'card-value' }, concept)), - div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, deadline ? new Date(deadline).toLocaleString() : '')), + div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, deadline ? moment(deadline).format("YYYY/MM/DD HH:mm") : '')), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.status + ':'), span({ class: 'card-value' }, status)), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.amount + ':'), span({ class: 'card-value' }, amount)), - div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.from + ':'), span({ class: 'card-value' }, a({ href: `/author/${encodeURIComponent(from)}`, target: '_blank' }, from))), - div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.to + ':'), span({ class: 'card-value' }, a({ href: `/author/${encodeURIComponent(to)}`, target: '_blank' }, to))), + div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.from + ':'), span({ class: 'card-value' }, a({ href: `/author/${encodeURIComponent(from)}`, target: '_blank', rel: 'noopener noreferrer' }, from))), + process.env.OASIS_MOBILE === '1' ? null : div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.to + ':'), span({ class: 'card-value' }, a({ href: `/author/${encodeURIComponent(to)}`, target: '_blank', rel: 'noopener noreferrer' }, to))), h2({ class: 'card-field' }, span({ class: 'card-label' }, i18n.transfersConfirmations + ': '), span({ class: 'card-value' }, `${confirmedBy.length}/2`)) ) ); @@ -192,7 +206,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new div({ class: 'card-section styled-text-content' }, div( { class: 'card-field' }, - span({ class: 'card-value', innerHTML: sanitizeHtml(renderTextWithStyles(c.title || c.name || c.text || c.description || '[no content]')) }) + span({ class: 'card-value', [RAW_HTML]: sanitizeHtml(renderStyledHtml(c.title || c.name || c.text || c.description || '[no content]')) }) ) ) ); @@ -204,6 +218,7 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new audio: 'audios', video: 'videos', torrent: 'torrents', + file: 'files', document: 'documents', feed: 'feed', votes: 'votes', @@ -215,7 +230,14 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new event: 'events', shopProduct: 'shops/product', housing: 'housing', - market: 'market' + market: 'market', + podcast: 'podcasts', + podcastEpisode: 'podcasts/episode', + campaign: 'campaigns', + logisticsRoute: 'logistics', + poll: 'polls', + schoolCourse: 'school/course', + industryBlueprint: 'industry/blueprint' }; const detailHref = detailPaths[c.type] ? `/${detailPaths[c.type]}/${encodeURIComponent(item.key)}` @@ -227,36 +249,26 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new div( { class: 'card-header activity-card-header' }, span({ class: 'pm-exposition-chip pm-exposition-whole' }, - span({ class: 'pm-exposition-text' }, String(c.type || '').toUpperCase()) + span({ class: 'pm-exposition-text' }, String(i18n['type' + String(c.type || '').charAt(0).toUpperCase() + String(c.type || '').slice(1)] || c.type || '').toUpperCase()) ), - renderContentActions(item.key, detailHref, { spread: spreadMap.get(item.key) || null }) + renderContentActions(item.key, detailHref, { + spread: spreadMap.get(item.key) || null, + author: item.value.author, + reportTitle: c.title || c.question || c.concept || '', + ...(CONTENT_TORRENTABLE.has(c.type) && typeof c.url === 'string' && c.url.startsWith('&') + ? { torrentFrom: { blobId: c.url, name: c.title || c.fileName || '' } } + : {}), + ...(CONTENT_FAV_KIND[c.type] + ? { favKind: CONTENT_FAV_KIND[c.type], isFavorite: params.favIndex instanceof Map ? params.favIndex.get(String(item.key)) === CONTENT_FAV_KIND[c.type] : false, returnTo: params.returnTo } + : {}) + }) ), div( { class: 'card-section trending-card-body' }, - contentHtml, - p( - { class: 'card-footer' }, - span({ class: 'date-link' }, `${created} ${i18n.performed} `), - userLink(item.value.author) - ), - (() => { - const ops = c.opinions || {}; - const entries = Object.entries(ops).filter(([, v]) => v > 0); - const dominantPart = (() => { - if (!entries.length) return null; - const maxVal = Math.max(...entries.map(([, v]) => v)); - const dominant = entries.filter(([, v]) => v === maxVal).map(([k]) => voteLabelFor(k)); - return [ - span({ class: 'trending-dominant-sep' }, '|'), - span({ class: 'trending-dominant-text' }, `${i18n.moreVoted || 'More Voted'}: ${dominant.join(' + ')}`) - ]; - })(); - return h2( - `${i18n.trendingTotalOpinions || i18n.trendingTotalCount}: `, - span({ class: 'trending-total-count' }, String(votes)), - ...(dominantPart || []) - ); - })(), + contentHtml + ), + renderVotesSummary(c.opinions), + renderCardMetaRow( details({ class: 'opinions-voting-collapse' }, summary({ class: 'opinions-summary' }, span({ class: 'opinions-summary-icon' }, 'ꔍ'), @@ -272,26 +284,35 @@ const renderTrendingCard = (item, votes, categories, seenTitles, spreadMap = new ) ) ) + ), + p( + { class: 'card-footer' }, + span({ class: 'date-link' }, `${created}`), + userLink(item.value.author) ) ) ); }; -exports.trendingView = (items, filter, categories = opinionCategories, spreadMap = new Map(), q = '') => { +exports.trendingView = (items, filter, categories = opinionCategories, spreadMap = new Map(), q = '', allItems = null, params = {}) => { const seenDocumentTitles = new Set(); const title = i18n.trendingTitle; - const baseFilters = ['ALL', 'MINE', 'RECENT', 'TOP']; + const baseFilters = ['ALL', 'RECENT', 'MINE', 'TOP']; const contentFilters = [ ['votes', 'event', 'task', 'report'], ['feed', 'project', 'industry', 'shopProduct', 'transfer'], - ['audio', 'bookmark', 'document', 'image', 'torrent', 'video'] + ['audio', 'bookmark', 'document', 'file', 'image', 'torrent', 'video'], + ['podcast', 'podcastEpisode', 'campaign', 'logisticsRoute'] ]; let filteredItems = items.filter(item => { const c = item.value?.content || item.content; return c && typeof c === 'object' && c.type !== 'tombstone'; }); + const censusItems = Array.isArray(allItems) ? allItems : filteredItems; + const presentTypes = new Set(censusItems.map(item => (item.value?.content || item.content || {}).type).filter(Boolean)); + const emptyTrend = censusItems.length === 0 && !String(q || '').trim(); if (filter === 'MINE') { filteredItems = filteredItems.filter(item => item.value.author === userId); @@ -316,15 +337,25 @@ exports.trendingView = (items, filter, categories = opinionCategories, spreadMap filteredItems.sort((a, b) => b.value.timestamp - a.value.timestamp); } - const header = div({ class: 'tags-header' }, h2(title), p(i18n.exploreTrending)); - const cards = filteredItems + const header = div({ class: 'tags-header module-header-line' }, h2(title), p(i18n.exploreTrending)); + const seenDocs = new Set(); + const shownItems = filteredItems.filter(item => { + const c = item.value.content || {}; + const t = c.type === 'document' ? String(c.title || '').trim() : ''; + if (!t) return true; + if (seenDocs.has(t)) return false; + seenDocs.add(t); + return true; + }); + const cards = paged(shownItems) .map(item => renderTrendingCard( item, Object.values(item.value.content.opinions || {}).reduce((s, n) => s + (n || 0), 0), categories, seenDocumentTitles, - spreadMap + spreadMap, + { favIndex: params.favIndex, returnTo: `/trending?filter=${encodeURIComponent(filter)}` } ) ) .filter(Boolean); @@ -335,14 +366,16 @@ exports.trendingView = (items, filter, categories = opinionCategories, spreadMap title, section( header, - div( + emptyTrend ? null : div( { class: 'mode-buttons' }, div({ class: 'column' }, baseFilters.map(mode => filterButton(mode, filter))), - ...contentFilters.map(row => - div({ class: 'column' }, row.map(mode => filterButton(mode, filter))) - ) + ...contentFilters.map(row => { + const visible = row.filter(mode => presentTypes.has(mode) || (mode === 'votes' && presentTypes.has('poll'))); + return visible.length ? div({ class: 'column' }, visible.map(mode => filterButton(mode, filter))) : null; + }) ), - div({ class: 'filters' }, + emptyTrend ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStats(shownItems.length), form({ method: 'GET', action: '/trending', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ type: 'text', name: 'q', value: q, placeholder: i18n.trendingSearchPlaceholder, class: 'filter-box__input' }), @@ -361,8 +394,7 @@ exports.trendingView = (items, filter, categories = opinionCategories, spreadMap if (hasDocument) { html += ` - - + `; } diff --git a/src/views/tribes_view.js b/src/views/tribes_view.js index bfb197b..aa4f38e 100644 --- a/src/views/tribes_view.js +++ b/src/views/tribes_view.js @@ -1,15 +1,82 @@ const { div, h2, h3, p, section, button, form, a, input, img, label, select, option, br, textarea, h1, span, nav, ul, li, video, audio, table, tr, td, thead, tbody, th } = require("../server/node_modules/hyperaxe"); +const { TEXT_CAP } = require('../backend/long_text'); const moment = require("../server/node_modules/moment"); -const { template, i18n, userLink, renderStateChip, renderPrivacyChip, renderLifespanChip, renderModeChip, renderInviteQrCard, renderContentActions } = require('./main_views'); -const { renderEncryptedChip: renderTribeEncryptedChip } = require('./clearnet_view'); -const { renderResults: renderPollResults, renderBallot: renderPollBallot } = require('./polls_view'); +const { template, i18n, userLink, renderStateChip, renderPrivacyChip, renderLifespanChip, renderModeChip, renderInviteQrCard, renderContentActions, renderSubscriptionBox, renderModuleStats, renderModuleStatsBy, moduleIsEmpty, renderTorrentDownload, torrentDownloadHref, contentDeleteAction, clearnetSlugFor, renderEngagement, renderOpinionsVoting, renderVotesSummary, paged, CLEARNET_HUB_CSS } = require('./main_views'); +const { renderTribeWikiSection } = require('./wiki_view'); +const { renderEncryptedChip: renderTribeEncryptedChip, renderLicenseSelect, renderReachChip, renderClearnetPage, escapeHtml, renderRichText, blobUrl: cnBlobUrl, paginateClearnet, renderClearnetPager, CLEARNET_PAGER_CSS } = require('./clearnet_view'); +const { renderPollCard } = require('./polls_view'); +const { renderFeedCard } = require('./feed_view'); +const { renderEventItem } = require('./event_view'); +const { renderTaskItem, renderTaskAssignAction, renderTaskStatusRow } = require('./task_view'); +const { renderVoteListItem, renderVoteOptionsBlock } = require('./vote_view'); +const { renderImageList } = require('./image_view'); +const { renderAudioList } = require('./audio_view'); +const { renderVideoList } = require('./video_view'); +const { renderDocumentList } = require('./document_view'); +const { renderBookmarkList } = require('./bookmark_view'); +const { renderTorrentTable } = require('./torrents_view'); +const { renderFileTable } = require('./files_view'); +const { renderPadCard } = require('./pads_view'); +const { renderChatCard } = require('./chats_view'); +const { renderCalendarCard } = require('./calendars_view'); +const { renderInhabitantCard } = require('./inhabitants_view'); const { config } = require('../server/SSB_server.js'); -const { renderUrl } = require('../backend/renderUrl'); -const { renderMapLocationUrl, renderMapLocationGrid, renderMapLocationVisitLabel } = require("./maps_view"); -const opinion_categories = require('../backend/opinion_categories.js'); +const { renderStyledText, safeExternalHref } = require('../backend/renderStyledText'); +const { renderMapCard, renderMapLocationVisitLabel, renderMapEmbed } = require("./maps_view"); +const { renderTribeRoomsSection, roomStatsBy } = require('./rooms_view'); const userId = config.keys.id; const isLarpHouseTribe = (t) => Array.isArray(t && t.tags) && t.tags.some(x => String(x).startsWith('larp-')); +const isTribeMember = (t) => !!t && (t.author === userId || (Array.isArray(t.members) && t.members.includes(userId))); + +const REACH_LEVELS = ['tribe', 'oasis', 'clearnet']; +const REACH_CONTENT_TYPES = new Set(['event', 'task', 'votation', 'forum', 'forum-reply', 'media', 'feed']); +const reachLevelOf = (item) => REACH_LEVELS.includes(item && item.reach) ? item.reach : 'tribe'; +const tribeReachTribeLabel = () => String(i18n.tribeReachTribe || 'TRIBE').toUpperCase(); +const tribeClearnetHref = (t) => `/c/tribe/${encodeURIComponent(clearnetSlugFor(t.title, t.rootId || t.id))}`; + +const renderTribeReachChip = (level, tribe) => level === 'oasis' || level === 'clearnet' + ? renderReachChip(level === 'clearnet', i18n, level === 'clearnet' && tribe ? tribeClearnetHref(tribe) : null) + : renderStateChip('encrypted', '🔒', i18n.tribeReachTribe || 'TRIBE'); + +const renderTribeReachSwitch = (tribe, item, returnTo) => { + const current = reachLevelOf(item); + const levelBtn = (level, text) => button({ type: 'submit', name: 'reach', value: level, class: current === level ? 'clearnet-switch-btn active' : 'clearnet-switch-btn', ...(current === level ? { disabled: true } : {}) }, text); + return form({ method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/content/${encodeURIComponent(item.id)}/reach`, class: 'clearnet-switch', title: i18n.tribeReachChange }, + input({ type: 'hidden', name: 'returnTo', value: returnTo }), + levelBtn('tribe', tribeReachTribeLabel()), + levelBtn('oasis', 'OASIS'), + levelBtn('clearnet', 'CLEARNET') + ); +}; + +const renderTribeReachSelector = () => + div({ class: 'clearnet-choice' }, + div({ class: 'clearnet-choice-options' }, + label({ class: 'clearnet-choice-option' }, input({ type: 'radio', name: 'reach', value: 'tribe', checked: true }), renderTribeReachChip('tribe')), + label({ class: 'clearnet-choice-option' }, input({ type: 'radio', name: 'reach', value: 'oasis' }), renderTribeReachChip('oasis')), + label({ class: 'clearnet-choice-option' }, input({ type: 'radio', name: 'reach', value: 'clearnet' }), renderTribeReachChip('clearnet')) + ), + p({ class: 'tribe-meta-label' }, i18n.tribeReachNotice) + ); + +const reachIndexOf = (items) => { + const byId = new Map(); + (Array.isArray(items) ? items : []).forEach(i => { + if (!i || i.contentType !== 'forum') return; + if (i.id) byId.set(i.id, i); + if (i.originId) byId.set(i.originId, i); + }); + return byId; +}; + +const renderTribeReachListChip = (tribe, item, threadsById) => { + if (!item || !REACH_CONTENT_TYPES.has(item.contentType)) return null; + const parent = item.contentType === 'forum-reply' && threadsById ? threadsById.get(item.parentId) : null; + const level = reachLevelOf(parent || item); + if (!tribe.reachAllowed && level === 'tribe') return null; + return div({ class: 'card-chips-row' }, renderTribeReachChip(level, item.tribeName ? null : tribe)); +}; const DEFAULT_HASH_ENC = "%260000000000000000000000000000000000000000000%3D.sha256"; const DEFAULT_HASH_PATH_RE = /\/image\/\d+\/%260000000000000000000000000000000000000000000%3D\.sha256$/; @@ -35,21 +102,20 @@ const resolvePhoto = (photoField) => { }; const MS_PER_DAY = 86400000; -const FEED_ITEMS_PER_PAGE = 5; const MAX_MESSAGE_LENGTH = 280; const renderMediaBlob = (value, fallbackSrc = null, attrs = {}) => { if (!value) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null const s = String(value).trim() if (!s) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null - if (s.startsWith('/assets/') || s.startsWith('/blob/') || s.startsWith('/image/')) return img({ src: s, ...attrs }) - if (s.startsWith('&')) return img({ src: `/blob/${encodeURIComponent(s)}`, ...attrs }) + if (s.startsWith('/assets/') || s.startsWith('/blob/') || s.startsWith('/image/')) return img({ loading: 'lazy', src: s, ...attrs }) + if (s.startsWith('&')) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(s)}`, ...attrs }) const mVideo = s.match(/\[video:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}` }) + if (mVideo) return video({ controls: true, class: attrs.class || 'post-video', src: `/blob/${encodeURIComponent(mVideo[1])}`, preload: 'metadata' }) const mAudio = s.match(/\[audio:[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) if (mAudio) return audio({ controls: true, class: attrs.class || 'post-audio', src: `/blob/${encodeURIComponent(mAudio[1])}` }) const mImg = s.match(/!\[[^\]]*\]\(\s*(&[^)\s]+\.sha256)\s*\)/) - if (mImg) return img({ src: `/blob/${encodeURIComponent(mImg[1])}`, ...attrs }) + if (mImg) return img({ loading: 'lazy', src: `/blob/${encodeURIComponent(mImg[1])}`, ...attrs }) return fallbackSrc ? img({ src: fallbackSrc, ...attrs }) : null } @@ -70,23 +136,11 @@ const toImageUrl = (raw, fallback) => { return url || fallback } -const filterAndSortFeed = (feed, feedFilter) => { - const parseDate = (d) => typeof d === 'string' ? Date.parse(d) : d; - if (feedFilter === 'MINE') return feed.filter(m => m.author === userId); - if (feedFilter === 'RECENT') { - const last24h = Date.now() - MS_PER_DAY; - return [...feed].filter(m => parseDate(m.createdAt) >= last24h).sort((a, b) => parseDate(b.createdAt) - parseDate(a.createdAt)); - } - if (feedFilter === 'ALL') return [...feed].sort((a, b) => parseDate(b.createdAt) - parseDate(a.createdAt)); - if (feedFilter === 'TOP') return [...feed].sort((a, b) => (b.refeeds || 0) - (a.refeeds || 0)); - return feed; -}; - const renderGallery = (sortedTribes) => { return div({ class: "gallery" }, sortedTribes.length ? sortedTribes.map(t => - a({ href: `#tribe-${encodeURIComponent(t.id)}`, class: "gallery-item" }, + a({ href: `#tribe-${encodeURIComponent(t.id)}`, id: `tribe-${encodeURIComponent(t.id)}-src`, class: "gallery-item" }, img({ src: toImageUrl(t.image, '/assets/images/default-tribe.png'), alt: t.title || "", class: "gallery-image" }) ) ) @@ -98,7 +152,7 @@ const renderLightbox = (sortedTribes) => { return sortedTribes.map(t => div( { id: `tribe-${encodeURIComponent(t.id)}`, class: "lightbox" }, - a({ href: "#", class: "lightbox-close" }, "×"), + a({ href: `#tribe-${encodeURIComponent(t.id)}-src`, class: "lightbox-close" }, "×"), img({ src: toImageUrl(t.image, '/assets/images/default-tribe.png'), class: "lightbox-image", alt: t.title || "" }) ) ); @@ -111,7 +165,7 @@ exports.renderInvitePage = (inviteCode) => { input({ type: 'text', value: inviteCode, readonly: 'readonly', class: 'tribe-invite-code-input' }), br(), form({ method: "GET", action: `/tribes` }, - button({ type: "submit", class: "filter-btn" }, i18n.walletBack) + button({ type: "submit", class: "tribe-action-btn" }, i18n.walletBack) ), ); return template(i18n.tribeInviteMode, section(pageContent)); @@ -169,9 +223,20 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul const title = i18n.tribesTitle; - const header = div({ class: 'tags-header' }, h2(title), p(i18n.tribeDescription)); + const header = div({ class: 'tags-header module-header-line' }, h2(title), p(i18n.tribeDescription)); - const filters = div({ class: 'filters' }, + const emptyMod = moduleIsEmpty(Array.isArray(tribes) ? tribes : [], filter || 'all', 'all', query.search); + const tribeChip = (m) => { + if (m === filter || m === 'all') return true; + if (m === 'top' || m === 'gallery') return tribes.length > 0; + if (m === 'mine') return tribes.some(t => t.author === userId); + if (m === 'membership') return tribes.some(t => Array.isArray(t.members) && t.members.includes(userId)); + if (m === 'subtribes') return tribes.some(t => !!t.parentTribeId); + if (m === 'recent') return tribes.length > 0; + return true; + }; + const filters = emptyMod ? null : div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + renderModuleStatsBy(sorted, t => t.isAnonymous ? 'PRIVATE' : 'PUBLIC', [{ value: 'PUBLIC', label: i18n.tribePublic }, { value: 'PRIVATE', label: i18n.tribePrivate }]), form({ method: 'GET', action: '/tribes', class: 'filter-box' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ type: 'text', name: 'search', placeholder: i18n.searchTribesPlaceholder, value: query.search || '', class: 'filter-box__input' }), @@ -182,7 +247,8 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul ); const modeButtons = div({ class: 'tribe-mode-buttons' }, - ['all','recent','mine','membership','subtribes','top','gallery'].map(f => + ...(emptyMod ? [] : [ + ['all','recent','mine','membership','subtribes','top','gallery'].filter(tribeChip).map(f => form({ method: 'GET', action: '/tribes' }, input({ type: 'hidden', name: 'filter', value: f }), button({ type: 'submit', class: filter === f ? 'filter-btn active' : 'filter-btn' }, @@ -190,6 +256,7 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul ) ) ), + ]), form({ method: 'GET', action: '/tribes/create' }, button({ type: 'submit', class: 'create-button' }, i18n.tribeCreateButton) ) @@ -211,7 +278,7 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul br(), label({ for: 'description' }, i18n.tribeDescriptionLabel), br, - textarea({ name: 'description', id: 'description', required: true, rows: 4, cols: 50, placeholder: i18n.tribeDescriptionPlaceholder }, tribeToEdit.description || ''), + textarea({ maxlength: "3000", name: 'description', id: 'description', required: true, rows: 4, cols: 50, placeholder: i18n.tribeDescriptionPlaceholder }, tribeToEdit.description || ''), br, label({ for: 'location' }, i18n.tribeLocationLabel), br, @@ -250,26 +317,22 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul const allT = allTribes || tribes; - const tribeCards = sorted.map(t => { - const isMember = t.members.includes(userId); + const shownTribes = filter === 'create' || filter === 'edit' ? [] : paged(sorted); + + const tribeCards = shownTribes.map(t => { const parentTribe = t.parentTribeId ? allT.find(p => p.id === t.parentTribeId) : null; const isOwn = String(t.author) === String(userId); return div({ class: 'trending-card tribes-card' + (isOwn ? ' own-content' : '') }, div({ class: 'card-header activity-card-header' }, span(), - renderContentActions(t.id, `/tribe/${encodeURIComponent(t.id)}`, { author: t.author, reportTitle: t.title }) + renderContentActions(t.id, `/tribe/${encodeURIComponent(t.id)}`, { author: t.author, reportTitle: t.title, deleteAction: isOwn && !isLarpHouseTribe(t) ? contentDeleteAction('tribe', t.id) : undefined }) ), div({ class: 'card-section tribes-card-body' }, div({ class: 'tribe-card-image-wrapper' }, a({ href: `/tribe/${encodeURIComponent(t.id)}` }, renderMediaBlob(t.image, '/assets/images/default-tribe.png', { class: 'tribe-card-hero-image' }) - ), - isMember - ? form({ method: 'GET', action: `/tribe/${encodeURIComponent(t.id)}`, class: 'tribe-visit-btn-wrapper' }, - button({ type: 'submit', class: 'filter-btn' }, t.parentTribeId ? String(i18n.tribeviewSubTribeButton || 'VISIT SUB-TRIBE').toUpperCase() : String(i18n.tribeviewTribeButton || '').toUpperCase()) - ) - : null + ) ), div({ class: 'tribe-card-body' }, div({ class: 'shop-title-row' }, @@ -279,45 +342,22 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul t.isAnonymous ? renderPrivacyChip(true, i18n) : renderPrivacyChip(false, i18n), t.isAnonymous ? renderTribeEncryptedChip(i18n) : null, renderModeChip(t.inviteMode, i18n), - renderLifespanChip(t.lifetime, i18n) + renderLifespanChip(t.lifetime, i18n), + t.subscriptionIn === true + ? renderStateChip('mutuals', '\u2709', i18n.subscriptionOn) + : (t.subscriptionIn === false ? renderStateChip('closed', '\u2709', i18n.subscriptionOff) : null) ), - t.description ? p({ class: 'tribe-card-description' }, ...renderUrl(t.description)) : null, + t.description ? p({ class: 'tribe-card-description' }, ...renderStyledText(t.description)) : null, renderMapLocationVisitLabel(t.mapUrl), - (parentTribe || t.location) ? table({ class: 'tribe-info-table' }, - parentTribe ? tr( + parentTribe ? table({ class: 'tribe-info-table' }, + tr( td({ class: 'tribe-info-label' }, i18n.tribeRootLabel || 'ROOT'), td({ class: 'tribe-info-value', colspan: '3' }, a({ href: `/tribe/${encodeURIComponent(parentTribe.id)}` }, parentTribe.title)) - ) : null, - t.location ? tr( - td({ class: 'tribe-info-label' }, i18n.tribeLocationLabel || 'LOCATION'), - td({ class: 'tribe-info-value', colspan: '3' }, ...renderUrl(t.location)) - ) : null + ) ) : null, div({ class: 'tribe-card-members' }, span({ class: 'tribe-members-count' }, `${i18n.tribeMembersCount}: ${t.memberCount != null ? t.memberCount : t.members.length}`) - ), - (t.openInviteCode && !isMember && String(t.author) !== String(userId)) ? div({ class: 'tribe-card-join' }, - form({ method: 'POST', action: '/tribes/open-invite/join' }, - input({ type: 'hidden', name: 'tribeId', value: t.id }), - button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeJoinOpen) - ), - renderInviteQrCard({ qrDataUrl: t.openInviteQr, name: t.title }) - ) : null, - isMember ? div({ class: 'tribe-card-actions' }, - !isLarpHouseTribe(t) - ? form({ method: 'POST', action: '/tribes/generate-invite' }, - input({ type: 'hidden', name: 'tribeId', value: t.id }), - button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeGenerateInvite) - ) - : null, - form({ method: 'POST', action: `/tribes/leave/${encodeURIComponent(t.id)}` }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeLeaveButton) - ) - ) : null, - filter === 'mine' ? div({ class: 'tribe-actions' }, - form({ method: 'GET', action: `/tribes/edit/${encodeURIComponent(t.id)}` }, button({ type: 'submit' }, i18n.tribeUpdateButton)), - form({ method: 'POST', action: `/tribes/delete/${encodeURIComponent(t.id)}` }, button({ type: 'submit', class: 'danger-btn' }, i18n.tribeDeleteButton)) - ) : null + ) ) ) ); @@ -326,64 +366,25 @@ exports.tribesView = async (tribes, filter, tribeId, query = {}, allTribes = nul return template( title, section(header), - section(filters), section(modeButtons), + filters ? section(filters) : null, section( (filter === 'create' || filter === 'edit') ? createForm : filter === 'gallery' - ? renderGallery(sorted) - : div({ class: 'tribe-grid' }, - tribeCards.length > 0 ? tribeCards : p(i18n.noTribes) - ) + ? renderGallery(shownTribes) + : tribeCards.length > 0 + ? div({ class: 'tribe-grid' }, tribeCards) + : div({ class: "no-content-box" }, p(i18n.noTribes)) ), - ...renderLightbox(sorted) - ); -}; - -const renderFeedTribeView = async (feedItems, tribe, query = {}, filter) => { - const feed = Array.isArray(feedItems) ? feedItems : []; - const feedFilter = (query.feedFilter || 'RECENT').toUpperCase(); - const filteredFeed = filterAndSortFeed(feed, feedFilter); - return div({ class: 'tribe-feed-full' }, - div({ class: 'feed-actions' }, - ['TOP', 'MINE', 'ALL', 'RECENT'].map(f => - form({ method: 'GET', action: `/tribe/${encodeURIComponent(tribe.id)}` }, - input({ type: 'hidden', name: 'section', value: 'feed' }), - input({ type: 'hidden', name: 'feedFilter', value: f }), - button({ type: 'submit', class: feedFilter === f ? 'filter-btn active' : 'filter-btn' }, String(i18n[`tribeFeedFilter${f}`]).toUpperCase()) - ) - ) - ), - filteredFeed.length === 0 - ? p(i18n.tribeFeedEmpty) - : div({ class: 'feed-list' }, - filteredFeed.map(m => div({ class: 'feed-item' }, - div({ class: 'feed-row' }, - div({ class: 'refeed-column' }, - h1(`${m.refeeds || 0}`), - !(m.refeeds_inhabitants || []).includes(userId) - ? form( - { method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/refeed/${encodeURIComponent(m.id)}` }, - button({ class: 'refeed-btn' }, i18n.tribeFeedRefeed) - ) - : null - ), - div({ class: 'feed-main' }, - p(`${new Date(m.createdAt).toLocaleString()} — `, userLink(m.author)), - br, - p(...renderUrl(m.description)) - ) - ) - )) - ) + ...renderLightbox(shownTribes) ); }; const sectionLink = (tribe, sectionKey, label, currentSection) => form({ method: 'GET', action: `/tribe/${encodeURIComponent(tribe.id)}` }, input({ type: 'hidden', name: 'section', value: sectionKey }), - button({ type: 'submit', class: currentSection === sectionKey ? 'filter-btn active' : 'filter-btn' }, label) + button({ type: 'submit', class: currentSection === sectionKey ? 'filter-btn active' : 'filter-btn' }, String(label).toUpperCase()) ); const renderSectionNav = (tribe, section) => { @@ -393,8 +394,8 @@ const renderSectionNav = (tribe, section) => { const sections = [ { items: firstGroup }, { items: [{ key: 'votations', label: i18n.tribeSectionVotations }, { key: 'polls', label: i18n.pollsTitle }, { key: 'events', label: i18n.tribeSectionEvents }, { key: 'tasks', label: i18n.tribeSectionTasks }] }, - { items: [{ key: 'feed', label: i18n.tribeSectionFeed }, { key: 'forum', label: i18n.tribeSectionForum }, { key: 'maps', label: i18n.tribeSectionMaps || 'MAPS' }, { key: 'torrents', label: i18n.tribeSectionTorrents || 'TORRENTS' }, { key: 'pads', label: i18n.tribeSectionPads || 'PADS' }, { key: 'chats', label: i18n.tribeSectionChats || 'CHATS' }, { key: 'calendars', label: i18n.tribeSectionCalendars || 'CALENDARS' }] }, - { items: [{ key: 'images', label: i18n.tribeSectionImages || 'IMAGES' }, { key: 'audios', label: i18n.tribeSectionAudios || 'AUDIOS' }, { key: 'videos', label: i18n.tribeSectionVideos || 'VIDEOS' }, { key: 'documents', label: i18n.tribeSectionDocuments || 'DOCUMENTS' }, { key: 'bookmarks', label: i18n.tribeSectionBookmarks || 'BOOKMARKS' }] }, + { items: [{ key: 'feed', label: i18n.tribeSectionFeed }, { key: 'forum', label: i18n.tribeSectionForum }, { key: 'maps', label: i18n.tribeSectionMaps || 'MAPS' }, { key: 'torrents', label: i18n.tribeSectionTorrents || 'TORRENTS' }, { key: 'pads', label: i18n.tribeSectionPads || 'PADS' }, { key: 'wiki', label: i18n.tribeSectionWiki || 'WIKI' }, { key: 'chats', label: i18n.tribeSectionChats || 'CHATS' }, { key: 'rooms', label: i18n.tribeSectionRooms || 'ROOMS' }, { key: 'calendars', label: i18n.tribeSectionCalendars || 'CALENDARS' }] }, + { items: [{ key: 'images', label: i18n.tribeSectionImages || 'IMAGES' }, { key: 'audios', label: i18n.tribeSectionAudios || 'AUDIOS' }, { key: 'videos', label: i18n.tribeSectionVideos || 'VIDEOS' }, { key: 'documents', label: i18n.tribeSectionDocuments || 'DOCUMENTS' }, { key: 'files', label: i18n.tribeSectionFiles || 'FILES' }, { key: 'bookmarks', label: i18n.tribeSectionBookmarks || 'BOOKMARKS' }] }, { items: [{ key: 'tags', label: i18n.tribeSectionTags || 'TAGS' }, { key: 'search', label: i18n.tribeSectionSearch }] }, ]; return div({ class: 'tribe-section-nav no-border' }, @@ -402,7 +403,7 @@ const renderSectionNav = (tribe, section) => { div({ class: 'tribe-section-group no-border' }, g.items.map(s => s.href ? form({ method: 'GET', action: s.href }, - button({ type: 'submit', class: 'filter-btn' }, s.label) + button({ type: 'submit', class: 'filter-btn' }, String(s.label).toUpperCase()) ) : sectionLink(tribe, s.key, s.label, section)) ) @@ -423,11 +424,6 @@ const priorityI18n = () => ({ 'CRITICAL': i18n.tribePriorityCritical, }); -const inviteModeI18n = () => ({ - 'strict': i18n.tribeStrict, - 'open': i18n.tribeOpen, -}); - const forumCatI18n = () => ({ 'GENERAL': i18n.tribeForumCatGeneral, 'PROPOSAL': i18n.tribeForumCatProposal, @@ -435,22 +431,6 @@ const forumCatI18n = () => ({ 'ANNOUNCEMENT': i18n.tribeForumCatAnnouncement, }); -const mediaTypeI18n = () => ({ - 'all': i18n.tribeMediaFilterAll, - 'image': i18n.tribeMediaTypeImage, - 'video': i18n.tribeMediaTypeVideo, - 'audio': i18n.tribeMediaTypeAudio, - 'document': i18n.tribeMediaTypeDocument, - 'bookmark': i18n.tribeMediaTypeBookmark, -}); - -const taskFilterI18n = () => ({ - 'all': i18n.tribeTaskFilterAll, - 'open': i18n.tribeStatusOpen, - 'in-progress': i18n.tribeStatusInProgress, - 'closed': i18n.tribeStatusClosed, -}); - const statusBadge = (status) => { const cls = status === 'OPEN' ? 'tribe-content-status-open' : status === 'CLOSED' ? 'tribe-content-status-closed' @@ -469,7 +449,7 @@ const contentTypeVerb = (ct) => { }; const contentTypeName = (ct) => { - const map = { event: i18n.tribeSectionEvents, task: i18n.tribeSectionTasks, votation: i18n.tribeSectionVotations, poll: i18n.pollsTitle, forum: i18n.tribeSectionForum, 'forum-reply': i18n.tribeSectionForum, media: i18n.tribeSectionMedia, feed: i18n.tribeSectionFeed, pad: i18n.tribeSectionPads || 'PADS', chat: i18n.tribeSectionChats || 'CHATS', calendar: i18n.tribeSectionCalendars || 'CALENDARS', map: i18n.tribeSectionMaps || 'MAPS' }; + const map = { event: i18n.tribeSectionEvents, task: i18n.tribeSectionTasks, votation: i18n.tribeSectionVotations, poll: i18n.pollsTitle, forum: i18n.tribeSectionForum, 'forum-reply': i18n.tribeSectionForum, blog: i18n.blogTitle, media: i18n.tribeSectionMedia, feed: i18n.tribeSectionFeed, pad: i18n.tribeSectionPads || 'PADS', chat: i18n.tribeSectionChats || 'CHATS', calendar: i18n.tribeSectionCalendars || 'CALENDARS', map: i18n.tribeSectionMaps || 'MAPS', wiki: i18n.tribeSectionWiki || 'WIKI' }; return map[ct] || ct; }; @@ -477,19 +457,19 @@ const activitySectionMap = { event: 'events', task: 'tasks', votation: 'votations', poll: 'polls', forum: 'forum', 'forum-reply': 'forum', feed: 'feed', - pad: 'pads', chat: 'chats', calendar: 'calendars', map: 'maps', torrent: 'torrents' + pad: 'pads', chat: 'chats', calendar: 'calendars', map: 'maps', wiki: 'wiki', torrent: 'torrents', file: 'files' }; const activitySectionForItem = (item) => { if (item.contentType === 'media' && item.mediaType) { - const map = { image: 'images', audio: 'audios', video: 'videos', document: 'documents', bookmark: 'bookmarks', torrent: 'torrents' }; + const map = { image: 'images', audio: 'audios', video: 'videos', document: 'documents', bookmark: 'bookmarks', torrent: 'torrents', file: 'files' }; return map[item.mediaType] || 'media'; } return activitySectionMap[item.contentType] || 'activity'; }; const activityMediaTypeName = (mt) => { - const map = { image: i18n.tribeSectionImages, audio: i18n.tribeSectionAudios, video: i18n.tribeSectionVideos, document: i18n.tribeSectionDocuments, bookmark: i18n.tribeSectionBookmarks, torrent: i18n.tribeSectionTorrents }; + const map = { image: i18n.tribeSectionImages, audio: i18n.tribeSectionAudios, video: i18n.tribeSectionVideos, document: i18n.tribeSectionDocuments, bookmark: i18n.tribeSectionBookmarks, torrent: i18n.tribeSectionTorrents, file: i18n.tribeSectionFiles }; return map[mt] || i18n.tribeSectionMedia || 'MEDIA'; }; @@ -497,19 +477,20 @@ const renderTribeActivitySection = (tribe, sectionData) => { const { activities } = sectionData || { activities: [] }; if (activities.length === 0) return div({ class: 'tribe-content-list' }, p(i18n.tribeActivityEmpty)); const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; + const threadsById = reachIndexOf(activities); return div({ class: 'tribe-content-list tribe-content-list-spaced' }, - activities.slice(0, 50).map(item => { + paged(activities).map(item => { if (item.encrypted) return div({ class: 'card card-rpg' }, div({ class: 'tribe-card-body' }, p({ class: 'tribe-meta-label' }, i18n.tribeContentEncrypted || 'Encrypted content'))); if (item.contentType === 'chatThread') { const replies = Array.isArray(item.replies) ? item.replies : []; - const tDate = item.timestamp ? new Date(item.timestamp).toLocaleString() : ''; + const tDate = item.timestamp ? moment(item.timestamp).format("YYYY/MM/DD HH:mm") : ''; const headerText = item.tribeName ? `[${String(i18n.typeChat || 'CHAT').toUpperCase()} · THREAD · ${item.tribeName}]` : `[${String(i18n.typeChat || 'CHAT').toUpperCase()} · THREAD]`; return div({ class: 'card card-rpg tribe-card-padded chat-thread' }, div({ class: 'card-header' }, h2({ class: 'card-label' }, headerText), - a({ href: item.directUrl, class: 'filter-btn' }, i18n.viewDetails || 'View Details') + a({ href: item.directUrl, class: 'tribe-action-btn' }, i18n.viewDetails || 'View Details') ), div({ class: 'tribe-card-body' }, item.title ? div({ class: 'card-field' }, span({ class: 'card-value' }, item.title)) : null, @@ -517,18 +498,18 @@ const renderTribeActivitySection = (tribe, sectionData) => { replies.map(r => div({ class: 'thread-reply-item' }, r.text ? p({ class: 'post-text post-text-pre' }, String(r.text)) : null, div({ class: 'card-footer thread-reply-footer' }, - span({ class: 'date-link' }, r.createdAt ? new Date(r.createdAt).toLocaleString() : ''), + span({ class: 'date-link' }, r.createdAt ? moment(r.createdAt).format("YYYY/MM/DD HH:mm") : ''), userLink(r.author, '') ) )) ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${tDate} ${i18n.performed || ''} `), + span({ class: 'date-link' }, `${tDate}`), userLink(item.author, item.authorName) ) ); } - const date = item.timestamp ? new Date(item.timestamp).toLocaleString() : ''; + const date = item.timestamp ? moment(item.timestamp).format("YYYY/MM/DD HH:mm") : ''; const typeLabel = item.contentType === 'media' && item.mediaType ? activityMediaTypeName(item.mediaType) : contentTypeName(item.contentType); @@ -539,27 +520,28 @@ const renderTribeActivitySection = (tribe, sectionData) => { const blobUrl = item.contentType === 'media' ? toBlobUrl(item.image) : null; const mediaContent = item.contentType === 'media' && item.mediaType === 'image' && blobUrl - ? a({ href: blobUrl, target: '_blank' }, img({ src: blobUrl, alt: item.title || '', class: 'tribe-media-thumb' })) + ? a({ href: blobUrl, target: '_blank', rel: 'noopener noreferrer' }, img({ src: blobUrl, alt: item.title || '', class: 'tribe-media-thumb' })) : item.contentType === 'media' && item.mediaType === 'audio' && blobUrl ? audio({ src: blobUrl, controls: true, class: 'tribe-media-audio' }) : item.contentType === 'media' && item.mediaType === 'video' && blobUrl - ? video({ src: blobUrl, controls: true, class: 'tribe-media-thumb' }) + ? video({ src: blobUrl, controls: true, class: 'tribe-media-thumb', preload: 'metadata' }) : item.contentType === 'media' && item.mediaType === 'document' && blobUrl - ? a({ href: blobUrl, target: '_blank', class: 'tribe-action-btn' }, i18n.readDocument || 'Read Document') + ? a({ href: blobUrl, target: '_blank', rel: 'noopener noreferrer', class: 'tribe-action-btn' }, i18n.readDocument || 'Read Document') : item.contentType === 'media' && item.mediaType === 'bookmark' && (item.url || item.description) - ? a({ href: item.url || item.description, target: '_blank', class: 'tribe-action-btn' }, item.url || item.description) + ? a({ href: safeExternalHref(item.url || item.description), target: '_blank', rel: 'noopener noreferrer', class: 'tribe-action-btn' }, item.url || item.description) : null; return div({ class: 'card card-rpg tribe-card-padded' }, div({ class: 'card-header' }, h2({ class: 'card-label' }, headerText), item.directUrl - ? a({ href: item.directUrl, class: 'filter-btn' }, i18n.viewDetails || 'View Details') + ? a({ href: item.directUrl, class: 'tribe-action-btn' }, i18n.viewDetails || 'View Details') : form({ method: 'GET', action: tribeUrl }, input({ type: 'hidden', name: 'section', value: targetSection }), - button({ type: 'submit', class: 'filter-btn' }, i18n.viewDetails || 'View Details') + button({ type: 'submit', class: 'tribe-action-btn' }, i18n.viewDetails || 'View Details') ) ), div({ class: 'tribe-card-body' }, + renderTribeReachListChip(tribe, item, threadsById), item.title ? div({ class: 'card-field' }, span({ class: 'card-value' }, item.title) ) : null, @@ -567,7 +549,7 @@ const renderTribeActivitySection = (tribe, sectionData) => { item.description && !(item.contentType === 'media' && item.mediaType === 'bookmark' && item.description === item.url) ? p(item.description.substring(0, 200)) : null ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, `${date} ${i18n.performed || ''} `), + span({ class: 'date-link' }, `${date}`), userLink(item.author, item.authorName) ) ); @@ -585,7 +567,7 @@ const renderTribeTrendingSection = (tribe, sectionData, query) => { div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionTrending)), div({ class: 'tribe-filter-bar' }, periodBtn('day', i18n.tribeTrendingPeriodDay), periodBtn('week', i18n.tribeTrendingPeriodWeek), periodBtn('all', i18n.tribeTrendingPeriodAll)), items.length === 0 ? p(i18n.tribeTrendingEmpty) : - items.slice(0, 30).map((item, idx) => { + paged(items.map((item, idx) => [item, idx])).map(([item, idx]) => { if (item.encrypted) return div({ class: 'tribe-content-card' }, div({ class: 'tribe-content-meta' }, span(`#${idx + 1}`)), p({ class: 'tribe-meta-label' }, i18n.tribeContentEncrypted || 'Encrypted content')); return div({ class: 'tribe-content-card' }, div({ class: 'tribe-content-meta' }, @@ -594,6 +576,7 @@ const renderTribeTrendingSection = (tribe, sectionData, query) => { span(`${i18n.tribeTrendingEngagement}: ${engagementScore(item)}`), (() => { const entries = Object.entries(item.opinions || {}); if (!entries.length) return null; const top = entries.reduce((a, b) => b[1] > a[1] ? b : a); return span(`| ${i18n.tribeTopCategory}: ${i18n['opinionCat' + top[0].charAt(0).toUpperCase() + top[0].slice(1)] || top[0]}`); })() ), + renderTribeReachListChip(tribe, item, null), item.title ? h2(item.title) : item.description ? h2(item.description) : null, div({ class: 'tribe-content-meta' }, item.refeeds ? span(`${i18n.tribeActivityRefeed}: ${item.refeeds}`) : null, @@ -610,8 +593,9 @@ const renderTribeTagsSection = (tribe, sectionData, query) => { const filteredItems = Array.isArray(sectionData?.filteredItems) ? sectionData.filteredItems : []; const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; const sortedTags = tagsList.slice().sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag)); + const threadsById = reachIndexOf(tagsList.flatMap(t => Array.isArray(t.items) ? t.items : [])); return section( - div({ class: 'tags-header' }, + div({ class: 'tags-header module-header-line' }, h2(i18n.tribeSectionTags || i18n.tagsTitle || 'TAGS'), p(i18n.tagsDescription || '') ), @@ -624,7 +608,7 @@ const renderTribeTagsSection = (tribe, sectionData, query) => { th(i18n.tagsTableHeaderCount || 'Count') )), tbody( - sortedTags.map(t => tr( + (selectedTag ? sortedTags : paged(sortedTags)).map(t => tr( td(a({ href: `${tribeUrl}?section=tags&tag=${encodeURIComponent(t.tag)}` }, t.tag)), td(`${t.count}`) )) @@ -634,14 +618,15 @@ const renderTribeTagsSection = (tribe, sectionData, query) => { selectedTag ? div({ class: 'tribe-content-list' }, h2(`#${selectedTag} (${filteredItems.length})`), filteredItems.length === 0 ? p(i18n.tribeTagsEmpty || 'No items') : - filteredItems.slice(0, 50).map(item => div({ class: 'card card-rpg' }, + paged(filteredItems).map(item => div({ class: 'card card-rpg' }, div({ class: 'card-header' }, h2({ class: 'card-label' }, `[${String(contentTypeName(item.contentType)).toUpperCase()}]`)), div({ class: 'card-body' }, + renderTribeReachListChip(tribe, item, threadsById), item.title ? div({ class: 'card-field' }, span({ class: 'card-value' }, item.title)) : null, item.description ? p(item.description.substring(0, 200)) : null ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(item.createdAt).toLocaleString()), + span({ class: 'date-link' }, moment(item.createdAt).format("YYYY/MM/DD HH:mm")), userLink(item.author) ) )) @@ -653,6 +638,7 @@ const renderTribeSearchSection = (tribe, sectionData, query) => { const { results } = sectionData || { results: [] }; const sq = sectionData?.query || ''; const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; + const threadsById = reachIndexOf(results); return div({ class: 'tribe-content-list' }, div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionSearch)), form({ method: 'GET', action: tribeUrl, class: 'tribe-search-form' }, @@ -664,11 +650,12 @@ const renderTribeSearchSection = (tribe, sectionData, query) => { sq.length >= 2 ? div( h2(`${i18n.tribeSearchResults}: ${results.length}`), results.length === 0 ? p(i18n.tribeSearchEmpty) : - results.map(item => div({ class: 'card card-rpg' }, + paged(results).map(item => div({ class: 'card card-rpg' }, div({ class: 'card-header' }, h2({ class: 'card-label' }, `[${String(contentTypeName(item.contentType)).toUpperCase()}]`) ), div({ class: 'card-body' }, + renderTribeReachListChip(tribe, item, threadsById), item.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, (i18n.title || 'Title') + ':'), span({ class: 'card-value' }, item.title) @@ -676,7 +663,7 @@ const renderTribeSearchSection = (tribe, sectionData, query) => { item.description ? p(item.description.substring(0, 200)) : null ), p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(item.createdAt).toLocaleDateString()), + span({ class: 'date-link' }, moment(item.createdAt).format("YYYY/MM/DD HH:mm")), userLink(item.author) ) )) @@ -698,8 +685,8 @@ const renderOverviewSection = (tribe, query, sectionData) => { recentFeed.length === 0 ? p(i18n.tribeFeedEmpty) : recentFeed.map(m => div({ class: 'feed-item' }, - p(`${new Date(m.createdAt).toLocaleString()} — `, userLink(m.author)), - p(...renderUrl(m.description)) + p(`${moment(m.createdAt).format("YYYY/MM/DD HH:mm")} — `, userLink(m.author)), + p(...renderStyledText(m.description)) ) ) ), @@ -740,23 +727,830 @@ const renderOverviewSection = (tribe, query, sectionData) => { ); }; -const renderInhabitantsSection = (tribe, members) => { - const resolved = Array.isArray(members) ? members : []; - if (resolved.length === 0) return p(i18n.tribeInhabitantsEmpty); - return div({ class: 'tribe-thumb-grid' }, - resolved.map(m => - a({ href: `/author/${encodeURIComponent(m.id)}`, class: 'tribe-thumb-link', title: m.name || m.id }, - img({ src: resolvePhoto(m.photo), class: 'tribe-thumb-img', alt: m.name || m.id }) +const tribeUrlOf = (tribe) => `/tribe/${encodeURIComponent(tribe.id)}`; +const sectionHref = (tribe, sectionKey, params = {}) => { + const qs = Object.entries({ section: sectionKey, ...params }) + .filter(([, v]) => v !== undefined && v !== null && String(v) !== '') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); + return `${tribeUrlOf(tribe)}?${qs}`; +}; +const asList = (v) => Array.isArray(v) ? v : []; +const tsOf = (item) => Date.parse(item.createdAt) || item._ts || 0; +const isRecentItem = (item) => tsOf(item) >= Date.now() - MS_PER_DAY; +const isMineItem = (item) => String(item.author) === String(userId); +const tribeStatusOf = (item) => String(item.status || 'OPEN').toUpperCase(); +const capFirst = (s) => String(s).charAt(0).toUpperCase() + String(s).slice(1); +const blobIdOf = (raw) => { + const s = String(raw || '').trim(); + if (s.startsWith('&')) return s; + const m = s.match(/\((&[^)\s]+\.sha256)\s*\)/); + return m ? m[1] : null; +}; +const matchesTribeQuery = (item, q) => { + if (!q) return true; + const needle = q.toLowerCase(); + return [item.title, item.description, item.question, item.url, item.location, item.number, ...asList(item.tags)] + .some(v => String(v || '').toLowerCase().includes(needle)); +}; +const sortRecentFirst = (items) => [...items].sort((a, b) => tsOf(b) - tsOf(a)); +const BASE_MODES = { recent: isRecentItem, mine: isMineItem, all: () => true, top: () => true }; +const applyTribeFilter = (items, filter, q, modes = BASE_MODES, score = engagementScore) => { + const pred = modes[filter] || modes.all || (() => true); + const kept = asList(items).filter(it => pred(it) && matchesTribeQuery(it, q)); + return filter === 'top' ? [...kept].sort((a, b) => score(b) - score(a) || tsOf(b) - tsOf(a)) : sortRecentFirst(kept); +}; +const tribeChipVisible = (items, filter, modes) => (mode) => + mode === filter || mode === 'all' || mode === 'ALL' || asList(items).some(it => (modes[mode] || (() => true))(it)); +const openClosedModes = (isClosed) => ({ ...BASE_MODES, open: it => !isClosed(it), closed: isClosed }); +const openClosedStats = (items, isClosed, openLabel, closedLabel) => + renderModuleStatsBy(items, it => isClosed(it) ? 'CLOSED' : 'OPEN', [{ value: 'OPEN', label: openLabel }, { value: 'CLOSED', label: closedLabel }]); + +const renderTribeSectionHeader = (title, description) => + div({ class: 'tags-header module-header-line' }, h2(title), description ? p(description) : null); + +const renderTribeModeChips = (tribe, sectionKey, { modes = [], filter = 'all', labelOf = (m) => m, visible = null, emptyMod = false, create = null }) => + div({ class: 'mode-buttons-row' }, + ...(emptyMod ? [] : modes.filter(m => !visible || visible(m)).map(m => + form({ method: 'GET', action: tribeUrlOf(tribe) }, + input({ type: 'hidden', name: 'section', value: sectionKey }), + input({ type: 'hidden', name: 'filter', value: m }), + button({ type: 'submit', class: filter === m ? 'filter-btn active' : 'filter-btn' }, String(labelOf(m) || m).toUpperCase()) + ) + )), + create + ? form({ method: 'GET', action: create.action || tribeUrlOf(tribe) }, + ...Object.entries(create.hidden || { section: sectionKey, action: 'create' }).map(([k, v]) => input({ type: 'hidden', name: k, value: v })), + button({ type: 'submit', class: 'create-button' }, create.label) + ) + : null + ); + +const renderTribeSearchRow = (tribe, sectionKey, filter, q, statsNode, placeholder, paramName = 'q') => + div({ class: 'filters activity-filter-chips activity-toolbar-row' }, + statsNode, + form({ method: 'GET', action: tribeUrlOf(tribe), class: 'filter-box' }, + input({ type: 'hidden', name: 'section', value: sectionKey }), + filter ? input({ type: 'hidden', name: 'filter', value: filter }) : null, + input({ type: 'text', name: paramName, value: q || '', placeholder, class: 'filter-box__input' }), + div({ class: 'filter-box__controls' }, button({ type: 'submit', class: 'filter-box__button' }, i18n.searchButton)) + ) + ); + +const renderTribeEmpty = (text) => div({ class: 'no-content-box' }, p(text)); + +const tribeDescription = (text) => text ? p({ class: 'tribe-card-description' }, ...renderStyledText(String(text))) : null; + +const tribeReachChipFor = (tribe, item) => { + const level = reachLevelOf(item); + return tribe.reachAllowed || level !== 'tribe' ? renderTribeReachChip(level, tribe) : null; +}; + +const renderTribeReachControls = (tribe, item, returnTo) => { + const levelSwitch = returnTo && tribe.reachAllowed && isTribeMember(tribe) && item.contentType !== 'forum-reply' && item.id ? renderTribeReachSwitch(tribe, item, returnTo) : null; + const openedBy = item.exposedBy && item.exposedBy !== item.author + ? span({ class: 'tribe-meta-label' }, `${i18n.tribeReachOpenedBy || 'Opened by'}: `, userLink(item.exposedBy)) + : null; + return levelSwitch || openedBy ? div({ class: 'card-chips-row' }, levelSwitch, openedBy) : null; +}; + +const tribeItemActions = (tribe, item, member, title) => + renderContentActions(item.id, null, { + author: item.author, + reportTitle: title || item.title || item.description, + deleteAction: member && isMineItem(item) && item.id ? `${tribeUrlOf(tribe)}/content/delete/${encodeURIComponent(item.id)}` : undefined + }); + +const tribeEngagement = (tribe, item, member, returnTo) => { + if (member) { + return renderEngagement(item.id, renderOpinionsVoting(null, item.id, item.opinions, returnTo, item.opinions_inhabitants, + (id, category) => `${tribeUrlOf(tribe)}/content/${encodeURIComponent(id)}/opinion/${category}`)); + } + const summary = renderVotesSummary(item.opinions); + return summary ? renderEngagement(item.id, summary) : null; +}; + +const toFeedShape = (m) => ({ + key: m.id, + value: { author: m.author, timestamp: tsOf(m), content: { text: m.description, author: m.author, refeeds: m.refeeds, refeeds_inhabitants: m.refeeds_inhabitants, opinions: m.opinions, opinions_inhabitants: m.opinions_inhabitants } } +}); + +const renderFeedComposeForm = (tribe) => + form({ method: 'POST', action: `${tribeUrlOf(tribe)}/message` }, + textarea({ name: 'message', required: true, minlength: '1', maxlength: String(MAX_MESSAGE_LENGTH), rows: '4', cols: 50, placeholder: i18n.feedPlaceholder }), + tribe.reachAllowed ? renderTribeReachSelector() : null, + div({ class: 'feed-compose-actions' }, + button({ type: 'submit', class: 'create-button' }, i18n.createFeedButton || 'Send Feed!') + ) + ); + +const FEED_MODES = ['ALL', 'RECENT', 'MINE', 'TOP']; +const renderFeedTribeView = (feedItems, tribe, query = {}) => { + const feed = asList(feedItems); + const member = isTribeMember(tribe); + const filter = String(query.filter || query.feedFilter || 'ALL').toUpperCase(); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(isCreate ? i18n.createFeedTitle : i18n.tribeSectionFeed, i18n.FeedshareYourOpinions); + const emptyMod = !isCreate && moduleIsEmpty(feed, filter, 'ALL', q); + const visible = tribeChipVisible(feed, filter, { RECENT: isRecentItem, MINE: isMineItem, ALL: () => true, TOP: () => true }); + const chips = renderTribeModeChips(tribe, 'feed', { modes: FEED_MODES, filter: isCreate ? 'CREATE' : filter, labelOf: m => i18n[`${m}Button`], visible, emptyMod, create: member ? { label: i18n.createFeedTitle || 'Create Feed' } : null }); + if (isCreate) return div({ class: 'tribe-content-list' }, header, chips, renderFeedComposeForm(tribe)); + const items = applyTribeFilter(feed, filter.toLowerCase(), q, BASE_MODES, it => Number(it.refeeds || 0)); + const returnTo = sectionHref(tribe, 'feed', { filter, q }); + const banner = query.sent ? div({ class: 'feed-success-msg' }, p('✓ ' + (i18n.feedPublishedSuccess || i18n.tribeFeedSent))) : null; + const cards = paged(items).map(m => { + const chip = tribeReachChipFor(tribe, m); + const controls = renderTribeReachControls(tribe, m, returnTo); + return renderFeedCard(toFeedShape(m), null, { + refeedAction: `${tribeUrl}/refeed/${encodeURIComponent(m.id)}`, + canRefeed: member, + headerActions: tribeItemActions(tribe, m, member, m.description), + chips: chip || controls ? [chip ? div({ class: 'card-chips-row' }, chip) : null, controls] : null, + engagement: tribeEngagement(tribe, m, member, returnTo) + }); + }).filter(Boolean); + return div({ class: 'tribe-content-list' }, + header, + banner, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, 'feed', filter, q, renderModuleStats(items.length), i18n.feedSearchPlaceholder), + cards.length ? div({ class: 'feed-container' }, ...cards) : renderTribeEmpty(i18n.noFeedsFound || i18n.tribeFeedEmpty) + ); +}; + +const EVENT_MODES = { + ...BASE_MODES, + today: e => moment(e.date).isSame(moment(), 'day'), + week: e => moment(e.date).isBetween(moment(), moment().add(7, 'days'), null, '[]'), + month: e => moment(e.date).isBetween(moment(), moment().add(1, 'month'), null, '[]'), + year: e => moment(e.date).isBetween(moment(), moment().add(1, 'year'), null, '[]'), + archived: e => tribeStatusOf(e) === 'CLOSED' +}; +const EVENT_MODE_LIST = ['all', 'recent', 'mine', 'today', 'week', 'month', 'year', 'archived']; + +const renderTribeEventCard = (tribe, e, member, returnTo) => { + const tribeUrl = tribeUrlOf(tribe); + const isOpen = tribeStatusOf(e) === 'OPEN'; + const attending = asList(e.attendees).includes(userId); + return renderEventItem({ ...e, organizer: e.author, isPublic: reachLevelOf(e) === 'tribe' ? 'private' : 'public' }, 'all', null, { + href: null, + headerActions: tribeItemActions(tribe, e, member, e.title), + extraChips: [tribeReachChipFor(tribe, e)], + bodyExtra: [ + tribeDescription(e.description), + renderTribeReachControls(tribe, e, returnTo), + member && isOpen + ? div({ class: 'tribe-side-actions' }, + form({ method: 'POST', action: `${tribeUrl}/events/attend/${encodeURIComponent(e.id)}` }, + button({ type: 'submit', class: 'tribe-action-btn' }, attending ? i18n.tribeEventUnattend : i18n.tribeEventAttend) + ) + ) + : null, + tribeEngagement(tribe, e, member, returnTo) + ] + }); +}; + +const renderEventsSection = (tribe, items, query = {}) => { + const events = asList(items); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(i18n.tribeSectionEvents, i18n.eventsDescription); + const emptyMod = !isCreate && moduleIsEmpty(events, filter, 'all', q); + const chips = renderTribeModeChips(tribe, 'events', { modes: EVENT_MODE_LIST, filter: isCreate ? 'create' : filter, labelOf: m => i18n[`eventFilter${capFirst(m)}`], visible: tribeChipVisible(events, filter, EVENT_MODES), emptyMod, create: member ? { label: i18n.eventCreateButton } : null }); + if (isCreate) { + const today = new Date().toISOString().split('T')[0]; + return div({ class: 'tribe-content-list' }, header, chips, + div({ class: 'event-form' }, + form({ method: 'POST', action: `${tribeUrl}/events/create` }, + label(i18n.eventTitleLabel), br(), + input({ type: 'text', name: 'title', id: 'title', maxlength: '100', required: true }), br(), + label(i18n.eventDescriptionLabel), br(), + textarea({ name: 'description', id: 'description', rows: '4', maxlength: String(TEXT_CAP), required: true, placeholder: i18n.eventDescriptionPlaceholder }), br(), + label(i18n.eventDateLabel), br(), + input({ type: 'date', name: 'date', id: 'date', required: true, min: today }), br(), br(), + label(i18n.eventLocationLabel), br(), + input({ type: 'text', name: 'location', id: 'location' }), br(), br(), + tribe.reachAllowed ? renderTribeReachSelector() : null, + button({ type: 'submit' }, i18n.eventCreateButton) + ) + ) + ); + } + const list = applyTribeFilter(events, filter, q, EVENT_MODES); + const returnTo = sectionHref(tribe, 'events', { filter, q }); + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, 'events', filter, q, openClosedStats(list, e => tribeStatusOf(e) === 'CLOSED', i18n.eventStatusOpen, i18n.eventStatusClosed), i18n.eventSearchPlaceholder), + list.length ? div({ class: 'jobs-grid' }, ...paged(list).map(e => renderTribeEventCard(tribe, e, member, returnTo))) : renderTribeEmpty(i18n.noevents || i18n.tribeEventsEmpty) + ); +}; + +const TASK_PRIORITIES = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']; +const TASK_MODES = { + ...BASE_MODES, + assigned: t => asList(t.assignees).includes(userId), + open: t => tribeStatusOf(t) === 'OPEN', + 'in-progress': t => tribeStatusOf(t) === 'IN-PROGRESS', + closed: t => tribeStatusOf(t) === 'CLOSED', + ...Object.fromEntries(TASK_PRIORITIES.map(pr => [`priority-${pr.toLowerCase()}`, t => String(t.priority || '').toUpperCase() === pr])) +}; +const TASK_MODE_LIST = ['all', 'recent', 'mine', 'assigned', 'open', 'in-progress', 'closed', ...TASK_PRIORITIES.map(pr => `priority-${pr.toLowerCase()}`)]; +const taskModeLabel = (m) => { + if (m === 'in-progress') return i18n.taskFilterInProgress; + if (m === 'priority-critical') return i18n.tribePriorityCritical; + if (m.startsWith('priority-')) return i18n[`taskFilter${capFirst(m.slice(9))}`]; + return i18n[`taskFilter${capFirst(m)}`]; +}; + +const renderTribeTaskCard = (tribe, t, member, returnTo) => { + const tribeUrl = tribeUrlOf(tribe); + const assigned = asList(t.assignees).includes(userId); + const assignRow = member ? renderTaskAssignAction(t, assigned, returnTo, `${tribeUrl}/tasks/assign/${encodeURIComponent(t.id)}`) : null; + return renderTaskItem({ ...t, isPublic: reachLevelOf(t) === 'tribe' ? 'PRIVATE' : 'PUBLIC' }, 'all', null, { + href: null, + headerActions: tribeItemActions(tribe, t, member, t.title), + extraChips: [tribeReachChipFor(tribe, t)], + bodyExtra: [ + tribeDescription(t.description), + t.deadline ? p({ class: 'job-meta-line' }, `${i18n.tribeTaskDeadline}: ${t.deadline}`) : null, + renderTribeReachControls(tribe, t, returnTo), + assignRow ? div({ class: 'tribe-side-actions housing-status-row task-assign-row' }, ...assignRow) : null, + member && isMineItem(t) ? renderTaskStatusRow(t, returnTo, `${tribeUrl}/tasks/status/${encodeURIComponent(t.id)}`) : null, + tribeEngagement(tribe, t, member, returnTo) + ] + }); +}; + +const renderTasksSection = (tribe, items, query = {}) => { + const tasks = asList(items); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(i18n.tribeSectionTasks, i18n.tasksDescription); + const emptyMod = !isCreate && moduleIsEmpty(tasks, filter, 'all', q); + const chips = renderTribeModeChips(tribe, 'tasks', { modes: TASK_MODE_LIST, filter: isCreate ? 'create' : filter, labelOf: taskModeLabel, visible: tribeChipVisible(tasks, filter, TASK_MODES), emptyMod, create: member ? { label: i18n.taskCreateButton } : null }); + if (isCreate) { + const today = new Date().toISOString().split('T')[0]; + return div({ class: 'tribe-content-list' }, header, chips, + div({ class: 'task-form' }, + form({ method: 'POST', action: `${tribeUrl}/tasks/create` }, + label(i18n.taskTitleLabel), br(), + input({ type: 'text', name: 'title', id: 'title', maxlength: '100', required: true }), br(), + label(i18n.taskDescriptionLabel), br(), + textarea({ name: 'description', id: 'description', rows: '4', maxlength: String(TEXT_CAP), required: true, placeholder: i18n.taskDescriptionPlaceholder }), br(), + label(i18n.taskPriorityLabel), br(), + select({ name: 'priority', id: 'priority', required: true }, + ...TASK_PRIORITIES.map(pr => option({ value: pr }, i18n[`tribePriority${capFirst(pr.toLowerCase())}`])) + ), br(), br(), + label(i18n.tribeTaskDeadline), br(), + input({ type: 'date', name: 'deadline', id: 'deadline', min: today }), br(), br(), + tribe.reachAllowed ? renderTribeReachSelector() : null, + button({ type: 'submit' }, i18n.taskCreateButton) + ) + ) + ); + } + const list = applyTribeFilter(tasks, filter, q, TASK_MODES); + const returnTo = sectionHref(tribe, 'tasks', { filter, q }); + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, 'tasks', filter, q, renderModuleStatsBy(list, tribeStatusOf, [{ value: 'OPEN', label: i18n.taskStatusOpen }, { value: 'IN-PROGRESS', label: i18n.taskStatusInProgress }, { value: 'CLOSED', label: i18n.taskStatusClosed }]), i18n.taskSearchPlaceholder), + list.length ? div({ class: 'jobs-grid' }, ...paged(list).map(t => renderTribeTaskCard(tribe, t, member, returnTo))) : renderTribeEmpty(i18n.notasks || i18n.tribeTasksEmpty) + ); +}; + +const toVoteShape = (v) => { + const options = asList(v.options).map(o => String(o)); + const votes = v.votes || {}; + const counts = {}; + options.forEach((o, i) => { counts[o] = (counts[o] || 0) + asList(votes[String(i)]).length; }); + const totalVotes = Object.values(counts).reduce((s, n) => s + n, 0); + return { ...v, question: v.title, createdBy: v.author, options, votes: counts, totalVotes }; +}; +const VOTE_MODES = openClosedModes(v => tribeStatusOf(v) === 'CLOSED'); +const VOTE_MODE_LIST = ['all', 'recent', 'mine', 'open', 'closed']; + +const renderTribeVotationCard = (tribe, v, member, returnTo) => { + const tribeUrl = tribeUrlOf(tribe); + const shaped = toVoteShape(v); + const isOpen = tribeStatusOf(v) === 'OPEN'; + const hasVoted = Object.values(v.votes || {}).some(arr => asList(arr).includes(userId)); + return renderVoteListItem(shaped, shaped.options, 'all', null, { + href: null, + headerActions: tribeItemActions(tribe, v, member, v.title), + extraChips: [tribeReachChipFor(tribe, v)], + bodyExtra: [ + tribeDescription(v.description), + renderTribeReachControls(tribe, v, returnTo), + ...renderVoteOptionsBlock(shaped, shaped.options, `${tribeUrl}/votations/${encodeURIComponent(v.id)}/vote`, returnTo, member && isOpen && !hasVoted), + member && isMineItem(v) && isOpen + ? div({ class: 'tribe-side-actions' }, + form({ method: 'POST', action: `${tribeUrl}/votations/close/${encodeURIComponent(v.id)}` }, + button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeVotationClose) + ) + ) + : null, + tribeEngagement(tribe, v, member, returnTo) + ] + }); +}; + +const renderVotationsSection = (tribe, items, query = {}) => { + const votations = asList(items); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(i18n.tribeSectionVotations, i18n.votationsDescription); + const emptyMod = !isCreate && moduleIsEmpty(votations, filter, 'all', q); + const chips = renderTribeModeChips(tribe, 'votations', { modes: VOTE_MODE_LIST, filter: isCreate ? 'create' : filter, labelOf: m => i18n[`voteFilter${capFirst(m)}`], visible: tribeChipVisible(votations, filter, VOTE_MODES), emptyMod, create: member ? { label: i18n.voteCreateButton } : null }); + if (isCreate) { + const today = new Date().toISOString().split('T')[0]; + return div({ class: 'tribe-content-list' }, header, chips, + div({ class: 'vote-form' }, + form({ method: 'POST', action: `${tribeUrl}/votations/create` }, + h2(i18n.voteQuestionLabel), + input({ type: 'text', name: 'title', id: 'title', maxlength: '100', required: true }), br(), br(), + label(i18n.tribeVotationDescription), br(), + textarea({ name: 'description', id: 'description', rows: '3', maxlength: String(TEXT_CAP) }), br(), br(), + label(i18n.voteDeadlineLabel), br(), + input({ type: 'date', name: 'deadline', id: 'deadline', min: today }), br(), br(), + label(i18n.tribeVotationOptions), br(), + input({ type: 'text', name: 'option1', placeholder: `${i18n.tribeVotationOptionPlaceholder} 1`, required: true }), br(), + input({ type: 'text', name: 'option2', placeholder: `${i18n.tribeVotationOptionPlaceholder} 2`, required: true }), br(), + input({ type: 'text', name: 'option3', placeholder: `${i18n.tribeVotationOptionPlaceholder} 3` }), br(), + input({ type: 'text', name: 'option4', placeholder: `${i18n.tribeVotationOptionPlaceholder} 4` }), br(), br(), + tribe.reachAllowed ? renderTribeReachSelector() : null, + button({ type: 'submit' }, i18n.voteCreateButton) + ) + ) + ); + } + const list = applyTribeFilter(votations, filter, q, VOTE_MODES); + const returnTo = sectionHref(tribe, 'votations', { filter, q }); + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, 'votations', filter, q, openClosedStats(list, v => tribeStatusOf(v) === 'CLOSED', i18n.voteStatusOpen, i18n.voteStatusClosed), i18n.votesSearchPlaceholder), + list.length ? div({ class: 'jobs-grid' }, ...paged(list).map(v => renderTribeVotationCard(tribe, v, member, returnTo))) : renderTribeEmpty(i18n.novotes || i18n.tribeVotationsEmpty) + ); +}; + +const POLL_MODES = { + RECENT: isRecentItem, MINE: isMineItem, ALL: () => true, TOP: () => true, + VOTED: pl => !!pl.hasVoted || asList(pl.myChoices).length > 0, + OPEN: pl => tribeStatusOf(pl) === 'OPEN', CLOSED: pl => tribeStatusOf(pl) === 'CLOSED' +}; +const POLL_MODE_LIST = ['ALL', 'RECENT', 'MINE', 'TOP', 'VOTED', 'OPEN', 'CLOSED']; +const pollModeLabel = (m) => i18n[`pollFilter${capFirst(m.toLowerCase())}`]; + +const renderTribePollsSection = (tribe, items, query = {}) => { + const polls = asList(items).filter(pl => !pl.undecryptable); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'ALL').toUpperCase(); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(i18n.pollsTitle, i18n.pollsDescription); + const emptyMod = !isCreate && moduleIsEmpty(polls, filter, 'ALL', q); + const chips = renderTribeModeChips(tribe, 'polls', { modes: POLL_MODE_LIST, filter: isCreate ? 'CREATE' : filter, labelOf: pollModeLabel, visible: tribeChipVisible(polls, filter, POLL_MODES), emptyMod, create: member ? { label: i18n.pollCreateButton } : null }); + if (isCreate) { + return div({ class: 'tribe-content-list' }, header, chips, + div({ class: 'div-center audio-form' }, + h2(i18n.pollCreateTitle), + form({ method: 'POST', action: `${tribeUrl}/polls/create` }, + label(i18n.pollQuestion), br(), + input({ type: 'text', name: 'question', required: true, maxlength: '300', placeholder: i18n.pollQuestionPlaceholder }), br(), br(), + label(i18n.pollOptions), br(), + textarea({ name: 'options', rows: '5', required: true, maxlength: '3000', placeholder: i18n.pollOptionsPlaceholder }, ''), br(), + div({ class: 'poll-switch' }, + input({ type: 'hidden', name: 'anonymous', value: '0' }), + label(input({ type: 'checkbox', name: 'anonymous', value: '1' }), ' ', i18n.pollAnonymousLabel) + ), + div({ class: 'poll-switch' }, + input({ type: 'hidden', name: 'multiple', value: '0' }), + label(input({ type: 'checkbox', name: 'multiple', value: '1' }), ' ', i18n.pollMultipleLabel) + ), + br(), + button({ type: 'submit', class: 'create-button' }, i18n.pollPublishButton) + ) + ) + ); + } + const pred = POLL_MODES[filter] || (() => true); + const kept = polls.filter(pl => pred(pl) && matchesTribeQuery(pl, q)); + const list = filter === 'TOP' ? [...kept].sort((a, b) => (b.totalVoters || 0) - (a.totalVoters || 0) || tsOf(b) - tsOf(a)) : sortRecentFirst(kept); + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, 'polls', filter, q, openClosedStats(list, pl => tribeStatusOf(pl) === 'CLOSED', i18n.pollStatusOpen, i18n.pollStatusClosed), i18n.pollSearchPlaceholder), + list.length ? div({ class: 'jobs-grid' }, ...paged(list).map(pl => renderPollCard(pl, filter, null))) : renderTribeEmpty(i18n.pollsNoItems) + ); +}; + +const FORUM_CATEGORIES = ['GENERAL', 'PROPOSAL', 'QUESTION', 'ANNOUNCEMENT']; +const forumCatLabel = (c) => forumCatI18n()[c] || c; +const forumScoreBox = (tribe, item, member, thread) => + div({ class: 'forum-score-box' }, + member + ? form({ method: 'POST', action: `${tribeUrlOf(tribe)}/forum/${encodeURIComponent(item.id)}/refeed${thread ? `?thread=${encodeURIComponent(thread)}` : ''}`, class: 'forum-score-form' }, + button({ type: 'submit', class: 'score-btn', title: i18n.refeedButton }, '▲'), + div({ class: 'score-total' }, String(item.refeeds || 0)) + ) + : div({ class: 'score-total' }, String(item.refeeds || 0)) + ); + +const renderForumThreadDetail = (tribe, allItems, threadId, member) => { + const tribeUrl = tribeUrlOf(tribe); + const thread = allItems.find(i => i.id === threadId); + if (!thread) return renderTribeEmpty(i18n.tribeForumEmpty); + const replies = allItems.filter(i => i.contentType === 'forum-reply' && i.parentId === threadId) + .sort((a, b) => (b.refeeds || 0) - (a.refeeds || 0) || tsOf(a) - tsOf(b)); + const participants = new Set([thread.author, ...replies.map(r => r.author)].filter(Boolean)); + const returnTo = sectionHref(tribe, 'forum', { thread: threadId }); + const reachChip = tribeReachChipFor(tribe, thread); + return div({ class: 'forum-thread-container' }, + div({ class: 'forum-card forum-thread-header' }, + div({ class: 'root-vote-col' }, forumScoreBox(tribe, thread, member, threadId)), + div({ class: 'forum-main-col' }, + div({ class: 'card-header activity-card-header' }, + renderContentActions(thread.id, null, { author: thread.author, reportTitle: thread.title, deleteAction: member && isMineItem(thread) ? `${tribeUrl}/content/delete/${encodeURIComponent(thread.id)}` : undefined }) + ), + div({ class: 'forum-header-row' }, + thread.category ? span({ class: 'forum-category' }, `[${forumCatLabel(thread.category)}]`) : null, + span({ class: 'forum-title' }, thread.title), + reachChip + ), + renderTribeReachControls(tribe, thread, returnTo), + div({ class: 'forum-body' }, ...renderStyledText(thread.description || '')), + div({ class: 'forum-meta' }, + span({ class: 'forum-positive-votes' }, `▲: ${thread.refeeds || 0}`), + span({ class: 'forum-participants' }, `${String(i18n.forumParticipants).toUpperCase()}: ${participants.size}`), + span({ class: 'forum-messages' }, `${String(i18n.forumMessages).toUpperCase()}: ${replies.length}`) + ), + div({ class: 'forum-footer' }, + span({ class: 'date-link' }, `${moment(thread.createdAt).format("YYYY/MM/DD HH:mm")}`), + userLink(thread.author) + ), + tribeEngagement(tribe, thread, member, returnTo) + ) + ), + member + ? div({ class: 'new-message-wrapper' }, + form({ method: 'POST', action: `${tribeUrl}/forum/${encodeURIComponent(threadId)}/reply`, class: 'new-message-form' }, + textarea({ maxlength: String(TEXT_CAP), name: 'description', rows: '4', required: true, placeholder: i18n.forumMessagePlaceholder, class: 'new-message-textarea' }), br(), + button({ type: 'submit', class: 'forum-send-btn' }, i18n.forumSendButton) + ) + ) + : null, + ...replies.map((r, idx) => + div({ class: `forum-comment level-0${idx === 0 ? ' highlighted-reply' : ''}` }, + div({ class: 'comment-header' }, + span({ class: 'date-link' }, `${moment(r.createdAt).format("YYYY/MM/DD HH:mm")}`), + userLink(r.author), + div({ class: 'comment-votes' }, + span({ class: 'votes-count' }, `▲: ${r.refeeds || 0}`), + tribeItemActions(tribe, r, member, r.description) + ) + ), + div({ class: 'comment-body-row' }, + div({ class: 'comment-vote-col' }, forumScoreBox(tribe, r, member, threadId)), + div({ class: 'comment-text-col' }, + div(...String(r.description || '').split('\n').map(l => l.trim()).filter(Boolean).map(l => p(...renderStyledText(l, { zoomImages: true })))) + ) + ) ) ) ); }; +const renderForumSection = (tribe, items, query = {}) => { + const allItems = asList(items); + const threads = allItems.filter(i => i.contentType === 'forum'); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all'); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(i18n.tribeSectionForum, i18n.forumDescription); + const replyCount = new Map(); + allItems.filter(i => i.contentType === 'forum-reply').forEach(r => replyCount.set(r.parentId, (replyCount.get(r.parentId) || 0) + 1)); + const forumScore = (t) => (t.refeeds || 0) + (replyCount.get(t.id) || 0); + const modes = { ...BASE_MODES, ...Object.fromEntries(FORUM_CATEGORIES.map(c => [c, t => t.category === c])) }; + const emptyMod = !isCreate && !query.thread && moduleIsEmpty(threads, filter, 'all', q); + const present = new Set(threads.map(t => t.category).filter(Boolean)); + const visible = (m) => FORUM_CATEGORIES.includes(m) ? (present.has(m) || m === filter) : tribeChipVisible(threads, filter, modes)(m); + const labelOf = (m) => FORUM_CATEGORIES.includes(m) ? forumCatLabel(m) : i18n[`forumFilter${capFirst(m)}`]; + const chips = renderTribeModeChips(tribe, 'forum', { modes: ['all', 'recent', 'mine', 'top', ...FORUM_CATEGORIES], filter: isCreate ? 'create' : filter, labelOf, visible, emptyMod, create: member ? { label: i18n.forumCreateButton } : null }); + + if (isCreate) { + return div({ class: 'tribe-content-list' }, header, chips, + div({ class: 'forum-form' }, + form({ method: 'POST', action: `${tribeUrl}/forum/create` }, + label(i18n.forumCategoryLabel), br(), + select({ name: 'category', required: true }, ...FORUM_CATEGORIES.map(c => option({ value: c }, forumCatLabel(c)))), br(), br(), + label(i18n.forumTitleLabel), br(), + input({ type: 'text', name: 'title', maxlength: '100', required: true, placeholder: i18n.forumTitlePlaceholder }), br(), br(), + label(i18n.forumMessageLabel), br(), + textarea({ maxlength: String(TEXT_CAP), name: 'description', required: true, rows: '4', placeholder: i18n.forumMessagePlaceholder }), br(), br(), + tribe.reachAllowed ? renderTribeReachSelector() : null, + button({ type: 'submit' }, i18n.forumCreateButton) + ) + ) + ); + } + + if (query.thread) { + return div({ class: 'tribe-content-list' }, header, chips, + form({ method: 'GET', action: tribeUrl, class: 'forum-back-form' }, + input({ type: 'hidden', name: 'section', value: 'forum' }), + button({ type: 'submit', class: 'tribe-action-btn' }, i18n.walletBack) + ), + renderForumThreadDetail(tribe, allItems, query.thread, member) + ); + } + + const list = applyTribeFilter(threads, filter, q, modes, forumScore); + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, 'forum', filter, q, renderModuleStats(list.length), i18n.forumSearchPlaceholder), + list.length + ? ul({ class: 'mailing-archive' }, + ...paged(list).map(t => { + const participants = new Set([t.author, ...allItems.filter(i => i.contentType === 'forum-reply' && i.parentId === t.id).map(r => r.author)].filter(Boolean)); + return li({ class: 'mailing-archive-item' }, + div({ class: 'emergency-update-head mailing-archive-head' }, + div({ class: 'mailing-archive-meta' }, + a({ href: sectionHref(tribe, 'forum', { thread: t.id }), class: 'user-link' }, t.title || '—'), + t.category ? span({ class: 'forum-category' }, `[${forumCatLabel(t.category)}]`) : null, + span({ class: 'card-label activity-update-counts mailing-counts' }, `👥: ${participants.size} · 💬 ${replyCount.get(t.id) || 0} · ▲ ${t.refeeds || 0}`), + tribeReachChipFor(tribe, t) + ), + tribeItemActions(tribe, t, member, t.title) + ) + ); + }) + ) + : renderTribeEmpty(i18n.noForums || i18n.tribeForumEmpty) + ); +}; + +const MEDIA_SECTIONS = { + image: { key: 'images', list: renderImageList, wrapper: 'images-list', formClass: 'image-form', title: 'tribeSectionImages', description: 'imageDescription', create: 'imageCreateButton', search: 'imageSearchPlaceholder', empty: 'noImages', file: 'imageFileLabel', titleLabel: 'imageTitleLabel', descLabel: 'imageDescriptionLabel', filter: 'imageFilter', accept: 'image/*' }, + audio: { key: 'audios', list: renderAudioList, wrapper: 'audios-list', formClass: 'audio-form', title: 'tribeSectionAudios', description: 'audioDescription', create: 'audioCreateButton', search: 'audioSearchPlaceholder', empty: 'noAudios', file: 'audioFileLabel', titleLabel: 'audioTitleLabel', descLabel: 'audioDescriptionLabel', filter: 'audioFilter', accept: 'audio/*' }, + video: { key: 'videos', list: renderVideoList, wrapper: 'videos-list', formClass: 'video-form', title: 'tribeSectionVideos', description: 'videoDescription', create: 'videoCreateButton', search: 'videoSearchPlaceholder', empty: 'noVideos', file: 'videoFileLabel', titleLabel: 'videoTitleLabel', descLabel: 'videoDescriptionLabel', filter: 'videoFilter', accept: 'video/*' }, + document: { key: 'documents', list: renderDocumentList, wrapper: 'documents-list', formClass: 'document-form', title: 'tribeSectionDocuments', description: 'documentDescription', create: 'documentCreateButton', search: 'documentSearchPlaceholder', empty: 'noDocuments', file: 'documentFileLabel', titleLabel: 'documentTitleLabel', descLabel: 'documentDescriptionLabel', filter: 'documentFilter', accept: 'application/pdf,.pdf,.doc,.docx,.txt,.odt' }, + bookmark: { key: 'bookmarks', list: renderBookmarkList, wrapper: 'bookmark-list', formClass: 'bookmark-form', title: 'tribeSectionBookmarks', description: 'bookmarkDescription', create: 'bookmarkCreateButton', search: 'bookmarkSearchPlaceholder', empty: 'noBookmarks', filter: 'bookmarkFilter' } +}; +const toMediaShape = (m) => ({ ...m, key: m.id, url: m.mediaType === 'bookmark' ? (m.url || m.description || '') : blobIdOf(m.image), updatedAt: undefined }); + +const renderTribeMediaTypeSection = (tribe, items, query = {}, mediaType) => { + const cfg = MEDIA_SECTIONS[mediaType]; + const media = asList(items).filter(m => m.mediaType === mediaType); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const isCreate = query.action === 'create' && member; + const tribeUrl = tribeUrlOf(tribe); + const header = renderTribeSectionHeader(i18n[cfg.title], i18n[cfg.description]); + const emptyMod = !isCreate && moduleIsEmpty(media, filter, 'all', q); + const chips = renderTribeModeChips(tribe, cfg.key, { modes: ['all', 'recent', 'mine', 'top'], filter: isCreate ? 'create' : filter, labelOf: m => i18n[`${cfg.filter}${capFirst(m)}`], visible: tribeChipVisible(media, filter, BASE_MODES), emptyMod, create: member ? { label: i18n[cfg.create] } : null }); + + if (isCreate) { + const common = [ + input({ type: 'hidden', name: 'mediaType', value: mediaType }), + input({ type: 'hidden', name: 'returnSection', value: cfg.key }) + ]; + const formNode = mediaType === 'bookmark' + ? form({ method: 'POST', action: `${tribeUrl}/media/upload` }, + ...common, + label(i18n.bookmarkUrlLabel), br(), + input({ type: 'url', name: 'url', id: 'url', required: true, placeholder: i18n.bookmarkUrlPlaceholder }), br(), br(), + label(i18n.tribeMediaTitle), br(), + input({ type: 'text', name: 'title', id: 'title', maxlength: '100', required: true }), br(), br(), + label(i18n.bookmarkDescriptionLabel), br(), + textarea({ maxlength: String(TEXT_CAP), name: 'description', id: 'description', rows: '4', placeholder: i18n.bookmarkDescriptionPlaceholder }, ''), br(), + tribe.reachAllowed ? renderTribeReachSelector() : null, br(), + button({ type: 'submit' }, i18n[cfg.create]) + ) + : form({ method: 'POST', action: `${tribeUrl}/media/upload`, enctype: 'multipart/form-data' }, + ...common, + label(i18n[cfg.file]), br(), + input({ type: 'file', name: 'media', id: 'media', accept: cfg.accept, required: true }), br(), br(), + label(i18n[cfg.titleLabel]), br(), + input({ type: 'text', name: 'title', id: 'title', maxlength: '100', required: true }), br(), + label(i18n[cfg.descLabel]), br(), + textarea({ maxlength: String(TEXT_CAP), name: 'description', id: 'description', rows: '4' }, ''), br(), + ...renderLicenseSelect('', i18n), br(), + tribe.reachAllowed ? renderTribeReachSelector() : null, br(), + button({ type: 'submit' }, i18n[cfg.create]) + ); + return div({ class: 'tribe-content-list' }, header, chips, div({ class: `div-center ${cfg.formClass}` }, formNode)); + } + + const list = applyTribeFilter(media, filter, q, BASE_MODES).map(toMediaShape); + const returnTo = sectionHref(tribe, cfg.key, { filter, q }); + const params = { + q, + hideLastVisit: true, + headerActions: (m) => tribeItemActions(tribe, m, member, m.title), + titleChips: (m) => [tribeReachChipFor(tribe, m)], + bodyExtra: (m) => [tribeDescription(m.description && m.description !== m.url ? m.description : null), renderTribeReachControls(tribe, m, returnTo)], + engagement: (m) => tribeEngagement(tribe, m, member, returnTo) + }; + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, cfg.key, filter, q, renderModuleStats(list.length), i18n[cfg.search]), + list.length ? div({ class: cfg.wrapper }, cfg.list(paged(list), filter, params)) : renderTribeEmpty(i18n[cfg.empty] || i18n.tribeMediaEmpty) + ); +}; + +const renderStandaloneSection = (tribe, sectionKey, items, query, cfg) => { + const list = asList(items); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const modes = cfg.modes || BASE_MODES; + const modeList = cfg.modeList || ['all', 'recent', 'mine']; + const header = renderTribeSectionHeader(cfg.title, cfg.description); + const emptyMod = moduleIsEmpty(list, filter, 'all', q); + const chips = renderTribeModeChips(tribe, sectionKey, { + modes: modeList, filter, labelOf: cfg.labelOf, visible: tribeChipVisible(list, filter, modes), emptyMod, + create: member && cfg.createAction ? { action: cfg.createAction, hidden: { filter: 'create', tribeId: tribe.id }, label: cfg.createLabel } : null + }); + const shown = applyTribeFilter(list, filter, q, modes, cfg.score || (() => 0)); + return div({ class: 'tribe-content-list' }, + header, + chips, + emptyMod ? null : renderTribeSearchRow(tribe, sectionKey, filter, q, cfg.stats ? cfg.stats(shown) : renderModuleStats(shown.length), cfg.placeholder), + shown.length ? cfg.render(paged(shown), member) : renderTribeEmpty(cfg.empty) + ); +}; + +const withModuleReach = (tribe, item, contentType, card, returnTo) => { + if (item.tribeOrigin) return card; + const it = { ...item, id: item.rootId || item.id || item.key, contentType }; + const chip = tribeReachChipFor(tribe, it); + return div(card, chip ? div({ class: 'card-chips-row' }, chip) : null, renderTribeReachControls(tribe, it, returnTo)); +}; + +const renderTribeMapsSection = (tribe, maps, query = {}) => + renderStandaloneSection(tribe, 'maps', maps, query, { + title: i18n.tribeSectionMaps || 'MAPS', description: i18n.mapDescription, createAction: '/maps', createLabel: i18n.mapCreateButton || 'Create Map', + labelOf: m => i18n[`mapFilter${capFirst(m)}`], placeholder: i18n.mapSearchPlaceholder, empty: i18n.noMaps, + render: (list) => { + const returnTo = sectionHref(tribe, 'maps', { filter: query.filter, q: query.q }); + return div({ class: 'jobs-grid' }, ...list.map(m => withModuleReach(tribe, m, 'map', renderMapCard(m, 'all', { q: query.q || '', returnTo }), returnTo))); + } + }); + +const renderTribeTorrentsSection = (tribe, torrents, query = {}) => + renderStandaloneSection(tribe, 'torrents', torrents, query, { + title: i18n.tribeSectionTorrents || 'TORRENTS', description: i18n.torrentsDescription, createAction: '/torrents', createLabel: i18n.tribeCreateTorrent || 'Upload Torrent', + labelOf: m => i18n[`torrentFilter${capFirst(m)}`], placeholder: i18n.torrentSearchPlaceholder, empty: i18n.tribeTorrentsEmpty || i18n.noTorrents, + render: (list, member) => { + const returnTo = sectionHref(tribe, 'torrents', { filter: query.filter, q: query.q }); + return div({ class: 'audios-list' }, renderTorrentTable(list, 'all', { + q: query.q || '', + rowFor: (t) => t._isMedia + ? { + titleChips: [tribeReachChipFor(tribe, t)], + details: renderTribeReachControls(tribe, { ...t, id: t.rootId || t.key, contentType: 'media' }, returnTo), + download: t.url ? renderTorrentDownload(torrentDownloadHref(t.url, t.title)) : null, + actions: tribeItemActions(tribe, { ...t, id: t.rootId || t.key }, member, t.title) + } + : null + })); + } + }); + +const renderTribeFilesSection = (tribe, files, query = {}) => + renderStandaloneSection(tribe, 'files', files, query, { + title: i18n.tribeSectionFiles || 'FILES', description: i18n.filesDescription, createAction: '/files', createLabel: i18n.tribeCreateFile || 'Upload File', + labelOf: m => i18n[`fileFilter${capFirst(m)}`], placeholder: i18n.fileSearchPlaceholder, empty: i18n.tribeFilesEmpty || i18n.noFiles, + render: (list) => div({ class: 'audios-list' }, renderFileTable(list, 'all', { q: query.q || '' })) + }); + +const closedByFlag = (it) => !!it.isClosed || String(it.status || '').toUpperCase() === 'CLOSED'; + +const renderTribePadsSection = (tribe, pads, query = {}) => + renderStandaloneSection(tribe, 'pads', pads, query, { + title: i18n.tribeSectionPads || 'PADS', description: i18n.padsDescription, createAction: '/pads', createLabel: i18n.tribePadCreate || 'Create Pad', + modes: openClosedModes(closedByFlag), modeList: ['all', 'recent', 'mine', 'open', 'closed'], + labelOf: m => i18n[`padFilter${capFirst(m)}`], placeholder: i18n.padSearchPlaceholder, empty: i18n.tribePadsEmpty || i18n.padsNoItems, + stats: (list) => renderModuleStatsBy(list, pd => pd.isClosed ? 'CLOSED' : String(pd.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.padStatusOpen }, { value: 'INVITE-ONLY', label: i18n.padStatusInviteOnly }, { value: 'CLOSED', label: i18n.padStatusClosed }]), + render: (list) => div({ class: 'tribe-grid pads-grid' }, ...list.map(pd => renderPadCard(pd, 'all', null))) + }); + +const renderTribeChatsSection = (tribe, chats, query = {}) => + renderStandaloneSection(tribe, 'chats', chats, query, { + title: i18n.tribeSectionChats || 'CHATS', description: i18n.modulesChatsDescription, createAction: '/chats', createLabel: i18n.tribeChatCreate || 'Create Chat', + modes: openClosedModes(closedByFlag), modeList: ['all', 'recent', 'mine', 'open', 'closed'], + labelOf: m => i18n[`chatFilter${capFirst(m)}`], placeholder: i18n.chatSearchPlaceholder, empty: i18n.tribeChatsEmpty || i18n.chatNoItems, + stats: (list) => renderModuleStatsBy(list, c => String(c.status || 'OPEN').toUpperCase(), [{ value: 'OPEN', label: i18n.chatStatusOpen }, { value: 'INVITE-ONLY', label: i18n.chatStatusInviteOnly }, { value: 'CLOSED', label: i18n.chatStatusClosed }]), + render: (list) => div({ class: 'tribe-grid' }, ...list.map(c => renderChatCard(c, 'all', { q: query.q || '' }))) + }); + +const renderTribeCalendarsSection = (tribe, calendars, query = {}) => + renderStandaloneSection(tribe, 'calendars', calendars, query, { + title: i18n.tribeSectionCalendars || 'CALENDARS', description: i18n.calendarsDescription, createAction: '/calendars', createLabel: i18n.tribeCalendarCreate || 'Create Calendar', + modes: openClosedModes(closedByFlag), modeList: ['all', 'recent', 'mine', 'open', 'closed'], + labelOf: m => i18n[`calendarFilter${capFirst(m)}`], placeholder: i18n.calendarSearchPlaceholder, empty: i18n.tribeCalendarsEmpty || i18n.calendarsNoItems, + stats: (list) => openClosedStats(list, closedByFlag, i18n.calendarStatusOpen, i18n.calendarStatusClosed), + render: (list) => { + const returnTo = sectionHref(tribe, 'calendars', { filter: query.filter, q: query.q }); + return div({ class: 'tribe-grid' }, ...list.map(c => withModuleReach(tribe, c, 'calendar', renderCalendarCard(c, null), returnTo))); + } + }); + +const renderTribeWikiSectionPage = (tribe, pages, query = {}) => { + const list = asList(pages); + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const emptyMod = moduleIsEmpty(list, filter, 'all', q); + const shown = applyTribeFilter(list, filter, q, BASE_MODES, () => 0); + const toolbar = div({ class: 'tribe-content-list' }, + renderTribeSectionHeader(i18n.tribeSectionWiki || 'WIKI', i18n.wikiDescription), + div({ class: 'mode-buttons-row' }, + ...(emptyMod ? [] : [ + ...['all', 'recent', 'mine'].filter(tribeChipVisible(list, filter, BASE_MODES)).map(m => + form({ method: 'GET', action: tribeUrlOf(tribe) }, + input({ type: 'hidden', name: 'section', value: 'wiki' }), + input({ type: 'hidden', name: 'filter', value: m }), + button({ type: 'submit', class: filter === m ? 'filter-btn active' : 'filter-btn' }, String(i18n[`wikiFilter${capFirst(m)}`] || m).toUpperCase()) + )), + form({ method: 'GET', action: '/wiki' }, + input({ type: 'hidden', name: 'filter', value: 'changes' }), + input({ type: 'hidden', name: 'tribeId', value: tribe.id }), + button({ type: 'submit', class: 'filter-btn' }, String(i18n.wikiChanges).toUpperCase()) + ) + ]), + member + ? form({ method: 'GET', action: '/wiki' }, + input({ type: 'hidden', name: 'filter', value: 'create' }), + input({ type: 'hidden', name: 'tribeId', value: tribe.id }), + button({ type: 'submit', class: 'create-button' }, i18n.tribeWikiCreate) + ) + : null + ), + emptyMod ? null : renderTribeSearchRow(tribe, 'wiki', filter, q, renderModuleStats(shown.length), i18n.wikiSearchPlaceholder) + ); + const returnTo = sectionHref(tribe, 'wiki', { filter: query.filter, q: query.q }); + return renderTribeWikiSection(tribe, shown, toolbar, (pg, card) => withModuleReach(tribe, pg, 'wiki', card, returnTo)); +}; + +const ROOM_MODES = { ...openClosedModes(r => !!r.isClosed), live: () => false }; +const renderTribeRoomsSectionPage = (tribe, sectionData, query = {}) => { + const rooms = asList(sectionData && sectionData.rooms); + const occupancy = sectionData && sectionData.occupancy; + const live = sectionData && sectionData.live; + const member = isTribeMember(tribe); + const filter = String(query.filter || 'all').toLowerCase(); + const q = String(query.q || '').trim(); + const modes = { ...ROOM_MODES, live: r => !!(occupancy && occupancy.get && occupancy.get(r.rootId) && occupancy.get(r.rootId).count > 0) }; + const emptyMod = moduleIsEmpty(rooms, filter, 'all', q); + const shown = applyTribeFilter(rooms, filter, q, modes, () => 0); + const toolbar = div({ class: 'tribe-content-list' }, + renderTribeSectionHeader(i18n.tribeSectionRooms || 'ROOMS', i18n.roomsDescription), + renderTribeModeChips(tribe, 'rooms', { + modes: ['all', 'recent', 'mine', 'live', 'open', 'closed'], filter, labelOf: m => i18n[`roomFilter${capFirst(m)}`], visible: tribeChipVisible(rooms, filter, modes), emptyMod, + create: member ? { action: '/rooms', hidden: { filter: 'create', tribeId: tribe.id }, label: i18n.tribeRoomCreate } : null + }), + emptyMod ? null : renderTribeSearchRow(tribe, 'rooms', filter, q, roomStatsBy(shown), i18n.roomSearchPlaceholder) + ); + return renderTribeRoomsSection(tribe, shown, occupancy, live, toolbar); +}; + +const renderInhabitantsSection = (tribe, members, query = {}) => { + const resolved = asList(members); + const search = String(query.search || '').trim().toLowerCase(); + const shown = search + ? resolved.filter(u => [u.name, u.id, u.description].some(v => String(v || '').toLowerCase().includes(search))) + : resolved; + return div({ class: 'tribe-content-list' }, + renderTribeSectionHeader(i18n.tribeSectionInhabitants, i18n.discoverPeople), + resolved.length ? renderTribeSearchRow(tribe, 'inhabitants', null, search, renderModuleStats(shown.length), i18n.searchInhabitantsPlaceholder, 'search') : null, + shown.length + ? div({ class: 'inhabitants-list inhabitants-grid' }, ...paged(shown).map(u => renderInhabitantCard(u, 'all', userId, false))) + : renderTribeEmpty(i18n.tribeInhabitantsEmpty || i18n.noInhabitantsFound) + ); +}; + const createButtonI18n = { - events: () => i18n.tribeEventCreate || 'Create Event', - tasks: () => i18n.tribeTaskCreate || 'Create Task', - votations: () => i18n.tribeVotationCreate || 'Create Votation', - forum: () => i18n.tribeForumCreate || 'Create Forum', subtribes: () => i18n.tribeSubTribesCreate || 'Create Sub-Tribe', }; @@ -770,10 +1564,11 @@ const renderCreateForm = (tribe, contentType, fields) => { form(formAttrs, ...fields.map(f => { const prefix = f.spaceBefore ? [br()] : []; - if (f.type === 'textarea') return [...prefix, label({ for: f.name }, f.label), br, textarea({ name: f.name, id: f.name, rows: f.rows || 4, required: f.required, placeholder: f.placeholder }, ''), br()]; + if (f.type === 'textarea') return [...prefix, label({ for: f.name }, f.label), br, textarea({ maxlength: String(TEXT_CAP), name: f.name, id: f.name, rows: f.rows || 4, required: f.required, placeholder: f.placeholder }, ''), br()]; if (f.type === 'select') return [...prefix, label({ for: f.name }, f.label), br, select({ name: f.name, id: f.name }, ...f.options.map(o => option({ value: o.value }, o.label))), br()]; if (f.type === 'file') return [...prefix, label({ for: f.name }, f.label), br, input({ type: 'file', name: f.name, id: f.name, accept: f.accept || '*/*' }), br()]; const attrs = { type: f.type || 'text', name: f.name, id: f.name, required: f.required, placeholder: f.placeholder }; + if (f.maxlength) attrs.maxlength = f.maxlength; if (f.min) attrs.min = f.min; return [...prefix, br, label({ for: f.name }, f.label), br, input(attrs), br()]; }).flat(),br(), @@ -782,575 +1577,6 @@ const renderCreateForm = (tribe, contentType, fields) => { ); }; -const renderEventsSection = (tribe, items, query) => { - const events = Array.isArray(items) ? items : []; - const action = query.action; - const today = new Date().toISOString().split('T')[0]; - if (action === 'create') { - return renderCreateForm(tribe, 'events', [ - { name: 'title', label: i18n.tribeEventTitle, required: true, placeholder: i18n.tribeEventTitle }, - { name: 'description', type: 'textarea', label: i18n.tribeEventDescription, required: true, placeholder: i18n.tribeEventDescription }, - { name: 'date', type: 'date', label: i18n.tribeEventDate, required: true, min: today }, - { name: 'location', label: i18n.tribeEventLocation, placeholder: i18n.tribeEventLocation }, - ]); - } - const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, - h2(i18n.tribeSectionEvents), - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: 'events' }), - input({ type: 'hidden', name: 'action', value: 'create' }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeEventCreate) - ) - ), - events.length === 0 ? p(i18n.tribeEventsEmpty) : - events.map(e => div({ class: 'tribe-content-card' }, - h2(e.title), - e.description ? p(...renderUrl(e.description)) : null, - e.date ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeEventDate || 'Date') + ':'), - span({ class: 'card-value' }, e.date) - ) : null, - e.location ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeEventLocation || 'Location') + ':'), - span({ class: 'card-value' }, ...renderUrl(e.location)) - ) : null, - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeEventAttendees || 'Attendees') + ':'), - span({ class: 'card-value' }, String((e.attendees || []).length)) - ), - statusBadge(e.status), - div({ class: 'tribe-content-actions' }, - form({ method: 'POST', action: `${tribeUrl}/events/attend/${encodeURIComponent(e.id)}` }, - button({ type: 'submit', class: 'filter-btn' }, - (e.attendees || []).includes(userId) ? i18n.tribeEventUnattend : i18n.tribeEventAttend - ) - ), - e.author === userId ? form({ method: 'POST', action: `${tribeUrl}/content/delete/${encodeURIComponent(e.id)}` }, - button({ type: 'submit', class: 'filter-btn danger-btn' }, i18n.tribeContentDelete) - ) : null - ) - )) - ); -}; - -const renderTasksSection = (tribe, items, query) => { - const tasks = Array.isArray(items) ? items : []; - const action = query.action; - const today = new Date().toISOString().split('T')[0]; - if (action === 'create') { - return renderCreateForm(tribe, 'tasks', [ - { name: 'title', label: i18n.tribeTaskTitle, required: true, placeholder: i18n.tribeTaskTitle }, - { name: 'description', type: 'textarea', label: i18n.tribeTaskDescription, required: true, placeholder: i18n.tribeTaskDescription }, - { name: 'priority', type: 'select', label: i18n.tribeTaskPriority, options: [ - { value: 'LOW', label: i18n.tribePriorityLow }, { value: 'MEDIUM', label: i18n.tribePriorityMedium }, - { value: 'HIGH', label: i18n.tribePriorityHigh }, { value: 'CRITICAL', label: i18n.tribePriorityCritical } - ]}, - { name: 'deadline', type: 'date', label: i18n.tribeTaskDeadline, min: today }, - ]); - } - const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, - h2(i18n.tribeSectionTasks), - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: 'tasks' }), - input({ type: 'hidden', name: 'action', value: 'create' }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeTaskCreate) - ) - ), - tasks.length === 0 ? p(i18n.tribeTasksEmpty) : - tasks.map(t => div({ class: 'tribe-content-card' }, - h2(t.title), - t.description ? p(...renderUrl(t.description)) : null, - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeTaskPriority || 'Priority') + ':'), - priorityLabel(t.priority) - ), - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeStatusLabel || 'Status') + ':'), - statusBadge(t.status) - ), - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeTaskAssignees || 'Assignees') + ':'), - span({ class: 'card-value' }, String((t.assignees || []).length)) - ), - br(), - t.deadline ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeTaskDeadline || 'Deadline') + ':'), - span({ class: 'card-value' }, t.deadline) - ) : null, - div({ class: 'tribe-content-actions' }, - form({ method: 'POST', action: `${tribeUrl}/tasks/assign/${encodeURIComponent(t.id)}` }, - button({ type: 'submit', class: 'filter-btn' }, - (t.assignees || []).includes(userId) ? i18n.tribeTaskUnassign : i18n.tribeTaskAssign - ) - ), - t.status !== 'IN-PROGRESS' && t.author === userId ? form({ method: 'POST', action: `${tribeUrl}/tasks/status/${encodeURIComponent(t.id)}` }, - input({ type: 'hidden', name: 'status', value: 'IN-PROGRESS' }), - button({ type: 'submit', class: 'filter-btn' }, i18n.tribeTaskStatusInProgress) - ) : null, - t.status !== 'CLOSED' && t.author === userId ? form({ method: 'POST', action: `${tribeUrl}/tasks/status/${encodeURIComponent(t.id)}` }, - input({ type: 'hidden', name: 'status', value: 'CLOSED' }), - button({ type: 'submit', class: 'filter-btn' }, i18n.tribeTaskStatusClosed) - ) : null, - t.author === userId ? form({ method: 'POST', action: `${tribeUrl}/content/delete/${encodeURIComponent(t.id)}` }, - button({ type: 'submit', class: 'filter-btn danger-btn' }, i18n.tribeContentDelete) - ) : null - ) - )) - ); -}; - -const renderTribePollsSection = (tribe, items, query) => { - const polls = Array.isArray(items) ? items : []; - const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; - if (query.action === 'create') { - return div({ class: 'create-tribe-form' }, - form({ method: 'POST', action: `${tribeUrl}/polls/create` }, - label(i18n.pollQuestion), br, - input({ type: 'text', name: 'question', required: true, maxlength: '300', placeholder: i18n.pollQuestionPlaceholder }), br(), - label(i18n.pollOptions), br, - textarea({ name: 'options', rows: 5, required: true, placeholder: i18n.pollOptionsPlaceholder }, ''), br(), - div({ class: 'poll-switch' }, - input({ type: 'hidden', name: 'anonymous', value: '0' }), - label(input({ type: 'checkbox', name: 'anonymous', value: '1' }), ' ', i18n.pollAnonymousLabel) - ), - div({ class: 'poll-switch' }, - input({ type: 'hidden', name: 'multiple', value: '0' }), - label(input({ type: 'checkbox', name: 'multiple', value: '1' }), ' ', i18n.pollMultipleLabel) - ), - button({ type: 'submit', class: 'create-button' }, i18n.pollPublishButton) - ) - ); - } - const returnTo = `${tribeUrl}?section=polls`; - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, - h2(i18n.pollsTitle), - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: 'polls' }), - input({ type: 'hidden', name: 'action', value: 'create' }), - button({ type: 'submit', class: 'create-button' }, i18n.pollCreateButton) - ) - ), - polls.length === 0 ? p(i18n.pollsNoItems) : - polls.map(poll => div({ class: 'tribe-content-card poll-card' }, - h2(poll.undecryptable ? i18n.contentAccessDenied : poll.question), - poll.undecryptable ? null : div({ class: 'card-chips-row' }, - statusBadge(poll.status), - poll.anonymous ? span({ class: 'chat-poll-tag' }, i18n.pollAnonymous) : null, - poll.multiple ? span({ class: 'chat-poll-tag' }, i18n.pollMultiple) : null - ), - poll.undecryptable ? null : (poll.hasVoted || poll.status === 'CLOSED' ? renderPollResults(poll) : null), - poll.undecryptable ? null : renderPollBallot(poll, returnTo, '/polls'), - poll.undecryptable ? null : div({ class: 'card-field' }, - span({ class: 'card-label' }, `${i18n.pollVoters}: `), - span({ class: 'card-value' }, String(poll.totalVoters)) - ), - div({ class: 'card-field' }, - span({ class: 'card-label' }, i18n.createdBy + ': '), - userLink(poll.author) - ), - poll.author === userId && poll.status === 'OPEN' - ? form({ method: 'POST', action: `/polls/close/${encodeURIComponent(poll.id)}` }, - input({ type: 'hidden', name: 'returnTo', value: returnTo }), - button({ type: 'submit', class: 'filter-btn' }, i18n.pollCloseButton) - ) - : null, - poll.author === userId - ? form({ method: 'POST', action: `/polls/delete/${encodeURIComponent(poll.id)}` }, - input({ type: 'hidden', name: 'returnTo', value: returnTo }), - button({ type: 'submit', class: 'filter-btn danger-btn' }, i18n.pollDeleteButton) - ) - : null - )) - ); -}; - -const renderVotationsSection = (tribe, items, query) => { - const votations = Array.isArray(items) ? items : []; - const action = query.action; - const today = new Date().toISOString().split('T')[0]; - if (action === 'create') { - return div({ class: 'create-tribe-form' }, - form({ method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/votations/create` }, - label({ for: 'title' }, i18n.tribeVotationTitle), br, - input({ type: 'text', name: 'title', maxlength: '100', id: 'title', required: true, placeholder: i18n.tribeVotationTitle }), br(), - label({ for: 'description' }, i18n.tribeVotationDescription), br, - textarea({ name: 'description', id: 'description', rows: 3, placeholder: i18n.tribeVotationDescription }, ''), br(), - label({ for: 'deadline' }, i18n.tribeVotationDeadline), br, - input({ type: 'date', name: 'deadline', id: 'deadline', min: today }), br(), - br(), - label(i18n.tribeVotationOptions), br, - input({ type: 'text', name: 'option1', placeholder: `${i18n.tribeVotationOptionPlaceholder} 1`, required: true }), br(), - input({ type: 'text', name: 'option2', placeholder: `${i18n.tribeVotationOptionPlaceholder} 2`, required: true }), br(), - input({ type: 'text', name: 'option3', placeholder: `${i18n.tribeVotationOptionPlaceholder} 3` }), br(), - input({ type: 'text', name: 'option4', placeholder: `${i18n.tribeVotationOptionPlaceholder} 4` }), br(), - button({ type: 'submit', class: 'create-button' }, i18n.tribeVotationCreate) - ) - ); - } - const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, - h2(i18n.tribeSectionVotations), - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: 'votations' }), - input({ type: 'hidden', name: 'action', value: 'create' }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeVotationCreate) - ) - ), - votations.length === 0 ? p(i18n.tribeVotationsEmpty) : - votations.map(v => { - const opts = Array.isArray(v.options) ? v.options : []; - const votes = v.votes || {}; - const totalVotes = Object.values(votes).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0); - const hasVoted = Object.values(votes).some(arr => Array.isArray(arr) && arr.includes(userId)); - const isOpen = v.status === 'OPEN'; - - return div({ class: 'tribe-content-card' }, - h2(v.title), - v.description ? p(...renderUrl(v.description)) : null, - statusBadge(v.status), - v.deadline ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeVotationDeadline || 'Deadline') + ':'), - span({ class: 'card-value' }, v.deadline) - ) : null, - div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.tribeVotationResults || 'Votes') + ':'), - span({ class: 'card-value' }, String(totalVotes)) - ), - br(), - div({ class: 'tribe-votation-options' }, - opts.map((opt, idx) => { - const count = Array.isArray(votes[String(idx)]) ? votes[String(idx)].length : 0; - const pct = totalVotes > 0 ? Math.round((count / totalVotes) * 100) : 0; - const roundedPct = Math.round(pct / 5) * 5; - return div({ class: 'tribe-votation-option' }, - span({ class: 'tribe-votation-label' }, opt), - div({ class: 'tribe-votation-bar' }, - div({ class: `tribe-votation-fill tribe-fill-${roundedPct}` }) - ), - span({ class: 'tribe-votation-count' }, `${count} (${pct}%)`), - isOpen && !hasVoted ? form({ method: 'POST', action: `${tribeUrl}/votations/${encodeURIComponent(v.id)}/vote` }, - input({ type: 'hidden', name: 'optionIndex', value: String(idx) }), - button({ type: 'submit', class: 'filter-btn' }, i18n.tribeVotationVote) - ) : null - ); - }) - ), - v.author === userId ? div({ class: 'tribe-content-actions' }, - isOpen ? form({ method: 'POST', action: `${tribeUrl}/votations/close/${encodeURIComponent(v.id)}` }, - button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeVotationClose) - ) : null, - form({ method: 'POST', action: `${tribeUrl}/content/delete/${encodeURIComponent(v.id)}` }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeContentDelete) - ) - ) : null - ); - }) - ); -}; - -const renderForumSection = (tribe, items, query) => { - const allItems = Array.isArray(items) ? items : []; - const threads = allItems.filter(i => i.contentType === 'forum'); - const action = query.action; - const threadId = query.thread; - const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; - - if (action === 'create') { - return renderCreateForm(tribe, 'forum', [ - { name: 'title', label: i18n.tribeForumTitle, required: true, placeholder: i18n.tribeForumTitle }, - { name: 'description', type: 'textarea', label: i18n.tribeForumText, required: true, placeholder: i18n.tribeForumText, rows: 6 }, - { name: 'category', type: 'select', label: i18n.tribeForumCategory, options: [ - { value: 'GENERAL', label: i18n.tribeForumCatGeneral }, { value: 'PROPOSAL', label: i18n.tribeForumCatProposal }, - { value: 'QUESTION', label: i18n.tribeForumCatQuestion }, { value: 'ANNOUNCEMENT', label: i18n.tribeForumCatAnnouncement } - ]} - ]); - } - - if (threadId) { - const thread = allItems.find(i => i.id === threadId); - const replies = allItems.filter(i => i.contentType === 'forum-reply' && i.parentId === threadId) - .sort((a, b) => (a.refeeds || 0) - (b.refeeds || 0) !== 0 ? (b.refeeds || 0) - (a.refeeds || 0) : (Date.parse(a.createdAt) || 0) - (Date.parse(b.createdAt) || 0)); - if (!thread) return p(i18n.tribeForumEmpty); - const replyCount = replies.length; - return div({ class: 'tribe-content-list' }, - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: 'forum' }), - button({ type: 'submit', class: 'filter-btn' }, i18n.walletBack) - ), - div({ class: 'forum-card forum-thread-header' }, - div({ class: 'forum-score-col' }, - div({ class: 'forum-score-box' }, - form({ method: 'POST', action: `${tribeUrl}/forum/${encodeURIComponent(thread.id)}/refeed` }, - button({ type: 'submit', class: 'score-btn' }, '▲') - ), - div({ class: 'score-total' }, String(thread.refeeds || 0)), - ) - ), - div({ class: 'forum-main-col' }, - div({ class: 'forum-header-row' }, - thread.category ? span({ class: 'forum-category' }, `[${forumCatI18n()[thread.category] || thread.category}]`) : null, - span({ class: 'forum-title' }, thread.title) - ), - div({ class: 'forum-footer' }, - span({ class: 'date-link' }, `${new Date(thread.createdAt).toLocaleString()} ${i18n.performed || ''}`), - userLink(thread.author) - ), - div({ class: 'forum-body' }, ...renderUrl(thread.description || '')), - div({ class: 'forum-meta' }, - span({ class: 'forum-positive-votes' }, `▲: ${thread.refeeds || 0}`), - span({ class: 'forum-messages' }, `${(i18n.forumMessages || i18n.tribeForumReplies || 'MESSAGES').toUpperCase()}: ${replyCount}`) - ) - ) - ), - div({ class: 'tribe-forum-reply-form' }, - form({ method: 'POST', action: `${tribeUrl}/forum/${encodeURIComponent(threadId)}/reply` }, - textarea({ name: 'description', rows: 3, required: true, placeholder: i18n.tribeForumReply }), - br(), - button({ type: 'submit', class: 'forum-send-btn' }, i18n.tribeForumReply) - ) - ), - replies.length > 0 - ? replies.map((r, idx) => - div({ class: `forum-comment${idx === 0 ? ' highlighted-reply' : ''}` }, - div({ class: 'comment-header' }, - span({ class: 'date-link' }, `${new Date(r.createdAt).toLocaleString()} ${i18n.performed || ''}`), - userLink(r.author), - div({ class: 'comment-votes' }, - span({ class: 'forum-positive-votes' }, `▲: ${r.refeeds || 0}`) - ) - ), - div({ class: 'comment-body-row' }, - div({ class: 'comment-vote-col' }, - div({ class: 'forum-score-box' }, - form({ method: 'POST', action: `${tribeUrl}/forum/${encodeURIComponent(r.id)}/refeed` }, - button({ type: 'submit', class: 'score-btn' }, '▲') - ), - div({ class: 'score-total' }, String(r.refeeds || 0)) - ) - ), - div({ class: 'comment-text-col' }, - ...(r.description || '').split('\n').map(l => l.trim()).filter(l => l).map(l => p(...renderUrl(l))) - ) - ), - r.author === userId ? div({ class: 'tribe-content-actions' }, - form({ method: 'POST', action: `${tribeUrl}/content/delete/${encodeURIComponent(r.id)}` }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeContentDelete) - ) - ) : null - ) - ) - : null - ); - } - - const replyMap = new Map(); - allItems.filter(i => i.contentType === 'forum-reply').forEach(r => { replyMap.set(r.parentId, (replyMap.get(r.parentId) || 0) + 1); }); - const sortedThreads = [...threads].sort((a, b) => ((b.refeeds || 0) + (replyMap.get(b.id) || 0)) - ((a.refeeds || 0) + (replyMap.get(a.id) || 0))); - - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, - h2(i18n.tribeSectionForum), - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: 'forum' }), - input({ type: 'hidden', name: 'action', value: 'create' }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeForumCreate) - ) - ), - sortedThreads.length === 0 ? p(i18n.tribeForumEmpty) : - div({ class: 'forum-list' }, - sortedThreads.map(t => { - const replyCount = allItems.filter(i => i.contentType === 'forum-reply' && i.parentId === t.id).length; - return div({ class: 'forum-card' }, - div({ class: 'forum-score-col' }, - div({ class: 'forum-score-box' }, - form({ method: 'POST', action: `${tribeUrl}/forum/${encodeURIComponent(t.id)}/refeed` }, - button({ type: 'submit', class: 'score-btn' }, '▲') - ), - div({ class: 'score-total' }, String(t.refeeds || 0)) - ) - ), - div({ class: 'forum-main-col' }, - div({ class: 'forum-header-row' }, - t.category ? span({ class: 'forum-category' }, `[${forumCatI18n()[t.category] || t.category}]`) : null, - form({ method: 'GET', action: tribeUrl, class: 'forum-title-form' }, - input({ type: 'hidden', name: 'section', value: 'forum' }), - input({ type: 'hidden', name: 'thread', value: t.id }), - button({ type: 'submit', class: 'forum-title' }, t.title) - ) - ), - t.description ? div({ class: 'forum-body' }, ...renderUrl((t.description || '').substring(0, 200))) : null, - div({ class: 'forum-meta' }, - span({ class: 'forum-positive-votes' }, `▲: ${t.refeeds || 0}`), - span({ class: 'forum-messages' }, `${(i18n.forumMessages || i18n.tribeForumReplies || 'MESSAGES').toUpperCase()}: ${replyCount}`) - ), - div({ class: 'forum-footer' }, - span({ class: 'date-link' }, `${new Date(t.createdAt).toLocaleString()} ${i18n.performed || ''}`), - userLink(t.author) - ), - t.author === userId ? div({ class: 'forum-owner-actions' }, - form({ method: 'POST', action: `${tribeUrl}/content/delete/${encodeURIComponent(t.id)}`, class: 'forum-delete-form' }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeContentDelete) - ) - ) : null - ) - ); - }) - ) - ); -}; - -const sectionKeyForMediaType = { image: 'images', audio: 'audios', video: 'videos', document: 'documents', bookmark: 'bookmarks', torrent: 'torrents' }; -const acceptForMediaType = { image: 'image/*', audio: 'audio/*', video: 'video/*', document: 'application/pdf,.pdf,.doc,.docx,.txt,.odt', bookmark: null, torrent: '.torrent' }; -const sectionTitleForMediaType = (mt) => { - const map = { image: i18n.tribeSectionImages, audio: i18n.tribeSectionAudios, video: i18n.tribeSectionVideos, document: i18n.tribeSectionDocuments, bookmark: i18n.tribeSectionBookmarks, torrent: i18n.tribeSectionTorrents }; - return map[mt] || mt; -}; - -const renderTribeMediaTypeSection = (tribe, items, query, mediaType) => { - const allMedia = Array.isArray(items) ? items : []; - const media = allMedia.filter(m => m.mediaType === mediaType); - const action = query.action; - const tribeUrl = `/tribe/${encodeURIComponent(tribe.id)}`; - const sectionKey = sectionKeyForMediaType[mediaType] || 'media'; - const sTitle = sectionTitleForMediaType(mediaType); - const createMediaLabel = { - image: () => i18n.tribeCreateImage || 'Create Image', - audio: () => i18n.tribeCreateAudio || 'Create Audio', - video: () => i18n.tribeCreateVideo || 'Create Video', - document: () => i18n.tribeCreateDocument || 'Create Document', - bookmark: () => i18n.tribeCreateBookmark || 'Create Bookmark', - torrent: () => i18n.tribeCreateTorrent || 'Upload Torrent', - }; - const mediaBtnLabel = createMediaLabel[mediaType] ? createMediaLabel[mediaType]() : i18n.tribeCreateButton; - - if (action === 'create') { - if (mediaType === 'bookmark') { - return div({ class: 'create-tribe-form' }, - form({ method: 'POST', action: `${tribeUrl}/media/upload` }, - input({ type: 'hidden', name: 'mediaType', value: 'bookmark' }), - input({ type: 'hidden', name: 'returnSection', value: sectionKey }), - label({ for: 'title' }, i18n.tribeMediaTitle), br, - input({ type: 'text', name: 'title', maxlength: '100', id: 'title', required: true, placeholder: i18n.tribeMediaTitle }), br(), - label({ for: 'url' }, i18n.bookmarkUrlLabel || 'URL'), br, - input({ type: 'url', name: 'url', id: 'url', required: true, placeholder: 'https://' }), br(),br(), - label({ for: 'description' }, i18n.tribeMediaDescription), br, - textarea({ name: 'description', id: 'description', rows: 3, placeholder: i18n.tribeMediaDescription }, ''), br(), - button({ type: 'submit', class: 'create-button' }, mediaBtnLabel) - ) - ); - } - return div({ class: 'create-tribe-form' }, - form({ method: 'POST', action: `${tribeUrl}/media/upload`, enctype: 'multipart/form-data' }, - input({ type: 'hidden', name: 'mediaType', value: mediaType }), - input({ type: 'hidden', name: 'returnSection', value: sectionKey }), - label({ for: 'title' }, i18n.tribeMediaTitle), br, - input({ type: 'text', name: 'title', maxlength: '100', id: 'title', required: true, placeholder: i18n.tribeMediaTitle }), br(), - label({ for: 'description' }, i18n.tribeMediaDescription), br, - textarea({ name: 'description', id: 'description', rows: 3, placeholder: i18n.tribeMediaDescription }, ''), br(), - label({ for: 'media' }, i18n.tribeMediaUpload), br, - input({ type: 'file', name: 'media', id: 'media', accept: acceptForMediaType[mediaType] || '*/*', required: true }), br(), br(), - button({ type: 'submit', class: 'create-button' }, mediaBtnLabel) - ) - ); - } - - const mediaFooter = (m) => [ - p({ class: 'tribe-media-date' }, span({ class: 'date-link' }, new Date(m.createdAt).toLocaleString())), - p({ class: 'tribe-media-author' }, userLink(m.author)), - m.author === userId ? form({ method: 'POST', action: `${tribeUrl}/content/delete/${encodeURIComponent(m.id)}` }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeContentDelete) - ) : null - ]; - - const renderMediaItem = (m) => { - const blobUrl = toBlobUrl(m.image); - if (mediaType === 'image') { - return div({ class: 'tribe-media-item' }, - blobUrl ? a({ href: blobUrl, target: '_blank' }, img({ src: blobUrl, alt: m.title || '', class: 'tribe-media-thumb' })) : null, - div({ class: 'tribe-media-item-info' }, - m.title ? h2(m.title) : null, - m.description ? p(...renderUrl(m.description)) : null, - ...mediaFooter(m) - ) - ); - } - if (mediaType === 'audio') { - return div({ class: 'tribe-media-item' }, - blobUrl ? audio({ src: blobUrl, controls: true, class: 'tribe-media-audio' }) : p(i18n.tribeMediaEmpty), - div({ class: 'tribe-media-item-info' }, - m.title ? h2(m.title) : null, - m.description ? p(...renderUrl(m.description)) : null, - ...mediaFooter(m) - ) - ); - } - if (mediaType === 'video') { - return div({ class: 'tribe-media-item' }, - blobUrl ? video({ src: blobUrl, controls: true, class: 'tribe-media-thumb' }) : p(i18n.tribeMediaEmpty), - div({ class: 'tribe-media-item-info' }, - m.title ? h2(m.title) : null, - m.description ? p(...renderUrl(m.description)) : null, - ...mediaFooter(m) - ) - ); - } - if (mediaType === 'document') { - return div({ class: 'tribe-media-item' }, - blobUrl ? a({ href: blobUrl, target: '_blank', class: 'tribe-action-btn' }, i18n.readDocument || 'Read Document') : p(i18n.tribeMediaEmpty), - div({ class: 'tribe-media-item-info' }, - m.title ? h2(m.title) : null, - m.description ? p(...renderUrl(m.description)) : null, - ...mediaFooter(m) - ) - ); - } - if (mediaType === 'bookmark') { - const url = m.url || m.description || ''; - return div({ class: 'tribe-media-item' }, - div({ class: 'tribe-media-item-info' }, - m.title ? h2(m.title) : null, - url ? div({ class: 'card-field' }, - span({ class: 'card-label' }, 'URL:'), - a({ href: url, target: '_blank', class: 'card-value' }, url) - ) : null, - m.description && m.description !== url ? p(...renderUrl(m.description)) : null, - ...mediaFooter(m) - ) - ); - } - if (mediaType === 'torrent') { - return div({ class: 'tribe-media-item' }, - blobUrl ? a({ href: blobUrl, class: 'tribe-action-btn' }, i18n.torrentDownloadButton || 'DOWNLOAD IT!') : p(i18n.tribeMediaEmpty), - div({ class: 'tribe-media-item-info' }, - m.title ? h2(m.title) : null, - m.description ? p(...renderUrl(m.description)) : null, - ...mediaFooter(m) - ) - ); - } - return null; - }; - - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, - h2(sTitle), - form({ method: 'GET', action: tribeUrl }, - input({ type: 'hidden', name: 'section', value: sectionKey }), - input({ type: 'hidden', name: 'action', value: 'create' }), - button({ type: 'submit', class: 'create-button' }, mediaBtnLabel) - ) - ), - media.length === 0 ? p(i18n.tribeMediaEmpty) : - div({ class: 'tribe-media-grid' }, media.map(renderMediaItem)) - ); -}; - const renderSubTribesSection = (tribe, items, query) => { const action = query.action; const isMemberSub = Array.isArray(tribe.members) && tribe.members.includes(userId); @@ -1359,7 +1585,7 @@ const renderSubTribesSection = (tribe, items, query) => { if (action === 'create' && canCreate) { return renderCreateForm(tribe, 'subtribes', [ - { name: 'title', label: i18n.tribeTitleLabel, required: true, placeholder: 'Name of the sub-tribe' }, + { name: 'title', label: i18n.tribeTitleLabel, maxlength: '100', required: true, placeholder: 'Name of the sub-tribe' }, { name: 'description', type: 'textarea', label: i18n.tribeDescriptionLabel, required: true, placeholder: 'Description of the sub-tribe' }, { name: 'location', label: i18n.tribeLocationLabel, placeholder: 'Where is this sub-tribe located?' }, { name: 'mapUrl', label: i18n.mapLocationTitle || 'Map Location', placeholder: i18n.mapUrlPlaceholder || '/maps/MAP_ID', spaceBefore: true }, @@ -1386,7 +1612,7 @@ const renderSubTribesSection = (tribe, items, query) => { subTribes.length === 0 ? null : div({ class: 'tribe-thumb-grid' }, - subTribes.map(st => { + paged(subTribes).map(st => { return a({ href: `/tribe/${encodeURIComponent(st.id)}`, class: 'tribe-thumb-link', title: st.title }, img({ src: toImageUrl(st.image, '/assets/images/default-tribe.png'), class: 'tribe-thumb-img', alt: st.title }) ); @@ -1395,176 +1621,6 @@ const renderSubTribesSection = (tribe, items, query) => { ); }; -const renderTribeMapsSection = (tribe, maps) => { - const items = Array.isArray(maps) ? maps : []; - const createBtn = form({ method: 'GET', action: '/maps' }, - input({ type: 'hidden', name: 'filter', value: 'create' }), - input({ type: 'hidden', name: 'tribeId', value: tribe.id }), - button({ type: 'submit', class: 'create-button' }, i18n.mapUploadButton || 'Create Map')); - if (items.length === 0) return div({ class: 'tribe-content-list' }, div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionMaps || 'MAPS'), createBtn), p(i18n.noMaps || 'No maps yet')); - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionMaps || 'MAPS'), createBtn), - items.map(m => - div({ class: 'card card-rpg tribe-card-padded' }, - div({ class: 'card-header' }, - h2({ class: 'card-label' }, `[${(i18n.typeMap || 'MAP').toUpperCase()}]`), - form({ method: 'GET', action: `/maps/${encodeURIComponent(m.key)}` }, - button({ type: 'submit', class: 'filter-btn' }, i18n.viewDetails || 'View Details')) - ), - div({ class: 'tribe-card-body' }, - m.title ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.title || 'Title') + ':'), - span({ class: 'card-value' }, a({ href: `/maps/${encodeURIComponent(m.key)}` }, m.title)) - ) : null, - m.description ? p(m.description.substring(0, 200)) : null, - m.lat && m.lng ? span({ class: 'map-coords' }, `📍 ${m.lat.toFixed(4)}, ${m.lng.toFixed(4)}`) : null - ), - p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(m.createdAt).toLocaleString()), - userLink(m.author) - ) - ) - ) - ); -}; - -const renderTribeTorrentsSection = (tribe, torrents) => { - const items = Array.isArray(torrents) ? torrents : []; - const createBtn = form({ method: 'GET', action: '/torrents' }, - input({ type: 'hidden', name: 'filter', value: 'create' }), - input({ type: 'hidden', name: 'tribeId', value: tribe.id }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeCreateTorrent || 'Upload Torrent')); - if (items.length === 0) return div({ class: 'tribe-content-list' }, div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionTorrents || 'TORRENTS'), createBtn), p(i18n.tribeTorrentsEmpty || 'No torrents, yet.')); - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionTorrents || 'TORRENTS'), createBtn), - items.map(m => { - const blobName = encodeURIComponent((m.title || 'download').replace(/\.torrent$/i, '') + '.torrent'); - const blobUrl = m.url ? `/blob/${encodeURIComponent(m.url)}?name=${blobName}` : null; - return div({ class: 'card card-rpg tribe-card-padded' }, - div({ class: 'card-header' }, - h2({ class: 'card-label' }, `[${(i18n.typeTorrent || 'TORRENT').toUpperCase()}]`), - m._isMedia - ? (blobUrl ? a({ href: blobUrl, class: 'filter-btn' }, i18n.torrentDownload || 'Download') : null) - : form({ method: 'GET', action: `/torrents/${encodeURIComponent(m.rootId || m.key)}` }, - button({ type: 'submit', class: 'filter-btn' }, i18n.viewDetails || 'View Details')) - ), - div({ class: 'tribe-card-body' }, - m.title ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.title || 'Title') + ':'), - span({ class: 'card-value' }, m._isMedia - ? (blobUrl ? a({ href: blobUrl }, m.title) : m.title) - : a({ href: `/torrents/${encodeURIComponent(m.rootId || m.key)}` }, m.title)) - ) : null, - m.description ? p(String(m.description).substring(0, 200)) : null, - blobUrl && !m._isMedia ? div({ class: 'card-field' }, a({ href: blobUrl, class: 'filter-btn' }, i18n.torrentDownload || 'Download')) : null - ), - p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(m.createdAt).toLocaleString()), - userLink(m.author) - ) - ); - }) - ); -}; - -const renderTribePadsSection = (tribe, pads) => { - const items = Array.isArray(pads) ? pads : []; - const createBtn = form({ method: 'GET', action: '/pads' }, - input({ type: 'hidden', name: 'filter', value: 'create' }), - input({ type: 'hidden', name: 'tribeId', value: tribe.id }), - button({ type: 'submit', class: 'create-button' }, i18n.tribePadCreate || 'Create Pad')); - if (items.length === 0) return div({ class: 'tribe-content-list' }, div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionPads || 'PADS'), createBtn), p(i18n.tribePadsEmpty || 'No pads, yet.')); - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionPads || 'PADS'), createBtn), - items.map(m => - div({ class: 'card card-rpg tribe-card-padded' }, - div({ class: 'card-header' }, - h2({ class: 'card-label' }, `[${(i18n.typePad || 'PAD').toUpperCase()}]`), - form({ method: 'GET', action: `/pads/${encodeURIComponent(m.rootId)}` }, - button({ type: 'submit', class: 'filter-btn' }, i18n.viewDetails || 'View Details')) - ), - div({ class: 'tribe-card-body' }, - m.title ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.title || 'Title') + ':'), - span({ class: 'card-value' }, a({ href: `/pads/${encodeURIComponent(m.rootId)}` }, m.title)) - ) : null - ), - p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(m.createdAt).toLocaleString()), - userLink(m.author) - ) - ) - ) - ); -}; - -const renderTribeChatsSection = (tribe, chats) => { - const items = Array.isArray(chats) ? chats : []; - const createBtn = form({ method: 'GET', action: '/chats' }, - input({ type: 'hidden', name: 'filter', value: 'create' }), - input({ type: 'hidden', name: 'tribeId', value: tribe.id }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeChatCreate || 'Create Chat')); - if (items.length === 0) return div({ class: 'tribe-content-list' }, div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionChats || 'CHATS'), createBtn), p(i18n.tribeChatsEmpty || 'No chats, yet.')); - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionChats || 'CHATS'), createBtn), - items.map(m => - div({ class: 'card card-rpg tribe-card-padded' }, - div({ class: 'card-header' }, - h2({ class: 'card-label' }, `[${(i18n.typeChat || 'CHAT').toUpperCase()}]`), - form({ method: 'GET', action: `/chats/${encodeURIComponent(m.key)}` }, - button({ type: 'submit', class: 'filter-btn' }, i18n.viewDetails || 'View Details')) - ), - div({ class: 'tribe-card-body' }, - m.title ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.title || 'Title') + ':'), - span({ class: 'card-value' }, a({ href: `/chats/${encodeURIComponent(m.key)}` }, m.title)) - ) : null, - m.description ? p(m.description.substring(0, 200)) : null - ), - p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(m.createdAt).toLocaleString()), - userLink(m.author) - ) - ) - ) - ); -}; - -const renderTribeCalendarsSection = (tribe, calendars) => { - const items = Array.isArray(calendars) ? calendars : []; - const createBtn = form({ method: 'GET', action: '/calendars' }, - input({ type: 'hidden', name: 'filter', value: 'create' }), - input({ type: 'hidden', name: 'tribeId', value: tribe.id }), - button({ type: 'submit', class: 'create-button' }, i18n.tribeCalendarCreate || 'Create Calendar')); - if (items.length === 0) return div({ class: 'tribe-content-list' }, div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionCalendars || 'CALENDARS'), createBtn), p(i18n.tribeCalendarsEmpty || 'No calendars, yet.')); - return div({ class: 'tribe-content-list' }, - div({ class: 'tribe-content-header' }, h2(i18n.tribeSectionCalendars || 'CALENDARS'), createBtn), - items.map(m => - div({ class: 'card card-rpg tribe-card-padded' }, - div({ class: 'card-header' }, - h2({ class: 'card-label' }, `[${(i18n.typeCalendar || 'CALENDAR').toUpperCase()}]`), - form({ method: 'GET', action: `/calendars/${encodeURIComponent(m.rootId)}` }, - button({ type: 'submit', class: 'filter-btn' }, i18n.viewDetails || 'View Details')) - ), - div({ class: 'tribe-card-body' }, - m.title ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.title || 'Title') + ':'), - span({ class: 'card-value' }, a({ href: `/calendars/${encodeURIComponent(m.rootId)}` }, m.title)) - ) : null, - m.deadline ? div({ class: 'card-field' }, - span({ class: 'card-label' }, (i18n.calendarDeadlineLabel || 'Deadline') + ':'), - span({ class: 'card-value' }, new Date(m.deadline).toLocaleDateString()) - ) : null - ), - p({ class: 'card-footer' }, - span({ class: 'date-link' }, new Date(m.createdAt).toLocaleString()), - userLink(m.author) - ) - ) - ) - ); -}; - exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { if (!tribe) { return div({ class: 'error' }, i18n.tribeNotFound); @@ -1578,21 +1634,9 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { let sectionContent; switch (section) { - case 'inhabitants': sectionContent = renderInhabitantsSection(tribe, sectionData); break; - case 'feed': - sectionContent = div( - query.sent ? div({ class: 'card card-rpg tribe-banner' }, - p({ class: 'bold' }, i18n.tribeFeedSent || 'Message sent successfully!') - ) : null, - tribe.members.includes(config.keys.id) - ? form({ class: 'tribe-feed-compose', method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/message` }, - textarea({ name: 'message', rows: 4, maxlength: MAX_MESSAGE_LENGTH, placeholder: i18n.tribeFeedMessagePlaceholder }), - button({ type: 'submit', class: 'tribe-feed-send' }, i18n.tribeFeedSend) - ) - : null, - await renderFeedTribeView(sectionData, tribe, query, query.filter) - ); - break; + case 'inhabitants': sectionContent = renderInhabitantsSection(tribe, sectionData, query); break; + case 'overview': sectionContent = renderOverviewSection(tribe, query, sectionData); break; + case 'feed': sectionContent = renderFeedTribeView(sectionData, tribe, query); break; case 'events': sectionContent = renderEventsSection(tribe, sectionData, query); break; case 'tasks': sectionContent = renderTasksSection(tribe, sectionData, query); break; case 'votations': sectionContent = renderVotationsSection(tribe, sectionData, query); break; @@ -1605,11 +1649,14 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { case 'videos': sectionContent = renderTribeMediaTypeSection(tribe, sectionData, query, 'video'); break; case 'documents': sectionContent = renderTribeMediaTypeSection(tribe, sectionData, query, 'document'); break; case 'bookmarks': sectionContent = renderTribeMediaTypeSection(tribe, sectionData, query, 'bookmark'); break; - case 'torrents': sectionContent = renderTribeTorrentsSection(tribe, sectionData); break; - case 'maps': sectionContent = renderTribeMapsSection(tribe, sectionData); break; - case 'pads': sectionContent = renderTribePadsSection(tribe, sectionData); break; - case 'chats': sectionContent = renderTribeChatsSection(tribe, sectionData); break; - case 'calendars': sectionContent = renderTribeCalendarsSection(tribe, sectionData); break; + case 'torrents': sectionContent = renderTribeTorrentsSection(tribe, sectionData, query); break; + case 'files': sectionContent = renderTribeFilesSection(tribe, sectionData, query); break; + case 'maps': sectionContent = renderTribeMapsSection(tribe, sectionData, query); break; + case 'pads': sectionContent = renderTribePadsSection(tribe, sectionData, query); break; + case 'wiki': sectionContent = renderTribeWikiSectionPage(tribe, sectionData, query); break; + case 'chats': sectionContent = renderTribeChatsSection(tribe, sectionData, query); break; + case 'rooms': sectionContent = renderTribeRoomsSectionPage(tribe, sectionData, query); break; + case 'calendars': sectionContent = renderTribeCalendarsSection(tribe, sectionData, query); break; case 'governance': sectionContent = renderGovernance(tribe, sectionData); break; case 'trending': sectionContent = renderTribeTrendingSection(tribe, sectionData, query); break; case 'tags': sectionContent = renderTribeTagsSection(tribe, sectionData, query); break; @@ -1629,7 +1676,7 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { : (isAuthorOfTribe || (tribe.inviteMode === 'open' && isMemberOfTribe)); const canLeave = isMemberOfTribe && (isLarpHouse || !isAuthorOfTribe); const showOwnerControls = !isLarpHouse && isAuthorOfTribe; - const inviteHref = isLarpHouse ? '/invites#invites-houses' : '/invites#invites-tribes'; + const inviteHref = '/invites?filter=others'; const showInviteTop = isOutsider && !tribe.parentTribeId; const hasOpenInvite = !!tribe.openInviteCode; const canManageOpenInvite = hasOpenInvite && (tribe.openInviteBy === userId || isAuthorOfTribe); @@ -1637,6 +1684,9 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { const tribeDetails = div({ class: 'tribe-details' }, div({ class: 'tribe-side' }, + div({ class: 'card-header activity-card-header' }, + renderContentActions(tribe.id, `/tribe/${encodeURIComponent(tribe.id)}`, { author: tribe.author, reportTitle: tribe.title, deleteAction: showOwnerControls ? contentDeleteAction('tribe', tribe.id) : undefined }) + ), tribe.parentTribe ? div({ class: 'tribe-parent-box' }, h2({ class: 'tribe-info-label' }, i18n.tribeMainTribeLabel || 'MAIN TRIBE'), @@ -1658,18 +1708,27 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { table({ class: 'tribe-info-table' }, tr( td({ class: 'tribe-info-label' }, i18n.tribeCreatedAt || 'CREATED'), - td({ class: 'tribe-info-value', colspan: '3' }, new Date(tribe.createdAt).toLocaleString()) + td({ class: 'tribe-info-value', colspan: '3' }, moment(tribe.createdAt).format("YYYY/MM/DD HH:mm")) ), tr( td({ class: 'tribe-info-value tribe-author-cell', colspan: '4' }, userLink(tribe.author)) ), tribe.location ? tr( td({ class: 'tribe-info-label' }, i18n.tribeLocationLabel || 'LOCATION'), - td({ class: 'tribe-info-value', colspan: '3' }, ...renderUrl(tribe.location)) + td({ class: 'tribe-info-value', colspan: '3' }, ...renderStyledText(tribe.location)) ) : null ), h2({ class: 'tribe-members-count' }, `${i18n.tribeMembersCount}: ${tribe.memberCount != null ? tribe.memberCount : tribe.members.length}`), - (canGenerateInvite || (isOutsider && !showInviteTop)) ? div({ class: 'tribe-side-actions' }, + (isOutsider && hasOpenInvite) + ? div({ class: 'tribe-side-actions tribe-side-center' }, + renderInviteQrCard({ qrDataUrl: tribe.openInviteQr, name: tribe.title }), + form({ method: 'POST', action: '/tribes/open-invite/join' }, + input({ type: 'hidden', name: 'tribeId', value: tribe.id }), + button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeJoinOpen) + ) + ) + : null, + (canGenerateInvite || (isOutsider && !showInviteTop)) ? div({ class: canGenerateInvite ? 'tribe-side-actions' : 'tribe-side-actions tribe-side-center tribe-enter-invite' }, canGenerateInvite ? (isLarpHouse ? form({ method: 'POST', action: '/larp/invite/create' }, @@ -1691,7 +1750,17 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { ? a({ class: 'tribe-action-btn', href: inviteHref }, i18n.tribeEnterInvite) : null ) : null, - (isLarpHouse && larpHouseKey === 'academia' && isOutsider) + (tribe.subscription && !isOutsider) + ? renderSubscriptionBox({ + target: tribe.id, + scope: 'tribes', + subscribed: tribe.subscription.subscribed === true, + count: tribe.subscription.count, + isOwner: String(tribe.author) === String(userIdParam), + returnTo: `/tribe/${encodeURIComponent(tribe.id)}` + }) + : null, + (isLarpHouse && larpHouseKey === 'academia' && isOutsider && String(tribe.viewerHouse || '') !== 'academia') ? div({ class: 'tribe-side-actions' }, form({ method: 'POST', action: '/larp/join' }, input({ type: 'hidden', name: 'house', value: 'academia' }), @@ -1711,9 +1780,12 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeRemoveInvitation) ) ) : null, - tribe.description ? p({ class: 'tribe-side-description' }, ...renderUrl(tribe.description)) : null, - renderMapLocationVisitLabel(tribe.mapUrl), - (!tribe.parentTribeId && (canCreateSub || subTribes.length > 0 || showInviteTop)) ? div({ class: 'tribe-side-subtribes' }, + tribe.description ? p({ class: 'tribe-side-description' }, ...renderStyledText(tribe.description)) : null, + renderMapEmbed(tribe.mapData, tribe.mapUrl), + showInviteTop + ? div({ class: 'tribe-side-actions tribe-side-center tribe-enter-invite' }, a({ class: 'tribe-action-btn', href: inviteHref }, i18n.tribeEnterInvite)) + : null, + (!tribe.parentTribeId && (canCreateSub || subTribes.length > 0)) ? div({ class: 'tribe-side-subtribes' }, canCreateSub ? form({ method: 'GET', action: `/tribe/${encodeURIComponent(tribe.id)}` }, input({ type: 'hidden', name: 'section', value: 'subtribes' }), @@ -1721,9 +1793,6 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeSubTribesCreate) ) : null, - showInviteTop - ? a({ class: 'tribe-action-btn', href: inviteHref }, i18n.tribeEnterInvite) - : null, subTribes.length > 0 ? div({ class: 'tribe-subtribes-list' }, subTribes.map(st => @@ -1743,11 +1812,6 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { button({ type: 'submit', class: 'tribe-action-btn' }, i18n.tribeUpdateButton) ) : null, - showOwnerControls - ? form({ method: 'POST', action: `/tribes/delete/${encodeURIComponent(tribe.id)}` }, - button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.tribeDeleteButton) - ) - : null, canLeave ? (isLarpHouse ? form({ method: 'POST', action: '/larp/leave' }, @@ -1768,10 +1832,134 @@ exports.tribeView = async (tribe, userIdParam, query, section, sectionData) => { ) ); - return template( + const page = template( pageTitle, tribeDetails ); + return section === 'documents' ? `${page}` : page; +}; + +exports.clearnetTribeView = async ({ tribe, items, names = {}, slug, page = 1, filterType = '', filterAuthor = '' }) => { + const t = tribe || {}; + const all = (Array.isArray(items) ? items : []).filter(it => it && it.contentType !== 'forum-reply'); + const kindKey = (it) => it.contentType === 'media' ? `media-${String(it.mediaType || '')}` : String(it.contentType || ''); + const kindName = (it) => it.contentType === 'media' ? activityMediaTypeName(String(it.mediaType || '')) : contentTypeName(it.contentType); + const activeType = String(filterType || '').toLowerCase(); + const activeAuthor = String(filterAuthor || ''); + const byAuthor = activeAuthor ? all.filter(it => it.author === activeAuthor) : all; + const list = activeType ? byAuthor.filter(it => kindKey(it) === activeType) : byAuthor; + const base = `/c/tribe/${encodeURIComponent(slug || '')}`; + const hrefFor = (type, author) => { + const q = new URLSearchParams(); + if (type) q.set('type', type); + if (author) q.set('author', author); + const qs = q.toString(); + return escapeHtml(qs ? `${base}?${qs}` : base); + }; + const paged = paginateClearnet(list, page); + const pager = renderClearnetPager({ base, params: { type: activeType, author: activeAuthor }, page: paged.page, pages: paged.pages, per: paged.per, total: paged.total }); + const name = t.title || i18n.cnUntitled || 'Untitled'; + const cover = cnBlobUrl(t.image); + const desc = renderRichText(t.description || ''); + const day = (v) => { const d = new Date(v); return v && !isNaN(d) ? d.toISOString().slice(0, 10) : ''; }; + const who = (id) => (names && names[id]) || (id ? `${String(id).slice(0, 10)}…` : ''); + const countBy = (arr, fn) => { const out = new Map(); for (const it of arr) { const k = fn(it); if (k) out.set(k, (out.get(k) || 0) + 1); } return out; }; + const typeCounts = countBy(byAuthor, kindKey); + const authorCounts = countBy(activeType ? all.filter(it => kindKey(it) === activeType) : all, it => it.author); + const btn = (href, label, count, active) => `${escapeHtml(label)} (${count})`; + const typeRow = typeCounts.size > 1 + ? `
${btn(hrefFor('', activeAuthor), i18n.all, byAuthor.length, !activeType)}${[...typeCounts.entries()].map(([k, n]) => btn(hrefFor(k, activeAuthor), kindName(byAuthor.find(it => kindKey(it) === k)), n, activeType === k)).join('')}
` + : ''; + const authorRow = authorCounts.size > 1 + ? `
${btn(hrefFor(activeType, ''), i18n.all, [...authorCounts.values()].reduce((a, b) => a + b, 0), !activeAuthor)}${[...authorCounts.entries()].map(([id, n]) => btn(hrefFor(activeType, id), who(id), n, activeAuthor === id)).join('')}
` + : ''; + const renderItem = (it) => { + const mt = it.contentType === 'media' ? String(it.mediaType || '') : ''; + const kind = kindName(it); + const title = escapeHtml(it.title || ''); + const blob = cnBlobUrl(it.image); + const media = !blob ? '' + : mt === 'image' ? `${title}` + : mt === 'audio' ? `` + : mt === 'video' ? `` + : ['document', 'file', 'torrent'].includes(mt) ? `⇩ ${title || escapeHtml(kind)}` + : ''; + const url = mt === 'bookmark' ? String(it.url || '') : ''; + const href = url ? safeExternalHref(url) : ''; + const bookmark = !url ? '' + : href ? `${escapeHtml(url)}` + : `${escapeHtml(url)}`; + const details = [ + it.date ? `${i18n.tribeEventDate || 'Date'}: ${it.date}` : '', + it.deadline ? `${i18n.tribeTaskDeadline || 'Deadline'}: ${it.deadline}` : '', + it.location ? `${i18n.tribeEventLocation || 'Location'}: ${it.location}` : '', + it.status ? (statusI18n()[it.status] || it.status) : '', + it.contentType === 'map' && Number.isFinite(Number(it.lat)) ? `📍 ${Number(it.lat).toFixed(4)}, ${Number(it.lng).toFixed(4)}` : '', + ...(it.contentType === 'map' && Array.isArray(it.markers) ? it.markers.map(mk => `📍 ${Number(mk.lat).toFixed(4)}, ${Number(mk.lng).toFixed(4)}${mk.label ? ` · ${mk.label}` : ''}`) : []), + ...(it.contentType === 'calendar' && Array.isArray(it.dates) ? it.dates.map(d => `📅 ${day(d.date)}${d.label ? ` · ${d.label}` : ''}`) : []) + ].filter(Boolean).map(d => `${escapeHtml(String(d))}`).join(''); + const options = it.contentType === 'votation' && Array.isArray(it.options) && it.options.length + ? `
    ${it.options.map(o => `
  • ${escapeHtml(String(o))}
  • `).join('')}
` + : ''; + const replies = it.contentType === 'forum' && Array.isArray(it.replies) ? it.replies : []; + const repliesHtml = replies.length + ? `
${escapeHtml(String(i18n.forumMessages || i18n.tribeForumReplies || 'MESSAGES').toUpperCase())}: ${replies.length}
${replies.map(r => `
${escapeHtml(who(r.author))}${day(r.createdAt) ? ` · ${escapeHtml(day(r.createdAt))}` : ''}

${renderRichText(r.description || '')}

`).join('')}
` + : ''; + const text = it.description && it.description !== url ? renderRichText(it.description) : ''; + const when = day(it.createdAt); + return `
+
[${escapeHtml(String(kind).toUpperCase())}]${when ? `📅 ${escapeHtml(when)}` : ''}${it.author ? `${escapeHtml(who(it.author))}` : ''}
+ ${title ? `

${it.href ? `${title}` : title}

` : ''} + ${media} + ${text ? `

${text}

` : ''} + ${bookmark} + ${details ? `
${details}
` : ''} + ${options} + ${repliesHtml} +
`; + }; + const extraCss = ` +.cn-tribe-head{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start;margin-bottom:24px} +.cn-tribe-cover{width:160px;height:160px;border-radius:8px;border:3px solid var(--fg);object-fit:cover;background:#000;flex:0 0 auto} +.cn-tribe-head-body{flex:1 1 280px;min-width:0} +.cn-tribe-name{color:var(--fg);margin:0 0 8px 0;font-size:28px;font-weight:700;word-break:break-word} +.cn-tribe-desc{color:var(--fg-soft);white-space:pre-wrap;margin:0} +.cn-tribe-list{display:flex;flex-direction:column;gap:14px;margin-top:16px} +.cn-tribe-item{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:14px 16px;min-width:0} +.cn-tribe-meta{display:flex;gap:12px;flex-wrap:wrap;align-items:baseline;color:var(--fg-dim);font-size:12px;word-break:break-all} +.cn-tribe-title{color:var(--fg);font-size:18px;font-weight:700;margin:8px 0;word-break:break-word} +.cn-tribe-text{color:var(--fg-soft);line-height:1.5;margin:8px 0;word-break:break-word} +.cn-tribe-img{display:block;max-width:100%;height:auto;border-radius:6px;border:1px solid var(--border);margin:10px 0} +.cn-tribe-player{display:block;width:100%;max-width:100%;margin:10px 0;border-radius:6px;background:#000} +.cn-tribe-link{display:inline-block;margin:8px 0;padding:6px 12px;background:var(--bg-sub);border:1px solid var(--border);border-radius:6px;color:var(--fg);word-break:break-all} +.cn-tribe-options{margin:8px 0;padding-left:20px;color:var(--fg-soft)} +.cn-tribe-replies{margin-top:12px;border-top:1px solid var(--border);padding-top:8px} +.cn-tribe-reply{border-left:2px solid var(--border);padding:4px 0 4px 12px;margin:8px 0} +.cn-tribe-reply-meta{color:var(--fg-dim);font-size:12px;word-break:break-all} +.cn-tribe-reply p{margin:4px 0 0 0;color:var(--fg-soft);line-height:1.5;word-break:break-word} +.cn-empty-content{background:var(--bg-elev);border:1px dashed var(--border);border-radius:8px;padding:24px;text-align:center;color:var(--fg-dim);font-size:14px} +` + CLEARNET_HUB_CSS + CLEARNET_PAGER_CSS; + const body = ` +
+ ${escapeHtml(name)} +
+

${escapeHtml(name)}

+ ${desc ? `

${desc}

` : ''} +
+
+ ${typeRow}${authorRow} + ${list.length + ? `

${escapeHtml(i18n.cnPublicContent || 'Content')} (${list.length})

${paged.items.map(renderItem).join('')}
${pager}` + : `
${escapeHtml(i18n.cnCategoryEmpty || '')}
`} +`; + return renderClearnetPage({ + title: `${name} | Oasis`, + ogTitle: name, + ogDescription: t.description || '', + ogImage: cover, + extraCss, + body + }); }; const GOVERNANCE_METHODS = ['DEMOCRACY', 'MAJORITY', 'MINORITY', 'DICTATORSHIP', 'KARMATOCRACY']; @@ -1919,7 +2107,7 @@ const candidaturesBlock = (tribe, candidatures, alreadyPublishedThisGlobalCycle) ' ', form({ method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/governance/candidature/vote`, class: 'inline-form' }, input({ type: 'hidden', name: 'candidatureId', value: c.id }), - button({ type: 'submit', class: 'filter-btn' }, i18n.vote || 'Vote') + button({ type: 'submit', class: 'tribe-action-btn' }, i18n.vote || 'Vote') ) ) )) @@ -1934,7 +2122,7 @@ const rulesBlock = (tribe, rules, isCreator) => div({}, label({}, i18n.tribeGovRuleTitle || 'Title'), br(), input({ type: 'text', name: 'title', maxlength: '100', required: true }), br(), br(), label({}, i18n.tribeGovRuleBody || 'Body'), br(), - textarea({ name: 'body', rows: 4 }), br(), br(), + textarea({ maxlength: "3000", name: 'body', rows: 4 }), br(), br(), button({ type: 'submit', class: 'create-button' }, i18n.save || 'Save') ) ) @@ -1949,7 +2137,7 @@ const rulesBlock = (tribe, rules, isCreator) => div({}, isCreator ? form({ method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/governance/rule/delete`, class: 'inline-form' }, input({ type: 'hidden', name: 'ruleId', value: r.id }), - button({ type: 'submit', class: 'filter-btn danger-btn' }, i18n.delete || 'Delete') + button({ type: 'submit', class: 'tribe-action-btn danger-btn' }, i18n.delete || 'Delete') ) : null ) @@ -1959,7 +2147,7 @@ const rulesBlock = (tribe, rules, isCreator) => div({}, const renderGovernance = (tribe, data) => { const { filter, term, candidatures, rules, leaders, isCreator, canPublishToGlobal, alreadyPublishedThisGlobalCycle, hasElectedCandidate } = data || {}; const f = filter || 'government'; - const header = div({ class: 'tags-header' }, + const header = div({ class: 'tags-header module-header-line' }, h2(i18n.tribeSectionGovernance || 'GOVERNANCE'), p(i18n.tribeGovernanceDesc || 'Internal governance for this tribe. Propose candidatures, debate rules, elect leaders — mirrors the global Parliament.') ); diff --git a/src/views/video_view.js b/src/views/video_view.js index 4ce1547..c46e4ed 100644 --- a/src/views/video_view.js +++ b/src/views/video_view.js @@ -18,10 +18,11 @@ const { const { renderCommentsSection: renderSharedCommentsSection, renderCommentsLink } = require("./comments_view"); const moment = require("../server/node_modules/moment"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions , renderSpreadEditWarning } = require("./main_views"); +const { renderLicenseChip, renderLicenseSelect, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderTribeOriginChip } = require('./clearnet_view'); +const { clearnetItemHref, template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderSpreadButton, renderEcoTax, renderLifespanChip, renderContentActions , renderSpreadEditWarning, renderModuleStats, moduleIsEmpty, contentDeleteAction, paged } = require("./main_views"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl") -const { renderMapLocationVisitLabel } = require("./maps_view"); +const { renderStyledText } = require("../backend/renderStyledText") +const { renderMapLocationVisitLabel, renderMapEmbed } = require("./maps_view"); const userId = config.keys.id; @@ -77,13 +78,6 @@ const renderVideoOwnerActions = (filter, videoObj, params = {}) => { ) ); } - items.push( - form( - { method: "POST", action: `/videos/delete/${encodeURIComponent(videoObj.key)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.videoDeleteButton) - ) - ); return items; }; @@ -105,22 +99,32 @@ const renderVideoList = exports.renderVideoList = (videos, filter, params = {}) const title = safeText(videoObj.title); const isOwn = videoObj.author && String(videoObj.author) === String(userId); + const headerActions = typeof params.headerActions === "function" + ? params.headerActions(videoObj) + : videoObj.tribeOrigin + ? renderContentActions(null, videoObj.tribeOrigin.href) + : renderContentActions(videoObj.key, `/videos/${encodeURIComponent(videoObj.key)}`, { spread: (params.spreadMap && params.spreadMap.get(videoObj.key)) || params.spreads || null, author: videoObj.author, favKind: 'videos', torrentFrom: { blobId: videoObj.url, name: videoObj.title }, isFavorite: videoObj.isFavorite, reportTitle: videoObj.title, returnTo, deleteAction: isOwn ? contentDeleteAction('video', videoObj.key) : undefined }); + const titleHref = typeof params.headerActions === "function" ? null : videoObj.tribeOrigin ? videoObj.tribeOrigin.href : `/videos/${encodeURIComponent(videoObj.key)}`; + const engagement = typeof params.engagement === "function" + ? params.engagement(videoObj) + : videoObj.tribeOrigin ? null : renderEngagement(videoObj.key, + renderOpinionsVoting('/videos/opinions', videoObj.key, videoObj.opinions, returnTo, videoObj.opinions_inhabitants), + renderCommentsLink({ href: `/videos/${encodeURIComponent(videoObj.key)}`, count: commentCount }) + ); return div( { class: "trending-card video-card" + (isOwn ? " own-content" : "") }, div( { class: "card-header activity-card-header" }, span(), - renderContentActions(videoObj.key, `/videos/${encodeURIComponent(videoObj.key)}`, { spread: (params.spreadMap && params.spreadMap.get(videoObj.key)) || params.spreads || null, author: videoObj.author, favKind: 'videos', isFavorite: videoObj.isFavorite, reportTitle: videoObj.title }) + headerActions ), div( { class: "card-section video-card-body" }, - title ? h2(title) : null, + div({ class: "shop-title-row" }, title ? h2(titleHref ? a({ href: titleHref, class: "card-title-link" }, title) : title) : null, videoObj.tribeOrigin ? renderTribeOriginChip(videoObj.tribeOrigin) : videoObj.clearnet === true ? renderReachChip(true, i18n, clearnetItemHref('videos', videoObj.title, videoObj.key)) : null, renderLicenseChip(videoObj.license), ...(typeof params.titleChips === "function" ? params.titleChips(videoObj) : [])), videoObj.lifetime ? div({ class: "card-chips-row" }, renderLifespanChip(videoObj.lifetime, i18n)) : null, renderVideoPlayer(videoObj), - renderEngagement(videoObj.key, - renderOpinionsVoting('/videos/opinions', videoObj.key, videoObj.opinions, returnTo, videoObj.opinions_inhabitants), - renderCommentsLink({ href: `/videos/${encodeURIComponent(videoObj.key)}`, count: commentCount }) - ), + ...(typeof params.bodyExtra === "function" ? params.bodyExtra(videoObj) : []), + engagement, renderMapLocationVisitLabel(videoObj.mapUrl), br(), (() => { @@ -130,7 +134,7 @@ const renderVideoList = exports.renderVideoList = (videos, filter, params = {}) return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(videoObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(videoObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(videoObj.author), showUpdated ? span( @@ -170,7 +174,7 @@ const renderVideoForm = (filter, videoId, videoToEdit, params = {}) => { br(), span(i18n.videoDescriptionLabel), br(), - textarea({ name: "description", placeholder: i18n.videoDescriptionPlaceholder, rows: "4" }, videoToEdit?.description || ""), + textarea({ maxlength: "5000", name: "description", placeholder: i18n.videoDescriptionPlaceholder, rows: "4" }, videoToEdit?.description || ""), br(), span(i18n.mapLocationTitle || "Map Location"), br(), @@ -185,12 +189,26 @@ const renderVideoForm = (filter, videoId, videoToEdit, params = {}) => { value: safeArr(videoToEdit?.tags).join(", ") }), br(), + ...renderLicenseSelect(videoToEdit?.license, i18n), + br(), + renderClearnetSelector(filter === "edit" ? !!videoToEdit?.clearnet : false, i18n), br(), button({ type: "submit" }, filter === "edit" ? i18n.videoUpdateButton : i18n.videoCreateButton) ) ); }; +const mediaChipFor = (filter, censusM) => (mode) => { + if (mode === filter) return true; + if (!Array.isArray(censusM)) return true; + if (mode === "top") return censusM.some((x) => !x.tribeOrigin); + if (mode === "mine") return censusM.some((x) => String(x.author) === String(userId)); + if (mode === "recent") return censusM.length > 0; + if (mode === "favorites") return censusM.some((x) => x.isFavorite); + if (mode === "bcs") return censusM.some((x) => String(x.title || "").toUpperCase().startsWith("BCS-")); + return true; +}; + exports.videoView = async (videos, filter = "all", videoId = null, params = {}) => { if (filter === "edit") params = { ...params, spreadWarning: await renderSpreadEditWarning(videoId) }; const title = i18n.videoTitle; @@ -199,35 +217,34 @@ exports.videoView = async (videos, filter = "all", videoId = null, params = {}) const sort = safeText(params.sort || "recent"); const list = safeArr(videos); + const emptyMod = moduleIsEmpty(list, filter, "all", q); + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : list); const videoToEdit = videoId ? list.find((v) => v.key === videoId) : null; return template( title, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(title), p(i18n.videoDescription) ), - (() => { - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.viewerPrefs && params.viewerPrefs.clearnetVideos); - return div({ class: "shop-title-row" }, renderReachChip(isClearnet, i18n)); - })(), - br(), div( { class: "filters" }, form( { method: "GET", action: "/videos", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterRecent).toUpperCase()), button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterMine).toUpperCase())] : []), + ...(mediaChip("favorites") ? [button( { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterFavorites).toUpperCase() - ), + )] : []), - button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterTop).toUpperCase()), + ...(mediaChip("top") ? [button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterTop).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.videoCreateButton) ) ) @@ -236,8 +253,9 @@ exports.videoView = async (videos, filter = "all", videoId = null, params = {}) filter === "create" || filter === "edit" ? renderVideoForm(filter, videoId, videoToEdit, { ...params, filter }) : section( - div( - { class: "videos-search" }, + emptyMod ? null : div( + { class: "videos-search activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), form( { method: "GET", action: "/videos", class: "filter-box" }, input({ type: "hidden", name: "filter", value: filter }), @@ -260,20 +278,21 @@ exports.videoView = async (videos, filter = "all", videoId = null, params = {}) ) ) ), - div({ class: "videos-list" }, renderVideoList(list, filter, { q, sort })) + div({ class: "videos-list" }, renderVideoList(paged(list), filter, { q, sort, spreadMap: params.spreadMap })) ) ) ); }; exports.singleVideoView = async (videoObj, filter = "all", comments = [], params = {}) => { + const mediaChip = mediaChipFor(filter, Array.isArray(params.censusList) ? params.censusList : null); const q = safeText(params.q || ""); const sort = safeText(params.sort || "recent"); const returnTo = safeText(params.returnTo) || buildReturnTo(filter, { q, sort }); const title = safeText(videoObj.title); - const { renderReachChip } = require('./clearnet_view'); - const isClearnet = !!(params.authorPrefs && params.authorPrefs.clearnetVideos); + const isAuthor = String(videoObj.author) === String(userId); + const isClearnet = !!videoObj.clearnet; const chips = [ renderLifespanChip(videoObj.lifetime, i18n), @@ -287,27 +306,30 @@ exports.singleVideoView = async (videoObj, filter = "all", comments = [], params const tagsNode = renderTags(videoObj.tags); const detailActions = div({ class: "card-header activity-card-header" }, - renderContentActions(videoObj.key, null, { + renderContentActions(videoObj.key, `/videos/${encodeURIComponent(videoObj.key)}`, { author: videoObj.author, - favKind: 'videos', + favKind: 'videos', torrentFrom: { blobId: videoObj.url, name: videoObj.title }, isFavorite: videoObj.isFavorite, spread: params.spreads || null, returnTo, - reportTitle: videoObj.title + reportTitle: videoObj.title, + deleteAction: String(videoObj.author) === String(userId) ? contentDeleteAction('video', videoObj.key) : undefined }) ); const videoSide = div({ class: "tribe-side" }, div({ class: "shop-title-row" }, title ? h2({ class: "tribe-card-title" }, title) : null, - renderReachChip(isClearnet, i18n) + renderReachChip(isClearnet, i18n, isClearnet ? clearnetItemHref('videos', videoObj.title, videoObj.key) : null), + isAuthor ? renderClearnetSwitch('videos', videoObj.rootId || videoObj.key, isClearnet) : null, + renderLicenseChip(videoObj.license) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, safeText(videoObj.description) - ? p({ class: "tribe-side-description" }, ...renderUrl(videoObj.description)) + ? p({ class: "tribe-side-description" }, ...renderStyledText(videoObj.description)) : null, tagsNode, - renderMapLocationVisitLabel(videoObj.mapUrl), + renderMapEmbed(params.mapData, videoObj.mapUrl), sideActions.length ? div({ class: "tribe-side-actions" }, ...sideActions) : null ); @@ -321,7 +343,7 @@ exports.singleVideoView = async (videoObj, filter = "all", comments = [], params return p( { class: "card-footer" }, - span({ class: "date-link" }, `${moment(videoObj.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), + span({ class: "date-link" }, `${moment(videoObj.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(videoObj.author), showUpdated ? span( @@ -340,7 +362,7 @@ exports.singleVideoView = async (videoObj, filter = "all", comments = [], params return template( i18n.videoTitle, section( - div({ class: "tags-header" }, + div({ class: "tags-header module-header-line" }, h2(i18n.videoAllSectionTitle || i18n.videoTitle), p(i18n.videoDescription) ), @@ -350,12 +372,12 @@ exports.singleVideoView = async (videoObj, filter = "all", comments = [], params { method: "GET", action: "/videos", class: "ui-toolbar ui-toolbar--filters" }, input({ type: "hidden", name: "q", value: q }), input({ type: "hidden", name: "sort", value: sort }), - button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterRecent).toUpperCase()), button( + button({ type: "submit", name: "filter", value: "all", class: filter === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterAll).toUpperCase()), ...(mediaChip("favorites") ? [button( + ...(mediaChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: filter === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterRecent).toUpperCase())] : []), + ...(mediaChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: filter === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterMine).toUpperCase())] : []), { type: "submit", name: "filter", value: "favorites", class: filter === "favorites" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterFavorites).toUpperCase() - ), + )] : []), button({ type: "submit", name: "filter", value: "top", class: filter === "top" ? "filter-btn active" : "filter-btn" }, String(i18n.videoFilterTop).toUpperCase()), button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.videoCreateButton) diff --git a/src/views/vote_view.js b/src/views/vote_view.js index 9a77013..72df6f1 100644 --- a/src/views/vote_view.js +++ b/src/views/vote_view.js @@ -1,9 +1,9 @@ const { div, h2, p, section, button, form, a, textarea, br, input, table, tr, th, td, label, span } = require("../server/node_modules/hyperaxe"); const { renderCommentsSection: renderSharedCommentsSection } = require("./comments_view"); -const { template, i18n, renderOpinionsVoting, renderEngagement, userLink, renderOpenClosedChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderDocumentActions } = require("./main_views"); +const { template, i18n, renderOpinionsVoting, renderEngagement, renderOpenClosedChip, renderLifespanChip, renderEcoTax, renderSpreadButton, renderContentActions, renderSpreadEditWarning, renderDocumentActions, renderModuleStatsBy, moduleIsEmpty, contentDeleteAction, paged, renderCreatedRows } = require("./main_views"); const moment = require("../server/node_modules/moment"); const { config } = require("../server/SSB_server.js"); -const { renderUrl } = require("../backend/renderUrl"); +const { renderTribeOriginChip } = require("./clearnet_view"); const userId = config.keys.id; const VOTE_QUORUM = 2; @@ -38,9 +38,12 @@ const statusLabel = (s) => { return up; }; -const renderVoteOwnerActions = (v, returnTo, mode) => { - const showUpdateButton = mode === "mine" && !Object.keys(v.opinions || {}).length; - const showDeleteButton = mode === "mine"; +const { isVoteLocked } = require("../models/votes_model"); +const isVoteAuthor = (v) => !!(v && v.createdBy && String(v.createdBy) === String(userId)); +const canModifyVote = (v) => isVoteAuthor(v) && !isVoteLocked(v); + +const renderVoteOwnerActions = (v, returnTo) => { + const showUpdateButton = canModifyVote(v); const actions = []; if (showUpdateButton) { @@ -52,15 +55,6 @@ const renderVoteOwnerActions = (v, returnTo, mode) => { ) ); } - if (showDeleteButton) { - actions.push( - form( - { method: "POST", action: `/votes/delete/${encodeURIComponent(v.id)}` }, - input({ type: "hidden", name: "returnTo", value: returnTo }), - button({ class: "delete-btn", type: "submit" }, i18n.voteDeleteButton) - ) - ); - } return actions; }; @@ -92,7 +86,7 @@ const renderVoteStatusChip = (status) => { return renderOpenClosedChip(status, { statusChipOPEN: localized, statusChipCLOSED: localized }); }; -const renderVoteListItem = (v, voteOptionsDefault, activeFilter, spreadInfo) => { +const renderVoteListItem = exports.renderVoteListItem = (v, voteOptionsDefault, activeFilter, spreadInfo, opts = {}) => { const voteOptions = Array.isArray(v.options) && v.options.length ? v.options : voteOptionsDefault; const baseCounts = voteOptions.reduce((acc, opt) => { acc[opt] = (v.votes && v.votes[opt]) ? v.votes[opt] : 0; @@ -100,35 +94,69 @@ const renderVoteListItem = (v, voteOptionsDefault, activeFilter, spreadInfo) => }, {}); const totalVotesNum = typeof v.totalVotes === "number" ? v.totalVotes : parseInt(String(v.totalVotes || "0"), 10) || 0; const outcome = computeVoteOutcome(baseCounts, voteOptions, totalVotesNum); + const origin = v.tribeOrigin || null; + const href = opts.href !== undefined ? opts.href : (origin ? origin.href : `/votes/${encodeURIComponent(v.id)}`); const chips = [ renderVoteStatusChip(v.status), - renderLifespanChip(v.lifetime, i18n) + renderLifespanChip(v.lifetime, i18n), + origin ? renderTribeOriginChip(origin) : null, + ...(Array.isArray(opts.extraChips) ? opts.extraChips : []) ].filter(Boolean); const returnTo = `/votes?filter=${encodeURIComponent(activeFilter || "all")}`; const totalOpinions = Object.values(v.opinions || {}).reduce((s, n) => s + (Number(n) || 0), 0); - const isOwn = v.createdBy && String(v.createdBy) === String(userId); + const isOwn = isVoteAuthor(v); + const headerActions = opts.headerActions !== undefined + ? opts.headerActions + : origin ? null : renderContentActions(v.id, `/votes/${encodeURIComponent(v.id)}`, { spread: spreadInfo || null, author: v.createdBy, favKind: 'votes', isFavorite: v.isFavorite, reportTitle: v.question, returnTo, deleteAction: canModifyVote(v) ? contentDeleteAction('votes', v.id) : undefined }); return div({ class: "trending-card vote-card" + (isOwn ? " own-content" : "") }, - div({ class: "card-header activity-card-header" }, + headerActions ? div({ class: "card-header activity-card-header" }, span(), - renderContentActions(v.id, `/votes/${encodeURIComponent(v.id)}`, { spread: spreadInfo || null, author: v.createdBy, favKind: 'votes', isFavorite: v.isFavorite, reportTitle: v.question }) - ), + headerActions + ) : null, div({ class: "card-section vote-card-body" }, div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, - a({ href: `/votes/${encodeURIComponent(v.id)}` }, v.question || i18n.votationsTitle) + href ? a({ href }, v.question || i18n.votationsTitle) : (v.question || i18n.votationsTitle) ) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - v.deadline ? p({ class: "card-date-highlight" }, moment(v.deadline).format("YYYY/MM/DD HH:mm")) : null, - div({ class: "tribe-card-members" }, + v.deadline ? p({ class: "time-chip" }, moment(v.deadline).format("YYYY/MM/DD HH:mm")) : null, + origin ? null : div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.eventAttendees}: ${totalVotesNum}`) ), - div({ class: `job-meta-line vote-outcome vote-outcome-${outcome.variant}` }, `${i18n.voteResults || "Results"}: ${outcome.text}`) + origin ? null : div({ class: `job-meta-line vote-outcome vote-outcome-${outcome.variant}` }, `${i18n.voteResults || "Results"}: ${outcome.text}`), + ...(Array.isArray(opts.bodyExtra) ? opts.bodyExtra.filter(Boolean) : []) ) ); }; +exports.renderVoteOptionsBlock = (v, voteOptions, action, returnTo, canVote) => { + const baseCounts = voteOptions.reduce((acc, opt) => { acc[opt] = (v.votes && v.votes[opt]) ? v.votes[opt] : 0; return acc; }, {}); + return [ + canVote ? div({ class: "job-section" }, + h2({ class: "job-section-title" }, i18n.voteCastTitle || "Cast Vote"), + div({ class: "vote-buttons-block" }, + div({ class: "vote-buttons-row-single" }, + ...voteOptions.map((opt, idx) => + form({ method: "POST", action }, + returnTo ? input({ type: "hidden", name: "returnTo", value: returnTo }) : null, + input({ type: "hidden", name: "optionIndex", value: String(idx) }), + button({ type: "submit", name: "choice", value: opt }, voteLabel(opt)) + ) + ) + ) + ) + ) : null, + div({ class: "vote-table" }, + table( + tr(...voteOptions.map((opt) => th(voteLabel(opt)))), + tr(...voteOptions.map((opt) => td(String(baseCounts[opt] || 0)))) + ) + ) + ]; +}; + const renderVoteDetail = (v, voteOptionsDefault, firstRow, secondRow, mode, activeFilter, params = {}) => { const voteOptions = Array.isArray(v.options) && v.options.length ? v.options : voteOptionsDefault; const baseCounts = voteOptions.reduce((acc, opt) => { @@ -147,7 +175,7 @@ const renderVoteDetail = (v, voteOptionsDefault, firstRow, secondRow, mode, acti ].filter(Boolean); const sideActions = []; - for (const a of renderVoteOwnerActions(v, returnTo, mode || "")) sideActions.push(a); + for (const a of renderVoteOwnerActions(v, returnTo)) sideActions.push(a); const cleanTags = (Array.isArray(v.tags) ? v.tags : []).filter((t) => t && !String(t).includes(":")); const tagsNode = cleanTags.length @@ -167,13 +195,13 @@ const renderVoteDetail = (v, voteOptionsDefault, firstRow, secondRow, mode, acti const voteSide = div({ class: "tribe-side" }, div({ class: "card-header activity-card-header" }, - renderContentActions(v.id, null, { spread: params.spreads || null, author: v.createdBy, favKind: 'votes', isFavorite: v.isFavorite, reportTitle: v.question }) + renderContentActions(v.id, `/votes/${encodeURIComponent(v.id)}`, { spread: params.spreads || null, author: v.createdBy, favKind: 'votes', isFavorite: v.isFavorite, reportTitle: v.question, deleteAction: canModifyVote(v) ? contentDeleteAction('votes', v.id) : undefined }) ), div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, v.question) ), chips.length ? div({ class: "card-chips-row" }, ...chips) : null, - table({ class: "tribe-info-table jobs-info-table" }, ...infoRows), + table({ class: "tribe-info-table jobs-info-table" }, ...infoRows, ...renderCreatedRows(v.createdAt, v.createdBy)), tagsNode, div({ class: "tribe-card-members" }, span({ class: "tribe-members-count" }, `${i18n.eventAttendees}: ${totalVotesNum}`) @@ -203,10 +231,6 @@ const renderVoteDetail = (v, voteOptionsDefault, firstRow, secondRow, mode, acti ) ) ), - p({ class: "card-footer" }, - span({ class: "date-link" }, `${moment(v.createdAt).format("YYYY/MM/DD HH:mm")} ${i18n.performed} `), - userLink(v.createdBy) - ), renderEngagement(v.id, renderOpinionsBar(v, returnTo), mode === "detail" ? renderCommentsSection(v.id, params.comments || [], activeFilter) : null) ); @@ -249,12 +273,22 @@ exports.voteView = async (votes, mode, voteId, comments = [], activeFilterParam, const secondRow = ["CONFUSED", "FOLLOW_MAJORITY", "NOT_INTERESTED"]; const header = div( - { class: "tags-header" }, + { class: "tags-header module-header-line" }, h2(i18n.votationsTitle), p(i18n.votationsDescription) ); const listReturnTo = standardFilters.includes(activeFilter) ? `/votes?filter=${encodeURIComponent(activeFilter)}` : "/votes"; + const emptyMod = moduleIsEmpty(filtered, mode, "all", (params && params.q) || ""); + const censusV = Array.isArray(params && params.censusList) ? params.censusList : list; + const voteChip = (m) => { + if (m === mode) return true; + if (m === "mine") return censusV.some((v) => v.createdBy === userId); + if (m === "recent") return censusV.length > 0; + if (m === "open") return censusV.some((v) => normalizeStatus(v.status) === "OPEN"); + if (m === "closed") return censusV.some((v) => normalizeStatus(v.status) === "CLOSED"); + return true; + }; const minVoteDays = Number(params.minVoteDays) > 0 ? Number(params.minVoteDays) : 7; const deadlineMin = moment().add(minVoteDays, "days").format("YYYY-MM-DDTHH:mm"); @@ -264,11 +298,6 @@ exports.voteView = async (votes, mode, voteId, comments = [], activeFilterParam, : (voteToEdit.deadline ? moment(voteToEdit.deadline).format("YYYY-MM-DDTHH:mm") : ""); const questionValue = draft.question !== undefined && draft.question !== "" ? String(draft.question) : (voteToEdit.question || ""); const tagsValue = draft.tags ? String(draft.tags) : editTags.join(", "); - const formError = params.error === "deadline" - ? String(i18n.voteErrorDeadlineMin || "The deadline must be at least {days} days from now.").replace("{days}", String(minVoteDays)) - : params.error - ? (i18n.voteErrorGeneric || "The vote could not be saved.") - : ""; return template( title, @@ -278,15 +307,19 @@ exports.voteView = async (votes, mode, voteId, comments = [], activeFilterParam, { class: "filters" }, form( { method: "GET", action: "/votes" }, + ...(emptyMod ? [] : [ button({ type: "submit", name: "filter", value: "all", class: mode === "all" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterAll).toUpperCase()), - button({ type: "submit", name: "filter", value: "mine", class: mode === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterMine).toUpperCase()), - button({ type: "submit", name: "filter", value: "open", class: mode === "open" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterOpen).toUpperCase()), - button({ type: "submit", name: "filter", value: "closed", class: mode === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterClosed).toUpperCase()), + ...(voteChip("recent") ? [button({ type: "submit", name: "filter", value: "recent", class: mode === "recent" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterRecent).toUpperCase())] : []), + ...(voteChip("mine") ? [button({ type: "submit", name: "filter", value: "mine", class: mode === "mine" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterMine).toUpperCase())] : []), + ...(voteChip("open") ? [button({ type: "submit", name: "filter", value: "open", class: mode === "open" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterOpen).toUpperCase())] : []), + ...(voteChip("closed") ? [button({ type: "submit", name: "filter", value: "closed", class: mode === "closed" ? "filter-btn active" : "filter-btn" }, String(i18n.voteFilterClosed).toUpperCase())] : []), + ]), button({ type: "submit", name: "filter", value: "create", class: mode === "create" ? "create-button active" : "create-button" }, i18n.voteCreateButton) ) ), - mode !== "create" && mode !== "edit" - ? div({ class: "filters" }, + mode !== "create" && mode !== "edit" && !emptyMod + ? div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStatsBy(filtered, v => normalizeStatus(v.status || 'OPEN'), [{ value: 'OPEN', label: i18n.voteStatusOpen }, { value: 'CLOSED', label: i18n.voteStatusClosed }]), form({ method: "GET", action: "/votes", class: "filter-box" }, input({ type: "hidden", name: "filter", value: mode }), input({ type: "text", name: "q", value: (params && params.q) || "", placeholder: i18n.votesSearchPlaceholder, class: "filter-box__input" }), @@ -302,12 +335,6 @@ exports.voteView = async (votes, mode, voteId, comments = [], activeFilterParam, ? div( { class: "vote-form" }, mode === "edit" ? await renderSpreadEditWarning(voteId) : null, - formError - ? div({ class: "error-box vote-form-error" }, - p({ class: "error-title" }, i18n.voteErrorTitle || "Check the form"), - p(formError) - ) - : null, form( { action: mode === "edit" ? `/votes/update/${encodeURIComponent(voteId)}` : "/votes/create", method: "POST" }, input({ type: "hidden", name: "returnTo", value: listReturnTo }), @@ -319,7 +346,7 @@ exports.voteView = async (votes, mode, voteId, comments = [], activeFilterParam, name: "deadline", id: "deadline", required: true, - min: mode === "create" ? deadlineMin : undefined, + min: deadlineMin, value: deadlineValue }), br(), br(), label(i18n.voteTagsLabel), br(), @@ -330,8 +357,8 @@ exports.voteView = async (votes, mode, voteId, comments = [], activeFilterParam, : mode === "detail" && voteId ? renderVoteDetail(filtered[0] || list.find(v => v.id === voteId) || {}, voteOptions, firstRow, secondRow, mode, activeFilter, { ...params, comments }) : filtered.length > 0 - ? div({ class: "jobs-grid" }, filtered.map((v) => renderVoteListItem(v, voteOptions, activeFilter, params.spreadMap && params.spreadMap.get(v.id)))) - : p(i18n.novotes), + ? div({ class: "jobs-grid" }, paged(filtered).map((v) => renderVoteListItem(v, voteOptions, activeFilter, params.spreadMap && params.spreadMap.get(v.id)))) + : div({ class: "no-content-box" }, p(i18n.novotes)), ) ); }; diff --git a/src/views/wallet_view.js b/src/views/wallet_view.js index ee8ca73..6c99485 100644 --- a/src/views/wallet_view.js +++ b/src/views/wallet_view.js @@ -1,67 +1,86 @@ const { form, button, div, h2, p, section, input, span, table, thead, tbody, tr, td, th, ul, li, a, br, label, img } = require("../server/node_modules/hyperaxe"); -const QRCode = require('../server/node_modules/qrcode'); -const { template, i18n } = require('./main_views'); +const moment = require("../server/node_modules/moment"); +const { template, i18n, userLink, renderWalletChip, renderStateChip, paged } = require('./main_views'); -const walletViewRender = (balance, address, ...elements) => { - const header = div({ class: 'tags-header' }, h2(i18n.walletTitle), p(i18n.walletDescription)); +const TX_CATEGORY_CHIPS = { + receive: ["mutuals", "\u2B07"], + send: ["whole", "\u2B06"], + generate: ["mutuals", "\u2713"], + stake: ["mutuals", "\u2713"], + immature: ["lifespan", "\u23F3"], + orphan: ["closed", "\u2717"] +}; +const renderTxCategory = (category) => { + const key = String(category || "").toLowerCase(); + const [variant, icon] = TX_CATEGORY_CHIPS[key] || ["encrypted", ""]; + return renderStateChip(variant, icon, (key || "-").toUpperCase()); +}; + +const WALLET_MODES = [ + { key: "send", href: "/wallet/send", label: () => i18n.walletSend }, + { key: "receive", href: "/wallet/receive", label: () => i18n.walletReceive }, + { key: "history", href: "/wallet/history", label: () => i18n.walletHistory } +]; + +const walletModeButtons = (current) => + div({ class: "mode-buttons-row" }, + ...WALLET_MODES.map(mode => + form({ method: "GET", action: mode.href }, + button({ type: "submit", class: current === mode.key ? "filter-btn active" : "filter-btn" }, String(mode.label()).toUpperCase()) + ) + ) + ); + +const walletViewRender = (balance, address, current, ...elements) => { + const header = div({ class: 'tags-header module-header-line' }, h2(i18n.walletTitle), p(i18n.walletDescription), renderWalletChip()); return template( i18n.walletTitle, section( header, + walletModeButtons(current), div({ class: "wallet-section" }, h2(i18n.walletAddress), div({ class: "wallet-address" }, h2({ class: "element" }, address || "-")), h2(i18n.walletBalanceTitle), - div({ class: "div-center" }, h2(i18n.walletBalanceLine({ balance: Number(balance || 0).toFixed(6) }))), - div({ class: "div-center" }, - span({ class: "wallet-form-button-group-center" }, - form({ action: "/wallet/send", method: "get" }, button({ type: 'submit' }, i18n.walletSend)), - form({ action: "/wallet/receive", method: "get" }, button({ type: 'submit' }, i18n.walletReceive)), - form({ action: "/wallet/history", method: "get" }, button({ type: 'submit' }, i18n.walletHistory)) - ) - ) + div({ class: "div-center" }, h2(i18n.walletBalanceLine({ balance: Number(balance || 0).toFixed(6) }))) ) ), - elements.length > 0 ? section(...elements) : null + elements.length > 0 ? section({ id: `wallet-${current || 'main'}` }, ...elements) : null ); }; exports.walletView = async (balance, address) => { - return walletViewRender(balance, address); + return walletViewRender(balance, address, null); }; exports.walletHistoryView = async (balance, transactions, address) => { - const rows = Array.isArray(transactions) ? transactions : []; + const rows = (Array.isArray(transactions) ? transactions.slice() : []).sort((a, b) => ((b.time || b.timereceived || 0) - (a.time || a.timereceived || 0))); return walletViewRender( balance, address, - h2(i18n.walletHistoryTitle), + "history", table( { class: "wallet-history" }, thead( tr( { class: "full-center" }, - th({ class: "col-10" }, i18n.walletCnfrs), - th(i18n.walletDate), + th({ class: "wallet-col-confirmations" }, i18n.walletCnfrs), + th({ class: "wallet-col-date" }, i18n.walletDate), th(i18n.walletType), - th(i18n.walletAmount), - th({ class: "col-30" }, i18n.walletTxId) + th(i18n.walletAmount) ) ), tbody( - ...rows.map(tx => { + ...paged(rows).map(tx => { const date = new Date((tx.time || tx.timereceived || 0) * 1000); const amount = Number(tx.amount) || 0; const fee = Number(tx.fee) || 0; const totalAmount = amount + fee; return tr( td({ class: "full-center" }, String(tx.confirmations || 0)), - td(date.toLocaleDateString(), br(), date.toLocaleTimeString()), - td(tx.category || "-"), - td(totalAmount.toFixed(6)), - td({ width: "30%", class: "tcell-ellipsis" }, - a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${encodeURIComponent(tx.txid || '')}`, target: "_blank" }, tx.txid || "-") - ) + td({ class: "wallet-col-date" }, moment(date).format("YYYY/MM/DD HH:mm")), + td(renderTxCategory(tx.category)), + td(totalAmount.toFixed(6)) ); }) ) @@ -70,42 +89,63 @@ exports.walletHistoryView = async (balance, transactions, address) => { }; exports.walletReceiveView = async (balance, address) => { - const qrDataUrl = await QRCode.toDataURL(address || '', { type: 'image/png', width: 320, margin: 1 }); return walletViewRender( balance, address, - h2(i18n.walletReceiveTitle), - div({ class: 'div-center qr-code' }, img({ src: qrDataUrl, alt: 'QR', class: 'wallet-qr-img' })) + "receive", + address + ? div({ class: 'div-center qr-code' }, img({ src: `/wallet/qr/${encodeURIComponent(address)}`, alt: 'QR', class: 'wallet-qr-img' })) + : null ); }; -exports.walletSendFormView = async (balance, destination, amount, fee, statusMessages, address) => { +exports.walletSendFormView = async (balance, destination, amount, fee, statusMessages, address, options = {}) => { + const transferCtx = options.transfer || null; + const paymentRef = options.payment || null; const type = statusMessages?.type || 'info'; const titleKey = statusMessages?.title || ''; const messages = statusMessages?.messages || []; - const statusBlock = messages.length > 0 - ? div( - { class: `wallet-status-${type}` }, - span(i18n.walletStatusMessages[titleKey] || titleKey || ''), - ul(...messages.map(error => li(i18n.walletStatusMessages[error] || error))) - ) + const statusText = [i18n.walletStatusMessages[titleKey] || titleKey || '', ...messages.map(error => i18n.walletStatusMessages[error] || error)].filter(Boolean).join(': '); + const statusBlock = messages.length > 0 && type !== 'error' + ? div({ class: 'flash-banner wallet-status' }, p(statusText)) : null; return walletViewRender( balance, address, - h2(i18n.walletWalletSendTitle), + "send", div( { class: "div-center" }, statusBlock, form( { action: '/wallet/send', method: 'POST' }, label({ for: 'destination' }, i18n.walletAddress), br(), - input({ type: 'text', id: 'destination', name: 'destination', placeholder: 'ETQ17sBv8QFoiCPGKDQzNcDJeXmB2317HX', value: destination || '' }), br(), + input({ type: 'text', id: 'destination', name: 'destination', placeholder: 'ETQ17sBv8QFoiCPGKDQzNcDJeXmB2317HX', value: destination || '', required: true, pattern: 'E[1-9A-HJ-NP-Za-km-z]{32,34}', maxlength: '35' }), br(), label({ for: 'amount' }, i18n.walletAmount), br(), - input({ type: 'text', id: 'amount', name: 'amount', placeholder: '0.25', value: amount || '' }), br(), + input({ type: 'number', id: 'amount', name: 'amount', placeholder: '0.25', value: amount || '', required: true, min: '0.000001', step: '0.000001', inputmode: 'decimal' }), br(), br(), label({ for: 'fee' }, i18n.walletFee), br(), - input({ type: 'text', id: 'fee', name: 'fee', placeholder: '0.01', value: fee || '' }), br(), + input({ type: 'number', id: 'fee', name: 'fee', placeholder: '5', value: fee || '', required: true, min: '0', step: '0.000001', inputmode: 'decimal' }), br(), br(), + paymentRef + ? div({ class: 'wallet-transfer-ctx' }, + div({ class: 'wallet-transfer-ctx-line' }, span({ class: 'wallet-transfer-ctx-label' }, `${i18n.walletPayeeLabel}: `), userLink(paymentRef.payeeId)), + paymentRef.concept ? div({ class: 'wallet-transfer-ctx-line' }, span({ class: 'wallet-transfer-ctx-label' }, `${i18n.walletConceptLabel}: `), span(paymentRef.concept)) : null, + input({ type: 'hidden', name: 'payeeId', value: paymentRef.payeeId }), + input({ type: 'hidden', name: 'concept', value: paymentRef.concept || '' }), + input({ type: 'hidden', name: 'refHref', value: paymentRef.href || '' }), + input({ type: 'hidden', name: 'refTag', value: paymentRef.tag || 'WALLET' }) + ) + : null, + transferCtx + ? div({ class: 'wallet-transfer-ctx' }, + span({ class: 'wallet-transfer-ctx-label' }, `${i18n.walletPayingTransfer}: `), + a({ href: `/transfers/${encodeURIComponent(transferCtx.id)}` }, transferCtx.concept || transferCtx.id), + input({ type: 'hidden', name: 'transferId', value: transferCtx.id }) + ) + : label({ class: 'wallet-create-transfer' }, + input({ type: 'checkbox', name: 'createTransfer', value: '1', ...(options.createTransfer ? { checked: true } : {}) }), + ` ${i18n.walletCreateTransferLabel}` + ), + br(), input({ type: 'hidden', name: 'action', value: 'confirm' }), div({ class: 'wallet-form-button-group-center' }, button({ type: 'submit' }, i18n.walletSend), @@ -116,25 +156,37 @@ exports.walletSendFormView = async (balance, destination, amount, fee, statusMes ); }; -exports.walletSendConfirmView = async (balance, destination, amount, fee) => { - const a = Number(amount || 0); - const f = Number(fee || 0); - const totalCost = a + f; +exports.walletSendConfirmView = async (balance, destination, amount, fee, options = {}) => { + const amountNum = Number(amount || 0); + const feeNum = Number(fee || 0); + const totalCost = amountNum + feeNum; return walletViewRender( balance, destination, + "send", p( i18n.walletAddressLine({ address: destination || '-' }), br(), - i18n.walletAmountLine({ amount: a.toFixed(6) }), br(), - i18n.walletFeeLine({ fee: f.toFixed(6) }), br(), + i18n.walletAmountLine({ amount: amountNum.toFixed(6) }), br(), + i18n.walletFeeLine({ fee: feeNum.toFixed(6) }), br(), i18n.walletTotalCostLine({ totalCost: totalCost.toFixed(6) }) ), form( { action: '/wallet/send', method: 'POST' }, input({ type: 'hidden', name: 'action', value: 'send' }), input({ type: 'hidden', name: 'destination', value: destination || '' }), - input({ type: 'hidden', name: 'amount', value: String(a) }), - input({ type: 'hidden', name: 'fee', value: String(f) }), + input({ type: 'hidden', name: 'amount', value: String(amountNum) }), + input({ type: 'hidden', name: 'fee', value: String(feeNum) }), + options.transfer ? input({ type: 'hidden', name: 'transferId', value: options.transfer.id }) : null, + options.createTransfer ? input({ type: 'hidden', name: 'createTransfer', value: '1' }) : null, + options.transfer ? p({ class: 'wallet-transfer-ctx' }, `${i18n.walletPayingTransfer}: ${options.transfer.concept || options.transfer.id}`) : null, + options.payment ? input({ type: 'hidden', name: 'payeeId', value: options.payment.payeeId }) : null, + options.payment ? input({ type: 'hidden', name: 'concept', value: options.payment.concept || '' }) : null, + options.payment ? input({ type: 'hidden', name: 'refHref', value: options.payment.href || '' }) : null, + options.payment ? input({ type: 'hidden', name: 'refTag', value: options.payment.tag || 'WALLET' }) : null, + options.payment ? div({ class: 'wallet-transfer-ctx' }, + div({ class: 'wallet-transfer-ctx-line' }, span({ class: 'wallet-transfer-ctx-label' }, `${i18n.walletPayeeLabel}: `), userLink(options.payment.payeeId)), + div({ class: 'wallet-transfer-ctx-line' }, span({ class: 'wallet-transfer-ctx-label' }, `${i18n.walletConceptLabel}: `), options.payment.concept || options.payment.href) + ) : null, div({ class: 'form-button-group-center' }, button({ type: 'submit' }, i18n.walletConfirm), a({ href: `/wallet/send`, class: "button-like-link" }, i18n.walletBack) @@ -143,20 +195,26 @@ exports.walletSendConfirmView = async (balance, destination, amount, fee) => { ); }; -exports.walletSendResultView = async (balance, destination, amount, txId) => { +exports.walletSendResultView = async (balance, destination, amount, txId, note = null) => { return walletViewRender( balance, destination, + "send", p( i18n.walletSentToLine({ destination: destination || '-', amount: Number(amount || 0).toFixed(6) }), br(), `${i18n.walletTransactionId}: `, - a({ href: `https://ecoin.03c8.net/blockexplorer/search?q=${encodeURIComponent(txId || '')}`, target: "_blank" }, txId || '-') - ) + span({ class: "bank-address-code" }, txId || '-') + ), + note ? p({ class: 'wallet-transfer-ctx' }, note) : null ); }; exports.walletErrorView = async (error) => { - const header = div({ class: 'tags-header' }, h2(i18n.walletTitle), p(i18n.walletDescription)); + const header = div({ class: 'tags-header module-header-line' }, h2(i18n.walletTitle), p(i18n.walletDescription), renderWalletChip()); + const w = (require('../configs/config-manager.js').getConfig() || {}).wallet || {}; + if (![w.url, w.user, w.pass].every(v => String(v || '').trim())) { + return template(i18n.walletTitle, section(header, div({ class: "wallet-error" }, h2(i18n.walletStatus), p(i18n.walletNotConfigured, ' ', a({ href: '/settings#wallet' }, i18n.walletConfiguration)), process.env.OASIS_MOBILE === '1' ? div({ class: "wallet-error-actions" }, a({ href: require('../backend/updater.js').ecoinWalletDownloadUrl(), class: "update-download-link" }, i18n.walletDownloadEcoin)) : null))); + } return template( i18n.walletTitle, section( diff --git a/src/views/welcome_view.js b/src/views/welcome_view.js index 8b573cd..cbb4c16 100644 --- a/src/views/welcome_view.js +++ b/src/views/welcome_view.js @@ -1,196 +1,140 @@ -const fs = require("fs") -const path = require("path") -const { form, button, div, h2, p, section, a, span, select, option, label, input, textarea, img, strong } = require("../server/node_modules/hyperaxe") -const { template, i18n } = require("./main_views") +const { form, button, div, h2, p, section, a, span, select, option, label, input, textarea, img, details, summary } = require("../server/node_modules/hyperaxe") +const { template, i18n, WIZARD_THEMES } = require("./main_views") +const { renderRestoreStatus } = require("./backup_view") +const { renderSuggestedPub } = require("./invites_view") const { config } = require("../server/SSB_server.js") +const { WIZARD_WORKFLOWS } = require("../models/workflows_model") const FEED_TEXT_MIN = Number(config?.feed?.minLength ?? 1) const FEED_TEXT_MAX = Number(config?.feed?.maxLength ?? 280) -const LANGUAGES = [ - ["English", "en"], - ["Español", "es"], - ["Français", "fr"], - ["Euskara", "eu"], - ["Deutsch", "de"], - ["Italiano", "it"], - ["Português", "pt"], - ["中文", "zh"], - ["العربية", "ar"], - ["हिन्दी", "hi"], - ["Русский", "ru"] -] - -const snhInvite = () => { - try { - const raw = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "configs", "snh-invite-code.json"), "utf8")) - if (!raw || !raw.code) return null - return { name: String(raw.name || "").trim(), code: String(raw.code) } - } catch (_) { - return null - } +const SECTIONS = { + profile: ["welcomeStepIdentity", "welcomeHintIdentity"], + ux: ["welcomeStepType", "welcomeHintType"], + workflow: ["welcomeStepUse", "welcomeHintUse"], + theme: ["welcomeStepLook", "welcomeHintLook"], + community: ["welcomeStepCommunity", "welcomeHintCommunity"], + greeting: ["welcomeStepIntro", "welcomeHintIntro"] } -const stepChip = (done) => span( - { class: done ? "welcome-chip welcome-chip-done" : "welcome-chip" }, - done ? `✓ ${i18n.welcomeStepDone || "done"}` : (i18n.welcomeStepPending || "pending") +const submitRow = (text) => div({ class: "welcome-action" }, button({ type: "submit" }, text)) + +const pickCard = (name, value, current, title, image) => label({ class: "welcome-ux-option" }, + input({ type: "radio", name, value, ...(value === current ? { checked: true } : {}) }), + img({ src: image, class: "welcome-ux-shot", alt: title }), + span({ class: "welcome-ux-label" }, title) ) -const linkAction = (href, label) => div({ class: "welcome-action" }, - a({ href, class: "filter-btn" }, label) -) - -const languageAction = (lang) => form({ method: "POST", action: "/language", class: "welcome-action welcome-language-form" }, - select({ name: "language" }, LANGUAGES.map(([label, code]) => option({ value: code, ...(code === lang ? { selected: true } : {}) }, label))), - button({ type: "submit", class: "filter-btn" }, i18n.setLanguage || "Set Language") -) - -const stepContent = (key, lang, profile) => { - if (key === "language") return { - title: i18n.welcomeStepLanguageTitle || "Choose your language", - text: i18n.welcomeStepLanguageText || "Oasis speaks different languages. You can change it whenever you want.", - action: languageAction(lang) - } - if (key === "profile") return { - title: i18n.welcomeStepProfileTitle || "Edit your profile", - text: i18n.welcomeStepProfileText || "Pick a name, add a description and set a picture so other inhabitants can recognise you.", - action: form({ method: "POST", action: "/welcome/profile", enctype: "multipart/form-data", class: "welcome-profile-form" }, - label(i18n.profileName || "Name"), - input({ type: "text", name: "name", maxlength: "80", value: profile.name || "", required: true }), - label(i18n.profileDescription || "Description"), - textarea({ name: "description", rows: "3", maxlength: "600" }, profile.description || ""), - label(i18n.profileImage || "Image"), - img({ class: "welcome-profile-avatar", src: profile.image ? `/image/256/${encodeURIComponent(profile.image)}` : "/assets/images/default-avatar.png" }), - input({ type: "file", name: "image", accept: "image/*" }), - div({ class: "welcome-action" }, - button({ type: "submit", class: "filter-btn" }, i18n.welcomeStepProfileAction || "Save Profile") - ) - ) - } - if (key === "federation") { - const invite = snhInvite() - return { - title: invite && invite.name ? `${i18n.welcomeJoinPub || "Join"} ${invite.name}` : (i18n.welcomeStepFederationTitle || "Join Main Network"), - text: i18n.welcomeStepFederationText || "Connect to our pub to meet other inhabitants and start replicating.", - action: div({ class: "welcome-action" }, - invite - ? form({ method: "POST", action: "/settings/invite/accept" }, - input({ type: "hidden", name: "invite", value: invite.code }), - button({ type: "submit", class: "filter-btn" }, i18n.welcomeJoinPubButton || "Join Pub") - ) - : a({ href: "/invites", class: "filter-btn" }, i18n.welcomeStepFederationAction || "Go to invites") - ) - } - } - if (key === "larp") return { - title: i18n.welcomeStepLarpTitle || "Join L.A.R.P.", - text: i18n.welcomeStepLarpText || "Join our live action role playing game.", - action: div({ class: "welcome-action" }, - form({ method: "POST", action: "/larp/join" }, - input({ type: "hidden", name: "house", value: "academia" }), - input({ type: "hidden", name: "returnTo", value: "/welcome" }), - button({ type: "submit", class: "filter-btn" }, i18n.welcomeStepLarpAction || "Join \"The Academy\"") - ) - ) - } +const stepAction = (key, profile, status) => { + if (key === "profile") return form({ method: "POST", action: "/welcome/profile", enctype: "multipart/form-data", class: "welcome-profile-form" }, + label(i18n.profileName), + input({ type: "text", name: "name", maxlength: "80", value: profile.name || "", placeholder: profile.name || String(profile.id || "").slice(1, 9) }), + label(i18n.profileImage), + profile.image ? img({ loading: "lazy", class: "welcome-profile-avatar", src: `/image/256/${encodeURIComponent(profile.image)}` }) : null, + input({ type: "file", name: "image", accept: "image/*" }), + submitRow(i18n.welcomeStepProfileAction) + ) if (key === "ux") { let cfg = {} try { cfg = require("../configs/config-manager.js").getConfig() || {} } catch (_) {} - const cur = cfg.ux?.current === "ainav" ? "ainav" : cfg.ux?.current === "chats" ? "chats" : "blocks" - const chatsOn = cfg.modules?.chatsMod === "on" - const aiOn = cfg.modules?.aiNavMod === "on" - const uxCard = (value, title, image) => label({ class: "welcome-ux-option" }, - input({ type: "radio", name: "ux", value, ...(value === "blocks" ? { checked: true } : {}) }), - img({ src: image, class: "welcome-ux-shot", alt: title }), - span({ class: "welcome-ux-label" }, title) - ) - return { - title: i18n.welcomeStepUxTitle || "Choose your interface", - text: i18n.welcomeStepUxText || "Pick how Oasis should look. You can change it later in Settings.", - action: form({ method: "POST", action: "/welcome/ux", class: "welcome-ux-form" }, - div({ class: "welcome-ux-grid" }, - uxCard("blocks", i18n.uxModeMenus || "Blocks", "/assets/images/ux-blocks.png"), - chatsOn ? uxCard("chats", i18n.chatsTitle || "Chats", "/assets/images/ux-chats.png") : null, - aiOn ? uxCard("ainav", i18n.uxModeAINav || "AI", "/assets/images/ux-ainav.png") : null - ), - div({ class: "welcome-action" }, - button({ type: "submit", class: "filter-btn" }, i18n.welcomeStepUxAction || "Use this view") - ) - ) - } - } - if (key === "backup") return { - title: i18n.welcomeStepBackupTitle || "Backup your ID", - text: i18n.welcomeStepBackupText || "Your identity is a key file on this device.", - extra: profile.id ? div({ class: "welcome-oasisid" }, a({ class: "user-link", href: `/author/${encodeURIComponent(profile.id)}` }, String(profile.id))) : null, - warning: i18n.welcomeStepBackupWarning || "IF YOU LOSE IT, NOBODY CAN RECOVER IT FOR YOU.", - action: linkAction("/legacy", i18n.welcomeStepBackupAction || "Backup!") - } - return { - title: i18n.welcomeStepGreetingTitle || "Send a \"Hello world!\"", - text: i18n.welcomeStepGreetingText || "Send a message so other inhabitants can discover you.", - action: form({ method: "POST", action: "/welcome/greeting", class: "welcome-greeting-form" }, - textarea({ - name: "text", - required: true, - minlength: String(FEED_TEXT_MIN), - maxlength: String(FEED_TEXT_MAX), - rows: 4, - cols: 50, - placeholder: i18n.feedPlaceholder - }), - div({ class: "welcome-action" }, - button({ type: "submit", class: "filter-btn" }, i18n.welcomeStepGreetingAction || "Send Feed") - ) + const cur = ["ainav", "chats", "feed", "phone"].includes(cfg.ux?.current) ? cfg.ux.current : "blocks" + return form({ method: "POST", action: "/welcome/ux", class: "welcome-ux-form" }, + div({ class: "welcome-ux-grid" }, + pickCard("ux", "blocks", cur, i18n.uxModeMenus, "/assets/images/ux-blocks.png"), + cfg.modules?.aiNavMod === "on" ? pickCard("ux", "ainav", cur, i18n.uxModeAINav, "/assets/images/ux-ainav.png") : null, + cfg.modules?.chatsMod === "on" ? pickCard("ux", "chats", cur, i18n.uxModeChats, "/assets/images/ux-chats.png") : null, + pickCard("ux", "feed", cur, i18n.uxModeFeed, "/assets/images/ux-feed.png"), + cfg.modules?.phoneMod !== "off" ? pickCard("ux", "phone", cur, i18n.phoneTitle, "/assets/images/ux-phone.png") : null + ), + submitRow(i18n.welcomeStepUxAction) ) } -} - -exports.welcomeView = async (status, currentLanguage, profile = {}) => { - const steps = (status && status.steps) || {} - const usable = (status && status.usable) || [] - const lang = String(currentLanguage || "en") - - return template( - i18n.welcomeTitle || "Welcome", - section( - div({ class: "tags-header" }, - h2(i18n.welcomeTitle || "Welcome"), - p(i18n.welcomeDescription || "A few things worth doing before you start.") + if (key === "workflow") { + const selected = WIZARD_WORKFLOWS.includes(status.workflow) ? status.workflow : "full" + return form({ method: "POST", action: "/welcome/workflow", class: "welcome-select-form" }, + select({ name: "workflow" }, + WIZARD_WORKFLOWS.map(k => option({ value: k, ...(k === selected ? { selected: true } : {}) }, k === "full" ? i18n.welcomeWorkflow_full : (i18n[`workflow_${k}`] || k))) ), - div({ class: "welcome-progress" }, - p(span({ class: "welcome-progress-label" }, `${i18n.welcomeProgress || "Completed"}: `), `${status.done}/${status.total}`), - status.pending - ? form({ method: "POST", action: "/welcome/dismiss" }, - button({ type: "submit", class: "filter-btn" }, i18n.welcomeSkip || "Skip for now") - ) - : null - ), - status.complete - ? div({ class: "welcome-complete" }, - h2(i18n.welcomeCompleteTitle || "All set."), - p(i18n.welcomeCompleteText || "Your node is ready. The network grows with the people you follow."), - div({ class: "welcome-action" }, a({ href: "/inhabitants", class: "filter-btn" }, i18n.welcomeFindPeople || "Find inhabitants")) - ) + submitRow(i18n.welcomeStepWorkflowAction) + ) + } + if (key === "theme") return form({ method: "POST", action: "/welcome/theme", class: "welcome-ux-form" }, + div({ class: "welcome-ux-grid" }, + WIZARD_THEMES.map(([value, name]) => pickCard("theme", value, status.theme, name, `/assets/images/theme-${value.toLowerCase()}.png`)) + ), + submitRow(i18n.welcomeStepThemeAction) + ) + if (key === "community") { + const pubs = Array.isArray(status.pubs) ? status.pubs : [] + const bootstrap = status.bootstrap + const canJoin = status.canJoin !== false + return form({ method: "POST", action: "/welcome/community", class: "welcome-community-form" }, + bootstrap && bootstrap.running ? p(i18n.welcomeBootstrapRunning) : null, + bootstrap ? renderRestoreStatus(bootstrap) : null, + pubs.length && !canJoin + ? p({ class: "invites-join-note" }, status.publicMode === true ? i18n.invitesJoinPubMode : i18n.invitesJoinLocalOnly) : null, - div({ class: "welcome-steps" }, - usable.map((key, idx) => { - const done = steps[key] === true - const content = stepContent(key, lang, profile || {}) - return div({ class: done ? "welcome-step welcome-step-done" : "welcome-step" }, - div({ class: "welcome-step-head" }, - span({ class: "welcome-step-number" }, String(idx + 1)), - h2({ class: "welcome-step-title" }, content.title), - stepChip(done) - ), - content.text ? p({ class: "welcome-step-text" }, content.text) : null, - content.extra || null, - content.warning ? p({ class: "welcome-step-warning" }, strong(content.warning)) : null, - content.action, - content.note ? p({ class: "welcome-step-note" }, content.note) : null - ) - }) + ...pubs.map(entry => label({ class: "welcome-pick" }, + canJoin && !entry.exhausted ? input({ type: "checkbox", name: "pub", value: entry.code }) : null, + renderSuggestedPub(entry, false) + )), + !pubs.length ? div({ class: "welcome-action" }, a({ href: "/invites", class: "filter-btn" }, i18n.invites)) : null, + div({ class: "welcome-action" }, + canJoin && pubs.some(e => !e.exhausted) ? button({ type: "submit", name: "action", value: "join" }, i18n.welcomeJoinPubs) : null, + button({ type: "submit", name: "action", value: "skip" }, i18n.welcomeNoFederation) ) ) + } + return form({ method: "POST", action: "/welcome/greeting", class: "welcome-greeting-form" }, + textarea({ + name: "text", + required: true, + minlength: String(FEED_TEXT_MIN), + maxlength: String(FEED_TEXT_MAX), + rows: 4, + cols: 50, + placeholder: i18n.welcomePmSubject + }), + submitRow(i18n.welcomeStepGreetingAction) ) } + +exports.welcomeView = async (status, profile = {}) => { + const steps = status.steps || {} + const usable = status.usable || [] + const opened = usable.find(key => !steps[key]) + const html = template( + i18n.welcomeTitle, + section( + div({ class: "tags-header module-header-line" }, + h2(i18n.welcomeTitle), + p(i18n.welcomeDescription) + ), + status.pending + ? form({ method: "POST", action: "/welcome/dismiss", class: "welcome-action" }, + input({ type: "hidden", name: "returnTo", value: "/" }), + button({ type: "submit" }, i18n.welcomeSkip) + ) + : null + ), + status.complete + ? section( + div({ class: "welcome-complete" }, + h2(i18n.welcomeCompleteTitle), + p(i18n.welcomeCompleteText), + div({ class: "welcome-action" }, a({ href: "/inhabitants", class: "filter-btn" }, i18n.welcomeFindPeople)) + ) + ) + : null, + ...usable.map(key => section( + details({ id: `step-${key}`, class: "welcome-step", ...(key === opened ? { open: true } : {}) }, + summary( + h2(`${steps[key] ? "✓ " : ""}${i18n[SECTIONS[key][0]]}`), + span({ class: "welcome-step-text" }, i18n[SECTIONS[key][1]]) + ), + stepAction(key, profile || {}, status) + ) + )) + ) + return status.bootstrap && status.bootstrap.running ? html.replace('', '') : html +} diff --git a/src/views/wiki_view.js b/src/views/wiki_view.js new file mode 100644 index 0000000..50e8dbe --- /dev/null +++ b/src/views/wiki_view.js @@ -0,0 +1,383 @@ +const { div, h2, h3, p, section, button, form, a, span, br, textarea, input, label, select, option, ul, li, img } = require("../server/node_modules/hyperaxe"); +const { clearnetItemHref, template, i18n, userLink, renderStateChip, renderContentActions, renderSubscriptionBox, renderModuleStats, moduleIsEmpty, renderCardMetaRow, paged } = require("./main_views"); +const { renderEncryptedChip, renderReachChip, renderClearnetSelector, renderClearnetSwitch, renderLicenseChip, renderLicenseSelect, renderTribeOriginChip } = require("./clearnet_view"); +const { renderStyledText, richTextarea } = require("../backend/renderStyledText"); +const { WIKILINK_RE, slugify, linkTarget } = require("../models/wiki_model"); +const moment = require("../server/node_modules/moment"); +const { config } = require("../server/SSB_server.js"); + +const userId = config.keys.id; +const FILTERS = ["all", "recent", "mine", "linked"]; + +const pageHref = (idOrSlug, tribeId = null) => + `/wiki/${encodeURIComponent(idOrSlug)}${tribeId ? `?tribeId=${encodeURIComponent(tribeId)}` : ""}`; + +const listHref = (filter, q, tribeId = null) => { + const parts = [`filter=${encodeURIComponent(filter || "all")}`]; + if (q) parts.push(`q=${encodeURIComponent(q)}`); + if (tribeId) parts.push(`tribeId=${encodeURIComponent(tribeId)}`); + return `/wiki?${parts.join("&")}`; +}; + +const renderWikiBody = (body, opts = {}) => { + const missing = new Set(opts.missing || []); + const tribeId = opts.tribeId || null; + const wikiLink = (target, slug, label) => { + const isMissing = missing.has(slug); + return a({ + href: isMissing ? `/wiki?filter=create&title=${encodeURIComponent(target)}${tribeId ? `&tribeId=${encodeURIComponent(tribeId)}` : ""}` : pageHref(slug, tribeId), + class: isMissing ? "wiki-link wiki-link-missing" : "wiki-link", + title: isMissing ? (i18n.wikiMissingLink || "Page does not exist yet") : label + }, label); + }; + const paragraphs = String(body || "").replace(/\r\n/g, "\n").split(/\n{2,}/); + return div({ class: "wiki-body" }, + ...paragraphs.filter(par => par.trim()).map(par => + p({ class: "wiki-paragraph" }, ...renderStyledText(par, { wikiLink, zoomImages: true })) + ) + ); +}; + +const diffLines = (oldText, newText) => { + const a = String(oldText || "").replace(/\r\n/g, "\n").split("\n"); + const b = String(newText || "").replace(/\r\n/g, "\n").split("\n"); + const n = a.length, m = b.length; + const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + for (let i = n - 1; i >= 0; i--) { + for (let j = m - 1; j >= 0; j--) { + lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]); + } + } + const out = []; + let i = 0, j = 0; + while (i < n && j < m) { + if (a[i] === b[j]) { out.push({ type: "ctx", text: a[i] }); i++; j++; } + else if (lcs[i + 1][j] >= lcs[i][j + 1]) { out.push({ type: "del", text: a[i] }); i++; } + else { out.push({ type: "add", text: b[j] }); j++; } + } + while (i < n) { out.push({ type: "del", text: a[i++] }); } + while (j < m) { out.push({ type: "add", text: b[j++] }); } + return out; +}; + +const renderDiff = (oldText, newText) => + div({ class: "wiki-diff" }, + ...diffLines(oldText, newText).map(l => + div({ class: `wiki-diff-line wiki-diff-${l.type}` }, span({ class: "wiki-diff-sign" }, l.type === "add" ? "+" : l.type === "del" ? "−" : " "), span({ class: "wiki-diff-text" }, l.text || " ")) + ) + ); + +const reachChip = (page) => + page.tribeId || page.encrypted + ? null + : renderReachChip(!!page.clearnet, i18n, page.clearnet ? clearnetItemHref('wiki', page.title, page.id) : null); + +const statusChip = (page) => + renderStateChip(page.editPolicy === "author" ? "closed" : "open", "", page.editPolicy === "author" ? i18n.wikiStatusClosed : i18n.wikiStatusOpen); + +const pageChips = (page, params = {}, { withStatus = true } = {}) => [ + page.encrypted ? renderEncryptedChip(i18n) : null, + withStatus ? statusChip(page) : null, + page.versionCount > 1 ? renderStateChip("neutral", "", `${page.versionCount} ${i18n.wikiVersions || "versions"}`) : null, + page.isLinked ? renderStateChip("open", "", i18n.wikiLinkedChip || "Linked") : null +].filter(Boolean); + +const renderTags = (tags) => (Array.isArray(tags) && tags.length) + ? div({ class: "card-tags" }, ...tags.map(t => a({ href: `/search?query=%23${encodeURIComponent(t)}`, class: "tag-link" }, `#${t}`))) + : null; + +const renderPageCard = (page, params = {}) => { + const origin = page.tribeOrigin || null; + const href = origin ? origin.href : pageHref(page.tribeId ? page.id : page.slug, page.tribeId); + return div({ class: "tribe-card wiki-card" }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(page.id, href, origin + ? { author: page.author, reportTitle: page.title } + : { author: page.author, reportTitle: page.title, favKind: 'wiki', isFavorite: page.isFavorite, spread: (params.spreadMap && params.spreadMap.get(page.id)) || null, deleteAction: !page.tribeId && String(page.author) === String(userId) ? `/wiki/delete/${encodeURIComponent(page.id)}` : undefined }) + ), + div({ class: "tribe-card-body" }, + page.image ? a({ href }, img({ loading: 'lazy', class: "wiki-card-cover", src: `/blob/${encodeURIComponent(page.image)}`, alt: page.title || "" })) : null, + div({ class: "shop-title-row" }, h2({ class: "tribe-card-title" }, a({ href }, page.title || "—"))), + div({ class: "card-chips-row" }, ...(origin ? [renderTribeOriginChip(origin)] : pageChips(page)), !origin && page.clearnet === true ? reachChip(page) : null, renderLicenseChip(page.license)), + origin && params.withBody && page.body ? renderWikiBody(page.body) : null + ) + ); +}; + +const renderFilters = (filter, q, params = {}, census = []) => { + const tribeId = params.tribeId || null; + const chipVisible = (mode) => { + if (mode === filter || mode === "all") return true; + if (mode === "mine") return census.some(p => String(p.author) === String(userId) || (p.versions || []).some(v => String(v.author) === String(userId))); + if (mode === "recent") return census.length > 0; + if (mode === "linked") return census.some(p => p.isLinked); + return true; + }; + const labelOf = (mode) => String(i18n[`wikiFilter${mode.charAt(0).toUpperCase() + mode.slice(1)}`] || mode).toUpperCase(); + return div({ class: "filters" }, + form({ method: "GET", action: "/wiki", class: "ui-toolbar ui-toolbar--filters" }, + input({ type: "hidden", name: "q", value: q || "" }), + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + ...(params.emptyMod ? [] : [ + ...FILTERS.filter(chipVisible).map(mode => button({ type: "submit", name: "filter", value: mode, class: filter === mode ? "filter-btn active" : "filter-btn" }, labelOf(mode))), + ...((census.length > 0 || filter === "changes") ? [button({ type: "submit", name: "filter", value: "changes", class: filter === "changes" ? "filter-btn active" : "filter-btn" }, String(i18n.wikiChanges || "Recent changes").toUpperCase())] : []) + ]), + button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.wikiCreate) + ) + ); +}; + +const renderForm = (page, params = {}) => { + const tribeId = (page && page.tribeId) || params.tribeId || ""; + const draft = params.draft || null; + const status = draft ? (String(draft.status || "OPEN").toUpperCase() === "CLOSED" ? "CLOSED" : "OPEN") : (page && page.editPolicy === "author" ? "CLOSED" : "OPEN"); + const titleValue = draft ? draft.title : (page ? page.title : (params.title || "")); + const bodyValue = draft ? draft.body : (page ? page.body : ""); + const tagsValue = draft ? draft.tags : (page ? page.tags.join(", ") : ""); + const summaryValue = draft ? draft.summary : ""; + const licenseValue = draft && draft.license ? draft.license : (page ? page.license : ""); + const canLicense = !page || String(page.author) === String(userId); + const statusOptions = [ + { value: "OPEN", label: i18n.wikiStatusOpen }, + { value: "CLOSED", label: i18n.wikiStatusClosed } + ]; + return div({ class: "div-center audio-form" }, + h2(page ? i18n.wikiUpdateSectionTitle : i18n.wikiCreateSectionTitle), + draft + ? section({ class: "post-preview wiki-preview" }, + div({ class: "preview-content" }, + h2(i18n.messagePreview), + h3({ class: "wiki-page-title" }, titleValue || "—"), + renderWikiBody(bodyValue, { missing: [], tribeId: tribeId || null }) + ) + ) + : null, + form({ method: "POST", action: page ? `/wiki/update/${encodeURIComponent(page.id)}` : "/wiki/create", enctype: "multipart/form-data" }, + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + page ? input({ type: "hidden", name: "pageId", value: page.id }) : null, + params.returnTo ? input({ type: "hidden", name: "returnTo", value: params.returnTo }) : null, + label(i18n.wikiTitleLabel), br(), + input({ type: "text", name: "title", maxlength: "100", required: true, placeholder: i18n.wikiTitlePlaceholder, value: titleValue }), br(), + label(i18n.wikiBodyLabel), br(), + richTextarea({ name: "body", rows: 14, maxlength: "20000", placeholder: i18n.wikiBodyPlaceholder }, bodyValue), br(), + label(i18n.uploadMedia), br(), + input({ type: "file", name: "blob", accept: "image/*,video/*,audio/*,application/pdf,.torrent" }), br(), br(), + page ? [label(i18n.wikiSummaryLabel), br(), input({ type: "text", name: "summary", maxlength: "200", placeholder: i18n.wikiSummaryPlaceholder, value: summaryValue }), br()] : null, + page + ? null + : [label(i18n.wikiStatusLabel), br(), select({ name: "status" }, ...statusOptions.map(o => option({ value: o.value, ...(o.value === status ? { selected: true } : {}) }, o.label))), br(), br()], + label(i18n.wikiTagsLabel), br(), + input({ type: "text", name: "tags", maxlength: "200", placeholder: i18n.wikiTagsPlaceholder, value: tagsValue }), br(), + canLicense ? renderLicenseSelect(licenseValue, i18n) : null, + !tribeId && canLicense ? renderClearnetSelector(draft ? draft.clearnet === true : !!(page && page.clearnet), i18n) : null, + br(), + button({ type: "submit", class: "filter-btn", formaction: "/wiki/preview", attrs: { formmethod: "POST" } }, i18n.wikiPreview), + " ", + button({ type: "submit", class: "create-button" }, page ? i18n.wikiUpdate : i18n.wikiCreate) + ) + ); +}; + +const renderHeader = (tribe = null) => + div({ class: "tags-header module-header-line" }, + h2(tribe ? `${i18n.wikiTitle} · ${tribe.title}` : i18n.wikiTitle), + p(i18n.wikiDescription) + ); + +exports.wikiView = async (pages, filter = "all", params = {}) => { + const list = Array.isArray(pages) ? pages : []; + const q = String(params.q || "").trim(); + const census = Array.isArray(params.censusList) ? params.censusList : list; + const isForm = filter === "create" || filter === "edit"; + const emptyMod = !isForm && census.length === 0 && !q; + return template( + i18n.wikiTitle, + section( + div({ class: "tags-header module-header-line" }, + h2(params.tribe ? `${i18n.wikiTitle} · ${params.tribe.title}` : i18n.wikiTitle), + p(i18n.wikiDescription) + ), + renderFilters(isForm ? "all" : filter, q, { ...params, emptyMod }, census) + ), + section( + isForm + ? renderForm(filter === "edit" ? params.page : null, params) + : [ + emptyMod ? null : div({ class: "filters activity-filter-chips activity-toolbar-row" }, + renderModuleStats(list.length), + form({ method: "GET", action: "/wiki", class: "filter-box" }, + input({ type: "hidden", name: "filter", value: filter }), + params.tribeId ? input({ type: "hidden", name: "tribeId", value: params.tribeId }) : null, + input({ type: "text", name: "q", value: q, placeholder: i18n.wikiSearchPlaceholder, class: "filter-box__input" }), + div({ class: "filter-box__controls" }, button({ type: "submit", class: "filter-box__button" }, i18n.searchButton)) + ) + ), + div({ class: "tribe-grid" }, + list.length ? paged(list).map(pg => renderPageCard(pg, params)) : p(i18n.wikiNoPages) + ) + ] + ) + ); +}; + +exports.wikiChangesView = async (changes, params = {}) => { + const list = Array.isArray(changes) ? changes : []; + const census = Array.isArray(params.censusList) ? params.censusList : []; + return template( + i18n.wikiTitle, + section(renderHeader(params.tribe || null), renderFilters("changes", "", { ...params, emptyMod: census.length === 0 }, census)), + section( + div({ class: "tags-header" }, h2(i18n.wikiChanges)), + list.length + ? div({ class: "wiki-changes" }, ...paged(list).map(c => + div({ class: "card-section wiki-change" }, + div({ class: "card-field" }, + span({ class: "card-label" }, `#${c.index}`), + span({ class: "card-value" }, a({ href: `${pageHref(c.pageId, c.tribeId)}${c.tribeId ? "&" : "?"}version=${encodeURIComponent(c.versionKey)}` }, c.title || c.slug)) + ), + c.summary ? p({ class: "wiki-change-summary" }, c.summary.startsWith("restore:") ? i18n.wikiRestoredSummary : c.summary) : null, + p({ class: "card-footer" }, span({ class: "date-link" }, `${moment(c.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(c.author)) + ) + )) + : div({ class: "no-content-box" }, p({ class: "no-content" }, i18n.wikiNoChanges)) + ) + ); +}; + +const versionRow = (page, v, idx, current, tribeId) => { + const isCurrent = v.key === page.tipId; + const base = pageHref(page.tribeId ? page.id : page.slug, tribeId); + const sep = tribeId ? "&" : "?"; + return div({ class: "card-section wiki-version" + (isCurrent ? " wiki-version-current" : "") }, + div({ class: "card-field" }, + span({ class: "card-label" }, `${i18n.wikiVersion} ${idx + 1}`), + isCurrent ? span({ class: "card-value" }, renderStateChip("mutuals", "", i18n.wikiCurrentVersion)) : span({ class: "card-value" }) + ), + v.summary ? [br(), p({ class: "wiki-change-summary" }, v.summary.startsWith("restore:") ? i18n.wikiRestoredSummary : v.summary), br()] : null, + div({ class: "tribe-side-actions wiki-version-actions" }, + form({ method: "GET", action: base }, + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + input({ type: "hidden", name: "version", value: v.key }), + button({ type: "submit", class: "update-btn" }, i18n.wikiViewVersion)), + idx > 0 + ? form({ method: "GET", action: base }, + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + input({ type: "hidden", name: "diff", value: v.key }), + button({ type: "submit", class: "update-btn" }, i18n.wikiDiff)) + : null, + !isCurrent && page.canEdit + ? form({ method: "POST", action: `/wiki/restore/${encodeURIComponent(page.id)}` }, + input({ type: "hidden", name: "version", value: v.key }), + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + button({ type: "submit", class: "update-btn" }, i18n.wikiRestore)) + : null + ), + p({ class: "card-footer" }, span({ class: "date-link" }, `${moment(v.createdAt).format("YYYY/MM/DD HH:mm")}`), userLink(v.author)) + ); +}; + +exports.wikiHistoryView = async (page, params = {}) => { + const tribeId = page.tribeId || null; + const versions = page.versions.slice().reverse(); + const census = Array.isArray(params.censusList) ? params.censusList : []; + return template( + page.title, + section(renderHeader(params.tribe || null), renderFilters("all", "", { ...params, emptyMod: census.length === 0 }, census)), + section( + div({ class: "tags-header" }, h2(i18n.wikiHistory)), + div({ class: "wiki-history" }, ...versions.map((v, i) => versionRow(page, v, page.versions.length - 1 - i, page.tipId, tribeId))) + ) + ); +}; + +exports.wikiPageView = async (page, params = {}) => { + const tribeId = page.tribeId || null; + const base = pageHref(tribeId ? page.id : page.slug, tribeId); + const sep = tribeId ? "&" : "?"; + const version = params.version || null; + const diffVersion = params.diff || null; + const shownTitle = version ? version.title : page.title; + const shownBody = version ? version.body : page.body; + const census = Array.isArray(params.censusList) ? params.censusList : []; + const subscriptionNodes = params.subscription + ? renderSubscriptionBox({ target: page.id, scope: "wiki", subscribed: params.subscription.subscribed, count: params.subscription.count, isOwner: page.isOwner, returnTo: base, inline: true, compact: true }) + : null; + const barChips = [reachChip(page), statusChip(page)].filter(Boolean); + const actions = div({ class: "tribe-side-actions wiki-actions-top" }, + span({ class: "wiki-actions-left" }, ...barChips), + page.isOwner && !tribeId && !page.encrypted ? renderClearnetSwitch("wiki", page.rootId || page.id, !!page.clearnet) : null, + ...(subscriptionNodes && subscriptionNodes.length ? subscriptionNodes : []), + page.canEdit && !version + ? form({ method: "GET", action: "/wiki" }, + input({ type: "hidden", name: "filter", value: "edit" }), + input({ type: "hidden", name: "id", value: page.id }), + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + button({ type: "submit", class: "update-btn" }, i18n.wikiEdit)) + : null, + form({ method: "GET", action: `/wiki/${encodeURIComponent(page.id)}/history` }, + tribeId ? input({ type: "hidden", name: "tribeId", value: tribeId }) : null, + button({ type: "submit", class: "update-btn" }, `${i18n.wikiHistory} (${page.versionCount})`)), + form({ method: "GET", action: `/wiki/${encodeURIComponent(page.id)}/pdf` }, + button({ type: "submit", class: "update-btn" }, i18n.generatePdf)), + page.isOwner && tribeId + ? form({ method: "POST", action: `/wiki/delete/${encodeURIComponent(page.id)}` }, input({ type: "hidden", name: "tribeId", value: tribeId }), button({ type: "submit", class: "delete-btn" }, i18n.wikiDelete)) + : null + ); + let diffBlock = null; + if (diffVersion) { + const idx = page.versions.findIndex(v => v.key === diffVersion.key); + const prev = idx > 0 ? page.versions[idx - 1] : null; + diffBlock = div({ class: "card-section" }, + h3(`${i18n.wikiDiff}: ${i18n.wikiVersion} ${idx} → ${idx + 1}`), + renderDiff(prev ? prev.body : "", diffVersion.body) + ); + } + return template( + page.title, + section(renderHeader(params.tribe || null), renderFilters("all", "", { ...params, emptyMod: census.length === 0 }, census)), + section( + div({ class: "trending-card wiki-page" }, + div({ class: "card-header activity-card-header" }, + span(), + renderContentActions(page.id, base, { author: page.author, reportTitle: page.title, favKind: 'wiki', isFavorite: page.isFavorite, spread: params.spread || null, deleteAction: page.isOwner && !tribeId ? `/wiki/delete/${encodeURIComponent(page.id)}` : undefined }) + ), + div({ class: "card-section" }, + actions, + div({ class: "shop-title-row" }, h2({ class: "wiki-page-title" }, shownTitle), renderLicenseChip(page.license)), + (() => { const rest = pageChips(page, params, { withStatus: false }); return rest.length ? div({ class: "card-chips-row" }, ...rest) : null; })(), + version ? p({ class: "wiki-version-notice" }, `${i18n.wikiViewingVersion} (${moment(version.createdAt).format("YYYY/MM/DD HH:mm")}) — `, a({ href: base }, i18n.wikiBackToPage)) : null, + diffBlock || renderWikiBody(shownBody, { missing: page.missingLinks, tribeId }), + renderTags(page.tags), + renderCardMetaRow(p({ class: "card-footer" }, + span({ class: "date-link" }, `${moment(page.createdAt).format("YYYY/MM/DD HH:mm")}`), + userLink(page.author) + )) + ), + ), + div({ class: "card-section wiki-backlinks" }, + h3(i18n.wikiLinkedPages), + page.linkedPages && page.linkedPages.length + ? ul(...page.linkedPages.map(b => li(a({ href: pageHref(b.tribeId ? b.id : b.slug, b.tribeId) }, b.title)))) + : p(i18n.wikiNoLinkedPages) + ) + ) + ); +}; + +exports.renderTribeWikiSection = (tribe, pages, toolbar = null, wrap = (pg, card) => card) => { + const items = Array.isArray(pages) ? pages : []; + const createBtn = form({ method: "GET", action: "/wiki" }, + input({ type: "hidden", name: "filter", value: "create" }), + input({ type: "hidden", name: "tribeId", value: tribe.id }), + button({ type: "submit", class: "create-button" }, i18n.tribeWikiCreate)); + const header = toolbar || div({ class: "tribe-content-header" }, h2(i18n.tribeSectionWiki), createBtn); + if (!items.length) return div({ class: "tribe-content-list" }, header, div({ class: "no-content-box" }, p(i18n.tribeWikiEmpty))); + return div({ class: "tribe-content-list" }, + header, + div({ class: "tribe-grid" }, ...paged(items).map(pg => wrap(pg, renderPageCard(pg, { tribeId: tribe.id, withBody: true })))) + ); +}; + +exports.renderWikiBody = renderWikiBody; +exports.diffLines = diffLines;